From e2e37cced861b98de8c1a7c9c0d3a50d2d90e433 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Wed, 22 May 2024 21:25:21 +0200 Subject: Fix line endlings --- indra/newview/llagent.cpp | 10432 ++++---- indra/newview/llagent.h | 2008 +- indra/newview/llagentcamera.cpp | 5914 ++--- indra/newview/llagentcamera.h | 836 +- indra/newview/llagentlanguage.cpp | 142 +- indra/newview/llagentlistener.cpp | 1042 +- indra/newview/llagentpicksinfo.h | 196 +- indra/newview/llagentpilot.cpp | 814 +- indra/newview/llagentpilot.h | 244 +- indra/newview/llagentui.cpp | 388 +- indra/newview/llagentui.h | 118 +- indra/newview/llagentwearables.cpp | 3312 +-- indra/newview/llagentwearables.h | 522 +- indra/newview/llaisapi.cpp | 3550 +-- indra/newview/llappearancemgr.cpp | 9552 ++++---- indra/newview/llappviewer.cpp | 11382 ++++----- indra/newview/llappviewer.h | 820 +- indra/newview/llappviewerlinux_api_dbus.cpp | 252 +- indra/newview/llappviewermacosx.cpp | 866 +- indra/newview/llappviewerwin32.cpp | 2014 +- indra/newview/llattachmentsmgr.cpp | 1094 +- indra/newview/llattachmentsmgr.h | 264 +- indra/newview/llavataractions.cpp | 3052 +-- indra/newview/llavatarlist.cpp | 1180 +- indra/newview/llavatarlist.h | 378 +- indra/newview/llavatarlistitem.cpp | 1402 +- indra/newview/llavatarlistitem.h | 484 +- indra/newview/llavatarpropertiesprocessor.cpp | 1442 +- indra/newview/llblockedlistitem.cpp | 228 +- indra/newview/llblockedlistitem.h | 146 +- indra/newview/llblocklist.cpp | 926 +- indra/newview/llblocklist.h | 318 +- indra/newview/llbuycurrencyhtml.cpp | 334 +- indra/newview/llcallbacklist.cpp | 610 +- indra/newview/llcallingcard.cpp | 1850 +- indra/newview/llcallingcard.h | 540 +- indra/newview/llchannelmanager.cpp | 540 +- indra/newview/llchatbar.cpp | 1306 +- indra/newview/llchatbar.h | 224 +- indra/newview/llchathistory.cpp | 3096 +-- indra/newview/llchatitemscontainerctrl.cpp | 822 +- indra/newview/llchatitemscontainerctrl.h | 200 +- indra/newview/llchiclet.cpp | 2458 +- indra/newview/llchiclet.h | 1810 +- indra/newview/llchicletbar.cpp | 474 +- indra/newview/llchicletbar.h | 180 +- indra/newview/llcofwearables.cpp | 1560 +- indra/newview/llcofwearables.h | 276 +- indra/newview/llcolorswatch.cpp | 752 +- indra/newview/llcolorswatch.h | 242 +- indra/newview/llcommandlineparser.cpp | 1510 +- indra/newview/llcompilequeue.cpp | 1652 +- indra/newview/llcompilequeue.h | 400 +- indra/newview/llcontrolavatar.cpp | 1438 +- indra/newview/llcontrolavatar.h | 248 +- indra/newview/llconversationlog.cpp | 1306 +- indra/newview/llconversationlog.h | 452 +- indra/newview/llconversationloglist.cpp | 1086 +- indra/newview/llconversationloglist.h | 306 +- indra/newview/llconversationloglistitem.cpp | 368 +- indra/newview/llconversationloglistitem.h | 172 +- indra/newview/llconversationmodel.cpp | 1530 +- indra/newview/llconversationmodel.h | 652 +- indra/newview/llconversationview.cpp | 1772 +- indra/newview/llconversationview.h | 378 +- indra/newview/llcurrencyuimanager.cpp | 1266 +- indra/newview/lldebugmessagebox.cpp | 582 +- indra/newview/lldebugmessagebox.h | 176 +- indra/newview/lldebugview.cpp | 268 +- indra/newview/lldebugview.h | 138 +- indra/newview/lldirpicker.cpp | 778 +- indra/newview/lldirpicker.h | 250 +- indra/newview/lldndbutton.cpp | 94 +- indra/newview/lldndbutton.h | 160 +- .../newview/lldonotdisturbnotificationstorage.cpp | 690 +- indra/newview/lldonotdisturbnotificationstorage.h | 160 +- indra/newview/lldrawable.cpp | 3590 +-- indra/newview/lldrawable.h | 690 +- indra/newview/lldrawpool.cpp | 1702 +- indra/newview/lldrawpool.h | 966 +- indra/newview/lldrawpoolalpha.cpp | 1870 +- indra/newview/lldrawpoolalpha.h | 202 +- indra/newview/lldrawpoolavatar.cpp | 1724 +- indra/newview/lldrawpoolavatar.h | 280 +- indra/newview/lldrawpoolbump.cpp | 2608 +- indra/newview/lldrawpoolbump.h | 326 +- indra/newview/lldrawpoolmaterials.cpp | 598 +- indra/newview/lldrawpooltree.cpp | 328 +- indra/newview/lldrawpooltree.h | 132 +- indra/newview/lldrawpoolwater.cpp | 722 +- indra/newview/lldrawpoolwater.h | 166 +- indra/newview/lldrawpoolwlsky.cpp | 962 +- indra/newview/lldrawpoolwlsky.h | 152 +- indra/newview/lldynamictexture.cpp | 558 +- indra/newview/lldynamictexture.h | 204 +- indra/newview/llemote.cpp | 286 +- indra/newview/llemote.h | 244 +- indra/newview/llenvironment.cpp | 7444 +++--- indra/newview/lleventnotifier.cpp | 626 +- indra/newview/lleventnotifier.h | 174 +- indra/newview/llexpandabletextbox.cpp | 936 +- indra/newview/llexpandabletextbox.h | 438 +- indra/newview/llexternaleditor.cpp | 406 +- indra/newview/llface.cpp | 4904 ++-- indra/newview/llface.h | 760 +- indra/newview/llfasttimerview.cpp | 3334 +-- indra/newview/llfasttimerview.h | 302 +- indra/newview/llfavoritesbar.cpp | 4536 ++-- indra/newview/llfavoritesbar.h | 594 +- indra/newview/llfeaturemanager.cpp | 1510 +- indra/newview/llfeaturemanager.h | 376 +- indra/newview/llfilepicker.cpp | 3378 +-- indra/newview/llfilepicker.h | 428 +- indra/newview/llfirstuse.cpp | 352 +- indra/newview/llflexibleobject.cpp | 1878 +- indra/newview/llflexibleobject.h | 312 +- indra/newview/llfloater360capture.cpp | 2 +- indra/newview/llfloaterabout.cpp | 888 +- indra/newview/llfloateraddpaymentmethod.cpp | 162 +- indra/newview/llfloateraddpaymentmethod.h | 104 +- indra/newview/llfloaterauction.cpp | 1118 +- indra/newview/llfloaterauction.h | 172 +- indra/newview/llfloaterautoreplacesettings.cpp | 1324 +- indra/newview/llfloaterautoreplacesettings.h | 236 +- indra/newview/llfloateravatar.cpp | 132 +- indra/newview/llfloateravatar.h | 92 +- indra/newview/llfloateravatarpicker.cpp | 1628 +- indra/newview/llfloateravatarpicker.h | 226 +- indra/newview/llfloateravatarrendersettings.cpp | 572 +- indra/newview/llfloateravatartextures.cpp | 406 +- indra/newview/llfloateravatartextures.h | 114 +- indra/newview/llfloaterbeacons.cpp | 262 +- indra/newview/llfloaterbeacons.h | 98 +- indra/newview/llfloaterbigpreview.cpp | 220 +- indra/newview/llfloaterbigpreview.h | 108 +- indra/newview/llfloaterbuildoptions.cpp | 134 +- indra/newview/llfloaterbuildoptions.h | 118 +- indra/newview/llfloaterbulkpermission.cpp | 834 +- indra/newview/llfloaterbulkpermission.h | 240 +- indra/newview/llfloaterbump.cpp | 548 +- indra/newview/llfloaterbump.h | 158 +- indra/newview/llfloaterbuy.cpp | 688 +- indra/newview/llfloaterbuy.h | 156 +- indra/newview/llfloaterbuycontents.cpp | 602 +- indra/newview/llfloaterbuycontents.h | 134 +- indra/newview/llfloaterbuycurrency.cpp | 724 +- indra/newview/llfloaterbuycurrencyhtml.cpp | 246 +- indra/newview/llfloaterbuycurrencyhtml.h | 118 +- indra/newview/llfloaterbuyland.cpp | 2758 +-- indra/newview/llfloaterbvhpreview.cpp | 2390 +- indra/newview/llfloaterbvhpreview.h | 266 +- indra/newview/llfloatercamera.cpp | 1476 +- indra/newview/llfloatercamera.h | 356 +- indra/newview/llfloatercamerapresets.cpp | 322 +- indra/newview/llfloaterchangeitemthumbnail.h | 284 +- indra/newview/llfloatercolorpicker.cpp | 2180 +- indra/newview/llfloatercolorpicker.h | 404 +- indra/newview/llfloaterconversationlog.cpp | 268 +- indra/newview/llfloaterconversationlog.h | 112 +- indra/newview/llfloaterconversationpreview.cpp | 548 +- indra/newview/llfloaterconversationpreview.h | 144 +- indra/newview/llfloatercreatelandmark.cpp | 890 +- indra/newview/llfloatercreatelandmark.h | 154 +- indra/newview/llfloaterdeleteprefpreset.cpp | 234 +- indra/newview/llfloaterdeleteprefpreset.h | 106 +- indra/newview/llfloaterdestinations.cpp | 108 +- indra/newview/llfloaterdestinations.h | 86 +- indra/newview/llfloaterdisplayname.cpp | 484 +- indra/newview/llfloatereditextdaycycle.cpp | 3708 +-- indra/newview/llfloatereditextdaycycle.h | 488 +- indra/newview/llfloateremojipicker.h | 246 +- indra/newview/llfloaterevent.cpp | 238 +- indra/newview/llfloaterevent.h | 124 +- indra/newview/llfloaterexperiencepicker.cpp | 216 +- indra/newview/llfloaterexperiencepicker.h | 134 +- indra/newview/llfloaterexperienceprofile.cpp | 1868 +- indra/newview/llfloaterexperienceprofile.h | 226 +- indra/newview/llfloaterexperiences.cpp | 834 +- indra/newview/llfloaterexperiences.h | 152 +- indra/newview/llfloaterfixedenvironment.h | 276 +- indra/newview/llfloatergesture.cpp | 1426 +- indra/newview/llfloatergesture.h | 220 +- indra/newview/llfloatergodtools.cpp | 2740 +-- indra/newview/llfloatergodtools.h | 546 +- indra/newview/llfloatergotoline.cpp | 320 +- indra/newview/llfloatergotoline.h | 132 +- indra/newview/llfloatergroups.cpp | 790 +- indra/newview/llfloatergroups.h | 236 +- indra/newview/llfloaterhelpbrowser.cpp | 308 +- indra/newview/llfloaterhelpbrowser.h | 130 +- indra/newview/llfloaterhoverheight.cpp | 310 +- indra/newview/llfloaterhoverheight.h | 104 +- indra/newview/llfloaterhowto.cpp | 184 +- indra/newview/llfloaterhud.cpp | 168 +- indra/newview/llfloaterhud.h | 100 +- indra/newview/llfloaterimagepreview.cpp | 1918 +- indra/newview/llfloaterimagepreview.h | 288 +- indra/newview/llfloaterimcontainer.cpp | 5000 ++-- indra/newview/llfloaterimcontainer.h | 478 +- indra/newview/llfloaterimnearbychat.cpp | 1886 +- indra/newview/llfloaterimnearbychat.h | 238 +- indra/newview/llfloaterimnearbychathandler.cpp | 1356 +- indra/newview/llfloaterimnearbychatlistener.cpp | 200 +- indra/newview/llfloaterimsession.cpp | 2712 +-- indra/newview/llfloaterimsession.h | 410 +- indra/newview/llfloaterimsessiontab.cpp | 2700 +-- indra/newview/llfloaterimsessiontab.h | 476 +- indra/newview/llfloaterinspect.cpp | 704 +- indra/newview/llfloaterinspect.h | 152 +- indra/newview/llfloaterjoystick.cpp | 976 +- indra/newview/llfloaterjoystick.h | 206 +- indra/newview/llfloaterlagmeter.cpp | 748 +- indra/newview/llfloaterlagmeter.h | 160 +- indra/newview/llfloaterland.cpp | 6918 +++--- indra/newview/llfloaterland.h | 838 +- indra/newview/llfloaterlandholdings.cpp | 710 +- indra/newview/llfloaterlandholdings.h | 156 +- indra/newview/llfloaterlinkreplace.cpp | 858 +- indra/newview/llfloaterlinkreplace.h | 246 +- indra/newview/llfloaterloadprefpreset.cpp | 220 +- indra/newview/llfloaterloadprefpreset.h | 106 +- indra/newview/llfloatermap.cpp | 508 +- indra/newview/llfloatermap.h | 134 +- indra/newview/llfloatermarketplacelistings.cpp | 2132 +- indra/newview/llfloatermarketplacelistings.h | 468 +- indra/newview/llfloatermediasettings.cpp | 670 +- indra/newview/llfloatermediasettings.h | 178 +- indra/newview/llfloatermemleak.cpp | 460 +- indra/newview/llfloatermemleak.h | 150 +- indra/newview/llfloatermodelpreview.cpp | 3970 ++-- indra/newview/llfloatermodelpreview.h | 478 +- indra/newview/llfloatermyenvironment.h | 156 +- indra/newview/llfloatermyscripts.cpp | 614 +- indra/newview/llfloatermyscripts.h | 124 +- indra/newview/llfloaternamedesc.cpp | 574 +- indra/newview/llfloaternamedesc.h | 168 +- indra/newview/llfloaternewfeaturenotification.h | 98 +- indra/newview/llfloaternotificationsconsole.cpp | 560 +- indra/newview/llfloaternotificationsconsole.h | 150 +- indra/newview/llfloaternotificationstabbed.cpp | 1152 +- indra/newview/llfloaternotificationstabbed.h | 348 +- indra/newview/llfloaterobjectweights.cpp | 548 +- indra/newview/llfloaterobjectweights.h | 186 +- indra/newview/llfloateropenobject.cpp | 460 +- indra/newview/llfloateropenobject.h | 164 +- indra/newview/llfloaterpathfindingcharacters.cpp | 652 +- indra/newview/llfloaterpathfindingcharacters.h | 198 +- indra/newview/llfloaterpathfindingconsole.cpp | 2546 +- indra/newview/llfloaterpathfindingconsole.h | 440 +- indra/newview/llfloaterpathfindinglinksets.cpp | 1602 +- indra/newview/llfloaterpathfindinglinksets.h | 286 +- indra/newview/llfloaterpathfindingobjects.cpp | 1780 +- indra/newview/llfloaterpathfindingobjects.h | 358 +- indra/newview/llfloaterpay.cpp | 1254 +- indra/newview/llfloaterpay.h | 110 +- indra/newview/llfloaterperformance.h | 208 +- indra/newview/llfloaterperms.cpp | 612 +- indra/newview/llfloaterperms.h | 196 +- indra/newview/llfloaterpostprocess.cpp | 458 +- indra/newview/llfloaterpostprocess.h | 144 +- indra/newview/llfloaterpreference.cpp | 6872 +++--- indra/newview/llfloaterpreference.h | 808 +- .../llfloaterpreferencesgraphicsadvanced.cpp | 1004 +- .../newview/llfloaterpreferencesgraphicsadvanced.h | 142 +- indra/newview/llfloaterpreviewtrash.cpp | 164 +- indra/newview/llfloaterpreviewtrash.h | 98 +- indra/newview/llfloaterprofile.cpp | 340 +- indra/newview/llfloaterregiondebugconsole.cpp | 422 +- indra/newview/llfloaterregiondebugconsole.h | 128 +- indra/newview/llfloaterregioninfo.cpp | 7772 +++--- indra/newview/llfloaterregioninfo.h | 984 +- indra/newview/llfloaterregionrestarting.cpp | 352 +- indra/newview/llfloaterregionrestarting.h | 138 +- indra/newview/llfloaterreporter.cpp | 2090 +- indra/newview/llfloaterreporter.h | 306 +- indra/newview/llfloatersavecamerapreset.cpp | 344 +- indra/newview/llfloatersavecamerapreset.h | 120 +- indra/newview/llfloatersaveprefpreset.cpp | 230 +- indra/newview/llfloatersaveprefpreset.h | 116 +- indra/newview/llfloatersceneloadstats.cpp | 80 +- indra/newview/llfloatersceneloadstats.h | 86 +- indra/newview/llfloaterscriptdebug.cpp | 434 +- indra/newview/llfloaterscriptdebug.h | 140 +- indra/newview/llfloaterscriptedprefs.cpp | 130 +- indra/newview/llfloaterscriptedprefs.h | 102 +- indra/newview/llfloaterscriptlimits.cpp | 1882 +- indra/newview/llfloaterscriptlimits.h | 310 +- indra/newview/llfloatersearch.cpp | 422 +- indra/newview/llfloatersearch.h | 188 +- indra/newview/llfloatersellland.cpp | 1114 +- indra/newview/llfloatersettingsdebug.cpp | 1276 +- indra/newview/llfloatersettingsdebug.h | 150 +- indra/newview/llfloatersidepanelcontainer.cpp | 374 +- indra/newview/llfloatersimplesnapshot.cpp | 1044 +- indra/newview/llfloatersimplesnapshot.h | 306 +- indra/newview/llfloatersnapshot.cpp | 2988 +-- indra/newview/llfloatersnapshot.h | 486 +- indra/newview/llfloatersounddevices.cpp | 172 +- indra/newview/llfloatersounddevices.h | 98 +- indra/newview/llfloaterspellchecksettings.cpp | 922 +- indra/newview/llfloaterspellchecksettings.h | 136 +- indra/newview/llfloatertelehub.cpp | 558 +- indra/newview/llfloatertelehub.h | 154 +- indra/newview/llfloatertestinspectors.cpp | 226 +- indra/newview/llfloatertestinspectors.h | 118 +- indra/newview/llfloatertools.cpp | 2340 +- indra/newview/llfloatertools.h | 394 +- indra/newview/llfloatertopobjects.cpp | 1100 +- indra/newview/llfloatertopobjects.h | 230 +- indra/newview/llfloatertos.cpp | 562 +- indra/newview/llfloatertos.h | 150 +- indra/newview/llfloatertoybox.cpp | 382 +- indra/newview/llfloatertoybox.h | 124 +- indra/newview/llfloatertranslationsettings.cpp | 848 +- indra/newview/llfloatertranslationsettings.h | 178 +- indra/newview/llfloateruipreview.cpp | 3434 +-- indra/newview/llfloaterurlentry.cpp | 596 +- indra/newview/llfloaterurlentry.h | 136 +- indra/newview/llfloatervoiceeffect.cpp | 578 +- indra/newview/llfloatervoiceeffect.h | 144 +- indra/newview/llfloatervoicevolume.cpp | 440 +- indra/newview/llfloaterwebcontent.cpp | 970 +- indra/newview/llfloaterwebcontent.h | 242 +- indra/newview/llfloaterwhitelistentry.cpp | 182 +- indra/newview/llfloaterwhitelistentry.h | 100 +- indra/newview/llfloaterwindowsize.cpp | 248 +- indra/newview/llfloaterwindowsize.h | 98 +- indra/newview/llfloaterworldmap.cpp | 3590 +-- indra/newview/llfloaterworldmap.h | 448 +- indra/newview/llfolderviewmodelinventory.h | 264 +- indra/newview/llfollowcam.cpp | 1756 +- indra/newview/llfollowcam.h | 464 +- indra/newview/llfriendcard.cpp | 1364 +- indra/newview/llgesturemgr.cpp | 3160 +-- indra/newview/llgesturemgr.h | 396 +- indra/newview/llgiveinventory.cpp | 1170 +- indra/newview/llglsandbox.cpp | 2400 +- indra/newview/llgroupactions.cpp | 1150 +- indra/newview/llgrouplist.cpp | 1244 +- indra/newview/llgrouplist.h | 288 +- indra/newview/llgroupmgr.cpp | 5014 ++-- indra/newview/llgroupmgr.h | 944 +- indra/newview/llhints.cpp | 842 +- indra/newview/llhudeffect.cpp | 266 +- indra/newview/llhudeffect.h | 154 +- indra/newview/llhudeffectbeam.cpp | 696 +- indra/newview/llhudeffectlookat.cpp | 1342 +- indra/newview/llhudeffectlookat.h | 190 +- indra/newview/llhudeffectpointat.cpp | 924 +- indra/newview/llhudeffectpointat.h | 168 +- indra/newview/llhudeffecttrail.cpp | 560 +- indra/newview/llhudeffecttrail.h | 190 +- indra/newview/llhudicon.cpp | 696 +- indra/newview/llhudicon.h | 176 +- indra/newview/llhudmanager.cpp | 420 +- indra/newview/llhudmanager.h | 120 +- indra/newview/llhudnametag.cpp | 1916 +- indra/newview/llhudnametag.h | 390 +- indra/newview/llhudobject.cpp | 662 +- indra/newview/llhudobject.h | 246 +- indra/newview/llhudrender.cpp | 298 +- indra/newview/llhudrender.h | 118 +- indra/newview/llhudtext.cpp | 1278 +- indra/newview/llhudtext.h | 346 +- indra/newview/llhudview.cpp | 144 +- indra/newview/llhudview.h | 100 +- indra/newview/llimprocessing.cpp | 3344 +-- indra/newview/llimview.cpp | 8408 +++---- indra/newview/llimview.h | 1284 +- indra/newview/llinspect.cpp | 330 +- indra/newview/llinspect.h | 128 +- indra/newview/llinspectavatar.cpp | 796 +- indra/newview/llinspectobject.cpp | 1382 +- indra/newview/llinspectremoteobject.cpp | 368 +- indra/newview/llinspecttexture.cpp | 504 +- indra/newview/llinspecttoast.cpp | 304 +- indra/newview/llinventorybridge.cpp | 16524 ++++++------- indra/newview/llinventorybridge.h | 1650 +- indra/newview/llinventoryfilter.cpp | 3442 +-- indra/newview/llinventoryfilter.h | 768 +- indra/newview/llinventoryfunctions.cpp | 7422 +++--- indra/newview/llinventoryfunctions.h | 1202 +- indra/newview/llinventorygallery.h | 848 +- indra/newview/llinventoryicon.cpp | 422 +- indra/newview/llinventoryicon.h | 112 +- indra/newview/llinventorylistitem.cpp | 948 +- indra/newview/llinventorylistitem.h | 478 +- indra/newview/llinventorymodel.cpp | 10118 ++++---- indra/newview/llinventorymodel.h | 1442 +- indra/newview/llinventorymodelbackgroundfetch.cpp | 3238 +-- indra/newview/llinventorymodelbackgroundfetch.h | 290 +- indra/newview/llinventoryobserver.cpp | 1732 +- indra/newview/llinventoryobserver.h | 628 +- indra/newview/llinventorypanel.cpp | 5178 ++-- indra/newview/llinventorypanel.h | 978 +- indra/newview/lljoystickbutton.cpp | 1888 +- indra/newview/lljoystickbutton.h | 464 +- indra/newview/lllandmarkactions.cpp | 796 +- indra/newview/lllandmarkactions.h | 264 +- indra/newview/lllandmarklist.cpp | 472 +- indra/newview/lllandmarklist.h | 176 +- indra/newview/lllegacyatmospherics.cpp | 1598 +- indra/newview/lllegacyatmospherics.h | 564 +- indra/newview/lllocalbitmaps.cpp | 2548 +- indra/newview/lllocalbitmaps.h | 316 +- indra/newview/lllocalgltfmaterials.h | 238 +- indra/newview/lllocationinputctrl.cpp | 2580 +- indra/newview/lllocationinputctrl.h | 426 +- indra/newview/lllogchat.cpp | 2534 +- indra/newview/lllogininstance.cpp | 1260 +- indra/newview/llmanip.cpp | 1244 +- indra/newview/llmanip.h | 330 +- indra/newview/llmaniprotate.cpp | 3900 +-- indra/newview/llmaniprotate.h | 232 +- indra/newview/llmanipscale.cpp | 4198 ++-- indra/newview/llmanipscale.h | 358 +- indra/newview/llmaniptranslate.cpp | 4598 ++-- indra/newview/llmaniptranslate.h | 228 +- indra/newview/llmarketplacefunctions.cpp | 3804 +-- indra/newview/llmaterialeditor.cpp | 7540 +++--- indra/newview/llmaterialeditor.h | 644 +- indra/newview/llmaterialmgr.h | 328 +- indra/newview/llmediactrl.cpp | 2512 +- indra/newview/llmediactrl.h | 448 +- indra/newview/llmediadataclient.cpp | 2254 +- indra/newview/llmediadataclient.h | 882 +- .../llmenuoptionpathfindingrebakenavmesh.cpp | 486 +- .../newview/llmenuoptionpathfindingrebakenavmesh.h | 170 +- indra/newview/llmeshrepository.cpp | 11062 ++++----- indra/newview/llmeshrepository.h | 1492 +- indra/newview/llmimetypes.cpp | 604 +- indra/newview/llmimetypes.h | 246 +- indra/newview/llmodelpreview.cpp | 8014 +++---- indra/newview/llmodelpreview.h | 690 +- indra/newview/llmorphview.cpp | 332 +- indra/newview/llmorphview.h | 174 +- indra/newview/llmoveview.cpp | 1462 +- indra/newview/llmoveview.h | 370 +- indra/newview/llmutelist.cpp | 2024 +- indra/newview/llmutelist.h | 414 +- indra/newview/llnamebox.cpp | 250 +- indra/newview/llnamebox.h | 152 +- indra/newview/llnameeditor.cpp | 220 +- indra/newview/llnameeditor.h | 158 +- indra/newview/llnamelistctrl.cpp | 1244 +- indra/newview/llnamelistctrl.h | 450 +- indra/newview/llnavigationbar.cpp | 1478 +- indra/newview/llnavigationbar.h | 326 +- indra/newview/llnetmap.cpp | 2492 +- indra/newview/llnetmap.h | 340 +- indra/newview/llnotificationhandlerutil.cpp | 658 +- indra/newview/llnotificationlistitem.cpp | 1238 +- indra/newview/llnotificationlistitem.h | 502 +- indra/newview/llnotificationofferhandler.cpp | 412 +- indra/newview/lloutfitgallery.cpp | 2646 +-- indra/newview/lloutfitgallery.h | 544 +- indra/newview/lloutfitslist.cpp | 2738 +-- indra/newview/lloutfitslist.h | 772 +- indra/newview/lloutputmonitorctrl.cpp | 726 +- indra/newview/lloutputmonitorctrl.h | 340 +- indra/newview/llpanelavatartag.cpp | 202 +- indra/newview/llpanelavatartag.h | 174 +- indra/newview/llpanelblockedlist.cpp | 652 +- indra/newview/llpanelblockedlist.h | 226 +- indra/newview/llpanelclassified.cpp | 1138 +- indra/newview/llpanelclassified.h | 350 +- indra/newview/llpanelcontents.cpp | 416 +- indra/newview/llpanelcontents.h | 150 +- indra/newview/llpaneleditsky.h | 350 +- indra/newview/llpaneleditwater.h | 196 +- indra/newview/llpaneleditwearable.h | 360 +- indra/newview/llpanelemojicomplete.cpp | 2 +- indra/newview/llpanelenvironment.cpp | 2356 +- indra/newview/llpanelexperiencelisteditor.cpp | 540 +- indra/newview/llpanelexperiencelisteditor.h | 202 +- indra/newview/llpanelexperiencelog.cpp | 534 +- indra/newview/llpanelexperiencelog.h | 128 +- indra/newview/llpanelexperiencepicker.cpp | 898 +- indra/newview/llpanelexperiencepicker.h | 192 +- indra/newview/llpanelexperiences.cpp | 478 +- indra/newview/llpanelexperiences.h | 196 +- indra/newview/llpanelface.cpp | 10996 ++++----- indra/newview/llpanelface.h | 1248 +- indra/newview/llpanelgroup.cpp | 1252 +- indra/newview/llpanelgroup.h | 342 +- indra/newview/llpanelgroupbulk.cpp | 844 +- indra/newview/llpanelgroupbulkban.cpp | 512 +- indra/newview/llpanelgroupbulkban.h | 98 +- indra/newview/llpanelgroupexperiences.cpp | 248 +- indra/newview/llpanelgroupexperiences.h | 112 +- indra/newview/llpanelgroupgeneral.cpp | 1492 +- indra/newview/llpanelgroupgeneral.h | 206 +- indra/newview/llpanelgroupinvite.cpp | 1400 +- indra/newview/llpanelgroupinvite.h | 124 +- indra/newview/llpanelgrouplandmoney.cpp | 3304 +-- indra/newview/llpanelgrouplandmoney.h | 132 +- indra/newview/llpanelgroupnotices.cpp | 1360 +- indra/newview/llpanelgroupnotices.h | 248 +- indra/newview/llpanelgrouproles.cpp | 6654 +++--- indra/newview/llpanelgrouproles.h | 762 +- indra/newview/llpanelhome.cpp | 144 +- indra/newview/llpanelhome.h | 116 +- indra/newview/llpanelland.cpp | 518 +- indra/newview/llpanelland.h | 130 +- indra/newview/llpanellandaudio.cpp | 424 +- indra/newview/llpanellandaudio.h | 122 +- indra/newview/llpanellandmarkinfo.cpp | 1104 +- indra/newview/llpanellandmarkinfo.h | 210 +- indra/newview/llpanellandmarks.cpp | 2496 +- indra/newview/llpanellandmarks.h | 338 +- indra/newview/llpanellandmedia.cpp | 658 +- indra/newview/llpanellandmedia.h | 146 +- indra/newview/llpanellogin.cpp | 2738 +-- indra/newview/llpanellogin.h | 264 +- indra/newview/llpanelmaininventory.cpp | 5314 ++--- indra/newview/llpanelmaininventory.h | 530 +- indra/newview/llpanelmarketplaceinbox.cpp | 562 +- indra/newview/llpanelmarketplaceinbox.h | 162 +- indra/newview/llpanelmarketplaceinboxinventory.cpp | 750 +- indra/newview/llpanelmarketplaceinboxinventory.h | 288 +- indra/newview/llpanelmediasettingsgeneral.cpp | 1080 +- indra/newview/llpanelmediasettingsgeneral.h | 200 +- indra/newview/llpanelmediasettingspermissions.cpp | 568 +- indra/newview/llpanelmediasettingspermissions.h | 146 +- indra/newview/llpanelmediasettingssecurity.cpp | 712 +- indra/newview/llpanelmediasettingssecurity.h | 164 +- indra/newview/llpanelnearbymedia.cpp | 2660 +-- indra/newview/llpanelnearbymedia.h | 362 +- indra/newview/llpanelobject.cpp | 4666 ++-- indra/newview/llpanelobject.h | 398 +- indra/newview/llpanelobjectinventory.cpp | 3866 +-- indra/newview/llpanelobjectinventory.h | 244 +- indra/newview/llpaneloutfitedit.cpp | 2898 +-- indra/newview/llpaneloutfitedit.h | 490 +- indra/newview/llpaneloutfitsinventory.cpp | 762 +- indra/newview/llpaneloutfitsinventory.h | 220 +- indra/newview/llpanelpeople.cpp | 3138 +-- indra/newview/llpanelpeople.h | 314 +- indra/newview/llpanelpermissions.cpp | 2702 +-- indra/newview/llpanelpermissions.h | 206 +- indra/newview/llpanelplaceinfo.cpp | 640 +- indra/newview/llpanelplaceinfo.h | 258 +- indra/newview/llpanelplaceprofile.cpp | 1364 +- indra/newview/llpanelplaceprofile.h | 242 +- indra/newview/llpanelplaces.cpp | 2716 +-- indra/newview/llpanelplaces.h | 338 +- indra/newview/llpanelplacestab.h | 156 +- indra/newview/llpanelpresetscamerapulldown.cpp | 304 +- indra/newview/llpanelpresetscamerapulldown.h | 98 +- indra/newview/llpanelpresetspulldown.cpp | 342 +- indra/newview/llpanelpresetspulldown.h | 102 +- indra/newview/llpanelprimmediacontrols.cpp | 2942 +-- indra/newview/llpanelprimmediacontrols.h | 468 +- indra/newview/llpanelprofile.cpp | 4862 ++-- indra/newview/llpanelprofile.h | 766 +- indra/newview/llpanelprofileclassifieds.cpp | 3112 +-- indra/newview/llpanelsnapshot.cpp | 490 +- indra/newview/llpanelsnapshot.h | 158 +- indra/newview/llpanelsnapshotinventory.cpp | 422 +- indra/newview/llpanelsnapshotlocal.cpp | 378 +- indra/newview/llpanelsnapshotoptions.cpp | 258 +- indra/newview/llpanelsnapshotpostcard.cpp | 506 +- indra/newview/llpanelsnapshotprofile.cpp | 202 +- indra/newview/llpanelteleporthistory.cpp | 2394 +- indra/newview/llpanelteleporthistory.h | 236 +- indra/newview/llpaneltiptoast.cpp | 126 +- indra/newview/llpaneltiptoast.h | 102 +- indra/newview/llpaneltopinfobar.cpp | 960 +- indra/newview/llpaneltopinfobar.h | 356 +- indra/newview/llpanelvoicedevicesettings.cpp | 732 +- indra/newview/llpanelvoicedevicesettings.h | 142 +- indra/newview/llpanelvoiceeffect.cpp | 330 +- indra/newview/llpanelvoiceeffect.h | 134 +- indra/newview/llpanelvolume.cpp | 3182 +-- indra/newview/llpanelvolume.h | 302 +- indra/newview/llpanelvolumepulldown.cpp | 234 +- indra/newview/llpanelvolumepulldown.h | 102 +- indra/newview/llpanelwearing.cpp | 1198 +- indra/newview/llpanelwearing.h | 226 +- indra/newview/llparcelselection.cpp | 164 +- indra/newview/llparcelselection.h | 158 +- indra/newview/llpathfindingcharacter.cpp | 198 +- indra/newview/llpathfindingcharacter.h | 126 +- indra/newview/llpathfindinglinkset.cpp | 816 +- indra/newview/llpathfindinglinkset.h | 228 +- indra/newview/llpathfindinglinksetlist.cpp | 426 +- indra/newview/llpathfindinglinksetlist.h | 116 +- indra/newview/llpathfindingmanager.cpp | 1836 +- indra/newview/llpathfindingmanager.h | 276 +- indra/newview/llpathfindingobject.h | 184 +- indra/newview/llpathfindingpathtool.cpp | 932 +- indra/newview/llpathfindingpathtool.h | 276 +- indra/newview/llperfstats.cpp | 1096 +- indra/newview/llphysicsmotion.cpp | 1552 +- indra/newview/llphysicsmotion.h | 236 +- indra/newview/llphysicsshapebuilderutil.h | 284 +- indra/newview/llplacesfolderview.cpp | 170 +- indra/newview/llplacesfolderview.h | 148 +- indra/newview/llplacesinventorypanel.cpp | 264 +- indra/newview/llplacesinventorypanel.h | 126 +- indra/newview/llpopupview.cpp | 542 +- indra/newview/llpopupview.h | 122 +- indra/newview/llpresetsmanager.cpp | 1184 +- indra/newview/llpreview.cpp | 1136 +- indra/newview/llpreview.h | 328 +- indra/newview/llpreviewanim.cpp | 474 +- indra/newview/llpreviewanim.h | 114 +- indra/newview/llpreviewgesture.cpp | 3632 +-- indra/newview/llpreviewgesture.h | 340 +- indra/newview/llpreviewnotecard.cpp | 1836 +- indra/newview/llpreviewnotecard.h | 260 +- indra/newview/llpreviewscript.cpp | 4982 ++-- indra/newview/llpreviewscript.h | 718 +- indra/newview/llpreviewsound.cpp | 200 +- indra/newview/llpreviewsound.h | 88 +- indra/newview/llpreviewtexture.cpp | 1486 +- indra/newview/llpreviewtexture.h | 218 +- indra/newview/llprogressview.cpp | 1310 +- indra/newview/llprogressview.h | 250 +- indra/newview/llregioninfomodel.cpp | 508 +- indra/newview/llregioninfomodel.h | 200 +- indra/newview/llscenemonitor.cpp | 1514 +- indra/newview/llscenemonitor.h | 258 +- indra/newview/llsceneview.cpp | 870 +- indra/newview/llscreenchannel.cpp | 2272 +- indra/newview/llscreenchannel.h | 626 +- indra/newview/llscripteditor.cpp | 492 +- indra/newview/llscripteditor.h | 152 +- indra/newview/llscriptfloater.cpp | 1556 +- indra/newview/llscriptfloater.h | 460 +- indra/newview/llscrollingpanelparam.cpp | 706 +- indra/newview/llscrollingpanelparam.h | 188 +- indra/newview/llscrollingpanelparambase.cpp | 224 +- indra/newview/llscrollingpanelparambase.h | 124 +- indra/newview/llsearchableui.cpp | 350 +- indra/newview/llsearchcombobox.cpp | 526 +- indra/newview/llsearchcombobox.h | 208 +- indra/newview/llsechandler_basic.cpp | 3944 ++-- indra/newview/llselectmgr.cpp | 17232 +++++++------- indra/newview/llselectmgr.h | 2116 +- indra/newview/llsettingspicker.h | 274 +- indra/newview/llsettingsvo.cpp | 3138 +-- indra/newview/llshareavatarhandler.cpp | 134 +- indra/newview/llsidepanelappearance.cpp | 1136 +- indra/newview/llsidepanelappearance.h | 204 +- indra/newview/llsidepanelinventory.cpp | 1190 +- indra/newview/llsidepanelinventory.h | 238 +- indra/newview/llsidepanelinventorysubpanel.cpp | 278 +- indra/newview/llsidepanelinventorysubpanel.h | 146 +- indra/newview/llsidepaneliteminfo.cpp | 2398 +- indra/newview/llsidepaneliteminfo.h | 250 +- indra/newview/llsidepaneltaskinfo.cpp | 2730 +-- indra/newview/llsidepaneltaskinfo.h | 340 +- indra/newview/llsidetraypanelcontainer.cpp | 186 +- indra/newview/llsidetraypanelcontainer.h | 192 +- indra/newview/llsky.cpp | 666 +- indra/newview/llsky.h | 194 +- indra/newview/llsnapshotlivepreview.cpp | 2138 +- indra/newview/llsnapshotlivepreview.h | 372 +- indra/newview/llspatialpartition.cpp | 8258 +++---- indra/newview/llspatialpartition.h | 1558 +- indra/newview/llspeakers.cpp | 2084 +- indra/newview/llspeakers.h | 706 +- indra/newview/llspeakingindicatormanager.cpp | 644 +- indra/newview/llsplitbutton.cpp | 546 +- indra/newview/llsplitbutton.h | 216 +- indra/newview/llsprite.cpp | 604 +- indra/newview/llsprite.h | 212 +- indra/newview/llstartup.cpp | 7752 +++--- indra/newview/llstatusbar.cpp | 1522 +- indra/newview/llstatusbar.h | 304 +- indra/newview/llsurface.cpp | 2628 +-- indra/newview/llsurface.h | 520 +- indra/newview/llsurfacepatch.cpp | 2064 +- indra/newview/llsurfacepatch.h | 370 +- indra/newview/llsyswellitem.cpp | 188 +- indra/newview/llsyswellitem.h | 162 +- indra/newview/llsyswellwindow.cpp | 900 +- indra/newview/llsyswellwindow.h | 306 +- indra/newview/lltexturecache.cpp | 4616 ++-- indra/newview/lltexturecache.h | 510 +- indra/newview/lltexturectrl.cpp | 4744 ++-- indra/newview/lltexturectrl.h | 890 +- indra/newview/lltexturefetch.cpp | 7476 +++--- indra/newview/lltexturefetch.h | 862 +- indra/newview/lltextureview.cpp | 2058 +- indra/newview/lltextureview.h | 158 +- indra/newview/llthumbnailctrl.cpp | 542 +- indra/newview/llthumbnailctrl.h | 190 +- indra/newview/lltoast.cpp | 1266 +- indra/newview/lltoast.h | 512 +- indra/newview/lltoastalertpanel.cpp | 1104 +- indra/newview/lltoastalertpanel.h | 214 +- indra/newview/lltoastgroupnotifypanel.cpp | 442 +- indra/newview/lltoastimpanel.cpp | 498 +- indra/newview/lltoastimpanel.h | 164 +- indra/newview/lltoastnotifypanel.cpp | 1122 +- indra/newview/lltoastnotifypanel.h | 336 +- indra/newview/lltoastpanel.cpp | 512 +- indra/newview/lltoastscriptquestion.cpp | 298 +- indra/newview/lltoastscriptquestion.h | 102 +- indra/newview/lltoastscripttextbox.cpp | 264 +- indra/newview/lltool.cpp | 420 +- indra/newview/lltool.h | 218 +- indra/newview/lltoolbarview.cpp | 1436 +- indra/newview/lltoolbarview.h | 258 +- indra/newview/lltoolbrush.cpp | 1436 +- indra/newview/lltoolbrush.h | 216 +- indra/newview/lltoolcomp.cpp | 1680 +- indra/newview/lltoolcomp.h | 490 +- indra/newview/lltooldraganddrop.cpp | 6344 ++--- indra/newview/lltooldraganddrop.h | 646 +- indra/newview/lltoolface.cpp | 302 +- indra/newview/lltoolface.h | 102 +- indra/newview/lltoolfocus.cpp | 956 +- indra/newview/lltoolfocus.h | 164 +- indra/newview/lltoolgrab.cpp | 2424 +- indra/newview/lltoolgrab.h | 318 +- indra/newview/lltoolgun.cpp | 296 +- indra/newview/lltoolgun.h | 106 +- indra/newview/lltoolindividual.cpp | 230 +- indra/newview/lltoolindividual.h | 114 +- indra/newview/lltoolmgr.cpp | 1060 +- indra/newview/lltoolmgr.h | 262 +- indra/newview/lltoolmorph.cpp | 662 +- indra/newview/lltoolmorph.h | 234 +- indra/newview/lltoolobjpicker.cpp | 348 +- indra/newview/lltoolobjpicker.h | 128 +- indra/newview/lltoolpie.cpp | 4072 ++-- indra/newview/lltoolpie.h | 252 +- indra/newview/lltoolpipette.cpp | 272 +- indra/newview/lltoolpipette.h | 140 +- indra/newview/lltoolplacer.cpp | 1072 +- indra/newview/lltoolplacer.h | 126 +- indra/newview/lltoolselect.cpp | 582 +- indra/newview/lltoolselect.h | 118 +- indra/newview/lltoolselectland.cpp | 468 +- indra/newview/lltoolselectland.h | 152 +- indra/newview/lltoolselectrect.cpp | 414 +- indra/newview/lltoolselectrect.h | 128 +- indra/newview/lltracker.cpp | 1718 +- indra/newview/lltracker.h | 310 +- indra/newview/lltransientdockablefloater.cpp | 198 +- indra/newview/lltransientdockablefloater.h | 106 +- indra/newview/lltransientfloatermgr.cpp | 338 +- indra/newview/lltransientfloatermgr.h | 178 +- indra/newview/lltranslate.cpp | 2606 +- indra/newview/lltranslate.h | 224 +- indra/newview/lluiavatar.cpp | 122 +- indra/newview/lluiavatar.h | 90 +- indra/newview/lluploaddialog.cpp | 324 +- indra/newview/llurl.cpp | 580 +- indra/newview/llurl.h | 190 +- indra/newview/llurllineeditorctrl.h | 184 +- indra/newview/llviewerassetstorage.cpp | 1208 +- indra/newview/llviewerassetstorage.h | 260 +- indra/newview/llviewerassetupload.cpp | 2064 +- indra/newview/llvieweraudio.cpp | 1194 +- indra/newview/llviewercamera.cpp | 1796 +- indra/newview/llviewercamera.h | 264 +- indra/newview/llviewercontrol.cpp | 1762 +- indra/newview/llviewerdisplay.cpp | 3470 +-- indra/newview/llviewerdisplay.h | 92 +- indra/newview/llviewerfoldertype.cpp | 642 +- indra/newview/llviewerfoldertype.h | 108 +- indra/newview/llviewergesture.cpp | 412 +- indra/newview/llviewergesture.h | 172 +- indra/newview/llviewerinput.cpp | 3734 +-- indra/newview/llviewerinput.h | 396 +- indra/newview/llviewerinventory.cpp | 4664 ++-- indra/newview/llviewerinventory.h | 964 +- indra/newview/llviewerjoint.cpp | 344 +- indra/newview/llviewerjoint.h | 134 +- indra/newview/llviewerjointattachment.cpp | 962 +- indra/newview/llviewerjointattachment.h | 222 +- indra/newview/llviewerjointmesh.cpp | 1050 +- indra/newview/llviewerjointmesh.h | 154 +- indra/newview/llviewerjoystick.cpp | 3202 +-- indra/newview/llviewermedia.cpp | 8034 +++---- indra/newview/llviewermedia.h | 1010 +- indra/newview/llviewermediafocus.cpp | 1278 +- indra/newview/llviewermediafocus.h | 240 +- indra/newview/llviewermenu.cpp | 19978 ++++++++-------- indra/newview/llviewermenu.h | 420 +- indra/newview/llviewermenufile.cpp | 2580 +- indra/newview/llviewermessage.cpp | 14122 +++++------ indra/newview/llviewermessage.h | 550 +- indra/newview/llviewerobject.cpp | 14762 ++++++------ indra/newview/llviewerobject.h | 2028 +- indra/newview/llviewerobjectlist.cpp | 4296 ++-- indra/newview/llviewerobjectlist.h | 596 +- indra/newview/llvieweroctree.cpp | 3100 +-- indra/newview/llvieweroctree.h | 850 +- indra/newview/llviewerparcelmedia.cpp | 1256 +- indra/newview/llviewerparcelmediaautoplay.cpp | 352 +- indra/newview/llviewerparcelmediaautoplay.h | 106 +- indra/newview/llviewerparcelmgr.cpp | 5468 ++--- indra/newview/llviewerparcelmgr.h | 770 +- indra/newview/llviewerparceloverlay.h | 256 +- indra/newview/llviewerpartsim.cpp | 1792 +- indra/newview/llviewerpartsim.h | 430 +- indra/newview/llviewerpartsource.cpp | 1892 +- indra/newview/llviewerpartsource.h | 422 +- indra/newview/llviewerregion.cpp | 7352 +++--- indra/newview/llviewerregion.h | 1422 +- indra/newview/llviewershadermgr.cpp | 5578 ++--- indra/newview/llviewershadermgr.h | 550 +- indra/newview/llviewerstats.cpp | 1884 +- indra/newview/llviewertexlayer.cpp | 706 +- indra/newview/llviewertexlayer.h | 266 +- indra/newview/llviewertexteditor.cpp | 2766 +-- indra/newview/llviewertexteditor.h | 274 +- indra/newview/llviewertexture.cpp | 8216 +++---- indra/newview/llviewertexture.h | 1584 +- indra/newview/llviewertexturelist.cpp | 3554 +-- indra/newview/llviewertexturelist.h | 588 +- indra/newview/llviewerthrottle.cpp | 668 +- indra/newview/llviewerthrottle.h | 180 +- indra/newview/llviewerwearable.cpp | 1246 +- indra/newview/llviewerwearable.h | 216 +- indra/newview/llviewerwindow.cpp | 12814 +++++----- indra/newview/llviewerwindow.h | 1156 +- indra/newview/llvlcomposition.cpp | 1000 +- indra/newview/llvlcomposition.h | 172 +- indra/newview/llvlmanager.cpp | 336 +- indra/newview/llvoavatar.cpp | 23482 +++++++++---------- indra/newview/llvoavatar.h | 2512 +- indra/newview/llvoavatarself.cpp | 5802 ++--- indra/newview/llvoavatarself.h | 822 +- indra/newview/llvocache.cpp | 3638 +-- indra/newview/llvocache.h | 660 +- indra/newview/llvograss.cpp | 1790 +- indra/newview/llvograss.h | 252 +- indra/newview/llvoicecallhandler.cpp | 126 +- indra/newview/llvoicechannel.cpp | 1894 +- indra/newview/llvoicechannel.h | 420 +- indra/newview/llvoiceclient.cpp | 2196 +- indra/newview/llvoiceclient.h | 1088 +- indra/newview/llvoicevisualizer.cpp | 1224 +- indra/newview/llvoicevisualizer.h | 314 +- indra/newview/llvoicevivox.cpp | 16204 ++++++------- indra/newview/llvoicevivox.h | 2168 +- indra/newview/llvopartgroup.cpp | 1968 +- indra/newview/llvopartgroup.h | 250 +- indra/newview/llvosky.cpp | 3182 +-- indra/newview/llvosky.h | 724 +- indra/newview/llvosurfacepatch.cpp | 2050 +- indra/newview/llvosurfacepatch.h | 282 +- indra/newview/llvotree.cpp | 2474 +- indra/newview/llvotree.h | 392 +- indra/newview/llvovolume.cpp | 13706 +++++------ indra/newview/llvovolume.h | 1002 +- indra/newview/llvowater.cpp | 614 +- indra/newview/llvowater.h | 192 +- indra/newview/llvowlsky.cpp | 1162 +- indra/newview/llvowlsky.h | 180 +- indra/newview/llwearableitemslist.cpp | 2298 +- indra/newview/llwearableitemslist.h | 1016 +- indra/newview/llwearablelist.cpp | 556 +- indra/newview/llwebprofile.cpp | 544 +- indra/newview/llwindebug.cpp | 402 +- indra/newview/llwindowlistener.cpp | 1050 +- indra/newview/llworld.cpp | 2838 +-- indra/newview/llworld.h | 450 +- indra/newview/llworldmapmessage.cpp | 524 +- indra/newview/llworldmapview.cpp | 3664 +-- indra/newview/llworldmapview.h | 440 +- indra/newview/llworldmipmap.cpp | 540 +- indra/newview/pipeline.cpp | 21668 ++++++++--------- indra/newview/pipeline.h | 2118 +- indra/newview/tests/llagentaccess_test.cpp | 582 +- indra/newview/tests/lllogininstance_test.cpp | 978 +- indra/newview/tests/llsecapi_test.cpp | 266 +- indra/newview/tests/llsechandler_basic_test.cpp | 2836 +-- indra/newview/tests/llslurl_test.cpp | 680 +- indra/newview/tests/llviewerassetstats_test.cpp | 1150 +- indra/newview/tests/llviewerhelputil_test.cpp | 320 +- indra/newview/tests/llviewernetwork_test.cpp | 900 +- indra/newview/tests/llworldmap_test.cpp | 1030 +- indra/newview/tests/llworldmipmap_test.cpp | 330 +- 880 files changed, 552224 insertions(+), 552224 deletions(-) (limited to 'indra/newview') diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index f0128b95bc..298ed82d92 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -1,5216 +1,5216 @@ -/** - * @file llagent.cpp - * @brief LLAgent class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#include "llviewerprecompiledheaders.h" - -#include "llagent.h" - -#include "pipeline.h" - -#include "llagentaccess.h" -#include "llagentbenefits.h" -#include "llagentcamera.h" -#include "llagentlistener.h" -#include "llagentwearables.h" -#include "llagentui.h" -#include "llappearancemgr.h" -#include "llanimationstates.h" -#include "llcallingcard.h" -#include "llchannelmanager.h" -#include "llchicletbar.h" -#include "llconsole.h" -#include "lldonotdisturbnotificationstorage.h" -#include "llfirstuse.h" -#include "llfloatercamera.h" -#include "llfloaterimcontainer.h" -#include "llfloaterperms.h" -#include "llfloaterpreference.h" -#include "llfloaterreg.h" -#include "llfloatersnapshot.h" -#include "llfloatertools.h" -#include "llgroupactions.h" -#include "llgroupmgr.h" -#include "llhudmanager.h" -#include "lljoystickbutton.h" -#include "llmorphview.h" -#include "llmoveview.h" -#include "llnavigationbar.h" // to show/hide navigation bar when changing mouse look state -#include "llfloaterimnearbychat.h" -#include "llnotificationsutil.h" -#include "llpaneltopinfobar.h" -#include "llparcel.h" -#include "llperfstats.h" -#include "llrendersphere.h" -#include "llscriptruntimeperms.h" -#include "llsdutil.h" -#include "llsky.h" -#include "llslurl.h" -#include "llsmoothstep.h" -#include "llstartup.h" -#include "llstatusbar.h" -#include "llteleportflags.h" -#include "lltool.h" -#include "lltoolbarview.h" -#include "lltoolpie.h" -#include "lltoolmgr.h" -#include "lltrans.h" -#include "lluictrl.h" -#include "llurlentry.h" -#include "llviewercontrol.h" -#include "llviewerdisplay.h" -#include "llviewerjoystick.h" -#include "llviewermediafocus.h" -#include "llviewermenu.h" -#include "llviewerobjectlist.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" -#include "llviewerstats.h" -#include "llviewerwindow.h" -#include "llvoavatarself.h" -#include "llwindow.h" -#include "llworld.h" -#include "llworldmap.h" -#include "stringize.h" -#include "llcorehttputil.h" -#include "lluiusage.h" - -using namespace LLAvatarAppearanceDefines; - -extern LLMenuBarGL* gMenuBarView; - -const bool ANIMATE = true; -const U8 AGENT_STATE_TYPING = 0x04; -const U8 AGENT_STATE_EDITING = 0x10; - -// Autopilot constants -const F32 AUTOPILOT_HEIGHT_ADJUST_DISTANCE = 8.f; // meters -const F32 AUTOPILOT_MIN_TARGET_HEIGHT_OFF_GROUND = 1.f; // meters -const F32 AUTOPILOT_MAX_TIME_NO_PROGRESS_WALK = 1.5f; // seconds -const F32 AUTOPILOT_MAX_TIME_NO_PROGRESS_FLY = 2.5f; // seconds. Flying is less presize, needs a bit more time - -const F32 MAX_VELOCITY_AUTO_LAND_SQUARED = 4.f * 4.f; -const F64 CHAT_AGE_FAST_RATE = 3.0; - -// fidget constants -const F32 MIN_FIDGET_TIME = 8.f; // seconds -const F32 MAX_FIDGET_TIME = 20.f; // seconds - -const S32 UI_FEATURE_VERSION = 1; -// For version 1: 1 - inventory, 2 - gltf -const S32 UI_FEATURE_FLAGS = 3; - -// The agent instance. -LLAgent gAgent; - -class LLTeleportRequest -{ -public: - enum EStatus - { - kPending, - kStarted, - kFailed, - kRestartPending - }; - - LLTeleportRequest(); - virtual ~LLTeleportRequest(); - - EStatus getStatus() const {return mStatus;}; - void setStatus(EStatus pStatus) {mStatus = pStatus;}; - - static std::map sTeleportStatusName; - static const std::string& statusName(EStatus status); - virtual void toOstream(std::ostream& os) const; - - virtual bool canRestartTeleport(); - - virtual void startTeleport() = 0; - virtual void restartTeleport(); - -protected: - -private: - EStatus mStatus; -}; - -std::map LLTeleportRequest::sTeleportStatusName = { { kPending, "kPending" }, - { kStarted, "kStarted" }, - { kFailed, "kFailed" }, - { kRestartPending, "kRestartPending"} }; - -class LLTeleportRequestViaLandmark : public LLTeleportRequest -{ -public: - LLTeleportRequestViaLandmark(const LLUUID &pLandmarkId); - virtual ~LLTeleportRequestViaLandmark(); - - virtual void toOstream(std::ostream& os) const; - - virtual bool canRestartTeleport(); - - virtual void startTeleport(); - virtual void restartTeleport(); - -protected: - inline const LLUUID &getLandmarkId() const {return mLandmarkId;}; - -private: - LLUUID mLandmarkId; -}; - -class LLTeleportRequestViaLure : public LLTeleportRequestViaLandmark -{ -public: - LLTeleportRequestViaLure(const LLUUID &pLureId, bool pIsLureGodLike); - virtual ~LLTeleportRequestViaLure(); - - virtual void toOstream(std::ostream& os) const; - - virtual bool canRestartTeleport(); - - virtual void startTeleport(); - -protected: - inline bool isLureGodLike() const {return mIsLureGodLike;}; - -private: - bool mIsLureGodLike; -}; - -class LLTeleportRequestViaLocation : public LLTeleportRequest -{ -public: - LLTeleportRequestViaLocation(const LLVector3d &pPosGlobal); - virtual ~LLTeleportRequestViaLocation(); - - virtual void toOstream(std::ostream& os) const; - - virtual bool canRestartTeleport(); - - virtual void startTeleport(); - virtual void restartTeleport(); - -protected: - inline const LLVector3d &getPosGlobal() const {return mPosGlobal;}; - -private: - LLVector3d mPosGlobal; -}; - - -class LLTeleportRequestViaLocationLookAt : public LLTeleportRequestViaLocation -{ -public: - LLTeleportRequestViaLocationLookAt(const LLVector3d &pPosGlobal); - virtual ~LLTeleportRequestViaLocationLookAt(); - - virtual void toOstream(std::ostream& os) const; - - virtual bool canRestartTeleport(); - - virtual void startTeleport(); - virtual void restartTeleport(); - -protected: - -private: - -}; - -//-------------------------------------------------------------------- -// Statics -// - -/// minimum time after setting away state before coming back based on movement -const F32 LLAgent::MIN_AFK_TIME = 10.0f; - -const F32 LLAgent::TYPING_TIMEOUT_SECS = 5.f; - -std::map LLAgent::sTeleportErrorMessages; -std::map LLAgent::sTeleportProgressMessages; - -class LLAgentFriendObserver : public LLFriendObserver -{ -public: - LLAgentFriendObserver() {} - virtual ~LLAgentFriendObserver() {} - virtual void changed(U32 mask); -}; - -void LLAgentFriendObserver::changed(U32 mask) -{ - // if there's a change we're interested in. - if((mask & (LLFriendObserver::POWERS)) != 0) - { - gAgent.friendsChanged(); - } -} - -bool handleSlowMotionAnimation(const LLSD& newvalue) -{ - if (newvalue.asBoolean()) - { - gAgentAvatarp->setAnimTimeFactor(0.2f); - } - else - { - gAgentAvatarp->setAnimTimeFactor(1.0f); - } - return true; -} - -void LLAgent::setCanEditParcel() // called via mParcelChangedSignal -{ - bool can_edit = LLToolMgr::getInstance()->canEdit(); - gAgent.mCanEditParcel = can_edit; -} - -// static -bool LLAgent::isActionAllowed(const LLSD& sdname) -{ - bool retval = false; - - const std::string& param = sdname.asString(); - - if (param == "speak") - { - bool allow_agent_voice = false; - LLVoiceChannel* channel = LLVoiceChannel::getCurrentVoiceChannel(); - if (channel != NULL) - { - if (channel->getSessionName().empty() && channel->getSessionID().isNull()) - { - // default channel - allow_agent_voice = LLViewerParcelMgr::getInstance()->allowAgentVoice(); - } - else - { - allow_agent_voice = channel->isActive() && channel->callStarted(); - } - } - - if (gAgent.isVoiceConnected() && - allow_agent_voice && - !LLVoiceClient::getInstance()->inTuningMode()) - { - retval = true; - } - else - { - retval = false; - } - } - - return retval; -} - -// static -void LLAgent::pressMicrophone(const LLSD& name) -{ - LLFirstUse::speak(false); - - LLVoiceClient::getInstance()->inputUserControlState(true); -} - -// static -void LLAgent::releaseMicrophone(const LLSD& name) -{ - LLVoiceClient::getInstance()->inputUserControlState(false); -} - -// static -void LLAgent::toggleMicrophone(const LLSD& name) -{ - LLVoiceClient::getInstance()->toggleUserPTTState(); -} - -// static -bool LLAgent::isMicrophoneOn(const LLSD& sdname) -{ - return LLVoiceClient::getInstance()->getUserPTTState(); -} - -// ************************************************************ -// Enabled this definition to compile a 'hacked' viewer that -// locally believes the end user has godlike powers. -// #define HACKED_GODLIKE_VIEWER -// For a toggled version, see viewer.h for the -// TOGGLE_HACKED_GODLIKE_VIEWER define, instead. -// ************************************************************ - -// Constructors and Destructors - -// JC - Please try to make this order match the order in the header -// file. Otherwise it's hard to find variables that aren't initialized. -//----------------------------------------------------------------------------- -// LLAgent() -//----------------------------------------------------------------------------- -LLAgent::LLAgent() : - mGroupPowers(0), - mHideGroupTitle(false), - mGroupID(), - - mInitialized(false), - mListener(), - - mDoubleTapRunTimer(), - mDoubleTapRunMode(DOUBLETAP_NONE), - - mbAlwaysRun(false), - mbRunning(false), - mbTeleportKeepsLookAt(false), - - mAgentAccess(new LLAgentAccess(gSavedSettings)), - mGodLevelChangeSignal(), - mCanEditParcel(false), - mTeleportSourceSLURL(new LLSLURL), - mTeleportRequest(), - mTeleportFinishedSlot(), - mTeleportFailedSlot(), - mIsMaturityRatingChangingDuringTeleport(false), - mTPNeedsNeabyChatSeparator(false), - mMaturityRatingChange(0U), - mIsDoSendMaturityPreferenceToServer(false), - mMaturityPreferenceRequestId(0U), - mMaturityPreferenceResponseId(0U), - mMaturityPreferenceNumRetries(0U), - mLastKnownRequestMaturity(SIM_ACCESS_MIN), - mLastKnownResponseMaturity(SIM_ACCESS_MIN), - mHttpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID), - mTeleportState(TELEPORT_NONE), - mRegionp(NULL), - mInterestListMode(IL_MODE_DEFAULT), - - mAgentOriginGlobal(), - mPositionGlobal(), - mLastTestGlobal(), - - mDistanceTraveled(0.F), - mLastPositionGlobal(LLVector3d::zero), - - mRenderState(0), - mTypingTimer(), - - mViewsPushed(false), - - mCustomAnim(false), - mShowAvatar(true), - mFrameAgent(), - - mIsDoNotDisturb(false), - - mControlFlags(0x00000000), - mbFlagsDirty(false), - mbFlagsNeedReset(false), - - mAutoPilot(false), - mAutoPilotFlyOnStop(false), - mAutoPilotAllowFlying(true), - mAutoPilotTargetGlobal(), - mAutoPilotStopDistance(1.f), - mAutoPilotUseRotation(false), - mAutoPilotTargetFacing(LLVector3::zero), - mAutoPilotTargetDist(0.f), - mAutoPilotNoProgressFrameCount(0), - mAutoPilotRotationThreshold(0.f), - mAutoPilotFinishedCallback(NULL), - mAutoPilotCallbackData(NULL), - - mMovementKeysLocked(false), - - mEffectColor(new LLUIColor(LLColor4(0.f, 1.f, 1.f, 1.f))), - - mHaveHomePosition(false), - mHomeRegionHandle( 0 ), - mNearChatRadius(CHAT_NORMAL_RADIUS / 2.f), - - mNextFidgetTime(0.f), - mCurrentFidget(0), - mFirstLogin(false), - mOutfitChosen(false), - - mVoiceConnected(false), - - mMouselookModeInSignal(NULL), - mMouselookModeOutSignal(NULL) -{ - for (U32 i = 0; i < TOTAL_CONTROLS; i++) - { - mControlsTakenCount[i] = 0; - mControlsTakenPassedOnCount[i] = 0; - } - - mListener.reset(new LLAgentListener(*this)); - - addParcelChangedCallback(&setCanEditParcel); - - mMoveTimer.stop(); -} - -// Requires gSavedSettings to be initialized. -//----------------------------------------------------------------------------- -// init() -//----------------------------------------------------------------------------- -void LLAgent::init() -{ - mMoveTimer.start(); - - gSavedSettings.declareBOOL("SlowMotionAnimation", false, "Declared in code", LLControlVariable::PERSIST_NO); - gSavedSettings.getControl("SlowMotionAnimation")->getSignal()->connect(boost::bind(&handleSlowMotionAnimation, _2)); - - // *Note: this is where LLViewerCamera::getInstance() used to be constructed. - - bool is_flying = gSavedSettings.getBOOL("FlyingAtExit"); - if(is_flying) - { - setFlying(is_flying); - } - - *mEffectColor = LLUIColorTable::instance().getColor("EffectColor"); - - gSavedSettings.getControl("PreferredMaturity")->getValidateSignal()->connect(boost::bind(&LLAgent::validateMaturity, this, _2)); - gSavedSettings.getControl("PreferredMaturity")->getSignal()->connect(boost::bind(&LLAgent::handleMaturity, this, _2)); - mLastKnownResponseMaturity = static_cast(gSavedSettings.getU32("PreferredMaturity")); - mLastKnownRequestMaturity = mLastKnownResponseMaturity; - mIsDoSendMaturityPreferenceToServer = true; - - if (!mTeleportFinishedSlot.connected()) - { - mTeleportFinishedSlot = LLViewerParcelMgr::getInstance()->setTeleportFinishedCallback(boost::bind(&LLAgent::handleTeleportFinished, this)); - } - if (!mTeleportFailedSlot.connected()) - { - mTeleportFailedSlot = LLViewerParcelMgr::getInstance()->setTeleportFailedCallback(boost::bind(&LLAgent::handleTeleportFailed, this)); - } - - LLAppCoreHttp & app_core_http(LLAppViewer::instance()->getAppCoreHttp()); - - mHttpPolicy = app_core_http.getPolicy(LLAppCoreHttp::AP_AGENT); - - mInitialized = true; -} - -//----------------------------------------------------------------------------- -// cleanup() -//----------------------------------------------------------------------------- -void LLAgent::cleanup() -{ - mRegionp = NULL; - mTeleportRequest = NULL; - mTeleportCanceled = NULL; - if (mTeleportFinishedSlot.connected()) - { - mTeleportFinishedSlot.disconnect(); - } - if (mTeleportFailedSlot.connected()) - { - mTeleportFailedSlot.disconnect(); - } -} - -//----------------------------------------------------------------------------- -// LLAgent() -//----------------------------------------------------------------------------- -LLAgent::~LLAgent() -{ - cleanup(); - - delete mMouselookModeInSignal; - mMouselookModeInSignal = NULL; - delete mMouselookModeOutSignal; - mMouselookModeOutSignal = NULL; - - delete mAgentAccess; - mAgentAccess = NULL; - delete mEffectColor; - mEffectColor = NULL; - delete mTeleportSourceSLURL; - mTeleportSourceSLURL = NULL; -} - -// Handle any actions that need to be performed when the main app gains focus -// (such as through alt-tab). -//----------------------------------------------------------------------------- -// onAppFocusGained() -//----------------------------------------------------------------------------- -void LLAgent::onAppFocusGained() -{ - if (CAMERA_MODE_MOUSELOOK == gAgentCamera.getCameraMode()) - { - gAgentCamera.changeCameraToDefault(); - LLToolMgr::getInstance()->clearSavedTool(); - } -} - -void LLAgent::setFirstLogin(bool b) -{ - mFirstLogin = b; - - if (mFirstLogin) - { - // Don't notify new users about new features - if (getFeatureVersion() <= UI_FEATURE_VERSION) - { - setFeatureVersion(UI_FEATURE_VERSION, UI_FEATURE_FLAGS); - } - } -} - -void LLAgent::setFeatureVersion(S32 version, S32 flags) -{ - LLSD updated_version; - updated_version["version"] = version; - updated_version["flags"] = flags; - gSavedSettings.setLLSD("LastUIFeatureVersion", updated_version); -} - -S32 LLAgent::getFeatureVersion() -{ - S32 version; - S32 flags; - getFeatureVersionAndFlags(version, flags); - return version; -} - -void LLAgent::getFeatureVersionAndFlags(S32& version, S32& flags) -{ - version = 0; - flags = 0; - LLSD feature_version = gSavedSettings.getLLSD("LastUIFeatureVersion"); - if (feature_version.isInteger()) - { - version = feature_version.asInteger(); - flags = 1; // inventory flag - } - else if (feature_version.isMap()) - { - version = feature_version["version"]; - flags = feature_version["flags"]; - } - else if (!feature_version.isString() && !feature_version.isUndefined()) - { - // is something newer inside? - version = UI_FEATURE_VERSION; - flags = UI_FEATURE_FLAGS; - } -} - -void LLAgent::showLatestFeatureNotification(const std::string key) -{ - S32 version; - S32 flags; // a single release can have multiple new features - getFeatureVersionAndFlags(version, flags); - if (version <= UI_FEATURE_VERSION && (flags & UI_FEATURE_FLAGS) != UI_FEATURE_FLAGS) - { - S32 flag = 0; - - if (key == "inventory") - { - // Notify user about new thumbnail support - flag = 1; - } - - if (key == "gltf") - { - flag = 2; - } - - if ((flags & flag) == 0) - { - // Need to open on top even if called from onOpen, - // do on idle to make sure it's on top - LLSD floater_key(key); - doOnIdleOneTime([floater_key]() - { - LLFloaterReg::showInstance("new_feature_notification", floater_key); - }); - - setFeatureVersion(UI_FEATURE_VERSION, flags | flag); - } - } -} - -void LLAgent::ageChat() -{ - if (isAgentAvatarValid()) - { - // get amount of time since I last chatted - F64 elapsed_time = (F64)gAgentAvatarp->mChatTimer.getElapsedTimeF32(); - // add in frame time * 3 (so it ages 4x) - gAgentAvatarp->mChatTimer.setAge(elapsed_time + (F64)gFrameDTClamped * (CHAT_AGE_FAST_RATE - 1.0)); - } -} - -//----------------------------------------------------------------------------- -// moveAt() -//----------------------------------------------------------------------------- -void LLAgent::moveAt(S32 direction, bool reset) -{ - LLUIUsage::instance().logCommand("Agent.MoveAt"); - - mMoveTimer.reset(); - LLFirstUse::notMoving(false); - - // age chat timer so it fades more quickly when you are intentionally moving - ageChat(); - - gAgentCamera.setAtKey(LLAgentCamera::directionToKey(direction)); - - if (direction > 0) - { - setControlFlags(AGENT_CONTROL_AT_POS | AGENT_CONTROL_FAST_AT); - } - else if (direction < 0) - { - setControlFlags(AGENT_CONTROL_AT_NEG | AGENT_CONTROL_FAST_AT); - } - - if (reset) - { - gAgentCamera.resetView(); - } -} - -//----------------------------------------------------------------------------- -// moveAtNudge() -//----------------------------------------------------------------------------- -void LLAgent::moveAtNudge(S32 direction) -{ - mMoveTimer.reset(); - LLFirstUse::notMoving(false); - - // age chat timer so it fades more quickly when you are intentionally moving - ageChat(); - - gAgentCamera.setWalkKey(LLAgentCamera::directionToKey(direction)); - - if (direction > 0) - { - setControlFlags(AGENT_CONTROL_NUDGE_AT_POS); - } - else if (direction < 0) - { - setControlFlags(AGENT_CONTROL_NUDGE_AT_NEG); - } - - gAgentCamera.resetView(); -} - -//----------------------------------------------------------------------------- -// moveLeft() -//----------------------------------------------------------------------------- -void LLAgent::moveLeft(S32 direction) -{ - mMoveTimer.reset(); - LLFirstUse::notMoving(false); - - // age chat timer so it fades more quickly when you are intentionally moving - ageChat(); - - gAgentCamera.setLeftKey(LLAgentCamera::directionToKey(direction)); - - if (direction > 0) - { - setControlFlags(AGENT_CONTROL_LEFT_POS | AGENT_CONTROL_FAST_LEFT); - } - else if (direction < 0) - { - setControlFlags(AGENT_CONTROL_LEFT_NEG | AGENT_CONTROL_FAST_LEFT); - } - - gAgentCamera.resetView(); -} - -//----------------------------------------------------------------------------- -// moveLeftNudge() -//----------------------------------------------------------------------------- -void LLAgent::moveLeftNudge(S32 direction) -{ - mMoveTimer.reset(); - LLFirstUse::notMoving(false); - - // age chat timer so it fades more quickly when you are intentionally moving - ageChat(); - - gAgentCamera.setLeftKey(LLAgentCamera::directionToKey(direction)); - - if (direction > 0) - { - setControlFlags(AGENT_CONTROL_NUDGE_LEFT_POS); - } - else if (direction < 0) - { - setControlFlags(AGENT_CONTROL_NUDGE_LEFT_NEG); - } - - gAgentCamera.resetView(); -} - -//----------------------------------------------------------------------------- -// moveUp() -//----------------------------------------------------------------------------- -void LLAgent::moveUp(S32 direction) -{ - mMoveTimer.reset(); - LLFirstUse::notMoving(false); - - // age chat timer so it fades more quickly when you are intentionally moving - ageChat(); - - gAgentCamera.setUpKey(LLAgentCamera::directionToKey(direction)); - - if (direction > 0) - { - setControlFlags(AGENT_CONTROL_UP_POS | AGENT_CONTROL_FAST_UP); - } - else if (direction < 0) - { - setControlFlags(AGENT_CONTROL_UP_NEG | AGENT_CONTROL_FAST_UP); - } - - gAgentCamera.resetView(); -} - -//----------------------------------------------------------------------------- -// moveYaw() -//----------------------------------------------------------------------------- -void LLAgent::moveYaw(F32 mag, bool reset_view) -{ - gAgentCamera.setYawKey(mag); - - if (mag > 0) - { - setControlFlags(AGENT_CONTROL_YAW_POS); - } - else if (mag < 0) - { - setControlFlags(AGENT_CONTROL_YAW_NEG); - } - - U32 mask = AGENT_CONTROL_YAW_POS | AGENT_CONTROL_YAW_NEG; - if ((getControlFlags() & mask) == mask) - { - // Rotation into both directions should cancel out - // But keep sending controls to simulator, - // it's needed for script based controls - gAgentCamera.setYawKey(0); - } - - if (reset_view) - { - gAgentCamera.resetView(); - } -} - -//----------------------------------------------------------------------------- -// movePitch() -//----------------------------------------------------------------------------- -void LLAgent::movePitch(F32 mag) -{ - gAgentCamera.setPitchKey(mag); - - if (mag > 0) - { - setControlFlags(AGENT_CONTROL_PITCH_POS); - } - else if (mag < 0) - { - setControlFlags(AGENT_CONTROL_PITCH_NEG); - } -} - - -// Does this parcel allow you to fly? -bool LLAgent::canFly() -{ - if (isGodlike()) return true; - - LLViewerRegion* regionp = getRegion(); - if (regionp && regionp->getBlockFly()) return false; - - LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - if (!parcel) return false; - - // Allow owners to fly on their own land. - if (LLViewerParcelMgr::isParcelOwnedByAgent(parcel, GP_LAND_ALLOW_FLY)) - { - return true; - } - - return parcel->getAllowFly(); -} - -bool LLAgent::getFlying() const -{ - return mControlFlags & AGENT_CONTROL_FLY; -} - -//----------------------------------------------------------------------------- -// setFlying() -//----------------------------------------------------------------------------- -void LLAgent::setFlying(bool fly, bool fail_sound) -{ - if (isAgentAvatarValid()) - { - // *HACK: Don't allow to start the flying mode if we got ANIM_AGENT_STANDUP signal - // because in this case we won't get a signal to start avatar flying animation and - // it will be walking with flying mode "ON" indication. However we allow to switch - // the flying mode off if we get ANIM_AGENT_STANDUP signal. See process_avatar_animation(). - // See EXT-2781. - if(fly && gAgentAvatarp->mSignaledAnimations.find(ANIM_AGENT_STANDUP) != gAgentAvatarp->mSignaledAnimations.end()) - { - return; - } - - // don't allow taking off while sitting - if (fly && gAgentAvatarp->isSitting()) - { - return; - } - } - - if (fly) - { - bool was_flying = getFlying(); - if (!canFly() && !was_flying) - { - // parcel doesn't let you start fly - // gods can always fly - // and it's OK if you're already flying - if (fail_sound) - { - make_ui_sound("UISndBadKeystroke"); - } - return; - } - if( !was_flying ) - { - add(LLStatViewer::FLY, 1); - } - setControlFlags(AGENT_CONTROL_FLY); - } - else - { - clearControlFlags(AGENT_CONTROL_FLY); - } - - - // Update Movement Controls according to Fly mode - LLFloaterMove::setFlyingMode(fly); - - mbFlagsDirty = true; -} - - -// UI based mechanism of setting fly state -//----------------------------------------------------------------------------- -// toggleFlying() -//----------------------------------------------------------------------------- -// static -void LLAgent::toggleFlying() -{ - if ( gAgent.mAutoPilot ) - { - LLToolPie::instance().stopClickToWalk(); - } - - bool fly = !gAgent.getFlying(); - - gAgent.mMoveTimer.reset(); - LLFirstUse::notMoving(false); - - gAgent.setFlying( fly ); - gAgentCamera.resetView(); -} - -// static -bool LLAgent::enableFlying() -{ - bool sitting = false; - if (isAgentAvatarValid()) - { - sitting = gAgentAvatarp->isSitting(); - } - return !sitting; -} - -// static -bool LLAgent::isSitting() -{ - bool sitting = false; - if (isAgentAvatarValid()) - { - sitting = gAgentAvatarp->isSitting(); - } - return sitting; -} - -void LLAgent::standUp() -{ - setControlFlags(AGENT_CONTROL_STAND_UP); -} - -void LLAgent::changeParcels() -{ - LL_DEBUGS("AgentLocation") << "Calling ParcelChanged callbacks" << LL_ENDL; - // Notify anything that wants to know about parcel changes - mParcelChangedSignal(); -} - -boost::signals2::connection LLAgent::addParcelChangedCallback(parcel_changed_callback_t cb) -{ - return mParcelChangedSignal.connect(cb); -} - -// static -void LLAgent::capabilityReceivedCallback(const LLUUID ®ion_id, LLViewerRegion *regionp) -{ // Changed regions and now have the region capabilities - if (regionp) - { - if (regionp->getRegionID() == region_id) - { - regionp->requestSimulatorFeatures(); - LLAppViewer::instance()->updateNameLookupUrl(regionp); - } - - if (gAgent.getInterestListMode() == IL_MODE_360) - { - gAgent.changeInterestListMode(IL_MODE_360); - } - } -} - - -//----------------------------------------------------------------------------- -// setRegion() -//----------------------------------------------------------------------------- -void LLAgent::setRegion(LLViewerRegion *regionp) -{ - llassert(regionp); - if (mRegionp != regionp) - { - - LL_INFOS("AgentLocation","Teleport") << "Moving agent into region: handle " << regionp->getHandle() - << " id " << regionp->getRegionID() - << " name " << regionp->getName() - << " previous region " - << (mRegionp ? mRegionp->getRegionID() : LLUUID::null) - << LL_ENDL; - if (mRegionp) - { - // We've changed regions, we're now going to change our agent coordinate frame. - mAgentOriginGlobal = regionp->getOriginGlobal(); - LLVector3d agent_offset_global = mRegionp->getOriginGlobal(); - - LLVector3 delta; - delta.setVec(regionp->getOriginGlobal() - mRegionp->getOriginGlobal()); - - setPositionAgent(getPositionAgent() - delta); - - LLVector3 camera_position_agent = LLViewerCamera::getInstance()->getOrigin(); - LLViewerCamera::getInstance()->setOrigin(camera_position_agent - delta); - - // Update all of the regions. - LLWorld::getInstance()->updateAgentOffset(agent_offset_global); - - // Hack to keep sky in the agent's region, otherwise it may get deleted - DJS 08/02/02 - // *TODO: possibly refactor into gSky->setAgentRegion(regionp)? -Brad - if (gSky.mVOSkyp) - { - gSky.mVOSkyp->setRegion(regionp); - } - - if (regionp->capabilitiesReceived()) - { - regionp->requestSimulatorFeatures(); - LLAppViewer::instance()->updateNameLookupUrl(regionp); - } - else - { - regionp->setCapabilitiesReceivedCallback(LLAgent::capabilityReceivedCallback); - } - - } - else - { - // First time initialization. - // We've changed regions, we're now going to change our agent coordinate frame. - mAgentOriginGlobal = regionp->getOriginGlobal(); - - LLVector3 delta; - delta.setVec(regionp->getOriginGlobal()); - - setPositionAgent(getPositionAgent() - delta); - LLVector3 camera_position_agent = LLViewerCamera::getInstance()->getOrigin(); - LLViewerCamera::getInstance()->setOrigin(camera_position_agent - delta); - - // Update all of the regions. - LLWorld::getInstance()->updateAgentOffset(mAgentOriginGlobal); - - if (regionp->capabilitiesReceived()) - { - LLAppViewer::instance()->updateNameLookupUrl(regionp); - } - else - { - regionp->setCapabilitiesReceivedCallback([](const LLUUID ®ion_id, LLViewerRegion* regionp) {LLAppViewer::instance()->updateNameLookupUrl(regionp); }); - } - } - - // Pass new region along to metrics components that care about this level of detail. - LLAppViewer::metricsUpdateRegion(regionp->getHandle()); - } - - mRegionp = regionp; - - // TODO - most of what follows probably should be moved into callbacks - - // Pass the region host to LLUrlEntryParcel to resolve parcel name - // with a server request. - LLUrlEntryParcel::setRegionHost(getRegionHost()); - - // Must shift hole-covering water object locations because local - // coordinate frame changed. - LLWorld::getInstance()->updateWaterObjects(); - - // keep a list of regions we've been too - // this is just an interesting stat, logged at the dataserver - // we could trake this at the dataserver side, but that's harder - U64 handle = regionp->getHandle(); - mRegionsVisited.insert(handle); - - LLSelectMgr::getInstance()->updateSelectionCenter(); - - LLFloaterMove::sUpdateFlyingStatus(); - - LL_DEBUGS("AgentLocation") << "Calling RegionChanged callbacks" << LL_ENDL; - mRegionChangedSignal(); -} - - -//----------------------------------------------------------------------------- -// getRegion() -//----------------------------------------------------------------------------- -LLViewerRegion *LLAgent::getRegion() const -{ - return mRegionp; -} - - -LLHost LLAgent::getRegionHost() const -{ - if (mRegionp) - { - return mRegionp->getHost(); - } - else - { - return LLHost(); - } -} - -boost::signals2::connection LLAgent::addRegionChangedCallback(const region_changed_signal_t::slot_type& cb) -{ - return mRegionChangedSignal.connect(cb); -} - -void LLAgent::removeRegionChangedCallback(boost::signals2::connection callback) -{ - mRegionChangedSignal.disconnect(callback); -} - -//----------------------------------------------------------------------------- -// inPrelude() -//----------------------------------------------------------------------------- -bool LLAgent::inPrelude() -{ - return mRegionp && mRegionp->isPrelude(); -} - - -std::string LLAgent::getRegionCapability(const std::string &name) -{ - if (!mRegionp) - return std::string(); - - return mRegionp->getCapability(name); -} - - -//----------------------------------------------------------------------------- -// canManageEstate() -//----------------------------------------------------------------------------- - -bool LLAgent::canManageEstate() const -{ - return mRegionp && mRegionp->canManageEstate(); -} - -//----------------------------------------------------------------------------- -// sendMessage() -//----------------------------------------------------------------------------- -void LLAgent::sendMessage() -{ - if (gDisconnected) - { - LL_WARNS() << "Trying to send message when disconnected!" << LL_ENDL; - return; - } - if (!mRegionp) - { - LL_ERRS() << "No region for agent yet!" << LL_ENDL; - return; - } - gMessageSystem->sendMessage(mRegionp->getHost()); -} - - -//----------------------------------------------------------------------------- -// sendReliableMessage() -//----------------------------------------------------------------------------- -void LLAgent::sendReliableMessage() -{ - if (gDisconnected) - { - LL_DEBUGS() << "Trying to send message when disconnected!" << LL_ENDL; - return; - } - if (!mRegionp) - { - LL_DEBUGS() << "LLAgent::sendReliableMessage No region for agent yet, not sending message!" << LL_ENDL; - return; - } - gMessageSystem->sendReliable(mRegionp->getHost()); -} - -//----------------------------------------------------------------------------- -// getVelocity() -//----------------------------------------------------------------------------- -LLVector3 LLAgent::getVelocity() const -{ - if (isAgentAvatarValid()) - { - return gAgentAvatarp->getVelocity(); - } - else - { - return LLVector3::zero; - } -} - - -//----------------------------------------------------------------------------- -// setPositionAgent() -//----------------------------------------------------------------------------- -void LLAgent::setPositionAgent(const LLVector3 &pos_agent) -{ - if (!pos_agent.isFinite()) - { - LL_ERRS() << "setPositionAgent is not a number" << LL_ENDL; - } - - if (isAgentAvatarValid() && gAgentAvatarp->getParent()) - { - LLVector3 pos_agent_sitting; - LLVector3d pos_agent_d; - LLViewerObject *parent = (LLViewerObject*)gAgentAvatarp->getParent(); - - pos_agent_sitting = gAgentAvatarp->getPosition() * parent->getRotation() + parent->getPositionAgent(); - pos_agent_d.setVec(pos_agent_sitting); - - mFrameAgent.setOrigin(pos_agent_sitting); - mPositionGlobal = pos_agent_d + mAgentOriginGlobal; - } - else - { - mFrameAgent.setOrigin(pos_agent); - - LLVector3d pos_agent_d; - pos_agent_d.setVec(pos_agent); - mPositionGlobal = pos_agent_d + mAgentOriginGlobal; - } - - if (((mLastTestGlobal - mPositionGlobal).lengthSquared() > 1.0) && !mOnPositionChanged.empty()) - { // If the position has changed my more than 1 meter since the last time we triggered. - // filters out some noise. - mLastTestGlobal = mPositionGlobal; - mOnPositionChanged(mFrameAgent.getOrigin(), mPositionGlobal); - } -} - -//----------------------------------------------------------------------------- -// getPositionGlobal() -//----------------------------------------------------------------------------- -const LLVector3d &LLAgent::getPositionGlobal() const -{ - if (isAgentAvatarValid() && !gAgentAvatarp->mDrawable.isNull()) - { - mPositionGlobal = getPosGlobalFromAgent(gAgentAvatarp->getRenderPosition()); - } - else - { - mPositionGlobal = getPosGlobalFromAgent(mFrameAgent.getOrigin()); - } - - return mPositionGlobal; -} - -//----------------------------------------------------------------------------- -// getPositionAgent() -//----------------------------------------------------------------------------- -const LLVector3 &LLAgent::getPositionAgent() -{ - if (isAgentAvatarValid()) - { - if(gAgentAvatarp->mDrawable.isNull()) - { - mFrameAgent.setOrigin(gAgentAvatarp->getPositionAgent()); - } - else - { - mFrameAgent.setOrigin(gAgentAvatarp->getRenderPosition()); - } - } - - - return mFrameAgent.getOrigin(); -} - -boost::signals2::connection LLAgent::whenPositionChanged(position_signal_t::slot_type fn) -{ - return mOnPositionChanged.connect(fn); -} - - -//----------------------------------------------------------------------------- -// getRegionsVisited() -//----------------------------------------------------------------------------- -S32 LLAgent::getRegionsVisited() const -{ - return mRegionsVisited.size(); -} - -//----------------------------------------------------------------------------- -// getDistanceTraveled() -//----------------------------------------------------------------------------- -F64 LLAgent::getDistanceTraveled() const -{ - return mDistanceTraveled; -} - - -//----------------------------------------------------------------------------- -// getPosAgentFromGlobal() -//----------------------------------------------------------------------------- -LLVector3 LLAgent::getPosAgentFromGlobal(const LLVector3d &pos_global) const -{ - LLVector3 pos_agent; - pos_agent.setVec(pos_global - mAgentOriginGlobal); - return pos_agent; -} - - -//----------------------------------------------------------------------------- -// getPosGlobalFromAgent() -//----------------------------------------------------------------------------- -LLVector3d LLAgent::getPosGlobalFromAgent(const LLVector3 &pos_agent) const -{ - LLVector3d pos_agent_d; - pos_agent_d.setVec(pos_agent); - return pos_agent_d + mAgentOriginGlobal; -} - -void LLAgent::sitDown() -{ - setControlFlags(AGENT_CONTROL_SIT_ON_GROUND); -} - - -//----------------------------------------------------------------------------- -// resetAxes() -//----------------------------------------------------------------------------- -void LLAgent::resetAxes() -{ - mFrameAgent.resetAxes(); -} - - -// Copied from LLCamera::setOriginAndLookAt -// Look_at must be unit vector -//----------------------------------------------------------------------------- -// resetAxes() -//----------------------------------------------------------------------------- -void LLAgent::resetAxes(const LLVector3 &look_at) -{ - LLVector3 skyward = getReferenceUpVector(); - - // if look_at has zero length, fail - // if look_at and skyward are parallel, fail - // - // Test both of these conditions with a cross product. - LLVector3 cross(look_at % skyward); - if (cross.isNull()) - { - LL_INFOS() << "LLAgent::resetAxes cross-product is zero" << LL_ENDL; - return; - } - - // Make sure look_at and skyward are not parallel - // and neither are zero length - LLVector3 left(skyward % look_at); - LLVector3 up(look_at % left); - - mFrameAgent.setAxes(look_at, left, up); -} - - -//----------------------------------------------------------------------------- -// rotate() -//----------------------------------------------------------------------------- -void LLAgent::rotate(F32 angle, const LLVector3 &axis) -{ - mFrameAgent.rotate(angle, axis); -} - - -//----------------------------------------------------------------------------- -// rotate() -//----------------------------------------------------------------------------- -void LLAgent::rotate(F32 angle, F32 x, F32 y, F32 z) -{ - mFrameAgent.rotate(angle, x, y, z); -} - - -//----------------------------------------------------------------------------- -// rotate() -//----------------------------------------------------------------------------- -void LLAgent::rotate(const LLMatrix3 &matrix) -{ - mFrameAgent.rotate(matrix); -} - - -//----------------------------------------------------------------------------- -// rotate() -//----------------------------------------------------------------------------- -void LLAgent::rotate(const LLQuaternion &quaternion) -{ - mFrameAgent.rotate(quaternion); -} - - -//----------------------------------------------------------------------------- -// getReferenceUpVector() -//----------------------------------------------------------------------------- -LLVector3 LLAgent::getReferenceUpVector() -{ - // this vector is in the coordinate frame of the avatar's parent object, or the world if none - LLVector3 up_vector = LLVector3::z_axis; - if (isAgentAvatarValid() && - gAgentAvatarp->getParent() && - gAgentAvatarp->mDrawable.notNull()) - { - U32 camera_mode = gAgentCamera.getCameraAnimating() ? gAgentCamera.getLastCameraMode() : gAgentCamera.getCameraMode(); - // and in third person... - if (camera_mode == CAMERA_MODE_THIRD_PERSON) - { - // make the up vector point to the absolute +z axis - up_vector = up_vector * ~((LLViewerObject*)gAgentAvatarp->getParent())->getRenderRotation(); - } - else if (camera_mode == CAMERA_MODE_MOUSELOOK) - { - // make the up vector point to the avatar's +z axis - up_vector = up_vector * gAgentAvatarp->mDrawable->getRotation(); - } - } - - return up_vector; -} - - -// Radians, positive is forward into ground -//----------------------------------------------------------------------------- -// pitch() -//----------------------------------------------------------------------------- -void LLAgent::pitch(F32 angle) -{ - // don't let user pitch if pointed almost all the way down or up - - // A dot B = mag(A) * mag(B) * cos(angle between A and B) - // so... cos(angle between A and B) = A dot B / mag(A) / mag(B) - // = A dot B for unit vectors - - LLVector3 skyward = getReferenceUpVector(); - - // clamp pitch to limits - if (angle >= 0.f) - { - const F32 look_down_limit = 179.f * DEG_TO_RAD; - F32 angle_from_skyward = acos(mFrameAgent.getAtAxis() * skyward); - if (angle_from_skyward + angle > look_down_limit) - { - angle = look_down_limit - angle_from_skyward; - } - } - else if (angle < 0.f) - { - const F32 look_up_limit = 5.f * DEG_TO_RAD; - const LLVector3& viewer_camera_pos = LLViewerCamera::getInstance()->getOrigin(); - LLVector3 agent_focus_pos = getPosAgentFromGlobal(gAgentCamera.calcFocusPositionTargetGlobal()); - LLVector3 look_dir = agent_focus_pos - viewer_camera_pos; - F32 angle_from_skyward = angle_between(look_dir, skyward); - if (angle_from_skyward + angle < look_up_limit) - { - angle = look_up_limit - angle_from_skyward; - } - } - - if (fabs(angle) > 1e-4) - { - mFrameAgent.pitch(angle); - } -} - - -//----------------------------------------------------------------------------- -// roll() -//----------------------------------------------------------------------------- -void LLAgent::roll(F32 angle) -{ - mFrameAgent.roll(angle); -} - - -//----------------------------------------------------------------------------- -// yaw() -//----------------------------------------------------------------------------- -void LLAgent::yaw(F32 angle) -{ - if (!rotateGrabbed()) - { - mFrameAgent.rotate(angle, getReferenceUpVector()); - } -} - - -// Returns a quat that represents the rotation of the agent in the absolute frame -//----------------------------------------------------------------------------- -// getQuat() -//----------------------------------------------------------------------------- -LLQuaternion LLAgent::getQuat() const -{ - return mFrameAgent.getQuaternion(); -} - -//----------------------------------------------------------------------------- -// getControlFlags() -//----------------------------------------------------------------------------- -U32 LLAgent::getControlFlags() -{ - return mControlFlags; -} - -//----------------------------------------------------------------------------- -// setControlFlags() -//----------------------------------------------------------------------------- -void LLAgent::setControlFlags(U32 mask) -{ - mControlFlags |= mask; - mbFlagsDirty = true; -} - - -//----------------------------------------------------------------------------- -// clearControlFlags() -//----------------------------------------------------------------------------- -void LLAgent::clearControlFlags(U32 mask) -{ - U32 old_flags = mControlFlags; - mControlFlags &= ~mask; - if (old_flags != mControlFlags) - { - mbFlagsDirty = true; - } -} - -//----------------------------------------------------------------------------- -// controlFlagsDirty() -//----------------------------------------------------------------------------- -bool LLAgent::controlFlagsDirty() const -{ - return mbFlagsDirty; -} - -//----------------------------------------------------------------------------- -// enableControlFlagReset() -//----------------------------------------------------------------------------- -void LLAgent::enableControlFlagReset() -{ - mbFlagsNeedReset = true; -} - -//----------------------------------------------------------------------------- -// resetControlFlags() -//----------------------------------------------------------------------------- -void LLAgent::resetControlFlags() -{ - if (mbFlagsNeedReset) - { - mbFlagsNeedReset = false; - mbFlagsDirty = false; - // reset all of the ephemeral flags - // some flags are managed elsewhere - mControlFlags &= AGENT_CONTROL_AWAY | AGENT_CONTROL_FLY | AGENT_CONTROL_MOUSELOOK; - } -} - -//----------------------------------------------------------------------------- -// setAFK() -//----------------------------------------------------------------------------- -void LLAgent::setAFK() -{ - if (gNonInteractive || !gAgent.getRegion()) - { - // Don't set AFK if we're not talking to a region yet. - return; - } - - if (!(mControlFlags & AGENT_CONTROL_AWAY)) - { - sendAnimationRequest(ANIM_AGENT_AWAY, ANIM_REQUEST_START); - setControlFlags(AGENT_CONTROL_AWAY | AGENT_CONTROL_STOP); - gAwayTimer.start(); - } -} - -//----------------------------------------------------------------------------- -// clearAFK() -//----------------------------------------------------------------------------- -void LLAgent::clearAFK() -{ - gAwayTriggerTimer.reset(); - - // Gods can sometimes get into away state (via gestures) - // without setting the appropriate control flag. JC - if (mControlFlags & AGENT_CONTROL_AWAY - || (isAgentAvatarValid() - && (gAgentAvatarp->mSignaledAnimations.find(ANIM_AGENT_AWAY) != gAgentAvatarp->mSignaledAnimations.end()))) - { - sendAnimationRequest(ANIM_AGENT_AWAY, ANIM_REQUEST_STOP); - clearControlFlags(AGENT_CONTROL_AWAY); - } -} - -//----------------------------------------------------------------------------- -// getAFK() -//----------------------------------------------------------------------------- -bool LLAgent::getAFK() const -{ - return (mControlFlags & AGENT_CONTROL_AWAY) != 0; -} - -//----------------------------------------------------------------------------- -// setDoNotDisturb() -//----------------------------------------------------------------------------- -void LLAgent::setDoNotDisturb(bool pIsDoNotDisturb) -{ - bool isDoNotDisturbSwitchedOff = (mIsDoNotDisturb && !pIsDoNotDisturb); - mIsDoNotDisturb = pIsDoNotDisturb; - sendAnimationRequest(ANIM_AGENT_DO_NOT_DISTURB, (pIsDoNotDisturb ? ANIM_REQUEST_START : ANIM_REQUEST_STOP)); - LLNotificationsUI::LLChannelManager::getInstance()->muteAllChannels(pIsDoNotDisturb); - if (isDoNotDisturbSwitchedOff) - { - LLDoNotDisturbNotificationStorage::getInstance()->updateNotifications(); - } - gIMMgr->updateDNDMessageStatus(); -} - -//----------------------------------------------------------------------------- -// isDoNotDisturb() -//----------------------------------------------------------------------------- -bool LLAgent::isDoNotDisturb() const -{ - return mIsDoNotDisturb; -} - - -//----------------------------------------------------------------------------- -// startAutoPilotGlobal() -//----------------------------------------------------------------------------- -void LLAgent::startAutoPilotGlobal( - const LLVector3d &target_global, - const std::string& behavior_name, - const LLQuaternion *target_rotation, - void (*finish_callback)(bool, void *), - void *callback_data, - F32 stop_distance, - F32 rot_threshold, - bool allow_flying) -{ - if (!isAgentAvatarValid()) - { - return; - } - - if (target_global.isExactlyZero()) - { - LL_WARNS() << "Canceling attempt to start autopilot towards invalid position" << LL_ENDL; - return; - } - - // Are there any pending callbacks from previous auto pilot requests? - if (mAutoPilotFinishedCallback) - { - mAutoPilotFinishedCallback(dist_vec(gAgent.getPositionGlobal(), mAutoPilotTargetGlobal) < mAutoPilotStopDistance, mAutoPilotCallbackData); - } - - mAutoPilotFinishedCallback = finish_callback; - mAutoPilotCallbackData = callback_data; - mAutoPilotRotationThreshold = rot_threshold; - mAutoPilotBehaviorName = behavior_name; - mAutoPilotAllowFlying = allow_flying; - - LLVector3d delta_pos( target_global ); - delta_pos -= getPositionGlobal(); - F64 distance = delta_pos.magVec(); - LLVector3d trace_target = target_global; - - trace_target.mdV[VZ] -= 10.f; - - LLVector3d intersection; - LLVector3 normal; - LLViewerObject *hit_obj; - F32 heightDelta = LLWorld::getInstance()->resolveStepHeightGlobal(NULL, target_global, trace_target, intersection, normal, &hit_obj); - - if (stop_distance > 0.f) - { - mAutoPilotStopDistance = stop_distance; - } - else - { - // Guess at a reasonable stop distance. - mAutoPilotStopDistance = (F32) sqrt( distance ); - if (mAutoPilotStopDistance < 0.5f) - { - mAutoPilotStopDistance = 0.5f; - } - } - - if (mAutoPilotAllowFlying) - { - mAutoPilotFlyOnStop = getFlying(); - } - else - { - mAutoPilotFlyOnStop = false; - } - - if (distance > 30.0 && mAutoPilotAllowFlying) - { - setFlying(true); - } - - if ( distance > 1.f && - mAutoPilotAllowFlying && - heightDelta > (sqrtf(mAutoPilotStopDistance) + 1.f)) - { - setFlying(true); - // Do not force flying for "Sit" behavior to prevent flying after pressing "Stand" - // from an object. See EXT-1655. - if ("Sit" != mAutoPilotBehaviorName) - mAutoPilotFlyOnStop = true; - } - - mAutoPilot = true; - setAutoPilotTargetGlobal(target_global); - - if (target_rotation) - { - mAutoPilotUseRotation = true; - mAutoPilotTargetFacing = LLVector3::x_axis * *target_rotation; - mAutoPilotTargetFacing.mV[VZ] = 0.f; - mAutoPilotTargetFacing.normalize(); - } - else - { - mAutoPilotUseRotation = false; - } - - mAutoPilotNoProgressFrameCount = 0; -} - - -//----------------------------------------------------------------------------- -// setAutoPilotTargetGlobal -//----------------------------------------------------------------------------- -void LLAgent::setAutoPilotTargetGlobal(const LLVector3d &target_global) -{ - if (mAutoPilot) - { - mAutoPilotTargetGlobal = target_global; - - // trace ray down to find height of destination from ground - LLVector3d traceEndPt = target_global; - traceEndPt.mdV[VZ] -= 20.f; - - LLVector3d targetOnGround; - LLVector3 groundNorm; - LLViewerObject *obj; - - LLWorld::getInstance()->resolveStepHeightGlobal(NULL, target_global, traceEndPt, targetOnGround, groundNorm, &obj); - // Note: this might malfunction for sitting agent, since pelvis stays same, but agent's position becomes lower - // But for autopilot to work we assume that agent is standing and ready to go. - F64 target_height = llmax((F64)gAgentAvatarp->getPelvisToFoot(), target_global.mdV[VZ] - targetOnGround.mdV[VZ]); - - // clamp z value of target to minimum height above ground - mAutoPilotTargetGlobal.mdV[VZ] = targetOnGround.mdV[VZ] + target_height; - mAutoPilotTargetDist = (F32)dist_vec(gAgent.getPositionGlobal(), mAutoPilotTargetGlobal); - } -} - -//----------------------------------------------------------------------------- -// startFollowPilot() -//----------------------------------------------------------------------------- -void LLAgent::startFollowPilot(const LLUUID &leader_id, bool allow_flying, F32 stop_distance) -{ - mLeaderID = leader_id; - if ( mLeaderID.isNull() ) return; - - LLViewerObject* object = gObjectList.findObject(mLeaderID); - if (!object) - { - mLeaderID = LLUUID::null; - return; - } - - startAutoPilotGlobal(object->getPositionGlobal(), - std::string(), // behavior_name - NULL, // target_rotation - NULL, // finish_callback - NULL, // callback_data - stop_distance, - 0.03f, // rotation_threshold - allow_flying); -} - - -//----------------------------------------------------------------------------- -// stopAutoPilot() -//----------------------------------------------------------------------------- -void LLAgent::stopAutoPilot(bool user_cancel) -{ - if (mAutoPilot) - { - mAutoPilot = false; - if (mAutoPilotUseRotation && !user_cancel) - { - resetAxes(mAutoPilotTargetFacing); - } - // Restore previous flying state before invoking mAutoPilotFinishedCallback to allow - // callback function to change the flying state (like in near_sit_down_point()). - // If the user cancelled, don't change the fly state - if (!user_cancel) - { - setFlying(mAutoPilotFlyOnStop); - } - //NB: auto pilot can terminate for a reason other than reaching the destination - if (mAutoPilotFinishedCallback) - { - mAutoPilotFinishedCallback(!user_cancel && dist_vec(gAgent.getPositionGlobal(), mAutoPilotTargetGlobal) < mAutoPilotStopDistance, mAutoPilotCallbackData); - mAutoPilotFinishedCallback = NULL; - } - mLeaderID = LLUUID::null; - - setControlFlags(AGENT_CONTROL_STOP); - - if (user_cancel && !mAutoPilotBehaviorName.empty()) - { - if (mAutoPilotBehaviorName == "Sit") - LL_INFOS("Agent") << "Autopilot-Sit was canceled by user action" << LL_ENDL; - else if (mAutoPilotBehaviorName == "Attach") - LLNotificationsUtil::add("CancelledAttach"); - else - LLNotificationsUtil::add("Cancelled"); - } - } -} - - -// Returns necessary agent pitch and yaw changes, radians. -//----------------------------------------------------------------------------- -// autoPilot() -//----------------------------------------------------------------------------- -void LLAgent::autoPilot(F32 *delta_yaw) -{ - if (mAutoPilot) - { - if (!mLeaderID.isNull()) - { - LLViewerObject* object = gObjectList.findObject(mLeaderID); - if (!object) - { - stopAutoPilot(); - return; - } - mAutoPilotTargetGlobal = object->getPositionGlobal(); - } - - if (!isAgentAvatarValid()) return; - - if (gAgentAvatarp->mInAir && mAutoPilotAllowFlying) - { - setFlying(true); - } - - LLVector3 at; - at.setVec(mFrameAgent.getAtAxis()); - LLVector3 target_agent = getPosAgentFromGlobal(mAutoPilotTargetGlobal); - LLVector3 direction = target_agent - getPositionAgent(); - - F32 target_dist = direction.magVec(); - - if (target_dist >= mAutoPilotTargetDist) - { - mAutoPilotNoProgressFrameCount++; - bool out_of_time = false; - if (getFlying()) - { - out_of_time = mAutoPilotNoProgressFrameCount > AUTOPILOT_MAX_TIME_NO_PROGRESS_FLY * gFPSClamped; - } - else - { - out_of_time = mAutoPilotNoProgressFrameCount > AUTOPILOT_MAX_TIME_NO_PROGRESS_WALK * gFPSClamped; - } - if (out_of_time) - { - stopAutoPilot(); - return; - } - } - - mAutoPilotTargetDist = target_dist; - - // Make this a two-dimensional solution - at.mV[VZ] = 0.f; - direction.mV[VZ] = 0.f; - - at.normalize(); - F32 xy_distance = direction.normalize(); - - F32 yaw = 0.f; - if (mAutoPilotTargetDist > mAutoPilotStopDistance) - { - yaw = angle_between(mFrameAgent.getAtAxis(), direction); - } - else if (mAutoPilotUseRotation) - { - // we're close now just aim at target facing - yaw = angle_between(at, mAutoPilotTargetFacing); - direction = mAutoPilotTargetFacing; - } - - yaw = 4.f * yaw / gFPSClamped; - - // figure out which direction to turn - LLVector3 scratch(at % direction); - - if (scratch.mV[VZ] > 0.f) - { - setControlFlags(AGENT_CONTROL_YAW_POS); - } - else - { - yaw = -yaw; - setControlFlags(AGENT_CONTROL_YAW_NEG); - } - - *delta_yaw = yaw; - - // Compute when to start slowing down - F32 slow_distance; - if (getFlying()) - { - slow_distance = llmax(8.f, mAutoPilotStopDistance + 5.f); - } - else - { - slow_distance = llmax(3.f, mAutoPilotStopDistance + 2.f); - } - - // If we're flying, handle autopilot points above or below you. - if (getFlying() && xy_distance < AUTOPILOT_HEIGHT_ADJUST_DISTANCE) - { - if (isAgentAvatarValid()) - { - F64 current_height = gAgentAvatarp->getPositionGlobal().mdV[VZ]; - F32 delta_z = (F32)(mAutoPilotTargetGlobal.mdV[VZ] - current_height); - F32 slope = delta_z / xy_distance; - if (slope > 0.45f && delta_z > 6.f) - { - setControlFlags(AGENT_CONTROL_FAST_UP | AGENT_CONTROL_UP_POS); - } - else if (slope > 0.002f && delta_z > 0.5f) - { - setControlFlags(AGENT_CONTROL_UP_POS); - } - else if (slope < -0.45f && delta_z < -6.f && current_height > AUTOPILOT_MIN_TARGET_HEIGHT_OFF_GROUND) - { - setControlFlags(AGENT_CONTROL_FAST_UP | AGENT_CONTROL_UP_NEG); - } - else if (slope < -0.002f && delta_z < -0.5f && current_height > AUTOPILOT_MIN_TARGET_HEIGHT_OFF_GROUND) - { - setControlFlags(AGENT_CONTROL_UP_NEG); - } - } - } - - // calculate delta rotation to target heading - F32 delta_target_heading = angle_between(mFrameAgent.getAtAxis(), mAutoPilotTargetFacing); - - if (xy_distance > slow_distance && yaw < (F_PI / 10.f)) - { - // walking/flying fast - setControlFlags(AGENT_CONTROL_FAST_AT | AGENT_CONTROL_AT_POS); - } - else if (mAutoPilotTargetDist > mAutoPilotStopDistance) - { - // walking/flying slow - U32 movement_flag = 0; - - if (at * direction > 0.9f) - { - movement_flag = AGENT_CONTROL_AT_POS; - } - else if (at * direction < -0.9f) - { - movement_flag = AGENT_CONTROL_AT_NEG; - } - - if (getFlying()) - { - // flying is too fast and has high inertia, artificially slow it down - // Don't update flags too often, server might not react - static U64 last_time_microsec = 0; - U64 time_microsec = LLTimer::getTotalTime(); - U64 delta = time_microsec - last_time_microsec; - // fly during ~0-40 ms, stop during ~40-250 ms - if (delta > 250000) // 250ms - { - // reset even if !movement_flag - last_time_microsec = time_microsec; - } - else if (delta > 40000) // 40 ms - { - clearControlFlags(AGENT_CONTROL_AT_POS | AGENT_CONTROL_AT_POS); - movement_flag = 0; - } - } - - if (movement_flag) - { - setControlFlags(movement_flag); - } - } - - // check to see if we need to keep rotating to target orientation - if (mAutoPilotTargetDist < mAutoPilotStopDistance) - { - setControlFlags(AGENT_CONTROL_STOP); - if(!mAutoPilotUseRotation || (delta_target_heading < mAutoPilotRotationThreshold)) - { - stopAutoPilot(); - } - } - } -} - - -//----------------------------------------------------------------------------- -// propagate() -//----------------------------------------------------------------------------- -void LLAgent::propagate(const F32 dt) -{ - // Update UI based on agent motion - LLFloaterMove *floater_move = LLFloaterReg::findTypedInstance("moveview"); - if (floater_move) - { - floater_move->mForwardButton ->setToggleState( gAgentCamera.getAtKey() > 0 || gAgentCamera.getWalkKey() > 0 ); - floater_move->mBackwardButton ->setToggleState( gAgentCamera.getAtKey() < 0 || gAgentCamera.getWalkKey() < 0 ); - floater_move->mTurnLeftButton ->setToggleState( gAgentCamera.getYawKey() > 0.f ); - floater_move->mTurnRightButton ->setToggleState( gAgentCamera.getYawKey() < 0.f ); - floater_move->mSlideLeftButton ->setToggleState( gAgentCamera.getLeftKey() > 0.f ); - floater_move->mSlideRightButton ->setToggleState( gAgentCamera.getLeftKey() < 0.f ); - floater_move->mMoveUpButton ->setToggleState( gAgentCamera.getUpKey() > 0 ); - floater_move->mMoveDownButton ->setToggleState( gAgentCamera.getUpKey() < 0 ); - } - - // handle rotation based on keyboard levels - const F32 YAW_RATE = 90.f * DEG_TO_RAD; // radians per second - yaw(YAW_RATE * gAgentCamera.getYawKey() * dt); - - const F32 PITCH_RATE = 90.f * DEG_TO_RAD; // radians per second - pitch(PITCH_RATE * gAgentCamera.getPitchKey() * dt); - - // handle auto-land behavior - if (isAgentAvatarValid()) - { - bool in_air = gAgentAvatarp->mInAir; - LLVector3 land_vel = getVelocity(); - land_vel.mV[VZ] = 0.f; - - if (!in_air - && gAgentCamera.getUpKey() < 0 - && land_vel.magVecSquared() < MAX_VELOCITY_AUTO_LAND_SQUARED - && gSavedSettings.getBOOL("AutomaticFly")) - { - // land automatically - setFlying(false); - } - } - - gAgentCamera.clearGeneralKeys(); -} - -//----------------------------------------------------------------------------- -// updateAgentPosition() -//----------------------------------------------------------------------------- -void LLAgent::updateAgentPosition(const F32 dt, const F32 yaw_radians, const S32 mouse_x, const S32 mouse_y) -{ - static LLCachedControl hint_timeout(gSavedSettings, "NotMovingHintTimeout"); - if (mMoveTimer.getStarted() && mMoveTimer.getElapsedTimeF32() > hint_timeout) - { - LLFirstUse::notMoving(); - } - - propagate(dt); - - // static S32 cameraUpdateCount = 0; - - rotate(yaw_radians, 0, 0, 1); - - // - // Check for water and land collision, set underwater flag - // - - gAgentCamera.updateLookAt(mouse_x, mouse_y); - - // When agent has no parents, position updates come from setPositionAgent() - // But when agent has a parent (ex: is seated), position remains unchanged - // relative to parent and no parent's position update trigger - // setPositionAgent(). - // But EEP's sky track selection still needs an update if agent has a parent - // and parent moves (ex: vehicles). - if (isAgentAvatarValid() - && gAgentAvatarp->getParent() - && !mOnPositionChanged.empty() - ) - { - LLVector3d new_position = getPositionGlobal(); - if ((mLastTestGlobal - new_position).lengthSquared() > 1.0) - { - // If the position has changed by more than 1 meter since the last time we triggered. - // filters out some noise. - mLastTestGlobal = new_position; - mOnPositionChanged(mFrameAgent.getOrigin(), new_position); - } - } -} - -// friends and operators - -std::ostream& operator<<(std::ostream &s, const LLAgent &agent) -{ - // This is unfinished, but might never be used. - // We'll just leave it for now; we can always delete it. - s << " { " - << " Frame = " << agent.mFrameAgent << "\n" - << " }"; - return s; -} - -// true if your own avatar needs to be rendered. Usually only -// in third person and build. -//----------------------------------------------------------------------------- -// needsRenderAvatar() -//----------------------------------------------------------------------------- -bool LLAgent::needsRenderAvatar() -{ - if (gAgentCamera.cameraMouselook() && !LLVOAvatar::sVisibleInFirstPerson) - { - return false; - } - - return mShowAvatar && mOutfitChosen; -} - -// true if we need to render your own avatar's head. -bool LLAgent::needsRenderHead() -{ - return (LLVOAvatar::sVisibleInFirstPerson && LLPipeline::sReflectionRender) || (mShowAvatar && !gAgentCamera.cameraMouselook()); -} - -//----------------------------------------------------------------------------- -// startTyping() -//----------------------------------------------------------------------------- -void LLAgent::startTyping() -{ - mTypingTimer.reset(); - - if (getRenderState() & AGENT_STATE_TYPING) - { - // already typing, don't trigger a different animation - return; - } - setRenderState(AGENT_STATE_TYPING); - - if (mChatTimer.getElapsedTimeF32() < 2.f) - { - LLViewerObject* chatter = gObjectList.findObject(mLastChatterID); - if (chatter && chatter->isAvatar()) - { - gAgentCamera.setLookAt(LOOKAT_TARGET_RESPOND, chatter, LLVector3::zero); - } - } - - if (gSavedSettings.getBOOL("PlayTypingAnim")) - { - sendAnimationRequest(ANIM_AGENT_TYPE, ANIM_REQUEST_START); - } - (LLFloaterReg::getTypedInstance("nearby_chat"))-> - sendChatFromViewer("", CHAT_TYPE_START, false); -} - -//----------------------------------------------------------------------------- -// stopTyping() -//----------------------------------------------------------------------------- -void LLAgent::stopTyping() -{ - if (mRenderState & AGENT_STATE_TYPING) - { - clearRenderState(AGENT_STATE_TYPING); - sendAnimationRequest(ANIM_AGENT_TYPE, ANIM_REQUEST_STOP); - (LLFloaterReg::getTypedInstance("nearby_chat"))-> - sendChatFromViewer("", CHAT_TYPE_STOP, false); - } -} - -//----------------------------------------------------------------------------- -// setRenderState() -//----------------------------------------------------------------------------- -void LLAgent::setRenderState(U8 newstate) -{ - mRenderState |= newstate; -} - -//----------------------------------------------------------------------------- -// clearRenderState() -//----------------------------------------------------------------------------- -void LLAgent::clearRenderState(U8 clearstate) -{ - mRenderState &= ~clearstate; -} - - -//----------------------------------------------------------------------------- -// getRenderState() -//----------------------------------------------------------------------------- -U8 LLAgent::getRenderState() -{ - // *FIX: don't do stuff in a getter! This is infinite loop city! - if ((mTypingTimer.getElapsedTimeF32() > TYPING_TIMEOUT_SECS) - && (mRenderState & AGENT_STATE_TYPING)) - { - stopTyping(); - } - - if ((!LLSelectMgr::getInstance()->getSelection()->isEmpty() && LLSelectMgr::getInstance()->shouldShowSelection()) - || LLToolMgr::getInstance()->getCurrentTool()->isEditing() ) - { - setRenderState(AGENT_STATE_EDITING); - } - else - { - clearRenderState(AGENT_STATE_EDITING); - } - - return mRenderState; -} - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - -//----------------------------------------------------------------------------- -// endAnimationUpdateUI() -//----------------------------------------------------------------------------- -void LLAgent::endAnimationUpdateUI() -{ - if (LLApp::isExiting() - || !gViewerWindow - || !gMenuBarView - || !gToolBarView - || !gStatusBar) - { - return; - } - if (gAgentCamera.getCameraMode() == gAgentCamera.getLastCameraMode()) - { - // We're already done endAnimationUpdateUI for this transition. - return; - } - - // clean up UI from mode we're leaving - if (gAgentCamera.getLastCameraMode() == CAMERA_MODE_MOUSELOOK ) - { - gToolBarView->setToolBarsVisible(true); - // show mouse cursor - gViewerWindow->showCursor(); - // show menus - gMenuBarView->setVisible(true); - LLNavigationBar::getInstance()->setVisible(true && gSavedSettings.getBOOL("ShowNavbarNavigationPanel")); - gStatusBar->setVisibleForMouselook(true); - - static LLCachedControl show_mini_location_panel(gSavedSettings, "ShowMiniLocationPanel"); - if (show_mini_location_panel) - { - LLPanelTopInfoBar::getInstance()->setVisible(true); - } - - LLChicletBar::getInstance()->setVisible(true); - - LLPanelStandStopFlying::getInstance()->setVisible(true); - - LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); - - LLFloaterCamera::onLeavingMouseLook(); - - if (mMouselookModeOutSignal) - { - (*mMouselookModeOutSignal)(); - } - - // Only pop if we have pushed... - if (true == mViewsPushed) - { -#if 0 // Use this once all floaters are registered - LLFloaterReg::restoreVisibleInstances(); -#else // Use this for now - LLFloaterView::skip_list_t skip_list; - if (LLFloaterReg::findInstance("mini_map")) - { - skip_list.insert(LLFloaterReg::findInstance("mini_map")); - } - if (LLFloaterReg::findInstance("beacons")) - { - skip_list.insert(LLFloaterReg::findInstance("beacons")); - } - LLFloaterIMContainer* im_box = LLFloaterReg::getTypedInstance("im_container"); - LLFloaterIMContainer::floater_list_t conversations; - im_box->getDetachedConversationFloaters(conversations); - for (LLFloater* conversation : conversations) - { - LL_INFOS() << "skip_list.insert(session_floater): " << conversation->getTitle() << LL_ENDL; - skip_list.insert(conversation); - } - - gFloaterView->popVisibleAll(skip_list); -#endif - mViewsPushed = false; - } - - - gAgentCamera.setLookAt(LOOKAT_TARGET_CLEAR); - if( gMorphView ) - { - gMorphView->setVisible( false ); - } - - // Disable mouselook-specific animations - if (isAgentAvatarValid()) - { - if( gAgentAvatarp->isAnyAnimationSignaled(AGENT_GUN_AIM_ANIMS, NUM_AGENT_GUN_AIM_ANIMS) ) - { - if (gAgentAvatarp->mSignaledAnimations.find(ANIM_AGENT_AIM_RIFLE_R) != gAgentAvatarp->mSignaledAnimations.end()) - { - sendAnimationRequest(ANIM_AGENT_AIM_RIFLE_R, ANIM_REQUEST_STOP); - sendAnimationRequest(ANIM_AGENT_HOLD_RIFLE_R, ANIM_REQUEST_START); - } - if (gAgentAvatarp->mSignaledAnimations.find(ANIM_AGENT_AIM_HANDGUN_R) != gAgentAvatarp->mSignaledAnimations.end()) - { - sendAnimationRequest(ANIM_AGENT_AIM_HANDGUN_R, ANIM_REQUEST_STOP); - sendAnimationRequest(ANIM_AGENT_HOLD_HANDGUN_R, ANIM_REQUEST_START); - } - if (gAgentAvatarp->mSignaledAnimations.find(ANIM_AGENT_AIM_BAZOOKA_R) != gAgentAvatarp->mSignaledAnimations.end()) - { - sendAnimationRequest(ANIM_AGENT_AIM_BAZOOKA_R, ANIM_REQUEST_STOP); - sendAnimationRequest(ANIM_AGENT_HOLD_BAZOOKA_R, ANIM_REQUEST_START); - } - if (gAgentAvatarp->mSignaledAnimations.find(ANIM_AGENT_AIM_BOW_L) != gAgentAvatarp->mSignaledAnimations.end()) - { - sendAnimationRequest(ANIM_AGENT_AIM_BOW_L, ANIM_REQUEST_STOP); - sendAnimationRequest(ANIM_AGENT_HOLD_BOW_L, ANIM_REQUEST_START); - } - } - } - } - else if (gAgentCamera.getLastCameraMode() == CAMERA_MODE_CUSTOMIZE_AVATAR) - { - // make sure we ask to save changes - - LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); - - if( gMorphView ) - { - gMorphView->setVisible( false ); - } - - if (isAgentAvatarValid()) - { - if(mCustomAnim) - { - sendAnimationRequest(ANIM_AGENT_CUSTOMIZE, ANIM_REQUEST_STOP); - sendAnimationRequest(ANIM_AGENT_CUSTOMIZE_DONE, ANIM_REQUEST_START); - - mCustomAnim = false ; - } - - } - gAgentCamera.setLookAt(LOOKAT_TARGET_CLEAR); - - LLFloaterCamera::onAvatarEditingAppearance(false); - } - - //--------------------------------------------------------------------- - // Set up UI for mode we're entering - //--------------------------------------------------------------------- - if (gAgentCamera.getCameraMode() == CAMERA_MODE_MOUSELOOK) - { - // clean up UI - // first show anything hidden by UI toggle - gViewerWindow->setUIVisibility(true); - - // then hide stuff we want hidden for mouselook - gToolBarView->setToolBarsVisible(false); - gMenuBarView->setVisible(false); - LLNavigationBar::getInstance()->setVisible(false); - gStatusBar->setVisibleForMouselook(false); - - LLPanelTopInfoBar::getInstance()->setVisible(false); - - LLChicletBar::getInstance()->setVisible(false); - - LLPanelStandStopFlying::getInstance()->setVisible(false); - - // clear out camera lag effect - gAgentCamera.clearCameraLag(); - - // JC - Added for always chat in third person option - gFocusMgr.setKeyboardFocus(NULL); - - LLToolMgr::getInstance()->setCurrentToolset(gMouselookToolset); - - mViewsPushed = true; - - if (mMouselookModeInSignal) - { - (*mMouselookModeInSignal)(); - } - - // hide all floaters except the mini map - -#if 0 // Use this once all floaters are registered - std::set exceptions; - exceptions.insert("mini_map"); - LLFloaterReg::hideVisibleInstances(exceptions); -#else // Use this for now - LLFloaterView::skip_list_t skip_list; - skip_list.insert(LLFloaterReg::findInstance("mini_map")); - skip_list.insert(LLFloaterReg::findInstance("beacons")); - gFloaterView->pushVisibleAll(false, skip_list); -#endif - - if( gMorphView ) - { - gMorphView->setVisible(false); - } - - gConsole->setVisible( true ); - - if (isAgentAvatarValid()) - { - // Trigger mouselook-specific animations - if( gAgentAvatarp->isAnyAnimationSignaled(AGENT_GUN_HOLD_ANIMS, NUM_AGENT_GUN_HOLD_ANIMS) ) - { - if (gAgentAvatarp->mSignaledAnimations.find(ANIM_AGENT_HOLD_RIFLE_R) != gAgentAvatarp->mSignaledAnimations.end()) - { - sendAnimationRequest(ANIM_AGENT_HOLD_RIFLE_R, ANIM_REQUEST_STOP); - sendAnimationRequest(ANIM_AGENT_AIM_RIFLE_R, ANIM_REQUEST_START); - } - if (gAgentAvatarp->mSignaledAnimations.find(ANIM_AGENT_HOLD_HANDGUN_R) != gAgentAvatarp->mSignaledAnimations.end()) - { - sendAnimationRequest(ANIM_AGENT_HOLD_HANDGUN_R, ANIM_REQUEST_STOP); - sendAnimationRequest(ANIM_AGENT_AIM_HANDGUN_R, ANIM_REQUEST_START); - } - if (gAgentAvatarp->mSignaledAnimations.find(ANIM_AGENT_HOLD_BAZOOKA_R) != gAgentAvatarp->mSignaledAnimations.end()) - { - sendAnimationRequest(ANIM_AGENT_HOLD_BAZOOKA_R, ANIM_REQUEST_STOP); - sendAnimationRequest(ANIM_AGENT_AIM_BAZOOKA_R, ANIM_REQUEST_START); - } - if (gAgentAvatarp->mSignaledAnimations.find(ANIM_AGENT_HOLD_BOW_L) != gAgentAvatarp->mSignaledAnimations.end()) - { - sendAnimationRequest(ANIM_AGENT_HOLD_BOW_L, ANIM_REQUEST_STOP); - sendAnimationRequest(ANIM_AGENT_AIM_BOW_L, ANIM_REQUEST_START); - } - } - if (gAgentAvatarp->getParent()) - { - LLVector3 at_axis = LLViewerCamera::getInstance()->getAtAxis(); - LLViewerObject* root_object = (LLViewerObject*)gAgentAvatarp->getRoot(); - if (root_object->flagCameraDecoupled()) - { - resetAxes(at_axis); - } - else - { - resetAxes(at_axis * ~((LLViewerObject*)gAgentAvatarp->getParent())->getRenderRotation()); - } - } - } - } - else if (gAgentCamera.getCameraMode() == CAMERA_MODE_CUSTOMIZE_AVATAR) - { - LLToolMgr::getInstance()->setCurrentToolset(gFaceEditToolset); - - if( gMorphView ) - { - gMorphView->setVisible( true ); - } - - // freeze avatar - if (isAgentAvatarValid()) - { - mPauseRequest = gAgentAvatarp->requestPause(); - } - - LLFloaterCamera::onAvatarEditingAppearance(true); - } - - if (isAgentAvatarValid()) - { - gAgentAvatarp->updateAttachmentVisibility(gAgentCamera.getCameraMode()); - } - - gFloaterTools->dirty(); - - // Don't let this be called more than once if the camera - // mode hasn't changed. --JC - gAgentCamera.updateLastCamera(); -} - -boost::signals2::connection LLAgent::setMouselookModeInCallback( const camera_signal_t::slot_type& cb ) -{ - if (!mMouselookModeInSignal) mMouselookModeInSignal = new camera_signal_t(); - return mMouselookModeInSignal->connect(cb); -} - -boost::signals2::connection LLAgent::setMouselookModeOutCallback( const camera_signal_t::slot_type& cb ) -{ - if (!mMouselookModeOutSignal) mMouselookModeOutSignal = new camera_signal_t(); - return mMouselookModeOutSignal->connect(cb); -} - -//----------------------------------------------------------------------------- -// heardChat() -//----------------------------------------------------------------------------- -void LLAgent::heardChat(const LLUUID& id) -{ - // log text and voice chat to speaker mgr - // for keeping track of active speakers, etc. - LLLocalSpeakerMgr::getInstance()->speakerChatted(id); - - // don't respond to your own voice - if (id == getID()) return; - - if (ll_rand(2) == 0) - { - LLViewerObject *chatter = gObjectList.findObject(mLastChatterID); - gAgentCamera.setLookAt(LOOKAT_TARGET_AUTO_LISTEN, chatter, LLVector3::zero); - } - - mLastChatterID = id; - mChatTimer.reset(); -} - -LLSD ll_sdmap_from_vector3(const LLVector3& vec) -{ - LLSD ret; - ret["X"] = vec.mV[VX]; - ret["Y"] = vec.mV[VY]; - ret["Z"] = vec.mV[VZ]; - return ret; -} - -LLVector3 ll_vector3_from_sdmap(const LLSD& sd) -{ - LLVector3 ret; - ret.mV[VX] = F32(sd["X"].asReal()); - ret.mV[VY] = F32(sd["Y"].asReal()); - ret.mV[VZ] = F32(sd["Z"].asReal()); - return ret; -} - -void LLAgent::setStartPosition( U32 location_id ) -{ - LLViewerObject *object; - - if (gAgentID == LLUUID::null) - { - return; - } - // we've got an ID for an agent viewerobject - object = gObjectList.findObject(gAgentID); - if (! object) - { - LL_INFOS() << "setStartPosition - Can't find agent viewerobject id " << gAgentID << LL_ENDL; - return; - } - // we've got the viewer object - // Sometimes the agent can be velocity interpolated off of - // this simulator. Clamp it to the region the agent is - // in, a little bit in on each side. - const F32 INSET = 0.5f; //meters - const F32 REGION_WIDTH = LLWorld::getInstance()->getRegionWidthInMeters(); - - LLVector3 agent_pos = getPositionAgent(); - - if (isAgentAvatarValid()) - { - // the z height is at the agent's feet - agent_pos.mV[VZ] -= 0.5f * (gAgentAvatarp->mBodySize.mV[VZ] + gAgentAvatarp->mAvatarOffset.mV[VZ]); - } - - agent_pos.mV[VX] = llclamp( agent_pos.mV[VX], INSET, REGION_WIDTH - INSET ); - agent_pos.mV[VY] = llclamp( agent_pos.mV[VY], INSET, REGION_WIDTH - INSET ); - - // Don't let them go below ground, or too high. - agent_pos.mV[VZ] = llclamp( agent_pos.mV[VZ], - mRegionp->getLandHeightRegion( agent_pos ), - LLWorld::getInstance()->getRegionMaxHeight() ); - // Send the CapReq - LLSD request; - LLSD body; - LLSD homeLocation; - - homeLocation["LocationId"] = LLSD::Integer(location_id); - homeLocation["LocationPos"] = ll_sdmap_from_vector3(agent_pos); - homeLocation["LocationLookAt"] = ll_sdmap_from_vector3(mFrameAgent.getAtAxis()); - - body["HomeLocation"] = homeLocation; - - if (!requestPostCapability("HomeLocation", body, - boost::bind(&LLAgent::setStartPositionSuccess, this, _1))) - LL_WARNS() << "Unable to post to HomeLocation capability." << LL_ENDL; -} - -void LLAgent::setStartPositionSuccess(const LLSD &result) -{ - LLVector3 agent_pos; - bool error = true; - - do { - // was the call to /agent//home-location successful? - // If not, we keep error set to true - if (!result.has("success")) - break; - - if (0 != strncmp("true", result["success"].asString().c_str(), 4)) - break; - - // did the simulator return a "justified" home location? - // If no, we keep error set to true - if (!result.has("HomeLocation")) - break; - - if ((!result["HomeLocation"].has("LocationPos")) || - (!result["HomeLocation"]["LocationPos"].has("X")) || - (!result["HomeLocation"]["LocationPos"].has("Y")) || - (!result["HomeLocation"]["LocationPos"].has("Z"))) - break; - - agent_pos.mV[VX] = result["HomeLocation"]["LocationPos"]["X"].asInteger(); - agent_pos.mV[VY] = result["HomeLocation"]["LocationPos"]["Y"].asInteger(); - agent_pos.mV[VZ] = result["HomeLocation"]["LocationPos"]["Z"].asInteger(); - - error = false; - - } while (0); - - if (error) - { - LL_WARNS() << "Error in response to home position set." << LL_ENDL; - } - else - { - LL_INFOS() << "setting home position" << LL_ENDL; - - LLViewerRegion *viewer_region = gAgent.getRegion(); - setHomePosRegion(viewer_region->getHandle(), agent_pos); - } -} - -void LLAgent::requestStopMotion( LLMotion* motion ) -{ - // Notify all avatars that a motion has stopped. - // This is needed to clear the animation state bits - LLUUID anim_state = motion->getID(); - onAnimStop(motion->getID()); - - // if motion is not looping, it could have stopped by running out of time - // so we need to tell the server this -// LL_INFOS() << "Sending stop for motion " << motion->getName() << LL_ENDL; - sendAnimationRequest( anim_state, ANIM_REQUEST_STOP ); -} - -void LLAgent::onAnimStop(const LLUUID& id) -{ - // handle automatic state transitions (based on completion of animation playback) - if (id == ANIM_AGENT_STAND) - { - stopFidget(); - } - else if (id == ANIM_AGENT_AWAY) - { - clearAFK(); - } - else if (id == ANIM_AGENT_STANDUP) - { - // send stand up command - setControlFlags(AGENT_CONTROL_FINISH_ANIM); - - // now trigger dusting self off animation - if (isAgentAvatarValid() && !gAgentAvatarp->mBelowWater && rand() % 3 == 0) - sendAnimationRequest( ANIM_AGENT_BRUSH, ANIM_REQUEST_START ); - } - else if (id == ANIM_AGENT_PRE_JUMP || id == ANIM_AGENT_LAND || id == ANIM_AGENT_MEDIUM_LAND) - { - setControlFlags(AGENT_CONTROL_FINISH_ANIM); - } -} - -bool LLAgent::isGodlike() const -{ - return mAgentAccess->isGodlike(); -} - -bool LLAgent::isGodlikeWithoutAdminMenuFakery() const -{ - return mAgentAccess->isGodlikeWithoutAdminMenuFakery(); -} - -U8 LLAgent::getGodLevel() const -{ - return mAgentAccess->getGodLevel(); -} - -bool LLAgent::wantsPGOnly() const -{ - return mAgentAccess->wantsPGOnly(); -} - -bool LLAgent::canAccessMature() const -{ - return mAgentAccess->canAccessMature(); -} - -bool LLAgent::canAccessAdult() const -{ - return mAgentAccess->canAccessAdult(); -} - -bool LLAgent::canAccessMaturityInRegion( U64 region_handle ) const -{ - LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle( region_handle ); - if( regionp ) - { - switch( regionp->getSimAccess() ) - { - case SIM_ACCESS_MATURE: - if( !canAccessMature() ) - return false; - break; - case SIM_ACCESS_ADULT: - if( !canAccessAdult() ) - return false; - break; - default: - // Oh, go on and hear the silly noises. - break; - } - } - - return true; -} - -bool LLAgent::canAccessMaturityAtGlobal( LLVector3d pos_global ) const -{ - U64 region_handle = to_region_handle_global( pos_global.mdV[0], pos_global.mdV[1] ); - return canAccessMaturityInRegion( region_handle ); -} - -bool LLAgent::prefersPG() const -{ - return mAgentAccess->prefersPG(); -} - -bool LLAgent::prefersMature() const -{ - return mAgentAccess->prefersMature(); -} - -bool LLAgent::prefersAdult() const -{ - return mAgentAccess->prefersAdult(); -} - -bool LLAgent::isTeen() const -{ - return mAgentAccess->isTeen(); -} - -bool LLAgent::isMature() const -{ - return mAgentAccess->isMature(); -} - -bool LLAgent::isAdult() const -{ - return mAgentAccess->isAdult(); -} - -//static -int LLAgent::convertTextToMaturity(char text) -{ - return LLAgentAccess::convertTextToMaturity(text); -} - -void LLAgent::handlePreferredMaturityResult(U8 pServerMaturity) -{ - // Update the number of responses received - ++mMaturityPreferenceResponseId; - llassert(mMaturityPreferenceResponseId <= mMaturityPreferenceRequestId); - - // Update the last known server maturity response - mLastKnownResponseMaturity = pServerMaturity; - - // Ignore all responses if we know there are more unanswered requests that are expected - if (mMaturityPreferenceResponseId == mMaturityPreferenceRequestId) - { - // If we received a response that matches the last known request, then we are good - if (mLastKnownRequestMaturity == mLastKnownResponseMaturity) - { - mMaturityPreferenceNumRetries = 0; - reportPreferredMaturitySuccess(); - llassert(static_cast(gSavedSettings.getU32("PreferredMaturity")) == mLastKnownResponseMaturity); - } - // Else, the viewer is out of sync with the server, so let's try to re-sync with the - // server by re-sending our last known request. Cap the re-tries at 3 just to be safe. - else if (++mMaturityPreferenceNumRetries <= 3) - { - LL_INFOS() << "Retrying attempt #" << mMaturityPreferenceNumRetries << " to set viewer preferred maturity to '" - << LLViewerRegion::accessToString(mLastKnownRequestMaturity) << "'" << LL_ENDL; - sendMaturityPreferenceToServer(mLastKnownRequestMaturity); - } - // Else, the viewer is style out of sync with the server after 3 retries, so inform the user - else - { - mMaturityPreferenceNumRetries = 0; - LL_WARNS() << "Too many retries for maturity preference" << LL_ENDL; - reportPreferredMaturityError(); - } - } -} - -void LLAgent::handlePreferredMaturityError() -{ - // Update the number of responses received - ++mMaturityPreferenceResponseId; - llassert(mMaturityPreferenceResponseId <= mMaturityPreferenceRequestId); - - // Ignore all responses if we know there are more unanswered requests that are expected - if (mMaturityPreferenceResponseId == mMaturityPreferenceRequestId) - { - mMaturityPreferenceNumRetries = 0; - - // If we received a response that matches the last known request, then we are synced with - // the server, but not quite sure why we are - if (mLastKnownRequestMaturity == mLastKnownResponseMaturity) - { - LL_WARNS() << "Got an error but maturity preference '" << LLViewerRegion::accessToString(mLastKnownRequestMaturity) - << "' seems to be in sync with the server" << LL_ENDL; - reportPreferredMaturitySuccess(); - } - // Else, the more likely case is that the last request does not match the last response, - // so inform the user - else - { - reportPreferredMaturityError(); - } - } -} - -void LLAgent::reportPreferredMaturitySuccess() -{ - // If there is a pending teleport request waiting for the maturity preference to be synced with - // the server, let's start the pending request - if (hasPendingTeleportRequest()) - { - startTeleportRequest(); - } -} - -void LLAgent::reportPreferredMaturityError() -{ - // If there is a pending teleport request waiting for the maturity preference to be synced with - // the server, we were unable to successfully sync with the server on maturity preference, so let's - // just raise the screen. - mIsMaturityRatingChangingDuringTeleport = false; - if (hasPendingTeleportRequest()) - { - LL_WARNS("Teleport") << "Teleport failing due to preferred maturity error" << LL_ENDL; - setTeleportState(LLAgent::TELEPORT_NONE); - } - - // Get the last known maturity request from the user activity - std::string preferredMaturity = LLViewerRegion::accessToString(mLastKnownRequestMaturity); - LLStringUtil::toLower(preferredMaturity); - - // Get the last known maturity response from the server - std::string actualMaturity = LLViewerRegion::accessToString(mLastKnownResponseMaturity); - LLStringUtil::toLower(actualMaturity); - - // Notify the user - LLSD args = LLSD::emptyMap(); - args["PREFERRED_MATURITY"] = preferredMaturity; - args["ACTUAL_MATURITY"] = actualMaturity; - LLNotificationsUtil::add("MaturityChangeError", args); - - // Check the saved settings to ensure that we are consistent. If we are not consistent, update - // the viewer, but do not send anything to server - U8 localMaturity = static_cast(gSavedSettings.getU32("PreferredMaturity")); - if (localMaturity != mLastKnownResponseMaturity) - { - bool tmpIsDoSendMaturityPreferenceToServer = mIsDoSendMaturityPreferenceToServer; - mIsDoSendMaturityPreferenceToServer = false; - LL_INFOS() << "Setting viewer preferred maturity to '" << LLViewerRegion::accessToString(mLastKnownResponseMaturity) << "'" << LL_ENDL; - gSavedSettings.setU32("PreferredMaturity", static_cast(mLastKnownResponseMaturity)); - mIsDoSendMaturityPreferenceToServer = tmpIsDoSendMaturityPreferenceToServer; - } -} - -bool LLAgent::isMaturityPreferenceSyncedWithServer() const -{ - return (mMaturityPreferenceRequestId == mMaturityPreferenceResponseId); -} - -void LLAgent::sendMaturityPreferenceToServer(U8 pPreferredMaturity) -{ - // Only send maturity preference to the server if enabled - if (mIsDoSendMaturityPreferenceToServer) - { - // Increment the number of requests. The handlers manage a separate count of responses. - ++mMaturityPreferenceRequestId; - - // Update the last know maturity request - mLastKnownRequestMaturity = pPreferredMaturity; - - // If we don't have a region, report it as an error - if (getRegion() == NULL) - { - LL_WARNS("Agent") << "Region is not defined, can not change Maturity setting." << LL_ENDL; - return; - } - - LLSD access_prefs = LLSD::emptyMap(); - access_prefs["max"] = LLViewerRegion::accessToShortString(pPreferredMaturity); - - LLSD postData = LLSD::emptyMap(); - postData["access_prefs"] = access_prefs; - LL_INFOS() << "Sending viewer preferred maturity to '" << LLViewerRegion::accessToString(pPreferredMaturity) << LL_ENDL; - - if (!requestPostCapability("UpdateAgentInformation", postData, - static_cast(boost::bind(&LLAgent::processMaturityPreferenceFromServer, this, _1, pPreferredMaturity)), - static_cast(boost::bind(&LLAgent::handlePreferredMaturityError, this)) - )) - { - LL_WARNS("Agent") << "Maturity request post failed." << LL_ENDL; - } - } -} - - -void LLAgent::processMaturityPreferenceFromServer(const LLSD &result, U8 perferredMaturity) -{ - U8 maturity = SIM_ACCESS_MIN; - - llassert(result.isDefined()); - llassert(result.isMap()); - llassert(result.has("access_prefs")); - llassert(result.get("access_prefs").isMap()); - llassert(result.get("access_prefs").has("max")); - llassert(result.get("access_prefs").get("max").isString()); - if (result.isDefined() && result.isMap() && result.has("access_prefs") - && result.get("access_prefs").isMap() && result.get("access_prefs").has("max") - && result.get("access_prefs").get("max").isString()) - { - LLSD::String actualPreference = result.get("access_prefs").get("max").asString(); - LLStringUtil::trim(actualPreference); - maturity = LLViewerRegion::shortStringToAccess(actualPreference); - } - - if (maturity != perferredMaturity) - { - LL_WARNS() << "while attempting to change maturity preference from '" - << LLViewerRegion::accessToString(mLastKnownResponseMaturity) - << "' to '" << LLViewerRegion::accessToString(perferredMaturity) - << "', the server responded with '" - << LLViewerRegion::accessToString(maturity) - << "' [value:" << static_cast(maturity) - << "], " << LL_ENDL; - } - handlePreferredMaturityResult(maturity); -} - -// Using a new capability, tell the simulator that we want it to send everything -// it knows about and not just what is in front of the camera, in its view -// frustum. We need this feature so that the contents of the region that appears -// in the 6 snapshots which we cannot see and is normally not "considered", is -// also rendered. Typically, this is turned on when the 360 capture floater is -// opened and turned off when it is closed. -// Note: for this version, we do not have a way to determine when "everything" -// has arrived and has been rendered so for now, the proposal is that users -// will need to experiment with the low resolution version and wait for some -// (hopefully) small period of time while the full contents resolves. -// Pass in a flag to ask the simulator/interest list to "send everything" or -// not (the default mode) -void LLAgent::changeInterestListMode(const std::string &new_mode) -{ - if (new_mode != mInterestListMode) - { - mInterestListMode = new_mode; - - // Change interest list mode for all regions. If they are already set for the current mode, - // the setting will have no effect. - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); - ++iter) - { - LLViewerRegion *regionp = *iter; - if (regionp && regionp->isAlive() && regionp->capabilitiesReceived()) - { - regionp->setInterestListMode(mInterestListMode); - } - } - } - else - { - LL_DEBUGS("360Capture") << "Agent interest list mode is already set to " << mInterestListMode << LL_ENDL; - } -} - - -bool LLAgent::requestPostCapability(const std::string &capName, LLSD &postData, httpCallback_t cbSuccess, httpCallback_t cbFailure) -{ - if (getRegion()) - { - return getRegion()->requestPostCapability(capName, postData, cbSuccess, cbFailure); - } - return false; -} - -bool LLAgent::requestGetCapability(const std::string &capName, httpCallback_t cbSuccess, httpCallback_t cbFailure) -{ - if (getRegion()) - { - return getRegion()->requestGetCapability(capName, cbSuccess, cbFailure); - } - return false; -} - -bool LLAgent::getAdminOverride() const -{ - return mAgentAccess->getAdminOverride(); -} - -void LLAgent::setMaturity(char text) -{ - mAgentAccess->setMaturity(text); -} - -void LLAgent::setAdminOverride(bool b) -{ - mAgentAccess->setAdminOverride(b); -} - -void LLAgent::setGodLevel(U8 god_level) -{ - mAgentAccess->setGodLevel(god_level); - mGodLevelChangeSignal(god_level); -} - -LLAgent::god_level_change_slot_t LLAgent::registerGodLevelChanageListener(god_level_change_callback_t pGodLevelChangeCallback) -{ - return mGodLevelChangeSignal.connect(pGodLevelChangeCallback); -} - -const LLAgentAccess& LLAgent::getAgentAccess() -{ - return *mAgentAccess; -} - -bool LLAgent::validateMaturity(const LLSD& newvalue) -{ - return mAgentAccess->canSetMaturity(newvalue.asInteger()); -} - -void LLAgent::handleMaturity(const LLSD &pNewValue) -{ - sendMaturityPreferenceToServer(static_cast(pNewValue.asInteger())); -} - -//---------------------------------------------------------------------------- - -//*TODO remove, is not used anywhere as of August 20, 2009 -void LLAgent::buildFullnameAndTitle(std::string& name) const -{ - if (isGroupMember()) - { - name = mGroupTitle; - name += ' '; - } - else - { - name.erase(0, name.length()); - } - - if (isAgentAvatarValid()) - { - name += gAgentAvatarp->getFullname(); - } -} - -bool LLAgent::isInGroup(const LLUUID& group_id, bool ignore_god_mode /* false */) const -{ - if (!ignore_god_mode && isGodlike()) - return true; - - U32 count = mGroups.size(); - for(U32 i = 0; i < count; ++i) - { - if(mGroups[i].mID == group_id) - { - return true; - } - } - return false; -} - -// This implementation should mirror LLAgentInfo::hasPowerInGroup -bool LLAgent::hasPowerInGroup(const LLUUID& group_id, U64 power) const -{ - if (isGodlikeWithoutAdminMenuFakery()) - return true; - - // GP_NO_POWERS can also mean no power is enough to grant an ability. - if (GP_NO_POWERS == power) return false; - - U32 count = mGroups.size(); - for(U32 i = 0; i < count; ++i) - { - if(mGroups[i].mID == group_id) - { - return (bool)((mGroups[i].mPowers & power) > 0); - } - } - return false; -} - -bool LLAgent::hasPowerInActiveGroup(U64 power) const -{ - return (mGroupID.notNull() && (hasPowerInGroup(mGroupID, power))); -} - -U64 LLAgent::getPowerInGroup(const LLUUID& group_id) const -{ - if (isGodlike()) - return GP_ALL_POWERS; - - U32 count = mGroups.size(); - for(U32 i = 0; i < count; ++i) - { - if(mGroups[i].mID == group_id) - { - return (mGroups[i].mPowers); - } - } - - return GP_NO_POWERS; -} - -bool LLAgent::getGroupData(const LLUUID& group_id, LLGroupData& data) const -{ - S32 count = mGroups.size(); - for(S32 i = 0; i < count; ++i) - { - if(mGroups[i].mID == group_id) - { - data = mGroups[i]; - return true; - } - } - return false; -} - -S32 LLAgent::getGroupContribution(const LLUUID& group_id) const -{ - S32 count = mGroups.size(); - for(S32 i = 0; i < count; ++i) - { - if(mGroups[i].mID == group_id) - { - S32 contribution = mGroups[i].mContribution; - return contribution; - } - } - return 0; -} - -bool LLAgent::setGroupContribution(const LLUUID& group_id, S32 contribution) -{ - S32 count = mGroups.size(); - for(S32 i = 0; i < count; ++i) - { - if(mGroups[i].mID == group_id) - { - mGroups[i].mContribution = contribution; - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("SetGroupContribution"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgentID); - msg->addUUID("SessionID", gAgentSessionID); - msg->nextBlock("Data"); - msg->addUUID("GroupID", group_id); - msg->addS32("Contribution", contribution); - sendReliableMessage(); - return true; - } - } - return false; -} - -bool LLAgent::setUserGroupFlags(const LLUUID& group_id, bool accept_notices, bool list_in_profile) -{ - S32 count = mGroups.size(); - for(S32 i = 0; i < count; ++i) - { - if(mGroups[i].mID == group_id) - { - mGroups[i].mAcceptNotices = accept_notices; - mGroups[i].mListInProfile = list_in_profile; - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("SetGroupAcceptNotices"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgentID); - msg->addUUID("SessionID", gAgentSessionID); - msg->nextBlock("Data"); - msg->addUUID("GroupID", group_id); - msg->addBOOL("AcceptNotices", accept_notices); - msg->nextBlock("NewData"); - msg->addBOOL("ListInProfile", list_in_profile); - sendReliableMessage(); - return true; - } - } - return false; -} - -bool LLAgent::canJoinGroups() const -{ - return (S32)mGroups.size() < LLAgentBenefitsMgr::current().getGroupMembershipLimit(); -} - -LLQuaternion LLAgent::getHeadRotation() -{ - if (!isAgentAvatarValid() || !gAgentAvatarp->mPelvisp || !gAgentAvatarp->mHeadp) - { - return LLQuaternion::DEFAULT; - } - - if (!gAgentCamera.cameraMouselook()) - { - return gAgentAvatarp->getRotation(); - } - - // We must be in mouselook - LLVector3 look_dir( LLViewerCamera::getInstance()->getAtAxis() ); - LLVector3 up = look_dir % mFrameAgent.getLeftAxis(); - LLVector3 left = up % look_dir; - - LLQuaternion rot(look_dir, left, up); - if (gAgentAvatarp->getParent()) - { - rot = rot * ~gAgentAvatarp->getParent()->getRotation(); - } - - return rot; -} - -void LLAgent::sendAnimationRequests(const std::vector &anim_ids, EAnimRequest request) -{ - if (gAgentID.isNull()) - { - return; - } - - S32 num_valid_anims = 0; - - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_AgentAnimation); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, getID()); - msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); - - for (const LLUUID& uuid : anim_ids) - { - if (uuid.notNull()) - { - msg->nextBlockFast(_PREHASH_AnimationList); - msg->addUUIDFast(_PREHASH_AnimID, uuid); - msg->addBOOLFast(_PREHASH_StartAnim, request == ANIM_REQUEST_START); - num_valid_anims++; - } - } - - msg->nextBlockFast(_PREHASH_PhysicalAvatarEventList); - msg->addBinaryDataFast(_PREHASH_TypeData, NULL, 0); - if (num_valid_anims) - { - sendReliableMessage(); - } -} - -void LLAgent::sendAnimationRequest(const LLUUID &anim_id, EAnimRequest request) -{ - if (gAgentID.isNull() || anim_id.isNull() || !mRegionp) - { - return; - } - - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_AgentAnimation); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, getID()); - msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); - - msg->nextBlockFast(_PREHASH_AnimationList); - msg->addUUIDFast(_PREHASH_AnimID, anim_id); - msg->addBOOLFast(_PREHASH_StartAnim, request == ANIM_REQUEST_START); - - msg->nextBlockFast(_PREHASH_PhysicalAvatarEventList); - msg->addBinaryDataFast(_PREHASH_TypeData, NULL, 0); - sendReliableMessage(); -} - -// Send a message to the region to stop the NULL animation state -// This will reset animation state overrides for the agent. -void LLAgent::sendAnimationStateReset() -{ - if (gAgentID.isNull() || !mRegionp) - { - return; - } - - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_AgentAnimation); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, getID()); - msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); - - msg->nextBlockFast(_PREHASH_AnimationList); - msg->addUUIDFast(_PREHASH_AnimID, LLUUID::null ); - msg->addBOOLFast(_PREHASH_StartAnim, false); - - msg->nextBlockFast(_PREHASH_PhysicalAvatarEventList); - msg->addBinaryDataFast(_PREHASH_TypeData, NULL, 0); - sendReliableMessage(); -} - - -// Send a message to the region to revoke sepecified permissions on ALL scripts in the region -// If the target is an object in the region, permissions in scripts on that object are cleared. -// If it is the region ID, all scripts clear the permissions for this agent -void LLAgent::sendRevokePermissions(const LLUUID & target, U32 permissions) -{ - // Currently only the bits for SCRIPT_PERMISSION_TRIGGER_ANIMATION and SCRIPT_PERMISSION_OVERRIDE_ANIMATIONS - // are supported by the server. Sending any other bits will cause the message to be dropped without changing permissions - - if (gAgentID.notNull() && gMessageSystem) - { - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_RevokePermissions); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, getID()); // Must be our ID - msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); - - msg->nextBlockFast(_PREHASH_Data); - msg->addUUIDFast(_PREHASH_ObjectID, target); // Must be in the region - msg->addS32Fast(_PREHASH_ObjectPermissions, (S32) permissions); - - sendReliableMessage(); - } -} - -void LLAgent::sendWalkRun(bool running) -{ - LLMessageSystem* msgsys = gMessageSystem; - if (msgsys) - { - msgsys->newMessageFast(_PREHASH_SetAlwaysRun); - msgsys->nextBlockFast(_PREHASH_AgentData); - msgsys->addUUIDFast(_PREHASH_AgentID, getID()); - msgsys->addUUIDFast(_PREHASH_SessionID, getSessionID()); - msgsys->addBOOLFast(_PREHASH_AlwaysRun, bool(running) ); - sendReliableMessage(); - } -} - -void LLAgent::friendsChanged() -{ - LLCollectProxyBuddies collector; - LLAvatarTracker::instance().applyFunctor(collector); - mProxyForAgents = collector.mProxy; -} - -bool LLAgent::isGrantedProxy(const LLPermissions& perm) -{ - return (mProxyForAgents.count(perm.getOwner()) > 0); -} - -bool LLAgent::allowOperation(PermissionBit op, - const LLPermissions& perm, - U64 group_proxy_power, - U8 god_minimum) -{ - // Check god level. - if (getGodLevel() >= god_minimum) return true; - - if (!perm.isOwned()) return false; - - // A group member with group_proxy_power can act as owner. - bool is_group_owned; - LLUUID owner_id; - perm.getOwnership(owner_id, is_group_owned); - LLUUID group_id(perm.getGroup()); - LLUUID agent_proxy(getID()); - - if (is_group_owned) - { - if (hasPowerInGroup(group_id, group_proxy_power)) - { - // Let the member assume the group's id for permission requests. - agent_proxy = owner_id; - } - } - else - { - // Check for granted mod permissions. - if ((PERM_OWNER != op) && isGrantedProxy(perm)) - { - agent_proxy = owner_id; - } - } - - // This is the group id to use for permission requests. - // Only group members may use this field. - LLUUID group_proxy = LLUUID::null; - if (group_id.notNull() && isInGroup(group_id)) - { - group_proxy = group_id; - } - - // We now have max ownership information. - if (PERM_OWNER == op) - { - // This this was just a check for ownership, we can now return the answer. - return (agent_proxy == owner_id); - } - - return perm.allowOperationBy(op, agent_proxy, group_proxy); -} - -const LLColor4 &LLAgent::getEffectColor() -{ - return *mEffectColor; -} - -void LLAgent::setEffectColor(const LLColor4 &color) -{ - *mEffectColor = color; -} - -void LLAgent::initOriginGlobal(const LLVector3d &origin_global) -{ - mAgentOriginGlobal = origin_global; -} - -bool LLAgent::leftButtonGrabbed() const -{ - const bool camera_mouse_look = gAgentCamera.cameraMouselook(); - return (!camera_mouse_look && mControlsTakenCount[CONTROL_LBUTTON_DOWN_INDEX] > 0) - || (camera_mouse_look && mControlsTakenCount[CONTROL_ML_LBUTTON_DOWN_INDEX] > 0) - || (!camera_mouse_look && mControlsTakenPassedOnCount[CONTROL_LBUTTON_DOWN_INDEX] > 0) - || (camera_mouse_look && mControlsTakenPassedOnCount[CONTROL_ML_LBUTTON_DOWN_INDEX] > 0); -} - -bool LLAgent::rotateGrabbed() const -{ - return (mControlsTakenCount[CONTROL_YAW_POS_INDEX] > 0) - || (mControlsTakenCount[CONTROL_YAW_NEG_INDEX] > 0); -} - -bool LLAgent::forwardGrabbed() const -{ - return (mControlsTakenCount[CONTROL_AT_POS_INDEX] > 0); -} - -bool LLAgent::backwardGrabbed() const -{ - return (mControlsTakenCount[CONTROL_AT_NEG_INDEX] > 0); -} - -bool LLAgent::upGrabbed() const -{ - return (mControlsTakenCount[CONTROL_UP_POS_INDEX] > 0); -} - -bool LLAgent::downGrabbed() const -{ - return (mControlsTakenCount[CONTROL_UP_NEG_INDEX] > 0); -} - -void update_group_floaters(const LLUUID& group_id) -{ - - LLGroupActions::refresh(group_id); - //*TODO Implement group update for Profile View - // still actual as of July 31, 2009 (DZ) - - gAgent.fireEvent(new LLOldEvents::LLEvent(&gAgent, "new group"), ""); -} - -// static -void LLAgent::processAgentDropGroup(LLMessageSystem *msg, void **) -{ - LLUUID agent_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); - - if (agent_id != gAgentID) - { - LL_WARNS() << "processAgentDropGroup for agent other than me" << LL_ENDL; - return; - } - - LLUUID group_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_GroupID, group_id ); - - // Remove the group if it already exists remove it and add the new data to pick up changes. - LLGroupData gd; - gd.mID = group_id; - std::vector::iterator found_it = std::find(gAgent.mGroups.begin(), gAgent.mGroups.end(), gd); - if (found_it != gAgent.mGroups.end()) - { - gAgent.mGroups.erase(found_it); - if (gAgent.getGroupID() == group_id) - { - gAgent.mGroupID.setNull(); - gAgent.mGroupPowers = 0; - gAgent.mGroupName.clear(); - gAgent.mGroupTitle.clear(); - } - - // refresh all group information - gAgent.sendAgentDataUpdateRequest(); - - LLGroupMgr::getInstance()->clearGroupData(group_id); - // close the floater for this group, if any. - LLGroupActions::closeGroup(group_id); - } - else - { - LL_WARNS() << "processAgentDropGroup, agent is not part of group " << group_id << LL_ENDL; - } -} - -class LLAgentDropGroupViewerNode : public LLHTTPNode -{ - virtual void post( - LLHTTPNode::ResponsePtr response, - const LLSD& context, - const LLSD& input) const - { - - if ( - !input.isMap() || - !input.has("body") ) - { - //what to do with badly formed message? - response->extendedResult(HTTP_BAD_REQUEST, LLSD("Invalid message parameters")); - } - - LLSD body = input["body"]; - if ( body.has("body") ) - { - //stupid message system doubles up the "body"s - body = body["body"]; - } - - if ( - body.has("AgentData") && - body["AgentData"].isArray() && - body["AgentData"][0].isMap() ) - { - LL_INFOS() << "VALID DROP GROUP" << LL_ENDL; - - //there is only one set of data in the AgentData block - LLSD agent_data = body["AgentData"][0]; - LLUUID agent_id; - LLUUID group_id; - - agent_id = agent_data["AgentID"].asUUID(); - group_id = agent_data["GroupID"].asUUID(); - - if (agent_id != gAgentID) - { - LL_WARNS() - << "AgentDropGroup for agent other than me" << LL_ENDL; - - response->notFound(); - return; - } - - // Remove the group if it already exists remove it - // and add the new data to pick up changes. - LLGroupData gd; - gd.mID = group_id; - std::vector::iterator found_it = std::find(gAgent.mGroups.begin(), gAgent.mGroups.end(), gd); - if (found_it != gAgent.mGroups.end()) - { - gAgent.mGroups.erase(found_it); - if (gAgent.getGroupID() == group_id) - { - gAgent.mGroupID.setNull(); - gAgent.mGroupPowers = 0; - gAgent.mGroupName.clear(); - gAgent.mGroupTitle.clear(); - } - - // refresh all group information - gAgent.sendAgentDataUpdateRequest(); - - LLGroupMgr::getInstance()->clearGroupData(group_id); - // close the floater for this group, if any. - LLGroupActions::closeGroup(group_id); - } - else - { - LL_WARNS() - << "AgentDropGroup, agent is not part of group " - << group_id << LL_ENDL; - } - - response->result(LLSD()); - } - else - { - //what to do with badly formed message? - response->extendedResult(HTTP_BAD_REQUEST, LLSD("Invalid message parameters")); - } - } -}; - -LLHTTPRegistration - gHTTPRegistrationAgentDropGroupViewerNode( - "/message/AgentDropGroup"); - -// static -void LLAgent::processAgentGroupDataUpdate(LLMessageSystem *msg, void **) -{ - LLUUID agent_id; - - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); - - if (agent_id != gAgentID) - { - LL_WARNS() << "processAgentGroupDataUpdate for agent other than me" << LL_ENDL; - return; - } - - S32 count = msg->getNumberOfBlocksFast(_PREHASH_GroupData); - LLGroupData group; - bool need_floater_update = false; - for(S32 i = 0; i < count; ++i) - { - msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupID, group.mID, i); - msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupInsigniaID, group.mInsigniaID, i); - msg->getU64(_PREHASH_GroupData, "GroupPowers", group.mPowers, i); - msg->getBOOL(_PREHASH_GroupData, "AcceptNotices", group.mAcceptNotices, i); - msg->getS32(_PREHASH_GroupData, "Contribution", group.mContribution, i); - msg->getStringFast(_PREHASH_GroupData, _PREHASH_GroupName, group.mName, i); - - if(group.mID.notNull()) - { - need_floater_update = true; - // Remove the group if it already exists remove it and add the new data to pick up changes. - std::vector::iterator found_it = std::find(gAgent.mGroups.begin(), gAgent.mGroups.end(), group); - if (found_it != gAgent.mGroups.end()) - { - gAgent.mGroups.erase(found_it); - } - gAgent.mGroups.push_back(group); - } - if (need_floater_update) - { - update_group_floaters(group.mID); - } - } - -} - -class LLAgentGroupDataUpdateViewerNode : public LLHTTPNode -{ - virtual void post( - LLHTTPNode::ResponsePtr response, - const LLSD& context, - const LLSD& input) const - { - LLSD body = input["body"]; - if(body.has("body")) - body = body["body"]; - LLUUID agent_id = body["AgentData"][0]["AgentID"].asUUID(); - - if (agent_id != gAgentID) - { - LL_WARNS() << "processAgentGroupDataUpdate for agent other than me" << LL_ENDL; - return; - } - - LLSD group_data = body["GroupData"]; - - LLSD::array_iterator iter_group = - group_data.beginArray(); - LLSD::array_iterator end_group = - group_data.endArray(); - int group_index = 0; - for(; iter_group != end_group; ++iter_group) - { - - LLGroupData group; - bool need_floater_update = false; - - group.mID = (*iter_group)["GroupID"].asUUID(); - group.mPowers = ll_U64_from_sd((*iter_group)["GroupPowers"]); - group.mAcceptNotices = (*iter_group)["AcceptNotices"].asBoolean(); - group.mListInProfile = body["NewGroupData"][group_index]["ListInProfile"].asBoolean(); - group.mInsigniaID = (*iter_group)["GroupInsigniaID"].asUUID(); - group.mName = (*iter_group)["GroupName"].asString(); - group.mContribution = (*iter_group)["Contribution"].asInteger(); - - group_index++; - - if(group.mID.notNull()) - { - need_floater_update = true; - // Remove the group if it already exists remove it and add the new data to pick up changes. - std::vector::iterator found_it = std::find(gAgent.mGroups.begin(), gAgent.mGroups.end(), group); - if (found_it != gAgent.mGroups.end()) - { - gAgent.mGroups.erase(found_it); - } - gAgent.mGroups.push_back(group); - } - if (need_floater_update) - { - update_group_floaters(group.mID); - } - } - } -}; - -LLHTTPRegistration - gHTTPRegistrationAgentGroupDataUpdateViewerNode ("/message/AgentGroupDataUpdate"); - -// static -void LLAgent::processAgentDataUpdate(LLMessageSystem *msg, void **) -{ - LLUUID agent_id; - - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); - - if (agent_id != gAgentID) - { - LL_WARNS() << "processAgentDataUpdate for agent other than me" << LL_ENDL; - return; - } - - msg->getStringFast(_PREHASH_AgentData, _PREHASH_GroupTitle, gAgent.mGroupTitle); - LLUUID active_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_ActiveGroupID, active_id); - - - if(active_id.notNull()) - { - gAgent.mGroupID = active_id; - msg->getU64(_PREHASH_AgentData, "GroupPowers", gAgent.mGroupPowers); - msg->getString(_PREHASH_AgentData, _PREHASH_GroupName, gAgent.mGroupName); - } - else - { - gAgent.mGroupID.setNull(); - gAgent.mGroupPowers = 0; - gAgent.mGroupName.clear(); - } - - update_group_floaters(active_id); -} - -// static -void LLAgent::processScriptControlChange(LLMessageSystem *msg, void **) -{ - S32 block_count = msg->getNumberOfBlocks("Data"); - for (S32 block_index = 0; block_index < block_count; block_index++) - { - bool take_controls; - U32 controls; - bool passon; - U32 i; - msg->getBOOL("Data", "TakeControls", take_controls, block_index); - if (take_controls) - { - // take controls - msg->getU32("Data", "Controls", controls, block_index ); - msg->getBOOL("Data", "PassToAgent", passon, block_index ); - for (i = 0; i < TOTAL_CONTROLS; i++) - { - if (controls & ( 1 << i)) - { - if (passon) - { - gAgent.mControlsTakenPassedOnCount[i]++; - } - else - { - gAgent.mControlsTakenCount[i]++; - } - } - } - } - else - { - // release controls - msg->getU32("Data", "Controls", controls, block_index ); - msg->getBOOL("Data", "PassToAgent", passon, block_index ); - for (i = 0; i < TOTAL_CONTROLS; i++) - { - if (controls & ( 1 << i)) - { - if (passon) - { - gAgent.mControlsTakenPassedOnCount[i]--; - if (gAgent.mControlsTakenPassedOnCount[i] < 0) - { - gAgent.mControlsTakenPassedOnCount[i] = 0; - } - } - else - { - gAgent.mControlsTakenCount[i]--; - if (gAgent.mControlsTakenCount[i] < 0) - { - gAgent.mControlsTakenCount[i] = 0; - } - } - } - } - } - } -} - -/* -// static -void LLAgent::processControlTake(LLMessageSystem *msg, void **) -{ - U32 controls; - msg->getU32("Data", "Controls", controls ); - U32 passon; - msg->getBOOL("Data", "PassToAgent", passon ); - - S32 i; - S32 total_count = 0; - for (i = 0; i < TOTAL_CONTROLS; i++) - { - if (controls & ( 1 << i)) - { - if (passon) - { - gAgent.mControlsTakenPassedOnCount[i]++; - } - else - { - gAgent.mControlsTakenCount[i]++; - } - total_count++; - } - } - - // Any control taken? If so, might be first time. - if (total_count > 0) - { - LLFirstUse::useOverrideKeys(); - } -} - -// static -void LLAgent::processControlRelease(LLMessageSystem *msg, void **) -{ - U32 controls; - msg->getU32("Data", "Controls", controls ); - U32 passon; - msg->getBOOL("Data", "PassToAgent", passon ); - - S32 i; - for (i = 0; i < TOTAL_CONTROLS; i++) - { - if (controls & ( 1 << i)) - { - if (passon) - { - gAgent.mControlsTakenPassedOnCount[i]--; - if (gAgent.mControlsTakenPassedOnCount[i] < 0) - { - gAgent.mControlsTakenPassedOnCount[i] = 0; - } - } - else - { - gAgent.mControlsTakenCount[i]--; - if (gAgent.mControlsTakenCount[i] < 0) - { - gAgent.mControlsTakenCount[i] = 0; - } - } - } - } -} -*/ - -bool LLAgent::anyControlGrabbed() const -{ - for (U32 i = 0; i < TOTAL_CONTROLS; i++) - { - if (gAgent.mControlsTakenCount[i] > 0) - return true; - if (gAgent.mControlsTakenPassedOnCount[i] > 0) - return true; - } - return false; -} - -bool LLAgent::isControlGrabbed(S32 control_index) const -{ - return mControlsTakenCount[control_index] > 0; -} - -void LLAgent::forceReleaseControls() -{ - gMessageSystem->newMessage("ForceScriptControlRelease"); - gMessageSystem->nextBlock("AgentData"); - gMessageSystem->addUUID("AgentID", getID()); - gMessageSystem->addUUID("SessionID", getSessionID()); - sendReliableMessage(); -} - -void LLAgent::setHomePosRegion( const U64& region_handle, const LLVector3& pos_region) -{ - mHaveHomePosition = true; - mHomeRegionHandle = region_handle; - mHomePosRegion = pos_region; -} - -bool LLAgent::getHomePosGlobal( LLVector3d* pos_global ) -{ - if(!mHaveHomePosition) - { - return false; - } - F32 x = 0; - F32 y = 0; - from_region_handle( mHomeRegionHandle, &x, &y); - pos_global->setVec( x + mHomePosRegion.mV[VX], y + mHomePosRegion.mV[VY], mHomePosRegion.mV[VZ] ); - return true; -} - -bool LLAgent::isInHomeRegion() -{ - if(!mHaveHomePosition) - { - return false; - } - if (!getRegion()) - { - return false; - } - if (getRegion()->getHandle() != mHomeRegionHandle) - { - return false; - } - return true; -} - -void LLAgent::clearVisualParams(void *data) -{ - if (isAgentAvatarValid()) - { - gAgentAvatarp->clearVisualParamWeights(); - gAgentAvatarp->updateVisualParams(); - } -} - -//--------------------------------------------------------------------------- -// Teleport -//--------------------------------------------------------------------------- - -// teleportCore() - stuff to do on any teleport -// protected -bool LLAgent::teleportCore(bool is_local) -{ - LL_DEBUGS("Teleport") << "In teleport core" << LL_ENDL; - if ((TELEPORT_NONE != mTeleportState) && (mTeleportState != TELEPORT_PENDING)) - { - LL_WARNS() << "Attempt to teleport when already teleporting." << LL_ENDL; - return false; - } - - // force stand up and stop a sitting animation (if any), see MAINT-3969 - if (isAgentAvatarValid() && gAgentAvatarp->getParent() && gAgentAvatarp->isSitting()) - { - gAgentAvatarp->getOffObject(); - } - -#if 0 - // This should not exist. It has been added, removed, added, and now removed again. - // This change needs to come from the simulator. Otherwise, the agent ends up out of - // sync with other viewers. Discuss in DEV-14145/VWR-6744 before reenabling. - - // Stop all animation before actual teleporting - if (isAgentAvatarValid()) - { - for ( LLVOAvatar::AnimIterator anim_it= gAgentAvatarp->mPlayingAnimations.begin(); - anim_it != gAgentAvatarp->mPlayingAnimations.end(); - ++anim_it) - { - gAgentAvatarp->stopMotion(anim_it->first); - } - gAgentAvatarp->processAnimationStateChanges(); - } -#endif - - // Don't call LLFirstUse::useTeleport because we don't know - // yet if the teleport will succeed. Look in - // process_teleport_location_reply - - // hide land floater too - it'll be out of date - LLFloaterReg::hideInstance("about_land"); - - // hide the Region/Estate floater - LLFloaterReg::hideInstance("region_info"); - - LLViewerParcelMgr::getInstance()->deselectLand(); - LLViewerMediaFocus::getInstance()->clearFocus(); - - // Close all pie menus, deselect land, etc. - // Don't change the camera until we know teleport succeeded. JC - gAgentCamera.resetView(false); - - // local logic - add(LLStatViewer::TELEPORT, 1); - if (is_local) - { - LL_INFOS("Teleport") << "Setting teleport state to TELEPORT_LOCAL" << LL_ENDL; - gAgent.setTeleportState( LLAgent::TELEPORT_LOCAL ); - } - else - { - gTeleportDisplay = true; - LL_INFOS("Teleport") << "Non-local, setting teleport state to TELEPORT_START" << LL_ENDL; - gAgent.setTeleportState( LLAgent::TELEPORT_START ); - } - make_ui_sound("UISndTeleportOut"); - - // MBW -- Let the voice client know a teleport has begun so it can leave the existing channel. - // This was breaking the case of teleporting within a single sim. Backing it out for now. -// LLVoiceClient::getInstance()->leaveChannel(); - - return true; -} - -bool LLAgent::hasRestartableFailedTeleportRequest() -{ - return ((mTeleportRequest != NULL) && (mTeleportRequest->getStatus() == LLTeleportRequest::kFailed) && - mTeleportRequest->canRestartTeleport()); -} - -void LLAgent::restartFailedTeleportRequest() -{ - LL_INFOS("Teleport") << "Agent wishes to restart failed teleport." << LL_ENDL; - if (hasRestartableFailedTeleportRequest()) - { - mTeleportRequest->setStatus(LLTeleportRequest::kRestartPending); - startTeleportRequest(); - } -} - -void LLAgent::clearTeleportRequest() -{ - if(LLVoiceClient::instanceExists()) - { - LLVoiceClient::getInstance()->setHidden(false); - } - mTeleportRequest.reset(); - mTPNeedsNeabyChatSeparator = false; -} - -void LLAgent::setMaturityRatingChangeDuringTeleport(U8 pMaturityRatingChange) -{ - mIsMaturityRatingChangingDuringTeleport = true; - mMaturityRatingChange = pMaturityRatingChange; -} - -void LLAgent::sheduleTeleportIM() -{ - // is supposed to be called during teleport so we are still waiting for parcel - mTPNeedsNeabyChatSeparator = true; -} - -bool LLAgent::hasPendingTeleportRequest() -{ - return ((mTeleportRequest != NULL) && - ((mTeleportRequest->getStatus() == LLTeleportRequest::kPending) || - (mTeleportRequest->getStatus() == LLTeleportRequest::kRestartPending))); -} - -void LLAgent::startTeleportRequest() -{ - LL_INFOS("Telport") << "Agent handling start teleport request." << LL_ENDL; - if(LLVoiceClient::instanceExists()) - { - LLVoiceClient::getInstance()->setHidden(true); - } - if (hasPendingTeleportRequest()) - { - LLUIUsage::instance().logCommand("Agent.StartTeleportRequest"); - mTeleportCanceled.reset(); - if (!isMaturityPreferenceSyncedWithServer()) - { - gTeleportDisplay = true; - LL_INFOS("Teleport") << "Maturity preference not synced yet, setting teleport state to TELEPORT_PENDING" << LL_ENDL; - setTeleportState(TELEPORT_PENDING); - } - else - { - switch (mTeleportRequest->getStatus()) - { - case LLTeleportRequest::kPending : - mTeleportRequest->setStatus(LLTeleportRequest::kStarted); - mTeleportRequest->startTeleport(); - break; - case LLTeleportRequest::kRestartPending : - llassert(mTeleportRequest->canRestartTeleport()); - mTeleportRequest->setStatus(LLTeleportRequest::kStarted); - mTeleportRequest->restartTeleport(); - break; - default : - llassert(0); - break; - } - } - } -} - -void LLAgent::handleTeleportFinished() -{ - LL_INFOS("Teleport") << "Agent handling teleport finished." << LL_ENDL; - if (mTPNeedsNeabyChatSeparator) - { - // parcel is ready at this point - addTPNearbyChatSeparator(); - mTPNeedsNeabyChatSeparator = false; - } - clearTeleportRequest(); - mTeleportCanceled.reset(); - if (mIsMaturityRatingChangingDuringTeleport) - { - // notify user that the maturity preference has been changed - std::string maturityRating = LLViewerRegion::accessToString(mMaturityRatingChange); - LLStringUtil::toLower(maturityRating); - LLSD args; - args["RATING"] = maturityRating; - LLNotificationsUtil::add("PreferredMaturityChanged", args); - mIsMaturityRatingChangingDuringTeleport = false; - } - - if (mRegionp) - { - if (mRegionp->capabilitiesReceived()) - { - LL_DEBUGS("Teleport") << "capabilities have been received for region handle " - << mRegionp->getHandle() - << " id " << mRegionp->getRegionID() - << ", calling onCapabilitiesReceivedAfterTeleport()" - << LL_ENDL; - onCapabilitiesReceivedAfterTeleport(); - } - else - { - LL_DEBUGS("Teleport") << "Capabilities not yet received for region handle " - << mRegionp->getHandle() - << " id " << mRegionp->getRegionID() - << LL_ENDL; - mRegionp->setCapabilitiesReceivedCallback(boost::bind(&LLAgent::onCapabilitiesReceivedAfterTeleport)); - } - } - LLPerfStats::tunables.autoTuneTimeout = true; -} - -void LLAgent::handleTeleportFailed() -{ - LL_WARNS("Teleport") << "Agent handling teleport failure!" << LL_ENDL; - if(LLVoiceClient::instanceExists()) - { - LLVoiceClient::getInstance()->setHidden(false); - } - - setTeleportState(LLAgent::TELEPORT_NONE); - // Unlock the UI if the progress bar has been shown. -// gViewerWindow->setShowProgress(false); -// gTeleportDisplay = false; - - if (mTeleportRequest) - { - mTeleportRequest->setStatus(LLTeleportRequest::kFailed); - } - if (mIsMaturityRatingChangingDuringTeleport) - { - // notify user that the maturity preference has been changed - std::string maturityRating = LLViewerRegion::accessToString(mMaturityRatingChange); - LLStringUtil::toLower(maturityRating); - LLSD args; - args["RATING"] = maturityRating; - LLNotificationsUtil::add("PreferredMaturityChanged", args); - mIsMaturityRatingChangingDuringTeleport = false; - } - - mTPNeedsNeabyChatSeparator = false; - - LLPerfStats::tunables.autoTuneTimeout = true; -} - -/*static*/ -void LLAgent::addTPNearbyChatSeparator() -{ - LLViewerRegion* agent_region = gAgent.getRegion(); - LLParcel* agent_parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - if (!agent_region || !agent_parcel) - { - return; - } - - LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); - if (nearby_chat) - { - std::string location_name; - LLAgentUI::ELocationFormat format = LLAgentUI::LOCATION_FORMAT_NO_MATURITY; - - // Might be better to provide slurl to chat - if (!LLAgentUI::buildLocationString(location_name, format)) - { - location_name = "Teleport to new region"; // Shouldn't happen - } - - LLChat chat; - chat.mFromName = location_name; - chat.mMuted = false; - chat.mFromID = LLUUID::null; - chat.mSourceType = CHAT_SOURCE_TELEPORT; - chat.mChatStyle = CHAT_STYLE_TELEPORT_SEP; - chat.mText = ""; - - LLSD args; - args["do_not_log"] = true; - nearby_chat->addMessage(chat, true, args); - } -} - -/*static*/ -void LLAgent::onCapabilitiesReceivedAfterTeleport() -{ - if (gAgent.getRegion()) - { - LL_DEBUGS("Teleport") << "running after capabilities received callback has been triggered, agent region " - << gAgent.getRegion()->getHandle() - << " id " << gAgent.getRegion()->getRegionID() - << " name " << gAgent.getRegion()->getName() - << LL_ENDL; - } - else - { - LL_WARNS("Teleport") << "called when agent region is null!" << LL_ENDL; - } - - check_merchant_status(); -} - - -void LLAgent::teleportRequest( - const U64& region_handle, - const LLVector3& pos_local, - bool look_at_from_camera) -{ - LLViewerRegion* regionp = getRegion(); - if (regionp && teleportCore(region_handle == regionp->getHandle())) - { - LL_INFOS("Teleport") << "Sending TeleportLocationRequest: '" << region_handle << "':" - << pos_local << LL_ENDL; - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("TeleportLocationRequest"); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, getID()); - msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); - msg->nextBlockFast(_PREHASH_Info); - msg->addU64("RegionHandle", region_handle); - msg->addVector3("Position", pos_local); - LLVector3 look_at(0,1,0); - if (look_at_from_camera) - { - look_at = LLViewerCamera::getInstance()->getAtAxis(); - } - msg->addVector3("LookAt", look_at); - sendReliableMessage(); - } -} - -// Landmark ID = LLUUID::null means teleport home -void LLAgent::teleportViaLandmark(const LLUUID& landmark_asset_id) -{ - if (landmark_asset_id.isNull()) - { - gAgentCamera.resetView(); - } - mTeleportRequest = LLTeleportRequestPtr(new LLTeleportRequestViaLandmark(landmark_asset_id)); - startTeleportRequest(); -} - -void LLAgent::doTeleportViaLandmark(const LLUUID& landmark_asset_id) -{ - LLViewerRegion *regionp = getRegion(); - if(regionp && teleportCore()) - { - LL_INFOS("Teleport") << "Sending TeleportLandmarkRequest. Current region handle " << regionp->getHandle() - << " region id " << regionp->getRegionID() - << " requested landmark id " << landmark_asset_id - << LL_ENDL; - - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_TeleportLandmarkRequest); - msg->nextBlockFast(_PREHASH_Info); - msg->addUUIDFast(_PREHASH_AgentID, getID()); - msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); - msg->addUUIDFast(_PREHASH_LandmarkID, landmark_asset_id); - sendReliableMessage(); - } -} - -void LLAgent::teleportViaLure(const LLUUID& lure_id, bool godlike) -{ - mTeleportRequest = LLTeleportRequestPtr(new LLTeleportRequestViaLure(lure_id, godlike)); - startTeleportRequest(); -} - -void LLAgent::doTeleportViaLure(const LLUUID& lure_id, bool godlike) -{ - LLViewerRegion* regionp = getRegion(); - if(regionp && teleportCore()) - { - U32 teleport_flags = 0x0; - if (godlike) - { - teleport_flags |= TELEPORT_FLAGS_VIA_GODLIKE_LURE; - teleport_flags |= TELEPORT_FLAGS_DISABLE_CANCEL; - } - else - { - teleport_flags |= TELEPORT_FLAGS_VIA_LURE; - } - - LL_INFOS("Teleport") << "Sending TeleportLureRequest." - << " Current region handle " << regionp->getHandle() - << " region id " << regionp->getRegionID() - << " lure id " << lure_id - << LL_ENDL; - // send the message - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_TeleportLureRequest); - msg->nextBlockFast(_PREHASH_Info); - msg->addUUIDFast(_PREHASH_AgentID, getID()); - msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); - msg->addUUIDFast(_PREHASH_LureID, lure_id); - // teleport_flags is a legacy field, now derived sim-side: - msg->addU32("TeleportFlags", teleport_flags); - sendReliableMessage(); - } -} - - -// James Cook, July 28, 2005 -void LLAgent::teleportCancel() -{ - if (!hasPendingTeleportRequest()) - { - LLViewerRegion* regionp = getRegion(); - if(regionp) - { - LL_INFOS("Teleport") << "Sending TeleportCancel" << LL_ENDL; - - // send the message - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("TeleportCancel"); - msg->nextBlockFast(_PREHASH_Info); - msg->addUUIDFast(_PREHASH_AgentID, getID()); - msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); - sendReliableMessage(); - } - mTeleportCanceled = mTeleportRequest; - } - clearTeleportRequest(); - gAgent.setTeleportState( LLAgent::TELEPORT_NONE ); -} - -void LLAgent::restoreCanceledTeleportRequest() -{ - if (mTeleportCanceled != NULL) - { - LL_INFOS() << "Restoring canceled teleport request, setting state to TELEPORT_REQUESTED" << LL_ENDL; - gAgent.setTeleportState( LLAgent::TELEPORT_REQUESTED ); - mTeleportRequest = mTeleportCanceled; - mTeleportCanceled.reset(); - gTeleportDisplay = true; - gTeleportDisplayTimer.reset(); - } -} - -void LLAgent::teleportViaLocation(const LLVector3d& pos_global) -{ - mTeleportRequest = LLTeleportRequestPtr(new LLTeleportRequestViaLocation(pos_global)); - startTeleportRequest(); -} - -void LLAgent::doTeleportViaLocation(const LLVector3d& pos_global) -{ - LLViewerRegion* regionp = getRegion(); - - if (!regionp) - { - return; - } - - U64 handle = to_region_handle(pos_global); - LLSimInfo* info = LLWorldMap::getInstance()->simInfoFromHandle(handle); - if(regionp && info) - { - LLVector3d region_origin = info->getGlobalOrigin(); - LLVector3 pos_local( - (F32)(pos_global.mdV[VX] - region_origin.mdV[VX]), - (F32)(pos_global.mdV[VY] - region_origin.mdV[VY]), - (F32)(pos_global.mdV[VZ])); - teleportRequest(handle, pos_local); - } - else if(regionp && - teleportCore(regionp->getHandle() == to_region_handle_global((F32)pos_global.mdV[VX], (F32)pos_global.mdV[VY]))) - { - // send the message - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_TeleportLocationRequest); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, getID()); - msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); - - msg->nextBlockFast(_PREHASH_Info); - F32 width = regionp->getWidth(); - LLVector3 pos(fmod((F32)pos_global.mdV[VX], width), - fmod((F32)pos_global.mdV[VY], width), - (F32)pos_global.mdV[VZ]); - F32 region_x = (F32)(pos_global.mdV[VX]); - F32 region_y = (F32)(pos_global.mdV[VY]); - U64 region_handle = to_region_handle_global(region_x, region_y); - msg->addU64Fast(_PREHASH_RegionHandle, region_handle); - msg->addVector3Fast(_PREHASH_Position, pos); - pos.mV[VX] += 1; - msg->addVector3Fast(_PREHASH_LookAt, pos); - - LL_WARNS("Teleport") << "Sending deprecated(?) TeleportLocationRequest." - << " pos_global " << pos_global - << " region_x " << region_x - << " region_y " << region_y - << " region_handle " << region_handle - << LL_ENDL; - - sendReliableMessage(); - } -} - -// Teleport to global position, but keep facing in the same direction -void LLAgent::teleportViaLocationLookAt(const LLVector3d& pos_global) -{ - mTeleportRequest = LLTeleportRequestPtr(new LLTeleportRequestViaLocationLookAt(pos_global)); - startTeleportRequest(); -} - -void LLAgent::doTeleportViaLocationLookAt(const LLVector3d& pos_global) -{ - mbTeleportKeepsLookAt = true; - - if(!gAgentCamera.isfollowCamLocked()) - { - gAgentCamera.setFocusOnAvatar(false, ANIMATE); // detach camera form avatar, so it keeps direction - } - - U64 region_handle = to_region_handle(pos_global); - LLVector3 pos_local = (LLVector3)(pos_global - from_region_handle(region_handle)); - teleportRequest(region_handle, pos_local, getTeleportKeepsLookAt()); -} - -LLAgent::ETeleportState LLAgent::getTeleportState() const -{ - return (mTeleportRequest && (mTeleportRequest->getStatus() == LLTeleportRequest::kFailed)) ? - TELEPORT_NONE : mTeleportState; -} - - -void LLAgent::setTeleportState(ETeleportState state) -{ - if (mTeleportRequest && (state != TELEPORT_NONE) && (mTeleportRequest->getStatus() == LLTeleportRequest::kFailed)) - { // A late message has come in regarding a failed teleport. - // We have already decided that it failed so should not reinitiate the teleport sequence in the viewer. - LL_WARNS("Teleport") << "Attempt to set teleport state to " << state << - " for previously failed teleport. Ignore!" << LL_ENDL; - return; - } - LL_DEBUGS("Teleport") << "Setting teleport state to " - << LLAgent::teleportStateName(state) << "(" << state << ")" - << " Previous state: " - << teleportStateName(mTeleportState) << "(" << mTeleportState << ")" - << LL_ENDL; - mTeleportState = state; - if (mTeleportState > TELEPORT_NONE && gSavedSettings.getBOOL("FreezeTime")) - { - LLFloaterReg::hideInstance("snapshot"); - } - - switch (mTeleportState) - { - case TELEPORT_NONE: - mbTeleportKeepsLookAt = false; - break; - - case TELEPORT_MOVING: - // We're outa here. Save "back" slurl. - LLAgentUI::buildSLURL(*mTeleportSourceSLURL); - break; - - case TELEPORT_ARRIVING: - // First two position updates after a teleport tend to be weird - //LLViewerStats::getInstance()->mAgentPositionSnaps.mCountOfNextUpdatesToIgnore = 2; - - // Let the interested parties know we've teleported. - LLViewerParcelMgr::getInstance()->onTeleportFinished(false, getPositionGlobal()); - break; - - default: - break; - } -} - - -void LLAgent::stopCurrentAnimations() -{ - LL_DEBUGS("Avatar") << "Stopping current animations" << LL_ENDL; - - // This function stops all current overriding animations on this - // avatar, propagating this change back to the server. - if (isAgentAvatarValid()) - { - std::vector anim_ids; - - for ( LLVOAvatar::AnimIterator anim_it = - gAgentAvatarp->mPlayingAnimations.begin(); - anim_it != gAgentAvatarp->mPlayingAnimations.end(); - anim_it++) - { - if ((anim_it->first == ANIM_AGENT_DO_NOT_DISTURB)|| - (anim_it->first == ANIM_AGENT_SIT_GROUND_CONSTRAINED)) - { - // don't cancel a ground-sit anim, as viewers - // use this animation's status in - // determining whether we're sitting. ick. - LL_DEBUGS("Avatar") << "sit or do-not-disturb animation will not be stopped" << LL_ENDL; - } - else - { - // stop this animation locally - gAgentAvatarp->stopMotion(anim_it->first, true); - // ...and tell the server to tell everyone. - anim_ids.push_back(anim_it->first); - } - } - - sendAnimationRequests(anim_ids, ANIM_REQUEST_STOP); - - // Tell the region to clear any animation state overrides - sendAnimationStateReset(); - - // Revoke all animation permissions - if (mRegionp && - gSavedSettings.getBOOL("RevokePermsOnStopAnimation")) - { - U32 permissions = SCRIPT_PERMISSIONS[SCRIPT_PERMISSION_TRIGGER_ANIMATION].permbit | SCRIPT_PERMISSIONS[SCRIPT_PERMISSION_OVERRIDE_ANIMATIONS].permbit; - sendRevokePermissions(mRegionp->getRegionID(), permissions); - if (gAgentAvatarp->isSitting()) - { // Also stand up, since auto-granted sit animation permission has been revoked - gAgent.standUp(); - } - } - - // re-assert at least the default standing animation, because - // viewers get confused by avs with no associated anims. - sendAnimationRequest(ANIM_AGENT_STAND, ANIM_REQUEST_START); - } -} - -void LLAgent::fidget() -{ - if (!getAFK()) - { - F32 curTime = mFidgetTimer.getElapsedTimeF32(); - if (curTime > mNextFidgetTime) - { - // pick a random fidget anim here - S32 oldFidget = mCurrentFidget; - - mCurrentFidget = ll_rand(NUM_AGENT_STAND_ANIMS); - - if (mCurrentFidget != oldFidget) - { - LLAgent::stopFidget(); - - - switch(mCurrentFidget) - { - case 0: - mCurrentFidget = 0; - break; - case 1: - sendAnimationRequest(ANIM_AGENT_STAND_1, ANIM_REQUEST_START); - mCurrentFidget = 1; - break; - case 2: - sendAnimationRequest(ANIM_AGENT_STAND_2, ANIM_REQUEST_START); - mCurrentFidget = 2; - break; - case 3: - sendAnimationRequest(ANIM_AGENT_STAND_3, ANIM_REQUEST_START); - mCurrentFidget = 3; - break; - case 4: - sendAnimationRequest(ANIM_AGENT_STAND_4, ANIM_REQUEST_START); - mCurrentFidget = 4; - break; - } - } - - // calculate next fidget time - mNextFidgetTime = curTime + ll_frand(MAX_FIDGET_TIME - MIN_FIDGET_TIME) + MIN_FIDGET_TIME; - } - } -} - -void LLAgent::stopFidget() -{ - std::vector anims; - anims.reserve(4); - anims.push_back(ANIM_AGENT_STAND_1); - anims.push_back(ANIM_AGENT_STAND_2); - anims.push_back(ANIM_AGENT_STAND_3); - anims.push_back(ANIM_AGENT_STAND_4); - - gAgent.sendAnimationRequests(anims, ANIM_REQUEST_STOP); -} - - -void LLAgent::requestEnterGodMode() -{ - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_RequestGodlikePowers); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_RequestBlock); - msg->addBOOLFast(_PREHASH_Godlike, true); - msg->addUUIDFast(_PREHASH_Token, LLUUID::null); - - // simulators need to know about your request - sendReliableMessage(); -} - -void LLAgent::requestLeaveGodMode() -{ - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_RequestGodlikePowers); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_RequestBlock); - msg->addBOOLFast(_PREHASH_Godlike, false); - msg->addUUIDFast(_PREHASH_Token, LLUUID::null); - - // simulator needs to know about your request - sendReliableMessage(); -} - -void LLAgent::sendAgentDataUpdateRequest() -{ - gMessageSystem->newMessageFast(_PREHASH_AgentDataUpdateRequest); - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - sendReliableMessage(); -} - -void LLAgent::sendAgentUserInfoRequest() -{ - std::string cap; - - if (getID().isNull()) - return; // not logged in - - if (mRegionp) - cap = mRegionp->getCapability("UserInfo"); - - if (!cap.empty()) - { - LLCoros::instance().launch("requestAgentUserInfoCoro", - boost::bind(&LLAgent::requestAgentUserInfoCoro, this, cap)); - } - else - { - sendAgentUserInfoRequestMessage(); - } -} - -void LLAgent::requestAgentUserInfoCoro(std::string capurl) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("requestAgentUserInfoCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); - LLCore::HttpHeaders::ptr_t httpHeaders; - - httpOpts->setFollowRedirects(true); - - LLSD result = httpAdapter->getAndSuspend(httpRequest, capurl, httpOpts, httpHeaders); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS("UserInfo") << "Failed to get user information." << LL_ENDL; - return; - } - else if (!result["success"].asBoolean()) - { - LL_WARNS("UserInfo") << "Failed to get user information: " << result["message"] << LL_ENDL; - return; - } - - std::string email; - std::string dir_visibility; - - email = result["email"].asString(); - dir_visibility = result["directory_visibility"].asString(); - - // TODO: This should probably be changed. I'm not entirely comfortable - // having LLAgent interact directly with the UI in this way. - LLFloaterPreference::updateUserInfo(dir_visibility); - LLFloaterSnapshot::setAgentEmail(email); -} - -void LLAgent::sendAgentUpdateUserInfo(const std::string& directory_visibility) -{ - std::string cap; - - if (getID().isNull()) - return; // not logged in - - if (mRegionp) - cap = mRegionp->getCapability("UserInfo"); - - if (!cap.empty()) - { - LLCoros::instance().launch("updateAgentUserInfoCoro", - boost::bind(&LLAgent::updateAgentUserInfoCoro, this, cap, directory_visibility)); - } - else - { - sendAgentUpdateUserInfoMessage(directory_visibility); - } -} - - -void LLAgent::updateAgentUserInfoCoro(std::string capurl, std::string directory_visibility) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("requestAgentUserInfoCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); - LLCore::HttpHeaders::ptr_t httpHeaders; - - httpOpts->setFollowRedirects(true); - LLSD body(LLSDMap - ("dir_visibility", LLSD::String(directory_visibility))); - - - LLSD result = httpAdapter->postAndSuspend(httpRequest, capurl, body, httpOpts, httpHeaders); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS("UserInfo") << "Failed to set user information." << LL_ENDL; - } - else if (!result["success"].asBoolean()) - { - LL_WARNS("UserInfo") << "Failed to set user information: " << result["message"] << LL_ENDL; - } -} - -// deprecated: -// May be removed when UserInfo cap propagates to all simhosts in grid -void LLAgent::sendAgentUserInfoRequestMessage() -{ - gMessageSystem->newMessageFast(_PREHASH_UserInfoRequest); - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, getID()); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, getSessionID()); - sendReliableMessage(); -} - -void LLAgent::sendAgentUpdateUserInfoMessage(const std::string& directory_visibility) -{ - gMessageSystem->newMessageFast(_PREHASH_UpdateUserInfo); - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, getID()); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, getSessionID()); - gMessageSystem->nextBlockFast(_PREHASH_UserData); - gMessageSystem->addString("DirectoryVisibility", directory_visibility); - gAgent.sendReliableMessage(); - -} -// end deprecated -//------ - -void LLAgent::observeFriends() -{ - if(!mFriendObserver) - { - mFriendObserver = new LLAgentFriendObserver; - LLAvatarTracker::instance().addObserver(mFriendObserver); - friendsChanged(); - } -} - -std::map LLAgent::sTeleportStateName = { { TELEPORT_NONE, "TELEPORT_NONE" }, - { TELEPORT_START, "TELEPORT_START" }, - { TELEPORT_REQUESTED, "TELEPORT_REQUESTED" }, - { TELEPORT_MOVING, "TELEPORT_MOVING" }, - { TELEPORT_START_ARRIVAL, "TELEPORT_START_ARRIVAL" }, - { TELEPORT_ARRIVING, "TELEPORT_ARRIVING" }, - { TELEPORT_LOCAL, "TELEPORT_LOCAL" }, - { TELEPORT_PENDING, "TELEPORT_PENDING" } }; - -const std::string& LLAgent::teleportStateName(S32 state) -{ - static std::string invalid_state_str("INVALID"); - auto iter = LLAgent::sTeleportStateName.find(state); - if (iter != LLAgent::sTeleportStateName.end()) - { - return iter->second; - } - else - { - return invalid_state_str; - } -} - -const std::string& LLAgent::getTeleportStateName() const -{ - return teleportStateName(getTeleportState()); -} - -void LLAgent::parseTeleportMessages(const std::string& xml_filename) -{ - LLXMLNodePtr root; - bool success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root); - - if (!success || !root || !root->hasName( "teleport_messages" )) - { - LL_ERRS() << "Problem reading teleport string XML file: " - << xml_filename << LL_ENDL; - return; - } - - for (LLXMLNode* message_set = root->getFirstChild(); - message_set != NULL; - message_set = message_set->getNextSibling()) - { - if ( !message_set->hasName("message_set") ) continue; - - std::map *teleport_msg_map = NULL; - std::string message_set_name; - - if ( message_set->getAttributeString("name", message_set_name) ) - { - //now we loop over all the string in the set and add them - //to the appropriate set - if ( message_set_name == "errors" ) - { - teleport_msg_map = &sTeleportErrorMessages; - } - else if ( message_set_name == "progress" ) - { - teleport_msg_map = &sTeleportProgressMessages; - } - } - - if ( !teleport_msg_map ) continue; - - std::string message_name; - for (LLXMLNode* message_node = message_set->getFirstChild(); - message_node != NULL; - message_node = message_node->getNextSibling()) - { - if ( message_node->hasName("message") && - message_node->getAttributeString("name", message_name) ) - { - (*teleport_msg_map)[message_name] = - message_node->getTextContents(); - } //end if ( message exists and has a name) - } //end for (all message in set) - }//end for (all message sets in xml file) -} - -const void LLAgent::getTeleportSourceSLURL(LLSLURL& slurl) const -{ - slurl = *mTeleportSourceSLURL; -} - -// static -void LLAgent::dumpGroupInfo() -{ - LL_INFOS() << "group " << gAgent.mGroupName << LL_ENDL; - LL_INFOS() << "ID " << gAgent.mGroupID << LL_ENDL; - LL_INFOS() << "powers " << gAgent.mGroupPowers << LL_ENDL; - LL_INFOS() << "title " << gAgent.mGroupTitle << LL_ENDL; - //LL_INFOS() << "insig " << gAgent.mGroupInsigniaID << LL_ENDL; -} - -// Draw a representation of current autopilot target -void LLAgent::renderAutoPilotTarget() -{ - if (mAutoPilot) - { - F32 height_meters; - LLVector3d target_global; - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - - // not textured - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - // lovely green - gGL.color4f(0.f, 1.f, 1.f, 1.f); - - target_global = mAutoPilotTargetGlobal; - - gGL.translatef((F32)(target_global.mdV[VX]), (F32)(target_global.mdV[VY]), (F32)(target_global.mdV[VZ])); - - height_meters = 1.f; - - gGL.scalef(height_meters, height_meters, height_meters); - - gSphere.render(); - - gGL.popMatrix(); - } -} - -/********************************************************************************/ - -//----------------------------------------------------------------------------- -// LLTeleportRequest -//----------------------------------------------------------------------------- - -LLTeleportRequest::LLTeleportRequest() - : mStatus(kPending) -{ -} - -LLTeleportRequest::~LLTeleportRequest() -{ -} - -bool LLTeleportRequest::canRestartTeleport() -{ - return false; -} - -void LLTeleportRequest::restartTeleport() -{ - llassert(0); -} - -// TODO this enum -> name idiom should be in a common class rather than repeated various places. -const std::string& LLTeleportRequest::statusName(EStatus status) -{ - static std::string invalid_status_str("INVALID"); - auto iter = LLTeleportRequest::sTeleportStatusName.find(status); - if (iter != LLTeleportRequest::sTeleportStatusName.end()) - { - return iter->second; - } - else - { - return invalid_status_str; - } -} - -std::ostream& operator<<(std::ostream& os, const LLTeleportRequest& req) -{ - req.toOstream(os); - return os; -} - -void LLTeleportRequest::toOstream(std::ostream& os) const -{ - os << "status " << statusName(mStatus) << "(" << mStatus << ")"; -} - -//----------------------------------------------------------------------------- -// LLTeleportRequestViaLandmark -//----------------------------------------------------------------------------- -LLTeleportRequestViaLandmark::LLTeleportRequestViaLandmark(const LLUUID &pLandmarkId) - : LLTeleportRequest(), - mLandmarkId(pLandmarkId) -{ - LL_INFOS("Teleport") << "LLTeleportRequestViaLandmark created, " << *this << LL_ENDL; -} - -LLTeleportRequestViaLandmark::~LLTeleportRequestViaLandmark() -{ - LL_INFOS("Teleport") << "~LLTeleportRequestViaLandmark, " << *this << LL_ENDL; -} - -void LLTeleportRequestViaLandmark::toOstream(std::ostream& os) const -{ - os << "landmark " << mLandmarkId << " "; - LLTeleportRequest::toOstream(os); -} - -bool LLTeleportRequestViaLandmark::canRestartTeleport() -{ - LL_INFOS("Teleport") << "LLTeleportRequestViaLandmark::canRestartTeleport? -> true, " << *this << LL_ENDL; - return true; -} - -void LLTeleportRequestViaLandmark::startTeleport() -{ - LL_INFOS("Teleport") << "LLTeleportRequestViaLandmark::startTeleport, " << *this << LL_ENDL; - gAgent.doTeleportViaLandmark(getLandmarkId()); -} - -void LLTeleportRequestViaLandmark::restartTeleport() -{ - LL_INFOS("Teleport") << "LLTeleportRequestViaLandmark::restartTeleport, " << *this << LL_ENDL; - gAgent.doTeleportViaLandmark(getLandmarkId()); -} -//----------------------------------------------------------------------------- -// LLTeleportRequestViaLure -//----------------------------------------------------------------------------- - -LLTeleportRequestViaLure::LLTeleportRequestViaLure(const LLUUID &pLureId, bool pIsLureGodLike) - : LLTeleportRequestViaLandmark(pLureId), - mIsLureGodLike(pIsLureGodLike) -{ - LL_INFOS("Teleport") << "LLTeleportRequestViaLure created" << LL_ENDL; -} - -LLTeleportRequestViaLure::~LLTeleportRequestViaLure() -{ - LL_INFOS("Teleport") << "~LLTeleportRequestViaLure" << LL_ENDL; -} - -void LLTeleportRequestViaLure::toOstream(std::ostream& os) const -{ - os << "mIsLureGodLike " << (S32) mIsLureGodLike << " "; - LLTeleportRequestViaLandmark::toOstream(os); -} - -bool LLTeleportRequestViaLure::canRestartTeleport() -{ - // stinson 05/17/2012 : cannot restart a teleport via lure because of server-side restrictions - // The current scenario is as follows: - // 1. User A initializes a request for User B to teleport via lure - // 2. User B accepts the teleport via lure request - // 3. The server sees the init request from User A and the accept request from User B and matches them up - // 4. The server then removes the paired requests up from the "queue" - // 5. The server then fails User B's teleport for reason of maturity level (for example) - // 6. User B's viewer prompts user to increase their maturity level profile value. - // 7. User B confirms and accepts increase in maturity level - // 8. User B's viewer then attempts to teleport via lure again - // 9. This request will time-out on the viewer-side because User A's initial request has been removed from the "queue" in step 4 - - LL_INFOS("Teleport") << "LLTeleportRequestViaLure::canRestartTeleport? -> false" << LL_ENDL; - return false; -} - -void LLTeleportRequestViaLure::startTeleport() -{ - LL_INFOS("Teleport") << "LLTeleportRequestViaLure::startTeleport" << LL_ENDL; - gAgent.doTeleportViaLure(getLandmarkId(), isLureGodLike()); -} - -//----------------------------------------------------------------------------- -// LLTeleportRequestViaLocation -//----------------------------------------------------------------------------- - -LLTeleportRequestViaLocation::LLTeleportRequestViaLocation(const LLVector3d &pPosGlobal) - : LLTeleportRequest(), - mPosGlobal(pPosGlobal) -{ - LL_INFOS("Teleport") << "LLTeleportRequestViaLocation created" << LL_ENDL; -} - -LLTeleportRequestViaLocation::~LLTeleportRequestViaLocation() -{ - LL_INFOS("Teleport") << "~LLTeleportRequestViaLocation" << LL_ENDL; -} - -void LLTeleportRequestViaLocation::toOstream(std::ostream& os) const -{ - os << "mPosGlobal " << mPosGlobal << " "; - LLTeleportRequest::toOstream(os); -} - -bool LLTeleportRequestViaLocation::canRestartTeleport() -{ - LL_INFOS("Teleport") << "LLTeleportRequestViaLocation::canRestartTeleport -> true" << LL_ENDL; - return true; -} - -void LLTeleportRequestViaLocation::startTeleport() -{ - LL_INFOS("Teleport") << "LLTeleportRequestViaLocation::startTeleport" << LL_ENDL; - gAgent.doTeleportViaLocation(getPosGlobal()); -} - -void LLTeleportRequestViaLocation::restartTeleport() -{ - LL_INFOS("Teleport") << "LLTeleportRequestViaLocation::restartTeleport" << LL_ENDL; - gAgent.doTeleportViaLocation(getPosGlobal()); -} - -//----------------------------------------------------------------------------- -// LLTeleportRequestViaLocationLookAt -//----------------------------------------------------------------------------- - -LLTeleportRequestViaLocationLookAt::LLTeleportRequestViaLocationLookAt(const LLVector3d &pPosGlobal) - : LLTeleportRequestViaLocation(pPosGlobal) -{ - LL_INFOS("Teleport") << "LLTeleportRequestViaLocationLookAt created" << LL_ENDL; -} - -LLTeleportRequestViaLocationLookAt::~LLTeleportRequestViaLocationLookAt() -{ - LL_INFOS("Teleport") << "~LLTeleportRequestViaLocationLookAt" << LL_ENDL; -} - -void LLTeleportRequestViaLocationLookAt::toOstream(std::ostream& os) const -{ - LLTeleportRequestViaLocation::toOstream(os); -} - -bool LLTeleportRequestViaLocationLookAt::canRestartTeleport() -{ - LL_INFOS("Teleport") << "LLTeleportRequestViaLocationLookAt::canRestartTeleport -> true" << LL_ENDL; - return true; -} - -void LLTeleportRequestViaLocationLookAt::startTeleport() -{ - LL_INFOS("Teleport") << "LLTeleportRequestViaLocationLookAt::startTeleport" << LL_ENDL; - gAgent.doTeleportViaLocationLookAt(getPosGlobal()); -} - -void LLTeleportRequestViaLocationLookAt::restartTeleport() -{ - LL_INFOS("Teleport") << "LLTeleportRequestViaLocationLookAt::restartTeleport" << LL_ENDL; - gAgent.doTeleportViaLocationLookAt(getPosGlobal()); -} - -// EOF +/** + * @file llagent.cpp + * @brief LLAgent class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#include "llviewerprecompiledheaders.h" + +#include "llagent.h" + +#include "pipeline.h" + +#include "llagentaccess.h" +#include "llagentbenefits.h" +#include "llagentcamera.h" +#include "llagentlistener.h" +#include "llagentwearables.h" +#include "llagentui.h" +#include "llappearancemgr.h" +#include "llanimationstates.h" +#include "llcallingcard.h" +#include "llchannelmanager.h" +#include "llchicletbar.h" +#include "llconsole.h" +#include "lldonotdisturbnotificationstorage.h" +#include "llfirstuse.h" +#include "llfloatercamera.h" +#include "llfloaterimcontainer.h" +#include "llfloaterperms.h" +#include "llfloaterpreference.h" +#include "llfloaterreg.h" +#include "llfloatersnapshot.h" +#include "llfloatertools.h" +#include "llgroupactions.h" +#include "llgroupmgr.h" +#include "llhudmanager.h" +#include "lljoystickbutton.h" +#include "llmorphview.h" +#include "llmoveview.h" +#include "llnavigationbar.h" // to show/hide navigation bar when changing mouse look state +#include "llfloaterimnearbychat.h" +#include "llnotificationsutil.h" +#include "llpaneltopinfobar.h" +#include "llparcel.h" +#include "llperfstats.h" +#include "llrendersphere.h" +#include "llscriptruntimeperms.h" +#include "llsdutil.h" +#include "llsky.h" +#include "llslurl.h" +#include "llsmoothstep.h" +#include "llstartup.h" +#include "llstatusbar.h" +#include "llteleportflags.h" +#include "lltool.h" +#include "lltoolbarview.h" +#include "lltoolpie.h" +#include "lltoolmgr.h" +#include "lltrans.h" +#include "lluictrl.h" +#include "llurlentry.h" +#include "llviewercontrol.h" +#include "llviewerdisplay.h" +#include "llviewerjoystick.h" +#include "llviewermediafocus.h" +#include "llviewermenu.h" +#include "llviewerobjectlist.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "llviewerstats.h" +#include "llviewerwindow.h" +#include "llvoavatarself.h" +#include "llwindow.h" +#include "llworld.h" +#include "llworldmap.h" +#include "stringize.h" +#include "llcorehttputil.h" +#include "lluiusage.h" + +using namespace LLAvatarAppearanceDefines; + +extern LLMenuBarGL* gMenuBarView; + +const bool ANIMATE = true; +const U8 AGENT_STATE_TYPING = 0x04; +const U8 AGENT_STATE_EDITING = 0x10; + +// Autopilot constants +const F32 AUTOPILOT_HEIGHT_ADJUST_DISTANCE = 8.f; // meters +const F32 AUTOPILOT_MIN_TARGET_HEIGHT_OFF_GROUND = 1.f; // meters +const F32 AUTOPILOT_MAX_TIME_NO_PROGRESS_WALK = 1.5f; // seconds +const F32 AUTOPILOT_MAX_TIME_NO_PROGRESS_FLY = 2.5f; // seconds. Flying is less presize, needs a bit more time + +const F32 MAX_VELOCITY_AUTO_LAND_SQUARED = 4.f * 4.f; +const F64 CHAT_AGE_FAST_RATE = 3.0; + +// fidget constants +const F32 MIN_FIDGET_TIME = 8.f; // seconds +const F32 MAX_FIDGET_TIME = 20.f; // seconds + +const S32 UI_FEATURE_VERSION = 1; +// For version 1: 1 - inventory, 2 - gltf +const S32 UI_FEATURE_FLAGS = 3; + +// The agent instance. +LLAgent gAgent; + +class LLTeleportRequest +{ +public: + enum EStatus + { + kPending, + kStarted, + kFailed, + kRestartPending + }; + + LLTeleportRequest(); + virtual ~LLTeleportRequest(); + + EStatus getStatus() const {return mStatus;}; + void setStatus(EStatus pStatus) {mStatus = pStatus;}; + + static std::map sTeleportStatusName; + static const std::string& statusName(EStatus status); + virtual void toOstream(std::ostream& os) const; + + virtual bool canRestartTeleport(); + + virtual void startTeleport() = 0; + virtual void restartTeleport(); + +protected: + +private: + EStatus mStatus; +}; + +std::map LLTeleportRequest::sTeleportStatusName = { { kPending, "kPending" }, + { kStarted, "kStarted" }, + { kFailed, "kFailed" }, + { kRestartPending, "kRestartPending"} }; + +class LLTeleportRequestViaLandmark : public LLTeleportRequest +{ +public: + LLTeleportRequestViaLandmark(const LLUUID &pLandmarkId); + virtual ~LLTeleportRequestViaLandmark(); + + virtual void toOstream(std::ostream& os) const; + + virtual bool canRestartTeleport(); + + virtual void startTeleport(); + virtual void restartTeleport(); + +protected: + inline const LLUUID &getLandmarkId() const {return mLandmarkId;}; + +private: + LLUUID mLandmarkId; +}; + +class LLTeleportRequestViaLure : public LLTeleportRequestViaLandmark +{ +public: + LLTeleportRequestViaLure(const LLUUID &pLureId, bool pIsLureGodLike); + virtual ~LLTeleportRequestViaLure(); + + virtual void toOstream(std::ostream& os) const; + + virtual bool canRestartTeleport(); + + virtual void startTeleport(); + +protected: + inline bool isLureGodLike() const {return mIsLureGodLike;}; + +private: + bool mIsLureGodLike; +}; + +class LLTeleportRequestViaLocation : public LLTeleportRequest +{ +public: + LLTeleportRequestViaLocation(const LLVector3d &pPosGlobal); + virtual ~LLTeleportRequestViaLocation(); + + virtual void toOstream(std::ostream& os) const; + + virtual bool canRestartTeleport(); + + virtual void startTeleport(); + virtual void restartTeleport(); + +protected: + inline const LLVector3d &getPosGlobal() const {return mPosGlobal;}; + +private: + LLVector3d mPosGlobal; +}; + + +class LLTeleportRequestViaLocationLookAt : public LLTeleportRequestViaLocation +{ +public: + LLTeleportRequestViaLocationLookAt(const LLVector3d &pPosGlobal); + virtual ~LLTeleportRequestViaLocationLookAt(); + + virtual void toOstream(std::ostream& os) const; + + virtual bool canRestartTeleport(); + + virtual void startTeleport(); + virtual void restartTeleport(); + +protected: + +private: + +}; + +//-------------------------------------------------------------------- +// Statics +// + +/// minimum time after setting away state before coming back based on movement +const F32 LLAgent::MIN_AFK_TIME = 10.0f; + +const F32 LLAgent::TYPING_TIMEOUT_SECS = 5.f; + +std::map LLAgent::sTeleportErrorMessages; +std::map LLAgent::sTeleportProgressMessages; + +class LLAgentFriendObserver : public LLFriendObserver +{ +public: + LLAgentFriendObserver() {} + virtual ~LLAgentFriendObserver() {} + virtual void changed(U32 mask); +}; + +void LLAgentFriendObserver::changed(U32 mask) +{ + // if there's a change we're interested in. + if((mask & (LLFriendObserver::POWERS)) != 0) + { + gAgent.friendsChanged(); + } +} + +bool handleSlowMotionAnimation(const LLSD& newvalue) +{ + if (newvalue.asBoolean()) + { + gAgentAvatarp->setAnimTimeFactor(0.2f); + } + else + { + gAgentAvatarp->setAnimTimeFactor(1.0f); + } + return true; +} + +void LLAgent::setCanEditParcel() // called via mParcelChangedSignal +{ + bool can_edit = LLToolMgr::getInstance()->canEdit(); + gAgent.mCanEditParcel = can_edit; +} + +// static +bool LLAgent::isActionAllowed(const LLSD& sdname) +{ + bool retval = false; + + const std::string& param = sdname.asString(); + + if (param == "speak") + { + bool allow_agent_voice = false; + LLVoiceChannel* channel = LLVoiceChannel::getCurrentVoiceChannel(); + if (channel != NULL) + { + if (channel->getSessionName().empty() && channel->getSessionID().isNull()) + { + // default channel + allow_agent_voice = LLViewerParcelMgr::getInstance()->allowAgentVoice(); + } + else + { + allow_agent_voice = channel->isActive() && channel->callStarted(); + } + } + + if (gAgent.isVoiceConnected() && + allow_agent_voice && + !LLVoiceClient::getInstance()->inTuningMode()) + { + retval = true; + } + else + { + retval = false; + } + } + + return retval; +} + +// static +void LLAgent::pressMicrophone(const LLSD& name) +{ + LLFirstUse::speak(false); + + LLVoiceClient::getInstance()->inputUserControlState(true); +} + +// static +void LLAgent::releaseMicrophone(const LLSD& name) +{ + LLVoiceClient::getInstance()->inputUserControlState(false); +} + +// static +void LLAgent::toggleMicrophone(const LLSD& name) +{ + LLVoiceClient::getInstance()->toggleUserPTTState(); +} + +// static +bool LLAgent::isMicrophoneOn(const LLSD& sdname) +{ + return LLVoiceClient::getInstance()->getUserPTTState(); +} + +// ************************************************************ +// Enabled this definition to compile a 'hacked' viewer that +// locally believes the end user has godlike powers. +// #define HACKED_GODLIKE_VIEWER +// For a toggled version, see viewer.h for the +// TOGGLE_HACKED_GODLIKE_VIEWER define, instead. +// ************************************************************ + +// Constructors and Destructors + +// JC - Please try to make this order match the order in the header +// file. Otherwise it's hard to find variables that aren't initialized. +//----------------------------------------------------------------------------- +// LLAgent() +//----------------------------------------------------------------------------- +LLAgent::LLAgent() : + mGroupPowers(0), + mHideGroupTitle(false), + mGroupID(), + + mInitialized(false), + mListener(), + + mDoubleTapRunTimer(), + mDoubleTapRunMode(DOUBLETAP_NONE), + + mbAlwaysRun(false), + mbRunning(false), + mbTeleportKeepsLookAt(false), + + mAgentAccess(new LLAgentAccess(gSavedSettings)), + mGodLevelChangeSignal(), + mCanEditParcel(false), + mTeleportSourceSLURL(new LLSLURL), + mTeleportRequest(), + mTeleportFinishedSlot(), + mTeleportFailedSlot(), + mIsMaturityRatingChangingDuringTeleport(false), + mTPNeedsNeabyChatSeparator(false), + mMaturityRatingChange(0U), + mIsDoSendMaturityPreferenceToServer(false), + mMaturityPreferenceRequestId(0U), + mMaturityPreferenceResponseId(0U), + mMaturityPreferenceNumRetries(0U), + mLastKnownRequestMaturity(SIM_ACCESS_MIN), + mLastKnownResponseMaturity(SIM_ACCESS_MIN), + mHttpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID), + mTeleportState(TELEPORT_NONE), + mRegionp(NULL), + mInterestListMode(IL_MODE_DEFAULT), + + mAgentOriginGlobal(), + mPositionGlobal(), + mLastTestGlobal(), + + mDistanceTraveled(0.F), + mLastPositionGlobal(LLVector3d::zero), + + mRenderState(0), + mTypingTimer(), + + mViewsPushed(false), + + mCustomAnim(false), + mShowAvatar(true), + mFrameAgent(), + + mIsDoNotDisturb(false), + + mControlFlags(0x00000000), + mbFlagsDirty(false), + mbFlagsNeedReset(false), + + mAutoPilot(false), + mAutoPilotFlyOnStop(false), + mAutoPilotAllowFlying(true), + mAutoPilotTargetGlobal(), + mAutoPilotStopDistance(1.f), + mAutoPilotUseRotation(false), + mAutoPilotTargetFacing(LLVector3::zero), + mAutoPilotTargetDist(0.f), + mAutoPilotNoProgressFrameCount(0), + mAutoPilotRotationThreshold(0.f), + mAutoPilotFinishedCallback(NULL), + mAutoPilotCallbackData(NULL), + + mMovementKeysLocked(false), + + mEffectColor(new LLUIColor(LLColor4(0.f, 1.f, 1.f, 1.f))), + + mHaveHomePosition(false), + mHomeRegionHandle( 0 ), + mNearChatRadius(CHAT_NORMAL_RADIUS / 2.f), + + mNextFidgetTime(0.f), + mCurrentFidget(0), + mFirstLogin(false), + mOutfitChosen(false), + + mVoiceConnected(false), + + mMouselookModeInSignal(NULL), + mMouselookModeOutSignal(NULL) +{ + for (U32 i = 0; i < TOTAL_CONTROLS; i++) + { + mControlsTakenCount[i] = 0; + mControlsTakenPassedOnCount[i] = 0; + } + + mListener.reset(new LLAgentListener(*this)); + + addParcelChangedCallback(&setCanEditParcel); + + mMoveTimer.stop(); +} + +// Requires gSavedSettings to be initialized. +//----------------------------------------------------------------------------- +// init() +//----------------------------------------------------------------------------- +void LLAgent::init() +{ + mMoveTimer.start(); + + gSavedSettings.declareBOOL("SlowMotionAnimation", false, "Declared in code", LLControlVariable::PERSIST_NO); + gSavedSettings.getControl("SlowMotionAnimation")->getSignal()->connect(boost::bind(&handleSlowMotionAnimation, _2)); + + // *Note: this is where LLViewerCamera::getInstance() used to be constructed. + + bool is_flying = gSavedSettings.getBOOL("FlyingAtExit"); + if(is_flying) + { + setFlying(is_flying); + } + + *mEffectColor = LLUIColorTable::instance().getColor("EffectColor"); + + gSavedSettings.getControl("PreferredMaturity")->getValidateSignal()->connect(boost::bind(&LLAgent::validateMaturity, this, _2)); + gSavedSettings.getControl("PreferredMaturity")->getSignal()->connect(boost::bind(&LLAgent::handleMaturity, this, _2)); + mLastKnownResponseMaturity = static_cast(gSavedSettings.getU32("PreferredMaturity")); + mLastKnownRequestMaturity = mLastKnownResponseMaturity; + mIsDoSendMaturityPreferenceToServer = true; + + if (!mTeleportFinishedSlot.connected()) + { + mTeleportFinishedSlot = LLViewerParcelMgr::getInstance()->setTeleportFinishedCallback(boost::bind(&LLAgent::handleTeleportFinished, this)); + } + if (!mTeleportFailedSlot.connected()) + { + mTeleportFailedSlot = LLViewerParcelMgr::getInstance()->setTeleportFailedCallback(boost::bind(&LLAgent::handleTeleportFailed, this)); + } + + LLAppCoreHttp & app_core_http(LLAppViewer::instance()->getAppCoreHttp()); + + mHttpPolicy = app_core_http.getPolicy(LLAppCoreHttp::AP_AGENT); + + mInitialized = true; +} + +//----------------------------------------------------------------------------- +// cleanup() +//----------------------------------------------------------------------------- +void LLAgent::cleanup() +{ + mRegionp = NULL; + mTeleportRequest = NULL; + mTeleportCanceled = NULL; + if (mTeleportFinishedSlot.connected()) + { + mTeleportFinishedSlot.disconnect(); + } + if (mTeleportFailedSlot.connected()) + { + mTeleportFailedSlot.disconnect(); + } +} + +//----------------------------------------------------------------------------- +// LLAgent() +//----------------------------------------------------------------------------- +LLAgent::~LLAgent() +{ + cleanup(); + + delete mMouselookModeInSignal; + mMouselookModeInSignal = NULL; + delete mMouselookModeOutSignal; + mMouselookModeOutSignal = NULL; + + delete mAgentAccess; + mAgentAccess = NULL; + delete mEffectColor; + mEffectColor = NULL; + delete mTeleportSourceSLURL; + mTeleportSourceSLURL = NULL; +} + +// Handle any actions that need to be performed when the main app gains focus +// (such as through alt-tab). +//----------------------------------------------------------------------------- +// onAppFocusGained() +//----------------------------------------------------------------------------- +void LLAgent::onAppFocusGained() +{ + if (CAMERA_MODE_MOUSELOOK == gAgentCamera.getCameraMode()) + { + gAgentCamera.changeCameraToDefault(); + LLToolMgr::getInstance()->clearSavedTool(); + } +} + +void LLAgent::setFirstLogin(bool b) +{ + mFirstLogin = b; + + if (mFirstLogin) + { + // Don't notify new users about new features + if (getFeatureVersion() <= UI_FEATURE_VERSION) + { + setFeatureVersion(UI_FEATURE_VERSION, UI_FEATURE_FLAGS); + } + } +} + +void LLAgent::setFeatureVersion(S32 version, S32 flags) +{ + LLSD updated_version; + updated_version["version"] = version; + updated_version["flags"] = flags; + gSavedSettings.setLLSD("LastUIFeatureVersion", updated_version); +} + +S32 LLAgent::getFeatureVersion() +{ + S32 version; + S32 flags; + getFeatureVersionAndFlags(version, flags); + return version; +} + +void LLAgent::getFeatureVersionAndFlags(S32& version, S32& flags) +{ + version = 0; + flags = 0; + LLSD feature_version = gSavedSettings.getLLSD("LastUIFeatureVersion"); + if (feature_version.isInteger()) + { + version = feature_version.asInteger(); + flags = 1; // inventory flag + } + else if (feature_version.isMap()) + { + version = feature_version["version"]; + flags = feature_version["flags"]; + } + else if (!feature_version.isString() && !feature_version.isUndefined()) + { + // is something newer inside? + version = UI_FEATURE_VERSION; + flags = UI_FEATURE_FLAGS; + } +} + +void LLAgent::showLatestFeatureNotification(const std::string key) +{ + S32 version; + S32 flags; // a single release can have multiple new features + getFeatureVersionAndFlags(version, flags); + if (version <= UI_FEATURE_VERSION && (flags & UI_FEATURE_FLAGS) != UI_FEATURE_FLAGS) + { + S32 flag = 0; + + if (key == "inventory") + { + // Notify user about new thumbnail support + flag = 1; + } + + if (key == "gltf") + { + flag = 2; + } + + if ((flags & flag) == 0) + { + // Need to open on top even if called from onOpen, + // do on idle to make sure it's on top + LLSD floater_key(key); + doOnIdleOneTime([floater_key]() + { + LLFloaterReg::showInstance("new_feature_notification", floater_key); + }); + + setFeatureVersion(UI_FEATURE_VERSION, flags | flag); + } + } +} + +void LLAgent::ageChat() +{ + if (isAgentAvatarValid()) + { + // get amount of time since I last chatted + F64 elapsed_time = (F64)gAgentAvatarp->mChatTimer.getElapsedTimeF32(); + // add in frame time * 3 (so it ages 4x) + gAgentAvatarp->mChatTimer.setAge(elapsed_time + (F64)gFrameDTClamped * (CHAT_AGE_FAST_RATE - 1.0)); + } +} + +//----------------------------------------------------------------------------- +// moveAt() +//----------------------------------------------------------------------------- +void LLAgent::moveAt(S32 direction, bool reset) +{ + LLUIUsage::instance().logCommand("Agent.MoveAt"); + + mMoveTimer.reset(); + LLFirstUse::notMoving(false); + + // age chat timer so it fades more quickly when you are intentionally moving + ageChat(); + + gAgentCamera.setAtKey(LLAgentCamera::directionToKey(direction)); + + if (direction > 0) + { + setControlFlags(AGENT_CONTROL_AT_POS | AGENT_CONTROL_FAST_AT); + } + else if (direction < 0) + { + setControlFlags(AGENT_CONTROL_AT_NEG | AGENT_CONTROL_FAST_AT); + } + + if (reset) + { + gAgentCamera.resetView(); + } +} + +//----------------------------------------------------------------------------- +// moveAtNudge() +//----------------------------------------------------------------------------- +void LLAgent::moveAtNudge(S32 direction) +{ + mMoveTimer.reset(); + LLFirstUse::notMoving(false); + + // age chat timer so it fades more quickly when you are intentionally moving + ageChat(); + + gAgentCamera.setWalkKey(LLAgentCamera::directionToKey(direction)); + + if (direction > 0) + { + setControlFlags(AGENT_CONTROL_NUDGE_AT_POS); + } + else if (direction < 0) + { + setControlFlags(AGENT_CONTROL_NUDGE_AT_NEG); + } + + gAgentCamera.resetView(); +} + +//----------------------------------------------------------------------------- +// moveLeft() +//----------------------------------------------------------------------------- +void LLAgent::moveLeft(S32 direction) +{ + mMoveTimer.reset(); + LLFirstUse::notMoving(false); + + // age chat timer so it fades more quickly when you are intentionally moving + ageChat(); + + gAgentCamera.setLeftKey(LLAgentCamera::directionToKey(direction)); + + if (direction > 0) + { + setControlFlags(AGENT_CONTROL_LEFT_POS | AGENT_CONTROL_FAST_LEFT); + } + else if (direction < 0) + { + setControlFlags(AGENT_CONTROL_LEFT_NEG | AGENT_CONTROL_FAST_LEFT); + } + + gAgentCamera.resetView(); +} + +//----------------------------------------------------------------------------- +// moveLeftNudge() +//----------------------------------------------------------------------------- +void LLAgent::moveLeftNudge(S32 direction) +{ + mMoveTimer.reset(); + LLFirstUse::notMoving(false); + + // age chat timer so it fades more quickly when you are intentionally moving + ageChat(); + + gAgentCamera.setLeftKey(LLAgentCamera::directionToKey(direction)); + + if (direction > 0) + { + setControlFlags(AGENT_CONTROL_NUDGE_LEFT_POS); + } + else if (direction < 0) + { + setControlFlags(AGENT_CONTROL_NUDGE_LEFT_NEG); + } + + gAgentCamera.resetView(); +} + +//----------------------------------------------------------------------------- +// moveUp() +//----------------------------------------------------------------------------- +void LLAgent::moveUp(S32 direction) +{ + mMoveTimer.reset(); + LLFirstUse::notMoving(false); + + // age chat timer so it fades more quickly when you are intentionally moving + ageChat(); + + gAgentCamera.setUpKey(LLAgentCamera::directionToKey(direction)); + + if (direction > 0) + { + setControlFlags(AGENT_CONTROL_UP_POS | AGENT_CONTROL_FAST_UP); + } + else if (direction < 0) + { + setControlFlags(AGENT_CONTROL_UP_NEG | AGENT_CONTROL_FAST_UP); + } + + gAgentCamera.resetView(); +} + +//----------------------------------------------------------------------------- +// moveYaw() +//----------------------------------------------------------------------------- +void LLAgent::moveYaw(F32 mag, bool reset_view) +{ + gAgentCamera.setYawKey(mag); + + if (mag > 0) + { + setControlFlags(AGENT_CONTROL_YAW_POS); + } + else if (mag < 0) + { + setControlFlags(AGENT_CONTROL_YAW_NEG); + } + + U32 mask = AGENT_CONTROL_YAW_POS | AGENT_CONTROL_YAW_NEG; + if ((getControlFlags() & mask) == mask) + { + // Rotation into both directions should cancel out + // But keep sending controls to simulator, + // it's needed for script based controls + gAgentCamera.setYawKey(0); + } + + if (reset_view) + { + gAgentCamera.resetView(); + } +} + +//----------------------------------------------------------------------------- +// movePitch() +//----------------------------------------------------------------------------- +void LLAgent::movePitch(F32 mag) +{ + gAgentCamera.setPitchKey(mag); + + if (mag > 0) + { + setControlFlags(AGENT_CONTROL_PITCH_POS); + } + else if (mag < 0) + { + setControlFlags(AGENT_CONTROL_PITCH_NEG); + } +} + + +// Does this parcel allow you to fly? +bool LLAgent::canFly() +{ + if (isGodlike()) return true; + + LLViewerRegion* regionp = getRegion(); + if (regionp && regionp->getBlockFly()) return false; + + LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if (!parcel) return false; + + // Allow owners to fly on their own land. + if (LLViewerParcelMgr::isParcelOwnedByAgent(parcel, GP_LAND_ALLOW_FLY)) + { + return true; + } + + return parcel->getAllowFly(); +} + +bool LLAgent::getFlying() const +{ + return mControlFlags & AGENT_CONTROL_FLY; +} + +//----------------------------------------------------------------------------- +// setFlying() +//----------------------------------------------------------------------------- +void LLAgent::setFlying(bool fly, bool fail_sound) +{ + if (isAgentAvatarValid()) + { + // *HACK: Don't allow to start the flying mode if we got ANIM_AGENT_STANDUP signal + // because in this case we won't get a signal to start avatar flying animation and + // it will be walking with flying mode "ON" indication. However we allow to switch + // the flying mode off if we get ANIM_AGENT_STANDUP signal. See process_avatar_animation(). + // See EXT-2781. + if(fly && gAgentAvatarp->mSignaledAnimations.find(ANIM_AGENT_STANDUP) != gAgentAvatarp->mSignaledAnimations.end()) + { + return; + } + + // don't allow taking off while sitting + if (fly && gAgentAvatarp->isSitting()) + { + return; + } + } + + if (fly) + { + bool was_flying = getFlying(); + if (!canFly() && !was_flying) + { + // parcel doesn't let you start fly + // gods can always fly + // and it's OK if you're already flying + if (fail_sound) + { + make_ui_sound("UISndBadKeystroke"); + } + return; + } + if( !was_flying ) + { + add(LLStatViewer::FLY, 1); + } + setControlFlags(AGENT_CONTROL_FLY); + } + else + { + clearControlFlags(AGENT_CONTROL_FLY); + } + + + // Update Movement Controls according to Fly mode + LLFloaterMove::setFlyingMode(fly); + + mbFlagsDirty = true; +} + + +// UI based mechanism of setting fly state +//----------------------------------------------------------------------------- +// toggleFlying() +//----------------------------------------------------------------------------- +// static +void LLAgent::toggleFlying() +{ + if ( gAgent.mAutoPilot ) + { + LLToolPie::instance().stopClickToWalk(); + } + + bool fly = !gAgent.getFlying(); + + gAgent.mMoveTimer.reset(); + LLFirstUse::notMoving(false); + + gAgent.setFlying( fly ); + gAgentCamera.resetView(); +} + +// static +bool LLAgent::enableFlying() +{ + bool sitting = false; + if (isAgentAvatarValid()) + { + sitting = gAgentAvatarp->isSitting(); + } + return !sitting; +} + +// static +bool LLAgent::isSitting() +{ + bool sitting = false; + if (isAgentAvatarValid()) + { + sitting = gAgentAvatarp->isSitting(); + } + return sitting; +} + +void LLAgent::standUp() +{ + setControlFlags(AGENT_CONTROL_STAND_UP); +} + +void LLAgent::changeParcels() +{ + LL_DEBUGS("AgentLocation") << "Calling ParcelChanged callbacks" << LL_ENDL; + // Notify anything that wants to know about parcel changes + mParcelChangedSignal(); +} + +boost::signals2::connection LLAgent::addParcelChangedCallback(parcel_changed_callback_t cb) +{ + return mParcelChangedSignal.connect(cb); +} + +// static +void LLAgent::capabilityReceivedCallback(const LLUUID ®ion_id, LLViewerRegion *regionp) +{ // Changed regions and now have the region capabilities + if (regionp) + { + if (regionp->getRegionID() == region_id) + { + regionp->requestSimulatorFeatures(); + LLAppViewer::instance()->updateNameLookupUrl(regionp); + } + + if (gAgent.getInterestListMode() == IL_MODE_360) + { + gAgent.changeInterestListMode(IL_MODE_360); + } + } +} + + +//----------------------------------------------------------------------------- +// setRegion() +//----------------------------------------------------------------------------- +void LLAgent::setRegion(LLViewerRegion *regionp) +{ + llassert(regionp); + if (mRegionp != regionp) + { + + LL_INFOS("AgentLocation","Teleport") << "Moving agent into region: handle " << regionp->getHandle() + << " id " << regionp->getRegionID() + << " name " << regionp->getName() + << " previous region " + << (mRegionp ? mRegionp->getRegionID() : LLUUID::null) + << LL_ENDL; + if (mRegionp) + { + // We've changed regions, we're now going to change our agent coordinate frame. + mAgentOriginGlobal = regionp->getOriginGlobal(); + LLVector3d agent_offset_global = mRegionp->getOriginGlobal(); + + LLVector3 delta; + delta.setVec(regionp->getOriginGlobal() - mRegionp->getOriginGlobal()); + + setPositionAgent(getPositionAgent() - delta); + + LLVector3 camera_position_agent = LLViewerCamera::getInstance()->getOrigin(); + LLViewerCamera::getInstance()->setOrigin(camera_position_agent - delta); + + // Update all of the regions. + LLWorld::getInstance()->updateAgentOffset(agent_offset_global); + + // Hack to keep sky in the agent's region, otherwise it may get deleted - DJS 08/02/02 + // *TODO: possibly refactor into gSky->setAgentRegion(regionp)? -Brad + if (gSky.mVOSkyp) + { + gSky.mVOSkyp->setRegion(regionp); + } + + if (regionp->capabilitiesReceived()) + { + regionp->requestSimulatorFeatures(); + LLAppViewer::instance()->updateNameLookupUrl(regionp); + } + else + { + regionp->setCapabilitiesReceivedCallback(LLAgent::capabilityReceivedCallback); + } + + } + else + { + // First time initialization. + // We've changed regions, we're now going to change our agent coordinate frame. + mAgentOriginGlobal = regionp->getOriginGlobal(); + + LLVector3 delta; + delta.setVec(regionp->getOriginGlobal()); + + setPositionAgent(getPositionAgent() - delta); + LLVector3 camera_position_agent = LLViewerCamera::getInstance()->getOrigin(); + LLViewerCamera::getInstance()->setOrigin(camera_position_agent - delta); + + // Update all of the regions. + LLWorld::getInstance()->updateAgentOffset(mAgentOriginGlobal); + + if (regionp->capabilitiesReceived()) + { + LLAppViewer::instance()->updateNameLookupUrl(regionp); + } + else + { + regionp->setCapabilitiesReceivedCallback([](const LLUUID ®ion_id, LLViewerRegion* regionp) {LLAppViewer::instance()->updateNameLookupUrl(regionp); }); + } + } + + // Pass new region along to metrics components that care about this level of detail. + LLAppViewer::metricsUpdateRegion(regionp->getHandle()); + } + + mRegionp = regionp; + + // TODO - most of what follows probably should be moved into callbacks + + // Pass the region host to LLUrlEntryParcel to resolve parcel name + // with a server request. + LLUrlEntryParcel::setRegionHost(getRegionHost()); + + // Must shift hole-covering water object locations because local + // coordinate frame changed. + LLWorld::getInstance()->updateWaterObjects(); + + // keep a list of regions we've been too + // this is just an interesting stat, logged at the dataserver + // we could trake this at the dataserver side, but that's harder + U64 handle = regionp->getHandle(); + mRegionsVisited.insert(handle); + + LLSelectMgr::getInstance()->updateSelectionCenter(); + + LLFloaterMove::sUpdateFlyingStatus(); + + LL_DEBUGS("AgentLocation") << "Calling RegionChanged callbacks" << LL_ENDL; + mRegionChangedSignal(); +} + + +//----------------------------------------------------------------------------- +// getRegion() +//----------------------------------------------------------------------------- +LLViewerRegion *LLAgent::getRegion() const +{ + return mRegionp; +} + + +LLHost LLAgent::getRegionHost() const +{ + if (mRegionp) + { + return mRegionp->getHost(); + } + else + { + return LLHost(); + } +} + +boost::signals2::connection LLAgent::addRegionChangedCallback(const region_changed_signal_t::slot_type& cb) +{ + return mRegionChangedSignal.connect(cb); +} + +void LLAgent::removeRegionChangedCallback(boost::signals2::connection callback) +{ + mRegionChangedSignal.disconnect(callback); +} + +//----------------------------------------------------------------------------- +// inPrelude() +//----------------------------------------------------------------------------- +bool LLAgent::inPrelude() +{ + return mRegionp && mRegionp->isPrelude(); +} + + +std::string LLAgent::getRegionCapability(const std::string &name) +{ + if (!mRegionp) + return std::string(); + + return mRegionp->getCapability(name); +} + + +//----------------------------------------------------------------------------- +// canManageEstate() +//----------------------------------------------------------------------------- + +bool LLAgent::canManageEstate() const +{ + return mRegionp && mRegionp->canManageEstate(); +} + +//----------------------------------------------------------------------------- +// sendMessage() +//----------------------------------------------------------------------------- +void LLAgent::sendMessage() +{ + if (gDisconnected) + { + LL_WARNS() << "Trying to send message when disconnected!" << LL_ENDL; + return; + } + if (!mRegionp) + { + LL_ERRS() << "No region for agent yet!" << LL_ENDL; + return; + } + gMessageSystem->sendMessage(mRegionp->getHost()); +} + + +//----------------------------------------------------------------------------- +// sendReliableMessage() +//----------------------------------------------------------------------------- +void LLAgent::sendReliableMessage() +{ + if (gDisconnected) + { + LL_DEBUGS() << "Trying to send message when disconnected!" << LL_ENDL; + return; + } + if (!mRegionp) + { + LL_DEBUGS() << "LLAgent::sendReliableMessage No region for agent yet, not sending message!" << LL_ENDL; + return; + } + gMessageSystem->sendReliable(mRegionp->getHost()); +} + +//----------------------------------------------------------------------------- +// getVelocity() +//----------------------------------------------------------------------------- +LLVector3 LLAgent::getVelocity() const +{ + if (isAgentAvatarValid()) + { + return gAgentAvatarp->getVelocity(); + } + else + { + return LLVector3::zero; + } +} + + +//----------------------------------------------------------------------------- +// setPositionAgent() +//----------------------------------------------------------------------------- +void LLAgent::setPositionAgent(const LLVector3 &pos_agent) +{ + if (!pos_agent.isFinite()) + { + LL_ERRS() << "setPositionAgent is not a number" << LL_ENDL; + } + + if (isAgentAvatarValid() && gAgentAvatarp->getParent()) + { + LLVector3 pos_agent_sitting; + LLVector3d pos_agent_d; + LLViewerObject *parent = (LLViewerObject*)gAgentAvatarp->getParent(); + + pos_agent_sitting = gAgentAvatarp->getPosition() * parent->getRotation() + parent->getPositionAgent(); + pos_agent_d.setVec(pos_agent_sitting); + + mFrameAgent.setOrigin(pos_agent_sitting); + mPositionGlobal = pos_agent_d + mAgentOriginGlobal; + } + else + { + mFrameAgent.setOrigin(pos_agent); + + LLVector3d pos_agent_d; + pos_agent_d.setVec(pos_agent); + mPositionGlobal = pos_agent_d + mAgentOriginGlobal; + } + + if (((mLastTestGlobal - mPositionGlobal).lengthSquared() > 1.0) && !mOnPositionChanged.empty()) + { // If the position has changed my more than 1 meter since the last time we triggered. + // filters out some noise. + mLastTestGlobal = mPositionGlobal; + mOnPositionChanged(mFrameAgent.getOrigin(), mPositionGlobal); + } +} + +//----------------------------------------------------------------------------- +// getPositionGlobal() +//----------------------------------------------------------------------------- +const LLVector3d &LLAgent::getPositionGlobal() const +{ + if (isAgentAvatarValid() && !gAgentAvatarp->mDrawable.isNull()) + { + mPositionGlobal = getPosGlobalFromAgent(gAgentAvatarp->getRenderPosition()); + } + else + { + mPositionGlobal = getPosGlobalFromAgent(mFrameAgent.getOrigin()); + } + + return mPositionGlobal; +} + +//----------------------------------------------------------------------------- +// getPositionAgent() +//----------------------------------------------------------------------------- +const LLVector3 &LLAgent::getPositionAgent() +{ + if (isAgentAvatarValid()) + { + if(gAgentAvatarp->mDrawable.isNull()) + { + mFrameAgent.setOrigin(gAgentAvatarp->getPositionAgent()); + } + else + { + mFrameAgent.setOrigin(gAgentAvatarp->getRenderPosition()); + } + } + + + return mFrameAgent.getOrigin(); +} + +boost::signals2::connection LLAgent::whenPositionChanged(position_signal_t::slot_type fn) +{ + return mOnPositionChanged.connect(fn); +} + + +//----------------------------------------------------------------------------- +// getRegionsVisited() +//----------------------------------------------------------------------------- +S32 LLAgent::getRegionsVisited() const +{ + return mRegionsVisited.size(); +} + +//----------------------------------------------------------------------------- +// getDistanceTraveled() +//----------------------------------------------------------------------------- +F64 LLAgent::getDistanceTraveled() const +{ + return mDistanceTraveled; +} + + +//----------------------------------------------------------------------------- +// getPosAgentFromGlobal() +//----------------------------------------------------------------------------- +LLVector3 LLAgent::getPosAgentFromGlobal(const LLVector3d &pos_global) const +{ + LLVector3 pos_agent; + pos_agent.setVec(pos_global - mAgentOriginGlobal); + return pos_agent; +} + + +//----------------------------------------------------------------------------- +// getPosGlobalFromAgent() +//----------------------------------------------------------------------------- +LLVector3d LLAgent::getPosGlobalFromAgent(const LLVector3 &pos_agent) const +{ + LLVector3d pos_agent_d; + pos_agent_d.setVec(pos_agent); + return pos_agent_d + mAgentOriginGlobal; +} + +void LLAgent::sitDown() +{ + setControlFlags(AGENT_CONTROL_SIT_ON_GROUND); +} + + +//----------------------------------------------------------------------------- +// resetAxes() +//----------------------------------------------------------------------------- +void LLAgent::resetAxes() +{ + mFrameAgent.resetAxes(); +} + + +// Copied from LLCamera::setOriginAndLookAt +// Look_at must be unit vector +//----------------------------------------------------------------------------- +// resetAxes() +//----------------------------------------------------------------------------- +void LLAgent::resetAxes(const LLVector3 &look_at) +{ + LLVector3 skyward = getReferenceUpVector(); + + // if look_at has zero length, fail + // if look_at and skyward are parallel, fail + // + // Test both of these conditions with a cross product. + LLVector3 cross(look_at % skyward); + if (cross.isNull()) + { + LL_INFOS() << "LLAgent::resetAxes cross-product is zero" << LL_ENDL; + return; + } + + // Make sure look_at and skyward are not parallel + // and neither are zero length + LLVector3 left(skyward % look_at); + LLVector3 up(look_at % left); + + mFrameAgent.setAxes(look_at, left, up); +} + + +//----------------------------------------------------------------------------- +// rotate() +//----------------------------------------------------------------------------- +void LLAgent::rotate(F32 angle, const LLVector3 &axis) +{ + mFrameAgent.rotate(angle, axis); +} + + +//----------------------------------------------------------------------------- +// rotate() +//----------------------------------------------------------------------------- +void LLAgent::rotate(F32 angle, F32 x, F32 y, F32 z) +{ + mFrameAgent.rotate(angle, x, y, z); +} + + +//----------------------------------------------------------------------------- +// rotate() +//----------------------------------------------------------------------------- +void LLAgent::rotate(const LLMatrix3 &matrix) +{ + mFrameAgent.rotate(matrix); +} + + +//----------------------------------------------------------------------------- +// rotate() +//----------------------------------------------------------------------------- +void LLAgent::rotate(const LLQuaternion &quaternion) +{ + mFrameAgent.rotate(quaternion); +} + + +//----------------------------------------------------------------------------- +// getReferenceUpVector() +//----------------------------------------------------------------------------- +LLVector3 LLAgent::getReferenceUpVector() +{ + // this vector is in the coordinate frame of the avatar's parent object, or the world if none + LLVector3 up_vector = LLVector3::z_axis; + if (isAgentAvatarValid() && + gAgentAvatarp->getParent() && + gAgentAvatarp->mDrawable.notNull()) + { + U32 camera_mode = gAgentCamera.getCameraAnimating() ? gAgentCamera.getLastCameraMode() : gAgentCamera.getCameraMode(); + // and in third person... + if (camera_mode == CAMERA_MODE_THIRD_PERSON) + { + // make the up vector point to the absolute +z axis + up_vector = up_vector * ~((LLViewerObject*)gAgentAvatarp->getParent())->getRenderRotation(); + } + else if (camera_mode == CAMERA_MODE_MOUSELOOK) + { + // make the up vector point to the avatar's +z axis + up_vector = up_vector * gAgentAvatarp->mDrawable->getRotation(); + } + } + + return up_vector; +} + + +// Radians, positive is forward into ground +//----------------------------------------------------------------------------- +// pitch() +//----------------------------------------------------------------------------- +void LLAgent::pitch(F32 angle) +{ + // don't let user pitch if pointed almost all the way down or up + + // A dot B = mag(A) * mag(B) * cos(angle between A and B) + // so... cos(angle between A and B) = A dot B / mag(A) / mag(B) + // = A dot B for unit vectors + + LLVector3 skyward = getReferenceUpVector(); + + // clamp pitch to limits + if (angle >= 0.f) + { + const F32 look_down_limit = 179.f * DEG_TO_RAD; + F32 angle_from_skyward = acos(mFrameAgent.getAtAxis() * skyward); + if (angle_from_skyward + angle > look_down_limit) + { + angle = look_down_limit - angle_from_skyward; + } + } + else if (angle < 0.f) + { + const F32 look_up_limit = 5.f * DEG_TO_RAD; + const LLVector3& viewer_camera_pos = LLViewerCamera::getInstance()->getOrigin(); + LLVector3 agent_focus_pos = getPosAgentFromGlobal(gAgentCamera.calcFocusPositionTargetGlobal()); + LLVector3 look_dir = agent_focus_pos - viewer_camera_pos; + F32 angle_from_skyward = angle_between(look_dir, skyward); + if (angle_from_skyward + angle < look_up_limit) + { + angle = look_up_limit - angle_from_skyward; + } + } + + if (fabs(angle) > 1e-4) + { + mFrameAgent.pitch(angle); + } +} + + +//----------------------------------------------------------------------------- +// roll() +//----------------------------------------------------------------------------- +void LLAgent::roll(F32 angle) +{ + mFrameAgent.roll(angle); +} + + +//----------------------------------------------------------------------------- +// yaw() +//----------------------------------------------------------------------------- +void LLAgent::yaw(F32 angle) +{ + if (!rotateGrabbed()) + { + mFrameAgent.rotate(angle, getReferenceUpVector()); + } +} + + +// Returns a quat that represents the rotation of the agent in the absolute frame +//----------------------------------------------------------------------------- +// getQuat() +//----------------------------------------------------------------------------- +LLQuaternion LLAgent::getQuat() const +{ + return mFrameAgent.getQuaternion(); +} + +//----------------------------------------------------------------------------- +// getControlFlags() +//----------------------------------------------------------------------------- +U32 LLAgent::getControlFlags() +{ + return mControlFlags; +} + +//----------------------------------------------------------------------------- +// setControlFlags() +//----------------------------------------------------------------------------- +void LLAgent::setControlFlags(U32 mask) +{ + mControlFlags |= mask; + mbFlagsDirty = true; +} + + +//----------------------------------------------------------------------------- +// clearControlFlags() +//----------------------------------------------------------------------------- +void LLAgent::clearControlFlags(U32 mask) +{ + U32 old_flags = mControlFlags; + mControlFlags &= ~mask; + if (old_flags != mControlFlags) + { + mbFlagsDirty = true; + } +} + +//----------------------------------------------------------------------------- +// controlFlagsDirty() +//----------------------------------------------------------------------------- +bool LLAgent::controlFlagsDirty() const +{ + return mbFlagsDirty; +} + +//----------------------------------------------------------------------------- +// enableControlFlagReset() +//----------------------------------------------------------------------------- +void LLAgent::enableControlFlagReset() +{ + mbFlagsNeedReset = true; +} + +//----------------------------------------------------------------------------- +// resetControlFlags() +//----------------------------------------------------------------------------- +void LLAgent::resetControlFlags() +{ + if (mbFlagsNeedReset) + { + mbFlagsNeedReset = false; + mbFlagsDirty = false; + // reset all of the ephemeral flags + // some flags are managed elsewhere + mControlFlags &= AGENT_CONTROL_AWAY | AGENT_CONTROL_FLY | AGENT_CONTROL_MOUSELOOK; + } +} + +//----------------------------------------------------------------------------- +// setAFK() +//----------------------------------------------------------------------------- +void LLAgent::setAFK() +{ + if (gNonInteractive || !gAgent.getRegion()) + { + // Don't set AFK if we're not talking to a region yet. + return; + } + + if (!(mControlFlags & AGENT_CONTROL_AWAY)) + { + sendAnimationRequest(ANIM_AGENT_AWAY, ANIM_REQUEST_START); + setControlFlags(AGENT_CONTROL_AWAY | AGENT_CONTROL_STOP); + gAwayTimer.start(); + } +} + +//----------------------------------------------------------------------------- +// clearAFK() +//----------------------------------------------------------------------------- +void LLAgent::clearAFK() +{ + gAwayTriggerTimer.reset(); + + // Gods can sometimes get into away state (via gestures) + // without setting the appropriate control flag. JC + if (mControlFlags & AGENT_CONTROL_AWAY + || (isAgentAvatarValid() + && (gAgentAvatarp->mSignaledAnimations.find(ANIM_AGENT_AWAY) != gAgentAvatarp->mSignaledAnimations.end()))) + { + sendAnimationRequest(ANIM_AGENT_AWAY, ANIM_REQUEST_STOP); + clearControlFlags(AGENT_CONTROL_AWAY); + } +} + +//----------------------------------------------------------------------------- +// getAFK() +//----------------------------------------------------------------------------- +bool LLAgent::getAFK() const +{ + return (mControlFlags & AGENT_CONTROL_AWAY) != 0; +} + +//----------------------------------------------------------------------------- +// setDoNotDisturb() +//----------------------------------------------------------------------------- +void LLAgent::setDoNotDisturb(bool pIsDoNotDisturb) +{ + bool isDoNotDisturbSwitchedOff = (mIsDoNotDisturb && !pIsDoNotDisturb); + mIsDoNotDisturb = pIsDoNotDisturb; + sendAnimationRequest(ANIM_AGENT_DO_NOT_DISTURB, (pIsDoNotDisturb ? ANIM_REQUEST_START : ANIM_REQUEST_STOP)); + LLNotificationsUI::LLChannelManager::getInstance()->muteAllChannels(pIsDoNotDisturb); + if (isDoNotDisturbSwitchedOff) + { + LLDoNotDisturbNotificationStorage::getInstance()->updateNotifications(); + } + gIMMgr->updateDNDMessageStatus(); +} + +//----------------------------------------------------------------------------- +// isDoNotDisturb() +//----------------------------------------------------------------------------- +bool LLAgent::isDoNotDisturb() const +{ + return mIsDoNotDisturb; +} + + +//----------------------------------------------------------------------------- +// startAutoPilotGlobal() +//----------------------------------------------------------------------------- +void LLAgent::startAutoPilotGlobal( + const LLVector3d &target_global, + const std::string& behavior_name, + const LLQuaternion *target_rotation, + void (*finish_callback)(bool, void *), + void *callback_data, + F32 stop_distance, + F32 rot_threshold, + bool allow_flying) +{ + if (!isAgentAvatarValid()) + { + return; + } + + if (target_global.isExactlyZero()) + { + LL_WARNS() << "Canceling attempt to start autopilot towards invalid position" << LL_ENDL; + return; + } + + // Are there any pending callbacks from previous auto pilot requests? + if (mAutoPilotFinishedCallback) + { + mAutoPilotFinishedCallback(dist_vec(gAgent.getPositionGlobal(), mAutoPilotTargetGlobal) < mAutoPilotStopDistance, mAutoPilotCallbackData); + } + + mAutoPilotFinishedCallback = finish_callback; + mAutoPilotCallbackData = callback_data; + mAutoPilotRotationThreshold = rot_threshold; + mAutoPilotBehaviorName = behavior_name; + mAutoPilotAllowFlying = allow_flying; + + LLVector3d delta_pos( target_global ); + delta_pos -= getPositionGlobal(); + F64 distance = delta_pos.magVec(); + LLVector3d trace_target = target_global; + + trace_target.mdV[VZ] -= 10.f; + + LLVector3d intersection; + LLVector3 normal; + LLViewerObject *hit_obj; + F32 heightDelta = LLWorld::getInstance()->resolveStepHeightGlobal(NULL, target_global, trace_target, intersection, normal, &hit_obj); + + if (stop_distance > 0.f) + { + mAutoPilotStopDistance = stop_distance; + } + else + { + // Guess at a reasonable stop distance. + mAutoPilotStopDistance = (F32) sqrt( distance ); + if (mAutoPilotStopDistance < 0.5f) + { + mAutoPilotStopDistance = 0.5f; + } + } + + if (mAutoPilotAllowFlying) + { + mAutoPilotFlyOnStop = getFlying(); + } + else + { + mAutoPilotFlyOnStop = false; + } + + if (distance > 30.0 && mAutoPilotAllowFlying) + { + setFlying(true); + } + + if ( distance > 1.f && + mAutoPilotAllowFlying && + heightDelta > (sqrtf(mAutoPilotStopDistance) + 1.f)) + { + setFlying(true); + // Do not force flying for "Sit" behavior to prevent flying after pressing "Stand" + // from an object. See EXT-1655. + if ("Sit" != mAutoPilotBehaviorName) + mAutoPilotFlyOnStop = true; + } + + mAutoPilot = true; + setAutoPilotTargetGlobal(target_global); + + if (target_rotation) + { + mAutoPilotUseRotation = true; + mAutoPilotTargetFacing = LLVector3::x_axis * *target_rotation; + mAutoPilotTargetFacing.mV[VZ] = 0.f; + mAutoPilotTargetFacing.normalize(); + } + else + { + mAutoPilotUseRotation = false; + } + + mAutoPilotNoProgressFrameCount = 0; +} + + +//----------------------------------------------------------------------------- +// setAutoPilotTargetGlobal +//----------------------------------------------------------------------------- +void LLAgent::setAutoPilotTargetGlobal(const LLVector3d &target_global) +{ + if (mAutoPilot) + { + mAutoPilotTargetGlobal = target_global; + + // trace ray down to find height of destination from ground + LLVector3d traceEndPt = target_global; + traceEndPt.mdV[VZ] -= 20.f; + + LLVector3d targetOnGround; + LLVector3 groundNorm; + LLViewerObject *obj; + + LLWorld::getInstance()->resolveStepHeightGlobal(NULL, target_global, traceEndPt, targetOnGround, groundNorm, &obj); + // Note: this might malfunction for sitting agent, since pelvis stays same, but agent's position becomes lower + // But for autopilot to work we assume that agent is standing and ready to go. + F64 target_height = llmax((F64)gAgentAvatarp->getPelvisToFoot(), target_global.mdV[VZ] - targetOnGround.mdV[VZ]); + + // clamp z value of target to minimum height above ground + mAutoPilotTargetGlobal.mdV[VZ] = targetOnGround.mdV[VZ] + target_height; + mAutoPilotTargetDist = (F32)dist_vec(gAgent.getPositionGlobal(), mAutoPilotTargetGlobal); + } +} + +//----------------------------------------------------------------------------- +// startFollowPilot() +//----------------------------------------------------------------------------- +void LLAgent::startFollowPilot(const LLUUID &leader_id, bool allow_flying, F32 stop_distance) +{ + mLeaderID = leader_id; + if ( mLeaderID.isNull() ) return; + + LLViewerObject* object = gObjectList.findObject(mLeaderID); + if (!object) + { + mLeaderID = LLUUID::null; + return; + } + + startAutoPilotGlobal(object->getPositionGlobal(), + std::string(), // behavior_name + NULL, // target_rotation + NULL, // finish_callback + NULL, // callback_data + stop_distance, + 0.03f, // rotation_threshold + allow_flying); +} + + +//----------------------------------------------------------------------------- +// stopAutoPilot() +//----------------------------------------------------------------------------- +void LLAgent::stopAutoPilot(bool user_cancel) +{ + if (mAutoPilot) + { + mAutoPilot = false; + if (mAutoPilotUseRotation && !user_cancel) + { + resetAxes(mAutoPilotTargetFacing); + } + // Restore previous flying state before invoking mAutoPilotFinishedCallback to allow + // callback function to change the flying state (like in near_sit_down_point()). + // If the user cancelled, don't change the fly state + if (!user_cancel) + { + setFlying(mAutoPilotFlyOnStop); + } + //NB: auto pilot can terminate for a reason other than reaching the destination + if (mAutoPilotFinishedCallback) + { + mAutoPilotFinishedCallback(!user_cancel && dist_vec(gAgent.getPositionGlobal(), mAutoPilotTargetGlobal) < mAutoPilotStopDistance, mAutoPilotCallbackData); + mAutoPilotFinishedCallback = NULL; + } + mLeaderID = LLUUID::null; + + setControlFlags(AGENT_CONTROL_STOP); + + if (user_cancel && !mAutoPilotBehaviorName.empty()) + { + if (mAutoPilotBehaviorName == "Sit") + LL_INFOS("Agent") << "Autopilot-Sit was canceled by user action" << LL_ENDL; + else if (mAutoPilotBehaviorName == "Attach") + LLNotificationsUtil::add("CancelledAttach"); + else + LLNotificationsUtil::add("Cancelled"); + } + } +} + + +// Returns necessary agent pitch and yaw changes, radians. +//----------------------------------------------------------------------------- +// autoPilot() +//----------------------------------------------------------------------------- +void LLAgent::autoPilot(F32 *delta_yaw) +{ + if (mAutoPilot) + { + if (!mLeaderID.isNull()) + { + LLViewerObject* object = gObjectList.findObject(mLeaderID); + if (!object) + { + stopAutoPilot(); + return; + } + mAutoPilotTargetGlobal = object->getPositionGlobal(); + } + + if (!isAgentAvatarValid()) return; + + if (gAgentAvatarp->mInAir && mAutoPilotAllowFlying) + { + setFlying(true); + } + + LLVector3 at; + at.setVec(mFrameAgent.getAtAxis()); + LLVector3 target_agent = getPosAgentFromGlobal(mAutoPilotTargetGlobal); + LLVector3 direction = target_agent - getPositionAgent(); + + F32 target_dist = direction.magVec(); + + if (target_dist >= mAutoPilotTargetDist) + { + mAutoPilotNoProgressFrameCount++; + bool out_of_time = false; + if (getFlying()) + { + out_of_time = mAutoPilotNoProgressFrameCount > AUTOPILOT_MAX_TIME_NO_PROGRESS_FLY * gFPSClamped; + } + else + { + out_of_time = mAutoPilotNoProgressFrameCount > AUTOPILOT_MAX_TIME_NO_PROGRESS_WALK * gFPSClamped; + } + if (out_of_time) + { + stopAutoPilot(); + return; + } + } + + mAutoPilotTargetDist = target_dist; + + // Make this a two-dimensional solution + at.mV[VZ] = 0.f; + direction.mV[VZ] = 0.f; + + at.normalize(); + F32 xy_distance = direction.normalize(); + + F32 yaw = 0.f; + if (mAutoPilotTargetDist > mAutoPilotStopDistance) + { + yaw = angle_between(mFrameAgent.getAtAxis(), direction); + } + else if (mAutoPilotUseRotation) + { + // we're close now just aim at target facing + yaw = angle_between(at, mAutoPilotTargetFacing); + direction = mAutoPilotTargetFacing; + } + + yaw = 4.f * yaw / gFPSClamped; + + // figure out which direction to turn + LLVector3 scratch(at % direction); + + if (scratch.mV[VZ] > 0.f) + { + setControlFlags(AGENT_CONTROL_YAW_POS); + } + else + { + yaw = -yaw; + setControlFlags(AGENT_CONTROL_YAW_NEG); + } + + *delta_yaw = yaw; + + // Compute when to start slowing down + F32 slow_distance; + if (getFlying()) + { + slow_distance = llmax(8.f, mAutoPilotStopDistance + 5.f); + } + else + { + slow_distance = llmax(3.f, mAutoPilotStopDistance + 2.f); + } + + // If we're flying, handle autopilot points above or below you. + if (getFlying() && xy_distance < AUTOPILOT_HEIGHT_ADJUST_DISTANCE) + { + if (isAgentAvatarValid()) + { + F64 current_height = gAgentAvatarp->getPositionGlobal().mdV[VZ]; + F32 delta_z = (F32)(mAutoPilotTargetGlobal.mdV[VZ] - current_height); + F32 slope = delta_z / xy_distance; + if (slope > 0.45f && delta_z > 6.f) + { + setControlFlags(AGENT_CONTROL_FAST_UP | AGENT_CONTROL_UP_POS); + } + else if (slope > 0.002f && delta_z > 0.5f) + { + setControlFlags(AGENT_CONTROL_UP_POS); + } + else if (slope < -0.45f && delta_z < -6.f && current_height > AUTOPILOT_MIN_TARGET_HEIGHT_OFF_GROUND) + { + setControlFlags(AGENT_CONTROL_FAST_UP | AGENT_CONTROL_UP_NEG); + } + else if (slope < -0.002f && delta_z < -0.5f && current_height > AUTOPILOT_MIN_TARGET_HEIGHT_OFF_GROUND) + { + setControlFlags(AGENT_CONTROL_UP_NEG); + } + } + } + + // calculate delta rotation to target heading + F32 delta_target_heading = angle_between(mFrameAgent.getAtAxis(), mAutoPilotTargetFacing); + + if (xy_distance > slow_distance && yaw < (F_PI / 10.f)) + { + // walking/flying fast + setControlFlags(AGENT_CONTROL_FAST_AT | AGENT_CONTROL_AT_POS); + } + else if (mAutoPilotTargetDist > mAutoPilotStopDistance) + { + // walking/flying slow + U32 movement_flag = 0; + + if (at * direction > 0.9f) + { + movement_flag = AGENT_CONTROL_AT_POS; + } + else if (at * direction < -0.9f) + { + movement_flag = AGENT_CONTROL_AT_NEG; + } + + if (getFlying()) + { + // flying is too fast and has high inertia, artificially slow it down + // Don't update flags too often, server might not react + static U64 last_time_microsec = 0; + U64 time_microsec = LLTimer::getTotalTime(); + U64 delta = time_microsec - last_time_microsec; + // fly during ~0-40 ms, stop during ~40-250 ms + if (delta > 250000) // 250ms + { + // reset even if !movement_flag + last_time_microsec = time_microsec; + } + else if (delta > 40000) // 40 ms + { + clearControlFlags(AGENT_CONTROL_AT_POS | AGENT_CONTROL_AT_POS); + movement_flag = 0; + } + } + + if (movement_flag) + { + setControlFlags(movement_flag); + } + } + + // check to see if we need to keep rotating to target orientation + if (mAutoPilotTargetDist < mAutoPilotStopDistance) + { + setControlFlags(AGENT_CONTROL_STOP); + if(!mAutoPilotUseRotation || (delta_target_heading < mAutoPilotRotationThreshold)) + { + stopAutoPilot(); + } + } + } +} + + +//----------------------------------------------------------------------------- +// propagate() +//----------------------------------------------------------------------------- +void LLAgent::propagate(const F32 dt) +{ + // Update UI based on agent motion + LLFloaterMove *floater_move = LLFloaterReg::findTypedInstance("moveview"); + if (floater_move) + { + floater_move->mForwardButton ->setToggleState( gAgentCamera.getAtKey() > 0 || gAgentCamera.getWalkKey() > 0 ); + floater_move->mBackwardButton ->setToggleState( gAgentCamera.getAtKey() < 0 || gAgentCamera.getWalkKey() < 0 ); + floater_move->mTurnLeftButton ->setToggleState( gAgentCamera.getYawKey() > 0.f ); + floater_move->mTurnRightButton ->setToggleState( gAgentCamera.getYawKey() < 0.f ); + floater_move->mSlideLeftButton ->setToggleState( gAgentCamera.getLeftKey() > 0.f ); + floater_move->mSlideRightButton ->setToggleState( gAgentCamera.getLeftKey() < 0.f ); + floater_move->mMoveUpButton ->setToggleState( gAgentCamera.getUpKey() > 0 ); + floater_move->mMoveDownButton ->setToggleState( gAgentCamera.getUpKey() < 0 ); + } + + // handle rotation based on keyboard levels + const F32 YAW_RATE = 90.f * DEG_TO_RAD; // radians per second + yaw(YAW_RATE * gAgentCamera.getYawKey() * dt); + + const F32 PITCH_RATE = 90.f * DEG_TO_RAD; // radians per second + pitch(PITCH_RATE * gAgentCamera.getPitchKey() * dt); + + // handle auto-land behavior + if (isAgentAvatarValid()) + { + bool in_air = gAgentAvatarp->mInAir; + LLVector3 land_vel = getVelocity(); + land_vel.mV[VZ] = 0.f; + + if (!in_air + && gAgentCamera.getUpKey() < 0 + && land_vel.magVecSquared() < MAX_VELOCITY_AUTO_LAND_SQUARED + && gSavedSettings.getBOOL("AutomaticFly")) + { + // land automatically + setFlying(false); + } + } + + gAgentCamera.clearGeneralKeys(); +} + +//----------------------------------------------------------------------------- +// updateAgentPosition() +//----------------------------------------------------------------------------- +void LLAgent::updateAgentPosition(const F32 dt, const F32 yaw_radians, const S32 mouse_x, const S32 mouse_y) +{ + static LLCachedControl hint_timeout(gSavedSettings, "NotMovingHintTimeout"); + if (mMoveTimer.getStarted() && mMoveTimer.getElapsedTimeF32() > hint_timeout) + { + LLFirstUse::notMoving(); + } + + propagate(dt); + + // static S32 cameraUpdateCount = 0; + + rotate(yaw_radians, 0, 0, 1); + + // + // Check for water and land collision, set underwater flag + // + + gAgentCamera.updateLookAt(mouse_x, mouse_y); + + // When agent has no parents, position updates come from setPositionAgent() + // But when agent has a parent (ex: is seated), position remains unchanged + // relative to parent and no parent's position update trigger + // setPositionAgent(). + // But EEP's sky track selection still needs an update if agent has a parent + // and parent moves (ex: vehicles). + if (isAgentAvatarValid() + && gAgentAvatarp->getParent() + && !mOnPositionChanged.empty() + ) + { + LLVector3d new_position = getPositionGlobal(); + if ((mLastTestGlobal - new_position).lengthSquared() > 1.0) + { + // If the position has changed by more than 1 meter since the last time we triggered. + // filters out some noise. + mLastTestGlobal = new_position; + mOnPositionChanged(mFrameAgent.getOrigin(), new_position); + } + } +} + +// friends and operators + +std::ostream& operator<<(std::ostream &s, const LLAgent &agent) +{ + // This is unfinished, but might never be used. + // We'll just leave it for now; we can always delete it. + s << " { " + << " Frame = " << agent.mFrameAgent << "\n" + << " }"; + return s; +} + +// true if your own avatar needs to be rendered. Usually only +// in third person and build. +//----------------------------------------------------------------------------- +// needsRenderAvatar() +//----------------------------------------------------------------------------- +bool LLAgent::needsRenderAvatar() +{ + if (gAgentCamera.cameraMouselook() && !LLVOAvatar::sVisibleInFirstPerson) + { + return false; + } + + return mShowAvatar && mOutfitChosen; +} + +// true if we need to render your own avatar's head. +bool LLAgent::needsRenderHead() +{ + return (LLVOAvatar::sVisibleInFirstPerson && LLPipeline::sReflectionRender) || (mShowAvatar && !gAgentCamera.cameraMouselook()); +} + +//----------------------------------------------------------------------------- +// startTyping() +//----------------------------------------------------------------------------- +void LLAgent::startTyping() +{ + mTypingTimer.reset(); + + if (getRenderState() & AGENT_STATE_TYPING) + { + // already typing, don't trigger a different animation + return; + } + setRenderState(AGENT_STATE_TYPING); + + if (mChatTimer.getElapsedTimeF32() < 2.f) + { + LLViewerObject* chatter = gObjectList.findObject(mLastChatterID); + if (chatter && chatter->isAvatar()) + { + gAgentCamera.setLookAt(LOOKAT_TARGET_RESPOND, chatter, LLVector3::zero); + } + } + + if (gSavedSettings.getBOOL("PlayTypingAnim")) + { + sendAnimationRequest(ANIM_AGENT_TYPE, ANIM_REQUEST_START); + } + (LLFloaterReg::getTypedInstance("nearby_chat"))-> + sendChatFromViewer("", CHAT_TYPE_START, false); +} + +//----------------------------------------------------------------------------- +// stopTyping() +//----------------------------------------------------------------------------- +void LLAgent::stopTyping() +{ + if (mRenderState & AGENT_STATE_TYPING) + { + clearRenderState(AGENT_STATE_TYPING); + sendAnimationRequest(ANIM_AGENT_TYPE, ANIM_REQUEST_STOP); + (LLFloaterReg::getTypedInstance("nearby_chat"))-> + sendChatFromViewer("", CHAT_TYPE_STOP, false); + } +} + +//----------------------------------------------------------------------------- +// setRenderState() +//----------------------------------------------------------------------------- +void LLAgent::setRenderState(U8 newstate) +{ + mRenderState |= newstate; +} + +//----------------------------------------------------------------------------- +// clearRenderState() +//----------------------------------------------------------------------------- +void LLAgent::clearRenderState(U8 clearstate) +{ + mRenderState &= ~clearstate; +} + + +//----------------------------------------------------------------------------- +// getRenderState() +//----------------------------------------------------------------------------- +U8 LLAgent::getRenderState() +{ + // *FIX: don't do stuff in a getter! This is infinite loop city! + if ((mTypingTimer.getElapsedTimeF32() > TYPING_TIMEOUT_SECS) + && (mRenderState & AGENT_STATE_TYPING)) + { + stopTyping(); + } + + if ((!LLSelectMgr::getInstance()->getSelection()->isEmpty() && LLSelectMgr::getInstance()->shouldShowSelection()) + || LLToolMgr::getInstance()->getCurrentTool()->isEditing() ) + { + setRenderState(AGENT_STATE_EDITING); + } + else + { + clearRenderState(AGENT_STATE_EDITING); + } + + return mRenderState; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// endAnimationUpdateUI() +//----------------------------------------------------------------------------- +void LLAgent::endAnimationUpdateUI() +{ + if (LLApp::isExiting() + || !gViewerWindow + || !gMenuBarView + || !gToolBarView + || !gStatusBar) + { + return; + } + if (gAgentCamera.getCameraMode() == gAgentCamera.getLastCameraMode()) + { + // We're already done endAnimationUpdateUI for this transition. + return; + } + + // clean up UI from mode we're leaving + if (gAgentCamera.getLastCameraMode() == CAMERA_MODE_MOUSELOOK ) + { + gToolBarView->setToolBarsVisible(true); + // show mouse cursor + gViewerWindow->showCursor(); + // show menus + gMenuBarView->setVisible(true); + LLNavigationBar::getInstance()->setVisible(true && gSavedSettings.getBOOL("ShowNavbarNavigationPanel")); + gStatusBar->setVisibleForMouselook(true); + + static LLCachedControl show_mini_location_panel(gSavedSettings, "ShowMiniLocationPanel"); + if (show_mini_location_panel) + { + LLPanelTopInfoBar::getInstance()->setVisible(true); + } + + LLChicletBar::getInstance()->setVisible(true); + + LLPanelStandStopFlying::getInstance()->setVisible(true); + + LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); + + LLFloaterCamera::onLeavingMouseLook(); + + if (mMouselookModeOutSignal) + { + (*mMouselookModeOutSignal)(); + } + + // Only pop if we have pushed... + if (true == mViewsPushed) + { +#if 0 // Use this once all floaters are registered + LLFloaterReg::restoreVisibleInstances(); +#else // Use this for now + LLFloaterView::skip_list_t skip_list; + if (LLFloaterReg::findInstance("mini_map")) + { + skip_list.insert(LLFloaterReg::findInstance("mini_map")); + } + if (LLFloaterReg::findInstance("beacons")) + { + skip_list.insert(LLFloaterReg::findInstance("beacons")); + } + LLFloaterIMContainer* im_box = LLFloaterReg::getTypedInstance("im_container"); + LLFloaterIMContainer::floater_list_t conversations; + im_box->getDetachedConversationFloaters(conversations); + for (LLFloater* conversation : conversations) + { + LL_INFOS() << "skip_list.insert(session_floater): " << conversation->getTitle() << LL_ENDL; + skip_list.insert(conversation); + } + + gFloaterView->popVisibleAll(skip_list); +#endif + mViewsPushed = false; + } + + + gAgentCamera.setLookAt(LOOKAT_TARGET_CLEAR); + if( gMorphView ) + { + gMorphView->setVisible( false ); + } + + // Disable mouselook-specific animations + if (isAgentAvatarValid()) + { + if( gAgentAvatarp->isAnyAnimationSignaled(AGENT_GUN_AIM_ANIMS, NUM_AGENT_GUN_AIM_ANIMS) ) + { + if (gAgentAvatarp->mSignaledAnimations.find(ANIM_AGENT_AIM_RIFLE_R) != gAgentAvatarp->mSignaledAnimations.end()) + { + sendAnimationRequest(ANIM_AGENT_AIM_RIFLE_R, ANIM_REQUEST_STOP); + sendAnimationRequest(ANIM_AGENT_HOLD_RIFLE_R, ANIM_REQUEST_START); + } + if (gAgentAvatarp->mSignaledAnimations.find(ANIM_AGENT_AIM_HANDGUN_R) != gAgentAvatarp->mSignaledAnimations.end()) + { + sendAnimationRequest(ANIM_AGENT_AIM_HANDGUN_R, ANIM_REQUEST_STOP); + sendAnimationRequest(ANIM_AGENT_HOLD_HANDGUN_R, ANIM_REQUEST_START); + } + if (gAgentAvatarp->mSignaledAnimations.find(ANIM_AGENT_AIM_BAZOOKA_R) != gAgentAvatarp->mSignaledAnimations.end()) + { + sendAnimationRequest(ANIM_AGENT_AIM_BAZOOKA_R, ANIM_REQUEST_STOP); + sendAnimationRequest(ANIM_AGENT_HOLD_BAZOOKA_R, ANIM_REQUEST_START); + } + if (gAgentAvatarp->mSignaledAnimations.find(ANIM_AGENT_AIM_BOW_L) != gAgentAvatarp->mSignaledAnimations.end()) + { + sendAnimationRequest(ANIM_AGENT_AIM_BOW_L, ANIM_REQUEST_STOP); + sendAnimationRequest(ANIM_AGENT_HOLD_BOW_L, ANIM_REQUEST_START); + } + } + } + } + else if (gAgentCamera.getLastCameraMode() == CAMERA_MODE_CUSTOMIZE_AVATAR) + { + // make sure we ask to save changes + + LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); + + if( gMorphView ) + { + gMorphView->setVisible( false ); + } + + if (isAgentAvatarValid()) + { + if(mCustomAnim) + { + sendAnimationRequest(ANIM_AGENT_CUSTOMIZE, ANIM_REQUEST_STOP); + sendAnimationRequest(ANIM_AGENT_CUSTOMIZE_DONE, ANIM_REQUEST_START); + + mCustomAnim = false ; + } + + } + gAgentCamera.setLookAt(LOOKAT_TARGET_CLEAR); + + LLFloaterCamera::onAvatarEditingAppearance(false); + } + + //--------------------------------------------------------------------- + // Set up UI for mode we're entering + //--------------------------------------------------------------------- + if (gAgentCamera.getCameraMode() == CAMERA_MODE_MOUSELOOK) + { + // clean up UI + // first show anything hidden by UI toggle + gViewerWindow->setUIVisibility(true); + + // then hide stuff we want hidden for mouselook + gToolBarView->setToolBarsVisible(false); + gMenuBarView->setVisible(false); + LLNavigationBar::getInstance()->setVisible(false); + gStatusBar->setVisibleForMouselook(false); + + LLPanelTopInfoBar::getInstance()->setVisible(false); + + LLChicletBar::getInstance()->setVisible(false); + + LLPanelStandStopFlying::getInstance()->setVisible(false); + + // clear out camera lag effect + gAgentCamera.clearCameraLag(); + + // JC - Added for always chat in third person option + gFocusMgr.setKeyboardFocus(NULL); + + LLToolMgr::getInstance()->setCurrentToolset(gMouselookToolset); + + mViewsPushed = true; + + if (mMouselookModeInSignal) + { + (*mMouselookModeInSignal)(); + } + + // hide all floaters except the mini map + +#if 0 // Use this once all floaters are registered + std::set exceptions; + exceptions.insert("mini_map"); + LLFloaterReg::hideVisibleInstances(exceptions); +#else // Use this for now + LLFloaterView::skip_list_t skip_list; + skip_list.insert(LLFloaterReg::findInstance("mini_map")); + skip_list.insert(LLFloaterReg::findInstance("beacons")); + gFloaterView->pushVisibleAll(false, skip_list); +#endif + + if( gMorphView ) + { + gMorphView->setVisible(false); + } + + gConsole->setVisible( true ); + + if (isAgentAvatarValid()) + { + // Trigger mouselook-specific animations + if( gAgentAvatarp->isAnyAnimationSignaled(AGENT_GUN_HOLD_ANIMS, NUM_AGENT_GUN_HOLD_ANIMS) ) + { + if (gAgentAvatarp->mSignaledAnimations.find(ANIM_AGENT_HOLD_RIFLE_R) != gAgentAvatarp->mSignaledAnimations.end()) + { + sendAnimationRequest(ANIM_AGENT_HOLD_RIFLE_R, ANIM_REQUEST_STOP); + sendAnimationRequest(ANIM_AGENT_AIM_RIFLE_R, ANIM_REQUEST_START); + } + if (gAgentAvatarp->mSignaledAnimations.find(ANIM_AGENT_HOLD_HANDGUN_R) != gAgentAvatarp->mSignaledAnimations.end()) + { + sendAnimationRequest(ANIM_AGENT_HOLD_HANDGUN_R, ANIM_REQUEST_STOP); + sendAnimationRequest(ANIM_AGENT_AIM_HANDGUN_R, ANIM_REQUEST_START); + } + if (gAgentAvatarp->mSignaledAnimations.find(ANIM_AGENT_HOLD_BAZOOKA_R) != gAgentAvatarp->mSignaledAnimations.end()) + { + sendAnimationRequest(ANIM_AGENT_HOLD_BAZOOKA_R, ANIM_REQUEST_STOP); + sendAnimationRequest(ANIM_AGENT_AIM_BAZOOKA_R, ANIM_REQUEST_START); + } + if (gAgentAvatarp->mSignaledAnimations.find(ANIM_AGENT_HOLD_BOW_L) != gAgentAvatarp->mSignaledAnimations.end()) + { + sendAnimationRequest(ANIM_AGENT_HOLD_BOW_L, ANIM_REQUEST_STOP); + sendAnimationRequest(ANIM_AGENT_AIM_BOW_L, ANIM_REQUEST_START); + } + } + if (gAgentAvatarp->getParent()) + { + LLVector3 at_axis = LLViewerCamera::getInstance()->getAtAxis(); + LLViewerObject* root_object = (LLViewerObject*)gAgentAvatarp->getRoot(); + if (root_object->flagCameraDecoupled()) + { + resetAxes(at_axis); + } + else + { + resetAxes(at_axis * ~((LLViewerObject*)gAgentAvatarp->getParent())->getRenderRotation()); + } + } + } + } + else if (gAgentCamera.getCameraMode() == CAMERA_MODE_CUSTOMIZE_AVATAR) + { + LLToolMgr::getInstance()->setCurrentToolset(gFaceEditToolset); + + if( gMorphView ) + { + gMorphView->setVisible( true ); + } + + // freeze avatar + if (isAgentAvatarValid()) + { + mPauseRequest = gAgentAvatarp->requestPause(); + } + + LLFloaterCamera::onAvatarEditingAppearance(true); + } + + if (isAgentAvatarValid()) + { + gAgentAvatarp->updateAttachmentVisibility(gAgentCamera.getCameraMode()); + } + + gFloaterTools->dirty(); + + // Don't let this be called more than once if the camera + // mode hasn't changed. --JC + gAgentCamera.updateLastCamera(); +} + +boost::signals2::connection LLAgent::setMouselookModeInCallback( const camera_signal_t::slot_type& cb ) +{ + if (!mMouselookModeInSignal) mMouselookModeInSignal = new camera_signal_t(); + return mMouselookModeInSignal->connect(cb); +} + +boost::signals2::connection LLAgent::setMouselookModeOutCallback( const camera_signal_t::slot_type& cb ) +{ + if (!mMouselookModeOutSignal) mMouselookModeOutSignal = new camera_signal_t(); + return mMouselookModeOutSignal->connect(cb); +} + +//----------------------------------------------------------------------------- +// heardChat() +//----------------------------------------------------------------------------- +void LLAgent::heardChat(const LLUUID& id) +{ + // log text and voice chat to speaker mgr + // for keeping track of active speakers, etc. + LLLocalSpeakerMgr::getInstance()->speakerChatted(id); + + // don't respond to your own voice + if (id == getID()) return; + + if (ll_rand(2) == 0) + { + LLViewerObject *chatter = gObjectList.findObject(mLastChatterID); + gAgentCamera.setLookAt(LOOKAT_TARGET_AUTO_LISTEN, chatter, LLVector3::zero); + } + + mLastChatterID = id; + mChatTimer.reset(); +} + +LLSD ll_sdmap_from_vector3(const LLVector3& vec) +{ + LLSD ret; + ret["X"] = vec.mV[VX]; + ret["Y"] = vec.mV[VY]; + ret["Z"] = vec.mV[VZ]; + return ret; +} + +LLVector3 ll_vector3_from_sdmap(const LLSD& sd) +{ + LLVector3 ret; + ret.mV[VX] = F32(sd["X"].asReal()); + ret.mV[VY] = F32(sd["Y"].asReal()); + ret.mV[VZ] = F32(sd["Z"].asReal()); + return ret; +} + +void LLAgent::setStartPosition( U32 location_id ) +{ + LLViewerObject *object; + + if (gAgentID == LLUUID::null) + { + return; + } + // we've got an ID for an agent viewerobject + object = gObjectList.findObject(gAgentID); + if (! object) + { + LL_INFOS() << "setStartPosition - Can't find agent viewerobject id " << gAgentID << LL_ENDL; + return; + } + // we've got the viewer object + // Sometimes the agent can be velocity interpolated off of + // this simulator. Clamp it to the region the agent is + // in, a little bit in on each side. + const F32 INSET = 0.5f; //meters + const F32 REGION_WIDTH = LLWorld::getInstance()->getRegionWidthInMeters(); + + LLVector3 agent_pos = getPositionAgent(); + + if (isAgentAvatarValid()) + { + // the z height is at the agent's feet + agent_pos.mV[VZ] -= 0.5f * (gAgentAvatarp->mBodySize.mV[VZ] + gAgentAvatarp->mAvatarOffset.mV[VZ]); + } + + agent_pos.mV[VX] = llclamp( agent_pos.mV[VX], INSET, REGION_WIDTH - INSET ); + agent_pos.mV[VY] = llclamp( agent_pos.mV[VY], INSET, REGION_WIDTH - INSET ); + + // Don't let them go below ground, or too high. + agent_pos.mV[VZ] = llclamp( agent_pos.mV[VZ], + mRegionp->getLandHeightRegion( agent_pos ), + LLWorld::getInstance()->getRegionMaxHeight() ); + // Send the CapReq + LLSD request; + LLSD body; + LLSD homeLocation; + + homeLocation["LocationId"] = LLSD::Integer(location_id); + homeLocation["LocationPos"] = ll_sdmap_from_vector3(agent_pos); + homeLocation["LocationLookAt"] = ll_sdmap_from_vector3(mFrameAgent.getAtAxis()); + + body["HomeLocation"] = homeLocation; + + if (!requestPostCapability("HomeLocation", body, + boost::bind(&LLAgent::setStartPositionSuccess, this, _1))) + LL_WARNS() << "Unable to post to HomeLocation capability." << LL_ENDL; +} + +void LLAgent::setStartPositionSuccess(const LLSD &result) +{ + LLVector3 agent_pos; + bool error = true; + + do { + // was the call to /agent//home-location successful? + // If not, we keep error set to true + if (!result.has("success")) + break; + + if (0 != strncmp("true", result["success"].asString().c_str(), 4)) + break; + + // did the simulator return a "justified" home location? + // If no, we keep error set to true + if (!result.has("HomeLocation")) + break; + + if ((!result["HomeLocation"].has("LocationPos")) || + (!result["HomeLocation"]["LocationPos"].has("X")) || + (!result["HomeLocation"]["LocationPos"].has("Y")) || + (!result["HomeLocation"]["LocationPos"].has("Z"))) + break; + + agent_pos.mV[VX] = result["HomeLocation"]["LocationPos"]["X"].asInteger(); + agent_pos.mV[VY] = result["HomeLocation"]["LocationPos"]["Y"].asInteger(); + agent_pos.mV[VZ] = result["HomeLocation"]["LocationPos"]["Z"].asInteger(); + + error = false; + + } while (0); + + if (error) + { + LL_WARNS() << "Error in response to home position set." << LL_ENDL; + } + else + { + LL_INFOS() << "setting home position" << LL_ENDL; + + LLViewerRegion *viewer_region = gAgent.getRegion(); + setHomePosRegion(viewer_region->getHandle(), agent_pos); + } +} + +void LLAgent::requestStopMotion( LLMotion* motion ) +{ + // Notify all avatars that a motion has stopped. + // This is needed to clear the animation state bits + LLUUID anim_state = motion->getID(); + onAnimStop(motion->getID()); + + // if motion is not looping, it could have stopped by running out of time + // so we need to tell the server this +// LL_INFOS() << "Sending stop for motion " << motion->getName() << LL_ENDL; + sendAnimationRequest( anim_state, ANIM_REQUEST_STOP ); +} + +void LLAgent::onAnimStop(const LLUUID& id) +{ + // handle automatic state transitions (based on completion of animation playback) + if (id == ANIM_AGENT_STAND) + { + stopFidget(); + } + else if (id == ANIM_AGENT_AWAY) + { + clearAFK(); + } + else if (id == ANIM_AGENT_STANDUP) + { + // send stand up command + setControlFlags(AGENT_CONTROL_FINISH_ANIM); + + // now trigger dusting self off animation + if (isAgentAvatarValid() && !gAgentAvatarp->mBelowWater && rand() % 3 == 0) + sendAnimationRequest( ANIM_AGENT_BRUSH, ANIM_REQUEST_START ); + } + else if (id == ANIM_AGENT_PRE_JUMP || id == ANIM_AGENT_LAND || id == ANIM_AGENT_MEDIUM_LAND) + { + setControlFlags(AGENT_CONTROL_FINISH_ANIM); + } +} + +bool LLAgent::isGodlike() const +{ + return mAgentAccess->isGodlike(); +} + +bool LLAgent::isGodlikeWithoutAdminMenuFakery() const +{ + return mAgentAccess->isGodlikeWithoutAdminMenuFakery(); +} + +U8 LLAgent::getGodLevel() const +{ + return mAgentAccess->getGodLevel(); +} + +bool LLAgent::wantsPGOnly() const +{ + return mAgentAccess->wantsPGOnly(); +} + +bool LLAgent::canAccessMature() const +{ + return mAgentAccess->canAccessMature(); +} + +bool LLAgent::canAccessAdult() const +{ + return mAgentAccess->canAccessAdult(); +} + +bool LLAgent::canAccessMaturityInRegion( U64 region_handle ) const +{ + LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle( region_handle ); + if( regionp ) + { + switch( regionp->getSimAccess() ) + { + case SIM_ACCESS_MATURE: + if( !canAccessMature() ) + return false; + break; + case SIM_ACCESS_ADULT: + if( !canAccessAdult() ) + return false; + break; + default: + // Oh, go on and hear the silly noises. + break; + } + } + + return true; +} + +bool LLAgent::canAccessMaturityAtGlobal( LLVector3d pos_global ) const +{ + U64 region_handle = to_region_handle_global( pos_global.mdV[0], pos_global.mdV[1] ); + return canAccessMaturityInRegion( region_handle ); +} + +bool LLAgent::prefersPG() const +{ + return mAgentAccess->prefersPG(); +} + +bool LLAgent::prefersMature() const +{ + return mAgentAccess->prefersMature(); +} + +bool LLAgent::prefersAdult() const +{ + return mAgentAccess->prefersAdult(); +} + +bool LLAgent::isTeen() const +{ + return mAgentAccess->isTeen(); +} + +bool LLAgent::isMature() const +{ + return mAgentAccess->isMature(); +} + +bool LLAgent::isAdult() const +{ + return mAgentAccess->isAdult(); +} + +//static +int LLAgent::convertTextToMaturity(char text) +{ + return LLAgentAccess::convertTextToMaturity(text); +} + +void LLAgent::handlePreferredMaturityResult(U8 pServerMaturity) +{ + // Update the number of responses received + ++mMaturityPreferenceResponseId; + llassert(mMaturityPreferenceResponseId <= mMaturityPreferenceRequestId); + + // Update the last known server maturity response + mLastKnownResponseMaturity = pServerMaturity; + + // Ignore all responses if we know there are more unanswered requests that are expected + if (mMaturityPreferenceResponseId == mMaturityPreferenceRequestId) + { + // If we received a response that matches the last known request, then we are good + if (mLastKnownRequestMaturity == mLastKnownResponseMaturity) + { + mMaturityPreferenceNumRetries = 0; + reportPreferredMaturitySuccess(); + llassert(static_cast(gSavedSettings.getU32("PreferredMaturity")) == mLastKnownResponseMaturity); + } + // Else, the viewer is out of sync with the server, so let's try to re-sync with the + // server by re-sending our last known request. Cap the re-tries at 3 just to be safe. + else if (++mMaturityPreferenceNumRetries <= 3) + { + LL_INFOS() << "Retrying attempt #" << mMaturityPreferenceNumRetries << " to set viewer preferred maturity to '" + << LLViewerRegion::accessToString(mLastKnownRequestMaturity) << "'" << LL_ENDL; + sendMaturityPreferenceToServer(mLastKnownRequestMaturity); + } + // Else, the viewer is style out of sync with the server after 3 retries, so inform the user + else + { + mMaturityPreferenceNumRetries = 0; + LL_WARNS() << "Too many retries for maturity preference" << LL_ENDL; + reportPreferredMaturityError(); + } + } +} + +void LLAgent::handlePreferredMaturityError() +{ + // Update the number of responses received + ++mMaturityPreferenceResponseId; + llassert(mMaturityPreferenceResponseId <= mMaturityPreferenceRequestId); + + // Ignore all responses if we know there are more unanswered requests that are expected + if (mMaturityPreferenceResponseId == mMaturityPreferenceRequestId) + { + mMaturityPreferenceNumRetries = 0; + + // If we received a response that matches the last known request, then we are synced with + // the server, but not quite sure why we are + if (mLastKnownRequestMaturity == mLastKnownResponseMaturity) + { + LL_WARNS() << "Got an error but maturity preference '" << LLViewerRegion::accessToString(mLastKnownRequestMaturity) + << "' seems to be in sync with the server" << LL_ENDL; + reportPreferredMaturitySuccess(); + } + // Else, the more likely case is that the last request does not match the last response, + // so inform the user + else + { + reportPreferredMaturityError(); + } + } +} + +void LLAgent::reportPreferredMaturitySuccess() +{ + // If there is a pending teleport request waiting for the maturity preference to be synced with + // the server, let's start the pending request + if (hasPendingTeleportRequest()) + { + startTeleportRequest(); + } +} + +void LLAgent::reportPreferredMaturityError() +{ + // If there is a pending teleport request waiting for the maturity preference to be synced with + // the server, we were unable to successfully sync with the server on maturity preference, so let's + // just raise the screen. + mIsMaturityRatingChangingDuringTeleport = false; + if (hasPendingTeleportRequest()) + { + LL_WARNS("Teleport") << "Teleport failing due to preferred maturity error" << LL_ENDL; + setTeleportState(LLAgent::TELEPORT_NONE); + } + + // Get the last known maturity request from the user activity + std::string preferredMaturity = LLViewerRegion::accessToString(mLastKnownRequestMaturity); + LLStringUtil::toLower(preferredMaturity); + + // Get the last known maturity response from the server + std::string actualMaturity = LLViewerRegion::accessToString(mLastKnownResponseMaturity); + LLStringUtil::toLower(actualMaturity); + + // Notify the user + LLSD args = LLSD::emptyMap(); + args["PREFERRED_MATURITY"] = preferredMaturity; + args["ACTUAL_MATURITY"] = actualMaturity; + LLNotificationsUtil::add("MaturityChangeError", args); + + // Check the saved settings to ensure that we are consistent. If we are not consistent, update + // the viewer, but do not send anything to server + U8 localMaturity = static_cast(gSavedSettings.getU32("PreferredMaturity")); + if (localMaturity != mLastKnownResponseMaturity) + { + bool tmpIsDoSendMaturityPreferenceToServer = mIsDoSendMaturityPreferenceToServer; + mIsDoSendMaturityPreferenceToServer = false; + LL_INFOS() << "Setting viewer preferred maturity to '" << LLViewerRegion::accessToString(mLastKnownResponseMaturity) << "'" << LL_ENDL; + gSavedSettings.setU32("PreferredMaturity", static_cast(mLastKnownResponseMaturity)); + mIsDoSendMaturityPreferenceToServer = tmpIsDoSendMaturityPreferenceToServer; + } +} + +bool LLAgent::isMaturityPreferenceSyncedWithServer() const +{ + return (mMaturityPreferenceRequestId == mMaturityPreferenceResponseId); +} + +void LLAgent::sendMaturityPreferenceToServer(U8 pPreferredMaturity) +{ + // Only send maturity preference to the server if enabled + if (mIsDoSendMaturityPreferenceToServer) + { + // Increment the number of requests. The handlers manage a separate count of responses. + ++mMaturityPreferenceRequestId; + + // Update the last know maturity request + mLastKnownRequestMaturity = pPreferredMaturity; + + // If we don't have a region, report it as an error + if (getRegion() == NULL) + { + LL_WARNS("Agent") << "Region is not defined, can not change Maturity setting." << LL_ENDL; + return; + } + + LLSD access_prefs = LLSD::emptyMap(); + access_prefs["max"] = LLViewerRegion::accessToShortString(pPreferredMaturity); + + LLSD postData = LLSD::emptyMap(); + postData["access_prefs"] = access_prefs; + LL_INFOS() << "Sending viewer preferred maturity to '" << LLViewerRegion::accessToString(pPreferredMaturity) << LL_ENDL; + + if (!requestPostCapability("UpdateAgentInformation", postData, + static_cast(boost::bind(&LLAgent::processMaturityPreferenceFromServer, this, _1, pPreferredMaturity)), + static_cast(boost::bind(&LLAgent::handlePreferredMaturityError, this)) + )) + { + LL_WARNS("Agent") << "Maturity request post failed." << LL_ENDL; + } + } +} + + +void LLAgent::processMaturityPreferenceFromServer(const LLSD &result, U8 perferredMaturity) +{ + U8 maturity = SIM_ACCESS_MIN; + + llassert(result.isDefined()); + llassert(result.isMap()); + llassert(result.has("access_prefs")); + llassert(result.get("access_prefs").isMap()); + llassert(result.get("access_prefs").has("max")); + llassert(result.get("access_prefs").get("max").isString()); + if (result.isDefined() && result.isMap() && result.has("access_prefs") + && result.get("access_prefs").isMap() && result.get("access_prefs").has("max") + && result.get("access_prefs").get("max").isString()) + { + LLSD::String actualPreference = result.get("access_prefs").get("max").asString(); + LLStringUtil::trim(actualPreference); + maturity = LLViewerRegion::shortStringToAccess(actualPreference); + } + + if (maturity != perferredMaturity) + { + LL_WARNS() << "while attempting to change maturity preference from '" + << LLViewerRegion::accessToString(mLastKnownResponseMaturity) + << "' to '" << LLViewerRegion::accessToString(perferredMaturity) + << "', the server responded with '" + << LLViewerRegion::accessToString(maturity) + << "' [value:" << static_cast(maturity) + << "], " << LL_ENDL; + } + handlePreferredMaturityResult(maturity); +} + +// Using a new capability, tell the simulator that we want it to send everything +// it knows about and not just what is in front of the camera, in its view +// frustum. We need this feature so that the contents of the region that appears +// in the 6 snapshots which we cannot see and is normally not "considered", is +// also rendered. Typically, this is turned on when the 360 capture floater is +// opened and turned off when it is closed. +// Note: for this version, we do not have a way to determine when "everything" +// has arrived and has been rendered so for now, the proposal is that users +// will need to experiment with the low resolution version and wait for some +// (hopefully) small period of time while the full contents resolves. +// Pass in a flag to ask the simulator/interest list to "send everything" or +// not (the default mode) +void LLAgent::changeInterestListMode(const std::string &new_mode) +{ + if (new_mode != mInterestListMode) + { + mInterestListMode = new_mode; + + // Change interest list mode for all regions. If they are already set for the current mode, + // the setting will have no effect. + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); + ++iter) + { + LLViewerRegion *regionp = *iter; + if (regionp && regionp->isAlive() && regionp->capabilitiesReceived()) + { + regionp->setInterestListMode(mInterestListMode); + } + } + } + else + { + LL_DEBUGS("360Capture") << "Agent interest list mode is already set to " << mInterestListMode << LL_ENDL; + } +} + + +bool LLAgent::requestPostCapability(const std::string &capName, LLSD &postData, httpCallback_t cbSuccess, httpCallback_t cbFailure) +{ + if (getRegion()) + { + return getRegion()->requestPostCapability(capName, postData, cbSuccess, cbFailure); + } + return false; +} + +bool LLAgent::requestGetCapability(const std::string &capName, httpCallback_t cbSuccess, httpCallback_t cbFailure) +{ + if (getRegion()) + { + return getRegion()->requestGetCapability(capName, cbSuccess, cbFailure); + } + return false; +} + +bool LLAgent::getAdminOverride() const +{ + return mAgentAccess->getAdminOverride(); +} + +void LLAgent::setMaturity(char text) +{ + mAgentAccess->setMaturity(text); +} + +void LLAgent::setAdminOverride(bool b) +{ + mAgentAccess->setAdminOverride(b); +} + +void LLAgent::setGodLevel(U8 god_level) +{ + mAgentAccess->setGodLevel(god_level); + mGodLevelChangeSignal(god_level); +} + +LLAgent::god_level_change_slot_t LLAgent::registerGodLevelChanageListener(god_level_change_callback_t pGodLevelChangeCallback) +{ + return mGodLevelChangeSignal.connect(pGodLevelChangeCallback); +} + +const LLAgentAccess& LLAgent::getAgentAccess() +{ + return *mAgentAccess; +} + +bool LLAgent::validateMaturity(const LLSD& newvalue) +{ + return mAgentAccess->canSetMaturity(newvalue.asInteger()); +} + +void LLAgent::handleMaturity(const LLSD &pNewValue) +{ + sendMaturityPreferenceToServer(static_cast(pNewValue.asInteger())); +} + +//---------------------------------------------------------------------------- + +//*TODO remove, is not used anywhere as of August 20, 2009 +void LLAgent::buildFullnameAndTitle(std::string& name) const +{ + if (isGroupMember()) + { + name = mGroupTitle; + name += ' '; + } + else + { + name.erase(0, name.length()); + } + + if (isAgentAvatarValid()) + { + name += gAgentAvatarp->getFullname(); + } +} + +bool LLAgent::isInGroup(const LLUUID& group_id, bool ignore_god_mode /* false */) const +{ + if (!ignore_god_mode && isGodlike()) + return true; + + U32 count = mGroups.size(); + for(U32 i = 0; i < count; ++i) + { + if(mGroups[i].mID == group_id) + { + return true; + } + } + return false; +} + +// This implementation should mirror LLAgentInfo::hasPowerInGroup +bool LLAgent::hasPowerInGroup(const LLUUID& group_id, U64 power) const +{ + if (isGodlikeWithoutAdminMenuFakery()) + return true; + + // GP_NO_POWERS can also mean no power is enough to grant an ability. + if (GP_NO_POWERS == power) return false; + + U32 count = mGroups.size(); + for(U32 i = 0; i < count; ++i) + { + if(mGroups[i].mID == group_id) + { + return (bool)((mGroups[i].mPowers & power) > 0); + } + } + return false; +} + +bool LLAgent::hasPowerInActiveGroup(U64 power) const +{ + return (mGroupID.notNull() && (hasPowerInGroup(mGroupID, power))); +} + +U64 LLAgent::getPowerInGroup(const LLUUID& group_id) const +{ + if (isGodlike()) + return GP_ALL_POWERS; + + U32 count = mGroups.size(); + for(U32 i = 0; i < count; ++i) + { + if(mGroups[i].mID == group_id) + { + return (mGroups[i].mPowers); + } + } + + return GP_NO_POWERS; +} + +bool LLAgent::getGroupData(const LLUUID& group_id, LLGroupData& data) const +{ + S32 count = mGroups.size(); + for(S32 i = 0; i < count; ++i) + { + if(mGroups[i].mID == group_id) + { + data = mGroups[i]; + return true; + } + } + return false; +} + +S32 LLAgent::getGroupContribution(const LLUUID& group_id) const +{ + S32 count = mGroups.size(); + for(S32 i = 0; i < count; ++i) + { + if(mGroups[i].mID == group_id) + { + S32 contribution = mGroups[i].mContribution; + return contribution; + } + } + return 0; +} + +bool LLAgent::setGroupContribution(const LLUUID& group_id, S32 contribution) +{ + S32 count = mGroups.size(); + for(S32 i = 0; i < count; ++i) + { + if(mGroups[i].mID == group_id) + { + mGroups[i].mContribution = contribution; + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("SetGroupContribution"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgentID); + msg->addUUID("SessionID", gAgentSessionID); + msg->nextBlock("Data"); + msg->addUUID("GroupID", group_id); + msg->addS32("Contribution", contribution); + sendReliableMessage(); + return true; + } + } + return false; +} + +bool LLAgent::setUserGroupFlags(const LLUUID& group_id, bool accept_notices, bool list_in_profile) +{ + S32 count = mGroups.size(); + for(S32 i = 0; i < count; ++i) + { + if(mGroups[i].mID == group_id) + { + mGroups[i].mAcceptNotices = accept_notices; + mGroups[i].mListInProfile = list_in_profile; + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("SetGroupAcceptNotices"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgentID); + msg->addUUID("SessionID", gAgentSessionID); + msg->nextBlock("Data"); + msg->addUUID("GroupID", group_id); + msg->addBOOL("AcceptNotices", accept_notices); + msg->nextBlock("NewData"); + msg->addBOOL("ListInProfile", list_in_profile); + sendReliableMessage(); + return true; + } + } + return false; +} + +bool LLAgent::canJoinGroups() const +{ + return (S32)mGroups.size() < LLAgentBenefitsMgr::current().getGroupMembershipLimit(); +} + +LLQuaternion LLAgent::getHeadRotation() +{ + if (!isAgentAvatarValid() || !gAgentAvatarp->mPelvisp || !gAgentAvatarp->mHeadp) + { + return LLQuaternion::DEFAULT; + } + + if (!gAgentCamera.cameraMouselook()) + { + return gAgentAvatarp->getRotation(); + } + + // We must be in mouselook + LLVector3 look_dir( LLViewerCamera::getInstance()->getAtAxis() ); + LLVector3 up = look_dir % mFrameAgent.getLeftAxis(); + LLVector3 left = up % look_dir; + + LLQuaternion rot(look_dir, left, up); + if (gAgentAvatarp->getParent()) + { + rot = rot * ~gAgentAvatarp->getParent()->getRotation(); + } + + return rot; +} + +void LLAgent::sendAnimationRequests(const std::vector &anim_ids, EAnimRequest request) +{ + if (gAgentID.isNull()) + { + return; + } + + S32 num_valid_anims = 0; + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_AgentAnimation); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, getID()); + msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); + + for (const LLUUID& uuid : anim_ids) + { + if (uuid.notNull()) + { + msg->nextBlockFast(_PREHASH_AnimationList); + msg->addUUIDFast(_PREHASH_AnimID, uuid); + msg->addBOOLFast(_PREHASH_StartAnim, request == ANIM_REQUEST_START); + num_valid_anims++; + } + } + + msg->nextBlockFast(_PREHASH_PhysicalAvatarEventList); + msg->addBinaryDataFast(_PREHASH_TypeData, NULL, 0); + if (num_valid_anims) + { + sendReliableMessage(); + } +} + +void LLAgent::sendAnimationRequest(const LLUUID &anim_id, EAnimRequest request) +{ + if (gAgentID.isNull() || anim_id.isNull() || !mRegionp) + { + return; + } + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_AgentAnimation); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, getID()); + msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); + + msg->nextBlockFast(_PREHASH_AnimationList); + msg->addUUIDFast(_PREHASH_AnimID, anim_id); + msg->addBOOLFast(_PREHASH_StartAnim, request == ANIM_REQUEST_START); + + msg->nextBlockFast(_PREHASH_PhysicalAvatarEventList); + msg->addBinaryDataFast(_PREHASH_TypeData, NULL, 0); + sendReliableMessage(); +} + +// Send a message to the region to stop the NULL animation state +// This will reset animation state overrides for the agent. +void LLAgent::sendAnimationStateReset() +{ + if (gAgentID.isNull() || !mRegionp) + { + return; + } + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_AgentAnimation); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, getID()); + msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); + + msg->nextBlockFast(_PREHASH_AnimationList); + msg->addUUIDFast(_PREHASH_AnimID, LLUUID::null ); + msg->addBOOLFast(_PREHASH_StartAnim, false); + + msg->nextBlockFast(_PREHASH_PhysicalAvatarEventList); + msg->addBinaryDataFast(_PREHASH_TypeData, NULL, 0); + sendReliableMessage(); +} + + +// Send a message to the region to revoke sepecified permissions on ALL scripts in the region +// If the target is an object in the region, permissions in scripts on that object are cleared. +// If it is the region ID, all scripts clear the permissions for this agent +void LLAgent::sendRevokePermissions(const LLUUID & target, U32 permissions) +{ + // Currently only the bits for SCRIPT_PERMISSION_TRIGGER_ANIMATION and SCRIPT_PERMISSION_OVERRIDE_ANIMATIONS + // are supported by the server. Sending any other bits will cause the message to be dropped without changing permissions + + if (gAgentID.notNull() && gMessageSystem) + { + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_RevokePermissions); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, getID()); // Must be our ID + msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); + + msg->nextBlockFast(_PREHASH_Data); + msg->addUUIDFast(_PREHASH_ObjectID, target); // Must be in the region + msg->addS32Fast(_PREHASH_ObjectPermissions, (S32) permissions); + + sendReliableMessage(); + } +} + +void LLAgent::sendWalkRun(bool running) +{ + LLMessageSystem* msgsys = gMessageSystem; + if (msgsys) + { + msgsys->newMessageFast(_PREHASH_SetAlwaysRun); + msgsys->nextBlockFast(_PREHASH_AgentData); + msgsys->addUUIDFast(_PREHASH_AgentID, getID()); + msgsys->addUUIDFast(_PREHASH_SessionID, getSessionID()); + msgsys->addBOOLFast(_PREHASH_AlwaysRun, bool(running) ); + sendReliableMessage(); + } +} + +void LLAgent::friendsChanged() +{ + LLCollectProxyBuddies collector; + LLAvatarTracker::instance().applyFunctor(collector); + mProxyForAgents = collector.mProxy; +} + +bool LLAgent::isGrantedProxy(const LLPermissions& perm) +{ + return (mProxyForAgents.count(perm.getOwner()) > 0); +} + +bool LLAgent::allowOperation(PermissionBit op, + const LLPermissions& perm, + U64 group_proxy_power, + U8 god_minimum) +{ + // Check god level. + if (getGodLevel() >= god_minimum) return true; + + if (!perm.isOwned()) return false; + + // A group member with group_proxy_power can act as owner. + bool is_group_owned; + LLUUID owner_id; + perm.getOwnership(owner_id, is_group_owned); + LLUUID group_id(perm.getGroup()); + LLUUID agent_proxy(getID()); + + if (is_group_owned) + { + if (hasPowerInGroup(group_id, group_proxy_power)) + { + // Let the member assume the group's id for permission requests. + agent_proxy = owner_id; + } + } + else + { + // Check for granted mod permissions. + if ((PERM_OWNER != op) && isGrantedProxy(perm)) + { + agent_proxy = owner_id; + } + } + + // This is the group id to use for permission requests. + // Only group members may use this field. + LLUUID group_proxy = LLUUID::null; + if (group_id.notNull() && isInGroup(group_id)) + { + group_proxy = group_id; + } + + // We now have max ownership information. + if (PERM_OWNER == op) + { + // This this was just a check for ownership, we can now return the answer. + return (agent_proxy == owner_id); + } + + return perm.allowOperationBy(op, agent_proxy, group_proxy); +} + +const LLColor4 &LLAgent::getEffectColor() +{ + return *mEffectColor; +} + +void LLAgent::setEffectColor(const LLColor4 &color) +{ + *mEffectColor = color; +} + +void LLAgent::initOriginGlobal(const LLVector3d &origin_global) +{ + mAgentOriginGlobal = origin_global; +} + +bool LLAgent::leftButtonGrabbed() const +{ + const bool camera_mouse_look = gAgentCamera.cameraMouselook(); + return (!camera_mouse_look && mControlsTakenCount[CONTROL_LBUTTON_DOWN_INDEX] > 0) + || (camera_mouse_look && mControlsTakenCount[CONTROL_ML_LBUTTON_DOWN_INDEX] > 0) + || (!camera_mouse_look && mControlsTakenPassedOnCount[CONTROL_LBUTTON_DOWN_INDEX] > 0) + || (camera_mouse_look && mControlsTakenPassedOnCount[CONTROL_ML_LBUTTON_DOWN_INDEX] > 0); +} + +bool LLAgent::rotateGrabbed() const +{ + return (mControlsTakenCount[CONTROL_YAW_POS_INDEX] > 0) + || (mControlsTakenCount[CONTROL_YAW_NEG_INDEX] > 0); +} + +bool LLAgent::forwardGrabbed() const +{ + return (mControlsTakenCount[CONTROL_AT_POS_INDEX] > 0); +} + +bool LLAgent::backwardGrabbed() const +{ + return (mControlsTakenCount[CONTROL_AT_NEG_INDEX] > 0); +} + +bool LLAgent::upGrabbed() const +{ + return (mControlsTakenCount[CONTROL_UP_POS_INDEX] > 0); +} + +bool LLAgent::downGrabbed() const +{ + return (mControlsTakenCount[CONTROL_UP_NEG_INDEX] > 0); +} + +void update_group_floaters(const LLUUID& group_id) +{ + + LLGroupActions::refresh(group_id); + //*TODO Implement group update for Profile View + // still actual as of July 31, 2009 (DZ) + + gAgent.fireEvent(new LLOldEvents::LLEvent(&gAgent, "new group"), ""); +} + +// static +void LLAgent::processAgentDropGroup(LLMessageSystem *msg, void **) +{ + LLUUID agent_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); + + if (agent_id != gAgentID) + { + LL_WARNS() << "processAgentDropGroup for agent other than me" << LL_ENDL; + return; + } + + LLUUID group_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_GroupID, group_id ); + + // Remove the group if it already exists remove it and add the new data to pick up changes. + LLGroupData gd; + gd.mID = group_id; + std::vector::iterator found_it = std::find(gAgent.mGroups.begin(), gAgent.mGroups.end(), gd); + if (found_it != gAgent.mGroups.end()) + { + gAgent.mGroups.erase(found_it); + if (gAgent.getGroupID() == group_id) + { + gAgent.mGroupID.setNull(); + gAgent.mGroupPowers = 0; + gAgent.mGroupName.clear(); + gAgent.mGroupTitle.clear(); + } + + // refresh all group information + gAgent.sendAgentDataUpdateRequest(); + + LLGroupMgr::getInstance()->clearGroupData(group_id); + // close the floater for this group, if any. + LLGroupActions::closeGroup(group_id); + } + else + { + LL_WARNS() << "processAgentDropGroup, agent is not part of group " << group_id << LL_ENDL; + } +} + +class LLAgentDropGroupViewerNode : public LLHTTPNode +{ + virtual void post( + LLHTTPNode::ResponsePtr response, + const LLSD& context, + const LLSD& input) const + { + + if ( + !input.isMap() || + !input.has("body") ) + { + //what to do with badly formed message? + response->extendedResult(HTTP_BAD_REQUEST, LLSD("Invalid message parameters")); + } + + LLSD body = input["body"]; + if ( body.has("body") ) + { + //stupid message system doubles up the "body"s + body = body["body"]; + } + + if ( + body.has("AgentData") && + body["AgentData"].isArray() && + body["AgentData"][0].isMap() ) + { + LL_INFOS() << "VALID DROP GROUP" << LL_ENDL; + + //there is only one set of data in the AgentData block + LLSD agent_data = body["AgentData"][0]; + LLUUID agent_id; + LLUUID group_id; + + agent_id = agent_data["AgentID"].asUUID(); + group_id = agent_data["GroupID"].asUUID(); + + if (agent_id != gAgentID) + { + LL_WARNS() + << "AgentDropGroup for agent other than me" << LL_ENDL; + + response->notFound(); + return; + } + + // Remove the group if it already exists remove it + // and add the new data to pick up changes. + LLGroupData gd; + gd.mID = group_id; + std::vector::iterator found_it = std::find(gAgent.mGroups.begin(), gAgent.mGroups.end(), gd); + if (found_it != gAgent.mGroups.end()) + { + gAgent.mGroups.erase(found_it); + if (gAgent.getGroupID() == group_id) + { + gAgent.mGroupID.setNull(); + gAgent.mGroupPowers = 0; + gAgent.mGroupName.clear(); + gAgent.mGroupTitle.clear(); + } + + // refresh all group information + gAgent.sendAgentDataUpdateRequest(); + + LLGroupMgr::getInstance()->clearGroupData(group_id); + // close the floater for this group, if any. + LLGroupActions::closeGroup(group_id); + } + else + { + LL_WARNS() + << "AgentDropGroup, agent is not part of group " + << group_id << LL_ENDL; + } + + response->result(LLSD()); + } + else + { + //what to do with badly formed message? + response->extendedResult(HTTP_BAD_REQUEST, LLSD("Invalid message parameters")); + } + } +}; + +LLHTTPRegistration + gHTTPRegistrationAgentDropGroupViewerNode( + "/message/AgentDropGroup"); + +// static +void LLAgent::processAgentGroupDataUpdate(LLMessageSystem *msg, void **) +{ + LLUUID agent_id; + + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); + + if (agent_id != gAgentID) + { + LL_WARNS() << "processAgentGroupDataUpdate for agent other than me" << LL_ENDL; + return; + } + + S32 count = msg->getNumberOfBlocksFast(_PREHASH_GroupData); + LLGroupData group; + bool need_floater_update = false; + for(S32 i = 0; i < count; ++i) + { + msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupID, group.mID, i); + msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupInsigniaID, group.mInsigniaID, i); + msg->getU64(_PREHASH_GroupData, "GroupPowers", group.mPowers, i); + msg->getBOOL(_PREHASH_GroupData, "AcceptNotices", group.mAcceptNotices, i); + msg->getS32(_PREHASH_GroupData, "Contribution", group.mContribution, i); + msg->getStringFast(_PREHASH_GroupData, _PREHASH_GroupName, group.mName, i); + + if(group.mID.notNull()) + { + need_floater_update = true; + // Remove the group if it already exists remove it and add the new data to pick up changes. + std::vector::iterator found_it = std::find(gAgent.mGroups.begin(), gAgent.mGroups.end(), group); + if (found_it != gAgent.mGroups.end()) + { + gAgent.mGroups.erase(found_it); + } + gAgent.mGroups.push_back(group); + } + if (need_floater_update) + { + update_group_floaters(group.mID); + } + } + +} + +class LLAgentGroupDataUpdateViewerNode : public LLHTTPNode +{ + virtual void post( + LLHTTPNode::ResponsePtr response, + const LLSD& context, + const LLSD& input) const + { + LLSD body = input["body"]; + if(body.has("body")) + body = body["body"]; + LLUUID agent_id = body["AgentData"][0]["AgentID"].asUUID(); + + if (agent_id != gAgentID) + { + LL_WARNS() << "processAgentGroupDataUpdate for agent other than me" << LL_ENDL; + return; + } + + LLSD group_data = body["GroupData"]; + + LLSD::array_iterator iter_group = + group_data.beginArray(); + LLSD::array_iterator end_group = + group_data.endArray(); + int group_index = 0; + for(; iter_group != end_group; ++iter_group) + { + + LLGroupData group; + bool need_floater_update = false; + + group.mID = (*iter_group)["GroupID"].asUUID(); + group.mPowers = ll_U64_from_sd((*iter_group)["GroupPowers"]); + group.mAcceptNotices = (*iter_group)["AcceptNotices"].asBoolean(); + group.mListInProfile = body["NewGroupData"][group_index]["ListInProfile"].asBoolean(); + group.mInsigniaID = (*iter_group)["GroupInsigniaID"].asUUID(); + group.mName = (*iter_group)["GroupName"].asString(); + group.mContribution = (*iter_group)["Contribution"].asInteger(); + + group_index++; + + if(group.mID.notNull()) + { + need_floater_update = true; + // Remove the group if it already exists remove it and add the new data to pick up changes. + std::vector::iterator found_it = std::find(gAgent.mGroups.begin(), gAgent.mGroups.end(), group); + if (found_it != gAgent.mGroups.end()) + { + gAgent.mGroups.erase(found_it); + } + gAgent.mGroups.push_back(group); + } + if (need_floater_update) + { + update_group_floaters(group.mID); + } + } + } +}; + +LLHTTPRegistration + gHTTPRegistrationAgentGroupDataUpdateViewerNode ("/message/AgentGroupDataUpdate"); + +// static +void LLAgent::processAgentDataUpdate(LLMessageSystem *msg, void **) +{ + LLUUID agent_id; + + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); + + if (agent_id != gAgentID) + { + LL_WARNS() << "processAgentDataUpdate for agent other than me" << LL_ENDL; + return; + } + + msg->getStringFast(_PREHASH_AgentData, _PREHASH_GroupTitle, gAgent.mGroupTitle); + LLUUID active_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_ActiveGroupID, active_id); + + + if(active_id.notNull()) + { + gAgent.mGroupID = active_id; + msg->getU64(_PREHASH_AgentData, "GroupPowers", gAgent.mGroupPowers); + msg->getString(_PREHASH_AgentData, _PREHASH_GroupName, gAgent.mGroupName); + } + else + { + gAgent.mGroupID.setNull(); + gAgent.mGroupPowers = 0; + gAgent.mGroupName.clear(); + } + + update_group_floaters(active_id); +} + +// static +void LLAgent::processScriptControlChange(LLMessageSystem *msg, void **) +{ + S32 block_count = msg->getNumberOfBlocks("Data"); + for (S32 block_index = 0; block_index < block_count; block_index++) + { + bool take_controls; + U32 controls; + bool passon; + U32 i; + msg->getBOOL("Data", "TakeControls", take_controls, block_index); + if (take_controls) + { + // take controls + msg->getU32("Data", "Controls", controls, block_index ); + msg->getBOOL("Data", "PassToAgent", passon, block_index ); + for (i = 0; i < TOTAL_CONTROLS; i++) + { + if (controls & ( 1 << i)) + { + if (passon) + { + gAgent.mControlsTakenPassedOnCount[i]++; + } + else + { + gAgent.mControlsTakenCount[i]++; + } + } + } + } + else + { + // release controls + msg->getU32("Data", "Controls", controls, block_index ); + msg->getBOOL("Data", "PassToAgent", passon, block_index ); + for (i = 0; i < TOTAL_CONTROLS; i++) + { + if (controls & ( 1 << i)) + { + if (passon) + { + gAgent.mControlsTakenPassedOnCount[i]--; + if (gAgent.mControlsTakenPassedOnCount[i] < 0) + { + gAgent.mControlsTakenPassedOnCount[i] = 0; + } + } + else + { + gAgent.mControlsTakenCount[i]--; + if (gAgent.mControlsTakenCount[i] < 0) + { + gAgent.mControlsTakenCount[i] = 0; + } + } + } + } + } + } +} + +/* +// static +void LLAgent::processControlTake(LLMessageSystem *msg, void **) +{ + U32 controls; + msg->getU32("Data", "Controls", controls ); + U32 passon; + msg->getBOOL("Data", "PassToAgent", passon ); + + S32 i; + S32 total_count = 0; + for (i = 0; i < TOTAL_CONTROLS; i++) + { + if (controls & ( 1 << i)) + { + if (passon) + { + gAgent.mControlsTakenPassedOnCount[i]++; + } + else + { + gAgent.mControlsTakenCount[i]++; + } + total_count++; + } + } + + // Any control taken? If so, might be first time. + if (total_count > 0) + { + LLFirstUse::useOverrideKeys(); + } +} + +// static +void LLAgent::processControlRelease(LLMessageSystem *msg, void **) +{ + U32 controls; + msg->getU32("Data", "Controls", controls ); + U32 passon; + msg->getBOOL("Data", "PassToAgent", passon ); + + S32 i; + for (i = 0; i < TOTAL_CONTROLS; i++) + { + if (controls & ( 1 << i)) + { + if (passon) + { + gAgent.mControlsTakenPassedOnCount[i]--; + if (gAgent.mControlsTakenPassedOnCount[i] < 0) + { + gAgent.mControlsTakenPassedOnCount[i] = 0; + } + } + else + { + gAgent.mControlsTakenCount[i]--; + if (gAgent.mControlsTakenCount[i] < 0) + { + gAgent.mControlsTakenCount[i] = 0; + } + } + } + } +} +*/ + +bool LLAgent::anyControlGrabbed() const +{ + for (U32 i = 0; i < TOTAL_CONTROLS; i++) + { + if (gAgent.mControlsTakenCount[i] > 0) + return true; + if (gAgent.mControlsTakenPassedOnCount[i] > 0) + return true; + } + return false; +} + +bool LLAgent::isControlGrabbed(S32 control_index) const +{ + return mControlsTakenCount[control_index] > 0; +} + +void LLAgent::forceReleaseControls() +{ + gMessageSystem->newMessage("ForceScriptControlRelease"); + gMessageSystem->nextBlock("AgentData"); + gMessageSystem->addUUID("AgentID", getID()); + gMessageSystem->addUUID("SessionID", getSessionID()); + sendReliableMessage(); +} + +void LLAgent::setHomePosRegion( const U64& region_handle, const LLVector3& pos_region) +{ + mHaveHomePosition = true; + mHomeRegionHandle = region_handle; + mHomePosRegion = pos_region; +} + +bool LLAgent::getHomePosGlobal( LLVector3d* pos_global ) +{ + if(!mHaveHomePosition) + { + return false; + } + F32 x = 0; + F32 y = 0; + from_region_handle( mHomeRegionHandle, &x, &y); + pos_global->setVec( x + mHomePosRegion.mV[VX], y + mHomePosRegion.mV[VY], mHomePosRegion.mV[VZ] ); + return true; +} + +bool LLAgent::isInHomeRegion() +{ + if(!mHaveHomePosition) + { + return false; + } + if (!getRegion()) + { + return false; + } + if (getRegion()->getHandle() != mHomeRegionHandle) + { + return false; + } + return true; +} + +void LLAgent::clearVisualParams(void *data) +{ + if (isAgentAvatarValid()) + { + gAgentAvatarp->clearVisualParamWeights(); + gAgentAvatarp->updateVisualParams(); + } +} + +//--------------------------------------------------------------------------- +// Teleport +//--------------------------------------------------------------------------- + +// teleportCore() - stuff to do on any teleport +// protected +bool LLAgent::teleportCore(bool is_local) +{ + LL_DEBUGS("Teleport") << "In teleport core" << LL_ENDL; + if ((TELEPORT_NONE != mTeleportState) && (mTeleportState != TELEPORT_PENDING)) + { + LL_WARNS() << "Attempt to teleport when already teleporting." << LL_ENDL; + return false; + } + + // force stand up and stop a sitting animation (if any), see MAINT-3969 + if (isAgentAvatarValid() && gAgentAvatarp->getParent() && gAgentAvatarp->isSitting()) + { + gAgentAvatarp->getOffObject(); + } + +#if 0 + // This should not exist. It has been added, removed, added, and now removed again. + // This change needs to come from the simulator. Otherwise, the agent ends up out of + // sync with other viewers. Discuss in DEV-14145/VWR-6744 before reenabling. + + // Stop all animation before actual teleporting + if (isAgentAvatarValid()) + { + for ( LLVOAvatar::AnimIterator anim_it= gAgentAvatarp->mPlayingAnimations.begin(); + anim_it != gAgentAvatarp->mPlayingAnimations.end(); + ++anim_it) + { + gAgentAvatarp->stopMotion(anim_it->first); + } + gAgentAvatarp->processAnimationStateChanges(); + } +#endif + + // Don't call LLFirstUse::useTeleport because we don't know + // yet if the teleport will succeed. Look in + // process_teleport_location_reply + + // hide land floater too - it'll be out of date + LLFloaterReg::hideInstance("about_land"); + + // hide the Region/Estate floater + LLFloaterReg::hideInstance("region_info"); + + LLViewerParcelMgr::getInstance()->deselectLand(); + LLViewerMediaFocus::getInstance()->clearFocus(); + + // Close all pie menus, deselect land, etc. + // Don't change the camera until we know teleport succeeded. JC + gAgentCamera.resetView(false); + + // local logic + add(LLStatViewer::TELEPORT, 1); + if (is_local) + { + LL_INFOS("Teleport") << "Setting teleport state to TELEPORT_LOCAL" << LL_ENDL; + gAgent.setTeleportState( LLAgent::TELEPORT_LOCAL ); + } + else + { + gTeleportDisplay = true; + LL_INFOS("Teleport") << "Non-local, setting teleport state to TELEPORT_START" << LL_ENDL; + gAgent.setTeleportState( LLAgent::TELEPORT_START ); + } + make_ui_sound("UISndTeleportOut"); + + // MBW -- Let the voice client know a teleport has begun so it can leave the existing channel. + // This was breaking the case of teleporting within a single sim. Backing it out for now. +// LLVoiceClient::getInstance()->leaveChannel(); + + return true; +} + +bool LLAgent::hasRestartableFailedTeleportRequest() +{ + return ((mTeleportRequest != NULL) && (mTeleportRequest->getStatus() == LLTeleportRequest::kFailed) && + mTeleportRequest->canRestartTeleport()); +} + +void LLAgent::restartFailedTeleportRequest() +{ + LL_INFOS("Teleport") << "Agent wishes to restart failed teleport." << LL_ENDL; + if (hasRestartableFailedTeleportRequest()) + { + mTeleportRequest->setStatus(LLTeleportRequest::kRestartPending); + startTeleportRequest(); + } +} + +void LLAgent::clearTeleportRequest() +{ + if(LLVoiceClient::instanceExists()) + { + LLVoiceClient::getInstance()->setHidden(false); + } + mTeleportRequest.reset(); + mTPNeedsNeabyChatSeparator = false; +} + +void LLAgent::setMaturityRatingChangeDuringTeleport(U8 pMaturityRatingChange) +{ + mIsMaturityRatingChangingDuringTeleport = true; + mMaturityRatingChange = pMaturityRatingChange; +} + +void LLAgent::sheduleTeleportIM() +{ + // is supposed to be called during teleport so we are still waiting for parcel + mTPNeedsNeabyChatSeparator = true; +} + +bool LLAgent::hasPendingTeleportRequest() +{ + return ((mTeleportRequest != NULL) && + ((mTeleportRequest->getStatus() == LLTeleportRequest::kPending) || + (mTeleportRequest->getStatus() == LLTeleportRequest::kRestartPending))); +} + +void LLAgent::startTeleportRequest() +{ + LL_INFOS("Telport") << "Agent handling start teleport request." << LL_ENDL; + if(LLVoiceClient::instanceExists()) + { + LLVoiceClient::getInstance()->setHidden(true); + } + if (hasPendingTeleportRequest()) + { + LLUIUsage::instance().logCommand("Agent.StartTeleportRequest"); + mTeleportCanceled.reset(); + if (!isMaturityPreferenceSyncedWithServer()) + { + gTeleportDisplay = true; + LL_INFOS("Teleport") << "Maturity preference not synced yet, setting teleport state to TELEPORT_PENDING" << LL_ENDL; + setTeleportState(TELEPORT_PENDING); + } + else + { + switch (mTeleportRequest->getStatus()) + { + case LLTeleportRequest::kPending : + mTeleportRequest->setStatus(LLTeleportRequest::kStarted); + mTeleportRequest->startTeleport(); + break; + case LLTeleportRequest::kRestartPending : + llassert(mTeleportRequest->canRestartTeleport()); + mTeleportRequest->setStatus(LLTeleportRequest::kStarted); + mTeleportRequest->restartTeleport(); + break; + default : + llassert(0); + break; + } + } + } +} + +void LLAgent::handleTeleportFinished() +{ + LL_INFOS("Teleport") << "Agent handling teleport finished." << LL_ENDL; + if (mTPNeedsNeabyChatSeparator) + { + // parcel is ready at this point + addTPNearbyChatSeparator(); + mTPNeedsNeabyChatSeparator = false; + } + clearTeleportRequest(); + mTeleportCanceled.reset(); + if (mIsMaturityRatingChangingDuringTeleport) + { + // notify user that the maturity preference has been changed + std::string maturityRating = LLViewerRegion::accessToString(mMaturityRatingChange); + LLStringUtil::toLower(maturityRating); + LLSD args; + args["RATING"] = maturityRating; + LLNotificationsUtil::add("PreferredMaturityChanged", args); + mIsMaturityRatingChangingDuringTeleport = false; + } + + if (mRegionp) + { + if (mRegionp->capabilitiesReceived()) + { + LL_DEBUGS("Teleport") << "capabilities have been received for region handle " + << mRegionp->getHandle() + << " id " << mRegionp->getRegionID() + << ", calling onCapabilitiesReceivedAfterTeleport()" + << LL_ENDL; + onCapabilitiesReceivedAfterTeleport(); + } + else + { + LL_DEBUGS("Teleport") << "Capabilities not yet received for region handle " + << mRegionp->getHandle() + << " id " << mRegionp->getRegionID() + << LL_ENDL; + mRegionp->setCapabilitiesReceivedCallback(boost::bind(&LLAgent::onCapabilitiesReceivedAfterTeleport)); + } + } + LLPerfStats::tunables.autoTuneTimeout = true; +} + +void LLAgent::handleTeleportFailed() +{ + LL_WARNS("Teleport") << "Agent handling teleport failure!" << LL_ENDL; + if(LLVoiceClient::instanceExists()) + { + LLVoiceClient::getInstance()->setHidden(false); + } + + setTeleportState(LLAgent::TELEPORT_NONE); + // Unlock the UI if the progress bar has been shown. +// gViewerWindow->setShowProgress(false); +// gTeleportDisplay = false; + + if (mTeleportRequest) + { + mTeleportRequest->setStatus(LLTeleportRequest::kFailed); + } + if (mIsMaturityRatingChangingDuringTeleport) + { + // notify user that the maturity preference has been changed + std::string maturityRating = LLViewerRegion::accessToString(mMaturityRatingChange); + LLStringUtil::toLower(maturityRating); + LLSD args; + args["RATING"] = maturityRating; + LLNotificationsUtil::add("PreferredMaturityChanged", args); + mIsMaturityRatingChangingDuringTeleport = false; + } + + mTPNeedsNeabyChatSeparator = false; + + LLPerfStats::tunables.autoTuneTimeout = true; +} + +/*static*/ +void LLAgent::addTPNearbyChatSeparator() +{ + LLViewerRegion* agent_region = gAgent.getRegion(); + LLParcel* agent_parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if (!agent_region || !agent_parcel) + { + return; + } + + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); + if (nearby_chat) + { + std::string location_name; + LLAgentUI::ELocationFormat format = LLAgentUI::LOCATION_FORMAT_NO_MATURITY; + + // Might be better to provide slurl to chat + if (!LLAgentUI::buildLocationString(location_name, format)) + { + location_name = "Teleport to new region"; // Shouldn't happen + } + + LLChat chat; + chat.mFromName = location_name; + chat.mMuted = false; + chat.mFromID = LLUUID::null; + chat.mSourceType = CHAT_SOURCE_TELEPORT; + chat.mChatStyle = CHAT_STYLE_TELEPORT_SEP; + chat.mText = ""; + + LLSD args; + args["do_not_log"] = true; + nearby_chat->addMessage(chat, true, args); + } +} + +/*static*/ +void LLAgent::onCapabilitiesReceivedAfterTeleport() +{ + if (gAgent.getRegion()) + { + LL_DEBUGS("Teleport") << "running after capabilities received callback has been triggered, agent region " + << gAgent.getRegion()->getHandle() + << " id " << gAgent.getRegion()->getRegionID() + << " name " << gAgent.getRegion()->getName() + << LL_ENDL; + } + else + { + LL_WARNS("Teleport") << "called when agent region is null!" << LL_ENDL; + } + + check_merchant_status(); +} + + +void LLAgent::teleportRequest( + const U64& region_handle, + const LLVector3& pos_local, + bool look_at_from_camera) +{ + LLViewerRegion* regionp = getRegion(); + if (regionp && teleportCore(region_handle == regionp->getHandle())) + { + LL_INFOS("Teleport") << "Sending TeleportLocationRequest: '" << region_handle << "':" + << pos_local << LL_ENDL; + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("TeleportLocationRequest"); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, getID()); + msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); + msg->nextBlockFast(_PREHASH_Info); + msg->addU64("RegionHandle", region_handle); + msg->addVector3("Position", pos_local); + LLVector3 look_at(0,1,0); + if (look_at_from_camera) + { + look_at = LLViewerCamera::getInstance()->getAtAxis(); + } + msg->addVector3("LookAt", look_at); + sendReliableMessage(); + } +} + +// Landmark ID = LLUUID::null means teleport home +void LLAgent::teleportViaLandmark(const LLUUID& landmark_asset_id) +{ + if (landmark_asset_id.isNull()) + { + gAgentCamera.resetView(); + } + mTeleportRequest = LLTeleportRequestPtr(new LLTeleportRequestViaLandmark(landmark_asset_id)); + startTeleportRequest(); +} + +void LLAgent::doTeleportViaLandmark(const LLUUID& landmark_asset_id) +{ + LLViewerRegion *regionp = getRegion(); + if(regionp && teleportCore()) + { + LL_INFOS("Teleport") << "Sending TeleportLandmarkRequest. Current region handle " << regionp->getHandle() + << " region id " << regionp->getRegionID() + << " requested landmark id " << landmark_asset_id + << LL_ENDL; + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_TeleportLandmarkRequest); + msg->nextBlockFast(_PREHASH_Info); + msg->addUUIDFast(_PREHASH_AgentID, getID()); + msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); + msg->addUUIDFast(_PREHASH_LandmarkID, landmark_asset_id); + sendReliableMessage(); + } +} + +void LLAgent::teleportViaLure(const LLUUID& lure_id, bool godlike) +{ + mTeleportRequest = LLTeleportRequestPtr(new LLTeleportRequestViaLure(lure_id, godlike)); + startTeleportRequest(); +} + +void LLAgent::doTeleportViaLure(const LLUUID& lure_id, bool godlike) +{ + LLViewerRegion* regionp = getRegion(); + if(regionp && teleportCore()) + { + U32 teleport_flags = 0x0; + if (godlike) + { + teleport_flags |= TELEPORT_FLAGS_VIA_GODLIKE_LURE; + teleport_flags |= TELEPORT_FLAGS_DISABLE_CANCEL; + } + else + { + teleport_flags |= TELEPORT_FLAGS_VIA_LURE; + } + + LL_INFOS("Teleport") << "Sending TeleportLureRequest." + << " Current region handle " << regionp->getHandle() + << " region id " << regionp->getRegionID() + << " lure id " << lure_id + << LL_ENDL; + // send the message + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_TeleportLureRequest); + msg->nextBlockFast(_PREHASH_Info); + msg->addUUIDFast(_PREHASH_AgentID, getID()); + msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); + msg->addUUIDFast(_PREHASH_LureID, lure_id); + // teleport_flags is a legacy field, now derived sim-side: + msg->addU32("TeleportFlags", teleport_flags); + sendReliableMessage(); + } +} + + +// James Cook, July 28, 2005 +void LLAgent::teleportCancel() +{ + if (!hasPendingTeleportRequest()) + { + LLViewerRegion* regionp = getRegion(); + if(regionp) + { + LL_INFOS("Teleport") << "Sending TeleportCancel" << LL_ENDL; + + // send the message + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("TeleportCancel"); + msg->nextBlockFast(_PREHASH_Info); + msg->addUUIDFast(_PREHASH_AgentID, getID()); + msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); + sendReliableMessage(); + } + mTeleportCanceled = mTeleportRequest; + } + clearTeleportRequest(); + gAgent.setTeleportState( LLAgent::TELEPORT_NONE ); +} + +void LLAgent::restoreCanceledTeleportRequest() +{ + if (mTeleportCanceled != NULL) + { + LL_INFOS() << "Restoring canceled teleport request, setting state to TELEPORT_REQUESTED" << LL_ENDL; + gAgent.setTeleportState( LLAgent::TELEPORT_REQUESTED ); + mTeleportRequest = mTeleportCanceled; + mTeleportCanceled.reset(); + gTeleportDisplay = true; + gTeleportDisplayTimer.reset(); + } +} + +void LLAgent::teleportViaLocation(const LLVector3d& pos_global) +{ + mTeleportRequest = LLTeleportRequestPtr(new LLTeleportRequestViaLocation(pos_global)); + startTeleportRequest(); +} + +void LLAgent::doTeleportViaLocation(const LLVector3d& pos_global) +{ + LLViewerRegion* regionp = getRegion(); + + if (!regionp) + { + return; + } + + U64 handle = to_region_handle(pos_global); + LLSimInfo* info = LLWorldMap::getInstance()->simInfoFromHandle(handle); + if(regionp && info) + { + LLVector3d region_origin = info->getGlobalOrigin(); + LLVector3 pos_local( + (F32)(pos_global.mdV[VX] - region_origin.mdV[VX]), + (F32)(pos_global.mdV[VY] - region_origin.mdV[VY]), + (F32)(pos_global.mdV[VZ])); + teleportRequest(handle, pos_local); + } + else if(regionp && + teleportCore(regionp->getHandle() == to_region_handle_global((F32)pos_global.mdV[VX], (F32)pos_global.mdV[VY]))) + { + // send the message + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_TeleportLocationRequest); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, getID()); + msg->addUUIDFast(_PREHASH_SessionID, getSessionID()); + + msg->nextBlockFast(_PREHASH_Info); + F32 width = regionp->getWidth(); + LLVector3 pos(fmod((F32)pos_global.mdV[VX], width), + fmod((F32)pos_global.mdV[VY], width), + (F32)pos_global.mdV[VZ]); + F32 region_x = (F32)(pos_global.mdV[VX]); + F32 region_y = (F32)(pos_global.mdV[VY]); + U64 region_handle = to_region_handle_global(region_x, region_y); + msg->addU64Fast(_PREHASH_RegionHandle, region_handle); + msg->addVector3Fast(_PREHASH_Position, pos); + pos.mV[VX] += 1; + msg->addVector3Fast(_PREHASH_LookAt, pos); + + LL_WARNS("Teleport") << "Sending deprecated(?) TeleportLocationRequest." + << " pos_global " << pos_global + << " region_x " << region_x + << " region_y " << region_y + << " region_handle " << region_handle + << LL_ENDL; + + sendReliableMessage(); + } +} + +// Teleport to global position, but keep facing in the same direction +void LLAgent::teleportViaLocationLookAt(const LLVector3d& pos_global) +{ + mTeleportRequest = LLTeleportRequestPtr(new LLTeleportRequestViaLocationLookAt(pos_global)); + startTeleportRequest(); +} + +void LLAgent::doTeleportViaLocationLookAt(const LLVector3d& pos_global) +{ + mbTeleportKeepsLookAt = true; + + if(!gAgentCamera.isfollowCamLocked()) + { + gAgentCamera.setFocusOnAvatar(false, ANIMATE); // detach camera form avatar, so it keeps direction + } + + U64 region_handle = to_region_handle(pos_global); + LLVector3 pos_local = (LLVector3)(pos_global - from_region_handle(region_handle)); + teleportRequest(region_handle, pos_local, getTeleportKeepsLookAt()); +} + +LLAgent::ETeleportState LLAgent::getTeleportState() const +{ + return (mTeleportRequest && (mTeleportRequest->getStatus() == LLTeleportRequest::kFailed)) ? + TELEPORT_NONE : mTeleportState; +} + + +void LLAgent::setTeleportState(ETeleportState state) +{ + if (mTeleportRequest && (state != TELEPORT_NONE) && (mTeleportRequest->getStatus() == LLTeleportRequest::kFailed)) + { // A late message has come in regarding a failed teleport. + // We have already decided that it failed so should not reinitiate the teleport sequence in the viewer. + LL_WARNS("Teleport") << "Attempt to set teleport state to " << state << + " for previously failed teleport. Ignore!" << LL_ENDL; + return; + } + LL_DEBUGS("Teleport") << "Setting teleport state to " + << LLAgent::teleportStateName(state) << "(" << state << ")" + << " Previous state: " + << teleportStateName(mTeleportState) << "(" << mTeleportState << ")" + << LL_ENDL; + mTeleportState = state; + if (mTeleportState > TELEPORT_NONE && gSavedSettings.getBOOL("FreezeTime")) + { + LLFloaterReg::hideInstance("snapshot"); + } + + switch (mTeleportState) + { + case TELEPORT_NONE: + mbTeleportKeepsLookAt = false; + break; + + case TELEPORT_MOVING: + // We're outa here. Save "back" slurl. + LLAgentUI::buildSLURL(*mTeleportSourceSLURL); + break; + + case TELEPORT_ARRIVING: + // First two position updates after a teleport tend to be weird + //LLViewerStats::getInstance()->mAgentPositionSnaps.mCountOfNextUpdatesToIgnore = 2; + + // Let the interested parties know we've teleported. + LLViewerParcelMgr::getInstance()->onTeleportFinished(false, getPositionGlobal()); + break; + + default: + break; + } +} + + +void LLAgent::stopCurrentAnimations() +{ + LL_DEBUGS("Avatar") << "Stopping current animations" << LL_ENDL; + + // This function stops all current overriding animations on this + // avatar, propagating this change back to the server. + if (isAgentAvatarValid()) + { + std::vector anim_ids; + + for ( LLVOAvatar::AnimIterator anim_it = + gAgentAvatarp->mPlayingAnimations.begin(); + anim_it != gAgentAvatarp->mPlayingAnimations.end(); + anim_it++) + { + if ((anim_it->first == ANIM_AGENT_DO_NOT_DISTURB)|| + (anim_it->first == ANIM_AGENT_SIT_GROUND_CONSTRAINED)) + { + // don't cancel a ground-sit anim, as viewers + // use this animation's status in + // determining whether we're sitting. ick. + LL_DEBUGS("Avatar") << "sit or do-not-disturb animation will not be stopped" << LL_ENDL; + } + else + { + // stop this animation locally + gAgentAvatarp->stopMotion(anim_it->first, true); + // ...and tell the server to tell everyone. + anim_ids.push_back(anim_it->first); + } + } + + sendAnimationRequests(anim_ids, ANIM_REQUEST_STOP); + + // Tell the region to clear any animation state overrides + sendAnimationStateReset(); + + // Revoke all animation permissions + if (mRegionp && + gSavedSettings.getBOOL("RevokePermsOnStopAnimation")) + { + U32 permissions = SCRIPT_PERMISSIONS[SCRIPT_PERMISSION_TRIGGER_ANIMATION].permbit | SCRIPT_PERMISSIONS[SCRIPT_PERMISSION_OVERRIDE_ANIMATIONS].permbit; + sendRevokePermissions(mRegionp->getRegionID(), permissions); + if (gAgentAvatarp->isSitting()) + { // Also stand up, since auto-granted sit animation permission has been revoked + gAgent.standUp(); + } + } + + // re-assert at least the default standing animation, because + // viewers get confused by avs with no associated anims. + sendAnimationRequest(ANIM_AGENT_STAND, ANIM_REQUEST_START); + } +} + +void LLAgent::fidget() +{ + if (!getAFK()) + { + F32 curTime = mFidgetTimer.getElapsedTimeF32(); + if (curTime > mNextFidgetTime) + { + // pick a random fidget anim here + S32 oldFidget = mCurrentFidget; + + mCurrentFidget = ll_rand(NUM_AGENT_STAND_ANIMS); + + if (mCurrentFidget != oldFidget) + { + LLAgent::stopFidget(); + + + switch(mCurrentFidget) + { + case 0: + mCurrentFidget = 0; + break; + case 1: + sendAnimationRequest(ANIM_AGENT_STAND_1, ANIM_REQUEST_START); + mCurrentFidget = 1; + break; + case 2: + sendAnimationRequest(ANIM_AGENT_STAND_2, ANIM_REQUEST_START); + mCurrentFidget = 2; + break; + case 3: + sendAnimationRequest(ANIM_AGENT_STAND_3, ANIM_REQUEST_START); + mCurrentFidget = 3; + break; + case 4: + sendAnimationRequest(ANIM_AGENT_STAND_4, ANIM_REQUEST_START); + mCurrentFidget = 4; + break; + } + } + + // calculate next fidget time + mNextFidgetTime = curTime + ll_frand(MAX_FIDGET_TIME - MIN_FIDGET_TIME) + MIN_FIDGET_TIME; + } + } +} + +void LLAgent::stopFidget() +{ + std::vector anims; + anims.reserve(4); + anims.push_back(ANIM_AGENT_STAND_1); + anims.push_back(ANIM_AGENT_STAND_2); + anims.push_back(ANIM_AGENT_STAND_3); + anims.push_back(ANIM_AGENT_STAND_4); + + gAgent.sendAnimationRequests(anims, ANIM_REQUEST_STOP); +} + + +void LLAgent::requestEnterGodMode() +{ + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_RequestGodlikePowers); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_RequestBlock); + msg->addBOOLFast(_PREHASH_Godlike, true); + msg->addUUIDFast(_PREHASH_Token, LLUUID::null); + + // simulators need to know about your request + sendReliableMessage(); +} + +void LLAgent::requestLeaveGodMode() +{ + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_RequestGodlikePowers); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_RequestBlock); + msg->addBOOLFast(_PREHASH_Godlike, false); + msg->addUUIDFast(_PREHASH_Token, LLUUID::null); + + // simulator needs to know about your request + sendReliableMessage(); +} + +void LLAgent::sendAgentDataUpdateRequest() +{ + gMessageSystem->newMessageFast(_PREHASH_AgentDataUpdateRequest); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + sendReliableMessage(); +} + +void LLAgent::sendAgentUserInfoRequest() +{ + std::string cap; + + if (getID().isNull()) + return; // not logged in + + if (mRegionp) + cap = mRegionp->getCapability("UserInfo"); + + if (!cap.empty()) + { + LLCoros::instance().launch("requestAgentUserInfoCoro", + boost::bind(&LLAgent::requestAgentUserInfoCoro, this, cap)); + } + else + { + sendAgentUserInfoRequestMessage(); + } +} + +void LLAgent::requestAgentUserInfoCoro(std::string capurl) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("requestAgentUserInfoCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + LLCore::HttpHeaders::ptr_t httpHeaders; + + httpOpts->setFollowRedirects(true); + + LLSD result = httpAdapter->getAndSuspend(httpRequest, capurl, httpOpts, httpHeaders); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS("UserInfo") << "Failed to get user information." << LL_ENDL; + return; + } + else if (!result["success"].asBoolean()) + { + LL_WARNS("UserInfo") << "Failed to get user information: " << result["message"] << LL_ENDL; + return; + } + + std::string email; + std::string dir_visibility; + + email = result["email"].asString(); + dir_visibility = result["directory_visibility"].asString(); + + // TODO: This should probably be changed. I'm not entirely comfortable + // having LLAgent interact directly with the UI in this way. + LLFloaterPreference::updateUserInfo(dir_visibility); + LLFloaterSnapshot::setAgentEmail(email); +} + +void LLAgent::sendAgentUpdateUserInfo(const std::string& directory_visibility) +{ + std::string cap; + + if (getID().isNull()) + return; // not logged in + + if (mRegionp) + cap = mRegionp->getCapability("UserInfo"); + + if (!cap.empty()) + { + LLCoros::instance().launch("updateAgentUserInfoCoro", + boost::bind(&LLAgent::updateAgentUserInfoCoro, this, cap, directory_visibility)); + } + else + { + sendAgentUpdateUserInfoMessage(directory_visibility); + } +} + + +void LLAgent::updateAgentUserInfoCoro(std::string capurl, std::string directory_visibility) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("requestAgentUserInfoCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + LLCore::HttpHeaders::ptr_t httpHeaders; + + httpOpts->setFollowRedirects(true); + LLSD body(LLSDMap + ("dir_visibility", LLSD::String(directory_visibility))); + + + LLSD result = httpAdapter->postAndSuspend(httpRequest, capurl, body, httpOpts, httpHeaders); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS("UserInfo") << "Failed to set user information." << LL_ENDL; + } + else if (!result["success"].asBoolean()) + { + LL_WARNS("UserInfo") << "Failed to set user information: " << result["message"] << LL_ENDL; + } +} + +// deprecated: +// May be removed when UserInfo cap propagates to all simhosts in grid +void LLAgent::sendAgentUserInfoRequestMessage() +{ + gMessageSystem->newMessageFast(_PREHASH_UserInfoRequest); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, getID()); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, getSessionID()); + sendReliableMessage(); +} + +void LLAgent::sendAgentUpdateUserInfoMessage(const std::string& directory_visibility) +{ + gMessageSystem->newMessageFast(_PREHASH_UpdateUserInfo); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, getID()); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, getSessionID()); + gMessageSystem->nextBlockFast(_PREHASH_UserData); + gMessageSystem->addString("DirectoryVisibility", directory_visibility); + gAgent.sendReliableMessage(); + +} +// end deprecated +//------ + +void LLAgent::observeFriends() +{ + if(!mFriendObserver) + { + mFriendObserver = new LLAgentFriendObserver; + LLAvatarTracker::instance().addObserver(mFriendObserver); + friendsChanged(); + } +} + +std::map LLAgent::sTeleportStateName = { { TELEPORT_NONE, "TELEPORT_NONE" }, + { TELEPORT_START, "TELEPORT_START" }, + { TELEPORT_REQUESTED, "TELEPORT_REQUESTED" }, + { TELEPORT_MOVING, "TELEPORT_MOVING" }, + { TELEPORT_START_ARRIVAL, "TELEPORT_START_ARRIVAL" }, + { TELEPORT_ARRIVING, "TELEPORT_ARRIVING" }, + { TELEPORT_LOCAL, "TELEPORT_LOCAL" }, + { TELEPORT_PENDING, "TELEPORT_PENDING" } }; + +const std::string& LLAgent::teleportStateName(S32 state) +{ + static std::string invalid_state_str("INVALID"); + auto iter = LLAgent::sTeleportStateName.find(state); + if (iter != LLAgent::sTeleportStateName.end()) + { + return iter->second; + } + else + { + return invalid_state_str; + } +} + +const std::string& LLAgent::getTeleportStateName() const +{ + return teleportStateName(getTeleportState()); +} + +void LLAgent::parseTeleportMessages(const std::string& xml_filename) +{ + LLXMLNodePtr root; + bool success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root); + + if (!success || !root || !root->hasName( "teleport_messages" )) + { + LL_ERRS() << "Problem reading teleport string XML file: " + << xml_filename << LL_ENDL; + return; + } + + for (LLXMLNode* message_set = root->getFirstChild(); + message_set != NULL; + message_set = message_set->getNextSibling()) + { + if ( !message_set->hasName("message_set") ) continue; + + std::map *teleport_msg_map = NULL; + std::string message_set_name; + + if ( message_set->getAttributeString("name", message_set_name) ) + { + //now we loop over all the string in the set and add them + //to the appropriate set + if ( message_set_name == "errors" ) + { + teleport_msg_map = &sTeleportErrorMessages; + } + else if ( message_set_name == "progress" ) + { + teleport_msg_map = &sTeleportProgressMessages; + } + } + + if ( !teleport_msg_map ) continue; + + std::string message_name; + for (LLXMLNode* message_node = message_set->getFirstChild(); + message_node != NULL; + message_node = message_node->getNextSibling()) + { + if ( message_node->hasName("message") && + message_node->getAttributeString("name", message_name) ) + { + (*teleport_msg_map)[message_name] = + message_node->getTextContents(); + } //end if ( message exists and has a name) + } //end for (all message in set) + }//end for (all message sets in xml file) +} + +const void LLAgent::getTeleportSourceSLURL(LLSLURL& slurl) const +{ + slurl = *mTeleportSourceSLURL; +} + +// static +void LLAgent::dumpGroupInfo() +{ + LL_INFOS() << "group " << gAgent.mGroupName << LL_ENDL; + LL_INFOS() << "ID " << gAgent.mGroupID << LL_ENDL; + LL_INFOS() << "powers " << gAgent.mGroupPowers << LL_ENDL; + LL_INFOS() << "title " << gAgent.mGroupTitle << LL_ENDL; + //LL_INFOS() << "insig " << gAgent.mGroupInsigniaID << LL_ENDL; +} + +// Draw a representation of current autopilot target +void LLAgent::renderAutoPilotTarget() +{ + if (mAutoPilot) + { + F32 height_meters; + LLVector3d target_global; + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + + // not textured + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + // lovely green + gGL.color4f(0.f, 1.f, 1.f, 1.f); + + target_global = mAutoPilotTargetGlobal; + + gGL.translatef((F32)(target_global.mdV[VX]), (F32)(target_global.mdV[VY]), (F32)(target_global.mdV[VZ])); + + height_meters = 1.f; + + gGL.scalef(height_meters, height_meters, height_meters); + + gSphere.render(); + + gGL.popMatrix(); + } +} + +/********************************************************************************/ + +//----------------------------------------------------------------------------- +// LLTeleportRequest +//----------------------------------------------------------------------------- + +LLTeleportRequest::LLTeleportRequest() + : mStatus(kPending) +{ +} + +LLTeleportRequest::~LLTeleportRequest() +{ +} + +bool LLTeleportRequest::canRestartTeleport() +{ + return false; +} + +void LLTeleportRequest::restartTeleport() +{ + llassert(0); +} + +// TODO this enum -> name idiom should be in a common class rather than repeated various places. +const std::string& LLTeleportRequest::statusName(EStatus status) +{ + static std::string invalid_status_str("INVALID"); + auto iter = LLTeleportRequest::sTeleportStatusName.find(status); + if (iter != LLTeleportRequest::sTeleportStatusName.end()) + { + return iter->second; + } + else + { + return invalid_status_str; + } +} + +std::ostream& operator<<(std::ostream& os, const LLTeleportRequest& req) +{ + req.toOstream(os); + return os; +} + +void LLTeleportRequest::toOstream(std::ostream& os) const +{ + os << "status " << statusName(mStatus) << "(" << mStatus << ")"; +} + +//----------------------------------------------------------------------------- +// LLTeleportRequestViaLandmark +//----------------------------------------------------------------------------- +LLTeleportRequestViaLandmark::LLTeleportRequestViaLandmark(const LLUUID &pLandmarkId) + : LLTeleportRequest(), + mLandmarkId(pLandmarkId) +{ + LL_INFOS("Teleport") << "LLTeleportRequestViaLandmark created, " << *this << LL_ENDL; +} + +LLTeleportRequestViaLandmark::~LLTeleportRequestViaLandmark() +{ + LL_INFOS("Teleport") << "~LLTeleportRequestViaLandmark, " << *this << LL_ENDL; +} + +void LLTeleportRequestViaLandmark::toOstream(std::ostream& os) const +{ + os << "landmark " << mLandmarkId << " "; + LLTeleportRequest::toOstream(os); +} + +bool LLTeleportRequestViaLandmark::canRestartTeleport() +{ + LL_INFOS("Teleport") << "LLTeleportRequestViaLandmark::canRestartTeleport? -> true, " << *this << LL_ENDL; + return true; +} + +void LLTeleportRequestViaLandmark::startTeleport() +{ + LL_INFOS("Teleport") << "LLTeleportRequestViaLandmark::startTeleport, " << *this << LL_ENDL; + gAgent.doTeleportViaLandmark(getLandmarkId()); +} + +void LLTeleportRequestViaLandmark::restartTeleport() +{ + LL_INFOS("Teleport") << "LLTeleportRequestViaLandmark::restartTeleport, " << *this << LL_ENDL; + gAgent.doTeleportViaLandmark(getLandmarkId()); +} +//----------------------------------------------------------------------------- +// LLTeleportRequestViaLure +//----------------------------------------------------------------------------- + +LLTeleportRequestViaLure::LLTeleportRequestViaLure(const LLUUID &pLureId, bool pIsLureGodLike) + : LLTeleportRequestViaLandmark(pLureId), + mIsLureGodLike(pIsLureGodLike) +{ + LL_INFOS("Teleport") << "LLTeleportRequestViaLure created" << LL_ENDL; +} + +LLTeleportRequestViaLure::~LLTeleportRequestViaLure() +{ + LL_INFOS("Teleport") << "~LLTeleportRequestViaLure" << LL_ENDL; +} + +void LLTeleportRequestViaLure::toOstream(std::ostream& os) const +{ + os << "mIsLureGodLike " << (S32) mIsLureGodLike << " "; + LLTeleportRequestViaLandmark::toOstream(os); +} + +bool LLTeleportRequestViaLure::canRestartTeleport() +{ + // stinson 05/17/2012 : cannot restart a teleport via lure because of server-side restrictions + // The current scenario is as follows: + // 1. User A initializes a request for User B to teleport via lure + // 2. User B accepts the teleport via lure request + // 3. The server sees the init request from User A and the accept request from User B and matches them up + // 4. The server then removes the paired requests up from the "queue" + // 5. The server then fails User B's teleport for reason of maturity level (for example) + // 6. User B's viewer prompts user to increase their maturity level profile value. + // 7. User B confirms and accepts increase in maturity level + // 8. User B's viewer then attempts to teleport via lure again + // 9. This request will time-out on the viewer-side because User A's initial request has been removed from the "queue" in step 4 + + LL_INFOS("Teleport") << "LLTeleportRequestViaLure::canRestartTeleport? -> false" << LL_ENDL; + return false; +} + +void LLTeleportRequestViaLure::startTeleport() +{ + LL_INFOS("Teleport") << "LLTeleportRequestViaLure::startTeleport" << LL_ENDL; + gAgent.doTeleportViaLure(getLandmarkId(), isLureGodLike()); +} + +//----------------------------------------------------------------------------- +// LLTeleportRequestViaLocation +//----------------------------------------------------------------------------- + +LLTeleportRequestViaLocation::LLTeleportRequestViaLocation(const LLVector3d &pPosGlobal) + : LLTeleportRequest(), + mPosGlobal(pPosGlobal) +{ + LL_INFOS("Teleport") << "LLTeleportRequestViaLocation created" << LL_ENDL; +} + +LLTeleportRequestViaLocation::~LLTeleportRequestViaLocation() +{ + LL_INFOS("Teleport") << "~LLTeleportRequestViaLocation" << LL_ENDL; +} + +void LLTeleportRequestViaLocation::toOstream(std::ostream& os) const +{ + os << "mPosGlobal " << mPosGlobal << " "; + LLTeleportRequest::toOstream(os); +} + +bool LLTeleportRequestViaLocation::canRestartTeleport() +{ + LL_INFOS("Teleport") << "LLTeleportRequestViaLocation::canRestartTeleport -> true" << LL_ENDL; + return true; +} + +void LLTeleportRequestViaLocation::startTeleport() +{ + LL_INFOS("Teleport") << "LLTeleportRequestViaLocation::startTeleport" << LL_ENDL; + gAgent.doTeleportViaLocation(getPosGlobal()); +} + +void LLTeleportRequestViaLocation::restartTeleport() +{ + LL_INFOS("Teleport") << "LLTeleportRequestViaLocation::restartTeleport" << LL_ENDL; + gAgent.doTeleportViaLocation(getPosGlobal()); +} + +//----------------------------------------------------------------------------- +// LLTeleportRequestViaLocationLookAt +//----------------------------------------------------------------------------- + +LLTeleportRequestViaLocationLookAt::LLTeleportRequestViaLocationLookAt(const LLVector3d &pPosGlobal) + : LLTeleportRequestViaLocation(pPosGlobal) +{ + LL_INFOS("Teleport") << "LLTeleportRequestViaLocationLookAt created" << LL_ENDL; +} + +LLTeleportRequestViaLocationLookAt::~LLTeleportRequestViaLocationLookAt() +{ + LL_INFOS("Teleport") << "~LLTeleportRequestViaLocationLookAt" << LL_ENDL; +} + +void LLTeleportRequestViaLocationLookAt::toOstream(std::ostream& os) const +{ + LLTeleportRequestViaLocation::toOstream(os); +} + +bool LLTeleportRequestViaLocationLookAt::canRestartTeleport() +{ + LL_INFOS("Teleport") << "LLTeleportRequestViaLocationLookAt::canRestartTeleport -> true" << LL_ENDL; + return true; +} + +void LLTeleportRequestViaLocationLookAt::startTeleport() +{ + LL_INFOS("Teleport") << "LLTeleportRequestViaLocationLookAt::startTeleport" << LL_ENDL; + gAgent.doTeleportViaLocationLookAt(getPosGlobal()); +} + +void LLTeleportRequestViaLocationLookAt::restartTeleport() +{ + LL_INFOS("Teleport") << "LLTeleportRequestViaLocationLookAt::restartTeleport" << LL_ENDL; + gAgent.doTeleportViaLocationLookAt(getPosGlobal()); +} + +// EOF diff --git a/indra/newview/llagent.h b/indra/newview/llagent.h index 5e016a9b20..9cd3e80f69 100644 --- a/indra/newview/llagent.h +++ b/indra/newview/llagent.h @@ -1,1004 +1,1004 @@ -/** - * @file llagent.h - * @brief LLAgent class header file - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLAGENT_H -#define LL_LLAGENT_H - -#include "indra_constants.h" -#include "llevent.h" // LLObservable base class -#include "llagentdata.h" // gAgentID, gAgentSessionID -#include "llcharacter.h" -#include "llcoordframe.h" // for mFrameAgent -#include "llavatarappearancedefines.h" -#include "llpermissionsflags.h" -#include "llevents.h" -#include "v3dmath.h" -#include "httprequest.h" -#include "llcorehttputil.h" - -#include -#include -#include - -extern const bool ANIMATE; -extern const U8 AGENT_STATE_TYPING; // Typing indication -extern const U8 AGENT_STATE_EDITING; // Set when agent has objects selected - -class LLViewerRegion; -class LLMotion; -class LLMessageSystem; -class LLPermissions; -class LLHost; -class LLFriendObserver; -class LLAgentDropGroupViewerNode; -class LLAgentAccess; -class LLSLURL; -class LLUIColor; -class LLTeleportRequest; - - - -typedef std::shared_ptr LLTeleportRequestPtr; - -//-------------------------------------------------------------------- -// Types -//-------------------------------------------------------------------- - -enum EAnimRequest -{ - ANIM_REQUEST_START, - ANIM_REQUEST_STOP -}; - -struct LLGroupData -{ - LLUUID mID; - LLUUID mInsigniaID; - U64 mPowers; - bool mAcceptNotices; - bool mListInProfile; - S32 mContribution; - std::string mName; -}; - -class LLAgentListener; - -//------------------------------------------------------------------------ -// LLAgent -//------------------------------------------------------------------------ -class LLAgent : public LLOldEvents::LLObservable -{ - LOG_CLASS(LLAgent); - -public: - friend class LLAgentDropGroupViewerNode; - -/******************************************************************************** - ** ** - ** INITIALIZATION - **/ - - //-------------------------------------------------------------------- - // Constructors / Destructors - //-------------------------------------------------------------------- -public: - LLAgent(); - virtual ~LLAgent(); - void init(); - void cleanup(); - -private: - - //-------------------------------------------------------------------- - // Login - //-------------------------------------------------------------------- -public: - void onAppFocusGained(); - void setFirstLogin(bool b); - // Return true if the database reported this login as the first for this particular user. - bool isFirstLogin() const { return mFirstLogin; } - bool isInitialized() const { return mInitialized; } - - void setFeatureVersion(S32 version, S32 flags); - S32 getFeatureVersion(); - void getFeatureVersionAndFlags(S32 &version, S32 &flags); - void showLatestFeatureNotification(const std::string key); -public: - std::string mMOTD; // Message of the day -private: - bool mInitialized; - bool mFirstLogin; - std::shared_ptr mListener; - - //-------------------------------------------------------------------- - // Session - //-------------------------------------------------------------------- -public: - const LLUUID& getID() const { return gAgentID; } - const LLUUID& getSessionID() const { return gAgentSessionID; } - // Note: NEVER send this value in the clear or over any weakly - // encrypted channel (such as simple XOR masking). If you are unsure - // ask Aaron or MarkL. - const LLUUID& getSecureSessionID() const { return mSecureSessionID; } -public: - LLUUID mSecureSessionID; // Secure token for this login session - -/** Initialization - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** IDENTITY - **/ - - //-------------------------------------------------------------------- - // Name - //-------------------------------------------------------------------- -public: - //*TODO remove, is not used as of August 20, 2009 - void buildFullnameAndTitle(std::string &name) const; - - //-------------------------------------------------------------------- - // Gender - //-------------------------------------------------------------------- -public: - // On the very first login, outfit needs to be chosen by some - // mechanism, usually by loading the requested initial outfit. We - // don't render the avatar until the choice is made. - bool isOutfitChosen() const { return mOutfitChosen; } - void setOutfitChosen(bool b) { mOutfitChosen = b; } -private: - bool mOutfitChosen; - -/** Identity - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** POSITION - **/ - - //-------------------------------------------------------------------- - // Position - //-------------------------------------------------------------------- -public: - typedef boost::signals2::signal position_signal_t; - - LLVector3 getPosAgentFromGlobal(const LLVector3d &pos_global) const; - LLVector3d getPosGlobalFromAgent(const LLVector3 &pos_agent) const; - const LLVector3d &getPositionGlobal() const; - const LLVector3 &getPositionAgent(); - // Call once per frame to update position, angles (radians). - void updateAgentPosition(const F32 dt, const F32 yaw, const S32 mouse_x, const S32 mouse_y); - void setPositionAgent(const LLVector3 ¢er); - - boost::signals2::connection whenPositionChanged(position_signal_t::slot_type fn); - -protected: - void propagate(const F32 dt); // ! BUG ! Should roll into updateAgentPosition -private: - mutable LLVector3d mPositionGlobal; - - position_signal_t mOnPositionChanged; - LLVector3d mLastTestGlobal; - - //-------------------------------------------------------------------- - // Velocity - //-------------------------------------------------------------------- -public: - LLVector3 getVelocity() const; - F32 getVelocityZ() const { return getVelocity().mV[VZ]; } // ! HACK ! - - //-------------------------------------------------------------------- - // Coordinate System - //-------------------------------------------------------------------- -public: - const LLCoordFrame& getFrameAgent() const { return mFrameAgent; } - void initOriginGlobal(const LLVector3d &origin_global); // Only to be used in ONE place - void resetAxes(); - void resetAxes(const LLVector3 &look_at); // Makes reasonable left and up - // The following three get*Axis functions return direction avatar is looking, not camera. - const LLVector3& getAtAxis() const { return mFrameAgent.getAtAxis(); } - const LLVector3& getUpAxis() const { return mFrameAgent.getUpAxis(); } - const LLVector3& getLeftAxis() const { return mFrameAgent.getLeftAxis(); } - LLQuaternion getQuat() const; // Returns the quat that represents the rotation of the agent in the absolute frame -private: - LLVector3d mAgentOriginGlobal; // Origin of agent coords from global coords - LLCoordFrame mFrameAgent; // Agent position and view, agent-region coordinates - - - //-------------------------------------------------------------------- - // Home - //-------------------------------------------------------------------- -public: - void setStartPosition(U32 location_id); // Marks current location as start, sends information to servers - void setHomePosRegion(const U64& region_handle, const LLVector3& pos_region); - bool getHomePosGlobal(LLVector3d* pos_global); - bool isInHomeRegion(); - -private: - void setStartPositionSuccess(const LLSD &result); - - bool mHaveHomePosition; - U64 mHomeRegionHandle; - LLVector3 mHomePosRegion; - - //-------------------------------------------------------------------- - // Parcel - //-------------------------------------------------------------------- -public: - void changeParcels(); // called by LLViewerParcelMgr when we cross a parcel boundary - - // Register a boost callback to be called when the agent changes parcels - typedef boost::function parcel_changed_callback_t; - boost::signals2::connection addParcelChangedCallback(parcel_changed_callback_t); - -private: - static void capabilityReceivedCallback(const LLUUID ®ion_id, LLViewerRegion *regionp); - - typedef boost::signals2::signal parcel_changed_signal_t; - parcel_changed_signal_t mParcelChangedSignal; - - //-------------------------------------------------------------------- - // Region - //-------------------------------------------------------------------- -public: - void setRegion(LLViewerRegion *regionp); - LLViewerRegion *getRegion() const; - LLHost getRegionHost() const; - bool inPrelude(); - - // Capability - std::string getRegionCapability(const std::string &name); // short hand for if (getRegion()) { getRegion()->getCapability(name) } - - /** - * Register a boost callback to be called when the agent changes regions - * Note that if you need to access a capability for the region, you may need to wait - * for the capabilities to be received, since in some cases your region changed - * callback will be called before the capabilities have been received. Your callback - * may need to look something like: - * - * LLViewerRegion* region = gAgent.getRegion(); - * if (region->capabilitiesReceived()) - * { - * useCapability(region); - * } - * else // Need to handle via callback after caps arrive. - * { - * region->setCapabilitiesReceivedCallback(boost::bind(&useCapability,region,_1)); - * // you may or may not want to remove that callback - * } - */ - typedef boost::signals2::signal region_changed_signal_t; - - boost::signals2::connection addRegionChangedCallback(const region_changed_signal_t::slot_type& cb); - void removeRegionChangedCallback(boost::signals2::connection callback); - - - void changeInterestListMode(const std::string & new_mode); - const std::string & getInterestListMode() const { return mInterestListMode; } - -private: - LLViewerRegion *mRegionp; - region_changed_signal_t mRegionChangedSignal; - - std::string mInterestListMode; // How agent wants regions to send updates - - //-------------------------------------------------------------------- - // History - //-------------------------------------------------------------------- -public: - S32 getRegionsVisited() const; - F64 getDistanceTraveled() const; - void setDistanceTraveled(F64 dist) { mDistanceTraveled = dist; } - - const LLVector3d &getLastPositionGlobal() const { return mLastPositionGlobal; } - void setLastPositionGlobal(const LLVector3d &pos) { mLastPositionGlobal = pos; } - -private: - std::set mRegionsVisited; // Stat - what distinct regions has the avatar been to? - F64 mDistanceTraveled; // Stat - how far has the avatar moved? - LLVector3d mLastPositionGlobal; // Used to calculate travel distance - -/** Position - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** ACTIONS - **/ - - //-------------------------------------------------------------------- - // Fidget - //-------------------------------------------------------------------- - // Trigger random fidget animations -public: - void fidget(); - static void stopFidget(); -private: - LLFrameTimer mFidgetTimer; - LLFrameTimer mFocusObjectFadeTimer; - LLFrameTimer mMoveTimer; - F32 mNextFidgetTime; - S32 mCurrentFidget; - - //-------------------------------------------------------------------- - // Fly - //-------------------------------------------------------------------- -public: - bool getFlying() const; - void setFlying(bool fly, bool fail_sound = false); - static void toggleFlying(); - static bool enableFlying(); - bool canFly(); // Does this parcel allow you to fly? - static bool isSitting(); - - //-------------------------------------------------------------------- - // Voice - //-------------------------------------------------------------------- -public: - bool isVoiceConnected() const { return mVoiceConnected; } - void setVoiceConnected(const bool b) { mVoiceConnected = b; } - - static void pressMicrophone(const LLSD& name); - static void releaseMicrophone(const LLSD& name); - static void toggleMicrophone(const LLSD& name); - static bool isMicrophoneOn(const LLSD& sdname); - static bool isActionAllowed(const LLSD& sdname); - -private: - bool mVoiceConnected; - - //-------------------------------------------------------------------- - // Chat - //-------------------------------------------------------------------- -public: - void heardChat(const LLUUID& id); - F32 getTypingTime() { return mTypingTimer.getElapsedTimeF32(); } - LLUUID getLastChatter() const { return mLastChatterID; } - F32 getNearChatRadius() { return mNearChatRadius; } -protected: - void ageChat(); // Helper function to prematurely age chat when agent is moving -private: - LLFrameTimer mChatTimer; - LLUUID mLastChatterID; - F32 mNearChatRadius; - - //-------------------------------------------------------------------- - // Typing - //-------------------------------------------------------------------- -public: - void startTyping(); - void stopTyping(); -public: - // When the agent hasn't typed anything for this duration, it leaves the - // typing state (for both chat and IM). - static const F32 TYPING_TIMEOUT_SECS; -private: - LLFrameTimer mTypingTimer; - - //-------------------------------------------------------------------- - // AFK - //-------------------------------------------------------------------- -public: - void setAFK(); - void clearAFK(); - bool getAFK() const; - static const F32 MIN_AFK_TIME; - - //-------------------------------------------------------------------- - // Run - //-------------------------------------------------------------------- -public: - enum EDoubleTapRunMode - { - DOUBLETAP_NONE, - DOUBLETAP_FORWARD, - DOUBLETAP_BACKWARD, - DOUBLETAP_SLIDELEFT, - DOUBLETAP_SLIDERIGHT - }; - - void setAlwaysRun() { mbAlwaysRun = true; } - void clearAlwaysRun() { mbAlwaysRun = false; } - void setRunning() { mbRunning = true; } - void clearRunning() { mbRunning = false; } - void sendWalkRun(bool running); - bool getAlwaysRun() const { return mbAlwaysRun; } - bool getRunning() const { return mbRunning; } -public: - LLFrameTimer mDoubleTapRunTimer; - EDoubleTapRunMode mDoubleTapRunMode; -private: - bool mbAlwaysRun; // Should the avatar run by default rather than walk? - bool mbRunning; // Is the avatar trying to run right now? - bool mbTeleportKeepsLookAt; // Try to keep look-at after teleport is complete - - //-------------------------------------------------------------------- - // Sit and stand - //-------------------------------------------------------------------- -public: - void standUp(); - /// @brief ground-sit at agent's current position - void sitDown(); - - //-------------------------------------------------------------------- - // Do Not Disturb - //-------------------------------------------------------------------- -public: - void setDoNotDisturb(bool pIsDoNotDisturb); - bool isDoNotDisturb() const; -private: - bool mIsDoNotDisturb; - - //-------------------------------------------------------------------- - // Grab - //-------------------------------------------------------------------- -public: - bool leftButtonGrabbed() const; - bool rotateGrabbed() const; - bool forwardGrabbed() const; - bool backwardGrabbed() const; - bool upGrabbed() const; - bool downGrabbed() const; - - //-------------------------------------------------------------------- - // Controls - //-------------------------------------------------------------------- -public: - U32 getControlFlags(); - void setControlFlags(U32 mask); // Performs bitwise mControlFlags |= mask - void clearControlFlags(U32 mask); // Performs bitwise mControlFlags &= ~mask - bool controlFlagsDirty() const; - void enableControlFlagReset(); - void resetControlFlags(); - bool anyControlGrabbed() const; // True iff a script has taken over a control - bool isControlGrabbed(S32 control_index) const; - // Send message to simulator to force grabbed controls to be - // released, in case of a poorly written script. - void forceReleaseControls(); - void setFlagsDirty() { mbFlagsDirty = true; } - -private: - S32 mControlsTakenCount[TOTAL_CONTROLS]; - S32 mControlsTakenPassedOnCount[TOTAL_CONTROLS]; - U32 mControlFlags; // Replacement for the mFooKey's - bool mbFlagsDirty; - bool mbFlagsNeedReset; // ! HACK ! For preventing incorrect flags sent when crossing region boundaries - - //-------------------------------------------------------------------- - // Animations - //-------------------------------------------------------------------- -public: - void stopCurrentAnimations(); - void requestStopMotion(LLMotion* motion); - void onAnimStop(const LLUUID& id); - void sendAnimationRequests(const std::vector &anim_ids, EAnimRequest request); - void sendAnimationRequest(const LLUUID &anim_id, EAnimRequest request); - void sendAnimationStateReset(); - void sendRevokePermissions(const LLUUID & target, U32 permissions); - - void endAnimationUpdateUI(); - void unpauseAnimation() { mPauseRequest = NULL; } - bool getCustomAnim() const { return mCustomAnim; } - void setCustomAnim(bool anim) { mCustomAnim = anim; } - - typedef boost::signals2::signal camera_signal_t; - boost::signals2::connection setMouselookModeInCallback( const camera_signal_t::slot_type& cb ); - boost::signals2::connection setMouselookModeOutCallback( const camera_signal_t::slot_type& cb ); - -private: - camera_signal_t* mMouselookModeInSignal; - camera_signal_t* mMouselookModeOutSignal; - bool mCustomAnim; // Current animation is ANIM_AGENT_CUSTOMIZE ? - LLPointer mPauseRequest; - bool mViewsPushed; // Keep track of whether or not we have pushed views - -/** Animation - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** MOVEMENT - **/ - - //-------------------------------------------------------------------- - // Movement from user input - //-------------------------------------------------------------------- - // All set the appropriate animation flags. - // All turn off autopilot and make sure the camera is behind the avatar. - // Direction is either positive, zero, or negative -public: - void moveAt(S32 direction, bool reset_view = true); - void moveAtNudge(S32 direction); - void moveLeft(S32 direction); - void moveLeftNudge(S32 direction); - void moveUp(S32 direction); - void moveYaw(F32 mag, bool reset_view = true); - void movePitch(F32 mag); - - bool isMovementLocked() const { return mMovementKeysLocked; } - void setMovementLocked(bool set_locked) { mMovementKeysLocked = set_locked; } - - //-------------------------------------------------------------------- - // Move the avatar's frame - //-------------------------------------------------------------------- -public: - void rotate(F32 angle, const LLVector3 &axis); - void rotate(F32 angle, F32 x, F32 y, F32 z); - void rotate(const LLMatrix3 &matrix); - void rotate(const LLQuaternion &quaternion); - void pitch(F32 angle); - void roll(F32 angle); - void yaw(F32 angle); - LLVector3 getReferenceUpVector(); - - //-------------------------------------------------------------------- - // Autopilot - //-------------------------------------------------------------------- -public: - bool getAutoPilot() const { return mAutoPilot; } - LLVector3d getAutoPilotTargetGlobal() const { return mAutoPilotTargetGlobal; } - LLUUID getAutoPilotLeaderID() const { return mLeaderID; } - F32 getAutoPilotStopDistance() const { return mAutoPilotStopDistance; } - F32 getAutoPilotTargetDist() const { return mAutoPilotTargetDist; } - bool getAutoPilotUseRotation() const { return mAutoPilotUseRotation; } - LLVector3 getAutoPilotTargetFacing() const { return mAutoPilotTargetFacing; } - F32 getAutoPilotRotationThreshold() const { return mAutoPilotRotationThreshold; } - std::string getAutoPilotBehaviorName() const { return mAutoPilotBehaviorName; } - - void startAutoPilotGlobal(const LLVector3d &pos_global, - const std::string& behavior_name = std::string(), - const LLQuaternion *target_rotation = NULL, - void (*finish_callback)(bool, void *) = NULL, void *callback_data = NULL, - F32 stop_distance = 0.f, F32 rotation_threshold = 0.03f, - bool allow_flying = true); - void startFollowPilot(const LLUUID &leader_id, bool allow_flying = true, F32 stop_distance = 0.5f); - void stopAutoPilot(bool user_cancel = false); - void setAutoPilotTargetGlobal(const LLVector3d &target_global); - void autoPilot(F32 *delta_yaw); // Autopilot walking action, angles in radians - void renderAutoPilotTarget(); -private: - bool mAutoPilot; - bool mAutoPilotFlyOnStop; - bool mAutoPilotAllowFlying; - LLVector3d mAutoPilotTargetGlobal; - F32 mAutoPilotStopDistance; - bool mAutoPilotUseRotation; - LLVector3 mAutoPilotTargetFacing; - F32 mAutoPilotTargetDist; - S32 mAutoPilotNoProgressFrameCount; - F32 mAutoPilotRotationThreshold; - std::string mAutoPilotBehaviorName; - void (*mAutoPilotFinishedCallback)(bool, void *); - void* mAutoPilotCallbackData; - LLUUID mLeaderID; - bool mMovementKeysLocked; - -/** Movement - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** TELEPORT - **/ - -public: - enum ETeleportState - { - TELEPORT_NONE = 0, // No teleport in progress - TELEPORT_START = 1, // Transition to REQUESTED. Viewer has sent a TeleportRequest to the source simulator - TELEPORT_REQUESTED = 2, // Waiting for source simulator to respond - TELEPORT_MOVING = 3, // Viewer has received destination location from source simulator - TELEPORT_START_ARRIVAL = 4, // Transition to ARRIVING. Viewer has received avatar update, etc., from destination simulator - TELEPORT_ARRIVING = 5, // Make the user wait while content "pre-caches" - TELEPORT_LOCAL = 6, // Teleporting in-sim without showing the progress screen - TELEPORT_PENDING = 7 - }; - - static std::map sTeleportStateName; - static const std::string& teleportStateName(S32); - const std::string& getTeleportStateName() const; - -public: - static void parseTeleportMessages(const std::string& xml_filename); - const void getTeleportSourceSLURL(LLSLURL& slurl) const; -public: - // ! TODO ! Define ERROR and PROGRESS enums here instead of exposing the mappings. - static std::map sTeleportErrorMessages; - static std::map sTeleportProgressMessages; -private: - LLSLURL * mTeleportSourceSLURL; // SLURL where last TP began - - //-------------------------------------------------------------------- - // Teleport Actions - //-------------------------------------------------------------------- -public: - void teleportViaLandmark(const LLUUID& landmark_id); // Teleport to a landmark - void teleportHome() { teleportViaLandmark(LLUUID::null); } // Go home - void teleportViaLure(const LLUUID& lure_id, bool godlike); // To an invited location - void teleportViaLocation(const LLVector3d& pos_global); // To a global location - this will probably need to be deprecated - void teleportViaLocationLookAt(const LLVector3d& pos_global);// To a global location, preserving camera rotation - void teleportCancel(); // May or may not be allowed by server - void restoreCanceledTeleportRequest(); - bool canRestoreCanceledTeleport() { return mTeleportCanceled != NULL; } - bool getTeleportKeepsLookAt() { return mbTeleportKeepsLookAt; } // Whether look-at reset after teleport -protected: - bool teleportCore(bool is_local = false); // Stuff for all teleports; returns true if the teleport can proceed - - //-------------------------------------------------------------------- - // Teleport State - //-------------------------------------------------------------------- - -public: - bool hasRestartableFailedTeleportRequest(); - void restartFailedTeleportRequest(); - void clearTeleportRequest(); - void setMaturityRatingChangeDuringTeleport(U8 pMaturityRatingChange); - void sheduleTeleportIM(); - -private: - - - friend class LLTeleportRequest; - friend class LLTeleportRequestViaLandmark; - friend class LLTeleportRequestViaLure; - friend class LLTeleportRequestViaLocation; - friend class LLTeleportRequestViaLocationLookAt; - - LLTeleportRequestPtr mTeleportRequest; - LLTeleportRequestPtr mTeleportCanceled; - boost::signals2::connection mTeleportFinishedSlot; - boost::signals2::connection mTeleportFailedSlot; - - bool mIsMaturityRatingChangingDuringTeleport; - bool mTPNeedsNeabyChatSeparator; - U8 mMaturityRatingChange; - - bool hasPendingTeleportRequest(); - void startTeleportRequest(); - - void teleportRequest(const U64& region_handle, - const LLVector3& pos_local, // Go to a named location home - bool look_at_from_camera = false); - void doTeleportViaLandmark(const LLUUID& landmark_id); // Teleport to a landmark - void doTeleportViaLure(const LLUUID& lure_id, bool godlike); // To an invited location - void doTeleportViaLocation(const LLVector3d& pos_global); // To a global location - this will probably need to be deprecated - void doTeleportViaLocationLookAt(const LLVector3d& pos_global);// To a global location, preserving camera rotation - - void handleTeleportFinished(); - void handleTeleportFailed(); - - static void addTPNearbyChatSeparator(); - static void onCapabilitiesReceivedAfterTeleport(); - - //-------------------------------------------------------------------- - // Teleport State - //-------------------------------------------------------------------- -public: - ETeleportState getTeleportState() const; - void setTeleportState(ETeleportState state); -private: - ETeleportState mTeleportState; - - //-------------------------------------------------------------------- - // Teleport Message - //-------------------------------------------------------------------- -public: - const std::string& getTeleportMessage() const { return mTeleportMessage; } - void setTeleportMessage(const std::string& message) { mTeleportMessage = message; } -private: - std::string mTeleportMessage; - -/** Teleport - ** ** - *******************************************************************************/ - - // Build -public: - bool canEditParcel() const { return mCanEditParcel; } -private: - static void setCanEditParcel(); - bool mCanEditParcel; - - - -/******************************************************************************** - ** ** - ** ACCESS - **/ - -public: - // Checks if agent can modify an object based on the permissions and the agent's proxy status. - bool isGrantedProxy(const LLPermissions& perm); - bool allowOperation(PermissionBit op, - const LLPermissions& perm, - U64 group_proxy_power = 0, - U8 god_minimum = GOD_MAINTENANCE); - const LLAgentAccess& getAgentAccess(); - bool canManageEstate() const; - bool getAdminOverride() const; -private: - LLAgentAccess * mAgentAccess; - - //-------------------------------------------------------------------- - // God - //-------------------------------------------------------------------- -public: - bool isGodlike() const; - bool isGodlikeWithoutAdminMenuFakery() const; - U8 getGodLevel() const; - void setAdminOverride(bool b); - void setGodLevel(U8 god_level); - void requestEnterGodMode(); - void requestLeaveGodMode(); - - typedef boost::function god_level_change_callback_t; - typedef boost::signals2::signal god_level_change_signal_t; - typedef boost::signals2::connection god_level_change_slot_t; - - god_level_change_slot_t registerGodLevelChanageListener(god_level_change_callback_t pGodLevelChangeCallback); - -private: - god_level_change_signal_t mGodLevelChangeSignal; - - - //-------------------------------------------------------------------- - // Maturity - //-------------------------------------------------------------------- -public: - // Note: this is a prime candidate for pulling out into a Maturity class. - // Rather than just expose the preference setting, we're going to actually - // expose what the client code cares about -- what the user should see - // based on a combination of the is* and prefers* flags, combined with god bit. - bool wantsPGOnly() const; - bool canAccessMature() const; - bool canAccessAdult() const; - bool canAccessMaturityInRegion( U64 region_handle ) const; - bool canAccessMaturityAtGlobal( LLVector3d pos_global ) const; - bool prefersPG() const; - bool prefersMature() const; - bool prefersAdult() const; - bool isTeen() const; - bool isMature() const; - bool isAdult() const; - void setMaturity(char text); - static int convertTextToMaturity(char text); - -private: - bool mIsDoSendMaturityPreferenceToServer; - unsigned int mMaturityPreferenceRequestId; - unsigned int mMaturityPreferenceResponseId; - unsigned int mMaturityPreferenceNumRetries; - U8 mLastKnownRequestMaturity; - U8 mLastKnownResponseMaturity; - LLCore::HttpRequest::policy_t mHttpPolicy; - - bool isMaturityPreferenceSyncedWithServer() const; - void sendMaturityPreferenceToServer(U8 pPreferredMaturity); - void processMaturityPreferenceFromServer(const LLSD &result, U8 perferredMaturity); - - void handlePreferredMaturityResult(U8 pServerMaturity); - void handlePreferredMaturityError(); - void reportPreferredMaturitySuccess(); - void reportPreferredMaturityError(); - - // Maturity callbacks for PreferredMaturity control variable - void handleMaturity(const LLSD &pNewValue); - bool validateMaturity(const LLSD& newvalue); - - -/** Access - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** RENDERING - **/ - -public: - LLQuaternion getHeadRotation(); - bool needsRenderAvatar(); // true when camera mode is such that your own avatar should draw - bool needsRenderHead(); - void setShowAvatar(bool show) { mShowAvatar = show; } - bool getShowAvatar() const { return mShowAvatar; } - -private: - bool mShowAvatar; // Should we render the avatar? - - //-------------------------------------------------------------------- - // Rendering state bitmap helpers - //-------------------------------------------------------------------- -public: - void setRenderState(U8 newstate); - void clearRenderState(U8 clearstate); - U8 getRenderState(); -private: - U8 mRenderState; // Current behavior state of agent - - //-------------------------------------------------------------------- - // HUD - //-------------------------------------------------------------------- -public: - const LLColor4 &getEffectColor(); - void setEffectColor(const LLColor4 &color); -private: - LLUIColor * mEffectColor; - -/** Rendering - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** GROUPS - **/ - -public: - const LLUUID &getGroupID() const { return mGroupID; } - // Get group information by group_id, or false if not in group. - bool getGroupData(const LLUUID& group_id, LLGroupData& data) const; - // Get just the agent's contribution to the given group. - S32 getGroupContribution(const LLUUID& group_id) const; - // Update internal datastructures and update the server. - bool setGroupContribution(const LLUUID& group_id, S32 contribution); - bool setUserGroupFlags(const LLUUID& group_id, bool accept_notices, bool list_in_profile); - const std::string &getGroupName() const { return mGroupName; } - bool canJoinGroups() const; -private: - std::string mGroupName; - LLUUID mGroupID; - - //-------------------------------------------------------------------- - // Group Membership - //-------------------------------------------------------------------- -public: - // Checks against all groups in the entire agent group list. - bool isInGroup(const LLUUID& group_id, bool ingnore_God_mod = false) const; -protected: - // Only used for building titles. - bool isGroupMember() const { return !mGroupID.isNull(); } -public: - std::vector mGroups; - - //-------------------------------------------------------------------- - // Group Title - //-------------------------------------------------------------------- -public: - void setHideGroupTitle(bool hide) { mHideGroupTitle = hide; } - bool isGroupTitleHidden() const { return mHideGroupTitle; } -private: - std::string mGroupTitle; // Honorific, like "Sir" - bool mHideGroupTitle; - - //-------------------------------------------------------------------- - // Group Powers - //-------------------------------------------------------------------- -public: - bool hasPowerInGroup(const LLUUID& group_id, U64 power) const; - bool hasPowerInActiveGroup(const U64 power) const; - U64 getPowerInGroup(const LLUUID& group_id) const; - U64 mGroupPowers; - - //-------------------------------------------------------------------- - // Friends - //-------------------------------------------------------------------- -public: - void observeFriends(); - void friendsChanged(); -private: - LLFriendObserver* mFriendObserver; - std::set mProxyForAgents; - -/** Groups - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** MESSAGING - **/ - - //-------------------------------------------------------------------- - // Send - //-------------------------------------------------------------------- -public: - void sendMessage(); // Send message to this agent's region - void sendReliableMessage(); - void sendAgentDataUpdateRequest(); - void sendAgentUserInfoRequest(); - -// IM to Email and Online visibility - void sendAgentUpdateUserInfo(const std::string& directory_visibility); - -private: - void requestAgentUserInfoCoro(std::string capurl); - void updateAgentUserInfoCoro(std::string capurl, std::string directory_visibility); - // DEPRECATED: may be removed when User Info cap propagates - void sendAgentUserInfoRequestMessage(); - void sendAgentUpdateUserInfoMessage(const std::string& directory_visibility); - - //-------------------------------------------------------------------- - // Receive - //-------------------------------------------------------------------- -public: - static void processAgentDataUpdate(LLMessageSystem *msg, void **); - static void processAgentGroupDataUpdate(LLMessageSystem *msg, void **); - static void processAgentDropGroup(LLMessageSystem *msg, void **); - static void processScriptControlChange(LLMessageSystem *msg, void **); - -/** Messaging - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** UTILITY - **/ -public: - typedef LLCoreHttpUtil::HttpCoroutineAdapter::completionCallback_t httpCallback_t; - - /// Utilities for allowing the the agent sub managers to post and get via - /// HTTP using the agent's policy settings and headers. - bool requestPostCapability(const std::string &capName, LLSD &postData, httpCallback_t cbSuccess = NULL, httpCallback_t cbFailure = NULL); - bool requestGetCapability(const std::string &capName, httpCallback_t cbSuccess = NULL, httpCallback_t cbFailure = NULL); - - LLCore::HttpRequest::policy_t getAgentPolicy() const { return mHttpPolicy; } - -/** Utility - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** DEBUGGING - **/ - -public: - static void dumpGroupInfo(); - static void clearVisualParams(void *); - friend std::ostream& operator<<(std::ostream &s, const LLAgent &sphere); - -/** Debugging - ** ** - *******************************************************************************/ - -}; - -extern LLAgent gAgent; - -inline bool operator==(const LLGroupData &a, const LLGroupData &b) -{ - return (a.mID == b.mID); -} - -#endif +/** + * @file llagent.h + * @brief LLAgent class header file + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLAGENT_H +#define LL_LLAGENT_H + +#include "indra_constants.h" +#include "llevent.h" // LLObservable base class +#include "llagentdata.h" // gAgentID, gAgentSessionID +#include "llcharacter.h" +#include "llcoordframe.h" // for mFrameAgent +#include "llavatarappearancedefines.h" +#include "llpermissionsflags.h" +#include "llevents.h" +#include "v3dmath.h" +#include "httprequest.h" +#include "llcorehttputil.h" + +#include +#include +#include + +extern const bool ANIMATE; +extern const U8 AGENT_STATE_TYPING; // Typing indication +extern const U8 AGENT_STATE_EDITING; // Set when agent has objects selected + +class LLViewerRegion; +class LLMotion; +class LLMessageSystem; +class LLPermissions; +class LLHost; +class LLFriendObserver; +class LLAgentDropGroupViewerNode; +class LLAgentAccess; +class LLSLURL; +class LLUIColor; +class LLTeleportRequest; + + + +typedef std::shared_ptr LLTeleportRequestPtr; + +//-------------------------------------------------------------------- +// Types +//-------------------------------------------------------------------- + +enum EAnimRequest +{ + ANIM_REQUEST_START, + ANIM_REQUEST_STOP +}; + +struct LLGroupData +{ + LLUUID mID; + LLUUID mInsigniaID; + U64 mPowers; + bool mAcceptNotices; + bool mListInProfile; + S32 mContribution; + std::string mName; +}; + +class LLAgentListener; + +//------------------------------------------------------------------------ +// LLAgent +//------------------------------------------------------------------------ +class LLAgent : public LLOldEvents::LLObservable +{ + LOG_CLASS(LLAgent); + +public: + friend class LLAgentDropGroupViewerNode; + +/******************************************************************************** + ** ** + ** INITIALIZATION + **/ + + //-------------------------------------------------------------------- + // Constructors / Destructors + //-------------------------------------------------------------------- +public: + LLAgent(); + virtual ~LLAgent(); + void init(); + void cleanup(); + +private: + + //-------------------------------------------------------------------- + // Login + //-------------------------------------------------------------------- +public: + void onAppFocusGained(); + void setFirstLogin(bool b); + // Return true if the database reported this login as the first for this particular user. + bool isFirstLogin() const { return mFirstLogin; } + bool isInitialized() const { return mInitialized; } + + void setFeatureVersion(S32 version, S32 flags); + S32 getFeatureVersion(); + void getFeatureVersionAndFlags(S32 &version, S32 &flags); + void showLatestFeatureNotification(const std::string key); +public: + std::string mMOTD; // Message of the day +private: + bool mInitialized; + bool mFirstLogin; + std::shared_ptr mListener; + + //-------------------------------------------------------------------- + // Session + //-------------------------------------------------------------------- +public: + const LLUUID& getID() const { return gAgentID; } + const LLUUID& getSessionID() const { return gAgentSessionID; } + // Note: NEVER send this value in the clear or over any weakly + // encrypted channel (such as simple XOR masking). If you are unsure + // ask Aaron or MarkL. + const LLUUID& getSecureSessionID() const { return mSecureSessionID; } +public: + LLUUID mSecureSessionID; // Secure token for this login session + +/** Initialization + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** IDENTITY + **/ + + //-------------------------------------------------------------------- + // Name + //-------------------------------------------------------------------- +public: + //*TODO remove, is not used as of August 20, 2009 + void buildFullnameAndTitle(std::string &name) const; + + //-------------------------------------------------------------------- + // Gender + //-------------------------------------------------------------------- +public: + // On the very first login, outfit needs to be chosen by some + // mechanism, usually by loading the requested initial outfit. We + // don't render the avatar until the choice is made. + bool isOutfitChosen() const { return mOutfitChosen; } + void setOutfitChosen(bool b) { mOutfitChosen = b; } +private: + bool mOutfitChosen; + +/** Identity + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** POSITION + **/ + + //-------------------------------------------------------------------- + // Position + //-------------------------------------------------------------------- +public: + typedef boost::signals2::signal position_signal_t; + + LLVector3 getPosAgentFromGlobal(const LLVector3d &pos_global) const; + LLVector3d getPosGlobalFromAgent(const LLVector3 &pos_agent) const; + const LLVector3d &getPositionGlobal() const; + const LLVector3 &getPositionAgent(); + // Call once per frame to update position, angles (radians). + void updateAgentPosition(const F32 dt, const F32 yaw, const S32 mouse_x, const S32 mouse_y); + void setPositionAgent(const LLVector3 ¢er); + + boost::signals2::connection whenPositionChanged(position_signal_t::slot_type fn); + +protected: + void propagate(const F32 dt); // ! BUG ! Should roll into updateAgentPosition +private: + mutable LLVector3d mPositionGlobal; + + position_signal_t mOnPositionChanged; + LLVector3d mLastTestGlobal; + + //-------------------------------------------------------------------- + // Velocity + //-------------------------------------------------------------------- +public: + LLVector3 getVelocity() const; + F32 getVelocityZ() const { return getVelocity().mV[VZ]; } // ! HACK ! + + //-------------------------------------------------------------------- + // Coordinate System + //-------------------------------------------------------------------- +public: + const LLCoordFrame& getFrameAgent() const { return mFrameAgent; } + void initOriginGlobal(const LLVector3d &origin_global); // Only to be used in ONE place + void resetAxes(); + void resetAxes(const LLVector3 &look_at); // Makes reasonable left and up + // The following three get*Axis functions return direction avatar is looking, not camera. + const LLVector3& getAtAxis() const { return mFrameAgent.getAtAxis(); } + const LLVector3& getUpAxis() const { return mFrameAgent.getUpAxis(); } + const LLVector3& getLeftAxis() const { return mFrameAgent.getLeftAxis(); } + LLQuaternion getQuat() const; // Returns the quat that represents the rotation of the agent in the absolute frame +private: + LLVector3d mAgentOriginGlobal; // Origin of agent coords from global coords + LLCoordFrame mFrameAgent; // Agent position and view, agent-region coordinates + + + //-------------------------------------------------------------------- + // Home + //-------------------------------------------------------------------- +public: + void setStartPosition(U32 location_id); // Marks current location as start, sends information to servers + void setHomePosRegion(const U64& region_handle, const LLVector3& pos_region); + bool getHomePosGlobal(LLVector3d* pos_global); + bool isInHomeRegion(); + +private: + void setStartPositionSuccess(const LLSD &result); + + bool mHaveHomePosition; + U64 mHomeRegionHandle; + LLVector3 mHomePosRegion; + + //-------------------------------------------------------------------- + // Parcel + //-------------------------------------------------------------------- +public: + void changeParcels(); // called by LLViewerParcelMgr when we cross a parcel boundary + + // Register a boost callback to be called when the agent changes parcels + typedef boost::function parcel_changed_callback_t; + boost::signals2::connection addParcelChangedCallback(parcel_changed_callback_t); + +private: + static void capabilityReceivedCallback(const LLUUID ®ion_id, LLViewerRegion *regionp); + + typedef boost::signals2::signal parcel_changed_signal_t; + parcel_changed_signal_t mParcelChangedSignal; + + //-------------------------------------------------------------------- + // Region + //-------------------------------------------------------------------- +public: + void setRegion(LLViewerRegion *regionp); + LLViewerRegion *getRegion() const; + LLHost getRegionHost() const; + bool inPrelude(); + + // Capability + std::string getRegionCapability(const std::string &name); // short hand for if (getRegion()) { getRegion()->getCapability(name) } + + /** + * Register a boost callback to be called when the agent changes regions + * Note that if you need to access a capability for the region, you may need to wait + * for the capabilities to be received, since in some cases your region changed + * callback will be called before the capabilities have been received. Your callback + * may need to look something like: + * + * LLViewerRegion* region = gAgent.getRegion(); + * if (region->capabilitiesReceived()) + * { + * useCapability(region); + * } + * else // Need to handle via callback after caps arrive. + * { + * region->setCapabilitiesReceivedCallback(boost::bind(&useCapability,region,_1)); + * // you may or may not want to remove that callback + * } + */ + typedef boost::signals2::signal region_changed_signal_t; + + boost::signals2::connection addRegionChangedCallback(const region_changed_signal_t::slot_type& cb); + void removeRegionChangedCallback(boost::signals2::connection callback); + + + void changeInterestListMode(const std::string & new_mode); + const std::string & getInterestListMode() const { return mInterestListMode; } + +private: + LLViewerRegion *mRegionp; + region_changed_signal_t mRegionChangedSignal; + + std::string mInterestListMode; // How agent wants regions to send updates + + //-------------------------------------------------------------------- + // History + //-------------------------------------------------------------------- +public: + S32 getRegionsVisited() const; + F64 getDistanceTraveled() const; + void setDistanceTraveled(F64 dist) { mDistanceTraveled = dist; } + + const LLVector3d &getLastPositionGlobal() const { return mLastPositionGlobal; } + void setLastPositionGlobal(const LLVector3d &pos) { mLastPositionGlobal = pos; } + +private: + std::set mRegionsVisited; // Stat - what distinct regions has the avatar been to? + F64 mDistanceTraveled; // Stat - how far has the avatar moved? + LLVector3d mLastPositionGlobal; // Used to calculate travel distance + +/** Position + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** ACTIONS + **/ + + //-------------------------------------------------------------------- + // Fidget + //-------------------------------------------------------------------- + // Trigger random fidget animations +public: + void fidget(); + static void stopFidget(); +private: + LLFrameTimer mFidgetTimer; + LLFrameTimer mFocusObjectFadeTimer; + LLFrameTimer mMoveTimer; + F32 mNextFidgetTime; + S32 mCurrentFidget; + + //-------------------------------------------------------------------- + // Fly + //-------------------------------------------------------------------- +public: + bool getFlying() const; + void setFlying(bool fly, bool fail_sound = false); + static void toggleFlying(); + static bool enableFlying(); + bool canFly(); // Does this parcel allow you to fly? + static bool isSitting(); + + //-------------------------------------------------------------------- + // Voice + //-------------------------------------------------------------------- +public: + bool isVoiceConnected() const { return mVoiceConnected; } + void setVoiceConnected(const bool b) { mVoiceConnected = b; } + + static void pressMicrophone(const LLSD& name); + static void releaseMicrophone(const LLSD& name); + static void toggleMicrophone(const LLSD& name); + static bool isMicrophoneOn(const LLSD& sdname); + static bool isActionAllowed(const LLSD& sdname); + +private: + bool mVoiceConnected; + + //-------------------------------------------------------------------- + // Chat + //-------------------------------------------------------------------- +public: + void heardChat(const LLUUID& id); + F32 getTypingTime() { return mTypingTimer.getElapsedTimeF32(); } + LLUUID getLastChatter() const { return mLastChatterID; } + F32 getNearChatRadius() { return mNearChatRadius; } +protected: + void ageChat(); // Helper function to prematurely age chat when agent is moving +private: + LLFrameTimer mChatTimer; + LLUUID mLastChatterID; + F32 mNearChatRadius; + + //-------------------------------------------------------------------- + // Typing + //-------------------------------------------------------------------- +public: + void startTyping(); + void stopTyping(); +public: + // When the agent hasn't typed anything for this duration, it leaves the + // typing state (for both chat and IM). + static const F32 TYPING_TIMEOUT_SECS; +private: + LLFrameTimer mTypingTimer; + + //-------------------------------------------------------------------- + // AFK + //-------------------------------------------------------------------- +public: + void setAFK(); + void clearAFK(); + bool getAFK() const; + static const F32 MIN_AFK_TIME; + + //-------------------------------------------------------------------- + // Run + //-------------------------------------------------------------------- +public: + enum EDoubleTapRunMode + { + DOUBLETAP_NONE, + DOUBLETAP_FORWARD, + DOUBLETAP_BACKWARD, + DOUBLETAP_SLIDELEFT, + DOUBLETAP_SLIDERIGHT + }; + + void setAlwaysRun() { mbAlwaysRun = true; } + void clearAlwaysRun() { mbAlwaysRun = false; } + void setRunning() { mbRunning = true; } + void clearRunning() { mbRunning = false; } + void sendWalkRun(bool running); + bool getAlwaysRun() const { return mbAlwaysRun; } + bool getRunning() const { return mbRunning; } +public: + LLFrameTimer mDoubleTapRunTimer; + EDoubleTapRunMode mDoubleTapRunMode; +private: + bool mbAlwaysRun; // Should the avatar run by default rather than walk? + bool mbRunning; // Is the avatar trying to run right now? + bool mbTeleportKeepsLookAt; // Try to keep look-at after teleport is complete + + //-------------------------------------------------------------------- + // Sit and stand + //-------------------------------------------------------------------- +public: + void standUp(); + /// @brief ground-sit at agent's current position + void sitDown(); + + //-------------------------------------------------------------------- + // Do Not Disturb + //-------------------------------------------------------------------- +public: + void setDoNotDisturb(bool pIsDoNotDisturb); + bool isDoNotDisturb() const; +private: + bool mIsDoNotDisturb; + + //-------------------------------------------------------------------- + // Grab + //-------------------------------------------------------------------- +public: + bool leftButtonGrabbed() const; + bool rotateGrabbed() const; + bool forwardGrabbed() const; + bool backwardGrabbed() const; + bool upGrabbed() const; + bool downGrabbed() const; + + //-------------------------------------------------------------------- + // Controls + //-------------------------------------------------------------------- +public: + U32 getControlFlags(); + void setControlFlags(U32 mask); // Performs bitwise mControlFlags |= mask + void clearControlFlags(U32 mask); // Performs bitwise mControlFlags &= ~mask + bool controlFlagsDirty() const; + void enableControlFlagReset(); + void resetControlFlags(); + bool anyControlGrabbed() const; // True iff a script has taken over a control + bool isControlGrabbed(S32 control_index) const; + // Send message to simulator to force grabbed controls to be + // released, in case of a poorly written script. + void forceReleaseControls(); + void setFlagsDirty() { mbFlagsDirty = true; } + +private: + S32 mControlsTakenCount[TOTAL_CONTROLS]; + S32 mControlsTakenPassedOnCount[TOTAL_CONTROLS]; + U32 mControlFlags; // Replacement for the mFooKey's + bool mbFlagsDirty; + bool mbFlagsNeedReset; // ! HACK ! For preventing incorrect flags sent when crossing region boundaries + + //-------------------------------------------------------------------- + // Animations + //-------------------------------------------------------------------- +public: + void stopCurrentAnimations(); + void requestStopMotion(LLMotion* motion); + void onAnimStop(const LLUUID& id); + void sendAnimationRequests(const std::vector &anim_ids, EAnimRequest request); + void sendAnimationRequest(const LLUUID &anim_id, EAnimRequest request); + void sendAnimationStateReset(); + void sendRevokePermissions(const LLUUID & target, U32 permissions); + + void endAnimationUpdateUI(); + void unpauseAnimation() { mPauseRequest = NULL; } + bool getCustomAnim() const { return mCustomAnim; } + void setCustomAnim(bool anim) { mCustomAnim = anim; } + + typedef boost::signals2::signal camera_signal_t; + boost::signals2::connection setMouselookModeInCallback( const camera_signal_t::slot_type& cb ); + boost::signals2::connection setMouselookModeOutCallback( const camera_signal_t::slot_type& cb ); + +private: + camera_signal_t* mMouselookModeInSignal; + camera_signal_t* mMouselookModeOutSignal; + bool mCustomAnim; // Current animation is ANIM_AGENT_CUSTOMIZE ? + LLPointer mPauseRequest; + bool mViewsPushed; // Keep track of whether or not we have pushed views + +/** Animation + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** MOVEMENT + **/ + + //-------------------------------------------------------------------- + // Movement from user input + //-------------------------------------------------------------------- + // All set the appropriate animation flags. + // All turn off autopilot and make sure the camera is behind the avatar. + // Direction is either positive, zero, or negative +public: + void moveAt(S32 direction, bool reset_view = true); + void moveAtNudge(S32 direction); + void moveLeft(S32 direction); + void moveLeftNudge(S32 direction); + void moveUp(S32 direction); + void moveYaw(F32 mag, bool reset_view = true); + void movePitch(F32 mag); + + bool isMovementLocked() const { return mMovementKeysLocked; } + void setMovementLocked(bool set_locked) { mMovementKeysLocked = set_locked; } + + //-------------------------------------------------------------------- + // Move the avatar's frame + //-------------------------------------------------------------------- +public: + void rotate(F32 angle, const LLVector3 &axis); + void rotate(F32 angle, F32 x, F32 y, F32 z); + void rotate(const LLMatrix3 &matrix); + void rotate(const LLQuaternion &quaternion); + void pitch(F32 angle); + void roll(F32 angle); + void yaw(F32 angle); + LLVector3 getReferenceUpVector(); + + //-------------------------------------------------------------------- + // Autopilot + //-------------------------------------------------------------------- +public: + bool getAutoPilot() const { return mAutoPilot; } + LLVector3d getAutoPilotTargetGlobal() const { return mAutoPilotTargetGlobal; } + LLUUID getAutoPilotLeaderID() const { return mLeaderID; } + F32 getAutoPilotStopDistance() const { return mAutoPilotStopDistance; } + F32 getAutoPilotTargetDist() const { return mAutoPilotTargetDist; } + bool getAutoPilotUseRotation() const { return mAutoPilotUseRotation; } + LLVector3 getAutoPilotTargetFacing() const { return mAutoPilotTargetFacing; } + F32 getAutoPilotRotationThreshold() const { return mAutoPilotRotationThreshold; } + std::string getAutoPilotBehaviorName() const { return mAutoPilotBehaviorName; } + + void startAutoPilotGlobal(const LLVector3d &pos_global, + const std::string& behavior_name = std::string(), + const LLQuaternion *target_rotation = NULL, + void (*finish_callback)(bool, void *) = NULL, void *callback_data = NULL, + F32 stop_distance = 0.f, F32 rotation_threshold = 0.03f, + bool allow_flying = true); + void startFollowPilot(const LLUUID &leader_id, bool allow_flying = true, F32 stop_distance = 0.5f); + void stopAutoPilot(bool user_cancel = false); + void setAutoPilotTargetGlobal(const LLVector3d &target_global); + void autoPilot(F32 *delta_yaw); // Autopilot walking action, angles in radians + void renderAutoPilotTarget(); +private: + bool mAutoPilot; + bool mAutoPilotFlyOnStop; + bool mAutoPilotAllowFlying; + LLVector3d mAutoPilotTargetGlobal; + F32 mAutoPilotStopDistance; + bool mAutoPilotUseRotation; + LLVector3 mAutoPilotTargetFacing; + F32 mAutoPilotTargetDist; + S32 mAutoPilotNoProgressFrameCount; + F32 mAutoPilotRotationThreshold; + std::string mAutoPilotBehaviorName; + void (*mAutoPilotFinishedCallback)(bool, void *); + void* mAutoPilotCallbackData; + LLUUID mLeaderID; + bool mMovementKeysLocked; + +/** Movement + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** TELEPORT + **/ + +public: + enum ETeleportState + { + TELEPORT_NONE = 0, // No teleport in progress + TELEPORT_START = 1, // Transition to REQUESTED. Viewer has sent a TeleportRequest to the source simulator + TELEPORT_REQUESTED = 2, // Waiting for source simulator to respond + TELEPORT_MOVING = 3, // Viewer has received destination location from source simulator + TELEPORT_START_ARRIVAL = 4, // Transition to ARRIVING. Viewer has received avatar update, etc., from destination simulator + TELEPORT_ARRIVING = 5, // Make the user wait while content "pre-caches" + TELEPORT_LOCAL = 6, // Teleporting in-sim without showing the progress screen + TELEPORT_PENDING = 7 + }; + + static std::map sTeleportStateName; + static const std::string& teleportStateName(S32); + const std::string& getTeleportStateName() const; + +public: + static void parseTeleportMessages(const std::string& xml_filename); + const void getTeleportSourceSLURL(LLSLURL& slurl) const; +public: + // ! TODO ! Define ERROR and PROGRESS enums here instead of exposing the mappings. + static std::map sTeleportErrorMessages; + static std::map sTeleportProgressMessages; +private: + LLSLURL * mTeleportSourceSLURL; // SLURL where last TP began + + //-------------------------------------------------------------------- + // Teleport Actions + //-------------------------------------------------------------------- +public: + void teleportViaLandmark(const LLUUID& landmark_id); // Teleport to a landmark + void teleportHome() { teleportViaLandmark(LLUUID::null); } // Go home + void teleportViaLure(const LLUUID& lure_id, bool godlike); // To an invited location + void teleportViaLocation(const LLVector3d& pos_global); // To a global location - this will probably need to be deprecated + void teleportViaLocationLookAt(const LLVector3d& pos_global);// To a global location, preserving camera rotation + void teleportCancel(); // May or may not be allowed by server + void restoreCanceledTeleportRequest(); + bool canRestoreCanceledTeleport() { return mTeleportCanceled != NULL; } + bool getTeleportKeepsLookAt() { return mbTeleportKeepsLookAt; } // Whether look-at reset after teleport +protected: + bool teleportCore(bool is_local = false); // Stuff for all teleports; returns true if the teleport can proceed + + //-------------------------------------------------------------------- + // Teleport State + //-------------------------------------------------------------------- + +public: + bool hasRestartableFailedTeleportRequest(); + void restartFailedTeleportRequest(); + void clearTeleportRequest(); + void setMaturityRatingChangeDuringTeleport(U8 pMaturityRatingChange); + void sheduleTeleportIM(); + +private: + + + friend class LLTeleportRequest; + friend class LLTeleportRequestViaLandmark; + friend class LLTeleportRequestViaLure; + friend class LLTeleportRequestViaLocation; + friend class LLTeleportRequestViaLocationLookAt; + + LLTeleportRequestPtr mTeleportRequest; + LLTeleportRequestPtr mTeleportCanceled; + boost::signals2::connection mTeleportFinishedSlot; + boost::signals2::connection mTeleportFailedSlot; + + bool mIsMaturityRatingChangingDuringTeleport; + bool mTPNeedsNeabyChatSeparator; + U8 mMaturityRatingChange; + + bool hasPendingTeleportRequest(); + void startTeleportRequest(); + + void teleportRequest(const U64& region_handle, + const LLVector3& pos_local, // Go to a named location home + bool look_at_from_camera = false); + void doTeleportViaLandmark(const LLUUID& landmark_id); // Teleport to a landmark + void doTeleportViaLure(const LLUUID& lure_id, bool godlike); // To an invited location + void doTeleportViaLocation(const LLVector3d& pos_global); // To a global location - this will probably need to be deprecated + void doTeleportViaLocationLookAt(const LLVector3d& pos_global);// To a global location, preserving camera rotation + + void handleTeleportFinished(); + void handleTeleportFailed(); + + static void addTPNearbyChatSeparator(); + static void onCapabilitiesReceivedAfterTeleport(); + + //-------------------------------------------------------------------- + // Teleport State + //-------------------------------------------------------------------- +public: + ETeleportState getTeleportState() const; + void setTeleportState(ETeleportState state); +private: + ETeleportState mTeleportState; + + //-------------------------------------------------------------------- + // Teleport Message + //-------------------------------------------------------------------- +public: + const std::string& getTeleportMessage() const { return mTeleportMessage; } + void setTeleportMessage(const std::string& message) { mTeleportMessage = message; } +private: + std::string mTeleportMessage; + +/** Teleport + ** ** + *******************************************************************************/ + + // Build +public: + bool canEditParcel() const { return mCanEditParcel; } +private: + static void setCanEditParcel(); + bool mCanEditParcel; + + + +/******************************************************************************** + ** ** + ** ACCESS + **/ + +public: + // Checks if agent can modify an object based on the permissions and the agent's proxy status. + bool isGrantedProxy(const LLPermissions& perm); + bool allowOperation(PermissionBit op, + const LLPermissions& perm, + U64 group_proxy_power = 0, + U8 god_minimum = GOD_MAINTENANCE); + const LLAgentAccess& getAgentAccess(); + bool canManageEstate() const; + bool getAdminOverride() const; +private: + LLAgentAccess * mAgentAccess; + + //-------------------------------------------------------------------- + // God + //-------------------------------------------------------------------- +public: + bool isGodlike() const; + bool isGodlikeWithoutAdminMenuFakery() const; + U8 getGodLevel() const; + void setAdminOverride(bool b); + void setGodLevel(U8 god_level); + void requestEnterGodMode(); + void requestLeaveGodMode(); + + typedef boost::function god_level_change_callback_t; + typedef boost::signals2::signal god_level_change_signal_t; + typedef boost::signals2::connection god_level_change_slot_t; + + god_level_change_slot_t registerGodLevelChanageListener(god_level_change_callback_t pGodLevelChangeCallback); + +private: + god_level_change_signal_t mGodLevelChangeSignal; + + + //-------------------------------------------------------------------- + // Maturity + //-------------------------------------------------------------------- +public: + // Note: this is a prime candidate for pulling out into a Maturity class. + // Rather than just expose the preference setting, we're going to actually + // expose what the client code cares about -- what the user should see + // based on a combination of the is* and prefers* flags, combined with god bit. + bool wantsPGOnly() const; + bool canAccessMature() const; + bool canAccessAdult() const; + bool canAccessMaturityInRegion( U64 region_handle ) const; + bool canAccessMaturityAtGlobal( LLVector3d pos_global ) const; + bool prefersPG() const; + bool prefersMature() const; + bool prefersAdult() const; + bool isTeen() const; + bool isMature() const; + bool isAdult() const; + void setMaturity(char text); + static int convertTextToMaturity(char text); + +private: + bool mIsDoSendMaturityPreferenceToServer; + unsigned int mMaturityPreferenceRequestId; + unsigned int mMaturityPreferenceResponseId; + unsigned int mMaturityPreferenceNumRetries; + U8 mLastKnownRequestMaturity; + U8 mLastKnownResponseMaturity; + LLCore::HttpRequest::policy_t mHttpPolicy; + + bool isMaturityPreferenceSyncedWithServer() const; + void sendMaturityPreferenceToServer(U8 pPreferredMaturity); + void processMaturityPreferenceFromServer(const LLSD &result, U8 perferredMaturity); + + void handlePreferredMaturityResult(U8 pServerMaturity); + void handlePreferredMaturityError(); + void reportPreferredMaturitySuccess(); + void reportPreferredMaturityError(); + + // Maturity callbacks for PreferredMaturity control variable + void handleMaturity(const LLSD &pNewValue); + bool validateMaturity(const LLSD& newvalue); + + +/** Access + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** RENDERING + **/ + +public: + LLQuaternion getHeadRotation(); + bool needsRenderAvatar(); // true when camera mode is such that your own avatar should draw + bool needsRenderHead(); + void setShowAvatar(bool show) { mShowAvatar = show; } + bool getShowAvatar() const { return mShowAvatar; } + +private: + bool mShowAvatar; // Should we render the avatar? + + //-------------------------------------------------------------------- + // Rendering state bitmap helpers + //-------------------------------------------------------------------- +public: + void setRenderState(U8 newstate); + void clearRenderState(U8 clearstate); + U8 getRenderState(); +private: + U8 mRenderState; // Current behavior state of agent + + //-------------------------------------------------------------------- + // HUD + //-------------------------------------------------------------------- +public: + const LLColor4 &getEffectColor(); + void setEffectColor(const LLColor4 &color); +private: + LLUIColor * mEffectColor; + +/** Rendering + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** GROUPS + **/ + +public: + const LLUUID &getGroupID() const { return mGroupID; } + // Get group information by group_id, or false if not in group. + bool getGroupData(const LLUUID& group_id, LLGroupData& data) const; + // Get just the agent's contribution to the given group. + S32 getGroupContribution(const LLUUID& group_id) const; + // Update internal datastructures and update the server. + bool setGroupContribution(const LLUUID& group_id, S32 contribution); + bool setUserGroupFlags(const LLUUID& group_id, bool accept_notices, bool list_in_profile); + const std::string &getGroupName() const { return mGroupName; } + bool canJoinGroups() const; +private: + std::string mGroupName; + LLUUID mGroupID; + + //-------------------------------------------------------------------- + // Group Membership + //-------------------------------------------------------------------- +public: + // Checks against all groups in the entire agent group list. + bool isInGroup(const LLUUID& group_id, bool ingnore_God_mod = false) const; +protected: + // Only used for building titles. + bool isGroupMember() const { return !mGroupID.isNull(); } +public: + std::vector mGroups; + + //-------------------------------------------------------------------- + // Group Title + //-------------------------------------------------------------------- +public: + void setHideGroupTitle(bool hide) { mHideGroupTitle = hide; } + bool isGroupTitleHidden() const { return mHideGroupTitle; } +private: + std::string mGroupTitle; // Honorific, like "Sir" + bool mHideGroupTitle; + + //-------------------------------------------------------------------- + // Group Powers + //-------------------------------------------------------------------- +public: + bool hasPowerInGroup(const LLUUID& group_id, U64 power) const; + bool hasPowerInActiveGroup(const U64 power) const; + U64 getPowerInGroup(const LLUUID& group_id) const; + U64 mGroupPowers; + + //-------------------------------------------------------------------- + // Friends + //-------------------------------------------------------------------- +public: + void observeFriends(); + void friendsChanged(); +private: + LLFriendObserver* mFriendObserver; + std::set mProxyForAgents; + +/** Groups + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** MESSAGING + **/ + + //-------------------------------------------------------------------- + // Send + //-------------------------------------------------------------------- +public: + void sendMessage(); // Send message to this agent's region + void sendReliableMessage(); + void sendAgentDataUpdateRequest(); + void sendAgentUserInfoRequest(); + +// IM to Email and Online visibility + void sendAgentUpdateUserInfo(const std::string& directory_visibility); + +private: + void requestAgentUserInfoCoro(std::string capurl); + void updateAgentUserInfoCoro(std::string capurl, std::string directory_visibility); + // DEPRECATED: may be removed when User Info cap propagates + void sendAgentUserInfoRequestMessage(); + void sendAgentUpdateUserInfoMessage(const std::string& directory_visibility); + + //-------------------------------------------------------------------- + // Receive + //-------------------------------------------------------------------- +public: + static void processAgentDataUpdate(LLMessageSystem *msg, void **); + static void processAgentGroupDataUpdate(LLMessageSystem *msg, void **); + static void processAgentDropGroup(LLMessageSystem *msg, void **); + static void processScriptControlChange(LLMessageSystem *msg, void **); + +/** Messaging + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** UTILITY + **/ +public: + typedef LLCoreHttpUtil::HttpCoroutineAdapter::completionCallback_t httpCallback_t; + + /// Utilities for allowing the the agent sub managers to post and get via + /// HTTP using the agent's policy settings and headers. + bool requestPostCapability(const std::string &capName, LLSD &postData, httpCallback_t cbSuccess = NULL, httpCallback_t cbFailure = NULL); + bool requestGetCapability(const std::string &capName, httpCallback_t cbSuccess = NULL, httpCallback_t cbFailure = NULL); + + LLCore::HttpRequest::policy_t getAgentPolicy() const { return mHttpPolicy; } + +/** Utility + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** DEBUGGING + **/ + +public: + static void dumpGroupInfo(); + static void clearVisualParams(void *); + friend std::ostream& operator<<(std::ostream &s, const LLAgent &sphere); + +/** Debugging + ** ** + *******************************************************************************/ + +}; + +extern LLAgent gAgent; + +inline bool operator==(const LLGroupData &a, const LLGroupData &b) +{ + return (a.mID == b.mID); +} + +#endif diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index 033805b5ed..6bdc8752d9 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -1,2957 +1,2957 @@ -/** - * @file llagentcamera.cpp - * @brief LLAgent class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llagentcamera.h" - -#include "pipeline.h" - -#include "llagent.h" -#include "llanimationstates.h" -#include "llfloatercamera.h" -#include "llfloaterreg.h" -#include "llhudmanager.h" -#include "lljoystickbutton.h" -#include "llmorphview.h" -#include "llmoveview.h" -#include "llselectmgr.h" -#include "llsmoothstep.h" -#include "lltoolmgr.h" -#include "llviewercamera.h" -#include "llviewercontrol.h" -#include "llviewerjoystick.h" -#include "llviewermenu.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llviewerwindow.h" -#include "llvoavatarself.h" -#include "llwindow.h" -#include "llworld.h" - -using namespace LLAvatarAppearanceDefines; - -extern LLMenuBarGL* gMenuBarView; - -// Mousewheel camera zoom -const F32 MIN_ZOOM_FRACTION = 0.25f; -const F32 INITIAL_ZOOM_FRACTION = 1.f; -const F32 MAX_ZOOM_FRACTION = 8.f; - -const F32 CAMERA_ZOOM_HALF_LIFE = 0.07f; // seconds -const F32 FOV_ZOOM_HALF_LIFE = 0.07f; // seconds - -const F32 CAMERA_FOCUS_HALF_LIFE = 0.f;//0.02f; -const F32 CAMERA_LAG_HALF_LIFE = 0.25f; -const F32 MIN_CAMERA_LAG = 0.5f; -const F32 MAX_CAMERA_LAG = 5.f; - -const F32 CAMERA_COLLIDE_EPSILON = 0.1f; -const F32 MIN_CAMERA_DISTANCE = 0.1f; - -const F32 AVATAR_ZOOM_MIN_X_FACTOR = 0.55f; -const F32 AVATAR_ZOOM_MIN_Y_FACTOR = 0.7f; -const F32 AVATAR_ZOOM_MIN_Z_FACTOR = 1.15f; - -const F32 MAX_CAMERA_DISTANCE_FROM_AGENT = 50.f; -const F32 MAX_CAMERA_DISTANCE_FROM_OBJECT = 496.f; -const F32 CAMERA_FUDGE_FROM_OBJECT = 16.f; - -const F32 MAX_CAMERA_SMOOTH_DISTANCE = 50.0f; - -const F32 HEAD_BUFFER_SIZE = 0.3f; - -const F32 CUSTOMIZE_AVATAR_CAMERA_ANIM_SLOP = 0.1f; - -const F32 LAND_MIN_ZOOM = 0.15f; - -const F32 AVATAR_MIN_ZOOM = 0.5f; -const F32 OBJECT_MIN_ZOOM = 0.02f; - -const F32 APPEARANCE_MIN_ZOOM = 0.39f; -const F32 APPEARANCE_MAX_ZOOM = 8.f; - -const F32 CUSTOMIZE_AVATAR_CAMERA_DEFAULT_DIST = 3.5f; - -const F32 GROUND_TO_AIR_CAMERA_TRANSITION_TIME = 0.5f; -const F32 GROUND_TO_AIR_CAMERA_TRANSITION_START_TIME = 0.5f; - -const F32 OBJECT_EXTENTS_PADDING = 0.5f; - -static bool isDisableCameraConstraints() -{ - static LLCachedControl sDisableCameraConstraints(gSavedSettings, "DisableCameraConstraints", false); - return sDisableCameraConstraints; -} - -// The agent instance. -LLAgentCamera gAgentCamera; - -//----------------------------------------------------------------------------- -// LLAgentCamera() -//----------------------------------------------------------------------------- -LLAgentCamera::LLAgentCamera() : - mInitialized(false), - - mDrawDistance( DEFAULT_FAR_PLANE ), - - mLookAt(NULL), - mPointAt(NULL), - - mHUDTargetZoom(1.f), - mHUDCurZoom(1.f), - - mForceMouselook(false), - - mCameraMode( CAMERA_MODE_THIRD_PERSON ), - mLastCameraMode( CAMERA_MODE_THIRD_PERSON ), - - mCameraPreset(CAMERA_PRESET_REAR_VIEW), - - mCameraAnimating( false ), - mAnimationCameraStartGlobal(), - mAnimationFocusStartGlobal(), - mAnimationTimer(), - mAnimationDuration(0.33f), - - mCameraFOVZoomFactor(0.f), - mCameraCurrentFOVZoomFactor(0.f), - mCameraFocusOffset(), - - mCameraCollidePlane(), - - mCurrentCameraDistance(2.f), // meters, set in init() - mTargetCameraDistance(2.f), - mCameraZoomFraction(1.f), // deprecated - mThirdPersonHeadOffset(0.f, 0.f, 1.f), - mSitCameraEnabled(false), - mCameraSmoothingLastPositionGlobal(), - mCameraSmoothingLastPositionAgent(), - mCameraSmoothingStop(false), - - mCameraUpVector(LLVector3::z_axis), // default is straight up - - mFocusOnAvatar(true), - mAllowChangeToFollow(false), - mFocusGlobal(), - mFocusTargetGlobal(), - mFocusObject(NULL), - mFocusObjectDist(0.f), - mFocusObjectOffset(), - mTrackFocusObject(true), - - mAtKey(0), // Either 1, 0, or -1... indicates that movement-key is pressed - mWalkKey(0), // like AtKey, but causes less forward thrust - mLeftKey(0), - mUpKey(0), - mYawKey(0.f), - mPitchKey(0.f), - - mOrbitLeftKey(0.f), - mOrbitRightKey(0.f), - mOrbitUpKey(0.f), - mOrbitDownKey(0.f), - mOrbitInKey(0.f), - mOrbitOutKey(0.f), - - mPanUpKey(0.f), - mPanDownKey(0.f), - mPanLeftKey(0.f), - mPanRightKey(0.f), - mPanInKey(0.f), - mPanOutKey(0.f) -{ - mFollowCam.setMaxCameraDistantFromSubject( MAX_CAMERA_DISTANCE_FROM_AGENT ); - - clearGeneralKeys(); - clearOrbitKeys(); - clearPanKeys(); - - resetPanDiff(); - resetOrbitDiff(); -} - -// Requires gSavedSettings to be initialized. -//----------------------------------------------------------------------------- -// init() -//----------------------------------------------------------------------------- -void LLAgentCamera::init() -{ - // *Note: this is where LLViewerCamera::getInstance() used to be constructed. - - mDrawDistance = gSavedSettings.getF32("RenderFarClip"); - - LLViewerCamera::getInstance()->setView(DEFAULT_FIELD_OF_VIEW); - // Leave at 0.1 meters until we have real near clip management - LLViewerCamera::getInstance()->setNear(0.1f); - LLViewerCamera::getInstance()->setFar(mDrawDistance); // if you want to change camera settings, do so in camera.h - LLViewerCamera::getInstance()->setAspect( gViewerWindow->getWorldViewAspectRatio() ); // default, overridden in LLViewerWindow::reshape - LLViewerCamera::getInstance()->setViewHeightInPixels(768); // default, overridden in LLViewerWindow::reshape - - mCameraFocusOffsetTarget = LLVector4(gSavedSettings.getVector3("CameraOffsetBuild")); - - mCameraPreset = (ECameraPreset) gSavedSettings.getU32("CameraPresetType"); - - mCameraCollidePlane.clearVec(); - mCurrentCameraDistance = getCameraOffsetInitial().magVec() * gSavedSettings.getF32("CameraOffsetScale"); - mTargetCameraDistance = mCurrentCameraDistance; - mCameraZoomFraction = 1.f; - mTrackFocusObject = gSavedSettings.getBOOL("TrackFocusObject"); - - mInitialized = true; -} - -//----------------------------------------------------------------------------- -// cleanup() -//----------------------------------------------------------------------------- -void LLAgentCamera::cleanup() -{ - setSitCamera(LLUUID::null); - - if(mLookAt) - { - mLookAt->markDead() ; - mLookAt = NULL; - } - if(mPointAt) - { - mPointAt->markDead() ; - mPointAt = NULL; - } - setFocusObject(NULL); -} - -void LLAgentCamera::setAvatarObject(LLVOAvatarSelf* avatar) -{ - if (!mLookAt) - { - mLookAt = (LLHUDEffectLookAt *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_LOOKAT); - } - if (!mPointAt) - { - mPointAt = (LLHUDEffectPointAt *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_POINTAT); - } - - if (!mLookAt.isNull()) - { - mLookAt->setSourceObject(avatar); - } - if (!mPointAt.isNull()) - { - mPointAt->setSourceObject(avatar); - } -} - -//----------------------------------------------------------------------------- -// LLAgent() -//----------------------------------------------------------------------------- -LLAgentCamera::~LLAgentCamera() -{ - cleanup(); - - // *Note: this is where LLViewerCamera::getInstance() used to be deleted. -} - -// Change camera back to third person, stop the autopilot, -// deselect stuff, etc. -//----------------------------------------------------------------------------- -// resetView() -//----------------------------------------------------------------------------- -void LLAgentCamera::resetView(bool reset_camera, bool change_camera) -{ - if (gDisconnected) - { - return; - } - - if (gAgent.getAutoPilot()) - { - gAgent.stopAutoPilot(true); - } - - LLSelectMgr::getInstance()->unhighlightAll(); - - // By popular request, keep land selection while walking around. JC - // LLViewerParcelMgr::getInstance()->deselectLand(); - - // force deselect when walking and attachment is selected - // this is so people don't wig out when their avatar moves without animating - if (LLSelectMgr::getInstance()->getSelection()->isAttachment()) - { - LLSelectMgr::getInstance()->deselectAll(); - } - - if (gMenuHolder != NULL) - { - // Hide all popup menus - gMenuHolder->hideMenus(); - } - - if (change_camera && !gSavedSettings.getBOOL("FreezeTime")) - { - changeCameraToDefault(); - - if (LLViewerJoystick::getInstance()->getOverrideCamera()) - { - handle_toggle_flycam(); - } - - // reset avatar mode from eventual residual motion - if (LLToolMgr::getInstance()->inBuildMode()) - { - LLViewerJoystick::getInstance()->moveAvatar(true); - } - - //Camera Tool is needed for Free Camera Control Mode - if (!LLFloaterCamera::inFreeCameraMode()) - { - LLFloaterReg::hideInstance("build"); - - // Switch back to basic toolset - LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); - } - - gViewerWindow->showCursor(); - } - - - if (reset_camera && !gSavedSettings.getBOOL("FreezeTime")) - { - if (!gViewerWindow->getLeftMouseDown() && cameraThirdPerson()) - { - // leaving mouse-steer mode - LLVector3 agent_at_axis = gAgent.getAtAxis(); - agent_at_axis -= projected_vec(agent_at_axis, gAgent.getReferenceUpVector()); - agent_at_axis.normalize(); - gAgent.resetAxes(lerp(gAgent.getAtAxis(), agent_at_axis, LLSmoothInterpolation::getInterpolant(0.3f))); - } - - setFocusOnAvatar(true, ANIMATE); - - mCameraFOVZoomFactor = 0.f; - } - resetPanDiff(); - resetOrbitDiff(); - mHUDTargetZoom = 1.f; - - if (LLSelectMgr::getInstance()->mAllowSelectAvatar) - { - // resetting camera also resets position overrides in debug mode 'AllowSelectAvatar' - LLObjectSelectionHandle selected_handle = LLSelectMgr::getInstance()->getSelection(); - if (selected_handle->getObjectCount() == 1 - && selected_handle->getFirstObject() != NULL - && selected_handle->getFirstObject()->isAvatar()) - { - LLSelectMgr::getInstance()->resetObjectOverrides(selected_handle); - } - } -} - -// Allow camera to be moved somewhere other than behind avatar. -//----------------------------------------------------------------------------- -// unlockView() -//----------------------------------------------------------------------------- -void LLAgentCamera::unlockView() -{ - if (getFocusOnAvatar()) - { - if (isAgentAvatarValid()) - { - setFocusGlobal(LLVector3d::zero, gAgentAvatarp->mID); - } - setFocusOnAvatar(false, false); // no animation - } -} - -//----------------------------------------------------------------------------- -// slamLookAt() -//----------------------------------------------------------------------------- -void LLAgentCamera::slamLookAt(const LLVector3 &look_at) -{ - LLVector3 look_at_norm = look_at; - look_at_norm.mV[VZ] = 0.f; - look_at_norm.normalize(); - gAgent.resetAxes(look_at_norm); -} - -//----------------------------------------------------------------------------- -// calcFocusOffset() -//----------------------------------------------------------------------------- -LLVector3 LLAgentCamera::calcFocusOffset(LLViewerObject *object, LLVector3 original_focus_point, S32 x, S32 y) -{ - LLMatrix4 obj_matrix = object->getRenderMatrix(); - LLQuaternion obj_rot = object->getRenderRotation(); - LLVector3 obj_pos = object->getRenderPosition(); - - // if is avatar - don't do any funk heuristics to position the focal point - // see DEV-30589 - if ((object->isAvatar() && !object->isRoot()) || (object->isAnimatedObject() && object->getControlAvatar())) - { - return original_focus_point - obj_pos; - } - if (object->isAvatar()) - { - LLVOAvatar* av = object->asAvatar(); - return original_focus_point - av->getCharacterPosition(); - } - - LLQuaternion inv_obj_rot = ~obj_rot; // get inverse of rotation - LLVector3 object_extents = object->getScale(); - - // make sure they object extents are non-zero - object_extents.clamp(0.001f, F32_MAX); - - // obj_to_cam_ray is unit vector pointing from object center to camera, in the coordinate frame of the object - LLVector3 obj_to_cam_ray = obj_pos - LLViewerCamera::getInstance()->getOrigin(); - obj_to_cam_ray.rotVec(inv_obj_rot); - obj_to_cam_ray.normalize(); - - // obj_to_cam_ray_proportions are the (positive) ratios of - // the obj_to_cam_ray x,y,z components with the x,y,z object dimensions. - LLVector3 obj_to_cam_ray_proportions; - obj_to_cam_ray_proportions.mV[VX] = llabs(obj_to_cam_ray.mV[VX] / object_extents.mV[VX]); - obj_to_cam_ray_proportions.mV[VY] = llabs(obj_to_cam_ray.mV[VY] / object_extents.mV[VY]); - obj_to_cam_ray_proportions.mV[VZ] = llabs(obj_to_cam_ray.mV[VZ] / object_extents.mV[VZ]); - - // find the largest ratio stored in obj_to_cam_ray_proportions - // this corresponds to the object's local axial plane (XY, YZ, XZ) that is *most* facing the camera - LLVector3 longest_object_axis; - // is x-axis longest? - if (obj_to_cam_ray_proportions.mV[VX] > obj_to_cam_ray_proportions.mV[VY] - && obj_to_cam_ray_proportions.mV[VX] > obj_to_cam_ray_proportions.mV[VZ]) - { - // then grab it - longest_object_axis.setVec(obj_matrix.getFwdRow4()); - } - // is y-axis longest? - else if (obj_to_cam_ray_proportions.mV[VY] > obj_to_cam_ray_proportions.mV[VZ]) - { - // then grab it - longest_object_axis.setVec(obj_matrix.getLeftRow4()); - } - // otherwise, use z axis - else - { - longest_object_axis.setVec(obj_matrix.getUpRow4()); - } - - // Use this axis as the normal to project mouse click on to plane with that normal, at the object center. - // This generates a point behind the mouse cursor that is approximately in the middle of the object in - // terms of depth. - // We do this to allow the camera rotation tool to "tumble" the object by rotating the camera. - // If the focus point were the object surface under the mouse, camera rotation would introduce an undesirable - // eccentricity to the object orientation - LLVector3 focus_plane_normal(longest_object_axis); - focus_plane_normal.normalize(); - - LLVector3d focus_pt_global; - gViewerWindow->mousePointOnPlaneGlobal(focus_pt_global, x, y, gAgent.getPosGlobalFromAgent(obj_pos), focus_plane_normal); - LLVector3 focus_pt = gAgent.getPosAgentFromGlobal(focus_pt_global); - - // find vector from camera to focus point in object space - LLVector3 camera_to_focus_vec = focus_pt - LLViewerCamera::getInstance()->getOrigin(); - camera_to_focus_vec.rotVec(inv_obj_rot); - - // find vector from object origin to focus point in object coordinates - LLVector3 focus_offset_from_object_center = focus_pt - obj_pos; - // convert to object-local space - focus_offset_from_object_center.rotVec(inv_obj_rot); - - // We need to project the focus point back into the bounding box of the focused object. - // Do this by calculating the XYZ scale factors needed to get focus offset back in bounds along the camera_focus axis - LLVector3 clip_fraction; - - // for each axis... - for (U32 axis = VX; axis <= VZ; axis++) - { - //...calculate distance that focus offset sits outside of bounding box along that axis... - //NOTE: dist_out_of_bounds keeps the sign of focus_offset_from_object_center - F32 dist_out_of_bounds; - if (focus_offset_from_object_center.mV[axis] > 0.f) - { - dist_out_of_bounds = llmax(0.f, focus_offset_from_object_center.mV[axis] - (object_extents.mV[axis] * 0.5f)); - } - else - { - dist_out_of_bounds = llmin(0.f, focus_offset_from_object_center.mV[axis] + (object_extents.mV[axis] * 0.5f)); - } - - //...then calculate the scale factor needed to push camera_to_focus_vec back in bounds along current axis - if (llabs(camera_to_focus_vec.mV[axis]) < 0.0001f) - { - // don't divide by very small number - clip_fraction.mV[axis] = 0.f; - } - else - { - clip_fraction.mV[axis] = dist_out_of_bounds / camera_to_focus_vec.mV[axis]; - } - } - - LLVector3 abs_clip_fraction = clip_fraction; - abs_clip_fraction.abs(); - - // find axis of focus offset that is *most* outside the bounding box and use that to - // rescale focus offset to inside object extents - if (abs_clip_fraction.mV[VX] > abs_clip_fraction.mV[VY] - && abs_clip_fraction.mV[VX] > abs_clip_fraction.mV[VZ]) - { - focus_offset_from_object_center -= clip_fraction.mV[VX] * camera_to_focus_vec; - } - else if (abs_clip_fraction.mV[VY] > abs_clip_fraction.mV[VZ]) - { - focus_offset_from_object_center -= clip_fraction.mV[VY] * camera_to_focus_vec; - } - else - { - focus_offset_from_object_center -= clip_fraction.mV[VZ] * camera_to_focus_vec; - } - - // convert back to world space - focus_offset_from_object_center.rotVec(obj_rot); - - // now, based on distance of camera from object relative to object size - // push the focus point towards the near surface of the object when (relatively) close to the objcet - // or keep the focus point in the object middle when (relatively) far - // NOTE: leave focus point in middle of avatars, since the behavior you want when alt-zooming on avatars - // is almost always "tumble about middle" and not "spin around surface point" - { - LLVector3 obj_rel = original_focus_point - object->getRenderPosition(); - - //now that we have the object relative position, we should bias toward the center of the object - //based on the distance of the camera to the focus point vs. the distance of the camera to the focus - - F32 relDist = llabs(obj_rel * LLViewerCamera::getInstance()->getAtAxis()); - F32 viewDist = dist_vec(obj_pos + obj_rel, LLViewerCamera::getInstance()->getOrigin()); - - - LLBBox obj_bbox = object->getBoundingBoxAgent(); - F32 bias = 0.f; - - // virtual_camera_pos is the camera position we are simulating by backing the camera off - // and adjusting the FOV - LLVector3 virtual_camera_pos = gAgent.getPosAgentFromGlobal(mFocusTargetGlobal + (getCameraPositionGlobal() - mFocusTargetGlobal) / (1.f + mCameraFOVZoomFactor)); - - // if the camera is inside the object (large, hollow objects, for example) - // leave focus point all the way to destination depth, away from object center - if(!obj_bbox.containsPointAgent(virtual_camera_pos)) - { - // perform magic number biasing of focus point towards surface vs. planar center - bias = clamp_rescale(relDist/viewDist, 0.1f, 0.7f, 0.0f, 1.0f); - obj_rel = lerp(focus_offset_from_object_center, obj_rel, bias); - } - - focus_offset_from_object_center = obj_rel; - } - - return focus_offset_from_object_center; -} - -//----------------------------------------------------------------------------- -// calcCameraMinDistance() -//----------------------------------------------------------------------------- -bool LLAgentCamera::calcCameraMinDistance(F32 &obj_min_distance) -{ - bool soft_limit = false; // is the bounding box to be treated literally (volumes) or as an approximation (avatars) - - if (!mFocusObject || mFocusObject->isDead() || - mFocusObject->isMesh() || - isDisableCameraConstraints()) - { - obj_min_distance = 0.f; - return true; - } - - if (mFocusObject->mDrawable.isNull()) - { -#ifdef LL_RELEASE_FOR_DOWNLOAD - LL_WARNS() << "Focus object with no drawable!" << LL_ENDL; -#else - mFocusObject->dump(); - LL_ERRS() << "Focus object with no drawable!" << LL_ENDL; -#endif - obj_min_distance = 0.f; - return true; - } - - LLQuaternion inv_object_rot = ~mFocusObject->getRenderRotation(); - LLVector3 target_offset_origin = mFocusObjectOffset; - LLVector3 camera_offset_target(getCameraPositionAgent() - gAgent.getPosAgentFromGlobal(mFocusTargetGlobal)); - - // convert offsets into object local space - camera_offset_target.rotVec(inv_object_rot); - target_offset_origin.rotVec(inv_object_rot); - - // push around object extents based on target offset - LLVector3 object_extents = mFocusObject->getScale(); - if (mFocusObject->isAvatar()) - { - // fudge factors that lets you zoom in on avatars a bit more (which don't do FOV zoom) - object_extents.mV[VX] *= AVATAR_ZOOM_MIN_X_FACTOR; - object_extents.mV[VY] *= AVATAR_ZOOM_MIN_Y_FACTOR; - object_extents.mV[VZ] *= AVATAR_ZOOM_MIN_Z_FACTOR; - soft_limit = true; - } - LLVector3 abs_target_offset = target_offset_origin; - abs_target_offset.abs(); - - LLVector3 target_offset_dir = target_offset_origin; - - bool target_outside_object_extents = false; - - for (U32 i = VX; i <= VZ; i++) - { - if (abs_target_offset.mV[i] * 2.f > object_extents.mV[i] + OBJECT_EXTENTS_PADDING) - { - target_outside_object_extents = true; - } - if (camera_offset_target.mV[i] > 0.f) - { - object_extents.mV[i] -= target_offset_origin.mV[i] * 2.f; - } - else - { - object_extents.mV[i] += target_offset_origin.mV[i] * 2.f; - } - } - - // don't shrink the object extents so far that the object inverts - object_extents.clamp(0.001f, F32_MAX); - - // move into first octant - LLVector3 camera_offset_target_abs_norm = camera_offset_target; - camera_offset_target_abs_norm.abs(); - // make sure offset is non-zero - camera_offset_target_abs_norm.clamp(0.001f, F32_MAX); - camera_offset_target_abs_norm.normalize(); - - // find camera position relative to normalized object extents - LLVector3 camera_offset_target_scaled = camera_offset_target_abs_norm; - camera_offset_target_scaled.mV[VX] /= object_extents.mV[VX]; - camera_offset_target_scaled.mV[VY] /= object_extents.mV[VY]; - camera_offset_target_scaled.mV[VZ] /= object_extents.mV[VZ]; - - if (camera_offset_target_scaled.mV[VX] > camera_offset_target_scaled.mV[VY] && - camera_offset_target_scaled.mV[VX] > camera_offset_target_scaled.mV[VZ]) - { - if (camera_offset_target_abs_norm.mV[VX] < 0.001f) - { - obj_min_distance = object_extents.mV[VX] * 0.5f; - } - else - { - obj_min_distance = object_extents.mV[VX] * 0.5f / camera_offset_target_abs_norm.mV[VX]; - } - } - else if (camera_offset_target_scaled.mV[VY] > camera_offset_target_scaled.mV[VZ]) - { - if (camera_offset_target_abs_norm.mV[VY] < 0.001f) - { - obj_min_distance = object_extents.mV[VY] * 0.5f; - } - else - { - obj_min_distance = object_extents.mV[VY] * 0.5f / camera_offset_target_abs_norm.mV[VY]; - } - } - else - { - if (camera_offset_target_abs_norm.mV[VZ] < 0.001f) - { - obj_min_distance = object_extents.mV[VZ] * 0.5f; - } - else - { - obj_min_distance = object_extents.mV[VZ] * 0.5f / camera_offset_target_abs_norm.mV[VZ]; - } - } - - LLVector3 object_split_axis; - LLVector3 target_offset_scaled = target_offset_origin; - target_offset_scaled.abs(); - target_offset_scaled.normalize(); - target_offset_scaled.mV[VX] /= object_extents.mV[VX]; - target_offset_scaled.mV[VY] /= object_extents.mV[VY]; - target_offset_scaled.mV[VZ] /= object_extents.mV[VZ]; - - if (target_offset_scaled.mV[VX] > target_offset_scaled.mV[VY] && - target_offset_scaled.mV[VX] > target_offset_scaled.mV[VZ]) - { - object_split_axis = LLVector3::x_axis; - } - else if (target_offset_scaled.mV[VY] > target_offset_scaled.mV[VZ]) - { - object_split_axis = LLVector3::y_axis; - } - else - { - object_split_axis = LLVector3::z_axis; - } - - LLVector3 camera_offset_object(getCameraPositionAgent() - mFocusObject->getPositionAgent()); - - - F32 camera_offset_clip = camera_offset_object * object_split_axis; - F32 target_offset_clip = target_offset_dir * object_split_axis; - - // target has moved outside of object extents - // check to see if camera and target are on same side - if (target_outside_object_extents) - { - if (camera_offset_clip > 0.f && target_offset_clip > 0.f) - { - return false; - } - else if (camera_offset_clip < 0.f && target_offset_clip < 0.f) - { - return false; - } - } - - // clamp obj distance to diagonal of 10 by 10 cube - obj_min_distance = llmin(obj_min_distance, 10.f * F_SQRT3); - - obj_min_distance += LLViewerCamera::getInstance()->getNear() + (soft_limit ? 0.1f : 0.2f); - - return true; -} - -F32 LLAgentCamera::getCameraZoomFraction(bool get_third_person) -{ - // 0.f -> camera zoomed all the way out - // 1.f -> camera zoomed all the way in - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - if (selection->getObjectCount() && selection->getSelectType() == SELECT_TYPE_HUD) - { - // already [0,1] - return mHUDTargetZoom; - } - - if (get_third_person || (mFocusOnAvatar && cameraThirdPerson())) - { - return clamp_rescale(mCameraZoomFraction, MIN_ZOOM_FRACTION, MAX_ZOOM_FRACTION, 1.f, 0.f); - } - - if (cameraCustomizeAvatar()) - { - F32 distance = (F32)mCameraFocusOffsetTarget.magVec(); - return clamp_rescale(distance, APPEARANCE_MIN_ZOOM, APPEARANCE_MAX_ZOOM, 1.f, 0.f ); - } - - F32 min_zoom; - F32 max_zoom = getCameraMaxZoomDistance(); - if (isDisableCameraConstraints()) - { - max_zoom = MAX_CAMERA_DISTANCE_FROM_OBJECT; - } - - F32 distance = (F32)mCameraFocusOffsetTarget.magVec(); - if (mFocusObject.notNull()) - { - if (mFocusObject->isAvatar()) - { - min_zoom = AVATAR_MIN_ZOOM; - } - else - { - min_zoom = OBJECT_MIN_ZOOM; - } - } - else - { - min_zoom = LAND_MIN_ZOOM; - } - - return clamp_rescale(distance, min_zoom, max_zoom, 1.f, 0.f); -} - -void LLAgentCamera::setCameraZoomFraction(F32 fraction) -{ - // 0.f -> camera zoomed all the way out - // 1.f -> camera zoomed all the way in - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - - if (selection->getObjectCount() && selection->getSelectType() == SELECT_TYPE_HUD) - { - mHUDTargetZoom = fraction; - } - else if (mFocusOnAvatar && cameraThirdPerson()) - { - mCameraZoomFraction = rescale(fraction, 0.f, 1.f, MAX_ZOOM_FRACTION, MIN_ZOOM_FRACTION); - } - else if (cameraCustomizeAvatar()) - { - LLVector3d camera_offset_dir = mCameraFocusOffsetTarget; - camera_offset_dir.normalize(); - mCameraFocusOffsetTarget = camera_offset_dir * rescale(fraction, 0.f, 1.f, APPEARANCE_MAX_ZOOM, APPEARANCE_MIN_ZOOM); - } - else - { - F32 min_zoom = LAND_MIN_ZOOM; - F32 max_zoom = getCameraMaxZoomDistance(); - if (isDisableCameraConstraints()) - { - max_zoom = MAX_CAMERA_DISTANCE_FROM_OBJECT; - } - - if (mFocusObject.notNull()) - { - if (mFocusObject.notNull()) - { - if (mFocusObject->isAvatar()) - { - min_zoom = AVATAR_MIN_ZOOM; - } - else - { - min_zoom = OBJECT_MIN_ZOOM; - } - } - } - - LLVector3d camera_offset_dir = mCameraFocusOffsetTarget; - camera_offset_dir.normalize(); - mCameraFocusOffsetTarget = camera_offset_dir * rescale(fraction, 0.f, 1.f, max_zoom, min_zoom); - } - - startCameraAnimation(); -} - -F32 LLAgentCamera::getAgentHUDTargetZoom() -{ - static LLCachedControl hud_scale_factor(gSavedSettings, "HUDScaleFactor"); - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - return (selection->getObjectCount() && selection->getSelectType() == SELECT_TYPE_HUD) ? hud_scale_factor*gAgentCamera.mHUDTargetZoom : hud_scale_factor; -} - -//----------------------------------------------------------------------------- -// cameraOrbitAround() -//----------------------------------------------------------------------------- -void LLAgentCamera::cameraOrbitAround(const F32 radians) -{ - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - if (selection->getObjectCount() && selection->getSelectType() == SELECT_TYPE_HUD) - { - // do nothing for hud selection - } - else if (mFocusOnAvatar && (mCameraMode == CAMERA_MODE_THIRD_PERSON || mCameraMode == CAMERA_MODE_FOLLOW)) - { - gAgent.yaw(radians); - } - else - { - mOrbitAroundRadians += radians; - mCameraFocusOffsetTarget.rotVec(radians, 0.f, 0.f, 1.f); - - cameraZoomIn(1.f); - } -} - - -//----------------------------------------------------------------------------- -// cameraOrbitOver() -//----------------------------------------------------------------------------- -void LLAgentCamera::cameraOrbitOver(const F32 angle) -{ - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - if (selection->getObjectCount() && selection->getSelectType() == SELECT_TYPE_HUD) - { - // do nothing for hud selection - } - else if (mFocusOnAvatar && mCameraMode == CAMERA_MODE_THIRD_PERSON) - { - gAgent.pitch(angle); - } - else - { - LLVector3 camera_offset_unit(mCameraFocusOffsetTarget); - camera_offset_unit.normalize(); - - F32 angle_from_up = acos( camera_offset_unit * gAgent.getReferenceUpVector() ); - - LLVector3d left_axis; - left_axis.setVec(LLViewerCamera::getInstance()->getLeftAxis()); - F32 new_angle = llclamp(angle_from_up - angle, 1.f * DEG_TO_RAD, 179.f * DEG_TO_RAD); - mOrbitOverAngle += angle_from_up - new_angle; - mCameraFocusOffsetTarget.rotVec(angle_from_up - new_angle, left_axis); - - cameraZoomIn(1.f); - } -} - -void LLAgentCamera::resetCameraOrbit() -{ - LLVector3 camera_offset_unit(mCameraFocusOffsetTarget); - camera_offset_unit.normalize(); - - LLVector3d left_axis; - left_axis.setVec(LLViewerCamera::getInstance()->getLeftAxis()); - mCameraFocusOffsetTarget.rotVec(-mOrbitOverAngle, left_axis); - - mCameraFocusOffsetTarget.rotVec(-mOrbitAroundRadians, 0.f, 0.f, 1.f); - - cameraZoomIn(1.f); - resetOrbitDiff(); -} - -void LLAgentCamera::resetOrbitDiff() -{ - mOrbitAroundRadians = 0; - mOrbitOverAngle = 0; -} - -//----------------------------------------------------------------------------- -// cameraZoomIn() -//----------------------------------------------------------------------------- -void LLAgentCamera::cameraZoomIn(const F32 fraction) -{ - if (gDisconnected) - { - return; - } - - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - if (LLToolMgr::getInstance()->inBuildMode() && selection->getObjectCount() && selection->getSelectType() == SELECT_TYPE_HUD) - { - // just update hud zoom level - mHUDTargetZoom /= fraction; - return; - } - - LLVector3d camera_offset_unit(mCameraFocusOffsetTarget); - F32 current_distance = (F32)camera_offset_unit.normalize(); - F32 new_distance = current_distance * fraction; - - // Unless camera is unlocked - if (!isDisableCameraConstraints()) - { - F32 min_zoom = LAND_MIN_ZOOM; - - // Don't move through focus point - if (mFocusObject) - { - LLVector3 camera_offset_dir((F32)camera_offset_unit.mdV[VX], (F32)camera_offset_unit.mdV[VY], (F32)camera_offset_unit.mdV[VZ]); - - if (mFocusObject->isAvatar()) - { - calcCameraMinDistance(min_zoom); - } - else - { - min_zoom = OBJECT_MIN_ZOOM; - } - } - - new_distance = llmax(new_distance, min_zoom); - - F32 max_distance = getCameraMaxZoomDistance(); - max_distance = llmin(max_distance, current_distance * 4.f); //Scaled max relative to current distance. MAINT-3154 - new_distance = llmin(new_distance, max_distance); - - if (cameraCustomizeAvatar()) - { - new_distance = llclamp(new_distance, APPEARANCE_MIN_ZOOM, APPEARANCE_MAX_ZOOM); - } - } - - mCameraFocusOffsetTarget = new_distance * camera_offset_unit; -} - -//----------------------------------------------------------------------------- -// cameraOrbitIn() -//----------------------------------------------------------------------------- -void LLAgentCamera::cameraOrbitIn(const F32 meters) -{ - if (mFocusOnAvatar && mCameraMode == CAMERA_MODE_THIRD_PERSON) - { - F32 camera_offset_dist = llmax(0.001f, getCameraOffsetInitial().magVec() * gSavedSettings.getF32("CameraOffsetScale")); - - mCameraZoomFraction = (mTargetCameraDistance - meters) / camera_offset_dist; - - if (!gSavedSettings.getBOOL("FreezeTime") && mCameraZoomFraction < MIN_ZOOM_FRACTION && meters > 0.f) - { - // No need to animate, camera is already there. - changeCameraToMouselook(false); - } - - if (!isDisableCameraConstraints()) - { - mCameraZoomFraction = llclamp(mCameraZoomFraction, MIN_ZOOM_FRACTION, MAX_ZOOM_FRACTION); - } - } - else - { - LLVector3d camera_offset_unit(mCameraFocusOffsetTarget); - F32 current_distance = (F32)camera_offset_unit.normalize(); - F32 new_distance = current_distance - meters; - - // Unless camera is unlocked - if (!isDisableCameraConstraints()) - { - F32 min_zoom = LAND_MIN_ZOOM; - - // Don't move through focus point - if (mFocusObject.notNull()) - { - if (mFocusObject->isAvatar()) - { - min_zoom = AVATAR_MIN_ZOOM; - } - else - { - min_zoom = OBJECT_MIN_ZOOM; - } - } - - new_distance = llmax(new_distance, min_zoom); - - F32 max_distance = getCameraMaxZoomDistance(); - new_distance = llmin(new_distance, max_distance); - - if (CAMERA_MODE_CUSTOMIZE_AVATAR == getCameraMode()) - { - new_distance = llclamp(new_distance, APPEARANCE_MIN_ZOOM, APPEARANCE_MAX_ZOOM); - } - } - - // Compute new camera offset - mCameraFocusOffsetTarget = new_distance * camera_offset_unit; - cameraZoomIn(1.f); - } -} - -//----------------------------------------------------------------------------- -// cameraPanIn() -//----------------------------------------------------------------------------- -void LLAgentCamera::cameraPanIn(F32 meters) -{ - LLVector3d at_axis; - at_axis.setVec(LLViewerCamera::getInstance()->getAtAxis()); - - mPanFocusDiff += meters * at_axis; - - mFocusTargetGlobal += meters * at_axis; - mFocusGlobal = mFocusTargetGlobal; - // don't enforce zoom constraints as this is the only way for users to get past them easily - updateFocusOffset(); - // NOTE: panning movements expect the camera to move exactly with the focus target, not animated behind -Nyx - mCameraSmoothingLastPositionGlobal = calcCameraPositionTargetGlobal(); -} - -//----------------------------------------------------------------------------- -// cameraPanLeft() -//----------------------------------------------------------------------------- -void LLAgentCamera::cameraPanLeft(F32 meters) -{ - LLVector3d left_axis; - left_axis.setVec(LLViewerCamera::getInstance()->getLeftAxis()); - - mPanFocusDiff += meters * left_axis; - - mFocusTargetGlobal += meters * left_axis; - mFocusGlobal = mFocusTargetGlobal; - - // disable smoothing for camera pan, which causes some residents unhappiness - mCameraSmoothingStop = true; - - cameraZoomIn(1.f); - updateFocusOffset(); - // NOTE: panning movements expect the camera to move exactly with the focus target, not animated behind - Nyx - mCameraSmoothingLastPositionGlobal = calcCameraPositionTargetGlobal(); -} - -//----------------------------------------------------------------------------- -// cameraPanUp() -//----------------------------------------------------------------------------- -void LLAgentCamera::cameraPanUp(F32 meters) -{ - LLVector3d up_axis; - up_axis.setVec(LLViewerCamera::getInstance()->getUpAxis()); - - mPanFocusDiff += meters * up_axis; - - mFocusTargetGlobal += meters * up_axis; - mFocusGlobal = mFocusTargetGlobal; - - // disable smoothing for camera pan, which causes some residents unhappiness - mCameraSmoothingStop = true; - - cameraZoomIn(1.f); - updateFocusOffset(); - // NOTE: panning movements expect the camera to move exactly with the focus target, not animated behind -Nyx - mCameraSmoothingLastPositionGlobal = calcCameraPositionTargetGlobal(); -} - -void LLAgentCamera::resetCameraPan() -{ - mFocusTargetGlobal -= mPanFocusDiff; - - mFocusGlobal = mFocusTargetGlobal; - mCameraSmoothingStop = true; - - cameraZoomIn(1.f); - updateFocusOffset(); - - mCameraSmoothingLastPositionGlobal = calcCameraPositionTargetGlobal(); - - resetPanDiff(); -} - -void LLAgentCamera::resetPanDiff() -{ - mPanFocusDiff.clear(); -} - -//----------------------------------------------------------------------------- -// updateLookAt() -//----------------------------------------------------------------------------- -void LLAgentCamera::updateLookAt(const S32 mouse_x, const S32 mouse_y) -{ - static LLVector3 last_at_axis; - - if (!isAgentAvatarValid()) return; - - LLQuaternion av_inv_rot = ~gAgentAvatarp->mRoot->getWorldRotation(); - LLVector3 root_at = LLVector3::x_axis * gAgentAvatarp->mRoot->getWorldRotation(); - - if (LLTrace::get_frame_recording().getLastRecording().getLastValue(*gViewerWindow->getMouseVelocityStat()) < 0.01f - && (root_at * last_at_axis > 0.95f)) - { - LLVector3 vel = gAgentAvatarp->getVelocity(); - if (vel.magVecSquared() > 4.f) - { - setLookAt(LOOKAT_TARGET_IDLE, gAgentAvatarp, vel * av_inv_rot); - } - else - { - // *FIX: rotate mframeagent by sit object's rotation? - LLQuaternion look_rotation = gAgentAvatarp->isSitting() ? gAgentAvatarp->getRenderRotation() : gAgent.getFrameAgent().getQuaternion(); // use camera's current rotation - LLVector3 look_offset = LLVector3(2.f, 0.f, 0.f) * look_rotation * av_inv_rot; - setLookAt(LOOKAT_TARGET_IDLE, gAgentAvatarp, look_offset); - } - last_at_axis = root_at; - return; - } - - last_at_axis = root_at; - - if (CAMERA_MODE_CUSTOMIZE_AVATAR == getCameraMode()) - { - setLookAt(LOOKAT_TARGET_NONE, gAgentAvatarp, LLVector3(-2.f, 0.f, 0.f)); - } - else - { - // Move head based on cursor position - ELookAtType lookAtType = LOOKAT_TARGET_NONE; - LLVector3 headLookAxis; - LLCoordFrame frameCamera = *((LLCoordFrame*)LLViewerCamera::getInstance()); - - if (cameraMouselook()) - { - lookAtType = LOOKAT_TARGET_MOUSELOOK; - } - else if (cameraThirdPerson()) - { - // range from -.5 to .5 - F32 x_from_center = - ((F32) mouse_x / (F32) gViewerWindow->getWorldViewWidthScaled() ) - 0.5f; - F32 y_from_center = - ((F32) mouse_y / (F32) gViewerWindow->getWorldViewHeightScaled() ) - 0.5f; - - frameCamera.yaw( - x_from_center * gSavedSettings.getF32("YawFromMousePosition") * DEG_TO_RAD); - frameCamera.pitch( - y_from_center * gSavedSettings.getF32("PitchFromMousePosition") * DEG_TO_RAD); - lookAtType = LOOKAT_TARGET_FREELOOK; - } - - headLookAxis = frameCamera.getAtAxis(); - // RN: we use world-space offset for mouselook and freelook - //headLookAxis = headLookAxis * av_inv_rot; - setLookAt(lookAtType, gAgentAvatarp, headLookAxis); - } -} - -static LLTrace::BlockTimerStatHandle FTM_UPDATE_CAMERA("Camera"); - -extern bool gCubeSnapshot; - -//----------------------------------------------------------------------------- -// updateCamera() -//----------------------------------------------------------------------------- -void LLAgentCamera::updateCamera() -{ - LL_RECORD_BLOCK_TIME(FTM_UPDATE_CAMERA); - if (gCubeSnapshot) - { - return; - } - - // - changed camera_skyward to the new global "mCameraUpVector" - mCameraUpVector = LLVector3::z_axis; - //LLVector3 camera_skyward(0.f, 0.f, 1.f); - - U32 camera_mode = mCameraAnimating ? mLastCameraMode : mCameraMode; - - validateFocusObject(); - - if (isAgentAvatarValid() && - gAgentAvatarp->isSitting() && - camera_mode == CAMERA_MODE_MOUSELOOK) - { - //changed camera_skyward to the new global "mCameraUpVector" - mCameraUpVector = mCameraUpVector * gAgentAvatarp->getRenderRotation(); - } - - if (cameraThirdPerson() && (mFocusOnAvatar || mAllowChangeToFollow) && LLFollowCamMgr::getInstance()->getActiveFollowCamParams()) - { - mAllowChangeToFollow = false; - mFocusOnAvatar = true; - changeCameraToFollow(); - } - - //NOTE - this needs to be integrated into a general upVector system here within llAgent. - if ( camera_mode == CAMERA_MODE_FOLLOW && mFocusOnAvatar ) - { - mCameraUpVector = mFollowCam.getUpVector(); - } - - if (mSitCameraEnabled) - { - if (mSitCameraReferenceObject->isDead()) - { - setSitCamera(LLUUID::null); - } - } - - // Update UI with our camera inputs - LLFloaterCamera* camera_floater = LLFloaterReg::findTypedInstance("camera"); - if (camera_floater) - { - camera_floater->mRotate->setToggleState(gAgentCamera.getOrbitRightKey() > 0.f, // left - gAgentCamera.getOrbitUpKey() > 0.f, // top - gAgentCamera.getOrbitLeftKey() > 0.f, // right - gAgentCamera.getOrbitDownKey() > 0.f); // bottom - - camera_floater->mTrack->setToggleState(gAgentCamera.getPanLeftKey() > 0.f, // left - gAgentCamera.getPanUpKey() > 0.f, // top - gAgentCamera.getPanRightKey() > 0.f, // right - gAgentCamera.getPanDownKey() > 0.f); // bottom - } - - // Handle camera movement based on keyboard. - const F32 ORBIT_OVER_RATE = 90.f * DEG_TO_RAD; // radians per second - const F32 ORBIT_AROUND_RATE = 90.f * DEG_TO_RAD; // radians per second - const F32 PAN_RATE = 5.f; // meters per second - - if (gAgentCamera.getOrbitUpKey() || gAgentCamera.getOrbitDownKey()) - { - F32 input_rate = gAgentCamera.getOrbitUpKey() - gAgentCamera.getOrbitDownKey(); - cameraOrbitOver( input_rate * ORBIT_OVER_RATE / gFPSClamped ); - } - - if (gAgentCamera.getOrbitLeftKey() || gAgentCamera.getOrbitRightKey()) - { - F32 input_rate = gAgentCamera.getOrbitLeftKey() - gAgentCamera.getOrbitRightKey(); - cameraOrbitAround(input_rate * ORBIT_AROUND_RATE / gFPSClamped); - } - - if (gAgentCamera.getOrbitInKey() || gAgentCamera.getOrbitOutKey()) - { - F32 input_rate = gAgentCamera.getOrbitInKey() - gAgentCamera.getOrbitOutKey(); - - LLVector3d to_focus = gAgent.getPosGlobalFromAgent(LLViewerCamera::getInstance()->getOrigin()) - calcFocusPositionTargetGlobal(); - F32 distance_to_focus = (F32)to_focus.magVec(); - // Move at distance (in meters) meters per second - cameraOrbitIn( input_rate * distance_to_focus / gFPSClamped ); - } - - if (gAgentCamera.getPanInKey() || gAgentCamera.getPanOutKey()) - { - F32 input_rate = gAgentCamera.getPanInKey() - gAgentCamera.getPanOutKey(); - cameraPanIn(input_rate * PAN_RATE / gFPSClamped); - } - - if (gAgentCamera.getPanRightKey() || gAgentCamera.getPanLeftKey()) - { - F32 input_rate = gAgentCamera.getPanRightKey() - gAgentCamera.getPanLeftKey(); - cameraPanLeft(input_rate * -PAN_RATE / gFPSClamped ); - } - - if (gAgentCamera.getPanUpKey() || gAgentCamera.getPanDownKey()) - { - F32 input_rate = gAgentCamera.getPanUpKey() - gAgentCamera.getPanDownKey(); - cameraPanUp(input_rate * PAN_RATE / gFPSClamped ); - } - - // Clear camera keyboard keys. - gAgentCamera.clearOrbitKeys(); - gAgentCamera.clearPanKeys(); - - // lerp camera focus offset - mCameraFocusOffset = lerp(mCameraFocusOffset, mCameraFocusOffsetTarget, LLSmoothInterpolation::getInterpolant(CAMERA_FOCUS_HALF_LIFE)); - - if ( mCameraMode == CAMERA_MODE_FOLLOW ) - { - if (isAgentAvatarValid()) - { - //-------------------------------------------------------------------------------- - // this is where the avatar's position and rotation are given to followCam, and - // where it is updated. All three of its attributes are updated: (1) position, - // (2) focus, and (3) upvector. They can then be queried elsewhere in llAgent. - //-------------------------------------------------------------------------------- - // *TODO: use combined rotation of frameagent and sit object - LLQuaternion avatarRotationForFollowCam = gAgentAvatarp->isSitting() ? gAgentAvatarp->getRenderRotation() : gAgent.getFrameAgent().getQuaternion(); - - LLFollowCamParams* current_cam = LLFollowCamMgr::getInstance()->getActiveFollowCamParams(); - if (current_cam) - { - mFollowCam.copyParams(*current_cam); - mFollowCam.setSubjectPositionAndRotation( gAgentAvatarp->getRenderPosition(), avatarRotationForFollowCam ); - mFollowCam.update(); - LLViewerJoystick::getInstance()->setCameraNeedsUpdate(true); - } - else - { - changeCameraToThirdPerson(true); - } - } - } - - bool hit_limit; - LLVector3d camera_pos_global; - LLVector3d camera_target_global = calcCameraPositionTargetGlobal(&hit_limit); - mCameraVirtualPositionAgent = gAgent.getPosAgentFromGlobal(camera_target_global); - LLVector3d focus_target_global = calcFocusPositionTargetGlobal(); - - // perform field of view correction - mCameraFOVZoomFactor = calcCameraFOVZoomFactor(); - camera_target_global = focus_target_global + (camera_target_global - focus_target_global) * (1.f + mCameraFOVZoomFactor); - - gAgent.setShowAvatar(true); // can see avatar by default - - // Adjust position for animation - if (mCameraAnimating) - { - F32 time = mAnimationTimer.getElapsedTimeF32(); - - // yet another instance of critically damped motion, hooray! - // F32 fraction_of_animation = 1.f - pow(2.f, -time / CAMERA_ZOOM_HALF_LIFE); - - // linear interpolation - F32 fraction_of_animation = time / mAnimationDuration; - - bool isfirstPerson = mCameraMode == CAMERA_MODE_MOUSELOOK; - bool wasfirstPerson = mLastCameraMode == CAMERA_MODE_MOUSELOOK; - F32 fraction_animation_to_skip; - - if (mAnimationCameraStartGlobal == camera_target_global) - { - fraction_animation_to_skip = 0.f; - } - else - { - LLVector3d cam_delta = mAnimationCameraStartGlobal - camera_target_global; - fraction_animation_to_skip = HEAD_BUFFER_SIZE / (F32)cam_delta.magVec(); - } - F32 animation_start_fraction = (wasfirstPerson) ? fraction_animation_to_skip : 0.f; - F32 animation_finish_fraction = (isfirstPerson) ? (1.f - fraction_animation_to_skip) : 1.f; - - if (fraction_of_animation < animation_finish_fraction) - { - if (fraction_of_animation < animation_start_fraction || fraction_of_animation > animation_finish_fraction ) - { - gAgent.setShowAvatar(false); - } - - // ...adjust position for animation - F32 smooth_fraction_of_animation = llsmoothstep(0.0f, 1.0f, fraction_of_animation); - camera_pos_global = lerp(mAnimationCameraStartGlobal, camera_target_global, smooth_fraction_of_animation); - mFocusGlobal = lerp(mAnimationFocusStartGlobal, focus_target_global, smooth_fraction_of_animation); - } - else - { - // ...animation complete - mCameraAnimating = false; - - camera_pos_global = camera_target_global; - mFocusGlobal = focus_target_global; - - gAgent.endAnimationUpdateUI(); - gAgent.setShowAvatar(true); - } - - if (isAgentAvatarValid() && (mCameraMode != CAMERA_MODE_MOUSELOOK)) - { - gAgentAvatarp->updateAttachmentVisibility(mCameraMode); - } - } - else - { - camera_pos_global = camera_target_global; - mFocusGlobal = focus_target_global; - gAgent.setShowAvatar(true); - } - - // smoothing - if (true) - { - LLVector3d agent_pos = gAgent.getPositionGlobal(); - LLVector3d camera_pos_agent = camera_pos_global - agent_pos; - // Sitting on what you're manipulating can cause camera jitter with smoothing. - // This turns off smoothing while editing. -MG - bool in_build_mode = LLToolMgr::getInstance()->inBuildMode(); - mCameraSmoothingStop = mCameraSmoothingStop || in_build_mode; - - if (cameraThirdPerson() && !mCameraSmoothingStop) - { - const F32 SMOOTHING_HALF_LIFE = 0.02f; - - F32 smoothing = LLSmoothInterpolation::getInterpolant(gSavedSettings.getF32("CameraPositionSmoothing") * SMOOTHING_HALF_LIFE, false); - - if (mFocusOnAvatar && !mFocusObject) // we differentiate on avatar mode - { - // for avatar-relative focus, we smooth in avatar space - - // the avatar moves too jerkily w/r/t global space to smooth there. - - LLVector3d delta = camera_pos_agent - mCameraSmoothingLastPositionAgent; - if (delta.magVec() < MAX_CAMERA_SMOOTH_DISTANCE) // only smooth over short distances please - { - camera_pos_agent = lerp(mCameraSmoothingLastPositionAgent, camera_pos_agent, smoothing); - camera_pos_global = camera_pos_agent + agent_pos; - } - } - else - { - LLVector3d delta = camera_pos_global - mCameraSmoothingLastPositionGlobal; - if (delta.magVec() < MAX_CAMERA_SMOOTH_DISTANCE) // only smooth over short distances please - { - camera_pos_global = lerp(mCameraSmoothingLastPositionGlobal, camera_pos_global, smoothing); - } - } - } - - mCameraSmoothingLastPositionGlobal = camera_pos_global; - mCameraSmoothingLastPositionAgent = camera_pos_agent; - mCameraSmoothingStop = false; - } - - - mCameraCurrentFOVZoomFactor = lerp(mCameraCurrentFOVZoomFactor, mCameraFOVZoomFactor, LLSmoothInterpolation::getInterpolant(FOV_ZOOM_HALF_LIFE)); - -// LL_INFOS() << "Current FOV Zoom: " << mCameraCurrentFOVZoomFactor << " Target FOV Zoom: " << mCameraFOVZoomFactor << " Object penetration: " << mFocusObjectDist << LL_ENDL; - - LLVector3 focus_agent = gAgent.getPosAgentFromGlobal(mFocusGlobal); - - mCameraPositionAgent = gAgent.getPosAgentFromGlobal(camera_pos_global); - - // Move the camera - - LLViewerCamera::getInstance()->updateCameraLocation(mCameraPositionAgent, mCameraUpVector, focus_agent); - //LLViewerCamera::getInstance()->updateCameraLocation(mCameraPositionAgent, camera_skyward, focus_agent); - - // Change FOV - LLViewerCamera::getInstance()->setView(LLViewerCamera::getInstance()->getDefaultFOV() / (1.f + mCameraCurrentFOVZoomFactor)); - - // follow camera when in customize mode - if (cameraCustomizeAvatar()) - { - setLookAt(LOOKAT_TARGET_FOCUS, NULL, mCameraPositionAgent); - } - - // update the travel distance stat - // this isn't directly related to the camera - // but this seemed like the best place to do this - LLVector3d global_pos = gAgent.getPositionGlobal(); - if (!gAgent.getLastPositionGlobal().isExactlyZero()) - { - LLVector3d delta = global_pos - gAgent.getLastPositionGlobal(); - gAgent.setDistanceTraveled(gAgent.getDistanceTraveled() + delta.magVec()); - } - gAgent.setLastPositionGlobal(global_pos); - - if (LLVOAvatar::sVisibleInFirstPerson && isAgentAvatarValid() && !gAgentAvatarp->isSitting() && cameraMouselook()) - { - LLVector3 head_pos = gAgentAvatarp->mHeadp->getWorldPosition() + - LLVector3(0.08f, 0.f, 0.05f) * gAgentAvatarp->mHeadp->getWorldRotation() + - LLVector3(0.1f, 0.f, 0.f) * gAgentAvatarp->mPelvisp->getWorldRotation(); - LLVector3 diff = mCameraPositionAgent - head_pos; - diff = diff * ~gAgentAvatarp->mRoot->getWorldRotation(); - - LLJoint* torso_joint = gAgentAvatarp->mTorsop; - LLJoint* chest_joint = gAgentAvatarp->mChestp; - LLVector3 torso_scale = torso_joint->getScale(); - LLVector3 chest_scale = chest_joint->getScale(); - - // shorten avatar skeleton to avoid foot interpenetration - if (!gAgentAvatarp->mInAir) - { - LLVector3 chest_offset = LLVector3(0.f, 0.f, chest_joint->getPosition().mV[VZ]) * torso_joint->getWorldRotation(); - F32 z_compensate = llclamp(-diff.mV[VZ], -0.2f, 1.f); - F32 scale_factor = llclamp(1.f - ((z_compensate * 0.5f) / chest_offset.mV[VZ]), 0.5f, 1.2f); - torso_joint->setScale(LLVector3(1.f, 1.f, scale_factor)); - - LLJoint* neck_joint = gAgentAvatarp->mNeckp; - LLVector3 neck_offset = LLVector3(0.f, 0.f, neck_joint->getPosition().mV[VZ]) * chest_joint->getWorldRotation(); - scale_factor = llclamp(1.f - ((z_compensate * 0.5f) / neck_offset.mV[VZ]), 0.5f, 1.2f); - chest_joint->setScale(LLVector3(1.f, 1.f, scale_factor)); - diff.mV[VZ] = 0.f; - } - - // SL-315 - gAgentAvatarp->mPelvisp->setPosition(gAgentAvatarp->mPelvisp->getPosition() + diff); - - gAgentAvatarp->mRoot->updateWorldMatrixChildren(); - - for (LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); - iter != gAgentAvatarp->mAttachmentPoints.end(); ) - { - LLVOAvatar::attachment_map_t::iterator curiter = iter++; - LLViewerJointAttachment* attachment = curiter->second; - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - LLViewerObject *attached_object = attachment_iter->get(); - if (attached_object && !attached_object->isDead() && attached_object->mDrawable.notNull()) - { - // clear any existing "early" movements of attachment - attached_object->mDrawable->clearState(LLDrawable::EARLY_MOVE); - gPipeline.updateMoveNormalAsync(attached_object->mDrawable); - attached_object->updateText(); - } - } - } - - torso_joint->setScale(torso_scale); - chest_joint->setScale(chest_scale); - } -} - -void LLAgentCamera::updateLastCamera() -{ - mLastCameraMode = mCameraMode; -} - -void LLAgentCamera::updateFocusOffset() -{ - validateFocusObject(); - if (mFocusObject.notNull()) - { - LLVector3d obj_pos = gAgent.getPosGlobalFromAgent(mFocusObject->getRenderPosition()); - mFocusObjectOffset.setVec(mFocusTargetGlobal - obj_pos); - } -} - -void LLAgentCamera::validateFocusObject() -{ - if (mFocusObject.notNull() && - mFocusObject->isDead()) - { - mFocusObjectOffset.clearVec(); - clearFocusObject(); - mCameraFOVZoomFactor = 0.f; - } -} - -//----------------------------------------------------------------------------- -// calcFocusPositionTargetGlobal() -//----------------------------------------------------------------------------- -LLVector3d LLAgentCamera::calcFocusPositionTargetGlobal() -{ - if (mFocusObject.notNull() && mFocusObject->isDead()) - { - clearFocusObject(); - } - - if (mCameraMode == CAMERA_MODE_FOLLOW && mFocusOnAvatar) - { - mFocusTargetGlobal = gAgent.getPosGlobalFromAgent(mFollowCam.getSimulatedFocus()); - return mFocusTargetGlobal; - } - else if (mCameraMode == CAMERA_MODE_MOUSELOOK) - { - LLVector3d at_axis(1.0, 0.0, 0.0); - LLQuaternion agent_rot = gAgent.getFrameAgent().getQuaternion(); - if (isAgentAvatarValid() && gAgentAvatarp->getParent()) - { - LLViewerObject* root_object = (LLViewerObject*)gAgentAvatarp->getRoot(); - if (!root_object->flagCameraDecoupled()) - { - agent_rot *= ((LLViewerObject*)(gAgentAvatarp->getParent()))->getRenderRotation(); - } - } - at_axis = at_axis * agent_rot; - mFocusTargetGlobal = calcCameraPositionTargetGlobal() + at_axis; - return mFocusTargetGlobal; - } - else if (mCameraMode == CAMERA_MODE_CUSTOMIZE_AVATAR) - { - if (mFocusOnAvatar) - { - LLVector3 focus_target = isAgentAvatarValid() - ? gAgentAvatarp->mHeadp->getWorldPosition() - : gAgent.getPositionAgent(); - LLVector3d focus_target_global = gAgent.getPosGlobalFromAgent(focus_target); - mFocusTargetGlobal = focus_target_global; - } - return mFocusTargetGlobal; - } - else if (!mFocusOnAvatar) - { - if (mFocusObject.notNull() && !mFocusObject->isDead() && mFocusObject->mDrawable.notNull()) - { - LLDrawable* drawablep = mFocusObject->mDrawable; - - if (mTrackFocusObject && - drawablep && - drawablep->isActive()) - { - if (!mFocusObject->isAvatar()) - { - if (mFocusObject->isSelected()) - { - gPipeline.updateMoveNormalAsync(drawablep); - } - else - { - if (drawablep->isState(LLDrawable::MOVE_UNDAMPED)) - { - gPipeline.updateMoveNormalAsync(drawablep); - } - else - { - gPipeline.updateMoveDampedAsync(drawablep); - } - } - } - } - // if not tracking object, update offset based on new object position - else - { - updateFocusOffset(); - } - LLVector3 focus_agent = mFocusObject->getRenderPosition() + mFocusObjectOffset; - mFocusTargetGlobal.setVec(gAgent.getPosGlobalFromAgent(focus_agent)); - } - return mFocusTargetGlobal; - } - else if (mSitCameraEnabled && isAgentAvatarValid() && gAgentAvatarp->isSitting() && mSitCameraReferenceObject.notNull()) - { - // sit camera - LLVector3 object_pos = mSitCameraReferenceObject->getRenderPosition(); - LLQuaternion object_rot = mSitCameraReferenceObject->getRenderRotation(); - - LLVector3 target_pos = object_pos + (mSitCameraFocus * object_rot); - return gAgent.getPosGlobalFromAgent(target_pos); - } - else - { - return gAgent.getPositionGlobal() + calcThirdPersonFocusOffset(); - } -} - -LLVector3d LLAgentCamera::calcThirdPersonFocusOffset() -{ - // ...offset from avatar - LLVector3d focus_offset; - LLQuaternion agent_rot = gAgent.getFrameAgent().getQuaternion(); - if (isAgentAvatarValid() && gAgentAvatarp->getParent()) - { - agent_rot *= ((LLViewerObject*)(gAgentAvatarp->getParent()))->getRenderRotation(); - } - - static LLCachedControl focus_offset_initial(gSavedSettings, "FocusOffsetRearView", LLVector3d()); - return focus_offset_initial * agent_rot; -} - -void LLAgentCamera::setupSitCamera() -{ - // agent frame entering this function is in world coordinates - if (isAgentAvatarValid() && gAgentAvatarp->getParent()) - { - LLQuaternion parent_rot = ((LLViewerObject*)gAgentAvatarp->getParent())->getRenderRotation(); - // slam agent coordinate frame to proper parent local version - LLVector3 at_axis = gAgent.getFrameAgent().getAtAxis(); - at_axis.mV[VZ] = 0.f; - at_axis.normalize(); - gAgent.resetAxes(at_axis * ~parent_rot); - } -} - -//----------------------------------------------------------------------------- -// getCameraPositionAgent() -//----------------------------------------------------------------------------- -const LLVector3 &LLAgentCamera::getCameraPositionAgent() const -{ - return LLViewerCamera::getInstance()->getOrigin(); -} - -//----------------------------------------------------------------------------- -// getCameraPositionGlobal() -//----------------------------------------------------------------------------- -LLVector3d LLAgentCamera::getCameraPositionGlobal() const -{ - return gAgent.getPosGlobalFromAgent(LLViewerCamera::getInstance()->getOrigin()); -} - -//----------------------------------------------------------------------------- -// calcCameraFOVZoomFactor() -//----------------------------------------------------------------------------- -F32 LLAgentCamera::calcCameraFOVZoomFactor() -{ - LLVector3 camera_offset_dir; - camera_offset_dir.setVec(mCameraFocusOffset); - - if (mCameraMode == CAMERA_MODE_MOUSELOOK) - { - return 0.f; - } - else if (mFocusObject.notNull() && !mFocusObject->isAvatar() && !mFocusOnAvatar) - { - // don't FOV zoom on mostly transparent objects - F32 obj_min_dist = 0.f; - calcCameraMinDistance(obj_min_dist); - F32 current_distance = llmax(0.001f, camera_offset_dir.magVec()); - - mFocusObjectDist = obj_min_dist - current_distance; - - F32 new_fov_zoom = llclamp(mFocusObjectDist / current_distance, 0.f, 1000.f); - return new_fov_zoom; - } - else // focusing on land or avatar - { - // keep old field of view until user changes focus explicitly - return mCameraFOVZoomFactor; - //return 0.f; - } -} - -//----------------------------------------------------------------------------- -// calcCameraPositionTargetGlobal() -//----------------------------------------------------------------------------- -LLVector3d LLAgentCamera::calcCameraPositionTargetGlobal(bool *hit_limit) -{ - // Compute base camera position and look-at points. - F32 camera_land_height; - LLVector3d frame_center_global = !isAgentAvatarValid() ? - gAgent.getPositionGlobal() : - gAgent.getPosGlobalFromAgent(getAvatarRootPosition()); - - bool isConstrained = false; - LLVector3d head_offset; - head_offset.setVec(mThirdPersonHeadOffset); - - LLVector3d camera_position_global; - - if (mCameraMode == CAMERA_MODE_FOLLOW && mFocusOnAvatar) - { - camera_position_global = gAgent.getPosGlobalFromAgent(mFollowCam.getSimulatedPosition()); - } - else if (mCameraMode == CAMERA_MODE_MOUSELOOK) - { - if (!isAgentAvatarValid() || gAgentAvatarp->mDrawable.isNull()) - { - LL_WARNS() << "Null avatar drawable!" << LL_ENDL; - return LLVector3d::zero; - } - - head_offset.clearVec(); - F32 fixup; - if (gAgentAvatarp->hasPelvisFixup(fixup) && !gAgentAvatarp->isSitting()) - { - head_offset[VZ] -= fixup; - } - if (gAgentAvatarp->isSitting()) - { - head_offset.mdV[VZ] += 0.1; - } - - if (gAgentAvatarp->isSitting() && gAgentAvatarp->getParent()) - { - gAgentAvatarp->updateHeadOffset(); - head_offset.mdV[VX] += gAgentAvatarp->mHeadOffset.mV[VX]; - head_offset.mdV[VY] += gAgentAvatarp->mHeadOffset.mV[VY]; - head_offset.mdV[VZ] += gAgentAvatarp->mHeadOffset.mV[VZ]; - const LLMatrix4& mat = ((LLViewerObject*) gAgentAvatarp->getParent())->getRenderMatrix(); - camera_position_global = gAgent.getPosGlobalFromAgent - ((gAgentAvatarp->getPosition()+ - LLVector3(head_offset)*gAgentAvatarp->getRotation()) * mat); - } - else - { - head_offset.mdV[VZ] += gAgentAvatarp->mHeadOffset.mV[VZ]; - camera_position_global = gAgent.getPosGlobalFromAgent(gAgentAvatarp->getRenderPosition());//frame_center_global; - head_offset = head_offset * gAgentAvatarp->getRenderRotation(); - camera_position_global = camera_position_global + head_offset; - } - } - else if (mCameraMode == CAMERA_MODE_THIRD_PERSON && mFocusOnAvatar) - { - LLVector3 local_camera_offset; - F32 camera_distance = 0.f; - - if (mSitCameraEnabled - && isAgentAvatarValid() - && gAgentAvatarp->isSitting() - && mSitCameraReferenceObject.notNull()) - { - // sit camera - LLVector3 object_pos = mSitCameraReferenceObject->getRenderPosition(); - LLQuaternion object_rot = mSitCameraReferenceObject->getRenderRotation(); - - LLVector3 target_pos = object_pos + (mSitCameraPos * object_rot); - - camera_position_global = gAgent.getPosGlobalFromAgent(target_pos); - } - else - { - static LLCachedControl camera_offset_scale(gSavedSettings, "CameraOffsetScale"); - local_camera_offset = mCameraZoomFraction * getCameraOffsetInitial() * camera_offset_scale; - - // are we sitting down? - if (isAgentAvatarValid() && gAgentAvatarp->getParent()) - { - LLQuaternion parent_rot = ((LLViewerObject*)gAgentAvatarp->getParent())->getRenderRotation(); - // slam agent coordinate frame to proper parent local version - LLVector3 at_axis = gAgent.getFrameAgent().getAtAxis() * parent_rot; - at_axis.mV[VZ] = 0.f; - at_axis.normalize(); - gAgent.resetAxes(at_axis * ~parent_rot); - - local_camera_offset = local_camera_offset * gAgent.getFrameAgent().getQuaternion() * parent_rot; - } - else - { - local_camera_offset = gAgent.getFrameAgent().rotateToAbsolute( local_camera_offset ); - } - - if (!isDisableCameraConstraints() && !mCameraCollidePlane.isExactlyZero() && - (!isAgentAvatarValid() || !gAgentAvatarp->isSitting())) - { - LLVector3 plane_normal; - plane_normal.setVec(mCameraCollidePlane.mV); - - F32 offset_dot_norm = local_camera_offset * plane_normal; - if (llabs(offset_dot_norm) < 0.001f) - { - offset_dot_norm = 0.001f; - } - - camera_distance = local_camera_offset.normalize(); - - F32 pos_dot_norm = gAgent.getPosAgentFromGlobal(frame_center_global + head_offset) * plane_normal; - - // if agent is outside the colliding half-plane - if (pos_dot_norm > mCameraCollidePlane.mV[VW]) - { - // check to see if camera is on the opposite side (inside) the half-plane - if (offset_dot_norm + pos_dot_norm < mCameraCollidePlane.mV[VW]) - { - // diminish offset by factor to push it back outside the half-plane - camera_distance *= (pos_dot_norm - mCameraCollidePlane.mV[VW] - CAMERA_COLLIDE_EPSILON) / -offset_dot_norm; - } - } - else - { - if (offset_dot_norm + pos_dot_norm > mCameraCollidePlane.mV[VW]) - { - camera_distance *= (mCameraCollidePlane.mV[VW] - pos_dot_norm - CAMERA_COLLIDE_EPSILON) / offset_dot_norm; - } - } - } - else - { - camera_distance = local_camera_offset.normalize(); - } - - mTargetCameraDistance = llmax(camera_distance, MIN_CAMERA_DISTANCE); - - if (mTargetCameraDistance != mCurrentCameraDistance) - { - F32 camera_lerp_amt = LLSmoothInterpolation::getInterpolant(CAMERA_ZOOM_HALF_LIFE); - - mCurrentCameraDistance = lerp(mCurrentCameraDistance, mTargetCameraDistance, camera_lerp_amt); - } - - // Make the camera distance current - local_camera_offset *= mCurrentCameraDistance; - - // set the global camera position - LLVector3d camera_offset; - - camera_offset.setVec( local_camera_offset ); - camera_position_global = frame_center_global + head_offset + camera_offset; - - if (isAgentAvatarValid()) - { - LLVector3d camera_lag_d; - F32 lag_interp = LLSmoothInterpolation::getInterpolant(CAMERA_LAG_HALF_LIFE); - LLVector3 target_lag; - LLVector3 vel = gAgent.getVelocity(); - - // lag by appropriate amount for flying - F32 time_in_air = gAgentAvatarp->mTimeInAir.getElapsedTimeF32(); - if(!mCameraAnimating && gAgentAvatarp->mInAir && time_in_air > GROUND_TO_AIR_CAMERA_TRANSITION_START_TIME) - { - LLVector3 frame_at_axis = gAgent.getFrameAgent().getAtAxis(); - frame_at_axis -= projected_vec(frame_at_axis, gAgent.getReferenceUpVector()); - frame_at_axis.normalize(); - - //transition smoothly in air mode, to avoid camera pop - F32 u = (time_in_air - GROUND_TO_AIR_CAMERA_TRANSITION_START_TIME) / GROUND_TO_AIR_CAMERA_TRANSITION_TIME; - u = llclamp(u, 0.f, 1.f); - - lag_interp *= u; - - if (gViewerWindow->getLeftMouseDown() && gViewerWindow->getLastPick().mObjectID == gAgentAvatarp->getID()) - { - // disable camera lag when using mouse-directed steering - target_lag.clearVec(); - } - else - { - LLCachedControl dynamic_camera_strength(gSavedSettings, "DynamicCameraStrength"); - target_lag = vel * dynamic_camera_strength / 30.f; - } - - mCameraLag = lerp(mCameraLag, target_lag, lag_interp); - - F32 lag_dist = mCameraLag.magVec(); - if (lag_dist > MAX_CAMERA_LAG) - { - mCameraLag = mCameraLag * MAX_CAMERA_LAG / lag_dist; - } - - // clamp camera lag so that avatar is always in front - F32 dot = (mCameraLag - (frame_at_axis * (MIN_CAMERA_LAG * u))) * frame_at_axis; - if (dot < -(MIN_CAMERA_LAG * u)) - { - mCameraLag -= (dot + (MIN_CAMERA_LAG * u)) * frame_at_axis; - } - } - else - { - mCameraLag = lerp(mCameraLag, LLVector3::zero, LLSmoothInterpolation::getInterpolant(0.15f)); - } - - camera_lag_d.setVec(mCameraLag); - camera_position_global = camera_position_global - camera_lag_d; - } - } - } - else - { - LLVector3d focusPosGlobal = calcFocusPositionTargetGlobal(); - // camera gets pushed out later wrt mCameraFOVZoomFactor...this is "raw" value - camera_position_global = focusPosGlobal + mCameraFocusOffset; - } - - if (!isDisableCameraConstraints() && !gAgent.isGodlike()) - { - LLViewerRegion* regionp = LLWorld::getInstance()->getRegionFromPosGlobal(camera_position_global); - bool constrain = true; - if(regionp && regionp->canManageEstate()) - { - constrain = false; - } - if(constrain) - { - F32 max_dist = (CAMERA_MODE_CUSTOMIZE_AVATAR == mCameraMode) ? APPEARANCE_MAX_ZOOM : mDrawDistance; - - LLVector3d camera_offset = camera_position_global - gAgent.getPositionGlobal(); - F32 camera_distance = (F32)camera_offset.magVec(); - - if(camera_distance > max_dist) - { - camera_position_global = gAgent.getPositionGlobal() + (max_dist/camera_distance)*camera_offset; - isConstrained = true; - } - } - -// JC - Could constrain camera based on parcel stuff here. -// LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromPosGlobal(camera_position_global); -// -// if (regionp && !regionp->mParcelOverlay->isBuildCameraAllowed(regionp->getPosRegionFromGlobal(camera_position_global))) -// { -// camera_position_global = last_position_global; -// -// isConstrained = true; -// } - } - - // Don't let camera go underground - F32 camera_min_off_ground = getCameraMinOffGround(); - camera_land_height = LLWorld::getInstance()->resolveLandHeightGlobal(camera_position_global); - F32 minZ = llmax(F_ALMOST_ZERO, camera_land_height + camera_min_off_ground); - if (camera_position_global.mdV[VZ] < minZ) - { - camera_position_global.mdV[VZ] = minZ; - isConstrained = true; - } - - if (hit_limit) - { - *hit_limit = isConstrained; - } - - return camera_position_global; -} - - -LLVector3 LLAgentCamera::getCurrentCameraOffset() -{ - return (LLViewerCamera::getInstance()->getOrigin() - getAvatarRootPosition() - mThirdPersonHeadOffset) * ~getCurrentAvatarRotation(); -} - -LLVector3d LLAgentCamera::getCurrentFocusOffset() -{ - return (mFocusTargetGlobal - gAgent.getPositionGlobal()) * ~getCurrentAvatarRotation(); -} - -LLQuaternion LLAgentCamera::getCurrentAvatarRotation() -{ - LLViewerObject* sit_object = (LLViewerObject*)gAgentAvatarp->getParent(); - - LLQuaternion av_rot = gAgent.getFrameAgent().getQuaternion(); - LLQuaternion obj_rot = sit_object ? sit_object->getRenderRotation() : LLQuaternion::DEFAULT; - return av_rot * obj_rot; -} - -bool LLAgentCamera::isJoystickCameraUsed() -{ - return ((mOrbitAroundRadians != 0) || (mOrbitOverAngle != 0) || !mPanFocusDiff.isNull()); -} - -LLVector3 LLAgentCamera::getCameraOffsetInitial() -{ - // getCameraOffsetInitial and getFocusOffsetInitial can be called on update from idle before init() - static LLCachedControl camera_offset_initial (gSavedSettings, "CameraOffsetRearView", LLVector3()); - return camera_offset_initial; -} - -LLVector3d LLAgentCamera::getFocusOffsetInitial() -{ - static LLCachedControl focus_offset_initial(gSavedSettings, "FocusOffsetRearView", LLVector3d()); - return focus_offset_initial; -} - -F32 LLAgentCamera::getCameraMaxZoomDistance() -{ - // SL-14706 / SL-14885 TPV have relaxed camera constraints allowing you to mousewheeel zoom WAY out. - static LLCachedControl s_disable_camera_constraints(gSavedSettings, "DisableCameraConstraints", false); - if (s_disable_camera_constraints) - { - return (F32)INT_MAX; - } - - // Ignore "DisableCameraConstraints", we don't want to be out of draw range when we focus onto objects or avatars - return llmin(MAX_CAMERA_DISTANCE_FROM_OBJECT, - mDrawDistance - 1, // convenience, don't hit draw limit when focusing on something - LLWorld::getInstance()->getRegionWidthInMeters() - CAMERA_FUDGE_FROM_OBJECT); -} - -LLVector3 LLAgentCamera::getAvatarRootPosition() -{ - static LLCachedControl use_hover_height(gSavedSettings, "HoverHeightAffectsCamera"); - return use_hover_height ? gAgentAvatarp->mRoot->getWorldPosition() : gAgentAvatarp->mRoot->getWorldPosition() - gAgentAvatarp->getHoverOffset(); - -} -//----------------------------------------------------------------------------- -// handleScrollWheel() -//----------------------------------------------------------------------------- -void LLAgentCamera::handleScrollWheel(S32 clicks) -{ - if (mCameraMode == CAMERA_MODE_FOLLOW && getFocusOnAvatar()) - { - if (!mFollowCam.getPositionLocked()) // not if the followCam position is locked in place - { - mFollowCam.zoom(clicks); - if (mFollowCam.isZoomedToMinimumDistance()) - { - changeCameraToMouselook(false); - } - } - } - else - { - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - const F32 ROOT_ROOT_TWO = sqrt(F_SQRT2); - - // Block if camera is animating - if (mCameraAnimating) - { - return; - } - - if (selection->getObjectCount() && selection->getSelectType() == SELECT_TYPE_HUD) - { - F32 zoom_factor = (F32)pow(0.8, -clicks); - cameraZoomIn(zoom_factor); - } - else if (mFocusOnAvatar && (mCameraMode == CAMERA_MODE_THIRD_PERSON)) - { - F32 camera_offset_initial_mag = getCameraOffsetInitial().magVec(); - - F32 current_zoom_fraction = mTargetCameraDistance / (camera_offset_initial_mag * gSavedSettings.getF32("CameraOffsetScale")); - current_zoom_fraction *= 1.f - pow(ROOT_ROOT_TWO, clicks); - - cameraOrbitIn(current_zoom_fraction * camera_offset_initial_mag * gSavedSettings.getF32("CameraOffsetScale")); - } - else - { - F32 current_zoom_fraction = (F32)mCameraFocusOffsetTarget.magVec(); - cameraOrbitIn(current_zoom_fraction * (1.f - pow(ROOT_ROOT_TWO, clicks))); - } - } -} - - -//----------------------------------------------------------------------------- -// getCameraMinOffGround() -//----------------------------------------------------------------------------- -F32 LLAgentCamera::getCameraMinOffGround() -{ - if (mCameraMode == CAMERA_MODE_MOUSELOOK) - { - return 0.f; - } - - if (isDisableCameraConstraints()) - { - return -1000.f; - } - - return 0.5f; -} - - -//----------------------------------------------------------------------------- -// resetCamera() -//----------------------------------------------------------------------------- -void LLAgentCamera::resetCamera() -{ - // Remove any pitch from the avatar - LLVector3 at = gAgent.getFrameAgent().getAtAxis(); - at.mV[VZ] = 0.f; - at.normalize(); - gAgent.resetAxes(at); - // have to explicitly clear field of view zoom now - mCameraFOVZoomFactor = 0.f; - - updateCamera(); -} - -//----------------------------------------------------------------------------- -// changeCameraToMouselook() -//----------------------------------------------------------------------------- -void LLAgentCamera::changeCameraToMouselook(bool animate) -{ - if (!gSavedSettings.getBOOL("EnableMouselook") - || LLViewerJoystick::getInstance()->getOverrideCamera()) - { - return; - } - - // visibility changes at end of animation - gViewerWindow->getWindow()->resetBusyCount(); - - // Menus should not remain open on switching to mouselook... - LLMenuGL::sMenuContainer->hideMenus(); - LLUI::getInstance()->clearPopups(); - - // unpause avatar animation - gAgent.unpauseAnimation(); - - LLToolMgr::getInstance()->setCurrentToolset(gMouselookToolset); - - if (isAgentAvatarValid()) - { - gAgentAvatarp->stopMotion(ANIM_AGENT_BODY_NOISE); - gAgentAvatarp->stopMotion(ANIM_AGENT_BREATHE_ROT); - } - - //gViewerWindow->stopGrab(); - LLSelectMgr::getInstance()->deselectAll(); - gViewerWindow->hideCursor(); - gViewerWindow->moveCursorToCenter(); - - if (mCameraMode != CAMERA_MODE_MOUSELOOK) - { - gFocusMgr.setKeyboardFocus(NULL); - - updateLastCamera(); - mCameraMode = CAMERA_MODE_MOUSELOOK; - const U32 old_flags = gAgent.getControlFlags(); - gAgent.setControlFlags(AGENT_CONTROL_MOUSELOOK); - if (old_flags != gAgent.getControlFlags()) - { - gAgent.setFlagsDirty(); - } - - if (animate) - { - startCameraAnimation(); - } - else - { - mCameraAnimating = false; - gAgent.endAnimationUpdateUI(); - } - } -} - - -//----------------------------------------------------------------------------- -// changeCameraToDefault() -//----------------------------------------------------------------------------- -void LLAgentCamera::changeCameraToDefault() -{ - if (LLViewerJoystick::getInstance()->getOverrideCamera()) - { - return; - } - - if (LLFollowCamMgr::getInstance()->getActiveFollowCamParams()) - { - changeCameraToFollow(); - } - else - { - changeCameraToThirdPerson(); - } - if (gSavedSettings.getBOOL("HideUIControls")) - { - gViewerWindow->setUIVisibility(false); - LLPanelStandStopFlying::getInstance()->setVisible(false); - } -} - - -//----------------------------------------------------------------------------- -// changeCameraToFollow() -//----------------------------------------------------------------------------- -void LLAgentCamera::changeCameraToFollow(bool animate) -{ - if (LLViewerJoystick::getInstance()->getOverrideCamera()) - { - return; - } - - if(mCameraMode != CAMERA_MODE_FOLLOW) - { - if (mCameraMode == CAMERA_MODE_MOUSELOOK) - { - animate = false; - } - startCameraAnimation(); - - updateLastCamera(); - mCameraMode = CAMERA_MODE_FOLLOW; - - // bang-in the current focus, position, and up vector of the follow cam - mFollowCam.reset(mCameraPositionAgent, LLViewerCamera::getInstance()->getPointOfInterest(), LLVector3::z_axis); - - if (gBasicToolset) - { - LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); - } - - if (isAgentAvatarValid()) - { - // SL-315 - gAgentAvatarp->mPelvisp->setPosition(LLVector3::zero); - gAgentAvatarp->startMotion( ANIM_AGENT_BODY_NOISE ); - gAgentAvatarp->startMotion( ANIM_AGENT_BREATHE_ROT ); - } - - // unpause avatar animation - gAgent.unpauseAnimation(); - - gAgent.clearControlFlags(AGENT_CONTROL_MOUSELOOK); - - if (animate) - { - startCameraAnimation(); - } - else - { - mCameraAnimating = false; - gAgent.endAnimationUpdateUI(); - } - } -} - -//----------------------------------------------------------------------------- -// changeCameraToThirdPerson() -//----------------------------------------------------------------------------- -void LLAgentCamera::changeCameraToThirdPerson(bool animate) -{ - if (LLViewerJoystick::getInstance()->getOverrideCamera()) - { - return; - } - - gViewerWindow->getWindow()->resetBusyCount(); - - mCameraZoomFraction = INITIAL_ZOOM_FRACTION; - - if (isAgentAvatarValid()) - { - if (!gAgentAvatarp->isSitting()) - { - // SL-315 - gAgentAvatarp->mPelvisp->setPosition(LLVector3::zero); - } - gAgentAvatarp->startMotion(ANIM_AGENT_BODY_NOISE); - gAgentAvatarp->startMotion(ANIM_AGENT_BREATHE_ROT); - } - - LLVector3 at_axis; - - // unpause avatar animation - gAgent.unpauseAnimation(); - - if (mCameraMode != CAMERA_MODE_THIRD_PERSON) - { - if (gBasicToolset) - { - LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); - } - - mCameraLag.clearVec(); - if (mCameraMode == CAMERA_MODE_MOUSELOOK) - { - mCurrentCameraDistance = MIN_CAMERA_DISTANCE; - mTargetCameraDistance = MIN_CAMERA_DISTANCE; - animate = false; - } - updateLastCamera(); - mCameraMode = CAMERA_MODE_THIRD_PERSON; - gAgent.clearControlFlags(AGENT_CONTROL_MOUSELOOK); - } - - // Remove any pitch from the avatar - if (!isAgentAvatarValid() || !gAgentAvatarp->getParent()) - { - at_axis = gAgent.getFrameAgent().getAtAxis(); - at_axis.mV[VZ] = 0.f; - at_axis.normalize(); - gAgent.resetAxes(at_axis); - } - - - if (animate) - { - startCameraAnimation(); - } - else - { - mCameraAnimating = false; - gAgent.endAnimationUpdateUI(); - } -} - -//----------------------------------------------------------------------------- -// changeCameraToCustomizeAvatar() -//----------------------------------------------------------------------------- -void LLAgentCamera::changeCameraToCustomizeAvatar() -{ - if (LLViewerJoystick::getInstance()->getOverrideCamera() || !isAgentAvatarValid()) - { - return; - } - - gAgent.standUp(); // force stand up - gViewerWindow->getWindow()->resetBusyCount(); - - if (LLSelectMgr::getInstance()->getSelection()->isAttachment()) - { - LLSelectMgr::getInstance()->deselectAll(); - } - - if (gFaceEditToolset) - { - LLToolMgr::getInstance()->setCurrentToolset(gFaceEditToolset); - } - - startCameraAnimation(); - - if (mCameraMode != CAMERA_MODE_CUSTOMIZE_AVATAR) - { - updateLastCamera(); - mCameraMode = CAMERA_MODE_CUSTOMIZE_AVATAR; - gAgent.clearControlFlags(AGENT_CONTROL_MOUSELOOK); - - gFocusMgr.setKeyboardFocus( NULL ); - gFocusMgr.setMouseCapture( NULL ); - if( gMorphView ) - { - gMorphView->setVisible( true ); - } - // Remove any pitch or rotation from the avatar - LLVector3 at = gAgent.getAtAxis(); - at.mV[VZ] = 0.f; - at.normalize(); - gAgent.resetAxes(at); - - gAgent.sendAnimationRequest(ANIM_AGENT_CUSTOMIZE, ANIM_REQUEST_START); - gAgent.setCustomAnim(true); - gAgentAvatarp->startMotion(ANIM_AGENT_CUSTOMIZE); - LLMotion* turn_motion = gAgentAvatarp->findMotion(ANIM_AGENT_CUSTOMIZE); - - if (turn_motion) - { - // delay camera animation long enough to play through turn animation - setAnimationDuration(turn_motion->getDuration() + CUSTOMIZE_AVATAR_CAMERA_ANIM_SLOP); - } - } - - LLVector3 agent_at = gAgent.getAtAxis(); - agent_at.mV[VZ] = 0.f; - agent_at.normalize(); - - // default focus point for customize avatar - LLVector3 focus_target = isAgentAvatarValid() - ? gAgentAvatarp->mHeadp->getWorldPosition() - : gAgent.getPositionAgent(); - - LLVector3d camera_offset(agent_at * -1.0); - // push camera up and out from avatar - camera_offset.mdV[VZ] = 0.1f; - camera_offset *= CUSTOMIZE_AVATAR_CAMERA_DEFAULT_DIST; - LLVector3d focus_target_global = gAgent.getPosGlobalFromAgent(focus_target); - setAnimationDuration(gSavedSettings.getF32("ZoomTime")); - setCameraPosAndFocusGlobal(focus_target_global + camera_offset, focus_target_global, gAgent.getID()); -} - - -void LLAgentCamera::switchCameraPreset(ECameraPreset preset) -{ - //zoom is supposed to be reset for the front and group views - mCameraZoomFraction = 1.f; - - //focusing on avatar in that case means following him on movements - mFocusOnAvatar = true; - - mCameraPreset = preset; - - resetPanDiff(); - resetOrbitDiff(); - - gSavedSettings.setU32("CameraPresetType", mCameraPreset); -} - - -// -// Focus point management -// - -void LLAgentCamera::setAnimationDuration(F32 duration) -{ - if (mCameraAnimating) - { - // do not cut any existing camera animation short - F32 animation_left = llmax(0.f, mAnimationDuration - mAnimationTimer.getElapsedTimeF32()); - mAnimationDuration = llmax(duration, animation_left); - } - else - { - mAnimationDuration = duration; - } -} - -//----------------------------------------------------------------------------- -// startCameraAnimation() -//----------------------------------------------------------------------------- -void LLAgentCamera::startCameraAnimation() -{ - mAnimationCameraStartGlobal = getCameraPositionGlobal(); - mAnimationFocusStartGlobal = mFocusGlobal; - setAnimationDuration(gSavedSettings.getF32("ZoomTime")); - mAnimationTimer.reset(); - mCameraAnimating = true; -} - -//----------------------------------------------------------------------------- -// stopCameraAnimation() -//----------------------------------------------------------------------------- -void LLAgentCamera::stopCameraAnimation() -{ - mCameraAnimating = false; -} - -void LLAgentCamera::clearFocusObject() -{ - if (mFocusObject.notNull()) - { - startCameraAnimation(); - - setFocusObject(NULL); - mFocusObjectOffset.clearVec(); - } -} - -void LLAgentCamera::setFocusObject(LLViewerObject* object) -{ - mFocusObject = object; -} - -// Focus on a point, but try to keep camera position stable. -//----------------------------------------------------------------------------- -// setFocusGlobal() -//----------------------------------------------------------------------------- -void LLAgentCamera::setFocusGlobal(const LLPickInfo& pick) -{ - LLViewerObject* objectp = gObjectList.findObject(pick.mObjectID); - - if (objectp) - { - // focus on object plus designated offset - // which may or may not be same as pick.mPosGlobal - setFocusGlobal(objectp->getPositionGlobal() + LLVector3d(pick.mObjectOffset), pick.mObjectID); - } - else - { - // focus directly on point where user clicked - setFocusGlobal(pick.mPosGlobal, pick.mObjectID); - } -} - - -void LLAgentCamera::setFocusGlobal(const LLVector3d& focus, const LLUUID &object_id) -{ - setFocusObject(gObjectList.findObject(object_id)); - LLVector3d old_focus = mFocusTargetGlobal; - LLViewerObject *focus_obj = mFocusObject; - - // if focus has changed - if (old_focus != focus) - { - if (focus.isExactlyZero()) - { - if (isAgentAvatarValid()) - { - mFocusTargetGlobal = gAgent.getPosGlobalFromAgent(gAgentAvatarp->mHeadp->getWorldPosition()); - } - else - { - mFocusTargetGlobal = gAgent.getPositionGlobal(); - } - mCameraFocusOffsetTarget = getCameraPositionGlobal() - mFocusTargetGlobal; - mCameraFocusOffset = mCameraFocusOffsetTarget; - setLookAt(LOOKAT_TARGET_CLEAR); - } - else - { - mFocusTargetGlobal = focus; - if (!focus_obj) - { - mCameraFOVZoomFactor = 0.f; - } - - mCameraFocusOffsetTarget = gAgent.getPosGlobalFromAgent(mCameraVirtualPositionAgent) - mFocusTargetGlobal; - - startCameraAnimation(); - - if (focus_obj) - { - if (focus_obj->isAvatar()) - { - setLookAt(LOOKAT_TARGET_FOCUS, focus_obj); - } - else - { - setLookAt(LOOKAT_TARGET_FOCUS, focus_obj, (gAgent.getPosAgentFromGlobal(focus) - focus_obj->getRenderPosition()) * ~focus_obj->getRenderRotation()); - } - } - else - { - setLookAt(LOOKAT_TARGET_FOCUS, NULL, gAgent.getPosAgentFromGlobal(mFocusTargetGlobal)); - } - } - } - else // focus == mFocusTargetGlobal - { - if (focus.isExactlyZero()) - { - if (isAgentAvatarValid()) - { - mFocusTargetGlobal = gAgent.getPosGlobalFromAgent(gAgentAvatarp->mHeadp->getWorldPosition()); - } - else - { - mFocusTargetGlobal = gAgent.getPositionGlobal(); - } - } - mCameraFocusOffsetTarget = (getCameraPositionGlobal() - mFocusTargetGlobal) / (1.f + mCameraFOVZoomFactor);; - mCameraFocusOffset = mCameraFocusOffsetTarget; - } - - if (mFocusObject.notNull()) - { - // for attachments, make offset relative to avatar, not the attachment - if (mFocusObject->isAttachment()) - { - while (mFocusObject.notNull() && !mFocusObject->isAvatar()) - { - mFocusObject = (LLViewerObject*) mFocusObject->getParent(); - } - setFocusObject((LLViewerObject*)mFocusObject); - } - updateFocusOffset(); - } -} - -// Used for avatar customization -//----------------------------------------------------------------------------- -// setCameraPosAndFocusGlobal() -//----------------------------------------------------------------------------- -void LLAgentCamera::setCameraPosAndFocusGlobal(const LLVector3d& camera_pos, const LLVector3d& focus, const LLUUID &object_id) -{ - LLVector3d old_focus = mFocusTargetGlobal.isExactlyZero() ? focus : mFocusTargetGlobal; - - F64 focus_delta_squared = (old_focus - focus).magVecSquared(); - const F64 ANIM_EPSILON_SQUARED = 0.0001; - if (focus_delta_squared > ANIM_EPSILON_SQUARED) - { - startCameraAnimation(); - } - - //LLViewerCamera::getInstance()->setOrigin( gAgent.getPosAgentFromGlobal( camera_pos ) ); - setFocusObject(gObjectList.findObject(object_id)); - mFocusTargetGlobal = focus; - mCameraFocusOffsetTarget = camera_pos - focus; - mCameraFocusOffset = mCameraFocusOffsetTarget; - - if (mFocusObject) - { - if (mFocusObject->isAvatar()) - { - setLookAt(LOOKAT_TARGET_FOCUS, mFocusObject); - } - else - { - setLookAt(LOOKAT_TARGET_FOCUS, mFocusObject, (gAgent.getPosAgentFromGlobal(focus) - mFocusObject->getRenderPosition()) * ~mFocusObject->getRenderRotation()); - } - } - else - { - setLookAt(LOOKAT_TARGET_FOCUS, NULL, gAgent.getPosAgentFromGlobal(mFocusTargetGlobal)); - } - - if (mCameraAnimating) - { - const F64 ANIM_METERS_PER_SECOND = 10.0; - const F64 MIN_ANIM_SECONDS = 0.5; - const F64 MAX_ANIM_SECONDS = 10.0; - F64 anim_duration = llmax( MIN_ANIM_SECONDS, sqrt(focus_delta_squared) / ANIM_METERS_PER_SECOND ); - anim_duration = llmin( anim_duration, MAX_ANIM_SECONDS ); - setAnimationDuration( (F32)anim_duration ); - } - - updateFocusOffset(); -} - -//----------------------------------------------------------------------------- -// setSitCamera() -//----------------------------------------------------------------------------- -void LLAgentCamera::setSitCamera(const LLUUID &object_id, const LLVector3 &camera_pos, const LLVector3 &camera_focus) -{ - bool camera_enabled = !object_id.isNull(); - - if (camera_enabled) - { - LLViewerObject *reference_object = gObjectList.findObject(object_id); - if (reference_object) - { - //convert to root object relative? - mSitCameraPos = camera_pos; - mSitCameraFocus = camera_focus; - mSitCameraReferenceObject = reference_object; - mSitCameraEnabled = true; - } - } - else - { - mSitCameraPos.clearVec(); - mSitCameraFocus.clearVec(); - mSitCameraReferenceObject = NULL; - mSitCameraEnabled = false; - } -} - -//----------------------------------------------------------------------------- -// setFocusOnAvatar() -//----------------------------------------------------------------------------- -void LLAgentCamera::setFocusOnAvatar(bool focus_on_avatar, bool animate, bool reset_axes) -{ - if (focus_on_avatar != mFocusOnAvatar) - { - if (animate) - { - startCameraAnimation(); - } - else - { - stopCameraAnimation(); - } - } - - //RN: when focused on the avatar, we're not "looking" at it - // looking implies intent while focusing on avatar means - // you're just walking around with a camera on you...eesh. - if (!mFocusOnAvatar && focus_on_avatar && reset_axes) - { - setFocusGlobal(LLVector3d::zero); - mCameraFOVZoomFactor = 0.f; - if (mCameraMode == CAMERA_MODE_THIRD_PERSON) - { - LLVector3 at_axis; - if (!isAgentAvatarValid() || !gAgentAvatarp->getParent()) - { - // In case of front view rotate agent to look into direction opposite to camera - // In case of rear view rotate agent into diraction same as camera, e t c - LLVector3 vect = getCameraOffsetInitial(); - F32 rotxy = F32(atan2(vect.mV[VY], vect.mV[VX])); - - LLCoordFrame frameCamera = *((LLCoordFrame*)LLViewerCamera::getInstance()); - // front view angle rotxy is zero, rear view rotxy angle is 180, compensate - frameCamera.yaw((180 * DEG_TO_RAD) - rotxy); - at_axis = frameCamera.getAtAxis(); - at_axis.mV[VZ] = 0.f; - at_axis.normalize(); - gAgent.resetAxes(at_axis); - gAgent.yaw(0); - } - } - } - // unlocking camera from avatar - else if (mFocusOnAvatar && !focus_on_avatar) - { - // keep camera focus point consistent, even though it is now unlocked - setFocusGlobal(gAgent.getPositionGlobal() + calcThirdPersonFocusOffset(), gAgent.getID()); - mAllowChangeToFollow = false; - } - - mFocusOnAvatar = focus_on_avatar; -} - - -bool LLAgentCamera::setLookAt(ELookAtType target_type, LLViewerObject *object, LLVector3 position) -{ - if(object && object->isAttachment()) - { - LLViewerObject* parent = object; - while(parent) - { - if (parent == gAgentAvatarp) - { - // looking at an attachment on ourselves, which we don't want to do - object = gAgentAvatarp; - position.clearVec(); - } - parent = (LLViewerObject*)parent->getParent(); - } - } - if(!mLookAt || mLookAt->isDead()) - { - mLookAt = (LLHUDEffectLookAt *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_LOOKAT); - mLookAt->setSourceObject(gAgentAvatarp); - } - - return mLookAt->setLookAt(target_type, object, position); -} - -//----------------------------------------------------------------------------- -// lookAtLastChat() -//----------------------------------------------------------------------------- -void LLAgentCamera::lookAtLastChat() -{ - // Block if camera is animating or not in normal third person camera mode - if (mCameraAnimating || !cameraThirdPerson()) - { - return; - } - - LLViewerObject *chatter = gObjectList.findObject(gAgent.getLastChatter()); - if (!chatter) - { - return; - } - - LLVector3 delta_pos; - if (chatter->isAvatar()) - { - LLVOAvatar *chatter_av = (LLVOAvatar*)chatter; - if (isAgentAvatarValid() && chatter_av->mHeadp) - { - delta_pos = chatter_av->mHeadp->getWorldPosition() - gAgentAvatarp->mHeadp->getWorldPosition(); - } - else - { - delta_pos = chatter->getPositionAgent() - gAgent.getPositionAgent(); - } - delta_pos.normalize(); - - gAgent.setControlFlags(AGENT_CONTROL_STOP); - - changeCameraToThirdPerson(); - - LLVector3 new_camera_pos = gAgentAvatarp->mHeadp->getWorldPosition(); - LLVector3 left = delta_pos % LLVector3::z_axis; - left.normalize(); - LLVector3 up = left % delta_pos; - up.normalize(); - new_camera_pos -= delta_pos * 0.4f; - new_camera_pos += left * 0.3f; - new_camera_pos += up * 0.2f; - - setFocusOnAvatar(false, false); - - if (chatter_av->mHeadp) - { - setFocusGlobal(gAgent.getPosGlobalFromAgent(chatter_av->mHeadp->getWorldPosition()), gAgent.getLastChatter()); - mCameraFocusOffsetTarget = gAgent.getPosGlobalFromAgent(new_camera_pos) - gAgent.getPosGlobalFromAgent(chatter_av->mHeadp->getWorldPosition()); - } - else - { - setFocusGlobal(chatter->getPositionGlobal(), gAgent.getLastChatter()); - mCameraFocusOffsetTarget = gAgent.getPosGlobalFromAgent(new_camera_pos) - chatter->getPositionGlobal(); - } - } - else - { - delta_pos = chatter->getRenderPosition() - gAgent.getPositionAgent(); - delta_pos.normalize(); - - gAgent.setControlFlags(AGENT_CONTROL_STOP); - - changeCameraToThirdPerson(); - - LLVector3 new_camera_pos = gAgentAvatarp->mHeadp->getWorldPosition(); - LLVector3 left = delta_pos % LLVector3::z_axis; - left.normalize(); - LLVector3 up = left % delta_pos; - up.normalize(); - new_camera_pos -= delta_pos * 0.4f; - new_camera_pos += left * 0.3f; - new_camera_pos += up * 0.2f; - - setFocusOnAvatar(false, false); - - setFocusGlobal(chatter->getPositionGlobal(), gAgent.getLastChatter()); - mCameraFocusOffsetTarget = gAgent.getPosGlobalFromAgent(new_camera_pos) - chatter->getPositionGlobal(); - } -} - -bool LLAgentCamera::isfollowCamLocked() -{ - return mFollowCam.getPositionLocked(); -} - -bool LLAgentCamera::setPointAt(EPointAtType target_type, LLViewerObject *object, LLVector3 position) -{ - // disallow pointing at attachments and avatars - if (object && (object->isAttachment() || object->isAvatar())) - { - return false; - } - if (!mPointAt || mPointAt->isDead()) - { - mPointAt = (LLHUDEffectPointAt *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_POINTAT); - mPointAt->setSourceObject(gAgentAvatarp); - } - return mPointAt->setPointAt(target_type, object, position); -} - -void LLAgentCamera::rotateToInitSitRot() -{ - gAgent.rotate(~gAgent.getFrameAgent().getQuaternion()); - gAgent.rotate(mInitSitRot); -} - -void LLAgentCamera::resetCameraZoomFraction() -{ - mCameraZoomFraction = INITIAL_ZOOM_FRACTION; -} - -ELookAtType LLAgentCamera::getLookAtType() -{ - if (mLookAt) - { - return mLookAt->getLookAtType(); - } - return LOOKAT_TARGET_NONE; -} - -EPointAtType LLAgentCamera::getPointAtType() -{ - if (mPointAt) - { - return mPointAt->getPointAtType(); - } - return POINTAT_TARGET_NONE; -} - -void LLAgentCamera::clearGeneralKeys() -{ - mAtKey = 0; - mWalkKey = 0; - mLeftKey = 0; - mUpKey = 0; - mYawKey = 0.f; - mPitchKey = 0.f; -} - -void LLAgentCamera::clearOrbitKeys() -{ - mOrbitLeftKey = 0.f; - mOrbitRightKey = 0.f; - mOrbitUpKey = 0.f; - mOrbitDownKey = 0.f; - mOrbitInKey = 0.f; - mOrbitOutKey = 0.f; -} - -void LLAgentCamera::clearPanKeys() -{ - mPanRightKey = 0.f; - mPanLeftKey = 0.f; - mPanUpKey = 0.f; - mPanDownKey = 0.f; - mPanInKey = 0.f; - mPanOutKey = 0.f; -} - -// static -S32 LLAgentCamera::directionToKey(S32 direction) -{ - if (direction > 0) return 1; - if (direction < 0) return -1; - return 0; -} - - -// EOF - +/** + * @file llagentcamera.cpp + * @brief LLAgent class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llagentcamera.h" + +#include "pipeline.h" + +#include "llagent.h" +#include "llanimationstates.h" +#include "llfloatercamera.h" +#include "llfloaterreg.h" +#include "llhudmanager.h" +#include "lljoystickbutton.h" +#include "llmorphview.h" +#include "llmoveview.h" +#include "llselectmgr.h" +#include "llsmoothstep.h" +#include "lltoolmgr.h" +#include "llviewercamera.h" +#include "llviewercontrol.h" +#include "llviewerjoystick.h" +#include "llviewermenu.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" +#include "llvoavatarself.h" +#include "llwindow.h" +#include "llworld.h" + +using namespace LLAvatarAppearanceDefines; + +extern LLMenuBarGL* gMenuBarView; + +// Mousewheel camera zoom +const F32 MIN_ZOOM_FRACTION = 0.25f; +const F32 INITIAL_ZOOM_FRACTION = 1.f; +const F32 MAX_ZOOM_FRACTION = 8.f; + +const F32 CAMERA_ZOOM_HALF_LIFE = 0.07f; // seconds +const F32 FOV_ZOOM_HALF_LIFE = 0.07f; // seconds + +const F32 CAMERA_FOCUS_HALF_LIFE = 0.f;//0.02f; +const F32 CAMERA_LAG_HALF_LIFE = 0.25f; +const F32 MIN_CAMERA_LAG = 0.5f; +const F32 MAX_CAMERA_LAG = 5.f; + +const F32 CAMERA_COLLIDE_EPSILON = 0.1f; +const F32 MIN_CAMERA_DISTANCE = 0.1f; + +const F32 AVATAR_ZOOM_MIN_X_FACTOR = 0.55f; +const F32 AVATAR_ZOOM_MIN_Y_FACTOR = 0.7f; +const F32 AVATAR_ZOOM_MIN_Z_FACTOR = 1.15f; + +const F32 MAX_CAMERA_DISTANCE_FROM_AGENT = 50.f; +const F32 MAX_CAMERA_DISTANCE_FROM_OBJECT = 496.f; +const F32 CAMERA_FUDGE_FROM_OBJECT = 16.f; + +const F32 MAX_CAMERA_SMOOTH_DISTANCE = 50.0f; + +const F32 HEAD_BUFFER_SIZE = 0.3f; + +const F32 CUSTOMIZE_AVATAR_CAMERA_ANIM_SLOP = 0.1f; + +const F32 LAND_MIN_ZOOM = 0.15f; + +const F32 AVATAR_MIN_ZOOM = 0.5f; +const F32 OBJECT_MIN_ZOOM = 0.02f; + +const F32 APPEARANCE_MIN_ZOOM = 0.39f; +const F32 APPEARANCE_MAX_ZOOM = 8.f; + +const F32 CUSTOMIZE_AVATAR_CAMERA_DEFAULT_DIST = 3.5f; + +const F32 GROUND_TO_AIR_CAMERA_TRANSITION_TIME = 0.5f; +const F32 GROUND_TO_AIR_CAMERA_TRANSITION_START_TIME = 0.5f; + +const F32 OBJECT_EXTENTS_PADDING = 0.5f; + +static bool isDisableCameraConstraints() +{ + static LLCachedControl sDisableCameraConstraints(gSavedSettings, "DisableCameraConstraints", false); + return sDisableCameraConstraints; +} + +// The agent instance. +LLAgentCamera gAgentCamera; + +//----------------------------------------------------------------------------- +// LLAgentCamera() +//----------------------------------------------------------------------------- +LLAgentCamera::LLAgentCamera() : + mInitialized(false), + + mDrawDistance( DEFAULT_FAR_PLANE ), + + mLookAt(NULL), + mPointAt(NULL), + + mHUDTargetZoom(1.f), + mHUDCurZoom(1.f), + + mForceMouselook(false), + + mCameraMode( CAMERA_MODE_THIRD_PERSON ), + mLastCameraMode( CAMERA_MODE_THIRD_PERSON ), + + mCameraPreset(CAMERA_PRESET_REAR_VIEW), + + mCameraAnimating( false ), + mAnimationCameraStartGlobal(), + mAnimationFocusStartGlobal(), + mAnimationTimer(), + mAnimationDuration(0.33f), + + mCameraFOVZoomFactor(0.f), + mCameraCurrentFOVZoomFactor(0.f), + mCameraFocusOffset(), + + mCameraCollidePlane(), + + mCurrentCameraDistance(2.f), // meters, set in init() + mTargetCameraDistance(2.f), + mCameraZoomFraction(1.f), // deprecated + mThirdPersonHeadOffset(0.f, 0.f, 1.f), + mSitCameraEnabled(false), + mCameraSmoothingLastPositionGlobal(), + mCameraSmoothingLastPositionAgent(), + mCameraSmoothingStop(false), + + mCameraUpVector(LLVector3::z_axis), // default is straight up + + mFocusOnAvatar(true), + mAllowChangeToFollow(false), + mFocusGlobal(), + mFocusTargetGlobal(), + mFocusObject(NULL), + mFocusObjectDist(0.f), + mFocusObjectOffset(), + mTrackFocusObject(true), + + mAtKey(0), // Either 1, 0, or -1... indicates that movement-key is pressed + mWalkKey(0), // like AtKey, but causes less forward thrust + mLeftKey(0), + mUpKey(0), + mYawKey(0.f), + mPitchKey(0.f), + + mOrbitLeftKey(0.f), + mOrbitRightKey(0.f), + mOrbitUpKey(0.f), + mOrbitDownKey(0.f), + mOrbitInKey(0.f), + mOrbitOutKey(0.f), + + mPanUpKey(0.f), + mPanDownKey(0.f), + mPanLeftKey(0.f), + mPanRightKey(0.f), + mPanInKey(0.f), + mPanOutKey(0.f) +{ + mFollowCam.setMaxCameraDistantFromSubject( MAX_CAMERA_DISTANCE_FROM_AGENT ); + + clearGeneralKeys(); + clearOrbitKeys(); + clearPanKeys(); + + resetPanDiff(); + resetOrbitDiff(); +} + +// Requires gSavedSettings to be initialized. +//----------------------------------------------------------------------------- +// init() +//----------------------------------------------------------------------------- +void LLAgentCamera::init() +{ + // *Note: this is where LLViewerCamera::getInstance() used to be constructed. + + mDrawDistance = gSavedSettings.getF32("RenderFarClip"); + + LLViewerCamera::getInstance()->setView(DEFAULT_FIELD_OF_VIEW); + // Leave at 0.1 meters until we have real near clip management + LLViewerCamera::getInstance()->setNear(0.1f); + LLViewerCamera::getInstance()->setFar(mDrawDistance); // if you want to change camera settings, do so in camera.h + LLViewerCamera::getInstance()->setAspect( gViewerWindow->getWorldViewAspectRatio() ); // default, overridden in LLViewerWindow::reshape + LLViewerCamera::getInstance()->setViewHeightInPixels(768); // default, overridden in LLViewerWindow::reshape + + mCameraFocusOffsetTarget = LLVector4(gSavedSettings.getVector3("CameraOffsetBuild")); + + mCameraPreset = (ECameraPreset) gSavedSettings.getU32("CameraPresetType"); + + mCameraCollidePlane.clearVec(); + mCurrentCameraDistance = getCameraOffsetInitial().magVec() * gSavedSettings.getF32("CameraOffsetScale"); + mTargetCameraDistance = mCurrentCameraDistance; + mCameraZoomFraction = 1.f; + mTrackFocusObject = gSavedSettings.getBOOL("TrackFocusObject"); + + mInitialized = true; +} + +//----------------------------------------------------------------------------- +// cleanup() +//----------------------------------------------------------------------------- +void LLAgentCamera::cleanup() +{ + setSitCamera(LLUUID::null); + + if(mLookAt) + { + mLookAt->markDead() ; + mLookAt = NULL; + } + if(mPointAt) + { + mPointAt->markDead() ; + mPointAt = NULL; + } + setFocusObject(NULL); +} + +void LLAgentCamera::setAvatarObject(LLVOAvatarSelf* avatar) +{ + if (!mLookAt) + { + mLookAt = (LLHUDEffectLookAt *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_LOOKAT); + } + if (!mPointAt) + { + mPointAt = (LLHUDEffectPointAt *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_POINTAT); + } + + if (!mLookAt.isNull()) + { + mLookAt->setSourceObject(avatar); + } + if (!mPointAt.isNull()) + { + mPointAt->setSourceObject(avatar); + } +} + +//----------------------------------------------------------------------------- +// LLAgent() +//----------------------------------------------------------------------------- +LLAgentCamera::~LLAgentCamera() +{ + cleanup(); + + // *Note: this is where LLViewerCamera::getInstance() used to be deleted. +} + +// Change camera back to third person, stop the autopilot, +// deselect stuff, etc. +//----------------------------------------------------------------------------- +// resetView() +//----------------------------------------------------------------------------- +void LLAgentCamera::resetView(bool reset_camera, bool change_camera) +{ + if (gDisconnected) + { + return; + } + + if (gAgent.getAutoPilot()) + { + gAgent.stopAutoPilot(true); + } + + LLSelectMgr::getInstance()->unhighlightAll(); + + // By popular request, keep land selection while walking around. JC + // LLViewerParcelMgr::getInstance()->deselectLand(); + + // force deselect when walking and attachment is selected + // this is so people don't wig out when their avatar moves without animating + if (LLSelectMgr::getInstance()->getSelection()->isAttachment()) + { + LLSelectMgr::getInstance()->deselectAll(); + } + + if (gMenuHolder != NULL) + { + // Hide all popup menus + gMenuHolder->hideMenus(); + } + + if (change_camera && !gSavedSettings.getBOOL("FreezeTime")) + { + changeCameraToDefault(); + + if (LLViewerJoystick::getInstance()->getOverrideCamera()) + { + handle_toggle_flycam(); + } + + // reset avatar mode from eventual residual motion + if (LLToolMgr::getInstance()->inBuildMode()) + { + LLViewerJoystick::getInstance()->moveAvatar(true); + } + + //Camera Tool is needed for Free Camera Control Mode + if (!LLFloaterCamera::inFreeCameraMode()) + { + LLFloaterReg::hideInstance("build"); + + // Switch back to basic toolset + LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); + } + + gViewerWindow->showCursor(); + } + + + if (reset_camera && !gSavedSettings.getBOOL("FreezeTime")) + { + if (!gViewerWindow->getLeftMouseDown() && cameraThirdPerson()) + { + // leaving mouse-steer mode + LLVector3 agent_at_axis = gAgent.getAtAxis(); + agent_at_axis -= projected_vec(agent_at_axis, gAgent.getReferenceUpVector()); + agent_at_axis.normalize(); + gAgent.resetAxes(lerp(gAgent.getAtAxis(), agent_at_axis, LLSmoothInterpolation::getInterpolant(0.3f))); + } + + setFocusOnAvatar(true, ANIMATE); + + mCameraFOVZoomFactor = 0.f; + } + resetPanDiff(); + resetOrbitDiff(); + mHUDTargetZoom = 1.f; + + if (LLSelectMgr::getInstance()->mAllowSelectAvatar) + { + // resetting camera also resets position overrides in debug mode 'AllowSelectAvatar' + LLObjectSelectionHandle selected_handle = LLSelectMgr::getInstance()->getSelection(); + if (selected_handle->getObjectCount() == 1 + && selected_handle->getFirstObject() != NULL + && selected_handle->getFirstObject()->isAvatar()) + { + LLSelectMgr::getInstance()->resetObjectOverrides(selected_handle); + } + } +} + +// Allow camera to be moved somewhere other than behind avatar. +//----------------------------------------------------------------------------- +// unlockView() +//----------------------------------------------------------------------------- +void LLAgentCamera::unlockView() +{ + if (getFocusOnAvatar()) + { + if (isAgentAvatarValid()) + { + setFocusGlobal(LLVector3d::zero, gAgentAvatarp->mID); + } + setFocusOnAvatar(false, false); // no animation + } +} + +//----------------------------------------------------------------------------- +// slamLookAt() +//----------------------------------------------------------------------------- +void LLAgentCamera::slamLookAt(const LLVector3 &look_at) +{ + LLVector3 look_at_norm = look_at; + look_at_norm.mV[VZ] = 0.f; + look_at_norm.normalize(); + gAgent.resetAxes(look_at_norm); +} + +//----------------------------------------------------------------------------- +// calcFocusOffset() +//----------------------------------------------------------------------------- +LLVector3 LLAgentCamera::calcFocusOffset(LLViewerObject *object, LLVector3 original_focus_point, S32 x, S32 y) +{ + LLMatrix4 obj_matrix = object->getRenderMatrix(); + LLQuaternion obj_rot = object->getRenderRotation(); + LLVector3 obj_pos = object->getRenderPosition(); + + // if is avatar - don't do any funk heuristics to position the focal point + // see DEV-30589 + if ((object->isAvatar() && !object->isRoot()) || (object->isAnimatedObject() && object->getControlAvatar())) + { + return original_focus_point - obj_pos; + } + if (object->isAvatar()) + { + LLVOAvatar* av = object->asAvatar(); + return original_focus_point - av->getCharacterPosition(); + } + + LLQuaternion inv_obj_rot = ~obj_rot; // get inverse of rotation + LLVector3 object_extents = object->getScale(); + + // make sure they object extents are non-zero + object_extents.clamp(0.001f, F32_MAX); + + // obj_to_cam_ray is unit vector pointing from object center to camera, in the coordinate frame of the object + LLVector3 obj_to_cam_ray = obj_pos - LLViewerCamera::getInstance()->getOrigin(); + obj_to_cam_ray.rotVec(inv_obj_rot); + obj_to_cam_ray.normalize(); + + // obj_to_cam_ray_proportions are the (positive) ratios of + // the obj_to_cam_ray x,y,z components with the x,y,z object dimensions. + LLVector3 obj_to_cam_ray_proportions; + obj_to_cam_ray_proportions.mV[VX] = llabs(obj_to_cam_ray.mV[VX] / object_extents.mV[VX]); + obj_to_cam_ray_proportions.mV[VY] = llabs(obj_to_cam_ray.mV[VY] / object_extents.mV[VY]); + obj_to_cam_ray_proportions.mV[VZ] = llabs(obj_to_cam_ray.mV[VZ] / object_extents.mV[VZ]); + + // find the largest ratio stored in obj_to_cam_ray_proportions + // this corresponds to the object's local axial plane (XY, YZ, XZ) that is *most* facing the camera + LLVector3 longest_object_axis; + // is x-axis longest? + if (obj_to_cam_ray_proportions.mV[VX] > obj_to_cam_ray_proportions.mV[VY] + && obj_to_cam_ray_proportions.mV[VX] > obj_to_cam_ray_proportions.mV[VZ]) + { + // then grab it + longest_object_axis.setVec(obj_matrix.getFwdRow4()); + } + // is y-axis longest? + else if (obj_to_cam_ray_proportions.mV[VY] > obj_to_cam_ray_proportions.mV[VZ]) + { + // then grab it + longest_object_axis.setVec(obj_matrix.getLeftRow4()); + } + // otherwise, use z axis + else + { + longest_object_axis.setVec(obj_matrix.getUpRow4()); + } + + // Use this axis as the normal to project mouse click on to plane with that normal, at the object center. + // This generates a point behind the mouse cursor that is approximately in the middle of the object in + // terms of depth. + // We do this to allow the camera rotation tool to "tumble" the object by rotating the camera. + // If the focus point were the object surface under the mouse, camera rotation would introduce an undesirable + // eccentricity to the object orientation + LLVector3 focus_plane_normal(longest_object_axis); + focus_plane_normal.normalize(); + + LLVector3d focus_pt_global; + gViewerWindow->mousePointOnPlaneGlobal(focus_pt_global, x, y, gAgent.getPosGlobalFromAgent(obj_pos), focus_plane_normal); + LLVector3 focus_pt = gAgent.getPosAgentFromGlobal(focus_pt_global); + + // find vector from camera to focus point in object space + LLVector3 camera_to_focus_vec = focus_pt - LLViewerCamera::getInstance()->getOrigin(); + camera_to_focus_vec.rotVec(inv_obj_rot); + + // find vector from object origin to focus point in object coordinates + LLVector3 focus_offset_from_object_center = focus_pt - obj_pos; + // convert to object-local space + focus_offset_from_object_center.rotVec(inv_obj_rot); + + // We need to project the focus point back into the bounding box of the focused object. + // Do this by calculating the XYZ scale factors needed to get focus offset back in bounds along the camera_focus axis + LLVector3 clip_fraction; + + // for each axis... + for (U32 axis = VX; axis <= VZ; axis++) + { + //...calculate distance that focus offset sits outside of bounding box along that axis... + //NOTE: dist_out_of_bounds keeps the sign of focus_offset_from_object_center + F32 dist_out_of_bounds; + if (focus_offset_from_object_center.mV[axis] > 0.f) + { + dist_out_of_bounds = llmax(0.f, focus_offset_from_object_center.mV[axis] - (object_extents.mV[axis] * 0.5f)); + } + else + { + dist_out_of_bounds = llmin(0.f, focus_offset_from_object_center.mV[axis] + (object_extents.mV[axis] * 0.5f)); + } + + //...then calculate the scale factor needed to push camera_to_focus_vec back in bounds along current axis + if (llabs(camera_to_focus_vec.mV[axis]) < 0.0001f) + { + // don't divide by very small number + clip_fraction.mV[axis] = 0.f; + } + else + { + clip_fraction.mV[axis] = dist_out_of_bounds / camera_to_focus_vec.mV[axis]; + } + } + + LLVector3 abs_clip_fraction = clip_fraction; + abs_clip_fraction.abs(); + + // find axis of focus offset that is *most* outside the bounding box and use that to + // rescale focus offset to inside object extents + if (abs_clip_fraction.mV[VX] > abs_clip_fraction.mV[VY] + && abs_clip_fraction.mV[VX] > abs_clip_fraction.mV[VZ]) + { + focus_offset_from_object_center -= clip_fraction.mV[VX] * camera_to_focus_vec; + } + else if (abs_clip_fraction.mV[VY] > abs_clip_fraction.mV[VZ]) + { + focus_offset_from_object_center -= clip_fraction.mV[VY] * camera_to_focus_vec; + } + else + { + focus_offset_from_object_center -= clip_fraction.mV[VZ] * camera_to_focus_vec; + } + + // convert back to world space + focus_offset_from_object_center.rotVec(obj_rot); + + // now, based on distance of camera from object relative to object size + // push the focus point towards the near surface of the object when (relatively) close to the objcet + // or keep the focus point in the object middle when (relatively) far + // NOTE: leave focus point in middle of avatars, since the behavior you want when alt-zooming on avatars + // is almost always "tumble about middle" and not "spin around surface point" + { + LLVector3 obj_rel = original_focus_point - object->getRenderPosition(); + + //now that we have the object relative position, we should bias toward the center of the object + //based on the distance of the camera to the focus point vs. the distance of the camera to the focus + + F32 relDist = llabs(obj_rel * LLViewerCamera::getInstance()->getAtAxis()); + F32 viewDist = dist_vec(obj_pos + obj_rel, LLViewerCamera::getInstance()->getOrigin()); + + + LLBBox obj_bbox = object->getBoundingBoxAgent(); + F32 bias = 0.f; + + // virtual_camera_pos is the camera position we are simulating by backing the camera off + // and adjusting the FOV + LLVector3 virtual_camera_pos = gAgent.getPosAgentFromGlobal(mFocusTargetGlobal + (getCameraPositionGlobal() - mFocusTargetGlobal) / (1.f + mCameraFOVZoomFactor)); + + // if the camera is inside the object (large, hollow objects, for example) + // leave focus point all the way to destination depth, away from object center + if(!obj_bbox.containsPointAgent(virtual_camera_pos)) + { + // perform magic number biasing of focus point towards surface vs. planar center + bias = clamp_rescale(relDist/viewDist, 0.1f, 0.7f, 0.0f, 1.0f); + obj_rel = lerp(focus_offset_from_object_center, obj_rel, bias); + } + + focus_offset_from_object_center = obj_rel; + } + + return focus_offset_from_object_center; +} + +//----------------------------------------------------------------------------- +// calcCameraMinDistance() +//----------------------------------------------------------------------------- +bool LLAgentCamera::calcCameraMinDistance(F32 &obj_min_distance) +{ + bool soft_limit = false; // is the bounding box to be treated literally (volumes) or as an approximation (avatars) + + if (!mFocusObject || mFocusObject->isDead() || + mFocusObject->isMesh() || + isDisableCameraConstraints()) + { + obj_min_distance = 0.f; + return true; + } + + if (mFocusObject->mDrawable.isNull()) + { +#ifdef LL_RELEASE_FOR_DOWNLOAD + LL_WARNS() << "Focus object with no drawable!" << LL_ENDL; +#else + mFocusObject->dump(); + LL_ERRS() << "Focus object with no drawable!" << LL_ENDL; +#endif + obj_min_distance = 0.f; + return true; + } + + LLQuaternion inv_object_rot = ~mFocusObject->getRenderRotation(); + LLVector3 target_offset_origin = mFocusObjectOffset; + LLVector3 camera_offset_target(getCameraPositionAgent() - gAgent.getPosAgentFromGlobal(mFocusTargetGlobal)); + + // convert offsets into object local space + camera_offset_target.rotVec(inv_object_rot); + target_offset_origin.rotVec(inv_object_rot); + + // push around object extents based on target offset + LLVector3 object_extents = mFocusObject->getScale(); + if (mFocusObject->isAvatar()) + { + // fudge factors that lets you zoom in on avatars a bit more (which don't do FOV zoom) + object_extents.mV[VX] *= AVATAR_ZOOM_MIN_X_FACTOR; + object_extents.mV[VY] *= AVATAR_ZOOM_MIN_Y_FACTOR; + object_extents.mV[VZ] *= AVATAR_ZOOM_MIN_Z_FACTOR; + soft_limit = true; + } + LLVector3 abs_target_offset = target_offset_origin; + abs_target_offset.abs(); + + LLVector3 target_offset_dir = target_offset_origin; + + bool target_outside_object_extents = false; + + for (U32 i = VX; i <= VZ; i++) + { + if (abs_target_offset.mV[i] * 2.f > object_extents.mV[i] + OBJECT_EXTENTS_PADDING) + { + target_outside_object_extents = true; + } + if (camera_offset_target.mV[i] > 0.f) + { + object_extents.mV[i] -= target_offset_origin.mV[i] * 2.f; + } + else + { + object_extents.mV[i] += target_offset_origin.mV[i] * 2.f; + } + } + + // don't shrink the object extents so far that the object inverts + object_extents.clamp(0.001f, F32_MAX); + + // move into first octant + LLVector3 camera_offset_target_abs_norm = camera_offset_target; + camera_offset_target_abs_norm.abs(); + // make sure offset is non-zero + camera_offset_target_abs_norm.clamp(0.001f, F32_MAX); + camera_offset_target_abs_norm.normalize(); + + // find camera position relative to normalized object extents + LLVector3 camera_offset_target_scaled = camera_offset_target_abs_norm; + camera_offset_target_scaled.mV[VX] /= object_extents.mV[VX]; + camera_offset_target_scaled.mV[VY] /= object_extents.mV[VY]; + camera_offset_target_scaled.mV[VZ] /= object_extents.mV[VZ]; + + if (camera_offset_target_scaled.mV[VX] > camera_offset_target_scaled.mV[VY] && + camera_offset_target_scaled.mV[VX] > camera_offset_target_scaled.mV[VZ]) + { + if (camera_offset_target_abs_norm.mV[VX] < 0.001f) + { + obj_min_distance = object_extents.mV[VX] * 0.5f; + } + else + { + obj_min_distance = object_extents.mV[VX] * 0.5f / camera_offset_target_abs_norm.mV[VX]; + } + } + else if (camera_offset_target_scaled.mV[VY] > camera_offset_target_scaled.mV[VZ]) + { + if (camera_offset_target_abs_norm.mV[VY] < 0.001f) + { + obj_min_distance = object_extents.mV[VY] * 0.5f; + } + else + { + obj_min_distance = object_extents.mV[VY] * 0.5f / camera_offset_target_abs_norm.mV[VY]; + } + } + else + { + if (camera_offset_target_abs_norm.mV[VZ] < 0.001f) + { + obj_min_distance = object_extents.mV[VZ] * 0.5f; + } + else + { + obj_min_distance = object_extents.mV[VZ] * 0.5f / camera_offset_target_abs_norm.mV[VZ]; + } + } + + LLVector3 object_split_axis; + LLVector3 target_offset_scaled = target_offset_origin; + target_offset_scaled.abs(); + target_offset_scaled.normalize(); + target_offset_scaled.mV[VX] /= object_extents.mV[VX]; + target_offset_scaled.mV[VY] /= object_extents.mV[VY]; + target_offset_scaled.mV[VZ] /= object_extents.mV[VZ]; + + if (target_offset_scaled.mV[VX] > target_offset_scaled.mV[VY] && + target_offset_scaled.mV[VX] > target_offset_scaled.mV[VZ]) + { + object_split_axis = LLVector3::x_axis; + } + else if (target_offset_scaled.mV[VY] > target_offset_scaled.mV[VZ]) + { + object_split_axis = LLVector3::y_axis; + } + else + { + object_split_axis = LLVector3::z_axis; + } + + LLVector3 camera_offset_object(getCameraPositionAgent() - mFocusObject->getPositionAgent()); + + + F32 camera_offset_clip = camera_offset_object * object_split_axis; + F32 target_offset_clip = target_offset_dir * object_split_axis; + + // target has moved outside of object extents + // check to see if camera and target are on same side + if (target_outside_object_extents) + { + if (camera_offset_clip > 0.f && target_offset_clip > 0.f) + { + return false; + } + else if (camera_offset_clip < 0.f && target_offset_clip < 0.f) + { + return false; + } + } + + // clamp obj distance to diagonal of 10 by 10 cube + obj_min_distance = llmin(obj_min_distance, 10.f * F_SQRT3); + + obj_min_distance += LLViewerCamera::getInstance()->getNear() + (soft_limit ? 0.1f : 0.2f); + + return true; +} + +F32 LLAgentCamera::getCameraZoomFraction(bool get_third_person) +{ + // 0.f -> camera zoomed all the way out + // 1.f -> camera zoomed all the way in + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + if (selection->getObjectCount() && selection->getSelectType() == SELECT_TYPE_HUD) + { + // already [0,1] + return mHUDTargetZoom; + } + + if (get_third_person || (mFocusOnAvatar && cameraThirdPerson())) + { + return clamp_rescale(mCameraZoomFraction, MIN_ZOOM_FRACTION, MAX_ZOOM_FRACTION, 1.f, 0.f); + } + + if (cameraCustomizeAvatar()) + { + F32 distance = (F32)mCameraFocusOffsetTarget.magVec(); + return clamp_rescale(distance, APPEARANCE_MIN_ZOOM, APPEARANCE_MAX_ZOOM, 1.f, 0.f ); + } + + F32 min_zoom; + F32 max_zoom = getCameraMaxZoomDistance(); + if (isDisableCameraConstraints()) + { + max_zoom = MAX_CAMERA_DISTANCE_FROM_OBJECT; + } + + F32 distance = (F32)mCameraFocusOffsetTarget.magVec(); + if (mFocusObject.notNull()) + { + if (mFocusObject->isAvatar()) + { + min_zoom = AVATAR_MIN_ZOOM; + } + else + { + min_zoom = OBJECT_MIN_ZOOM; + } + } + else + { + min_zoom = LAND_MIN_ZOOM; + } + + return clamp_rescale(distance, min_zoom, max_zoom, 1.f, 0.f); +} + +void LLAgentCamera::setCameraZoomFraction(F32 fraction) +{ + // 0.f -> camera zoomed all the way out + // 1.f -> camera zoomed all the way in + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + + if (selection->getObjectCount() && selection->getSelectType() == SELECT_TYPE_HUD) + { + mHUDTargetZoom = fraction; + } + else if (mFocusOnAvatar && cameraThirdPerson()) + { + mCameraZoomFraction = rescale(fraction, 0.f, 1.f, MAX_ZOOM_FRACTION, MIN_ZOOM_FRACTION); + } + else if (cameraCustomizeAvatar()) + { + LLVector3d camera_offset_dir = mCameraFocusOffsetTarget; + camera_offset_dir.normalize(); + mCameraFocusOffsetTarget = camera_offset_dir * rescale(fraction, 0.f, 1.f, APPEARANCE_MAX_ZOOM, APPEARANCE_MIN_ZOOM); + } + else + { + F32 min_zoom = LAND_MIN_ZOOM; + F32 max_zoom = getCameraMaxZoomDistance(); + if (isDisableCameraConstraints()) + { + max_zoom = MAX_CAMERA_DISTANCE_FROM_OBJECT; + } + + if (mFocusObject.notNull()) + { + if (mFocusObject.notNull()) + { + if (mFocusObject->isAvatar()) + { + min_zoom = AVATAR_MIN_ZOOM; + } + else + { + min_zoom = OBJECT_MIN_ZOOM; + } + } + } + + LLVector3d camera_offset_dir = mCameraFocusOffsetTarget; + camera_offset_dir.normalize(); + mCameraFocusOffsetTarget = camera_offset_dir * rescale(fraction, 0.f, 1.f, max_zoom, min_zoom); + } + + startCameraAnimation(); +} + +F32 LLAgentCamera::getAgentHUDTargetZoom() +{ + static LLCachedControl hud_scale_factor(gSavedSettings, "HUDScaleFactor"); + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + return (selection->getObjectCount() && selection->getSelectType() == SELECT_TYPE_HUD) ? hud_scale_factor*gAgentCamera.mHUDTargetZoom : hud_scale_factor; +} + +//----------------------------------------------------------------------------- +// cameraOrbitAround() +//----------------------------------------------------------------------------- +void LLAgentCamera::cameraOrbitAround(const F32 radians) +{ + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + if (selection->getObjectCount() && selection->getSelectType() == SELECT_TYPE_HUD) + { + // do nothing for hud selection + } + else if (mFocusOnAvatar && (mCameraMode == CAMERA_MODE_THIRD_PERSON || mCameraMode == CAMERA_MODE_FOLLOW)) + { + gAgent.yaw(radians); + } + else + { + mOrbitAroundRadians += radians; + mCameraFocusOffsetTarget.rotVec(radians, 0.f, 0.f, 1.f); + + cameraZoomIn(1.f); + } +} + + +//----------------------------------------------------------------------------- +// cameraOrbitOver() +//----------------------------------------------------------------------------- +void LLAgentCamera::cameraOrbitOver(const F32 angle) +{ + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + if (selection->getObjectCount() && selection->getSelectType() == SELECT_TYPE_HUD) + { + // do nothing for hud selection + } + else if (mFocusOnAvatar && mCameraMode == CAMERA_MODE_THIRD_PERSON) + { + gAgent.pitch(angle); + } + else + { + LLVector3 camera_offset_unit(mCameraFocusOffsetTarget); + camera_offset_unit.normalize(); + + F32 angle_from_up = acos( camera_offset_unit * gAgent.getReferenceUpVector() ); + + LLVector3d left_axis; + left_axis.setVec(LLViewerCamera::getInstance()->getLeftAxis()); + F32 new_angle = llclamp(angle_from_up - angle, 1.f * DEG_TO_RAD, 179.f * DEG_TO_RAD); + mOrbitOverAngle += angle_from_up - new_angle; + mCameraFocusOffsetTarget.rotVec(angle_from_up - new_angle, left_axis); + + cameraZoomIn(1.f); + } +} + +void LLAgentCamera::resetCameraOrbit() +{ + LLVector3 camera_offset_unit(mCameraFocusOffsetTarget); + camera_offset_unit.normalize(); + + LLVector3d left_axis; + left_axis.setVec(LLViewerCamera::getInstance()->getLeftAxis()); + mCameraFocusOffsetTarget.rotVec(-mOrbitOverAngle, left_axis); + + mCameraFocusOffsetTarget.rotVec(-mOrbitAroundRadians, 0.f, 0.f, 1.f); + + cameraZoomIn(1.f); + resetOrbitDiff(); +} + +void LLAgentCamera::resetOrbitDiff() +{ + mOrbitAroundRadians = 0; + mOrbitOverAngle = 0; +} + +//----------------------------------------------------------------------------- +// cameraZoomIn() +//----------------------------------------------------------------------------- +void LLAgentCamera::cameraZoomIn(const F32 fraction) +{ + if (gDisconnected) + { + return; + } + + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + if (LLToolMgr::getInstance()->inBuildMode() && selection->getObjectCount() && selection->getSelectType() == SELECT_TYPE_HUD) + { + // just update hud zoom level + mHUDTargetZoom /= fraction; + return; + } + + LLVector3d camera_offset_unit(mCameraFocusOffsetTarget); + F32 current_distance = (F32)camera_offset_unit.normalize(); + F32 new_distance = current_distance * fraction; + + // Unless camera is unlocked + if (!isDisableCameraConstraints()) + { + F32 min_zoom = LAND_MIN_ZOOM; + + // Don't move through focus point + if (mFocusObject) + { + LLVector3 camera_offset_dir((F32)camera_offset_unit.mdV[VX], (F32)camera_offset_unit.mdV[VY], (F32)camera_offset_unit.mdV[VZ]); + + if (mFocusObject->isAvatar()) + { + calcCameraMinDistance(min_zoom); + } + else + { + min_zoom = OBJECT_MIN_ZOOM; + } + } + + new_distance = llmax(new_distance, min_zoom); + + F32 max_distance = getCameraMaxZoomDistance(); + max_distance = llmin(max_distance, current_distance * 4.f); //Scaled max relative to current distance. MAINT-3154 + new_distance = llmin(new_distance, max_distance); + + if (cameraCustomizeAvatar()) + { + new_distance = llclamp(new_distance, APPEARANCE_MIN_ZOOM, APPEARANCE_MAX_ZOOM); + } + } + + mCameraFocusOffsetTarget = new_distance * camera_offset_unit; +} + +//----------------------------------------------------------------------------- +// cameraOrbitIn() +//----------------------------------------------------------------------------- +void LLAgentCamera::cameraOrbitIn(const F32 meters) +{ + if (mFocusOnAvatar && mCameraMode == CAMERA_MODE_THIRD_PERSON) + { + F32 camera_offset_dist = llmax(0.001f, getCameraOffsetInitial().magVec() * gSavedSettings.getF32("CameraOffsetScale")); + + mCameraZoomFraction = (mTargetCameraDistance - meters) / camera_offset_dist; + + if (!gSavedSettings.getBOOL("FreezeTime") && mCameraZoomFraction < MIN_ZOOM_FRACTION && meters > 0.f) + { + // No need to animate, camera is already there. + changeCameraToMouselook(false); + } + + if (!isDisableCameraConstraints()) + { + mCameraZoomFraction = llclamp(mCameraZoomFraction, MIN_ZOOM_FRACTION, MAX_ZOOM_FRACTION); + } + } + else + { + LLVector3d camera_offset_unit(mCameraFocusOffsetTarget); + F32 current_distance = (F32)camera_offset_unit.normalize(); + F32 new_distance = current_distance - meters; + + // Unless camera is unlocked + if (!isDisableCameraConstraints()) + { + F32 min_zoom = LAND_MIN_ZOOM; + + // Don't move through focus point + if (mFocusObject.notNull()) + { + if (mFocusObject->isAvatar()) + { + min_zoom = AVATAR_MIN_ZOOM; + } + else + { + min_zoom = OBJECT_MIN_ZOOM; + } + } + + new_distance = llmax(new_distance, min_zoom); + + F32 max_distance = getCameraMaxZoomDistance(); + new_distance = llmin(new_distance, max_distance); + + if (CAMERA_MODE_CUSTOMIZE_AVATAR == getCameraMode()) + { + new_distance = llclamp(new_distance, APPEARANCE_MIN_ZOOM, APPEARANCE_MAX_ZOOM); + } + } + + // Compute new camera offset + mCameraFocusOffsetTarget = new_distance * camera_offset_unit; + cameraZoomIn(1.f); + } +} + +//----------------------------------------------------------------------------- +// cameraPanIn() +//----------------------------------------------------------------------------- +void LLAgentCamera::cameraPanIn(F32 meters) +{ + LLVector3d at_axis; + at_axis.setVec(LLViewerCamera::getInstance()->getAtAxis()); + + mPanFocusDiff += meters * at_axis; + + mFocusTargetGlobal += meters * at_axis; + mFocusGlobal = mFocusTargetGlobal; + // don't enforce zoom constraints as this is the only way for users to get past them easily + updateFocusOffset(); + // NOTE: panning movements expect the camera to move exactly with the focus target, not animated behind -Nyx + mCameraSmoothingLastPositionGlobal = calcCameraPositionTargetGlobal(); +} + +//----------------------------------------------------------------------------- +// cameraPanLeft() +//----------------------------------------------------------------------------- +void LLAgentCamera::cameraPanLeft(F32 meters) +{ + LLVector3d left_axis; + left_axis.setVec(LLViewerCamera::getInstance()->getLeftAxis()); + + mPanFocusDiff += meters * left_axis; + + mFocusTargetGlobal += meters * left_axis; + mFocusGlobal = mFocusTargetGlobal; + + // disable smoothing for camera pan, which causes some residents unhappiness + mCameraSmoothingStop = true; + + cameraZoomIn(1.f); + updateFocusOffset(); + // NOTE: panning movements expect the camera to move exactly with the focus target, not animated behind - Nyx + mCameraSmoothingLastPositionGlobal = calcCameraPositionTargetGlobal(); +} + +//----------------------------------------------------------------------------- +// cameraPanUp() +//----------------------------------------------------------------------------- +void LLAgentCamera::cameraPanUp(F32 meters) +{ + LLVector3d up_axis; + up_axis.setVec(LLViewerCamera::getInstance()->getUpAxis()); + + mPanFocusDiff += meters * up_axis; + + mFocusTargetGlobal += meters * up_axis; + mFocusGlobal = mFocusTargetGlobal; + + // disable smoothing for camera pan, which causes some residents unhappiness + mCameraSmoothingStop = true; + + cameraZoomIn(1.f); + updateFocusOffset(); + // NOTE: panning movements expect the camera to move exactly with the focus target, not animated behind -Nyx + mCameraSmoothingLastPositionGlobal = calcCameraPositionTargetGlobal(); +} + +void LLAgentCamera::resetCameraPan() +{ + mFocusTargetGlobal -= mPanFocusDiff; + + mFocusGlobal = mFocusTargetGlobal; + mCameraSmoothingStop = true; + + cameraZoomIn(1.f); + updateFocusOffset(); + + mCameraSmoothingLastPositionGlobal = calcCameraPositionTargetGlobal(); + + resetPanDiff(); +} + +void LLAgentCamera::resetPanDiff() +{ + mPanFocusDiff.clear(); +} + +//----------------------------------------------------------------------------- +// updateLookAt() +//----------------------------------------------------------------------------- +void LLAgentCamera::updateLookAt(const S32 mouse_x, const S32 mouse_y) +{ + static LLVector3 last_at_axis; + + if (!isAgentAvatarValid()) return; + + LLQuaternion av_inv_rot = ~gAgentAvatarp->mRoot->getWorldRotation(); + LLVector3 root_at = LLVector3::x_axis * gAgentAvatarp->mRoot->getWorldRotation(); + + if (LLTrace::get_frame_recording().getLastRecording().getLastValue(*gViewerWindow->getMouseVelocityStat()) < 0.01f + && (root_at * last_at_axis > 0.95f)) + { + LLVector3 vel = gAgentAvatarp->getVelocity(); + if (vel.magVecSquared() > 4.f) + { + setLookAt(LOOKAT_TARGET_IDLE, gAgentAvatarp, vel * av_inv_rot); + } + else + { + // *FIX: rotate mframeagent by sit object's rotation? + LLQuaternion look_rotation = gAgentAvatarp->isSitting() ? gAgentAvatarp->getRenderRotation() : gAgent.getFrameAgent().getQuaternion(); // use camera's current rotation + LLVector3 look_offset = LLVector3(2.f, 0.f, 0.f) * look_rotation * av_inv_rot; + setLookAt(LOOKAT_TARGET_IDLE, gAgentAvatarp, look_offset); + } + last_at_axis = root_at; + return; + } + + last_at_axis = root_at; + + if (CAMERA_MODE_CUSTOMIZE_AVATAR == getCameraMode()) + { + setLookAt(LOOKAT_TARGET_NONE, gAgentAvatarp, LLVector3(-2.f, 0.f, 0.f)); + } + else + { + // Move head based on cursor position + ELookAtType lookAtType = LOOKAT_TARGET_NONE; + LLVector3 headLookAxis; + LLCoordFrame frameCamera = *((LLCoordFrame*)LLViewerCamera::getInstance()); + + if (cameraMouselook()) + { + lookAtType = LOOKAT_TARGET_MOUSELOOK; + } + else if (cameraThirdPerson()) + { + // range from -.5 to .5 + F32 x_from_center = + ((F32) mouse_x / (F32) gViewerWindow->getWorldViewWidthScaled() ) - 0.5f; + F32 y_from_center = + ((F32) mouse_y / (F32) gViewerWindow->getWorldViewHeightScaled() ) - 0.5f; + + frameCamera.yaw( - x_from_center * gSavedSettings.getF32("YawFromMousePosition") * DEG_TO_RAD); + frameCamera.pitch( - y_from_center * gSavedSettings.getF32("PitchFromMousePosition") * DEG_TO_RAD); + lookAtType = LOOKAT_TARGET_FREELOOK; + } + + headLookAxis = frameCamera.getAtAxis(); + // RN: we use world-space offset for mouselook and freelook + //headLookAxis = headLookAxis * av_inv_rot; + setLookAt(lookAtType, gAgentAvatarp, headLookAxis); + } +} + +static LLTrace::BlockTimerStatHandle FTM_UPDATE_CAMERA("Camera"); + +extern bool gCubeSnapshot; + +//----------------------------------------------------------------------------- +// updateCamera() +//----------------------------------------------------------------------------- +void LLAgentCamera::updateCamera() +{ + LL_RECORD_BLOCK_TIME(FTM_UPDATE_CAMERA); + if (gCubeSnapshot) + { + return; + } + + // - changed camera_skyward to the new global "mCameraUpVector" + mCameraUpVector = LLVector3::z_axis; + //LLVector3 camera_skyward(0.f, 0.f, 1.f); + + U32 camera_mode = mCameraAnimating ? mLastCameraMode : mCameraMode; + + validateFocusObject(); + + if (isAgentAvatarValid() && + gAgentAvatarp->isSitting() && + camera_mode == CAMERA_MODE_MOUSELOOK) + { + //changed camera_skyward to the new global "mCameraUpVector" + mCameraUpVector = mCameraUpVector * gAgentAvatarp->getRenderRotation(); + } + + if (cameraThirdPerson() && (mFocusOnAvatar || mAllowChangeToFollow) && LLFollowCamMgr::getInstance()->getActiveFollowCamParams()) + { + mAllowChangeToFollow = false; + mFocusOnAvatar = true; + changeCameraToFollow(); + } + + //NOTE - this needs to be integrated into a general upVector system here within llAgent. + if ( camera_mode == CAMERA_MODE_FOLLOW && mFocusOnAvatar ) + { + mCameraUpVector = mFollowCam.getUpVector(); + } + + if (mSitCameraEnabled) + { + if (mSitCameraReferenceObject->isDead()) + { + setSitCamera(LLUUID::null); + } + } + + // Update UI with our camera inputs + LLFloaterCamera* camera_floater = LLFloaterReg::findTypedInstance("camera"); + if (camera_floater) + { + camera_floater->mRotate->setToggleState(gAgentCamera.getOrbitRightKey() > 0.f, // left + gAgentCamera.getOrbitUpKey() > 0.f, // top + gAgentCamera.getOrbitLeftKey() > 0.f, // right + gAgentCamera.getOrbitDownKey() > 0.f); // bottom + + camera_floater->mTrack->setToggleState(gAgentCamera.getPanLeftKey() > 0.f, // left + gAgentCamera.getPanUpKey() > 0.f, // top + gAgentCamera.getPanRightKey() > 0.f, // right + gAgentCamera.getPanDownKey() > 0.f); // bottom + } + + // Handle camera movement based on keyboard. + const F32 ORBIT_OVER_RATE = 90.f * DEG_TO_RAD; // radians per second + const F32 ORBIT_AROUND_RATE = 90.f * DEG_TO_RAD; // radians per second + const F32 PAN_RATE = 5.f; // meters per second + + if (gAgentCamera.getOrbitUpKey() || gAgentCamera.getOrbitDownKey()) + { + F32 input_rate = gAgentCamera.getOrbitUpKey() - gAgentCamera.getOrbitDownKey(); + cameraOrbitOver( input_rate * ORBIT_OVER_RATE / gFPSClamped ); + } + + if (gAgentCamera.getOrbitLeftKey() || gAgentCamera.getOrbitRightKey()) + { + F32 input_rate = gAgentCamera.getOrbitLeftKey() - gAgentCamera.getOrbitRightKey(); + cameraOrbitAround(input_rate * ORBIT_AROUND_RATE / gFPSClamped); + } + + if (gAgentCamera.getOrbitInKey() || gAgentCamera.getOrbitOutKey()) + { + F32 input_rate = gAgentCamera.getOrbitInKey() - gAgentCamera.getOrbitOutKey(); + + LLVector3d to_focus = gAgent.getPosGlobalFromAgent(LLViewerCamera::getInstance()->getOrigin()) - calcFocusPositionTargetGlobal(); + F32 distance_to_focus = (F32)to_focus.magVec(); + // Move at distance (in meters) meters per second + cameraOrbitIn( input_rate * distance_to_focus / gFPSClamped ); + } + + if (gAgentCamera.getPanInKey() || gAgentCamera.getPanOutKey()) + { + F32 input_rate = gAgentCamera.getPanInKey() - gAgentCamera.getPanOutKey(); + cameraPanIn(input_rate * PAN_RATE / gFPSClamped); + } + + if (gAgentCamera.getPanRightKey() || gAgentCamera.getPanLeftKey()) + { + F32 input_rate = gAgentCamera.getPanRightKey() - gAgentCamera.getPanLeftKey(); + cameraPanLeft(input_rate * -PAN_RATE / gFPSClamped ); + } + + if (gAgentCamera.getPanUpKey() || gAgentCamera.getPanDownKey()) + { + F32 input_rate = gAgentCamera.getPanUpKey() - gAgentCamera.getPanDownKey(); + cameraPanUp(input_rate * PAN_RATE / gFPSClamped ); + } + + // Clear camera keyboard keys. + gAgentCamera.clearOrbitKeys(); + gAgentCamera.clearPanKeys(); + + // lerp camera focus offset + mCameraFocusOffset = lerp(mCameraFocusOffset, mCameraFocusOffsetTarget, LLSmoothInterpolation::getInterpolant(CAMERA_FOCUS_HALF_LIFE)); + + if ( mCameraMode == CAMERA_MODE_FOLLOW ) + { + if (isAgentAvatarValid()) + { + //-------------------------------------------------------------------------------- + // this is where the avatar's position and rotation are given to followCam, and + // where it is updated. All three of its attributes are updated: (1) position, + // (2) focus, and (3) upvector. They can then be queried elsewhere in llAgent. + //-------------------------------------------------------------------------------- + // *TODO: use combined rotation of frameagent and sit object + LLQuaternion avatarRotationForFollowCam = gAgentAvatarp->isSitting() ? gAgentAvatarp->getRenderRotation() : gAgent.getFrameAgent().getQuaternion(); + + LLFollowCamParams* current_cam = LLFollowCamMgr::getInstance()->getActiveFollowCamParams(); + if (current_cam) + { + mFollowCam.copyParams(*current_cam); + mFollowCam.setSubjectPositionAndRotation( gAgentAvatarp->getRenderPosition(), avatarRotationForFollowCam ); + mFollowCam.update(); + LLViewerJoystick::getInstance()->setCameraNeedsUpdate(true); + } + else + { + changeCameraToThirdPerson(true); + } + } + } + + bool hit_limit; + LLVector3d camera_pos_global; + LLVector3d camera_target_global = calcCameraPositionTargetGlobal(&hit_limit); + mCameraVirtualPositionAgent = gAgent.getPosAgentFromGlobal(camera_target_global); + LLVector3d focus_target_global = calcFocusPositionTargetGlobal(); + + // perform field of view correction + mCameraFOVZoomFactor = calcCameraFOVZoomFactor(); + camera_target_global = focus_target_global + (camera_target_global - focus_target_global) * (1.f + mCameraFOVZoomFactor); + + gAgent.setShowAvatar(true); // can see avatar by default + + // Adjust position for animation + if (mCameraAnimating) + { + F32 time = mAnimationTimer.getElapsedTimeF32(); + + // yet another instance of critically damped motion, hooray! + // F32 fraction_of_animation = 1.f - pow(2.f, -time / CAMERA_ZOOM_HALF_LIFE); + + // linear interpolation + F32 fraction_of_animation = time / mAnimationDuration; + + bool isfirstPerson = mCameraMode == CAMERA_MODE_MOUSELOOK; + bool wasfirstPerson = mLastCameraMode == CAMERA_MODE_MOUSELOOK; + F32 fraction_animation_to_skip; + + if (mAnimationCameraStartGlobal == camera_target_global) + { + fraction_animation_to_skip = 0.f; + } + else + { + LLVector3d cam_delta = mAnimationCameraStartGlobal - camera_target_global; + fraction_animation_to_skip = HEAD_BUFFER_SIZE / (F32)cam_delta.magVec(); + } + F32 animation_start_fraction = (wasfirstPerson) ? fraction_animation_to_skip : 0.f; + F32 animation_finish_fraction = (isfirstPerson) ? (1.f - fraction_animation_to_skip) : 1.f; + + if (fraction_of_animation < animation_finish_fraction) + { + if (fraction_of_animation < animation_start_fraction || fraction_of_animation > animation_finish_fraction ) + { + gAgent.setShowAvatar(false); + } + + // ...adjust position for animation + F32 smooth_fraction_of_animation = llsmoothstep(0.0f, 1.0f, fraction_of_animation); + camera_pos_global = lerp(mAnimationCameraStartGlobal, camera_target_global, smooth_fraction_of_animation); + mFocusGlobal = lerp(mAnimationFocusStartGlobal, focus_target_global, smooth_fraction_of_animation); + } + else + { + // ...animation complete + mCameraAnimating = false; + + camera_pos_global = camera_target_global; + mFocusGlobal = focus_target_global; + + gAgent.endAnimationUpdateUI(); + gAgent.setShowAvatar(true); + } + + if (isAgentAvatarValid() && (mCameraMode != CAMERA_MODE_MOUSELOOK)) + { + gAgentAvatarp->updateAttachmentVisibility(mCameraMode); + } + } + else + { + camera_pos_global = camera_target_global; + mFocusGlobal = focus_target_global; + gAgent.setShowAvatar(true); + } + + // smoothing + if (true) + { + LLVector3d agent_pos = gAgent.getPositionGlobal(); + LLVector3d camera_pos_agent = camera_pos_global - agent_pos; + // Sitting on what you're manipulating can cause camera jitter with smoothing. + // This turns off smoothing while editing. -MG + bool in_build_mode = LLToolMgr::getInstance()->inBuildMode(); + mCameraSmoothingStop = mCameraSmoothingStop || in_build_mode; + + if (cameraThirdPerson() && !mCameraSmoothingStop) + { + const F32 SMOOTHING_HALF_LIFE = 0.02f; + + F32 smoothing = LLSmoothInterpolation::getInterpolant(gSavedSettings.getF32("CameraPositionSmoothing") * SMOOTHING_HALF_LIFE, false); + + if (mFocusOnAvatar && !mFocusObject) // we differentiate on avatar mode + { + // for avatar-relative focus, we smooth in avatar space - + // the avatar moves too jerkily w/r/t global space to smooth there. + + LLVector3d delta = camera_pos_agent - mCameraSmoothingLastPositionAgent; + if (delta.magVec() < MAX_CAMERA_SMOOTH_DISTANCE) // only smooth over short distances please + { + camera_pos_agent = lerp(mCameraSmoothingLastPositionAgent, camera_pos_agent, smoothing); + camera_pos_global = camera_pos_agent + agent_pos; + } + } + else + { + LLVector3d delta = camera_pos_global - mCameraSmoothingLastPositionGlobal; + if (delta.magVec() < MAX_CAMERA_SMOOTH_DISTANCE) // only smooth over short distances please + { + camera_pos_global = lerp(mCameraSmoothingLastPositionGlobal, camera_pos_global, smoothing); + } + } + } + + mCameraSmoothingLastPositionGlobal = camera_pos_global; + mCameraSmoothingLastPositionAgent = camera_pos_agent; + mCameraSmoothingStop = false; + } + + + mCameraCurrentFOVZoomFactor = lerp(mCameraCurrentFOVZoomFactor, mCameraFOVZoomFactor, LLSmoothInterpolation::getInterpolant(FOV_ZOOM_HALF_LIFE)); + +// LL_INFOS() << "Current FOV Zoom: " << mCameraCurrentFOVZoomFactor << " Target FOV Zoom: " << mCameraFOVZoomFactor << " Object penetration: " << mFocusObjectDist << LL_ENDL; + + LLVector3 focus_agent = gAgent.getPosAgentFromGlobal(mFocusGlobal); + + mCameraPositionAgent = gAgent.getPosAgentFromGlobal(camera_pos_global); + + // Move the camera + + LLViewerCamera::getInstance()->updateCameraLocation(mCameraPositionAgent, mCameraUpVector, focus_agent); + //LLViewerCamera::getInstance()->updateCameraLocation(mCameraPositionAgent, camera_skyward, focus_agent); + + // Change FOV + LLViewerCamera::getInstance()->setView(LLViewerCamera::getInstance()->getDefaultFOV() / (1.f + mCameraCurrentFOVZoomFactor)); + + // follow camera when in customize mode + if (cameraCustomizeAvatar()) + { + setLookAt(LOOKAT_TARGET_FOCUS, NULL, mCameraPositionAgent); + } + + // update the travel distance stat + // this isn't directly related to the camera + // but this seemed like the best place to do this + LLVector3d global_pos = gAgent.getPositionGlobal(); + if (!gAgent.getLastPositionGlobal().isExactlyZero()) + { + LLVector3d delta = global_pos - gAgent.getLastPositionGlobal(); + gAgent.setDistanceTraveled(gAgent.getDistanceTraveled() + delta.magVec()); + } + gAgent.setLastPositionGlobal(global_pos); + + if (LLVOAvatar::sVisibleInFirstPerson && isAgentAvatarValid() && !gAgentAvatarp->isSitting() && cameraMouselook()) + { + LLVector3 head_pos = gAgentAvatarp->mHeadp->getWorldPosition() + + LLVector3(0.08f, 0.f, 0.05f) * gAgentAvatarp->mHeadp->getWorldRotation() + + LLVector3(0.1f, 0.f, 0.f) * gAgentAvatarp->mPelvisp->getWorldRotation(); + LLVector3 diff = mCameraPositionAgent - head_pos; + diff = diff * ~gAgentAvatarp->mRoot->getWorldRotation(); + + LLJoint* torso_joint = gAgentAvatarp->mTorsop; + LLJoint* chest_joint = gAgentAvatarp->mChestp; + LLVector3 torso_scale = torso_joint->getScale(); + LLVector3 chest_scale = chest_joint->getScale(); + + // shorten avatar skeleton to avoid foot interpenetration + if (!gAgentAvatarp->mInAir) + { + LLVector3 chest_offset = LLVector3(0.f, 0.f, chest_joint->getPosition().mV[VZ]) * torso_joint->getWorldRotation(); + F32 z_compensate = llclamp(-diff.mV[VZ], -0.2f, 1.f); + F32 scale_factor = llclamp(1.f - ((z_compensate * 0.5f) / chest_offset.mV[VZ]), 0.5f, 1.2f); + torso_joint->setScale(LLVector3(1.f, 1.f, scale_factor)); + + LLJoint* neck_joint = gAgentAvatarp->mNeckp; + LLVector3 neck_offset = LLVector3(0.f, 0.f, neck_joint->getPosition().mV[VZ]) * chest_joint->getWorldRotation(); + scale_factor = llclamp(1.f - ((z_compensate * 0.5f) / neck_offset.mV[VZ]), 0.5f, 1.2f); + chest_joint->setScale(LLVector3(1.f, 1.f, scale_factor)); + diff.mV[VZ] = 0.f; + } + + // SL-315 + gAgentAvatarp->mPelvisp->setPosition(gAgentAvatarp->mPelvisp->getPosition() + diff); + + gAgentAvatarp->mRoot->updateWorldMatrixChildren(); + + for (LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); + iter != gAgentAvatarp->mAttachmentPoints.end(); ) + { + LLVOAvatar::attachment_map_t::iterator curiter = iter++; + LLViewerJointAttachment* attachment = curiter->second; + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + LLViewerObject *attached_object = attachment_iter->get(); + if (attached_object && !attached_object->isDead() && attached_object->mDrawable.notNull()) + { + // clear any existing "early" movements of attachment + attached_object->mDrawable->clearState(LLDrawable::EARLY_MOVE); + gPipeline.updateMoveNormalAsync(attached_object->mDrawable); + attached_object->updateText(); + } + } + } + + torso_joint->setScale(torso_scale); + chest_joint->setScale(chest_scale); + } +} + +void LLAgentCamera::updateLastCamera() +{ + mLastCameraMode = mCameraMode; +} + +void LLAgentCamera::updateFocusOffset() +{ + validateFocusObject(); + if (mFocusObject.notNull()) + { + LLVector3d obj_pos = gAgent.getPosGlobalFromAgent(mFocusObject->getRenderPosition()); + mFocusObjectOffset.setVec(mFocusTargetGlobal - obj_pos); + } +} + +void LLAgentCamera::validateFocusObject() +{ + if (mFocusObject.notNull() && + mFocusObject->isDead()) + { + mFocusObjectOffset.clearVec(); + clearFocusObject(); + mCameraFOVZoomFactor = 0.f; + } +} + +//----------------------------------------------------------------------------- +// calcFocusPositionTargetGlobal() +//----------------------------------------------------------------------------- +LLVector3d LLAgentCamera::calcFocusPositionTargetGlobal() +{ + if (mFocusObject.notNull() && mFocusObject->isDead()) + { + clearFocusObject(); + } + + if (mCameraMode == CAMERA_MODE_FOLLOW && mFocusOnAvatar) + { + mFocusTargetGlobal = gAgent.getPosGlobalFromAgent(mFollowCam.getSimulatedFocus()); + return mFocusTargetGlobal; + } + else if (mCameraMode == CAMERA_MODE_MOUSELOOK) + { + LLVector3d at_axis(1.0, 0.0, 0.0); + LLQuaternion agent_rot = gAgent.getFrameAgent().getQuaternion(); + if (isAgentAvatarValid() && gAgentAvatarp->getParent()) + { + LLViewerObject* root_object = (LLViewerObject*)gAgentAvatarp->getRoot(); + if (!root_object->flagCameraDecoupled()) + { + agent_rot *= ((LLViewerObject*)(gAgentAvatarp->getParent()))->getRenderRotation(); + } + } + at_axis = at_axis * agent_rot; + mFocusTargetGlobal = calcCameraPositionTargetGlobal() + at_axis; + return mFocusTargetGlobal; + } + else if (mCameraMode == CAMERA_MODE_CUSTOMIZE_AVATAR) + { + if (mFocusOnAvatar) + { + LLVector3 focus_target = isAgentAvatarValid() + ? gAgentAvatarp->mHeadp->getWorldPosition() + : gAgent.getPositionAgent(); + LLVector3d focus_target_global = gAgent.getPosGlobalFromAgent(focus_target); + mFocusTargetGlobal = focus_target_global; + } + return mFocusTargetGlobal; + } + else if (!mFocusOnAvatar) + { + if (mFocusObject.notNull() && !mFocusObject->isDead() && mFocusObject->mDrawable.notNull()) + { + LLDrawable* drawablep = mFocusObject->mDrawable; + + if (mTrackFocusObject && + drawablep && + drawablep->isActive()) + { + if (!mFocusObject->isAvatar()) + { + if (mFocusObject->isSelected()) + { + gPipeline.updateMoveNormalAsync(drawablep); + } + else + { + if (drawablep->isState(LLDrawable::MOVE_UNDAMPED)) + { + gPipeline.updateMoveNormalAsync(drawablep); + } + else + { + gPipeline.updateMoveDampedAsync(drawablep); + } + } + } + } + // if not tracking object, update offset based on new object position + else + { + updateFocusOffset(); + } + LLVector3 focus_agent = mFocusObject->getRenderPosition() + mFocusObjectOffset; + mFocusTargetGlobal.setVec(gAgent.getPosGlobalFromAgent(focus_agent)); + } + return mFocusTargetGlobal; + } + else if (mSitCameraEnabled && isAgentAvatarValid() && gAgentAvatarp->isSitting() && mSitCameraReferenceObject.notNull()) + { + // sit camera + LLVector3 object_pos = mSitCameraReferenceObject->getRenderPosition(); + LLQuaternion object_rot = mSitCameraReferenceObject->getRenderRotation(); + + LLVector3 target_pos = object_pos + (mSitCameraFocus * object_rot); + return gAgent.getPosGlobalFromAgent(target_pos); + } + else + { + return gAgent.getPositionGlobal() + calcThirdPersonFocusOffset(); + } +} + +LLVector3d LLAgentCamera::calcThirdPersonFocusOffset() +{ + // ...offset from avatar + LLVector3d focus_offset; + LLQuaternion agent_rot = gAgent.getFrameAgent().getQuaternion(); + if (isAgentAvatarValid() && gAgentAvatarp->getParent()) + { + agent_rot *= ((LLViewerObject*)(gAgentAvatarp->getParent()))->getRenderRotation(); + } + + static LLCachedControl focus_offset_initial(gSavedSettings, "FocusOffsetRearView", LLVector3d()); + return focus_offset_initial * agent_rot; +} + +void LLAgentCamera::setupSitCamera() +{ + // agent frame entering this function is in world coordinates + if (isAgentAvatarValid() && gAgentAvatarp->getParent()) + { + LLQuaternion parent_rot = ((LLViewerObject*)gAgentAvatarp->getParent())->getRenderRotation(); + // slam agent coordinate frame to proper parent local version + LLVector3 at_axis = gAgent.getFrameAgent().getAtAxis(); + at_axis.mV[VZ] = 0.f; + at_axis.normalize(); + gAgent.resetAxes(at_axis * ~parent_rot); + } +} + +//----------------------------------------------------------------------------- +// getCameraPositionAgent() +//----------------------------------------------------------------------------- +const LLVector3 &LLAgentCamera::getCameraPositionAgent() const +{ + return LLViewerCamera::getInstance()->getOrigin(); +} + +//----------------------------------------------------------------------------- +// getCameraPositionGlobal() +//----------------------------------------------------------------------------- +LLVector3d LLAgentCamera::getCameraPositionGlobal() const +{ + return gAgent.getPosGlobalFromAgent(LLViewerCamera::getInstance()->getOrigin()); +} + +//----------------------------------------------------------------------------- +// calcCameraFOVZoomFactor() +//----------------------------------------------------------------------------- +F32 LLAgentCamera::calcCameraFOVZoomFactor() +{ + LLVector3 camera_offset_dir; + camera_offset_dir.setVec(mCameraFocusOffset); + + if (mCameraMode == CAMERA_MODE_MOUSELOOK) + { + return 0.f; + } + else if (mFocusObject.notNull() && !mFocusObject->isAvatar() && !mFocusOnAvatar) + { + // don't FOV zoom on mostly transparent objects + F32 obj_min_dist = 0.f; + calcCameraMinDistance(obj_min_dist); + F32 current_distance = llmax(0.001f, camera_offset_dir.magVec()); + + mFocusObjectDist = obj_min_dist - current_distance; + + F32 new_fov_zoom = llclamp(mFocusObjectDist / current_distance, 0.f, 1000.f); + return new_fov_zoom; + } + else // focusing on land or avatar + { + // keep old field of view until user changes focus explicitly + return mCameraFOVZoomFactor; + //return 0.f; + } +} + +//----------------------------------------------------------------------------- +// calcCameraPositionTargetGlobal() +//----------------------------------------------------------------------------- +LLVector3d LLAgentCamera::calcCameraPositionTargetGlobal(bool *hit_limit) +{ + // Compute base camera position and look-at points. + F32 camera_land_height; + LLVector3d frame_center_global = !isAgentAvatarValid() ? + gAgent.getPositionGlobal() : + gAgent.getPosGlobalFromAgent(getAvatarRootPosition()); + + bool isConstrained = false; + LLVector3d head_offset; + head_offset.setVec(mThirdPersonHeadOffset); + + LLVector3d camera_position_global; + + if (mCameraMode == CAMERA_MODE_FOLLOW && mFocusOnAvatar) + { + camera_position_global = gAgent.getPosGlobalFromAgent(mFollowCam.getSimulatedPosition()); + } + else if (mCameraMode == CAMERA_MODE_MOUSELOOK) + { + if (!isAgentAvatarValid() || gAgentAvatarp->mDrawable.isNull()) + { + LL_WARNS() << "Null avatar drawable!" << LL_ENDL; + return LLVector3d::zero; + } + + head_offset.clearVec(); + F32 fixup; + if (gAgentAvatarp->hasPelvisFixup(fixup) && !gAgentAvatarp->isSitting()) + { + head_offset[VZ] -= fixup; + } + if (gAgentAvatarp->isSitting()) + { + head_offset.mdV[VZ] += 0.1; + } + + if (gAgentAvatarp->isSitting() && gAgentAvatarp->getParent()) + { + gAgentAvatarp->updateHeadOffset(); + head_offset.mdV[VX] += gAgentAvatarp->mHeadOffset.mV[VX]; + head_offset.mdV[VY] += gAgentAvatarp->mHeadOffset.mV[VY]; + head_offset.mdV[VZ] += gAgentAvatarp->mHeadOffset.mV[VZ]; + const LLMatrix4& mat = ((LLViewerObject*) gAgentAvatarp->getParent())->getRenderMatrix(); + camera_position_global = gAgent.getPosGlobalFromAgent + ((gAgentAvatarp->getPosition()+ + LLVector3(head_offset)*gAgentAvatarp->getRotation()) * mat); + } + else + { + head_offset.mdV[VZ] += gAgentAvatarp->mHeadOffset.mV[VZ]; + camera_position_global = gAgent.getPosGlobalFromAgent(gAgentAvatarp->getRenderPosition());//frame_center_global; + head_offset = head_offset * gAgentAvatarp->getRenderRotation(); + camera_position_global = camera_position_global + head_offset; + } + } + else if (mCameraMode == CAMERA_MODE_THIRD_PERSON && mFocusOnAvatar) + { + LLVector3 local_camera_offset; + F32 camera_distance = 0.f; + + if (mSitCameraEnabled + && isAgentAvatarValid() + && gAgentAvatarp->isSitting() + && mSitCameraReferenceObject.notNull()) + { + // sit camera + LLVector3 object_pos = mSitCameraReferenceObject->getRenderPosition(); + LLQuaternion object_rot = mSitCameraReferenceObject->getRenderRotation(); + + LLVector3 target_pos = object_pos + (mSitCameraPos * object_rot); + + camera_position_global = gAgent.getPosGlobalFromAgent(target_pos); + } + else + { + static LLCachedControl camera_offset_scale(gSavedSettings, "CameraOffsetScale"); + local_camera_offset = mCameraZoomFraction * getCameraOffsetInitial() * camera_offset_scale; + + // are we sitting down? + if (isAgentAvatarValid() && gAgentAvatarp->getParent()) + { + LLQuaternion parent_rot = ((LLViewerObject*)gAgentAvatarp->getParent())->getRenderRotation(); + // slam agent coordinate frame to proper parent local version + LLVector3 at_axis = gAgent.getFrameAgent().getAtAxis() * parent_rot; + at_axis.mV[VZ] = 0.f; + at_axis.normalize(); + gAgent.resetAxes(at_axis * ~parent_rot); + + local_camera_offset = local_camera_offset * gAgent.getFrameAgent().getQuaternion() * parent_rot; + } + else + { + local_camera_offset = gAgent.getFrameAgent().rotateToAbsolute( local_camera_offset ); + } + + if (!isDisableCameraConstraints() && !mCameraCollidePlane.isExactlyZero() && + (!isAgentAvatarValid() || !gAgentAvatarp->isSitting())) + { + LLVector3 plane_normal; + plane_normal.setVec(mCameraCollidePlane.mV); + + F32 offset_dot_norm = local_camera_offset * plane_normal; + if (llabs(offset_dot_norm) < 0.001f) + { + offset_dot_norm = 0.001f; + } + + camera_distance = local_camera_offset.normalize(); + + F32 pos_dot_norm = gAgent.getPosAgentFromGlobal(frame_center_global + head_offset) * plane_normal; + + // if agent is outside the colliding half-plane + if (pos_dot_norm > mCameraCollidePlane.mV[VW]) + { + // check to see if camera is on the opposite side (inside) the half-plane + if (offset_dot_norm + pos_dot_norm < mCameraCollidePlane.mV[VW]) + { + // diminish offset by factor to push it back outside the half-plane + camera_distance *= (pos_dot_norm - mCameraCollidePlane.mV[VW] - CAMERA_COLLIDE_EPSILON) / -offset_dot_norm; + } + } + else + { + if (offset_dot_norm + pos_dot_norm > mCameraCollidePlane.mV[VW]) + { + camera_distance *= (mCameraCollidePlane.mV[VW] - pos_dot_norm - CAMERA_COLLIDE_EPSILON) / offset_dot_norm; + } + } + } + else + { + camera_distance = local_camera_offset.normalize(); + } + + mTargetCameraDistance = llmax(camera_distance, MIN_CAMERA_DISTANCE); + + if (mTargetCameraDistance != mCurrentCameraDistance) + { + F32 camera_lerp_amt = LLSmoothInterpolation::getInterpolant(CAMERA_ZOOM_HALF_LIFE); + + mCurrentCameraDistance = lerp(mCurrentCameraDistance, mTargetCameraDistance, camera_lerp_amt); + } + + // Make the camera distance current + local_camera_offset *= mCurrentCameraDistance; + + // set the global camera position + LLVector3d camera_offset; + + camera_offset.setVec( local_camera_offset ); + camera_position_global = frame_center_global + head_offset + camera_offset; + + if (isAgentAvatarValid()) + { + LLVector3d camera_lag_d; + F32 lag_interp = LLSmoothInterpolation::getInterpolant(CAMERA_LAG_HALF_LIFE); + LLVector3 target_lag; + LLVector3 vel = gAgent.getVelocity(); + + // lag by appropriate amount for flying + F32 time_in_air = gAgentAvatarp->mTimeInAir.getElapsedTimeF32(); + if(!mCameraAnimating && gAgentAvatarp->mInAir && time_in_air > GROUND_TO_AIR_CAMERA_TRANSITION_START_TIME) + { + LLVector3 frame_at_axis = gAgent.getFrameAgent().getAtAxis(); + frame_at_axis -= projected_vec(frame_at_axis, gAgent.getReferenceUpVector()); + frame_at_axis.normalize(); + + //transition smoothly in air mode, to avoid camera pop + F32 u = (time_in_air - GROUND_TO_AIR_CAMERA_TRANSITION_START_TIME) / GROUND_TO_AIR_CAMERA_TRANSITION_TIME; + u = llclamp(u, 0.f, 1.f); + + lag_interp *= u; + + if (gViewerWindow->getLeftMouseDown() && gViewerWindow->getLastPick().mObjectID == gAgentAvatarp->getID()) + { + // disable camera lag when using mouse-directed steering + target_lag.clearVec(); + } + else + { + LLCachedControl dynamic_camera_strength(gSavedSettings, "DynamicCameraStrength"); + target_lag = vel * dynamic_camera_strength / 30.f; + } + + mCameraLag = lerp(mCameraLag, target_lag, lag_interp); + + F32 lag_dist = mCameraLag.magVec(); + if (lag_dist > MAX_CAMERA_LAG) + { + mCameraLag = mCameraLag * MAX_CAMERA_LAG / lag_dist; + } + + // clamp camera lag so that avatar is always in front + F32 dot = (mCameraLag - (frame_at_axis * (MIN_CAMERA_LAG * u))) * frame_at_axis; + if (dot < -(MIN_CAMERA_LAG * u)) + { + mCameraLag -= (dot + (MIN_CAMERA_LAG * u)) * frame_at_axis; + } + } + else + { + mCameraLag = lerp(mCameraLag, LLVector3::zero, LLSmoothInterpolation::getInterpolant(0.15f)); + } + + camera_lag_d.setVec(mCameraLag); + camera_position_global = camera_position_global - camera_lag_d; + } + } + } + else + { + LLVector3d focusPosGlobal = calcFocusPositionTargetGlobal(); + // camera gets pushed out later wrt mCameraFOVZoomFactor...this is "raw" value + camera_position_global = focusPosGlobal + mCameraFocusOffset; + } + + if (!isDisableCameraConstraints() && !gAgent.isGodlike()) + { + LLViewerRegion* regionp = LLWorld::getInstance()->getRegionFromPosGlobal(camera_position_global); + bool constrain = true; + if(regionp && regionp->canManageEstate()) + { + constrain = false; + } + if(constrain) + { + F32 max_dist = (CAMERA_MODE_CUSTOMIZE_AVATAR == mCameraMode) ? APPEARANCE_MAX_ZOOM : mDrawDistance; + + LLVector3d camera_offset = camera_position_global - gAgent.getPositionGlobal(); + F32 camera_distance = (F32)camera_offset.magVec(); + + if(camera_distance > max_dist) + { + camera_position_global = gAgent.getPositionGlobal() + (max_dist/camera_distance)*camera_offset; + isConstrained = true; + } + } + +// JC - Could constrain camera based on parcel stuff here. +// LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromPosGlobal(camera_position_global); +// +// if (regionp && !regionp->mParcelOverlay->isBuildCameraAllowed(regionp->getPosRegionFromGlobal(camera_position_global))) +// { +// camera_position_global = last_position_global; +// +// isConstrained = true; +// } + } + + // Don't let camera go underground + F32 camera_min_off_ground = getCameraMinOffGround(); + camera_land_height = LLWorld::getInstance()->resolveLandHeightGlobal(camera_position_global); + F32 minZ = llmax(F_ALMOST_ZERO, camera_land_height + camera_min_off_ground); + if (camera_position_global.mdV[VZ] < minZ) + { + camera_position_global.mdV[VZ] = minZ; + isConstrained = true; + } + + if (hit_limit) + { + *hit_limit = isConstrained; + } + + return camera_position_global; +} + + +LLVector3 LLAgentCamera::getCurrentCameraOffset() +{ + return (LLViewerCamera::getInstance()->getOrigin() - getAvatarRootPosition() - mThirdPersonHeadOffset) * ~getCurrentAvatarRotation(); +} + +LLVector3d LLAgentCamera::getCurrentFocusOffset() +{ + return (mFocusTargetGlobal - gAgent.getPositionGlobal()) * ~getCurrentAvatarRotation(); +} + +LLQuaternion LLAgentCamera::getCurrentAvatarRotation() +{ + LLViewerObject* sit_object = (LLViewerObject*)gAgentAvatarp->getParent(); + + LLQuaternion av_rot = gAgent.getFrameAgent().getQuaternion(); + LLQuaternion obj_rot = sit_object ? sit_object->getRenderRotation() : LLQuaternion::DEFAULT; + return av_rot * obj_rot; +} + +bool LLAgentCamera::isJoystickCameraUsed() +{ + return ((mOrbitAroundRadians != 0) || (mOrbitOverAngle != 0) || !mPanFocusDiff.isNull()); +} + +LLVector3 LLAgentCamera::getCameraOffsetInitial() +{ + // getCameraOffsetInitial and getFocusOffsetInitial can be called on update from idle before init() + static LLCachedControl camera_offset_initial (gSavedSettings, "CameraOffsetRearView", LLVector3()); + return camera_offset_initial; +} + +LLVector3d LLAgentCamera::getFocusOffsetInitial() +{ + static LLCachedControl focus_offset_initial(gSavedSettings, "FocusOffsetRearView", LLVector3d()); + return focus_offset_initial; +} + +F32 LLAgentCamera::getCameraMaxZoomDistance() +{ + // SL-14706 / SL-14885 TPV have relaxed camera constraints allowing you to mousewheeel zoom WAY out. + static LLCachedControl s_disable_camera_constraints(gSavedSettings, "DisableCameraConstraints", false); + if (s_disable_camera_constraints) + { + return (F32)INT_MAX; + } + + // Ignore "DisableCameraConstraints", we don't want to be out of draw range when we focus onto objects or avatars + return llmin(MAX_CAMERA_DISTANCE_FROM_OBJECT, + mDrawDistance - 1, // convenience, don't hit draw limit when focusing on something + LLWorld::getInstance()->getRegionWidthInMeters() - CAMERA_FUDGE_FROM_OBJECT); +} + +LLVector3 LLAgentCamera::getAvatarRootPosition() +{ + static LLCachedControl use_hover_height(gSavedSettings, "HoverHeightAffectsCamera"); + return use_hover_height ? gAgentAvatarp->mRoot->getWorldPosition() : gAgentAvatarp->mRoot->getWorldPosition() - gAgentAvatarp->getHoverOffset(); + +} +//----------------------------------------------------------------------------- +// handleScrollWheel() +//----------------------------------------------------------------------------- +void LLAgentCamera::handleScrollWheel(S32 clicks) +{ + if (mCameraMode == CAMERA_MODE_FOLLOW && getFocusOnAvatar()) + { + if (!mFollowCam.getPositionLocked()) // not if the followCam position is locked in place + { + mFollowCam.zoom(clicks); + if (mFollowCam.isZoomedToMinimumDistance()) + { + changeCameraToMouselook(false); + } + } + } + else + { + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + const F32 ROOT_ROOT_TWO = sqrt(F_SQRT2); + + // Block if camera is animating + if (mCameraAnimating) + { + return; + } + + if (selection->getObjectCount() && selection->getSelectType() == SELECT_TYPE_HUD) + { + F32 zoom_factor = (F32)pow(0.8, -clicks); + cameraZoomIn(zoom_factor); + } + else if (mFocusOnAvatar && (mCameraMode == CAMERA_MODE_THIRD_PERSON)) + { + F32 camera_offset_initial_mag = getCameraOffsetInitial().magVec(); + + F32 current_zoom_fraction = mTargetCameraDistance / (camera_offset_initial_mag * gSavedSettings.getF32("CameraOffsetScale")); + current_zoom_fraction *= 1.f - pow(ROOT_ROOT_TWO, clicks); + + cameraOrbitIn(current_zoom_fraction * camera_offset_initial_mag * gSavedSettings.getF32("CameraOffsetScale")); + } + else + { + F32 current_zoom_fraction = (F32)mCameraFocusOffsetTarget.magVec(); + cameraOrbitIn(current_zoom_fraction * (1.f - pow(ROOT_ROOT_TWO, clicks))); + } + } +} + + +//----------------------------------------------------------------------------- +// getCameraMinOffGround() +//----------------------------------------------------------------------------- +F32 LLAgentCamera::getCameraMinOffGround() +{ + if (mCameraMode == CAMERA_MODE_MOUSELOOK) + { + return 0.f; + } + + if (isDisableCameraConstraints()) + { + return -1000.f; + } + + return 0.5f; +} + + +//----------------------------------------------------------------------------- +// resetCamera() +//----------------------------------------------------------------------------- +void LLAgentCamera::resetCamera() +{ + // Remove any pitch from the avatar + LLVector3 at = gAgent.getFrameAgent().getAtAxis(); + at.mV[VZ] = 0.f; + at.normalize(); + gAgent.resetAxes(at); + // have to explicitly clear field of view zoom now + mCameraFOVZoomFactor = 0.f; + + updateCamera(); +} + +//----------------------------------------------------------------------------- +// changeCameraToMouselook() +//----------------------------------------------------------------------------- +void LLAgentCamera::changeCameraToMouselook(bool animate) +{ + if (!gSavedSettings.getBOOL("EnableMouselook") + || LLViewerJoystick::getInstance()->getOverrideCamera()) + { + return; + } + + // visibility changes at end of animation + gViewerWindow->getWindow()->resetBusyCount(); + + // Menus should not remain open on switching to mouselook... + LLMenuGL::sMenuContainer->hideMenus(); + LLUI::getInstance()->clearPopups(); + + // unpause avatar animation + gAgent.unpauseAnimation(); + + LLToolMgr::getInstance()->setCurrentToolset(gMouselookToolset); + + if (isAgentAvatarValid()) + { + gAgentAvatarp->stopMotion(ANIM_AGENT_BODY_NOISE); + gAgentAvatarp->stopMotion(ANIM_AGENT_BREATHE_ROT); + } + + //gViewerWindow->stopGrab(); + LLSelectMgr::getInstance()->deselectAll(); + gViewerWindow->hideCursor(); + gViewerWindow->moveCursorToCenter(); + + if (mCameraMode != CAMERA_MODE_MOUSELOOK) + { + gFocusMgr.setKeyboardFocus(NULL); + + updateLastCamera(); + mCameraMode = CAMERA_MODE_MOUSELOOK; + const U32 old_flags = gAgent.getControlFlags(); + gAgent.setControlFlags(AGENT_CONTROL_MOUSELOOK); + if (old_flags != gAgent.getControlFlags()) + { + gAgent.setFlagsDirty(); + } + + if (animate) + { + startCameraAnimation(); + } + else + { + mCameraAnimating = false; + gAgent.endAnimationUpdateUI(); + } + } +} + + +//----------------------------------------------------------------------------- +// changeCameraToDefault() +//----------------------------------------------------------------------------- +void LLAgentCamera::changeCameraToDefault() +{ + if (LLViewerJoystick::getInstance()->getOverrideCamera()) + { + return; + } + + if (LLFollowCamMgr::getInstance()->getActiveFollowCamParams()) + { + changeCameraToFollow(); + } + else + { + changeCameraToThirdPerson(); + } + if (gSavedSettings.getBOOL("HideUIControls")) + { + gViewerWindow->setUIVisibility(false); + LLPanelStandStopFlying::getInstance()->setVisible(false); + } +} + + +//----------------------------------------------------------------------------- +// changeCameraToFollow() +//----------------------------------------------------------------------------- +void LLAgentCamera::changeCameraToFollow(bool animate) +{ + if (LLViewerJoystick::getInstance()->getOverrideCamera()) + { + return; + } + + if(mCameraMode != CAMERA_MODE_FOLLOW) + { + if (mCameraMode == CAMERA_MODE_MOUSELOOK) + { + animate = false; + } + startCameraAnimation(); + + updateLastCamera(); + mCameraMode = CAMERA_MODE_FOLLOW; + + // bang-in the current focus, position, and up vector of the follow cam + mFollowCam.reset(mCameraPositionAgent, LLViewerCamera::getInstance()->getPointOfInterest(), LLVector3::z_axis); + + if (gBasicToolset) + { + LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); + } + + if (isAgentAvatarValid()) + { + // SL-315 + gAgentAvatarp->mPelvisp->setPosition(LLVector3::zero); + gAgentAvatarp->startMotion( ANIM_AGENT_BODY_NOISE ); + gAgentAvatarp->startMotion( ANIM_AGENT_BREATHE_ROT ); + } + + // unpause avatar animation + gAgent.unpauseAnimation(); + + gAgent.clearControlFlags(AGENT_CONTROL_MOUSELOOK); + + if (animate) + { + startCameraAnimation(); + } + else + { + mCameraAnimating = false; + gAgent.endAnimationUpdateUI(); + } + } +} + +//----------------------------------------------------------------------------- +// changeCameraToThirdPerson() +//----------------------------------------------------------------------------- +void LLAgentCamera::changeCameraToThirdPerson(bool animate) +{ + if (LLViewerJoystick::getInstance()->getOverrideCamera()) + { + return; + } + + gViewerWindow->getWindow()->resetBusyCount(); + + mCameraZoomFraction = INITIAL_ZOOM_FRACTION; + + if (isAgentAvatarValid()) + { + if (!gAgentAvatarp->isSitting()) + { + // SL-315 + gAgentAvatarp->mPelvisp->setPosition(LLVector3::zero); + } + gAgentAvatarp->startMotion(ANIM_AGENT_BODY_NOISE); + gAgentAvatarp->startMotion(ANIM_AGENT_BREATHE_ROT); + } + + LLVector3 at_axis; + + // unpause avatar animation + gAgent.unpauseAnimation(); + + if (mCameraMode != CAMERA_MODE_THIRD_PERSON) + { + if (gBasicToolset) + { + LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); + } + + mCameraLag.clearVec(); + if (mCameraMode == CAMERA_MODE_MOUSELOOK) + { + mCurrentCameraDistance = MIN_CAMERA_DISTANCE; + mTargetCameraDistance = MIN_CAMERA_DISTANCE; + animate = false; + } + updateLastCamera(); + mCameraMode = CAMERA_MODE_THIRD_PERSON; + gAgent.clearControlFlags(AGENT_CONTROL_MOUSELOOK); + } + + // Remove any pitch from the avatar + if (!isAgentAvatarValid() || !gAgentAvatarp->getParent()) + { + at_axis = gAgent.getFrameAgent().getAtAxis(); + at_axis.mV[VZ] = 0.f; + at_axis.normalize(); + gAgent.resetAxes(at_axis); + } + + + if (animate) + { + startCameraAnimation(); + } + else + { + mCameraAnimating = false; + gAgent.endAnimationUpdateUI(); + } +} + +//----------------------------------------------------------------------------- +// changeCameraToCustomizeAvatar() +//----------------------------------------------------------------------------- +void LLAgentCamera::changeCameraToCustomizeAvatar() +{ + if (LLViewerJoystick::getInstance()->getOverrideCamera() || !isAgentAvatarValid()) + { + return; + } + + gAgent.standUp(); // force stand up + gViewerWindow->getWindow()->resetBusyCount(); + + if (LLSelectMgr::getInstance()->getSelection()->isAttachment()) + { + LLSelectMgr::getInstance()->deselectAll(); + } + + if (gFaceEditToolset) + { + LLToolMgr::getInstance()->setCurrentToolset(gFaceEditToolset); + } + + startCameraAnimation(); + + if (mCameraMode != CAMERA_MODE_CUSTOMIZE_AVATAR) + { + updateLastCamera(); + mCameraMode = CAMERA_MODE_CUSTOMIZE_AVATAR; + gAgent.clearControlFlags(AGENT_CONTROL_MOUSELOOK); + + gFocusMgr.setKeyboardFocus( NULL ); + gFocusMgr.setMouseCapture( NULL ); + if( gMorphView ) + { + gMorphView->setVisible( true ); + } + // Remove any pitch or rotation from the avatar + LLVector3 at = gAgent.getAtAxis(); + at.mV[VZ] = 0.f; + at.normalize(); + gAgent.resetAxes(at); + + gAgent.sendAnimationRequest(ANIM_AGENT_CUSTOMIZE, ANIM_REQUEST_START); + gAgent.setCustomAnim(true); + gAgentAvatarp->startMotion(ANIM_AGENT_CUSTOMIZE); + LLMotion* turn_motion = gAgentAvatarp->findMotion(ANIM_AGENT_CUSTOMIZE); + + if (turn_motion) + { + // delay camera animation long enough to play through turn animation + setAnimationDuration(turn_motion->getDuration() + CUSTOMIZE_AVATAR_CAMERA_ANIM_SLOP); + } + } + + LLVector3 agent_at = gAgent.getAtAxis(); + agent_at.mV[VZ] = 0.f; + agent_at.normalize(); + + // default focus point for customize avatar + LLVector3 focus_target = isAgentAvatarValid() + ? gAgentAvatarp->mHeadp->getWorldPosition() + : gAgent.getPositionAgent(); + + LLVector3d camera_offset(agent_at * -1.0); + // push camera up and out from avatar + camera_offset.mdV[VZ] = 0.1f; + camera_offset *= CUSTOMIZE_AVATAR_CAMERA_DEFAULT_DIST; + LLVector3d focus_target_global = gAgent.getPosGlobalFromAgent(focus_target); + setAnimationDuration(gSavedSettings.getF32("ZoomTime")); + setCameraPosAndFocusGlobal(focus_target_global + camera_offset, focus_target_global, gAgent.getID()); +} + + +void LLAgentCamera::switchCameraPreset(ECameraPreset preset) +{ + //zoom is supposed to be reset for the front and group views + mCameraZoomFraction = 1.f; + + //focusing on avatar in that case means following him on movements + mFocusOnAvatar = true; + + mCameraPreset = preset; + + resetPanDiff(); + resetOrbitDiff(); + + gSavedSettings.setU32("CameraPresetType", mCameraPreset); +} + + +// +// Focus point management +// + +void LLAgentCamera::setAnimationDuration(F32 duration) +{ + if (mCameraAnimating) + { + // do not cut any existing camera animation short + F32 animation_left = llmax(0.f, mAnimationDuration - mAnimationTimer.getElapsedTimeF32()); + mAnimationDuration = llmax(duration, animation_left); + } + else + { + mAnimationDuration = duration; + } +} + +//----------------------------------------------------------------------------- +// startCameraAnimation() +//----------------------------------------------------------------------------- +void LLAgentCamera::startCameraAnimation() +{ + mAnimationCameraStartGlobal = getCameraPositionGlobal(); + mAnimationFocusStartGlobal = mFocusGlobal; + setAnimationDuration(gSavedSettings.getF32("ZoomTime")); + mAnimationTimer.reset(); + mCameraAnimating = true; +} + +//----------------------------------------------------------------------------- +// stopCameraAnimation() +//----------------------------------------------------------------------------- +void LLAgentCamera::stopCameraAnimation() +{ + mCameraAnimating = false; +} + +void LLAgentCamera::clearFocusObject() +{ + if (mFocusObject.notNull()) + { + startCameraAnimation(); + + setFocusObject(NULL); + mFocusObjectOffset.clearVec(); + } +} + +void LLAgentCamera::setFocusObject(LLViewerObject* object) +{ + mFocusObject = object; +} + +// Focus on a point, but try to keep camera position stable. +//----------------------------------------------------------------------------- +// setFocusGlobal() +//----------------------------------------------------------------------------- +void LLAgentCamera::setFocusGlobal(const LLPickInfo& pick) +{ + LLViewerObject* objectp = gObjectList.findObject(pick.mObjectID); + + if (objectp) + { + // focus on object plus designated offset + // which may or may not be same as pick.mPosGlobal + setFocusGlobal(objectp->getPositionGlobal() + LLVector3d(pick.mObjectOffset), pick.mObjectID); + } + else + { + // focus directly on point where user clicked + setFocusGlobal(pick.mPosGlobal, pick.mObjectID); + } +} + + +void LLAgentCamera::setFocusGlobal(const LLVector3d& focus, const LLUUID &object_id) +{ + setFocusObject(gObjectList.findObject(object_id)); + LLVector3d old_focus = mFocusTargetGlobal; + LLViewerObject *focus_obj = mFocusObject; + + // if focus has changed + if (old_focus != focus) + { + if (focus.isExactlyZero()) + { + if (isAgentAvatarValid()) + { + mFocusTargetGlobal = gAgent.getPosGlobalFromAgent(gAgentAvatarp->mHeadp->getWorldPosition()); + } + else + { + mFocusTargetGlobal = gAgent.getPositionGlobal(); + } + mCameraFocusOffsetTarget = getCameraPositionGlobal() - mFocusTargetGlobal; + mCameraFocusOffset = mCameraFocusOffsetTarget; + setLookAt(LOOKAT_TARGET_CLEAR); + } + else + { + mFocusTargetGlobal = focus; + if (!focus_obj) + { + mCameraFOVZoomFactor = 0.f; + } + + mCameraFocusOffsetTarget = gAgent.getPosGlobalFromAgent(mCameraVirtualPositionAgent) - mFocusTargetGlobal; + + startCameraAnimation(); + + if (focus_obj) + { + if (focus_obj->isAvatar()) + { + setLookAt(LOOKAT_TARGET_FOCUS, focus_obj); + } + else + { + setLookAt(LOOKAT_TARGET_FOCUS, focus_obj, (gAgent.getPosAgentFromGlobal(focus) - focus_obj->getRenderPosition()) * ~focus_obj->getRenderRotation()); + } + } + else + { + setLookAt(LOOKAT_TARGET_FOCUS, NULL, gAgent.getPosAgentFromGlobal(mFocusTargetGlobal)); + } + } + } + else // focus == mFocusTargetGlobal + { + if (focus.isExactlyZero()) + { + if (isAgentAvatarValid()) + { + mFocusTargetGlobal = gAgent.getPosGlobalFromAgent(gAgentAvatarp->mHeadp->getWorldPosition()); + } + else + { + mFocusTargetGlobal = gAgent.getPositionGlobal(); + } + } + mCameraFocusOffsetTarget = (getCameraPositionGlobal() - mFocusTargetGlobal) / (1.f + mCameraFOVZoomFactor);; + mCameraFocusOffset = mCameraFocusOffsetTarget; + } + + if (mFocusObject.notNull()) + { + // for attachments, make offset relative to avatar, not the attachment + if (mFocusObject->isAttachment()) + { + while (mFocusObject.notNull() && !mFocusObject->isAvatar()) + { + mFocusObject = (LLViewerObject*) mFocusObject->getParent(); + } + setFocusObject((LLViewerObject*)mFocusObject); + } + updateFocusOffset(); + } +} + +// Used for avatar customization +//----------------------------------------------------------------------------- +// setCameraPosAndFocusGlobal() +//----------------------------------------------------------------------------- +void LLAgentCamera::setCameraPosAndFocusGlobal(const LLVector3d& camera_pos, const LLVector3d& focus, const LLUUID &object_id) +{ + LLVector3d old_focus = mFocusTargetGlobal.isExactlyZero() ? focus : mFocusTargetGlobal; + + F64 focus_delta_squared = (old_focus - focus).magVecSquared(); + const F64 ANIM_EPSILON_SQUARED = 0.0001; + if (focus_delta_squared > ANIM_EPSILON_SQUARED) + { + startCameraAnimation(); + } + + //LLViewerCamera::getInstance()->setOrigin( gAgent.getPosAgentFromGlobal( camera_pos ) ); + setFocusObject(gObjectList.findObject(object_id)); + mFocusTargetGlobal = focus; + mCameraFocusOffsetTarget = camera_pos - focus; + mCameraFocusOffset = mCameraFocusOffsetTarget; + + if (mFocusObject) + { + if (mFocusObject->isAvatar()) + { + setLookAt(LOOKAT_TARGET_FOCUS, mFocusObject); + } + else + { + setLookAt(LOOKAT_TARGET_FOCUS, mFocusObject, (gAgent.getPosAgentFromGlobal(focus) - mFocusObject->getRenderPosition()) * ~mFocusObject->getRenderRotation()); + } + } + else + { + setLookAt(LOOKAT_TARGET_FOCUS, NULL, gAgent.getPosAgentFromGlobal(mFocusTargetGlobal)); + } + + if (mCameraAnimating) + { + const F64 ANIM_METERS_PER_SECOND = 10.0; + const F64 MIN_ANIM_SECONDS = 0.5; + const F64 MAX_ANIM_SECONDS = 10.0; + F64 anim_duration = llmax( MIN_ANIM_SECONDS, sqrt(focus_delta_squared) / ANIM_METERS_PER_SECOND ); + anim_duration = llmin( anim_duration, MAX_ANIM_SECONDS ); + setAnimationDuration( (F32)anim_duration ); + } + + updateFocusOffset(); +} + +//----------------------------------------------------------------------------- +// setSitCamera() +//----------------------------------------------------------------------------- +void LLAgentCamera::setSitCamera(const LLUUID &object_id, const LLVector3 &camera_pos, const LLVector3 &camera_focus) +{ + bool camera_enabled = !object_id.isNull(); + + if (camera_enabled) + { + LLViewerObject *reference_object = gObjectList.findObject(object_id); + if (reference_object) + { + //convert to root object relative? + mSitCameraPos = camera_pos; + mSitCameraFocus = camera_focus; + mSitCameraReferenceObject = reference_object; + mSitCameraEnabled = true; + } + } + else + { + mSitCameraPos.clearVec(); + mSitCameraFocus.clearVec(); + mSitCameraReferenceObject = NULL; + mSitCameraEnabled = false; + } +} + +//----------------------------------------------------------------------------- +// setFocusOnAvatar() +//----------------------------------------------------------------------------- +void LLAgentCamera::setFocusOnAvatar(bool focus_on_avatar, bool animate, bool reset_axes) +{ + if (focus_on_avatar != mFocusOnAvatar) + { + if (animate) + { + startCameraAnimation(); + } + else + { + stopCameraAnimation(); + } + } + + //RN: when focused on the avatar, we're not "looking" at it + // looking implies intent while focusing on avatar means + // you're just walking around with a camera on you...eesh. + if (!mFocusOnAvatar && focus_on_avatar && reset_axes) + { + setFocusGlobal(LLVector3d::zero); + mCameraFOVZoomFactor = 0.f; + if (mCameraMode == CAMERA_MODE_THIRD_PERSON) + { + LLVector3 at_axis; + if (!isAgentAvatarValid() || !gAgentAvatarp->getParent()) + { + // In case of front view rotate agent to look into direction opposite to camera + // In case of rear view rotate agent into diraction same as camera, e t c + LLVector3 vect = getCameraOffsetInitial(); + F32 rotxy = F32(atan2(vect.mV[VY], vect.mV[VX])); + + LLCoordFrame frameCamera = *((LLCoordFrame*)LLViewerCamera::getInstance()); + // front view angle rotxy is zero, rear view rotxy angle is 180, compensate + frameCamera.yaw((180 * DEG_TO_RAD) - rotxy); + at_axis = frameCamera.getAtAxis(); + at_axis.mV[VZ] = 0.f; + at_axis.normalize(); + gAgent.resetAxes(at_axis); + gAgent.yaw(0); + } + } + } + // unlocking camera from avatar + else if (mFocusOnAvatar && !focus_on_avatar) + { + // keep camera focus point consistent, even though it is now unlocked + setFocusGlobal(gAgent.getPositionGlobal() + calcThirdPersonFocusOffset(), gAgent.getID()); + mAllowChangeToFollow = false; + } + + mFocusOnAvatar = focus_on_avatar; +} + + +bool LLAgentCamera::setLookAt(ELookAtType target_type, LLViewerObject *object, LLVector3 position) +{ + if(object && object->isAttachment()) + { + LLViewerObject* parent = object; + while(parent) + { + if (parent == gAgentAvatarp) + { + // looking at an attachment on ourselves, which we don't want to do + object = gAgentAvatarp; + position.clearVec(); + } + parent = (LLViewerObject*)parent->getParent(); + } + } + if(!mLookAt || mLookAt->isDead()) + { + mLookAt = (LLHUDEffectLookAt *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_LOOKAT); + mLookAt->setSourceObject(gAgentAvatarp); + } + + return mLookAt->setLookAt(target_type, object, position); +} + +//----------------------------------------------------------------------------- +// lookAtLastChat() +//----------------------------------------------------------------------------- +void LLAgentCamera::lookAtLastChat() +{ + // Block if camera is animating or not in normal third person camera mode + if (mCameraAnimating || !cameraThirdPerson()) + { + return; + } + + LLViewerObject *chatter = gObjectList.findObject(gAgent.getLastChatter()); + if (!chatter) + { + return; + } + + LLVector3 delta_pos; + if (chatter->isAvatar()) + { + LLVOAvatar *chatter_av = (LLVOAvatar*)chatter; + if (isAgentAvatarValid() && chatter_av->mHeadp) + { + delta_pos = chatter_av->mHeadp->getWorldPosition() - gAgentAvatarp->mHeadp->getWorldPosition(); + } + else + { + delta_pos = chatter->getPositionAgent() - gAgent.getPositionAgent(); + } + delta_pos.normalize(); + + gAgent.setControlFlags(AGENT_CONTROL_STOP); + + changeCameraToThirdPerson(); + + LLVector3 new_camera_pos = gAgentAvatarp->mHeadp->getWorldPosition(); + LLVector3 left = delta_pos % LLVector3::z_axis; + left.normalize(); + LLVector3 up = left % delta_pos; + up.normalize(); + new_camera_pos -= delta_pos * 0.4f; + new_camera_pos += left * 0.3f; + new_camera_pos += up * 0.2f; + + setFocusOnAvatar(false, false); + + if (chatter_av->mHeadp) + { + setFocusGlobal(gAgent.getPosGlobalFromAgent(chatter_av->mHeadp->getWorldPosition()), gAgent.getLastChatter()); + mCameraFocusOffsetTarget = gAgent.getPosGlobalFromAgent(new_camera_pos) - gAgent.getPosGlobalFromAgent(chatter_av->mHeadp->getWorldPosition()); + } + else + { + setFocusGlobal(chatter->getPositionGlobal(), gAgent.getLastChatter()); + mCameraFocusOffsetTarget = gAgent.getPosGlobalFromAgent(new_camera_pos) - chatter->getPositionGlobal(); + } + } + else + { + delta_pos = chatter->getRenderPosition() - gAgent.getPositionAgent(); + delta_pos.normalize(); + + gAgent.setControlFlags(AGENT_CONTROL_STOP); + + changeCameraToThirdPerson(); + + LLVector3 new_camera_pos = gAgentAvatarp->mHeadp->getWorldPosition(); + LLVector3 left = delta_pos % LLVector3::z_axis; + left.normalize(); + LLVector3 up = left % delta_pos; + up.normalize(); + new_camera_pos -= delta_pos * 0.4f; + new_camera_pos += left * 0.3f; + new_camera_pos += up * 0.2f; + + setFocusOnAvatar(false, false); + + setFocusGlobal(chatter->getPositionGlobal(), gAgent.getLastChatter()); + mCameraFocusOffsetTarget = gAgent.getPosGlobalFromAgent(new_camera_pos) - chatter->getPositionGlobal(); + } +} + +bool LLAgentCamera::isfollowCamLocked() +{ + return mFollowCam.getPositionLocked(); +} + +bool LLAgentCamera::setPointAt(EPointAtType target_type, LLViewerObject *object, LLVector3 position) +{ + // disallow pointing at attachments and avatars + if (object && (object->isAttachment() || object->isAvatar())) + { + return false; + } + if (!mPointAt || mPointAt->isDead()) + { + mPointAt = (LLHUDEffectPointAt *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_POINTAT); + mPointAt->setSourceObject(gAgentAvatarp); + } + return mPointAt->setPointAt(target_type, object, position); +} + +void LLAgentCamera::rotateToInitSitRot() +{ + gAgent.rotate(~gAgent.getFrameAgent().getQuaternion()); + gAgent.rotate(mInitSitRot); +} + +void LLAgentCamera::resetCameraZoomFraction() +{ + mCameraZoomFraction = INITIAL_ZOOM_FRACTION; +} + +ELookAtType LLAgentCamera::getLookAtType() +{ + if (mLookAt) + { + return mLookAt->getLookAtType(); + } + return LOOKAT_TARGET_NONE; +} + +EPointAtType LLAgentCamera::getPointAtType() +{ + if (mPointAt) + { + return mPointAt->getPointAtType(); + } + return POINTAT_TARGET_NONE; +} + +void LLAgentCamera::clearGeneralKeys() +{ + mAtKey = 0; + mWalkKey = 0; + mLeftKey = 0; + mUpKey = 0; + mYawKey = 0.f; + mPitchKey = 0.f; +} + +void LLAgentCamera::clearOrbitKeys() +{ + mOrbitLeftKey = 0.f; + mOrbitRightKey = 0.f; + mOrbitUpKey = 0.f; + mOrbitDownKey = 0.f; + mOrbitInKey = 0.f; + mOrbitOutKey = 0.f; +} + +void LLAgentCamera::clearPanKeys() +{ + mPanRightKey = 0.f; + mPanLeftKey = 0.f; + mPanUpKey = 0.f; + mPanDownKey = 0.f; + mPanInKey = 0.f; + mPanOutKey = 0.f; +} + +// static +S32 LLAgentCamera::directionToKey(S32 direction) +{ + if (direction > 0) return 1; + if (direction < 0) return -1; + return 0; +} + + +// EOF + diff --git a/indra/newview/llagentcamera.h b/indra/newview/llagentcamera.h index d5b21a6cf4..52571f3c55 100644 --- a/indra/newview/llagentcamera.h +++ b/indra/newview/llagentcamera.h @@ -1,418 +1,418 @@ -/** - * @file llagent.h - * @brief LLAgent class header file - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLAGENTCAMERA_H -#define LL_LLAGENTCAMERA_H - -#include "llfollowcam.h" // Ventrella -#include "llhudeffectlookat.h" // EPointAtType -#include "llhudeffectpointat.h" // ELookAtType - -class LLPickInfo; -class LLVOAvatarSelf; -class LLControlVariable; - -//-------------------------------------------------------------------- -// Types -//-------------------------------------------------------------------- -enum ECameraMode -{ - CAMERA_MODE_THIRD_PERSON, - CAMERA_MODE_MOUSELOOK, - CAMERA_MODE_CUSTOMIZE_AVATAR, - CAMERA_MODE_FOLLOW -}; - -/** Camera Presets for CAMERA_MODE_THIRD_PERSON */ -enum ECameraPreset -{ - /** Default preset, what the Third Person Mode actually was */ - CAMERA_PRESET_REAR_VIEW, - - /** "Looking at the Avatar from the front" */ - CAMERA_PRESET_FRONT_VIEW, - - /** "Above and to the left, over the shoulder, pulled back a little on the zoom" */ - CAMERA_PRESET_GROUP_VIEW, - - /** Current view when a preset is saved */ - CAMERA_PRESET_CUSTOM -}; - -//------------------------------------------------------------------------ -// LLAgentCamera -//------------------------------------------------------------------------ -class LLAgentCamera -{ - LOG_CLASS(LLAgentCamera); - -public: - //-------------------------------------------------------------------- - // Constructors / Destructors - //-------------------------------------------------------------------- -public: - LLAgentCamera(); - virtual ~LLAgentCamera(); - void init(); - void cleanup(); - void setAvatarObject(LLVOAvatarSelf* avatar); - bool isInitialized() { return mInitialized; } -private: - bool mInitialized; - - - //-------------------------------------------------------------------- - // Mode - //-------------------------------------------------------------------- -public: - void changeCameraToDefault(); - void changeCameraToMouselook(bool animate = true); - void changeCameraToThirdPerson(bool animate = true); - void changeCameraToCustomizeAvatar(); // Trigger transition animation - void changeCameraToFollow(bool animate = true); // Ventrella - bool cameraThirdPerson() const { return (mCameraMode == CAMERA_MODE_THIRD_PERSON && mLastCameraMode == CAMERA_MODE_THIRD_PERSON); } - bool cameraMouselook() const { return (mCameraMode == CAMERA_MODE_MOUSELOOK && mLastCameraMode == CAMERA_MODE_MOUSELOOK); } - bool cameraCustomizeAvatar() const { return (mCameraMode == CAMERA_MODE_CUSTOMIZE_AVATAR /*&& !mCameraAnimating*/); } - bool cameraFollow() const { return (mCameraMode == CAMERA_MODE_FOLLOW && mLastCameraMode == CAMERA_MODE_FOLLOW); } - ECameraMode getCameraMode() const { return mCameraMode; } - ECameraMode getLastCameraMode() const { return mLastCameraMode; } - void updateCamera(); // Call once per frame to update camera location/orientation - void resetCamera(); // Slam camera into its default position - void updateLastCamera(); // Set last camera to current camera - -private: - ECameraMode mCameraMode; // Target mode after transition animation is done - ECameraMode mLastCameraMode; - - //-------------------------------------------------------------------- - // Preset - //-------------------------------------------------------------------- -public: - void switchCameraPreset(ECameraPreset preset); - /** Determines default camera offset depending on the current camera preset */ - LLVector3 getCameraOffsetInitial(); - /** Determines default focus offset depending on the current camera preset */ - LLVector3d getFocusOffsetInitial(); - - LLVector3 getCurrentCameraOffset(); - LLVector3d getCurrentFocusOffset(); - LLQuaternion getCurrentAvatarRotation(); - bool isJoystickCameraUsed(); - void setInitSitRot(LLQuaternion sit_rot) { mInitSitRot = sit_rot; }; - void rotateToInitSitRot(); - -private: - /** Determines maximum camera distance from target for mouselook, opposite to LAND_MIN_ZOOM */ - F32 getCameraMaxZoomDistance(); - - /** Camera preset in Third Person Mode */ - ECameraPreset mCameraPreset; - - LLQuaternion mInitSitRot; - - //-------------------------------------------------------------------- - // Position - //-------------------------------------------------------------------- -public: - LLVector3d getCameraPositionGlobal() const; - const LLVector3 &getCameraPositionAgent() const; - LLVector3d calcCameraPositionTargetGlobal(bool *hit_limit = NULL); // Calculate the camera position target - F32 getCameraMinOffGround(); // Minimum height off ground for this mode, meters - void setCameraCollidePlane(const LLVector4 &plane) { mCameraCollidePlane = plane; } - bool calcCameraMinDistance(F32 &obj_min_distance); - F32 getCurrentCameraBuildOffset() { return (F32)mCameraFocusOffset.length(); } - void clearCameraLag() { mCameraLag.clearVec(); } -private: - LLVector3 getAvatarRootPosition(); - - F32 mCurrentCameraDistance; // Current camera offset from avatar - F32 mTargetCameraDistance; // Target camera offset from avatar - F32 mCameraFOVZoomFactor; // Amount of fov zoom applied to camera when zeroing in on an object - F32 mCameraCurrentFOVZoomFactor; // Interpolated fov zoom - LLVector4 mCameraCollidePlane; // Colliding plane for camera - F32 mCameraZoomFraction; // Mousewheel driven fraction of zoom - LLVector3 mCameraPositionAgent; // Camera position in agent coordinates - LLVector3 mCameraVirtualPositionAgent; // Camera virtual position (target) before performing FOV zoom - LLVector3d mCameraSmoothingLastPositionGlobal; - LLVector3d mCameraSmoothingLastPositionAgent; - bool mCameraSmoothingStop; - LLVector3 mCameraLag; // Third person camera lag - LLVector3 mCameraUpVector; // Camera's up direction in world coordinates (determines the 'roll' of the view) - - //-------------------------------------------------------------------- - // Follow - //-------------------------------------------------------------------- -public: - bool isfollowCamLocked(); -private: - LLFollowCam mFollowCam; // Ventrella - - //-------------------------------------------------------------------- - // Sit - //-------------------------------------------------------------------- -public: - void setupSitCamera(); - bool sitCameraEnabled() { return mSitCameraEnabled; } - void setSitCamera(const LLUUID &object_id, - const LLVector3 &camera_pos = LLVector3::zero, const LLVector3 &camera_focus = LLVector3::zero); -private: - LLPointer mSitCameraReferenceObject; // Object to which camera is related when sitting - bool mSitCameraEnabled; // Use provided camera information when sitting? - LLVector3 mSitCameraPos; // Root relative camera pos when sitting - LLVector3 mSitCameraFocus; // Root relative camera target when sitting - - //-------------------------------------------------------------------- - // Animation - //-------------------------------------------------------------------- -public: - void setCameraAnimating(bool b) { mCameraAnimating = b; } - bool getCameraAnimating() { return mCameraAnimating; } - void setAnimationDuration(F32 seconds); - void startCameraAnimation(); - void stopCameraAnimation(); -private: - LLFrameTimer mAnimationTimer; // Seconds that transition animation has been active - F32 mAnimationDuration; // In seconds - bool mCameraAnimating; // Camera is transitioning from one mode to another - LLVector3d mAnimationCameraStartGlobal; // Camera start position, global coords - LLVector3d mAnimationFocusStartGlobal; // Camera focus point, global coords - - //-------------------------------------------------------------------- - // Focus - //-------------------------------------------------------------------- -public: - LLVector3d calcFocusPositionTargetGlobal(); - LLVector3 calcFocusOffset(LLViewerObject *object, LLVector3 pos_agent, S32 x, S32 y); - bool getFocusOnAvatar() const { return mFocusOnAvatar; } - LLPointer& getFocusObject() { return mFocusObject; } - F32 getFocusObjectDist() const { return mFocusObjectDist; } - void updateFocusOffset(); - void validateFocusObject(); - void setFocusGlobal(const LLPickInfo& pick); - void setFocusGlobal(const LLVector3d &focus, const LLUUID &object_id = LLUUID::null); - void setFocusOnAvatar(bool focus, bool animate, bool reset_axes = true); - void setCameraPosAndFocusGlobal(const LLVector3d& pos, const LLVector3d& focus, const LLUUID &object_id); - void clearFocusObject(); - void setFocusObject(LLViewerObject* object); - void setAllowChangeToFollow(bool focus) { mAllowChangeToFollow = focus; } - void setObjectTracking(bool track) { mTrackFocusObject = track; } - const LLVector3d &getFocusGlobal() const { return mFocusGlobal; } - const LLVector3d &getFocusTargetGlobal() const { return mFocusTargetGlobal; } -private: - LLVector3d mCameraFocusOffset; // Offset from focus point in build mode - LLVector3d mCameraFocusOffsetTarget; // Target towards which we are lerping the camera's focus offset - bool mFocusOnAvatar; - bool mAllowChangeToFollow; - LLVector3d mFocusGlobal; - LLVector3d mFocusTargetGlobal; - LLPointer mFocusObject; - F32 mFocusObjectDist; - LLVector3 mFocusObjectOffset; - bool mTrackFocusObject; - - //-------------------------------------------------------------------- - // Lookat / Pointat - //-------------------------------------------------------------------- -public: - void updateLookAt(const S32 mouse_x, const S32 mouse_y); - bool setLookAt(ELookAtType target_type, LLViewerObject *object = NULL, LLVector3 position = LLVector3::zero); - ELookAtType getLookAtType(); - void lookAtLastChat(); - void slamLookAt(const LLVector3 &look_at); // Set the physics data - bool setPointAt(EPointAtType target_type, LLViewerObject *object = NULL, LLVector3 position = LLVector3::zero); - EPointAtType getPointAtType(); -public: - LLPointer mLookAt; - LLPointer mPointAt; - - //-------------------------------------------------------------------- - // Third person - //-------------------------------------------------------------------- -public: - LLVector3d calcThirdPersonFocusOffset(); - void setThirdPersonHeadOffset(LLVector3 offset) { mThirdPersonHeadOffset = offset; } -private: - LLVector3 mThirdPersonHeadOffset; // Head offset for third person camera position - - //-------------------------------------------------------------------- - // Orbit - //-------------------------------------------------------------------- -public: - void cameraOrbitAround(const F32 radians); // Rotate camera CCW radians about build focus point - void cameraOrbitOver(const F32 radians); // Rotate camera forward radians over build focus point - void cameraOrbitIn(const F32 meters); // Move camera in toward build focus point - void resetCameraOrbit(); - void resetOrbitDiff(); - //-------------------------------------------------------------------- - // Zoom - //-------------------------------------------------------------------- -public: - void handleScrollWheel(S32 clicks); // Mousewheel driven zoom - void cameraZoomIn(const F32 factor); // Zoom in by fraction of current distance - F32 getCameraZoomFraction(bool get_third_person = false); // Get camera zoom as fraction of minimum and maximum zoom - void setCameraZoomFraction(F32 fraction); // Set camera zoom as fraction of minimum and maximum zoom - F32 calcCameraFOVZoomFactor(); - F32 getAgentHUDTargetZoom(); - - void resetCameraZoomFraction(); - F32 getCurrentCameraZoomFraction() { return mCameraZoomFraction; } - - //-------------------------------------------------------------------- - // Pan - //-------------------------------------------------------------------- -public: - void cameraPanIn(const F32 meters); - void cameraPanLeft(const F32 meters); - void cameraPanUp(const F32 meters); - void resetCameraPan(); - void resetPanDiff(); - //-------------------------------------------------------------------- - // View - //-------------------------------------------------------------------- -public: - // Called whenever the agent moves. Puts camera back in default position, deselects items, etc. - void resetView(bool reset_camera = true, bool change_camera = false); - // Called on camera movement. Unlocks camera from the default position behind the avatar. - void unlockView(); -public: - F32 mDrawDistance; - - //-------------------------------------------------------------------- - // Mouselook - //-------------------------------------------------------------------- -public: - bool getForceMouselook() const { return mForceMouselook; } - void setForceMouselook(bool mouselook) { mForceMouselook = mouselook; } -private: - bool mForceMouselook; - - //-------------------------------------------------------------------- - // HUD - //-------------------------------------------------------------------- -public: - F32 mHUDTargetZoom; // Target zoom level for HUD objects (used when editing) - F32 mHUDCurZoom; // Current animated zoom level for HUD objects - - -/******************************************************************************** - ** ** - ** KEYS - **/ - -public: - S32 getAtKey() const { return mAtKey; } - S32 getWalkKey() const { return mWalkKey; } - S32 getLeftKey() const { return mLeftKey; } - S32 getUpKey() const { return mUpKey; } - F32 getYawKey() const { return mYawKey; } - F32 getPitchKey() const { return mPitchKey; } - - void setAtKey(S32 mag) { mAtKey = mag; } - void setWalkKey(S32 mag) { mWalkKey = mag; } - void setLeftKey(S32 mag) { mLeftKey = mag; } - void setUpKey(S32 mag) { mUpKey = mag; } - void setYawKey(F32 mag) { mYawKey = mag; } - void setPitchKey(F32 mag) { mPitchKey = mag; } - - void clearGeneralKeys(); - static S32 directionToKey(S32 direction); // Changes direction to -1/0/1 - -private: - S32 mAtKey; // Either 1, 0, or -1. Indicates that movement key is pressed - S32 mWalkKey; // Like AtKey, but causes less forward thrust - S32 mLeftKey; - S32 mUpKey; - F32 mYawKey; - F32 mPitchKey; - - //-------------------------------------------------------------------- - // Orbit - //-------------------------------------------------------------------- -public: - F32 getOrbitLeftKey() const { return mOrbitLeftKey; } - F32 getOrbitRightKey() const { return mOrbitRightKey; } - F32 getOrbitUpKey() const { return mOrbitUpKey; } - F32 getOrbitDownKey() const { return mOrbitDownKey; } - F32 getOrbitInKey() const { return mOrbitInKey; } - F32 getOrbitOutKey() const { return mOrbitOutKey; } - - void setOrbitLeftKey(F32 mag) { mOrbitLeftKey = mag; } - void setOrbitRightKey(F32 mag) { mOrbitRightKey = mag; } - void setOrbitUpKey(F32 mag) { mOrbitUpKey = mag; } - void setOrbitDownKey(F32 mag) { mOrbitDownKey = mag; } - void setOrbitInKey(F32 mag) { mOrbitInKey = mag; } - void setOrbitOutKey(F32 mag) { mOrbitOutKey = mag; } - - void clearOrbitKeys(); -private: - F32 mOrbitLeftKey; - F32 mOrbitRightKey; - F32 mOrbitUpKey; - F32 mOrbitDownKey; - F32 mOrbitInKey; - F32 mOrbitOutKey; - - F32 mOrbitAroundRadians; - F32 mOrbitOverAngle; - - //-------------------------------------------------------------------- - // Pan - //-------------------------------------------------------------------- -public: - F32 getPanLeftKey() const { return mPanLeftKey; } - F32 getPanRightKey() const { return mPanRightKey; } - F32 getPanUpKey() const { return mPanUpKey; } - F32 getPanDownKey() const { return mPanDownKey; } - F32 getPanInKey() const { return mPanInKey; } - F32 getPanOutKey() const { return mPanOutKey; } - - void setPanLeftKey(F32 mag) { mPanLeftKey = mag; } - void setPanRightKey(F32 mag) { mPanRightKey = mag; } - void setPanUpKey(F32 mag) { mPanUpKey = mag; } - void setPanDownKey(F32 mag) { mPanDownKey = mag; } - void setPanInKey(F32 mag) { mPanInKey = mag; } - void setPanOutKey(F32 mag) { mPanOutKey = mag; } - - void clearPanKeys(); -private: - F32 mPanUpKey; - F32 mPanDownKey; - F32 mPanLeftKey; - F32 mPanRightKey; - F32 mPanInKey; - F32 mPanOutKey; - - LLVector3d mPanFocusDiff; - -/** Keys - ** ** - *******************************************************************************/ - -}; - -extern LLAgentCamera gAgentCamera; - -#endif +/** + * @file llagent.h + * @brief LLAgent class header file + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLAGENTCAMERA_H +#define LL_LLAGENTCAMERA_H + +#include "llfollowcam.h" // Ventrella +#include "llhudeffectlookat.h" // EPointAtType +#include "llhudeffectpointat.h" // ELookAtType + +class LLPickInfo; +class LLVOAvatarSelf; +class LLControlVariable; + +//-------------------------------------------------------------------- +// Types +//-------------------------------------------------------------------- +enum ECameraMode +{ + CAMERA_MODE_THIRD_PERSON, + CAMERA_MODE_MOUSELOOK, + CAMERA_MODE_CUSTOMIZE_AVATAR, + CAMERA_MODE_FOLLOW +}; + +/** Camera Presets for CAMERA_MODE_THIRD_PERSON */ +enum ECameraPreset +{ + /** Default preset, what the Third Person Mode actually was */ + CAMERA_PRESET_REAR_VIEW, + + /** "Looking at the Avatar from the front" */ + CAMERA_PRESET_FRONT_VIEW, + + /** "Above and to the left, over the shoulder, pulled back a little on the zoom" */ + CAMERA_PRESET_GROUP_VIEW, + + /** Current view when a preset is saved */ + CAMERA_PRESET_CUSTOM +}; + +//------------------------------------------------------------------------ +// LLAgentCamera +//------------------------------------------------------------------------ +class LLAgentCamera +{ + LOG_CLASS(LLAgentCamera); + +public: + //-------------------------------------------------------------------- + // Constructors / Destructors + //-------------------------------------------------------------------- +public: + LLAgentCamera(); + virtual ~LLAgentCamera(); + void init(); + void cleanup(); + void setAvatarObject(LLVOAvatarSelf* avatar); + bool isInitialized() { return mInitialized; } +private: + bool mInitialized; + + + //-------------------------------------------------------------------- + // Mode + //-------------------------------------------------------------------- +public: + void changeCameraToDefault(); + void changeCameraToMouselook(bool animate = true); + void changeCameraToThirdPerson(bool animate = true); + void changeCameraToCustomizeAvatar(); // Trigger transition animation + void changeCameraToFollow(bool animate = true); // Ventrella + bool cameraThirdPerson() const { return (mCameraMode == CAMERA_MODE_THIRD_PERSON && mLastCameraMode == CAMERA_MODE_THIRD_PERSON); } + bool cameraMouselook() const { return (mCameraMode == CAMERA_MODE_MOUSELOOK && mLastCameraMode == CAMERA_MODE_MOUSELOOK); } + bool cameraCustomizeAvatar() const { return (mCameraMode == CAMERA_MODE_CUSTOMIZE_AVATAR /*&& !mCameraAnimating*/); } + bool cameraFollow() const { return (mCameraMode == CAMERA_MODE_FOLLOW && mLastCameraMode == CAMERA_MODE_FOLLOW); } + ECameraMode getCameraMode() const { return mCameraMode; } + ECameraMode getLastCameraMode() const { return mLastCameraMode; } + void updateCamera(); // Call once per frame to update camera location/orientation + void resetCamera(); // Slam camera into its default position + void updateLastCamera(); // Set last camera to current camera + +private: + ECameraMode mCameraMode; // Target mode after transition animation is done + ECameraMode mLastCameraMode; + + //-------------------------------------------------------------------- + // Preset + //-------------------------------------------------------------------- +public: + void switchCameraPreset(ECameraPreset preset); + /** Determines default camera offset depending on the current camera preset */ + LLVector3 getCameraOffsetInitial(); + /** Determines default focus offset depending on the current camera preset */ + LLVector3d getFocusOffsetInitial(); + + LLVector3 getCurrentCameraOffset(); + LLVector3d getCurrentFocusOffset(); + LLQuaternion getCurrentAvatarRotation(); + bool isJoystickCameraUsed(); + void setInitSitRot(LLQuaternion sit_rot) { mInitSitRot = sit_rot; }; + void rotateToInitSitRot(); + +private: + /** Determines maximum camera distance from target for mouselook, opposite to LAND_MIN_ZOOM */ + F32 getCameraMaxZoomDistance(); + + /** Camera preset in Third Person Mode */ + ECameraPreset mCameraPreset; + + LLQuaternion mInitSitRot; + + //-------------------------------------------------------------------- + // Position + //-------------------------------------------------------------------- +public: + LLVector3d getCameraPositionGlobal() const; + const LLVector3 &getCameraPositionAgent() const; + LLVector3d calcCameraPositionTargetGlobal(bool *hit_limit = NULL); // Calculate the camera position target + F32 getCameraMinOffGround(); // Minimum height off ground for this mode, meters + void setCameraCollidePlane(const LLVector4 &plane) { mCameraCollidePlane = plane; } + bool calcCameraMinDistance(F32 &obj_min_distance); + F32 getCurrentCameraBuildOffset() { return (F32)mCameraFocusOffset.length(); } + void clearCameraLag() { mCameraLag.clearVec(); } +private: + LLVector3 getAvatarRootPosition(); + + F32 mCurrentCameraDistance; // Current camera offset from avatar + F32 mTargetCameraDistance; // Target camera offset from avatar + F32 mCameraFOVZoomFactor; // Amount of fov zoom applied to camera when zeroing in on an object + F32 mCameraCurrentFOVZoomFactor; // Interpolated fov zoom + LLVector4 mCameraCollidePlane; // Colliding plane for camera + F32 mCameraZoomFraction; // Mousewheel driven fraction of zoom + LLVector3 mCameraPositionAgent; // Camera position in agent coordinates + LLVector3 mCameraVirtualPositionAgent; // Camera virtual position (target) before performing FOV zoom + LLVector3d mCameraSmoothingLastPositionGlobal; + LLVector3d mCameraSmoothingLastPositionAgent; + bool mCameraSmoothingStop; + LLVector3 mCameraLag; // Third person camera lag + LLVector3 mCameraUpVector; // Camera's up direction in world coordinates (determines the 'roll' of the view) + + //-------------------------------------------------------------------- + // Follow + //-------------------------------------------------------------------- +public: + bool isfollowCamLocked(); +private: + LLFollowCam mFollowCam; // Ventrella + + //-------------------------------------------------------------------- + // Sit + //-------------------------------------------------------------------- +public: + void setupSitCamera(); + bool sitCameraEnabled() { return mSitCameraEnabled; } + void setSitCamera(const LLUUID &object_id, + const LLVector3 &camera_pos = LLVector3::zero, const LLVector3 &camera_focus = LLVector3::zero); +private: + LLPointer mSitCameraReferenceObject; // Object to which camera is related when sitting + bool mSitCameraEnabled; // Use provided camera information when sitting? + LLVector3 mSitCameraPos; // Root relative camera pos when sitting + LLVector3 mSitCameraFocus; // Root relative camera target when sitting + + //-------------------------------------------------------------------- + // Animation + //-------------------------------------------------------------------- +public: + void setCameraAnimating(bool b) { mCameraAnimating = b; } + bool getCameraAnimating() { return mCameraAnimating; } + void setAnimationDuration(F32 seconds); + void startCameraAnimation(); + void stopCameraAnimation(); +private: + LLFrameTimer mAnimationTimer; // Seconds that transition animation has been active + F32 mAnimationDuration; // In seconds + bool mCameraAnimating; // Camera is transitioning from one mode to another + LLVector3d mAnimationCameraStartGlobal; // Camera start position, global coords + LLVector3d mAnimationFocusStartGlobal; // Camera focus point, global coords + + //-------------------------------------------------------------------- + // Focus + //-------------------------------------------------------------------- +public: + LLVector3d calcFocusPositionTargetGlobal(); + LLVector3 calcFocusOffset(LLViewerObject *object, LLVector3 pos_agent, S32 x, S32 y); + bool getFocusOnAvatar() const { return mFocusOnAvatar; } + LLPointer& getFocusObject() { return mFocusObject; } + F32 getFocusObjectDist() const { return mFocusObjectDist; } + void updateFocusOffset(); + void validateFocusObject(); + void setFocusGlobal(const LLPickInfo& pick); + void setFocusGlobal(const LLVector3d &focus, const LLUUID &object_id = LLUUID::null); + void setFocusOnAvatar(bool focus, bool animate, bool reset_axes = true); + void setCameraPosAndFocusGlobal(const LLVector3d& pos, const LLVector3d& focus, const LLUUID &object_id); + void clearFocusObject(); + void setFocusObject(LLViewerObject* object); + void setAllowChangeToFollow(bool focus) { mAllowChangeToFollow = focus; } + void setObjectTracking(bool track) { mTrackFocusObject = track; } + const LLVector3d &getFocusGlobal() const { return mFocusGlobal; } + const LLVector3d &getFocusTargetGlobal() const { return mFocusTargetGlobal; } +private: + LLVector3d mCameraFocusOffset; // Offset from focus point in build mode + LLVector3d mCameraFocusOffsetTarget; // Target towards which we are lerping the camera's focus offset + bool mFocusOnAvatar; + bool mAllowChangeToFollow; + LLVector3d mFocusGlobal; + LLVector3d mFocusTargetGlobal; + LLPointer mFocusObject; + F32 mFocusObjectDist; + LLVector3 mFocusObjectOffset; + bool mTrackFocusObject; + + //-------------------------------------------------------------------- + // Lookat / Pointat + //-------------------------------------------------------------------- +public: + void updateLookAt(const S32 mouse_x, const S32 mouse_y); + bool setLookAt(ELookAtType target_type, LLViewerObject *object = NULL, LLVector3 position = LLVector3::zero); + ELookAtType getLookAtType(); + void lookAtLastChat(); + void slamLookAt(const LLVector3 &look_at); // Set the physics data + bool setPointAt(EPointAtType target_type, LLViewerObject *object = NULL, LLVector3 position = LLVector3::zero); + EPointAtType getPointAtType(); +public: + LLPointer mLookAt; + LLPointer mPointAt; + + //-------------------------------------------------------------------- + // Third person + //-------------------------------------------------------------------- +public: + LLVector3d calcThirdPersonFocusOffset(); + void setThirdPersonHeadOffset(LLVector3 offset) { mThirdPersonHeadOffset = offset; } +private: + LLVector3 mThirdPersonHeadOffset; // Head offset for third person camera position + + //-------------------------------------------------------------------- + // Orbit + //-------------------------------------------------------------------- +public: + void cameraOrbitAround(const F32 radians); // Rotate camera CCW radians about build focus point + void cameraOrbitOver(const F32 radians); // Rotate camera forward radians over build focus point + void cameraOrbitIn(const F32 meters); // Move camera in toward build focus point + void resetCameraOrbit(); + void resetOrbitDiff(); + //-------------------------------------------------------------------- + // Zoom + //-------------------------------------------------------------------- +public: + void handleScrollWheel(S32 clicks); // Mousewheel driven zoom + void cameraZoomIn(const F32 factor); // Zoom in by fraction of current distance + F32 getCameraZoomFraction(bool get_third_person = false); // Get camera zoom as fraction of minimum and maximum zoom + void setCameraZoomFraction(F32 fraction); // Set camera zoom as fraction of minimum and maximum zoom + F32 calcCameraFOVZoomFactor(); + F32 getAgentHUDTargetZoom(); + + void resetCameraZoomFraction(); + F32 getCurrentCameraZoomFraction() { return mCameraZoomFraction; } + + //-------------------------------------------------------------------- + // Pan + //-------------------------------------------------------------------- +public: + void cameraPanIn(const F32 meters); + void cameraPanLeft(const F32 meters); + void cameraPanUp(const F32 meters); + void resetCameraPan(); + void resetPanDiff(); + //-------------------------------------------------------------------- + // View + //-------------------------------------------------------------------- +public: + // Called whenever the agent moves. Puts camera back in default position, deselects items, etc. + void resetView(bool reset_camera = true, bool change_camera = false); + // Called on camera movement. Unlocks camera from the default position behind the avatar. + void unlockView(); +public: + F32 mDrawDistance; + + //-------------------------------------------------------------------- + // Mouselook + //-------------------------------------------------------------------- +public: + bool getForceMouselook() const { return mForceMouselook; } + void setForceMouselook(bool mouselook) { mForceMouselook = mouselook; } +private: + bool mForceMouselook; + + //-------------------------------------------------------------------- + // HUD + //-------------------------------------------------------------------- +public: + F32 mHUDTargetZoom; // Target zoom level for HUD objects (used when editing) + F32 mHUDCurZoom; // Current animated zoom level for HUD objects + + +/******************************************************************************** + ** ** + ** KEYS + **/ + +public: + S32 getAtKey() const { return mAtKey; } + S32 getWalkKey() const { return mWalkKey; } + S32 getLeftKey() const { return mLeftKey; } + S32 getUpKey() const { return mUpKey; } + F32 getYawKey() const { return mYawKey; } + F32 getPitchKey() const { return mPitchKey; } + + void setAtKey(S32 mag) { mAtKey = mag; } + void setWalkKey(S32 mag) { mWalkKey = mag; } + void setLeftKey(S32 mag) { mLeftKey = mag; } + void setUpKey(S32 mag) { mUpKey = mag; } + void setYawKey(F32 mag) { mYawKey = mag; } + void setPitchKey(F32 mag) { mPitchKey = mag; } + + void clearGeneralKeys(); + static S32 directionToKey(S32 direction); // Changes direction to -1/0/1 + +private: + S32 mAtKey; // Either 1, 0, or -1. Indicates that movement key is pressed + S32 mWalkKey; // Like AtKey, but causes less forward thrust + S32 mLeftKey; + S32 mUpKey; + F32 mYawKey; + F32 mPitchKey; + + //-------------------------------------------------------------------- + // Orbit + //-------------------------------------------------------------------- +public: + F32 getOrbitLeftKey() const { return mOrbitLeftKey; } + F32 getOrbitRightKey() const { return mOrbitRightKey; } + F32 getOrbitUpKey() const { return mOrbitUpKey; } + F32 getOrbitDownKey() const { return mOrbitDownKey; } + F32 getOrbitInKey() const { return mOrbitInKey; } + F32 getOrbitOutKey() const { return mOrbitOutKey; } + + void setOrbitLeftKey(F32 mag) { mOrbitLeftKey = mag; } + void setOrbitRightKey(F32 mag) { mOrbitRightKey = mag; } + void setOrbitUpKey(F32 mag) { mOrbitUpKey = mag; } + void setOrbitDownKey(F32 mag) { mOrbitDownKey = mag; } + void setOrbitInKey(F32 mag) { mOrbitInKey = mag; } + void setOrbitOutKey(F32 mag) { mOrbitOutKey = mag; } + + void clearOrbitKeys(); +private: + F32 mOrbitLeftKey; + F32 mOrbitRightKey; + F32 mOrbitUpKey; + F32 mOrbitDownKey; + F32 mOrbitInKey; + F32 mOrbitOutKey; + + F32 mOrbitAroundRadians; + F32 mOrbitOverAngle; + + //-------------------------------------------------------------------- + // Pan + //-------------------------------------------------------------------- +public: + F32 getPanLeftKey() const { return mPanLeftKey; } + F32 getPanRightKey() const { return mPanRightKey; } + F32 getPanUpKey() const { return mPanUpKey; } + F32 getPanDownKey() const { return mPanDownKey; } + F32 getPanInKey() const { return mPanInKey; } + F32 getPanOutKey() const { return mPanOutKey; } + + void setPanLeftKey(F32 mag) { mPanLeftKey = mag; } + void setPanRightKey(F32 mag) { mPanRightKey = mag; } + void setPanUpKey(F32 mag) { mPanUpKey = mag; } + void setPanDownKey(F32 mag) { mPanDownKey = mag; } + void setPanInKey(F32 mag) { mPanInKey = mag; } + void setPanOutKey(F32 mag) { mPanOutKey = mag; } + + void clearPanKeys(); +private: + F32 mPanUpKey; + F32 mPanDownKey; + F32 mPanLeftKey; + F32 mPanRightKey; + F32 mPanInKey; + F32 mPanOutKey; + + LLVector3d mPanFocusDiff; + +/** Keys + ** ** + *******************************************************************************/ + +}; + +extern LLAgentCamera gAgentCamera; + +#endif diff --git a/indra/newview/llagentlanguage.cpp b/indra/newview/llagentlanguage.cpp index cff33f51c1..3a29b85783 100644 --- a/indra/newview/llagentlanguage.cpp +++ b/indra/newview/llagentlanguage.cpp @@ -1,71 +1,71 @@ -/** - * @file llagentlanguage.cpp - * @brief Transmit language information to server - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llagentlanguage.h" -// viewer includes -#include "llagent.h" -#include "llviewercontrol.h" -#include "llviewerregion.h" -// library includes -#include "llui.h" // getLanguage() -#include "httpcommon.h" - -// static -void LLAgentLanguage::init() -{ - gSavedSettings.getControl("Language")->getSignal()->connect(boost::bind(&onChange)); - gSavedSettings.getControl("InstallLanguage")->getSignal()->connect(boost::bind(&onChange)); - gSavedSettings.getControl("SystemLanguage")->getSignal()->connect(boost::bind(&onChange)); - gSavedSettings.getControl("LanguageIsPublic")->getSignal()->connect(boost::bind(&onChange)); -} - -// static -void LLAgentLanguage::onChange() -{ - // Clear inventory cache so that default names of inventory items - // appear retranslated (EXT-8308). - gSavedSettings.setBOOL("PurgeCacheOnNextStartup", true); -} - -// send language settings to the sim -// static -bool LLAgentLanguage::update() -{ - LLSD body; - - std::string language = LLUI::getLanguage(); - - body["language"] = language; - body["language_is_public"] = gSavedSettings.getBOOL("LanguageIsPublic"); - - if (!gAgent.requestPostCapability("UpdateAgentLanguage", body)) - { - LL_WARNS("Language") << "Language capability unavailable." << LL_ENDL; - } - - return true; -} +/** + * @file llagentlanguage.cpp + * @brief Transmit language information to server + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llagentlanguage.h" +// viewer includes +#include "llagent.h" +#include "llviewercontrol.h" +#include "llviewerregion.h" +// library includes +#include "llui.h" // getLanguage() +#include "httpcommon.h" + +// static +void LLAgentLanguage::init() +{ + gSavedSettings.getControl("Language")->getSignal()->connect(boost::bind(&onChange)); + gSavedSettings.getControl("InstallLanguage")->getSignal()->connect(boost::bind(&onChange)); + gSavedSettings.getControl("SystemLanguage")->getSignal()->connect(boost::bind(&onChange)); + gSavedSettings.getControl("LanguageIsPublic")->getSignal()->connect(boost::bind(&onChange)); +} + +// static +void LLAgentLanguage::onChange() +{ + // Clear inventory cache so that default names of inventory items + // appear retranslated (EXT-8308). + gSavedSettings.setBOOL("PurgeCacheOnNextStartup", true); +} + +// send language settings to the sim +// static +bool LLAgentLanguage::update() +{ + LLSD body; + + std::string language = LLUI::getLanguage(); + + body["language"] = language; + body["language_is_public"] = gSavedSettings.getBOOL("LanguageIsPublic"); + + if (!gAgent.requestPostCapability("UpdateAgentLanguage", body)) + { + LL_WARNS("Language") << "Language capability unavailable." << LL_ENDL; + } + + return true; +} diff --git a/indra/newview/llagentlistener.cpp b/indra/newview/llagentlistener.cpp index 04b10358d6..005a518910 100644 --- a/indra/newview/llagentlistener.cpp +++ b/indra/newview/llagentlistener.cpp @@ -1,521 +1,521 @@ -/** - * @file llagentlistener.cpp - * @author Brad Kittenbrink - * @date 2009-07-10 - * @brief Implementation for llagentlistener. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llagentlistener.h" - -#include "llagent.h" -#include "llvoavatar.h" -#include "llcommandhandler.h" -#include "llslurl.h" -#include "llurldispatcher.h" -#include "llviewernetwork.h" -#include "llviewerobject.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llsdutil.h" -#include "llsdutil_math.h" -#include "lltoolgrab.h" -#include "llhudeffectlookat.h" -#include "llagentcamera.h" - -LLAgentListener::LLAgentListener(LLAgent &agent) - : LLEventAPI("LLAgent", - "LLAgent listener to (e.g.) teleport, sit, stand, etc."), - mAgent(agent) -{ - add("requestTeleport", - "Teleport: [\"regionname\"], [\"x\"], [\"y\"], [\"z\"]\n" - "If [\"skip_confirmation\"] is true, use LLURLDispatcher rather than LLCommandDispatcher.", - &LLAgentListener::requestTeleport); - add("requestSit", - "[\"obj_uuid\"]: id of object to sit on, use this or [\"position\"] to indicate the sit target" - "[\"position\"]: region position {x, y, z} where to find closest object to sit on", - &LLAgentListener::requestSit); - add("requestStand", - "Ask to stand up", - &LLAgentListener::requestStand); - add("requestTouch", - "[\"obj_uuid\"]: id of object to touch, use this or [\"position\"] to indicate the object to touch" - "[\"position\"]: region position {x, y, z} where to find closest object to touch" - "[\"face\"]: optional object face number to touch[Default: 0]", - &LLAgentListener::requestTouch); - add("resetAxes", - "Set the agent to a fixed orientation (optionally specify [\"lookat\"] = array of [x, y, z])", - &LLAgentListener::resetAxes); - add("getAxes", - "Obsolete - use getPosition instead\n" - "Send information about the agent's orientation on [\"reply\"]:\n" - "[\"euler\"]: map of {roll, pitch, yaw}\n" - "[\"quat\"]: array of [x, y, z, w] quaternion values", - &LLAgentListener::getAxes, - LLSDMap("reply", LLSD())); - add("getPosition", - "Send information about the agent's position and orientation on [\"reply\"]:\n" - "[\"region\"]: array of region {x, y, z} position\n" - "[\"global\"]: array of global {x, y, z} position\n" - "[\"euler\"]: map of {roll, pitch, yaw}\n" - "[\"quat\"]: array of [x, y, z, w] quaternion values", - &LLAgentListener::getPosition, - LLSDMap("reply", LLSD())); - add("startAutoPilot", - "Start the autopilot system using the following parameters:\n" - "[\"target_global\"]: array of target global {x, y, z} position\n" - "[\"stop_distance\"]: target maxiumum distance from target [default: autopilot guess]\n" - "[\"target_rotation\"]: array of [x, y, z, w] quaternion values [default: no target]\n" - "[\"rotation_threshold\"]: target maximum angle from target facing rotation [default: 0.03 radians]\n" - "[\"behavior_name\"]: name of the autopilot behavior [default: \"\"]" - "[\"allow_flying\"]: allow flying during autopilot [default: True]", - //"[\"callback_pump\"]: pump to send success/failure and callback data to [default: none]\n" - //"[\"callback_data\"]: data to send back during a callback [default: none]", - &LLAgentListener::startAutoPilot); - add("getAutoPilot", - "Send information about current state of the autopilot system to [\"reply\"]:\n" - "[\"enabled\"]: boolean indicating whether or not autopilot is enabled\n" - "[\"target_global\"]: array of target global {x, y, z} position\n" - "[\"leader_id\"]: uuid of target autopilot is following\n" - "[\"stop_distance\"]: target maximum distance from target\n" - "[\"target_distance\"]: last known distance from target\n" - "[\"use_rotation\"]: boolean indicating if autopilot has a target facing rotation\n" - "[\"target_facing\"]: array of {x, y} target direction to face\n" - "[\"rotation_threshold\"]: target maximum angle from target facing rotation\n" - "[\"behavior_name\"]: name of the autopilot behavior", - &LLAgentListener::getAutoPilot, - LLSDMap("reply", LLSD())); - add("startFollowPilot", - "[\"leader_id\"]: uuid of target to follow using the autopilot system (optional with avatar_name)\n" - "[\"avatar_name\"]: avatar name to follow using the autopilot system (optional with leader_id)\n" - "[\"allow_flying\"]: allow flying during autopilot [default: True]\n" - "[\"stop_distance\"]: target maxiumum distance from target [default: autopilot guess]", - &LLAgentListener::startFollowPilot); - add("setAutoPilotTarget", - "Update target for currently running autopilot:\n" - "[\"target_global\"]: array of target global {x, y, z} position", - &LLAgentListener::setAutoPilotTarget); - add("stopAutoPilot", - "Stop the autopilot system:\n" - "[\"user_cancel\"] indicates whether or not to act as though user canceled autopilot [default: false]", - &LLAgentListener::stopAutoPilot); - add("lookAt", - "[\"type\"]: number to indicate the lookAt type, 0 to clear\n" - "[\"obj_uuid\"]: id of object to look at, use this or [\"position\"] to indicate the target\n" - "[\"position\"]: region position {x, y, z} where to find closest object or avatar to look at", - &LLAgentListener::lookAt); - add("getGroups", - "Send information about the agent's groups on [\"reply\"]:\n" - "[\"groups\"]: array of group information\n" - "[\"id\"]: group id\n" - "[\"name\"]: group name\n" - "[\"insignia\"]: group insignia texture id\n" - "[\"notices\"]: boolean indicating if this user accepts notices from this group\n" - "[\"display\"]: boolean indicating if this group is listed in the user's profile\n" - "[\"contrib\"]: user's land contribution to this group\n", - &LLAgentListener::getGroups, - LLSDMap("reply", LLSD())); -} - -void LLAgentListener::requestTeleport(LLSD const & event_data) const -{ - if(event_data["skip_confirmation"].asBoolean()) - { - LLSD params(LLSD::emptyArray()); - params.append(event_data["regionname"]); - params.append(event_data["x"]); - params.append(event_data["y"]); - params.append(event_data["z"]); - LLCommandDispatcher::dispatch("teleport", params, LLSD(), LLGridManager::getInstance()->getGrid(), NULL, LLCommandHandler::NAV_TYPE_CLICKED, true); - // *TODO - lookup other LLCommandHandlers for "agent", "classified", "event", "group", "floater", "parcel", "login", login_refresh", "balance", "chat" - // should we just compose LLCommandHandler and LLDispatchListener? - } - else - { - std::string url = LLSLURL(event_data["regionname"], - LLVector3(event_data["x"].asReal(), - event_data["y"].asReal(), - event_data["z"].asReal())).getSLURLString(); - LLURLDispatcher::dispatch(url, LLCommandHandler::NAV_TYPE_CLICKED, NULL, false); - } -} - -void LLAgentListener::requestSit(LLSD const & event_data) const -{ - //mAgent.getAvatarObject()->sitOnObject(); - // shamelessly ripped from llviewermenu.cpp:handle_sit_or_stand() - // *TODO - find a permanent place to share this code properly. - - LLViewerObject *object = NULL; - if (event_data.has("obj_uuid")) - { - object = gObjectList.findObject(event_data["obj_uuid"]); - } - else if (event_data.has("position")) - { - LLVector3 target_position = ll_vector3_from_sd(event_data["position"]); - object = findObjectClosestTo(target_position); - } - - if (object && object->getPCode() == LL_PCODE_VOLUME) - { - gMessageSystem->newMessageFast(_PREHASH_AgentRequestSit); - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, mAgent.getID()); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, mAgent.getSessionID()); - gMessageSystem->nextBlockFast(_PREHASH_TargetObject); - gMessageSystem->addUUIDFast(_PREHASH_TargetID, object->mID); - gMessageSystem->addVector3Fast(_PREHASH_Offset, LLVector3(0,0,0)); - - object->getRegion()->sendReliableMessage(); - } - else - { - LL_WARNS() << "LLAgent requestSit could not find the sit target: " - << event_data << LL_ENDL; - } -} - -void LLAgentListener::requestStand(LLSD const & event_data) const -{ - mAgent.setControlFlags(AGENT_CONTROL_STAND_UP); -} - - -LLViewerObject * LLAgentListener::findObjectClosestTo( const LLVector3 & position ) const -{ - LLViewerObject *object = NULL; - - // Find the object closest to that position - F32 min_distance = 10000.0f; // Start big - S32 num_objects = gObjectList.getNumObjects(); - S32 cur_index = 0; - while (cur_index < num_objects) - { - LLViewerObject * cur_object = gObjectList.getObject(cur_index++); - if (cur_object) - { // Calculate distance from the target position - LLVector3 target_diff = cur_object->getPositionRegion() - position; - F32 distance_to_target = target_diff.length(); - if (distance_to_target < min_distance) - { // Found an object closer - min_distance = distance_to_target; - object = cur_object; - } - } - } - - return object; -} - - -void LLAgentListener::requestTouch(LLSD const & event_data) const -{ - LLViewerObject *object = NULL; - - if (event_data.has("obj_uuid")) - { - object = gObjectList.findObject(event_data["obj_uuid"]); - } - else if (event_data.has("position")) - { - LLVector3 target_position = ll_vector3_from_sd(event_data["position"]); - object = findObjectClosestTo(target_position); - } - - S32 face = 0; - if (event_data.has("face")) - { - face = event_data["face"].asInteger(); - } - - if (object && object->getPCode() == LL_PCODE_VOLUME) - { - // Fake enough pick info to get it to (hopefully) work - LLPickInfo pick; - pick.mObjectFace = face; - - /* - These values are sent to the simulator, but face seems to be easiest to use - - pick.mUVCoords "UVCoord" - pick.mSTCoords "STCoord" - pick.mObjectFace "FaceIndex" - pick.mIntersection "Position" - pick.mNormal "Normal" - pick.mBinormal "Binormal" - */ - - // A touch is a sketchy message sequence ... send a grab, immediately - // followed by un-grabbing, crossing fingers and hoping packets arrive in - // the correct order - send_ObjectGrab_message(object, pick, LLVector3::zero); - send_ObjectDeGrab_message(object, pick); - } - else - { - LL_WARNS() << "LLAgent requestTouch could not find the touch target " - << event_data["obj_uuid"].asUUID() << LL_ENDL; - } -} - - -void LLAgentListener::resetAxes(const LLSD& event_data) const -{ - if (event_data.has("lookat")) - { - mAgent.resetAxes(ll_vector3_from_sd(event_data["lookat"])); - } - else - { - // no "lookat", default call - mAgent.resetAxes(); - } -} - -void LLAgentListener::getAxes(const LLSD& event_data) const -{ - LLQuaternion quat(mAgent.getQuat()); - F32 roll, pitch, yaw; - quat.getEulerAngles(&roll, &pitch, &yaw); - // The official query API for LLQuaternion's [x, y, z, w] values is its - // public member mQ... - LLSD reply = LLSD::emptyMap(); - reply["quat"] = llsd_copy_array(boost::begin(quat.mQ), boost::end(quat.mQ)); - reply["euler"] = LLSD::emptyMap(); - reply["euler"]["roll"] = roll; - reply["euler"]["pitch"] = pitch; - reply["euler"]["yaw"] = yaw; - sendReply(reply, event_data); -} - -void LLAgentListener::getPosition(const LLSD& event_data) const -{ - F32 roll, pitch, yaw; - LLQuaternion quat(mAgent.getQuat()); - quat.getEulerAngles(&roll, &pitch, &yaw); - - LLSD reply = LLSD::emptyMap(); - reply["quat"] = llsd_copy_array(boost::begin(quat.mQ), boost::end(quat.mQ)); - reply["euler"] = LLSD::emptyMap(); - reply["euler"]["roll"] = roll; - reply["euler"]["pitch"] = pitch; - reply["euler"]["yaw"] = yaw; - reply["region"] = ll_sd_from_vector3(mAgent.getPositionAgent()); - reply["global"] = ll_sd_from_vector3d(mAgent.getPositionGlobal()); - - sendReply(reply, event_data); -} - - -void LLAgentListener::startAutoPilot(LLSD const & event_data) -{ - LLQuaternion target_rotation_value; - LLQuaternion* target_rotation = NULL; - if (event_data.has("target_rotation")) - { - target_rotation_value = ll_quaternion_from_sd(event_data["target_rotation"]); - target_rotation = &target_rotation_value; - } - // *TODO: Use callback_pump and callback_data - F32 rotation_threshold = 0.03f; - if (event_data.has("rotation_threshold")) - { - rotation_threshold = event_data["rotation_threshold"].asReal(); - } - - bool allow_flying = true; - if (event_data.has("allow_flying")) - { - allow_flying = (bool) event_data["allow_flying"].asBoolean(); - mAgent.setFlying(allow_flying); - } - - F32 stop_distance = 0.f; - if (event_data.has("stop_distance")) - { - stop_distance = event_data["stop_distance"].asReal(); - } - - // Clear follow target, this is doing a path - mFollowTarget.setNull(); - - mAgent.startAutoPilotGlobal(ll_vector3d_from_sd(event_data["target_global"]), - event_data["behavior_name"], - target_rotation, - NULL, NULL, - stop_distance, - rotation_threshold, - allow_flying); -} - -void LLAgentListener::getAutoPilot(const LLSD& event_data) const -{ - LLSD reply = LLSD::emptyMap(); - - LLSD::Boolean enabled = mAgent.getAutoPilot(); - reply["enabled"] = enabled; - - reply["target_global"] = ll_sd_from_vector3d(mAgent.getAutoPilotTargetGlobal()); - - reply["leader_id"] = mAgent.getAutoPilotLeaderID(); - - reply["stop_distance"] = mAgent.getAutoPilotStopDistance(); - - reply["target_distance"] = mAgent.getAutoPilotTargetDist(); - if (!enabled && - mFollowTarget.notNull()) - { // Get an actual distance from the target object we were following - LLViewerObject * target = gObjectList.findObject(mFollowTarget); - if (target) - { // Found the target AV, return the actual distance to them as well as their ID - LLVector3 difference = target->getPositionRegion() - mAgent.getPositionAgent(); - reply["target_distance"] = difference.length(); - reply["leader_id"] = mFollowTarget; - } - } - - reply["use_rotation"] = (LLSD::Boolean) mAgent.getAutoPilotUseRotation(); - reply["target_facing"] = ll_sd_from_vector3(mAgent.getAutoPilotTargetFacing()); - reply["rotation_threshold"] = mAgent.getAutoPilotRotationThreshold(); - reply["behavior_name"] = mAgent.getAutoPilotBehaviorName(); - reply["fly"] = (LLSD::Boolean) mAgent.getFlying(); - - sendReply(reply, event_data); -} - -void LLAgentListener::startFollowPilot(LLSD const & event_data) -{ - LLUUID target_id; - - bool allow_flying = true; - if (event_data.has("allow_flying")) - { - allow_flying = (bool) event_data["allow_flying"].asBoolean(); - } - - if (event_data.has("leader_id")) - { - target_id = event_data["leader_id"]; - } - else if (event_data.has("avatar_name")) - { // Find the avatar with matching name - std::string target_name = event_data["avatar_name"].asString(); - - if (target_name.length() > 0) - { - S32 num_objects = gObjectList.getNumObjects(); - S32 cur_index = 0; - while (cur_index < num_objects) - { - LLViewerObject * cur_object = gObjectList.getObject(cur_index++); - if (cur_object && - cur_object->asAvatar() && - cur_object->asAvatar()->getFullname() == target_name) - { // Found avatar with matching name, extract id and break out of loop - target_id = cur_object->getID(); - break; - } - } - } - } - - F32 stop_distance = 0.f; - if (event_data.has("stop_distance")) - { - stop_distance = event_data["stop_distance"].asReal(); - } - - if (target_id.notNull()) - { - mAgent.setFlying(allow_flying); - mFollowTarget = target_id; // Save follow target so we can report distance later - - mAgent.startFollowPilot(target_id, allow_flying, stop_distance); - } -} - -void LLAgentListener::setAutoPilotTarget(LLSD const & event_data) const -{ - if (event_data.has("target_global")) - { - LLVector3d target_global(ll_vector3d_from_sd(event_data["target_global"])); - mAgent.setAutoPilotTargetGlobal(target_global); - } -} - -void LLAgentListener::stopAutoPilot(LLSD const & event_data) const -{ - bool user_cancel = false; - if (event_data.has("user_cancel")) - { - user_cancel = event_data["user_cancel"].asBoolean(); - } - mAgent.stopAutoPilot(user_cancel); -} - -void LLAgentListener::lookAt(LLSD const & event_data) const -{ - LLViewerObject *object = NULL; - if (event_data.has("obj_uuid")) - { - object = gObjectList.findObject(event_data["obj_uuid"]); - } - else if (event_data.has("position")) - { - LLVector3 target_position = ll_vector3_from_sd(event_data["position"]); - object = findObjectClosestTo(target_position); - } - - S32 look_at_type = (S32) LOOKAT_TARGET_NONE; - if (event_data.has("type")) - { - look_at_type = event_data["type"].asInteger(); - } - if (look_at_type >= (S32) LOOKAT_TARGET_NONE && - look_at_type < (S32) LOOKAT_NUM_TARGETS) - { - gAgentCamera.setLookAt((ELookAtType) look_at_type, object); - } -} - -void LLAgentListener::getGroups(const LLSD& event) const -{ - LLSD reply(LLSD::emptyArray()); - for (std::vector::const_iterator - gi(mAgent.mGroups.begin()), gend(mAgent.mGroups.end()); - gi != gend; ++gi) - { - reply.append(LLSDMap - ("id", gi->mID) - ("name", gi->mName) - ("insignia", gi->mInsigniaID) - ("notices", bool(gi->mAcceptNotices)) - ("display", bool(gi->mListInProfile)) - ("contrib", gi->mContribution)); - } - sendReply(LLSDMap("groups", reply), event); -} +/** + * @file llagentlistener.cpp + * @author Brad Kittenbrink + * @date 2009-07-10 + * @brief Implementation for llagentlistener. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llagentlistener.h" + +#include "llagent.h" +#include "llvoavatar.h" +#include "llcommandhandler.h" +#include "llslurl.h" +#include "llurldispatcher.h" +#include "llviewernetwork.h" +#include "llviewerobject.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llsdutil.h" +#include "llsdutil_math.h" +#include "lltoolgrab.h" +#include "llhudeffectlookat.h" +#include "llagentcamera.h" + +LLAgentListener::LLAgentListener(LLAgent &agent) + : LLEventAPI("LLAgent", + "LLAgent listener to (e.g.) teleport, sit, stand, etc."), + mAgent(agent) +{ + add("requestTeleport", + "Teleport: [\"regionname\"], [\"x\"], [\"y\"], [\"z\"]\n" + "If [\"skip_confirmation\"] is true, use LLURLDispatcher rather than LLCommandDispatcher.", + &LLAgentListener::requestTeleport); + add("requestSit", + "[\"obj_uuid\"]: id of object to sit on, use this or [\"position\"] to indicate the sit target" + "[\"position\"]: region position {x, y, z} where to find closest object to sit on", + &LLAgentListener::requestSit); + add("requestStand", + "Ask to stand up", + &LLAgentListener::requestStand); + add("requestTouch", + "[\"obj_uuid\"]: id of object to touch, use this or [\"position\"] to indicate the object to touch" + "[\"position\"]: region position {x, y, z} where to find closest object to touch" + "[\"face\"]: optional object face number to touch[Default: 0]", + &LLAgentListener::requestTouch); + add("resetAxes", + "Set the agent to a fixed orientation (optionally specify [\"lookat\"] = array of [x, y, z])", + &LLAgentListener::resetAxes); + add("getAxes", + "Obsolete - use getPosition instead\n" + "Send information about the agent's orientation on [\"reply\"]:\n" + "[\"euler\"]: map of {roll, pitch, yaw}\n" + "[\"quat\"]: array of [x, y, z, w] quaternion values", + &LLAgentListener::getAxes, + LLSDMap("reply", LLSD())); + add("getPosition", + "Send information about the agent's position and orientation on [\"reply\"]:\n" + "[\"region\"]: array of region {x, y, z} position\n" + "[\"global\"]: array of global {x, y, z} position\n" + "[\"euler\"]: map of {roll, pitch, yaw}\n" + "[\"quat\"]: array of [x, y, z, w] quaternion values", + &LLAgentListener::getPosition, + LLSDMap("reply", LLSD())); + add("startAutoPilot", + "Start the autopilot system using the following parameters:\n" + "[\"target_global\"]: array of target global {x, y, z} position\n" + "[\"stop_distance\"]: target maxiumum distance from target [default: autopilot guess]\n" + "[\"target_rotation\"]: array of [x, y, z, w] quaternion values [default: no target]\n" + "[\"rotation_threshold\"]: target maximum angle from target facing rotation [default: 0.03 radians]\n" + "[\"behavior_name\"]: name of the autopilot behavior [default: \"\"]" + "[\"allow_flying\"]: allow flying during autopilot [default: True]", + //"[\"callback_pump\"]: pump to send success/failure and callback data to [default: none]\n" + //"[\"callback_data\"]: data to send back during a callback [default: none]", + &LLAgentListener::startAutoPilot); + add("getAutoPilot", + "Send information about current state of the autopilot system to [\"reply\"]:\n" + "[\"enabled\"]: boolean indicating whether or not autopilot is enabled\n" + "[\"target_global\"]: array of target global {x, y, z} position\n" + "[\"leader_id\"]: uuid of target autopilot is following\n" + "[\"stop_distance\"]: target maximum distance from target\n" + "[\"target_distance\"]: last known distance from target\n" + "[\"use_rotation\"]: boolean indicating if autopilot has a target facing rotation\n" + "[\"target_facing\"]: array of {x, y} target direction to face\n" + "[\"rotation_threshold\"]: target maximum angle from target facing rotation\n" + "[\"behavior_name\"]: name of the autopilot behavior", + &LLAgentListener::getAutoPilot, + LLSDMap("reply", LLSD())); + add("startFollowPilot", + "[\"leader_id\"]: uuid of target to follow using the autopilot system (optional with avatar_name)\n" + "[\"avatar_name\"]: avatar name to follow using the autopilot system (optional with leader_id)\n" + "[\"allow_flying\"]: allow flying during autopilot [default: True]\n" + "[\"stop_distance\"]: target maxiumum distance from target [default: autopilot guess]", + &LLAgentListener::startFollowPilot); + add("setAutoPilotTarget", + "Update target for currently running autopilot:\n" + "[\"target_global\"]: array of target global {x, y, z} position", + &LLAgentListener::setAutoPilotTarget); + add("stopAutoPilot", + "Stop the autopilot system:\n" + "[\"user_cancel\"] indicates whether or not to act as though user canceled autopilot [default: false]", + &LLAgentListener::stopAutoPilot); + add("lookAt", + "[\"type\"]: number to indicate the lookAt type, 0 to clear\n" + "[\"obj_uuid\"]: id of object to look at, use this or [\"position\"] to indicate the target\n" + "[\"position\"]: region position {x, y, z} where to find closest object or avatar to look at", + &LLAgentListener::lookAt); + add("getGroups", + "Send information about the agent's groups on [\"reply\"]:\n" + "[\"groups\"]: array of group information\n" + "[\"id\"]: group id\n" + "[\"name\"]: group name\n" + "[\"insignia\"]: group insignia texture id\n" + "[\"notices\"]: boolean indicating if this user accepts notices from this group\n" + "[\"display\"]: boolean indicating if this group is listed in the user's profile\n" + "[\"contrib\"]: user's land contribution to this group\n", + &LLAgentListener::getGroups, + LLSDMap("reply", LLSD())); +} + +void LLAgentListener::requestTeleport(LLSD const & event_data) const +{ + if(event_data["skip_confirmation"].asBoolean()) + { + LLSD params(LLSD::emptyArray()); + params.append(event_data["regionname"]); + params.append(event_data["x"]); + params.append(event_data["y"]); + params.append(event_data["z"]); + LLCommandDispatcher::dispatch("teleport", params, LLSD(), LLGridManager::getInstance()->getGrid(), NULL, LLCommandHandler::NAV_TYPE_CLICKED, true); + // *TODO - lookup other LLCommandHandlers for "agent", "classified", "event", "group", "floater", "parcel", "login", login_refresh", "balance", "chat" + // should we just compose LLCommandHandler and LLDispatchListener? + } + else + { + std::string url = LLSLURL(event_data["regionname"], + LLVector3(event_data["x"].asReal(), + event_data["y"].asReal(), + event_data["z"].asReal())).getSLURLString(); + LLURLDispatcher::dispatch(url, LLCommandHandler::NAV_TYPE_CLICKED, NULL, false); + } +} + +void LLAgentListener::requestSit(LLSD const & event_data) const +{ + //mAgent.getAvatarObject()->sitOnObject(); + // shamelessly ripped from llviewermenu.cpp:handle_sit_or_stand() + // *TODO - find a permanent place to share this code properly. + + LLViewerObject *object = NULL; + if (event_data.has("obj_uuid")) + { + object = gObjectList.findObject(event_data["obj_uuid"]); + } + else if (event_data.has("position")) + { + LLVector3 target_position = ll_vector3_from_sd(event_data["position"]); + object = findObjectClosestTo(target_position); + } + + if (object && object->getPCode() == LL_PCODE_VOLUME) + { + gMessageSystem->newMessageFast(_PREHASH_AgentRequestSit); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, mAgent.getID()); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, mAgent.getSessionID()); + gMessageSystem->nextBlockFast(_PREHASH_TargetObject); + gMessageSystem->addUUIDFast(_PREHASH_TargetID, object->mID); + gMessageSystem->addVector3Fast(_PREHASH_Offset, LLVector3(0,0,0)); + + object->getRegion()->sendReliableMessage(); + } + else + { + LL_WARNS() << "LLAgent requestSit could not find the sit target: " + << event_data << LL_ENDL; + } +} + +void LLAgentListener::requestStand(LLSD const & event_data) const +{ + mAgent.setControlFlags(AGENT_CONTROL_STAND_UP); +} + + +LLViewerObject * LLAgentListener::findObjectClosestTo( const LLVector3 & position ) const +{ + LLViewerObject *object = NULL; + + // Find the object closest to that position + F32 min_distance = 10000.0f; // Start big + S32 num_objects = gObjectList.getNumObjects(); + S32 cur_index = 0; + while (cur_index < num_objects) + { + LLViewerObject * cur_object = gObjectList.getObject(cur_index++); + if (cur_object) + { // Calculate distance from the target position + LLVector3 target_diff = cur_object->getPositionRegion() - position; + F32 distance_to_target = target_diff.length(); + if (distance_to_target < min_distance) + { // Found an object closer + min_distance = distance_to_target; + object = cur_object; + } + } + } + + return object; +} + + +void LLAgentListener::requestTouch(LLSD const & event_data) const +{ + LLViewerObject *object = NULL; + + if (event_data.has("obj_uuid")) + { + object = gObjectList.findObject(event_data["obj_uuid"]); + } + else if (event_data.has("position")) + { + LLVector3 target_position = ll_vector3_from_sd(event_data["position"]); + object = findObjectClosestTo(target_position); + } + + S32 face = 0; + if (event_data.has("face")) + { + face = event_data["face"].asInteger(); + } + + if (object && object->getPCode() == LL_PCODE_VOLUME) + { + // Fake enough pick info to get it to (hopefully) work + LLPickInfo pick; + pick.mObjectFace = face; + + /* + These values are sent to the simulator, but face seems to be easiest to use + + pick.mUVCoords "UVCoord" + pick.mSTCoords "STCoord" + pick.mObjectFace "FaceIndex" + pick.mIntersection "Position" + pick.mNormal "Normal" + pick.mBinormal "Binormal" + */ + + // A touch is a sketchy message sequence ... send a grab, immediately + // followed by un-grabbing, crossing fingers and hoping packets arrive in + // the correct order + send_ObjectGrab_message(object, pick, LLVector3::zero); + send_ObjectDeGrab_message(object, pick); + } + else + { + LL_WARNS() << "LLAgent requestTouch could not find the touch target " + << event_data["obj_uuid"].asUUID() << LL_ENDL; + } +} + + +void LLAgentListener::resetAxes(const LLSD& event_data) const +{ + if (event_data.has("lookat")) + { + mAgent.resetAxes(ll_vector3_from_sd(event_data["lookat"])); + } + else + { + // no "lookat", default call + mAgent.resetAxes(); + } +} + +void LLAgentListener::getAxes(const LLSD& event_data) const +{ + LLQuaternion quat(mAgent.getQuat()); + F32 roll, pitch, yaw; + quat.getEulerAngles(&roll, &pitch, &yaw); + // The official query API for LLQuaternion's [x, y, z, w] values is its + // public member mQ... + LLSD reply = LLSD::emptyMap(); + reply["quat"] = llsd_copy_array(boost::begin(quat.mQ), boost::end(quat.mQ)); + reply["euler"] = LLSD::emptyMap(); + reply["euler"]["roll"] = roll; + reply["euler"]["pitch"] = pitch; + reply["euler"]["yaw"] = yaw; + sendReply(reply, event_data); +} + +void LLAgentListener::getPosition(const LLSD& event_data) const +{ + F32 roll, pitch, yaw; + LLQuaternion quat(mAgent.getQuat()); + quat.getEulerAngles(&roll, &pitch, &yaw); + + LLSD reply = LLSD::emptyMap(); + reply["quat"] = llsd_copy_array(boost::begin(quat.mQ), boost::end(quat.mQ)); + reply["euler"] = LLSD::emptyMap(); + reply["euler"]["roll"] = roll; + reply["euler"]["pitch"] = pitch; + reply["euler"]["yaw"] = yaw; + reply["region"] = ll_sd_from_vector3(mAgent.getPositionAgent()); + reply["global"] = ll_sd_from_vector3d(mAgent.getPositionGlobal()); + + sendReply(reply, event_data); +} + + +void LLAgentListener::startAutoPilot(LLSD const & event_data) +{ + LLQuaternion target_rotation_value; + LLQuaternion* target_rotation = NULL; + if (event_data.has("target_rotation")) + { + target_rotation_value = ll_quaternion_from_sd(event_data["target_rotation"]); + target_rotation = &target_rotation_value; + } + // *TODO: Use callback_pump and callback_data + F32 rotation_threshold = 0.03f; + if (event_data.has("rotation_threshold")) + { + rotation_threshold = event_data["rotation_threshold"].asReal(); + } + + bool allow_flying = true; + if (event_data.has("allow_flying")) + { + allow_flying = (bool) event_data["allow_flying"].asBoolean(); + mAgent.setFlying(allow_flying); + } + + F32 stop_distance = 0.f; + if (event_data.has("stop_distance")) + { + stop_distance = event_data["stop_distance"].asReal(); + } + + // Clear follow target, this is doing a path + mFollowTarget.setNull(); + + mAgent.startAutoPilotGlobal(ll_vector3d_from_sd(event_data["target_global"]), + event_data["behavior_name"], + target_rotation, + NULL, NULL, + stop_distance, + rotation_threshold, + allow_flying); +} + +void LLAgentListener::getAutoPilot(const LLSD& event_data) const +{ + LLSD reply = LLSD::emptyMap(); + + LLSD::Boolean enabled = mAgent.getAutoPilot(); + reply["enabled"] = enabled; + + reply["target_global"] = ll_sd_from_vector3d(mAgent.getAutoPilotTargetGlobal()); + + reply["leader_id"] = mAgent.getAutoPilotLeaderID(); + + reply["stop_distance"] = mAgent.getAutoPilotStopDistance(); + + reply["target_distance"] = mAgent.getAutoPilotTargetDist(); + if (!enabled && + mFollowTarget.notNull()) + { // Get an actual distance from the target object we were following + LLViewerObject * target = gObjectList.findObject(mFollowTarget); + if (target) + { // Found the target AV, return the actual distance to them as well as their ID + LLVector3 difference = target->getPositionRegion() - mAgent.getPositionAgent(); + reply["target_distance"] = difference.length(); + reply["leader_id"] = mFollowTarget; + } + } + + reply["use_rotation"] = (LLSD::Boolean) mAgent.getAutoPilotUseRotation(); + reply["target_facing"] = ll_sd_from_vector3(mAgent.getAutoPilotTargetFacing()); + reply["rotation_threshold"] = mAgent.getAutoPilotRotationThreshold(); + reply["behavior_name"] = mAgent.getAutoPilotBehaviorName(); + reply["fly"] = (LLSD::Boolean) mAgent.getFlying(); + + sendReply(reply, event_data); +} + +void LLAgentListener::startFollowPilot(LLSD const & event_data) +{ + LLUUID target_id; + + bool allow_flying = true; + if (event_data.has("allow_flying")) + { + allow_flying = (bool) event_data["allow_flying"].asBoolean(); + } + + if (event_data.has("leader_id")) + { + target_id = event_data["leader_id"]; + } + else if (event_data.has("avatar_name")) + { // Find the avatar with matching name + std::string target_name = event_data["avatar_name"].asString(); + + if (target_name.length() > 0) + { + S32 num_objects = gObjectList.getNumObjects(); + S32 cur_index = 0; + while (cur_index < num_objects) + { + LLViewerObject * cur_object = gObjectList.getObject(cur_index++); + if (cur_object && + cur_object->asAvatar() && + cur_object->asAvatar()->getFullname() == target_name) + { // Found avatar with matching name, extract id and break out of loop + target_id = cur_object->getID(); + break; + } + } + } + } + + F32 stop_distance = 0.f; + if (event_data.has("stop_distance")) + { + stop_distance = event_data["stop_distance"].asReal(); + } + + if (target_id.notNull()) + { + mAgent.setFlying(allow_flying); + mFollowTarget = target_id; // Save follow target so we can report distance later + + mAgent.startFollowPilot(target_id, allow_flying, stop_distance); + } +} + +void LLAgentListener::setAutoPilotTarget(LLSD const & event_data) const +{ + if (event_data.has("target_global")) + { + LLVector3d target_global(ll_vector3d_from_sd(event_data["target_global"])); + mAgent.setAutoPilotTargetGlobal(target_global); + } +} + +void LLAgentListener::stopAutoPilot(LLSD const & event_data) const +{ + bool user_cancel = false; + if (event_data.has("user_cancel")) + { + user_cancel = event_data["user_cancel"].asBoolean(); + } + mAgent.stopAutoPilot(user_cancel); +} + +void LLAgentListener::lookAt(LLSD const & event_data) const +{ + LLViewerObject *object = NULL; + if (event_data.has("obj_uuid")) + { + object = gObjectList.findObject(event_data["obj_uuid"]); + } + else if (event_data.has("position")) + { + LLVector3 target_position = ll_vector3_from_sd(event_data["position"]); + object = findObjectClosestTo(target_position); + } + + S32 look_at_type = (S32) LOOKAT_TARGET_NONE; + if (event_data.has("type")) + { + look_at_type = event_data["type"].asInteger(); + } + if (look_at_type >= (S32) LOOKAT_TARGET_NONE && + look_at_type < (S32) LOOKAT_NUM_TARGETS) + { + gAgentCamera.setLookAt((ELookAtType) look_at_type, object); + } +} + +void LLAgentListener::getGroups(const LLSD& event) const +{ + LLSD reply(LLSD::emptyArray()); + for (std::vector::const_iterator + gi(mAgent.mGroups.begin()), gend(mAgent.mGroups.end()); + gi != gend; ++gi) + { + reply.append(LLSDMap + ("id", gi->mID) + ("name", gi->mName) + ("insignia", gi->mInsigniaID) + ("notices", bool(gi->mAcceptNotices)) + ("display", bool(gi->mListInProfile)) + ("contrib", gi->mContribution)); + } + sendReply(LLSDMap("groups", reply), event); +} diff --git a/indra/newview/llagentpicksinfo.h b/indra/newview/llagentpicksinfo.h index 3922cbe574..9bc105a655 100644 --- a/indra/newview/llagentpicksinfo.h +++ b/indra/newview/llagentpicksinfo.h @@ -1,98 +1,98 @@ -/** - * @file llagentpicksinfo.h - * @brief LLAgentPicksInfo class header file - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLAGENTPICKS_H -#define LL_LLAGENTPICKS_H - -#include "llsingleton.h" - -struct LLAvatarData; - -/** - * Class that provides information about Agent Picks - */ -class LLAgentPicksInfo : public LLSingleton -{ - LLSINGLETON(LLAgentPicksInfo); - virtual ~LLAgentPicksInfo(); - - class LLAgentPicksObserver; - -public: - /** - * Requests number of picks from server. - * - * Number of Picks is requested from server, thus it is not available immediately. - */ - void requestNumberOfPicks(); - - /** - * Returns number of Picks. - */ - S32 getNumberOfPicks() { return mNumberOfPicks; } - - /** - * Returns maximum number of Picks. - */ - S32 getMaxNumberOfPicks() { return mMaxNumberOfPicks; } - - /** - * Returns true if Agent has maximum allowed number of Picks. - */ - bool isPickLimitReached(); - - /** - * After creating or deleting a Pick we can assume operation on server will be - * completed successfully. Incrementing/decrementing number of picks makes new number - * of picks available immediately. Actual number of picks will be updated when we receive - * response from server. - */ - void incrementNumberOfPicks() { ++mNumberOfPicks; } - - void decrementNumberOfPicks() { --mNumberOfPicks; } - - void onServerRespond(LLAvatarData* picks); - -private: - - /** - * Sets number of Picks. - */ - void setNumberOfPicks(S32 number) { mNumberOfPicks = number; } - - /** - * Sets maximum number of Picks. - */ - void setMaxNumberOfPicks(S32 max_picks) { mMaxNumberOfPicks = max_picks; } - -private: - - LLAgentPicksObserver* mAgentPicksObserver; - S32 mMaxNumberOfPicks; - S32 mNumberOfPicks; -}; - -#endif //LL_LLAGENTPICKS_H +/** + * @file llagentpicksinfo.h + * @brief LLAgentPicksInfo class header file + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLAGENTPICKS_H +#define LL_LLAGENTPICKS_H + +#include "llsingleton.h" + +struct LLAvatarData; + +/** + * Class that provides information about Agent Picks + */ +class LLAgentPicksInfo : public LLSingleton +{ + LLSINGLETON(LLAgentPicksInfo); + virtual ~LLAgentPicksInfo(); + + class LLAgentPicksObserver; + +public: + /** + * Requests number of picks from server. + * + * Number of Picks is requested from server, thus it is not available immediately. + */ + void requestNumberOfPicks(); + + /** + * Returns number of Picks. + */ + S32 getNumberOfPicks() { return mNumberOfPicks; } + + /** + * Returns maximum number of Picks. + */ + S32 getMaxNumberOfPicks() { return mMaxNumberOfPicks; } + + /** + * Returns true if Agent has maximum allowed number of Picks. + */ + bool isPickLimitReached(); + + /** + * After creating or deleting a Pick we can assume operation on server will be + * completed successfully. Incrementing/decrementing number of picks makes new number + * of picks available immediately. Actual number of picks will be updated when we receive + * response from server. + */ + void incrementNumberOfPicks() { ++mNumberOfPicks; } + + void decrementNumberOfPicks() { --mNumberOfPicks; } + + void onServerRespond(LLAvatarData* picks); + +private: + + /** + * Sets number of Picks. + */ + void setNumberOfPicks(S32 number) { mNumberOfPicks = number; } + + /** + * Sets maximum number of Picks. + */ + void setMaxNumberOfPicks(S32 max_picks) { mMaxNumberOfPicks = max_picks; } + +private: + + LLAgentPicksObserver* mAgentPicksObserver; + S32 mMaxNumberOfPicks; + S32 mNumberOfPicks; +}; + +#endif //LL_LLAGENTPICKS_H diff --git a/indra/newview/llagentpilot.cpp b/indra/newview/llagentpilot.cpp index 0aaa93b6d2..40f1679663 100644 --- a/indra/newview/llagentpilot.cpp +++ b/indra/newview/llagentpilot.cpp @@ -1,407 +1,407 @@ -/** - * @file llagentpilot.cpp - * @brief LLAgentPilot class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include -#include -#include - -#include "llagentpilot.h" -#include "llagent.h" -#include "llappviewer.h" -#include "llviewercontrol.h" -#include "llviewercamera.h" -#include "llsdserialize.h" -#include "llsdutil_math.h" - -LLAgentPilot gAgentPilot; - -LLAgentPilot::LLAgentPilot() : - mNumRuns(-1), - mQuitAfterRuns(false), - mRecording(false), - mLastRecordTime(0.f), - mStarted(false), - mPlaying(false), - mCurrentAction(0), - mOverrideCamera(false), - mLoop(true), - mReplaySession(false) -{ -} - -LLAgentPilot::~LLAgentPilot() -{ -} - -void LLAgentPilot::load() -{ - std::string txt_filename = gSavedSettings.getString("StatsPilotFile"); - std::string xml_filename = gSavedSettings.getString("StatsPilotXMLFile"); - if (LLFile::isfile(xml_filename)) - { - loadXML(xml_filename); - } - else if (LLFile::isfile(txt_filename)) - { - loadTxt(txt_filename); - } - else - { - LL_DEBUGS() << "no autopilot file found" << LL_ENDL; - return; - } -} - -void LLAgentPilot::loadTxt(const std::string& filename) -{ - if(filename.empty()) - { - return; - } - - llifstream file(filename.c_str()); - - if (!file) - { - LL_DEBUGS() << "Couldn't open " << filename - << ", aborting agentpilot load!" << LL_ENDL; - return; - } - else - { - LL_INFOS() << "Opening pilot file " << filename << LL_ENDL; - } - - mActions.clear(); - S32 num_actions; - - file >> num_actions; - - mActions.reserve(num_actions); - for (S32 i = 0; i < num_actions; i++) - { - S32 action_type; - Action new_action; - file >> new_action.mTime >> action_type; - file >> new_action.mTarget.mdV[VX] >> new_action.mTarget.mdV[VY] >> new_action.mTarget.mdV[VZ]; - new_action.mType = (EActionType)action_type; - mActions.push_back(new_action); - } - - mOverrideCamera = false; - - file.close(); -} - -void LLAgentPilot::loadXML(const std::string& filename) -{ - if(filename.empty()) - { - return; - } - - llifstream file(filename.c_str()); - - if (!file) - { - LL_DEBUGS() << "Couldn't open " << filename - << ", aborting agentpilot load!" << LL_ENDL; - return; - } - else - { - LL_INFOS() << "Opening pilot file " << filename << LL_ENDL; - } - - mActions.clear(); - LLSD record; - while (!file.eof() && LLSDParser::PARSE_FAILURE != LLSDSerialize::fromXML(record, file)) - { - Action action; - action.mTime = record["time"].asReal(); - action.mType = (EActionType)record["type"].asInteger(); - action.mCameraView = record["camera_view"].asReal(); - action.mTarget = ll_vector3d_from_sd(record["target"]); - action.mCameraOrigin = ll_vector3_from_sd(record["camera_origin"]); - action.mCameraXAxis = ll_vector3_from_sd(record["camera_xaxis"]); - action.mCameraYAxis = ll_vector3_from_sd(record["camera_yaxis"]); - action.mCameraZAxis = ll_vector3_from_sd(record["camera_zaxis"]); - mActions.push_back(action); - } - mOverrideCamera = true; - file.close(); -} - -void LLAgentPilot::save() -{ - std::string txt_filename = gSavedSettings.getString("StatsPilotFile"); - std::string xml_filename = gSavedSettings.getString("StatsPilotXMLFile"); - saveTxt(txt_filename); - saveXML(xml_filename); -} - -void LLAgentPilot::saveTxt(const std::string& filename) -{ - llofstream file; - file.open(filename.c_str()); - - if (!file) - { - LL_INFOS() << "Couldn't open " << filename << ", aborting agentpilot save!" << LL_ENDL; - } - - file << mActions.size() << '\n'; - - S32 i; - for (i = 0; i < mActions.size(); i++) - { - file << mActions[i].mTime << "\t" << mActions[i].mType << "\t"; - file << std::setprecision(32) << mActions[i].mTarget.mdV[VX] << "\t" << mActions[i].mTarget.mdV[VY] << "\t" << mActions[i].mTarget.mdV[VZ]; - file << '\n'; - } - - file.close(); -} - -void LLAgentPilot::saveXML(const std::string& filename) -{ - llofstream file; - file.open(filename.c_str()); - - if (!file) - { - LL_INFOS() << "Couldn't open " << filename << ", aborting agentpilot save!" << LL_ENDL; - } - - S32 i; - for (i = 0; i < mActions.size(); i++) - { - Action& action = mActions[i]; - LLSD record; - record["time"] = (LLSD::Real)action.mTime; - record["type"] = (LLSD::Integer)action.mType; - record["camera_view"] = (LLSD::Real)action.mCameraView; - record["target"] = ll_sd_from_vector3d(action.mTarget); - record["camera_origin"] = ll_sd_from_vector3(action.mCameraOrigin); - record["camera_xaxis"] = ll_sd_from_vector3(action.mCameraXAxis); - record["camera_yaxis"] = ll_sd_from_vector3(action.mCameraYAxis); - record["camera_zaxis"] = ll_sd_from_vector3(action.mCameraZAxis); - LLSDSerialize::toXML(record, file); - } - file.close(); -} - -void LLAgentPilot::startRecord() -{ - mActions.clear(); - mTimer.reset(); - addAction(STRAIGHT); - mRecording = true; -} - -void LLAgentPilot::stopRecord() -{ - gAgentPilot.addAction(STRAIGHT); - gAgentPilot.save(); - mRecording = false; -} - -void LLAgentPilot::addAction(enum EActionType action_type) -{ - LL_INFOS() << "Adding waypoint: " << gAgent.getPositionGlobal() << LL_ENDL; - Action action; - action.mType = action_type; - action.mTarget = gAgent.getPositionGlobal(); - action.mTime = mTimer.getElapsedTimeF32(); - LLViewerCamera *cam = LLViewerCamera::getInstance(); - action.mCameraView = cam->getView(); - action.mCameraOrigin = cam->getOrigin(); - action.mCameraXAxis = cam->getXAxis(); - action.mCameraYAxis = cam->getYAxis(); - action.mCameraZAxis = cam->getZAxis(); - mLastRecordTime = (F32)action.mTime; - mActions.push_back(action); -} - -void LLAgentPilot::startPlayback() -{ - if (!mPlaying) - { - mPlaying = true; - mCurrentAction = 0; - mTimer.reset(); - - if (mActions.size()) - { - LL_INFOS() << "Starting playback, moving to waypoint 0" << LL_ENDL; - gAgent.startAutoPilotGlobal(mActions[0].mTarget); - moveCamera(); - mStarted = false; - } - else - { - LL_INFOS() << "No autopilot data, cancelling!" << LL_ENDL; - mPlaying = false; - } - } -} - -void LLAgentPilot::stopPlayback() -{ - if (mPlaying) - { - mPlaying = false; - mCurrentAction = 0; - mTimer.reset(); - gAgent.stopAutoPilot(); - } - - if (mReplaySession) - { - LLAppViewer::instance()->forceQuit(); - } -} - -void LLAgentPilot::moveCamera() -{ - if (!getOverrideCamera()) - return; - - if (mCurrentAction 0.0) - { - t = tickelapsed/timedelta; - } - - if ((t<0.0)||(t>1.0)) - { - LL_WARNS() << "mCurrentAction is invalid, t = " << t << LL_ENDL; - return; - } - - Action& start = mActions[start_index]; - Action& end = mActions[end_index]; - - F32 view = lerp(start.mCameraView, end.mCameraView, t); - LLVector3 origin = lerp(start.mCameraOrigin, end.mCameraOrigin, t); - LLQuaternion start_quat(start.mCameraXAxis, start.mCameraYAxis, start.mCameraZAxis); - LLQuaternion end_quat(end.mCameraXAxis, end.mCameraYAxis, end.mCameraZAxis); - LLQuaternion quat = nlerp(t, start_quat, end_quat); - LLMatrix3 mat(quat); - - LLViewerCamera::getInstance()->setView(view); - LLViewerCamera::getInstance()->setOrigin(origin); - LLViewerCamera::getInstance()->mXAxis = LLVector3(mat.mMatrix[0]); - LLViewerCamera::getInstance()->mYAxis = LLVector3(mat.mMatrix[1]); - LLViewerCamera::getInstance()->mZAxis = LLVector3(mat.mMatrix[2]); - } -} - -void LLAgentPilot::updateTarget() -{ - if (mPlaying) - { - if (mCurrentAction < mActions.size()) - { - if (0 == mCurrentAction) - { - if (gAgent.getAutoPilot()) - { - // Wait until we get to the first location before starting. - return; - } - else - { - if (!mStarted) - { - LL_INFOS() << "At start, beginning playback" << LL_ENDL; - mTimer.reset(); - mStarted = true; - } - } - } - if (mTimer.getElapsedTimeF32() > mActions[mCurrentAction].mTime) - { - //gAgent.stopAutoPilot(); - mCurrentAction++; - - if (mCurrentAction < mActions.size()) - { - gAgent.startAutoPilotGlobal(mActions[mCurrentAction].mTarget); - moveCamera(); - } - else - { - stopPlayback(); - mNumRuns--; - if (mLoop) - { - if ((mNumRuns < 0) || (mNumRuns > 0)) - { - LL_INFOS() << "Looping, restarting playback" << LL_ENDL; - startPlayback(); - } - else if (mQuitAfterRuns) - { - LL_INFOS() << "Done with all runs, quitting viewer!" << LL_ENDL; - LLAppViewer::instance()->forceQuit(); - } - else - { - LL_INFOS() << "Done with all runs, disabling pilot" << LL_ENDL; - stopPlayback(); - } - } - } - } - } - else - { - stopPlayback(); - } - } - else if (mRecording) - { - if (mTimer.getElapsedTimeF32() - mLastRecordTime > 1.f) - { - addAction(STRAIGHT); - } - } -} - -void LLAgentPilot::addWaypoint() -{ - addAction(STRAIGHT); -} - +/** + * @file llagentpilot.cpp + * @brief LLAgentPilot class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include +#include +#include + +#include "llagentpilot.h" +#include "llagent.h" +#include "llappviewer.h" +#include "llviewercontrol.h" +#include "llviewercamera.h" +#include "llsdserialize.h" +#include "llsdutil_math.h" + +LLAgentPilot gAgentPilot; + +LLAgentPilot::LLAgentPilot() : + mNumRuns(-1), + mQuitAfterRuns(false), + mRecording(false), + mLastRecordTime(0.f), + mStarted(false), + mPlaying(false), + mCurrentAction(0), + mOverrideCamera(false), + mLoop(true), + mReplaySession(false) +{ +} + +LLAgentPilot::~LLAgentPilot() +{ +} + +void LLAgentPilot::load() +{ + std::string txt_filename = gSavedSettings.getString("StatsPilotFile"); + std::string xml_filename = gSavedSettings.getString("StatsPilotXMLFile"); + if (LLFile::isfile(xml_filename)) + { + loadXML(xml_filename); + } + else if (LLFile::isfile(txt_filename)) + { + loadTxt(txt_filename); + } + else + { + LL_DEBUGS() << "no autopilot file found" << LL_ENDL; + return; + } +} + +void LLAgentPilot::loadTxt(const std::string& filename) +{ + if(filename.empty()) + { + return; + } + + llifstream file(filename.c_str()); + + if (!file) + { + LL_DEBUGS() << "Couldn't open " << filename + << ", aborting agentpilot load!" << LL_ENDL; + return; + } + else + { + LL_INFOS() << "Opening pilot file " << filename << LL_ENDL; + } + + mActions.clear(); + S32 num_actions; + + file >> num_actions; + + mActions.reserve(num_actions); + for (S32 i = 0; i < num_actions; i++) + { + S32 action_type; + Action new_action; + file >> new_action.mTime >> action_type; + file >> new_action.mTarget.mdV[VX] >> new_action.mTarget.mdV[VY] >> new_action.mTarget.mdV[VZ]; + new_action.mType = (EActionType)action_type; + mActions.push_back(new_action); + } + + mOverrideCamera = false; + + file.close(); +} + +void LLAgentPilot::loadXML(const std::string& filename) +{ + if(filename.empty()) + { + return; + } + + llifstream file(filename.c_str()); + + if (!file) + { + LL_DEBUGS() << "Couldn't open " << filename + << ", aborting agentpilot load!" << LL_ENDL; + return; + } + else + { + LL_INFOS() << "Opening pilot file " << filename << LL_ENDL; + } + + mActions.clear(); + LLSD record; + while (!file.eof() && LLSDParser::PARSE_FAILURE != LLSDSerialize::fromXML(record, file)) + { + Action action; + action.mTime = record["time"].asReal(); + action.mType = (EActionType)record["type"].asInteger(); + action.mCameraView = record["camera_view"].asReal(); + action.mTarget = ll_vector3d_from_sd(record["target"]); + action.mCameraOrigin = ll_vector3_from_sd(record["camera_origin"]); + action.mCameraXAxis = ll_vector3_from_sd(record["camera_xaxis"]); + action.mCameraYAxis = ll_vector3_from_sd(record["camera_yaxis"]); + action.mCameraZAxis = ll_vector3_from_sd(record["camera_zaxis"]); + mActions.push_back(action); + } + mOverrideCamera = true; + file.close(); +} + +void LLAgentPilot::save() +{ + std::string txt_filename = gSavedSettings.getString("StatsPilotFile"); + std::string xml_filename = gSavedSettings.getString("StatsPilotXMLFile"); + saveTxt(txt_filename); + saveXML(xml_filename); +} + +void LLAgentPilot::saveTxt(const std::string& filename) +{ + llofstream file; + file.open(filename.c_str()); + + if (!file) + { + LL_INFOS() << "Couldn't open " << filename << ", aborting agentpilot save!" << LL_ENDL; + } + + file << mActions.size() << '\n'; + + S32 i; + for (i = 0; i < mActions.size(); i++) + { + file << mActions[i].mTime << "\t" << mActions[i].mType << "\t"; + file << std::setprecision(32) << mActions[i].mTarget.mdV[VX] << "\t" << mActions[i].mTarget.mdV[VY] << "\t" << mActions[i].mTarget.mdV[VZ]; + file << '\n'; + } + + file.close(); +} + +void LLAgentPilot::saveXML(const std::string& filename) +{ + llofstream file; + file.open(filename.c_str()); + + if (!file) + { + LL_INFOS() << "Couldn't open " << filename << ", aborting agentpilot save!" << LL_ENDL; + } + + S32 i; + for (i = 0; i < mActions.size(); i++) + { + Action& action = mActions[i]; + LLSD record; + record["time"] = (LLSD::Real)action.mTime; + record["type"] = (LLSD::Integer)action.mType; + record["camera_view"] = (LLSD::Real)action.mCameraView; + record["target"] = ll_sd_from_vector3d(action.mTarget); + record["camera_origin"] = ll_sd_from_vector3(action.mCameraOrigin); + record["camera_xaxis"] = ll_sd_from_vector3(action.mCameraXAxis); + record["camera_yaxis"] = ll_sd_from_vector3(action.mCameraYAxis); + record["camera_zaxis"] = ll_sd_from_vector3(action.mCameraZAxis); + LLSDSerialize::toXML(record, file); + } + file.close(); +} + +void LLAgentPilot::startRecord() +{ + mActions.clear(); + mTimer.reset(); + addAction(STRAIGHT); + mRecording = true; +} + +void LLAgentPilot::stopRecord() +{ + gAgentPilot.addAction(STRAIGHT); + gAgentPilot.save(); + mRecording = false; +} + +void LLAgentPilot::addAction(enum EActionType action_type) +{ + LL_INFOS() << "Adding waypoint: " << gAgent.getPositionGlobal() << LL_ENDL; + Action action; + action.mType = action_type; + action.mTarget = gAgent.getPositionGlobal(); + action.mTime = mTimer.getElapsedTimeF32(); + LLViewerCamera *cam = LLViewerCamera::getInstance(); + action.mCameraView = cam->getView(); + action.mCameraOrigin = cam->getOrigin(); + action.mCameraXAxis = cam->getXAxis(); + action.mCameraYAxis = cam->getYAxis(); + action.mCameraZAxis = cam->getZAxis(); + mLastRecordTime = (F32)action.mTime; + mActions.push_back(action); +} + +void LLAgentPilot::startPlayback() +{ + if (!mPlaying) + { + mPlaying = true; + mCurrentAction = 0; + mTimer.reset(); + + if (mActions.size()) + { + LL_INFOS() << "Starting playback, moving to waypoint 0" << LL_ENDL; + gAgent.startAutoPilotGlobal(mActions[0].mTarget); + moveCamera(); + mStarted = false; + } + else + { + LL_INFOS() << "No autopilot data, cancelling!" << LL_ENDL; + mPlaying = false; + } + } +} + +void LLAgentPilot::stopPlayback() +{ + if (mPlaying) + { + mPlaying = false; + mCurrentAction = 0; + mTimer.reset(); + gAgent.stopAutoPilot(); + } + + if (mReplaySession) + { + LLAppViewer::instance()->forceQuit(); + } +} + +void LLAgentPilot::moveCamera() +{ + if (!getOverrideCamera()) + return; + + if (mCurrentAction 0.0) + { + t = tickelapsed/timedelta; + } + + if ((t<0.0)||(t>1.0)) + { + LL_WARNS() << "mCurrentAction is invalid, t = " << t << LL_ENDL; + return; + } + + Action& start = mActions[start_index]; + Action& end = mActions[end_index]; + + F32 view = lerp(start.mCameraView, end.mCameraView, t); + LLVector3 origin = lerp(start.mCameraOrigin, end.mCameraOrigin, t); + LLQuaternion start_quat(start.mCameraXAxis, start.mCameraYAxis, start.mCameraZAxis); + LLQuaternion end_quat(end.mCameraXAxis, end.mCameraYAxis, end.mCameraZAxis); + LLQuaternion quat = nlerp(t, start_quat, end_quat); + LLMatrix3 mat(quat); + + LLViewerCamera::getInstance()->setView(view); + LLViewerCamera::getInstance()->setOrigin(origin); + LLViewerCamera::getInstance()->mXAxis = LLVector3(mat.mMatrix[0]); + LLViewerCamera::getInstance()->mYAxis = LLVector3(mat.mMatrix[1]); + LLViewerCamera::getInstance()->mZAxis = LLVector3(mat.mMatrix[2]); + } +} + +void LLAgentPilot::updateTarget() +{ + if (mPlaying) + { + if (mCurrentAction < mActions.size()) + { + if (0 == mCurrentAction) + { + if (gAgent.getAutoPilot()) + { + // Wait until we get to the first location before starting. + return; + } + else + { + if (!mStarted) + { + LL_INFOS() << "At start, beginning playback" << LL_ENDL; + mTimer.reset(); + mStarted = true; + } + } + } + if (mTimer.getElapsedTimeF32() > mActions[mCurrentAction].mTime) + { + //gAgent.stopAutoPilot(); + mCurrentAction++; + + if (mCurrentAction < mActions.size()) + { + gAgent.startAutoPilotGlobal(mActions[mCurrentAction].mTarget); + moveCamera(); + } + else + { + stopPlayback(); + mNumRuns--; + if (mLoop) + { + if ((mNumRuns < 0) || (mNumRuns > 0)) + { + LL_INFOS() << "Looping, restarting playback" << LL_ENDL; + startPlayback(); + } + else if (mQuitAfterRuns) + { + LL_INFOS() << "Done with all runs, quitting viewer!" << LL_ENDL; + LLAppViewer::instance()->forceQuit(); + } + else + { + LL_INFOS() << "Done with all runs, disabling pilot" << LL_ENDL; + stopPlayback(); + } + } + } + } + } + else + { + stopPlayback(); + } + } + else if (mRecording) + { + if (mTimer.getElapsedTimeF32() - mLastRecordTime > 1.f) + { + addAction(STRAIGHT); + } + } +} + +void LLAgentPilot::addWaypoint() +{ + addAction(STRAIGHT); +} + diff --git a/indra/newview/llagentpilot.h b/indra/newview/llagentpilot.h index c2051bbe15..eff38d2468 100644 --- a/indra/newview/llagentpilot.h +++ b/indra/newview/llagentpilot.h @@ -1,122 +1,122 @@ -/** - * @file llagentpilot.h - * @brief LLAgentPilot class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLAGENTPILOT_H -#define LL_LLAGENTPILOT_H - -#include "stdtypes.h" -#include "lltimer.h" -#include "v3dmath.h" - -// Class that drives the agent around according to a "script". - -class LLAgentPilot -{ -public: - enum EActionType - { - STRAIGHT, - TURN - }; - - LLAgentPilot(); - virtual ~LLAgentPilot(); - - void load(); - void loadTxt(const std::string& filename); - void loadXML(const std::string& filename); - void save(); - void saveTxt(const std::string& filename); - void saveXML(const std::string& filename); - - void startRecord(); - void stopRecord(); - void addAction(enum EActionType action); - - void startPlayback(); - void stopPlayback(); - - bool isRecording() { return mRecording; } - bool isPlaying() { return mPlaying; } - bool getOverrideCamera() { return mOverrideCamera; } - - void updateTarget(); - - void addWaypoint(); - void moveCamera(); - - void setReplaySession(bool new_val) { mReplaySession = new_val; } - bool getReplaySession() { return mReplaySession; } - - void setLoop(bool new_val) { mLoop = new_val; } - bool getLoop() { return mLoop; } - - void setQuitAfterRuns(bool quit_val) { mQuitAfterRuns = quit_val; } - void setNumRuns(S32 num_runs) { mNumRuns = num_runs; } - -private: - - - - bool mLoop; - bool mReplaySession; - - S32 mNumRuns; - bool mQuitAfterRuns; - - void setAutopilotTarget(const S32 id); - - bool mRecording; - F32 mLastRecordTime; - - bool mStarted; - bool mPlaying; - S32 mCurrentAction; - - bool mOverrideCamera; - - class Action - { - public: - - EActionType mType; - LLVector3d mTarget; - F64 mTime; - F32 mCameraView; - LLVector3 mCameraOrigin; - LLVector3 mCameraXAxis; - LLVector3 mCameraYAxis; - LLVector3 mCameraZAxis; - }; - - std::vector mActions; - LLTimer mTimer; - -}; - -extern LLAgentPilot gAgentPilot; - -#endif // LL_LLAGENTPILOT_H +/** + * @file llagentpilot.h + * @brief LLAgentPilot class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLAGENTPILOT_H +#define LL_LLAGENTPILOT_H + +#include "stdtypes.h" +#include "lltimer.h" +#include "v3dmath.h" + +// Class that drives the agent around according to a "script". + +class LLAgentPilot +{ +public: + enum EActionType + { + STRAIGHT, + TURN + }; + + LLAgentPilot(); + virtual ~LLAgentPilot(); + + void load(); + void loadTxt(const std::string& filename); + void loadXML(const std::string& filename); + void save(); + void saveTxt(const std::string& filename); + void saveXML(const std::string& filename); + + void startRecord(); + void stopRecord(); + void addAction(enum EActionType action); + + void startPlayback(); + void stopPlayback(); + + bool isRecording() { return mRecording; } + bool isPlaying() { return mPlaying; } + bool getOverrideCamera() { return mOverrideCamera; } + + void updateTarget(); + + void addWaypoint(); + void moveCamera(); + + void setReplaySession(bool new_val) { mReplaySession = new_val; } + bool getReplaySession() { return mReplaySession; } + + void setLoop(bool new_val) { mLoop = new_val; } + bool getLoop() { return mLoop; } + + void setQuitAfterRuns(bool quit_val) { mQuitAfterRuns = quit_val; } + void setNumRuns(S32 num_runs) { mNumRuns = num_runs; } + +private: + + + + bool mLoop; + bool mReplaySession; + + S32 mNumRuns; + bool mQuitAfterRuns; + + void setAutopilotTarget(const S32 id); + + bool mRecording; + F32 mLastRecordTime; + + bool mStarted; + bool mPlaying; + S32 mCurrentAction; + + bool mOverrideCamera; + + class Action + { + public: + + EActionType mType; + LLVector3d mTarget; + F64 mTime; + F32 mCameraView; + LLVector3 mCameraOrigin; + LLVector3 mCameraXAxis; + LLVector3 mCameraYAxis; + LLVector3 mCameraZAxis; + }; + + std::vector mActions; + LLTimer mTimer; + +}; + +extern LLAgentPilot gAgentPilot; + +#endif // LL_LLAGENTPILOT_H diff --git a/indra/newview/llagentui.cpp b/indra/newview/llagentui.cpp index ab71e70a8a..81a3942d8a 100644 --- a/indra/newview/llagentui.cpp +++ b/indra/newview/llagentui.cpp @@ -1,194 +1,194 @@ -/** - * @file llagentui.cpp - * @brief Utility methods to process agent's data as slurl's etc. before displaying - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llagentui.h" - -// Library includes -#include "llparcel.h" - -// Viewer includes -#include "llagent.h" -#include "llviewercontrol.h" -#include "llviewerregion.h" -#include "llviewerparcelmgr.h" -#include "llvoavatarself.h" -#include "llslurl.h" - -//static -void LLAgentUI::buildFullname(std::string& name) -{ - if (isAgentAvatarValid()) - name = gAgentAvatarp->getFullname(); -} - -//static -void LLAgentUI::buildSLURL(LLSLURL& slurl, const bool escaped /*= true*/) -{ - LLSLURL return_slurl; - LLViewerRegion *regionp = gAgent.getRegion(); - if (regionp) - { - // Make sure coordinates are within current region - LLVector3d global_pos = gAgent.getPositionGlobal(); - LLVector3d region_origin = regionp->getOriginGlobal(); - // -1 otherwise slurl will fmod 256 to 0. - // And valid slurl range is supposed to be 0..255 - F64 max_val = REGION_WIDTH_METERS - 1; - global_pos.mdV[VX] = llclamp(global_pos[VX], region_origin[VX], region_origin[VX] + max_val); - global_pos.mdV[VY] = llclamp(global_pos[VY], region_origin[VY], region_origin[VY] + max_val); - - return_slurl = LLSLURL(regionp->getName(), global_pos); - } - slurl = return_slurl; -} - -//static -bool LLAgentUI::checkAgentDistance(const LLVector3& pole, F32 radius) -{ - F32 delta_x = gAgent.getPositionAgent().mV[VX] - pole.mV[VX]; - F32 delta_y = gAgent.getPositionAgent().mV[VY] - pole.mV[VY]; - - return sqrt( delta_x* delta_x + delta_y* delta_y ) < radius; -} -bool LLAgentUI::buildLocationString(std::string& str, ELocationFormat fmt,const LLVector3& agent_pos_region) -{ - LLViewerRegion* region = gAgent.getRegion(); - LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - - if (!region || !parcel) return false; - - S32 pos_x = S32(agent_pos_region.mV[VX] + 0.5f); - S32 pos_y = S32(agent_pos_region.mV[VY] + 0.5f); - S32 pos_z = S32(agent_pos_region.mV[VZ] + 0.5f); - - // Round the numbers based on the velocity - F32 velocity_mag_sq = gAgent.getVelocity().magVecSquared(); - - const F32 FLY_CUTOFF = 6.f; // meters/sec - const F32 FLY_CUTOFF_SQ = FLY_CUTOFF * FLY_CUTOFF; - const F32 WALK_CUTOFF = 1.5f; // meters/sec - const F32 WALK_CUTOFF_SQ = WALK_CUTOFF * WALK_CUTOFF; - - if (velocity_mag_sq > FLY_CUTOFF_SQ) - { - pos_x -= pos_x % 4; - pos_y -= pos_y % 4; - } - else if (velocity_mag_sq > WALK_CUTOFF_SQ) - { - pos_x -= pos_x % 2; - pos_y -= pos_y % 2; - } - - // create a default name and description for the landmark - std::string parcel_name = LLViewerParcelMgr::getInstance()->getAgentParcelName(); - std::string region_name = region->getName(); - std::string sim_access_string = region->getSimAccessString(); - std::string buffer; - if( parcel_name.empty() ) - { - // the parcel doesn't have a name - switch (fmt) - { - case LOCATION_FORMAT_LANDMARK: - buffer = llformat("%.100s", region_name.c_str()); - break; - case LOCATION_FORMAT_NORMAL: - buffer = llformat("%s", region_name.c_str()); - break; - case LOCATION_FORMAT_NORMAL_COORDS: - buffer = llformat("%s (%d, %d, %d)", - region_name.c_str(), - pos_x, pos_y, pos_z); - break; - case LOCATION_FORMAT_NO_COORDS: - buffer = llformat("%s%s%s", - region_name.c_str(), - sim_access_string.empty() ? "" : " - ", - sim_access_string.c_str()); - break; - case LOCATION_FORMAT_NO_MATURITY: - buffer = llformat("%s (%d, %d, %d)", - region_name.c_str(), - pos_x, pos_y, pos_z); - break; - case LOCATION_FORMAT_FULL: - buffer = llformat("%s (%d, %d, %d)%s%s", - region_name.c_str(), - pos_x, pos_y, pos_z, - sim_access_string.empty() ? "" : " - ", - sim_access_string.c_str()); - break; - } - } - else - { - // the parcel has a name, so include it in the landmark name - switch (fmt) - { - case LOCATION_FORMAT_LANDMARK: - buffer = llformat("%.100s", parcel_name.c_str()); - break; - case LOCATION_FORMAT_NORMAL: - buffer = llformat("%s, %s", parcel_name.c_str(), region_name.c_str()); - break; - case LOCATION_FORMAT_NORMAL_COORDS: - buffer = llformat("%s (%d, %d, %d)", - parcel_name.c_str(), - pos_x, pos_y, pos_z); - break; - case LOCATION_FORMAT_NO_MATURITY: - buffer = llformat("%s, %s (%d, %d, %d)", - parcel_name.c_str(), - region_name.c_str(), - pos_x, pos_y, pos_z); - break; - case LOCATION_FORMAT_NO_COORDS: - buffer = llformat("%s, %s%s%s", - parcel_name.c_str(), - region_name.c_str(), - sim_access_string.empty() ? "" : " - ", - sim_access_string.c_str()); - break; - case LOCATION_FORMAT_FULL: - buffer = llformat("%s, %s (%d, %d, %d)%s%s", - parcel_name.c_str(), - region_name.c_str(), - pos_x, pos_y, pos_z, - sim_access_string.empty() ? "" : " - ", - sim_access_string.c_str()); - break; - } - } - str = buffer; - return true; -} -bool LLAgentUI::buildLocationString(std::string& str, ELocationFormat fmt) -{ - return buildLocationString(str,fmt, gAgent.getPositionAgent()); -} +/** + * @file llagentui.cpp + * @brief Utility methods to process agent's data as slurl's etc. before displaying + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llagentui.h" + +// Library includes +#include "llparcel.h" + +// Viewer includes +#include "llagent.h" +#include "llviewercontrol.h" +#include "llviewerregion.h" +#include "llviewerparcelmgr.h" +#include "llvoavatarself.h" +#include "llslurl.h" + +//static +void LLAgentUI::buildFullname(std::string& name) +{ + if (isAgentAvatarValid()) + name = gAgentAvatarp->getFullname(); +} + +//static +void LLAgentUI::buildSLURL(LLSLURL& slurl, const bool escaped /*= true*/) +{ + LLSLURL return_slurl; + LLViewerRegion *regionp = gAgent.getRegion(); + if (regionp) + { + // Make sure coordinates are within current region + LLVector3d global_pos = gAgent.getPositionGlobal(); + LLVector3d region_origin = regionp->getOriginGlobal(); + // -1 otherwise slurl will fmod 256 to 0. + // And valid slurl range is supposed to be 0..255 + F64 max_val = REGION_WIDTH_METERS - 1; + global_pos.mdV[VX] = llclamp(global_pos[VX], region_origin[VX], region_origin[VX] + max_val); + global_pos.mdV[VY] = llclamp(global_pos[VY], region_origin[VY], region_origin[VY] + max_val); + + return_slurl = LLSLURL(regionp->getName(), global_pos); + } + slurl = return_slurl; +} + +//static +bool LLAgentUI::checkAgentDistance(const LLVector3& pole, F32 radius) +{ + F32 delta_x = gAgent.getPositionAgent().mV[VX] - pole.mV[VX]; + F32 delta_y = gAgent.getPositionAgent().mV[VY] - pole.mV[VY]; + + return sqrt( delta_x* delta_x + delta_y* delta_y ) < radius; +} +bool LLAgentUI::buildLocationString(std::string& str, ELocationFormat fmt,const LLVector3& agent_pos_region) +{ + LLViewerRegion* region = gAgent.getRegion(); + LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + + if (!region || !parcel) return false; + + S32 pos_x = S32(agent_pos_region.mV[VX] + 0.5f); + S32 pos_y = S32(agent_pos_region.mV[VY] + 0.5f); + S32 pos_z = S32(agent_pos_region.mV[VZ] + 0.5f); + + // Round the numbers based on the velocity + F32 velocity_mag_sq = gAgent.getVelocity().magVecSquared(); + + const F32 FLY_CUTOFF = 6.f; // meters/sec + const F32 FLY_CUTOFF_SQ = FLY_CUTOFF * FLY_CUTOFF; + const F32 WALK_CUTOFF = 1.5f; // meters/sec + const F32 WALK_CUTOFF_SQ = WALK_CUTOFF * WALK_CUTOFF; + + if (velocity_mag_sq > FLY_CUTOFF_SQ) + { + pos_x -= pos_x % 4; + pos_y -= pos_y % 4; + } + else if (velocity_mag_sq > WALK_CUTOFF_SQ) + { + pos_x -= pos_x % 2; + pos_y -= pos_y % 2; + } + + // create a default name and description for the landmark + std::string parcel_name = LLViewerParcelMgr::getInstance()->getAgentParcelName(); + std::string region_name = region->getName(); + std::string sim_access_string = region->getSimAccessString(); + std::string buffer; + if( parcel_name.empty() ) + { + // the parcel doesn't have a name + switch (fmt) + { + case LOCATION_FORMAT_LANDMARK: + buffer = llformat("%.100s", region_name.c_str()); + break; + case LOCATION_FORMAT_NORMAL: + buffer = llformat("%s", region_name.c_str()); + break; + case LOCATION_FORMAT_NORMAL_COORDS: + buffer = llformat("%s (%d, %d, %d)", + region_name.c_str(), + pos_x, pos_y, pos_z); + break; + case LOCATION_FORMAT_NO_COORDS: + buffer = llformat("%s%s%s", + region_name.c_str(), + sim_access_string.empty() ? "" : " - ", + sim_access_string.c_str()); + break; + case LOCATION_FORMAT_NO_MATURITY: + buffer = llformat("%s (%d, %d, %d)", + region_name.c_str(), + pos_x, pos_y, pos_z); + break; + case LOCATION_FORMAT_FULL: + buffer = llformat("%s (%d, %d, %d)%s%s", + region_name.c_str(), + pos_x, pos_y, pos_z, + sim_access_string.empty() ? "" : " - ", + sim_access_string.c_str()); + break; + } + } + else + { + // the parcel has a name, so include it in the landmark name + switch (fmt) + { + case LOCATION_FORMAT_LANDMARK: + buffer = llformat("%.100s", parcel_name.c_str()); + break; + case LOCATION_FORMAT_NORMAL: + buffer = llformat("%s, %s", parcel_name.c_str(), region_name.c_str()); + break; + case LOCATION_FORMAT_NORMAL_COORDS: + buffer = llformat("%s (%d, %d, %d)", + parcel_name.c_str(), + pos_x, pos_y, pos_z); + break; + case LOCATION_FORMAT_NO_MATURITY: + buffer = llformat("%s, %s (%d, %d, %d)", + parcel_name.c_str(), + region_name.c_str(), + pos_x, pos_y, pos_z); + break; + case LOCATION_FORMAT_NO_COORDS: + buffer = llformat("%s, %s%s%s", + parcel_name.c_str(), + region_name.c_str(), + sim_access_string.empty() ? "" : " - ", + sim_access_string.c_str()); + break; + case LOCATION_FORMAT_FULL: + buffer = llformat("%s, %s (%d, %d, %d)%s%s", + parcel_name.c_str(), + region_name.c_str(), + pos_x, pos_y, pos_z, + sim_access_string.empty() ? "" : " - ", + sim_access_string.c_str()); + break; + } + } + str = buffer; + return true; +} +bool LLAgentUI::buildLocationString(std::string& str, ELocationFormat fmt) +{ + return buildLocationString(str,fmt, gAgent.getPositionAgent()); +} diff --git a/indra/newview/llagentui.h b/indra/newview/llagentui.h index b382744739..aae55d6444 100644 --- a/indra/newview/llagentui.h +++ b/indra/newview/llagentui.h @@ -1,59 +1,59 @@ -/** - * @file llagentui.h - * @brief Utility methods to process agent's data as slurl's etc. before displaying - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLAGENTUI_H -#define LLAGENTUI_H - -class LLSLURL; - -class LLAgentUI -{ -public: - enum ELocationFormat - { - LOCATION_FORMAT_NORMAL, // Parcel - LOCATION_FORMAT_NORMAL_COORDS, // Parcel (x, y, z) - LOCATION_FORMAT_LANDMARK, // Parcel, Region - LOCATION_FORMAT_NO_MATURITY, // Parcel, Region (x, y, z) - LOCATION_FORMAT_NO_COORDS, // Parcel, Region - Maturity - LOCATION_FORMAT_FULL, // Parcel, Region (x, y, z) - Maturity - }; - - static void buildFullname(std::string &name); - - static void buildSLURL(LLSLURL& slurl, const bool escaped = true); - //build location string using the current position of gAgent. - static bool buildLocationString(std::string& str, ELocationFormat fmt = LOCATION_FORMAT_LANDMARK); - //build location string using a region position of the avatar. - static bool buildLocationString(std::string& str, ELocationFormat fmt,const LLVector3& agent_pos_region); - /** - * @brief Check whether the agent is in neighborhood of the pole Within same region - * @return true if the agent is in neighborhood. - */ - static bool checkAgentDistance(const LLVector3& local_pole, F32 radius); -}; - -#endif //LLAGENTUI_H +/** + * @file llagentui.h + * @brief Utility methods to process agent's data as slurl's etc. before displaying + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLAGENTUI_H +#define LLAGENTUI_H + +class LLSLURL; + +class LLAgentUI +{ +public: + enum ELocationFormat + { + LOCATION_FORMAT_NORMAL, // Parcel + LOCATION_FORMAT_NORMAL_COORDS, // Parcel (x, y, z) + LOCATION_FORMAT_LANDMARK, // Parcel, Region + LOCATION_FORMAT_NO_MATURITY, // Parcel, Region (x, y, z) + LOCATION_FORMAT_NO_COORDS, // Parcel, Region - Maturity + LOCATION_FORMAT_FULL, // Parcel, Region (x, y, z) - Maturity + }; + + static void buildFullname(std::string &name); + + static void buildSLURL(LLSLURL& slurl, const bool escaped = true); + //build location string using the current position of gAgent. + static bool buildLocationString(std::string& str, ELocationFormat fmt = LOCATION_FORMAT_LANDMARK); + //build location string using a region position of the avatar. + static bool buildLocationString(std::string& str, ELocationFormat fmt,const LLVector3& agent_pos_region); + /** + * @brief Check whether the agent is in neighborhood of the pole Within same region + * @return true if the agent is in neighborhood. + */ + static bool checkAgentDistance(const LLVector3& local_pole, F32 radius); +}; + +#endif //LLAGENTUI_H diff --git a/indra/newview/llagentwearables.cpp b/indra/newview/llagentwearables.cpp index 2409b12090..46f7d84ace 100644 --- a/indra/newview/llagentwearables.cpp +++ b/indra/newview/llagentwearables.cpp @@ -1,1656 +1,1656 @@ -/** - * @file llagentwearables.cpp - * @brief LLAgentWearables class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llagentwearables.h" - -#include "llattachmentsmgr.h" -#include "llaccordionctrltab.h" -#include "llagent.h" -#include "llagentcamera.h" -#include "llappearancemgr.h" -#include "llcallbacklist.h" -#include "llfloatersidepanelcontainer.h" -#include "llgesturemgr.h" -#include "llinventorybridge.h" -#include "llinventoryfunctions.h" -#include "llinventorymodelbackgroundfetch.h" -#include "llinventoryobserver.h" -#include "llinventorypanel.h" -#include "lllocaltextureobject.h" -#include "llnotificationsutil.h" -#include "lloutfitobserver.h" -#include "llsidepanelappearance.h" -#include "lltexlayer.h" -#include "lltooldraganddrop.h" -#include "llviewerregion.h" -#include "llvoavatarself.h" -#include "llviewerwearable.h" -#include "llwearablelist.h" -#include "llfloaterperms.h" - -#include - -LLAgentWearables gAgentWearables; - -bool LLAgentWearables::mInitialWearablesUpdateReceived = false; - -using namespace LLAvatarAppearanceDefines; - -/////////////////////////////////////////////////////////////////////////////// - -void set_default_permissions(LLViewerInventoryItem* item) -{ - llassert(item); - LLPermissions perm = item->getPermissions(); - if (perm.getMaskNextOwner() != LLFloaterPerms::getNextOwnerPerms("Wearables") - || perm.getMaskEveryone() != LLFloaterPerms::getEveryonePerms("Wearables") - || perm.getMaskGroup() != LLFloaterPerms::getGroupPerms("Wearables")) - { - perm.setMaskNext(LLFloaterPerms::getNextOwnerPerms("Wearables")); - perm.setMaskEveryone(LLFloaterPerms::getEveryonePerms("Wearables")); - perm.setMaskGroup(LLFloaterPerms::getGroupPerms("Wearables")); - - item->setPermissions(perm); - - item->updateServer(false); - } -} - -// Callback to wear and start editing an item that has just been created. -void wear_and_edit_cb(const LLUUID& inv_item) -{ - if (inv_item.isNull()) return; - - LLViewerInventoryItem* item = gInventory.getItem(inv_item); - if (!item) return; - - set_default_permissions(item); - - // item was just created, update even if permissions did not changed - gInventory.updateItem(item); - gInventory.notifyObservers(); - - // Request editing the item after it gets worn. - gAgentWearables.requestEditingWearable(inv_item); - - // Wear it. - LLAppearanceMgr::instance().wearItemOnAvatar(inv_item,true); -} - -void wear_cb(const LLUUID& inv_item) -{ - if (!inv_item.isNull()) - { - LLViewerInventoryItem* item = gInventory.getItem(inv_item); - if (item) - { - set_default_permissions(item); - - gInventory.updateItem(item); - gInventory.notifyObservers(); - } - } -} - -/////////////////////////////////////////////////////////////////////////////// - -// HACK: For EXT-3923: Pants item shows in inventory with skin icon and messes with "current look" -// Some db items are corrupted, have inventory flags = 0, implying wearable type = shape, even though -// wearable type stored in asset is some other value. -// Calling this function whenever a wearable is added to increase visibility if this problem -// turns up in other inventories. -void checkWearableAgainstInventory(LLViewerWearable *wearable) -{ - if (wearable->getItemID().isNull()) - return; - - // Check for wearable type consistent with inventory item wearable type. - LLViewerInventoryItem *item = gInventory.getItem(wearable->getItemID()); - if (item) - { - if (!item->isWearableType()) - { - LL_WARNS() << "wearable associated with non-wearable item" << LL_ENDL; - } - if (item->getWearableType() != wearable->getType()) - { - LL_WARNS() << "type mismatch: wearable " << wearable->getName() - << " has type " << wearable->getType() - << " but inventory item " << item->getName() - << " has type " << item->getWearableType() << LL_ENDL; - } - } - else - { - LL_WARNS() << "wearable inventory item not found" << wearable->getName() - << " itemID " << wearable->getItemID().asString() << LL_ENDL; - } -} - -void LLAgentWearables::dump() -{ - LL_INFOS() << "LLAgentWearablesDump" << LL_ENDL; - for (S32 i = 0; i < LLWearableType::WT_COUNT; i++) - { - U32 count = getWearableCount((LLWearableType::EType)i); - LL_INFOS() << "Type: " << i << " count " << count << LL_ENDL; - for (U32 j=0; jgetName() - << " description " << wearable->getDescription() << LL_ENDL; - - } - } -} - -struct LLAgentDumper -{ - LLAgentDumper(std::string name): - mName(name) - { - LL_INFOS() << LL_ENDL; - LL_INFOS() << "LLAgentDumper " << mName << LL_ENDL; - gAgentWearables.dump(); - } - - ~LLAgentDumper() - { - LL_INFOS() << LL_ENDL; - LL_INFOS() << "~LLAgentDumper " << mName << LL_ENDL; - gAgentWearables.dump(); - } - - std::string mName; -}; - -LLAgentWearables::LLAgentWearables() : - LLWearableData(), - mWearablesLoaded(false) -, mCOFChangeInProgress(false) -{ -} - -LLAgentWearables::~LLAgentWearables() -{ - cleanup(); -} - -void LLAgentWearables::cleanup() -{ -} - -// static -void LLAgentWearables::initClass() -{ - // this can not be called from constructor because its instance is global and is created too early. - // Subscribe to "COF is Saved" signal to notify observers about this (Loading indicator for ex.). - LLOutfitObserver::instance().addCOFSavedCallback(boost::bind(&LLAgentWearables::notifyLoadingFinished, &gAgentWearables)); -} - -void LLAgentWearables::setAvatarObject(LLVOAvatarSelf *avatar) -{ - llassert(avatar); - setAvatarAppearance(avatar); -} - -/** - * @brief Construct a callback for dealing with the wearables. - * - * Would like to pass the agent in here, but we can't safely - * count on it being around later. Just use gAgent directly. - * @param cb callback to execute on completion (? unused ?) - * @param type Type for the wearable in the agent - * @param wearable The wearable data. - * @param todo Bitmask of actions to take on completion. - */ -LLAgentWearables::AddWearableToAgentInventoryCallback::AddWearableToAgentInventoryCallback( - LLPointer cb, LLWearableType::EType type, U32 index, LLViewerWearable* wearable, U32 todo, const std::string description) : - mType(type), - mIndex(index), - mWearable(wearable), - mTodo(todo), - mCB(cb), - mDescription(description) -{ - LL_INFOS() << "constructor" << LL_ENDL; -} - -void LLAgentWearables::AddWearableToAgentInventoryCallback::fire(const LLUUID& inv_item) -{ - if (inv_item.isNull()) - return; - - gAgentWearables.addWearabletoAgentInventoryDone(mType, mIndex, inv_item, mWearable); - - /* - * Do this for every one in the loop - */ - if (mTodo & CALL_MAKENEWOUTFITDONE) - { - gAgentWearables.makeNewOutfitDone(mType, mIndex); - } - if (mTodo & CALL_WEARITEM) - { - LLAppearanceMgr::instance().addCOFItemLink(inv_item, - new LLUpdateAppearanceAndEditWearableOnDestroy(inv_item), mDescription); - editWearable(inv_item); - } -} - -void LLAgentWearables::addWearabletoAgentInventoryDone(const LLWearableType::EType type, - const U32 index, - const LLUUID& item_id, - LLViewerWearable* wearable) -{ - LL_INFOS() << "type " << type << " index " << index << " item " << item_id.asString() << LL_ENDL; - - if (item_id.isNull()) - return; - - LLUUID old_item_id = getWearableItemID(type,index); - - if (wearable) - { - wearable->setItemID(item_id); - - if (old_item_id.notNull()) - { - gInventory.addChangedMask(LLInventoryObserver::LABEL, old_item_id); - setWearable(type,index,wearable); - } - else - { - pushWearable(type,wearable); - } - } - - gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id); - - LLViewerInventoryItem* item = gInventory.getItem(item_id); - if (item && wearable) - { - // We're changing the asset id, so we both need to set it - // locally via setAssetUUID() and via setTransactionID() which - // will be decoded on the server. JC - item->setAssetUUID(wearable->getAssetID()); - item->setTransactionID(wearable->getTransactionID()); - gInventory.addChangedMask(LLInventoryObserver::INTERNAL, item_id); - item->updateServer(false); - } - gInventory.notifyObservers(); -} - -void LLAgentWearables::saveWearable(const LLWearableType::EType type, const U32 index, - const std::string new_name) -{ - LLViewerWearable* old_wearable = getViewerWearable(type, index); - if(!old_wearable) return; - bool name_changed = !new_name.empty() && (new_name != old_wearable->getName()); - if (name_changed || old_wearable->isDirty() || old_wearable->isOldVersion()) - { - LLUUID old_item_id = old_wearable->getItemID(); - LLViewerWearable* new_wearable = LLWearableList::instance().createCopy(old_wearable); - new_wearable->setItemID(old_item_id); // should this be in LLViewerWearable::copyDataFrom()? - setWearable(type,index,new_wearable); - - // old_wearable may still be referred to by other inventory items. Revert - // unsaved changes so other inventory items aren't affected by the changes - // that were just saved. - old_wearable->revertValues(); - - LLInventoryItem* item = gInventory.getItem(old_item_id); - if (item) - { - std::string item_name = item->getName(); - if (name_changed) - { - LL_INFOS() << "saveWearable changing name from " << item->getName() << " to " << new_name << LL_ENDL; - item_name = new_name; - } - // Update existing inventory item - LLPointer template_item = - new LLViewerInventoryItem(item->getUUID(), - item->getParentUUID(), - item->getPermissions(), - new_wearable->getAssetID(), - new_wearable->getAssetType(), - item->getInventoryType(), - item_name, - item->getDescription(), - item->getSaleInfo(), - item->getFlags(), - item->getCreationDate()); - template_item->setTransactionID(new_wearable->getTransactionID()); - update_inventory_item(template_item, gAgentAvatarp->mEndCustomizeCallback); - } - else - { - // Add a new inventory item (shouldn't ever happen here) - U32 todo = AddWearableToAgentInventoryCallback::CALL_NONE; - LLPointer cb = - new AddWearableToAgentInventoryCallback( - LLPointer(NULL), - type, - index, - new_wearable, - todo); - addWearableToAgentInventory(cb, new_wearable); - return; - } - - gAgentAvatarp->wearableUpdated(type); - } -} - -void LLAgentWearables::saveWearableAs(const LLWearableType::EType type, - const U32 index, - const std::string& new_name, - const std::string& description, - bool save_in_lost_and_found) -{ - if (!isWearableCopyable(type, index)) - { - LL_WARNS() << "LLAgent::saveWearableAs() not copyable." << LL_ENDL; - return; - } - LLViewerWearable* old_wearable = getViewerWearable(type, index); - if (!old_wearable) - { - LL_WARNS() << "LLAgent::saveWearableAs() no old wearable." << LL_ENDL; - return; - } - - LLInventoryItem* item = gInventory.getItem(getWearableItemID(type,index)); - if (!item) - { - LL_WARNS() << "LLAgent::saveWearableAs() no inventory item." << LL_ENDL; - return; - } - std::string trunc_name(new_name); - LLStringUtil::truncate(trunc_name, DB_INV_ITEM_NAME_STR_LEN); - LLViewerWearable* new_wearable = LLWearableList::instance().createCopy( - old_wearable, - trunc_name); - - LLPointer cb = - new AddWearableToAgentInventoryCallback( - LLPointer(NULL), - type, - index, - new_wearable, - AddWearableToAgentInventoryCallback::CALL_WEARITEM, - description - ); - LLUUID category_id; - if (save_in_lost_and_found) - { - category_id = gInventory.findCategoryUUIDForType( - LLFolderType::FT_LOST_AND_FOUND); - } - else - { - // put in same folder as original - category_id = item->getParentUUID(); - } - - copy_inventory_item( - gAgent.getID(), - item->getPermissions().getOwner(), - item->getUUID(), - category_id, - new_name, - cb); - - // old_wearable may still be referred to by other inventory items. Revert - // unsaved changes so other inventory items aren't affected by the changes - // that were just saved. - old_wearable->revertValuesWithoutUpdate(); -} - -void LLAgentWearables::revertWearable(const LLWearableType::EType type, const U32 index) -{ - LLViewerWearable* wearable = getViewerWearable(type, index); - llassert(wearable); - if (wearable) - { - wearable->revertValues(); - } -} - -void LLAgentWearables::saveAllWearables() -{ - //if (!gInventory.isLoaded()) - //{ - // return; - //} - - for (S32 i=0; i < LLWearableType::WT_COUNT; i++) - { - for (U32 j=0; j < getWearableCount((LLWearableType::EType)i); j++) - saveWearable((LLWearableType::EType)i, j); - } -} - -// Called when the user changes the name of a wearable inventory item that is currently being worn. -void LLAgentWearables::setWearableName(const LLUUID& item_id, const std::string& new_name) -{ - for (S32 i=0; i < LLWearableType::WT_COUNT; i++) - { - for (U32 j=0; j < getWearableCount((LLWearableType::EType)i); j++) - { - LLUUID curr_item_id = getWearableItemID((LLWearableType::EType)i,j); - if (curr_item_id == item_id) - { - LLViewerWearable* old_wearable = getViewerWearable((LLWearableType::EType)i,j); - llassert(old_wearable); - if (!old_wearable) continue; - - std::string old_name = old_wearable->getName(); - old_wearable->setName(new_name); - LLViewerWearable* new_wearable = LLWearableList::instance().createCopy(old_wearable); - new_wearable->setItemID(item_id); - LLInventoryItem* item = gInventory.getItem(item_id); - if (item) - { - new_wearable->setPermissions(item->getPermissions()); - } - old_wearable->setName(old_name); - - setWearable((LLWearableType::EType)i,j,new_wearable); - break; - } - } - } -} - - -bool LLAgentWearables::isWearableModifiable(LLWearableType::EType type, U32 index) const -{ - LLUUID item_id = getWearableItemID(type, index); - return item_id.notNull() ? isWearableModifiable(item_id) : false; -} - -bool LLAgentWearables::isWearableModifiable(const LLUUID& item_id) const -{ - const LLUUID& linked_id = gInventory.getLinkedItemID(item_id); - if (linked_id.notNull()) - { - LLInventoryItem* item = gInventory.getItem(linked_id); - if (item && item->getPermissions().allowModifyBy(gAgent.getID(), - gAgent.getGroupID())) - { - return true; - } - } - return false; -} - -bool LLAgentWearables::isWearableCopyable(LLWearableType::EType type, U32 index) const -{ - LLUUID item_id = getWearableItemID(type, index); - if (!item_id.isNull()) - { - LLInventoryItem* item = gInventory.getItem(item_id); - if (item && item->getPermissions().allowCopyBy(gAgent.getID(), - gAgent.getGroupID())) - { - return true; - } - } - return false; -} - -LLInventoryItem* LLAgentWearables::getWearableInventoryItem(LLWearableType::EType type, U32 index) -{ - LLUUID item_id = getWearableItemID(type,index); - LLInventoryItem* item = NULL; - if (item_id.notNull()) - { - item = gInventory.getItem(item_id); - } - return item; -} - -const LLViewerWearable* LLAgentWearables::getWearableFromItemID(const LLUUID& item_id) const -{ - const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id); - for (S32 i=0; i < LLWearableType::WT_COUNT; i++) - { - for (U32 j=0; j < getWearableCount((LLWearableType::EType)i); j++) - { - const LLViewerWearable * curr_wearable = getViewerWearable((LLWearableType::EType)i, j); - if (curr_wearable && (curr_wearable->getItemID() == base_item_id)) - { - return curr_wearable; - } - } - } - return NULL; -} - -LLViewerWearable* LLAgentWearables::getWearableFromItemID(const LLUUID& item_id) -{ - const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id); - for (S32 i=0; i < LLWearableType::WT_COUNT; i++) - { - for (U32 j=0; j < getWearableCount((LLWearableType::EType)i); j++) - { - LLViewerWearable * curr_wearable = getViewerWearable((LLWearableType::EType)i, j); - if (curr_wearable && (curr_wearable->getItemID() == base_item_id)) - { - return curr_wearable; - } - } - } - return NULL; -} - -LLViewerWearable* LLAgentWearables::getWearableFromAssetID(const LLUUID& asset_id) -{ - for (S32 i=0; i < LLWearableType::WT_COUNT; i++) - { - for (U32 j=0; j < getWearableCount((LLWearableType::EType)i); j++) - { - LLViewerWearable * curr_wearable = getViewerWearable((LLWearableType::EType)i, j); - if (curr_wearable && (curr_wearable->getAssetID() == asset_id)) - { - return curr_wearable; - } - } - } - return NULL; -} - -LLViewerWearable* LLAgentWearables::getViewerWearable(const LLWearableType::EType type, U32 index /*= 0*/) -{ - return dynamic_cast (getWearable(type, index)); -} - -const LLViewerWearable* LLAgentWearables::getViewerWearable(const LLWearableType::EType type, U32 index /*= 0*/) const -{ - return dynamic_cast (getWearable(type, index)); -} - -// static -bool LLAgentWearables::selfHasWearable(LLWearableType::EType type) -{ - return (gAgentWearables.getWearableCount(type) > 0); -} - -// virtual -void LLAgentWearables::wearableUpdated(LLWearable *wearable, bool removed) -{ - if (isAgentAvatarValid()) - { - gAgentAvatarp->wearableUpdated(wearable->getType()); - } - - LLWearableData::wearableUpdated(wearable, removed); - - if (!removed) - { - LLViewerWearable* viewer_wearable = dynamic_cast(wearable); - viewer_wearable->refreshName(); - - // Hack pt 2. If the wearable we just loaded has definition version 24, - // then force a re-save of this wearable after slamming the version number to 22. - // This number was incorrectly incremented for internal builds before release, and - // this fix will ensure that the affected wearables are re-saved with the right version number. - // the versions themselves are compatible. This code can be removed before release. - if( wearable->getDefinitionVersion() == 24 ) - { - U32 index; - if (getWearableIndex(wearable,index)) - { - LL_INFOS() << "forcing wearable type " << wearable->getType() << " to version 22 from 24" << LL_ENDL; - wearable->setDefinitionVersion(22); - saveWearable(wearable->getType(),index); - } - } - - checkWearableAgainstInventory(viewer_wearable); - } -} - -const LLUUID LLAgentWearables::getWearableItemID(LLWearableType::EType type, U32 index) const -{ - const LLViewerWearable *wearable = getViewerWearable(type,index); - if (wearable) - return wearable->getItemID(); - else - return LLUUID(); -} - -const LLUUID LLAgentWearables::getWearableAssetID(LLWearableType::EType type, U32 index) const -{ - const LLViewerWearable *wearable = getViewerWearable(type,index); - if (wearable) - return wearable->getAssetID(); - else - return LLUUID(); -} - -bool LLAgentWearables::isWearingItem(const LLUUID& item_id) const -{ - return getWearableFromItemID(item_id) != nullptr; -} - -void LLAgentWearables::addLocalTextureObject(const LLWearableType::EType wearable_type, const LLAvatarAppearanceDefines::ETextureIndex texture_type, U32 wearable_index) -{ - LLViewerWearable* wearable = getViewerWearable((LLWearableType::EType)wearable_type, wearable_index); - if (!wearable) - { - LL_ERRS() << "Tried to add local texture object to invalid wearable with type " << wearable_type << " and index " << wearable_index << LL_ENDL; - return; - } - LLLocalTextureObject lto; - wearable->setLocalTextureObject(texture_type, lto); -} - -class OnWearableItemCreatedCB: public LLInventoryCallback -{ -public: - OnWearableItemCreatedCB(): - mWearablesAwaitingItems(LLWearableType::WT_COUNT,NULL) - { - LL_INFOS() << "created callback" << LL_ENDL; - } - /* virtual */ void fire(const LLUUID& inv_item) - { - LL_INFOS() << "One item created " << inv_item.asString() << LL_ENDL; - LLConstPointer item = gInventory.getItem(inv_item); - mItemsToLink.push_back(item); - updatePendingWearable(inv_item); - } - ~OnWearableItemCreatedCB() - { - LL_INFOS() << "All items created" << LL_ENDL; - LLPointer link_waiter = new LLUpdateAppearanceOnDestroy; - link_inventory_array(LLAppearanceMgr::instance().getCOF(), - mItemsToLink, - link_waiter); - } - void addPendingWearable(LLViewerWearable *wearable) - { - if (!wearable) - { - LL_WARNS() << "no wearable" << LL_ENDL; - return; - } - LLWearableType::EType type = wearable->getType(); - if (typeisWearableType()) - { - LL_WARNS() << "non-wearable item found" << LL_ENDL; - return; - } - if (item && item->isWearableType()) - { - LLWearableType::EType type = item->getWearableType(); - if (type < LLWearableType::WT_COUNT) - { - LLViewerWearable *wearable = mWearablesAwaitingItems[type]; - if (wearable) - wearable->setItemID(inv_item); - } - else - { - LL_WARNS() << "invalid wearable type " << type << LL_ENDL; - } - } - } - -private: - LLInventoryObject::const_object_list_t mItemsToLink; - std::vector mWearablesAwaitingItems; -}; - -void LLAgentWearables::createStandardWearables() -{ - LL_WARNS() << "Creating standard wearables" << LL_ENDL; - - if (!isAgentAvatarValid()) return; - - constexpr bool create[LLWearableType::WT_COUNT] = - { - true, //LLWearableType::WT_SHAPE - true, //LLWearableType::WT_SKIN - true, //LLWearableType::WT_HAIR - true, //LLWearableType::WT_EYES - true, //LLWearableType::WT_SHIRT - true, //LLWearableType::WT_PANTS - true, //LLWearableType::WT_SHOES - true, //LLWearableType::WT_SOCKS - false, //LLWearableType::WT_JACKET - false, //LLWearableType::WT_GLOVES - true, //LLWearableType::WT_UNDERSHIRT - true, //LLWearableType::WT_UNDERPANTS - false //LLWearableType::WT_SKIRT - }; - - LLPointer cb = new OnWearableItemCreatedCB; - for (S32 i=0; i < LLWearableType::WT_COUNT; i++) - { - if (create[i]) - { - llassert(getWearableCount((LLWearableType::EType)i) == 0); - LLViewerWearable* wearable = LLWearableList::instance().createNewWearable((LLWearableType::EType)i, gAgentAvatarp); - ((OnWearableItemCreatedCB*)(&(*cb)))->addPendingWearable(wearable); - // no need to update here... - LLUUID category_id = LLUUID::null; - create_inventory_wearable(gAgent.getID(), - gAgent.getSessionID(), - category_id, - wearable->getTransactionID(), - wearable->getName(), - wearable->getDescription(), - wearable->getAssetType(), - wearable->getType(), - wearable->getPermissions().getMaskNextOwner(), - cb); - } - } -} - -// We no longer need this message in the current viewer, but send -// it for now to maintain compatibility with release viewers. Can -// remove this function once the SH-3455 changesets are universally deployed. -void LLAgentWearables::sendDummyAgentWearablesUpdate() -{ - LL_DEBUGS("Avatar") << "sendAgentWearablesUpdate()" << LL_ENDL; - - // Send the AgentIsNowWearing - gMessageSystem->newMessageFast(_PREHASH_AgentIsNowWearing); - - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - - // Send 4 standardized nonsense item ids (same as returned by the modified sim, not that it especially matters). - gMessageSystem->nextBlockFast(_PREHASH_WearableData); - gMessageSystem->addU8Fast(_PREHASH_WearableType, U8(1)); - gMessageSystem->addUUIDFast(_PREHASH_ItemID, LLUUID("db5a4e5f-9da3-44c8-992d-1181c5795498")); - - gMessageSystem->nextBlockFast(_PREHASH_WearableData); - gMessageSystem->addU8Fast(_PREHASH_WearableType, U8(2)); - gMessageSystem->addUUIDFast(_PREHASH_ItemID, LLUUID("6969c7cc-f72f-4a76-a19b-c293cce8ce4f")); - - gMessageSystem->nextBlockFast(_PREHASH_WearableData); - gMessageSystem->addU8Fast(_PREHASH_WearableType, U8(3)); - gMessageSystem->addUUIDFast(_PREHASH_ItemID, LLUUID("7999702b-b291-48f9-8903-c91dfb828408")); - - gMessageSystem->nextBlockFast(_PREHASH_WearableData); - gMessageSystem->addU8Fast(_PREHASH_WearableType, U8(4)); - gMessageSystem->addUUIDFast(_PREHASH_ItemID, LLUUID("566cb59e-ef60-41d7-bfa6-e0f293fbea40")); - - gAgent.sendReliableMessage(); -} - -void LLAgentWearables::makeNewOutfitDone(S32 type, U32 index) -{ - LLUUID first_item_id = getWearableItemID((LLWearableType::EType)type, index); - // Open the inventory and select the first item we added. - if (first_item_id.notNull()) - { - LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(); - if (active_panel) - { - active_panel->setSelection(first_item_id, TAKE_FOCUS_NO); - } - } -} - - -void LLAgentWearables::addWearableToAgentInventory(LLPointer cb, - LLViewerWearable* wearable, - const LLUUID& category_id, - bool notify) -{ - create_inventory_wearable(gAgent.getID(), - gAgent.getSessionID(), - category_id, - wearable->getTransactionID(), - wearable->getName(), - wearable->getDescription(), - wearable->getAssetType(), - wearable->getType(), - wearable->getPermissions().getMaskNextOwner(), - cb); -} - -void LLAgentWearables::removeWearable(const LLWearableType::EType type, bool do_remove_all, U32 index) -{ - if (getWearableCount(type) == 0) - { - // no wearables to remove - return; - } - - if (do_remove_all) - { - removeWearableFinal(type, do_remove_all, index); - } - else - { - LLViewerWearable* old_wearable = getViewerWearable(type,index); - - if (old_wearable) - { - if (old_wearable->isDirty()) - { - LLSD payload; - payload["wearable_type"] = (S32)type; - payload["wearable_index"] = (S32)index; - // Bring up view-modal dialog: Save changes? Yes, No, Cancel - LLNotificationsUtil::add("WearableSave", LLSD(), payload, &LLAgentWearables::onRemoveWearableDialog); - return; - } - else - { - removeWearableFinal(type, do_remove_all, index); - } - } - } -} - - -// static -bool LLAgentWearables::onRemoveWearableDialog(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - LLWearableType::EType type = (LLWearableType::EType)notification["payload"]["wearable_type"].asInteger(); - S32 index = (S32)notification["payload"]["wearable_index"].asInteger(); - switch(option) - { - case 0: // "Save" - gAgentWearables.saveWearable(type, index); - gAgentWearables.removeWearableFinal(type, false, index); - break; - - case 1: // "Don't Save" - gAgentWearables.removeWearableFinal(type, false, index); - break; - - case 2: // "Cancel" - break; - - default: - llassert(0); - break; - } - return false; -} - -// Called by removeWearable() and onRemoveWearableDialog() to actually do the removal. -void LLAgentWearables::removeWearableFinal(const LLWearableType::EType type, bool do_remove_all, U32 index) -{ - //LLAgentDumper dumper("removeWearable"); - if (do_remove_all) - { - S32 max_entry = getWearableCount(type)-1; - for (S32 i=max_entry; i>=0; i--) - { - LLViewerWearable* old_wearable = getViewerWearable(type,i); - if (old_wearable) - { - eraseWearable(old_wearable); - old_wearable->removeFromAvatar(); - } - } - clearWearableType(type); - } - else - { - LLViewerWearable* old_wearable = getViewerWearable(type, index); - - if (old_wearable) - { - eraseWearable(old_wearable); - old_wearable->removeFromAvatar(); - } - } - - gInventory.notifyObservers(); -} - -// Assumes existing wearables are not dirty. -void LLAgentWearables::setWearableOutfit(const LLInventoryItem::item_array_t& items, - const std::vector< LLViewerWearable* >& wearables) -{ - LL_INFOS() << "setWearableOutfit() start" << LL_ENDL; - - S32 count = wearables.size(); - llassert(items.size() == count); - - // Check for whether outfit already matches the one requested - S32 matched = 0, mismatched = 0; - const S32 arr_size = LLWearableType::WT_COUNT; - S32 type_counts[arr_size]; - bool update_inventory{ false }; - std::fill(type_counts,type_counts+arr_size,0); - for (S32 i = 0; i < count; i++) - { - LLViewerWearable* new_wearable = wearables[i]; - LLPointer new_item = items[i]; - - const LLWearableType::EType type = new_wearable->getType(); - if (type < 0 || type>=LLWearableType::WT_COUNT) - { - LL_WARNS() << "invalid type " << type << LL_ENDL; - mismatched++; - continue; - } - S32 index = type_counts[type]; - type_counts[type]++; - - LLViewerWearable *curr_wearable = dynamic_cast(getWearable(type,index)); - if (!new_wearable || !curr_wearable || - new_wearable->getAssetID() != curr_wearable->getAssetID()) - { - LL_DEBUGS("Avatar") << "mismatch, type " << type << " index " << index - << " names " << (curr_wearable ? curr_wearable->getName() : "NONE") << "," - << " names " << (new_wearable ? new_wearable->getName() : "NONE") << LL_ENDL; - mismatched++; - continue; - } - - // Update only inventory in this case - ordering of wearables with the same asset id has no effect. - // Updating wearables in this case causes the two-alphas error in MAINT-4158. - // We should actually disallow wearing two wearables with the same asset id. - if (curr_wearable->getName() != new_item->getName() || - curr_wearable->getItemID() != new_item->getUUID()) - { - LL_DEBUGS("Avatar") << "mismatch on name or inventory id, names " - << curr_wearable->getName() << " vs " << new_item->getName() - << " item ids " << curr_wearable->getItemID() << " vs " << new_item->getUUID() - << LL_ENDL; - update_inventory = true; - continue; - } - // If we got here, everything matches. - matched++; - } - LL_DEBUGS("Avatar") << "matched " << matched << " mismatched " << mismatched << LL_ENDL; - for (S32 j=0; jgetAssetType((LLWearableType::EType)j) == LLAssetType::AT_CLOTHING) - { - removeWearable((LLWearableType::EType)j, true, 0); - } - } - - for (S32 i = 0; i < count; i++) - { - LLViewerWearable* new_wearable = wearables[i]; - LLPointer new_item = items[i]; - - llassert(new_wearable); - if (new_wearable) - { - const LLWearableType::EType type = new_wearable->getType(); - - LLUUID old_wearable_id = new_wearable->getItemID(); - new_wearable->setName(new_item->getName()); - new_wearable->setItemID(new_item->getUUID()); - - if (wearable_type_inst->getAssetType(type) == LLAssetType::AT_BODYPART) - { - // exactly one wearable per body part - setWearable(type,0,new_wearable); - if (old_wearable_id.notNull()) - { - // we changed id before setting wearable, update old item manually - // to complete the swap. - gInventory.addChangedMask(LLInventoryObserver::LABEL, old_wearable_id); - } - } - else - { - pushWearable(type,new_wearable); - } - - constexpr bool removed = false; - wearableUpdated(new_wearable, removed); - } - } - - gInventory.notifyObservers(); - - if (mismatched == 0) - { - LL_DEBUGS("Avatar") << "inventory updated, wearable assets not changed, bailing out" << LL_ENDL; - notifyLoadingFinished(); - return; - } - - // updating agent avatar - - if (isAgentAvatarValid()) - { - gAgentAvatarp->setCompositeUpdatesEnabled(true); - - // If we have not yet declouded, we may want to use - // baked texture UUIDs sent from the first objectUpdate message - // don't overwrite these. If we have already declouded, we've saved - // these ids as the last known good textures and can invalidate without - // re-clouding. - if (!gAgentAvatarp->getIsCloud()) - { - gAgentAvatarp->invalidateAll(); - } - } - - // Start rendering & update the server - mWearablesLoaded = true; - - notifyLoadingFinished(); - - // Copy wearable params to avatar. - gAgentAvatarp->writeWearablesToAvatar(); - - // Then update the avatar based on the copied params. - gAgentAvatarp->updateVisualParams(); - - gAgentAvatarp->dumpAvatarTEs("setWearableOutfit"); - - LL_DEBUGS("Avatar") << "setWearableOutfit() end" << LL_ENDL; -} - - -// User has picked "wear on avatar" from a menu. -void LLAgentWearables::setWearableItem(LLInventoryItem* new_item, LLViewerWearable* new_wearable, bool do_append) -{ - //LLAgentDumper dumper("setWearableItem"); - if (isWearingItem(new_item->getUUID())) - { - LL_WARNS() << "wearable " << new_item->getUUID() << " is already worn" << LL_ENDL; - return; - } - - const LLWearableType::EType type = new_wearable->getType(); - - if (!do_append) - { - // Remove old wearable, if any - // MULTI_WEARABLE: hardwired to 0 - LLViewerWearable* old_wearable = getViewerWearable(type,0); - if (old_wearable) - { - const LLUUID& old_item_id = old_wearable->getItemID(); - if ((old_wearable->getAssetID() == new_wearable->getAssetID()) && - (old_item_id == new_item->getUUID())) - { - LL_DEBUGS() << "No change to wearable asset and item: " << LLWearableType::getInstance()->getTypeName(type) << LL_ENDL; - return; - } - - if (old_wearable->isDirty()) - { - // Bring up modal dialog: Save changes? Yes, No, Cancel - LLSD payload; - payload["item_id"] = new_item->getUUID(); - LLNotificationsUtil::add("WearableSave", LLSD(), payload, boost::bind(onSetWearableDialog, _1, _2, new_wearable)); - return; - } - } - } - - setWearableFinal(new_item, new_wearable, do_append); -} - -// static -bool LLAgentWearables::onSetWearableDialog(const LLSD& notification, const LLSD& response, LLViewerWearable* wearable) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - LLInventoryItem* new_item = gInventory.getItem(notification["payload"]["item_id"].asUUID()); - U32 index; - if (!gAgentWearables.getWearableIndex(wearable,index)) - { - LL_WARNS() << "Wearable not found" << LL_ENDL; - delete wearable; - return false; - } - if (!new_item) - { - delete wearable; - return false; - } - - switch(option) - { - case 0: // "Save" - gAgentWearables.saveWearable(wearable->getType(),index); - gAgentWearables.setWearableFinal(new_item, wearable); - break; - - case 1: // "Don't Save" - gAgentWearables.setWearableFinal(new_item, wearable); - break; - - case 2: // "Cancel" - break; - - default: - llassert(0); - break; - } - - delete wearable; - return false; -} - -// Called from setWearableItem() and onSetWearableDialog() to actually set the wearable. -// MULTI_WEARABLE: unify code after null objects are gone. -void LLAgentWearables::setWearableFinal(LLInventoryItem* new_item, LLViewerWearable* new_wearable, bool do_append) -{ - const LLWearableType::EType type = new_wearable->getType(); - - if (do_append && getWearableItemID(type,0).notNull()) - { - new_wearable->setItemID(new_item->getUUID()); - const bool trigger_updated = false; - pushWearable(type, new_wearable, trigger_updated); - LL_INFOS() << "Added additional wearable for type " << type - << " size is now " << getWearableCount(type) << LL_ENDL; - checkWearableAgainstInventory(new_wearable); - } - else - { - // Replace the old wearable with a new one. - llassert(new_item->getAssetUUID() == new_wearable->getAssetID()); - - LLViewerWearable *old_wearable = getViewerWearable(type,0); - LLUUID old_item_id; - if (old_wearable) - { - old_item_id = old_wearable->getItemID(); - } - new_wearable->setItemID(new_item->getUUID()); - setWearable(type,0,new_wearable); - - if (old_item_id.notNull()) - { - gInventory.addChangedMask(LLInventoryObserver::LABEL, old_item_id); - gInventory.notifyObservers(); - } - LL_INFOS() << "Replaced current element 0 for type " << type - << " size is now " << getWearableCount(type) << LL_ENDL; - } -} - -// User has picked "remove from avatar" from a menu. -// static -void LLAgentWearables::userRemoveWearable(const LLWearableType::EType &type, const U32 &index) -{ - if (!(type==LLWearableType::WT_SHAPE || type==LLWearableType::WT_SKIN || type==LLWearableType::WT_HAIR || type==LLWearableType::WT_EYES)) //&& - //!((!gAgent.isTeen()) && (type==LLWearableType::WT_UNDERPANTS || type==LLWearableType::WT_UNDERSHIRT))) - { - gAgentWearables.removeWearable(type,false,index); - } -} - -//static -void LLAgentWearables::userRemoveWearablesOfType(const LLWearableType::EType &type) -{ - if (!(type==LLWearableType::WT_SHAPE || type==LLWearableType::WT_SKIN || type==LLWearableType::WT_HAIR || type==LLWearableType::WT_EYES)) //&& - //!((!gAgent.isTeen()) && (type==LLWearableType::WT_UNDERPANTS || type==LLWearableType::WT_UNDERSHIRT))) - { - gAgentWearables.removeWearable(type,true,0); - } -} - -// Given a desired set of attachments, find what objects need to be -// removed, and what additional inventory items need to be added. -void LLAgentWearables::findAttachmentsAddRemoveInfo(LLInventoryModel::item_array_t& obj_item_array, - llvo_vec_t& objects_to_remove, - llvo_vec_t& objects_to_retain, - LLInventoryModel::item_array_t& items_to_add) -{ - // Possible cases: - // already wearing but not in request set -> take off. - // already wearing and in request set -> leave alone. - // not wearing and in request set -> put on. - - if (!isAgentAvatarValid()) return; - - std::set requested_item_ids; - std::set current_item_ids; - for (S32 i=0; igetLinkedUUID(); - //LL_INFOS() << "Requested attachment id " << requested_id << LL_ENDL; - requested_item_ids.insert(requested_id); - } - - // Build up list of objects to be removed and items currently attached. - LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); - LLVOAvatar::attachment_map_t::iterator end = gAgentAvatarp->mAttachmentPoints.end(); - while (iter != end) - { - LLVOAvatar::attachment_map_t::iterator curiter = iter++; - LLViewerJointAttachment* attachment = curiter->second; - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - LLViewerObject *objectp = attachment_iter->get(); - if (objectp) - { - LLUUID object_item_id = objectp->getAttachmentItemID(); - - bool remove_attachment = true; - if (requested_item_ids.find(object_item_id) != requested_item_ids.end()) - { // Object currently worn, was requested to keep it - // Flag as currently worn so we won't have to add it again. - remove_attachment = false; - } - else if (objectp->isTempAttachment()) - { // Check if we should keep this temp attachment - remove_attachment = LLAppearanceMgr::instance().shouldRemoveTempAttachment(objectp->getID()); - } - - if (remove_attachment) - { - // LL_INFOS() << "found object to remove, id " << objectp->getID() << ", item " << objectp->getAttachmentItemID() << LL_ENDL; - objects_to_remove.push_back(objectp); - } - else - { - // LL_INFOS() << "found object to keep, id " << objectp->getID() << ", item " << objectp->getAttachmentItemID() << LL_ENDL; - current_item_ids.insert(object_item_id); - objects_to_retain.push_back(objectp); - } - } - } - } - - for (LLInventoryModel::item_array_t::iterator it = obj_item_array.begin(); - it != obj_item_array.end(); - ++it) - { - LLUUID linked_id = (*it).get()->getLinkedUUID(); - if (current_item_ids.find(linked_id) != current_item_ids.end()) - { - // Requested attachment is already worn. - } - else - { - // Requested attachment is not worn yet. - items_to_add.push_back(*it); - } - } - // S32 remove_count = objects_to_remove.size(); - // S32 add_count = items_to_add.size(); - // LL_INFOS() << "remove " << remove_count << " add " << add_count << LL_ENDL; -} - -std::vector LLAgentWearables::getTempAttachments() -{ - llvo_vec_t temp_attachs; - if (isAgentAvatarValid()) - { - for (LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); iter != gAgentAvatarp->mAttachmentPoints.end();) - { - LLVOAvatar::attachment_map_t::iterator curiter = iter++; - LLViewerJointAttachment* attachment = curiter->second; - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - LLViewerObject *objectp = attachment_iter->get(); - if (objectp && objectp->isTempAttachment()) - { - temp_attachs.push_back(objectp); - } - } - } - } - return temp_attachs; -} - -void LLAgentWearables::userRemoveMultipleAttachments(llvo_vec_t& objects_to_remove) -{ - if (!isAgentAvatarValid()) return; - - if (objects_to_remove.empty()) - return; - - LL_DEBUGS("Avatar") << "ATT [ObjectDetach] removing " << objects_to_remove.size() << " objects" << LL_ENDL; - gMessageSystem->newMessage("ObjectDetach"); - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - - for (llvo_vec_t::iterator it = objects_to_remove.begin(); - it != objects_to_remove.end(); - ++it) - { - LLViewerObject *objectp = *it; - //gAgentAvatarp->resetJointPositionsOnDetach(objectp); - gMessageSystem->nextBlockFast(_PREHASH_ObjectData); - gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, objectp->getLocalID()); - const LLUUID& item_id = objectp->getAttachmentItemID(); - LLViewerInventoryItem *item = gInventory.getItem(item_id); - LL_DEBUGS("Avatar") << "ATT removing object, item is " << (item ? item->getName() : "UNKNOWN") << " " << item_id << LL_ENDL; - LLAttachmentsMgr::instance().onDetachRequested(item_id); - } - gMessageSystem->sendReliable(gAgent.getRegionHost()); -} - -void LLAgentWearables::userAttachMultipleAttachments(LLInventoryModel::item_array_t& obj_item_array) -{ - // Build a compound message to send all the objects that need to be rezzed. - S32 obj_count = obj_item_array.size(); - if (obj_count > 0) - { - LL_DEBUGS("Avatar") << "ATT attaching multiple, total obj_count " << obj_count << LL_ENDL; - } - - for(LLInventoryModel::item_array_t::const_iterator it = obj_item_array.begin(); - it != obj_item_array.end(); - ++it) - { - const LLInventoryItem* item = *it; - LLAttachmentsMgr::instance().addAttachmentRequest(item->getLinkedUUID(), 0, true); - } -} - -// Returns false if the given wearable is already topmost/bottommost -// (depending on closer_to_body parameter). -bool LLAgentWearables::canMoveWearable(const LLUUID& item_id, bool closer_to_body) const -{ - const LLWearable* wearable = getWearableFromItemID(item_id); - if (!wearable) return false; - - LLWearableType::EType wtype = wearable->getType(); - const LLWearable* marginal_wearable = closer_to_body ? getBottomWearable(wtype) : getTopWearable(wtype); - if (!marginal_wearable) return false; - - return wearable != marginal_wearable; -} - -bool LLAgentWearables::areWearablesLoaded() const -{ - return mWearablesLoaded; -} - -bool LLAgentWearables::canWearableBeRemoved(const LLViewerWearable* wearable) const -{ - if (!wearable) return false; - - LLWearableType::EType type = wearable->getType(); - // Make sure the user always has at least one shape, skin, eyes, and hair type currently worn. - return !(((type == LLWearableType::WT_SHAPE) || (type == LLWearableType::WT_SKIN) || (type == LLWearableType::WT_HAIR) || (type == LLWearableType::WT_EYES)) - && (getWearableCount(type) <= 1) ); -} -void LLAgentWearables::animateAllWearableParams(F32 delta) -{ - for( S32 type = 0; type < LLWearableType::WT_COUNT; ++type ) - { - for (S32 count = 0; count < (S32)getWearableCount((LLWearableType::EType)type); ++count) - { - LLViewerWearable *wearable = getViewerWearable((LLWearableType::EType)type,count); - llassert(wearable); - if (wearable) - { - wearable->animateParams(delta); - } - } - } -} - -bool LLAgentWearables::moveWearable(const LLViewerInventoryItem* item, bool closer_to_body) -{ - if (!item) return false; - if (!item->isWearableType()) return false; - - LLWearableType::EType type = item->getWearableType(); - U32 wearable_count = getWearableCount(type); - if (0 == wearable_count) return false; - - const LLUUID& asset_id = item->getAssetUUID(); - - //nowhere to move if the wearable is already on any boundary (closest to the body/furthest from the body) - if (closer_to_body) - { - LLViewerWearable* bottom_wearable = dynamic_cast( getBottomWearable(type) ); - if (bottom_wearable->getAssetID() == asset_id) - { - return false; - } - } - else // !closer_to_body - { - LLViewerWearable* top_wearable = dynamic_cast( getTopWearable(type) ); - if (top_wearable->getAssetID() == asset_id) - { - return false; - } - } - - for (U32 i = 0; i < wearable_count; ++i) - { - LLViewerWearable* wearable = getViewerWearable(type, i); - if (!wearable) continue; - if (wearable->getAssetID() != asset_id) continue; - - //swapping wearables - U32 swap_i = closer_to_body ? i-1 : i+1; - swapWearables(type, i, swap_i); - return true; - } - - return false; -} - -// static -void LLAgentWearables::createWearable(LLWearableType::EType type, bool wear, const LLUUID& parent_id, std::function created_cb) -{ - if (type == LLWearableType::WT_INVALID || type == LLWearableType::WT_NONE) return; - - if (type == LLWearableType::WT_UNIVERSAL && !gAgent.getRegion()->bakesOnMeshEnabled()) - { - LL_WARNS("Inventory") << "Can't create WT_UNIVERSAL type " << LL_ENDL; - return; - } - - LLViewerWearable* wearable = LLWearableList::instance().createNewWearable(type, gAgentAvatarp); - LLAssetType::EType asset_type = wearable->getAssetType(); - LLPointer cb; - if(wear) - { - cb = new LLBoostFuncInventoryCallback(wear_and_edit_cb); - } - else - { - cb = new LLBoostFuncInventoryCallback(wear_cb); - } - if (created_cb != NULL) - { - cb->addOnFireFunc(created_cb); - } - - LLUUID folder_id; - - if (parent_id.notNull()) - { - folder_id = parent_id; - } - else - { - LLFolderType::EType folder_type = LLFolderType::assetTypeToFolderType(asset_type); - folder_id = gInventory.findCategoryUUIDForType(folder_type); - } - - create_inventory_wearable(gAgent.getID(), - gAgent.getSessionID(), - folder_id, - wearable->getTransactionID(), - wearable->getName(), - wearable->getDescription(), - asset_type, - wearable->getType(), - LLFloaterPerms::getNextOwnerPerms("Wearables"), - cb); -} - -// static -void LLAgentWearables::editWearable(const LLUUID& item_id) -{ - LLViewerInventoryItem* item = gInventory.getLinkedItem(item_id); - if (!item) - { - LL_WARNS() << "Failed to get linked item" << LL_ENDL; - return; - } - - if (!item->isFinished()) - { - LL_WARNS() << "Tried to edit wearable that isn't loaded" << LL_ENDL; - // Restart fetch or put item to the front - LLInventoryModelBackgroundFetch::instance().start(item->getUUID(), false); - return; - } - - LLViewerWearable* wearable = gAgentWearables.getWearableFromItemID(item_id); - if (!wearable) - { - LL_WARNS() << "Cannot get wearable" << LL_ENDL; - return; - } - - if (!gAgentWearables.isWearableModifiable(item->getUUID())) - { - LL_WARNS() << "Cannot modify wearable" << LL_ENDL; - return; - } - - S32 shape_count = gAgentWearables.getWearableCount(LLWearableType::WT_SHAPE); - S32 hair_count = gAgentWearables.getWearableCount(LLWearableType::WT_HAIR); - S32 eye_count = gAgentWearables.getWearableCount(LLWearableType::WT_EYES); - S32 skin_count = gAgentWearables.getWearableCount(LLWearableType::WT_SKIN); - if (!shape_count || !hair_count || !eye_count || !skin_count) - { - // Don't let user edit wearables if avatar is cloud due to missing parts. - // Let user edit wearables if avatar is cloud due to missing textures. - LL_WARNS() << "Cannot modify wearable. Avatar is cloud and missing parts." << LL_ENDL; - return; - } - - const bool disable_camera_switch = LLWearableType::getInstance()->getDisableCameraSwitch(wearable->getType()); - LLPanel* panel = LLFloaterSidePanelContainer::getPanel("appearance"); - LLSidepanelAppearance::editWearable(wearable, panel, disable_camera_switch); -} - -// Request editing the item after it gets worn. -void LLAgentWearables::requestEditingWearable(const LLUUID& item_id) -{ - mItemToEdit = gInventory.getLinkedItemID(item_id); -} - -// Start editing the item if previously requested. -void LLAgentWearables::editWearableIfRequested(const LLUUID& item_id) -{ - if (mItemToEdit.notNull() && - mItemToEdit == gInventory.getLinkedItemID(item_id)) - { - LLAgentWearables::editWearable(item_id); - mItemToEdit.setNull(); - } -} - -boost::signals2::connection LLAgentWearables::addLoadingStartedCallback(loading_started_callback_t cb) -{ - return mLoadingStartedSignal.connect(cb); -} - -boost::signals2::connection LLAgentWearables::addLoadedCallback(loaded_callback_t cb) -{ - return mLoadedSignal.connect(cb); -} - -bool LLAgentWearables::changeInProgress() const -{ - return mCOFChangeInProgress; -} - -void LLAgentWearables::notifyLoadingStarted() -{ - mCOFChangeInProgress = true; - mCOFChangeTimer.reset(); - mLoadingStartedSignal(); -} - -void LLAgentWearables::notifyLoadingFinished() -{ - mCOFChangeInProgress = false; - mLoadedSignal(); -} -// EOF +/** + * @file llagentwearables.cpp + * @brief LLAgentWearables class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llagentwearables.h" + +#include "llattachmentsmgr.h" +#include "llaccordionctrltab.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llappearancemgr.h" +#include "llcallbacklist.h" +#include "llfloatersidepanelcontainer.h" +#include "llgesturemgr.h" +#include "llinventorybridge.h" +#include "llinventoryfunctions.h" +#include "llinventorymodelbackgroundfetch.h" +#include "llinventoryobserver.h" +#include "llinventorypanel.h" +#include "lllocaltextureobject.h" +#include "llnotificationsutil.h" +#include "lloutfitobserver.h" +#include "llsidepanelappearance.h" +#include "lltexlayer.h" +#include "lltooldraganddrop.h" +#include "llviewerregion.h" +#include "llvoavatarself.h" +#include "llviewerwearable.h" +#include "llwearablelist.h" +#include "llfloaterperms.h" + +#include + +LLAgentWearables gAgentWearables; + +bool LLAgentWearables::mInitialWearablesUpdateReceived = false; + +using namespace LLAvatarAppearanceDefines; + +/////////////////////////////////////////////////////////////////////////////// + +void set_default_permissions(LLViewerInventoryItem* item) +{ + llassert(item); + LLPermissions perm = item->getPermissions(); + if (perm.getMaskNextOwner() != LLFloaterPerms::getNextOwnerPerms("Wearables") + || perm.getMaskEveryone() != LLFloaterPerms::getEveryonePerms("Wearables") + || perm.getMaskGroup() != LLFloaterPerms::getGroupPerms("Wearables")) + { + perm.setMaskNext(LLFloaterPerms::getNextOwnerPerms("Wearables")); + perm.setMaskEveryone(LLFloaterPerms::getEveryonePerms("Wearables")); + perm.setMaskGroup(LLFloaterPerms::getGroupPerms("Wearables")); + + item->setPermissions(perm); + + item->updateServer(false); + } +} + +// Callback to wear and start editing an item that has just been created. +void wear_and_edit_cb(const LLUUID& inv_item) +{ + if (inv_item.isNull()) return; + + LLViewerInventoryItem* item = gInventory.getItem(inv_item); + if (!item) return; + + set_default_permissions(item); + + // item was just created, update even if permissions did not changed + gInventory.updateItem(item); + gInventory.notifyObservers(); + + // Request editing the item after it gets worn. + gAgentWearables.requestEditingWearable(inv_item); + + // Wear it. + LLAppearanceMgr::instance().wearItemOnAvatar(inv_item,true); +} + +void wear_cb(const LLUUID& inv_item) +{ + if (!inv_item.isNull()) + { + LLViewerInventoryItem* item = gInventory.getItem(inv_item); + if (item) + { + set_default_permissions(item); + + gInventory.updateItem(item); + gInventory.notifyObservers(); + } + } +} + +/////////////////////////////////////////////////////////////////////////////// + +// HACK: For EXT-3923: Pants item shows in inventory with skin icon and messes with "current look" +// Some db items are corrupted, have inventory flags = 0, implying wearable type = shape, even though +// wearable type stored in asset is some other value. +// Calling this function whenever a wearable is added to increase visibility if this problem +// turns up in other inventories. +void checkWearableAgainstInventory(LLViewerWearable *wearable) +{ + if (wearable->getItemID().isNull()) + return; + + // Check for wearable type consistent with inventory item wearable type. + LLViewerInventoryItem *item = gInventory.getItem(wearable->getItemID()); + if (item) + { + if (!item->isWearableType()) + { + LL_WARNS() << "wearable associated with non-wearable item" << LL_ENDL; + } + if (item->getWearableType() != wearable->getType()) + { + LL_WARNS() << "type mismatch: wearable " << wearable->getName() + << " has type " << wearable->getType() + << " but inventory item " << item->getName() + << " has type " << item->getWearableType() << LL_ENDL; + } + } + else + { + LL_WARNS() << "wearable inventory item not found" << wearable->getName() + << " itemID " << wearable->getItemID().asString() << LL_ENDL; + } +} + +void LLAgentWearables::dump() +{ + LL_INFOS() << "LLAgentWearablesDump" << LL_ENDL; + for (S32 i = 0; i < LLWearableType::WT_COUNT; i++) + { + U32 count = getWearableCount((LLWearableType::EType)i); + LL_INFOS() << "Type: " << i << " count " << count << LL_ENDL; + for (U32 j=0; jgetName() + << " description " << wearable->getDescription() << LL_ENDL; + + } + } +} + +struct LLAgentDumper +{ + LLAgentDumper(std::string name): + mName(name) + { + LL_INFOS() << LL_ENDL; + LL_INFOS() << "LLAgentDumper " << mName << LL_ENDL; + gAgentWearables.dump(); + } + + ~LLAgentDumper() + { + LL_INFOS() << LL_ENDL; + LL_INFOS() << "~LLAgentDumper " << mName << LL_ENDL; + gAgentWearables.dump(); + } + + std::string mName; +}; + +LLAgentWearables::LLAgentWearables() : + LLWearableData(), + mWearablesLoaded(false) +, mCOFChangeInProgress(false) +{ +} + +LLAgentWearables::~LLAgentWearables() +{ + cleanup(); +} + +void LLAgentWearables::cleanup() +{ +} + +// static +void LLAgentWearables::initClass() +{ + // this can not be called from constructor because its instance is global and is created too early. + // Subscribe to "COF is Saved" signal to notify observers about this (Loading indicator for ex.). + LLOutfitObserver::instance().addCOFSavedCallback(boost::bind(&LLAgentWearables::notifyLoadingFinished, &gAgentWearables)); +} + +void LLAgentWearables::setAvatarObject(LLVOAvatarSelf *avatar) +{ + llassert(avatar); + setAvatarAppearance(avatar); +} + +/** + * @brief Construct a callback for dealing with the wearables. + * + * Would like to pass the agent in here, but we can't safely + * count on it being around later. Just use gAgent directly. + * @param cb callback to execute on completion (? unused ?) + * @param type Type for the wearable in the agent + * @param wearable The wearable data. + * @param todo Bitmask of actions to take on completion. + */ +LLAgentWearables::AddWearableToAgentInventoryCallback::AddWearableToAgentInventoryCallback( + LLPointer cb, LLWearableType::EType type, U32 index, LLViewerWearable* wearable, U32 todo, const std::string description) : + mType(type), + mIndex(index), + mWearable(wearable), + mTodo(todo), + mCB(cb), + mDescription(description) +{ + LL_INFOS() << "constructor" << LL_ENDL; +} + +void LLAgentWearables::AddWearableToAgentInventoryCallback::fire(const LLUUID& inv_item) +{ + if (inv_item.isNull()) + return; + + gAgentWearables.addWearabletoAgentInventoryDone(mType, mIndex, inv_item, mWearable); + + /* + * Do this for every one in the loop + */ + if (mTodo & CALL_MAKENEWOUTFITDONE) + { + gAgentWearables.makeNewOutfitDone(mType, mIndex); + } + if (mTodo & CALL_WEARITEM) + { + LLAppearanceMgr::instance().addCOFItemLink(inv_item, + new LLUpdateAppearanceAndEditWearableOnDestroy(inv_item), mDescription); + editWearable(inv_item); + } +} + +void LLAgentWearables::addWearabletoAgentInventoryDone(const LLWearableType::EType type, + const U32 index, + const LLUUID& item_id, + LLViewerWearable* wearable) +{ + LL_INFOS() << "type " << type << " index " << index << " item " << item_id.asString() << LL_ENDL; + + if (item_id.isNull()) + return; + + LLUUID old_item_id = getWearableItemID(type,index); + + if (wearable) + { + wearable->setItemID(item_id); + + if (old_item_id.notNull()) + { + gInventory.addChangedMask(LLInventoryObserver::LABEL, old_item_id); + setWearable(type,index,wearable); + } + else + { + pushWearable(type,wearable); + } + } + + gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id); + + LLViewerInventoryItem* item = gInventory.getItem(item_id); + if (item && wearable) + { + // We're changing the asset id, so we both need to set it + // locally via setAssetUUID() and via setTransactionID() which + // will be decoded on the server. JC + item->setAssetUUID(wearable->getAssetID()); + item->setTransactionID(wearable->getTransactionID()); + gInventory.addChangedMask(LLInventoryObserver::INTERNAL, item_id); + item->updateServer(false); + } + gInventory.notifyObservers(); +} + +void LLAgentWearables::saveWearable(const LLWearableType::EType type, const U32 index, + const std::string new_name) +{ + LLViewerWearable* old_wearable = getViewerWearable(type, index); + if(!old_wearable) return; + bool name_changed = !new_name.empty() && (new_name != old_wearable->getName()); + if (name_changed || old_wearable->isDirty() || old_wearable->isOldVersion()) + { + LLUUID old_item_id = old_wearable->getItemID(); + LLViewerWearable* new_wearable = LLWearableList::instance().createCopy(old_wearable); + new_wearable->setItemID(old_item_id); // should this be in LLViewerWearable::copyDataFrom()? + setWearable(type,index,new_wearable); + + // old_wearable may still be referred to by other inventory items. Revert + // unsaved changes so other inventory items aren't affected by the changes + // that were just saved. + old_wearable->revertValues(); + + LLInventoryItem* item = gInventory.getItem(old_item_id); + if (item) + { + std::string item_name = item->getName(); + if (name_changed) + { + LL_INFOS() << "saveWearable changing name from " << item->getName() << " to " << new_name << LL_ENDL; + item_name = new_name; + } + // Update existing inventory item + LLPointer template_item = + new LLViewerInventoryItem(item->getUUID(), + item->getParentUUID(), + item->getPermissions(), + new_wearable->getAssetID(), + new_wearable->getAssetType(), + item->getInventoryType(), + item_name, + item->getDescription(), + item->getSaleInfo(), + item->getFlags(), + item->getCreationDate()); + template_item->setTransactionID(new_wearable->getTransactionID()); + update_inventory_item(template_item, gAgentAvatarp->mEndCustomizeCallback); + } + else + { + // Add a new inventory item (shouldn't ever happen here) + U32 todo = AddWearableToAgentInventoryCallback::CALL_NONE; + LLPointer cb = + new AddWearableToAgentInventoryCallback( + LLPointer(NULL), + type, + index, + new_wearable, + todo); + addWearableToAgentInventory(cb, new_wearable); + return; + } + + gAgentAvatarp->wearableUpdated(type); + } +} + +void LLAgentWearables::saveWearableAs(const LLWearableType::EType type, + const U32 index, + const std::string& new_name, + const std::string& description, + bool save_in_lost_and_found) +{ + if (!isWearableCopyable(type, index)) + { + LL_WARNS() << "LLAgent::saveWearableAs() not copyable." << LL_ENDL; + return; + } + LLViewerWearable* old_wearable = getViewerWearable(type, index); + if (!old_wearable) + { + LL_WARNS() << "LLAgent::saveWearableAs() no old wearable." << LL_ENDL; + return; + } + + LLInventoryItem* item = gInventory.getItem(getWearableItemID(type,index)); + if (!item) + { + LL_WARNS() << "LLAgent::saveWearableAs() no inventory item." << LL_ENDL; + return; + } + std::string trunc_name(new_name); + LLStringUtil::truncate(trunc_name, DB_INV_ITEM_NAME_STR_LEN); + LLViewerWearable* new_wearable = LLWearableList::instance().createCopy( + old_wearable, + trunc_name); + + LLPointer cb = + new AddWearableToAgentInventoryCallback( + LLPointer(NULL), + type, + index, + new_wearable, + AddWearableToAgentInventoryCallback::CALL_WEARITEM, + description + ); + LLUUID category_id; + if (save_in_lost_and_found) + { + category_id = gInventory.findCategoryUUIDForType( + LLFolderType::FT_LOST_AND_FOUND); + } + else + { + // put in same folder as original + category_id = item->getParentUUID(); + } + + copy_inventory_item( + gAgent.getID(), + item->getPermissions().getOwner(), + item->getUUID(), + category_id, + new_name, + cb); + + // old_wearable may still be referred to by other inventory items. Revert + // unsaved changes so other inventory items aren't affected by the changes + // that were just saved. + old_wearable->revertValuesWithoutUpdate(); +} + +void LLAgentWearables::revertWearable(const LLWearableType::EType type, const U32 index) +{ + LLViewerWearable* wearable = getViewerWearable(type, index); + llassert(wearable); + if (wearable) + { + wearable->revertValues(); + } +} + +void LLAgentWearables::saveAllWearables() +{ + //if (!gInventory.isLoaded()) + //{ + // return; + //} + + for (S32 i=0; i < LLWearableType::WT_COUNT; i++) + { + for (U32 j=0; j < getWearableCount((LLWearableType::EType)i); j++) + saveWearable((LLWearableType::EType)i, j); + } +} + +// Called when the user changes the name of a wearable inventory item that is currently being worn. +void LLAgentWearables::setWearableName(const LLUUID& item_id, const std::string& new_name) +{ + for (S32 i=0; i < LLWearableType::WT_COUNT; i++) + { + for (U32 j=0; j < getWearableCount((LLWearableType::EType)i); j++) + { + LLUUID curr_item_id = getWearableItemID((LLWearableType::EType)i,j); + if (curr_item_id == item_id) + { + LLViewerWearable* old_wearable = getViewerWearable((LLWearableType::EType)i,j); + llassert(old_wearable); + if (!old_wearable) continue; + + std::string old_name = old_wearable->getName(); + old_wearable->setName(new_name); + LLViewerWearable* new_wearable = LLWearableList::instance().createCopy(old_wearable); + new_wearable->setItemID(item_id); + LLInventoryItem* item = gInventory.getItem(item_id); + if (item) + { + new_wearable->setPermissions(item->getPermissions()); + } + old_wearable->setName(old_name); + + setWearable((LLWearableType::EType)i,j,new_wearable); + break; + } + } + } +} + + +bool LLAgentWearables::isWearableModifiable(LLWearableType::EType type, U32 index) const +{ + LLUUID item_id = getWearableItemID(type, index); + return item_id.notNull() ? isWearableModifiable(item_id) : false; +} + +bool LLAgentWearables::isWearableModifiable(const LLUUID& item_id) const +{ + const LLUUID& linked_id = gInventory.getLinkedItemID(item_id); + if (linked_id.notNull()) + { + LLInventoryItem* item = gInventory.getItem(linked_id); + if (item && item->getPermissions().allowModifyBy(gAgent.getID(), + gAgent.getGroupID())) + { + return true; + } + } + return false; +} + +bool LLAgentWearables::isWearableCopyable(LLWearableType::EType type, U32 index) const +{ + LLUUID item_id = getWearableItemID(type, index); + if (!item_id.isNull()) + { + LLInventoryItem* item = gInventory.getItem(item_id); + if (item && item->getPermissions().allowCopyBy(gAgent.getID(), + gAgent.getGroupID())) + { + return true; + } + } + return false; +} + +LLInventoryItem* LLAgentWearables::getWearableInventoryItem(LLWearableType::EType type, U32 index) +{ + LLUUID item_id = getWearableItemID(type,index); + LLInventoryItem* item = NULL; + if (item_id.notNull()) + { + item = gInventory.getItem(item_id); + } + return item; +} + +const LLViewerWearable* LLAgentWearables::getWearableFromItemID(const LLUUID& item_id) const +{ + const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id); + for (S32 i=0; i < LLWearableType::WT_COUNT; i++) + { + for (U32 j=0; j < getWearableCount((LLWearableType::EType)i); j++) + { + const LLViewerWearable * curr_wearable = getViewerWearable((LLWearableType::EType)i, j); + if (curr_wearable && (curr_wearable->getItemID() == base_item_id)) + { + return curr_wearable; + } + } + } + return NULL; +} + +LLViewerWearable* LLAgentWearables::getWearableFromItemID(const LLUUID& item_id) +{ + const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id); + for (S32 i=0; i < LLWearableType::WT_COUNT; i++) + { + for (U32 j=0; j < getWearableCount((LLWearableType::EType)i); j++) + { + LLViewerWearable * curr_wearable = getViewerWearable((LLWearableType::EType)i, j); + if (curr_wearable && (curr_wearable->getItemID() == base_item_id)) + { + return curr_wearable; + } + } + } + return NULL; +} + +LLViewerWearable* LLAgentWearables::getWearableFromAssetID(const LLUUID& asset_id) +{ + for (S32 i=0; i < LLWearableType::WT_COUNT; i++) + { + for (U32 j=0; j < getWearableCount((LLWearableType::EType)i); j++) + { + LLViewerWearable * curr_wearable = getViewerWearable((LLWearableType::EType)i, j); + if (curr_wearable && (curr_wearable->getAssetID() == asset_id)) + { + return curr_wearable; + } + } + } + return NULL; +} + +LLViewerWearable* LLAgentWearables::getViewerWearable(const LLWearableType::EType type, U32 index /*= 0*/) +{ + return dynamic_cast (getWearable(type, index)); +} + +const LLViewerWearable* LLAgentWearables::getViewerWearable(const LLWearableType::EType type, U32 index /*= 0*/) const +{ + return dynamic_cast (getWearable(type, index)); +} + +// static +bool LLAgentWearables::selfHasWearable(LLWearableType::EType type) +{ + return (gAgentWearables.getWearableCount(type) > 0); +} + +// virtual +void LLAgentWearables::wearableUpdated(LLWearable *wearable, bool removed) +{ + if (isAgentAvatarValid()) + { + gAgentAvatarp->wearableUpdated(wearable->getType()); + } + + LLWearableData::wearableUpdated(wearable, removed); + + if (!removed) + { + LLViewerWearable* viewer_wearable = dynamic_cast(wearable); + viewer_wearable->refreshName(); + + // Hack pt 2. If the wearable we just loaded has definition version 24, + // then force a re-save of this wearable after slamming the version number to 22. + // This number was incorrectly incremented for internal builds before release, and + // this fix will ensure that the affected wearables are re-saved with the right version number. + // the versions themselves are compatible. This code can be removed before release. + if( wearable->getDefinitionVersion() == 24 ) + { + U32 index; + if (getWearableIndex(wearable,index)) + { + LL_INFOS() << "forcing wearable type " << wearable->getType() << " to version 22 from 24" << LL_ENDL; + wearable->setDefinitionVersion(22); + saveWearable(wearable->getType(),index); + } + } + + checkWearableAgainstInventory(viewer_wearable); + } +} + +const LLUUID LLAgentWearables::getWearableItemID(LLWearableType::EType type, U32 index) const +{ + const LLViewerWearable *wearable = getViewerWearable(type,index); + if (wearable) + return wearable->getItemID(); + else + return LLUUID(); +} + +const LLUUID LLAgentWearables::getWearableAssetID(LLWearableType::EType type, U32 index) const +{ + const LLViewerWearable *wearable = getViewerWearable(type,index); + if (wearable) + return wearable->getAssetID(); + else + return LLUUID(); +} + +bool LLAgentWearables::isWearingItem(const LLUUID& item_id) const +{ + return getWearableFromItemID(item_id) != nullptr; +} + +void LLAgentWearables::addLocalTextureObject(const LLWearableType::EType wearable_type, const LLAvatarAppearanceDefines::ETextureIndex texture_type, U32 wearable_index) +{ + LLViewerWearable* wearable = getViewerWearable((LLWearableType::EType)wearable_type, wearable_index); + if (!wearable) + { + LL_ERRS() << "Tried to add local texture object to invalid wearable with type " << wearable_type << " and index " << wearable_index << LL_ENDL; + return; + } + LLLocalTextureObject lto; + wearable->setLocalTextureObject(texture_type, lto); +} + +class OnWearableItemCreatedCB: public LLInventoryCallback +{ +public: + OnWearableItemCreatedCB(): + mWearablesAwaitingItems(LLWearableType::WT_COUNT,NULL) + { + LL_INFOS() << "created callback" << LL_ENDL; + } + /* virtual */ void fire(const LLUUID& inv_item) + { + LL_INFOS() << "One item created " << inv_item.asString() << LL_ENDL; + LLConstPointer item = gInventory.getItem(inv_item); + mItemsToLink.push_back(item); + updatePendingWearable(inv_item); + } + ~OnWearableItemCreatedCB() + { + LL_INFOS() << "All items created" << LL_ENDL; + LLPointer link_waiter = new LLUpdateAppearanceOnDestroy; + link_inventory_array(LLAppearanceMgr::instance().getCOF(), + mItemsToLink, + link_waiter); + } + void addPendingWearable(LLViewerWearable *wearable) + { + if (!wearable) + { + LL_WARNS() << "no wearable" << LL_ENDL; + return; + } + LLWearableType::EType type = wearable->getType(); + if (typeisWearableType()) + { + LL_WARNS() << "non-wearable item found" << LL_ENDL; + return; + } + if (item && item->isWearableType()) + { + LLWearableType::EType type = item->getWearableType(); + if (type < LLWearableType::WT_COUNT) + { + LLViewerWearable *wearable = mWearablesAwaitingItems[type]; + if (wearable) + wearable->setItemID(inv_item); + } + else + { + LL_WARNS() << "invalid wearable type " << type << LL_ENDL; + } + } + } + +private: + LLInventoryObject::const_object_list_t mItemsToLink; + std::vector mWearablesAwaitingItems; +}; + +void LLAgentWearables::createStandardWearables() +{ + LL_WARNS() << "Creating standard wearables" << LL_ENDL; + + if (!isAgentAvatarValid()) return; + + constexpr bool create[LLWearableType::WT_COUNT] = + { + true, //LLWearableType::WT_SHAPE + true, //LLWearableType::WT_SKIN + true, //LLWearableType::WT_HAIR + true, //LLWearableType::WT_EYES + true, //LLWearableType::WT_SHIRT + true, //LLWearableType::WT_PANTS + true, //LLWearableType::WT_SHOES + true, //LLWearableType::WT_SOCKS + false, //LLWearableType::WT_JACKET + false, //LLWearableType::WT_GLOVES + true, //LLWearableType::WT_UNDERSHIRT + true, //LLWearableType::WT_UNDERPANTS + false //LLWearableType::WT_SKIRT + }; + + LLPointer cb = new OnWearableItemCreatedCB; + for (S32 i=0; i < LLWearableType::WT_COUNT; i++) + { + if (create[i]) + { + llassert(getWearableCount((LLWearableType::EType)i) == 0); + LLViewerWearable* wearable = LLWearableList::instance().createNewWearable((LLWearableType::EType)i, gAgentAvatarp); + ((OnWearableItemCreatedCB*)(&(*cb)))->addPendingWearable(wearable); + // no need to update here... + LLUUID category_id = LLUUID::null; + create_inventory_wearable(gAgent.getID(), + gAgent.getSessionID(), + category_id, + wearable->getTransactionID(), + wearable->getName(), + wearable->getDescription(), + wearable->getAssetType(), + wearable->getType(), + wearable->getPermissions().getMaskNextOwner(), + cb); + } + } +} + +// We no longer need this message in the current viewer, but send +// it for now to maintain compatibility with release viewers. Can +// remove this function once the SH-3455 changesets are universally deployed. +void LLAgentWearables::sendDummyAgentWearablesUpdate() +{ + LL_DEBUGS("Avatar") << "sendAgentWearablesUpdate()" << LL_ENDL; + + // Send the AgentIsNowWearing + gMessageSystem->newMessageFast(_PREHASH_AgentIsNowWearing); + + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + + // Send 4 standardized nonsense item ids (same as returned by the modified sim, not that it especially matters). + gMessageSystem->nextBlockFast(_PREHASH_WearableData); + gMessageSystem->addU8Fast(_PREHASH_WearableType, U8(1)); + gMessageSystem->addUUIDFast(_PREHASH_ItemID, LLUUID("db5a4e5f-9da3-44c8-992d-1181c5795498")); + + gMessageSystem->nextBlockFast(_PREHASH_WearableData); + gMessageSystem->addU8Fast(_PREHASH_WearableType, U8(2)); + gMessageSystem->addUUIDFast(_PREHASH_ItemID, LLUUID("6969c7cc-f72f-4a76-a19b-c293cce8ce4f")); + + gMessageSystem->nextBlockFast(_PREHASH_WearableData); + gMessageSystem->addU8Fast(_PREHASH_WearableType, U8(3)); + gMessageSystem->addUUIDFast(_PREHASH_ItemID, LLUUID("7999702b-b291-48f9-8903-c91dfb828408")); + + gMessageSystem->nextBlockFast(_PREHASH_WearableData); + gMessageSystem->addU8Fast(_PREHASH_WearableType, U8(4)); + gMessageSystem->addUUIDFast(_PREHASH_ItemID, LLUUID("566cb59e-ef60-41d7-bfa6-e0f293fbea40")); + + gAgent.sendReliableMessage(); +} + +void LLAgentWearables::makeNewOutfitDone(S32 type, U32 index) +{ + LLUUID first_item_id = getWearableItemID((LLWearableType::EType)type, index); + // Open the inventory and select the first item we added. + if (first_item_id.notNull()) + { + LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(); + if (active_panel) + { + active_panel->setSelection(first_item_id, TAKE_FOCUS_NO); + } + } +} + + +void LLAgentWearables::addWearableToAgentInventory(LLPointer cb, + LLViewerWearable* wearable, + const LLUUID& category_id, + bool notify) +{ + create_inventory_wearable(gAgent.getID(), + gAgent.getSessionID(), + category_id, + wearable->getTransactionID(), + wearable->getName(), + wearable->getDescription(), + wearable->getAssetType(), + wearable->getType(), + wearable->getPermissions().getMaskNextOwner(), + cb); +} + +void LLAgentWearables::removeWearable(const LLWearableType::EType type, bool do_remove_all, U32 index) +{ + if (getWearableCount(type) == 0) + { + // no wearables to remove + return; + } + + if (do_remove_all) + { + removeWearableFinal(type, do_remove_all, index); + } + else + { + LLViewerWearable* old_wearable = getViewerWearable(type,index); + + if (old_wearable) + { + if (old_wearable->isDirty()) + { + LLSD payload; + payload["wearable_type"] = (S32)type; + payload["wearable_index"] = (S32)index; + // Bring up view-modal dialog: Save changes? Yes, No, Cancel + LLNotificationsUtil::add("WearableSave", LLSD(), payload, &LLAgentWearables::onRemoveWearableDialog); + return; + } + else + { + removeWearableFinal(type, do_remove_all, index); + } + } + } +} + + +// static +bool LLAgentWearables::onRemoveWearableDialog(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + LLWearableType::EType type = (LLWearableType::EType)notification["payload"]["wearable_type"].asInteger(); + S32 index = (S32)notification["payload"]["wearable_index"].asInteger(); + switch(option) + { + case 0: // "Save" + gAgentWearables.saveWearable(type, index); + gAgentWearables.removeWearableFinal(type, false, index); + break; + + case 1: // "Don't Save" + gAgentWearables.removeWearableFinal(type, false, index); + break; + + case 2: // "Cancel" + break; + + default: + llassert(0); + break; + } + return false; +} + +// Called by removeWearable() and onRemoveWearableDialog() to actually do the removal. +void LLAgentWearables::removeWearableFinal(const LLWearableType::EType type, bool do_remove_all, U32 index) +{ + //LLAgentDumper dumper("removeWearable"); + if (do_remove_all) + { + S32 max_entry = getWearableCount(type)-1; + for (S32 i=max_entry; i>=0; i--) + { + LLViewerWearable* old_wearable = getViewerWearable(type,i); + if (old_wearable) + { + eraseWearable(old_wearable); + old_wearable->removeFromAvatar(); + } + } + clearWearableType(type); + } + else + { + LLViewerWearable* old_wearable = getViewerWearable(type, index); + + if (old_wearable) + { + eraseWearable(old_wearable); + old_wearable->removeFromAvatar(); + } + } + + gInventory.notifyObservers(); +} + +// Assumes existing wearables are not dirty. +void LLAgentWearables::setWearableOutfit(const LLInventoryItem::item_array_t& items, + const std::vector< LLViewerWearable* >& wearables) +{ + LL_INFOS() << "setWearableOutfit() start" << LL_ENDL; + + S32 count = wearables.size(); + llassert(items.size() == count); + + // Check for whether outfit already matches the one requested + S32 matched = 0, mismatched = 0; + const S32 arr_size = LLWearableType::WT_COUNT; + S32 type_counts[arr_size]; + bool update_inventory{ false }; + std::fill(type_counts,type_counts+arr_size,0); + for (S32 i = 0; i < count; i++) + { + LLViewerWearable* new_wearable = wearables[i]; + LLPointer new_item = items[i]; + + const LLWearableType::EType type = new_wearable->getType(); + if (type < 0 || type>=LLWearableType::WT_COUNT) + { + LL_WARNS() << "invalid type " << type << LL_ENDL; + mismatched++; + continue; + } + S32 index = type_counts[type]; + type_counts[type]++; + + LLViewerWearable *curr_wearable = dynamic_cast(getWearable(type,index)); + if (!new_wearable || !curr_wearable || + new_wearable->getAssetID() != curr_wearable->getAssetID()) + { + LL_DEBUGS("Avatar") << "mismatch, type " << type << " index " << index + << " names " << (curr_wearable ? curr_wearable->getName() : "NONE") << "," + << " names " << (new_wearable ? new_wearable->getName() : "NONE") << LL_ENDL; + mismatched++; + continue; + } + + // Update only inventory in this case - ordering of wearables with the same asset id has no effect. + // Updating wearables in this case causes the two-alphas error in MAINT-4158. + // We should actually disallow wearing two wearables with the same asset id. + if (curr_wearable->getName() != new_item->getName() || + curr_wearable->getItemID() != new_item->getUUID()) + { + LL_DEBUGS("Avatar") << "mismatch on name or inventory id, names " + << curr_wearable->getName() << " vs " << new_item->getName() + << " item ids " << curr_wearable->getItemID() << " vs " << new_item->getUUID() + << LL_ENDL; + update_inventory = true; + continue; + } + // If we got here, everything matches. + matched++; + } + LL_DEBUGS("Avatar") << "matched " << matched << " mismatched " << mismatched << LL_ENDL; + for (S32 j=0; jgetAssetType((LLWearableType::EType)j) == LLAssetType::AT_CLOTHING) + { + removeWearable((LLWearableType::EType)j, true, 0); + } + } + + for (S32 i = 0; i < count; i++) + { + LLViewerWearable* new_wearable = wearables[i]; + LLPointer new_item = items[i]; + + llassert(new_wearable); + if (new_wearable) + { + const LLWearableType::EType type = new_wearable->getType(); + + LLUUID old_wearable_id = new_wearable->getItemID(); + new_wearable->setName(new_item->getName()); + new_wearable->setItemID(new_item->getUUID()); + + if (wearable_type_inst->getAssetType(type) == LLAssetType::AT_BODYPART) + { + // exactly one wearable per body part + setWearable(type,0,new_wearable); + if (old_wearable_id.notNull()) + { + // we changed id before setting wearable, update old item manually + // to complete the swap. + gInventory.addChangedMask(LLInventoryObserver::LABEL, old_wearable_id); + } + } + else + { + pushWearable(type,new_wearable); + } + + constexpr bool removed = false; + wearableUpdated(new_wearable, removed); + } + } + + gInventory.notifyObservers(); + + if (mismatched == 0) + { + LL_DEBUGS("Avatar") << "inventory updated, wearable assets not changed, bailing out" << LL_ENDL; + notifyLoadingFinished(); + return; + } + + // updating agent avatar + + if (isAgentAvatarValid()) + { + gAgentAvatarp->setCompositeUpdatesEnabled(true); + + // If we have not yet declouded, we may want to use + // baked texture UUIDs sent from the first objectUpdate message + // don't overwrite these. If we have already declouded, we've saved + // these ids as the last known good textures and can invalidate without + // re-clouding. + if (!gAgentAvatarp->getIsCloud()) + { + gAgentAvatarp->invalidateAll(); + } + } + + // Start rendering & update the server + mWearablesLoaded = true; + + notifyLoadingFinished(); + + // Copy wearable params to avatar. + gAgentAvatarp->writeWearablesToAvatar(); + + // Then update the avatar based on the copied params. + gAgentAvatarp->updateVisualParams(); + + gAgentAvatarp->dumpAvatarTEs("setWearableOutfit"); + + LL_DEBUGS("Avatar") << "setWearableOutfit() end" << LL_ENDL; +} + + +// User has picked "wear on avatar" from a menu. +void LLAgentWearables::setWearableItem(LLInventoryItem* new_item, LLViewerWearable* new_wearable, bool do_append) +{ + //LLAgentDumper dumper("setWearableItem"); + if (isWearingItem(new_item->getUUID())) + { + LL_WARNS() << "wearable " << new_item->getUUID() << " is already worn" << LL_ENDL; + return; + } + + const LLWearableType::EType type = new_wearable->getType(); + + if (!do_append) + { + // Remove old wearable, if any + // MULTI_WEARABLE: hardwired to 0 + LLViewerWearable* old_wearable = getViewerWearable(type,0); + if (old_wearable) + { + const LLUUID& old_item_id = old_wearable->getItemID(); + if ((old_wearable->getAssetID() == new_wearable->getAssetID()) && + (old_item_id == new_item->getUUID())) + { + LL_DEBUGS() << "No change to wearable asset and item: " << LLWearableType::getInstance()->getTypeName(type) << LL_ENDL; + return; + } + + if (old_wearable->isDirty()) + { + // Bring up modal dialog: Save changes? Yes, No, Cancel + LLSD payload; + payload["item_id"] = new_item->getUUID(); + LLNotificationsUtil::add("WearableSave", LLSD(), payload, boost::bind(onSetWearableDialog, _1, _2, new_wearable)); + return; + } + } + } + + setWearableFinal(new_item, new_wearable, do_append); +} + +// static +bool LLAgentWearables::onSetWearableDialog(const LLSD& notification, const LLSD& response, LLViewerWearable* wearable) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + LLInventoryItem* new_item = gInventory.getItem(notification["payload"]["item_id"].asUUID()); + U32 index; + if (!gAgentWearables.getWearableIndex(wearable,index)) + { + LL_WARNS() << "Wearable not found" << LL_ENDL; + delete wearable; + return false; + } + if (!new_item) + { + delete wearable; + return false; + } + + switch(option) + { + case 0: // "Save" + gAgentWearables.saveWearable(wearable->getType(),index); + gAgentWearables.setWearableFinal(new_item, wearable); + break; + + case 1: // "Don't Save" + gAgentWearables.setWearableFinal(new_item, wearable); + break; + + case 2: // "Cancel" + break; + + default: + llassert(0); + break; + } + + delete wearable; + return false; +} + +// Called from setWearableItem() and onSetWearableDialog() to actually set the wearable. +// MULTI_WEARABLE: unify code after null objects are gone. +void LLAgentWearables::setWearableFinal(LLInventoryItem* new_item, LLViewerWearable* new_wearable, bool do_append) +{ + const LLWearableType::EType type = new_wearable->getType(); + + if (do_append && getWearableItemID(type,0).notNull()) + { + new_wearable->setItemID(new_item->getUUID()); + const bool trigger_updated = false; + pushWearable(type, new_wearable, trigger_updated); + LL_INFOS() << "Added additional wearable for type " << type + << " size is now " << getWearableCount(type) << LL_ENDL; + checkWearableAgainstInventory(new_wearable); + } + else + { + // Replace the old wearable with a new one. + llassert(new_item->getAssetUUID() == new_wearable->getAssetID()); + + LLViewerWearable *old_wearable = getViewerWearable(type,0); + LLUUID old_item_id; + if (old_wearable) + { + old_item_id = old_wearable->getItemID(); + } + new_wearable->setItemID(new_item->getUUID()); + setWearable(type,0,new_wearable); + + if (old_item_id.notNull()) + { + gInventory.addChangedMask(LLInventoryObserver::LABEL, old_item_id); + gInventory.notifyObservers(); + } + LL_INFOS() << "Replaced current element 0 for type " << type + << " size is now " << getWearableCount(type) << LL_ENDL; + } +} + +// User has picked "remove from avatar" from a menu. +// static +void LLAgentWearables::userRemoveWearable(const LLWearableType::EType &type, const U32 &index) +{ + if (!(type==LLWearableType::WT_SHAPE || type==LLWearableType::WT_SKIN || type==LLWearableType::WT_HAIR || type==LLWearableType::WT_EYES)) //&& + //!((!gAgent.isTeen()) && (type==LLWearableType::WT_UNDERPANTS || type==LLWearableType::WT_UNDERSHIRT))) + { + gAgentWearables.removeWearable(type,false,index); + } +} + +//static +void LLAgentWearables::userRemoveWearablesOfType(const LLWearableType::EType &type) +{ + if (!(type==LLWearableType::WT_SHAPE || type==LLWearableType::WT_SKIN || type==LLWearableType::WT_HAIR || type==LLWearableType::WT_EYES)) //&& + //!((!gAgent.isTeen()) && (type==LLWearableType::WT_UNDERPANTS || type==LLWearableType::WT_UNDERSHIRT))) + { + gAgentWearables.removeWearable(type,true,0); + } +} + +// Given a desired set of attachments, find what objects need to be +// removed, and what additional inventory items need to be added. +void LLAgentWearables::findAttachmentsAddRemoveInfo(LLInventoryModel::item_array_t& obj_item_array, + llvo_vec_t& objects_to_remove, + llvo_vec_t& objects_to_retain, + LLInventoryModel::item_array_t& items_to_add) +{ + // Possible cases: + // already wearing but not in request set -> take off. + // already wearing and in request set -> leave alone. + // not wearing and in request set -> put on. + + if (!isAgentAvatarValid()) return; + + std::set requested_item_ids; + std::set current_item_ids; + for (S32 i=0; igetLinkedUUID(); + //LL_INFOS() << "Requested attachment id " << requested_id << LL_ENDL; + requested_item_ids.insert(requested_id); + } + + // Build up list of objects to be removed and items currently attached. + LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); + LLVOAvatar::attachment_map_t::iterator end = gAgentAvatarp->mAttachmentPoints.end(); + while (iter != end) + { + LLVOAvatar::attachment_map_t::iterator curiter = iter++; + LLViewerJointAttachment* attachment = curiter->second; + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + LLViewerObject *objectp = attachment_iter->get(); + if (objectp) + { + LLUUID object_item_id = objectp->getAttachmentItemID(); + + bool remove_attachment = true; + if (requested_item_ids.find(object_item_id) != requested_item_ids.end()) + { // Object currently worn, was requested to keep it + // Flag as currently worn so we won't have to add it again. + remove_attachment = false; + } + else if (objectp->isTempAttachment()) + { // Check if we should keep this temp attachment + remove_attachment = LLAppearanceMgr::instance().shouldRemoveTempAttachment(objectp->getID()); + } + + if (remove_attachment) + { + // LL_INFOS() << "found object to remove, id " << objectp->getID() << ", item " << objectp->getAttachmentItemID() << LL_ENDL; + objects_to_remove.push_back(objectp); + } + else + { + // LL_INFOS() << "found object to keep, id " << objectp->getID() << ", item " << objectp->getAttachmentItemID() << LL_ENDL; + current_item_ids.insert(object_item_id); + objects_to_retain.push_back(objectp); + } + } + } + } + + for (LLInventoryModel::item_array_t::iterator it = obj_item_array.begin(); + it != obj_item_array.end(); + ++it) + { + LLUUID linked_id = (*it).get()->getLinkedUUID(); + if (current_item_ids.find(linked_id) != current_item_ids.end()) + { + // Requested attachment is already worn. + } + else + { + // Requested attachment is not worn yet. + items_to_add.push_back(*it); + } + } + // S32 remove_count = objects_to_remove.size(); + // S32 add_count = items_to_add.size(); + // LL_INFOS() << "remove " << remove_count << " add " << add_count << LL_ENDL; +} + +std::vector LLAgentWearables::getTempAttachments() +{ + llvo_vec_t temp_attachs; + if (isAgentAvatarValid()) + { + for (LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); iter != gAgentAvatarp->mAttachmentPoints.end();) + { + LLVOAvatar::attachment_map_t::iterator curiter = iter++; + LLViewerJointAttachment* attachment = curiter->second; + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + LLViewerObject *objectp = attachment_iter->get(); + if (objectp && objectp->isTempAttachment()) + { + temp_attachs.push_back(objectp); + } + } + } + } + return temp_attachs; +} + +void LLAgentWearables::userRemoveMultipleAttachments(llvo_vec_t& objects_to_remove) +{ + if (!isAgentAvatarValid()) return; + + if (objects_to_remove.empty()) + return; + + LL_DEBUGS("Avatar") << "ATT [ObjectDetach] removing " << objects_to_remove.size() << " objects" << LL_ENDL; + gMessageSystem->newMessage("ObjectDetach"); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + + for (llvo_vec_t::iterator it = objects_to_remove.begin(); + it != objects_to_remove.end(); + ++it) + { + LLViewerObject *objectp = *it; + //gAgentAvatarp->resetJointPositionsOnDetach(objectp); + gMessageSystem->nextBlockFast(_PREHASH_ObjectData); + gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, objectp->getLocalID()); + const LLUUID& item_id = objectp->getAttachmentItemID(); + LLViewerInventoryItem *item = gInventory.getItem(item_id); + LL_DEBUGS("Avatar") << "ATT removing object, item is " << (item ? item->getName() : "UNKNOWN") << " " << item_id << LL_ENDL; + LLAttachmentsMgr::instance().onDetachRequested(item_id); + } + gMessageSystem->sendReliable(gAgent.getRegionHost()); +} + +void LLAgentWearables::userAttachMultipleAttachments(LLInventoryModel::item_array_t& obj_item_array) +{ + // Build a compound message to send all the objects that need to be rezzed. + S32 obj_count = obj_item_array.size(); + if (obj_count > 0) + { + LL_DEBUGS("Avatar") << "ATT attaching multiple, total obj_count " << obj_count << LL_ENDL; + } + + for(LLInventoryModel::item_array_t::const_iterator it = obj_item_array.begin(); + it != obj_item_array.end(); + ++it) + { + const LLInventoryItem* item = *it; + LLAttachmentsMgr::instance().addAttachmentRequest(item->getLinkedUUID(), 0, true); + } +} + +// Returns false if the given wearable is already topmost/bottommost +// (depending on closer_to_body parameter). +bool LLAgentWearables::canMoveWearable(const LLUUID& item_id, bool closer_to_body) const +{ + const LLWearable* wearable = getWearableFromItemID(item_id); + if (!wearable) return false; + + LLWearableType::EType wtype = wearable->getType(); + const LLWearable* marginal_wearable = closer_to_body ? getBottomWearable(wtype) : getTopWearable(wtype); + if (!marginal_wearable) return false; + + return wearable != marginal_wearable; +} + +bool LLAgentWearables::areWearablesLoaded() const +{ + return mWearablesLoaded; +} + +bool LLAgentWearables::canWearableBeRemoved(const LLViewerWearable* wearable) const +{ + if (!wearable) return false; + + LLWearableType::EType type = wearable->getType(); + // Make sure the user always has at least one shape, skin, eyes, and hair type currently worn. + return !(((type == LLWearableType::WT_SHAPE) || (type == LLWearableType::WT_SKIN) || (type == LLWearableType::WT_HAIR) || (type == LLWearableType::WT_EYES)) + && (getWearableCount(type) <= 1) ); +} +void LLAgentWearables::animateAllWearableParams(F32 delta) +{ + for( S32 type = 0; type < LLWearableType::WT_COUNT; ++type ) + { + for (S32 count = 0; count < (S32)getWearableCount((LLWearableType::EType)type); ++count) + { + LLViewerWearable *wearable = getViewerWearable((LLWearableType::EType)type,count); + llassert(wearable); + if (wearable) + { + wearable->animateParams(delta); + } + } + } +} + +bool LLAgentWearables::moveWearable(const LLViewerInventoryItem* item, bool closer_to_body) +{ + if (!item) return false; + if (!item->isWearableType()) return false; + + LLWearableType::EType type = item->getWearableType(); + U32 wearable_count = getWearableCount(type); + if (0 == wearable_count) return false; + + const LLUUID& asset_id = item->getAssetUUID(); + + //nowhere to move if the wearable is already on any boundary (closest to the body/furthest from the body) + if (closer_to_body) + { + LLViewerWearable* bottom_wearable = dynamic_cast( getBottomWearable(type) ); + if (bottom_wearable->getAssetID() == asset_id) + { + return false; + } + } + else // !closer_to_body + { + LLViewerWearable* top_wearable = dynamic_cast( getTopWearable(type) ); + if (top_wearable->getAssetID() == asset_id) + { + return false; + } + } + + for (U32 i = 0; i < wearable_count; ++i) + { + LLViewerWearable* wearable = getViewerWearable(type, i); + if (!wearable) continue; + if (wearable->getAssetID() != asset_id) continue; + + //swapping wearables + U32 swap_i = closer_to_body ? i-1 : i+1; + swapWearables(type, i, swap_i); + return true; + } + + return false; +} + +// static +void LLAgentWearables::createWearable(LLWearableType::EType type, bool wear, const LLUUID& parent_id, std::function created_cb) +{ + if (type == LLWearableType::WT_INVALID || type == LLWearableType::WT_NONE) return; + + if (type == LLWearableType::WT_UNIVERSAL && !gAgent.getRegion()->bakesOnMeshEnabled()) + { + LL_WARNS("Inventory") << "Can't create WT_UNIVERSAL type " << LL_ENDL; + return; + } + + LLViewerWearable* wearable = LLWearableList::instance().createNewWearable(type, gAgentAvatarp); + LLAssetType::EType asset_type = wearable->getAssetType(); + LLPointer cb; + if(wear) + { + cb = new LLBoostFuncInventoryCallback(wear_and_edit_cb); + } + else + { + cb = new LLBoostFuncInventoryCallback(wear_cb); + } + if (created_cb != NULL) + { + cb->addOnFireFunc(created_cb); + } + + LLUUID folder_id; + + if (parent_id.notNull()) + { + folder_id = parent_id; + } + else + { + LLFolderType::EType folder_type = LLFolderType::assetTypeToFolderType(asset_type); + folder_id = gInventory.findCategoryUUIDForType(folder_type); + } + + create_inventory_wearable(gAgent.getID(), + gAgent.getSessionID(), + folder_id, + wearable->getTransactionID(), + wearable->getName(), + wearable->getDescription(), + asset_type, + wearable->getType(), + LLFloaterPerms::getNextOwnerPerms("Wearables"), + cb); +} + +// static +void LLAgentWearables::editWearable(const LLUUID& item_id) +{ + LLViewerInventoryItem* item = gInventory.getLinkedItem(item_id); + if (!item) + { + LL_WARNS() << "Failed to get linked item" << LL_ENDL; + return; + } + + if (!item->isFinished()) + { + LL_WARNS() << "Tried to edit wearable that isn't loaded" << LL_ENDL; + // Restart fetch or put item to the front + LLInventoryModelBackgroundFetch::instance().start(item->getUUID(), false); + return; + } + + LLViewerWearable* wearable = gAgentWearables.getWearableFromItemID(item_id); + if (!wearable) + { + LL_WARNS() << "Cannot get wearable" << LL_ENDL; + return; + } + + if (!gAgentWearables.isWearableModifiable(item->getUUID())) + { + LL_WARNS() << "Cannot modify wearable" << LL_ENDL; + return; + } + + S32 shape_count = gAgentWearables.getWearableCount(LLWearableType::WT_SHAPE); + S32 hair_count = gAgentWearables.getWearableCount(LLWearableType::WT_HAIR); + S32 eye_count = gAgentWearables.getWearableCount(LLWearableType::WT_EYES); + S32 skin_count = gAgentWearables.getWearableCount(LLWearableType::WT_SKIN); + if (!shape_count || !hair_count || !eye_count || !skin_count) + { + // Don't let user edit wearables if avatar is cloud due to missing parts. + // Let user edit wearables if avatar is cloud due to missing textures. + LL_WARNS() << "Cannot modify wearable. Avatar is cloud and missing parts." << LL_ENDL; + return; + } + + const bool disable_camera_switch = LLWearableType::getInstance()->getDisableCameraSwitch(wearable->getType()); + LLPanel* panel = LLFloaterSidePanelContainer::getPanel("appearance"); + LLSidepanelAppearance::editWearable(wearable, panel, disable_camera_switch); +} + +// Request editing the item after it gets worn. +void LLAgentWearables::requestEditingWearable(const LLUUID& item_id) +{ + mItemToEdit = gInventory.getLinkedItemID(item_id); +} + +// Start editing the item if previously requested. +void LLAgentWearables::editWearableIfRequested(const LLUUID& item_id) +{ + if (mItemToEdit.notNull() && + mItemToEdit == gInventory.getLinkedItemID(item_id)) + { + LLAgentWearables::editWearable(item_id); + mItemToEdit.setNull(); + } +} + +boost::signals2::connection LLAgentWearables::addLoadingStartedCallback(loading_started_callback_t cb) +{ + return mLoadingStartedSignal.connect(cb); +} + +boost::signals2::connection LLAgentWearables::addLoadedCallback(loaded_callback_t cb) +{ + return mLoadedSignal.connect(cb); +} + +bool LLAgentWearables::changeInProgress() const +{ + return mCOFChangeInProgress; +} + +void LLAgentWearables::notifyLoadingStarted() +{ + mCOFChangeInProgress = true; + mCOFChangeTimer.reset(); + mLoadingStartedSignal(); +} + +void LLAgentWearables::notifyLoadingFinished() +{ + mCOFChangeInProgress = false; + mLoadedSignal(); +} +// EOF diff --git a/indra/newview/llagentwearables.h b/indra/newview/llagentwearables.h index 14835bd528..3b8ff93c76 100644 --- a/indra/newview/llagentwearables.h +++ b/indra/newview/llagentwearables.h @@ -1,261 +1,261 @@ -/** - * @file llagentwearables.h - * @brief LLAgentWearables class header file - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLAGENTWEARABLES_H -#define LL_LLAGENTWEARABLES_H - -// libraries -#include "llmemory.h" -#include "llui.h" -#include "lluuid.h" -#include "llinventory.h" - -// newview -#include "llinventorymodel.h" -#include "llviewerinventory.h" -#include "llavatarappearancedefines.h" -#include "llwearabledata.h" -#include "llinitdestroyclass.h" - -class LLInventoryItem; -class LLVOAvatarSelf; -class LLViewerWearable; -class LLViewerObject; - -class LLAgentWearables : public LLInitClass, public LLWearableData -{ - //-------------------------------------------------------------------- - // Constructors / destructors / Initializers - //-------------------------------------------------------------------- -public: - - LLAgentWearables(); - virtual ~LLAgentWearables(); - void setAvatarObject(LLVOAvatarSelf *avatar); - void createStandardWearables(); - void cleanup(); - void dump(); - - // LLInitClass interface - static void initClass(); - - //-------------------------------------------------------------------- - // Queries - //-------------------------------------------------------------------- -public: - bool isWearingItem(const LLUUID& item_id) const; - bool isWearableModifiable(LLWearableType::EType type, U32 index /*= 0*/) const; - bool isWearableModifiable(const LLUUID& item_id) const; - - bool isWearableCopyable(LLWearableType::EType type, U32 index /*= 0*/) const; - bool areWearablesLoaded() const; - bool isCOFChangeInProgress() const { return mCOFChangeInProgress; } - F32 getCOFChangeTime() const { return mCOFChangeTimer.getElapsedTimeF32(); } - bool canMoveWearable(const LLUUID& item_id, bool closer_to_body) const; - - // Note: False for shape, skin, eyes, and hair, unless you have MORE than 1. - bool canWearableBeRemoved(const LLViewerWearable* wearable) const; - - void animateAllWearableParams(F32 delta); - - //-------------------------------------------------------------------- - // Accessors - //-------------------------------------------------------------------- -public: - const LLUUID getWearableItemID(LLWearableType::EType type, U32 index /*= 0*/) const; - const LLUUID getWearableAssetID(LLWearableType::EType type, U32 index /*= 0*/) const; - const LLViewerWearable* getWearableFromItemID(const LLUUID& item_id) const; - LLViewerWearable* getWearableFromItemID(const LLUUID& item_id); - LLViewerWearable* getWearableFromAssetID(const LLUUID& asset_id); - LLViewerWearable* getViewerWearable(const LLWearableType::EType type, U32 index /*= 0*/); - const LLViewerWearable* getViewerWearable(const LLWearableType::EType type, U32 index /*= 0*/) const; - LLInventoryItem* getWearableInventoryItem(LLWearableType::EType type, U32 index /*= 0*/); - static bool selfHasWearable(LLWearableType::EType type); - - //-------------------------------------------------------------------- - // Setters - //-------------------------------------------------------------------- -private: - /*virtual*/void wearableUpdated(LLWearable *wearable, bool removed); -public: - void setWearableItem(LLInventoryItem* new_item, LLViewerWearable* wearable, bool do_append = false); - void setWearableOutfit(const LLInventoryItem::item_array_t& items, const std::vector< LLViewerWearable* >& wearables); - void setWearableName(const LLUUID& item_id, const std::string& new_name); - // *TODO: Move this into llappearance/LLWearableData ? - void addLocalTextureObject(const LLWearableType::EType wearable_type, const LLAvatarAppearanceDefines::ETextureIndex texture_type, U32 wearable_index); - -protected: - void setWearableFinal(LLInventoryItem* new_item, LLViewerWearable* new_wearable, bool do_append = false); - static bool onSetWearableDialog(const LLSD& notification, const LLSD& response, LLViewerWearable* wearable); - - void addWearableToAgentInventory(LLPointer cb, - LLViewerWearable* wearable, - const LLUUID& category_id = LLUUID::null, - bool notify = true); - void addWearabletoAgentInventoryDone(const LLWearableType::EType type, - const U32 index, - const LLUUID& item_id, - LLViewerWearable* wearable); - - //-------------------------------------------------------------------- - // Editing/moving wearables - //-------------------------------------------------------------------- - -public: - static void createWearable(LLWearableType::EType type, bool wear = false, const LLUUID& parent_id = LLUUID::null, std::function created_cb = nullptr); - static void editWearable(const LLUUID& item_id); - bool moveWearable(const LLViewerInventoryItem* item, bool closer_to_body); - - void requestEditingWearable(const LLUUID& item_id); - void editWearableIfRequested(const LLUUID& item_id); - -private: - LLUUID mItemToEdit; - - //-------------------------------------------------------------------- - // Removing wearables - //-------------------------------------------------------------------- -public: - void removeWearable(const LLWearableType::EType type, bool do_remove_all /*= false*/, U32 index /*= 0*/); -private: - void removeWearableFinal(const LLWearableType::EType type, bool do_remove_all /*= false*/, U32 index /*= 0*/); -protected: - static bool onRemoveWearableDialog(const LLSD& notification, const LLSD& response); - - //-------------------------------------------------------------------- - // Outfits - //-------------------------------------------------------------------- -private: - void makeNewOutfitDone(S32 type, U32 index); - - //-------------------------------------------------------------------- - // Save Wearables - //-------------------------------------------------------------------- -public: - void saveWearableAs(const LLWearableType::EType type, const U32 index, const std::string& new_name, const std::string& description, bool save_in_lost_and_found); - void saveWearable(const LLWearableType::EType type, const U32 index, - const std::string new_name = ""); - void saveAllWearables(); - void revertWearable(const LLWearableType::EType type, const U32 index); - - // We no longer need this message in the current viewer, but send - // it for now to maintain compatibility with release viewers. Can - // remove this function once the SH-3455 changesets are universally deployed. - void sendDummyAgentWearablesUpdate(); - - //-------------------------------------------------------------------- - // Static UI hooks - //-------------------------------------------------------------------- -public: - static void userRemoveWearable(const LLWearableType::EType &type, const U32 &index); - static void userRemoveWearablesOfType(const LLWearableType::EType &type); - - typedef std::vector llvo_vec_t; - - static void findAttachmentsAddRemoveInfo(LLInventoryModel::item_array_t& obj_item_array, - llvo_vec_t& objects_to_remove, - llvo_vec_t& objects_to_retain, - LLInventoryModel::item_array_t& items_to_add); - static void userRemoveMultipleAttachments(llvo_vec_t& llvo_array); - static void userAttachMultipleAttachments(LLInventoryModel::item_array_t& obj_item_array); - - static llvo_vec_t getTempAttachments(); - - //-------------------------------------------------------------------- - // Signals - //-------------------------------------------------------------------- -public: - typedef boost::function loading_started_callback_t; - typedef boost::signals2::signal loading_started_signal_t; - boost::signals2::connection addLoadingStartedCallback(loading_started_callback_t cb); - - typedef boost::function loaded_callback_t; - typedef boost::signals2::signal loaded_signal_t; - boost::signals2::connection addLoadedCallback(loaded_callback_t cb); - - bool changeInProgress() const; - void notifyLoadingStarted(); - void notifyLoadingFinished(); - -private: - loading_started_signal_t mLoadingStartedSignal; // should be called before wearables are changed - loaded_signal_t mLoadedSignal; // emitted when all agent wearables get loaded - - //-------------------------------------------------------------------- - // Member variables - //-------------------------------------------------------------------- -private: - static bool mInitialWearablesUpdateReceived; - bool mWearablesLoaded; - - /** - * True if agent's outfit is being changed now. - */ - bool mCOFChangeInProgress; - LLTimer mCOFChangeTimer; - - //-------------------------------------------------------------------------------- - // Support classes - //-------------------------------------------------------------------------------- -private: - class AddWearableToAgentInventoryCallback : public LLInventoryCallback - { - public: - enum ETodo - { - CALL_NONE = 0, - CALL_UPDATE = 1, - CALL_RECOVERDONE = 2, - CALL_CREATESTANDARDDONE = 4, - CALL_MAKENEWOUTFITDONE = 8, - CALL_WEARITEM = 16 - }; - - AddWearableToAgentInventoryCallback(LLPointer cb, - LLWearableType::EType type, - U32 index, - LLViewerWearable* wearable, - U32 todo = CALL_NONE, - const std::string description = ""); - virtual void fire(const LLUUID& inv_item); - private: - LLWearableType::EType mType; - U32 mIndex; - LLViewerWearable* mWearable; - U32 mTodo; - LLPointer mCB; - std::string mDescription; - }; - -}; // LLAgentWearables - -extern LLAgentWearables gAgentWearables; - -//-------------------------------------------------------------------- -// Types -//-------------------------------------------------------------------- - -#endif // LL_AGENTWEARABLES_H +/** + * @file llagentwearables.h + * @brief LLAgentWearables class header file + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLAGENTWEARABLES_H +#define LL_LLAGENTWEARABLES_H + +// libraries +#include "llmemory.h" +#include "llui.h" +#include "lluuid.h" +#include "llinventory.h" + +// newview +#include "llinventorymodel.h" +#include "llviewerinventory.h" +#include "llavatarappearancedefines.h" +#include "llwearabledata.h" +#include "llinitdestroyclass.h" + +class LLInventoryItem; +class LLVOAvatarSelf; +class LLViewerWearable; +class LLViewerObject; + +class LLAgentWearables : public LLInitClass, public LLWearableData +{ + //-------------------------------------------------------------------- + // Constructors / destructors / Initializers + //-------------------------------------------------------------------- +public: + + LLAgentWearables(); + virtual ~LLAgentWearables(); + void setAvatarObject(LLVOAvatarSelf *avatar); + void createStandardWearables(); + void cleanup(); + void dump(); + + // LLInitClass interface + static void initClass(); + + //-------------------------------------------------------------------- + // Queries + //-------------------------------------------------------------------- +public: + bool isWearingItem(const LLUUID& item_id) const; + bool isWearableModifiable(LLWearableType::EType type, U32 index /*= 0*/) const; + bool isWearableModifiable(const LLUUID& item_id) const; + + bool isWearableCopyable(LLWearableType::EType type, U32 index /*= 0*/) const; + bool areWearablesLoaded() const; + bool isCOFChangeInProgress() const { return mCOFChangeInProgress; } + F32 getCOFChangeTime() const { return mCOFChangeTimer.getElapsedTimeF32(); } + bool canMoveWearable(const LLUUID& item_id, bool closer_to_body) const; + + // Note: False for shape, skin, eyes, and hair, unless you have MORE than 1. + bool canWearableBeRemoved(const LLViewerWearable* wearable) const; + + void animateAllWearableParams(F32 delta); + + //-------------------------------------------------------------------- + // Accessors + //-------------------------------------------------------------------- +public: + const LLUUID getWearableItemID(LLWearableType::EType type, U32 index /*= 0*/) const; + const LLUUID getWearableAssetID(LLWearableType::EType type, U32 index /*= 0*/) const; + const LLViewerWearable* getWearableFromItemID(const LLUUID& item_id) const; + LLViewerWearable* getWearableFromItemID(const LLUUID& item_id); + LLViewerWearable* getWearableFromAssetID(const LLUUID& asset_id); + LLViewerWearable* getViewerWearable(const LLWearableType::EType type, U32 index /*= 0*/); + const LLViewerWearable* getViewerWearable(const LLWearableType::EType type, U32 index /*= 0*/) const; + LLInventoryItem* getWearableInventoryItem(LLWearableType::EType type, U32 index /*= 0*/); + static bool selfHasWearable(LLWearableType::EType type); + + //-------------------------------------------------------------------- + // Setters + //-------------------------------------------------------------------- +private: + /*virtual*/void wearableUpdated(LLWearable *wearable, bool removed); +public: + void setWearableItem(LLInventoryItem* new_item, LLViewerWearable* wearable, bool do_append = false); + void setWearableOutfit(const LLInventoryItem::item_array_t& items, const std::vector< LLViewerWearable* >& wearables); + void setWearableName(const LLUUID& item_id, const std::string& new_name); + // *TODO: Move this into llappearance/LLWearableData ? + void addLocalTextureObject(const LLWearableType::EType wearable_type, const LLAvatarAppearanceDefines::ETextureIndex texture_type, U32 wearable_index); + +protected: + void setWearableFinal(LLInventoryItem* new_item, LLViewerWearable* new_wearable, bool do_append = false); + static bool onSetWearableDialog(const LLSD& notification, const LLSD& response, LLViewerWearable* wearable); + + void addWearableToAgentInventory(LLPointer cb, + LLViewerWearable* wearable, + const LLUUID& category_id = LLUUID::null, + bool notify = true); + void addWearabletoAgentInventoryDone(const LLWearableType::EType type, + const U32 index, + const LLUUID& item_id, + LLViewerWearable* wearable); + + //-------------------------------------------------------------------- + // Editing/moving wearables + //-------------------------------------------------------------------- + +public: + static void createWearable(LLWearableType::EType type, bool wear = false, const LLUUID& parent_id = LLUUID::null, std::function created_cb = nullptr); + static void editWearable(const LLUUID& item_id); + bool moveWearable(const LLViewerInventoryItem* item, bool closer_to_body); + + void requestEditingWearable(const LLUUID& item_id); + void editWearableIfRequested(const LLUUID& item_id); + +private: + LLUUID mItemToEdit; + + //-------------------------------------------------------------------- + // Removing wearables + //-------------------------------------------------------------------- +public: + void removeWearable(const LLWearableType::EType type, bool do_remove_all /*= false*/, U32 index /*= 0*/); +private: + void removeWearableFinal(const LLWearableType::EType type, bool do_remove_all /*= false*/, U32 index /*= 0*/); +protected: + static bool onRemoveWearableDialog(const LLSD& notification, const LLSD& response); + + //-------------------------------------------------------------------- + // Outfits + //-------------------------------------------------------------------- +private: + void makeNewOutfitDone(S32 type, U32 index); + + //-------------------------------------------------------------------- + // Save Wearables + //-------------------------------------------------------------------- +public: + void saveWearableAs(const LLWearableType::EType type, const U32 index, const std::string& new_name, const std::string& description, bool save_in_lost_and_found); + void saveWearable(const LLWearableType::EType type, const U32 index, + const std::string new_name = ""); + void saveAllWearables(); + void revertWearable(const LLWearableType::EType type, const U32 index); + + // We no longer need this message in the current viewer, but send + // it for now to maintain compatibility with release viewers. Can + // remove this function once the SH-3455 changesets are universally deployed. + void sendDummyAgentWearablesUpdate(); + + //-------------------------------------------------------------------- + // Static UI hooks + //-------------------------------------------------------------------- +public: + static void userRemoveWearable(const LLWearableType::EType &type, const U32 &index); + static void userRemoveWearablesOfType(const LLWearableType::EType &type); + + typedef std::vector llvo_vec_t; + + static void findAttachmentsAddRemoveInfo(LLInventoryModel::item_array_t& obj_item_array, + llvo_vec_t& objects_to_remove, + llvo_vec_t& objects_to_retain, + LLInventoryModel::item_array_t& items_to_add); + static void userRemoveMultipleAttachments(llvo_vec_t& llvo_array); + static void userAttachMultipleAttachments(LLInventoryModel::item_array_t& obj_item_array); + + static llvo_vec_t getTempAttachments(); + + //-------------------------------------------------------------------- + // Signals + //-------------------------------------------------------------------- +public: + typedef boost::function loading_started_callback_t; + typedef boost::signals2::signal loading_started_signal_t; + boost::signals2::connection addLoadingStartedCallback(loading_started_callback_t cb); + + typedef boost::function loaded_callback_t; + typedef boost::signals2::signal loaded_signal_t; + boost::signals2::connection addLoadedCallback(loaded_callback_t cb); + + bool changeInProgress() const; + void notifyLoadingStarted(); + void notifyLoadingFinished(); + +private: + loading_started_signal_t mLoadingStartedSignal; // should be called before wearables are changed + loaded_signal_t mLoadedSignal; // emitted when all agent wearables get loaded + + //-------------------------------------------------------------------- + // Member variables + //-------------------------------------------------------------------- +private: + static bool mInitialWearablesUpdateReceived; + bool mWearablesLoaded; + + /** + * True if agent's outfit is being changed now. + */ + bool mCOFChangeInProgress; + LLTimer mCOFChangeTimer; + + //-------------------------------------------------------------------------------- + // Support classes + //-------------------------------------------------------------------------------- +private: + class AddWearableToAgentInventoryCallback : public LLInventoryCallback + { + public: + enum ETodo + { + CALL_NONE = 0, + CALL_UPDATE = 1, + CALL_RECOVERDONE = 2, + CALL_CREATESTANDARDDONE = 4, + CALL_MAKENEWOUTFITDONE = 8, + CALL_WEARITEM = 16 + }; + + AddWearableToAgentInventoryCallback(LLPointer cb, + LLWearableType::EType type, + U32 index, + LLViewerWearable* wearable, + U32 todo = CALL_NONE, + const std::string description = ""); + virtual void fire(const LLUUID& inv_item); + private: + LLWearableType::EType mType; + U32 mIndex; + LLViewerWearable* mWearable; + U32 mTodo; + LLPointer mCB; + std::string mDescription; + }; + +}; // LLAgentWearables + +extern LLAgentWearables gAgentWearables; + +//-------------------------------------------------------------------- +// Types +//-------------------------------------------------------------------- + +#endif // LL_AGENTWEARABLES_H diff --git a/indra/newview/llaisapi.cpp b/indra/newview/llaisapi.cpp index 8779baeff4..0dbd2d2ba3 100644 --- a/indra/newview/llaisapi.cpp +++ b/indra/newview/llaisapi.cpp @@ -1,1775 +1,1775 @@ -/** - * @file llaisapi.cpp - * @brief classes and functions for interfacing with the v3+ ais inventory service. - * - * $LicenseInfo:firstyear=2013&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2013, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - * - */ - -#include "llviewerprecompiledheaders.h" -#include "llaisapi.h" - -#include "llagent.h" -#include "llappviewer.h" -#include "llcallbacklist.h" -#include "llinventorymodel.h" -#include "llinventoryobserver.h" -#include "llnotificationsutil.h" -#include "llsdutil.h" -#include "llviewerregion.h" -#include "llvoavatar.h" -#include "llvoavatarself.h" -#include "llviewercontrol.h" - -///---------------------------------------------------------------------------- -/// Classes for AISv3 support. -///---------------------------------------------------------------------------- - -//========================================================================= -const std::string AISAPI::INVENTORY_CAP_NAME("InventoryAPIv3"); -const std::string AISAPI::LIBRARY_CAP_NAME("LibraryAPIv3"); -const S32 AISAPI::HTTP_TIMEOUT = 180; - -std::list AISAPI::sPostponedQuery; - -const S32 MAX_SIMULTANEOUS_COROUTINES = 2048; - -// AIS3 allows '*' requests, but in reality those will be cut at some point -// Specify own depth to be able to anticipate it and mark folders as incomplete -const S32 MAX_FOLDER_DEPTH_REQUEST = 50; - -//------------------------------------------------------------------------- -/*static*/ -bool AISAPI::isAvailable() -{ - if (gAgent.getRegion()) - { - return gAgent.getRegion()->isCapabilityAvailable(INVENTORY_CAP_NAME); - } - return false; -} - -/*static*/ -void AISAPI::getCapNames(LLSD& capNames) -{ - capNames.append(INVENTORY_CAP_NAME); - capNames.append(LIBRARY_CAP_NAME); -} - -/*static*/ -std::string AISAPI::getInvCap() -{ - if (gAgent.getRegion()) - { - return gAgent.getRegion()->getCapability(INVENTORY_CAP_NAME); - } - return std::string(); -} - -/*static*/ -std::string AISAPI::getLibCap() -{ - if (gAgent.getRegion()) - { - return gAgent.getRegion()->getCapability(LIBRARY_CAP_NAME); - } - return std::string(); -} - -/*static*/ -void AISAPI::CreateInventory(const LLUUID& parentId, const LLSD& newInventory, completion_t callback) -{ - std::string cap = getInvCap(); - if (cap.empty()) - { - LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; - if (callback) - { - callback(LLUUID::null); - } - return; - } - - LLUUID tid; - tid.generate(); - - std::string url = cap + std::string("/category/") + parentId.asString() + "?tid=" + tid.asString(); - LL_DEBUGS("Inventory") << "url: " << url << " parentID " << parentId << " newInventory " << newInventory << LL_ENDL; - - // I may be suffering from golden hammer here, but the first part of this bind - // is actually a static cast for &HttpCoroutineAdapter::postAndSuspend so that - // the compiler can identify the correct signature to select. - // - // Reads as follows: - // LLSD - method returning LLSD - // (LLCoreHttpUtil::HttpCoroutineAdapter::*) - pointer to member function of HttpCoroutineAdapter - // (LLCore::HttpRequest::ptr_t, const std::string &, const LLSD &, LLCore::HttpOptions::ptr_t, LLCore::HttpHeaders::ptr_t) - signature of method - // - invokationFn_t postFn = boost::bind( - // Humans ignore next line. It is just a cast. - static_cast - //---- - // _1 -> httpAdapter - // _2 -> httpRequest - // _3 -> url - // _4 -> body - // _5 -> httpOptions - // _6 -> httpHeaders - (&LLCoreHttpUtil::HttpCoroutineAdapter::postAndSuspend), _1, _2, _3, _4, _5, _6); - - LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, - _1, postFn, url, parentId, newInventory, callback, CREATEINVENTORY)); - EnqueueAISCommand("CreateInventory", proc); -} - -/*static*/ -void AISAPI::SlamFolder(const LLUUID& folderId, const LLSD& newInventory, completion_t callback) -{ - std::string cap = getInvCap(); - if (cap.empty()) - { - LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; - if (callback) - { - callback(LLUUID::null); - } - return; - } - - LLUUID tid; - tid.generate(); - - std::string url = cap + std::string("/category/") + folderId.asString() + "/links?tid=" + tid.asString(); - - // see comment above in CreateInventoryCommand - invokationFn_t putFn = boost::bind( - // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. - static_cast - //---- - // _1 -> httpAdapter - // _2 -> httpRequest - // _3 -> url - // _4 -> body - // _5 -> httpOptions - // _6 -> httpHeaders - (&LLCoreHttpUtil::HttpCoroutineAdapter::putAndSuspend), _1, _2, _3, _4, _5, _6); - - LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, - _1, putFn, url, folderId, newInventory, callback, SLAMFOLDER)); - - EnqueueAISCommand("SlamFolder", proc); -} - -void AISAPI::RemoveCategory(const LLUUID &categoryId, completion_t callback) -{ - std::string cap; - - cap = getInvCap(); - if (cap.empty()) - { - LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; - if (callback) - { - callback(LLUUID::null); - } - return; - } - - std::string url = cap + std::string("/category/") + categoryId.asString(); - LL_DEBUGS("Inventory") << "url: " << url << LL_ENDL; - - invokationFn_t delFn = boost::bind( - // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. - static_cast - //---- - // _1 -> httpAdapter - // _2 -> httpRequest - // _3 -> url - // _4 -> body - // _5 -> httpOptions - // _6 -> httpHeaders - (&LLCoreHttpUtil::HttpCoroutineAdapter::deleteAndSuspend), _1, _2, _3, _5, _6); - - LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, - _1, delFn, url, categoryId, LLSD(), callback, REMOVECATEGORY)); - - EnqueueAISCommand("RemoveCategory", proc); -} - -/*static*/ -void AISAPI::RemoveItem(const LLUUID &itemId, completion_t callback) -{ - std::string cap; - - cap = getInvCap(); - if (cap.empty()) - { - LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; - if (callback) - { - callback(LLUUID::null); - } - return; - } - - std::string url = cap + std::string("/item/") + itemId.asString(); - LL_DEBUGS("Inventory") << "url: " << url << LL_ENDL; - - invokationFn_t delFn = boost::bind( - // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. - static_cast - //---- - // _1 -> httpAdapter - // _2 -> httpRequest - // _3 -> url - // _4 -> body - // _5 -> httpOptions - // _6 -> httpHeaders - (&LLCoreHttpUtil::HttpCoroutineAdapter::deleteAndSuspend), _1, _2, _3, _5, _6); - - LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, - _1, delFn, url, itemId, LLSD(), callback, REMOVEITEM)); - - EnqueueAISCommand("RemoveItem", proc); -} - -void AISAPI::CopyLibraryCategory(const LLUUID& sourceId, const LLUUID& destId, bool copySubfolders, completion_t callback) -{ - std::string cap; - - cap = getLibCap(); - if (cap.empty()) - { - LL_WARNS("Inventory") << "Library cap not found!" << LL_ENDL; - if (callback) - { - callback(LLUUID::null); - } - return; - } - - LL_DEBUGS("Inventory") << "Copying library category: " << sourceId << " => " << destId << LL_ENDL; - - LLUUID tid; - tid.generate(); - - std::string url = cap + std::string("/category/") + sourceId.asString() + "?tid=" + tid.asString(); - if (!copySubfolders) - { - url += ",depth=0"; - } - LL_INFOS() << url << LL_ENDL; - - std::string destination = destId.asString(); - - invokationFn_t copyFn = boost::bind( - // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. - static_cast - //---- - // _1 -> httpAdapter - // _2 -> httpRequest - // _3 -> url - // _4 -> body - // _5 -> httpOptions - // _6 -> httpHeaders - (&LLCoreHttpUtil::HttpCoroutineAdapter::copyAndSuspend), _1, _2, _3, destination, _5, _6); - - LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, - _1, copyFn, url, destId, LLSD(), callback, COPYLIBRARYCATEGORY)); - - EnqueueAISCommand("CopyLibraryCategory", proc); -} - -/*static*/ -void AISAPI::PurgeDescendents(const LLUUID &categoryId, completion_t callback) -{ - std::string cap; - - cap = getInvCap(); - if (cap.empty()) - { - LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; - if (callback) - { - callback(LLUUID::null); - } - return; - } - - std::string url = cap + std::string("/category/") + categoryId.asString() + "/children"; - LL_DEBUGS("Inventory") << "url: " << url << LL_ENDL; - - invokationFn_t delFn = boost::bind( - // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. - static_cast - //---- - // _1 -> httpAdapter - // _2 -> httpRequest - // _3 -> url - // _4 -> body - // _5 -> httpOptions - // _6 -> httpHeaders - (&LLCoreHttpUtil::HttpCoroutineAdapter::deleteAndSuspend), _1, _2, _3, _5, _6); - - LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, - _1, delFn, url, categoryId, LLSD(), callback, PURGEDESCENDENTS)); - - EnqueueAISCommand("PurgeDescendents", proc); -} - - -/*static*/ -void AISAPI::UpdateCategory(const LLUUID &categoryId, const LLSD &updates, completion_t callback) -{ - std::string cap; - - cap = getInvCap(); - if (cap.empty()) - { - LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; - if (callback) - { - callback(LLUUID::null); - } - return; - } - std::string url = cap + std::string("/category/") + categoryId.asString(); - - invokationFn_t patchFn = boost::bind( - // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. - static_cast - //---- - // _1 -> httpAdapter - // _2 -> httpRequest - // _3 -> url - // _4 -> body - // _5 -> httpOptions - // _6 -> httpHeaders - (&LLCoreHttpUtil::HttpCoroutineAdapter::patchAndSuspend), _1, _2, _3, _4, _5, _6); - - LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, - _1, patchFn, url, categoryId, updates, callback, UPDATECATEGORY)); - - EnqueueAISCommand("UpdateCategory", proc); -} - -/*static*/ -void AISAPI::UpdateItem(const LLUUID &itemId, const LLSD &updates, completion_t callback) -{ - - std::string cap; - - cap = getInvCap(); - if (cap.empty()) - { - LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; - if (callback) - { - callback(LLUUID::null); - } - return; - } - std::string url = cap + std::string("/item/") + itemId.asString(); - - invokationFn_t patchFn = boost::bind( - // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. - static_cast - //---- - // _1 -> httpAdapter - // _2 -> httpRequest - // _3 -> url - // _4 -> body - // _5 -> httpOptions - // _6 -> httpHeaders - (&LLCoreHttpUtil::HttpCoroutineAdapter::patchAndSuspend), _1, _2, _3, _4, _5, _6); - - LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, - _1, patchFn, url, itemId, updates, callback, UPDATEITEM)); - - EnqueueAISCommand("UpdateItem", proc); -} - -/*static*/ -void AISAPI::FetchItem(const LLUUID &itemId, ITEM_TYPE type, completion_t callback) -{ - std::string cap; - - cap = (type == INVENTORY) ? getInvCap() : getLibCap(); - if (cap.empty()) - { - LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; - if (callback) - { - callback(LLUUID::null); - } - return; - } - std::string url = cap + std::string("/item/") + itemId.asString(); - - invokationFn_t getFn = boost::bind( - // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. - static_cast - //---- - // _1 -> httpAdapter - // _2 -> httpRequest - // _3 -> url - // _4 -> body - // _5 -> httpOptions - // _6 -> httpHeaders - (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), _1, _2, _3, _5, _6); - - LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, - _1, getFn, url, itemId, LLSD(), callback, FETCHITEM)); - - EnqueueAISCommand("FetchItem", proc); -} - -/*static*/ -void AISAPI::FetchCategoryChildren(const LLUUID &catId, ITEM_TYPE type, bool recursive, completion_t callback, S32 depth) -{ - std::string cap; - - cap = (type == INVENTORY) ? getInvCap() : getLibCap(); - if (cap.empty()) - { - LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; - if (callback) - { - callback(LLUUID::null); - } - return; - } - std::string url = cap + std::string("/category/") + catId.asString() + "/children"; - - if (recursive) - { - // can specify depth=*, but server side is going to cap requests - // and reject everything 'over the top',. - depth = MAX_FOLDER_DEPTH_REQUEST; - } - else - { - depth = llmin(depth, MAX_FOLDER_DEPTH_REQUEST); - } - - url += "?depth=" + std::to_string(depth); - - invokationFn_t getFn = boost::bind( - // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. - static_cast - //---- - // _1 -> httpAdapter - // _2 -> httpRequest - // _3 -> url - // _4 -> body - // _5 -> httpOptions - // _6 -> httpHeaders - (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), _1, _2, _3, _5, _6); - - // get doesn't use body, can pass additional data - LLSD body; - body["depth"] = depth; - LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, - _1, getFn, url, catId, body, callback, FETCHCATEGORYCHILDREN)); - - EnqueueAISCommand("FetchCategoryChildren", proc); -} - -// some folders can be requested by name, like -// animatn | bodypart | clothing | current | favorite | gesture | inbox | landmark | lsltext -// lstndfnd | my_otfts | notecard | object | outbox | root | snapshot | sound | texture | trash -void AISAPI::FetchCategoryChildren(const std::string &identifier, bool recursive, completion_t callback, S32 depth) -{ - std::string cap; - - cap = getInvCap(); - if (cap.empty()) - { - LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; - if (callback) - { - callback(LLUUID::null); - } - return; - } - std::string url = cap + std::string("/category/") + identifier + "/children"; - - if (recursive) - { - // can specify depth=*, but server side is going to cap requests - // and reject everything 'over the top',. - depth = MAX_FOLDER_DEPTH_REQUEST; - } - else - { - depth = llmin(depth, MAX_FOLDER_DEPTH_REQUEST); - } - - url += "?depth=" + std::to_string(depth); - - invokationFn_t getFn = boost::bind( - // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. - static_cast - //---- - // _1 -> httpAdapter - // _2 -> httpRequest - // _3 -> url - // _4 -> body - // _5 -> httpOptions - // _6 -> httpHeaders - (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), _1, _2, _3, _5, _6); - - // get doesn't use body, can pass additional data - LLSD body; - body["depth"] = depth; - LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, - _1, getFn, url, LLUUID::null, body, callback, FETCHCATEGORYCHILDREN)); - - EnqueueAISCommand("FetchCategoryChildren", proc); -} - -/*static*/ -void AISAPI::FetchCategoryCategories(const LLUUID &catId, ITEM_TYPE type, bool recursive, completion_t callback, S32 depth) -{ - std::string cap; - - cap = (type == INVENTORY) ? getInvCap() : getLibCap(); - if (cap.empty()) - { - LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; - if (callback) - { - callback(LLUUID::null); - } - return; - } - std::string url = cap + std::string("/category/") + catId.asString() + "/categories"; - - if (recursive) - { - // can specify depth=*, but server side is going to cap requests - // and reject everything 'over the top',. - depth = MAX_FOLDER_DEPTH_REQUEST; - } - else - { - depth = llmin(depth, MAX_FOLDER_DEPTH_REQUEST); - } - - url += "?depth=" + std::to_string(depth); - - invokationFn_t getFn = boost::bind( - // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. - static_cast - //---- - // _1 -> httpAdapter - // _2 -> httpRequest - // _3 -> url - // _4 -> body - // _5 -> httpOptions - // _6 -> httpHeaders - (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), _1, _2, _3, _5, _6); - - // get doesn't use body, can pass additional data - LLSD body; - body["depth"] = depth; - LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, - _1, getFn, url, catId, body, callback, FETCHCATEGORYCATEGORIES)); - - EnqueueAISCommand("FetchCategoryCategories", proc); -} - -void AISAPI::FetchCategorySubset(const LLUUID& catId, - const uuid_vec_t specificChildren, - ITEM_TYPE type, - bool recursive, - completion_t callback, - S32 depth) -{ - std::string cap = (type == INVENTORY) ? getInvCap() : getLibCap(); - if (cap.empty()) - { - LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; - if (callback) - { - callback(LLUUID::null); - } - return; - } - if (specificChildren.empty()) - { - LL_WARNS("Inventory") << "Empty request!" << LL_ENDL; - if (callback) - { - callback(LLUUID::null); - } - return; - } - // category/any_folder_id/children?depth=*&children=child_id1,child_id2,child_id3 - std::string url = cap + std::string("/category/") + catId.asString() + "/children"; - - if (recursive) - { - depth = MAX_FOLDER_DEPTH_REQUEST; - } - else - { - depth = llmin(depth, MAX_FOLDER_DEPTH_REQUEST); - } - - uuid_vec_t::const_iterator iter = specificChildren.begin(); - uuid_vec_t::const_iterator end = specificChildren.end(); - - url += "?depth=" + std::to_string(depth) + "&children=" + iter->asString(); - iter++; - - while (iter != end) - { - url += "," + iter->asString(); - iter++; - } - - const S32 MAX_URL_LENGH = 2000; // RFC documentation specifies a maximum length of 2048 - if (url.length() > MAX_URL_LENGH) - { - LL_WARNS("Inventory") << "Request url is too long, url: " << url << LL_ENDL; - } - - invokationFn_t getFn = boost::bind( - // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. - static_cast - //---- - // _1 -> httpAdapter - // _2 -> httpRequest - // _3 -> url - // _4 -> body - // _5 -> httpOptions - // _6 -> httpHeaders - (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), _1, _2, _3, _5, _6); - - // get doesn't use body, can pass additional data - LLSD body; - body["depth"] = depth; - LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, - _1, getFn, url, catId, body, callback, FETCHCATEGORYSUBSET)); - - EnqueueAISCommand("FetchCategorySubset", proc); -} - -/*static*/ -// Will get COF folder, links in it and items those links point to -void AISAPI::FetchCOF(completion_t callback) -{ - std::string cap = getInvCap(); - if (cap.empty()) - { - LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; - if (callback) - { - callback(LLUUID::null); - } - return; - } - std::string url = cap + std::string("/category/current/links"); - - invokationFn_t getFn = boost::bind( - // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. - static_cast - //---- - // _1 -> httpAdapter - // _2 -> httpRequest - // _3 -> url - // _4 -> body - // _5 -> httpOptions - // _6 -> httpHeaders - (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), _1, _2, _3, _5, _6); - - LLSD body; - // Only cof folder will be full, but cof can contain an outfit - // link with embedded outfit folder for request to parse - body["depth"] = 0; - LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, - _1, getFn, url, LLUUID::null, body, callback, FETCHCOF)); - - EnqueueAISCommand("FetchCOF", proc); -} - -void AISAPI::FetchCategoryLinks(const LLUUID &catId, completion_t callback) -{ - std::string cap = getInvCap(); - if (cap.empty()) - { - LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; - if (callback) - { - callback(LLUUID::null); - } - return; - } - std::string url = cap + std::string("/category/") + catId.asString() + "/links"; - - invokationFn_t getFn = boost::bind( - // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. - static_cast - //---- - // _1 -> httpAdapter - // _2 -> httpRequest - // _3 -> url - // _4 -> body - // _5 -> httpOptions - // _6 -> httpHeaders - (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), - _1, _2, _3, _5, _6); - - LLSD body; - body["depth"] = 0; - LLCoprocedureManager::CoProcedure_t proc( - boost::bind(&AISAPI::InvokeAISCommandCoro, _1, getFn, url, LLUUID::null, body, callback, FETCHCATEGORYLINKS)); - - EnqueueAISCommand("FetchCategoryLinks", proc); -} - -/*static*/ -void AISAPI::FetchOrphans(completion_t callback) -{ - std::string cap = getInvCap(); - if (cap.empty()) - { - LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; - if (callback) - { - callback(LLUUID::null); - } - return; - } - std::string url = cap + std::string("/orphans"); - - invokationFn_t getFn = boost::bind( - // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. - static_cast - //---- - // _1 -> httpAdapter - // _2 -> httpRequest - // _3 -> url - // _4 -> body - // _5 -> httpOptions - // _6 -> httpHeaders - (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend) , _1 , _2 , _3 , _5 , _6); - - LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro , - _1 , getFn , url , LLUUID::null , LLSD() , callback , FETCHORPHANS)); - - EnqueueAISCommand("FetchOrphans" , proc); -} - -/*static*/ -void AISAPI::EnqueueAISCommand(const std::string &procName, LLCoprocedureManager::CoProcedure_t proc) -{ - LLCoprocedureManager &inst = LLCoprocedureManager::instance(); - S32 pending_in_pool = inst.countPending("AIS"); - std::string procFullName = "AIS(" + procName + ")"; - if (pending_in_pool < MAX_SIMULTANEOUS_COROUTINES) - { - inst.enqueueCoprocedure("AIS", procFullName, proc); - } - else - { - // As I understand it, coroutines have built-in 'pending' pool - // but unfortunately it has limited size which inventory often goes over - // so this is a workaround to not overfill it. - if (sPostponedQuery.empty()) - { - sPostponedQuery.push_back(ais_query_item_t(procFullName, proc)); - gIdleCallbacks.addFunction(onIdle, NULL); - } - else - { - sPostponedQuery.push_back(ais_query_item_t(procFullName, proc)); - } - } -} - -/*static*/ -void AISAPI::onIdle(void *userdata) -{ - if (!sPostponedQuery.empty()) - { - LLCoprocedureManager &inst = LLCoprocedureManager::instance(); - S32 pending_in_pool = inst.countPending("AIS"); - while (pending_in_pool < MAX_SIMULTANEOUS_COROUTINES && !sPostponedQuery.empty()) - { - ais_query_item_t &item = sPostponedQuery.front(); - inst.enqueueCoprocedure("AIS", item.first, item.second); - sPostponedQuery.pop_front(); - pending_in_pool++; - } - } - - if (sPostponedQuery.empty()) - { - // Nothing to do anymore - gIdleCallbacks.deleteFunction(onIdle, NULL); - } -} - -/*static*/ -void AISAPI::onUpdateReceived(const LLSD& update, COMMAND_TYPE type, const LLSD& request_body) -{ - LLTimer timer; - if ( (type == UPDATECATEGORY || type == UPDATEITEM) - && gSavedSettings.getBOOL("DebugAvatarAppearanceMessage")) - { - dump_sequential_xml(gAgentAvatarp->getFullname() + "_ais_update", update); - } - - AISUpdate ais_update(update, type, request_body); - ais_update.doUpdate(); // execute the updates in the appropriate order. - LL_DEBUGS("Inventory", "AIS3") << "Elapsed processing: " << timer.getElapsedTimeF32() << LL_ENDL; -} - -/*static*/ -void AISAPI::InvokeAISCommandCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter, - invokationFn_t invoke, std::string url, - LLUUID targetId, LLSD body, completion_t callback, COMMAND_TYPE type) -{ - if (gDisconnected) - { - if (callback) - { - callback(LLUUID::null); - } - return; - } - - LLCore::HttpOptions::ptr_t httpOptions(new LLCore::HttpOptions); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest()); - LLCore::HttpHeaders::ptr_t httpHeaders; - - httpOptions->setTimeout(HTTP_TIMEOUT); - - LL_DEBUGS("Inventory") << "Request url: " << url << LL_ENDL; - - LLSD result; - LLSD httpResults; - LLCore::HttpStatus status; - - result = invoke(httpAdapter , httpRequest , url , body , httpOptions , httpHeaders); - httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status || !result.isMap()) - { - if (!result.isMap()) - { - status = LLCore::HttpStatus(HTTP_INTERNAL_ERROR, "Malformed response contents"); - } - else if (status.getType() == 410) //GONE - { - // Item does not exist or was already deleted from server. - // parent folder is out of sync - if (type == REMOVECATEGORY) - { - LLViewerInventoryCategory *cat = gInventory.getCategory(targetId); - if (cat) - { - LL_WARNS("Inventory") << "Purge failed for '" << cat->getName() - << "' local version:" << cat->getVersion() - << " since folder no longer exists at server. Descendent count: server == " << cat->getDescendentCount() - << ", viewer == " << cat->getViewerDescendentCount() - << LL_ENDL; - gInventory.fetchDescendentsOf(cat->getParentUUID()); - // Note: don't delete folder here - contained items will be deparented (or deleted) - // and since we are clearly out of sync we can't be sure we won't get rid of something we need. - // For example folder could have been moved or renamed with items intact, let it fetch first. - } - } - else if (type == REMOVEITEM) - { - LLViewerInventoryItem *item = gInventory.getItem(targetId); - if (item) - { - LL_WARNS("Inventory") << "Purge failed for '" << item->getName() - << "' since item no longer exists at server." << LL_ENDL; - gInventory.fetchDescendentsOf(item->getParentUUID()); - // since item not on the server and exists at viewer, so it needs an update at the least, - // so delete it, in worst case item will be refetched with new params. - gInventory.onObjectDeletedFromServer(targetId); - } - } - } - else if (status == LLCore::HttpStatus(HTTP_FORBIDDEN) /*403*/) - { - if (type == FETCHCATEGORYCHILDREN) - { - if (body.has("depth") && body["depth"].asInteger() == 0) - { - // Can't fetch a single folder with depth 0, folder is too big. - static bool first_call = true; - if (first_call) - { - first_call = false; - LLNotificationsUtil::add("InventoryLimitReachedAISAlert"); - } - else - { - LLNotificationsUtil::add("InventoryLimitReachedAIS"); - } - LL_WARNS("Inventory") << "Fetch failed, content is over limit, url: " << url << LL_ENDL; - } - else - { - // Result was too big, but situation is recoverable by requesting with lower depth - LL_DEBUGS("Inventory") << "Fetch failed, content is over limit, url: " << url << LL_ENDL; - } - } - } - LL_WARNS("Inventory") << "Inventory error: " << status.toString() << LL_ENDL; - LL_WARNS("Inventory") << ll_pretty_print_sd(result) << LL_ENDL; - } - - LL_DEBUGS("Inventory", "AIS3") << "Result: " << result << LL_ENDL; - onUpdateReceived(result, type, body); - - if (callback && !callback.empty()) - { - bool needs_callback = true; - LLUUID id(LLUUID::null); - - switch (type) - { - case COPYLIBRARYCATEGORY: - case FETCHCATEGORYCATEGORIES: - case FETCHCATEGORYCHILDREN: - case FETCHCATEGORYSUBSET: - case FETCHCATEGORYLINKS: - case FETCHCOF: - if (result.has("category_id")) - { - id = result["category_id"]; - } - break; - case FETCHITEM: - if (result.has("item_id")) - { - // Error message might contain an item_id!!! - id = result["item_id"]; - } - if (result.has("linked_id")) - { - id = result["linked_id"]; - } - break; - case CREATEINVENTORY: - // CREATEINVENTORY can have multiple callbacks - if (result.has("_created_categories")) - { - LLSD& cats = result["_created_categories"]; - LLSD::array_const_iterator cat_iter; - for (cat_iter = cats.beginArray(); cat_iter != cats.endArray(); ++cat_iter) - { - LLUUID cat_id = *cat_iter; - callback(cat_id); - needs_callback = false; - } - } - if (result.has("_created_items")) - { - LLSD& items = result["_created_items"]; - LLSD::array_const_iterator item_iter; - for (item_iter = items.beginArray(); item_iter != items.endArray(); ++item_iter) - { - LLUUID item_id = *item_iter; - callback(item_id); - needs_callback = false; - } - } - break; - default: - break; - } - - if (needs_callback) - { - // Call callback at least once regardless of failure. - // UPDATEITEM doesn't expect an id - callback(id); - } - } - -} - -//------------------------------------------------------------------------- -AISUpdate::AISUpdate(const LLSD& update, AISAPI::COMMAND_TYPE type, const LLSD& request_body) -: mType(type) -{ - mFetch = (type == AISAPI::FETCHITEM) - || (type == AISAPI::FETCHCATEGORYCHILDREN) - || (type == AISAPI::FETCHCATEGORYCATEGORIES) - || (type == AISAPI::FETCHCATEGORYSUBSET) - || (type == AISAPI::FETCHCOF) - || (type == AISAPI::FETCHCATEGORYLINKS) - || (type == AISAPI::FETCHORPHANS); - // parse update llsd into stuff to do or parse received items. - mFetchDepth = MAX_FOLDER_DEPTH_REQUEST; - if (mFetch && request_body.has("depth")) - { - mFetchDepth = request_body["depth"].asInteger(); - } - - mTimer.setTimerExpirySec(AIS_EXPIRY_SECONDS); - mTimer.start(); - parseUpdate(update); -} - -void AISUpdate::clearParseResults() -{ - mCatDescendentDeltas.clear(); - mCatDescendentsKnown.clear(); - mCatVersionsUpdated.clear(); - mItemsCreated.clear(); - mItemsLost.clear(); - mItemsUpdated.clear(); - mCategoriesCreated.clear(); - mCategoriesUpdated.clear(); - mObjectsDeletedIds.clear(); - mItemIds.clear(); - mCategoryIds.clear(); -} - -void AISUpdate::checkTimeout() -{ - if (mTimer.hasExpired()) - { - llcoro::suspend(); - LLCoros::checkStop(); - mTimer.setTimerExpirySec(AIS_EXPIRY_SECONDS); - } -} - -void AISUpdate::parseUpdate(const LLSD& update) -{ - clearParseResults(); - parseMeta(update); - parseContent(update); -} - -void AISUpdate::parseMeta(const LLSD& update) -{ - // parse _categories_removed -> mObjectsDeletedIds - uuid_list_t cat_ids; - parseUUIDArray(update,"_categories_removed",cat_ids); - for (uuid_list_t::const_iterator it = cat_ids.begin(); - it != cat_ids.end(); ++it) - { - LLViewerInventoryCategory *cat = gInventory.getCategory(*it); - if(cat) - { - mCatDescendentDeltas[cat->getParentUUID()]--; - mObjectsDeletedIds.insert(*it); - } - else - { - LL_WARNS("Inventory") << "removed category not found " << *it << LL_ENDL; - } - } - - // parse _categories_items_removed -> mObjectsDeletedIds - uuid_list_t item_ids; - parseUUIDArray(update,"_category_items_removed",item_ids); - parseUUIDArray(update,"_removed_items",item_ids); - for (uuid_list_t::const_iterator it = item_ids.begin(); - it != item_ids.end(); ++it) - { - LLViewerInventoryItem *item = gInventory.getItem(*it); - if(item) - { - mCatDescendentDeltas[item->getParentUUID()]--; - mObjectsDeletedIds.insert(*it); - } - else - { - LL_WARNS("Inventory") << "removed item not found " << *it << LL_ENDL; - } - } - - // parse _broken_links_removed -> mObjectsDeletedIds - uuid_list_t broken_link_ids; - parseUUIDArray(update,"_broken_links_removed",broken_link_ids); - for (uuid_list_t::const_iterator it = broken_link_ids.begin(); - it != broken_link_ids.end(); ++it) - { - LLViewerInventoryItem *item = gInventory.getItem(*it); - if(item) - { - mCatDescendentDeltas[item->getParentUUID()]--; - mObjectsDeletedIds.insert(*it); - } - else - { - LL_WARNS("Inventory") << "broken link not found " << *it << LL_ENDL; - } - } - - // parse _created_items - parseUUIDArray(update,"_created_items",mItemIds); - - // parse _created_categories - parseUUIDArray(update,"_created_categories",mCategoryIds); - - // Parse updated category versions. - const std::string& ucv = "_updated_category_versions"; - if (update.has(ucv)) - { - for(LLSD::map_const_iterator it = update[ucv].beginMap(), - end = update[ucv].endMap(); - it != end; ++it) - { - const LLUUID id((*it).first); - S32 version = (*it).second.asInteger(); - mCatVersionsUpdated[id] = version; - } - } -} - -void AISUpdate::parseContent(const LLSD& update) -{ - // Errors from a fetch request might contain id without - // full item or folder. - // Todo: Depending on error we might want to do something, - // like removing a 404 item or refetching parent folder - if (update.has("linked_id") && update.has("parent_id")) - { - parseLink(update, mFetchDepth); - } - else if (update.has("item_id") && update.has("parent_id")) - { - parseItem(update); - } - - if (mType == AISAPI::FETCHCATEGORYSUBSET) - { - // initial category is incomplete, don't process it, - // go for content instead - if (update.has("_embedded")) - { - parseEmbedded(update["_embedded"], mFetchDepth - 1); - } - } - else if (update.has("category_id") && update.has("parent_id")) - { - parseCategory(update, mFetchDepth); - } - else - { - if (update.has("_embedded")) - { - parseEmbedded(update["_embedded"], mFetchDepth); - } - } -} - -void AISUpdate::parseItem(const LLSD& item_map) -{ - LLUUID item_id = item_map["item_id"].asUUID(); - LLPointer new_item(new LLViewerInventoryItem); - LLViewerInventoryItem *curr_item = gInventory.getItem(item_id); - if (curr_item) - { - // Default to current values where not provided. - new_item->copyViewerItem(curr_item); - } - bool rv = new_item->unpackMessage(item_map); - if (rv) - { - if (mFetch) - { - mItemsCreated[item_id] = new_item; - new_item->setComplete(true); - - if (new_item->getParentUUID().isNull()) - { - mItemsLost[item_id] = new_item; - } - } - else if (curr_item) - { - mItemsUpdated[item_id] = new_item; - // This statement is here to cause a new entry with 0 - // delta to be created if it does not already exist; - // otherwise has no effect. - mCatDescendentDeltas[new_item->getParentUUID()]; - } - else - { - mItemsCreated[item_id] = new_item; - mCatDescendentDeltas[new_item->getParentUUID()]++; - new_item->setComplete(true); - } - } - else - { - // *TODO: Wow, harsh. Should we just complain and get out? - LL_ERRS() << "unpack failed" << LL_ENDL; - } -} - -void AISUpdate::parseLink(const LLSD& link_map, S32 depth) -{ - LLUUID item_id = link_map["item_id"].asUUID(); - LLPointer new_link(new LLViewerInventoryItem); - LLViewerInventoryItem *curr_link = gInventory.getItem(item_id); - if (curr_link) - { - // Default to current values where not provided. - new_link->copyViewerItem(curr_link); - } - bool rv = new_link->unpackMessage(link_map); - if (rv) - { - const LLUUID& parent_id = new_link->getParentUUID(); - if (mFetch) - { - LLPermissions default_perms; - default_perms.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null); - default_perms.initMasks(PERM_NONE, PERM_NONE, PERM_NONE, PERM_NONE, PERM_NONE); - new_link->setPermissions(default_perms); - LLSaleInfo default_sale_info; - new_link->setSaleInfo(default_sale_info); - //LL_DEBUGS("Inventory") << "creating link from llsd: " << ll_pretty_print_sd(link_map) << LL_ENDL; - mItemsCreated[item_id] = new_link; - new_link->setComplete(true); - - if (new_link->getParentUUID().isNull()) - { - mItemsLost[item_id] = new_link; - } - } - else if (curr_link) - { - mItemsUpdated[item_id] = new_link; - // This statement is here to cause a new entry with 0 - // delta to be created if it does not already exist; - // otherwise has no effect. - mCatDescendentDeltas[parent_id]; - } - else - { - LLPermissions default_perms; - default_perms.init(gAgent.getID(),gAgent.getID(),LLUUID::null,LLUUID::null); - default_perms.initMasks(PERM_NONE,PERM_NONE,PERM_NONE,PERM_NONE,PERM_NONE); - new_link->setPermissions(default_perms); - LLSaleInfo default_sale_info; - new_link->setSaleInfo(default_sale_info); - //LL_DEBUGS("Inventory") << "creating link from llsd: " << ll_pretty_print_sd(link_map) << LL_ENDL; - mItemsCreated[item_id] = new_link; - mCatDescendentDeltas[parent_id]++; - new_link->setComplete(true); - } - - if (link_map.has("_embedded")) - { - parseEmbedded(link_map["_embedded"], depth); - } - } - else - { - // *TODO: Wow, harsh. Should we just complain and get out? - LL_ERRS() << "unpack failed" << LL_ENDL; - } -} - - -void AISUpdate::parseCategory(const LLSD& category_map, S32 depth) -{ - LLUUID category_id = category_map["category_id"].asUUID(); - S32 version = LLViewerInventoryCategory::VERSION_UNKNOWN; - - if (category_map.has("version")) - { - version = category_map["version"].asInteger(); - } - - LLViewerInventoryCategory *curr_cat = gInventory.getCategory(category_id); - - if (curr_cat - && curr_cat->getVersion() > LLViewerInventoryCategory::VERSION_UNKNOWN - && curr_cat->getDescendentCount() != LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN - && version > LLViewerInventoryCategory::VERSION_UNKNOWN - && version < curr_cat->getVersion()) - { - LL_WARNS() << "Got stale folder, known: " << curr_cat->getVersion() - << ", received: " << version << LL_ENDL; - return; - } - - LLPointer new_cat; - if (curr_cat) - { - // Default to current values where not provided. - new_cat = new LLViewerInventoryCategory(curr_cat); - } - else - { - if (category_map.has("agent_id")) - { - new_cat = new LLViewerInventoryCategory(category_map["agent_id"].asUUID()); - } - else - { - LL_DEBUGS() << "No owner provided, folder might be assigned wrong owner" << LL_ENDL; - new_cat = new LLViewerInventoryCategory(LLUUID::null); - } - } - bool rv = new_cat->unpackMessage(category_map); - // *NOTE: unpackMessage does not unpack version or descendent count. - if (rv) - { - // Check descendent count first, as it may be needed - // to populate newly created categories - if (category_map.has("_embedded")) - { - parseDescendentCount(category_id, new_cat->getPreferredType(), category_map["_embedded"]); - } - - if (mFetch) - { - uuid_int_map_t::const_iterator lookup_it = mCatDescendentsKnown.find(category_id); - if (mCatDescendentsKnown.end() != lookup_it) - { - S32 descendent_count = lookup_it->second; - LL_DEBUGS("Inventory") << "Setting descendents count to " << descendent_count - << " for category " << category_id << LL_ENDL; - new_cat->setDescendentCount(descendent_count); - - // set version only if we are sure this update has full data and embeded items - // since viewer uses version to decide if folder and content still need fetching - if (version > LLViewerInventoryCategory::VERSION_UNKNOWN - && depth >= 0) - { - if (curr_cat && curr_cat->getVersion() > version) - { - LL_WARNS("Inventory") << "Version was " << curr_cat->getVersion() - << ", but fetch returned version " << version - << " for category " << category_id << LL_ENDL; - } - else - { - LL_DEBUGS("Inventory") << "Setting version to " << version - << " for category " << category_id << LL_ENDL; - } - - new_cat->setVersion(version); - } - } - else if (curr_cat - && curr_cat->getVersion() > LLViewerInventoryCategory::VERSION_UNKNOWN - && version > curr_cat->getVersion()) - { - // Potentially should new_cat->setVersion(unknown) here, - // but might be waiting for a callback that would increment - LL_DEBUGS("Inventory") << "Category " << category_id - << " is stale. Known version: " << curr_cat->getVersion() - << " server version: " << version << LL_ENDL; - } - mCategoriesCreated[category_id] = new_cat; - } - else if (curr_cat) - { - mCategoriesUpdated[category_id] = new_cat; - // This statement is here to cause a new entry with 0 - // delta to be created if it does not already exist; - // otherwise has no effect. - mCatDescendentDeltas[new_cat->getParentUUID()]; - // Capture update for the category itself as well. - mCatDescendentDeltas[category_id]; - } - else - { - // Set version/descendents for newly created categories. - uuid_int_map_t::const_iterator lookup_it = mCatDescendentsKnown.find(category_id); - if (mCatDescendentsKnown.end() != lookup_it) - { - S32 descendent_count = lookup_it->second; - LL_DEBUGS("Inventory") << "Setting descendents count to " << descendent_count - << " for new category " << category_id << LL_ENDL; - new_cat->setDescendentCount(descendent_count); - - // Don't set version unles correct children count is present - if (category_map.has("version")) - { - S32 version = category_map["version"].asInteger(); - LL_DEBUGS("Inventory") << "Setting version to " << version - << " for new category " << category_id << LL_ENDL; - new_cat->setVersion(version); - } - } - mCategoriesCreated[category_id] = new_cat; - mCatDescendentDeltas[new_cat->getParentUUID()]++; - } - } - else - { - // *TODO: Wow, harsh. Should we just complain and get out? - LL_ERRS() << "unpack failed" << LL_ENDL; - } - - // Check for more embedded content. - if (category_map.has("_embedded")) - { - parseEmbedded(category_map["_embedded"], depth - 1); - } -} - -void AISUpdate::parseDescendentCount(const LLUUID& category_id, LLFolderType::EType type, const LLSD& embedded) -{ - // We can only determine true descendent count if this contains all descendent types. - if (embedded.has("categories") && - embedded.has("links") && - embedded.has("items")) - { - mCatDescendentsKnown[category_id] = embedded["categories"].size(); - mCatDescendentsKnown[category_id] += embedded["links"].size(); - mCatDescendentsKnown[category_id] += embedded["items"].size(); - } - else if (mFetch && embedded.has("links") && (type == LLFolderType::FT_CURRENT_OUTFIT || type == LLFolderType::FT_OUTFIT)) - { - // COF and outfits contain links only - mCatDescendentsKnown[category_id] = embedded["links"].size(); - } -} - -void AISUpdate::parseEmbedded(const LLSD& embedded, S32 depth) -{ - checkTimeout(); - - if (embedded.has("links")) // _embedded in a category - { - parseEmbeddedLinks(embedded["links"], depth); - } - if (embedded.has("items")) // _embedded in a category - { - parseEmbeddedItems(embedded["items"]); - } - if (embedded.has("item")) // _embedded in a link - { - parseEmbeddedItem(embedded["item"]); - } - if (embedded.has("categories")) // _embedded in a category - { - parseEmbeddedCategories(embedded["categories"], depth); - } - if (embedded.has("category")) // _embedded in a link - { - parseEmbeddedCategory(embedded["category"], depth); - } -} - -void AISUpdate::parseUUIDArray(const LLSD& content, const std::string& name, uuid_list_t& ids) -{ - if (content.has(name)) - { - for(LLSD::array_const_iterator it = content[name].beginArray(), - end = content[name].endArray(); - it != end; ++it) - { - ids.insert((*it).asUUID()); - } - } -} - -void AISUpdate::parseEmbeddedLinks(const LLSD& links, S32 depth) -{ - for(LLSD::map_const_iterator linkit = links.beginMap(), - linkend = links.endMap(); - linkit != linkend; ++linkit) - { - const LLUUID link_id((*linkit).first); - const LLSD& link_map = (*linkit).second; - if (!mFetch && mItemIds.end() == mItemIds.find(link_id)) - { - LL_DEBUGS("Inventory") << "Ignoring link not in items list " << link_id << LL_ENDL; - } - else - { - parseLink(link_map, depth); - } - } -} - -void AISUpdate::parseEmbeddedItem(const LLSD& item) -{ - // a single item (_embedded in a link) - if (item.has("item_id")) - { - if (mFetch || mItemIds.end() != mItemIds.find(item["item_id"].asUUID())) - { - parseItem(item); - } - } -} - -void AISUpdate::parseEmbeddedItems(const LLSD& items) -{ - // a map of items (_embedded in a category) - for(LLSD::map_const_iterator itemit = items.beginMap(), - itemend = items.endMap(); - itemit != itemend; ++itemit) - { - const LLUUID item_id((*itemit).first); - const LLSD& item_map = (*itemit).second; - if (!mFetch && mItemIds.end() == mItemIds.find(item_id)) - { - LL_DEBUGS("Inventory") << "Ignoring item not in items list " << item_id << LL_ENDL; - } - else - { - parseItem(item_map); - } - } -} - -void AISUpdate::parseEmbeddedCategory(const LLSD& category, S32 depth) -{ - // a single category (_embedded in a link) - if (category.has("category_id")) - { - if (mFetch || mCategoryIds.end() != mCategoryIds.find(category["category_id"].asUUID())) - { - parseCategory(category, depth); - } - } -} - -void AISUpdate::parseEmbeddedCategories(const LLSD& categories, S32 depth) -{ - // a map of categories (_embedded in a category) - for(LLSD::map_const_iterator categoryit = categories.beginMap(), - categoryend = categories.endMap(); - categoryit != categoryend; ++categoryit) - { - const LLUUID category_id((*categoryit).first); - const LLSD& category_map = (*categoryit).second; - if (!mFetch && mCategoryIds.end() == mCategoryIds.find(category_id)) - { - LL_DEBUGS("Inventory") << "Ignoring category not in categories list " << category_id << LL_ENDL; - } - else - { - parseCategory(category_map, depth); - } - } -} - -void AISUpdate::doUpdate() -{ - checkTimeout(); - - // Do version/descendant accounting. - for (std::map::const_iterator catit = mCatDescendentDeltas.begin(); - catit != mCatDescendentDeltas.end(); ++catit) - { - LL_DEBUGS("Inventory") << "descendant accounting for " << catit->first << LL_ENDL; - - const LLUUID cat_id(catit->first); - // Don't account for update if we just created this category. - if (mCategoriesCreated.find(cat_id) != mCategoriesCreated.end()) - { - LL_DEBUGS("Inventory") << "Skipping version increment for new category " << cat_id << LL_ENDL; - continue; - } - - // Don't account for update unless AIS told us it updated that category. - if (mCatVersionsUpdated.find(cat_id) == mCatVersionsUpdated.end()) - { - LL_DEBUGS("Inventory") << "Skipping version increment for non-updated category " << cat_id << LL_ENDL; - continue; - } - - // If we have a known descendant count, set that now. - LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); - if (cat) - { - S32 descendent_delta = catit->second; - S32 old_count = cat->getDescendentCount(); - LL_DEBUGS("Inventory") << "Updating descendant count for " - << cat->getName() << " " << cat_id - << " with delta " << descendent_delta << " from " - << old_count << " to " << (old_count+descendent_delta) << LL_ENDL; - LLInventoryModel::LLCategoryUpdate up(cat_id, descendent_delta); - gInventory.accountForUpdate(up); - } - else - { - LL_DEBUGS("Inventory") << "Skipping version accounting for unknown category " << cat_id << LL_ENDL; - } - } - - // CREATE CATEGORIES - const S32 MAX_UPDATE_BACKLOG = 50; // stall prevention - for (deferred_category_map_t::const_iterator create_it = mCategoriesCreated.begin(); - create_it != mCategoriesCreated.end(); ++create_it) - { - LLUUID category_id(create_it->first); - LLPointer new_category = create_it->second; - - gInventory.updateCategory(new_category, LLInventoryObserver::CREATE); - LL_DEBUGS("Inventory") << "created category " << category_id << LL_ENDL; - - // fetching can receive massive amount of items and folders - if (gInventory.getChangedIDs().size() > MAX_UPDATE_BACKLOG) - { - gInventory.notifyObservers(); - checkTimeout(); - } - } - - // UPDATE CATEGORIES - for (deferred_category_map_t::const_iterator update_it = mCategoriesUpdated.begin(); - update_it != mCategoriesUpdated.end(); ++update_it) - { - LLUUID category_id(update_it->first); - LLPointer new_category = update_it->second; - // Since this is a copy of the category *before* the accounting update, above, - // we need to transfer back the updated version/descendant count. - LLViewerInventoryCategory* curr_cat = gInventory.getCategory(new_category->getUUID()); - if (!curr_cat) - { - LL_WARNS("Inventory") << "Failed to update unknown category " << new_category->getUUID() << LL_ENDL; - } - else - { - new_category->setVersion(curr_cat->getVersion()); - new_category->setDescendentCount(curr_cat->getDescendentCount()); - gInventory.updateCategory(new_category); - LL_DEBUGS("Inventory") << "updated category " << new_category->getName() << " " << category_id << LL_ENDL; - } - } - - // LOST ITEMS - if (!mItemsLost.empty()) - { - LL_INFOS("Inventory") << "Received " << (S32)mItemsLost.size() << " items without a parent" << LL_ENDL; - const LLUUID lost_uuid(gInventory.findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND)); - if (lost_uuid.notNull()) - { - for (deferred_item_map_t::const_iterator lost_it = mItemsLost.begin(); - lost_it != mItemsLost.end(); ++lost_it) - { - LLPointer new_item = lost_it->second; - - new_item->setParent(lost_uuid); - new_item->updateParentOnServer(false); - } - } - } - - // CREATE ITEMS - for (deferred_item_map_t::const_iterator create_it = mItemsCreated.begin(); - create_it != mItemsCreated.end(); ++create_it) - { - LLUUID item_id(create_it->first); - LLPointer new_item = create_it->second; - - // FIXME risky function since it calls updateServer() in some - // cases. Maybe break out the update/create cases, in which - // case this is create. - LL_DEBUGS("Inventory") << "created item " << item_id << LL_ENDL; - gInventory.updateItem(new_item, LLInventoryObserver::CREATE); - - // fetching can receive massive amount of items and folders - if (gInventory.getChangedIDs().size() > MAX_UPDATE_BACKLOG) - { - gInventory.notifyObservers(); - checkTimeout(); - } - } - - // UPDATE ITEMS - for (deferred_item_map_t::const_iterator update_it = mItemsUpdated.begin(); - update_it != mItemsUpdated.end(); ++update_it) - { - LLUUID item_id(update_it->first); - LLPointer new_item = update_it->second; - // FIXME risky function since it calls updateServer() in some - // cases. Maybe break out the update/create cases, in which - // case this is update. - LL_DEBUGS("Inventory") << "updated item " << item_id << LL_ENDL; - //LL_DEBUGS("Inventory") << ll_pretty_print_sd(new_item->asLLSD()) << LL_ENDL; - gInventory.updateItem(new_item); - } - - // DELETE OBJECTS - for (uuid_list_t::const_iterator del_it = mObjectsDeletedIds.begin(); - del_it != mObjectsDeletedIds.end(); ++del_it) - { - LL_DEBUGS("Inventory") << "deleted item " << *del_it << LL_ENDL; - gInventory.onObjectDeletedFromServer(*del_it, false, false, false); - } - - // TODO - how can we use this version info? Need to be sure all - // changes are going through AIS first, or at least through - // something with a reliable responder. - for (uuid_int_map_t::iterator ucv_it = mCatVersionsUpdated.begin(); - ucv_it != mCatVersionsUpdated.end(); ++ucv_it) - { - const LLUUID id = ucv_it->first; - S32 version = ucv_it->second; - LLViewerInventoryCategory *cat = gInventory.getCategory(id); - LL_DEBUGS("Inventory") << "cat version update " << cat->getName() << " to version " << cat->getVersion() << LL_ENDL; - if (cat->getVersion() != version) - { - // the AIS version should be considered the true version. Adjust - // our local category model to reflect this version number. Otherwise - // it becomes possible to get stuck with the viewer being out of - // sync with the inventory system. Under normal circumstances - // inventory COF is maintained on the viewer through calls to - // LLInventoryModel::accountForUpdate when a changing operation - // is performed. This occasionally gets out of sync however. - if (version != LLViewerInventoryCategory::VERSION_UNKNOWN) - { - LL_WARNS() << "Possible version mismatch for category " << cat->getName() - << ", viewer version " << cat->getVersion() - << " AIS version " << version << " !!!Adjusting local version!!!" << LL_ENDL; - cat->setVersion(version); - } - else - { - // We do not account for update if version is UNKNOWN, so we shouldn't rise version - // either or viewer will get stuck on descendants count -1, try to refetch folder instead - // - // Todo: proper backoff? - - LL_WARNS() << "Possible version mismatch for category " << cat->getName() - << ", viewer version " << cat->getVersion() - << " AIS version " << version << " !!!Rerequesting category!!!" << LL_ENDL; - const S32 LONG_EXPIRY = 360; - cat->fetch(LONG_EXPIRY); - } - } - } - - checkTimeout(); - - gInventory.notifyObservers(); -} - +/** + * @file llaisapi.cpp + * @brief classes and functions for interfacing with the v3+ ais inventory service. + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + * + */ + +#include "llviewerprecompiledheaders.h" +#include "llaisapi.h" + +#include "llagent.h" +#include "llappviewer.h" +#include "llcallbacklist.h" +#include "llinventorymodel.h" +#include "llinventoryobserver.h" +#include "llnotificationsutil.h" +#include "llsdutil.h" +#include "llviewerregion.h" +#include "llvoavatar.h" +#include "llvoavatarself.h" +#include "llviewercontrol.h" + +///---------------------------------------------------------------------------- +/// Classes for AISv3 support. +///---------------------------------------------------------------------------- + +//========================================================================= +const std::string AISAPI::INVENTORY_CAP_NAME("InventoryAPIv3"); +const std::string AISAPI::LIBRARY_CAP_NAME("LibraryAPIv3"); +const S32 AISAPI::HTTP_TIMEOUT = 180; + +std::list AISAPI::sPostponedQuery; + +const S32 MAX_SIMULTANEOUS_COROUTINES = 2048; + +// AIS3 allows '*' requests, but in reality those will be cut at some point +// Specify own depth to be able to anticipate it and mark folders as incomplete +const S32 MAX_FOLDER_DEPTH_REQUEST = 50; + +//------------------------------------------------------------------------- +/*static*/ +bool AISAPI::isAvailable() +{ + if (gAgent.getRegion()) + { + return gAgent.getRegion()->isCapabilityAvailable(INVENTORY_CAP_NAME); + } + return false; +} + +/*static*/ +void AISAPI::getCapNames(LLSD& capNames) +{ + capNames.append(INVENTORY_CAP_NAME); + capNames.append(LIBRARY_CAP_NAME); +} + +/*static*/ +std::string AISAPI::getInvCap() +{ + if (gAgent.getRegion()) + { + return gAgent.getRegion()->getCapability(INVENTORY_CAP_NAME); + } + return std::string(); +} + +/*static*/ +std::string AISAPI::getLibCap() +{ + if (gAgent.getRegion()) + { + return gAgent.getRegion()->getCapability(LIBRARY_CAP_NAME); + } + return std::string(); +} + +/*static*/ +void AISAPI::CreateInventory(const LLUUID& parentId, const LLSD& newInventory, completion_t callback) +{ + std::string cap = getInvCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + + LLUUID tid; + tid.generate(); + + std::string url = cap + std::string("/category/") + parentId.asString() + "?tid=" + tid.asString(); + LL_DEBUGS("Inventory") << "url: " << url << " parentID " << parentId << " newInventory " << newInventory << LL_ENDL; + + // I may be suffering from golden hammer here, but the first part of this bind + // is actually a static cast for &HttpCoroutineAdapter::postAndSuspend so that + // the compiler can identify the correct signature to select. + // + // Reads as follows: + // LLSD - method returning LLSD + // (LLCoreHttpUtil::HttpCoroutineAdapter::*) - pointer to member function of HttpCoroutineAdapter + // (LLCore::HttpRequest::ptr_t, const std::string &, const LLSD &, LLCore::HttpOptions::ptr_t, LLCore::HttpHeaders::ptr_t) - signature of method + // + invokationFn_t postFn = boost::bind( + // Humans ignore next line. It is just a cast. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::postAndSuspend), _1, _2, _3, _4, _5, _6); + + LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, + _1, postFn, url, parentId, newInventory, callback, CREATEINVENTORY)); + EnqueueAISCommand("CreateInventory", proc); +} + +/*static*/ +void AISAPI::SlamFolder(const LLUUID& folderId, const LLSD& newInventory, completion_t callback) +{ + std::string cap = getInvCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + + LLUUID tid; + tid.generate(); + + std::string url = cap + std::string("/category/") + folderId.asString() + "/links?tid=" + tid.asString(); + + // see comment above in CreateInventoryCommand + invokationFn_t putFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::putAndSuspend), _1, _2, _3, _4, _5, _6); + + LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, + _1, putFn, url, folderId, newInventory, callback, SLAMFOLDER)); + + EnqueueAISCommand("SlamFolder", proc); +} + +void AISAPI::RemoveCategory(const LLUUID &categoryId, completion_t callback) +{ + std::string cap; + + cap = getInvCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + + std::string url = cap + std::string("/category/") + categoryId.asString(); + LL_DEBUGS("Inventory") << "url: " << url << LL_ENDL; + + invokationFn_t delFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::deleteAndSuspend), _1, _2, _3, _5, _6); + + LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, + _1, delFn, url, categoryId, LLSD(), callback, REMOVECATEGORY)); + + EnqueueAISCommand("RemoveCategory", proc); +} + +/*static*/ +void AISAPI::RemoveItem(const LLUUID &itemId, completion_t callback) +{ + std::string cap; + + cap = getInvCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + + std::string url = cap + std::string("/item/") + itemId.asString(); + LL_DEBUGS("Inventory") << "url: " << url << LL_ENDL; + + invokationFn_t delFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::deleteAndSuspend), _1, _2, _3, _5, _6); + + LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, + _1, delFn, url, itemId, LLSD(), callback, REMOVEITEM)); + + EnqueueAISCommand("RemoveItem", proc); +} + +void AISAPI::CopyLibraryCategory(const LLUUID& sourceId, const LLUUID& destId, bool copySubfolders, completion_t callback) +{ + std::string cap; + + cap = getLibCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Library cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + + LL_DEBUGS("Inventory") << "Copying library category: " << sourceId << " => " << destId << LL_ENDL; + + LLUUID tid; + tid.generate(); + + std::string url = cap + std::string("/category/") + sourceId.asString() + "?tid=" + tid.asString(); + if (!copySubfolders) + { + url += ",depth=0"; + } + LL_INFOS() << url << LL_ENDL; + + std::string destination = destId.asString(); + + invokationFn_t copyFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::copyAndSuspend), _1, _2, _3, destination, _5, _6); + + LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, + _1, copyFn, url, destId, LLSD(), callback, COPYLIBRARYCATEGORY)); + + EnqueueAISCommand("CopyLibraryCategory", proc); +} + +/*static*/ +void AISAPI::PurgeDescendents(const LLUUID &categoryId, completion_t callback) +{ + std::string cap; + + cap = getInvCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + + std::string url = cap + std::string("/category/") + categoryId.asString() + "/children"; + LL_DEBUGS("Inventory") << "url: " << url << LL_ENDL; + + invokationFn_t delFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::deleteAndSuspend), _1, _2, _3, _5, _6); + + LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, + _1, delFn, url, categoryId, LLSD(), callback, PURGEDESCENDENTS)); + + EnqueueAISCommand("PurgeDescendents", proc); +} + + +/*static*/ +void AISAPI::UpdateCategory(const LLUUID &categoryId, const LLSD &updates, completion_t callback) +{ + std::string cap; + + cap = getInvCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + std::string url = cap + std::string("/category/") + categoryId.asString(); + + invokationFn_t patchFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::patchAndSuspend), _1, _2, _3, _4, _5, _6); + + LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, + _1, patchFn, url, categoryId, updates, callback, UPDATECATEGORY)); + + EnqueueAISCommand("UpdateCategory", proc); +} + +/*static*/ +void AISAPI::UpdateItem(const LLUUID &itemId, const LLSD &updates, completion_t callback) +{ + + std::string cap; + + cap = getInvCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + std::string url = cap + std::string("/item/") + itemId.asString(); + + invokationFn_t patchFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::patchAndSuspend), _1, _2, _3, _4, _5, _6); + + LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, + _1, patchFn, url, itemId, updates, callback, UPDATEITEM)); + + EnqueueAISCommand("UpdateItem", proc); +} + +/*static*/ +void AISAPI::FetchItem(const LLUUID &itemId, ITEM_TYPE type, completion_t callback) +{ + std::string cap; + + cap = (type == INVENTORY) ? getInvCap() : getLibCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + std::string url = cap + std::string("/item/") + itemId.asString(); + + invokationFn_t getFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), _1, _2, _3, _5, _6); + + LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, + _1, getFn, url, itemId, LLSD(), callback, FETCHITEM)); + + EnqueueAISCommand("FetchItem", proc); +} + +/*static*/ +void AISAPI::FetchCategoryChildren(const LLUUID &catId, ITEM_TYPE type, bool recursive, completion_t callback, S32 depth) +{ + std::string cap; + + cap = (type == INVENTORY) ? getInvCap() : getLibCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + std::string url = cap + std::string("/category/") + catId.asString() + "/children"; + + if (recursive) + { + // can specify depth=*, but server side is going to cap requests + // and reject everything 'over the top',. + depth = MAX_FOLDER_DEPTH_REQUEST; + } + else + { + depth = llmin(depth, MAX_FOLDER_DEPTH_REQUEST); + } + + url += "?depth=" + std::to_string(depth); + + invokationFn_t getFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), _1, _2, _3, _5, _6); + + // get doesn't use body, can pass additional data + LLSD body; + body["depth"] = depth; + LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, + _1, getFn, url, catId, body, callback, FETCHCATEGORYCHILDREN)); + + EnqueueAISCommand("FetchCategoryChildren", proc); +} + +// some folders can be requested by name, like +// animatn | bodypart | clothing | current | favorite | gesture | inbox | landmark | lsltext +// lstndfnd | my_otfts | notecard | object | outbox | root | snapshot | sound | texture | trash +void AISAPI::FetchCategoryChildren(const std::string &identifier, bool recursive, completion_t callback, S32 depth) +{ + std::string cap; + + cap = getInvCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + std::string url = cap + std::string("/category/") + identifier + "/children"; + + if (recursive) + { + // can specify depth=*, but server side is going to cap requests + // and reject everything 'over the top',. + depth = MAX_FOLDER_DEPTH_REQUEST; + } + else + { + depth = llmin(depth, MAX_FOLDER_DEPTH_REQUEST); + } + + url += "?depth=" + std::to_string(depth); + + invokationFn_t getFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), _1, _2, _3, _5, _6); + + // get doesn't use body, can pass additional data + LLSD body; + body["depth"] = depth; + LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, + _1, getFn, url, LLUUID::null, body, callback, FETCHCATEGORYCHILDREN)); + + EnqueueAISCommand("FetchCategoryChildren", proc); +} + +/*static*/ +void AISAPI::FetchCategoryCategories(const LLUUID &catId, ITEM_TYPE type, bool recursive, completion_t callback, S32 depth) +{ + std::string cap; + + cap = (type == INVENTORY) ? getInvCap() : getLibCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + std::string url = cap + std::string("/category/") + catId.asString() + "/categories"; + + if (recursive) + { + // can specify depth=*, but server side is going to cap requests + // and reject everything 'over the top',. + depth = MAX_FOLDER_DEPTH_REQUEST; + } + else + { + depth = llmin(depth, MAX_FOLDER_DEPTH_REQUEST); + } + + url += "?depth=" + std::to_string(depth); + + invokationFn_t getFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), _1, _2, _3, _5, _6); + + // get doesn't use body, can pass additional data + LLSD body; + body["depth"] = depth; + LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, + _1, getFn, url, catId, body, callback, FETCHCATEGORYCATEGORIES)); + + EnqueueAISCommand("FetchCategoryCategories", proc); +} + +void AISAPI::FetchCategorySubset(const LLUUID& catId, + const uuid_vec_t specificChildren, + ITEM_TYPE type, + bool recursive, + completion_t callback, + S32 depth) +{ + std::string cap = (type == INVENTORY) ? getInvCap() : getLibCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + if (specificChildren.empty()) + { + LL_WARNS("Inventory") << "Empty request!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + // category/any_folder_id/children?depth=*&children=child_id1,child_id2,child_id3 + std::string url = cap + std::string("/category/") + catId.asString() + "/children"; + + if (recursive) + { + depth = MAX_FOLDER_DEPTH_REQUEST; + } + else + { + depth = llmin(depth, MAX_FOLDER_DEPTH_REQUEST); + } + + uuid_vec_t::const_iterator iter = specificChildren.begin(); + uuid_vec_t::const_iterator end = specificChildren.end(); + + url += "?depth=" + std::to_string(depth) + "&children=" + iter->asString(); + iter++; + + while (iter != end) + { + url += "," + iter->asString(); + iter++; + } + + const S32 MAX_URL_LENGH = 2000; // RFC documentation specifies a maximum length of 2048 + if (url.length() > MAX_URL_LENGH) + { + LL_WARNS("Inventory") << "Request url is too long, url: " << url << LL_ENDL; + } + + invokationFn_t getFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), _1, _2, _3, _5, _6); + + // get doesn't use body, can pass additional data + LLSD body; + body["depth"] = depth; + LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, + _1, getFn, url, catId, body, callback, FETCHCATEGORYSUBSET)); + + EnqueueAISCommand("FetchCategorySubset", proc); +} + +/*static*/ +// Will get COF folder, links in it and items those links point to +void AISAPI::FetchCOF(completion_t callback) +{ + std::string cap = getInvCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + std::string url = cap + std::string("/category/current/links"); + + invokationFn_t getFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), _1, _2, _3, _5, _6); + + LLSD body; + // Only cof folder will be full, but cof can contain an outfit + // link with embedded outfit folder for request to parse + body["depth"] = 0; + LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro, + _1, getFn, url, LLUUID::null, body, callback, FETCHCOF)); + + EnqueueAISCommand("FetchCOF", proc); +} + +void AISAPI::FetchCategoryLinks(const LLUUID &catId, completion_t callback) +{ + std::string cap = getInvCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + std::string url = cap + std::string("/category/") + catId.asString() + "/links"; + + invokationFn_t getFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), + _1, _2, _3, _5, _6); + + LLSD body; + body["depth"] = 0; + LLCoprocedureManager::CoProcedure_t proc( + boost::bind(&AISAPI::InvokeAISCommandCoro, _1, getFn, url, LLUUID::null, body, callback, FETCHCATEGORYLINKS)); + + EnqueueAISCommand("FetchCategoryLinks", proc); +} + +/*static*/ +void AISAPI::FetchOrphans(completion_t callback) +{ + std::string cap = getInvCap(); + if (cap.empty()) + { + LL_WARNS("Inventory") << "Inventory cap not found!" << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + std::string url = cap + std::string("/orphans"); + + invokationFn_t getFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> body + // _5 -> httpOptions + // _6 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend) , _1 , _2 , _3 , _5 , _6); + + LLCoprocedureManager::CoProcedure_t proc(boost::bind(&AISAPI::InvokeAISCommandCoro , + _1 , getFn , url , LLUUID::null , LLSD() , callback , FETCHORPHANS)); + + EnqueueAISCommand("FetchOrphans" , proc); +} + +/*static*/ +void AISAPI::EnqueueAISCommand(const std::string &procName, LLCoprocedureManager::CoProcedure_t proc) +{ + LLCoprocedureManager &inst = LLCoprocedureManager::instance(); + S32 pending_in_pool = inst.countPending("AIS"); + std::string procFullName = "AIS(" + procName + ")"; + if (pending_in_pool < MAX_SIMULTANEOUS_COROUTINES) + { + inst.enqueueCoprocedure("AIS", procFullName, proc); + } + else + { + // As I understand it, coroutines have built-in 'pending' pool + // but unfortunately it has limited size which inventory often goes over + // so this is a workaround to not overfill it. + if (sPostponedQuery.empty()) + { + sPostponedQuery.push_back(ais_query_item_t(procFullName, proc)); + gIdleCallbacks.addFunction(onIdle, NULL); + } + else + { + sPostponedQuery.push_back(ais_query_item_t(procFullName, proc)); + } + } +} + +/*static*/ +void AISAPI::onIdle(void *userdata) +{ + if (!sPostponedQuery.empty()) + { + LLCoprocedureManager &inst = LLCoprocedureManager::instance(); + S32 pending_in_pool = inst.countPending("AIS"); + while (pending_in_pool < MAX_SIMULTANEOUS_COROUTINES && !sPostponedQuery.empty()) + { + ais_query_item_t &item = sPostponedQuery.front(); + inst.enqueueCoprocedure("AIS", item.first, item.second); + sPostponedQuery.pop_front(); + pending_in_pool++; + } + } + + if (sPostponedQuery.empty()) + { + // Nothing to do anymore + gIdleCallbacks.deleteFunction(onIdle, NULL); + } +} + +/*static*/ +void AISAPI::onUpdateReceived(const LLSD& update, COMMAND_TYPE type, const LLSD& request_body) +{ + LLTimer timer; + if ( (type == UPDATECATEGORY || type == UPDATEITEM) + && gSavedSettings.getBOOL("DebugAvatarAppearanceMessage")) + { + dump_sequential_xml(gAgentAvatarp->getFullname() + "_ais_update", update); + } + + AISUpdate ais_update(update, type, request_body); + ais_update.doUpdate(); // execute the updates in the appropriate order. + LL_DEBUGS("Inventory", "AIS3") << "Elapsed processing: " << timer.getElapsedTimeF32() << LL_ENDL; +} + +/*static*/ +void AISAPI::InvokeAISCommandCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter, + invokationFn_t invoke, std::string url, + LLUUID targetId, LLSD body, completion_t callback, COMMAND_TYPE type) +{ + if (gDisconnected) + { + if (callback) + { + callback(LLUUID::null); + } + return; + } + + LLCore::HttpOptions::ptr_t httpOptions(new LLCore::HttpOptions); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest()); + LLCore::HttpHeaders::ptr_t httpHeaders; + + httpOptions->setTimeout(HTTP_TIMEOUT); + + LL_DEBUGS("Inventory") << "Request url: " << url << LL_ENDL; + + LLSD result; + LLSD httpResults; + LLCore::HttpStatus status; + + result = invoke(httpAdapter , httpRequest , url , body , httpOptions , httpHeaders); + httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status || !result.isMap()) + { + if (!result.isMap()) + { + status = LLCore::HttpStatus(HTTP_INTERNAL_ERROR, "Malformed response contents"); + } + else if (status.getType() == 410) //GONE + { + // Item does not exist or was already deleted from server. + // parent folder is out of sync + if (type == REMOVECATEGORY) + { + LLViewerInventoryCategory *cat = gInventory.getCategory(targetId); + if (cat) + { + LL_WARNS("Inventory") << "Purge failed for '" << cat->getName() + << "' local version:" << cat->getVersion() + << " since folder no longer exists at server. Descendent count: server == " << cat->getDescendentCount() + << ", viewer == " << cat->getViewerDescendentCount() + << LL_ENDL; + gInventory.fetchDescendentsOf(cat->getParentUUID()); + // Note: don't delete folder here - contained items will be deparented (or deleted) + // and since we are clearly out of sync we can't be sure we won't get rid of something we need. + // For example folder could have been moved or renamed with items intact, let it fetch first. + } + } + else if (type == REMOVEITEM) + { + LLViewerInventoryItem *item = gInventory.getItem(targetId); + if (item) + { + LL_WARNS("Inventory") << "Purge failed for '" << item->getName() + << "' since item no longer exists at server." << LL_ENDL; + gInventory.fetchDescendentsOf(item->getParentUUID()); + // since item not on the server and exists at viewer, so it needs an update at the least, + // so delete it, in worst case item will be refetched with new params. + gInventory.onObjectDeletedFromServer(targetId); + } + } + } + else if (status == LLCore::HttpStatus(HTTP_FORBIDDEN) /*403*/) + { + if (type == FETCHCATEGORYCHILDREN) + { + if (body.has("depth") && body["depth"].asInteger() == 0) + { + // Can't fetch a single folder with depth 0, folder is too big. + static bool first_call = true; + if (first_call) + { + first_call = false; + LLNotificationsUtil::add("InventoryLimitReachedAISAlert"); + } + else + { + LLNotificationsUtil::add("InventoryLimitReachedAIS"); + } + LL_WARNS("Inventory") << "Fetch failed, content is over limit, url: " << url << LL_ENDL; + } + else + { + // Result was too big, but situation is recoverable by requesting with lower depth + LL_DEBUGS("Inventory") << "Fetch failed, content is over limit, url: " << url << LL_ENDL; + } + } + } + LL_WARNS("Inventory") << "Inventory error: " << status.toString() << LL_ENDL; + LL_WARNS("Inventory") << ll_pretty_print_sd(result) << LL_ENDL; + } + + LL_DEBUGS("Inventory", "AIS3") << "Result: " << result << LL_ENDL; + onUpdateReceived(result, type, body); + + if (callback && !callback.empty()) + { + bool needs_callback = true; + LLUUID id(LLUUID::null); + + switch (type) + { + case COPYLIBRARYCATEGORY: + case FETCHCATEGORYCATEGORIES: + case FETCHCATEGORYCHILDREN: + case FETCHCATEGORYSUBSET: + case FETCHCATEGORYLINKS: + case FETCHCOF: + if (result.has("category_id")) + { + id = result["category_id"]; + } + break; + case FETCHITEM: + if (result.has("item_id")) + { + // Error message might contain an item_id!!! + id = result["item_id"]; + } + if (result.has("linked_id")) + { + id = result["linked_id"]; + } + break; + case CREATEINVENTORY: + // CREATEINVENTORY can have multiple callbacks + if (result.has("_created_categories")) + { + LLSD& cats = result["_created_categories"]; + LLSD::array_const_iterator cat_iter; + for (cat_iter = cats.beginArray(); cat_iter != cats.endArray(); ++cat_iter) + { + LLUUID cat_id = *cat_iter; + callback(cat_id); + needs_callback = false; + } + } + if (result.has("_created_items")) + { + LLSD& items = result["_created_items"]; + LLSD::array_const_iterator item_iter; + for (item_iter = items.beginArray(); item_iter != items.endArray(); ++item_iter) + { + LLUUID item_id = *item_iter; + callback(item_id); + needs_callback = false; + } + } + break; + default: + break; + } + + if (needs_callback) + { + // Call callback at least once regardless of failure. + // UPDATEITEM doesn't expect an id + callback(id); + } + } + +} + +//------------------------------------------------------------------------- +AISUpdate::AISUpdate(const LLSD& update, AISAPI::COMMAND_TYPE type, const LLSD& request_body) +: mType(type) +{ + mFetch = (type == AISAPI::FETCHITEM) + || (type == AISAPI::FETCHCATEGORYCHILDREN) + || (type == AISAPI::FETCHCATEGORYCATEGORIES) + || (type == AISAPI::FETCHCATEGORYSUBSET) + || (type == AISAPI::FETCHCOF) + || (type == AISAPI::FETCHCATEGORYLINKS) + || (type == AISAPI::FETCHORPHANS); + // parse update llsd into stuff to do or parse received items. + mFetchDepth = MAX_FOLDER_DEPTH_REQUEST; + if (mFetch && request_body.has("depth")) + { + mFetchDepth = request_body["depth"].asInteger(); + } + + mTimer.setTimerExpirySec(AIS_EXPIRY_SECONDS); + mTimer.start(); + parseUpdate(update); +} + +void AISUpdate::clearParseResults() +{ + mCatDescendentDeltas.clear(); + mCatDescendentsKnown.clear(); + mCatVersionsUpdated.clear(); + mItemsCreated.clear(); + mItemsLost.clear(); + mItemsUpdated.clear(); + mCategoriesCreated.clear(); + mCategoriesUpdated.clear(); + mObjectsDeletedIds.clear(); + mItemIds.clear(); + mCategoryIds.clear(); +} + +void AISUpdate::checkTimeout() +{ + if (mTimer.hasExpired()) + { + llcoro::suspend(); + LLCoros::checkStop(); + mTimer.setTimerExpirySec(AIS_EXPIRY_SECONDS); + } +} + +void AISUpdate::parseUpdate(const LLSD& update) +{ + clearParseResults(); + parseMeta(update); + parseContent(update); +} + +void AISUpdate::parseMeta(const LLSD& update) +{ + // parse _categories_removed -> mObjectsDeletedIds + uuid_list_t cat_ids; + parseUUIDArray(update,"_categories_removed",cat_ids); + for (uuid_list_t::const_iterator it = cat_ids.begin(); + it != cat_ids.end(); ++it) + { + LLViewerInventoryCategory *cat = gInventory.getCategory(*it); + if(cat) + { + mCatDescendentDeltas[cat->getParentUUID()]--; + mObjectsDeletedIds.insert(*it); + } + else + { + LL_WARNS("Inventory") << "removed category not found " << *it << LL_ENDL; + } + } + + // parse _categories_items_removed -> mObjectsDeletedIds + uuid_list_t item_ids; + parseUUIDArray(update,"_category_items_removed",item_ids); + parseUUIDArray(update,"_removed_items",item_ids); + for (uuid_list_t::const_iterator it = item_ids.begin(); + it != item_ids.end(); ++it) + { + LLViewerInventoryItem *item = gInventory.getItem(*it); + if(item) + { + mCatDescendentDeltas[item->getParentUUID()]--; + mObjectsDeletedIds.insert(*it); + } + else + { + LL_WARNS("Inventory") << "removed item not found " << *it << LL_ENDL; + } + } + + // parse _broken_links_removed -> mObjectsDeletedIds + uuid_list_t broken_link_ids; + parseUUIDArray(update,"_broken_links_removed",broken_link_ids); + for (uuid_list_t::const_iterator it = broken_link_ids.begin(); + it != broken_link_ids.end(); ++it) + { + LLViewerInventoryItem *item = gInventory.getItem(*it); + if(item) + { + mCatDescendentDeltas[item->getParentUUID()]--; + mObjectsDeletedIds.insert(*it); + } + else + { + LL_WARNS("Inventory") << "broken link not found " << *it << LL_ENDL; + } + } + + // parse _created_items + parseUUIDArray(update,"_created_items",mItemIds); + + // parse _created_categories + parseUUIDArray(update,"_created_categories",mCategoryIds); + + // Parse updated category versions. + const std::string& ucv = "_updated_category_versions"; + if (update.has(ucv)) + { + for(LLSD::map_const_iterator it = update[ucv].beginMap(), + end = update[ucv].endMap(); + it != end; ++it) + { + const LLUUID id((*it).first); + S32 version = (*it).second.asInteger(); + mCatVersionsUpdated[id] = version; + } + } +} + +void AISUpdate::parseContent(const LLSD& update) +{ + // Errors from a fetch request might contain id without + // full item or folder. + // Todo: Depending on error we might want to do something, + // like removing a 404 item or refetching parent folder + if (update.has("linked_id") && update.has("parent_id")) + { + parseLink(update, mFetchDepth); + } + else if (update.has("item_id") && update.has("parent_id")) + { + parseItem(update); + } + + if (mType == AISAPI::FETCHCATEGORYSUBSET) + { + // initial category is incomplete, don't process it, + // go for content instead + if (update.has("_embedded")) + { + parseEmbedded(update["_embedded"], mFetchDepth - 1); + } + } + else if (update.has("category_id") && update.has("parent_id")) + { + parseCategory(update, mFetchDepth); + } + else + { + if (update.has("_embedded")) + { + parseEmbedded(update["_embedded"], mFetchDepth); + } + } +} + +void AISUpdate::parseItem(const LLSD& item_map) +{ + LLUUID item_id = item_map["item_id"].asUUID(); + LLPointer new_item(new LLViewerInventoryItem); + LLViewerInventoryItem *curr_item = gInventory.getItem(item_id); + if (curr_item) + { + // Default to current values where not provided. + new_item->copyViewerItem(curr_item); + } + bool rv = new_item->unpackMessage(item_map); + if (rv) + { + if (mFetch) + { + mItemsCreated[item_id] = new_item; + new_item->setComplete(true); + + if (new_item->getParentUUID().isNull()) + { + mItemsLost[item_id] = new_item; + } + } + else if (curr_item) + { + mItemsUpdated[item_id] = new_item; + // This statement is here to cause a new entry with 0 + // delta to be created if it does not already exist; + // otherwise has no effect. + mCatDescendentDeltas[new_item->getParentUUID()]; + } + else + { + mItemsCreated[item_id] = new_item; + mCatDescendentDeltas[new_item->getParentUUID()]++; + new_item->setComplete(true); + } + } + else + { + // *TODO: Wow, harsh. Should we just complain and get out? + LL_ERRS() << "unpack failed" << LL_ENDL; + } +} + +void AISUpdate::parseLink(const LLSD& link_map, S32 depth) +{ + LLUUID item_id = link_map["item_id"].asUUID(); + LLPointer new_link(new LLViewerInventoryItem); + LLViewerInventoryItem *curr_link = gInventory.getItem(item_id); + if (curr_link) + { + // Default to current values where not provided. + new_link->copyViewerItem(curr_link); + } + bool rv = new_link->unpackMessage(link_map); + if (rv) + { + const LLUUID& parent_id = new_link->getParentUUID(); + if (mFetch) + { + LLPermissions default_perms; + default_perms.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null); + default_perms.initMasks(PERM_NONE, PERM_NONE, PERM_NONE, PERM_NONE, PERM_NONE); + new_link->setPermissions(default_perms); + LLSaleInfo default_sale_info; + new_link->setSaleInfo(default_sale_info); + //LL_DEBUGS("Inventory") << "creating link from llsd: " << ll_pretty_print_sd(link_map) << LL_ENDL; + mItemsCreated[item_id] = new_link; + new_link->setComplete(true); + + if (new_link->getParentUUID().isNull()) + { + mItemsLost[item_id] = new_link; + } + } + else if (curr_link) + { + mItemsUpdated[item_id] = new_link; + // This statement is here to cause a new entry with 0 + // delta to be created if it does not already exist; + // otherwise has no effect. + mCatDescendentDeltas[parent_id]; + } + else + { + LLPermissions default_perms; + default_perms.init(gAgent.getID(),gAgent.getID(),LLUUID::null,LLUUID::null); + default_perms.initMasks(PERM_NONE,PERM_NONE,PERM_NONE,PERM_NONE,PERM_NONE); + new_link->setPermissions(default_perms); + LLSaleInfo default_sale_info; + new_link->setSaleInfo(default_sale_info); + //LL_DEBUGS("Inventory") << "creating link from llsd: " << ll_pretty_print_sd(link_map) << LL_ENDL; + mItemsCreated[item_id] = new_link; + mCatDescendentDeltas[parent_id]++; + new_link->setComplete(true); + } + + if (link_map.has("_embedded")) + { + parseEmbedded(link_map["_embedded"], depth); + } + } + else + { + // *TODO: Wow, harsh. Should we just complain and get out? + LL_ERRS() << "unpack failed" << LL_ENDL; + } +} + + +void AISUpdate::parseCategory(const LLSD& category_map, S32 depth) +{ + LLUUID category_id = category_map["category_id"].asUUID(); + S32 version = LLViewerInventoryCategory::VERSION_UNKNOWN; + + if (category_map.has("version")) + { + version = category_map["version"].asInteger(); + } + + LLViewerInventoryCategory *curr_cat = gInventory.getCategory(category_id); + + if (curr_cat + && curr_cat->getVersion() > LLViewerInventoryCategory::VERSION_UNKNOWN + && curr_cat->getDescendentCount() != LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN + && version > LLViewerInventoryCategory::VERSION_UNKNOWN + && version < curr_cat->getVersion()) + { + LL_WARNS() << "Got stale folder, known: " << curr_cat->getVersion() + << ", received: " << version << LL_ENDL; + return; + } + + LLPointer new_cat; + if (curr_cat) + { + // Default to current values where not provided. + new_cat = new LLViewerInventoryCategory(curr_cat); + } + else + { + if (category_map.has("agent_id")) + { + new_cat = new LLViewerInventoryCategory(category_map["agent_id"].asUUID()); + } + else + { + LL_DEBUGS() << "No owner provided, folder might be assigned wrong owner" << LL_ENDL; + new_cat = new LLViewerInventoryCategory(LLUUID::null); + } + } + bool rv = new_cat->unpackMessage(category_map); + // *NOTE: unpackMessage does not unpack version or descendent count. + if (rv) + { + // Check descendent count first, as it may be needed + // to populate newly created categories + if (category_map.has("_embedded")) + { + parseDescendentCount(category_id, new_cat->getPreferredType(), category_map["_embedded"]); + } + + if (mFetch) + { + uuid_int_map_t::const_iterator lookup_it = mCatDescendentsKnown.find(category_id); + if (mCatDescendentsKnown.end() != lookup_it) + { + S32 descendent_count = lookup_it->second; + LL_DEBUGS("Inventory") << "Setting descendents count to " << descendent_count + << " for category " << category_id << LL_ENDL; + new_cat->setDescendentCount(descendent_count); + + // set version only if we are sure this update has full data and embeded items + // since viewer uses version to decide if folder and content still need fetching + if (version > LLViewerInventoryCategory::VERSION_UNKNOWN + && depth >= 0) + { + if (curr_cat && curr_cat->getVersion() > version) + { + LL_WARNS("Inventory") << "Version was " << curr_cat->getVersion() + << ", but fetch returned version " << version + << " for category " << category_id << LL_ENDL; + } + else + { + LL_DEBUGS("Inventory") << "Setting version to " << version + << " for category " << category_id << LL_ENDL; + } + + new_cat->setVersion(version); + } + } + else if (curr_cat + && curr_cat->getVersion() > LLViewerInventoryCategory::VERSION_UNKNOWN + && version > curr_cat->getVersion()) + { + // Potentially should new_cat->setVersion(unknown) here, + // but might be waiting for a callback that would increment + LL_DEBUGS("Inventory") << "Category " << category_id + << " is stale. Known version: " << curr_cat->getVersion() + << " server version: " << version << LL_ENDL; + } + mCategoriesCreated[category_id] = new_cat; + } + else if (curr_cat) + { + mCategoriesUpdated[category_id] = new_cat; + // This statement is here to cause a new entry with 0 + // delta to be created if it does not already exist; + // otherwise has no effect. + mCatDescendentDeltas[new_cat->getParentUUID()]; + // Capture update for the category itself as well. + mCatDescendentDeltas[category_id]; + } + else + { + // Set version/descendents for newly created categories. + uuid_int_map_t::const_iterator lookup_it = mCatDescendentsKnown.find(category_id); + if (mCatDescendentsKnown.end() != lookup_it) + { + S32 descendent_count = lookup_it->second; + LL_DEBUGS("Inventory") << "Setting descendents count to " << descendent_count + << " for new category " << category_id << LL_ENDL; + new_cat->setDescendentCount(descendent_count); + + // Don't set version unles correct children count is present + if (category_map.has("version")) + { + S32 version = category_map["version"].asInteger(); + LL_DEBUGS("Inventory") << "Setting version to " << version + << " for new category " << category_id << LL_ENDL; + new_cat->setVersion(version); + } + } + mCategoriesCreated[category_id] = new_cat; + mCatDescendentDeltas[new_cat->getParentUUID()]++; + } + } + else + { + // *TODO: Wow, harsh. Should we just complain and get out? + LL_ERRS() << "unpack failed" << LL_ENDL; + } + + // Check for more embedded content. + if (category_map.has("_embedded")) + { + parseEmbedded(category_map["_embedded"], depth - 1); + } +} + +void AISUpdate::parseDescendentCount(const LLUUID& category_id, LLFolderType::EType type, const LLSD& embedded) +{ + // We can only determine true descendent count if this contains all descendent types. + if (embedded.has("categories") && + embedded.has("links") && + embedded.has("items")) + { + mCatDescendentsKnown[category_id] = embedded["categories"].size(); + mCatDescendentsKnown[category_id] += embedded["links"].size(); + mCatDescendentsKnown[category_id] += embedded["items"].size(); + } + else if (mFetch && embedded.has("links") && (type == LLFolderType::FT_CURRENT_OUTFIT || type == LLFolderType::FT_OUTFIT)) + { + // COF and outfits contain links only + mCatDescendentsKnown[category_id] = embedded["links"].size(); + } +} + +void AISUpdate::parseEmbedded(const LLSD& embedded, S32 depth) +{ + checkTimeout(); + + if (embedded.has("links")) // _embedded in a category + { + parseEmbeddedLinks(embedded["links"], depth); + } + if (embedded.has("items")) // _embedded in a category + { + parseEmbeddedItems(embedded["items"]); + } + if (embedded.has("item")) // _embedded in a link + { + parseEmbeddedItem(embedded["item"]); + } + if (embedded.has("categories")) // _embedded in a category + { + parseEmbeddedCategories(embedded["categories"], depth); + } + if (embedded.has("category")) // _embedded in a link + { + parseEmbeddedCategory(embedded["category"], depth); + } +} + +void AISUpdate::parseUUIDArray(const LLSD& content, const std::string& name, uuid_list_t& ids) +{ + if (content.has(name)) + { + for(LLSD::array_const_iterator it = content[name].beginArray(), + end = content[name].endArray(); + it != end; ++it) + { + ids.insert((*it).asUUID()); + } + } +} + +void AISUpdate::parseEmbeddedLinks(const LLSD& links, S32 depth) +{ + for(LLSD::map_const_iterator linkit = links.beginMap(), + linkend = links.endMap(); + linkit != linkend; ++linkit) + { + const LLUUID link_id((*linkit).first); + const LLSD& link_map = (*linkit).second; + if (!mFetch && mItemIds.end() == mItemIds.find(link_id)) + { + LL_DEBUGS("Inventory") << "Ignoring link not in items list " << link_id << LL_ENDL; + } + else + { + parseLink(link_map, depth); + } + } +} + +void AISUpdate::parseEmbeddedItem(const LLSD& item) +{ + // a single item (_embedded in a link) + if (item.has("item_id")) + { + if (mFetch || mItemIds.end() != mItemIds.find(item["item_id"].asUUID())) + { + parseItem(item); + } + } +} + +void AISUpdate::parseEmbeddedItems(const LLSD& items) +{ + // a map of items (_embedded in a category) + for(LLSD::map_const_iterator itemit = items.beginMap(), + itemend = items.endMap(); + itemit != itemend; ++itemit) + { + const LLUUID item_id((*itemit).first); + const LLSD& item_map = (*itemit).second; + if (!mFetch && mItemIds.end() == mItemIds.find(item_id)) + { + LL_DEBUGS("Inventory") << "Ignoring item not in items list " << item_id << LL_ENDL; + } + else + { + parseItem(item_map); + } + } +} + +void AISUpdate::parseEmbeddedCategory(const LLSD& category, S32 depth) +{ + // a single category (_embedded in a link) + if (category.has("category_id")) + { + if (mFetch || mCategoryIds.end() != mCategoryIds.find(category["category_id"].asUUID())) + { + parseCategory(category, depth); + } + } +} + +void AISUpdate::parseEmbeddedCategories(const LLSD& categories, S32 depth) +{ + // a map of categories (_embedded in a category) + for(LLSD::map_const_iterator categoryit = categories.beginMap(), + categoryend = categories.endMap(); + categoryit != categoryend; ++categoryit) + { + const LLUUID category_id((*categoryit).first); + const LLSD& category_map = (*categoryit).second; + if (!mFetch && mCategoryIds.end() == mCategoryIds.find(category_id)) + { + LL_DEBUGS("Inventory") << "Ignoring category not in categories list " << category_id << LL_ENDL; + } + else + { + parseCategory(category_map, depth); + } + } +} + +void AISUpdate::doUpdate() +{ + checkTimeout(); + + // Do version/descendant accounting. + for (std::map::const_iterator catit = mCatDescendentDeltas.begin(); + catit != mCatDescendentDeltas.end(); ++catit) + { + LL_DEBUGS("Inventory") << "descendant accounting for " << catit->first << LL_ENDL; + + const LLUUID cat_id(catit->first); + // Don't account for update if we just created this category. + if (mCategoriesCreated.find(cat_id) != mCategoriesCreated.end()) + { + LL_DEBUGS("Inventory") << "Skipping version increment for new category " << cat_id << LL_ENDL; + continue; + } + + // Don't account for update unless AIS told us it updated that category. + if (mCatVersionsUpdated.find(cat_id) == mCatVersionsUpdated.end()) + { + LL_DEBUGS("Inventory") << "Skipping version increment for non-updated category " << cat_id << LL_ENDL; + continue; + } + + // If we have a known descendant count, set that now. + LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); + if (cat) + { + S32 descendent_delta = catit->second; + S32 old_count = cat->getDescendentCount(); + LL_DEBUGS("Inventory") << "Updating descendant count for " + << cat->getName() << " " << cat_id + << " with delta " << descendent_delta << " from " + << old_count << " to " << (old_count+descendent_delta) << LL_ENDL; + LLInventoryModel::LLCategoryUpdate up(cat_id, descendent_delta); + gInventory.accountForUpdate(up); + } + else + { + LL_DEBUGS("Inventory") << "Skipping version accounting for unknown category " << cat_id << LL_ENDL; + } + } + + // CREATE CATEGORIES + const S32 MAX_UPDATE_BACKLOG = 50; // stall prevention + for (deferred_category_map_t::const_iterator create_it = mCategoriesCreated.begin(); + create_it != mCategoriesCreated.end(); ++create_it) + { + LLUUID category_id(create_it->first); + LLPointer new_category = create_it->second; + + gInventory.updateCategory(new_category, LLInventoryObserver::CREATE); + LL_DEBUGS("Inventory") << "created category " << category_id << LL_ENDL; + + // fetching can receive massive amount of items and folders + if (gInventory.getChangedIDs().size() > MAX_UPDATE_BACKLOG) + { + gInventory.notifyObservers(); + checkTimeout(); + } + } + + // UPDATE CATEGORIES + for (deferred_category_map_t::const_iterator update_it = mCategoriesUpdated.begin(); + update_it != mCategoriesUpdated.end(); ++update_it) + { + LLUUID category_id(update_it->first); + LLPointer new_category = update_it->second; + // Since this is a copy of the category *before* the accounting update, above, + // we need to transfer back the updated version/descendant count. + LLViewerInventoryCategory* curr_cat = gInventory.getCategory(new_category->getUUID()); + if (!curr_cat) + { + LL_WARNS("Inventory") << "Failed to update unknown category " << new_category->getUUID() << LL_ENDL; + } + else + { + new_category->setVersion(curr_cat->getVersion()); + new_category->setDescendentCount(curr_cat->getDescendentCount()); + gInventory.updateCategory(new_category); + LL_DEBUGS("Inventory") << "updated category " << new_category->getName() << " " << category_id << LL_ENDL; + } + } + + // LOST ITEMS + if (!mItemsLost.empty()) + { + LL_INFOS("Inventory") << "Received " << (S32)mItemsLost.size() << " items without a parent" << LL_ENDL; + const LLUUID lost_uuid(gInventory.findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND)); + if (lost_uuid.notNull()) + { + for (deferred_item_map_t::const_iterator lost_it = mItemsLost.begin(); + lost_it != mItemsLost.end(); ++lost_it) + { + LLPointer new_item = lost_it->second; + + new_item->setParent(lost_uuid); + new_item->updateParentOnServer(false); + } + } + } + + // CREATE ITEMS + for (deferred_item_map_t::const_iterator create_it = mItemsCreated.begin(); + create_it != mItemsCreated.end(); ++create_it) + { + LLUUID item_id(create_it->first); + LLPointer new_item = create_it->second; + + // FIXME risky function since it calls updateServer() in some + // cases. Maybe break out the update/create cases, in which + // case this is create. + LL_DEBUGS("Inventory") << "created item " << item_id << LL_ENDL; + gInventory.updateItem(new_item, LLInventoryObserver::CREATE); + + // fetching can receive massive amount of items and folders + if (gInventory.getChangedIDs().size() > MAX_UPDATE_BACKLOG) + { + gInventory.notifyObservers(); + checkTimeout(); + } + } + + // UPDATE ITEMS + for (deferred_item_map_t::const_iterator update_it = mItemsUpdated.begin(); + update_it != mItemsUpdated.end(); ++update_it) + { + LLUUID item_id(update_it->first); + LLPointer new_item = update_it->second; + // FIXME risky function since it calls updateServer() in some + // cases. Maybe break out the update/create cases, in which + // case this is update. + LL_DEBUGS("Inventory") << "updated item " << item_id << LL_ENDL; + //LL_DEBUGS("Inventory") << ll_pretty_print_sd(new_item->asLLSD()) << LL_ENDL; + gInventory.updateItem(new_item); + } + + // DELETE OBJECTS + for (uuid_list_t::const_iterator del_it = mObjectsDeletedIds.begin(); + del_it != mObjectsDeletedIds.end(); ++del_it) + { + LL_DEBUGS("Inventory") << "deleted item " << *del_it << LL_ENDL; + gInventory.onObjectDeletedFromServer(*del_it, false, false, false); + } + + // TODO - how can we use this version info? Need to be sure all + // changes are going through AIS first, or at least through + // something with a reliable responder. + for (uuid_int_map_t::iterator ucv_it = mCatVersionsUpdated.begin(); + ucv_it != mCatVersionsUpdated.end(); ++ucv_it) + { + const LLUUID id = ucv_it->first; + S32 version = ucv_it->second; + LLViewerInventoryCategory *cat = gInventory.getCategory(id); + LL_DEBUGS("Inventory") << "cat version update " << cat->getName() << " to version " << cat->getVersion() << LL_ENDL; + if (cat->getVersion() != version) + { + // the AIS version should be considered the true version. Adjust + // our local category model to reflect this version number. Otherwise + // it becomes possible to get stuck with the viewer being out of + // sync with the inventory system. Under normal circumstances + // inventory COF is maintained on the viewer through calls to + // LLInventoryModel::accountForUpdate when a changing operation + // is performed. This occasionally gets out of sync however. + if (version != LLViewerInventoryCategory::VERSION_UNKNOWN) + { + LL_WARNS() << "Possible version mismatch for category " << cat->getName() + << ", viewer version " << cat->getVersion() + << " AIS version " << version << " !!!Adjusting local version!!!" << LL_ENDL; + cat->setVersion(version); + } + else + { + // We do not account for update if version is UNKNOWN, so we shouldn't rise version + // either or viewer will get stuck on descendants count -1, try to refetch folder instead + // + // Todo: proper backoff? + + LL_WARNS() << "Possible version mismatch for category " << cat->getName() + << ", viewer version " << cat->getVersion() + << " AIS version " << version << " !!!Rerequesting category!!!" << LL_ENDL; + const S32 LONG_EXPIRY = 360; + cat->fetch(LONG_EXPIRY); + } + } + } + + checkTimeout(); + + gInventory.notifyObservers(); +} + diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index 716e525a76..6efb200ed8 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -1,4776 +1,4776 @@ -/** - * @file llappearancemgr.cpp - * @brief Manager for initiating appearance changes on the viewer - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include -#include "llaccordionctrltab.h" -#include "llagent.h" -#include "llagentcamera.h" -#include "llagentwearables.h" -#include "llappearancemgr.h" -#include "llattachmentsmgr.h" -#include "llcommandhandler.h" -#include "lleventtimer.h" -#include "llfloatersidepanelcontainer.h" -#include "llgesturemgr.h" -#include "llinventorybridge.h" -#include "llinventoryfunctions.h" -#include "llinventorymodelbackgroundfetch.h" -#include "llinventoryobserver.h" -#include "llmd5.h" -#include "llnotificationsutil.h" -#include "llmd5.h" -#include "lloutfitobserver.h" -#include "lloutfitslist.h" -#include "llselectmgr.h" -#include "llsidepanelappearance.h" -#include "llviewerobjectlist.h" -#include "llvoavatar.h" -#include "llvoavatarself.h" -#include "llviewerregion.h" -#include "llwearablelist.h" -#include "llsdutil.h" -#include "llsdserialize.h" -#include "llhttpretrypolicy.h" -#include "llaisapi.h" -#include "llhttpsdhandler.h" -#include "llcorehttputil.h" -#include "llappviewer.h" -#include "llcoros.h" -#include "lleventcoro.h" -#include "lluiusage.h" - -#include "llavatarpropertiesprocessor.h" - -#if LL_MSVC -// disable boost::lexical_cast warning -#pragma warning (disable:4702) -#endif - -namespace -{ - const S32 BAKE_RETRY_MAX_COUNT = 5; - const F32 BAKE_RETRY_TIMEOUT = 2.0F; -} - -// *TODO$: LLInventoryCallback should be deprecated to conform to the new boost::bind/coroutine model. -// temp code in transition -void doAppearanceCb(LLPointer cb, LLUUID id) -{ - if (cb.notNull()) - cb->fire(id); -} - -std::string self_av_string() -{ - // On logout gAgentAvatarp can already be invalid - return isAgentAvatarValid() ? gAgentAvatarp->avString() : ""; -} - -// RAII thingy to guarantee that a variable gets reset when the Setter -// goes out of scope. More general utility would be handy - TODO: -// check boost. -class BoolSetter -{ -public: - BoolSetter(bool& var): - mVar(var) - { - mVar = true; - } - ~BoolSetter() - { - mVar = false; - } -private: - bool& mVar; -}; - -char ORDER_NUMBER_SEPARATOR('@'); - -class LLOutfitUnLockTimer: public LLEventTimer -{ -public: - LLOutfitUnLockTimer(F32 period) : LLEventTimer(period) - { - // restart timer on BOF changed event - LLOutfitObserver::instance().addBOFChangedCallback(boost::bind( - &LLOutfitUnLockTimer::reset, this)); - stop(); - } - - /*virtual*/ - bool tick() - { - if(mEventTimer.hasExpired()) - { - LLAppearanceMgr::instance().setOutfitLocked(false); - } - return false; - } - void stop() { mEventTimer.stop(); } - void start() { mEventTimer.start(); } - void reset() { mEventTimer.reset(); } - bool getStarted() { return mEventTimer.getStarted(); } - - LLTimer& getEventTimer() { return mEventTimer;} -}; - -// support for secondlife:///app/appearance SLapps -class LLAppearanceHandler : public LLCommandHandler -{ -public: - // requests will be throttled from a non-trusted browser - LLAppearanceHandler() : LLCommandHandler("appearance", UNTRUSTED_THROTTLE) {} - - bool handle(const LLSD& params, - const LLSD& query_map, - const std::string& grid, - LLMediaCtrl* web) - { - // support secondlife:///app/appearance/show, but for now we just - // make all secondlife:///app/appearance SLapps behave this way - if (!LLUI::getInstance()->mSettingGroups["config"]->getBOOL("EnableAppearance")) - { - LLNotificationsUtil::add("NoAppearance", LLSD(), LLSD(), std::string("SwitchToStandardSkinAndQuit")); - return true; - } - - LLFloaterSidePanelContainer::showPanel("appearance", LLSD()); - return true; - } -}; - -LLAppearanceHandler gAppearanceHandler; - - -LLUUID findDescendentCategoryIDByName(const LLUUID& parent_id, const std::string& name) -{ - LLInventoryModel::cat_array_t cat_array; - LLInventoryModel::item_array_t item_array; - LLNameCategoryCollector has_name(name); - gInventory.collectDescendentsIf(parent_id, - cat_array, - item_array, - LLInventoryModel::EXCLUDE_TRASH, - has_name); - if (0 == cat_array.size()) - return LLUUID(); - else - { - LLViewerInventoryCategory *cat = cat_array.at(0); - if (cat) - return cat->getUUID(); - else - { - LL_WARNS() << "null cat" << LL_ENDL; - return LLUUID(); - } - } -} - -// We want this to be much lower (e.g. 15.0 is usually fine), bumping -// up for now until we can diagnose some cases of very slow response -// to requests. -const F32 DEFAULT_RETRY_AFTER_INTERVAL = 300.0; - -// Given the current back-end problems, retrying is causing too many -// duplicate items. Bump this back to 2 once they are resolved (or can -// leave at 0 if the operations become actually reliable). -const S32 DEFAULT_MAX_RETRIES = 0; - -class LLCallAfterInventoryBatchMgr: public LLEventTimer -{ -public: - LLCallAfterInventoryBatchMgr(const LLUUID& dst_cat_id, - const std::string& phase_name, - nullary_func_t on_completion_func, - nullary_func_t on_failure_func = no_op, - F32 retry_after = DEFAULT_RETRY_AFTER_INTERVAL, - S32 max_retries = DEFAULT_MAX_RETRIES - ): - mDstCatID(dst_cat_id), - mTrackingPhase(phase_name), - mOnCompletionFunc(on_completion_func), - mOnFailureFunc(on_failure_func), - mRetryAfter(retry_after), - mMaxRetries(max_retries), - mPendingRequests(0), - mFailCount(0), - mCompletionOrFailureCalled(false), - mRetryCount(0), - LLEventTimer(5.0) - { - if (!mTrackingPhase.empty()) - { - selfStartPhase(mTrackingPhase); - } - } - - void addItems(LLInventoryModel::item_array_t& src_items) - { - for (LLInventoryModel::item_array_t::const_iterator it = src_items.begin(); - it != src_items.end(); - ++it) - { - LLViewerInventoryItem* item = *it; - llassert(item); - addItem(item->getUUID()); - } - } - - // Request or re-request operation for specified item. - void addItem(const LLUUID& item_id) - { - LL_DEBUGS("Avatar") << "item_id " << item_id << LL_ENDL; - if (!requestOperation(item_id)) - { - LL_DEBUGS("Avatar") << "item_id " << item_id << " requestOperation false, skipping" << LL_ENDL; - return; - } - - mPendingRequests++; - // On a re-request, this will reset the timer. - mWaitTimes[item_id] = LLTimer(); - if (mRetryCounts.find(item_id) == mRetryCounts.end()) - { - mRetryCounts[item_id] = 0; - } - else - { - mRetryCounts[item_id]++; - } - } - - virtual bool requestOperation(const LLUUID& item_id) = 0; - - void onOp(const LLUUID& src_id, const LLUUID& dst_id, LLTimer timestamp) - { - if (ll_frand() < gSavedSettings.getF32("InventoryDebugSimulateLateOpRate")) - { - LL_WARNS() << "Simulating late operation by punting handling to later" << LL_ENDL; - doAfterInterval(boost::bind(&LLCallAfterInventoryBatchMgr::onOp,this,src_id,dst_id,timestamp), - mRetryAfter); - return; - } - mPendingRequests--; - F32 elapsed = timestamp.getElapsedTimeF32(); - LL_DEBUGS("Avatar") << "op done, src_id " << src_id << " dst_id " << dst_id << " after " << elapsed << " seconds" << LL_ENDL; - if (mWaitTimes.find(src_id) == mWaitTimes.end()) - { - // No longer waiting for this item - either serviced - // already or gave up after too many retries. - LL_WARNS() << "duplicate or late operation, src_id " << src_id << "dst_id " << dst_id - << " elapsed " << elapsed << " after end " << (S32) mCompletionOrFailureCalled << LL_ENDL; - } - mTimeStats.push(elapsed); - mWaitTimes.erase(src_id); - if (mWaitTimes.empty() && !mCompletionOrFailureCalled) - { - onCompletionOrFailure(); - } - } - - void onCompletionOrFailure() - { - assert (!mCompletionOrFailureCalled); - mCompletionOrFailureCalled = true; - - // Will never call onCompletion() if any item has been flagged as - // a failure - otherwise could wind up with corrupted - // outfit, involuntary nudity, etc. - reportStats(); - if (!mTrackingPhase.empty()) - { - selfStopPhase(mTrackingPhase); - } - if (!mFailCount) - { - onCompletion(); - } - else - { - onFailure(); - } - } - - void onFailure() - { - LL_INFOS() << "failed" << LL_ENDL; - mOnFailureFunc(); - } - - void onCompletion() - { - LL_INFOS() << "done" << LL_ENDL; - mOnCompletionFunc(); - } - - // virtual - // Will be deleted after returning true - only safe to do this if all callbacks have fired. - bool tick() - { - // mPendingRequests will be zero if all requests have been - // responded to. mWaitTimes.empty() will be true if we have - // received at least one reply for each UUID. If requests - // have been dropped and retried, these will not necessarily - // be the same. Only safe to return true if all requests have - // been serviced, since it will result in this object being - // deleted. - bool all_done = (mPendingRequests==0); - - if (!mWaitTimes.empty()) - { - LL_WARNS() << "still waiting on " << mWaitTimes.size() << " items" << LL_ENDL; - for (std::map::iterator it = mWaitTimes.begin(); - it != mWaitTimes.end();) - { - // Use a copy of iterator because it may be erased/invalidated. - std::map::iterator curr_it = it; - ++it; - - F32 time_waited = curr_it->second.getElapsedTimeF32(); - S32 retries = mRetryCounts[curr_it->first]; - if (time_waited > mRetryAfter) - { - if (retries < mMaxRetries) - { - LL_DEBUGS("Avatar") << "Waited " << time_waited << - " for " << curr_it->first << ", retrying" << LL_ENDL; - mRetryCount++; - addItem(curr_it->first); - } - else - { - LL_WARNS() << "Giving up on " << curr_it->first << " after too many retries" << LL_ENDL; - mWaitTimes.erase(curr_it); - mFailCount++; - } - } - if (mWaitTimes.empty()) - { - onCompletionOrFailure(); - } - - } - } - return all_done; - } - - void reportStats() - { - LL_DEBUGS("Avatar") << "Phase: " << mTrackingPhase << LL_ENDL; - LL_DEBUGS("Avatar") << "mFailCount: " << mFailCount << LL_ENDL; - LL_DEBUGS("Avatar") << "mRetryCount: " << mRetryCount << LL_ENDL; - LL_DEBUGS("Avatar") << "Times: n " << mTimeStats.getCount() << " min " << mTimeStats.getMinValue() << " max " << mTimeStats.getMaxValue() << LL_ENDL; - LL_DEBUGS("Avatar") << "Mean " << mTimeStats.getMean() << " stddev " << mTimeStats.getStdDev() << LL_ENDL; - } - - virtual ~LLCallAfterInventoryBatchMgr() - { - LL_DEBUGS("Avatar") << "deleting" << LL_ENDL; - } - -protected: - std::string mTrackingPhase; - std::map mWaitTimes; - std::map mRetryCounts; - LLUUID mDstCatID; - nullary_func_t mOnCompletionFunc; - nullary_func_t mOnFailureFunc; - F32 mRetryAfter; - S32 mMaxRetries; - S32 mPendingRequests; - S32 mFailCount; - S32 mRetryCount; - bool mCompletionOrFailureCalled; - LLViewerStats::StatsAccumulator mTimeStats; -}; - -class LLCallAfterInventoryCopyMgr: public LLCallAfterInventoryBatchMgr -{ -public: - LLCallAfterInventoryCopyMgr(LLInventoryModel::item_array_t& src_items, - const LLUUID& dst_cat_id, - const std::string& phase_name, - nullary_func_t on_completion_func, - nullary_func_t on_failure_func = no_op, - F32 retry_after = DEFAULT_RETRY_AFTER_INTERVAL, - S32 max_retries = DEFAULT_MAX_RETRIES - ): - LLCallAfterInventoryBatchMgr(dst_cat_id, phase_name, on_completion_func, on_failure_func, retry_after, max_retries) - { - addItems(src_items); - sInstanceCount++; - } - - ~LLCallAfterInventoryCopyMgr() - { - sInstanceCount--; - } - - virtual bool requestOperation(const LLUUID& item_id) - { - LLViewerInventoryItem *item = gInventory.getItem(item_id); - llassert(item); - LL_DEBUGS("Avatar") << "copying item " << item_id << LL_ENDL; - if (ll_frand() < gSavedSettings.getF32("InventoryDebugSimulateOpFailureRate")) - { - LL_DEBUGS("Avatar") << "simulating failure by not sending request for item " << item_id << LL_ENDL; - return true; - } - copy_inventory_item( - gAgent.getID(), - item->getPermissions().getOwner(), - item->getUUID(), - mDstCatID, - std::string(), - new LLBoostFuncInventoryCallback(boost::bind(&LLCallAfterInventoryBatchMgr::onOp,this,item_id,_1,LLTimer())) - ); - return true; - } - - static S32 getInstanceCount() { return sInstanceCount; } - -private: - static S32 sInstanceCount; -}; - -S32 LLCallAfterInventoryCopyMgr::sInstanceCount = 0; - -class LLWearCategoryAfterCopy: public LLInventoryCallback -{ -public: - LLWearCategoryAfterCopy(bool append): - mAppend(append) - {} - - // virtual - void fire(const LLUUID& id) - { - // Wear the inventory category. - LLInventoryCategory* cat = gInventory.getCategory(id); - LLAppearanceMgr::instance().wearInventoryCategoryOnAvatar(cat, mAppend); - } - -private: - bool mAppend; -}; - -class LLTrackPhaseWrapper : public LLInventoryCallback -{ -public: - LLTrackPhaseWrapper(const std::string& phase_name, LLPointer cb = NULL): - mTrackingPhase(phase_name), - mCB(cb) - { - selfStartPhase(mTrackingPhase); - } - - // virtual - void fire(const LLUUID& id) - { - if (mCB) - { - mCB->fire(id); - } - } - - // virtual - ~LLTrackPhaseWrapper() - { - selfStopPhase(mTrackingPhase); - } - -protected: - std::string mTrackingPhase; - LLPointer mCB; -}; - -LLUpdateAppearanceOnDestroy::LLUpdateAppearanceOnDestroy(bool enforce_item_restrictions, - bool enforce_ordering, - nullary_func_t post_update_func - ): - mFireCount(0), - mEnforceItemRestrictions(enforce_item_restrictions), - mEnforceOrdering(enforce_ordering), - mPostUpdateFunc(post_update_func) -{ - selfStartPhase("update_appearance_on_destroy"); -} - -void LLUpdateAppearanceOnDestroy::fire(const LLUUID& inv_item) -{ - LLViewerInventoryItem* item = (LLViewerInventoryItem*)gInventory.getItem(inv_item); - const std::string item_name = item ? item->getName() : "ITEM NOT FOUND"; -#ifndef LL_RELEASE_FOR_DOWNLOAD - LL_DEBUGS("Avatar") << self_av_string() << "callback fired [ name:" << item_name << " UUID:" << inv_item << " count:" << mFireCount << " ] " << LL_ENDL; -#endif - mFireCount++; -} - -LLUpdateAppearanceOnDestroy::~LLUpdateAppearanceOnDestroy() -{ - if (!LLApp::isExiting()) - { - // speculative fix for MAINT-1150 - LL_INFOS("Avatar") << self_av_string() << "done update appearance on destroy" << LL_ENDL; - - selfStopPhase("update_appearance_on_destroy"); - - LLAppearanceMgr::instance().updateAppearanceFromCOF(mEnforceItemRestrictions, - mEnforceOrdering, - mPostUpdateFunc); - } -} - -LLUpdateAppearanceAndEditWearableOnDestroy::LLUpdateAppearanceAndEditWearableOnDestroy(const LLUUID& item_id): - mItemID(item_id) -{ -} - -LLRequestServerAppearanceUpdateOnDestroy::~LLRequestServerAppearanceUpdateOnDestroy() -{ - LL_DEBUGS("Avatar") << "ATT requesting server appearance update" << LL_ENDL; - if (!LLApp::isExiting()) - { - LLAppearanceMgr::instance().requestServerAppearanceUpdate(); - } -} - -void edit_wearable_and_customize_avatar(LLUUID item_id) -{ - // Start editing the item if previously requested. - gAgentWearables.editWearableIfRequested(item_id); - - // TODO: camera mode may not be changed if a debug setting is tweaked - if( gAgentCamera.cameraCustomizeAvatar() ) - { - // If we're in appearance editing mode, the current tab may need to be refreshed - LLSidepanelAppearance *panel = dynamic_cast( - LLFloaterSidePanelContainer::getPanel("appearance")); - if (panel) - { - panel->showDefaultSubpart(); - } - } -} - -LLUpdateAppearanceAndEditWearableOnDestroy::~LLUpdateAppearanceAndEditWearableOnDestroy() -{ - if (!LLApp::isExiting()) - { - LLAppearanceMgr::instance().updateAppearanceFromCOF( - true,true, - boost::bind(edit_wearable_and_customize_avatar, mItemID)); - } -} - -class LLBrokenLinkObserver : public LLInventoryObserver -{ -public: - LLUUID mUUID; - bool mEnforceItemRestrictions; - bool mEnforceOrdering; - nullary_func_t mPostUpdateFunc; - - LLBrokenLinkObserver(const LLUUID& uuid, - bool enforce_item_restrictions , - bool enforce_ordering , - nullary_func_t post_update_func) : - mUUID(uuid), - mEnforceItemRestrictions(enforce_item_restrictions), - mEnforceOrdering(enforce_ordering), - mPostUpdateFunc(post_update_func) - { - } - /* virtual */ void changed(U32 mask); - void postProcess(); -}; - -void LLBrokenLinkObserver::changed(U32 mask) -{ - if (mask & LLInventoryObserver::REBUILD) - { - // This observer should be executed after LLInventoryPanel::itemChanged(), - // but if it isn't, consider calling updateAppearanceFromCOF with a delay - const uuid_set_t& changed_item_ids = gInventory.getChangedIDs(); - for (uuid_set_t::const_iterator it = changed_item_ids.begin(); it != changed_item_ids.end(); ++it) - { - const LLUUID& id = *it; - if (id == mUUID) - { - // Might not be processed yet and it is not a - // good idea to update appearane here, postpone. - doOnIdleOneTime([this]() - { - postProcess(); - }); - - gInventory.removeObserver(this); - return; - } - } - } -} - -void LLBrokenLinkObserver::postProcess() -{ - LLViewerInventoryItem* item = gInventory.getItem(mUUID); - llassert(item && !item->getIsBrokenLink()); // the whole point was to get a correct link - if (item && item->getIsBrokenLink()) - { - LL_INFOS_ONCE("Avatar") << "Outfit link broken despite being regenerated" << LL_ENDL; - LL_DEBUGS("Avatar", "Inventory") << "Outfit link " << mUUID << " \"" << item->getName() << "\" is broken despite being regenerated" << LL_ENDL; - } - - LLAppearanceMgr::instance().updateAppearanceFromCOF( - mEnforceItemRestrictions , - mEnforceOrdering , - mPostUpdateFunc); - delete this; -} - - -struct LLFoundData -{ - LLFoundData() : - mAssetType(LLAssetType::AT_NONE), - mWearableType(LLWearableType::WT_INVALID), - mWearable(NULL) {} - - LLFoundData(const LLUUID& item_id, - const LLUUID& asset_id, - const std::string& name, - const LLAssetType::EType& asset_type, - const LLWearableType::EType& wearable_type, - const bool is_replacement = false - ) : - mItemID(item_id), - mAssetID(asset_id), - mName(name), - mAssetType(asset_type), - mWearableType(wearable_type), - mIsReplacement(is_replacement), - mWearable( NULL ) {} - - LLUUID mItemID; - LLUUID mAssetID; - std::string mName; - LLAssetType::EType mAssetType; - LLWearableType::EType mWearableType; - LLViewerWearable* mWearable; - bool mIsReplacement; -}; - - -class LLWearableHoldingPattern -{ - LOG_CLASS(LLWearableHoldingPattern); - -public: - LLWearableHoldingPattern(); - ~LLWearableHoldingPattern(); - - bool pollFetchCompletion(); - void onFetchCompletion(); - bool isFetchCompleted(); - bool isTimedOut(); - - void checkMissingWearables(); - bool pollMissingWearables(); - bool isMissingCompleted(); - void recoverMissingWearable(LLWearableType::EType type); - void clearCOFLinksForMissingWearables(); - - void onWearableAssetFetch(LLViewerWearable *wearable); - void onAllComplete(); - - typedef std::list found_list_t; - found_list_t& getFoundList(); - void eraseTypeToLink(LLWearableType::EType type); - void eraseTypeToRecover(LLWearableType::EType type); - void setObjItems(const LLInventoryModel::item_array_t& items); - void setGestItems(const LLInventoryModel::item_array_t& items); - bool isMostRecent(); - void handleLateArrivals(); - void resetTime(F32 timeout); - static S32 countActive() { return sActiveHoldingPatterns.size(); } - S32 index() { return mIndex; } - -private: - found_list_t mFoundList; - LLInventoryModel::item_array_t mObjItems; - LLInventoryModel::item_array_t mGestItems; - typedef std::set type_set_t; - type_set_t mTypesToRecover; - type_set_t mTypesToLink; - S32 mResolved; - LLTimer mWaitTime; - bool mFired; - typedef std::set type_set_hp; - static type_set_hp sActiveHoldingPatterns; - static S32 sNextIndex; - S32 mIndex; - bool mIsMostRecent; - std::set mLateArrivals; - bool mIsAllComplete; -}; - -LLWearableHoldingPattern::type_set_hp LLWearableHoldingPattern::sActiveHoldingPatterns; -S32 LLWearableHoldingPattern::sNextIndex = 0; - -LLWearableHoldingPattern::LLWearableHoldingPattern(): - mResolved(0), - mFired(false), - mIsMostRecent(true), - mIsAllComplete(false) -{ - if (countActive()>0) - { - LL_INFOS() << "Creating LLWearableHoldingPattern when " - << countActive() - << " other attempts are active." - << " Flagging others as invalid." - << LL_ENDL; - for (type_set_hp::iterator it = sActiveHoldingPatterns.begin(); - it != sActiveHoldingPatterns.end(); - ++it) - { - (*it)->mIsMostRecent = false; - } - - } - mIndex = sNextIndex++; - sActiveHoldingPatterns.insert(this); - LL_DEBUGS("Avatar") << "HP " << index() << " created" << LL_ENDL; - selfStartPhase("holding_pattern"); -} - -LLWearableHoldingPattern::~LLWearableHoldingPattern() -{ - sActiveHoldingPatterns.erase(this); - if (isMostRecent()) - { - selfStopPhase("holding_pattern"); - } - LL_DEBUGS("Avatar") << "HP " << index() << " deleted" << LL_ENDL; -} - -bool LLWearableHoldingPattern::isMostRecent() -{ - return mIsMostRecent; -} - -LLWearableHoldingPattern::found_list_t& LLWearableHoldingPattern::getFoundList() -{ - return mFoundList; -} - -void LLWearableHoldingPattern::eraseTypeToLink(LLWearableType::EType type) -{ - mTypesToLink.erase(type); -} - -void LLWearableHoldingPattern::eraseTypeToRecover(LLWearableType::EType type) -{ - mTypesToRecover.erase(type); -} - -void LLWearableHoldingPattern::setObjItems(const LLInventoryModel::item_array_t& items) -{ - mObjItems = items; -} - -void LLWearableHoldingPattern::setGestItems(const LLInventoryModel::item_array_t& items) -{ - mGestItems = items; -} - -bool LLWearableHoldingPattern::isFetchCompleted() -{ - return (mResolved >= (S32)getFoundList().size()); // have everything we were waiting for? -} - -bool LLWearableHoldingPattern::isTimedOut() -{ - return mWaitTime.hasExpired(); -} - -void LLWearableHoldingPattern::checkMissingWearables() -{ - if (!isMostRecent()) - { - // runway why don't we actually skip here? - LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; - } - - std::vector found_by_type(LLWearableType::WT_COUNT,0); - std::vector requested_by_type(LLWearableType::WT_COUNT,0); - for (found_list_t::iterator it = getFoundList().begin(); it != getFoundList().end(); ++it) - { - LLFoundData &data = *it; - if (data.mWearableType < LLWearableType::WT_COUNT) - requested_by_type[data.mWearableType]++; - if (data.mWearable) - found_by_type[data.mWearableType]++; - } - - for (S32 type = 0; type < LLWearableType::WT_COUNT; ++type) - { - if (requested_by_type[type] > found_by_type[type]) - { - LL_WARNS() << self_av_string() << "got fewer wearables than requested, type " << type << ": requested " << requested_by_type[type] << ", found " << found_by_type[type] << LL_ENDL; - } - if (found_by_type[type] > 0) - continue; - if ( - // If at least one wearable of certain types (pants/shirt/skirt) - // was requested but none was found, create a default asset as a replacement. - // In all other cases, don't do anything. - // For critical types (shape/hair/skin/eyes), this will keep the avatar as a cloud - // due to logic in LLVOAvatarSelf::getIsCloud(). - // For non-critical types (tatoo, socks, etc.) the wearable will just be missing. - (requested_by_type[type] > 0) && - ((type == LLWearableType::WT_PANTS) || (type == LLWearableType::WT_SHIRT) || (type == LLWearableType::WT_SKIRT))) - { - mTypesToRecover.insert(type); - mTypesToLink.insert(type); - recoverMissingWearable((LLWearableType::EType)type); - LL_WARNS() << self_av_string() << "need to replace " << type << LL_ENDL; - } - } - - resetTime(60.0F); - - if (isMostRecent()) - { - selfStartPhase("get_missing_wearables_2"); - } - if (!pollMissingWearables()) - { - doOnIdleRepeating(boost::bind(&LLWearableHoldingPattern::pollMissingWearables,this)); - } -} - -void LLWearableHoldingPattern::onAllComplete() -{ - if (isAgentAvatarValid()) - { - gAgentAvatarp->outputRezTiming("Agent wearables fetch complete"); - } - - if (!isMostRecent()) - { - // runway need to skip here? - LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; - } - - // Activate all gestures in this folder - if (mGestItems.size() > 0) - { - LL_DEBUGS("Avatar") << self_av_string() << "Activating " << mGestItems.size() << " gestures" << LL_ENDL; - - LLGestureMgr::instance().activateGestures(mGestItems); - - // Update the inventory item labels to reflect the fact - // they are active. - LLViewerInventoryCategory* catp = - gInventory.getCategory(LLAppearanceMgr::instance().getCOF()); - - if (catp) - { - gInventory.updateCategory(catp); - gInventory.notifyObservers(); - } - } - - if (isAgentAvatarValid()) - { - LL_DEBUGS("Avatar") << self_av_string() << "Updating " << mObjItems.size() << " attachments" << LL_ENDL; - LLAgentWearables::llvo_vec_t objects_to_remove; - LLAgentWearables::llvo_vec_t objects_to_retain; - LLInventoryModel::item_array_t items_to_add; - - LLAgentWearables::findAttachmentsAddRemoveInfo(mObjItems, - objects_to_remove, - objects_to_retain, - items_to_add); - - LL_DEBUGS("Avatar") << self_av_string() << "Removing " << objects_to_remove.size() - << " attachments" << LL_ENDL; - - // Here we remove the attachment pos overrides for *all* - // attachments, even those that are not being removed. This is - // needed to get joint positions all slammed down to their - // pre-attachment states. - gAgentAvatarp->clearAttachmentOverrides(); - - if (objects_to_remove.size() || items_to_add.size()) - { - LL_DEBUGS("Avatar") << "ATT will remove " << objects_to_remove.size() - << " and add " << items_to_add.size() << " items" << LL_ENDL; - } - - // Take off the attachments that will no longer be in the outfit. - LLAgentWearables::userRemoveMultipleAttachments(objects_to_remove); - - // Update wearables. - LL_INFOS("Avatar") << self_av_string() << "HP " << index() << " updating agent wearables with " - << mResolved << " wearable items " << LL_ENDL; - LLAppearanceMgr::instance().updateAgentWearables(this); - - // Restore attachment pos overrides for the attachments that - // are remaining in the outfit. - for (LLAgentWearables::llvo_vec_t::iterator it = objects_to_retain.begin(); - it != objects_to_retain.end(); - ++it) - { - LLViewerObject *objectp = *it; - if (!objectp->isAnimatedObject()) - { - gAgentAvatarp->addAttachmentOverridesForObject(objectp); - } - } - - // Add new attachments to match those requested. - LL_DEBUGS("Avatar") << self_av_string() << "Adding " << items_to_add.size() << " attachments" << LL_ENDL; - LLAgentWearables::userAttachMultipleAttachments(items_to_add); - } - - if (isFetchCompleted() && isMissingCompleted()) - { - // Only safe to delete if all wearable callbacks and all missing wearables completed. - delete this; - } - else - { - mIsAllComplete = true; - handleLateArrivals(); - } -} - -void LLWearableHoldingPattern::onFetchCompletion() -{ - if (isMostRecent()) - { - selfStopPhase("get_wearables_2"); - } - - if (!isMostRecent()) - { - // runway skip here? - LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; - } - - checkMissingWearables(); -} - -// Runs as an idle callback until all wearables are fetched (or we time out). -bool LLWearableHoldingPattern::pollFetchCompletion() -{ - if (!isMostRecent()) - { - // runway skip here? - LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; - } - - bool completed = isFetchCompleted(); - bool timed_out = isTimedOut(); - bool done = completed || timed_out; - - if (done) - { - LL_INFOS("Avatar") << self_av_string() << "HP " << index() << " polling, done status: " << completed << " timed out " << timed_out - << " elapsed " << mWaitTime.getElapsedTimeF32() << LL_ENDL; - - mFired = true; - - if (timed_out) - { - LL_WARNS() << self_av_string() << "Exceeded max wait time for wearables, updating appearance based on what has arrived" << LL_ENDL; - } - - onFetchCompletion(); - } - return done; -} - -void recovered_item_link_cb(const LLUUID& item_id, LLWearableType::EType type, LLViewerWearable *wearable, LLWearableHoldingPattern* holder) -{ - if (!holder->isMostRecent()) - { - LL_WARNS() << "HP " << holder->index() << " skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; - // runway skip here? - } - - LL_INFOS("Avatar") << "HP " << holder->index() << " recovered item link for type " << type << LL_ENDL; - holder->eraseTypeToLink(type); - // Add wearable to FoundData for actual wearing - LLViewerInventoryItem *item = gInventory.getItem(item_id); - LLViewerInventoryItem *linked_item = item ? item->getLinkedItem() : NULL; - - if (linked_item) - { - gInventory.addChangedMask(LLInventoryObserver::LABEL, linked_item->getUUID()); - - if (item) - { - LLFoundData found(linked_item->getUUID(), - linked_item->getAssetUUID(), - linked_item->getName(), - linked_item->getType(), - linked_item->isWearableType() ? linked_item->getWearableType() : LLWearableType::WT_INVALID, - true // is replacement - ); - found.mWearable = wearable; - holder->getFoundList().push_front(found); - } - else - { - LL_WARNS() << self_av_string() << "inventory link not found for recovered wearable" << LL_ENDL; - } - } - else - { - LL_WARNS() << self_av_string() << "HP " << holder->index() << " inventory link not found for recovered wearable" << LL_ENDL; - } -} - -void recovered_item_cb(const LLUUID& item_id, LLWearableType::EType type, LLViewerWearable *wearable, LLWearableHoldingPattern* holder) -{ - if (!holder->isMostRecent()) - { - // runway skip here? - LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; - } - - LL_DEBUGS("Avatar") << self_av_string() << "Recovered item for type " << type << LL_ENDL; - LLConstPointer itemp = gInventory.getItem(item_id); - wearable->setItemID(item_id); - holder->eraseTypeToRecover(type); - llassert(itemp); - if (itemp) - { - LLPointer cb = new LLBoostFuncInventoryCallback(boost::bind(recovered_item_link_cb,_1,type,wearable,holder)); - - link_inventory_object(LLAppearanceMgr::instance().getCOF(), itemp, cb); - } -} - -void LLWearableHoldingPattern::recoverMissingWearable(LLWearableType::EType type) -{ - if (!isMostRecent()) - { - // runway skip here? - LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; - } - - // Try to recover by replacing missing wearable with a new one. - LLNotificationsUtil::add("ReplacedMissingWearable"); - LL_DEBUGS("Avatar") << "Wearable of type '" << LLWearableType::getInstance()->getTypeName(type) - << "' could not be downloaded. Replaced inventory item with default wearable." << LL_ENDL; - LLViewerWearable* wearable = LLWearableList::instance().createNewWearable(type, gAgentAvatarp); - - // Add a new one in the lost and found folder. - const LLUUID lost_and_found_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); - LLPointer cb = new LLBoostFuncInventoryCallback(boost::bind(recovered_item_cb,_1,type,wearable,this)); - - create_inventory_wearable(gAgent.getID(), - gAgent.getSessionID(), - lost_and_found_id, - wearable->getTransactionID(), - wearable->getName(), - wearable->getDescription(), - wearable->getAssetType(), - wearable->getType(), - wearable->getPermissions().getMaskNextOwner(), - cb); -} - -bool LLWearableHoldingPattern::isMissingCompleted() -{ - return mTypesToLink.size()==0 && mTypesToRecover.size()==0; -} - -void LLWearableHoldingPattern::clearCOFLinksForMissingWearables() -{ - for (found_list_t::iterator it = getFoundList().begin(); it != getFoundList().end(); ++it) - { - LLFoundData &data = *it; - if ((data.mWearableType < LLWearableType::WT_COUNT) && (!data.mWearable)) - { - // Wearable link that was never resolved; remove links to it from COF - LL_INFOS("Avatar") << self_av_string() << "HP " << index() << " removing link for unresolved item " << data.mItemID.asString() << LL_ENDL; - LLAppearanceMgr::instance().removeCOFItemLinks(data.mItemID); - } - } -} - -bool LLWearableHoldingPattern::pollMissingWearables() -{ - if (!isMostRecent()) - { - // runway skip here? - LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; - } - - bool timed_out = isTimedOut(); - bool missing_completed = isMissingCompleted(); - bool done = timed_out || missing_completed; - - if (!done) - { - LL_INFOS("Avatar") << self_av_string() << "HP " << index() << " polling missing wearables, waiting for items " << mTypesToRecover.size() - << " links " << mTypesToLink.size() - << " wearables, timed out " << timed_out - << " elapsed " << mWaitTime.getElapsedTimeF32() - << " done " << done << LL_ENDL; - } - - if (done) - { - if (isMostRecent()) - { - selfStopPhase("get_missing_wearables_2"); - } - - gAgentAvatarp->debugWearablesLoaded(); - - // BAP - if we don't call clearCOFLinksForMissingWearables() - // here, we won't have to add the link back in later if the - // wearable arrives late. This is to avoid corruption of - // wearable ordering info. Also has the effect of making - // unworn item links visible in the COF under some - // circumstances. - - //clearCOFLinksForMissingWearables(); - onAllComplete(); - } - return done; -} - -// Handle wearables that arrived after the timeout period expired. -void LLWearableHoldingPattern::handleLateArrivals() -{ - // Only safe to run if we have previously finished the missing - // wearables and other processing - otherwise we could be in some - // intermediate state - but have not been superceded by a later - // outfit change request. - if (mLateArrivals.size() == 0) - { - // Nothing to process. - return; - } - if (!isMostRecent()) - { - LL_WARNS() << self_av_string() << "Late arrivals not handled - outfit change no longer valid" << LL_ENDL; - } - if (!mIsAllComplete) - { - LL_WARNS() << self_av_string() << "Late arrivals not handled - in middle of missing wearables processing" << LL_ENDL; - } - - LL_INFOS("Avatar") << self_av_string() << "HP " << index() << " need to handle " << mLateArrivals.size() << " late arriving wearables" << LL_ENDL; - - // Update mFoundList using late-arriving wearables. - std::set replaced_types; - for (LLWearableHoldingPattern::found_list_t::iterator iter = getFoundList().begin(); - iter != getFoundList().end(); ++iter) - { - LLFoundData& data = *iter; - for (std::set::iterator wear_it = mLateArrivals.begin(); - wear_it != mLateArrivals.end(); - ++wear_it) - { - LLViewerWearable *wearable = *wear_it; - - if(wearable->getAssetID() == data.mAssetID) - { - data.mWearable = wearable; - - replaced_types.insert(data.mWearableType); - - // BAP - if we didn't call - // clearCOFLinksForMissingWearables() earlier, we - // don't need to restore the link here. Fixes - // wearable ordering problems. - - // LLAppearanceMgr::instance().addCOFItemLink(data.mItemID,false); - - // BAP failing this means inventory or asset server - // are corrupted in a way we don't handle. - llassert((data.mWearableType < LLWearableType::WT_COUNT) && (wearable->getType() == data.mWearableType)); - break; - } - } - } - - // Remove COF links for any default wearables previously used to replace the late arrivals. - // All this pussyfooting around with a while loop and explicit - // iterator incrementing is to allow removing items from the list - // without clobbering the iterator we're using to navigate. - LLWearableHoldingPattern::found_list_t::iterator iter = getFoundList().begin(); - while (iter != getFoundList().end()) - { - LLFoundData& data = *iter; - - // If an item of this type has recently shown up, removed the corresponding replacement wearable from COF. - if (data.mWearable && data.mIsReplacement && - replaced_types.find(data.mWearableType) != replaced_types.end()) - { - LLAppearanceMgr::instance().removeCOFItemLinks(data.mItemID); - std::list::iterator clobber_ator = iter; - ++iter; - getFoundList().erase(clobber_ator); - } - else - { - ++iter; - } - } - - // Clear contents of late arrivals. - mLateArrivals.clear(); - - // Update appearance based on mFoundList - LLAppearanceMgr::instance().updateAgentWearables(this); -} - -void LLWearableHoldingPattern::resetTime(F32 timeout) -{ - mWaitTime.reset(); - mWaitTime.setTimerExpirySec(timeout); -} - -void LLWearableHoldingPattern::onWearableAssetFetch(LLViewerWearable *wearable) -{ - if (!isMostRecent()) - { - LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; - } - - mResolved += 1; // just counting callbacks, not successes. - LL_DEBUGS("Avatar") << self_av_string() << "HP " << index() << " resolved " << mResolved << "/" << getFoundList().size() << LL_ENDL; - if (!wearable) - { - LL_WARNS() << self_av_string() << "no wearable found" << LL_ENDL; - } - - if (mFired) - { - LL_WARNS() << self_av_string() << "called after holder fired" << LL_ENDL; - if (wearable) - { - mLateArrivals.insert(wearable); - if (mIsAllComplete) - { - handleLateArrivals(); - } - } - return; - } - - if (!wearable) - { - return; - } - - U32 use_count = 0; - for (LLWearableHoldingPattern::found_list_t::iterator iter = getFoundList().begin(); - iter != getFoundList().end(); ++iter) - { - LLFoundData& data = *iter; - if (wearable->getAssetID() == data.mAssetID) - { - // Failing this means inventory or asset server are corrupted in a way we don't handle. - if ((data.mWearableType >= LLWearableType::WT_COUNT) || (wearable->getType() != data.mWearableType)) - { - LL_WARNS() << self_av_string() << "recovered wearable but type invalid. inventory wearable type: " << data.mWearableType << " asset wearable type: " << wearable->getType() << LL_ENDL; - break; - } - - if (use_count == 0) - { - data.mWearable = wearable; - use_count++; - } - else - { - LLViewerInventoryItem* wearable_item = gInventory.getItem(data.mItemID); - if (wearable_item && wearable_item->isFinished() && wearable_item->getPermissions().allowModifyBy(gAgentID)) - { - // We can't edit and do some other interactions with same asset twice, copy it - // Note: can't update incomplete items. Usually attached from previous viewer build, but - // consider adding fetch and completion callback - LLViewerWearable* new_wearable = LLWearableList::instance().createCopy(wearable, wearable->getName()); - data.mWearable = new_wearable; - data.mAssetID = new_wearable->getAssetID(); - - // Update existing inventory item - wearable_item->setAssetUUID(new_wearable->getAssetID()); - wearable_item->setTransactionID(new_wearable->getTransactionID()); - gInventory.updateItem(wearable_item, LLInventoryObserver::INTERNAL); - wearable_item->updateServer(false); - - use_count++; - } - else - { - // Note: technically a bug, LLViewerWearable can identify only one item id at a time, - // yet we are tying it to multiple items here. - // LLViewerWearable need to support more then one item. - LL_WARNS() << "Same LLViewerWearable is used by multiple items! " << wearable->getAssetID() << LL_ENDL; - data.mWearable = wearable; - } - } - } - } - - if (use_count > 1) - { - LL_WARNS() << "Copying wearable, multiple asset id uses! " << wearable->getAssetID() << LL_ENDL; - gInventory.notifyObservers(); - } -} - -static void onWearableAssetFetch(LLViewerWearable* wearable, void* data) -{ - LLWearableHoldingPattern* holder = (LLWearableHoldingPattern*)data; - holder->onWearableAssetFetch(wearable); -} - - -static void removeDuplicateItems(LLInventoryModel::item_array_t& items) -{ - LLInventoryModel::item_array_t new_items; - std::set items_seen; - std::deque tmp_list; - // Traverse from the front and keep the first of each item - // encountered, so we actually keep the *last* of each duplicate - // item. This is needed to give the right priority when adding - // duplicate items to an existing outfit. - for (S32 i=items.size()-1; i>=0; i--) - { - LLViewerInventoryItem *item = items.at(i); - LLUUID item_id = item->getLinkedUUID(); - if (items_seen.find(item_id)!=items_seen.end()) - continue; - items_seen.insert(item_id); - tmp_list.push_front(item); - } - for (std::deque::iterator it = tmp_list.begin(); - it != tmp_list.end(); - ++it) - { - new_items.push_back(*it); - } - items = new_items; -} - -//========================================================================= - -const std::string LLAppearanceMgr::sExpectedTextureName = "OutfitPreview"; - -const LLUUID LLAppearanceMgr::getCOF() const -{ - return mCOFID; -} - -S32 LLAppearanceMgr::getCOFVersion() const -{ - LLViewerInventoryCategory *cof = gInventory.getCategory(getCOF()); - if (cof) - { - return cof->getVersion(); - } - else - { - return LLViewerInventoryCategory::VERSION_UNKNOWN; - } -} - -void LLAppearanceMgr::initCOFID() -{ - mCOFID = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); -} - -const LLViewerInventoryItem* LLAppearanceMgr::getBaseOutfitLink() -{ - const LLUUID& current_outfit_cat = getCOF(); - LLInventoryModel::cat_array_t cat_array; - LLInventoryModel::item_array_t item_array; - // Can't search on FT_OUTFIT since links to categories return FT_CATEGORY for type since they don't - // return preferred type. - LLIsType is_category( LLAssetType::AT_CATEGORY ); - gInventory.collectDescendentsIf(current_outfit_cat, - cat_array, - item_array, - false, - is_category); - for (LLInventoryModel::item_array_t::const_iterator iter = item_array.begin(); - iter != item_array.end(); - iter++) - { - const LLViewerInventoryItem *item = (*iter); - const LLViewerInventoryCategory *cat = item->getLinkedCategory(); - if (cat && cat->getPreferredType() == LLFolderType::FT_OUTFIT) - { - const LLUUID parent_id = cat->getParentUUID(); - LLViewerInventoryCategory* parent_cat = gInventory.getCategory(parent_id); - // if base outfit moved to trash it means that we don't have base outfit - if (parent_cat != NULL && parent_cat->getPreferredType() == LLFolderType::FT_TRASH) - { - return NULL; - } - return item; - } - } - return NULL; -} - -bool LLAppearanceMgr::getBaseOutfitName(std::string& name) -{ - const LLViewerInventoryItem* outfit_link = getBaseOutfitLink(); - if(outfit_link) - { - const LLViewerInventoryCategory *cat = outfit_link->getLinkedCategory(); - if (cat) - { - name = cat->getName(); - return true; - } - } - return false; -} - -const LLUUID LLAppearanceMgr::getBaseOutfitUUID() -{ - const LLViewerInventoryItem* outfit_link = getBaseOutfitLink(); - if (!outfit_link || !outfit_link->getIsLinkType()) return LLUUID::null; - - const LLViewerInventoryCategory* outfit_cat = outfit_link->getLinkedCategory(); - if (!outfit_cat) return LLUUID::null; - - if (outfit_cat->getPreferredType() != LLFolderType::FT_OUTFIT) - { - LL_WARNS() << "Expected outfit type:" << LLFolderType::FT_OUTFIT << " but got type:" << outfit_cat->getType() << " for folder name:" << outfit_cat->getName() << LL_ENDL; - return LLUUID::null; - } - - return outfit_cat->getUUID(); -} - -void wear_on_avatar_cb(const LLUUID& inv_item, bool do_replace = false) -{ - if (inv_item.isNull()) - return; - - LLViewerInventoryItem *item = gInventory.getItem(inv_item); - if (item) - { - LLAppearanceMgr::instance().wearItemOnAvatar(inv_item, true, do_replace); - } -} - -void LLAppearanceMgr::wearItemsOnAvatar(const uuid_vec_t& item_ids_to_wear, - bool do_update, - bool replace, - LLPointer cb) -{ - LL_DEBUGS("UIUsage") << "wearItemsOnAvatar" << LL_ENDL; - LLUIUsage::instance().logCommand("Avatar.WearItem"); - - bool first = true; - - LLInventoryObject::const_object_list_t items_to_link; - - for (uuid_vec_t::const_iterator it = item_ids_to_wear.begin(); - it != item_ids_to_wear.end(); - ++it) - { - replace = first && replace; - first = false; - - const LLUUID& item_id_to_wear = *it; - - if (item_id_to_wear.isNull()) - { - LL_DEBUGS("Avatar") << "null id " << item_id_to_wear << LL_ENDL; - continue; - } - - LLViewerInventoryItem* item_to_wear = gInventory.getItem(item_id_to_wear); - if (!item_to_wear) - { - LL_DEBUGS("Avatar") << "inventory item not found for id " << item_id_to_wear << LL_ENDL; - continue; - } - - if (gInventory.isObjectDescendentOf(item_to_wear->getUUID(), gInventory.getLibraryRootFolderID())) - { - LL_DEBUGS("Avatar") << "inventory item in library, will copy and wear " - << item_to_wear->getName() << " id " << item_id_to_wear << LL_ENDL; - LLPointer cb = new LLBoostFuncInventoryCallback(boost::bind(wear_on_avatar_cb,_1,replace)); - copy_inventory_item(gAgent.getID(), item_to_wear->getPermissions().getOwner(), - item_to_wear->getUUID(), LLUUID::null, std::string(), cb); - continue; - } - else if (!gInventory.isObjectDescendentOf(item_to_wear->getUUID(), gInventory.getRootFolderID())) - { - // not in library and not in agent's inventory - LL_DEBUGS("Avatar") << "inventory item not in user inventory or library, skipping " - << item_to_wear->getName() << " id " << item_id_to_wear << LL_ENDL; - continue; - } - else if (gInventory.isObjectDescendentOf(item_to_wear->getUUID(), gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH))) - { - LLNotificationsUtil::add("CannotWearTrash"); - LL_DEBUGS("Avatar") << "inventory item is in trash, skipping " - << item_to_wear->getName() << " id " << item_id_to_wear << LL_ENDL; - continue; - } - else if (isLinkedInCOF(item_to_wear->getUUID())) // EXT-84911 - { - LL_DEBUGS("Avatar") << "inventory item is already in COF, skipping " - << item_to_wear->getName() << " id " << item_id_to_wear << LL_ENDL; - continue; - } - - switch (item_to_wear->getType()) - { - case LLAssetType::AT_CLOTHING: - { - if (gAgentWearables.areWearablesLoaded()) - { - if (!cb && do_update) - { - cb = new LLUpdateAppearanceAndEditWearableOnDestroy(item_id_to_wear); - } - LLWearableType::EType type = item_to_wear->getWearableType(); - S32 wearable_count = gAgentWearables.getWearableCount(type); - if ((replace && wearable_count != 0) || !gAgentWearables.canAddWearable(type)) - { - LLUUID item_id = gAgentWearables.getWearableItemID(item_to_wear->getWearableType(), - wearable_count-1); - removeCOFItemLinks(item_id, cb); - } - - items_to_link.push_back(item_to_wear); - } - } - break; - - case LLAssetType::AT_BODYPART: - { - // TODO: investigate wearables may not be loaded at this point EXT-8231 - - // Remove the existing wearables of the same type. - // Remove existing body parts anyway because we must not be able to wear e.g. two skins. - removeCOFLinksOfType(item_to_wear->getWearableType()); - if (!cb && do_update) - { - cb = new LLUpdateAppearanceAndEditWearableOnDestroy(item_id_to_wear); - } - items_to_link.push_back(item_to_wear); - } - break; - - case LLAssetType::AT_OBJECT: - { - rez_attachment(item_to_wear, NULL, replace); - } - break; - - default: continue; - } - } - - // Batch up COF link creation - more efficient if using AIS. - if (items_to_link.size()) - { - link_inventory_array(getCOF(), items_to_link, cb); - } -} - -void LLAppearanceMgr::wearItemOnAvatar(const LLUUID& item_id_to_wear, - bool do_update, - bool replace, - LLPointer cb) -{ - uuid_vec_t ids; - ids.push_back(item_id_to_wear); - wearItemsOnAvatar(ids, do_update, replace, cb); -} - -// Update appearance from outfit folder. -void LLAppearanceMgr::changeOutfit(bool proceed, const LLUUID& category, bool append) -{ - if (!proceed) - return; - LLAppearanceMgr::instance().updateCOF(category,append); -} - -void LLAppearanceMgr::replaceCurrentOutfit(const LLUUID& new_outfit) -{ - LLViewerInventoryCategory* cat = gInventory.getCategory(new_outfit); - wearInventoryCategory(cat, false, false); -} - -// Remove existing photo link from outfit folder. -void LLAppearanceMgr::removeOutfitPhoto(const LLUUID& outfit_id) -{ - LLInventoryModel::cat_array_t sub_cat_array; - LLInventoryModel::item_array_t outfit_item_array; - gInventory.collectDescendents( - outfit_id, - sub_cat_array, - outfit_item_array, - LLInventoryModel::EXCLUDE_TRASH); - for (LLViewerInventoryItem* outfit_item : outfit_item_array) - { - LLViewerInventoryItem* linked_item = outfit_item->getLinkedItem(); - if (linked_item != NULL) - { - if (linked_item->getActualType() == LLAssetType::AT_TEXTURE) - { - gInventory.removeItem(outfit_item->getUUID()); - } - } - else if (outfit_item->getActualType() == LLAssetType::AT_TEXTURE) - { - gInventory.removeItem(outfit_item->getUUID()); - } - } -} - -// Open outfit renaming dialog. -void LLAppearanceMgr::renameOutfit(const LLUUID& outfit_id) -{ - LLViewerInventoryCategory* cat = gInventory.getCategory(outfit_id); - if (!cat) - { - return; - } - - LLSD args; - args["NAME"] = cat->getName(); - - LLSD payload; - payload["cat_id"] = outfit_id; - - LLNotificationsUtil::add("RenameOutfit", args, payload, boost::bind(onOutfitRename, _1, _2)); -} - -// User typed new outfit name. -// static -void LLAppearanceMgr::onOutfitRename(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option != 0) return; // canceled - - std::string outfit_name = response["new_name"].asString(); - LLStringUtil::trim(outfit_name); - if (!outfit_name.empty()) - { - LLUUID cat_id = notification["payload"]["cat_id"].asUUID(); - rename_category(&gInventory, cat_id, outfit_name); - } -} - -void LLAppearanceMgr::setOutfitLocked(bool locked) -{ - if (mOutfitLocked == locked) - { - return; - } - - mOutfitLocked = locked; - if (locked) - { - mUnlockOutfitTimer->reset(); - mUnlockOutfitTimer->start(); - } - else - { - mUnlockOutfitTimer->stop(); - } - - LLOutfitObserver::instance().notifyOutfitLockChanged(); -} - -void LLAppearanceMgr::addCategoryToCurrentOutfit(const LLUUID& cat_id) -{ - LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); - wearInventoryCategory(cat, false, true); -} - -void LLAppearanceMgr::takeOffOutfit(const LLUUID& cat_id) -{ - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - LLFindWearablesEx collector(/*is_worn=*/ true, /*include_body_parts=*/ false); - - gInventory.collectDescendentsIf(cat_id, cats, items, false, collector); - - LLInventoryModel::item_array_t::const_iterator it = items.begin(); - const LLInventoryModel::item_array_t::const_iterator it_end = items.end(); - uuid_vec_t uuids_to_remove; - for( ; it_end != it; ++it) - { - LLViewerInventoryItem* item = *it; - uuids_to_remove.push_back(item->getUUID()); - } - removeItemsFromAvatar(uuids_to_remove); - - // deactivate all gestures in the outfit folder - LLInventoryModel::item_array_t gest_items; - getDescendentsOfAssetType(cat_id, gest_items, LLAssetType::AT_GESTURE); - for(S32 i = 0; i < gest_items.size(); ++i) - { - LLViewerInventoryItem *gest_item = gest_items[i]; - if ( LLGestureMgr::instance().isGestureActive( gest_item->getLinkedUUID()) ) - { - LLGestureMgr::instance().deactivateGesture( gest_item->getLinkedUUID() ); - } - } -} - -// Create a copy of src_id + contents as a subfolder of dst_id. -void LLAppearanceMgr::shallowCopyCategory(const LLUUID& src_id, const LLUUID& dst_id, - LLPointer cb) -{ - LLInventoryCategory *src_cat = gInventory.getCategory(src_id); - if (!src_cat) - { - LL_WARNS() << "folder not found for src " << src_id.asString() << LL_ENDL; - return; - } - LL_INFOS() << "starting, src_id " << src_id << " name " << src_cat->getName() << " dst_id " << dst_id << LL_ENDL; - LLUUID parent_id = dst_id; - if(parent_id.isNull()) - { - parent_id = gInventory.getRootFolderID(); - } - gInventory.createNewCategory( - parent_id, - LLFolderType::FT_NONE, - src_cat->getName(), - [src_id, cb](const LLUUID &new_id) - { - LLAppearanceMgr::getInstance()->shallowCopyCategoryContents(src_id, new_id, cb); - - gInventory.notifyObservers(); - }, - src_cat->getThumbnailUUID() - ); -} - -void LLAppearanceMgr::slamCategoryLinks(const LLUUID& src_id, const LLUUID& dst_id, - bool include_folder_links, LLPointer cb) -{ - LLInventoryModel::cat_array_t* cats; - LLInventoryModel::item_array_t* items; - LLSD contents = LLSD::emptyArray(); - gInventory.getDirectDescendentsOf(src_id, cats, items); - if (!cats || !items) - { - // NULL means the call failed -- cats/items map doesn't exist (note: this does NOT mean - // that the cat just doesn't have any items or subfolders). - LLViewerInventoryCategory* category = gInventory.getCategory(src_id); - if (category) - { - LL_WARNS() << "Category '" << category->getName() << "' descendents corrupted, linking content failed." << LL_ENDL; - } - else - { - LL_WARNS() << "Category could not be retrieved, linking content failed." << LL_ENDL; - } - llassert(cats != NULL && items != NULL); - - return; - } - - LL_INFOS() << "copying " << items->size() << " items" << LL_ENDL; - for (LLInventoryModel::item_array_t::const_iterator iter = items->begin(); - iter != items->end(); - ++iter) - { - const LLViewerInventoryItem* item = (*iter); - switch (item->getActualType()) - { - case LLAssetType::AT_LINK: - { - LL_DEBUGS("Avatar") << "linking inventory item " << item->getName() << LL_ENDL; - //getActualDescription() is used for a new description - //to propagate ordering information saved in descriptions of links - LLSD item_contents; - item_contents["name"] = item->getName(); - item_contents["desc"] = item->getActualDescription(); - item_contents["linked_id"] = item->getLinkedUUID(); - item_contents["type"] = LLAssetType::AT_LINK; - contents.append(item_contents); - break; - } - case LLAssetType::AT_LINK_FOLDER: - { - LLViewerInventoryCategory *catp = item->getLinkedCategory(); - if (catp && include_folder_links) - { - LL_DEBUGS("Avatar") << "linking inventory folder " << item->getName() << LL_ENDL; - LLSD base_contents; - base_contents["name"] = catp->getName(); - base_contents["desc"] = ""; // categories don't have descriptions. - base_contents["linked_id"] = catp->getLinkedUUID(); - base_contents["type"] = LLAssetType::AT_LINK_FOLDER; - contents.append(base_contents); - } - break; - } - default: - { - // Linux refuses to compile unless all possible enums are handled. Really, Linux? - break; - } - } - } - slam_inventory_folder(dst_id, contents, cb); -} -// Copy contents of src_id to dst_id. -void LLAppearanceMgr::shallowCopyCategoryContents(const LLUUID& src_id, const LLUUID& dst_id, - LLPointer cb) -{ - LLInventoryModel::cat_array_t* cats; - LLInventoryModel::item_array_t* items; - gInventory.getDirectDescendentsOf(src_id, cats, items); - LL_INFOS() << "copying " << items->size() << " items" << LL_ENDL; - LLInventoryObject::const_object_list_t link_array; - for (LLInventoryModel::item_array_t::const_iterator iter = items->begin(); - iter != items->end(); - ++iter) - { - const LLViewerInventoryItem* item = (*iter); - switch (item->getActualType()) - { - case LLAssetType::AT_LINK: - { - LL_DEBUGS("Avatar") << "linking inventory item " << item->getName() << LL_ENDL; - link_array.push_back(LLConstPointer(item)); - break; - } - case LLAssetType::AT_LINK_FOLDER: - { - LLViewerInventoryCategory *catp = item->getLinkedCategory(); - // Skip copying outfit links. - if (catp && catp->getPreferredType() != LLFolderType::FT_OUTFIT) - { - LL_DEBUGS("Avatar") << "linking inventory folder " << item->getName() << LL_ENDL; - link_array.push_back(LLConstPointer(item)); - } - break; - } - case LLAssetType::AT_CLOTHING: - case LLAssetType::AT_OBJECT: - case LLAssetType::AT_BODYPART: - case LLAssetType::AT_GESTURE: - { - LL_DEBUGS("Avatar") << "copying inventory item " << item->getName() << LL_ENDL; - copy_inventory_item(gAgent.getID(), - item->getPermissions().getOwner(), - item->getUUID(), - dst_id, - item->getName(), - cb); - break; - } - default: - // Ignore non-outfit asset types - break; - } - } - if (!link_array.empty()) - { - link_inventory_array(dst_id, link_array, cb); - } -} - -bool LLAppearanceMgr::getCanMakeFolderIntoOutfit(const LLUUID& folder_id) -{ - // These are the wearable items that are required for considering this - // folder as containing a complete outfit. - U32 required_wearables = 0; - required_wearables |= 1LL << LLWearableType::WT_SHAPE; - required_wearables |= 1LL << LLWearableType::WT_SKIN; - required_wearables |= 1LL << LLWearableType::WT_HAIR; - required_wearables |= 1LL << LLWearableType::WT_EYES; - - // These are the wearables that the folder actually contains. - U32 folder_wearables = 0; - LLInventoryModel::cat_array_t* cats; - LLInventoryModel::item_array_t* items; - gInventory.getDirectDescendentsOf(folder_id, cats, items); - for (LLInventoryModel::item_array_t::const_iterator iter = items->begin(); - iter != items->end(); - ++iter) - { - const LLViewerInventoryItem* item = (*iter); - if (item->isWearableType()) - { - const LLWearableType::EType wearable_type = item->getWearableType(); - folder_wearables |= 1LL << wearable_type; - } - } - - // If the folder contains the required wearables, return true. - return ((required_wearables & folder_wearables) == required_wearables); -} - -bool LLAppearanceMgr::getCanRemoveOutfit(const LLUUID& outfit_cat_id) -{ - // Disallow removing the base outfit. - if (outfit_cat_id == getBaseOutfitUUID()) - { - return false; - } - - // Check if the outfit folder itself is removable. - if (!get_is_category_removable(&gInventory, outfit_cat_id)) - { - return false; - } - - // Check for the folder's non-removable descendants. - LLFindNonRemovableObjects filter_non_removable; - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - gInventory.collectDescendentsIf(outfit_cat_id, cats, items, false, filter_non_removable); - if (!cats.empty() || !items.empty()) - { - return false; - } - - return true; -} - -// static -bool LLAppearanceMgr::getCanRemoveFromCOF(const LLUUID& outfit_cat_id) -{ - if (gAgentWearables.isCOFChangeInProgress()) - { - return false; - } - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - LLFindWearablesEx is_worn(/*is_worn=*/ true, /*include_body_parts=*/ false); - gInventory.collectDescendentsIf(outfit_cat_id, - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - is_worn); - return items.size() > 0; -} - -// static -bool LLAppearanceMgr::getCanAddToCOF(const LLUUID& outfit_cat_id) -{ - if (gAgentWearables.isCOFChangeInProgress()) - { - return false; - } - - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - LLFindWearablesEx not_worn(/*is_worn=*/ false, /*include_body_parts=*/ false); - gInventory.collectDescendentsIf(outfit_cat_id, - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - not_worn); - - return items.size() > 0; -} - -bool LLAppearanceMgr::getCanReplaceCOF(const LLUUID& outfit_cat_id) -{ - // Don't allow wearing anything while we're changing appearance. - if (gAgentWearables.isCOFChangeInProgress()) - { - return false; - } - - // Check whether it's the base outfit. - if (outfit_cat_id.isNull()) - { - return false; - } - - // Check whether the outfit contains any wearables - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - LLFindWearables is_wearable; - gInventory.collectDescendentsIf(outfit_cat_id, - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - is_wearable); - - return items.size() > 0; -} - -// Moved from LLWearableList::ContextMenu for wider utility. -bool LLAppearanceMgr::canAddWearables(const uuid_vec_t& item_ids) const -{ - // TODO: investigate wearables may not be loaded at this point EXT-8231 - - U32 n_objects = 0; - U32 n_clothes = 0; - - // Count given clothes (by wearable type) and objects. - for (uuid_vec_t::const_iterator it = item_ids.begin(); it != item_ids.end(); ++it) - { - const LLViewerInventoryItem* item = gInventory.getItem(*it); - if (!item) - { - return false; - } - - if (item->getType() == LLAssetType::AT_OBJECT) - { - ++n_objects; - } - else if (item->getType() == LLAssetType::AT_CLOTHING) - { - ++n_clothes; - } - else if (item->getType() == LLAssetType::AT_BODYPART || item->getType() == LLAssetType::AT_GESTURE) - { - return isAgentAvatarValid(); - } - else - { - LL_WARNS() << "Unexpected wearable type" << LL_ENDL; - return false; - } - } - - // Check whether we can add all the objects. - if (!isAgentAvatarValid() || !gAgentAvatarp->canAttachMoreObjects(n_objects)) - { - return false; - } - - // Check whether we can add all the clothes. - U32 sum_clothes = n_clothes + gAgentWearables.getClothingLayerCount(); - return sum_clothes <= LLAgentWearables::MAX_CLOTHING_LAYERS; -} - -void LLAppearanceMgr::purgeBaseOutfitLink(const LLUUID& category, LLPointer cb) -{ - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - gInventory.collectDescendents(category, cats, items, - LLInventoryModel::EXCLUDE_TRASH); - for (S32 i = 0; i < items.size(); ++i) - { - LLViewerInventoryItem *item = items.at(i); - if (item->getActualType() != LLAssetType::AT_LINK_FOLDER) - continue; - LLViewerInventoryCategory* catp = item->getLinkedCategory(); - if(catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT) - { - remove_inventory_item(item->getUUID(), cb); - } - } -} - -// Keep the last N wearables of each type. For viewer 2.0, N is 1 for -// both body parts and clothing items. -void LLAppearanceMgr::filterWearableItems( - LLInventoryModel::item_array_t& items, S32 max_per_type, S32 max_total) -{ - // Restrict by max total items first. - if ((max_total > 0) && (items.size() > max_total)) - { - LLInventoryModel::item_array_t items_to_keep; - for (S32 i=0; i 0) - { - // Divvy items into arrays by wearable type. - std::vector items_by_type(LLWearableType::WT_COUNT); - divvyWearablesByType(items, items_by_type); - - // rebuild items list, retaining the last max_per_type of each array - items.clear(); - for (S32 i=0; igetName() : "[UNKNOWN]") << "'" << LL_ENDL; - - const LLUUID cof = getCOF(); - - // Deactivate currently active gestures in the COF, if replacing outfit - if (!append) - { - LLInventoryModel::item_array_t gest_items; - getDescendentsOfAssetType(cof, gest_items, LLAssetType::AT_GESTURE); - for(S32 i = 0; i < gest_items.size(); ++i) - { - LLViewerInventoryItem *gest_item = gest_items.at(i); - if ( LLGestureMgr::instance().isGestureActive( gest_item->getLinkedUUID()) ) - { - LLGestureMgr::instance().deactivateGesture( gest_item->getLinkedUUID() ); - } - } - } - - // Collect and filter descendents to determine new COF contents. - - // - Body parts: always include COF contents as a fallback in case any - // required parts are missing. - // Preserve body parts from COF if appending. - LLInventoryModel::item_array_t body_items; - getDescendentsOfAssetType(cof, body_items, LLAssetType::AT_BODYPART); - getDescendentsOfAssetType(category, body_items, LLAssetType::AT_BODYPART); - if (append) - reverse(body_items.begin(), body_items.end()); - // Reduce body items to max of one per type. - removeDuplicateItems(body_items); - filterWearableItems(body_items, 1, 0); - - // - Wearables: include COF contents only if appending. - LLInventoryModel::item_array_t wear_items; - if (append) - getDescendentsOfAssetType(cof, wear_items, LLAssetType::AT_CLOTHING); - getDescendentsOfAssetType(category, wear_items, LLAssetType::AT_CLOTHING); - // Reduce wearables to max of one per type. - removeDuplicateItems(wear_items); - filterWearableItems(wear_items, 0, LLAgentWearables::MAX_CLOTHING_LAYERS); - - // - Attachments: include COF contents only if appending. - LLInventoryModel::item_array_t obj_items; - if (append) - getDescendentsOfAssetType(cof, obj_items, LLAssetType::AT_OBJECT); - getDescendentsOfAssetType(category, obj_items, LLAssetType::AT_OBJECT); - removeDuplicateItems(obj_items); - - // - Gestures: include COF contents only if appending. - LLInventoryModel::item_array_t gest_items; - if (append) - getDescendentsOfAssetType(cof, gest_items, LLAssetType::AT_GESTURE); - getDescendentsOfAssetType(category, gest_items, LLAssetType::AT_GESTURE); - removeDuplicateItems(gest_items); - - // Create links to new COF contents. - LLInventoryModel::item_array_t all_items; - std::copy(body_items.begin(), body_items.end(), std::back_inserter(all_items)); - std::copy(wear_items.begin(), wear_items.end(), std::back_inserter(all_items)); - std::copy(obj_items.begin(), obj_items.end(), std::back_inserter(all_items)); - std::copy(gest_items.begin(), gest_items.end(), std::back_inserter(all_items)); - - // Find any wearables that need description set to enforce ordering. - desc_map_t desc_map; - getWearableOrderingDescUpdates(wear_items, desc_map); - - // Will link all the above items. - // link_waiter enforce flags are false because we've already fixed everything up in updateCOF(). - LLPointer link_waiter = new LLUpdateAppearanceOnDestroy(false,false); - LLSD contents = LLSD::emptyArray(); - - for (LLInventoryModel::item_array_t::const_iterator it = all_items.begin(); - it != all_items.end(); ++it) - { - LLSD item_contents; - LLInventoryItem *item = *it; - - std::string desc; - desc_map_t::const_iterator desc_iter = desc_map.find(item->getUUID()); - if (desc_iter != desc_map.end()) - { - desc = desc_iter->second; - LL_DEBUGS("Avatar") << item->getName() << " overriding desc to: " << desc - << " (was: " << item->getActualDescription() << ")" << LL_ENDL; - } - else - { - desc = item->getActualDescription(); - } - - item_contents["name"] = item->getName(); - item_contents["desc"] = desc; - item_contents["linked_id"] = item->getLinkedUUID(); - item_contents["type"] = LLAssetType::AT_LINK; - contents.append(item_contents); - } - const LLUUID& base_id = append ? getBaseOutfitUUID() : category; - LLViewerInventoryCategory *base_cat = gInventory.getCategory(base_id); - if (base_cat && (base_cat->getPreferredType() == LLFolderType::FT_OUTFIT)) - { - LLSD base_contents; - base_contents["name"] = base_cat->getName(); - base_contents["desc"] = ""; - base_contents["linked_id"] = base_cat->getLinkedUUID(); - base_contents["type"] = LLAssetType::AT_LINK_FOLDER; - contents.append(base_contents); - } - if (gSavedSettings.getBOOL("DebugAvatarAppearanceMessage")) - { - dump_sequential_xml(gAgentAvatarp->getFullname() + "_slam_request", contents); - } - slam_inventory_folder(getCOF(), contents, link_waiter); - - LL_DEBUGS("Avatar") << self_av_string() << "waiting for LLUpdateAppearanceOnDestroy" << LL_ENDL; -} - -void LLAppearanceMgr::updatePanelOutfitName(const std::string& name) -{ - LLSidepanelAppearance* panel_appearance = - dynamic_cast(LLFloaterSidePanelContainer::getPanel("appearance")); - if (panel_appearance) - { - panel_appearance->refreshCurrentOutfitName(name); - } -} - -void LLAppearanceMgr::createBaseOutfitLink(const LLUUID& category, LLPointer link_waiter) -{ - const LLUUID cof = getCOF(); - LLViewerInventoryCategory* catp = gInventory.getCategory(category); - std::string new_outfit_name = ""; - - purgeBaseOutfitLink(cof, link_waiter); - - if (catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT) - { - link_inventory_object(cof, catp, link_waiter); - new_outfit_name = catp->getName(); - } - - updatePanelOutfitName(new_outfit_name); -} - -void LLAppearanceMgr::updateAgentWearables(LLWearableHoldingPattern* holder) -{ - LL_DEBUGS("Avatar") << "updateAgentWearables()" << LL_ENDL; - LLInventoryItem::item_array_t items; - std::vector< LLViewerWearable* > wearables; - wearables.reserve(32); - - // For each wearable type, find the wearables of that type. - for( S32 i = 0; i < LLWearableType::WT_COUNT; i++ ) - { - for (LLWearableHoldingPattern::found_list_t::iterator iter = holder->getFoundList().begin(); - iter != holder->getFoundList().end(); ++iter) - { - LLFoundData& data = *iter; - LLViewerWearable* wearable = data.mWearable; - if( wearable && ((S32)wearable->getType() == i) ) - { - LLViewerInventoryItem* item = (LLViewerInventoryItem*)gInventory.getItem(data.mItemID); - if( item && (item->getAssetUUID() == wearable->getAssetID()) ) - { - items.push_back(item); - wearables.push_back(wearable); - } - } - } - } - - if(wearables.size() > 0) - { - gAgentWearables.setWearableOutfit(items, wearables); - } -} - -S32 LLAppearanceMgr::countActiveHoldingPatterns() -{ - return LLWearableHoldingPattern::countActive(); -} - -static void remove_non_link_items(LLInventoryModel::item_array_t &items) -{ - LLInventoryModel::item_array_t pruned_items; - for (LLInventoryModel::item_array_t::const_iterator iter = items.begin(); - iter != items.end(); - ++iter) - { - const LLViewerInventoryItem *item = (*iter); - if (item && item->getIsLinkType()) - { - pruned_items.push_back((*iter)); - } - } - items = pruned_items; -} - -//a predicate for sorting inventory items by actual descriptions -bool sort_by_actual_description(const LLInventoryItem* item1, const LLInventoryItem* item2) -{ - if (!item1 || !item2) - { - LL_WARNS() << "either item1 or item2 is NULL" << LL_ENDL; - return true; - } - - return item1->getActualDescription() < item2->getActualDescription(); -} - -void item_array_diff(LLInventoryModel::item_array_t& full_list, - LLInventoryModel::item_array_t& keep_list, - LLInventoryModel::item_array_t& kill_list) - -{ - for (LLInventoryModel::item_array_t::iterator it = full_list.begin(); - it != full_list.end(); - ++it) - { - LLViewerInventoryItem *item = *it; - if (std::find(keep_list.begin(), keep_list.end(), item) == keep_list.end()) - { - kill_list.push_back(item); - } - } -} - -S32 LLAppearanceMgr::findExcessOrDuplicateItems(const LLUUID& cat_id, - LLAssetType::EType type, - S32 max_items_per_type, - S32 max_items_total, - LLInventoryObject::object_list_t& items_to_kill) -{ - S32 to_kill_count = 0; - - LLInventoryModel::item_array_t items; - getDescendentsOfAssetType(cat_id, items, type); - LLInventoryModel::item_array_t curr_items = items; - removeDuplicateItems(items); - if (max_items_per_type > 0 || max_items_total > 0) - { - filterWearableItems(items, max_items_per_type, max_items_total); - } - LLInventoryModel::item_array_t kill_items; - item_array_diff(curr_items,items,kill_items); - for (LLInventoryModel::item_array_t::iterator it = kill_items.begin(); - it != kill_items.end(); - ++it) - { - items_to_kill.push_back(LLPointer(*it)); - to_kill_count++; - } - return to_kill_count; -} - - -void LLAppearanceMgr::findAllExcessOrDuplicateItems(const LLUUID& cat_id, - LLInventoryObject::object_list_t& items_to_kill) -{ - findExcessOrDuplicateItems(cat_id,LLAssetType::AT_BODYPART, - 1, 0, items_to_kill); - findExcessOrDuplicateItems(cat_id,LLAssetType::AT_CLOTHING, - 0, LLAgentWearables::MAX_CLOTHING_LAYERS, items_to_kill); - findExcessOrDuplicateItems(cat_id,LLAssetType::AT_OBJECT, - 0, 0, items_to_kill); -} - -void LLAppearanceMgr::enforceCOFItemRestrictions(LLPointer cb) -{ - LLInventoryObject::object_list_t items_to_kill; - findAllExcessOrDuplicateItems(getCOF(), items_to_kill); - if (items_to_kill.size()>0) - { - // Remove duplicate or excess wearables. Should normally be enforced at the UI level, but - // this should catch anything that gets through. - remove_inventory_items(items_to_kill, cb); - } -} - -bool sort_by_linked_uuid(const LLViewerInventoryItem* item1, const LLViewerInventoryItem* item2) -{ - if (!item1 || !item2) - { - LL_WARNS() << "item1, item2 cannot be null, something is very wrong" << LL_ENDL; - return true; - } - - return item1->getLinkedUUID() < item2->getLinkedUUID(); -} - -void get_sorted_base_and_cof_items(LLInventoryModel::item_array_t& cof_item_array, LLInventoryModel::item_array_t& outfit_item_array) -{ - LLUUID base_outfit_id = LLAppearanceMgr::instance().getBaseOutfitUUID(); - - if (base_outfit_id.notNull()) - { - LLIsValidItemLink collector; - LLInventoryModel::cat_array_t sub_cat_array; - - gInventory.collectDescendents(base_outfit_id, - sub_cat_array, - outfit_item_array, - LLInventoryModel::EXCLUDE_TRASH); - - LLInventoryModel::cat_array_t cof_cats; - - gInventory.collectDescendentsIf(LLAppearanceMgr::instance().getCOF(), cof_cats, cof_item_array, - LLInventoryModel::EXCLUDE_TRASH, collector); - - for (U32 i = 0; i < outfit_item_array.size(); ++i) - { - LLViewerInventoryItem* linked_item = outfit_item_array.at(i)->getLinkedItem(); - if (linked_item != NULL && linked_item->getActualType() == LLAssetType::AT_TEXTURE) - { - outfit_item_array.erase(outfit_item_array.begin() + i); - break; - } - } - - std::sort(cof_item_array.begin(), cof_item_array.end(), sort_by_linked_uuid); - std::sort(outfit_item_array.begin(), outfit_item_array.end(), sort_by_linked_uuid); - } -} - - -void LLAppearanceMgr::updateAppearanceFromCOF(bool enforce_item_restrictions, - bool enforce_ordering, - nullary_func_t post_update_func) -{ - if (mIsInUpdateAppearanceFromCOF) - { - LL_WARNS() << "Called updateAppearanceFromCOF inside updateAppearanceFromCOF, skipping" << LL_ENDL; - return; - } - - LL_DEBUGS("Avatar") << self_av_string() << "starting" << LL_ENDL; - - if (gInventory.hasPosiblyBrockenLinks()) - { - // Inventory has either broken links or links that - // haven't loaded yet. - // Check if LLAppearanceMgr needs to wait. - LLUUID current_outfit_id = getCOF(); - LLInventoryModel::item_array_t cof_items; - LLInventoryModel::cat_array_t cof_cats; - LLFindBrokenLinks is_brocken_link; - gInventory.collectDescendentsIf(current_outfit_id, - cof_cats, - cof_items, - LLInventoryModel::EXCLUDE_TRASH, - is_brocken_link); - - if (cof_items.size() > 0) - { - // Some links haven't loaded yet, but fetch isn't complete so - // links are likely fine and we will have to wait for them to - // load - if (LLInventoryModelBackgroundFetch::getInstance()->folderFetchActive()) - { - - LLBrokenLinkObserver* observer = new LLBrokenLinkObserver(cof_items.front()->getUUID(), - enforce_item_restrictions, - enforce_ordering, - post_update_func); - gInventory.addObserver(observer); - return; - } - } - } - - if (enforce_item_restrictions) - { - // The point here is just to call - // updateAppearanceFromCOF() again after excess items - // have been removed. That time we will set - // enforce_item_restrictions to false so we don't get - // caught in a perpetual loop. - LLPointer cb( - new LLUpdateAppearanceOnDestroy(false, enforce_ordering, post_update_func)); - enforceCOFItemRestrictions(cb); - return; - } - - if (enforce_ordering) - { - //checking integrity of the COF in terms of ordering of wearables, - //checking and updating links' descriptions of wearables in the COF (before analyzed for "dirty" state) - - // As with enforce_item_restrictions handling above, we want - // to wait for the update callbacks, then (finally!) call - // updateAppearanceFromCOF() with no additional COF munging needed. - LLPointer cb( - new LLUpdateAppearanceOnDestroy(false, false, post_update_func)); - updateClothingOrderingInfo(LLUUID::null, cb); - return; - } - - if (!validateClothingOrderingInfo()) - { - - LLInventoryModel::item_array_t outfit_item_array; - LLInventoryModel::item_array_t cof_item_array; - get_sorted_base_and_cof_items(cof_item_array, outfit_item_array); - - if (outfit_item_array.size() == cof_item_array.size()) - { - for (U32 i = 0; i < cof_item_array.size(); ++i) - { - LLViewerInventoryItem *cof_it = cof_item_array.at(i); - LLViewerInventoryItem *base_it = outfit_item_array.at(i); - - if (cof_it->getActualDescription() != base_it->getActualDescription()) - { - if (cof_it->getLinkedUUID() == base_it->getLinkedUUID()) - { - cof_it->setDescription(base_it->getActualDescription()); - gInventory.updateItem(cof_it); - } - } - } - LLAppearanceMgr::getInstance()->updateIsDirty(); - } - - } - - BoolSetter setIsInUpdateAppearanceFromCOF(mIsInUpdateAppearanceFromCOF); - selfStartPhase("update_appearance_from_cof"); - - // update dirty flag to see if the state of the COF matches - // the saved outfit stored as a folder link - updateIsDirty(); - - // Send server request for appearance update - if (gAgent.getRegion() && gAgent.getRegion()->getCentralBakeVersion()) - { - requestServerAppearanceUpdate(); - } - - LLUUID current_outfit_id = getCOF(); - - // Find all the wearables that are in the COF's subtree. - LL_DEBUGS() << "LLAppearanceMgr::updateFromCOF()" << LL_ENDL; - LLInventoryModel::item_array_t wear_items; - LLInventoryModel::item_array_t obj_items; - LLInventoryModel::item_array_t gest_items; - getUserDescendents(current_outfit_id, wear_items, obj_items, gest_items); - // Get rid of non-links in case somehow the COF was corrupted. - remove_non_link_items(wear_items); - remove_non_link_items(obj_items); - remove_non_link_items(gest_items); - - dumpItemArray(wear_items,"asset_dump: wear_item"); - dumpItemArray(obj_items,"asset_dump: obj_item"); - - LLViewerInventoryCategory *cof = gInventory.getCategory(current_outfit_id); - if (!gInventory.isCategoryComplete(current_outfit_id)) - { - LL_WARNS() << "COF info is not complete. Version " << cof->getVersion() - << " descendent_count " << cof->getDescendentCount() - << " viewer desc count " << cof->getViewerDescendentCount() << LL_ENDL; - } - if(!wear_items.size()) - { - LLNotificationsUtil::add("CouldNotPutOnOutfit"); - return; - } - - //preparing the list of wearables in the correct order for LLAgentWearables - sortItemsByActualDescription(wear_items); - - - LL_DEBUGS("Avatar") << "HP block starts" << LL_ENDL; - LLTimer hp_block_timer; - LLWearableHoldingPattern* holder = new LLWearableHoldingPattern; - - holder->setObjItems(obj_items); - holder->setGestItems(gest_items); - - // Note: can't do normal iteration, because if all the - // wearables can be resolved immediately, then the - // callback will be called (and this object deleted) - // before the final getNextData(). - - for(S32 i = 0; i < wear_items.size(); ++i) - { - LLViewerInventoryItem *item = wear_items.at(i); - LLViewerInventoryItem *linked_item = item ? item->getLinkedItem() : NULL; - - // Fault injection: use debug setting to test asset - // fetch failures (should be replaced by new defaults in - // lost&found). - U32 skip_type = gSavedSettings.getU32("ForceAssetFail"); - - if (item && item->getIsLinkType() && linked_item) - { - LLFoundData found(linked_item->getUUID(), - linked_item->getAssetUUID(), - linked_item->getName(), - linked_item->getType(), - linked_item->isWearableType() ? linked_item->getWearableType() : LLWearableType::WT_INVALID - ); - - if (skip_type != LLWearableType::WT_INVALID && skip_type == found.mWearableType) - { - found.mAssetID.generate(); // Replace with new UUID, guaranteed not to exist in DB - } - //pushing back, not front, to preserve order of wearables for LLAgentWearables - holder->getFoundList().push_back(found); - } - else - { - if (!item) - { - LL_WARNS() << "Attempt to wear a null item " << LL_ENDL; - } - else if (!linked_item) - { - LL_WARNS() << "Attempt to wear a broken link [ name:" << item->getName() << " ] " << LL_ENDL; - } - } - } - - selfStartPhase("get_wearables_2"); - - for (LLWearableHoldingPattern::found_list_t::iterator it = holder->getFoundList().begin(); - it != holder->getFoundList().end(); ++it) - { - LLFoundData& found = *it; - - LL_DEBUGS() << self_av_string() << "waiting for onWearableAssetFetch callback, asset " << found.mAssetID.asString() << LL_ENDL; - - // Fetch the wearables about to be worn. - LLWearableList::instance().getAsset(found.mAssetID, - found.mName, - gAgentAvatarp, - found.mAssetType, - onWearableAssetFetch, - (void*)holder); - - } - - holder->resetTime(gSavedSettings.getF32("MaxWearableWaitTime")); - if (!holder->pollFetchCompletion()) - { - doOnIdleRepeating(boost::bind(&LLWearableHoldingPattern::pollFetchCompletion,holder)); - } - post_update_func(); - - LL_DEBUGS("Avatar") << "HP block ends, elapsed " << hp_block_timer.getElapsedTimeF32() << LL_ENDL; -} - -void LLAppearanceMgr::getDescendentsOfAssetType(const LLUUID& category, - LLInventoryModel::item_array_t& items, - LLAssetType::EType type) -{ - LLInventoryModel::cat_array_t cats; - LLIsType is_of_type(type); - gInventory.collectDescendentsIf(category, - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - is_of_type); -} - -void LLAppearanceMgr::getUserDescendents(const LLUUID& category, - LLInventoryModel::item_array_t& wear_items, - LLInventoryModel::item_array_t& obj_items, - LLInventoryModel::item_array_t& gest_items) -{ - LLInventoryModel::cat_array_t wear_cats; - LLFindWearables is_wearable; - gInventory.collectDescendentsIf(category, - wear_cats, - wear_items, - LLInventoryModel::EXCLUDE_TRASH, - is_wearable); - - LLInventoryModel::cat_array_t obj_cats; - LLIsType is_object( LLAssetType::AT_OBJECT ); - gInventory.collectDescendentsIf(category, - obj_cats, - obj_items, - LLInventoryModel::EXCLUDE_TRASH, - is_object); - - // Find all gestures in this folder - LLInventoryModel::cat_array_t gest_cats; - LLIsType is_gesture( LLAssetType::AT_GESTURE ); - gInventory.collectDescendentsIf(category, - gest_cats, - gest_items, - LLInventoryModel::EXCLUDE_TRASH, - is_gesture); -} - -void LLAppearanceMgr::wearInventoryCategory(LLInventoryCategory* category, bool copy, bool append) -{ - if(!category) return; - - selfClearPhases(); - selfStartPhase("wear_inventory_category"); - - gAgentWearables.notifyLoadingStarted(); - - LL_INFOS("Avatar") << self_av_string() << "wearInventoryCategory( " << category->getName() - << " )" << LL_ENDL; - - // If we are copying from library, attempt to use AIS to copy the category. - if (copy && AISAPI::isAvailable()) - { - LLUUID parent_id; - parent_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CLOTHING); - if (parent_id.isNull()) - { - parent_id = gInventory.getRootFolderID(); - } - - LLPointer copy_cb = new LLWearCategoryAfterCopy(append); - LLPointer track_cb = new LLTrackPhaseWrapper( - std::string("wear_inventory_category_callback"), copy_cb); - - AISAPI::completion_t cr = boost::bind(&doAppearanceCb, track_cb, _1); - AISAPI::CopyLibraryCategory(category->getUUID(), parent_id, false, cr); - } - else - { - selfStartPhase("wear_inventory_category_fetch"); - if (AISAPI::isAvailable() && category->getPreferredType() == LLFolderType::FT_OUTFIT) - { - // for reliability just fetch it whole, linked items included - LLUUID cat_id = category->getUUID(); - LLInventoryModelBackgroundFetch::getInstance()->fetchFolderAndLinks( - cat_id, - [cat_id, copy, append] - { - LLAppearanceMgr::instance().wearCategoryFinal(cat_id, copy, append); - }); - } - else - { - callAfterCategoryFetch(category->getUUID(), boost::bind(&LLAppearanceMgr::wearCategoryFinal, - &LLAppearanceMgr::instance(), - category->getUUID(), copy, append)); - } - } -} - -S32 LLAppearanceMgr::getActiveCopyOperations() const -{ - return LLCallAfterInventoryCopyMgr::getInstanceCount(); -} - -void LLAppearanceMgr::wearCategoryFinal(const LLUUID& cat_id, bool copy_items, bool append) -{ - LL_INFOS("Avatar") << self_av_string() << "starting" << LL_ENDL; - - selfStopPhase("wear_inventory_category_fetch"); - - // We now have an outfit ready to be copied to agent inventory. Do - // it, and wear that outfit normally. - LLInventoryCategory* cat = gInventory.getCategory(cat_id); - if(copy_items) - { - LLInventoryModel::cat_array_t* cats; - LLInventoryModel::item_array_t* items; - gInventory.getDirectDescendentsOf(cat_id, cats, items); - std::string name; - if(!cat) - { - // should never happen. - name = "New Outfit"; - } - else - { - name = cat->getName(); - } - LLViewerInventoryItem* item = NULL; - LLInventoryModel::item_array_t::const_iterator it = items->begin(); - LLInventoryModel::item_array_t::const_iterator end = items->end(); - LLUUID pid; - for(; it < end; ++it) - { - item = *it; - if(item) - { - if(LLInventoryType::IT_GESTURE == item->getInventoryType()) - { - pid = gInventory.findCategoryUUIDForType(LLFolderType::FT_GESTURE); - } - else - { - pid = gInventory.findCategoryUUIDForType(LLFolderType::FT_CLOTHING); - } - break; - } - } - if(pid.isNull()) - { - pid = gInventory.getRootFolderID(); - } - - gInventory.createNewCategory( - pid, - LLFolderType::FT_NONE, - name, - [cat_id, append](const LLUUID& new_cat_id) - { - LLInventoryModel::cat_array_t* cats; - LLInventoryModel::item_array_t* items; - gInventory.getDirectDescendentsOf(cat_id, cats, items); - // Create a CopyMgr that will copy items, manage its own destruction - new LLCallAfterInventoryCopyMgr( - *items, new_cat_id, std::string("wear_inventory_category_callback"), - boost::bind(&LLAppearanceMgr::wearInventoryCategoryOnAvatar, - LLAppearanceMgr::getInstance(), - gInventory.getCategory(new_cat_id), - append)); - - // BAP fixes a lag in display of created dir. - gInventory.notifyObservers(); - }, - cat->getThumbnailUUID() - ); - } - else - { - // Wear the inventory category. - LLAppearanceMgr::instance().wearInventoryCategoryOnAvatar(cat, append); - } -} - -// *NOTE: hack to get from avatar inventory to avatar -void LLAppearanceMgr::wearInventoryCategoryOnAvatar( LLInventoryCategory* category, bool append ) -{ - // Avoid unintentionally overwriting old wearables. We have to do - // this up front to avoid having to deal with the case of multiple - // wearables being dirty. - if (!category) return; - - if ( !LLInventoryCallbackManager::is_instantiated() ) - { - // shutting down, ignore. - return; - } - - LL_INFOS("Avatar") << self_av_string() << "wearInventoryCategoryOnAvatar '" << category->getName() - << "'" << LL_ENDL; - LLUIUsage::instance().logCommand("Avatar.WearCategory"); - - if (gAgentCamera.cameraCustomizeAvatar()) - { - // switching to outfit editor should automagically save any currently edited wearable - LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "edit_outfit")); - } - - LLAppearanceMgr::changeOutfit(true, category->getUUID(), append); -} - -// FIXME do we really want to search entire inventory for matching name? -void LLAppearanceMgr::wearOutfitByName(const std::string& name) -{ - LL_INFOS("Avatar") << self_av_string() << "Wearing category " << name << LL_ENDL; - - LLInventoryModel::cat_array_t cat_array; - LLInventoryModel::item_array_t item_array; - LLNameCategoryCollector has_name(name); - gInventory.collectDescendentsIf(gInventory.getRootFolderID(), - cat_array, - item_array, - LLInventoryModel::EXCLUDE_TRASH, - has_name); - bool copy_items = false; - LLInventoryCategory* cat = NULL; - if (cat_array.size() > 0) - { - // Just wear the first one that matches - cat = cat_array.at(0); - } - else - { - gInventory.collectDescendentsIf(LLUUID::null, - cat_array, - item_array, - LLInventoryModel::EXCLUDE_TRASH, - has_name); - if(cat_array.size() > 0) - { - cat = cat_array.at(0); - copy_items = true; - } - } - - if(cat) - { - LLAppearanceMgr::wearInventoryCategory(cat, copy_items, false); - } - else - { - LL_WARNS() << "Couldn't find outfit " <isWearableType() && b->isWearableType() && - (a->getWearableType() == b->getWearableType())); -} - -class LLDeferredCOFLinkObserver: public LLInventoryObserver -{ -public: - LLDeferredCOFLinkObserver(const LLUUID& item_id, LLPointer cb, const std::string& description): - mItemID(item_id), - mCallback(cb), - mDescription(description) - { - } - - ~LLDeferredCOFLinkObserver() - { - } - - /* virtual */ void changed(U32 mask) - { - const LLInventoryItem *item = gInventory.getItem(mItemID); - if (item) - { - gInventory.removeObserver(this); - LLAppearanceMgr::instance().addCOFItemLink(item, mCallback, mDescription); - delete this; - } - } - -private: - const LLUUID mItemID; - std::string mDescription; - LLPointer mCallback; -}; - - -// BAP - note that this runs asynchronously if the item is not already loaded from inventory. -// Dangerous if caller assumes link will exist after calling the function. -void LLAppearanceMgr::addCOFItemLink(const LLUUID &item_id, - LLPointer cb, - const std::string description) -{ - const LLInventoryItem *item = gInventory.getItem(item_id); - if (!item) - { - LLDeferredCOFLinkObserver *observer = new LLDeferredCOFLinkObserver(item_id, cb, description); - gInventory.addObserver(observer); - } - else - { - addCOFItemLink(item, cb, description); - } -} - -void LLAppearanceMgr::addCOFItemLink(const LLInventoryItem *item, - LLPointer cb, - const std::string description) -{ - const LLViewerInventoryItem *vitem = dynamic_cast(item); - if (!vitem) - { - LL_WARNS() << "not an llviewerinventoryitem, failed" << LL_ENDL; - return; - } - - gInventory.addChangedMask(LLInventoryObserver::LABEL, vitem->getLinkedUUID()); - - LLInventoryModel::cat_array_t cat_array; - LLInventoryModel::item_array_t item_array; - gInventory.collectDescendents(LLAppearanceMgr::getCOF(), - cat_array, - item_array, - LLInventoryModel::EXCLUDE_TRASH); - bool linked_already = false; - for (S32 i=0; igetWearableType(); - - const bool is_body_part = (wearable_type == LLWearableType::WT_SHAPE) - || (wearable_type == LLWearableType::WT_HAIR) - || (wearable_type == LLWearableType::WT_EYES) - || (wearable_type == LLWearableType::WT_SKIN); - - if (inv_item->getLinkedUUID() == vitem->getLinkedUUID()) - { - linked_already = true; - } - // Are these links to different items of the same body part - // type? If so, new item will replace old. - else if ((vitem->isWearableType()) && (vitem->getWearableType() == wearable_type)) - { - if (is_body_part && inv_item->getIsLinkType()) - { - remove_inventory_item(inv_item->getUUID(), cb); - } - else if (!gAgentWearables.canAddWearable(wearable_type)) - { - // MULTI-WEARABLES: make sure we don't go over clothing limits - remove_inventory_item(inv_item->getUUID(), cb); - } - } - } - - if (!linked_already) - { - LLViewerInventoryItem *copy_item = new LLViewerInventoryItem; - copy_item->copyViewerItem(vitem); - copy_item->setDescription(description); - link_inventory_object(getCOF(), copy_item, cb); - } -} - -LLInventoryModel::item_array_t LLAppearanceMgr::findCOFItemLinks(const LLUUID& item_id) -{ - LLInventoryModel::item_array_t result; - - LLUUID linked_id = gInventory.getLinkedItemID(item_id); - LLInventoryModel::cat_array_t cat_array; - LLInventoryModel::item_array_t item_array; - gInventory.collectDescendents(LLAppearanceMgr::getCOF(), - cat_array, - item_array, - LLInventoryModel::EXCLUDE_TRASH); - for (S32 i=0; igetLinkedUUID() == linked_id) - { - result.push_back(item_array.at(i)); - } - } - return result; -} - -bool LLAppearanceMgr::isLinkedInCOF(const LLUUID& item_id) -{ - LLInventoryModel::item_array_t links = LLAppearanceMgr::instance().findCOFItemLinks(item_id); - return links.size() > 0; -} - -void LLAppearanceMgr::removeAllClothesFromAvatar() -{ - // Fetch worn clothes (i.e. the ones in COF). - LLInventoryModel::item_array_t clothing_items; - LLInventoryModel::cat_array_t dummy; - LLIsType is_clothing(LLAssetType::AT_CLOTHING); - gInventory.collectDescendentsIf(getCOF(), - dummy, - clothing_items, - LLInventoryModel::EXCLUDE_TRASH, - is_clothing); - uuid_vec_t item_ids; - for (LLInventoryModel::item_array_t::iterator it = clothing_items.begin(); - it != clothing_items.end(); ++it) - { - item_ids.push_back((*it).get()->getLinkedUUID()); - } - - // Take them off by removing from COF. - removeItemsFromAvatar(item_ids); -} - -void LLAppearanceMgr::removeAllAttachmentsFromAvatar() -{ - if (!isAgentAvatarValid()) return; - - LLAgentWearables::llvo_vec_t objects_to_remove; - - for (LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); - iter != gAgentAvatarp->mAttachmentPoints.end();) - { - LLVOAvatar::attachment_map_t::iterator curiter = iter++; - LLViewerJointAttachment* attachment = curiter->second; - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - LLViewerObject *attached_object = attachment_iter->get(); - if (attached_object) - { - objects_to_remove.push_back(attached_object); - } - } - } - uuid_vec_t ids_to_remove; - for (LLAgentWearables::llvo_vec_t::iterator it = objects_to_remove.begin(); - it != objects_to_remove.end(); - ++it) - { - ids_to_remove.push_back((*it)->getAttachmentItemID()); - } - removeItemsFromAvatar(ids_to_remove); -} - -class LLUpdateOnCOFLinkRemove : public LLInventoryCallback -{ -public: - LLUpdateOnCOFLinkRemove(const LLUUID& remove_item_id, LLPointer cb = NULL): - mItemID(remove_item_id), - mCB(cb) - { - } - - /* virtual */ void fire(const LLUUID& item_id) - { - // just removed cof link, "(wear)" suffix depends on presence of link, so update label - gInventory.addChangedMask(LLInventoryObserver::LABEL, mItemID); - if (mCB.notNull()) - { - mCB->fire(item_id); - } - } - -private: - LLUUID mItemID; - LLPointer mCB; -}; - -void LLAppearanceMgr::removeCOFItemLinks(const LLUUID& item_id, LLPointer cb) -{ LLInventoryModel::cat_array_t cat_array; - LLInventoryModel::item_array_t item_array; - gInventory.collectDescendents(LLAppearanceMgr::getCOF(), - cat_array, - item_array, - LLInventoryModel::EXCLUDE_TRASH); - for (S32 i=0; igetIsLinkType() && item->getLinkedUUID() == item_id) - { - if (item->getType() == LLAssetType::AT_OBJECT) - { - // Immediate delete - remove_inventory_item(item->getUUID(), cb, true); - gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id); - } - else - { - // Delayed delete - // Pointless to update item_id label here since link still exists and first notifyObservers - // call will restore (wear) suffix, mark for update after deletion - LLPointer cb_label = new LLUpdateOnCOFLinkRemove(item_id, cb); - remove_inventory_item(item->getUUID(), cb_label, false); - } - } - } -} - -void LLAppearanceMgr::removeCOFLinksOfType(LLWearableType::EType type, LLPointer cb) -{ - LLFindWearablesOfType filter_wearables_of_type(type); - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - LLInventoryModel::item_array_t::const_iterator it; - - gInventory.collectDescendentsIf(getCOF(), cats, items, true, filter_wearables_of_type); - for (it = items.begin(); it != items.end(); ++it) - { - const LLViewerInventoryItem* item = *it; - if (item->getIsLinkType()) // we must operate on links only - { - remove_inventory_item(item->getUUID(), cb); - } - } -} - -void LLAppearanceMgr::updateIsDirty() -{ - LLUUID cof = getCOF(); - LLUUID base_outfit; - - // find base outfit link - const LLViewerInventoryItem* base_outfit_item = getBaseOutfitLink(); - LLViewerInventoryCategory* catp = NULL; - if (base_outfit_item && base_outfit_item->getIsLinkType()) - { - catp = base_outfit_item->getLinkedCategory(); - } - if(catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT) - { - base_outfit = catp->getUUID(); - } - - // Set dirty to "false" if no base outfit found to disable "Save" - // and leave only "Save As" enabled in My Outfits. - mOutfitIsDirty = false; - - if (base_outfit.notNull()) - { - LLIsValidItemLink collector; - - LLInventoryModel::cat_array_t cof_cats; - LLInventoryModel::item_array_t cof_items; - gInventory.collectDescendentsIf(cof, cof_cats, cof_items, - LLInventoryModel::EXCLUDE_TRASH, collector); - - LLInventoryModel::cat_array_t outfit_cats; - LLInventoryModel::item_array_t outfit_items; - gInventory.collectDescendentsIf(base_outfit, outfit_cats, outfit_items, - LLInventoryModel::EXCLUDE_TRASH, collector); - - for (U32 i = 0; i < outfit_items.size(); ++i) - { - LLViewerInventoryItem* linked_item = outfit_items.at(i)->getLinkedItem(); - if (linked_item != NULL && linked_item->getActualType() == LLAssetType::AT_TEXTURE) - { - outfit_items.erase(outfit_items.begin() + i); - break; - } - } - - if(outfit_items.size() != cof_items.size()) - { - LL_DEBUGS("Avatar") << "item count different - base " << outfit_items.size() << " cof " << cof_items.size() << LL_ENDL; - // Current outfit folder should have one more item than the outfit folder. - // this one item is the link back to the outfit folder itself. - mOutfitIsDirty = true; - return; - } - - //"dirty" - also means a difference in linked UUIDs and/or a difference in wearables order (links' descriptions) - std::sort(cof_items.begin(), cof_items.end(), sort_by_linked_uuid); - std::sort(outfit_items.begin(), outfit_items.end(), sort_by_linked_uuid); - - for (U32 i = 0; i < cof_items.size(); ++i) - { - LLViewerInventoryItem *item1 = cof_items.at(i); - LLViewerInventoryItem *item2 = outfit_items.at(i); - - if (item1->getLinkedUUID() != item2->getLinkedUUID() || - item1->getName() != item2->getName() || - item1->getActualDescription() != item2->getActualDescription()) - { - if (item1->getLinkedUUID() != item2->getLinkedUUID()) - { - LL_DEBUGS("Avatar") << "link id different " << LL_ENDL; - } - else - { - if (item1->getName() != item2->getName()) - { - LL_DEBUGS("Avatar") << "name different " << item1->getName() << " " << item2->getName() << LL_ENDL; - } - if (item1->getActualDescription() != item2->getActualDescription()) - { - LL_DEBUGS("Avatar") << "desc different " << item1->getActualDescription() - << " " << item2->getActualDescription() - << " names " << item1->getName() << " " << item2->getName() << LL_ENDL; - } - } - mOutfitIsDirty = true; - return; - } - } - } - llassert(!mOutfitIsDirty); - LL_DEBUGS("Avatar") << "clean" << LL_ENDL; -} - -// *HACK: Must match name in Library or agent inventory -const std::string ROOT_GESTURES_FOLDER = "Gestures"; -const std::string COMMON_GESTURES_FOLDER = "Common Gestures"; -const std::string MALE_GESTURES_FOLDER = "Male Gestures"; -const std::string FEMALE_GESTURES_FOLDER = "Female Gestures"; -const std::string SPEECH_GESTURES_FOLDER = "Speech Gestures"; -const std::string OTHER_GESTURES_FOLDER = "Other Gestures"; - -void LLAppearanceMgr::copyLibraryGestures() -{ - LL_INFOS("Avatar") << self_av_string() << "Copying library gestures" << LL_ENDL; - - // Copy gestures - LLUUID lib_gesture_cat_id = - gInventory.findLibraryCategoryUUIDForType(LLFolderType::FT_GESTURE); - if (lib_gesture_cat_id.isNull()) - { - LL_WARNS() << "Unable to copy gestures, source category not found" << LL_ENDL; - } - LLUUID dst_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_GESTURE); - - std::vector gesture_folders_to_copy; - gesture_folders_to_copy.push_back(MALE_GESTURES_FOLDER); - gesture_folders_to_copy.push_back(FEMALE_GESTURES_FOLDER); - gesture_folders_to_copy.push_back(COMMON_GESTURES_FOLDER); - gesture_folders_to_copy.push_back(SPEECH_GESTURES_FOLDER); - gesture_folders_to_copy.push_back(OTHER_GESTURES_FOLDER); - - for(std::vector::iterator it = gesture_folders_to_copy.begin(); - it != gesture_folders_to_copy.end(); - ++it) - { - std::string& folder_name = *it; - - LLPointer cb(NULL); - - // After copying gestures, activate Common, Other, plus - // Male and/or Female, depending upon the initial outfit gender. - ESex gender = gAgentAvatarp->getSex(); - - std::string activate_male_gestures; - std::string activate_female_gestures; - switch (gender) { - case SEX_MALE: - activate_male_gestures = MALE_GESTURES_FOLDER; - break; - case SEX_FEMALE: - activate_female_gestures = FEMALE_GESTURES_FOLDER; - break; - case SEX_BOTH: - activate_male_gestures = MALE_GESTURES_FOLDER; - activate_female_gestures = FEMALE_GESTURES_FOLDER; - break; - } - - if (folder_name == activate_male_gestures || - folder_name == activate_female_gestures || - folder_name == COMMON_GESTURES_FOLDER || - folder_name == OTHER_GESTURES_FOLDER) - { - cb = new LLBoostFuncInventoryCallback(activate_gesture_cb); - } - - LLUUID cat_id = findDescendentCategoryIDByName(lib_gesture_cat_id,folder_name); - if (cat_id.isNull()) - { - LL_WARNS() << self_av_string() << "failed to find gesture folder for " << folder_name << LL_ENDL; - } - else - { - LL_DEBUGS("Avatar") << self_av_string() << "initiating fetch and copy for " << folder_name << " cat_id " << cat_id << LL_ENDL; - callAfterCategoryFetch(cat_id, - boost::bind(&LLAppearanceMgr::shallowCopyCategory, - &LLAppearanceMgr::instance(), - cat_id, dst_id, cb)); - } - } -} - -// Handler for anything that's deferred until avatar de-clouds. -void LLAppearanceMgr::onFirstFullyVisible() -{ - gAgentAvatarp->outputRezTiming("Avatar fully loaded"); - gAgentAvatarp->reportAvatarRezTime(); - gAgentAvatarp->debugAvatarVisible(); - - // If this is the first time we've ever logged in, - // then copy default gestures from the library. - if (gAgent.isFirstLogin()) { - copyLibraryGestures(); - } -} - -// update "dirty" state - defined outside class to allow for calling -// after appearance mgr instance has been destroyed. -void appearance_mgr_update_dirty_state() -{ - if (LLAppearanceMgr::instanceExists()) - { - LLAppearanceMgr& app_mgr = LLAppearanceMgr::instance(); - LLUUID image_id = app_mgr.getOutfitImage(); - if(image_id.notNull()) - { - LLPointer cb = NULL; - link_inventory_object(app_mgr.getBaseOutfitUUID(), image_id, cb); - } - - LLAppearanceMgr::getInstance()->updateIsDirty(); - LLAppearanceMgr::getInstance()->setOutfitLocked(false); - gAgentWearables.notifyLoadingFinished(); - } -} - -void update_base_outfit_after_ordering() -{ - LLAppearanceMgr& app_mgr = LLAppearanceMgr::instance(); - app_mgr.setOutfitImage(LLUUID()); - LLInventoryModel::cat_array_t sub_cat_array; - LLInventoryModel::item_array_t outfit_item_array; - gInventory.collectDescendents(app_mgr.getBaseOutfitUUID(), - sub_cat_array, - outfit_item_array, - LLInventoryModel::EXCLUDE_TRASH); - for (LLViewerInventoryItem* outfit_item : outfit_item_array) - { - LLViewerInventoryItem* linked_item = outfit_item->getLinkedItem(); - if (linked_item != NULL) - { - if (linked_item->getActualType() == LLAssetType::AT_TEXTURE) - { - app_mgr.setOutfitImage(linked_item->getLinkedUUID()); - if (linked_item->getName() == LLAppearanceMgr::sExpectedTextureName) - { - // Images with "appropriate" name take priority - break; - } - } - } - else if (outfit_item->getActualType() == LLAssetType::AT_TEXTURE) - { - app_mgr.setOutfitImage(outfit_item->getUUID()); - if (outfit_item->getName() == LLAppearanceMgr::sExpectedTextureName) - { - // Images with "appropriate" name take priority - break; - } - } - } - - LLPointer dirty_state_updater = - new LLBoostFuncInventoryCallback(no_op_inventory_func, appearance_mgr_update_dirty_state); - - //COF contains only links so we copy to the Base Outfit only links - const LLUUID base_outfit_id = app_mgr.getBaseOutfitUUID(); - bool copy_folder_links = false; - app_mgr.slamCategoryLinks(app_mgr.getCOF(), base_outfit_id, copy_folder_links, dirty_state_updater); - - if (base_outfit_id.notNull()) - { - LLIsValidItemLink collector; - - LLInventoryModel::cat_array_t cof_cats; - LLInventoryModel::item_array_t cof_item_array; - gInventory.collectDescendentsIf(app_mgr.getCOF(), cof_cats, cof_item_array, - LLInventoryModel::EXCLUDE_TRASH, collector); - - for (U32 i = 0; i < outfit_item_array.size(); ++i) - { - LLViewerInventoryItem* linked_item = outfit_item_array.at(i)->getLinkedItem(); - if (linked_item != NULL && linked_item->getActualType() == LLAssetType::AT_TEXTURE) - { - outfit_item_array.erase(outfit_item_array.begin() + i); - break; - } - } - - if (outfit_item_array.size() != cof_item_array.size()) - { - return; - } - - std::sort(cof_item_array.begin(), cof_item_array.end(), sort_by_linked_uuid); - std::sort(outfit_item_array.begin(), outfit_item_array.end(), sort_by_linked_uuid); - - for (U32 i = 0; i < cof_item_array.size(); ++i) - { - LLViewerInventoryItem *cof_it = cof_item_array.at(i); - LLViewerInventoryItem *base_it = outfit_item_array.at(i); - - if (cof_it->getActualDescription() != base_it->getActualDescription()) - { - if (cof_it->getLinkedUUID() == base_it->getLinkedUUID()) - { - base_it->setDescription(cof_it->getActualDescription()); - gInventory.updateItem(base_it); - } - } - } - LLAppearanceMgr::getInstance()->updateIsDirty(); - } - -} - -// Save COF changes - update the contents of the current base outfit -// to match the current COF. Fails if no current base outfit is set. -bool LLAppearanceMgr::updateBaseOutfit() -{ - if (isOutfitLocked()) - { - // don't allow modify locked outfit - llassert(!isOutfitLocked()); - return false; - } - - setOutfitLocked(true); - - gAgentWearables.notifyLoadingStarted(); - - const LLUUID base_outfit_id = getBaseOutfitUUID(); - if (base_outfit_id.isNull()) return false; - LL_DEBUGS("Avatar") << "saving cof to base outfit " << base_outfit_id << LL_ENDL; - - LLPointer cb = - new LLBoostFuncInventoryCallback(no_op_inventory_func, update_base_outfit_after_ordering); - // Really shouldn't be needed unless there's a race condition - - // updateAppearanceFromCOF() already calls updateClothingOrderingInfo. - updateClothingOrderingInfo(LLUUID::null, cb); - - return true; -} - -void LLAppearanceMgr::divvyWearablesByType(const LLInventoryModel::item_array_t& items, wearables_by_type_t& items_by_type) -{ - items_by_type.resize(LLWearableType::WT_COUNT); - if (items.empty()) return; - - for (S32 i=0; iisWearableType()) - continue; - LLWearableType::EType type = item->getWearableType(); - if(type < 0 || type >= LLWearableType::WT_COUNT) - { - LL_WARNS("Appearance") << "Invalid wearable type. Inventory type does not match wearable flag bitfield." << LL_ENDL; - continue; - } - items_by_type[type].push_back(item); - } -} - -std::string build_order_string(LLWearableType::EType type, U32 i) -{ - std::ostringstream order_num; - order_num << ORDER_NUMBER_SEPARATOR << type * 100 + i; - return order_num.str(); -} - -struct WearablesOrderComparator -{ - LOG_CLASS(WearablesOrderComparator); - WearablesOrderComparator(const LLWearableType::EType type) - { - mControlSize = build_order_string(type, 0).size(); - }; - - bool operator()(const LLInventoryItem* item1, const LLInventoryItem* item2) - { - const std::string& desc1 = item1->getActualDescription(); - const std::string& desc2 = item2->getActualDescription(); - - bool item1_valid = (desc1.size() == mControlSize) && (ORDER_NUMBER_SEPARATOR == desc1[0]); - bool item2_valid = (desc2.size() == mControlSize) && (ORDER_NUMBER_SEPARATOR == desc2[0]); - - if (item1_valid && item2_valid) - return desc1 < desc2; - - //we need to sink down invalid items: items with empty descriptions, items with "Broken link" descriptions, - //items with ordering information but not for the associated wearables type - if (!item1_valid && item2_valid) - return false; - else if (item1_valid && !item2_valid) - return true; - - return item1->getName() < item2->getName(); - } - - U32 mControlSize; -}; - -void LLAppearanceMgr::getWearableOrderingDescUpdates(LLInventoryModel::item_array_t& wear_items, - desc_map_t& desc_map) -{ - wearables_by_type_t items_by_type(LLWearableType::WT_COUNT); - divvyWearablesByType(wear_items, items_by_type); - - for (U32 type = LLWearableType::WT_SHIRT; type < LLWearableType::WT_COUNT; type++) - { - U32 size = items_by_type[type].size(); - if (!size) continue; - - //sinking down invalid items which need reordering - std::sort(items_by_type[type].begin(), items_by_type[type].end(), WearablesOrderComparator((LLWearableType::EType) type)); - - //requesting updates only for those links which don't have "valid" descriptions - for (U32 i = 0; i < size; i++) - { - LLViewerInventoryItem* item = items_by_type[type][i]; - if (!item) continue; - - std::string new_order_str = build_order_string((LLWearableType::EType)type, i); - if (new_order_str == item->getActualDescription()) continue; - - desc_map[item->getUUID()] = new_order_str; - } - } -} - -bool LLAppearanceMgr::validateClothingOrderingInfo(LLUUID cat_id) -{ - // COF is processed if cat_id is not specified - if (cat_id.isNull()) - { - cat_id = getCOF(); - } - - LLInventoryModel::item_array_t wear_items; - getDescendentsOfAssetType(cat_id, wear_items, LLAssetType::AT_CLOTHING); - - // Identify items for which desc needs to change. - desc_map_t desc_map; - getWearableOrderingDescUpdates(wear_items, desc_map); - - for (desc_map_t::const_iterator it = desc_map.begin(); - it != desc_map.end(); ++it) - { - const LLUUID& item_id = it->first; - const std::string& new_order_str = it->second; - LLViewerInventoryItem *item = gInventory.getItem(item_id); - LL_WARNS() << "Order validation fails: " << item->getName() - << " needs to update desc to: " << new_order_str - << " (from: " << item->getActualDescription() << ")" << LL_ENDL; - } - - return desc_map.size() == 0; -} - -void LLAppearanceMgr::updateClothingOrderingInfo(LLUUID cat_id, - LLPointer cb) -{ - // COF is processed if cat_id is not specified - if (cat_id.isNull()) - { - cat_id = getCOF(); - } - - LLInventoryModel::item_array_t wear_items; - getDescendentsOfAssetType(cat_id, wear_items, LLAssetType::AT_CLOTHING); - - // Identify items for which desc needs to change. - desc_map_t desc_map; - getWearableOrderingDescUpdates(wear_items, desc_map); - - for (desc_map_t::const_iterator it = desc_map.begin(); - it != desc_map.end(); ++it) - { - LLSD updates; - const LLUUID& item_id = it->first; - const std::string& new_order_str = it->second; - LLViewerInventoryItem *item = gInventory.getItem(item_id); - LL_DEBUGS("Avatar") << item->getName() << " updating desc to: " << new_order_str - << " (was: " << item->getActualDescription() << ")" << LL_ENDL; - updates["desc"] = new_order_str; - update_inventory_item(item_id,updates,cb); - } - -} - - -LLSD LLAppearanceMgr::dumpCOF() const -{ - LLSD links = LLSD::emptyArray(); - LLMD5 md5; - - LLInventoryModel::cat_array_t cat_array; - LLInventoryModel::item_array_t item_array; - gInventory.collectDescendents(getCOF(),cat_array,item_array,LLInventoryModel::EXCLUDE_TRASH); - for (S32 i=0; igetUUID()); - md5.update((unsigned char*)item_id.mData, 16); - item["description"] = inv_item->getActualDescription(); - md5.update(inv_item->getActualDescription()); - item["asset_type"] = inv_item->getActualType(); - LLUUID linked_id(inv_item->getLinkedUUID()); - item["linked_id"] = linked_id; - md5.update((unsigned char*)linked_id.mData, 16); - - if (LLAssetType::AT_LINK == inv_item->getActualType()) - { - const LLViewerInventoryItem* linked_item = inv_item->getLinkedItem(); - if (NULL == linked_item) - { - LL_WARNS() << "Broken link for item '" << inv_item->getName() - << "' (" << inv_item->getUUID() - << ") during requestServerAppearanceUpdate" << LL_ENDL; - continue; - } - // Some assets may be 'hidden' and show up as null in the viewer. - //if (linked_item->getAssetUUID().isNull()) - //{ - // LL_WARNS() << "Broken link (null asset) for item '" << inv_item->getName() - // << "' (" << inv_item->getUUID() - // << ") during requestServerAppearanceUpdate" << LL_ENDL; - // continue; - //} - LLUUID linked_asset_id(linked_item->getAssetUUID()); - md5.update((unsigned char*)linked_asset_id.mData, 16); - U32 flags = linked_item->getFlags(); - md5.update(std::to_string(flags)); - } - else if (LLAssetType::AT_LINK_FOLDER != inv_item->getActualType()) - { - LL_WARNS() << "Non-link item '" << inv_item->getName() - << "' (" << inv_item->getUUID() - << ") type " << (S32) inv_item->getActualType() - << " during requestServerAppearanceUpdate" << LL_ENDL; - continue; - } - links.append(item); - } - LLSD result = LLSD::emptyMap(); - result["cof_contents"] = links; - char cof_md5sum[MD5HEX_STR_SIZE]; - md5.finalize(); - md5.hex_digest(cof_md5sum); - result["cof_md5sum"] = std::string(cof_md5sum); - return result; -} - -void LLAppearanceMgr::cleanup() -{ - mIsInUpdateAppearanceFromCOF = false; - mOutstandingAppearanceBakeRequest = false; - mRerequestAppearanceBake = false; - mCOFID.setNull(); -} - -// static -void LLAppearanceMgr::onIdle(void *) -{ - LLAppearanceMgr* mgr = LLAppearanceMgr::getInstance(); - if (mgr->mRerequestAppearanceBake) - { - mgr->requestServerAppearanceUpdate(); - } -} - -void LLAppearanceMgr::requestServerAppearanceUpdate() -{ - // Workaround: we shouldn't request update from server prior to uploading all attachments, but it is - // complicated to check for pending attachment uploads, so we are just waiting for uploads to complete - if (!mOutstandingAppearanceBakeRequest && gAssetStorage->getNumPendingUploads() == 0) - { - mRerequestAppearanceBake = false; - LLCoprocedureManager::CoProcedure_t proc = boost::bind(&LLAppearanceMgr::serverAppearanceUpdateCoro, this, _1); - LLCoprocedureManager::instance().enqueueCoprocedure("AIS", "LLAppearanceMgr::serverAppearanceUpdateCoro", proc); - } - else - { - // Shedule update - mRerequestAppearanceBake = true; - } -} - -void LLAppearanceMgr::serverAppearanceUpdateCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &httpAdapter) -{ - BoolSetter outstanding(mOutstandingAppearanceBakeRequest); - if (!gAgent.getRegion()) - { - LL_WARNS("Avatar") << "Region not set, cannot request server appearance update" << LL_ENDL; - return; - } - if (gAgent.getRegion()->getCentralBakeVersion() == 0) - { - LL_WARNS("Avatar") << "Region does not support baking" << LL_ENDL; - return; - } - - std::string url = gAgent.getRegion()->getCapability("UpdateAvatarAppearance"); - if (url.empty()) - { - LL_WARNS("Agent") << "Could not retrieve region capability \"UpdateAvatarAppearance\"" << LL_ENDL; - return; - } - - //---------------- - if (gAgentAvatarp->isEditingAppearance()) - { - LL_WARNS("Avatar") << "Avatar editing appearance, not sending request." << LL_ENDL; - // don't send out appearance updates if in appearance editing mode - return; - } - - llcoro::suspend(); - if (LLApp::isExiting()) - { - return; - } - S32 retryCount(0); - bool bRetry; - do - { - // If we have already received an update for this or higher cof version, - // put a warning in the log and cancel the request. - S32 cofVersion = getCOFVersion(); - S32 lastRcv = gAgentAvatarp->mLastUpdateReceivedCOFVersion; - S32 lastReq = gAgentAvatarp->mLastUpdateRequestCOFVersion; - - LL_INFOS("Avatar") << "Requesting COF version " << cofVersion << - " (Last Received:" << lastRcv << ")" << - " (Last Requested:" << lastReq << ")" << LL_ENDL; - - if (cofVersion == LLViewerInventoryCategory::VERSION_UNKNOWN) - { - LL_INFOS("AVatar") << "COF version is unknown... not requesting until COF version is known." << LL_ENDL; - return; - } - else - { - if (cofVersion <= lastRcv) - { - LL_WARNS("Avatar") << "Have already received update for cof version " << lastRcv - << " but requesting for " << cofVersion << LL_ENDL; - return; - } - if (lastReq >= cofVersion) - { - LL_WARNS("Avatar") << "Request already in flight for cof version " << lastReq - << " but requesting for " << cofVersion << LL_ENDL; - return; - } - } - - // Actually send the request. - LL_DEBUGS("Avatar") << "Will send request for cof_version " << cofVersion << LL_ENDL; - - bRetry = false; - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest()); - - if (gSavedSettings.getBOOL("DebugForceAppearanceRequestFailure")) - { - cofVersion += 999; - LL_WARNS("Avatar") << "Forcing version failure on COF Baking" << LL_ENDL; - } - - LL_INFOS("Avatar") << "Requesting bake for COF version " << cofVersion << LL_ENDL; - - LLSD postData; - if (gSavedSettings.getBOOL("DebugAvatarExperimentalServerAppearanceUpdate")) - { - postData = dumpCOF(); - } - else - { - postData["cof_version"] = cofVersion; - } - - gAgentAvatarp->mLastUpdateRequestCOFVersion = cofVersion; - - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); - - if (LLApp::isExiting()) - { - return; - } - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status || !result["success"].asBoolean()) - { - std::string message = (result.has("error")) ? result["error"].asString() : status.toString(); - LL_WARNS("Avatar") << "Appearance Failure. server responded with \"" << message << "\"" << LL_ENDL; - - // We may have requested a bake for a stale COF (especially if the inventory - // is still updating. If that is the case re send the request with the - // corrected COF version. (This may also be the case if the viewer is running - // on multiple machines. - if (result.has("expected")) - { - S32 expectedCofVersion = result["expected"].asInteger(); - LL_WARNS("Avatar") << "Server expected " << expectedCofVersion << " as COF version" << LL_ENDL; - - // Force an update texture request for ourself. The message will return - // through the UDP and be handled in LLVOAvatar::processAvatarAppearance - // this should ensure that we receive a new canonical COF from the sim - // host. Hopefully it will return before the timeout. - LLAvatarPropertiesProcessor::getInstance()->sendAvatarTexturesRequest(gAgent.getID()); - - bRetry = true; - // Wait for a 1/2 second before trying again. Just to keep from asking too quickly. - if (++retryCount > BAKE_RETRY_MAX_COUNT) - { - LL_WARNS("Avatar") << "Bake retry count exceeded!" << LL_ENDL; - break; - } - F32 timeout = pow(BAKE_RETRY_TIMEOUT, static_cast(retryCount)) - 1.0; - - LL_WARNS("Avatar") << "Bake retry #" << retryCount << " in " << timeout << " seconds." << LL_ENDL; - - llcoro::suspendUntilTimeout(timeout); - if (LLApp::isExiting()) - { - return; - } - bRetry = true; - continue; - } - else - { - LL_WARNS("Avatar") << "No retry attempted." << LL_ENDL; - break; - } - } - - LL_DEBUGS("Avatar") << "succeeded" << LL_ENDL; - if (gSavedSettings.getBOOL("DebugAvatarAppearanceMessage")) - { - dump_sequential_xml(gAgentAvatarp->getFullname() + "_appearance_request_ok", result); - } - - } while (bRetry); -} - -/*static*/ -void LLAppearanceMgr::debugAppearanceUpdateCOF(const LLSD& content) -{ - dump_sequential_xml(gAgentAvatarp->getFullname() + "_appearance_request_error", content); - - LL_INFOS("Avatar") << "AIS COF, version received: " << content["expected"].asInteger() - << " ================================= " << LL_ENDL; - std::set ais_items, local_items; - const LLSD& cof_raw = content["cof_raw"]; - for (LLSD::array_const_iterator it = cof_raw.beginArray(); - it != cof_raw.endArray(); ++it) - { - const LLSD& item = *it; - if (item["parent_id"].asUUID() == LLAppearanceMgr::instance().getCOF()) - { - ais_items.insert(item["item_id"].asUUID()); - if (item["type"].asInteger() == 24) // link - { - LL_INFOS("Avatar") << "AIS Link: item_id: " << item["item_id"].asUUID() - << " linked_item_id: " << item["asset_id"].asUUID() - << " name: " << item["name"].asString() - << LL_ENDL; - } - else if (item["type"].asInteger() == 25) // folder link - { - LL_INFOS("Avatar") << "AIS Folder link: item_id: " << item["item_id"].asUUID() - << " linked_item_id: " << item["asset_id"].asUUID() - << " name: " << item["name"].asString() - << LL_ENDL; - } - else - { - LL_INFOS("Avatar") << "AIS Other: item_id: " << item["item_id"].asUUID() - << " linked_item_id: " << item["asset_id"].asUUID() - << " name: " << item["name"].asString() - << " type: " << item["type"].asInteger() - << LL_ENDL; - } - } - } - LL_INFOS("Avatar") << LL_ENDL; - LL_INFOS("Avatar") << "Local COF, version requested: " << content["observed"].asInteger() - << " ================================= " << LL_ENDL; - LLInventoryModel::cat_array_t cat_array; - LLInventoryModel::item_array_t item_array; - gInventory.collectDescendents(LLAppearanceMgr::instance().getCOF(), - cat_array, item_array, LLInventoryModel::EXCLUDE_TRASH); - for (S32 i = 0; i < item_array.size(); i++) - { - const LLViewerInventoryItem* inv_item = item_array.at(i).get(); - local_items.insert(inv_item->getUUID()); - LL_INFOS("Avatar") << "LOCAL: item_id: " << inv_item->getUUID() - << " linked_item_id: " << inv_item->getLinkedUUID() - << " name: " << inv_item->getName() - << " parent: " << inv_item->getParentUUID() - << LL_ENDL; - } - LL_INFOS("Avatar") << " ================================= " << LL_ENDL; - S32 local_only = 0, ais_only = 0; - for (std::set::iterator it = local_items.begin(); it != local_items.end(); ++it) - { - if (ais_items.find(*it) == ais_items.end()) - { - LL_INFOS("Avatar") << "LOCAL ONLY: " << *it << LL_ENDL; - local_only++; - } - } - for (std::set::iterator it = ais_items.begin(); it != ais_items.end(); ++it) - { - if (local_items.find(*it) == local_items.end()) - { - LL_INFOS("Avatar") << "AIS ONLY: " << *it << LL_ENDL; - ais_only++; - } - } - if (local_only == 0 && ais_only == 0) - { - LL_INFOS("Avatar") << "COF contents identical, only version numbers differ (req " - << content["observed"].asInteger() - << " rcv " << content["expected"].asInteger() - << ")" << LL_ENDL; - } -} - - -std::string LLAppearanceMgr::getAppearanceServiceURL() const -{ - if (gSavedSettings.getString("DebugAvatarAppearanceServiceURLOverride").empty()) - { - return mAppearanceServiceURL; - } - return gSavedSettings.getString("DebugAvatarAppearanceServiceURLOverride"); -} - -void show_created_outfit(LLUUID& folder_id, bool show_panel = true) -{ - if (!LLApp::isRunning()) - { - LL_WARNS() << "called during shutdown, skipping" << LL_ENDL; - return; - } - - LL_DEBUGS("Avatar") << "called" << LL_ENDL; - LLSD key; - - //EXT-7727. For new accounts inventory callback is created during login process - // and may be processed after login process is finished - if (show_panel) - { - LL_DEBUGS("Avatar") << "showing panel" << LL_ENDL; - LLFloaterSidePanelContainer::showPanel("appearance", "panel_outfits_inventory", key); - - } - LLOutfitsList *outfits_list = - dynamic_cast(LLFloaterSidePanelContainer::getPanel("appearance", "outfitslist_tab")); - if (outfits_list) - { - outfits_list->setSelectedOutfitByUUID(folder_id); - } - - LLAppearanceMgr::getInstance()->updateIsDirty(); - gAgentWearables.notifyLoadingFinished(); // New outfit is saved. - LLAppearanceMgr::getInstance()->updatePanelOutfitName(""); - - // For SSB, need to update appearance after we add a base outfit - // link, since, the COF version has changed. There is a race - // condition in initial outfit setup which can lead to rez - // failures - SH-3860. - LL_DEBUGS("Avatar") << "requesting appearance update after createBaseOutfitLink" << LL_ENDL; - LLPointer cb = new LLUpdateAppearanceOnDestroy; - LLAppearanceMgr::getInstance()->createBaseOutfitLink(folder_id, cb); -} - -void LLAppearanceMgr::onOutfitFolderCreated(const LLUUID& folder_id, bool show_panel) -{ - LLPointer cb = - new LLBoostFuncInventoryCallback(no_op_inventory_func, - boost::bind(&LLAppearanceMgr::onOutfitFolderCreatedAndClothingOrdered,this,folder_id,show_panel)); - updateClothingOrderingInfo(LLUUID::null, cb); -} - -void LLAppearanceMgr::onOutfitFolderCreatedAndClothingOrdered(const LLUUID& folder_id, bool show_panel) -{ - LLPointer cb = - new LLBoostFuncInventoryCallback(no_op_inventory_func, - boost::bind(show_created_outfit,folder_id,show_panel)); - bool copy_folder_links = false; - slamCategoryLinks(getCOF(), folder_id, copy_folder_links, cb); -} - -void LLAppearanceMgr::makeNewOutfitLinks(const std::string& new_folder_name, bool show_panel) -{ - if (!isAgentAvatarValid()) return; - - LLUIUsage::instance().logCommand("Avatar.CreateNewOutfit"); - - LL_DEBUGS("Avatar") << "creating new outfit" << LL_ENDL; - - gAgentWearables.notifyLoadingStarted(); - - // First, make a folder in the My Outfits directory. - const LLUUID parent_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); - - gInventory.createNewCategory( - parent_id, - LLFolderType::FT_OUTFIT, - new_folder_name, - [show_panel](const LLUUID &new_cat_id) - { - LLAppearanceMgr::getInstance()->onOutfitFolderCreated(new_cat_id, show_panel); - }); -} - -void LLAppearanceMgr::wearBaseOutfit() -{ - const LLUUID& base_outfit_id = getBaseOutfitUUID(); - if (base_outfit_id.isNull()) return; - - updateCOF(base_outfit_id); -} - -void LLAppearanceMgr::removeItemsFromAvatar(const uuid_vec_t& ids_to_remove, nullary_func_t post_update_func) -{ - LL_DEBUGS("UIUsage") << "removeItemsFromAvatar" << LL_ENDL; - LLUIUsage::instance().logCommand("Avatar.RemoveItem"); - - if (ids_to_remove.empty()) - { - LL_WARNS() << "called with empty list, nothing to do" << LL_ENDL; - return; - } - LLPointer cb = new LLUpdateAppearanceOnDestroy(true, true, post_update_func); - for (uuid_vec_t::const_iterator it = ids_to_remove.begin(); it != ids_to_remove.end(); ++it) - { - const LLUUID& id_to_remove = *it; - const LLUUID& linked_item_id = gInventory.getLinkedItemID(id_to_remove); - LLViewerInventoryItem *item = gInventory.getItem(linked_item_id); - if (item && item->getType() == LLAssetType::AT_OBJECT) - { - LL_DEBUGS("Avatar") << "ATT removing attachment " << item->getName() << " id " << item->getUUID() << LL_ENDL; - } - if (item && item->getType() == LLAssetType::AT_BODYPART) - { - continue; - } - removeCOFItemLinks(linked_item_id, cb); - addDoomedTempAttachment(linked_item_id); - } -} - -void LLAppearanceMgr::removeItemFromAvatar(const LLUUID& id_to_remove, nullary_func_t post_update_func) -{ - uuid_vec_t ids_to_remove; - ids_to_remove.push_back(id_to_remove); - removeItemsFromAvatar(ids_to_remove, post_update_func); -} - - -// Adds the given item ID to mDoomedTempAttachmentIDs iff it's a temp attachment -void LLAppearanceMgr::addDoomedTempAttachment(const LLUUID& id_to_remove) -{ - LLViewerObject * attachmentp = gAgentAvatarp->findAttachmentByID(id_to_remove); - if (attachmentp && - attachmentp->isTempAttachment()) - { // If this is a temp attachment and we want to remove it, record the ID - // so it will be deleted when attachments are synced up with COF - mDoomedTempAttachmentIDs.insert(id_to_remove); - //LL_INFOS() << "Will remove temp attachment id " << id_to_remove << LL_ENDL; - } -} - -// Find AND REMOVES the given UUID from mDoomedTempAttachmentIDs -bool LLAppearanceMgr::shouldRemoveTempAttachment(const LLUUID& item_id) -{ - doomed_temp_attachments_t::iterator iter = mDoomedTempAttachmentIDs.find(item_id); - if (iter != mDoomedTempAttachmentIDs.end()) - { - mDoomedTempAttachmentIDs.erase(iter); - return true; - } - return false; -} - - -bool LLAppearanceMgr::moveWearable(LLViewerInventoryItem* item, bool closer_to_body) -{ - if (!item || !item->isWearableType()) return false; - if (item->getType() != LLAssetType::AT_CLOTHING) return false; - if (!gInventory.isObjectDescendentOf(item->getUUID(), getCOF())) return false; - - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - LLFindWearablesOfType filter_wearables_of_type(item->getWearableType()); - gInventory.collectDescendentsIf(getCOF(), cats, items, true, filter_wearables_of_type); - if (items.empty()) return false; - - // We assume that the items have valid descriptions. - std::sort(items.begin(), items.end(), WearablesOrderComparator(item->getWearableType())); - - if (closer_to_body && items.front() == item) return false; - if (!closer_to_body && items.back() == item) return false; - - LLInventoryModel::item_array_t::iterator it = std::find(items.begin(), items.end(), item); - if (items.end() == it) return false; - - - //swapping descriptions - closer_to_body ? --it : ++it; - LLViewerInventoryItem* swap_item = *it; - if (!swap_item) return false; - std::string tmp = swap_item->getActualDescription(); - swap_item->setDescription(item->getActualDescription()); - item->setDescription(tmp); - - // LL_DEBUGS("Inventory") << "swap, item " - // << ll_pretty_print_sd(item->asLLSD()) - // << " swap_item " - // << ll_pretty_print_sd(swap_item->asLLSD()) << LL_ENDL; - - // FIXME switch to use AISv3 where supported. - //items need to be updated on a dataserver - item->setComplete(true); - item->updateServer(false); - gInventory.updateItem(item); - - swap_item->setComplete(true); - swap_item->updateServer(false); - gInventory.updateItem(swap_item); - - //to cause appearance of the agent to be updated - bool result = false; - if ((result = gAgentWearables.moveWearable(item, closer_to_body))) - { - gAgentAvatarp->wearableUpdated(item->getWearableType()); - } - - setOutfitDirty(true); - - //*TODO do we need to notify observers here in such a way? - gInventory.notifyObservers(); - - return result; -} - -//static -void LLAppearanceMgr::sortItemsByActualDescription(LLInventoryModel::item_array_t& items) -{ - if (items.size() < 2) return; - - std::sort(items.begin(), items.end(), sort_by_actual_description); -} - -//#define DUMP_CAT_VERBOSE - -void LLAppearanceMgr::dumpCat(const LLUUID& cat_id, const std::string& msg) -{ - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - gInventory.collectDescendents(cat_id, cats, items, LLInventoryModel::EXCLUDE_TRASH); - -#ifdef DUMP_CAT_VERBOSE - LL_INFOS() << LL_ENDL; - LL_INFOS() << str << LL_ENDL; - S32 hitcount = 0; - for(S32 i=0; igetName() <getLinkedItem() : NULL; - LLUUID asset_id; - if (linked_item) - { - asset_id = linked_item->getAssetUUID(); - } - LL_DEBUGS("Avatar") << self_av_string() << msg << " " << i <<" " << (item ? item->getName() : "(nullitem)") << " " << asset_id.asString() << LL_ENDL; - } -} - -bool LLAppearanceMgr::mActive = true; - -LLAppearanceMgr::LLAppearanceMgr(): - mAttachmentInvLinkEnabled(false), - mOutfitIsDirty(false), - mOutfitLocked(false), - mInFlightTimer(), - mIsInUpdateAppearanceFromCOF(false), - mOutstandingAppearanceBakeRequest(false), - mRerequestAppearanceBake(false) -{ - LLOutfitObserver& outfit_observer = LLOutfitObserver::instance(); - // unlock outfit on save operation completed - outfit_observer.addCOFSavedCallback(boost::bind( - &LLAppearanceMgr::setOutfitLocked, this, false)); - - mUnlockOutfitTimer.reset(new LLOutfitUnLockTimer(gSavedSettings.getS32( - "OutfitOperationsTimeout"))); - - gIdleCallbacks.addFunction(&LLAttachmentsMgr::onIdle, NULL); - gIdleCallbacks.addFunction(&LLAppearanceMgr::onIdle, NULL); //sheduling appearance update requests -} - -LLAppearanceMgr::~LLAppearanceMgr() -{ - mActive = false; -} - -void LLAppearanceMgr::setAttachmentInvLinkEnable(bool val) -{ - LL_DEBUGS("Avatar") << "setAttachmentInvLinkEnable => " << (int) val << LL_ENDL; - mAttachmentInvLinkEnabled = val; -} -boost::signals2::connection LLAppearanceMgr::setAttachmentsChangedCallback(attachments_changed_callback_t cb) -{ - return mAttachmentsChangeSignal.connect(cb); -} - -void dumpAttachmentSet(const std::set& atts, const std::string& msg) -{ - LL_INFOS() << msg << LL_ENDL; - for (std::set::const_iterator it = atts.begin(); - it != atts.end(); - ++it) - { - LLUUID item_id = *it; - LLViewerInventoryItem *item = gInventory.getItem(item_id); - if (item) - LL_INFOS() << "atts " << item->getName() << LL_ENDL; - else - LL_INFOS() << "atts " << "UNKNOWN[" << item_id.asString() << "]" << LL_ENDL; - } - LL_INFOS() << LL_ENDL; -} - -void LLAppearanceMgr::registerAttachment(const LLUUID& item_id) -{ - LLViewerInventoryItem *item = gInventory.getItem(item_id); - LL_DEBUGS("Avatar") << "ATT registering attachment " - << (item ? item->getName() : "UNKNOWN") << " " << item_id << LL_ENDL; - gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id); - - LLAttachmentsMgr::instance().onAttachmentArrived(item_id); - - mAttachmentsChangeSignal(); -} - -void LLAppearanceMgr::unregisterAttachment(const LLUUID& item_id) -{ - LLViewerInventoryItem *item = gInventory.getItem(item_id); - LL_DEBUGS("Avatar") << "ATT unregistering attachment " - << (item ? item->getName() : "UNKNOWN") << " " << item_id << LL_ENDL; - gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id); - - LLAttachmentsMgr::instance().onDetachCompleted(item_id); - if (mAttachmentInvLinkEnabled && isLinkedInCOF(item_id)) - { - LL_DEBUGS("Avatar") << "ATT removing COF link for attachment " - << (item ? item->getName() : "UNKNOWN") << " " << item_id << LL_ENDL; - LLAppearanceMgr::removeCOFItemLinks(item_id); - } - else - { - //LL_INFOS() << "no link changes, inv link not enabled" << LL_ENDL; - } - - mAttachmentsChangeSignal(); -} - -bool LLAppearanceMgr::getIsInCOF(const LLUUID& obj_id) const -{ - const LLUUID& cof = getCOF(); - if (obj_id == cof) - return true; - const LLInventoryObject* obj = gInventory.getObject(obj_id); - if (obj && obj->getParentUUID() == cof) - return true; - return false; -} - -bool LLAppearanceMgr::getIsInCOF(const LLInventoryObject* obj) const -{ - const LLUUID& cof = getCOF(); - if (obj->getUUID() == cof) - return true; - if (obj && obj->getParentUUID() == cof) - return true; - return false; -} - -bool LLAppearanceMgr::getIsProtectedCOFItem(const LLUUID& obj_id) const -{ - if (!getIsInCOF(obj_id)) return false; - - // If a non-link somehow ended up in COF, allow deletion. - const LLInventoryObject *obj = gInventory.getObject(obj_id); - if (obj && !obj->getIsLinkType()) - { - return false; - } - - // For now, don't allow direct deletion from the COF. Instead, force users - // to choose "Detach" or "Take Off". - return true; -} - -bool LLAppearanceMgr::getIsProtectedCOFItem(const LLInventoryObject* obj) const -{ - if (!getIsInCOF(obj)) return false; - - // If a non-link somehow ended up in COF, allow deletion. - if (obj && !obj->getIsLinkType()) - { - return false; - } - - // For now, don't allow direct deletion from the COF. Instead, force users - // to choose "Detach" or "Take Off". - return true; -} - -class CallAfterCategoryFetchStage2: public LLInventoryFetchItemsObserver -{ -public: - CallAfterCategoryFetchStage2(const uuid_vec_t& ids, - nullary_func_t callable) : - LLInventoryFetchItemsObserver(ids), - mCallable(callable) - { - } - ~CallAfterCategoryFetchStage2() - { - } - virtual void done() - { - LL_INFOS() << this << " done with incomplete " << mIncomplete.size() - << " complete " << mComplete.size() << " calling callable" << LL_ENDL; - - gInventory.removeObserver(this); - doOnIdleOneTime(mCallable); - delete this; - } -protected: - nullary_func_t mCallable; -}; - -class CallAfterCategoryFetchStage1: public LLInventoryFetchDescendentsObserver -{ -public: - CallAfterCategoryFetchStage1(const LLUUID& cat_id, nullary_func_t callable) : - LLInventoryFetchDescendentsObserver(cat_id), - mCallable(callable) - { - } - ~CallAfterCategoryFetchStage1() - { - } - /*virtual*/ void startFetch() - { - bool ais3 = AISAPI::isAvailable(); - for (uuid_vec_t::const_iterator it = mIDs.begin(); it != mIDs.end(); ++it) - { - LLViewerInventoryCategory* cat = gInventory.getCategory(*it); - if (!cat) continue; - if (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) - { - // CHECK IT: isCategoryComplete() checks both version and descendant count but - // fetch() only works for Unknown version and doesn't care about descentants, - // as result fetch won't start and folder will potentially get stuck as - // incomplete in observer. - // Likely either both should use only version or both should check descendants. - cat->fetch(); //blindly fetch it without seeing if anything else is fetching it. - mIncomplete.push_back(*it); //Add to list of things being downloaded for this observer. - } - else if (!isCategoryComplete(cat)) - { - LL_DEBUGS("Inventory") << "Categoty " << *it << " incomplete despite having version" << LL_ENDL; - LLInventoryModelBackgroundFetch::instance().scheduleFolderFetch(*it, true); - mIncomplete.push_back(*it); - } - else if (ais3) - { - LLInventoryModel::cat_array_t* cats; - LLInventoryModel::item_array_t* items; - gInventory.getDirectDescendentsOf(cat->getUUID(), cats, items); - - if (items) - { - S32 complete_count = 0; - S32 incomplete_count = 0; - for (LLInventoryModel::item_array_t::const_iterator it = items->begin(); it < items->end(); ++it) - { - if (!(*it)->isFinished()) - { - incomplete_count++; - } - else - { - complete_count++; - } - } - // AIS can fetch couple items, but if there - // is more than a dozen it will be very slow - // it's faster to get whole folder in such case - if (incomplete_count > LLInventoryFetchItemsObserver::MAX_INDIVIDUAL_ITEM_REQUESTS - || (incomplete_count > 1 && complete_count == 0)) - { - LLInventoryModelBackgroundFetch::instance().scheduleFolderFetch(*it, true); - mIncomplete.push_back(*it); - } - else - { - // let stage2 handle incomplete ones - mComplete.push_back(*it); - } - } - // else should have been handled by isCategoryComplete - } - else - { - mComplete.push_back(*it); - } - } - } - virtual void done() - { - if (mComplete.size() <= 0) - { - // Ex: timeout - LL_WARNS() << "Failed to load data. Removing observer " << LL_ENDL; - gInventory.removeObserver(this); - doOnIdleOneTime(mCallable); - - delete this; - return; - } - - // What we do here is get the complete information on the - // items in the requested category, and set up an observer - // that will wait for that to happen. - LLInventoryModel::cat_array_t* cats; - LLInventoryModel::item_array_t* items; - gInventory.getDirectDescendentsOf(mComplete.front(), cats, items); - - S32 count = items->size(); - if(!count) - { - LL_WARNS() << "Nothing fetched in category " << mComplete.front() - << LL_ENDL; - gInventory.removeObserver(this); - doOnIdleOneTime(mCallable); - - delete this; - return; - } - - LLViewerInventoryCategory* cat = gInventory.getCategory(mComplete.front()); - S32 version = cat ? cat->getVersion() : -2; - LL_INFOS() << "stage1, category " << mComplete.front() << " got " << count << " items, version " << version << " passing to stage2 " << LL_ENDL; - uuid_vec_t ids; - for(S32 i = 0; i < count; ++i) - { - ids.push_back(items->at(i)->getUUID()); - } - - gInventory.removeObserver(this); - - // do the fetch - CallAfterCategoryFetchStage2 *stage2 = new CallAfterCategoryFetchStage2(ids, mCallable); - stage2->startFetch(); - if(stage2->isFinished()) - { - // everything is already here - call done. - stage2->done(); - } - else - { - // it's all on it's way - add an observer, and the inventory - // will call done for us when everything is here. - gInventory.addObserver(stage2); - } - delete this; - } -protected: - nullary_func_t mCallable; -}; - -void callAfterCOFFetch(nullary_func_t cb) -{ - if (AISAPI::isAvailable()) - { - // For reliability assume that we have no relevant cache, so - // fetch cof along with items cof's links point to. - LLInventoryModelBackgroundFetch::getInstance()->fetchCOF(cb); - } - else - { - LL_INFOS() << "AIS API v3 not available, using callAfterCategoryFetch" << LL_ENDL; - LLUUID cat_id = LLAppearanceMgr::instance().getCOF(); - LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); - - // Special case, startup should have marked cof as FETCH_RECURSIVE - // to prevent dupplicate request, remove that - cat->setFetching(LLViewerInventoryCategory::FETCH_NONE); - callAfterCategoryFetch(cat_id, cb); - } -} - -void callAfterCategoryFetch(const LLUUID& cat_id, nullary_func_t cb) -{ - CallAfterCategoryFetchStage1* stage1 = new CallAfterCategoryFetchStage1(cat_id, cb); - stage1->startFetch(); - if (stage1->isFinished()) - { - stage1->done(); - } - else - { - gInventory.addObserver(stage1); - } -} - -void callAfterCategoryLinksFetch(const LLUUID &cat_id, nullary_func_t cb) -{ - if (AISAPI::isAvailable()) - { - // Assume that we have no relevant cache. Fetch folder, and items folder's links point to. - LLInventoryModelBackgroundFetch::getInstance()->fetchFolderAndLinks(cat_id, cb); - } - else - { - LL_WARNS() << "AIS API v3 not available, can't use AISAPI::FetchCOF" << LL_ENDL; - callAfterCategoryFetch(cat_id, cb); - } - -} - -void add_wearable_type_counts(const uuid_vec_t& ids, - S32& clothing_count, - S32& bodypart_count, - S32& object_count, - S32& other_count) -{ - for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it) - { - const LLUUID& item_id_to_wear = *it; - LLViewerInventoryItem* item_to_wear = gInventory.getItem(item_id_to_wear); - if (item_to_wear) - { - if (item_to_wear->getType() == LLAssetType::AT_CLOTHING) - { - clothing_count++; - } - else if (item_to_wear->getType() == LLAssetType::AT_BODYPART) - { - bodypart_count++; - } - else if (item_to_wear->getType() == LLAssetType::AT_OBJECT) - { - object_count++; - } - else - { - other_count++; - } - } - else - { - other_count++; - } - } -} - -void wear_multiple(const uuid_vec_t& ids, bool replace) -{ - S32 clothing_count = 0; - S32 bodypart_count = 0; - S32 object_count = 0; - S32 other_count = 0; - add_wearable_type_counts(ids, clothing_count, bodypart_count, object_count, other_count); - - LLPointer cb = NULL; - if (clothing_count > 0 || bodypart_count > 0) - { - cb = new LLUpdateAppearanceOnDestroy; - } - LLAppearanceMgr::instance().wearItemsOnAvatar(ids, true, replace, cb); -} - -// SLapp for easy-wearing of a stock (library) avatar -// -class LLWearFolderHandler : public LLCommandHandler -{ -public: - // not allowed from outside the app - LLWearFolderHandler() : LLCommandHandler("wear_folder", UNTRUSTED_BLOCK) { } - - bool handle(const LLSD& tokens, - const LLSD& query_map, - const std::string& grid, - LLMediaCtrl* web) - { - LLSD::UUID folder_uuid; - - if (folder_uuid.isNull() && query_map.has("folder_name")) - { - std::string outfit_folder_name = query_map["folder_name"]; - folder_uuid = findDescendentCategoryIDByName( - gInventory.getLibraryRootFolderID(), - outfit_folder_name); - } - if (folder_uuid.isNull() && query_map.has("folder_id")) - { - folder_uuid = query_map["folder_id"].asUUID(); - } - - if (folder_uuid.notNull()) - { - LLPointer category = new LLInventoryCategory(folder_uuid, - LLUUID::null, - LLFolderType::FT_CLOTHING, - "Quick Appearance"); - if ( gInventory.getCategory( folder_uuid ) != NULL ) - { - // Assume this is coming from the predefined avatars web floater - LLUIUsage::instance().logCommand("Avatar.WearPredefinedAppearance"); - LLAppearanceMgr::getInstance()->wearInventoryCategory(category, true, false); - - // *TODOw: This may not be necessary if initial outfit is chosen already -- josh - gAgent.setOutfitChosen(true); - } - } - - // release avatar picker keyboard focus - gFocusMgr.setKeyboardFocus( NULL ); - - return true; - } -}; - -LLWearFolderHandler gWearFolderHandler; +/** + * @file llappearancemgr.cpp + * @brief Manager for initiating appearance changes on the viewer + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include +#include "llaccordionctrltab.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llagentwearables.h" +#include "llappearancemgr.h" +#include "llattachmentsmgr.h" +#include "llcommandhandler.h" +#include "lleventtimer.h" +#include "llfloatersidepanelcontainer.h" +#include "llgesturemgr.h" +#include "llinventorybridge.h" +#include "llinventoryfunctions.h" +#include "llinventorymodelbackgroundfetch.h" +#include "llinventoryobserver.h" +#include "llmd5.h" +#include "llnotificationsutil.h" +#include "llmd5.h" +#include "lloutfitobserver.h" +#include "lloutfitslist.h" +#include "llselectmgr.h" +#include "llsidepanelappearance.h" +#include "llviewerobjectlist.h" +#include "llvoavatar.h" +#include "llvoavatarself.h" +#include "llviewerregion.h" +#include "llwearablelist.h" +#include "llsdutil.h" +#include "llsdserialize.h" +#include "llhttpretrypolicy.h" +#include "llaisapi.h" +#include "llhttpsdhandler.h" +#include "llcorehttputil.h" +#include "llappviewer.h" +#include "llcoros.h" +#include "lleventcoro.h" +#include "lluiusage.h" + +#include "llavatarpropertiesprocessor.h" + +#if LL_MSVC +// disable boost::lexical_cast warning +#pragma warning (disable:4702) +#endif + +namespace +{ + const S32 BAKE_RETRY_MAX_COUNT = 5; + const F32 BAKE_RETRY_TIMEOUT = 2.0F; +} + +// *TODO$: LLInventoryCallback should be deprecated to conform to the new boost::bind/coroutine model. +// temp code in transition +void doAppearanceCb(LLPointer cb, LLUUID id) +{ + if (cb.notNull()) + cb->fire(id); +} + +std::string self_av_string() +{ + // On logout gAgentAvatarp can already be invalid + return isAgentAvatarValid() ? gAgentAvatarp->avString() : ""; +} + +// RAII thingy to guarantee that a variable gets reset when the Setter +// goes out of scope. More general utility would be handy - TODO: +// check boost. +class BoolSetter +{ +public: + BoolSetter(bool& var): + mVar(var) + { + mVar = true; + } + ~BoolSetter() + { + mVar = false; + } +private: + bool& mVar; +}; + +char ORDER_NUMBER_SEPARATOR('@'); + +class LLOutfitUnLockTimer: public LLEventTimer +{ +public: + LLOutfitUnLockTimer(F32 period) : LLEventTimer(period) + { + // restart timer on BOF changed event + LLOutfitObserver::instance().addBOFChangedCallback(boost::bind( + &LLOutfitUnLockTimer::reset, this)); + stop(); + } + + /*virtual*/ + bool tick() + { + if(mEventTimer.hasExpired()) + { + LLAppearanceMgr::instance().setOutfitLocked(false); + } + return false; + } + void stop() { mEventTimer.stop(); } + void start() { mEventTimer.start(); } + void reset() { mEventTimer.reset(); } + bool getStarted() { return mEventTimer.getStarted(); } + + LLTimer& getEventTimer() { return mEventTimer;} +}; + +// support for secondlife:///app/appearance SLapps +class LLAppearanceHandler : public LLCommandHandler +{ +public: + // requests will be throttled from a non-trusted browser + LLAppearanceHandler() : LLCommandHandler("appearance", UNTRUSTED_THROTTLE) {} + + bool handle(const LLSD& params, + const LLSD& query_map, + const std::string& grid, + LLMediaCtrl* web) + { + // support secondlife:///app/appearance/show, but for now we just + // make all secondlife:///app/appearance SLapps behave this way + if (!LLUI::getInstance()->mSettingGroups["config"]->getBOOL("EnableAppearance")) + { + LLNotificationsUtil::add("NoAppearance", LLSD(), LLSD(), std::string("SwitchToStandardSkinAndQuit")); + return true; + } + + LLFloaterSidePanelContainer::showPanel("appearance", LLSD()); + return true; + } +}; + +LLAppearanceHandler gAppearanceHandler; + + +LLUUID findDescendentCategoryIDByName(const LLUUID& parent_id, const std::string& name) +{ + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + LLNameCategoryCollector has_name(name); + gInventory.collectDescendentsIf(parent_id, + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH, + has_name); + if (0 == cat_array.size()) + return LLUUID(); + else + { + LLViewerInventoryCategory *cat = cat_array.at(0); + if (cat) + return cat->getUUID(); + else + { + LL_WARNS() << "null cat" << LL_ENDL; + return LLUUID(); + } + } +} + +// We want this to be much lower (e.g. 15.0 is usually fine), bumping +// up for now until we can diagnose some cases of very slow response +// to requests. +const F32 DEFAULT_RETRY_AFTER_INTERVAL = 300.0; + +// Given the current back-end problems, retrying is causing too many +// duplicate items. Bump this back to 2 once they are resolved (or can +// leave at 0 if the operations become actually reliable). +const S32 DEFAULT_MAX_RETRIES = 0; + +class LLCallAfterInventoryBatchMgr: public LLEventTimer +{ +public: + LLCallAfterInventoryBatchMgr(const LLUUID& dst_cat_id, + const std::string& phase_name, + nullary_func_t on_completion_func, + nullary_func_t on_failure_func = no_op, + F32 retry_after = DEFAULT_RETRY_AFTER_INTERVAL, + S32 max_retries = DEFAULT_MAX_RETRIES + ): + mDstCatID(dst_cat_id), + mTrackingPhase(phase_name), + mOnCompletionFunc(on_completion_func), + mOnFailureFunc(on_failure_func), + mRetryAfter(retry_after), + mMaxRetries(max_retries), + mPendingRequests(0), + mFailCount(0), + mCompletionOrFailureCalled(false), + mRetryCount(0), + LLEventTimer(5.0) + { + if (!mTrackingPhase.empty()) + { + selfStartPhase(mTrackingPhase); + } + } + + void addItems(LLInventoryModel::item_array_t& src_items) + { + for (LLInventoryModel::item_array_t::const_iterator it = src_items.begin(); + it != src_items.end(); + ++it) + { + LLViewerInventoryItem* item = *it; + llassert(item); + addItem(item->getUUID()); + } + } + + // Request or re-request operation for specified item. + void addItem(const LLUUID& item_id) + { + LL_DEBUGS("Avatar") << "item_id " << item_id << LL_ENDL; + if (!requestOperation(item_id)) + { + LL_DEBUGS("Avatar") << "item_id " << item_id << " requestOperation false, skipping" << LL_ENDL; + return; + } + + mPendingRequests++; + // On a re-request, this will reset the timer. + mWaitTimes[item_id] = LLTimer(); + if (mRetryCounts.find(item_id) == mRetryCounts.end()) + { + mRetryCounts[item_id] = 0; + } + else + { + mRetryCounts[item_id]++; + } + } + + virtual bool requestOperation(const LLUUID& item_id) = 0; + + void onOp(const LLUUID& src_id, const LLUUID& dst_id, LLTimer timestamp) + { + if (ll_frand() < gSavedSettings.getF32("InventoryDebugSimulateLateOpRate")) + { + LL_WARNS() << "Simulating late operation by punting handling to later" << LL_ENDL; + doAfterInterval(boost::bind(&LLCallAfterInventoryBatchMgr::onOp,this,src_id,dst_id,timestamp), + mRetryAfter); + return; + } + mPendingRequests--; + F32 elapsed = timestamp.getElapsedTimeF32(); + LL_DEBUGS("Avatar") << "op done, src_id " << src_id << " dst_id " << dst_id << " after " << elapsed << " seconds" << LL_ENDL; + if (mWaitTimes.find(src_id) == mWaitTimes.end()) + { + // No longer waiting for this item - either serviced + // already or gave up after too many retries. + LL_WARNS() << "duplicate or late operation, src_id " << src_id << "dst_id " << dst_id + << " elapsed " << elapsed << " after end " << (S32) mCompletionOrFailureCalled << LL_ENDL; + } + mTimeStats.push(elapsed); + mWaitTimes.erase(src_id); + if (mWaitTimes.empty() && !mCompletionOrFailureCalled) + { + onCompletionOrFailure(); + } + } + + void onCompletionOrFailure() + { + assert (!mCompletionOrFailureCalled); + mCompletionOrFailureCalled = true; + + // Will never call onCompletion() if any item has been flagged as + // a failure - otherwise could wind up with corrupted + // outfit, involuntary nudity, etc. + reportStats(); + if (!mTrackingPhase.empty()) + { + selfStopPhase(mTrackingPhase); + } + if (!mFailCount) + { + onCompletion(); + } + else + { + onFailure(); + } + } + + void onFailure() + { + LL_INFOS() << "failed" << LL_ENDL; + mOnFailureFunc(); + } + + void onCompletion() + { + LL_INFOS() << "done" << LL_ENDL; + mOnCompletionFunc(); + } + + // virtual + // Will be deleted after returning true - only safe to do this if all callbacks have fired. + bool tick() + { + // mPendingRequests will be zero if all requests have been + // responded to. mWaitTimes.empty() will be true if we have + // received at least one reply for each UUID. If requests + // have been dropped and retried, these will not necessarily + // be the same. Only safe to return true if all requests have + // been serviced, since it will result in this object being + // deleted. + bool all_done = (mPendingRequests==0); + + if (!mWaitTimes.empty()) + { + LL_WARNS() << "still waiting on " << mWaitTimes.size() << " items" << LL_ENDL; + for (std::map::iterator it = mWaitTimes.begin(); + it != mWaitTimes.end();) + { + // Use a copy of iterator because it may be erased/invalidated. + std::map::iterator curr_it = it; + ++it; + + F32 time_waited = curr_it->second.getElapsedTimeF32(); + S32 retries = mRetryCounts[curr_it->first]; + if (time_waited > mRetryAfter) + { + if (retries < mMaxRetries) + { + LL_DEBUGS("Avatar") << "Waited " << time_waited << + " for " << curr_it->first << ", retrying" << LL_ENDL; + mRetryCount++; + addItem(curr_it->first); + } + else + { + LL_WARNS() << "Giving up on " << curr_it->first << " after too many retries" << LL_ENDL; + mWaitTimes.erase(curr_it); + mFailCount++; + } + } + if (mWaitTimes.empty()) + { + onCompletionOrFailure(); + } + + } + } + return all_done; + } + + void reportStats() + { + LL_DEBUGS("Avatar") << "Phase: " << mTrackingPhase << LL_ENDL; + LL_DEBUGS("Avatar") << "mFailCount: " << mFailCount << LL_ENDL; + LL_DEBUGS("Avatar") << "mRetryCount: " << mRetryCount << LL_ENDL; + LL_DEBUGS("Avatar") << "Times: n " << mTimeStats.getCount() << " min " << mTimeStats.getMinValue() << " max " << mTimeStats.getMaxValue() << LL_ENDL; + LL_DEBUGS("Avatar") << "Mean " << mTimeStats.getMean() << " stddev " << mTimeStats.getStdDev() << LL_ENDL; + } + + virtual ~LLCallAfterInventoryBatchMgr() + { + LL_DEBUGS("Avatar") << "deleting" << LL_ENDL; + } + +protected: + std::string mTrackingPhase; + std::map mWaitTimes; + std::map mRetryCounts; + LLUUID mDstCatID; + nullary_func_t mOnCompletionFunc; + nullary_func_t mOnFailureFunc; + F32 mRetryAfter; + S32 mMaxRetries; + S32 mPendingRequests; + S32 mFailCount; + S32 mRetryCount; + bool mCompletionOrFailureCalled; + LLViewerStats::StatsAccumulator mTimeStats; +}; + +class LLCallAfterInventoryCopyMgr: public LLCallAfterInventoryBatchMgr +{ +public: + LLCallAfterInventoryCopyMgr(LLInventoryModel::item_array_t& src_items, + const LLUUID& dst_cat_id, + const std::string& phase_name, + nullary_func_t on_completion_func, + nullary_func_t on_failure_func = no_op, + F32 retry_after = DEFAULT_RETRY_AFTER_INTERVAL, + S32 max_retries = DEFAULT_MAX_RETRIES + ): + LLCallAfterInventoryBatchMgr(dst_cat_id, phase_name, on_completion_func, on_failure_func, retry_after, max_retries) + { + addItems(src_items); + sInstanceCount++; + } + + ~LLCallAfterInventoryCopyMgr() + { + sInstanceCount--; + } + + virtual bool requestOperation(const LLUUID& item_id) + { + LLViewerInventoryItem *item = gInventory.getItem(item_id); + llassert(item); + LL_DEBUGS("Avatar") << "copying item " << item_id << LL_ENDL; + if (ll_frand() < gSavedSettings.getF32("InventoryDebugSimulateOpFailureRate")) + { + LL_DEBUGS("Avatar") << "simulating failure by not sending request for item " << item_id << LL_ENDL; + return true; + } + copy_inventory_item( + gAgent.getID(), + item->getPermissions().getOwner(), + item->getUUID(), + mDstCatID, + std::string(), + new LLBoostFuncInventoryCallback(boost::bind(&LLCallAfterInventoryBatchMgr::onOp,this,item_id,_1,LLTimer())) + ); + return true; + } + + static S32 getInstanceCount() { return sInstanceCount; } + +private: + static S32 sInstanceCount; +}; + +S32 LLCallAfterInventoryCopyMgr::sInstanceCount = 0; + +class LLWearCategoryAfterCopy: public LLInventoryCallback +{ +public: + LLWearCategoryAfterCopy(bool append): + mAppend(append) + {} + + // virtual + void fire(const LLUUID& id) + { + // Wear the inventory category. + LLInventoryCategory* cat = gInventory.getCategory(id); + LLAppearanceMgr::instance().wearInventoryCategoryOnAvatar(cat, mAppend); + } + +private: + bool mAppend; +}; + +class LLTrackPhaseWrapper : public LLInventoryCallback +{ +public: + LLTrackPhaseWrapper(const std::string& phase_name, LLPointer cb = NULL): + mTrackingPhase(phase_name), + mCB(cb) + { + selfStartPhase(mTrackingPhase); + } + + // virtual + void fire(const LLUUID& id) + { + if (mCB) + { + mCB->fire(id); + } + } + + // virtual + ~LLTrackPhaseWrapper() + { + selfStopPhase(mTrackingPhase); + } + +protected: + std::string mTrackingPhase; + LLPointer mCB; +}; + +LLUpdateAppearanceOnDestroy::LLUpdateAppearanceOnDestroy(bool enforce_item_restrictions, + bool enforce_ordering, + nullary_func_t post_update_func + ): + mFireCount(0), + mEnforceItemRestrictions(enforce_item_restrictions), + mEnforceOrdering(enforce_ordering), + mPostUpdateFunc(post_update_func) +{ + selfStartPhase("update_appearance_on_destroy"); +} + +void LLUpdateAppearanceOnDestroy::fire(const LLUUID& inv_item) +{ + LLViewerInventoryItem* item = (LLViewerInventoryItem*)gInventory.getItem(inv_item); + const std::string item_name = item ? item->getName() : "ITEM NOT FOUND"; +#ifndef LL_RELEASE_FOR_DOWNLOAD + LL_DEBUGS("Avatar") << self_av_string() << "callback fired [ name:" << item_name << " UUID:" << inv_item << " count:" << mFireCount << " ] " << LL_ENDL; +#endif + mFireCount++; +} + +LLUpdateAppearanceOnDestroy::~LLUpdateAppearanceOnDestroy() +{ + if (!LLApp::isExiting()) + { + // speculative fix for MAINT-1150 + LL_INFOS("Avatar") << self_av_string() << "done update appearance on destroy" << LL_ENDL; + + selfStopPhase("update_appearance_on_destroy"); + + LLAppearanceMgr::instance().updateAppearanceFromCOF(mEnforceItemRestrictions, + mEnforceOrdering, + mPostUpdateFunc); + } +} + +LLUpdateAppearanceAndEditWearableOnDestroy::LLUpdateAppearanceAndEditWearableOnDestroy(const LLUUID& item_id): + mItemID(item_id) +{ +} + +LLRequestServerAppearanceUpdateOnDestroy::~LLRequestServerAppearanceUpdateOnDestroy() +{ + LL_DEBUGS("Avatar") << "ATT requesting server appearance update" << LL_ENDL; + if (!LLApp::isExiting()) + { + LLAppearanceMgr::instance().requestServerAppearanceUpdate(); + } +} + +void edit_wearable_and_customize_avatar(LLUUID item_id) +{ + // Start editing the item if previously requested. + gAgentWearables.editWearableIfRequested(item_id); + + // TODO: camera mode may not be changed if a debug setting is tweaked + if( gAgentCamera.cameraCustomizeAvatar() ) + { + // If we're in appearance editing mode, the current tab may need to be refreshed + LLSidepanelAppearance *panel = dynamic_cast( + LLFloaterSidePanelContainer::getPanel("appearance")); + if (panel) + { + panel->showDefaultSubpart(); + } + } +} + +LLUpdateAppearanceAndEditWearableOnDestroy::~LLUpdateAppearanceAndEditWearableOnDestroy() +{ + if (!LLApp::isExiting()) + { + LLAppearanceMgr::instance().updateAppearanceFromCOF( + true,true, + boost::bind(edit_wearable_and_customize_avatar, mItemID)); + } +} + +class LLBrokenLinkObserver : public LLInventoryObserver +{ +public: + LLUUID mUUID; + bool mEnforceItemRestrictions; + bool mEnforceOrdering; + nullary_func_t mPostUpdateFunc; + + LLBrokenLinkObserver(const LLUUID& uuid, + bool enforce_item_restrictions , + bool enforce_ordering , + nullary_func_t post_update_func) : + mUUID(uuid), + mEnforceItemRestrictions(enforce_item_restrictions), + mEnforceOrdering(enforce_ordering), + mPostUpdateFunc(post_update_func) + { + } + /* virtual */ void changed(U32 mask); + void postProcess(); +}; + +void LLBrokenLinkObserver::changed(U32 mask) +{ + if (mask & LLInventoryObserver::REBUILD) + { + // This observer should be executed after LLInventoryPanel::itemChanged(), + // but if it isn't, consider calling updateAppearanceFromCOF with a delay + const uuid_set_t& changed_item_ids = gInventory.getChangedIDs(); + for (uuid_set_t::const_iterator it = changed_item_ids.begin(); it != changed_item_ids.end(); ++it) + { + const LLUUID& id = *it; + if (id == mUUID) + { + // Might not be processed yet and it is not a + // good idea to update appearane here, postpone. + doOnIdleOneTime([this]() + { + postProcess(); + }); + + gInventory.removeObserver(this); + return; + } + } + } +} + +void LLBrokenLinkObserver::postProcess() +{ + LLViewerInventoryItem* item = gInventory.getItem(mUUID); + llassert(item && !item->getIsBrokenLink()); // the whole point was to get a correct link + if (item && item->getIsBrokenLink()) + { + LL_INFOS_ONCE("Avatar") << "Outfit link broken despite being regenerated" << LL_ENDL; + LL_DEBUGS("Avatar", "Inventory") << "Outfit link " << mUUID << " \"" << item->getName() << "\" is broken despite being regenerated" << LL_ENDL; + } + + LLAppearanceMgr::instance().updateAppearanceFromCOF( + mEnforceItemRestrictions , + mEnforceOrdering , + mPostUpdateFunc); + delete this; +} + + +struct LLFoundData +{ + LLFoundData() : + mAssetType(LLAssetType::AT_NONE), + mWearableType(LLWearableType::WT_INVALID), + mWearable(NULL) {} + + LLFoundData(const LLUUID& item_id, + const LLUUID& asset_id, + const std::string& name, + const LLAssetType::EType& asset_type, + const LLWearableType::EType& wearable_type, + const bool is_replacement = false + ) : + mItemID(item_id), + mAssetID(asset_id), + mName(name), + mAssetType(asset_type), + mWearableType(wearable_type), + mIsReplacement(is_replacement), + mWearable( NULL ) {} + + LLUUID mItemID; + LLUUID mAssetID; + std::string mName; + LLAssetType::EType mAssetType; + LLWearableType::EType mWearableType; + LLViewerWearable* mWearable; + bool mIsReplacement; +}; + + +class LLWearableHoldingPattern +{ + LOG_CLASS(LLWearableHoldingPattern); + +public: + LLWearableHoldingPattern(); + ~LLWearableHoldingPattern(); + + bool pollFetchCompletion(); + void onFetchCompletion(); + bool isFetchCompleted(); + bool isTimedOut(); + + void checkMissingWearables(); + bool pollMissingWearables(); + bool isMissingCompleted(); + void recoverMissingWearable(LLWearableType::EType type); + void clearCOFLinksForMissingWearables(); + + void onWearableAssetFetch(LLViewerWearable *wearable); + void onAllComplete(); + + typedef std::list found_list_t; + found_list_t& getFoundList(); + void eraseTypeToLink(LLWearableType::EType type); + void eraseTypeToRecover(LLWearableType::EType type); + void setObjItems(const LLInventoryModel::item_array_t& items); + void setGestItems(const LLInventoryModel::item_array_t& items); + bool isMostRecent(); + void handleLateArrivals(); + void resetTime(F32 timeout); + static S32 countActive() { return sActiveHoldingPatterns.size(); } + S32 index() { return mIndex; } + +private: + found_list_t mFoundList; + LLInventoryModel::item_array_t mObjItems; + LLInventoryModel::item_array_t mGestItems; + typedef std::set type_set_t; + type_set_t mTypesToRecover; + type_set_t mTypesToLink; + S32 mResolved; + LLTimer mWaitTime; + bool mFired; + typedef std::set type_set_hp; + static type_set_hp sActiveHoldingPatterns; + static S32 sNextIndex; + S32 mIndex; + bool mIsMostRecent; + std::set mLateArrivals; + bool mIsAllComplete; +}; + +LLWearableHoldingPattern::type_set_hp LLWearableHoldingPattern::sActiveHoldingPatterns; +S32 LLWearableHoldingPattern::sNextIndex = 0; + +LLWearableHoldingPattern::LLWearableHoldingPattern(): + mResolved(0), + mFired(false), + mIsMostRecent(true), + mIsAllComplete(false) +{ + if (countActive()>0) + { + LL_INFOS() << "Creating LLWearableHoldingPattern when " + << countActive() + << " other attempts are active." + << " Flagging others as invalid." + << LL_ENDL; + for (type_set_hp::iterator it = sActiveHoldingPatterns.begin(); + it != sActiveHoldingPatterns.end(); + ++it) + { + (*it)->mIsMostRecent = false; + } + + } + mIndex = sNextIndex++; + sActiveHoldingPatterns.insert(this); + LL_DEBUGS("Avatar") << "HP " << index() << " created" << LL_ENDL; + selfStartPhase("holding_pattern"); +} + +LLWearableHoldingPattern::~LLWearableHoldingPattern() +{ + sActiveHoldingPatterns.erase(this); + if (isMostRecent()) + { + selfStopPhase("holding_pattern"); + } + LL_DEBUGS("Avatar") << "HP " << index() << " deleted" << LL_ENDL; +} + +bool LLWearableHoldingPattern::isMostRecent() +{ + return mIsMostRecent; +} + +LLWearableHoldingPattern::found_list_t& LLWearableHoldingPattern::getFoundList() +{ + return mFoundList; +} + +void LLWearableHoldingPattern::eraseTypeToLink(LLWearableType::EType type) +{ + mTypesToLink.erase(type); +} + +void LLWearableHoldingPattern::eraseTypeToRecover(LLWearableType::EType type) +{ + mTypesToRecover.erase(type); +} + +void LLWearableHoldingPattern::setObjItems(const LLInventoryModel::item_array_t& items) +{ + mObjItems = items; +} + +void LLWearableHoldingPattern::setGestItems(const LLInventoryModel::item_array_t& items) +{ + mGestItems = items; +} + +bool LLWearableHoldingPattern::isFetchCompleted() +{ + return (mResolved >= (S32)getFoundList().size()); // have everything we were waiting for? +} + +bool LLWearableHoldingPattern::isTimedOut() +{ + return mWaitTime.hasExpired(); +} + +void LLWearableHoldingPattern::checkMissingWearables() +{ + if (!isMostRecent()) + { + // runway why don't we actually skip here? + LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; + } + + std::vector found_by_type(LLWearableType::WT_COUNT,0); + std::vector requested_by_type(LLWearableType::WT_COUNT,0); + for (found_list_t::iterator it = getFoundList().begin(); it != getFoundList().end(); ++it) + { + LLFoundData &data = *it; + if (data.mWearableType < LLWearableType::WT_COUNT) + requested_by_type[data.mWearableType]++; + if (data.mWearable) + found_by_type[data.mWearableType]++; + } + + for (S32 type = 0; type < LLWearableType::WT_COUNT; ++type) + { + if (requested_by_type[type] > found_by_type[type]) + { + LL_WARNS() << self_av_string() << "got fewer wearables than requested, type " << type << ": requested " << requested_by_type[type] << ", found " << found_by_type[type] << LL_ENDL; + } + if (found_by_type[type] > 0) + continue; + if ( + // If at least one wearable of certain types (pants/shirt/skirt) + // was requested but none was found, create a default asset as a replacement. + // In all other cases, don't do anything. + // For critical types (shape/hair/skin/eyes), this will keep the avatar as a cloud + // due to logic in LLVOAvatarSelf::getIsCloud(). + // For non-critical types (tatoo, socks, etc.) the wearable will just be missing. + (requested_by_type[type] > 0) && + ((type == LLWearableType::WT_PANTS) || (type == LLWearableType::WT_SHIRT) || (type == LLWearableType::WT_SKIRT))) + { + mTypesToRecover.insert(type); + mTypesToLink.insert(type); + recoverMissingWearable((LLWearableType::EType)type); + LL_WARNS() << self_av_string() << "need to replace " << type << LL_ENDL; + } + } + + resetTime(60.0F); + + if (isMostRecent()) + { + selfStartPhase("get_missing_wearables_2"); + } + if (!pollMissingWearables()) + { + doOnIdleRepeating(boost::bind(&LLWearableHoldingPattern::pollMissingWearables,this)); + } +} + +void LLWearableHoldingPattern::onAllComplete() +{ + if (isAgentAvatarValid()) + { + gAgentAvatarp->outputRezTiming("Agent wearables fetch complete"); + } + + if (!isMostRecent()) + { + // runway need to skip here? + LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; + } + + // Activate all gestures in this folder + if (mGestItems.size() > 0) + { + LL_DEBUGS("Avatar") << self_av_string() << "Activating " << mGestItems.size() << " gestures" << LL_ENDL; + + LLGestureMgr::instance().activateGestures(mGestItems); + + // Update the inventory item labels to reflect the fact + // they are active. + LLViewerInventoryCategory* catp = + gInventory.getCategory(LLAppearanceMgr::instance().getCOF()); + + if (catp) + { + gInventory.updateCategory(catp); + gInventory.notifyObservers(); + } + } + + if (isAgentAvatarValid()) + { + LL_DEBUGS("Avatar") << self_av_string() << "Updating " << mObjItems.size() << " attachments" << LL_ENDL; + LLAgentWearables::llvo_vec_t objects_to_remove; + LLAgentWearables::llvo_vec_t objects_to_retain; + LLInventoryModel::item_array_t items_to_add; + + LLAgentWearables::findAttachmentsAddRemoveInfo(mObjItems, + objects_to_remove, + objects_to_retain, + items_to_add); + + LL_DEBUGS("Avatar") << self_av_string() << "Removing " << objects_to_remove.size() + << " attachments" << LL_ENDL; + + // Here we remove the attachment pos overrides for *all* + // attachments, even those that are not being removed. This is + // needed to get joint positions all slammed down to their + // pre-attachment states. + gAgentAvatarp->clearAttachmentOverrides(); + + if (objects_to_remove.size() || items_to_add.size()) + { + LL_DEBUGS("Avatar") << "ATT will remove " << objects_to_remove.size() + << " and add " << items_to_add.size() << " items" << LL_ENDL; + } + + // Take off the attachments that will no longer be in the outfit. + LLAgentWearables::userRemoveMultipleAttachments(objects_to_remove); + + // Update wearables. + LL_INFOS("Avatar") << self_av_string() << "HP " << index() << " updating agent wearables with " + << mResolved << " wearable items " << LL_ENDL; + LLAppearanceMgr::instance().updateAgentWearables(this); + + // Restore attachment pos overrides for the attachments that + // are remaining in the outfit. + for (LLAgentWearables::llvo_vec_t::iterator it = objects_to_retain.begin(); + it != objects_to_retain.end(); + ++it) + { + LLViewerObject *objectp = *it; + if (!objectp->isAnimatedObject()) + { + gAgentAvatarp->addAttachmentOverridesForObject(objectp); + } + } + + // Add new attachments to match those requested. + LL_DEBUGS("Avatar") << self_av_string() << "Adding " << items_to_add.size() << " attachments" << LL_ENDL; + LLAgentWearables::userAttachMultipleAttachments(items_to_add); + } + + if (isFetchCompleted() && isMissingCompleted()) + { + // Only safe to delete if all wearable callbacks and all missing wearables completed. + delete this; + } + else + { + mIsAllComplete = true; + handleLateArrivals(); + } +} + +void LLWearableHoldingPattern::onFetchCompletion() +{ + if (isMostRecent()) + { + selfStopPhase("get_wearables_2"); + } + + if (!isMostRecent()) + { + // runway skip here? + LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; + } + + checkMissingWearables(); +} + +// Runs as an idle callback until all wearables are fetched (or we time out). +bool LLWearableHoldingPattern::pollFetchCompletion() +{ + if (!isMostRecent()) + { + // runway skip here? + LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; + } + + bool completed = isFetchCompleted(); + bool timed_out = isTimedOut(); + bool done = completed || timed_out; + + if (done) + { + LL_INFOS("Avatar") << self_av_string() << "HP " << index() << " polling, done status: " << completed << " timed out " << timed_out + << " elapsed " << mWaitTime.getElapsedTimeF32() << LL_ENDL; + + mFired = true; + + if (timed_out) + { + LL_WARNS() << self_av_string() << "Exceeded max wait time for wearables, updating appearance based on what has arrived" << LL_ENDL; + } + + onFetchCompletion(); + } + return done; +} + +void recovered_item_link_cb(const LLUUID& item_id, LLWearableType::EType type, LLViewerWearable *wearable, LLWearableHoldingPattern* holder) +{ + if (!holder->isMostRecent()) + { + LL_WARNS() << "HP " << holder->index() << " skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; + // runway skip here? + } + + LL_INFOS("Avatar") << "HP " << holder->index() << " recovered item link for type " << type << LL_ENDL; + holder->eraseTypeToLink(type); + // Add wearable to FoundData for actual wearing + LLViewerInventoryItem *item = gInventory.getItem(item_id); + LLViewerInventoryItem *linked_item = item ? item->getLinkedItem() : NULL; + + if (linked_item) + { + gInventory.addChangedMask(LLInventoryObserver::LABEL, linked_item->getUUID()); + + if (item) + { + LLFoundData found(linked_item->getUUID(), + linked_item->getAssetUUID(), + linked_item->getName(), + linked_item->getType(), + linked_item->isWearableType() ? linked_item->getWearableType() : LLWearableType::WT_INVALID, + true // is replacement + ); + found.mWearable = wearable; + holder->getFoundList().push_front(found); + } + else + { + LL_WARNS() << self_av_string() << "inventory link not found for recovered wearable" << LL_ENDL; + } + } + else + { + LL_WARNS() << self_av_string() << "HP " << holder->index() << " inventory link not found for recovered wearable" << LL_ENDL; + } +} + +void recovered_item_cb(const LLUUID& item_id, LLWearableType::EType type, LLViewerWearable *wearable, LLWearableHoldingPattern* holder) +{ + if (!holder->isMostRecent()) + { + // runway skip here? + LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; + } + + LL_DEBUGS("Avatar") << self_av_string() << "Recovered item for type " << type << LL_ENDL; + LLConstPointer itemp = gInventory.getItem(item_id); + wearable->setItemID(item_id); + holder->eraseTypeToRecover(type); + llassert(itemp); + if (itemp) + { + LLPointer cb = new LLBoostFuncInventoryCallback(boost::bind(recovered_item_link_cb,_1,type,wearable,holder)); + + link_inventory_object(LLAppearanceMgr::instance().getCOF(), itemp, cb); + } +} + +void LLWearableHoldingPattern::recoverMissingWearable(LLWearableType::EType type) +{ + if (!isMostRecent()) + { + // runway skip here? + LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; + } + + // Try to recover by replacing missing wearable with a new one. + LLNotificationsUtil::add("ReplacedMissingWearable"); + LL_DEBUGS("Avatar") << "Wearable of type '" << LLWearableType::getInstance()->getTypeName(type) + << "' could not be downloaded. Replaced inventory item with default wearable." << LL_ENDL; + LLViewerWearable* wearable = LLWearableList::instance().createNewWearable(type, gAgentAvatarp); + + // Add a new one in the lost and found folder. + const LLUUID lost_and_found_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); + LLPointer cb = new LLBoostFuncInventoryCallback(boost::bind(recovered_item_cb,_1,type,wearable,this)); + + create_inventory_wearable(gAgent.getID(), + gAgent.getSessionID(), + lost_and_found_id, + wearable->getTransactionID(), + wearable->getName(), + wearable->getDescription(), + wearable->getAssetType(), + wearable->getType(), + wearable->getPermissions().getMaskNextOwner(), + cb); +} + +bool LLWearableHoldingPattern::isMissingCompleted() +{ + return mTypesToLink.size()==0 && mTypesToRecover.size()==0; +} + +void LLWearableHoldingPattern::clearCOFLinksForMissingWearables() +{ + for (found_list_t::iterator it = getFoundList().begin(); it != getFoundList().end(); ++it) + { + LLFoundData &data = *it; + if ((data.mWearableType < LLWearableType::WT_COUNT) && (!data.mWearable)) + { + // Wearable link that was never resolved; remove links to it from COF + LL_INFOS("Avatar") << self_av_string() << "HP " << index() << " removing link for unresolved item " << data.mItemID.asString() << LL_ENDL; + LLAppearanceMgr::instance().removeCOFItemLinks(data.mItemID); + } + } +} + +bool LLWearableHoldingPattern::pollMissingWearables() +{ + if (!isMostRecent()) + { + // runway skip here? + LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; + } + + bool timed_out = isTimedOut(); + bool missing_completed = isMissingCompleted(); + bool done = timed_out || missing_completed; + + if (!done) + { + LL_INFOS("Avatar") << self_av_string() << "HP " << index() << " polling missing wearables, waiting for items " << mTypesToRecover.size() + << " links " << mTypesToLink.size() + << " wearables, timed out " << timed_out + << " elapsed " << mWaitTime.getElapsedTimeF32() + << " done " << done << LL_ENDL; + } + + if (done) + { + if (isMostRecent()) + { + selfStopPhase("get_missing_wearables_2"); + } + + gAgentAvatarp->debugWearablesLoaded(); + + // BAP - if we don't call clearCOFLinksForMissingWearables() + // here, we won't have to add the link back in later if the + // wearable arrives late. This is to avoid corruption of + // wearable ordering info. Also has the effect of making + // unworn item links visible in the COF under some + // circumstances. + + //clearCOFLinksForMissingWearables(); + onAllComplete(); + } + return done; +} + +// Handle wearables that arrived after the timeout period expired. +void LLWearableHoldingPattern::handleLateArrivals() +{ + // Only safe to run if we have previously finished the missing + // wearables and other processing - otherwise we could be in some + // intermediate state - but have not been superceded by a later + // outfit change request. + if (mLateArrivals.size() == 0) + { + // Nothing to process. + return; + } + if (!isMostRecent()) + { + LL_WARNS() << self_av_string() << "Late arrivals not handled - outfit change no longer valid" << LL_ENDL; + } + if (!mIsAllComplete) + { + LL_WARNS() << self_av_string() << "Late arrivals not handled - in middle of missing wearables processing" << LL_ENDL; + } + + LL_INFOS("Avatar") << self_av_string() << "HP " << index() << " need to handle " << mLateArrivals.size() << " late arriving wearables" << LL_ENDL; + + // Update mFoundList using late-arriving wearables. + std::set replaced_types; + for (LLWearableHoldingPattern::found_list_t::iterator iter = getFoundList().begin(); + iter != getFoundList().end(); ++iter) + { + LLFoundData& data = *iter; + for (std::set::iterator wear_it = mLateArrivals.begin(); + wear_it != mLateArrivals.end(); + ++wear_it) + { + LLViewerWearable *wearable = *wear_it; + + if(wearable->getAssetID() == data.mAssetID) + { + data.mWearable = wearable; + + replaced_types.insert(data.mWearableType); + + // BAP - if we didn't call + // clearCOFLinksForMissingWearables() earlier, we + // don't need to restore the link here. Fixes + // wearable ordering problems. + + // LLAppearanceMgr::instance().addCOFItemLink(data.mItemID,false); + + // BAP failing this means inventory or asset server + // are corrupted in a way we don't handle. + llassert((data.mWearableType < LLWearableType::WT_COUNT) && (wearable->getType() == data.mWearableType)); + break; + } + } + } + + // Remove COF links for any default wearables previously used to replace the late arrivals. + // All this pussyfooting around with a while loop and explicit + // iterator incrementing is to allow removing items from the list + // without clobbering the iterator we're using to navigate. + LLWearableHoldingPattern::found_list_t::iterator iter = getFoundList().begin(); + while (iter != getFoundList().end()) + { + LLFoundData& data = *iter; + + // If an item of this type has recently shown up, removed the corresponding replacement wearable from COF. + if (data.mWearable && data.mIsReplacement && + replaced_types.find(data.mWearableType) != replaced_types.end()) + { + LLAppearanceMgr::instance().removeCOFItemLinks(data.mItemID); + std::list::iterator clobber_ator = iter; + ++iter; + getFoundList().erase(clobber_ator); + } + else + { + ++iter; + } + } + + // Clear contents of late arrivals. + mLateArrivals.clear(); + + // Update appearance based on mFoundList + LLAppearanceMgr::instance().updateAgentWearables(this); +} + +void LLWearableHoldingPattern::resetTime(F32 timeout) +{ + mWaitTime.reset(); + mWaitTime.setTimerExpirySec(timeout); +} + +void LLWearableHoldingPattern::onWearableAssetFetch(LLViewerWearable *wearable) +{ + if (!isMostRecent()) + { + LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; + } + + mResolved += 1; // just counting callbacks, not successes. + LL_DEBUGS("Avatar") << self_av_string() << "HP " << index() << " resolved " << mResolved << "/" << getFoundList().size() << LL_ENDL; + if (!wearable) + { + LL_WARNS() << self_av_string() << "no wearable found" << LL_ENDL; + } + + if (mFired) + { + LL_WARNS() << self_av_string() << "called after holder fired" << LL_ENDL; + if (wearable) + { + mLateArrivals.insert(wearable); + if (mIsAllComplete) + { + handleLateArrivals(); + } + } + return; + } + + if (!wearable) + { + return; + } + + U32 use_count = 0; + for (LLWearableHoldingPattern::found_list_t::iterator iter = getFoundList().begin(); + iter != getFoundList().end(); ++iter) + { + LLFoundData& data = *iter; + if (wearable->getAssetID() == data.mAssetID) + { + // Failing this means inventory or asset server are corrupted in a way we don't handle. + if ((data.mWearableType >= LLWearableType::WT_COUNT) || (wearable->getType() != data.mWearableType)) + { + LL_WARNS() << self_av_string() << "recovered wearable but type invalid. inventory wearable type: " << data.mWearableType << " asset wearable type: " << wearable->getType() << LL_ENDL; + break; + } + + if (use_count == 0) + { + data.mWearable = wearable; + use_count++; + } + else + { + LLViewerInventoryItem* wearable_item = gInventory.getItem(data.mItemID); + if (wearable_item && wearable_item->isFinished() && wearable_item->getPermissions().allowModifyBy(gAgentID)) + { + // We can't edit and do some other interactions with same asset twice, copy it + // Note: can't update incomplete items. Usually attached from previous viewer build, but + // consider adding fetch and completion callback + LLViewerWearable* new_wearable = LLWearableList::instance().createCopy(wearable, wearable->getName()); + data.mWearable = new_wearable; + data.mAssetID = new_wearable->getAssetID(); + + // Update existing inventory item + wearable_item->setAssetUUID(new_wearable->getAssetID()); + wearable_item->setTransactionID(new_wearable->getTransactionID()); + gInventory.updateItem(wearable_item, LLInventoryObserver::INTERNAL); + wearable_item->updateServer(false); + + use_count++; + } + else + { + // Note: technically a bug, LLViewerWearable can identify only one item id at a time, + // yet we are tying it to multiple items here. + // LLViewerWearable need to support more then one item. + LL_WARNS() << "Same LLViewerWearable is used by multiple items! " << wearable->getAssetID() << LL_ENDL; + data.mWearable = wearable; + } + } + } + } + + if (use_count > 1) + { + LL_WARNS() << "Copying wearable, multiple asset id uses! " << wearable->getAssetID() << LL_ENDL; + gInventory.notifyObservers(); + } +} + +static void onWearableAssetFetch(LLViewerWearable* wearable, void* data) +{ + LLWearableHoldingPattern* holder = (LLWearableHoldingPattern*)data; + holder->onWearableAssetFetch(wearable); +} + + +static void removeDuplicateItems(LLInventoryModel::item_array_t& items) +{ + LLInventoryModel::item_array_t new_items; + std::set items_seen; + std::deque tmp_list; + // Traverse from the front and keep the first of each item + // encountered, so we actually keep the *last* of each duplicate + // item. This is needed to give the right priority when adding + // duplicate items to an existing outfit. + for (S32 i=items.size()-1; i>=0; i--) + { + LLViewerInventoryItem *item = items.at(i); + LLUUID item_id = item->getLinkedUUID(); + if (items_seen.find(item_id)!=items_seen.end()) + continue; + items_seen.insert(item_id); + tmp_list.push_front(item); + } + for (std::deque::iterator it = tmp_list.begin(); + it != tmp_list.end(); + ++it) + { + new_items.push_back(*it); + } + items = new_items; +} + +//========================================================================= + +const std::string LLAppearanceMgr::sExpectedTextureName = "OutfitPreview"; + +const LLUUID LLAppearanceMgr::getCOF() const +{ + return mCOFID; +} + +S32 LLAppearanceMgr::getCOFVersion() const +{ + LLViewerInventoryCategory *cof = gInventory.getCategory(getCOF()); + if (cof) + { + return cof->getVersion(); + } + else + { + return LLViewerInventoryCategory::VERSION_UNKNOWN; + } +} + +void LLAppearanceMgr::initCOFID() +{ + mCOFID = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); +} + +const LLViewerInventoryItem* LLAppearanceMgr::getBaseOutfitLink() +{ + const LLUUID& current_outfit_cat = getCOF(); + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + // Can't search on FT_OUTFIT since links to categories return FT_CATEGORY for type since they don't + // return preferred type. + LLIsType is_category( LLAssetType::AT_CATEGORY ); + gInventory.collectDescendentsIf(current_outfit_cat, + cat_array, + item_array, + false, + is_category); + for (LLInventoryModel::item_array_t::const_iterator iter = item_array.begin(); + iter != item_array.end(); + iter++) + { + const LLViewerInventoryItem *item = (*iter); + const LLViewerInventoryCategory *cat = item->getLinkedCategory(); + if (cat && cat->getPreferredType() == LLFolderType::FT_OUTFIT) + { + const LLUUID parent_id = cat->getParentUUID(); + LLViewerInventoryCategory* parent_cat = gInventory.getCategory(parent_id); + // if base outfit moved to trash it means that we don't have base outfit + if (parent_cat != NULL && parent_cat->getPreferredType() == LLFolderType::FT_TRASH) + { + return NULL; + } + return item; + } + } + return NULL; +} + +bool LLAppearanceMgr::getBaseOutfitName(std::string& name) +{ + const LLViewerInventoryItem* outfit_link = getBaseOutfitLink(); + if(outfit_link) + { + const LLViewerInventoryCategory *cat = outfit_link->getLinkedCategory(); + if (cat) + { + name = cat->getName(); + return true; + } + } + return false; +} + +const LLUUID LLAppearanceMgr::getBaseOutfitUUID() +{ + const LLViewerInventoryItem* outfit_link = getBaseOutfitLink(); + if (!outfit_link || !outfit_link->getIsLinkType()) return LLUUID::null; + + const LLViewerInventoryCategory* outfit_cat = outfit_link->getLinkedCategory(); + if (!outfit_cat) return LLUUID::null; + + if (outfit_cat->getPreferredType() != LLFolderType::FT_OUTFIT) + { + LL_WARNS() << "Expected outfit type:" << LLFolderType::FT_OUTFIT << " but got type:" << outfit_cat->getType() << " for folder name:" << outfit_cat->getName() << LL_ENDL; + return LLUUID::null; + } + + return outfit_cat->getUUID(); +} + +void wear_on_avatar_cb(const LLUUID& inv_item, bool do_replace = false) +{ + if (inv_item.isNull()) + return; + + LLViewerInventoryItem *item = gInventory.getItem(inv_item); + if (item) + { + LLAppearanceMgr::instance().wearItemOnAvatar(inv_item, true, do_replace); + } +} + +void LLAppearanceMgr::wearItemsOnAvatar(const uuid_vec_t& item_ids_to_wear, + bool do_update, + bool replace, + LLPointer cb) +{ + LL_DEBUGS("UIUsage") << "wearItemsOnAvatar" << LL_ENDL; + LLUIUsage::instance().logCommand("Avatar.WearItem"); + + bool first = true; + + LLInventoryObject::const_object_list_t items_to_link; + + for (uuid_vec_t::const_iterator it = item_ids_to_wear.begin(); + it != item_ids_to_wear.end(); + ++it) + { + replace = first && replace; + first = false; + + const LLUUID& item_id_to_wear = *it; + + if (item_id_to_wear.isNull()) + { + LL_DEBUGS("Avatar") << "null id " << item_id_to_wear << LL_ENDL; + continue; + } + + LLViewerInventoryItem* item_to_wear = gInventory.getItem(item_id_to_wear); + if (!item_to_wear) + { + LL_DEBUGS("Avatar") << "inventory item not found for id " << item_id_to_wear << LL_ENDL; + continue; + } + + if (gInventory.isObjectDescendentOf(item_to_wear->getUUID(), gInventory.getLibraryRootFolderID())) + { + LL_DEBUGS("Avatar") << "inventory item in library, will copy and wear " + << item_to_wear->getName() << " id " << item_id_to_wear << LL_ENDL; + LLPointer cb = new LLBoostFuncInventoryCallback(boost::bind(wear_on_avatar_cb,_1,replace)); + copy_inventory_item(gAgent.getID(), item_to_wear->getPermissions().getOwner(), + item_to_wear->getUUID(), LLUUID::null, std::string(), cb); + continue; + } + else if (!gInventory.isObjectDescendentOf(item_to_wear->getUUID(), gInventory.getRootFolderID())) + { + // not in library and not in agent's inventory + LL_DEBUGS("Avatar") << "inventory item not in user inventory or library, skipping " + << item_to_wear->getName() << " id " << item_id_to_wear << LL_ENDL; + continue; + } + else if (gInventory.isObjectDescendentOf(item_to_wear->getUUID(), gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH))) + { + LLNotificationsUtil::add("CannotWearTrash"); + LL_DEBUGS("Avatar") << "inventory item is in trash, skipping " + << item_to_wear->getName() << " id " << item_id_to_wear << LL_ENDL; + continue; + } + else if (isLinkedInCOF(item_to_wear->getUUID())) // EXT-84911 + { + LL_DEBUGS("Avatar") << "inventory item is already in COF, skipping " + << item_to_wear->getName() << " id " << item_id_to_wear << LL_ENDL; + continue; + } + + switch (item_to_wear->getType()) + { + case LLAssetType::AT_CLOTHING: + { + if (gAgentWearables.areWearablesLoaded()) + { + if (!cb && do_update) + { + cb = new LLUpdateAppearanceAndEditWearableOnDestroy(item_id_to_wear); + } + LLWearableType::EType type = item_to_wear->getWearableType(); + S32 wearable_count = gAgentWearables.getWearableCount(type); + if ((replace && wearable_count != 0) || !gAgentWearables.canAddWearable(type)) + { + LLUUID item_id = gAgentWearables.getWearableItemID(item_to_wear->getWearableType(), + wearable_count-1); + removeCOFItemLinks(item_id, cb); + } + + items_to_link.push_back(item_to_wear); + } + } + break; + + case LLAssetType::AT_BODYPART: + { + // TODO: investigate wearables may not be loaded at this point EXT-8231 + + // Remove the existing wearables of the same type. + // Remove existing body parts anyway because we must not be able to wear e.g. two skins. + removeCOFLinksOfType(item_to_wear->getWearableType()); + if (!cb && do_update) + { + cb = new LLUpdateAppearanceAndEditWearableOnDestroy(item_id_to_wear); + } + items_to_link.push_back(item_to_wear); + } + break; + + case LLAssetType::AT_OBJECT: + { + rez_attachment(item_to_wear, NULL, replace); + } + break; + + default: continue; + } + } + + // Batch up COF link creation - more efficient if using AIS. + if (items_to_link.size()) + { + link_inventory_array(getCOF(), items_to_link, cb); + } +} + +void LLAppearanceMgr::wearItemOnAvatar(const LLUUID& item_id_to_wear, + bool do_update, + bool replace, + LLPointer cb) +{ + uuid_vec_t ids; + ids.push_back(item_id_to_wear); + wearItemsOnAvatar(ids, do_update, replace, cb); +} + +// Update appearance from outfit folder. +void LLAppearanceMgr::changeOutfit(bool proceed, const LLUUID& category, bool append) +{ + if (!proceed) + return; + LLAppearanceMgr::instance().updateCOF(category,append); +} + +void LLAppearanceMgr::replaceCurrentOutfit(const LLUUID& new_outfit) +{ + LLViewerInventoryCategory* cat = gInventory.getCategory(new_outfit); + wearInventoryCategory(cat, false, false); +} + +// Remove existing photo link from outfit folder. +void LLAppearanceMgr::removeOutfitPhoto(const LLUUID& outfit_id) +{ + LLInventoryModel::cat_array_t sub_cat_array; + LLInventoryModel::item_array_t outfit_item_array; + gInventory.collectDescendents( + outfit_id, + sub_cat_array, + outfit_item_array, + LLInventoryModel::EXCLUDE_TRASH); + for (LLViewerInventoryItem* outfit_item : outfit_item_array) + { + LLViewerInventoryItem* linked_item = outfit_item->getLinkedItem(); + if (linked_item != NULL) + { + if (linked_item->getActualType() == LLAssetType::AT_TEXTURE) + { + gInventory.removeItem(outfit_item->getUUID()); + } + } + else if (outfit_item->getActualType() == LLAssetType::AT_TEXTURE) + { + gInventory.removeItem(outfit_item->getUUID()); + } + } +} + +// Open outfit renaming dialog. +void LLAppearanceMgr::renameOutfit(const LLUUID& outfit_id) +{ + LLViewerInventoryCategory* cat = gInventory.getCategory(outfit_id); + if (!cat) + { + return; + } + + LLSD args; + args["NAME"] = cat->getName(); + + LLSD payload; + payload["cat_id"] = outfit_id; + + LLNotificationsUtil::add("RenameOutfit", args, payload, boost::bind(onOutfitRename, _1, _2)); +} + +// User typed new outfit name. +// static +void LLAppearanceMgr::onOutfitRename(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) return; // canceled + + std::string outfit_name = response["new_name"].asString(); + LLStringUtil::trim(outfit_name); + if (!outfit_name.empty()) + { + LLUUID cat_id = notification["payload"]["cat_id"].asUUID(); + rename_category(&gInventory, cat_id, outfit_name); + } +} + +void LLAppearanceMgr::setOutfitLocked(bool locked) +{ + if (mOutfitLocked == locked) + { + return; + } + + mOutfitLocked = locked; + if (locked) + { + mUnlockOutfitTimer->reset(); + mUnlockOutfitTimer->start(); + } + else + { + mUnlockOutfitTimer->stop(); + } + + LLOutfitObserver::instance().notifyOutfitLockChanged(); +} + +void LLAppearanceMgr::addCategoryToCurrentOutfit(const LLUUID& cat_id) +{ + LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); + wearInventoryCategory(cat, false, true); +} + +void LLAppearanceMgr::takeOffOutfit(const LLUUID& cat_id) +{ + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLFindWearablesEx collector(/*is_worn=*/ true, /*include_body_parts=*/ false); + + gInventory.collectDescendentsIf(cat_id, cats, items, false, collector); + + LLInventoryModel::item_array_t::const_iterator it = items.begin(); + const LLInventoryModel::item_array_t::const_iterator it_end = items.end(); + uuid_vec_t uuids_to_remove; + for( ; it_end != it; ++it) + { + LLViewerInventoryItem* item = *it; + uuids_to_remove.push_back(item->getUUID()); + } + removeItemsFromAvatar(uuids_to_remove); + + // deactivate all gestures in the outfit folder + LLInventoryModel::item_array_t gest_items; + getDescendentsOfAssetType(cat_id, gest_items, LLAssetType::AT_GESTURE); + for(S32 i = 0; i < gest_items.size(); ++i) + { + LLViewerInventoryItem *gest_item = gest_items[i]; + if ( LLGestureMgr::instance().isGestureActive( gest_item->getLinkedUUID()) ) + { + LLGestureMgr::instance().deactivateGesture( gest_item->getLinkedUUID() ); + } + } +} + +// Create a copy of src_id + contents as a subfolder of dst_id. +void LLAppearanceMgr::shallowCopyCategory(const LLUUID& src_id, const LLUUID& dst_id, + LLPointer cb) +{ + LLInventoryCategory *src_cat = gInventory.getCategory(src_id); + if (!src_cat) + { + LL_WARNS() << "folder not found for src " << src_id.asString() << LL_ENDL; + return; + } + LL_INFOS() << "starting, src_id " << src_id << " name " << src_cat->getName() << " dst_id " << dst_id << LL_ENDL; + LLUUID parent_id = dst_id; + if(parent_id.isNull()) + { + parent_id = gInventory.getRootFolderID(); + } + gInventory.createNewCategory( + parent_id, + LLFolderType::FT_NONE, + src_cat->getName(), + [src_id, cb](const LLUUID &new_id) + { + LLAppearanceMgr::getInstance()->shallowCopyCategoryContents(src_id, new_id, cb); + + gInventory.notifyObservers(); + }, + src_cat->getThumbnailUUID() + ); +} + +void LLAppearanceMgr::slamCategoryLinks(const LLUUID& src_id, const LLUUID& dst_id, + bool include_folder_links, LLPointer cb) +{ + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + LLSD contents = LLSD::emptyArray(); + gInventory.getDirectDescendentsOf(src_id, cats, items); + if (!cats || !items) + { + // NULL means the call failed -- cats/items map doesn't exist (note: this does NOT mean + // that the cat just doesn't have any items or subfolders). + LLViewerInventoryCategory* category = gInventory.getCategory(src_id); + if (category) + { + LL_WARNS() << "Category '" << category->getName() << "' descendents corrupted, linking content failed." << LL_ENDL; + } + else + { + LL_WARNS() << "Category could not be retrieved, linking content failed." << LL_ENDL; + } + llassert(cats != NULL && items != NULL); + + return; + } + + LL_INFOS() << "copying " << items->size() << " items" << LL_ENDL; + for (LLInventoryModel::item_array_t::const_iterator iter = items->begin(); + iter != items->end(); + ++iter) + { + const LLViewerInventoryItem* item = (*iter); + switch (item->getActualType()) + { + case LLAssetType::AT_LINK: + { + LL_DEBUGS("Avatar") << "linking inventory item " << item->getName() << LL_ENDL; + //getActualDescription() is used for a new description + //to propagate ordering information saved in descriptions of links + LLSD item_contents; + item_contents["name"] = item->getName(); + item_contents["desc"] = item->getActualDescription(); + item_contents["linked_id"] = item->getLinkedUUID(); + item_contents["type"] = LLAssetType::AT_LINK; + contents.append(item_contents); + break; + } + case LLAssetType::AT_LINK_FOLDER: + { + LLViewerInventoryCategory *catp = item->getLinkedCategory(); + if (catp && include_folder_links) + { + LL_DEBUGS("Avatar") << "linking inventory folder " << item->getName() << LL_ENDL; + LLSD base_contents; + base_contents["name"] = catp->getName(); + base_contents["desc"] = ""; // categories don't have descriptions. + base_contents["linked_id"] = catp->getLinkedUUID(); + base_contents["type"] = LLAssetType::AT_LINK_FOLDER; + contents.append(base_contents); + } + break; + } + default: + { + // Linux refuses to compile unless all possible enums are handled. Really, Linux? + break; + } + } + } + slam_inventory_folder(dst_id, contents, cb); +} +// Copy contents of src_id to dst_id. +void LLAppearanceMgr::shallowCopyCategoryContents(const LLUUID& src_id, const LLUUID& dst_id, + LLPointer cb) +{ + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(src_id, cats, items); + LL_INFOS() << "copying " << items->size() << " items" << LL_ENDL; + LLInventoryObject::const_object_list_t link_array; + for (LLInventoryModel::item_array_t::const_iterator iter = items->begin(); + iter != items->end(); + ++iter) + { + const LLViewerInventoryItem* item = (*iter); + switch (item->getActualType()) + { + case LLAssetType::AT_LINK: + { + LL_DEBUGS("Avatar") << "linking inventory item " << item->getName() << LL_ENDL; + link_array.push_back(LLConstPointer(item)); + break; + } + case LLAssetType::AT_LINK_FOLDER: + { + LLViewerInventoryCategory *catp = item->getLinkedCategory(); + // Skip copying outfit links. + if (catp && catp->getPreferredType() != LLFolderType::FT_OUTFIT) + { + LL_DEBUGS("Avatar") << "linking inventory folder " << item->getName() << LL_ENDL; + link_array.push_back(LLConstPointer(item)); + } + break; + } + case LLAssetType::AT_CLOTHING: + case LLAssetType::AT_OBJECT: + case LLAssetType::AT_BODYPART: + case LLAssetType::AT_GESTURE: + { + LL_DEBUGS("Avatar") << "copying inventory item " << item->getName() << LL_ENDL; + copy_inventory_item(gAgent.getID(), + item->getPermissions().getOwner(), + item->getUUID(), + dst_id, + item->getName(), + cb); + break; + } + default: + // Ignore non-outfit asset types + break; + } + } + if (!link_array.empty()) + { + link_inventory_array(dst_id, link_array, cb); + } +} + +bool LLAppearanceMgr::getCanMakeFolderIntoOutfit(const LLUUID& folder_id) +{ + // These are the wearable items that are required for considering this + // folder as containing a complete outfit. + U32 required_wearables = 0; + required_wearables |= 1LL << LLWearableType::WT_SHAPE; + required_wearables |= 1LL << LLWearableType::WT_SKIN; + required_wearables |= 1LL << LLWearableType::WT_HAIR; + required_wearables |= 1LL << LLWearableType::WT_EYES; + + // These are the wearables that the folder actually contains. + U32 folder_wearables = 0; + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(folder_id, cats, items); + for (LLInventoryModel::item_array_t::const_iterator iter = items->begin(); + iter != items->end(); + ++iter) + { + const LLViewerInventoryItem* item = (*iter); + if (item->isWearableType()) + { + const LLWearableType::EType wearable_type = item->getWearableType(); + folder_wearables |= 1LL << wearable_type; + } + } + + // If the folder contains the required wearables, return true. + return ((required_wearables & folder_wearables) == required_wearables); +} + +bool LLAppearanceMgr::getCanRemoveOutfit(const LLUUID& outfit_cat_id) +{ + // Disallow removing the base outfit. + if (outfit_cat_id == getBaseOutfitUUID()) + { + return false; + } + + // Check if the outfit folder itself is removable. + if (!get_is_category_removable(&gInventory, outfit_cat_id)) + { + return false; + } + + // Check for the folder's non-removable descendants. + LLFindNonRemovableObjects filter_non_removable; + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + gInventory.collectDescendentsIf(outfit_cat_id, cats, items, false, filter_non_removable); + if (!cats.empty() || !items.empty()) + { + return false; + } + + return true; +} + +// static +bool LLAppearanceMgr::getCanRemoveFromCOF(const LLUUID& outfit_cat_id) +{ + if (gAgentWearables.isCOFChangeInProgress()) + { + return false; + } + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLFindWearablesEx is_worn(/*is_worn=*/ true, /*include_body_parts=*/ false); + gInventory.collectDescendentsIf(outfit_cat_id, + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + is_worn); + return items.size() > 0; +} + +// static +bool LLAppearanceMgr::getCanAddToCOF(const LLUUID& outfit_cat_id) +{ + if (gAgentWearables.isCOFChangeInProgress()) + { + return false; + } + + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLFindWearablesEx not_worn(/*is_worn=*/ false, /*include_body_parts=*/ false); + gInventory.collectDescendentsIf(outfit_cat_id, + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + not_worn); + + return items.size() > 0; +} + +bool LLAppearanceMgr::getCanReplaceCOF(const LLUUID& outfit_cat_id) +{ + // Don't allow wearing anything while we're changing appearance. + if (gAgentWearables.isCOFChangeInProgress()) + { + return false; + } + + // Check whether it's the base outfit. + if (outfit_cat_id.isNull()) + { + return false; + } + + // Check whether the outfit contains any wearables + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLFindWearables is_wearable; + gInventory.collectDescendentsIf(outfit_cat_id, + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + is_wearable); + + return items.size() > 0; +} + +// Moved from LLWearableList::ContextMenu for wider utility. +bool LLAppearanceMgr::canAddWearables(const uuid_vec_t& item_ids) const +{ + // TODO: investigate wearables may not be loaded at this point EXT-8231 + + U32 n_objects = 0; + U32 n_clothes = 0; + + // Count given clothes (by wearable type) and objects. + for (uuid_vec_t::const_iterator it = item_ids.begin(); it != item_ids.end(); ++it) + { + const LLViewerInventoryItem* item = gInventory.getItem(*it); + if (!item) + { + return false; + } + + if (item->getType() == LLAssetType::AT_OBJECT) + { + ++n_objects; + } + else if (item->getType() == LLAssetType::AT_CLOTHING) + { + ++n_clothes; + } + else if (item->getType() == LLAssetType::AT_BODYPART || item->getType() == LLAssetType::AT_GESTURE) + { + return isAgentAvatarValid(); + } + else + { + LL_WARNS() << "Unexpected wearable type" << LL_ENDL; + return false; + } + } + + // Check whether we can add all the objects. + if (!isAgentAvatarValid() || !gAgentAvatarp->canAttachMoreObjects(n_objects)) + { + return false; + } + + // Check whether we can add all the clothes. + U32 sum_clothes = n_clothes + gAgentWearables.getClothingLayerCount(); + return sum_clothes <= LLAgentWearables::MAX_CLOTHING_LAYERS; +} + +void LLAppearanceMgr::purgeBaseOutfitLink(const LLUUID& category, LLPointer cb) +{ + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + gInventory.collectDescendents(category, cats, items, + LLInventoryModel::EXCLUDE_TRASH); + for (S32 i = 0; i < items.size(); ++i) + { + LLViewerInventoryItem *item = items.at(i); + if (item->getActualType() != LLAssetType::AT_LINK_FOLDER) + continue; + LLViewerInventoryCategory* catp = item->getLinkedCategory(); + if(catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT) + { + remove_inventory_item(item->getUUID(), cb); + } + } +} + +// Keep the last N wearables of each type. For viewer 2.0, N is 1 for +// both body parts and clothing items. +void LLAppearanceMgr::filterWearableItems( + LLInventoryModel::item_array_t& items, S32 max_per_type, S32 max_total) +{ + // Restrict by max total items first. + if ((max_total > 0) && (items.size() > max_total)) + { + LLInventoryModel::item_array_t items_to_keep; + for (S32 i=0; i 0) + { + // Divvy items into arrays by wearable type. + std::vector items_by_type(LLWearableType::WT_COUNT); + divvyWearablesByType(items, items_by_type); + + // rebuild items list, retaining the last max_per_type of each array + items.clear(); + for (S32 i=0; igetName() : "[UNKNOWN]") << "'" << LL_ENDL; + + const LLUUID cof = getCOF(); + + // Deactivate currently active gestures in the COF, if replacing outfit + if (!append) + { + LLInventoryModel::item_array_t gest_items; + getDescendentsOfAssetType(cof, gest_items, LLAssetType::AT_GESTURE); + for(S32 i = 0; i < gest_items.size(); ++i) + { + LLViewerInventoryItem *gest_item = gest_items.at(i); + if ( LLGestureMgr::instance().isGestureActive( gest_item->getLinkedUUID()) ) + { + LLGestureMgr::instance().deactivateGesture( gest_item->getLinkedUUID() ); + } + } + } + + // Collect and filter descendents to determine new COF contents. + + // - Body parts: always include COF contents as a fallback in case any + // required parts are missing. + // Preserve body parts from COF if appending. + LLInventoryModel::item_array_t body_items; + getDescendentsOfAssetType(cof, body_items, LLAssetType::AT_BODYPART); + getDescendentsOfAssetType(category, body_items, LLAssetType::AT_BODYPART); + if (append) + reverse(body_items.begin(), body_items.end()); + // Reduce body items to max of one per type. + removeDuplicateItems(body_items); + filterWearableItems(body_items, 1, 0); + + // - Wearables: include COF contents only if appending. + LLInventoryModel::item_array_t wear_items; + if (append) + getDescendentsOfAssetType(cof, wear_items, LLAssetType::AT_CLOTHING); + getDescendentsOfAssetType(category, wear_items, LLAssetType::AT_CLOTHING); + // Reduce wearables to max of one per type. + removeDuplicateItems(wear_items); + filterWearableItems(wear_items, 0, LLAgentWearables::MAX_CLOTHING_LAYERS); + + // - Attachments: include COF contents only if appending. + LLInventoryModel::item_array_t obj_items; + if (append) + getDescendentsOfAssetType(cof, obj_items, LLAssetType::AT_OBJECT); + getDescendentsOfAssetType(category, obj_items, LLAssetType::AT_OBJECT); + removeDuplicateItems(obj_items); + + // - Gestures: include COF contents only if appending. + LLInventoryModel::item_array_t gest_items; + if (append) + getDescendentsOfAssetType(cof, gest_items, LLAssetType::AT_GESTURE); + getDescendentsOfAssetType(category, gest_items, LLAssetType::AT_GESTURE); + removeDuplicateItems(gest_items); + + // Create links to new COF contents. + LLInventoryModel::item_array_t all_items; + std::copy(body_items.begin(), body_items.end(), std::back_inserter(all_items)); + std::copy(wear_items.begin(), wear_items.end(), std::back_inserter(all_items)); + std::copy(obj_items.begin(), obj_items.end(), std::back_inserter(all_items)); + std::copy(gest_items.begin(), gest_items.end(), std::back_inserter(all_items)); + + // Find any wearables that need description set to enforce ordering. + desc_map_t desc_map; + getWearableOrderingDescUpdates(wear_items, desc_map); + + // Will link all the above items. + // link_waiter enforce flags are false because we've already fixed everything up in updateCOF(). + LLPointer link_waiter = new LLUpdateAppearanceOnDestroy(false,false); + LLSD contents = LLSD::emptyArray(); + + for (LLInventoryModel::item_array_t::const_iterator it = all_items.begin(); + it != all_items.end(); ++it) + { + LLSD item_contents; + LLInventoryItem *item = *it; + + std::string desc; + desc_map_t::const_iterator desc_iter = desc_map.find(item->getUUID()); + if (desc_iter != desc_map.end()) + { + desc = desc_iter->second; + LL_DEBUGS("Avatar") << item->getName() << " overriding desc to: " << desc + << " (was: " << item->getActualDescription() << ")" << LL_ENDL; + } + else + { + desc = item->getActualDescription(); + } + + item_contents["name"] = item->getName(); + item_contents["desc"] = desc; + item_contents["linked_id"] = item->getLinkedUUID(); + item_contents["type"] = LLAssetType::AT_LINK; + contents.append(item_contents); + } + const LLUUID& base_id = append ? getBaseOutfitUUID() : category; + LLViewerInventoryCategory *base_cat = gInventory.getCategory(base_id); + if (base_cat && (base_cat->getPreferredType() == LLFolderType::FT_OUTFIT)) + { + LLSD base_contents; + base_contents["name"] = base_cat->getName(); + base_contents["desc"] = ""; + base_contents["linked_id"] = base_cat->getLinkedUUID(); + base_contents["type"] = LLAssetType::AT_LINK_FOLDER; + contents.append(base_contents); + } + if (gSavedSettings.getBOOL("DebugAvatarAppearanceMessage")) + { + dump_sequential_xml(gAgentAvatarp->getFullname() + "_slam_request", contents); + } + slam_inventory_folder(getCOF(), contents, link_waiter); + + LL_DEBUGS("Avatar") << self_av_string() << "waiting for LLUpdateAppearanceOnDestroy" << LL_ENDL; +} + +void LLAppearanceMgr::updatePanelOutfitName(const std::string& name) +{ + LLSidepanelAppearance* panel_appearance = + dynamic_cast(LLFloaterSidePanelContainer::getPanel("appearance")); + if (panel_appearance) + { + panel_appearance->refreshCurrentOutfitName(name); + } +} + +void LLAppearanceMgr::createBaseOutfitLink(const LLUUID& category, LLPointer link_waiter) +{ + const LLUUID cof = getCOF(); + LLViewerInventoryCategory* catp = gInventory.getCategory(category); + std::string new_outfit_name = ""; + + purgeBaseOutfitLink(cof, link_waiter); + + if (catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT) + { + link_inventory_object(cof, catp, link_waiter); + new_outfit_name = catp->getName(); + } + + updatePanelOutfitName(new_outfit_name); +} + +void LLAppearanceMgr::updateAgentWearables(LLWearableHoldingPattern* holder) +{ + LL_DEBUGS("Avatar") << "updateAgentWearables()" << LL_ENDL; + LLInventoryItem::item_array_t items; + std::vector< LLViewerWearable* > wearables; + wearables.reserve(32); + + // For each wearable type, find the wearables of that type. + for( S32 i = 0; i < LLWearableType::WT_COUNT; i++ ) + { + for (LLWearableHoldingPattern::found_list_t::iterator iter = holder->getFoundList().begin(); + iter != holder->getFoundList().end(); ++iter) + { + LLFoundData& data = *iter; + LLViewerWearable* wearable = data.mWearable; + if( wearable && ((S32)wearable->getType() == i) ) + { + LLViewerInventoryItem* item = (LLViewerInventoryItem*)gInventory.getItem(data.mItemID); + if( item && (item->getAssetUUID() == wearable->getAssetID()) ) + { + items.push_back(item); + wearables.push_back(wearable); + } + } + } + } + + if(wearables.size() > 0) + { + gAgentWearables.setWearableOutfit(items, wearables); + } +} + +S32 LLAppearanceMgr::countActiveHoldingPatterns() +{ + return LLWearableHoldingPattern::countActive(); +} + +static void remove_non_link_items(LLInventoryModel::item_array_t &items) +{ + LLInventoryModel::item_array_t pruned_items; + for (LLInventoryModel::item_array_t::const_iterator iter = items.begin(); + iter != items.end(); + ++iter) + { + const LLViewerInventoryItem *item = (*iter); + if (item && item->getIsLinkType()) + { + pruned_items.push_back((*iter)); + } + } + items = pruned_items; +} + +//a predicate for sorting inventory items by actual descriptions +bool sort_by_actual_description(const LLInventoryItem* item1, const LLInventoryItem* item2) +{ + if (!item1 || !item2) + { + LL_WARNS() << "either item1 or item2 is NULL" << LL_ENDL; + return true; + } + + return item1->getActualDescription() < item2->getActualDescription(); +} + +void item_array_diff(LLInventoryModel::item_array_t& full_list, + LLInventoryModel::item_array_t& keep_list, + LLInventoryModel::item_array_t& kill_list) + +{ + for (LLInventoryModel::item_array_t::iterator it = full_list.begin(); + it != full_list.end(); + ++it) + { + LLViewerInventoryItem *item = *it; + if (std::find(keep_list.begin(), keep_list.end(), item) == keep_list.end()) + { + kill_list.push_back(item); + } + } +} + +S32 LLAppearanceMgr::findExcessOrDuplicateItems(const LLUUID& cat_id, + LLAssetType::EType type, + S32 max_items_per_type, + S32 max_items_total, + LLInventoryObject::object_list_t& items_to_kill) +{ + S32 to_kill_count = 0; + + LLInventoryModel::item_array_t items; + getDescendentsOfAssetType(cat_id, items, type); + LLInventoryModel::item_array_t curr_items = items; + removeDuplicateItems(items); + if (max_items_per_type > 0 || max_items_total > 0) + { + filterWearableItems(items, max_items_per_type, max_items_total); + } + LLInventoryModel::item_array_t kill_items; + item_array_diff(curr_items,items,kill_items); + for (LLInventoryModel::item_array_t::iterator it = kill_items.begin(); + it != kill_items.end(); + ++it) + { + items_to_kill.push_back(LLPointer(*it)); + to_kill_count++; + } + return to_kill_count; +} + + +void LLAppearanceMgr::findAllExcessOrDuplicateItems(const LLUUID& cat_id, + LLInventoryObject::object_list_t& items_to_kill) +{ + findExcessOrDuplicateItems(cat_id,LLAssetType::AT_BODYPART, + 1, 0, items_to_kill); + findExcessOrDuplicateItems(cat_id,LLAssetType::AT_CLOTHING, + 0, LLAgentWearables::MAX_CLOTHING_LAYERS, items_to_kill); + findExcessOrDuplicateItems(cat_id,LLAssetType::AT_OBJECT, + 0, 0, items_to_kill); +} + +void LLAppearanceMgr::enforceCOFItemRestrictions(LLPointer cb) +{ + LLInventoryObject::object_list_t items_to_kill; + findAllExcessOrDuplicateItems(getCOF(), items_to_kill); + if (items_to_kill.size()>0) + { + // Remove duplicate or excess wearables. Should normally be enforced at the UI level, but + // this should catch anything that gets through. + remove_inventory_items(items_to_kill, cb); + } +} + +bool sort_by_linked_uuid(const LLViewerInventoryItem* item1, const LLViewerInventoryItem* item2) +{ + if (!item1 || !item2) + { + LL_WARNS() << "item1, item2 cannot be null, something is very wrong" << LL_ENDL; + return true; + } + + return item1->getLinkedUUID() < item2->getLinkedUUID(); +} + +void get_sorted_base_and_cof_items(LLInventoryModel::item_array_t& cof_item_array, LLInventoryModel::item_array_t& outfit_item_array) +{ + LLUUID base_outfit_id = LLAppearanceMgr::instance().getBaseOutfitUUID(); + + if (base_outfit_id.notNull()) + { + LLIsValidItemLink collector; + LLInventoryModel::cat_array_t sub_cat_array; + + gInventory.collectDescendents(base_outfit_id, + sub_cat_array, + outfit_item_array, + LLInventoryModel::EXCLUDE_TRASH); + + LLInventoryModel::cat_array_t cof_cats; + + gInventory.collectDescendentsIf(LLAppearanceMgr::instance().getCOF(), cof_cats, cof_item_array, + LLInventoryModel::EXCLUDE_TRASH, collector); + + for (U32 i = 0; i < outfit_item_array.size(); ++i) + { + LLViewerInventoryItem* linked_item = outfit_item_array.at(i)->getLinkedItem(); + if (linked_item != NULL && linked_item->getActualType() == LLAssetType::AT_TEXTURE) + { + outfit_item_array.erase(outfit_item_array.begin() + i); + break; + } + } + + std::sort(cof_item_array.begin(), cof_item_array.end(), sort_by_linked_uuid); + std::sort(outfit_item_array.begin(), outfit_item_array.end(), sort_by_linked_uuid); + } +} + + +void LLAppearanceMgr::updateAppearanceFromCOF(bool enforce_item_restrictions, + bool enforce_ordering, + nullary_func_t post_update_func) +{ + if (mIsInUpdateAppearanceFromCOF) + { + LL_WARNS() << "Called updateAppearanceFromCOF inside updateAppearanceFromCOF, skipping" << LL_ENDL; + return; + } + + LL_DEBUGS("Avatar") << self_av_string() << "starting" << LL_ENDL; + + if (gInventory.hasPosiblyBrockenLinks()) + { + // Inventory has either broken links or links that + // haven't loaded yet. + // Check if LLAppearanceMgr needs to wait. + LLUUID current_outfit_id = getCOF(); + LLInventoryModel::item_array_t cof_items; + LLInventoryModel::cat_array_t cof_cats; + LLFindBrokenLinks is_brocken_link; + gInventory.collectDescendentsIf(current_outfit_id, + cof_cats, + cof_items, + LLInventoryModel::EXCLUDE_TRASH, + is_brocken_link); + + if (cof_items.size() > 0) + { + // Some links haven't loaded yet, but fetch isn't complete so + // links are likely fine and we will have to wait for them to + // load + if (LLInventoryModelBackgroundFetch::getInstance()->folderFetchActive()) + { + + LLBrokenLinkObserver* observer = new LLBrokenLinkObserver(cof_items.front()->getUUID(), + enforce_item_restrictions, + enforce_ordering, + post_update_func); + gInventory.addObserver(observer); + return; + } + } + } + + if (enforce_item_restrictions) + { + // The point here is just to call + // updateAppearanceFromCOF() again after excess items + // have been removed. That time we will set + // enforce_item_restrictions to false so we don't get + // caught in a perpetual loop. + LLPointer cb( + new LLUpdateAppearanceOnDestroy(false, enforce_ordering, post_update_func)); + enforceCOFItemRestrictions(cb); + return; + } + + if (enforce_ordering) + { + //checking integrity of the COF in terms of ordering of wearables, + //checking and updating links' descriptions of wearables in the COF (before analyzed for "dirty" state) + + // As with enforce_item_restrictions handling above, we want + // to wait for the update callbacks, then (finally!) call + // updateAppearanceFromCOF() with no additional COF munging needed. + LLPointer cb( + new LLUpdateAppearanceOnDestroy(false, false, post_update_func)); + updateClothingOrderingInfo(LLUUID::null, cb); + return; + } + + if (!validateClothingOrderingInfo()) + { + + LLInventoryModel::item_array_t outfit_item_array; + LLInventoryModel::item_array_t cof_item_array; + get_sorted_base_and_cof_items(cof_item_array, outfit_item_array); + + if (outfit_item_array.size() == cof_item_array.size()) + { + for (U32 i = 0; i < cof_item_array.size(); ++i) + { + LLViewerInventoryItem *cof_it = cof_item_array.at(i); + LLViewerInventoryItem *base_it = outfit_item_array.at(i); + + if (cof_it->getActualDescription() != base_it->getActualDescription()) + { + if (cof_it->getLinkedUUID() == base_it->getLinkedUUID()) + { + cof_it->setDescription(base_it->getActualDescription()); + gInventory.updateItem(cof_it); + } + } + } + LLAppearanceMgr::getInstance()->updateIsDirty(); + } + + } + + BoolSetter setIsInUpdateAppearanceFromCOF(mIsInUpdateAppearanceFromCOF); + selfStartPhase("update_appearance_from_cof"); + + // update dirty flag to see if the state of the COF matches + // the saved outfit stored as a folder link + updateIsDirty(); + + // Send server request for appearance update + if (gAgent.getRegion() && gAgent.getRegion()->getCentralBakeVersion()) + { + requestServerAppearanceUpdate(); + } + + LLUUID current_outfit_id = getCOF(); + + // Find all the wearables that are in the COF's subtree. + LL_DEBUGS() << "LLAppearanceMgr::updateFromCOF()" << LL_ENDL; + LLInventoryModel::item_array_t wear_items; + LLInventoryModel::item_array_t obj_items; + LLInventoryModel::item_array_t gest_items; + getUserDescendents(current_outfit_id, wear_items, obj_items, gest_items); + // Get rid of non-links in case somehow the COF was corrupted. + remove_non_link_items(wear_items); + remove_non_link_items(obj_items); + remove_non_link_items(gest_items); + + dumpItemArray(wear_items,"asset_dump: wear_item"); + dumpItemArray(obj_items,"asset_dump: obj_item"); + + LLViewerInventoryCategory *cof = gInventory.getCategory(current_outfit_id); + if (!gInventory.isCategoryComplete(current_outfit_id)) + { + LL_WARNS() << "COF info is not complete. Version " << cof->getVersion() + << " descendent_count " << cof->getDescendentCount() + << " viewer desc count " << cof->getViewerDescendentCount() << LL_ENDL; + } + if(!wear_items.size()) + { + LLNotificationsUtil::add("CouldNotPutOnOutfit"); + return; + } + + //preparing the list of wearables in the correct order for LLAgentWearables + sortItemsByActualDescription(wear_items); + + + LL_DEBUGS("Avatar") << "HP block starts" << LL_ENDL; + LLTimer hp_block_timer; + LLWearableHoldingPattern* holder = new LLWearableHoldingPattern; + + holder->setObjItems(obj_items); + holder->setGestItems(gest_items); + + // Note: can't do normal iteration, because if all the + // wearables can be resolved immediately, then the + // callback will be called (and this object deleted) + // before the final getNextData(). + + for(S32 i = 0; i < wear_items.size(); ++i) + { + LLViewerInventoryItem *item = wear_items.at(i); + LLViewerInventoryItem *linked_item = item ? item->getLinkedItem() : NULL; + + // Fault injection: use debug setting to test asset + // fetch failures (should be replaced by new defaults in + // lost&found). + U32 skip_type = gSavedSettings.getU32("ForceAssetFail"); + + if (item && item->getIsLinkType() && linked_item) + { + LLFoundData found(linked_item->getUUID(), + linked_item->getAssetUUID(), + linked_item->getName(), + linked_item->getType(), + linked_item->isWearableType() ? linked_item->getWearableType() : LLWearableType::WT_INVALID + ); + + if (skip_type != LLWearableType::WT_INVALID && skip_type == found.mWearableType) + { + found.mAssetID.generate(); // Replace with new UUID, guaranteed not to exist in DB + } + //pushing back, not front, to preserve order of wearables for LLAgentWearables + holder->getFoundList().push_back(found); + } + else + { + if (!item) + { + LL_WARNS() << "Attempt to wear a null item " << LL_ENDL; + } + else if (!linked_item) + { + LL_WARNS() << "Attempt to wear a broken link [ name:" << item->getName() << " ] " << LL_ENDL; + } + } + } + + selfStartPhase("get_wearables_2"); + + for (LLWearableHoldingPattern::found_list_t::iterator it = holder->getFoundList().begin(); + it != holder->getFoundList().end(); ++it) + { + LLFoundData& found = *it; + + LL_DEBUGS() << self_av_string() << "waiting for onWearableAssetFetch callback, asset " << found.mAssetID.asString() << LL_ENDL; + + // Fetch the wearables about to be worn. + LLWearableList::instance().getAsset(found.mAssetID, + found.mName, + gAgentAvatarp, + found.mAssetType, + onWearableAssetFetch, + (void*)holder); + + } + + holder->resetTime(gSavedSettings.getF32("MaxWearableWaitTime")); + if (!holder->pollFetchCompletion()) + { + doOnIdleRepeating(boost::bind(&LLWearableHoldingPattern::pollFetchCompletion,holder)); + } + post_update_func(); + + LL_DEBUGS("Avatar") << "HP block ends, elapsed " << hp_block_timer.getElapsedTimeF32() << LL_ENDL; +} + +void LLAppearanceMgr::getDescendentsOfAssetType(const LLUUID& category, + LLInventoryModel::item_array_t& items, + LLAssetType::EType type) +{ + LLInventoryModel::cat_array_t cats; + LLIsType is_of_type(type); + gInventory.collectDescendentsIf(category, + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + is_of_type); +} + +void LLAppearanceMgr::getUserDescendents(const LLUUID& category, + LLInventoryModel::item_array_t& wear_items, + LLInventoryModel::item_array_t& obj_items, + LLInventoryModel::item_array_t& gest_items) +{ + LLInventoryModel::cat_array_t wear_cats; + LLFindWearables is_wearable; + gInventory.collectDescendentsIf(category, + wear_cats, + wear_items, + LLInventoryModel::EXCLUDE_TRASH, + is_wearable); + + LLInventoryModel::cat_array_t obj_cats; + LLIsType is_object( LLAssetType::AT_OBJECT ); + gInventory.collectDescendentsIf(category, + obj_cats, + obj_items, + LLInventoryModel::EXCLUDE_TRASH, + is_object); + + // Find all gestures in this folder + LLInventoryModel::cat_array_t gest_cats; + LLIsType is_gesture( LLAssetType::AT_GESTURE ); + gInventory.collectDescendentsIf(category, + gest_cats, + gest_items, + LLInventoryModel::EXCLUDE_TRASH, + is_gesture); +} + +void LLAppearanceMgr::wearInventoryCategory(LLInventoryCategory* category, bool copy, bool append) +{ + if(!category) return; + + selfClearPhases(); + selfStartPhase("wear_inventory_category"); + + gAgentWearables.notifyLoadingStarted(); + + LL_INFOS("Avatar") << self_av_string() << "wearInventoryCategory( " << category->getName() + << " )" << LL_ENDL; + + // If we are copying from library, attempt to use AIS to copy the category. + if (copy && AISAPI::isAvailable()) + { + LLUUID parent_id; + parent_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CLOTHING); + if (parent_id.isNull()) + { + parent_id = gInventory.getRootFolderID(); + } + + LLPointer copy_cb = new LLWearCategoryAfterCopy(append); + LLPointer track_cb = new LLTrackPhaseWrapper( + std::string("wear_inventory_category_callback"), copy_cb); + + AISAPI::completion_t cr = boost::bind(&doAppearanceCb, track_cb, _1); + AISAPI::CopyLibraryCategory(category->getUUID(), parent_id, false, cr); + } + else + { + selfStartPhase("wear_inventory_category_fetch"); + if (AISAPI::isAvailable() && category->getPreferredType() == LLFolderType::FT_OUTFIT) + { + // for reliability just fetch it whole, linked items included + LLUUID cat_id = category->getUUID(); + LLInventoryModelBackgroundFetch::getInstance()->fetchFolderAndLinks( + cat_id, + [cat_id, copy, append] + { + LLAppearanceMgr::instance().wearCategoryFinal(cat_id, copy, append); + }); + } + else + { + callAfterCategoryFetch(category->getUUID(), boost::bind(&LLAppearanceMgr::wearCategoryFinal, + &LLAppearanceMgr::instance(), + category->getUUID(), copy, append)); + } + } +} + +S32 LLAppearanceMgr::getActiveCopyOperations() const +{ + return LLCallAfterInventoryCopyMgr::getInstanceCount(); +} + +void LLAppearanceMgr::wearCategoryFinal(const LLUUID& cat_id, bool copy_items, bool append) +{ + LL_INFOS("Avatar") << self_av_string() << "starting" << LL_ENDL; + + selfStopPhase("wear_inventory_category_fetch"); + + // We now have an outfit ready to be copied to agent inventory. Do + // it, and wear that outfit normally. + LLInventoryCategory* cat = gInventory.getCategory(cat_id); + if(copy_items) + { + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(cat_id, cats, items); + std::string name; + if(!cat) + { + // should never happen. + name = "New Outfit"; + } + else + { + name = cat->getName(); + } + LLViewerInventoryItem* item = NULL; + LLInventoryModel::item_array_t::const_iterator it = items->begin(); + LLInventoryModel::item_array_t::const_iterator end = items->end(); + LLUUID pid; + for(; it < end; ++it) + { + item = *it; + if(item) + { + if(LLInventoryType::IT_GESTURE == item->getInventoryType()) + { + pid = gInventory.findCategoryUUIDForType(LLFolderType::FT_GESTURE); + } + else + { + pid = gInventory.findCategoryUUIDForType(LLFolderType::FT_CLOTHING); + } + break; + } + } + if(pid.isNull()) + { + pid = gInventory.getRootFolderID(); + } + + gInventory.createNewCategory( + pid, + LLFolderType::FT_NONE, + name, + [cat_id, append](const LLUUID& new_cat_id) + { + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(cat_id, cats, items); + // Create a CopyMgr that will copy items, manage its own destruction + new LLCallAfterInventoryCopyMgr( + *items, new_cat_id, std::string("wear_inventory_category_callback"), + boost::bind(&LLAppearanceMgr::wearInventoryCategoryOnAvatar, + LLAppearanceMgr::getInstance(), + gInventory.getCategory(new_cat_id), + append)); + + // BAP fixes a lag in display of created dir. + gInventory.notifyObservers(); + }, + cat->getThumbnailUUID() + ); + } + else + { + // Wear the inventory category. + LLAppearanceMgr::instance().wearInventoryCategoryOnAvatar(cat, append); + } +} + +// *NOTE: hack to get from avatar inventory to avatar +void LLAppearanceMgr::wearInventoryCategoryOnAvatar( LLInventoryCategory* category, bool append ) +{ + // Avoid unintentionally overwriting old wearables. We have to do + // this up front to avoid having to deal with the case of multiple + // wearables being dirty. + if (!category) return; + + if ( !LLInventoryCallbackManager::is_instantiated() ) + { + // shutting down, ignore. + return; + } + + LL_INFOS("Avatar") << self_av_string() << "wearInventoryCategoryOnAvatar '" << category->getName() + << "'" << LL_ENDL; + LLUIUsage::instance().logCommand("Avatar.WearCategory"); + + if (gAgentCamera.cameraCustomizeAvatar()) + { + // switching to outfit editor should automagically save any currently edited wearable + LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "edit_outfit")); + } + + LLAppearanceMgr::changeOutfit(true, category->getUUID(), append); +} + +// FIXME do we really want to search entire inventory for matching name? +void LLAppearanceMgr::wearOutfitByName(const std::string& name) +{ + LL_INFOS("Avatar") << self_av_string() << "Wearing category " << name << LL_ENDL; + + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + LLNameCategoryCollector has_name(name); + gInventory.collectDescendentsIf(gInventory.getRootFolderID(), + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH, + has_name); + bool copy_items = false; + LLInventoryCategory* cat = NULL; + if (cat_array.size() > 0) + { + // Just wear the first one that matches + cat = cat_array.at(0); + } + else + { + gInventory.collectDescendentsIf(LLUUID::null, + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH, + has_name); + if(cat_array.size() > 0) + { + cat = cat_array.at(0); + copy_items = true; + } + } + + if(cat) + { + LLAppearanceMgr::wearInventoryCategory(cat, copy_items, false); + } + else + { + LL_WARNS() << "Couldn't find outfit " <isWearableType() && b->isWearableType() && + (a->getWearableType() == b->getWearableType())); +} + +class LLDeferredCOFLinkObserver: public LLInventoryObserver +{ +public: + LLDeferredCOFLinkObserver(const LLUUID& item_id, LLPointer cb, const std::string& description): + mItemID(item_id), + mCallback(cb), + mDescription(description) + { + } + + ~LLDeferredCOFLinkObserver() + { + } + + /* virtual */ void changed(U32 mask) + { + const LLInventoryItem *item = gInventory.getItem(mItemID); + if (item) + { + gInventory.removeObserver(this); + LLAppearanceMgr::instance().addCOFItemLink(item, mCallback, mDescription); + delete this; + } + } + +private: + const LLUUID mItemID; + std::string mDescription; + LLPointer mCallback; +}; + + +// BAP - note that this runs asynchronously if the item is not already loaded from inventory. +// Dangerous if caller assumes link will exist after calling the function. +void LLAppearanceMgr::addCOFItemLink(const LLUUID &item_id, + LLPointer cb, + const std::string description) +{ + const LLInventoryItem *item = gInventory.getItem(item_id); + if (!item) + { + LLDeferredCOFLinkObserver *observer = new LLDeferredCOFLinkObserver(item_id, cb, description); + gInventory.addObserver(observer); + } + else + { + addCOFItemLink(item, cb, description); + } +} + +void LLAppearanceMgr::addCOFItemLink(const LLInventoryItem *item, + LLPointer cb, + const std::string description) +{ + const LLViewerInventoryItem *vitem = dynamic_cast(item); + if (!vitem) + { + LL_WARNS() << "not an llviewerinventoryitem, failed" << LL_ENDL; + return; + } + + gInventory.addChangedMask(LLInventoryObserver::LABEL, vitem->getLinkedUUID()); + + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + gInventory.collectDescendents(LLAppearanceMgr::getCOF(), + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH); + bool linked_already = false; + for (S32 i=0; igetWearableType(); + + const bool is_body_part = (wearable_type == LLWearableType::WT_SHAPE) + || (wearable_type == LLWearableType::WT_HAIR) + || (wearable_type == LLWearableType::WT_EYES) + || (wearable_type == LLWearableType::WT_SKIN); + + if (inv_item->getLinkedUUID() == vitem->getLinkedUUID()) + { + linked_already = true; + } + // Are these links to different items of the same body part + // type? If so, new item will replace old. + else if ((vitem->isWearableType()) && (vitem->getWearableType() == wearable_type)) + { + if (is_body_part && inv_item->getIsLinkType()) + { + remove_inventory_item(inv_item->getUUID(), cb); + } + else if (!gAgentWearables.canAddWearable(wearable_type)) + { + // MULTI-WEARABLES: make sure we don't go over clothing limits + remove_inventory_item(inv_item->getUUID(), cb); + } + } + } + + if (!linked_already) + { + LLViewerInventoryItem *copy_item = new LLViewerInventoryItem; + copy_item->copyViewerItem(vitem); + copy_item->setDescription(description); + link_inventory_object(getCOF(), copy_item, cb); + } +} + +LLInventoryModel::item_array_t LLAppearanceMgr::findCOFItemLinks(const LLUUID& item_id) +{ + LLInventoryModel::item_array_t result; + + LLUUID linked_id = gInventory.getLinkedItemID(item_id); + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + gInventory.collectDescendents(LLAppearanceMgr::getCOF(), + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH); + for (S32 i=0; igetLinkedUUID() == linked_id) + { + result.push_back(item_array.at(i)); + } + } + return result; +} + +bool LLAppearanceMgr::isLinkedInCOF(const LLUUID& item_id) +{ + LLInventoryModel::item_array_t links = LLAppearanceMgr::instance().findCOFItemLinks(item_id); + return links.size() > 0; +} + +void LLAppearanceMgr::removeAllClothesFromAvatar() +{ + // Fetch worn clothes (i.e. the ones in COF). + LLInventoryModel::item_array_t clothing_items; + LLInventoryModel::cat_array_t dummy; + LLIsType is_clothing(LLAssetType::AT_CLOTHING); + gInventory.collectDescendentsIf(getCOF(), + dummy, + clothing_items, + LLInventoryModel::EXCLUDE_TRASH, + is_clothing); + uuid_vec_t item_ids; + for (LLInventoryModel::item_array_t::iterator it = clothing_items.begin(); + it != clothing_items.end(); ++it) + { + item_ids.push_back((*it).get()->getLinkedUUID()); + } + + // Take them off by removing from COF. + removeItemsFromAvatar(item_ids); +} + +void LLAppearanceMgr::removeAllAttachmentsFromAvatar() +{ + if (!isAgentAvatarValid()) return; + + LLAgentWearables::llvo_vec_t objects_to_remove; + + for (LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); + iter != gAgentAvatarp->mAttachmentPoints.end();) + { + LLVOAvatar::attachment_map_t::iterator curiter = iter++; + LLViewerJointAttachment* attachment = curiter->second; + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + LLViewerObject *attached_object = attachment_iter->get(); + if (attached_object) + { + objects_to_remove.push_back(attached_object); + } + } + } + uuid_vec_t ids_to_remove; + for (LLAgentWearables::llvo_vec_t::iterator it = objects_to_remove.begin(); + it != objects_to_remove.end(); + ++it) + { + ids_to_remove.push_back((*it)->getAttachmentItemID()); + } + removeItemsFromAvatar(ids_to_remove); +} + +class LLUpdateOnCOFLinkRemove : public LLInventoryCallback +{ +public: + LLUpdateOnCOFLinkRemove(const LLUUID& remove_item_id, LLPointer cb = NULL): + mItemID(remove_item_id), + mCB(cb) + { + } + + /* virtual */ void fire(const LLUUID& item_id) + { + // just removed cof link, "(wear)" suffix depends on presence of link, so update label + gInventory.addChangedMask(LLInventoryObserver::LABEL, mItemID); + if (mCB.notNull()) + { + mCB->fire(item_id); + } + } + +private: + LLUUID mItemID; + LLPointer mCB; +}; + +void LLAppearanceMgr::removeCOFItemLinks(const LLUUID& item_id, LLPointer cb) +{ LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + gInventory.collectDescendents(LLAppearanceMgr::getCOF(), + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH); + for (S32 i=0; igetIsLinkType() && item->getLinkedUUID() == item_id) + { + if (item->getType() == LLAssetType::AT_OBJECT) + { + // Immediate delete + remove_inventory_item(item->getUUID(), cb, true); + gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id); + } + else + { + // Delayed delete + // Pointless to update item_id label here since link still exists and first notifyObservers + // call will restore (wear) suffix, mark for update after deletion + LLPointer cb_label = new LLUpdateOnCOFLinkRemove(item_id, cb); + remove_inventory_item(item->getUUID(), cb_label, false); + } + } + } +} + +void LLAppearanceMgr::removeCOFLinksOfType(LLWearableType::EType type, LLPointer cb) +{ + LLFindWearablesOfType filter_wearables_of_type(type); + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLInventoryModel::item_array_t::const_iterator it; + + gInventory.collectDescendentsIf(getCOF(), cats, items, true, filter_wearables_of_type); + for (it = items.begin(); it != items.end(); ++it) + { + const LLViewerInventoryItem* item = *it; + if (item->getIsLinkType()) // we must operate on links only + { + remove_inventory_item(item->getUUID(), cb); + } + } +} + +void LLAppearanceMgr::updateIsDirty() +{ + LLUUID cof = getCOF(); + LLUUID base_outfit; + + // find base outfit link + const LLViewerInventoryItem* base_outfit_item = getBaseOutfitLink(); + LLViewerInventoryCategory* catp = NULL; + if (base_outfit_item && base_outfit_item->getIsLinkType()) + { + catp = base_outfit_item->getLinkedCategory(); + } + if(catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT) + { + base_outfit = catp->getUUID(); + } + + // Set dirty to "false" if no base outfit found to disable "Save" + // and leave only "Save As" enabled in My Outfits. + mOutfitIsDirty = false; + + if (base_outfit.notNull()) + { + LLIsValidItemLink collector; + + LLInventoryModel::cat_array_t cof_cats; + LLInventoryModel::item_array_t cof_items; + gInventory.collectDescendentsIf(cof, cof_cats, cof_items, + LLInventoryModel::EXCLUDE_TRASH, collector); + + LLInventoryModel::cat_array_t outfit_cats; + LLInventoryModel::item_array_t outfit_items; + gInventory.collectDescendentsIf(base_outfit, outfit_cats, outfit_items, + LLInventoryModel::EXCLUDE_TRASH, collector); + + for (U32 i = 0; i < outfit_items.size(); ++i) + { + LLViewerInventoryItem* linked_item = outfit_items.at(i)->getLinkedItem(); + if (linked_item != NULL && linked_item->getActualType() == LLAssetType::AT_TEXTURE) + { + outfit_items.erase(outfit_items.begin() + i); + break; + } + } + + if(outfit_items.size() != cof_items.size()) + { + LL_DEBUGS("Avatar") << "item count different - base " << outfit_items.size() << " cof " << cof_items.size() << LL_ENDL; + // Current outfit folder should have one more item than the outfit folder. + // this one item is the link back to the outfit folder itself. + mOutfitIsDirty = true; + return; + } + + //"dirty" - also means a difference in linked UUIDs and/or a difference in wearables order (links' descriptions) + std::sort(cof_items.begin(), cof_items.end(), sort_by_linked_uuid); + std::sort(outfit_items.begin(), outfit_items.end(), sort_by_linked_uuid); + + for (U32 i = 0; i < cof_items.size(); ++i) + { + LLViewerInventoryItem *item1 = cof_items.at(i); + LLViewerInventoryItem *item2 = outfit_items.at(i); + + if (item1->getLinkedUUID() != item2->getLinkedUUID() || + item1->getName() != item2->getName() || + item1->getActualDescription() != item2->getActualDescription()) + { + if (item1->getLinkedUUID() != item2->getLinkedUUID()) + { + LL_DEBUGS("Avatar") << "link id different " << LL_ENDL; + } + else + { + if (item1->getName() != item2->getName()) + { + LL_DEBUGS("Avatar") << "name different " << item1->getName() << " " << item2->getName() << LL_ENDL; + } + if (item1->getActualDescription() != item2->getActualDescription()) + { + LL_DEBUGS("Avatar") << "desc different " << item1->getActualDescription() + << " " << item2->getActualDescription() + << " names " << item1->getName() << " " << item2->getName() << LL_ENDL; + } + } + mOutfitIsDirty = true; + return; + } + } + } + llassert(!mOutfitIsDirty); + LL_DEBUGS("Avatar") << "clean" << LL_ENDL; +} + +// *HACK: Must match name in Library or agent inventory +const std::string ROOT_GESTURES_FOLDER = "Gestures"; +const std::string COMMON_GESTURES_FOLDER = "Common Gestures"; +const std::string MALE_GESTURES_FOLDER = "Male Gestures"; +const std::string FEMALE_GESTURES_FOLDER = "Female Gestures"; +const std::string SPEECH_GESTURES_FOLDER = "Speech Gestures"; +const std::string OTHER_GESTURES_FOLDER = "Other Gestures"; + +void LLAppearanceMgr::copyLibraryGestures() +{ + LL_INFOS("Avatar") << self_av_string() << "Copying library gestures" << LL_ENDL; + + // Copy gestures + LLUUID lib_gesture_cat_id = + gInventory.findLibraryCategoryUUIDForType(LLFolderType::FT_GESTURE); + if (lib_gesture_cat_id.isNull()) + { + LL_WARNS() << "Unable to copy gestures, source category not found" << LL_ENDL; + } + LLUUID dst_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_GESTURE); + + std::vector gesture_folders_to_copy; + gesture_folders_to_copy.push_back(MALE_GESTURES_FOLDER); + gesture_folders_to_copy.push_back(FEMALE_GESTURES_FOLDER); + gesture_folders_to_copy.push_back(COMMON_GESTURES_FOLDER); + gesture_folders_to_copy.push_back(SPEECH_GESTURES_FOLDER); + gesture_folders_to_copy.push_back(OTHER_GESTURES_FOLDER); + + for(std::vector::iterator it = gesture_folders_to_copy.begin(); + it != gesture_folders_to_copy.end(); + ++it) + { + std::string& folder_name = *it; + + LLPointer cb(NULL); + + // After copying gestures, activate Common, Other, plus + // Male and/or Female, depending upon the initial outfit gender. + ESex gender = gAgentAvatarp->getSex(); + + std::string activate_male_gestures; + std::string activate_female_gestures; + switch (gender) { + case SEX_MALE: + activate_male_gestures = MALE_GESTURES_FOLDER; + break; + case SEX_FEMALE: + activate_female_gestures = FEMALE_GESTURES_FOLDER; + break; + case SEX_BOTH: + activate_male_gestures = MALE_GESTURES_FOLDER; + activate_female_gestures = FEMALE_GESTURES_FOLDER; + break; + } + + if (folder_name == activate_male_gestures || + folder_name == activate_female_gestures || + folder_name == COMMON_GESTURES_FOLDER || + folder_name == OTHER_GESTURES_FOLDER) + { + cb = new LLBoostFuncInventoryCallback(activate_gesture_cb); + } + + LLUUID cat_id = findDescendentCategoryIDByName(lib_gesture_cat_id,folder_name); + if (cat_id.isNull()) + { + LL_WARNS() << self_av_string() << "failed to find gesture folder for " << folder_name << LL_ENDL; + } + else + { + LL_DEBUGS("Avatar") << self_av_string() << "initiating fetch and copy for " << folder_name << " cat_id " << cat_id << LL_ENDL; + callAfterCategoryFetch(cat_id, + boost::bind(&LLAppearanceMgr::shallowCopyCategory, + &LLAppearanceMgr::instance(), + cat_id, dst_id, cb)); + } + } +} + +// Handler for anything that's deferred until avatar de-clouds. +void LLAppearanceMgr::onFirstFullyVisible() +{ + gAgentAvatarp->outputRezTiming("Avatar fully loaded"); + gAgentAvatarp->reportAvatarRezTime(); + gAgentAvatarp->debugAvatarVisible(); + + // If this is the first time we've ever logged in, + // then copy default gestures from the library. + if (gAgent.isFirstLogin()) { + copyLibraryGestures(); + } +} + +// update "dirty" state - defined outside class to allow for calling +// after appearance mgr instance has been destroyed. +void appearance_mgr_update_dirty_state() +{ + if (LLAppearanceMgr::instanceExists()) + { + LLAppearanceMgr& app_mgr = LLAppearanceMgr::instance(); + LLUUID image_id = app_mgr.getOutfitImage(); + if(image_id.notNull()) + { + LLPointer cb = NULL; + link_inventory_object(app_mgr.getBaseOutfitUUID(), image_id, cb); + } + + LLAppearanceMgr::getInstance()->updateIsDirty(); + LLAppearanceMgr::getInstance()->setOutfitLocked(false); + gAgentWearables.notifyLoadingFinished(); + } +} + +void update_base_outfit_after_ordering() +{ + LLAppearanceMgr& app_mgr = LLAppearanceMgr::instance(); + app_mgr.setOutfitImage(LLUUID()); + LLInventoryModel::cat_array_t sub_cat_array; + LLInventoryModel::item_array_t outfit_item_array; + gInventory.collectDescendents(app_mgr.getBaseOutfitUUID(), + sub_cat_array, + outfit_item_array, + LLInventoryModel::EXCLUDE_TRASH); + for (LLViewerInventoryItem* outfit_item : outfit_item_array) + { + LLViewerInventoryItem* linked_item = outfit_item->getLinkedItem(); + if (linked_item != NULL) + { + if (linked_item->getActualType() == LLAssetType::AT_TEXTURE) + { + app_mgr.setOutfitImage(linked_item->getLinkedUUID()); + if (linked_item->getName() == LLAppearanceMgr::sExpectedTextureName) + { + // Images with "appropriate" name take priority + break; + } + } + } + else if (outfit_item->getActualType() == LLAssetType::AT_TEXTURE) + { + app_mgr.setOutfitImage(outfit_item->getUUID()); + if (outfit_item->getName() == LLAppearanceMgr::sExpectedTextureName) + { + // Images with "appropriate" name take priority + break; + } + } + } + + LLPointer dirty_state_updater = + new LLBoostFuncInventoryCallback(no_op_inventory_func, appearance_mgr_update_dirty_state); + + //COF contains only links so we copy to the Base Outfit only links + const LLUUID base_outfit_id = app_mgr.getBaseOutfitUUID(); + bool copy_folder_links = false; + app_mgr.slamCategoryLinks(app_mgr.getCOF(), base_outfit_id, copy_folder_links, dirty_state_updater); + + if (base_outfit_id.notNull()) + { + LLIsValidItemLink collector; + + LLInventoryModel::cat_array_t cof_cats; + LLInventoryModel::item_array_t cof_item_array; + gInventory.collectDescendentsIf(app_mgr.getCOF(), cof_cats, cof_item_array, + LLInventoryModel::EXCLUDE_TRASH, collector); + + for (U32 i = 0; i < outfit_item_array.size(); ++i) + { + LLViewerInventoryItem* linked_item = outfit_item_array.at(i)->getLinkedItem(); + if (linked_item != NULL && linked_item->getActualType() == LLAssetType::AT_TEXTURE) + { + outfit_item_array.erase(outfit_item_array.begin() + i); + break; + } + } + + if (outfit_item_array.size() != cof_item_array.size()) + { + return; + } + + std::sort(cof_item_array.begin(), cof_item_array.end(), sort_by_linked_uuid); + std::sort(outfit_item_array.begin(), outfit_item_array.end(), sort_by_linked_uuid); + + for (U32 i = 0; i < cof_item_array.size(); ++i) + { + LLViewerInventoryItem *cof_it = cof_item_array.at(i); + LLViewerInventoryItem *base_it = outfit_item_array.at(i); + + if (cof_it->getActualDescription() != base_it->getActualDescription()) + { + if (cof_it->getLinkedUUID() == base_it->getLinkedUUID()) + { + base_it->setDescription(cof_it->getActualDescription()); + gInventory.updateItem(base_it); + } + } + } + LLAppearanceMgr::getInstance()->updateIsDirty(); + } + +} + +// Save COF changes - update the contents of the current base outfit +// to match the current COF. Fails if no current base outfit is set. +bool LLAppearanceMgr::updateBaseOutfit() +{ + if (isOutfitLocked()) + { + // don't allow modify locked outfit + llassert(!isOutfitLocked()); + return false; + } + + setOutfitLocked(true); + + gAgentWearables.notifyLoadingStarted(); + + const LLUUID base_outfit_id = getBaseOutfitUUID(); + if (base_outfit_id.isNull()) return false; + LL_DEBUGS("Avatar") << "saving cof to base outfit " << base_outfit_id << LL_ENDL; + + LLPointer cb = + new LLBoostFuncInventoryCallback(no_op_inventory_func, update_base_outfit_after_ordering); + // Really shouldn't be needed unless there's a race condition - + // updateAppearanceFromCOF() already calls updateClothingOrderingInfo. + updateClothingOrderingInfo(LLUUID::null, cb); + + return true; +} + +void LLAppearanceMgr::divvyWearablesByType(const LLInventoryModel::item_array_t& items, wearables_by_type_t& items_by_type) +{ + items_by_type.resize(LLWearableType::WT_COUNT); + if (items.empty()) return; + + for (S32 i=0; iisWearableType()) + continue; + LLWearableType::EType type = item->getWearableType(); + if(type < 0 || type >= LLWearableType::WT_COUNT) + { + LL_WARNS("Appearance") << "Invalid wearable type. Inventory type does not match wearable flag bitfield." << LL_ENDL; + continue; + } + items_by_type[type].push_back(item); + } +} + +std::string build_order_string(LLWearableType::EType type, U32 i) +{ + std::ostringstream order_num; + order_num << ORDER_NUMBER_SEPARATOR << type * 100 + i; + return order_num.str(); +} + +struct WearablesOrderComparator +{ + LOG_CLASS(WearablesOrderComparator); + WearablesOrderComparator(const LLWearableType::EType type) + { + mControlSize = build_order_string(type, 0).size(); + }; + + bool operator()(const LLInventoryItem* item1, const LLInventoryItem* item2) + { + const std::string& desc1 = item1->getActualDescription(); + const std::string& desc2 = item2->getActualDescription(); + + bool item1_valid = (desc1.size() == mControlSize) && (ORDER_NUMBER_SEPARATOR == desc1[0]); + bool item2_valid = (desc2.size() == mControlSize) && (ORDER_NUMBER_SEPARATOR == desc2[0]); + + if (item1_valid && item2_valid) + return desc1 < desc2; + + //we need to sink down invalid items: items with empty descriptions, items with "Broken link" descriptions, + //items with ordering information but not for the associated wearables type + if (!item1_valid && item2_valid) + return false; + else if (item1_valid && !item2_valid) + return true; + + return item1->getName() < item2->getName(); + } + + U32 mControlSize; +}; + +void LLAppearanceMgr::getWearableOrderingDescUpdates(LLInventoryModel::item_array_t& wear_items, + desc_map_t& desc_map) +{ + wearables_by_type_t items_by_type(LLWearableType::WT_COUNT); + divvyWearablesByType(wear_items, items_by_type); + + for (U32 type = LLWearableType::WT_SHIRT; type < LLWearableType::WT_COUNT; type++) + { + U32 size = items_by_type[type].size(); + if (!size) continue; + + //sinking down invalid items which need reordering + std::sort(items_by_type[type].begin(), items_by_type[type].end(), WearablesOrderComparator((LLWearableType::EType) type)); + + //requesting updates only for those links which don't have "valid" descriptions + for (U32 i = 0; i < size; i++) + { + LLViewerInventoryItem* item = items_by_type[type][i]; + if (!item) continue; + + std::string new_order_str = build_order_string((LLWearableType::EType)type, i); + if (new_order_str == item->getActualDescription()) continue; + + desc_map[item->getUUID()] = new_order_str; + } + } +} + +bool LLAppearanceMgr::validateClothingOrderingInfo(LLUUID cat_id) +{ + // COF is processed if cat_id is not specified + if (cat_id.isNull()) + { + cat_id = getCOF(); + } + + LLInventoryModel::item_array_t wear_items; + getDescendentsOfAssetType(cat_id, wear_items, LLAssetType::AT_CLOTHING); + + // Identify items for which desc needs to change. + desc_map_t desc_map; + getWearableOrderingDescUpdates(wear_items, desc_map); + + for (desc_map_t::const_iterator it = desc_map.begin(); + it != desc_map.end(); ++it) + { + const LLUUID& item_id = it->first; + const std::string& new_order_str = it->second; + LLViewerInventoryItem *item = gInventory.getItem(item_id); + LL_WARNS() << "Order validation fails: " << item->getName() + << " needs to update desc to: " << new_order_str + << " (from: " << item->getActualDescription() << ")" << LL_ENDL; + } + + return desc_map.size() == 0; +} + +void LLAppearanceMgr::updateClothingOrderingInfo(LLUUID cat_id, + LLPointer cb) +{ + // COF is processed if cat_id is not specified + if (cat_id.isNull()) + { + cat_id = getCOF(); + } + + LLInventoryModel::item_array_t wear_items; + getDescendentsOfAssetType(cat_id, wear_items, LLAssetType::AT_CLOTHING); + + // Identify items for which desc needs to change. + desc_map_t desc_map; + getWearableOrderingDescUpdates(wear_items, desc_map); + + for (desc_map_t::const_iterator it = desc_map.begin(); + it != desc_map.end(); ++it) + { + LLSD updates; + const LLUUID& item_id = it->first; + const std::string& new_order_str = it->second; + LLViewerInventoryItem *item = gInventory.getItem(item_id); + LL_DEBUGS("Avatar") << item->getName() << " updating desc to: " << new_order_str + << " (was: " << item->getActualDescription() << ")" << LL_ENDL; + updates["desc"] = new_order_str; + update_inventory_item(item_id,updates,cb); + } + +} + + +LLSD LLAppearanceMgr::dumpCOF() const +{ + LLSD links = LLSD::emptyArray(); + LLMD5 md5; + + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + gInventory.collectDescendents(getCOF(),cat_array,item_array,LLInventoryModel::EXCLUDE_TRASH); + for (S32 i=0; igetUUID()); + md5.update((unsigned char*)item_id.mData, 16); + item["description"] = inv_item->getActualDescription(); + md5.update(inv_item->getActualDescription()); + item["asset_type"] = inv_item->getActualType(); + LLUUID linked_id(inv_item->getLinkedUUID()); + item["linked_id"] = linked_id; + md5.update((unsigned char*)linked_id.mData, 16); + + if (LLAssetType::AT_LINK == inv_item->getActualType()) + { + const LLViewerInventoryItem* linked_item = inv_item->getLinkedItem(); + if (NULL == linked_item) + { + LL_WARNS() << "Broken link for item '" << inv_item->getName() + << "' (" << inv_item->getUUID() + << ") during requestServerAppearanceUpdate" << LL_ENDL; + continue; + } + // Some assets may be 'hidden' and show up as null in the viewer. + //if (linked_item->getAssetUUID().isNull()) + //{ + // LL_WARNS() << "Broken link (null asset) for item '" << inv_item->getName() + // << "' (" << inv_item->getUUID() + // << ") during requestServerAppearanceUpdate" << LL_ENDL; + // continue; + //} + LLUUID linked_asset_id(linked_item->getAssetUUID()); + md5.update((unsigned char*)linked_asset_id.mData, 16); + U32 flags = linked_item->getFlags(); + md5.update(std::to_string(flags)); + } + else if (LLAssetType::AT_LINK_FOLDER != inv_item->getActualType()) + { + LL_WARNS() << "Non-link item '" << inv_item->getName() + << "' (" << inv_item->getUUID() + << ") type " << (S32) inv_item->getActualType() + << " during requestServerAppearanceUpdate" << LL_ENDL; + continue; + } + links.append(item); + } + LLSD result = LLSD::emptyMap(); + result["cof_contents"] = links; + char cof_md5sum[MD5HEX_STR_SIZE]; + md5.finalize(); + md5.hex_digest(cof_md5sum); + result["cof_md5sum"] = std::string(cof_md5sum); + return result; +} + +void LLAppearanceMgr::cleanup() +{ + mIsInUpdateAppearanceFromCOF = false; + mOutstandingAppearanceBakeRequest = false; + mRerequestAppearanceBake = false; + mCOFID.setNull(); +} + +// static +void LLAppearanceMgr::onIdle(void *) +{ + LLAppearanceMgr* mgr = LLAppearanceMgr::getInstance(); + if (mgr->mRerequestAppearanceBake) + { + mgr->requestServerAppearanceUpdate(); + } +} + +void LLAppearanceMgr::requestServerAppearanceUpdate() +{ + // Workaround: we shouldn't request update from server prior to uploading all attachments, but it is + // complicated to check for pending attachment uploads, so we are just waiting for uploads to complete + if (!mOutstandingAppearanceBakeRequest && gAssetStorage->getNumPendingUploads() == 0) + { + mRerequestAppearanceBake = false; + LLCoprocedureManager::CoProcedure_t proc = boost::bind(&LLAppearanceMgr::serverAppearanceUpdateCoro, this, _1); + LLCoprocedureManager::instance().enqueueCoprocedure("AIS", "LLAppearanceMgr::serverAppearanceUpdateCoro", proc); + } + else + { + // Shedule update + mRerequestAppearanceBake = true; + } +} + +void LLAppearanceMgr::serverAppearanceUpdateCoro(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &httpAdapter) +{ + BoolSetter outstanding(mOutstandingAppearanceBakeRequest); + if (!gAgent.getRegion()) + { + LL_WARNS("Avatar") << "Region not set, cannot request server appearance update" << LL_ENDL; + return; + } + if (gAgent.getRegion()->getCentralBakeVersion() == 0) + { + LL_WARNS("Avatar") << "Region does not support baking" << LL_ENDL; + return; + } + + std::string url = gAgent.getRegion()->getCapability("UpdateAvatarAppearance"); + if (url.empty()) + { + LL_WARNS("Agent") << "Could not retrieve region capability \"UpdateAvatarAppearance\"" << LL_ENDL; + return; + } + + //---------------- + if (gAgentAvatarp->isEditingAppearance()) + { + LL_WARNS("Avatar") << "Avatar editing appearance, not sending request." << LL_ENDL; + // don't send out appearance updates if in appearance editing mode + return; + } + + llcoro::suspend(); + if (LLApp::isExiting()) + { + return; + } + S32 retryCount(0); + bool bRetry; + do + { + // If we have already received an update for this or higher cof version, + // put a warning in the log and cancel the request. + S32 cofVersion = getCOFVersion(); + S32 lastRcv = gAgentAvatarp->mLastUpdateReceivedCOFVersion; + S32 lastReq = gAgentAvatarp->mLastUpdateRequestCOFVersion; + + LL_INFOS("Avatar") << "Requesting COF version " << cofVersion << + " (Last Received:" << lastRcv << ")" << + " (Last Requested:" << lastReq << ")" << LL_ENDL; + + if (cofVersion == LLViewerInventoryCategory::VERSION_UNKNOWN) + { + LL_INFOS("AVatar") << "COF version is unknown... not requesting until COF version is known." << LL_ENDL; + return; + } + else + { + if (cofVersion <= lastRcv) + { + LL_WARNS("Avatar") << "Have already received update for cof version " << lastRcv + << " but requesting for " << cofVersion << LL_ENDL; + return; + } + if (lastReq >= cofVersion) + { + LL_WARNS("Avatar") << "Request already in flight for cof version " << lastReq + << " but requesting for " << cofVersion << LL_ENDL; + return; + } + } + + // Actually send the request. + LL_DEBUGS("Avatar") << "Will send request for cof_version " << cofVersion << LL_ENDL; + + bRetry = false; + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest()); + + if (gSavedSettings.getBOOL("DebugForceAppearanceRequestFailure")) + { + cofVersion += 999; + LL_WARNS("Avatar") << "Forcing version failure on COF Baking" << LL_ENDL; + } + + LL_INFOS("Avatar") << "Requesting bake for COF version " << cofVersion << LL_ENDL; + + LLSD postData; + if (gSavedSettings.getBOOL("DebugAvatarExperimentalServerAppearanceUpdate")) + { + postData = dumpCOF(); + } + else + { + postData["cof_version"] = cofVersion; + } + + gAgentAvatarp->mLastUpdateRequestCOFVersion = cofVersion; + + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); + + if (LLApp::isExiting()) + { + return; + } + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status || !result["success"].asBoolean()) + { + std::string message = (result.has("error")) ? result["error"].asString() : status.toString(); + LL_WARNS("Avatar") << "Appearance Failure. server responded with \"" << message << "\"" << LL_ENDL; + + // We may have requested a bake for a stale COF (especially if the inventory + // is still updating. If that is the case re send the request with the + // corrected COF version. (This may also be the case if the viewer is running + // on multiple machines. + if (result.has("expected")) + { + S32 expectedCofVersion = result["expected"].asInteger(); + LL_WARNS("Avatar") << "Server expected " << expectedCofVersion << " as COF version" << LL_ENDL; + + // Force an update texture request for ourself. The message will return + // through the UDP and be handled in LLVOAvatar::processAvatarAppearance + // this should ensure that we receive a new canonical COF from the sim + // host. Hopefully it will return before the timeout. + LLAvatarPropertiesProcessor::getInstance()->sendAvatarTexturesRequest(gAgent.getID()); + + bRetry = true; + // Wait for a 1/2 second before trying again. Just to keep from asking too quickly. + if (++retryCount > BAKE_RETRY_MAX_COUNT) + { + LL_WARNS("Avatar") << "Bake retry count exceeded!" << LL_ENDL; + break; + } + F32 timeout = pow(BAKE_RETRY_TIMEOUT, static_cast(retryCount)) - 1.0; + + LL_WARNS("Avatar") << "Bake retry #" << retryCount << " in " << timeout << " seconds." << LL_ENDL; + + llcoro::suspendUntilTimeout(timeout); + if (LLApp::isExiting()) + { + return; + } + bRetry = true; + continue; + } + else + { + LL_WARNS("Avatar") << "No retry attempted." << LL_ENDL; + break; + } + } + + LL_DEBUGS("Avatar") << "succeeded" << LL_ENDL; + if (gSavedSettings.getBOOL("DebugAvatarAppearanceMessage")) + { + dump_sequential_xml(gAgentAvatarp->getFullname() + "_appearance_request_ok", result); + } + + } while (bRetry); +} + +/*static*/ +void LLAppearanceMgr::debugAppearanceUpdateCOF(const LLSD& content) +{ + dump_sequential_xml(gAgentAvatarp->getFullname() + "_appearance_request_error", content); + + LL_INFOS("Avatar") << "AIS COF, version received: " << content["expected"].asInteger() + << " ================================= " << LL_ENDL; + std::set ais_items, local_items; + const LLSD& cof_raw = content["cof_raw"]; + for (LLSD::array_const_iterator it = cof_raw.beginArray(); + it != cof_raw.endArray(); ++it) + { + const LLSD& item = *it; + if (item["parent_id"].asUUID() == LLAppearanceMgr::instance().getCOF()) + { + ais_items.insert(item["item_id"].asUUID()); + if (item["type"].asInteger() == 24) // link + { + LL_INFOS("Avatar") << "AIS Link: item_id: " << item["item_id"].asUUID() + << " linked_item_id: " << item["asset_id"].asUUID() + << " name: " << item["name"].asString() + << LL_ENDL; + } + else if (item["type"].asInteger() == 25) // folder link + { + LL_INFOS("Avatar") << "AIS Folder link: item_id: " << item["item_id"].asUUID() + << " linked_item_id: " << item["asset_id"].asUUID() + << " name: " << item["name"].asString() + << LL_ENDL; + } + else + { + LL_INFOS("Avatar") << "AIS Other: item_id: " << item["item_id"].asUUID() + << " linked_item_id: " << item["asset_id"].asUUID() + << " name: " << item["name"].asString() + << " type: " << item["type"].asInteger() + << LL_ENDL; + } + } + } + LL_INFOS("Avatar") << LL_ENDL; + LL_INFOS("Avatar") << "Local COF, version requested: " << content["observed"].asInteger() + << " ================================= " << LL_ENDL; + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + gInventory.collectDescendents(LLAppearanceMgr::instance().getCOF(), + cat_array, item_array, LLInventoryModel::EXCLUDE_TRASH); + for (S32 i = 0; i < item_array.size(); i++) + { + const LLViewerInventoryItem* inv_item = item_array.at(i).get(); + local_items.insert(inv_item->getUUID()); + LL_INFOS("Avatar") << "LOCAL: item_id: " << inv_item->getUUID() + << " linked_item_id: " << inv_item->getLinkedUUID() + << " name: " << inv_item->getName() + << " parent: " << inv_item->getParentUUID() + << LL_ENDL; + } + LL_INFOS("Avatar") << " ================================= " << LL_ENDL; + S32 local_only = 0, ais_only = 0; + for (std::set::iterator it = local_items.begin(); it != local_items.end(); ++it) + { + if (ais_items.find(*it) == ais_items.end()) + { + LL_INFOS("Avatar") << "LOCAL ONLY: " << *it << LL_ENDL; + local_only++; + } + } + for (std::set::iterator it = ais_items.begin(); it != ais_items.end(); ++it) + { + if (local_items.find(*it) == local_items.end()) + { + LL_INFOS("Avatar") << "AIS ONLY: " << *it << LL_ENDL; + ais_only++; + } + } + if (local_only == 0 && ais_only == 0) + { + LL_INFOS("Avatar") << "COF contents identical, only version numbers differ (req " + << content["observed"].asInteger() + << " rcv " << content["expected"].asInteger() + << ")" << LL_ENDL; + } +} + + +std::string LLAppearanceMgr::getAppearanceServiceURL() const +{ + if (gSavedSettings.getString("DebugAvatarAppearanceServiceURLOverride").empty()) + { + return mAppearanceServiceURL; + } + return gSavedSettings.getString("DebugAvatarAppearanceServiceURLOverride"); +} + +void show_created_outfit(LLUUID& folder_id, bool show_panel = true) +{ + if (!LLApp::isRunning()) + { + LL_WARNS() << "called during shutdown, skipping" << LL_ENDL; + return; + } + + LL_DEBUGS("Avatar") << "called" << LL_ENDL; + LLSD key; + + //EXT-7727. For new accounts inventory callback is created during login process + // and may be processed after login process is finished + if (show_panel) + { + LL_DEBUGS("Avatar") << "showing panel" << LL_ENDL; + LLFloaterSidePanelContainer::showPanel("appearance", "panel_outfits_inventory", key); + + } + LLOutfitsList *outfits_list = + dynamic_cast(LLFloaterSidePanelContainer::getPanel("appearance", "outfitslist_tab")); + if (outfits_list) + { + outfits_list->setSelectedOutfitByUUID(folder_id); + } + + LLAppearanceMgr::getInstance()->updateIsDirty(); + gAgentWearables.notifyLoadingFinished(); // New outfit is saved. + LLAppearanceMgr::getInstance()->updatePanelOutfitName(""); + + // For SSB, need to update appearance after we add a base outfit + // link, since, the COF version has changed. There is a race + // condition in initial outfit setup which can lead to rez + // failures - SH-3860. + LL_DEBUGS("Avatar") << "requesting appearance update after createBaseOutfitLink" << LL_ENDL; + LLPointer cb = new LLUpdateAppearanceOnDestroy; + LLAppearanceMgr::getInstance()->createBaseOutfitLink(folder_id, cb); +} + +void LLAppearanceMgr::onOutfitFolderCreated(const LLUUID& folder_id, bool show_panel) +{ + LLPointer cb = + new LLBoostFuncInventoryCallback(no_op_inventory_func, + boost::bind(&LLAppearanceMgr::onOutfitFolderCreatedAndClothingOrdered,this,folder_id,show_panel)); + updateClothingOrderingInfo(LLUUID::null, cb); +} + +void LLAppearanceMgr::onOutfitFolderCreatedAndClothingOrdered(const LLUUID& folder_id, bool show_panel) +{ + LLPointer cb = + new LLBoostFuncInventoryCallback(no_op_inventory_func, + boost::bind(show_created_outfit,folder_id,show_panel)); + bool copy_folder_links = false; + slamCategoryLinks(getCOF(), folder_id, copy_folder_links, cb); +} + +void LLAppearanceMgr::makeNewOutfitLinks(const std::string& new_folder_name, bool show_panel) +{ + if (!isAgentAvatarValid()) return; + + LLUIUsage::instance().logCommand("Avatar.CreateNewOutfit"); + + LL_DEBUGS("Avatar") << "creating new outfit" << LL_ENDL; + + gAgentWearables.notifyLoadingStarted(); + + // First, make a folder in the My Outfits directory. + const LLUUID parent_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + + gInventory.createNewCategory( + parent_id, + LLFolderType::FT_OUTFIT, + new_folder_name, + [show_panel](const LLUUID &new_cat_id) + { + LLAppearanceMgr::getInstance()->onOutfitFolderCreated(new_cat_id, show_panel); + }); +} + +void LLAppearanceMgr::wearBaseOutfit() +{ + const LLUUID& base_outfit_id = getBaseOutfitUUID(); + if (base_outfit_id.isNull()) return; + + updateCOF(base_outfit_id); +} + +void LLAppearanceMgr::removeItemsFromAvatar(const uuid_vec_t& ids_to_remove, nullary_func_t post_update_func) +{ + LL_DEBUGS("UIUsage") << "removeItemsFromAvatar" << LL_ENDL; + LLUIUsage::instance().logCommand("Avatar.RemoveItem"); + + if (ids_to_remove.empty()) + { + LL_WARNS() << "called with empty list, nothing to do" << LL_ENDL; + return; + } + LLPointer cb = new LLUpdateAppearanceOnDestroy(true, true, post_update_func); + for (uuid_vec_t::const_iterator it = ids_to_remove.begin(); it != ids_to_remove.end(); ++it) + { + const LLUUID& id_to_remove = *it; + const LLUUID& linked_item_id = gInventory.getLinkedItemID(id_to_remove); + LLViewerInventoryItem *item = gInventory.getItem(linked_item_id); + if (item && item->getType() == LLAssetType::AT_OBJECT) + { + LL_DEBUGS("Avatar") << "ATT removing attachment " << item->getName() << " id " << item->getUUID() << LL_ENDL; + } + if (item && item->getType() == LLAssetType::AT_BODYPART) + { + continue; + } + removeCOFItemLinks(linked_item_id, cb); + addDoomedTempAttachment(linked_item_id); + } +} + +void LLAppearanceMgr::removeItemFromAvatar(const LLUUID& id_to_remove, nullary_func_t post_update_func) +{ + uuid_vec_t ids_to_remove; + ids_to_remove.push_back(id_to_remove); + removeItemsFromAvatar(ids_to_remove, post_update_func); +} + + +// Adds the given item ID to mDoomedTempAttachmentIDs iff it's a temp attachment +void LLAppearanceMgr::addDoomedTempAttachment(const LLUUID& id_to_remove) +{ + LLViewerObject * attachmentp = gAgentAvatarp->findAttachmentByID(id_to_remove); + if (attachmentp && + attachmentp->isTempAttachment()) + { // If this is a temp attachment and we want to remove it, record the ID + // so it will be deleted when attachments are synced up with COF + mDoomedTempAttachmentIDs.insert(id_to_remove); + //LL_INFOS() << "Will remove temp attachment id " << id_to_remove << LL_ENDL; + } +} + +// Find AND REMOVES the given UUID from mDoomedTempAttachmentIDs +bool LLAppearanceMgr::shouldRemoveTempAttachment(const LLUUID& item_id) +{ + doomed_temp_attachments_t::iterator iter = mDoomedTempAttachmentIDs.find(item_id); + if (iter != mDoomedTempAttachmentIDs.end()) + { + mDoomedTempAttachmentIDs.erase(iter); + return true; + } + return false; +} + + +bool LLAppearanceMgr::moveWearable(LLViewerInventoryItem* item, bool closer_to_body) +{ + if (!item || !item->isWearableType()) return false; + if (item->getType() != LLAssetType::AT_CLOTHING) return false; + if (!gInventory.isObjectDescendentOf(item->getUUID(), getCOF())) return false; + + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLFindWearablesOfType filter_wearables_of_type(item->getWearableType()); + gInventory.collectDescendentsIf(getCOF(), cats, items, true, filter_wearables_of_type); + if (items.empty()) return false; + + // We assume that the items have valid descriptions. + std::sort(items.begin(), items.end(), WearablesOrderComparator(item->getWearableType())); + + if (closer_to_body && items.front() == item) return false; + if (!closer_to_body && items.back() == item) return false; + + LLInventoryModel::item_array_t::iterator it = std::find(items.begin(), items.end(), item); + if (items.end() == it) return false; + + + //swapping descriptions + closer_to_body ? --it : ++it; + LLViewerInventoryItem* swap_item = *it; + if (!swap_item) return false; + std::string tmp = swap_item->getActualDescription(); + swap_item->setDescription(item->getActualDescription()); + item->setDescription(tmp); + + // LL_DEBUGS("Inventory") << "swap, item " + // << ll_pretty_print_sd(item->asLLSD()) + // << " swap_item " + // << ll_pretty_print_sd(swap_item->asLLSD()) << LL_ENDL; + + // FIXME switch to use AISv3 where supported. + //items need to be updated on a dataserver + item->setComplete(true); + item->updateServer(false); + gInventory.updateItem(item); + + swap_item->setComplete(true); + swap_item->updateServer(false); + gInventory.updateItem(swap_item); + + //to cause appearance of the agent to be updated + bool result = false; + if ((result = gAgentWearables.moveWearable(item, closer_to_body))) + { + gAgentAvatarp->wearableUpdated(item->getWearableType()); + } + + setOutfitDirty(true); + + //*TODO do we need to notify observers here in such a way? + gInventory.notifyObservers(); + + return result; +} + +//static +void LLAppearanceMgr::sortItemsByActualDescription(LLInventoryModel::item_array_t& items) +{ + if (items.size() < 2) return; + + std::sort(items.begin(), items.end(), sort_by_actual_description); +} + +//#define DUMP_CAT_VERBOSE + +void LLAppearanceMgr::dumpCat(const LLUUID& cat_id, const std::string& msg) +{ + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + gInventory.collectDescendents(cat_id, cats, items, LLInventoryModel::EXCLUDE_TRASH); + +#ifdef DUMP_CAT_VERBOSE + LL_INFOS() << LL_ENDL; + LL_INFOS() << str << LL_ENDL; + S32 hitcount = 0; + for(S32 i=0; igetName() <getLinkedItem() : NULL; + LLUUID asset_id; + if (linked_item) + { + asset_id = linked_item->getAssetUUID(); + } + LL_DEBUGS("Avatar") << self_av_string() << msg << " " << i <<" " << (item ? item->getName() : "(nullitem)") << " " << asset_id.asString() << LL_ENDL; + } +} + +bool LLAppearanceMgr::mActive = true; + +LLAppearanceMgr::LLAppearanceMgr(): + mAttachmentInvLinkEnabled(false), + mOutfitIsDirty(false), + mOutfitLocked(false), + mInFlightTimer(), + mIsInUpdateAppearanceFromCOF(false), + mOutstandingAppearanceBakeRequest(false), + mRerequestAppearanceBake(false) +{ + LLOutfitObserver& outfit_observer = LLOutfitObserver::instance(); + // unlock outfit on save operation completed + outfit_observer.addCOFSavedCallback(boost::bind( + &LLAppearanceMgr::setOutfitLocked, this, false)); + + mUnlockOutfitTimer.reset(new LLOutfitUnLockTimer(gSavedSettings.getS32( + "OutfitOperationsTimeout"))); + + gIdleCallbacks.addFunction(&LLAttachmentsMgr::onIdle, NULL); + gIdleCallbacks.addFunction(&LLAppearanceMgr::onIdle, NULL); //sheduling appearance update requests +} + +LLAppearanceMgr::~LLAppearanceMgr() +{ + mActive = false; +} + +void LLAppearanceMgr::setAttachmentInvLinkEnable(bool val) +{ + LL_DEBUGS("Avatar") << "setAttachmentInvLinkEnable => " << (int) val << LL_ENDL; + mAttachmentInvLinkEnabled = val; +} +boost::signals2::connection LLAppearanceMgr::setAttachmentsChangedCallback(attachments_changed_callback_t cb) +{ + return mAttachmentsChangeSignal.connect(cb); +} + +void dumpAttachmentSet(const std::set& atts, const std::string& msg) +{ + LL_INFOS() << msg << LL_ENDL; + for (std::set::const_iterator it = atts.begin(); + it != atts.end(); + ++it) + { + LLUUID item_id = *it; + LLViewerInventoryItem *item = gInventory.getItem(item_id); + if (item) + LL_INFOS() << "atts " << item->getName() << LL_ENDL; + else + LL_INFOS() << "atts " << "UNKNOWN[" << item_id.asString() << "]" << LL_ENDL; + } + LL_INFOS() << LL_ENDL; +} + +void LLAppearanceMgr::registerAttachment(const LLUUID& item_id) +{ + LLViewerInventoryItem *item = gInventory.getItem(item_id); + LL_DEBUGS("Avatar") << "ATT registering attachment " + << (item ? item->getName() : "UNKNOWN") << " " << item_id << LL_ENDL; + gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id); + + LLAttachmentsMgr::instance().onAttachmentArrived(item_id); + + mAttachmentsChangeSignal(); +} + +void LLAppearanceMgr::unregisterAttachment(const LLUUID& item_id) +{ + LLViewerInventoryItem *item = gInventory.getItem(item_id); + LL_DEBUGS("Avatar") << "ATT unregistering attachment " + << (item ? item->getName() : "UNKNOWN") << " " << item_id << LL_ENDL; + gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id); + + LLAttachmentsMgr::instance().onDetachCompleted(item_id); + if (mAttachmentInvLinkEnabled && isLinkedInCOF(item_id)) + { + LL_DEBUGS("Avatar") << "ATT removing COF link for attachment " + << (item ? item->getName() : "UNKNOWN") << " " << item_id << LL_ENDL; + LLAppearanceMgr::removeCOFItemLinks(item_id); + } + else + { + //LL_INFOS() << "no link changes, inv link not enabled" << LL_ENDL; + } + + mAttachmentsChangeSignal(); +} + +bool LLAppearanceMgr::getIsInCOF(const LLUUID& obj_id) const +{ + const LLUUID& cof = getCOF(); + if (obj_id == cof) + return true; + const LLInventoryObject* obj = gInventory.getObject(obj_id); + if (obj && obj->getParentUUID() == cof) + return true; + return false; +} + +bool LLAppearanceMgr::getIsInCOF(const LLInventoryObject* obj) const +{ + const LLUUID& cof = getCOF(); + if (obj->getUUID() == cof) + return true; + if (obj && obj->getParentUUID() == cof) + return true; + return false; +} + +bool LLAppearanceMgr::getIsProtectedCOFItem(const LLUUID& obj_id) const +{ + if (!getIsInCOF(obj_id)) return false; + + // If a non-link somehow ended up in COF, allow deletion. + const LLInventoryObject *obj = gInventory.getObject(obj_id); + if (obj && !obj->getIsLinkType()) + { + return false; + } + + // For now, don't allow direct deletion from the COF. Instead, force users + // to choose "Detach" or "Take Off". + return true; +} + +bool LLAppearanceMgr::getIsProtectedCOFItem(const LLInventoryObject* obj) const +{ + if (!getIsInCOF(obj)) return false; + + // If a non-link somehow ended up in COF, allow deletion. + if (obj && !obj->getIsLinkType()) + { + return false; + } + + // For now, don't allow direct deletion from the COF. Instead, force users + // to choose "Detach" or "Take Off". + return true; +} + +class CallAfterCategoryFetchStage2: public LLInventoryFetchItemsObserver +{ +public: + CallAfterCategoryFetchStage2(const uuid_vec_t& ids, + nullary_func_t callable) : + LLInventoryFetchItemsObserver(ids), + mCallable(callable) + { + } + ~CallAfterCategoryFetchStage2() + { + } + virtual void done() + { + LL_INFOS() << this << " done with incomplete " << mIncomplete.size() + << " complete " << mComplete.size() << " calling callable" << LL_ENDL; + + gInventory.removeObserver(this); + doOnIdleOneTime(mCallable); + delete this; + } +protected: + nullary_func_t mCallable; +}; + +class CallAfterCategoryFetchStage1: public LLInventoryFetchDescendentsObserver +{ +public: + CallAfterCategoryFetchStage1(const LLUUID& cat_id, nullary_func_t callable) : + LLInventoryFetchDescendentsObserver(cat_id), + mCallable(callable) + { + } + ~CallAfterCategoryFetchStage1() + { + } + /*virtual*/ void startFetch() + { + bool ais3 = AISAPI::isAvailable(); + for (uuid_vec_t::const_iterator it = mIDs.begin(); it != mIDs.end(); ++it) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(*it); + if (!cat) continue; + if (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) + { + // CHECK IT: isCategoryComplete() checks both version and descendant count but + // fetch() only works for Unknown version and doesn't care about descentants, + // as result fetch won't start and folder will potentially get stuck as + // incomplete in observer. + // Likely either both should use only version or both should check descendants. + cat->fetch(); //blindly fetch it without seeing if anything else is fetching it. + mIncomplete.push_back(*it); //Add to list of things being downloaded for this observer. + } + else if (!isCategoryComplete(cat)) + { + LL_DEBUGS("Inventory") << "Categoty " << *it << " incomplete despite having version" << LL_ENDL; + LLInventoryModelBackgroundFetch::instance().scheduleFolderFetch(*it, true); + mIncomplete.push_back(*it); + } + else if (ais3) + { + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(cat->getUUID(), cats, items); + + if (items) + { + S32 complete_count = 0; + S32 incomplete_count = 0; + for (LLInventoryModel::item_array_t::const_iterator it = items->begin(); it < items->end(); ++it) + { + if (!(*it)->isFinished()) + { + incomplete_count++; + } + else + { + complete_count++; + } + } + // AIS can fetch couple items, but if there + // is more than a dozen it will be very slow + // it's faster to get whole folder in such case + if (incomplete_count > LLInventoryFetchItemsObserver::MAX_INDIVIDUAL_ITEM_REQUESTS + || (incomplete_count > 1 && complete_count == 0)) + { + LLInventoryModelBackgroundFetch::instance().scheduleFolderFetch(*it, true); + mIncomplete.push_back(*it); + } + else + { + // let stage2 handle incomplete ones + mComplete.push_back(*it); + } + } + // else should have been handled by isCategoryComplete + } + else + { + mComplete.push_back(*it); + } + } + } + virtual void done() + { + if (mComplete.size() <= 0) + { + // Ex: timeout + LL_WARNS() << "Failed to load data. Removing observer " << LL_ENDL; + gInventory.removeObserver(this); + doOnIdleOneTime(mCallable); + + delete this; + return; + } + + // What we do here is get the complete information on the + // items in the requested category, and set up an observer + // that will wait for that to happen. + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(mComplete.front(), cats, items); + + S32 count = items->size(); + if(!count) + { + LL_WARNS() << "Nothing fetched in category " << mComplete.front() + << LL_ENDL; + gInventory.removeObserver(this); + doOnIdleOneTime(mCallable); + + delete this; + return; + } + + LLViewerInventoryCategory* cat = gInventory.getCategory(mComplete.front()); + S32 version = cat ? cat->getVersion() : -2; + LL_INFOS() << "stage1, category " << mComplete.front() << " got " << count << " items, version " << version << " passing to stage2 " << LL_ENDL; + uuid_vec_t ids; + for(S32 i = 0; i < count; ++i) + { + ids.push_back(items->at(i)->getUUID()); + } + + gInventory.removeObserver(this); + + // do the fetch + CallAfterCategoryFetchStage2 *stage2 = new CallAfterCategoryFetchStage2(ids, mCallable); + stage2->startFetch(); + if(stage2->isFinished()) + { + // everything is already here - call done. + stage2->done(); + } + else + { + // it's all on it's way - add an observer, and the inventory + // will call done for us when everything is here. + gInventory.addObserver(stage2); + } + delete this; + } +protected: + nullary_func_t mCallable; +}; + +void callAfterCOFFetch(nullary_func_t cb) +{ + if (AISAPI::isAvailable()) + { + // For reliability assume that we have no relevant cache, so + // fetch cof along with items cof's links point to. + LLInventoryModelBackgroundFetch::getInstance()->fetchCOF(cb); + } + else + { + LL_INFOS() << "AIS API v3 not available, using callAfterCategoryFetch" << LL_ENDL; + LLUUID cat_id = LLAppearanceMgr::instance().getCOF(); + LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); + + // Special case, startup should have marked cof as FETCH_RECURSIVE + // to prevent dupplicate request, remove that + cat->setFetching(LLViewerInventoryCategory::FETCH_NONE); + callAfterCategoryFetch(cat_id, cb); + } +} + +void callAfterCategoryFetch(const LLUUID& cat_id, nullary_func_t cb) +{ + CallAfterCategoryFetchStage1* stage1 = new CallAfterCategoryFetchStage1(cat_id, cb); + stage1->startFetch(); + if (stage1->isFinished()) + { + stage1->done(); + } + else + { + gInventory.addObserver(stage1); + } +} + +void callAfterCategoryLinksFetch(const LLUUID &cat_id, nullary_func_t cb) +{ + if (AISAPI::isAvailable()) + { + // Assume that we have no relevant cache. Fetch folder, and items folder's links point to. + LLInventoryModelBackgroundFetch::getInstance()->fetchFolderAndLinks(cat_id, cb); + } + else + { + LL_WARNS() << "AIS API v3 not available, can't use AISAPI::FetchCOF" << LL_ENDL; + callAfterCategoryFetch(cat_id, cb); + } + +} + +void add_wearable_type_counts(const uuid_vec_t& ids, + S32& clothing_count, + S32& bodypart_count, + S32& object_count, + S32& other_count) +{ + for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it) + { + const LLUUID& item_id_to_wear = *it; + LLViewerInventoryItem* item_to_wear = gInventory.getItem(item_id_to_wear); + if (item_to_wear) + { + if (item_to_wear->getType() == LLAssetType::AT_CLOTHING) + { + clothing_count++; + } + else if (item_to_wear->getType() == LLAssetType::AT_BODYPART) + { + bodypart_count++; + } + else if (item_to_wear->getType() == LLAssetType::AT_OBJECT) + { + object_count++; + } + else + { + other_count++; + } + } + else + { + other_count++; + } + } +} + +void wear_multiple(const uuid_vec_t& ids, bool replace) +{ + S32 clothing_count = 0; + S32 bodypart_count = 0; + S32 object_count = 0; + S32 other_count = 0; + add_wearable_type_counts(ids, clothing_count, bodypart_count, object_count, other_count); + + LLPointer cb = NULL; + if (clothing_count > 0 || bodypart_count > 0) + { + cb = new LLUpdateAppearanceOnDestroy; + } + LLAppearanceMgr::instance().wearItemsOnAvatar(ids, true, replace, cb); +} + +// SLapp for easy-wearing of a stock (library) avatar +// +class LLWearFolderHandler : public LLCommandHandler +{ +public: + // not allowed from outside the app + LLWearFolderHandler() : LLCommandHandler("wear_folder", UNTRUSTED_BLOCK) { } + + bool handle(const LLSD& tokens, + const LLSD& query_map, + const std::string& grid, + LLMediaCtrl* web) + { + LLSD::UUID folder_uuid; + + if (folder_uuid.isNull() && query_map.has("folder_name")) + { + std::string outfit_folder_name = query_map["folder_name"]; + folder_uuid = findDescendentCategoryIDByName( + gInventory.getLibraryRootFolderID(), + outfit_folder_name); + } + if (folder_uuid.isNull() && query_map.has("folder_id")) + { + folder_uuid = query_map["folder_id"].asUUID(); + } + + if (folder_uuid.notNull()) + { + LLPointer category = new LLInventoryCategory(folder_uuid, + LLUUID::null, + LLFolderType::FT_CLOTHING, + "Quick Appearance"); + if ( gInventory.getCategory( folder_uuid ) != NULL ) + { + // Assume this is coming from the predefined avatars web floater + LLUIUsage::instance().logCommand("Avatar.WearPredefinedAppearance"); + LLAppearanceMgr::getInstance()->wearInventoryCategory(category, true, false); + + // *TODOw: This may not be necessary if initial outfit is chosen already -- josh + gAgent.setOutfitChosen(true); + } + } + + // release avatar picker keyboard focus + gFocusMgr.setKeyboardFocus( NULL ); + + return true; + } +}; + +LLWearFolderHandler gWearFolderHandler; diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index f41003c509..e6953c5dda 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -1,5691 +1,5691 @@ -/** - * @file llappviewer.cpp - * @brief The LLAppViewer class definitions - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llappviewer.h" - -// Viewer includes -#include "llversioninfo.h" -#include "llfeaturemanager.h" -#include "lluictrlfactory.h" -#include "lltexteditor.h" -#include "llenvironment.h" -#include "llerrorcontrol.h" -#include "lleventtimer.h" -#include "llfile.h" -#include "llviewertexturelist.h" -#include "llgroupmgr.h" -#include "llagent.h" -#include "llagentcamera.h" -#include "llagentlanguage.h" -#include "llagentui.h" -#include "llagentwearables.h" -#include "lldirpicker.h" -#include "llfloaterimcontainer.h" -#include "llimprocessing.h" -#include "llwindow.h" -#include "llviewerstats.h" -#include "llviewerstatsrecorder.h" -#include "llkeyconflict.h" // for legacy keybinding support, remove later -#include "llmarketplacefunctions.h" -#include "llmarketplacenotifications.h" -#include "llmd5.h" -#include "llmeshrepository.h" -#include "llpumpio.h" -#include "llmimetypes.h" -#include "llslurl.h" -#include "llstartup.h" -#include "llfocusmgr.h" -#include "llurlfloaterdispatchhandler.h" -#include "llviewerjoystick.h" -#include "llallocator.h" -#include "llcalc.h" -#include "llconversationlog.h" -#if LL_WINDOWS -#include "lldxhardware.h" -#endif -#include "lltexturestats.h" -#include "lltrace.h" -#include "lltracethreadrecorder.h" -#include "llviewerwindow.h" -#include "llviewerdisplay.h" -#include "llviewermedia.h" -#include "llviewerparcelaskplay.h" -#include "llviewerparcelmedia.h" -#include "llviewershadermgr.h" -#include "llviewermediafocus.h" -#include "llviewermessage.h" -#include "llviewerobjectlist.h" -#include "llworldmap.h" -#include "llmutelist.h" -#include "llviewerhelp.h" -#include "lluicolortable.h" -#include "llurldispatcher.h" -#include "llurlhistory.h" -#include "llrender.h" -#include "llteleporthistory.h" -#include "lltoast.h" -#include "llsdutil_math.h" -#include "lllocationhistory.h" -#include "llfasttimerview.h" -#include "llvector4a.h" -#include "llviewermenufile.h" -#include "llvoicechannel.h" -#include "llvoavatarself.h" -#include "llurlmatch.h" -#include "lltextutil.h" -#include "lllogininstance.h" -#include "llprogressview.h" -#include "llvocache.h" -#include "lldiskcache.h" -#include "llvopartgroup.h" -#include "llweb.h" -#include "llspellcheck.h" -#include "llscenemonitor.h" -#include "llavatarrenderinfoaccountant.h" -#include "lllocalbitmaps.h" -#include "llperfstats.h" -#include "llgltfmateriallist.h" - -// Linden library includes -#include "llavatarnamecache.h" -#include "lldiriterator.h" -#include "llexperiencecache.h" -#include "llimagej2c.h" -#include "llmemory.h" -#include "llprimitive.h" -#include "llurlaction.h" -#include "llurlentry.h" -#include "llvolumemgr.h" -#include "llxfermanager.h" -#include "llphysicsextensions.h" - -#include "llnotificationmanager.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" - -#include "llleap.h" -#include "stringize.h" -#include "llcoros.h" -#include "llexception.h" -#if !LL_LINUX -#include "cef/dullahan_version.h" -#include "vlc/libvlc_version.h" -#endif // LL_LINUX - -#if LL_DARWIN -#include "llwindowmacosx.h" -#endif - -// Third party library includes -#include -#include -#include -#include - -#if LL_WINDOWS -# include // For _SH_DENYWR in processMarkerFiles -#else -# include // For processMarkerFiles -#endif - -#include "llapr.h" -#include - -#include "llviewerinput.h" -#include "lllfsthread.h" -#include "llworkerthread.h" -#include "lltexturecache.h" -#include "lltexturefetch.h" -#include "llimageworker.h" -#include "llevents.h" - -// The files below handle dependencies from cleanup. -#include "llkeyframemotion.h" -#include "llworldmap.h" -#include "llhudmanager.h" -#include "lltoolmgr.h" -#include "llassetstorage.h" -#include "llpolymesh.h" -#include "llproxy.h" -#include "llaudioengine.h" -#include "llstreamingaudio.h" -#include "llviewermenu.h" -#include "llselectmgr.h" -#include "lltrans.h" -#include "lltransutil.h" -#include "lltracker.h" -#include "llviewerparcelmgr.h" -#include "llworldmapview.h" -#include "llpostprocess.h" - -#include "lldebugview.h" -#include "llconsole.h" -#include "llcontainerview.h" -#include "lltooltip.h" - -#include "llsdutil.h" -#include "llsdserialize.h" - -#include "llworld.h" -#include "llhudeffecttrail.h" -#include "llslurl.h" -#include "llurlregistry.h" -#include "llwatchdog.h" - -// Included so that constants/settings might be initialized -// in save_settings_to_globals() -#include "llbutton.h" -#include "llstatusbar.h" -#include "llsurface.h" -#include "llvosky.h" -#include "llvotree.h" -#include "llvoavatar.h" -#include "llfolderview.h" -#include "llagentpilot.h" -#include "llvovolume.h" -#include "llflexibleobject.h" -#include "llvosurfacepatch.h" -#include "llviewerfloaterreg.h" -#include "llcommandlineparser.h" -#include "llfloatermemleak.h" -#include "llfloaterreg.h" -#include "llfloatersimplesnapshot.h" -#include "llfloatersnapshot.h" -#include "llsidepanelinventory.h" -#include "llatmosphere.h" - -// includes for idle() idleShutdown() -#include "llviewercontrol.h" -#include "lleventnotifier.h" -#include "llcallbacklist.h" -#include "lldeferredsounds.h" -#include "pipeline.h" -#include "llgesturemgr.h" -#include "llsky.h" -#include "llvlmanager.h" -#include "llviewercamera.h" -#include "lldrawpoolbump.h" -#include "llvieweraudio.h" -#include "llimview.h" -#include "llviewerthrottle.h" -#include "llparcel.h" -#include "llavatariconctrl.h" -#include "llgroupiconctrl.h" -#include "llviewerassetstats.h" -#include "workqueue.h" -using namespace LL; - -// Include for security api initialization -#include "llsecapi.h" -#include "llmachineid.h" -#include "llcleanup.h" - -#include "llcoproceduremanager.h" -#include "llviewereventrecorder.h" - -// *FIX: These extern globals should be cleaned up. -// The globals either represent state/config/resource-storage of either -// this app, or another 'component' of the viewer. App globals should be -// moved into the app class, where as the other globals should be -// moved out of here. -// If a global symbol reference seems valid, it will be included -// via header files above. - -//---------------------------------------------------------------------------- -// llviewernetwork.h -#include "llviewernetwork.h" -// define a self-registering event API object -#include "llappviewerlistener.h" - -#if LL_LINUX && LL_GTK -#include "glib.h" -#endif // (LL_LINUX) && LL_GTK - -#if LL_MSVC -// disable boost::lexical_cast warning -#pragma warning (disable:4702) -#endif - -static LLAppViewerListener sAppViewerListener(LLAppViewer::instance); - -////// Windows-specific includes to the bottom - nasty defines in these pollute the preprocessor -// -//---------------------------------------------------------------------------- -// viewer.cpp - these are only used in viewer, should be easily moved. - -#if LL_DARWIN -extern void init_apple_menu(const char* product); -#endif // LL_DARWIN - -extern bool gRandomizeFramerate; -extern bool gPeriodicSlowFrame; -extern bool gDebugGL; - -#if LL_DARWIN -extern bool gHiDPISupport; -#endif - -//////////////////////////////////////////////////////////// -// All from the last globals push... - -F32 gSimLastTime; // Used in LLAppViewer::init and send_viewer_stats() -F32 gSimFrames; - -bool gShowObjectUpdates = false; -bool gUseQuickTime = true; - -eLastExecEvent gLastExecEvent = LAST_EXEC_NORMAL; -S32 gLastExecDuration = -1; // (<0 indicates unknown) - -#if LL_WINDOWS -# define LL_PLATFORM_KEY "win" -#elif LL_DARWIN -# define LL_PLATFORM_KEY "mac" -#elif LL_LINUX -# define LL_PLATFORM_KEY "lnx" -#else -# error "Unknown Platform" -#endif -const char* gPlatform = LL_PLATFORM_KEY; - -LLSD gDebugInfo; - -U32 gFrameCount = 0; -U32 gForegroundFrameCount = 0; // number of frames that app window was in foreground -LLPumpIO* gServicePump = NULL; - -U64MicrosecondsImplicit gFrameTime = 0; -F32SecondsImplicit gFrameTimeSeconds = 0.f; -F32SecondsImplicit gFrameIntervalSeconds = 0.f; -F32 gFPSClamped = 10.f; // Pretend we start at target rate. -F32 gFrameDTClamped = 0.f; // Time between adjacent checks to network for packets -U64MicrosecondsImplicit gStartTime = 0; // gStartTime is "private", used only to calculate gFrameTimeSeconds - -LLTimer gRenderStartTime; -LLFrameTimer gForegroundTime; -LLFrameTimer gLoggedInTime; -LLTimer gLogoutTimer; -static const F32 LOGOUT_REQUEST_TIME = 6.f; // this will be cut short by the LogoutReply msg. -F32 gLogoutMaxTime = LOGOUT_REQUEST_TIME; - - -S32 gPendingMetricsUploads = 0; - - -bool gDisconnected = false; - -// used to restore texture state after a mode switch -LLFrameTimer gRestoreGLTimer; -bool gRestoreGL = false; -bool gUseWireframe = false; - -LLMemoryInfo gSysMemory; -U64Bytes gMemoryAllocated(0); // updated in display_stats() in llviewerdisplay.cpp - -std::string gLastVersionChannel; - -LLVector3 gWindVec(3.0, 3.0, 0.0); -LLVector3 gRelativeWindVec(0.0, 0.0, 0.0); - -U32 gPacketsIn = 0; - -bool gPrintMessagesThisFrame = false; - -bool gRandomizeFramerate = false; -bool gPeriodicSlowFrame = false; - -bool gCrashOnStartup = false; -bool gLLErrorActivated = false; -bool gLogoutInProgress = false; - -bool gSimulateMemLeak = false; - -// We don't want anyone, especially threads working on the graphics pipeline, -// to have to block due to this WorkQueue being full. -WorkQueue gMainloopWork("mainloop", 1024*1024); - -//////////////////////////////////////////////////////////// -// Internal globals... that should be removed. -static std::string gArgs; -const int MAX_MARKER_LENGTH = 1024; -const std::string MARKER_FILE_NAME("SecondLife.exec_marker"); -const std::string START_MARKER_FILE_NAME("SecondLife.start_marker"); -const std::string ERROR_MARKER_FILE_NAME("SecondLife.error_marker"); -const std::string LLERROR_MARKER_FILE_NAME("SecondLife.llerror_marker"); -const std::string LOGOUT_MARKER_FILE_NAME("SecondLife.logout_marker"); -static bool gDoDisconnect = false; -static std::string gLaunchFileOnQuit; - -// Used on Win32 for other apps to identify our window (eg, win_setup) -const char* const VIEWER_WINDOW_CLASSNAME = "Second Life"; - -//---------------------------------------------------------------------------- - -// List of entries from strings.xml to always replace -static std::set default_trans_args; -void init_default_trans_args() -{ - default_trans_args.insert("SECOND_LIFE"); // World - default_trans_args.insert("APP_NAME"); - default_trans_args.insert("CAPITALIZED_APP_NAME"); - default_trans_args.insert("SECOND_LIFE_GRID"); - default_trans_args.insert("SUPPORT_SITE"); - // This URL shows up in a surprising number of places in various skin - // files. We really only want to have to maintain a single copy of it. - default_trans_args.insert("create_account_url"); -} - -struct SettingsFile : public LLInitParam::Block -{ - Mandatory name; - Optional file_name; - Optional required, - persistent; - Optional file_name_setting; - - SettingsFile() - : name("name"), - file_name("file_name"), - required("required", false), - persistent("persistent", true), - file_name_setting("file_name_setting") - {} -}; - -struct SettingsGroup : public LLInitParam::Block -{ - Mandatory name; - Mandatory path_index; - Multiple files; - - SettingsGroup() - : name("name"), - path_index("path_index"), - files("file") - {} -}; - -struct SettingsFiles : public LLInitParam::Block -{ - Multiple groups; - - SettingsFiles() - : groups("group") - {} -}; - -static std::string gWindowTitle; - -//---------------------------------------------------------------------------- -// Metrics logging control constants -//---------------------------------------------------------------------------- -static const F32 METRICS_INTERVAL_DEFAULT = 600.0; -static const F32 METRICS_INTERVAL_QA = 30.0; -static F32 app_metrics_interval = METRICS_INTERVAL_DEFAULT; -static bool app_metrics_qa_mode = false; - -void idle_afk_check() -{ - // check idle timers - F32 current_idle = gAwayTriggerTimer.getElapsedTimeF32(); - F32 afk_timeout = gSavedSettings.getS32("AFKTimeout"); - if (afk_timeout && (current_idle > afk_timeout) && ! gAgent.getAFK()) - { - LL_INFOS("IdleAway") << "Idle more than " << afk_timeout << " seconds: automatically changing to Away status" << LL_ENDL; - gAgent.setAFK(); - } -} - -// A callback set in LLAppViewer::init() -static void ui_audio_callback(const LLUUID& uuid) -{ - if (gAudiop) - { - SoundData soundData(uuid, gAgent.getID(), 1.0f, LLAudioEngine::AUDIO_TYPE_UI); - gAudiop->triggerSound(soundData); - } -} - -// A callback set in LLAppViewer::init() -static void deferred_ui_audio_callback(const LLUUID& uuid) -{ - if (gAudiop) - { - SoundData soundData(uuid, gAgent.getID(), 1.0f, LLAudioEngine::AUDIO_TYPE_UI); - LLDeferredSounds::instance().deferSound(soundData); - } -} - -bool create_text_segment_icon_from_url_match(LLUrlMatch* match,LLTextBase* base) -{ - if(!match || !base || base->getPlainText()) - return false; - - LLUUID match_id = match->getID(); - - LLIconCtrl* icon; - - if( match->getMenuName() == "menu_url_group.xml" // See LLUrlEntryGroup constructor - || gAgent.isInGroup(match_id, true)) //This check seems unfiting, urls are either /agent or /group - { - LLGroupIconCtrl::Params icon_params; - icon_params.group_id = match_id; - icon_params.rect = LLRect(0, 16, 16, 0); - icon_params.visible = true; - icon = LLUICtrlFactory::instance().create(icon_params); - } - else - { - LLAvatarIconCtrl::Params icon_params; - icon_params.avatar_id = match_id; - icon_params.rect = LLRect(0, 16, 16, 0); - icon_params.visible = true; - icon = LLUICtrlFactory::instance().create(icon_params); - } - - LLInlineViewSegment::Params params; - params.force_newline = false; - params.view = icon; - params.left_pad = 4; - params.right_pad = 4; - params.top_pad = -2; - params.bottom_pad = 2; - - base->appendWidget(params," ",false); - - return true; -} - -// Use these strictly for things that are constructed at startup, -// or for things that are performance critical. JC -static void settings_to_globals() -{ - LLSurface::setTextureSize(gSavedSettings.getU32("RegionTextureSize")); - -#if LL_DARWIN - LLRender::sGLCoreProfile = true; -#else - LLRender::sGLCoreProfile = gSavedSettings.getBOOL("RenderGLContextCoreProfile"); -#endif - LLRender::sNsightDebugSupport = gSavedSettings.getBOOL("RenderNsightDebugSupport"); - LLImageGL::sGlobalUseAnisotropic = gSavedSettings.getBOOL("RenderAnisotropic"); - LLImageGL::sCompressTextures = gSavedSettings.getBOOL("RenderCompressTextures"); - LLVOVolume::sLODFactor = llclamp(gSavedSettings.getF32("RenderVolumeLODFactor"), 0.01f, MAX_LOD_FACTOR); - LLVOVolume::sDistanceFactor = 1.f-LLVOVolume::sLODFactor * 0.1f; - LLVolumeImplFlexible::sUpdateFactor = gSavedSettings.getF32("RenderFlexTimeFactor"); - LLVOTree::sTreeFactor = gSavedSettings.getF32("RenderTreeLODFactor"); - LLVOAvatar::sLODFactor = llclamp(gSavedSettings.getF32("RenderAvatarLODFactor"), 0.f, MAX_AVATAR_LOD_FACTOR); - LLVOAvatar::sPhysicsLODFactor = llclamp(gSavedSettings.getF32("RenderAvatarPhysicsLODFactor"), 0.f, MAX_AVATAR_LOD_FACTOR); - LLVOAvatar::updateImpostorRendering(gSavedSettings.getU32("RenderAvatarMaxNonImpostors")); - LLVOAvatar::sVisibleInFirstPerson = gSavedSettings.getBOOL("FirstPersonAvatarVisible"); - // clamp auto-open time to some minimum usable value - LLFolderView::sAutoOpenTime = llmax(0.25f, gSavedSettings.getF32("FolderAutoOpenDelay")); - LLSelectMgr::sRectSelectInclusive = gSavedSettings.getBOOL("RectangleSelectInclusive"); - LLSelectMgr::sRenderHiddenSelections = gSavedSettings.getBOOL("RenderHiddenSelections"); - LLSelectMgr::sRenderLightRadius = gSavedSettings.getBOOL("RenderLightRadius"); - - gAgentPilot.setNumRuns(gSavedSettings.getS32("StatsNumRuns")); - gAgentPilot.setQuitAfterRuns(gSavedSettings.getBOOL("StatsQuitAfterRuns")); - gAgent.setHideGroupTitle(gSavedSettings.getBOOL("RenderHideGroupTitle")); - - gDebugWindowProc = gSavedSettings.getBOOL("DebugWindowProc"); - gShowObjectUpdates = gSavedSettings.getBOOL("ShowObjectUpdates"); - LLWorldMapView::setScaleSetting(gSavedSettings.getF32("MapScale")); - -#if LL_DARWIN - LLWindowMacOSX::sUseMultGL = gSavedSettings.getBOOL("RenderAppleUseMultGL"); - gHiDPISupport = gSavedSettings.getBOOL("RenderHiDPI"); -#endif -} - -static void settings_modify() -{ - LLPipeline::sRenderTransparentWater = gSavedSettings.getBOOL("RenderTransparentWater"); - LLPipeline::sRenderDeferred = true; // false is deprecated - LLRenderTarget::sUseFBO = LLPipeline::sRenderDeferred; - LLVOSurfacePatch::sLODFactor = gSavedSettings.getF32("RenderTerrainLODFactor"); - LLVOSurfacePatch::sLODFactor *= LLVOSurfacePatch::sLODFactor; //square lod factor to get exponential range of [1,4] - gDebugGL = gDebugGLSession || gDebugSession; - gDebugPipeline = gSavedSettings.getBOOL("RenderDebugPipeline"); -} - -class LLFastTimerLogThread : public LLThread -{ -public: - std::string mFile; - - LLFastTimerLogThread(std::string& test_name) : LLThread("fast timer log") - { - std::string file_name = test_name + std::string(".slp"); - mFile = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, file_name); - } - - void run() - { - llofstream os(mFile.c_str()); - - while (!LLAppViewer::instance()->isQuitting()) - { - LLTrace::BlockTimer::writeLog(os); - os.flush(); - ms_sleep(32); - } - - os.close(); - } -}; - -//virtual -bool LLAppViewer::initSLURLHandler() -{ - // does nothing unless subclassed - return false; -} - -//virtual -bool LLAppViewer::sendURLToOtherInstance(const std::string& url) -{ - // does nothing unless subclassed - return false; -} - -//---------------------------------------------------------------------------- -// LLAppViewer definition - -// Static members. -// The single viewer app. -LLAppViewer* LLAppViewer::sInstance = NULL; -LLTextureCache* LLAppViewer::sTextureCache = NULL; -LLImageDecodeThread* LLAppViewer::sImageDecodeThread = NULL; -LLTextureFetch* LLAppViewer::sTextureFetch = NULL; -LLPurgeDiskCacheThread* LLAppViewer::sPurgeDiskCacheThread = NULL; - -std::string getRuntime() -{ - return llformat("%.4f", (F32)LLTimer::getElapsedSeconds().value()); -} - -LLAppViewer::LLAppViewer() -: mMarkerFile(), - mLogoutMarkerFile(), - mReportedCrash(false), - mNumSessions(0), - mGeneralThreadPool(nullptr), - mPurgeCache(false), - mPurgeCacheOnExit(false), - mPurgeUserDataOnExit(false), - mSecondInstance(false), - mUpdaterNotFound(false), - mSavedFinalSnapshot(false), - mSavePerAccountSettings(false), // don't save settings on logout unless login succeeded. - mQuitRequested(false), - mLogoutRequestSent(false), - mLastAgentControlFlags(0), - mLastAgentForceUpdate(0), - mMainloopTimeout(NULL), - mAgentRegionLastAlive(false), - mRandomizeFramerate(LLCachedControl(gSavedSettings,"Randomize Framerate", false)), - mPeriodicSlowFrame(LLCachedControl(gSavedSettings,"Periodic Slow Frame", false)), - mFastTimerLogThread(NULL), - mSettingsLocationList(NULL), - mIsFirstRun(false) -{ - if(NULL != sInstance) - { - LL_ERRS() << "Oh no! An instance of LLAppViewer already exists! LLAppViewer is sort of like a singleton." << LL_ENDL; - } - - mDumpPath =""; - - // Need to do this initialization before we do anything else, since anything - // that touches files should really go through the lldir API - gDirUtilp->initAppDirs("SecondLife"); - // - // IMPORTANT! Do NOT put anything that will write - // into the log files during normal startup until AFTER - // we run the "program crashed last time" error handler below. - // - sInstance = this; - - gLoggedInTime.stop(); - - processMarkerFiles(); - // - // OK to write stuff to logs now, we've now crash reported if necessary - // - - LLLoginInstance::instance().setPlatformInfo(gPlatform, LLOSInfo::instance().getOSVersionString(), LLOSInfo::instance().getOSStringSimple()); - - // Under some circumstances we want to read the static_debug_info.log file - // from the previous viewer run between this constructor call and the - // init() call, which will overwrite the static_debug_info.log file for - // THIS run. So setDebugFileNames() early. -# ifdef LL_BUGSPLAT - // MAINT-8917: don't create a dump directory just for the - // static_debug_info.log file - std::string logdir = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ""); -# else // ! LL_BUGSPLAT - // write Google Breakpad minidump files to a per-run dump directory to avoid multiple viewer issues. - std::string logdir = gDirUtilp->getExpandedFilename(LL_PATH_DUMP, ""); -# endif // ! LL_BUGSPLAT - mDumpPath = logdir; - - setDebugFileNames(logdir); -} - -LLAppViewer::~LLAppViewer() -{ - delete mSettingsLocationList; - - destroyMainloopTimeout(); - - // If we got to this destructor somehow, the app didn't hang. - removeMarkerFiles(); -} - -class LLUITranslationBridge : public LLTranslationBridge -{ -public: - virtual std::string getString(const std::string &xml_desc) - { - return LLTrans::getString(xml_desc); - } -}; - - -bool LLAppViewer::init() -{ - setupErrorHandling(mSecondInstance); - - // - // Start of the application - // - - // initialize the LLSettingsType translation bridge. - LLTranslationBridge::ptr_t trans = std::make_shared(); - LLSettingsType::initParamSingleton(trans); - - // initialize SSE options - LLVector4a::initClass(); - - //initialize particle index pool - LLVOPartGroup::initClass(); - - // set skin search path to default, will be overridden later - // this allows simple skinned file lookups to work - gDirUtilp->setSkinFolder("default", "en"); - -// initLoggingAndGetLastDuration(); - - // - // OK to write stuff to logs now, we've now crash reported if necessary - // - init_default_trans_args(); - - // inits from settings.xml and from strings.xml - if (!initConfiguration()) - return false; - - LL_INFOS("InitInfo") << "Configuration initialized." << LL_ENDL ; - - //set the max heap size. - initMaxHeapSize() ; - LLCoros::instance().setStackSize(gSavedSettings.getS32("CoroutineStackSize")); - - - // Although initLoggingAndGetLastDuration() is the right place to mess with - // setFatalFunction(), we can't query gSavedSettings until after - // initConfiguration(). - S32 rc(gSavedSettings.getS32("QAModeTermCode")); - if (rc >= 0) - { - // QAModeTermCode set, terminate with that rc on LL_ERRS. Use - // _exit() rather than exit() because normal cleanup depends too - // much on successful startup! - LLError::setFatalFunction([rc](const std::string&){ _exit(rc); }); - } - - mAlloc.setProfilingEnabled(gSavedSettings.getBOOL("MemProfiling")); - - // Initialize the non-LLCurl libcurl library. Should be called - // before consumers (LLTextureFetch). - mAppCoreHttp.init(); - - LL_INFOS("InitInfo") << "LLCore::Http initialized." << LL_ENDL ; - - LLMachineID::init(); - - { - if (gSavedSettings.getBOOL("QAModeMetrics")) - { - app_metrics_qa_mode = true; - app_metrics_interval = METRICS_INTERVAL_QA; - } - LLViewerAssetStatsFF::init(); - } - - initThreads(); - LL_INFOS("InitInfo") << "Threads initialized." << LL_ENDL ; - - // Initialize settings early so that the defaults for ignorable dialogs are - // picked up and then correctly re-saved after launching the updater (STORM-1268). - LLUI::settings_map_t settings_map; - settings_map["config"] = &gSavedSettings; - settings_map["ignores"] = &gWarningSettings; - settings_map["floater"] = &gSavedSettings; // *TODO: New settings file - settings_map["account"] = &gSavedPerAccountSettings; - - LLUI::initParamSingleton(settings_map, - LLUIImageList::getInstance(), - ui_audio_callback, - deferred_ui_audio_callback); - LL_INFOS("InitInfo") << "UI initialized." << LL_ENDL ; - - // NOW LLUI::getLanguage() should work. gDirUtilp must know the language - // for this session ASAP so all the file-loading commands that follow, - // that use findSkinnedFilenames(), will include the localized files. - gDirUtilp->setSkinFolder(gDirUtilp->getSkinFolder(), LLUI::getLanguage()); - - // Setup LLTrans after LLUI::initClass has been called. - initStrings(); - - // initialize LLWearableType translation bridge. - // Will immediately use LLTranslationBridge to init LLWearableDictionary - LLWearableType::initParamSingleton(trans); - - // Setup notifications after LLUI::initClass() has been called. - LLNotifications::instance(); - LL_INFOS("InitInfo") << "Notifications initialized." << LL_ENDL ; - - ////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////// - // *FIX: The following code isn't grouped into functions yet. - - // - // Various introspection concerning the libs we're using - particularly - // the libs involved in getting to a full login screen. - // - LL_INFOS("InitInfo") << "J2C Engine is: " << LLImageJ2C::getEngineInfo() << LL_ENDL; - LL_INFOS("InitInfo") << "libcurl version is: " << LLCore::LLHttp::getCURLVersion() << LL_ENDL; - - ///////////////////////////////////////////////// - // OS-specific login dialogs - ///////////////////////////////////////////////// - - //test_cached_control(); - - // track number of times that app has run - mNumSessions = gSavedSettings.getS32("NumSessions"); - mNumSessions++; - gSavedSettings.setS32("NumSessions", mNumSessions); - - // LLKeyboard relies on LLUI to know what some accelerator keys are called. - LLKeyboard::setStringTranslatorFunc( LLTrans::getKeyboardString ); - - // Provide the text fields with callbacks for opening Urls - LLUrlAction::setOpenURLCallback(boost::bind(&LLWeb::loadURL, _1, LLStringUtil::null, LLStringUtil::null)); - LLUrlAction::setOpenURLInternalCallback(boost::bind(&LLWeb::loadURLInternal, _1, LLStringUtil::null, LLStringUtil::null, false)); - LLUrlAction::setOpenURLExternalCallback(boost::bind(&LLWeb::loadURLExternal, _1, true, LLStringUtil::null)); - LLUrlAction::setExecuteSLURLCallback(&LLURLDispatcher::dispatchFromTextEditor); - - // Let code in llui access the viewer help floater - LLUI::getInstance()->mHelpImpl = LLViewerHelp::getInstance(); - - LL_INFOS("InitInfo") << "UI initialization is done." << LL_ENDL ; - - // Load translations for tooltips - LLFloater::initClass(); - LLUrlFloaterDispatchHandler::registerInDispatcher(); - - ///////////////////////////////////////////////// - - LLToolMgr::getInstance(); // Initialize tool manager if not already instantiated - - LLViewerFloaterReg::registerFloaters(); - - ///////////////////////////////////////////////// - // - // Load settings files - // - // - LLGroupMgr::parseRoleActions("role_actions.xml"); - - LLAgent::parseTeleportMessages("teleport_strings.xml"); - - // load MIME type -> media impl mappings - std::string mime_types_name; -#if LL_DARWIN - mime_types_name = "mime_types_mac.xml"; -#elif LL_LINUX - mime_types_name = "mime_types_linux.xml"; -#else - mime_types_name = "mime_types.xml"; -#endif - LLMIMETypes::parseMIMETypes( mime_types_name ); - - // Copy settings to globals. *TODO: Remove or move to appropriage class initializers - settings_to_globals(); - // Setup settings listeners - settings_setup_listeners(); - // Modify settings based on system configuration and compile options - settings_modify(); - - // Find partition serial number (Windows) or hardware serial (Mac) - mSerialNumber = generateSerialNumber(); - - // do any necessary set-up for accepting incoming SLURLs from apps - initSLURLHandler(); - - if(false == initHardwareTest()) - { - // Early out from user choice. - return false; - } - LL_INFOS("InitInfo") << "Hardware test initialization done." << LL_ENDL ; - - // Prepare for out-of-memory situations, during which we will crash on - // purpose and save a dump. -#if LL_WINDOWS && LL_RELEASE_FOR_DOWNLOAD && LL_USE_SMARTHEAP - MemSetErrorHandler(first_mem_error_handler); -#endif // LL_WINDOWS && LL_RELEASE_FOR_DOWNLOAD && LL_USE_SMARTHEAP - - // *Note: this is where gViewerStats used to be created. - - if (!initCache()) - { - LL_WARNS("InitInfo") << "Failed to init cache" << LL_ENDL; - std::ostringstream msg; - msg << LLTrans::getString("MBUnableToAccessFile"); - OSMessageBox(msg.str(),LLStringUtil::null,OSMB_OK); - return 0; - } - LL_INFOS("InitInfo") << "Cache initialization is done." << LL_ENDL ; - - // Initialize event recorder - LLViewerEventRecorder::createInstance(); - - // - // Initialize the window - // - gGLActive = true; - initWindow(); - LL_INFOS("InitInfo") << "Window is initialized." << LL_ENDL ; - - // writeSystemInfo can be called after window is initialized (gViewerWindow non-null) - writeSystemInfo(); - - // initWindow also initializes the Feature List, so now we can initialize this global. - LLCubeMap::sUseCubeMaps = LLFeatureManager::getInstance()->isFeatureAvailable("RenderCubeMap"); - - // call all self-registered classes - LLInitClassList::instance().fireCallbacks(); - - LLFolderViewItem::initClass(); // SJB: Needs to happen after initWindow(), not sure why but related to fonts - - gGLManager.getGLInfo(gDebugInfo); - gGLManager.printGLInfoString(); - - // If we don't have the right GL requirements, exit. - if (!gGLManager.mHasRequirements) - { - // already handled with a MBVideoDrvErr - return 0; - } - - // Without SSE2 support we will crash almost immediately, warn here. - if (!gSysCPU.hasSSE2()) - { - // can't use an alert here since we're exiting and - // all hell breaks lose. - OSMessageBox( - LLNotifications::instance().getGlobalString("UnsupportedCPUSSE2"), - LLStringUtil::null, - OSMB_OK); - return 0; - } - - // alert the user if they are using unsupported hardware - if(!gSavedSettings.getBOOL("AlertedUnsupportedHardware")) - { - bool unsupported = false; - LLSD args; - std::string minSpecs; - - // get cpu data from xml - std::stringstream minCPUString(LLNotifications::instance().getGlobalString("UnsupportedCPUAmount")); - S32 minCPU = 0; - minCPUString >> minCPU; - - // get RAM data from XML - std::stringstream minRAMString(LLNotifications::instance().getGlobalString("UnsupportedRAMAmount")); - U64Bytes minRAM; - minRAMString >> minRAM; - - if(!LLFeatureManager::getInstance()->isGPUSupported() && LLFeatureManager::getInstance()->getGPUClass() != GPU_CLASS_UNKNOWN) - { - minSpecs += LLNotifications::instance().getGlobalString("UnsupportedGPU"); - minSpecs += "\n"; - unsupported = true; - } - if(gSysCPU.getMHz() < minCPU) - { - minSpecs += LLNotifications::instance().getGlobalString("UnsupportedCPU"); - minSpecs += "\n"; - unsupported = true; - } - if(gSysMemory.getPhysicalMemoryKB() < minRAM) - { - minSpecs += LLNotifications::instance().getGlobalString("UnsupportedRAM"); - minSpecs += "\n"; - unsupported = true; - } - - if (LLFeatureManager::getInstance()->getGPUClass() == GPU_CLASS_UNKNOWN) - { - LLNotificationsUtil::add("UnknownGPU"); - } - - if(unsupported) - { - if(!gSavedSettings.controlExists("WarnUnsupportedHardware") - || gSavedSettings.getBOOL("WarnUnsupportedHardware")) - { - args["MINSPECS"] = minSpecs; - LLNotificationsUtil::add("UnsupportedHardware", args ); - } - - } - } - -#if LL_WINDOWS && ADDRESS_SIZE == 64 - if (gGLManager.mIsIntel) - { - // Check intel driver's version - // Ex: "3.1.0 - Build 8.15.10.2559"; - std::string version = ll_safe_string((const char *)glGetString(GL_VERSION)); - - const boost::regex is_intel_string("[0-9].[0-9].[0-9] - Build [0-9]{1,2}.[0-9]{2}.[0-9]{2}.[0-9]{4}"); - - if (boost::regex_search(version, is_intel_string)) - { - // Valid string, extract driver version - std::size_t found = version.find("Build "); - std::string driver = version.substr(found + 6); - S32 v1, v2, v3, v4; - S32 count = sscanf(driver.c_str(), "%d.%d.%d.%d", &v1, &v2, &v3, &v4); - if (count > 0 && v1 <= 10) - { - LL_INFOS("AppInit") << "Detected obsolete intel driver: " << driver << LL_ENDL; - - if (!gViewerWindow->getInitAlert().empty() // graphic initialization crashed on last run - || LLVersionInfo::getInstance()->getChannelAndVersion() != gLastRunVersion // viewer was updated - || mNumSessions % 20 == 0 //periodically remind user to update driver - ) - { - LLUIString details = LLNotifications::instance().getGlobalString("UnsupportedIntelDriver"); - std::string gpu_name = ll_safe_string((const char *)glGetString(GL_RENDERER)); - LL_INFOS("AppInit") << "Notifying user about obsolete intel driver for " << gpu_name << LL_ENDL; - details.setArg("[VERSION]", driver); - details.setArg("[GPUNAME]", gpu_name); - S32 button = OSMessageBox(details.getString(), - LLStringUtil::null, - OSMB_YESNO); - if (OSBTN_YES == button && gViewerWindow) - { - std::string url = LLWeb::escapeURL(LLTrans::getString("IntelDriverPage")); - if (gViewerWindow->getWindow()) - { - gViewerWindow->getWindow()->spawnWebBrowser(url, false); - } - } - } - } - } - } -#endif - - // Obsolete? mExpectedGLVersion is always zero -#if LL_WINDOWS - if (gGLManager.mGLVersion < LLFeatureManager::getInstance()->getExpectedGLVersion()) - { - std::string url; - if (gGLManager.mIsIntel) - { - url = LLTrans::getString("IntelDriverPage"); - } - else if (gGLManager.mIsNVIDIA) - { - url = LLTrans::getString("NvidiaDriverPage"); - } - else if (gGLManager.mIsAMD) - { - url = LLTrans::getString("AMDDriverPage"); - } - - if (!url.empty()) - { - LLNotificationsUtil::add("OldGPUDriver", LLSD().with("URL", url)); - } - } -#endif - - - // save the graphics card - gDebugInfo["GraphicsCard"] = LLFeatureManager::getInstance()->getGPUString(); - - // Save the current version to the prefs file - gSavedSettings.setString("LastRunVersion", - LLVersionInfo::instance().getChannelAndVersion()); - - gSimLastTime = gRenderStartTime.getElapsedTimeF32(); - gSimFrames = (F32)gFrameCount; - - if (gSavedSettings.getBOOL("JoystickEnabled")) - { - LLViewerJoystick::getInstance()->init(false); - } - - try { - initializeSecHandler(); - } - catch (LLProtectedDataException&) - { - LLNotificationsUtil::add("CorruptedProtectedDataStore"); - } - - gGLActive = false; - -#if LL_RELEASE_FOR_DOWNLOAD - // Skip updater if this is a non-interactive instance - if (!gSavedSettings.getBOOL("CmdLineSkipUpdater") && !gNonInteractive) - { - LLProcess::Params updater; - updater.desc = "updater process"; - // Because it's the updater, it MUST persist beyond the lifespan of the - // viewer itself. - updater.autokill = false; - std::string updater_file; -#if LL_WINDOWS - updater_file = "SLVersionChecker.exe"; - updater.executable = gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, updater_file); -#elif LL_DARWIN - updater_file = "SLVersionChecker"; - updater.executable = gDirUtilp->add(gDirUtilp->getAppRODataDir(), "updater", updater_file); -#else - updater_file = "SLVersionChecker"; - updater.executable = gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, updater_file); -#endif - // add LEAP mode command-line argument to whichever of these we selected - updater.args.add("leap"); - // UpdaterServiceSettings - if (gSavedSettings.getBOOL("FirstLoginThisInstall")) - { - // Befor first login, treat this as 'manual' updates, - // updater won't install anything, but required updates - updater.args.add("0"); - } - else - { - updater.args.add(stringize(gSavedSettings.getU32("UpdaterServiceSetting"))); - } - // channel - updater.args.add(LLVersionInfo::instance().getChannel()); - // testok - updater.args.add(stringize(gSavedSettings.getBOOL("UpdaterWillingToTest"))); - // ForceAddressSize - updater.args.add(stringize(gSavedSettings.getU32("ForceAddressSize"))); - - try - { - // Run the updater. An exception from launching the updater should bother us. - LLLeap::create(updater, true); - mUpdaterNotFound = false; - } - catch (...) - { - LLUIString details = LLNotifications::instance().getGlobalString("LLLeapUpdaterFailure"); - details.setArg("[UPDATER_APP]", updater_file); - OSMessageBox( - details.getString(), - LLStringUtil::null, - OSMB_OK); - mUpdaterNotFound = true; - } - } - else - { - LL_WARNS("InitInfo") << "Skipping updater check." << LL_ENDL; - } -#endif //LL_RELEASE_FOR_DOWNLOAD - - { - // Iterate over --leap command-line options. But this is a bit tricky: if - // there's only one, it won't be an array at all. - LLSD LeapCommand(gSavedSettings.getLLSD("LeapCommand")); - LL_DEBUGS("InitInfo") << "LeapCommand: " << LeapCommand << LL_ENDL; - if (LeapCommand.isDefined() && !LeapCommand.isArray()) - { - // If LeapCommand is actually a scalar value, make an array of it. - // Have to do it in two steps because LeapCommand.append(LeapCommand) - // trashes content! :-P - LLSD item(LeapCommand); - LeapCommand.append(item); - } - for (const auto& leap : llsd::inArray(LeapCommand)) - { - LL_INFOS("InitInfo") << "processing --leap \"" << leap << '"' << LL_ENDL; - // We don't have any better description of this plugin than the - // user-specified command line. Passing "" causes LLLeap to derive a - // description from the command line itself. - // Suppress LLLeap::Error exception: trust LLLeap's own logging. We - // don't consider any one --leap command mission-critical, so if one - // fails, log it, shrug and carry on. - LLLeap::create("", leap, false); // exception=false - } - } - - if (gSavedSettings.getBOOL("QAMode") && gSavedSettings.getS32("QAModeEventHostPort") > 0) - { - LL_WARNS("InitInfo") << "QAModeEventHostPort DEPRECATED: " - << "lleventhost no longer supported as a dynamic library" - << LL_ENDL; - } - - LLTextUtil::TextHelpers::iconCallbackCreationFunction = create_text_segment_icon_from_url_match; - - //EXT-7013 - On windows for some locale (Japanese) standard - //datetime formatting functions didn't support some parameters such as "weekday". - //Names for days and months localized in xml are also useful for Polish locale(STORM-107). - std::string language = gSavedSettings.getString("Language"); - if(language == "ja" || language == "pl") - { - LLStringOps::setupWeekDaysNames(LLTrans::getString("dateTimeWeekdaysNames")); - LLStringOps::setupWeekDaysShortNames(LLTrans::getString("dateTimeWeekdaysShortNames")); - LLStringOps::setupMonthNames(LLTrans::getString("dateTimeMonthNames")); - LLStringOps::setupMonthShortNames(LLTrans::getString("dateTimeMonthShortNames")); - LLStringOps::setupDayFormat(LLTrans::getString("dateTimeDayFormat")); - - LLStringOps::sAM = LLTrans::getString("dateTimeAM"); - LLStringOps::sPM = LLTrans::getString("dateTimePM"); - } - - LLAgentLanguage::init(); - - /// Tell the Coprocedure manager how to discover and store the pool sizes - // what I wanted - LLCoprocedureManager::getInstance()->setPropertyMethods( - boost::bind(&LLControlGroup::getU32, boost::ref(gSavedSettings), _1), - boost::bind(&LLControlGroup::declareU32, boost::ref(gSavedSettings), _1, _2, _3, LLControlVariable::PERSIST_ALWAYS)); - - // TODO: consider moving proxy initialization here or LLCopocedureManager after proxy initialization, may be implement - // some other protection to make sure we don't use network before initializng proxy - - /*----------------------------------------------------------------------*/ - // nat 2016-06-29 moved the following here from the former mainLoop(). - mMainloopTimeout = new LLWatchdogTimeout(); - - // Create IO Pump to use for HTTP Requests. - gServicePump = new LLPumpIO(gAPRPoolp); - - // Note: this is where gLocalSpeakerMgr and gActiveSpeakerMgr used to be instantiated. - - LLVoiceChannel::initClass(); - LLVoiceClient::initParamSingleton(gServicePump); - LLVoiceChannel::setCurrentVoiceChannelChangedCallback(boost::bind(&LLFloaterIMContainer::onCurrentChannelChanged, _1), true); - - joystick = LLViewerJoystick::getInstance(); - joystick->setNeedsReset(true); - /*----------------------------------------------------------------------*/ - // Load User's bindings - loadKeyBindings(); - - //LLSimpleton creations - LLEnvironment::createInstance(); - LLWorld::createInstance(); - LLSelectMgr::createInstance(); - LLViewerCamera::createInstance(); - -#if LL_WINDOWS - if (!mSecondInstance) - { - gDirUtilp->deleteDirAndContents(gDirUtilp->getDumpLogsDirPath()); - } -#endif - - return true; -} - -void LLAppViewer::initMaxHeapSize() -{ - //set the max heap size. - //here is some info regarding to the max heap size: - //------------------------------------------------------------------------------------------ - // OS | setting | SL address bits | max manageable memory space | max heap size - // Win 32 | default | 32-bit | 2GB | < 1.7GB - // Win 32 | /3G | 32-bit | 3GB | < 1.7GB or 2.7GB - //Linux 32 | default | 32-bit | 3GB | < 2.7GB - //Linux 32 |HUGEMEM | 32-bit | 4GB | < 3.7GB - //64-bit OS |default | 32-bit | 4GB | < 3.7GB - //64-bit OS |default | 64-bit | N/A (> 4GB) | N/A (> 4GB) - //------------------------------------------------------------------------------------------ - //currently SL is built under 32-bit setting, we set its max heap size no more than 1.6 GB. - - #ifndef LL_X86_64 - F32Gigabytes max_heap_size_gb = (F32Gigabytes)gSavedSettings.getF32("MaxHeapSize") ; -#else - F32Gigabytes max_heap_size_gb = (F32Gigabytes)gSavedSettings.getF32("MaxHeapSize64"); -#endif - - LLMemory::initMaxHeapSizeGB(max_heap_size_gb); -} - - -// externally visible timers -LLTrace::BlockTimerStatHandle FTM_FRAME("Frame"); - -bool LLAppViewer::frame() -{ - bool ret = false; - - if (gSimulateMemLeak) - { - try - { - ret = doFrame(); - } - catch (const LLContinueError&) - { - LOG_UNHANDLED_EXCEPTION(""); - } - catch (std::bad_alloc&) - { - LLMemory::logMemoryInfo(true); - LLFloaterMemLeak* mem_leak_instance = LLFloaterReg::findTypedInstance("mem_leaking"); - if (mem_leak_instance) - { - mem_leak_instance->stop(); - } - LL_WARNS() << "Bad memory allocation in LLAppViewer::frame()!" << LL_ENDL; - } - } - else - { - try - { - ret = doFrame(); - } - catch (const LLContinueError&) - { - LOG_UNHANDLED_EXCEPTION(""); - } - } - - return ret; -} - -bool LLAppViewer::doFrame() -{ - LL_RECORD_BLOCK_TIME(FTM_FRAME); - { - // and now adjust the visuals from previous frame. - if(LLPerfStats::tunables.userAutoTuneEnabled && LLPerfStats::tunables.tuningFlag != LLPerfStats::Tunables::Nothing) - { - LLPerfStats::tunables.applyUpdates(); - } - - LLPerfStats::RecordSceneTime T (LLPerfStats::StatType_t::RENDER_FRAME); - if (!LLWorld::instanceExists()) - { - LLWorld::createInstance(); - } - - LLEventPump& mainloop(LLEventPumps::instance().obtain("mainloop")); - LLSD newFrame; - { - LLPerfStats::RecordSceneTime T (LLPerfStats::StatType_t::RENDER_IDLE); // perf stats - { - LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df LLTrace"); - if (LLFloaterReg::instanceVisible("block_timers")) - { - LLTrace::BlockTimer::processTimes(); - } - - LLTrace::get_frame_recording().nextPeriod(); - LLTrace::BlockTimer::logStats(); - } - - LLTrace::get_thread_recorder()->pullFromChildren(); - - //clear call stack records - LL_CLEAR_CALLSTACKS(); - } - { - { - LLPerfStats::RecordSceneTime T(LLPerfStats::StatType_t::RENDER_IDLE); // ensure we have the entire top scope of frame covered (input event and coro) - LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df processMiscNativeEvents") - pingMainloopTimeout("Main:MiscNativeWindowEvents"); - - if (gViewerWindow) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_APP("System Messages"); - gViewerWindow->getWindow()->processMiscNativeEvents(); - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df gatherInput") - pingMainloopTimeout("Main:GatherInput"); - } - - if (gViewerWindow) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_APP("System Messages"); - if (!restoreErrorTrap()) - { - LL_WARNS() << " Someone took over my signal/exception handler (post messagehandling)!" << LL_ENDL; - } - - gViewerWindow->getWindow()->gatherInput(); - } - - //memory leaking simulation - if (gSimulateMemLeak) - { - LLFloaterMemLeak* mem_leak_instance = - LLFloaterReg::findTypedInstance("mem_leaking"); - if (mem_leak_instance) - { - mem_leak_instance->idle(); - } - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df mainloop") - // canonical per-frame event - mainloop.post(newFrame); - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df suspend") - // give listeners a chance to run - llcoro::suspend(); - // if one of our coroutines threw an uncaught exception, rethrow it now - LLCoros::instance().rethrow(); - } - } - - if (!LLApp::isExiting()) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_APP( "df JoystickKeyboard" ) - pingMainloopTimeout("Main:JoystickKeyboard"); - - // Scan keyboard for movement keys. Command keys and typing - // are handled by windows callbacks. Don't do this until we're - // done initializing. JC - if (gViewerWindow - && (gHeadlessClient || gViewerWindow->getWindow()->getVisible()) - && gViewerWindow->getActive() - && !gViewerWindow->getWindow()->getMinimized() - && LLStartUp::getStartupState() == STATE_STARTED - && (gHeadlessClient || !gViewerWindow->getShowProgress()) - && !gFocusMgr.focusLocked()) - { - LLPerfStats::RecordSceneTime T (LLPerfStats::StatType_t::RENDER_IDLE); - joystick->scanJoystick(); - gKeyboard->scanKeyboard(); - gViewerInput.scanMouse(); - } - - // Update state based on messages, user input, object idle. - { - { - LL_PROFILE_ZONE_NAMED_CATEGORY_APP( "df pauseMainloopTimeout" ) - pauseMainloopTimeout(); // *TODO: Remove. Messages shouldn't be stalling for 20+ seconds! - } - - { - LLPerfStats::RecordSceneTime T (LLPerfStats::StatType_t::RENDER_IDLE); - LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df idle"); - idle(); - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_APP( "df resumeMainloopTimeout" ) - resumeMainloopTimeout(); - } - } - - if (gDoDisconnect && (LLStartUp::getStartupState() == STATE_STARTED)) - { - pauseMainloopTimeout(); - saveFinalSnapshot(); - - if (LLVoiceClient::instanceExists()) - { - LLVoiceClient::getInstance()->terminate(); - } - - disconnectViewer(); - resumeMainloopTimeout(); - } - - // Render scene. - // *TODO: Should we run display() even during gHeadlessClient? DK 2011-02-18 - if (!LLApp::isExiting() && !gHeadlessClient && gViewerWindow) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df Display"); - pingMainloopTimeout("Main:Display"); - gGLActive = true; - - display(); - - { - LLPerfStats::RecordSceneTime T(LLPerfStats::StatType_t::RENDER_IDLE); - LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df Snapshot"); - pingMainloopTimeout("Main:Snapshot"); - gPipeline.mReflectionMapManager.update(); - LLFloaterSnapshot::update(); // take snapshots - LLFloaterSimpleSnapshot::update(); - gGLActive = false; - } - - if (LLViewerStatsRecorder::instanceExists()) - { - LLViewerStatsRecorder::instance().idle(); - } - } - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_APP( "df pauseMainloopTimeout" ) - pingMainloopTimeout("Main:Sleep"); - - pauseMainloopTimeout(); - } - - // Sleep and run background threads - { - //LL_RECORD_BLOCK_TIME(SLEEP2); - LL_PROFILE_ZONE_WARN( "Sleep2" ) - - // yield some time to the os based on command line option - static LLCachedControl yield_time(gSavedSettings, "YieldTime", -1); - if(yield_time >= 0) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_APP("Yield"); - LL_PROFILE_ZONE_NUM( yield_time ) - ms_sleep(yield_time); - } - - if (gNonInteractive) - { - S32 non_interactive_ms_sleep_time = 100; - LLAppViewer::getTextureCache()->pause(); - ms_sleep(non_interactive_ms_sleep_time); - } - - // yield cooperatively when not running as foreground window - // and when not quiting (causes trouble at mac's cleanup stage) - if (!LLApp::isExiting() - && ((gViewerWindow && !gViewerWindow->getWindow()->getVisible()) - || !gFocusMgr.getAppHasFocus())) - { - // Sleep if we're not rendering, or the window is minimized. - static LLCachedControl s_background_yield_time(gSavedSettings, "BackgroundYieldTime", 40); - S32 milliseconds_to_sleep = llclamp((S32)s_background_yield_time, 0, 1000); - // don't sleep when BackgroundYieldTime set to 0, since this will still yield to other threads - // of equal priority on Windows - if (milliseconds_to_sleep > 0) - { - LLPerfStats::RecordSceneTime T ( LLPerfStats::StatType_t::RENDER_SLEEP ); - ms_sleep(milliseconds_to_sleep); - // also pause worker threads during this wait period - LLAppViewer::getTextureCache()->pause(); - } - } - - if (mRandomizeFramerate) - { - ms_sleep(rand() % 200); - } - - if (mPeriodicSlowFrame - && (gFrameCount % 10 == 0)) - { - LL_INFOS() << "Periodic slow frame - sleeping 500 ms" << LL_ENDL; - ms_sleep(500); - } - - S32 total_work_pending = 0; - S32 total_io_pending = 0; - { - S32 work_pending = 0; - S32 io_pending = 0; - F32 max_time = llmin(gFrameIntervalSeconds.value() *10.f, 1.f); - - work_pending += updateTextureThreads(max_time); - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_APP("LFS Thread"); - io_pending += LLLFSThread::updateClass(1); - } - - if (io_pending > 1000) - { - ms_sleep(llmin(io_pending/100,100)); // give the lfs some time to catch up - } - - total_work_pending += work_pending ; - total_io_pending += io_pending ; - - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_APP( "df gMeshRepo" ) - gMeshRepo.update() ; - } - - if(!total_work_pending) //pause texture fetching threads if nothing to process. - { - LL_PROFILE_ZONE_NAMED_CATEGORY_APP( "df getTextureCache" ) - LLAppViewer::getTextureCache()->pause(); - LLAppViewer::getTextureFetch()->pause(); - } - if(!total_io_pending) //pause file threads if nothing to process. - { - LL_PROFILE_ZONE_NAMED_CATEGORY_APP( "df LLVFSThread" ) - LLLFSThread::sLocal->pause(); - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_APP( "df resumeMainloopTimeout" ) - resumeMainloopTimeout(); - } - pingMainloopTimeout("Main:End"); - } - } - - if (LLApp::isExiting()) - { - // Save snapshot for next time, if we made it through initialization - if (STATE_STARTED == LLStartUp::getStartupState()) - { - saveFinalSnapshot(); - } - - if (LLVoiceClient::instanceExists()) - { - LLVoiceClient::getInstance()->terminate(); - } - - delete gServicePump; - gServicePump = NULL; - - destroyMainloopTimeout(); - - LL_INFOS() << "Exiting main_loop" << LL_ENDL; - } - }LLPerfStats::StatsRecorder::endFrame(); - LL_PROFILER_FRAME_END - - return ! LLApp::isRunning(); -} - -S32 LLAppViewer::updateTextureThreads(F32 max_time) -{ - S32 work_pending = 0; - { - LL_PROFILE_ZONE_NAMED_CATEGORY_APP("Texture Cache"); - work_pending += LLAppViewer::getTextureCache()->update(max_time); // unpauses the texture cache thread - } - { - LL_PROFILE_ZONE_NAMED_CATEGORY_APP("Image Decode"); - work_pending += LLAppViewer::getImageDecodeThread()->update(max_time); // unpauses the image thread - } - { - LL_PROFILE_ZONE_NAMED_CATEGORY_APP("Image Fetch"); - work_pending += LLAppViewer::getTextureFetch()->update(max_time); // unpauses the texture fetch thread - } - return work_pending; -} - -void LLAppViewer::flushLFSIO() -{ - S32 pending = LLLFSThread::updateClass(0); - if (pending > 0) - { - LL_INFOS() << "Waiting for pending IO to finish: " << pending << LL_ENDL; - while (1) - { - pending = LLLFSThread::updateClass(0); - if (!pending) - { - break; - } - ms_sleep(100); - } - } -} - -bool LLAppViewer::cleanup() -{ - LLAtmosphere::cleanupClass(); - - //ditch LLVOAvatarSelf instance - gAgentAvatarp = NULL; - - LLNotifications::instance().clear(); - - // workaround for DEV-35406 crash on shutdown - LLEventPumps::instance().reset(true); - - //dump scene loading monitor results - if (LLSceneMonitor::instanceExists()) - { - if (!isSecondInstance()) - { - std::string dump_path = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "scene_monitor_results.csv"); - LLSceneMonitor::instance().dumpToFile(dump_path); - } - LLSceneMonitor::deleteSingleton(); - } - - // There used to be an 'if (LLFastTimerView::sAnalyzePerformance)' block - // here, completely redundant with the one that occurs later in this same - // function. Presumably the duplication was due to an automated merge gone - // bad. Not knowing which instance to prefer, we chose to retain the later - // one because it happens just after mFastTimerLogThread is deleted. This - // comment is in case we guessed wrong, so we can move it here instead. - -#if LL_LINUX - // remove any old breakpad minidump files from the log directory - if (! isError()) - { - std::string logdir = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ""); - gDirUtilp->deleteFilesInDir(logdir, "*-*-*-*-*.dmp"); - } -#endif - - // Kill off LLLeap objects. We can find them all because LLLeap is derived - // from LLInstanceTracker. - LLLeap::instance_snapshot().deleteAll(); - - //flag all elements as needing to be destroyed immediately - // to ensure shutdown order - LLMortician::setZealous(true); - - // Give any remaining SLPlugin instances a chance to exit cleanly. - LLPluginProcessParent::shutdown(); - - disconnectViewer(); - LLViewerCamera::deleteSingleton(); - - LL_INFOS() << "Viewer disconnected" << LL_ENDL; - - if (gKeyboard) - { - gKeyboard->resetKeys(); - } - - display_cleanup(); - - release_start_screen(); // just in case - - LLError::logToFixedBuffer(NULL); // stop the fixed buffer recorder - - LL_INFOS() << "Cleaning Up" << LL_ENDL; - - // shut down mesh streamer - gMeshRepo.shutdown(); - - // shut down Havok - LLPhysicsExtensions::quitSystem(); - - // Must clean up texture references before viewer window is destroyed. - if(LLHUDManager::instanceExists()) - { - LLHUDManager::getInstance()->updateEffects(); - LLHUDObject::updateAll(); - LLHUDManager::getInstance()->cleanupEffects(); - LLHUDObject::cleanupHUDObjects(); - LL_INFOS() << "HUD Objects cleaned up" << LL_ENDL; - } - - LLKeyframeDataCache::clear(); - - // End TransferManager before deleting systems it depends on (Audio, AssetStorage) -#if 0 // this seems to get us stuck in an infinite loop... - gTransferManager.cleanup(); -#endif - - // Note: this is where gWorldMap used to be deleted. - - // Note: this is where gHUDManager used to be deleted. - if(LLHUDManager::instanceExists()) - { - LLHUDManager::getInstance()->shutdownClass(); - } - - delete gAssetStorage; - gAssetStorage = NULL; - - LLPolyMesh::freeAllMeshes(); - - LLStartUp::cleanupNameCache(); - - // Note: this is where gLocalSpeakerMgr and gActiveSpeakerMgr used to be deleted. - - if (LLWorldMap::instanceExists()) - { - LLWorldMap::getInstance()->reset(); // release any images - } - - LLCalc::cleanUp(); - - LL_INFOS() << "Global stuff deleted" << LL_ENDL; - - if (gAudiop) - { - LL_INFOS() << "Shutting down audio" << LL_ENDL; - - // be sure to stop the internet stream cleanly BEFORE destroying the interface to stop it. - gAudiop->stopInternetStream(); - // shut down the streaming audio sub-subsystem first, in case it relies on not outliving the general audio subsystem. - LLStreamingAudioInterface *sai = gAudiop->getStreamingAudioImpl(); - delete sai; - gAudiop->setStreamingAudioImpl(NULL); - - // shut down the audio subsystem - gAudiop->shutdown(); - - delete gAudiop; - gAudiop = NULL; - } - - // Note: this is where LLFeatureManager::getInstance()-> used to be deleted. - - // Patch up settings for next time - // Must do this before we delete the viewer window, - // such that we can suck rectangle information out of - // it. - cleanupSavedSettings(); - LL_INFOS() << "Settings patched up" << LL_ENDL; - - // delete some of the files left around in the cache. - removeCacheFiles("*.wav"); - removeCacheFiles("*.tmp"); - removeCacheFiles("*.lso"); - removeCacheFiles("*.out"); - removeCacheFiles("*.dsf"); - removeCacheFiles("*.bodypart"); - removeCacheFiles("*.clothing"); - - LL_INFOS() << "Cache files removed" << LL_ENDL; - - LL_INFOS() << "Shutting down Views" << LL_ENDL; - - // Destroy the UI - if( gViewerWindow) - gViewerWindow->shutdownViews(); - - LL_INFOS() << "Cleaning up Inventory" << LL_ENDL; - - // Cleanup Inventory after the UI since it will delete any remaining observers - // (Deleted observers should have already removed themselves) - gInventory.cleanupInventory(); - - LLCoros::getInstance()->printActiveCoroutines(); - - LL_INFOS() << "Cleaning up Selections" << LL_ENDL; - - // Clean up selection managers after UI is destroyed, as UI may be observing them. - // Clean up before GL is shut down because we might be holding on to objects with texture references - LLSelectMgr::cleanupGlobals(); - - LL_INFOS() << "Shutting down OpenGL" << LL_ENDL; - - // Shut down OpenGL - if( gViewerWindow) - { - gViewerWindow->shutdownGL(); - - // Destroy window, and make sure we're not fullscreen - // This may generate window reshape and activation events. - // Therefore must do this before destroying the message system. - delete gViewerWindow; - gViewerWindow = NULL; - LL_INFOS() << "ViewerWindow deleted" << LL_ENDL; - } - - LLSplashScreen::show(); - LLSplashScreen::update(LLTrans::getString("ShuttingDown")); - - LL_INFOS() << "Cleaning up Keyboard & Joystick" << LL_ENDL; - - // viewer UI relies on keyboard so keep it aound until viewer UI isa gone - delete gKeyboard; - gKeyboard = NULL; - - if (LLViewerJoystick::instanceExists()) - { - // Turn off Space Navigator and similar devices - LLViewerJoystick::getInstance()->terminate(); - } - - LL_INFOS() << "Cleaning up Objects" << LL_ENDL; - - LLViewerObject::cleanupVOClasses(); - - SUBSYSTEM_CLEANUP(LLAvatarAppearance); - - SUBSYSTEM_CLEANUP(LLPostProcess); - - LLTracker::cleanupInstance(); - - // *FIX: This is handled in LLAppViewerWin32::cleanup(). - // I'm keeping the comment to remember its order in cleanup, - // in case of unforseen dependency. - //#if LL_WINDOWS - // gDXHardware.cleanup(); - //#endif // LL_WINDOWS - - LLVolumeMgr* volume_manager = LLPrimitive::getVolumeManager(); - if (!volume_manager->cleanup()) - { - LL_WARNS() << "Remaining references in the volume manager!" << LL_ENDL; - } - LLPrimitive::cleanupVolumeManager(); - - LL_INFOS() << "Additional Cleanup..." << LL_ENDL; - - LLViewerParcelMgr::cleanupGlobals(); - - // *Note: this is where gViewerStats used to be deleted. - - //end_messaging_system(); - - LLPrimitive::cleanupVolumeManager(); - SUBSYSTEM_CLEANUP(LLWorldMapView); - SUBSYSTEM_CLEANUP(LLFolderViewItem); - - LL_INFOS() << "Saving Data" << LL_ENDL; - - // Store the time of our current logoff - gSavedPerAccountSettings.setU32("LastLogoff", time_corrected()); - - if (LLEnvironment::instanceExists()) - { - //Store environment settings if necessary - LLEnvironment::getInstance()->saveToSettings(); - } - - // Must do this after all panels have been deleted because panels that have persistent rects - // save their rects on delete. - gSavedSettings.saveToFile(gSavedSettings.getString("ClientSettingsFile"), true); - - LLUIColorTable::instance().saveUserSettings(); - - // PerAccountSettingsFile should be empty if no user has been logged on. - // *FIX:Mani This should get really saved in a "logoff" mode. - if (gSavedSettings.getString("PerAccountSettingsFile").empty()) - { - LL_INFOS() << "Not saving per-account settings; don't know the account name yet." << LL_ENDL; - } - // Only save per account settings if the previous login succeeded, otherwise - // we might end up with a cleared out settings file in case a previous login - // failed after loading per account settings. - else if (!mSavePerAccountSettings) - { - LL_INFOS() << "Not saving per-account settings; last login was not successful." << LL_ENDL; - } - else - { - gSavedPerAccountSettings.saveToFile(gSavedSettings.getString("PerAccountSettingsFile"), true); - LL_INFOS() << "Saved settings" << LL_ENDL; - - if (LLViewerParcelAskPlay::instanceExists()) - { - LLViewerParcelAskPlay::getInstance()->saveSettings(); - } - } - - std::string warnings_settings_filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, getSettingsFilename("Default", "Warnings")); - gWarningSettings.saveToFile(warnings_settings_filename, true); - - // Save URL history file - LLURLHistory::saveFile("url_history.xml"); - - // save mute list. gMuteList used to also be deleted here too. - if (gAgent.isInitialized() && LLMuteList::instanceExists()) - { - LLMuteList::getInstance()->cache(gAgent.getID()); - } - - //save call log list - if (LLConversationLog::instanceExists()) - { - LLConversationLog::instance().cache(); - } - - clearSecHandler(); - - if (mPurgeCacheOnExit) - { - LL_INFOS() << "Purging all cache files on exit" << LL_ENDL; - gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE,""), "*.*"); - } - - writeDebugInfo(); - - LLLocationHistory::getInstance()->save(); - - LLAvatarIconIDCache::getInstance()->save(); - - // Stop the plugin read thread if it's running. - LLPluginProcessParent::setUseReadThread(false); - - LL_INFOS() << "Shutting down Threads" << LL_ENDL; - - // Let threads finish - LLTimer idleTimer; - idleTimer.reset(); - const F64 max_idle_time = 5.f; // 5 seconds - while(1) - { - S32 pending = 0; - pending += LLAppViewer::getTextureCache()->update(1); // unpauses the worker thread - pending += LLAppViewer::getImageDecodeThread()->update(1); // unpauses the image thread - pending += LLAppViewer::getTextureFetch()->update(1); // unpauses the texture fetch thread - pending += LLLFSThread::updateClass(0); - F64 idle_time = idleTimer.getElapsedTimeF64(); - if(!pending) - { - break ; //done - } - else if(idle_time >= max_idle_time) - { - LL_WARNS() << "Quitting with pending background tasks." << LL_ENDL; - break; - } - } - - if (mPurgeUserDataOnExit) - { - // Ideally we should not save anything from this session since it is going to be purged now, - // but this is a very 'rare' case (user deleting himself), not worth overcomplicating 'save&cleanup' code - std::string user_path = gDirUtilp->getOSUserAppDir() + gDirUtilp->getDirDelimiter() + LLStartUp::getUserId(); - gDirUtilp->deleteDirAndContents(user_path); - } - - // Delete workers first - // shotdown all worker threads before deleting them in case of co-dependencies - mAppCoreHttp.requestStop(); - sTextureFetch->shutdown(); - sTextureCache->shutdown(); - sImageDecodeThread->shutdown(); - sPurgeDiskCacheThread->shutdown(); - if (mGeneralThreadPool) - { - mGeneralThreadPool->close(); - } - - sTextureFetch->shutDownTextureCacheThread() ; - LLLFSThread::sLocal->shutdown(); - - LL_INFOS() << "Shutting down message system" << LL_ENDL; - end_messaging_system(); - - // Non-LLCurl libcurl library - mAppCoreHttp.cleanup(); - - SUBSYSTEM_CLEANUP(LLFilePickerThread); - SUBSYSTEM_CLEANUP(LLDirPickerThread); - - //MUST happen AFTER SUBSYSTEM_CLEANUP(LLCurl) - delete sTextureCache; - sTextureCache = NULL; - if (sTextureFetch) - { - sTextureFetch->shutdown(); - sTextureFetch->waitOnPending(); - delete sTextureFetch; - sTextureFetch = NULL; - } - delete sImageDecodeThread; - sImageDecodeThread = NULL; - delete mFastTimerLogThread; - mFastTimerLogThread = NULL; - delete sPurgeDiskCacheThread; - sPurgeDiskCacheThread = NULL; - delete mGeneralThreadPool; - mGeneralThreadPool = NULL; - - if (LLFastTimerView::sAnalyzePerformance) - { - LL_INFOS() << "Analyzing performance" << LL_ENDL; - - std::string baseline_name = LLTrace::BlockTimer::sLogName + "_baseline.slp"; - std::string current_name = LLTrace::BlockTimer::sLogName + ".slp"; - std::string report_name = LLTrace::BlockTimer::sLogName + "_report.csv"; - - LLFastTimerView::doAnalysis( - gDirUtilp->getExpandedFilename(LL_PATH_LOGS, baseline_name), - gDirUtilp->getExpandedFilename(LL_PATH_LOGS, current_name), - gDirUtilp->getExpandedFilename(LL_PATH_LOGS, report_name)); - } - - SUBSYSTEM_CLEANUP(LLMetricPerformanceTesterBasic) ; - - LL_INFOS() << "Cleaning up Media and Textures" << LL_ENDL; - - //Note: - //SUBSYSTEM_CLEANUP(LLViewerMedia) has to be put before gTextureList.shutdown() - //because some new image might be generated during cleaning up media. --bao - gTextureList.shutdown(); // shutdown again in case a callback added something - LLUIImageList::getInstance()->cleanUp(); - - SUBSYSTEM_CLEANUP(LLImage); - SUBSYSTEM_CLEANUP(LLLFSThread); - - LL_INFOS() << "Misc Cleanup" << LL_ENDL; - - gSavedSettings.cleanup(); - LLUIColorTable::instance().clear(); - - LLWatchdog::getInstance()->cleanup(); - - LLViewerAssetStatsFF::cleanup(); - - // If we're exiting to launch an URL, do that here so the screen - // is at the right resolution before we launch IE. - if (!gLaunchFileOnQuit.empty()) - { - LL_INFOS() << "Launch file on quit." << LL_ENDL; -#if LL_WINDOWS - // Indicate an application is starting. - SetCursor(LoadCursor(NULL, IDC_WAIT)); -#endif - - // HACK: Attempt to wait until the screen res. switch is complete. - ms_sleep(1000); - - LLWeb::loadURLExternal( gLaunchFileOnQuit, false ); - LL_INFOS() << "File launched." << LL_ENDL; - } - // make sure nothing uses applyProxySettings by this point. - LL_INFOS() << "Cleaning up LLProxy." << LL_ENDL; - SUBSYSTEM_CLEANUP(LLProxy); - LLCore::LLHttp::cleanup(); - - ll_close_fail_log(); - - LLError::LLCallStacks::cleanup(); - - LLEnvironment::deleteSingleton(); - LLSelectMgr::deleteSingleton(); - LLViewerEventRecorder::deleteSingleton(); - LLWorld::deleteSingleton(); - LLVoiceClient::deleteSingleton(); - - // It's not at first obvious where, in this long sequence, a generic cleanup - // call OUGHT to go. So let's say this: as we migrate cleanup from - // explicit hand-placed calls into the generic mechanism, eventually - // all cleanup will get subsumed into the generic call. So the calls you - // still see above are calls that MUST happen before the generic cleanup - // kicks in. - - // This calls every remaining LLSingleton's cleanupSingleton() and - // deleteSingleton() methods. - LLSingletonBase::deleteAll(); - - LLSplashScreen::hide(); - - LL_INFOS() << "Goodbye!" << LL_ENDL; - - removeDumpDir(); - - // return 0; - return true; -} - -void LLAppViewer::initGeneralThread() -{ - if (mGeneralThreadPool) - { - return; - } - - mGeneralThreadPool = new LL::ThreadPool("General", 3); - mGeneralThreadPool->start(); -} - -bool LLAppViewer::initThreads() -{ - static const bool enable_threads = true; - - LLImage::initClass(gSavedSettings.getBOOL("TextureNewByteRange"),gSavedSettings.getS32("TextureReverseByteRange")); - - LLLFSThread::initClass(enable_threads && true); // TODO: fix crashes associated with this shutdo - - //auto configure thread count - LLSD threadCounts = gSavedSettings.getLLSD("ThreadPoolSizes"); - - // get the number of concurrent threads that can run - S32 cores = std::thread::hardware_concurrency(); - - U32 max_cores = gSavedSettings.getU32("EmulateCoreCount"); - if (max_cores != 0) - { - cores = llmin(cores, (S32) max_cores); - } - - // The only configurable thread count right now is ImageDecode - // The viewer typically starts around 8 threads not including image decode, - // so try to leave at least one core free - S32 image_decode_count = llclamp(cores - 9, 1, 8); - threadCounts["ImageDecode"] = image_decode_count; - gSavedSettings.setLLSD("ThreadPoolSizes", threadCounts); - - // Image decoding - LLAppViewer::sImageDecodeThread = new LLImageDecodeThread(enable_threads && true); - LLAppViewer::sTextureCache = new LLTextureCache(enable_threads && true); - LLAppViewer::sTextureFetch = new LLTextureFetch(LLAppViewer::getTextureCache(), - enable_threads && true, - app_metrics_qa_mode); - - // general task background thread (LLPerfStats, etc) - LLAppViewer::instance()->initGeneralThread(); - - LLAppViewer::sPurgeDiskCacheThread = new LLPurgeDiskCacheThread(); - - if (LLTrace::BlockTimer::sLog || LLTrace::BlockTimer::sMetricLog) - { - LLTrace::BlockTimer::setLogLock(new LLMutex()); - mFastTimerLogThread = new LLFastTimerLogThread(LLTrace::BlockTimer::sLogName); - mFastTimerLogThread->start(); - } - - // Mesh streaming and caching - gMeshRepo.init(); - - LLFilePickerThread::initClass(); - LLDirPickerThread::initClass(); - - // *FIX: no error handling here! - return true; -} - -void errorCallback(LLError::ELevel level, const std::string &error_string) -{ - if (level == LLError::LEVEL_ERROR) - { -#ifndef LL_RELEASE_FOR_DOWNLOAD - OSMessageBox(error_string, LLTrans::getString("MBFatalError"), OSMB_OK); -#endif - - gDebugInfo["FatalMessage"] = error_string; - // We're not already crashing -- we simply *intend* to crash. Since we - // haven't actually trashed anything yet, we can afford to write the whole - // static info file. - LLAppViewer::instance()->writeDebugInfo(); - } -} - -void errorMSG(const std::string& title_string, const std::string& message_string) -{ - if (!message_string.empty()) - { - OSMessageBox(message_string, title_string.empty() ? LLTrans::getString("MBFatalError") : title_string, OSMB_OK); - } -} - -void LLAppViewer::initLoggingAndGetLastDuration() -{ - // - // Set up logging defaults for the viewer - // - LLError::initForApplication( gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "") - ,gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "") - ); - LLError::addGenericRecorder(&errorCallback); - //LLError::setTimeFunction(getRuntime); - - LLError::LLUserWarningMsg::setHandler(errorMSG); - - - if (mSecondInstance) - { - LLFile::mkdir(gDirUtilp->getDumpLogsDirPath()); - - LLUUID uid; - uid.generate(); - LLError::logToFile(gDirUtilp->getDumpLogsDirPath(uid.asString() + ".log")); - } - else - { - // Remove the last ".old" log file. - std::string old_log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, - "SecondLife.old"); - LLFile::remove(old_log_file); - - // Get name of the log file - std::string log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, - "SecondLife.log"); - /* - * Before touching any log files, compute the duration of the last run - * by comparing the ctime of the previous start marker file with the ctime - * of the last log file. - */ - std::string start_marker_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, START_MARKER_FILE_NAME); - llstat start_marker_stat; - llstat log_file_stat; - std::ostringstream duration_log_stream; // can't log yet, so save any message for when we can below - int start_stat_result = LLFile::stat(start_marker_file_name, &start_marker_stat); - int log_stat_result = LLFile::stat(log_file, &log_file_stat); - if (0 == start_stat_result && 0 == log_stat_result) - { - int elapsed_seconds = log_file_stat.st_ctime - start_marker_stat.st_ctime; - // only report a last run time if the last viewer was the same version - // because this stat will be counted against this version - if (markerIsSameVersion(start_marker_file_name)) - { - gLastExecDuration = elapsed_seconds; - } - else - { - duration_log_stream << "start marker from some other version; duration is not reported"; - gLastExecDuration = -1; - } - } - else - { - // at least one of the LLFile::stat calls failed, so we can't compute the run time - duration_log_stream << "duration stat failure; start: " << start_stat_result << " log: " << log_stat_result; - gLastExecDuration = -1; // unknown - } - std::string duration_log_msg(duration_log_stream.str()); - - // Create a new start marker file for comparison with log file time for the next run - LLAPRFile start_marker_file; - start_marker_file.open(start_marker_file_name, LL_APR_WB); - if (start_marker_file.getFileHandle()) - { - recordMarkerVersion(start_marker_file); - start_marker_file.close(); - } - - // Rename current log file to ".old" - LLFile::rename(log_file, old_log_file); - - // Set the log file to SecondLife.log - LLError::logToFile(log_file); - LL_INFOS() << "Started logging to " << log_file << LL_ENDL; - if (!duration_log_msg.empty()) - { - LL_WARNS("MarkerFile") << duration_log_msg << LL_ENDL; - } - } -} - -bool LLAppViewer::loadSettingsFromDirectory(const std::string& location_key, - bool set_defaults) -{ - if (!mSettingsLocationList) - { - LL_ERRS() << "Invalid settings location list" << LL_ENDL; - } - - for (const SettingsGroup& group : mSettingsLocationList->groups) - { - // skip settings groups that aren't the one we requested - if (group.name() != location_key) continue; - - ELLPath path_index = (ELLPath)group.path_index(); - if(path_index <= LL_PATH_NONE || path_index >= LL_PATH_LAST) - { - LL_ERRS() << "Out of range path index in app_settings/settings_files.xml" << LL_ENDL; - return false; - } - - for (const SettingsFile& file : group.files) - { - LL_INFOS("Settings") << "Attempting to load settings for the group " << file.name() - << " - from location " << location_key << LL_ENDL; - - auto settings_group = LLControlGroup::getInstance(file.name); - if(!settings_group) - { - LL_WARNS("Settings") << "No matching settings group for name " << file.name() << LL_ENDL; - continue; - } - - std::string full_settings_path; - - if (file.file_name_setting.isProvided() - && gSavedSettings.controlExists(file.file_name_setting)) - { - // try to find filename stored in file_name_setting control - full_settings_path = gSavedSettings.getString(file.file_name_setting()); - if (full_settings_path.empty()) - { - continue; - } - else if (!gDirUtilp->fileExists(full_settings_path)) - { - // search in default path - full_settings_path = gDirUtilp->getExpandedFilename((ELLPath)path_index, full_settings_path); - } - } - else - { - // by default, use specified file name - full_settings_path = gDirUtilp->getExpandedFilename((ELLPath)path_index, file.file_name()); - } - - if(settings_group->loadFromFile(full_settings_path, set_defaults, file.persistent)) - { // success! - LL_INFOS("Settings") << "Loaded settings file " << full_settings_path << LL_ENDL; - } - else - { // failed to load - if(file.required) - { - LLError::LLUserWarningMsg::showMissingFiles(); - LL_ERRS() << "Error: Cannot load required settings file from: " << full_settings_path << LL_ENDL; - return false; - } - else - { - // only complain if we actually have a filename at this point - if (!full_settings_path.empty()) - { - LL_INFOS("Settings") << "Cannot load " << full_settings_path << " - No settings found." << LL_ENDL; - } - } - } - } - } - - return true; -} - -std::string LLAppViewer::getSettingsFilename(const std::string& location_key, - const std::string& file) -{ - for (const SettingsGroup& group : mSettingsLocationList->groups) - { - if (group.name() == location_key) - { - for (const SettingsFile& settings_file : group.files) - { - if (settings_file.name() == file) - { - return settings_file.file_name; - } - } - } - } - - return std::string(); -} - -void LLAppViewer::loadColorSettings() -{ - LLUIColorTable::instance().loadFromSettings(); -} - -namespace -{ - void handleCommandLineError(LLControlGroupCLP& clp) - { - LL_WARNS() << "Error parsing command line options. Command Line options ignored." << LL_ENDL; - - LL_INFOS() << "Command line usage:\n" << clp << LL_ENDL; - - OSMessageBox(STRINGIZE(LLTrans::getString("MBCmdLineError") << clp.getErrorMessage()), - LLStringUtil::null, - OSMB_OK); - } -} // anonymous namespace - -// Set a named control temporarily for this session, as when set via the command line --set option. -// Name can be specified as ".", with default group being Global. -bool tempSetControl(const std::string& name, const std::string& value) -{ - std::string name_part; - std::string group_part; - LLControlVariable* control = NULL; - - // Name can be further split into ControlGroup.Name, with the default control group being Global - size_t pos = name.find('.'); - if (pos != std::string::npos) - { - group_part = name.substr(0, pos); - name_part = name.substr(pos+1); - LL_INFOS() << "Setting " << group_part << "." << name_part << " to " << value << LL_ENDL; - auto g = LLControlGroup::getInstance(group_part); - if (g) control = g->getControl(name_part); - } - else - { - LL_INFOS() << "Setting Global." << name << " to " << value << LL_ENDL; - control = gSavedSettings.getControl(name); - } - - if (control) - { - control->setValue(value, false); - return true; - } - return false; -} - -bool LLAppViewer::initConfiguration() -{ - //Load settings files list - std::string settings_file_list = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "settings_files.xml"); - LLXMLNodePtr root; - bool success = LLXMLNode::parseFile(settings_file_list, root, NULL); - if (!success) - { - LL_WARNS() << "Cannot load default configuration file " << settings_file_list << LL_ENDL; - LLError::LLUserWarningMsg::showMissingFiles(); - if (gDirUtilp->fileExists(settings_file_list)) - { - LL_ERRS() << "Cannot load default configuration file settings_files.xml. " - << "Please reinstall viewer from https://secondlife.com/support/downloads/ " - << "and contact https://support.secondlife.com if issue persists after reinstall." - << LL_ENDL; - } - else - { - LL_ERRS() << "Default configuration file settings_files.xml not found. " - << "Please reinstall viewer from https://secondlife.com/support/downloads/ " - << "and contact https://support.secondlife.com if issue persists after reinstall." - << LL_ENDL; - } - } - - mSettingsLocationList = new SettingsFiles(); - - LLXUIParser parser; - parser.readXUI(root, *mSettingsLocationList, settings_file_list); - - if (!mSettingsLocationList->validateBlock()) - { - LLError::LLUserWarningMsg::showMissingFiles(); - LL_ERRS() << "Invalid settings file list " << settings_file_list << LL_ENDL; - } - - // The settings and command line parsing have a fragile - // order-of-operation: - // - load defaults from app_settings - // - set procedural settings values - // - read command line settings - // - selectively apply settings needed to load user settings. - // - load overrides from user_settings - // - apply command line settings (to override the overrides) - // - load per account settings (happens in llstartup - - // - load defaults - bool set_defaults = true; - if(!loadSettingsFromDirectory("Default", set_defaults)) - { - OSMessageBox( - "Unable to load default settings file. The installation may be corrupted.", - LLStringUtil::null,OSMB_OK); - return false; - } - - initStrings(); // setup paths for LLTrans based on settings files only - // - set procedural settings - // Note: can't use LL_PATH_PER_SL_ACCOUNT for any of these since we haven't logged in yet - gSavedSettings.setString("ClientSettingsFile", - gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, getSettingsFilename("Default", "Global"))); - -#ifndef LL_RELEASE_FOR_DOWNLOAD - // provide developer build only overrides for these control variables that are not - // persisted to settings.xml - LLControlVariable* c = gSavedSettings.getControl("AllowMultipleViewers"); - if (c) - { - c->setValue(true, false); - } - - gSavedSettings.setBOOL("QAMode", true ); - gSavedSettings.setS32("WatchdogEnabled", 0); -#endif - - // These are warnings that appear on the first experience of that condition. - // They are already set in the settings_default.xml file, but still need to be added to LLFirstUse - // for disable/reset ability -// LLFirstUse::addConfigVariable("FirstBalanceIncrease"); -// LLFirstUse::addConfigVariable("FirstBalanceDecrease"); -// LLFirstUse::addConfigVariable("FirstSit"); -// LLFirstUse::addConfigVariable("FirstMap"); -// LLFirstUse::addConfigVariable("FirstGoTo"); -// LLFirstUse::addConfigVariable("FirstBuild"); -// LLFirstUse::addConfigVariable("FirstLeftClickNoHit"); -// LLFirstUse::addConfigVariable("FirstTeleport"); -// LLFirstUse::addConfigVariable("FirstOverrideKeys"); -// LLFirstUse::addConfigVariable("FirstAttach"); -// LLFirstUse::addConfigVariable("FirstAppearance"); -// LLFirstUse::addConfigVariable("FirstInventory"); -// LLFirstUse::addConfigVariable("FirstSandbox"); -// LLFirstUse::addConfigVariable("FirstFlexible"); -// LLFirstUse::addConfigVariable("FirstDebugMenus"); -// LLFirstUse::addConfigVariable("FirstSculptedPrim"); -// LLFirstUse::addConfigVariable("FirstVoice"); -// LLFirstUse::addConfigVariable("FirstMedia"); - - // - read command line settings. - LLControlGroupCLP clp; - std::string cmd_line_config = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, - "cmd_line.xml"); - - clp.configure(cmd_line_config, &gSavedSettings); - - if(!initParseCommandLine(clp)) - { - handleCommandLineError(clp); - return false; - } - - // - selectively apply settings - - // If the user has specified a alternate settings file name. - // Load it now before loading the user_settings/settings.xml - if(clp.hasOption("settings")) - { - std::string user_settings_filename = - gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, - clp.getOption("settings")[0]); - gSavedSettings.setString("ClientSettingsFile", user_settings_filename); - LL_INFOS("Settings") << "Using command line specified settings filename: " - << user_settings_filename << LL_ENDL; - } - - // - load overrides from user_settings - loadSettingsFromDirectory("User"); - - if (gSavedSettings.getBOOL("FirstRunThisInstall")) - { - // Set firstrun flag to indicate that some further init actiona should be taken - // like determining screen DPI value and so on - mIsFirstRun = true; - - gSavedSettings.setBOOL("FirstRunThisInstall", false); - } - - if (clp.hasOption("sessionsettings")) - { - std::string session_settings_filename = clp.getOption("sessionsettings")[0]; - gSavedSettings.setString("SessionSettingsFile", session_settings_filename); - LL_INFOS("Settings") << "Using session settings filename: " - << session_settings_filename << LL_ENDL; - } - loadSettingsFromDirectory("Session"); - - if (clp.hasOption("usersessionsettings")) - { - std::string user_session_settings_filename = clp.getOption("usersessionsettings")[0]; - gSavedSettings.setString("UserSessionSettingsFile", user_session_settings_filename); - LL_INFOS("Settings") << "Using user session settings filename: " - << user_session_settings_filename << LL_ENDL; - - } - loadSettingsFromDirectory("UserSession"); - - // - apply command line settings - if (! clp.notify()) - { - handleCommandLineError(clp); - return false; - } - - // Register the core crash option as soon as we can - // if we want gdb post-mortem on cores we need to be up and running - // ASAP or we might miss init issue etc. - if(gSavedSettings.getBOOL("DisableCrashLogger")) - { - LL_WARNS() << "Crashes will be handled by system, stack trace logs and crash logger are both disabled" << LL_ENDL; - disableCrashlogger(); - } - - gNonInteractive = gSavedSettings.getBOOL("NonInteractive"); - // Handle initialization from settings. - // Start up the debugging console before handling other options. - if (gSavedSettings.getBOOL("ShowConsoleWindow") && !gNonInteractive) - { - initConsole(); - } - - if(clp.hasOption("help")) - { - std::ostringstream msg; - msg << LLTrans::getString("MBCmdLineUsg") << "\n" << clp; - LL_INFOS() << msg.str() << LL_ENDL; - - OSMessageBox( - msg.str(), - LLStringUtil::null, - OSMB_OK); - - return false; - } - - if(clp.hasOption("set")) - { - const LLCommandLineParser::token_vector_t& set_values = clp.getOption("set"); - if(0x1 & set_values.size()) - { - LL_WARNS() << "Invalid '--set' parameter count." << LL_ENDL; - } - else - { - LLCommandLineParser::token_vector_t::const_iterator itr = set_values.begin(); - for(; itr != set_values.end(); ++itr) - { - const std::string& name = *itr; - const std::string& value = *(++itr); - if (!tempSetControl(name,value)) - { - LL_WARNS() << "Failed --set " << name << ": setting name unknown." << LL_ENDL; - } - } - } - } - - if (clp.hasOption("logevents")) { - LLViewerEventRecorder::instance().setEventLoggingOn(); - } - - std::string CmdLineChannel(gSavedSettings.getString("CmdLineChannel")); - if(! CmdLineChannel.empty()) - { - LLVersionInfo::instance().resetChannel(CmdLineChannel); - } - - // If we have specified crash on startup, set the global so we'll trigger the crash at the right time - gCrashOnStartup = gSavedSettings.getBOOL("CrashOnStartup"); - - if (gSavedSettings.getBOOL("LogPerformance")) - { - LLTrace::BlockTimer::sLog = true; - LLTrace::BlockTimer::sLogName = std::string("performance"); - } - - std::string test_name(gSavedSettings.getString("LogMetrics")); - if (! test_name.empty()) - { - LLTrace::BlockTimer::sMetricLog = true; - // '--logmetrics' is specified with a named test metric argument so the data gathering is done only on that test - // In the absence of argument, every metric would be gathered (makes for a rather slow run and hard to decipher report...) - LL_INFOS() << "'--logmetrics' argument : " << test_name << LL_ENDL; - LLTrace::BlockTimer::sLogName = test_name; - } - - if (clp.hasOption("graphicslevel")) - { - // User explicitly requested --graphicslevel on the command line. We - // expect this switch has already set RenderQualityPerformance. Check - // that value for validity later. - // Capture the requested value separately from the settings variable - // because, if this is the first run, LLViewerWindow's constructor - // will call LLFeatureManager::applyRecommendedSettings(), which - // overwrites this settings variable! - mForceGraphicsLevel = gSavedSettings.getU32("RenderQualityPerformance"); - } - - LLFastTimerView::sAnalyzePerformance = gSavedSettings.getBOOL("AnalyzePerformance"); - gAgentPilot.setReplaySession(gSavedSettings.getBOOL("ReplaySession")); - - if (gSavedSettings.getBOOL("DebugSession")) - { - gDebugSession = true; - gDebugGL = true; - - ll_init_fail_log(gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "test_failures.log")); - } - - if (gSavedSettings.getBOOL("RenderDebugGLSession")) - { - gDebugGLSession = true; - gDebugGL = true; - // gDebugGL can cause excessive logging - // so it's limited to a single session - gSavedSettings.setBOOL("RenderDebugGLSession", false); - } - - const LLControlVariable* skinfolder = gSavedSettings.getControl("SkinCurrent"); - if(skinfolder && LLStringUtil::null != skinfolder->getValue().asString()) - { - // Examining "Language" may not suffice -- see LLUI::getLanguage() - // logic. Unfortunately LLUI::getLanguage() doesn't yet do us much - // good because we haven't yet called LLUI::initClass(). - gDirUtilp->setSkinFolder(skinfolder->getValue().asString(), - gSavedSettings.getString("Language")); - } - - if (gSavedSettings.getBOOL("SpellCheck")) - { - std::list dict_list; - std::string dict_setting = gSavedSettings.getString("SpellCheckDictionary"); - boost::split(dict_list, dict_setting, boost::is_any_of(std::string(","))); - if (!dict_list.empty()) - { - LLSpellChecker::setUseSpellCheck(dict_list.front()); - dict_list.pop_front(); - LLSpellChecker::instance().setSecondaryDictionaries(dict_list); - } - } - - if (gNonInteractive) - { - tempSetControl("AllowMultipleViewers", "true"); - tempSetControl("SLURLPassToOtherInstance", "false"); - tempSetControl("RenderWater", "false"); - tempSetControl("FlyingAtExit", "false"); - tempSetControl("WindowWidth", "1024"); - tempSetControl("WindowHeight", "200"); - LLError::setEnabledLogTypesMask(0); - llassert_always(!gSavedSettings.getBOOL("SLURLPassToOtherInstance")); - } - - - // Handle slurl use. NOTE: Don't let SL-55321 reappear. - // This initial-SLURL logic, up through the call to - // sendURLToOtherInstance(), must precede LLSplashScreen::show() -- - // because if sendURLToOtherInstance() succeeds, we take a fast exit, - // SKIPPING the splash screen and everything else. - - // *FIX: This init code should be made more robust to prevent - // the issue SL-55321 from returning. One thought is to allow - // only select options to be set from command line when a slurl - // is specified. More work on the settings system is needed to - // achieve this. For now... - - // *NOTE:Mani The command line parser parses tokens and is - // setup to bail after parsing the '--url' option or the - // first option specified without a '--option' flag (or - // any other option that uses the 'last_option' setting - - // see LLControlGroupCLP::configure()) - - // What can happen is that someone can use IE (or potentially - // other browsers) and do the rough equivalent of command - // injection and steal passwords. Phoenix. SL-55321 - - std::string starting_location; - - std::string cmd_line_login_location(gSavedSettings.getString("CmdLineLoginLocation")); - if(! cmd_line_login_location.empty()) - { - starting_location = cmd_line_login_location; - } - else - { - std::string default_login_location(gSavedSettings.getString("DefaultLoginLocation")); - if (! default_login_location.empty()) - { - starting_location = default_login_location; - } - } - - LLSLURL start_slurl; - if (! starting_location.empty()) - { - start_slurl = starting_location; - LLStartUp::setStartSLURL(start_slurl); - if(start_slurl.getType() == LLSLURL::LOCATION) - { - LLGridManager::getInstance()->setGridChoice(start_slurl.getGrid()); - } - } - - // NextLoginLocation is set as a side effect of LLStartUp::setStartSLURL() - std::string nextLoginLocation = gSavedSettings.getString( "NextLoginLocation" ); - if ( !nextLoginLocation.empty() ) - { - LL_DEBUGS("AppInit")<<"set start from NextLoginLocation: "<useMutex(); // LLApp and LLMutex magic must be manually enabled - LLPrimitive::setVolumeManager(volume_manager); - - // Note: this is where we used to initialize gFeatureManagerp. - - gStartTime = totalTime(); - - // - // Set the name of the window - // - gWindowTitle = LLTrans::getString("APP_NAME"); -#if LL_DEBUG - gWindowTitle += std::string(" [DEBUG]"); -#endif - if (!gArgs.empty()) - { - gWindowTitle += std::string(" ") + gArgs; - } - LLStringUtil::truncate(gWindowTitle, 255); - - // - // Check for another instance of the app running - // This happens AFTER LLSplashScreen::show(). That may or may not be - // important. - // - if (mSecondInstance && !gSavedSettings.getBOOL("AllowMultipleViewers")) - { - OSMessageBox( - LLTrans::getString("MBAlreadyRunning"), - LLStringUtil::null, - OSMB_OK); - return false; - } - - if (mSecondInstance) - { - // This is the second instance of SL. Mute voice, - // but make sure the setting is *not* persisted. - // Also see LLVivoxVoiceClient::voiceEnabled() - LLControlVariable* enable_voice = gSavedSettings.getControl("EnableVoiceChat"); - if(enable_voice) - { - const bool DO_NOT_PERSIST = false; - enable_voice->setValue(LLSD(false), DO_NOT_PERSIST); - } - } - - gLastRunVersion = gSavedSettings.getString("LastRunVersion"); - - loadColorSettings(); - - // Let anyone else who cares know that we've populated our settings - // variables. - for (const auto& key : LLControlGroup::key_snapshot()) - { - // For each named instance of LLControlGroup, send an event saying - // we've initialized an LLControlGroup instance by that name. - LLEventPumps::instance().obtain("LLControlGroup").post(LLSDMap("init", key)); - } - - LLError::LLUserWarningMsg::setOutOfMemoryStrings(LLTrans::getString("MBOutOfMemoryTitle"), LLTrans::getString("MBOutOfMemoryErr")); - - return true; // Config was successful. -} - -// The following logic is replicated in initConfiguration() (to be able to get -// some initial strings before we've finished initializing enough to know the -// current language) and also in init() (to initialize for real). Somehow it -// keeps growing, necessitating a method all its own. -void LLAppViewer::initStrings() -{ - std::string strings_file = "strings.xml"; - std::string strings_path_full = gDirUtilp->findSkinnedFilenameBaseLang(LLDir::XUI, strings_file); - if (strings_path_full.empty() || !LLFile::isfile(strings_path_full)) - { - if (strings_path_full.empty()) - { - LL_WARNS() << "The file '" << strings_file << "' is not found" << LL_ENDL; - } - else - { - llstat st; - int rc = LLFile::stat(strings_path_full, &st); - if (rc != 0) - { - LL_WARNS() << "The file '" << strings_path_full << "' failed to get status. Error code: " << rc << LL_ENDL; - } - else if (S_ISDIR(st.st_mode)) - { - LL_WARNS() << "The filename '" << strings_path_full << "' is a directory name" << LL_ENDL; - } - else - { - LL_WARNS() << "The filename '" << strings_path_full << "' doesn't seem to be a regular file name" << LL_ENDL; - } - } - - // initial check to make sure files are there failed - gDirUtilp->dumpCurrentDirectories(LLError::LEVEL_WARN); - LLError::LLUserWarningMsg::showMissingFiles(); - LL_ERRS() << "Viewer failed to find localization and UI files." - << " Please reinstall viewer from https://secondlife.com/support/downloads" - << " and contact https://support.secondlife.com if issue persists after reinstall." << LL_ENDL; - } - LLTransUtil::parseStrings(strings_file, default_trans_args); - LLTransUtil::parseLanguageStrings("language_settings.xml"); - - // parseStrings() sets up the LLTrans substitution table. Add this one item. - LLTrans::setDefaultArg("[sourceid]", gSavedSettings.getString("sourceid")); - - // Now that we've set "[sourceid]", have to go back through - // default_trans_args and reinitialize all those other keys because some - // of them, in turn, reference "[sourceid]". - for (const std::string& key : default_trans_args) - { - std::string brackets(key), nobrackets(key); - // Invalid to inspect key[0] if key is empty(). But then, the entire - // body of this loop is pointless if key is empty(). - if (key.empty()) - continue; - - if (key[0] != '[') - { - // key was passed without brackets. That means that 'nobrackets' - // is correct but 'brackets' is not. - brackets = STRINGIZE('[' << brackets << ']'); - } - else - { - // key was passed with brackets. That means that 'brackets' is - // correct but 'nobrackets' is not. Erase the left bracket. - nobrackets.erase(0, 1); - std::string::size_type length(nobrackets.length()); - if (length && nobrackets[length - 1] == ']') - { - nobrackets.erase(length - 1); - } - } - // Calling LLTrans::getString() is what embeds the other default - // translation strings into this one. - LLTrans::setDefaultArg(brackets, LLTrans::getString(nobrackets)); - } -} - -bool LLAppViewer::meetsRequirementsForMaximizedStart() -{ - bool maximizedOk = (gSysMemory.getPhysicalMemoryKB() >= U32Gigabytes(1)); - - return maximizedOk; -} - -bool LLAppViewer::initWindow() -{ - LL_INFOS("AppInit") << "Initializing window..." << LL_ENDL; - - // store setting in a global for easy access and modification - gHeadlessClient = gSavedSettings.getBOOL("HeadlessClient"); - - // always start windowed - bool ignorePixelDepth = gSavedSettings.getBOOL("IgnorePixelDepth"); - - LLViewerWindow::Params window_params; - window_params - .title(gWindowTitle) - .name(VIEWER_WINDOW_CLASSNAME) - .x(gSavedSettings.getS32("WindowX")) - .y(gSavedSettings.getS32("WindowY")) - .width(gSavedSettings.getU32("WindowWidth")) - .height(gSavedSettings.getU32("WindowHeight")) - .min_width(gSavedSettings.getU32("MinWindowWidth")) - .min_height(gSavedSettings.getU32("MinWindowHeight")) - .fullscreen(gSavedSettings.getBOOL("FullScreen")) - .ignore_pixel_depth(ignorePixelDepth) - .first_run(mIsFirstRun); - - gViewerWindow = new LLViewerWindow(window_params); - - LL_INFOS("AppInit") << "gViewerwindow created." << LL_ENDL; - - // Need to load feature table before cheking to start watchdog. - bool use_watchdog = false; - int watchdog_enabled_setting = gSavedSettings.getS32("WatchdogEnabled"); - if (watchdog_enabled_setting == -1) - { - use_watchdog = !LLFeatureManager::getInstance()->isFeatureAvailable("WatchdogDisabled"); - } - else - { - // The user has explicitly set this setting; always use that value. - use_watchdog = bool(watchdog_enabled_setting); - } - - LL_INFOS("AppInit") << "watchdog" - << (use_watchdog ? " " : " NOT ") - << "enabled" - << " (setting = " << watchdog_enabled_setting << ")" - << LL_ENDL; - - if (use_watchdog) - { - LLWatchdog::getInstance()->init(); - } - - LLNotificationsUI::LLNotificationManager::getInstance(); - - -#ifdef LL_DARWIN - //Satisfy both MAINT-3135 (OSX 10.6 and earlier) MAINT-3288 (OSX 10.7 and later) - LLOSInfo& os_info = LLOSInfo::instance(); - if (os_info.mMajorVer == 10 && os_info.mMinorVer < 7) - { - if ( os_info.mMinorVer == 6 && os_info.mBuild < 8 ) - gViewerWindow->getWindow()->setOldResize(true); - } -#endif - - if (gSavedSettings.getBOOL("WindowMaximized")) - { - gViewerWindow->getWindow()->maximize(); - } - - // - // Initialize GL stuff - // - - if (mForceGraphicsLevel && (LLFeatureManager::instance().isValidGraphicsLevel(*mForceGraphicsLevel))) - { - LLFeatureManager::getInstance()->setGraphicsLevel(*mForceGraphicsLevel, false); - gSavedSettings.setU32("RenderQualityPerformance", *mForceGraphicsLevel); - } - - // Set this flag in case we crash while initializing GL - gSavedSettings.setBOOL("RenderInitError", true); - gSavedSettings.saveToFile( gSavedSettings.getString("ClientSettingsFile"), true ); - - gPipeline.init(); - LL_INFOS("AppInit") << "gPipeline Initialized" << LL_ENDL; - - stop_glerror(); - gViewerWindow->initGLDefaults(); - - gSavedSettings.setBOOL("RenderInitError", false); - gSavedSettings.saveToFile( gSavedSettings.getString("ClientSettingsFile"), true ); - - //If we have a startup crash, it's usually near GL initialization, so simulate that. - if(gCrashOnStartup) - { - LLAppViewer::instance()->forceErrorLLError(); - } - - // - // Determine if the window should start maximized on initial run based - // on graphics capability - // - if (gSavedSettings.getBOOL("FirstLoginThisInstall") && meetsRequirementsForMaximizedStart()) - { - LL_INFOS("AppInit") << "This client met the requirements for a maximized initial screen." << LL_ENDL; - gSavedSettings.setBOOL("WindowMaximized", true); - } - - if (gSavedSettings.getBOOL("WindowMaximized")) - { - gViewerWindow->getWindow()->maximize(); - } - - LLUI::getInstance()->mWindow = gViewerWindow->getWindow(); - - // Show watch cursor - gViewerWindow->setCursor(UI_CURSOR_WAIT); - - // Finish view initialization - gViewerWindow->initBase(); - - // show viewer window - //gViewerWindow->getWindow()->show(); - - LL_INFOS("AppInit") << "Window initialization done." << LL_ENDL; - - return true; -} - -bool LLAppViewer::isUpdaterMissing() -{ - return mUpdaterNotFound; -} - -bool LLAppViewer::waitForUpdater() -{ - return !gSavedSettings.getBOOL("CmdLineSkipUpdater") && !mUpdaterNotFound && !gNonInteractive; -} - -void LLAppViewer::writeDebugInfo(bool isStatic) -{ -#if LL_WINDOWS && LL_BUGSPLAT - // bugsplat does not create dump folder and debug logs are written directly - // to logs folder, so it conflicts with main instance - if (mSecondInstance) - { - return; - } -#endif - - //Try to do the minimum when writing data during a crash. - std::string* debug_filename; - debug_filename = ( isStatic - ? getStaticDebugFile() - : getDynamicDebugFile() ); - - LL_INFOS() << "Writing debug file " << *debug_filename << LL_ENDL; - llofstream out_file(debug_filename->c_str()); - - isStatic ? LLSDSerialize::toPrettyXML(gDebugInfo, out_file) - : LLSDSerialize::toPrettyXML(gDebugInfo["Dynamic"], out_file); -} - -LLSD LLAppViewer::getViewerInfo() const -{ - // The point of having one method build an LLSD info block and the other - // construct the user-visible About string is to ensure that the same info - // is available to a getInfo() caller as to the user opening - // LLFloaterAbout. - LLSD info; - auto& versionInfo(LLVersionInfo::instance()); - // With GitHub builds, the build number is too big to fit in a 32-bit int, - // and LLSD doesn't deal with integers wider than int. Use string. - info["VIEWER_VERSION"] = llsd::array(versionInfo.getMajor(), versionInfo.getMinor(), - versionInfo.getPatch(), stringize(versionInfo.getBuild())); - info["VIEWER_VERSION_STR"] = versionInfo.getVersion(); - info["CHANNEL"] = versionInfo.getChannel(); - info["ADDRESS_SIZE"] = ADDRESS_SIZE; - std::string build_config = versionInfo.getBuildConfig(); - if (build_config != "Release") - { - info["BUILD_CONFIG"] = build_config; - } - - // return a URL to the release notes for this viewer, such as: - // https://releasenotes.secondlife.com/viewer/2.1.0.123456.html - std::string url = versionInfo.getReleaseNotes(); // VVM supplied - if (url.empty()) - { - url = LLTrans::getString("RELEASE_NOTES_BASE_URL"); - if (!LLStringUtil::endsWith(url, "/")) - url += "/"; - url += LLURI::escape(versionInfo.getVersion()) + ".html"; - } - info["VIEWER_RELEASE_NOTES_URL"] = url; - - // Position - LLViewerRegion* region = gAgent.getRegion(); - if (region) - { - LLVector3d pos = gAgent.getPositionGlobal(); - info["POSITION"] = ll_sd_from_vector3d(pos); - info["POSITION_LOCAL"] = ll_sd_from_vector3(gAgent.getPosAgentFromGlobal(pos)); - info["REGION"] = gAgent.getRegion()->getName(); - - boost::regex regex("\\.(secondlife|lindenlab)\\..*"); - info["HOSTNAME"] = boost::regex_replace(gAgent.getRegion()->getSimHostName(), regex, ""); - info["SERVER_VERSION"] = gLastVersionChannel; - LLSLURL slurl; - LLAgentUI::buildSLURL(slurl); - info["SLURL"] = slurl.getSLURLString(); - } - - // CPU - info["CPU"] = gSysCPU.getCPUString(); - info["MEMORY_MB"] = LLSD::Integer(gSysMemory.getPhysicalMemoryKB().valueInUnits()); - // Moved hack adjustment to Windows memory size into llsys.cpp - info["OS_VERSION"] = LLOSInfo::instance().getOSString(); - info["GRAPHICS_CARD_VENDOR"] = ll_safe_string((const char*)(glGetString(GL_VENDOR))); - info["GRAPHICS_CARD"] = ll_safe_string((const char*)(glGetString(GL_RENDERER))); - -#if LL_WINDOWS - std::string drvinfo; - - if (gGLManager.mIsIntel) - { - drvinfo = gDXHardware.getDriverVersionWMI(LLDXHardware::GPU_INTEL); - } - else if (gGLManager.mIsNVIDIA) - { - drvinfo = gDXHardware.getDriverVersionWMI(LLDXHardware::GPU_NVIDIA); - } - else if (gGLManager.mIsAMD) - { - drvinfo = gDXHardware.getDriverVersionWMI(LLDXHardware::GPU_AMD); - } - - if (drvinfo.empty()) - { - // Generic/substitute windows driver? Unknown vendor? - LL_WARNS("DriverVersion") << "Vendor based driver search failed, searching for any driver" << LL_ENDL; - drvinfo = gDXHardware.getDriverVersionWMI(LLDXHardware::GPU_ANY); - } - - if (!drvinfo.empty()) - { - info["GRAPHICS_DRIVER_VERSION"] = drvinfo; - } - else - { - LL_WARNS("DriverVersion")<< "Cannot get driver version from getDriverVersionWMI" << LL_ENDL; - LLSD driver_info = gDXHardware.getDisplayInfo(); - if (driver_info.has("DriverVersion")) - { - info["GRAPHICS_DRIVER_VERSION"] = driver_info["DriverVersion"]; - } - } -#endif - - info["OPENGL_VERSION"] = ll_safe_string((const char*)(glGetString(GL_VERSION))); - - // Settings - - LLRect window_rect = gViewerWindow->getWindowRectRaw(); - info["WINDOW_WIDTH"] = window_rect.getWidth(); - info["WINDOW_HEIGHT"] = window_rect.getHeight(); - info["FONT_SIZE_ADJUSTMENT"] = gSavedSettings.getF32("FontScreenDPI"); - info["UI_SCALE"] = gSavedSettings.getF32("UIScaleFactor"); - info["DRAW_DISTANCE"] = gSavedSettings.getF32("RenderFarClip"); - info["NET_BANDWITH"] = gSavedSettings.getF32("ThrottleBandwidthKBPS"); - info["LOD_FACTOR"] = gSavedSettings.getF32("RenderVolumeLODFactor"); - info["RENDER_QUALITY"] = (F32)gSavedSettings.getU32("RenderQualityPerformance"); - info["TEXTURE_MEMORY"] = gGLManager.mVRAM; - -#if LL_DARWIN - info["HIDPI"] = gHiDPISupport; -#endif - - // Libraries - - info["J2C_VERSION"] = LLImageJ2C::getEngineInfo(); - bool want_fullname = true; - info["AUDIO_DRIVER_VERSION"] = gAudiop ? LLSD(gAudiop->getDriverName(want_fullname)) : "Undefined"; - if(LLVoiceClient::getInstance()->voiceEnabled()) - { - LLVoiceVersionInfo version = LLVoiceClient::getInstance()->getVersion(); - const std::string build_version = version.mBuildVersion; - std::ostringstream version_string; - if (std::equal(build_version.begin(), build_version.begin() + version.serverVersion.size(), - version.serverVersion.begin())) - { // Normal case: Show type and build version. - version_string << version.serverType << " " << build_version << std::endl; - } - else - { // Mismatch: Show both versions. - version_string << version.serverVersion << "/" << build_version << std::endl; - } - info["VOICE_VERSION"] = version_string.str(); - } - else - { - info["VOICE_VERSION"] = LLTrans::getString("NotConnected"); - } - -#if !LL_LINUX - std::ostringstream cef_ver_codec; - cef_ver_codec << "Dullahan: "; - cef_ver_codec << DULLAHAN_VERSION_MAJOR; - cef_ver_codec << "."; - cef_ver_codec << DULLAHAN_VERSION_MINOR; - cef_ver_codec << "."; - cef_ver_codec << DULLAHAN_VERSION_POINT; - cef_ver_codec << "."; - cef_ver_codec << DULLAHAN_VERSION_BUILD; - - cef_ver_codec << std::endl; - cef_ver_codec << " CEF: "; - cef_ver_codec << CEF_VERSION; - - cef_ver_codec << std::endl; - cef_ver_codec << " Chromium: "; - cef_ver_codec << CHROME_VERSION_MAJOR; - cef_ver_codec << "."; - cef_ver_codec << CHROME_VERSION_MINOR; - cef_ver_codec << "."; - cef_ver_codec << CHROME_VERSION_BUILD; - cef_ver_codec << "."; - cef_ver_codec << CHROME_VERSION_PATCH; - - info["LIBCEF_VERSION"] = cef_ver_codec.str(); -#else - info["LIBCEF_VERSION"] = "Undefined"; -#endif - -#if !LL_LINUX - std::ostringstream vlc_ver_codec; - vlc_ver_codec << LIBVLC_VERSION_MAJOR; - vlc_ver_codec << "."; - vlc_ver_codec << LIBVLC_VERSION_MINOR; - vlc_ver_codec << "."; - vlc_ver_codec << LIBVLC_VERSION_REVISION; - info["LIBVLC_VERSION"] = vlc_ver_codec.str(); -#else - info["LIBVLC_VERSION"] = "Undefined"; -#endif - - S32 packets_in = LLViewerStats::instance().getRecording().getSum(LLStatViewer::PACKETS_IN); - if (packets_in > 0) - { - info["PACKETS_LOST"] = LLViewerStats::instance().getRecording().getSum(LLStatViewer::PACKETS_LOST); - info["PACKETS_IN"] = packets_in; - info["PACKETS_PCT"] = 100.f*info["PACKETS_LOST"].asReal() / info["PACKETS_IN"].asReal(); - } - - if (mServerReleaseNotesURL.empty()) - { - if (gAgent.getRegion()) - { - info["SERVER_RELEASE_NOTES_URL"] = LLTrans::getString("RetrievingData"); - } - else - { - info["SERVER_RELEASE_NOTES_URL"] = LLTrans::getString("NotConnected"); - } - } - else if (LLStringUtil::startsWith(mServerReleaseNotesURL, "http")) // it's an URL - { - info["SERVER_RELEASE_NOTES_URL"] = "[" + LLWeb::escapeURL(mServerReleaseNotesURL) + " " + LLTrans::getString("ReleaseNotes") + "]"; - } - else - { - info["SERVER_RELEASE_NOTES_URL"] = mServerReleaseNotesURL; - } - - // populate field for new local disk cache with some details - info["DISK_CACHE_INFO"] = LLDiskCache::getInstance()->getCacheInfo(); - - return info; -} - -std::string LLAppViewer::getViewerInfoString(bool default_string) const -{ - std::ostringstream support; - - LLSD info(getViewerInfo()); - - // Render the LLSD from getInfo() as a format_map_t - LLStringUtil::format_map_t args; - - // allow the "Release Notes" URL label to be localized - args["ReleaseNotes"] = LLTrans::getString("ReleaseNotes", default_string); - - for (LLSD::map_const_iterator ii(info.beginMap()), iend(info.endMap()); - ii != iend; ++ii) - { - if (! ii->second.isArray()) - { - // Scalar value - if (ii->second.isUndefined()) - { - args[ii->first] = LLTrans::getString("none_text", default_string); - } - else - { - // don't forget to render value asString() - args[ii->first] = ii->second.asString(); - } - } - else - { - // array value: build KEY_0, KEY_1 etc. entries - for (LLSD::Integer n(0), size(ii->second.size()); n < size; ++n) - { - args[STRINGIZE(ii->first << '_' << n)] = ii->second[n].asString(); - } - } - } - - // Now build the various pieces - support << LLTrans::getString("AboutHeader", args, default_string); - if (info.has("BUILD_CONFIG")) - { - support << "\n" << LLTrans::getString("BuildConfig", args, default_string); - } - if (info.has("REGION")) - { - support << "\n\n" << LLTrans::getString("AboutPosition", args, default_string); - } - support << "\n\n" << LLTrans::getString("AboutSystem", args, default_string); - support << "\n"; - if (info.has("GRAPHICS_DRIVER_VERSION")) - { - support << "\n" << LLTrans::getString("AboutDriver", args, default_string); - } - support << "\n" << LLTrans::getString("AboutOGL", args, default_string); - support << "\n\n" << LLTrans::getString("AboutSettings", args, default_string); -#if LL_DARWIN - support << "\n" << LLTrans::getString("AboutOSXHiDPI", args, default_string); -#endif - support << "\n\n" << LLTrans::getString("AboutLibs", args, default_string); - if (info.has("COMPILER")) - { - support << "\n" << LLTrans::getString("AboutCompiler", args, default_string); - } - if (info.has("PACKETS_IN")) - { - support << '\n' << LLTrans::getString("AboutTraffic", args, default_string); - } - - // SLT timestamp - LLSD substitution; - substitution["datetime"] = (S32)time(NULL);//(S32)time_corrected(); - support << "\n" << LLTrans::getString("AboutTime", substitution, default_string); - - return support.str(); -} - -void LLAppViewer::cleanupSavedSettings() -{ - gSavedSettings.setBOOL("MouseSun", false); - - gSavedSettings.setBOOL("UseEnergy", true); // force toggle to turn off, since sends message to simulator - - gSavedSettings.setBOOL("DebugWindowProc", gDebugWindowProc); - - gSavedSettings.setBOOL("ShowObjectUpdates", gShowObjectUpdates); - - if (gDebugView) - { - gSavedSettings.setBOOL("ShowDebugConsole", gDebugView->mDebugConsolep->getVisible()); - } - - // save window position if not maximized - // as we don't track it in callbacks - if(NULL != gViewerWindow) - { - bool maximized = gViewerWindow->getWindow()->getMaximized(); - if (!maximized) - { - LLCoordScreen window_pos; - - if (gViewerWindow->getWindow()->getPosition(&window_pos)) - { - gSavedSettings.setS32("WindowX", window_pos.mX); - gSavedSettings.setS32("WindowY", window_pos.mY); - } - } - } - - gSavedSettings.setF32("MapScale", LLWorldMapView::getScaleSetting()); - - // Some things are cached in LLAgent. - if (gAgent.isInitialized()) - { - gSavedSettings.setF32("RenderFarClip", gAgentCamera.mDrawDistance); - } -} - -void LLAppViewer::removeCacheFiles(const std::string& file_mask) -{ - gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ""), file_mask); -} - -void LLAppViewer::writeSystemInfo() -{ - - if (! gDebugInfo.has("Dynamic") ) - gDebugInfo["Dynamic"] = LLSD::emptyMap(); - -#if LL_WINDOWS && !LL_BUGSPLAT - gDebugInfo["SLLog"] = gDirUtilp->getExpandedFilename(LL_PATH_DUMP,"SecondLife.log"); -#else - //Not ideal but sufficient for good reporting. - gDebugInfo["SLLog"] = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.old"); //LLError::logFileName(); -#endif - - gDebugInfo["ClientInfo"]["Name"] = LLVersionInfo::instance().getChannel(); - gDebugInfo["ClientInfo"]["MajorVersion"] = LLVersionInfo::instance().getMajor(); - gDebugInfo["ClientInfo"]["MinorVersion"] = LLVersionInfo::instance().getMinor(); - gDebugInfo["ClientInfo"]["PatchVersion"] = LLVersionInfo::instance().getPatch(); - gDebugInfo["ClientInfo"]["BuildVersion"] = std::to_string(LLVersionInfo::instance().getBuild()); - gDebugInfo["ClientInfo"]["AddressSize"] = LLVersionInfo::instance().getAddressSize(); - - gDebugInfo["CAFilename"] = gDirUtilp->getCAFile(); - - gDebugInfo["CPUInfo"]["CPUString"] = gSysCPU.getCPUString(); - gDebugInfo["CPUInfo"]["CPUFamily"] = gSysCPU.getFamily(); - gDebugInfo["CPUInfo"]["CPUMhz"] = (S32)gSysCPU.getMHz(); - gDebugInfo["CPUInfo"]["CPUAltivec"] = gSysCPU.hasAltivec(); - gDebugInfo["CPUInfo"]["CPUSSE"] = gSysCPU.hasSSE(); - gDebugInfo["CPUInfo"]["CPUSSE2"] = gSysCPU.hasSSE2(); - - gDebugInfo["RAMInfo"]["Physical"] = LLSD::Integer(gSysMemory.getPhysicalMemoryKB().value()); - gDebugInfo["RAMInfo"]["Allocated"] = LLSD::Integer(gMemoryAllocated.valueInUnits()); - gDebugInfo["OSInfo"] = LLOSInfo::instance().getOSStringSimple(); - - // The user is not logged on yet, but record the current grid choice login url - // which may have been the intended grid. - gDebugInfo["GridName"] = LLGridManager::getInstance()->getGridId(); - - // *FIX:Mani - move this down in llappviewerwin32 -#ifdef LL_WINDOWS - DWORD thread_id = GetCurrentThreadId(); - gDebugInfo["MainloopThreadID"] = (S32)thread_id; -#endif - -#ifndef LL_BUGSPLAT - // "CrashNotHandled" is set here, while things are running well, - // in case of a freeze. If there is a freeze, the crash logger will be launched - // and can read this value from the debug_info.log. - gDebugInfo["CrashNotHandled"] = LLSD::Boolean(true); -#else // LL_BUGSPLAT - // "CrashNotHandled" is obsolete; it used (not very successsfully) - // to try to distinguish crashes from freezes - the intent here to to avoid calling it a freeze - gDebugInfo["CrashNotHandled"] = LLSD::Boolean(false); -#endif // ! LL_BUGSPLAT - - // Insert crash host url (url to post crash log to) if configured. This insures - // that the crash report will go to the proper location in the case of a - // prior freeze. - std::string crashHostUrl = gSavedSettings.get("CrashHostUrl"); - if(crashHostUrl != "") - { - gDebugInfo["CrashHostUrl"] = crashHostUrl; - } - - // Dump some debugging info - LL_INFOS("SystemInfo") << "Application: " << LLTrans::getString("APP_NAME") << LL_ENDL; - LL_INFOS("SystemInfo") << "Version: " << LLVersionInfo::instance().getChannelAndVersion() << LL_ENDL; - - // Dump the local time and time zone - time_t now; - time(&now); - char tbuffer[256]; /* Flawfinder: ignore */ - strftime(tbuffer, 256, "%Y-%m-%dT%H:%M:%S %Z", localtime(&now)); - LL_INFOS("SystemInfo") << "Local time: " << tbuffer << LL_ENDL; - - // query some system information - LL_INFOS("SystemInfo") << "CPU info:\n" << gSysCPU << LL_ENDL; - LL_INFOS("SystemInfo") << "Memory info:\n" << gSysMemory << LL_ENDL; - LL_INFOS("SystemInfo") << "OS: " << LLOSInfo::instance().getOSStringSimple() << LL_ENDL; - LL_INFOS("SystemInfo") << "OS info: " << LLOSInfo::instance() << LL_ENDL; - - gDebugInfo["SettingsFilename"] = gSavedSettings.getString("ClientSettingsFile"); - gDebugInfo["ViewerExePath"] = gDirUtilp->getExecutablePathAndName(); - gDebugInfo["CurrentPath"] = gDirUtilp->getCurPath(); - gDebugInfo["FirstLogin"] = LLSD::Boolean(gAgent.isFirstLogin()); - gDebugInfo["FirstRunThisInstall"] = gSavedSettings.getBOOL("FirstRunThisInstall"); - gDebugInfo["StartupState"] = LLStartUp::getStartupStateString(); - - if (gViewerWindow) - { - std::vector resolutions = gViewerWindow->getWindow()->getDisplaysResolutionList(); - for (auto res_iter : resolutions) - { - gDebugInfo["DisplayInfo"].append(res_iter); - } - } - - writeDebugInfo(); // Save out debug_info.log early, in case of crash. -} - -#ifdef LL_WINDOWS -//For whatever reason, in Windows when using OOP server for breakpad, the callback to get the -//name of the dump file is not getting triggered by the breakpad library. Unfortunately they -//also didn't see fit to provide a simple query request across the pipe to get this name either. -//Since we are putting our output in a runtime generated directory and we know the header data in -//the dump format, we can however use the following hack to identify our file. -// TODO make this a member function. -void getFileList() -{ - std::stringstream filenames; - - typedef std::vector vec; - std::string pathname = gDirUtilp->getExpandedFilename(LL_PATH_DUMP,""); - vec file_vec = gDirUtilp->getFilesInDir(pathname); - for(vec::const_iterator iter=file_vec.begin(); iter!=file_vec.end(); ++iter) - { - filenames << *iter << " "; - if ( ( iter->length() > 30 ) && (iter->rfind(".dmp") == (iter->length()-4) ) ) - { - std::string fullname = pathname + *iter; - llifstream fdat( fullname.c_str(), std::ifstream::binary); - if (fdat) - { - char buf[5]; - fdat.read(buf,4); - fdat.close(); - if (!strncmp(buf,"MDMP",4)) - { - gDebugInfo["Dynamic"]["MinidumpPath"] = fullname; - break; - } - } - } - } - filenames << std::endl; - gDebugInfo["Dynamic"]["DumpDirContents"] = filenames.str(); -} -#endif - -// static -void LLAppViewer::recordMarkerVersion(LLAPRFile& marker_file) -{ - std::string marker_version(LLVersionInfo::instance().getChannelAndVersion()); - if ( marker_version.length() > MAX_MARKER_LENGTH ) - { - LL_WARNS_ONCE("MarkerFile") << "Version length ("<< marker_version.length()<< ")" - << " greater than maximum (" << MAX_MARKER_LENGTH << ")" - << ": marker matching may be incorrect" - << LL_ENDL; - } - - // record the viewer version in the marker file - marker_file.write(marker_version.data(), marker_version.length()); -} - -bool LLAppViewer::markerIsSameVersion(const std::string& marker_name) const -{ - bool sameVersion = false; - - std::string my_version(LLVersionInfo::instance().getChannelAndVersion()); - char marker_version[MAX_MARKER_LENGTH]; - S32 marker_version_length; - - LLAPRFile marker_file; - marker_file.open(marker_name, LL_APR_RB); - if (marker_file.getFileHandle()) - { - marker_version_length = marker_file.read(marker_version, sizeof(marker_version)); - std::string marker_string(marker_version, marker_version_length); - if ( 0 == my_version.compare( 0, my_version.length(), marker_version, 0, marker_version_length ) ) - { - sameVersion = true; - } - LL_DEBUGS("MarkerFile") << "Compare markers for '" << marker_name << "': " - << "\n mine '" << my_version << "'" - << "\n marker '" << marker_string << "'" - << "\n " << ( sameVersion ? "same" : "different" ) << " version" - << LL_ENDL; - marker_file.close(); - } - return sameVersion; -} - -void LLAppViewer::processMarkerFiles() -{ - //We've got 4 things to test for here - // - Other Process Running (SecondLife.exec_marker present, locked) - // - Freeze (SecondLife.exec_marker present, not locked) - // - LLError Crash (SecondLife.llerror_marker present) - // - Other Crash (SecondLife.error_marker present) - // These checks should also remove these files for the last 2 cases if they currently exist - - std::ostringstream marker_log_stream; - bool marker_is_same_version = true; - // first, look for the marker created at startup and deleted on a clean exit - mMarkerFileName = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,MARKER_FILE_NAME); - if (LLAPRFile::isExist(mMarkerFileName, NULL, LL_APR_RB)) - { - // File exists... - // first, read it to see if it was created by the same version (we need this later) - marker_is_same_version = markerIsSameVersion(mMarkerFileName); - - // now test to see if this file is locked by a running process (try to open for write) - marker_log_stream << "Checking exec marker file for lock..."; - mMarkerFile.open(mMarkerFileName, LL_APR_WB); - apr_file_t* fMarker = mMarkerFile.getFileHandle() ; - if (!fMarker) - { - marker_log_stream << "Exec marker file open failed - assume it is locked."; - mSecondInstance = true; // lock means that instance is running. - } - else - { - // We were able to open it, now try to lock it ourselves... - if (apr_file_lock(fMarker, APR_FLOCK_NONBLOCK | APR_FLOCK_EXCLUSIVE) != APR_SUCCESS) - { - marker_log_stream << "Locking exec marker failed."; - mSecondInstance = true; // lost a race? be conservative - } - else - { - // No other instances; we've locked this file now, so record our version; delete on quit. - recordMarkerVersion(mMarkerFile); - marker_log_stream << "Exec marker file existed but was not locked; rewritten."; - } - } - initLoggingAndGetLastDuration(); - - std::string marker_log_msg(marker_log_stream.str()); - LL_INFOS("MarkerFile") << marker_log_msg << LL_ENDL; - - if (mSecondInstance) - { - LL_INFOS("MarkerFile") << "Exec marker '"<< mMarkerFileName << "' owned by another instance" << LL_ENDL; - } - else if (marker_is_same_version) - { - // the file existed, is ours, and matched our version, so we can report on what it says - LL_INFOS("MarkerFile") << "Exec marker '"<< mMarkerFileName << "' found; last exec crashed" << LL_ENDL; - gLastExecEvent = LAST_EXEC_OTHER_CRASH; - } - else - { - LL_INFOS("MarkerFile") << "Exec marker '"<< mMarkerFileName << "' found, but versions did not match" << LL_ENDL; - } - } - else // marker did not exist... last exec (if any) did not freeze - { - initLoggingAndGetLastDuration(); - // Create the marker file for this execution & lock it; it will be deleted on a clean exit - apr_status_t s; - s = mMarkerFile.open(mMarkerFileName, LL_APR_WB, true); - - if (s == APR_SUCCESS && mMarkerFile.getFileHandle()) - { - LL_DEBUGS("MarkerFile") << "Exec marker file '"<< mMarkerFileName << "' created." << LL_ENDL; - if (APR_SUCCESS == apr_file_lock(mMarkerFile.getFileHandle(), APR_FLOCK_NONBLOCK | APR_FLOCK_EXCLUSIVE)) - { - recordMarkerVersion(mMarkerFile); - LL_DEBUGS("MarkerFile") << "Exec marker file locked." << LL_ENDL; - } - else - { - LL_WARNS("MarkerFile") << "Exec marker file cannot be locked." << LL_ENDL; - } - } - else - { - LL_WARNS("MarkerFile") << "Failed to create exec marker file '"<< mMarkerFileName << "'." << LL_ENDL; - } - } - - // now check for cases in which the exec marker may have been cleaned up by crash handlers - - // check for any last exec event report based on whether or not it happened during logout - // (the logout marker is created when logout begins) - std::string logout_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, LOGOUT_MARKER_FILE_NAME); - if(LLAPRFile::isExist(logout_marker_file, NULL, LL_APR_RB)) - { - if (markerIsSameVersion(logout_marker_file)) - { - gLastExecEvent = LAST_EXEC_LOGOUT_FROZE; - LL_INFOS("MarkerFile") << "Logout crash marker '"<< logout_marker_file << "', changing LastExecEvent to LOGOUT_FROZE" << LL_ENDL; - } - else - { - LL_INFOS("MarkerFile") << "Logout crash marker '"<< logout_marker_file << "' found, but versions did not match" << LL_ENDL; - } - LLAPRFile::remove(logout_marker_file); - } - // further refine based on whether or not a marker created during an llerr crash is found - std::string llerror_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, LLERROR_MARKER_FILE_NAME); - if(LLAPRFile::isExist(llerror_marker_file, NULL, LL_APR_RB)) - { - if (markerIsSameVersion(llerror_marker_file)) - { - if ( gLastExecEvent == LAST_EXEC_LOGOUT_FROZE ) - { - gLastExecEvent = LAST_EXEC_LOGOUT_CRASH; - LL_INFOS("MarkerFile") << "LLError marker '"<< llerror_marker_file << "' crashed, setting LastExecEvent to LOGOUT_CRASH" << LL_ENDL; - } - else - { - gLastExecEvent = LAST_EXEC_LLERROR_CRASH; - LL_INFOS("MarkerFile") << "LLError marker '"<< llerror_marker_file << "' crashed, setting LastExecEvent to LLERROR_CRASH" << LL_ENDL; - } - } - else - { - LL_INFOS("MarkerFile") << "LLError marker '"<< llerror_marker_file << "' found, but versions did not match" << LL_ENDL; - } - LLAPRFile::remove(llerror_marker_file); - } - // and last refine based on whether or not a marker created during a non-llerr crash is found - std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ERROR_MARKER_FILE_NAME); - if(LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB)) - { - if (markerIsSameVersion(error_marker_file)) - { - if (gLastExecEvent == LAST_EXEC_LOGOUT_FROZE) - { - gLastExecEvent = LAST_EXEC_LOGOUT_CRASH; - LL_INFOS("MarkerFile") << "Error marker '"<< error_marker_file << "' crashed, setting LastExecEvent to LOGOUT_CRASH" << LL_ENDL; - } - else - { - gLastExecEvent = LAST_EXEC_OTHER_CRASH; - LL_INFOS("MarkerFile") << "Error marker '"<< error_marker_file << "' crashed, setting LastExecEvent to " << gLastExecEvent << LL_ENDL; - } - } - else - { - LL_INFOS("MarkerFile") << "Error marker '"<< error_marker_file << "' marker found, but versions did not match" << LL_ENDL; - } - LLAPRFile::remove(error_marker_file); - } -} - -void LLAppViewer::removeMarkerFiles() -{ - if (!mSecondInstance) - { - if (mMarkerFile.getFileHandle()) - { - mMarkerFile.close() ; - LLAPRFile::remove( mMarkerFileName ); - LL_DEBUGS("MarkerFile") << "removed exec marker '"<dumpDirExists()) // Check if dump dir was created this run - { - std::string dump_dir = gDirUtilp->getExpandedFilename(LL_PATH_DUMP, ""); - gDirUtilp->deleteDirAndContents(dump_dir); - } - - if (mSecondInstance && !isError()) - { - std::string log_filename = LLError::logFileName(); - LLError::logToFile(""); - LLFile::remove(log_filename); - } -} - -void LLAppViewer::forceQuit() -{ - LLApp::setQuitting(); -} - -//TODO: remove -void LLAppViewer::fastQuit(S32 error_code) -{ - // finish pending transfers - flushLFSIO(); - // let sim know we're logging out - sendLogoutRequest(); - // flush network buffers by shutting down messaging system - end_messaging_system(); - // figure out the error code - S32 final_error_code = error_code ? error_code : (S32)isError(); - // this isn't a crash - removeMarkerFiles(); - // get outta here - _exit(final_error_code); -} - -void LLAppViewer::requestQuit() -{ - LL_INFOS() << "requestQuit" << LL_ENDL; - - LLViewerRegion* region = gAgent.getRegion(); - - if( (LLStartUp::getStartupState() < STATE_STARTED) || !region ) - { - // If we have a region, make some attempt to send a logout request first. - // This prevents the halfway-logged-in avatar from hanging around inworld for a couple minutes. - if(region) - { - sendLogoutRequest(); - } - - // Quit immediately - forceQuit(); - return; - } - - // Try to send metrics back to the grid - metricsSend(!gDisconnected); - - // Try to send last batch of avatar rez metrics. - if (!gDisconnected && isAgentAvatarValid()) - { - gAgentAvatarp->updateAvatarRezMetrics(true); // force a last packet to be sent. - } - - LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral*)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_POINT, true); - effectp->setPositionGlobal(gAgent.getPositionGlobal()); - effectp->setColor(LLColor4U(gAgent.getEffectColor())); - LLHUDManager::getInstance()->sendEffects(); - effectp->markDead() ;//remove it. - - // Attempt to close all floaters that might be - // editing things. - if (gFloaterView) - { - // application is quitting - gFloaterView->closeAllChildren(true); - } - - // Send preferences once, when exiting - bool include_preferences = true; - send_viewer_stats(include_preferences); - - gLogoutTimer.reset(); - mQuitRequested = true; -} - -static bool finish_quit(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - - if (option == 0) - { - LLAppViewer::instance()->requestQuit(); - } - return false; -} -static LLNotificationFunctorRegistration finish_quit_reg("ConfirmQuit", finish_quit); - -void LLAppViewer::userQuit() -{ - LL_INFOS() << "User requested quit" << LL_ENDL; - if (gDisconnected - || !gViewerWindow - || !gViewerWindow->getProgressView() - || gViewerWindow->getProgressView()->getVisible()) - { - requestQuit(); - } - else - { - LLNotificationsUtil::add("ConfirmQuit"); - } -} - -static bool finish_early_exit(const LLSD& notification, const LLSD& response) -{ - LLAppViewer::instance()->forceQuit(); - return false; -} - -void LLAppViewer::earlyExit(const std::string& name, const LLSD& substitutions) -{ - LL_WARNS() << "app_early_exit: " << name << LL_ENDL; - gDoDisconnect = true; - LLNotificationsUtil::add(name, substitutions, LLSD(), finish_early_exit); -} - -// case where we need the viewer to exit without any need for notifications -void LLAppViewer::earlyExitNoNotify() -{ - LL_WARNS() << "app_early_exit with no notification: " << LL_ENDL; - gDoDisconnect = true; - finish_early_exit( LLSD(), LLSD() ); -} - -void LLAppViewer::abortQuit() -{ - LL_INFOS() << "abortQuit()" << LL_ENDL; - mQuitRequested = false; -} - -void LLAppViewer::migrateCacheDirectory() -{ -#if LL_WINDOWS || LL_DARWIN - // NOTE: (Nyx) as of 1.21, cache for mac is moving to /library/caches/SecondLife from - // /library/application support/SecondLife/cache This should clear/delete the old dir. - - // As of 1.23 the Windows cache moved from - // C:\Documents and Settings\James\Application Support\SecondLife\cache - // to - // C:\Documents and Settings\James\Local Settings\Application Support\SecondLife - // - // The Windows Vista equivalent is from - // C:\Users\James\AppData\Roaming\SecondLife\cache - // to - // C:\Users\James\AppData\Local\SecondLife - // - // Note the absence of \cache on the second path. James. - - // Only do this once per fresh install of this version. - if (gSavedSettings.getBOOL("MigrateCacheDirectory")) - { - gSavedSettings.setBOOL("MigrateCacheDirectory", false); - - std::string old_cache_dir = gDirUtilp->add(gDirUtilp->getOSUserAppDir(), "cache"); - std::string new_cache_dir = gDirUtilp->getCacheDir(true); - - if (gDirUtilp->fileExists(old_cache_dir)) - { - LL_INFOS() << "Migrating cache from " << old_cache_dir << " to " << new_cache_dir << LL_ENDL; - - // Migrate inventory cache to avoid pain to inventory database after mass update - S32 file_count = 0; - std::string file_name; - std::string mask = "*.*"; - - LLDirIterator iter(old_cache_dir, mask); - while (iter.next(file_name)) - { - if (file_name == "." || file_name == "..") continue; - std::string source_path = gDirUtilp->add(old_cache_dir, file_name); - std::string dest_path = gDirUtilp->add(new_cache_dir, file_name); - if (!LLFile::rename(source_path, dest_path)) - { - file_count++; - } - } - LL_INFOS() << "Moved " << file_count << " files" << LL_ENDL; - - // Nuke the old cache - gDirUtilp->setCacheDir(old_cache_dir); - purgeCache(); - gDirUtilp->setCacheDir(new_cache_dir); - -#if LL_DARWIN - // Clean up Mac files not deleted by removing *.* - std::string ds_store = old_cache_dir + "/.DS_Store"; - if (gDirUtilp->fileExists(ds_store)) - { - LLFile::remove(ds_store); - } -#endif - if (LLFile::rmdir(old_cache_dir) != 0) - { - LL_WARNS() << "could not delete old cache directory " << old_cache_dir << LL_ENDL; - } - } - } -#endif // LL_WINDOWS || LL_DARWIN -} - -//static -U32 LLAppViewer::getTextureCacheVersion() -{ - // Viewer texture cache version, change if the texture cache format changes. - // 2021-03-10 Bumping up by one to help obviate texture cache issues with - // Simple Cache Viewer - see SL-14985 for more information - //const U32 TEXTURE_CACHE_VERSION = 8; - const U32 TEXTURE_CACHE_VERSION = 9; - - return TEXTURE_CACHE_VERSION ; -} - -//static -U32 LLAppViewer::getDiskCacheVersion() -{ - // Viewer disk cache version intorduced in Simple Cache Viewer, change if the cache format changes. - const U32 DISK_CACHE_VERSION = 1; - - return DISK_CACHE_VERSION ; -} - -//static -U32 LLAppViewer::getObjectCacheVersion() -{ - // Viewer object cache version, change if object update - // format changes. JC - const U32 INDRA_OBJECT_CACHE_VERSION = 17; - - return INDRA_OBJECT_CACHE_VERSION; -} - -bool LLAppViewer::initCache() -{ - mPurgeCache = false; - bool read_only = mSecondInstance; - LLAppViewer::getTextureCache()->setReadOnly(read_only) ; - LLVOCache::initParamSingleton(read_only); - - // initialize the new disk cache using saved settings - const std::string cache_dir_name = gSavedSettings.getString("DiskCacheDirName"); - - const U32 MB = 1024 * 1024; - const uintmax_t MIN_CACHE_SIZE = 256 * MB; - const uintmax_t MAX_CACHE_SIZE = 9984ll * MB; - const uintmax_t setting_cache_total_size = uintmax_t(gSavedSettings.getU32("CacheSize")) * MB; - const uintmax_t cache_total_size = llclamp(setting_cache_total_size, MIN_CACHE_SIZE, MAX_CACHE_SIZE); - const F64 disk_cache_percent = gSavedSettings.getF32("DiskCachePercentOfTotal"); - const F64 texture_cache_percent = 100.0 - disk_cache_percent; - - // note that the maximum size of this cache is defined as a percentage of the - // total cache size - the 'CacheSize' pref - for all caches. - const uintmax_t disk_cache_size = uintmax_t(cache_total_size * disk_cache_percent / 100); - const bool enable_cache_debug_info = gSavedSettings.getBOOL("EnableDiskCacheDebugInfo"); - - bool texture_cache_mismatch = false; - bool remove_vfs_files = false; - if (gSavedSettings.getS32("LocalCacheVersion") != LLAppViewer::getTextureCacheVersion()) - { - texture_cache_mismatch = true; - if (!read_only) - { - gSavedSettings.setS32("LocalCacheVersion", LLAppViewer::getTextureCacheVersion()); - - //texture cache version was bumped up in Simple Cache Viewer, and at this point old vfs files are not needed - remove_vfs_files = true; - } - } - - if (!read_only) - { - // Purge cache if user requested it - if (gSavedSettings.getBOOL("PurgeCacheOnStartup") || - gSavedSettings.getBOOL("PurgeCacheOnNextStartup")) - { - LL_INFOS("AppCache") << "Startup cache purge requested: " << (gSavedSettings.getBOOL("PurgeCacheOnStartup") ? "ALWAYS" : "ONCE") << LL_ENDL; - gSavedSettings.setBOOL("PurgeCacheOnNextStartup", false); - mPurgeCache = true; - // STORM-1141 force purgeAllTextures to get called to prevent a crash here. -brad - texture_cache_mismatch = true; - } - - // We have moved the location of the cache directory over time. - migrateCacheDirectory(); - - // Setup and verify the cache location - std::string cache_location = gSavedSettings.getString("CacheLocation"); - std::string new_cache_location = gSavedSettings.getString("NewCacheLocation"); - if (new_cache_location != cache_location) - { - LL_INFOS("AppCache") << "Cache location changed, cache needs purging" << LL_ENDL; - gDirUtilp->setCacheDir(gSavedSettings.getString("CacheLocation")); - purgeCache(); // purge old cache - gDirUtilp->deleteDirAndContents(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, cache_dir_name)); - gSavedSettings.setString("CacheLocation", new_cache_location); - gSavedSettings.setString("CacheLocationTopFolder", gDirUtilp->getBaseFileName(new_cache_location)); - } - } - - if (!gDirUtilp->setCacheDir(gSavedSettings.getString("CacheLocation"))) - { - LL_WARNS("AppCache") << "Unable to set cache location" << LL_ENDL; - gSavedSettings.setString("CacheLocation", ""); - gSavedSettings.setString("CacheLocationTopFolder", ""); - } - - const std::string cache_dir = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, cache_dir_name); - LLDiskCache::initParamSingleton(cache_dir, disk_cache_size, enable_cache_debug_info); - - if (!read_only) - { - if (gSavedSettings.getS32("DiskCacheVersion") != LLAppViewer::getDiskCacheVersion()) - { - LLDiskCache::getInstance()->clearCache(); - remove_vfs_files = true; - gSavedSettings.setS32("DiskCacheVersion", LLAppViewer::getDiskCacheVersion()); - } - - if (remove_vfs_files) - { - LLDiskCache::getInstance()->removeOldVFSFiles(); - } - - if (mPurgeCache) - { - LLSplashScreen::update(LLTrans::getString("StartupClearingCache")); - purgeCache(); - - // clear the new C++ file system based cache - LLDiskCache::getInstance()->clearCache(); - } - else - { - // purge excessive files from the new file system based cache - LLDiskCache::getInstance()->purge(); - } - } - LLAppViewer::getPurgeDiskCacheThread()->start(); - - LLSplashScreen::update(LLTrans::getString("StartupInitializingTextureCache")); - - // Init the texture cache - // Allocate the remaining percent which is not allocated to the disk cache - const S64 texture_cache_size = S64(cache_total_size * texture_cache_percent / 100); - - LLAppViewer::getTextureCache()->initCache(LL_PATH_CACHE, texture_cache_size, texture_cache_mismatch); - - const U32 CACHE_NUMBER_OF_REGIONS_FOR_OBJECTS = 128; - LLVOCache::getInstance()->initCache(LL_PATH_CACHE, CACHE_NUMBER_OF_REGIONS_FOR_OBJECTS, getObjectCacheVersion()); - - return true; -} - -void LLAppViewer::addOnIdleCallback(const boost::function& cb) -{ - gMainloopWork.post(cb); -} - -void LLAppViewer::loadKeyBindings() -{ - std::string key_bindings_file = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "key_bindings.xml"); - if (!gDirUtilp->fileExists(key_bindings_file) || !gViewerInput.loadBindingsXML(key_bindings_file)) - { - // Failed to load custom bindings, try default ones - key_bindings_file = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "key_bindings.xml"); - if (!gViewerInput.loadBindingsXML(key_bindings_file)) - { - LLError::LLUserWarningMsg::showMissingFiles(); - LL_ERRS("InitInfo") << "Unable to open default key bindings from " << key_bindings_file << LL_ENDL; - } - } - LLUrlRegistry::instance().setKeybindingHandler(&gViewerInput); -} - -void LLAppViewer::purgeCache() -{ - LL_INFOS("AppCache") << "Purging Cache and Texture Cache..." << LL_ENDL; - LLAppViewer::getTextureCache()->purgeCache(LL_PATH_CACHE); - LLVOCache::getInstance()->removeCache(LL_PATH_CACHE); - LLViewerShaderMgr::instance()->clearShaderCache(); - std::string browser_cache = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "cef_cache"); - if (LLFile::isdir(browser_cache)) - { - // cef does not support clear_cache and clear_cookies, so clear what we can manually. - gDirUtilp->deleteDirAndContents(browser_cache); - } - gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ""), "*"); -} - -//purge cache immediately, do not wait until the next login. -void LLAppViewer::purgeCacheImmediate() -{ - LL_INFOS("AppCache") << "Purging Object Cache and Texture Cache immediately..." << LL_ENDL; - LLAppViewer::getTextureCache()->purgeCache(LL_PATH_CACHE, false); - LLVOCache::getInstance()->removeCache(LL_PATH_CACHE, true); -} - -std::string LLAppViewer::getSecondLifeTitle() const -{ - return LLTrans::getString("APP_NAME"); -} - -std::string LLAppViewer::getWindowTitle() const -{ - return gWindowTitle; -} - -// Callback from a dialog indicating user was logged out. -bool finish_disconnect(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - - if (1 == option) - { - LLAppViewer::instance()->forceQuit(); - } - return false; -} - -// Callback from an early disconnect dialog, force an exit -bool finish_forced_disconnect(const LLSD& notification, const LLSD& response) -{ - LLAppViewer::instance()->forceQuit(); - return false; -} - - -void LLAppViewer::forceDisconnect(const std::string& mesg) -{ - if (gDoDisconnect) - { - // Already popped up one of these dialogs, don't - // do this again. - return; - } - - // *TODO: Translate the message if possible - std::string big_reason = LLAgent::sTeleportErrorMessages[mesg]; - if ( big_reason.size() == 0 ) - { - big_reason = mesg; - } - - LLSD args; - gDoDisconnect = true; - - if (LLStartUp::getStartupState() < STATE_STARTED) - { - // Tell users what happened - args["ERROR_MESSAGE"] = big_reason; - LLNotificationsUtil::add("ErrorMessage", args, LLSD(), &finish_forced_disconnect); - } - else - { - args["MESSAGE"] = big_reason; - LLNotificationsUtil::add("YouHaveBeenLoggedOut", args, LLSD(), &finish_disconnect ); - } -} - -void LLAppViewer::badNetworkHandler() -{ - // Dump the packet - gMessageSystem->dumpPacketToLog(); - - // Flush all of our caches on exit in the case of disconnect due to - // invalid packets. - - mPurgeCacheOnExit = true; - - std::ostringstream message; - message << - "The viewer has detected mangled network data indicative\n" - "of a bad upstream network connection or an incomplete\n" - "local installation of " << LLAppViewer::instance()->getSecondLifeTitle() << ". \n" - " \n" - "Try uninstalling and reinstalling to see if this resolves \n" - "the issue. \n" - " \n" - "If the problem continues, see the Tech Support FAQ at: \n" - "www.secondlife.com/support"; - forceDisconnect(message.str()); - - LLApp::instance()->writeMiniDump(); -} - -// This routine may get called more than once during the shutdown process. -// This can happen because we need to get the screenshot before the window -// is destroyed. -void LLAppViewer::saveFinalSnapshot() -{ - if (!mSavedFinalSnapshot) - { - gSavedSettings.setVector3d("FocusPosOnLogout", gAgentCamera.calcFocusPositionTargetGlobal()); - gSavedSettings.setVector3d("CameraPosOnLogout", gAgentCamera.calcCameraPositionTargetGlobal()); - gViewerWindow->setCursor(UI_CURSOR_WAIT); - gAgentCamera.changeCameraToThirdPerson( false ); // don't animate, need immediate switch - gSavedSettings.setBOOL("ShowParcelOwners", false); - idle(); - - std::string snap_filename = gDirUtilp->getLindenUserDir(); - snap_filename += gDirUtilp->getDirDelimiter(); - snap_filename += LLStartUp::getScreenLastFilename(); - // use full pixel dimensions of viewer window (not post-scale dimensions) - gViewerWindow->saveSnapshot(snap_filename, - gViewerWindow->getWindowWidthRaw(), - gViewerWindow->getWindowHeightRaw(), - false, - gSavedSettings.getBOOL("RenderHUDInSnapshot"), - true, - LLSnapshotModel::SNAPSHOT_TYPE_COLOR, - LLSnapshotModel::SNAPSHOT_FORMAT_PNG); - mSavedFinalSnapshot = true; - - if (gAgent.isInHomeRegion()) - { - LLVector3d home; - if (gAgent.getHomePosGlobal(&home) && dist_vec(home, gAgent.getPositionGlobal()) < 10) - { - // We are at home position or close to it, see if we need to create home screenshot - // Notes: - // 1. It might be beneficial to also replace home if file is too old - // 2. This is far from best way/place to update screenshot since location might be not fully loaded, - // but we don't have many options - std::string snap_home = gDirUtilp->getLindenUserDir(); - snap_home += gDirUtilp->getDirDelimiter(); - snap_home += LLStartUp::getScreenHomeFilename(); - if (!gDirUtilp->fileExists(snap_home)) - { - // We are at home position yet no home image exist, fix it - LLFile::copy(snap_filename, snap_home); - } - } - } - } -} - -void LLAppViewer::loadNameCache() -{ - // display names cache - std::string filename = - gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "avatar_name_cache.xml"); - LL_INFOS("AvNameCache") << filename << LL_ENDL; - llifstream name_cache_stream(filename.c_str()); - if(name_cache_stream.is_open()) - { - if ( ! LLAvatarNameCache::getInstance()->importFile(name_cache_stream)) - { - LL_WARNS("AppInit") << "removing invalid '" << filename << "'" << LL_ENDL; - name_cache_stream.close(); - LLFile::remove(filename); - } - } - - if (!gCacheName) return; - - std::string name_cache; - name_cache = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "name.cache"); - llifstream cache_file(name_cache.c_str()); - if(cache_file.is_open()) - { - if(gCacheName->importFile(cache_file)) return; - } -} - -void LLAppViewer::saveNameCache() -{ - // display names cache - std::string filename = - gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "avatar_name_cache.xml"); - llofstream name_cache_stream(filename.c_str()); - if(name_cache_stream.is_open()) - { - LLAvatarNameCache::getInstance()->exportFile(name_cache_stream); - } - - // real names cache - if (gCacheName) - { - std::string name_cache; - name_cache = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "name.cache"); - llofstream cache_file(name_cache.c_str()); - if(cache_file.is_open()) - { - gCacheName->exportFile(cache_file); - } - } -} - - -/*! @brief This class is an LLFrameTimer that can be created with - an elapsed time that starts counting up from the given value - rather than 0.0. - - Otherwise it behaves the same way as LLFrameTimer. -*/ -class LLFrameStatsTimer : public LLFrameTimer -{ -public: - LLFrameStatsTimer(F64 elapsed_already = 0.0) - : LLFrameTimer() - { - mStartTime -= elapsed_already; - } -}; - -static LLTrace::BlockTimerStatHandle FTM_AUDIO_UPDATE("Update Audio"); -static LLTrace::BlockTimerStatHandle FTM_CLEANUP("Cleanup"); -static LLTrace::BlockTimerStatHandle FTM_CLEANUP_DRAWABLES("Drawables"); -static LLTrace::BlockTimerStatHandle FTM_IDLE_CB("Idle Callbacks"); -static LLTrace::BlockTimerStatHandle FTM_LOD_UPDATE("Update LOD"); -static LLTrace::BlockTimerStatHandle FTM_OBJECTLIST_UPDATE("Update Objectlist"); -static LLTrace::BlockTimerStatHandle FTM_REGION_UPDATE("Update Region"); -static LLTrace::BlockTimerStatHandle FTM_WORLD_UPDATE("Update World"); -static LLTrace::BlockTimerStatHandle FTM_NETWORK("Network"); -static LLTrace::BlockTimerStatHandle FTM_AGENT_NETWORK("Agent Network"); -static LLTrace::BlockTimerStatHandle FTM_VLMANAGER("VL Manager"); -static LLTrace::BlockTimerStatHandle FTM_AGENT_POSITION("Agent Position"); -static LLTrace::BlockTimerStatHandle FTM_HUD_EFFECTS("HUD Effects"); - -/////////////////////////////////////////////////////// -// idle() -// -// Called every time the window is not doing anything. -// Receive packets, update statistics, and schedule a redisplay. -/////////////////////////////////////////////////////// -void LLAppViewer::idle() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_APP; - pingMainloopTimeout("Main:Idle"); - - // Update frame timers - static LLTimer idle_timer; - - LLFrameTimer::updateFrameTime(); - LLFrameTimer::updateFrameCount(); - LLEventTimer::updateClass(); - LLPerfStats::updateClass(); - - // LLApp::stepFrame() performs the above three calls plus mRunner.run(). - // Not sure why we don't call stepFrame() here, except that LLRunner seems - // completely redundant with LLEventTimer. - LLNotificationsUI::LLToast::updateClass(); - LLSmoothInterpolation::updateInterpolants(); - LLMortician::updateClass(); - LLFilePickerThread::clearDead(); //calls LLFilePickerThread::notify() - LLDirPickerThread::clearDead(); - F32 dt_raw = idle_timer.getElapsedTimeAndResetF32(); - - LLGLTFMaterialList::flushUpdates(); - - // Service the WorkQueue we use for replies from worker threads. - // Use function statics for the timeslice setting so we only have to fetch - // and convert MainWorkTime once. - static F32 MainWorkTimeRaw = gSavedSettings.getF32("MainWorkTime"); - static F32Milliseconds MainWorkTimeMs(MainWorkTimeRaw); - // MainWorkTime is specified in fractional milliseconds, but std::chrono - // uses integer representations. What if we want less than a microsecond? - // Use nanoseconds. We're very sure we will never need to specify a - // MainWorkTime that would be larger than we could express in - // std::chrono::nanoseconds. - static std::chrono::nanoseconds MainWorkTimeNanoSec{ - std::chrono::nanoseconds::rep(MainWorkTimeMs.value() * 1000000)}; - gMainloopWork.runFor(MainWorkTimeNanoSec); - - // Cap out-of-control frame times - // Too low because in menus, swapping, debugger, etc. - // Too high because idle called with no objects in view, etc. - const F32 MIN_FRAME_RATE = 1.f; - const F32 MAX_FRAME_RATE = 200.f; - - F32 frame_rate_clamped = 1.f / dt_raw; - frame_rate_clamped = llclamp(frame_rate_clamped, MIN_FRAME_RATE, MAX_FRAME_RATE); - gFrameDTClamped = 1.f / frame_rate_clamped; - - // Global frame timer - // Smoothly weight toward current frame - gFPSClamped = (frame_rate_clamped + (4.f * gFPSClamped)) / 5.f; - - F32 qas = gSavedSettings.getF32("QuitAfterSeconds"); - if (qas > 0.f) - { - if (gRenderStartTime.getElapsedTimeF32() > qas) - { - LL_INFOS() << "Quitting after " << qas << " seconds. See setting \"QuitAfterSeconds\"." << LL_ENDL; - LLAppViewer::instance()->forceQuit(); - } - } - - // Must wait until both have avatar object and mute list, so poll - // here. - LLIMProcessing::requestOfflineMessages(); - - /////////////////////////////////// - // - // Special case idle if still starting up - // - if (LLStartUp::getStartupState() < STATE_STARTED) - { - // Skip rest if idle startup returns false (essentially, no world yet) - gGLActive = true; - if (!idle_startup()) - { - gGLActive = false; - return; - } - gGLActive = false; - } - - - F32 yaw = 0.f; // radians - - if (!gDisconnected) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_NETWORK("network"); //LL_RECORD_BLOCK_TIME(FTM_NETWORK); - // Update spaceserver timeinfo - LLWorld::getInstance()->setSpaceTimeUSec(LLWorld::getInstance()->getSpaceTimeUSec() + LLUnits::Seconds::fromValue(dt_raw)); - - - ////////////////////////////////////// - // - // Update simulator agent state - // - - if (gSavedSettings.getBOOL("RotateRight")) - { - gAgent.moveYaw(-1.f); - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_APP("Autopilot"); - // Handle automatic walking towards points - gAgentPilot.updateTarget(); - gAgent.autoPilot(&yaw); - } - - static LLFrameTimer agent_update_timer; - - // When appropriate, update agent location to the simulator. - F32 agent_update_time = agent_update_timer.getElapsedTimeF32(); - F32 agent_force_update_time = mLastAgentForceUpdate + agent_update_time; - bool timed_out = agent_update_time > (1.0f / (F32)AGENT_UPDATES_PER_SECOND); - bool force_send = - // if there is something to send - (gAgent.controlFlagsDirty() && timed_out) - // if something changed - || (mLastAgentControlFlags != gAgent.getControlFlags()) - // keep alive - || (agent_force_update_time > (1.0f / (F32) AGENT_FORCE_UPDATES_PER_SECOND)); - // timing out doesn't warranty that an update will be sent, - // just that it will be checked. - if (force_send || timed_out) - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; - // Send avatar and camera info - mLastAgentControlFlags = gAgent.getControlFlags(); - mLastAgentForceUpdate = force_send ? 0 : agent_force_update_time; - send_agent_update(force_send); - agent_update_timer.reset(); - } - } - - ////////////////////////////////////// - // - // Manage statistics - // - // - { - // Initialize the viewer_stats_timer with an already elapsed time - // of SEND_STATS_PERIOD so that the initial stats report will - // be sent immediately. - static LLFrameStatsTimer viewer_stats_timer(SEND_STATS_PERIOD); - - // Update session stats every large chunk of time - // *FIX: (?) SAMANTHA - if (viewer_stats_timer.getElapsedTimeF32() >= SEND_STATS_PERIOD && !gDisconnected) - { - LL_INFOS() << "Transmitting sessions stats" << LL_ENDL; - bool include_preferences = false; - send_viewer_stats(include_preferences); - viewer_stats_timer.reset(); - } - - // Print the object debugging stats - static LLFrameTimer object_debug_timer; - if (object_debug_timer.getElapsedTimeF32() > 5.f) - { - object_debug_timer.reset(); - if (gObjectList.mNumDeadObjectUpdates) - { - LL_INFOS() << "Dead object updates: " << gObjectList.mNumDeadObjectUpdates << LL_ENDL; - gObjectList.mNumDeadObjectUpdates = 0; - } - if (gObjectList.mNumUnknownUpdates) - { - LL_INFOS() << "Unknown object updates: " << gObjectList.mNumUnknownUpdates << LL_ENDL; - gObjectList.mNumUnknownUpdates = 0; - } - - } - } - - if (!gDisconnected) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Network"); - - //////////////////////////////////////////////// - // - // Network processing - // - // NOTE: Starting at this point, we may still have pointers to "dead" objects - // floating throughout the various object lists. - // - idleNameCache(); - idleNetwork(); - - - // Check for away from keyboard, kick idle agents. - idle_afk_check(); - - // Update statistics for this frame - update_statistics(); - } - - //////////////////////////////////////// - // - // Handle the regular UI idle callbacks as well as - // hover callbacks - // - -#ifdef LL_DARWIN - if (!mQuitRequested) //MAINT-4243 -#endif - { -// LL_RECORD_BLOCK_TIME(FTM_IDLE_CB); - - // Do event notifications if necessary. Yes, we may want to move this elsewhere. - gEventNotifier.update(); - - gIdleCallbacks.callFunctions(); - gInventory.idleNotifyObservers(); - LLAvatarTracker::instance().idleNotifyObservers(); - } - - // Metrics logging (LLViewerAssetStats, etc.) - { - static LLTimer report_interval; - - // *TODO: Add configuration controls for this - F32 seconds = report_interval.getElapsedTimeF32(); - if (seconds >= app_metrics_interval) - { - metricsSend(! gDisconnected); - report_interval.reset(); - } - } - - - // Update layonts, handle mouse events, tooltips, e t c - // updateUI() needs to be called even in case viewer disconected - // since related notification still needs handling and allows - // opening chat. - gViewerWindow->updateUI(); - - if (gDisconnected) - { - return; - } - - if (gTeleportDisplay) - { - return; - } - - /////////////////////////////////////// - // Agent and camera movement - // - LLCoordGL current_mouse = gViewerWindow->getCurrentMouse(); - - { - // After agent and camera moved, figure out if we need to - // deselect objects. - LLSelectMgr::getInstance()->deselectAllIfTooFar(); - - } - - { - // Handle pending gesture processing - LL_RECORD_BLOCK_TIME(FTM_AGENT_POSITION); - LLGestureMgr::instance().update(); - - gAgent.updateAgentPosition(gFrameDTClamped, yaw, current_mouse.mX, current_mouse.mY); - } - - { - LL_RECORD_BLOCK_TIME(FTM_OBJECTLIST_UPDATE); - - if (!(logoutRequestSent() && hasSavedFinalSnapshot())) - { - gObjectList.update(gAgent); - } - } - - ////////////////////////////////////// - // - // Deletes objects... - // Has to be done after doing idleUpdates (which can kill objects) - // - - { - LL_RECORD_BLOCK_TIME(FTM_CLEANUP); - { - gObjectList.cleanDeadObjects(); - } - { - LL_RECORD_BLOCK_TIME(FTM_CLEANUP_DRAWABLES); - LLDrawable::cleanupDeadDrawables(); - } - } - - // - // After this point, in theory we should never see a dead object - // in the various object/drawable lists. - // - - ////////////////////////////////////// - // - // Update/send HUD effects - // - // At this point, HUD effects may clean up some references to - // dead objects. - // - - { - LL_RECORD_BLOCK_TIME(FTM_HUD_EFFECTS); - LLSelectMgr::getInstance()->updateEffects(); - LLHUDManager::getInstance()->cleanupEffects(); - LLHUDManager::getInstance()->sendEffects(); - } - - //////////////////////////////////////// - // - // Unpack layer data that we've received - // - - { - LL_RECORD_BLOCK_TIME(FTM_NETWORK); - gVLManager.unpackData(); - } - - ///////////////////////// - // - // Update surfaces, and surface textures as well. - // - - LLWorld::getInstance()->updateVisibilities(); - { - const F32 max_region_update_time = .001f; // 1ms - LL_RECORD_BLOCK_TIME(FTM_REGION_UPDATE); - LLWorld::getInstance()->updateRegions(max_region_update_time); - } - - ///////////////////////// - // - // Update weather effects - // - - // Update wind vector - LLVector3 wind_position_region; - static LLVector3 average_wind; - - LLViewerRegion *regionp; - regionp = LLWorld::getInstance()->resolveRegionGlobal(wind_position_region, gAgent.getPositionGlobal()); // puts agent's local coords into wind_position - if (regionp) - { - gWindVec = regionp->mWind.getVelocity(wind_position_region); - - // Compute average wind and use to drive motion of water - - average_wind = regionp->mWind.getAverage(); - gSky.setWind(average_wind); - //LLVOWater::setWind(average_wind); - } - else - { - gWindVec.setVec(0.0f, 0.0f, 0.0f); - } - - ////////////////////////////////////// - // - // Sort and cull in the new renderer are moved to pipeline.cpp - // Here, particles are updated and drawables are moved. - // - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_APP("world update"); //LL_RECORD_BLOCK_TIME(FTM_WORLD_UPDATE); - gPipeline.updateMove(); - } - - LLWorld::getInstance()->updateParticles(); - - if (gAgentPilot.isPlaying() && gAgentPilot.getOverrideCamera()) - { - gAgentPilot.moveCamera(); - } - else if (LLViewerJoystick::getInstance()->getOverrideCamera()) - { - LLViewerJoystick::getInstance()->moveFlycam(); - } - else - { - if (LLToolMgr::getInstance()->inBuildMode()) - { - LLViewerJoystick::getInstance()->moveObjects(); - } - - gAgentCamera.updateCamera(); - } - - // update media focus - LLViewerMediaFocus::getInstance()->update(); - - // Update marketplace - LLMarketplaceInventoryImporter::update(); - LLMarketplaceInventoryNotifications::update(); - - // objects and camera should be in sync, do LOD calculations now - { - LL_RECORD_BLOCK_TIME(FTM_LOD_UPDATE); - gObjectList.updateApparentAngles(gAgent); - } - - // Update AV render info - LLAvatarRenderInfoAccountant::getInstance()->idle(); - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_APP("audio update"); //LL_RECORD_BLOCK_TIME(FTM_AUDIO_UPDATE); - - if (gAudiop) - { - audio_update_volume(false); - audio_update_listener(); - audio_update_wind(false); - - // this line actually commits the changes we've made to source positions, etc. - gAudiop->idle(); - } - } - - // Handle shutdown process, for example, - // wait for floaters to close, send quit message, - // forcibly quit if it has taken too long - if (mQuitRequested) - { - gGLActive = true; - idleShutdown(); - } -} - -void LLAppViewer::idleShutdown() -{ - // Wait for all modal alerts to get resolved - if (LLModalDialog::activeCount() > 0) - { - return; - } - - // close IM interface - if(gIMMgr) - { - gIMMgr->disconnectAllSessions(); - } - - // Wait for all floaters to get resolved - if (gFloaterView - && !gFloaterView->allChildrenClosed()) - { - return; - } - - - - - // ProductEngine: Try moving this code to where we shut down sTextureCache in cleanup() - // *TODO: ugly - static bool saved_teleport_history = false; - if (!saved_teleport_history) - { - saved_teleport_history = true; - LLTeleportHistory::getInstance()->dump(); - LLLocationHistory::getInstance()->save(); // *TODO: find a better place for doing this - return; - } - - static bool saved_snapshot = false; - if (!saved_snapshot) - { - saved_snapshot = true; - saveFinalSnapshot(); - return; - } - - const F32 SHUTDOWN_UPLOAD_SAVE_TIME = 5.f; - - S32 pending_uploads = gAssetStorage->getNumPendingUploads(); - if (pending_uploads > 0 - && gLogoutTimer.getElapsedTimeF32() < SHUTDOWN_UPLOAD_SAVE_TIME - && !logoutRequestSent()) - { - static S32 total_uploads = 0; - // Sometimes total upload count can change during logout. - total_uploads = llmax(total_uploads, pending_uploads); - gViewerWindow->setShowProgress(true); - S32 finished_uploads = total_uploads - pending_uploads; - F32 percent = 100.f * finished_uploads / total_uploads; - gViewerWindow->setProgressPercent(percent); - gViewerWindow->setProgressString(LLTrans::getString("SavingSettings")); - return; - } - - if (gPendingMetricsUploads > 0 - && gLogoutTimer.getElapsedTimeF32() < SHUTDOWN_UPLOAD_SAVE_TIME - && !logoutRequestSent()) - { - gViewerWindow->setShowProgress(true); - gViewerWindow->setProgressPercent(100.f); - gViewerWindow->setProgressString(LLTrans::getString("LoggingOut")); - return; - } - - // All floaters are closed. Tell server we want to quit. - if( !logoutRequestSent() ) - { - sendLogoutRequest(); - - // Wait for a LogoutReply message - gViewerWindow->setShowProgress(true); - gViewerWindow->setProgressPercent(100.f); - gViewerWindow->setProgressString(LLTrans::getString("LoggingOut")); - return; - } - - // Make sure that we quit if we haven't received a reply from the server. - if( logoutRequestSent() - && gLogoutTimer.getElapsedTimeF32() > gLogoutMaxTime ) - { - forceQuit(); - return; - } -} - -void LLAppViewer::sendLogoutRequest() -{ - if(!mLogoutRequestSent && gMessageSystem) - { - //Set internal status variables and marker files before actually starting the logout process - gLogoutInProgress = true; - if (!mSecondInstance) - { - mLogoutMarkerFileName = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,LOGOUT_MARKER_FILE_NAME); - - mLogoutMarkerFile.open(mLogoutMarkerFileName, LL_APR_WB); - if (mLogoutMarkerFile.getFileHandle()) - { - LL_INFOS("MarkerFile") << "Created logout marker file '"<< mLogoutMarkerFileName << "' " << LL_ENDL; - recordMarkerVersion(mLogoutMarkerFile); - } - else - { - LL_WARNS("MarkerFile") << "Cannot create logout marker file " << mLogoutMarkerFileName << LL_ENDL; - } - } - else - { - LL_INFOS("MarkerFile") << "Did not logout marker file because this is a second instance" << LL_ENDL; - } - - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_LogoutRequest); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - gAgent.sendReliableMessage(); - - gLogoutTimer.reset(); - gLogoutMaxTime = LOGOUT_REQUEST_TIME; - mLogoutRequestSent = true; - - if(LLVoiceClient::instanceExists()) - { - LLVoiceClient::getInstance()->leaveChannel(); - } - } -} - -void LLAppViewer::updateNameLookupUrl(const LLViewerRegion * regionp) -{ - if (!regionp || !regionp->capabilitiesReceived()) - { - return; - } - - LLAvatarNameCache *name_cache = LLAvatarNameCache::getInstance(); - bool had_capability = LLAvatarNameCache::getInstance()->hasNameLookupURL(); - std::string name_lookup_url; - name_lookup_url.reserve(128); // avoid a memory allocation below - name_lookup_url = regionp->getCapability("GetDisplayNames"); - bool have_capability = !name_lookup_url.empty(); - if (have_capability) - { - // we have support for display names, use it - U32 url_size = name_lookup_url.size(); - // capabilities require URLs with slashes before query params: - // https://:/cap//?ids= - // but the caps are granted like: - // https://:/cap/ - if (url_size > 0 && name_lookup_url[url_size - 1] != '/') - { - name_lookup_url += '/'; - } - name_cache->setNameLookupURL(name_lookup_url); - } - else - { - // Display names not available on this region - name_cache->setNameLookupURL(std::string()); - } - - // Error recovery - did we change state? - if (had_capability != have_capability) - { - // name tags are persistant on screen, so make sure they refresh - LLVOAvatar::invalidateNameTags(); - } -} - -void LLAppViewer::idleNameCache() -{ - // Neither old nor new name cache can function before agent has a region - LLViewerRegion* region = gAgent.getRegion(); - if (!region) - { - return; - } - - // deal with any queued name requests and replies. - gCacheName->processPending(); - - // Can't run the new cache until we have the list of capabilities - // for the agent region, and can therefore decide whether to use - // display names or fall back to the old name system. - if (!region->capabilitiesReceived()) - { - return; - } - - LLAvatarNameCache::getInstance()->idle(); -} - -// -// Handle messages, and all message related stuff -// - -#define TIME_THROTTLE_MESSAGES - -#ifdef TIME_THROTTLE_MESSAGES -#define CHECK_MESSAGES_DEFAULT_MAX_TIME .020f // 50 ms = 50 fps (just for messages!) -static F32 CheckMessagesMaxTime = CHECK_MESSAGES_DEFAULT_MAX_TIME; -#endif - -static LLTrace::BlockTimerStatHandle FTM_IDLE_NETWORK("Idle Network"); -static LLTrace::BlockTimerStatHandle FTM_MESSAGE_ACKS("Message Acks"); -static LLTrace::BlockTimerStatHandle FTM_RETRANSMIT("Retransmit"); -static LLTrace::BlockTimerStatHandle FTM_TIMEOUT_CHECK("Timeout Check"); -static LLTrace::BlockTimerStatHandle FTM_DYNAMIC_THROTTLE("Dynamic Throttle"); -static LLTrace::BlockTimerStatHandle FTM_CHECK_REGION_CIRCUIT("Check Region Circuit"); - -void LLAppViewer::idleNetwork() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; - pingMainloopTimeout("idleNetwork"); - - gObjectList.mNumNewObjects = 0; - S32 total_decoded = 0; - - if (!gSavedSettings.getBOOL("SpeedTest")) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_NETWORK("idle network"); //LL_RECORD_BLOCK_TIME(FTM_IDLE_NETWORK); // decode - - LLTimer check_message_timer; - // Read all available packets from network - const S64 frame_count = gFrameCount; // U32->S64 - F32 total_time = 0.0f; - - { - LockMessageChecker lmc(gMessageSystem); - while (lmc.checkAllMessages(frame_count, gServicePump)) - { - if (gDoDisconnect) - { - // We're disconnecting, don't process any more messages from the server - // We're usually disconnecting due to either network corruption or a - // server going down, so this is OK. - break; - } - - total_decoded++; - gPacketsIn++; - - if (total_decoded > MESSAGE_MAX_PER_FRAME) - { - break; - } - -#ifdef TIME_THROTTLE_MESSAGES - // Prevent slow packets from completely destroying the frame rate. - // This usually happens due to clumps of avatars taking huge amount - // of network processing time (which needs to be fixed, but this is - // a good limit anyway). - total_time = check_message_timer.getElapsedTimeF32(); - if (total_time >= CheckMessagesMaxTime) - break; -#endif - } - - // Handle per-frame message system processing. - lmc.processAcks(gSavedSettings.getF32("AckCollectTime")); - } - -#ifdef TIME_THROTTLE_MESSAGES - if (total_time >= CheckMessagesMaxTime) - { - // Increase CheckMessagesMaxTime so that we will eventually catch up - CheckMessagesMaxTime *= 1.035f; // 3.5% ~= x2 in 20 frames, ~8x in 60 frames - } - else - { - // Reset CheckMessagesMaxTime to default value - CheckMessagesMaxTime = CHECK_MESSAGES_DEFAULT_MAX_TIME; - } -#endif - - - - // we want to clear the control after sending out all necessary agent updates - gAgent.resetControlFlags(); - - // Decode enqueued messages... - S32 remaining_possible_decodes = MESSAGE_MAX_PER_FRAME - total_decoded; - - if( remaining_possible_decodes <= 0 ) - { - LL_INFOS() << "Maxed out number of messages per frame at " << MESSAGE_MAX_PER_FRAME << LL_ENDL; - } - - if (gPrintMessagesThisFrame) - { - LL_INFOS() << "Decoded " << total_decoded << " msgs this frame!" << LL_ENDL; - gPrintMessagesThisFrame = false; - } - } - add(LLStatViewer::NUM_NEW_OBJECTS, gObjectList.mNumNewObjects); - - // Retransmit unacknowledged packets. - gXferManager->retransmitUnackedPackets(); - gAssetStorage->checkForTimeouts(); - gViewerThrottle.updateDynamicThrottle(); - - // Check that the circuit between the viewer and the agent's current - // region is still alive - LLViewerRegion *agent_region = gAgent.getRegion(); - if (agent_region && (LLStartUp::getStartupState()==STATE_STARTED)) - { - LLUUID this_region_id = agent_region->getRegionID(); - bool this_region_alive = agent_region->isAlive(); - if ((mAgentRegionLastAlive && !this_region_alive) // newly dead - && (mAgentRegionLastID == this_region_id)) // same region - { - forceDisconnect(LLTrans::getString("AgentLostConnection")); - } - mAgentRegionLastID = this_region_id; - mAgentRegionLastAlive = this_region_alive; - } -} - -void LLAppViewer::disconnectViewer() -{ - if (gDisconnected) - { - return; - } - // - // Cleanup after quitting. - // - // Save snapshot for next time, if we made it through initialization - - LL_INFOS() << "Disconnecting viewer!" << LL_ENDL; - - // Dump our frame statistics - - // Remember if we were flying - gSavedSettings.setBOOL("FlyingAtExit", gAgent.getFlying() ); - - // Un-minimize all windows so they don't get saved minimized - if (gFloaterView) - { - gFloaterView->restoreAll(); - } - - if (LLSelectMgr::instanceExists()) - { - LLSelectMgr::getInstance()->deselectAll(); - } - - // save inventory if appropriate - if (gInventory.isInventoryUsable() - && gAgent.getID().notNull()) // Shouldn't be null at this stage - { - gInventory.cache(gInventory.getRootFolderID(), gAgent.getID()); - if (gInventory.getLibraryRootFolderID().notNull() - && gInventory.getLibraryOwnerID().notNull() - && !mSecondInstance) // agent is unique, library isn't - { - gInventory.cache( - gInventory.getLibraryRootFolderID(), - gInventory.getLibraryOwnerID()); - } - } - - saveNameCache(); - if (LLExperienceCache::instanceExists()) - { - // TODO: LLExperienceCache::cleanup() logic should be moved to - // cleanupSingleton(). - LLExperienceCache::instance().cleanup(); - } - - // close inventory interface, close all windows - LLSidepanelInventory::cleanup(); - - gAgentWearables.cleanup(); - gAgentCamera.cleanup(); - // Also writes cached agent settings to gSavedSettings - gAgent.cleanup(); - - // This is where we used to call gObjectList.destroy() and then delete gWorldp. - // Now we just ask the LLWorld singleton to cleanly shut down. - if(LLWorld::instanceExists()) - { - LLWorld::getInstance()->resetClass(); - } - LLVOCache::deleteSingleton(); - - // call all self-registered classes - LLDestroyClassList::instance().fireCallbacks(); - - cleanup_xfer_manager(); - gDisconnected = true; - - // Pass the connection state to LLUrlEntryParcel not to attempt - // parcel info requests while disconnected. - LLUrlEntryParcel::setDisconnected(gDisconnected); -} - -void LLAppViewer::forceErrorLLError() -{ - LL_ERRS() << "This is a deliberate llerror" << LL_ENDL; -} - -void LLAppViewer::forceErrorLLErrorMsg() -{ - LLError::LLUserWarningMsg::show("Deliberate error"); - // Note: under debug this will show a message as well, - // but release won't show anything and will quit silently - LL_ERRS() << "This is a deliberate llerror with a message" << LL_ENDL; -} - -void LLAppViewer::forceErrorBreakpoint() -{ - LL_WARNS() << "Forcing a deliberate breakpoint" << LL_ENDL; -#ifdef LL_WINDOWS - DebugBreak(); -#else - asm ("int $3"); -#endif - return; -} - -void LLAppViewer::forceErrorBadMemoryAccess() -{ - LL_WARNS() << "Forcing a deliberate bad memory access" << LL_ENDL; - S32* crash = NULL; - *crash = 0xDEADBEEF; - return; -} - -void LLAppViewer::forceErrorInfiniteLoop() -{ - LL_WARNS() << "Forcing a deliberate infinite loop" << LL_ENDL; - // Loop is intentionally complicated to fool basic loop detection - LLTimer timer_total; - LLTimer timer_expiry; - const S32 report_frequency = 10; - timer_expiry.setTimerExpirySec(report_frequency); - while(true) - { - if (timer_expiry.hasExpired()) - { - LL_INFOS() << "Infinite loop time : " << timer_total.getElapsedSeconds() << LL_ENDL; - timer_expiry.setTimerExpirySec(report_frequency); - } - } - return; -} - -void LLAppViewer::forceErrorSoftwareException() -{ - LL_WARNS() << "Forcing a deliberate exception" << LL_ENDL; - LLTHROW(LLException("User selected Force Software Exception")); -} - -void LLAppViewer::forceErrorOSSpecificException() -{ - // Virtual, MacOS only - const std::string exception_text = "User selected Force OS Exception, Not implemented on this OS"; - throw std::runtime_error(exception_text); -} - -void LLAppViewer::forceErrorDriverCrash() -{ - LL_WARNS() << "Forcing a deliberate driver crash" << LL_ENDL; - glDeleteTextures(1, NULL); -} - -void LLAppViewer::forceErrorCoroutineCrash() -{ - LL_WARNS() << "Forcing a crash in LLCoros" << LL_ENDL; - LLCoros::instance().launch("LLAppViewer::crashyCoro", [] {throw LLException("A deliberate crash from LLCoros"); }); -} - -void LLAppViewer::forceErrorThreadCrash() -{ - class LLCrashTestThread : public LLThread - { - public: - - LLCrashTestThread() : LLThread("Crash logging test thread") - { - } - - void run() - { - LL_ERRS() << "This is a deliberate llerror in thread" << LL_ENDL; - } - }; - - LL_WARNS() << "This is a deliberate crash in a thread" << LL_ENDL; - LLCrashTestThread *thread = new LLCrashTestThread(); - thread->start(); -} - -void LLAppViewer::initMainloopTimeout(const std::string& state, F32 secs) -{ - if(!mMainloopTimeout) - { - mMainloopTimeout = new LLWatchdogTimeout(); - resumeMainloopTimeout(state, secs); - } -} - -void LLAppViewer::destroyMainloopTimeout() -{ - if(mMainloopTimeout) - { - delete mMainloopTimeout; - mMainloopTimeout = NULL; - } -} - -void LLAppViewer::resumeMainloopTimeout(const std::string& state, F32 secs) -{ - if(mMainloopTimeout) - { - if(secs < 0.0f) - { - static LLCachedControl mainloop_timeout(gSavedSettings, "MainloopTimeoutDefault", 60); - secs = mainloop_timeout; - } - - mMainloopTimeout->setTimeout(secs); - mMainloopTimeout->start(state); - } -} - -void LLAppViewer::pauseMainloopTimeout() -{ - if(mMainloopTimeout) - { - mMainloopTimeout->stop(); - } -} - -void LLAppViewer::pingMainloopTimeout(const std::string& state, F32 secs) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_APP; - - if(mMainloopTimeout) - { - if(secs < 0.0f) - { - static LLCachedControl mainloop_timeout(gSavedSettings, "MainloopTimeoutDefault", 60); - secs = mainloop_timeout; - } - - mMainloopTimeout->setTimeout(secs); - mMainloopTimeout->ping(state); - } -} - -void LLAppViewer::handleLoginComplete() -{ - gLoggedInTime.start(); - initMainloopTimeout("Mainloop Init"); - - // Store some data to DebugInfo in case of a freeze. - gDebugInfo["ClientInfo"]["Name"] = LLVersionInfo::instance().getChannel(); - - gDebugInfo["ClientInfo"]["MajorVersion"] = LLVersionInfo::instance().getMajor(); - gDebugInfo["ClientInfo"]["MinorVersion"] = LLVersionInfo::instance().getMinor(); - gDebugInfo["ClientInfo"]["PatchVersion"] = LLVersionInfo::instance().getPatch(); - gDebugInfo["ClientInfo"]["BuildVersion"] = std::to_string(LLVersionInfo::instance().getBuild()); - - LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - if ( parcel && parcel->getMusicURL()[0]) - { - gDebugInfo["ParcelMusicURL"] = parcel->getMusicURL(); - } - if ( parcel && parcel->getMediaURL()[0]) - { - gDebugInfo["ParcelMediaURL"] = parcel->getMediaURL(); - } - - gDebugInfo["SettingsFilename"] = gSavedSettings.getString("ClientSettingsFile"); - gDebugInfo["CAFilename"] = gDirUtilp->getCAFile(); - gDebugInfo["ViewerExePath"] = gDirUtilp->getExecutablePathAndName(); - gDebugInfo["CurrentPath"] = gDirUtilp->getCurPath(); - - if(gAgent.getRegion()) - { - gDebugInfo["CurrentSimHost"] = gAgent.getRegion()->getSimHostName(); - gDebugInfo["CurrentRegion"] = gAgent.getRegion()->getName(); - } - - if(LLAppViewer::instance()->mMainloopTimeout) - { - gDebugInfo["MainloopTimeoutState"] = LLAppViewer::instance()->mMainloopTimeout->getState(); - } - - mOnLoginCompleted(); - - writeDebugInfo(); - - // we logged in successfully, so save settings on logout - LL_INFOS() << "Login successful, per account settings will be saved on log out." << LL_ENDL; - mSavePerAccountSettings=true; -} - -//virtual -void LLAppViewer::setMasterSystemAudioMute(bool mute) -{ - gSavedSettings.setBOOL("MuteAudio", mute); -} - -//virtual -bool LLAppViewer::getMasterSystemAudioMute() -{ - return gSavedSettings.getBOOL("MuteAudio"); -} - -//---------------------------------------------------------------------------- -// Metrics-related methods (static and otherwise) -//---------------------------------------------------------------------------- - -/** - * LLViewerAssetStats collects data on a per-region (as defined by the agent's - * location) so we need to tell it about region changes which become a kind of - * hidden variable/global state in the collectors. For collectors not running - * on the main thread, we need to send a message to move the data over safely - * and cheaply (amortized over a run). - */ -void LLAppViewer::metricsUpdateRegion(U64 region_handle) -{ - if (0 != region_handle) - { - LLViewerAssetStatsFF::set_region(region_handle); - } -} - -/** - * Attempts to start a multi-threaded metrics report to be sent back to - * the grid for consumption. - */ -void LLAppViewer::metricsSend(bool enable_reporting) -{ - if (! gViewerAssetStats) - return; - - if (LLAppViewer::sTextureFetch) - { - LLViewerRegion * regionp = gAgent.getRegion(); - - if (enable_reporting && regionp) - { - std::string caps_url = regionp->getCapability("ViewerMetrics"); - - LLSD sd = gViewerAssetStats->asLLSD(true); - - // Send a report request into 'thread1' to get the rest of the data - // and provide some additional parameters while here. - LLAppViewer::sTextureFetch->commandSendMetrics(caps_url, - gAgentSessionID, - gAgentID, - sd); - } - else - { - LLAppViewer::sTextureFetch->commandDataBreak(); - } - } - - // Reset even if we can't report. Rather than gather up a huge chunk of - // data, we'll keep to our sampling interval and retain the data - // resolution in time. - gViewerAssetStats->restart(); -} - +/** + * @file llappviewer.cpp + * @brief The LLAppViewer class definitions + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llappviewer.h" + +// Viewer includes +#include "llversioninfo.h" +#include "llfeaturemanager.h" +#include "lluictrlfactory.h" +#include "lltexteditor.h" +#include "llenvironment.h" +#include "llerrorcontrol.h" +#include "lleventtimer.h" +#include "llfile.h" +#include "llviewertexturelist.h" +#include "llgroupmgr.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llagentlanguage.h" +#include "llagentui.h" +#include "llagentwearables.h" +#include "lldirpicker.h" +#include "llfloaterimcontainer.h" +#include "llimprocessing.h" +#include "llwindow.h" +#include "llviewerstats.h" +#include "llviewerstatsrecorder.h" +#include "llkeyconflict.h" // for legacy keybinding support, remove later +#include "llmarketplacefunctions.h" +#include "llmarketplacenotifications.h" +#include "llmd5.h" +#include "llmeshrepository.h" +#include "llpumpio.h" +#include "llmimetypes.h" +#include "llslurl.h" +#include "llstartup.h" +#include "llfocusmgr.h" +#include "llurlfloaterdispatchhandler.h" +#include "llviewerjoystick.h" +#include "llallocator.h" +#include "llcalc.h" +#include "llconversationlog.h" +#if LL_WINDOWS +#include "lldxhardware.h" +#endif +#include "lltexturestats.h" +#include "lltrace.h" +#include "lltracethreadrecorder.h" +#include "llviewerwindow.h" +#include "llviewerdisplay.h" +#include "llviewermedia.h" +#include "llviewerparcelaskplay.h" +#include "llviewerparcelmedia.h" +#include "llviewershadermgr.h" +#include "llviewermediafocus.h" +#include "llviewermessage.h" +#include "llviewerobjectlist.h" +#include "llworldmap.h" +#include "llmutelist.h" +#include "llviewerhelp.h" +#include "lluicolortable.h" +#include "llurldispatcher.h" +#include "llurlhistory.h" +#include "llrender.h" +#include "llteleporthistory.h" +#include "lltoast.h" +#include "llsdutil_math.h" +#include "lllocationhistory.h" +#include "llfasttimerview.h" +#include "llvector4a.h" +#include "llviewermenufile.h" +#include "llvoicechannel.h" +#include "llvoavatarself.h" +#include "llurlmatch.h" +#include "lltextutil.h" +#include "lllogininstance.h" +#include "llprogressview.h" +#include "llvocache.h" +#include "lldiskcache.h" +#include "llvopartgroup.h" +#include "llweb.h" +#include "llspellcheck.h" +#include "llscenemonitor.h" +#include "llavatarrenderinfoaccountant.h" +#include "lllocalbitmaps.h" +#include "llperfstats.h" +#include "llgltfmateriallist.h" + +// Linden library includes +#include "llavatarnamecache.h" +#include "lldiriterator.h" +#include "llexperiencecache.h" +#include "llimagej2c.h" +#include "llmemory.h" +#include "llprimitive.h" +#include "llurlaction.h" +#include "llurlentry.h" +#include "llvolumemgr.h" +#include "llxfermanager.h" +#include "llphysicsextensions.h" + +#include "llnotificationmanager.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" + +#include "llleap.h" +#include "stringize.h" +#include "llcoros.h" +#include "llexception.h" +#if !LL_LINUX +#include "cef/dullahan_version.h" +#include "vlc/libvlc_version.h" +#endif // LL_LINUX + +#if LL_DARWIN +#include "llwindowmacosx.h" +#endif + +// Third party library includes +#include +#include +#include +#include + +#if LL_WINDOWS +# include // For _SH_DENYWR in processMarkerFiles +#else +# include // For processMarkerFiles +#endif + +#include "llapr.h" +#include + +#include "llviewerinput.h" +#include "lllfsthread.h" +#include "llworkerthread.h" +#include "lltexturecache.h" +#include "lltexturefetch.h" +#include "llimageworker.h" +#include "llevents.h" + +// The files below handle dependencies from cleanup. +#include "llkeyframemotion.h" +#include "llworldmap.h" +#include "llhudmanager.h" +#include "lltoolmgr.h" +#include "llassetstorage.h" +#include "llpolymesh.h" +#include "llproxy.h" +#include "llaudioengine.h" +#include "llstreamingaudio.h" +#include "llviewermenu.h" +#include "llselectmgr.h" +#include "lltrans.h" +#include "lltransutil.h" +#include "lltracker.h" +#include "llviewerparcelmgr.h" +#include "llworldmapview.h" +#include "llpostprocess.h" + +#include "lldebugview.h" +#include "llconsole.h" +#include "llcontainerview.h" +#include "lltooltip.h" + +#include "llsdutil.h" +#include "llsdserialize.h" + +#include "llworld.h" +#include "llhudeffecttrail.h" +#include "llslurl.h" +#include "llurlregistry.h" +#include "llwatchdog.h" + +// Included so that constants/settings might be initialized +// in save_settings_to_globals() +#include "llbutton.h" +#include "llstatusbar.h" +#include "llsurface.h" +#include "llvosky.h" +#include "llvotree.h" +#include "llvoavatar.h" +#include "llfolderview.h" +#include "llagentpilot.h" +#include "llvovolume.h" +#include "llflexibleobject.h" +#include "llvosurfacepatch.h" +#include "llviewerfloaterreg.h" +#include "llcommandlineparser.h" +#include "llfloatermemleak.h" +#include "llfloaterreg.h" +#include "llfloatersimplesnapshot.h" +#include "llfloatersnapshot.h" +#include "llsidepanelinventory.h" +#include "llatmosphere.h" + +// includes for idle() idleShutdown() +#include "llviewercontrol.h" +#include "lleventnotifier.h" +#include "llcallbacklist.h" +#include "lldeferredsounds.h" +#include "pipeline.h" +#include "llgesturemgr.h" +#include "llsky.h" +#include "llvlmanager.h" +#include "llviewercamera.h" +#include "lldrawpoolbump.h" +#include "llvieweraudio.h" +#include "llimview.h" +#include "llviewerthrottle.h" +#include "llparcel.h" +#include "llavatariconctrl.h" +#include "llgroupiconctrl.h" +#include "llviewerassetstats.h" +#include "workqueue.h" +using namespace LL; + +// Include for security api initialization +#include "llsecapi.h" +#include "llmachineid.h" +#include "llcleanup.h" + +#include "llcoproceduremanager.h" +#include "llviewereventrecorder.h" + +// *FIX: These extern globals should be cleaned up. +// The globals either represent state/config/resource-storage of either +// this app, or another 'component' of the viewer. App globals should be +// moved into the app class, where as the other globals should be +// moved out of here. +// If a global symbol reference seems valid, it will be included +// via header files above. + +//---------------------------------------------------------------------------- +// llviewernetwork.h +#include "llviewernetwork.h" +// define a self-registering event API object +#include "llappviewerlistener.h" + +#if LL_LINUX && LL_GTK +#include "glib.h" +#endif // (LL_LINUX) && LL_GTK + +#if LL_MSVC +// disable boost::lexical_cast warning +#pragma warning (disable:4702) +#endif + +static LLAppViewerListener sAppViewerListener(LLAppViewer::instance); + +////// Windows-specific includes to the bottom - nasty defines in these pollute the preprocessor +// +//---------------------------------------------------------------------------- +// viewer.cpp - these are only used in viewer, should be easily moved. + +#if LL_DARWIN +extern void init_apple_menu(const char* product); +#endif // LL_DARWIN + +extern bool gRandomizeFramerate; +extern bool gPeriodicSlowFrame; +extern bool gDebugGL; + +#if LL_DARWIN +extern bool gHiDPISupport; +#endif + +//////////////////////////////////////////////////////////// +// All from the last globals push... + +F32 gSimLastTime; // Used in LLAppViewer::init and send_viewer_stats() +F32 gSimFrames; + +bool gShowObjectUpdates = false; +bool gUseQuickTime = true; + +eLastExecEvent gLastExecEvent = LAST_EXEC_NORMAL; +S32 gLastExecDuration = -1; // (<0 indicates unknown) + +#if LL_WINDOWS +# define LL_PLATFORM_KEY "win" +#elif LL_DARWIN +# define LL_PLATFORM_KEY "mac" +#elif LL_LINUX +# define LL_PLATFORM_KEY "lnx" +#else +# error "Unknown Platform" +#endif +const char* gPlatform = LL_PLATFORM_KEY; + +LLSD gDebugInfo; + +U32 gFrameCount = 0; +U32 gForegroundFrameCount = 0; // number of frames that app window was in foreground +LLPumpIO* gServicePump = NULL; + +U64MicrosecondsImplicit gFrameTime = 0; +F32SecondsImplicit gFrameTimeSeconds = 0.f; +F32SecondsImplicit gFrameIntervalSeconds = 0.f; +F32 gFPSClamped = 10.f; // Pretend we start at target rate. +F32 gFrameDTClamped = 0.f; // Time between adjacent checks to network for packets +U64MicrosecondsImplicit gStartTime = 0; // gStartTime is "private", used only to calculate gFrameTimeSeconds + +LLTimer gRenderStartTime; +LLFrameTimer gForegroundTime; +LLFrameTimer gLoggedInTime; +LLTimer gLogoutTimer; +static const F32 LOGOUT_REQUEST_TIME = 6.f; // this will be cut short by the LogoutReply msg. +F32 gLogoutMaxTime = LOGOUT_REQUEST_TIME; + + +S32 gPendingMetricsUploads = 0; + + +bool gDisconnected = false; + +// used to restore texture state after a mode switch +LLFrameTimer gRestoreGLTimer; +bool gRestoreGL = false; +bool gUseWireframe = false; + +LLMemoryInfo gSysMemory; +U64Bytes gMemoryAllocated(0); // updated in display_stats() in llviewerdisplay.cpp + +std::string gLastVersionChannel; + +LLVector3 gWindVec(3.0, 3.0, 0.0); +LLVector3 gRelativeWindVec(0.0, 0.0, 0.0); + +U32 gPacketsIn = 0; + +bool gPrintMessagesThisFrame = false; + +bool gRandomizeFramerate = false; +bool gPeriodicSlowFrame = false; + +bool gCrashOnStartup = false; +bool gLLErrorActivated = false; +bool gLogoutInProgress = false; + +bool gSimulateMemLeak = false; + +// We don't want anyone, especially threads working on the graphics pipeline, +// to have to block due to this WorkQueue being full. +WorkQueue gMainloopWork("mainloop", 1024*1024); + +//////////////////////////////////////////////////////////// +// Internal globals... that should be removed. +static std::string gArgs; +const int MAX_MARKER_LENGTH = 1024; +const std::string MARKER_FILE_NAME("SecondLife.exec_marker"); +const std::string START_MARKER_FILE_NAME("SecondLife.start_marker"); +const std::string ERROR_MARKER_FILE_NAME("SecondLife.error_marker"); +const std::string LLERROR_MARKER_FILE_NAME("SecondLife.llerror_marker"); +const std::string LOGOUT_MARKER_FILE_NAME("SecondLife.logout_marker"); +static bool gDoDisconnect = false; +static std::string gLaunchFileOnQuit; + +// Used on Win32 for other apps to identify our window (eg, win_setup) +const char* const VIEWER_WINDOW_CLASSNAME = "Second Life"; + +//---------------------------------------------------------------------------- + +// List of entries from strings.xml to always replace +static std::set default_trans_args; +void init_default_trans_args() +{ + default_trans_args.insert("SECOND_LIFE"); // World + default_trans_args.insert("APP_NAME"); + default_trans_args.insert("CAPITALIZED_APP_NAME"); + default_trans_args.insert("SECOND_LIFE_GRID"); + default_trans_args.insert("SUPPORT_SITE"); + // This URL shows up in a surprising number of places in various skin + // files. We really only want to have to maintain a single copy of it. + default_trans_args.insert("create_account_url"); +} + +struct SettingsFile : public LLInitParam::Block +{ + Mandatory name; + Optional file_name; + Optional required, + persistent; + Optional file_name_setting; + + SettingsFile() + : name("name"), + file_name("file_name"), + required("required", false), + persistent("persistent", true), + file_name_setting("file_name_setting") + {} +}; + +struct SettingsGroup : public LLInitParam::Block +{ + Mandatory name; + Mandatory path_index; + Multiple files; + + SettingsGroup() + : name("name"), + path_index("path_index"), + files("file") + {} +}; + +struct SettingsFiles : public LLInitParam::Block +{ + Multiple groups; + + SettingsFiles() + : groups("group") + {} +}; + +static std::string gWindowTitle; + +//---------------------------------------------------------------------------- +// Metrics logging control constants +//---------------------------------------------------------------------------- +static const F32 METRICS_INTERVAL_DEFAULT = 600.0; +static const F32 METRICS_INTERVAL_QA = 30.0; +static F32 app_metrics_interval = METRICS_INTERVAL_DEFAULT; +static bool app_metrics_qa_mode = false; + +void idle_afk_check() +{ + // check idle timers + F32 current_idle = gAwayTriggerTimer.getElapsedTimeF32(); + F32 afk_timeout = gSavedSettings.getS32("AFKTimeout"); + if (afk_timeout && (current_idle > afk_timeout) && ! gAgent.getAFK()) + { + LL_INFOS("IdleAway") << "Idle more than " << afk_timeout << " seconds: automatically changing to Away status" << LL_ENDL; + gAgent.setAFK(); + } +} + +// A callback set in LLAppViewer::init() +static void ui_audio_callback(const LLUUID& uuid) +{ + if (gAudiop) + { + SoundData soundData(uuid, gAgent.getID(), 1.0f, LLAudioEngine::AUDIO_TYPE_UI); + gAudiop->triggerSound(soundData); + } +} + +// A callback set in LLAppViewer::init() +static void deferred_ui_audio_callback(const LLUUID& uuid) +{ + if (gAudiop) + { + SoundData soundData(uuid, gAgent.getID(), 1.0f, LLAudioEngine::AUDIO_TYPE_UI); + LLDeferredSounds::instance().deferSound(soundData); + } +} + +bool create_text_segment_icon_from_url_match(LLUrlMatch* match,LLTextBase* base) +{ + if(!match || !base || base->getPlainText()) + return false; + + LLUUID match_id = match->getID(); + + LLIconCtrl* icon; + + if( match->getMenuName() == "menu_url_group.xml" // See LLUrlEntryGroup constructor + || gAgent.isInGroup(match_id, true)) //This check seems unfiting, urls are either /agent or /group + { + LLGroupIconCtrl::Params icon_params; + icon_params.group_id = match_id; + icon_params.rect = LLRect(0, 16, 16, 0); + icon_params.visible = true; + icon = LLUICtrlFactory::instance().create(icon_params); + } + else + { + LLAvatarIconCtrl::Params icon_params; + icon_params.avatar_id = match_id; + icon_params.rect = LLRect(0, 16, 16, 0); + icon_params.visible = true; + icon = LLUICtrlFactory::instance().create(icon_params); + } + + LLInlineViewSegment::Params params; + params.force_newline = false; + params.view = icon; + params.left_pad = 4; + params.right_pad = 4; + params.top_pad = -2; + params.bottom_pad = 2; + + base->appendWidget(params," ",false); + + return true; +} + +// Use these strictly for things that are constructed at startup, +// or for things that are performance critical. JC +static void settings_to_globals() +{ + LLSurface::setTextureSize(gSavedSettings.getU32("RegionTextureSize")); + +#if LL_DARWIN + LLRender::sGLCoreProfile = true; +#else + LLRender::sGLCoreProfile = gSavedSettings.getBOOL("RenderGLContextCoreProfile"); +#endif + LLRender::sNsightDebugSupport = gSavedSettings.getBOOL("RenderNsightDebugSupport"); + LLImageGL::sGlobalUseAnisotropic = gSavedSettings.getBOOL("RenderAnisotropic"); + LLImageGL::sCompressTextures = gSavedSettings.getBOOL("RenderCompressTextures"); + LLVOVolume::sLODFactor = llclamp(gSavedSettings.getF32("RenderVolumeLODFactor"), 0.01f, MAX_LOD_FACTOR); + LLVOVolume::sDistanceFactor = 1.f-LLVOVolume::sLODFactor * 0.1f; + LLVolumeImplFlexible::sUpdateFactor = gSavedSettings.getF32("RenderFlexTimeFactor"); + LLVOTree::sTreeFactor = gSavedSettings.getF32("RenderTreeLODFactor"); + LLVOAvatar::sLODFactor = llclamp(gSavedSettings.getF32("RenderAvatarLODFactor"), 0.f, MAX_AVATAR_LOD_FACTOR); + LLVOAvatar::sPhysicsLODFactor = llclamp(gSavedSettings.getF32("RenderAvatarPhysicsLODFactor"), 0.f, MAX_AVATAR_LOD_FACTOR); + LLVOAvatar::updateImpostorRendering(gSavedSettings.getU32("RenderAvatarMaxNonImpostors")); + LLVOAvatar::sVisibleInFirstPerson = gSavedSettings.getBOOL("FirstPersonAvatarVisible"); + // clamp auto-open time to some minimum usable value + LLFolderView::sAutoOpenTime = llmax(0.25f, gSavedSettings.getF32("FolderAutoOpenDelay")); + LLSelectMgr::sRectSelectInclusive = gSavedSettings.getBOOL("RectangleSelectInclusive"); + LLSelectMgr::sRenderHiddenSelections = gSavedSettings.getBOOL("RenderHiddenSelections"); + LLSelectMgr::sRenderLightRadius = gSavedSettings.getBOOL("RenderLightRadius"); + + gAgentPilot.setNumRuns(gSavedSettings.getS32("StatsNumRuns")); + gAgentPilot.setQuitAfterRuns(gSavedSettings.getBOOL("StatsQuitAfterRuns")); + gAgent.setHideGroupTitle(gSavedSettings.getBOOL("RenderHideGroupTitle")); + + gDebugWindowProc = gSavedSettings.getBOOL("DebugWindowProc"); + gShowObjectUpdates = gSavedSettings.getBOOL("ShowObjectUpdates"); + LLWorldMapView::setScaleSetting(gSavedSettings.getF32("MapScale")); + +#if LL_DARWIN + LLWindowMacOSX::sUseMultGL = gSavedSettings.getBOOL("RenderAppleUseMultGL"); + gHiDPISupport = gSavedSettings.getBOOL("RenderHiDPI"); +#endif +} + +static void settings_modify() +{ + LLPipeline::sRenderTransparentWater = gSavedSettings.getBOOL("RenderTransparentWater"); + LLPipeline::sRenderDeferred = true; // false is deprecated + LLRenderTarget::sUseFBO = LLPipeline::sRenderDeferred; + LLVOSurfacePatch::sLODFactor = gSavedSettings.getF32("RenderTerrainLODFactor"); + LLVOSurfacePatch::sLODFactor *= LLVOSurfacePatch::sLODFactor; //square lod factor to get exponential range of [1,4] + gDebugGL = gDebugGLSession || gDebugSession; + gDebugPipeline = gSavedSettings.getBOOL("RenderDebugPipeline"); +} + +class LLFastTimerLogThread : public LLThread +{ +public: + std::string mFile; + + LLFastTimerLogThread(std::string& test_name) : LLThread("fast timer log") + { + std::string file_name = test_name + std::string(".slp"); + mFile = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, file_name); + } + + void run() + { + llofstream os(mFile.c_str()); + + while (!LLAppViewer::instance()->isQuitting()) + { + LLTrace::BlockTimer::writeLog(os); + os.flush(); + ms_sleep(32); + } + + os.close(); + } +}; + +//virtual +bool LLAppViewer::initSLURLHandler() +{ + // does nothing unless subclassed + return false; +} + +//virtual +bool LLAppViewer::sendURLToOtherInstance(const std::string& url) +{ + // does nothing unless subclassed + return false; +} + +//---------------------------------------------------------------------------- +// LLAppViewer definition + +// Static members. +// The single viewer app. +LLAppViewer* LLAppViewer::sInstance = NULL; +LLTextureCache* LLAppViewer::sTextureCache = NULL; +LLImageDecodeThread* LLAppViewer::sImageDecodeThread = NULL; +LLTextureFetch* LLAppViewer::sTextureFetch = NULL; +LLPurgeDiskCacheThread* LLAppViewer::sPurgeDiskCacheThread = NULL; + +std::string getRuntime() +{ + return llformat("%.4f", (F32)LLTimer::getElapsedSeconds().value()); +} + +LLAppViewer::LLAppViewer() +: mMarkerFile(), + mLogoutMarkerFile(), + mReportedCrash(false), + mNumSessions(0), + mGeneralThreadPool(nullptr), + mPurgeCache(false), + mPurgeCacheOnExit(false), + mPurgeUserDataOnExit(false), + mSecondInstance(false), + mUpdaterNotFound(false), + mSavedFinalSnapshot(false), + mSavePerAccountSettings(false), // don't save settings on logout unless login succeeded. + mQuitRequested(false), + mLogoutRequestSent(false), + mLastAgentControlFlags(0), + mLastAgentForceUpdate(0), + mMainloopTimeout(NULL), + mAgentRegionLastAlive(false), + mRandomizeFramerate(LLCachedControl(gSavedSettings,"Randomize Framerate", false)), + mPeriodicSlowFrame(LLCachedControl(gSavedSettings,"Periodic Slow Frame", false)), + mFastTimerLogThread(NULL), + mSettingsLocationList(NULL), + mIsFirstRun(false) +{ + if(NULL != sInstance) + { + LL_ERRS() << "Oh no! An instance of LLAppViewer already exists! LLAppViewer is sort of like a singleton." << LL_ENDL; + } + + mDumpPath =""; + + // Need to do this initialization before we do anything else, since anything + // that touches files should really go through the lldir API + gDirUtilp->initAppDirs("SecondLife"); + // + // IMPORTANT! Do NOT put anything that will write + // into the log files during normal startup until AFTER + // we run the "program crashed last time" error handler below. + // + sInstance = this; + + gLoggedInTime.stop(); + + processMarkerFiles(); + // + // OK to write stuff to logs now, we've now crash reported if necessary + // + + LLLoginInstance::instance().setPlatformInfo(gPlatform, LLOSInfo::instance().getOSVersionString(), LLOSInfo::instance().getOSStringSimple()); + + // Under some circumstances we want to read the static_debug_info.log file + // from the previous viewer run between this constructor call and the + // init() call, which will overwrite the static_debug_info.log file for + // THIS run. So setDebugFileNames() early. +# ifdef LL_BUGSPLAT + // MAINT-8917: don't create a dump directory just for the + // static_debug_info.log file + std::string logdir = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ""); +# else // ! LL_BUGSPLAT + // write Google Breakpad minidump files to a per-run dump directory to avoid multiple viewer issues. + std::string logdir = gDirUtilp->getExpandedFilename(LL_PATH_DUMP, ""); +# endif // ! LL_BUGSPLAT + mDumpPath = logdir; + + setDebugFileNames(logdir); +} + +LLAppViewer::~LLAppViewer() +{ + delete mSettingsLocationList; + + destroyMainloopTimeout(); + + // If we got to this destructor somehow, the app didn't hang. + removeMarkerFiles(); +} + +class LLUITranslationBridge : public LLTranslationBridge +{ +public: + virtual std::string getString(const std::string &xml_desc) + { + return LLTrans::getString(xml_desc); + } +}; + + +bool LLAppViewer::init() +{ + setupErrorHandling(mSecondInstance); + + // + // Start of the application + // + + // initialize the LLSettingsType translation bridge. + LLTranslationBridge::ptr_t trans = std::make_shared(); + LLSettingsType::initParamSingleton(trans); + + // initialize SSE options + LLVector4a::initClass(); + + //initialize particle index pool + LLVOPartGroup::initClass(); + + // set skin search path to default, will be overridden later + // this allows simple skinned file lookups to work + gDirUtilp->setSkinFolder("default", "en"); + +// initLoggingAndGetLastDuration(); + + // + // OK to write stuff to logs now, we've now crash reported if necessary + // + init_default_trans_args(); + + // inits from settings.xml and from strings.xml + if (!initConfiguration()) + return false; + + LL_INFOS("InitInfo") << "Configuration initialized." << LL_ENDL ; + + //set the max heap size. + initMaxHeapSize() ; + LLCoros::instance().setStackSize(gSavedSettings.getS32("CoroutineStackSize")); + + + // Although initLoggingAndGetLastDuration() is the right place to mess with + // setFatalFunction(), we can't query gSavedSettings until after + // initConfiguration(). + S32 rc(gSavedSettings.getS32("QAModeTermCode")); + if (rc >= 0) + { + // QAModeTermCode set, terminate with that rc on LL_ERRS. Use + // _exit() rather than exit() because normal cleanup depends too + // much on successful startup! + LLError::setFatalFunction([rc](const std::string&){ _exit(rc); }); + } + + mAlloc.setProfilingEnabled(gSavedSettings.getBOOL("MemProfiling")); + + // Initialize the non-LLCurl libcurl library. Should be called + // before consumers (LLTextureFetch). + mAppCoreHttp.init(); + + LL_INFOS("InitInfo") << "LLCore::Http initialized." << LL_ENDL ; + + LLMachineID::init(); + + { + if (gSavedSettings.getBOOL("QAModeMetrics")) + { + app_metrics_qa_mode = true; + app_metrics_interval = METRICS_INTERVAL_QA; + } + LLViewerAssetStatsFF::init(); + } + + initThreads(); + LL_INFOS("InitInfo") << "Threads initialized." << LL_ENDL ; + + // Initialize settings early so that the defaults for ignorable dialogs are + // picked up and then correctly re-saved after launching the updater (STORM-1268). + LLUI::settings_map_t settings_map; + settings_map["config"] = &gSavedSettings; + settings_map["ignores"] = &gWarningSettings; + settings_map["floater"] = &gSavedSettings; // *TODO: New settings file + settings_map["account"] = &gSavedPerAccountSettings; + + LLUI::initParamSingleton(settings_map, + LLUIImageList::getInstance(), + ui_audio_callback, + deferred_ui_audio_callback); + LL_INFOS("InitInfo") << "UI initialized." << LL_ENDL ; + + // NOW LLUI::getLanguage() should work. gDirUtilp must know the language + // for this session ASAP so all the file-loading commands that follow, + // that use findSkinnedFilenames(), will include the localized files. + gDirUtilp->setSkinFolder(gDirUtilp->getSkinFolder(), LLUI::getLanguage()); + + // Setup LLTrans after LLUI::initClass has been called. + initStrings(); + + // initialize LLWearableType translation bridge. + // Will immediately use LLTranslationBridge to init LLWearableDictionary + LLWearableType::initParamSingleton(trans); + + // Setup notifications after LLUI::initClass() has been called. + LLNotifications::instance(); + LL_INFOS("InitInfo") << "Notifications initialized." << LL_ENDL ; + + ////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////// + // *FIX: The following code isn't grouped into functions yet. + + // + // Various introspection concerning the libs we're using - particularly + // the libs involved in getting to a full login screen. + // + LL_INFOS("InitInfo") << "J2C Engine is: " << LLImageJ2C::getEngineInfo() << LL_ENDL; + LL_INFOS("InitInfo") << "libcurl version is: " << LLCore::LLHttp::getCURLVersion() << LL_ENDL; + + ///////////////////////////////////////////////// + // OS-specific login dialogs + ///////////////////////////////////////////////// + + //test_cached_control(); + + // track number of times that app has run + mNumSessions = gSavedSettings.getS32("NumSessions"); + mNumSessions++; + gSavedSettings.setS32("NumSessions", mNumSessions); + + // LLKeyboard relies on LLUI to know what some accelerator keys are called. + LLKeyboard::setStringTranslatorFunc( LLTrans::getKeyboardString ); + + // Provide the text fields with callbacks for opening Urls + LLUrlAction::setOpenURLCallback(boost::bind(&LLWeb::loadURL, _1, LLStringUtil::null, LLStringUtil::null)); + LLUrlAction::setOpenURLInternalCallback(boost::bind(&LLWeb::loadURLInternal, _1, LLStringUtil::null, LLStringUtil::null, false)); + LLUrlAction::setOpenURLExternalCallback(boost::bind(&LLWeb::loadURLExternal, _1, true, LLStringUtil::null)); + LLUrlAction::setExecuteSLURLCallback(&LLURLDispatcher::dispatchFromTextEditor); + + // Let code in llui access the viewer help floater + LLUI::getInstance()->mHelpImpl = LLViewerHelp::getInstance(); + + LL_INFOS("InitInfo") << "UI initialization is done." << LL_ENDL ; + + // Load translations for tooltips + LLFloater::initClass(); + LLUrlFloaterDispatchHandler::registerInDispatcher(); + + ///////////////////////////////////////////////// + + LLToolMgr::getInstance(); // Initialize tool manager if not already instantiated + + LLViewerFloaterReg::registerFloaters(); + + ///////////////////////////////////////////////// + // + // Load settings files + // + // + LLGroupMgr::parseRoleActions("role_actions.xml"); + + LLAgent::parseTeleportMessages("teleport_strings.xml"); + + // load MIME type -> media impl mappings + std::string mime_types_name; +#if LL_DARWIN + mime_types_name = "mime_types_mac.xml"; +#elif LL_LINUX + mime_types_name = "mime_types_linux.xml"; +#else + mime_types_name = "mime_types.xml"; +#endif + LLMIMETypes::parseMIMETypes( mime_types_name ); + + // Copy settings to globals. *TODO: Remove or move to appropriage class initializers + settings_to_globals(); + // Setup settings listeners + settings_setup_listeners(); + // Modify settings based on system configuration and compile options + settings_modify(); + + // Find partition serial number (Windows) or hardware serial (Mac) + mSerialNumber = generateSerialNumber(); + + // do any necessary set-up for accepting incoming SLURLs from apps + initSLURLHandler(); + + if(false == initHardwareTest()) + { + // Early out from user choice. + return false; + } + LL_INFOS("InitInfo") << "Hardware test initialization done." << LL_ENDL ; + + // Prepare for out-of-memory situations, during which we will crash on + // purpose and save a dump. +#if LL_WINDOWS && LL_RELEASE_FOR_DOWNLOAD && LL_USE_SMARTHEAP + MemSetErrorHandler(first_mem_error_handler); +#endif // LL_WINDOWS && LL_RELEASE_FOR_DOWNLOAD && LL_USE_SMARTHEAP + + // *Note: this is where gViewerStats used to be created. + + if (!initCache()) + { + LL_WARNS("InitInfo") << "Failed to init cache" << LL_ENDL; + std::ostringstream msg; + msg << LLTrans::getString("MBUnableToAccessFile"); + OSMessageBox(msg.str(),LLStringUtil::null,OSMB_OK); + return 0; + } + LL_INFOS("InitInfo") << "Cache initialization is done." << LL_ENDL ; + + // Initialize event recorder + LLViewerEventRecorder::createInstance(); + + // + // Initialize the window + // + gGLActive = true; + initWindow(); + LL_INFOS("InitInfo") << "Window is initialized." << LL_ENDL ; + + // writeSystemInfo can be called after window is initialized (gViewerWindow non-null) + writeSystemInfo(); + + // initWindow also initializes the Feature List, so now we can initialize this global. + LLCubeMap::sUseCubeMaps = LLFeatureManager::getInstance()->isFeatureAvailable("RenderCubeMap"); + + // call all self-registered classes + LLInitClassList::instance().fireCallbacks(); + + LLFolderViewItem::initClass(); // SJB: Needs to happen after initWindow(), not sure why but related to fonts + + gGLManager.getGLInfo(gDebugInfo); + gGLManager.printGLInfoString(); + + // If we don't have the right GL requirements, exit. + if (!gGLManager.mHasRequirements) + { + // already handled with a MBVideoDrvErr + return 0; + } + + // Without SSE2 support we will crash almost immediately, warn here. + if (!gSysCPU.hasSSE2()) + { + // can't use an alert here since we're exiting and + // all hell breaks lose. + OSMessageBox( + LLNotifications::instance().getGlobalString("UnsupportedCPUSSE2"), + LLStringUtil::null, + OSMB_OK); + return 0; + } + + // alert the user if they are using unsupported hardware + if(!gSavedSettings.getBOOL("AlertedUnsupportedHardware")) + { + bool unsupported = false; + LLSD args; + std::string minSpecs; + + // get cpu data from xml + std::stringstream minCPUString(LLNotifications::instance().getGlobalString("UnsupportedCPUAmount")); + S32 minCPU = 0; + minCPUString >> minCPU; + + // get RAM data from XML + std::stringstream minRAMString(LLNotifications::instance().getGlobalString("UnsupportedRAMAmount")); + U64Bytes minRAM; + minRAMString >> minRAM; + + if(!LLFeatureManager::getInstance()->isGPUSupported() && LLFeatureManager::getInstance()->getGPUClass() != GPU_CLASS_UNKNOWN) + { + minSpecs += LLNotifications::instance().getGlobalString("UnsupportedGPU"); + minSpecs += "\n"; + unsupported = true; + } + if(gSysCPU.getMHz() < minCPU) + { + minSpecs += LLNotifications::instance().getGlobalString("UnsupportedCPU"); + minSpecs += "\n"; + unsupported = true; + } + if(gSysMemory.getPhysicalMemoryKB() < minRAM) + { + minSpecs += LLNotifications::instance().getGlobalString("UnsupportedRAM"); + minSpecs += "\n"; + unsupported = true; + } + + if (LLFeatureManager::getInstance()->getGPUClass() == GPU_CLASS_UNKNOWN) + { + LLNotificationsUtil::add("UnknownGPU"); + } + + if(unsupported) + { + if(!gSavedSettings.controlExists("WarnUnsupportedHardware") + || gSavedSettings.getBOOL("WarnUnsupportedHardware")) + { + args["MINSPECS"] = minSpecs; + LLNotificationsUtil::add("UnsupportedHardware", args ); + } + + } + } + +#if LL_WINDOWS && ADDRESS_SIZE == 64 + if (gGLManager.mIsIntel) + { + // Check intel driver's version + // Ex: "3.1.0 - Build 8.15.10.2559"; + std::string version = ll_safe_string((const char *)glGetString(GL_VERSION)); + + const boost::regex is_intel_string("[0-9].[0-9].[0-9] - Build [0-9]{1,2}.[0-9]{2}.[0-9]{2}.[0-9]{4}"); + + if (boost::regex_search(version, is_intel_string)) + { + // Valid string, extract driver version + std::size_t found = version.find("Build "); + std::string driver = version.substr(found + 6); + S32 v1, v2, v3, v4; + S32 count = sscanf(driver.c_str(), "%d.%d.%d.%d", &v1, &v2, &v3, &v4); + if (count > 0 && v1 <= 10) + { + LL_INFOS("AppInit") << "Detected obsolete intel driver: " << driver << LL_ENDL; + + if (!gViewerWindow->getInitAlert().empty() // graphic initialization crashed on last run + || LLVersionInfo::getInstance()->getChannelAndVersion() != gLastRunVersion // viewer was updated + || mNumSessions % 20 == 0 //periodically remind user to update driver + ) + { + LLUIString details = LLNotifications::instance().getGlobalString("UnsupportedIntelDriver"); + std::string gpu_name = ll_safe_string((const char *)glGetString(GL_RENDERER)); + LL_INFOS("AppInit") << "Notifying user about obsolete intel driver for " << gpu_name << LL_ENDL; + details.setArg("[VERSION]", driver); + details.setArg("[GPUNAME]", gpu_name); + S32 button = OSMessageBox(details.getString(), + LLStringUtil::null, + OSMB_YESNO); + if (OSBTN_YES == button && gViewerWindow) + { + std::string url = LLWeb::escapeURL(LLTrans::getString("IntelDriverPage")); + if (gViewerWindow->getWindow()) + { + gViewerWindow->getWindow()->spawnWebBrowser(url, false); + } + } + } + } + } + } +#endif + + // Obsolete? mExpectedGLVersion is always zero +#if LL_WINDOWS + if (gGLManager.mGLVersion < LLFeatureManager::getInstance()->getExpectedGLVersion()) + { + std::string url; + if (gGLManager.mIsIntel) + { + url = LLTrans::getString("IntelDriverPage"); + } + else if (gGLManager.mIsNVIDIA) + { + url = LLTrans::getString("NvidiaDriverPage"); + } + else if (gGLManager.mIsAMD) + { + url = LLTrans::getString("AMDDriverPage"); + } + + if (!url.empty()) + { + LLNotificationsUtil::add("OldGPUDriver", LLSD().with("URL", url)); + } + } +#endif + + + // save the graphics card + gDebugInfo["GraphicsCard"] = LLFeatureManager::getInstance()->getGPUString(); + + // Save the current version to the prefs file + gSavedSettings.setString("LastRunVersion", + LLVersionInfo::instance().getChannelAndVersion()); + + gSimLastTime = gRenderStartTime.getElapsedTimeF32(); + gSimFrames = (F32)gFrameCount; + + if (gSavedSettings.getBOOL("JoystickEnabled")) + { + LLViewerJoystick::getInstance()->init(false); + } + + try { + initializeSecHandler(); + } + catch (LLProtectedDataException&) + { + LLNotificationsUtil::add("CorruptedProtectedDataStore"); + } + + gGLActive = false; + +#if LL_RELEASE_FOR_DOWNLOAD + // Skip updater if this is a non-interactive instance + if (!gSavedSettings.getBOOL("CmdLineSkipUpdater") && !gNonInteractive) + { + LLProcess::Params updater; + updater.desc = "updater process"; + // Because it's the updater, it MUST persist beyond the lifespan of the + // viewer itself. + updater.autokill = false; + std::string updater_file; +#if LL_WINDOWS + updater_file = "SLVersionChecker.exe"; + updater.executable = gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, updater_file); +#elif LL_DARWIN + updater_file = "SLVersionChecker"; + updater.executable = gDirUtilp->add(gDirUtilp->getAppRODataDir(), "updater", updater_file); +#else + updater_file = "SLVersionChecker"; + updater.executable = gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, updater_file); +#endif + // add LEAP mode command-line argument to whichever of these we selected + updater.args.add("leap"); + // UpdaterServiceSettings + if (gSavedSettings.getBOOL("FirstLoginThisInstall")) + { + // Befor first login, treat this as 'manual' updates, + // updater won't install anything, but required updates + updater.args.add("0"); + } + else + { + updater.args.add(stringize(gSavedSettings.getU32("UpdaterServiceSetting"))); + } + // channel + updater.args.add(LLVersionInfo::instance().getChannel()); + // testok + updater.args.add(stringize(gSavedSettings.getBOOL("UpdaterWillingToTest"))); + // ForceAddressSize + updater.args.add(stringize(gSavedSettings.getU32("ForceAddressSize"))); + + try + { + // Run the updater. An exception from launching the updater should bother us. + LLLeap::create(updater, true); + mUpdaterNotFound = false; + } + catch (...) + { + LLUIString details = LLNotifications::instance().getGlobalString("LLLeapUpdaterFailure"); + details.setArg("[UPDATER_APP]", updater_file); + OSMessageBox( + details.getString(), + LLStringUtil::null, + OSMB_OK); + mUpdaterNotFound = true; + } + } + else + { + LL_WARNS("InitInfo") << "Skipping updater check." << LL_ENDL; + } +#endif //LL_RELEASE_FOR_DOWNLOAD + + { + // Iterate over --leap command-line options. But this is a bit tricky: if + // there's only one, it won't be an array at all. + LLSD LeapCommand(gSavedSettings.getLLSD("LeapCommand")); + LL_DEBUGS("InitInfo") << "LeapCommand: " << LeapCommand << LL_ENDL; + if (LeapCommand.isDefined() && !LeapCommand.isArray()) + { + // If LeapCommand is actually a scalar value, make an array of it. + // Have to do it in two steps because LeapCommand.append(LeapCommand) + // trashes content! :-P + LLSD item(LeapCommand); + LeapCommand.append(item); + } + for (const auto& leap : llsd::inArray(LeapCommand)) + { + LL_INFOS("InitInfo") << "processing --leap \"" << leap << '"' << LL_ENDL; + // We don't have any better description of this plugin than the + // user-specified command line. Passing "" causes LLLeap to derive a + // description from the command line itself. + // Suppress LLLeap::Error exception: trust LLLeap's own logging. We + // don't consider any one --leap command mission-critical, so if one + // fails, log it, shrug and carry on. + LLLeap::create("", leap, false); // exception=false + } + } + + if (gSavedSettings.getBOOL("QAMode") && gSavedSettings.getS32("QAModeEventHostPort") > 0) + { + LL_WARNS("InitInfo") << "QAModeEventHostPort DEPRECATED: " + << "lleventhost no longer supported as a dynamic library" + << LL_ENDL; + } + + LLTextUtil::TextHelpers::iconCallbackCreationFunction = create_text_segment_icon_from_url_match; + + //EXT-7013 - On windows for some locale (Japanese) standard + //datetime formatting functions didn't support some parameters such as "weekday". + //Names for days and months localized in xml are also useful for Polish locale(STORM-107). + std::string language = gSavedSettings.getString("Language"); + if(language == "ja" || language == "pl") + { + LLStringOps::setupWeekDaysNames(LLTrans::getString("dateTimeWeekdaysNames")); + LLStringOps::setupWeekDaysShortNames(LLTrans::getString("dateTimeWeekdaysShortNames")); + LLStringOps::setupMonthNames(LLTrans::getString("dateTimeMonthNames")); + LLStringOps::setupMonthShortNames(LLTrans::getString("dateTimeMonthShortNames")); + LLStringOps::setupDayFormat(LLTrans::getString("dateTimeDayFormat")); + + LLStringOps::sAM = LLTrans::getString("dateTimeAM"); + LLStringOps::sPM = LLTrans::getString("dateTimePM"); + } + + LLAgentLanguage::init(); + + /// Tell the Coprocedure manager how to discover and store the pool sizes + // what I wanted + LLCoprocedureManager::getInstance()->setPropertyMethods( + boost::bind(&LLControlGroup::getU32, boost::ref(gSavedSettings), _1), + boost::bind(&LLControlGroup::declareU32, boost::ref(gSavedSettings), _1, _2, _3, LLControlVariable::PERSIST_ALWAYS)); + + // TODO: consider moving proxy initialization here or LLCopocedureManager after proxy initialization, may be implement + // some other protection to make sure we don't use network before initializng proxy + + /*----------------------------------------------------------------------*/ + // nat 2016-06-29 moved the following here from the former mainLoop(). + mMainloopTimeout = new LLWatchdogTimeout(); + + // Create IO Pump to use for HTTP Requests. + gServicePump = new LLPumpIO(gAPRPoolp); + + // Note: this is where gLocalSpeakerMgr and gActiveSpeakerMgr used to be instantiated. + + LLVoiceChannel::initClass(); + LLVoiceClient::initParamSingleton(gServicePump); + LLVoiceChannel::setCurrentVoiceChannelChangedCallback(boost::bind(&LLFloaterIMContainer::onCurrentChannelChanged, _1), true); + + joystick = LLViewerJoystick::getInstance(); + joystick->setNeedsReset(true); + /*----------------------------------------------------------------------*/ + // Load User's bindings + loadKeyBindings(); + + //LLSimpleton creations + LLEnvironment::createInstance(); + LLWorld::createInstance(); + LLSelectMgr::createInstance(); + LLViewerCamera::createInstance(); + +#if LL_WINDOWS + if (!mSecondInstance) + { + gDirUtilp->deleteDirAndContents(gDirUtilp->getDumpLogsDirPath()); + } +#endif + + return true; +} + +void LLAppViewer::initMaxHeapSize() +{ + //set the max heap size. + //here is some info regarding to the max heap size: + //------------------------------------------------------------------------------------------ + // OS | setting | SL address bits | max manageable memory space | max heap size + // Win 32 | default | 32-bit | 2GB | < 1.7GB + // Win 32 | /3G | 32-bit | 3GB | < 1.7GB or 2.7GB + //Linux 32 | default | 32-bit | 3GB | < 2.7GB + //Linux 32 |HUGEMEM | 32-bit | 4GB | < 3.7GB + //64-bit OS |default | 32-bit | 4GB | < 3.7GB + //64-bit OS |default | 64-bit | N/A (> 4GB) | N/A (> 4GB) + //------------------------------------------------------------------------------------------ + //currently SL is built under 32-bit setting, we set its max heap size no more than 1.6 GB. + + #ifndef LL_X86_64 + F32Gigabytes max_heap_size_gb = (F32Gigabytes)gSavedSettings.getF32("MaxHeapSize") ; +#else + F32Gigabytes max_heap_size_gb = (F32Gigabytes)gSavedSettings.getF32("MaxHeapSize64"); +#endif + + LLMemory::initMaxHeapSizeGB(max_heap_size_gb); +} + + +// externally visible timers +LLTrace::BlockTimerStatHandle FTM_FRAME("Frame"); + +bool LLAppViewer::frame() +{ + bool ret = false; + + if (gSimulateMemLeak) + { + try + { + ret = doFrame(); + } + catch (const LLContinueError&) + { + LOG_UNHANDLED_EXCEPTION(""); + } + catch (std::bad_alloc&) + { + LLMemory::logMemoryInfo(true); + LLFloaterMemLeak* mem_leak_instance = LLFloaterReg::findTypedInstance("mem_leaking"); + if (mem_leak_instance) + { + mem_leak_instance->stop(); + } + LL_WARNS() << "Bad memory allocation in LLAppViewer::frame()!" << LL_ENDL; + } + } + else + { + try + { + ret = doFrame(); + } + catch (const LLContinueError&) + { + LOG_UNHANDLED_EXCEPTION(""); + } + } + + return ret; +} + +bool LLAppViewer::doFrame() +{ + LL_RECORD_BLOCK_TIME(FTM_FRAME); + { + // and now adjust the visuals from previous frame. + if(LLPerfStats::tunables.userAutoTuneEnabled && LLPerfStats::tunables.tuningFlag != LLPerfStats::Tunables::Nothing) + { + LLPerfStats::tunables.applyUpdates(); + } + + LLPerfStats::RecordSceneTime T (LLPerfStats::StatType_t::RENDER_FRAME); + if (!LLWorld::instanceExists()) + { + LLWorld::createInstance(); + } + + LLEventPump& mainloop(LLEventPumps::instance().obtain("mainloop")); + LLSD newFrame; + { + LLPerfStats::RecordSceneTime T (LLPerfStats::StatType_t::RENDER_IDLE); // perf stats + { + LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df LLTrace"); + if (LLFloaterReg::instanceVisible("block_timers")) + { + LLTrace::BlockTimer::processTimes(); + } + + LLTrace::get_frame_recording().nextPeriod(); + LLTrace::BlockTimer::logStats(); + } + + LLTrace::get_thread_recorder()->pullFromChildren(); + + //clear call stack records + LL_CLEAR_CALLSTACKS(); + } + { + { + LLPerfStats::RecordSceneTime T(LLPerfStats::StatType_t::RENDER_IDLE); // ensure we have the entire top scope of frame covered (input event and coro) + LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df processMiscNativeEvents") + pingMainloopTimeout("Main:MiscNativeWindowEvents"); + + if (gViewerWindow) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_APP("System Messages"); + gViewerWindow->getWindow()->processMiscNativeEvents(); + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df gatherInput") + pingMainloopTimeout("Main:GatherInput"); + } + + if (gViewerWindow) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_APP("System Messages"); + if (!restoreErrorTrap()) + { + LL_WARNS() << " Someone took over my signal/exception handler (post messagehandling)!" << LL_ENDL; + } + + gViewerWindow->getWindow()->gatherInput(); + } + + //memory leaking simulation + if (gSimulateMemLeak) + { + LLFloaterMemLeak* mem_leak_instance = + LLFloaterReg::findTypedInstance("mem_leaking"); + if (mem_leak_instance) + { + mem_leak_instance->idle(); + } + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df mainloop") + // canonical per-frame event + mainloop.post(newFrame); + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df suspend") + // give listeners a chance to run + llcoro::suspend(); + // if one of our coroutines threw an uncaught exception, rethrow it now + LLCoros::instance().rethrow(); + } + } + + if (!LLApp::isExiting()) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_APP( "df JoystickKeyboard" ) + pingMainloopTimeout("Main:JoystickKeyboard"); + + // Scan keyboard for movement keys. Command keys and typing + // are handled by windows callbacks. Don't do this until we're + // done initializing. JC + if (gViewerWindow + && (gHeadlessClient || gViewerWindow->getWindow()->getVisible()) + && gViewerWindow->getActive() + && !gViewerWindow->getWindow()->getMinimized() + && LLStartUp::getStartupState() == STATE_STARTED + && (gHeadlessClient || !gViewerWindow->getShowProgress()) + && !gFocusMgr.focusLocked()) + { + LLPerfStats::RecordSceneTime T (LLPerfStats::StatType_t::RENDER_IDLE); + joystick->scanJoystick(); + gKeyboard->scanKeyboard(); + gViewerInput.scanMouse(); + } + + // Update state based on messages, user input, object idle. + { + { + LL_PROFILE_ZONE_NAMED_CATEGORY_APP( "df pauseMainloopTimeout" ) + pauseMainloopTimeout(); // *TODO: Remove. Messages shouldn't be stalling for 20+ seconds! + } + + { + LLPerfStats::RecordSceneTime T (LLPerfStats::StatType_t::RENDER_IDLE); + LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df idle"); + idle(); + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_APP( "df resumeMainloopTimeout" ) + resumeMainloopTimeout(); + } + } + + if (gDoDisconnect && (LLStartUp::getStartupState() == STATE_STARTED)) + { + pauseMainloopTimeout(); + saveFinalSnapshot(); + + if (LLVoiceClient::instanceExists()) + { + LLVoiceClient::getInstance()->terminate(); + } + + disconnectViewer(); + resumeMainloopTimeout(); + } + + // Render scene. + // *TODO: Should we run display() even during gHeadlessClient? DK 2011-02-18 + if (!LLApp::isExiting() && !gHeadlessClient && gViewerWindow) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df Display"); + pingMainloopTimeout("Main:Display"); + gGLActive = true; + + display(); + + { + LLPerfStats::RecordSceneTime T(LLPerfStats::StatType_t::RENDER_IDLE); + LL_PROFILE_ZONE_NAMED_CATEGORY_APP("df Snapshot"); + pingMainloopTimeout("Main:Snapshot"); + gPipeline.mReflectionMapManager.update(); + LLFloaterSnapshot::update(); // take snapshots + LLFloaterSimpleSnapshot::update(); + gGLActive = false; + } + + if (LLViewerStatsRecorder::instanceExists()) + { + LLViewerStatsRecorder::instance().idle(); + } + } + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_APP( "df pauseMainloopTimeout" ) + pingMainloopTimeout("Main:Sleep"); + + pauseMainloopTimeout(); + } + + // Sleep and run background threads + { + //LL_RECORD_BLOCK_TIME(SLEEP2); + LL_PROFILE_ZONE_WARN( "Sleep2" ) + + // yield some time to the os based on command line option + static LLCachedControl yield_time(gSavedSettings, "YieldTime", -1); + if(yield_time >= 0) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_APP("Yield"); + LL_PROFILE_ZONE_NUM( yield_time ) + ms_sleep(yield_time); + } + + if (gNonInteractive) + { + S32 non_interactive_ms_sleep_time = 100; + LLAppViewer::getTextureCache()->pause(); + ms_sleep(non_interactive_ms_sleep_time); + } + + // yield cooperatively when not running as foreground window + // and when not quiting (causes trouble at mac's cleanup stage) + if (!LLApp::isExiting() + && ((gViewerWindow && !gViewerWindow->getWindow()->getVisible()) + || !gFocusMgr.getAppHasFocus())) + { + // Sleep if we're not rendering, or the window is minimized. + static LLCachedControl s_background_yield_time(gSavedSettings, "BackgroundYieldTime", 40); + S32 milliseconds_to_sleep = llclamp((S32)s_background_yield_time, 0, 1000); + // don't sleep when BackgroundYieldTime set to 0, since this will still yield to other threads + // of equal priority on Windows + if (milliseconds_to_sleep > 0) + { + LLPerfStats::RecordSceneTime T ( LLPerfStats::StatType_t::RENDER_SLEEP ); + ms_sleep(milliseconds_to_sleep); + // also pause worker threads during this wait period + LLAppViewer::getTextureCache()->pause(); + } + } + + if (mRandomizeFramerate) + { + ms_sleep(rand() % 200); + } + + if (mPeriodicSlowFrame + && (gFrameCount % 10 == 0)) + { + LL_INFOS() << "Periodic slow frame - sleeping 500 ms" << LL_ENDL; + ms_sleep(500); + } + + S32 total_work_pending = 0; + S32 total_io_pending = 0; + { + S32 work_pending = 0; + S32 io_pending = 0; + F32 max_time = llmin(gFrameIntervalSeconds.value() *10.f, 1.f); + + work_pending += updateTextureThreads(max_time); + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_APP("LFS Thread"); + io_pending += LLLFSThread::updateClass(1); + } + + if (io_pending > 1000) + { + ms_sleep(llmin(io_pending/100,100)); // give the lfs some time to catch up + } + + total_work_pending += work_pending ; + total_io_pending += io_pending ; + + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_APP( "df gMeshRepo" ) + gMeshRepo.update() ; + } + + if(!total_work_pending) //pause texture fetching threads if nothing to process. + { + LL_PROFILE_ZONE_NAMED_CATEGORY_APP( "df getTextureCache" ) + LLAppViewer::getTextureCache()->pause(); + LLAppViewer::getTextureFetch()->pause(); + } + if(!total_io_pending) //pause file threads if nothing to process. + { + LL_PROFILE_ZONE_NAMED_CATEGORY_APP( "df LLVFSThread" ) + LLLFSThread::sLocal->pause(); + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_APP( "df resumeMainloopTimeout" ) + resumeMainloopTimeout(); + } + pingMainloopTimeout("Main:End"); + } + } + + if (LLApp::isExiting()) + { + // Save snapshot for next time, if we made it through initialization + if (STATE_STARTED == LLStartUp::getStartupState()) + { + saveFinalSnapshot(); + } + + if (LLVoiceClient::instanceExists()) + { + LLVoiceClient::getInstance()->terminate(); + } + + delete gServicePump; + gServicePump = NULL; + + destroyMainloopTimeout(); + + LL_INFOS() << "Exiting main_loop" << LL_ENDL; + } + }LLPerfStats::StatsRecorder::endFrame(); + LL_PROFILER_FRAME_END + + return ! LLApp::isRunning(); +} + +S32 LLAppViewer::updateTextureThreads(F32 max_time) +{ + S32 work_pending = 0; + { + LL_PROFILE_ZONE_NAMED_CATEGORY_APP("Texture Cache"); + work_pending += LLAppViewer::getTextureCache()->update(max_time); // unpauses the texture cache thread + } + { + LL_PROFILE_ZONE_NAMED_CATEGORY_APP("Image Decode"); + work_pending += LLAppViewer::getImageDecodeThread()->update(max_time); // unpauses the image thread + } + { + LL_PROFILE_ZONE_NAMED_CATEGORY_APP("Image Fetch"); + work_pending += LLAppViewer::getTextureFetch()->update(max_time); // unpauses the texture fetch thread + } + return work_pending; +} + +void LLAppViewer::flushLFSIO() +{ + S32 pending = LLLFSThread::updateClass(0); + if (pending > 0) + { + LL_INFOS() << "Waiting for pending IO to finish: " << pending << LL_ENDL; + while (1) + { + pending = LLLFSThread::updateClass(0); + if (!pending) + { + break; + } + ms_sleep(100); + } + } +} + +bool LLAppViewer::cleanup() +{ + LLAtmosphere::cleanupClass(); + + //ditch LLVOAvatarSelf instance + gAgentAvatarp = NULL; + + LLNotifications::instance().clear(); + + // workaround for DEV-35406 crash on shutdown + LLEventPumps::instance().reset(true); + + //dump scene loading monitor results + if (LLSceneMonitor::instanceExists()) + { + if (!isSecondInstance()) + { + std::string dump_path = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "scene_monitor_results.csv"); + LLSceneMonitor::instance().dumpToFile(dump_path); + } + LLSceneMonitor::deleteSingleton(); + } + + // There used to be an 'if (LLFastTimerView::sAnalyzePerformance)' block + // here, completely redundant with the one that occurs later in this same + // function. Presumably the duplication was due to an automated merge gone + // bad. Not knowing which instance to prefer, we chose to retain the later + // one because it happens just after mFastTimerLogThread is deleted. This + // comment is in case we guessed wrong, so we can move it here instead. + +#if LL_LINUX + // remove any old breakpad minidump files from the log directory + if (! isError()) + { + std::string logdir = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ""); + gDirUtilp->deleteFilesInDir(logdir, "*-*-*-*-*.dmp"); + } +#endif + + // Kill off LLLeap objects. We can find them all because LLLeap is derived + // from LLInstanceTracker. + LLLeap::instance_snapshot().deleteAll(); + + //flag all elements as needing to be destroyed immediately + // to ensure shutdown order + LLMortician::setZealous(true); + + // Give any remaining SLPlugin instances a chance to exit cleanly. + LLPluginProcessParent::shutdown(); + + disconnectViewer(); + LLViewerCamera::deleteSingleton(); + + LL_INFOS() << "Viewer disconnected" << LL_ENDL; + + if (gKeyboard) + { + gKeyboard->resetKeys(); + } + + display_cleanup(); + + release_start_screen(); // just in case + + LLError::logToFixedBuffer(NULL); // stop the fixed buffer recorder + + LL_INFOS() << "Cleaning Up" << LL_ENDL; + + // shut down mesh streamer + gMeshRepo.shutdown(); + + // shut down Havok + LLPhysicsExtensions::quitSystem(); + + // Must clean up texture references before viewer window is destroyed. + if(LLHUDManager::instanceExists()) + { + LLHUDManager::getInstance()->updateEffects(); + LLHUDObject::updateAll(); + LLHUDManager::getInstance()->cleanupEffects(); + LLHUDObject::cleanupHUDObjects(); + LL_INFOS() << "HUD Objects cleaned up" << LL_ENDL; + } + + LLKeyframeDataCache::clear(); + + // End TransferManager before deleting systems it depends on (Audio, AssetStorage) +#if 0 // this seems to get us stuck in an infinite loop... + gTransferManager.cleanup(); +#endif + + // Note: this is where gWorldMap used to be deleted. + + // Note: this is where gHUDManager used to be deleted. + if(LLHUDManager::instanceExists()) + { + LLHUDManager::getInstance()->shutdownClass(); + } + + delete gAssetStorage; + gAssetStorage = NULL; + + LLPolyMesh::freeAllMeshes(); + + LLStartUp::cleanupNameCache(); + + // Note: this is where gLocalSpeakerMgr and gActiveSpeakerMgr used to be deleted. + + if (LLWorldMap::instanceExists()) + { + LLWorldMap::getInstance()->reset(); // release any images + } + + LLCalc::cleanUp(); + + LL_INFOS() << "Global stuff deleted" << LL_ENDL; + + if (gAudiop) + { + LL_INFOS() << "Shutting down audio" << LL_ENDL; + + // be sure to stop the internet stream cleanly BEFORE destroying the interface to stop it. + gAudiop->stopInternetStream(); + // shut down the streaming audio sub-subsystem first, in case it relies on not outliving the general audio subsystem. + LLStreamingAudioInterface *sai = gAudiop->getStreamingAudioImpl(); + delete sai; + gAudiop->setStreamingAudioImpl(NULL); + + // shut down the audio subsystem + gAudiop->shutdown(); + + delete gAudiop; + gAudiop = NULL; + } + + // Note: this is where LLFeatureManager::getInstance()-> used to be deleted. + + // Patch up settings for next time + // Must do this before we delete the viewer window, + // such that we can suck rectangle information out of + // it. + cleanupSavedSettings(); + LL_INFOS() << "Settings patched up" << LL_ENDL; + + // delete some of the files left around in the cache. + removeCacheFiles("*.wav"); + removeCacheFiles("*.tmp"); + removeCacheFiles("*.lso"); + removeCacheFiles("*.out"); + removeCacheFiles("*.dsf"); + removeCacheFiles("*.bodypart"); + removeCacheFiles("*.clothing"); + + LL_INFOS() << "Cache files removed" << LL_ENDL; + + LL_INFOS() << "Shutting down Views" << LL_ENDL; + + // Destroy the UI + if( gViewerWindow) + gViewerWindow->shutdownViews(); + + LL_INFOS() << "Cleaning up Inventory" << LL_ENDL; + + // Cleanup Inventory after the UI since it will delete any remaining observers + // (Deleted observers should have already removed themselves) + gInventory.cleanupInventory(); + + LLCoros::getInstance()->printActiveCoroutines(); + + LL_INFOS() << "Cleaning up Selections" << LL_ENDL; + + // Clean up selection managers after UI is destroyed, as UI may be observing them. + // Clean up before GL is shut down because we might be holding on to objects with texture references + LLSelectMgr::cleanupGlobals(); + + LL_INFOS() << "Shutting down OpenGL" << LL_ENDL; + + // Shut down OpenGL + if( gViewerWindow) + { + gViewerWindow->shutdownGL(); + + // Destroy window, and make sure we're not fullscreen + // This may generate window reshape and activation events. + // Therefore must do this before destroying the message system. + delete gViewerWindow; + gViewerWindow = NULL; + LL_INFOS() << "ViewerWindow deleted" << LL_ENDL; + } + + LLSplashScreen::show(); + LLSplashScreen::update(LLTrans::getString("ShuttingDown")); + + LL_INFOS() << "Cleaning up Keyboard & Joystick" << LL_ENDL; + + // viewer UI relies on keyboard so keep it aound until viewer UI isa gone + delete gKeyboard; + gKeyboard = NULL; + + if (LLViewerJoystick::instanceExists()) + { + // Turn off Space Navigator and similar devices + LLViewerJoystick::getInstance()->terminate(); + } + + LL_INFOS() << "Cleaning up Objects" << LL_ENDL; + + LLViewerObject::cleanupVOClasses(); + + SUBSYSTEM_CLEANUP(LLAvatarAppearance); + + SUBSYSTEM_CLEANUP(LLPostProcess); + + LLTracker::cleanupInstance(); + + // *FIX: This is handled in LLAppViewerWin32::cleanup(). + // I'm keeping the comment to remember its order in cleanup, + // in case of unforseen dependency. + //#if LL_WINDOWS + // gDXHardware.cleanup(); + //#endif // LL_WINDOWS + + LLVolumeMgr* volume_manager = LLPrimitive::getVolumeManager(); + if (!volume_manager->cleanup()) + { + LL_WARNS() << "Remaining references in the volume manager!" << LL_ENDL; + } + LLPrimitive::cleanupVolumeManager(); + + LL_INFOS() << "Additional Cleanup..." << LL_ENDL; + + LLViewerParcelMgr::cleanupGlobals(); + + // *Note: this is where gViewerStats used to be deleted. + + //end_messaging_system(); + + LLPrimitive::cleanupVolumeManager(); + SUBSYSTEM_CLEANUP(LLWorldMapView); + SUBSYSTEM_CLEANUP(LLFolderViewItem); + + LL_INFOS() << "Saving Data" << LL_ENDL; + + // Store the time of our current logoff + gSavedPerAccountSettings.setU32("LastLogoff", time_corrected()); + + if (LLEnvironment::instanceExists()) + { + //Store environment settings if necessary + LLEnvironment::getInstance()->saveToSettings(); + } + + // Must do this after all panels have been deleted because panels that have persistent rects + // save their rects on delete. + gSavedSettings.saveToFile(gSavedSettings.getString("ClientSettingsFile"), true); + + LLUIColorTable::instance().saveUserSettings(); + + // PerAccountSettingsFile should be empty if no user has been logged on. + // *FIX:Mani This should get really saved in a "logoff" mode. + if (gSavedSettings.getString("PerAccountSettingsFile").empty()) + { + LL_INFOS() << "Not saving per-account settings; don't know the account name yet." << LL_ENDL; + } + // Only save per account settings if the previous login succeeded, otherwise + // we might end up with a cleared out settings file in case a previous login + // failed after loading per account settings. + else if (!mSavePerAccountSettings) + { + LL_INFOS() << "Not saving per-account settings; last login was not successful." << LL_ENDL; + } + else + { + gSavedPerAccountSettings.saveToFile(gSavedSettings.getString("PerAccountSettingsFile"), true); + LL_INFOS() << "Saved settings" << LL_ENDL; + + if (LLViewerParcelAskPlay::instanceExists()) + { + LLViewerParcelAskPlay::getInstance()->saveSettings(); + } + } + + std::string warnings_settings_filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, getSettingsFilename("Default", "Warnings")); + gWarningSettings.saveToFile(warnings_settings_filename, true); + + // Save URL history file + LLURLHistory::saveFile("url_history.xml"); + + // save mute list. gMuteList used to also be deleted here too. + if (gAgent.isInitialized() && LLMuteList::instanceExists()) + { + LLMuteList::getInstance()->cache(gAgent.getID()); + } + + //save call log list + if (LLConversationLog::instanceExists()) + { + LLConversationLog::instance().cache(); + } + + clearSecHandler(); + + if (mPurgeCacheOnExit) + { + LL_INFOS() << "Purging all cache files on exit" << LL_ENDL; + gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE,""), "*.*"); + } + + writeDebugInfo(); + + LLLocationHistory::getInstance()->save(); + + LLAvatarIconIDCache::getInstance()->save(); + + // Stop the plugin read thread if it's running. + LLPluginProcessParent::setUseReadThread(false); + + LL_INFOS() << "Shutting down Threads" << LL_ENDL; + + // Let threads finish + LLTimer idleTimer; + idleTimer.reset(); + const F64 max_idle_time = 5.f; // 5 seconds + while(1) + { + S32 pending = 0; + pending += LLAppViewer::getTextureCache()->update(1); // unpauses the worker thread + pending += LLAppViewer::getImageDecodeThread()->update(1); // unpauses the image thread + pending += LLAppViewer::getTextureFetch()->update(1); // unpauses the texture fetch thread + pending += LLLFSThread::updateClass(0); + F64 idle_time = idleTimer.getElapsedTimeF64(); + if(!pending) + { + break ; //done + } + else if(idle_time >= max_idle_time) + { + LL_WARNS() << "Quitting with pending background tasks." << LL_ENDL; + break; + } + } + + if (mPurgeUserDataOnExit) + { + // Ideally we should not save anything from this session since it is going to be purged now, + // but this is a very 'rare' case (user deleting himself), not worth overcomplicating 'save&cleanup' code + std::string user_path = gDirUtilp->getOSUserAppDir() + gDirUtilp->getDirDelimiter() + LLStartUp::getUserId(); + gDirUtilp->deleteDirAndContents(user_path); + } + + // Delete workers first + // shotdown all worker threads before deleting them in case of co-dependencies + mAppCoreHttp.requestStop(); + sTextureFetch->shutdown(); + sTextureCache->shutdown(); + sImageDecodeThread->shutdown(); + sPurgeDiskCacheThread->shutdown(); + if (mGeneralThreadPool) + { + mGeneralThreadPool->close(); + } + + sTextureFetch->shutDownTextureCacheThread() ; + LLLFSThread::sLocal->shutdown(); + + LL_INFOS() << "Shutting down message system" << LL_ENDL; + end_messaging_system(); + + // Non-LLCurl libcurl library + mAppCoreHttp.cleanup(); + + SUBSYSTEM_CLEANUP(LLFilePickerThread); + SUBSYSTEM_CLEANUP(LLDirPickerThread); + + //MUST happen AFTER SUBSYSTEM_CLEANUP(LLCurl) + delete sTextureCache; + sTextureCache = NULL; + if (sTextureFetch) + { + sTextureFetch->shutdown(); + sTextureFetch->waitOnPending(); + delete sTextureFetch; + sTextureFetch = NULL; + } + delete sImageDecodeThread; + sImageDecodeThread = NULL; + delete mFastTimerLogThread; + mFastTimerLogThread = NULL; + delete sPurgeDiskCacheThread; + sPurgeDiskCacheThread = NULL; + delete mGeneralThreadPool; + mGeneralThreadPool = NULL; + + if (LLFastTimerView::sAnalyzePerformance) + { + LL_INFOS() << "Analyzing performance" << LL_ENDL; + + std::string baseline_name = LLTrace::BlockTimer::sLogName + "_baseline.slp"; + std::string current_name = LLTrace::BlockTimer::sLogName + ".slp"; + std::string report_name = LLTrace::BlockTimer::sLogName + "_report.csv"; + + LLFastTimerView::doAnalysis( + gDirUtilp->getExpandedFilename(LL_PATH_LOGS, baseline_name), + gDirUtilp->getExpandedFilename(LL_PATH_LOGS, current_name), + gDirUtilp->getExpandedFilename(LL_PATH_LOGS, report_name)); + } + + SUBSYSTEM_CLEANUP(LLMetricPerformanceTesterBasic) ; + + LL_INFOS() << "Cleaning up Media and Textures" << LL_ENDL; + + //Note: + //SUBSYSTEM_CLEANUP(LLViewerMedia) has to be put before gTextureList.shutdown() + //because some new image might be generated during cleaning up media. --bao + gTextureList.shutdown(); // shutdown again in case a callback added something + LLUIImageList::getInstance()->cleanUp(); + + SUBSYSTEM_CLEANUP(LLImage); + SUBSYSTEM_CLEANUP(LLLFSThread); + + LL_INFOS() << "Misc Cleanup" << LL_ENDL; + + gSavedSettings.cleanup(); + LLUIColorTable::instance().clear(); + + LLWatchdog::getInstance()->cleanup(); + + LLViewerAssetStatsFF::cleanup(); + + // If we're exiting to launch an URL, do that here so the screen + // is at the right resolution before we launch IE. + if (!gLaunchFileOnQuit.empty()) + { + LL_INFOS() << "Launch file on quit." << LL_ENDL; +#if LL_WINDOWS + // Indicate an application is starting. + SetCursor(LoadCursor(NULL, IDC_WAIT)); +#endif + + // HACK: Attempt to wait until the screen res. switch is complete. + ms_sleep(1000); + + LLWeb::loadURLExternal( gLaunchFileOnQuit, false ); + LL_INFOS() << "File launched." << LL_ENDL; + } + // make sure nothing uses applyProxySettings by this point. + LL_INFOS() << "Cleaning up LLProxy." << LL_ENDL; + SUBSYSTEM_CLEANUP(LLProxy); + LLCore::LLHttp::cleanup(); + + ll_close_fail_log(); + + LLError::LLCallStacks::cleanup(); + + LLEnvironment::deleteSingleton(); + LLSelectMgr::deleteSingleton(); + LLViewerEventRecorder::deleteSingleton(); + LLWorld::deleteSingleton(); + LLVoiceClient::deleteSingleton(); + + // It's not at first obvious where, in this long sequence, a generic cleanup + // call OUGHT to go. So let's say this: as we migrate cleanup from + // explicit hand-placed calls into the generic mechanism, eventually + // all cleanup will get subsumed into the generic call. So the calls you + // still see above are calls that MUST happen before the generic cleanup + // kicks in. + + // This calls every remaining LLSingleton's cleanupSingleton() and + // deleteSingleton() methods. + LLSingletonBase::deleteAll(); + + LLSplashScreen::hide(); + + LL_INFOS() << "Goodbye!" << LL_ENDL; + + removeDumpDir(); + + // return 0; + return true; +} + +void LLAppViewer::initGeneralThread() +{ + if (mGeneralThreadPool) + { + return; + } + + mGeneralThreadPool = new LL::ThreadPool("General", 3); + mGeneralThreadPool->start(); +} + +bool LLAppViewer::initThreads() +{ + static const bool enable_threads = true; + + LLImage::initClass(gSavedSettings.getBOOL("TextureNewByteRange"),gSavedSettings.getS32("TextureReverseByteRange")); + + LLLFSThread::initClass(enable_threads && true); // TODO: fix crashes associated with this shutdo + + //auto configure thread count + LLSD threadCounts = gSavedSettings.getLLSD("ThreadPoolSizes"); + + // get the number of concurrent threads that can run + S32 cores = std::thread::hardware_concurrency(); + + U32 max_cores = gSavedSettings.getU32("EmulateCoreCount"); + if (max_cores != 0) + { + cores = llmin(cores, (S32) max_cores); + } + + // The only configurable thread count right now is ImageDecode + // The viewer typically starts around 8 threads not including image decode, + // so try to leave at least one core free + S32 image_decode_count = llclamp(cores - 9, 1, 8); + threadCounts["ImageDecode"] = image_decode_count; + gSavedSettings.setLLSD("ThreadPoolSizes", threadCounts); + + // Image decoding + LLAppViewer::sImageDecodeThread = new LLImageDecodeThread(enable_threads && true); + LLAppViewer::sTextureCache = new LLTextureCache(enable_threads && true); + LLAppViewer::sTextureFetch = new LLTextureFetch(LLAppViewer::getTextureCache(), + enable_threads && true, + app_metrics_qa_mode); + + // general task background thread (LLPerfStats, etc) + LLAppViewer::instance()->initGeneralThread(); + + LLAppViewer::sPurgeDiskCacheThread = new LLPurgeDiskCacheThread(); + + if (LLTrace::BlockTimer::sLog || LLTrace::BlockTimer::sMetricLog) + { + LLTrace::BlockTimer::setLogLock(new LLMutex()); + mFastTimerLogThread = new LLFastTimerLogThread(LLTrace::BlockTimer::sLogName); + mFastTimerLogThread->start(); + } + + // Mesh streaming and caching + gMeshRepo.init(); + + LLFilePickerThread::initClass(); + LLDirPickerThread::initClass(); + + // *FIX: no error handling here! + return true; +} + +void errorCallback(LLError::ELevel level, const std::string &error_string) +{ + if (level == LLError::LEVEL_ERROR) + { +#ifndef LL_RELEASE_FOR_DOWNLOAD + OSMessageBox(error_string, LLTrans::getString("MBFatalError"), OSMB_OK); +#endif + + gDebugInfo["FatalMessage"] = error_string; + // We're not already crashing -- we simply *intend* to crash. Since we + // haven't actually trashed anything yet, we can afford to write the whole + // static info file. + LLAppViewer::instance()->writeDebugInfo(); + } +} + +void errorMSG(const std::string& title_string, const std::string& message_string) +{ + if (!message_string.empty()) + { + OSMessageBox(message_string, title_string.empty() ? LLTrans::getString("MBFatalError") : title_string, OSMB_OK); + } +} + +void LLAppViewer::initLoggingAndGetLastDuration() +{ + // + // Set up logging defaults for the viewer + // + LLError::initForApplication( gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "") + ,gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "") + ); + LLError::addGenericRecorder(&errorCallback); + //LLError::setTimeFunction(getRuntime); + + LLError::LLUserWarningMsg::setHandler(errorMSG); + + + if (mSecondInstance) + { + LLFile::mkdir(gDirUtilp->getDumpLogsDirPath()); + + LLUUID uid; + uid.generate(); + LLError::logToFile(gDirUtilp->getDumpLogsDirPath(uid.asString() + ".log")); + } + else + { + // Remove the last ".old" log file. + std::string old_log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, + "SecondLife.old"); + LLFile::remove(old_log_file); + + // Get name of the log file + std::string log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, + "SecondLife.log"); + /* + * Before touching any log files, compute the duration of the last run + * by comparing the ctime of the previous start marker file with the ctime + * of the last log file. + */ + std::string start_marker_file_name = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, START_MARKER_FILE_NAME); + llstat start_marker_stat; + llstat log_file_stat; + std::ostringstream duration_log_stream; // can't log yet, so save any message for when we can below + int start_stat_result = LLFile::stat(start_marker_file_name, &start_marker_stat); + int log_stat_result = LLFile::stat(log_file, &log_file_stat); + if (0 == start_stat_result && 0 == log_stat_result) + { + int elapsed_seconds = log_file_stat.st_ctime - start_marker_stat.st_ctime; + // only report a last run time if the last viewer was the same version + // because this stat will be counted against this version + if (markerIsSameVersion(start_marker_file_name)) + { + gLastExecDuration = elapsed_seconds; + } + else + { + duration_log_stream << "start marker from some other version; duration is not reported"; + gLastExecDuration = -1; + } + } + else + { + // at least one of the LLFile::stat calls failed, so we can't compute the run time + duration_log_stream << "duration stat failure; start: " << start_stat_result << " log: " << log_stat_result; + gLastExecDuration = -1; // unknown + } + std::string duration_log_msg(duration_log_stream.str()); + + // Create a new start marker file for comparison with log file time for the next run + LLAPRFile start_marker_file; + start_marker_file.open(start_marker_file_name, LL_APR_WB); + if (start_marker_file.getFileHandle()) + { + recordMarkerVersion(start_marker_file); + start_marker_file.close(); + } + + // Rename current log file to ".old" + LLFile::rename(log_file, old_log_file); + + // Set the log file to SecondLife.log + LLError::logToFile(log_file); + LL_INFOS() << "Started logging to " << log_file << LL_ENDL; + if (!duration_log_msg.empty()) + { + LL_WARNS("MarkerFile") << duration_log_msg << LL_ENDL; + } + } +} + +bool LLAppViewer::loadSettingsFromDirectory(const std::string& location_key, + bool set_defaults) +{ + if (!mSettingsLocationList) + { + LL_ERRS() << "Invalid settings location list" << LL_ENDL; + } + + for (const SettingsGroup& group : mSettingsLocationList->groups) + { + // skip settings groups that aren't the one we requested + if (group.name() != location_key) continue; + + ELLPath path_index = (ELLPath)group.path_index(); + if(path_index <= LL_PATH_NONE || path_index >= LL_PATH_LAST) + { + LL_ERRS() << "Out of range path index in app_settings/settings_files.xml" << LL_ENDL; + return false; + } + + for (const SettingsFile& file : group.files) + { + LL_INFOS("Settings") << "Attempting to load settings for the group " << file.name() + << " - from location " << location_key << LL_ENDL; + + auto settings_group = LLControlGroup::getInstance(file.name); + if(!settings_group) + { + LL_WARNS("Settings") << "No matching settings group for name " << file.name() << LL_ENDL; + continue; + } + + std::string full_settings_path; + + if (file.file_name_setting.isProvided() + && gSavedSettings.controlExists(file.file_name_setting)) + { + // try to find filename stored in file_name_setting control + full_settings_path = gSavedSettings.getString(file.file_name_setting()); + if (full_settings_path.empty()) + { + continue; + } + else if (!gDirUtilp->fileExists(full_settings_path)) + { + // search in default path + full_settings_path = gDirUtilp->getExpandedFilename((ELLPath)path_index, full_settings_path); + } + } + else + { + // by default, use specified file name + full_settings_path = gDirUtilp->getExpandedFilename((ELLPath)path_index, file.file_name()); + } + + if(settings_group->loadFromFile(full_settings_path, set_defaults, file.persistent)) + { // success! + LL_INFOS("Settings") << "Loaded settings file " << full_settings_path << LL_ENDL; + } + else + { // failed to load + if(file.required) + { + LLError::LLUserWarningMsg::showMissingFiles(); + LL_ERRS() << "Error: Cannot load required settings file from: " << full_settings_path << LL_ENDL; + return false; + } + else + { + // only complain if we actually have a filename at this point + if (!full_settings_path.empty()) + { + LL_INFOS("Settings") << "Cannot load " << full_settings_path << " - No settings found." << LL_ENDL; + } + } + } + } + } + + return true; +} + +std::string LLAppViewer::getSettingsFilename(const std::string& location_key, + const std::string& file) +{ + for (const SettingsGroup& group : mSettingsLocationList->groups) + { + if (group.name() == location_key) + { + for (const SettingsFile& settings_file : group.files) + { + if (settings_file.name() == file) + { + return settings_file.file_name; + } + } + } + } + + return std::string(); +} + +void LLAppViewer::loadColorSettings() +{ + LLUIColorTable::instance().loadFromSettings(); +} + +namespace +{ + void handleCommandLineError(LLControlGroupCLP& clp) + { + LL_WARNS() << "Error parsing command line options. Command Line options ignored." << LL_ENDL; + + LL_INFOS() << "Command line usage:\n" << clp << LL_ENDL; + + OSMessageBox(STRINGIZE(LLTrans::getString("MBCmdLineError") << clp.getErrorMessage()), + LLStringUtil::null, + OSMB_OK); + } +} // anonymous namespace + +// Set a named control temporarily for this session, as when set via the command line --set option. +// Name can be specified as ".", with default group being Global. +bool tempSetControl(const std::string& name, const std::string& value) +{ + std::string name_part; + std::string group_part; + LLControlVariable* control = NULL; + + // Name can be further split into ControlGroup.Name, with the default control group being Global + size_t pos = name.find('.'); + if (pos != std::string::npos) + { + group_part = name.substr(0, pos); + name_part = name.substr(pos+1); + LL_INFOS() << "Setting " << group_part << "." << name_part << " to " << value << LL_ENDL; + auto g = LLControlGroup::getInstance(group_part); + if (g) control = g->getControl(name_part); + } + else + { + LL_INFOS() << "Setting Global." << name << " to " << value << LL_ENDL; + control = gSavedSettings.getControl(name); + } + + if (control) + { + control->setValue(value, false); + return true; + } + return false; +} + +bool LLAppViewer::initConfiguration() +{ + //Load settings files list + std::string settings_file_list = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "settings_files.xml"); + LLXMLNodePtr root; + bool success = LLXMLNode::parseFile(settings_file_list, root, NULL); + if (!success) + { + LL_WARNS() << "Cannot load default configuration file " << settings_file_list << LL_ENDL; + LLError::LLUserWarningMsg::showMissingFiles(); + if (gDirUtilp->fileExists(settings_file_list)) + { + LL_ERRS() << "Cannot load default configuration file settings_files.xml. " + << "Please reinstall viewer from https://secondlife.com/support/downloads/ " + << "and contact https://support.secondlife.com if issue persists after reinstall." + << LL_ENDL; + } + else + { + LL_ERRS() << "Default configuration file settings_files.xml not found. " + << "Please reinstall viewer from https://secondlife.com/support/downloads/ " + << "and contact https://support.secondlife.com if issue persists after reinstall." + << LL_ENDL; + } + } + + mSettingsLocationList = new SettingsFiles(); + + LLXUIParser parser; + parser.readXUI(root, *mSettingsLocationList, settings_file_list); + + if (!mSettingsLocationList->validateBlock()) + { + LLError::LLUserWarningMsg::showMissingFiles(); + LL_ERRS() << "Invalid settings file list " << settings_file_list << LL_ENDL; + } + + // The settings and command line parsing have a fragile + // order-of-operation: + // - load defaults from app_settings + // - set procedural settings values + // - read command line settings + // - selectively apply settings needed to load user settings. + // - load overrides from user_settings + // - apply command line settings (to override the overrides) + // - load per account settings (happens in llstartup + + // - load defaults + bool set_defaults = true; + if(!loadSettingsFromDirectory("Default", set_defaults)) + { + OSMessageBox( + "Unable to load default settings file. The installation may be corrupted.", + LLStringUtil::null,OSMB_OK); + return false; + } + + initStrings(); // setup paths for LLTrans based on settings files only + // - set procedural settings + // Note: can't use LL_PATH_PER_SL_ACCOUNT for any of these since we haven't logged in yet + gSavedSettings.setString("ClientSettingsFile", + gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, getSettingsFilename("Default", "Global"))); + +#ifndef LL_RELEASE_FOR_DOWNLOAD + // provide developer build only overrides for these control variables that are not + // persisted to settings.xml + LLControlVariable* c = gSavedSettings.getControl("AllowMultipleViewers"); + if (c) + { + c->setValue(true, false); + } + + gSavedSettings.setBOOL("QAMode", true ); + gSavedSettings.setS32("WatchdogEnabled", 0); +#endif + + // These are warnings that appear on the first experience of that condition. + // They are already set in the settings_default.xml file, but still need to be added to LLFirstUse + // for disable/reset ability +// LLFirstUse::addConfigVariable("FirstBalanceIncrease"); +// LLFirstUse::addConfigVariable("FirstBalanceDecrease"); +// LLFirstUse::addConfigVariable("FirstSit"); +// LLFirstUse::addConfigVariable("FirstMap"); +// LLFirstUse::addConfigVariable("FirstGoTo"); +// LLFirstUse::addConfigVariable("FirstBuild"); +// LLFirstUse::addConfigVariable("FirstLeftClickNoHit"); +// LLFirstUse::addConfigVariable("FirstTeleport"); +// LLFirstUse::addConfigVariable("FirstOverrideKeys"); +// LLFirstUse::addConfigVariable("FirstAttach"); +// LLFirstUse::addConfigVariable("FirstAppearance"); +// LLFirstUse::addConfigVariable("FirstInventory"); +// LLFirstUse::addConfigVariable("FirstSandbox"); +// LLFirstUse::addConfigVariable("FirstFlexible"); +// LLFirstUse::addConfigVariable("FirstDebugMenus"); +// LLFirstUse::addConfigVariable("FirstSculptedPrim"); +// LLFirstUse::addConfigVariable("FirstVoice"); +// LLFirstUse::addConfigVariable("FirstMedia"); + + // - read command line settings. + LLControlGroupCLP clp; + std::string cmd_line_config = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, + "cmd_line.xml"); + + clp.configure(cmd_line_config, &gSavedSettings); + + if(!initParseCommandLine(clp)) + { + handleCommandLineError(clp); + return false; + } + + // - selectively apply settings + + // If the user has specified a alternate settings file name. + // Load it now before loading the user_settings/settings.xml + if(clp.hasOption("settings")) + { + std::string user_settings_filename = + gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, + clp.getOption("settings")[0]); + gSavedSettings.setString("ClientSettingsFile", user_settings_filename); + LL_INFOS("Settings") << "Using command line specified settings filename: " + << user_settings_filename << LL_ENDL; + } + + // - load overrides from user_settings + loadSettingsFromDirectory("User"); + + if (gSavedSettings.getBOOL("FirstRunThisInstall")) + { + // Set firstrun flag to indicate that some further init actiona should be taken + // like determining screen DPI value and so on + mIsFirstRun = true; + + gSavedSettings.setBOOL("FirstRunThisInstall", false); + } + + if (clp.hasOption("sessionsettings")) + { + std::string session_settings_filename = clp.getOption("sessionsettings")[0]; + gSavedSettings.setString("SessionSettingsFile", session_settings_filename); + LL_INFOS("Settings") << "Using session settings filename: " + << session_settings_filename << LL_ENDL; + } + loadSettingsFromDirectory("Session"); + + if (clp.hasOption("usersessionsettings")) + { + std::string user_session_settings_filename = clp.getOption("usersessionsettings")[0]; + gSavedSettings.setString("UserSessionSettingsFile", user_session_settings_filename); + LL_INFOS("Settings") << "Using user session settings filename: " + << user_session_settings_filename << LL_ENDL; + + } + loadSettingsFromDirectory("UserSession"); + + // - apply command line settings + if (! clp.notify()) + { + handleCommandLineError(clp); + return false; + } + + // Register the core crash option as soon as we can + // if we want gdb post-mortem on cores we need to be up and running + // ASAP or we might miss init issue etc. + if(gSavedSettings.getBOOL("DisableCrashLogger")) + { + LL_WARNS() << "Crashes will be handled by system, stack trace logs and crash logger are both disabled" << LL_ENDL; + disableCrashlogger(); + } + + gNonInteractive = gSavedSettings.getBOOL("NonInteractive"); + // Handle initialization from settings. + // Start up the debugging console before handling other options. + if (gSavedSettings.getBOOL("ShowConsoleWindow") && !gNonInteractive) + { + initConsole(); + } + + if(clp.hasOption("help")) + { + std::ostringstream msg; + msg << LLTrans::getString("MBCmdLineUsg") << "\n" << clp; + LL_INFOS() << msg.str() << LL_ENDL; + + OSMessageBox( + msg.str(), + LLStringUtil::null, + OSMB_OK); + + return false; + } + + if(clp.hasOption("set")) + { + const LLCommandLineParser::token_vector_t& set_values = clp.getOption("set"); + if(0x1 & set_values.size()) + { + LL_WARNS() << "Invalid '--set' parameter count." << LL_ENDL; + } + else + { + LLCommandLineParser::token_vector_t::const_iterator itr = set_values.begin(); + for(; itr != set_values.end(); ++itr) + { + const std::string& name = *itr; + const std::string& value = *(++itr); + if (!tempSetControl(name,value)) + { + LL_WARNS() << "Failed --set " << name << ": setting name unknown." << LL_ENDL; + } + } + } + } + + if (clp.hasOption("logevents")) { + LLViewerEventRecorder::instance().setEventLoggingOn(); + } + + std::string CmdLineChannel(gSavedSettings.getString("CmdLineChannel")); + if(! CmdLineChannel.empty()) + { + LLVersionInfo::instance().resetChannel(CmdLineChannel); + } + + // If we have specified crash on startup, set the global so we'll trigger the crash at the right time + gCrashOnStartup = gSavedSettings.getBOOL("CrashOnStartup"); + + if (gSavedSettings.getBOOL("LogPerformance")) + { + LLTrace::BlockTimer::sLog = true; + LLTrace::BlockTimer::sLogName = std::string("performance"); + } + + std::string test_name(gSavedSettings.getString("LogMetrics")); + if (! test_name.empty()) + { + LLTrace::BlockTimer::sMetricLog = true; + // '--logmetrics' is specified with a named test metric argument so the data gathering is done only on that test + // In the absence of argument, every metric would be gathered (makes for a rather slow run and hard to decipher report...) + LL_INFOS() << "'--logmetrics' argument : " << test_name << LL_ENDL; + LLTrace::BlockTimer::sLogName = test_name; + } + + if (clp.hasOption("graphicslevel")) + { + // User explicitly requested --graphicslevel on the command line. We + // expect this switch has already set RenderQualityPerformance. Check + // that value for validity later. + // Capture the requested value separately from the settings variable + // because, if this is the first run, LLViewerWindow's constructor + // will call LLFeatureManager::applyRecommendedSettings(), which + // overwrites this settings variable! + mForceGraphicsLevel = gSavedSettings.getU32("RenderQualityPerformance"); + } + + LLFastTimerView::sAnalyzePerformance = gSavedSettings.getBOOL("AnalyzePerformance"); + gAgentPilot.setReplaySession(gSavedSettings.getBOOL("ReplaySession")); + + if (gSavedSettings.getBOOL("DebugSession")) + { + gDebugSession = true; + gDebugGL = true; + + ll_init_fail_log(gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "test_failures.log")); + } + + if (gSavedSettings.getBOOL("RenderDebugGLSession")) + { + gDebugGLSession = true; + gDebugGL = true; + // gDebugGL can cause excessive logging + // so it's limited to a single session + gSavedSettings.setBOOL("RenderDebugGLSession", false); + } + + const LLControlVariable* skinfolder = gSavedSettings.getControl("SkinCurrent"); + if(skinfolder && LLStringUtil::null != skinfolder->getValue().asString()) + { + // Examining "Language" may not suffice -- see LLUI::getLanguage() + // logic. Unfortunately LLUI::getLanguage() doesn't yet do us much + // good because we haven't yet called LLUI::initClass(). + gDirUtilp->setSkinFolder(skinfolder->getValue().asString(), + gSavedSettings.getString("Language")); + } + + if (gSavedSettings.getBOOL("SpellCheck")) + { + std::list dict_list; + std::string dict_setting = gSavedSettings.getString("SpellCheckDictionary"); + boost::split(dict_list, dict_setting, boost::is_any_of(std::string(","))); + if (!dict_list.empty()) + { + LLSpellChecker::setUseSpellCheck(dict_list.front()); + dict_list.pop_front(); + LLSpellChecker::instance().setSecondaryDictionaries(dict_list); + } + } + + if (gNonInteractive) + { + tempSetControl("AllowMultipleViewers", "true"); + tempSetControl("SLURLPassToOtherInstance", "false"); + tempSetControl("RenderWater", "false"); + tempSetControl("FlyingAtExit", "false"); + tempSetControl("WindowWidth", "1024"); + tempSetControl("WindowHeight", "200"); + LLError::setEnabledLogTypesMask(0); + llassert_always(!gSavedSettings.getBOOL("SLURLPassToOtherInstance")); + } + + + // Handle slurl use. NOTE: Don't let SL-55321 reappear. + // This initial-SLURL logic, up through the call to + // sendURLToOtherInstance(), must precede LLSplashScreen::show() -- + // because if sendURLToOtherInstance() succeeds, we take a fast exit, + // SKIPPING the splash screen and everything else. + + // *FIX: This init code should be made more robust to prevent + // the issue SL-55321 from returning. One thought is to allow + // only select options to be set from command line when a slurl + // is specified. More work on the settings system is needed to + // achieve this. For now... + + // *NOTE:Mani The command line parser parses tokens and is + // setup to bail after parsing the '--url' option or the + // first option specified without a '--option' flag (or + // any other option that uses the 'last_option' setting - + // see LLControlGroupCLP::configure()) + + // What can happen is that someone can use IE (or potentially + // other browsers) and do the rough equivalent of command + // injection and steal passwords. Phoenix. SL-55321 + + std::string starting_location; + + std::string cmd_line_login_location(gSavedSettings.getString("CmdLineLoginLocation")); + if(! cmd_line_login_location.empty()) + { + starting_location = cmd_line_login_location; + } + else + { + std::string default_login_location(gSavedSettings.getString("DefaultLoginLocation")); + if (! default_login_location.empty()) + { + starting_location = default_login_location; + } + } + + LLSLURL start_slurl; + if (! starting_location.empty()) + { + start_slurl = starting_location; + LLStartUp::setStartSLURL(start_slurl); + if(start_slurl.getType() == LLSLURL::LOCATION) + { + LLGridManager::getInstance()->setGridChoice(start_slurl.getGrid()); + } + } + + // NextLoginLocation is set as a side effect of LLStartUp::setStartSLURL() + std::string nextLoginLocation = gSavedSettings.getString( "NextLoginLocation" ); + if ( !nextLoginLocation.empty() ) + { + LL_DEBUGS("AppInit")<<"set start from NextLoginLocation: "<useMutex(); // LLApp and LLMutex magic must be manually enabled + LLPrimitive::setVolumeManager(volume_manager); + + // Note: this is where we used to initialize gFeatureManagerp. + + gStartTime = totalTime(); + + // + // Set the name of the window + // + gWindowTitle = LLTrans::getString("APP_NAME"); +#if LL_DEBUG + gWindowTitle += std::string(" [DEBUG]"); +#endif + if (!gArgs.empty()) + { + gWindowTitle += std::string(" ") + gArgs; + } + LLStringUtil::truncate(gWindowTitle, 255); + + // + // Check for another instance of the app running + // This happens AFTER LLSplashScreen::show(). That may or may not be + // important. + // + if (mSecondInstance && !gSavedSettings.getBOOL("AllowMultipleViewers")) + { + OSMessageBox( + LLTrans::getString("MBAlreadyRunning"), + LLStringUtil::null, + OSMB_OK); + return false; + } + + if (mSecondInstance) + { + // This is the second instance of SL. Mute voice, + // but make sure the setting is *not* persisted. + // Also see LLVivoxVoiceClient::voiceEnabled() + LLControlVariable* enable_voice = gSavedSettings.getControl("EnableVoiceChat"); + if(enable_voice) + { + const bool DO_NOT_PERSIST = false; + enable_voice->setValue(LLSD(false), DO_NOT_PERSIST); + } + } + + gLastRunVersion = gSavedSettings.getString("LastRunVersion"); + + loadColorSettings(); + + // Let anyone else who cares know that we've populated our settings + // variables. + for (const auto& key : LLControlGroup::key_snapshot()) + { + // For each named instance of LLControlGroup, send an event saying + // we've initialized an LLControlGroup instance by that name. + LLEventPumps::instance().obtain("LLControlGroup").post(LLSDMap("init", key)); + } + + LLError::LLUserWarningMsg::setOutOfMemoryStrings(LLTrans::getString("MBOutOfMemoryTitle"), LLTrans::getString("MBOutOfMemoryErr")); + + return true; // Config was successful. +} + +// The following logic is replicated in initConfiguration() (to be able to get +// some initial strings before we've finished initializing enough to know the +// current language) and also in init() (to initialize for real). Somehow it +// keeps growing, necessitating a method all its own. +void LLAppViewer::initStrings() +{ + std::string strings_file = "strings.xml"; + std::string strings_path_full = gDirUtilp->findSkinnedFilenameBaseLang(LLDir::XUI, strings_file); + if (strings_path_full.empty() || !LLFile::isfile(strings_path_full)) + { + if (strings_path_full.empty()) + { + LL_WARNS() << "The file '" << strings_file << "' is not found" << LL_ENDL; + } + else + { + llstat st; + int rc = LLFile::stat(strings_path_full, &st); + if (rc != 0) + { + LL_WARNS() << "The file '" << strings_path_full << "' failed to get status. Error code: " << rc << LL_ENDL; + } + else if (S_ISDIR(st.st_mode)) + { + LL_WARNS() << "The filename '" << strings_path_full << "' is a directory name" << LL_ENDL; + } + else + { + LL_WARNS() << "The filename '" << strings_path_full << "' doesn't seem to be a regular file name" << LL_ENDL; + } + } + + // initial check to make sure files are there failed + gDirUtilp->dumpCurrentDirectories(LLError::LEVEL_WARN); + LLError::LLUserWarningMsg::showMissingFiles(); + LL_ERRS() << "Viewer failed to find localization and UI files." + << " Please reinstall viewer from https://secondlife.com/support/downloads" + << " and contact https://support.secondlife.com if issue persists after reinstall." << LL_ENDL; + } + LLTransUtil::parseStrings(strings_file, default_trans_args); + LLTransUtil::parseLanguageStrings("language_settings.xml"); + + // parseStrings() sets up the LLTrans substitution table. Add this one item. + LLTrans::setDefaultArg("[sourceid]", gSavedSettings.getString("sourceid")); + + // Now that we've set "[sourceid]", have to go back through + // default_trans_args and reinitialize all those other keys because some + // of them, in turn, reference "[sourceid]". + for (const std::string& key : default_trans_args) + { + std::string brackets(key), nobrackets(key); + // Invalid to inspect key[0] if key is empty(). But then, the entire + // body of this loop is pointless if key is empty(). + if (key.empty()) + continue; + + if (key[0] != '[') + { + // key was passed without brackets. That means that 'nobrackets' + // is correct but 'brackets' is not. + brackets = STRINGIZE('[' << brackets << ']'); + } + else + { + // key was passed with brackets. That means that 'brackets' is + // correct but 'nobrackets' is not. Erase the left bracket. + nobrackets.erase(0, 1); + std::string::size_type length(nobrackets.length()); + if (length && nobrackets[length - 1] == ']') + { + nobrackets.erase(length - 1); + } + } + // Calling LLTrans::getString() is what embeds the other default + // translation strings into this one. + LLTrans::setDefaultArg(brackets, LLTrans::getString(nobrackets)); + } +} + +bool LLAppViewer::meetsRequirementsForMaximizedStart() +{ + bool maximizedOk = (gSysMemory.getPhysicalMemoryKB() >= U32Gigabytes(1)); + + return maximizedOk; +} + +bool LLAppViewer::initWindow() +{ + LL_INFOS("AppInit") << "Initializing window..." << LL_ENDL; + + // store setting in a global for easy access and modification + gHeadlessClient = gSavedSettings.getBOOL("HeadlessClient"); + + // always start windowed + bool ignorePixelDepth = gSavedSettings.getBOOL("IgnorePixelDepth"); + + LLViewerWindow::Params window_params; + window_params + .title(gWindowTitle) + .name(VIEWER_WINDOW_CLASSNAME) + .x(gSavedSettings.getS32("WindowX")) + .y(gSavedSettings.getS32("WindowY")) + .width(gSavedSettings.getU32("WindowWidth")) + .height(gSavedSettings.getU32("WindowHeight")) + .min_width(gSavedSettings.getU32("MinWindowWidth")) + .min_height(gSavedSettings.getU32("MinWindowHeight")) + .fullscreen(gSavedSettings.getBOOL("FullScreen")) + .ignore_pixel_depth(ignorePixelDepth) + .first_run(mIsFirstRun); + + gViewerWindow = new LLViewerWindow(window_params); + + LL_INFOS("AppInit") << "gViewerwindow created." << LL_ENDL; + + // Need to load feature table before cheking to start watchdog. + bool use_watchdog = false; + int watchdog_enabled_setting = gSavedSettings.getS32("WatchdogEnabled"); + if (watchdog_enabled_setting == -1) + { + use_watchdog = !LLFeatureManager::getInstance()->isFeatureAvailable("WatchdogDisabled"); + } + else + { + // The user has explicitly set this setting; always use that value. + use_watchdog = bool(watchdog_enabled_setting); + } + + LL_INFOS("AppInit") << "watchdog" + << (use_watchdog ? " " : " NOT ") + << "enabled" + << " (setting = " << watchdog_enabled_setting << ")" + << LL_ENDL; + + if (use_watchdog) + { + LLWatchdog::getInstance()->init(); + } + + LLNotificationsUI::LLNotificationManager::getInstance(); + + +#ifdef LL_DARWIN + //Satisfy both MAINT-3135 (OSX 10.6 and earlier) MAINT-3288 (OSX 10.7 and later) + LLOSInfo& os_info = LLOSInfo::instance(); + if (os_info.mMajorVer == 10 && os_info.mMinorVer < 7) + { + if ( os_info.mMinorVer == 6 && os_info.mBuild < 8 ) + gViewerWindow->getWindow()->setOldResize(true); + } +#endif + + if (gSavedSettings.getBOOL("WindowMaximized")) + { + gViewerWindow->getWindow()->maximize(); + } + + // + // Initialize GL stuff + // + + if (mForceGraphicsLevel && (LLFeatureManager::instance().isValidGraphicsLevel(*mForceGraphicsLevel))) + { + LLFeatureManager::getInstance()->setGraphicsLevel(*mForceGraphicsLevel, false); + gSavedSettings.setU32("RenderQualityPerformance", *mForceGraphicsLevel); + } + + // Set this flag in case we crash while initializing GL + gSavedSettings.setBOOL("RenderInitError", true); + gSavedSettings.saveToFile( gSavedSettings.getString("ClientSettingsFile"), true ); + + gPipeline.init(); + LL_INFOS("AppInit") << "gPipeline Initialized" << LL_ENDL; + + stop_glerror(); + gViewerWindow->initGLDefaults(); + + gSavedSettings.setBOOL("RenderInitError", false); + gSavedSettings.saveToFile( gSavedSettings.getString("ClientSettingsFile"), true ); + + //If we have a startup crash, it's usually near GL initialization, so simulate that. + if(gCrashOnStartup) + { + LLAppViewer::instance()->forceErrorLLError(); + } + + // + // Determine if the window should start maximized on initial run based + // on graphics capability + // + if (gSavedSettings.getBOOL("FirstLoginThisInstall") && meetsRequirementsForMaximizedStart()) + { + LL_INFOS("AppInit") << "This client met the requirements for a maximized initial screen." << LL_ENDL; + gSavedSettings.setBOOL("WindowMaximized", true); + } + + if (gSavedSettings.getBOOL("WindowMaximized")) + { + gViewerWindow->getWindow()->maximize(); + } + + LLUI::getInstance()->mWindow = gViewerWindow->getWindow(); + + // Show watch cursor + gViewerWindow->setCursor(UI_CURSOR_WAIT); + + // Finish view initialization + gViewerWindow->initBase(); + + // show viewer window + //gViewerWindow->getWindow()->show(); + + LL_INFOS("AppInit") << "Window initialization done." << LL_ENDL; + + return true; +} + +bool LLAppViewer::isUpdaterMissing() +{ + return mUpdaterNotFound; +} + +bool LLAppViewer::waitForUpdater() +{ + return !gSavedSettings.getBOOL("CmdLineSkipUpdater") && !mUpdaterNotFound && !gNonInteractive; +} + +void LLAppViewer::writeDebugInfo(bool isStatic) +{ +#if LL_WINDOWS && LL_BUGSPLAT + // bugsplat does not create dump folder and debug logs are written directly + // to logs folder, so it conflicts with main instance + if (mSecondInstance) + { + return; + } +#endif + + //Try to do the minimum when writing data during a crash. + std::string* debug_filename; + debug_filename = ( isStatic + ? getStaticDebugFile() + : getDynamicDebugFile() ); + + LL_INFOS() << "Writing debug file " << *debug_filename << LL_ENDL; + llofstream out_file(debug_filename->c_str()); + + isStatic ? LLSDSerialize::toPrettyXML(gDebugInfo, out_file) + : LLSDSerialize::toPrettyXML(gDebugInfo["Dynamic"], out_file); +} + +LLSD LLAppViewer::getViewerInfo() const +{ + // The point of having one method build an LLSD info block and the other + // construct the user-visible About string is to ensure that the same info + // is available to a getInfo() caller as to the user opening + // LLFloaterAbout. + LLSD info; + auto& versionInfo(LLVersionInfo::instance()); + // With GitHub builds, the build number is too big to fit in a 32-bit int, + // and LLSD doesn't deal with integers wider than int. Use string. + info["VIEWER_VERSION"] = llsd::array(versionInfo.getMajor(), versionInfo.getMinor(), + versionInfo.getPatch(), stringize(versionInfo.getBuild())); + info["VIEWER_VERSION_STR"] = versionInfo.getVersion(); + info["CHANNEL"] = versionInfo.getChannel(); + info["ADDRESS_SIZE"] = ADDRESS_SIZE; + std::string build_config = versionInfo.getBuildConfig(); + if (build_config != "Release") + { + info["BUILD_CONFIG"] = build_config; + } + + // return a URL to the release notes for this viewer, such as: + // https://releasenotes.secondlife.com/viewer/2.1.0.123456.html + std::string url = versionInfo.getReleaseNotes(); // VVM supplied + if (url.empty()) + { + url = LLTrans::getString("RELEASE_NOTES_BASE_URL"); + if (!LLStringUtil::endsWith(url, "/")) + url += "/"; + url += LLURI::escape(versionInfo.getVersion()) + ".html"; + } + info["VIEWER_RELEASE_NOTES_URL"] = url; + + // Position + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + LLVector3d pos = gAgent.getPositionGlobal(); + info["POSITION"] = ll_sd_from_vector3d(pos); + info["POSITION_LOCAL"] = ll_sd_from_vector3(gAgent.getPosAgentFromGlobal(pos)); + info["REGION"] = gAgent.getRegion()->getName(); + + boost::regex regex("\\.(secondlife|lindenlab)\\..*"); + info["HOSTNAME"] = boost::regex_replace(gAgent.getRegion()->getSimHostName(), regex, ""); + info["SERVER_VERSION"] = gLastVersionChannel; + LLSLURL slurl; + LLAgentUI::buildSLURL(slurl); + info["SLURL"] = slurl.getSLURLString(); + } + + // CPU + info["CPU"] = gSysCPU.getCPUString(); + info["MEMORY_MB"] = LLSD::Integer(gSysMemory.getPhysicalMemoryKB().valueInUnits()); + // Moved hack adjustment to Windows memory size into llsys.cpp + info["OS_VERSION"] = LLOSInfo::instance().getOSString(); + info["GRAPHICS_CARD_VENDOR"] = ll_safe_string((const char*)(glGetString(GL_VENDOR))); + info["GRAPHICS_CARD"] = ll_safe_string((const char*)(glGetString(GL_RENDERER))); + +#if LL_WINDOWS + std::string drvinfo; + + if (gGLManager.mIsIntel) + { + drvinfo = gDXHardware.getDriverVersionWMI(LLDXHardware::GPU_INTEL); + } + else if (gGLManager.mIsNVIDIA) + { + drvinfo = gDXHardware.getDriverVersionWMI(LLDXHardware::GPU_NVIDIA); + } + else if (gGLManager.mIsAMD) + { + drvinfo = gDXHardware.getDriverVersionWMI(LLDXHardware::GPU_AMD); + } + + if (drvinfo.empty()) + { + // Generic/substitute windows driver? Unknown vendor? + LL_WARNS("DriverVersion") << "Vendor based driver search failed, searching for any driver" << LL_ENDL; + drvinfo = gDXHardware.getDriverVersionWMI(LLDXHardware::GPU_ANY); + } + + if (!drvinfo.empty()) + { + info["GRAPHICS_DRIVER_VERSION"] = drvinfo; + } + else + { + LL_WARNS("DriverVersion")<< "Cannot get driver version from getDriverVersionWMI" << LL_ENDL; + LLSD driver_info = gDXHardware.getDisplayInfo(); + if (driver_info.has("DriverVersion")) + { + info["GRAPHICS_DRIVER_VERSION"] = driver_info["DriverVersion"]; + } + } +#endif + + info["OPENGL_VERSION"] = ll_safe_string((const char*)(glGetString(GL_VERSION))); + + // Settings + + LLRect window_rect = gViewerWindow->getWindowRectRaw(); + info["WINDOW_WIDTH"] = window_rect.getWidth(); + info["WINDOW_HEIGHT"] = window_rect.getHeight(); + info["FONT_SIZE_ADJUSTMENT"] = gSavedSettings.getF32("FontScreenDPI"); + info["UI_SCALE"] = gSavedSettings.getF32("UIScaleFactor"); + info["DRAW_DISTANCE"] = gSavedSettings.getF32("RenderFarClip"); + info["NET_BANDWITH"] = gSavedSettings.getF32("ThrottleBandwidthKBPS"); + info["LOD_FACTOR"] = gSavedSettings.getF32("RenderVolumeLODFactor"); + info["RENDER_QUALITY"] = (F32)gSavedSettings.getU32("RenderQualityPerformance"); + info["TEXTURE_MEMORY"] = gGLManager.mVRAM; + +#if LL_DARWIN + info["HIDPI"] = gHiDPISupport; +#endif + + // Libraries + + info["J2C_VERSION"] = LLImageJ2C::getEngineInfo(); + bool want_fullname = true; + info["AUDIO_DRIVER_VERSION"] = gAudiop ? LLSD(gAudiop->getDriverName(want_fullname)) : "Undefined"; + if(LLVoiceClient::getInstance()->voiceEnabled()) + { + LLVoiceVersionInfo version = LLVoiceClient::getInstance()->getVersion(); + const std::string build_version = version.mBuildVersion; + std::ostringstream version_string; + if (std::equal(build_version.begin(), build_version.begin() + version.serverVersion.size(), + version.serverVersion.begin())) + { // Normal case: Show type and build version. + version_string << version.serverType << " " << build_version << std::endl; + } + else + { // Mismatch: Show both versions. + version_string << version.serverVersion << "/" << build_version << std::endl; + } + info["VOICE_VERSION"] = version_string.str(); + } + else + { + info["VOICE_VERSION"] = LLTrans::getString("NotConnected"); + } + +#if !LL_LINUX + std::ostringstream cef_ver_codec; + cef_ver_codec << "Dullahan: "; + cef_ver_codec << DULLAHAN_VERSION_MAJOR; + cef_ver_codec << "."; + cef_ver_codec << DULLAHAN_VERSION_MINOR; + cef_ver_codec << "."; + cef_ver_codec << DULLAHAN_VERSION_POINT; + cef_ver_codec << "."; + cef_ver_codec << DULLAHAN_VERSION_BUILD; + + cef_ver_codec << std::endl; + cef_ver_codec << " CEF: "; + cef_ver_codec << CEF_VERSION; + + cef_ver_codec << std::endl; + cef_ver_codec << " Chromium: "; + cef_ver_codec << CHROME_VERSION_MAJOR; + cef_ver_codec << "."; + cef_ver_codec << CHROME_VERSION_MINOR; + cef_ver_codec << "."; + cef_ver_codec << CHROME_VERSION_BUILD; + cef_ver_codec << "."; + cef_ver_codec << CHROME_VERSION_PATCH; + + info["LIBCEF_VERSION"] = cef_ver_codec.str(); +#else + info["LIBCEF_VERSION"] = "Undefined"; +#endif + +#if !LL_LINUX + std::ostringstream vlc_ver_codec; + vlc_ver_codec << LIBVLC_VERSION_MAJOR; + vlc_ver_codec << "."; + vlc_ver_codec << LIBVLC_VERSION_MINOR; + vlc_ver_codec << "."; + vlc_ver_codec << LIBVLC_VERSION_REVISION; + info["LIBVLC_VERSION"] = vlc_ver_codec.str(); +#else + info["LIBVLC_VERSION"] = "Undefined"; +#endif + + S32 packets_in = LLViewerStats::instance().getRecording().getSum(LLStatViewer::PACKETS_IN); + if (packets_in > 0) + { + info["PACKETS_LOST"] = LLViewerStats::instance().getRecording().getSum(LLStatViewer::PACKETS_LOST); + info["PACKETS_IN"] = packets_in; + info["PACKETS_PCT"] = 100.f*info["PACKETS_LOST"].asReal() / info["PACKETS_IN"].asReal(); + } + + if (mServerReleaseNotesURL.empty()) + { + if (gAgent.getRegion()) + { + info["SERVER_RELEASE_NOTES_URL"] = LLTrans::getString("RetrievingData"); + } + else + { + info["SERVER_RELEASE_NOTES_URL"] = LLTrans::getString("NotConnected"); + } + } + else if (LLStringUtil::startsWith(mServerReleaseNotesURL, "http")) // it's an URL + { + info["SERVER_RELEASE_NOTES_URL"] = "[" + LLWeb::escapeURL(mServerReleaseNotesURL) + " " + LLTrans::getString("ReleaseNotes") + "]"; + } + else + { + info["SERVER_RELEASE_NOTES_URL"] = mServerReleaseNotesURL; + } + + // populate field for new local disk cache with some details + info["DISK_CACHE_INFO"] = LLDiskCache::getInstance()->getCacheInfo(); + + return info; +} + +std::string LLAppViewer::getViewerInfoString(bool default_string) const +{ + std::ostringstream support; + + LLSD info(getViewerInfo()); + + // Render the LLSD from getInfo() as a format_map_t + LLStringUtil::format_map_t args; + + // allow the "Release Notes" URL label to be localized + args["ReleaseNotes"] = LLTrans::getString("ReleaseNotes", default_string); + + for (LLSD::map_const_iterator ii(info.beginMap()), iend(info.endMap()); + ii != iend; ++ii) + { + if (! ii->second.isArray()) + { + // Scalar value + if (ii->second.isUndefined()) + { + args[ii->first] = LLTrans::getString("none_text", default_string); + } + else + { + // don't forget to render value asString() + args[ii->first] = ii->second.asString(); + } + } + else + { + // array value: build KEY_0, KEY_1 etc. entries + for (LLSD::Integer n(0), size(ii->second.size()); n < size; ++n) + { + args[STRINGIZE(ii->first << '_' << n)] = ii->second[n].asString(); + } + } + } + + // Now build the various pieces + support << LLTrans::getString("AboutHeader", args, default_string); + if (info.has("BUILD_CONFIG")) + { + support << "\n" << LLTrans::getString("BuildConfig", args, default_string); + } + if (info.has("REGION")) + { + support << "\n\n" << LLTrans::getString("AboutPosition", args, default_string); + } + support << "\n\n" << LLTrans::getString("AboutSystem", args, default_string); + support << "\n"; + if (info.has("GRAPHICS_DRIVER_VERSION")) + { + support << "\n" << LLTrans::getString("AboutDriver", args, default_string); + } + support << "\n" << LLTrans::getString("AboutOGL", args, default_string); + support << "\n\n" << LLTrans::getString("AboutSettings", args, default_string); +#if LL_DARWIN + support << "\n" << LLTrans::getString("AboutOSXHiDPI", args, default_string); +#endif + support << "\n\n" << LLTrans::getString("AboutLibs", args, default_string); + if (info.has("COMPILER")) + { + support << "\n" << LLTrans::getString("AboutCompiler", args, default_string); + } + if (info.has("PACKETS_IN")) + { + support << '\n' << LLTrans::getString("AboutTraffic", args, default_string); + } + + // SLT timestamp + LLSD substitution; + substitution["datetime"] = (S32)time(NULL);//(S32)time_corrected(); + support << "\n" << LLTrans::getString("AboutTime", substitution, default_string); + + return support.str(); +} + +void LLAppViewer::cleanupSavedSettings() +{ + gSavedSettings.setBOOL("MouseSun", false); + + gSavedSettings.setBOOL("UseEnergy", true); // force toggle to turn off, since sends message to simulator + + gSavedSettings.setBOOL("DebugWindowProc", gDebugWindowProc); + + gSavedSettings.setBOOL("ShowObjectUpdates", gShowObjectUpdates); + + if (gDebugView) + { + gSavedSettings.setBOOL("ShowDebugConsole", gDebugView->mDebugConsolep->getVisible()); + } + + // save window position if not maximized + // as we don't track it in callbacks + if(NULL != gViewerWindow) + { + bool maximized = gViewerWindow->getWindow()->getMaximized(); + if (!maximized) + { + LLCoordScreen window_pos; + + if (gViewerWindow->getWindow()->getPosition(&window_pos)) + { + gSavedSettings.setS32("WindowX", window_pos.mX); + gSavedSettings.setS32("WindowY", window_pos.mY); + } + } + } + + gSavedSettings.setF32("MapScale", LLWorldMapView::getScaleSetting()); + + // Some things are cached in LLAgent. + if (gAgent.isInitialized()) + { + gSavedSettings.setF32("RenderFarClip", gAgentCamera.mDrawDistance); + } +} + +void LLAppViewer::removeCacheFiles(const std::string& file_mask) +{ + gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ""), file_mask); +} + +void LLAppViewer::writeSystemInfo() +{ + + if (! gDebugInfo.has("Dynamic") ) + gDebugInfo["Dynamic"] = LLSD::emptyMap(); + +#if LL_WINDOWS && !LL_BUGSPLAT + gDebugInfo["SLLog"] = gDirUtilp->getExpandedFilename(LL_PATH_DUMP,"SecondLife.log"); +#else + //Not ideal but sufficient for good reporting. + gDebugInfo["SLLog"] = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,"SecondLife.old"); //LLError::logFileName(); +#endif + + gDebugInfo["ClientInfo"]["Name"] = LLVersionInfo::instance().getChannel(); + gDebugInfo["ClientInfo"]["MajorVersion"] = LLVersionInfo::instance().getMajor(); + gDebugInfo["ClientInfo"]["MinorVersion"] = LLVersionInfo::instance().getMinor(); + gDebugInfo["ClientInfo"]["PatchVersion"] = LLVersionInfo::instance().getPatch(); + gDebugInfo["ClientInfo"]["BuildVersion"] = std::to_string(LLVersionInfo::instance().getBuild()); + gDebugInfo["ClientInfo"]["AddressSize"] = LLVersionInfo::instance().getAddressSize(); + + gDebugInfo["CAFilename"] = gDirUtilp->getCAFile(); + + gDebugInfo["CPUInfo"]["CPUString"] = gSysCPU.getCPUString(); + gDebugInfo["CPUInfo"]["CPUFamily"] = gSysCPU.getFamily(); + gDebugInfo["CPUInfo"]["CPUMhz"] = (S32)gSysCPU.getMHz(); + gDebugInfo["CPUInfo"]["CPUAltivec"] = gSysCPU.hasAltivec(); + gDebugInfo["CPUInfo"]["CPUSSE"] = gSysCPU.hasSSE(); + gDebugInfo["CPUInfo"]["CPUSSE2"] = gSysCPU.hasSSE2(); + + gDebugInfo["RAMInfo"]["Physical"] = LLSD::Integer(gSysMemory.getPhysicalMemoryKB().value()); + gDebugInfo["RAMInfo"]["Allocated"] = LLSD::Integer(gMemoryAllocated.valueInUnits()); + gDebugInfo["OSInfo"] = LLOSInfo::instance().getOSStringSimple(); + + // The user is not logged on yet, but record the current grid choice login url + // which may have been the intended grid. + gDebugInfo["GridName"] = LLGridManager::getInstance()->getGridId(); + + // *FIX:Mani - move this down in llappviewerwin32 +#ifdef LL_WINDOWS + DWORD thread_id = GetCurrentThreadId(); + gDebugInfo["MainloopThreadID"] = (S32)thread_id; +#endif + +#ifndef LL_BUGSPLAT + // "CrashNotHandled" is set here, while things are running well, + // in case of a freeze. If there is a freeze, the crash logger will be launched + // and can read this value from the debug_info.log. + gDebugInfo["CrashNotHandled"] = LLSD::Boolean(true); +#else // LL_BUGSPLAT + // "CrashNotHandled" is obsolete; it used (not very successsfully) + // to try to distinguish crashes from freezes - the intent here to to avoid calling it a freeze + gDebugInfo["CrashNotHandled"] = LLSD::Boolean(false); +#endif // ! LL_BUGSPLAT + + // Insert crash host url (url to post crash log to) if configured. This insures + // that the crash report will go to the proper location in the case of a + // prior freeze. + std::string crashHostUrl = gSavedSettings.get("CrashHostUrl"); + if(crashHostUrl != "") + { + gDebugInfo["CrashHostUrl"] = crashHostUrl; + } + + // Dump some debugging info + LL_INFOS("SystemInfo") << "Application: " << LLTrans::getString("APP_NAME") << LL_ENDL; + LL_INFOS("SystemInfo") << "Version: " << LLVersionInfo::instance().getChannelAndVersion() << LL_ENDL; + + // Dump the local time and time zone + time_t now; + time(&now); + char tbuffer[256]; /* Flawfinder: ignore */ + strftime(tbuffer, 256, "%Y-%m-%dT%H:%M:%S %Z", localtime(&now)); + LL_INFOS("SystemInfo") << "Local time: " << tbuffer << LL_ENDL; + + // query some system information + LL_INFOS("SystemInfo") << "CPU info:\n" << gSysCPU << LL_ENDL; + LL_INFOS("SystemInfo") << "Memory info:\n" << gSysMemory << LL_ENDL; + LL_INFOS("SystemInfo") << "OS: " << LLOSInfo::instance().getOSStringSimple() << LL_ENDL; + LL_INFOS("SystemInfo") << "OS info: " << LLOSInfo::instance() << LL_ENDL; + + gDebugInfo["SettingsFilename"] = gSavedSettings.getString("ClientSettingsFile"); + gDebugInfo["ViewerExePath"] = gDirUtilp->getExecutablePathAndName(); + gDebugInfo["CurrentPath"] = gDirUtilp->getCurPath(); + gDebugInfo["FirstLogin"] = LLSD::Boolean(gAgent.isFirstLogin()); + gDebugInfo["FirstRunThisInstall"] = gSavedSettings.getBOOL("FirstRunThisInstall"); + gDebugInfo["StartupState"] = LLStartUp::getStartupStateString(); + + if (gViewerWindow) + { + std::vector resolutions = gViewerWindow->getWindow()->getDisplaysResolutionList(); + for (auto res_iter : resolutions) + { + gDebugInfo["DisplayInfo"].append(res_iter); + } + } + + writeDebugInfo(); // Save out debug_info.log early, in case of crash. +} + +#ifdef LL_WINDOWS +//For whatever reason, in Windows when using OOP server for breakpad, the callback to get the +//name of the dump file is not getting triggered by the breakpad library. Unfortunately they +//also didn't see fit to provide a simple query request across the pipe to get this name either. +//Since we are putting our output in a runtime generated directory and we know the header data in +//the dump format, we can however use the following hack to identify our file. +// TODO make this a member function. +void getFileList() +{ + std::stringstream filenames; + + typedef std::vector vec; + std::string pathname = gDirUtilp->getExpandedFilename(LL_PATH_DUMP,""); + vec file_vec = gDirUtilp->getFilesInDir(pathname); + for(vec::const_iterator iter=file_vec.begin(); iter!=file_vec.end(); ++iter) + { + filenames << *iter << " "; + if ( ( iter->length() > 30 ) && (iter->rfind(".dmp") == (iter->length()-4) ) ) + { + std::string fullname = pathname + *iter; + llifstream fdat( fullname.c_str(), std::ifstream::binary); + if (fdat) + { + char buf[5]; + fdat.read(buf,4); + fdat.close(); + if (!strncmp(buf,"MDMP",4)) + { + gDebugInfo["Dynamic"]["MinidumpPath"] = fullname; + break; + } + } + } + } + filenames << std::endl; + gDebugInfo["Dynamic"]["DumpDirContents"] = filenames.str(); +} +#endif + +// static +void LLAppViewer::recordMarkerVersion(LLAPRFile& marker_file) +{ + std::string marker_version(LLVersionInfo::instance().getChannelAndVersion()); + if ( marker_version.length() > MAX_MARKER_LENGTH ) + { + LL_WARNS_ONCE("MarkerFile") << "Version length ("<< marker_version.length()<< ")" + << " greater than maximum (" << MAX_MARKER_LENGTH << ")" + << ": marker matching may be incorrect" + << LL_ENDL; + } + + // record the viewer version in the marker file + marker_file.write(marker_version.data(), marker_version.length()); +} + +bool LLAppViewer::markerIsSameVersion(const std::string& marker_name) const +{ + bool sameVersion = false; + + std::string my_version(LLVersionInfo::instance().getChannelAndVersion()); + char marker_version[MAX_MARKER_LENGTH]; + S32 marker_version_length; + + LLAPRFile marker_file; + marker_file.open(marker_name, LL_APR_RB); + if (marker_file.getFileHandle()) + { + marker_version_length = marker_file.read(marker_version, sizeof(marker_version)); + std::string marker_string(marker_version, marker_version_length); + if ( 0 == my_version.compare( 0, my_version.length(), marker_version, 0, marker_version_length ) ) + { + sameVersion = true; + } + LL_DEBUGS("MarkerFile") << "Compare markers for '" << marker_name << "': " + << "\n mine '" << my_version << "'" + << "\n marker '" << marker_string << "'" + << "\n " << ( sameVersion ? "same" : "different" ) << " version" + << LL_ENDL; + marker_file.close(); + } + return sameVersion; +} + +void LLAppViewer::processMarkerFiles() +{ + //We've got 4 things to test for here + // - Other Process Running (SecondLife.exec_marker present, locked) + // - Freeze (SecondLife.exec_marker present, not locked) + // - LLError Crash (SecondLife.llerror_marker present) + // - Other Crash (SecondLife.error_marker present) + // These checks should also remove these files for the last 2 cases if they currently exist + + std::ostringstream marker_log_stream; + bool marker_is_same_version = true; + // first, look for the marker created at startup and deleted on a clean exit + mMarkerFileName = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,MARKER_FILE_NAME); + if (LLAPRFile::isExist(mMarkerFileName, NULL, LL_APR_RB)) + { + // File exists... + // first, read it to see if it was created by the same version (we need this later) + marker_is_same_version = markerIsSameVersion(mMarkerFileName); + + // now test to see if this file is locked by a running process (try to open for write) + marker_log_stream << "Checking exec marker file for lock..."; + mMarkerFile.open(mMarkerFileName, LL_APR_WB); + apr_file_t* fMarker = mMarkerFile.getFileHandle() ; + if (!fMarker) + { + marker_log_stream << "Exec marker file open failed - assume it is locked."; + mSecondInstance = true; // lock means that instance is running. + } + else + { + // We were able to open it, now try to lock it ourselves... + if (apr_file_lock(fMarker, APR_FLOCK_NONBLOCK | APR_FLOCK_EXCLUSIVE) != APR_SUCCESS) + { + marker_log_stream << "Locking exec marker failed."; + mSecondInstance = true; // lost a race? be conservative + } + else + { + // No other instances; we've locked this file now, so record our version; delete on quit. + recordMarkerVersion(mMarkerFile); + marker_log_stream << "Exec marker file existed but was not locked; rewritten."; + } + } + initLoggingAndGetLastDuration(); + + std::string marker_log_msg(marker_log_stream.str()); + LL_INFOS("MarkerFile") << marker_log_msg << LL_ENDL; + + if (mSecondInstance) + { + LL_INFOS("MarkerFile") << "Exec marker '"<< mMarkerFileName << "' owned by another instance" << LL_ENDL; + } + else if (marker_is_same_version) + { + // the file existed, is ours, and matched our version, so we can report on what it says + LL_INFOS("MarkerFile") << "Exec marker '"<< mMarkerFileName << "' found; last exec crashed" << LL_ENDL; + gLastExecEvent = LAST_EXEC_OTHER_CRASH; + } + else + { + LL_INFOS("MarkerFile") << "Exec marker '"<< mMarkerFileName << "' found, but versions did not match" << LL_ENDL; + } + } + else // marker did not exist... last exec (if any) did not freeze + { + initLoggingAndGetLastDuration(); + // Create the marker file for this execution & lock it; it will be deleted on a clean exit + apr_status_t s; + s = mMarkerFile.open(mMarkerFileName, LL_APR_WB, true); + + if (s == APR_SUCCESS && mMarkerFile.getFileHandle()) + { + LL_DEBUGS("MarkerFile") << "Exec marker file '"<< mMarkerFileName << "' created." << LL_ENDL; + if (APR_SUCCESS == apr_file_lock(mMarkerFile.getFileHandle(), APR_FLOCK_NONBLOCK | APR_FLOCK_EXCLUSIVE)) + { + recordMarkerVersion(mMarkerFile); + LL_DEBUGS("MarkerFile") << "Exec marker file locked." << LL_ENDL; + } + else + { + LL_WARNS("MarkerFile") << "Exec marker file cannot be locked." << LL_ENDL; + } + } + else + { + LL_WARNS("MarkerFile") << "Failed to create exec marker file '"<< mMarkerFileName << "'." << LL_ENDL; + } + } + + // now check for cases in which the exec marker may have been cleaned up by crash handlers + + // check for any last exec event report based on whether or not it happened during logout + // (the logout marker is created when logout begins) + std::string logout_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, LOGOUT_MARKER_FILE_NAME); + if(LLAPRFile::isExist(logout_marker_file, NULL, LL_APR_RB)) + { + if (markerIsSameVersion(logout_marker_file)) + { + gLastExecEvent = LAST_EXEC_LOGOUT_FROZE; + LL_INFOS("MarkerFile") << "Logout crash marker '"<< logout_marker_file << "', changing LastExecEvent to LOGOUT_FROZE" << LL_ENDL; + } + else + { + LL_INFOS("MarkerFile") << "Logout crash marker '"<< logout_marker_file << "' found, but versions did not match" << LL_ENDL; + } + LLAPRFile::remove(logout_marker_file); + } + // further refine based on whether or not a marker created during an llerr crash is found + std::string llerror_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, LLERROR_MARKER_FILE_NAME); + if(LLAPRFile::isExist(llerror_marker_file, NULL, LL_APR_RB)) + { + if (markerIsSameVersion(llerror_marker_file)) + { + if ( gLastExecEvent == LAST_EXEC_LOGOUT_FROZE ) + { + gLastExecEvent = LAST_EXEC_LOGOUT_CRASH; + LL_INFOS("MarkerFile") << "LLError marker '"<< llerror_marker_file << "' crashed, setting LastExecEvent to LOGOUT_CRASH" << LL_ENDL; + } + else + { + gLastExecEvent = LAST_EXEC_LLERROR_CRASH; + LL_INFOS("MarkerFile") << "LLError marker '"<< llerror_marker_file << "' crashed, setting LastExecEvent to LLERROR_CRASH" << LL_ENDL; + } + } + else + { + LL_INFOS("MarkerFile") << "LLError marker '"<< llerror_marker_file << "' found, but versions did not match" << LL_ENDL; + } + LLAPRFile::remove(llerror_marker_file); + } + // and last refine based on whether or not a marker created during a non-llerr crash is found + std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ERROR_MARKER_FILE_NAME); + if(LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB)) + { + if (markerIsSameVersion(error_marker_file)) + { + if (gLastExecEvent == LAST_EXEC_LOGOUT_FROZE) + { + gLastExecEvent = LAST_EXEC_LOGOUT_CRASH; + LL_INFOS("MarkerFile") << "Error marker '"<< error_marker_file << "' crashed, setting LastExecEvent to LOGOUT_CRASH" << LL_ENDL; + } + else + { + gLastExecEvent = LAST_EXEC_OTHER_CRASH; + LL_INFOS("MarkerFile") << "Error marker '"<< error_marker_file << "' crashed, setting LastExecEvent to " << gLastExecEvent << LL_ENDL; + } + } + else + { + LL_INFOS("MarkerFile") << "Error marker '"<< error_marker_file << "' marker found, but versions did not match" << LL_ENDL; + } + LLAPRFile::remove(error_marker_file); + } +} + +void LLAppViewer::removeMarkerFiles() +{ + if (!mSecondInstance) + { + if (mMarkerFile.getFileHandle()) + { + mMarkerFile.close() ; + LLAPRFile::remove( mMarkerFileName ); + LL_DEBUGS("MarkerFile") << "removed exec marker '"<dumpDirExists()) // Check if dump dir was created this run + { + std::string dump_dir = gDirUtilp->getExpandedFilename(LL_PATH_DUMP, ""); + gDirUtilp->deleteDirAndContents(dump_dir); + } + + if (mSecondInstance && !isError()) + { + std::string log_filename = LLError::logFileName(); + LLError::logToFile(""); + LLFile::remove(log_filename); + } +} + +void LLAppViewer::forceQuit() +{ + LLApp::setQuitting(); +} + +//TODO: remove +void LLAppViewer::fastQuit(S32 error_code) +{ + // finish pending transfers + flushLFSIO(); + // let sim know we're logging out + sendLogoutRequest(); + // flush network buffers by shutting down messaging system + end_messaging_system(); + // figure out the error code + S32 final_error_code = error_code ? error_code : (S32)isError(); + // this isn't a crash + removeMarkerFiles(); + // get outta here + _exit(final_error_code); +} + +void LLAppViewer::requestQuit() +{ + LL_INFOS() << "requestQuit" << LL_ENDL; + + LLViewerRegion* region = gAgent.getRegion(); + + if( (LLStartUp::getStartupState() < STATE_STARTED) || !region ) + { + // If we have a region, make some attempt to send a logout request first. + // This prevents the halfway-logged-in avatar from hanging around inworld for a couple minutes. + if(region) + { + sendLogoutRequest(); + } + + // Quit immediately + forceQuit(); + return; + } + + // Try to send metrics back to the grid + metricsSend(!gDisconnected); + + // Try to send last batch of avatar rez metrics. + if (!gDisconnected && isAgentAvatarValid()) + { + gAgentAvatarp->updateAvatarRezMetrics(true); // force a last packet to be sent. + } + + LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral*)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_POINT, true); + effectp->setPositionGlobal(gAgent.getPositionGlobal()); + effectp->setColor(LLColor4U(gAgent.getEffectColor())); + LLHUDManager::getInstance()->sendEffects(); + effectp->markDead() ;//remove it. + + // Attempt to close all floaters that might be + // editing things. + if (gFloaterView) + { + // application is quitting + gFloaterView->closeAllChildren(true); + } + + // Send preferences once, when exiting + bool include_preferences = true; + send_viewer_stats(include_preferences); + + gLogoutTimer.reset(); + mQuitRequested = true; +} + +static bool finish_quit(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + + if (option == 0) + { + LLAppViewer::instance()->requestQuit(); + } + return false; +} +static LLNotificationFunctorRegistration finish_quit_reg("ConfirmQuit", finish_quit); + +void LLAppViewer::userQuit() +{ + LL_INFOS() << "User requested quit" << LL_ENDL; + if (gDisconnected + || !gViewerWindow + || !gViewerWindow->getProgressView() + || gViewerWindow->getProgressView()->getVisible()) + { + requestQuit(); + } + else + { + LLNotificationsUtil::add("ConfirmQuit"); + } +} + +static bool finish_early_exit(const LLSD& notification, const LLSD& response) +{ + LLAppViewer::instance()->forceQuit(); + return false; +} + +void LLAppViewer::earlyExit(const std::string& name, const LLSD& substitutions) +{ + LL_WARNS() << "app_early_exit: " << name << LL_ENDL; + gDoDisconnect = true; + LLNotificationsUtil::add(name, substitutions, LLSD(), finish_early_exit); +} + +// case where we need the viewer to exit without any need for notifications +void LLAppViewer::earlyExitNoNotify() +{ + LL_WARNS() << "app_early_exit with no notification: " << LL_ENDL; + gDoDisconnect = true; + finish_early_exit( LLSD(), LLSD() ); +} + +void LLAppViewer::abortQuit() +{ + LL_INFOS() << "abortQuit()" << LL_ENDL; + mQuitRequested = false; +} + +void LLAppViewer::migrateCacheDirectory() +{ +#if LL_WINDOWS || LL_DARWIN + // NOTE: (Nyx) as of 1.21, cache for mac is moving to /library/caches/SecondLife from + // /library/application support/SecondLife/cache This should clear/delete the old dir. + + // As of 1.23 the Windows cache moved from + // C:\Documents and Settings\James\Application Support\SecondLife\cache + // to + // C:\Documents and Settings\James\Local Settings\Application Support\SecondLife + // + // The Windows Vista equivalent is from + // C:\Users\James\AppData\Roaming\SecondLife\cache + // to + // C:\Users\James\AppData\Local\SecondLife + // + // Note the absence of \cache on the second path. James. + + // Only do this once per fresh install of this version. + if (gSavedSettings.getBOOL("MigrateCacheDirectory")) + { + gSavedSettings.setBOOL("MigrateCacheDirectory", false); + + std::string old_cache_dir = gDirUtilp->add(gDirUtilp->getOSUserAppDir(), "cache"); + std::string new_cache_dir = gDirUtilp->getCacheDir(true); + + if (gDirUtilp->fileExists(old_cache_dir)) + { + LL_INFOS() << "Migrating cache from " << old_cache_dir << " to " << new_cache_dir << LL_ENDL; + + // Migrate inventory cache to avoid pain to inventory database after mass update + S32 file_count = 0; + std::string file_name; + std::string mask = "*.*"; + + LLDirIterator iter(old_cache_dir, mask); + while (iter.next(file_name)) + { + if (file_name == "." || file_name == "..") continue; + std::string source_path = gDirUtilp->add(old_cache_dir, file_name); + std::string dest_path = gDirUtilp->add(new_cache_dir, file_name); + if (!LLFile::rename(source_path, dest_path)) + { + file_count++; + } + } + LL_INFOS() << "Moved " << file_count << " files" << LL_ENDL; + + // Nuke the old cache + gDirUtilp->setCacheDir(old_cache_dir); + purgeCache(); + gDirUtilp->setCacheDir(new_cache_dir); + +#if LL_DARWIN + // Clean up Mac files not deleted by removing *.* + std::string ds_store = old_cache_dir + "/.DS_Store"; + if (gDirUtilp->fileExists(ds_store)) + { + LLFile::remove(ds_store); + } +#endif + if (LLFile::rmdir(old_cache_dir) != 0) + { + LL_WARNS() << "could not delete old cache directory " << old_cache_dir << LL_ENDL; + } + } + } +#endif // LL_WINDOWS || LL_DARWIN +} + +//static +U32 LLAppViewer::getTextureCacheVersion() +{ + // Viewer texture cache version, change if the texture cache format changes. + // 2021-03-10 Bumping up by one to help obviate texture cache issues with + // Simple Cache Viewer - see SL-14985 for more information + //const U32 TEXTURE_CACHE_VERSION = 8; + const U32 TEXTURE_CACHE_VERSION = 9; + + return TEXTURE_CACHE_VERSION ; +} + +//static +U32 LLAppViewer::getDiskCacheVersion() +{ + // Viewer disk cache version intorduced in Simple Cache Viewer, change if the cache format changes. + const U32 DISK_CACHE_VERSION = 1; + + return DISK_CACHE_VERSION ; +} + +//static +U32 LLAppViewer::getObjectCacheVersion() +{ + // Viewer object cache version, change if object update + // format changes. JC + const U32 INDRA_OBJECT_CACHE_VERSION = 17; + + return INDRA_OBJECT_CACHE_VERSION; +} + +bool LLAppViewer::initCache() +{ + mPurgeCache = false; + bool read_only = mSecondInstance; + LLAppViewer::getTextureCache()->setReadOnly(read_only) ; + LLVOCache::initParamSingleton(read_only); + + // initialize the new disk cache using saved settings + const std::string cache_dir_name = gSavedSettings.getString("DiskCacheDirName"); + + const U32 MB = 1024 * 1024; + const uintmax_t MIN_CACHE_SIZE = 256 * MB; + const uintmax_t MAX_CACHE_SIZE = 9984ll * MB; + const uintmax_t setting_cache_total_size = uintmax_t(gSavedSettings.getU32("CacheSize")) * MB; + const uintmax_t cache_total_size = llclamp(setting_cache_total_size, MIN_CACHE_SIZE, MAX_CACHE_SIZE); + const F64 disk_cache_percent = gSavedSettings.getF32("DiskCachePercentOfTotal"); + const F64 texture_cache_percent = 100.0 - disk_cache_percent; + + // note that the maximum size of this cache is defined as a percentage of the + // total cache size - the 'CacheSize' pref - for all caches. + const uintmax_t disk_cache_size = uintmax_t(cache_total_size * disk_cache_percent / 100); + const bool enable_cache_debug_info = gSavedSettings.getBOOL("EnableDiskCacheDebugInfo"); + + bool texture_cache_mismatch = false; + bool remove_vfs_files = false; + if (gSavedSettings.getS32("LocalCacheVersion") != LLAppViewer::getTextureCacheVersion()) + { + texture_cache_mismatch = true; + if (!read_only) + { + gSavedSettings.setS32("LocalCacheVersion", LLAppViewer::getTextureCacheVersion()); + + //texture cache version was bumped up in Simple Cache Viewer, and at this point old vfs files are not needed + remove_vfs_files = true; + } + } + + if (!read_only) + { + // Purge cache if user requested it + if (gSavedSettings.getBOOL("PurgeCacheOnStartup") || + gSavedSettings.getBOOL("PurgeCacheOnNextStartup")) + { + LL_INFOS("AppCache") << "Startup cache purge requested: " << (gSavedSettings.getBOOL("PurgeCacheOnStartup") ? "ALWAYS" : "ONCE") << LL_ENDL; + gSavedSettings.setBOOL("PurgeCacheOnNextStartup", false); + mPurgeCache = true; + // STORM-1141 force purgeAllTextures to get called to prevent a crash here. -brad + texture_cache_mismatch = true; + } + + // We have moved the location of the cache directory over time. + migrateCacheDirectory(); + + // Setup and verify the cache location + std::string cache_location = gSavedSettings.getString("CacheLocation"); + std::string new_cache_location = gSavedSettings.getString("NewCacheLocation"); + if (new_cache_location != cache_location) + { + LL_INFOS("AppCache") << "Cache location changed, cache needs purging" << LL_ENDL; + gDirUtilp->setCacheDir(gSavedSettings.getString("CacheLocation")); + purgeCache(); // purge old cache + gDirUtilp->deleteDirAndContents(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, cache_dir_name)); + gSavedSettings.setString("CacheLocation", new_cache_location); + gSavedSettings.setString("CacheLocationTopFolder", gDirUtilp->getBaseFileName(new_cache_location)); + } + } + + if (!gDirUtilp->setCacheDir(gSavedSettings.getString("CacheLocation"))) + { + LL_WARNS("AppCache") << "Unable to set cache location" << LL_ENDL; + gSavedSettings.setString("CacheLocation", ""); + gSavedSettings.setString("CacheLocationTopFolder", ""); + } + + const std::string cache_dir = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, cache_dir_name); + LLDiskCache::initParamSingleton(cache_dir, disk_cache_size, enable_cache_debug_info); + + if (!read_only) + { + if (gSavedSettings.getS32("DiskCacheVersion") != LLAppViewer::getDiskCacheVersion()) + { + LLDiskCache::getInstance()->clearCache(); + remove_vfs_files = true; + gSavedSettings.setS32("DiskCacheVersion", LLAppViewer::getDiskCacheVersion()); + } + + if (remove_vfs_files) + { + LLDiskCache::getInstance()->removeOldVFSFiles(); + } + + if (mPurgeCache) + { + LLSplashScreen::update(LLTrans::getString("StartupClearingCache")); + purgeCache(); + + // clear the new C++ file system based cache + LLDiskCache::getInstance()->clearCache(); + } + else + { + // purge excessive files from the new file system based cache + LLDiskCache::getInstance()->purge(); + } + } + LLAppViewer::getPurgeDiskCacheThread()->start(); + + LLSplashScreen::update(LLTrans::getString("StartupInitializingTextureCache")); + + // Init the texture cache + // Allocate the remaining percent which is not allocated to the disk cache + const S64 texture_cache_size = S64(cache_total_size * texture_cache_percent / 100); + + LLAppViewer::getTextureCache()->initCache(LL_PATH_CACHE, texture_cache_size, texture_cache_mismatch); + + const U32 CACHE_NUMBER_OF_REGIONS_FOR_OBJECTS = 128; + LLVOCache::getInstance()->initCache(LL_PATH_CACHE, CACHE_NUMBER_OF_REGIONS_FOR_OBJECTS, getObjectCacheVersion()); + + return true; +} + +void LLAppViewer::addOnIdleCallback(const boost::function& cb) +{ + gMainloopWork.post(cb); +} + +void LLAppViewer::loadKeyBindings() +{ + std::string key_bindings_file = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "key_bindings.xml"); + if (!gDirUtilp->fileExists(key_bindings_file) || !gViewerInput.loadBindingsXML(key_bindings_file)) + { + // Failed to load custom bindings, try default ones + key_bindings_file = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "key_bindings.xml"); + if (!gViewerInput.loadBindingsXML(key_bindings_file)) + { + LLError::LLUserWarningMsg::showMissingFiles(); + LL_ERRS("InitInfo") << "Unable to open default key bindings from " << key_bindings_file << LL_ENDL; + } + } + LLUrlRegistry::instance().setKeybindingHandler(&gViewerInput); +} + +void LLAppViewer::purgeCache() +{ + LL_INFOS("AppCache") << "Purging Cache and Texture Cache..." << LL_ENDL; + LLAppViewer::getTextureCache()->purgeCache(LL_PATH_CACHE); + LLVOCache::getInstance()->removeCache(LL_PATH_CACHE); + LLViewerShaderMgr::instance()->clearShaderCache(); + std::string browser_cache = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "cef_cache"); + if (LLFile::isdir(browser_cache)) + { + // cef does not support clear_cache and clear_cookies, so clear what we can manually. + gDirUtilp->deleteDirAndContents(browser_cache); + } + gDirUtilp->deleteFilesInDir(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ""), "*"); +} + +//purge cache immediately, do not wait until the next login. +void LLAppViewer::purgeCacheImmediate() +{ + LL_INFOS("AppCache") << "Purging Object Cache and Texture Cache immediately..." << LL_ENDL; + LLAppViewer::getTextureCache()->purgeCache(LL_PATH_CACHE, false); + LLVOCache::getInstance()->removeCache(LL_PATH_CACHE, true); +} + +std::string LLAppViewer::getSecondLifeTitle() const +{ + return LLTrans::getString("APP_NAME"); +} + +std::string LLAppViewer::getWindowTitle() const +{ + return gWindowTitle; +} + +// Callback from a dialog indicating user was logged out. +bool finish_disconnect(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + + if (1 == option) + { + LLAppViewer::instance()->forceQuit(); + } + return false; +} + +// Callback from an early disconnect dialog, force an exit +bool finish_forced_disconnect(const LLSD& notification, const LLSD& response) +{ + LLAppViewer::instance()->forceQuit(); + return false; +} + + +void LLAppViewer::forceDisconnect(const std::string& mesg) +{ + if (gDoDisconnect) + { + // Already popped up one of these dialogs, don't + // do this again. + return; + } + + // *TODO: Translate the message if possible + std::string big_reason = LLAgent::sTeleportErrorMessages[mesg]; + if ( big_reason.size() == 0 ) + { + big_reason = mesg; + } + + LLSD args; + gDoDisconnect = true; + + if (LLStartUp::getStartupState() < STATE_STARTED) + { + // Tell users what happened + args["ERROR_MESSAGE"] = big_reason; + LLNotificationsUtil::add("ErrorMessage", args, LLSD(), &finish_forced_disconnect); + } + else + { + args["MESSAGE"] = big_reason; + LLNotificationsUtil::add("YouHaveBeenLoggedOut", args, LLSD(), &finish_disconnect ); + } +} + +void LLAppViewer::badNetworkHandler() +{ + // Dump the packet + gMessageSystem->dumpPacketToLog(); + + // Flush all of our caches on exit in the case of disconnect due to + // invalid packets. + + mPurgeCacheOnExit = true; + + std::ostringstream message; + message << + "The viewer has detected mangled network data indicative\n" + "of a bad upstream network connection or an incomplete\n" + "local installation of " << LLAppViewer::instance()->getSecondLifeTitle() << ". \n" + " \n" + "Try uninstalling and reinstalling to see if this resolves \n" + "the issue. \n" + " \n" + "If the problem continues, see the Tech Support FAQ at: \n" + "www.secondlife.com/support"; + forceDisconnect(message.str()); + + LLApp::instance()->writeMiniDump(); +} + +// This routine may get called more than once during the shutdown process. +// This can happen because we need to get the screenshot before the window +// is destroyed. +void LLAppViewer::saveFinalSnapshot() +{ + if (!mSavedFinalSnapshot) + { + gSavedSettings.setVector3d("FocusPosOnLogout", gAgentCamera.calcFocusPositionTargetGlobal()); + gSavedSettings.setVector3d("CameraPosOnLogout", gAgentCamera.calcCameraPositionTargetGlobal()); + gViewerWindow->setCursor(UI_CURSOR_WAIT); + gAgentCamera.changeCameraToThirdPerson( false ); // don't animate, need immediate switch + gSavedSettings.setBOOL("ShowParcelOwners", false); + idle(); + + std::string snap_filename = gDirUtilp->getLindenUserDir(); + snap_filename += gDirUtilp->getDirDelimiter(); + snap_filename += LLStartUp::getScreenLastFilename(); + // use full pixel dimensions of viewer window (not post-scale dimensions) + gViewerWindow->saveSnapshot(snap_filename, + gViewerWindow->getWindowWidthRaw(), + gViewerWindow->getWindowHeightRaw(), + false, + gSavedSettings.getBOOL("RenderHUDInSnapshot"), + true, + LLSnapshotModel::SNAPSHOT_TYPE_COLOR, + LLSnapshotModel::SNAPSHOT_FORMAT_PNG); + mSavedFinalSnapshot = true; + + if (gAgent.isInHomeRegion()) + { + LLVector3d home; + if (gAgent.getHomePosGlobal(&home) && dist_vec(home, gAgent.getPositionGlobal()) < 10) + { + // We are at home position or close to it, see if we need to create home screenshot + // Notes: + // 1. It might be beneficial to also replace home if file is too old + // 2. This is far from best way/place to update screenshot since location might be not fully loaded, + // but we don't have many options + std::string snap_home = gDirUtilp->getLindenUserDir(); + snap_home += gDirUtilp->getDirDelimiter(); + snap_home += LLStartUp::getScreenHomeFilename(); + if (!gDirUtilp->fileExists(snap_home)) + { + // We are at home position yet no home image exist, fix it + LLFile::copy(snap_filename, snap_home); + } + } + } + } +} + +void LLAppViewer::loadNameCache() +{ + // display names cache + std::string filename = + gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "avatar_name_cache.xml"); + LL_INFOS("AvNameCache") << filename << LL_ENDL; + llifstream name_cache_stream(filename.c_str()); + if(name_cache_stream.is_open()) + { + if ( ! LLAvatarNameCache::getInstance()->importFile(name_cache_stream)) + { + LL_WARNS("AppInit") << "removing invalid '" << filename << "'" << LL_ENDL; + name_cache_stream.close(); + LLFile::remove(filename); + } + } + + if (!gCacheName) return; + + std::string name_cache; + name_cache = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "name.cache"); + llifstream cache_file(name_cache.c_str()); + if(cache_file.is_open()) + { + if(gCacheName->importFile(cache_file)) return; + } +} + +void LLAppViewer::saveNameCache() +{ + // display names cache + std::string filename = + gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "avatar_name_cache.xml"); + llofstream name_cache_stream(filename.c_str()); + if(name_cache_stream.is_open()) + { + LLAvatarNameCache::getInstance()->exportFile(name_cache_stream); + } + + // real names cache + if (gCacheName) + { + std::string name_cache; + name_cache = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "name.cache"); + llofstream cache_file(name_cache.c_str()); + if(cache_file.is_open()) + { + gCacheName->exportFile(cache_file); + } + } +} + + +/*! @brief This class is an LLFrameTimer that can be created with + an elapsed time that starts counting up from the given value + rather than 0.0. + + Otherwise it behaves the same way as LLFrameTimer. +*/ +class LLFrameStatsTimer : public LLFrameTimer +{ +public: + LLFrameStatsTimer(F64 elapsed_already = 0.0) + : LLFrameTimer() + { + mStartTime -= elapsed_already; + } +}; + +static LLTrace::BlockTimerStatHandle FTM_AUDIO_UPDATE("Update Audio"); +static LLTrace::BlockTimerStatHandle FTM_CLEANUP("Cleanup"); +static LLTrace::BlockTimerStatHandle FTM_CLEANUP_DRAWABLES("Drawables"); +static LLTrace::BlockTimerStatHandle FTM_IDLE_CB("Idle Callbacks"); +static LLTrace::BlockTimerStatHandle FTM_LOD_UPDATE("Update LOD"); +static LLTrace::BlockTimerStatHandle FTM_OBJECTLIST_UPDATE("Update Objectlist"); +static LLTrace::BlockTimerStatHandle FTM_REGION_UPDATE("Update Region"); +static LLTrace::BlockTimerStatHandle FTM_WORLD_UPDATE("Update World"); +static LLTrace::BlockTimerStatHandle FTM_NETWORK("Network"); +static LLTrace::BlockTimerStatHandle FTM_AGENT_NETWORK("Agent Network"); +static LLTrace::BlockTimerStatHandle FTM_VLMANAGER("VL Manager"); +static LLTrace::BlockTimerStatHandle FTM_AGENT_POSITION("Agent Position"); +static LLTrace::BlockTimerStatHandle FTM_HUD_EFFECTS("HUD Effects"); + +/////////////////////////////////////////////////////// +// idle() +// +// Called every time the window is not doing anything. +// Receive packets, update statistics, and schedule a redisplay. +/////////////////////////////////////////////////////// +void LLAppViewer::idle() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_APP; + pingMainloopTimeout("Main:Idle"); + + // Update frame timers + static LLTimer idle_timer; + + LLFrameTimer::updateFrameTime(); + LLFrameTimer::updateFrameCount(); + LLEventTimer::updateClass(); + LLPerfStats::updateClass(); + + // LLApp::stepFrame() performs the above three calls plus mRunner.run(). + // Not sure why we don't call stepFrame() here, except that LLRunner seems + // completely redundant with LLEventTimer. + LLNotificationsUI::LLToast::updateClass(); + LLSmoothInterpolation::updateInterpolants(); + LLMortician::updateClass(); + LLFilePickerThread::clearDead(); //calls LLFilePickerThread::notify() + LLDirPickerThread::clearDead(); + F32 dt_raw = idle_timer.getElapsedTimeAndResetF32(); + + LLGLTFMaterialList::flushUpdates(); + + // Service the WorkQueue we use for replies from worker threads. + // Use function statics for the timeslice setting so we only have to fetch + // and convert MainWorkTime once. + static F32 MainWorkTimeRaw = gSavedSettings.getF32("MainWorkTime"); + static F32Milliseconds MainWorkTimeMs(MainWorkTimeRaw); + // MainWorkTime is specified in fractional milliseconds, but std::chrono + // uses integer representations. What if we want less than a microsecond? + // Use nanoseconds. We're very sure we will never need to specify a + // MainWorkTime that would be larger than we could express in + // std::chrono::nanoseconds. + static std::chrono::nanoseconds MainWorkTimeNanoSec{ + std::chrono::nanoseconds::rep(MainWorkTimeMs.value() * 1000000)}; + gMainloopWork.runFor(MainWorkTimeNanoSec); + + // Cap out-of-control frame times + // Too low because in menus, swapping, debugger, etc. + // Too high because idle called with no objects in view, etc. + const F32 MIN_FRAME_RATE = 1.f; + const F32 MAX_FRAME_RATE = 200.f; + + F32 frame_rate_clamped = 1.f / dt_raw; + frame_rate_clamped = llclamp(frame_rate_clamped, MIN_FRAME_RATE, MAX_FRAME_RATE); + gFrameDTClamped = 1.f / frame_rate_clamped; + + // Global frame timer + // Smoothly weight toward current frame + gFPSClamped = (frame_rate_clamped + (4.f * gFPSClamped)) / 5.f; + + F32 qas = gSavedSettings.getF32("QuitAfterSeconds"); + if (qas > 0.f) + { + if (gRenderStartTime.getElapsedTimeF32() > qas) + { + LL_INFOS() << "Quitting after " << qas << " seconds. See setting \"QuitAfterSeconds\"." << LL_ENDL; + LLAppViewer::instance()->forceQuit(); + } + } + + // Must wait until both have avatar object and mute list, so poll + // here. + LLIMProcessing::requestOfflineMessages(); + + /////////////////////////////////// + // + // Special case idle if still starting up + // + if (LLStartUp::getStartupState() < STATE_STARTED) + { + // Skip rest if idle startup returns false (essentially, no world yet) + gGLActive = true; + if (!idle_startup()) + { + gGLActive = false; + return; + } + gGLActive = false; + } + + + F32 yaw = 0.f; // radians + + if (!gDisconnected) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_NETWORK("network"); //LL_RECORD_BLOCK_TIME(FTM_NETWORK); + // Update spaceserver timeinfo + LLWorld::getInstance()->setSpaceTimeUSec(LLWorld::getInstance()->getSpaceTimeUSec() + LLUnits::Seconds::fromValue(dt_raw)); + + + ////////////////////////////////////// + // + // Update simulator agent state + // + + if (gSavedSettings.getBOOL("RotateRight")) + { + gAgent.moveYaw(-1.f); + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_APP("Autopilot"); + // Handle automatic walking towards points + gAgentPilot.updateTarget(); + gAgent.autoPilot(&yaw); + } + + static LLFrameTimer agent_update_timer; + + // When appropriate, update agent location to the simulator. + F32 agent_update_time = agent_update_timer.getElapsedTimeF32(); + F32 agent_force_update_time = mLastAgentForceUpdate + agent_update_time; + bool timed_out = agent_update_time > (1.0f / (F32)AGENT_UPDATES_PER_SECOND); + bool force_send = + // if there is something to send + (gAgent.controlFlagsDirty() && timed_out) + // if something changed + || (mLastAgentControlFlags != gAgent.getControlFlags()) + // keep alive + || (agent_force_update_time > (1.0f / (F32) AGENT_FORCE_UPDATES_PER_SECOND)); + // timing out doesn't warranty that an update will be sent, + // just that it will be checked. + if (force_send || timed_out) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; + // Send avatar and camera info + mLastAgentControlFlags = gAgent.getControlFlags(); + mLastAgentForceUpdate = force_send ? 0 : agent_force_update_time; + send_agent_update(force_send); + agent_update_timer.reset(); + } + } + + ////////////////////////////////////// + // + // Manage statistics + // + // + { + // Initialize the viewer_stats_timer with an already elapsed time + // of SEND_STATS_PERIOD so that the initial stats report will + // be sent immediately. + static LLFrameStatsTimer viewer_stats_timer(SEND_STATS_PERIOD); + + // Update session stats every large chunk of time + // *FIX: (?) SAMANTHA + if (viewer_stats_timer.getElapsedTimeF32() >= SEND_STATS_PERIOD && !gDisconnected) + { + LL_INFOS() << "Transmitting sessions stats" << LL_ENDL; + bool include_preferences = false; + send_viewer_stats(include_preferences); + viewer_stats_timer.reset(); + } + + // Print the object debugging stats + static LLFrameTimer object_debug_timer; + if (object_debug_timer.getElapsedTimeF32() > 5.f) + { + object_debug_timer.reset(); + if (gObjectList.mNumDeadObjectUpdates) + { + LL_INFOS() << "Dead object updates: " << gObjectList.mNumDeadObjectUpdates << LL_ENDL; + gObjectList.mNumDeadObjectUpdates = 0; + } + if (gObjectList.mNumUnknownUpdates) + { + LL_INFOS() << "Unknown object updates: " << gObjectList.mNumUnknownUpdates << LL_ENDL; + gObjectList.mNumUnknownUpdates = 0; + } + + } + } + + if (!gDisconnected) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Network"); + + //////////////////////////////////////////////// + // + // Network processing + // + // NOTE: Starting at this point, we may still have pointers to "dead" objects + // floating throughout the various object lists. + // + idleNameCache(); + idleNetwork(); + + + // Check for away from keyboard, kick idle agents. + idle_afk_check(); + + // Update statistics for this frame + update_statistics(); + } + + //////////////////////////////////////// + // + // Handle the regular UI idle callbacks as well as + // hover callbacks + // + +#ifdef LL_DARWIN + if (!mQuitRequested) //MAINT-4243 +#endif + { +// LL_RECORD_BLOCK_TIME(FTM_IDLE_CB); + + // Do event notifications if necessary. Yes, we may want to move this elsewhere. + gEventNotifier.update(); + + gIdleCallbacks.callFunctions(); + gInventory.idleNotifyObservers(); + LLAvatarTracker::instance().idleNotifyObservers(); + } + + // Metrics logging (LLViewerAssetStats, etc.) + { + static LLTimer report_interval; + + // *TODO: Add configuration controls for this + F32 seconds = report_interval.getElapsedTimeF32(); + if (seconds >= app_metrics_interval) + { + metricsSend(! gDisconnected); + report_interval.reset(); + } + } + + + // Update layonts, handle mouse events, tooltips, e t c + // updateUI() needs to be called even in case viewer disconected + // since related notification still needs handling and allows + // opening chat. + gViewerWindow->updateUI(); + + if (gDisconnected) + { + return; + } + + if (gTeleportDisplay) + { + return; + } + + /////////////////////////////////////// + // Agent and camera movement + // + LLCoordGL current_mouse = gViewerWindow->getCurrentMouse(); + + { + // After agent and camera moved, figure out if we need to + // deselect objects. + LLSelectMgr::getInstance()->deselectAllIfTooFar(); + + } + + { + // Handle pending gesture processing + LL_RECORD_BLOCK_TIME(FTM_AGENT_POSITION); + LLGestureMgr::instance().update(); + + gAgent.updateAgentPosition(gFrameDTClamped, yaw, current_mouse.mX, current_mouse.mY); + } + + { + LL_RECORD_BLOCK_TIME(FTM_OBJECTLIST_UPDATE); + + if (!(logoutRequestSent() && hasSavedFinalSnapshot())) + { + gObjectList.update(gAgent); + } + } + + ////////////////////////////////////// + // + // Deletes objects... + // Has to be done after doing idleUpdates (which can kill objects) + // + + { + LL_RECORD_BLOCK_TIME(FTM_CLEANUP); + { + gObjectList.cleanDeadObjects(); + } + { + LL_RECORD_BLOCK_TIME(FTM_CLEANUP_DRAWABLES); + LLDrawable::cleanupDeadDrawables(); + } + } + + // + // After this point, in theory we should never see a dead object + // in the various object/drawable lists. + // + + ////////////////////////////////////// + // + // Update/send HUD effects + // + // At this point, HUD effects may clean up some references to + // dead objects. + // + + { + LL_RECORD_BLOCK_TIME(FTM_HUD_EFFECTS); + LLSelectMgr::getInstance()->updateEffects(); + LLHUDManager::getInstance()->cleanupEffects(); + LLHUDManager::getInstance()->sendEffects(); + } + + //////////////////////////////////////// + // + // Unpack layer data that we've received + // + + { + LL_RECORD_BLOCK_TIME(FTM_NETWORK); + gVLManager.unpackData(); + } + + ///////////////////////// + // + // Update surfaces, and surface textures as well. + // + + LLWorld::getInstance()->updateVisibilities(); + { + const F32 max_region_update_time = .001f; // 1ms + LL_RECORD_BLOCK_TIME(FTM_REGION_UPDATE); + LLWorld::getInstance()->updateRegions(max_region_update_time); + } + + ///////////////////////// + // + // Update weather effects + // + + // Update wind vector + LLVector3 wind_position_region; + static LLVector3 average_wind; + + LLViewerRegion *regionp; + regionp = LLWorld::getInstance()->resolveRegionGlobal(wind_position_region, gAgent.getPositionGlobal()); // puts agent's local coords into wind_position + if (regionp) + { + gWindVec = regionp->mWind.getVelocity(wind_position_region); + + // Compute average wind and use to drive motion of water + + average_wind = regionp->mWind.getAverage(); + gSky.setWind(average_wind); + //LLVOWater::setWind(average_wind); + } + else + { + gWindVec.setVec(0.0f, 0.0f, 0.0f); + } + + ////////////////////////////////////// + // + // Sort and cull in the new renderer are moved to pipeline.cpp + // Here, particles are updated and drawables are moved. + // + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_APP("world update"); //LL_RECORD_BLOCK_TIME(FTM_WORLD_UPDATE); + gPipeline.updateMove(); + } + + LLWorld::getInstance()->updateParticles(); + + if (gAgentPilot.isPlaying() && gAgentPilot.getOverrideCamera()) + { + gAgentPilot.moveCamera(); + } + else if (LLViewerJoystick::getInstance()->getOverrideCamera()) + { + LLViewerJoystick::getInstance()->moveFlycam(); + } + else + { + if (LLToolMgr::getInstance()->inBuildMode()) + { + LLViewerJoystick::getInstance()->moveObjects(); + } + + gAgentCamera.updateCamera(); + } + + // update media focus + LLViewerMediaFocus::getInstance()->update(); + + // Update marketplace + LLMarketplaceInventoryImporter::update(); + LLMarketplaceInventoryNotifications::update(); + + // objects and camera should be in sync, do LOD calculations now + { + LL_RECORD_BLOCK_TIME(FTM_LOD_UPDATE); + gObjectList.updateApparentAngles(gAgent); + } + + // Update AV render info + LLAvatarRenderInfoAccountant::getInstance()->idle(); + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_APP("audio update"); //LL_RECORD_BLOCK_TIME(FTM_AUDIO_UPDATE); + + if (gAudiop) + { + audio_update_volume(false); + audio_update_listener(); + audio_update_wind(false); + + // this line actually commits the changes we've made to source positions, etc. + gAudiop->idle(); + } + } + + // Handle shutdown process, for example, + // wait for floaters to close, send quit message, + // forcibly quit if it has taken too long + if (mQuitRequested) + { + gGLActive = true; + idleShutdown(); + } +} + +void LLAppViewer::idleShutdown() +{ + // Wait for all modal alerts to get resolved + if (LLModalDialog::activeCount() > 0) + { + return; + } + + // close IM interface + if(gIMMgr) + { + gIMMgr->disconnectAllSessions(); + } + + // Wait for all floaters to get resolved + if (gFloaterView + && !gFloaterView->allChildrenClosed()) + { + return; + } + + + + + // ProductEngine: Try moving this code to where we shut down sTextureCache in cleanup() + // *TODO: ugly + static bool saved_teleport_history = false; + if (!saved_teleport_history) + { + saved_teleport_history = true; + LLTeleportHistory::getInstance()->dump(); + LLLocationHistory::getInstance()->save(); // *TODO: find a better place for doing this + return; + } + + static bool saved_snapshot = false; + if (!saved_snapshot) + { + saved_snapshot = true; + saveFinalSnapshot(); + return; + } + + const F32 SHUTDOWN_UPLOAD_SAVE_TIME = 5.f; + + S32 pending_uploads = gAssetStorage->getNumPendingUploads(); + if (pending_uploads > 0 + && gLogoutTimer.getElapsedTimeF32() < SHUTDOWN_UPLOAD_SAVE_TIME + && !logoutRequestSent()) + { + static S32 total_uploads = 0; + // Sometimes total upload count can change during logout. + total_uploads = llmax(total_uploads, pending_uploads); + gViewerWindow->setShowProgress(true); + S32 finished_uploads = total_uploads - pending_uploads; + F32 percent = 100.f * finished_uploads / total_uploads; + gViewerWindow->setProgressPercent(percent); + gViewerWindow->setProgressString(LLTrans::getString("SavingSettings")); + return; + } + + if (gPendingMetricsUploads > 0 + && gLogoutTimer.getElapsedTimeF32() < SHUTDOWN_UPLOAD_SAVE_TIME + && !logoutRequestSent()) + { + gViewerWindow->setShowProgress(true); + gViewerWindow->setProgressPercent(100.f); + gViewerWindow->setProgressString(LLTrans::getString("LoggingOut")); + return; + } + + // All floaters are closed. Tell server we want to quit. + if( !logoutRequestSent() ) + { + sendLogoutRequest(); + + // Wait for a LogoutReply message + gViewerWindow->setShowProgress(true); + gViewerWindow->setProgressPercent(100.f); + gViewerWindow->setProgressString(LLTrans::getString("LoggingOut")); + return; + } + + // Make sure that we quit if we haven't received a reply from the server. + if( logoutRequestSent() + && gLogoutTimer.getElapsedTimeF32() > gLogoutMaxTime ) + { + forceQuit(); + return; + } +} + +void LLAppViewer::sendLogoutRequest() +{ + if(!mLogoutRequestSent && gMessageSystem) + { + //Set internal status variables and marker files before actually starting the logout process + gLogoutInProgress = true; + if (!mSecondInstance) + { + mLogoutMarkerFileName = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,LOGOUT_MARKER_FILE_NAME); + + mLogoutMarkerFile.open(mLogoutMarkerFileName, LL_APR_WB); + if (mLogoutMarkerFile.getFileHandle()) + { + LL_INFOS("MarkerFile") << "Created logout marker file '"<< mLogoutMarkerFileName << "' " << LL_ENDL; + recordMarkerVersion(mLogoutMarkerFile); + } + else + { + LL_WARNS("MarkerFile") << "Cannot create logout marker file " << mLogoutMarkerFileName << LL_ENDL; + } + } + else + { + LL_INFOS("MarkerFile") << "Did not logout marker file because this is a second instance" << LL_ENDL; + } + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_LogoutRequest); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + gAgent.sendReliableMessage(); + + gLogoutTimer.reset(); + gLogoutMaxTime = LOGOUT_REQUEST_TIME; + mLogoutRequestSent = true; + + if(LLVoiceClient::instanceExists()) + { + LLVoiceClient::getInstance()->leaveChannel(); + } + } +} + +void LLAppViewer::updateNameLookupUrl(const LLViewerRegion * regionp) +{ + if (!regionp || !regionp->capabilitiesReceived()) + { + return; + } + + LLAvatarNameCache *name_cache = LLAvatarNameCache::getInstance(); + bool had_capability = LLAvatarNameCache::getInstance()->hasNameLookupURL(); + std::string name_lookup_url; + name_lookup_url.reserve(128); // avoid a memory allocation below + name_lookup_url = regionp->getCapability("GetDisplayNames"); + bool have_capability = !name_lookup_url.empty(); + if (have_capability) + { + // we have support for display names, use it + U32 url_size = name_lookup_url.size(); + // capabilities require URLs with slashes before query params: + // https://:/cap//?ids= + // but the caps are granted like: + // https://:/cap/ + if (url_size > 0 && name_lookup_url[url_size - 1] != '/') + { + name_lookup_url += '/'; + } + name_cache->setNameLookupURL(name_lookup_url); + } + else + { + // Display names not available on this region + name_cache->setNameLookupURL(std::string()); + } + + // Error recovery - did we change state? + if (had_capability != have_capability) + { + // name tags are persistant on screen, so make sure they refresh + LLVOAvatar::invalidateNameTags(); + } +} + +void LLAppViewer::idleNameCache() +{ + // Neither old nor new name cache can function before agent has a region + LLViewerRegion* region = gAgent.getRegion(); + if (!region) + { + return; + } + + // deal with any queued name requests and replies. + gCacheName->processPending(); + + // Can't run the new cache until we have the list of capabilities + // for the agent region, and can therefore decide whether to use + // display names or fall back to the old name system. + if (!region->capabilitiesReceived()) + { + return; + } + + LLAvatarNameCache::getInstance()->idle(); +} + +// +// Handle messages, and all message related stuff +// + +#define TIME_THROTTLE_MESSAGES + +#ifdef TIME_THROTTLE_MESSAGES +#define CHECK_MESSAGES_DEFAULT_MAX_TIME .020f // 50 ms = 50 fps (just for messages!) +static F32 CheckMessagesMaxTime = CHECK_MESSAGES_DEFAULT_MAX_TIME; +#endif + +static LLTrace::BlockTimerStatHandle FTM_IDLE_NETWORK("Idle Network"); +static LLTrace::BlockTimerStatHandle FTM_MESSAGE_ACKS("Message Acks"); +static LLTrace::BlockTimerStatHandle FTM_RETRANSMIT("Retransmit"); +static LLTrace::BlockTimerStatHandle FTM_TIMEOUT_CHECK("Timeout Check"); +static LLTrace::BlockTimerStatHandle FTM_DYNAMIC_THROTTLE("Dynamic Throttle"); +static LLTrace::BlockTimerStatHandle FTM_CHECK_REGION_CIRCUIT("Check Region Circuit"); + +void LLAppViewer::idleNetwork() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; + pingMainloopTimeout("idleNetwork"); + + gObjectList.mNumNewObjects = 0; + S32 total_decoded = 0; + + if (!gSavedSettings.getBOOL("SpeedTest")) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_NETWORK("idle network"); //LL_RECORD_BLOCK_TIME(FTM_IDLE_NETWORK); // decode + + LLTimer check_message_timer; + // Read all available packets from network + const S64 frame_count = gFrameCount; // U32->S64 + F32 total_time = 0.0f; + + { + LockMessageChecker lmc(gMessageSystem); + while (lmc.checkAllMessages(frame_count, gServicePump)) + { + if (gDoDisconnect) + { + // We're disconnecting, don't process any more messages from the server + // We're usually disconnecting due to either network corruption or a + // server going down, so this is OK. + break; + } + + total_decoded++; + gPacketsIn++; + + if (total_decoded > MESSAGE_MAX_PER_FRAME) + { + break; + } + +#ifdef TIME_THROTTLE_MESSAGES + // Prevent slow packets from completely destroying the frame rate. + // This usually happens due to clumps of avatars taking huge amount + // of network processing time (which needs to be fixed, but this is + // a good limit anyway). + total_time = check_message_timer.getElapsedTimeF32(); + if (total_time >= CheckMessagesMaxTime) + break; +#endif + } + + // Handle per-frame message system processing. + lmc.processAcks(gSavedSettings.getF32("AckCollectTime")); + } + +#ifdef TIME_THROTTLE_MESSAGES + if (total_time >= CheckMessagesMaxTime) + { + // Increase CheckMessagesMaxTime so that we will eventually catch up + CheckMessagesMaxTime *= 1.035f; // 3.5% ~= x2 in 20 frames, ~8x in 60 frames + } + else + { + // Reset CheckMessagesMaxTime to default value + CheckMessagesMaxTime = CHECK_MESSAGES_DEFAULT_MAX_TIME; + } +#endif + + + + // we want to clear the control after sending out all necessary agent updates + gAgent.resetControlFlags(); + + // Decode enqueued messages... + S32 remaining_possible_decodes = MESSAGE_MAX_PER_FRAME - total_decoded; + + if( remaining_possible_decodes <= 0 ) + { + LL_INFOS() << "Maxed out number of messages per frame at " << MESSAGE_MAX_PER_FRAME << LL_ENDL; + } + + if (gPrintMessagesThisFrame) + { + LL_INFOS() << "Decoded " << total_decoded << " msgs this frame!" << LL_ENDL; + gPrintMessagesThisFrame = false; + } + } + add(LLStatViewer::NUM_NEW_OBJECTS, gObjectList.mNumNewObjects); + + // Retransmit unacknowledged packets. + gXferManager->retransmitUnackedPackets(); + gAssetStorage->checkForTimeouts(); + gViewerThrottle.updateDynamicThrottle(); + + // Check that the circuit between the viewer and the agent's current + // region is still alive + LLViewerRegion *agent_region = gAgent.getRegion(); + if (agent_region && (LLStartUp::getStartupState()==STATE_STARTED)) + { + LLUUID this_region_id = agent_region->getRegionID(); + bool this_region_alive = agent_region->isAlive(); + if ((mAgentRegionLastAlive && !this_region_alive) // newly dead + && (mAgentRegionLastID == this_region_id)) // same region + { + forceDisconnect(LLTrans::getString("AgentLostConnection")); + } + mAgentRegionLastID = this_region_id; + mAgentRegionLastAlive = this_region_alive; + } +} + +void LLAppViewer::disconnectViewer() +{ + if (gDisconnected) + { + return; + } + // + // Cleanup after quitting. + // + // Save snapshot for next time, if we made it through initialization + + LL_INFOS() << "Disconnecting viewer!" << LL_ENDL; + + // Dump our frame statistics + + // Remember if we were flying + gSavedSettings.setBOOL("FlyingAtExit", gAgent.getFlying() ); + + // Un-minimize all windows so they don't get saved minimized + if (gFloaterView) + { + gFloaterView->restoreAll(); + } + + if (LLSelectMgr::instanceExists()) + { + LLSelectMgr::getInstance()->deselectAll(); + } + + // save inventory if appropriate + if (gInventory.isInventoryUsable() + && gAgent.getID().notNull()) // Shouldn't be null at this stage + { + gInventory.cache(gInventory.getRootFolderID(), gAgent.getID()); + if (gInventory.getLibraryRootFolderID().notNull() + && gInventory.getLibraryOwnerID().notNull() + && !mSecondInstance) // agent is unique, library isn't + { + gInventory.cache( + gInventory.getLibraryRootFolderID(), + gInventory.getLibraryOwnerID()); + } + } + + saveNameCache(); + if (LLExperienceCache::instanceExists()) + { + // TODO: LLExperienceCache::cleanup() logic should be moved to + // cleanupSingleton(). + LLExperienceCache::instance().cleanup(); + } + + // close inventory interface, close all windows + LLSidepanelInventory::cleanup(); + + gAgentWearables.cleanup(); + gAgentCamera.cleanup(); + // Also writes cached agent settings to gSavedSettings + gAgent.cleanup(); + + // This is where we used to call gObjectList.destroy() and then delete gWorldp. + // Now we just ask the LLWorld singleton to cleanly shut down. + if(LLWorld::instanceExists()) + { + LLWorld::getInstance()->resetClass(); + } + LLVOCache::deleteSingleton(); + + // call all self-registered classes + LLDestroyClassList::instance().fireCallbacks(); + + cleanup_xfer_manager(); + gDisconnected = true; + + // Pass the connection state to LLUrlEntryParcel not to attempt + // parcel info requests while disconnected. + LLUrlEntryParcel::setDisconnected(gDisconnected); +} + +void LLAppViewer::forceErrorLLError() +{ + LL_ERRS() << "This is a deliberate llerror" << LL_ENDL; +} + +void LLAppViewer::forceErrorLLErrorMsg() +{ + LLError::LLUserWarningMsg::show("Deliberate error"); + // Note: under debug this will show a message as well, + // but release won't show anything and will quit silently + LL_ERRS() << "This is a deliberate llerror with a message" << LL_ENDL; +} + +void LLAppViewer::forceErrorBreakpoint() +{ + LL_WARNS() << "Forcing a deliberate breakpoint" << LL_ENDL; +#ifdef LL_WINDOWS + DebugBreak(); +#else + asm ("int $3"); +#endif + return; +} + +void LLAppViewer::forceErrorBadMemoryAccess() +{ + LL_WARNS() << "Forcing a deliberate bad memory access" << LL_ENDL; + S32* crash = NULL; + *crash = 0xDEADBEEF; + return; +} + +void LLAppViewer::forceErrorInfiniteLoop() +{ + LL_WARNS() << "Forcing a deliberate infinite loop" << LL_ENDL; + // Loop is intentionally complicated to fool basic loop detection + LLTimer timer_total; + LLTimer timer_expiry; + const S32 report_frequency = 10; + timer_expiry.setTimerExpirySec(report_frequency); + while(true) + { + if (timer_expiry.hasExpired()) + { + LL_INFOS() << "Infinite loop time : " << timer_total.getElapsedSeconds() << LL_ENDL; + timer_expiry.setTimerExpirySec(report_frequency); + } + } + return; +} + +void LLAppViewer::forceErrorSoftwareException() +{ + LL_WARNS() << "Forcing a deliberate exception" << LL_ENDL; + LLTHROW(LLException("User selected Force Software Exception")); +} + +void LLAppViewer::forceErrorOSSpecificException() +{ + // Virtual, MacOS only + const std::string exception_text = "User selected Force OS Exception, Not implemented on this OS"; + throw std::runtime_error(exception_text); +} + +void LLAppViewer::forceErrorDriverCrash() +{ + LL_WARNS() << "Forcing a deliberate driver crash" << LL_ENDL; + glDeleteTextures(1, NULL); +} + +void LLAppViewer::forceErrorCoroutineCrash() +{ + LL_WARNS() << "Forcing a crash in LLCoros" << LL_ENDL; + LLCoros::instance().launch("LLAppViewer::crashyCoro", [] {throw LLException("A deliberate crash from LLCoros"); }); +} + +void LLAppViewer::forceErrorThreadCrash() +{ + class LLCrashTestThread : public LLThread + { + public: + + LLCrashTestThread() : LLThread("Crash logging test thread") + { + } + + void run() + { + LL_ERRS() << "This is a deliberate llerror in thread" << LL_ENDL; + } + }; + + LL_WARNS() << "This is a deliberate crash in a thread" << LL_ENDL; + LLCrashTestThread *thread = new LLCrashTestThread(); + thread->start(); +} + +void LLAppViewer::initMainloopTimeout(const std::string& state, F32 secs) +{ + if(!mMainloopTimeout) + { + mMainloopTimeout = new LLWatchdogTimeout(); + resumeMainloopTimeout(state, secs); + } +} + +void LLAppViewer::destroyMainloopTimeout() +{ + if(mMainloopTimeout) + { + delete mMainloopTimeout; + mMainloopTimeout = NULL; + } +} + +void LLAppViewer::resumeMainloopTimeout(const std::string& state, F32 secs) +{ + if(mMainloopTimeout) + { + if(secs < 0.0f) + { + static LLCachedControl mainloop_timeout(gSavedSettings, "MainloopTimeoutDefault", 60); + secs = mainloop_timeout; + } + + mMainloopTimeout->setTimeout(secs); + mMainloopTimeout->start(state); + } +} + +void LLAppViewer::pauseMainloopTimeout() +{ + if(mMainloopTimeout) + { + mMainloopTimeout->stop(); + } +} + +void LLAppViewer::pingMainloopTimeout(const std::string& state, F32 secs) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_APP; + + if(mMainloopTimeout) + { + if(secs < 0.0f) + { + static LLCachedControl mainloop_timeout(gSavedSettings, "MainloopTimeoutDefault", 60); + secs = mainloop_timeout; + } + + mMainloopTimeout->setTimeout(secs); + mMainloopTimeout->ping(state); + } +} + +void LLAppViewer::handleLoginComplete() +{ + gLoggedInTime.start(); + initMainloopTimeout("Mainloop Init"); + + // Store some data to DebugInfo in case of a freeze. + gDebugInfo["ClientInfo"]["Name"] = LLVersionInfo::instance().getChannel(); + + gDebugInfo["ClientInfo"]["MajorVersion"] = LLVersionInfo::instance().getMajor(); + gDebugInfo["ClientInfo"]["MinorVersion"] = LLVersionInfo::instance().getMinor(); + gDebugInfo["ClientInfo"]["PatchVersion"] = LLVersionInfo::instance().getPatch(); + gDebugInfo["ClientInfo"]["BuildVersion"] = std::to_string(LLVersionInfo::instance().getBuild()); + + LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if ( parcel && parcel->getMusicURL()[0]) + { + gDebugInfo["ParcelMusicURL"] = parcel->getMusicURL(); + } + if ( parcel && parcel->getMediaURL()[0]) + { + gDebugInfo["ParcelMediaURL"] = parcel->getMediaURL(); + } + + gDebugInfo["SettingsFilename"] = gSavedSettings.getString("ClientSettingsFile"); + gDebugInfo["CAFilename"] = gDirUtilp->getCAFile(); + gDebugInfo["ViewerExePath"] = gDirUtilp->getExecutablePathAndName(); + gDebugInfo["CurrentPath"] = gDirUtilp->getCurPath(); + + if(gAgent.getRegion()) + { + gDebugInfo["CurrentSimHost"] = gAgent.getRegion()->getSimHostName(); + gDebugInfo["CurrentRegion"] = gAgent.getRegion()->getName(); + } + + if(LLAppViewer::instance()->mMainloopTimeout) + { + gDebugInfo["MainloopTimeoutState"] = LLAppViewer::instance()->mMainloopTimeout->getState(); + } + + mOnLoginCompleted(); + + writeDebugInfo(); + + // we logged in successfully, so save settings on logout + LL_INFOS() << "Login successful, per account settings will be saved on log out." << LL_ENDL; + mSavePerAccountSettings=true; +} + +//virtual +void LLAppViewer::setMasterSystemAudioMute(bool mute) +{ + gSavedSettings.setBOOL("MuteAudio", mute); +} + +//virtual +bool LLAppViewer::getMasterSystemAudioMute() +{ + return gSavedSettings.getBOOL("MuteAudio"); +} + +//---------------------------------------------------------------------------- +// Metrics-related methods (static and otherwise) +//---------------------------------------------------------------------------- + +/** + * LLViewerAssetStats collects data on a per-region (as defined by the agent's + * location) so we need to tell it about region changes which become a kind of + * hidden variable/global state in the collectors. For collectors not running + * on the main thread, we need to send a message to move the data over safely + * and cheaply (amortized over a run). + */ +void LLAppViewer::metricsUpdateRegion(U64 region_handle) +{ + if (0 != region_handle) + { + LLViewerAssetStatsFF::set_region(region_handle); + } +} + +/** + * Attempts to start a multi-threaded metrics report to be sent back to + * the grid for consumption. + */ +void LLAppViewer::metricsSend(bool enable_reporting) +{ + if (! gViewerAssetStats) + return; + + if (LLAppViewer::sTextureFetch) + { + LLViewerRegion * regionp = gAgent.getRegion(); + + if (enable_reporting && regionp) + { + std::string caps_url = regionp->getCapability("ViewerMetrics"); + + LLSD sd = gViewerAssetStats->asLLSD(true); + + // Send a report request into 'thread1' to get the rest of the data + // and provide some additional parameters while here. + LLAppViewer::sTextureFetch->commandSendMetrics(caps_url, + gAgentSessionID, + gAgentID, + sd); + } + else + { + LLAppViewer::sTextureFetch->commandDataBreak(); + } + } + + // Reset even if we can't report. Rather than gather up a huge chunk of + // data, we'll keep to our sampling interval and retain the data + // resolution in time. + gViewerAssetStats->restart(); +} + diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index fc13fd9093..4245e3da97 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -1,410 +1,410 @@ -/** - * @mainpage - * @mainpage - * - * This is the sources for the Second Life Viewer; - * information on the open source project is at - * https://wiki.secondlife.com/wiki/Open_Source_Portal - * - * The Mercurial repository for the trunk version is at - * https://bitbucket.org/lindenlab/viewer-release - * - * @section source-license Source License - * @verbinclude LICENSE-source.txt - * - * @section artwork-license Artwork License - * @verbinclude LICENSE-logos.txt - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - * - * @file llappviewer.h - * @brief The LLAppViewer class declaration - */ - -#ifndef LL_LLAPPVIEWER_H -#define LL_LLAPPVIEWER_H - -#include "llallocator.h" -#include "llapr.h" -#include "llcontrol.h" -#include "llsys.h" // for LLOSInfo -#include "lltimer.h" -#include "llappcorehttp.h" -#include "threadpool_fwd.h" - -#include - -class LLCommandLineParser; -class LLFrameTimer; -class LLPumpIO; -class LLTextureCache; -class LLImageDecodeThread; -class LLTextureFetch; -class LLWatchdogTimeout; -class LLViewerJoystick; -class LLPurgeDiskCacheThread; -class LLViewerRegion; - -extern LLTrace::BlockTimerStatHandle FTM_FRAME; - -class LLAppViewer : public LLApp -{ -public: - LLAppViewer(); - virtual ~LLAppViewer(); - - /** - * @brief Access to the LLAppViewer singleton. - * - * The LLAppViewer singleton is created in main()/WinMain(). - * So don't use it in pre-entry (static initialization) code. - */ - static LLAppViewer* instance() {return sInstance; } - - // - // Main application logic - // - virtual bool init(); // Override to do application initialization - virtual bool cleanup(); // Override to do application cleanup - virtual bool frame(); // Override for application body logic - - // Application control - void flushLFSIO(); // waits for lfs transfers to complete - void forceQuit(); // Puts the viewer into 'shutting down without error' mode. - void fastQuit(S32 error_code = 0); // Shuts down the viewer immediately after sending a logout message - void requestQuit(); // Request a quit. A kinder, gentler quit. - void userQuit(); // The users asks to quit. Confirm, then requestQuit() - void earlyExit(const std::string& name, - const LLSD& substitutions = LLSD()); // Display an error dialog and forcibly quit. - void earlyExitNoNotify(); // Do not display error dialog then forcibly quit. - void abortQuit(); // Called to abort a quit request. - - bool quitRequested() { return mQuitRequested; } - bool logoutRequestSent() { return mLogoutRequestSent; } - bool isSecondInstance() { return mSecondInstance; } - bool isUpdaterMissing(); // In use by tests - bool waitForUpdater(); - - void writeDebugInfo(bool isStatic=true); - - void setServerReleaseNotesURL(const std::string& url) { mServerReleaseNotesURL = url; } - LLSD getViewerInfo() const; - std::string getViewerInfoString(bool default_string = false) const; - - // Report true if under the control of a debugger. A null-op default. - virtual bool beingDebugged() { return false; } - - virtual bool restoreErrorTrap() = 0; // Require platform specific override to reset error handling mechanism. - // return false if the error trap needed restoration. - void checkForCrash(); - - // Thread accessors - static LLTextureCache* getTextureCache() { return sTextureCache; } - static LLImageDecodeThread* getImageDecodeThread() { return sImageDecodeThread; } - static LLTextureFetch* getTextureFetch() { return sTextureFetch; } - static LLPurgeDiskCacheThread* getPurgeDiskCacheThread() { return sPurgeDiskCacheThread; } - - static U32 getTextureCacheVersion() ; - static U32 getObjectCacheVersion() ; - static U32 getDiskCacheVersion() ; - - const std::string& getSerialNumber() { return mSerialNumber; } - - bool getPurgeCache() const { return mPurgeCache; } - - std::string getSecondLifeTitle() const; // The Second Life title. - std::string getWindowTitle() const; // The window display name. - - void forceDisconnect(const std::string& msg); // Force disconnection, with a message to the user. - void badNetworkHandler(); // Cause a crash state due to bad network packet. - - bool hasSavedFinalSnapshot() { return mSavedFinalSnapshot; } - void saveFinalSnapshot(); - - void loadNameCache(); - void saveNameCache(); - - void loadExperienceCache(); - void saveExperienceCache(); - - void removeMarkerFiles(); - - void removeDumpDir(); - // LLAppViewer testing helpers. - // *NOTE: These will potentially crash the viewer. Only for debugging. - virtual void forceErrorLLError(); - virtual void forceErrorLLErrorMsg(); - virtual void forceErrorBreakpoint(); - virtual void forceErrorBadMemoryAccess(); - virtual void forceErrorInfiniteLoop(); - virtual void forceErrorSoftwareException(); - virtual void forceErrorOSSpecificException(); - virtual void forceErrorDriverCrash(); - virtual void forceErrorCoroutineCrash(); - virtual void forceErrorThreadCrash(); - - // The list is found in app_settings/settings_files.xml - // but since they are used explicitly in code, - // the follow consts should also do the trick. - static const std::string sGlobalSettingsName; - - LLCachedControl mRandomizeFramerate; - LLCachedControl mPeriodicSlowFrame; - - // Load settings from the location specified by loction_key. - // Key availale and rules for loading, are specified in - // 'app_settings/settings_files.xml' - bool loadSettingsFromDirectory(const std::string& location_key, - bool set_defaults = false); - - std::string getSettingsFilename(const std::string& location_key, - const std::string& file); - void loadColorSettings(); - - // For thread debugging. - // llstartup needs to control init. - // llworld, send_agent_pause() also controls pause/resume. - void initMainloopTimeout(const std::string& state, F32 secs = -1.0f); - void destroyMainloopTimeout(); - void pauseMainloopTimeout(); - void resumeMainloopTimeout(const std::string& state = "", F32 secs = -1.0f); - void pingMainloopTimeout(const std::string& state, F32 secs = -1.0f); - - // Handle the 'login completed' event. - // *NOTE:Mani Fix this for login abstraction!! - void handleLoginComplete(); - - LLAllocator & getAllocator() { return mAlloc; } - - // On LoginCompleted callback - typedef boost::signals2::signal login_completed_signal_t; - login_completed_signal_t mOnLoginCompleted; - boost::signals2::connection setOnLoginCompletedCallback( const login_completed_signal_t::slot_type& cb ) - { - return mOnLoginCompleted.connect(cb); - } - - void addOnIdleCallback(const boost::function& cb); // add a callback to fire (once) when idle - - void initGeneralThread(); - void purgeUserDataOnExit() { mPurgeUserDataOnExit = true; } - void purgeCache(); // Clear the local cache. - void purgeCacheImmediate(); //clear local cache immediately. - S32 updateTextureThreads(F32 max_time); - - void loadKeyBindings(); - - // mute/unmute the system's master audio - virtual void setMasterSystemAudioMute(bool mute); - virtual bool getMasterSystemAudioMute(); - - // Metrics policy helper statics. - static void metricsUpdateRegion(U64 region_handle); - static void metricsSend(bool enable_reporting); - - // llcorehttp init/shutdown/config information. - LLAppCoreHttp & getAppCoreHttp() { return mAppCoreHttp; } - - void updateNameLookupUrl(const LLViewerRegion* regionp); - -protected: - virtual bool initWindow(); // Initialize the viewer's window. - virtual void initLoggingAndGetLastDuration(); // Initialize log files, logging system - virtual void initConsole() {}; // Initialize OS level debugging console. - virtual bool initHardwareTest() { return true; } // A false result indicates the app should quit. - virtual bool initSLURLHandler(); - virtual bool sendURLToOtherInstance(const std::string& url); - - virtual bool initParseCommandLine(LLCommandLineParser& clp) - { return true; } // Allow platforms to specify the command line args. - - virtual std::string generateSerialNumber() = 0; // Platforms specific classes generate this. - - virtual bool meetsRequirementsForMaximizedStart(); // Used on first login to decide to launch maximized - -private: - - bool doFrame(); - - void initMaxHeapSize(); - bool initThreads(); // Initialize viewer threads, return false on failure. - bool initConfiguration(); // Initialize settings from the command line/config file. - void initStrings(); // Initialize LLTrans machinery - bool initCache(); // Initialize local client cache. - - // We have switched locations of both Mac and Windows cache, make sure - // files migrate and old cache is cleared out. - void migrateCacheDirectory(); - - void cleanupSavedSettings(); // Sets some config data to current or default values during cleanup. - void removeCacheFiles(const std::string& filemask); // Deletes cached files the match the given wildcard. - - void writeSystemInfo(); // Write system info to "debug_info.log" - - void processMarkerFiles(); - static void recordMarkerVersion(LLAPRFile& marker_file); - bool markerIsSameVersion(const std::string& marker_name) const; - - void idle(); - void idleShutdown(); - // update avatar SLID and display name caches - void idleNameCache(); - void idleNetwork(); - - void sendLogoutRequest(); - void disconnectViewer(); - - // *FIX: the app viewer class should be some sort of singleton, no? - // Perhaps its child class is the singleton and this should be an abstract base. - static LLAppViewer* sInstance; - - bool mSecondInstance; // Is this a second instance of the app? - bool mUpdaterNotFound; // True when attempt to start updater failed - - std::string mMarkerFileName; - LLAPRFile mMarkerFile; // A file created to indicate the app is running. - - std::string mLogoutMarkerFileName; - LLAPRFile mLogoutMarkerFile; // A file created to indicate the app is running. - - bool mReportedCrash; - - std::string mServerReleaseNotesURL; - - // Thread objects. - static LLTextureCache* sTextureCache; - static LLImageDecodeThread* sImageDecodeThread; - static LLTextureFetch* sTextureFetch; - static LLPurgeDiskCacheThread* sPurgeDiskCacheThread; - LL::ThreadPool* mGeneralThreadPool; - - S32 mNumSessions; - - std::string mSerialNumber; - bool mPurgeCache; - bool mPurgeCacheOnExit; - bool mPurgeUserDataOnExit; - LLViewerJoystick* joystick; - - bool mSavedFinalSnapshot; - bool mSavePerAccountSettings; // only save per account settings if login succeeded - - boost::optional mForceGraphicsLevel; - - bool mQuitRequested; // User wants to quit, may have modified documents open. - bool mLogoutRequestSent; // Disconnect message sent to simulator, no longer safe to send messages to the sim. - U32 mLastAgentControlFlags; - F32 mLastAgentForceUpdate; - struct SettingsFiles* mSettingsLocationList; - - LLWatchdogTimeout* mMainloopTimeout; - - // For performance and metric gathering - class LLThread* mFastTimerLogThread; - - // for tracking viewer<->region circuit death - bool mAgentRegionLastAlive; - LLUUID mAgentRegionLastID; - - LLAllocator mAlloc; - - // llcorehttp library init/shutdown helper - LLAppCoreHttp mAppCoreHttp; - - bool mIsFirstRun; -}; - -// consts from viewer.h -const S32 AGENT_UPDATES_PER_SECOND = 10; -const S32 AGENT_FORCE_UPDATES_PER_SECOND = 1; - -// Globals with external linkage. From viewer.h -// *NOTE:Mani - These will be removed as the Viewer App Cleanup project continues. -// -// "// llstartup" indicates that llstartup is the only client for this global. - -extern LLSD gDebugInfo; -extern bool gShowObjectUpdates; - -typedef enum -{ - LAST_EXEC_NORMAL = 0, - LAST_EXEC_FROZE, - LAST_EXEC_LLERROR_CRASH, - LAST_EXEC_OTHER_CRASH, - LAST_EXEC_LOGOUT_FROZE, - LAST_EXEC_LOGOUT_CRASH -} eLastExecEvent; - -extern eLastExecEvent gLastExecEvent; // llstartup -extern S32 gLastExecDuration; ///< the duration of the previous run in seconds (<0 indicates unknown) - -extern const char* gPlatform; - -extern U32 gFrameCount; -extern U32 gForegroundFrameCount; - -extern LLPumpIO* gServicePump; - -extern U64MicrosecondsImplicit gStartTime; -extern U64MicrosecondsImplicit gFrameTime; // The timestamp of the most-recently-processed frame -extern F32SecondsImplicit gFrameTimeSeconds; // Loses msec precision after ~4.5 hours... -extern F32SecondsImplicit gFrameIntervalSeconds; // Elapsed time between current and previous gFrameTimeSeconds -extern F32 gFPSClamped; // Frames per second, smoothed, weighted toward last frame -extern F32 gFrameDTClamped; - -extern LLTimer gRenderStartTime; -extern LLFrameTimer gForegroundTime; -extern LLFrameTimer gLoggedInTime; - -extern F32 gLogoutMaxTime; -extern LLTimer gLogoutTimer; - -extern S32 gPendingMetricsUploads; - -extern F32 gSimLastTime; -extern F32 gSimFrames; - -extern bool gDisconnected; - -extern LLFrameTimer gRestoreGLTimer; -extern bool gRestoreGL; -extern bool gUseWireframe; - -extern LLMemoryInfo gSysMemory; -extern U64Bytes gMemoryAllocated; - -extern std::string gLastVersionChannel; - -extern LLVector3 gWindVec; -extern LLVector3 gRelativeWindVec; -extern U32 gPacketsIn; -extern bool gPrintMessagesThisFrame; - -extern LLUUID gBlackSquareID; - -extern bool gRandomizeFramerate; -extern bool gPeriodicSlowFrame; - -extern bool gSimulateMemLeak; - -#endif // LL_LLAPPVIEWER_H +/** + * @mainpage + * @mainpage + * + * This is the sources for the Second Life Viewer; + * information on the open source project is at + * https://wiki.secondlife.com/wiki/Open_Source_Portal + * + * The Mercurial repository for the trunk version is at + * https://bitbucket.org/lindenlab/viewer-release + * + * @section source-license Source License + * @verbinclude LICENSE-source.txt + * + * @section artwork-license Artwork License + * @verbinclude LICENSE-logos.txt + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + * + * @file llappviewer.h + * @brief The LLAppViewer class declaration + */ + +#ifndef LL_LLAPPVIEWER_H +#define LL_LLAPPVIEWER_H + +#include "llallocator.h" +#include "llapr.h" +#include "llcontrol.h" +#include "llsys.h" // for LLOSInfo +#include "lltimer.h" +#include "llappcorehttp.h" +#include "threadpool_fwd.h" + +#include + +class LLCommandLineParser; +class LLFrameTimer; +class LLPumpIO; +class LLTextureCache; +class LLImageDecodeThread; +class LLTextureFetch; +class LLWatchdogTimeout; +class LLViewerJoystick; +class LLPurgeDiskCacheThread; +class LLViewerRegion; + +extern LLTrace::BlockTimerStatHandle FTM_FRAME; + +class LLAppViewer : public LLApp +{ +public: + LLAppViewer(); + virtual ~LLAppViewer(); + + /** + * @brief Access to the LLAppViewer singleton. + * + * The LLAppViewer singleton is created in main()/WinMain(). + * So don't use it in pre-entry (static initialization) code. + */ + static LLAppViewer* instance() {return sInstance; } + + // + // Main application logic + // + virtual bool init(); // Override to do application initialization + virtual bool cleanup(); // Override to do application cleanup + virtual bool frame(); // Override for application body logic + + // Application control + void flushLFSIO(); // waits for lfs transfers to complete + void forceQuit(); // Puts the viewer into 'shutting down without error' mode. + void fastQuit(S32 error_code = 0); // Shuts down the viewer immediately after sending a logout message + void requestQuit(); // Request a quit. A kinder, gentler quit. + void userQuit(); // The users asks to quit. Confirm, then requestQuit() + void earlyExit(const std::string& name, + const LLSD& substitutions = LLSD()); // Display an error dialog and forcibly quit. + void earlyExitNoNotify(); // Do not display error dialog then forcibly quit. + void abortQuit(); // Called to abort a quit request. + + bool quitRequested() { return mQuitRequested; } + bool logoutRequestSent() { return mLogoutRequestSent; } + bool isSecondInstance() { return mSecondInstance; } + bool isUpdaterMissing(); // In use by tests + bool waitForUpdater(); + + void writeDebugInfo(bool isStatic=true); + + void setServerReleaseNotesURL(const std::string& url) { mServerReleaseNotesURL = url; } + LLSD getViewerInfo() const; + std::string getViewerInfoString(bool default_string = false) const; + + // Report true if under the control of a debugger. A null-op default. + virtual bool beingDebugged() { return false; } + + virtual bool restoreErrorTrap() = 0; // Require platform specific override to reset error handling mechanism. + // return false if the error trap needed restoration. + void checkForCrash(); + + // Thread accessors + static LLTextureCache* getTextureCache() { return sTextureCache; } + static LLImageDecodeThread* getImageDecodeThread() { return sImageDecodeThread; } + static LLTextureFetch* getTextureFetch() { return sTextureFetch; } + static LLPurgeDiskCacheThread* getPurgeDiskCacheThread() { return sPurgeDiskCacheThread; } + + static U32 getTextureCacheVersion() ; + static U32 getObjectCacheVersion() ; + static U32 getDiskCacheVersion() ; + + const std::string& getSerialNumber() { return mSerialNumber; } + + bool getPurgeCache() const { return mPurgeCache; } + + std::string getSecondLifeTitle() const; // The Second Life title. + std::string getWindowTitle() const; // The window display name. + + void forceDisconnect(const std::string& msg); // Force disconnection, with a message to the user. + void badNetworkHandler(); // Cause a crash state due to bad network packet. + + bool hasSavedFinalSnapshot() { return mSavedFinalSnapshot; } + void saveFinalSnapshot(); + + void loadNameCache(); + void saveNameCache(); + + void loadExperienceCache(); + void saveExperienceCache(); + + void removeMarkerFiles(); + + void removeDumpDir(); + // LLAppViewer testing helpers. + // *NOTE: These will potentially crash the viewer. Only for debugging. + virtual void forceErrorLLError(); + virtual void forceErrorLLErrorMsg(); + virtual void forceErrorBreakpoint(); + virtual void forceErrorBadMemoryAccess(); + virtual void forceErrorInfiniteLoop(); + virtual void forceErrorSoftwareException(); + virtual void forceErrorOSSpecificException(); + virtual void forceErrorDriverCrash(); + virtual void forceErrorCoroutineCrash(); + virtual void forceErrorThreadCrash(); + + // The list is found in app_settings/settings_files.xml + // but since they are used explicitly in code, + // the follow consts should also do the trick. + static const std::string sGlobalSettingsName; + + LLCachedControl mRandomizeFramerate; + LLCachedControl mPeriodicSlowFrame; + + // Load settings from the location specified by loction_key. + // Key availale and rules for loading, are specified in + // 'app_settings/settings_files.xml' + bool loadSettingsFromDirectory(const std::string& location_key, + bool set_defaults = false); + + std::string getSettingsFilename(const std::string& location_key, + const std::string& file); + void loadColorSettings(); + + // For thread debugging. + // llstartup needs to control init. + // llworld, send_agent_pause() also controls pause/resume. + void initMainloopTimeout(const std::string& state, F32 secs = -1.0f); + void destroyMainloopTimeout(); + void pauseMainloopTimeout(); + void resumeMainloopTimeout(const std::string& state = "", F32 secs = -1.0f); + void pingMainloopTimeout(const std::string& state, F32 secs = -1.0f); + + // Handle the 'login completed' event. + // *NOTE:Mani Fix this for login abstraction!! + void handleLoginComplete(); + + LLAllocator & getAllocator() { return mAlloc; } + + // On LoginCompleted callback + typedef boost::signals2::signal login_completed_signal_t; + login_completed_signal_t mOnLoginCompleted; + boost::signals2::connection setOnLoginCompletedCallback( const login_completed_signal_t::slot_type& cb ) + { + return mOnLoginCompleted.connect(cb); + } + + void addOnIdleCallback(const boost::function& cb); // add a callback to fire (once) when idle + + void initGeneralThread(); + void purgeUserDataOnExit() { mPurgeUserDataOnExit = true; } + void purgeCache(); // Clear the local cache. + void purgeCacheImmediate(); //clear local cache immediately. + S32 updateTextureThreads(F32 max_time); + + void loadKeyBindings(); + + // mute/unmute the system's master audio + virtual void setMasterSystemAudioMute(bool mute); + virtual bool getMasterSystemAudioMute(); + + // Metrics policy helper statics. + static void metricsUpdateRegion(U64 region_handle); + static void metricsSend(bool enable_reporting); + + // llcorehttp init/shutdown/config information. + LLAppCoreHttp & getAppCoreHttp() { return mAppCoreHttp; } + + void updateNameLookupUrl(const LLViewerRegion* regionp); + +protected: + virtual bool initWindow(); // Initialize the viewer's window. + virtual void initLoggingAndGetLastDuration(); // Initialize log files, logging system + virtual void initConsole() {}; // Initialize OS level debugging console. + virtual bool initHardwareTest() { return true; } // A false result indicates the app should quit. + virtual bool initSLURLHandler(); + virtual bool sendURLToOtherInstance(const std::string& url); + + virtual bool initParseCommandLine(LLCommandLineParser& clp) + { return true; } // Allow platforms to specify the command line args. + + virtual std::string generateSerialNumber() = 0; // Platforms specific classes generate this. + + virtual bool meetsRequirementsForMaximizedStart(); // Used on first login to decide to launch maximized + +private: + + bool doFrame(); + + void initMaxHeapSize(); + bool initThreads(); // Initialize viewer threads, return false on failure. + bool initConfiguration(); // Initialize settings from the command line/config file. + void initStrings(); // Initialize LLTrans machinery + bool initCache(); // Initialize local client cache. + + // We have switched locations of both Mac and Windows cache, make sure + // files migrate and old cache is cleared out. + void migrateCacheDirectory(); + + void cleanupSavedSettings(); // Sets some config data to current or default values during cleanup. + void removeCacheFiles(const std::string& filemask); // Deletes cached files the match the given wildcard. + + void writeSystemInfo(); // Write system info to "debug_info.log" + + void processMarkerFiles(); + static void recordMarkerVersion(LLAPRFile& marker_file); + bool markerIsSameVersion(const std::string& marker_name) const; + + void idle(); + void idleShutdown(); + // update avatar SLID and display name caches + void idleNameCache(); + void idleNetwork(); + + void sendLogoutRequest(); + void disconnectViewer(); + + // *FIX: the app viewer class should be some sort of singleton, no? + // Perhaps its child class is the singleton and this should be an abstract base. + static LLAppViewer* sInstance; + + bool mSecondInstance; // Is this a second instance of the app? + bool mUpdaterNotFound; // True when attempt to start updater failed + + std::string mMarkerFileName; + LLAPRFile mMarkerFile; // A file created to indicate the app is running. + + std::string mLogoutMarkerFileName; + LLAPRFile mLogoutMarkerFile; // A file created to indicate the app is running. + + bool mReportedCrash; + + std::string mServerReleaseNotesURL; + + // Thread objects. + static LLTextureCache* sTextureCache; + static LLImageDecodeThread* sImageDecodeThread; + static LLTextureFetch* sTextureFetch; + static LLPurgeDiskCacheThread* sPurgeDiskCacheThread; + LL::ThreadPool* mGeneralThreadPool; + + S32 mNumSessions; + + std::string mSerialNumber; + bool mPurgeCache; + bool mPurgeCacheOnExit; + bool mPurgeUserDataOnExit; + LLViewerJoystick* joystick; + + bool mSavedFinalSnapshot; + bool mSavePerAccountSettings; // only save per account settings if login succeeded + + boost::optional mForceGraphicsLevel; + + bool mQuitRequested; // User wants to quit, may have modified documents open. + bool mLogoutRequestSent; // Disconnect message sent to simulator, no longer safe to send messages to the sim. + U32 mLastAgentControlFlags; + F32 mLastAgentForceUpdate; + struct SettingsFiles* mSettingsLocationList; + + LLWatchdogTimeout* mMainloopTimeout; + + // For performance and metric gathering + class LLThread* mFastTimerLogThread; + + // for tracking viewer<->region circuit death + bool mAgentRegionLastAlive; + LLUUID mAgentRegionLastID; + + LLAllocator mAlloc; + + // llcorehttp library init/shutdown helper + LLAppCoreHttp mAppCoreHttp; + + bool mIsFirstRun; +}; + +// consts from viewer.h +const S32 AGENT_UPDATES_PER_SECOND = 10; +const S32 AGENT_FORCE_UPDATES_PER_SECOND = 1; + +// Globals with external linkage. From viewer.h +// *NOTE:Mani - These will be removed as the Viewer App Cleanup project continues. +// +// "// llstartup" indicates that llstartup is the only client for this global. + +extern LLSD gDebugInfo; +extern bool gShowObjectUpdates; + +typedef enum +{ + LAST_EXEC_NORMAL = 0, + LAST_EXEC_FROZE, + LAST_EXEC_LLERROR_CRASH, + LAST_EXEC_OTHER_CRASH, + LAST_EXEC_LOGOUT_FROZE, + LAST_EXEC_LOGOUT_CRASH +} eLastExecEvent; + +extern eLastExecEvent gLastExecEvent; // llstartup +extern S32 gLastExecDuration; ///< the duration of the previous run in seconds (<0 indicates unknown) + +extern const char* gPlatform; + +extern U32 gFrameCount; +extern U32 gForegroundFrameCount; + +extern LLPumpIO* gServicePump; + +extern U64MicrosecondsImplicit gStartTime; +extern U64MicrosecondsImplicit gFrameTime; // The timestamp of the most-recently-processed frame +extern F32SecondsImplicit gFrameTimeSeconds; // Loses msec precision after ~4.5 hours... +extern F32SecondsImplicit gFrameIntervalSeconds; // Elapsed time between current and previous gFrameTimeSeconds +extern F32 gFPSClamped; // Frames per second, smoothed, weighted toward last frame +extern F32 gFrameDTClamped; + +extern LLTimer gRenderStartTime; +extern LLFrameTimer gForegroundTime; +extern LLFrameTimer gLoggedInTime; + +extern F32 gLogoutMaxTime; +extern LLTimer gLogoutTimer; + +extern S32 gPendingMetricsUploads; + +extern F32 gSimLastTime; +extern F32 gSimFrames; + +extern bool gDisconnected; + +extern LLFrameTimer gRestoreGLTimer; +extern bool gRestoreGL; +extern bool gUseWireframe; + +extern LLMemoryInfo gSysMemory; +extern U64Bytes gMemoryAllocated; + +extern std::string gLastVersionChannel; + +extern LLVector3 gWindVec; +extern LLVector3 gRelativeWindVec; +extern U32 gPacketsIn; +extern bool gPrintMessagesThisFrame; + +extern LLUUID gBlackSquareID; + +extern bool gRandomizeFramerate; +extern bool gPeriodicSlowFrame; + +extern bool gSimulateMemLeak; + +#endif // LL_LLAPPVIEWER_H diff --git a/indra/newview/llappviewerlinux_api_dbus.cpp b/indra/newview/llappviewerlinux_api_dbus.cpp index 767517bce6..9aed8a98d4 100644 --- a/indra/newview/llappviewerlinux_api_dbus.cpp +++ b/indra/newview/llappviewerlinux_api_dbus.cpp @@ -1,126 +1,126 @@ -/** - * @file llappviewerlinux_api_dbus.cpp - * @brief dynamic DBus symbol-grabbing code - * - * $LicenseInfo:firstyear=2008&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#if LL_DBUS_ENABLED - -#include "linden_common.h" - -extern "C" { -#include - -#include "apr_pools.h" -#include "apr_dso.h" -} - -#define DEBUGMSG(...) do { LL_DEBUGS() << llformat(__VA_ARGS__) << LL_ENDL; } while(0) -#define INFOMSG(...) do { LL_INFOS() << llformat(__VA_ARGS__) << LL_ENDL; } while(0) -#define WARNMSG(...) do { LL_WARNS() << llformat(__VA_ARGS__) << LL_ENDL; } while(0) - -#define LL_DBUS_SYM(REQUIRED, DBUSSYM, RTN, ...) RTN (*ll##DBUSSYM)(__VA_ARGS__) = NULL -#include "llappviewerlinux_api_dbus_syms_raw.inc" -#undef LL_DBUS_SYM - -static bool sSymsGrabbed = false; -static apr_pool_t *sSymDBUSDSOMemoryPool = NULL; -static apr_dso_handle_t *sSymDBUSDSOHandleG = NULL; - -bool grab_dbus_syms(std::string dbus_dso_name) -{ - if (sSymsGrabbed) - { - // already have grabbed good syms - return true; - } - - bool sym_error = false; - bool rtn = false; - apr_status_t rv; - apr_dso_handle_t *sSymDBUSDSOHandle = NULL; - -#define LL_DBUS_SYM(REQUIRED, DBUSSYM, RTN, ...) do{rv = apr_dso_sym((apr_dso_handle_sym_t*)&ll##DBUSSYM, sSymDBUSDSOHandle, #DBUSSYM); if (rv != APR_SUCCESS) {INFOMSG("Failed to grab symbol: %s", #DBUSSYM); if (REQUIRED) sym_error = true;} else DEBUGMSG("grabbed symbol: %s from %p", #DBUSSYM, (void*)ll##DBUSSYM);}while(0) - - //attempt to load the shared library - apr_pool_create(&sSymDBUSDSOMemoryPool, NULL); - - if ( APR_SUCCESS == (rv = apr_dso_load(&sSymDBUSDSOHandle, - dbus_dso_name.c_str(), - sSymDBUSDSOMemoryPool) )) - { - INFOMSG("Found DSO: %s", dbus_dso_name.c_str()); - -#include "llappviewerlinux_api_dbus_syms_raw.inc" - - if ( sSymDBUSDSOHandle ) - { - sSymDBUSDSOHandleG = sSymDBUSDSOHandle; - sSymDBUSDSOHandle = NULL; - } - - rtn = !sym_error; - } - else - { - INFOMSG("Couldn't load DSO: %s", dbus_dso_name.c_str()); - rtn = false; // failure - } - - if (sym_error) - { - WARNMSG("Failed to find necessary symbols in DBUS-GLIB libraries."); - } -#undef LL_DBUS_SYM - - sSymsGrabbed = rtn; - return rtn; -} - - -void ungrab_dbus_syms() -{ - // should be safe to call regardless of whether we've - // actually grabbed syms. - - if ( sSymDBUSDSOHandleG ) - { - apr_dso_unload(sSymDBUSDSOHandleG); - sSymDBUSDSOHandleG = NULL; - } - - if ( sSymDBUSDSOMemoryPool ) - { - apr_pool_destroy(sSymDBUSDSOMemoryPool); - sSymDBUSDSOMemoryPool = NULL; - } - - // NULL-out all of the symbols we'd grabbed -#define LL_DBUS_SYM(REQUIRED, DBUSSYM, RTN, ...) do{ll##DBUSSYM = NULL;}while(0) -#include "llappviewerlinux_api_dbus_syms_raw.inc" -#undef LL_DBUS_SYM - - sSymsGrabbed = false; -} - -#endif // LL_DBUS_ENABLED +/** + * @file llappviewerlinux_api_dbus.cpp + * @brief dynamic DBus symbol-grabbing code + * + * $LicenseInfo:firstyear=2008&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#if LL_DBUS_ENABLED + +#include "linden_common.h" + +extern "C" { +#include + +#include "apr_pools.h" +#include "apr_dso.h" +} + +#define DEBUGMSG(...) do { LL_DEBUGS() << llformat(__VA_ARGS__) << LL_ENDL; } while(0) +#define INFOMSG(...) do { LL_INFOS() << llformat(__VA_ARGS__) << LL_ENDL; } while(0) +#define WARNMSG(...) do { LL_WARNS() << llformat(__VA_ARGS__) << LL_ENDL; } while(0) + +#define LL_DBUS_SYM(REQUIRED, DBUSSYM, RTN, ...) RTN (*ll##DBUSSYM)(__VA_ARGS__) = NULL +#include "llappviewerlinux_api_dbus_syms_raw.inc" +#undef LL_DBUS_SYM + +static bool sSymsGrabbed = false; +static apr_pool_t *sSymDBUSDSOMemoryPool = NULL; +static apr_dso_handle_t *sSymDBUSDSOHandleG = NULL; + +bool grab_dbus_syms(std::string dbus_dso_name) +{ + if (sSymsGrabbed) + { + // already have grabbed good syms + return true; + } + + bool sym_error = false; + bool rtn = false; + apr_status_t rv; + apr_dso_handle_t *sSymDBUSDSOHandle = NULL; + +#define LL_DBUS_SYM(REQUIRED, DBUSSYM, RTN, ...) do{rv = apr_dso_sym((apr_dso_handle_sym_t*)&ll##DBUSSYM, sSymDBUSDSOHandle, #DBUSSYM); if (rv != APR_SUCCESS) {INFOMSG("Failed to grab symbol: %s", #DBUSSYM); if (REQUIRED) sym_error = true;} else DEBUGMSG("grabbed symbol: %s from %p", #DBUSSYM, (void*)ll##DBUSSYM);}while(0) + + //attempt to load the shared library + apr_pool_create(&sSymDBUSDSOMemoryPool, NULL); + + if ( APR_SUCCESS == (rv = apr_dso_load(&sSymDBUSDSOHandle, + dbus_dso_name.c_str(), + sSymDBUSDSOMemoryPool) )) + { + INFOMSG("Found DSO: %s", dbus_dso_name.c_str()); + +#include "llappviewerlinux_api_dbus_syms_raw.inc" + + if ( sSymDBUSDSOHandle ) + { + sSymDBUSDSOHandleG = sSymDBUSDSOHandle; + sSymDBUSDSOHandle = NULL; + } + + rtn = !sym_error; + } + else + { + INFOMSG("Couldn't load DSO: %s", dbus_dso_name.c_str()); + rtn = false; // failure + } + + if (sym_error) + { + WARNMSG("Failed to find necessary symbols in DBUS-GLIB libraries."); + } +#undef LL_DBUS_SYM + + sSymsGrabbed = rtn; + return rtn; +} + + +void ungrab_dbus_syms() +{ + // should be safe to call regardless of whether we've + // actually grabbed syms. + + if ( sSymDBUSDSOHandleG ) + { + apr_dso_unload(sSymDBUSDSOHandleG); + sSymDBUSDSOHandleG = NULL; + } + + if ( sSymDBUSDSOMemoryPool ) + { + apr_pool_destroy(sSymDBUSDSOMemoryPool); + sSymDBUSDSOMemoryPool = NULL; + } + + // NULL-out all of the symbols we'd grabbed +#define LL_DBUS_SYM(REQUIRED, DBUSSYM, RTN, ...) do{ll##DBUSSYM = NULL;}while(0) +#include "llappviewerlinux_api_dbus_syms_raw.inc" +#undef LL_DBUS_SYM + + sSymsGrabbed = false; +} + +#endif // LL_DBUS_ENABLED diff --git a/indra/newview/llappviewermacosx.cpp b/indra/newview/llappviewermacosx.cpp index d018b2d111..4162c0479a 100644 --- a/indra/newview/llappviewermacosx.cpp +++ b/indra/newview/llappviewermacosx.cpp @@ -1,433 +1,433 @@ -/** - * @file llappviewermacosx.cpp - * @brief The LLAppViewerMacOSX class definitions - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#if !defined LL_DARWIN - #error "Use only with macOS" -#endif - -#define LL_CARBON_CRASH_HANDLER 1 - -#include "llwindowmacosx.h" -#include "llappviewermacosx-objc.h" - -#include "llappviewermacosx.h" -#include "llappviewermacosx-for-objc.h" -#include "llwindowmacosx-objc.h" -#include "llcommandlineparser.h" -#include "llsdserialize.h" - -#include "llviewernetwork.h" -#include "llviewercontrol.h" -#include "llmd5.h" -#include "llfloaterworldmap.h" -#include "llurldispatcher.h" -#include "llerrorcontrol.h" -#include "llvoavatarself.h" // for gAgentAvatarp->getFullname() -#include -#ifdef LL_CARBON_CRASH_HANDLER -#include -#endif -#include -#include -#include - -#include "lldir.h" -#include "lldiriterator.h" -#include -#include // for systemwide mute -class LLMediaCtrl; // for LLURLDispatcher - -namespace -{ - // The command line args stored. - // They are not used immediately by the app. - int gArgC; - char** gArgV; - LLAppViewerMacOSX* gViewerAppPtr = NULL; - std::string gHandleSLURL; -} - -void constructViewer() -{ - // Set the working dir to /Contents/Resources - if (chdir(gDirUtilp->getAppRODataDir().c_str()) == -1) - { - LL_WARNS("InitOSX") << "Could not change directory to " - << gDirUtilp->getAppRODataDir() << ": " << strerror(errno) - << LL_ENDL; - } - - gViewerAppPtr = new LLAppViewerMacOSX(); -} - -bool initViewer() -{ - bool ok = gViewerAppPtr->init(); - if(!ok) - { - LL_WARNS("InitOSX") << "Application init failed." << LL_ENDL; - } - else if (!gHandleSLURL.empty()) - { - dispatchUrl(gHandleSLURL); - gHandleSLURL = ""; - } - return ok; -} - -void handleQuit() -{ - LLAppViewer::instance()->userQuit(); -} - -// This function is called pumpMainLoop() rather than runMainLoop() because -// it passes control to the viewer's main-loop logic for a single frame. Like -// LLAppViewer::frame(), it returns 'true' when it's done. Until then, it -// expects to be called again by the timer in LLAppDelegate -// (llappdelegate-objc.mm). -bool pumpMainLoop() -{ - bool ret = LLApp::isQuitting(); - if (!ret && gViewerAppPtr != NULL) - { - ret = gViewerAppPtr->frame(); - } else { - ret = true; - } - - return ret; -} - -void cleanupViewer() -{ - if(!LLApp::isError()) - { - if (gViewerAppPtr) - gViewerAppPtr->cleanup(); - } - - delete gViewerAppPtr; - gViewerAppPtr = NULL; -} - -void clearDumpLogsDir() -{ - if (!LLAppViewer::instance()->isSecondInstance()) - { - gDirUtilp->deleteDirAndContents(gDirUtilp->getDumpLogsDirPath()); - } -} - -// The BugsplatMac API is structured as a number of different method -// overrides, each returning a different piece of metadata. But since we -// obtain such metadata by opening and parsing a file, it seems ridiculous to -// reopen and reparse it for every individual string desired. What we want is -// to open and parse the file once, retaining the data for subsequent -// requests. That's why this is an LLSingleton. -// Another approach would be to provide a function that simply returns -// CrashMetadata, storing the struct in LLAppDelegate, but nat doesn't know -// enough Objective-C++ to code that. We'd still have to detect which of the -// method overrides is called first so that the results are order-insensitive. -class CrashMetadataSingleton: public CrashMetadata, public LLSingleton -{ - LLSINGLETON(CrashMetadataSingleton); - - // convenience method to log each metadata field retrieved by constructor - std::string get_metadata(const LLSD& info, const LLSD::String& key) const - { - std::string data(info[key].asString()); - LL_INFOS("Bugsplat") << " " << key << "='" << data << "'" << LL_ENDL; - return data; - } -}; - -// Populate the fields of our public base-class struct. -CrashMetadataSingleton::CrashMetadataSingleton() -{ - // Note: we depend on being able to read the static_debug_info.log file - // from the *previous* run before we overwrite it with the new one for - // *this* run. LLAppViewer initialization must happen in the Right Order. - staticDebugPathname = *gViewerAppPtr->getStaticDebugFile(); - std::ifstream static_file(staticDebugPathname); - LLSD info; - if (! static_file.is_open()) - { - LL_WARNS("Bugsplat") << "Can't open '" << staticDebugPathname - << "'; no metadata about previous run" << LL_ENDL; - } - else if (! LLSDSerialize::deserialize(info, static_file, LLSDSerialize::SIZE_UNLIMITED)) - { - LL_WARNS("Bugsplat") << "Can't parse '" << staticDebugPathname - << "'; no metadata about previous run" << LL_ENDL; - } - else - { - LL_INFOS("Bugsplat") << "Previous run metadata from '" << staticDebugPathname << "':" << LL_ENDL; - logFilePathname = get_metadata(info, "SLLog"); - userSettingsPathname = get_metadata(info, "SettingsFilename"); - accountSettingsPathname = get_metadata(info, "PerAccountSettingsFilename"); - OSInfo = get_metadata(info, "OSInfo"); - agentFullname = get_metadata(info, "LoginName"); - // Translate underscores back to spaces - LLStringUtil::replaceChar(agentFullname, '_', ' '); - regionName = get_metadata(info, "CurrentRegion"); - fatalMessage = get_metadata(info, "FatalMessage"); - - if (gDirUtilp->fileExists(gDirUtilp->getDumpLogsDirPath())) - { - LLDirIterator file_iter(gDirUtilp->getDumpLogsDirPath(), "*.log"); - std::string file_name; - bool found = true; - while(found) - { - if((found = file_iter.next(file_name))) - { - std::string log_filename = gDirUtilp->getDumpLogsDirPath(file_name); - if(LLError::logFileName() != log_filename) - { - secondLogFilePathname = log_filename; - } - } - } - } - } -} - -// Avoid having to compile all of our LLSingleton machinery in Objective-C++. -CrashMetadata& CrashMetadata_instance() -{ - return CrashMetadataSingleton::instance(); -} - -void infos(const std::string& message) -{ - LL_INFOS("InitOSX", "Bugsplat") << message << LL_ENDL; -} - -int main( int argc, char **argv ) -{ - // Store off the command line args for use later. - gArgC = argc; - gArgV = argv; - return createNSApp(argc, (const char**)argv); -} - -LLAppViewerMacOSX::LLAppViewerMacOSX() -{ -} - -LLAppViewerMacOSX::~LLAppViewerMacOSX() -{ -} - -bool LLAppViewerMacOSX::init() -{ - return LLAppViewer::init(); -} - -void LLAppViewerMacOSX::forceErrorOSSpecificException() -{ - force_ns_sxeption(); -} - -// MacOSX may add and addition command line arguement for the process serial number. -// The option takes a form like '-psn_0_12345'. The following method should be able to recognize -// and either ignore or return a pair of values for the option. -// look for this method to be added to the parser in parseAndStoreResults. -std::pair parse_psn(const std::string& s) -{ - if (s.find("-psn_") == 0) - { - // *FIX:Mani Not sure that the value makes sense. - // fix it once the actual -psn_XXX syntax is known. - return std::make_pair("psn", s.substr(5)); - } - else - { - return std::make_pair(std::string(), std::string()); - } -} - -bool LLAppViewerMacOSX::initParseCommandLine(LLCommandLineParser& clp) -{ - // The next two lines add the support for parsing the mac -psn_XXX arg. - clp.addOptionDesc("psn", NULL, 1, "MacOSX process serial number"); - clp.setCustomParser(parse_psn); - - // parse the user's command line - if(clp.parseCommandLine(gArgC, gArgV) == false) - { - return false; - } - - // Get the user's preferred language string based on the Mac OS localization mechanism. - // To add a new localization: - // go to the "Resources" section of the project - // get info on "language.txt" - // in the "General" tab, click the "Add Localization" button - // create a new localization for the language you're adding - // set the contents of the new localization of the file to the string corresponding to our localization - // (i.e. "en", "ja", etc. Use the existing ones as a guide.) - CFURLRef url = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("language"), CFSTR("txt"), NULL); - char path[MAX_PATH]; - if(CFURLGetFileSystemRepresentation(url, false, (UInt8 *)path, sizeof(path))) - { - std::string lang; - if(_read_file_into_string(lang, path)) /* Flawfinder: ignore*/ - { - LLControlVariable* c = gSavedSettings.getControl("SystemLanguage"); - if(c) - { - c->setValue(lang, false); - } - } - } - CFRelease(url); - - return true; -} - -// *FIX:Mani It would be nice to provide a clean interface to get the -// default_unix_signal_handler for the LLApp class. -extern void default_unix_signal_handler(int, siginfo_t *, void *); -bool LLAppViewerMacOSX::restoreErrorTrap() -{ - // This method intends to reinstate signal handlers. - // *NOTE:Mani It was found that the first execution of a shader was overriding - // our initial signal handlers somehow. - // This method will be called (at least) once per mainloop execution. - // *NOTE:Mani The signals used below are copied over from the - // setup_signals() func in LLApp.cpp - // LLApp could use some way of overriding that func, but for this viewer - // fix I opt to avoid affecting the server code. - - // Set up signal handlers that may result in program termination - // - struct sigaction act; - struct sigaction old_act; - act.sa_sigaction = default_unix_signal_handler; - sigemptyset( &act.sa_mask ); - act.sa_flags = SA_SIGINFO; - - unsigned int reset_count = 0; - -#define SET_SIG(SIGNAL) sigaction(SIGNAL, &act, &old_act); \ - if(act.sa_sigaction != old_act.sa_sigaction) ++reset_count; - // Synchronous signals -# ifndef LL_BUGSPLAT - SET_SIG(SIGABRT) // let bugsplat catch this -# endif - SET_SIG(SIGALRM) - SET_SIG(SIGBUS) - SET_SIG(SIGFPE) - SET_SIG(SIGHUP) - SET_SIG(SIGILL) - SET_SIG(SIGPIPE) - SET_SIG(SIGSEGV) - SET_SIG(SIGSYS) - - SET_SIG(LL_HEARTBEAT_SIGNAL) - SET_SIG(LL_SMACKDOWN_SIGNAL) - - // Asynchronous signals that are normally ignored - SET_SIG(SIGCHLD) - SET_SIG(SIGUSR2) - - // Asynchronous signals that result in attempted graceful exit - SET_SIG(SIGHUP) - SET_SIG(SIGTERM) - SET_SIG(SIGINT) - - // Asynchronous signals that result in core - SET_SIG(SIGQUIT) -#undef SET_SIG - - return reset_count == 0; -} - -std::string LLAppViewerMacOSX::generateSerialNumber() -{ - char serial_md5[MD5HEX_STR_SIZE]; // Flawfinder: ignore - serial_md5[0] = 0; - - // JC: Sample code from http://developer.apple.com/technotes/tn/tn1103.html - CFStringRef serialNumber = NULL; - io_service_t platformExpert = IOServiceGetMatchingService(kIOMasterPortDefault, - IOServiceMatching("IOPlatformExpertDevice")); - if (platformExpert) - { - serialNumber = (CFStringRef) IORegistryEntryCreateCFProperty(platformExpert, - CFSTR(kIOPlatformSerialNumberKey), - kCFAllocatorDefault, 0); - IOObjectRelease(platformExpert); - } - - if (serialNumber) - { - char buffer[MAX_STRING]; // Flawfinder: ignore - if (CFStringGetCString(serialNumber, buffer, MAX_STRING, kCFStringEncodingASCII)) - { - LLMD5 md5( (unsigned char*)buffer ); - md5.hex_digest(serial_md5); - } - CFRelease(serialNumber); - } - - return serial_md5; -} - -void handleUrl(const char* url_utf8) -{ - if (url_utf8 && gViewerAppPtr) - { - gHandleSLURL = ""; - dispatchUrl(url_utf8); - } - else if (url_utf8) - { - gHandleSLURL = url_utf8; - } -} - -void dispatchUrl(std::string url) -{ - // Safari 3.2 silently mangles secondlife:///app/ URLs into - // secondlife:/app/ (only one leading slash). - // Fix them up to meet the URL specification. JC - const std::string prefix = "secondlife:/app/"; - std::string test_prefix = url.substr(0, prefix.length()); - LLStringUtil::toLower(test_prefix); - if (test_prefix == prefix) - { - url.replace(0, prefix.length(), "secondlife:///app/"); - } - - LLMediaCtrl* web = NULL; - const bool trusted_browser = false; - LLURLDispatcher::dispatch(url, "", web, trusted_browser); -} +/** + * @file llappviewermacosx.cpp + * @brief The LLAppViewerMacOSX class definitions + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#if !defined LL_DARWIN + #error "Use only with macOS" +#endif + +#define LL_CARBON_CRASH_HANDLER 1 + +#include "llwindowmacosx.h" +#include "llappviewermacosx-objc.h" + +#include "llappviewermacosx.h" +#include "llappviewermacosx-for-objc.h" +#include "llwindowmacosx-objc.h" +#include "llcommandlineparser.h" +#include "llsdserialize.h" + +#include "llviewernetwork.h" +#include "llviewercontrol.h" +#include "llmd5.h" +#include "llfloaterworldmap.h" +#include "llurldispatcher.h" +#include "llerrorcontrol.h" +#include "llvoavatarself.h" // for gAgentAvatarp->getFullname() +#include +#ifdef LL_CARBON_CRASH_HANDLER +#include +#endif +#include +#include +#include + +#include "lldir.h" +#include "lldiriterator.h" +#include +#include // for systemwide mute +class LLMediaCtrl; // for LLURLDispatcher + +namespace +{ + // The command line args stored. + // They are not used immediately by the app. + int gArgC; + char** gArgV; + LLAppViewerMacOSX* gViewerAppPtr = NULL; + std::string gHandleSLURL; +} + +void constructViewer() +{ + // Set the working dir to /Contents/Resources + if (chdir(gDirUtilp->getAppRODataDir().c_str()) == -1) + { + LL_WARNS("InitOSX") << "Could not change directory to " + << gDirUtilp->getAppRODataDir() << ": " << strerror(errno) + << LL_ENDL; + } + + gViewerAppPtr = new LLAppViewerMacOSX(); +} + +bool initViewer() +{ + bool ok = gViewerAppPtr->init(); + if(!ok) + { + LL_WARNS("InitOSX") << "Application init failed." << LL_ENDL; + } + else if (!gHandleSLURL.empty()) + { + dispatchUrl(gHandleSLURL); + gHandleSLURL = ""; + } + return ok; +} + +void handleQuit() +{ + LLAppViewer::instance()->userQuit(); +} + +// This function is called pumpMainLoop() rather than runMainLoop() because +// it passes control to the viewer's main-loop logic for a single frame. Like +// LLAppViewer::frame(), it returns 'true' when it's done. Until then, it +// expects to be called again by the timer in LLAppDelegate +// (llappdelegate-objc.mm). +bool pumpMainLoop() +{ + bool ret = LLApp::isQuitting(); + if (!ret && gViewerAppPtr != NULL) + { + ret = gViewerAppPtr->frame(); + } else { + ret = true; + } + + return ret; +} + +void cleanupViewer() +{ + if(!LLApp::isError()) + { + if (gViewerAppPtr) + gViewerAppPtr->cleanup(); + } + + delete gViewerAppPtr; + gViewerAppPtr = NULL; +} + +void clearDumpLogsDir() +{ + if (!LLAppViewer::instance()->isSecondInstance()) + { + gDirUtilp->deleteDirAndContents(gDirUtilp->getDumpLogsDirPath()); + } +} + +// The BugsplatMac API is structured as a number of different method +// overrides, each returning a different piece of metadata. But since we +// obtain such metadata by opening and parsing a file, it seems ridiculous to +// reopen and reparse it for every individual string desired. What we want is +// to open and parse the file once, retaining the data for subsequent +// requests. That's why this is an LLSingleton. +// Another approach would be to provide a function that simply returns +// CrashMetadata, storing the struct in LLAppDelegate, but nat doesn't know +// enough Objective-C++ to code that. We'd still have to detect which of the +// method overrides is called first so that the results are order-insensitive. +class CrashMetadataSingleton: public CrashMetadata, public LLSingleton +{ + LLSINGLETON(CrashMetadataSingleton); + + // convenience method to log each metadata field retrieved by constructor + std::string get_metadata(const LLSD& info, const LLSD::String& key) const + { + std::string data(info[key].asString()); + LL_INFOS("Bugsplat") << " " << key << "='" << data << "'" << LL_ENDL; + return data; + } +}; + +// Populate the fields of our public base-class struct. +CrashMetadataSingleton::CrashMetadataSingleton() +{ + // Note: we depend on being able to read the static_debug_info.log file + // from the *previous* run before we overwrite it with the new one for + // *this* run. LLAppViewer initialization must happen in the Right Order. + staticDebugPathname = *gViewerAppPtr->getStaticDebugFile(); + std::ifstream static_file(staticDebugPathname); + LLSD info; + if (! static_file.is_open()) + { + LL_WARNS("Bugsplat") << "Can't open '" << staticDebugPathname + << "'; no metadata about previous run" << LL_ENDL; + } + else if (! LLSDSerialize::deserialize(info, static_file, LLSDSerialize::SIZE_UNLIMITED)) + { + LL_WARNS("Bugsplat") << "Can't parse '" << staticDebugPathname + << "'; no metadata about previous run" << LL_ENDL; + } + else + { + LL_INFOS("Bugsplat") << "Previous run metadata from '" << staticDebugPathname << "':" << LL_ENDL; + logFilePathname = get_metadata(info, "SLLog"); + userSettingsPathname = get_metadata(info, "SettingsFilename"); + accountSettingsPathname = get_metadata(info, "PerAccountSettingsFilename"); + OSInfo = get_metadata(info, "OSInfo"); + agentFullname = get_metadata(info, "LoginName"); + // Translate underscores back to spaces + LLStringUtil::replaceChar(agentFullname, '_', ' '); + regionName = get_metadata(info, "CurrentRegion"); + fatalMessage = get_metadata(info, "FatalMessage"); + + if (gDirUtilp->fileExists(gDirUtilp->getDumpLogsDirPath())) + { + LLDirIterator file_iter(gDirUtilp->getDumpLogsDirPath(), "*.log"); + std::string file_name; + bool found = true; + while(found) + { + if((found = file_iter.next(file_name))) + { + std::string log_filename = gDirUtilp->getDumpLogsDirPath(file_name); + if(LLError::logFileName() != log_filename) + { + secondLogFilePathname = log_filename; + } + } + } + } + } +} + +// Avoid having to compile all of our LLSingleton machinery in Objective-C++. +CrashMetadata& CrashMetadata_instance() +{ + return CrashMetadataSingleton::instance(); +} + +void infos(const std::string& message) +{ + LL_INFOS("InitOSX", "Bugsplat") << message << LL_ENDL; +} + +int main( int argc, char **argv ) +{ + // Store off the command line args for use later. + gArgC = argc; + gArgV = argv; + return createNSApp(argc, (const char**)argv); +} + +LLAppViewerMacOSX::LLAppViewerMacOSX() +{ +} + +LLAppViewerMacOSX::~LLAppViewerMacOSX() +{ +} + +bool LLAppViewerMacOSX::init() +{ + return LLAppViewer::init(); +} + +void LLAppViewerMacOSX::forceErrorOSSpecificException() +{ + force_ns_sxeption(); +} + +// MacOSX may add and addition command line arguement for the process serial number. +// The option takes a form like '-psn_0_12345'. The following method should be able to recognize +// and either ignore or return a pair of values for the option. +// look for this method to be added to the parser in parseAndStoreResults. +std::pair parse_psn(const std::string& s) +{ + if (s.find("-psn_") == 0) + { + // *FIX:Mani Not sure that the value makes sense. + // fix it once the actual -psn_XXX syntax is known. + return std::make_pair("psn", s.substr(5)); + } + else + { + return std::make_pair(std::string(), std::string()); + } +} + +bool LLAppViewerMacOSX::initParseCommandLine(LLCommandLineParser& clp) +{ + // The next two lines add the support for parsing the mac -psn_XXX arg. + clp.addOptionDesc("psn", NULL, 1, "MacOSX process serial number"); + clp.setCustomParser(parse_psn); + + // parse the user's command line + if(clp.parseCommandLine(gArgC, gArgV) == false) + { + return false; + } + + // Get the user's preferred language string based on the Mac OS localization mechanism. + // To add a new localization: + // go to the "Resources" section of the project + // get info on "language.txt" + // in the "General" tab, click the "Add Localization" button + // create a new localization for the language you're adding + // set the contents of the new localization of the file to the string corresponding to our localization + // (i.e. "en", "ja", etc. Use the existing ones as a guide.) + CFURLRef url = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("language"), CFSTR("txt"), NULL); + char path[MAX_PATH]; + if(CFURLGetFileSystemRepresentation(url, false, (UInt8 *)path, sizeof(path))) + { + std::string lang; + if(_read_file_into_string(lang, path)) /* Flawfinder: ignore*/ + { + LLControlVariable* c = gSavedSettings.getControl("SystemLanguage"); + if(c) + { + c->setValue(lang, false); + } + } + } + CFRelease(url); + + return true; +} + +// *FIX:Mani It would be nice to provide a clean interface to get the +// default_unix_signal_handler for the LLApp class. +extern void default_unix_signal_handler(int, siginfo_t *, void *); +bool LLAppViewerMacOSX::restoreErrorTrap() +{ + // This method intends to reinstate signal handlers. + // *NOTE:Mani It was found that the first execution of a shader was overriding + // our initial signal handlers somehow. + // This method will be called (at least) once per mainloop execution. + // *NOTE:Mani The signals used below are copied over from the + // setup_signals() func in LLApp.cpp + // LLApp could use some way of overriding that func, but for this viewer + // fix I opt to avoid affecting the server code. + + // Set up signal handlers that may result in program termination + // + struct sigaction act; + struct sigaction old_act; + act.sa_sigaction = default_unix_signal_handler; + sigemptyset( &act.sa_mask ); + act.sa_flags = SA_SIGINFO; + + unsigned int reset_count = 0; + +#define SET_SIG(SIGNAL) sigaction(SIGNAL, &act, &old_act); \ + if(act.sa_sigaction != old_act.sa_sigaction) ++reset_count; + // Synchronous signals +# ifndef LL_BUGSPLAT + SET_SIG(SIGABRT) // let bugsplat catch this +# endif + SET_SIG(SIGALRM) + SET_SIG(SIGBUS) + SET_SIG(SIGFPE) + SET_SIG(SIGHUP) + SET_SIG(SIGILL) + SET_SIG(SIGPIPE) + SET_SIG(SIGSEGV) + SET_SIG(SIGSYS) + + SET_SIG(LL_HEARTBEAT_SIGNAL) + SET_SIG(LL_SMACKDOWN_SIGNAL) + + // Asynchronous signals that are normally ignored + SET_SIG(SIGCHLD) + SET_SIG(SIGUSR2) + + // Asynchronous signals that result in attempted graceful exit + SET_SIG(SIGHUP) + SET_SIG(SIGTERM) + SET_SIG(SIGINT) + + // Asynchronous signals that result in core + SET_SIG(SIGQUIT) +#undef SET_SIG + + return reset_count == 0; +} + +std::string LLAppViewerMacOSX::generateSerialNumber() +{ + char serial_md5[MD5HEX_STR_SIZE]; // Flawfinder: ignore + serial_md5[0] = 0; + + // JC: Sample code from http://developer.apple.com/technotes/tn/tn1103.html + CFStringRef serialNumber = NULL; + io_service_t platformExpert = IOServiceGetMatchingService(kIOMasterPortDefault, + IOServiceMatching("IOPlatformExpertDevice")); + if (platformExpert) + { + serialNumber = (CFStringRef) IORegistryEntryCreateCFProperty(platformExpert, + CFSTR(kIOPlatformSerialNumberKey), + kCFAllocatorDefault, 0); + IOObjectRelease(platformExpert); + } + + if (serialNumber) + { + char buffer[MAX_STRING]; // Flawfinder: ignore + if (CFStringGetCString(serialNumber, buffer, MAX_STRING, kCFStringEncodingASCII)) + { + LLMD5 md5( (unsigned char*)buffer ); + md5.hex_digest(serial_md5); + } + CFRelease(serialNumber); + } + + return serial_md5; +} + +void handleUrl(const char* url_utf8) +{ + if (url_utf8 && gViewerAppPtr) + { + gHandleSLURL = ""; + dispatchUrl(url_utf8); + } + else if (url_utf8) + { + gHandleSLURL = url_utf8; + } +} + +void dispatchUrl(std::string url) +{ + // Safari 3.2 silently mangles secondlife:///app/ URLs into + // secondlife:/app/ (only one leading slash). + // Fix them up to meet the URL specification. JC + const std::string prefix = "secondlife:/app/"; + std::string test_prefix = url.substr(0, prefix.length()); + LLStringUtil::toLower(test_prefix); + if (test_prefix == prefix) + { + url.replace(0, prefix.length(), "secondlife:///app/"); + } + + LLMediaCtrl* web = NULL; + const bool trusted_browser = false; + LLURLDispatcher::dispatch(url, "", web, trusted_browser); +} diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index 8451e8eb97..3d2964b930 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -1,1007 +1,1007 @@ -/** - * @file llappviewerwin32.cpp - * @brief The LLAppViewerWin32 class definitions - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#ifdef INCLUDE_VLD -#include "vld.h" -#endif -#include "llwin32headers.h" - -#include "llwindowwin32.h" // *FIX: for setting gIconResource. - -#include "llappviewerwin32.h" - -#include "llgl.h" -#include "res/resource.h" // *FIX: for setting gIconResource. - -#include //_O_APPEND -#include //_open_osfhandle() -#include // for WerAddExcludedApplication() -#include // _spawnl() -#include // For TCHAR support - -#include "llviewercontrol.h" -#include "lldxhardware.h" - -#include "nvapi/nvapi.h" -#include "nvapi/NvApiDriverSettings.h" - -#include - -#include "llweb.h" - -#include "llviewernetwork.h" -#include "llmd5.h" -#include "llfindlocale.h" - -#include "llcommandlineparser.h" -#include "lltrans.h" - -#ifndef LL_RELEASE_FOR_DOWNLOAD -#include "llwindebug.h" -#endif - -#include "stringize.h" -#include "lldir.h" -#include "llerrorcontrol.h" - -#include -#include - -// Bugsplat (http://bugsplat.com) crash reporting tool -#ifdef LL_BUGSPLAT -#include "BugSplat.h" -#include "boost/json.hpp" // Boost.Json -#include "llagent.h" // for agent location -#include "llviewerregion.h" -#include "llvoavatarself.h" // for agent name - -namespace -{ - // MiniDmpSender's constructor is defined to accept __wchar_t* instead of - // plain wchar_t*. That said, wunder() returns std::basic_string<__wchar_t>, - // NOT plain __wchar_t*, despite the apparent convenience. Calling - // wunder(something).c_str() as an argument expression is fine: that - // std::basic_string instance will survive until the function returns. - // Calling c_str() on a std::basic_string local to wunder() would be - // Undefined Behavior: we'd be left with a pointer into a destroyed - // std::basic_string instance. But we can do that with a macro... - #define WCSTR(string) wunder(string).c_str() - - // It would be nice if, when wchar_t is the same as __wchar_t, this whole - // function would optimize away. However, we use it only for the arguments - // to the BugSplat API -- a handful of calls. - inline std::basic_string<__wchar_t> wunder(const std::wstring& str) - { - return { str.begin(), str.end() }; - } - - // when what we have in hand is a std::string, convert from UTF-8 using - // specific wstringize() overload - inline std::basic_string<__wchar_t> wunder(const std::string& str) - { - return wunder(wstringize(str)); - } - - // Irritatingly, MiniDmpSender::setCallback() is defined to accept a - // classic-C function pointer instead of an arbitrary C++ callable. If it - // did accept a modern callable, we could pass a lambda that binds our - // MiniDmpSender pointer. As things stand, though, we must define an - // actual function and store the pointer statically. - static MiniDmpSender *sBugSplatSender = nullptr; - - bool bugsplatSendLog(UINT nCode, LPVOID lpVal1, LPVOID lpVal2) - { - if (nCode == MDSCB_EXCEPTIONCODE) - { - // send the main viewer log file, one per instance - // widen to wstring, convert to __wchar_t, then pass c_str() - sBugSplatSender->sendAdditionalFile( - WCSTR(LLError::logFileName())); - - // second instance does not have some log files - // TODO: This needs fixing, if each instance now has individual logs, - // same should be made true for static debug files - if (!LLAppViewer::instance()->isSecondInstance()) - { - sBugSplatSender->sendAdditionalFile( - WCSTR(*LLAppViewer::instance()->getStaticDebugFile())); - } - - sBugSplatSender->sendAdditionalFile( - WCSTR(gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "settings.xml"))); - - // We don't have an email address for any user. Hijack this - // metadata field for the platform identifier. - sBugSplatSender->setDefaultUserEmail( - WCSTR(STRINGIZE(LLOSInfo::instance().getOSStringSimple() << " (" - << ADDRESS_SIZE << "-bit)"))); - - if (gAgentAvatarp) - { - // user name, when we have it - sBugSplatSender->setDefaultUserName(WCSTR(gAgentAvatarp->getFullname())); - - sBugSplatSender->sendAdditionalFile( - WCSTR(gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "settings_per_account.xml"))); - } - - // LL_ERRS message, when there is one - sBugSplatSender->setDefaultUserDescription(WCSTR(LLError::getFatalMessage())); - - if (gAgent.getRegion()) - { - // region location, when we have it - LLVector3 loc = gAgent.getPositionAgent(); - sBugSplatSender->resetAppIdentifier( - WCSTR(STRINGIZE(gAgent.getRegion()->getName() - << '/' << loc.mV[0] - << '/' << loc.mV[1] - << '/' << loc.mV[2]))); - } - } // MDSCB_EXCEPTIONCODE - - return false; - } -} -#endif // LL_BUGSPLAT - -namespace -{ - void (*gOldTerminateHandler)() = NULL; -} - -static void exceptionTerminateHandler() -{ - // reinstall default terminate() handler in case we re-terminate. - if (gOldTerminateHandler) std::set_terminate(gOldTerminateHandler); - // treat this like a regular viewer crash, with nice stacktrace etc. - long *null_ptr; - null_ptr = 0; - *null_ptr = 0xDEADBEEF; //Force an exception that will trigger breakpad. - - // we've probably been killed-off before now, but... - gOldTerminateHandler(); // call old terminate() handler -} - -LONG WINAPI catchallCrashHandler(EXCEPTION_POINTERS * /*ExceptionInfo*/) -{ - LL_WARNS() << "Hit last ditch-effort attempt to catch crash." << LL_ENDL; - exceptionTerminateHandler(); - return 0; -} - -// *FIX:Mani - This hack is to fix a linker issue with libndofdev.lib -// The lib was compiled under VS2005 - in VS2003 we need to remap assert -#ifdef LL_DEBUG -#ifdef LL_MSVC7 -extern "C" { - void _wassert(const wchar_t * _Message, const wchar_t *_File, unsigned _Line) - { - LL_ERRS() << _Message << LL_ENDL; - } -} -#endif -#endif - -const std::string LLAppViewerWin32::sWindowClass = "Second Life"; - -/* - This function is used to print to the command line a text message - describing the nvapi error and quits -*/ -void nvapi_error(NvAPI_Status status) -{ - NvAPI_ShortString szDesc = {0}; - NvAPI_GetErrorMessage(status, szDesc); - LL_WARNS() << szDesc << LL_ENDL; - - //should always trigger when asserts are enabled - //llassert(status == NVAPI_OK); -} - -// Create app mutex creates a unique global windows object. -// If the object can be created it returns true, otherwise -// it returns false. The false result can be used to determine -// if another instance of a second life app (this vers. or later) -// is running. -// *NOTE: Do not use this method to run a single instance of the app. -// This is intended to help debug problems with the cross-platform -// locked file method used for that purpose. -bool create_app_mutex() -{ - bool result = true; - LPCWSTR unique_mutex_name = L"SecondLifeAppMutex"; - HANDLE hMutex; - hMutex = CreateMutex(NULL, TRUE, unique_mutex_name); - if(GetLastError() == ERROR_ALREADY_EXISTS) - { - result = false; - } - return result; -} - -void ll_nvapi_init(NvDRSSessionHandle hSession) -{ - // (2) load all the system settings into the session - NvAPI_Status status = NvAPI_DRS_LoadSettings(hSession); - if (status != NVAPI_OK) - { - nvapi_error(status); - return; - } - - NvAPI_UnicodeString profile_name; - std::string app_name = LLTrans::getString("APP_NAME"); - llutf16string w_app_name = utf8str_to_utf16str(app_name); - wsprintf(profile_name, L"%s", w_app_name.c_str()); - NvDRSProfileHandle hProfile = 0; - // (3) Check if we already have an application profile for the viewer - status = NvAPI_DRS_FindProfileByName(hSession, profile_name, &hProfile); - if (status != NVAPI_OK && status != NVAPI_PROFILE_NOT_FOUND) - { - nvapi_error(status); - return; - } - else if (status == NVAPI_PROFILE_NOT_FOUND) - { - // Don't have an application profile yet - create one - LL_INFOS() << "Creating NVIDIA application profile" << LL_ENDL; - - NVDRS_PROFILE profileInfo; - profileInfo.version = NVDRS_PROFILE_VER; - profileInfo.isPredefined = 0; - wsprintf(profileInfo.profileName, L"%s", w_app_name.c_str()); - - status = NvAPI_DRS_CreateProfile(hSession, &profileInfo, &hProfile); - if (status != NVAPI_OK) - { - nvapi_error(status); - return; - } - } - - // (4) Check if current exe is part of the profile - std::string exe_name = gDirUtilp->getExecutableFilename(); - NVDRS_APPLICATION profile_application; - profile_application.version = NVDRS_APPLICATION_VER; - - llutf16string w_exe_name = utf8str_to_utf16str(exe_name); - NvAPI_UnicodeString profile_app_name; - wsprintf(profile_app_name, L"%s", w_exe_name.c_str()); - - status = NvAPI_DRS_GetApplicationInfo(hSession, hProfile, profile_app_name, &profile_application); - if (status != NVAPI_OK && status != NVAPI_EXECUTABLE_NOT_FOUND) - { - nvapi_error(status); - return; - } - else if (status == NVAPI_EXECUTABLE_NOT_FOUND) - { - LL_INFOS() << "Creating application for " << exe_name << " for NVIDIA application profile" << LL_ENDL; - - // Add this exe to the profile - NVDRS_APPLICATION application; - application.version = NVDRS_APPLICATION_VER; - application.isPredefined = 0; - wsprintf(application.appName, L"%s", w_exe_name.c_str()); - wsprintf(application.userFriendlyName, L"%s", w_exe_name.c_str()); - wsprintf(application.launcher, L"%s", w_exe_name.c_str()); - wsprintf(application.fileInFolder, L"%s", ""); - - status = NvAPI_DRS_CreateApplication(hSession, hProfile, &application); - if (status != NVAPI_OK) - { - nvapi_error(status); - return; - } - - // Save application in case we added one - status = NvAPI_DRS_SaveSettings(hSession); - if (status != NVAPI_OK) - { - nvapi_error(status); - return; - } - } - - // load settings for querying - status = NvAPI_DRS_LoadSettings(hSession); - if (status != NVAPI_OK) - { - nvapi_error(status); - return; - } - - //get the preferred power management mode for Second Life - NVDRS_SETTING drsSetting = {0}; - drsSetting.version = NVDRS_SETTING_VER; - status = NvAPI_DRS_GetSetting(hSession, hProfile, PREFERRED_PSTATE_ID, &drsSetting); - if (status == NVAPI_SETTING_NOT_FOUND) - { //only override if the user hasn't specifically set this setting - // (5) Specify that we want to enable maximum performance setting - // first we fill the NVDRS_SETTING struct, then we call the function - drsSetting.version = NVDRS_SETTING_VER; - drsSetting.settingId = PREFERRED_PSTATE_ID; - drsSetting.settingType = NVDRS_DWORD_TYPE; - drsSetting.u32CurrentValue = PREFERRED_PSTATE_PREFER_MAX; - status = NvAPI_DRS_SetSetting(hSession, hProfile, &drsSetting); - if (status != NVAPI_OK) - { - nvapi_error(status); - return; - } - - // (6) Now we apply (or save) our changes to the system - status = NvAPI_DRS_SaveSettings(hSession); - if (status != NVAPI_OK) - { - nvapi_error(status); - return; - } - } - else if (status != NVAPI_OK) - { - nvapi_error(status); - return; - } - - // enable Threaded Optimization instead of letting the driver decide - status = NvAPI_DRS_GetSetting(hSession, hProfile, OGL_THREAD_CONTROL_ID, &drsSetting); - if (status == NVAPI_SETTING_NOT_FOUND || (status == NVAPI_OK && drsSetting.u32CurrentValue != OGL_THREAD_CONTROL_ENABLE)) - { - drsSetting.version = NVDRS_SETTING_VER; - drsSetting.settingId = OGL_THREAD_CONTROL_ID; - drsSetting.settingType = NVDRS_DWORD_TYPE; - drsSetting.u32CurrentValue = OGL_THREAD_CONTROL_ENABLE; - status = NvAPI_DRS_SetSetting(hSession, hProfile, &drsSetting); - if (status != NVAPI_OK) - { - nvapi_error(status); - return; - } - - // Now we apply (or save) our changes to the system - status = NvAPI_DRS_SaveSettings(hSession); - if (status != NVAPI_OK) - { - nvapi_error(status); - return; - } - } - else if (status != NVAPI_OK) - { - nvapi_error(status); - return; - } -} - -//#define DEBUGGING_SEH_FILTER 1 -#if DEBUGGING_SEH_FILTER -# define WINMAIN DebuggingWinMain -#else -# define WINMAIN wWinMain -#endif - -int APIENTRY WINMAIN(HINSTANCE hInstance, - HINSTANCE hPrevInstance, - PWSTR pCmdLine, - int nCmdShow) -{ - // Call Tracy first thing to have it allocate memory - // https://github.com/wolfpld/tracy/issues/196 - LL_PROFILER_FRAME_END; - LL_PROFILER_SET_THREAD_NAME("App"); - - const S32 MAX_HEAPS = 255; - DWORD heap_enable_lfh_error[MAX_HEAPS]; - S32 num_heaps = 0; - - LLWindowWin32::setDPIAwareness(); - -#if WINDOWS_CRT_MEM_CHECKS && !INCLUDE_VLD - _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); // dump memory leaks on exit -#elif 0 - // Experimental - enable the low fragmentation heap - // This results in a 2-3x improvement in opening a new Inventory window (which uses a large numebr of allocations) - // Note: This won't work when running from the debugger unless the _NO_DEBUG_HEAP environment variable is set to 1 - - // Enable to get mem debugging within visual studio. -#if LL_DEBUG - _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); -#else - _CrtSetDbgFlag(0); // default, just making explicit - - ULONG ulEnableLFH = 2; - HANDLE* hHeaps = new HANDLE[MAX_HEAPS]; - num_heaps = GetProcessHeaps(MAX_HEAPS, hHeaps); - for(S32 i = 0; i < num_heaps; i++) - { - bool success = HeapSetInformation(hHeaps[i], HeapCompatibilityInformation, &ulEnableLFH, sizeof(ulEnableLFH)); - if (success) - heap_enable_lfh_error[i] = 0; - else - heap_enable_lfh_error[i] = GetLastError(); - } -#endif -#endif - - // *FIX: global - gIconResource = MAKEINTRESOURCE(IDI_LL_ICON); - - LLAppViewerWin32* viewer_app_ptr = new LLAppViewerWin32(ll_convert_wide_to_string(pCmdLine).c_str()); - - gOldTerminateHandler = std::set_terminate(exceptionTerminateHandler); - - // Set a debug info flag to indicate if multiple instances are running. - bool found_other_instance = !create_app_mutex(); - gDebugInfo["FoundOtherInstanceAtStartup"] = LLSD::Boolean(found_other_instance); - - bool ok = viewer_app_ptr->init(); - if(!ok) - { - LL_WARNS() << "Application init failed." << LL_ENDL; - return -1; - } - - NvDRSSessionHandle hSession = 0; - static LLCachedControl use_nv_api(gSavedSettings, "NvAPICreateApplicationProfile", true); - if (use_nv_api) - { - NvAPI_Status status; - - // Initialize NVAPI - status = NvAPI_Initialize(); - - if (status == NVAPI_OK) - { - // Create the session handle to access driver settings - status = NvAPI_DRS_CreateSession(&hSession); - if (status != NVAPI_OK) - { - nvapi_error(status); - } - else - { - //override driver setting as needed - ll_nvapi_init(hSession); - } - } - } - - // Have to wait until after logging is initialized to display LFH info - if (num_heaps > 0) - { - LL_INFOS() << "Attempted to enable LFH for " << num_heaps << " heaps." << LL_ENDL; - for(S32 i = 0; i < num_heaps; i++) - { - if (heap_enable_lfh_error[i]) - { - LL_INFOS() << " Failed to enable LFH for heap: " << i << " Error: " << heap_enable_lfh_error[i] << LL_ENDL; - } - } - } - - // Run the application main loop - while (! viewer_app_ptr->frame()) - {} - - if (!LLApp::isError()) - { - // - // We don't want to do cleanup here if the error handler got called - - // the assumption is that the error handler is responsible for doing - // app cleanup if there was a problem. - // -#if WINDOWS_CRT_MEM_CHECKS - LL_INFOS() << "CRT Checking memory:" << LL_ENDL; - if (!_CrtCheckMemory()) - { - LL_WARNS() << "_CrtCheckMemory() failed at prior to cleanup!" << LL_ENDL; - } - else - { - LL_INFOS() << " No corruption detected." << LL_ENDL; - } -#endif - - gGLActive = true; - - viewer_app_ptr->cleanup(); - -#if WINDOWS_CRT_MEM_CHECKS - LL_INFOS() << "CRT Checking memory:" << LL_ENDL; - if (!_CrtCheckMemory()) - { - LL_WARNS() << "_CrtCheckMemory() failed after cleanup!" << LL_ENDL; - } - else - { - LL_INFOS() << " No corruption detected." << LL_ENDL; - } -#endif - - } - delete viewer_app_ptr; - viewer_app_ptr = NULL; - - // (NVAPI) (6) We clean up. This is analogous to doing a free() - if (hSession) - { - NvAPI_DRS_DestroySession(hSession); - hSession = 0; - } - - return 0; -} - -#if DEBUGGING_SEH_FILTER -// The compiler doesn't like it when you use __try/__except blocks -// in a method that uses object destructors. Go figure. -// This winmain just calls the real winmain inside __try. -// The __except calls our exception filter function. For debugging purposes. -int APIENTRY wWinMain(HINSTANCE hInstance, - HINSTANCE hPrevInstance, - PWSTR lpCmdLine, - int nCmdShow) -{ - __try - { - WINMAIN(hInstance, hPrevInstance, lpCmdLine, nCmdShow); - } - __except( viewer_windows_exception_handler( GetExceptionInformation() ) ) - { - _tprintf( _T("Exception handled.\n") ); - } -} -#endif - -void LLAppViewerWin32::disableWinErrorReporting() -{ - std::string executable_name = gDirUtilp->getExecutableFilename(); - - if( S_OK == WerAddExcludedApplication( utf8str_to_utf16str(executable_name).c_str(), FALSE ) ) - { - LL_INFOS() << "WerAddExcludedApplication() succeeded for " << executable_name << LL_ENDL; - } - else - { - LL_INFOS() << "WerAddExcludedApplication() failed for " << executable_name << LL_ENDL; - } -} - -const S32 MAX_CONSOLE_LINES = 7500; -// Only defined in newer SDKs than we currently use -#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING -#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 4 -#endif - -namespace { - -void set_stream(const char* desc, FILE* fp, DWORD handle_id, const char* name, const char* mode="w"); - -bool create_console() -{ - // allocate a console for this app - const bool isConsoleAllocated = AllocConsole(); - - if (isConsoleAllocated) - { - // set the screen buffer to be big enough to let us scroll text - CONSOLE_SCREEN_BUFFER_INFO coninfo; - GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo); - coninfo.dwSize.Y = MAX_CONSOLE_LINES; - SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize); - - // redirect unbuffered STDOUT to the console - set_stream("stdout", stdout, STD_OUTPUT_HANDLE, "CONOUT$"); - // redirect unbuffered STDERR to the console - set_stream("stderr", stderr, STD_ERROR_HANDLE, "CONOUT$"); - // redirect unbuffered STDIN to the console - // Don't bother: our console is solely for log output. We never read stdin. -// set_stream("stdin", stdin, STD_INPUT_HANDLE, "CONIN$", "r"); - } - - return isConsoleAllocated; -} - -void set_stream(const char* desc, FILE* fp, DWORD handle_id, const char* name, const char* mode) -{ - // SL-13528: This code used to be based on - // http://dslweb.nwnexus.com/~ast/dload/guicon.htm - // (referenced in https://stackoverflow.com/a/191880). - // But one of the comments on that StackOverflow answer points out that - // assigning to *stdout or *stderr "probably doesn't even work with the - // Universal CRT that was introduced in 2015," suggesting freopen_s() - // instead. Code below is based on https://stackoverflow.com/a/55875595. - auto std_handle = GetStdHandle(handle_id); - if (std_handle == INVALID_HANDLE_VALUE) - { - LL_WARNS() << "create_console() failed to get " << desc << " handle" << LL_ENDL; - } - else - { - if (mode == std::string("w")) - { - // Enable color processing on Windows 10 console windows. - DWORD dwMode = 0; - GetConsoleMode(std_handle, &dwMode); - dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; - SetConsoleMode(std_handle, dwMode); - } - // Redirect the passed fp to the console. - FILE* ignore; - if (freopen_s(&ignore, name, mode, fp) == 0) - { - // use unbuffered I/O - setvbuf( fp, NULL, _IONBF, 0 ); - } - } -} - -} // anonymous namespace - -LLAppViewerWin32::LLAppViewerWin32(const char* cmd_line) : - mCmdLine(cmd_line), - mIsConsoleAllocated(false) -{ -} - -LLAppViewerWin32::~LLAppViewerWin32() -{ -} - -bool LLAppViewerWin32::init() -{ - // Platform specific initialization. - - // Turn off Windows Error Reporting - // (Don't send our data to Microsoft--at least until we are Logo approved and have a way - // of getting the data back from them.) - // - // LL_INFOS() << "Turning off Windows error reporting." << LL_ENDL; - disableWinErrorReporting(); - -#ifndef LL_RELEASE_FOR_DOWNLOAD - // Merely requesting the LLSingleton instance initializes it. - LLWinDebug::instance(); -#endif - -#if LL_SEND_CRASH_REPORTS -#if ! defined(LL_BUGSPLAT) -#pragma message("Building without BugSplat") - -#else // LL_BUGSPLAT -#pragma message("Building with BugSplat") - - if (!isSecondInstance()) - { - // Cleanup previous session - std::string log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "bugsplat.log"); - LLFile::remove(log_file, ENOENT); - } - - // Win7 is no longer supported - bool is_win_7_or_below = LLOSInfo::getInstance()->mMajorVer <= 6 && LLOSInfo::getInstance()->mMajorVer <= 1; - - if (!is_win_7_or_below) - { - std::string build_data_fname( - gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, "build_data.json")); - // Use llifstream instead of std::ifstream because LL_PATH_EXECUTABLE - // could contain non-ASCII characters, which std::ifstream doesn't handle. - llifstream inf(build_data_fname.c_str()); - if (!inf.is_open()) - { - LL_WARNS("BUGSPLAT") << "Can't initialize BugSplat, can't read '" << build_data_fname - << "'" << LL_ENDL; - } - else - { - boost::json::error_code ec; - boost::json::value build_data = boost::json::parse(inf, ec); - if(ec.failed()) - { - // gah, the typo is baked into Json::Reader API - LL_WARNS("BUGSPLAT") << "Can't initialize BugSplat, can't parse '" << build_data_fname - << "': " << ec.what() << LL_ENDL; - } - else - { - if (!build_data.is_object() || !build_data.as_object().contains("BugSplat DB")) - { - LL_WARNS("BUGSPLAT") << "Can't initialize BugSplat, no 'BugSplat DB' entry in '" - << build_data_fname << "'" << LL_ENDL; - } - else - { - boost::json::value BugSplat_DB = build_data.at("BugSplat DB"); - - // Got BugSplat_DB, onward! - std::wstring version_string(WSTRINGIZE(LL_VIEWER_VERSION_MAJOR << '.' << - LL_VIEWER_VERSION_MINOR << '.' << - LL_VIEWER_VERSION_PATCH << '.' << - LL_VIEWER_VERSION_BUILD)); - - DWORD dwFlags = MDSF_NONINTERACTIVE | // automatically submit report without prompting - MDSF_PREVENTHIJACKING; // disallow swiping Exception filter - - bool needs_log_file = !isSecondInstance(); - LL_DEBUGS("BUGSPLAT"); - if (needs_log_file) - { - // Startup only! - LL_INFOS("BUGSPLAT") << "Engaged BugSplat logging to bugsplat.log" << LL_ENDL; - dwFlags |= MDSF_LOGFILE | MDSF_LOG_VERBOSE; - } - LL_ENDL; - - // have to convert normal wide strings to strings of __wchar_t - sBugSplatSender = new MiniDmpSender( - WCSTR(boost::json::value_to(BugSplat_DB)), - WCSTR(LL_TO_WSTRING(LL_VIEWER_CHANNEL)), - WCSTR(version_string), - nullptr, // szAppIdentifier -- set later - dwFlags); - sBugSplatSender->setCallback(bugsplatSendLog); - - LL_DEBUGS("BUGSPLAT"); - if (needs_log_file) - { - // Log file will be created in %TEMP%, but it will be moved into logs folder in case of crash - std::string log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "bugsplat.log"); - sBugSplatSender->setLogFilePath(WCSTR(log_file)); - } - LL_ENDL; - - // engage stringize() overload that converts from wstring - LL_INFOS("BUGSPLAT") << "Engaged BugSplat(" << LL_TO_STRING(LL_VIEWER_CHANNEL) - << ' ' << stringize(version_string) << ')' << LL_ENDL; - } // got BugSplat_DB - } // parsed build_data.json - } // opened build_data.json - } // !is_win_7_or_below -#endif // LL_BUGSPLAT -#endif // LL_SEND_CRASH_REPORTS - - bool success = LLAppViewer::init(); - - return success; -} - -bool LLAppViewerWin32::cleanup() -{ - bool result = LLAppViewer::cleanup(); - - gDXHardware.cleanup(); - - if (mIsConsoleAllocated) - { - FreeConsole(); - mIsConsoleAllocated = false; - } - - return result; -} - -void LLAppViewerWin32::reportCrashToBugsplat(void* pExcepInfo) -{ -#if defined(LL_BUGSPLAT) - if (sBugSplatSender) - { - sBugSplatSender->createReport((EXCEPTION_POINTERS*)pExcepInfo); - } -#endif // LL_BUGSPLAT -} - -void LLAppViewerWin32::initLoggingAndGetLastDuration() -{ - LLAppViewer::initLoggingAndGetLastDuration(); -} - -void LLAppViewerWin32::initConsole() -{ - // pop up debug console - mIsConsoleAllocated = create_console(); - return LLAppViewer::initConsole(); -} - -void write_debug_dx(const char* str) -{ - std::string value = gDebugInfo["DXInfo"].asString(); - value += str; - gDebugInfo["DXInfo"] = value; -} - -void write_debug_dx(const std::string& str) -{ - write_debug_dx(str.c_str()); -} - -bool LLAppViewerWin32::initHardwareTest() -{ - // - // Do driver verification and initialization based on DirectX - // hardware polling and driver versions - // - if (true == gSavedSettings.getBOOL("ProbeHardwareOnStartup") && false == gSavedSettings.getBOOL("NoHardwareProbe")) - { - // per DEV-11631 - disable hardware probing for everything - // but vram. - bool vram_only = true; - - LLSplashScreen::update(LLTrans::getString("StartupDetectingHardware")); - - LL_DEBUGS("AppInit") << "Attempting to poll DirectX for hardware info" << LL_ENDL; - gDXHardware.setWriteDebugFunc(write_debug_dx); - bool probe_ok = gDXHardware.getInfo(vram_only); - - if (!probe_ok - && gWarningSettings.getBOOL("AboutDirectX9")) - { - LL_WARNS("AppInit") << "DirectX probe failed, alerting user." << LL_ENDL; - - // Warn them that runnin without DirectX 9 will - // not allow us to tell them about driver issues - std::ostringstream msg; - msg << LLTrans::getString ("MBNoDirectX"); - S32 button = OSMessageBox( - msg.str(), - LLTrans::getString("MBWarning"), - OSMB_YESNO); - if (OSBTN_NO== button) - { - LL_INFOS("AppInit") << "User quitting after failed DirectX 9 detection" << LL_ENDL; - LLWeb::loadURLExternal("http://secondlife.com/support/", false); - return false; - } - gWarningSettings.setBOOL("AboutDirectX9", false); - } - LL_DEBUGS("AppInit") << "Done polling DirectX for hardware info" << LL_ENDL; - - // Only probe once after installation - gSavedSettings.setBOOL("ProbeHardwareOnStartup", false); - - // Disable so debugger can work - std::string splash_msg; - LLStringUtil::format_map_t args; - args["[APP_NAME]"] = LLAppViewer::instance()->getSecondLifeTitle(); - splash_msg = LLTrans::getString("StartupLoading", args); - - LLSplashScreen::update(splash_msg); - } - - if (!restoreErrorTrap()) - { - LL_WARNS("AppInit") << " Someone took over my exception handler (post hardware probe)!" << LL_ENDL; - } - - if (gGLManager.mVRAM == 0) - { - gGLManager.mVRAM = gDXHardware.getVRAM(); - } - - LL_INFOS("AppInit") << "Detected VRAM: " << gGLManager.mVRAM << LL_ENDL; - - return true; -} - -bool LLAppViewerWin32::initParseCommandLine(LLCommandLineParser& clp) -{ - if (!clp.parseCommandLineString(mCmdLine)) - { - return false; - } - - // Find the system language. - FL_Locale *locale = NULL; - FL_Success success = FL_FindLocale(&locale, FL_MESSAGES); - if (success != 0) - { - if (success >= 2 && locale->lang) // confident! - { - LL_INFOS("AppInit") << "Language: " << ll_safe_string(locale->lang) << LL_ENDL; - LL_INFOS("AppInit") << "Location: " << ll_safe_string(locale->country) << LL_ENDL; - LL_INFOS("AppInit") << "Variant: " << ll_safe_string(locale->variant) << LL_ENDL; - LLControlVariable* c = gSavedSettings.getControl("SystemLanguage"); - if(c) - { - c->setValue(std::string(locale->lang), false); - } - } - } - FL_FreeLocale(&locale); - - return true; -} - -bool LLAppViewerWin32::beingDebugged() -{ - return IsDebuggerPresent(); -} - -bool LLAppViewerWin32::restoreErrorTrap() -{ - return true; // we don't check for handler collisions on windows, so just say they're ok -} - -//virtual -bool LLAppViewerWin32::sendURLToOtherInstance(const std::string& url) -{ - wchar_t window_class[256]; /* Flawfinder: ignore */ // Assume max length < 255 chars. - mbstowcs(window_class, sWindowClass.c_str(), 255); - window_class[255] = 0; - // Use the class instead of the window name. - HWND other_window = FindWindow(window_class, NULL); - - if (other_window != NULL) - { - LL_DEBUGS() << "Found other window with the name '" << getWindowTitle() << "'" << LL_ENDL; - COPYDATASTRUCT cds; - const S32 SLURL_MESSAGE_TYPE = 0; - cds.dwData = SLURL_MESSAGE_TYPE; - cds.cbData = url.length() + 1; - cds.lpData = (void*)url.c_str(); - - LRESULT msg_result = SendMessage(other_window, WM_COPYDATA, NULL, (LPARAM)&cds); - LL_DEBUGS() << "SendMessage(WM_COPYDATA) to other window '" - << getWindowTitle() << "' returned " << msg_result << LL_ENDL; - return true; - } - return false; -} - - -std::string LLAppViewerWin32::generateSerialNumber() -{ - char serial_md5[MD5HEX_STR_SIZE]; // Flawfinder: ignore - serial_md5[0] = 0; - - DWORD serial = 0; - DWORD flags = 0; - BOOL success = GetVolumeInformation( - L"C:\\", - NULL, // volume name buffer - 0, // volume name buffer size - &serial, // volume serial - NULL, // max component length - &flags, // file system flags - NULL, // file system name buffer - 0); // file system name buffer size - if (success) - { - LLMD5 md5; - md5.update( (unsigned char*)&serial, sizeof(DWORD)); - md5.finalize(); - md5.hex_digest(serial_md5); - } - else - { - LL_WARNS() << "GetVolumeInformation failed" << LL_ENDL; - } - return serial_md5; -} +/** + * @file llappviewerwin32.cpp + * @brief The LLAppViewerWin32 class definitions + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#ifdef INCLUDE_VLD +#include "vld.h" +#endif +#include "llwin32headers.h" + +#include "llwindowwin32.h" // *FIX: for setting gIconResource. + +#include "llappviewerwin32.h" + +#include "llgl.h" +#include "res/resource.h" // *FIX: for setting gIconResource. + +#include //_O_APPEND +#include //_open_osfhandle() +#include // for WerAddExcludedApplication() +#include // _spawnl() +#include // For TCHAR support + +#include "llviewercontrol.h" +#include "lldxhardware.h" + +#include "nvapi/nvapi.h" +#include "nvapi/NvApiDriverSettings.h" + +#include + +#include "llweb.h" + +#include "llviewernetwork.h" +#include "llmd5.h" +#include "llfindlocale.h" + +#include "llcommandlineparser.h" +#include "lltrans.h" + +#ifndef LL_RELEASE_FOR_DOWNLOAD +#include "llwindebug.h" +#endif + +#include "stringize.h" +#include "lldir.h" +#include "llerrorcontrol.h" + +#include +#include + +// Bugsplat (http://bugsplat.com) crash reporting tool +#ifdef LL_BUGSPLAT +#include "BugSplat.h" +#include "boost/json.hpp" // Boost.Json +#include "llagent.h" // for agent location +#include "llviewerregion.h" +#include "llvoavatarself.h" // for agent name + +namespace +{ + // MiniDmpSender's constructor is defined to accept __wchar_t* instead of + // plain wchar_t*. That said, wunder() returns std::basic_string<__wchar_t>, + // NOT plain __wchar_t*, despite the apparent convenience. Calling + // wunder(something).c_str() as an argument expression is fine: that + // std::basic_string instance will survive until the function returns. + // Calling c_str() on a std::basic_string local to wunder() would be + // Undefined Behavior: we'd be left with a pointer into a destroyed + // std::basic_string instance. But we can do that with a macro... + #define WCSTR(string) wunder(string).c_str() + + // It would be nice if, when wchar_t is the same as __wchar_t, this whole + // function would optimize away. However, we use it only for the arguments + // to the BugSplat API -- a handful of calls. + inline std::basic_string<__wchar_t> wunder(const std::wstring& str) + { + return { str.begin(), str.end() }; + } + + // when what we have in hand is a std::string, convert from UTF-8 using + // specific wstringize() overload + inline std::basic_string<__wchar_t> wunder(const std::string& str) + { + return wunder(wstringize(str)); + } + + // Irritatingly, MiniDmpSender::setCallback() is defined to accept a + // classic-C function pointer instead of an arbitrary C++ callable. If it + // did accept a modern callable, we could pass a lambda that binds our + // MiniDmpSender pointer. As things stand, though, we must define an + // actual function and store the pointer statically. + static MiniDmpSender *sBugSplatSender = nullptr; + + bool bugsplatSendLog(UINT nCode, LPVOID lpVal1, LPVOID lpVal2) + { + if (nCode == MDSCB_EXCEPTIONCODE) + { + // send the main viewer log file, one per instance + // widen to wstring, convert to __wchar_t, then pass c_str() + sBugSplatSender->sendAdditionalFile( + WCSTR(LLError::logFileName())); + + // second instance does not have some log files + // TODO: This needs fixing, if each instance now has individual logs, + // same should be made true for static debug files + if (!LLAppViewer::instance()->isSecondInstance()) + { + sBugSplatSender->sendAdditionalFile( + WCSTR(*LLAppViewer::instance()->getStaticDebugFile())); + } + + sBugSplatSender->sendAdditionalFile( + WCSTR(gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "settings.xml"))); + + // We don't have an email address for any user. Hijack this + // metadata field for the platform identifier. + sBugSplatSender->setDefaultUserEmail( + WCSTR(STRINGIZE(LLOSInfo::instance().getOSStringSimple() << " (" + << ADDRESS_SIZE << "-bit)"))); + + if (gAgentAvatarp) + { + // user name, when we have it + sBugSplatSender->setDefaultUserName(WCSTR(gAgentAvatarp->getFullname())); + + sBugSplatSender->sendAdditionalFile( + WCSTR(gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "settings_per_account.xml"))); + } + + // LL_ERRS message, when there is one + sBugSplatSender->setDefaultUserDescription(WCSTR(LLError::getFatalMessage())); + + if (gAgent.getRegion()) + { + // region location, when we have it + LLVector3 loc = gAgent.getPositionAgent(); + sBugSplatSender->resetAppIdentifier( + WCSTR(STRINGIZE(gAgent.getRegion()->getName() + << '/' << loc.mV[0] + << '/' << loc.mV[1] + << '/' << loc.mV[2]))); + } + } // MDSCB_EXCEPTIONCODE + + return false; + } +} +#endif // LL_BUGSPLAT + +namespace +{ + void (*gOldTerminateHandler)() = NULL; +} + +static void exceptionTerminateHandler() +{ + // reinstall default terminate() handler in case we re-terminate. + if (gOldTerminateHandler) std::set_terminate(gOldTerminateHandler); + // treat this like a regular viewer crash, with nice stacktrace etc. + long *null_ptr; + null_ptr = 0; + *null_ptr = 0xDEADBEEF; //Force an exception that will trigger breakpad. + + // we've probably been killed-off before now, but... + gOldTerminateHandler(); // call old terminate() handler +} + +LONG WINAPI catchallCrashHandler(EXCEPTION_POINTERS * /*ExceptionInfo*/) +{ + LL_WARNS() << "Hit last ditch-effort attempt to catch crash." << LL_ENDL; + exceptionTerminateHandler(); + return 0; +} + +// *FIX:Mani - This hack is to fix a linker issue with libndofdev.lib +// The lib was compiled under VS2005 - in VS2003 we need to remap assert +#ifdef LL_DEBUG +#ifdef LL_MSVC7 +extern "C" { + void _wassert(const wchar_t * _Message, const wchar_t *_File, unsigned _Line) + { + LL_ERRS() << _Message << LL_ENDL; + } +} +#endif +#endif + +const std::string LLAppViewerWin32::sWindowClass = "Second Life"; + +/* + This function is used to print to the command line a text message + describing the nvapi error and quits +*/ +void nvapi_error(NvAPI_Status status) +{ + NvAPI_ShortString szDesc = {0}; + NvAPI_GetErrorMessage(status, szDesc); + LL_WARNS() << szDesc << LL_ENDL; + + //should always trigger when asserts are enabled + //llassert(status == NVAPI_OK); +} + +// Create app mutex creates a unique global windows object. +// If the object can be created it returns true, otherwise +// it returns false. The false result can be used to determine +// if another instance of a second life app (this vers. or later) +// is running. +// *NOTE: Do not use this method to run a single instance of the app. +// This is intended to help debug problems with the cross-platform +// locked file method used for that purpose. +bool create_app_mutex() +{ + bool result = true; + LPCWSTR unique_mutex_name = L"SecondLifeAppMutex"; + HANDLE hMutex; + hMutex = CreateMutex(NULL, TRUE, unique_mutex_name); + if(GetLastError() == ERROR_ALREADY_EXISTS) + { + result = false; + } + return result; +} + +void ll_nvapi_init(NvDRSSessionHandle hSession) +{ + // (2) load all the system settings into the session + NvAPI_Status status = NvAPI_DRS_LoadSettings(hSession); + if (status != NVAPI_OK) + { + nvapi_error(status); + return; + } + + NvAPI_UnicodeString profile_name; + std::string app_name = LLTrans::getString("APP_NAME"); + llutf16string w_app_name = utf8str_to_utf16str(app_name); + wsprintf(profile_name, L"%s", w_app_name.c_str()); + NvDRSProfileHandle hProfile = 0; + // (3) Check if we already have an application profile for the viewer + status = NvAPI_DRS_FindProfileByName(hSession, profile_name, &hProfile); + if (status != NVAPI_OK && status != NVAPI_PROFILE_NOT_FOUND) + { + nvapi_error(status); + return; + } + else if (status == NVAPI_PROFILE_NOT_FOUND) + { + // Don't have an application profile yet - create one + LL_INFOS() << "Creating NVIDIA application profile" << LL_ENDL; + + NVDRS_PROFILE profileInfo; + profileInfo.version = NVDRS_PROFILE_VER; + profileInfo.isPredefined = 0; + wsprintf(profileInfo.profileName, L"%s", w_app_name.c_str()); + + status = NvAPI_DRS_CreateProfile(hSession, &profileInfo, &hProfile); + if (status != NVAPI_OK) + { + nvapi_error(status); + return; + } + } + + // (4) Check if current exe is part of the profile + std::string exe_name = gDirUtilp->getExecutableFilename(); + NVDRS_APPLICATION profile_application; + profile_application.version = NVDRS_APPLICATION_VER; + + llutf16string w_exe_name = utf8str_to_utf16str(exe_name); + NvAPI_UnicodeString profile_app_name; + wsprintf(profile_app_name, L"%s", w_exe_name.c_str()); + + status = NvAPI_DRS_GetApplicationInfo(hSession, hProfile, profile_app_name, &profile_application); + if (status != NVAPI_OK && status != NVAPI_EXECUTABLE_NOT_FOUND) + { + nvapi_error(status); + return; + } + else if (status == NVAPI_EXECUTABLE_NOT_FOUND) + { + LL_INFOS() << "Creating application for " << exe_name << " for NVIDIA application profile" << LL_ENDL; + + // Add this exe to the profile + NVDRS_APPLICATION application; + application.version = NVDRS_APPLICATION_VER; + application.isPredefined = 0; + wsprintf(application.appName, L"%s", w_exe_name.c_str()); + wsprintf(application.userFriendlyName, L"%s", w_exe_name.c_str()); + wsprintf(application.launcher, L"%s", w_exe_name.c_str()); + wsprintf(application.fileInFolder, L"%s", ""); + + status = NvAPI_DRS_CreateApplication(hSession, hProfile, &application); + if (status != NVAPI_OK) + { + nvapi_error(status); + return; + } + + // Save application in case we added one + status = NvAPI_DRS_SaveSettings(hSession); + if (status != NVAPI_OK) + { + nvapi_error(status); + return; + } + } + + // load settings for querying + status = NvAPI_DRS_LoadSettings(hSession); + if (status != NVAPI_OK) + { + nvapi_error(status); + return; + } + + //get the preferred power management mode for Second Life + NVDRS_SETTING drsSetting = {0}; + drsSetting.version = NVDRS_SETTING_VER; + status = NvAPI_DRS_GetSetting(hSession, hProfile, PREFERRED_PSTATE_ID, &drsSetting); + if (status == NVAPI_SETTING_NOT_FOUND) + { //only override if the user hasn't specifically set this setting + // (5) Specify that we want to enable maximum performance setting + // first we fill the NVDRS_SETTING struct, then we call the function + drsSetting.version = NVDRS_SETTING_VER; + drsSetting.settingId = PREFERRED_PSTATE_ID; + drsSetting.settingType = NVDRS_DWORD_TYPE; + drsSetting.u32CurrentValue = PREFERRED_PSTATE_PREFER_MAX; + status = NvAPI_DRS_SetSetting(hSession, hProfile, &drsSetting); + if (status != NVAPI_OK) + { + nvapi_error(status); + return; + } + + // (6) Now we apply (or save) our changes to the system + status = NvAPI_DRS_SaveSettings(hSession); + if (status != NVAPI_OK) + { + nvapi_error(status); + return; + } + } + else if (status != NVAPI_OK) + { + nvapi_error(status); + return; + } + + // enable Threaded Optimization instead of letting the driver decide + status = NvAPI_DRS_GetSetting(hSession, hProfile, OGL_THREAD_CONTROL_ID, &drsSetting); + if (status == NVAPI_SETTING_NOT_FOUND || (status == NVAPI_OK && drsSetting.u32CurrentValue != OGL_THREAD_CONTROL_ENABLE)) + { + drsSetting.version = NVDRS_SETTING_VER; + drsSetting.settingId = OGL_THREAD_CONTROL_ID; + drsSetting.settingType = NVDRS_DWORD_TYPE; + drsSetting.u32CurrentValue = OGL_THREAD_CONTROL_ENABLE; + status = NvAPI_DRS_SetSetting(hSession, hProfile, &drsSetting); + if (status != NVAPI_OK) + { + nvapi_error(status); + return; + } + + // Now we apply (or save) our changes to the system + status = NvAPI_DRS_SaveSettings(hSession); + if (status != NVAPI_OK) + { + nvapi_error(status); + return; + } + } + else if (status != NVAPI_OK) + { + nvapi_error(status); + return; + } +} + +//#define DEBUGGING_SEH_FILTER 1 +#if DEBUGGING_SEH_FILTER +# define WINMAIN DebuggingWinMain +#else +# define WINMAIN wWinMain +#endif + +int APIENTRY WINMAIN(HINSTANCE hInstance, + HINSTANCE hPrevInstance, + PWSTR pCmdLine, + int nCmdShow) +{ + // Call Tracy first thing to have it allocate memory + // https://github.com/wolfpld/tracy/issues/196 + LL_PROFILER_FRAME_END; + LL_PROFILER_SET_THREAD_NAME("App"); + + const S32 MAX_HEAPS = 255; + DWORD heap_enable_lfh_error[MAX_HEAPS]; + S32 num_heaps = 0; + + LLWindowWin32::setDPIAwareness(); + +#if WINDOWS_CRT_MEM_CHECKS && !INCLUDE_VLD + _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); // dump memory leaks on exit +#elif 0 + // Experimental - enable the low fragmentation heap + // This results in a 2-3x improvement in opening a new Inventory window (which uses a large numebr of allocations) + // Note: This won't work when running from the debugger unless the _NO_DEBUG_HEAP environment variable is set to 1 + + // Enable to get mem debugging within visual studio. +#if LL_DEBUG + _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); +#else + _CrtSetDbgFlag(0); // default, just making explicit + + ULONG ulEnableLFH = 2; + HANDLE* hHeaps = new HANDLE[MAX_HEAPS]; + num_heaps = GetProcessHeaps(MAX_HEAPS, hHeaps); + for(S32 i = 0; i < num_heaps; i++) + { + bool success = HeapSetInformation(hHeaps[i], HeapCompatibilityInformation, &ulEnableLFH, sizeof(ulEnableLFH)); + if (success) + heap_enable_lfh_error[i] = 0; + else + heap_enable_lfh_error[i] = GetLastError(); + } +#endif +#endif + + // *FIX: global + gIconResource = MAKEINTRESOURCE(IDI_LL_ICON); + + LLAppViewerWin32* viewer_app_ptr = new LLAppViewerWin32(ll_convert_wide_to_string(pCmdLine).c_str()); + + gOldTerminateHandler = std::set_terminate(exceptionTerminateHandler); + + // Set a debug info flag to indicate if multiple instances are running. + bool found_other_instance = !create_app_mutex(); + gDebugInfo["FoundOtherInstanceAtStartup"] = LLSD::Boolean(found_other_instance); + + bool ok = viewer_app_ptr->init(); + if(!ok) + { + LL_WARNS() << "Application init failed." << LL_ENDL; + return -1; + } + + NvDRSSessionHandle hSession = 0; + static LLCachedControl use_nv_api(gSavedSettings, "NvAPICreateApplicationProfile", true); + if (use_nv_api) + { + NvAPI_Status status; + + // Initialize NVAPI + status = NvAPI_Initialize(); + + if (status == NVAPI_OK) + { + // Create the session handle to access driver settings + status = NvAPI_DRS_CreateSession(&hSession); + if (status != NVAPI_OK) + { + nvapi_error(status); + } + else + { + //override driver setting as needed + ll_nvapi_init(hSession); + } + } + } + + // Have to wait until after logging is initialized to display LFH info + if (num_heaps > 0) + { + LL_INFOS() << "Attempted to enable LFH for " << num_heaps << " heaps." << LL_ENDL; + for(S32 i = 0; i < num_heaps; i++) + { + if (heap_enable_lfh_error[i]) + { + LL_INFOS() << " Failed to enable LFH for heap: " << i << " Error: " << heap_enable_lfh_error[i] << LL_ENDL; + } + } + } + + // Run the application main loop + while (! viewer_app_ptr->frame()) + {} + + if (!LLApp::isError()) + { + // + // We don't want to do cleanup here if the error handler got called - + // the assumption is that the error handler is responsible for doing + // app cleanup if there was a problem. + // +#if WINDOWS_CRT_MEM_CHECKS + LL_INFOS() << "CRT Checking memory:" << LL_ENDL; + if (!_CrtCheckMemory()) + { + LL_WARNS() << "_CrtCheckMemory() failed at prior to cleanup!" << LL_ENDL; + } + else + { + LL_INFOS() << " No corruption detected." << LL_ENDL; + } +#endif + + gGLActive = true; + + viewer_app_ptr->cleanup(); + +#if WINDOWS_CRT_MEM_CHECKS + LL_INFOS() << "CRT Checking memory:" << LL_ENDL; + if (!_CrtCheckMemory()) + { + LL_WARNS() << "_CrtCheckMemory() failed after cleanup!" << LL_ENDL; + } + else + { + LL_INFOS() << " No corruption detected." << LL_ENDL; + } +#endif + + } + delete viewer_app_ptr; + viewer_app_ptr = NULL; + + // (NVAPI) (6) We clean up. This is analogous to doing a free() + if (hSession) + { + NvAPI_DRS_DestroySession(hSession); + hSession = 0; + } + + return 0; +} + +#if DEBUGGING_SEH_FILTER +// The compiler doesn't like it when you use __try/__except blocks +// in a method that uses object destructors. Go figure. +// This winmain just calls the real winmain inside __try. +// The __except calls our exception filter function. For debugging purposes. +int APIENTRY wWinMain(HINSTANCE hInstance, + HINSTANCE hPrevInstance, + PWSTR lpCmdLine, + int nCmdShow) +{ + __try + { + WINMAIN(hInstance, hPrevInstance, lpCmdLine, nCmdShow); + } + __except( viewer_windows_exception_handler( GetExceptionInformation() ) ) + { + _tprintf( _T("Exception handled.\n") ); + } +} +#endif + +void LLAppViewerWin32::disableWinErrorReporting() +{ + std::string executable_name = gDirUtilp->getExecutableFilename(); + + if( S_OK == WerAddExcludedApplication( utf8str_to_utf16str(executable_name).c_str(), FALSE ) ) + { + LL_INFOS() << "WerAddExcludedApplication() succeeded for " << executable_name << LL_ENDL; + } + else + { + LL_INFOS() << "WerAddExcludedApplication() failed for " << executable_name << LL_ENDL; + } +} + +const S32 MAX_CONSOLE_LINES = 7500; +// Only defined in newer SDKs than we currently use +#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING +#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 4 +#endif + +namespace { + +void set_stream(const char* desc, FILE* fp, DWORD handle_id, const char* name, const char* mode="w"); + +bool create_console() +{ + // allocate a console for this app + const bool isConsoleAllocated = AllocConsole(); + + if (isConsoleAllocated) + { + // set the screen buffer to be big enough to let us scroll text + CONSOLE_SCREEN_BUFFER_INFO coninfo; + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &coninfo); + coninfo.dwSize.Y = MAX_CONSOLE_LINES; + SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE), coninfo.dwSize); + + // redirect unbuffered STDOUT to the console + set_stream("stdout", stdout, STD_OUTPUT_HANDLE, "CONOUT$"); + // redirect unbuffered STDERR to the console + set_stream("stderr", stderr, STD_ERROR_HANDLE, "CONOUT$"); + // redirect unbuffered STDIN to the console + // Don't bother: our console is solely for log output. We never read stdin. +// set_stream("stdin", stdin, STD_INPUT_HANDLE, "CONIN$", "r"); + } + + return isConsoleAllocated; +} + +void set_stream(const char* desc, FILE* fp, DWORD handle_id, const char* name, const char* mode) +{ + // SL-13528: This code used to be based on + // http://dslweb.nwnexus.com/~ast/dload/guicon.htm + // (referenced in https://stackoverflow.com/a/191880). + // But one of the comments on that StackOverflow answer points out that + // assigning to *stdout or *stderr "probably doesn't even work with the + // Universal CRT that was introduced in 2015," suggesting freopen_s() + // instead. Code below is based on https://stackoverflow.com/a/55875595. + auto std_handle = GetStdHandle(handle_id); + if (std_handle == INVALID_HANDLE_VALUE) + { + LL_WARNS() << "create_console() failed to get " << desc << " handle" << LL_ENDL; + } + else + { + if (mode == std::string("w")) + { + // Enable color processing on Windows 10 console windows. + DWORD dwMode = 0; + GetConsoleMode(std_handle, &dwMode); + dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + SetConsoleMode(std_handle, dwMode); + } + // Redirect the passed fp to the console. + FILE* ignore; + if (freopen_s(&ignore, name, mode, fp) == 0) + { + // use unbuffered I/O + setvbuf( fp, NULL, _IONBF, 0 ); + } + } +} + +} // anonymous namespace + +LLAppViewerWin32::LLAppViewerWin32(const char* cmd_line) : + mCmdLine(cmd_line), + mIsConsoleAllocated(false) +{ +} + +LLAppViewerWin32::~LLAppViewerWin32() +{ +} + +bool LLAppViewerWin32::init() +{ + // Platform specific initialization. + + // Turn off Windows Error Reporting + // (Don't send our data to Microsoft--at least until we are Logo approved and have a way + // of getting the data back from them.) + // + // LL_INFOS() << "Turning off Windows error reporting." << LL_ENDL; + disableWinErrorReporting(); + +#ifndef LL_RELEASE_FOR_DOWNLOAD + // Merely requesting the LLSingleton instance initializes it. + LLWinDebug::instance(); +#endif + +#if LL_SEND_CRASH_REPORTS +#if ! defined(LL_BUGSPLAT) +#pragma message("Building without BugSplat") + +#else // LL_BUGSPLAT +#pragma message("Building with BugSplat") + + if (!isSecondInstance()) + { + // Cleanup previous session + std::string log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "bugsplat.log"); + LLFile::remove(log_file, ENOENT); + } + + // Win7 is no longer supported + bool is_win_7_or_below = LLOSInfo::getInstance()->mMajorVer <= 6 && LLOSInfo::getInstance()->mMajorVer <= 1; + + if (!is_win_7_or_below) + { + std::string build_data_fname( + gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, "build_data.json")); + // Use llifstream instead of std::ifstream because LL_PATH_EXECUTABLE + // could contain non-ASCII characters, which std::ifstream doesn't handle. + llifstream inf(build_data_fname.c_str()); + if (!inf.is_open()) + { + LL_WARNS("BUGSPLAT") << "Can't initialize BugSplat, can't read '" << build_data_fname + << "'" << LL_ENDL; + } + else + { + boost::json::error_code ec; + boost::json::value build_data = boost::json::parse(inf, ec); + if(ec.failed()) + { + // gah, the typo is baked into Json::Reader API + LL_WARNS("BUGSPLAT") << "Can't initialize BugSplat, can't parse '" << build_data_fname + << "': " << ec.what() << LL_ENDL; + } + else + { + if (!build_data.is_object() || !build_data.as_object().contains("BugSplat DB")) + { + LL_WARNS("BUGSPLAT") << "Can't initialize BugSplat, no 'BugSplat DB' entry in '" + << build_data_fname << "'" << LL_ENDL; + } + else + { + boost::json::value BugSplat_DB = build_data.at("BugSplat DB"); + + // Got BugSplat_DB, onward! + std::wstring version_string(WSTRINGIZE(LL_VIEWER_VERSION_MAJOR << '.' << + LL_VIEWER_VERSION_MINOR << '.' << + LL_VIEWER_VERSION_PATCH << '.' << + LL_VIEWER_VERSION_BUILD)); + + DWORD dwFlags = MDSF_NONINTERACTIVE | // automatically submit report without prompting + MDSF_PREVENTHIJACKING; // disallow swiping Exception filter + + bool needs_log_file = !isSecondInstance(); + LL_DEBUGS("BUGSPLAT"); + if (needs_log_file) + { + // Startup only! + LL_INFOS("BUGSPLAT") << "Engaged BugSplat logging to bugsplat.log" << LL_ENDL; + dwFlags |= MDSF_LOGFILE | MDSF_LOG_VERBOSE; + } + LL_ENDL; + + // have to convert normal wide strings to strings of __wchar_t + sBugSplatSender = new MiniDmpSender( + WCSTR(boost::json::value_to(BugSplat_DB)), + WCSTR(LL_TO_WSTRING(LL_VIEWER_CHANNEL)), + WCSTR(version_string), + nullptr, // szAppIdentifier -- set later + dwFlags); + sBugSplatSender->setCallback(bugsplatSendLog); + + LL_DEBUGS("BUGSPLAT"); + if (needs_log_file) + { + // Log file will be created in %TEMP%, but it will be moved into logs folder in case of crash + std::string log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "bugsplat.log"); + sBugSplatSender->setLogFilePath(WCSTR(log_file)); + } + LL_ENDL; + + // engage stringize() overload that converts from wstring + LL_INFOS("BUGSPLAT") << "Engaged BugSplat(" << LL_TO_STRING(LL_VIEWER_CHANNEL) + << ' ' << stringize(version_string) << ')' << LL_ENDL; + } // got BugSplat_DB + } // parsed build_data.json + } // opened build_data.json + } // !is_win_7_or_below +#endif // LL_BUGSPLAT +#endif // LL_SEND_CRASH_REPORTS + + bool success = LLAppViewer::init(); + + return success; +} + +bool LLAppViewerWin32::cleanup() +{ + bool result = LLAppViewer::cleanup(); + + gDXHardware.cleanup(); + + if (mIsConsoleAllocated) + { + FreeConsole(); + mIsConsoleAllocated = false; + } + + return result; +} + +void LLAppViewerWin32::reportCrashToBugsplat(void* pExcepInfo) +{ +#if defined(LL_BUGSPLAT) + if (sBugSplatSender) + { + sBugSplatSender->createReport((EXCEPTION_POINTERS*)pExcepInfo); + } +#endif // LL_BUGSPLAT +} + +void LLAppViewerWin32::initLoggingAndGetLastDuration() +{ + LLAppViewer::initLoggingAndGetLastDuration(); +} + +void LLAppViewerWin32::initConsole() +{ + // pop up debug console + mIsConsoleAllocated = create_console(); + return LLAppViewer::initConsole(); +} + +void write_debug_dx(const char* str) +{ + std::string value = gDebugInfo["DXInfo"].asString(); + value += str; + gDebugInfo["DXInfo"] = value; +} + +void write_debug_dx(const std::string& str) +{ + write_debug_dx(str.c_str()); +} + +bool LLAppViewerWin32::initHardwareTest() +{ + // + // Do driver verification and initialization based on DirectX + // hardware polling and driver versions + // + if (true == gSavedSettings.getBOOL("ProbeHardwareOnStartup") && false == gSavedSettings.getBOOL("NoHardwareProbe")) + { + // per DEV-11631 - disable hardware probing for everything + // but vram. + bool vram_only = true; + + LLSplashScreen::update(LLTrans::getString("StartupDetectingHardware")); + + LL_DEBUGS("AppInit") << "Attempting to poll DirectX for hardware info" << LL_ENDL; + gDXHardware.setWriteDebugFunc(write_debug_dx); + bool probe_ok = gDXHardware.getInfo(vram_only); + + if (!probe_ok + && gWarningSettings.getBOOL("AboutDirectX9")) + { + LL_WARNS("AppInit") << "DirectX probe failed, alerting user." << LL_ENDL; + + // Warn them that runnin without DirectX 9 will + // not allow us to tell them about driver issues + std::ostringstream msg; + msg << LLTrans::getString ("MBNoDirectX"); + S32 button = OSMessageBox( + msg.str(), + LLTrans::getString("MBWarning"), + OSMB_YESNO); + if (OSBTN_NO== button) + { + LL_INFOS("AppInit") << "User quitting after failed DirectX 9 detection" << LL_ENDL; + LLWeb::loadURLExternal("http://secondlife.com/support/", false); + return false; + } + gWarningSettings.setBOOL("AboutDirectX9", false); + } + LL_DEBUGS("AppInit") << "Done polling DirectX for hardware info" << LL_ENDL; + + // Only probe once after installation + gSavedSettings.setBOOL("ProbeHardwareOnStartup", false); + + // Disable so debugger can work + std::string splash_msg; + LLStringUtil::format_map_t args; + args["[APP_NAME]"] = LLAppViewer::instance()->getSecondLifeTitle(); + splash_msg = LLTrans::getString("StartupLoading", args); + + LLSplashScreen::update(splash_msg); + } + + if (!restoreErrorTrap()) + { + LL_WARNS("AppInit") << " Someone took over my exception handler (post hardware probe)!" << LL_ENDL; + } + + if (gGLManager.mVRAM == 0) + { + gGLManager.mVRAM = gDXHardware.getVRAM(); + } + + LL_INFOS("AppInit") << "Detected VRAM: " << gGLManager.mVRAM << LL_ENDL; + + return true; +} + +bool LLAppViewerWin32::initParseCommandLine(LLCommandLineParser& clp) +{ + if (!clp.parseCommandLineString(mCmdLine)) + { + return false; + } + + // Find the system language. + FL_Locale *locale = NULL; + FL_Success success = FL_FindLocale(&locale, FL_MESSAGES); + if (success != 0) + { + if (success >= 2 && locale->lang) // confident! + { + LL_INFOS("AppInit") << "Language: " << ll_safe_string(locale->lang) << LL_ENDL; + LL_INFOS("AppInit") << "Location: " << ll_safe_string(locale->country) << LL_ENDL; + LL_INFOS("AppInit") << "Variant: " << ll_safe_string(locale->variant) << LL_ENDL; + LLControlVariable* c = gSavedSettings.getControl("SystemLanguage"); + if(c) + { + c->setValue(std::string(locale->lang), false); + } + } + } + FL_FreeLocale(&locale); + + return true; +} + +bool LLAppViewerWin32::beingDebugged() +{ + return IsDebuggerPresent(); +} + +bool LLAppViewerWin32::restoreErrorTrap() +{ + return true; // we don't check for handler collisions on windows, so just say they're ok +} + +//virtual +bool LLAppViewerWin32::sendURLToOtherInstance(const std::string& url) +{ + wchar_t window_class[256]; /* Flawfinder: ignore */ // Assume max length < 255 chars. + mbstowcs(window_class, sWindowClass.c_str(), 255); + window_class[255] = 0; + // Use the class instead of the window name. + HWND other_window = FindWindow(window_class, NULL); + + if (other_window != NULL) + { + LL_DEBUGS() << "Found other window with the name '" << getWindowTitle() << "'" << LL_ENDL; + COPYDATASTRUCT cds; + const S32 SLURL_MESSAGE_TYPE = 0; + cds.dwData = SLURL_MESSAGE_TYPE; + cds.cbData = url.length() + 1; + cds.lpData = (void*)url.c_str(); + + LRESULT msg_result = SendMessage(other_window, WM_COPYDATA, NULL, (LPARAM)&cds); + LL_DEBUGS() << "SendMessage(WM_COPYDATA) to other window '" + << getWindowTitle() << "' returned " << msg_result << LL_ENDL; + return true; + } + return false; +} + + +std::string LLAppViewerWin32::generateSerialNumber() +{ + char serial_md5[MD5HEX_STR_SIZE]; // Flawfinder: ignore + serial_md5[0] = 0; + + DWORD serial = 0; + DWORD flags = 0; + BOOL success = GetVolumeInformation( + L"C:\\", + NULL, // volume name buffer + 0, // volume name buffer size + &serial, // volume serial + NULL, // max component length + &flags, // file system flags + NULL, // file system name buffer + 0); // file system name buffer size + if (success) + { + LLMD5 md5; + md5.update( (unsigned char*)&serial, sizeof(DWORD)); + md5.finalize(); + md5.hex_digest(serial_md5); + } + else + { + LL_WARNS() << "GetVolumeInformation failed" << LL_ENDL; + } + return serial_md5; +} diff --git a/indra/newview/llattachmentsmgr.cpp b/indra/newview/llattachmentsmgr.cpp index e00ad2cd9f..8f5dd0dab8 100644 --- a/indra/newview/llattachmentsmgr.cpp +++ b/indra/newview/llattachmentsmgr.cpp @@ -1,547 +1,547 @@ -/** - * @file llattachmentsmgr.cpp - * @brief Manager for initiating attachments changes on the viewer - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llattachmentsmgr.h" - -#include "llvoavatarself.h" -#include "llagent.h" -#include "llappearancemgr.h" -#include "llinventorymodel.h" -#include "llstartup.h" -#include "lltooldraganddrop.h" // pack_permissions_slam -#include "llviewerinventory.h" -#include "llviewerregion.h" -#include "message.h" - -const F32 COF_LINK_BATCH_TIME = 5.0F; -const F32 MAX_ATTACHMENT_REQUEST_LIFETIME = 30.0F; -const F32 MIN_RETRY_REQUEST_TIME = 5.0F; -const F32 MAX_BAD_COF_TIME = 30.0F; - -LLAttachmentsMgr::LLAttachmentsMgr(): - mAttachmentRequests("attach",MIN_RETRY_REQUEST_TIME), - mDetachRequests("detach",MIN_RETRY_REQUEST_TIME), - mQuestionableCOFLinks("badcof",MAX_BAD_COF_TIME) -{ -} - -LLAttachmentsMgr::~LLAttachmentsMgr() -{ -} - -void LLAttachmentsMgr::addAttachmentRequest(const LLUUID& item_id, - const U8 attachment_pt, - const bool add) -{ - LLViewerInventoryItem *item = gInventory.getItem(item_id); - - if (mAttachmentRequests.wasRequestedRecently(item_id)) - { - LL_DEBUGS("Avatar") << "ATT not adding attachment to mPendingAttachments, recent request is already pending: " - << (item ? item->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL; - return; - } - - LL_DEBUGS("Avatar") << "ATT adding attachment to mPendingAttachments " - << (item ? item->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL; - - AttachmentsInfo attachment; - attachment.mItemID = item_id; - attachment.mAttachmentPt = attachment_pt; - attachment.mAdd = add; - mPendingAttachments.push_back(attachment); - - mAttachmentRequests.addTime(item_id); -} - -void LLAttachmentsMgr::onAttachmentRequested(const LLUUID& item_id) -{ - LLViewerInventoryItem *item = gInventory.getItem(item_id); - LL_DEBUGS("Avatar") << "ATT attachment was requested " - << (item ? item->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL; - mAttachmentRequests.addTime(item_id); -} - -// static -void LLAttachmentsMgr::onIdle(void *) -{ - LLAttachmentsMgr::instance().onIdle(); -} - -void LLAttachmentsMgr::onIdle() -{ - // Make sure we got a region before trying anything else - if( !gAgent.getRegion() ) - { - return; - } - - if (LLApp::isExiting()) - { - return; - } - - requestPendingAttachments(); - - linkRecentlyArrivedAttachments(); - - expireOldAttachmentRequests(); - - expireOldDetachRequests(); - - checkInvalidCOFLinks(); - - spamStatusInfo(); -} - -void LLAttachmentsMgr::requestPendingAttachments() -{ - if (mPendingAttachments.size()) - { - requestAttachments(mPendingAttachments); - } -} - -// Send request(s) for a group of attachments. As coded, this can -// request at most 40 attachments and the rest will be -// ignored. Currently the max attachments per avatar is 38, so the 40 -// limit should not be hit in practice. -void LLAttachmentsMgr::requestAttachments(attachments_vec_t& attachment_requests) -{ - // Make sure we got a region before trying anything else - if( !gAgent.getRegion() ) - { - return; - } - - // For unknown reasons, requesting many attachments at once causes - // frequent server-side failures. Here we're limiting the number - // of attachments requested per idle loop. - const S32 max_objects_per_request = 5; - S32 obj_count = llmin((S32)attachment_requests.size(),max_objects_per_request); - if (obj_count == 0) - { - return; - } - - // Limit number of packets to send - const S32 MAX_PACKETS_TO_SEND = 10; - const S32 OBJECTS_PER_PACKET = 4; - const S32 MAX_OBJECTS_TO_SEND = MAX_PACKETS_TO_SEND * OBJECTS_PER_PACKET; - if( obj_count > MAX_OBJECTS_TO_SEND ) - { - LL_WARNS() << "ATT Too many attachments requested: " << obj_count - << " exceeds limit of " << MAX_OBJECTS_TO_SEND << LL_ENDL; - - obj_count = MAX_OBJECTS_TO_SEND; - } - - LL_DEBUGS("Avatar") << "ATT [RezMultipleAttachmentsFromInv] attaching multiple from attachment_requests," - " total obj_count " << obj_count << LL_ENDL; - - LLUUID compound_msg_id; - compound_msg_id.generate(); - LLMessageSystem* msg = gMessageSystem; - - // by construction above, obj_count <= attachment_requests.size(), so no - // check against attachment_requests.empty() is needed. - llassert(obj_count <= attachment_requests.size()); - - for (S32 i=0; inewMessageFast(_PREHASH_RezMultipleAttachmentsFromInv); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_HeaderData); - msg->addUUIDFast(_PREHASH_CompoundMsgID, compound_msg_id ); - msg->addU8Fast(_PREHASH_TotalObjects, obj_count ); - msg->addBOOLFast(_PREHASH_FirstDetachAll, false ); - } - - const AttachmentsInfo& attachment = attachment_requests.front(); - LLViewerInventoryItem* item = gInventory.getItem(attachment.mItemID); - if (item) - { - LL_DEBUGS("Avatar") << "ATT requesting from attachment_requests " << item->getName() - << " " << item->getLinkedUUID() << LL_ENDL; - S32 attachment_pt = attachment.mAttachmentPt; - if (attachment.mAdd) - attachment_pt |= ATTACHMENT_ADD; - - msg->nextBlockFast(_PREHASH_ObjectData ); - msg->addUUIDFast(_PREHASH_ItemID, item->getLinkedUUID()); - msg->addUUIDFast(_PREHASH_OwnerID, item->getPermissions().getOwner()); - msg->addU8Fast(_PREHASH_AttachmentPt, attachment_pt); - pack_permissions_slam(msg, item->getFlags(), item->getPermissions()); - msg->addStringFast(_PREHASH_Name, item->getName()); - msg->addStringFast(_PREHASH_Description, item->getDescription()); - } - else - { - LL_WARNS("Avatar") << "ATT Attempted to add non-existent item ID:" << attachment.mItemID << LL_ENDL; - } - - if( (i+1 == obj_count) || ((OBJECTS_PER_PACKET-1) == (i % OBJECTS_PER_PACKET)) ) - { - // End of message chunk - msg->sendReliable( gAgent.getRegion()->getHost() ); - } - attachment_requests.pop_front(); - } -} - -void LLAttachmentsMgr::linkRecentlyArrivedAttachments() -{ - if (mRecentlyArrivedAttachments.size()) - { - // One or more attachments have arrived but have not yet been - // processed for COF links - if (mAttachmentRequests.empty()) - { - // Not waiting for any more. - LL_DEBUGS("Avatar") << "ATT all pending attachments have arrived after " - << mCOFLinkBatchTimer.getElapsedTimeF32() << " seconds" << LL_ENDL; - } - else if (mCOFLinkBatchTimer.getElapsedTimeF32() > COF_LINK_BATCH_TIME) - { - LL_DEBUGS("Avatar") << "ATT " << mAttachmentRequests.size() - << " pending attachments have not arrived, but wait time exceeded" << LL_ENDL; - } - else - { - return; - } - - if (LLAppearanceMgr::instance().getCOFVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) - { - // Wait for cof to load - LL_DEBUGS_ONCE("Avatar") << "Received atachments, but cof isn't loaded yet, postponing processing" << LL_ENDL; - return; - } - - LL_DEBUGS("Avatar") << "ATT checking COF linkability for " << mRecentlyArrivedAttachments.size() - << " recently arrived items" << LL_ENDL; - - uuid_vec_t ids_to_link; - for (std::set::iterator it = mRecentlyArrivedAttachments.begin(); - it != mRecentlyArrivedAttachments.end(); ++it) - { - if (isAgentAvatarValid() && - gAgentAvatarp->isWearingAttachment(*it) && - !gAgentAvatarp->getWornAttachment(*it)->isTempAttachment() && // Don't link temp attachments in COF! - !LLAppearanceMgr::instance().isLinkedInCOF(*it)) - { - LLUUID item_id = *it; - LLViewerInventoryItem *item = gInventory.getItem(item_id); - LL_DEBUGS("Avatar") << "ATT adding COF link for attachment " - << (item ? item->getName() : "UNKNOWN") << " " << item_id << LL_ENDL; - ids_to_link.push_back(item_id); - } - } - if (ids_to_link.size()) - { - LLPointer cb = new LLRequestServerAppearanceUpdateOnDestroy(); - for (uuid_vec_t::const_iterator uuid_it = ids_to_link.begin(); - uuid_it != ids_to_link.end(); ++uuid_it) - { - LLAppearanceMgr::instance().addCOFItemLink(*uuid_it, cb); - } - } - mRecentlyArrivedAttachments.clear(); - } -} - -LLAttachmentsMgr::LLItemRequestTimes::LLItemRequestTimes(const std::string& op_name, F32 timeout): - mOpName(op_name), - mTimeout(timeout) -{ -} - -void LLAttachmentsMgr::LLItemRequestTimes::addTime(const LLUUID& inv_item_id) -{ - LLInventoryItem *item = gInventory.getItem(inv_item_id); - LL_DEBUGS("Avatar") << "ATT " << mOpName << " adding request time " << (item ? item->getName() : "UNKNOWN") << " " << inv_item_id << LL_ENDL; - LLTimer current_time; - (*this)[inv_item_id] = current_time; -} - -void LLAttachmentsMgr::LLItemRequestTimes::removeTime(const LLUUID& inv_item_id) -{ - LLInventoryItem *item = gInventory.getItem(inv_item_id); - S32 remove_count = (*this).erase(inv_item_id); - if (remove_count) - { - LL_DEBUGS("Avatar") << "ATT " << mOpName << " removing request time " - << (item ? item->getName() : "UNKNOWN") << " " << inv_item_id << LL_ENDL; - } -} - -bool LLAttachmentsMgr::LLItemRequestTimes::getTime(const LLUUID& inv_item_id, LLTimer& timer) const -{ - std::map::const_iterator it = (*this).find(inv_item_id); - if (it != (*this).end()) - { - timer = it->second; - return true; - } - return false; -} - -bool LLAttachmentsMgr::LLItemRequestTimes::wasRequestedRecently(const LLUUID& inv_item_id) const -{ - LLTimer request_time; - if (getTime(inv_item_id, request_time)) - { - F32 request_time_elapsed = request_time.getElapsedTimeF32(); - return request_time_elapsed < mTimeout; - } - else - { - return false; - } -} - -// If we've been waiting for an attachment a long time, we want to -// forget the request, because if the request is invalid (say the -// object does not exist), the existence of a request that never goes -// away will gum up the COF batch logic, causing it to always wait for -// the timeout. Expiring a request means if the item does show up -// late, the COF link request may not get properly batched up, but -// behavior will be no worse than before we had the batching mechanism -// in place; the COF link will still be created, but extra -// requestServerAppearanceUpdate() calls may occur. -void LLAttachmentsMgr::expireOldAttachmentRequests() -{ - for (std::map::iterator it = mAttachmentRequests.begin(); - it != mAttachmentRequests.end(); ) - { - std::map::iterator curr_it = it; - ++it; - if (curr_it->second.getElapsedTimeF32() > MAX_ATTACHMENT_REQUEST_LIFETIME) - { - LLInventoryItem *item = gInventory.getItem(curr_it->first); - LL_WARNS("Avatar") << "ATT expiring request for attachment " - << (item ? item->getName() : "UNKNOWN") << " item_id " << curr_it->first - << " after " << MAX_ATTACHMENT_REQUEST_LIFETIME << " seconds" << LL_ENDL; - mAttachmentRequests.erase(curr_it); - } - } -} - -void LLAttachmentsMgr::expireOldDetachRequests() -{ - for (std::map::iterator it = mDetachRequests.begin(); - it != mDetachRequests.end(); ) - { - std::map::iterator curr_it = it; - ++it; - if (curr_it->second.getElapsedTimeF32() > MAX_ATTACHMENT_REQUEST_LIFETIME) - { - LLInventoryItem *item = gInventory.getItem(curr_it->first); - LL_WARNS("Avatar") << "ATT expiring request for detach " - << (item ? item->getName() : "UNKNOWN") << " item_id " << curr_it->first - << " after " << MAX_ATTACHMENT_REQUEST_LIFETIME << " seconds" << LL_ENDL; - mDetachRequests.erase(curr_it); - } - } -} - -// When an attachment arrives, we want to stop waiting for it, and add -// it to the set of recently arrived items. -void LLAttachmentsMgr::onAttachmentArrived(const LLUUID& inv_item_id) -{ - LLTimer timer; - bool expected = mAttachmentRequests.getTime(inv_item_id, timer); - if (!expected && LLStartUp::getStartupState() > STATE_WEARABLES_WAIT) - { - LLInventoryItem *item = gInventory.getItem(inv_item_id); - LL_WARNS() << "ATT Attachment was unexpected or arrived after " << MAX_ATTACHMENT_REQUEST_LIFETIME << " seconds: " - << (item ? item->getName() : "UNKNOWN") << " id " << inv_item_id << LL_ENDL; - } - mAttachmentRequests.removeTime(inv_item_id); - if (expected && mAttachmentRequests.empty()) - { - // mAttachmentRequests just emptied out - LL_DEBUGS("Avatar") << "ATT all active attachment requests have completed" << LL_ENDL; - } - if (mRecentlyArrivedAttachments.empty()) - { - // Start the timer for sending off a COF link batch. - mCOFLinkBatchTimer.reset(); - } - mRecentlyArrivedAttachments.insert(inv_item_id); -} - -void LLAttachmentsMgr::onDetachRequested(const LLUUID& inv_item_id) -{ - mDetachRequests.addTime(inv_item_id); -} - -void LLAttachmentsMgr::onDetachCompleted(const LLUUID& inv_item_id) -{ - LLTimer timer; - LLInventoryItem *item = gInventory.getItem(inv_item_id); - if (mDetachRequests.getTime(inv_item_id, timer)) - { - LL_DEBUGS("Avatar") << "ATT detach completed after " << timer.getElapsedTimeF32() - << " seconds for " << (item ? item->getName() : "UNKNOWN") << " " << inv_item_id << LL_ENDL; - mDetachRequests.removeTime(inv_item_id); - if (mDetachRequests.empty()) - { - LL_DEBUGS("Avatar") << "ATT all detach requests have completed" << LL_ENDL; - } - } - else if (!LLApp::isExiting()) - { - LL_WARNS() << "ATT unexpected detach for " - << (item ? item->getName() : "UNKNOWN") << " id " << inv_item_id << LL_ENDL; - } - else - { - LL_DEBUGS("Avatar") << "ATT detach on shutdown for " << (item ? item->getName() : "UNKNOWN") << " " << inv_item_id << LL_ENDL; - } - - LL_DEBUGS("Avatar") << "ATT detached item flagging as questionable for COF link checking " - << (item ? item->getName() : "UNKNOWN") << " id " << inv_item_id << LL_ENDL; - mQuestionableCOFLinks.addTime(inv_item_id); -} - -bool LLAttachmentsMgr::isAttachmentStateComplete() const -{ - return mPendingAttachments.empty() - && mAttachmentRequests.empty() - && mDetachRequests.empty() - && mRecentlyArrivedAttachments.empty() - && mQuestionableCOFLinks.empty(); -} - -// Check for attachments that are (a) linked in COF and (b) not -// attached to the avatar. This is a rotten function to have to -// include, because it runs the risk of either repeatedly spamming out -// COF link removals if they're failing for some reason, or getting -// into a tug of war with some other sequence of events that's in the -// process of adding the attachment in question. However, it's needed -// because we have no definitive source of authority for what things -// are actually supposed to be attached. Scripts, run on the server -// side, can remove an attachment without our expecting it. If this -// happens to an attachment that's just been added, then the COF link -// creation may still be in flight, and we will have to delete the -// link after it shows up. -// -// Note that we only flag items for possible link removal if they have -// been previously detached. This means that an attachment failure -// will leave the link in the COF, where it will hopefully resolve -// correctly on relog. -// -// See related: MAINT-5070, MAINT-4409 -// -void LLAttachmentsMgr::checkInvalidCOFLinks() -{ - if (!gInventory.isInventoryUsable()) - { - return; - } - LLInventoryModel::cat_array_t cat_array; - LLInventoryModel::item_array_t item_array; - gInventory.collectDescendents(LLAppearanceMgr::instance().getCOF(), - cat_array,item_array,LLInventoryModel::EXCLUDE_TRASH); - for (S32 i=0; igetLinkedUUID(); - if (inv_item->getType() == LLAssetType::AT_OBJECT) - { - LLTimer timer; - bool is_flagged_questionable = mQuestionableCOFLinks.getTime(item_id,timer); - bool is_wearing_attachment = isAgentAvatarValid() && gAgentAvatarp->isWearingAttachment(item_id); - if (is_wearing_attachment && is_flagged_questionable) - { - LL_DEBUGS("Avatar") << "ATT was flagged questionable but is now " - << (is_wearing_attachment ? "attached " : "") - <<"removing flag after " - << timer.getElapsedTimeF32() << " item " - << inv_item->getName() << " id " << item_id << LL_ENDL; - mQuestionableCOFLinks.removeTime(item_id); - } - } - } - - for(LLItemRequestTimes::iterator it = mQuestionableCOFLinks.begin(); - it != mQuestionableCOFLinks.end(); ) - { - LLItemRequestTimes::iterator curr_it = it; - ++it; - const LLUUID& item_id = curr_it->first; - LLViewerInventoryItem *inv_item = gInventory.getItem(item_id); - if (curr_it->second.getElapsedTimeF32() > MAX_BAD_COF_TIME) - { - if (LLAppearanceMgr::instance().isLinkedInCOF(item_id)) - { - LL_DEBUGS("Avatar") << "ATT Linked in COF but not attached or requested, deleting link after " - << curr_it->second.getElapsedTimeF32() << " seconds for " - << (inv_item ? inv_item->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL; - LLAppearanceMgr::instance().removeCOFItemLinks(item_id); - } - mQuestionableCOFLinks.erase(curr_it); - continue; - } - } -} - -void LLAttachmentsMgr::spamStatusInfo() -{ -#if 0 - static LLTimer spam_timer; - const F32 spam_frequency = 100.0F; - - if (spam_timer.getElapsedTimeF32() > spam_frequency) - { - spam_timer.reset(); - - LLInventoryModel::cat_array_t cat_array; - LLInventoryModel::item_array_t item_array; - gInventory.collectDescendents(LLAppearanceMgr::instance().getCOF(), - cat_array,item_array,LLInventoryModel::EXCLUDE_TRASH); - for (S32 i=0; igetType() == LLAssetType::AT_OBJECT) - { - LL_DEBUGS("Avatar") << "item_id: " << inv_item->getUUID() - << " linked_item_id: " << inv_item->getLinkedUUID() - << " name: " << inv_item->getName() - << " parent: " << inv_item->getParentUUID() - << LL_ENDL; - } - } - } -#endif -} +/** + * @file llattachmentsmgr.cpp + * @brief Manager for initiating attachments changes on the viewer + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llattachmentsmgr.h" + +#include "llvoavatarself.h" +#include "llagent.h" +#include "llappearancemgr.h" +#include "llinventorymodel.h" +#include "llstartup.h" +#include "lltooldraganddrop.h" // pack_permissions_slam +#include "llviewerinventory.h" +#include "llviewerregion.h" +#include "message.h" + +const F32 COF_LINK_BATCH_TIME = 5.0F; +const F32 MAX_ATTACHMENT_REQUEST_LIFETIME = 30.0F; +const F32 MIN_RETRY_REQUEST_TIME = 5.0F; +const F32 MAX_BAD_COF_TIME = 30.0F; + +LLAttachmentsMgr::LLAttachmentsMgr(): + mAttachmentRequests("attach",MIN_RETRY_REQUEST_TIME), + mDetachRequests("detach",MIN_RETRY_REQUEST_TIME), + mQuestionableCOFLinks("badcof",MAX_BAD_COF_TIME) +{ +} + +LLAttachmentsMgr::~LLAttachmentsMgr() +{ +} + +void LLAttachmentsMgr::addAttachmentRequest(const LLUUID& item_id, + const U8 attachment_pt, + const bool add) +{ + LLViewerInventoryItem *item = gInventory.getItem(item_id); + + if (mAttachmentRequests.wasRequestedRecently(item_id)) + { + LL_DEBUGS("Avatar") << "ATT not adding attachment to mPendingAttachments, recent request is already pending: " + << (item ? item->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL; + return; + } + + LL_DEBUGS("Avatar") << "ATT adding attachment to mPendingAttachments " + << (item ? item->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL; + + AttachmentsInfo attachment; + attachment.mItemID = item_id; + attachment.mAttachmentPt = attachment_pt; + attachment.mAdd = add; + mPendingAttachments.push_back(attachment); + + mAttachmentRequests.addTime(item_id); +} + +void LLAttachmentsMgr::onAttachmentRequested(const LLUUID& item_id) +{ + LLViewerInventoryItem *item = gInventory.getItem(item_id); + LL_DEBUGS("Avatar") << "ATT attachment was requested " + << (item ? item->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL; + mAttachmentRequests.addTime(item_id); +} + +// static +void LLAttachmentsMgr::onIdle(void *) +{ + LLAttachmentsMgr::instance().onIdle(); +} + +void LLAttachmentsMgr::onIdle() +{ + // Make sure we got a region before trying anything else + if( !gAgent.getRegion() ) + { + return; + } + + if (LLApp::isExiting()) + { + return; + } + + requestPendingAttachments(); + + linkRecentlyArrivedAttachments(); + + expireOldAttachmentRequests(); + + expireOldDetachRequests(); + + checkInvalidCOFLinks(); + + spamStatusInfo(); +} + +void LLAttachmentsMgr::requestPendingAttachments() +{ + if (mPendingAttachments.size()) + { + requestAttachments(mPendingAttachments); + } +} + +// Send request(s) for a group of attachments. As coded, this can +// request at most 40 attachments and the rest will be +// ignored. Currently the max attachments per avatar is 38, so the 40 +// limit should not be hit in practice. +void LLAttachmentsMgr::requestAttachments(attachments_vec_t& attachment_requests) +{ + // Make sure we got a region before trying anything else + if( !gAgent.getRegion() ) + { + return; + } + + // For unknown reasons, requesting many attachments at once causes + // frequent server-side failures. Here we're limiting the number + // of attachments requested per idle loop. + const S32 max_objects_per_request = 5; + S32 obj_count = llmin((S32)attachment_requests.size(),max_objects_per_request); + if (obj_count == 0) + { + return; + } + + // Limit number of packets to send + const S32 MAX_PACKETS_TO_SEND = 10; + const S32 OBJECTS_PER_PACKET = 4; + const S32 MAX_OBJECTS_TO_SEND = MAX_PACKETS_TO_SEND * OBJECTS_PER_PACKET; + if( obj_count > MAX_OBJECTS_TO_SEND ) + { + LL_WARNS() << "ATT Too many attachments requested: " << obj_count + << " exceeds limit of " << MAX_OBJECTS_TO_SEND << LL_ENDL; + + obj_count = MAX_OBJECTS_TO_SEND; + } + + LL_DEBUGS("Avatar") << "ATT [RezMultipleAttachmentsFromInv] attaching multiple from attachment_requests," + " total obj_count " << obj_count << LL_ENDL; + + LLUUID compound_msg_id; + compound_msg_id.generate(); + LLMessageSystem* msg = gMessageSystem; + + // by construction above, obj_count <= attachment_requests.size(), so no + // check against attachment_requests.empty() is needed. + llassert(obj_count <= attachment_requests.size()); + + for (S32 i=0; inewMessageFast(_PREHASH_RezMultipleAttachmentsFromInv); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_HeaderData); + msg->addUUIDFast(_PREHASH_CompoundMsgID, compound_msg_id ); + msg->addU8Fast(_PREHASH_TotalObjects, obj_count ); + msg->addBOOLFast(_PREHASH_FirstDetachAll, false ); + } + + const AttachmentsInfo& attachment = attachment_requests.front(); + LLViewerInventoryItem* item = gInventory.getItem(attachment.mItemID); + if (item) + { + LL_DEBUGS("Avatar") << "ATT requesting from attachment_requests " << item->getName() + << " " << item->getLinkedUUID() << LL_ENDL; + S32 attachment_pt = attachment.mAttachmentPt; + if (attachment.mAdd) + attachment_pt |= ATTACHMENT_ADD; + + msg->nextBlockFast(_PREHASH_ObjectData ); + msg->addUUIDFast(_PREHASH_ItemID, item->getLinkedUUID()); + msg->addUUIDFast(_PREHASH_OwnerID, item->getPermissions().getOwner()); + msg->addU8Fast(_PREHASH_AttachmentPt, attachment_pt); + pack_permissions_slam(msg, item->getFlags(), item->getPermissions()); + msg->addStringFast(_PREHASH_Name, item->getName()); + msg->addStringFast(_PREHASH_Description, item->getDescription()); + } + else + { + LL_WARNS("Avatar") << "ATT Attempted to add non-existent item ID:" << attachment.mItemID << LL_ENDL; + } + + if( (i+1 == obj_count) || ((OBJECTS_PER_PACKET-1) == (i % OBJECTS_PER_PACKET)) ) + { + // End of message chunk + msg->sendReliable( gAgent.getRegion()->getHost() ); + } + attachment_requests.pop_front(); + } +} + +void LLAttachmentsMgr::linkRecentlyArrivedAttachments() +{ + if (mRecentlyArrivedAttachments.size()) + { + // One or more attachments have arrived but have not yet been + // processed for COF links + if (mAttachmentRequests.empty()) + { + // Not waiting for any more. + LL_DEBUGS("Avatar") << "ATT all pending attachments have arrived after " + << mCOFLinkBatchTimer.getElapsedTimeF32() << " seconds" << LL_ENDL; + } + else if (mCOFLinkBatchTimer.getElapsedTimeF32() > COF_LINK_BATCH_TIME) + { + LL_DEBUGS("Avatar") << "ATT " << mAttachmentRequests.size() + << " pending attachments have not arrived, but wait time exceeded" << LL_ENDL; + } + else + { + return; + } + + if (LLAppearanceMgr::instance().getCOFVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) + { + // Wait for cof to load + LL_DEBUGS_ONCE("Avatar") << "Received atachments, but cof isn't loaded yet, postponing processing" << LL_ENDL; + return; + } + + LL_DEBUGS("Avatar") << "ATT checking COF linkability for " << mRecentlyArrivedAttachments.size() + << " recently arrived items" << LL_ENDL; + + uuid_vec_t ids_to_link; + for (std::set::iterator it = mRecentlyArrivedAttachments.begin(); + it != mRecentlyArrivedAttachments.end(); ++it) + { + if (isAgentAvatarValid() && + gAgentAvatarp->isWearingAttachment(*it) && + !gAgentAvatarp->getWornAttachment(*it)->isTempAttachment() && // Don't link temp attachments in COF! + !LLAppearanceMgr::instance().isLinkedInCOF(*it)) + { + LLUUID item_id = *it; + LLViewerInventoryItem *item = gInventory.getItem(item_id); + LL_DEBUGS("Avatar") << "ATT adding COF link for attachment " + << (item ? item->getName() : "UNKNOWN") << " " << item_id << LL_ENDL; + ids_to_link.push_back(item_id); + } + } + if (ids_to_link.size()) + { + LLPointer cb = new LLRequestServerAppearanceUpdateOnDestroy(); + for (uuid_vec_t::const_iterator uuid_it = ids_to_link.begin(); + uuid_it != ids_to_link.end(); ++uuid_it) + { + LLAppearanceMgr::instance().addCOFItemLink(*uuid_it, cb); + } + } + mRecentlyArrivedAttachments.clear(); + } +} + +LLAttachmentsMgr::LLItemRequestTimes::LLItemRequestTimes(const std::string& op_name, F32 timeout): + mOpName(op_name), + mTimeout(timeout) +{ +} + +void LLAttachmentsMgr::LLItemRequestTimes::addTime(const LLUUID& inv_item_id) +{ + LLInventoryItem *item = gInventory.getItem(inv_item_id); + LL_DEBUGS("Avatar") << "ATT " << mOpName << " adding request time " << (item ? item->getName() : "UNKNOWN") << " " << inv_item_id << LL_ENDL; + LLTimer current_time; + (*this)[inv_item_id] = current_time; +} + +void LLAttachmentsMgr::LLItemRequestTimes::removeTime(const LLUUID& inv_item_id) +{ + LLInventoryItem *item = gInventory.getItem(inv_item_id); + S32 remove_count = (*this).erase(inv_item_id); + if (remove_count) + { + LL_DEBUGS("Avatar") << "ATT " << mOpName << " removing request time " + << (item ? item->getName() : "UNKNOWN") << " " << inv_item_id << LL_ENDL; + } +} + +bool LLAttachmentsMgr::LLItemRequestTimes::getTime(const LLUUID& inv_item_id, LLTimer& timer) const +{ + std::map::const_iterator it = (*this).find(inv_item_id); + if (it != (*this).end()) + { + timer = it->second; + return true; + } + return false; +} + +bool LLAttachmentsMgr::LLItemRequestTimes::wasRequestedRecently(const LLUUID& inv_item_id) const +{ + LLTimer request_time; + if (getTime(inv_item_id, request_time)) + { + F32 request_time_elapsed = request_time.getElapsedTimeF32(); + return request_time_elapsed < mTimeout; + } + else + { + return false; + } +} + +// If we've been waiting for an attachment a long time, we want to +// forget the request, because if the request is invalid (say the +// object does not exist), the existence of a request that never goes +// away will gum up the COF batch logic, causing it to always wait for +// the timeout. Expiring a request means if the item does show up +// late, the COF link request may not get properly batched up, but +// behavior will be no worse than before we had the batching mechanism +// in place; the COF link will still be created, but extra +// requestServerAppearanceUpdate() calls may occur. +void LLAttachmentsMgr::expireOldAttachmentRequests() +{ + for (std::map::iterator it = mAttachmentRequests.begin(); + it != mAttachmentRequests.end(); ) + { + std::map::iterator curr_it = it; + ++it; + if (curr_it->second.getElapsedTimeF32() > MAX_ATTACHMENT_REQUEST_LIFETIME) + { + LLInventoryItem *item = gInventory.getItem(curr_it->first); + LL_WARNS("Avatar") << "ATT expiring request for attachment " + << (item ? item->getName() : "UNKNOWN") << " item_id " << curr_it->first + << " after " << MAX_ATTACHMENT_REQUEST_LIFETIME << " seconds" << LL_ENDL; + mAttachmentRequests.erase(curr_it); + } + } +} + +void LLAttachmentsMgr::expireOldDetachRequests() +{ + for (std::map::iterator it = mDetachRequests.begin(); + it != mDetachRequests.end(); ) + { + std::map::iterator curr_it = it; + ++it; + if (curr_it->second.getElapsedTimeF32() > MAX_ATTACHMENT_REQUEST_LIFETIME) + { + LLInventoryItem *item = gInventory.getItem(curr_it->first); + LL_WARNS("Avatar") << "ATT expiring request for detach " + << (item ? item->getName() : "UNKNOWN") << " item_id " << curr_it->first + << " after " << MAX_ATTACHMENT_REQUEST_LIFETIME << " seconds" << LL_ENDL; + mDetachRequests.erase(curr_it); + } + } +} + +// When an attachment arrives, we want to stop waiting for it, and add +// it to the set of recently arrived items. +void LLAttachmentsMgr::onAttachmentArrived(const LLUUID& inv_item_id) +{ + LLTimer timer; + bool expected = mAttachmentRequests.getTime(inv_item_id, timer); + if (!expected && LLStartUp::getStartupState() > STATE_WEARABLES_WAIT) + { + LLInventoryItem *item = gInventory.getItem(inv_item_id); + LL_WARNS() << "ATT Attachment was unexpected or arrived after " << MAX_ATTACHMENT_REQUEST_LIFETIME << " seconds: " + << (item ? item->getName() : "UNKNOWN") << " id " << inv_item_id << LL_ENDL; + } + mAttachmentRequests.removeTime(inv_item_id); + if (expected && mAttachmentRequests.empty()) + { + // mAttachmentRequests just emptied out + LL_DEBUGS("Avatar") << "ATT all active attachment requests have completed" << LL_ENDL; + } + if (mRecentlyArrivedAttachments.empty()) + { + // Start the timer for sending off a COF link batch. + mCOFLinkBatchTimer.reset(); + } + mRecentlyArrivedAttachments.insert(inv_item_id); +} + +void LLAttachmentsMgr::onDetachRequested(const LLUUID& inv_item_id) +{ + mDetachRequests.addTime(inv_item_id); +} + +void LLAttachmentsMgr::onDetachCompleted(const LLUUID& inv_item_id) +{ + LLTimer timer; + LLInventoryItem *item = gInventory.getItem(inv_item_id); + if (mDetachRequests.getTime(inv_item_id, timer)) + { + LL_DEBUGS("Avatar") << "ATT detach completed after " << timer.getElapsedTimeF32() + << " seconds for " << (item ? item->getName() : "UNKNOWN") << " " << inv_item_id << LL_ENDL; + mDetachRequests.removeTime(inv_item_id); + if (mDetachRequests.empty()) + { + LL_DEBUGS("Avatar") << "ATT all detach requests have completed" << LL_ENDL; + } + } + else if (!LLApp::isExiting()) + { + LL_WARNS() << "ATT unexpected detach for " + << (item ? item->getName() : "UNKNOWN") << " id " << inv_item_id << LL_ENDL; + } + else + { + LL_DEBUGS("Avatar") << "ATT detach on shutdown for " << (item ? item->getName() : "UNKNOWN") << " " << inv_item_id << LL_ENDL; + } + + LL_DEBUGS("Avatar") << "ATT detached item flagging as questionable for COF link checking " + << (item ? item->getName() : "UNKNOWN") << " id " << inv_item_id << LL_ENDL; + mQuestionableCOFLinks.addTime(inv_item_id); +} + +bool LLAttachmentsMgr::isAttachmentStateComplete() const +{ + return mPendingAttachments.empty() + && mAttachmentRequests.empty() + && mDetachRequests.empty() + && mRecentlyArrivedAttachments.empty() + && mQuestionableCOFLinks.empty(); +} + +// Check for attachments that are (a) linked in COF and (b) not +// attached to the avatar. This is a rotten function to have to +// include, because it runs the risk of either repeatedly spamming out +// COF link removals if they're failing for some reason, or getting +// into a tug of war with some other sequence of events that's in the +// process of adding the attachment in question. However, it's needed +// because we have no definitive source of authority for what things +// are actually supposed to be attached. Scripts, run on the server +// side, can remove an attachment without our expecting it. If this +// happens to an attachment that's just been added, then the COF link +// creation may still be in flight, and we will have to delete the +// link after it shows up. +// +// Note that we only flag items for possible link removal if they have +// been previously detached. This means that an attachment failure +// will leave the link in the COF, where it will hopefully resolve +// correctly on relog. +// +// See related: MAINT-5070, MAINT-4409 +// +void LLAttachmentsMgr::checkInvalidCOFLinks() +{ + if (!gInventory.isInventoryUsable()) + { + return; + } + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + gInventory.collectDescendents(LLAppearanceMgr::instance().getCOF(), + cat_array,item_array,LLInventoryModel::EXCLUDE_TRASH); + for (S32 i=0; igetLinkedUUID(); + if (inv_item->getType() == LLAssetType::AT_OBJECT) + { + LLTimer timer; + bool is_flagged_questionable = mQuestionableCOFLinks.getTime(item_id,timer); + bool is_wearing_attachment = isAgentAvatarValid() && gAgentAvatarp->isWearingAttachment(item_id); + if (is_wearing_attachment && is_flagged_questionable) + { + LL_DEBUGS("Avatar") << "ATT was flagged questionable but is now " + << (is_wearing_attachment ? "attached " : "") + <<"removing flag after " + << timer.getElapsedTimeF32() << " item " + << inv_item->getName() << " id " << item_id << LL_ENDL; + mQuestionableCOFLinks.removeTime(item_id); + } + } + } + + for(LLItemRequestTimes::iterator it = mQuestionableCOFLinks.begin(); + it != mQuestionableCOFLinks.end(); ) + { + LLItemRequestTimes::iterator curr_it = it; + ++it; + const LLUUID& item_id = curr_it->first; + LLViewerInventoryItem *inv_item = gInventory.getItem(item_id); + if (curr_it->second.getElapsedTimeF32() > MAX_BAD_COF_TIME) + { + if (LLAppearanceMgr::instance().isLinkedInCOF(item_id)) + { + LL_DEBUGS("Avatar") << "ATT Linked in COF but not attached or requested, deleting link after " + << curr_it->second.getElapsedTimeF32() << " seconds for " + << (inv_item ? inv_item->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL; + LLAppearanceMgr::instance().removeCOFItemLinks(item_id); + } + mQuestionableCOFLinks.erase(curr_it); + continue; + } + } +} + +void LLAttachmentsMgr::spamStatusInfo() +{ +#if 0 + static LLTimer spam_timer; + const F32 spam_frequency = 100.0F; + + if (spam_timer.getElapsedTimeF32() > spam_frequency) + { + spam_timer.reset(); + + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + gInventory.collectDescendents(LLAppearanceMgr::instance().getCOF(), + cat_array,item_array,LLInventoryModel::EXCLUDE_TRASH); + for (S32 i=0; igetType() == LLAssetType::AT_OBJECT) + { + LL_DEBUGS("Avatar") << "item_id: " << inv_item->getUUID() + << " linked_item_id: " << inv_item->getLinkedUUID() + << " name: " << inv_item->getName() + << " parent: " << inv_item->getParentUUID() + << LL_ENDL; + } + } + } +#endif +} diff --git a/indra/newview/llattachmentsmgr.h b/indra/newview/llattachmentsmgr.h index 630b10030d..2428acfb38 100644 --- a/indra/newview/llattachmentsmgr.h +++ b/indra/newview/llattachmentsmgr.h @@ -1,132 +1,132 @@ -/** - * @file llattachmentsmgr.h - * @brief Batches up attachment requests and sends them all - * in one message. - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLATTACHMENTSMGR_H -#define LL_LLATTACHMENTSMGR_H - -#include "llsingleton.h" - -//-------------------------------------------------------------------------------- -// LLAttachmentsMgr -// -// This class manages batching up of requests at two stages of -// attachment rezzing. -// -// First, attachments requested to rez get saved in -// mPendingAttachments and sent as a single -// RezMultipleAttachmentsFromInv request. This batching is needed -// mainly because of weaknessing the UI element->inventory item -// handling, such that we don't always know when we are requesting -// multiple items. Now they just pile up and get swept into a single -// request during the idle loop. -// -// Second, after attachments arrive, we need to generate COF links for -// them. There are both efficiency and UI correctness reasons why it -// is better to request all the COF links at once and run a single -// callback after they all complete. Given the vagaries of the -// attachment system, there is no guarantee that we will get all the -// attachments we ask for, but we frequently do. So in the common case -// that all the desired attachments arrive fairly quickly, we generate -// a single batched request for COF links. If attachments arrive late -// or not at all, we will still issue COF link requests once a timeout -// value has been exceeded. -// -// To handle attachments that never arrive, we forget about requests -// that exceed a timeout value. -//-------------------------------------------------------------------------------- -class LLAttachmentsMgr: public LLSingleton -{ - LLSINGLETON(LLAttachmentsMgr); - virtual ~LLAttachmentsMgr(); - -public: - // Stores info for attachments that will be requested during idle. - struct AttachmentsInfo - { - LLUUID mItemID; - U8 mAttachmentPt; - bool mAdd; - }; - typedef std::deque attachments_vec_t; - - void addAttachmentRequest(const LLUUID& item_id, - const U8 attachment_pt, - const bool add); - void onAttachmentRequested(const LLUUID& item_id); - void requestAttachments(attachments_vec_t& attachment_requests); - static void onIdle(void *); - - void onAttachmentArrived(const LLUUID& inv_item_id); - - void onDetachRequested(const LLUUID& inv_item_id); - void onDetachCompleted(const LLUUID& inv_item_id); - - bool isAttachmentStateComplete() const; - -private: - - class LLItemRequestTimes: public std::map - { - public: - LLItemRequestTimes(const std::string& op_name, F32 timeout); - void addTime(const LLUUID& inv_item_id); - void removeTime(const LLUUID& inv_item_id); - bool wasRequestedRecently(const LLUUID& item_id) const; - bool getTime(const LLUUID& inv_item_id, LLTimer& timer) const; - - private: - F32 mTimeout; - std::string mOpName; - }; - - void removeAttachmentRequestTime(const LLUUID& inv_item_id); - void onIdle(); - void requestPendingAttachments(); - void linkRecentlyArrivedAttachments(); - void expireOldAttachmentRequests(); - void expireOldDetachRequests(); - void checkInvalidCOFLinks(); - void spamStatusInfo(); - - // Attachments that we are planning to rez but haven't requested from the server yet. - attachments_vec_t mPendingAttachments; - - // Attachments that have been requested from server but have not arrived yet. - LLItemRequestTimes mAttachmentRequests; - - // Attachments that have been requested to detach but have not gone away yet. - LLItemRequestTimes mDetachRequests; - - // Attachments that have arrived but have not been linked in the COF yet. - std::set mRecentlyArrivedAttachments; - LLTimer mCOFLinkBatchTimer; - - // Attachments that are linked in the COF but may be invalid. - LLItemRequestTimes mQuestionableCOFLinks; -}; - -#endif +/** + * @file llattachmentsmgr.h + * @brief Batches up attachment requests and sends them all + * in one message. + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLATTACHMENTSMGR_H +#define LL_LLATTACHMENTSMGR_H + +#include "llsingleton.h" + +//-------------------------------------------------------------------------------- +// LLAttachmentsMgr +// +// This class manages batching up of requests at two stages of +// attachment rezzing. +// +// First, attachments requested to rez get saved in +// mPendingAttachments and sent as a single +// RezMultipleAttachmentsFromInv request. This batching is needed +// mainly because of weaknessing the UI element->inventory item +// handling, such that we don't always know when we are requesting +// multiple items. Now they just pile up and get swept into a single +// request during the idle loop. +// +// Second, after attachments arrive, we need to generate COF links for +// them. There are both efficiency and UI correctness reasons why it +// is better to request all the COF links at once and run a single +// callback after they all complete. Given the vagaries of the +// attachment system, there is no guarantee that we will get all the +// attachments we ask for, but we frequently do. So in the common case +// that all the desired attachments arrive fairly quickly, we generate +// a single batched request for COF links. If attachments arrive late +// or not at all, we will still issue COF link requests once a timeout +// value has been exceeded. +// +// To handle attachments that never arrive, we forget about requests +// that exceed a timeout value. +//-------------------------------------------------------------------------------- +class LLAttachmentsMgr: public LLSingleton +{ + LLSINGLETON(LLAttachmentsMgr); + virtual ~LLAttachmentsMgr(); + +public: + // Stores info for attachments that will be requested during idle. + struct AttachmentsInfo + { + LLUUID mItemID; + U8 mAttachmentPt; + bool mAdd; + }; + typedef std::deque attachments_vec_t; + + void addAttachmentRequest(const LLUUID& item_id, + const U8 attachment_pt, + const bool add); + void onAttachmentRequested(const LLUUID& item_id); + void requestAttachments(attachments_vec_t& attachment_requests); + static void onIdle(void *); + + void onAttachmentArrived(const LLUUID& inv_item_id); + + void onDetachRequested(const LLUUID& inv_item_id); + void onDetachCompleted(const LLUUID& inv_item_id); + + bool isAttachmentStateComplete() const; + +private: + + class LLItemRequestTimes: public std::map + { + public: + LLItemRequestTimes(const std::string& op_name, F32 timeout); + void addTime(const LLUUID& inv_item_id); + void removeTime(const LLUUID& inv_item_id); + bool wasRequestedRecently(const LLUUID& item_id) const; + bool getTime(const LLUUID& inv_item_id, LLTimer& timer) const; + + private: + F32 mTimeout; + std::string mOpName; + }; + + void removeAttachmentRequestTime(const LLUUID& inv_item_id); + void onIdle(); + void requestPendingAttachments(); + void linkRecentlyArrivedAttachments(); + void expireOldAttachmentRequests(); + void expireOldDetachRequests(); + void checkInvalidCOFLinks(); + void spamStatusInfo(); + + // Attachments that we are planning to rez but haven't requested from the server yet. + attachments_vec_t mPendingAttachments; + + // Attachments that have been requested from server but have not arrived yet. + LLItemRequestTimes mAttachmentRequests; + + // Attachments that have been requested to detach but have not gone away yet. + LLItemRequestTimes mDetachRequests; + + // Attachments that have arrived but have not been linked in the COF yet. + std::set mRecentlyArrivedAttachments; + LLTimer mCOFLinkBatchTimer; + + // Attachments that are linked in the COF but may be invalid. + LLItemRequestTimes mQuestionableCOFLinks; +}; + +#endif diff --git a/indra/newview/llavataractions.cpp b/indra/newview/llavataractions.cpp index 47f2b39022..aeed29b999 100644 --- a/indra/newview/llavataractions.cpp +++ b/indra/newview/llavataractions.cpp @@ -1,1526 +1,1526 @@ -/** - * @file llavataractions.cpp - * @brief Friend-related actions (add, remove, offer teleport, etc) - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#include "llviewerprecompiledheaders.h" - -#include "llavataractions.h" - -#include "boost/lambda/lambda.hpp" // for lambda::constant - -#include "llavatarnamecache.h" // IDEVO -#include "llsd.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "roles_constants.h" // for GP_MEMBER_INVITE - -#include "llagent.h" -#include "llappviewer.h" // for gLastVersionChannel -#include "llcachename.h" -#include "llcallingcard.h" // for LLAvatarTracker -#include "llconversationlog.h" -#include "llfloateravatarpicker.h" // for LLFloaterAvatarPicker -#include "llfloaterconversationpreview.h" -#include "llfloatergroupinvite.h" -#include "llfloatergroups.h" -#include "llfloaterreg.h" -#include "llfloaterpay.h" -#include "llfloaterprofile.h" -#include "llfloatersidepanelcontainer.h" -#include "llfloaterwebcontent.h" -#include "llfloaterworldmap.h" -#include "llfolderview.h" -#include "llgiveinventory.h" -#include "llinventorybridge.h" -#include "llinventorymodel.h" // for gInventory.findCategoryUUIDForType -#include "llinventorypanel.h" -#include "llfloaterimcontainer.h" -#include "llimview.h" // for gIMMgr -#include "llmutelist.h" -#include "llnotificationsutil.h" // for LLNotificationsUtil -#include "llpaneloutfitedit.h" -#include "llpanelprofile.h" -#include "llparcel.h" -#include "llrecentpeople.h" -#include "lltrans.h" -#include "llviewercontrol.h" -#include "llviewerobjectlist.h" -#include "llviewermessage.h" // for handle_lure -#include "llviewernetwork.h" //LLGridManager -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" -#include "lltrans.h" -#include "llcallingcard.h" -#include "llslurl.h" // IDEVO -#include "llsidepanelinventory.h" -#include "llavatarname.h" -#include "llagentui.h" -#include "lluiusage.h" - -// Flags for kick message -const U32 KICK_FLAGS_DEFAULT = 0x0; -const U32 KICK_FLAGS_FREEZE = 1 << 0; -const U32 KICK_FLAGS_UNFREEZE = 1 << 1; - - -std::string getProfileURL(const std::string& agent_name, bool feed_only) -{ - std::string url = "[WEB_PROFILE_URL][AGENT_NAME][FEED_ONLY]"; - LLSD subs; - subs["WEB_PROFILE_URL"] = LLGridManager::getInstance()->getWebProfileURL(); - subs["AGENT_NAME"] = agent_name; - subs["FEED_ONLY"] = feed_only ? "/?feed_only=true" : ""; - url = LLWeb::expandURLSubstitutions(url, subs); - LLStringUtil::toLower(url); - return url; -} - - -// static -void LLAvatarActions::requestFriendshipDialog(const LLUUID& id, const std::string& name) -{ - if(id == gAgentID) - { - LLNotificationsUtil::add("AddSelfFriend"); - return; - } - - LLSD args; - args["NAME"] = LLSLURL("agent", id, "completename").getSLURLString(); - LLSD payload; - payload["id"] = id; - payload["name"] = name; - - LLNotificationsUtil::add("AddFriendWithMessage", args, payload, &callbackAddFriendWithMessage); - - // add friend to recent people list - LLRecentPeople::instance().add(id); -} - -static void on_avatar_name_friendship(const LLUUID& id, const LLAvatarName av_name) -{ - LLAvatarActions::requestFriendshipDialog(id, av_name.getCompleteName()); -} - -// static -void LLAvatarActions::requestFriendshipDialog(const LLUUID& id) -{ - if(id.isNull()) - { - return; - } - - LLAvatarNameCache::get(id, boost::bind(&on_avatar_name_friendship, _1, _2)); -} - -// static -void LLAvatarActions::removeFriendDialog(const LLUUID& id) -{ - if (id.isNull()) - return; - - uuid_vec_t ids; - ids.push_back(id); - removeFriendsDialog(ids); -} - -// static -void LLAvatarActions::removeFriendsDialog(const uuid_vec_t& ids) -{ - if(ids.size() == 0) - return; - - LLSD args; - std::string msgType; - if(ids.size() == 1) - { - LLUUID agent_id = ids[0]; - LLAvatarName av_name; - if(LLAvatarNameCache::get(agent_id, &av_name)) - { - args["NAME"] = av_name.getCompleteName(); - } - - msgType = "RemoveFromFriends"; - } - else - { - msgType = "RemoveMultipleFromFriends"; - } - - LLSD payload; - for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it) - { - payload["ids"].append(*it); - } - - LLNotificationsUtil::add(msgType, - args, - payload, - &handleRemove); -} - -// static -void LLAvatarActions::offerTeleport(const LLUUID& invitee) -{ - if (invitee.isNull()) - return; - - std::vector ids; - ids.push_back(invitee); - offerTeleport(ids); -} - -// static -void LLAvatarActions::offerTeleport(const uuid_vec_t& ids) -{ - if (ids.size() == 0) - return; - - handle_lure(ids); -} - -static void on_avatar_name_cache_start_im(const LLUUID& agent_id, - const LLAvatarName& av_name) -{ - std::string name = av_name.getDisplayName(); - LLUUID session_id = gIMMgr->addSession(name, IM_NOTHING_SPECIAL, agent_id); - if (session_id != LLUUID::null) - { - LLFloaterIMContainer::getInstance()->showConversation(session_id); - } - make_ui_sound("UISndStartIM"); -} - -// static -void LLAvatarActions::startIM(const LLUUID& id) -{ - if (id.isNull() || gAgent.getID() == id) - return; - - LLAvatarNameCache::get(id, boost::bind(&on_avatar_name_cache_start_im, _1, _2)); -} - -// static -void LLAvatarActions::endIM(const LLUUID& id) -{ - if (id.isNull()) - return; - - LLUUID session_id = gIMMgr->computeSessionID(IM_NOTHING_SPECIAL, id); - if (session_id != LLUUID::null) - { - gIMMgr->leaveSession(session_id); - } -} - -static void on_avatar_name_cache_start_call(const LLUUID& agent_id, - const LLAvatarName& av_name) -{ - std::string name = av_name.getDisplayName(); - LLUUID session_id = gIMMgr->addSession(name, IM_NOTHING_SPECIAL, agent_id, true); - if (session_id != LLUUID::null) - { - gIMMgr->startCall(session_id); - } - make_ui_sound("UISndStartIM"); -} - -// static -void LLAvatarActions::startCall(const LLUUID& id) -{ - if (id.isNull()) - { - return; - } - LLAvatarNameCache::get(id, boost::bind(&on_avatar_name_cache_start_call, _1, _2)); -} - -// static -void LLAvatarActions::startAdhocCall(const uuid_vec_t& ids, const LLUUID& floater_id) -{ - if (ids.size() == 0) - { - return; - } - - // convert vector into std::vector for addSession - std::vector id_array; - id_array.reserve(ids.size()); - for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it) - { - id_array.push_back(*it); - } - - // create the new ad hoc voice session - const std::string title = LLTrans::getString("conference-title"); - LLUUID session_id = gIMMgr->addSession(title, IM_SESSION_CONFERENCE_START, - ids[0], id_array, true, floater_id); - if (session_id == LLUUID::null) - { - return; - } - - gIMMgr->autoStartCallOnStartup(session_id); - - make_ui_sound("UISndStartIM"); -} - -/* AD *TODO: Is this function needed any more? - I fixed it a bit(added check for canCall), but it appears that it is not used - anywhere. Maybe it should be removed? -// static -bool LLAvatarActions::isCalling(const LLUUID &id) -{ - if (id.isNull() || !canCall()) - { - return false; - } - - LLUUID session_id = gIMMgr->computeSessionID(IM_NOTHING_SPECIAL, id); - return (LLIMModel::getInstance()->findIMSession(session_id) != NULL); -}*/ - -//static -bool LLAvatarActions::canCall() -{ - return LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking(); -} - -// static -void LLAvatarActions::startConference(const uuid_vec_t& ids, const LLUUID& floater_id) -{ - // *HACK: Copy into dynamic array - std::vector id_array; - - id_array.reserve(ids.size()); - for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it) - { - id_array.push_back(*it); - } - const std::string title = LLTrans::getString("conference-title"); - LLUUID session_id = gIMMgr->addSession(title, IM_SESSION_CONFERENCE_START, ids[0], id_array, false, floater_id); - - if (session_id == LLUUID::null) - { - return; - } - - LLFloaterIMContainer::getInstance()->showConversation(session_id); - - make_ui_sound("UISndStartIM"); -} - -// static -void LLAvatarActions::showProfile(const LLUUID& avatar_id) -{ - if (avatar_id.notNull()) - { - LLFloaterReg::showInstance("profile", LLSD().with("id", avatar_id)); - } -} - -// static -void LLAvatarActions::showPicks(const LLUUID& avatar_id) -{ - if (avatar_id.notNull()) - { - LLFloaterProfile* profilefloater = dynamic_cast(LLFloaterReg::showInstance("profile", LLSD().with("id", avatar_id))); - if (profilefloater) - { - profilefloater->showPick(); - } - } -} - -// static -void LLAvatarActions::showPick(const LLUUID& avatar_id, const LLUUID& pick_id) -{ - if (avatar_id.notNull()) - { - LLFloaterProfile* profilefloater = dynamic_cast(LLFloaterReg::showInstance("profile", LLSD().with("id", avatar_id))); - if (profilefloater) - { - profilefloater->showPick(pick_id); - } - } -} - -// static -void LLAvatarActions::createPick() -{ - LLFloaterProfile* profilefloater = dynamic_cast(LLFloaterReg::showInstance("profile", LLSD().with("id", gAgent.getID()))); - LLViewerRegion* region = gAgent.getRegion(); - if (profilefloater && region) - { - LLPickData data; - data.pos_global = gAgent.getPositionGlobal(); - data.sim_name = region->getName(); - - LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - if (parcel) - { - data.name = parcel->getName(); - data.desc = parcel->getDesc(); - data.snapshot_id = parcel->getSnapshotID(); - data.parcel_id = parcel->getID(); - } - else - { - data.name = region->getName(); - } - - profilefloater->createPick(data); - } -} - -// static -bool LLAvatarActions::isPickTabSelected(const LLUUID& avatar_id) -{ - if (avatar_id.notNull()) - { - LLFloaterProfile* profilefloater = LLFloaterReg::findTypedInstance("profile", LLSD().with("id", avatar_id)); - if (profilefloater) - { - return profilefloater->isPickTabSelected(); - } - } - return false; -} - -// static -void LLAvatarActions::showClassifieds(const LLUUID& avatar_id) -{ - if (avatar_id.notNull()) - { - LLFloaterProfile* profilefloater = dynamic_cast(LLFloaterReg::showInstance("profile", LLSD().with("id", avatar_id))); - if (profilefloater) - { - profilefloater->showClassified(); - } - } -} - -// static -void LLAvatarActions::showClassified(const LLUUID& avatar_id, const LLUUID& classified_id, bool edit) -{ - if (avatar_id.notNull()) - { - LLFloaterProfile* profilefloater = dynamic_cast(LLFloaterReg::showInstance("profile", LLSD().with("id", avatar_id))); - if (profilefloater) - { - profilefloater->showClassified(classified_id, edit); - } - } -} - -// static -void LLAvatarActions::createClassified() -{ - LLFloaterProfile* profilefloater = dynamic_cast(LLFloaterReg::showInstance("profile", LLSD().with("id", gAgent.getID()))); - if (profilefloater) - { - profilefloater->createClassified(); - } -} - -//static -bool LLAvatarActions::profileVisible(const LLUUID& avatar_id) -{ - LLSD sd; - sd["id"] = avatar_id; - LLFloater* floater = getProfileFloater(avatar_id); - return floater && floater->isShown(); -} - -//static -LLFloater* LLAvatarActions::getProfileFloater(const LLUUID& avatar_id) -{ - LLFloaterProfile* floater = LLFloaterReg::findTypedInstance("profile", LLSD().with("id", avatar_id)); - return floater; -} - -//static -void LLAvatarActions::hideProfile(const LLUUID& avatar_id) -{ - LLSD sd; - sd["id"] = avatar_id; - LLFloater* floater = getProfileFloater(avatar_id); - if (floater) - { - floater->closeFloater(); - } -} - -// static -void LLAvatarActions::showOnMap(const LLUUID& id) -{ - LLAvatarName av_name; - if (!LLAvatarNameCache::get(id, &av_name)) - { - LLAvatarNameCache::get(id, boost::bind(&LLAvatarActions::showOnMap, id)); - return; - } - - gFloaterWorldMap->trackAvatar(id, av_name.getDisplayName()); - LLFloaterReg::showInstance("world_map", "center"); -} - -// static -void LLAvatarActions::pay(const LLUUID& id) -{ - LLNotification::Params params("DoNotDisturbModePay"); - params.functor.function(boost::bind(&LLAvatarActions::handlePay, _1, _2, id)); - - if (gAgent.isDoNotDisturb()) - { - // warn users of being in do not disturb mode during a transaction - LLNotifications::instance().add(params); - } - else - { - LLNotifications::instance().forceResponse(params, 1); - } -} - -void LLAvatarActions::teleport_request_callback(const LLSD& notification, const LLSD& response) -{ - S32 option; - if (response.isInteger()) - { - option = response.asInteger(); - } - else - { - option = LLNotificationsUtil::getSelectedOption(notification, response); - } - - if (0 == option) - { - LLMessageSystem* msg = gMessageSystem; - - msg->newMessageFast(_PREHASH_ImprovedInstantMessage); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - - msg->nextBlockFast(_PREHASH_MessageBlock); - msg->addBOOLFast(_PREHASH_FromGroup, false); - msg->addUUIDFast(_PREHASH_ToAgentID, notification["substitutions"]["uuid"] ); - msg->addU8Fast(_PREHASH_Offline, IM_ONLINE); - msg->addU8Fast(_PREHASH_Dialog, IM_TELEPORT_REQUEST); - msg->addUUIDFast(_PREHASH_ID, LLUUID::null); - msg->addU32Fast(_PREHASH_Timestamp, NO_TIMESTAMP); // no timestamp necessary - - std::string name; - LLAgentUI::buildFullname(name); - - msg->addStringFast(_PREHASH_FromAgentName, name); - msg->addStringFast(_PREHASH_Message, response["message"]); - msg->addU32Fast(_PREHASH_ParentEstateID, 0); - msg->addUUIDFast(_PREHASH_RegionID, LLUUID::null); - msg->addVector3Fast(_PREHASH_Position, gAgent.getPositionAgent()); - - gMessageSystem->addBinaryDataFast( - _PREHASH_BinaryBucket, - EMPTY_BINARY_BUCKET, - EMPTY_BINARY_BUCKET_SIZE); - - gAgent.sendReliableMessage(); - } -} - -// static -void LLAvatarActions::teleportRequest(const LLUUID& id) -{ - LLSD notification; - notification["uuid"] = id; - LLAvatarName av_name; - if (!LLAvatarNameCache::get(id, &av_name)) - { - // unlikely ... they just picked this name from somewhere... - LLAvatarNameCache::get(id, boost::bind(&LLAvatarActions::teleportRequest, id)); - return; // reinvoke this when the name resolves - } - notification["NAME"] = av_name.getCompleteName(); - - LLSD payload; - - LLNotificationsUtil::add("TeleportRequestPrompt", notification, payload, teleport_request_callback); -} - -// static -void LLAvatarActions::kick(const LLUUID& id) -{ - LLSD payload; - payload["avatar_id"] = id; - LLNotifications::instance().add("KickUser", LLSD(), payload, handleKick); -} - -// static -void LLAvatarActions::freezeAvatar(const LLUUID& id) -{ - LLAvatarName av_name; - LLSD payload; - payload["avatar_id"] = id; - - if (LLAvatarNameCache::get(id, &av_name)) - { - LLSD args; - args["AVATAR_NAME"] = av_name.getUserName(); - LLNotificationsUtil::add("FreezeAvatarFullname", args, payload, handleFreezeAvatar); - } - else - { - LLNotificationsUtil::add("FreezeAvatar", LLSD(), payload, handleFreezeAvatar); - } -} - -// static -void LLAvatarActions::ejectAvatar(const LLUUID& id, bool ban_enabled) -{ - LLAvatarName av_name; - LLSD payload; - payload["avatar_id"] = id; - payload["ban_enabled"] = ban_enabled; - LLSD args; - bool has_name = LLAvatarNameCache::get(id, &av_name); - if (has_name) - { - args["AVATAR_NAME"] = av_name.getUserName(); - } - - if (ban_enabled) - { - LLNotificationsUtil::add("EjectAvatarFullname", args, payload, handleEjectAvatar); - } - else - { - if (has_name) - { - LLNotificationsUtil::add("EjectAvatarFullnameNoBan", args, payload, handleEjectAvatar); - } - else - { - LLNotificationsUtil::add("EjectAvatarNoBan", LLSD(), payload, handleEjectAvatar); - } - } -} - -// static -void LLAvatarActions::freeze(const LLUUID& id) -{ - LLSD payload; - payload["avatar_id"] = id; - LLNotifications::instance().add("FreezeUser", LLSD(), payload, handleFreeze); -} -// static -void LLAvatarActions::unfreeze(const LLUUID& id) -{ - LLSD payload; - payload["avatar_id"] = id; - LLNotifications::instance().add("UnFreezeUser", LLSD(), payload, handleUnfreeze); -} - -//static -void LLAvatarActions::csr(const LLUUID& id, std::string name) -{ - if (name.empty()) return; - - std::string url = "http://csr.lindenlab.com/agent/"; - - // slow and stupid, but it's late - S32 len = name.length(); - for (S32 i = 0; i < len; i++) - { - if (name[i] == ' ') - { - url += "%20"; - } - else - { - url += name[i]; - } - } - - LLWeb::loadURL(url); -} - -//static -void LLAvatarActions::share(const LLUUID& id) -{ - LLSD key; - LLFloaterSidePanelContainer::showPanel("inventory", key); - LLFloaterReg::showInstance("im_container"); - - LLUUID session_id = gIMMgr->computeSessionID(IM_NOTHING_SPECIAL,id); - - if (!gIMMgr->hasSession(session_id)) - { - startIM(id); - } - - if (gIMMgr->hasSession(session_id)) - { - // we should always get here, but check to verify anyways - LLIMModel::getInstance()->addMessage(session_id, SYSTEM_FROM, LLUUID::null, LLTrans::getString("share_alert"), false); - - LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(session_id); - if (session_floater && session_floater->isMinimized()) - { - session_floater->setMinimized(false); - } - LLFloaterIMContainer *im_container = LLFloaterReg::getTypedInstance("im_container"); - im_container->selectConversationPair(session_id, true); - } -} - -namespace action_give_inventory -{ - /** - * Returns a pointer to 'Add More' inventory panel of Edit Outfit SP. - */ - static LLInventoryPanel* get_outfit_editor_inventory_panel() - { - LLPanelOutfitEdit* panel_outfit_edit = dynamic_cast(LLFloaterSidePanelContainer::getPanel("appearance", "panel_outfit_edit")); - if (NULL == panel_outfit_edit) return NULL; - - LLInventoryPanel* inventory_panel = panel_outfit_edit->findChild("folder_view"); - return inventory_panel; - } - - /** - * @return active inventory panel, or NULL if there's no such panel - */ - static LLInventoryPanel* get_active_inventory_panel() - { - LLInventoryPanel* active_panel = LLInventoryPanel::getActiveInventoryPanel(false); - LLFloater* floater_appearance = LLFloaterReg::findInstance("appearance"); - if (!active_panel || (floater_appearance && floater_appearance->hasFocus())) - { - active_panel = get_outfit_editor_inventory_panel(); - } - - return active_panel; - } - - /** - * Checks My Inventory visibility. - */ - static bool is_give_inventory_acceptable_ids(const std::set inventory_selected_uuids) - { - if (inventory_selected_uuids.empty()) return false; // nothing selected - - bool acceptable = false; - std::set::const_iterator it = inventory_selected_uuids.begin(); - const std::set::const_iterator it_end = inventory_selected_uuids.end(); - for (; it != it_end; ++it) - { - LLViewerInventoryCategory* inv_cat = gInventory.getCategory(*it); - // any category can be offered. - if (inv_cat) - { - acceptable = true; - continue; - } - - LLViewerInventoryItem* inv_item = gInventory.getItem(*it); - // check if inventory item can be given - if (LLGiveInventory::isInventoryGiveAcceptable(inv_item)) - { - acceptable = true; - continue; - } - - // there are neither item nor category in inventory - acceptable = false; - break; - } - return acceptable; - } - - static bool is_give_inventory_acceptable(LLInventoryPanel* panel = NULL) - { - // check selection in the panel - std::set inventory_selected_uuids = LLAvatarActions::getInventorySelectedUUIDs(panel); - if (inventory_selected_uuids.empty()) - { - if(panel && panel->getRootFolder() && panel->getRootFolder()->isSingleFolderMode()) - { - inventory_selected_uuids.insert(panel->getRootFolderID()); - } - else - { - return false; // nothing selected - } - } - - return is_give_inventory_acceptable_ids(inventory_selected_uuids); - } - - static void build_items_string(const std::set& inventory_selected_uuids , std::string& items_string) - { - llassert(inventory_selected_uuids.size() > 0); - - const std::string& separator = LLTrans::getString("words_separator"); - for (std::set::const_iterator it = inventory_selected_uuids.begin(); ; ) - { - LLViewerInventoryCategory* inv_cat = gInventory.getCategory(*it); - if (NULL != inv_cat) - { - items_string = inv_cat->getName(); - break; - } - LLViewerInventoryItem* inv_item = gInventory.getItem(*it); - if (NULL != inv_item) - { - items_string.append(inv_item->getName()); - } - if(++it == inventory_selected_uuids.end()) - { - break; - } - items_string.append(separator); - } - } - - struct LLShareInfo : public LLSingleton - { - LLSINGLETON_EMPTY_CTOR(LLShareInfo); - public: - std::vector mAvatarNames; - uuid_vec_t mAvatarUuids; - }; - - static void give_inventory_cb(const LLSD& notification, const LLSD& response, std::set inventory_selected_uuids) - { - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - // if Cancel pressed - if (option == 1) - { - return; - } - - if (inventory_selected_uuids.empty()) - { - return; - } - - S32 count = LLShareInfo::instance().mAvatarNames.size(); - bool shared = count && !inventory_selected_uuids.empty(); - - // iterate through avatars - for(S32 i = 0; i < count; ++i) - { - const LLUUID& avatar_uuid = LLShareInfo::instance().mAvatarUuids[i]; - - // We souldn't open IM session, just calculate session ID for logging purpose. See EXT-6710 - const LLUUID session_id = gIMMgr->computeSessionID(IM_NOTHING_SPECIAL, avatar_uuid); - - std::set::const_iterator it = inventory_selected_uuids.begin(); - const std::set::const_iterator it_end = inventory_selected_uuids.end(); - - const std::string& separator = LLTrans::getString("words_separator"); - std::string noncopy_item_names; - LLSD noncopy_items = LLSD::emptyArray(); - // iterate through selected inventory objects - for (; it != it_end; ++it) - { - LLViewerInventoryCategory* inv_cat = gInventory.getCategory(*it); - if (inv_cat) - { - if (!LLGiveInventory::doGiveInventoryCategory(avatar_uuid, inv_cat, session_id, "ItemsShared")) - { - shared = false; - } - break; - } - LLViewerInventoryItem* inv_item = gInventory.getItem(*it); - if (!inv_item->getPermissions().allowCopyBy(gAgentID)) - { - if (!noncopy_item_names.empty()) - { - noncopy_item_names.append(separator); - } - noncopy_item_names.append(inv_item->getName()); - noncopy_items.append(*it); - } - else - { - if (!LLGiveInventory::doGiveInventoryItem(avatar_uuid, inv_item, session_id)) - { - shared = false; - } - } - } - if (noncopy_items.beginArray() != noncopy_items.endArray()) - { - LLSD substitutions; - substitutions["ITEMS"] = noncopy_item_names; - LLSD payload; - payload["agent_id"] = avatar_uuid; - payload["items"] = noncopy_items; - payload["success_notification"] = "ItemsShared"; - LLNotificationsUtil::add("CannotCopyWarning", substitutions, payload, - &LLGiveInventory::handleCopyProtectedItem); - shared = false; - break; - } - } - if (shared) - { - LLFloaterReg::hideInstance("avatar_picker"); - LLNotificationsUtil::add("ItemsShared"); - } - } - - /** - * Performs "give inventory" operations for provided avatars. - * - * Sends one requests to give all selected inventory items for each passed avatar. - * Avatars are represent by two vectors: names and UUIDs which must be sychronized with each other. - * - * @param avatar_names - avatar names request to be sent. - * @param avatar_uuids - avatar names request to be sent. - */ - - static void give_inventory_ids(const uuid_vec_t& avatar_uuids, const std::vector avatar_names, const uuid_set_t inventory_selected_uuids) - { - llassert(avatar_names.size() == avatar_uuids.size()); - - if (inventory_selected_uuids.empty()) - { - return; - } - - std::string residents; - LLAvatarActions::buildResidentsString(avatar_names, residents, true); - - std::string items; - build_items_string(inventory_selected_uuids, items); - - int folders_count = 0; - std::set::const_iterator it = inventory_selected_uuids.begin(); - - //traverse through selected inventory items and count folders among them - for ( ; it != inventory_selected_uuids.end() && folders_count <=1 ; ++it) - { - LLViewerInventoryCategory* inv_cat = gInventory.getCategory(*it); - if (NULL != inv_cat) - { - folders_count++; - } - } - - // EXP-1599 - // In case of sharing multiple folders, make the confirmation - // dialog contain a warning that only one folder can be shared at a time. - std::string notification = (folders_count > 1) ? "ShareFolderConfirmation" : "ShareItemsConfirmation"; - LLSD substitutions; - substitutions["RESIDENTS"] = residents; - substitutions["ITEMS"] = items; - LLShareInfo::instance().mAvatarNames = avatar_names; - LLShareInfo::instance().mAvatarUuids = avatar_uuids; - LLNotificationsUtil::add(notification, substitutions, LLSD(), boost::bind(&give_inventory_cb, _1, _2, inventory_selected_uuids)); - } - - static void give_inventory(const uuid_vec_t& avatar_uuids, const std::vector avatar_names, LLInventoryPanel* panel = NULL) - { - llassert(avatar_names.size() == avatar_uuids.size()); - std::set inventory_selected_uuids = LLAvatarActions::getInventorySelectedUUIDs(panel);; - - if (inventory_selected_uuids.empty()) - { - if(panel && panel->getRootFolder() && panel->getRootFolder()->isSingleFolderMode()) - { - inventory_selected_uuids.insert(panel->getRootFolderID()); - } - else - { - return; - } - } - give_inventory_ids(avatar_uuids, avatar_names, inventory_selected_uuids); - } -} - -// static -void LLAvatarActions::buildResidentsString(std::vector avatar_names, std::string& residents_string, bool complete_name) -{ - llassert(avatar_names.size() > 0); - - std::sort(avatar_names.begin(), avatar_names.end()); - const std::string& separator = LLTrans::getString("words_separator"); - for (std::vector::const_iterator it = avatar_names.begin(); ; ) - { - if(complete_name) - { - residents_string.append((*it).getCompleteName()); - } - else - { - residents_string.append((*it).getDisplayName()); - } - - if (++it == avatar_names.end()) - { - break; - } - residents_string.append(separator); - } -} - -// static -void LLAvatarActions::buildResidentsString(const uuid_vec_t& avatar_uuids, std::string& residents_string) -{ - std::vector avatar_names; - uuid_vec_t::const_iterator it = avatar_uuids.begin(); - for (; it != avatar_uuids.end(); ++it) - { - LLAvatarName av_name; - if (LLAvatarNameCache::get(*it, &av_name)) - { - avatar_names.push_back(av_name); - } - } - - // We should check whether the vector is not empty to pass the assertion - // that avatar_names.size() > 0 in LLAvatarActions::buildResidentsString. - if (!avatar_names.empty()) - { - LLAvatarActions::buildResidentsString(avatar_names, residents_string); - } -} - -//static -std::set LLAvatarActions::getInventorySelectedUUIDs(LLInventoryPanel* active_panel) -{ - std::set inventory_selected; - - if (!active_panel) - { - active_panel = action_give_inventory::get_active_inventory_panel(); - } - if (active_panel) - { - inventory_selected= active_panel->getRootFolder()->getSelectionList(); - } - - if (inventory_selected.empty()) - { - LLSidepanelInventory *sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); - if (sidepanel_inventory) - { - inventory_selected= sidepanel_inventory->getInboxSelectionList(); - } - } - - std::set inventory_selected_uuids; - for (std::set::iterator it = inventory_selected.begin(), end_it = inventory_selected.end(); - it != end_it; - ++it) - { - inventory_selected_uuids.insert(static_cast((*it)->getViewModelItem())->getUUID()); - } - return inventory_selected_uuids; -} - -//static -void LLAvatarActions::shareWithAvatars(LLView * panel) -{ - using namespace action_give_inventory; - - LLFloater* root_floater = gFloaterView->getParentFloater(panel); - LLInventoryPanel* inv_panel = dynamic_cast(panel); - LLFloaterAvatarPicker* picker = - LLFloaterAvatarPicker::show(boost::bind(give_inventory, _1, _2, inv_panel), true, false, false, root_floater->getName()); - if (!picker) - { - return; - } - - picker->setOkBtnEnableCb(boost::bind(is_give_inventory_acceptable, inv_panel)); - picker->openFriendsTab(); - - if (root_floater) - { - root_floater->addDependentFloater(picker); - } - LLNotificationsUtil::add("ShareNotification"); -} - -//static -void LLAvatarActions::shareWithAvatars(const uuid_set_t inventory_selected_uuids, LLFloater* root_floater) -{ - using namespace action_give_inventory; - - LLFloaterAvatarPicker* picker = - LLFloaterAvatarPicker::show(boost::bind(give_inventory_ids, _1, _2, inventory_selected_uuids), true, false, false, root_floater->getName()); - if (!picker) - { - return; - } - - picker->setOkBtnEnableCb(boost::bind(is_give_inventory_acceptable_ids, inventory_selected_uuids)); - picker->openFriendsTab(); - - if (root_floater) - { - root_floater->addDependentFloater(picker); - } - LLNotificationsUtil::add("ShareNotification"); -} - -// static -bool LLAvatarActions::canShareSelectedItems(LLInventoryPanel* inv_panel /* = NULL*/) -{ - using namespace action_give_inventory; - - if (!inv_panel) - { - LLInventoryPanel* active_panel = get_active_inventory_panel(); - if (!active_panel) return false; - inv_panel = active_panel; - } - - // check selection in the panel - LLFolderView* root_folder = inv_panel->getRootFolder(); - if (!root_folder) - { - return false; - } - const std::set inventory_selected = root_folder->getSelectionList(); - if (inventory_selected.empty()) return false; // nothing selected - - const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - bool can_share = true; - std::set::const_iterator it = inventory_selected.begin(); - const std::set::const_iterator it_end = inventory_selected.end(); - for (; it != it_end; ++it) - { - LLUUID cat_id = static_cast((*it)->getViewModelItem())->getUUID(); - LLViewerInventoryCategory* inv_cat = gInventory.getCategory(cat_id); - // any category can be offered if it's not in trash. - if (inv_cat) - { - if ((cat_id == trash_id) || gInventory.isObjectDescendentOf(cat_id, trash_id)) - { - can_share = false; - break; - } - continue; - } - - // check if inventory item can be given - LLFolderViewItem* item = *it; - if (!item) return false; - LLInvFVBridge* bridge = dynamic_cast(item->getViewModelItem()); - if (bridge && bridge->canShare()) - { - continue; - } - - // there are neither item nor category in inventory - can_share = false; - break; - } - - return can_share; -} - -// static -bool LLAvatarActions::toggleBlock(const LLUUID& id) -{ - LLAvatarName av_name; - LLAvatarNameCache::get(id, &av_name); - - LLMute mute(id, av_name.getUserName(), LLMute::AGENT); - - if (LLMuteList::getInstance()->isMuted(mute.mID, mute.mName)) - { - LLMuteList::getInstance()->remove(mute); - return false; - } - else - { - LLMuteList::getInstance()->add(mute); - return true; - } -} - -// static -void LLAvatarActions::toggleMute(const LLUUID& id, U32 flags) -{ - LLAvatarName av_name; - LLAvatarNameCache::get(id, &av_name); - - LLMuteList* mute_list = LLMuteList::getInstance(); - bool is_muted = mute_list->isMuted(id, flags); - - LLMute mute(id, av_name.getUserName(), LLMute::AGENT); - if (!is_muted) - { - mute_list->add(mute, flags); - } - else - { - mute_list->remove(mute, flags); - } -} - -// static -void LLAvatarActions::toggleMuteVoice(const LLUUID& id) -{ - toggleMute(id, LLMute::flagVoiceChat); -} - -// static -bool LLAvatarActions::canOfferTeleport(const LLUUID& id) -{ - // First use LLAvatarTracker::isBuddy() - // If LLAvatarTracker::instance().isBuddyOnline function only is used - // then for avatars that are online and not a friend it will return false. - // But we should give an ability to offer a teleport for such avatars. - if(LLAvatarTracker::instance().isBuddy(id)) - { - return LLAvatarTracker::instance().isBuddyOnline(id); - } - - return true; -} - -// static -bool LLAvatarActions::canOfferTeleport(const uuid_vec_t& ids) -{ - // We can't send more than 250 lures in a single message, so disable this - // button when there are too many id's selected. - if(ids.size() > 250) return false; - - bool result = true; - for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it) - { - if(!canOfferTeleport(*it)) - { - result = false; - break; - } - } - return result; -} - -void LLAvatarActions::inviteToGroup(const LLUUID& id) -{ - LLFloaterGroupPicker* widget = LLFloaterReg::showTypedInstance("group_picker", LLSD(id)); - if (widget) - { - widget->center(); - widget->setPowersMask(GP_MEMBER_INVITE); - widget->removeNoneOption(); - widget->setSelectGroupCallback(boost::bind(callback_invite_to_group, _1, id)); - } -} - -// static -void LLAvatarActions::viewChatHistory(const LLUUID& id) -{ - const std::vector& conversations = LLConversationLog::instance().getConversations(); - std::vector::const_iterator iter = conversations.begin(); - - for (; iter != conversations.end(); ++iter) - { - if (iter->getParticipantID() == id) - { - LLFloaterReg::showInstance("preview_conversation", iter->getSessionID(), true); - return; - } - } - - if (LLLogChat::isTranscriptExist(id)) - { - LLAvatarName avatar_name; - LLSD extended_id(id); - - LLAvatarNameCache::get(id, &avatar_name); - extended_id[LL_FCP_COMPLETE_NAME] = avatar_name.getCompleteName(); - extended_id[LL_FCP_ACCOUNT_NAME] = avatar_name.getAccountName(); - LLFloaterReg::showInstance("preview_conversation", extended_id, true); - } -} - -//== private methods ======================================================================================== - -// static -bool LLAvatarActions::handleRemove(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - - const LLSD& ids = notification["payload"]["ids"]; - for (LLSD::array_const_iterator itr = ids.beginArray(); itr != ids.endArray(); ++itr) - { - LLUUID id = itr->asUUID(); - const LLRelationship* ip = LLAvatarTracker::instance().getBuddyInfo(id); - if (ip) - { - switch (option) - { - case 0: // YES - if( ip->isRightGrantedTo(LLRelationship::GRANT_MODIFY_OBJECTS)) - { - LLAvatarTracker::instance().empower(id, false); - LLAvatarTracker::instance().notifyObservers(); - } - LLAvatarTracker::instance().terminateBuddy(id); - LLAvatarTracker::instance().notifyObservers(); - break; - - case 1: // NO - default: - LL_INFOS() << "No removal performed." << LL_ENDL; - break; - } - } - } - return false; -} - -// static -bool LLAvatarActions::handlePay(const LLSD& notification, const LLSD& response, LLUUID avatar_id) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) - { - gAgent.setDoNotDisturb(false); - } - - LLFloaterPayUtil::payDirectly(&give_money, avatar_id, /*is_group=*/false); - return false; -} - -// static -void LLAvatarActions::callback_invite_to_group(LLUUID group_id, LLUUID id) -{ - uuid_vec_t agent_ids; - agent_ids.push_back(id); - - LLFloaterGroupInvite::showForGroup(group_id, &agent_ids); -} - - -// static -bool LLAvatarActions::callbackAddFriendWithMessage(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) - { - requestFriendship(notification["payload"]["id"].asUUID(), - notification["payload"]["name"].asString(), - response["message"].asString()); - } - return false; -} - -// static -bool LLAvatarActions::handleKick(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotification::getSelectedOption(notification, response); - - if (option == 0) - { - LLUUID avatar_id = notification["payload"]["avatar_id"].asUUID(); - LLMessageSystem* msg = gMessageSystem; - - msg->newMessageFast(_PREHASH_GodKickUser); - msg->nextBlockFast(_PREHASH_UserInfo); - msg->addUUIDFast(_PREHASH_GodID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_GodSessionID, gAgent.getSessionID()); - msg->addUUIDFast(_PREHASH_AgentID, avatar_id ); - msg->addU32("KickFlags", KICK_FLAGS_DEFAULT ); - msg->addStringFast(_PREHASH_Reason, response["message"].asString() ); - gAgent.sendReliableMessage(); - } - return false; -} - -bool LLAvatarActions::handleFreezeAvatar(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotification::getSelectedOption(notification, response); - - if (0 == option || 1 == option) - { - U32 flags = 0x0; - if (1 == option) - { - // unfreeze - flags |= 0x1; - } - LLUUID avatar_id = notification["payload"]["avatar_id"].asUUID(); - LLMessageSystem* msg = gMessageSystem; - - msg->newMessage("FreezeUser"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->nextBlock("Data"); - msg->addUUID("TargetID", avatar_id ); - msg->addU32("Flags", flags ); - gAgent.sendReliableMessage(); - } - return false; -} - -bool LLAvatarActions::handleEjectAvatar(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (2 == option) - { - return false; - } - LLUUID avatar_id = notification["payload"]["avatar_id"].asUUID(); - bool ban_enabled = notification["payload"]["ban_enabled"].asBoolean(); - - if (0 == option) - { - LLMessageSystem* msg = gMessageSystem; - U32 flags = 0x0; - msg->newMessage("EjectUser"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID() ); - msg->addUUID("SessionID", gAgent.getSessionID() ); - msg->nextBlock("Data"); - msg->addUUID("TargetID", avatar_id ); - msg->addU32("Flags", flags ); - gAgent.sendReliableMessage(); - } - else if (ban_enabled) - { - LLMessageSystem* msg = gMessageSystem; - - U32 flags = 0x1; - msg->newMessage("EjectUser"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID() ); - msg->addUUID("SessionID", gAgent.getSessionID() ); - msg->nextBlock("Data"); - msg->addUUID("TargetID", avatar_id ); - msg->addU32("Flags", flags ); - gAgent.sendReliableMessage(); - } - return false; -} - -bool LLAvatarActions::handleFreeze(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotification::getSelectedOption(notification, response); - if (option == 0) - { - LLUUID avatar_id = notification["payload"]["avatar_id"].asUUID(); - LLMessageSystem* msg = gMessageSystem; - - msg->newMessageFast(_PREHASH_GodKickUser); - msg->nextBlockFast(_PREHASH_UserInfo); - msg->addUUIDFast(_PREHASH_GodID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_GodSessionID, gAgent.getSessionID()); - msg->addUUIDFast(_PREHASH_AgentID, avatar_id ); - msg->addU32("KickFlags", KICK_FLAGS_FREEZE ); - msg->addStringFast(_PREHASH_Reason, response["message"].asString() ); - gAgent.sendReliableMessage(); - } - return false; -} - -bool LLAvatarActions::handleUnfreeze(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotification::getSelectedOption(notification, response); - std::string text = response["message"].asString(); - if (option == 0) - { - LLUUID avatar_id = notification["payload"]["avatar_id"].asUUID(); - LLMessageSystem* msg = gMessageSystem; - - msg->newMessageFast(_PREHASH_GodKickUser); - msg->nextBlockFast(_PREHASH_UserInfo); - msg->addUUIDFast(_PREHASH_GodID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_GodSessionID, gAgent.getSessionID()); - msg->addUUIDFast(_PREHASH_AgentID, avatar_id ); - msg->addU32("KickFlags", KICK_FLAGS_UNFREEZE ); - msg->addStringFast(_PREHASH_Reason, text ); - gAgent.sendReliableMessage(); - } - return false; -} - -// static -void LLAvatarActions::requestFriendship(const LLUUID& target_id, const std::string& target_name, const std::string& message) -{ - const LLUUID calling_card_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD); - LLUIUsage::instance().logCommand("Agent.SendFriendRequest"); - - send_improved_im(target_id, - target_name, - message, - IM_ONLINE, - IM_FRIENDSHIP_OFFERED, - calling_card_folder_id); - - LLSD args; - args["TO_NAME"] = target_name; - - LLSD payload; - payload["from_id"] = target_id; - LLNotificationsUtil::add("FriendshipOffered", args, payload); -} - -//static -bool LLAvatarActions::isFriend(const LLUUID& id) -{ - return ( NULL != LLAvatarTracker::instance().getBuddyInfo(id) ); -} - -// static -bool LLAvatarActions::isBlocked(const LLUUID& id) -{ - LLAvatarName av_name; - LLAvatarNameCache::get(id, &av_name); - return LLMuteList::getInstance()->isMuted(id, av_name.getUserName()); -} - -// static -bool LLAvatarActions::isVoiceMuted(const LLUUID& id) -{ - return LLMuteList::getInstance()->isMuted(id, LLMute::flagVoiceChat); -} - -// static -bool LLAvatarActions::canBlock(const LLUUID& id) -{ - LLAvatarName av_name; - LLAvatarNameCache::get(id, &av_name); - - std::string full_name = av_name.getUserName(); - bool is_linden = (full_name.find("Linden") != std::string::npos); - bool is_self = id == gAgentID; - return !is_self && !is_linden; -} +/** + * @file llavataractions.cpp + * @brief Friend-related actions (add, remove, offer teleport, etc) + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#include "llviewerprecompiledheaders.h" + +#include "llavataractions.h" + +#include "boost/lambda/lambda.hpp" // for lambda::constant + +#include "llavatarnamecache.h" // IDEVO +#include "llsd.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "roles_constants.h" // for GP_MEMBER_INVITE + +#include "llagent.h" +#include "llappviewer.h" // for gLastVersionChannel +#include "llcachename.h" +#include "llcallingcard.h" // for LLAvatarTracker +#include "llconversationlog.h" +#include "llfloateravatarpicker.h" // for LLFloaterAvatarPicker +#include "llfloaterconversationpreview.h" +#include "llfloatergroupinvite.h" +#include "llfloatergroups.h" +#include "llfloaterreg.h" +#include "llfloaterpay.h" +#include "llfloaterprofile.h" +#include "llfloatersidepanelcontainer.h" +#include "llfloaterwebcontent.h" +#include "llfloaterworldmap.h" +#include "llfolderview.h" +#include "llgiveinventory.h" +#include "llinventorybridge.h" +#include "llinventorymodel.h" // for gInventory.findCategoryUUIDForType +#include "llinventorypanel.h" +#include "llfloaterimcontainer.h" +#include "llimview.h" // for gIMMgr +#include "llmutelist.h" +#include "llnotificationsutil.h" // for LLNotificationsUtil +#include "llpaneloutfitedit.h" +#include "llpanelprofile.h" +#include "llparcel.h" +#include "llrecentpeople.h" +#include "lltrans.h" +#include "llviewercontrol.h" +#include "llviewerobjectlist.h" +#include "llviewermessage.h" // for handle_lure +#include "llviewernetwork.h" //LLGridManager +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "lltrans.h" +#include "llcallingcard.h" +#include "llslurl.h" // IDEVO +#include "llsidepanelinventory.h" +#include "llavatarname.h" +#include "llagentui.h" +#include "lluiusage.h" + +// Flags for kick message +const U32 KICK_FLAGS_DEFAULT = 0x0; +const U32 KICK_FLAGS_FREEZE = 1 << 0; +const U32 KICK_FLAGS_UNFREEZE = 1 << 1; + + +std::string getProfileURL(const std::string& agent_name, bool feed_only) +{ + std::string url = "[WEB_PROFILE_URL][AGENT_NAME][FEED_ONLY]"; + LLSD subs; + subs["WEB_PROFILE_URL"] = LLGridManager::getInstance()->getWebProfileURL(); + subs["AGENT_NAME"] = agent_name; + subs["FEED_ONLY"] = feed_only ? "/?feed_only=true" : ""; + url = LLWeb::expandURLSubstitutions(url, subs); + LLStringUtil::toLower(url); + return url; +} + + +// static +void LLAvatarActions::requestFriendshipDialog(const LLUUID& id, const std::string& name) +{ + if(id == gAgentID) + { + LLNotificationsUtil::add("AddSelfFriend"); + return; + } + + LLSD args; + args["NAME"] = LLSLURL("agent", id, "completename").getSLURLString(); + LLSD payload; + payload["id"] = id; + payload["name"] = name; + + LLNotificationsUtil::add("AddFriendWithMessage", args, payload, &callbackAddFriendWithMessage); + + // add friend to recent people list + LLRecentPeople::instance().add(id); +} + +static void on_avatar_name_friendship(const LLUUID& id, const LLAvatarName av_name) +{ + LLAvatarActions::requestFriendshipDialog(id, av_name.getCompleteName()); +} + +// static +void LLAvatarActions::requestFriendshipDialog(const LLUUID& id) +{ + if(id.isNull()) + { + return; + } + + LLAvatarNameCache::get(id, boost::bind(&on_avatar_name_friendship, _1, _2)); +} + +// static +void LLAvatarActions::removeFriendDialog(const LLUUID& id) +{ + if (id.isNull()) + return; + + uuid_vec_t ids; + ids.push_back(id); + removeFriendsDialog(ids); +} + +// static +void LLAvatarActions::removeFriendsDialog(const uuid_vec_t& ids) +{ + if(ids.size() == 0) + return; + + LLSD args; + std::string msgType; + if(ids.size() == 1) + { + LLUUID agent_id = ids[0]; + LLAvatarName av_name; + if(LLAvatarNameCache::get(agent_id, &av_name)) + { + args["NAME"] = av_name.getCompleteName(); + } + + msgType = "RemoveFromFriends"; + } + else + { + msgType = "RemoveMultipleFromFriends"; + } + + LLSD payload; + for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it) + { + payload["ids"].append(*it); + } + + LLNotificationsUtil::add(msgType, + args, + payload, + &handleRemove); +} + +// static +void LLAvatarActions::offerTeleport(const LLUUID& invitee) +{ + if (invitee.isNull()) + return; + + std::vector ids; + ids.push_back(invitee); + offerTeleport(ids); +} + +// static +void LLAvatarActions::offerTeleport(const uuid_vec_t& ids) +{ + if (ids.size() == 0) + return; + + handle_lure(ids); +} + +static void on_avatar_name_cache_start_im(const LLUUID& agent_id, + const LLAvatarName& av_name) +{ + std::string name = av_name.getDisplayName(); + LLUUID session_id = gIMMgr->addSession(name, IM_NOTHING_SPECIAL, agent_id); + if (session_id != LLUUID::null) + { + LLFloaterIMContainer::getInstance()->showConversation(session_id); + } + make_ui_sound("UISndStartIM"); +} + +// static +void LLAvatarActions::startIM(const LLUUID& id) +{ + if (id.isNull() || gAgent.getID() == id) + return; + + LLAvatarNameCache::get(id, boost::bind(&on_avatar_name_cache_start_im, _1, _2)); +} + +// static +void LLAvatarActions::endIM(const LLUUID& id) +{ + if (id.isNull()) + return; + + LLUUID session_id = gIMMgr->computeSessionID(IM_NOTHING_SPECIAL, id); + if (session_id != LLUUID::null) + { + gIMMgr->leaveSession(session_id); + } +} + +static void on_avatar_name_cache_start_call(const LLUUID& agent_id, + const LLAvatarName& av_name) +{ + std::string name = av_name.getDisplayName(); + LLUUID session_id = gIMMgr->addSession(name, IM_NOTHING_SPECIAL, agent_id, true); + if (session_id != LLUUID::null) + { + gIMMgr->startCall(session_id); + } + make_ui_sound("UISndStartIM"); +} + +// static +void LLAvatarActions::startCall(const LLUUID& id) +{ + if (id.isNull()) + { + return; + } + LLAvatarNameCache::get(id, boost::bind(&on_avatar_name_cache_start_call, _1, _2)); +} + +// static +void LLAvatarActions::startAdhocCall(const uuid_vec_t& ids, const LLUUID& floater_id) +{ + if (ids.size() == 0) + { + return; + } + + // convert vector into std::vector for addSession + std::vector id_array; + id_array.reserve(ids.size()); + for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it) + { + id_array.push_back(*it); + } + + // create the new ad hoc voice session + const std::string title = LLTrans::getString("conference-title"); + LLUUID session_id = gIMMgr->addSession(title, IM_SESSION_CONFERENCE_START, + ids[0], id_array, true, floater_id); + if (session_id == LLUUID::null) + { + return; + } + + gIMMgr->autoStartCallOnStartup(session_id); + + make_ui_sound("UISndStartIM"); +} + +/* AD *TODO: Is this function needed any more? + I fixed it a bit(added check for canCall), but it appears that it is not used + anywhere. Maybe it should be removed? +// static +bool LLAvatarActions::isCalling(const LLUUID &id) +{ + if (id.isNull() || !canCall()) + { + return false; + } + + LLUUID session_id = gIMMgr->computeSessionID(IM_NOTHING_SPECIAL, id); + return (LLIMModel::getInstance()->findIMSession(session_id) != NULL); +}*/ + +//static +bool LLAvatarActions::canCall() +{ + return LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking(); +} + +// static +void LLAvatarActions::startConference(const uuid_vec_t& ids, const LLUUID& floater_id) +{ + // *HACK: Copy into dynamic array + std::vector id_array; + + id_array.reserve(ids.size()); + for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it) + { + id_array.push_back(*it); + } + const std::string title = LLTrans::getString("conference-title"); + LLUUID session_id = gIMMgr->addSession(title, IM_SESSION_CONFERENCE_START, ids[0], id_array, false, floater_id); + + if (session_id == LLUUID::null) + { + return; + } + + LLFloaterIMContainer::getInstance()->showConversation(session_id); + + make_ui_sound("UISndStartIM"); +} + +// static +void LLAvatarActions::showProfile(const LLUUID& avatar_id) +{ + if (avatar_id.notNull()) + { + LLFloaterReg::showInstance("profile", LLSD().with("id", avatar_id)); + } +} + +// static +void LLAvatarActions::showPicks(const LLUUID& avatar_id) +{ + if (avatar_id.notNull()) + { + LLFloaterProfile* profilefloater = dynamic_cast(LLFloaterReg::showInstance("profile", LLSD().with("id", avatar_id))); + if (profilefloater) + { + profilefloater->showPick(); + } + } +} + +// static +void LLAvatarActions::showPick(const LLUUID& avatar_id, const LLUUID& pick_id) +{ + if (avatar_id.notNull()) + { + LLFloaterProfile* profilefloater = dynamic_cast(LLFloaterReg::showInstance("profile", LLSD().with("id", avatar_id))); + if (profilefloater) + { + profilefloater->showPick(pick_id); + } + } +} + +// static +void LLAvatarActions::createPick() +{ + LLFloaterProfile* profilefloater = dynamic_cast(LLFloaterReg::showInstance("profile", LLSD().with("id", gAgent.getID()))); + LLViewerRegion* region = gAgent.getRegion(); + if (profilefloater && region) + { + LLPickData data; + data.pos_global = gAgent.getPositionGlobal(); + data.sim_name = region->getName(); + + LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if (parcel) + { + data.name = parcel->getName(); + data.desc = parcel->getDesc(); + data.snapshot_id = parcel->getSnapshotID(); + data.parcel_id = parcel->getID(); + } + else + { + data.name = region->getName(); + } + + profilefloater->createPick(data); + } +} + +// static +bool LLAvatarActions::isPickTabSelected(const LLUUID& avatar_id) +{ + if (avatar_id.notNull()) + { + LLFloaterProfile* profilefloater = LLFloaterReg::findTypedInstance("profile", LLSD().with("id", avatar_id)); + if (profilefloater) + { + return profilefloater->isPickTabSelected(); + } + } + return false; +} + +// static +void LLAvatarActions::showClassifieds(const LLUUID& avatar_id) +{ + if (avatar_id.notNull()) + { + LLFloaterProfile* profilefloater = dynamic_cast(LLFloaterReg::showInstance("profile", LLSD().with("id", avatar_id))); + if (profilefloater) + { + profilefloater->showClassified(); + } + } +} + +// static +void LLAvatarActions::showClassified(const LLUUID& avatar_id, const LLUUID& classified_id, bool edit) +{ + if (avatar_id.notNull()) + { + LLFloaterProfile* profilefloater = dynamic_cast(LLFloaterReg::showInstance("profile", LLSD().with("id", avatar_id))); + if (profilefloater) + { + profilefloater->showClassified(classified_id, edit); + } + } +} + +// static +void LLAvatarActions::createClassified() +{ + LLFloaterProfile* profilefloater = dynamic_cast(LLFloaterReg::showInstance("profile", LLSD().with("id", gAgent.getID()))); + if (profilefloater) + { + profilefloater->createClassified(); + } +} + +//static +bool LLAvatarActions::profileVisible(const LLUUID& avatar_id) +{ + LLSD sd; + sd["id"] = avatar_id; + LLFloater* floater = getProfileFloater(avatar_id); + return floater && floater->isShown(); +} + +//static +LLFloater* LLAvatarActions::getProfileFloater(const LLUUID& avatar_id) +{ + LLFloaterProfile* floater = LLFloaterReg::findTypedInstance("profile", LLSD().with("id", avatar_id)); + return floater; +} + +//static +void LLAvatarActions::hideProfile(const LLUUID& avatar_id) +{ + LLSD sd; + sd["id"] = avatar_id; + LLFloater* floater = getProfileFloater(avatar_id); + if (floater) + { + floater->closeFloater(); + } +} + +// static +void LLAvatarActions::showOnMap(const LLUUID& id) +{ + LLAvatarName av_name; + if (!LLAvatarNameCache::get(id, &av_name)) + { + LLAvatarNameCache::get(id, boost::bind(&LLAvatarActions::showOnMap, id)); + return; + } + + gFloaterWorldMap->trackAvatar(id, av_name.getDisplayName()); + LLFloaterReg::showInstance("world_map", "center"); +} + +// static +void LLAvatarActions::pay(const LLUUID& id) +{ + LLNotification::Params params("DoNotDisturbModePay"); + params.functor.function(boost::bind(&LLAvatarActions::handlePay, _1, _2, id)); + + if (gAgent.isDoNotDisturb()) + { + // warn users of being in do not disturb mode during a transaction + LLNotifications::instance().add(params); + } + else + { + LLNotifications::instance().forceResponse(params, 1); + } +} + +void LLAvatarActions::teleport_request_callback(const LLSD& notification, const LLSD& response) +{ + S32 option; + if (response.isInteger()) + { + option = response.asInteger(); + } + else + { + option = LLNotificationsUtil::getSelectedOption(notification, response); + } + + if (0 == option) + { + LLMessageSystem* msg = gMessageSystem; + + msg->newMessageFast(_PREHASH_ImprovedInstantMessage); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + + msg->nextBlockFast(_PREHASH_MessageBlock); + msg->addBOOLFast(_PREHASH_FromGroup, false); + msg->addUUIDFast(_PREHASH_ToAgentID, notification["substitutions"]["uuid"] ); + msg->addU8Fast(_PREHASH_Offline, IM_ONLINE); + msg->addU8Fast(_PREHASH_Dialog, IM_TELEPORT_REQUEST); + msg->addUUIDFast(_PREHASH_ID, LLUUID::null); + msg->addU32Fast(_PREHASH_Timestamp, NO_TIMESTAMP); // no timestamp necessary + + std::string name; + LLAgentUI::buildFullname(name); + + msg->addStringFast(_PREHASH_FromAgentName, name); + msg->addStringFast(_PREHASH_Message, response["message"]); + msg->addU32Fast(_PREHASH_ParentEstateID, 0); + msg->addUUIDFast(_PREHASH_RegionID, LLUUID::null); + msg->addVector3Fast(_PREHASH_Position, gAgent.getPositionAgent()); + + gMessageSystem->addBinaryDataFast( + _PREHASH_BinaryBucket, + EMPTY_BINARY_BUCKET, + EMPTY_BINARY_BUCKET_SIZE); + + gAgent.sendReliableMessage(); + } +} + +// static +void LLAvatarActions::teleportRequest(const LLUUID& id) +{ + LLSD notification; + notification["uuid"] = id; + LLAvatarName av_name; + if (!LLAvatarNameCache::get(id, &av_name)) + { + // unlikely ... they just picked this name from somewhere... + LLAvatarNameCache::get(id, boost::bind(&LLAvatarActions::teleportRequest, id)); + return; // reinvoke this when the name resolves + } + notification["NAME"] = av_name.getCompleteName(); + + LLSD payload; + + LLNotificationsUtil::add("TeleportRequestPrompt", notification, payload, teleport_request_callback); +} + +// static +void LLAvatarActions::kick(const LLUUID& id) +{ + LLSD payload; + payload["avatar_id"] = id; + LLNotifications::instance().add("KickUser", LLSD(), payload, handleKick); +} + +// static +void LLAvatarActions::freezeAvatar(const LLUUID& id) +{ + LLAvatarName av_name; + LLSD payload; + payload["avatar_id"] = id; + + if (LLAvatarNameCache::get(id, &av_name)) + { + LLSD args; + args["AVATAR_NAME"] = av_name.getUserName(); + LLNotificationsUtil::add("FreezeAvatarFullname", args, payload, handleFreezeAvatar); + } + else + { + LLNotificationsUtil::add("FreezeAvatar", LLSD(), payload, handleFreezeAvatar); + } +} + +// static +void LLAvatarActions::ejectAvatar(const LLUUID& id, bool ban_enabled) +{ + LLAvatarName av_name; + LLSD payload; + payload["avatar_id"] = id; + payload["ban_enabled"] = ban_enabled; + LLSD args; + bool has_name = LLAvatarNameCache::get(id, &av_name); + if (has_name) + { + args["AVATAR_NAME"] = av_name.getUserName(); + } + + if (ban_enabled) + { + LLNotificationsUtil::add("EjectAvatarFullname", args, payload, handleEjectAvatar); + } + else + { + if (has_name) + { + LLNotificationsUtil::add("EjectAvatarFullnameNoBan", args, payload, handleEjectAvatar); + } + else + { + LLNotificationsUtil::add("EjectAvatarNoBan", LLSD(), payload, handleEjectAvatar); + } + } +} + +// static +void LLAvatarActions::freeze(const LLUUID& id) +{ + LLSD payload; + payload["avatar_id"] = id; + LLNotifications::instance().add("FreezeUser", LLSD(), payload, handleFreeze); +} +// static +void LLAvatarActions::unfreeze(const LLUUID& id) +{ + LLSD payload; + payload["avatar_id"] = id; + LLNotifications::instance().add("UnFreezeUser", LLSD(), payload, handleUnfreeze); +} + +//static +void LLAvatarActions::csr(const LLUUID& id, std::string name) +{ + if (name.empty()) return; + + std::string url = "http://csr.lindenlab.com/agent/"; + + // slow and stupid, but it's late + S32 len = name.length(); + for (S32 i = 0; i < len; i++) + { + if (name[i] == ' ') + { + url += "%20"; + } + else + { + url += name[i]; + } + } + + LLWeb::loadURL(url); +} + +//static +void LLAvatarActions::share(const LLUUID& id) +{ + LLSD key; + LLFloaterSidePanelContainer::showPanel("inventory", key); + LLFloaterReg::showInstance("im_container"); + + LLUUID session_id = gIMMgr->computeSessionID(IM_NOTHING_SPECIAL,id); + + if (!gIMMgr->hasSession(session_id)) + { + startIM(id); + } + + if (gIMMgr->hasSession(session_id)) + { + // we should always get here, but check to verify anyways + LLIMModel::getInstance()->addMessage(session_id, SYSTEM_FROM, LLUUID::null, LLTrans::getString("share_alert"), false); + + LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(session_id); + if (session_floater && session_floater->isMinimized()) + { + session_floater->setMinimized(false); + } + LLFloaterIMContainer *im_container = LLFloaterReg::getTypedInstance("im_container"); + im_container->selectConversationPair(session_id, true); + } +} + +namespace action_give_inventory +{ + /** + * Returns a pointer to 'Add More' inventory panel of Edit Outfit SP. + */ + static LLInventoryPanel* get_outfit_editor_inventory_panel() + { + LLPanelOutfitEdit* panel_outfit_edit = dynamic_cast(LLFloaterSidePanelContainer::getPanel("appearance", "panel_outfit_edit")); + if (NULL == panel_outfit_edit) return NULL; + + LLInventoryPanel* inventory_panel = panel_outfit_edit->findChild("folder_view"); + return inventory_panel; + } + + /** + * @return active inventory panel, or NULL if there's no such panel + */ + static LLInventoryPanel* get_active_inventory_panel() + { + LLInventoryPanel* active_panel = LLInventoryPanel::getActiveInventoryPanel(false); + LLFloater* floater_appearance = LLFloaterReg::findInstance("appearance"); + if (!active_panel || (floater_appearance && floater_appearance->hasFocus())) + { + active_panel = get_outfit_editor_inventory_panel(); + } + + return active_panel; + } + + /** + * Checks My Inventory visibility. + */ + static bool is_give_inventory_acceptable_ids(const std::set inventory_selected_uuids) + { + if (inventory_selected_uuids.empty()) return false; // nothing selected + + bool acceptable = false; + std::set::const_iterator it = inventory_selected_uuids.begin(); + const std::set::const_iterator it_end = inventory_selected_uuids.end(); + for (; it != it_end; ++it) + { + LLViewerInventoryCategory* inv_cat = gInventory.getCategory(*it); + // any category can be offered. + if (inv_cat) + { + acceptable = true; + continue; + } + + LLViewerInventoryItem* inv_item = gInventory.getItem(*it); + // check if inventory item can be given + if (LLGiveInventory::isInventoryGiveAcceptable(inv_item)) + { + acceptable = true; + continue; + } + + // there are neither item nor category in inventory + acceptable = false; + break; + } + return acceptable; + } + + static bool is_give_inventory_acceptable(LLInventoryPanel* panel = NULL) + { + // check selection in the panel + std::set inventory_selected_uuids = LLAvatarActions::getInventorySelectedUUIDs(panel); + if (inventory_selected_uuids.empty()) + { + if(panel && panel->getRootFolder() && panel->getRootFolder()->isSingleFolderMode()) + { + inventory_selected_uuids.insert(panel->getRootFolderID()); + } + else + { + return false; // nothing selected + } + } + + return is_give_inventory_acceptable_ids(inventory_selected_uuids); + } + + static void build_items_string(const std::set& inventory_selected_uuids , std::string& items_string) + { + llassert(inventory_selected_uuids.size() > 0); + + const std::string& separator = LLTrans::getString("words_separator"); + for (std::set::const_iterator it = inventory_selected_uuids.begin(); ; ) + { + LLViewerInventoryCategory* inv_cat = gInventory.getCategory(*it); + if (NULL != inv_cat) + { + items_string = inv_cat->getName(); + break; + } + LLViewerInventoryItem* inv_item = gInventory.getItem(*it); + if (NULL != inv_item) + { + items_string.append(inv_item->getName()); + } + if(++it == inventory_selected_uuids.end()) + { + break; + } + items_string.append(separator); + } + } + + struct LLShareInfo : public LLSingleton + { + LLSINGLETON_EMPTY_CTOR(LLShareInfo); + public: + std::vector mAvatarNames; + uuid_vec_t mAvatarUuids; + }; + + static void give_inventory_cb(const LLSD& notification, const LLSD& response, std::set inventory_selected_uuids) + { + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + // if Cancel pressed + if (option == 1) + { + return; + } + + if (inventory_selected_uuids.empty()) + { + return; + } + + S32 count = LLShareInfo::instance().mAvatarNames.size(); + bool shared = count && !inventory_selected_uuids.empty(); + + // iterate through avatars + for(S32 i = 0; i < count; ++i) + { + const LLUUID& avatar_uuid = LLShareInfo::instance().mAvatarUuids[i]; + + // We souldn't open IM session, just calculate session ID for logging purpose. See EXT-6710 + const LLUUID session_id = gIMMgr->computeSessionID(IM_NOTHING_SPECIAL, avatar_uuid); + + std::set::const_iterator it = inventory_selected_uuids.begin(); + const std::set::const_iterator it_end = inventory_selected_uuids.end(); + + const std::string& separator = LLTrans::getString("words_separator"); + std::string noncopy_item_names; + LLSD noncopy_items = LLSD::emptyArray(); + // iterate through selected inventory objects + for (; it != it_end; ++it) + { + LLViewerInventoryCategory* inv_cat = gInventory.getCategory(*it); + if (inv_cat) + { + if (!LLGiveInventory::doGiveInventoryCategory(avatar_uuid, inv_cat, session_id, "ItemsShared")) + { + shared = false; + } + break; + } + LLViewerInventoryItem* inv_item = gInventory.getItem(*it); + if (!inv_item->getPermissions().allowCopyBy(gAgentID)) + { + if (!noncopy_item_names.empty()) + { + noncopy_item_names.append(separator); + } + noncopy_item_names.append(inv_item->getName()); + noncopy_items.append(*it); + } + else + { + if (!LLGiveInventory::doGiveInventoryItem(avatar_uuid, inv_item, session_id)) + { + shared = false; + } + } + } + if (noncopy_items.beginArray() != noncopy_items.endArray()) + { + LLSD substitutions; + substitutions["ITEMS"] = noncopy_item_names; + LLSD payload; + payload["agent_id"] = avatar_uuid; + payload["items"] = noncopy_items; + payload["success_notification"] = "ItemsShared"; + LLNotificationsUtil::add("CannotCopyWarning", substitutions, payload, + &LLGiveInventory::handleCopyProtectedItem); + shared = false; + break; + } + } + if (shared) + { + LLFloaterReg::hideInstance("avatar_picker"); + LLNotificationsUtil::add("ItemsShared"); + } + } + + /** + * Performs "give inventory" operations for provided avatars. + * + * Sends one requests to give all selected inventory items for each passed avatar. + * Avatars are represent by two vectors: names and UUIDs which must be sychronized with each other. + * + * @param avatar_names - avatar names request to be sent. + * @param avatar_uuids - avatar names request to be sent. + */ + + static void give_inventory_ids(const uuid_vec_t& avatar_uuids, const std::vector avatar_names, const uuid_set_t inventory_selected_uuids) + { + llassert(avatar_names.size() == avatar_uuids.size()); + + if (inventory_selected_uuids.empty()) + { + return; + } + + std::string residents; + LLAvatarActions::buildResidentsString(avatar_names, residents, true); + + std::string items; + build_items_string(inventory_selected_uuids, items); + + int folders_count = 0; + std::set::const_iterator it = inventory_selected_uuids.begin(); + + //traverse through selected inventory items and count folders among them + for ( ; it != inventory_selected_uuids.end() && folders_count <=1 ; ++it) + { + LLViewerInventoryCategory* inv_cat = gInventory.getCategory(*it); + if (NULL != inv_cat) + { + folders_count++; + } + } + + // EXP-1599 + // In case of sharing multiple folders, make the confirmation + // dialog contain a warning that only one folder can be shared at a time. + std::string notification = (folders_count > 1) ? "ShareFolderConfirmation" : "ShareItemsConfirmation"; + LLSD substitutions; + substitutions["RESIDENTS"] = residents; + substitutions["ITEMS"] = items; + LLShareInfo::instance().mAvatarNames = avatar_names; + LLShareInfo::instance().mAvatarUuids = avatar_uuids; + LLNotificationsUtil::add(notification, substitutions, LLSD(), boost::bind(&give_inventory_cb, _1, _2, inventory_selected_uuids)); + } + + static void give_inventory(const uuid_vec_t& avatar_uuids, const std::vector avatar_names, LLInventoryPanel* panel = NULL) + { + llassert(avatar_names.size() == avatar_uuids.size()); + std::set inventory_selected_uuids = LLAvatarActions::getInventorySelectedUUIDs(panel);; + + if (inventory_selected_uuids.empty()) + { + if(panel && panel->getRootFolder() && panel->getRootFolder()->isSingleFolderMode()) + { + inventory_selected_uuids.insert(panel->getRootFolderID()); + } + else + { + return; + } + } + give_inventory_ids(avatar_uuids, avatar_names, inventory_selected_uuids); + } +} + +// static +void LLAvatarActions::buildResidentsString(std::vector avatar_names, std::string& residents_string, bool complete_name) +{ + llassert(avatar_names.size() > 0); + + std::sort(avatar_names.begin(), avatar_names.end()); + const std::string& separator = LLTrans::getString("words_separator"); + for (std::vector::const_iterator it = avatar_names.begin(); ; ) + { + if(complete_name) + { + residents_string.append((*it).getCompleteName()); + } + else + { + residents_string.append((*it).getDisplayName()); + } + + if (++it == avatar_names.end()) + { + break; + } + residents_string.append(separator); + } +} + +// static +void LLAvatarActions::buildResidentsString(const uuid_vec_t& avatar_uuids, std::string& residents_string) +{ + std::vector avatar_names; + uuid_vec_t::const_iterator it = avatar_uuids.begin(); + for (; it != avatar_uuids.end(); ++it) + { + LLAvatarName av_name; + if (LLAvatarNameCache::get(*it, &av_name)) + { + avatar_names.push_back(av_name); + } + } + + // We should check whether the vector is not empty to pass the assertion + // that avatar_names.size() > 0 in LLAvatarActions::buildResidentsString. + if (!avatar_names.empty()) + { + LLAvatarActions::buildResidentsString(avatar_names, residents_string); + } +} + +//static +std::set LLAvatarActions::getInventorySelectedUUIDs(LLInventoryPanel* active_panel) +{ + std::set inventory_selected; + + if (!active_panel) + { + active_panel = action_give_inventory::get_active_inventory_panel(); + } + if (active_panel) + { + inventory_selected= active_panel->getRootFolder()->getSelectionList(); + } + + if (inventory_selected.empty()) + { + LLSidepanelInventory *sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); + if (sidepanel_inventory) + { + inventory_selected= sidepanel_inventory->getInboxSelectionList(); + } + } + + std::set inventory_selected_uuids; + for (std::set::iterator it = inventory_selected.begin(), end_it = inventory_selected.end(); + it != end_it; + ++it) + { + inventory_selected_uuids.insert(static_cast((*it)->getViewModelItem())->getUUID()); + } + return inventory_selected_uuids; +} + +//static +void LLAvatarActions::shareWithAvatars(LLView * panel) +{ + using namespace action_give_inventory; + + LLFloater* root_floater = gFloaterView->getParentFloater(panel); + LLInventoryPanel* inv_panel = dynamic_cast(panel); + LLFloaterAvatarPicker* picker = + LLFloaterAvatarPicker::show(boost::bind(give_inventory, _1, _2, inv_panel), true, false, false, root_floater->getName()); + if (!picker) + { + return; + } + + picker->setOkBtnEnableCb(boost::bind(is_give_inventory_acceptable, inv_panel)); + picker->openFriendsTab(); + + if (root_floater) + { + root_floater->addDependentFloater(picker); + } + LLNotificationsUtil::add("ShareNotification"); +} + +//static +void LLAvatarActions::shareWithAvatars(const uuid_set_t inventory_selected_uuids, LLFloater* root_floater) +{ + using namespace action_give_inventory; + + LLFloaterAvatarPicker* picker = + LLFloaterAvatarPicker::show(boost::bind(give_inventory_ids, _1, _2, inventory_selected_uuids), true, false, false, root_floater->getName()); + if (!picker) + { + return; + } + + picker->setOkBtnEnableCb(boost::bind(is_give_inventory_acceptable_ids, inventory_selected_uuids)); + picker->openFriendsTab(); + + if (root_floater) + { + root_floater->addDependentFloater(picker); + } + LLNotificationsUtil::add("ShareNotification"); +} + +// static +bool LLAvatarActions::canShareSelectedItems(LLInventoryPanel* inv_panel /* = NULL*/) +{ + using namespace action_give_inventory; + + if (!inv_panel) + { + LLInventoryPanel* active_panel = get_active_inventory_panel(); + if (!active_panel) return false; + inv_panel = active_panel; + } + + // check selection in the panel + LLFolderView* root_folder = inv_panel->getRootFolder(); + if (!root_folder) + { + return false; + } + const std::set inventory_selected = root_folder->getSelectionList(); + if (inventory_selected.empty()) return false; // nothing selected + + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + bool can_share = true; + std::set::const_iterator it = inventory_selected.begin(); + const std::set::const_iterator it_end = inventory_selected.end(); + for (; it != it_end; ++it) + { + LLUUID cat_id = static_cast((*it)->getViewModelItem())->getUUID(); + LLViewerInventoryCategory* inv_cat = gInventory.getCategory(cat_id); + // any category can be offered if it's not in trash. + if (inv_cat) + { + if ((cat_id == trash_id) || gInventory.isObjectDescendentOf(cat_id, trash_id)) + { + can_share = false; + break; + } + continue; + } + + // check if inventory item can be given + LLFolderViewItem* item = *it; + if (!item) return false; + LLInvFVBridge* bridge = dynamic_cast(item->getViewModelItem()); + if (bridge && bridge->canShare()) + { + continue; + } + + // there are neither item nor category in inventory + can_share = false; + break; + } + + return can_share; +} + +// static +bool LLAvatarActions::toggleBlock(const LLUUID& id) +{ + LLAvatarName av_name; + LLAvatarNameCache::get(id, &av_name); + + LLMute mute(id, av_name.getUserName(), LLMute::AGENT); + + if (LLMuteList::getInstance()->isMuted(mute.mID, mute.mName)) + { + LLMuteList::getInstance()->remove(mute); + return false; + } + else + { + LLMuteList::getInstance()->add(mute); + return true; + } +} + +// static +void LLAvatarActions::toggleMute(const LLUUID& id, U32 flags) +{ + LLAvatarName av_name; + LLAvatarNameCache::get(id, &av_name); + + LLMuteList* mute_list = LLMuteList::getInstance(); + bool is_muted = mute_list->isMuted(id, flags); + + LLMute mute(id, av_name.getUserName(), LLMute::AGENT); + if (!is_muted) + { + mute_list->add(mute, flags); + } + else + { + mute_list->remove(mute, flags); + } +} + +// static +void LLAvatarActions::toggleMuteVoice(const LLUUID& id) +{ + toggleMute(id, LLMute::flagVoiceChat); +} + +// static +bool LLAvatarActions::canOfferTeleport(const LLUUID& id) +{ + // First use LLAvatarTracker::isBuddy() + // If LLAvatarTracker::instance().isBuddyOnline function only is used + // then for avatars that are online and not a friend it will return false. + // But we should give an ability to offer a teleport for such avatars. + if(LLAvatarTracker::instance().isBuddy(id)) + { + return LLAvatarTracker::instance().isBuddyOnline(id); + } + + return true; +} + +// static +bool LLAvatarActions::canOfferTeleport(const uuid_vec_t& ids) +{ + // We can't send more than 250 lures in a single message, so disable this + // button when there are too many id's selected. + if(ids.size() > 250) return false; + + bool result = true; + for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it) + { + if(!canOfferTeleport(*it)) + { + result = false; + break; + } + } + return result; +} + +void LLAvatarActions::inviteToGroup(const LLUUID& id) +{ + LLFloaterGroupPicker* widget = LLFloaterReg::showTypedInstance("group_picker", LLSD(id)); + if (widget) + { + widget->center(); + widget->setPowersMask(GP_MEMBER_INVITE); + widget->removeNoneOption(); + widget->setSelectGroupCallback(boost::bind(callback_invite_to_group, _1, id)); + } +} + +// static +void LLAvatarActions::viewChatHistory(const LLUUID& id) +{ + const std::vector& conversations = LLConversationLog::instance().getConversations(); + std::vector::const_iterator iter = conversations.begin(); + + for (; iter != conversations.end(); ++iter) + { + if (iter->getParticipantID() == id) + { + LLFloaterReg::showInstance("preview_conversation", iter->getSessionID(), true); + return; + } + } + + if (LLLogChat::isTranscriptExist(id)) + { + LLAvatarName avatar_name; + LLSD extended_id(id); + + LLAvatarNameCache::get(id, &avatar_name); + extended_id[LL_FCP_COMPLETE_NAME] = avatar_name.getCompleteName(); + extended_id[LL_FCP_ACCOUNT_NAME] = avatar_name.getAccountName(); + LLFloaterReg::showInstance("preview_conversation", extended_id, true); + } +} + +//== private methods ======================================================================================== + +// static +bool LLAvatarActions::handleRemove(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + + const LLSD& ids = notification["payload"]["ids"]; + for (LLSD::array_const_iterator itr = ids.beginArray(); itr != ids.endArray(); ++itr) + { + LLUUID id = itr->asUUID(); + const LLRelationship* ip = LLAvatarTracker::instance().getBuddyInfo(id); + if (ip) + { + switch (option) + { + case 0: // YES + if( ip->isRightGrantedTo(LLRelationship::GRANT_MODIFY_OBJECTS)) + { + LLAvatarTracker::instance().empower(id, false); + LLAvatarTracker::instance().notifyObservers(); + } + LLAvatarTracker::instance().terminateBuddy(id); + LLAvatarTracker::instance().notifyObservers(); + break; + + case 1: // NO + default: + LL_INFOS() << "No removal performed." << LL_ENDL; + break; + } + } + } + return false; +} + +// static +bool LLAvatarActions::handlePay(const LLSD& notification, const LLSD& response, LLUUID avatar_id) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) + { + gAgent.setDoNotDisturb(false); + } + + LLFloaterPayUtil::payDirectly(&give_money, avatar_id, /*is_group=*/false); + return false; +} + +// static +void LLAvatarActions::callback_invite_to_group(LLUUID group_id, LLUUID id) +{ + uuid_vec_t agent_ids; + agent_ids.push_back(id); + + LLFloaterGroupInvite::showForGroup(group_id, &agent_ids); +} + + +// static +bool LLAvatarActions::callbackAddFriendWithMessage(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) + { + requestFriendship(notification["payload"]["id"].asUUID(), + notification["payload"]["name"].asString(), + response["message"].asString()); + } + return false; +} + +// static +bool LLAvatarActions::handleKick(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotification::getSelectedOption(notification, response); + + if (option == 0) + { + LLUUID avatar_id = notification["payload"]["avatar_id"].asUUID(); + LLMessageSystem* msg = gMessageSystem; + + msg->newMessageFast(_PREHASH_GodKickUser); + msg->nextBlockFast(_PREHASH_UserInfo); + msg->addUUIDFast(_PREHASH_GodID, gAgent.getID() ); + msg->addUUIDFast(_PREHASH_GodSessionID, gAgent.getSessionID()); + msg->addUUIDFast(_PREHASH_AgentID, avatar_id ); + msg->addU32("KickFlags", KICK_FLAGS_DEFAULT ); + msg->addStringFast(_PREHASH_Reason, response["message"].asString() ); + gAgent.sendReliableMessage(); + } + return false; +} + +bool LLAvatarActions::handleFreezeAvatar(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotification::getSelectedOption(notification, response); + + if (0 == option || 1 == option) + { + U32 flags = 0x0; + if (1 == option) + { + // unfreeze + flags |= 0x1; + } + LLUUID avatar_id = notification["payload"]["avatar_id"].asUUID(); + LLMessageSystem* msg = gMessageSystem; + + msg->newMessage("FreezeUser"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->nextBlock("Data"); + msg->addUUID("TargetID", avatar_id ); + msg->addU32("Flags", flags ); + gAgent.sendReliableMessage(); + } + return false; +} + +bool LLAvatarActions::handleEjectAvatar(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (2 == option) + { + return false; + } + LLUUID avatar_id = notification["payload"]["avatar_id"].asUUID(); + bool ban_enabled = notification["payload"]["ban_enabled"].asBoolean(); + + if (0 == option) + { + LLMessageSystem* msg = gMessageSystem; + U32 flags = 0x0; + msg->newMessage("EjectUser"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID() ); + msg->addUUID("SessionID", gAgent.getSessionID() ); + msg->nextBlock("Data"); + msg->addUUID("TargetID", avatar_id ); + msg->addU32("Flags", flags ); + gAgent.sendReliableMessage(); + } + else if (ban_enabled) + { + LLMessageSystem* msg = gMessageSystem; + + U32 flags = 0x1; + msg->newMessage("EjectUser"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID() ); + msg->addUUID("SessionID", gAgent.getSessionID() ); + msg->nextBlock("Data"); + msg->addUUID("TargetID", avatar_id ); + msg->addU32("Flags", flags ); + gAgent.sendReliableMessage(); + } + return false; +} + +bool LLAvatarActions::handleFreeze(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotification::getSelectedOption(notification, response); + if (option == 0) + { + LLUUID avatar_id = notification["payload"]["avatar_id"].asUUID(); + LLMessageSystem* msg = gMessageSystem; + + msg->newMessageFast(_PREHASH_GodKickUser); + msg->nextBlockFast(_PREHASH_UserInfo); + msg->addUUIDFast(_PREHASH_GodID, gAgent.getID() ); + msg->addUUIDFast(_PREHASH_GodSessionID, gAgent.getSessionID()); + msg->addUUIDFast(_PREHASH_AgentID, avatar_id ); + msg->addU32("KickFlags", KICK_FLAGS_FREEZE ); + msg->addStringFast(_PREHASH_Reason, response["message"].asString() ); + gAgent.sendReliableMessage(); + } + return false; +} + +bool LLAvatarActions::handleUnfreeze(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotification::getSelectedOption(notification, response); + std::string text = response["message"].asString(); + if (option == 0) + { + LLUUID avatar_id = notification["payload"]["avatar_id"].asUUID(); + LLMessageSystem* msg = gMessageSystem; + + msg->newMessageFast(_PREHASH_GodKickUser); + msg->nextBlockFast(_PREHASH_UserInfo); + msg->addUUIDFast(_PREHASH_GodID, gAgent.getID() ); + msg->addUUIDFast(_PREHASH_GodSessionID, gAgent.getSessionID()); + msg->addUUIDFast(_PREHASH_AgentID, avatar_id ); + msg->addU32("KickFlags", KICK_FLAGS_UNFREEZE ); + msg->addStringFast(_PREHASH_Reason, text ); + gAgent.sendReliableMessage(); + } + return false; +} + +// static +void LLAvatarActions::requestFriendship(const LLUUID& target_id, const std::string& target_name, const std::string& message) +{ + const LLUUID calling_card_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD); + LLUIUsage::instance().logCommand("Agent.SendFriendRequest"); + + send_improved_im(target_id, + target_name, + message, + IM_ONLINE, + IM_FRIENDSHIP_OFFERED, + calling_card_folder_id); + + LLSD args; + args["TO_NAME"] = target_name; + + LLSD payload; + payload["from_id"] = target_id; + LLNotificationsUtil::add("FriendshipOffered", args, payload); +} + +//static +bool LLAvatarActions::isFriend(const LLUUID& id) +{ + return ( NULL != LLAvatarTracker::instance().getBuddyInfo(id) ); +} + +// static +bool LLAvatarActions::isBlocked(const LLUUID& id) +{ + LLAvatarName av_name; + LLAvatarNameCache::get(id, &av_name); + return LLMuteList::getInstance()->isMuted(id, av_name.getUserName()); +} + +// static +bool LLAvatarActions::isVoiceMuted(const LLUUID& id) +{ + return LLMuteList::getInstance()->isMuted(id, LLMute::flagVoiceChat); +} + +// static +bool LLAvatarActions::canBlock(const LLUUID& id) +{ + LLAvatarName av_name; + LLAvatarNameCache::get(id, &av_name); + + std::string full_name = av_name.getUserName(); + bool is_linden = (full_name.find("Linden") != std::string::npos); + bool is_self = id == gAgentID; + return !is_self && !is_linden; +} diff --git a/indra/newview/llavatarlist.cpp b/indra/newview/llavatarlist.cpp index 1eae991870..8f858fe4e1 100644 --- a/indra/newview/llavatarlist.cpp +++ b/indra/newview/llavatarlist.cpp @@ -1,590 +1,590 @@ -/** - * @file llavatarlist.h - * @brief Generic avatar list - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llavatarlist.h" - -// common -#include "lltrans.h" -#include "llcommonutils.h" - -// llui -#include "lltextutil.h" - -// newview -#include "llagentdata.h" // for comparator -#include "llavatariconctrl.h" -#include "llavatarnamecache.h" -#include "llcallingcard.h" // for LLAvatarTracker -#include "llcachename.h" -#include "lllistcontextmenu.h" -#include "llrecentpeople.h" -#include "lluuid.h" -#include "llvoiceclient.h" -#include "llviewercontrol.h" // for gSavedSettings -#include "lltooldraganddrop.h" - -static LLDefaultChildRegistry::Register r("avatar_list"); - -// Last interaction time update period. -static const F32 LIT_UPDATE_PERIOD = 5; - -// Maximum number of avatars that can be added to a list in one pass. -// Used to limit time spent for avatar list update per frame. -static const unsigned ADD_LIMIT = 50; - -bool LLAvatarList::contains(const LLUUID& id) -{ - const uuid_vec_t& ids = getIDs(); - return std::find(ids.begin(), ids.end(), id) != ids.end(); -} - -void LLAvatarList::toggleIcons() -{ - // Save the new value for new items to use. - mShowIcons = !mShowIcons; - gSavedSettings.setBOOL(mIconParamName, mShowIcons); - - // Show/hide icons for all existing items. - std::vector items; - getItems(items); - for( std::vector::const_iterator it = items.begin(); it != items.end(); it++) - { - static_cast(*it)->setAvatarIconVisible(mShowIcons); - } -} - -void LLAvatarList::setSpeakingIndicatorsVisible(bool visible) -{ - // Save the new value for new items to use. - mShowSpeakingIndicator = visible; - - // Show/hide icons for all existing items. - std::vector items; - getItems(items); - for( std::vector::const_iterator it = items.begin(); it != items.end(); it++) - { - static_cast(*it)->showSpeakingIndicator(mShowSpeakingIndicator); - } -} - -void LLAvatarList::showPermissions(bool visible) -{ - // Save the value for new items to use. - mShowPermissions = visible; - - // Enable or disable showing permissions icons for all existing items. - std::vector items; - getItems(items); - for(std::vector::const_iterator it = items.begin(), end_it = items.end(); it != end_it; ++it) - { - static_cast(*it)->setShowPermissions(mShowPermissions); - } -} - -static bool findInsensitive(std::string haystack, const std::string& needle_upper) -{ - LLStringUtil::toUpper(haystack); - return haystack.find(needle_upper) != std::string::npos; -} - - -//comparators -static const LLAvatarItemNameComparator NAME_COMPARATOR; -static const LLFlatListView::ItemReverseComparator REVERSE_NAME_COMPARATOR(NAME_COMPARATOR); - -LLAvatarList::Params::Params() -: ignore_online_status("ignore_online_status", false) -, show_last_interaction_time("show_last_interaction_time", false) -, show_info_btn("show_info_btn", true) -, show_profile_btn("show_profile_btn", true) -, show_speaking_indicator("show_speaking_indicator", true) -, show_permissions_granted("show_permissions_granted", false) -{ -} - -LLAvatarList::LLAvatarList(const Params& p) -: LLFlatListViewEx(p) -, mIgnoreOnlineStatus(p.ignore_online_status) -, mShowLastInteractionTime(p.show_last_interaction_time) -, mContextMenu(NULL) -, mDirty(true) // to force initial update -, mNeedUpdateNames(false) -, mLITUpdateTimer(NULL) -, mShowIcons(true) -, mShowInfoBtn(p.show_info_btn) -, mShowProfileBtn(p.show_profile_btn) -, mShowSpeakingIndicator(p.show_speaking_indicator) -, mShowPermissions(p.show_permissions_granted) -, mShowCompleteName(false) -{ - setCommitOnSelectionChange(true); - - // Set default sort order. - setComparator(&NAME_COMPARATOR); - - if (mShowLastInteractionTime) - { - mLITUpdateTimer = new LLTimer(); - mLITUpdateTimer->setTimerExpirySec(0); // zero to force initial update - mLITUpdateTimer->start(); - } - - LLAvatarNameCache::getInstance()->addUseDisplayNamesCallback(boost::bind(&LLAvatarList::handleDisplayNamesOptionChanged, this)); -} - - -void LLAvatarList::handleDisplayNamesOptionChanged() -{ - mNeedUpdateNames = true; -} - - -LLAvatarList::~LLAvatarList() -{ - delete mLITUpdateTimer; -} - -void LLAvatarList::setShowIcons(std::string param_name) -{ - mIconParamName= param_name; - mShowIcons = gSavedSettings.getBOOL(mIconParamName); -} - -std::string LLAvatarList::getAvatarName(LLAvatarName av_name) -{ - return mShowCompleteName? av_name.getCompleteName(false) : av_name.getDisplayName(); -} - -// virtual -void LLAvatarList::draw() -{ - // *NOTE dzaporozhan - // Call refresh() after draw() to avoid flickering of avatar list items. - - LLFlatListViewEx::draw(); - - if (mNeedUpdateNames) - { - updateAvatarNames(); - } - - if (mDirty) - refresh(); - - if (mShowLastInteractionTime && mLITUpdateTimer->hasExpired()) - { - updateLastInteractionTimes(); - mLITUpdateTimer->setTimerExpirySec(LIT_UPDATE_PERIOD); // restart the timer - } -} - -//virtual -void LLAvatarList::clear() -{ - getIDs().clear(); - setDirty(true); - LLFlatListViewEx::clear(); -} - -void LLAvatarList::setNameFilter(const std::string& filter) -{ - std::string filter_upper = filter; - LLStringUtil::toUpper(filter_upper); - if (mNameFilter != filter_upper) - { - mNameFilter = filter_upper; - - // update message for empty state here instead of refresh() to avoid blinking when switch - // between tabs. - updateNoItemsMessage(filter); - setDirty(); - } -} - -void LLAvatarList::sortByName() -{ - setComparator(&NAME_COMPARATOR); - sort(); -} - -void LLAvatarList::setDirty(bool val /*= true*/, bool force_refresh /*= false*/) -{ - mDirty = val; - if(mDirty && force_refresh) - { - refresh(); - } -} - -////////////////////////////////////////////////////////////////////////// -// PROTECTED SECTION -////////////////////////////////////////////////////////////////////////// -void LLAvatarList::refresh() -{ - bool have_names = true; - bool add_limit_exceeded = false; - bool modified = false; - bool have_filter = !mNameFilter.empty(); - - // Save selection. - uuid_vec_t selected_ids; - getSelectedUUIDs(selected_ids); - LLUUID current_id = getSelectedUUID(); - - // Determine what to add and what to remove. - uuid_vec_t added, removed; - LLAvatarList::computeDifference(getIDs(), added, removed); - - // Handle added items. - unsigned nadded = 0; - const std::string waiting_str = LLTrans::getString("AvatarNameWaiting"); - - for (uuid_vec_t::const_iterator it=added.begin(); it != added.end(); it++) - { - const LLUUID& buddy_id = *it; - LLAvatarName av_name; - have_names &= LLAvatarNameCache::get(buddy_id, &av_name); - - if (!have_filter || findInsensitive(getAvatarName(av_name), mNameFilter)) - { - if (nadded >= ADD_LIMIT) - { - add_limit_exceeded = true; - break; - } - else - { - // *NOTE: If you change the UI to show a different string, - // be sure to change the filter code below. - std::string display_name = getAvatarName(av_name); - addNewItem(buddy_id, - display_name.empty() ? waiting_str : display_name, - LLAvatarTracker::instance().isBuddyOnline(buddy_id)); - - modified = true; - nadded++; - } - } - } - - // Handle removed items. - for (uuid_vec_t::const_iterator it=removed.begin(); it != removed.end(); it++) - { - removeItemByUUID(*it); - modified = true; - } - - // Handle filter. - if (have_filter) - { - std::vector cur_values; - getValues(cur_values); - - for (std::vector::const_iterator it=cur_values.begin(); it != cur_values.end(); it++) - { - const LLUUID& buddy_id = it->asUUID(); - LLAvatarName av_name; - have_names &= LLAvatarNameCache::get(buddy_id, &av_name); - if (!findInsensitive(getAvatarName(av_name), mNameFilter)) - { - removeItemByUUID(buddy_id); - modified = true; - } - } - } - - // Changed item in place, need to request sort and update columns - // because we might have changed data in a column on which the user - // has already sorted. JC - sort(); - - // re-select items - // selectMultiple(selected_ids); // TODO: implement in LLFlatListView if need - selectItemByUUID(current_id); - - // If the name filter is specified and the names are incomplete, - // we need to re-update when the names are complete so that - // the filter can be applied correctly. - // - // Otherwise, if we have no filter then no need to update again - // because the items will update their names. - bool dirty = add_limit_exceeded || (have_filter && !have_names); - setDirty(dirty); - - // Refreshed all items. - if(!dirty) - { - // Highlight items matching the filter. - std::vector items; - getItems(items); - for( std::vector::const_iterator it = items.begin(); it != items.end(); it++) - { - static_cast(*it)->setHighlight(mNameFilter); - } - - // Send refresh_complete signal. - mRefreshCompleteSignal(this, LLSD((S32)size(false))); - } - - // Commit if we've added/removed items. - if (modified) - onCommit(); -} - -void LLAvatarList::updateAvatarNames() -{ - std::vector items; - getItems(items); - - for( std::vector::const_iterator it = items.begin(); it != items.end(); it++) - { - LLAvatarListItem* item = static_cast(*it); - item->setShowCompleteName(mShowCompleteName); - item->updateAvatarName(); - } - mNeedUpdateNames = false; -} - - -bool LLAvatarList::filterHasMatches() -{ - uuid_vec_t values = getIDs(); - - for (uuid_vec_t::const_iterator it=values.begin(); it != values.end(); it++) - { - const LLUUID& buddy_id = *it; - LLAvatarName av_name; - bool have_name = LLAvatarNameCache::get(buddy_id, &av_name); - - // If name has not been loaded yet we consider it as a match. - // When the name will be loaded the filter will be applied again(in refresh()). - - if (have_name && !findInsensitive(getAvatarName(av_name), mNameFilter)) - { - continue; - } - - return true; - } - return false; -} - -boost::signals2::connection LLAvatarList::setRefreshCompleteCallback(const commit_signal_t::slot_type& cb) -{ - return mRefreshCompleteSignal.connect(cb); -} - -boost::signals2::connection LLAvatarList::setItemDoubleClickCallback(const mouse_signal_t::slot_type& cb) -{ - return mItemDoubleClickSignal.connect(cb); -} - -//virtual -S32 LLAvatarList::notifyParent(const LLSD& info) -{ - if (info.has("sort") && &NAME_COMPARATOR == mItemComparator) - { - sort(); - return 1; - } - return LLFlatListViewEx::notifyParent(info); -} - -void LLAvatarList::addNewItem(const LLUUID& id, const std::string& name, bool is_online, EAddPosition pos) -{ - LLAvatarListItem* item = new LLAvatarListItem(); - item->setShowCompleteName(mShowCompleteName); - // This sets the name as a side effect - item->setAvatarId(id, mSessionID, mIgnoreOnlineStatus); - item->setOnline(mIgnoreOnlineStatus ? true : is_online); - item->showLastInteractionTime(mShowLastInteractionTime); - - item->setAvatarIconVisible(mShowIcons); - item->setShowInfoBtn(mShowInfoBtn); - item->setShowProfileBtn(mShowProfileBtn); - item->showSpeakingIndicator(mShowSpeakingIndicator); - item->setShowPermissions(mShowPermissions); - - - item->setDoubleClickCallback(boost::bind(&LLAvatarList::onItemDoubleClicked, this, _1, _2, _3, _4)); - - addItem(item, id, pos); -} - -// virtual -bool LLAvatarList::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = LLUICtrl::handleRightMouseDown(x, y, mask); - if ( mContextMenu) - { - uuid_vec_t selected_uuids; - getSelectedUUIDs(selected_uuids); - mContextMenu->show(this, selected_uuids, x, y); - } - return handled; -} - -bool LLAvatarList::handleMouseDown(S32 x, S32 y, MASK mask) -{ - gFocusMgr.setMouseCapture(this); - - S32 screen_x; - S32 screen_y; - localPointToScreen(x, y, &screen_x, &screen_y); - LLToolDragAndDrop::getInstance()->setDragStart(screen_x, screen_y); - - return LLFlatListViewEx::handleMouseDown(x, y, mask); -} - -bool LLAvatarList::handleMouseUp( S32 x, S32 y, MASK mask ) -{ - if(hasMouseCapture()) - { - gFocusMgr.setMouseCapture(NULL); - } - - return LLFlatListViewEx::handleMouseUp(x, y, mask); -} - -bool LLAvatarList::handleHover(S32 x, S32 y, MASK mask) -{ - bool handled = hasMouseCapture(); - if(handled) - { - S32 screen_x; - S32 screen_y; - localPointToScreen(x, y, &screen_x, &screen_y); - - if(LLToolDragAndDrop::getInstance()->isOverThreshold(screen_x, screen_y)) - { - // First, create the global drag and drop object - std::vector types; - uuid_vec_t cargo_ids; - getSelectedUUIDs(cargo_ids); - types.resize(cargo_ids.size(), DAD_PERSON); - LLToolDragAndDrop::ESource src = LLToolDragAndDrop::SOURCE_PEOPLE; - LLToolDragAndDrop::getInstance()->beginMultiDrag(types, cargo_ids, src); - } - } - - if(!handled) - { - handled = LLFlatListViewEx::handleHover(x, y, mask); - } - - return handled; -} - -void LLAvatarList::setVisible(bool visible) -{ - if (!visible && mContextMenu ) - { - mContextMenu->hide(); - } - LLFlatListViewEx::setVisible(visible); -} - -void LLAvatarList::computeDifference( - const uuid_vec_t& vnew_unsorted, - uuid_vec_t& vadded, - uuid_vec_t& vremoved) -{ - uuid_vec_t vcur; - - // Convert LLSDs to LLUUIDs. - { - std::vector vcur_values; - getValues(vcur_values); - - for (size_t i=0; i items; - getItems(items); - - for( std::vector::const_iterator it = items.begin(); it != items.end(); it++) - { - // *TODO: error handling - LLAvatarListItem* item = static_cast(*it); - S32 secs_since = now - (S32) LLRecentPeople::instance().getDate(item->getAvatarId()).secondsSinceEpoch(); - if (secs_since >= 0) - item->setLastInteractionTime(secs_since); - } -} - -void LLAvatarList::onItemDoubleClicked(LLUICtrl* ctrl, S32 x, S32 y, MASK mask) -{ - mItemDoubleClickSignal(ctrl, x, y, mask); -} - -bool LLAvatarItemComparator::compare(const LLPanel* item1, const LLPanel* item2) const -{ - const LLAvatarListItem* avatar_item1 = dynamic_cast(item1); - const LLAvatarListItem* avatar_item2 = dynamic_cast(item2); - - if (!avatar_item1 || !avatar_item2) - { - LL_ERRS() << "item1 and item2 cannot be null" << LL_ENDL; - return true; - } - - return doCompare(avatar_item1, avatar_item2); -} - -bool LLAvatarItemNameComparator::doCompare(const LLAvatarListItem* avatar_item1, const LLAvatarListItem* avatar_item2) const -{ - std::string name1 = avatar_item1->getAvatarName(); - std::string name2 = avatar_item2->getAvatarName(); - - LLStringUtil::toUpper(name1); - LLStringUtil::toUpper(name2); - - return name1 < name2; -} -bool LLAvatarItemAgentOnTopComparator::doCompare(const LLAvatarListItem* avatar_item1, const LLAvatarListItem* avatar_item2) const -{ - //keep agent on top, if first is agent, - //then we need to return true to elevate this id, otherwise false. - if(avatar_item1->getAvatarId() == gAgentID) - { - return true; - } - else if (avatar_item2->getAvatarId() == gAgentID) - { - return false; - } - return LLAvatarItemNameComparator::doCompare(avatar_item1,avatar_item2); -} +/** + * @file llavatarlist.h + * @brief Generic avatar list + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llavatarlist.h" + +// common +#include "lltrans.h" +#include "llcommonutils.h" + +// llui +#include "lltextutil.h" + +// newview +#include "llagentdata.h" // for comparator +#include "llavatariconctrl.h" +#include "llavatarnamecache.h" +#include "llcallingcard.h" // for LLAvatarTracker +#include "llcachename.h" +#include "lllistcontextmenu.h" +#include "llrecentpeople.h" +#include "lluuid.h" +#include "llvoiceclient.h" +#include "llviewercontrol.h" // for gSavedSettings +#include "lltooldraganddrop.h" + +static LLDefaultChildRegistry::Register r("avatar_list"); + +// Last interaction time update period. +static const F32 LIT_UPDATE_PERIOD = 5; + +// Maximum number of avatars that can be added to a list in one pass. +// Used to limit time spent for avatar list update per frame. +static const unsigned ADD_LIMIT = 50; + +bool LLAvatarList::contains(const LLUUID& id) +{ + const uuid_vec_t& ids = getIDs(); + return std::find(ids.begin(), ids.end(), id) != ids.end(); +} + +void LLAvatarList::toggleIcons() +{ + // Save the new value for new items to use. + mShowIcons = !mShowIcons; + gSavedSettings.setBOOL(mIconParamName, mShowIcons); + + // Show/hide icons for all existing items. + std::vector items; + getItems(items); + for( std::vector::const_iterator it = items.begin(); it != items.end(); it++) + { + static_cast(*it)->setAvatarIconVisible(mShowIcons); + } +} + +void LLAvatarList::setSpeakingIndicatorsVisible(bool visible) +{ + // Save the new value for new items to use. + mShowSpeakingIndicator = visible; + + // Show/hide icons for all existing items. + std::vector items; + getItems(items); + for( std::vector::const_iterator it = items.begin(); it != items.end(); it++) + { + static_cast(*it)->showSpeakingIndicator(mShowSpeakingIndicator); + } +} + +void LLAvatarList::showPermissions(bool visible) +{ + // Save the value for new items to use. + mShowPermissions = visible; + + // Enable or disable showing permissions icons for all existing items. + std::vector items; + getItems(items); + for(std::vector::const_iterator it = items.begin(), end_it = items.end(); it != end_it; ++it) + { + static_cast(*it)->setShowPermissions(mShowPermissions); + } +} + +static bool findInsensitive(std::string haystack, const std::string& needle_upper) +{ + LLStringUtil::toUpper(haystack); + return haystack.find(needle_upper) != std::string::npos; +} + + +//comparators +static const LLAvatarItemNameComparator NAME_COMPARATOR; +static const LLFlatListView::ItemReverseComparator REVERSE_NAME_COMPARATOR(NAME_COMPARATOR); + +LLAvatarList::Params::Params() +: ignore_online_status("ignore_online_status", false) +, show_last_interaction_time("show_last_interaction_time", false) +, show_info_btn("show_info_btn", true) +, show_profile_btn("show_profile_btn", true) +, show_speaking_indicator("show_speaking_indicator", true) +, show_permissions_granted("show_permissions_granted", false) +{ +} + +LLAvatarList::LLAvatarList(const Params& p) +: LLFlatListViewEx(p) +, mIgnoreOnlineStatus(p.ignore_online_status) +, mShowLastInteractionTime(p.show_last_interaction_time) +, mContextMenu(NULL) +, mDirty(true) // to force initial update +, mNeedUpdateNames(false) +, mLITUpdateTimer(NULL) +, mShowIcons(true) +, mShowInfoBtn(p.show_info_btn) +, mShowProfileBtn(p.show_profile_btn) +, mShowSpeakingIndicator(p.show_speaking_indicator) +, mShowPermissions(p.show_permissions_granted) +, mShowCompleteName(false) +{ + setCommitOnSelectionChange(true); + + // Set default sort order. + setComparator(&NAME_COMPARATOR); + + if (mShowLastInteractionTime) + { + mLITUpdateTimer = new LLTimer(); + mLITUpdateTimer->setTimerExpirySec(0); // zero to force initial update + mLITUpdateTimer->start(); + } + + LLAvatarNameCache::getInstance()->addUseDisplayNamesCallback(boost::bind(&LLAvatarList::handleDisplayNamesOptionChanged, this)); +} + + +void LLAvatarList::handleDisplayNamesOptionChanged() +{ + mNeedUpdateNames = true; +} + + +LLAvatarList::~LLAvatarList() +{ + delete mLITUpdateTimer; +} + +void LLAvatarList::setShowIcons(std::string param_name) +{ + mIconParamName= param_name; + mShowIcons = gSavedSettings.getBOOL(mIconParamName); +} + +std::string LLAvatarList::getAvatarName(LLAvatarName av_name) +{ + return mShowCompleteName? av_name.getCompleteName(false) : av_name.getDisplayName(); +} + +// virtual +void LLAvatarList::draw() +{ + // *NOTE dzaporozhan + // Call refresh() after draw() to avoid flickering of avatar list items. + + LLFlatListViewEx::draw(); + + if (mNeedUpdateNames) + { + updateAvatarNames(); + } + + if (mDirty) + refresh(); + + if (mShowLastInteractionTime && mLITUpdateTimer->hasExpired()) + { + updateLastInteractionTimes(); + mLITUpdateTimer->setTimerExpirySec(LIT_UPDATE_PERIOD); // restart the timer + } +} + +//virtual +void LLAvatarList::clear() +{ + getIDs().clear(); + setDirty(true); + LLFlatListViewEx::clear(); +} + +void LLAvatarList::setNameFilter(const std::string& filter) +{ + std::string filter_upper = filter; + LLStringUtil::toUpper(filter_upper); + if (mNameFilter != filter_upper) + { + mNameFilter = filter_upper; + + // update message for empty state here instead of refresh() to avoid blinking when switch + // between tabs. + updateNoItemsMessage(filter); + setDirty(); + } +} + +void LLAvatarList::sortByName() +{ + setComparator(&NAME_COMPARATOR); + sort(); +} + +void LLAvatarList::setDirty(bool val /*= true*/, bool force_refresh /*= false*/) +{ + mDirty = val; + if(mDirty && force_refresh) + { + refresh(); + } +} + +////////////////////////////////////////////////////////////////////////// +// PROTECTED SECTION +////////////////////////////////////////////////////////////////////////// +void LLAvatarList::refresh() +{ + bool have_names = true; + bool add_limit_exceeded = false; + bool modified = false; + bool have_filter = !mNameFilter.empty(); + + // Save selection. + uuid_vec_t selected_ids; + getSelectedUUIDs(selected_ids); + LLUUID current_id = getSelectedUUID(); + + // Determine what to add and what to remove. + uuid_vec_t added, removed; + LLAvatarList::computeDifference(getIDs(), added, removed); + + // Handle added items. + unsigned nadded = 0; + const std::string waiting_str = LLTrans::getString("AvatarNameWaiting"); + + for (uuid_vec_t::const_iterator it=added.begin(); it != added.end(); it++) + { + const LLUUID& buddy_id = *it; + LLAvatarName av_name; + have_names &= LLAvatarNameCache::get(buddy_id, &av_name); + + if (!have_filter || findInsensitive(getAvatarName(av_name), mNameFilter)) + { + if (nadded >= ADD_LIMIT) + { + add_limit_exceeded = true; + break; + } + else + { + // *NOTE: If you change the UI to show a different string, + // be sure to change the filter code below. + std::string display_name = getAvatarName(av_name); + addNewItem(buddy_id, + display_name.empty() ? waiting_str : display_name, + LLAvatarTracker::instance().isBuddyOnline(buddy_id)); + + modified = true; + nadded++; + } + } + } + + // Handle removed items. + for (uuid_vec_t::const_iterator it=removed.begin(); it != removed.end(); it++) + { + removeItemByUUID(*it); + modified = true; + } + + // Handle filter. + if (have_filter) + { + std::vector cur_values; + getValues(cur_values); + + for (std::vector::const_iterator it=cur_values.begin(); it != cur_values.end(); it++) + { + const LLUUID& buddy_id = it->asUUID(); + LLAvatarName av_name; + have_names &= LLAvatarNameCache::get(buddy_id, &av_name); + if (!findInsensitive(getAvatarName(av_name), mNameFilter)) + { + removeItemByUUID(buddy_id); + modified = true; + } + } + } + + // Changed item in place, need to request sort and update columns + // because we might have changed data in a column on which the user + // has already sorted. JC + sort(); + + // re-select items + // selectMultiple(selected_ids); // TODO: implement in LLFlatListView if need + selectItemByUUID(current_id); + + // If the name filter is specified and the names are incomplete, + // we need to re-update when the names are complete so that + // the filter can be applied correctly. + // + // Otherwise, if we have no filter then no need to update again + // because the items will update their names. + bool dirty = add_limit_exceeded || (have_filter && !have_names); + setDirty(dirty); + + // Refreshed all items. + if(!dirty) + { + // Highlight items matching the filter. + std::vector items; + getItems(items); + for( std::vector::const_iterator it = items.begin(); it != items.end(); it++) + { + static_cast(*it)->setHighlight(mNameFilter); + } + + // Send refresh_complete signal. + mRefreshCompleteSignal(this, LLSD((S32)size(false))); + } + + // Commit if we've added/removed items. + if (modified) + onCommit(); +} + +void LLAvatarList::updateAvatarNames() +{ + std::vector items; + getItems(items); + + for( std::vector::const_iterator it = items.begin(); it != items.end(); it++) + { + LLAvatarListItem* item = static_cast(*it); + item->setShowCompleteName(mShowCompleteName); + item->updateAvatarName(); + } + mNeedUpdateNames = false; +} + + +bool LLAvatarList::filterHasMatches() +{ + uuid_vec_t values = getIDs(); + + for (uuid_vec_t::const_iterator it=values.begin(); it != values.end(); it++) + { + const LLUUID& buddy_id = *it; + LLAvatarName av_name; + bool have_name = LLAvatarNameCache::get(buddy_id, &av_name); + + // If name has not been loaded yet we consider it as a match. + // When the name will be loaded the filter will be applied again(in refresh()). + + if (have_name && !findInsensitive(getAvatarName(av_name), mNameFilter)) + { + continue; + } + + return true; + } + return false; +} + +boost::signals2::connection LLAvatarList::setRefreshCompleteCallback(const commit_signal_t::slot_type& cb) +{ + return mRefreshCompleteSignal.connect(cb); +} + +boost::signals2::connection LLAvatarList::setItemDoubleClickCallback(const mouse_signal_t::slot_type& cb) +{ + return mItemDoubleClickSignal.connect(cb); +} + +//virtual +S32 LLAvatarList::notifyParent(const LLSD& info) +{ + if (info.has("sort") && &NAME_COMPARATOR == mItemComparator) + { + sort(); + return 1; + } + return LLFlatListViewEx::notifyParent(info); +} + +void LLAvatarList::addNewItem(const LLUUID& id, const std::string& name, bool is_online, EAddPosition pos) +{ + LLAvatarListItem* item = new LLAvatarListItem(); + item->setShowCompleteName(mShowCompleteName); + // This sets the name as a side effect + item->setAvatarId(id, mSessionID, mIgnoreOnlineStatus); + item->setOnline(mIgnoreOnlineStatus ? true : is_online); + item->showLastInteractionTime(mShowLastInteractionTime); + + item->setAvatarIconVisible(mShowIcons); + item->setShowInfoBtn(mShowInfoBtn); + item->setShowProfileBtn(mShowProfileBtn); + item->showSpeakingIndicator(mShowSpeakingIndicator); + item->setShowPermissions(mShowPermissions); + + + item->setDoubleClickCallback(boost::bind(&LLAvatarList::onItemDoubleClicked, this, _1, _2, _3, _4)); + + addItem(item, id, pos); +} + +// virtual +bool LLAvatarList::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = LLUICtrl::handleRightMouseDown(x, y, mask); + if ( mContextMenu) + { + uuid_vec_t selected_uuids; + getSelectedUUIDs(selected_uuids); + mContextMenu->show(this, selected_uuids, x, y); + } + return handled; +} + +bool LLAvatarList::handleMouseDown(S32 x, S32 y, MASK mask) +{ + gFocusMgr.setMouseCapture(this); + + S32 screen_x; + S32 screen_y; + localPointToScreen(x, y, &screen_x, &screen_y); + LLToolDragAndDrop::getInstance()->setDragStart(screen_x, screen_y); + + return LLFlatListViewEx::handleMouseDown(x, y, mask); +} + +bool LLAvatarList::handleMouseUp( S32 x, S32 y, MASK mask ) +{ + if(hasMouseCapture()) + { + gFocusMgr.setMouseCapture(NULL); + } + + return LLFlatListViewEx::handleMouseUp(x, y, mask); +} + +bool LLAvatarList::handleHover(S32 x, S32 y, MASK mask) +{ + bool handled = hasMouseCapture(); + if(handled) + { + S32 screen_x; + S32 screen_y; + localPointToScreen(x, y, &screen_x, &screen_y); + + if(LLToolDragAndDrop::getInstance()->isOverThreshold(screen_x, screen_y)) + { + // First, create the global drag and drop object + std::vector types; + uuid_vec_t cargo_ids; + getSelectedUUIDs(cargo_ids); + types.resize(cargo_ids.size(), DAD_PERSON); + LLToolDragAndDrop::ESource src = LLToolDragAndDrop::SOURCE_PEOPLE; + LLToolDragAndDrop::getInstance()->beginMultiDrag(types, cargo_ids, src); + } + } + + if(!handled) + { + handled = LLFlatListViewEx::handleHover(x, y, mask); + } + + return handled; +} + +void LLAvatarList::setVisible(bool visible) +{ + if (!visible && mContextMenu ) + { + mContextMenu->hide(); + } + LLFlatListViewEx::setVisible(visible); +} + +void LLAvatarList::computeDifference( + const uuid_vec_t& vnew_unsorted, + uuid_vec_t& vadded, + uuid_vec_t& vremoved) +{ + uuid_vec_t vcur; + + // Convert LLSDs to LLUUIDs. + { + std::vector vcur_values; + getValues(vcur_values); + + for (size_t i=0; i items; + getItems(items); + + for( std::vector::const_iterator it = items.begin(); it != items.end(); it++) + { + // *TODO: error handling + LLAvatarListItem* item = static_cast(*it); + S32 secs_since = now - (S32) LLRecentPeople::instance().getDate(item->getAvatarId()).secondsSinceEpoch(); + if (secs_since >= 0) + item->setLastInteractionTime(secs_since); + } +} + +void LLAvatarList::onItemDoubleClicked(LLUICtrl* ctrl, S32 x, S32 y, MASK mask) +{ + mItemDoubleClickSignal(ctrl, x, y, mask); +} + +bool LLAvatarItemComparator::compare(const LLPanel* item1, const LLPanel* item2) const +{ + const LLAvatarListItem* avatar_item1 = dynamic_cast(item1); + const LLAvatarListItem* avatar_item2 = dynamic_cast(item2); + + if (!avatar_item1 || !avatar_item2) + { + LL_ERRS() << "item1 and item2 cannot be null" << LL_ENDL; + return true; + } + + return doCompare(avatar_item1, avatar_item2); +} + +bool LLAvatarItemNameComparator::doCompare(const LLAvatarListItem* avatar_item1, const LLAvatarListItem* avatar_item2) const +{ + std::string name1 = avatar_item1->getAvatarName(); + std::string name2 = avatar_item2->getAvatarName(); + + LLStringUtil::toUpper(name1); + LLStringUtil::toUpper(name2); + + return name1 < name2; +} +bool LLAvatarItemAgentOnTopComparator::doCompare(const LLAvatarListItem* avatar_item1, const LLAvatarListItem* avatar_item2) const +{ + //keep agent on top, if first is agent, + //then we need to return true to elevate this id, otherwise false. + if(avatar_item1->getAvatarId() == gAgentID) + { + return true; + } + else if (avatar_item2->getAvatarId() == gAgentID) + { + return false; + } + return LLAvatarItemNameComparator::doCompare(avatar_item1,avatar_item2); +} diff --git a/indra/newview/llavatarlist.h b/indra/newview/llavatarlist.h index bd40124f8a..af5bfefcde 100644 --- a/indra/newview/llavatarlist.h +++ b/indra/newview/llavatarlist.h @@ -1,189 +1,189 @@ -/** - * @file llavatarlist.h - * @brief Generic avatar list - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLAVATARLIST_H -#define LL_LLAVATARLIST_H - -#include "llflatlistview.h" -#include "llavatarlistitem.h" - -class LLTimer; -class LLListContextMenu; - -/** - * Generic list of avatars. - * - * Updates itself when it's dirty, using optional name filter. - * To initiate update, modify the UUID list and call setDirty(). - * - * @see getIDs() - * @see setDirty() - * @see setNameFilter() - */ -class LLAvatarList : public LLFlatListViewEx -{ - LOG_CLASS(LLAvatarList); -public: - struct Params : public LLInitParam::Block - { - Optional ignore_online_status, // show all items as online - show_last_interaction_time, // show most recent interaction time. *HACK: move this to a derived class - show_info_btn, - show_profile_btn, - show_speaking_indicator, - show_permissions_granted; - Params(); - }; - - LLAvatarList(const Params&); - virtual ~LLAvatarList(); - - virtual void draw(); // from LLView - - virtual void clear(); - - virtual void setVisible(bool visible); - - void setNameFilter(const std::string& filter); - void setDirty(bool val = true, bool force_refresh = false); - uuid_vec_t& getIDs() { return mIDs; } - bool contains(const LLUUID& id); - - void setContextMenu(LLListContextMenu* menu) { mContextMenu = menu; } - void setSessionID(const LLUUID& session_id) { mSessionID = session_id; } - const LLUUID& getSessionID() { return mSessionID; } - - void toggleIcons(); - void setSpeakingIndicatorsVisible(bool visible); - void showPermissions(bool visible); - void sortByName(); - void setShowIcons(std::string param_name); - bool getIconsVisible() const { return mShowIcons; } - const std::string getIconParamName() const{return mIconParamName;} - std::string getAvatarName(LLAvatarName av_name); - virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMouseDown( S32 x, S32 y, MASK mask ); - /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); - - // Return true if filter has at least one match. - bool filterHasMatches(); - - boost::signals2::connection setRefreshCompleteCallback(const commit_signal_t::slot_type& cb); - - boost::signals2::connection setItemDoubleClickCallback(const mouse_signal_t::slot_type& cb); - - virtual S32 notifyParent(const LLSD& info); - - void handleDisplayNamesOptionChanged(); - - void setShowCompleteName(bool show) { mShowCompleteName = show;}; - -protected: - void refresh(); - - void addNewItem(const LLUUID& id, const std::string& name, bool is_online, EAddPosition pos = ADD_BOTTOM); - void computeDifference( - const uuid_vec_t& vnew, - uuid_vec_t& vadded, - uuid_vec_t& vremoved); - void updateLastInteractionTimes(); - void rebuildNames(); - void onItemDoubleClicked(LLUICtrl* ctrl, S32 x, S32 y, MASK mask); - void updateAvatarNames(); - -private: - - bool mIgnoreOnlineStatus; - bool mShowLastInteractionTime; - bool mDirty; - bool mNeedUpdateNames; - bool mShowIcons; - bool mShowInfoBtn; - bool mShowProfileBtn; - bool mShowSpeakingIndicator; - bool mShowPermissions; - bool mShowCompleteName; - - LLTimer* mLITUpdateTimer; // last interaction time update timer - std::string mIconParamName; - std::string mNameFilter; - uuid_vec_t mIDs; - LLUUID mSessionID; - - LLListContextMenu* mContextMenu; - - commit_signal_t mRefreshCompleteSignal; - mouse_signal_t mItemDoubleClickSignal; -}; - -/** Abstract comparator for avatar items */ -class LLAvatarItemComparator : public LLFlatListView::ItemComparator -{ - LOG_CLASS(LLAvatarItemComparator); - -public: - LLAvatarItemComparator() {}; - virtual ~LLAvatarItemComparator() {}; - - virtual bool compare(const LLPanel* item1, const LLPanel* item2) const; - -protected: - - /** - * Returns true if avatar_item1 < avatar_item2, false otherwise - * Implement this method in your particular comparator. - * In Linux a compiler failed to build it using the name "compare", so it was renamed to doCompare - */ - virtual bool doCompare(const LLAvatarListItem* avatar_item1, const LLAvatarListItem* avatar_item2) const = 0; -}; - - -class LLAvatarItemNameComparator : public LLAvatarItemComparator -{ - LOG_CLASS(LLAvatarItemNameComparator); - -public: - LLAvatarItemNameComparator() {}; - virtual ~LLAvatarItemNameComparator() {}; - -protected: - virtual bool doCompare(const LLAvatarListItem* avatar_item1, const LLAvatarListItem* avatar_item2) const; -}; - -class LLAvatarItemAgentOnTopComparator : public LLAvatarItemNameComparator -{ - LOG_CLASS(LLAvatarItemAgentOnTopComparator); - -public: - LLAvatarItemAgentOnTopComparator() {}; - virtual ~LLAvatarItemAgentOnTopComparator() {}; - -protected: - virtual bool doCompare(const LLAvatarListItem* avatar_item1, const LLAvatarListItem* avatar_item2) const; -}; - -#endif // LL_LLAVATARLIST_H +/** + * @file llavatarlist.h + * @brief Generic avatar list + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLAVATARLIST_H +#define LL_LLAVATARLIST_H + +#include "llflatlistview.h" +#include "llavatarlistitem.h" + +class LLTimer; +class LLListContextMenu; + +/** + * Generic list of avatars. + * + * Updates itself when it's dirty, using optional name filter. + * To initiate update, modify the UUID list and call setDirty(). + * + * @see getIDs() + * @see setDirty() + * @see setNameFilter() + */ +class LLAvatarList : public LLFlatListViewEx +{ + LOG_CLASS(LLAvatarList); +public: + struct Params : public LLInitParam::Block + { + Optional ignore_online_status, // show all items as online + show_last_interaction_time, // show most recent interaction time. *HACK: move this to a derived class + show_info_btn, + show_profile_btn, + show_speaking_indicator, + show_permissions_granted; + Params(); + }; + + LLAvatarList(const Params&); + virtual ~LLAvatarList(); + + virtual void draw(); // from LLView + + virtual void clear(); + + virtual void setVisible(bool visible); + + void setNameFilter(const std::string& filter); + void setDirty(bool val = true, bool force_refresh = false); + uuid_vec_t& getIDs() { return mIDs; } + bool contains(const LLUUID& id); + + void setContextMenu(LLListContextMenu* menu) { mContextMenu = menu; } + void setSessionID(const LLUUID& session_id) { mSessionID = session_id; } + const LLUUID& getSessionID() { return mSessionID; } + + void toggleIcons(); + void setSpeakingIndicatorsVisible(bool visible); + void showPermissions(bool visible); + void sortByName(); + void setShowIcons(std::string param_name); + bool getIconsVisible() const { return mShowIcons; } + const std::string getIconParamName() const{return mIconParamName;} + std::string getAvatarName(LLAvatarName av_name); + virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMouseDown( S32 x, S32 y, MASK mask ); + /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); + + // Return true if filter has at least one match. + bool filterHasMatches(); + + boost::signals2::connection setRefreshCompleteCallback(const commit_signal_t::slot_type& cb); + + boost::signals2::connection setItemDoubleClickCallback(const mouse_signal_t::slot_type& cb); + + virtual S32 notifyParent(const LLSD& info); + + void handleDisplayNamesOptionChanged(); + + void setShowCompleteName(bool show) { mShowCompleteName = show;}; + +protected: + void refresh(); + + void addNewItem(const LLUUID& id, const std::string& name, bool is_online, EAddPosition pos = ADD_BOTTOM); + void computeDifference( + const uuid_vec_t& vnew, + uuid_vec_t& vadded, + uuid_vec_t& vremoved); + void updateLastInteractionTimes(); + void rebuildNames(); + void onItemDoubleClicked(LLUICtrl* ctrl, S32 x, S32 y, MASK mask); + void updateAvatarNames(); + +private: + + bool mIgnoreOnlineStatus; + bool mShowLastInteractionTime; + bool mDirty; + bool mNeedUpdateNames; + bool mShowIcons; + bool mShowInfoBtn; + bool mShowProfileBtn; + bool mShowSpeakingIndicator; + bool mShowPermissions; + bool mShowCompleteName; + + LLTimer* mLITUpdateTimer; // last interaction time update timer + std::string mIconParamName; + std::string mNameFilter; + uuid_vec_t mIDs; + LLUUID mSessionID; + + LLListContextMenu* mContextMenu; + + commit_signal_t mRefreshCompleteSignal; + mouse_signal_t mItemDoubleClickSignal; +}; + +/** Abstract comparator for avatar items */ +class LLAvatarItemComparator : public LLFlatListView::ItemComparator +{ + LOG_CLASS(LLAvatarItemComparator); + +public: + LLAvatarItemComparator() {}; + virtual ~LLAvatarItemComparator() {}; + + virtual bool compare(const LLPanel* item1, const LLPanel* item2) const; + +protected: + + /** + * Returns true if avatar_item1 < avatar_item2, false otherwise + * Implement this method in your particular comparator. + * In Linux a compiler failed to build it using the name "compare", so it was renamed to doCompare + */ + virtual bool doCompare(const LLAvatarListItem* avatar_item1, const LLAvatarListItem* avatar_item2) const = 0; +}; + + +class LLAvatarItemNameComparator : public LLAvatarItemComparator +{ + LOG_CLASS(LLAvatarItemNameComparator); + +public: + LLAvatarItemNameComparator() {}; + virtual ~LLAvatarItemNameComparator() {}; + +protected: + virtual bool doCompare(const LLAvatarListItem* avatar_item1, const LLAvatarListItem* avatar_item2) const; +}; + +class LLAvatarItemAgentOnTopComparator : public LLAvatarItemNameComparator +{ + LOG_CLASS(LLAvatarItemAgentOnTopComparator); + +public: + LLAvatarItemAgentOnTopComparator() {}; + virtual ~LLAvatarItemAgentOnTopComparator() {}; + +protected: + virtual bool doCompare(const LLAvatarListItem* avatar_item1, const LLAvatarListItem* avatar_item2) const; +}; + +#endif // LL_LLAVATARLIST_H diff --git a/indra/newview/llavatarlistitem.cpp b/indra/newview/llavatarlistitem.cpp index c434b6c82a..880910d18e 100644 --- a/indra/newview/llavatarlistitem.cpp +++ b/indra/newview/llavatarlistitem.cpp @@ -1,701 +1,701 @@ -/** - * @file llavatarlistitem.cpp - * @brief avatar list item source file - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#include "llviewerprecompiledheaders.h" - -#include - -#include "llavataractions.h" -#include "llavatarlistitem.h" - -#include "llbutton.h" -#include "llfloaterreg.h" -#include "lltextutil.h" - -#include "llagent.h" -#include "llavatarnamecache.h" -#include "llavatariconctrl.h" -#include "lloutputmonitorctrl.h" -#include "lltooldraganddrop.h" - -bool LLAvatarListItem::sStaticInitialized = false; -S32 LLAvatarListItem::sLeftPadding = 0; -S32 LLAvatarListItem::sNameRightPadding = 0; -S32 LLAvatarListItem::sChildrenWidths[LLAvatarListItem::ALIC_COUNT]; - -static LLWidgetNameRegistry::StaticRegistrar sRegisterAvatarListItemParams(&typeid(LLAvatarListItem::Params), "avatar_list_item"); - -LLAvatarListItem::Params::Params() -: default_style("default_style"), - voice_call_invited_style("voice_call_invited_style"), - voice_call_joined_style("voice_call_joined_style"), - voice_call_left_style("voice_call_left_style"), - online_style("online_style"), - offline_style("offline_style"), - name_right_pad("name_right_pad", 0) -{}; - - -LLAvatarListItem::LLAvatarListItem(bool not_from_ui_factory/* = true*/) - : LLPanel(), - LLFriendObserver(), - mAvatarIcon(NULL), - mAvatarName(NULL), - mLastInteractionTime(NULL), - mIconPermissionOnline(NULL), - mIconPermissionMap(NULL), - mIconPermissionEditMine(NULL), - mIconPermissionEditTheirs(NULL), - mSpeakingIndicator(NULL), - mInfoBtn(NULL), - mProfileBtn(NULL), - mOnlineStatus(E_UNKNOWN), - mShowInfoBtn(true), - mShowProfileBtn(true), - mShowPermissions(false), - mShowCompleteName(false), - mHovered(false), - mAvatarNameCacheConnection(), - mGreyOutUsername("") -{ - if (not_from_ui_factory) - { - buildFromFile("panel_avatar_list_item.xml"); - } - // *NOTE: mantipov: do not use any member here. They can be uninitialized here in case instance - // is created from the UICtrlFactory -} - -LLAvatarListItem::~LLAvatarListItem() -{ - if (mAvatarId.notNull()) - { - LLAvatarTracker::instance().removeParticularFriendObserver(mAvatarId, this); - } - - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } -} - -bool LLAvatarListItem::postBuild() -{ - mAvatarIcon = getChild("avatar_icon"); - mAvatarName = getChild("avatar_name"); - mLastInteractionTime = getChild("last_interaction"); - - mIconPermissionOnline = getChild("permission_online_icon"); - mIconPermissionMap = getChild("permission_map_icon"); - mIconPermissionEditMine = getChild("permission_edit_mine_icon"); - mIconPermissionEditTheirs = getChild("permission_edit_theirs_icon"); - mIconPermissionOnline->setVisible(false); - mIconPermissionMap->setVisible(false); - mIconPermissionEditMine->setVisible(false); - mIconPermissionEditTheirs->setVisible(false); - - mSpeakingIndicator = getChild("speaking_indicator"); - mSpeakingIndicator->setChannelState(LLOutputMonitorCtrl::UNDEFINED_CHANNEL); - mInfoBtn = getChild("info_btn"); - mProfileBtn = getChild("profile_btn"); - - mInfoBtn->setVisible(false); - mInfoBtn->setClickedCallback(boost::bind(&LLAvatarListItem::onInfoBtnClick, this)); - - mProfileBtn->setVisible(false); - mProfileBtn->setClickedCallback(boost::bind(&LLAvatarListItem::onProfileBtnClick, this)); - - if (!sStaticInitialized) - { - // Remember children widths including their padding from the next sibling, - // so that we can hide and show them again later. - initChildrenWidths(this); - - // Right padding between avatar name text box and nearest visible child. - sNameRightPadding = LLUICtrlFactory::getDefaultParams().name_right_pad; - - sStaticInitialized = true; - } - - return true; -} - -void LLAvatarListItem::handleVisibilityChange ( bool new_visibility ) -{ - //Adjust positions of icons (info button etc) when - //speaking indicator visibility was changed/toggled while panel was closed (not visible) - if(new_visibility && mSpeakingIndicator->getIndicatorToggled()) - { - updateChildren(); - mSpeakingIndicator->setIndicatorToggled(false); - } -} - -void LLAvatarListItem::fetchAvatarName() -{ - if (mAvatarId.notNull()) - { - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - mAvatarNameCacheConnection = LLAvatarNameCache::get(getAvatarId(), boost::bind(&LLAvatarListItem::onAvatarNameCache, this, _2)); - } -} - -S32 LLAvatarListItem::notifyParent(const LLSD& info) -{ - if (info.has("visibility_changed")) - { - updateChildren(); - return 1; - } - return LLPanel::notifyParent(info); -} - -void LLAvatarListItem::onMouseEnter(S32 x, S32 y, MASK mask) -{ - getChildView("hovered_icon")->setVisible( true); - mInfoBtn->setVisible(mShowInfoBtn); - mProfileBtn->setVisible(mShowProfileBtn); - - mHovered = true; - LLPanel::onMouseEnter(x, y, mask); - - showPermissions(mShowPermissions); - updateChildren(); -} - -void LLAvatarListItem::onMouseLeave(S32 x, S32 y, MASK mask) -{ - getChildView("hovered_icon")->setVisible( false); - mInfoBtn->setVisible(false); - mProfileBtn->setVisible(false); - - mHovered = false; - LLPanel::onMouseLeave(x, y, mask); - - showPermissions(false); - updateChildren(); -} - -// virtual, called by LLAvatarTracker -void LLAvatarListItem::changed(U32 mask) -{ - // no need to check mAvatarId for null in this case - setOnline(LLAvatarTracker::instance().isBuddyOnline(mAvatarId)); - - if (mask & LLFriendObserver::POWERS) - { - showPermissions(mShowPermissions && mHovered); - updateChildren(); - } -} - -void LLAvatarListItem::setOnline(bool online) -{ - // *FIX: setName() overrides font style set by setOnline(). Not an issue ATM. - - if (mOnlineStatus != E_UNKNOWN && (bool) mOnlineStatus == online) - return; - - mOnlineStatus = (EOnlineStatus) online; - - // Change avatar name font style depending on the new online status. - setState(online ? IS_ONLINE : IS_OFFLINE); -} - -void LLAvatarListItem::setAvatarName(const std::string& name) -{ - setNameInternal(name, mHighlihtSubstring); -} - -void LLAvatarListItem::setAvatarToolTip(const std::string& tooltip) -{ - mAvatarName->setToolTip(tooltip); -} - -void LLAvatarListItem::setHighlight(const std::string& highlight) -{ - setNameInternal(mAvatarName->getText(), mHighlihtSubstring = highlight); -} - -void LLAvatarListItem::setState(EItemState item_style) -{ - const LLAvatarListItem::Params& params = LLUICtrlFactory::getDefaultParams(); - - switch(item_style) - { - default: - case IS_DEFAULT: - mAvatarNameStyle = params.default_style(); - break; - case IS_VOICE_INVITED: - mAvatarNameStyle = params.voice_call_invited_style(); - break; - case IS_VOICE_JOINED: - mAvatarNameStyle = params.voice_call_joined_style(); - break; - case IS_VOICE_LEFT: - mAvatarNameStyle = params.voice_call_left_style(); - break; - case IS_ONLINE: - mAvatarNameStyle = params.online_style(); - break; - case IS_OFFLINE: - mAvatarNameStyle = params.offline_style(); - break; - } - - // *NOTE: You cannot set the style on a text box anymore, you must - // rebuild the text. This will cause problems if the text contains - // hyperlinks, as their styles will be wrong. - setNameInternal(mAvatarName->getText(), mHighlihtSubstring); - - icon_color_map_t& item_icon_color_map = getItemIconColorMap(); - mAvatarIcon->setColor(item_icon_color_map[item_style]); -} - -void LLAvatarListItem::setAvatarId(const LLUUID& id, const LLUUID& session_id, bool ignore_status_changes/* = false*/, bool is_resident/* = true*/) -{ - if (mAvatarId.notNull()) - LLAvatarTracker::instance().removeParticularFriendObserver(mAvatarId, this); - - mAvatarId = id; - mSpeakingIndicator->setSpeakerId(id, session_id); - - // We'll be notified on avatar online status changes - if (!ignore_status_changes && mAvatarId.notNull()) - LLAvatarTracker::instance().addParticularFriendObserver(mAvatarId, this); - - if (is_resident) - { - mAvatarIcon->setValue(id); - - // Set avatar name. - fetchAvatarName(); - } -} - -void LLAvatarListItem::showLastInteractionTime(bool show) -{ - mLastInteractionTime->setVisible(show); - updateChildren(); -} - -void LLAvatarListItem::setLastInteractionTime(U32 secs_since) -{ - mLastInteractionTime->setValue(formatSeconds(secs_since)); -} - -void LLAvatarListItem::setShowInfoBtn(bool show) -{ - mShowInfoBtn = show; -} - -void LLAvatarListItem::setShowProfileBtn(bool show) -{ - mShowProfileBtn = show; -} - -void LLAvatarListItem::showSpeakingIndicator(bool visible) -{ - // Already done? Then do nothing. - if (mSpeakingIndicator->getVisible() == (bool)visible) - return; -// Disabled to not contradict with SpeakingIndicatorManager functionality. EXT-3976 -// probably this method should be totally removed. -// mSpeakingIndicator->setVisible(visible); -// updateChildren(); -} - -void LLAvatarListItem::setAvatarIconVisible(bool visible) -{ - // Already done? Then do nothing. - if (mAvatarIcon->getVisible() == (bool)visible) - { - return; - } - - // Show/hide avatar icon. - mAvatarIcon->setVisible(visible); - updateChildren(); -} - -void LLAvatarListItem::onInfoBtnClick() -{ - LLFloaterReg::showInstance("inspect_avatar", LLSD().with("avatar_id", mAvatarId)); -} - -void LLAvatarListItem::onProfileBtnClick() -{ - LLAvatarActions::showProfile(mAvatarId); -} - -bool LLAvatarListItem::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - if(mInfoBtn->getRect().pointInRect(x, y)) - { - onInfoBtnClick(); - return true; - } - if(mProfileBtn->getRect().pointInRect(x, y)) - { - onProfileBtnClick(); - return true; - } - return LLPanel::handleDoubleClick(x, y, mask); -} - -void LLAvatarListItem::setValue( const LLSD& value ) -{ - if (!value.isMap()) return;; - if (!value.has("selected")) return; - getChildView("selected_icon")->setVisible( value["selected"]); -} - -const LLUUID& LLAvatarListItem::getAvatarId() const -{ - return mAvatarId; -} - -std::string LLAvatarListItem::getAvatarName() const -{ - return mAvatarName->getValue(); -} - -std::string LLAvatarListItem::getAvatarToolTip() const -{ - return mAvatarName->getToolTip(); -} - -void LLAvatarListItem::updateAvatarName() -{ - fetchAvatarName(); -} - -//== PRIVATE SECITON ========================================================== - -void LLAvatarListItem::setNameInternal(const std::string& name, const std::string& highlight) -{ - if(mShowCompleteName && highlight.empty()) - { - LLTextUtil::textboxSetGreyedVal(mAvatarName, mAvatarNameStyle, name, mGreyOutUsername); - } - else - { - LLTextUtil::textboxSetHighlightedVal(mAvatarName, mAvatarNameStyle, name, highlight); - } -} - -void LLAvatarListItem::onAvatarNameCache(const LLAvatarName& av_name) -{ - mAvatarNameCacheConnection.disconnect(); - - mGreyOutUsername = ""; - std::string name_string = mShowCompleteName? av_name.getCompleteName(false) : av_name.getDisplayName(); - if(av_name.getCompleteName() != av_name.getUserName()) - { - mGreyOutUsername = "[ " + av_name.getUserName(true) + " ]"; - LLStringUtil::toLower(mGreyOutUsername); - } - setAvatarName(name_string); - setAvatarToolTip(av_name.getUserName()); - - //requesting the list to resort - notifyParent(LLSD().with("sort", LLSD())); -} - -// Convert given number of seconds to a string like "23 minutes", "15 hours" or "3 years", -// taking i18n into account. The format string to use is taken from the panel XML. -std::string LLAvatarListItem::formatSeconds(U32 secs) -{ - static const U32 LL_ALI_MIN = 60; - static const U32 LL_ALI_HOUR = LL_ALI_MIN * 60; - static const U32 LL_ALI_DAY = LL_ALI_HOUR * 24; - static const U32 LL_ALI_WEEK = LL_ALI_DAY * 7; - static const U32 LL_ALI_MONTH = LL_ALI_DAY * 30; - static const U32 LL_ALI_YEAR = LL_ALI_DAY * 365; - - std::string fmt; - U32 count = 0; - - if (secs >= LL_ALI_YEAR) - { - fmt = "FormatYears"; count = secs / LL_ALI_YEAR; - } - else if (secs >= LL_ALI_MONTH) - { - fmt = "FormatMonths"; count = secs / LL_ALI_MONTH; - } - else if (secs >= LL_ALI_WEEK) - { - fmt = "FormatWeeks"; count = secs / LL_ALI_WEEK; - } - else if (secs >= LL_ALI_DAY) - { - fmt = "FormatDays"; count = secs / LL_ALI_DAY; - } - else if (secs >= LL_ALI_HOUR) - { - fmt = "FormatHours"; count = secs / LL_ALI_HOUR; - } - else if (secs >= LL_ALI_MIN) - { - fmt = "FormatMinutes"; count = secs / LL_ALI_MIN; - } - else - { - fmt = "FormatSeconds"; count = secs; - } - - LLStringUtil::format_map_t args; - args["[COUNT]"] = llformat("%u", count); - return getString(fmt, args); -} - -// static -LLAvatarListItem::icon_color_map_t& LLAvatarListItem::getItemIconColorMap() -{ - static icon_color_map_t item_icon_color_map; - if (!item_icon_color_map.empty()) return item_icon_color_map; - - item_icon_color_map.insert( - std::make_pair(IS_DEFAULT, - LLUIColorTable::instance().getColor("AvatarListItemIconDefaultColor", LLColor4::white))); - - item_icon_color_map.insert( - std::make_pair(IS_VOICE_INVITED, - LLUIColorTable::instance().getColor("AvatarListItemIconVoiceInvitedColor", LLColor4::white))); - - item_icon_color_map.insert( - std::make_pair(IS_VOICE_JOINED, - LLUIColorTable::instance().getColor("AvatarListItemIconVoiceJoinedColor", LLColor4::white))); - - item_icon_color_map.insert( - std::make_pair(IS_VOICE_LEFT, - LLUIColorTable::instance().getColor("AvatarListItemIconVoiceLeftColor", LLColor4::white))); - - item_icon_color_map.insert( - std::make_pair(IS_ONLINE, - LLUIColorTable::instance().getColor("AvatarListItemIconOnlineColor", LLColor4::white))); - - item_icon_color_map.insert( - std::make_pair(IS_OFFLINE, - LLUIColorTable::instance().getColor("AvatarListItemIconOfflineColor", LLColor4::white))); - - return item_icon_color_map; -} - -// static -void LLAvatarListItem::initChildrenWidths(LLAvatarListItem* avatar_item) -{ - //speaking indicator width + padding - S32 speaking_indicator_width = avatar_item->getRect().getWidth() - avatar_item->mSpeakingIndicator->getRect().mLeft; - - //profile btn width + padding - S32 profile_btn_width = avatar_item->mSpeakingIndicator->getRect().mLeft - avatar_item->mProfileBtn->getRect().mLeft; - - //info btn width + padding - S32 info_btn_width = avatar_item->mProfileBtn->getRect().mLeft - avatar_item->mInfoBtn->getRect().mLeft; - - // online permission icon width + padding - S32 permission_online_width = avatar_item->mInfoBtn->getRect().mLeft - avatar_item->mIconPermissionOnline->getRect().mLeft; - - // map permission icon width + padding - S32 permission_map_width = avatar_item->mIconPermissionOnline->getRect().mLeft - avatar_item->mIconPermissionMap->getRect().mLeft; - - // edit my objects permission icon width + padding - S32 permission_edit_mine_width = avatar_item->mIconPermissionMap->getRect().mLeft - avatar_item->mIconPermissionEditMine->getRect().mLeft; - - // edit their objects permission icon width + padding - S32 permission_edit_theirs_width = avatar_item->mIconPermissionEditMine->getRect().mLeft - avatar_item->mIconPermissionEditTheirs->getRect().mLeft; - - // last interaction time textbox width + padding - S32 last_interaction_time_width = avatar_item->mIconPermissionEditTheirs->getRect().mLeft - avatar_item->mLastInteractionTime->getRect().mLeft; - - // avatar icon width + padding - S32 icon_width = avatar_item->mAvatarName->getRect().mLeft - avatar_item->mAvatarIcon->getRect().mLeft; - - sLeftPadding = avatar_item->mAvatarIcon->getRect().mLeft; - - S32 index = ALIC_COUNT; - sChildrenWidths[--index] = icon_width; - sChildrenWidths[--index] = 0; // for avatar name we don't need its width, it will be calculated as "left available space" - sChildrenWidths[--index] = last_interaction_time_width; - sChildrenWidths[--index] = permission_edit_theirs_width; - sChildrenWidths[--index] = permission_edit_mine_width; - sChildrenWidths[--index] = permission_map_width; - sChildrenWidths[--index] = permission_online_width; - sChildrenWidths[--index] = info_btn_width; - sChildrenWidths[--index] = profile_btn_width; - sChildrenWidths[--index] = speaking_indicator_width; - llassert(index == 0); -} - -void LLAvatarListItem::updateChildren() -{ - LL_DEBUGS("AvatarItemReshape") << LL_ENDL; - LL_DEBUGS("AvatarItemReshape") << "Updating for: " << getAvatarName() << LL_ENDL; - - S32 name_new_width = getRect().getWidth(); - S32 ctrl_new_left = name_new_width; - S32 name_new_left = sLeftPadding; - - // iterate through all children and set them into correct position depend on each child visibility - // assume that child indexes are in back order: the first in Enum is the last (right) in the item - // iterate & set child views starting from right to left - for (S32 i = 0; i < ALIC_COUNT; ++i) - { - // skip "name" textbox, it will be processed out of loop later - if (ALIC_NAME == i) continue; - - LLView* control = getItemChildView((EAvatarListItemChildIndex)i); - - LL_DEBUGS("AvatarItemReshape") << "Processing control: " << control->getName() << LL_ENDL; - // skip invisible views - if (!control->getVisible()) continue; - - S32 ctrl_width = sChildrenWidths[i]; // including space between current & left controls - - // decrease available for - name_new_width -= ctrl_width; - LL_DEBUGS("AvatarItemReshape") << "width: " << ctrl_width << ", name_new_width: " << name_new_width << LL_ENDL; - - LLRect control_rect = control->getRect(); - LL_DEBUGS("AvatarItemReshape") << "rect before: " << control_rect << LL_ENDL; - - if (ALIC_ICON == i) - { - // assume that this is the last iteration, - // so it is not necessary to save "ctrl_new_left" value calculated on previous iterations - ctrl_new_left = sLeftPadding; - name_new_left = ctrl_new_left + ctrl_width; - } - else - { - ctrl_new_left -= ctrl_width; - } - - LL_DEBUGS("AvatarItemReshape") << "ctrl_new_left: " << ctrl_new_left << LL_ENDL; - - control_rect.setLeftTopAndSize( - ctrl_new_left, - control_rect.mTop, - control_rect.getWidth(), - control_rect.getHeight()); - - LL_DEBUGS("AvatarItemReshape") << "rect after: " << control_rect << LL_ENDL; - control->setShape(control_rect); - } - - // set size and position of the "name" child - LLView* name_view = getItemChildView(ALIC_NAME); - LLRect name_view_rect = name_view->getRect(); - LL_DEBUGS("AvatarItemReshape") << "name rect before: " << name_view_rect << LL_ENDL; - - // apply paddings - name_new_width -= sLeftPadding; - name_new_width -= sNameRightPadding; - - name_view_rect.setLeftTopAndSize( - name_new_left, - name_view_rect.mTop, - name_new_width, - name_view_rect.getHeight()); - - name_view->setShape(name_view_rect); - - LL_DEBUGS("AvatarItemReshape") << "name rect after: " << name_view_rect << LL_ENDL; -} - -bool LLAvatarListItem::showPermissions(bool visible) -{ - const LLRelationship* relation = LLAvatarTracker::instance().getBuddyInfo(getAvatarId()); - if(relation && visible) - { - mIconPermissionOnline->setVisible(relation->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS)); - mIconPermissionMap->setVisible(relation->isRightGrantedTo(LLRelationship::GRANT_MAP_LOCATION)); - mIconPermissionEditMine->setVisible(relation->isRightGrantedTo(LLRelationship::GRANT_MODIFY_OBJECTS)); - mIconPermissionEditTheirs->setVisible(relation->isRightGrantedFrom(LLRelationship::GRANT_MODIFY_OBJECTS)); - } - else - { - mIconPermissionOnline->setVisible(false); - mIconPermissionMap->setVisible(false); - mIconPermissionEditMine->setVisible(false); - mIconPermissionEditTheirs->setVisible(false); - } - - return NULL != relation; -} - -LLView* LLAvatarListItem::getItemChildView(EAvatarListItemChildIndex child_view_index) -{ - LLView* child_view = mAvatarName; - - switch (child_view_index) - { - case ALIC_ICON: - child_view = mAvatarIcon; - break; - case ALIC_NAME: - child_view = mAvatarName; - break; - case ALIC_INTERACTION_TIME: - child_view = mLastInteractionTime; - break; - case ALIC_SPEAKER_INDICATOR: - child_view = mSpeakingIndicator; - break; - case ALIC_PERMISSION_ONLINE: - child_view = mIconPermissionOnline; - break; - case ALIC_PERMISSION_MAP: - child_view = mIconPermissionMap; - break; - case ALIC_PERMISSION_EDIT_MINE: - child_view = mIconPermissionEditMine; - break; - case ALIC_PERMISSION_EDIT_THEIRS: - child_view = mIconPermissionEditTheirs; - break; - case ALIC_INFO_BUTTON: - child_view = mInfoBtn; - break; - case ALIC_PROFILE_BUTTON: - child_view = mProfileBtn; - break; - default: - LL_WARNS("AvatarItemReshape") << "Unexpected child view index is passed: " << child_view_index << LL_ENDL; - // leave child_view untouched - } - - return child_view; -} - -// EOF +/** + * @file llavatarlistitem.cpp + * @brief avatar list item source file + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#include "llviewerprecompiledheaders.h" + +#include + +#include "llavataractions.h" +#include "llavatarlistitem.h" + +#include "llbutton.h" +#include "llfloaterreg.h" +#include "lltextutil.h" + +#include "llagent.h" +#include "llavatarnamecache.h" +#include "llavatariconctrl.h" +#include "lloutputmonitorctrl.h" +#include "lltooldraganddrop.h" + +bool LLAvatarListItem::sStaticInitialized = false; +S32 LLAvatarListItem::sLeftPadding = 0; +S32 LLAvatarListItem::sNameRightPadding = 0; +S32 LLAvatarListItem::sChildrenWidths[LLAvatarListItem::ALIC_COUNT]; + +static LLWidgetNameRegistry::StaticRegistrar sRegisterAvatarListItemParams(&typeid(LLAvatarListItem::Params), "avatar_list_item"); + +LLAvatarListItem::Params::Params() +: default_style("default_style"), + voice_call_invited_style("voice_call_invited_style"), + voice_call_joined_style("voice_call_joined_style"), + voice_call_left_style("voice_call_left_style"), + online_style("online_style"), + offline_style("offline_style"), + name_right_pad("name_right_pad", 0) +{}; + + +LLAvatarListItem::LLAvatarListItem(bool not_from_ui_factory/* = true*/) + : LLPanel(), + LLFriendObserver(), + mAvatarIcon(NULL), + mAvatarName(NULL), + mLastInteractionTime(NULL), + mIconPermissionOnline(NULL), + mIconPermissionMap(NULL), + mIconPermissionEditMine(NULL), + mIconPermissionEditTheirs(NULL), + mSpeakingIndicator(NULL), + mInfoBtn(NULL), + mProfileBtn(NULL), + mOnlineStatus(E_UNKNOWN), + mShowInfoBtn(true), + mShowProfileBtn(true), + mShowPermissions(false), + mShowCompleteName(false), + mHovered(false), + mAvatarNameCacheConnection(), + mGreyOutUsername("") +{ + if (not_from_ui_factory) + { + buildFromFile("panel_avatar_list_item.xml"); + } + // *NOTE: mantipov: do not use any member here. They can be uninitialized here in case instance + // is created from the UICtrlFactory +} + +LLAvatarListItem::~LLAvatarListItem() +{ + if (mAvatarId.notNull()) + { + LLAvatarTracker::instance().removeParticularFriendObserver(mAvatarId, this); + } + + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } +} + +bool LLAvatarListItem::postBuild() +{ + mAvatarIcon = getChild("avatar_icon"); + mAvatarName = getChild("avatar_name"); + mLastInteractionTime = getChild("last_interaction"); + + mIconPermissionOnline = getChild("permission_online_icon"); + mIconPermissionMap = getChild("permission_map_icon"); + mIconPermissionEditMine = getChild("permission_edit_mine_icon"); + mIconPermissionEditTheirs = getChild("permission_edit_theirs_icon"); + mIconPermissionOnline->setVisible(false); + mIconPermissionMap->setVisible(false); + mIconPermissionEditMine->setVisible(false); + mIconPermissionEditTheirs->setVisible(false); + + mSpeakingIndicator = getChild("speaking_indicator"); + mSpeakingIndicator->setChannelState(LLOutputMonitorCtrl::UNDEFINED_CHANNEL); + mInfoBtn = getChild("info_btn"); + mProfileBtn = getChild("profile_btn"); + + mInfoBtn->setVisible(false); + mInfoBtn->setClickedCallback(boost::bind(&LLAvatarListItem::onInfoBtnClick, this)); + + mProfileBtn->setVisible(false); + mProfileBtn->setClickedCallback(boost::bind(&LLAvatarListItem::onProfileBtnClick, this)); + + if (!sStaticInitialized) + { + // Remember children widths including their padding from the next sibling, + // so that we can hide and show them again later. + initChildrenWidths(this); + + // Right padding between avatar name text box and nearest visible child. + sNameRightPadding = LLUICtrlFactory::getDefaultParams().name_right_pad; + + sStaticInitialized = true; + } + + return true; +} + +void LLAvatarListItem::handleVisibilityChange ( bool new_visibility ) +{ + //Adjust positions of icons (info button etc) when + //speaking indicator visibility was changed/toggled while panel was closed (not visible) + if(new_visibility && mSpeakingIndicator->getIndicatorToggled()) + { + updateChildren(); + mSpeakingIndicator->setIndicatorToggled(false); + } +} + +void LLAvatarListItem::fetchAvatarName() +{ + if (mAvatarId.notNull()) + { + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + mAvatarNameCacheConnection = LLAvatarNameCache::get(getAvatarId(), boost::bind(&LLAvatarListItem::onAvatarNameCache, this, _2)); + } +} + +S32 LLAvatarListItem::notifyParent(const LLSD& info) +{ + if (info.has("visibility_changed")) + { + updateChildren(); + return 1; + } + return LLPanel::notifyParent(info); +} + +void LLAvatarListItem::onMouseEnter(S32 x, S32 y, MASK mask) +{ + getChildView("hovered_icon")->setVisible( true); + mInfoBtn->setVisible(mShowInfoBtn); + mProfileBtn->setVisible(mShowProfileBtn); + + mHovered = true; + LLPanel::onMouseEnter(x, y, mask); + + showPermissions(mShowPermissions); + updateChildren(); +} + +void LLAvatarListItem::onMouseLeave(S32 x, S32 y, MASK mask) +{ + getChildView("hovered_icon")->setVisible( false); + mInfoBtn->setVisible(false); + mProfileBtn->setVisible(false); + + mHovered = false; + LLPanel::onMouseLeave(x, y, mask); + + showPermissions(false); + updateChildren(); +} + +// virtual, called by LLAvatarTracker +void LLAvatarListItem::changed(U32 mask) +{ + // no need to check mAvatarId for null in this case + setOnline(LLAvatarTracker::instance().isBuddyOnline(mAvatarId)); + + if (mask & LLFriendObserver::POWERS) + { + showPermissions(mShowPermissions && mHovered); + updateChildren(); + } +} + +void LLAvatarListItem::setOnline(bool online) +{ + // *FIX: setName() overrides font style set by setOnline(). Not an issue ATM. + + if (mOnlineStatus != E_UNKNOWN && (bool) mOnlineStatus == online) + return; + + mOnlineStatus = (EOnlineStatus) online; + + // Change avatar name font style depending on the new online status. + setState(online ? IS_ONLINE : IS_OFFLINE); +} + +void LLAvatarListItem::setAvatarName(const std::string& name) +{ + setNameInternal(name, mHighlihtSubstring); +} + +void LLAvatarListItem::setAvatarToolTip(const std::string& tooltip) +{ + mAvatarName->setToolTip(tooltip); +} + +void LLAvatarListItem::setHighlight(const std::string& highlight) +{ + setNameInternal(mAvatarName->getText(), mHighlihtSubstring = highlight); +} + +void LLAvatarListItem::setState(EItemState item_style) +{ + const LLAvatarListItem::Params& params = LLUICtrlFactory::getDefaultParams(); + + switch(item_style) + { + default: + case IS_DEFAULT: + mAvatarNameStyle = params.default_style(); + break; + case IS_VOICE_INVITED: + mAvatarNameStyle = params.voice_call_invited_style(); + break; + case IS_VOICE_JOINED: + mAvatarNameStyle = params.voice_call_joined_style(); + break; + case IS_VOICE_LEFT: + mAvatarNameStyle = params.voice_call_left_style(); + break; + case IS_ONLINE: + mAvatarNameStyle = params.online_style(); + break; + case IS_OFFLINE: + mAvatarNameStyle = params.offline_style(); + break; + } + + // *NOTE: You cannot set the style on a text box anymore, you must + // rebuild the text. This will cause problems if the text contains + // hyperlinks, as their styles will be wrong. + setNameInternal(mAvatarName->getText(), mHighlihtSubstring); + + icon_color_map_t& item_icon_color_map = getItemIconColorMap(); + mAvatarIcon->setColor(item_icon_color_map[item_style]); +} + +void LLAvatarListItem::setAvatarId(const LLUUID& id, const LLUUID& session_id, bool ignore_status_changes/* = false*/, bool is_resident/* = true*/) +{ + if (mAvatarId.notNull()) + LLAvatarTracker::instance().removeParticularFriendObserver(mAvatarId, this); + + mAvatarId = id; + mSpeakingIndicator->setSpeakerId(id, session_id); + + // We'll be notified on avatar online status changes + if (!ignore_status_changes && mAvatarId.notNull()) + LLAvatarTracker::instance().addParticularFriendObserver(mAvatarId, this); + + if (is_resident) + { + mAvatarIcon->setValue(id); + + // Set avatar name. + fetchAvatarName(); + } +} + +void LLAvatarListItem::showLastInteractionTime(bool show) +{ + mLastInteractionTime->setVisible(show); + updateChildren(); +} + +void LLAvatarListItem::setLastInteractionTime(U32 secs_since) +{ + mLastInteractionTime->setValue(formatSeconds(secs_since)); +} + +void LLAvatarListItem::setShowInfoBtn(bool show) +{ + mShowInfoBtn = show; +} + +void LLAvatarListItem::setShowProfileBtn(bool show) +{ + mShowProfileBtn = show; +} + +void LLAvatarListItem::showSpeakingIndicator(bool visible) +{ + // Already done? Then do nothing. + if (mSpeakingIndicator->getVisible() == (bool)visible) + return; +// Disabled to not contradict with SpeakingIndicatorManager functionality. EXT-3976 +// probably this method should be totally removed. +// mSpeakingIndicator->setVisible(visible); +// updateChildren(); +} + +void LLAvatarListItem::setAvatarIconVisible(bool visible) +{ + // Already done? Then do nothing. + if (mAvatarIcon->getVisible() == (bool)visible) + { + return; + } + + // Show/hide avatar icon. + mAvatarIcon->setVisible(visible); + updateChildren(); +} + +void LLAvatarListItem::onInfoBtnClick() +{ + LLFloaterReg::showInstance("inspect_avatar", LLSD().with("avatar_id", mAvatarId)); +} + +void LLAvatarListItem::onProfileBtnClick() +{ + LLAvatarActions::showProfile(mAvatarId); +} + +bool LLAvatarListItem::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + if(mInfoBtn->getRect().pointInRect(x, y)) + { + onInfoBtnClick(); + return true; + } + if(mProfileBtn->getRect().pointInRect(x, y)) + { + onProfileBtnClick(); + return true; + } + return LLPanel::handleDoubleClick(x, y, mask); +} + +void LLAvatarListItem::setValue( const LLSD& value ) +{ + if (!value.isMap()) return;; + if (!value.has("selected")) return; + getChildView("selected_icon")->setVisible( value["selected"]); +} + +const LLUUID& LLAvatarListItem::getAvatarId() const +{ + return mAvatarId; +} + +std::string LLAvatarListItem::getAvatarName() const +{ + return mAvatarName->getValue(); +} + +std::string LLAvatarListItem::getAvatarToolTip() const +{ + return mAvatarName->getToolTip(); +} + +void LLAvatarListItem::updateAvatarName() +{ + fetchAvatarName(); +} + +//== PRIVATE SECITON ========================================================== + +void LLAvatarListItem::setNameInternal(const std::string& name, const std::string& highlight) +{ + if(mShowCompleteName && highlight.empty()) + { + LLTextUtil::textboxSetGreyedVal(mAvatarName, mAvatarNameStyle, name, mGreyOutUsername); + } + else + { + LLTextUtil::textboxSetHighlightedVal(mAvatarName, mAvatarNameStyle, name, highlight); + } +} + +void LLAvatarListItem::onAvatarNameCache(const LLAvatarName& av_name) +{ + mAvatarNameCacheConnection.disconnect(); + + mGreyOutUsername = ""; + std::string name_string = mShowCompleteName? av_name.getCompleteName(false) : av_name.getDisplayName(); + if(av_name.getCompleteName() != av_name.getUserName()) + { + mGreyOutUsername = "[ " + av_name.getUserName(true) + " ]"; + LLStringUtil::toLower(mGreyOutUsername); + } + setAvatarName(name_string); + setAvatarToolTip(av_name.getUserName()); + + //requesting the list to resort + notifyParent(LLSD().with("sort", LLSD())); +} + +// Convert given number of seconds to a string like "23 minutes", "15 hours" or "3 years", +// taking i18n into account. The format string to use is taken from the panel XML. +std::string LLAvatarListItem::formatSeconds(U32 secs) +{ + static const U32 LL_ALI_MIN = 60; + static const U32 LL_ALI_HOUR = LL_ALI_MIN * 60; + static const U32 LL_ALI_DAY = LL_ALI_HOUR * 24; + static const U32 LL_ALI_WEEK = LL_ALI_DAY * 7; + static const U32 LL_ALI_MONTH = LL_ALI_DAY * 30; + static const U32 LL_ALI_YEAR = LL_ALI_DAY * 365; + + std::string fmt; + U32 count = 0; + + if (secs >= LL_ALI_YEAR) + { + fmt = "FormatYears"; count = secs / LL_ALI_YEAR; + } + else if (secs >= LL_ALI_MONTH) + { + fmt = "FormatMonths"; count = secs / LL_ALI_MONTH; + } + else if (secs >= LL_ALI_WEEK) + { + fmt = "FormatWeeks"; count = secs / LL_ALI_WEEK; + } + else if (secs >= LL_ALI_DAY) + { + fmt = "FormatDays"; count = secs / LL_ALI_DAY; + } + else if (secs >= LL_ALI_HOUR) + { + fmt = "FormatHours"; count = secs / LL_ALI_HOUR; + } + else if (secs >= LL_ALI_MIN) + { + fmt = "FormatMinutes"; count = secs / LL_ALI_MIN; + } + else + { + fmt = "FormatSeconds"; count = secs; + } + + LLStringUtil::format_map_t args; + args["[COUNT]"] = llformat("%u", count); + return getString(fmt, args); +} + +// static +LLAvatarListItem::icon_color_map_t& LLAvatarListItem::getItemIconColorMap() +{ + static icon_color_map_t item_icon_color_map; + if (!item_icon_color_map.empty()) return item_icon_color_map; + + item_icon_color_map.insert( + std::make_pair(IS_DEFAULT, + LLUIColorTable::instance().getColor("AvatarListItemIconDefaultColor", LLColor4::white))); + + item_icon_color_map.insert( + std::make_pair(IS_VOICE_INVITED, + LLUIColorTable::instance().getColor("AvatarListItemIconVoiceInvitedColor", LLColor4::white))); + + item_icon_color_map.insert( + std::make_pair(IS_VOICE_JOINED, + LLUIColorTable::instance().getColor("AvatarListItemIconVoiceJoinedColor", LLColor4::white))); + + item_icon_color_map.insert( + std::make_pair(IS_VOICE_LEFT, + LLUIColorTable::instance().getColor("AvatarListItemIconVoiceLeftColor", LLColor4::white))); + + item_icon_color_map.insert( + std::make_pair(IS_ONLINE, + LLUIColorTable::instance().getColor("AvatarListItemIconOnlineColor", LLColor4::white))); + + item_icon_color_map.insert( + std::make_pair(IS_OFFLINE, + LLUIColorTable::instance().getColor("AvatarListItemIconOfflineColor", LLColor4::white))); + + return item_icon_color_map; +} + +// static +void LLAvatarListItem::initChildrenWidths(LLAvatarListItem* avatar_item) +{ + //speaking indicator width + padding + S32 speaking_indicator_width = avatar_item->getRect().getWidth() - avatar_item->mSpeakingIndicator->getRect().mLeft; + + //profile btn width + padding + S32 profile_btn_width = avatar_item->mSpeakingIndicator->getRect().mLeft - avatar_item->mProfileBtn->getRect().mLeft; + + //info btn width + padding + S32 info_btn_width = avatar_item->mProfileBtn->getRect().mLeft - avatar_item->mInfoBtn->getRect().mLeft; + + // online permission icon width + padding + S32 permission_online_width = avatar_item->mInfoBtn->getRect().mLeft - avatar_item->mIconPermissionOnline->getRect().mLeft; + + // map permission icon width + padding + S32 permission_map_width = avatar_item->mIconPermissionOnline->getRect().mLeft - avatar_item->mIconPermissionMap->getRect().mLeft; + + // edit my objects permission icon width + padding + S32 permission_edit_mine_width = avatar_item->mIconPermissionMap->getRect().mLeft - avatar_item->mIconPermissionEditMine->getRect().mLeft; + + // edit their objects permission icon width + padding + S32 permission_edit_theirs_width = avatar_item->mIconPermissionEditMine->getRect().mLeft - avatar_item->mIconPermissionEditTheirs->getRect().mLeft; + + // last interaction time textbox width + padding + S32 last_interaction_time_width = avatar_item->mIconPermissionEditTheirs->getRect().mLeft - avatar_item->mLastInteractionTime->getRect().mLeft; + + // avatar icon width + padding + S32 icon_width = avatar_item->mAvatarName->getRect().mLeft - avatar_item->mAvatarIcon->getRect().mLeft; + + sLeftPadding = avatar_item->mAvatarIcon->getRect().mLeft; + + S32 index = ALIC_COUNT; + sChildrenWidths[--index] = icon_width; + sChildrenWidths[--index] = 0; // for avatar name we don't need its width, it will be calculated as "left available space" + sChildrenWidths[--index] = last_interaction_time_width; + sChildrenWidths[--index] = permission_edit_theirs_width; + sChildrenWidths[--index] = permission_edit_mine_width; + sChildrenWidths[--index] = permission_map_width; + sChildrenWidths[--index] = permission_online_width; + sChildrenWidths[--index] = info_btn_width; + sChildrenWidths[--index] = profile_btn_width; + sChildrenWidths[--index] = speaking_indicator_width; + llassert(index == 0); +} + +void LLAvatarListItem::updateChildren() +{ + LL_DEBUGS("AvatarItemReshape") << LL_ENDL; + LL_DEBUGS("AvatarItemReshape") << "Updating for: " << getAvatarName() << LL_ENDL; + + S32 name_new_width = getRect().getWidth(); + S32 ctrl_new_left = name_new_width; + S32 name_new_left = sLeftPadding; + + // iterate through all children and set them into correct position depend on each child visibility + // assume that child indexes are in back order: the first in Enum is the last (right) in the item + // iterate & set child views starting from right to left + for (S32 i = 0; i < ALIC_COUNT; ++i) + { + // skip "name" textbox, it will be processed out of loop later + if (ALIC_NAME == i) continue; + + LLView* control = getItemChildView((EAvatarListItemChildIndex)i); + + LL_DEBUGS("AvatarItemReshape") << "Processing control: " << control->getName() << LL_ENDL; + // skip invisible views + if (!control->getVisible()) continue; + + S32 ctrl_width = sChildrenWidths[i]; // including space between current & left controls + + // decrease available for + name_new_width -= ctrl_width; + LL_DEBUGS("AvatarItemReshape") << "width: " << ctrl_width << ", name_new_width: " << name_new_width << LL_ENDL; + + LLRect control_rect = control->getRect(); + LL_DEBUGS("AvatarItemReshape") << "rect before: " << control_rect << LL_ENDL; + + if (ALIC_ICON == i) + { + // assume that this is the last iteration, + // so it is not necessary to save "ctrl_new_left" value calculated on previous iterations + ctrl_new_left = sLeftPadding; + name_new_left = ctrl_new_left + ctrl_width; + } + else + { + ctrl_new_left -= ctrl_width; + } + + LL_DEBUGS("AvatarItemReshape") << "ctrl_new_left: " << ctrl_new_left << LL_ENDL; + + control_rect.setLeftTopAndSize( + ctrl_new_left, + control_rect.mTop, + control_rect.getWidth(), + control_rect.getHeight()); + + LL_DEBUGS("AvatarItemReshape") << "rect after: " << control_rect << LL_ENDL; + control->setShape(control_rect); + } + + // set size and position of the "name" child + LLView* name_view = getItemChildView(ALIC_NAME); + LLRect name_view_rect = name_view->getRect(); + LL_DEBUGS("AvatarItemReshape") << "name rect before: " << name_view_rect << LL_ENDL; + + // apply paddings + name_new_width -= sLeftPadding; + name_new_width -= sNameRightPadding; + + name_view_rect.setLeftTopAndSize( + name_new_left, + name_view_rect.mTop, + name_new_width, + name_view_rect.getHeight()); + + name_view->setShape(name_view_rect); + + LL_DEBUGS("AvatarItemReshape") << "name rect after: " << name_view_rect << LL_ENDL; +} + +bool LLAvatarListItem::showPermissions(bool visible) +{ + const LLRelationship* relation = LLAvatarTracker::instance().getBuddyInfo(getAvatarId()); + if(relation && visible) + { + mIconPermissionOnline->setVisible(relation->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS)); + mIconPermissionMap->setVisible(relation->isRightGrantedTo(LLRelationship::GRANT_MAP_LOCATION)); + mIconPermissionEditMine->setVisible(relation->isRightGrantedTo(LLRelationship::GRANT_MODIFY_OBJECTS)); + mIconPermissionEditTheirs->setVisible(relation->isRightGrantedFrom(LLRelationship::GRANT_MODIFY_OBJECTS)); + } + else + { + mIconPermissionOnline->setVisible(false); + mIconPermissionMap->setVisible(false); + mIconPermissionEditMine->setVisible(false); + mIconPermissionEditTheirs->setVisible(false); + } + + return NULL != relation; +} + +LLView* LLAvatarListItem::getItemChildView(EAvatarListItemChildIndex child_view_index) +{ + LLView* child_view = mAvatarName; + + switch (child_view_index) + { + case ALIC_ICON: + child_view = mAvatarIcon; + break; + case ALIC_NAME: + child_view = mAvatarName; + break; + case ALIC_INTERACTION_TIME: + child_view = mLastInteractionTime; + break; + case ALIC_SPEAKER_INDICATOR: + child_view = mSpeakingIndicator; + break; + case ALIC_PERMISSION_ONLINE: + child_view = mIconPermissionOnline; + break; + case ALIC_PERMISSION_MAP: + child_view = mIconPermissionMap; + break; + case ALIC_PERMISSION_EDIT_MINE: + child_view = mIconPermissionEditMine; + break; + case ALIC_PERMISSION_EDIT_THEIRS: + child_view = mIconPermissionEditTheirs; + break; + case ALIC_INFO_BUTTON: + child_view = mInfoBtn; + break; + case ALIC_PROFILE_BUTTON: + child_view = mProfileBtn; + break; + default: + LL_WARNS("AvatarItemReshape") << "Unexpected child view index is passed: " << child_view_index << LL_ENDL; + // leave child_view untouched + } + + return child_view; +} + +// EOF diff --git a/indra/newview/llavatarlistitem.h b/indra/newview/llavatarlistitem.h index c27604ba2f..2e4c597d30 100644 --- a/indra/newview/llavatarlistitem.h +++ b/indra/newview/llavatarlistitem.h @@ -1,242 +1,242 @@ -/** - * @file llavatarlistitem.h - * @brief avatar list item header file - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLAVATARLISTITEM_H -#define LL_LLAVATARLISTITEM_H - -#include - -#include "llpanel.h" -#include "llbutton.h" -#include "lltextbox.h" -#include "llstyle.h" - -#include "llcallingcard.h" // for LLFriendObserver - -class LLAvatarIconCtrl; -class LLOutputMonitorCtrl; -class LLAvatarName; -class LLIconCtrl; - -class LLAvatarListItem : public LLPanel, public LLFriendObserver -{ -public: - struct Params : public LLInitParam::Block - { - Optional default_style, - voice_call_invited_style, - voice_call_joined_style, - voice_call_left_style, - online_style, - offline_style; - - Optional name_right_pad; - - Params(); - }; - - typedef enum e_item_state_type { - IS_DEFAULT, - IS_VOICE_INVITED, - IS_VOICE_JOINED, - IS_VOICE_LEFT, - IS_ONLINE, - IS_OFFLINE, - } EItemState; - - /** - * Creates an instance of LLAvatarListItem. - * - * It is not registered with LLDefaultChildRegistry. It is built via LLUICtrlFactory::buildPanel - * or via registered LLCallbackMap depend on passed parameter. - * - * @param not_from_ui_factory if true instance will be build with LLUICtrlFactory::buildPanel - * otherwise it should be registered via LLCallbackMap before creating. - */ - LLAvatarListItem(bool not_from_ui_factory = true); - virtual ~LLAvatarListItem(); - - virtual bool postBuild(); - - /** - * Processes notification from speaker indicator to update children when indicator's visibility is changed. - */ - virtual void handleVisibilityChange ( bool new_visibility ); - virtual S32 notifyParent(const LLSD& info); - virtual void onMouseLeave(S32 x, S32 y, MASK mask); - virtual void onMouseEnter(S32 x, S32 y, MASK mask); - virtual void setValue(const LLSD& value); - virtual void changed(U32 mask); // from LLFriendObserver - - void setOnline(bool online); - void updateAvatarName(); // re-query the name cache - void setAvatarName(const std::string& name); - void setAvatarToolTip(const std::string& tooltip); - void setHighlight(const std::string& highlight); - void setState(EItemState item_style); - void setAvatarId(const LLUUID& id, const LLUUID& session_id, bool ignore_status_changes = false, bool is_resident = true); - void setLastInteractionTime(U32 secs_since); - //Show/hide profile/info btn, translating speaker indicator and avatar name coordinates accordingly - void setShowProfileBtn(bool show); - void setShowInfoBtn(bool show); - void showSpeakingIndicator(bool show); - void setShowPermissions(bool show) { mShowPermissions = show; }; - void showLastInteractionTime(bool show); - void setAvatarIconVisible(bool visible); - void setShowCompleteName(bool show) { mShowCompleteName = show;}; - - const LLUUID& getAvatarId() const; - std::string getAvatarName() const; - std::string getAvatarToolTip() const; - - void onInfoBtnClick(); - void onProfileBtnClick(); - - /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask); - -protected: - /** - * Contains indicator to show voice activity. - */ - LLOutputMonitorCtrl* mSpeakingIndicator; - - LLAvatarIconCtrl* mAvatarIcon; - - /// Indicator for permission to see me online. - LLIconCtrl* mIconPermissionOnline; - /// Indicator for permission to see my position on the map. - LLIconCtrl* mIconPermissionMap; - /// Indicator for permission to edit my objects. - LLIconCtrl* mIconPermissionEditMine; - /// Indicator for permission to edit their objects. - LLIconCtrl* mIconPermissionEditTheirs; - -private: - - typedef enum e_online_status { - E_OFFLINE, - E_ONLINE, - E_UNKNOWN, - } EOnlineStatus; - - /** - * Enumeration of item elements in order from right to left. - * - * updateChildren() assumes that indexes are in the such order to process avatar icon easier. - * - * @see updateChildren() - */ - typedef enum e_avatar_item_child { - ALIC_SPEAKER_INDICATOR, - ALIC_PROFILE_BUTTON, - ALIC_INFO_BUTTON, - ALIC_PERMISSION_ONLINE, - ALIC_PERMISSION_MAP, - ALIC_PERMISSION_EDIT_MINE, - ALIC_PERMISSION_EDIT_THEIRS, - ALIC_INTERACTION_TIME, - ALIC_NAME, - ALIC_ICON, - ALIC_COUNT, - } EAvatarListItemChildIndex; - - void setNameInternal(const std::string& name, const std::string& highlight); - void onAvatarNameCache(const LLAvatarName& av_name); - - std::string formatSeconds(U32 secs); - - typedef std::map icon_color_map_t; - static icon_color_map_t& getItemIconColorMap(); - - /** - * Initializes widths of all children to use them while changing visibility of any of them. - * - * @see updateChildren() - */ - static void initChildrenWidths(LLAvatarListItem* self); - - /** - * Updates position and rectangle of visible children to fit all available item's width. - */ - void updateChildren(); - - /** - * Update visibility of active permissions icons. - * - * Need to call updateChildren() afterwards to sort out their layout. - */ - bool showPermissions(bool visible); - - /** - * Gets child view specified by index. - * - * This method implemented via switch by all EAvatarListItemChildIndex values. - * It is used to not store children in array or vector to avoid of increasing memory usage. - */ - LLView* getItemChildView(EAvatarListItemChildIndex child_index); - - LLTextBox* mAvatarName; - LLTextBox* mLastInteractionTime; - LLStyle::Params mAvatarNameStyle; - - LLButton* mInfoBtn; - LLButton* mProfileBtn; - - LLUUID mAvatarId; - std::string mHighlihtSubstring; // substring to highlight - EOnlineStatus mOnlineStatus; - //Flag indicating that info/profile button shouldn't be shown at all. - //Speaker indicator and avatar name coords are translated accordingly - bool mShowInfoBtn; - bool mShowProfileBtn; - - /// indicates whether to show icons representing permissions granted - bool mShowPermissions; - - /// true when the mouse pointer is hovering over this item - bool mHovered; - - bool mShowCompleteName; - std::string mGreyOutUsername; - - void fetchAvatarName(); - boost::signals2::connection mAvatarNameCacheConnection; - - static bool sStaticInitialized; // this variable is introduced to improve code readability - static S32 sLeftPadding; // padding to first left visible child (icon or name) - static S32 sNameRightPadding; // right padding from name to next visible child - - /** - * Contains widths of each child specified by EAvatarListItemChildIndex - * including padding to the next right one. - * - * @see initChildrenWidths() - */ - static S32 sChildrenWidths[ALIC_COUNT]; - -}; - -#endif //LL_LLAVATARLISTITEM_H +/** + * @file llavatarlistitem.h + * @brief avatar list item header file + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLAVATARLISTITEM_H +#define LL_LLAVATARLISTITEM_H + +#include + +#include "llpanel.h" +#include "llbutton.h" +#include "lltextbox.h" +#include "llstyle.h" + +#include "llcallingcard.h" // for LLFriendObserver + +class LLAvatarIconCtrl; +class LLOutputMonitorCtrl; +class LLAvatarName; +class LLIconCtrl; + +class LLAvatarListItem : public LLPanel, public LLFriendObserver +{ +public: + struct Params : public LLInitParam::Block + { + Optional default_style, + voice_call_invited_style, + voice_call_joined_style, + voice_call_left_style, + online_style, + offline_style; + + Optional name_right_pad; + + Params(); + }; + + typedef enum e_item_state_type { + IS_DEFAULT, + IS_VOICE_INVITED, + IS_VOICE_JOINED, + IS_VOICE_LEFT, + IS_ONLINE, + IS_OFFLINE, + } EItemState; + + /** + * Creates an instance of LLAvatarListItem. + * + * It is not registered with LLDefaultChildRegistry. It is built via LLUICtrlFactory::buildPanel + * or via registered LLCallbackMap depend on passed parameter. + * + * @param not_from_ui_factory if true instance will be build with LLUICtrlFactory::buildPanel + * otherwise it should be registered via LLCallbackMap before creating. + */ + LLAvatarListItem(bool not_from_ui_factory = true); + virtual ~LLAvatarListItem(); + + virtual bool postBuild(); + + /** + * Processes notification from speaker indicator to update children when indicator's visibility is changed. + */ + virtual void handleVisibilityChange ( bool new_visibility ); + virtual S32 notifyParent(const LLSD& info); + virtual void onMouseLeave(S32 x, S32 y, MASK mask); + virtual void onMouseEnter(S32 x, S32 y, MASK mask); + virtual void setValue(const LLSD& value); + virtual void changed(U32 mask); // from LLFriendObserver + + void setOnline(bool online); + void updateAvatarName(); // re-query the name cache + void setAvatarName(const std::string& name); + void setAvatarToolTip(const std::string& tooltip); + void setHighlight(const std::string& highlight); + void setState(EItemState item_style); + void setAvatarId(const LLUUID& id, const LLUUID& session_id, bool ignore_status_changes = false, bool is_resident = true); + void setLastInteractionTime(U32 secs_since); + //Show/hide profile/info btn, translating speaker indicator and avatar name coordinates accordingly + void setShowProfileBtn(bool show); + void setShowInfoBtn(bool show); + void showSpeakingIndicator(bool show); + void setShowPermissions(bool show) { mShowPermissions = show; }; + void showLastInteractionTime(bool show); + void setAvatarIconVisible(bool visible); + void setShowCompleteName(bool show) { mShowCompleteName = show;}; + + const LLUUID& getAvatarId() const; + std::string getAvatarName() const; + std::string getAvatarToolTip() const; + + void onInfoBtnClick(); + void onProfileBtnClick(); + + /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask); + +protected: + /** + * Contains indicator to show voice activity. + */ + LLOutputMonitorCtrl* mSpeakingIndicator; + + LLAvatarIconCtrl* mAvatarIcon; + + /// Indicator for permission to see me online. + LLIconCtrl* mIconPermissionOnline; + /// Indicator for permission to see my position on the map. + LLIconCtrl* mIconPermissionMap; + /// Indicator for permission to edit my objects. + LLIconCtrl* mIconPermissionEditMine; + /// Indicator for permission to edit their objects. + LLIconCtrl* mIconPermissionEditTheirs; + +private: + + typedef enum e_online_status { + E_OFFLINE, + E_ONLINE, + E_UNKNOWN, + } EOnlineStatus; + + /** + * Enumeration of item elements in order from right to left. + * + * updateChildren() assumes that indexes are in the such order to process avatar icon easier. + * + * @see updateChildren() + */ + typedef enum e_avatar_item_child { + ALIC_SPEAKER_INDICATOR, + ALIC_PROFILE_BUTTON, + ALIC_INFO_BUTTON, + ALIC_PERMISSION_ONLINE, + ALIC_PERMISSION_MAP, + ALIC_PERMISSION_EDIT_MINE, + ALIC_PERMISSION_EDIT_THEIRS, + ALIC_INTERACTION_TIME, + ALIC_NAME, + ALIC_ICON, + ALIC_COUNT, + } EAvatarListItemChildIndex; + + void setNameInternal(const std::string& name, const std::string& highlight); + void onAvatarNameCache(const LLAvatarName& av_name); + + std::string formatSeconds(U32 secs); + + typedef std::map icon_color_map_t; + static icon_color_map_t& getItemIconColorMap(); + + /** + * Initializes widths of all children to use them while changing visibility of any of them. + * + * @see updateChildren() + */ + static void initChildrenWidths(LLAvatarListItem* self); + + /** + * Updates position and rectangle of visible children to fit all available item's width. + */ + void updateChildren(); + + /** + * Update visibility of active permissions icons. + * + * Need to call updateChildren() afterwards to sort out their layout. + */ + bool showPermissions(bool visible); + + /** + * Gets child view specified by index. + * + * This method implemented via switch by all EAvatarListItemChildIndex values. + * It is used to not store children in array or vector to avoid of increasing memory usage. + */ + LLView* getItemChildView(EAvatarListItemChildIndex child_index); + + LLTextBox* mAvatarName; + LLTextBox* mLastInteractionTime; + LLStyle::Params mAvatarNameStyle; + + LLButton* mInfoBtn; + LLButton* mProfileBtn; + + LLUUID mAvatarId; + std::string mHighlihtSubstring; // substring to highlight + EOnlineStatus mOnlineStatus; + //Flag indicating that info/profile button shouldn't be shown at all. + //Speaker indicator and avatar name coords are translated accordingly + bool mShowInfoBtn; + bool mShowProfileBtn; + + /// indicates whether to show icons representing permissions granted + bool mShowPermissions; + + /// true when the mouse pointer is hovering over this item + bool mHovered; + + bool mShowCompleteName; + std::string mGreyOutUsername; + + void fetchAvatarName(); + boost::signals2::connection mAvatarNameCacheConnection; + + static bool sStaticInitialized; // this variable is introduced to improve code readability + static S32 sLeftPadding; // padding to first left visible child (icon or name) + static S32 sNameRightPadding; // right padding from name to next visible child + + /** + * Contains widths of each child specified by EAvatarListItemChildIndex + * including padding to the next right one. + * + * @see initChildrenWidths() + */ + static S32 sChildrenWidths[ALIC_COUNT]; + +}; + +#endif //LL_LLAVATARLISTITEM_H diff --git a/indra/newview/llavatarpropertiesprocessor.cpp b/indra/newview/llavatarpropertiesprocessor.cpp index daf2237629..65e32610c3 100644 --- a/indra/newview/llavatarpropertiesprocessor.cpp +++ b/indra/newview/llavatarpropertiesprocessor.cpp @@ -1,721 +1,721 @@ -/** - * @file llavatarpropertiesprocessor.cpp - * @brief LLAvatarPropertiesProcessor class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llavatarpropertiesprocessor.h" - -// Viewer includes -#include "llagent.h" -#include "llagentpicksinfo.h" -#include "lldateutil.h" -#include "llviewergenericmessage.h" -#include "llstartup.h" - -// Linden library includes -#include "llavataractions.h" // for getProfileUrl -#include "lldate.h" -#include "lltrans.h" -#include "llui.h" // LLUI::getLanguage() -#include "message.h" - -LLAvatarPropertiesProcessor::LLAvatarPropertiesProcessor() -{ -} - -LLAvatarPropertiesProcessor::~LLAvatarPropertiesProcessor() -{ -} - -void LLAvatarPropertiesProcessor::addObserver(const LLUUID& avatar_id, LLAvatarPropertiesObserver* observer) -{ - if (!observer) - return; - - // Check if that observer is already in mObservers for that avatar_id - using pair = std::pair; - observer_multimap_t::iterator begin = mObservers.begin(); - observer_multimap_t::iterator end = mObservers.end(); - observer_multimap_t::iterator it = std::find_if(begin, end, [&](const pair& p) - { - return p.first == avatar_id && p.second == observer; - }); - - // IAN BUG this should update the observer's UUID if this is a dupe - sent to PE - if (it == end) - { - mObservers.emplace(avatar_id, observer); - } -} - -void LLAvatarPropertiesProcessor::removeObserver(const LLUUID& avatar_id, LLAvatarPropertiesObserver* observer) -{ - if (!observer) - { - return; - } - - // Check if that observer is in mObservers for that avatar_id - using pair = std::pair; - observer_multimap_t::iterator begin = mObservers.begin(); - observer_multimap_t::iterator end = mObservers.end(); - observer_multimap_t::iterator it = std::find_if(begin, end, [&](const pair& p) - { - return p.first == avatar_id && p.second == observer; - }); - - if (it != end) - { - mObservers.erase(it); - } -} - -void LLAvatarPropertiesProcessor::sendRequest(const LLUUID& avatar_id, EAvatarProcessorType type, const std::string &method) -{ - // this is the startup state when send_complete_agent_movement() message is sent. - // Before this messages won't work so don't bother trying - if (LLStartUp::getStartupState() <= STATE_AGENT_SEND) - { - return; - } - - if (avatar_id.isNull()) - { - return; - } - - // Suppress duplicate requests while waiting for a response from the network - if (isPendingRequest(avatar_id, type)) - { - // waiting for a response, don't re-request - return; - } - - // Try to send HTTP request if cap_url is available - if (type == APT_PROPERTIES) - { - std::string cap_url = gAgent.getRegionCapability("AgentProfile"); - if (!cap_url.empty()) - { - initAgentProfileCapRequest(avatar_id, cap_url, type); - } - else - { - // Don't sent UDP request for APT_PROPERTIES - LL_WARNS() << "No cap_url for APT_PROPERTIES, request for " << avatar_id << " is not sent" << LL_ENDL; - } - return; - } - - // Send UDP request - if (type == APT_PROPERTIES_LEGACY) - { - sendAvatarPropertiesRequestMessage(avatar_id); - } - else - { - sendGenericRequest(avatar_id, type, method); - } -} - -void LLAvatarPropertiesProcessor::sendGenericRequest(const LLUUID& avatar_id, EAvatarProcessorType type, const std::string &method) -{ - // indicate we're going to make a request - addPendingRequest(avatar_id, type); - - std::vector strings{ avatar_id.asString() }; - send_generic_message(method, strings); -} - -void LLAvatarPropertiesProcessor::sendAvatarPropertiesRequestMessage(const LLUUID& avatar_id) -{ - addPendingRequest(avatar_id, APT_PROPERTIES_LEGACY); - - LLMessageSystem *msg = gMessageSystem; - - msg->newMessageFast(_PREHASH_AvatarPropertiesRequest); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgentID); - msg->addUUIDFast(_PREHASH_SessionID, gAgentSessionID); - msg->addUUIDFast(_PREHASH_AvatarID, avatar_id); - gAgent.sendReliableMessage(); -} - -void LLAvatarPropertiesProcessor::initAgentProfileCapRequest(const LLUUID& avatar_id, const std::string& cap_url, EAvatarProcessorType type) -{ - addPendingRequest(avatar_id, type); - LLCoros::instance().launch("requestAgentUserInfoCoro", - [cap_url, avatar_id, type]() { requestAvatarPropertiesCoro(cap_url, avatar_id, type); }); -} - -void LLAvatarPropertiesProcessor::sendAvatarPropertiesRequest(const LLUUID& avatar_id) -{ - sendRequest(avatar_id, APT_PROPERTIES, "AvatarPropertiesRequest"); -} - -void LLAvatarPropertiesProcessor::sendAvatarLegacyPropertiesRequest(const LLUUID& avatar_id) -{ - sendRequest(avatar_id, APT_PROPERTIES_LEGACY, "AvatarPropertiesRequest"); -} - -void LLAvatarPropertiesProcessor::sendAvatarTexturesRequest(const LLUUID& avatar_id) -{ - sendGenericRequest(avatar_id, APT_TEXTURES, "avatartexturesrequest"); - // No response expected. - removePendingRequest(avatar_id, APT_TEXTURES); -} - -void LLAvatarPropertiesProcessor::sendAvatarClassifiedsRequest(const LLUUID& avatar_id) -{ - sendGenericRequest(avatar_id, APT_CLASSIFIEDS, "avatarclassifiedsrequest"); -} - -//static -std::string LLAvatarPropertiesProcessor::accountType(const LLAvatarData* avatar_data) -{ - // If you have a special account, like M Linden ("El Jefe!") - // return an untranslated "special" string - if (!avatar_data->caption_text.empty()) - { - return avatar_data->caption_text; - } - const char* const ACCT_TYPE[] = { - "AcctTypeResident", - "AcctTypeTrial", - "AcctTypeCharterMember", - "AcctTypeEmployee" - }; - U8 caption_max = (U8)LL_ARRAY_SIZE(ACCT_TYPE)-1; - U8 caption_index = llclamp(avatar_data->caption_index, (U8)0, caption_max); - return LLTrans::getString(ACCT_TYPE[caption_index]); -} - -//static -std::string LLAvatarPropertiesProcessor::paymentInfo(const LLAvatarData* avatar_data) -{ - // Special accounts like M Linden don't have payment info revealed. - if (!avatar_data->caption_text.empty()) - return ""; - - // Linden employees don't have payment info revealed - constexpr S32 LINDEN_EMPLOYEE_INDEX = 3; - if (avatar_data->caption_index == LINDEN_EMPLOYEE_INDEX) - return ""; - - bool transacted = (avatar_data->flags & AVATAR_TRANSACTED); - bool identified = (avatar_data->flags & AVATAR_IDENTIFIED); - // Not currently getting set in dataserver/lldataavatar.cpp for privacy considerations - //bool age_verified = (avatar_data->flags & AVATAR_AGEVERIFIED); - - const char* payment_text; - if (transacted) - { - payment_text = "PaymentInfoUsed"; - } - else if (identified) - { - payment_text = "PaymentInfoOnFile"; - } - else - { - payment_text = "NoPaymentInfoOnFile"; - } - return LLTrans::getString(payment_text); -} - -//static -bool LLAvatarPropertiesProcessor::hasPaymentInfoOnFile(const LLAvatarData* avatar_data) -{ - // Special accounts like M Linden don't have payment info revealed. - if (!avatar_data->caption_text.empty()) - return true; - - // Linden employees don't have payment info revealed - constexpr S32 LINDEN_EMPLOYEE_INDEX = 3; - if (avatar_data->caption_index == LINDEN_EMPLOYEE_INDEX) - return true; - - return ((avatar_data->flags & AVATAR_TRANSACTED) || (avatar_data->flags & AVATAR_IDENTIFIED)); -} - -// static -void LLAvatarPropertiesProcessor::requestAvatarPropertiesCoro(std::string cap_url, LLUUID avatar_id, EAvatarProcessorType type) -{ - LLAvatarPropertiesProcessor& inst = instance(); - - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("requestAvatarPropertiesCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpHeaders::ptr_t httpHeaders; - - LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); - httpOpts->setFollowRedirects(true); - - std::string finalUrl = cap_url + "/" + avatar_id.asString(); - - LLSD result = httpAdapter->getAndSuspend(httpRequest, finalUrl, httpOpts, httpHeaders); - - // Response is being processed, no longer pending is required - inst.removePendingRequest(avatar_id, type); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status - || !result.has("id") - || avatar_id != result["id"].asUUID()) - { - LL_WARNS("AvatarProperties") << "Failed to get agent information for id " << avatar_id - << (!status ? " (no HTTP status)" : !result.has("id") ? " (no result.id)" : - std::string(" (result.id=") + result["id"].asUUID().asString() + ")") - << LL_ENDL; - return; - } - - LLAvatarData avatar_data; - - std::string birth_date; - - avatar_data.agent_id = gAgentID; - avatar_data.avatar_id = avatar_id; - avatar_data.image_id = result["sl_image_id"].asUUID(); - avatar_data.fl_image_id = result["fl_image_id"].asUUID(); - avatar_data.partner_id = result["partner_id"].asUUID(); - avatar_data.about_text = result["sl_about_text"].asString(); - avatar_data.fl_about_text = result["fl_about_text"].asString(); - avatar_data.born_on = result["member_since"].asDate(); - // TODO: SL-20163 Remove the "has" check when SRV-684 is done - // and the field "hide_age" is included to the http response - inst.mIsHideAgeSupportedByServer = result.has("hide_age"); - avatar_data.hide_age = inst.isHideAgeSupportedByServer() && result["hide_age"].asBoolean(); - avatar_data.profile_url = getProfileURL(avatar_id.asString()); - avatar_data.customer_type = result["customer_type"].asString(); - avatar_data.notes = result["notes"].asString(); - - avatar_data.flags = 0; - if (result["online"].asBoolean()) - { - avatar_data.flags |= AVATAR_ONLINE; - } - if (result["allow_publish"].asBoolean()) - { - avatar_data.flags |= AVATAR_ALLOW_PUBLISH; - } - if (result["identified"].asBoolean()) - { - avatar_data.flags |= AVATAR_IDENTIFIED; - } - if (result["transacted"].asBoolean()) - { - avatar_data.flags |= AVATAR_TRANSACTED; - } - - avatar_data.caption_index = 0; - if (result.has("charter_member")) // won't be present if "caption" is set - { - avatar_data.caption_index = result["charter_member"].asInteger(); - } - else if (result.has("caption")) - { - avatar_data.caption_text = result["caption"].asString(); - } - - // Groups - LLSD groups_array = result["groups"]; - for (LLSD::array_const_iterator it = groups_array.beginArray(); it != groups_array.endArray(); ++it) - { - const LLSD& group_info = *it; - LLAvatarData::LLGroupData group_data; - group_data.group_powers = 0; // Not in use? - group_data.group_title = group_info["name"].asString(); // Missing data, not in use? - group_data.group_id = group_info["id"].asUUID(); - group_data.group_name = group_info["name"].asString(); - group_data.group_insignia_id = group_info["image_id"].asUUID(); - - avatar_data.group_list.push_back(group_data); - } - - // Picks - LLSD picks_array = result["picks"]; - for (LLSD::array_const_iterator it = picks_array.beginArray(); it != picks_array.endArray(); ++it) - { - const LLSD& pick_data = *it; - avatar_data.picks_list.emplace_back(pick_data["id"].asUUID(), pick_data["name"].asString()); - } - - inst.notifyObservers(avatar_id, &avatar_data, type); -} - -void LLAvatarPropertiesProcessor::processAvatarLegacyPropertiesReply(LLMessageSystem* msg, void**) -{ - LLAvatarLegacyData avatar_data; - std::string birth_date; - - msg->getUUIDFast( _PREHASH_AgentData, _PREHASH_AgentID, avatar_data.agent_id); - msg->getUUIDFast( _PREHASH_AgentData, _PREHASH_AvatarID, avatar_data.avatar_id); - msg->getUUIDFast( _PREHASH_PropertiesData, _PREHASH_ImageID, avatar_data.image_id); - msg->getUUIDFast( _PREHASH_PropertiesData, _PREHASH_FLImageID, avatar_data.fl_image_id); - msg->getUUIDFast( _PREHASH_PropertiesData, _PREHASH_PartnerID, avatar_data.partner_id); - msg->getStringFast( _PREHASH_PropertiesData, _PREHASH_AboutText, avatar_data.about_text); - msg->getStringFast( _PREHASH_PropertiesData, _PREHASH_FLAboutText, avatar_data.fl_about_text); - msg->getStringFast( _PREHASH_PropertiesData, _PREHASH_BornOn, birth_date); - msg->getString( _PREHASH_PropertiesData, _PREHASH_ProfileURL, avatar_data.profile_url); - msg->getU32Fast( _PREHASH_PropertiesData, _PREHASH_Flags, avatar_data.flags); - - LLDateUtil::dateFromPDTString(avatar_data.born_on, birth_date); - avatar_data.caption_index = 0; - - S32 charter_member_size = 0; - charter_member_size = msg->getSize(_PREHASH_PropertiesData, _PREHASH_CharterMember); - if (1 == charter_member_size) - { - msg->getBinaryData(_PREHASH_PropertiesData, _PREHASH_CharterMember, &avatar_data.caption_index, 1); - } - else if (1 < charter_member_size) - { - msg->getString(_PREHASH_PropertiesData, _PREHASH_CharterMember, avatar_data.caption_text); - } - LLAvatarPropertiesProcessor* self = getInstance(); - // Request processed, no longer pending - self->removePendingRequest(avatar_data.avatar_id, APT_PROPERTIES_LEGACY); - self->notifyObservers(avatar_data.avatar_id, &avatar_data, APT_PROPERTIES_LEGACY); -} - -void LLAvatarPropertiesProcessor::processAvatarInterestsReply(LLMessageSystem* msg, void**) -{ -/* - AvatarInterestsReply is automatically sent by the server in response to the - AvatarPropertiesRequest (in addition to the AvatarPropertiesReply message). - If the interests panel is no longer part of the design (?) we should just register the message - to a handler function that does nothing. - That will suppress the warnings and be compatible with old server versions. - WARNING: LLTemplateMessageReader::decodeData: Message from 216.82.37.237:13000 with no handler function received: AvatarInterestsReply -*/ -} - -void LLAvatarPropertiesProcessor::processAvatarClassifiedsReply(LLMessageSystem* msg, void**) -{ - LLAvatarClassifieds classifieds; - - msg->getUUID(_PREHASH_AgentData, _PREHASH_AgentID, classifieds.agent_id); - msg->getUUID(_PREHASH_AgentData, _PREHASH_TargetID, classifieds.target_id); - - S32 block_count = msg->getNumberOfBlocks(_PREHASH_Data); - - for(int n = 0; n < block_count; ++n) - { - LLAvatarClassifieds::classified_data data; - - msg->getUUID(_PREHASH_Data, _PREHASH_ClassifiedID, data.classified_id, n); - msg->getString(_PREHASH_Data, _PREHASH_Name, data.name, n); - - classifieds.classifieds_list.emplace_back(data); - } - - LLAvatarPropertiesProcessor* self = getInstance(); - // Request processed, no longer pending - self->removePendingRequest(classifieds.target_id, APT_CLASSIFIEDS); - self->notifyObservers(classifieds.target_id,&classifieds,APT_CLASSIFIEDS); -} - -void LLAvatarPropertiesProcessor::processClassifiedInfoReply(LLMessageSystem* msg, void**) -{ - LLAvatarClassifiedInfo c_info; - - msg->getUUID(_PREHASH_AgentData, _PREHASH_AgentID, c_info.agent_id); - - msg->getUUID(_PREHASH_Data, _PREHASH_ClassifiedID, c_info.classified_id); - msg->getUUID(_PREHASH_Data, _PREHASH_CreatorID, c_info.creator_id); - msg->getU32(_PREHASH_Data, _PREHASH_CreationDate, c_info.creation_date); - msg->getU32(_PREHASH_Data, _PREHASH_ExpirationDate, c_info.expiration_date); - msg->getU32(_PREHASH_Data, _PREHASH_Category, c_info.category); - msg->getString(_PREHASH_Data, _PREHASH_Name, c_info.name); - msg->getString(_PREHASH_Data, _PREHASH_Desc, c_info.description); - msg->getUUID(_PREHASH_Data, _PREHASH_ParcelID, c_info.parcel_id); - msg->getU32(_PREHASH_Data, _PREHASH_ParentEstate, c_info.parent_estate); - msg->getUUID(_PREHASH_Data, _PREHASH_SnapshotID, c_info.snapshot_id); - msg->getString(_PREHASH_Data, _PREHASH_SimName, c_info.sim_name); - msg->getVector3d(_PREHASH_Data, _PREHASH_PosGlobal, c_info.pos_global); - msg->getString(_PREHASH_Data, _PREHASH_ParcelName, c_info.parcel_name); - msg->getU8(_PREHASH_Data, _PREHASH_ClassifiedFlags, c_info.flags); - msg->getS32(_PREHASH_Data, _PREHASH_PriceForListing, c_info.price_for_listing); - - LLAvatarPropertiesProcessor* self = getInstance(); - // Request processed, no longer pending - self->removePendingRequest(c_info.creator_id, APT_CLASSIFIED_INFO); - self->notifyObservers(c_info.creator_id, &c_info, APT_CLASSIFIED_INFO); -} - - -void LLAvatarPropertiesProcessor::processAvatarNotesReply(LLMessageSystem* msg, void**) -{ - // Deprecated, new "AgentProfile" allows larger notes -} - -void LLAvatarPropertiesProcessor::processAvatarPicksReply(LLMessageSystem* msg, void**) -{ - LLUUID agent_id; - LLUUID target_id; - msg->getUUID(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); - msg->getUUID(_PREHASH_AgentData, _PREHASH_TargetID, target_id); - - LL_DEBUGS("AvatarProperties") << "Received AvatarPicksReply for " << target_id << LL_ENDL; -} - -void LLAvatarPropertiesProcessor::processPickInfoReply(LLMessageSystem* msg, void**) -{ - LLPickData pick_data; - - // Extract the agent id and verify the message is for this - // client. - msg->getUUID(_PREHASH_AgentData, _PREHASH_AgentID, pick_data.agent_id ); - msg->getUUID(_PREHASH_Data, _PREHASH_PickID, pick_data.pick_id); - msg->getUUID(_PREHASH_Data, _PREHASH_CreatorID, pick_data.creator_id); - - // ** top_pick should be deleted, not being used anymore - angela - msg->getBOOL(_PREHASH_Data, _PREHASH_TopPick, pick_data.top_pick); - msg->getUUID(_PREHASH_Data, _PREHASH_ParcelID, pick_data.parcel_id); - msg->getString(_PREHASH_Data, _PREHASH_Name, pick_data.name); - msg->getString(_PREHASH_Data, _PREHASH_Desc, pick_data.desc); - msg->getUUID(_PREHASH_Data, _PREHASH_SnapshotID, pick_data.snapshot_id); - - msg->getString(_PREHASH_Data, _PREHASH_User, pick_data.user_name); - msg->getString(_PREHASH_Data, _PREHASH_OriginalName, pick_data.original_name); - msg->getString(_PREHASH_Data, _PREHASH_SimName, pick_data.sim_name); - msg->getVector3d(_PREHASH_Data, _PREHASH_PosGlobal, pick_data.pos_global); - - msg->getS32(_PREHASH_Data, _PREHASH_SortOrder, pick_data.sort_order); - msg->getBOOL(_PREHASH_Data, _PREHASH_Enabled, pick_data.enabled); - - LLAvatarPropertiesProcessor* self = getInstance(); - // don't need to remove pending request as we don't track pick info - self->notifyObservers(pick_data.creator_id, &pick_data, APT_PICK_INFO); -} - -void LLAvatarPropertiesProcessor::processAvatarGroupsReply(LLMessageSystem* msg, void**) -{ - /* - AvatarGroupsReply is automatically sent by the server in response to the - AvatarPropertiesRequest in addition to the AvatarPropertiesReply message. - */ - LLUUID agent_id; - LLUUID avatar_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AvatarID, avatar_id); - - LL_DEBUGS("AvatarProperties") << "Received AvatarGroupsReply for " << avatar_id << LL_ENDL; -} - -void LLAvatarPropertiesProcessor::notifyObservers(const LLUUID& id, void* data, EAvatarProcessorType type) -{ - // Copy the map (because observers may delete themselves when updated?) - LLAvatarPropertiesProcessor::observer_multimap_t observers = mObservers; - - for (const auto& [agent_id, observer] : observers) - { - // only notify observers for the same agent, or if the observer - // didn't know the agent ID and passed a NULL id. - if (agent_id == id || agent_id.isNull()) - { - observer->processProperties(data, type); - } - } -} - -void LLAvatarPropertiesProcessor::sendFriendRights(const LLUUID& avatar_id, S32 rights) -{ - if(!avatar_id.isNull()) - { - LLMessageSystem* msg = gMessageSystem; - - // setup message header - msg->newMessageFast(_PREHASH_GrantUserRights); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUID(_PREHASH_AgentID, gAgentID); - msg->addUUID(_PREHASH_SessionID, gAgentSessionID); - - msg->nextBlockFast(_PREHASH_Rights); - msg->addUUID(_PREHASH_AgentRelated, avatar_id); - msg->addS32(_PREHASH_RelatedRights, rights); - - gAgent.sendReliableMessage(); - } -} - -void LLAvatarPropertiesProcessor::sendPickDelete( const LLUUID& pick_id ) -{ - LLMessageSystem* msg = gMessageSystem; - msg->newMessage(_PREHASH_PickDelete); - msg->nextBlock(_PREHASH_AgentData); - msg->addUUID(_PREHASH_AgentID, gAgentID); - msg->addUUID(_PREHASH_SessionID, gAgentSessionID); - msg->nextBlock(_PREHASH_Data); - msg->addUUID(_PREHASH_PickID, pick_id); - gAgent.sendReliableMessage(); - - LLAgentPicksInfo::getInstance()->requestNumberOfPicks(); - LLAgentPicksInfo::getInstance()->decrementNumberOfPicks(); -} - -void LLAvatarPropertiesProcessor::sendClassifiedDelete(const LLUUID& classified_id) -{ - LLMessageSystem* msg = gMessageSystem; - - msg->newMessage(_PREHASH_ClassifiedDelete); - - msg->nextBlock(_PREHASH_AgentData); - msg->addUUID(_PREHASH_AgentID, gAgentID); - msg->addUUID(_PREHASH_SessionID, gAgentSessionID); - - msg->nextBlock(_PREHASH_Data); - msg->addUUID(_PREHASH_ClassifiedID, classified_id); - - gAgent.sendReliableMessage(); -} - -void LLAvatarPropertiesProcessor::sendPickInfoUpdate(const LLPickData* new_pick) -{ - if (!new_pick) - return; - - LLMessageSystem* msg = gMessageSystem; - - msg->newMessage(_PREHASH_PickInfoUpdate); - msg->nextBlock(_PREHASH_AgentData); - msg->addUUID(_PREHASH_AgentID, gAgentID); - msg->addUUID(_PREHASH_SessionID, gAgentSessionID); - - msg->nextBlock(_PREHASH_Data); - msg->addUUID(_PREHASH_PickID, new_pick->pick_id); - msg->addUUID(_PREHASH_CreatorID, new_pick->creator_id); - - //legacy var need to be deleted - msg->addBOOL(_PREHASH_TopPick, false); - - // fills in on simulator if null - msg->addUUID(_PREHASH_ParcelID, new_pick->parcel_id); - msg->addString(_PREHASH_Name, new_pick->name); - msg->addString(_PREHASH_Desc, new_pick->desc); - msg->addUUID(_PREHASH_SnapshotID, new_pick->snapshot_id); - msg->addVector3d(_PREHASH_PosGlobal, new_pick->pos_global); - - // Only top picks have a sort order - msg->addS32(_PREHASH_SortOrder, 0); - - msg->addBOOL(_PREHASH_Enabled, new_pick->enabled); - gAgent.sendReliableMessage(); - - LLAgentPicksInfo::getInstance()->requestNumberOfPicks(); -} - -void LLAvatarPropertiesProcessor::sendClassifiedInfoUpdate(const LLAvatarClassifiedInfo* c_data) -{ - if(!c_data) - { - return; - } - - LLMessageSystem* msg = gMessageSystem; - - msg->newMessage(_PREHASH_ClassifiedInfoUpdate); - - msg->nextBlock(_PREHASH_AgentData); - msg->addUUID(_PREHASH_AgentID, gAgentID); - msg->addUUID(_PREHASH_SessionID, gAgentSessionID); - - msg->nextBlock(_PREHASH_Data); - msg->addUUID(_PREHASH_ClassifiedID, c_data->classified_id); - msg->addU32(_PREHASH_Category, c_data->category); - msg->addString(_PREHASH_Name, c_data->name); - msg->addString(_PREHASH_Desc, c_data->description); - msg->addUUID(_PREHASH_ParcelID, c_data->parcel_id); - msg->addU32(_PREHASH_ParentEstate, 0); - msg->addUUID(_PREHASH_SnapshotID, c_data->snapshot_id); - msg->addVector3d(_PREHASH_PosGlobal, c_data->pos_global); - msg->addU8(_PREHASH_ClassifiedFlags, c_data->flags); - msg->addS32(_PREHASH_PriceForListing, c_data->price_for_listing); - - gAgent.sendReliableMessage(); -} - -void LLAvatarPropertiesProcessor::sendPickInfoRequest(const LLUUID& creator_id, const LLUUID& pick_id) -{ - // Must ask for a pick based on the creator id because - // the pick database is distributed to the inventory cluster. JC - std::vector request_params{ creator_id.asString(), pick_id.asString() }; - send_generic_message("pickinforequest", request_params); -} - -void LLAvatarPropertiesProcessor::sendClassifiedInfoRequest(const LLUUID& classified_id) -{ - LLMessageSystem* msg = gMessageSystem; - - msg->newMessage(_PREHASH_ClassifiedInfoRequest); - msg->nextBlock(_PREHASH_AgentData); - - msg->addUUID(_PREHASH_AgentID, gAgentID); - msg->addUUID(_PREHASH_SessionID, gAgentSessionID); - - msg->nextBlock(_PREHASH_Data); - msg->addUUID(_PREHASH_ClassifiedID, classified_id); - - gAgent.sendReliableMessage(); -} - -bool LLAvatarPropertiesProcessor::isPendingRequest(const LLUUID& avatar_id, EAvatarProcessorType type) -{ - timestamp_map_t::key_type key = std::make_pair(avatar_id, type); - timestamp_map_t::iterator it = mRequestTimestamps.find(key); - - // Is this a new request? - if (it == mRequestTimestamps.end()) return false; - - // We found a request, check if it has timed out - U32 now = time(nullptr); - const U32 REQUEST_EXPIRE_SECS = 5; - U32 expires = it->second + REQUEST_EXPIRE_SECS; - - // Request is still pending if it hasn't expired yet - // *NOTE: Expired requests will accumulate in this map, but they are rare, - // the data is small, and they will be updated if the same data is - // re-requested - return (now < expires); -} - -void LLAvatarPropertiesProcessor::addPendingRequest(const LLUUID& avatar_id, EAvatarProcessorType type) -{ - timestamp_map_t::key_type key = std::make_pair(avatar_id, type); - U32 now = time(nullptr); - // Add or update existing (expired) request - mRequestTimestamps[ key ] = now; -} - -void LLAvatarPropertiesProcessor::removePendingRequest(const LLUUID& avatar_id, EAvatarProcessorType type) -{ - timestamp_map_t::key_type key = std::make_pair(avatar_id, type); - mRequestTimestamps.erase(key); -} +/** + * @file llavatarpropertiesprocessor.cpp + * @brief LLAvatarPropertiesProcessor class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llavatarpropertiesprocessor.h" + +// Viewer includes +#include "llagent.h" +#include "llagentpicksinfo.h" +#include "lldateutil.h" +#include "llviewergenericmessage.h" +#include "llstartup.h" + +// Linden library includes +#include "llavataractions.h" // for getProfileUrl +#include "lldate.h" +#include "lltrans.h" +#include "llui.h" // LLUI::getLanguage() +#include "message.h" + +LLAvatarPropertiesProcessor::LLAvatarPropertiesProcessor() +{ +} + +LLAvatarPropertiesProcessor::~LLAvatarPropertiesProcessor() +{ +} + +void LLAvatarPropertiesProcessor::addObserver(const LLUUID& avatar_id, LLAvatarPropertiesObserver* observer) +{ + if (!observer) + return; + + // Check if that observer is already in mObservers for that avatar_id + using pair = std::pair; + observer_multimap_t::iterator begin = mObservers.begin(); + observer_multimap_t::iterator end = mObservers.end(); + observer_multimap_t::iterator it = std::find_if(begin, end, [&](const pair& p) + { + return p.first == avatar_id && p.second == observer; + }); + + // IAN BUG this should update the observer's UUID if this is a dupe - sent to PE + if (it == end) + { + mObservers.emplace(avatar_id, observer); + } +} + +void LLAvatarPropertiesProcessor::removeObserver(const LLUUID& avatar_id, LLAvatarPropertiesObserver* observer) +{ + if (!observer) + { + return; + } + + // Check if that observer is in mObservers for that avatar_id + using pair = std::pair; + observer_multimap_t::iterator begin = mObservers.begin(); + observer_multimap_t::iterator end = mObservers.end(); + observer_multimap_t::iterator it = std::find_if(begin, end, [&](const pair& p) + { + return p.first == avatar_id && p.second == observer; + }); + + if (it != end) + { + mObservers.erase(it); + } +} + +void LLAvatarPropertiesProcessor::sendRequest(const LLUUID& avatar_id, EAvatarProcessorType type, const std::string &method) +{ + // this is the startup state when send_complete_agent_movement() message is sent. + // Before this messages won't work so don't bother trying + if (LLStartUp::getStartupState() <= STATE_AGENT_SEND) + { + return; + } + + if (avatar_id.isNull()) + { + return; + } + + // Suppress duplicate requests while waiting for a response from the network + if (isPendingRequest(avatar_id, type)) + { + // waiting for a response, don't re-request + return; + } + + // Try to send HTTP request if cap_url is available + if (type == APT_PROPERTIES) + { + std::string cap_url = gAgent.getRegionCapability("AgentProfile"); + if (!cap_url.empty()) + { + initAgentProfileCapRequest(avatar_id, cap_url, type); + } + else + { + // Don't sent UDP request for APT_PROPERTIES + LL_WARNS() << "No cap_url for APT_PROPERTIES, request for " << avatar_id << " is not sent" << LL_ENDL; + } + return; + } + + // Send UDP request + if (type == APT_PROPERTIES_LEGACY) + { + sendAvatarPropertiesRequestMessage(avatar_id); + } + else + { + sendGenericRequest(avatar_id, type, method); + } +} + +void LLAvatarPropertiesProcessor::sendGenericRequest(const LLUUID& avatar_id, EAvatarProcessorType type, const std::string &method) +{ + // indicate we're going to make a request + addPendingRequest(avatar_id, type); + + std::vector strings{ avatar_id.asString() }; + send_generic_message(method, strings); +} + +void LLAvatarPropertiesProcessor::sendAvatarPropertiesRequestMessage(const LLUUID& avatar_id) +{ + addPendingRequest(avatar_id, APT_PROPERTIES_LEGACY); + + LLMessageSystem *msg = gMessageSystem; + + msg->newMessageFast(_PREHASH_AvatarPropertiesRequest); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgentID); + msg->addUUIDFast(_PREHASH_SessionID, gAgentSessionID); + msg->addUUIDFast(_PREHASH_AvatarID, avatar_id); + gAgent.sendReliableMessage(); +} + +void LLAvatarPropertiesProcessor::initAgentProfileCapRequest(const LLUUID& avatar_id, const std::string& cap_url, EAvatarProcessorType type) +{ + addPendingRequest(avatar_id, type); + LLCoros::instance().launch("requestAgentUserInfoCoro", + [cap_url, avatar_id, type]() { requestAvatarPropertiesCoro(cap_url, avatar_id, type); }); +} + +void LLAvatarPropertiesProcessor::sendAvatarPropertiesRequest(const LLUUID& avatar_id) +{ + sendRequest(avatar_id, APT_PROPERTIES, "AvatarPropertiesRequest"); +} + +void LLAvatarPropertiesProcessor::sendAvatarLegacyPropertiesRequest(const LLUUID& avatar_id) +{ + sendRequest(avatar_id, APT_PROPERTIES_LEGACY, "AvatarPropertiesRequest"); +} + +void LLAvatarPropertiesProcessor::sendAvatarTexturesRequest(const LLUUID& avatar_id) +{ + sendGenericRequest(avatar_id, APT_TEXTURES, "avatartexturesrequest"); + // No response expected. + removePendingRequest(avatar_id, APT_TEXTURES); +} + +void LLAvatarPropertiesProcessor::sendAvatarClassifiedsRequest(const LLUUID& avatar_id) +{ + sendGenericRequest(avatar_id, APT_CLASSIFIEDS, "avatarclassifiedsrequest"); +} + +//static +std::string LLAvatarPropertiesProcessor::accountType(const LLAvatarData* avatar_data) +{ + // If you have a special account, like M Linden ("El Jefe!") + // return an untranslated "special" string + if (!avatar_data->caption_text.empty()) + { + return avatar_data->caption_text; + } + const char* const ACCT_TYPE[] = { + "AcctTypeResident", + "AcctTypeTrial", + "AcctTypeCharterMember", + "AcctTypeEmployee" + }; + U8 caption_max = (U8)LL_ARRAY_SIZE(ACCT_TYPE)-1; + U8 caption_index = llclamp(avatar_data->caption_index, (U8)0, caption_max); + return LLTrans::getString(ACCT_TYPE[caption_index]); +} + +//static +std::string LLAvatarPropertiesProcessor::paymentInfo(const LLAvatarData* avatar_data) +{ + // Special accounts like M Linden don't have payment info revealed. + if (!avatar_data->caption_text.empty()) + return ""; + + // Linden employees don't have payment info revealed + constexpr S32 LINDEN_EMPLOYEE_INDEX = 3; + if (avatar_data->caption_index == LINDEN_EMPLOYEE_INDEX) + return ""; + + bool transacted = (avatar_data->flags & AVATAR_TRANSACTED); + bool identified = (avatar_data->flags & AVATAR_IDENTIFIED); + // Not currently getting set in dataserver/lldataavatar.cpp for privacy considerations + //bool age_verified = (avatar_data->flags & AVATAR_AGEVERIFIED); + + const char* payment_text; + if (transacted) + { + payment_text = "PaymentInfoUsed"; + } + else if (identified) + { + payment_text = "PaymentInfoOnFile"; + } + else + { + payment_text = "NoPaymentInfoOnFile"; + } + return LLTrans::getString(payment_text); +} + +//static +bool LLAvatarPropertiesProcessor::hasPaymentInfoOnFile(const LLAvatarData* avatar_data) +{ + // Special accounts like M Linden don't have payment info revealed. + if (!avatar_data->caption_text.empty()) + return true; + + // Linden employees don't have payment info revealed + constexpr S32 LINDEN_EMPLOYEE_INDEX = 3; + if (avatar_data->caption_index == LINDEN_EMPLOYEE_INDEX) + return true; + + return ((avatar_data->flags & AVATAR_TRANSACTED) || (avatar_data->flags & AVATAR_IDENTIFIED)); +} + +// static +void LLAvatarPropertiesProcessor::requestAvatarPropertiesCoro(std::string cap_url, LLUUID avatar_id, EAvatarProcessorType type) +{ + LLAvatarPropertiesProcessor& inst = instance(); + + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("requestAvatarPropertiesCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpHeaders::ptr_t httpHeaders; + + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + httpOpts->setFollowRedirects(true); + + std::string finalUrl = cap_url + "/" + avatar_id.asString(); + + LLSD result = httpAdapter->getAndSuspend(httpRequest, finalUrl, httpOpts, httpHeaders); + + // Response is being processed, no longer pending is required + inst.removePendingRequest(avatar_id, type); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status + || !result.has("id") + || avatar_id != result["id"].asUUID()) + { + LL_WARNS("AvatarProperties") << "Failed to get agent information for id " << avatar_id + << (!status ? " (no HTTP status)" : !result.has("id") ? " (no result.id)" : + std::string(" (result.id=") + result["id"].asUUID().asString() + ")") + << LL_ENDL; + return; + } + + LLAvatarData avatar_data; + + std::string birth_date; + + avatar_data.agent_id = gAgentID; + avatar_data.avatar_id = avatar_id; + avatar_data.image_id = result["sl_image_id"].asUUID(); + avatar_data.fl_image_id = result["fl_image_id"].asUUID(); + avatar_data.partner_id = result["partner_id"].asUUID(); + avatar_data.about_text = result["sl_about_text"].asString(); + avatar_data.fl_about_text = result["fl_about_text"].asString(); + avatar_data.born_on = result["member_since"].asDate(); + // TODO: SL-20163 Remove the "has" check when SRV-684 is done + // and the field "hide_age" is included to the http response + inst.mIsHideAgeSupportedByServer = result.has("hide_age"); + avatar_data.hide_age = inst.isHideAgeSupportedByServer() && result["hide_age"].asBoolean(); + avatar_data.profile_url = getProfileURL(avatar_id.asString()); + avatar_data.customer_type = result["customer_type"].asString(); + avatar_data.notes = result["notes"].asString(); + + avatar_data.flags = 0; + if (result["online"].asBoolean()) + { + avatar_data.flags |= AVATAR_ONLINE; + } + if (result["allow_publish"].asBoolean()) + { + avatar_data.flags |= AVATAR_ALLOW_PUBLISH; + } + if (result["identified"].asBoolean()) + { + avatar_data.flags |= AVATAR_IDENTIFIED; + } + if (result["transacted"].asBoolean()) + { + avatar_data.flags |= AVATAR_TRANSACTED; + } + + avatar_data.caption_index = 0; + if (result.has("charter_member")) // won't be present if "caption" is set + { + avatar_data.caption_index = result["charter_member"].asInteger(); + } + else if (result.has("caption")) + { + avatar_data.caption_text = result["caption"].asString(); + } + + // Groups + LLSD groups_array = result["groups"]; + for (LLSD::array_const_iterator it = groups_array.beginArray(); it != groups_array.endArray(); ++it) + { + const LLSD& group_info = *it; + LLAvatarData::LLGroupData group_data; + group_data.group_powers = 0; // Not in use? + group_data.group_title = group_info["name"].asString(); // Missing data, not in use? + group_data.group_id = group_info["id"].asUUID(); + group_data.group_name = group_info["name"].asString(); + group_data.group_insignia_id = group_info["image_id"].asUUID(); + + avatar_data.group_list.push_back(group_data); + } + + // Picks + LLSD picks_array = result["picks"]; + for (LLSD::array_const_iterator it = picks_array.beginArray(); it != picks_array.endArray(); ++it) + { + const LLSD& pick_data = *it; + avatar_data.picks_list.emplace_back(pick_data["id"].asUUID(), pick_data["name"].asString()); + } + + inst.notifyObservers(avatar_id, &avatar_data, type); +} + +void LLAvatarPropertiesProcessor::processAvatarLegacyPropertiesReply(LLMessageSystem* msg, void**) +{ + LLAvatarLegacyData avatar_data; + std::string birth_date; + + msg->getUUIDFast( _PREHASH_AgentData, _PREHASH_AgentID, avatar_data.agent_id); + msg->getUUIDFast( _PREHASH_AgentData, _PREHASH_AvatarID, avatar_data.avatar_id); + msg->getUUIDFast( _PREHASH_PropertiesData, _PREHASH_ImageID, avatar_data.image_id); + msg->getUUIDFast( _PREHASH_PropertiesData, _PREHASH_FLImageID, avatar_data.fl_image_id); + msg->getUUIDFast( _PREHASH_PropertiesData, _PREHASH_PartnerID, avatar_data.partner_id); + msg->getStringFast( _PREHASH_PropertiesData, _PREHASH_AboutText, avatar_data.about_text); + msg->getStringFast( _PREHASH_PropertiesData, _PREHASH_FLAboutText, avatar_data.fl_about_text); + msg->getStringFast( _PREHASH_PropertiesData, _PREHASH_BornOn, birth_date); + msg->getString( _PREHASH_PropertiesData, _PREHASH_ProfileURL, avatar_data.profile_url); + msg->getU32Fast( _PREHASH_PropertiesData, _PREHASH_Flags, avatar_data.flags); + + LLDateUtil::dateFromPDTString(avatar_data.born_on, birth_date); + avatar_data.caption_index = 0; + + S32 charter_member_size = 0; + charter_member_size = msg->getSize(_PREHASH_PropertiesData, _PREHASH_CharterMember); + if (1 == charter_member_size) + { + msg->getBinaryData(_PREHASH_PropertiesData, _PREHASH_CharterMember, &avatar_data.caption_index, 1); + } + else if (1 < charter_member_size) + { + msg->getString(_PREHASH_PropertiesData, _PREHASH_CharterMember, avatar_data.caption_text); + } + LLAvatarPropertiesProcessor* self = getInstance(); + // Request processed, no longer pending + self->removePendingRequest(avatar_data.avatar_id, APT_PROPERTIES_LEGACY); + self->notifyObservers(avatar_data.avatar_id, &avatar_data, APT_PROPERTIES_LEGACY); +} + +void LLAvatarPropertiesProcessor::processAvatarInterestsReply(LLMessageSystem* msg, void**) +{ +/* + AvatarInterestsReply is automatically sent by the server in response to the + AvatarPropertiesRequest (in addition to the AvatarPropertiesReply message). + If the interests panel is no longer part of the design (?) we should just register the message + to a handler function that does nothing. + That will suppress the warnings and be compatible with old server versions. + WARNING: LLTemplateMessageReader::decodeData: Message from 216.82.37.237:13000 with no handler function received: AvatarInterestsReply +*/ +} + +void LLAvatarPropertiesProcessor::processAvatarClassifiedsReply(LLMessageSystem* msg, void**) +{ + LLAvatarClassifieds classifieds; + + msg->getUUID(_PREHASH_AgentData, _PREHASH_AgentID, classifieds.agent_id); + msg->getUUID(_PREHASH_AgentData, _PREHASH_TargetID, classifieds.target_id); + + S32 block_count = msg->getNumberOfBlocks(_PREHASH_Data); + + for(int n = 0; n < block_count; ++n) + { + LLAvatarClassifieds::classified_data data; + + msg->getUUID(_PREHASH_Data, _PREHASH_ClassifiedID, data.classified_id, n); + msg->getString(_PREHASH_Data, _PREHASH_Name, data.name, n); + + classifieds.classifieds_list.emplace_back(data); + } + + LLAvatarPropertiesProcessor* self = getInstance(); + // Request processed, no longer pending + self->removePendingRequest(classifieds.target_id, APT_CLASSIFIEDS); + self->notifyObservers(classifieds.target_id,&classifieds,APT_CLASSIFIEDS); +} + +void LLAvatarPropertiesProcessor::processClassifiedInfoReply(LLMessageSystem* msg, void**) +{ + LLAvatarClassifiedInfo c_info; + + msg->getUUID(_PREHASH_AgentData, _PREHASH_AgentID, c_info.agent_id); + + msg->getUUID(_PREHASH_Data, _PREHASH_ClassifiedID, c_info.classified_id); + msg->getUUID(_PREHASH_Data, _PREHASH_CreatorID, c_info.creator_id); + msg->getU32(_PREHASH_Data, _PREHASH_CreationDate, c_info.creation_date); + msg->getU32(_PREHASH_Data, _PREHASH_ExpirationDate, c_info.expiration_date); + msg->getU32(_PREHASH_Data, _PREHASH_Category, c_info.category); + msg->getString(_PREHASH_Data, _PREHASH_Name, c_info.name); + msg->getString(_PREHASH_Data, _PREHASH_Desc, c_info.description); + msg->getUUID(_PREHASH_Data, _PREHASH_ParcelID, c_info.parcel_id); + msg->getU32(_PREHASH_Data, _PREHASH_ParentEstate, c_info.parent_estate); + msg->getUUID(_PREHASH_Data, _PREHASH_SnapshotID, c_info.snapshot_id); + msg->getString(_PREHASH_Data, _PREHASH_SimName, c_info.sim_name); + msg->getVector3d(_PREHASH_Data, _PREHASH_PosGlobal, c_info.pos_global); + msg->getString(_PREHASH_Data, _PREHASH_ParcelName, c_info.parcel_name); + msg->getU8(_PREHASH_Data, _PREHASH_ClassifiedFlags, c_info.flags); + msg->getS32(_PREHASH_Data, _PREHASH_PriceForListing, c_info.price_for_listing); + + LLAvatarPropertiesProcessor* self = getInstance(); + // Request processed, no longer pending + self->removePendingRequest(c_info.creator_id, APT_CLASSIFIED_INFO); + self->notifyObservers(c_info.creator_id, &c_info, APT_CLASSIFIED_INFO); +} + + +void LLAvatarPropertiesProcessor::processAvatarNotesReply(LLMessageSystem* msg, void**) +{ + // Deprecated, new "AgentProfile" allows larger notes +} + +void LLAvatarPropertiesProcessor::processAvatarPicksReply(LLMessageSystem* msg, void**) +{ + LLUUID agent_id; + LLUUID target_id; + msg->getUUID(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); + msg->getUUID(_PREHASH_AgentData, _PREHASH_TargetID, target_id); + + LL_DEBUGS("AvatarProperties") << "Received AvatarPicksReply for " << target_id << LL_ENDL; +} + +void LLAvatarPropertiesProcessor::processPickInfoReply(LLMessageSystem* msg, void**) +{ + LLPickData pick_data; + + // Extract the agent id and verify the message is for this + // client. + msg->getUUID(_PREHASH_AgentData, _PREHASH_AgentID, pick_data.agent_id ); + msg->getUUID(_PREHASH_Data, _PREHASH_PickID, pick_data.pick_id); + msg->getUUID(_PREHASH_Data, _PREHASH_CreatorID, pick_data.creator_id); + + // ** top_pick should be deleted, not being used anymore - angela + msg->getBOOL(_PREHASH_Data, _PREHASH_TopPick, pick_data.top_pick); + msg->getUUID(_PREHASH_Data, _PREHASH_ParcelID, pick_data.parcel_id); + msg->getString(_PREHASH_Data, _PREHASH_Name, pick_data.name); + msg->getString(_PREHASH_Data, _PREHASH_Desc, pick_data.desc); + msg->getUUID(_PREHASH_Data, _PREHASH_SnapshotID, pick_data.snapshot_id); + + msg->getString(_PREHASH_Data, _PREHASH_User, pick_data.user_name); + msg->getString(_PREHASH_Data, _PREHASH_OriginalName, pick_data.original_name); + msg->getString(_PREHASH_Data, _PREHASH_SimName, pick_data.sim_name); + msg->getVector3d(_PREHASH_Data, _PREHASH_PosGlobal, pick_data.pos_global); + + msg->getS32(_PREHASH_Data, _PREHASH_SortOrder, pick_data.sort_order); + msg->getBOOL(_PREHASH_Data, _PREHASH_Enabled, pick_data.enabled); + + LLAvatarPropertiesProcessor* self = getInstance(); + // don't need to remove pending request as we don't track pick info + self->notifyObservers(pick_data.creator_id, &pick_data, APT_PICK_INFO); +} + +void LLAvatarPropertiesProcessor::processAvatarGroupsReply(LLMessageSystem* msg, void**) +{ + /* + AvatarGroupsReply is automatically sent by the server in response to the + AvatarPropertiesRequest in addition to the AvatarPropertiesReply message. + */ + LLUUID agent_id; + LLUUID avatar_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AvatarID, avatar_id); + + LL_DEBUGS("AvatarProperties") << "Received AvatarGroupsReply for " << avatar_id << LL_ENDL; +} + +void LLAvatarPropertiesProcessor::notifyObservers(const LLUUID& id, void* data, EAvatarProcessorType type) +{ + // Copy the map (because observers may delete themselves when updated?) + LLAvatarPropertiesProcessor::observer_multimap_t observers = mObservers; + + for (const auto& [agent_id, observer] : observers) + { + // only notify observers for the same agent, or if the observer + // didn't know the agent ID and passed a NULL id. + if (agent_id == id || agent_id.isNull()) + { + observer->processProperties(data, type); + } + } +} + +void LLAvatarPropertiesProcessor::sendFriendRights(const LLUUID& avatar_id, S32 rights) +{ + if(!avatar_id.isNull()) + { + LLMessageSystem* msg = gMessageSystem; + + // setup message header + msg->newMessageFast(_PREHASH_GrantUserRights); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUID(_PREHASH_AgentID, gAgentID); + msg->addUUID(_PREHASH_SessionID, gAgentSessionID); + + msg->nextBlockFast(_PREHASH_Rights); + msg->addUUID(_PREHASH_AgentRelated, avatar_id); + msg->addS32(_PREHASH_RelatedRights, rights); + + gAgent.sendReliableMessage(); + } +} + +void LLAvatarPropertiesProcessor::sendPickDelete( const LLUUID& pick_id ) +{ + LLMessageSystem* msg = gMessageSystem; + msg->newMessage(_PREHASH_PickDelete); + msg->nextBlock(_PREHASH_AgentData); + msg->addUUID(_PREHASH_AgentID, gAgentID); + msg->addUUID(_PREHASH_SessionID, gAgentSessionID); + msg->nextBlock(_PREHASH_Data); + msg->addUUID(_PREHASH_PickID, pick_id); + gAgent.sendReliableMessage(); + + LLAgentPicksInfo::getInstance()->requestNumberOfPicks(); + LLAgentPicksInfo::getInstance()->decrementNumberOfPicks(); +} + +void LLAvatarPropertiesProcessor::sendClassifiedDelete(const LLUUID& classified_id) +{ + LLMessageSystem* msg = gMessageSystem; + + msg->newMessage(_PREHASH_ClassifiedDelete); + + msg->nextBlock(_PREHASH_AgentData); + msg->addUUID(_PREHASH_AgentID, gAgentID); + msg->addUUID(_PREHASH_SessionID, gAgentSessionID); + + msg->nextBlock(_PREHASH_Data); + msg->addUUID(_PREHASH_ClassifiedID, classified_id); + + gAgent.sendReliableMessage(); +} + +void LLAvatarPropertiesProcessor::sendPickInfoUpdate(const LLPickData* new_pick) +{ + if (!new_pick) + return; + + LLMessageSystem* msg = gMessageSystem; + + msg->newMessage(_PREHASH_PickInfoUpdate); + msg->nextBlock(_PREHASH_AgentData); + msg->addUUID(_PREHASH_AgentID, gAgentID); + msg->addUUID(_PREHASH_SessionID, gAgentSessionID); + + msg->nextBlock(_PREHASH_Data); + msg->addUUID(_PREHASH_PickID, new_pick->pick_id); + msg->addUUID(_PREHASH_CreatorID, new_pick->creator_id); + + //legacy var need to be deleted + msg->addBOOL(_PREHASH_TopPick, false); + + // fills in on simulator if null + msg->addUUID(_PREHASH_ParcelID, new_pick->parcel_id); + msg->addString(_PREHASH_Name, new_pick->name); + msg->addString(_PREHASH_Desc, new_pick->desc); + msg->addUUID(_PREHASH_SnapshotID, new_pick->snapshot_id); + msg->addVector3d(_PREHASH_PosGlobal, new_pick->pos_global); + + // Only top picks have a sort order + msg->addS32(_PREHASH_SortOrder, 0); + + msg->addBOOL(_PREHASH_Enabled, new_pick->enabled); + gAgent.sendReliableMessage(); + + LLAgentPicksInfo::getInstance()->requestNumberOfPicks(); +} + +void LLAvatarPropertiesProcessor::sendClassifiedInfoUpdate(const LLAvatarClassifiedInfo* c_data) +{ + if(!c_data) + { + return; + } + + LLMessageSystem* msg = gMessageSystem; + + msg->newMessage(_PREHASH_ClassifiedInfoUpdate); + + msg->nextBlock(_PREHASH_AgentData); + msg->addUUID(_PREHASH_AgentID, gAgentID); + msg->addUUID(_PREHASH_SessionID, gAgentSessionID); + + msg->nextBlock(_PREHASH_Data); + msg->addUUID(_PREHASH_ClassifiedID, c_data->classified_id); + msg->addU32(_PREHASH_Category, c_data->category); + msg->addString(_PREHASH_Name, c_data->name); + msg->addString(_PREHASH_Desc, c_data->description); + msg->addUUID(_PREHASH_ParcelID, c_data->parcel_id); + msg->addU32(_PREHASH_ParentEstate, 0); + msg->addUUID(_PREHASH_SnapshotID, c_data->snapshot_id); + msg->addVector3d(_PREHASH_PosGlobal, c_data->pos_global); + msg->addU8(_PREHASH_ClassifiedFlags, c_data->flags); + msg->addS32(_PREHASH_PriceForListing, c_data->price_for_listing); + + gAgent.sendReliableMessage(); +} + +void LLAvatarPropertiesProcessor::sendPickInfoRequest(const LLUUID& creator_id, const LLUUID& pick_id) +{ + // Must ask for a pick based on the creator id because + // the pick database is distributed to the inventory cluster. JC + std::vector request_params{ creator_id.asString(), pick_id.asString() }; + send_generic_message("pickinforequest", request_params); +} + +void LLAvatarPropertiesProcessor::sendClassifiedInfoRequest(const LLUUID& classified_id) +{ + LLMessageSystem* msg = gMessageSystem; + + msg->newMessage(_PREHASH_ClassifiedInfoRequest); + msg->nextBlock(_PREHASH_AgentData); + + msg->addUUID(_PREHASH_AgentID, gAgentID); + msg->addUUID(_PREHASH_SessionID, gAgentSessionID); + + msg->nextBlock(_PREHASH_Data); + msg->addUUID(_PREHASH_ClassifiedID, classified_id); + + gAgent.sendReliableMessage(); +} + +bool LLAvatarPropertiesProcessor::isPendingRequest(const LLUUID& avatar_id, EAvatarProcessorType type) +{ + timestamp_map_t::key_type key = std::make_pair(avatar_id, type); + timestamp_map_t::iterator it = mRequestTimestamps.find(key); + + // Is this a new request? + if (it == mRequestTimestamps.end()) return false; + + // We found a request, check if it has timed out + U32 now = time(nullptr); + const U32 REQUEST_EXPIRE_SECS = 5; + U32 expires = it->second + REQUEST_EXPIRE_SECS; + + // Request is still pending if it hasn't expired yet + // *NOTE: Expired requests will accumulate in this map, but they are rare, + // the data is small, and they will be updated if the same data is + // re-requested + return (now < expires); +} + +void LLAvatarPropertiesProcessor::addPendingRequest(const LLUUID& avatar_id, EAvatarProcessorType type) +{ + timestamp_map_t::key_type key = std::make_pair(avatar_id, type); + U32 now = time(nullptr); + // Add or update existing (expired) request + mRequestTimestamps[ key ] = now; +} + +void LLAvatarPropertiesProcessor::removePendingRequest(const LLUUID& avatar_id, EAvatarProcessorType type) +{ + timestamp_map_t::key_type key = std::make_pair(avatar_id, type); + mRequestTimestamps.erase(key); +} diff --git a/indra/newview/llblockedlistitem.cpp b/indra/newview/llblockedlistitem.cpp index 18adada47e..2a0255fb10 100644 --- a/indra/newview/llblockedlistitem.cpp +++ b/indra/newview/llblockedlistitem.cpp @@ -1,114 +1,114 @@ -/** - * @file llviewerobjectlistitem.cpp - * @brief viewer object list item implementation - * - * Class LLPanelInventoryListItemBase displays inventory item as an element - * of LLInventoryItemsList. - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llblockedlistitem.h" - -// llui -#include "lliconctrl.h" -#include "lltextbox.h" -#include "lltextutil.h" - -// newview -#include "llavatariconctrl.h" -#include "llgroupiconctrl.h" -#include "llinventoryicon.h" -#include "llviewerobject.h" - -LLBlockedListItem::LLBlockedListItem(const LLMute* item) -: LLPanel(), - mItemID(item->mID), - mItemName(item->mName), - mMuteType(item->mType) -{ - buildFromFile("panel_blocked_list_item.xml"); -} - -bool LLBlockedListItem::postBuild() -{ - mTitleCtrl = getChild("item_name"); - mTitleCtrl->setValue(mItemName); - - switch (mMuteType) - { - case LLMute::AGENT: - case LLMute::EXTERNAL: - { - LLAvatarIconCtrl* avatar_icon = getChild("avatar_icon"); - avatar_icon->setVisible(true); - avatar_icon->setValue(mItemID); - } - break; - case LLMute::GROUP: - { - LLGroupIconCtrl* group_icon = getChild("group_icon"); - group_icon->setVisible(true); - group_icon->setValue(mItemID); - } - break; - case LLMute::OBJECT: - case LLMute::BY_NAME: - getChild("object_icon")->setVisible(true); - break; - - default: - break; - } - - return true; -} - -void LLBlockedListItem::onMouseEnter(S32 x, S32 y, MASK mask) -{ - getChildView("hovered_icon")->setVisible(true); - LLPanel::onMouseEnter(x, y, mask); -} - -void LLBlockedListItem::onMouseLeave(S32 x, S32 y, MASK mask) -{ - getChildView("hovered_icon")->setVisible(false); - LLPanel::onMouseLeave(x, y, mask); -} - -void LLBlockedListItem::setValue(const LLSD& value) -{ - if (!value.isMap() || !value.has("selected")) - { - return; - } - - getChildView("selected_icon")->setVisible(value["selected"]); -} - -void LLBlockedListItem::highlightName(const std::string& highlited_text) -{ - LLStyle::Params params; - LLTextUtil::textboxSetHighlightedVal(mTitleCtrl, params, mItemName, highlited_text); -} +/** + * @file llviewerobjectlistitem.cpp + * @brief viewer object list item implementation + * + * Class LLPanelInventoryListItemBase displays inventory item as an element + * of LLInventoryItemsList. + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llblockedlistitem.h" + +// llui +#include "lliconctrl.h" +#include "lltextbox.h" +#include "lltextutil.h" + +// newview +#include "llavatariconctrl.h" +#include "llgroupiconctrl.h" +#include "llinventoryicon.h" +#include "llviewerobject.h" + +LLBlockedListItem::LLBlockedListItem(const LLMute* item) +: LLPanel(), + mItemID(item->mID), + mItemName(item->mName), + mMuteType(item->mType) +{ + buildFromFile("panel_blocked_list_item.xml"); +} + +bool LLBlockedListItem::postBuild() +{ + mTitleCtrl = getChild("item_name"); + mTitleCtrl->setValue(mItemName); + + switch (mMuteType) + { + case LLMute::AGENT: + case LLMute::EXTERNAL: + { + LLAvatarIconCtrl* avatar_icon = getChild("avatar_icon"); + avatar_icon->setVisible(true); + avatar_icon->setValue(mItemID); + } + break; + case LLMute::GROUP: + { + LLGroupIconCtrl* group_icon = getChild("group_icon"); + group_icon->setVisible(true); + group_icon->setValue(mItemID); + } + break; + case LLMute::OBJECT: + case LLMute::BY_NAME: + getChild("object_icon")->setVisible(true); + break; + + default: + break; + } + + return true; +} + +void LLBlockedListItem::onMouseEnter(S32 x, S32 y, MASK mask) +{ + getChildView("hovered_icon")->setVisible(true); + LLPanel::onMouseEnter(x, y, mask); +} + +void LLBlockedListItem::onMouseLeave(S32 x, S32 y, MASK mask) +{ + getChildView("hovered_icon")->setVisible(false); + LLPanel::onMouseLeave(x, y, mask); +} + +void LLBlockedListItem::setValue(const LLSD& value) +{ + if (!value.isMap() || !value.has("selected")) + { + return; + } + + getChildView("selected_icon")->setVisible(value["selected"]); +} + +void LLBlockedListItem::highlightName(const std::string& highlited_text) +{ + LLStyle::Params params; + LLTextUtil::textboxSetHighlightedVal(mTitleCtrl, params, mItemName, highlited_text); +} diff --git a/indra/newview/llblockedlistitem.h b/indra/newview/llblockedlistitem.h index 52925fd26d..01894ad11a 100644 --- a/indra/newview/llblockedlistitem.h +++ b/indra/newview/llblockedlistitem.h @@ -1,73 +1,73 @@ -/** - * @file llviewerobjectlistitem.h - * @brief viewer object list item header file - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ -#ifndef LLVIEWEROBJECTLISTITEM_H_ -#define LLVIEWEROBJECTLISTITEM_H_ - -#include "llmutelist.h" -#include "llpanel.h" -#include "llstyle.h" -#include "lltextbox.h" -#include "lliconctrl.h" - -/** - * This class represents items of LLBlockList, which represents - * contents of LLMuteList. LLMuteList "consists" of LLMute items. - * Each LLMute represents either blocked avatar or object and - * stores info about mute type (avatar or object) - * - * Each item consists if object/avatar icon and object/avatar name - * - * To create a blocked list item just need to pass LLMute pointer - * and appropriate block list item will be created depending on - * LLMute type (LLMute::EType) and other LLMute's info - */ -class LLBlockedListItem : public LLPanel -{ -public: - - LLBlockedListItem(const LLMute* item); - virtual bool postBuild(); - - void onMouseEnter(S32 x, S32 y, MASK mask); - void onMouseLeave(S32 x, S32 y, MASK mask); - - virtual void setValue(const LLSD& value); - - void highlightName(const std::string& highlited_text); - const std::string& getName() const { return mItemName; } - const LLMute::EType& getType() const { return mMuteType; } - const LLUUID& getUUID() const { return mItemID; } - -private: - - LLTextBox* mTitleCtrl; - const LLUUID mItemID; - std::string mItemName; - LLMute::EType mMuteType; - -}; - -#endif /* LLVIEWEROBJECTLISTITEM_H_ */ +/** + * @file llviewerobjectlistitem.h + * @brief viewer object list item header file + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#ifndef LLVIEWEROBJECTLISTITEM_H_ +#define LLVIEWEROBJECTLISTITEM_H_ + +#include "llmutelist.h" +#include "llpanel.h" +#include "llstyle.h" +#include "lltextbox.h" +#include "lliconctrl.h" + +/** + * This class represents items of LLBlockList, which represents + * contents of LLMuteList. LLMuteList "consists" of LLMute items. + * Each LLMute represents either blocked avatar or object and + * stores info about mute type (avatar or object) + * + * Each item consists if object/avatar icon and object/avatar name + * + * To create a blocked list item just need to pass LLMute pointer + * and appropriate block list item will be created depending on + * LLMute type (LLMute::EType) and other LLMute's info + */ +class LLBlockedListItem : public LLPanel +{ +public: + + LLBlockedListItem(const LLMute* item); + virtual bool postBuild(); + + void onMouseEnter(S32 x, S32 y, MASK mask); + void onMouseLeave(S32 x, S32 y, MASK mask); + + virtual void setValue(const LLSD& value); + + void highlightName(const std::string& highlited_text); + const std::string& getName() const { return mItemName; } + const LLMute::EType& getType() const { return mMuteType; } + const LLUUID& getUUID() const { return mItemID; } + +private: + + LLTextBox* mTitleCtrl; + const LLUUID mItemID; + std::string mItemName; + LLMute::EType mMuteType; + +}; + +#endif /* LLVIEWEROBJECTLISTITEM_H_ */ diff --git a/indra/newview/llblocklist.cpp b/indra/newview/llblocklist.cpp index f9e5ce9e04..3821559b9e 100644 --- a/indra/newview/llblocklist.cpp +++ b/indra/newview/llblocklist.cpp @@ -1,463 +1,463 @@ -/** - * @file llblocklist.cpp - * @brief List of the blocked avatars and objects. - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llblocklist.h" - -#include "llavataractions.h" -#include "llblockedlistitem.h" -#include "llfloatersidepanelcontainer.h" -#include "llviewermenu.h" - -static LLDefaultChildRegistry::Register r("block_list"); - -static const LLBlockListNameComparator NAME_COMPARATOR; -static const LLBlockListNameTypeComparator NAME_TYPE_COMPARATOR; - -LLBlockList::LLBlockList(const Params& p) -: LLFlatListViewEx(p), - mDirty(true), - mShouldAddAll(true), - mActionType(NONE), - mMuteListSize(0) -{ - - LLMuteList::getInstance()->addObserver(this); - mMuteListSize = LLMuteList::getInstance()->getMutes().size(); - - // Set up context menu. - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; - - registrar.add ("Block.Action", boost::bind(&LLBlockList::onCustomAction, this, _2)); - enable_registrar.add("Block.Enable", boost::bind(&LLBlockList::isActionEnabled, this, _2)); - enable_registrar.add("Block.Check", boost::bind(&LLBlockList::isMenuItemChecked, this, _2)); - enable_registrar.add("Block.Visible", boost::bind(&LLBlockList::isMenuItemVisible, this, _2)); - - LLToggleableMenu* context_menu = LLUICtrlFactory::getInstance()->createFromFile( - "menu_people_blocked_gear.xml", - gMenuHolder, - LLViewerMenuHolderGL::child_registry_t::instance()); - if(context_menu) - { - mContextMenu = context_menu->getHandle(); - } -} - -LLBlockList::~LLBlockList() -{ - if (mContextMenu.get()) - { - mContextMenu.get()->die(); - } - - LLMuteList::getInstance()->removeObserver(this); -} - -void LLBlockList::createList() -{ - std::vector mutes = LLMuteList::instance().getMutes(); - std::vector::const_iterator mute_it = mutes.begin(); - - for (; mute_it != mutes.end(); ++mute_it) - { - addNewItem(&*mute_it); - } -} - -BlockListActionType LLBlockList::getCurrentMuteListActionType() -{ - BlockListActionType type = NONE; - U32 curSize = LLMuteList::getInstance()->getMutes().size(); - if( curSize > mMuteListSize) - type = ADD; - else if(curSize < mMuteListSize) - type = REMOVE; - - return type; -} - -void LLBlockList::onChangeDetailed(const LLMute &mute) -{ - mActionType = getCurrentMuteListActionType(); - - mCurItemId = mute.mID; - mCurItemName = mute.mName; - mCurItemType = mute.mType; - mCurItemFlags = mute.mFlags; - - refresh(); -} - -bool LLBlockList::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = LLUICtrl::handleRightMouseDown(x, y, mask); - - LLToggleableMenu* context_menu = mContextMenu.get(); - if (context_menu && size()) - { - context_menu->buildDrawLabels(); - context_menu->updateParent(LLMenuGL::sMenuContainer); - LLMenuGL::showPopup(this, context_menu, x, y); - } - - return handled; -} - -void LLBlockList::removeListItem(const LLMute* mute) -{ - if (mute->mID.notNull()) - { - removeItemByUUID(mute->mID); - } - else - { - removeItemByValue(mute->mName); - } -} - -void LLBlockList::hideListItem(LLBlockedListItem* item, bool show) -{ - item->setVisible(show); -} - -void LLBlockList::setNameFilter(const std::string& filter) -{ - std::string filter_upper = filter; - LLStringUtil::toUpper(filter_upper); - if (mNameFilter != filter_upper) - { - mNameFilter = filter_upper; - setDirty(); - } -} - -void LLBlockList::sortByName() -{ - setComparator(&NAME_COMPARATOR); - sort(); -} - -void LLBlockList::sortByType() -{ - setComparator(&NAME_TYPE_COMPARATOR); - sort(); -} - -void LLBlockList::draw() -{ - if (mDirty) - { - refresh(); - } - - LLFlatListView::draw(); -} - -void LLBlockList::addNewItem(const LLMute* mute) -{ - LLBlockedListItem* item = new LLBlockedListItem(mute); - if (!mNameFilter.empty()) - { - item->highlightName(mNameFilter); - } - if (item->getUUID().notNull()) - { - addItem(item, item->getUUID(), ADD_BOTTOM); - } - else - { - addItem(item, item->getName(), ADD_BOTTOM); - } -} - -void LLBlockList::refresh() -{ - bool have_filter = !mNameFilter.empty(); - - // save selection to restore it after list rebuilt - LLSD selected = getSelectedValue(); - LLSD next_selected; - - if(mShouldAddAll) // creating list of blockers - { - clear(); - createList(); - mShouldAddAll = false; - } - else - { - // handle remove/add functionality - LLMute mute(mCurItemId, mCurItemName, mCurItemType, mCurItemFlags); - if(mActionType == ADD) - { - addNewItem(&mute); - } - else if(mActionType == REMOVE) - { - if ((mute.mID.notNull() && selected.isUUID() && selected.asUUID() == mute.mID) - || (mute.mID.isNull() && selected.isString() && selected.asString() == mute.mName)) - { - // we are going to remove currently selected item, so select next item and save the selection to restore it - if (!selectNextItemPair(false, true)) - { - selectNextItemPair(true, true); - } - next_selected = getSelectedValue(); - } - removeListItem(&mute); - } - mActionType = NONE; - } - - // handle filter functionality - if(have_filter || (!have_filter && !mPrevNameFilter.empty())) - { - // we should update visibility of our items if previous filter was not empty - std::vector < LLPanel* > allItems; - getItems(allItems); - std::vector < LLPanel* >::iterator it = allItems.begin(); - - for(; it != allItems.end() ; ++it) - { - LLBlockedListItem * curItem = dynamic_cast (*it); - if(curItem) - { - hideListItem(curItem, findInsensitive(curItem->getName(), mNameFilter)); - } - } - } - mPrevNameFilter = mNameFilter; - - if (selected.isDefined()) - { - if (getItemPair(selected)) - { - // restore previously selected item - selectItemPair(getItemPair(selected), true); - } - else if (next_selected.isDefined() && getItemPair(next_selected)) - { - // previously selected item was removed, so select next item - selectItemPair(getItemPair(next_selected), true); - } - } - mMuteListSize = LLMuteList::getInstance()->getMutes().size(); - - // Sort the list. - sort(); - - setDirty(false); -} - -bool LLBlockList::findInsensitive(std::string haystack, const std::string& needle_upper) -{ - LLStringUtil::toUpper(haystack); - return haystack.find(needle_upper) != std::string::npos; -} - -LLBlockedListItem* LLBlockList::getBlockedItem() const -{ - LLPanel* panel = LLFlatListView::getSelectedItem(); - LLBlockedListItem* item = dynamic_cast(panel); - return item; -} - -bool LLBlockList::isActionEnabled(const LLSD& userdata) -{ - bool action_enabled = true; - - const std::string command_name = userdata.asString(); - - if ("profile_item" == command_name - || "block_voice" == command_name - || "block_text" == command_name - || "block_particles" == command_name - || "block_obj_sounds" == command_name) - { - LLBlockedListItem* item = getBlockedItem(); - action_enabled = item && (LLMute::AGENT == item->getType()); - } - - if ("unblock_item" == command_name) - { - action_enabled = getSelectedItem() != NULL; - } - - return action_enabled; -} - -void LLBlockList::onCustomAction(const LLSD& userdata) -{ - if (!isActionEnabled(userdata)) - { - return; - } - - LLBlockedListItem* item = getBlockedItem(); - const std::string command_name = userdata.asString(); - - if ("unblock_item" == command_name) - { - LLMute mute(item->getUUID(), item->getName()); - LLMuteList::getInstance()->remove(mute); - } - else if ("profile_item" == command_name) - { - switch(item->getType()) - { - - case LLMute::AGENT: - LLAvatarActions::showProfile(item->getUUID()); - break; - - default: - break; - } - } - else if ("block_voice" == command_name) - { - toggleMute(LLMute::flagVoiceChat); - } - else if ("block_text" == command_name) - { - toggleMute(LLMute::flagTextChat); - } - else if ("block_particles" == command_name) - { - toggleMute(LLMute::flagParticles); - } - else if ("block_obj_sounds" == command_name) - { - toggleMute(LLMute::flagObjectSounds); - } -} - -bool LLBlockList::isMenuItemChecked(const LLSD& userdata) -{ - LLBlockedListItem* item = getBlockedItem(); - if (!item) - { - return false; - } - - const std::string command_name = userdata.asString(); - - if ("block_voice" == command_name) - { - return LLMuteList::getInstance()->isMuted(item->getUUID(), LLMute::flagVoiceChat); - } - else if ("block_text" == command_name) - { - return LLMuteList::getInstance()->isMuted(item->getUUID(), LLMute::flagTextChat); - } - else if ("block_particles" == command_name) - { - return LLMuteList::getInstance()->isMuted(item->getUUID(), LLMute::flagParticles); - } - else if ("block_obj_sounds" == command_name) - { - return LLMuteList::getInstance()->isMuted(item->getUUID(), LLMute::flagObjectSounds); - } - - return false; -} - -bool LLBlockList::isMenuItemVisible(const LLSD& userdata) -{ - LLBlockedListItem* item = getBlockedItem(); - const std::string command_name = userdata.asString(); - - if ("block_voice" == command_name - || "block_text" == command_name - || "block_particles" == command_name - || "block_obj_sounds" == command_name) - { - return item && (LLMute::AGENT == item->getType()); - } - - return false; -} - -void LLBlockList::toggleMute(U32 flags) -{ - LLBlockedListItem* item = getBlockedItem(); - LLMute mute(item->getUUID(), item->getName(), item->getType()); - - if (!LLMuteList::getInstance()->isMuted(item->getUUID(), flags)) - { - LLMuteList::getInstance()->add(mute, flags); - } - else - { - LLMuteList::getInstance()->remove(mute, flags); - } -} - -bool LLBlockListItemComparator::compare(const LLPanel* item1, const LLPanel* item2) const -{ - const LLBlockedListItem* blocked_item1 = dynamic_cast(item1); - const LLBlockedListItem* blocked_item2 = dynamic_cast(item2); - - if (!blocked_item1 || !blocked_item2) - { - LL_ERRS() << "blocked_item1 and blocked_item2 cannot be null" << LL_ENDL; - return true; - } - - return doCompare(blocked_item1, blocked_item2); -} - -bool LLBlockListNameComparator::doCompare(const LLBlockedListItem* blocked_item1, const LLBlockedListItem* blocked_item2) const -{ - std::string name1 = blocked_item1->getName(); - std::string name2 = blocked_item2->getName(); - - LLStringUtil::toUpper(name1); - LLStringUtil::toUpper(name2); - - return name1 < name2; -} - -bool LLBlockListNameTypeComparator::doCompare(const LLBlockedListItem* blocked_item1, const LLBlockedListItem* blocked_item2) const -{ - LLMute::EType type1 = blocked_item1->getType(); - LLMute::EType type2 = blocked_item2->getType(); - - // if mute type is LLMute::BY_NAME or LLMute::OBJECT it means that this mute is an object - bool both_mutes_are_objects = (LLMute::OBJECT == type1 || LLMute::BY_NAME == type1) && (LLMute::OBJECT == type2 || LLMute::BY_NAME == type2); - - // mute types may be different, but since both LLMute::BY_NAME and LLMute::OBJECT types represent objects - // it's needed to perform additional checking of both_mutes_are_objects variable - if (type1 != type2 && !both_mutes_are_objects) - { - // objects in block list go first, so return true if mute type is not an avatar - return LLMute::AGENT != type1; - } - - return NAME_COMPARATOR.compare(blocked_item1, blocked_item2); -} +/** + * @file llblocklist.cpp + * @brief List of the blocked avatars and objects. + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llblocklist.h" + +#include "llavataractions.h" +#include "llblockedlistitem.h" +#include "llfloatersidepanelcontainer.h" +#include "llviewermenu.h" + +static LLDefaultChildRegistry::Register r("block_list"); + +static const LLBlockListNameComparator NAME_COMPARATOR; +static const LLBlockListNameTypeComparator NAME_TYPE_COMPARATOR; + +LLBlockList::LLBlockList(const Params& p) +: LLFlatListViewEx(p), + mDirty(true), + mShouldAddAll(true), + mActionType(NONE), + mMuteListSize(0) +{ + + LLMuteList::getInstance()->addObserver(this); + mMuteListSize = LLMuteList::getInstance()->getMutes().size(); + + // Set up context menu. + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + + registrar.add ("Block.Action", boost::bind(&LLBlockList::onCustomAction, this, _2)); + enable_registrar.add("Block.Enable", boost::bind(&LLBlockList::isActionEnabled, this, _2)); + enable_registrar.add("Block.Check", boost::bind(&LLBlockList::isMenuItemChecked, this, _2)); + enable_registrar.add("Block.Visible", boost::bind(&LLBlockList::isMenuItemVisible, this, _2)); + + LLToggleableMenu* context_menu = LLUICtrlFactory::getInstance()->createFromFile( + "menu_people_blocked_gear.xml", + gMenuHolder, + LLViewerMenuHolderGL::child_registry_t::instance()); + if(context_menu) + { + mContextMenu = context_menu->getHandle(); + } +} + +LLBlockList::~LLBlockList() +{ + if (mContextMenu.get()) + { + mContextMenu.get()->die(); + } + + LLMuteList::getInstance()->removeObserver(this); +} + +void LLBlockList::createList() +{ + std::vector mutes = LLMuteList::instance().getMutes(); + std::vector::const_iterator mute_it = mutes.begin(); + + for (; mute_it != mutes.end(); ++mute_it) + { + addNewItem(&*mute_it); + } +} + +BlockListActionType LLBlockList::getCurrentMuteListActionType() +{ + BlockListActionType type = NONE; + U32 curSize = LLMuteList::getInstance()->getMutes().size(); + if( curSize > mMuteListSize) + type = ADD; + else if(curSize < mMuteListSize) + type = REMOVE; + + return type; +} + +void LLBlockList::onChangeDetailed(const LLMute &mute) +{ + mActionType = getCurrentMuteListActionType(); + + mCurItemId = mute.mID; + mCurItemName = mute.mName; + mCurItemType = mute.mType; + mCurItemFlags = mute.mFlags; + + refresh(); +} + +bool LLBlockList::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = LLUICtrl::handleRightMouseDown(x, y, mask); + + LLToggleableMenu* context_menu = mContextMenu.get(); + if (context_menu && size()) + { + context_menu->buildDrawLabels(); + context_menu->updateParent(LLMenuGL::sMenuContainer); + LLMenuGL::showPopup(this, context_menu, x, y); + } + + return handled; +} + +void LLBlockList::removeListItem(const LLMute* mute) +{ + if (mute->mID.notNull()) + { + removeItemByUUID(mute->mID); + } + else + { + removeItemByValue(mute->mName); + } +} + +void LLBlockList::hideListItem(LLBlockedListItem* item, bool show) +{ + item->setVisible(show); +} + +void LLBlockList::setNameFilter(const std::string& filter) +{ + std::string filter_upper = filter; + LLStringUtil::toUpper(filter_upper); + if (mNameFilter != filter_upper) + { + mNameFilter = filter_upper; + setDirty(); + } +} + +void LLBlockList::sortByName() +{ + setComparator(&NAME_COMPARATOR); + sort(); +} + +void LLBlockList::sortByType() +{ + setComparator(&NAME_TYPE_COMPARATOR); + sort(); +} + +void LLBlockList::draw() +{ + if (mDirty) + { + refresh(); + } + + LLFlatListView::draw(); +} + +void LLBlockList::addNewItem(const LLMute* mute) +{ + LLBlockedListItem* item = new LLBlockedListItem(mute); + if (!mNameFilter.empty()) + { + item->highlightName(mNameFilter); + } + if (item->getUUID().notNull()) + { + addItem(item, item->getUUID(), ADD_BOTTOM); + } + else + { + addItem(item, item->getName(), ADD_BOTTOM); + } +} + +void LLBlockList::refresh() +{ + bool have_filter = !mNameFilter.empty(); + + // save selection to restore it after list rebuilt + LLSD selected = getSelectedValue(); + LLSD next_selected; + + if(mShouldAddAll) // creating list of blockers + { + clear(); + createList(); + mShouldAddAll = false; + } + else + { + // handle remove/add functionality + LLMute mute(mCurItemId, mCurItemName, mCurItemType, mCurItemFlags); + if(mActionType == ADD) + { + addNewItem(&mute); + } + else if(mActionType == REMOVE) + { + if ((mute.mID.notNull() && selected.isUUID() && selected.asUUID() == mute.mID) + || (mute.mID.isNull() && selected.isString() && selected.asString() == mute.mName)) + { + // we are going to remove currently selected item, so select next item and save the selection to restore it + if (!selectNextItemPair(false, true)) + { + selectNextItemPair(true, true); + } + next_selected = getSelectedValue(); + } + removeListItem(&mute); + } + mActionType = NONE; + } + + // handle filter functionality + if(have_filter || (!have_filter && !mPrevNameFilter.empty())) + { + // we should update visibility of our items if previous filter was not empty + std::vector < LLPanel* > allItems; + getItems(allItems); + std::vector < LLPanel* >::iterator it = allItems.begin(); + + for(; it != allItems.end() ; ++it) + { + LLBlockedListItem * curItem = dynamic_cast (*it); + if(curItem) + { + hideListItem(curItem, findInsensitive(curItem->getName(), mNameFilter)); + } + } + } + mPrevNameFilter = mNameFilter; + + if (selected.isDefined()) + { + if (getItemPair(selected)) + { + // restore previously selected item + selectItemPair(getItemPair(selected), true); + } + else if (next_selected.isDefined() && getItemPair(next_selected)) + { + // previously selected item was removed, so select next item + selectItemPair(getItemPair(next_selected), true); + } + } + mMuteListSize = LLMuteList::getInstance()->getMutes().size(); + + // Sort the list. + sort(); + + setDirty(false); +} + +bool LLBlockList::findInsensitive(std::string haystack, const std::string& needle_upper) +{ + LLStringUtil::toUpper(haystack); + return haystack.find(needle_upper) != std::string::npos; +} + +LLBlockedListItem* LLBlockList::getBlockedItem() const +{ + LLPanel* panel = LLFlatListView::getSelectedItem(); + LLBlockedListItem* item = dynamic_cast(panel); + return item; +} + +bool LLBlockList::isActionEnabled(const LLSD& userdata) +{ + bool action_enabled = true; + + const std::string command_name = userdata.asString(); + + if ("profile_item" == command_name + || "block_voice" == command_name + || "block_text" == command_name + || "block_particles" == command_name + || "block_obj_sounds" == command_name) + { + LLBlockedListItem* item = getBlockedItem(); + action_enabled = item && (LLMute::AGENT == item->getType()); + } + + if ("unblock_item" == command_name) + { + action_enabled = getSelectedItem() != NULL; + } + + return action_enabled; +} + +void LLBlockList::onCustomAction(const LLSD& userdata) +{ + if (!isActionEnabled(userdata)) + { + return; + } + + LLBlockedListItem* item = getBlockedItem(); + const std::string command_name = userdata.asString(); + + if ("unblock_item" == command_name) + { + LLMute mute(item->getUUID(), item->getName()); + LLMuteList::getInstance()->remove(mute); + } + else if ("profile_item" == command_name) + { + switch(item->getType()) + { + + case LLMute::AGENT: + LLAvatarActions::showProfile(item->getUUID()); + break; + + default: + break; + } + } + else if ("block_voice" == command_name) + { + toggleMute(LLMute::flagVoiceChat); + } + else if ("block_text" == command_name) + { + toggleMute(LLMute::flagTextChat); + } + else if ("block_particles" == command_name) + { + toggleMute(LLMute::flagParticles); + } + else if ("block_obj_sounds" == command_name) + { + toggleMute(LLMute::flagObjectSounds); + } +} + +bool LLBlockList::isMenuItemChecked(const LLSD& userdata) +{ + LLBlockedListItem* item = getBlockedItem(); + if (!item) + { + return false; + } + + const std::string command_name = userdata.asString(); + + if ("block_voice" == command_name) + { + return LLMuteList::getInstance()->isMuted(item->getUUID(), LLMute::flagVoiceChat); + } + else if ("block_text" == command_name) + { + return LLMuteList::getInstance()->isMuted(item->getUUID(), LLMute::flagTextChat); + } + else if ("block_particles" == command_name) + { + return LLMuteList::getInstance()->isMuted(item->getUUID(), LLMute::flagParticles); + } + else if ("block_obj_sounds" == command_name) + { + return LLMuteList::getInstance()->isMuted(item->getUUID(), LLMute::flagObjectSounds); + } + + return false; +} + +bool LLBlockList::isMenuItemVisible(const LLSD& userdata) +{ + LLBlockedListItem* item = getBlockedItem(); + const std::string command_name = userdata.asString(); + + if ("block_voice" == command_name + || "block_text" == command_name + || "block_particles" == command_name + || "block_obj_sounds" == command_name) + { + return item && (LLMute::AGENT == item->getType()); + } + + return false; +} + +void LLBlockList::toggleMute(U32 flags) +{ + LLBlockedListItem* item = getBlockedItem(); + LLMute mute(item->getUUID(), item->getName(), item->getType()); + + if (!LLMuteList::getInstance()->isMuted(item->getUUID(), flags)) + { + LLMuteList::getInstance()->add(mute, flags); + } + else + { + LLMuteList::getInstance()->remove(mute, flags); + } +} + +bool LLBlockListItemComparator::compare(const LLPanel* item1, const LLPanel* item2) const +{ + const LLBlockedListItem* blocked_item1 = dynamic_cast(item1); + const LLBlockedListItem* blocked_item2 = dynamic_cast(item2); + + if (!blocked_item1 || !blocked_item2) + { + LL_ERRS() << "blocked_item1 and blocked_item2 cannot be null" << LL_ENDL; + return true; + } + + return doCompare(blocked_item1, blocked_item2); +} + +bool LLBlockListNameComparator::doCompare(const LLBlockedListItem* blocked_item1, const LLBlockedListItem* blocked_item2) const +{ + std::string name1 = blocked_item1->getName(); + std::string name2 = blocked_item2->getName(); + + LLStringUtil::toUpper(name1); + LLStringUtil::toUpper(name2); + + return name1 < name2; +} + +bool LLBlockListNameTypeComparator::doCompare(const LLBlockedListItem* blocked_item1, const LLBlockedListItem* blocked_item2) const +{ + LLMute::EType type1 = blocked_item1->getType(); + LLMute::EType type2 = blocked_item2->getType(); + + // if mute type is LLMute::BY_NAME or LLMute::OBJECT it means that this mute is an object + bool both_mutes_are_objects = (LLMute::OBJECT == type1 || LLMute::BY_NAME == type1) && (LLMute::OBJECT == type2 || LLMute::BY_NAME == type2); + + // mute types may be different, but since both LLMute::BY_NAME and LLMute::OBJECT types represent objects + // it's needed to perform additional checking of both_mutes_are_objects variable + if (type1 != type2 && !both_mutes_are_objects) + { + // objects in block list go first, so return true if mute type is not an avatar + return LLMute::AGENT != type1; + } + + return NAME_COMPARATOR.compare(blocked_item1, blocked_item2); +} diff --git a/indra/newview/llblocklist.h b/indra/newview/llblocklist.h index dd75b949e7..64e8246f43 100644 --- a/indra/newview/llblocklist.h +++ b/indra/newview/llblocklist.h @@ -1,159 +1,159 @@ -/** - * @file llblocklist.h - * @brief List of the blocked avatars and objects. - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ -#ifndef LLBLOCKLIST_H_ -#define LLBLOCKLIST_H_ - -#include "llflatlistview.h" -#include "lllistcontextmenu.h" -#include "llmutelist.h" -#include "lltoggleablemenu.h" - -class LLBlockedListItem; -class LLMute; - -enum BlockListActionType {NONE, ADD, REMOVE}; - -/** - * List of blocked avatars and objects. - * This list represents contents of the LLMuteList. - * Each change in LLMuteList leads to rebuilding this list, so - * it's always in actual state. - */ -class LLBlockList: public LLFlatListViewEx, public LLMuteListObserver -{ - LOG_CLASS(LLBlockList); -public: - struct Params : public LLInitParam::Block - { - Params(){}; - }; - - LLBlockList(const Params& p); - virtual ~LLBlockList(); - - virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); - LLToggleableMenu* getContextMenu() const { return mContextMenu.get(); } - LLBlockedListItem* getBlockedItem() const; - - virtual void onChange() { } - virtual void onChangeDetailed(const LLMute& ); - virtual void draw(); - - void setNameFilter(const std::string& filter); - void sortByName(); - void sortByType(); - void refresh(); - - U32 getMuteListSize() { return mMuteListSize; } - -private: - - void addNewItem(const LLMute* mute); - void removeListItem(const LLMute* mute); - void hideListItem(LLBlockedListItem* item, bool show); - void setDirty(bool dirty = true) { mDirty = dirty; } - bool findInsensitive(std::string haystack, const std::string& needle_upper); - - bool isActionEnabled(const LLSD& userdata); - void onCustomAction (const LLSD& userdata); - bool isMenuItemChecked(const LLSD& userdata); - bool isMenuItemVisible(const LLSD& userdata); - void toggleMute(U32 flags); - void createList(); - - BlockListActionType getCurrentMuteListActionType(); - - LLHandle mContextMenu; - - std::string mNameFilter; - bool mDirty; - bool mShouldAddAll; - BlockListActionType mActionType; - U32 mMuteListSize; - - // This data is used to save information about item that currently changed(added or removed) - LLUUID mCurItemId; - std::string mCurItemName; - LLMute::EType mCurItemType; - U32 mCurItemFlags; - std::string mPrevNameFilter; - -}; - - -/* - * Abstract comparator for blocked items - */ -class LLBlockListItemComparator : public LLFlatListView::ItemComparator -{ - LOG_CLASS(LLBlockListItemComparator); - -public: - LLBlockListItemComparator() {}; - virtual ~LLBlockListItemComparator() {}; - - virtual bool compare(const LLPanel* item1, const LLPanel* item2) const; - -protected: - - virtual bool doCompare(const LLBlockedListItem* blocked_item1, const LLBlockedListItem* blocked_item2) const = 0; -}; - - -/* - * Compares items by name - */ -class LLBlockListNameComparator : public LLBlockListItemComparator -{ - LOG_CLASS(LLBlockListNameComparator); - -public: - LLBlockListNameComparator() {}; - virtual ~LLBlockListNameComparator() {}; - -protected: - - virtual bool doCompare(const LLBlockedListItem* blocked_item1, const LLBlockedListItem* blocked_item2) const; -}; - -/* - * Compares items by type and then by name within type - * Objects come first then avatars - */ -class LLBlockListNameTypeComparator : public LLBlockListItemComparator -{ - LOG_CLASS(LLBlockListNameTypeComparator); - -public: - LLBlockListNameTypeComparator() {}; - virtual ~LLBlockListNameTypeComparator() {}; - -protected: - - virtual bool doCompare(const LLBlockedListItem* blocked_item1, const LLBlockedListItem* blocked_item2) const; -}; - -#endif /* LLBLOCKLIST_H_ */ +/** + * @file llblocklist.h + * @brief List of the blocked avatars and objects. + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#ifndef LLBLOCKLIST_H_ +#define LLBLOCKLIST_H_ + +#include "llflatlistview.h" +#include "lllistcontextmenu.h" +#include "llmutelist.h" +#include "lltoggleablemenu.h" + +class LLBlockedListItem; +class LLMute; + +enum BlockListActionType {NONE, ADD, REMOVE}; + +/** + * List of blocked avatars and objects. + * This list represents contents of the LLMuteList. + * Each change in LLMuteList leads to rebuilding this list, so + * it's always in actual state. + */ +class LLBlockList: public LLFlatListViewEx, public LLMuteListObserver +{ + LOG_CLASS(LLBlockList); +public: + struct Params : public LLInitParam::Block + { + Params(){}; + }; + + LLBlockList(const Params& p); + virtual ~LLBlockList(); + + virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); + LLToggleableMenu* getContextMenu() const { return mContextMenu.get(); } + LLBlockedListItem* getBlockedItem() const; + + virtual void onChange() { } + virtual void onChangeDetailed(const LLMute& ); + virtual void draw(); + + void setNameFilter(const std::string& filter); + void sortByName(); + void sortByType(); + void refresh(); + + U32 getMuteListSize() { return mMuteListSize; } + +private: + + void addNewItem(const LLMute* mute); + void removeListItem(const LLMute* mute); + void hideListItem(LLBlockedListItem* item, bool show); + void setDirty(bool dirty = true) { mDirty = dirty; } + bool findInsensitive(std::string haystack, const std::string& needle_upper); + + bool isActionEnabled(const LLSD& userdata); + void onCustomAction (const LLSD& userdata); + bool isMenuItemChecked(const LLSD& userdata); + bool isMenuItemVisible(const LLSD& userdata); + void toggleMute(U32 flags); + void createList(); + + BlockListActionType getCurrentMuteListActionType(); + + LLHandle mContextMenu; + + std::string mNameFilter; + bool mDirty; + bool mShouldAddAll; + BlockListActionType mActionType; + U32 mMuteListSize; + + // This data is used to save information about item that currently changed(added or removed) + LLUUID mCurItemId; + std::string mCurItemName; + LLMute::EType mCurItemType; + U32 mCurItemFlags; + std::string mPrevNameFilter; + +}; + + +/* + * Abstract comparator for blocked items + */ +class LLBlockListItemComparator : public LLFlatListView::ItemComparator +{ + LOG_CLASS(LLBlockListItemComparator); + +public: + LLBlockListItemComparator() {}; + virtual ~LLBlockListItemComparator() {}; + + virtual bool compare(const LLPanel* item1, const LLPanel* item2) const; + +protected: + + virtual bool doCompare(const LLBlockedListItem* blocked_item1, const LLBlockedListItem* blocked_item2) const = 0; +}; + + +/* + * Compares items by name + */ +class LLBlockListNameComparator : public LLBlockListItemComparator +{ + LOG_CLASS(LLBlockListNameComparator); + +public: + LLBlockListNameComparator() {}; + virtual ~LLBlockListNameComparator() {}; + +protected: + + virtual bool doCompare(const LLBlockedListItem* blocked_item1, const LLBlockedListItem* blocked_item2) const; +}; + +/* + * Compares items by type and then by name within type + * Objects come first then avatars + */ +class LLBlockListNameTypeComparator : public LLBlockListItemComparator +{ + LOG_CLASS(LLBlockListNameTypeComparator); + +public: + LLBlockListNameTypeComparator() {}; + virtual ~LLBlockListNameTypeComparator() {}; + +protected: + + virtual bool doCompare(const LLBlockedListItem* blocked_item1, const LLBlockedListItem* blocked_item2) const; +}; + +#endif /* LLBLOCKLIST_H_ */ diff --git a/indra/newview/llbuycurrencyhtml.cpp b/indra/newview/llbuycurrencyhtml.cpp index 75bf2e4ff6..e6c4d44573 100644 --- a/indra/newview/llbuycurrencyhtml.cpp +++ b/indra/newview/llbuycurrencyhtml.cpp @@ -1,167 +1,167 @@ -/** - * @file llbuycurrencyhtml.cpp - * @brief Manages Buy Currency HTML floater - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterbuycurrency.h" -#include "llbuycurrencyhtml.h" -#include "llfloaterbuycurrencyhtml.h" - -#include "llfloaterreg.h" -#include "llcommandhandler.h" -#include "llviewercontrol.h" -#include "llstatusbar.h" - -// support for secondlife:///app/buycurrencyhtml/{ACTION}/{NEXT_ACTION}/{RETURN_CODE} SLapps -class LLBuyCurrencyHTMLHandler : - public LLCommandHandler -{ -public: - // requests will be throttled from a non-trusted browser - LLBuyCurrencyHTMLHandler() : LLCommandHandler( "buycurrencyhtml", UNTRUSTED_THROTTLE) {} - - bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) - { - std::string action( "" ); - if ( params.size() >= 1 ) - { - action = params[ 0 ].asString(); - }; - - std::string next_action( "" ); - if ( params.size() >= 2 ) - { - next_action = params[ 1 ].asString(); - }; - - int result_code = 0; - if ( params.size() >= 3 ) - { - result_code = params[ 2 ].asInteger(); - if ( result_code != 0 ) - { - LL_WARNS("LLBuyCurrency") << "Received nonzero result code: " << result_code << LL_ENDL ; - } - }; - - // open the legacy XUI based currency floater - if ( "open_legacy" == next_action ) - { - LLFloaterBuyCurrency::buyCurrency(); - }; - - // ask the Buy Currency floater to close - // note: this is the last thing we can do so make - // sure any other actions are processed before this. - if ( "close" == action ) - { - LLBuyCurrencyHTML::closeDialog(); - }; - - return true; - }; -}; -LLBuyCurrencyHTMLHandler gBuyCurrencyHTMLHandler; - -//////////////////////////////////////////////////////////////////////////////// -// static -// Opens the legacy XUI based floater or new HTML based one based on -// the QuickBuyCurrency value in settings.xml - this overload is for -// the case where the amount is not requested. -void LLBuyCurrencyHTML::openCurrencyFloater() -{ - if ( gSavedSettings.getBOOL( "QuickBuyCurrency" ) ) - { - // HTML version - LLBuyCurrencyHTML::showDialog( false, "", 0 ); - } - else - { - // legacy version - LLFloaterBuyCurrency::buyCurrency(); - }; -} - -//////////////////////////////////////////////////////////////////////////////// -// static -// Opens the legacy XUI based floater or new HTML based one based on -// the QuickBuyCurrency value in settings.xml - this overload is for -// the case where the amount and a string to display are requested. -void LLBuyCurrencyHTML::openCurrencyFloater( const std::string& message, S32 sum ) -{ - if ( gSavedSettings.getBOOL( "QuickBuyCurrency" ) ) - { - // HTML version - LLBuyCurrencyHTML::showDialog( true, message, sum ); - } - else - { - // legacy version - LLFloaterBuyCurrency::buyCurrency( message, sum ); - }; -} - -//////////////////////////////////////////////////////////////////////////////// -// static -void LLBuyCurrencyHTML::showDialog( bool specific_sum_requested, const std::string& message, S32 sum ) -{ - LLFloaterBuyCurrencyHTML* buy_currency_floater = dynamic_cast< LLFloaterBuyCurrencyHTML* >( LLFloaterReg::getInstance( "buy_currency_html" ) ); - if ( buy_currency_floater ) - { - // pass on flag indicating if we want to buy specific amount and if so, how much - buy_currency_floater->setParams( specific_sum_requested, message, sum ); - - // force navigate to new URL - buy_currency_floater->navigateToFinalURL(); - - // make it visible and raise to front - bool visible = true; - buy_currency_floater->setVisible( visible ); - bool take_focus = true; - buy_currency_floater->setFrontmost( take_focus ); - - // spec calls for floater to be centered on client window - buy_currency_floater->center(); - } - else - { - LL_WARNS() << "Buy Currency (HTML) Floater not found" << LL_ENDL; - }; -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLBuyCurrencyHTML::closeDialog() -{ - LLFloaterBuyCurrencyHTML* buy_currency_floater = dynamic_cast< LLFloaterBuyCurrencyHTML* >(LLFloaterReg::getInstance( "buy_currency_html" ) ); - if ( buy_currency_floater ) - { - buy_currency_floater->closeFloater(); - }; - - // Update L$ balance in the status bar in case L$ were purchased - LLStatusBar::sendMoneyBalanceRequest(); -} +/** + * @file llbuycurrencyhtml.cpp + * @brief Manages Buy Currency HTML floater + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterbuycurrency.h" +#include "llbuycurrencyhtml.h" +#include "llfloaterbuycurrencyhtml.h" + +#include "llfloaterreg.h" +#include "llcommandhandler.h" +#include "llviewercontrol.h" +#include "llstatusbar.h" + +// support for secondlife:///app/buycurrencyhtml/{ACTION}/{NEXT_ACTION}/{RETURN_CODE} SLapps +class LLBuyCurrencyHTMLHandler : + public LLCommandHandler +{ +public: + // requests will be throttled from a non-trusted browser + LLBuyCurrencyHTMLHandler() : LLCommandHandler( "buycurrencyhtml", UNTRUSTED_THROTTLE) {} + + bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) + { + std::string action( "" ); + if ( params.size() >= 1 ) + { + action = params[ 0 ].asString(); + }; + + std::string next_action( "" ); + if ( params.size() >= 2 ) + { + next_action = params[ 1 ].asString(); + }; + + int result_code = 0; + if ( params.size() >= 3 ) + { + result_code = params[ 2 ].asInteger(); + if ( result_code != 0 ) + { + LL_WARNS("LLBuyCurrency") << "Received nonzero result code: " << result_code << LL_ENDL ; + } + }; + + // open the legacy XUI based currency floater + if ( "open_legacy" == next_action ) + { + LLFloaterBuyCurrency::buyCurrency(); + }; + + // ask the Buy Currency floater to close + // note: this is the last thing we can do so make + // sure any other actions are processed before this. + if ( "close" == action ) + { + LLBuyCurrencyHTML::closeDialog(); + }; + + return true; + }; +}; +LLBuyCurrencyHTMLHandler gBuyCurrencyHTMLHandler; + +//////////////////////////////////////////////////////////////////////////////// +// static +// Opens the legacy XUI based floater or new HTML based one based on +// the QuickBuyCurrency value in settings.xml - this overload is for +// the case where the amount is not requested. +void LLBuyCurrencyHTML::openCurrencyFloater() +{ + if ( gSavedSettings.getBOOL( "QuickBuyCurrency" ) ) + { + // HTML version + LLBuyCurrencyHTML::showDialog( false, "", 0 ); + } + else + { + // legacy version + LLFloaterBuyCurrency::buyCurrency(); + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// static +// Opens the legacy XUI based floater or new HTML based one based on +// the QuickBuyCurrency value in settings.xml - this overload is for +// the case where the amount and a string to display are requested. +void LLBuyCurrencyHTML::openCurrencyFloater( const std::string& message, S32 sum ) +{ + if ( gSavedSettings.getBOOL( "QuickBuyCurrency" ) ) + { + // HTML version + LLBuyCurrencyHTML::showDialog( true, message, sum ); + } + else + { + // legacy version + LLFloaterBuyCurrency::buyCurrency( message, sum ); + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// static +void LLBuyCurrencyHTML::showDialog( bool specific_sum_requested, const std::string& message, S32 sum ) +{ + LLFloaterBuyCurrencyHTML* buy_currency_floater = dynamic_cast< LLFloaterBuyCurrencyHTML* >( LLFloaterReg::getInstance( "buy_currency_html" ) ); + if ( buy_currency_floater ) + { + // pass on flag indicating if we want to buy specific amount and if so, how much + buy_currency_floater->setParams( specific_sum_requested, message, sum ); + + // force navigate to new URL + buy_currency_floater->navigateToFinalURL(); + + // make it visible and raise to front + bool visible = true; + buy_currency_floater->setVisible( visible ); + bool take_focus = true; + buy_currency_floater->setFrontmost( take_focus ); + + // spec calls for floater to be centered on client window + buy_currency_floater->center(); + } + else + { + LL_WARNS() << "Buy Currency (HTML) Floater not found" << LL_ENDL; + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLBuyCurrencyHTML::closeDialog() +{ + LLFloaterBuyCurrencyHTML* buy_currency_floater = dynamic_cast< LLFloaterBuyCurrencyHTML* >(LLFloaterReg::getInstance( "buy_currency_html" ) ); + if ( buy_currency_floater ) + { + buy_currency_floater->closeFloater(); + }; + + // Update L$ balance in the status bar in case L$ were purchased + LLStatusBar::sendMoneyBalanceRequest(); +} diff --git a/indra/newview/llcallbacklist.cpp b/indra/newview/llcallbacklist.cpp index 2231d07c23..c474f2885b 100644 --- a/indra/newview/llcallbacklist.cpp +++ b/indra/newview/llcallbacklist.cpp @@ -1,305 +1,305 @@ -/** - * @file llcallbacklist.cpp - * @brief A simple list of callback functions to call. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llcallbacklist.h" -#include "lleventtimer.h" - -// Library includes -#include "llerror.h" - - -// -// Globals -// -LLCallbackList gIdleCallbacks; - -// -// Member functions -// - -LLCallbackList::LLCallbackList() -{ - // nothing -} - -LLCallbackList::~LLCallbackList() -{ -} - - -void LLCallbackList::addFunction( callback_t func, void *data) -{ - if (!func) - { - LL_ERRS() << "LLCallbackList::addFunction - function is NULL" << LL_ENDL; - return; - } - - // only add one callback per func/data pair - callback_pair_t t(func, data); - callback_list_t::iterator iter = std::find(mCallbackList.begin(), mCallbackList.end(), t); - if (iter == mCallbackList.end()) - { - mCallbackList.push_back(t); - } -} - - -BOOL LLCallbackList::containsFunction( callback_t func, void *data) -{ - callback_pair_t t(func, data); - callback_list_t::iterator iter = std::find(mCallbackList.begin(), mCallbackList.end(), t); - if (iter != mCallbackList.end()) - { - return TRUE; - } - else - { - return FALSE; - } -} - - -BOOL LLCallbackList::deleteFunction( callback_t func, void *data) -{ - callback_pair_t t(func, data); - callback_list_t::iterator iter = std::find(mCallbackList.begin(), mCallbackList.end(), t); - if (iter != mCallbackList.end()) - { - mCallbackList.erase(iter); - return TRUE; - } - else - { - return FALSE; - } -} - - -void LLCallbackList::deleteAllFunctions() -{ - mCallbackList.clear(); -} - - -void LLCallbackList::callFunctions() -{ - for (callback_list_t::iterator iter = mCallbackList.begin(); iter != mCallbackList.end(); ) - { - callback_list_t::iterator curiter = iter++; - curiter->first(curiter->second); - } -} - -// Shim class to allow arbitrary boost::bind -// expressions to be run as one-time idle callbacks. -class OnIdleCallbackOneTime -{ -public: - OnIdleCallbackOneTime(nullary_func_t callable): - mCallable(callable) - { - } - static void onIdle(void *data) - { - gIdleCallbacks.deleteFunction(onIdle, data); - OnIdleCallbackOneTime* self = reinterpret_cast(data); - self->call(); - delete self; - } - void call() - { - mCallable(); - } -private: - nullary_func_t mCallable; -}; - -void doOnIdleOneTime(nullary_func_t callable) -{ - OnIdleCallbackOneTime* cb_functor = new OnIdleCallbackOneTime(callable); - gIdleCallbacks.addFunction(&OnIdleCallbackOneTime::onIdle,cb_functor); -} - -// Shim class to allow generic boost functions to be run as -// recurring idle callbacks. Callable should return true when done, -// false to continue getting called. -class OnIdleCallbackRepeating -{ -public: - OnIdleCallbackRepeating(bool_func_t callable): - mCallable(callable) - { - } - // Will keep getting called until the callable returns true. - static void onIdle(void *data) - { - OnIdleCallbackRepeating* self = reinterpret_cast(data); - bool done = self->call(); - if (done) - { - gIdleCallbacks.deleteFunction(onIdle, data); - delete self; - } - } - bool call() - { - return mCallable(); - } -private: - bool_func_t mCallable; -}; - -void doOnIdleRepeating(bool_func_t callable) -{ - OnIdleCallbackRepeating* cb_functor = new OnIdleCallbackRepeating(callable); - gIdleCallbacks.addFunction(&OnIdleCallbackRepeating::onIdle,cb_functor); -} - -class NullaryFuncEventTimer: public LLEventTimer -{ -public: - NullaryFuncEventTimer(nullary_func_t callable, F32 seconds): - LLEventTimer(seconds), - mCallable(callable) - { - } - -private: - bool tick() - { - mCallable(); - return true; - } - - nullary_func_t mCallable; -}; - -// Call a given callable once after specified interval. -void doAfterInterval(nullary_func_t callable, F32 seconds) -{ - new NullaryFuncEventTimer(callable, seconds); -} - -class BoolFuncEventTimer: public LLEventTimer -{ -public: - BoolFuncEventTimer(bool_func_t callable, F32 seconds): - LLEventTimer(seconds), - mCallable(callable) - { - } -private: - bool tick() - { - return mCallable(); - } - - bool_func_t mCallable; -}; - -// Call a given callable every specified number of seconds, until it returns true. -void doPeriodically(bool_func_t callable, F32 seconds) -{ - new BoolFuncEventTimer(callable, seconds); -} - -#ifdef _DEBUG - -void test1(void *data) -{ - S32 *s32_data = (S32 *)data; - LL_INFOS() << "testfunc1 " << *s32_data << LL_ENDL; -} - - -void test2(void *data) -{ - S32 *s32_data = (S32 *)data; - LL_INFOS() << "testfunc2 " << *s32_data << LL_ENDL; -} - - -void -LLCallbackList::test() -{ - S32 a = 1; - S32 b = 2; - LLCallbackList *list = new LLCallbackList; - - LL_INFOS() << "Testing LLCallbackList" << LL_ENDL; - - if (!list->deleteFunction(NULL)) - { - LL_INFOS() << "passed 1" << LL_ENDL; - } - else - { - LL_INFOS() << "error, removed function from empty list" << LL_ENDL; - } - - // LL_INFOS() << "This should crash" << LL_ENDL; - // list->addFunction(NULL); - - list->addFunction(&test1, &a); - list->addFunction(&test1, &a); - - LL_INFOS() << "Expect: test1 1, test1 1" << LL_ENDL; - list->callFunctions(); - - list->addFunction(&test1, &b); - list->addFunction(&test2, &b); - - LL_INFOS() << "Expect: test1 1, test1 1, test1 2, test2 2" << LL_ENDL; - list->callFunctions(); - - if (list->deleteFunction(&test1, &b)) - { - LL_INFOS() << "passed 3" << LL_ENDL; - } - else - { - LL_INFOS() << "error removing function" << LL_ENDL; - } - - LL_INFOS() << "Expect: test1 1, test1 1, test2 2" << LL_ENDL; - list->callFunctions(); - - list->deleteAllFunctions(); - - LL_INFOS() << "Expect nothing" << LL_ENDL; - list->callFunctions(); - - LL_INFOS() << "nothing :-)" << LL_ENDL; - - delete list; - - LL_INFOS() << "test complete" << LL_ENDL; -} - -#endif // _DEBUG +/** + * @file llcallbacklist.cpp + * @brief A simple list of callback functions to call. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llcallbacklist.h" +#include "lleventtimer.h" + +// Library includes +#include "llerror.h" + + +// +// Globals +// +LLCallbackList gIdleCallbacks; + +// +// Member functions +// + +LLCallbackList::LLCallbackList() +{ + // nothing +} + +LLCallbackList::~LLCallbackList() +{ +} + + +void LLCallbackList::addFunction( callback_t func, void *data) +{ + if (!func) + { + LL_ERRS() << "LLCallbackList::addFunction - function is NULL" << LL_ENDL; + return; + } + + // only add one callback per func/data pair + callback_pair_t t(func, data); + callback_list_t::iterator iter = std::find(mCallbackList.begin(), mCallbackList.end(), t); + if (iter == mCallbackList.end()) + { + mCallbackList.push_back(t); + } +} + + +BOOL LLCallbackList::containsFunction( callback_t func, void *data) +{ + callback_pair_t t(func, data); + callback_list_t::iterator iter = std::find(mCallbackList.begin(), mCallbackList.end(), t); + if (iter != mCallbackList.end()) + { + return TRUE; + } + else + { + return FALSE; + } +} + + +BOOL LLCallbackList::deleteFunction( callback_t func, void *data) +{ + callback_pair_t t(func, data); + callback_list_t::iterator iter = std::find(mCallbackList.begin(), mCallbackList.end(), t); + if (iter != mCallbackList.end()) + { + mCallbackList.erase(iter); + return TRUE; + } + else + { + return FALSE; + } +} + + +void LLCallbackList::deleteAllFunctions() +{ + mCallbackList.clear(); +} + + +void LLCallbackList::callFunctions() +{ + for (callback_list_t::iterator iter = mCallbackList.begin(); iter != mCallbackList.end(); ) + { + callback_list_t::iterator curiter = iter++; + curiter->first(curiter->second); + } +} + +// Shim class to allow arbitrary boost::bind +// expressions to be run as one-time idle callbacks. +class OnIdleCallbackOneTime +{ +public: + OnIdleCallbackOneTime(nullary_func_t callable): + mCallable(callable) + { + } + static void onIdle(void *data) + { + gIdleCallbacks.deleteFunction(onIdle, data); + OnIdleCallbackOneTime* self = reinterpret_cast(data); + self->call(); + delete self; + } + void call() + { + mCallable(); + } +private: + nullary_func_t mCallable; +}; + +void doOnIdleOneTime(nullary_func_t callable) +{ + OnIdleCallbackOneTime* cb_functor = new OnIdleCallbackOneTime(callable); + gIdleCallbacks.addFunction(&OnIdleCallbackOneTime::onIdle,cb_functor); +} + +// Shim class to allow generic boost functions to be run as +// recurring idle callbacks. Callable should return true when done, +// false to continue getting called. +class OnIdleCallbackRepeating +{ +public: + OnIdleCallbackRepeating(bool_func_t callable): + mCallable(callable) + { + } + // Will keep getting called until the callable returns true. + static void onIdle(void *data) + { + OnIdleCallbackRepeating* self = reinterpret_cast(data); + bool done = self->call(); + if (done) + { + gIdleCallbacks.deleteFunction(onIdle, data); + delete self; + } + } + bool call() + { + return mCallable(); + } +private: + bool_func_t mCallable; +}; + +void doOnIdleRepeating(bool_func_t callable) +{ + OnIdleCallbackRepeating* cb_functor = new OnIdleCallbackRepeating(callable); + gIdleCallbacks.addFunction(&OnIdleCallbackRepeating::onIdle,cb_functor); +} + +class NullaryFuncEventTimer: public LLEventTimer +{ +public: + NullaryFuncEventTimer(nullary_func_t callable, F32 seconds): + LLEventTimer(seconds), + mCallable(callable) + { + } + +private: + bool tick() + { + mCallable(); + return true; + } + + nullary_func_t mCallable; +}; + +// Call a given callable once after specified interval. +void doAfterInterval(nullary_func_t callable, F32 seconds) +{ + new NullaryFuncEventTimer(callable, seconds); +} + +class BoolFuncEventTimer: public LLEventTimer +{ +public: + BoolFuncEventTimer(bool_func_t callable, F32 seconds): + LLEventTimer(seconds), + mCallable(callable) + { + } +private: + bool tick() + { + return mCallable(); + } + + bool_func_t mCallable; +}; + +// Call a given callable every specified number of seconds, until it returns true. +void doPeriodically(bool_func_t callable, F32 seconds) +{ + new BoolFuncEventTimer(callable, seconds); +} + +#ifdef _DEBUG + +void test1(void *data) +{ + S32 *s32_data = (S32 *)data; + LL_INFOS() << "testfunc1 " << *s32_data << LL_ENDL; +} + + +void test2(void *data) +{ + S32 *s32_data = (S32 *)data; + LL_INFOS() << "testfunc2 " << *s32_data << LL_ENDL; +} + + +void +LLCallbackList::test() +{ + S32 a = 1; + S32 b = 2; + LLCallbackList *list = new LLCallbackList; + + LL_INFOS() << "Testing LLCallbackList" << LL_ENDL; + + if (!list->deleteFunction(NULL)) + { + LL_INFOS() << "passed 1" << LL_ENDL; + } + else + { + LL_INFOS() << "error, removed function from empty list" << LL_ENDL; + } + + // LL_INFOS() << "This should crash" << LL_ENDL; + // list->addFunction(NULL); + + list->addFunction(&test1, &a); + list->addFunction(&test1, &a); + + LL_INFOS() << "Expect: test1 1, test1 1" << LL_ENDL; + list->callFunctions(); + + list->addFunction(&test1, &b); + list->addFunction(&test2, &b); + + LL_INFOS() << "Expect: test1 1, test1 1, test1 2, test2 2" << LL_ENDL; + list->callFunctions(); + + if (list->deleteFunction(&test1, &b)) + { + LL_INFOS() << "passed 3" << LL_ENDL; + } + else + { + LL_INFOS() << "error removing function" << LL_ENDL; + } + + LL_INFOS() << "Expect: test1 1, test1 1, test2 2" << LL_ENDL; + list->callFunctions(); + + list->deleteAllFunctions(); + + LL_INFOS() << "Expect nothing" << LL_ENDL; + list->callFunctions(); + + LL_INFOS() << "nothing :-)" << LL_ENDL; + + delete list; + + LL_INFOS() << "test complete" << LL_ENDL; +} + +#endif // _DEBUG diff --git a/indra/newview/llcallingcard.cpp b/indra/newview/llcallingcard.cpp index 5db62086b4..7fd29b297b 100644 --- a/indra/newview/llcallingcard.cpp +++ b/indra/newview/llcallingcard.cpp @@ -1,925 +1,925 @@ -/** - * @file llcallingcard.cpp - * @brief Implementation of the LLPreviewCallingCard class - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#if LL_WINDOWS -#pragma warning( disable : 4800 ) // performance warning in -#endif - -#include "llcallingcard.h" - -#include - -#include "indra_constants.h" -//#include "llcachename.h" -#include "llstl.h" -#include "lltimer.h" -#include "lluuid.h" -#include "message.h" - -#include "llagent.h" -#include "llavatarnamecache.h" -#include "llinventoryobserver.h" -#include "llinventorymodel.h" -#include "llnotifications.h" -#include "llslurl.h" -#include "llimview.h" -#include "lltrans.h" -#include "llviewercontrol.h" -#include "llviewerobjectlist.h" -#include "llvoavatar.h" -#include "llavataractions.h" -#include "lluiusage.h" - -///---------------------------------------------------------------------------- -/// Local function declarations, constants, enums, and typedefs -///---------------------------------------------------------------------------- - -class LLTrackingData -{ -public: - LLTrackingData(const LLUUID& avatar_id, const std::string& name); - bool haveTrackingInfo(); - void setTrackedCoarseLocation(const LLVector3d& global_pos); - void agentFound(const LLUUID& prey, - const LLVector3d& estimated_global_pos); - -public: - LLUUID mAvatarID; - std::string mName; - LLVector3d mGlobalPositionEstimate; - bool mHaveInfo; - bool mHaveCoarseInfo; - LLTimer mCoarseLocationTimer; - LLTimer mUpdateTimer; - LLTimer mAgentGone; -}; - -const F32 COARSE_FREQUENCY = 2.2f; -const F32 FIND_FREQUENCY = 29.7f; // This results in a database query, so cut these back -const F32 OFFLINE_SECONDS = FIND_FREQUENCY + 8.0f; - -// static -LLAvatarTracker LLAvatarTracker::sInstance; - -static void on_avatar_name_cache_notify(const LLUUID& agent_id, - const LLAvatarName& av_name, - bool online, - LLSD payload); - -///---------------------------------------------------------------------------- -/// Class LLAvatarTracker -///---------------------------------------------------------------------------- - -LLAvatarTracker::LLAvatarTracker() : - mTrackingData(NULL), - mTrackedAgentValid(false), - mModifyMask(0x0), - mIsNotifyObservers(false) -{ -} - -LLAvatarTracker::~LLAvatarTracker() -{ - deleteTrackingData(); - std::for_each(mObservers.begin(), mObservers.end(), DeletePointer()); - mObservers.clear(); - std::for_each(mBuddyInfo.begin(), mBuddyInfo.end(), DeletePairedPointer()); - mBuddyInfo.clear(); -} - -void LLAvatarTracker::track(const LLUUID& avatar_id, const std::string& name) -{ - deleteTrackingData(); - mTrackedAgentValid = false; - mTrackingData = new LLTrackingData(avatar_id, name); - findAgent(); - - // We track here because findAgent() is called on a timer (for now). - if(avatar_id.notNull()) - { - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_TrackAgent); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_TargetData); - msg->addUUIDFast(_PREHASH_PreyID, avatar_id); - gAgent.sendReliableMessage(); - } -} - -void LLAvatarTracker::untrack(const LLUUID& avatar_id) -{ - if (mTrackingData && mTrackingData->mAvatarID == avatar_id) - { - deleteTrackingData(); - mTrackedAgentValid = false; - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_TrackAgent); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_TargetData); - msg->addUUIDFast(_PREHASH_PreyID, LLUUID::null); - gAgent.sendReliableMessage(); - } -} - -void LLAvatarTracker::setTrackedCoarseLocation(const LLVector3d& global_pos) -{ - if(mTrackingData) - { - mTrackingData->setTrackedCoarseLocation(global_pos); - } -} - -bool LLAvatarTracker::haveTrackingInfo() -{ - if(mTrackingData) - { - return mTrackingData->haveTrackingInfo(); - } - return false; -} - -LLVector3d LLAvatarTracker::getGlobalPos() -{ - if(!mTrackedAgentValid || !mTrackingData) return LLVector3d(); - LLVector3d global_pos; - - LLViewerObject* object = gObjectList.findObject(mTrackingData->mAvatarID); - if(object && !object->isDead()) - { - global_pos = object->getPositionGlobal(); - // HACK - for making the tracker point above the avatar's head - // rather than its groin - LLVOAvatar* av = (LLVOAvatar*)object; - global_pos.mdV[VZ] += 0.7f * (av->mBodySize.mV[VZ] + av->mAvatarOffset.mV[VZ]); - - mTrackingData->mGlobalPositionEstimate = global_pos; - } - else - { - global_pos = mTrackingData->mGlobalPositionEstimate; - } - - return global_pos; -} - -void LLAvatarTracker::getDegreesAndDist(F32& rot, - F64& horiz_dist, - F64& vert_dist) -{ - if(!mTrackingData) return; - - LLVector3d global_pos; - - LLViewerObject* object = gObjectList.findObject(mTrackingData->mAvatarID); - if(object && !object->isDead()) - { - global_pos = object->getPositionGlobal(); - mTrackingData->mGlobalPositionEstimate = global_pos; - } - else - { - global_pos = mTrackingData->mGlobalPositionEstimate; - } - LLVector3d to_vec = global_pos - gAgent.getPositionGlobal(); - horiz_dist = sqrt(to_vec.mdV[VX] * to_vec.mdV[VX] + to_vec.mdV[VY] * to_vec.mdV[VY]); - vert_dist = to_vec.mdV[VZ]; - rot = F32(RAD_TO_DEG * atan2(to_vec.mdV[VY], to_vec.mdV[VX])); -} - -const std::string& LLAvatarTracker::getName() -{ - if(mTrackingData) - { - return mTrackingData->mName; - } - else - { - return LLStringUtil::null; - } -} - -const LLUUID& LLAvatarTracker::getAvatarID() -{ - if(mTrackingData) - { - return mTrackingData->mAvatarID; - } - else - { - return LLUUID::null; - } -} - -S32 LLAvatarTracker::addBuddyList(const LLAvatarTracker::buddy_map_t& buds) -{ - using namespace std; - - U32 new_buddy_count = 0; - LLUUID agent_id; - for(buddy_map_t::const_iterator itr = buds.begin(); itr != buds.end(); ++itr) - { - agent_id = (*itr).first; - buddy_map_t::const_iterator existing_buddy = mBuddyInfo.find(agent_id); - if(existing_buddy == mBuddyInfo.end()) - { - ++new_buddy_count; - mBuddyInfo[agent_id] = (*itr).second; - - // pre-request name for notifications? - LLAvatarName av_name; - LLAvatarNameCache::get(agent_id, &av_name); - - addChangedMask(LLFriendObserver::ADD, agent_id); - LL_DEBUGS() << "Added buddy " << agent_id - << ", " << (mBuddyInfo[agent_id]->isOnline() ? "Online" : "Offline") - << ", TO: " << mBuddyInfo[agent_id]->getRightsGrantedTo() - << ", FROM: " << mBuddyInfo[agent_id]->getRightsGrantedFrom() - << LL_ENDL; - } - else - { - LLRelationship* e_r = (*existing_buddy).second; - LLRelationship* n_r = (*itr).second; - LL_WARNS() << "!! Add buddy for existing buddy: " << agent_id - << " [" << (e_r->isOnline() ? "Online" : "Offline") << "->" << (n_r->isOnline() ? "Online" : "Offline") - << ", " << e_r->getRightsGrantedTo() << "->" << n_r->getRightsGrantedTo() - << ", " << e_r->getRightsGrantedTo() << "->" << n_r->getRightsGrantedTo() - << "]" << LL_ENDL; - } - } - // do not notify observers here - list can be large so let it be done on idle. - - return new_buddy_count; -} - - -void LLAvatarTracker::copyBuddyList(buddy_map_t& buddies) const -{ - buddy_map_t::const_iterator it = mBuddyInfo.begin(); - buddy_map_t::const_iterator end = mBuddyInfo.end(); - for(; it != end; ++it) - { - buddies[(*it).first] = (*it).second; - } -} - -void LLAvatarTracker::terminateBuddy(const LLUUID& id) -{ - LL_DEBUGS() << "LLAvatarTracker::terminateBuddy()" << LL_ENDL; - LLUIUsage::instance().logCommand("Agent.TerminateFriendship"); - - LLRelationship* buddy = get_ptr_in_map(mBuddyInfo, id); - if(!buddy) return; - mBuddyInfo.erase(id); - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("TerminateFriendship"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->nextBlock("ExBlock"); - msg->addUUID("OtherID", id); - gAgent.sendReliableMessage(); - - addChangedMask(LLFriendObserver::REMOVE, id); - delete buddy; -} - -// get all buddy info -const LLRelationship* LLAvatarTracker::getBuddyInfo(const LLUUID& id) const -{ - if(id.isNull()) return NULL; - return get_ptr_in_map(mBuddyInfo, id); -} - -bool LLAvatarTracker::isBuddy(const LLUUID& id) const -{ - LLRelationship* info = get_ptr_in_map(mBuddyInfo, id); - return (info != NULL); -} - -// online status -void LLAvatarTracker::setBuddyOnline(const LLUUID& id, bool is_online) -{ - LLRelationship* info = get_ptr_in_map(mBuddyInfo, id); - if(info) - { - info->online(is_online); - addChangedMask(LLFriendObserver::ONLINE, id); - LL_DEBUGS() << "Set buddy " << id << (is_online ? " Online" : " Offline") << LL_ENDL; - } - else - { - LL_WARNS() << "!! No buddy info found for " << id - << ", setting to " << (is_online ? "Online" : "Offline") << LL_ENDL; - } -} - -bool LLAvatarTracker::isBuddyOnline(const LLUUID& id) const -{ - LLRelationship* info = get_ptr_in_map(mBuddyInfo, id); - if(info) - { - return info->isOnline(); - } - return false; -} - -// empowered status -void LLAvatarTracker::setBuddyEmpowered(const LLUUID& id, bool is_empowered) -{ - LLRelationship* info = get_ptr_in_map(mBuddyInfo, id); - if(info) - { - info->grantRights(LLRelationship::GRANT_MODIFY_OBJECTS, 0); - mModifyMask |= LLFriendObserver::POWERS; - } -} - -bool LLAvatarTracker::isBuddyEmpowered(const LLUUID& id) const -{ - LLRelationship* info = get_ptr_in_map(mBuddyInfo, id); - if(info) - { - return info->isRightGrantedTo(LLRelationship::GRANT_MODIFY_OBJECTS); - } - return false; -} - -void LLAvatarTracker::empower(const LLUUID& id, bool grant) -{ - // wrapper for ease of use in some situations. - buddy_map_t list; - /* - list.insert(id); - empowerList(list, grant); - */ -} - -void LLAvatarTracker::empowerList(const buddy_map_t& list, bool grant) -{ - LL_WARNS() << "LLAvatarTracker::empowerList() not implemented." << LL_ENDL; -/* - LLMessageSystem* msg = gMessageSystem; - const char* message_name; - const char* block_name; - const char* field_name; - if(grant) - { - message_name = _PREHASH_GrantModification; - block_name = _PREHASH_EmpoweredBlock; - field_name = _PREHASH_EmpoweredID; - } - else - { - message_name = _PREHASH_RevokeModification; - block_name = _PREHASH_RevokedBlock; - field_name = _PREHASH_RevokedID; - } - - std::string name; - gAgent.buildFullnameAndTitle(name); - - bool start_new_message = true; - buddy_list_t::const_iterator it = list.begin(); - buddy_list_t::const_iterator end = list.end(); - for(; it != end; ++it) - { - if(NULL == get_ptr_in_map(mBuddyInfo, (*it))) continue; - setBuddyEmpowered((*it), grant); - if(start_new_message) - { - start_new_message = false; - msg->newMessageFast(message_name); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addStringFast(_PREHASH_GranterName, name); - } - msg->nextBlockFast(block_name); - msg->addUUIDFast(field_name, (*it)); - if(msg->isSendFullFast(block_name)) - { - start_new_message = true; - gAgent.sendReliableMessage(); - } - } - if(!start_new_message) - { - gAgent.sendReliableMessage(); - } -*/ -} - -void LLAvatarTracker::deleteTrackingData() -{ - //make sure mTrackingData never points to freed memory - LLTrackingData* tmp = mTrackingData; - mTrackingData = NULL; - delete tmp; -} - -void LLAvatarTracker::findAgent() -{ - if (!mTrackingData) return; - if (mTrackingData->mAvatarID.isNull()) return; - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_FindAgent); // Request - msg->nextBlockFast(_PREHASH_AgentBlock); - msg->addUUIDFast(_PREHASH_Hunter, gAgentID); - msg->addUUIDFast(_PREHASH_Prey, mTrackingData->mAvatarID); - msg->addU32Fast(_PREHASH_SpaceIP, 0); // will get filled in by simulator - msg->nextBlockFast(_PREHASH_LocationBlock); - const F64 NO_LOCATION = 0.0; - msg->addF64Fast(_PREHASH_GlobalX, NO_LOCATION); - msg->addF64Fast(_PREHASH_GlobalY, NO_LOCATION); - gAgent.sendReliableMessage(); -} - -void LLAvatarTracker::addObserver(LLFriendObserver* observer) -{ - if(observer) - { - mObservers.push_back(observer); - } -} - -void LLAvatarTracker::removeObserver(LLFriendObserver* observer) -{ - mObservers.erase( - std::remove(mObservers.begin(), mObservers.end(), observer), - mObservers.end()); -} - -void LLAvatarTracker::idleNotifyObservers() -{ - if (mModifyMask == LLFriendObserver::NONE && mChangedBuddyIDs.size() == 0) - { - return; - } - notifyObservers(); -} - -void LLAvatarTracker::notifyObservers() -{ - if (mIsNotifyObservers) - { - // Don't allow multiple calls. - // new masks and ids will be processed later from idle. - return; - } - LL_PROFILE_ZONE_SCOPED - mIsNotifyObservers = true; - - observer_list_t observers(mObservers); - observer_list_t::iterator it = observers.begin(); - observer_list_t::iterator end = observers.end(); - for(; it != end; ++it) - { - (*it)->changed(mModifyMask); - } - - for (changed_buddy_t::iterator it = mChangedBuddyIDs.begin(); it != mChangedBuddyIDs.end(); it++) - { - notifyParticularFriendObservers(*it); - } - - mModifyMask = LLFriendObserver::NONE; - mChangedBuddyIDs.clear(); - mIsNotifyObservers = false; -} - -void LLAvatarTracker::addParticularFriendObserver(const LLUUID& buddy_id, LLFriendObserver* observer) -{ - if (buddy_id.notNull() && observer) - mParticularFriendObserverMap[buddy_id].insert(observer); -} - -void LLAvatarTracker::removeParticularFriendObserver(const LLUUID& buddy_id, LLFriendObserver* observer) -{ - if (buddy_id.isNull() || !observer) - return; - - observer_map_t::iterator obs_it = mParticularFriendObserverMap.find(buddy_id); - if(obs_it == mParticularFriendObserverMap.end()) - return; - - obs_it->second.erase(observer); - - // purge empty sets from the map - if (obs_it->second.size() == 0) - mParticularFriendObserverMap.erase(obs_it); -} - -void LLAvatarTracker::notifyParticularFriendObservers(const LLUUID& buddy_id) -{ - observer_map_t::iterator obs_it = mParticularFriendObserverMap.find(buddy_id); - if(obs_it == mParticularFriendObserverMap.end()) - return; - - // Notify observers interested in buddy_id. - observer_set_t& obs = obs_it->second; - for (observer_set_t::iterator ob_it = obs.begin(); ob_it != obs.end(); ob_it++) - { - (*ob_it)->changed(mModifyMask); - } -} - -// store flag for change -// and id of object change applies to -void LLAvatarTracker::addChangedMask(U32 mask, const LLUUID& referent) -{ - mModifyMask |= mask; - if (referent.notNull()) - { - mChangedBuddyIDs.insert(referent); - } -} - -void LLAvatarTracker::applyFunctor(LLRelationshipFunctor& f) -{ - buddy_map_t::iterator it = mBuddyInfo.begin(); - buddy_map_t::iterator end = mBuddyInfo.end(); - for(; it != end; ++it) - { - f((*it).first, (*it).second); - } -} - -void LLAvatarTracker::registerCallbacks(LLMessageSystem* msg) -{ - msg->setHandlerFuncFast(_PREHASH_FindAgent, processAgentFound); - msg->setHandlerFuncFast(_PREHASH_OnlineNotification, - processOnlineNotification); - msg->setHandlerFuncFast(_PREHASH_OfflineNotification, - processOfflineNotification); - //msg->setHandlerFuncFast(_PREHASH_GrantedProxies, - // processGrantedProxies); - msg->setHandlerFunc("TerminateFriendship", processTerminateFriendship); - msg->setHandlerFunc(_PREHASH_ChangeUserRights, processChangeUserRights); -} - -// static -void LLAvatarTracker::processAgentFound(LLMessageSystem* msg, void**) -{ - LLUUID id; - - - msg->getUUIDFast(_PREHASH_AgentBlock, _PREHASH_Hunter, id); - msg->getUUIDFast(_PREHASH_AgentBlock, _PREHASH_Prey, id); - // *FIX: should make sure prey id matches. - LLVector3d estimated_global_pos; - msg->getF64Fast(_PREHASH_LocationBlock, _PREHASH_GlobalX, - estimated_global_pos.mdV[VX]); - msg->getF64Fast(_PREHASH_LocationBlock, _PREHASH_GlobalY, - estimated_global_pos.mdV[VY]); - LLAvatarTracker::instance().agentFound(id, estimated_global_pos); -} - -void LLAvatarTracker::agentFound(const LLUUID& prey, - const LLVector3d& estimated_global_pos) -{ - if(!mTrackingData) return; - //if we get a valid reply from the server, that means the agent - //is our friend and mappable, so enable interest list based updates - LLAvatarTracker::instance().setTrackedAgentValid(true); - mTrackingData->agentFound(prey, estimated_global_pos); -} - -// static -void LLAvatarTracker::processOnlineNotification(LLMessageSystem* msg, void**) -{ - LL_DEBUGS() << "LLAvatarTracker::processOnlineNotification()" << LL_ENDL; - instance().processNotify(msg, true); -} - -// static -void LLAvatarTracker::processOfflineNotification(LLMessageSystem* msg, void**) -{ - LL_DEBUGS() << "LLAvatarTracker::processOfflineNotification()" << LL_ENDL; - instance().processNotify(msg, false); -} - -void LLAvatarTracker::processChange(LLMessageSystem* msg) -{ - S32 count = msg->getNumberOfBlocksFast(_PREHASH_Rights); - LLUUID agent_id, agent_related; - S32 new_rights; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); - for(int i = 0; i < count; ++i) - { - msg->getUUIDFast(_PREHASH_Rights, _PREHASH_AgentRelated, agent_related, i); - msg->getS32Fast(_PREHASH_Rights,_PREHASH_RelatedRights, new_rights, i); - if(agent_id == gAgent.getID()) - { - if(mBuddyInfo.find(agent_related) != mBuddyInfo.end()) - { - (mBuddyInfo[agent_related])->setRightsTo(new_rights); - mChangedBuddyIDs.insert(agent_related); - } - } - else - { - if(mBuddyInfo.find(agent_id) != mBuddyInfo.end()) - { - if (((mBuddyInfo[agent_id]->getRightsGrantedFrom() ^ new_rights) & LLRelationship::GRANT_MODIFY_OBJECTS)) - { - LLSD args; - args["NAME"] = LLSLURL("agent", agent_id, "displayname").getSLURLString(); - - LLSD payload; - payload["from_id"] = agent_id; - if(LLRelationship::GRANT_MODIFY_OBJECTS & new_rights) - { - LLNotifications::instance().add("GrantedModifyRights",args, payload); - } - else - { - LLNotifications::instance().add("RevokedModifyRights",args, payload); - } - } - (mBuddyInfo[agent_id])->setRightsFrom(new_rights); - } - } - } - - addChangedMask(LLFriendObserver::POWERS, agent_id); - notifyObservers(); -} - -void LLAvatarTracker::processChangeUserRights(LLMessageSystem* msg, void**) -{ - LL_DEBUGS() << "LLAvatarTracker::processChangeUserRights()" << LL_ENDL; - instance().processChange(msg); -} - -void LLAvatarTracker::processNotify(LLMessageSystem* msg, bool online) -{ - LL_PROFILE_ZONE_SCOPED - S32 count = msg->getNumberOfBlocksFast(_PREHASH_AgentBlock); - bool chat_notify = gSavedSettings.getBOOL("ChatOnlineNotification"); - - LL_DEBUGS() << "Received " << count << " online notifications **** " << LL_ENDL; - if(count > 0) - { - LLUUID agent_id; - const LLRelationship* info = NULL; - LLUUID tracking_id; - if(mTrackingData) - { - tracking_id = mTrackingData->mAvatarID; - } - LLSD payload; - for(S32 i = 0; i < count; ++i) - { - msg->getUUIDFast(_PREHASH_AgentBlock, _PREHASH_AgentID, agent_id, i); - payload["FROM_ID"] = agent_id; - info = getBuddyInfo(agent_id); - if(info) - { - setBuddyOnline(agent_id,online); - } - else - { - LL_WARNS() << "Received online notification for unknown buddy: " - << agent_id << " is " << (online ? "ONLINE" : "OFFLINE") << LL_ENDL; - } - - if(tracking_id == agent_id) - { - // we were tracking someone who went offline - deleteTrackingData(); - } - - if(chat_notify) - { - // Look up the name of this agent for the notification - LLAvatarNameCache::get(agent_id,boost::bind(&on_avatar_name_cache_notify,_1, _2, online, payload)); - } - } - - mModifyMask |= LLFriendObserver::ONLINE; - instance().notifyObservers(); - gInventory.notifyObservers(); - } -} - -static void on_avatar_name_cache_notify(const LLUUID& agent_id, - const LLAvatarName& av_name, - bool online, - LLSD payload) -{ - // Popup a notify box with online status of this agent - // Use display name only because this user is your friend - LLSD args; - args["NAME"] = av_name.getDisplayName(); - args["STATUS"] = online ? LLTrans::getString("OnlineStatus") : LLTrans::getString("OfflineStatus"); - - LLNotificationPtr notification; - if (online) - { - notification = - LLNotifications::instance().add("FriendOnlineOffline", - args, - payload.with("respond_on_mousedown", true), - boost::bind(&LLAvatarActions::startIM, agent_id)); - } - else - { - notification = - LLNotifications::instance().add("FriendOnlineOffline", args, payload); - } - - // If there's an open IM session with this agent, send a notification there too. - LLUUID session_id = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, agent_id); - std::string notify_msg = notification->getMessage(); - LLIMModel::instance().proccessOnlineOfflineNotification(session_id, notify_msg); -} - -void LLAvatarTracker::formFriendship(const LLUUID& id) -{ - if(id.notNull()) - { - LLRelationship* buddy_info = get_ptr_in_map(instance().mBuddyInfo, id); - if(!buddy_info) - { - LLAvatarTracker& at = LLAvatarTracker::instance(); - //The default for relationship establishment is to have both parties - //visible online to each other. - buddy_info = new LLRelationship(LLRelationship::GRANT_ONLINE_STATUS,LLRelationship::GRANT_ONLINE_STATUS, false); - at.mBuddyInfo[id] = buddy_info; - at.addChangedMask(LLFriendObserver::ADD, id); - at.notifyObservers(); - } - } -} - -void LLAvatarTracker::processTerminateFriendship(LLMessageSystem* msg, void**) -{ - LLUUID id; - msg->getUUID("ExBlock", "OtherID", id); - if(id.notNull()) - { - LLAvatarTracker& at = LLAvatarTracker::instance(); - LLRelationship* buddy = get_ptr_in_map(at.mBuddyInfo, id); - if(!buddy) return; - at.mBuddyInfo.erase(id); - at.addChangedMask(LLFriendObserver::REMOVE, id); - delete buddy; - at.notifyObservers(); - } -} - -///---------------------------------------------------------------------------- -/// Tracking Data -///---------------------------------------------------------------------------- - -LLTrackingData::LLTrackingData(const LLUUID& avatar_id, const std::string& name) -: mAvatarID(avatar_id), - mHaveInfo(false), - mHaveCoarseInfo(false) -{ - mCoarseLocationTimer.setTimerExpirySec(COARSE_FREQUENCY); - mUpdateTimer.setTimerExpirySec(FIND_FREQUENCY); - mAgentGone.setTimerExpirySec(OFFLINE_SECONDS); - if(!name.empty()) - { - mName = name; - } -} - -void LLTrackingData::agentFound(const LLUUID& prey, - const LLVector3d& estimated_global_pos) -{ - if(prey != mAvatarID) - { - LL_WARNS() << "LLTrackingData::agentFound() - found " << prey - << " but looking for " << mAvatarID << LL_ENDL; - } - mHaveInfo = true; - mAgentGone.setTimerExpirySec(OFFLINE_SECONDS); - mGlobalPositionEstimate = estimated_global_pos; -} - -bool LLTrackingData::haveTrackingInfo() -{ - LLViewerObject* object = gObjectList.findObject(mAvatarID); - if(object && !object->isDead()) - { - mCoarseLocationTimer.checkExpirationAndReset(COARSE_FREQUENCY); - mUpdateTimer.setTimerExpirySec(FIND_FREQUENCY); - mAgentGone.setTimerExpirySec(OFFLINE_SECONDS); - mHaveInfo = true; - return true; - } - if(mHaveCoarseInfo && - !mCoarseLocationTimer.checkExpirationAndReset(COARSE_FREQUENCY)) - { - // if we reach here, then we have a 'recent' coarse update - mUpdateTimer.setTimerExpirySec(FIND_FREQUENCY); - mAgentGone.setTimerExpirySec(OFFLINE_SECONDS); - return true; - } - if(mUpdateTimer.checkExpirationAndReset(FIND_FREQUENCY)) - { - LLAvatarTracker::instance().findAgent(); - mHaveCoarseInfo = false; - } - if(mAgentGone.checkExpirationAndReset(OFFLINE_SECONDS)) - { - mHaveInfo = false; - mHaveCoarseInfo = false; - } - return mHaveInfo; -} - -void LLTrackingData::setTrackedCoarseLocation(const LLVector3d& global_pos) -{ - mCoarseLocationTimer.setTimerExpirySec(COARSE_FREQUENCY); - mGlobalPositionEstimate = global_pos; - mHaveInfo = true; - mHaveCoarseInfo = true; -} - -///---------------------------------------------------------------------------- -// various buddy functors -///---------------------------------------------------------------------------- - -bool LLCollectProxyBuddies::operator()(const LLUUID& buddy_id, LLRelationship* buddy) -{ - if(buddy->isRightGrantedFrom(LLRelationship::GRANT_MODIFY_OBJECTS)) - { - mProxy.insert(buddy_id); - } - return true; -} - -bool LLCollectMappableBuddies::operator()(const LLUUID& buddy_id, LLRelationship* buddy) -{ - LLAvatarName av_name; - LLAvatarNameCache::get( buddy_id, &av_name); - buddy_map_t::value_type value(buddy_id, av_name.getDisplayName()); - if(buddy->isOnline() && buddy->isRightGrantedFrom(LLRelationship::GRANT_MAP_LOCATION)) - { - mMappable.insert(value); - } - return true; -} - -bool LLCollectOnlineBuddies::operator()(const LLUUID& buddy_id, LLRelationship* buddy) -{ - LLAvatarName av_name; - LLAvatarNameCache::get(buddy_id, &av_name); - mFullName = av_name.getUserName(); - buddy_map_t::value_type value(buddy_id, mFullName); - if(buddy->isOnline()) - { - mOnline.insert(value); - } - return true; -} - -bool LLCollectAllBuddies::operator()(const LLUUID& buddy_id, LLRelationship* buddy) -{ - LLAvatarName av_name; - LLAvatarNameCache::get(buddy_id, &av_name); - mFullName = av_name.getCompleteName(); - buddy_map_t::value_type value(buddy_id, mFullName); - if(buddy->isOnline()) - { - mOnline.insert(value); - } - else - { - mOffline.insert(value); - } - return true; -} +/** + * @file llcallingcard.cpp + * @brief Implementation of the LLPreviewCallingCard class + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#if LL_WINDOWS +#pragma warning( disable : 4800 ) // performance warning in +#endif + +#include "llcallingcard.h" + +#include + +#include "indra_constants.h" +//#include "llcachename.h" +#include "llstl.h" +#include "lltimer.h" +#include "lluuid.h" +#include "message.h" + +#include "llagent.h" +#include "llavatarnamecache.h" +#include "llinventoryobserver.h" +#include "llinventorymodel.h" +#include "llnotifications.h" +#include "llslurl.h" +#include "llimview.h" +#include "lltrans.h" +#include "llviewercontrol.h" +#include "llviewerobjectlist.h" +#include "llvoavatar.h" +#include "llavataractions.h" +#include "lluiusage.h" + +///---------------------------------------------------------------------------- +/// Local function declarations, constants, enums, and typedefs +///---------------------------------------------------------------------------- + +class LLTrackingData +{ +public: + LLTrackingData(const LLUUID& avatar_id, const std::string& name); + bool haveTrackingInfo(); + void setTrackedCoarseLocation(const LLVector3d& global_pos); + void agentFound(const LLUUID& prey, + const LLVector3d& estimated_global_pos); + +public: + LLUUID mAvatarID; + std::string mName; + LLVector3d mGlobalPositionEstimate; + bool mHaveInfo; + bool mHaveCoarseInfo; + LLTimer mCoarseLocationTimer; + LLTimer mUpdateTimer; + LLTimer mAgentGone; +}; + +const F32 COARSE_FREQUENCY = 2.2f; +const F32 FIND_FREQUENCY = 29.7f; // This results in a database query, so cut these back +const F32 OFFLINE_SECONDS = FIND_FREQUENCY + 8.0f; + +// static +LLAvatarTracker LLAvatarTracker::sInstance; + +static void on_avatar_name_cache_notify(const LLUUID& agent_id, + const LLAvatarName& av_name, + bool online, + LLSD payload); + +///---------------------------------------------------------------------------- +/// Class LLAvatarTracker +///---------------------------------------------------------------------------- + +LLAvatarTracker::LLAvatarTracker() : + mTrackingData(NULL), + mTrackedAgentValid(false), + mModifyMask(0x0), + mIsNotifyObservers(false) +{ +} + +LLAvatarTracker::~LLAvatarTracker() +{ + deleteTrackingData(); + std::for_each(mObservers.begin(), mObservers.end(), DeletePointer()); + mObservers.clear(); + std::for_each(mBuddyInfo.begin(), mBuddyInfo.end(), DeletePairedPointer()); + mBuddyInfo.clear(); +} + +void LLAvatarTracker::track(const LLUUID& avatar_id, const std::string& name) +{ + deleteTrackingData(); + mTrackedAgentValid = false; + mTrackingData = new LLTrackingData(avatar_id, name); + findAgent(); + + // We track here because findAgent() is called on a timer (for now). + if(avatar_id.notNull()) + { + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_TrackAgent); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_TargetData); + msg->addUUIDFast(_PREHASH_PreyID, avatar_id); + gAgent.sendReliableMessage(); + } +} + +void LLAvatarTracker::untrack(const LLUUID& avatar_id) +{ + if (mTrackingData && mTrackingData->mAvatarID == avatar_id) + { + deleteTrackingData(); + mTrackedAgentValid = false; + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_TrackAgent); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_TargetData); + msg->addUUIDFast(_PREHASH_PreyID, LLUUID::null); + gAgent.sendReliableMessage(); + } +} + +void LLAvatarTracker::setTrackedCoarseLocation(const LLVector3d& global_pos) +{ + if(mTrackingData) + { + mTrackingData->setTrackedCoarseLocation(global_pos); + } +} + +bool LLAvatarTracker::haveTrackingInfo() +{ + if(mTrackingData) + { + return mTrackingData->haveTrackingInfo(); + } + return false; +} + +LLVector3d LLAvatarTracker::getGlobalPos() +{ + if(!mTrackedAgentValid || !mTrackingData) return LLVector3d(); + LLVector3d global_pos; + + LLViewerObject* object = gObjectList.findObject(mTrackingData->mAvatarID); + if(object && !object->isDead()) + { + global_pos = object->getPositionGlobal(); + // HACK - for making the tracker point above the avatar's head + // rather than its groin + LLVOAvatar* av = (LLVOAvatar*)object; + global_pos.mdV[VZ] += 0.7f * (av->mBodySize.mV[VZ] + av->mAvatarOffset.mV[VZ]); + + mTrackingData->mGlobalPositionEstimate = global_pos; + } + else + { + global_pos = mTrackingData->mGlobalPositionEstimate; + } + + return global_pos; +} + +void LLAvatarTracker::getDegreesAndDist(F32& rot, + F64& horiz_dist, + F64& vert_dist) +{ + if(!mTrackingData) return; + + LLVector3d global_pos; + + LLViewerObject* object = gObjectList.findObject(mTrackingData->mAvatarID); + if(object && !object->isDead()) + { + global_pos = object->getPositionGlobal(); + mTrackingData->mGlobalPositionEstimate = global_pos; + } + else + { + global_pos = mTrackingData->mGlobalPositionEstimate; + } + LLVector3d to_vec = global_pos - gAgent.getPositionGlobal(); + horiz_dist = sqrt(to_vec.mdV[VX] * to_vec.mdV[VX] + to_vec.mdV[VY] * to_vec.mdV[VY]); + vert_dist = to_vec.mdV[VZ]; + rot = F32(RAD_TO_DEG * atan2(to_vec.mdV[VY], to_vec.mdV[VX])); +} + +const std::string& LLAvatarTracker::getName() +{ + if(mTrackingData) + { + return mTrackingData->mName; + } + else + { + return LLStringUtil::null; + } +} + +const LLUUID& LLAvatarTracker::getAvatarID() +{ + if(mTrackingData) + { + return mTrackingData->mAvatarID; + } + else + { + return LLUUID::null; + } +} + +S32 LLAvatarTracker::addBuddyList(const LLAvatarTracker::buddy_map_t& buds) +{ + using namespace std; + + U32 new_buddy_count = 0; + LLUUID agent_id; + for(buddy_map_t::const_iterator itr = buds.begin(); itr != buds.end(); ++itr) + { + agent_id = (*itr).first; + buddy_map_t::const_iterator existing_buddy = mBuddyInfo.find(agent_id); + if(existing_buddy == mBuddyInfo.end()) + { + ++new_buddy_count; + mBuddyInfo[agent_id] = (*itr).second; + + // pre-request name for notifications? + LLAvatarName av_name; + LLAvatarNameCache::get(agent_id, &av_name); + + addChangedMask(LLFriendObserver::ADD, agent_id); + LL_DEBUGS() << "Added buddy " << agent_id + << ", " << (mBuddyInfo[agent_id]->isOnline() ? "Online" : "Offline") + << ", TO: " << mBuddyInfo[agent_id]->getRightsGrantedTo() + << ", FROM: " << mBuddyInfo[agent_id]->getRightsGrantedFrom() + << LL_ENDL; + } + else + { + LLRelationship* e_r = (*existing_buddy).second; + LLRelationship* n_r = (*itr).second; + LL_WARNS() << "!! Add buddy for existing buddy: " << agent_id + << " [" << (e_r->isOnline() ? "Online" : "Offline") << "->" << (n_r->isOnline() ? "Online" : "Offline") + << ", " << e_r->getRightsGrantedTo() << "->" << n_r->getRightsGrantedTo() + << ", " << e_r->getRightsGrantedTo() << "->" << n_r->getRightsGrantedTo() + << "]" << LL_ENDL; + } + } + // do not notify observers here - list can be large so let it be done on idle. + + return new_buddy_count; +} + + +void LLAvatarTracker::copyBuddyList(buddy_map_t& buddies) const +{ + buddy_map_t::const_iterator it = mBuddyInfo.begin(); + buddy_map_t::const_iterator end = mBuddyInfo.end(); + for(; it != end; ++it) + { + buddies[(*it).first] = (*it).second; + } +} + +void LLAvatarTracker::terminateBuddy(const LLUUID& id) +{ + LL_DEBUGS() << "LLAvatarTracker::terminateBuddy()" << LL_ENDL; + LLUIUsage::instance().logCommand("Agent.TerminateFriendship"); + + LLRelationship* buddy = get_ptr_in_map(mBuddyInfo, id); + if(!buddy) return; + mBuddyInfo.erase(id); + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("TerminateFriendship"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->nextBlock("ExBlock"); + msg->addUUID("OtherID", id); + gAgent.sendReliableMessage(); + + addChangedMask(LLFriendObserver::REMOVE, id); + delete buddy; +} + +// get all buddy info +const LLRelationship* LLAvatarTracker::getBuddyInfo(const LLUUID& id) const +{ + if(id.isNull()) return NULL; + return get_ptr_in_map(mBuddyInfo, id); +} + +bool LLAvatarTracker::isBuddy(const LLUUID& id) const +{ + LLRelationship* info = get_ptr_in_map(mBuddyInfo, id); + return (info != NULL); +} + +// online status +void LLAvatarTracker::setBuddyOnline(const LLUUID& id, bool is_online) +{ + LLRelationship* info = get_ptr_in_map(mBuddyInfo, id); + if(info) + { + info->online(is_online); + addChangedMask(LLFriendObserver::ONLINE, id); + LL_DEBUGS() << "Set buddy " << id << (is_online ? " Online" : " Offline") << LL_ENDL; + } + else + { + LL_WARNS() << "!! No buddy info found for " << id + << ", setting to " << (is_online ? "Online" : "Offline") << LL_ENDL; + } +} + +bool LLAvatarTracker::isBuddyOnline(const LLUUID& id) const +{ + LLRelationship* info = get_ptr_in_map(mBuddyInfo, id); + if(info) + { + return info->isOnline(); + } + return false; +} + +// empowered status +void LLAvatarTracker::setBuddyEmpowered(const LLUUID& id, bool is_empowered) +{ + LLRelationship* info = get_ptr_in_map(mBuddyInfo, id); + if(info) + { + info->grantRights(LLRelationship::GRANT_MODIFY_OBJECTS, 0); + mModifyMask |= LLFriendObserver::POWERS; + } +} + +bool LLAvatarTracker::isBuddyEmpowered(const LLUUID& id) const +{ + LLRelationship* info = get_ptr_in_map(mBuddyInfo, id); + if(info) + { + return info->isRightGrantedTo(LLRelationship::GRANT_MODIFY_OBJECTS); + } + return false; +} + +void LLAvatarTracker::empower(const LLUUID& id, bool grant) +{ + // wrapper for ease of use in some situations. + buddy_map_t list; + /* + list.insert(id); + empowerList(list, grant); + */ +} + +void LLAvatarTracker::empowerList(const buddy_map_t& list, bool grant) +{ + LL_WARNS() << "LLAvatarTracker::empowerList() not implemented." << LL_ENDL; +/* + LLMessageSystem* msg = gMessageSystem; + const char* message_name; + const char* block_name; + const char* field_name; + if(grant) + { + message_name = _PREHASH_GrantModification; + block_name = _PREHASH_EmpoweredBlock; + field_name = _PREHASH_EmpoweredID; + } + else + { + message_name = _PREHASH_RevokeModification; + block_name = _PREHASH_RevokedBlock; + field_name = _PREHASH_RevokedID; + } + + std::string name; + gAgent.buildFullnameAndTitle(name); + + bool start_new_message = true; + buddy_list_t::const_iterator it = list.begin(); + buddy_list_t::const_iterator end = list.end(); + for(; it != end; ++it) + { + if(NULL == get_ptr_in_map(mBuddyInfo, (*it))) continue; + setBuddyEmpowered((*it), grant); + if(start_new_message) + { + start_new_message = false; + msg->newMessageFast(message_name); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addStringFast(_PREHASH_GranterName, name); + } + msg->nextBlockFast(block_name); + msg->addUUIDFast(field_name, (*it)); + if(msg->isSendFullFast(block_name)) + { + start_new_message = true; + gAgent.sendReliableMessage(); + } + } + if(!start_new_message) + { + gAgent.sendReliableMessage(); + } +*/ +} + +void LLAvatarTracker::deleteTrackingData() +{ + //make sure mTrackingData never points to freed memory + LLTrackingData* tmp = mTrackingData; + mTrackingData = NULL; + delete tmp; +} + +void LLAvatarTracker::findAgent() +{ + if (!mTrackingData) return; + if (mTrackingData->mAvatarID.isNull()) return; + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_FindAgent); // Request + msg->nextBlockFast(_PREHASH_AgentBlock); + msg->addUUIDFast(_PREHASH_Hunter, gAgentID); + msg->addUUIDFast(_PREHASH_Prey, mTrackingData->mAvatarID); + msg->addU32Fast(_PREHASH_SpaceIP, 0); // will get filled in by simulator + msg->nextBlockFast(_PREHASH_LocationBlock); + const F64 NO_LOCATION = 0.0; + msg->addF64Fast(_PREHASH_GlobalX, NO_LOCATION); + msg->addF64Fast(_PREHASH_GlobalY, NO_LOCATION); + gAgent.sendReliableMessage(); +} + +void LLAvatarTracker::addObserver(LLFriendObserver* observer) +{ + if(observer) + { + mObservers.push_back(observer); + } +} + +void LLAvatarTracker::removeObserver(LLFriendObserver* observer) +{ + mObservers.erase( + std::remove(mObservers.begin(), mObservers.end(), observer), + mObservers.end()); +} + +void LLAvatarTracker::idleNotifyObservers() +{ + if (mModifyMask == LLFriendObserver::NONE && mChangedBuddyIDs.size() == 0) + { + return; + } + notifyObservers(); +} + +void LLAvatarTracker::notifyObservers() +{ + if (mIsNotifyObservers) + { + // Don't allow multiple calls. + // new masks and ids will be processed later from idle. + return; + } + LL_PROFILE_ZONE_SCOPED + mIsNotifyObservers = true; + + observer_list_t observers(mObservers); + observer_list_t::iterator it = observers.begin(); + observer_list_t::iterator end = observers.end(); + for(; it != end; ++it) + { + (*it)->changed(mModifyMask); + } + + for (changed_buddy_t::iterator it = mChangedBuddyIDs.begin(); it != mChangedBuddyIDs.end(); it++) + { + notifyParticularFriendObservers(*it); + } + + mModifyMask = LLFriendObserver::NONE; + mChangedBuddyIDs.clear(); + mIsNotifyObservers = false; +} + +void LLAvatarTracker::addParticularFriendObserver(const LLUUID& buddy_id, LLFriendObserver* observer) +{ + if (buddy_id.notNull() && observer) + mParticularFriendObserverMap[buddy_id].insert(observer); +} + +void LLAvatarTracker::removeParticularFriendObserver(const LLUUID& buddy_id, LLFriendObserver* observer) +{ + if (buddy_id.isNull() || !observer) + return; + + observer_map_t::iterator obs_it = mParticularFriendObserverMap.find(buddy_id); + if(obs_it == mParticularFriendObserverMap.end()) + return; + + obs_it->second.erase(observer); + + // purge empty sets from the map + if (obs_it->second.size() == 0) + mParticularFriendObserverMap.erase(obs_it); +} + +void LLAvatarTracker::notifyParticularFriendObservers(const LLUUID& buddy_id) +{ + observer_map_t::iterator obs_it = mParticularFriendObserverMap.find(buddy_id); + if(obs_it == mParticularFriendObserverMap.end()) + return; + + // Notify observers interested in buddy_id. + observer_set_t& obs = obs_it->second; + for (observer_set_t::iterator ob_it = obs.begin(); ob_it != obs.end(); ob_it++) + { + (*ob_it)->changed(mModifyMask); + } +} + +// store flag for change +// and id of object change applies to +void LLAvatarTracker::addChangedMask(U32 mask, const LLUUID& referent) +{ + mModifyMask |= mask; + if (referent.notNull()) + { + mChangedBuddyIDs.insert(referent); + } +} + +void LLAvatarTracker::applyFunctor(LLRelationshipFunctor& f) +{ + buddy_map_t::iterator it = mBuddyInfo.begin(); + buddy_map_t::iterator end = mBuddyInfo.end(); + for(; it != end; ++it) + { + f((*it).first, (*it).second); + } +} + +void LLAvatarTracker::registerCallbacks(LLMessageSystem* msg) +{ + msg->setHandlerFuncFast(_PREHASH_FindAgent, processAgentFound); + msg->setHandlerFuncFast(_PREHASH_OnlineNotification, + processOnlineNotification); + msg->setHandlerFuncFast(_PREHASH_OfflineNotification, + processOfflineNotification); + //msg->setHandlerFuncFast(_PREHASH_GrantedProxies, + // processGrantedProxies); + msg->setHandlerFunc("TerminateFriendship", processTerminateFriendship); + msg->setHandlerFunc(_PREHASH_ChangeUserRights, processChangeUserRights); +} + +// static +void LLAvatarTracker::processAgentFound(LLMessageSystem* msg, void**) +{ + LLUUID id; + + + msg->getUUIDFast(_PREHASH_AgentBlock, _PREHASH_Hunter, id); + msg->getUUIDFast(_PREHASH_AgentBlock, _PREHASH_Prey, id); + // *FIX: should make sure prey id matches. + LLVector3d estimated_global_pos; + msg->getF64Fast(_PREHASH_LocationBlock, _PREHASH_GlobalX, + estimated_global_pos.mdV[VX]); + msg->getF64Fast(_PREHASH_LocationBlock, _PREHASH_GlobalY, + estimated_global_pos.mdV[VY]); + LLAvatarTracker::instance().agentFound(id, estimated_global_pos); +} + +void LLAvatarTracker::agentFound(const LLUUID& prey, + const LLVector3d& estimated_global_pos) +{ + if(!mTrackingData) return; + //if we get a valid reply from the server, that means the agent + //is our friend and mappable, so enable interest list based updates + LLAvatarTracker::instance().setTrackedAgentValid(true); + mTrackingData->agentFound(prey, estimated_global_pos); +} + +// static +void LLAvatarTracker::processOnlineNotification(LLMessageSystem* msg, void**) +{ + LL_DEBUGS() << "LLAvatarTracker::processOnlineNotification()" << LL_ENDL; + instance().processNotify(msg, true); +} + +// static +void LLAvatarTracker::processOfflineNotification(LLMessageSystem* msg, void**) +{ + LL_DEBUGS() << "LLAvatarTracker::processOfflineNotification()" << LL_ENDL; + instance().processNotify(msg, false); +} + +void LLAvatarTracker::processChange(LLMessageSystem* msg) +{ + S32 count = msg->getNumberOfBlocksFast(_PREHASH_Rights); + LLUUID agent_id, agent_related; + S32 new_rights; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); + for(int i = 0; i < count; ++i) + { + msg->getUUIDFast(_PREHASH_Rights, _PREHASH_AgentRelated, agent_related, i); + msg->getS32Fast(_PREHASH_Rights,_PREHASH_RelatedRights, new_rights, i); + if(agent_id == gAgent.getID()) + { + if(mBuddyInfo.find(agent_related) != mBuddyInfo.end()) + { + (mBuddyInfo[agent_related])->setRightsTo(new_rights); + mChangedBuddyIDs.insert(agent_related); + } + } + else + { + if(mBuddyInfo.find(agent_id) != mBuddyInfo.end()) + { + if (((mBuddyInfo[agent_id]->getRightsGrantedFrom() ^ new_rights) & LLRelationship::GRANT_MODIFY_OBJECTS)) + { + LLSD args; + args["NAME"] = LLSLURL("agent", agent_id, "displayname").getSLURLString(); + + LLSD payload; + payload["from_id"] = agent_id; + if(LLRelationship::GRANT_MODIFY_OBJECTS & new_rights) + { + LLNotifications::instance().add("GrantedModifyRights",args, payload); + } + else + { + LLNotifications::instance().add("RevokedModifyRights",args, payload); + } + } + (mBuddyInfo[agent_id])->setRightsFrom(new_rights); + } + } + } + + addChangedMask(LLFriendObserver::POWERS, agent_id); + notifyObservers(); +} + +void LLAvatarTracker::processChangeUserRights(LLMessageSystem* msg, void**) +{ + LL_DEBUGS() << "LLAvatarTracker::processChangeUserRights()" << LL_ENDL; + instance().processChange(msg); +} + +void LLAvatarTracker::processNotify(LLMessageSystem* msg, bool online) +{ + LL_PROFILE_ZONE_SCOPED + S32 count = msg->getNumberOfBlocksFast(_PREHASH_AgentBlock); + bool chat_notify = gSavedSettings.getBOOL("ChatOnlineNotification"); + + LL_DEBUGS() << "Received " << count << " online notifications **** " << LL_ENDL; + if(count > 0) + { + LLUUID agent_id; + const LLRelationship* info = NULL; + LLUUID tracking_id; + if(mTrackingData) + { + tracking_id = mTrackingData->mAvatarID; + } + LLSD payload; + for(S32 i = 0; i < count; ++i) + { + msg->getUUIDFast(_PREHASH_AgentBlock, _PREHASH_AgentID, agent_id, i); + payload["FROM_ID"] = agent_id; + info = getBuddyInfo(agent_id); + if(info) + { + setBuddyOnline(agent_id,online); + } + else + { + LL_WARNS() << "Received online notification for unknown buddy: " + << agent_id << " is " << (online ? "ONLINE" : "OFFLINE") << LL_ENDL; + } + + if(tracking_id == agent_id) + { + // we were tracking someone who went offline + deleteTrackingData(); + } + + if(chat_notify) + { + // Look up the name of this agent for the notification + LLAvatarNameCache::get(agent_id,boost::bind(&on_avatar_name_cache_notify,_1, _2, online, payload)); + } + } + + mModifyMask |= LLFriendObserver::ONLINE; + instance().notifyObservers(); + gInventory.notifyObservers(); + } +} + +static void on_avatar_name_cache_notify(const LLUUID& agent_id, + const LLAvatarName& av_name, + bool online, + LLSD payload) +{ + // Popup a notify box with online status of this agent + // Use display name only because this user is your friend + LLSD args; + args["NAME"] = av_name.getDisplayName(); + args["STATUS"] = online ? LLTrans::getString("OnlineStatus") : LLTrans::getString("OfflineStatus"); + + LLNotificationPtr notification; + if (online) + { + notification = + LLNotifications::instance().add("FriendOnlineOffline", + args, + payload.with("respond_on_mousedown", true), + boost::bind(&LLAvatarActions::startIM, agent_id)); + } + else + { + notification = + LLNotifications::instance().add("FriendOnlineOffline", args, payload); + } + + // If there's an open IM session with this agent, send a notification there too. + LLUUID session_id = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, agent_id); + std::string notify_msg = notification->getMessage(); + LLIMModel::instance().proccessOnlineOfflineNotification(session_id, notify_msg); +} + +void LLAvatarTracker::formFriendship(const LLUUID& id) +{ + if(id.notNull()) + { + LLRelationship* buddy_info = get_ptr_in_map(instance().mBuddyInfo, id); + if(!buddy_info) + { + LLAvatarTracker& at = LLAvatarTracker::instance(); + //The default for relationship establishment is to have both parties + //visible online to each other. + buddy_info = new LLRelationship(LLRelationship::GRANT_ONLINE_STATUS,LLRelationship::GRANT_ONLINE_STATUS, false); + at.mBuddyInfo[id] = buddy_info; + at.addChangedMask(LLFriendObserver::ADD, id); + at.notifyObservers(); + } + } +} + +void LLAvatarTracker::processTerminateFriendship(LLMessageSystem* msg, void**) +{ + LLUUID id; + msg->getUUID("ExBlock", "OtherID", id); + if(id.notNull()) + { + LLAvatarTracker& at = LLAvatarTracker::instance(); + LLRelationship* buddy = get_ptr_in_map(at.mBuddyInfo, id); + if(!buddy) return; + at.mBuddyInfo.erase(id); + at.addChangedMask(LLFriendObserver::REMOVE, id); + delete buddy; + at.notifyObservers(); + } +} + +///---------------------------------------------------------------------------- +/// Tracking Data +///---------------------------------------------------------------------------- + +LLTrackingData::LLTrackingData(const LLUUID& avatar_id, const std::string& name) +: mAvatarID(avatar_id), + mHaveInfo(false), + mHaveCoarseInfo(false) +{ + mCoarseLocationTimer.setTimerExpirySec(COARSE_FREQUENCY); + mUpdateTimer.setTimerExpirySec(FIND_FREQUENCY); + mAgentGone.setTimerExpirySec(OFFLINE_SECONDS); + if(!name.empty()) + { + mName = name; + } +} + +void LLTrackingData::agentFound(const LLUUID& prey, + const LLVector3d& estimated_global_pos) +{ + if(prey != mAvatarID) + { + LL_WARNS() << "LLTrackingData::agentFound() - found " << prey + << " but looking for " << mAvatarID << LL_ENDL; + } + mHaveInfo = true; + mAgentGone.setTimerExpirySec(OFFLINE_SECONDS); + mGlobalPositionEstimate = estimated_global_pos; +} + +bool LLTrackingData::haveTrackingInfo() +{ + LLViewerObject* object = gObjectList.findObject(mAvatarID); + if(object && !object->isDead()) + { + mCoarseLocationTimer.checkExpirationAndReset(COARSE_FREQUENCY); + mUpdateTimer.setTimerExpirySec(FIND_FREQUENCY); + mAgentGone.setTimerExpirySec(OFFLINE_SECONDS); + mHaveInfo = true; + return true; + } + if(mHaveCoarseInfo && + !mCoarseLocationTimer.checkExpirationAndReset(COARSE_FREQUENCY)) + { + // if we reach here, then we have a 'recent' coarse update + mUpdateTimer.setTimerExpirySec(FIND_FREQUENCY); + mAgentGone.setTimerExpirySec(OFFLINE_SECONDS); + return true; + } + if(mUpdateTimer.checkExpirationAndReset(FIND_FREQUENCY)) + { + LLAvatarTracker::instance().findAgent(); + mHaveCoarseInfo = false; + } + if(mAgentGone.checkExpirationAndReset(OFFLINE_SECONDS)) + { + mHaveInfo = false; + mHaveCoarseInfo = false; + } + return mHaveInfo; +} + +void LLTrackingData::setTrackedCoarseLocation(const LLVector3d& global_pos) +{ + mCoarseLocationTimer.setTimerExpirySec(COARSE_FREQUENCY); + mGlobalPositionEstimate = global_pos; + mHaveInfo = true; + mHaveCoarseInfo = true; +} + +///---------------------------------------------------------------------------- +// various buddy functors +///---------------------------------------------------------------------------- + +bool LLCollectProxyBuddies::operator()(const LLUUID& buddy_id, LLRelationship* buddy) +{ + if(buddy->isRightGrantedFrom(LLRelationship::GRANT_MODIFY_OBJECTS)) + { + mProxy.insert(buddy_id); + } + return true; +} + +bool LLCollectMappableBuddies::operator()(const LLUUID& buddy_id, LLRelationship* buddy) +{ + LLAvatarName av_name; + LLAvatarNameCache::get( buddy_id, &av_name); + buddy_map_t::value_type value(buddy_id, av_name.getDisplayName()); + if(buddy->isOnline() && buddy->isRightGrantedFrom(LLRelationship::GRANT_MAP_LOCATION)) + { + mMappable.insert(value); + } + return true; +} + +bool LLCollectOnlineBuddies::operator()(const LLUUID& buddy_id, LLRelationship* buddy) +{ + LLAvatarName av_name; + LLAvatarNameCache::get(buddy_id, &av_name); + mFullName = av_name.getUserName(); + buddy_map_t::value_type value(buddy_id, mFullName); + if(buddy->isOnline()) + { + mOnline.insert(value); + } + return true; +} + +bool LLCollectAllBuddies::operator()(const LLUUID& buddy_id, LLRelationship* buddy) +{ + LLAvatarName av_name; + LLAvatarNameCache::get(buddy_id, &av_name); + mFullName = av_name.getCompleteName(); + buddy_map_t::value_type value(buddy_id, mFullName); + if(buddy->isOnline()) + { + mOnline.insert(value); + } + else + { + mOffline.insert(value); + } + return true; +} diff --git a/indra/newview/llcallingcard.h b/indra/newview/llcallingcard.h index 93a0d21461..48b93fdf9d 100644 --- a/indra/newview/llcallingcard.h +++ b/indra/newview/llcallingcard.h @@ -1,270 +1,270 @@ -/** - * @file llcallingcard.h - * @brief Definition of the LLPreviewCallingCard class - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLCALLINGCARD_H -#define LL_LLCALLINGCARD_H - -#include -#include -#include -#include -#include "lluserrelations.h" -#include "lluuid.h" -#include "v3dmath.h" - -//class LLInventoryModel; -//class LLInventoryObserver; -class LLMessageSystem; -class LLTrackingData; - -class LLFriendObserver -{ -public: - // This enumeration is a way to refer to what changed in a more - // human readable format. You can mask the value provided by - // changed() to see if the observer is interested in the change. - enum - { - NONE = 0, - ADD = 1, - REMOVE = 2, - ONLINE = 4, - POWERS = 8, - ALL = 0xffffffff - }; - virtual ~LLFriendObserver() {} - virtual void changed(U32 mask) = 0; -}; - -/* -struct LLBuddyInfo -{ - bool mIsOnline; - bool mIsEmpowered; - LLBuddyInfo() : mIsOnline(false), mIsEmpowered(false) {} -}; -*/ - -// This is used as a base class for doing operations on all buddies. -class LLRelationshipFunctor -{ -public: - virtual ~LLRelationshipFunctor() {} - virtual bool operator()(const LLUUID& buddy_id, LLRelationship* buddy) = 0; -}; - - -class LLAvatarTracker -{ -public: - static LLAvatarTracker& instance() { return sInstance; } - - void track(const LLUUID& avatar_id, const std::string& name); - void untrack(const LLUUID& avatar_id); - bool isTrackedAgentValid() { return mTrackedAgentValid; } - void setTrackedAgentValid(bool valid) { mTrackedAgentValid = valid; } - void findAgent(); - - // coarse update information - void setTrackedCoarseLocation(const LLVector3d& global_pos); - - // dealing with the tracked agent location - bool haveTrackingInfo(); - void getDegreesAndDist(F32& rot, F64& horiz_dist, F64& vert_dist); - LLVector3d getGlobalPos(); - - // Get the name passed in, returns null string if uninitialized. - const std::string& getName(); - - // Get the avatar being tracked, returns LLUUID::null if uninitialized - const LLUUID& getAvatarID(); - - // Deal with inventory - //void observe(LLInventoryModel* model); - //void inventoryChanged(); - - // add or remove agents from buddy list. Each method takes a set - // of buddies and returns how many were actually added or removed. - typedef std::map buddy_map_t; - - S32 addBuddyList(const buddy_map_t& buddies); - //S32 removeBuddyList(const buddy_list_t& exes); - void copyBuddyList(buddy_map_t& buddies) const; - - // deal with termination of friendhsip - void terminateBuddy(const LLUUID& id); - - // get full info - const LLRelationship* getBuddyInfo(const LLUUID& id) const; - - // Is this person a friend/buddy/calling card holder? - bool isBuddy(const LLUUID& id) const; - - // online status - void setBuddyOnline(const LLUUID& id, bool is_online); - bool isBuddyOnline(const LLUUID& id) const; - - // simple empowered status - void setBuddyEmpowered(const LLUUID& id, bool is_empowered); - bool isBuddyEmpowered(const LLUUID& id) const; - - // set the empower bit & message the server. - void empowerList(const buddy_map_t& list, bool grant); - void empower(const LLUUID& id, bool grant); // wrapper for above - - // register callbacks - void registerCallbacks(LLMessageSystem* msg); - - // Add/remove an observer. If the observer is destroyed, be sure - // to remove it. On destruction of the tracker, it will delete any - // observers left behind. - void addObserver(LLFriendObserver* observer); - void removeObserver(LLFriendObserver* observer); - void idleNotifyObservers(); - void notifyObservers(); - - // Observers interested in updates of a particular avatar. - // On destruction these observers are NOT deleted. - void addParticularFriendObserver(const LLUUID& buddy_id, LLFriendObserver* observer); - void removeParticularFriendObserver(const LLUUID& buddy_id, LLFriendObserver* observer); - void notifyParticularFriendObservers(const LLUUID& buddy_id); - - /** - * Stores flag for change and id of object change applies to - * - * This allows outsiders to tell the AvatarTracker if something has - * been changed 'under the hood', - * and next notification will have exact avatar IDs have been changed. - */ - void addChangedMask(U32 mask, const LLUUID& referent); - - const std::set& getChangedIDs() { return mChangedBuddyIDs; } - - // Apply the functor to every buddy. Do not actually modify the - // buddy list in the functor or bad things will happen. - void applyFunctor(LLRelationshipFunctor& f); - - static void formFriendship(const LLUUID& friend_id); - -protected: - void deleteTrackingData(); - void agentFound(const LLUUID& prey, - const LLVector3d& estimated_global_pos); - - // Message system functionality - static void processAgentFound(LLMessageSystem* msg, void**); - static void processOnlineNotification(LLMessageSystem* msg, void**); - static void processOfflineNotification(LLMessageSystem* msg, void**); - //static void processGrantedProxies(LLMessageSystem* msg, void**); - static void processTerminateFriendship(LLMessageSystem* msg, void**); - static void processChangeUserRights(LLMessageSystem* msg, void**); - - void processNotify(LLMessageSystem* msg, bool online); - void processChange(LLMessageSystem* msg); - -protected: - static LLAvatarTracker sInstance; - LLTrackingData* mTrackingData; - bool mTrackedAgentValid; - U32 mModifyMask; - //LLInventoryModel* mInventory; - //LLInventoryObserver* mInventoryObserver; - - buddy_map_t mBuddyInfo; - - typedef std::set changed_buddy_t; - changed_buddy_t mChangedBuddyIDs; - - typedef std::vector observer_list_t; - observer_list_t mObservers; - - typedef std::set observer_set_t; - typedef std::map observer_map_t; - observer_map_t mParticularFriendObserverMap; - -private: - // do not implement - LLAvatarTracker(const LLAvatarTracker&); - bool operator==(const LLAvatarTracker&); - - bool mIsNotifyObservers; - -public: - // don't you dare create or delete this object - LLAvatarTracker(); - ~LLAvatarTracker(); -}; - -// collect set of LLUUIDs we're a proxy for -class LLCollectProxyBuddies : public LLRelationshipFunctor -{ -public: - LLCollectProxyBuddies() {} - virtual ~LLCollectProxyBuddies() {} - virtual bool operator()(const LLUUID& buddy_id, LLRelationship* buddy); - typedef std::set buddy_list_t; - buddy_list_t mProxy; -}; - -// collect dictionary sorted map of name -> agent_id for every online buddy -class LLCollectMappableBuddies : public LLRelationshipFunctor -{ -public: - LLCollectMappableBuddies() {} - virtual ~LLCollectMappableBuddies() {} - virtual bool operator()(const LLUUID& buddy_id, LLRelationship* buddy); - typedef std::map buddy_map_t; - buddy_map_t mMappable; - std::string mFullName; -}; - -// collect dictionary sorted map of name -> agent_id for every online buddy -class LLCollectOnlineBuddies : public LLRelationshipFunctor -{ -public: - LLCollectOnlineBuddies() {} - virtual ~LLCollectOnlineBuddies() {} - virtual bool operator()(const LLUUID& buddy_id, LLRelationship* buddy); - typedef std::map buddy_map_t; - buddy_map_t mOnline; - std::string mFullName; -}; - -// collect dictionary sorted map of name -> agent_id for every buddy, -// one map is offline and the other map is online. -class LLCollectAllBuddies : public LLRelationshipFunctor -{ -public: - LLCollectAllBuddies() {} - virtual ~LLCollectAllBuddies() {} - virtual bool operator()(const LLUUID& buddy_id, LLRelationship* buddy); - typedef std::map buddy_map_t; - buddy_map_t mOnline; - buddy_map_t mOffline; - std::string mFullName; -}; - -#endif // LL_LLCALLINGCARD_H +/** + * @file llcallingcard.h + * @brief Definition of the LLPreviewCallingCard class + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLCALLINGCARD_H +#define LL_LLCALLINGCARD_H + +#include +#include +#include +#include +#include "lluserrelations.h" +#include "lluuid.h" +#include "v3dmath.h" + +//class LLInventoryModel; +//class LLInventoryObserver; +class LLMessageSystem; +class LLTrackingData; + +class LLFriendObserver +{ +public: + // This enumeration is a way to refer to what changed in a more + // human readable format. You can mask the value provided by + // changed() to see if the observer is interested in the change. + enum + { + NONE = 0, + ADD = 1, + REMOVE = 2, + ONLINE = 4, + POWERS = 8, + ALL = 0xffffffff + }; + virtual ~LLFriendObserver() {} + virtual void changed(U32 mask) = 0; +}; + +/* +struct LLBuddyInfo +{ + bool mIsOnline; + bool mIsEmpowered; + LLBuddyInfo() : mIsOnline(false), mIsEmpowered(false) {} +}; +*/ + +// This is used as a base class for doing operations on all buddies. +class LLRelationshipFunctor +{ +public: + virtual ~LLRelationshipFunctor() {} + virtual bool operator()(const LLUUID& buddy_id, LLRelationship* buddy) = 0; +}; + + +class LLAvatarTracker +{ +public: + static LLAvatarTracker& instance() { return sInstance; } + + void track(const LLUUID& avatar_id, const std::string& name); + void untrack(const LLUUID& avatar_id); + bool isTrackedAgentValid() { return mTrackedAgentValid; } + void setTrackedAgentValid(bool valid) { mTrackedAgentValid = valid; } + void findAgent(); + + // coarse update information + void setTrackedCoarseLocation(const LLVector3d& global_pos); + + // dealing with the tracked agent location + bool haveTrackingInfo(); + void getDegreesAndDist(F32& rot, F64& horiz_dist, F64& vert_dist); + LLVector3d getGlobalPos(); + + // Get the name passed in, returns null string if uninitialized. + const std::string& getName(); + + // Get the avatar being tracked, returns LLUUID::null if uninitialized + const LLUUID& getAvatarID(); + + // Deal with inventory + //void observe(LLInventoryModel* model); + //void inventoryChanged(); + + // add or remove agents from buddy list. Each method takes a set + // of buddies and returns how many were actually added or removed. + typedef std::map buddy_map_t; + + S32 addBuddyList(const buddy_map_t& buddies); + //S32 removeBuddyList(const buddy_list_t& exes); + void copyBuddyList(buddy_map_t& buddies) const; + + // deal with termination of friendhsip + void terminateBuddy(const LLUUID& id); + + // get full info + const LLRelationship* getBuddyInfo(const LLUUID& id) const; + + // Is this person a friend/buddy/calling card holder? + bool isBuddy(const LLUUID& id) const; + + // online status + void setBuddyOnline(const LLUUID& id, bool is_online); + bool isBuddyOnline(const LLUUID& id) const; + + // simple empowered status + void setBuddyEmpowered(const LLUUID& id, bool is_empowered); + bool isBuddyEmpowered(const LLUUID& id) const; + + // set the empower bit & message the server. + void empowerList(const buddy_map_t& list, bool grant); + void empower(const LLUUID& id, bool grant); // wrapper for above + + // register callbacks + void registerCallbacks(LLMessageSystem* msg); + + // Add/remove an observer. If the observer is destroyed, be sure + // to remove it. On destruction of the tracker, it will delete any + // observers left behind. + void addObserver(LLFriendObserver* observer); + void removeObserver(LLFriendObserver* observer); + void idleNotifyObservers(); + void notifyObservers(); + + // Observers interested in updates of a particular avatar. + // On destruction these observers are NOT deleted. + void addParticularFriendObserver(const LLUUID& buddy_id, LLFriendObserver* observer); + void removeParticularFriendObserver(const LLUUID& buddy_id, LLFriendObserver* observer); + void notifyParticularFriendObservers(const LLUUID& buddy_id); + + /** + * Stores flag for change and id of object change applies to + * + * This allows outsiders to tell the AvatarTracker if something has + * been changed 'under the hood', + * and next notification will have exact avatar IDs have been changed. + */ + void addChangedMask(U32 mask, const LLUUID& referent); + + const std::set& getChangedIDs() { return mChangedBuddyIDs; } + + // Apply the functor to every buddy. Do not actually modify the + // buddy list in the functor or bad things will happen. + void applyFunctor(LLRelationshipFunctor& f); + + static void formFriendship(const LLUUID& friend_id); + +protected: + void deleteTrackingData(); + void agentFound(const LLUUID& prey, + const LLVector3d& estimated_global_pos); + + // Message system functionality + static void processAgentFound(LLMessageSystem* msg, void**); + static void processOnlineNotification(LLMessageSystem* msg, void**); + static void processOfflineNotification(LLMessageSystem* msg, void**); + //static void processGrantedProxies(LLMessageSystem* msg, void**); + static void processTerminateFriendship(LLMessageSystem* msg, void**); + static void processChangeUserRights(LLMessageSystem* msg, void**); + + void processNotify(LLMessageSystem* msg, bool online); + void processChange(LLMessageSystem* msg); + +protected: + static LLAvatarTracker sInstance; + LLTrackingData* mTrackingData; + bool mTrackedAgentValid; + U32 mModifyMask; + //LLInventoryModel* mInventory; + //LLInventoryObserver* mInventoryObserver; + + buddy_map_t mBuddyInfo; + + typedef std::set changed_buddy_t; + changed_buddy_t mChangedBuddyIDs; + + typedef std::vector observer_list_t; + observer_list_t mObservers; + + typedef std::set observer_set_t; + typedef std::map observer_map_t; + observer_map_t mParticularFriendObserverMap; + +private: + // do not implement + LLAvatarTracker(const LLAvatarTracker&); + bool operator==(const LLAvatarTracker&); + + bool mIsNotifyObservers; + +public: + // don't you dare create or delete this object + LLAvatarTracker(); + ~LLAvatarTracker(); +}; + +// collect set of LLUUIDs we're a proxy for +class LLCollectProxyBuddies : public LLRelationshipFunctor +{ +public: + LLCollectProxyBuddies() {} + virtual ~LLCollectProxyBuddies() {} + virtual bool operator()(const LLUUID& buddy_id, LLRelationship* buddy); + typedef std::set buddy_list_t; + buddy_list_t mProxy; +}; + +// collect dictionary sorted map of name -> agent_id for every online buddy +class LLCollectMappableBuddies : public LLRelationshipFunctor +{ +public: + LLCollectMappableBuddies() {} + virtual ~LLCollectMappableBuddies() {} + virtual bool operator()(const LLUUID& buddy_id, LLRelationship* buddy); + typedef std::map buddy_map_t; + buddy_map_t mMappable; + std::string mFullName; +}; + +// collect dictionary sorted map of name -> agent_id for every online buddy +class LLCollectOnlineBuddies : public LLRelationshipFunctor +{ +public: + LLCollectOnlineBuddies() {} + virtual ~LLCollectOnlineBuddies() {} + virtual bool operator()(const LLUUID& buddy_id, LLRelationship* buddy); + typedef std::map buddy_map_t; + buddy_map_t mOnline; + std::string mFullName; +}; + +// collect dictionary sorted map of name -> agent_id for every buddy, +// one map is offline and the other map is online. +class LLCollectAllBuddies : public LLRelationshipFunctor +{ +public: + LLCollectAllBuddies() {} + virtual ~LLCollectAllBuddies() {} + virtual bool operator()(const LLUUID& buddy_id, LLRelationship* buddy); + typedef std::map buddy_map_t; + buddy_map_t mOnline; + buddy_map_t mOffline; + std::string mFullName; +}; + +#endif // LL_LLCALLINGCARD_H diff --git a/indra/newview/llchannelmanager.cpp b/indra/newview/llchannelmanager.cpp index 6dc75938cc..060430862b 100644 --- a/indra/newview/llchannelmanager.cpp +++ b/indra/newview/llchannelmanager.cpp @@ -1,270 +1,270 @@ -/** - * @file llchannelmanager.cpp - * @brief This class rules screen notification channels. - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" // must be first include - -#include "llchannelmanager.h" - -#include "llappviewer.h" -#include "lldonotdisturbnotificationstorage.h" -#include "llpersistentnotificationstorage.h" -#include "llviewercontrol.h" -#include "llviewerwindow.h" -#include "llrootview.h" -#include "llsyswellwindow.h" -#include "llfloaternotificationstabbed.h" -#include "llfloaterreg.h" - -#include - -using namespace LLNotificationsUI; - -//-------------------------------------------------------------------------- -LLChannelManager::LLChannelManager() -{ - LLAppViewer::instance()->setOnLoginCompletedCallback(boost::bind(&LLChannelManager::onLoginCompleted, this)); - mChannelList.clear(); - mStartUpChannel = NULL; - - if(!gViewerWindow) - { - LL_ERRS() << "LLChannelManager::LLChannelManager() - viwer window is not initialized yet" << LL_ENDL; - } - - // We don't actually need this instance right now, but our - // cleanupSingleton() method deletes LLScreenChannels, which need to - // unregister from LLUI. Calling LLUI::instance() here establishes the - // dependency so LLSingletonBase::deleteAll() calls our deleteSingleton() - // before LLUI::deleteSingleton(). - LLUI::instance(); -} - -//-------------------------------------------------------------------------- -LLChannelManager::~LLChannelManager() -{ -} - -//-------------------------------------------------------------------------- -void LLChannelManager::cleanupSingleton() -{ - // Note: LLScreenChannelBase is a LLUICtrl and depends onto other singletions - // not captured by singleton-dependency, so cleanup it here instead of destructor - for (std::vector::iterator it = mChannelList.begin(); it != mChannelList.end(); ++it) - { - LLScreenChannelBase* channel = it->channel.get(); - if (!channel) continue; - - delete channel; - } - - mChannelList.clear(); -} - -//-------------------------------------------------------------------------- -LLScreenChannel* LLChannelManager::createNotificationChannel() -{ - // creating params for a channel - LLScreenChannelBase::Params p; - p.id = NOTIFICATION_CHANNEL_UUID; - p.channel_align = CA_RIGHT; - p.toast_align = NA_TOP; - - // Getting a Channel for our notifications - return dynamic_cast (LLChannelManager::getInstance()->getChannel(p)); -} - -//-------------------------------------------------------------------------- -void LLChannelManager::onLoginCompleted() -{ - S32 away_notifications = 0; - - // calc a number of all offline notifications - for(std::vector::iterator it = mChannelList.begin(); it != mChannelList.end(); ++it) - { - LLScreenChannelBase* channel = it->channel.get(); - if (!channel) continue; - - // don't calc notifications for Nearby Chat - if(channel->getChannelID() == NEARBY_CHAT_CHANNEL_UUID) - { - continue; - } - - // don't calc notifications for channels that always show their notifications - if(!channel->getDisplayToastsAlways()) - { - away_notifications +=channel->getNumberOfHiddenToasts(); - } - } - - away_notifications += gIMMgr->getNumberOfUnreadIM(); - - if(!away_notifications) - { - onStartUpToastClose(); - } - else - { - // create a channel for the StartUp Toast - LLScreenChannelBase::Params p; - p.id = STARTUP_CHANNEL_UUID; - p.channel_align = CA_RIGHT; - mStartUpChannel = createChannel(p); - - if(!mStartUpChannel) - { - onStartUpToastClose(); - } - else - { - gViewerWindow->getRootView()->addChild(mStartUpChannel); - - // init channel's position and size - S32 channel_right_bound = gViewerWindow->getWorldViewRectScaled().mRight - gSavedSettings.getS32("NotificationChannelRightMargin"); - mStartUpChannel->init(channel_right_bound - NOTIFY_BOX_WIDTH, channel_right_bound); - mStartUpChannel->setMouseDownCallback(boost::bind(&LLFloaterNotificationsTabbed::onStartUpToastClick, LLFloaterNotificationsTabbed::getInstance(), _2, _3, _4)); - - mStartUpChannel->setCommitCallback(boost::bind(&LLChannelManager::onStartUpToastClose, this)); - mStartUpChannel->createStartUpToast(away_notifications, gSavedSettings.getS32("StartUpToastLifeTime")); - } - } - - LLPersistentNotificationStorage::getInstance()->loadNotifications(); - LLDoNotDisturbNotificationStorage::getInstance()->loadNotifications(); -} - -//-------------------------------------------------------------------------- -void LLChannelManager::onStartUpToastClose() -{ - if(mStartUpChannel) - { - mStartUpChannel->setVisible(false); - mStartUpChannel->closeStartUpToast(); - removeChannelByID(STARTUP_CHANNEL_UUID); - mStartUpChannel = NULL; - } - - // set StartUp Toast Flag to allow all other channels to show incoming toasts - LLScreenChannel::setStartUpToastShown(); -} - -//-------------------------------------------------------------------------- - -LLScreenChannelBase* LLChannelManager::addChannel(LLScreenChannelBase* channel) -{ - if(!channel) - return 0; - - ChannelElem new_elem; - new_elem.id = channel->getChannelID(); - new_elem.channel = channel->getHandle(); - - mChannelList.push_back(new_elem); - - return channel; -} - -LLScreenChannel* LLChannelManager::createChannel(LLScreenChannelBase::Params& p) -{ - LLScreenChannel* new_channel = new LLScreenChannel(p); - - addChannel(new_channel); - return new_channel; -} - -LLScreenChannelBase* LLChannelManager::getChannel(LLScreenChannelBase::Params& p) -{ - LLScreenChannelBase* new_channel = findChannelByID(p.id); - - if(new_channel) - return new_channel; - - return createChannel(p); - -} - -//-------------------------------------------------------------------------- -LLScreenChannelBase* LLChannelManager::findChannelByID(const LLUUID& id) -{ - std::vector::iterator it = find(mChannelList.begin(), mChannelList.end(), id); - if(it != mChannelList.end()) - { - return (*it).channel.get(); - } - - return NULL; -} - -//-------------------------------------------------------------------------- -void LLChannelManager::removeChannelByID(const LLUUID& id) -{ - std::vector::iterator it = find(mChannelList.begin(), mChannelList.end(), id); - if(it != mChannelList.end()) - { - mChannelList.erase(it); - } -} - -//-------------------------------------------------------------------------- -void LLChannelManager::muteAllChannels(bool mute) -{ - for (std::vector::iterator it = mChannelList.begin(); - it != mChannelList.end(); it++) - { - if (it->channel.get()) - { - it->channel.get()->setShowToasts(!mute); - } - } -} - -void LLChannelManager::killToastsFromChannel(const LLUUID& channel_id, const LLScreenChannel::Matcher& matcher) -{ - LLScreenChannel - * screen_channel = - dynamic_cast (findChannelByID(channel_id)); - if (screen_channel != NULL) - { - screen_channel->killMatchedToasts(matcher); - } -} - -// static -LLNotificationsUI::LLScreenChannel* LLChannelManager::getNotificationScreenChannel() -{ - LLNotificationsUI::LLScreenChannel* channel = static_cast - (LLNotificationsUI::LLChannelManager::getInstance()-> - findChannelByID(NOTIFICATION_CHANNEL_UUID)); - - if (channel == NULL) - { - LL_WARNS() << "Can't find screen channel by NotificationChannelUUID" << LL_ENDL; - llassert(!"Can't find screen channel by NotificationChannelUUID"); - } - - return channel; -} - +/** + * @file llchannelmanager.cpp + * @brief This class rules screen notification channels. + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" // must be first include + +#include "llchannelmanager.h" + +#include "llappviewer.h" +#include "lldonotdisturbnotificationstorage.h" +#include "llpersistentnotificationstorage.h" +#include "llviewercontrol.h" +#include "llviewerwindow.h" +#include "llrootview.h" +#include "llsyswellwindow.h" +#include "llfloaternotificationstabbed.h" +#include "llfloaterreg.h" + +#include + +using namespace LLNotificationsUI; + +//-------------------------------------------------------------------------- +LLChannelManager::LLChannelManager() +{ + LLAppViewer::instance()->setOnLoginCompletedCallback(boost::bind(&LLChannelManager::onLoginCompleted, this)); + mChannelList.clear(); + mStartUpChannel = NULL; + + if(!gViewerWindow) + { + LL_ERRS() << "LLChannelManager::LLChannelManager() - viwer window is not initialized yet" << LL_ENDL; + } + + // We don't actually need this instance right now, but our + // cleanupSingleton() method deletes LLScreenChannels, which need to + // unregister from LLUI. Calling LLUI::instance() here establishes the + // dependency so LLSingletonBase::deleteAll() calls our deleteSingleton() + // before LLUI::deleteSingleton(). + LLUI::instance(); +} + +//-------------------------------------------------------------------------- +LLChannelManager::~LLChannelManager() +{ +} + +//-------------------------------------------------------------------------- +void LLChannelManager::cleanupSingleton() +{ + // Note: LLScreenChannelBase is a LLUICtrl and depends onto other singletions + // not captured by singleton-dependency, so cleanup it here instead of destructor + for (std::vector::iterator it = mChannelList.begin(); it != mChannelList.end(); ++it) + { + LLScreenChannelBase* channel = it->channel.get(); + if (!channel) continue; + + delete channel; + } + + mChannelList.clear(); +} + +//-------------------------------------------------------------------------- +LLScreenChannel* LLChannelManager::createNotificationChannel() +{ + // creating params for a channel + LLScreenChannelBase::Params p; + p.id = NOTIFICATION_CHANNEL_UUID; + p.channel_align = CA_RIGHT; + p.toast_align = NA_TOP; + + // Getting a Channel for our notifications + return dynamic_cast (LLChannelManager::getInstance()->getChannel(p)); +} + +//-------------------------------------------------------------------------- +void LLChannelManager::onLoginCompleted() +{ + S32 away_notifications = 0; + + // calc a number of all offline notifications + for(std::vector::iterator it = mChannelList.begin(); it != mChannelList.end(); ++it) + { + LLScreenChannelBase* channel = it->channel.get(); + if (!channel) continue; + + // don't calc notifications for Nearby Chat + if(channel->getChannelID() == NEARBY_CHAT_CHANNEL_UUID) + { + continue; + } + + // don't calc notifications for channels that always show their notifications + if(!channel->getDisplayToastsAlways()) + { + away_notifications +=channel->getNumberOfHiddenToasts(); + } + } + + away_notifications += gIMMgr->getNumberOfUnreadIM(); + + if(!away_notifications) + { + onStartUpToastClose(); + } + else + { + // create a channel for the StartUp Toast + LLScreenChannelBase::Params p; + p.id = STARTUP_CHANNEL_UUID; + p.channel_align = CA_RIGHT; + mStartUpChannel = createChannel(p); + + if(!mStartUpChannel) + { + onStartUpToastClose(); + } + else + { + gViewerWindow->getRootView()->addChild(mStartUpChannel); + + // init channel's position and size + S32 channel_right_bound = gViewerWindow->getWorldViewRectScaled().mRight - gSavedSettings.getS32("NotificationChannelRightMargin"); + mStartUpChannel->init(channel_right_bound - NOTIFY_BOX_WIDTH, channel_right_bound); + mStartUpChannel->setMouseDownCallback(boost::bind(&LLFloaterNotificationsTabbed::onStartUpToastClick, LLFloaterNotificationsTabbed::getInstance(), _2, _3, _4)); + + mStartUpChannel->setCommitCallback(boost::bind(&LLChannelManager::onStartUpToastClose, this)); + mStartUpChannel->createStartUpToast(away_notifications, gSavedSettings.getS32("StartUpToastLifeTime")); + } + } + + LLPersistentNotificationStorage::getInstance()->loadNotifications(); + LLDoNotDisturbNotificationStorage::getInstance()->loadNotifications(); +} + +//-------------------------------------------------------------------------- +void LLChannelManager::onStartUpToastClose() +{ + if(mStartUpChannel) + { + mStartUpChannel->setVisible(false); + mStartUpChannel->closeStartUpToast(); + removeChannelByID(STARTUP_CHANNEL_UUID); + mStartUpChannel = NULL; + } + + // set StartUp Toast Flag to allow all other channels to show incoming toasts + LLScreenChannel::setStartUpToastShown(); +} + +//-------------------------------------------------------------------------- + +LLScreenChannelBase* LLChannelManager::addChannel(LLScreenChannelBase* channel) +{ + if(!channel) + return 0; + + ChannelElem new_elem; + new_elem.id = channel->getChannelID(); + new_elem.channel = channel->getHandle(); + + mChannelList.push_back(new_elem); + + return channel; +} + +LLScreenChannel* LLChannelManager::createChannel(LLScreenChannelBase::Params& p) +{ + LLScreenChannel* new_channel = new LLScreenChannel(p); + + addChannel(new_channel); + return new_channel; +} + +LLScreenChannelBase* LLChannelManager::getChannel(LLScreenChannelBase::Params& p) +{ + LLScreenChannelBase* new_channel = findChannelByID(p.id); + + if(new_channel) + return new_channel; + + return createChannel(p); + +} + +//-------------------------------------------------------------------------- +LLScreenChannelBase* LLChannelManager::findChannelByID(const LLUUID& id) +{ + std::vector::iterator it = find(mChannelList.begin(), mChannelList.end(), id); + if(it != mChannelList.end()) + { + return (*it).channel.get(); + } + + return NULL; +} + +//-------------------------------------------------------------------------- +void LLChannelManager::removeChannelByID(const LLUUID& id) +{ + std::vector::iterator it = find(mChannelList.begin(), mChannelList.end(), id); + if(it != mChannelList.end()) + { + mChannelList.erase(it); + } +} + +//-------------------------------------------------------------------------- +void LLChannelManager::muteAllChannels(bool mute) +{ + for (std::vector::iterator it = mChannelList.begin(); + it != mChannelList.end(); it++) + { + if (it->channel.get()) + { + it->channel.get()->setShowToasts(!mute); + } + } +} + +void LLChannelManager::killToastsFromChannel(const LLUUID& channel_id, const LLScreenChannel::Matcher& matcher) +{ + LLScreenChannel + * screen_channel = + dynamic_cast (findChannelByID(channel_id)); + if (screen_channel != NULL) + { + screen_channel->killMatchedToasts(matcher); + } +} + +// static +LLNotificationsUI::LLScreenChannel* LLChannelManager::getNotificationScreenChannel() +{ + LLNotificationsUI::LLScreenChannel* channel = static_cast + (LLNotificationsUI::LLChannelManager::getInstance()-> + findChannelByID(NOTIFICATION_CHANNEL_UUID)); + + if (channel == NULL) + { + LL_WARNS() << "Can't find screen channel by NotificationChannelUUID" << LL_ENDL; + llassert(!"Can't find screen channel by NotificationChannelUUID"); + } + + return channel; +} + diff --git a/indra/newview/llchatbar.cpp b/indra/newview/llchatbar.cpp index a039604f21..6205b44375 100644 --- a/indra/newview/llchatbar.cpp +++ b/indra/newview/llchatbar.cpp @@ -1,653 +1,653 @@ -/** - * @file llchatbar.cpp - * @brief LLChatBar class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llchatbar.h" - -#include "llfontgl.h" -#include "llrect.h" -#include "llerror.h" -#include "llparcel.h" -#include "llstring.h" -#include "message.h" -#include "llfocusmgr.h" - -#include "llagent.h" -#include "llbutton.h" -#include "llcombobox.h" -#include "llcommandhandler.h" // secondlife:///app/chat/ support -#include "llviewercontrol.h" -#include "llgesturemgr.h" -#include "llkeyboard.h" -#include "lllineeditor.h" -#include "llstatusbar.h" -#include "lltextbox.h" -#include "lluiconstants.h" -#include "llviewergesture.h" // for triggering gestures -#include "llviewermenu.h" // for deleting object with DEL key -#include "llviewerstats.h" -#include "llviewerwindow.h" -#include "llframetimer.h" -#include "llresmgr.h" -#include "llworld.h" -#include "llinventorymodel.h" -#include "llmultigesture.h" -#include "llui.h" -#include "lluictrlfactory.h" -#include "lluiusage.h" - -// -// Globals -// -constexpr F32 AGENT_TYPING_TIMEOUT = 5.f; // seconds - -LLChatBar *gChatBar = NULL; - -class LLChatBarGestureObserver : public LLGestureManagerObserver -{ -public: - LLChatBarGestureObserver(LLChatBar* chat_barp) : mChatBar(chat_barp){} - virtual ~LLChatBarGestureObserver() {} - virtual void changed() { mChatBar->refreshGestures(); } -private: - LLChatBar* mChatBar; -}; - - -extern void send_chat_from_viewer(const std::string& utf8_out_text, EChatType type, S32 channel); - -// -// Functions -// - -LLChatBar::LLChatBar() -: LLPanel(), - mInputEditor(NULL), - mGestureLabelTimer(), - mLastSpecialChatChannel(0), - mIsBuilt(false), - mGestureCombo(NULL), - mObserver(NULL) -{ - //setIsChrome(true); -} - - -LLChatBar::~LLChatBar() -{ - LLGestureMgr::instance().removeObserver(mObserver); - delete mObserver; - mObserver = NULL; - // LLView destructor cleans up children -} - -bool LLChatBar::postBuild() -{ - getChild("Say")->setCommitCallback(boost::bind(&LLChatBar::onClickSay, this, _1)); - - // * NOTE: mantipov: getChild with default parameters returns dummy widget. - // Seems this class will be completle removed - // attempt to bind to an existing combo box named gesture - setGestureCombo(findChild( "Gesture")); - - mInputEditor = getChild("Chat Editor"); - mInputEditor->setKeystrokeCallback(&onInputEditorKeystroke, this); - mInputEditor->setFocusLostCallback(boost::bind(&LLChatBar::onInputEditorFocusLost)); - mInputEditor->setFocusReceivedCallback(boost::bind(&LLChatBar::onInputEditorGainFocus)); - mInputEditor->setCommitOnFocusLost( false ); - mInputEditor->setRevertOnEsc( false ); - mInputEditor->setIgnoreTab(true); - mInputEditor->setPassDelete(true); - mInputEditor->setReplaceNewlinesWithSpaces(false); - - mInputEditor->setMaxTextLength(DB_CHAT_MSG_STR_LEN); - mInputEditor->setEnableLineHistory(true); - - mIsBuilt = true; - - return true; -} - -//----------------------------------------------------------------------- -// Overrides -//----------------------------------------------------------------------- - -// virtual -bool LLChatBar::handleKeyHere( KEY key, MASK mask ) -{ - bool handled = false; - - if( KEY_RETURN == key ) - { - if (mask == MASK_CONTROL) - { - // shout - sendChat(CHAT_TYPE_SHOUT); - handled = true; - } - else if (mask == MASK_NONE) - { - // say - sendChat( CHAT_TYPE_NORMAL ); - handled = true; - } - } - // only do this in main chatbar - else if ( KEY_ESCAPE == key && gChatBar == this) - { - stopChat(); - - handled = true; - } - - return handled; -} - -void LLChatBar::refresh() -{ - // HACK: Leave the name of the gesture in place for a few seconds. - const F32 SHOW_GESTURE_NAME_TIME = 2.f; - if (mGestureLabelTimer.getStarted() && mGestureLabelTimer.getElapsedTimeF32() > SHOW_GESTURE_NAME_TIME) - { - LLCtrlListInterface* gestures = mGestureCombo ? mGestureCombo->getListInterface() : NULL; - if (gestures) gestures->selectFirstItem(); - mGestureLabelTimer.stop(); - } - - if ((gAgent.getTypingTime() > AGENT_TYPING_TIMEOUT) && (gAgent.getRenderState() & AGENT_STATE_TYPING)) - { - gAgent.stopTyping(); - } - - getChildView("Say")->setEnabled(mInputEditor->getText().size() > 0); - -} - -void LLChatBar::refreshGestures() -{ - if (mGestureCombo) - { - //store current selection so we can maintain it - std::string cur_gesture = mGestureCombo->getValue().asString(); - mGestureCombo->selectFirstItem(); - std::string label = mGestureCombo->getValue().asString();; - // clear - mGestureCombo->clearRows(); - - // collect list of unique gestures - std::map unique; - LLGestureMgr::item_map_t::const_iterator it; - const LLGestureMgr::item_map_t& active_gestures = LLGestureMgr::instance().getActiveGestures(); - for (it = active_gestures.begin(); it != active_gestures.end(); ++it) - { - LLMultiGesture* gesture = (*it).second; - if (gesture) - { - if (!gesture->mTrigger.empty()) - { - unique[gesture->mTrigger] = true; - } - } - } - - // add unique gestures - std::map ::iterator it2; - for (it2 = unique.begin(); it2 != unique.end(); ++it2) - { - mGestureCombo->addSimpleElement((*it2).first); - } - - mGestureCombo->sortByName(); - // Insert label after sorting, at top, with separator below it - mGestureCombo->addSeparator(ADD_TOP); - mGestureCombo->addSimpleElement(getString("gesture_label"), ADD_TOP); - - if (!cur_gesture.empty()) - { - mGestureCombo->selectByValue(LLSD(cur_gesture)); - } - else - { - mGestureCombo->selectFirstItem(); - } - } -} - -// Move the cursor to the correct input field. -void LLChatBar::setKeyboardFocus(bool focus) -{ - if (focus) - { - if (mInputEditor) - { - mInputEditor->setFocus(true); - mInputEditor->selectAll(); - } - } - else if (gFocusMgr.childHasKeyboardFocus(this)) - { - if (mInputEditor) - { - mInputEditor->deselect(); - } - setFocus(false); - } -} - - -// Ignore arrow keys in chat bar -void LLChatBar::setIgnoreArrowKeys(bool b) -{ - if (mInputEditor) - { - mInputEditor->setIgnoreArrowKeys(b); - } -} - -bool LLChatBar::inputEditorHasFocus() -{ - return mInputEditor && mInputEditor->hasFocus(); -} - -std::string LLChatBar::getCurrentChat() -{ - return mInputEditor ? mInputEditor->getText() : LLStringUtil::null; -} - -void LLChatBar::setGestureCombo(LLComboBox* combo) -{ - mGestureCombo = combo; - if (mGestureCombo) - { - mGestureCombo->setCommitCallback(boost::bind(&LLChatBar::onCommitGesture, this, _1)); - - // now register observer since we have a place to put the results - mObserver = new LLChatBarGestureObserver(this); - LLGestureMgr::instance().addObserver(mObserver); - - // refresh list from current active gestures - refreshGestures(); - } -} - -//----------------------------------------------------------------------- -// Internal functions -//----------------------------------------------------------------------- - -// If input of the form "/20foo" or "/20 foo", returns "foo" and channel 20. -// Otherwise returns input and channel 0. -LLWString LLChatBar::stripChannelNumber(const LLWString &mesg, S32* channel) -{ - if (mesg[0] == '/' - && mesg[1] == '/') - { - // This is a "repeat channel send" - *channel = mLastSpecialChatChannel; - return mesg.substr(2, mesg.length() - 2); - } - else if (mesg[0] == '/' - && mesg[1] - && (LLStringOps::isDigit(mesg[1]) - || (mesg[1] == '-' && mesg[2] && LLStringOps::isDigit(mesg[2])))) - { - // This a special "/20" speak on a channel - S32 pos = 0; - - // Copy the channel number into a string - LLWString channel_string; - llwchar c; - do - { - c = mesg[pos+1]; - channel_string.push_back(c); - pos++; - } - while(c && pos < 64 && (LLStringOps::isDigit(c) || (pos == 1 && c == '-'))); - - // Move the pointer forward to the first non-whitespace char - // Check isspace before looping, so we can handle "/33foo" - // as well as "/33 foo" - while(c && iswspace(c)) - { - c = mesg[pos+1]; - pos++; - } - - mLastSpecialChatChannel = strtol(wstring_to_utf8str(channel_string).c_str(), NULL, 10); - *channel = mLastSpecialChatChannel; - return mesg.substr(pos, mesg.length() - pos); - } - else - { - // This is normal chat. - *channel = 0; - return mesg; - } -} - - -void LLChatBar::sendChat( EChatType type ) -{ - if (mInputEditor) - { - LLWString text = mInputEditor->getConvertedText(); - if (!text.empty()) - { - // store sent line in history, duplicates will get filtered - if (mInputEditor) mInputEditor->updateHistory(); - // Check if this is destined for another channel - S32 channel = 0; - stripChannelNumber(text, &channel); - - std::string utf8text = wstring_to_utf8str(text); - // Try to trigger a gesture, if not chat to a script. - std::string utf8_revised_text; - if (0 == channel) - { - // discard returned "found" boolean - LLGestureMgr::instance().triggerAndReviseString(utf8text, &utf8_revised_text); - } - else - { - utf8_revised_text = utf8text; - } - - utf8_revised_text = utf8str_trim(utf8_revised_text); - - if (!utf8_revised_text.empty()) - { - // Chat with animation - sendChatFromViewer(utf8_revised_text, type, gSavedSettings.getBOOL("PlayChatAnim")); - } - } - } - - getChild("Chat Editor")->setValue(LLStringUtil::null); - - gAgent.stopTyping(); - - // If the user wants to stop chatting on hitting return, lose focus - // and go out of chat mode. - if (gChatBar == this && gSavedSettings.getBOOL("CloseChatOnReturn")) - { - stopChat(); - } -} - - -//----------------------------------------------------------------------- -// Static functions -//----------------------------------------------------------------------- - -// static -void LLChatBar::startChat(const char* line) -{ - //TODO* remove DUMMY chat - //if(gBottomTray && gBottomTray->getChatBox()) - //{ - // gBottomTray->setVisible(true); - // gBottomTray->getChatBox()->setFocus(true); - //} - - // *TODO Vadim: Why was this code commented out? - -// gChatBar->setVisible(true); -// gChatBar->setKeyboardFocus(true); -// gSavedSettings.setBOOL("ChatVisible", true); -// -// if (line && gChatBar->mInputEditor) -// { -// std::string line_string(line); -// gChatBar->mInputEditor->setText(line_string); -// } -// // always move cursor to end so users don't obliterate chat when accidentally hitting WASD -// gChatBar->mInputEditor->setCursorToEnd(); -} - - -// Exit "chat mode" and do the appropriate focus changes -// static -void LLChatBar::stopChat() -{ - //TODO* remove DUMMY chat - //if(gBottomTray && gBottomTray->getChatBox()) - ///{ - // gBottomTray->getChatBox()->setFocus(false); - //} - - // *TODO Vadim: Why was this code commented out? - -// // In simple UI mode, we never release focus from the chat bar -// gChatBar->setKeyboardFocus(false); -// -// // If we typed a movement key and pressed return during the -// // same frame, the keyboard handlers will see the key as having -// // gone down this frame and try to move the avatar. -// gKeyboard->resetKeys(); -// gKeyboard->resetMaskKeys(); -// -// // stop typing animation -// gAgent.stopTyping(); -// -// // hide chat bar so it doesn't grab focus back -// gChatBar->setVisible(false); -// gSavedSettings.setBOOL("ChatVisible", false); -} - -// static -void LLChatBar::onInputEditorKeystroke( LLLineEditor* caller, void* userdata ) -{ - LLChatBar* self = (LLChatBar *)userdata; - - LLWString raw_text; - if (self->mInputEditor) raw_text = self->mInputEditor->getWText(); - - // Can't trim the end, because that will cause autocompletion - // to eat trailing spaces that might be part of a gesture. - LLWStringUtil::trimHead(raw_text); - - S32 length = raw_text.length(); - - if( (length > 0) && (raw_text[0] != '/') ) // forward slash is used for escape (eg. emote) sequences - { - gAgent.startTyping(); - } - else - { - gAgent.stopTyping(); - } - - /* Doesn't work -- can't tell the difference between a backspace - that killed the selection vs. backspace at the end of line. - if (length > 1 - && text[0] == '/' - && key == KEY_BACKSPACE) - { - // the selection will already be deleted, but we need to trim - // off the character before - std::string new_text = raw_text.substr(0, length-1); - self->mInputEditor->setText( new_text ); - self->mInputEditor->setCursorToEnd(); - length = length - 1; - } - */ - - KEY key = gKeyboard->currentKey(); - - // Ignore "special" keys, like backspace, arrows, etc. - if (length > 1 - && raw_text[0] == '/' - && key < KEY_SPECIAL) - { - // we're starting a gesture, attempt to autocomplete - - std::string utf8_trigger = wstring_to_utf8str(raw_text); - std::string utf8_out_str(utf8_trigger); - - if (LLGestureMgr::instance().matchPrefix(utf8_trigger, &utf8_out_str)) - { - if (self->mInputEditor) - { - std::string rest_of_match = utf8_out_str.substr(utf8_trigger.size()); - self->mInputEditor->setText(utf8_trigger + rest_of_match); // keep original capitalization for user-entered part - S32 outlength = self->mInputEditor->getLength(); // in characters - - // Select to end of line, starting from the character - // after the last one the user typed. - self->mInputEditor->setSelection(length, outlength); - } - } - - //LL_INFOS() << "GESTUREDEBUG " << trigger - // << " len " << length - // << " outlen " << out_str.getLength() - // << LL_ENDL; - } -} - -// static -void LLChatBar::onInputEditorFocusLost() -{ - // stop typing animation - gAgent.stopTyping(); -} - -// static -void LLChatBar::onInputEditorGainFocus() -{ - //LLFloaterChat::setHistoryCursorAndScrollToEnd(); -} - -void LLChatBar::onClickSay( LLUICtrl* ctrl ) -{ - std::string cmd = ctrl->getValue().asString(); - e_chat_type chat_type = CHAT_TYPE_NORMAL; - if (cmd == "shout") - { - chat_type = CHAT_TYPE_SHOUT; - } - else if (cmd == "whisper") - { - chat_type = CHAT_TYPE_WHISPER; - } - sendChat(chat_type); -} - -void LLChatBar::sendChatFromViewer(const std::string &utf8text, EChatType type, bool animate) -{ - sendChatFromViewer(utf8str_to_wstring(utf8text), type, animate); -} - -void LLChatBar::sendChatFromViewer(const LLWString &wtext, EChatType type, bool animate) -{ - // as soon as we say something, we no longer care about teaching the user - // how to chat - gWarningSettings.setBOOL("FirstOtherChatBeforeUser", false); - - // Look for "/20 foo" channel chats. - S32 channel = 0; - LLWString out_text = stripChannelNumber(wtext, &channel); - std::string utf8_out_text = wstring_to_utf8str(out_text); - if (!utf8_out_text.empty()) - { - utf8_out_text = utf8str_truncate(utf8_out_text, MAX_MSG_STR_LEN); - } - - std::string utf8_text = wstring_to_utf8str(wtext); - utf8_text = utf8str_trim(utf8_text); - if (!utf8_text.empty()) - { - utf8_text = utf8str_truncate(utf8_text, MAX_STRING - 1); - } - - // Don't animate for chats people can't hear (chat to scripts) - if (animate && (channel == 0)) - { - if (type == CHAT_TYPE_WHISPER) - { - LL_DEBUGS() << "You whisper " << utf8_text << LL_ENDL; - gAgent.sendAnimationRequest(ANIM_AGENT_WHISPER, ANIM_REQUEST_START); - } - else if (type == CHAT_TYPE_NORMAL) - { - LL_DEBUGS() << "You say " << utf8_text << LL_ENDL; - gAgent.sendAnimationRequest(ANIM_AGENT_TALK, ANIM_REQUEST_START); - } - else if (type == CHAT_TYPE_SHOUT) - { - LL_DEBUGS() << "You shout " << utf8_text << LL_ENDL; - gAgent.sendAnimationRequest(ANIM_AGENT_SHOUT, ANIM_REQUEST_START); - } - else - { - LL_INFOS() << "send_chat_from_viewer() - invalid volume" << LL_ENDL; - return; - } - } - else - { - if (type != CHAT_TYPE_START && type != CHAT_TYPE_STOP) - { - LL_DEBUGS() << "Channel chat: " << utf8_text << LL_ENDL; - } - } - - send_chat_from_viewer(utf8_out_text, type, channel); -} - -void LLChatBar::onCommitGesture(LLUICtrl* ctrl) -{ - LLCtrlListInterface* gestures = mGestureCombo ? mGestureCombo->getListInterface() : NULL; - if (gestures) - { - S32 index = gestures->getFirstSelectedIndex(); - if (index == 0) - { - return; - } - const std::string& trigger = gestures->getSelectedValue().asString(); - - // pretend the user chatted the trigger string, to invoke - // substitution and logging. - std::string text(trigger); - std::string revised_text; - LLGestureMgr::instance().triggerAndReviseString(text, &revised_text); - - revised_text = utf8str_trim(revised_text); - if (!revised_text.empty()) - { - // Don't play nodding animation - sendChatFromViewer(revised_text, CHAT_TYPE_NORMAL, false); - } - } - mGestureLabelTimer.start(); - if (mGestureCombo != NULL) - { - // free focus back to chat bar - mGestureCombo->setFocus(false); - } -} +/** + * @file llchatbar.cpp + * @brief LLChatBar class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llchatbar.h" + +#include "llfontgl.h" +#include "llrect.h" +#include "llerror.h" +#include "llparcel.h" +#include "llstring.h" +#include "message.h" +#include "llfocusmgr.h" + +#include "llagent.h" +#include "llbutton.h" +#include "llcombobox.h" +#include "llcommandhandler.h" // secondlife:///app/chat/ support +#include "llviewercontrol.h" +#include "llgesturemgr.h" +#include "llkeyboard.h" +#include "lllineeditor.h" +#include "llstatusbar.h" +#include "lltextbox.h" +#include "lluiconstants.h" +#include "llviewergesture.h" // for triggering gestures +#include "llviewermenu.h" // for deleting object with DEL key +#include "llviewerstats.h" +#include "llviewerwindow.h" +#include "llframetimer.h" +#include "llresmgr.h" +#include "llworld.h" +#include "llinventorymodel.h" +#include "llmultigesture.h" +#include "llui.h" +#include "lluictrlfactory.h" +#include "lluiusage.h" + +// +// Globals +// +constexpr F32 AGENT_TYPING_TIMEOUT = 5.f; // seconds + +LLChatBar *gChatBar = NULL; + +class LLChatBarGestureObserver : public LLGestureManagerObserver +{ +public: + LLChatBarGestureObserver(LLChatBar* chat_barp) : mChatBar(chat_barp){} + virtual ~LLChatBarGestureObserver() {} + virtual void changed() { mChatBar->refreshGestures(); } +private: + LLChatBar* mChatBar; +}; + + +extern void send_chat_from_viewer(const std::string& utf8_out_text, EChatType type, S32 channel); + +// +// Functions +// + +LLChatBar::LLChatBar() +: LLPanel(), + mInputEditor(NULL), + mGestureLabelTimer(), + mLastSpecialChatChannel(0), + mIsBuilt(false), + mGestureCombo(NULL), + mObserver(NULL) +{ + //setIsChrome(true); +} + + +LLChatBar::~LLChatBar() +{ + LLGestureMgr::instance().removeObserver(mObserver); + delete mObserver; + mObserver = NULL; + // LLView destructor cleans up children +} + +bool LLChatBar::postBuild() +{ + getChild("Say")->setCommitCallback(boost::bind(&LLChatBar::onClickSay, this, _1)); + + // * NOTE: mantipov: getChild with default parameters returns dummy widget. + // Seems this class will be completle removed + // attempt to bind to an existing combo box named gesture + setGestureCombo(findChild( "Gesture")); + + mInputEditor = getChild("Chat Editor"); + mInputEditor->setKeystrokeCallback(&onInputEditorKeystroke, this); + mInputEditor->setFocusLostCallback(boost::bind(&LLChatBar::onInputEditorFocusLost)); + mInputEditor->setFocusReceivedCallback(boost::bind(&LLChatBar::onInputEditorGainFocus)); + mInputEditor->setCommitOnFocusLost( false ); + mInputEditor->setRevertOnEsc( false ); + mInputEditor->setIgnoreTab(true); + mInputEditor->setPassDelete(true); + mInputEditor->setReplaceNewlinesWithSpaces(false); + + mInputEditor->setMaxTextLength(DB_CHAT_MSG_STR_LEN); + mInputEditor->setEnableLineHistory(true); + + mIsBuilt = true; + + return true; +} + +//----------------------------------------------------------------------- +// Overrides +//----------------------------------------------------------------------- + +// virtual +bool LLChatBar::handleKeyHere( KEY key, MASK mask ) +{ + bool handled = false; + + if( KEY_RETURN == key ) + { + if (mask == MASK_CONTROL) + { + // shout + sendChat(CHAT_TYPE_SHOUT); + handled = true; + } + else if (mask == MASK_NONE) + { + // say + sendChat( CHAT_TYPE_NORMAL ); + handled = true; + } + } + // only do this in main chatbar + else if ( KEY_ESCAPE == key && gChatBar == this) + { + stopChat(); + + handled = true; + } + + return handled; +} + +void LLChatBar::refresh() +{ + // HACK: Leave the name of the gesture in place for a few seconds. + const F32 SHOW_GESTURE_NAME_TIME = 2.f; + if (mGestureLabelTimer.getStarted() && mGestureLabelTimer.getElapsedTimeF32() > SHOW_GESTURE_NAME_TIME) + { + LLCtrlListInterface* gestures = mGestureCombo ? mGestureCombo->getListInterface() : NULL; + if (gestures) gestures->selectFirstItem(); + mGestureLabelTimer.stop(); + } + + if ((gAgent.getTypingTime() > AGENT_TYPING_TIMEOUT) && (gAgent.getRenderState() & AGENT_STATE_TYPING)) + { + gAgent.stopTyping(); + } + + getChildView("Say")->setEnabled(mInputEditor->getText().size() > 0); + +} + +void LLChatBar::refreshGestures() +{ + if (mGestureCombo) + { + //store current selection so we can maintain it + std::string cur_gesture = mGestureCombo->getValue().asString(); + mGestureCombo->selectFirstItem(); + std::string label = mGestureCombo->getValue().asString();; + // clear + mGestureCombo->clearRows(); + + // collect list of unique gestures + std::map unique; + LLGestureMgr::item_map_t::const_iterator it; + const LLGestureMgr::item_map_t& active_gestures = LLGestureMgr::instance().getActiveGestures(); + for (it = active_gestures.begin(); it != active_gestures.end(); ++it) + { + LLMultiGesture* gesture = (*it).second; + if (gesture) + { + if (!gesture->mTrigger.empty()) + { + unique[gesture->mTrigger] = true; + } + } + } + + // add unique gestures + std::map ::iterator it2; + for (it2 = unique.begin(); it2 != unique.end(); ++it2) + { + mGestureCombo->addSimpleElement((*it2).first); + } + + mGestureCombo->sortByName(); + // Insert label after sorting, at top, with separator below it + mGestureCombo->addSeparator(ADD_TOP); + mGestureCombo->addSimpleElement(getString("gesture_label"), ADD_TOP); + + if (!cur_gesture.empty()) + { + mGestureCombo->selectByValue(LLSD(cur_gesture)); + } + else + { + mGestureCombo->selectFirstItem(); + } + } +} + +// Move the cursor to the correct input field. +void LLChatBar::setKeyboardFocus(bool focus) +{ + if (focus) + { + if (mInputEditor) + { + mInputEditor->setFocus(true); + mInputEditor->selectAll(); + } + } + else if (gFocusMgr.childHasKeyboardFocus(this)) + { + if (mInputEditor) + { + mInputEditor->deselect(); + } + setFocus(false); + } +} + + +// Ignore arrow keys in chat bar +void LLChatBar::setIgnoreArrowKeys(bool b) +{ + if (mInputEditor) + { + mInputEditor->setIgnoreArrowKeys(b); + } +} + +bool LLChatBar::inputEditorHasFocus() +{ + return mInputEditor && mInputEditor->hasFocus(); +} + +std::string LLChatBar::getCurrentChat() +{ + return mInputEditor ? mInputEditor->getText() : LLStringUtil::null; +} + +void LLChatBar::setGestureCombo(LLComboBox* combo) +{ + mGestureCombo = combo; + if (mGestureCombo) + { + mGestureCombo->setCommitCallback(boost::bind(&LLChatBar::onCommitGesture, this, _1)); + + // now register observer since we have a place to put the results + mObserver = new LLChatBarGestureObserver(this); + LLGestureMgr::instance().addObserver(mObserver); + + // refresh list from current active gestures + refreshGestures(); + } +} + +//----------------------------------------------------------------------- +// Internal functions +//----------------------------------------------------------------------- + +// If input of the form "/20foo" or "/20 foo", returns "foo" and channel 20. +// Otherwise returns input and channel 0. +LLWString LLChatBar::stripChannelNumber(const LLWString &mesg, S32* channel) +{ + if (mesg[0] == '/' + && mesg[1] == '/') + { + // This is a "repeat channel send" + *channel = mLastSpecialChatChannel; + return mesg.substr(2, mesg.length() - 2); + } + else if (mesg[0] == '/' + && mesg[1] + && (LLStringOps::isDigit(mesg[1]) + || (mesg[1] == '-' && mesg[2] && LLStringOps::isDigit(mesg[2])))) + { + // This a special "/20" speak on a channel + S32 pos = 0; + + // Copy the channel number into a string + LLWString channel_string; + llwchar c; + do + { + c = mesg[pos+1]; + channel_string.push_back(c); + pos++; + } + while(c && pos < 64 && (LLStringOps::isDigit(c) || (pos == 1 && c == '-'))); + + // Move the pointer forward to the first non-whitespace char + // Check isspace before looping, so we can handle "/33foo" + // as well as "/33 foo" + while(c && iswspace(c)) + { + c = mesg[pos+1]; + pos++; + } + + mLastSpecialChatChannel = strtol(wstring_to_utf8str(channel_string).c_str(), NULL, 10); + *channel = mLastSpecialChatChannel; + return mesg.substr(pos, mesg.length() - pos); + } + else + { + // This is normal chat. + *channel = 0; + return mesg; + } +} + + +void LLChatBar::sendChat( EChatType type ) +{ + if (mInputEditor) + { + LLWString text = mInputEditor->getConvertedText(); + if (!text.empty()) + { + // store sent line in history, duplicates will get filtered + if (mInputEditor) mInputEditor->updateHistory(); + // Check if this is destined for another channel + S32 channel = 0; + stripChannelNumber(text, &channel); + + std::string utf8text = wstring_to_utf8str(text); + // Try to trigger a gesture, if not chat to a script. + std::string utf8_revised_text; + if (0 == channel) + { + // discard returned "found" boolean + LLGestureMgr::instance().triggerAndReviseString(utf8text, &utf8_revised_text); + } + else + { + utf8_revised_text = utf8text; + } + + utf8_revised_text = utf8str_trim(utf8_revised_text); + + if (!utf8_revised_text.empty()) + { + // Chat with animation + sendChatFromViewer(utf8_revised_text, type, gSavedSettings.getBOOL("PlayChatAnim")); + } + } + } + + getChild("Chat Editor")->setValue(LLStringUtil::null); + + gAgent.stopTyping(); + + // If the user wants to stop chatting on hitting return, lose focus + // and go out of chat mode. + if (gChatBar == this && gSavedSettings.getBOOL("CloseChatOnReturn")) + { + stopChat(); + } +} + + +//----------------------------------------------------------------------- +// Static functions +//----------------------------------------------------------------------- + +// static +void LLChatBar::startChat(const char* line) +{ + //TODO* remove DUMMY chat + //if(gBottomTray && gBottomTray->getChatBox()) + //{ + // gBottomTray->setVisible(true); + // gBottomTray->getChatBox()->setFocus(true); + //} + + // *TODO Vadim: Why was this code commented out? + +// gChatBar->setVisible(true); +// gChatBar->setKeyboardFocus(true); +// gSavedSettings.setBOOL("ChatVisible", true); +// +// if (line && gChatBar->mInputEditor) +// { +// std::string line_string(line); +// gChatBar->mInputEditor->setText(line_string); +// } +// // always move cursor to end so users don't obliterate chat when accidentally hitting WASD +// gChatBar->mInputEditor->setCursorToEnd(); +} + + +// Exit "chat mode" and do the appropriate focus changes +// static +void LLChatBar::stopChat() +{ + //TODO* remove DUMMY chat + //if(gBottomTray && gBottomTray->getChatBox()) + ///{ + // gBottomTray->getChatBox()->setFocus(false); + //} + + // *TODO Vadim: Why was this code commented out? + +// // In simple UI mode, we never release focus from the chat bar +// gChatBar->setKeyboardFocus(false); +// +// // If we typed a movement key and pressed return during the +// // same frame, the keyboard handlers will see the key as having +// // gone down this frame and try to move the avatar. +// gKeyboard->resetKeys(); +// gKeyboard->resetMaskKeys(); +// +// // stop typing animation +// gAgent.stopTyping(); +// +// // hide chat bar so it doesn't grab focus back +// gChatBar->setVisible(false); +// gSavedSettings.setBOOL("ChatVisible", false); +} + +// static +void LLChatBar::onInputEditorKeystroke( LLLineEditor* caller, void* userdata ) +{ + LLChatBar* self = (LLChatBar *)userdata; + + LLWString raw_text; + if (self->mInputEditor) raw_text = self->mInputEditor->getWText(); + + // Can't trim the end, because that will cause autocompletion + // to eat trailing spaces that might be part of a gesture. + LLWStringUtil::trimHead(raw_text); + + S32 length = raw_text.length(); + + if( (length > 0) && (raw_text[0] != '/') ) // forward slash is used for escape (eg. emote) sequences + { + gAgent.startTyping(); + } + else + { + gAgent.stopTyping(); + } + + /* Doesn't work -- can't tell the difference between a backspace + that killed the selection vs. backspace at the end of line. + if (length > 1 + && text[0] == '/' + && key == KEY_BACKSPACE) + { + // the selection will already be deleted, but we need to trim + // off the character before + std::string new_text = raw_text.substr(0, length-1); + self->mInputEditor->setText( new_text ); + self->mInputEditor->setCursorToEnd(); + length = length - 1; + } + */ + + KEY key = gKeyboard->currentKey(); + + // Ignore "special" keys, like backspace, arrows, etc. + if (length > 1 + && raw_text[0] == '/' + && key < KEY_SPECIAL) + { + // we're starting a gesture, attempt to autocomplete + + std::string utf8_trigger = wstring_to_utf8str(raw_text); + std::string utf8_out_str(utf8_trigger); + + if (LLGestureMgr::instance().matchPrefix(utf8_trigger, &utf8_out_str)) + { + if (self->mInputEditor) + { + std::string rest_of_match = utf8_out_str.substr(utf8_trigger.size()); + self->mInputEditor->setText(utf8_trigger + rest_of_match); // keep original capitalization for user-entered part + S32 outlength = self->mInputEditor->getLength(); // in characters + + // Select to end of line, starting from the character + // after the last one the user typed. + self->mInputEditor->setSelection(length, outlength); + } + } + + //LL_INFOS() << "GESTUREDEBUG " << trigger + // << " len " << length + // << " outlen " << out_str.getLength() + // << LL_ENDL; + } +} + +// static +void LLChatBar::onInputEditorFocusLost() +{ + // stop typing animation + gAgent.stopTyping(); +} + +// static +void LLChatBar::onInputEditorGainFocus() +{ + //LLFloaterChat::setHistoryCursorAndScrollToEnd(); +} + +void LLChatBar::onClickSay( LLUICtrl* ctrl ) +{ + std::string cmd = ctrl->getValue().asString(); + e_chat_type chat_type = CHAT_TYPE_NORMAL; + if (cmd == "shout") + { + chat_type = CHAT_TYPE_SHOUT; + } + else if (cmd == "whisper") + { + chat_type = CHAT_TYPE_WHISPER; + } + sendChat(chat_type); +} + +void LLChatBar::sendChatFromViewer(const std::string &utf8text, EChatType type, bool animate) +{ + sendChatFromViewer(utf8str_to_wstring(utf8text), type, animate); +} + +void LLChatBar::sendChatFromViewer(const LLWString &wtext, EChatType type, bool animate) +{ + // as soon as we say something, we no longer care about teaching the user + // how to chat + gWarningSettings.setBOOL("FirstOtherChatBeforeUser", false); + + // Look for "/20 foo" channel chats. + S32 channel = 0; + LLWString out_text = stripChannelNumber(wtext, &channel); + std::string utf8_out_text = wstring_to_utf8str(out_text); + if (!utf8_out_text.empty()) + { + utf8_out_text = utf8str_truncate(utf8_out_text, MAX_MSG_STR_LEN); + } + + std::string utf8_text = wstring_to_utf8str(wtext); + utf8_text = utf8str_trim(utf8_text); + if (!utf8_text.empty()) + { + utf8_text = utf8str_truncate(utf8_text, MAX_STRING - 1); + } + + // Don't animate for chats people can't hear (chat to scripts) + if (animate && (channel == 0)) + { + if (type == CHAT_TYPE_WHISPER) + { + LL_DEBUGS() << "You whisper " << utf8_text << LL_ENDL; + gAgent.sendAnimationRequest(ANIM_AGENT_WHISPER, ANIM_REQUEST_START); + } + else if (type == CHAT_TYPE_NORMAL) + { + LL_DEBUGS() << "You say " << utf8_text << LL_ENDL; + gAgent.sendAnimationRequest(ANIM_AGENT_TALK, ANIM_REQUEST_START); + } + else if (type == CHAT_TYPE_SHOUT) + { + LL_DEBUGS() << "You shout " << utf8_text << LL_ENDL; + gAgent.sendAnimationRequest(ANIM_AGENT_SHOUT, ANIM_REQUEST_START); + } + else + { + LL_INFOS() << "send_chat_from_viewer() - invalid volume" << LL_ENDL; + return; + } + } + else + { + if (type != CHAT_TYPE_START && type != CHAT_TYPE_STOP) + { + LL_DEBUGS() << "Channel chat: " << utf8_text << LL_ENDL; + } + } + + send_chat_from_viewer(utf8_out_text, type, channel); +} + +void LLChatBar::onCommitGesture(LLUICtrl* ctrl) +{ + LLCtrlListInterface* gestures = mGestureCombo ? mGestureCombo->getListInterface() : NULL; + if (gestures) + { + S32 index = gestures->getFirstSelectedIndex(); + if (index == 0) + { + return; + } + const std::string& trigger = gestures->getSelectedValue().asString(); + + // pretend the user chatted the trigger string, to invoke + // substitution and logging. + std::string text(trigger); + std::string revised_text; + LLGestureMgr::instance().triggerAndReviseString(text, &revised_text); + + revised_text = utf8str_trim(revised_text); + if (!revised_text.empty()) + { + // Don't play nodding animation + sendChatFromViewer(revised_text, CHAT_TYPE_NORMAL, false); + } + } + mGestureLabelTimer.start(); + if (mGestureCombo != NULL) + { + // free focus back to chat bar + mGestureCombo->setFocus(false); + } +} diff --git a/indra/newview/llchatbar.h b/indra/newview/llchatbar.h index e538f72533..5eede61708 100644 --- a/indra/newview/llchatbar.h +++ b/indra/newview/llchatbar.h @@ -1,112 +1,112 @@ -/** - * @file llchatbar.h - * @brief LLChatBar class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLCHATBAR_H -#define LL_LLCHATBAR_H - -#include "llpanel.h" -#include "llframetimer.h" -#include "llchat.h" - -class LLLineEditor; -class LLMessageSystem; -class LLUICtrl; -class LLUUID; -class LLFrameTimer; -class LLChatBarGestureObserver; -class LLComboBox; - - -class LLChatBar -: public LLPanel -{ -public: - // constructor for inline chat-bars (e.g. hosted in chat history window) - LLChatBar(); - ~LLChatBar(); - virtual bool postBuild(); - - virtual bool handleKeyHere(KEY key, MASK mask); - - void refresh(); - void refreshGestures(); - - // Move cursor into chat input field. - void setKeyboardFocus(bool b); - - // Ignore arrow keys for chat bar - void setIgnoreArrowKeys(bool b); - - bool inputEditorHasFocus(); - std::string getCurrentChat(); - - // since chat bar logic is reused for chat history - // gesture combo box might not be a direct child - void setGestureCombo(LLComboBox* combo); - - // Send a chat (after stripping /20foo channel chats). - // "Animate" means the nodding animation for regular text. - void sendChatFromViewer(const LLWString &wtext, EChatType type, bool animate); - void sendChatFromViewer(const std::string &utf8text, EChatType type, bool animate); - - // If input of the form "/20foo" or "/20 foo", returns "foo" and channel 20. - // Otherwise returns input and channel 0. - LLWString stripChannelNumber(const LLWString &mesg, S32* channel); - - // callbacks - void onClickSay(LLUICtrl* ctrl); - - static void onTabClick( void* userdata ); - static void onInputEditorKeystroke(LLLineEditor* caller, void* userdata); - static void onInputEditorFocusLost(); - static void onInputEditorGainFocus(); - - void onCommitGesture(LLUICtrl* ctrl); - - static void startChat(const char* line); - static void stopChat(); - -protected: - void sendChat(EChatType type); - void updateChat(); - -protected: - LLLineEditor* mInputEditor; - - LLFrameTimer mGestureLabelTimer; - - // Which non-zero channel did we last chat on? - S32 mLastSpecialChatChannel; - - bool mIsBuilt; - LLComboBox* mGestureCombo; - - LLChatBarGestureObserver* mObserver; -}; - -extern LLChatBar *gChatBar; - -#endif +/** + * @file llchatbar.h + * @brief LLChatBar class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLCHATBAR_H +#define LL_LLCHATBAR_H + +#include "llpanel.h" +#include "llframetimer.h" +#include "llchat.h" + +class LLLineEditor; +class LLMessageSystem; +class LLUICtrl; +class LLUUID; +class LLFrameTimer; +class LLChatBarGestureObserver; +class LLComboBox; + + +class LLChatBar +: public LLPanel +{ +public: + // constructor for inline chat-bars (e.g. hosted in chat history window) + LLChatBar(); + ~LLChatBar(); + virtual bool postBuild(); + + virtual bool handleKeyHere(KEY key, MASK mask); + + void refresh(); + void refreshGestures(); + + // Move cursor into chat input field. + void setKeyboardFocus(bool b); + + // Ignore arrow keys for chat bar + void setIgnoreArrowKeys(bool b); + + bool inputEditorHasFocus(); + std::string getCurrentChat(); + + // since chat bar logic is reused for chat history + // gesture combo box might not be a direct child + void setGestureCombo(LLComboBox* combo); + + // Send a chat (after stripping /20foo channel chats). + // "Animate" means the nodding animation for regular text. + void sendChatFromViewer(const LLWString &wtext, EChatType type, bool animate); + void sendChatFromViewer(const std::string &utf8text, EChatType type, bool animate); + + // If input of the form "/20foo" or "/20 foo", returns "foo" and channel 20. + // Otherwise returns input and channel 0. + LLWString stripChannelNumber(const LLWString &mesg, S32* channel); + + // callbacks + void onClickSay(LLUICtrl* ctrl); + + static void onTabClick( void* userdata ); + static void onInputEditorKeystroke(LLLineEditor* caller, void* userdata); + static void onInputEditorFocusLost(); + static void onInputEditorGainFocus(); + + void onCommitGesture(LLUICtrl* ctrl); + + static void startChat(const char* line); + static void stopChat(); + +protected: + void sendChat(EChatType type); + void updateChat(); + +protected: + LLLineEditor* mInputEditor; + + LLFrameTimer mGestureLabelTimer; + + // Which non-zero channel did we last chat on? + S32 mLastSpecialChatChannel; + + bool mIsBuilt; + LLComboBox* mGestureCombo; + + LLChatBarGestureObserver* mObserver; +}; + +extern LLChatBar *gChatBar; + +#endif diff --git a/indra/newview/llchathistory.cpp b/indra/newview/llchathistory.cpp index 75f3cb11a8..3893b21c2a 100644 --- a/indra/newview/llchathistory.cpp +++ b/indra/newview/llchathistory.cpp @@ -1,1548 +1,1548 @@ -/** - * @file llchathistory.cpp - * @brief LLTextEditor base class - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llchathistory.h" - -#include - -#include "llavatarnamecache.h" -#include "llinstantmessage.h" - -#include "llimview.h" -#include "llcommandhandler.h" -#include "llpanel.h" -#include "lluictrlfactory.h" -#include "llscrollcontainer.h" -#include "llagent.h" -#include "llagentdata.h" -#include "llavataractions.h" -#include "llavatariconctrl.h" -#include "llcallingcard.h" //for LLAvatarTracker -#include "llgroupactions.h" -#include "llgroupmgr.h" -#include "llspeakers.h" //for LLIMSpeakerMgr -#include "lltrans.h" -#include "llfloaterreg.h" -#include "llfloaterreporter.h" -#include "llfloatersidepanelcontainer.h" -#include "llmutelist.h" -#include "llstylemap.h" -#include "llslurl.h" -#include "lllayoutstack.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "lltoastnotifypanel.h" -#include "lltooltip.h" -#include "llviewerregion.h" -#include "llviewertexteditor.h" -#include "llworld.h" -#include "lluiconstants.h" -#include "llstring.h" -#include "llurlaction.h" -#include "llviewercontrol.h" -#include "llviewermenu.h" -#include "llviewerobjectlist.h" - -static LLDefaultChildRegistry::Register r("chat_history"); - -const static std::string NEW_LINE(rawstr_to_utf8("\n")); - -const static std::string SLURL_APP_AGENT = "secondlife:///app/agent/"; -const static std::string SLURL_ABOUT = "/about"; - -// support for secondlife:///app/objectim/{UUID}/ SLapps -class LLObjectIMHandler : public LLCommandHandler -{ -public: - // requests will be throttled from a non-trusted browser - LLObjectIMHandler() : LLCommandHandler("objectim", UNTRUSTED_THROTTLE) {} - - bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) - { - if (params.size() < 1) - { - return false; - } - - LLUUID object_id; - if (!object_id.set(params[0], false)) - { - return false; - } - - LLSD payload; - payload["object_id"] = object_id; - payload["owner_id"] = query_map["owner"]; - payload["name"] = query_map["name"]; - payload["slurl"] = LLWeb::escapeURL(query_map["slurl"]); - payload["group_owned"] = query_map["groupowned"]; - LLFloaterReg::showInstance("inspect_remote_object", payload); - return true; - } -}; -LLObjectIMHandler gObjectIMHandler; - -class LLChatHistoryHeader: public LLPanel -{ -public: - LLChatHistoryHeader() - : LLPanel(), - mInfoCtrl(NULL), - mPopupMenuHandleAvatar(), - mPopupMenuHandleObject(), - mAvatarID(), - mSourceType(CHAT_SOURCE_UNKNOWN), - mFrom(), - mSessionID(), - mCreationTime(time_corrected()), - mMinUserNameWidth(0), - mUserNameFont(NULL), - mUserNameTextBox(NULL), - mTimeBoxTextBox(NULL), - mNeedsTimeBox(true), - mAvatarNameCacheConnection() - {} - - static LLChatHistoryHeader* createInstance(const std::string& file_name) - { - LLChatHistoryHeader* pInstance = new LLChatHistoryHeader; - pInstance->buildFromFile(file_name); - return pInstance; - } - - ~LLChatHistoryHeader() - { - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - auto menu = mPopupMenuHandleAvatar.get(); - if (menu) - { - menu->die(); - mPopupMenuHandleAvatar.markDead(); - } - menu = mPopupMenuHandleObject.get(); - if (menu) - { - menu->die(); - mPopupMenuHandleObject.markDead(); - } - } - - bool handleMouseUp(S32 x, S32 y, MASK mask) - { - return LLPanel::handleMouseUp(x,y,mask); - } - - void onObjectIconContextMenuItemClicked(const LLSD& userdata) - { - std::string level = userdata.asString(); - - if (level == "profile") - { - LLFloaterReg::showInstance("inspect_remote_object", mObjectData); - } - else if (level == "block") - { - LLMuteList::getInstance()->add(LLMute(getAvatarId(), mFrom, LLMute::OBJECT)); - - LLFloaterSidePanelContainer::showPanel("people", "panel_people", - LLSD().with("people_panel_tab_name", "blocked_panel").with("blocked_to_select", getAvatarId())); - } - else if (level == "unblock") - { - LLMuteList::getInstance()->remove(LLMute(getAvatarId(), mFrom, LLMute::OBJECT)); - } - else if (level == "map") - { - std::string url = "secondlife://" + mObjectData["slurl"].asString(); - LLUrlAction::showLocationOnMap(url); - } - else if (level == "teleport") - { - std::string url = "secondlife://" + mObjectData["slurl"].asString(); - LLUrlAction::teleportToLocation(url); - } - - } - - bool onObjectIconContextMenuItemVisible(const LLSD& userdata) - { - std::string level = userdata.asString(); - if (level == "is_blocked") - { - return LLMuteList::getInstance()->isMuted(getAvatarId(), mFrom, LLMute::flagTextChat); - } - else if (level == "not_blocked") - { - return !LLMuteList::getInstance()->isMuted(getAvatarId(), mFrom, LLMute::flagTextChat); - } - return false; - } - - void banGroupMember(const LLUUID& participant_uuid) - { - LLUUID group_uuid = mSessionID; - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_uuid); - if (!gdatap) - { - // Not a group - return; - } - - gdatap->banMemberById(participant_uuid); - } - - bool canBanInGroup() - { - LLUUID group_uuid = mSessionID; - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_uuid); - if (!gdatap) - { - // Not a group - return false; - } - - if (gAgent.hasPowerInGroup(group_uuid, GP_ROLE_REMOVE_MEMBER) - && gAgent.hasPowerInGroup(group_uuid, GP_GROUP_BAN_ACCESS)) - { - return true; - } - - return false; - } - - bool canBanGroupMember(const LLUUID& participant_uuid) - { - LLUUID group_uuid = mSessionID; - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_uuid); - if (!gdatap) - { - // Not a group - return false; - } - - if (gdatap->mPendingBanRequest) - { - return false; - } - - if (gAgentID == getAvatarId()) - { - //Don't ban self - return false; - } - - if (gdatap->isRoleMemberDataComplete()) - { - if (gdatap->mMembers.size()) - { - LLGroupMgrGroupData::member_list_t::iterator mi = gdatap->mMembers.find(participant_uuid); - if (mi != gdatap->mMembers.end()) - { - LLGroupMemberData* member_data = (*mi).second; - // Is the member an owner? - if (member_data && member_data->isInRole(gdatap->mOwnerRole)) - { - return false; - } - - if (gAgent.hasPowerInGroup(group_uuid, GP_ROLE_REMOVE_MEMBER) - && gAgent.hasPowerInGroup(group_uuid, GP_GROUP_BAN_ACCESS)) - { - return true; - } - } - } - } - - LLSpeakerMgr * speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - if (speaker_mgr) - { - LLSpeaker * speakerp = speaker_mgr->findSpeaker(participant_uuid).get(); - - if (speakerp - && gAgent.hasPowerInGroup(group_uuid, GP_ROLE_REMOVE_MEMBER) - && gAgent.hasPowerInGroup(group_uuid, GP_GROUP_BAN_ACCESS)) - { - return true; - } - } - - return false; - } - - bool isGroupModerator() - { - LLSpeakerMgr * speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - if (!speaker_mgr) - { - LL_WARNS() << "Speaker manager is missing" << LL_ENDL; - return false; - } - - // Is session a group call/chat? - if(gAgent.isInGroup(mSessionID)) - { - LLSpeaker * speakerp = speaker_mgr->findSpeaker(gAgentID).get(); - - // Is agent a moderator? - return speakerp && speakerp->mIsModerator; - } - - return false; - } - - bool canModerate(const std::string& userdata) - { - // only group moderators can perform actions related to this "enable callback" - if (!isGroupModerator() || gAgentID == getAvatarId()) - { - return false; - } - - LLSpeakerMgr * speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - if (!speaker_mgr) - { - return false; - } - - LLSpeaker * speakerp = speaker_mgr->findSpeaker(getAvatarId()).get(); - if (!speakerp) - { - return false; - } - - bool voice_channel = speakerp->isInVoiceChannel(); - - if ("can_moderate_voice" == userdata) - { - return voice_channel; - } - else if ("can_mute" == userdata) - { - return voice_channel && (speakerp->mStatus != LLSpeaker::STATUS_MUTED); - } - else if ("can_unmute" == userdata) - { - return speakerp->mStatus == LLSpeaker::STATUS_MUTED; - } - else if ("can_allow_text_chat" == userdata) - { - return true; - } - - return false; - } - - void onAvatarIconContextMenuItemClicked(const LLSD& userdata) - { - std::string level = userdata.asString(); - - if (level == "profile") - { - LLAvatarActions::showProfile(getAvatarId()); - } - else if (level == "im") - { - LLAvatarActions::startIM(getAvatarId()); - } - else if (level == "teleport") - { - LLAvatarActions::offerTeleport(getAvatarId()); - } - else if (level == "request_teleport") - { - LLAvatarActions::teleportRequest(getAvatarId()); - } - else if (level == "voice_call") - { - LLAvatarActions::startCall(getAvatarId()); - } - else if (level == "chat_history") - { - LLAvatarActions::viewChatHistory(getAvatarId()); - } - else if (level == "add") - { - LLAvatarActions::requestFriendshipDialog(getAvatarId(), mFrom); - } - else if (level == "remove") - { - LLAvatarActions::removeFriendDialog(getAvatarId()); - } - else if (level == "invite_to_group") - { - LLAvatarActions::inviteToGroup(getAvatarId()); - } - else if (level == "zoom_in") - { - handle_zoom_to_object(getAvatarId()); - } - else if (level == "map") - { - LLAvatarActions::showOnMap(getAvatarId()); - } - else if (level == "share") - { - LLAvatarActions::share(getAvatarId()); - } - else if (level == "pay") - { - LLAvatarActions::pay(getAvatarId()); - } - else if (level == "report_abuse") - { - std::string time_string; - if (mTime > 0) // have frame time - { - time_t current_time = time_corrected(); - time_t message_time = current_time - LLFrameTimer::getElapsedSeconds() + mTime; - - time_string = "[" + LLTrans::getString("TimeMonth") + "]/[" - + LLTrans::getString("TimeDay") + "]/[" - + LLTrans::getString("TimeYear") + "] [" - + LLTrans::getString("TimeHour") + "]:[" - + LLTrans::getString("TimeMin") + "]"; - - LLSD substitution; - - substitution["datetime"] = (S32)message_time; - LLStringUtil::format(time_string, substitution); - } - else - { - // From history. This might be empty or not full. - // See LLChatLogParser::parse - time_string = getChild("time_box")->getValue().asString(); - - // Just add current date if not full. - // Should be fine since both times are supposed to be stl - if (!time_string.empty() && time_string.size() < 7) - { - time_string = "[" + LLTrans::getString("TimeMonth") + "]/[" - + LLTrans::getString("TimeDay") + "]/[" - + LLTrans::getString("TimeYear") + "] " + time_string; - - LLSD substitution; - // To avoid adding today's date to yesterday's timestamp, - // use creation time instead of current time - substitution["datetime"] = (S32)mCreationTime; - LLStringUtil::format(time_string, substitution); - } - } - LLFloaterReporter::showFromChat(mAvatarID, mFrom, time_string, mText); - } - else if(level == "block_unblock") - { - LLAvatarActions::toggleMute(getAvatarId(), LLMute::flagVoiceChat); - } - else if(level == "mute_unmute") - { - LLAvatarActions::toggleMute(getAvatarId(), LLMute::flagTextChat); - } - else if(level == "toggle_allow_text_chat") - { - LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - speaker_mgr->toggleAllowTextChat(getAvatarId()); - } - else if(level == "group_mute") - { - LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - if (speaker_mgr) - { - speaker_mgr->moderateVoiceParticipant(getAvatarId(), false); - } - } - else if(level == "group_unmute") - { - LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - if (speaker_mgr) - { - speaker_mgr->moderateVoiceParticipant(getAvatarId(), true); - } - } - else if(level == "ban_member") - { - banGroupMember(getAvatarId()); - } - } - - bool onAvatarIconContextMenuItemChecked(const LLSD& userdata) - { - std::string level = userdata.asString(); - - if (level == "is_blocked") - { - return LLMuteList::getInstance()->isMuted(getAvatarId(), LLMute::flagVoiceChat); - } - if (level == "is_muted") - { - return LLMuteList::getInstance()->isMuted(getAvatarId(), LLMute::flagTextChat); - } - else if (level == "is_allowed_text_chat") - { - if (gAgent.isInGroup(mSessionID)) - { - LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - if(speaker_mgr) - { - const LLSpeaker * speakerp = speaker_mgr->findSpeaker(getAvatarId()); - if (NULL != speakerp) - { - return !speakerp->mModeratorMutedText; - } - } - } - return false; - } - return false; - } - - bool onAvatarIconContextMenuItemEnabled(const LLSD& userdata) - { - std::string level = userdata.asString(); - - if (level == "can_allow_text_chat" || level == "can_mute" || level == "can_unmute") - { - return canModerate(userdata); - } - else if (level == "report_abuse") - { - return gAgentID != mAvatarID; - } - else if (level == "can_ban_member") - { - return canBanGroupMember(getAvatarId()); - } - return false; - } - - bool onAvatarIconContextMenuItemVisible(const LLSD& userdata) - { - std::string level = userdata.asString(); - - if (level == "show_mute") - { - LLSpeakerMgr * speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - if (speaker_mgr) - { - LLSpeaker * speakerp = speaker_mgr->findSpeaker(getAvatarId()).get(); - if (speakerp) - { - return speakerp->isInVoiceChannel() && speakerp->mStatus != LLSpeaker::STATUS_MUTED; - } - } - return false; - } - else if (level == "show_unmute") - { - LLSpeakerMgr * speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - if (speaker_mgr) - { - LLSpeaker * speakerp = speaker_mgr->findSpeaker(getAvatarId()).get(); - if (speakerp) - { - return speakerp->mStatus == LLSpeaker::STATUS_MUTED; - } - } - return false; - } - return false; - } - - bool postBuild() - { - setDoubleClickCallback(boost::bind(&LLChatHistoryHeader::showInspector, this)); - - setMouseEnterCallback(boost::bind(&LLChatHistoryHeader::showInfoCtrl, this)); - setMouseLeaveCallback(boost::bind(&LLChatHistoryHeader::hideInfoCtrl, this)); - - mUserNameTextBox = getChild("user_name"); - mTimeBoxTextBox = getChild("time_box"); - - mInfoCtrl = LLUICtrlFactory::getInstance()->createFromFile("inspector_info_ctrl.xml", this, LLPanel::child_registry_t::instance()); - if (mInfoCtrl) - { - mInfoCtrl->setCommitCallback(boost::bind(&LLChatHistoryHeader::onClickInfoCtrl, mInfoCtrl)); - mInfoCtrl->setVisible(false); - } - else - { - LL_ERRS() << "Failed to create an interface element due to missing or corrupted file inspector_info_ctrl.xml" << LL_ENDL; - } - - return LLPanel::postBuild(); - } - - bool pointInChild(const std::string& name,S32 x,S32 y) - { - LLUICtrl* child = findChild(name); - if(!child) - return false; - - LLView* parent = child->getParent(); - if(parent!=this) - { - x-=parent->getRect().mLeft; - y-=parent->getRect().mBottom; - } - - S32 local_x = x - child->getRect().mLeft ; - S32 local_y = y - child->getRect().mBottom ; - return child->pointInView(local_x, local_y); - } - - bool handleRightMouseDown(S32 x, S32 y, MASK mask) - { - if(pointInChild("avatar_icon",x,y) || pointInChild("user_name",x,y)) - { - showContextMenu(x,y); - return true; - } - - return LLPanel::handleRightMouseDown(x,y,mask); - } - - void showInspector() - { - if (mAvatarID.isNull() && CHAT_SOURCE_SYSTEM != mSourceType && CHAT_SOURCE_REGION != mSourceType) return; - - if (mSourceType == CHAT_SOURCE_OBJECT) - { - LLFloaterReg::showInstance("inspect_remote_object", mObjectData); - } - else if (mSourceType == CHAT_SOURCE_AGENT) - { - LLFloaterReg::showInstance("inspect_avatar", LLSD().with("avatar_id", mAvatarID)); - } - //if chat source is system, you may add "else" here to define behaviour. - } - - static void onClickInfoCtrl(LLUICtrl* info_ctrl) - { - if (!info_ctrl) return; - - LLChatHistoryHeader* header = dynamic_cast(info_ctrl->getParent()); - if (!header) return; - - header->showInspector(); - } - - - const LLUUID& getAvatarId () const { return mAvatarID;} - - void setup(const LLChat& chat, const LLStyle::Params& style_params, const LLSD& args) - { - mAvatarID = chat.mFromID; - mSessionID = chat.mSessionID; - mSourceType = chat.mSourceType; - - // To be able to report a message, we need a copy of it's text - // and it's easier to store text directly than trying to get - // it from a lltextsegment or chat's mEditor - mText = chat.mText; - mTime = chat.mTime; - - //*TODO overly defensive thing, source type should be maintained out there - if((chat.mFromID.isNull() && chat.mFromName.empty()) || (chat.mFromName == SYSTEM_FROM && chat.mFromID.isNull())) - { - mSourceType = CHAT_SOURCE_SYSTEM; - } - - mUserNameFont = style_params.font(); - if (!mUserNameTextBox) - { - mUserNameTextBox = getChild("user_name"); - mTimeBoxTextBox = getChild("time_box"); - } - LLTextBox* user_name = mUserNameTextBox; - user_name->setReadOnlyColor(style_params.readonly_color()); - user_name->setColor(style_params.color()); - - if (mSourceType == CHAT_SOURCE_TELEPORT - && chat.mChatStyle == CHAT_STYLE_TELEPORT_SEP) - { - mFrom = chat.mFromName; - mNeedsTimeBox = false; - user_name->setValue(mFrom); - updateMinUserNameWidth(); - LLColor4 sep_color = LLUIColorTable::instance().getColor("ChatTeleportSeparatorColor"); - setTransparentColor(sep_color); - mTimeBoxTextBox->setVisible(false); - } - else if (chat.mFromName.empty() - || mSourceType == CHAT_SOURCE_SYSTEM) - { - mFrom = LLTrans::getString("SECOND_LIFE"); - if(!chat.mFromName.empty() && (mFrom != chat.mFromName)) - { - mFrom += " (" + chat.mFromName + ")"; - } - user_name->setValue(mFrom); - updateMinUserNameWidth(); - } - else if (mSourceType == CHAT_SOURCE_AGENT - && !mAvatarID.isNull() - && chat.mChatStyle != CHAT_STYLE_HISTORY) - { - // ...from a normal user, lookup the name and fill in later. - // *NOTE: Do not do this for chat history logs, otherwise the viewer - // will flood the People API with lookup requests on startup - - // Start with blank so sample data from XUI XML doesn't - // flash on the screen - user_name->setValue( LLSD() ); - fetchAvatarName(); - } - else if (chat.mChatStyle == CHAT_STYLE_HISTORY || - mSourceType == CHAT_SOURCE_AGENT) - { - //if it's an avatar name with a username add formatting - S32 username_start = chat.mFromName.rfind(" ("); - S32 username_end = chat.mFromName.rfind(')'); - - if (username_start != std::string::npos && - username_end == (chat.mFromName.length() - 1)) - { - mFrom = chat.mFromName.substr(0, username_start); - user_name->setValue(mFrom); - - if (gSavedSettings.getBOOL("NameTagShowUsernames")) - { - std::string username = chat.mFromName.substr(username_start + 2); - username = username.substr(0, username.length() - 1); - LLStyle::Params style_params_name; - LLColor4 userNameColor = LLUIColorTable::instance().getColor("EmphasisColor"); - style_params_name.color(userNameColor); - style_params_name.font.name("SansSerifSmall"); - style_params_name.font.style("NORMAL"); - style_params_name.readonly_color(userNameColor); - user_name->appendText(" - " + username, false, style_params_name); - } - } - else - { - mFrom = chat.mFromName; - user_name->setValue(mFrom); - updateMinUserNameWidth(); - } - } - else - { - // ...from an object, just use name as given - mFrom = chat.mFromName; - user_name->setValue(mFrom); - updateMinUserNameWidth(); - } - - - setTimeField(chat); - - // Set up the icon. - LLAvatarIconCtrl* icon = getChild("avatar_icon"); - - if(mSourceType != CHAT_SOURCE_AGENT || mAvatarID.isNull()) - icon->setDrawTooltip(false); - - switch (mSourceType) - { - case CHAT_SOURCE_AGENT: - icon->setValue(chat.mFromID); - break; - case CHAT_SOURCE_OBJECT: - icon->setValue(LLSD("OBJECT_Icon")); - break; - case CHAT_SOURCE_SYSTEM: - case CHAT_SOURCE_REGION: - icon->setValue(LLSD("SL_Logo")); - break; - case CHAT_SOURCE_TELEPORT: - icon->setValue(LLSD("Command_Destinations_Icon")); - break; - case CHAT_SOURCE_UNKNOWN: - icon->setValue(LLSD("Unknown_Icon")); - } - - // In case the message came from an object, save the object info - // to be able properly show its profile. - if ( chat.mSourceType == CHAT_SOURCE_OBJECT) - { - std::string slurl = args["slurl"].asString(); - if (slurl.empty() && LLWorld::instanceExists()) - { - LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosAgent(chat.mPosAgent); - if(region) - { - LLSLURL region_slurl(region->getName(), chat.mPosAgent); - slurl = region_slurl.getLocationString(); - } - } - - LLSD payload; - payload["object_id"] = chat.mFromID; - payload["name"] = chat.mFromName; - payload["owner_id"] = chat.mOwnerID; - payload["slurl"] = LLWeb::escapeURL(slurl); - - mObjectData = payload; - } - } - - /*virtual*/ void draw() - { - LLTextBox* user_name = mUserNameTextBox; //getChild("user_name"); - LLTextBox* time_box = mTimeBoxTextBox; //getChild("time_box"); - - LLRect user_name_rect = user_name->getRect(); - S32 user_name_width = user_name_rect.getWidth(); - S32 time_box_width = time_box->getRect().getWidth(); - - if (mNeedsTimeBox && !time_box->getVisible() && user_name_width > mMinUserNameWidth) - { - user_name_rect.mRight -= time_box_width; - user_name->reshape(user_name_rect.getWidth(), user_name_rect.getHeight()); - user_name->setRect(user_name_rect); - - time_box->setVisible(true); - } - - LLPanel::draw(); - } - - void updateMinUserNameWidth() - { - if (mUserNameFont) - { - LLTextBox* user_name = getChild("user_name"); - const LLWString& text = user_name->getWText(); - mMinUserNameWidth = mUserNameFont->getWidth(text.c_str()) + PADDING; - } - } - -protected: - static const S32 PADDING = 20; - - void showContextMenu(S32 x,S32 y) - { - if(mSourceType == CHAT_SOURCE_SYSTEM) - showSystemContextMenu(x,y); - if(mAvatarID.notNull() && mSourceType == CHAT_SOURCE_AGENT) - showAvatarContextMenu(x,y); - if(mAvatarID.notNull() && mSourceType == CHAT_SOURCE_OBJECT) - showObjectContextMenu(x,y); - } - - void showSystemContextMenu(S32 x,S32 y) - { - } - - void showObjectContextMenu(S32 x,S32 y) - { - LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandleObject.get(); - if (!menu) - { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - LLUICtrl::EnableCallbackRegistry::ScopedRegistrar registrar_enable; - registrar.add("ObjectIcon.Action", boost::bind(&LLChatHistoryHeader::onObjectIconContextMenuItemClicked, this, _2)); - registrar_enable.add("ObjectIcon.Visible", boost::bind(&LLChatHistoryHeader::onObjectIconContextMenuItemVisible, this, _2)); - - menu = LLUICtrlFactory::getInstance()->createFromFile("menu_object_icon.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - if (menu) - { - mPopupMenuHandleObject = menu->getHandle(); - menu->updateParent(LLMenuGL::sMenuContainer); - LLMenuGL::showPopup(this, menu, x, y); - } - else - { - LL_WARNS() << " Failed to create menu_object_icon.xml" << LL_ENDL; - } - } - else - { - LLMenuGL::showPopup(this, menu, x, y); - } - } - - void showAvatarContextMenu(S32 x,S32 y) - { - LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandleAvatar.get(); - if (!menu) - { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - LLUICtrl::EnableCallbackRegistry::ScopedRegistrar registrar_enable; - registrar.add("AvatarIcon.Action", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemClicked, this, _2)); - registrar_enable.add("AvatarIcon.Check", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemChecked, this, _2)); - registrar_enable.add("AvatarIcon.Enable", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemEnabled, this, _2)); - registrar_enable.add("AvatarIcon.Visible", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemVisible, this, _2)); - - menu = LLUICtrlFactory::getInstance()->createFromFile("menu_avatar_icon.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - if (menu) - { - mPopupMenuHandleAvatar = menu->getHandle(); - } - else - { - LL_WARNS() << " Failed to create menu_avatar_icon.xml" << LL_ENDL; - } - } - - if(menu) - { - bool is_friend = LLAvatarActions::isFriend(mAvatarID); - bool is_group_session = gAgent.isInGroup(mSessionID); - - menu->setItemEnabled("Add Friend", !is_friend); - menu->setItemEnabled("Remove Friend", is_friend); - menu->setItemVisible("Moderator Options Separator", is_group_session && isGroupModerator()); - menu->setItemVisible("Moderator Options", is_group_session && isGroupModerator()); - menu->setItemVisible("Group Ban Separator", is_group_session && canBanInGroup()); - menu->setItemVisible("BanMember", is_group_session && canBanInGroup()); - - if(gAgentID == mAvatarID) - { - menu->setItemEnabled("Add Friend", false); - menu->setItemEnabled("Send IM", false); - menu->setItemEnabled("Remove Friend", false); - menu->setItemEnabled("Offer Teleport",false); - menu->setItemEnabled("Request Teleport",false); - menu->setItemEnabled("Voice Call", false); - menu->setItemEnabled("Chat History", false); - menu->setItemEnabled("Invite Group", false); - menu->setItemEnabled("Zoom In", false); - menu->setItemEnabled("Share", false); - menu->setItemEnabled("Pay", false); - menu->setItemEnabled("Block Unblock", false); - menu->setItemEnabled("Mute Text", false); - } - else - { - LLUUID currentSessionID = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, mAvatarID); - if (mSessionID == currentSessionID) - { - menu->setItemVisible("Send IM", false); - } - menu->setItemEnabled("Offer Teleport", LLAvatarActions::canOfferTeleport(mAvatarID)); - menu->setItemEnabled("Request Teleport", LLAvatarActions::canOfferTeleport(mAvatarID)); - menu->setItemEnabled("Voice Call", LLAvatarActions::canCall()); - - // We should only show 'Zoom in' item in a nearby chat - bool should_show_zoom = !LLIMModel::getInstance()->findIMSession(currentSessionID); - menu->setItemVisible("Zoom In", should_show_zoom && gObjectList.findObject(mAvatarID)); - menu->setItemEnabled("Block Unblock", LLAvatarActions::canBlock(mAvatarID)); - menu->setItemEnabled("Mute Text", LLAvatarActions::canBlock(mAvatarID)); - menu->setItemEnabled("Chat History", LLLogChat::isTranscriptExist(mAvatarID)); - } - - menu->setItemEnabled("Map", (LLAvatarTracker::instance().isBuddyOnline(mAvatarID) && is_agent_mappable(mAvatarID)) || gAgent.isGodlike() ); - menu->buildDrawLabels(); - menu->updateParent(LLMenuGL::sMenuContainer); - LLMenuGL::showPopup(this, menu, x, y); - } - } - - void showInfoCtrl() - { - const bool isVisible = !mAvatarID.isNull() && !mFrom.empty() && CHAT_SOURCE_SYSTEM != mSourceType && CHAT_SOURCE_REGION != mSourceType; - if (isVisible) - { - const LLRect sticky_rect = mUserNameTextBox->getRect(); - S32 icon_x = llmin(sticky_rect.mLeft + mUserNameTextBox->getTextBoundingRect().getWidth() + 7, sticky_rect.mRight - 3); - mInfoCtrl->setOrigin(icon_x, sticky_rect.getCenterY() - mInfoCtrl->getRect().getHeight() / 2 ) ; - } - mInfoCtrl->setVisible(isVisible); - } - - void hideInfoCtrl() - { - mInfoCtrl->setVisible(false); - } - -private: - void setTimeField(const LLChat& chat) - { - LLTextBox* time_box = getChild("time_box"); - - LLRect rect_before = time_box->getRect(); - - time_box->setValue(chat.mTimeStr); - - // set necessary textbox width to fit all text - time_box->reshapeToFitText(); - LLRect rect_after = time_box->getRect(); - - // move rect to the left to correct position... - S32 delta_pos_x = rect_before.getWidth() - rect_after.getWidth(); - S32 delta_pos_y = rect_before.getHeight() - rect_after.getHeight(); - time_box->translate(delta_pos_x, delta_pos_y); - - //... & change width of the name control - LLView* user_name = getChild("user_name"); - const LLRect& user_rect = user_name->getRect(); - user_name->reshape(user_rect.getWidth() + delta_pos_x, user_rect.getHeight()); - } - - void fetchAvatarName() - { - if (mAvatarID.notNull()) - { - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - mAvatarNameCacheConnection = LLAvatarNameCache::get(mAvatarID, - boost::bind(&LLChatHistoryHeader::onAvatarNameCache, this, _1, _2)); - } - } - - void onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name) - { - mAvatarNameCacheConnection.disconnect(); - - mFrom = av_name.getDisplayName(); - - LLTextBox* user_name = getChild("user_name"); - user_name->setValue( LLSD(av_name.getDisplayName() ) ); - user_name->setToolTip( av_name.getUserName() ); - - if (gSavedSettings.getBOOL("NameTagShowUsernames") && - av_name.useDisplayNames() && - !av_name.isDisplayNameDefault()) - { - LLStyle::Params style_params_name; - LLColor4 userNameColor = LLUIColorTable::instance().getColor("EmphasisColor"); - style_params_name.color(userNameColor); - style_params_name.font.name("SansSerifSmall"); - style_params_name.font.style("NORMAL"); - style_params_name.readonly_color(userNameColor); - user_name->appendText(" - " + av_name.getUserName(), false, style_params_name); - } - setToolTip( av_name.getUserName() ); - // name might have changed, update width - updateMinUserNameWidth(); - } - -protected: - LLHandle mPopupMenuHandleAvatar; - LLHandle mPopupMenuHandleObject; - - LLUICtrl* mInfoCtrl; - - LLUUID mAvatarID; - LLSD mObjectData; - EChatSourceType mSourceType; - std::string mFrom; - LLUUID mSessionID; - std::string mText; - F64 mTime; // IM's frame time - time_t mCreationTime; // Views's time - - S32 mMinUserNameWidth; - const LLFontGL* mUserNameFont; - LLTextBox* mUserNameTextBox; - LLTextBox* mTimeBoxTextBox; - - bool mNeedsTimeBox; - -private: - boost::signals2::connection mAvatarNameCacheConnection; -}; - -LLChatHistory::LLChatHistory(const LLChatHistory::Params& p) -: LLUICtrl(p), - mMessageHeaderFilename(p.message_header), - mMessageSeparatorFilename(p.message_separator), - mLeftTextPad(p.left_text_pad), - mRightTextPad(p.right_text_pad), - mLeftWidgetPad(p.left_widget_pad), - mRightWidgetPad(p.right_widget_pad), - mTopSeparatorPad(p.top_separator_pad), - mBottomSeparatorPad(p.bottom_separator_pad), - mTopHeaderPad(p.top_header_pad), - mBottomHeaderPad(p.bottom_header_pad), - mIsLastMessageFromLog(false), - mNotifyAboutUnreadMsg(p.notify_unread_msg) -{ - LLTextEditor::Params editor_params(p); - editor_params.rect = getLocalRect(); - editor_params.follows.flags = FOLLOWS_ALL; - editor_params.enabled = false; // read only - editor_params.show_context_menu = "true"; - editor_params.trusted_content = false; - editor_params.text_valign = LLFontGL::VAlign::VCENTER; - editor_params.use_color = true; - mEditor = LLUICtrlFactory::create(editor_params, this); - mEditor->setIsFriendCallback(LLAvatarActions::isFriend); - mEditor->setIsObjectBlockedCallback(boost::bind(&LLMuteList::isMuted, LLMuteList::getInstance(), _1, _2, 0)); - -} - -LLSD LLChatHistory::getValue() const -{ - return LLSD(mEditor->getText()); -} - -LLChatHistory::~LLChatHistory() -{ - this->clear(); -} - -void LLChatHistory::initFromParams(const LLChatHistory::Params& p) -{ - static LLUICachedControl scrollbar_size ("UIScrollbarSize", 0); - - LLRect stack_rect = getLocalRect(); - stack_rect.mRight -= scrollbar_size; - LLLayoutStack::Params layout_p; - layout_p.rect = stack_rect; - layout_p.follows.flags = FOLLOWS_ALL; - layout_p.orientation = LLLayoutStack::VERTICAL; - layout_p.mouse_opaque = false; - - LLLayoutStack* stackp = LLUICtrlFactory::create(layout_p, this); - - const S32 NEW_TEXT_NOTICE_HEIGHT = 20; - - LLLayoutPanel::Params panel_p; - panel_p.name = "spacer"; - panel_p.background_visible = false; - panel_p.has_border = false; - panel_p.mouse_opaque = false; - panel_p.min_dim = 30; - panel_p.auto_resize = true; - panel_p.user_resize = false; - - stackp->addPanel(LLUICtrlFactory::create(panel_p), LLLayoutStack::ANIMATE); - - panel_p.name = "new_text_notice_holder"; - LLRect new_text_notice_rect = getLocalRect(); - new_text_notice_rect.mTop = new_text_notice_rect.mBottom + NEW_TEXT_NOTICE_HEIGHT; - panel_p.rect = new_text_notice_rect; - panel_p.background_opaque = true; - panel_p.background_visible = true; - panel_p.visible = false; - panel_p.min_dim = 0; - panel_p.auto_resize = false; - panel_p.user_resize = false; - mMoreChatPanel = LLUICtrlFactory::create(panel_p); - - LLTextBox::Params text_p(p.more_chat_text); - text_p.rect = mMoreChatPanel->getLocalRect(); - text_p.follows.flags = FOLLOWS_ALL; - text_p.name = "more_chat_text"; - mMoreChatText = LLUICtrlFactory::create(text_p, mMoreChatPanel); - mMoreChatText->setClickedCallback(boost::bind(&LLChatHistory::onClickMoreText, this)); - - stackp->addPanel(mMoreChatPanel, LLLayoutStack::ANIMATE); -} - - -/*void LLChatHistory::updateTextRect() -{ - static LLUICachedControl texteditor_border ("UITextEditorBorder", 0); - - LLRect old_text_rect = mVisibleTextRect; - mVisibleTextRect = mScroller->getContentWindowRect(); - mVisibleTextRect.stretch(-texteditor_border); - mVisibleTextRect.mLeft += mLeftTextPad; - mVisibleTextRect.mRight -= mRightTextPad; - if (mVisibleTextRect != old_text_rect) - { - needsReflow(); - } -}*/ - -LLView* LLChatHistory::getSeparator() -{ - LLPanel* separator = LLUICtrlFactory::getInstance()->createFromFile(mMessageSeparatorFilename, NULL, LLPanel::child_registry_t::instance()); - return separator; -} - -LLView* LLChatHistory::getHeader(const LLChat& chat,const LLStyle::Params& style_params, const LLSD& args) -{ - LLChatHistoryHeader* header = LLChatHistoryHeader::createInstance(mMessageHeaderFilename); - if (header) - header->setup(chat, style_params, args); - return header; -} - -void LLChatHistory::onClickMoreText() -{ - mEditor->endOfDoc(); -} - -void LLChatHistory::clear() -{ - mLastFromName.clear(); - mEditor->clear(); - mLastFromID = LLUUID::null; -} - -static LLTrace::BlockTimerStatHandle FTM_APPEND_MESSAGE("Append Chat Message"); - -void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LLStyle::Params& input_append_params) -{ - LL_RECORD_BLOCK_TIME(FTM_APPEND_MESSAGE); - bool use_plain_text_chat_history = args["use_plain_text_chat_history"].asBoolean(); - bool square_brackets = false; // square brackets necessary for a system messages - - llassert(mEditor); - if (!mEditor) - return; - - bool from_me = chat.mFromID == gAgent.getID(); - mEditor->setPlainText(use_plain_text_chat_history); - - if (mNotifyAboutUnreadMsg && !mEditor->scrolledToEnd() && !from_me && !chat.mFromName.empty()) - { - mUnreadChatSources.insert(chat.mFromName); - mMoreChatPanel->setVisible(true); - std::string chatters; - for (const std::string& source : mUnreadChatSources) - { - chatters += chatters.size() ? ", " + source : source; - } - LLStringUtil::format_map_t args; - args["SOURCES"] = chatters; - - std::string xml_desc = mUnreadChatSources.size() == 1 ? - "unread_chat_single" : "unread_chat_multiple"; - mMoreChatText->setValue(LLTrans::getString(xml_desc, args)); - S32 height = mMoreChatText->getTextPixelHeight() + 5; - mMoreChatPanel->reshape(mMoreChatPanel->getRect().getWidth(), height); - } - - LLColor4 txt_color = LLUIColorTable::instance().getColor("White"); - LLColor4 name_color(txt_color); - - LLViewerChat::getChatColor(chat,txt_color); - LLFontGL* fontp = LLViewerChat::getChatFont(); - std::string font_name = LLFontGL::nameFromFont(fontp); - std::string font_size = LLFontGL::sizeFromFont(fontp); - - LLStyle::Params body_message_params; - body_message_params.color(txt_color); - body_message_params.readonly_color(txt_color); - body_message_params.font.name(font_name); - body_message_params.font.size(font_size); - body_message_params.font.style(input_append_params.font.style); - - LLStyle::Params name_params(body_message_params); - name_params.color(name_color); - name_params.readonly_color(name_color); - - std::string prefix = chat.mText.substr(0, 4); - - //IRC styled /me messages. - bool irc_me = prefix == "/me " || prefix == "/me'"; - - // Delimiter after a name in header copy/past and in plain text mode - std::string delimiter = ": "; - std::string shout = LLTrans::getString("shout"); - std::string whisper = LLTrans::getString("whisper"); - if (chat.mChatType == CHAT_TYPE_SHOUT || - chat.mChatType == CHAT_TYPE_WHISPER || - chat.mText.compare(0, shout.length(), shout) == 0 || - chat.mText.compare(0, whisper.length(), whisper) == 0) - { - delimiter = " "; - } - - // Don't add any delimiter after name in irc styled messages - if (irc_me || chat.mChatStyle == CHAT_STYLE_IRC) - { - delimiter = LLStringUtil::null; - body_message_params.font.style = "ITALIC"; - } - - if (chat.mChatType == CHAT_TYPE_WHISPER) - { - body_message_params.font.style = "ITALIC"; - } - else if (chat.mChatType == CHAT_TYPE_SHOUT) - { - body_message_params.font.style = "BOLD"; - } - - bool message_from_log = chat.mChatStyle == CHAT_STYLE_HISTORY; - bool teleport_separator = chat.mSourceType == CHAT_SOURCE_TELEPORT; - // We graying out chat history by graying out messages that contains full date in a time string - if (message_from_log) - { - txt_color = LLColor4::grey; - body_message_params.color(txt_color); - body_message_params.readonly_color(txt_color); - name_params.color(txt_color); - name_params.readonly_color(txt_color); - } - - bool prependNewLineState = mEditor->getText().size() != 0; - - // compact mode: show a timestamp and name - if (use_plain_text_chat_history) - { - square_brackets = chat.mSourceType == CHAT_SOURCE_SYSTEM; - - LLStyle::Params timestamp_style(body_message_params); - - // out of the timestamp - if (args["show_time"].asBoolean() && !teleport_separator) - { - if (!message_from_log) - { - LLColor4 timestamp_color = LLUIColorTable::instance().getColor("ChatTimestampColor"); - timestamp_style.color(timestamp_color); - timestamp_style.readonly_color(timestamp_color); - } - mEditor->appendText("[" + chat.mTimeStr + "] ", prependNewLineState, timestamp_style); - prependNewLineState = false; - } - - // out the opening square bracket (if need) - if (square_brackets) - { - mEditor->appendText("[", prependNewLineState, body_message_params); - prependNewLineState = false; - } - - // names showing - if (args["show_names_for_p2p_conv"].asBoolean() && utf8str_trim(chat.mFromName).size()) - { - // Don't hotlink any messages from the system (e.g. "Second Life:"), so just add those in plain text. - if (chat.mSourceType == CHAT_SOURCE_OBJECT && chat.mFromID.notNull()) - { - // for object IMs, create a secondlife:///app/objectim SLapp - std::string url = LLViewerChat::getSenderSLURL(chat, args); - - // set the link for the object name to be the objectim SLapp - // (don't let object names with hyperlinks override our objectim Url) - LLStyle::Params link_params(body_message_params); - LLColor4 link_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); - link_params.color = link_color; - link_params.readonly_color = link_color; - link_params.is_link = true; - link_params.link_href = url; - - mEditor->appendText(chat.mFromName + delimiter, prependNewLineState, link_params); - prependNewLineState = false; - } - else if ( chat.mFromName != SYSTEM_FROM && chat.mFromID.notNull() && !message_from_log && chat.mSourceType != CHAT_SOURCE_REGION) - { - LLStyle::Params link_params(body_message_params); - link_params.overwriteFrom(LLStyleMap::instance().lookupAgent(chat.mFromID)); - - // Add link to avatar's inspector and delimiter to message. - mEditor->appendText(std::string(link_params.link_href) + delimiter, - prependNewLineState, link_params); - prependNewLineState = false; - } - else if (teleport_separator) - { - std::string tp_text = LLTrans::getString("teleport_preamble_compact_chat"); - mEditor->appendText(tp_text + " " + chat.mFromName + "", - prependNewLineState, body_message_params); - prependNewLineState = false; - } - else - { - mEditor->appendText("" + chat.mFromName + "" + delimiter, - prependNewLineState, body_message_params); - prependNewLineState = false; - } - } - } - else // showing timestamp and name in the expanded mode - { - prependNewLineState = false; - LLView* view = NULL; - LLInlineViewSegment::Params p; - p.force_newline = true; - p.left_pad = mLeftWidgetPad; - p.right_pad = mRightWidgetPad; - - LLDate new_message_time = LLDate::now(); - if (!teleport_separator - && mLastFromName == chat.mFromName - && mLastFromID == chat.mFromID - && mLastMessageTime.notNull() - && (new_message_time.secondsSinceEpoch() - mLastMessageTime.secondsSinceEpoch()) < 60.0 - && mIsLastMessageFromLog == message_from_log) //distinguish between current and previous chat session's histories - { - view = getSeparator(); - if (!view) - { - // Might be wiser to make this LL_ERRS, getSeparator() should work in case of correct instalation. - LL_WARNS() << "Failed to create separator from " << mMessageSeparatorFilename << ": can't append to history" << LL_ENDL; - return; - } - - p.top_pad = mTopSeparatorPad; - p.bottom_pad = mBottomSeparatorPad; - } - else - { - view = getHeader(chat, name_params, args); - if (!view) - { - LL_WARNS() << "Failed to create header from " << mMessageHeaderFilename << ": can't append to history" << LL_ENDL; - return; - } - - p.top_pad = mEditor->getLength() ? mTopHeaderPad : 0; - p.bottom_pad = teleport_separator ? mBottomSeparatorPad : mBottomHeaderPad; - } - p.view = view; - - //Prepare the rect for the view - LLRect target_rect = mEditor->getDocumentView()->getRect(); - // squeeze down the widget by subtracting padding off left and right - target_rect.mLeft += mLeftWidgetPad + mEditor->getHPad(); - target_rect.mRight -= mRightWidgetPad; - view->reshape(target_rect.getWidth(), view->getRect().getHeight()); - view->setOrigin(target_rect.mLeft, view->getRect().mBottom); - - std::string widget_associated_text = "\n[" + chat.mTimeStr + "] "; - if (utf8str_trim(chat.mFromName).size() != 0 && chat.mFromName != SYSTEM_FROM) - widget_associated_text += chat.mFromName + delimiter; - - mEditor->appendWidget(p, widget_associated_text, false); - mLastFromName = chat.mFromName; - mLastFromID = chat.mFromID; - mLastMessageTime = new_message_time; - mIsLastMessageFromLog = message_from_log; - } - - // body of the message processing - - // notify processing - if (chat.mNotifId.notNull()) - { - LLNotificationPtr notification = LLNotificationsUtil::find(chat.mNotifId); - if (notification != NULL) - { - bool create_toast = true; - if (notification->getName() == "OfferFriendship") - { - // We don't want multiple friendship offers to appear, this code checks if there are previous offers - // by iterating though all panels. - // Note: it might be better to simply add a "pending offer" flag somewhere - for (auto& panel : LLToastNotifyPanel::instance_snapshot()) - { - LLIMToastNotifyPanel * imtoastp = dynamic_cast(&panel); - const std::string& notification_name = panel.getNotificationName(); - if (notification_name == "OfferFriendship" - && panel.isControlPanelEnabled() - && imtoastp) - { - create_toast = false; - break; - } - } - } - - if (create_toast) - { - LLIMToastNotifyPanel* notify_box = new LLIMToastNotifyPanel( - notification, chat.mSessionID, LLRect::null, !use_plain_text_chat_history, mEditor); - - //Prepare the rect for the view - LLRect target_rect = mEditor->getDocumentView()->getRect(); - // squeeze down the widget by subtracting padding off left and right - target_rect.mLeft += mLeftWidgetPad + mEditor->getHPad(); - target_rect.mRight -= mRightWidgetPad; - notify_box->reshape(target_rect.getWidth(), notify_box->getRect().getHeight()); - notify_box->setOrigin(target_rect.mLeft, notify_box->getRect().mBottom); - - LLInlineViewSegment::Params params; - params.view = notify_box; - params.left_pad = mLeftWidgetPad; - params.right_pad = mRightWidgetPad; - mEditor->appendWidget(params, "\n", false); - } - } - } - // usual messages showing - else if (!teleport_separator) - { - std::string message = irc_me ? chat.mText.substr(3) : chat.mText; - - //MESSAGE TEXT PROCESSING - //*HACK getting rid of redundant sender names in system notifications sent using sender name (see EXT-5010) - if (use_plain_text_chat_history && !from_me && chat.mFromID.notNull()) - { - std::string slurl_about = SLURL_APP_AGENT + chat.mFromID.asString() + SLURL_ABOUT; - if (message.length() > slurl_about.length() && - message.compare(0, slurl_about.length(), slurl_about) == 0) - { - message = message.substr(slurl_about.length(), message.length()-1); - } - } - - if (irc_me && !use_plain_text_chat_history) - { - std::string from_name = chat.mFromName; - LLAvatarName av_name; - if (!chat.mFromID.isNull() && - LLAvatarNameCache::get(chat.mFromID, &av_name) && - !av_name.isDisplayNameDefault()) - { - from_name = av_name.getCompleteName(); - } - message = from_name + message; - } - - if (square_brackets) - { - message += "]"; - } - - mEditor->appendText(message, prependNewLineState, body_message_params); - prependNewLineState = false; - } - - mEditor->blockUndo(); - - // automatically scroll to end when receiving chat from myself - if (from_me) - { - mEditor->setCursorAndScrollToEnd(); - } -} - -void LLChatHistory::draw() -{ - if (mEditor->scrolledToEnd()) - { - mUnreadChatSources.clear(); - mMoreChatPanel->setVisible(false); - } - - LLUICtrl::draw(); -} +/** + * @file llchathistory.cpp + * @brief LLTextEditor base class + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llchathistory.h" + +#include + +#include "llavatarnamecache.h" +#include "llinstantmessage.h" + +#include "llimview.h" +#include "llcommandhandler.h" +#include "llpanel.h" +#include "lluictrlfactory.h" +#include "llscrollcontainer.h" +#include "llagent.h" +#include "llagentdata.h" +#include "llavataractions.h" +#include "llavatariconctrl.h" +#include "llcallingcard.h" //for LLAvatarTracker +#include "llgroupactions.h" +#include "llgroupmgr.h" +#include "llspeakers.h" //for LLIMSpeakerMgr +#include "lltrans.h" +#include "llfloaterreg.h" +#include "llfloaterreporter.h" +#include "llfloatersidepanelcontainer.h" +#include "llmutelist.h" +#include "llstylemap.h" +#include "llslurl.h" +#include "lllayoutstack.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "lltoastnotifypanel.h" +#include "lltooltip.h" +#include "llviewerregion.h" +#include "llviewertexteditor.h" +#include "llworld.h" +#include "lluiconstants.h" +#include "llstring.h" +#include "llurlaction.h" +#include "llviewercontrol.h" +#include "llviewermenu.h" +#include "llviewerobjectlist.h" + +static LLDefaultChildRegistry::Register r("chat_history"); + +const static std::string NEW_LINE(rawstr_to_utf8("\n")); + +const static std::string SLURL_APP_AGENT = "secondlife:///app/agent/"; +const static std::string SLURL_ABOUT = "/about"; + +// support for secondlife:///app/objectim/{UUID}/ SLapps +class LLObjectIMHandler : public LLCommandHandler +{ +public: + // requests will be throttled from a non-trusted browser + LLObjectIMHandler() : LLCommandHandler("objectim", UNTRUSTED_THROTTLE) {} + + bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) + { + if (params.size() < 1) + { + return false; + } + + LLUUID object_id; + if (!object_id.set(params[0], false)) + { + return false; + } + + LLSD payload; + payload["object_id"] = object_id; + payload["owner_id"] = query_map["owner"]; + payload["name"] = query_map["name"]; + payload["slurl"] = LLWeb::escapeURL(query_map["slurl"]); + payload["group_owned"] = query_map["groupowned"]; + LLFloaterReg::showInstance("inspect_remote_object", payload); + return true; + } +}; +LLObjectIMHandler gObjectIMHandler; + +class LLChatHistoryHeader: public LLPanel +{ +public: + LLChatHistoryHeader() + : LLPanel(), + mInfoCtrl(NULL), + mPopupMenuHandleAvatar(), + mPopupMenuHandleObject(), + mAvatarID(), + mSourceType(CHAT_SOURCE_UNKNOWN), + mFrom(), + mSessionID(), + mCreationTime(time_corrected()), + mMinUserNameWidth(0), + mUserNameFont(NULL), + mUserNameTextBox(NULL), + mTimeBoxTextBox(NULL), + mNeedsTimeBox(true), + mAvatarNameCacheConnection() + {} + + static LLChatHistoryHeader* createInstance(const std::string& file_name) + { + LLChatHistoryHeader* pInstance = new LLChatHistoryHeader; + pInstance->buildFromFile(file_name); + return pInstance; + } + + ~LLChatHistoryHeader() + { + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + auto menu = mPopupMenuHandleAvatar.get(); + if (menu) + { + menu->die(); + mPopupMenuHandleAvatar.markDead(); + } + menu = mPopupMenuHandleObject.get(); + if (menu) + { + menu->die(); + mPopupMenuHandleObject.markDead(); + } + } + + bool handleMouseUp(S32 x, S32 y, MASK mask) + { + return LLPanel::handleMouseUp(x,y,mask); + } + + void onObjectIconContextMenuItemClicked(const LLSD& userdata) + { + std::string level = userdata.asString(); + + if (level == "profile") + { + LLFloaterReg::showInstance("inspect_remote_object", mObjectData); + } + else if (level == "block") + { + LLMuteList::getInstance()->add(LLMute(getAvatarId(), mFrom, LLMute::OBJECT)); + + LLFloaterSidePanelContainer::showPanel("people", "panel_people", + LLSD().with("people_panel_tab_name", "blocked_panel").with("blocked_to_select", getAvatarId())); + } + else if (level == "unblock") + { + LLMuteList::getInstance()->remove(LLMute(getAvatarId(), mFrom, LLMute::OBJECT)); + } + else if (level == "map") + { + std::string url = "secondlife://" + mObjectData["slurl"].asString(); + LLUrlAction::showLocationOnMap(url); + } + else if (level == "teleport") + { + std::string url = "secondlife://" + mObjectData["slurl"].asString(); + LLUrlAction::teleportToLocation(url); + } + + } + + bool onObjectIconContextMenuItemVisible(const LLSD& userdata) + { + std::string level = userdata.asString(); + if (level == "is_blocked") + { + return LLMuteList::getInstance()->isMuted(getAvatarId(), mFrom, LLMute::flagTextChat); + } + else if (level == "not_blocked") + { + return !LLMuteList::getInstance()->isMuted(getAvatarId(), mFrom, LLMute::flagTextChat); + } + return false; + } + + void banGroupMember(const LLUUID& participant_uuid) + { + LLUUID group_uuid = mSessionID; + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_uuid); + if (!gdatap) + { + // Not a group + return; + } + + gdatap->banMemberById(participant_uuid); + } + + bool canBanInGroup() + { + LLUUID group_uuid = mSessionID; + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_uuid); + if (!gdatap) + { + // Not a group + return false; + } + + if (gAgent.hasPowerInGroup(group_uuid, GP_ROLE_REMOVE_MEMBER) + && gAgent.hasPowerInGroup(group_uuid, GP_GROUP_BAN_ACCESS)) + { + return true; + } + + return false; + } + + bool canBanGroupMember(const LLUUID& participant_uuid) + { + LLUUID group_uuid = mSessionID; + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_uuid); + if (!gdatap) + { + // Not a group + return false; + } + + if (gdatap->mPendingBanRequest) + { + return false; + } + + if (gAgentID == getAvatarId()) + { + //Don't ban self + return false; + } + + if (gdatap->isRoleMemberDataComplete()) + { + if (gdatap->mMembers.size()) + { + LLGroupMgrGroupData::member_list_t::iterator mi = gdatap->mMembers.find(participant_uuid); + if (mi != gdatap->mMembers.end()) + { + LLGroupMemberData* member_data = (*mi).second; + // Is the member an owner? + if (member_data && member_data->isInRole(gdatap->mOwnerRole)) + { + return false; + } + + if (gAgent.hasPowerInGroup(group_uuid, GP_ROLE_REMOVE_MEMBER) + && gAgent.hasPowerInGroup(group_uuid, GP_GROUP_BAN_ACCESS)) + { + return true; + } + } + } + } + + LLSpeakerMgr * speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + if (speaker_mgr) + { + LLSpeaker * speakerp = speaker_mgr->findSpeaker(participant_uuid).get(); + + if (speakerp + && gAgent.hasPowerInGroup(group_uuid, GP_ROLE_REMOVE_MEMBER) + && gAgent.hasPowerInGroup(group_uuid, GP_GROUP_BAN_ACCESS)) + { + return true; + } + } + + return false; + } + + bool isGroupModerator() + { + LLSpeakerMgr * speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + if (!speaker_mgr) + { + LL_WARNS() << "Speaker manager is missing" << LL_ENDL; + return false; + } + + // Is session a group call/chat? + if(gAgent.isInGroup(mSessionID)) + { + LLSpeaker * speakerp = speaker_mgr->findSpeaker(gAgentID).get(); + + // Is agent a moderator? + return speakerp && speakerp->mIsModerator; + } + + return false; + } + + bool canModerate(const std::string& userdata) + { + // only group moderators can perform actions related to this "enable callback" + if (!isGroupModerator() || gAgentID == getAvatarId()) + { + return false; + } + + LLSpeakerMgr * speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + if (!speaker_mgr) + { + return false; + } + + LLSpeaker * speakerp = speaker_mgr->findSpeaker(getAvatarId()).get(); + if (!speakerp) + { + return false; + } + + bool voice_channel = speakerp->isInVoiceChannel(); + + if ("can_moderate_voice" == userdata) + { + return voice_channel; + } + else if ("can_mute" == userdata) + { + return voice_channel && (speakerp->mStatus != LLSpeaker::STATUS_MUTED); + } + else if ("can_unmute" == userdata) + { + return speakerp->mStatus == LLSpeaker::STATUS_MUTED; + } + else if ("can_allow_text_chat" == userdata) + { + return true; + } + + return false; + } + + void onAvatarIconContextMenuItemClicked(const LLSD& userdata) + { + std::string level = userdata.asString(); + + if (level == "profile") + { + LLAvatarActions::showProfile(getAvatarId()); + } + else if (level == "im") + { + LLAvatarActions::startIM(getAvatarId()); + } + else if (level == "teleport") + { + LLAvatarActions::offerTeleport(getAvatarId()); + } + else if (level == "request_teleport") + { + LLAvatarActions::teleportRequest(getAvatarId()); + } + else if (level == "voice_call") + { + LLAvatarActions::startCall(getAvatarId()); + } + else if (level == "chat_history") + { + LLAvatarActions::viewChatHistory(getAvatarId()); + } + else if (level == "add") + { + LLAvatarActions::requestFriendshipDialog(getAvatarId(), mFrom); + } + else if (level == "remove") + { + LLAvatarActions::removeFriendDialog(getAvatarId()); + } + else if (level == "invite_to_group") + { + LLAvatarActions::inviteToGroup(getAvatarId()); + } + else if (level == "zoom_in") + { + handle_zoom_to_object(getAvatarId()); + } + else if (level == "map") + { + LLAvatarActions::showOnMap(getAvatarId()); + } + else if (level == "share") + { + LLAvatarActions::share(getAvatarId()); + } + else if (level == "pay") + { + LLAvatarActions::pay(getAvatarId()); + } + else if (level == "report_abuse") + { + std::string time_string; + if (mTime > 0) // have frame time + { + time_t current_time = time_corrected(); + time_t message_time = current_time - LLFrameTimer::getElapsedSeconds() + mTime; + + time_string = "[" + LLTrans::getString("TimeMonth") + "]/[" + + LLTrans::getString("TimeDay") + "]/[" + + LLTrans::getString("TimeYear") + "] [" + + LLTrans::getString("TimeHour") + "]:[" + + LLTrans::getString("TimeMin") + "]"; + + LLSD substitution; + + substitution["datetime"] = (S32)message_time; + LLStringUtil::format(time_string, substitution); + } + else + { + // From history. This might be empty or not full. + // See LLChatLogParser::parse + time_string = getChild("time_box")->getValue().asString(); + + // Just add current date if not full. + // Should be fine since both times are supposed to be stl + if (!time_string.empty() && time_string.size() < 7) + { + time_string = "[" + LLTrans::getString("TimeMonth") + "]/[" + + LLTrans::getString("TimeDay") + "]/[" + + LLTrans::getString("TimeYear") + "] " + time_string; + + LLSD substitution; + // To avoid adding today's date to yesterday's timestamp, + // use creation time instead of current time + substitution["datetime"] = (S32)mCreationTime; + LLStringUtil::format(time_string, substitution); + } + } + LLFloaterReporter::showFromChat(mAvatarID, mFrom, time_string, mText); + } + else if(level == "block_unblock") + { + LLAvatarActions::toggleMute(getAvatarId(), LLMute::flagVoiceChat); + } + else if(level == "mute_unmute") + { + LLAvatarActions::toggleMute(getAvatarId(), LLMute::flagTextChat); + } + else if(level == "toggle_allow_text_chat") + { + LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + speaker_mgr->toggleAllowTextChat(getAvatarId()); + } + else if(level == "group_mute") + { + LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + if (speaker_mgr) + { + speaker_mgr->moderateVoiceParticipant(getAvatarId(), false); + } + } + else if(level == "group_unmute") + { + LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + if (speaker_mgr) + { + speaker_mgr->moderateVoiceParticipant(getAvatarId(), true); + } + } + else if(level == "ban_member") + { + banGroupMember(getAvatarId()); + } + } + + bool onAvatarIconContextMenuItemChecked(const LLSD& userdata) + { + std::string level = userdata.asString(); + + if (level == "is_blocked") + { + return LLMuteList::getInstance()->isMuted(getAvatarId(), LLMute::flagVoiceChat); + } + if (level == "is_muted") + { + return LLMuteList::getInstance()->isMuted(getAvatarId(), LLMute::flagTextChat); + } + else if (level == "is_allowed_text_chat") + { + if (gAgent.isInGroup(mSessionID)) + { + LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + if(speaker_mgr) + { + const LLSpeaker * speakerp = speaker_mgr->findSpeaker(getAvatarId()); + if (NULL != speakerp) + { + return !speakerp->mModeratorMutedText; + } + } + } + return false; + } + return false; + } + + bool onAvatarIconContextMenuItemEnabled(const LLSD& userdata) + { + std::string level = userdata.asString(); + + if (level == "can_allow_text_chat" || level == "can_mute" || level == "can_unmute") + { + return canModerate(userdata); + } + else if (level == "report_abuse") + { + return gAgentID != mAvatarID; + } + else if (level == "can_ban_member") + { + return canBanGroupMember(getAvatarId()); + } + return false; + } + + bool onAvatarIconContextMenuItemVisible(const LLSD& userdata) + { + std::string level = userdata.asString(); + + if (level == "show_mute") + { + LLSpeakerMgr * speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + if (speaker_mgr) + { + LLSpeaker * speakerp = speaker_mgr->findSpeaker(getAvatarId()).get(); + if (speakerp) + { + return speakerp->isInVoiceChannel() && speakerp->mStatus != LLSpeaker::STATUS_MUTED; + } + } + return false; + } + else if (level == "show_unmute") + { + LLSpeakerMgr * speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + if (speaker_mgr) + { + LLSpeaker * speakerp = speaker_mgr->findSpeaker(getAvatarId()).get(); + if (speakerp) + { + return speakerp->mStatus == LLSpeaker::STATUS_MUTED; + } + } + return false; + } + return false; + } + + bool postBuild() + { + setDoubleClickCallback(boost::bind(&LLChatHistoryHeader::showInspector, this)); + + setMouseEnterCallback(boost::bind(&LLChatHistoryHeader::showInfoCtrl, this)); + setMouseLeaveCallback(boost::bind(&LLChatHistoryHeader::hideInfoCtrl, this)); + + mUserNameTextBox = getChild("user_name"); + mTimeBoxTextBox = getChild("time_box"); + + mInfoCtrl = LLUICtrlFactory::getInstance()->createFromFile("inspector_info_ctrl.xml", this, LLPanel::child_registry_t::instance()); + if (mInfoCtrl) + { + mInfoCtrl->setCommitCallback(boost::bind(&LLChatHistoryHeader::onClickInfoCtrl, mInfoCtrl)); + mInfoCtrl->setVisible(false); + } + else + { + LL_ERRS() << "Failed to create an interface element due to missing or corrupted file inspector_info_ctrl.xml" << LL_ENDL; + } + + return LLPanel::postBuild(); + } + + bool pointInChild(const std::string& name,S32 x,S32 y) + { + LLUICtrl* child = findChild(name); + if(!child) + return false; + + LLView* parent = child->getParent(); + if(parent!=this) + { + x-=parent->getRect().mLeft; + y-=parent->getRect().mBottom; + } + + S32 local_x = x - child->getRect().mLeft ; + S32 local_y = y - child->getRect().mBottom ; + return child->pointInView(local_x, local_y); + } + + bool handleRightMouseDown(S32 x, S32 y, MASK mask) + { + if(pointInChild("avatar_icon",x,y) || pointInChild("user_name",x,y)) + { + showContextMenu(x,y); + return true; + } + + return LLPanel::handleRightMouseDown(x,y,mask); + } + + void showInspector() + { + if (mAvatarID.isNull() && CHAT_SOURCE_SYSTEM != mSourceType && CHAT_SOURCE_REGION != mSourceType) return; + + if (mSourceType == CHAT_SOURCE_OBJECT) + { + LLFloaterReg::showInstance("inspect_remote_object", mObjectData); + } + else if (mSourceType == CHAT_SOURCE_AGENT) + { + LLFloaterReg::showInstance("inspect_avatar", LLSD().with("avatar_id", mAvatarID)); + } + //if chat source is system, you may add "else" here to define behaviour. + } + + static void onClickInfoCtrl(LLUICtrl* info_ctrl) + { + if (!info_ctrl) return; + + LLChatHistoryHeader* header = dynamic_cast(info_ctrl->getParent()); + if (!header) return; + + header->showInspector(); + } + + + const LLUUID& getAvatarId () const { return mAvatarID;} + + void setup(const LLChat& chat, const LLStyle::Params& style_params, const LLSD& args) + { + mAvatarID = chat.mFromID; + mSessionID = chat.mSessionID; + mSourceType = chat.mSourceType; + + // To be able to report a message, we need a copy of it's text + // and it's easier to store text directly than trying to get + // it from a lltextsegment or chat's mEditor + mText = chat.mText; + mTime = chat.mTime; + + //*TODO overly defensive thing, source type should be maintained out there + if((chat.mFromID.isNull() && chat.mFromName.empty()) || (chat.mFromName == SYSTEM_FROM && chat.mFromID.isNull())) + { + mSourceType = CHAT_SOURCE_SYSTEM; + } + + mUserNameFont = style_params.font(); + if (!mUserNameTextBox) + { + mUserNameTextBox = getChild("user_name"); + mTimeBoxTextBox = getChild("time_box"); + } + LLTextBox* user_name = mUserNameTextBox; + user_name->setReadOnlyColor(style_params.readonly_color()); + user_name->setColor(style_params.color()); + + if (mSourceType == CHAT_SOURCE_TELEPORT + && chat.mChatStyle == CHAT_STYLE_TELEPORT_SEP) + { + mFrom = chat.mFromName; + mNeedsTimeBox = false; + user_name->setValue(mFrom); + updateMinUserNameWidth(); + LLColor4 sep_color = LLUIColorTable::instance().getColor("ChatTeleportSeparatorColor"); + setTransparentColor(sep_color); + mTimeBoxTextBox->setVisible(false); + } + else if (chat.mFromName.empty() + || mSourceType == CHAT_SOURCE_SYSTEM) + { + mFrom = LLTrans::getString("SECOND_LIFE"); + if(!chat.mFromName.empty() && (mFrom != chat.mFromName)) + { + mFrom += " (" + chat.mFromName + ")"; + } + user_name->setValue(mFrom); + updateMinUserNameWidth(); + } + else if (mSourceType == CHAT_SOURCE_AGENT + && !mAvatarID.isNull() + && chat.mChatStyle != CHAT_STYLE_HISTORY) + { + // ...from a normal user, lookup the name and fill in later. + // *NOTE: Do not do this for chat history logs, otherwise the viewer + // will flood the People API with lookup requests on startup + + // Start with blank so sample data from XUI XML doesn't + // flash on the screen + user_name->setValue( LLSD() ); + fetchAvatarName(); + } + else if (chat.mChatStyle == CHAT_STYLE_HISTORY || + mSourceType == CHAT_SOURCE_AGENT) + { + //if it's an avatar name with a username add formatting + S32 username_start = chat.mFromName.rfind(" ("); + S32 username_end = chat.mFromName.rfind(')'); + + if (username_start != std::string::npos && + username_end == (chat.mFromName.length() - 1)) + { + mFrom = chat.mFromName.substr(0, username_start); + user_name->setValue(mFrom); + + if (gSavedSettings.getBOOL("NameTagShowUsernames")) + { + std::string username = chat.mFromName.substr(username_start + 2); + username = username.substr(0, username.length() - 1); + LLStyle::Params style_params_name; + LLColor4 userNameColor = LLUIColorTable::instance().getColor("EmphasisColor"); + style_params_name.color(userNameColor); + style_params_name.font.name("SansSerifSmall"); + style_params_name.font.style("NORMAL"); + style_params_name.readonly_color(userNameColor); + user_name->appendText(" - " + username, false, style_params_name); + } + } + else + { + mFrom = chat.mFromName; + user_name->setValue(mFrom); + updateMinUserNameWidth(); + } + } + else + { + // ...from an object, just use name as given + mFrom = chat.mFromName; + user_name->setValue(mFrom); + updateMinUserNameWidth(); + } + + + setTimeField(chat); + + // Set up the icon. + LLAvatarIconCtrl* icon = getChild("avatar_icon"); + + if(mSourceType != CHAT_SOURCE_AGENT || mAvatarID.isNull()) + icon->setDrawTooltip(false); + + switch (mSourceType) + { + case CHAT_SOURCE_AGENT: + icon->setValue(chat.mFromID); + break; + case CHAT_SOURCE_OBJECT: + icon->setValue(LLSD("OBJECT_Icon")); + break; + case CHAT_SOURCE_SYSTEM: + case CHAT_SOURCE_REGION: + icon->setValue(LLSD("SL_Logo")); + break; + case CHAT_SOURCE_TELEPORT: + icon->setValue(LLSD("Command_Destinations_Icon")); + break; + case CHAT_SOURCE_UNKNOWN: + icon->setValue(LLSD("Unknown_Icon")); + } + + // In case the message came from an object, save the object info + // to be able properly show its profile. + if ( chat.mSourceType == CHAT_SOURCE_OBJECT) + { + std::string slurl = args["slurl"].asString(); + if (slurl.empty() && LLWorld::instanceExists()) + { + LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosAgent(chat.mPosAgent); + if(region) + { + LLSLURL region_slurl(region->getName(), chat.mPosAgent); + slurl = region_slurl.getLocationString(); + } + } + + LLSD payload; + payload["object_id"] = chat.mFromID; + payload["name"] = chat.mFromName; + payload["owner_id"] = chat.mOwnerID; + payload["slurl"] = LLWeb::escapeURL(slurl); + + mObjectData = payload; + } + } + + /*virtual*/ void draw() + { + LLTextBox* user_name = mUserNameTextBox; //getChild("user_name"); + LLTextBox* time_box = mTimeBoxTextBox; //getChild("time_box"); + + LLRect user_name_rect = user_name->getRect(); + S32 user_name_width = user_name_rect.getWidth(); + S32 time_box_width = time_box->getRect().getWidth(); + + if (mNeedsTimeBox && !time_box->getVisible() && user_name_width > mMinUserNameWidth) + { + user_name_rect.mRight -= time_box_width; + user_name->reshape(user_name_rect.getWidth(), user_name_rect.getHeight()); + user_name->setRect(user_name_rect); + + time_box->setVisible(true); + } + + LLPanel::draw(); + } + + void updateMinUserNameWidth() + { + if (mUserNameFont) + { + LLTextBox* user_name = getChild("user_name"); + const LLWString& text = user_name->getWText(); + mMinUserNameWidth = mUserNameFont->getWidth(text.c_str()) + PADDING; + } + } + +protected: + static const S32 PADDING = 20; + + void showContextMenu(S32 x,S32 y) + { + if(mSourceType == CHAT_SOURCE_SYSTEM) + showSystemContextMenu(x,y); + if(mAvatarID.notNull() && mSourceType == CHAT_SOURCE_AGENT) + showAvatarContextMenu(x,y); + if(mAvatarID.notNull() && mSourceType == CHAT_SOURCE_OBJECT) + showObjectContextMenu(x,y); + } + + void showSystemContextMenu(S32 x,S32 y) + { + } + + void showObjectContextMenu(S32 x,S32 y) + { + LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandleObject.get(); + if (!menu) + { + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar registrar_enable; + registrar.add("ObjectIcon.Action", boost::bind(&LLChatHistoryHeader::onObjectIconContextMenuItemClicked, this, _2)); + registrar_enable.add("ObjectIcon.Visible", boost::bind(&LLChatHistoryHeader::onObjectIconContextMenuItemVisible, this, _2)); + + menu = LLUICtrlFactory::getInstance()->createFromFile("menu_object_icon.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + if (menu) + { + mPopupMenuHandleObject = menu->getHandle(); + menu->updateParent(LLMenuGL::sMenuContainer); + LLMenuGL::showPopup(this, menu, x, y); + } + else + { + LL_WARNS() << " Failed to create menu_object_icon.xml" << LL_ENDL; + } + } + else + { + LLMenuGL::showPopup(this, menu, x, y); + } + } + + void showAvatarContextMenu(S32 x,S32 y) + { + LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandleAvatar.get(); + if (!menu) + { + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar registrar_enable; + registrar.add("AvatarIcon.Action", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemClicked, this, _2)); + registrar_enable.add("AvatarIcon.Check", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemChecked, this, _2)); + registrar_enable.add("AvatarIcon.Enable", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemEnabled, this, _2)); + registrar_enable.add("AvatarIcon.Visible", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemVisible, this, _2)); + + menu = LLUICtrlFactory::getInstance()->createFromFile("menu_avatar_icon.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + if (menu) + { + mPopupMenuHandleAvatar = menu->getHandle(); + } + else + { + LL_WARNS() << " Failed to create menu_avatar_icon.xml" << LL_ENDL; + } + } + + if(menu) + { + bool is_friend = LLAvatarActions::isFriend(mAvatarID); + bool is_group_session = gAgent.isInGroup(mSessionID); + + menu->setItemEnabled("Add Friend", !is_friend); + menu->setItemEnabled("Remove Friend", is_friend); + menu->setItemVisible("Moderator Options Separator", is_group_session && isGroupModerator()); + menu->setItemVisible("Moderator Options", is_group_session && isGroupModerator()); + menu->setItemVisible("Group Ban Separator", is_group_session && canBanInGroup()); + menu->setItemVisible("BanMember", is_group_session && canBanInGroup()); + + if(gAgentID == mAvatarID) + { + menu->setItemEnabled("Add Friend", false); + menu->setItemEnabled("Send IM", false); + menu->setItemEnabled("Remove Friend", false); + menu->setItemEnabled("Offer Teleport",false); + menu->setItemEnabled("Request Teleport",false); + menu->setItemEnabled("Voice Call", false); + menu->setItemEnabled("Chat History", false); + menu->setItemEnabled("Invite Group", false); + menu->setItemEnabled("Zoom In", false); + menu->setItemEnabled("Share", false); + menu->setItemEnabled("Pay", false); + menu->setItemEnabled("Block Unblock", false); + menu->setItemEnabled("Mute Text", false); + } + else + { + LLUUID currentSessionID = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, mAvatarID); + if (mSessionID == currentSessionID) + { + menu->setItemVisible("Send IM", false); + } + menu->setItemEnabled("Offer Teleport", LLAvatarActions::canOfferTeleport(mAvatarID)); + menu->setItemEnabled("Request Teleport", LLAvatarActions::canOfferTeleport(mAvatarID)); + menu->setItemEnabled("Voice Call", LLAvatarActions::canCall()); + + // We should only show 'Zoom in' item in a nearby chat + bool should_show_zoom = !LLIMModel::getInstance()->findIMSession(currentSessionID); + menu->setItemVisible("Zoom In", should_show_zoom && gObjectList.findObject(mAvatarID)); + menu->setItemEnabled("Block Unblock", LLAvatarActions::canBlock(mAvatarID)); + menu->setItemEnabled("Mute Text", LLAvatarActions::canBlock(mAvatarID)); + menu->setItemEnabled("Chat History", LLLogChat::isTranscriptExist(mAvatarID)); + } + + menu->setItemEnabled("Map", (LLAvatarTracker::instance().isBuddyOnline(mAvatarID) && is_agent_mappable(mAvatarID)) || gAgent.isGodlike() ); + menu->buildDrawLabels(); + menu->updateParent(LLMenuGL::sMenuContainer); + LLMenuGL::showPopup(this, menu, x, y); + } + } + + void showInfoCtrl() + { + const bool isVisible = !mAvatarID.isNull() && !mFrom.empty() && CHAT_SOURCE_SYSTEM != mSourceType && CHAT_SOURCE_REGION != mSourceType; + if (isVisible) + { + const LLRect sticky_rect = mUserNameTextBox->getRect(); + S32 icon_x = llmin(sticky_rect.mLeft + mUserNameTextBox->getTextBoundingRect().getWidth() + 7, sticky_rect.mRight - 3); + mInfoCtrl->setOrigin(icon_x, sticky_rect.getCenterY() - mInfoCtrl->getRect().getHeight() / 2 ) ; + } + mInfoCtrl->setVisible(isVisible); + } + + void hideInfoCtrl() + { + mInfoCtrl->setVisible(false); + } + +private: + void setTimeField(const LLChat& chat) + { + LLTextBox* time_box = getChild("time_box"); + + LLRect rect_before = time_box->getRect(); + + time_box->setValue(chat.mTimeStr); + + // set necessary textbox width to fit all text + time_box->reshapeToFitText(); + LLRect rect_after = time_box->getRect(); + + // move rect to the left to correct position... + S32 delta_pos_x = rect_before.getWidth() - rect_after.getWidth(); + S32 delta_pos_y = rect_before.getHeight() - rect_after.getHeight(); + time_box->translate(delta_pos_x, delta_pos_y); + + //... & change width of the name control + LLView* user_name = getChild("user_name"); + const LLRect& user_rect = user_name->getRect(); + user_name->reshape(user_rect.getWidth() + delta_pos_x, user_rect.getHeight()); + } + + void fetchAvatarName() + { + if (mAvatarID.notNull()) + { + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + mAvatarNameCacheConnection = LLAvatarNameCache::get(mAvatarID, + boost::bind(&LLChatHistoryHeader::onAvatarNameCache, this, _1, _2)); + } + } + + void onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name) + { + mAvatarNameCacheConnection.disconnect(); + + mFrom = av_name.getDisplayName(); + + LLTextBox* user_name = getChild("user_name"); + user_name->setValue( LLSD(av_name.getDisplayName() ) ); + user_name->setToolTip( av_name.getUserName() ); + + if (gSavedSettings.getBOOL("NameTagShowUsernames") && + av_name.useDisplayNames() && + !av_name.isDisplayNameDefault()) + { + LLStyle::Params style_params_name; + LLColor4 userNameColor = LLUIColorTable::instance().getColor("EmphasisColor"); + style_params_name.color(userNameColor); + style_params_name.font.name("SansSerifSmall"); + style_params_name.font.style("NORMAL"); + style_params_name.readonly_color(userNameColor); + user_name->appendText(" - " + av_name.getUserName(), false, style_params_name); + } + setToolTip( av_name.getUserName() ); + // name might have changed, update width + updateMinUserNameWidth(); + } + +protected: + LLHandle mPopupMenuHandleAvatar; + LLHandle mPopupMenuHandleObject; + + LLUICtrl* mInfoCtrl; + + LLUUID mAvatarID; + LLSD mObjectData; + EChatSourceType mSourceType; + std::string mFrom; + LLUUID mSessionID; + std::string mText; + F64 mTime; // IM's frame time + time_t mCreationTime; // Views's time + + S32 mMinUserNameWidth; + const LLFontGL* mUserNameFont; + LLTextBox* mUserNameTextBox; + LLTextBox* mTimeBoxTextBox; + + bool mNeedsTimeBox; + +private: + boost::signals2::connection mAvatarNameCacheConnection; +}; + +LLChatHistory::LLChatHistory(const LLChatHistory::Params& p) +: LLUICtrl(p), + mMessageHeaderFilename(p.message_header), + mMessageSeparatorFilename(p.message_separator), + mLeftTextPad(p.left_text_pad), + mRightTextPad(p.right_text_pad), + mLeftWidgetPad(p.left_widget_pad), + mRightWidgetPad(p.right_widget_pad), + mTopSeparatorPad(p.top_separator_pad), + mBottomSeparatorPad(p.bottom_separator_pad), + mTopHeaderPad(p.top_header_pad), + mBottomHeaderPad(p.bottom_header_pad), + mIsLastMessageFromLog(false), + mNotifyAboutUnreadMsg(p.notify_unread_msg) +{ + LLTextEditor::Params editor_params(p); + editor_params.rect = getLocalRect(); + editor_params.follows.flags = FOLLOWS_ALL; + editor_params.enabled = false; // read only + editor_params.show_context_menu = "true"; + editor_params.trusted_content = false; + editor_params.text_valign = LLFontGL::VAlign::VCENTER; + editor_params.use_color = true; + mEditor = LLUICtrlFactory::create(editor_params, this); + mEditor->setIsFriendCallback(LLAvatarActions::isFriend); + mEditor->setIsObjectBlockedCallback(boost::bind(&LLMuteList::isMuted, LLMuteList::getInstance(), _1, _2, 0)); + +} + +LLSD LLChatHistory::getValue() const +{ + return LLSD(mEditor->getText()); +} + +LLChatHistory::~LLChatHistory() +{ + this->clear(); +} + +void LLChatHistory::initFromParams(const LLChatHistory::Params& p) +{ + static LLUICachedControl scrollbar_size ("UIScrollbarSize", 0); + + LLRect stack_rect = getLocalRect(); + stack_rect.mRight -= scrollbar_size; + LLLayoutStack::Params layout_p; + layout_p.rect = stack_rect; + layout_p.follows.flags = FOLLOWS_ALL; + layout_p.orientation = LLLayoutStack::VERTICAL; + layout_p.mouse_opaque = false; + + LLLayoutStack* stackp = LLUICtrlFactory::create(layout_p, this); + + const S32 NEW_TEXT_NOTICE_HEIGHT = 20; + + LLLayoutPanel::Params panel_p; + panel_p.name = "spacer"; + panel_p.background_visible = false; + panel_p.has_border = false; + panel_p.mouse_opaque = false; + panel_p.min_dim = 30; + panel_p.auto_resize = true; + panel_p.user_resize = false; + + stackp->addPanel(LLUICtrlFactory::create(panel_p), LLLayoutStack::ANIMATE); + + panel_p.name = "new_text_notice_holder"; + LLRect new_text_notice_rect = getLocalRect(); + new_text_notice_rect.mTop = new_text_notice_rect.mBottom + NEW_TEXT_NOTICE_HEIGHT; + panel_p.rect = new_text_notice_rect; + panel_p.background_opaque = true; + panel_p.background_visible = true; + panel_p.visible = false; + panel_p.min_dim = 0; + panel_p.auto_resize = false; + panel_p.user_resize = false; + mMoreChatPanel = LLUICtrlFactory::create(panel_p); + + LLTextBox::Params text_p(p.more_chat_text); + text_p.rect = mMoreChatPanel->getLocalRect(); + text_p.follows.flags = FOLLOWS_ALL; + text_p.name = "more_chat_text"; + mMoreChatText = LLUICtrlFactory::create(text_p, mMoreChatPanel); + mMoreChatText->setClickedCallback(boost::bind(&LLChatHistory::onClickMoreText, this)); + + stackp->addPanel(mMoreChatPanel, LLLayoutStack::ANIMATE); +} + + +/*void LLChatHistory::updateTextRect() +{ + static LLUICachedControl texteditor_border ("UITextEditorBorder", 0); + + LLRect old_text_rect = mVisibleTextRect; + mVisibleTextRect = mScroller->getContentWindowRect(); + mVisibleTextRect.stretch(-texteditor_border); + mVisibleTextRect.mLeft += mLeftTextPad; + mVisibleTextRect.mRight -= mRightTextPad; + if (mVisibleTextRect != old_text_rect) + { + needsReflow(); + } +}*/ + +LLView* LLChatHistory::getSeparator() +{ + LLPanel* separator = LLUICtrlFactory::getInstance()->createFromFile(mMessageSeparatorFilename, NULL, LLPanel::child_registry_t::instance()); + return separator; +} + +LLView* LLChatHistory::getHeader(const LLChat& chat,const LLStyle::Params& style_params, const LLSD& args) +{ + LLChatHistoryHeader* header = LLChatHistoryHeader::createInstance(mMessageHeaderFilename); + if (header) + header->setup(chat, style_params, args); + return header; +} + +void LLChatHistory::onClickMoreText() +{ + mEditor->endOfDoc(); +} + +void LLChatHistory::clear() +{ + mLastFromName.clear(); + mEditor->clear(); + mLastFromID = LLUUID::null; +} + +static LLTrace::BlockTimerStatHandle FTM_APPEND_MESSAGE("Append Chat Message"); + +void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LLStyle::Params& input_append_params) +{ + LL_RECORD_BLOCK_TIME(FTM_APPEND_MESSAGE); + bool use_plain_text_chat_history = args["use_plain_text_chat_history"].asBoolean(); + bool square_brackets = false; // square brackets necessary for a system messages + + llassert(mEditor); + if (!mEditor) + return; + + bool from_me = chat.mFromID == gAgent.getID(); + mEditor->setPlainText(use_plain_text_chat_history); + + if (mNotifyAboutUnreadMsg && !mEditor->scrolledToEnd() && !from_me && !chat.mFromName.empty()) + { + mUnreadChatSources.insert(chat.mFromName); + mMoreChatPanel->setVisible(true); + std::string chatters; + for (const std::string& source : mUnreadChatSources) + { + chatters += chatters.size() ? ", " + source : source; + } + LLStringUtil::format_map_t args; + args["SOURCES"] = chatters; + + std::string xml_desc = mUnreadChatSources.size() == 1 ? + "unread_chat_single" : "unread_chat_multiple"; + mMoreChatText->setValue(LLTrans::getString(xml_desc, args)); + S32 height = mMoreChatText->getTextPixelHeight() + 5; + mMoreChatPanel->reshape(mMoreChatPanel->getRect().getWidth(), height); + } + + LLColor4 txt_color = LLUIColorTable::instance().getColor("White"); + LLColor4 name_color(txt_color); + + LLViewerChat::getChatColor(chat,txt_color); + LLFontGL* fontp = LLViewerChat::getChatFont(); + std::string font_name = LLFontGL::nameFromFont(fontp); + std::string font_size = LLFontGL::sizeFromFont(fontp); + + LLStyle::Params body_message_params; + body_message_params.color(txt_color); + body_message_params.readonly_color(txt_color); + body_message_params.font.name(font_name); + body_message_params.font.size(font_size); + body_message_params.font.style(input_append_params.font.style); + + LLStyle::Params name_params(body_message_params); + name_params.color(name_color); + name_params.readonly_color(name_color); + + std::string prefix = chat.mText.substr(0, 4); + + //IRC styled /me messages. + bool irc_me = prefix == "/me " || prefix == "/me'"; + + // Delimiter after a name in header copy/past and in plain text mode + std::string delimiter = ": "; + std::string shout = LLTrans::getString("shout"); + std::string whisper = LLTrans::getString("whisper"); + if (chat.mChatType == CHAT_TYPE_SHOUT || + chat.mChatType == CHAT_TYPE_WHISPER || + chat.mText.compare(0, shout.length(), shout) == 0 || + chat.mText.compare(0, whisper.length(), whisper) == 0) + { + delimiter = " "; + } + + // Don't add any delimiter after name in irc styled messages + if (irc_me || chat.mChatStyle == CHAT_STYLE_IRC) + { + delimiter = LLStringUtil::null; + body_message_params.font.style = "ITALIC"; + } + + if (chat.mChatType == CHAT_TYPE_WHISPER) + { + body_message_params.font.style = "ITALIC"; + } + else if (chat.mChatType == CHAT_TYPE_SHOUT) + { + body_message_params.font.style = "BOLD"; + } + + bool message_from_log = chat.mChatStyle == CHAT_STYLE_HISTORY; + bool teleport_separator = chat.mSourceType == CHAT_SOURCE_TELEPORT; + // We graying out chat history by graying out messages that contains full date in a time string + if (message_from_log) + { + txt_color = LLColor4::grey; + body_message_params.color(txt_color); + body_message_params.readonly_color(txt_color); + name_params.color(txt_color); + name_params.readonly_color(txt_color); + } + + bool prependNewLineState = mEditor->getText().size() != 0; + + // compact mode: show a timestamp and name + if (use_plain_text_chat_history) + { + square_brackets = chat.mSourceType == CHAT_SOURCE_SYSTEM; + + LLStyle::Params timestamp_style(body_message_params); + + // out of the timestamp + if (args["show_time"].asBoolean() && !teleport_separator) + { + if (!message_from_log) + { + LLColor4 timestamp_color = LLUIColorTable::instance().getColor("ChatTimestampColor"); + timestamp_style.color(timestamp_color); + timestamp_style.readonly_color(timestamp_color); + } + mEditor->appendText("[" + chat.mTimeStr + "] ", prependNewLineState, timestamp_style); + prependNewLineState = false; + } + + // out the opening square bracket (if need) + if (square_brackets) + { + mEditor->appendText("[", prependNewLineState, body_message_params); + prependNewLineState = false; + } + + // names showing + if (args["show_names_for_p2p_conv"].asBoolean() && utf8str_trim(chat.mFromName).size()) + { + // Don't hotlink any messages from the system (e.g. "Second Life:"), so just add those in plain text. + if (chat.mSourceType == CHAT_SOURCE_OBJECT && chat.mFromID.notNull()) + { + // for object IMs, create a secondlife:///app/objectim SLapp + std::string url = LLViewerChat::getSenderSLURL(chat, args); + + // set the link for the object name to be the objectim SLapp + // (don't let object names with hyperlinks override our objectim Url) + LLStyle::Params link_params(body_message_params); + LLColor4 link_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + link_params.color = link_color; + link_params.readonly_color = link_color; + link_params.is_link = true; + link_params.link_href = url; + + mEditor->appendText(chat.mFromName + delimiter, prependNewLineState, link_params); + prependNewLineState = false; + } + else if ( chat.mFromName != SYSTEM_FROM && chat.mFromID.notNull() && !message_from_log && chat.mSourceType != CHAT_SOURCE_REGION) + { + LLStyle::Params link_params(body_message_params); + link_params.overwriteFrom(LLStyleMap::instance().lookupAgent(chat.mFromID)); + + // Add link to avatar's inspector and delimiter to message. + mEditor->appendText(std::string(link_params.link_href) + delimiter, + prependNewLineState, link_params); + prependNewLineState = false; + } + else if (teleport_separator) + { + std::string tp_text = LLTrans::getString("teleport_preamble_compact_chat"); + mEditor->appendText(tp_text + " " + chat.mFromName + "", + prependNewLineState, body_message_params); + prependNewLineState = false; + } + else + { + mEditor->appendText("" + chat.mFromName + "" + delimiter, + prependNewLineState, body_message_params); + prependNewLineState = false; + } + } + } + else // showing timestamp and name in the expanded mode + { + prependNewLineState = false; + LLView* view = NULL; + LLInlineViewSegment::Params p; + p.force_newline = true; + p.left_pad = mLeftWidgetPad; + p.right_pad = mRightWidgetPad; + + LLDate new_message_time = LLDate::now(); + if (!teleport_separator + && mLastFromName == chat.mFromName + && mLastFromID == chat.mFromID + && mLastMessageTime.notNull() + && (new_message_time.secondsSinceEpoch() - mLastMessageTime.secondsSinceEpoch()) < 60.0 + && mIsLastMessageFromLog == message_from_log) //distinguish between current and previous chat session's histories + { + view = getSeparator(); + if (!view) + { + // Might be wiser to make this LL_ERRS, getSeparator() should work in case of correct instalation. + LL_WARNS() << "Failed to create separator from " << mMessageSeparatorFilename << ": can't append to history" << LL_ENDL; + return; + } + + p.top_pad = mTopSeparatorPad; + p.bottom_pad = mBottomSeparatorPad; + } + else + { + view = getHeader(chat, name_params, args); + if (!view) + { + LL_WARNS() << "Failed to create header from " << mMessageHeaderFilename << ": can't append to history" << LL_ENDL; + return; + } + + p.top_pad = mEditor->getLength() ? mTopHeaderPad : 0; + p.bottom_pad = teleport_separator ? mBottomSeparatorPad : mBottomHeaderPad; + } + p.view = view; + + //Prepare the rect for the view + LLRect target_rect = mEditor->getDocumentView()->getRect(); + // squeeze down the widget by subtracting padding off left and right + target_rect.mLeft += mLeftWidgetPad + mEditor->getHPad(); + target_rect.mRight -= mRightWidgetPad; + view->reshape(target_rect.getWidth(), view->getRect().getHeight()); + view->setOrigin(target_rect.mLeft, view->getRect().mBottom); + + std::string widget_associated_text = "\n[" + chat.mTimeStr + "] "; + if (utf8str_trim(chat.mFromName).size() != 0 && chat.mFromName != SYSTEM_FROM) + widget_associated_text += chat.mFromName + delimiter; + + mEditor->appendWidget(p, widget_associated_text, false); + mLastFromName = chat.mFromName; + mLastFromID = chat.mFromID; + mLastMessageTime = new_message_time; + mIsLastMessageFromLog = message_from_log; + } + + // body of the message processing + + // notify processing + if (chat.mNotifId.notNull()) + { + LLNotificationPtr notification = LLNotificationsUtil::find(chat.mNotifId); + if (notification != NULL) + { + bool create_toast = true; + if (notification->getName() == "OfferFriendship") + { + // We don't want multiple friendship offers to appear, this code checks if there are previous offers + // by iterating though all panels. + // Note: it might be better to simply add a "pending offer" flag somewhere + for (auto& panel : LLToastNotifyPanel::instance_snapshot()) + { + LLIMToastNotifyPanel * imtoastp = dynamic_cast(&panel); + const std::string& notification_name = panel.getNotificationName(); + if (notification_name == "OfferFriendship" + && panel.isControlPanelEnabled() + && imtoastp) + { + create_toast = false; + break; + } + } + } + + if (create_toast) + { + LLIMToastNotifyPanel* notify_box = new LLIMToastNotifyPanel( + notification, chat.mSessionID, LLRect::null, !use_plain_text_chat_history, mEditor); + + //Prepare the rect for the view + LLRect target_rect = mEditor->getDocumentView()->getRect(); + // squeeze down the widget by subtracting padding off left and right + target_rect.mLeft += mLeftWidgetPad + mEditor->getHPad(); + target_rect.mRight -= mRightWidgetPad; + notify_box->reshape(target_rect.getWidth(), notify_box->getRect().getHeight()); + notify_box->setOrigin(target_rect.mLeft, notify_box->getRect().mBottom); + + LLInlineViewSegment::Params params; + params.view = notify_box; + params.left_pad = mLeftWidgetPad; + params.right_pad = mRightWidgetPad; + mEditor->appendWidget(params, "\n", false); + } + } + } + // usual messages showing + else if (!teleport_separator) + { + std::string message = irc_me ? chat.mText.substr(3) : chat.mText; + + //MESSAGE TEXT PROCESSING + //*HACK getting rid of redundant sender names in system notifications sent using sender name (see EXT-5010) + if (use_plain_text_chat_history && !from_me && chat.mFromID.notNull()) + { + std::string slurl_about = SLURL_APP_AGENT + chat.mFromID.asString() + SLURL_ABOUT; + if (message.length() > slurl_about.length() && + message.compare(0, slurl_about.length(), slurl_about) == 0) + { + message = message.substr(slurl_about.length(), message.length()-1); + } + } + + if (irc_me && !use_plain_text_chat_history) + { + std::string from_name = chat.mFromName; + LLAvatarName av_name; + if (!chat.mFromID.isNull() && + LLAvatarNameCache::get(chat.mFromID, &av_name) && + !av_name.isDisplayNameDefault()) + { + from_name = av_name.getCompleteName(); + } + message = from_name + message; + } + + if (square_brackets) + { + message += "]"; + } + + mEditor->appendText(message, prependNewLineState, body_message_params); + prependNewLineState = false; + } + + mEditor->blockUndo(); + + // automatically scroll to end when receiving chat from myself + if (from_me) + { + mEditor->setCursorAndScrollToEnd(); + } +} + +void LLChatHistory::draw() +{ + if (mEditor->scrolledToEnd()) + { + mUnreadChatSources.clear(); + mMoreChatPanel->setVisible(false); + } + + LLUICtrl::draw(); +} diff --git a/indra/newview/llchatitemscontainerctrl.cpp b/indra/newview/llchatitemscontainerctrl.cpp index 560a883b1e..d517f5a19d 100644 --- a/indra/newview/llchatitemscontainerctrl.cpp +++ b/indra/newview/llchatitemscontainerctrl.cpp @@ -1,411 +1,411 @@ -/** - * @file llchatitemscontainer.cpp - * @brief chat history scrolling panel implementation - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llchatitemscontainerctrl.h" -#include "llchatmsgbox.h" -#include "lltextbox.h" - -#include "llavataractions.h" -#include "llavatariconctrl.h" -#include "llcommandhandler.h" -#include "llfloaterreg.h" -#include "lllocalcliprect.h" -#include "lltrans.h" -#include "llfloaterimnearbychat.h" - -#include "llviewercontrol.h" -#include "llagentdata.h" - -#include "llslurl.h" - -static constexpr S32 msg_left_offset = 10; -static constexpr S32 msg_right_offset = 10; -static constexpr S32 msg_height_pad = 5; - -//******************************************************************************************************************* -// LLObjectHandler -//******************************************************************************************************************* - -// handle secondlife:///app/object//inspect SLURLs -class LLObjectHandler : public LLCommandHandler -{ -public: - LLObjectHandler() : LLCommandHandler("object", UNTRUSTED_BLOCK) { } - - bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) - { - if (params.size() < 2) return false; - - LLUUID object_id; - if (!object_id.set(params[0], false)) - { - return false; - } - - const std::string verb = params[1].asString(); - - if (verb == "inspect") - { - LLFloaterReg::showInstance("inspect_object", LLSD().with("object_id", object_id)); - return true; - } - - return false; - } -}; - -LLObjectHandler gObjectHandler; - -//******************************************************************************************************************* -//LLFloaterIMNearbyChatToastPanel -//******************************************************************************************************************* - -LLFloaterIMNearbyChatToastPanel* LLFloaterIMNearbyChatToastPanel::createInstance() -{ - LLFloaterIMNearbyChatToastPanel* item = new LLFloaterIMNearbyChatToastPanel(); - item->buildFromFile("panel_chat_item.xml"); - item->setFollows(FOLLOWS_NONE); - return item; -} - -void LLFloaterIMNearbyChatToastPanel::reshape (S32 width, S32 height, bool called_from_parent ) -{ - LLPanel::reshape(width, height,called_from_parent); - - // reshape() may be called from LLView::initFromParams() before the children are created. - // We call findChild() instead of getChild() here to avoid creating dummy controls. - LLUICtrl* msg_text = findChild("msg_text", false); - LLUICtrl* icon = findChild("avatar_icon", false); - - if (!msg_text || !icon) - { - return; - } - - LLRect msg_text_rect = msg_text->getRect(); - LLRect avatar_rect = icon->getRect(); - - avatar_rect.setLeftTopAndSize(2,height-2,avatar_rect.getWidth(),avatar_rect.getHeight()); - icon->setRect(avatar_rect); - - - msg_text_rect.setLeftTopAndSize( avatar_rect.mRight + msg_left_offset, - height - msg_height_pad, - width - avatar_rect.mRight - msg_left_offset - msg_right_offset, - height - 2*msg_height_pad); - msg_text->reshape( msg_text_rect.getWidth(), msg_text_rect.getHeight(), 1); - msg_text->setRect(msg_text_rect); -} - -bool LLFloaterIMNearbyChatToastPanel::postBuild() -{ - return LLPanel::postBuild(); -} - -void LLFloaterIMNearbyChatToastPanel::addMessage(LLSD& notification) -{ - std::string messageText = notification["message"].asString(); // UTF-8 line of text - - - std::string color_name = notification["text_color"].asString(); - - LLColor4 textColor = LLUIColorTable::instance().getColor(color_name); - textColor.mV[VALPHA] =notification["color_alpha"].asReal(); - - S32 font_size = notification["font_size"].asInteger(); - - LLFontGL* messageFont; - switch(font_size) - { - case 0: messageFont = LLFontGL::getFontSansSerifSmall(); break; - default: - case 1: messageFont = LLFontGL::getFontSansSerif(); break; - case 2: messageFont = LLFontGL::getFontSansSerifBig(); break; - } - - //append text - { - LLStyle::Params style_params; - style_params.color(textColor); - std::string font_name = LLFontGL::nameFromFont(messageFont); - std::string font_style_size = LLFontGL::sizeFromFont(messageFont); - style_params.font.name(font_name); - style_params.font.size(font_style_size); - - int chat_type = notification["chat_type"].asInteger(); - - if(notification["chat_style"].asInteger()== CHAT_STYLE_IRC) - { - style_params.font.style = "ITALIC"; - } - else if( chat_type == CHAT_TYPE_SHOUT) - { - style_params.font.style = "BOLD"; - } - else if( chat_type == CHAT_TYPE_WHISPER) - { - style_params.font.style = "ITALIC"; - } - mMsgText->appendText(messageText, true, style_params); - } - - snapToMessageHeight(); - -} - -void LLFloaterIMNearbyChatToastPanel::init(LLSD& notification) -{ - std::string messageText = notification["message"].asString(); // UTF-8 line of text - std::string fromName = notification["from"].asString(); // agent or object name - mFromID = notification["from_id"].asUUID(); // agent id or object id - mFromName = fromName; - - int sType = notification["source"].asInteger(); - mSourceType = (EChatSourceType)sType; - - std::string color_name = notification["text_color"].asString(); - - LLColor4 textColor = LLUIColorTable::instance().getColor(color_name); - textColor.mV[VALPHA] =notification["color_alpha"].asReal(); - - S32 font_size = notification["font_size"].asInteger(); - - LLFontGL* messageFont; - switch(font_size) - { - case 0: messageFont = LLFontGL::getFontSansSerifSmall(); break; - default: - case 1: messageFont = LLFontGL::getFontSansSerif(); break; - case 2: messageFont = LLFontGL::getFontSansSerifBig(); break; - } - - mMsgText = getChild("msg_text", false); - mMsgText->setContentTrusted(false); - mMsgText->setIsFriendCallback(LLAvatarActions::isFriend); - - mMsgText->setText(std::string("")); - - if ( notification["chat_style"].asInteger() != CHAT_STYLE_IRC ) - { - std::string str_sender; - - str_sender = fromName; - - str_sender+=" "; - - //append sender name - if (mSourceType == CHAT_SOURCE_AGENT || mSourceType == CHAT_SOURCE_OBJECT) - { - LLStyle::Params style_params_name; - - LLColor4 user_name_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); - style_params_name.color(user_name_color); - - std::string font_name = LLFontGL::nameFromFont(messageFont); - std::string font_style_size = LLFontGL::sizeFromFont(messageFont); - style_params_name.font.name(font_name); - style_params_name.font.size(font_style_size); - - style_params_name.link_href = notification["sender_slurl"].asString(); - style_params_name.is_link = true; - - mMsgText->appendText(str_sender, false, style_params_name); - - } - else - { - mMsgText->appendText(str_sender, false); - } - } - - S32 chars_in_line = mMsgText->getRect().getWidth() / messageFont->getWidth("c"); - S32 max_lines = notification["available_height"].asInteger() / (mMsgText->getTextPixelHeight() + 4); - int lines = 0; - int chars = 0; - - //Remove excessive chars if message does not fit in available height. MAINT-6891 - std::string::iterator it; - for (it = messageText.begin(); it < messageText.end() && lines < max_lines; it++) - { - if (*it == '\n') - ++lines; - else - ++chars; - - if (chars >= chars_in_line) - { - chars = 0; - ++lines; - } - } - - if (it < messageText.end()) - { - messageText.erase(it, messageText.end()); - messageText += " ..."; - } - - //append text - { - LLStyle::Params style_params; - style_params.color(textColor); - std::string font_name = LLFontGL::nameFromFont(messageFont); - std::string font_style_size = LLFontGL::sizeFromFont(messageFont); - style_params.font.name(font_name); - style_params.font.size(font_style_size); - - int chat_type = notification["chat_type"].asInteger(); - - if(notification["chat_style"].asInteger()== CHAT_STYLE_IRC) - { - style_params.font.style = "ITALIC"; - } - else if( chat_type == CHAT_TYPE_SHOUT) - { - style_params.font.style = "BOLD"; - } - else if( chat_type == CHAT_TYPE_WHISPER) - { - style_params.font.style = "ITALIC"; - } - mMsgText->appendText(messageText, false, style_params); - } - - - snapToMessageHeight(); - - mIsDirty = true;//will set Avatar Icon in draw -} - -void LLFloaterIMNearbyChatToastPanel::snapToMessageHeight () -{ - S32 new_height = llmax (mMsgText->getTextPixelHeight() + 2*mMsgText->getVPad() + 2*msg_height_pad, 25); - - LLRect panel_rect = getRect(); - - panel_rect.setLeftTopAndSize( panel_rect.mLeft, panel_rect.mTop, panel_rect.getWidth(), new_height); - - reshape( getRect().getWidth(), getRect().getHeight(), 1); - - setRect(panel_rect); - -} - -void LLFloaterIMNearbyChatToastPanel::onMouseLeave (S32 x, S32 y, MASK mask) -{ - -} -void LLFloaterIMNearbyChatToastPanel::onMouseEnter (S32 x, S32 y, MASK mask) -{ - if(mSourceType != CHAT_SOURCE_AGENT) - return; -} - -bool LLFloaterIMNearbyChatToastPanel::handleMouseDown (S32 x, S32 y, MASK mask) -{ - return LLPanel::handleMouseDown(x,y,mask); -} - -bool LLFloaterIMNearbyChatToastPanel::handleMouseUp (S32 x, S32 y, MASK mask) -{ - /* - fix for request EXT-4780 - leaving this commented since I don't remember why ew block those messages... - if(mSourceType != CHAT_SOURCE_AGENT) - return LLPanel::handleMouseUp(x,y,mask); - */ - - S32 local_x = x - mMsgText->getRect().mLeft; - S32 local_y = y - mMsgText->getRect().mBottom; - - //if text_box process mouse up (ussually this is click on url) - we didn't show nearby_chat. - if (mMsgText->pointInView(local_x, local_y)) - { - if (mMsgText->handleMouseUp(local_x, local_y, mask)) - return true; - - LLFloaterReg::getTypedInstance("nearby_chat")->showHistory(); - return false; - } - - LLFloaterReg::getTypedInstance("nearby_chat")->showHistory(); - return LLPanel::handleMouseUp(x, y, mask); -} - -void LLFloaterIMNearbyChatToastPanel::setHeaderVisibility(EShowItemHeader e) -{ - LLUICtrl* icon = getChild("avatar_icon", false); - if(icon) - icon->setVisible(e == CHATITEMHEADER_SHOW_ONLY_ICON || e==CHATITEMHEADER_SHOW_BOTH); - -} - -bool LLFloaterIMNearbyChatToastPanel::canAddText () -{ - LLChatMsgBox* msg_text = findChild("msg_text"); - if(!msg_text) - return false; - return msg_text->getLineCount()<10; -} - -bool LLFloaterIMNearbyChatToastPanel::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - LLUICtrl* avatar_icon = getChild("avatar_icon", false); - - S32 local_x = x - avatar_icon->getRect().mLeft; - S32 local_y = y - avatar_icon->getRect().mBottom; - - //eat message for avatar icon if msg was from object - if(avatar_icon->pointInView(local_x, local_y) && mSourceType != CHAT_SOURCE_AGENT) - return true; - return LLPanel::handleRightMouseDown(x,y,mask); -} -void LLFloaterIMNearbyChatToastPanel::draw() -{ - LLPanel::draw(); - - if(mIsDirty) - { - LLAvatarIconCtrl* icon = getChild("avatar_icon", false); - if(icon) - { - icon->setDrawTooltip(mSourceType == CHAT_SOURCE_AGENT); - if(mSourceType == CHAT_SOURCE_OBJECT) - icon->setValue(LLSD("OBJECT_Icon")); - else if(mSourceType == CHAT_SOURCE_SYSTEM) - icon->setValue(LLSD("SL_Logo")); - else if(mSourceType == CHAT_SOURCE_AGENT) - icon->setValue(mFromID); - else if(!mFromID.isNull()) - icon->setValue(mFromID); - } - mIsDirty = false; - } -} - - +/** + * @file llchatitemscontainer.cpp + * @brief chat history scrolling panel implementation + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llchatitemscontainerctrl.h" +#include "llchatmsgbox.h" +#include "lltextbox.h" + +#include "llavataractions.h" +#include "llavatariconctrl.h" +#include "llcommandhandler.h" +#include "llfloaterreg.h" +#include "lllocalcliprect.h" +#include "lltrans.h" +#include "llfloaterimnearbychat.h" + +#include "llviewercontrol.h" +#include "llagentdata.h" + +#include "llslurl.h" + +static constexpr S32 msg_left_offset = 10; +static constexpr S32 msg_right_offset = 10; +static constexpr S32 msg_height_pad = 5; + +//******************************************************************************************************************* +// LLObjectHandler +//******************************************************************************************************************* + +// handle secondlife:///app/object//inspect SLURLs +class LLObjectHandler : public LLCommandHandler +{ +public: + LLObjectHandler() : LLCommandHandler("object", UNTRUSTED_BLOCK) { } + + bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) + { + if (params.size() < 2) return false; + + LLUUID object_id; + if (!object_id.set(params[0], false)) + { + return false; + } + + const std::string verb = params[1].asString(); + + if (verb == "inspect") + { + LLFloaterReg::showInstance("inspect_object", LLSD().with("object_id", object_id)); + return true; + } + + return false; + } +}; + +LLObjectHandler gObjectHandler; + +//******************************************************************************************************************* +//LLFloaterIMNearbyChatToastPanel +//******************************************************************************************************************* + +LLFloaterIMNearbyChatToastPanel* LLFloaterIMNearbyChatToastPanel::createInstance() +{ + LLFloaterIMNearbyChatToastPanel* item = new LLFloaterIMNearbyChatToastPanel(); + item->buildFromFile("panel_chat_item.xml"); + item->setFollows(FOLLOWS_NONE); + return item; +} + +void LLFloaterIMNearbyChatToastPanel::reshape (S32 width, S32 height, bool called_from_parent ) +{ + LLPanel::reshape(width, height,called_from_parent); + + // reshape() may be called from LLView::initFromParams() before the children are created. + // We call findChild() instead of getChild() here to avoid creating dummy controls. + LLUICtrl* msg_text = findChild("msg_text", false); + LLUICtrl* icon = findChild("avatar_icon", false); + + if (!msg_text || !icon) + { + return; + } + + LLRect msg_text_rect = msg_text->getRect(); + LLRect avatar_rect = icon->getRect(); + + avatar_rect.setLeftTopAndSize(2,height-2,avatar_rect.getWidth(),avatar_rect.getHeight()); + icon->setRect(avatar_rect); + + + msg_text_rect.setLeftTopAndSize( avatar_rect.mRight + msg_left_offset, + height - msg_height_pad, + width - avatar_rect.mRight - msg_left_offset - msg_right_offset, + height - 2*msg_height_pad); + msg_text->reshape( msg_text_rect.getWidth(), msg_text_rect.getHeight(), 1); + msg_text->setRect(msg_text_rect); +} + +bool LLFloaterIMNearbyChatToastPanel::postBuild() +{ + return LLPanel::postBuild(); +} + +void LLFloaterIMNearbyChatToastPanel::addMessage(LLSD& notification) +{ + std::string messageText = notification["message"].asString(); // UTF-8 line of text + + + std::string color_name = notification["text_color"].asString(); + + LLColor4 textColor = LLUIColorTable::instance().getColor(color_name); + textColor.mV[VALPHA] =notification["color_alpha"].asReal(); + + S32 font_size = notification["font_size"].asInteger(); + + LLFontGL* messageFont; + switch(font_size) + { + case 0: messageFont = LLFontGL::getFontSansSerifSmall(); break; + default: + case 1: messageFont = LLFontGL::getFontSansSerif(); break; + case 2: messageFont = LLFontGL::getFontSansSerifBig(); break; + } + + //append text + { + LLStyle::Params style_params; + style_params.color(textColor); + std::string font_name = LLFontGL::nameFromFont(messageFont); + std::string font_style_size = LLFontGL::sizeFromFont(messageFont); + style_params.font.name(font_name); + style_params.font.size(font_style_size); + + int chat_type = notification["chat_type"].asInteger(); + + if(notification["chat_style"].asInteger()== CHAT_STYLE_IRC) + { + style_params.font.style = "ITALIC"; + } + else if( chat_type == CHAT_TYPE_SHOUT) + { + style_params.font.style = "BOLD"; + } + else if( chat_type == CHAT_TYPE_WHISPER) + { + style_params.font.style = "ITALIC"; + } + mMsgText->appendText(messageText, true, style_params); + } + + snapToMessageHeight(); + +} + +void LLFloaterIMNearbyChatToastPanel::init(LLSD& notification) +{ + std::string messageText = notification["message"].asString(); // UTF-8 line of text + std::string fromName = notification["from"].asString(); // agent or object name + mFromID = notification["from_id"].asUUID(); // agent id or object id + mFromName = fromName; + + int sType = notification["source"].asInteger(); + mSourceType = (EChatSourceType)sType; + + std::string color_name = notification["text_color"].asString(); + + LLColor4 textColor = LLUIColorTable::instance().getColor(color_name); + textColor.mV[VALPHA] =notification["color_alpha"].asReal(); + + S32 font_size = notification["font_size"].asInteger(); + + LLFontGL* messageFont; + switch(font_size) + { + case 0: messageFont = LLFontGL::getFontSansSerifSmall(); break; + default: + case 1: messageFont = LLFontGL::getFontSansSerif(); break; + case 2: messageFont = LLFontGL::getFontSansSerifBig(); break; + } + + mMsgText = getChild("msg_text", false); + mMsgText->setContentTrusted(false); + mMsgText->setIsFriendCallback(LLAvatarActions::isFriend); + + mMsgText->setText(std::string("")); + + if ( notification["chat_style"].asInteger() != CHAT_STYLE_IRC ) + { + std::string str_sender; + + str_sender = fromName; + + str_sender+=" "; + + //append sender name + if (mSourceType == CHAT_SOURCE_AGENT || mSourceType == CHAT_SOURCE_OBJECT) + { + LLStyle::Params style_params_name; + + LLColor4 user_name_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + style_params_name.color(user_name_color); + + std::string font_name = LLFontGL::nameFromFont(messageFont); + std::string font_style_size = LLFontGL::sizeFromFont(messageFont); + style_params_name.font.name(font_name); + style_params_name.font.size(font_style_size); + + style_params_name.link_href = notification["sender_slurl"].asString(); + style_params_name.is_link = true; + + mMsgText->appendText(str_sender, false, style_params_name); + + } + else + { + mMsgText->appendText(str_sender, false); + } + } + + S32 chars_in_line = mMsgText->getRect().getWidth() / messageFont->getWidth("c"); + S32 max_lines = notification["available_height"].asInteger() / (mMsgText->getTextPixelHeight() + 4); + int lines = 0; + int chars = 0; + + //Remove excessive chars if message does not fit in available height. MAINT-6891 + std::string::iterator it; + for (it = messageText.begin(); it < messageText.end() && lines < max_lines; it++) + { + if (*it == '\n') + ++lines; + else + ++chars; + + if (chars >= chars_in_line) + { + chars = 0; + ++lines; + } + } + + if (it < messageText.end()) + { + messageText.erase(it, messageText.end()); + messageText += " ..."; + } + + //append text + { + LLStyle::Params style_params; + style_params.color(textColor); + std::string font_name = LLFontGL::nameFromFont(messageFont); + std::string font_style_size = LLFontGL::sizeFromFont(messageFont); + style_params.font.name(font_name); + style_params.font.size(font_style_size); + + int chat_type = notification["chat_type"].asInteger(); + + if(notification["chat_style"].asInteger()== CHAT_STYLE_IRC) + { + style_params.font.style = "ITALIC"; + } + else if( chat_type == CHAT_TYPE_SHOUT) + { + style_params.font.style = "BOLD"; + } + else if( chat_type == CHAT_TYPE_WHISPER) + { + style_params.font.style = "ITALIC"; + } + mMsgText->appendText(messageText, false, style_params); + } + + + snapToMessageHeight(); + + mIsDirty = true;//will set Avatar Icon in draw +} + +void LLFloaterIMNearbyChatToastPanel::snapToMessageHeight () +{ + S32 new_height = llmax (mMsgText->getTextPixelHeight() + 2*mMsgText->getVPad() + 2*msg_height_pad, 25); + + LLRect panel_rect = getRect(); + + panel_rect.setLeftTopAndSize( panel_rect.mLeft, panel_rect.mTop, panel_rect.getWidth(), new_height); + + reshape( getRect().getWidth(), getRect().getHeight(), 1); + + setRect(panel_rect); + +} + +void LLFloaterIMNearbyChatToastPanel::onMouseLeave (S32 x, S32 y, MASK mask) +{ + +} +void LLFloaterIMNearbyChatToastPanel::onMouseEnter (S32 x, S32 y, MASK mask) +{ + if(mSourceType != CHAT_SOURCE_AGENT) + return; +} + +bool LLFloaterIMNearbyChatToastPanel::handleMouseDown (S32 x, S32 y, MASK mask) +{ + return LLPanel::handleMouseDown(x,y,mask); +} + +bool LLFloaterIMNearbyChatToastPanel::handleMouseUp (S32 x, S32 y, MASK mask) +{ + /* + fix for request EXT-4780 + leaving this commented since I don't remember why ew block those messages... + if(mSourceType != CHAT_SOURCE_AGENT) + return LLPanel::handleMouseUp(x,y,mask); + */ + + S32 local_x = x - mMsgText->getRect().mLeft; + S32 local_y = y - mMsgText->getRect().mBottom; + + //if text_box process mouse up (ussually this is click on url) - we didn't show nearby_chat. + if (mMsgText->pointInView(local_x, local_y)) + { + if (mMsgText->handleMouseUp(local_x, local_y, mask)) + return true; + + LLFloaterReg::getTypedInstance("nearby_chat")->showHistory(); + return false; + } + + LLFloaterReg::getTypedInstance("nearby_chat")->showHistory(); + return LLPanel::handleMouseUp(x, y, mask); +} + +void LLFloaterIMNearbyChatToastPanel::setHeaderVisibility(EShowItemHeader e) +{ + LLUICtrl* icon = getChild("avatar_icon", false); + if(icon) + icon->setVisible(e == CHATITEMHEADER_SHOW_ONLY_ICON || e==CHATITEMHEADER_SHOW_BOTH); + +} + +bool LLFloaterIMNearbyChatToastPanel::canAddText () +{ + LLChatMsgBox* msg_text = findChild("msg_text"); + if(!msg_text) + return false; + return msg_text->getLineCount()<10; +} + +bool LLFloaterIMNearbyChatToastPanel::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + LLUICtrl* avatar_icon = getChild("avatar_icon", false); + + S32 local_x = x - avatar_icon->getRect().mLeft; + S32 local_y = y - avatar_icon->getRect().mBottom; + + //eat message for avatar icon if msg was from object + if(avatar_icon->pointInView(local_x, local_y) && mSourceType != CHAT_SOURCE_AGENT) + return true; + return LLPanel::handleRightMouseDown(x,y,mask); +} +void LLFloaterIMNearbyChatToastPanel::draw() +{ + LLPanel::draw(); + + if(mIsDirty) + { + LLAvatarIconCtrl* icon = getChild("avatar_icon", false); + if(icon) + { + icon->setDrawTooltip(mSourceType == CHAT_SOURCE_AGENT); + if(mSourceType == CHAT_SOURCE_OBJECT) + icon->setValue(LLSD("OBJECT_Icon")); + else if(mSourceType == CHAT_SOURCE_SYSTEM) + icon->setValue(LLSD("SL_Logo")); + else if(mSourceType == CHAT_SOURCE_AGENT) + icon->setValue(mFromID); + else if(!mFromID.isNull()) + icon->setValue(mFromID); + } + mIsDirty = false; + } +} + + diff --git a/indra/newview/llchatitemscontainerctrl.h b/indra/newview/llchatitemscontainerctrl.h index 5e85a704fb..4ae73a0c43 100644 --- a/indra/newview/llchatitemscontainerctrl.h +++ b/indra/newview/llchatitemscontainerctrl.h @@ -1,100 +1,100 @@ -/** - * @file llchatitemscontainerctrl.h - * @brief chat history scrolling panel implementation - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLCHATITEMSCONTAINERCTRL_H_ -#define LL_LLCHATITEMSCONTAINERCTRL_H_ - -#include "llchat.h" -#include "llpanel.h" -#include "llscrollbar.h" -#include "llviewerchat.h" -#include "lltoastpanel.h" - -class LLChatMsgBox; - -typedef enum e_show_item_header -{ - CHATITEMHEADER_SHOW_ONLY_NAME = 0, - CHATITEMHEADER_SHOW_ONLY_ICON = 1, - CHATITEMHEADER_SHOW_BOTH -} EShowItemHeader; - -class LLFloaterIMNearbyChatToastPanel : public LLPanel -{ -protected: - LLFloaterIMNearbyChatToastPanel() - : - mIsDirty(false), - mSourceType(CHAT_SOURCE_OBJECT) - {}; -public: - ~LLFloaterIMNearbyChatToastPanel(){} - - static LLFloaterIMNearbyChatToastPanel* createInstance(); - - const LLUUID& getFromID() const { return mFromID;} - const std::string& getFromName() const { return mFromName; } - - //void addText (const std::string& message , const LLStyle::Params& input_params = LLStyle::Params()); - //void setMessage (const LLChat& msg); - void snapToMessageHeight (); - - bool canAddText (); - - void onMouseLeave (S32 x, S32 y, MASK mask); - void onMouseEnter (S32 x, S32 y, MASK mask); - bool handleMouseDown (S32 x, S32 y, MASK mask); - bool handleMouseUp (S32 x, S32 y, MASK mask); - - virtual bool postBuild(); - - void reshape (S32 width, S32 height, bool called_from_parent = true); - - void setHeaderVisibility(EShowItemHeader e); - bool handleRightMouseDown(S32 x, S32 y, MASK mask); - - virtual void init(LLSD& data); - virtual void addMessage(LLSD& data); - - virtual void draw(); - - //*TODO REMOVE, why a dup of getFromID? - const LLUUID& messageID() const { return mFromID;} -private: - LLUUID mFromID; // agent id or object id - std::string mFromName; - EChatSourceType mSourceType; - LLChatMsgBox* mMsgText; - - - - bool mIsDirty; -}; - - -#endif - - +/** + * @file llchatitemscontainerctrl.h + * @brief chat history scrolling panel implementation + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLCHATITEMSCONTAINERCTRL_H_ +#define LL_LLCHATITEMSCONTAINERCTRL_H_ + +#include "llchat.h" +#include "llpanel.h" +#include "llscrollbar.h" +#include "llviewerchat.h" +#include "lltoastpanel.h" + +class LLChatMsgBox; + +typedef enum e_show_item_header +{ + CHATITEMHEADER_SHOW_ONLY_NAME = 0, + CHATITEMHEADER_SHOW_ONLY_ICON = 1, + CHATITEMHEADER_SHOW_BOTH +} EShowItemHeader; + +class LLFloaterIMNearbyChatToastPanel : public LLPanel +{ +protected: + LLFloaterIMNearbyChatToastPanel() + : + mIsDirty(false), + mSourceType(CHAT_SOURCE_OBJECT) + {}; +public: + ~LLFloaterIMNearbyChatToastPanel(){} + + static LLFloaterIMNearbyChatToastPanel* createInstance(); + + const LLUUID& getFromID() const { return mFromID;} + const std::string& getFromName() const { return mFromName; } + + //void addText (const std::string& message , const LLStyle::Params& input_params = LLStyle::Params()); + //void setMessage (const LLChat& msg); + void snapToMessageHeight (); + + bool canAddText (); + + void onMouseLeave (S32 x, S32 y, MASK mask); + void onMouseEnter (S32 x, S32 y, MASK mask); + bool handleMouseDown (S32 x, S32 y, MASK mask); + bool handleMouseUp (S32 x, S32 y, MASK mask); + + virtual bool postBuild(); + + void reshape (S32 width, S32 height, bool called_from_parent = true); + + void setHeaderVisibility(EShowItemHeader e); + bool handleRightMouseDown(S32 x, S32 y, MASK mask); + + virtual void init(LLSD& data); + virtual void addMessage(LLSD& data); + + virtual void draw(); + + //*TODO REMOVE, why a dup of getFromID? + const LLUUID& messageID() const { return mFromID;} +private: + LLUUID mFromID; // agent id or object id + std::string mFromName; + EChatSourceType mSourceType; + LLChatMsgBox* mMsgText; + + + + bool mIsDirty; +}; + + +#endif + + diff --git a/indra/newview/llchiclet.cpp b/indra/newview/llchiclet.cpp index dcbdf6d8de..4c0f160f6f 100644 --- a/indra/newview/llchiclet.cpp +++ b/indra/newview/llchiclet.cpp @@ -1,1229 +1,1229 @@ -/** - * @file llchiclet.cpp - * @brief LLChiclet class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" // must be first include -#include "llchiclet.h" - -#include "llchicletbar.h" -#include "llfloaterimsession.h" -#include "llfloaterimcontainer.h" -#include "llfloaterreg.h" -#include "lllocalcliprect.h" -#include "llscriptfloater.h" -#include "llsingleton.h" -#include "llsyswellwindow.h" -#include "llfloaternotificationstabbed.h" -#include "llviewermenu.h" - -static LLDefaultChildRegistry::Register t1("chiclet_panel"); -static LLDefaultChildRegistry::Register t2("chiclet_notification"); -static LLDefaultChildRegistry::Register t6("chiclet_script"); -static LLDefaultChildRegistry::Register t7("chiclet_offer"); - -boost::signals2::signal > > - LLIMChiclet::sFindChicletsSignal; -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -LLSysWellChiclet::Params::Params() - : button("button") - , unread_notifications("unread_notifications") - , max_displayed_count("max_displayed_count", 99) -{ - button.name = "button"; - button.tab_stop = false; - button.label = LLStringUtil::null; -} - -LLSysWellChiclet::LLSysWellChiclet(const Params& p) - : LLChiclet(p) - , mButton(NULL) - , mCounter(0) - , mMaxDisplayedCount(p.max_displayed_count) - , mIsNewMessagesState(false) - , mFlashToLitTimer(NULL) -{ - LLButton::Params button_params = p.button; - mButton = LLUICtrlFactory::create(button_params); - addChild(mButton); - - mFlashToLitTimer = new LLFlashTimer(boost::bind(&LLSysWellChiclet::changeLitState, this, _1)); -} - -LLSysWellChiclet::~LLSysWellChiclet() -{ - mFlashToLitTimer->unset(); - LLContextMenu* menu = static_cast(mContextMenuHandle.get()); - if (menu) - { - menu->die(); - mContextMenuHandle.markDead(); - } -} - -void LLSysWellChiclet::setCounter(S32 counter) -{ - // do nothing if the same counter is coming. EXT-3678. - if (counter == mCounter) return; - - // note same code in LLChicletNotificationCounterCtrl::setCounter(S32 counter) - std::string s_count; - if(counter != 0) - { - static std::string more_messages_exist("+"); - std::string more_messages(counter > mMaxDisplayedCount ? more_messages_exist : ""); - s_count = llformat("%d%s" - , llmin(counter, mMaxDisplayedCount) - , more_messages.c_str() - ); - } - - mButton->setLabel(s_count); - - mCounter = counter; -} - -boost::signals2::connection LLSysWellChiclet::setClickCallback( - const commit_callback_t& cb) -{ - return mButton->setClickedCallback(cb); -} - -void LLSysWellChiclet::setToggleState(bool toggled) { - mButton->setToggleState(toggled); -} - -void LLSysWellChiclet::changeLitState(bool blink) -{ - setNewMessagesState(!mIsNewMessagesState); -} - -void LLSysWellChiclet::setNewMessagesState(bool new_messages) -{ - /* - Emulate 4 states of button by background images, see detains in EXT-3147 - xml attribute Description - image_unselected "Unlit" - there are no new messages - image_selected "Unlit" + "Selected" - there are no new messages and the Well is open - image_pressed "Lit" - there are new messages - image_pressed_selected "Lit" + "Selected" - there are new messages and the Well is open - */ - mButton->setForcePressedState(new_messages); - - mIsNewMessagesState = new_messages; -} - -void LLSysWellChiclet::updateWidget(bool is_window_empty) -{ - mButton->setEnabled(!is_window_empty); - - if (LLChicletBar::instanceExists()) - { - LLChicletBar::getInstance()->showWellButton(getName(), !is_window_empty); - } -} -// virtual -bool LLSysWellChiclet::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - LLContextMenu* menu_avatar = mContextMenuHandle.get(); - if(!menu_avatar) - { - createMenu(); - menu_avatar = mContextMenuHandle.get(); - } - if (menu_avatar) - { - menu_avatar->show(x, y); - LLMenuGL::showPopup(this, menu_avatar, x, y); - } - return true; -} - -/************************************************************************/ -/* LLNotificationChiclet implementation */ -/************************************************************************/ -LLNotificationChiclet::LLNotificationChiclet(const Params& p) -: LLSysWellChiclet(p), - mUreadSystemNotifications(0) -{ - mNotificationChannel.reset(new ChicletNotificationChannel(this)); - // ensure that notification well window exists, to synchronously - // handle toast add/delete events. - LLFloaterNotificationsTabbed::getInstance()->setSysWellChiclet(this); -} - -LLNotificationChiclet::~LLNotificationChiclet() -{ - mNotificationChannel.reset(); -} - -void LLNotificationChiclet::onMenuItemClicked(const LLSD& user_data) -{ - std::string action = user_data.asString(); - if("close all" == action) - { - LLFloaterNotificationsTabbed::getInstance()->closeAll(); - LLIMWellWindow::getInstance()->closeAll(); - } -} - -bool LLNotificationChiclet::enableMenuItem(const LLSD& user_data) -{ - std::string item = user_data.asString(); - if (item == "can close all") - { - return mUreadSystemNotifications != 0; - } - return true; -} - -void LLNotificationChiclet::createMenu() -{ - if(mContextMenuHandle.get()) - { - LL_WARNS() << "Menu already exists" << LL_ENDL; - return; - } - - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - registrar.add("NotificationWellChicletMenu.Action", - boost::bind(&LLNotificationChiclet::onMenuItemClicked, this, _2)); - - LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; - enable_registrar.add("NotificationWellChicletMenu.EnableItem", - boost::bind(&LLNotificationChiclet::enableMenuItem, this, _2)); - - llassert(LLMenuGL::sMenuContainer != NULL); - LLContextMenu* menu = LLUICtrlFactory::getInstance()->createFromFile - ("menu_notification_well_button.xml", - LLMenuGL::sMenuContainer, - LLViewerMenuHolderGL::child_registry_t::instance()); - if (menu) - { - mContextMenuHandle = menu->getHandle(); - } -} - -/*virtual*/ -void LLNotificationChiclet::setCounter(S32 counter) -{ - LLSysWellChiclet::setCounter(counter); - updateWidget(getCounter() == 0); - -} - -bool LLNotificationChiclet::ChicletNotificationChannel::filterNotification( LLNotificationPtr notification ) -{ - bool displayNotification; - if ( (notification->getName() == "ScriptDialog") // special case for scripts - // if there is no toast window for the notification, filter it - //|| (!LLNotificationWellWindow::getInstance()->findItemByID(notification->getID())) - || (!LLFloaterNotificationsTabbed::getInstance()->findItemByID(notification->getID(), notification->getName())) - ) - { - displayNotification = false; - } - else if( !(notification->canLogToIM() && notification->hasFormElements()) - && (!notification->getPayload().has("give_inventory_notification") - || notification->getPayload()["give_inventory_notification"])) - { - displayNotification = true; - } - else - { - displayNotification = false; - } - return displayNotification; -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -LLChiclet::Params::Params() - : show_counter("show_counter", true) - , enable_counter("enable_counter", false) -{ -} - -LLChiclet::LLChiclet(const Params& p) -: LLUICtrl(p) -, mSessionId(LLUUID::null) -, mShowCounter(p.show_counter) -{ -} - -boost::signals2::connection LLChiclet::setLeftButtonClickCallback( - const commit_callback_t& cb) -{ - return setCommitCallback(cb); -} - -bool LLChiclet::handleMouseDown(S32 x, S32 y, MASK mask) -{ - onCommit(); - childrenHandleMouseDown(x,y,mask); - return true; -} - -boost::signals2::connection LLChiclet::setChicletSizeChangedCallback( - const chiclet_size_changed_callback_t& cb) -{ - return mChicletSizeChangedSignal.connect(cb); -} - -void LLChiclet::onChicletSizeChanged() -{ - mChicletSizeChangedSignal(this, getValue()); -} - -LLSD LLChiclet::getValue() const -{ - return LLSD(getSessionId()); -} - -void LLChiclet::setValue(const LLSD& value) -{ - if(value.isUUID()) - { - setSessionId(value.asUUID()); - } -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -LLIMChiclet::LLIMChiclet(const LLIMChiclet::Params& p) -: LLChiclet(p) -, mShowSpeaker(false) -, mDefaultWidth(p.rect().getWidth()) -, mNewMessagesIcon(NULL) -, mChicletButton(NULL) -{ -} - -LLIMChiclet::~LLIMChiclet() -{ - auto menu = mPopupMenuHandle.get(); - if (menu) - { - menu->die(); - mPopupMenuHandle.markDead(); - } -} - -/* virtual*/ -bool LLIMChiclet::postBuild() -{ - mChicletButton = getChild("chiclet_button"); - mChicletButton->setCommitCallback(boost::bind(&LLIMChiclet::onMouseDown, this)); - mChicletButton->setDoubleClickCallback(boost::bind(&LLIMChiclet::onMouseDown, this)); - return true; -} - -void LLIMChiclet::enableCounterControl(bool enable) -{ - mCounterEnabled = enable; - if(!enable) - { - LLChiclet::setShowCounter(false); - } -} - -void LLIMChiclet::setRequiredWidth() -{ - S32 required_width = mDefaultWidth; - reshape(required_width, getRect().getHeight()); - onChicletSizeChanged(); -} - -void LLIMChiclet::setShowNewMessagesIcon(bool show) -{ - if(mNewMessagesIcon) - { - mNewMessagesIcon->setVisible(show); - } - setRequiredWidth(); -} - -bool LLIMChiclet::getShowNewMessagesIcon() -{ - return mNewMessagesIcon->getVisible(); -} - -void LLIMChiclet::onMouseDown() -{ - LLFloaterIMSession::toggle(getSessionId()); -} - -void LLIMChiclet::setToggleState(bool toggle) -{ - mChicletButton->setToggleState(toggle); -} - -bool LLIMChiclet::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - auto menu = static_cast(mPopupMenuHandle.get()); - if(!menu) - { - createPopupMenu(); - menu = static_cast(mPopupMenuHandle.get()); - } - - if (menu) - { - updateMenuItems(); - menu->arrangeAndClear(); - LLMenuGL::showPopup(this, menu, x, y); - } - - return true; -} - -void LLIMChiclet::hidePopupMenu() -{ - auto menu = mPopupMenuHandle.get(); - if (menu) - { - menu->setVisible(false); - } -} - -bool LLIMChiclet::canCreateMenu() -{ - if(mPopupMenuHandle.get()) - { - LL_WARNS() << "Menu already exists" << LL_ENDL; - return false; - } - if(getSessionId().isNull()) - { - return false; - } - return true; -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -LLChicletPanel::Params::Params() -: chiclet_padding("chiclet_padding") -, scrolling_offset("scrolling_offset") -, scroll_button_hpad("scroll_button_hpad") -, scroll_ratio("scroll_ratio") -, min_width("min_width") -{ -}; - -LLChicletPanel::LLChicletPanel(const Params&p) -: LLPanel(p) -, mScrollArea(NULL) -, mLeftScrollButton(NULL) -, mRightScrollButton(NULL) -, mChicletPadding(p.chiclet_padding) -, mScrollingOffset(p.scrolling_offset) -, mScrollButtonHPad(p.scroll_button_hpad) -, mScrollRatio(p.scroll_ratio) -, mMinWidth(p.min_width) -, mShowControls(true) -{ - LLPanel::Params panel_params; - panel_params.follows.flags(FOLLOWS_LEFT | FOLLOWS_RIGHT); - mScrollArea = LLUICtrlFactory::create(panel_params,this); - - // important for Show/Hide Camera and Move controls menu in bottom tray to work properly - mScrollArea->setMouseOpaque(false); - - addChild(mScrollArea); -} - -LLChicletPanel::~LLChicletPanel() -{ - if(LLTransientFloaterMgr::instanceExists()) - { - LLTransientFloaterMgr::getInstance()->removeControlView(mLeftScrollButton); - LLTransientFloaterMgr::getInstance()->removeControlView(mRightScrollButton); - } -} - -void LLChicletPanel::onMessageCountChanged(const LLSD& data) -{ - // *TODO : we either suppress this method or return a value. Right now, it servers no purpose. - /* - - //LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); - //if (im_floater && im_floater->getVisible() && im_floater->hasFocus()) - //{ - // unread = 0; - //} - */ -} - -void LLChicletPanel::objectChicletCallback(const LLSD& data) -{ - LLUUID notification_id = data["notification_id"]; - bool new_message = data["new_message"]; - - std::list chiclets = LLIMChiclet::sFindChicletsSignal(notification_id); - std::list::iterator iter; - for (iter = chiclets.begin(); iter != chiclets.end(); iter++) - { - LLIMChiclet* chiclet = dynamic_cast(*iter); - if (chiclet != NULL) - { - chiclet->setShowNewMessagesIcon(new_message); - } - } -} - -bool LLChicletPanel::postBuild() -{ - LLPanel::postBuild(); - LLIMModel::instance().addNewMsgCallback(boost::bind(&LLChicletPanel::onMessageCountChanged, this, _1)); - LLIMModel::instance().addNoUnreadMsgsCallback(boost::bind(&LLChicletPanel::onMessageCountChanged, this, _1)); - LLScriptFloaterManager::getInstance()->addNewObjectCallback(boost::bind(&LLChicletPanel::objectChicletCallback, this, _1)); - LLScriptFloaterManager::getInstance()->addToggleObjectFloaterCallback(boost::bind(&LLChicletPanel::objectChicletCallback, this, _1)); - LLIMChiclet::sFindChicletsSignal.connect(boost::bind(&LLChicletPanel::findChiclet, this, _1)); - LLVoiceChannel::setCurrentVoiceChannelChangedCallback(boost::bind(&LLChicletPanel::onCurrentVoiceChannelChanged, this, _1)); - - mLeftScrollButton=getChild("chicklet_left_scroll_button"); - LLTransientFloaterMgr::getInstance()->addControlView(mLeftScrollButton); - mLeftScrollButton->setMouseDownCallback(boost::bind(&LLChicletPanel::onLeftScrollClick,this)); - mLeftScrollButton->setHeldDownCallback(boost::bind(&LLChicletPanel::onLeftScrollHeldDown,this)); - mLeftScrollButton->setEnabled(false); - - mRightScrollButton=getChild("chicklet_right_scroll_button"); - LLTransientFloaterMgr::getInstance()->addControlView(mRightScrollButton); - mRightScrollButton->setMouseDownCallback(boost::bind(&LLChicletPanel::onRightScrollClick,this)); - mRightScrollButton->setHeldDownCallback(boost::bind(&LLChicletPanel::onRightScrollHeldDown,this)); - mRightScrollButton->setEnabled(false); - - return true; -} - -void LLChicletPanel::onCurrentVoiceChannelChanged(const LLUUID& session_id) -{ - static LLUUID s_previous_active_voice_session_id; - - std::list chiclets = LLIMChiclet::sFindChicletsSignal(session_id); - - for(std::list::iterator it = chiclets.begin(); it != chiclets.end(); ++it) - { - LLIMChiclet* chiclet = dynamic_cast(*it); - if(chiclet) - { - if (gSavedSettings.getBOOL("OpenIMOnVoice")) - { - LLFloaterIMContainer::getInstance()->showConversation(session_id); - } - } - } - - s_previous_active_voice_session_id = session_id; -} - -bool LLChicletPanel::addChiclet(LLChiclet* chiclet, S32 index) -{ - if(mScrollArea->addChild(chiclet)) - { - // chiclets should be aligned to right edge of scroll panel - S32 left_shift = 0; - - if (!canScrollLeft()) - { - // init left shift for the first chiclet in the list... - if (mChicletList.empty()) - { - // ...start from the right border of the scroll area for the first added chiclet - left_shift = mScrollArea->getRect().getWidth(); - } - else - { - // ... start from the left border of the first chiclet minus padding - left_shift = getChiclet(0)->getRect().mLeft - getChicletPadding(); - } - - // take into account width of the being added chiclet - left_shift -= chiclet->getRequiredRect().getWidth(); - - // if we overflow the scroll area we do not need to shift chiclets - if (left_shift < 0) - { - left_shift = 0; - } - } - - mChicletList.insert(mChicletList.begin() + index, chiclet); - - // shift first chiclet to place it in correct position. - // rest ones will be placed in arrange() - if (!canScrollLeft()) - { - getChiclet(0)->translate(left_shift - getChiclet(0)->getRect().mLeft, 0); - } - - chiclet->setLeftButtonClickCallback(boost::bind(&LLChicletPanel::onChicletClick, this, _1, _2)); - chiclet->setChicletSizeChangedCallback(boost::bind(&LLChicletPanel::onChicletSizeChanged, this, _1, index)); - - arrange(); - LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::IM, chiclet); - - return true; - } - - return false; -} - -void LLChicletPanel::onChicletSizeChanged(LLChiclet* ctrl, const LLSD& param) -{ - arrange(); -} - -void LLChicletPanel::onChicletClick(LLUICtrl*ctrl,const LLSD¶m) -{ - if (mCommitSignal) - { - (*mCommitSignal)(ctrl,param); - } -} - -void LLChicletPanel::removeChiclet(chiclet_list_t::iterator it) -{ - LLChiclet* chiclet = *it; - mScrollArea->removeChild(chiclet); - mChicletList.erase(it); - - arrange(); - LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::IM, chiclet); - chiclet->die(); -} - -void LLChicletPanel::removeChiclet(S32 index) -{ - if(index >= 0 && index < getChicletCount()) - { - removeChiclet(mChicletList.begin() + index); - } -} - -S32 LLChicletPanel::getChicletIndex(const LLChiclet* chiclet) -{ - if(mChicletList.empty()) - return -1; - - S32 size = getChicletCount(); - for(int n = 0; n < size; ++n) - { - if(chiclet == mChicletList[n]) - return n; - } - - return -1; -} - -void LLChicletPanel::removeChiclet(LLChiclet*chiclet) -{ - chiclet_list_t::iterator it = mChicletList.begin(); - for( ; mChicletList.end() != it; ++it) - { - LLChiclet* temp = *it; - if(temp == chiclet) - { - removeChiclet(it); - return; - } - } -} - -void LLChicletPanel::removeChiclet(const LLUUID& im_session_id) -{ - chiclet_list_t::iterator it = mChicletList.begin(); - for( ; mChicletList.end() != it; ++it) - { - LLIMChiclet* chiclet = dynamic_cast(*it); - - if(chiclet->getSessionId() == im_session_id) - { - removeChiclet(it); - return; - } - } -} - -void LLChicletPanel::removeAll() -{ - S32 size = getChicletCount(); - for(S32 n = 0; n < size; ++n) - { - mScrollArea->removeChild(mChicletList[n]); - } - - mChicletList.erase(mChicletList.begin(), mChicletList.end()); - - showScrollButtonsIfNeeded(); -} - -void LLChicletPanel::scrollToChiclet(const LLChiclet* chiclet) -{ - const LLRect& rect = chiclet->getRect(); - - if (rect.mLeft < 0) - { - scroll(llabs(rect.mLeft)); - showScrollButtonsIfNeeded(); - } - else - { - S32 scrollWidth = mScrollArea->getRect().getWidth(); - - if (rect.mRight > scrollWidth) - { - scroll(-llabs(rect.mRight - scrollWidth)); - showScrollButtonsIfNeeded(); - } - } -} - -void LLChicletPanel::reshape(S32 width, S32 height, bool called_from_parent ) -{ - LLPanel::reshape(width,height,called_from_parent); - - //Needed once- to avoid error at first call of reshape() before postBuild() - if(!mLeftScrollButton||!mRightScrollButton) - return; - - LLRect scroll_button_rect = mLeftScrollButton->getRect(); - mLeftScrollButton->setRect(LLRect(0,scroll_button_rect.mTop,scroll_button_rect.getWidth(), - scroll_button_rect.mBottom)); - scroll_button_rect = mRightScrollButton->getRect(); - mRightScrollButton->setRect(LLRect(width - scroll_button_rect.getWidth(),scroll_button_rect.mTop, - width, scroll_button_rect.mBottom)); - - - bool need_show_scroll = needShowScroll(); - if(need_show_scroll) - { - mScrollArea->setRect(LLRect(scroll_button_rect.getWidth() + mScrollButtonHPad, - height, width - scroll_button_rect.getWidth() - mScrollButtonHPad, 0)); - } - else - { - mScrollArea->setRect(LLRect(0,height, width, 0)); - } - - mShowControls = width >= mMinWidth; - - mScrollArea->setVisible(mShowControls); - - trimChiclets(); - showScrollButtonsIfNeeded(); - -} - -S32 LLChicletPanel::notifyParent(const LLSD& info) -{ - if(info.has("notification")) - { - std::string str_notification = info["notification"]; - if(str_notification == "size_changes") - { - arrange(); - return 1; - } - } - return LLPanel::notifyParent(info); -} - -void LLChicletPanel::setChicletToggleState(const LLUUID& session_id, bool toggle) -{ - if(session_id.isNull()) - { - LL_WARNS() << "Null Session ID" << LL_ENDL; - } - - // toggle off all chiclets, except specified - S32 size = getChicletCount(); - for(int n = 0; n < size; ++n) - { - LLIMChiclet* chiclet = getChiclet(n); - if(chiclet && chiclet->getSessionId() != session_id) - { - chiclet->setToggleState(false); - } - } - - // toggle specified chiclet - LLIMChiclet* chiclet = findChiclet(session_id); - if(chiclet) - { - chiclet->setToggleState(toggle); - } -} - -void LLChicletPanel::arrange() -{ - if(mChicletList.empty()) - return; - - //initial arrange of chicklets positions - S32 chiclet_left = getChiclet(0)->getRect().mLeft; - S32 size = getChicletCount(); - for( int n = 0; n < size; ++n) - { - LLChiclet* chiclet = getChiclet(n); - - S32 chiclet_width = chiclet->getRequiredRect().getWidth(); - LLRect rect = chiclet->getRect(); - rect.set(chiclet_left, rect.mTop, chiclet_left + chiclet_width, rect.mBottom); - - chiclet->setRect(rect); - - chiclet_left += chiclet_width + getChicletPadding(); - } - - //reset size and pos on mScrollArea - LLRect rect = getRect(); - LLRect scroll_button_rect = mLeftScrollButton->getRect(); - - bool need_show_scroll = needShowScroll(); - if(need_show_scroll) - { - mScrollArea->setRect(LLRect(scroll_button_rect.getWidth() + mScrollButtonHPad, - rect.getHeight(), rect.getWidth() - scroll_button_rect.getWidth() - mScrollButtonHPad, 0)); - } - else - { - mScrollArea->setRect(LLRect(0,rect.getHeight(), rect.getWidth(), 0)); - } - - trimChiclets(); - showScrollButtonsIfNeeded(); -} - -void LLChicletPanel::trimChiclets() -{ - // trim right - if(!mChicletList.empty()) - { - S32 last_chiclet_right = (*mChicletList.rbegin())->getRect().mRight; - S32 first_chiclet_left = getChiclet(0)->getRect().mLeft; - S32 scroll_width = mScrollArea->getRect().getWidth(); - if(last_chiclet_right < scroll_width || first_chiclet_left > 0) - { - shiftChiclets(scroll_width - last_chiclet_right); - } - } -} - -bool LLChicletPanel::needShowScroll() -{ - if(mChicletList.empty()) - return false; - - S32 chicklet_width = (*mChicletList.rbegin())->getRect().mRight - (*mChicletList.begin())->getRect().mLeft; - - return chicklet_width>getRect().getWidth(); -} - - -void LLChicletPanel::showScrollButtonsIfNeeded() -{ - bool can_scroll_left = canScrollLeft(); - bool can_scroll_right = canScrollRight(); - - mLeftScrollButton->setEnabled(can_scroll_left); - mRightScrollButton->setEnabled(can_scroll_right); - - bool show_scroll_buttons = (can_scroll_left || can_scroll_right) && mShowControls; - - mLeftScrollButton->setVisible(show_scroll_buttons); - mRightScrollButton->setVisible(show_scroll_buttons); -} - -void LLChicletPanel::draw() -{ - child_list_const_iter_t it = getChildList()->begin(); - for( ; getChildList()->end() != it; ++it) - { - LLView* child = *it; - if(child == dynamic_cast(mScrollArea)) - { - LLLocalClipRect clip(mScrollArea->getRect()); - drawChild(mScrollArea); - } - else - { - drawChild(child); - } - } -} - -bool LLChicletPanel::canScrollRight() -{ - if(mChicletList.empty()) - return false; - - S32 scroll_width = mScrollArea->getRect().getWidth(); - S32 last_chiclet_right = (*mChicletList.rbegin())->getRect().mRight; - - if(last_chiclet_right > scroll_width) - return true; - - return false; -} - -bool LLChicletPanel::canScrollLeft() -{ - if(mChicletList.empty()) - return false; - - return getChiclet(0)->getRect().mLeft < 0; -} - -void LLChicletPanel::scroll(S32 offset) -{ - shiftChiclets(offset); -} - -void LLChicletPanel::shiftChiclets(S32 offset, S32 start_index /* = 0 */) -{ - if(start_index < 0 || start_index >= getChicletCount()) - { - return; - } - - chiclet_list_t::const_iterator it = mChicletList.begin() + start_index; - for(;mChicletList.end() != it; ++it) - { - LLChiclet* chiclet = *it; - chiclet->translate(offset,0); - } -} - -void LLChicletPanel::scrollLeft() -{ - if(canScrollLeft()) - { - S32 offset = getScrollingOffset(); - LLRect first_chiclet_rect = getChiclet(0)->getRect(); - - // shift chiclets in case first chiclet is partially visible - if(first_chiclet_rect.mLeft < 0 && first_chiclet_rect.mRight > 0) - { - offset = llabs(first_chiclet_rect.mLeft); - } - - scroll(offset); - - showScrollButtonsIfNeeded(); - } -} - -void LLChicletPanel::scrollRight() -{ - if(canScrollRight()) - { - S32 offset = - getScrollingOffset(); - - S32 last_chiclet_right = (*mChicletList.rbegin())->getRect().mRight; - S32 scroll_rect_width = mScrollArea->getRect().getWidth(); - // if after scrolling, the last chiclet will not be aligned to - // scroll area right side - align it. - if( last_chiclet_right + offset < scroll_rect_width ) - { - offset = scroll_rect_width - last_chiclet_right; - } - - scroll(offset); - - showScrollButtonsIfNeeded(); - } -} - -void LLChicletPanel::onLeftScrollClick() -{ - scrollLeft(); -} - -void LLChicletPanel::onRightScrollClick() -{ - scrollRight(); -} - -void LLChicletPanel::onLeftScrollHeldDown() -{ - S32 offset = mScrollingOffset; - mScrollingOffset = mScrollingOffset / mScrollRatio; - scrollLeft(); - mScrollingOffset = offset; -} - -void LLChicletPanel::onRightScrollHeldDown() -{ - S32 offset = mScrollingOffset; - mScrollingOffset = mScrollingOffset / mScrollRatio; - scrollRight(); - mScrollingOffset = offset; -} - -boost::signals2::connection LLChicletPanel::setChicletClickedCallback( - const commit_callback_t& cb) -{ - return setCommitCallback(cb); -} - -bool LLChicletPanel::handleScrollWheel(S32 x, S32 y, S32 clicks) -{ - if(clicks > 0) - { - scrollRight(); - } - else - { - scrollLeft(); - } - return true; -} - -bool LLChicletPanel::isAnyIMFloaterDoked() -{ - bool res = false; - for (chiclet_list_t::iterator it = mChicletList.begin(); it - != mChicletList.end(); it++) - { - LLFloaterIMSession* im_floater = LLFloaterReg::findTypedInstance( - "impanel", (*it)->getSessionId()); - if (im_floater != NULL && im_floater->getVisible() - && !im_floater->isMinimized() && im_floater->isDocked()) - { - res = true; - break; - } - } - - return res; -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -LLChicletNotificationCounterCtrl::Params::Params() - : max_displayed_count("max_displayed_count", 99) -{ -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -LLChicletAvatarIconCtrl::LLChicletAvatarIconCtrl(const Params& p) - : LLAvatarIconCtrl(p) -{ -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -LLChicletInvOfferIconCtrl::LLChicletInvOfferIconCtrl(const Params& p) -: LLChicletAvatarIconCtrl(p) - , mDefaultIcon(p.default_icon) -{ -} - -void LLChicletInvOfferIconCtrl::setValue(const LLSD& value ) -{ - if(value.asUUID().isNull()) - { - LLIconCtrl::setValue(mDefaultIcon); - } - else - { - LLChicletAvatarIconCtrl::setValue(value); - } -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -LLScriptChiclet::Params::Params() - : icon("icon") - , chiclet_button("chiclet_button") - , new_message_icon("new_message_icon") -{ -} - -LLScriptChiclet::LLScriptChiclet(const Params&p) - : LLIMChiclet(p) - , mChicletIconCtrl(NULL) -{ - LLButton::Params button_params = p.chiclet_button; - mChicletButton = LLUICtrlFactory::create(button_params); - addChild(mChicletButton); - - LLIconCtrl::Params new_msg_params = p.new_message_icon; - mNewMessagesIcon = LLUICtrlFactory::create(new_msg_params); - addChild(mNewMessagesIcon); - - LLIconCtrl::Params icon_params = p.icon; - mChicletIconCtrl = LLUICtrlFactory::create(icon_params); - addChild(mChicletIconCtrl); - - sendChildToFront(mNewMessagesIcon); -} - -void LLScriptChiclet::setSessionId(const LLUUID& session_id) -{ - setShowNewMessagesIcon( getSessionId() != session_id ); - - LLIMChiclet::setSessionId(session_id); - - setToolTip(LLScriptFloaterManager::getObjectName(session_id)); -} - -void LLScriptChiclet::onMouseDown() -{ - LLScriptFloaterManager::getInstance()->toggleScriptFloater(getSessionId()); -} - -void LLScriptChiclet::onMenuItemClicked(const LLSD& user_data) -{ - std::string action = user_data.asString(); - - if("end" == action) - { - LLScriptFloaterManager::instance().removeNotification(getSessionId()); - } - else if ("close all" == action) - { - LLIMWellWindow::getInstance()->closeAll(); - } -} - -void LLScriptChiclet::createPopupMenu() -{ - if(!canCreateMenu()) - return; - - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - registrar.add("ScriptChiclet.Action", boost::bind(&LLScriptChiclet::onMenuItemClicked, this, _2)); - - LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile - ("menu_script_chiclet.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - if (menu) - { - mPopupMenuHandle = menu->getHandle(); - } - -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -static const std::string INVENTORY_USER_OFFER ("UserGiveItem"); - -LLInvOfferChiclet::Params::Params() - : icon("icon") - , chiclet_button("chiclet_button") - , new_message_icon("new_message_icon") -{ -} - -LLInvOfferChiclet::LLInvOfferChiclet(const Params&p) - : LLIMChiclet(p) - , mChicletIconCtrl(NULL) -{ - LLButton::Params button_params = p.chiclet_button; - mChicletButton = LLUICtrlFactory::create(button_params); - addChild(mChicletButton); - - LLIconCtrl::Params new_msg_params = p.new_message_icon; - mNewMessagesIcon = LLUICtrlFactory::create(new_msg_params); - addChild(mNewMessagesIcon); - - LLChicletInvOfferIconCtrl::Params icon_params = p.icon; - mChicletIconCtrl = LLUICtrlFactory::create(icon_params); - addChild(mChicletIconCtrl); - - sendChildToFront(mNewMessagesIcon); -} - -void LLInvOfferChiclet::setSessionId(const LLUUID& session_id) -{ - setShowNewMessagesIcon( getSessionId() != session_id ); - - setToolTip(LLScriptFloaterManager::getObjectName(session_id)); - - LLIMChiclet::setSessionId(session_id); - LLNotificationPtr notification = LLNotifications::getInstance()->find(session_id); - - if ( notification && notification->getName() == INVENTORY_USER_OFFER ) - { - mChicletIconCtrl->setValue(notification->getPayload()["from_id"]); - } - else - { - mChicletIconCtrl->setValue(LLUUID::null); - } -} - -void LLInvOfferChiclet::onMouseDown() -{ - LLScriptFloaterManager::instance().toggleScriptFloater(getSessionId()); -} - -void LLInvOfferChiclet::onMenuItemClicked(const LLSD& user_data) -{ - std::string action = user_data.asString(); - - if("end" == action) - { - LLScriptFloaterManager::instance().removeNotification(getSessionId()); - } -} - -void LLInvOfferChiclet::createPopupMenu() -{ - if(!canCreateMenu()) - return; - - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - registrar.add("InvOfferChiclet.Action", boost::bind(&LLInvOfferChiclet::onMenuItemClicked, this, _2)); - - LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile - ("menu_inv_offer_chiclet.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - if (menu) - { - mPopupMenuHandle = menu->getHandle(); - } -} - -// EOF +/** + * @file llchiclet.cpp + * @brief LLChiclet class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" // must be first include +#include "llchiclet.h" + +#include "llchicletbar.h" +#include "llfloaterimsession.h" +#include "llfloaterimcontainer.h" +#include "llfloaterreg.h" +#include "lllocalcliprect.h" +#include "llscriptfloater.h" +#include "llsingleton.h" +#include "llsyswellwindow.h" +#include "llfloaternotificationstabbed.h" +#include "llviewermenu.h" + +static LLDefaultChildRegistry::Register t1("chiclet_panel"); +static LLDefaultChildRegistry::Register t2("chiclet_notification"); +static LLDefaultChildRegistry::Register t6("chiclet_script"); +static LLDefaultChildRegistry::Register t7("chiclet_offer"); + +boost::signals2::signal > > + LLIMChiclet::sFindChicletsSignal; +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +LLSysWellChiclet::Params::Params() + : button("button") + , unread_notifications("unread_notifications") + , max_displayed_count("max_displayed_count", 99) +{ + button.name = "button"; + button.tab_stop = false; + button.label = LLStringUtil::null; +} + +LLSysWellChiclet::LLSysWellChiclet(const Params& p) + : LLChiclet(p) + , mButton(NULL) + , mCounter(0) + , mMaxDisplayedCount(p.max_displayed_count) + , mIsNewMessagesState(false) + , mFlashToLitTimer(NULL) +{ + LLButton::Params button_params = p.button; + mButton = LLUICtrlFactory::create(button_params); + addChild(mButton); + + mFlashToLitTimer = new LLFlashTimer(boost::bind(&LLSysWellChiclet::changeLitState, this, _1)); +} + +LLSysWellChiclet::~LLSysWellChiclet() +{ + mFlashToLitTimer->unset(); + LLContextMenu* menu = static_cast(mContextMenuHandle.get()); + if (menu) + { + menu->die(); + mContextMenuHandle.markDead(); + } +} + +void LLSysWellChiclet::setCounter(S32 counter) +{ + // do nothing if the same counter is coming. EXT-3678. + if (counter == mCounter) return; + + // note same code in LLChicletNotificationCounterCtrl::setCounter(S32 counter) + std::string s_count; + if(counter != 0) + { + static std::string more_messages_exist("+"); + std::string more_messages(counter > mMaxDisplayedCount ? more_messages_exist : ""); + s_count = llformat("%d%s" + , llmin(counter, mMaxDisplayedCount) + , more_messages.c_str() + ); + } + + mButton->setLabel(s_count); + + mCounter = counter; +} + +boost::signals2::connection LLSysWellChiclet::setClickCallback( + const commit_callback_t& cb) +{ + return mButton->setClickedCallback(cb); +} + +void LLSysWellChiclet::setToggleState(bool toggled) { + mButton->setToggleState(toggled); +} + +void LLSysWellChiclet::changeLitState(bool blink) +{ + setNewMessagesState(!mIsNewMessagesState); +} + +void LLSysWellChiclet::setNewMessagesState(bool new_messages) +{ + /* + Emulate 4 states of button by background images, see detains in EXT-3147 + xml attribute Description + image_unselected "Unlit" - there are no new messages + image_selected "Unlit" + "Selected" - there are no new messages and the Well is open + image_pressed "Lit" - there are new messages + image_pressed_selected "Lit" + "Selected" - there are new messages and the Well is open + */ + mButton->setForcePressedState(new_messages); + + mIsNewMessagesState = new_messages; +} + +void LLSysWellChiclet::updateWidget(bool is_window_empty) +{ + mButton->setEnabled(!is_window_empty); + + if (LLChicletBar::instanceExists()) + { + LLChicletBar::getInstance()->showWellButton(getName(), !is_window_empty); + } +} +// virtual +bool LLSysWellChiclet::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + LLContextMenu* menu_avatar = mContextMenuHandle.get(); + if(!menu_avatar) + { + createMenu(); + menu_avatar = mContextMenuHandle.get(); + } + if (menu_avatar) + { + menu_avatar->show(x, y); + LLMenuGL::showPopup(this, menu_avatar, x, y); + } + return true; +} + +/************************************************************************/ +/* LLNotificationChiclet implementation */ +/************************************************************************/ +LLNotificationChiclet::LLNotificationChiclet(const Params& p) +: LLSysWellChiclet(p), + mUreadSystemNotifications(0) +{ + mNotificationChannel.reset(new ChicletNotificationChannel(this)); + // ensure that notification well window exists, to synchronously + // handle toast add/delete events. + LLFloaterNotificationsTabbed::getInstance()->setSysWellChiclet(this); +} + +LLNotificationChiclet::~LLNotificationChiclet() +{ + mNotificationChannel.reset(); +} + +void LLNotificationChiclet::onMenuItemClicked(const LLSD& user_data) +{ + std::string action = user_data.asString(); + if("close all" == action) + { + LLFloaterNotificationsTabbed::getInstance()->closeAll(); + LLIMWellWindow::getInstance()->closeAll(); + } +} + +bool LLNotificationChiclet::enableMenuItem(const LLSD& user_data) +{ + std::string item = user_data.asString(); + if (item == "can close all") + { + return mUreadSystemNotifications != 0; + } + return true; +} + +void LLNotificationChiclet::createMenu() +{ + if(mContextMenuHandle.get()) + { + LL_WARNS() << "Menu already exists" << LL_ENDL; + return; + } + + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + registrar.add("NotificationWellChicletMenu.Action", + boost::bind(&LLNotificationChiclet::onMenuItemClicked, this, _2)); + + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + enable_registrar.add("NotificationWellChicletMenu.EnableItem", + boost::bind(&LLNotificationChiclet::enableMenuItem, this, _2)); + + llassert(LLMenuGL::sMenuContainer != NULL); + LLContextMenu* menu = LLUICtrlFactory::getInstance()->createFromFile + ("menu_notification_well_button.xml", + LLMenuGL::sMenuContainer, + LLViewerMenuHolderGL::child_registry_t::instance()); + if (menu) + { + mContextMenuHandle = menu->getHandle(); + } +} + +/*virtual*/ +void LLNotificationChiclet::setCounter(S32 counter) +{ + LLSysWellChiclet::setCounter(counter); + updateWidget(getCounter() == 0); + +} + +bool LLNotificationChiclet::ChicletNotificationChannel::filterNotification( LLNotificationPtr notification ) +{ + bool displayNotification; + if ( (notification->getName() == "ScriptDialog") // special case for scripts + // if there is no toast window for the notification, filter it + //|| (!LLNotificationWellWindow::getInstance()->findItemByID(notification->getID())) + || (!LLFloaterNotificationsTabbed::getInstance()->findItemByID(notification->getID(), notification->getName())) + ) + { + displayNotification = false; + } + else if( !(notification->canLogToIM() && notification->hasFormElements()) + && (!notification->getPayload().has("give_inventory_notification") + || notification->getPayload()["give_inventory_notification"])) + { + displayNotification = true; + } + else + { + displayNotification = false; + } + return displayNotification; +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +LLChiclet::Params::Params() + : show_counter("show_counter", true) + , enable_counter("enable_counter", false) +{ +} + +LLChiclet::LLChiclet(const Params& p) +: LLUICtrl(p) +, mSessionId(LLUUID::null) +, mShowCounter(p.show_counter) +{ +} + +boost::signals2::connection LLChiclet::setLeftButtonClickCallback( + const commit_callback_t& cb) +{ + return setCommitCallback(cb); +} + +bool LLChiclet::handleMouseDown(S32 x, S32 y, MASK mask) +{ + onCommit(); + childrenHandleMouseDown(x,y,mask); + return true; +} + +boost::signals2::connection LLChiclet::setChicletSizeChangedCallback( + const chiclet_size_changed_callback_t& cb) +{ + return mChicletSizeChangedSignal.connect(cb); +} + +void LLChiclet::onChicletSizeChanged() +{ + mChicletSizeChangedSignal(this, getValue()); +} + +LLSD LLChiclet::getValue() const +{ + return LLSD(getSessionId()); +} + +void LLChiclet::setValue(const LLSD& value) +{ + if(value.isUUID()) + { + setSessionId(value.asUUID()); + } +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +LLIMChiclet::LLIMChiclet(const LLIMChiclet::Params& p) +: LLChiclet(p) +, mShowSpeaker(false) +, mDefaultWidth(p.rect().getWidth()) +, mNewMessagesIcon(NULL) +, mChicletButton(NULL) +{ +} + +LLIMChiclet::~LLIMChiclet() +{ + auto menu = mPopupMenuHandle.get(); + if (menu) + { + menu->die(); + mPopupMenuHandle.markDead(); + } +} + +/* virtual*/ +bool LLIMChiclet::postBuild() +{ + mChicletButton = getChild("chiclet_button"); + mChicletButton->setCommitCallback(boost::bind(&LLIMChiclet::onMouseDown, this)); + mChicletButton->setDoubleClickCallback(boost::bind(&LLIMChiclet::onMouseDown, this)); + return true; +} + +void LLIMChiclet::enableCounterControl(bool enable) +{ + mCounterEnabled = enable; + if(!enable) + { + LLChiclet::setShowCounter(false); + } +} + +void LLIMChiclet::setRequiredWidth() +{ + S32 required_width = mDefaultWidth; + reshape(required_width, getRect().getHeight()); + onChicletSizeChanged(); +} + +void LLIMChiclet::setShowNewMessagesIcon(bool show) +{ + if(mNewMessagesIcon) + { + mNewMessagesIcon->setVisible(show); + } + setRequiredWidth(); +} + +bool LLIMChiclet::getShowNewMessagesIcon() +{ + return mNewMessagesIcon->getVisible(); +} + +void LLIMChiclet::onMouseDown() +{ + LLFloaterIMSession::toggle(getSessionId()); +} + +void LLIMChiclet::setToggleState(bool toggle) +{ + mChicletButton->setToggleState(toggle); +} + +bool LLIMChiclet::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + auto menu = static_cast(mPopupMenuHandle.get()); + if(!menu) + { + createPopupMenu(); + menu = static_cast(mPopupMenuHandle.get()); + } + + if (menu) + { + updateMenuItems(); + menu->arrangeAndClear(); + LLMenuGL::showPopup(this, menu, x, y); + } + + return true; +} + +void LLIMChiclet::hidePopupMenu() +{ + auto menu = mPopupMenuHandle.get(); + if (menu) + { + menu->setVisible(false); + } +} + +bool LLIMChiclet::canCreateMenu() +{ + if(mPopupMenuHandle.get()) + { + LL_WARNS() << "Menu already exists" << LL_ENDL; + return false; + } + if(getSessionId().isNull()) + { + return false; + } + return true; +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +LLChicletPanel::Params::Params() +: chiclet_padding("chiclet_padding") +, scrolling_offset("scrolling_offset") +, scroll_button_hpad("scroll_button_hpad") +, scroll_ratio("scroll_ratio") +, min_width("min_width") +{ +}; + +LLChicletPanel::LLChicletPanel(const Params&p) +: LLPanel(p) +, mScrollArea(NULL) +, mLeftScrollButton(NULL) +, mRightScrollButton(NULL) +, mChicletPadding(p.chiclet_padding) +, mScrollingOffset(p.scrolling_offset) +, mScrollButtonHPad(p.scroll_button_hpad) +, mScrollRatio(p.scroll_ratio) +, mMinWidth(p.min_width) +, mShowControls(true) +{ + LLPanel::Params panel_params; + panel_params.follows.flags(FOLLOWS_LEFT | FOLLOWS_RIGHT); + mScrollArea = LLUICtrlFactory::create(panel_params,this); + + // important for Show/Hide Camera and Move controls menu in bottom tray to work properly + mScrollArea->setMouseOpaque(false); + + addChild(mScrollArea); +} + +LLChicletPanel::~LLChicletPanel() +{ + if(LLTransientFloaterMgr::instanceExists()) + { + LLTransientFloaterMgr::getInstance()->removeControlView(mLeftScrollButton); + LLTransientFloaterMgr::getInstance()->removeControlView(mRightScrollButton); + } +} + +void LLChicletPanel::onMessageCountChanged(const LLSD& data) +{ + // *TODO : we either suppress this method or return a value. Right now, it servers no purpose. + /* + + //LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); + //if (im_floater && im_floater->getVisible() && im_floater->hasFocus()) + //{ + // unread = 0; + //} + */ +} + +void LLChicletPanel::objectChicletCallback(const LLSD& data) +{ + LLUUID notification_id = data["notification_id"]; + bool new_message = data["new_message"]; + + std::list chiclets = LLIMChiclet::sFindChicletsSignal(notification_id); + std::list::iterator iter; + for (iter = chiclets.begin(); iter != chiclets.end(); iter++) + { + LLIMChiclet* chiclet = dynamic_cast(*iter); + if (chiclet != NULL) + { + chiclet->setShowNewMessagesIcon(new_message); + } + } +} + +bool LLChicletPanel::postBuild() +{ + LLPanel::postBuild(); + LLIMModel::instance().addNewMsgCallback(boost::bind(&LLChicletPanel::onMessageCountChanged, this, _1)); + LLIMModel::instance().addNoUnreadMsgsCallback(boost::bind(&LLChicletPanel::onMessageCountChanged, this, _1)); + LLScriptFloaterManager::getInstance()->addNewObjectCallback(boost::bind(&LLChicletPanel::objectChicletCallback, this, _1)); + LLScriptFloaterManager::getInstance()->addToggleObjectFloaterCallback(boost::bind(&LLChicletPanel::objectChicletCallback, this, _1)); + LLIMChiclet::sFindChicletsSignal.connect(boost::bind(&LLChicletPanel::findChiclet, this, _1)); + LLVoiceChannel::setCurrentVoiceChannelChangedCallback(boost::bind(&LLChicletPanel::onCurrentVoiceChannelChanged, this, _1)); + + mLeftScrollButton=getChild("chicklet_left_scroll_button"); + LLTransientFloaterMgr::getInstance()->addControlView(mLeftScrollButton); + mLeftScrollButton->setMouseDownCallback(boost::bind(&LLChicletPanel::onLeftScrollClick,this)); + mLeftScrollButton->setHeldDownCallback(boost::bind(&LLChicletPanel::onLeftScrollHeldDown,this)); + mLeftScrollButton->setEnabled(false); + + mRightScrollButton=getChild("chicklet_right_scroll_button"); + LLTransientFloaterMgr::getInstance()->addControlView(mRightScrollButton); + mRightScrollButton->setMouseDownCallback(boost::bind(&LLChicletPanel::onRightScrollClick,this)); + mRightScrollButton->setHeldDownCallback(boost::bind(&LLChicletPanel::onRightScrollHeldDown,this)); + mRightScrollButton->setEnabled(false); + + return true; +} + +void LLChicletPanel::onCurrentVoiceChannelChanged(const LLUUID& session_id) +{ + static LLUUID s_previous_active_voice_session_id; + + std::list chiclets = LLIMChiclet::sFindChicletsSignal(session_id); + + for(std::list::iterator it = chiclets.begin(); it != chiclets.end(); ++it) + { + LLIMChiclet* chiclet = dynamic_cast(*it); + if(chiclet) + { + if (gSavedSettings.getBOOL("OpenIMOnVoice")) + { + LLFloaterIMContainer::getInstance()->showConversation(session_id); + } + } + } + + s_previous_active_voice_session_id = session_id; +} + +bool LLChicletPanel::addChiclet(LLChiclet* chiclet, S32 index) +{ + if(mScrollArea->addChild(chiclet)) + { + // chiclets should be aligned to right edge of scroll panel + S32 left_shift = 0; + + if (!canScrollLeft()) + { + // init left shift for the first chiclet in the list... + if (mChicletList.empty()) + { + // ...start from the right border of the scroll area for the first added chiclet + left_shift = mScrollArea->getRect().getWidth(); + } + else + { + // ... start from the left border of the first chiclet minus padding + left_shift = getChiclet(0)->getRect().mLeft - getChicletPadding(); + } + + // take into account width of the being added chiclet + left_shift -= chiclet->getRequiredRect().getWidth(); + + // if we overflow the scroll area we do not need to shift chiclets + if (left_shift < 0) + { + left_shift = 0; + } + } + + mChicletList.insert(mChicletList.begin() + index, chiclet); + + // shift first chiclet to place it in correct position. + // rest ones will be placed in arrange() + if (!canScrollLeft()) + { + getChiclet(0)->translate(left_shift - getChiclet(0)->getRect().mLeft, 0); + } + + chiclet->setLeftButtonClickCallback(boost::bind(&LLChicletPanel::onChicletClick, this, _1, _2)); + chiclet->setChicletSizeChangedCallback(boost::bind(&LLChicletPanel::onChicletSizeChanged, this, _1, index)); + + arrange(); + LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::IM, chiclet); + + return true; + } + + return false; +} + +void LLChicletPanel::onChicletSizeChanged(LLChiclet* ctrl, const LLSD& param) +{ + arrange(); +} + +void LLChicletPanel::onChicletClick(LLUICtrl*ctrl,const LLSD¶m) +{ + if (mCommitSignal) + { + (*mCommitSignal)(ctrl,param); + } +} + +void LLChicletPanel::removeChiclet(chiclet_list_t::iterator it) +{ + LLChiclet* chiclet = *it; + mScrollArea->removeChild(chiclet); + mChicletList.erase(it); + + arrange(); + LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::IM, chiclet); + chiclet->die(); +} + +void LLChicletPanel::removeChiclet(S32 index) +{ + if(index >= 0 && index < getChicletCount()) + { + removeChiclet(mChicletList.begin() + index); + } +} + +S32 LLChicletPanel::getChicletIndex(const LLChiclet* chiclet) +{ + if(mChicletList.empty()) + return -1; + + S32 size = getChicletCount(); + for(int n = 0; n < size; ++n) + { + if(chiclet == mChicletList[n]) + return n; + } + + return -1; +} + +void LLChicletPanel::removeChiclet(LLChiclet*chiclet) +{ + chiclet_list_t::iterator it = mChicletList.begin(); + for( ; mChicletList.end() != it; ++it) + { + LLChiclet* temp = *it; + if(temp == chiclet) + { + removeChiclet(it); + return; + } + } +} + +void LLChicletPanel::removeChiclet(const LLUUID& im_session_id) +{ + chiclet_list_t::iterator it = mChicletList.begin(); + for( ; mChicletList.end() != it; ++it) + { + LLIMChiclet* chiclet = dynamic_cast(*it); + + if(chiclet->getSessionId() == im_session_id) + { + removeChiclet(it); + return; + } + } +} + +void LLChicletPanel::removeAll() +{ + S32 size = getChicletCount(); + for(S32 n = 0; n < size; ++n) + { + mScrollArea->removeChild(mChicletList[n]); + } + + mChicletList.erase(mChicletList.begin(), mChicletList.end()); + + showScrollButtonsIfNeeded(); +} + +void LLChicletPanel::scrollToChiclet(const LLChiclet* chiclet) +{ + const LLRect& rect = chiclet->getRect(); + + if (rect.mLeft < 0) + { + scroll(llabs(rect.mLeft)); + showScrollButtonsIfNeeded(); + } + else + { + S32 scrollWidth = mScrollArea->getRect().getWidth(); + + if (rect.mRight > scrollWidth) + { + scroll(-llabs(rect.mRight - scrollWidth)); + showScrollButtonsIfNeeded(); + } + } +} + +void LLChicletPanel::reshape(S32 width, S32 height, bool called_from_parent ) +{ + LLPanel::reshape(width,height,called_from_parent); + + //Needed once- to avoid error at first call of reshape() before postBuild() + if(!mLeftScrollButton||!mRightScrollButton) + return; + + LLRect scroll_button_rect = mLeftScrollButton->getRect(); + mLeftScrollButton->setRect(LLRect(0,scroll_button_rect.mTop,scroll_button_rect.getWidth(), + scroll_button_rect.mBottom)); + scroll_button_rect = mRightScrollButton->getRect(); + mRightScrollButton->setRect(LLRect(width - scroll_button_rect.getWidth(),scroll_button_rect.mTop, + width, scroll_button_rect.mBottom)); + + + bool need_show_scroll = needShowScroll(); + if(need_show_scroll) + { + mScrollArea->setRect(LLRect(scroll_button_rect.getWidth() + mScrollButtonHPad, + height, width - scroll_button_rect.getWidth() - mScrollButtonHPad, 0)); + } + else + { + mScrollArea->setRect(LLRect(0,height, width, 0)); + } + + mShowControls = width >= mMinWidth; + + mScrollArea->setVisible(mShowControls); + + trimChiclets(); + showScrollButtonsIfNeeded(); + +} + +S32 LLChicletPanel::notifyParent(const LLSD& info) +{ + if(info.has("notification")) + { + std::string str_notification = info["notification"]; + if(str_notification == "size_changes") + { + arrange(); + return 1; + } + } + return LLPanel::notifyParent(info); +} + +void LLChicletPanel::setChicletToggleState(const LLUUID& session_id, bool toggle) +{ + if(session_id.isNull()) + { + LL_WARNS() << "Null Session ID" << LL_ENDL; + } + + // toggle off all chiclets, except specified + S32 size = getChicletCount(); + for(int n = 0; n < size; ++n) + { + LLIMChiclet* chiclet = getChiclet(n); + if(chiclet && chiclet->getSessionId() != session_id) + { + chiclet->setToggleState(false); + } + } + + // toggle specified chiclet + LLIMChiclet* chiclet = findChiclet(session_id); + if(chiclet) + { + chiclet->setToggleState(toggle); + } +} + +void LLChicletPanel::arrange() +{ + if(mChicletList.empty()) + return; + + //initial arrange of chicklets positions + S32 chiclet_left = getChiclet(0)->getRect().mLeft; + S32 size = getChicletCount(); + for( int n = 0; n < size; ++n) + { + LLChiclet* chiclet = getChiclet(n); + + S32 chiclet_width = chiclet->getRequiredRect().getWidth(); + LLRect rect = chiclet->getRect(); + rect.set(chiclet_left, rect.mTop, chiclet_left + chiclet_width, rect.mBottom); + + chiclet->setRect(rect); + + chiclet_left += chiclet_width + getChicletPadding(); + } + + //reset size and pos on mScrollArea + LLRect rect = getRect(); + LLRect scroll_button_rect = mLeftScrollButton->getRect(); + + bool need_show_scroll = needShowScroll(); + if(need_show_scroll) + { + mScrollArea->setRect(LLRect(scroll_button_rect.getWidth() + mScrollButtonHPad, + rect.getHeight(), rect.getWidth() - scroll_button_rect.getWidth() - mScrollButtonHPad, 0)); + } + else + { + mScrollArea->setRect(LLRect(0,rect.getHeight(), rect.getWidth(), 0)); + } + + trimChiclets(); + showScrollButtonsIfNeeded(); +} + +void LLChicletPanel::trimChiclets() +{ + // trim right + if(!mChicletList.empty()) + { + S32 last_chiclet_right = (*mChicletList.rbegin())->getRect().mRight; + S32 first_chiclet_left = getChiclet(0)->getRect().mLeft; + S32 scroll_width = mScrollArea->getRect().getWidth(); + if(last_chiclet_right < scroll_width || first_chiclet_left > 0) + { + shiftChiclets(scroll_width - last_chiclet_right); + } + } +} + +bool LLChicletPanel::needShowScroll() +{ + if(mChicletList.empty()) + return false; + + S32 chicklet_width = (*mChicletList.rbegin())->getRect().mRight - (*mChicletList.begin())->getRect().mLeft; + + return chicklet_width>getRect().getWidth(); +} + + +void LLChicletPanel::showScrollButtonsIfNeeded() +{ + bool can_scroll_left = canScrollLeft(); + bool can_scroll_right = canScrollRight(); + + mLeftScrollButton->setEnabled(can_scroll_left); + mRightScrollButton->setEnabled(can_scroll_right); + + bool show_scroll_buttons = (can_scroll_left || can_scroll_right) && mShowControls; + + mLeftScrollButton->setVisible(show_scroll_buttons); + mRightScrollButton->setVisible(show_scroll_buttons); +} + +void LLChicletPanel::draw() +{ + child_list_const_iter_t it = getChildList()->begin(); + for( ; getChildList()->end() != it; ++it) + { + LLView* child = *it; + if(child == dynamic_cast(mScrollArea)) + { + LLLocalClipRect clip(mScrollArea->getRect()); + drawChild(mScrollArea); + } + else + { + drawChild(child); + } + } +} + +bool LLChicletPanel::canScrollRight() +{ + if(mChicletList.empty()) + return false; + + S32 scroll_width = mScrollArea->getRect().getWidth(); + S32 last_chiclet_right = (*mChicletList.rbegin())->getRect().mRight; + + if(last_chiclet_right > scroll_width) + return true; + + return false; +} + +bool LLChicletPanel::canScrollLeft() +{ + if(mChicletList.empty()) + return false; + + return getChiclet(0)->getRect().mLeft < 0; +} + +void LLChicletPanel::scroll(S32 offset) +{ + shiftChiclets(offset); +} + +void LLChicletPanel::shiftChiclets(S32 offset, S32 start_index /* = 0 */) +{ + if(start_index < 0 || start_index >= getChicletCount()) + { + return; + } + + chiclet_list_t::const_iterator it = mChicletList.begin() + start_index; + for(;mChicletList.end() != it; ++it) + { + LLChiclet* chiclet = *it; + chiclet->translate(offset,0); + } +} + +void LLChicletPanel::scrollLeft() +{ + if(canScrollLeft()) + { + S32 offset = getScrollingOffset(); + LLRect first_chiclet_rect = getChiclet(0)->getRect(); + + // shift chiclets in case first chiclet is partially visible + if(first_chiclet_rect.mLeft < 0 && first_chiclet_rect.mRight > 0) + { + offset = llabs(first_chiclet_rect.mLeft); + } + + scroll(offset); + + showScrollButtonsIfNeeded(); + } +} + +void LLChicletPanel::scrollRight() +{ + if(canScrollRight()) + { + S32 offset = - getScrollingOffset(); + + S32 last_chiclet_right = (*mChicletList.rbegin())->getRect().mRight; + S32 scroll_rect_width = mScrollArea->getRect().getWidth(); + // if after scrolling, the last chiclet will not be aligned to + // scroll area right side - align it. + if( last_chiclet_right + offset < scroll_rect_width ) + { + offset = scroll_rect_width - last_chiclet_right; + } + + scroll(offset); + + showScrollButtonsIfNeeded(); + } +} + +void LLChicletPanel::onLeftScrollClick() +{ + scrollLeft(); +} + +void LLChicletPanel::onRightScrollClick() +{ + scrollRight(); +} + +void LLChicletPanel::onLeftScrollHeldDown() +{ + S32 offset = mScrollingOffset; + mScrollingOffset = mScrollingOffset / mScrollRatio; + scrollLeft(); + mScrollingOffset = offset; +} + +void LLChicletPanel::onRightScrollHeldDown() +{ + S32 offset = mScrollingOffset; + mScrollingOffset = mScrollingOffset / mScrollRatio; + scrollRight(); + mScrollingOffset = offset; +} + +boost::signals2::connection LLChicletPanel::setChicletClickedCallback( + const commit_callback_t& cb) +{ + return setCommitCallback(cb); +} + +bool LLChicletPanel::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + if(clicks > 0) + { + scrollRight(); + } + else + { + scrollLeft(); + } + return true; +} + +bool LLChicletPanel::isAnyIMFloaterDoked() +{ + bool res = false; + for (chiclet_list_t::iterator it = mChicletList.begin(); it + != mChicletList.end(); it++) + { + LLFloaterIMSession* im_floater = LLFloaterReg::findTypedInstance( + "impanel", (*it)->getSessionId()); + if (im_floater != NULL && im_floater->getVisible() + && !im_floater->isMinimized() && im_floater->isDocked()) + { + res = true; + break; + } + } + + return res; +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +LLChicletNotificationCounterCtrl::Params::Params() + : max_displayed_count("max_displayed_count", 99) +{ +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +LLChicletAvatarIconCtrl::LLChicletAvatarIconCtrl(const Params& p) + : LLAvatarIconCtrl(p) +{ +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +LLChicletInvOfferIconCtrl::LLChicletInvOfferIconCtrl(const Params& p) +: LLChicletAvatarIconCtrl(p) + , mDefaultIcon(p.default_icon) +{ +} + +void LLChicletInvOfferIconCtrl::setValue(const LLSD& value ) +{ + if(value.asUUID().isNull()) + { + LLIconCtrl::setValue(mDefaultIcon); + } + else + { + LLChicletAvatarIconCtrl::setValue(value); + } +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +LLScriptChiclet::Params::Params() + : icon("icon") + , chiclet_button("chiclet_button") + , new_message_icon("new_message_icon") +{ +} + +LLScriptChiclet::LLScriptChiclet(const Params&p) + : LLIMChiclet(p) + , mChicletIconCtrl(NULL) +{ + LLButton::Params button_params = p.chiclet_button; + mChicletButton = LLUICtrlFactory::create(button_params); + addChild(mChicletButton); + + LLIconCtrl::Params new_msg_params = p.new_message_icon; + mNewMessagesIcon = LLUICtrlFactory::create(new_msg_params); + addChild(mNewMessagesIcon); + + LLIconCtrl::Params icon_params = p.icon; + mChicletIconCtrl = LLUICtrlFactory::create(icon_params); + addChild(mChicletIconCtrl); + + sendChildToFront(mNewMessagesIcon); +} + +void LLScriptChiclet::setSessionId(const LLUUID& session_id) +{ + setShowNewMessagesIcon( getSessionId() != session_id ); + + LLIMChiclet::setSessionId(session_id); + + setToolTip(LLScriptFloaterManager::getObjectName(session_id)); +} + +void LLScriptChiclet::onMouseDown() +{ + LLScriptFloaterManager::getInstance()->toggleScriptFloater(getSessionId()); +} + +void LLScriptChiclet::onMenuItemClicked(const LLSD& user_data) +{ + std::string action = user_data.asString(); + + if("end" == action) + { + LLScriptFloaterManager::instance().removeNotification(getSessionId()); + } + else if ("close all" == action) + { + LLIMWellWindow::getInstance()->closeAll(); + } +} + +void LLScriptChiclet::createPopupMenu() +{ + if(!canCreateMenu()) + return; + + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + registrar.add("ScriptChiclet.Action", boost::bind(&LLScriptChiclet::onMenuItemClicked, this, _2)); + + LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile + ("menu_script_chiclet.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + if (menu) + { + mPopupMenuHandle = menu->getHandle(); + } + +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +static const std::string INVENTORY_USER_OFFER ("UserGiveItem"); + +LLInvOfferChiclet::Params::Params() + : icon("icon") + , chiclet_button("chiclet_button") + , new_message_icon("new_message_icon") +{ +} + +LLInvOfferChiclet::LLInvOfferChiclet(const Params&p) + : LLIMChiclet(p) + , mChicletIconCtrl(NULL) +{ + LLButton::Params button_params = p.chiclet_button; + mChicletButton = LLUICtrlFactory::create(button_params); + addChild(mChicletButton); + + LLIconCtrl::Params new_msg_params = p.new_message_icon; + mNewMessagesIcon = LLUICtrlFactory::create(new_msg_params); + addChild(mNewMessagesIcon); + + LLChicletInvOfferIconCtrl::Params icon_params = p.icon; + mChicletIconCtrl = LLUICtrlFactory::create(icon_params); + addChild(mChicletIconCtrl); + + sendChildToFront(mNewMessagesIcon); +} + +void LLInvOfferChiclet::setSessionId(const LLUUID& session_id) +{ + setShowNewMessagesIcon( getSessionId() != session_id ); + + setToolTip(LLScriptFloaterManager::getObjectName(session_id)); + + LLIMChiclet::setSessionId(session_id); + LLNotificationPtr notification = LLNotifications::getInstance()->find(session_id); + + if ( notification && notification->getName() == INVENTORY_USER_OFFER ) + { + mChicletIconCtrl->setValue(notification->getPayload()["from_id"]); + } + else + { + mChicletIconCtrl->setValue(LLUUID::null); + } +} + +void LLInvOfferChiclet::onMouseDown() +{ + LLScriptFloaterManager::instance().toggleScriptFloater(getSessionId()); +} + +void LLInvOfferChiclet::onMenuItemClicked(const LLSD& user_data) +{ + std::string action = user_data.asString(); + + if("end" == action) + { + LLScriptFloaterManager::instance().removeNotification(getSessionId()); + } +} + +void LLInvOfferChiclet::createPopupMenu() +{ + if(!canCreateMenu()) + return; + + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + registrar.add("InvOfferChiclet.Action", boost::bind(&LLInvOfferChiclet::onMenuItemClicked, this, _2)); + + LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile + ("menu_inv_offer_chiclet.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + if (menu) + { + mPopupMenuHandle = menu->getHandle(); + } +} + +// EOF diff --git a/indra/newview/llchiclet.h b/indra/newview/llchiclet.h index 73a983c5ca..313fd58624 100644 --- a/indra/newview/llchiclet.h +++ b/indra/newview/llchiclet.h @@ -1,905 +1,905 @@ -/** - * @file llchiclet.h - * @brief LLChiclet class header file - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLCHICLET_H -#define LL_LLCHICLET_H - -#include "llavatariconctrl.h" -#include "llbutton.h" -#include "llnotifications.h" -#include "lltextbox.h" - -class LLMenuGL; -class LLFloaterIMSession; - -/** - * Class for displaying amount of messages/notifications(unread). - */ -class LLChicletNotificationCounterCtrl : public LLTextBox -{ -public: - - struct Params : public LLInitParam::Block - { - /** - * Contains maximum displayed count of unread messages. Default value is 9. - * - * If count is less than "max_unread_count" will be displayed as is. - * Otherwise 9+ will be shown (for default value). - */ - Optional max_displayed_count; - - Params(); - }; - - /** - * Sets number of notifications - */ - virtual void setCounter(S32 counter); - - /** - * Returns number of notifications - */ - virtual S32 getCounter() const { return mCounter; } - - /** - * Returns width, required to display amount of notifications in text form. - * Width is the only valid value. - */ - /*virtual*/ LLRect getRequiredRect(); - - /** - * Sets number of notifications using LLSD - */ - /*virtual*/ void setValue(const LLSD& value); - - /** - * Returns number of notifications wrapped in LLSD - */ - /*virtual*/ LLSD getValue() const; - -protected: - - LLChicletNotificationCounterCtrl(const Params& p); - friend class LLUICtrlFactory; - -private: - - S32 mCounter; - S32 mInitialWidth; - S32 mMaxDisplayedCount; -}; - -/** - * Class for displaying avatar's icon in P2P chiclet. - */ -class LLChicletAvatarIconCtrl : public LLAvatarIconCtrl -{ -public: - - struct Params : public LLInitParam::Block - { - Params() - { - changeDefault(draw_tooltip, false); - changeDefault(mouse_opaque, false); - changeDefault(default_icon_name, "Generic_Person"); - }; - }; - -protected: - - LLChicletAvatarIconCtrl(const Params& p); - friend class LLUICtrlFactory; -}; - -/** - * Class for displaying icon in inventory offer chiclet. - */ -class LLChicletInvOfferIconCtrl : public LLChicletAvatarIconCtrl -{ -public: - - struct Params : - public LLInitParam::Block - { - Optional default_icon; - - Params() - : default_icon("default_icon", "Generic_Object_Small") - { - changeDefault(avatar_id, LLUUID::null); - }; - }; - - /** - * Sets icon, if value is LLUUID::null - default icon will be set. - */ - virtual void setValue(const LLSD& value ); - -protected: - - LLChicletInvOfferIconCtrl(const Params& p); - friend class LLUICtrlFactory; - -private: - std::string mDefaultIcon; -}; - -/** - * Base class for all chiclets. - */ -class LLChiclet : public LLUICtrl -{ -public: - - struct Params : public LLInitParam::Block - { - Optional show_counter, - enable_counter; - - Params(); - }; - - virtual ~LLChiclet() {} - - /** - * Associates chat session id with chiclet. - */ - virtual void setSessionId(const LLUUID& session_id) { mSessionId = session_id; } - - /** - * Returns associated chat session. - */ - virtual const LLUUID& getSessionId() const { return mSessionId; } - - /** - * Sets show counter state. - */ - virtual void setShowCounter(bool show) { mShowCounter = show; } - - /** - * Connects chiclet clicked event with callback. - */ - /*virtual*/ boost::signals2::connection setLeftButtonClickCallback( - const commit_callback_t& cb); - - typedef boost::function - chiclet_size_changed_callback_t; - - /** - * Connects chiclets size changed event with callback. - */ - virtual boost::signals2::connection setChicletSizeChangedCallback( - const chiclet_size_changed_callback_t& cb); - - /** - * Sets IM Session id using LLSD - */ - /*virtual*/ LLSD getValue() const; - - /** - * Returns IM Session id using LLSD - */ - /*virtual*/ void setValue(const LLSD& value); - -protected: - - friend class LLUICtrlFactory; - LLChiclet(const Params& p); - - /** - * Notifies subscribers about click on chiclet. - */ - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); - - /** - * Notifies subscribers about chiclet size changed event. - */ - virtual void onChicletSizeChanged(); - -private: - - LLUUID mSessionId; - - bool mShowCounter; - - typedef boost::signals2::signal - chiclet_size_changed_signal_t; - - chiclet_size_changed_signal_t mChicletSizeChangedSignal; -}; - - -/** - * Base class for Instant Message chiclets. - * IMChiclet displays icon, number of unread messages(optional) - * and voice chat status(optional). - */ -class LLIMChiclet : public LLChiclet -{ -public: - enum EType { - TYPE_UNKNOWN, - TYPE_IM, - TYPE_GROUP, - TYPE_AD_HOC - }; - struct Params : public LLInitParam::Block - {}; - - - virtual ~LLIMChiclet(); - - /** - * It is used for default setting up of chicklet:click handler, etc. - */ - bool postBuild(); - - /** - * Sets IM session name. This name will be displayed in chiclet tooltip. - */ - virtual void setIMSessionName(const std::string& name) { setToolTip(name); } - - /** - * Sets id of person/group user is chatting with. - * Session id should be set before calling this - */ - virtual void setOtherParticipantId(const LLUUID& other_participant_id) { mOtherParticipantId = other_participant_id; } - - /** - * Enables/disables the counter control for a chiclet. - */ - virtual void enableCounterControl(bool enable); - - /** - * Sets required width for a chiclet according to visible controls. - */ - virtual void setRequiredWidth(); - - /** - * Shows/hides overlay icon concerning new unread messages. - */ - virtual void setShowNewMessagesIcon(bool show); - - /** - * Returns visibility of overlay icon concerning new unread messages. - */ - virtual bool getShowNewMessagesIcon(); - - /** - * The action taken on mouse down event. - * - * Made public so that it can be triggered from outside - * (more specifically, from the Active IM window). - */ - virtual void onMouseDown(); - - virtual void setToggleState(bool toggle); - - /** - * Displays popup menu. - */ - virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); - - void hidePopupMenu(); - -protected: - - LLIMChiclet(const LLIMChiclet::Params& p); - -protected: - - /** - * Creates chiclet popup menu. - */ - virtual void createPopupMenu() = 0; - - /** - * Enables/disables menus. - */ - virtual void updateMenuItems() {}; - - bool canCreateMenu(); - - LLHandle mPopupMenuHandle; - - bool mShowSpeaker; - bool mCounterEnabled; - /* initial width of chiclet, should not include counter or speaker width */ - S32 mDefaultWidth; - - LLIconCtrl* mNewMessagesIcon; - LLButton* mChicletButton; - - /** the id of another participant, either an avatar id or a group id*/ - LLUUID mOtherParticipantId; - - template - struct CollectChicletCombiner { - typedef Container result_type; - - template - Container operator()(InputIterator first, InputIterator last) const { - Container c = Container(); - for (InputIterator iter = first; iter != last; iter++) { - if (*iter != NULL) { - c.push_back(*iter); - } - } - return c; - } - }; - -public: - static boost::signals2::signal > > - sFindChicletsSignal; -}; - - -/** - * Chiclet for script floaters. - */ -class LLScriptChiclet : public LLIMChiclet -{ -public: - - struct Params : public LLInitParam::Block - { - Optional chiclet_button; - - Optional icon; - - Optional new_message_icon; - - Params(); - }; - - /*virtual*/ void setSessionId(const LLUUID& session_id); - - /** - * Toggle script floater - */ - /*virtual*/ void onMouseDown(); - -protected: - - LLScriptChiclet(const Params&); - friend class LLUICtrlFactory; - - /** - * Creates chiclet popup menu. - */ - virtual void createPopupMenu(); - - /** - * Processes clicks on chiclet popup menu. - */ - virtual void onMenuItemClicked(const LLSD& user_data); - -private: - - LLIconCtrl* mChicletIconCtrl; -}; - -/** - * Chiclet for inventory offer script floaters. - */ -class LLInvOfferChiclet: public LLIMChiclet -{ -public: - - struct Params : public LLInitParam::Block - { - Optional chiclet_button; - - Optional icon; - - Optional new_message_icon; - - Params(); - }; - - /*virtual*/ void setSessionId(const LLUUID& session_id); - - /** - * Toggle script floater - */ - /*virtual*/ void onMouseDown(); - -protected: - LLInvOfferChiclet(const Params&); - friend class LLUICtrlFactory; - - /** - * Creates chiclet popup menu. - */ - virtual void createPopupMenu(); - - /** - * Processes clicks on chiclet popup menu. - */ - virtual void onMenuItemClicked(const LLSD& user_data); - -private: - LLChicletInvOfferIconCtrl* mChicletIconCtrl; -}; - -/** - * Implements notification chiclet. Used to display total amount of unread messages - * across all IM sessions, total amount of system notifications. See EXT-3147 for details - */ -class LLSysWellChiclet : public LLChiclet -{ -public: - - struct Params : public LLInitParam::Block - { - Optional button; - - Optional unread_notifications; - - /** - * Contains maximum displayed count of unread messages. Default value is 9. - * - * If count is less than "max_unread_count" will be displayed as is. - * Otherwise 9+ will be shown (for default value). - */ - Optional max_displayed_count; - - Params(); - }; - - /*virtual*/ void setCounter(S32 counter); - - // *TODO: mantipov: seems getCounter is not necessary for LLNotificationChiclet - // but inherited interface requires it to implement. - // Probably it can be safe removed. - /*virtual*/S32 getCounter() { return mCounter; } - - boost::signals2::connection setClickCallback(const commit_callback_t& cb); - - /*virtual*/ ~LLSysWellChiclet(); - - void setToggleState(bool toggled); - - void setNewMessagesState(bool new_messages); - //this method should change a widget according to state of the SysWellWindow - virtual void updateWidget(bool is_window_empty); - -protected: - - LLSysWellChiclet(const Params& p); - friend class LLUICtrlFactory; - - /** - * Change Well 'Lit' state from 'Lit' to 'Unlit' and vice-versa. - * - * There is an assumption that it will be called 2*N times to do not change its start state. - * @see FlashToLitTimer - */ - void changeLitState(bool blink); - - /** - * Displays menu. - */ - virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); - - virtual void createMenu() = 0; - -protected: - class FlashToLitTimer; - LLButton* mButton; - S32 mCounter; - S32 mMaxDisplayedCount; - bool mIsNewMessagesState; - - LLFlashTimer* mFlashToLitTimer; - LLHandle mContextMenuHandle; -}; - -class LLNotificationChiclet : public LLSysWellChiclet -{ - LOG_CLASS(LLNotificationChiclet); - - friend class LLUICtrlFactory; -public: - struct Params : public LLInitParam::Block{}; - -protected: - class ChicletNotificationChannel : public LLNotificationChannel - { - public: - ChicletNotificationChannel(LLNotificationChiclet* chiclet) - : LLNotificationChannel(LLNotificationChannel::Params().filter(filterNotification).name(chiclet->getSessionId().asString())) - , mChiclet(chiclet) - { - // connect counter handlers to the signals - connectToChannel("Group Notifications"); - connectToChannel("Offer"); - connectToChannel("Notifications"); - } - virtual ~ChicletNotificationChannel() {} - - static bool filterNotification(LLNotificationPtr notify); - // connect counter updaters to the corresponding signals - /*virtual*/ void onAdd(LLNotificationPtr p) { mChiclet->setCounter(++mChiclet->mUreadSystemNotifications); } - /*virtual*/ void onLoad(LLNotificationPtr p) { mChiclet->setCounter(++mChiclet->mUreadSystemNotifications); } - /*virtual*/ void onDelete(LLNotificationPtr p) { mChiclet->setCounter(--mChiclet->mUreadSystemNotifications); } - - LLNotificationChiclet* const mChiclet; - }; - - boost::scoped_ptr mNotificationChannel; - - LLNotificationChiclet(const Params& p); - ~LLNotificationChiclet(); - - /** - * Processes clicks on chiclet menu. - */ - void onMenuItemClicked(const LLSD& user_data); - - /** - * Enables chiclet menu items. - */ - bool enableMenuItem(const LLSD& user_data); - - /** - * Creates menu. - */ - /*virtual*/ void createMenu(); - - /*virtual*/ void setCounter(S32 counter); - S32 mUreadSystemNotifications; -}; - -/** - * Storage class for all IM chiclets. Provides mechanism to display, - * scroll, create, remove chiclets. - */ -class LLChicletPanel : public LLPanel -{ -public: - - struct Params : public LLInitParam::Block - { - Optional chiclet_padding, - scrolling_offset, - scroll_button_hpad, - scroll_ratio; - - Optional min_width; - - Params(); - }; - - virtual ~LLChicletPanel(); - - /** - * Creates chiclet and adds it to chiclet list at specified index. - */ - template T* createChiclet(const LLUUID& session_id, S32 index); - - /** - * Creates chiclet and adds it to chiclet list at right. - */ - template T* createChiclet(const LLUUID& session_id); - - /** - * Returns pointer to chiclet of specified type at specified index. - */ - template T* getChiclet(S32 index); - - /** - * Returns pointer to LLChiclet at specified index. - */ - LLChiclet* getChiclet(S32 index) { return getChiclet(index); } - - /** - * Searches a chiclet using IM session id. - */ - template T* findChiclet(const LLUUID& im_session_id); - - /** - * Returns number of hosted chiclets. - */ - S32 getChicletCount() {return mChicletList.size();}; - - /** - * Returns index of chiclet in list. - */ - S32 getChicletIndex(const LLChiclet* chiclet); - - /** - * Removes chiclet by index. - */ - void removeChiclet(S32 index); - - /** - * Removes chiclet by pointer. - */ - void removeChiclet(LLChiclet* chiclet); - - /** - * Removes chiclet by IM session id. - */ - void removeChiclet(const LLUUID& im_session_id); - - /** - * Removes all chiclets. - */ - void removeAll(); - - /** - * Scrolls the panel to the specified chiclet - */ - void scrollToChiclet(const LLChiclet* chiclet); - - boost::signals2::connection setChicletClickedCallback( - const commit_callback_t& cb); - - /*virtual*/ bool postBuild(); - - /** - * Handler for the Voice Client's signal. Finds a corresponding chiclet and toggles its SpeakerControl - */ - void onCurrentVoiceChannelChanged(const LLUUID& session_id); - - /** - * Reshapes controls and rearranges chiclets if needed. - */ - /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true ); - - /*virtual*/ void draw(); - - S32 getMinWidth() const { return mMinWidth; } - - /*virtual*/ S32 notifyParent(const LLSD& info); - - /** - * Toggle chiclet by session id ON and toggle OFF all other chiclets. - */ - void setChicletToggleState(const LLUUID& session_id, bool toggle); - -protected: - LLChicletPanel(const Params&p); - friend class LLUICtrlFactory; - - /** - * Adds chiclet to list and rearranges all chiclets. - * They should be right aligned, most recent right. See EXT-1293 - * - * It calculates position of the first chiclet in the list. Other chiclets are placed in arrange(). - * - * @see arrange() - */ - bool addChiclet(LLChiclet*, S32 index); - - /** - * Arranges chiclets to have them in correct positions. - * - * Method bases on assumption that first chiclet has correct rect and starts from the its position. - * - * @see addChiclet() - */ - void arrange(); - - /** - * Returns true if chiclets can be scrolled right. - */ - bool canScrollRight(); - - /** - * Returns true if we need to show scroll buttons - */ - bool needShowScroll(); - - /** - * Returns true if chiclets can be scrolled left. - */ - bool canScrollLeft(); - - /** - * Shows or hides chiclet scroll buttons if chiclets can or can not be scrolled. - */ - void showScrollButtonsIfNeeded(); - - /** - * Shifts chiclets left or right. - */ - void shiftChiclets(S32 offset, S32 start_index = 0); - - /** - * Removes gaps between first chiclet and scroll area left side, - * last chiclet and scroll area right side. - */ - void trimChiclets(); - - /** - * Scrolls chiclets to right or left. - */ - void scroll(S32 offset); - - /** - * Verifies that chiclets can be scrolled left, then calls scroll() - */ - void scrollLeft(); - - /** - * Verifies that chiclets can be scrolled right, then calls scroll() - */ - void scrollRight(); - - /** - * Callback for left scroll button clicked - */ - void onLeftScrollClick(); - - /** - * Callback for right scroll button clicked - */ - void onRightScrollClick(); - - /** - * Callback for right scroll button held down event - */ - void onLeftScrollHeldDown(); - - /** - * Callback for left scroll button held down event - */ - void onRightScrollHeldDown(); - - /** - * Callback for mouse wheel scrolled, calls scrollRight() or scrollLeft() - */ - bool handleScrollWheel(S32 x, S32 y, S32 clicks); - - /** - * Notifies subscribers about click on chiclet. - * Do not place any code here, instead subscribe on event (see setChicletClickedCallback). - */ - void onChicletClick(LLUICtrl*ctrl,const LLSD¶m); - - /** - * Callback for chiclet size changed event, rearranges chiclets. - */ - void onChicletSizeChanged(LLChiclet* ctrl, const LLSD& param); - - void onMessageCountChanged(const LLSD& data); - - void objectChicletCallback(const LLSD& data); - - typedef std::vector chiclet_list_t; - - /** - * Removes chiclet from scroll area and chiclet list. - */ - void removeChiclet(chiclet_list_t::iterator it); - - S32 getChicletPadding() { return mChicletPadding; } - - S32 getScrollingOffset() { return mScrollingOffset; } - - bool isAnyIMFloaterDoked(); - -protected: - - chiclet_list_t mChicletList; - LLButton* mLeftScrollButton; - LLButton* mRightScrollButton; - LLPanel* mScrollArea; - - S32 mChicletPadding; - S32 mScrollingOffset; - S32 mScrollButtonHPad; - S32 mScrollRatio; - S32 mMinWidth; - bool mShowControls; - static const S32 s_scroll_ratio; -}; - -template -T* LLChicletPanel::createChiclet(const LLUUID& session_id, S32 index) -{ - typename T::Params params; - T* chiclet = LLUICtrlFactory::create(params); - if(!chiclet) - { - LL_WARNS() << "Could not create chiclet" << LL_ENDL; - return NULL; - } - if(!addChiclet(chiclet, index)) - { - delete chiclet; - LL_WARNS() << "Could not add chiclet to chiclet panel" << LL_ENDL; - return NULL; - } - - if (!isAnyIMFloaterDoked()) - { - scrollToChiclet(chiclet); - } - - chiclet->setSessionId(session_id); - - return chiclet; -} - -template -T* LLChicletPanel::createChiclet(const LLUUID& session_id) -{ - return createChiclet(session_id, mChicletList.size()); -} - -template -T* LLChicletPanel::findChiclet(const LLUUID& im_session_id) -{ - if(im_session_id.isNull()) - { - return NULL; - } - - chiclet_list_t::const_iterator it = mChicletList.begin(); - for( ; mChicletList.end() != it; ++it) - { - LLChiclet* chiclet = *it; - - llassert(chiclet); - if (!chiclet) continue; - if(chiclet->getSessionId() == im_session_id) - { - T* result = dynamic_cast(chiclet); - if(!result) - { - LL_WARNS() << "Found chiclet but of wrong type " << LL_ENDL; - continue; - } - return result; - } - } - return NULL; -} - -template T* LLChicletPanel::getChiclet(S32 index) -{ - if(index < 0 || index >= getChicletCount()) - { - return NULL; - } - - LLChiclet* chiclet = mChicletList[index]; - T*result = dynamic_cast(chiclet); - if(!result && chiclet) - { - LL_WARNS() << "Found chiclet but of wrong type " << LL_ENDL; - } - return result; -} - -#endif // LL_LLCHICLET_H +/** + * @file llchiclet.h + * @brief LLChiclet class header file + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLCHICLET_H +#define LL_LLCHICLET_H + +#include "llavatariconctrl.h" +#include "llbutton.h" +#include "llnotifications.h" +#include "lltextbox.h" + +class LLMenuGL; +class LLFloaterIMSession; + +/** + * Class for displaying amount of messages/notifications(unread). + */ +class LLChicletNotificationCounterCtrl : public LLTextBox +{ +public: + + struct Params : public LLInitParam::Block + { + /** + * Contains maximum displayed count of unread messages. Default value is 9. + * + * If count is less than "max_unread_count" will be displayed as is. + * Otherwise 9+ will be shown (for default value). + */ + Optional max_displayed_count; + + Params(); + }; + + /** + * Sets number of notifications + */ + virtual void setCounter(S32 counter); + + /** + * Returns number of notifications + */ + virtual S32 getCounter() const { return mCounter; } + + /** + * Returns width, required to display amount of notifications in text form. + * Width is the only valid value. + */ + /*virtual*/ LLRect getRequiredRect(); + + /** + * Sets number of notifications using LLSD + */ + /*virtual*/ void setValue(const LLSD& value); + + /** + * Returns number of notifications wrapped in LLSD + */ + /*virtual*/ LLSD getValue() const; + +protected: + + LLChicletNotificationCounterCtrl(const Params& p); + friend class LLUICtrlFactory; + +private: + + S32 mCounter; + S32 mInitialWidth; + S32 mMaxDisplayedCount; +}; + +/** + * Class for displaying avatar's icon in P2P chiclet. + */ +class LLChicletAvatarIconCtrl : public LLAvatarIconCtrl +{ +public: + + struct Params : public LLInitParam::Block + { + Params() + { + changeDefault(draw_tooltip, false); + changeDefault(mouse_opaque, false); + changeDefault(default_icon_name, "Generic_Person"); + }; + }; + +protected: + + LLChicletAvatarIconCtrl(const Params& p); + friend class LLUICtrlFactory; +}; + +/** + * Class for displaying icon in inventory offer chiclet. + */ +class LLChicletInvOfferIconCtrl : public LLChicletAvatarIconCtrl +{ +public: + + struct Params : + public LLInitParam::Block + { + Optional default_icon; + + Params() + : default_icon("default_icon", "Generic_Object_Small") + { + changeDefault(avatar_id, LLUUID::null); + }; + }; + + /** + * Sets icon, if value is LLUUID::null - default icon will be set. + */ + virtual void setValue(const LLSD& value ); + +protected: + + LLChicletInvOfferIconCtrl(const Params& p); + friend class LLUICtrlFactory; + +private: + std::string mDefaultIcon; +}; + +/** + * Base class for all chiclets. + */ +class LLChiclet : public LLUICtrl +{ +public: + + struct Params : public LLInitParam::Block + { + Optional show_counter, + enable_counter; + + Params(); + }; + + virtual ~LLChiclet() {} + + /** + * Associates chat session id with chiclet. + */ + virtual void setSessionId(const LLUUID& session_id) { mSessionId = session_id; } + + /** + * Returns associated chat session. + */ + virtual const LLUUID& getSessionId() const { return mSessionId; } + + /** + * Sets show counter state. + */ + virtual void setShowCounter(bool show) { mShowCounter = show; } + + /** + * Connects chiclet clicked event with callback. + */ + /*virtual*/ boost::signals2::connection setLeftButtonClickCallback( + const commit_callback_t& cb); + + typedef boost::function + chiclet_size_changed_callback_t; + + /** + * Connects chiclets size changed event with callback. + */ + virtual boost::signals2::connection setChicletSizeChangedCallback( + const chiclet_size_changed_callback_t& cb); + + /** + * Sets IM Session id using LLSD + */ + /*virtual*/ LLSD getValue() const; + + /** + * Returns IM Session id using LLSD + */ + /*virtual*/ void setValue(const LLSD& value); + +protected: + + friend class LLUICtrlFactory; + LLChiclet(const Params& p); + + /** + * Notifies subscribers about click on chiclet. + */ + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); + + /** + * Notifies subscribers about chiclet size changed event. + */ + virtual void onChicletSizeChanged(); + +private: + + LLUUID mSessionId; + + bool mShowCounter; + + typedef boost::signals2::signal + chiclet_size_changed_signal_t; + + chiclet_size_changed_signal_t mChicletSizeChangedSignal; +}; + + +/** + * Base class for Instant Message chiclets. + * IMChiclet displays icon, number of unread messages(optional) + * and voice chat status(optional). + */ +class LLIMChiclet : public LLChiclet +{ +public: + enum EType { + TYPE_UNKNOWN, + TYPE_IM, + TYPE_GROUP, + TYPE_AD_HOC + }; + struct Params : public LLInitParam::Block + {}; + + + virtual ~LLIMChiclet(); + + /** + * It is used for default setting up of chicklet:click handler, etc. + */ + bool postBuild(); + + /** + * Sets IM session name. This name will be displayed in chiclet tooltip. + */ + virtual void setIMSessionName(const std::string& name) { setToolTip(name); } + + /** + * Sets id of person/group user is chatting with. + * Session id should be set before calling this + */ + virtual void setOtherParticipantId(const LLUUID& other_participant_id) { mOtherParticipantId = other_participant_id; } + + /** + * Enables/disables the counter control for a chiclet. + */ + virtual void enableCounterControl(bool enable); + + /** + * Sets required width for a chiclet according to visible controls. + */ + virtual void setRequiredWidth(); + + /** + * Shows/hides overlay icon concerning new unread messages. + */ + virtual void setShowNewMessagesIcon(bool show); + + /** + * Returns visibility of overlay icon concerning new unread messages. + */ + virtual bool getShowNewMessagesIcon(); + + /** + * The action taken on mouse down event. + * + * Made public so that it can be triggered from outside + * (more specifically, from the Active IM window). + */ + virtual void onMouseDown(); + + virtual void setToggleState(bool toggle); + + /** + * Displays popup menu. + */ + virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); + + void hidePopupMenu(); + +protected: + + LLIMChiclet(const LLIMChiclet::Params& p); + +protected: + + /** + * Creates chiclet popup menu. + */ + virtual void createPopupMenu() = 0; + + /** + * Enables/disables menus. + */ + virtual void updateMenuItems() {}; + + bool canCreateMenu(); + + LLHandle mPopupMenuHandle; + + bool mShowSpeaker; + bool mCounterEnabled; + /* initial width of chiclet, should not include counter or speaker width */ + S32 mDefaultWidth; + + LLIconCtrl* mNewMessagesIcon; + LLButton* mChicletButton; + + /** the id of another participant, either an avatar id or a group id*/ + LLUUID mOtherParticipantId; + + template + struct CollectChicletCombiner { + typedef Container result_type; + + template + Container operator()(InputIterator first, InputIterator last) const { + Container c = Container(); + for (InputIterator iter = first; iter != last; iter++) { + if (*iter != NULL) { + c.push_back(*iter); + } + } + return c; + } + }; + +public: + static boost::signals2::signal > > + sFindChicletsSignal; +}; + + +/** + * Chiclet for script floaters. + */ +class LLScriptChiclet : public LLIMChiclet +{ +public: + + struct Params : public LLInitParam::Block + { + Optional chiclet_button; + + Optional icon; + + Optional new_message_icon; + + Params(); + }; + + /*virtual*/ void setSessionId(const LLUUID& session_id); + + /** + * Toggle script floater + */ + /*virtual*/ void onMouseDown(); + +protected: + + LLScriptChiclet(const Params&); + friend class LLUICtrlFactory; + + /** + * Creates chiclet popup menu. + */ + virtual void createPopupMenu(); + + /** + * Processes clicks on chiclet popup menu. + */ + virtual void onMenuItemClicked(const LLSD& user_data); + +private: + + LLIconCtrl* mChicletIconCtrl; +}; + +/** + * Chiclet for inventory offer script floaters. + */ +class LLInvOfferChiclet: public LLIMChiclet +{ +public: + + struct Params : public LLInitParam::Block + { + Optional chiclet_button; + + Optional icon; + + Optional new_message_icon; + + Params(); + }; + + /*virtual*/ void setSessionId(const LLUUID& session_id); + + /** + * Toggle script floater + */ + /*virtual*/ void onMouseDown(); + +protected: + LLInvOfferChiclet(const Params&); + friend class LLUICtrlFactory; + + /** + * Creates chiclet popup menu. + */ + virtual void createPopupMenu(); + + /** + * Processes clicks on chiclet popup menu. + */ + virtual void onMenuItemClicked(const LLSD& user_data); + +private: + LLChicletInvOfferIconCtrl* mChicletIconCtrl; +}; + +/** + * Implements notification chiclet. Used to display total amount of unread messages + * across all IM sessions, total amount of system notifications. See EXT-3147 for details + */ +class LLSysWellChiclet : public LLChiclet +{ +public: + + struct Params : public LLInitParam::Block + { + Optional button; + + Optional unread_notifications; + + /** + * Contains maximum displayed count of unread messages. Default value is 9. + * + * If count is less than "max_unread_count" will be displayed as is. + * Otherwise 9+ will be shown (for default value). + */ + Optional max_displayed_count; + + Params(); + }; + + /*virtual*/ void setCounter(S32 counter); + + // *TODO: mantipov: seems getCounter is not necessary for LLNotificationChiclet + // but inherited interface requires it to implement. + // Probably it can be safe removed. + /*virtual*/S32 getCounter() { return mCounter; } + + boost::signals2::connection setClickCallback(const commit_callback_t& cb); + + /*virtual*/ ~LLSysWellChiclet(); + + void setToggleState(bool toggled); + + void setNewMessagesState(bool new_messages); + //this method should change a widget according to state of the SysWellWindow + virtual void updateWidget(bool is_window_empty); + +protected: + + LLSysWellChiclet(const Params& p); + friend class LLUICtrlFactory; + + /** + * Change Well 'Lit' state from 'Lit' to 'Unlit' and vice-versa. + * + * There is an assumption that it will be called 2*N times to do not change its start state. + * @see FlashToLitTimer + */ + void changeLitState(bool blink); + + /** + * Displays menu. + */ + virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); + + virtual void createMenu() = 0; + +protected: + class FlashToLitTimer; + LLButton* mButton; + S32 mCounter; + S32 mMaxDisplayedCount; + bool mIsNewMessagesState; + + LLFlashTimer* mFlashToLitTimer; + LLHandle mContextMenuHandle; +}; + +class LLNotificationChiclet : public LLSysWellChiclet +{ + LOG_CLASS(LLNotificationChiclet); + + friend class LLUICtrlFactory; +public: + struct Params : public LLInitParam::Block{}; + +protected: + class ChicletNotificationChannel : public LLNotificationChannel + { + public: + ChicletNotificationChannel(LLNotificationChiclet* chiclet) + : LLNotificationChannel(LLNotificationChannel::Params().filter(filterNotification).name(chiclet->getSessionId().asString())) + , mChiclet(chiclet) + { + // connect counter handlers to the signals + connectToChannel("Group Notifications"); + connectToChannel("Offer"); + connectToChannel("Notifications"); + } + virtual ~ChicletNotificationChannel() {} + + static bool filterNotification(LLNotificationPtr notify); + // connect counter updaters to the corresponding signals + /*virtual*/ void onAdd(LLNotificationPtr p) { mChiclet->setCounter(++mChiclet->mUreadSystemNotifications); } + /*virtual*/ void onLoad(LLNotificationPtr p) { mChiclet->setCounter(++mChiclet->mUreadSystemNotifications); } + /*virtual*/ void onDelete(LLNotificationPtr p) { mChiclet->setCounter(--mChiclet->mUreadSystemNotifications); } + + LLNotificationChiclet* const mChiclet; + }; + + boost::scoped_ptr mNotificationChannel; + + LLNotificationChiclet(const Params& p); + ~LLNotificationChiclet(); + + /** + * Processes clicks on chiclet menu. + */ + void onMenuItemClicked(const LLSD& user_data); + + /** + * Enables chiclet menu items. + */ + bool enableMenuItem(const LLSD& user_data); + + /** + * Creates menu. + */ + /*virtual*/ void createMenu(); + + /*virtual*/ void setCounter(S32 counter); + S32 mUreadSystemNotifications; +}; + +/** + * Storage class for all IM chiclets. Provides mechanism to display, + * scroll, create, remove chiclets. + */ +class LLChicletPanel : public LLPanel +{ +public: + + struct Params : public LLInitParam::Block + { + Optional chiclet_padding, + scrolling_offset, + scroll_button_hpad, + scroll_ratio; + + Optional min_width; + + Params(); + }; + + virtual ~LLChicletPanel(); + + /** + * Creates chiclet and adds it to chiclet list at specified index. + */ + template T* createChiclet(const LLUUID& session_id, S32 index); + + /** + * Creates chiclet and adds it to chiclet list at right. + */ + template T* createChiclet(const LLUUID& session_id); + + /** + * Returns pointer to chiclet of specified type at specified index. + */ + template T* getChiclet(S32 index); + + /** + * Returns pointer to LLChiclet at specified index. + */ + LLChiclet* getChiclet(S32 index) { return getChiclet(index); } + + /** + * Searches a chiclet using IM session id. + */ + template T* findChiclet(const LLUUID& im_session_id); + + /** + * Returns number of hosted chiclets. + */ + S32 getChicletCount() {return mChicletList.size();}; + + /** + * Returns index of chiclet in list. + */ + S32 getChicletIndex(const LLChiclet* chiclet); + + /** + * Removes chiclet by index. + */ + void removeChiclet(S32 index); + + /** + * Removes chiclet by pointer. + */ + void removeChiclet(LLChiclet* chiclet); + + /** + * Removes chiclet by IM session id. + */ + void removeChiclet(const LLUUID& im_session_id); + + /** + * Removes all chiclets. + */ + void removeAll(); + + /** + * Scrolls the panel to the specified chiclet + */ + void scrollToChiclet(const LLChiclet* chiclet); + + boost::signals2::connection setChicletClickedCallback( + const commit_callback_t& cb); + + /*virtual*/ bool postBuild(); + + /** + * Handler for the Voice Client's signal. Finds a corresponding chiclet and toggles its SpeakerControl + */ + void onCurrentVoiceChannelChanged(const LLUUID& session_id); + + /** + * Reshapes controls and rearranges chiclets if needed. + */ + /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true ); + + /*virtual*/ void draw(); + + S32 getMinWidth() const { return mMinWidth; } + + /*virtual*/ S32 notifyParent(const LLSD& info); + + /** + * Toggle chiclet by session id ON and toggle OFF all other chiclets. + */ + void setChicletToggleState(const LLUUID& session_id, bool toggle); + +protected: + LLChicletPanel(const Params&p); + friend class LLUICtrlFactory; + + /** + * Adds chiclet to list and rearranges all chiclets. + * They should be right aligned, most recent right. See EXT-1293 + * + * It calculates position of the first chiclet in the list. Other chiclets are placed in arrange(). + * + * @see arrange() + */ + bool addChiclet(LLChiclet*, S32 index); + + /** + * Arranges chiclets to have them in correct positions. + * + * Method bases on assumption that first chiclet has correct rect and starts from the its position. + * + * @see addChiclet() + */ + void arrange(); + + /** + * Returns true if chiclets can be scrolled right. + */ + bool canScrollRight(); + + /** + * Returns true if we need to show scroll buttons + */ + bool needShowScroll(); + + /** + * Returns true if chiclets can be scrolled left. + */ + bool canScrollLeft(); + + /** + * Shows or hides chiclet scroll buttons if chiclets can or can not be scrolled. + */ + void showScrollButtonsIfNeeded(); + + /** + * Shifts chiclets left or right. + */ + void shiftChiclets(S32 offset, S32 start_index = 0); + + /** + * Removes gaps between first chiclet and scroll area left side, + * last chiclet and scroll area right side. + */ + void trimChiclets(); + + /** + * Scrolls chiclets to right or left. + */ + void scroll(S32 offset); + + /** + * Verifies that chiclets can be scrolled left, then calls scroll() + */ + void scrollLeft(); + + /** + * Verifies that chiclets can be scrolled right, then calls scroll() + */ + void scrollRight(); + + /** + * Callback for left scroll button clicked + */ + void onLeftScrollClick(); + + /** + * Callback for right scroll button clicked + */ + void onRightScrollClick(); + + /** + * Callback for right scroll button held down event + */ + void onLeftScrollHeldDown(); + + /** + * Callback for left scroll button held down event + */ + void onRightScrollHeldDown(); + + /** + * Callback for mouse wheel scrolled, calls scrollRight() or scrollLeft() + */ + bool handleScrollWheel(S32 x, S32 y, S32 clicks); + + /** + * Notifies subscribers about click on chiclet. + * Do not place any code here, instead subscribe on event (see setChicletClickedCallback). + */ + void onChicletClick(LLUICtrl*ctrl,const LLSD¶m); + + /** + * Callback for chiclet size changed event, rearranges chiclets. + */ + void onChicletSizeChanged(LLChiclet* ctrl, const LLSD& param); + + void onMessageCountChanged(const LLSD& data); + + void objectChicletCallback(const LLSD& data); + + typedef std::vector chiclet_list_t; + + /** + * Removes chiclet from scroll area and chiclet list. + */ + void removeChiclet(chiclet_list_t::iterator it); + + S32 getChicletPadding() { return mChicletPadding; } + + S32 getScrollingOffset() { return mScrollingOffset; } + + bool isAnyIMFloaterDoked(); + +protected: + + chiclet_list_t mChicletList; + LLButton* mLeftScrollButton; + LLButton* mRightScrollButton; + LLPanel* mScrollArea; + + S32 mChicletPadding; + S32 mScrollingOffset; + S32 mScrollButtonHPad; + S32 mScrollRatio; + S32 mMinWidth; + bool mShowControls; + static const S32 s_scroll_ratio; +}; + +template +T* LLChicletPanel::createChiclet(const LLUUID& session_id, S32 index) +{ + typename T::Params params; + T* chiclet = LLUICtrlFactory::create(params); + if(!chiclet) + { + LL_WARNS() << "Could not create chiclet" << LL_ENDL; + return NULL; + } + if(!addChiclet(chiclet, index)) + { + delete chiclet; + LL_WARNS() << "Could not add chiclet to chiclet panel" << LL_ENDL; + return NULL; + } + + if (!isAnyIMFloaterDoked()) + { + scrollToChiclet(chiclet); + } + + chiclet->setSessionId(session_id); + + return chiclet; +} + +template +T* LLChicletPanel::createChiclet(const LLUUID& session_id) +{ + return createChiclet(session_id, mChicletList.size()); +} + +template +T* LLChicletPanel::findChiclet(const LLUUID& im_session_id) +{ + if(im_session_id.isNull()) + { + return NULL; + } + + chiclet_list_t::const_iterator it = mChicletList.begin(); + for( ; mChicletList.end() != it; ++it) + { + LLChiclet* chiclet = *it; + + llassert(chiclet); + if (!chiclet) continue; + if(chiclet->getSessionId() == im_session_id) + { + T* result = dynamic_cast(chiclet); + if(!result) + { + LL_WARNS() << "Found chiclet but of wrong type " << LL_ENDL; + continue; + } + return result; + } + } + return NULL; +} + +template T* LLChicletPanel::getChiclet(S32 index) +{ + if(index < 0 || index >= getChicletCount()) + { + return NULL; + } + + LLChiclet* chiclet = mChicletList[index]; + T*result = dynamic_cast(chiclet); + if(!result && chiclet) + { + LL_WARNS() << "Found chiclet but of wrong type " << LL_ENDL; + } + return result; +} + +#endif // LL_LLCHICLET_H diff --git a/indra/newview/llchicletbar.cpp b/indra/newview/llchicletbar.cpp index c09439547f..bef96d0f11 100644 --- a/indra/newview/llchicletbar.cpp +++ b/indra/newview/llchicletbar.cpp @@ -1,237 +1,237 @@ -/** - * @file llchicletbar.cpp - * @brief LLChicletBar class implementation - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" // must be first include -#include "llchicletbar.h" - -#include "llchiclet.h" -#include "lllayoutstack.h" -#include "llpaneltopinfobar.h" -#include "llsyswellwindow.h" -#include "llfloaternotificationstabbed.h" - -namespace -{ - const std::string& PANEL_CHICLET_NAME = "chiclet_list_panel"; -} - -LLChicletBar::LLChicletBar() -: mChicletPanel(NULL), - mToolbarStack(NULL) -{ - buildFromFile("panel_chiclet_bar.xml"); -} - -bool LLChicletBar::postBuild() -{ - mToolbarStack = getChild("toolbar_stack"); - mChicletPanel = getChild("chiclet_list"); - - showWellButton("notification_well", !LLFloaterNotificationsTabbed::getInstance()->isWindowEmpty()); - - LLPanelTopInfoBar::instance().setResizeCallback(boost::bind(&LLChicletBar::fitWithTopInfoBar, this)); - LLPanelTopInfoBar::instance().setVisibleCallback(boost::bind(&LLChicletBar::fitWithTopInfoBar, this)); - - return true; -} - -void LLChicletBar::showWellButton(const std::string& well_name, bool visible) -{ - LLView * panel = findChild(well_name + "_panel"); - if (!panel) return; - - panel->setVisible(visible); -} - -void LLChicletBar::log(LLView* panel, const std::string& descr) -{ - if (NULL == panel) return; - LLView* layout = panel->getParent(); - LL_DEBUGS("ChicletBarRects") << descr << ": " - << "panel: " << panel->getName() - << ", rect: " << panel->getRect() - << " layout: " << layout->getName() - << ", rect: " << layout->getRect() - << LL_ENDL; -} - -void LLChicletBar::reshape(S32 width, S32 height, bool called_from_parent) -{ - static S32 debug_calling_number = 0; - LL_DEBUGS() << "**************************************** " << ++debug_calling_number << LL_ENDL; - - S32 current_width = getRect().getWidth(); - S32 delta_width = width - current_width; - LL_DEBUGS() << "Reshaping: " - << ", width: " << width - << ", cur width: " << current_width - << ", delta_width: " << delta_width - << ", called_from_parent: " << called_from_parent - << LL_ENDL; - - if (mChicletPanel) log(mChicletPanel, "before"); - - // Difference between chiclet bar width required to fit its children and the actual width. (see EXT-991) - // Positive value means that chiclet bar is not wide enough. - // Negative value means that there is free space. - static S32 extra_shrink_width = 0; - bool should_be_reshaped = true; - - if (mChicletPanel && mToolbarStack) - { - // Firstly, update layout stack to ensure we deal with correct panel sizes. - { - // Force the updating of layout to reset panels collapse factor. - mToolbarStack->updateLayout(); - } - - // chiclet bar is narrowed - if (delta_width < 0) - { - if (extra_shrink_width > 0) // not enough space - { - extra_shrink_width += llabs(delta_width); - should_be_reshaped = false; - } - else - { - extra_shrink_width = processWidthDecreased(delta_width); - - // increase new width to extra_shrink_width value to not reshape less than chiclet bar minimum - width += extra_shrink_width; - } - } - // chiclet bar is widened - else - { - if (extra_shrink_width > delta_width) - { - // Still not enough space. - // Only subtract the delta from the required delta and don't reshape. - extra_shrink_width -= delta_width; - should_be_reshaped = false; - } - else if (extra_shrink_width > 0) - { - // If we have some extra shrink width let's reduce delta_width & width - delta_width -= extra_shrink_width; - width -= extra_shrink_width; - extra_shrink_width = 0; - } - } - } - - if (should_be_reshaped) - { - LL_DEBUGS() << "Reshape all children with width: " << width << LL_ENDL; - LLPanel::reshape(width, height, called_from_parent); - } - - if (mChicletPanel) log(mChicletPanel, "after"); -} - -S32 LLChicletBar::processWidthDecreased(S32 delta_width) -{ - bool still_should_be_processed = true; - - const S32 chiclet_panel_shrink_headroom = getChicletPanelShrinkHeadroom(); - - // Decreasing width of chiclet panel. - if (chiclet_panel_shrink_headroom > 0) - { - // we have some space to decrease chiclet panel - S32 shrink_by = llmin(-delta_width, chiclet_panel_shrink_headroom); - - LL_DEBUGS() << "delta_width: " << delta_width - << ", panel_delta_min: " << chiclet_panel_shrink_headroom - << ", shrink_by: " << shrink_by - << LL_ENDL; - - // is chiclet panel wide enough to process resizing? - delta_width += chiclet_panel_shrink_headroom; - - still_should_be_processed = delta_width < 0; - - LL_DEBUGS() << "Shrinking chiclet panel by " << shrink_by << " px" << LL_ENDL; - mChicletPanel->getParent()->reshape(mChicletPanel->getParent()->getRect().getWidth() - shrink_by, mChicletPanel->getParent()->getRect().getHeight()); - log(mChicletPanel, "after processing panel decreasing via chiclet panel"); - - LL_DEBUGS() << "RS_CHICLET_PANEL" - << ", delta_width: " << delta_width - << LL_ENDL; - } - - S32 extra_shrink_width = 0; - - if (still_should_be_processed) - { - extra_shrink_width = -delta_width; - LL_WARNS() << "There is no enough width to reshape all children: " - << extra_shrink_width << LL_ENDL; - } - - return extra_shrink_width; -} - -S32 LLChicletBar::getChicletPanelShrinkHeadroom() const -{ - static const S32 min_width = mChicletPanel->getMinWidth(); - const S32 cur_width = mChicletPanel->getParent()->getRect().getWidth(); - - S32 shrink_headroom = cur_width - min_width; - llassert(shrink_headroom >= 0); // the panel cannot get narrower than the minimum - return shrink_headroom; -} - -void LLChicletBar::fitWithTopInfoBar() -{ - LLPanelTopInfoBar& top_info_bar = LLPanelTopInfoBar::instance(); - - LLRect rect = getRect(); - S32 width = rect.getWidth(); - - if (top_info_bar.getVisible()) - { - S32 delta = top_info_bar.calcScreenRect().mRight - calcScreenRect().mLeft; - if (delta < 0 && rect.mLeft < llabs(delta)) - delta = -rect.mLeft; - rect.setLeftTopAndSize(rect.mLeft + delta, rect.mTop, rect.getWidth(), rect.getHeight()); - width = rect.getWidth() - delta; - } - else - { - LLView* parent = getParent(); - if (parent) - { - LLRect parent_rect = parent->getRect(); - rect.setLeftTopAndSize(0, rect.mTop, rect.getWidth(), rect.getHeight()); - width = parent_rect.getWidth(); - } - } - - setRect(rect); - LLPanel::reshape(width, rect.getHeight(), false); -} +/** + * @file llchicletbar.cpp + * @brief LLChicletBar class implementation + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" // must be first include +#include "llchicletbar.h" + +#include "llchiclet.h" +#include "lllayoutstack.h" +#include "llpaneltopinfobar.h" +#include "llsyswellwindow.h" +#include "llfloaternotificationstabbed.h" + +namespace +{ + const std::string& PANEL_CHICLET_NAME = "chiclet_list_panel"; +} + +LLChicletBar::LLChicletBar() +: mChicletPanel(NULL), + mToolbarStack(NULL) +{ + buildFromFile("panel_chiclet_bar.xml"); +} + +bool LLChicletBar::postBuild() +{ + mToolbarStack = getChild("toolbar_stack"); + mChicletPanel = getChild("chiclet_list"); + + showWellButton("notification_well", !LLFloaterNotificationsTabbed::getInstance()->isWindowEmpty()); + + LLPanelTopInfoBar::instance().setResizeCallback(boost::bind(&LLChicletBar::fitWithTopInfoBar, this)); + LLPanelTopInfoBar::instance().setVisibleCallback(boost::bind(&LLChicletBar::fitWithTopInfoBar, this)); + + return true; +} + +void LLChicletBar::showWellButton(const std::string& well_name, bool visible) +{ + LLView * panel = findChild(well_name + "_panel"); + if (!panel) return; + + panel->setVisible(visible); +} + +void LLChicletBar::log(LLView* panel, const std::string& descr) +{ + if (NULL == panel) return; + LLView* layout = panel->getParent(); + LL_DEBUGS("ChicletBarRects") << descr << ": " + << "panel: " << panel->getName() + << ", rect: " << panel->getRect() + << " layout: " << layout->getName() + << ", rect: " << layout->getRect() + << LL_ENDL; +} + +void LLChicletBar::reshape(S32 width, S32 height, bool called_from_parent) +{ + static S32 debug_calling_number = 0; + LL_DEBUGS() << "**************************************** " << ++debug_calling_number << LL_ENDL; + + S32 current_width = getRect().getWidth(); + S32 delta_width = width - current_width; + LL_DEBUGS() << "Reshaping: " + << ", width: " << width + << ", cur width: " << current_width + << ", delta_width: " << delta_width + << ", called_from_parent: " << called_from_parent + << LL_ENDL; + + if (mChicletPanel) log(mChicletPanel, "before"); + + // Difference between chiclet bar width required to fit its children and the actual width. (see EXT-991) + // Positive value means that chiclet bar is not wide enough. + // Negative value means that there is free space. + static S32 extra_shrink_width = 0; + bool should_be_reshaped = true; + + if (mChicletPanel && mToolbarStack) + { + // Firstly, update layout stack to ensure we deal with correct panel sizes. + { + // Force the updating of layout to reset panels collapse factor. + mToolbarStack->updateLayout(); + } + + // chiclet bar is narrowed + if (delta_width < 0) + { + if (extra_shrink_width > 0) // not enough space + { + extra_shrink_width += llabs(delta_width); + should_be_reshaped = false; + } + else + { + extra_shrink_width = processWidthDecreased(delta_width); + + // increase new width to extra_shrink_width value to not reshape less than chiclet bar minimum + width += extra_shrink_width; + } + } + // chiclet bar is widened + else + { + if (extra_shrink_width > delta_width) + { + // Still not enough space. + // Only subtract the delta from the required delta and don't reshape. + extra_shrink_width -= delta_width; + should_be_reshaped = false; + } + else if (extra_shrink_width > 0) + { + // If we have some extra shrink width let's reduce delta_width & width + delta_width -= extra_shrink_width; + width -= extra_shrink_width; + extra_shrink_width = 0; + } + } + } + + if (should_be_reshaped) + { + LL_DEBUGS() << "Reshape all children with width: " << width << LL_ENDL; + LLPanel::reshape(width, height, called_from_parent); + } + + if (mChicletPanel) log(mChicletPanel, "after"); +} + +S32 LLChicletBar::processWidthDecreased(S32 delta_width) +{ + bool still_should_be_processed = true; + + const S32 chiclet_panel_shrink_headroom = getChicletPanelShrinkHeadroom(); + + // Decreasing width of chiclet panel. + if (chiclet_panel_shrink_headroom > 0) + { + // we have some space to decrease chiclet panel + S32 shrink_by = llmin(-delta_width, chiclet_panel_shrink_headroom); + + LL_DEBUGS() << "delta_width: " << delta_width + << ", panel_delta_min: " << chiclet_panel_shrink_headroom + << ", shrink_by: " << shrink_by + << LL_ENDL; + + // is chiclet panel wide enough to process resizing? + delta_width += chiclet_panel_shrink_headroom; + + still_should_be_processed = delta_width < 0; + + LL_DEBUGS() << "Shrinking chiclet panel by " << shrink_by << " px" << LL_ENDL; + mChicletPanel->getParent()->reshape(mChicletPanel->getParent()->getRect().getWidth() - shrink_by, mChicletPanel->getParent()->getRect().getHeight()); + log(mChicletPanel, "after processing panel decreasing via chiclet panel"); + + LL_DEBUGS() << "RS_CHICLET_PANEL" + << ", delta_width: " << delta_width + << LL_ENDL; + } + + S32 extra_shrink_width = 0; + + if (still_should_be_processed) + { + extra_shrink_width = -delta_width; + LL_WARNS() << "There is no enough width to reshape all children: " + << extra_shrink_width << LL_ENDL; + } + + return extra_shrink_width; +} + +S32 LLChicletBar::getChicletPanelShrinkHeadroom() const +{ + static const S32 min_width = mChicletPanel->getMinWidth(); + const S32 cur_width = mChicletPanel->getParent()->getRect().getWidth(); + + S32 shrink_headroom = cur_width - min_width; + llassert(shrink_headroom >= 0); // the panel cannot get narrower than the minimum + return shrink_headroom; +} + +void LLChicletBar::fitWithTopInfoBar() +{ + LLPanelTopInfoBar& top_info_bar = LLPanelTopInfoBar::instance(); + + LLRect rect = getRect(); + S32 width = rect.getWidth(); + + if (top_info_bar.getVisible()) + { + S32 delta = top_info_bar.calcScreenRect().mRight - calcScreenRect().mLeft; + if (delta < 0 && rect.mLeft < llabs(delta)) + delta = -rect.mLeft; + rect.setLeftTopAndSize(rect.mLeft + delta, rect.mTop, rect.getWidth(), rect.getHeight()); + width = rect.getWidth() - delta; + } + else + { + LLView* parent = getParent(); + if (parent) + { + LLRect parent_rect = parent->getRect(); + rect.setLeftTopAndSize(0, rect.mTop, rect.getWidth(), rect.getHeight()); + width = parent_rect.getWidth(); + } + } + + setRect(rect); + LLPanel::reshape(width, rect.getHeight(), false); +} diff --git a/indra/newview/llchicletbar.h b/indra/newview/llchicletbar.h index 5a0ef32b44..53abebc7ab 100644 --- a/indra/newview/llchicletbar.h +++ b/indra/newview/llchicletbar.h @@ -1,90 +1,90 @@ -/** -* @file llchicletbar.h -* @brief LLChicletBar class header file -* -* $LicenseInfo:firstyear=2011&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2011, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#ifndef LL_LLCHICLETBAR_H -#define LL_LLCHICLETBAR_H - -#include "llpanel.h" - -class LLChicletPanel; -class LLIMChiclet; -class LLLayoutPanel; -class LLLayoutStack; - -class LLChicletBar - : public LLSingleton - , public LLPanel -{ - LLSINGLETON(LLChicletBar); - LOG_CLASS(LLChicletBar); - -public: - - bool postBuild() override; - - LLChicletPanel* getChicletPanel() { return mChicletPanel; } - - void reshape(S32 width, S32 height, bool called_from_parent) override; - - - /** - * Shows/hides panel with specified well button (IM or Notification) - * - * @param well_name - name of the well panel to be processed. - * @param visible - a flag specifying whether a button should be shown or hidden. - */ - void showWellButton(const std::string& well_name, bool visible); - -private: - /** - * Updates child controls size and visibility when it is necessary to reduce total width. - * - * @param delta_width - value by which chiclet bar should be shrunk. It is a negative value. - * @returns positive value which chiclet bar can not process when it reaches its minimal width. - * Zero if there was enough space to process delta_width. - */ - S32 processWidthDecreased(S32 delta_width); - - /** helper function to log debug messages */ - void log(LLView* panel, const std::string& descr); - - /** - * @return difference between current chiclet panel width and the minimum. - */ - S32 getChicletPanelShrinkHeadroom() const; - - /** - * function adjusts Chiclet bar width to prevent overlapping with Mini-Location bar - * EXP-1463 - */ - void fitWithTopInfoBar(); - -protected: - LLChicletPanel* mChicletPanel; - LLLayoutStack* mToolbarStack; -}; - -#endif // LL_LLCHICLETBAR_H +/** +* @file llchicletbar.h +* @brief LLChicletBar class header file +* +* $LicenseInfo:firstyear=2011&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2011, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#ifndef LL_LLCHICLETBAR_H +#define LL_LLCHICLETBAR_H + +#include "llpanel.h" + +class LLChicletPanel; +class LLIMChiclet; +class LLLayoutPanel; +class LLLayoutStack; + +class LLChicletBar + : public LLSingleton + , public LLPanel +{ + LLSINGLETON(LLChicletBar); + LOG_CLASS(LLChicletBar); + +public: + + bool postBuild() override; + + LLChicletPanel* getChicletPanel() { return mChicletPanel; } + + void reshape(S32 width, S32 height, bool called_from_parent) override; + + + /** + * Shows/hides panel with specified well button (IM or Notification) + * + * @param well_name - name of the well panel to be processed. + * @param visible - a flag specifying whether a button should be shown or hidden. + */ + void showWellButton(const std::string& well_name, bool visible); + +private: + /** + * Updates child controls size and visibility when it is necessary to reduce total width. + * + * @param delta_width - value by which chiclet bar should be shrunk. It is a negative value. + * @returns positive value which chiclet bar can not process when it reaches its minimal width. + * Zero if there was enough space to process delta_width. + */ + S32 processWidthDecreased(S32 delta_width); + + /** helper function to log debug messages */ + void log(LLView* panel, const std::string& descr); + + /** + * @return difference between current chiclet panel width and the minimum. + */ + S32 getChicletPanelShrinkHeadroom() const; + + /** + * function adjusts Chiclet bar width to prevent overlapping with Mini-Location bar + * EXP-1463 + */ + void fitWithTopInfoBar(); + +protected: + LLChicletPanel* mChicletPanel; + LLLayoutStack* mToolbarStack; +}; + +#endif // LL_LLCHICLETBAR_H diff --git a/indra/newview/llcofwearables.cpp b/indra/newview/llcofwearables.cpp index d413d88a93..b42d217477 100644 --- a/indra/newview/llcofwearables.cpp +++ b/indra/newview/llcofwearables.cpp @@ -1,780 +1,780 @@ -/** - * @file llcofwearables.cpp - * @brief LLCOFWearables displayes wearables from the current outfit split into three lists (attachments, clothing and body parts) - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llcofwearables.h" - -#include "llaccordionctrl.h" -#include "llaccordionctrltab.h" -#include "llagentdata.h" -#include "llagentwearables.h" -#include "llappearancemgr.h" -#include "llfloatersidepanelcontainer.h" -#include "llinventory.h" -#include "llinventoryfunctions.h" -#include "lllistcontextmenu.h" -#include "llmenugl.h" -#include "llviewermenu.h" -#include "llwearableitemslist.h" -#include "llpaneloutfitedit.h" -#include "lltrans.h" -#include "llvoavatarself.h" - -static LLPanelInjector t_cof_wearables("cof_wearables"); - -const LLSD REARRANGE = LLSD().with("rearrange", LLSD()); - -static const LLWearableItemNameComparator WEARABLE_NAME_COMPARATOR; - -////////////////////////////////////////////////////////////////////////// - -class CofContextMenu : public LLListContextMenu -{ -protected: - CofContextMenu(LLCOFWearables* cof_wearables) - : mCOFWearables(cof_wearables) - { - llassert(mCOFWearables); - } - - void updateCreateWearableLabel(LLMenuGL* menu, const LLUUID& item_id) - { - LLMenuItemGL* menu_item = menu->getChild("create_new"); - LLWearableType::EType w_type = getWearableType(item_id); - - // Hide the "Create new " if it's irrelevant. - if (w_type == LLWearableType::WT_NONE) - { - menu_item->setVisible(false); - return; - } - - // Set proper label for the "Create new " menu item. - std::string new_label = LLTrans::getString("create_new_" + LLWearableType::getInstance()->getTypeName(w_type)); - menu_item->setLabel(new_label); - } - - void createNew(const LLUUID& item_id) - { - LLAgentWearables::createWearable(getWearableType(item_id), true); - } - - // Get wearable type of the given item. - // - // There is a special case: so-called "dummy items" - // (i.e. the ones that are there just to indicate that you're not wearing - // any wearables of the corresponding type. They are currently grayed out - // and suffixed with "not worn"). - // Those items don't have an UUID, but they do have an associated wearable type. - // If the user has invoked context menu for such item, - // we ignore the passed item_id and retrieve wearable type from the item. - LLWearableType::EType getWearableType(const LLUUID& item_id) - { - if (!isDummyItem(item_id)) - { - LLViewerInventoryItem* item = gInventory.getLinkedItem(item_id); - if (item && item->isWearableType()) - { - return item->getWearableType(); - } - } - else if (mCOFWearables) // dummy item selected - { - LLPanelDummyClothingListItem* item; - - item = dynamic_cast(mCOFWearables->getSelectedItem()); - if (item) - { - return item->getWearableType(); - } - } - - return LLWearableType::WT_NONE; - } - - static bool isDummyItem(const LLUUID& item_id) - { - return item_id.isNull(); - } - - LLCOFWearables* mCOFWearables; -}; - -////////////////////////////////////////////////////////////////////////// - -class CofAttachmentContextMenu : public CofContextMenu -{ -public: - CofAttachmentContextMenu(LLCOFWearables* cof_wearables) - : CofContextMenu(cof_wearables) - { - } - -protected: - - /*virtual*/ LLContextMenu* createMenu() - { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - - registrar.add("Attachment.Touch", boost::bind(handleMultiple, handle_attachment_touch, mUUIDs)); - registrar.add("Attachment.Edit", boost::bind(handleMultiple, handle_item_edit, mUUIDs)); - registrar.add("Attachment.Detach", boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), mUUIDs, no_op)); - - LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; - enable_registrar.add("Attachment.OnEnable", boost::bind(&CofAttachmentContextMenu::onEnable, this, _2)); - - return createFromFile("menu_cof_attachment.xml"); - } - - bool onEnable(const LLSD& userdata) - { - const std::string event_name = userdata.asString(); - - if ("touch" == event_name) - { - return (1 == mUUIDs.size()) && (enable_attachment_touch(mUUIDs.front())); - } - else if ("edit" == event_name) - { - return (1 == mUUIDs.size()) && (get_is_item_editable(mUUIDs.front())); - } - - return true; - } -}; - -////////////////////////////////////////////////////////////////////////// - -class CofClothingContextMenu : public CofContextMenu -{ -public: - CofClothingContextMenu(LLCOFWearables* cof_wearables) - : CofContextMenu(cof_wearables) - { - } - -protected: - static void replaceWearable(const LLUUID& item_id) - { - LLPanelOutfitEdit * panel_outfit_edit = - dynamic_cast (LLFloaterSidePanelContainer::getPanel("appearance", - "panel_outfit_edit")); - if (panel_outfit_edit != NULL) - { - panel_outfit_edit->onReplaceMenuItemClicked(item_id); - } - } - - /*virtual*/ LLContextMenu* createMenu() - { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; - LLUUID selected_id = mUUIDs.back(); - - registrar.add("Clothing.TakeOff", boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), mUUIDs, no_op)); - registrar.add("Clothing.Replace", boost::bind(replaceWearable, selected_id)); - registrar.add("Clothing.Edit", boost::bind(LLAgentWearables::editWearable, selected_id)); - registrar.add("Clothing.Create", boost::bind(&CofClothingContextMenu::createNew, this, selected_id)); - - enable_registrar.add("Clothing.OnEnable", boost::bind(&CofClothingContextMenu::onEnable, this, _2)); - - LLContextMenu* menu = createFromFile("menu_cof_clothing.xml"); - llassert(menu); - if (menu) - { - updateCreateWearableLabel(menu, selected_id); - } - return menu; - } - - bool onEnable(const LLSD& data) - { - std::string param = data.asString(); - LLUUID selected_id = mUUIDs.back(); - - if ("take_off" == param) - { - return get_is_item_worn(selected_id); - } - else if ("edit" == param) - { - return mUUIDs.size() == 1 && gAgentWearables.isWearableModifiable(selected_id); - } - else if ("replace" == param) - { - return get_is_item_worn(selected_id) && mUUIDs.size() == 1; - } - - return true; - } -}; - -////////////////////////////////////////////////////////////////////////// - -class CofBodyPartContextMenu : public CofContextMenu -{ -public: - CofBodyPartContextMenu(LLCOFWearables* cof_wearables) - : CofContextMenu(cof_wearables) - { - } - -protected: - /*virtual*/ LLContextMenu* createMenu() - { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; - LLUUID selected_id = mUUIDs.back(); - - LLPanelOutfitEdit* panel_oe = dynamic_cast(LLFloaterSidePanelContainer::getPanel("appearance", "panel_outfit_edit")); - registrar.add("BodyPart.Replace", boost::bind(&LLPanelOutfitEdit::onReplaceMenuItemClicked, panel_oe, selected_id)); - registrar.add("BodyPart.Edit", boost::bind(LLAgentWearables::editWearable, selected_id)); - registrar.add("BodyPart.Create", boost::bind(&CofBodyPartContextMenu::createNew, this, selected_id)); - - enable_registrar.add("BodyPart.OnEnable", boost::bind(&CofBodyPartContextMenu::onEnable, this, _2)); - - LLContextMenu* menu = createFromFile("menu_cof_body_part.xml"); - llassert(menu); - if (menu) - { - updateCreateWearableLabel(menu, selected_id); - } - return menu; - } - - bool onEnable(const LLSD& data) - { - std::string param = data.asString(); - LLUUID selected_id = mUUIDs.back(); - - if ("edit" == param) - { - return mUUIDs.size() == 1 && gAgentWearables.isWearableModifiable(selected_id); - } - - return true; - } -}; - -////////////////////////////////////////////////////////////////////////// - -LLCOFWearables::LLCOFWearables() : LLPanel(), - mAttachments(NULL), - mClothing(NULL), - mBodyParts(NULL), - mLastSelectedList(NULL), - mClothingTab(NULL), - mAttachmentsTab(NULL), - mBodyPartsTab(NULL), - mLastSelectedTab(NULL), - mAccordionCtrl(NULL), - mCOFVersion(-1) -{ - mClothingMenu = new CofClothingContextMenu(this); - mAttachmentMenu = new CofAttachmentContextMenu(this); - mBodyPartMenu = new CofBodyPartContextMenu(this); -}; - -LLCOFWearables::~LLCOFWearables() -{ - delete mClothingMenu; - delete mAttachmentMenu; - delete mBodyPartMenu; -} - -// virtual -bool LLCOFWearables::postBuild() -{ - mAttachments = getChild("list_attachments"); - mClothing = getChild("list_clothing"); - mBodyParts = getChild("list_body_parts"); - - mClothing->setRightMouseDownCallback(boost::bind(&LLCOFWearables::onListRightClick, this, _1, _2, _3, mClothingMenu)); - mAttachments->setRightMouseDownCallback(boost::bind(&LLCOFWearables::onListRightClick, this, _1, _2, _3, mAttachmentMenu)); - mBodyParts->setRightMouseDownCallback(boost::bind(&LLCOFWearables::onListRightClick, this, _1, _2, _3, mBodyPartMenu)); - - //selection across different list/tabs is not supported - mAttachments->setCommitCallback(boost::bind(&LLCOFWearables::onSelectionChange, this, mAttachments)); - mClothing->setCommitCallback(boost::bind(&LLCOFWearables::onSelectionChange, this, mClothing)); - mBodyParts->setCommitCallback(boost::bind(&LLCOFWearables::onSelectionChange, this, mBodyParts)); - - mAttachments->setCommitOnSelectionChange(true); - mClothing->setCommitOnSelectionChange(true); - mBodyParts->setCommitOnSelectionChange(true); - - //clothing is sorted according to its position relatively to the body - mAttachments->setComparator(&WEARABLE_NAME_COMPARATOR); - mBodyParts->setComparator(&WEARABLE_NAME_COMPARATOR); - - mClothingTab = getChild("tab_clothing"); - mClothingTab->setDropDownStateChangedCallback(boost::bind(&LLCOFWearables::onAccordionTabStateChanged, this, _1, _2)); - - mAttachmentsTab = getChild("tab_attachments"); - mAttachmentsTab->setDropDownStateChangedCallback(boost::bind(&LLCOFWearables::onAccordionTabStateChanged, this, _1, _2)); - - mBodyPartsTab = getChild("tab_body_parts"); - mBodyPartsTab->setDropDownStateChangedCallback(boost::bind(&LLCOFWearables::onAccordionTabStateChanged, this, _1, _2)); - - mTab2AssetType[mClothingTab] = LLAssetType::AT_CLOTHING; - mTab2AssetType[mAttachmentsTab] = LLAssetType::AT_OBJECT; - mTab2AssetType[mBodyPartsTab] = LLAssetType::AT_BODYPART; - - mAccordionCtrl = getChild("cof_wearables_accordion"); - - return LLPanel::postBuild(); -} - -void LLCOFWearables::setAttachmentsTitle() -{ - if (mAttachmentsTab) - { - U32 free_slots = gAgentAvatarp->getMaxAttachments() - mAttachments->size(); - - LLStringUtil::format_map_t args_attachments; - args_attachments["[COUNT]"] = llformat ("%d", free_slots); - std::string attachments_title = LLTrans::getString("Attachments remain", args_attachments); - mAttachmentsTab->setTitle(attachments_title); - } -} - -void LLCOFWearables::onSelectionChange(LLFlatListView* selected_list) -{ - if (!selected_list) return; - - if (selected_list != mLastSelectedList) - { - if (selected_list != mAttachments) mAttachments->resetSelection(true); - if (selected_list != mClothing) mClothing->resetSelection(true); - if (selected_list != mBodyParts) mBodyParts->resetSelection(true); - - mLastSelectedList = selected_list; - } - - onCommit(); -} - -void LLCOFWearables::onAccordionTabStateChanged(LLUICtrl* ctrl, const LLSD& expanded) -{ - bool had_selected_items = mClothing->numSelected() || mAttachments->numSelected() || mBodyParts->numSelected(); - mClothing->resetSelection(true); - mAttachments->resetSelection(true); - mBodyParts->resetSelection(true); - - bool tab_selection_changed = false; - LLAccordionCtrlTab* tab = dynamic_cast(ctrl); - if (tab && tab != mLastSelectedTab) - { - mLastSelectedTab = tab; - tab_selection_changed = true; - } - - if (had_selected_items || tab_selection_changed) - { - //sending commit signal to indicate selection changes - onCommit(); - } -} - -void LLCOFWearables::refresh() -{ - const LLUUID cof_id = LLAppearanceMgr::instance().getCOF(); - if (cof_id.isNull()) - { - LL_WARNS() << "COF ID cannot be NULL" << LL_ENDL; - return; - } - - LLViewerInventoryCategory* catp = gInventory.getCategory(cof_id); - if (!catp) - { - LL_WARNS() << "COF category cannot be NULL" << LL_ENDL; - return; - } - - // BAP - this check has to be removed because an item name change does not - // change cat version - ie, checking version is not a complete way - // of finding out whether anything has changed in this category. - //if (mCOFVersion == catp->getVersion()) return; - - mCOFVersion = catp->getVersion(); - - // Save current scrollbar position. - typedef std::map scroll_pos_map_t; - scroll_pos_map_t saved_scroll_pos; - - saved_scroll_pos[mAttachments] = mAttachments->getVisibleContentRect(); - saved_scroll_pos[mClothing] = mClothing->getVisibleContentRect(); - saved_scroll_pos[mBodyParts] = mBodyParts->getVisibleContentRect(); - - // Save current selection. - typedef std::vector values_vector_t; - typedef std::map selection_map_t; - - selection_map_t preserve_selection; - - mAttachments->getSelectedValues(preserve_selection[mAttachments]); - mClothing->getSelectedValues(preserve_selection[mClothing]); - mBodyParts->getSelectedValues(preserve_selection[mBodyParts]); - - clear(); - - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t cof_items; - - gInventory.collectDescendents(cof_id, cats, cof_items, LLInventoryModel::EXCLUDE_TRASH); - - populateAttachmentsAndBodypartsLists(cof_items); - - - LLAppearanceMgr::wearables_by_type_t clothing_by_type(LLWearableType::WT_COUNT); - LLAppearanceMgr::getInstance()->divvyWearablesByType(cof_items, clothing_by_type); - - populateClothingList(clothing_by_type); - - // Restore previous selection - for (selection_map_t::iterator - iter = preserve_selection.begin(), - iter_end = preserve_selection.end(); - iter != iter_end; ++iter) - { - LLFlatListView* list = iter->first; - if (!list) continue; - - //restoring selection should not fire commit callbacks - list->setCommitOnSelectionChange(false); - - const values_vector_t& values = iter->second; - for (values_vector_t::const_iterator - value_it = values.begin(), - value_it_end = values.end(); - value_it != value_it_end; ++value_it) - { - // value_it may be null because of dummy items - // Dummy items have no ID - if(value_it->asUUID().notNull()) - { - list->selectItemByValue(*value_it); - } - } - - list->setCommitOnSelectionChange(true); - } - - // Restore previous scrollbar position. - for (scroll_pos_map_t::const_iterator it = saved_scroll_pos.begin(); it != saved_scroll_pos.end(); ++it) - { - LLFlatListView* list = it->first; - LLRect scroll_pos = it->second; - - list->scrollToShowRect(scroll_pos); - } -} - - -void LLCOFWearables::populateAttachmentsAndBodypartsLists(const LLInventoryModel::item_array_t& cof_items) -{ - for (U32 i = 0; i < cof_items.size(); ++i) - { - LLViewerInventoryItem* item = cof_items.at(i); - if (!item) continue; - - const LLAssetType::EType item_type = item->getType(); - if (item_type == LLAssetType::AT_CLOTHING) continue; - LLPanelInventoryListItemBase* item_panel = NULL; - if (item_type == LLAssetType::AT_OBJECT || item_type == LLAssetType::AT_GESTURE) - { - item_panel = buildAttachemntListItem(item); - mAttachments->addItem(item_panel, item->getUUID(), ADD_BOTTOM, false); - } - else if (item_type == LLAssetType::AT_BODYPART) - { - item_panel = buildBodypartListItem(item); - if (!item_panel) continue; - - mBodyParts->addItem(item_panel, item->getUUID(), ADD_BOTTOM, false); - } - } - - if (mAttachments->size()) - { - mAttachments->sort(); - mAttachments->notify(REARRANGE); //notifying the parent about the list's size change (cause items were added with rearrange=false) - } - else - { - mAttachments->setNoItemsCommentText(LLTrans::getString("no_attachments")); - } - - setAttachmentsTitle(); - - if (mBodyParts->size()) - { - mBodyParts->sort(); - mBodyParts->notify(REARRANGE); - } -} - -//create a clothing list item, update verbs and show/hide line separator -LLPanelClothingListItem* LLCOFWearables::buildClothingListItem(LLViewerInventoryItem* item, bool first, bool last) -{ - llassert(item); - if (!item) return NULL; - LLPanelClothingListItem* item_panel = LLPanelClothingListItem::create(item); - if (!item_panel) return NULL; - - //updating verbs - //we don't need to use permissions of a link but of an actual/linked item - if (item->getLinkedItem()) item = item->getLinkedItem(); - llassert(item); - if (!item) return NULL; - - bool allow_modify = item->getPermissions().allowModifyBy(gAgentID); - - item_panel->setShowLockButton(!allow_modify); - item_panel->setShowEditButton(allow_modify); - - item_panel->setShowMoveUpButton(!first); - item_panel->setShowMoveDownButton(!last); - - //setting callbacks - //*TODO move that item panel's inner structure disclosing stuff into the panels - item_panel->childSetAction("btn_delete", boost::bind(mCOFCallbacks.mDeleteWearable)); - item_panel->childSetAction("btn_move_up", boost::bind(mCOFCallbacks.mMoveWearableFurther)); - item_panel->childSetAction("btn_move_down", boost::bind(mCOFCallbacks.mMoveWearableCloser)); - item_panel->childSetAction("btn_edit", boost::bind(mCOFCallbacks.mEditWearable)); - - //turning on gray separator line for the last item in the items group of the same wearable type - item_panel->setSeparatorVisible(last); - - return item_panel; -} - -LLPanelBodyPartsListItem* LLCOFWearables::buildBodypartListItem(LLViewerInventoryItem* item) -{ - llassert(item); - if (!item) return NULL; - LLPanelBodyPartsListItem* item_panel = LLPanelBodyPartsListItem::create(item); - if (!item_panel) return NULL; - - //updating verbs - //we don't need to use permissions of a link but of an actual/linked item - if (item->getLinkedItem()) item = item->getLinkedItem(); - llassert(item); - if (!item) return NULL; - bool allow_modify = item->getPermissions().allowModifyBy(gAgentID); - item_panel->setShowLockButton(!allow_modify); - item_panel->setShowEditButton(allow_modify); - - //setting callbacks - //*TODO move that item panel's inner structure disclosing stuff into the panels - item_panel->childSetAction("btn_delete", boost::bind(mCOFCallbacks.mDeleteWearable)); - item_panel->childSetAction("btn_edit", boost::bind(mCOFCallbacks.mEditWearable)); - - return item_panel; -} - -LLPanelDeletableWearableListItem* LLCOFWearables::buildAttachemntListItem(LLViewerInventoryItem* item) -{ - llassert(item); - if (!item) return NULL; - - LLPanelAttachmentListItem* item_panel = LLPanelAttachmentListItem::create(item); - if (!item_panel) return NULL; - - //setting callbacks - //*TODO move that item panel's inner structure disclosing stuff into the panels - item_panel->childSetAction("btn_delete", boost::bind(mCOFCallbacks.mDeleteWearable)); - - return item_panel; -} - -void LLCOFWearables::populateClothingList(LLAppearanceMgr::wearables_by_type_t& clothing_by_type) -{ - llassert(clothing_by_type.size() == LLWearableType::WT_COUNT); - - for (U32 type = LLWearableType::WT_SHIRT; type < LLWearableType::WT_COUNT; ++type) - { - U32 size = clothing_by_type[type].size(); - if (!size) continue; - - LLAppearanceMgr::sortItemsByActualDescription(clothing_by_type[type]); - - //clothing items are displayed in reverse order, from furthest ones to closest ones (relatively to the body) - for (U32 i = size; i != 0; --i) - { - LLViewerInventoryItem* item = clothing_by_type[type][i-1]; - - LLPanelClothingListItem* item_panel = buildClothingListItem(item, i == size, i == 1); - if (!item_panel) continue; - - mClothing->addItem(item_panel, item->getUUID(), ADD_BOTTOM, false); - } - } - - addClothingTypesDummies(clothing_by_type); - - mClothing->notify(REARRANGE); -} - -//adding dummy items for missing wearable types -void LLCOFWearables::addClothingTypesDummies(const LLAppearanceMgr::wearables_by_type_t& clothing_by_type) -{ - llassert(clothing_by_type.size() == LLWearableType::WT_COUNT); - - for (U32 type = LLWearableType::WT_SHIRT; type < LLWearableType::WT_COUNT; type++) - { - U32 size = clothing_by_type[type].size(); - if (size) continue; - - LLWearableType::EType w_type = static_cast(type); - LLPanelInventoryListItemBase* item_panel = LLPanelDummyClothingListItem::create(w_type); - if(!item_panel) continue; - item_panel->childSetAction("btn_add", boost::bind(mCOFCallbacks.mAddWearable)); - mClothing->addItem(item_panel, LLUUID::null, ADD_BOTTOM, false); - } -} - -LLUUID LLCOFWearables::getSelectedUUID() -{ - if (!mLastSelectedList) return LLUUID::null; - - return mLastSelectedList->getSelectedUUID(); -} - -bool LLCOFWearables::getSelectedUUIDs(uuid_vec_t& selected_ids) -{ - if (!mLastSelectedList) return false; - - mLastSelectedList->getSelectedUUIDs(selected_ids); - return selected_ids.size() != 0; -} - -LLPanel* LLCOFWearables::getSelectedItem() -{ - if (!mLastSelectedList) return NULL; - - return mLastSelectedList->getSelectedItem(); -} - -void LLCOFWearables::getSelectedItems(std::vector& selected_items) const -{ - if (mLastSelectedList) - { - mLastSelectedList->getSelectedItems(selected_items); - } -} - -void LLCOFWearables::clear() -{ - mAttachments->clear(); - mClothing->clear(); - mBodyParts->clear(); -} - -LLAssetType::EType LLCOFWearables::getExpandedAccordionAssetType() -{ - typedef std::map type_map_t; - - static type_map_t type_map; - - if (mAccordionCtrl != NULL) - { - const LLAccordionCtrlTab* expanded_tab = mAccordionCtrl->getExpandedTab(); - - return get_if_there(mTab2AssetType, expanded_tab, LLAssetType::AT_NONE); - } - - return LLAssetType::AT_NONE; -} - -LLAssetType::EType LLCOFWearables::getSelectedAccordionAssetType() - { - if (mAccordionCtrl != NULL) - { - const LLAccordionCtrlTab* selected_tab = mAccordionCtrl->getSelectedTab(); - - return get_if_there(mTab2AssetType, selected_tab, LLAssetType::AT_NONE); -} - - return LLAssetType::AT_NONE; -} - -void LLCOFWearables::expandDefaultAccordionTab() -{ - if (mAccordionCtrl != NULL) - { - mAccordionCtrl->expandDefaultTab(); - } -} - -void LLCOFWearables::onListRightClick(LLUICtrl* ctrl, S32 x, S32 y, LLListContextMenu* menu) -{ - if(menu) - { - uuid_vec_t selected_uuids; - if(getSelectedUUIDs(selected_uuids)) - { - bool show_menu = false; - for(uuid_vec_t::iterator it = selected_uuids.begin();it!=selected_uuids.end();++it) - { - if ((*it).notNull()) - { - show_menu = true; - break; - } - } - - if(show_menu) - { - menu->show(ctrl, selected_uuids, x, y); - } - } - } -} - -void LLCOFWearables::selectClothing(LLWearableType::EType clothing_type) -{ - std::vector clothing_items; - - mClothing->getItems(clothing_items); - - std::vector::iterator it; - - for (it = clothing_items.begin(); it != clothing_items.end(); ++it ) - { - LLPanelClothingListItem* clothing_item = dynamic_cast(*it); - - if (clothing_item && clothing_item->getWearableType() == clothing_type) - { // clothing item has specified LLWearableType::EType. Select it and exit. - - mClothing->selectItem(clothing_item); - break; - } - } -} - -//EOF +/** + * @file llcofwearables.cpp + * @brief LLCOFWearables displayes wearables from the current outfit split into three lists (attachments, clothing and body parts) + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llcofwearables.h" + +#include "llaccordionctrl.h" +#include "llaccordionctrltab.h" +#include "llagentdata.h" +#include "llagentwearables.h" +#include "llappearancemgr.h" +#include "llfloatersidepanelcontainer.h" +#include "llinventory.h" +#include "llinventoryfunctions.h" +#include "lllistcontextmenu.h" +#include "llmenugl.h" +#include "llviewermenu.h" +#include "llwearableitemslist.h" +#include "llpaneloutfitedit.h" +#include "lltrans.h" +#include "llvoavatarself.h" + +static LLPanelInjector t_cof_wearables("cof_wearables"); + +const LLSD REARRANGE = LLSD().with("rearrange", LLSD()); + +static const LLWearableItemNameComparator WEARABLE_NAME_COMPARATOR; + +////////////////////////////////////////////////////////////////////////// + +class CofContextMenu : public LLListContextMenu +{ +protected: + CofContextMenu(LLCOFWearables* cof_wearables) + : mCOFWearables(cof_wearables) + { + llassert(mCOFWearables); + } + + void updateCreateWearableLabel(LLMenuGL* menu, const LLUUID& item_id) + { + LLMenuItemGL* menu_item = menu->getChild("create_new"); + LLWearableType::EType w_type = getWearableType(item_id); + + // Hide the "Create new " if it's irrelevant. + if (w_type == LLWearableType::WT_NONE) + { + menu_item->setVisible(false); + return; + } + + // Set proper label for the "Create new " menu item. + std::string new_label = LLTrans::getString("create_new_" + LLWearableType::getInstance()->getTypeName(w_type)); + menu_item->setLabel(new_label); + } + + void createNew(const LLUUID& item_id) + { + LLAgentWearables::createWearable(getWearableType(item_id), true); + } + + // Get wearable type of the given item. + // + // There is a special case: so-called "dummy items" + // (i.e. the ones that are there just to indicate that you're not wearing + // any wearables of the corresponding type. They are currently grayed out + // and suffixed with "not worn"). + // Those items don't have an UUID, but they do have an associated wearable type. + // If the user has invoked context menu for such item, + // we ignore the passed item_id and retrieve wearable type from the item. + LLWearableType::EType getWearableType(const LLUUID& item_id) + { + if (!isDummyItem(item_id)) + { + LLViewerInventoryItem* item = gInventory.getLinkedItem(item_id); + if (item && item->isWearableType()) + { + return item->getWearableType(); + } + } + else if (mCOFWearables) // dummy item selected + { + LLPanelDummyClothingListItem* item; + + item = dynamic_cast(mCOFWearables->getSelectedItem()); + if (item) + { + return item->getWearableType(); + } + } + + return LLWearableType::WT_NONE; + } + + static bool isDummyItem(const LLUUID& item_id) + { + return item_id.isNull(); + } + + LLCOFWearables* mCOFWearables; +}; + +////////////////////////////////////////////////////////////////////////// + +class CofAttachmentContextMenu : public CofContextMenu +{ +public: + CofAttachmentContextMenu(LLCOFWearables* cof_wearables) + : CofContextMenu(cof_wearables) + { + } + +protected: + + /*virtual*/ LLContextMenu* createMenu() + { + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + + registrar.add("Attachment.Touch", boost::bind(handleMultiple, handle_attachment_touch, mUUIDs)); + registrar.add("Attachment.Edit", boost::bind(handleMultiple, handle_item_edit, mUUIDs)); + registrar.add("Attachment.Detach", boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), mUUIDs, no_op)); + + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + enable_registrar.add("Attachment.OnEnable", boost::bind(&CofAttachmentContextMenu::onEnable, this, _2)); + + return createFromFile("menu_cof_attachment.xml"); + } + + bool onEnable(const LLSD& userdata) + { + const std::string event_name = userdata.asString(); + + if ("touch" == event_name) + { + return (1 == mUUIDs.size()) && (enable_attachment_touch(mUUIDs.front())); + } + else if ("edit" == event_name) + { + return (1 == mUUIDs.size()) && (get_is_item_editable(mUUIDs.front())); + } + + return true; + } +}; + +////////////////////////////////////////////////////////////////////////// + +class CofClothingContextMenu : public CofContextMenu +{ +public: + CofClothingContextMenu(LLCOFWearables* cof_wearables) + : CofContextMenu(cof_wearables) + { + } + +protected: + static void replaceWearable(const LLUUID& item_id) + { + LLPanelOutfitEdit * panel_outfit_edit = + dynamic_cast (LLFloaterSidePanelContainer::getPanel("appearance", + "panel_outfit_edit")); + if (panel_outfit_edit != NULL) + { + panel_outfit_edit->onReplaceMenuItemClicked(item_id); + } + } + + /*virtual*/ LLContextMenu* createMenu() + { + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + LLUUID selected_id = mUUIDs.back(); + + registrar.add("Clothing.TakeOff", boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), mUUIDs, no_op)); + registrar.add("Clothing.Replace", boost::bind(replaceWearable, selected_id)); + registrar.add("Clothing.Edit", boost::bind(LLAgentWearables::editWearable, selected_id)); + registrar.add("Clothing.Create", boost::bind(&CofClothingContextMenu::createNew, this, selected_id)); + + enable_registrar.add("Clothing.OnEnable", boost::bind(&CofClothingContextMenu::onEnable, this, _2)); + + LLContextMenu* menu = createFromFile("menu_cof_clothing.xml"); + llassert(menu); + if (menu) + { + updateCreateWearableLabel(menu, selected_id); + } + return menu; + } + + bool onEnable(const LLSD& data) + { + std::string param = data.asString(); + LLUUID selected_id = mUUIDs.back(); + + if ("take_off" == param) + { + return get_is_item_worn(selected_id); + } + else if ("edit" == param) + { + return mUUIDs.size() == 1 && gAgentWearables.isWearableModifiable(selected_id); + } + else if ("replace" == param) + { + return get_is_item_worn(selected_id) && mUUIDs.size() == 1; + } + + return true; + } +}; + +////////////////////////////////////////////////////////////////////////// + +class CofBodyPartContextMenu : public CofContextMenu +{ +public: + CofBodyPartContextMenu(LLCOFWearables* cof_wearables) + : CofContextMenu(cof_wearables) + { + } + +protected: + /*virtual*/ LLContextMenu* createMenu() + { + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + LLUUID selected_id = mUUIDs.back(); + + LLPanelOutfitEdit* panel_oe = dynamic_cast(LLFloaterSidePanelContainer::getPanel("appearance", "panel_outfit_edit")); + registrar.add("BodyPart.Replace", boost::bind(&LLPanelOutfitEdit::onReplaceMenuItemClicked, panel_oe, selected_id)); + registrar.add("BodyPart.Edit", boost::bind(LLAgentWearables::editWearable, selected_id)); + registrar.add("BodyPart.Create", boost::bind(&CofBodyPartContextMenu::createNew, this, selected_id)); + + enable_registrar.add("BodyPart.OnEnable", boost::bind(&CofBodyPartContextMenu::onEnable, this, _2)); + + LLContextMenu* menu = createFromFile("menu_cof_body_part.xml"); + llassert(menu); + if (menu) + { + updateCreateWearableLabel(menu, selected_id); + } + return menu; + } + + bool onEnable(const LLSD& data) + { + std::string param = data.asString(); + LLUUID selected_id = mUUIDs.back(); + + if ("edit" == param) + { + return mUUIDs.size() == 1 && gAgentWearables.isWearableModifiable(selected_id); + } + + return true; + } +}; + +////////////////////////////////////////////////////////////////////////// + +LLCOFWearables::LLCOFWearables() : LLPanel(), + mAttachments(NULL), + mClothing(NULL), + mBodyParts(NULL), + mLastSelectedList(NULL), + mClothingTab(NULL), + mAttachmentsTab(NULL), + mBodyPartsTab(NULL), + mLastSelectedTab(NULL), + mAccordionCtrl(NULL), + mCOFVersion(-1) +{ + mClothingMenu = new CofClothingContextMenu(this); + mAttachmentMenu = new CofAttachmentContextMenu(this); + mBodyPartMenu = new CofBodyPartContextMenu(this); +}; + +LLCOFWearables::~LLCOFWearables() +{ + delete mClothingMenu; + delete mAttachmentMenu; + delete mBodyPartMenu; +} + +// virtual +bool LLCOFWearables::postBuild() +{ + mAttachments = getChild("list_attachments"); + mClothing = getChild("list_clothing"); + mBodyParts = getChild("list_body_parts"); + + mClothing->setRightMouseDownCallback(boost::bind(&LLCOFWearables::onListRightClick, this, _1, _2, _3, mClothingMenu)); + mAttachments->setRightMouseDownCallback(boost::bind(&LLCOFWearables::onListRightClick, this, _1, _2, _3, mAttachmentMenu)); + mBodyParts->setRightMouseDownCallback(boost::bind(&LLCOFWearables::onListRightClick, this, _1, _2, _3, mBodyPartMenu)); + + //selection across different list/tabs is not supported + mAttachments->setCommitCallback(boost::bind(&LLCOFWearables::onSelectionChange, this, mAttachments)); + mClothing->setCommitCallback(boost::bind(&LLCOFWearables::onSelectionChange, this, mClothing)); + mBodyParts->setCommitCallback(boost::bind(&LLCOFWearables::onSelectionChange, this, mBodyParts)); + + mAttachments->setCommitOnSelectionChange(true); + mClothing->setCommitOnSelectionChange(true); + mBodyParts->setCommitOnSelectionChange(true); + + //clothing is sorted according to its position relatively to the body + mAttachments->setComparator(&WEARABLE_NAME_COMPARATOR); + mBodyParts->setComparator(&WEARABLE_NAME_COMPARATOR); + + mClothingTab = getChild("tab_clothing"); + mClothingTab->setDropDownStateChangedCallback(boost::bind(&LLCOFWearables::onAccordionTabStateChanged, this, _1, _2)); + + mAttachmentsTab = getChild("tab_attachments"); + mAttachmentsTab->setDropDownStateChangedCallback(boost::bind(&LLCOFWearables::onAccordionTabStateChanged, this, _1, _2)); + + mBodyPartsTab = getChild("tab_body_parts"); + mBodyPartsTab->setDropDownStateChangedCallback(boost::bind(&LLCOFWearables::onAccordionTabStateChanged, this, _1, _2)); + + mTab2AssetType[mClothingTab] = LLAssetType::AT_CLOTHING; + mTab2AssetType[mAttachmentsTab] = LLAssetType::AT_OBJECT; + mTab2AssetType[mBodyPartsTab] = LLAssetType::AT_BODYPART; + + mAccordionCtrl = getChild("cof_wearables_accordion"); + + return LLPanel::postBuild(); +} + +void LLCOFWearables::setAttachmentsTitle() +{ + if (mAttachmentsTab) + { + U32 free_slots = gAgentAvatarp->getMaxAttachments() - mAttachments->size(); + + LLStringUtil::format_map_t args_attachments; + args_attachments["[COUNT]"] = llformat ("%d", free_slots); + std::string attachments_title = LLTrans::getString("Attachments remain", args_attachments); + mAttachmentsTab->setTitle(attachments_title); + } +} + +void LLCOFWearables::onSelectionChange(LLFlatListView* selected_list) +{ + if (!selected_list) return; + + if (selected_list != mLastSelectedList) + { + if (selected_list != mAttachments) mAttachments->resetSelection(true); + if (selected_list != mClothing) mClothing->resetSelection(true); + if (selected_list != mBodyParts) mBodyParts->resetSelection(true); + + mLastSelectedList = selected_list; + } + + onCommit(); +} + +void LLCOFWearables::onAccordionTabStateChanged(LLUICtrl* ctrl, const LLSD& expanded) +{ + bool had_selected_items = mClothing->numSelected() || mAttachments->numSelected() || mBodyParts->numSelected(); + mClothing->resetSelection(true); + mAttachments->resetSelection(true); + mBodyParts->resetSelection(true); + + bool tab_selection_changed = false; + LLAccordionCtrlTab* tab = dynamic_cast(ctrl); + if (tab && tab != mLastSelectedTab) + { + mLastSelectedTab = tab; + tab_selection_changed = true; + } + + if (had_selected_items || tab_selection_changed) + { + //sending commit signal to indicate selection changes + onCommit(); + } +} + +void LLCOFWearables::refresh() +{ + const LLUUID cof_id = LLAppearanceMgr::instance().getCOF(); + if (cof_id.isNull()) + { + LL_WARNS() << "COF ID cannot be NULL" << LL_ENDL; + return; + } + + LLViewerInventoryCategory* catp = gInventory.getCategory(cof_id); + if (!catp) + { + LL_WARNS() << "COF category cannot be NULL" << LL_ENDL; + return; + } + + // BAP - this check has to be removed because an item name change does not + // change cat version - ie, checking version is not a complete way + // of finding out whether anything has changed in this category. + //if (mCOFVersion == catp->getVersion()) return; + + mCOFVersion = catp->getVersion(); + + // Save current scrollbar position. + typedef std::map scroll_pos_map_t; + scroll_pos_map_t saved_scroll_pos; + + saved_scroll_pos[mAttachments] = mAttachments->getVisibleContentRect(); + saved_scroll_pos[mClothing] = mClothing->getVisibleContentRect(); + saved_scroll_pos[mBodyParts] = mBodyParts->getVisibleContentRect(); + + // Save current selection. + typedef std::vector values_vector_t; + typedef std::map selection_map_t; + + selection_map_t preserve_selection; + + mAttachments->getSelectedValues(preserve_selection[mAttachments]); + mClothing->getSelectedValues(preserve_selection[mClothing]); + mBodyParts->getSelectedValues(preserve_selection[mBodyParts]); + + clear(); + + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t cof_items; + + gInventory.collectDescendents(cof_id, cats, cof_items, LLInventoryModel::EXCLUDE_TRASH); + + populateAttachmentsAndBodypartsLists(cof_items); + + + LLAppearanceMgr::wearables_by_type_t clothing_by_type(LLWearableType::WT_COUNT); + LLAppearanceMgr::getInstance()->divvyWearablesByType(cof_items, clothing_by_type); + + populateClothingList(clothing_by_type); + + // Restore previous selection + for (selection_map_t::iterator + iter = preserve_selection.begin(), + iter_end = preserve_selection.end(); + iter != iter_end; ++iter) + { + LLFlatListView* list = iter->first; + if (!list) continue; + + //restoring selection should not fire commit callbacks + list->setCommitOnSelectionChange(false); + + const values_vector_t& values = iter->second; + for (values_vector_t::const_iterator + value_it = values.begin(), + value_it_end = values.end(); + value_it != value_it_end; ++value_it) + { + // value_it may be null because of dummy items + // Dummy items have no ID + if(value_it->asUUID().notNull()) + { + list->selectItemByValue(*value_it); + } + } + + list->setCommitOnSelectionChange(true); + } + + // Restore previous scrollbar position. + for (scroll_pos_map_t::const_iterator it = saved_scroll_pos.begin(); it != saved_scroll_pos.end(); ++it) + { + LLFlatListView* list = it->first; + LLRect scroll_pos = it->second; + + list->scrollToShowRect(scroll_pos); + } +} + + +void LLCOFWearables::populateAttachmentsAndBodypartsLists(const LLInventoryModel::item_array_t& cof_items) +{ + for (U32 i = 0; i < cof_items.size(); ++i) + { + LLViewerInventoryItem* item = cof_items.at(i); + if (!item) continue; + + const LLAssetType::EType item_type = item->getType(); + if (item_type == LLAssetType::AT_CLOTHING) continue; + LLPanelInventoryListItemBase* item_panel = NULL; + if (item_type == LLAssetType::AT_OBJECT || item_type == LLAssetType::AT_GESTURE) + { + item_panel = buildAttachemntListItem(item); + mAttachments->addItem(item_panel, item->getUUID(), ADD_BOTTOM, false); + } + else if (item_type == LLAssetType::AT_BODYPART) + { + item_panel = buildBodypartListItem(item); + if (!item_panel) continue; + + mBodyParts->addItem(item_panel, item->getUUID(), ADD_BOTTOM, false); + } + } + + if (mAttachments->size()) + { + mAttachments->sort(); + mAttachments->notify(REARRANGE); //notifying the parent about the list's size change (cause items were added with rearrange=false) + } + else + { + mAttachments->setNoItemsCommentText(LLTrans::getString("no_attachments")); + } + + setAttachmentsTitle(); + + if (mBodyParts->size()) + { + mBodyParts->sort(); + mBodyParts->notify(REARRANGE); + } +} + +//create a clothing list item, update verbs and show/hide line separator +LLPanelClothingListItem* LLCOFWearables::buildClothingListItem(LLViewerInventoryItem* item, bool first, bool last) +{ + llassert(item); + if (!item) return NULL; + LLPanelClothingListItem* item_panel = LLPanelClothingListItem::create(item); + if (!item_panel) return NULL; + + //updating verbs + //we don't need to use permissions of a link but of an actual/linked item + if (item->getLinkedItem()) item = item->getLinkedItem(); + llassert(item); + if (!item) return NULL; + + bool allow_modify = item->getPermissions().allowModifyBy(gAgentID); + + item_panel->setShowLockButton(!allow_modify); + item_panel->setShowEditButton(allow_modify); + + item_panel->setShowMoveUpButton(!first); + item_panel->setShowMoveDownButton(!last); + + //setting callbacks + //*TODO move that item panel's inner structure disclosing stuff into the panels + item_panel->childSetAction("btn_delete", boost::bind(mCOFCallbacks.mDeleteWearable)); + item_panel->childSetAction("btn_move_up", boost::bind(mCOFCallbacks.mMoveWearableFurther)); + item_panel->childSetAction("btn_move_down", boost::bind(mCOFCallbacks.mMoveWearableCloser)); + item_panel->childSetAction("btn_edit", boost::bind(mCOFCallbacks.mEditWearable)); + + //turning on gray separator line for the last item in the items group of the same wearable type + item_panel->setSeparatorVisible(last); + + return item_panel; +} + +LLPanelBodyPartsListItem* LLCOFWearables::buildBodypartListItem(LLViewerInventoryItem* item) +{ + llassert(item); + if (!item) return NULL; + LLPanelBodyPartsListItem* item_panel = LLPanelBodyPartsListItem::create(item); + if (!item_panel) return NULL; + + //updating verbs + //we don't need to use permissions of a link but of an actual/linked item + if (item->getLinkedItem()) item = item->getLinkedItem(); + llassert(item); + if (!item) return NULL; + bool allow_modify = item->getPermissions().allowModifyBy(gAgentID); + item_panel->setShowLockButton(!allow_modify); + item_panel->setShowEditButton(allow_modify); + + //setting callbacks + //*TODO move that item panel's inner structure disclosing stuff into the panels + item_panel->childSetAction("btn_delete", boost::bind(mCOFCallbacks.mDeleteWearable)); + item_panel->childSetAction("btn_edit", boost::bind(mCOFCallbacks.mEditWearable)); + + return item_panel; +} + +LLPanelDeletableWearableListItem* LLCOFWearables::buildAttachemntListItem(LLViewerInventoryItem* item) +{ + llassert(item); + if (!item) return NULL; + + LLPanelAttachmentListItem* item_panel = LLPanelAttachmentListItem::create(item); + if (!item_panel) return NULL; + + //setting callbacks + //*TODO move that item panel's inner structure disclosing stuff into the panels + item_panel->childSetAction("btn_delete", boost::bind(mCOFCallbacks.mDeleteWearable)); + + return item_panel; +} + +void LLCOFWearables::populateClothingList(LLAppearanceMgr::wearables_by_type_t& clothing_by_type) +{ + llassert(clothing_by_type.size() == LLWearableType::WT_COUNT); + + for (U32 type = LLWearableType::WT_SHIRT; type < LLWearableType::WT_COUNT; ++type) + { + U32 size = clothing_by_type[type].size(); + if (!size) continue; + + LLAppearanceMgr::sortItemsByActualDescription(clothing_by_type[type]); + + //clothing items are displayed in reverse order, from furthest ones to closest ones (relatively to the body) + for (U32 i = size; i != 0; --i) + { + LLViewerInventoryItem* item = clothing_by_type[type][i-1]; + + LLPanelClothingListItem* item_panel = buildClothingListItem(item, i == size, i == 1); + if (!item_panel) continue; + + mClothing->addItem(item_panel, item->getUUID(), ADD_BOTTOM, false); + } + } + + addClothingTypesDummies(clothing_by_type); + + mClothing->notify(REARRANGE); +} + +//adding dummy items for missing wearable types +void LLCOFWearables::addClothingTypesDummies(const LLAppearanceMgr::wearables_by_type_t& clothing_by_type) +{ + llassert(clothing_by_type.size() == LLWearableType::WT_COUNT); + + for (U32 type = LLWearableType::WT_SHIRT; type < LLWearableType::WT_COUNT; type++) + { + U32 size = clothing_by_type[type].size(); + if (size) continue; + + LLWearableType::EType w_type = static_cast(type); + LLPanelInventoryListItemBase* item_panel = LLPanelDummyClothingListItem::create(w_type); + if(!item_panel) continue; + item_panel->childSetAction("btn_add", boost::bind(mCOFCallbacks.mAddWearable)); + mClothing->addItem(item_panel, LLUUID::null, ADD_BOTTOM, false); + } +} + +LLUUID LLCOFWearables::getSelectedUUID() +{ + if (!mLastSelectedList) return LLUUID::null; + + return mLastSelectedList->getSelectedUUID(); +} + +bool LLCOFWearables::getSelectedUUIDs(uuid_vec_t& selected_ids) +{ + if (!mLastSelectedList) return false; + + mLastSelectedList->getSelectedUUIDs(selected_ids); + return selected_ids.size() != 0; +} + +LLPanel* LLCOFWearables::getSelectedItem() +{ + if (!mLastSelectedList) return NULL; + + return mLastSelectedList->getSelectedItem(); +} + +void LLCOFWearables::getSelectedItems(std::vector& selected_items) const +{ + if (mLastSelectedList) + { + mLastSelectedList->getSelectedItems(selected_items); + } +} + +void LLCOFWearables::clear() +{ + mAttachments->clear(); + mClothing->clear(); + mBodyParts->clear(); +} + +LLAssetType::EType LLCOFWearables::getExpandedAccordionAssetType() +{ + typedef std::map type_map_t; + + static type_map_t type_map; + + if (mAccordionCtrl != NULL) + { + const LLAccordionCtrlTab* expanded_tab = mAccordionCtrl->getExpandedTab(); + + return get_if_there(mTab2AssetType, expanded_tab, LLAssetType::AT_NONE); + } + + return LLAssetType::AT_NONE; +} + +LLAssetType::EType LLCOFWearables::getSelectedAccordionAssetType() + { + if (mAccordionCtrl != NULL) + { + const LLAccordionCtrlTab* selected_tab = mAccordionCtrl->getSelectedTab(); + + return get_if_there(mTab2AssetType, selected_tab, LLAssetType::AT_NONE); +} + + return LLAssetType::AT_NONE; +} + +void LLCOFWearables::expandDefaultAccordionTab() +{ + if (mAccordionCtrl != NULL) + { + mAccordionCtrl->expandDefaultTab(); + } +} + +void LLCOFWearables::onListRightClick(LLUICtrl* ctrl, S32 x, S32 y, LLListContextMenu* menu) +{ + if(menu) + { + uuid_vec_t selected_uuids; + if(getSelectedUUIDs(selected_uuids)) + { + bool show_menu = false; + for(uuid_vec_t::iterator it = selected_uuids.begin();it!=selected_uuids.end();++it) + { + if ((*it).notNull()) + { + show_menu = true; + break; + } + } + + if(show_menu) + { + menu->show(ctrl, selected_uuids, x, y); + } + } + } +} + +void LLCOFWearables::selectClothing(LLWearableType::EType clothing_type) +{ + std::vector clothing_items; + + mClothing->getItems(clothing_items); + + std::vector::iterator it; + + for (it = clothing_items.begin(); it != clothing_items.end(); ++it ) + { + LLPanelClothingListItem* clothing_item = dynamic_cast(*it); + + if (clothing_item && clothing_item->getWearableType() == clothing_type) + { // clothing item has specified LLWearableType::EType. Select it and exit. + + mClothing->selectItem(clothing_item); + break; + } + } +} + +//EOF diff --git a/indra/newview/llcofwearables.h b/indra/newview/llcofwearables.h index 5bec4d9e6b..6f0e97a98b 100644 --- a/indra/newview/llcofwearables.h +++ b/indra/newview/llcofwearables.h @@ -1,138 +1,138 @@ -/** - * @file llcofwearables.h - * @brief LLCOFWearables displayes wearables from the current outfit split into three lists (attachments, clothing and body parts) - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLCOFWEARABLES_H -#define LL_LLCOFWEARABLES_H - -// llui -#include "llflatlistview.h" -#include "llpanel.h" - -#include "llappearancemgr.h" -#include "llinventorymodel.h" - -class LLAccordionCtrl; -class LLAccordionCtrlTab; -class LLListContextMenu; -class LLPanelClothingListItem; -class LLPanelBodyPartsListItem; -class LLPanelDeletableWearableListItem; - -class LLCOFWearables : public LLPanel -{ -public: - - /** - * Represents a collection of callbacks assigned to inventory panel item's buttons - */ - class LLCOFCallbacks - { - public: - LLCOFCallbacks() {}; - virtual ~LLCOFCallbacks() {}; - - typedef boost::function cof_callback_t; - - cof_callback_t mAddWearable; - cof_callback_t mMoveWearableCloser; - cof_callback_t mMoveWearableFurther; - cof_callback_t mEditWearable; - cof_callback_t mDeleteWearable; - }; - - - - LLCOFWearables(); - virtual ~LLCOFWearables(); - - /*virtual*/ bool postBuild(); - - LLUUID getSelectedUUID(); - bool getSelectedUUIDs(uuid_vec_t& selected_ids); - - LLPanel* getSelectedItem(); - void getSelectedItems(std::vector& selected_items) const; - - /* Repopulate the COF wearables list if the COF category has been changed since the last refresh */ - void refresh(); - void clear(); - - LLAssetType::EType getExpandedAccordionAssetType(); - LLAssetType::EType getSelectedAccordionAssetType(); - void expandDefaultAccordionTab(); - - LLCOFCallbacks& getCOFCallbacks() { return mCOFCallbacks; } - - /** - * Selects first clothing item with specified LLWearableType::EType from clothing list - */ - void selectClothing(LLWearableType::EType clothing_type); - - void setAttachmentsTitle(); - -protected: - - void populateAttachmentsAndBodypartsLists(const LLInventoryModel::item_array_t& cof_items); - void populateClothingList(LLAppearanceMgr::wearables_by_type_t& clothing_by_type); - - void addClothingTypesDummies(const LLAppearanceMgr::wearables_by_type_t& clothing_by_type); - void onSelectionChange(LLFlatListView* selected_list); - void onAccordionTabStateChanged(LLUICtrl* ctrl, const LLSD& expanded); - - LLPanelClothingListItem* buildClothingListItem(LLViewerInventoryItem* item, bool first, bool last); - LLPanelBodyPartsListItem* buildBodypartListItem(LLViewerInventoryItem* item); - LLPanelDeletableWearableListItem* buildAttachemntListItem(LLViewerInventoryItem* item); - - void onListRightClick(LLUICtrl* ctrl, S32 x, S32 y, LLListContextMenu* menu); - - LLFlatListView* mAttachments; - LLFlatListView* mClothing; - LLFlatListView* mBodyParts; - - LLFlatListView* mLastSelectedList; - - LLAccordionCtrlTab* mClothingTab; - LLAccordionCtrlTab* mAttachmentsTab; - LLAccordionCtrlTab* mBodyPartsTab; - - LLAccordionCtrlTab* mLastSelectedTab; - - std::map mTab2AssetType; - - LLCOFCallbacks mCOFCallbacks; - - LLListContextMenu* mClothingMenu; - LLListContextMenu* mAttachmentMenu; - LLListContextMenu* mBodyPartMenu; - - LLAccordionCtrl* mAccordionCtrl; - - /* COF category version since last refresh */ - S32 mCOFVersion; -}; - - -#endif +/** + * @file llcofwearables.h + * @brief LLCOFWearables displayes wearables from the current outfit split into three lists (attachments, clothing and body parts) + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLCOFWEARABLES_H +#define LL_LLCOFWEARABLES_H + +// llui +#include "llflatlistview.h" +#include "llpanel.h" + +#include "llappearancemgr.h" +#include "llinventorymodel.h" + +class LLAccordionCtrl; +class LLAccordionCtrlTab; +class LLListContextMenu; +class LLPanelClothingListItem; +class LLPanelBodyPartsListItem; +class LLPanelDeletableWearableListItem; + +class LLCOFWearables : public LLPanel +{ +public: + + /** + * Represents a collection of callbacks assigned to inventory panel item's buttons + */ + class LLCOFCallbacks + { + public: + LLCOFCallbacks() {}; + virtual ~LLCOFCallbacks() {}; + + typedef boost::function cof_callback_t; + + cof_callback_t mAddWearable; + cof_callback_t mMoveWearableCloser; + cof_callback_t mMoveWearableFurther; + cof_callback_t mEditWearable; + cof_callback_t mDeleteWearable; + }; + + + + LLCOFWearables(); + virtual ~LLCOFWearables(); + + /*virtual*/ bool postBuild(); + + LLUUID getSelectedUUID(); + bool getSelectedUUIDs(uuid_vec_t& selected_ids); + + LLPanel* getSelectedItem(); + void getSelectedItems(std::vector& selected_items) const; + + /* Repopulate the COF wearables list if the COF category has been changed since the last refresh */ + void refresh(); + void clear(); + + LLAssetType::EType getExpandedAccordionAssetType(); + LLAssetType::EType getSelectedAccordionAssetType(); + void expandDefaultAccordionTab(); + + LLCOFCallbacks& getCOFCallbacks() { return mCOFCallbacks; } + + /** + * Selects first clothing item with specified LLWearableType::EType from clothing list + */ + void selectClothing(LLWearableType::EType clothing_type); + + void setAttachmentsTitle(); + +protected: + + void populateAttachmentsAndBodypartsLists(const LLInventoryModel::item_array_t& cof_items); + void populateClothingList(LLAppearanceMgr::wearables_by_type_t& clothing_by_type); + + void addClothingTypesDummies(const LLAppearanceMgr::wearables_by_type_t& clothing_by_type); + void onSelectionChange(LLFlatListView* selected_list); + void onAccordionTabStateChanged(LLUICtrl* ctrl, const LLSD& expanded); + + LLPanelClothingListItem* buildClothingListItem(LLViewerInventoryItem* item, bool first, bool last); + LLPanelBodyPartsListItem* buildBodypartListItem(LLViewerInventoryItem* item); + LLPanelDeletableWearableListItem* buildAttachemntListItem(LLViewerInventoryItem* item); + + void onListRightClick(LLUICtrl* ctrl, S32 x, S32 y, LLListContextMenu* menu); + + LLFlatListView* mAttachments; + LLFlatListView* mClothing; + LLFlatListView* mBodyParts; + + LLFlatListView* mLastSelectedList; + + LLAccordionCtrlTab* mClothingTab; + LLAccordionCtrlTab* mAttachmentsTab; + LLAccordionCtrlTab* mBodyPartsTab; + + LLAccordionCtrlTab* mLastSelectedTab; + + std::map mTab2AssetType; + + LLCOFCallbacks mCOFCallbacks; + + LLListContextMenu* mClothingMenu; + LLListContextMenu* mAttachmentMenu; + LLListContextMenu* mBodyPartMenu; + + LLAccordionCtrl* mAccordionCtrl; + + /* COF category version since last refresh */ + S32 mCOFVersion; +}; + + +#endif diff --git a/indra/newview/llcolorswatch.cpp b/indra/newview/llcolorswatch.cpp index 96ab91b76c..97d2345778 100644 --- a/indra/newview/llcolorswatch.cpp +++ b/indra/newview/llcolorswatch.cpp @@ -1,376 +1,376 @@ -/** - * @file llcolorswatch.cpp - * @brief LLColorSwatch class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -// File include -#include "llcolorswatch.h" - -// Linden library includes -#include "v4color.h" -#include "llwindow.h" // setCursor() - -// Project includes -#include "llui.h" -#include "llrender.h" -#include "lluiconstants.h" -#include "llbutton.h" -#include "llfloatercolorpicker.h" -#include "llviewborder.h" -#include "llfocusmgr.h" -#include "lltextbox.h" - -static LLDefaultChildRegistry::Register r("color_swatch"); - -LLColorSwatchCtrl::Params::Params() -: color("color", LLColor4::white), - can_apply_immediately("can_apply_immediately", false), - alpha_background_image("alpha_background_image"), - border_color("border_color"), - label_width("label_width", -1), - label_height("label_height", -1), - caption_text("caption_text"), - border("border") -{ -} - -LLColorSwatchCtrl::LLColorSwatchCtrl(const Params& p) -: LLUICtrl(p), - mValid( true ), - mColor(p.color()), - mCanApplyImmediately(p.can_apply_immediately), - mAlphaGradientImage(p.alpha_background_image), - mOnCancelCallback(p.cancel_callback()), - mOnSelectCallback(p.select_callback()), - mBorderColor(p.border_color()), - mLabelWidth(p.label_width), - mLabelHeight(p.label_height) -{ - LLTextBox::Params tp = p.caption_text; - // use custom label height if it is provided - mLabelHeight = mLabelHeight != -1 ? mLabelHeight : BTN_HEIGHT_SMALL; - // label_width is specified, not -1 - if(mLabelWidth!= -1) - { - tp.rect(LLRect( 0, mLabelHeight, mLabelWidth, 0 )); - } - else - { - tp.rect(LLRect( 0, mLabelHeight, getRect().getWidth(), 0 )); - } - - tp.initial_value(p.label()); - mCaption = LLUICtrlFactory::create(tp); - addChild( mCaption ); - - LLRect border_rect = getLocalRect(); - border_rect.mTop -= 1; - border_rect.mRight -=1; - border_rect.mBottom += mLabelHeight; - - LLViewBorder::Params params = p.border; - params.rect(border_rect); - mBorder = LLUICtrlFactory::create (params); - addChild(mBorder); -} - -LLColorSwatchCtrl::~LLColorSwatchCtrl () -{ - // parent dialog is destroyed so we are too and we need to cancel selection - LLFloaterColorPicker* pickerp = (LLFloaterColorPicker*)mPickerHandle.get(); - if (pickerp) - { - pickerp->cancelSelection(); - pickerp->closeFloater(); - } -} - -bool LLColorSwatchCtrl::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - return handleMouseDown(x, y, mask); -} - -bool LLColorSwatchCtrl::handleHover(S32 x, S32 y, MASK mask) -{ - getWindow()->setCursor(UI_CURSOR_HAND); - return true; -} - -bool LLColorSwatchCtrl::handleUnicodeCharHere(llwchar uni_char) -{ - if( ' ' == uni_char ) - { - showPicker(true); - } - return LLUICtrl::handleUnicodeCharHere(uni_char); -} - -// forces color of this swatch and any associated floater to the input value, if currently invalid -void LLColorSwatchCtrl::setOriginal(const LLColor4& color) -{ - mColor = color; - LLFloaterColorPicker* pickerp = (LLFloaterColorPicker*)mPickerHandle.get(); - if (pickerp) - { - pickerp->setOrigRgb(mColor.mV[VRED], mColor.mV[VGREEN], mColor.mV[VBLUE]); - } -} - -void LLColorSwatchCtrl::set(const LLColor4& color, bool update_picker, bool from_event) -{ - mColor = color; - LLFloaterColorPicker* pickerp = (LLFloaterColorPicker*)mPickerHandle.get(); - if (pickerp && update_picker) - { - pickerp->setCurRgb(mColor.mV[VRED], mColor.mV[VGREEN], mColor.mV[VBLUE]); - } - if (!from_event) - { - setControlValue(mColor.getValue()); - } -} - -void LLColorSwatchCtrl::setLabel(const std::string& label) -{ - mCaption->setText(label); -} - -bool LLColorSwatchCtrl::handleMouseDown(S32 x, S32 y, MASK mask) -{ - // Route future Mouse messages here preemptively. (Release on mouse up.) - // No handler is needed for capture lost since this object has no state that depends on it. - gFocusMgr.setMouseCapture( this ); - - return true; -} - - -bool LLColorSwatchCtrl::handleMouseUp(S32 x, S32 y, MASK mask) -{ - // We only handle the click if the click both started and ended within us - if( hasMouseCapture() ) - { - // Release the mouse - gFocusMgr.setMouseCapture( NULL ); - - // If mouseup in the widget, it's been clicked - if ( pointInView(x, y) ) - { - llassert(getEnabled()); - llassert(getVisible()); - - // Focus the widget now in order to return the focus - // after the color picker is closed. - setFocus(true); - - showPicker(false); - } - } - - return true; -} - -// assumes GL state is set for 2D -void LLColorSwatchCtrl::draw() -{ - // If we're in a focused floater, don't apply the floater's alpha to the color swatch (STORM-676). - F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); - - mBorder->setKeyboardFocusHighlight(hasFocus()); - // Draw border - LLRect border( 0, getRect().getHeight(), getRect().getWidth(), mLabelHeight ); - gl_rect_2d( border, mBorderColor.get(), false ); - - LLRect interior = border; - interior.stretch( -1 ); - - // Check state - if ( mValid ) - { - if (!mColor.isOpaque()) - { - // Draw checker board. - gl_rect_2d_checkerboard(interior, alpha); - } - - // Draw the color swatch - gl_rect_2d(interior, mColor % alpha, true); - - if (!mColor.isOpaque()) - { - // Draw semi-transparent center area in filled with mColor. - LLColor4 opaque_color = mColor; - opaque_color.mV[VALPHA] = alpha; - gGL.color4fv(opaque_color.mV); - if (mAlphaGradientImage.notNull()) - { - gGL.pushMatrix(); - { - mAlphaGradientImage->draw(interior, mColor % alpha); - } - gGL.popMatrix(); - } - } - } - else - { - if (mFallbackImage.notNull()) - { - mFallbackImage->draw(interior.mLeft, interior.mBottom, interior.getWidth(), interior.getHeight(), LLColor4::white % alpha); - } - else - { - // Draw grey and an X - gl_rect_2d(interior, LLColor4::grey % alpha, true); - - gl_draw_x(interior, LLColor4::black % alpha); - } - } - - LLUICtrl::draw(); -} - -void LLColorSwatchCtrl::setEnabled( bool enabled ) -{ - mCaption->setEnabled( enabled ); - LLView::setEnabled( enabled ); - - if (!enabled) - { - LLFloaterColorPicker* pickerp = (LLFloaterColorPicker*)mPickerHandle.get(); - if (pickerp) - { - pickerp->cancelSelection(); - pickerp->closeFloater(); - } - } -} - - -void LLColorSwatchCtrl::setValue(const LLSD& value) -{ - set(LLColor4(value), true, true); -} - -////////////////////////////////////////////////////////////////////////////// -// called (infrequently) when the color changes so the subject of the swatch can be updated. -void LLColorSwatchCtrl::onColorChanged ( void* data, EColorPickOp pick_op ) -{ - LLColorSwatchCtrl* subject = ( LLColorSwatchCtrl* )data; - if ( subject ) - { - LLFloaterColorPicker* pickerp = (LLFloaterColorPicker*)subject->mPickerHandle.get(); - if (pickerp) - { - // move color across from selector to internal widget storage - LLColor4 updatedColor ( pickerp->getCurR (), - pickerp->getCurG (), - pickerp->getCurB (), - subject->mColor.mV[VALPHA] ); // keep current alpha - - bool color_changed = subject->mColor != updatedColor; - if (color_changed) - { - subject->mColor = updatedColor; - subject->setControlValue(updatedColor.getValue()); - } - - if (pick_op == COLOR_CANCEL && subject->mOnCancelCallback) - { - subject->mOnCancelCallback( subject, LLSD()); - } - else if (pick_op == COLOR_SELECT && subject->mOnSelectCallback) - { - subject->mOnSelectCallback( subject, LLSD() ); - } - else - { - // just commit change - subject->onCommit (); - } - - if (pick_op == COLOR_CANCEL || pick_op == COLOR_SELECT) - { - // both select and cancel close LLFloaterColorPicker - // but COLOR_CHANGE does not - subject->setFocus(true); - } - } - } -} - -// This is called when the main floatercustomize panel is closed. -// Since this class has pointers up to its parents, we need to cleanup -// this class first in order to avoid a crash. -void LLColorSwatchCtrl::closeFloaterColorPicker() -{ - LLFloaterColorPicker* pickerp = (LLFloaterColorPicker*)mPickerHandle.get(); - if (pickerp) - { - pickerp->setSwatch(NULL); - pickerp->closeFloater(); - } - - mPickerHandle.markDead(); -} - -void LLColorSwatchCtrl::setValid(bool valid ) -{ - mValid = valid; - - LLFloaterColorPicker* pickerp = (LLFloaterColorPicker*)mPickerHandle.get(); - if (pickerp) - { - pickerp->setActive(valid); - } -} - -void LLColorSwatchCtrl::showPicker(bool take_focus) -{ - LLFloaterColorPicker* pickerp = (LLFloaterColorPicker*)mPickerHandle.get(); - if (!pickerp) - { - pickerp = new LLFloaterColorPicker(this, mCanApplyImmediately); - LLFloater* parent = gFloaterView->getParentFloater(this); - if (parent) - { - parent->addDependentFloater(pickerp); - } - mPickerHandle = pickerp->getHandle(); - } - - // initialize picker with current color - pickerp->initUI ( mColor.mV [ VRED ], mColor.mV [ VGREEN ], mColor.mV [ VBLUE ] ); - - // display it - pickerp->showUI (); - - if (take_focus) - { - pickerp->setFocus(true); - } -} - +/** + * @file llcolorswatch.cpp + * @brief LLColorSwatch class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +// File include +#include "llcolorswatch.h" + +// Linden library includes +#include "v4color.h" +#include "llwindow.h" // setCursor() + +// Project includes +#include "llui.h" +#include "llrender.h" +#include "lluiconstants.h" +#include "llbutton.h" +#include "llfloatercolorpicker.h" +#include "llviewborder.h" +#include "llfocusmgr.h" +#include "lltextbox.h" + +static LLDefaultChildRegistry::Register r("color_swatch"); + +LLColorSwatchCtrl::Params::Params() +: color("color", LLColor4::white), + can_apply_immediately("can_apply_immediately", false), + alpha_background_image("alpha_background_image"), + border_color("border_color"), + label_width("label_width", -1), + label_height("label_height", -1), + caption_text("caption_text"), + border("border") +{ +} + +LLColorSwatchCtrl::LLColorSwatchCtrl(const Params& p) +: LLUICtrl(p), + mValid( true ), + mColor(p.color()), + mCanApplyImmediately(p.can_apply_immediately), + mAlphaGradientImage(p.alpha_background_image), + mOnCancelCallback(p.cancel_callback()), + mOnSelectCallback(p.select_callback()), + mBorderColor(p.border_color()), + mLabelWidth(p.label_width), + mLabelHeight(p.label_height) +{ + LLTextBox::Params tp = p.caption_text; + // use custom label height if it is provided + mLabelHeight = mLabelHeight != -1 ? mLabelHeight : BTN_HEIGHT_SMALL; + // label_width is specified, not -1 + if(mLabelWidth!= -1) + { + tp.rect(LLRect( 0, mLabelHeight, mLabelWidth, 0 )); + } + else + { + tp.rect(LLRect( 0, mLabelHeight, getRect().getWidth(), 0 )); + } + + tp.initial_value(p.label()); + mCaption = LLUICtrlFactory::create(tp); + addChild( mCaption ); + + LLRect border_rect = getLocalRect(); + border_rect.mTop -= 1; + border_rect.mRight -=1; + border_rect.mBottom += mLabelHeight; + + LLViewBorder::Params params = p.border; + params.rect(border_rect); + mBorder = LLUICtrlFactory::create (params); + addChild(mBorder); +} + +LLColorSwatchCtrl::~LLColorSwatchCtrl () +{ + // parent dialog is destroyed so we are too and we need to cancel selection + LLFloaterColorPicker* pickerp = (LLFloaterColorPicker*)mPickerHandle.get(); + if (pickerp) + { + pickerp->cancelSelection(); + pickerp->closeFloater(); + } +} + +bool LLColorSwatchCtrl::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + return handleMouseDown(x, y, mask); +} + +bool LLColorSwatchCtrl::handleHover(S32 x, S32 y, MASK mask) +{ + getWindow()->setCursor(UI_CURSOR_HAND); + return true; +} + +bool LLColorSwatchCtrl::handleUnicodeCharHere(llwchar uni_char) +{ + if( ' ' == uni_char ) + { + showPicker(true); + } + return LLUICtrl::handleUnicodeCharHere(uni_char); +} + +// forces color of this swatch and any associated floater to the input value, if currently invalid +void LLColorSwatchCtrl::setOriginal(const LLColor4& color) +{ + mColor = color; + LLFloaterColorPicker* pickerp = (LLFloaterColorPicker*)mPickerHandle.get(); + if (pickerp) + { + pickerp->setOrigRgb(mColor.mV[VRED], mColor.mV[VGREEN], mColor.mV[VBLUE]); + } +} + +void LLColorSwatchCtrl::set(const LLColor4& color, bool update_picker, bool from_event) +{ + mColor = color; + LLFloaterColorPicker* pickerp = (LLFloaterColorPicker*)mPickerHandle.get(); + if (pickerp && update_picker) + { + pickerp->setCurRgb(mColor.mV[VRED], mColor.mV[VGREEN], mColor.mV[VBLUE]); + } + if (!from_event) + { + setControlValue(mColor.getValue()); + } +} + +void LLColorSwatchCtrl::setLabel(const std::string& label) +{ + mCaption->setText(label); +} + +bool LLColorSwatchCtrl::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // Route future Mouse messages here preemptively. (Release on mouse up.) + // No handler is needed for capture lost since this object has no state that depends on it. + gFocusMgr.setMouseCapture( this ); + + return true; +} + + +bool LLColorSwatchCtrl::handleMouseUp(S32 x, S32 y, MASK mask) +{ + // We only handle the click if the click both started and ended within us + if( hasMouseCapture() ) + { + // Release the mouse + gFocusMgr.setMouseCapture( NULL ); + + // If mouseup in the widget, it's been clicked + if ( pointInView(x, y) ) + { + llassert(getEnabled()); + llassert(getVisible()); + + // Focus the widget now in order to return the focus + // after the color picker is closed. + setFocus(true); + + showPicker(false); + } + } + + return true; +} + +// assumes GL state is set for 2D +void LLColorSwatchCtrl::draw() +{ + // If we're in a focused floater, don't apply the floater's alpha to the color swatch (STORM-676). + F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); + + mBorder->setKeyboardFocusHighlight(hasFocus()); + // Draw border + LLRect border( 0, getRect().getHeight(), getRect().getWidth(), mLabelHeight ); + gl_rect_2d( border, mBorderColor.get(), false ); + + LLRect interior = border; + interior.stretch( -1 ); + + // Check state + if ( mValid ) + { + if (!mColor.isOpaque()) + { + // Draw checker board. + gl_rect_2d_checkerboard(interior, alpha); + } + + // Draw the color swatch + gl_rect_2d(interior, mColor % alpha, true); + + if (!mColor.isOpaque()) + { + // Draw semi-transparent center area in filled with mColor. + LLColor4 opaque_color = mColor; + opaque_color.mV[VALPHA] = alpha; + gGL.color4fv(opaque_color.mV); + if (mAlphaGradientImage.notNull()) + { + gGL.pushMatrix(); + { + mAlphaGradientImage->draw(interior, mColor % alpha); + } + gGL.popMatrix(); + } + } + } + else + { + if (mFallbackImage.notNull()) + { + mFallbackImage->draw(interior.mLeft, interior.mBottom, interior.getWidth(), interior.getHeight(), LLColor4::white % alpha); + } + else + { + // Draw grey and an X + gl_rect_2d(interior, LLColor4::grey % alpha, true); + + gl_draw_x(interior, LLColor4::black % alpha); + } + } + + LLUICtrl::draw(); +} + +void LLColorSwatchCtrl::setEnabled( bool enabled ) +{ + mCaption->setEnabled( enabled ); + LLView::setEnabled( enabled ); + + if (!enabled) + { + LLFloaterColorPicker* pickerp = (LLFloaterColorPicker*)mPickerHandle.get(); + if (pickerp) + { + pickerp->cancelSelection(); + pickerp->closeFloater(); + } + } +} + + +void LLColorSwatchCtrl::setValue(const LLSD& value) +{ + set(LLColor4(value), true, true); +} + +////////////////////////////////////////////////////////////////////////////// +// called (infrequently) when the color changes so the subject of the swatch can be updated. +void LLColorSwatchCtrl::onColorChanged ( void* data, EColorPickOp pick_op ) +{ + LLColorSwatchCtrl* subject = ( LLColorSwatchCtrl* )data; + if ( subject ) + { + LLFloaterColorPicker* pickerp = (LLFloaterColorPicker*)subject->mPickerHandle.get(); + if (pickerp) + { + // move color across from selector to internal widget storage + LLColor4 updatedColor ( pickerp->getCurR (), + pickerp->getCurG (), + pickerp->getCurB (), + subject->mColor.mV[VALPHA] ); // keep current alpha + + bool color_changed = subject->mColor != updatedColor; + if (color_changed) + { + subject->mColor = updatedColor; + subject->setControlValue(updatedColor.getValue()); + } + + if (pick_op == COLOR_CANCEL && subject->mOnCancelCallback) + { + subject->mOnCancelCallback( subject, LLSD()); + } + else if (pick_op == COLOR_SELECT && subject->mOnSelectCallback) + { + subject->mOnSelectCallback( subject, LLSD() ); + } + else + { + // just commit change + subject->onCommit (); + } + + if (pick_op == COLOR_CANCEL || pick_op == COLOR_SELECT) + { + // both select and cancel close LLFloaterColorPicker + // but COLOR_CHANGE does not + subject->setFocus(true); + } + } + } +} + +// This is called when the main floatercustomize panel is closed. +// Since this class has pointers up to its parents, we need to cleanup +// this class first in order to avoid a crash. +void LLColorSwatchCtrl::closeFloaterColorPicker() +{ + LLFloaterColorPicker* pickerp = (LLFloaterColorPicker*)mPickerHandle.get(); + if (pickerp) + { + pickerp->setSwatch(NULL); + pickerp->closeFloater(); + } + + mPickerHandle.markDead(); +} + +void LLColorSwatchCtrl::setValid(bool valid ) +{ + mValid = valid; + + LLFloaterColorPicker* pickerp = (LLFloaterColorPicker*)mPickerHandle.get(); + if (pickerp) + { + pickerp->setActive(valid); + } +} + +void LLColorSwatchCtrl::showPicker(bool take_focus) +{ + LLFloaterColorPicker* pickerp = (LLFloaterColorPicker*)mPickerHandle.get(); + if (!pickerp) + { + pickerp = new LLFloaterColorPicker(this, mCanApplyImmediately); + LLFloater* parent = gFloaterView->getParentFloater(this); + if (parent) + { + parent->addDependentFloater(pickerp); + } + mPickerHandle = pickerp->getHandle(); + } + + // initialize picker with current color + pickerp->initUI ( mColor.mV [ VRED ], mColor.mV [ VGREEN ], mColor.mV [ VBLUE ] ); + + // display it + pickerp->showUI (); + + if (take_focus) + { + pickerp->setFocus(true); + } +} + diff --git a/indra/newview/llcolorswatch.h b/indra/newview/llcolorswatch.h index b787600b5c..2ef715d648 100644 --- a/indra/newview/llcolorswatch.h +++ b/indra/newview/llcolorswatch.h @@ -1,121 +1,121 @@ -/** - * @file llcolorswatch.h - * @brief LLColorSwatch class definition - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLCOLORSWATCH_H -#define LL_LLCOLORSWATCH_H - -#include "lluictrl.h" -#include "v4color.h" -#include "llfloater.h" -#include "lltextbox.h" - -// -// Classes -// -class LLColor4; - -class LLColorSwatchCtrl -: public LLUICtrl -{ -public: - typedef enum e_color_pick_op - { - COLOR_CHANGE, - COLOR_SELECT, - COLOR_CANCEL - } EColorPickOp; - - struct Params : public LLInitParam::Block - { - Optional color; - Optional can_apply_immediately; - Optional alpha_background_image; - Optional cancel_callback; - Optional select_callback; - Optional preview_callback; - Optional border_color; - Optional label_width; - Optional label_height; - - Optional caption_text; - Optional border; - Params(); - }; - -protected: - LLColorSwatchCtrl(const Params& p); - friend class LLUICtrlFactory; -public: - ~LLColorSwatchCtrl (); - - /*virtual*/ void setValue(const LLSD& value); - - /*virtual*/ LLSD getValue() const { return mColor.getValue(); } - const LLColor4& get() { return mColor; } - - void set(const LLColor4& color, bool update_picker = false, bool from_event = false); - void setOriginal(const LLColor4& color); - void setValid(bool valid); - void setLabel(const std::string& label); - void setLabelWidth(S32 label_width) {mLabelWidth =label_width;} - void setCanApplyImmediately(bool apply) { mCanApplyImmediately = apply; } - void setOnCancelCallback(commit_callback_t cb) { mOnCancelCallback = cb; } - void setOnSelectCallback(commit_callback_t cb) { mOnSelectCallback = cb; } - void setPreviewCallback(commit_callback_t cb) { mPreviewCallback = cb; } - void setFallbackImage(LLPointer image) { mFallbackImage = image; } - - void showPicker(bool take_focus); - - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleDoubleClick(S32 x,S32 y,MASK mask); - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleUnicodeCharHere(llwchar uni_char); - /*virtual*/ void draw(); - /*virtual*/ void setEnabled( bool enabled ); - - static void onColorChanged ( void* data, EColorPickOp pick_op = COLOR_CHANGE ); - void closeFloaterColorPicker(); - -protected: - bool mValid; - LLColor4 mColor; - LLUIColor mBorderColor; - LLTextBox* mCaption; - LLHandle mPickerHandle; - class LLViewBorder* mBorder; - bool mCanApplyImmediately; - commit_callback_t mOnCancelCallback, - mOnSelectCallback; - commit_callback_t mPreviewCallback; - S32 mLabelWidth, - mLabelHeight; - - LLPointer mAlphaGradientImage; - LLPointer mFallbackImage; -}; - -#endif // LL_LLBUTTON_H +/** + * @file llcolorswatch.h + * @brief LLColorSwatch class definition + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLCOLORSWATCH_H +#define LL_LLCOLORSWATCH_H + +#include "lluictrl.h" +#include "v4color.h" +#include "llfloater.h" +#include "lltextbox.h" + +// +// Classes +// +class LLColor4; + +class LLColorSwatchCtrl +: public LLUICtrl +{ +public: + typedef enum e_color_pick_op + { + COLOR_CHANGE, + COLOR_SELECT, + COLOR_CANCEL + } EColorPickOp; + + struct Params : public LLInitParam::Block + { + Optional color; + Optional can_apply_immediately; + Optional alpha_background_image; + Optional cancel_callback; + Optional select_callback; + Optional preview_callback; + Optional border_color; + Optional label_width; + Optional label_height; + + Optional caption_text; + Optional border; + Params(); + }; + +protected: + LLColorSwatchCtrl(const Params& p); + friend class LLUICtrlFactory; +public: + ~LLColorSwatchCtrl (); + + /*virtual*/ void setValue(const LLSD& value); + + /*virtual*/ LLSD getValue() const { return mColor.getValue(); } + const LLColor4& get() { return mColor; } + + void set(const LLColor4& color, bool update_picker = false, bool from_event = false); + void setOriginal(const LLColor4& color); + void setValid(bool valid); + void setLabel(const std::string& label); + void setLabelWidth(S32 label_width) {mLabelWidth =label_width;} + void setCanApplyImmediately(bool apply) { mCanApplyImmediately = apply; } + void setOnCancelCallback(commit_callback_t cb) { mOnCancelCallback = cb; } + void setOnSelectCallback(commit_callback_t cb) { mOnSelectCallback = cb; } + void setPreviewCallback(commit_callback_t cb) { mPreviewCallback = cb; } + void setFallbackImage(LLPointer image) { mFallbackImage = image; } + + void showPicker(bool take_focus); + + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleDoubleClick(S32 x,S32 y,MASK mask); + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleUnicodeCharHere(llwchar uni_char); + /*virtual*/ void draw(); + /*virtual*/ void setEnabled( bool enabled ); + + static void onColorChanged ( void* data, EColorPickOp pick_op = COLOR_CHANGE ); + void closeFloaterColorPicker(); + +protected: + bool mValid; + LLColor4 mColor; + LLUIColor mBorderColor; + LLTextBox* mCaption; + LLHandle mPickerHandle; + class LLViewBorder* mBorder; + bool mCanApplyImmediately; + commit_callback_t mOnCancelCallback, + mOnSelectCallback; + commit_callback_t mPreviewCallback; + S32 mLabelWidth, + mLabelHeight; + + LLPointer mAlphaGradientImage; + LLPointer mFallbackImage; +}; + +#endif // LL_LLBUTTON_H diff --git a/indra/newview/llcommandlineparser.cpp b/indra/newview/llcommandlineparser.cpp index 62eec9fd5f..5b8d8b5480 100644 --- a/indra/newview/llcommandlineparser.cpp +++ b/indra/newview/llcommandlineparser.cpp @@ -1,755 +1,755 @@ -/** - * @file llcommandlineparser.cpp - * @brief The LLCommandLineParser class definitions - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llcommandlineparser.h" -#include "llexception.h" - -// *NOTE: The boost::lexical_cast generates -// the warning C4701(local used with out assignment) in VC7.1. -// Disable the warning for the boost includes. -#if _MSC_VER -# pragma warning(push) -# pragma warning( disable : 4701 ) -#else -// NOTE: For the other platforms? -#endif - -#include -#include -#include -#include -#include - -#if _MSC_VER -# pragma warning(pop) -#endif - -#include "llsdserialize.h" -#include "llerror.h" -#include "stringize.h" -#include "llexception.h" -#include -#include -#include -#include -#include - -#include "llcontrol.h" - -namespace po = boost::program_options; - -// *NOTE:MEP - Currently the boost object reside in file scope. -// This has a couple of negatives, they are always around and -// there can be only one instance of each. -// The plus is that the boost-ly-ness of this implementation is -// hidden from the rest of the world. -// Its importatnt to realize that multiple LLCommandLineParser objects -// will all have this single repository of option escs and parsed options. -// This could be good or bad, and probably won't matter for most use cases. -namespace -{ - // List of command-line switches that can't map-to settings variables. - // Going forward, we want every new command-line switch to map-to some - // settings variable. This list is used to validate that. - const std::set unmapped_options = boost::assign::list_of - ("help") - ("set") - ("setdefault") - ("settings") - ("sessionsettings") - ("usersessionsettings") - ; - - po::options_description gOptionsDesc; - po::positional_options_description gPositionalOptions; - po::variables_map gVariableMap; - - const LLCommandLineParser::token_vector_t gEmptyValue; - - void read_file_into_string(std::string& str, const std::basic_istream < char >& file) - { - std::ostringstream oss; - oss << file.rdbuf(); - str = oss.str(); - } - - bool gPastLastOption = false; -} - -class LLCLPError : public LLException { -public: - LLCLPError(const std::string& what) : LLException(what) {} -}; - -class LLCLPLastOption : public LLException { -public: - LLCLPLastOption(const std::string& what) : LLException(what) {} -}; - -class LLCLPValue : public po::value_semantic_codecvt_helper -{ - unsigned mMinTokens; - unsigned mMaxTokens; - bool mIsComposing; - typedef boost::function1 notify_callback_t; - notify_callback_t mNotifyCallback; - bool mLastOption; - -public: - LLCLPValue() : - mMinTokens(0), - mMaxTokens(0), - mIsComposing(false), - mLastOption(false) - {} - - virtual ~LLCLPValue() {}; - - void setMinTokens(unsigned c) - { - mMinTokens = c; - } - - void setMaxTokens(unsigned c) - { - mMaxTokens = c; - } - - void setComposing(bool c) - { - mIsComposing = c; - } - - void setLastOption(bool c) - { - mLastOption = c; - } - - void setNotifyCallback(notify_callback_t f) - { - mNotifyCallback = f; - } - - // Overrides to support the value_semantic interface. - virtual std::string name() const - { - const std::string arg("arg"); - const std::string args("args"); - return (max_tokens() > 1) ? args : arg; - } - - virtual unsigned min_tokens() const - { - return mMinTokens; - } - - virtual unsigned max_tokens() const - { - return mMaxTokens; - } - - virtual bool is_composing() const - { - return mIsComposing; - } - - // Needed for boost 1.42 - virtual bool is_required() const - { - return false; // All our command line options are optional. - } - - virtual bool apply_default(boost::any& value_store) const - { - return false; // No defaults. - } - - virtual void notify(const boost::any& value_store) const - { - const LLCommandLineParser::token_vector_t* value = - boost::any_cast(&value_store); - if(mNotifyCallback) - { - mNotifyCallback(*value); - } - } - -protected: - void xparse(boost::any& value_store, - const std::vector& new_tokens) const - { - if(gPastLastOption) - { - LLTHROW(LLCLPLastOption("Don't parse no more!")); - } - - // Error checks. Needed? - if (!value_store.empty() && !is_composing()) - { - LLTHROW(LLCLPError("Non composing value with multiple occurences.")); - } - if (new_tokens.size() < min_tokens() || new_tokens.size() > max_tokens()) - { - LLTHROW(LLCLPError("Illegal number of tokens specified.")); - } - - if(value_store.empty()) - { - value_store = boost::any(LLCommandLineParser::token_vector_t()); - } - LLCommandLineParser::token_vector_t* tv = - boost::any_cast(&value_store); - - for(unsigned i = 0; i < new_tokens.size() && i < mMaxTokens; ++i) - { - tv->push_back(new_tokens[i]); - } - - if(mLastOption) - { - gPastLastOption = true; - } - } -}; - -//---------------------------------------------------------------------------- -// LLCommandLineParser defintions -//---------------------------------------------------------------------------- -void LLCommandLineParser::addOptionDesc(const std::string& option_name, - boost::function1 notify_callback, - unsigned int token_count, - const std::string& description, - const std::string& short_name, - bool composing, - bool positional, - bool last_option) -{ - // Compose the name for boost::po. - // It takes the format "long_name, short name" - const std::string comma(","); - std::string boost_option_name = option_name; - if(short_name != LLStringUtil::null) - { - boost_option_name += comma; - boost_option_name += short_name; - } - - LLCLPValue* value_desc = new LLCLPValue(); - value_desc->setMinTokens(token_count); - value_desc->setMaxTokens(token_count); - value_desc->setComposing(composing); - value_desc->setLastOption(last_option); - - boost::shared_ptr d( - new po::option_description(boost_option_name.c_str(), - value_desc, - description.c_str())); - - if(!notify_callback.empty()) - { - value_desc->setNotifyCallback(notify_callback); - } - - gOptionsDesc.add(d); - - if(positional) - { - gPositionalOptions.add(boost_option_name.c_str(), token_count); - } -} - -bool LLCommandLineParser::parseAndStoreResults(po::command_line_parser& clp) -{ - try - { - clp.options(gOptionsDesc); - clp.positional(gPositionalOptions); - // SNOW-626: Boost 1.42 erroneously added allow_guessing to the default style - // (see http://groups.google.com/group/boost-list/browse_thread/thread/545d7bf98ff9bb16?fwc=2&pli=1) - // Remove allow_guessing from the default style, because that is not allowed - // when we have options that are a prefix of other options (aka, --help and --helperuri). - clp.style((po::command_line_style::default_style & ~po::command_line_style::allow_guessing) - | po::command_line_style::allow_long_disguise); - if(mExtraParser) - { - clp.extra_parser(mExtraParser); - } - - po::basic_parsed_options opts = clp.run(); - po::store(opts, gVariableMap); - } - catch(po::error& e) - { - LL_WARNS() << "Caught Error:" << e.what() << LL_ENDL; - mErrorMsg = e.what(); - return false; - } - catch(LLCLPError& e) - { - LL_WARNS() << "Caught Error:" << e.what() << LL_ENDL; - mErrorMsg = e.what(); - return false; - } - catch(LLCLPLastOption&) - { - // This exception means a token was read after an option - // that must be the last option was reached (see url and slurl options) - - // boost::po will have stored a malformed option. - // All such options will be removed below. - // The last option read, the last_option option, and its value - // are put into the error message. - std::string last_option; - std::string last_value; - for(po::variables_map::iterator i = gVariableMap.begin(); i != gVariableMap.end();) - { - po::variables_map::iterator tempI = i++; - if(tempI->second.empty()) - { - gVariableMap.erase(tempI); - } - else - { - last_option = tempI->first; - LLCommandLineParser::token_vector_t* tv = - boost::any_cast(&(tempI->second.value())); - if(!tv->empty()) - { - last_value = (*tv)[tv->size()-1]; - } - } - } - - // Continue without parsing. - std::ostringstream msg; - msg << "Caught Error: Found options after last option: " - << last_option << " " - << last_value; - - LL_WARNS() << msg.str() << LL_ENDL; - mErrorMsg = msg.str(); - return false; - } - return true; -} - -bool LLCommandLineParser::parseCommandLine(int argc, char **argv) -{ - po::command_line_parser clp(argc, argv); - return parseAndStoreResults(clp); -} - -// TODO: -// - Break out this funky parsing logic into separate method -// - Unit-test it with tests like LLStringUtil::getTokens() (the command-line -// overload that supports quoted tokens) -// - Unless this logic offers significant semantic benefits, replace it with -// LLStringUtil::getTokens(). This would fix a known bug: you cannot --set a -// string-valued variable to the empty string, because empty strings are -// eliminated below. - -bool LLCommandLineParser::parseCommandLineString(const std::string& str) -{ - std::string cmd_line_string(""); - if (!str.empty()) - { - bool add_last_c = true; - S32 last_c_pos = str.size() - 1; //don't get out of bounds on pos+1, last char will be processed separately - for (S32 pos = 0; pos < last_c_pos; ++pos) - { - cmd_line_string.append(&str[pos], 1); - if (str[pos] == '\\') - { - cmd_line_string.append("\\", 1); - if (str[pos + 1] == '\\') - { - ++pos; - add_last_c = (pos != last_c_pos); - } - } - } - if (add_last_c) - { - cmd_line_string.append(&str[last_c_pos], 1); - if (str[last_c_pos] == '\\') - { - cmd_line_string.append("\\", 1); - } - } - } - - std::vector tokens; - try - { - // Split the string content into tokens - const char* escape_chars = "\\"; - const char* separator_chars = "\r\n "; - const char* quote_chars = "\"'"; - boost::escaped_list_separator sep(escape_chars, separator_chars, quote_chars); - boost::tokenizer< boost::escaped_list_separator > tok(cmd_line_string, sep); - // std::copy(tok.begin(), tok.end(), std::back_inserter(tokens)); - for (boost::tokenizer< boost::escaped_list_separator >::iterator i = tok.begin(); - i != tok.end(); - ++i) - { - if (0 != i->size()) - { - tokens.push_back(*i); - } - } - } - catch (...) - { - CRASH_ON_UNHANDLED_EXCEPTION(STRINGIZE("Unexpected crash while parsing: " << str)); - } - - po::command_line_parser clp(tokens); - return parseAndStoreResults(clp); - -} - -bool LLCommandLineParser::parseCommandLineFile(const std::basic_istream < char >& file) -{ - std::string args; - read_file_into_string(args, file); - - return parseCommandLineString(args); -} - -bool LLCommandLineParser::notify() -{ - try - { - po::notify(gVariableMap); - return true; - } - catch (const LLCLPError& e) - { - LL_WARNS() << "Caught Error: " << e.what() << LL_ENDL; - mErrorMsg = e.what(); - return false; - } -} - -void LLCommandLineParser::printOptions() const -{ - for(po::variables_map::iterator i = gVariableMap.begin(); i != gVariableMap.end(); ++i) - { - std::string name = i->first; - token_vector_t values = i->second.as(); - std::ostringstream oss; - oss << name << ": "; - for(token_vector_t::iterator t_itr = values.begin(); t_itr != values.end(); ++t_itr) - { - oss << t_itr->c_str() << " "; - } - LL_INFOS() << oss.str() << LL_ENDL; - } -} - -std::ostream& LLCommandLineParser::printOptionsDesc(std::ostream& os) const -{ - return os << gOptionsDesc; -} - -bool LLCommandLineParser::hasOption(const std::string& name) const -{ - return gVariableMap.count(name) > 0; -} - -const LLCommandLineParser::token_vector_t& LLCommandLineParser::getOption(const std::string& name) const -{ - if(hasOption(name)) - { - return gVariableMap[name].as(); - } - - return gEmptyValue; -} - -//---------------------------------------------------------------------------- -// LLControlGroupCLP defintions -//---------------------------------------------------------------------------- -namespace { -LLCommandLineParser::token_vector_t::value_type -onevalue(const std::string& option, - const LLCommandLineParser::token_vector_t& value) -{ - if (value.empty()) - { - // What does it mean when the user specifies a command-line switch - // that requires a value, but omits the value? Complain. - LLTHROW(LLCLPError(STRINGIZE("No value specified for --" << option << "!"))); - } - else if (value.size() > 1) - { - LL_WARNS() << "Ignoring extra tokens specified for --" - << option << "." << LL_ENDL; - } - return value[0]; -} - -void badvalue(const std::string& option, - const std::string& varname, - const std::string& type, - const std::string& value) -{ - // If the user passes an unusable value for a command-line switch, it - // seems like a really bad idea to just ignore it, even with a log - // warning. - LLTHROW(LLCLPError(STRINGIZE("Invalid value specified by command-line switch '" << option - << "' for variable '" << varname << "' of type " << type - << ": '" << value << "'"))); -} - -template -T convertTo(const std::string& option, - const std::string& varname, - const LLCommandLineParser::token_vector_t::value_type& value) -{ - try - { - return boost::lexical_cast(value); - } - catch (const boost::bad_lexical_cast&) - { - badvalue(option, varname, typeid(T).name(), value); - // bogus return; compiler unaware that badvalue() won't return - return T(); - } -} - -void setControlValueCB(const LLCommandLineParser::token_vector_t& value, - const std::string& option, - LLControlVariable* ctrl) -{ - // *FIX: Do semantic conversion here. - // LLSD (ImplString) Is no good for doing string to type conversion for... - // booleans - // compound types - // ?... - - if(NULL != ctrl) - { - switch(ctrl->type()) - { - case TYPE_BOOLEAN: - if (value.empty()) - { - // Boolean-valued command-line switches are unusual. If you - // simply specify the switch without an explicit value, we can - // infer you mean 'true'. - ctrl->setValue(LLSD(true), false); - } - else - { - // Only call onevalue() AFTER handling value.empty() case! - std::string token(onevalue(option, value)); - - // There's a token. check the string for true/false/1/0 etc. - bool result = false; - bool gotSet = LLStringUtil::convertToBOOL(token, result); - if (gotSet) - { - ctrl->setValue(LLSD(result), false); - } - else - { - badvalue(option, ctrl->getName(), "bool", token); - } - } - break; - - case TYPE_U32: - { - std::string token(onevalue(option, value)); - // To my surprise, for an unsigned target, lexical_cast() doesn't - // complain about an input string such as "-17". In that case, you - // get a very large positive result. So for U32, make sure there's - // no minus sign! - if (token.find('-') == std::string::npos) - { - ctrl->setValue(LLSD::Integer(convertTo(option, ctrl->getName(), token)), - false); - } - else - { - badvalue(option, ctrl->getName(), "unsigned", token); - } - break; - } - - case TYPE_S32: - ctrl->setValue(convertTo(option, ctrl->getName(), - onevalue(option, value)), false); - break; - - case TYPE_F32: - ctrl->setValue(convertTo(option, ctrl->getName(), - onevalue(option, value)), false); - break; - - // It appears that no one has yet tried to define a command-line - // switch mapped to a settings variable of TYPE_VEC3, TYPE_VEC3D, - // TYPE_RECT, TYPE_COL4, TYPE_COL3. Such types would certainly seem to - // call for a bit of special handling here... - default: - { - // For the default types, let llsd do the conversion. - if(value.size() > 1 && ctrl->isType(TYPE_LLSD)) - { - // Assume its an array... - LLSD llsdArray; - for(unsigned int i = 0; i < value.size(); ++i) - { - LLSD llsdValue; - llsdValue.assign(LLSD::String(value[i])); - llsdArray.set(i, llsdValue); - } - - ctrl->setValue(llsdArray, false); - } - else - { - ctrl->setValue(onevalue(option, value), false); - } - } - break; - } - } - else - { - // This isn't anything a user can affect -- it's a misconfiguration on - // the part of the coder. Rub the coder's nose in the problem right - // away so even preliminary testing will surface it. - LL_ERRS() << "Command Line option --" << option - << " maps to unknown setting!" << LL_ENDL; - } -} -} // anonymous namespace - -void LLControlGroupCLP::configure(const std::string& config_filename, LLControlGroup* controlGroup) -{ - // This method reads the llsd based config file, and uses it to set - // members of a control group. - LLSD clpConfigLLSD; - - llifstream input_stream; - input_stream.open(config_filename.c_str(), std::ios::in | std::ios::binary); - - if(input_stream.is_open()) - { - LLSDSerialize::fromXML(clpConfigLLSD, input_stream); - for(LLSD::map_iterator option_itr = clpConfigLLSD.beginMap(); - option_itr != clpConfigLLSD.endMap(); - ++option_itr) - { - LLSD::String long_name = option_itr->first; - LLSD option_params = option_itr->second; - - std::string desc("n/a"); - if(option_params.has("desc")) - { - desc = option_params["desc"].asString(); - } - - std::string short_name = LLStringUtil::null; - if(option_params.has("short")) - { - short_name = option_params["short"].asString(); - } - - unsigned int token_count = 0; - if(option_params.has("count")) - { - token_count = option_params["count"].asInteger(); - } - - bool composing = false; - if(option_params.has("compose")) - { - composing = option_params["compose"].asBoolean(); - } - - bool positional = false; - if(option_params.has("positional")) - { - positional = option_params["positional"].asBoolean(); - } - - bool last_option = false; - if(option_params.has("last_option")) - { - last_option = option_params["last_option"].asBoolean(); - } - - boost::function1 callback; - if (! option_params.has("map-to")) - { - // If this option isn't mapped to a settings variable, is it - // one of the ones for which that's unreasonable, or did - // someone carelessly add a new option? (Make all these - // configuration errors fatal so a maintainer will catch them - // right away.) - std::set::const_iterator found = unmapped_options.find(long_name); - if (found == unmapped_options.end()) - { - LL_ERRS() << "New command-line option " << long_name - << " should map-to a variable in settings.xml" << LL_ENDL; - } - } - else // option specifies map-to - { - std::string controlName = option_params["map-to"].asString(); - if (! controlGroup) - { - LL_ERRS() << "Must pass gSavedSettings to LLControlGroupCLP::configure() for " - << long_name << " (map-to " << controlName << ")" << LL_ENDL; - } - - LLControlVariable* ctrl = controlGroup->getControl(controlName); - if (! ctrl) - { - LL_ERRS() << "Option " << long_name << " specifies map-to " << controlName - << " which does not exist" << LL_ENDL; - } - - callback = boost::bind(setControlValueCB, _1, long_name, ctrl); - } - - this->addOptionDesc( - long_name, - callback, - token_count, - desc, - short_name, - composing, - positional, - last_option); - } - } -} +/** + * @file llcommandlineparser.cpp + * @brief The LLCommandLineParser class definitions + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llcommandlineparser.h" +#include "llexception.h" + +// *NOTE: The boost::lexical_cast generates +// the warning C4701(local used with out assignment) in VC7.1. +// Disable the warning for the boost includes. +#if _MSC_VER +# pragma warning(push) +# pragma warning( disable : 4701 ) +#else +// NOTE: For the other platforms? +#endif + +#include +#include +#include +#include +#include + +#if _MSC_VER +# pragma warning(pop) +#endif + +#include "llsdserialize.h" +#include "llerror.h" +#include "stringize.h" +#include "llexception.h" +#include +#include +#include +#include +#include + +#include "llcontrol.h" + +namespace po = boost::program_options; + +// *NOTE:MEP - Currently the boost object reside in file scope. +// This has a couple of negatives, they are always around and +// there can be only one instance of each. +// The plus is that the boost-ly-ness of this implementation is +// hidden from the rest of the world. +// Its importatnt to realize that multiple LLCommandLineParser objects +// will all have this single repository of option escs and parsed options. +// This could be good or bad, and probably won't matter for most use cases. +namespace +{ + // List of command-line switches that can't map-to settings variables. + // Going forward, we want every new command-line switch to map-to some + // settings variable. This list is used to validate that. + const std::set unmapped_options = boost::assign::list_of + ("help") + ("set") + ("setdefault") + ("settings") + ("sessionsettings") + ("usersessionsettings") + ; + + po::options_description gOptionsDesc; + po::positional_options_description gPositionalOptions; + po::variables_map gVariableMap; + + const LLCommandLineParser::token_vector_t gEmptyValue; + + void read_file_into_string(std::string& str, const std::basic_istream < char >& file) + { + std::ostringstream oss; + oss << file.rdbuf(); + str = oss.str(); + } + + bool gPastLastOption = false; +} + +class LLCLPError : public LLException { +public: + LLCLPError(const std::string& what) : LLException(what) {} +}; + +class LLCLPLastOption : public LLException { +public: + LLCLPLastOption(const std::string& what) : LLException(what) {} +}; + +class LLCLPValue : public po::value_semantic_codecvt_helper +{ + unsigned mMinTokens; + unsigned mMaxTokens; + bool mIsComposing; + typedef boost::function1 notify_callback_t; + notify_callback_t mNotifyCallback; + bool mLastOption; + +public: + LLCLPValue() : + mMinTokens(0), + mMaxTokens(0), + mIsComposing(false), + mLastOption(false) + {} + + virtual ~LLCLPValue() {}; + + void setMinTokens(unsigned c) + { + mMinTokens = c; + } + + void setMaxTokens(unsigned c) + { + mMaxTokens = c; + } + + void setComposing(bool c) + { + mIsComposing = c; + } + + void setLastOption(bool c) + { + mLastOption = c; + } + + void setNotifyCallback(notify_callback_t f) + { + mNotifyCallback = f; + } + + // Overrides to support the value_semantic interface. + virtual std::string name() const + { + const std::string arg("arg"); + const std::string args("args"); + return (max_tokens() > 1) ? args : arg; + } + + virtual unsigned min_tokens() const + { + return mMinTokens; + } + + virtual unsigned max_tokens() const + { + return mMaxTokens; + } + + virtual bool is_composing() const + { + return mIsComposing; + } + + // Needed for boost 1.42 + virtual bool is_required() const + { + return false; // All our command line options are optional. + } + + virtual bool apply_default(boost::any& value_store) const + { + return false; // No defaults. + } + + virtual void notify(const boost::any& value_store) const + { + const LLCommandLineParser::token_vector_t* value = + boost::any_cast(&value_store); + if(mNotifyCallback) + { + mNotifyCallback(*value); + } + } + +protected: + void xparse(boost::any& value_store, + const std::vector& new_tokens) const + { + if(gPastLastOption) + { + LLTHROW(LLCLPLastOption("Don't parse no more!")); + } + + // Error checks. Needed? + if (!value_store.empty() && !is_composing()) + { + LLTHROW(LLCLPError("Non composing value with multiple occurences.")); + } + if (new_tokens.size() < min_tokens() || new_tokens.size() > max_tokens()) + { + LLTHROW(LLCLPError("Illegal number of tokens specified.")); + } + + if(value_store.empty()) + { + value_store = boost::any(LLCommandLineParser::token_vector_t()); + } + LLCommandLineParser::token_vector_t* tv = + boost::any_cast(&value_store); + + for(unsigned i = 0; i < new_tokens.size() && i < mMaxTokens; ++i) + { + tv->push_back(new_tokens[i]); + } + + if(mLastOption) + { + gPastLastOption = true; + } + } +}; + +//---------------------------------------------------------------------------- +// LLCommandLineParser defintions +//---------------------------------------------------------------------------- +void LLCommandLineParser::addOptionDesc(const std::string& option_name, + boost::function1 notify_callback, + unsigned int token_count, + const std::string& description, + const std::string& short_name, + bool composing, + bool positional, + bool last_option) +{ + // Compose the name for boost::po. + // It takes the format "long_name, short name" + const std::string comma(","); + std::string boost_option_name = option_name; + if(short_name != LLStringUtil::null) + { + boost_option_name += comma; + boost_option_name += short_name; + } + + LLCLPValue* value_desc = new LLCLPValue(); + value_desc->setMinTokens(token_count); + value_desc->setMaxTokens(token_count); + value_desc->setComposing(composing); + value_desc->setLastOption(last_option); + + boost::shared_ptr d( + new po::option_description(boost_option_name.c_str(), + value_desc, + description.c_str())); + + if(!notify_callback.empty()) + { + value_desc->setNotifyCallback(notify_callback); + } + + gOptionsDesc.add(d); + + if(positional) + { + gPositionalOptions.add(boost_option_name.c_str(), token_count); + } +} + +bool LLCommandLineParser::parseAndStoreResults(po::command_line_parser& clp) +{ + try + { + clp.options(gOptionsDesc); + clp.positional(gPositionalOptions); + // SNOW-626: Boost 1.42 erroneously added allow_guessing to the default style + // (see http://groups.google.com/group/boost-list/browse_thread/thread/545d7bf98ff9bb16?fwc=2&pli=1) + // Remove allow_guessing from the default style, because that is not allowed + // when we have options that are a prefix of other options (aka, --help and --helperuri). + clp.style((po::command_line_style::default_style & ~po::command_line_style::allow_guessing) + | po::command_line_style::allow_long_disguise); + if(mExtraParser) + { + clp.extra_parser(mExtraParser); + } + + po::basic_parsed_options opts = clp.run(); + po::store(opts, gVariableMap); + } + catch(po::error& e) + { + LL_WARNS() << "Caught Error:" << e.what() << LL_ENDL; + mErrorMsg = e.what(); + return false; + } + catch(LLCLPError& e) + { + LL_WARNS() << "Caught Error:" << e.what() << LL_ENDL; + mErrorMsg = e.what(); + return false; + } + catch(LLCLPLastOption&) + { + // This exception means a token was read after an option + // that must be the last option was reached (see url and slurl options) + + // boost::po will have stored a malformed option. + // All such options will be removed below. + // The last option read, the last_option option, and its value + // are put into the error message. + std::string last_option; + std::string last_value; + for(po::variables_map::iterator i = gVariableMap.begin(); i != gVariableMap.end();) + { + po::variables_map::iterator tempI = i++; + if(tempI->second.empty()) + { + gVariableMap.erase(tempI); + } + else + { + last_option = tempI->first; + LLCommandLineParser::token_vector_t* tv = + boost::any_cast(&(tempI->second.value())); + if(!tv->empty()) + { + last_value = (*tv)[tv->size()-1]; + } + } + } + + // Continue without parsing. + std::ostringstream msg; + msg << "Caught Error: Found options after last option: " + << last_option << " " + << last_value; + + LL_WARNS() << msg.str() << LL_ENDL; + mErrorMsg = msg.str(); + return false; + } + return true; +} + +bool LLCommandLineParser::parseCommandLine(int argc, char **argv) +{ + po::command_line_parser clp(argc, argv); + return parseAndStoreResults(clp); +} + +// TODO: +// - Break out this funky parsing logic into separate method +// - Unit-test it with tests like LLStringUtil::getTokens() (the command-line +// overload that supports quoted tokens) +// - Unless this logic offers significant semantic benefits, replace it with +// LLStringUtil::getTokens(). This would fix a known bug: you cannot --set a +// string-valued variable to the empty string, because empty strings are +// eliminated below. + +bool LLCommandLineParser::parseCommandLineString(const std::string& str) +{ + std::string cmd_line_string(""); + if (!str.empty()) + { + bool add_last_c = true; + S32 last_c_pos = str.size() - 1; //don't get out of bounds on pos+1, last char will be processed separately + for (S32 pos = 0; pos < last_c_pos; ++pos) + { + cmd_line_string.append(&str[pos], 1); + if (str[pos] == '\\') + { + cmd_line_string.append("\\", 1); + if (str[pos + 1] == '\\') + { + ++pos; + add_last_c = (pos != last_c_pos); + } + } + } + if (add_last_c) + { + cmd_line_string.append(&str[last_c_pos], 1); + if (str[last_c_pos] == '\\') + { + cmd_line_string.append("\\", 1); + } + } + } + + std::vector tokens; + try + { + // Split the string content into tokens + const char* escape_chars = "\\"; + const char* separator_chars = "\r\n "; + const char* quote_chars = "\"'"; + boost::escaped_list_separator sep(escape_chars, separator_chars, quote_chars); + boost::tokenizer< boost::escaped_list_separator > tok(cmd_line_string, sep); + // std::copy(tok.begin(), tok.end(), std::back_inserter(tokens)); + for (boost::tokenizer< boost::escaped_list_separator >::iterator i = tok.begin(); + i != tok.end(); + ++i) + { + if (0 != i->size()) + { + tokens.push_back(*i); + } + } + } + catch (...) + { + CRASH_ON_UNHANDLED_EXCEPTION(STRINGIZE("Unexpected crash while parsing: " << str)); + } + + po::command_line_parser clp(tokens); + return parseAndStoreResults(clp); + +} + +bool LLCommandLineParser::parseCommandLineFile(const std::basic_istream < char >& file) +{ + std::string args; + read_file_into_string(args, file); + + return parseCommandLineString(args); +} + +bool LLCommandLineParser::notify() +{ + try + { + po::notify(gVariableMap); + return true; + } + catch (const LLCLPError& e) + { + LL_WARNS() << "Caught Error: " << e.what() << LL_ENDL; + mErrorMsg = e.what(); + return false; + } +} + +void LLCommandLineParser::printOptions() const +{ + for(po::variables_map::iterator i = gVariableMap.begin(); i != gVariableMap.end(); ++i) + { + std::string name = i->first; + token_vector_t values = i->second.as(); + std::ostringstream oss; + oss << name << ": "; + for(token_vector_t::iterator t_itr = values.begin(); t_itr != values.end(); ++t_itr) + { + oss << t_itr->c_str() << " "; + } + LL_INFOS() << oss.str() << LL_ENDL; + } +} + +std::ostream& LLCommandLineParser::printOptionsDesc(std::ostream& os) const +{ + return os << gOptionsDesc; +} + +bool LLCommandLineParser::hasOption(const std::string& name) const +{ + return gVariableMap.count(name) > 0; +} + +const LLCommandLineParser::token_vector_t& LLCommandLineParser::getOption(const std::string& name) const +{ + if(hasOption(name)) + { + return gVariableMap[name].as(); + } + + return gEmptyValue; +} + +//---------------------------------------------------------------------------- +// LLControlGroupCLP defintions +//---------------------------------------------------------------------------- +namespace { +LLCommandLineParser::token_vector_t::value_type +onevalue(const std::string& option, + const LLCommandLineParser::token_vector_t& value) +{ + if (value.empty()) + { + // What does it mean when the user specifies a command-line switch + // that requires a value, but omits the value? Complain. + LLTHROW(LLCLPError(STRINGIZE("No value specified for --" << option << "!"))); + } + else if (value.size() > 1) + { + LL_WARNS() << "Ignoring extra tokens specified for --" + << option << "." << LL_ENDL; + } + return value[0]; +} + +void badvalue(const std::string& option, + const std::string& varname, + const std::string& type, + const std::string& value) +{ + // If the user passes an unusable value for a command-line switch, it + // seems like a really bad idea to just ignore it, even with a log + // warning. + LLTHROW(LLCLPError(STRINGIZE("Invalid value specified by command-line switch '" << option + << "' for variable '" << varname << "' of type " << type + << ": '" << value << "'"))); +} + +template +T convertTo(const std::string& option, + const std::string& varname, + const LLCommandLineParser::token_vector_t::value_type& value) +{ + try + { + return boost::lexical_cast(value); + } + catch (const boost::bad_lexical_cast&) + { + badvalue(option, varname, typeid(T).name(), value); + // bogus return; compiler unaware that badvalue() won't return + return T(); + } +} + +void setControlValueCB(const LLCommandLineParser::token_vector_t& value, + const std::string& option, + LLControlVariable* ctrl) +{ + // *FIX: Do semantic conversion here. + // LLSD (ImplString) Is no good for doing string to type conversion for... + // booleans + // compound types + // ?... + + if(NULL != ctrl) + { + switch(ctrl->type()) + { + case TYPE_BOOLEAN: + if (value.empty()) + { + // Boolean-valued command-line switches are unusual. If you + // simply specify the switch without an explicit value, we can + // infer you mean 'true'. + ctrl->setValue(LLSD(true), false); + } + else + { + // Only call onevalue() AFTER handling value.empty() case! + std::string token(onevalue(option, value)); + + // There's a token. check the string for true/false/1/0 etc. + bool result = false; + bool gotSet = LLStringUtil::convertToBOOL(token, result); + if (gotSet) + { + ctrl->setValue(LLSD(result), false); + } + else + { + badvalue(option, ctrl->getName(), "bool", token); + } + } + break; + + case TYPE_U32: + { + std::string token(onevalue(option, value)); + // To my surprise, for an unsigned target, lexical_cast() doesn't + // complain about an input string such as "-17". In that case, you + // get a very large positive result. So for U32, make sure there's + // no minus sign! + if (token.find('-') == std::string::npos) + { + ctrl->setValue(LLSD::Integer(convertTo(option, ctrl->getName(), token)), + false); + } + else + { + badvalue(option, ctrl->getName(), "unsigned", token); + } + break; + } + + case TYPE_S32: + ctrl->setValue(convertTo(option, ctrl->getName(), + onevalue(option, value)), false); + break; + + case TYPE_F32: + ctrl->setValue(convertTo(option, ctrl->getName(), + onevalue(option, value)), false); + break; + + // It appears that no one has yet tried to define a command-line + // switch mapped to a settings variable of TYPE_VEC3, TYPE_VEC3D, + // TYPE_RECT, TYPE_COL4, TYPE_COL3. Such types would certainly seem to + // call for a bit of special handling here... + default: + { + // For the default types, let llsd do the conversion. + if(value.size() > 1 && ctrl->isType(TYPE_LLSD)) + { + // Assume its an array... + LLSD llsdArray; + for(unsigned int i = 0; i < value.size(); ++i) + { + LLSD llsdValue; + llsdValue.assign(LLSD::String(value[i])); + llsdArray.set(i, llsdValue); + } + + ctrl->setValue(llsdArray, false); + } + else + { + ctrl->setValue(onevalue(option, value), false); + } + } + break; + } + } + else + { + // This isn't anything a user can affect -- it's a misconfiguration on + // the part of the coder. Rub the coder's nose in the problem right + // away so even preliminary testing will surface it. + LL_ERRS() << "Command Line option --" << option + << " maps to unknown setting!" << LL_ENDL; + } +} +} // anonymous namespace + +void LLControlGroupCLP::configure(const std::string& config_filename, LLControlGroup* controlGroup) +{ + // This method reads the llsd based config file, and uses it to set + // members of a control group. + LLSD clpConfigLLSD; + + llifstream input_stream; + input_stream.open(config_filename.c_str(), std::ios::in | std::ios::binary); + + if(input_stream.is_open()) + { + LLSDSerialize::fromXML(clpConfigLLSD, input_stream); + for(LLSD::map_iterator option_itr = clpConfigLLSD.beginMap(); + option_itr != clpConfigLLSD.endMap(); + ++option_itr) + { + LLSD::String long_name = option_itr->first; + LLSD option_params = option_itr->second; + + std::string desc("n/a"); + if(option_params.has("desc")) + { + desc = option_params["desc"].asString(); + } + + std::string short_name = LLStringUtil::null; + if(option_params.has("short")) + { + short_name = option_params["short"].asString(); + } + + unsigned int token_count = 0; + if(option_params.has("count")) + { + token_count = option_params["count"].asInteger(); + } + + bool composing = false; + if(option_params.has("compose")) + { + composing = option_params["compose"].asBoolean(); + } + + bool positional = false; + if(option_params.has("positional")) + { + positional = option_params["positional"].asBoolean(); + } + + bool last_option = false; + if(option_params.has("last_option")) + { + last_option = option_params["last_option"].asBoolean(); + } + + boost::function1 callback; + if (! option_params.has("map-to")) + { + // If this option isn't mapped to a settings variable, is it + // one of the ones for which that's unreasonable, or did + // someone carelessly add a new option? (Make all these + // configuration errors fatal so a maintainer will catch them + // right away.) + std::set::const_iterator found = unmapped_options.find(long_name); + if (found == unmapped_options.end()) + { + LL_ERRS() << "New command-line option " << long_name + << " should map-to a variable in settings.xml" << LL_ENDL; + } + } + else // option specifies map-to + { + std::string controlName = option_params["map-to"].asString(); + if (! controlGroup) + { + LL_ERRS() << "Must pass gSavedSettings to LLControlGroupCLP::configure() for " + << long_name << " (map-to " << controlName << ")" << LL_ENDL; + } + + LLControlVariable* ctrl = controlGroup->getControl(controlName); + if (! ctrl) + { + LL_ERRS() << "Option " << long_name << " specifies map-to " << controlName + << " which does not exist" << LL_ENDL; + } + + callback = boost::bind(setControlValueCB, _1, long_name, ctrl); + } + + this->addOptionDesc( + long_name, + callback, + token_count, + desc, + short_name, + composing, + positional, + last_option); + } + } +} diff --git a/indra/newview/llcompilequeue.cpp b/indra/newview/llcompilequeue.cpp index 906c61c535..552ea75559 100644 --- a/indra/newview/llcompilequeue.cpp +++ b/indra/newview/llcompilequeue.cpp @@ -1,826 +1,826 @@ -/** - * @file llcompilequeue.cpp - * @brief LLCompileQueueData class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/** - * - * Implementation of the script queue which keeps an array of object - * UUIDs and manipulates all of the scripts on each of them. - * - */ - - -#include "llviewerprecompiledheaders.h" - -#include "llcompilequeue.h" - -#include "llagent.h" -#include "llchat.h" -#include "llfloaterreg.h" -#include "llviewerwindow.h" -#include "llviewerobject.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llviewercontrol.h" -#include "llviewerobject.h" -#include "llviewerregion.h" -#include "llresmgr.h" - -#include "llbutton.h" -#include "lldir.h" -#include "llnotificationsutil.h" -#include "llviewerstats.h" -#include "llfilesystem.h" -#include "lluictrlfactory.h" -#include "lltrans.h" - -#include "llselectmgr.h" -#include "llexperiencecache.h" - -#include "llviewerassetupload.h" -#include "llcorehttputil.h" - -namespace -{ - - const std::string QUEUE_EVENTPUMP_NAME("ScriptActionQueue"); - const F32 QUEUE_INVENTORY_FETCH_TIMEOUT = 300.f; - - // ObjectIventoryFetcher is an adapter between the LLVOInventoryListener::inventoryChanged - // callback mechanism and the LLEventPump coroutine architecture allowing the - // coroutine to wait for the inventory event. - class ObjectInventoryFetcher: public LLVOInventoryListener - { - public: - typedef std::shared_ptr ptr_t; - - ObjectInventoryFetcher(LLEventPump &pump, LLViewerObject* object, void* user_data) : - mPump(pump), - LLVOInventoryListener() - { - registerVOInventoryListener(object, this); - } - - virtual void inventoryChanged(LLViewerObject* object, - LLInventoryObject::object_list_t* inventory, - S32 serial_num, - void* user_data); - - void fetchInventory() - { - requestVOInventory(); - } - - const LLInventoryObject::object_list_t & getInventoryList() const { return mInventoryList; } - - private: - LLInventoryObject::object_list_t mInventoryList; - LLEventPump & mPump; - }; - - class HandleScriptUserData - { - public: - HandleScriptUserData(const std::string &pumpname) : - mPumpname(pumpname) - { } - - const std::string &getPumpName() const { return mPumpname; } - - private: - std::string mPumpname; - }; - - -} - -// *NOTE$: A minor specialization of LLScriptAssetUpload, it does not require a buffer -// (and does not save a buffer to the cache) and it finds the compile queue window and -// displays a compiling message. -class LLQueuedScriptAssetUpload : public LLScriptAssetUpload -{ -public: - LLQueuedScriptAssetUpload(LLUUID taskId, LLUUID itemId, LLUUID assetId, TargetType_t targetType, - bool isRunning, std::string scriptName, LLUUID queueId, LLUUID exerienceId, taskUploadFinish_f finish) : - LLScriptAssetUpload(taskId, itemId, targetType, isRunning, - exerienceId, std::string(), finish, nullptr), - mScriptName(scriptName), - mQueueId(queueId) - { - setAssetId(assetId); - } - - virtual LLSD prepareUpload() - { - /* *NOTE$: The parent class (LLScriptAssetUpload will attempt to save - * the script buffer into to the cache. Since the resource is already in - * the cache we don't want to do that. Just put a compiling message in - * the window and move on - */ - LLFloaterCompileQueue* queue = LLFloaterReg::findTypedInstance("compile_queue", LLSD(mQueueId)); - if (queue) - { - std::string message = std::string("Compiling \"") + getScriptName() + std::string("\"..."); - - queue->getChild("queue output")->addSimpleElement(message, ADD_BOTTOM); - } - - return LLSDMap("success", LLSD::Boolean(true)); - } - - - std::string getScriptName() const { return mScriptName; } - -private: - void setScriptName(const std::string &scriptName) { mScriptName = scriptName; } - - LLUUID mQueueId; - std::string mScriptName; -}; - -///---------------------------------------------------------------------------- -/// Local function declarations, constants, enums, and typedefs -///---------------------------------------------------------------------------- - -struct LLScriptQueueData -{ - LLUUID mQueueID; - LLUUID mTaskId; - LLPointer mItem; - LLHost mHost; - LLUUID mExperienceId; - std::string mExperiencename; - LLScriptQueueData(const LLUUID& q_id, const LLUUID& task_id, LLInventoryItem* item) : - mQueueID(q_id), mTaskId(task_id), mItem(new LLInventoryItem(item)) {} - -}; - -///---------------------------------------------------------------------------- -/// Class LLFloaterScriptQueue -///---------------------------------------------------------------------------- - -// Default constructor -LLFloaterScriptQueue::LLFloaterScriptQueue(const LLSD& key) : - LLFloater(key), - mDone(false), - mMono(false) -{ - -} - -// Destroys the object -LLFloaterScriptQueue::~LLFloaterScriptQueue() -{ -} - -bool LLFloaterScriptQueue::postBuild() -{ - childSetAction("close",onCloseBtn,this); - getChildView("close")->setEnabled(false); - setVisible(true); - return true; -} - -// static -void LLFloaterScriptQueue::onCloseBtn(void* user_data) -{ - LLFloaterScriptQueue* self = (LLFloaterScriptQueue*)user_data; - self->closeFloater(); -} - -void LLFloaterScriptQueue::addObject(const LLUUID& id, std::string name) -{ - ObjectData obj = { id, name }; - mObjectList.push_back(obj); -} - -bool LLFloaterScriptQueue::start() -{ - std::string buffer; - - LLStringUtil::format_map_t args; - args["[START]"] = mStartString; - args["[COUNT]"] = llformat ("%d", mObjectList.size()); - buffer = getString ("Starting", args); - - getChild("queue output")->addSimpleElement(buffer, ADD_BOTTOM); - - return startQueue(); -} - -void LLFloaterScriptQueue::addProcessingMessage(const std::string &message, const LLSD &args) -{ - std::string buffer(LLTrans::getString(message, args)); - - getChild("queue output")->addSimpleElement(buffer, ADD_BOTTOM); -} - -void LLFloaterScriptQueue::addStringMessage(const std::string &message) -{ - getChild("queue output")->addSimpleElement(message, ADD_BOTTOM); -} - - -bool LLFloaterScriptQueue::isDone() const -{ - return (mCurrentObjectID.isNull() && (mObjectList.size() == 0)); -} - -///---------------------------------------------------------------------------- -/// Class LLFloaterCompileQueue -///---------------------------------------------------------------------------- -LLFloaterCompileQueue::LLFloaterCompileQueue(const LLSD& key) - : LLFloaterScriptQueue(key) -{ - setTitle(LLTrans::getString("CompileQueueTitle")); - setStartString(LLTrans::getString("CompileQueueStart")); - -} - -LLFloaterCompileQueue::~LLFloaterCompileQueue() -{ -} - -void LLFloaterCompileQueue::experienceIdsReceived( const LLSD& content ) -{ - for(LLSD::array_const_iterator it = content.beginArray(); it != content.endArray(); ++it) - { - mExperienceIds.insert(it->asUUID()); - } -} - -bool LLFloaterCompileQueue::hasExperience( const LLUUID& id ) const -{ - return mExperienceIds.find(id) != mExperienceIds.end(); -} - -// //Attempt to record this asset ID. If it can not be inserted into the set -// //then it has already been processed so return false. - -void LLFloaterCompileQueue::handleHTTPResponse(std::string pumpName, const LLSD &expresult) -{ - LLEventPumps::instance().post(pumpName, expresult); -} - -// *TODO: handleSCriptRetrieval is passed into the cache via a legacy C function pointer -// future project would be to convert these to C++ callables (std::function<>) so that -// we can use bind and remove the userData parameter. -// -void LLFloaterCompileQueue::handleScriptRetrieval(const LLUUID& assetId, - LLAssetType::EType type, void* userData, S32 status, LLExtStat extStatus) -{ - LLSD result(LLSD::emptyMap()); - - result["asset_id"] = assetId; - if (status) - { - result["error"] = status; - - if (status == LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE) - { - result["message"] = LLTrans::getString("CompileQueueProblemDownloading") + (":"); - result["alert"] = LLTrans::getString("CompileQueueScriptNotFound"); - } - else if (LL_ERR_INSUFFICIENT_PERMISSIONS == status) - { - result["message"] = LLTrans::getString("CompileQueueInsufficientPermFor") + (":"); - result["alert"] = LLTrans::getString("CompileQueueInsufficientPermDownload"); - } - else - { - result["message"] = LLTrans::getString("CompileQueueUnknownFailure"); - } - } - - LLEventPumps::instance().post(((HandleScriptUserData *)userData)->getPumpName(), result); - -} - -/*static*/ -void LLFloaterCompileQueue::processExperienceIdResults(LLSD result, LLUUID parent) -{ - LLFloaterCompileQueue* queue = LLFloaterReg::findTypedInstance("compile_queue", parent); - if (!queue) - return; - - queue->experienceIdsReceived(result["experience_ids"]); - - // getDerived handle gets a handle that can be resolved to a parent class of the derived object. - LLHandle hFloater(queue->getDerivedHandle()); - - // note subtle difference here: getDerivedHandle in this case is for an LLFloaterCompileQueue - fnQueueAction_t fn = boost::bind(LLFloaterCompileQueue::processScript, - queue->getDerivedHandle(), _1, _2, _3); - - - LLCoros::instance().launch("ScriptQueueCompile", boost::bind(LLFloaterScriptQueue::objectScriptProcessingQueueCoro, - queue->mStartString, - hFloater, - queue->mObjectList, - fn)); - -} - -/// This is a utility function to be bound and called from objectScriptProcessingQueueCoro. -/// Do not call directly. It may throw a LLCheckedHandle<>::Stale exception. -bool LLFloaterCompileQueue::processScript(LLHandle hfloater, - const LLPointer &object, LLInventoryObject* inventory, LLEventPump &pump) -{ - if (LLApp::isExiting()) - { - // Reply from coroutine came on shutdown - // We are quiting, don't start any more coroutines! - return true; - } - - LLSD result; - LLCheckedHandle floater(hfloater); - // Dereferencing floater may fail. If they do they throw LLExeceptionStaleHandle. - // which is caught in objectScriptProcessingQueueCoro - bool monocompile = floater->mMono; - - // Initial test to see if we can (or should) attempt to compile the script. - LLInventoryItem *item = dynamic_cast(inventory); - - if (!item) - { - LL_WARNS("SCRIPTQ") << "item retrieved is not an LLInventoryItem." << LL_ENDL; - return true; - } - - if (!item->getPermissions().allowModifyBy(gAgent.getID(), gAgent.getGroupID()) || - !item->getPermissions().allowCopyBy(gAgent.getID(), gAgent.getGroupID())) - { - std::string buffer = "Skipping: " + item->getName() + "(Permissions)"; - floater->addStringMessage(buffer); - return true; - } - - // Attempt to retrieve the experience - LLUUID experienceId; - { - LLExperienceCache::instance().fetchAssociatedExperience(inventory->getParentUUID(), inventory->getUUID(), - boost::bind(&LLFloaterCompileQueue::handleHTTPResponse, pump.getName(), _1)); - - result = llcoro::suspendUntilEventOnWithTimeout(pump, QUEUE_INVENTORY_FETCH_TIMEOUT, - LLSDMap("timeout", LLSD::Boolean(true))); - - floater.check(); - - if (result.has("timeout")) - { // A timeout filed in the result will always be true if present. - LLStringUtil::format_map_t args; - args["[OBJECT_NAME]"] = inventory->getName(); - std::string buffer = floater->getString("Timeout", args); - floater->addStringMessage(buffer); - return true; - } - - if (result.has(LLExperienceCache::EXPERIENCE_ID)) - { - experienceId = result[LLExperienceCache::EXPERIENCE_ID].asUUID(); - if (!floater->hasExperience(experienceId)) - { - floater->addProcessingMessage("CompileNoExperiencePerm", - LLSDMap("SCRIPT", inventory->getName()) - ("EXPERIENCE", result[LLExperienceCache::NAME].asString())); - return true; - } - } - - } - - if (!gAssetStorage) - { - // viewer likely is shutting down - return true; - } - - { - HandleScriptUserData userData(pump.getName()); - - - // request the asset - gAssetStorage->getInvItemAsset(LLHost(), - gAgent.getID(), - gAgent.getSessionID(), - item->getPermissions().getOwner(), - object->getID(), - item->getUUID(), - item->getAssetUUID(), - item->getType(), - &LLFloaterCompileQueue::handleScriptRetrieval, - &userData); - - result = llcoro::suspendUntilEventOnWithTimeout(pump, QUEUE_INVENTORY_FETCH_TIMEOUT, - LLSDMap("timeout", LLSD::Boolean(true))); - } - - if (result.has("timeout")) - { // A timeout filed in the result will always be true if present. - LLStringUtil::format_map_t args; - args["[OBJECT_NAME]"] = inventory->getName(); - std::string buffer = floater->getString("Timeout", args); - floater->addStringMessage(buffer); - return true; - } - - if (result.has("error")) - { - LL_WARNS("SCRIPTQ") << "Inventory fetch returned with error. Code: " << result["error"].asString() << LL_ENDL; - std::string buffer = result["message"].asString() + " " + inventory->getName(); - floater->addStringMessage(buffer); - - if (result.has("alert")) - { - LLSD args; - args["MESSAGE"] = result["alert"].asString(); - LLNotificationsUtil::add("SystemMessage", args); - } - return true; - } - - LLUUID assetId = result["asset_id"]; - - std::string url = object->getRegion()->getCapability("UpdateScriptTask"); - - { - LLResourceUploadInfo::ptr_t uploadInfo(new LLQueuedScriptAssetUpload(object->getID(), - inventory->getUUID(), - assetId, - monocompile ? LLScriptAssetUpload::MONO : LLScriptAssetUpload::LSL2, - true, - inventory->getName(), - LLUUID(), - experienceId, - boost::bind(&LLFloaterCompileQueue::handleHTTPResponse, pump.getName(), _4))); - - LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo); - } - - result = llcoro::suspendUntilEventOnWithTimeout(pump, QUEUE_INVENTORY_FETCH_TIMEOUT, LLSDMap("timeout", LLSD::Boolean(true))); - - floater.check(); - - if (result.has("timeout")) - { // A timeout filed in the result will always be true if present. - LLStringUtil::format_map_t args; - args["[OBJECT_NAME]"] = inventory->getName(); - std::string buffer = floater->getString("Timeout", args); - floater->addStringMessage(buffer); - return true; - } - - // Bytecode save completed - if (result["compiled"]) - { - std::string buffer = std::string("Compilation of \"") + inventory->getName() + std::string("\" succeeded"); - - floater->addStringMessage(buffer); - LL_INFOS() << buffer << LL_ENDL; - } - else - { - LLSD compile_errors = result["errors"]; - std::string buffer = std::string("Compilation of \"") + inventory->getName() + std::string("\" failed:"); - floater->addStringMessage(buffer); - for (LLSD::array_const_iterator line = compile_errors.beginArray(); - line < compile_errors.endArray(); line++) - { - std::string str = line->asString(); - str.erase(std::remove(str.begin(), str.end(), '\n'), str.end()); - - floater->addStringMessage(str); - } - LL_INFOS() << result["errors"] << LL_ENDL; - } - - return true; -} - -bool LLFloaterCompileQueue::startQueue() -{ - LLViewerRegion* region = gAgent.getRegion(); - if (region) - { - std::string lookup_url = region->getCapability("GetCreatorExperiences"); - if (!lookup_url.empty()) - { - LLCoreHttpUtil::HttpCoroutineAdapter::completionCallback_t success = - boost::bind(&LLFloaterCompileQueue::processExperienceIdResults, _1, getKey().asUUID()); - - LLCoreHttpUtil::HttpCoroutineAdapter::completionCallback_t failure = - boost::bind(&LLFloaterCompileQueue::processExperienceIdResults, LLSD(), getKey().asUUID()); - - LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpGet(lookup_url, - success, failure); - return true; - } - } - - return true; -} - - -///---------------------------------------------------------------------------- -/// Class LLFloaterResetQueue -///---------------------------------------------------------------------------- - -LLFloaterResetQueue::LLFloaterResetQueue(const LLSD& key) - : LLFloaterScriptQueue(key) -{ - setTitle(LLTrans::getString("ResetQueueTitle")); - setStartString(LLTrans::getString("ResetQueueStart")); -} - -LLFloaterResetQueue::~LLFloaterResetQueue() -{ -} - -/// This is a utility function to be bound and called from objectScriptProcessingQueueCoro. -/// Do not call directly. It may throw a LLCheckedHandle<>::Stale exception. -bool LLFloaterResetQueue::resetObjectScripts(LLHandle hfloater, - const LLPointer &object, LLInventoryObject* inventory, LLEventPump &pump) -{ - LLCheckedHandle floater(hfloater); - // Dereferencing floater may fail. If they do they throw LLExeceptionStaleHandle. - // which is caught in objectScriptProcessingQueueCoro - - std::string buffer; - buffer = floater->getString("Resetting") + (": ") + inventory->getName(); - floater->addStringMessage(buffer); - - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_ScriptReset); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_Script); - msg->addUUIDFast(_PREHASH_ObjectID, object->getID()); - msg->addUUIDFast(_PREHASH_ItemID, inventory->getUUID()); - msg->sendReliable(object->getRegion()->getHost()); - - return true; -} - -bool LLFloaterResetQueue::startQueue() -{ - // Bind the resetObjectScripts method into a QueueAction function and pass it - // into the object queue processing coroutine. - fnQueueAction_t fn = boost::bind(LLFloaterResetQueue::resetObjectScripts, - getDerivedHandle(), _1, _2, _3); - - LLCoros::instance().launch("ScriptResetQueue", boost::bind(LLFloaterScriptQueue::objectScriptProcessingQueueCoro, - mStartString, - getDerivedHandle(), - mObjectList, - fn)); - - return true; -} - -///---------------------------------------------------------------------------- -/// Class LLFloaterRunQueue -///---------------------------------------------------------------------------- - -LLFloaterRunQueue::LLFloaterRunQueue(const LLSD& key) - : LLFloaterScriptQueue(key) -{ - setTitle(LLTrans::getString("RunQueueTitle")); - setStartString(LLTrans::getString("RunQueueStart")); -} - -LLFloaterRunQueue::~LLFloaterRunQueue() -{ -} - -/// This is a utility function to be bound and called from objectScriptProcessingQueueCoro. -/// Do not call directly. It may throw a LLCheckedHandle<>::Stale exception. -bool LLFloaterRunQueue::runObjectScripts(LLHandle hfloater, - const LLPointer &object, LLInventoryObject* inventory, LLEventPump &pump) -{ - LLCheckedHandle floater(hfloater); - // Dereferencing floater may fail. If they do they throw LLExeceptionStaleHandle. - // which is caught in objectScriptProcessingQueueCoro - - std::string buffer; - buffer = floater->getString("Running") + (": ") + inventory->getName(); - floater->addStringMessage(buffer); - - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_SetScriptRunning); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_Script); - msg->addUUIDFast(_PREHASH_ObjectID, object->getID()); - msg->addUUIDFast(_PREHASH_ItemID, inventory->getUUID()); - msg->addBOOLFast(_PREHASH_Running, true); - msg->sendReliable(object->getRegion()->getHost()); - - return true; -} - -bool LLFloaterRunQueue::startQueue() -{ - LLHandle hFloater(getDerivedHandle()); - fnQueueAction_t fn = boost::bind(LLFloaterRunQueue::runObjectScripts, hFloater, _1, _2, _3); - - LLCoros::instance().launch("ScriptRunQueue", boost::bind(LLFloaterScriptQueue::objectScriptProcessingQueueCoro, - mStartString, - hFloater, - mObjectList, - fn)); - - return true; -} - - -///---------------------------------------------------------------------------- -/// Class LLFloaterNotRunQueue -///---------------------------------------------------------------------------- - -LLFloaterNotRunQueue::LLFloaterNotRunQueue(const LLSD& key) - : LLFloaterScriptQueue(key) -{ - setTitle(LLTrans::getString("NotRunQueueTitle")); - setStartString(LLTrans::getString("NotRunQueueStart")); -} - -LLFloaterNotRunQueue::~LLFloaterNotRunQueue() -{ -} - -/// This is a utility function to be bound and called from objectScriptProcessingQueueCoro. -/// Do not call directly. It may throw a LLCheckedHandle<>::Stale exception. -bool LLFloaterNotRunQueue::stopObjectScripts(LLHandle hfloater, - const LLPointer &object, LLInventoryObject* inventory, LLEventPump &pump) -{ - LLCheckedHandle floater(hfloater); - // Dereferencing floater may fail. If they do they throw LLExeceptionStaleHandle. - // which is caught in objectScriptProcessingQueueCoro - - std::string buffer; - buffer = floater->getString("NotRunning") + (": ") + inventory->getName(); - floater->addStringMessage(buffer); - - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_SetScriptRunning); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_Script); - msg->addUUIDFast(_PREHASH_ObjectID, object->getID()); - msg->addUUIDFast(_PREHASH_ItemID, inventory->getUUID()); - msg->addBOOLFast(_PREHASH_Running, false); - msg->sendReliable(object->getRegion()->getHost()); - - return true; -} - -bool LLFloaterNotRunQueue::startQueue() -{ - LLHandle hFloater(getDerivedHandle()); - - fnQueueAction_t fn = boost::bind(&LLFloaterNotRunQueue::stopObjectScripts, hFloater, _1, _2, _3); - LLCoros::instance().launch("ScriptQueueNotRun", boost::bind(LLFloaterScriptQueue::objectScriptProcessingQueueCoro, - mStartString, - hFloater, - mObjectList, - fn)); - - return true; -} - -///---------------------------------------------------------------------------- -/// Local function definitions -///---------------------------------------------------------------------------- -void ObjectInventoryFetcher::inventoryChanged(LLViewerObject* object, - LLInventoryObject::object_list_t* inventory, S32 serial_num, void* user_data) -{ - mInventoryList.clear(); - mInventoryList.assign(inventory->begin(), inventory->end()); - - mPump.post(LLSDMap("changed", LLSD::Boolean(true))); - -} - -void LLFloaterScriptQueue::objectScriptProcessingQueueCoro(std::string action, LLHandle hfloater, - object_data_list_t objectList, fnQueueAction_t func) -{ - LLCoros::set_consuming(true); - LLCheckedHandle floater(hfloater); - // Dereferencing floater may fail. If they do they throw LLExeceptionStaleHandle. - // This is expected if the dialog closes. - LLEventMailDrop maildrop(QUEUE_EVENTPUMP_NAME, true); - - try - { - for (object_data_list_t::iterator itObj(objectList.begin()); (itObj != objectList.end()); ++itObj) - { - bool firstForObject = true; - LLUUID object_id = (*itObj).mObjectId; - LL_INFOS("SCRIPTQ") << "Next object in queue with ID=" << object_id.asString() << LL_ENDL; - - LLPointer obj = gObjectList.findObject(object_id); - LLInventoryObject::object_list_t inventory; - if (obj) - { - ObjectInventoryFetcher::ptr_t fetcher(new ObjectInventoryFetcher(maildrop, obj, NULL)); - - fetcher->fetchInventory(); - - LLStringUtil::format_map_t args; - args["[OBJECT_NAME]"] = (*itObj).mObjectName; - floater->addStringMessage(floater->getString("LoadingObjInv", args)); - - LLSD result = llcoro::suspendUntilEventOnWithTimeout(maildrop, QUEUE_INVENTORY_FETCH_TIMEOUT, - LLSDMap("timeout", LLSD::Boolean(true))); - - if (result.has("timeout")) - { // A timeout filed in the result will always be true if present. - LL_WARNS("SCRIPTQ") << "Unable to retrieve inventory for object " << object_id.asString() << - ". Skipping to next object." << LL_ENDL; - - LLStringUtil::format_map_t args; - args["[OBJECT_NAME]"] = (*itObj).mObjectName; - floater->addStringMessage(floater->getString("Timeout", args)); - - continue; - } - - inventory.assign(fetcher->getInventoryList().begin(), fetcher->getInventoryList().end()); - } - else - { - LL_WARNS("SCRIPTQ") << "Unable to retrieve object with ID of " << object_id << - ". Skipping to next." << LL_ENDL; - continue; - } - - // TODO: Get the name of the object we are looking at here so that we can display it below. - //std::string objName = (dynamic_cast(obj.get()))->getName(); - LL_DEBUGS("SCRIPTQ") << "Object has " << inventory.size() << " items." << LL_ENDL; - - for (LLInventoryObject::object_list_t::iterator itInv = inventory.begin(); - itInv != inventory.end(); ++itInv) - { - floater.check(); - - // note, we have a smart pointer to the obj above... but if we didn't we'd check that - // it still exists here. - - if (((*itInv)->getType() == LLAssetType::AT_LSL_TEXT)) - { - LL_DEBUGS("SCRIPTQ") << "Inventory item " << (*itInv)->getUUID().asString() << "\"" << (*itInv)->getName() << "\"" << LL_ENDL; - if (firstForObject) - { - //floater->addStringMessage(objName + ":"); - firstForObject = false; - } - - if (!func(obj, (*itInv), maildrop)) - { - continue; - } - } - - // no other explicit suspension point in this loop. func(...) MIGHT suspend - // but offers no guarantee of doing so. - llcoro::suspend(); - } - floater.check(); - } - - floater->addStringMessage("Done"); - floater->getChildView("close")->setEnabled(true); - } - catch (LLCheckedHandleBase::Stale &) - { - // This is expected. It means that floater has been closed before - // processing was completed. - LL_DEBUGS("SCRIPTQ") << "LLExeceptionStaleHandle caught! Floater has most likely been closed." << LL_ENDL; - } -} +/** + * @file llcompilequeue.cpp + * @brief LLCompileQueueData class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/** + * + * Implementation of the script queue which keeps an array of object + * UUIDs and manipulates all of the scripts on each of them. + * + */ + + +#include "llviewerprecompiledheaders.h" + +#include "llcompilequeue.h" + +#include "llagent.h" +#include "llchat.h" +#include "llfloaterreg.h" +#include "llviewerwindow.h" +#include "llviewerobject.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llviewercontrol.h" +#include "llviewerobject.h" +#include "llviewerregion.h" +#include "llresmgr.h" + +#include "llbutton.h" +#include "lldir.h" +#include "llnotificationsutil.h" +#include "llviewerstats.h" +#include "llfilesystem.h" +#include "lluictrlfactory.h" +#include "lltrans.h" + +#include "llselectmgr.h" +#include "llexperiencecache.h" + +#include "llviewerassetupload.h" +#include "llcorehttputil.h" + +namespace +{ + + const std::string QUEUE_EVENTPUMP_NAME("ScriptActionQueue"); + const F32 QUEUE_INVENTORY_FETCH_TIMEOUT = 300.f; + + // ObjectIventoryFetcher is an adapter between the LLVOInventoryListener::inventoryChanged + // callback mechanism and the LLEventPump coroutine architecture allowing the + // coroutine to wait for the inventory event. + class ObjectInventoryFetcher: public LLVOInventoryListener + { + public: + typedef std::shared_ptr ptr_t; + + ObjectInventoryFetcher(LLEventPump &pump, LLViewerObject* object, void* user_data) : + mPump(pump), + LLVOInventoryListener() + { + registerVOInventoryListener(object, this); + } + + virtual void inventoryChanged(LLViewerObject* object, + LLInventoryObject::object_list_t* inventory, + S32 serial_num, + void* user_data); + + void fetchInventory() + { + requestVOInventory(); + } + + const LLInventoryObject::object_list_t & getInventoryList() const { return mInventoryList; } + + private: + LLInventoryObject::object_list_t mInventoryList; + LLEventPump & mPump; + }; + + class HandleScriptUserData + { + public: + HandleScriptUserData(const std::string &pumpname) : + mPumpname(pumpname) + { } + + const std::string &getPumpName() const { return mPumpname; } + + private: + std::string mPumpname; + }; + + +} + +// *NOTE$: A minor specialization of LLScriptAssetUpload, it does not require a buffer +// (and does not save a buffer to the cache) and it finds the compile queue window and +// displays a compiling message. +class LLQueuedScriptAssetUpload : public LLScriptAssetUpload +{ +public: + LLQueuedScriptAssetUpload(LLUUID taskId, LLUUID itemId, LLUUID assetId, TargetType_t targetType, + bool isRunning, std::string scriptName, LLUUID queueId, LLUUID exerienceId, taskUploadFinish_f finish) : + LLScriptAssetUpload(taskId, itemId, targetType, isRunning, + exerienceId, std::string(), finish, nullptr), + mScriptName(scriptName), + mQueueId(queueId) + { + setAssetId(assetId); + } + + virtual LLSD prepareUpload() + { + /* *NOTE$: The parent class (LLScriptAssetUpload will attempt to save + * the script buffer into to the cache. Since the resource is already in + * the cache we don't want to do that. Just put a compiling message in + * the window and move on + */ + LLFloaterCompileQueue* queue = LLFloaterReg::findTypedInstance("compile_queue", LLSD(mQueueId)); + if (queue) + { + std::string message = std::string("Compiling \"") + getScriptName() + std::string("\"..."); + + queue->getChild("queue output")->addSimpleElement(message, ADD_BOTTOM); + } + + return LLSDMap("success", LLSD::Boolean(true)); + } + + + std::string getScriptName() const { return mScriptName; } + +private: + void setScriptName(const std::string &scriptName) { mScriptName = scriptName; } + + LLUUID mQueueId; + std::string mScriptName; +}; + +///---------------------------------------------------------------------------- +/// Local function declarations, constants, enums, and typedefs +///---------------------------------------------------------------------------- + +struct LLScriptQueueData +{ + LLUUID mQueueID; + LLUUID mTaskId; + LLPointer mItem; + LLHost mHost; + LLUUID mExperienceId; + std::string mExperiencename; + LLScriptQueueData(const LLUUID& q_id, const LLUUID& task_id, LLInventoryItem* item) : + mQueueID(q_id), mTaskId(task_id), mItem(new LLInventoryItem(item)) {} + +}; + +///---------------------------------------------------------------------------- +/// Class LLFloaterScriptQueue +///---------------------------------------------------------------------------- + +// Default constructor +LLFloaterScriptQueue::LLFloaterScriptQueue(const LLSD& key) : + LLFloater(key), + mDone(false), + mMono(false) +{ + +} + +// Destroys the object +LLFloaterScriptQueue::~LLFloaterScriptQueue() +{ +} + +bool LLFloaterScriptQueue::postBuild() +{ + childSetAction("close",onCloseBtn,this); + getChildView("close")->setEnabled(false); + setVisible(true); + return true; +} + +// static +void LLFloaterScriptQueue::onCloseBtn(void* user_data) +{ + LLFloaterScriptQueue* self = (LLFloaterScriptQueue*)user_data; + self->closeFloater(); +} + +void LLFloaterScriptQueue::addObject(const LLUUID& id, std::string name) +{ + ObjectData obj = { id, name }; + mObjectList.push_back(obj); +} + +bool LLFloaterScriptQueue::start() +{ + std::string buffer; + + LLStringUtil::format_map_t args; + args["[START]"] = mStartString; + args["[COUNT]"] = llformat ("%d", mObjectList.size()); + buffer = getString ("Starting", args); + + getChild("queue output")->addSimpleElement(buffer, ADD_BOTTOM); + + return startQueue(); +} + +void LLFloaterScriptQueue::addProcessingMessage(const std::string &message, const LLSD &args) +{ + std::string buffer(LLTrans::getString(message, args)); + + getChild("queue output")->addSimpleElement(buffer, ADD_BOTTOM); +} + +void LLFloaterScriptQueue::addStringMessage(const std::string &message) +{ + getChild("queue output")->addSimpleElement(message, ADD_BOTTOM); +} + + +bool LLFloaterScriptQueue::isDone() const +{ + return (mCurrentObjectID.isNull() && (mObjectList.size() == 0)); +} + +///---------------------------------------------------------------------------- +/// Class LLFloaterCompileQueue +///---------------------------------------------------------------------------- +LLFloaterCompileQueue::LLFloaterCompileQueue(const LLSD& key) + : LLFloaterScriptQueue(key) +{ + setTitle(LLTrans::getString("CompileQueueTitle")); + setStartString(LLTrans::getString("CompileQueueStart")); + +} + +LLFloaterCompileQueue::~LLFloaterCompileQueue() +{ +} + +void LLFloaterCompileQueue::experienceIdsReceived( const LLSD& content ) +{ + for(LLSD::array_const_iterator it = content.beginArray(); it != content.endArray(); ++it) + { + mExperienceIds.insert(it->asUUID()); + } +} + +bool LLFloaterCompileQueue::hasExperience( const LLUUID& id ) const +{ + return mExperienceIds.find(id) != mExperienceIds.end(); +} + +// //Attempt to record this asset ID. If it can not be inserted into the set +// //then it has already been processed so return false. + +void LLFloaterCompileQueue::handleHTTPResponse(std::string pumpName, const LLSD &expresult) +{ + LLEventPumps::instance().post(pumpName, expresult); +} + +// *TODO: handleSCriptRetrieval is passed into the cache via a legacy C function pointer +// future project would be to convert these to C++ callables (std::function<>) so that +// we can use bind and remove the userData parameter. +// +void LLFloaterCompileQueue::handleScriptRetrieval(const LLUUID& assetId, + LLAssetType::EType type, void* userData, S32 status, LLExtStat extStatus) +{ + LLSD result(LLSD::emptyMap()); + + result["asset_id"] = assetId; + if (status) + { + result["error"] = status; + + if (status == LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE) + { + result["message"] = LLTrans::getString("CompileQueueProblemDownloading") + (":"); + result["alert"] = LLTrans::getString("CompileQueueScriptNotFound"); + } + else if (LL_ERR_INSUFFICIENT_PERMISSIONS == status) + { + result["message"] = LLTrans::getString("CompileQueueInsufficientPermFor") + (":"); + result["alert"] = LLTrans::getString("CompileQueueInsufficientPermDownload"); + } + else + { + result["message"] = LLTrans::getString("CompileQueueUnknownFailure"); + } + } + + LLEventPumps::instance().post(((HandleScriptUserData *)userData)->getPumpName(), result); + +} + +/*static*/ +void LLFloaterCompileQueue::processExperienceIdResults(LLSD result, LLUUID parent) +{ + LLFloaterCompileQueue* queue = LLFloaterReg::findTypedInstance("compile_queue", parent); + if (!queue) + return; + + queue->experienceIdsReceived(result["experience_ids"]); + + // getDerived handle gets a handle that can be resolved to a parent class of the derived object. + LLHandle hFloater(queue->getDerivedHandle()); + + // note subtle difference here: getDerivedHandle in this case is for an LLFloaterCompileQueue + fnQueueAction_t fn = boost::bind(LLFloaterCompileQueue::processScript, + queue->getDerivedHandle(), _1, _2, _3); + + + LLCoros::instance().launch("ScriptQueueCompile", boost::bind(LLFloaterScriptQueue::objectScriptProcessingQueueCoro, + queue->mStartString, + hFloater, + queue->mObjectList, + fn)); + +} + +/// This is a utility function to be bound and called from objectScriptProcessingQueueCoro. +/// Do not call directly. It may throw a LLCheckedHandle<>::Stale exception. +bool LLFloaterCompileQueue::processScript(LLHandle hfloater, + const LLPointer &object, LLInventoryObject* inventory, LLEventPump &pump) +{ + if (LLApp::isExiting()) + { + // Reply from coroutine came on shutdown + // We are quiting, don't start any more coroutines! + return true; + } + + LLSD result; + LLCheckedHandle floater(hfloater); + // Dereferencing floater may fail. If they do they throw LLExeceptionStaleHandle. + // which is caught in objectScriptProcessingQueueCoro + bool monocompile = floater->mMono; + + // Initial test to see if we can (or should) attempt to compile the script. + LLInventoryItem *item = dynamic_cast(inventory); + + if (!item) + { + LL_WARNS("SCRIPTQ") << "item retrieved is not an LLInventoryItem." << LL_ENDL; + return true; + } + + if (!item->getPermissions().allowModifyBy(gAgent.getID(), gAgent.getGroupID()) || + !item->getPermissions().allowCopyBy(gAgent.getID(), gAgent.getGroupID())) + { + std::string buffer = "Skipping: " + item->getName() + "(Permissions)"; + floater->addStringMessage(buffer); + return true; + } + + // Attempt to retrieve the experience + LLUUID experienceId; + { + LLExperienceCache::instance().fetchAssociatedExperience(inventory->getParentUUID(), inventory->getUUID(), + boost::bind(&LLFloaterCompileQueue::handleHTTPResponse, pump.getName(), _1)); + + result = llcoro::suspendUntilEventOnWithTimeout(pump, QUEUE_INVENTORY_FETCH_TIMEOUT, + LLSDMap("timeout", LLSD::Boolean(true))); + + floater.check(); + + if (result.has("timeout")) + { // A timeout filed in the result will always be true if present. + LLStringUtil::format_map_t args; + args["[OBJECT_NAME]"] = inventory->getName(); + std::string buffer = floater->getString("Timeout", args); + floater->addStringMessage(buffer); + return true; + } + + if (result.has(LLExperienceCache::EXPERIENCE_ID)) + { + experienceId = result[LLExperienceCache::EXPERIENCE_ID].asUUID(); + if (!floater->hasExperience(experienceId)) + { + floater->addProcessingMessage("CompileNoExperiencePerm", + LLSDMap("SCRIPT", inventory->getName()) + ("EXPERIENCE", result[LLExperienceCache::NAME].asString())); + return true; + } + } + + } + + if (!gAssetStorage) + { + // viewer likely is shutting down + return true; + } + + { + HandleScriptUserData userData(pump.getName()); + + + // request the asset + gAssetStorage->getInvItemAsset(LLHost(), + gAgent.getID(), + gAgent.getSessionID(), + item->getPermissions().getOwner(), + object->getID(), + item->getUUID(), + item->getAssetUUID(), + item->getType(), + &LLFloaterCompileQueue::handleScriptRetrieval, + &userData); + + result = llcoro::suspendUntilEventOnWithTimeout(pump, QUEUE_INVENTORY_FETCH_TIMEOUT, + LLSDMap("timeout", LLSD::Boolean(true))); + } + + if (result.has("timeout")) + { // A timeout filed in the result will always be true if present. + LLStringUtil::format_map_t args; + args["[OBJECT_NAME]"] = inventory->getName(); + std::string buffer = floater->getString("Timeout", args); + floater->addStringMessage(buffer); + return true; + } + + if (result.has("error")) + { + LL_WARNS("SCRIPTQ") << "Inventory fetch returned with error. Code: " << result["error"].asString() << LL_ENDL; + std::string buffer = result["message"].asString() + " " + inventory->getName(); + floater->addStringMessage(buffer); + + if (result.has("alert")) + { + LLSD args; + args["MESSAGE"] = result["alert"].asString(); + LLNotificationsUtil::add("SystemMessage", args); + } + return true; + } + + LLUUID assetId = result["asset_id"]; + + std::string url = object->getRegion()->getCapability("UpdateScriptTask"); + + { + LLResourceUploadInfo::ptr_t uploadInfo(new LLQueuedScriptAssetUpload(object->getID(), + inventory->getUUID(), + assetId, + monocompile ? LLScriptAssetUpload::MONO : LLScriptAssetUpload::LSL2, + true, + inventory->getName(), + LLUUID(), + experienceId, + boost::bind(&LLFloaterCompileQueue::handleHTTPResponse, pump.getName(), _4))); + + LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo); + } + + result = llcoro::suspendUntilEventOnWithTimeout(pump, QUEUE_INVENTORY_FETCH_TIMEOUT, LLSDMap("timeout", LLSD::Boolean(true))); + + floater.check(); + + if (result.has("timeout")) + { // A timeout filed in the result will always be true if present. + LLStringUtil::format_map_t args; + args["[OBJECT_NAME]"] = inventory->getName(); + std::string buffer = floater->getString("Timeout", args); + floater->addStringMessage(buffer); + return true; + } + + // Bytecode save completed + if (result["compiled"]) + { + std::string buffer = std::string("Compilation of \"") + inventory->getName() + std::string("\" succeeded"); + + floater->addStringMessage(buffer); + LL_INFOS() << buffer << LL_ENDL; + } + else + { + LLSD compile_errors = result["errors"]; + std::string buffer = std::string("Compilation of \"") + inventory->getName() + std::string("\" failed:"); + floater->addStringMessage(buffer); + for (LLSD::array_const_iterator line = compile_errors.beginArray(); + line < compile_errors.endArray(); line++) + { + std::string str = line->asString(); + str.erase(std::remove(str.begin(), str.end(), '\n'), str.end()); + + floater->addStringMessage(str); + } + LL_INFOS() << result["errors"] << LL_ENDL; + } + + return true; +} + +bool LLFloaterCompileQueue::startQueue() +{ + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + std::string lookup_url = region->getCapability("GetCreatorExperiences"); + if (!lookup_url.empty()) + { + LLCoreHttpUtil::HttpCoroutineAdapter::completionCallback_t success = + boost::bind(&LLFloaterCompileQueue::processExperienceIdResults, _1, getKey().asUUID()); + + LLCoreHttpUtil::HttpCoroutineAdapter::completionCallback_t failure = + boost::bind(&LLFloaterCompileQueue::processExperienceIdResults, LLSD(), getKey().asUUID()); + + LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpGet(lookup_url, + success, failure); + return true; + } + } + + return true; +} + + +///---------------------------------------------------------------------------- +/// Class LLFloaterResetQueue +///---------------------------------------------------------------------------- + +LLFloaterResetQueue::LLFloaterResetQueue(const LLSD& key) + : LLFloaterScriptQueue(key) +{ + setTitle(LLTrans::getString("ResetQueueTitle")); + setStartString(LLTrans::getString("ResetQueueStart")); +} + +LLFloaterResetQueue::~LLFloaterResetQueue() +{ +} + +/// This is a utility function to be bound and called from objectScriptProcessingQueueCoro. +/// Do not call directly. It may throw a LLCheckedHandle<>::Stale exception. +bool LLFloaterResetQueue::resetObjectScripts(LLHandle hfloater, + const LLPointer &object, LLInventoryObject* inventory, LLEventPump &pump) +{ + LLCheckedHandle floater(hfloater); + // Dereferencing floater may fail. If they do they throw LLExeceptionStaleHandle. + // which is caught in objectScriptProcessingQueueCoro + + std::string buffer; + buffer = floater->getString("Resetting") + (": ") + inventory->getName(); + floater->addStringMessage(buffer); + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_ScriptReset); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_Script); + msg->addUUIDFast(_PREHASH_ObjectID, object->getID()); + msg->addUUIDFast(_PREHASH_ItemID, inventory->getUUID()); + msg->sendReliable(object->getRegion()->getHost()); + + return true; +} + +bool LLFloaterResetQueue::startQueue() +{ + // Bind the resetObjectScripts method into a QueueAction function and pass it + // into the object queue processing coroutine. + fnQueueAction_t fn = boost::bind(LLFloaterResetQueue::resetObjectScripts, + getDerivedHandle(), _1, _2, _3); + + LLCoros::instance().launch("ScriptResetQueue", boost::bind(LLFloaterScriptQueue::objectScriptProcessingQueueCoro, + mStartString, + getDerivedHandle(), + mObjectList, + fn)); + + return true; +} + +///---------------------------------------------------------------------------- +/// Class LLFloaterRunQueue +///---------------------------------------------------------------------------- + +LLFloaterRunQueue::LLFloaterRunQueue(const LLSD& key) + : LLFloaterScriptQueue(key) +{ + setTitle(LLTrans::getString("RunQueueTitle")); + setStartString(LLTrans::getString("RunQueueStart")); +} + +LLFloaterRunQueue::~LLFloaterRunQueue() +{ +} + +/// This is a utility function to be bound and called from objectScriptProcessingQueueCoro. +/// Do not call directly. It may throw a LLCheckedHandle<>::Stale exception. +bool LLFloaterRunQueue::runObjectScripts(LLHandle hfloater, + const LLPointer &object, LLInventoryObject* inventory, LLEventPump &pump) +{ + LLCheckedHandle floater(hfloater); + // Dereferencing floater may fail. If they do they throw LLExeceptionStaleHandle. + // which is caught in objectScriptProcessingQueueCoro + + std::string buffer; + buffer = floater->getString("Running") + (": ") + inventory->getName(); + floater->addStringMessage(buffer); + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_SetScriptRunning); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_Script); + msg->addUUIDFast(_PREHASH_ObjectID, object->getID()); + msg->addUUIDFast(_PREHASH_ItemID, inventory->getUUID()); + msg->addBOOLFast(_PREHASH_Running, true); + msg->sendReliable(object->getRegion()->getHost()); + + return true; +} + +bool LLFloaterRunQueue::startQueue() +{ + LLHandle hFloater(getDerivedHandle()); + fnQueueAction_t fn = boost::bind(LLFloaterRunQueue::runObjectScripts, hFloater, _1, _2, _3); + + LLCoros::instance().launch("ScriptRunQueue", boost::bind(LLFloaterScriptQueue::objectScriptProcessingQueueCoro, + mStartString, + hFloater, + mObjectList, + fn)); + + return true; +} + + +///---------------------------------------------------------------------------- +/// Class LLFloaterNotRunQueue +///---------------------------------------------------------------------------- + +LLFloaterNotRunQueue::LLFloaterNotRunQueue(const LLSD& key) + : LLFloaterScriptQueue(key) +{ + setTitle(LLTrans::getString("NotRunQueueTitle")); + setStartString(LLTrans::getString("NotRunQueueStart")); +} + +LLFloaterNotRunQueue::~LLFloaterNotRunQueue() +{ +} + +/// This is a utility function to be bound and called from objectScriptProcessingQueueCoro. +/// Do not call directly. It may throw a LLCheckedHandle<>::Stale exception. +bool LLFloaterNotRunQueue::stopObjectScripts(LLHandle hfloater, + const LLPointer &object, LLInventoryObject* inventory, LLEventPump &pump) +{ + LLCheckedHandle floater(hfloater); + // Dereferencing floater may fail. If they do they throw LLExeceptionStaleHandle. + // which is caught in objectScriptProcessingQueueCoro + + std::string buffer; + buffer = floater->getString("NotRunning") + (": ") + inventory->getName(); + floater->addStringMessage(buffer); + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_SetScriptRunning); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_Script); + msg->addUUIDFast(_PREHASH_ObjectID, object->getID()); + msg->addUUIDFast(_PREHASH_ItemID, inventory->getUUID()); + msg->addBOOLFast(_PREHASH_Running, false); + msg->sendReliable(object->getRegion()->getHost()); + + return true; +} + +bool LLFloaterNotRunQueue::startQueue() +{ + LLHandle hFloater(getDerivedHandle()); + + fnQueueAction_t fn = boost::bind(&LLFloaterNotRunQueue::stopObjectScripts, hFloater, _1, _2, _3); + LLCoros::instance().launch("ScriptQueueNotRun", boost::bind(LLFloaterScriptQueue::objectScriptProcessingQueueCoro, + mStartString, + hFloater, + mObjectList, + fn)); + + return true; +} + +///---------------------------------------------------------------------------- +/// Local function definitions +///---------------------------------------------------------------------------- +void ObjectInventoryFetcher::inventoryChanged(LLViewerObject* object, + LLInventoryObject::object_list_t* inventory, S32 serial_num, void* user_data) +{ + mInventoryList.clear(); + mInventoryList.assign(inventory->begin(), inventory->end()); + + mPump.post(LLSDMap("changed", LLSD::Boolean(true))); + +} + +void LLFloaterScriptQueue::objectScriptProcessingQueueCoro(std::string action, LLHandle hfloater, + object_data_list_t objectList, fnQueueAction_t func) +{ + LLCoros::set_consuming(true); + LLCheckedHandle floater(hfloater); + // Dereferencing floater may fail. If they do they throw LLExeceptionStaleHandle. + // This is expected if the dialog closes. + LLEventMailDrop maildrop(QUEUE_EVENTPUMP_NAME, true); + + try + { + for (object_data_list_t::iterator itObj(objectList.begin()); (itObj != objectList.end()); ++itObj) + { + bool firstForObject = true; + LLUUID object_id = (*itObj).mObjectId; + LL_INFOS("SCRIPTQ") << "Next object in queue with ID=" << object_id.asString() << LL_ENDL; + + LLPointer obj = gObjectList.findObject(object_id); + LLInventoryObject::object_list_t inventory; + if (obj) + { + ObjectInventoryFetcher::ptr_t fetcher(new ObjectInventoryFetcher(maildrop, obj, NULL)); + + fetcher->fetchInventory(); + + LLStringUtil::format_map_t args; + args["[OBJECT_NAME]"] = (*itObj).mObjectName; + floater->addStringMessage(floater->getString("LoadingObjInv", args)); + + LLSD result = llcoro::suspendUntilEventOnWithTimeout(maildrop, QUEUE_INVENTORY_FETCH_TIMEOUT, + LLSDMap("timeout", LLSD::Boolean(true))); + + if (result.has("timeout")) + { // A timeout filed in the result will always be true if present. + LL_WARNS("SCRIPTQ") << "Unable to retrieve inventory for object " << object_id.asString() << + ". Skipping to next object." << LL_ENDL; + + LLStringUtil::format_map_t args; + args["[OBJECT_NAME]"] = (*itObj).mObjectName; + floater->addStringMessage(floater->getString("Timeout", args)); + + continue; + } + + inventory.assign(fetcher->getInventoryList().begin(), fetcher->getInventoryList().end()); + } + else + { + LL_WARNS("SCRIPTQ") << "Unable to retrieve object with ID of " << object_id << + ". Skipping to next." << LL_ENDL; + continue; + } + + // TODO: Get the name of the object we are looking at here so that we can display it below. + //std::string objName = (dynamic_cast(obj.get()))->getName(); + LL_DEBUGS("SCRIPTQ") << "Object has " << inventory.size() << " items." << LL_ENDL; + + for (LLInventoryObject::object_list_t::iterator itInv = inventory.begin(); + itInv != inventory.end(); ++itInv) + { + floater.check(); + + // note, we have a smart pointer to the obj above... but if we didn't we'd check that + // it still exists here. + + if (((*itInv)->getType() == LLAssetType::AT_LSL_TEXT)) + { + LL_DEBUGS("SCRIPTQ") << "Inventory item " << (*itInv)->getUUID().asString() << "\"" << (*itInv)->getName() << "\"" << LL_ENDL; + if (firstForObject) + { + //floater->addStringMessage(objName + ":"); + firstForObject = false; + } + + if (!func(obj, (*itInv), maildrop)) + { + continue; + } + } + + // no other explicit suspension point in this loop. func(...) MIGHT suspend + // but offers no guarantee of doing so. + llcoro::suspend(); + } + floater.check(); + } + + floater->addStringMessage("Done"); + floater->getChildView("close")->setEnabled(true); + } + catch (LLCheckedHandleBase::Stale &) + { + // This is expected. It means that floater has been closed before + // processing was completed. + LL_DEBUGS("SCRIPTQ") << "LLExeceptionStaleHandle caught! Floater has most likely been closed." << LL_ENDL; + } +} diff --git a/indra/newview/llcompilequeue.h b/indra/newview/llcompilequeue.h index 792ad363cb..951d4800e8 100644 --- a/indra/newview/llcompilequeue.h +++ b/indra/newview/llcompilequeue.h @@ -1,200 +1,200 @@ -/** - * @file llcompilequeue.h - * @brief LLCompileQueue class header file - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLCOMPILEQUEUE_H -#define LL_LLCOMPILEQUEUE_H - -#include "llinventory.h" -#include "llviewerobject.h" -#include "lluuid.h" - -#include "llfloater.h" -#include "llscrolllistctrl.h" - -#include "llevents.h" - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLFloaterScriptQueue -// -// This class provides a mechanism of adding objects to a list that -// will go through and execute action for the scripts on each object. The -// objects will be accessed serially and the scripts may be -// manipulated in parallel. For example, selecting two objects each -// with three scripts will result in the first object having all three -// scripts manipulated. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLFloaterScriptQueue : public LLFloater/*, public LLVOInventoryListener*/ -{ -public: - LLFloaterScriptQueue(const LLSD& key); - virtual ~LLFloaterScriptQueue(); - - /*virtual*/ bool postBuild(); - - void setMono(bool mono) { mMono = mono; } - - // addObject() accepts an object id. - void addObject(const LLUUID& id, std::string name); - - // start() returns true if the queue has started, otherwise false. - bool start(); - - void addProcessingMessage(const std::string &message, const LLSD &args); - void addStringMessage(const std::string &message); - - std::string getStartString() const { return mStartString; } - -protected: - static void onCloseBtn(void* user_data); - - // returns true if this is done - bool isDone() const; - - virtual bool startQueue() = 0; - - void setStartString(const std::string& s) { mStartString = s; } - -protected: - // UI - LLScrollListCtrl* mMessages; - LLButton* mCloseBtn; - - // Object Queue - struct ObjectData - { - LLUUID mObjectId; - std::string mObjectName; - }; - typedef std::vector object_data_list_t; - - object_data_list_t mObjectList; - LLUUID mCurrentObjectID; - bool mDone; - - std::string mStartString; - bool mMono; - - typedef boost::function &, LLInventoryObject*, LLEventPump &)> fnQueueAction_t; - static void objectScriptProcessingQueueCoro(std::string action, LLHandle hfloater, object_data_list_t objectList, fnQueueAction_t func); - -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLFloaterCompileQueue -// -// This script queue will recompile each script. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -struct LLCompileQueueData -{ - LLUUID mQueueID; - LLUUID mItemId; - LLCompileQueueData(const LLUUID& q_id, const LLUUID& item_id) : - mQueueID(q_id), mItemId(item_id) {} -}; - -class LLFloaterCompileQueue : public LLFloaterScriptQueue -{ - friend class LLFloaterReg; -public: - - void experienceIdsReceived( const LLSD& content ); - bool hasExperience(const LLUUID& id)const; - -protected: - LLFloaterCompileQueue(const LLSD& key); - virtual ~LLFloaterCompileQueue(); - - virtual bool startQueue(); - - static bool processScript(LLHandle hfloater, const LLPointer &object, LLInventoryObject* inventory, LLEventPump &pump); - - //bool checkAssetId(const LLUUID &assetId); - static void handleHTTPResponse(std::string pumpName, const LLSD &expresult); - static void handleScriptRetrieval(const LLUUID& assetId, LLAssetType::EType type, void* userData, S32 status, LLExtStat extStatus); - -private: - static void processExperienceIdResults(LLSD result, LLUUID parent); - //uuid_list_t mAssetIds; // list of asset IDs processed. - uuid_list_t mExperienceIds; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLFloaterResetQueue -// -// This script queue will reset each script. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLFloaterResetQueue : public LLFloaterScriptQueue -{ - friend class LLFloaterReg; -protected: - LLFloaterResetQueue(const LLSD& key); - virtual ~LLFloaterResetQueue(); - - static bool resetObjectScripts(LLHandle hfloater, const LLPointer &object, LLInventoryObject* inventory, LLEventPump &pump); - - virtual bool startQueue(); -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLFloaterRunQueue -// -// This script queue will set each script as running. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLFloaterRunQueue : public LLFloaterScriptQueue -{ - friend class LLFloaterReg; -protected: - LLFloaterRunQueue(const LLSD& key); - virtual ~LLFloaterRunQueue(); - - static bool runObjectScripts(LLHandle hfloater, const LLPointer &object, LLInventoryObject* inventory, LLEventPump &pump); - - virtual bool startQueue(); -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLFloaterNotRunQueue -// -// This script queue will set each script as not running. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLFloaterNotRunQueue : public LLFloaterScriptQueue -{ - friend class LLFloaterReg; -protected: - LLFloaterNotRunQueue(const LLSD& key); - virtual ~LLFloaterNotRunQueue(); - - static bool stopObjectScripts(LLHandle hfloater, const LLPointer &object, LLInventoryObject* inventory, LLEventPump &pump); - - virtual bool startQueue(); -}; - -#endif // LL_LLCOMPILEQUEUE_H +/** + * @file llcompilequeue.h + * @brief LLCompileQueue class header file + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLCOMPILEQUEUE_H +#define LL_LLCOMPILEQUEUE_H + +#include "llinventory.h" +#include "llviewerobject.h" +#include "lluuid.h" + +#include "llfloater.h" +#include "llscrolllistctrl.h" + +#include "llevents.h" + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLFloaterScriptQueue +// +// This class provides a mechanism of adding objects to a list that +// will go through and execute action for the scripts on each object. The +// objects will be accessed serially and the scripts may be +// manipulated in parallel. For example, selecting two objects each +// with three scripts will result in the first object having all three +// scripts manipulated. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLFloaterScriptQueue : public LLFloater/*, public LLVOInventoryListener*/ +{ +public: + LLFloaterScriptQueue(const LLSD& key); + virtual ~LLFloaterScriptQueue(); + + /*virtual*/ bool postBuild(); + + void setMono(bool mono) { mMono = mono; } + + // addObject() accepts an object id. + void addObject(const LLUUID& id, std::string name); + + // start() returns true if the queue has started, otherwise false. + bool start(); + + void addProcessingMessage(const std::string &message, const LLSD &args); + void addStringMessage(const std::string &message); + + std::string getStartString() const { return mStartString; } + +protected: + static void onCloseBtn(void* user_data); + + // returns true if this is done + bool isDone() const; + + virtual bool startQueue() = 0; + + void setStartString(const std::string& s) { mStartString = s; } + +protected: + // UI + LLScrollListCtrl* mMessages; + LLButton* mCloseBtn; + + // Object Queue + struct ObjectData + { + LLUUID mObjectId; + std::string mObjectName; + }; + typedef std::vector object_data_list_t; + + object_data_list_t mObjectList; + LLUUID mCurrentObjectID; + bool mDone; + + std::string mStartString; + bool mMono; + + typedef boost::function &, LLInventoryObject*, LLEventPump &)> fnQueueAction_t; + static void objectScriptProcessingQueueCoro(std::string action, LLHandle hfloater, object_data_list_t objectList, fnQueueAction_t func); + +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLFloaterCompileQueue +// +// This script queue will recompile each script. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +struct LLCompileQueueData +{ + LLUUID mQueueID; + LLUUID mItemId; + LLCompileQueueData(const LLUUID& q_id, const LLUUID& item_id) : + mQueueID(q_id), mItemId(item_id) {} +}; + +class LLFloaterCompileQueue : public LLFloaterScriptQueue +{ + friend class LLFloaterReg; +public: + + void experienceIdsReceived( const LLSD& content ); + bool hasExperience(const LLUUID& id)const; + +protected: + LLFloaterCompileQueue(const LLSD& key); + virtual ~LLFloaterCompileQueue(); + + virtual bool startQueue(); + + static bool processScript(LLHandle hfloater, const LLPointer &object, LLInventoryObject* inventory, LLEventPump &pump); + + //bool checkAssetId(const LLUUID &assetId); + static void handleHTTPResponse(std::string pumpName, const LLSD &expresult); + static void handleScriptRetrieval(const LLUUID& assetId, LLAssetType::EType type, void* userData, S32 status, LLExtStat extStatus); + +private: + static void processExperienceIdResults(LLSD result, LLUUID parent); + //uuid_list_t mAssetIds; // list of asset IDs processed. + uuid_list_t mExperienceIds; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLFloaterResetQueue +// +// This script queue will reset each script. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLFloaterResetQueue : public LLFloaterScriptQueue +{ + friend class LLFloaterReg; +protected: + LLFloaterResetQueue(const LLSD& key); + virtual ~LLFloaterResetQueue(); + + static bool resetObjectScripts(LLHandle hfloater, const LLPointer &object, LLInventoryObject* inventory, LLEventPump &pump); + + virtual bool startQueue(); +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLFloaterRunQueue +// +// This script queue will set each script as running. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLFloaterRunQueue : public LLFloaterScriptQueue +{ + friend class LLFloaterReg; +protected: + LLFloaterRunQueue(const LLSD& key); + virtual ~LLFloaterRunQueue(); + + static bool runObjectScripts(LLHandle hfloater, const LLPointer &object, LLInventoryObject* inventory, LLEventPump &pump); + + virtual bool startQueue(); +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLFloaterNotRunQueue +// +// This script queue will set each script as not running. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLFloaterNotRunQueue : public LLFloaterScriptQueue +{ + friend class LLFloaterReg; +protected: + LLFloaterNotRunQueue(const LLSD& key); + virtual ~LLFloaterNotRunQueue(); + + static bool stopObjectScripts(LLHandle hfloater, const LLPointer &object, LLInventoryObject* inventory, LLEventPump &pump); + + virtual bool startQueue(); +}; + +#endif // LL_LLCOMPILEQUEUE_H diff --git a/indra/newview/llcontrolavatar.cpp b/indra/newview/llcontrolavatar.cpp index 909e0f9e95..bb853a3ddc 100644 --- a/indra/newview/llcontrolavatar.cpp +++ b/indra/newview/llcontrolavatar.cpp @@ -1,719 +1,719 @@ -/** - * @file llcontrolavatar.cpp - * @brief Implementation for special dummy avatar used to drive rigged meshes. - * - * $LicenseInfo:firstyear=2017&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2017, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llcontrolavatar.h" -#include "llagent.h" // Get state values from here -#include "llviewerobjectlist.h" -#include "pipeline.h" -#include "llanimationstates.h" -#include "llviewercontrol.h" -#include "llmeshrepository.h" -#include "llviewerregion.h" -#include "llskinningutil.h" - -const F32 LLControlAvatar::MAX_LEGAL_OFFSET = 3.0f; -const F32 LLControlAvatar::MAX_LEGAL_SIZE = 64.0f; - -//static -boost::signals2::connection LLControlAvatar::sRegionChangedSlot; - -LLControlAvatar::LLControlAvatar(const LLUUID& id, const LLPCode pcode, LLViewerRegion* regionp) : - LLVOAvatar(id, pcode, regionp), - mPlaying(false), - mGlobalScale(1.0f), - mMarkedForDeath(false), - mRootVolp(NULL), - mControlAVBridge(NULL), - mScaleConstraintFixup(1.0), - mRegionChanged(false) -{ - mIsDummy = true; - mIsControlAvatar = true; - mEnableDefaultMotions = false; -} - -// virtual -LLControlAvatar::~LLControlAvatar() -{ - // Should already have been unlinked before destruction - llassert(!mRootVolp); -} - -// virtual -void LLControlAvatar::initInstance() -{ - // Potential optimizations here: avoid creating system - // avatar mesh content since it's not used. For now we just clean some - // things up after the fact in releaseMeshData(). - LLVOAvatar::initInstance(); - - createDrawable(&gPipeline); - updateJointLODs(); - updateGeometry(mDrawable); - hideSkirt(); - - mInitFlags |= 1<<4; -} - -const LLVOAvatar *LLControlAvatar::getAttachedAvatar() const -{ - if (mRootVolp && mRootVolp->isAttachment()) - { - return mRootVolp->getAvatarAncestor(); - } - return NULL; -} - -LLVOAvatar *LLControlAvatar::getAttachedAvatar() -{ - if (mRootVolp && mRootVolp->isAttachment()) - { - return mRootVolp->getAvatarAncestor(); - } - return NULL; -} - -void LLControlAvatar::getNewConstraintFixups(LLVector3& new_pos_fixup, F32& new_scale_fixup) const -{ - F32 max_legal_offset = MAX_LEGAL_OFFSET; - if (gSavedSettings.getControl("AnimatedObjectsMaxLegalOffset")) - { - max_legal_offset = gSavedSettings.getF32("AnimatedObjectsMaxLegalOffset"); - } - max_legal_offset = llmax(max_legal_offset,0.f); - - F32 max_legal_size = MAX_LEGAL_SIZE; - if (gSavedSettings.getControl("AnimatedObjectsMaxLegalSize")) - { - max_legal_size = gSavedSettings.getF32("AnimatedObjectsMaxLegalSize"); - } - max_legal_size = llmax(max_legal_size, 1.f); - - new_pos_fixup = LLVector3(); - new_scale_fixup = 1.0f; - LLVector3 vol_pos = mRootVolp->getRenderPosition(); - - // Fix up position if needed to prevent visual encroachment - if (box_valid_and_non_zero(getLastAnimExtents())) // wait for state to settle down - { - // The goal here is to ensure that the extent of the avatar's - // bounding box does not wander too far from the - // official position of the corresponding volume. We - // do this by tracking the distance and applying a - // correction to the control avatar position if - // needed. - const LLVector3 *extents = getLastAnimExtents(); - LLVector3 unshift_extents[2]; - unshift_extents[0] = extents[0] - mPositionConstraintFixup; - unshift_extents[1] = extents[1] - mPositionConstraintFixup; - LLVector3 box_dims = extents[1]-extents[0]; - F32 box_size = llmax(box_dims[0],box_dims[1],box_dims[2]); - - if (!mRootVolp->isAttachment()) - { - LLVector3 pos_box_offset = point_to_box_offset(vol_pos, unshift_extents); - F32 offset_dist = pos_box_offset.length(); - if (offset_dist > MAX_LEGAL_OFFSET && offset_dist > 0.f) - { - F32 target_dist = (offset_dist - MAX_LEGAL_OFFSET); - new_pos_fixup = (target_dist/offset_dist)*pos_box_offset; - } - if (new_pos_fixup != mPositionConstraintFixup) - { - LL_DEBUGS("ConstraintFix") << getFullname() << " pos fix, offset_dist " << offset_dist << " pos fixup " - << new_pos_fixup << " was " << mPositionConstraintFixup << LL_ENDL; - LL_DEBUGS("ConstraintFix") << "vol_pos " << vol_pos << LL_ENDL; - LL_DEBUGS("ConstraintFix") << "extents " << extents[0] << " " << extents[1] << LL_ENDL; - LL_DEBUGS("ConstraintFix") << "unshift_extents " << unshift_extents[0] << " " << unshift_extents[1] << LL_ENDL; - - } - } - if (box_size/mScaleConstraintFixup > MAX_LEGAL_SIZE) - { - new_scale_fixup = mScaleConstraintFixup* MAX_LEGAL_SIZE /box_size; - LL_DEBUGS("ConstraintFix") << getFullname() << " scale fix, box_size " << box_size << " fixup " - << mScaleConstraintFixup << " max legal " << MAX_LEGAL_SIZE - << " -> new scale " << new_scale_fixup << LL_ENDL; - } - } -} - -void LLControlAvatar::matchVolumeTransform() -{ - if (mRootVolp) - { - LLVector3 new_pos_fixup; - F32 new_scale_fixup; - if (mRegionChanged) - { - new_scale_fixup = mScaleConstraintFixup; - new_pos_fixup = mPositionConstraintFixup; - mRegionChanged = false; - } - else - { - getNewConstraintFixups(new_pos_fixup, new_scale_fixup); - } - mPositionConstraintFixup = new_pos_fixup; - mScaleConstraintFixup = new_scale_fixup; - - if (mRootVolp->isAttachment()) - { - LLVOAvatar *attached_av = getAttachedAvatar(); - if (attached_av) - { - LLViewerJointAttachment *attach = attached_av->getTargetAttachmentPoint(mRootVolp); - if (getRegion() && !isDead()) - { - setPositionAgent(mRootVolp->getRenderPosition()); - } - attach->updateWorldPRSParent(); - LLVector3 joint_pos = attach->getWorldPosition(); - LLQuaternion joint_rot = attach->getWorldRotation(); - LLVector3 obj_pos = mRootVolp->mDrawable->getPosition(); - LLQuaternion obj_rot = mRootVolp->mDrawable->getRotation(); - obj_pos.rotVec(joint_rot); - mRoot->setWorldPosition(obj_pos + joint_pos); - mRoot->setWorldRotation(obj_rot * joint_rot); - setRotation(mRoot->getRotation()); - - setGlobalScale(mScaleConstraintFixup); - } - else - { - LL_WARNS_ONCE() << "can't find attached av!" << LL_ENDL; - } - } - else - { - LLVector3 vol_pos = mRootVolp->getRenderPosition(); - - // FIXME: Currently if you're doing something like playing an - // animation that moves the pelvis (on an avatar or - // animated object), the name tag and debug text will be - // left behind. Ideally setPosition() would follow the - // skeleton around in a smarter way, so name tags, - // complexity info and such line up better. Should defer - // this until avatars also get fixed. - - LLQuaternion obj_rot; - if (mRootVolp->mDrawable) - { - obj_rot = mRootVolp->mDrawable->getRotation(); - } - else - { - obj_rot = mRootVolp->getRotation(); - } - - LLMatrix3 bind_mat; - - LLQuaternion bind_rot; -#define MATCH_BIND_SHAPE -#ifdef MATCH_BIND_SHAPE - // MAINT-8671 - based on a patch from Beq Janus - const LLMeshSkinInfo* skin_info = mRootVolp->getSkinInfo(); - if (skin_info) - { - LL_DEBUGS("BindShape") << getFullname() << " bind shape " << skin_info->mBindShapeMatrix << LL_ENDL; - bind_rot = LLSkinningUtil::getUnscaledQuaternion(LLMatrix4(skin_info->mBindShapeMatrix)); - } -#endif - setRotation(bind_rot*obj_rot); - mRoot->setWorldRotation(bind_rot*obj_rot); - if (getRegion() && !isDead()) - { - setPositionAgent(vol_pos); - } - mRoot->setPosition(vol_pos + mPositionConstraintFixup); - - setGlobalScale(mScaleConstraintFixup); - } - } -} - -void LLControlAvatar::setGlobalScale(F32 scale) -{ - if (scale <= 0.0) - { - LL_WARNS() << "invalid global scale " << scale << LL_ENDL; - return; - } - if (scale != mGlobalScale) - { - F32 adjust_scale = scale/mGlobalScale; - LL_INFOS() << "scale " << scale << " adjustment " << adjust_scale << LL_ENDL; - // should we be scaling from the pelvis or the root? - recursiveScaleJoint(mPelvisp,adjust_scale); - mGlobalScale = scale; - } -} - -void LLControlAvatar::recursiveScaleJoint(LLJoint* joint, F32 factor) -{ - joint->setScale(factor * joint->getScale()); - - for (LLJoint::joints_t::iterator iter = joint->mChildren.begin(); - iter != joint->mChildren.end(); ++iter) - { - LLJoint* child = *iter; - recursiveScaleJoint(child, factor); - } -} - -// Based on LLViewerJointAttachment::setupDrawable(), without the attaching part. -void LLControlAvatar::updateVolumeGeom() -{ - if (!mRootVolp->mDrawable) - return; - if (mRootVolp->mDrawable->isActive()) - { - mRootVolp->mDrawable->makeStatic(false); - } - mRootVolp->mDrawable->makeActive(); - gPipeline.markMoved(mRootVolp->mDrawable); - gPipeline.markTextured(mRootVolp->mDrawable); // face may need to change draw pool to/from POOL_HUD - - LLViewerObject::const_child_list_t& child_list = mRootVolp->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); ++iter) - { - LLViewerObject* childp = *iter; - if (childp && childp->mDrawable.notNull()) - { - gPipeline.markTextured(childp->mDrawable); // face may need to change draw pool to/from POOL_HUD - gPipeline.markMoved(childp->mDrawable); - } - } - - gPipeline.markRebuild(mRootVolp->mDrawable, LLDrawable::REBUILD_ALL); - mRootVolp->markForUpdate(); - - // Note that attachment overrides aren't needed here, have already - // been applied at the time the mControlAvatar was created, in - // llvovolume.cpp. - - matchVolumeTransform(); - - // Initial exploration of allowing scaling skeleton to match root - // prim bounding box. If enabled, would probably be controlled by - // an additional checkbox and default to off. Not enabled for - // initial release. - - // What should the scale be? What we really want is the ratio - // between the scale at which the object was originally designed - // and rigged, and the scale to which it has been subsequently - // modified - for example, if the object has been scaled down by a - // factor of 2 then we should use 0.5 as the global scale. But we - // don't have the original scale stored anywhere, just the current - // scale. Possibilities - 1) remember the original scale - // somewhere, 2) add another field to let the user specify the - // global scale, 3) approximate the original scale by looking at - // the proportions of the skeleton after joint positions have - // been applied - - //LLVector3 obj_scale = obj->getScale(); - //F32 obj_scale_z = llmax(obj_scale[2],0.1f); - //setGlobalScale(obj_scale_z/2.0f); // roughly fit avatar height range (2m) into object height -} - -LLControlAvatar *LLControlAvatar::createControlAvatar(LLVOVolume *obj) -{ - LLControlAvatar *cav = (LLControlAvatar*)gObjectList.createObjectViewer(LL_PCODE_LEGACY_AVATAR, gAgent.getRegion(), CO_FLAG_CONTROL_AVATAR); - - if (cav) - { - cav->mRootVolp = obj; - - // Sync up position/rotation with object - cav->matchVolumeTransform(); - } - - return cav; -} - -void LLControlAvatar::markForDeath() -{ - mMarkedForDeath = true; - // object unlinked cav and might be dead already - // might need to clean mControlAVBridge here as well - mRootVolp = NULL; -} - -void LLControlAvatar::idleUpdate(LLAgent &agent, const F64 &time) -{ - if (mMarkedForDeath) - { - markDead(); - mMarkedForDeath = false; - } - else - { - LLVOAvatar::idleUpdate(agent,time); - } -} - -void LLControlAvatar::markDead() -{ - mRootVolp = NULL; - super::markDead(); - mControlAVBridge = NULL; -} - -bool LLControlAvatar::computeNeedsUpdate() -{ - computeUpdatePeriod(); - - // Animesh attachments are a special case. Should have the same update cadence as their attached parent avatar. - LLVOAvatar *attached_av = getAttachedAvatar(); - if (attached_av) - { - // Have to run computeNeedsUpdate() for attached av in - // case it hasn't run updateCharacter() already this - // frame. Note this means that the attached av will - // run computeNeedsUpdate() multiple times per frame - // if it has animesh attachments. Results will be - // consistent except for the corner case of exceeding - // MAX_IMPOSTOR_INTERVAL in one call but not another, - // which should be rare. - attached_av->computeNeedsUpdate(); - mNeedsImpostorUpdate = attached_av->mNeedsImpostorUpdate; - if (mNeedsImpostorUpdate) - { - mLastImpostorUpdateReason = 12; - } - return mNeedsImpostorUpdate; - } - return LLVOAvatar::computeNeedsUpdate(); -} - -bool LLControlAvatar::updateCharacter(LLAgent &agent) -{ - return LLVOAvatar::updateCharacter(agent); -} - -//virtual -void LLControlAvatar::updateDebugText() -{ - if (gSavedSettings.getBOOL("DebugAnimatedObjects")) - { - S32 total_linkset_count = 0; - if (mRootVolp) - { - total_linkset_count = 1 + mRootVolp->getChildren().size(); - } - std::vector volumes; - getAnimatedVolumes(volumes); - S32 animated_volume_count = volumes.size(); - std::string active_string; - std::string type_string; - std::string lod_string; - std::string animated_object_flag_string; - S32 total_tris = 0; - S32 total_verts = 0; - F32 est_tris = 0.f; - F32 est_streaming_tris = 0.f; - F32 streaming_cost = 0.f; - std::string cam_dist_string = ""; - S32 cam_dist_count = 0; - F32 lod_radius = mRootVolp ? mRootVolp->mLODRadius : 0.f; - - for (std::vector::iterator it = volumes.begin(); - it != volumes.end(); ++it) - { - LLVOVolume *volp = *it; - S32 verts = 0; - total_tris += volp->getTriangleCount(&verts); - total_verts += verts; - est_tris += volp->getEstTrianglesMax(); - est_streaming_tris += volp->getEstTrianglesStreamingCost(); - streaming_cost += volp->getStreamingCost(); - lod_string += llformat("%d",volp->getLOD()); - if (volp && volp->mDrawable) - { - bool is_animated_flag = volp->getExtendedMeshFlags() & LLExtendedMeshParams::ANIMATED_MESH_ENABLED_FLAG; - if (is_animated_flag) - { - animated_object_flag_string += "1"; - } - else - { - animated_object_flag_string += "0"; - } - if (volp->mDrawable->isActive()) - { - active_string += "A"; - } - else - { - active_string += "S"; - } - if (volp->isRiggedMesh()) - { - // Rigged/animatable mesh - type_string += "R"; - lod_radius = volp->mLODRadius; - } - else if (volp->isMesh()) - { - // Static mesh - type_string += "M"; - } - else - { - // Any other prim - type_string += "P"; - } - if (cam_dist_count < 4) - { - cam_dist_string += LLStringOps::getReadableNumber(volp->mLODDistance) + "/" + - LLStringOps::getReadableNumber(volp->mLODAdjustedDistance) + " "; - cam_dist_count++; - } - } - else - { - active_string += "-"; - type_string += "-"; - } - } - addDebugText(llformat("CAV obj %d anim %d active %s impost %d upprd %d strcst %f", - total_linkset_count, animated_volume_count, - active_string.c_str(), (S32) isImpostor(), getUpdatePeriod(), streaming_cost)); - addDebugText(llformat("types %s lods %s", type_string.c_str(), lod_string.c_str())); - addDebugText(llformat("flags %s", animated_object_flag_string.c_str())); - addDebugText(llformat("tris %d (est %.1f, streaming %.1f), verts %d", total_tris, est_tris, est_streaming_tris, total_verts)); - addDebugText(llformat("pxarea %s rank %d", LLStringOps::getReadableNumber(getPixelArea()).c_str(), getVisibilityRank())); - addDebugText(llformat("lod_radius %s dists %s", LLStringOps::getReadableNumber(lod_radius).c_str(),cam_dist_string.c_str())); - if (mPositionConstraintFixup.length() > 0.0f || mScaleConstraintFixup != 1.0f) - { - addDebugText(llformat("pos fix (%.1f %.1f %.1f) scale %f", - mPositionConstraintFixup[0], - mPositionConstraintFixup[1], - mPositionConstraintFixup[2], - mScaleConstraintFixup)); - } - -#if 0 - std::string region_name = "no region"; - if (mRootVolp->getRegion()) - { - region_name = mRootVolp->getRegion()->getName(); - } - std::string skel_region_name = "skel no region"; - if (getRegion()) - { - skel_region_name = getRegion()->getName(); - } - addDebugText(llformat("region %x %s skel %x %s", - mRootVolp->getRegion(), region_name.c_str(), - getRegion(), skel_region_name.c_str())); -#endif - - } - LLVOAvatar::updateDebugText(); -} - -void LLControlAvatar::getAnimatedVolumes(std::vector& volumes) -{ - if (!mRootVolp) - { - return; - } - - volumes.push_back(mRootVolp); - - LLViewerObject::const_child_list_t& child_list = mRootVolp->getChildren(); - for (LLViewerObject::const_child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); ++iter) - { - LLViewerObject* childp = *iter; - LLVOVolume *child_volp = dynamic_cast(childp); - if (child_volp && child_volp->isAnimatedObject()) - { - volumes.push_back(child_volp); - } - } -} - -// This is called after an associated object receives an animation -// message. Combine the signaled animations for all associated objects -// and process any resulting state changes. -void LLControlAvatar::updateAnimations() -{ - if (!mRootVolp) - { - LL_WARNS_ONCE("AnimatedObjectsNotify") << "No root vol" << LL_ENDL; - return; - } - - std::vector volumes; - getAnimatedVolumes(volumes); - - // Rebuild mSignaledAnimations from the associated volumes. - std::map anims; - for (std::vector::iterator vol_it = volumes.begin(); vol_it != volumes.end(); ++vol_it) - { - LLVOVolume *volp = *vol_it; - //LL_INFOS("AnimatedObjects") << "updating anim for vol " << volp->getID() << " root " << mRootVolp->getID() << LL_ENDL; - signaled_animation_map_t& signaled_animations = LLObjectSignaledAnimationMap::instance().getMap()[volp->getID()]; - for (std::map::iterator anim_it = signaled_animations.begin(); - anim_it != signaled_animations.end(); - ++anim_it) - { - std::map::iterator found_anim_it = anims.find(anim_it->first); - if (found_anim_it != anims.end()) - { - // Animation already present, use the larger sequence id - anims[anim_it->first] = llmax(found_anim_it->second, anim_it->second); - } - else - { - // Animation not already present, use this sequence id. - anims[anim_it->first] = anim_it->second; - } - LL_DEBUGS("AnimatedObjectsNotify") << "found anim for vol " << volp->getID() << " anim " << anim_it->first << " root " << mRootVolp->getID() << LL_ENDL; - } - } - if (!mPlaying) - { - mPlaying = true; - //if (!mRootVolp->isAnySelected()) - { - updateVolumeGeom(); - mRootVolp->recursiveMarkForUpdate(); - } - } - - mSignaledAnimations = anims; - processAnimationStateChanges(); -} - -// virtual -LLViewerObject* LLControlAvatar::lineSegmentIntersectRiggedAttachments(const LLVector4a& start, const LLVector4a& end, - S32 face, - bool pick_transparent, - bool pick_rigged, - bool pick_unselectable, - S32* face_hit, - LLVector4a* intersection, - LLVector2* tex_coord, - LLVector4a* normal, - LLVector4a* tangent) -{ - if (!mRootVolp) - { - return NULL; - } - - LLViewerObject* hit = NULL; - - if (lineSegmentBoundingBox(start, end)) - { - LLVector4a local_end = end; - LLVector4a local_intersection; - if (mRootVolp->lineSegmentIntersect(start, local_end, face, pick_transparent, pick_rigged, pick_unselectable, face_hit, &local_intersection, tex_coord, normal, tangent)) - { - local_end = local_intersection; - if (intersection) - { - *intersection = local_intersection; - } - hit = mRootVolp; - } - else - { - std::vector volumes; - getAnimatedVolumes(volumes); - - for (std::vector::iterator vol_it = volumes.begin(); vol_it != volumes.end(); ++vol_it) - { - LLVOVolume *volp = *vol_it; - if (mRootVolp != volp && volp->lineSegmentIntersect(start, local_end, face, pick_transparent, pick_rigged, pick_unselectable, face_hit, &local_intersection, tex_coord, normal, tangent)) - { - local_end = local_intersection; - if (intersection) - { - *intersection = local_intersection; - } - hit = volp; - break; - } - } - } - } - - return hit; -} - -// virtual -std::string LLControlAvatar::getFullname() const -{ - if (mRootVolp) - { - return "AO_" + mRootVolp->getID().getString(); - } - else - { - return "AO_no_root_vol"; - } -} - -// virtual -bool LLControlAvatar::shouldRenderRigged() const -{ - const LLVOAvatar *attached_av = getAttachedAvatar(); - if (attached_av) - { - return attached_av->shouldRenderRigged(); - } - return true; -} - -// virtual -bool LLControlAvatar::isImpostor() -{ - // Attached animated objects should match state of their attached av. - LLVOAvatar *attached_av = getAttachedAvatar(); - if (attached_av) - { - return attached_av->isImpostor(); - } - return LLVOAvatar::isImpostor(); -} - -//static -void LLControlAvatar::onRegionChanged() -{ - std::vector::iterator it = LLCharacter::sInstances.begin(); - for ( ; it != LLCharacter::sInstances.end(); ++it) - { - LLControlAvatar* cav = dynamic_cast(*it); - if (!cav) continue; - cav->mRegionChanged = true; - } -} +/** + * @file llcontrolavatar.cpp + * @brief Implementation for special dummy avatar used to drive rigged meshes. + * + * $LicenseInfo:firstyear=2017&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2017, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llcontrolavatar.h" +#include "llagent.h" // Get state values from here +#include "llviewerobjectlist.h" +#include "pipeline.h" +#include "llanimationstates.h" +#include "llviewercontrol.h" +#include "llmeshrepository.h" +#include "llviewerregion.h" +#include "llskinningutil.h" + +const F32 LLControlAvatar::MAX_LEGAL_OFFSET = 3.0f; +const F32 LLControlAvatar::MAX_LEGAL_SIZE = 64.0f; + +//static +boost::signals2::connection LLControlAvatar::sRegionChangedSlot; + +LLControlAvatar::LLControlAvatar(const LLUUID& id, const LLPCode pcode, LLViewerRegion* regionp) : + LLVOAvatar(id, pcode, regionp), + mPlaying(false), + mGlobalScale(1.0f), + mMarkedForDeath(false), + mRootVolp(NULL), + mControlAVBridge(NULL), + mScaleConstraintFixup(1.0), + mRegionChanged(false) +{ + mIsDummy = true; + mIsControlAvatar = true; + mEnableDefaultMotions = false; +} + +// virtual +LLControlAvatar::~LLControlAvatar() +{ + // Should already have been unlinked before destruction + llassert(!mRootVolp); +} + +// virtual +void LLControlAvatar::initInstance() +{ + // Potential optimizations here: avoid creating system + // avatar mesh content since it's not used. For now we just clean some + // things up after the fact in releaseMeshData(). + LLVOAvatar::initInstance(); + + createDrawable(&gPipeline); + updateJointLODs(); + updateGeometry(mDrawable); + hideSkirt(); + + mInitFlags |= 1<<4; +} + +const LLVOAvatar *LLControlAvatar::getAttachedAvatar() const +{ + if (mRootVolp && mRootVolp->isAttachment()) + { + return mRootVolp->getAvatarAncestor(); + } + return NULL; +} + +LLVOAvatar *LLControlAvatar::getAttachedAvatar() +{ + if (mRootVolp && mRootVolp->isAttachment()) + { + return mRootVolp->getAvatarAncestor(); + } + return NULL; +} + +void LLControlAvatar::getNewConstraintFixups(LLVector3& new_pos_fixup, F32& new_scale_fixup) const +{ + F32 max_legal_offset = MAX_LEGAL_OFFSET; + if (gSavedSettings.getControl("AnimatedObjectsMaxLegalOffset")) + { + max_legal_offset = gSavedSettings.getF32("AnimatedObjectsMaxLegalOffset"); + } + max_legal_offset = llmax(max_legal_offset,0.f); + + F32 max_legal_size = MAX_LEGAL_SIZE; + if (gSavedSettings.getControl("AnimatedObjectsMaxLegalSize")) + { + max_legal_size = gSavedSettings.getF32("AnimatedObjectsMaxLegalSize"); + } + max_legal_size = llmax(max_legal_size, 1.f); + + new_pos_fixup = LLVector3(); + new_scale_fixup = 1.0f; + LLVector3 vol_pos = mRootVolp->getRenderPosition(); + + // Fix up position if needed to prevent visual encroachment + if (box_valid_and_non_zero(getLastAnimExtents())) // wait for state to settle down + { + // The goal here is to ensure that the extent of the avatar's + // bounding box does not wander too far from the + // official position of the corresponding volume. We + // do this by tracking the distance and applying a + // correction to the control avatar position if + // needed. + const LLVector3 *extents = getLastAnimExtents(); + LLVector3 unshift_extents[2]; + unshift_extents[0] = extents[0] - mPositionConstraintFixup; + unshift_extents[1] = extents[1] - mPositionConstraintFixup; + LLVector3 box_dims = extents[1]-extents[0]; + F32 box_size = llmax(box_dims[0],box_dims[1],box_dims[2]); + + if (!mRootVolp->isAttachment()) + { + LLVector3 pos_box_offset = point_to_box_offset(vol_pos, unshift_extents); + F32 offset_dist = pos_box_offset.length(); + if (offset_dist > MAX_LEGAL_OFFSET && offset_dist > 0.f) + { + F32 target_dist = (offset_dist - MAX_LEGAL_OFFSET); + new_pos_fixup = (target_dist/offset_dist)*pos_box_offset; + } + if (new_pos_fixup != mPositionConstraintFixup) + { + LL_DEBUGS("ConstraintFix") << getFullname() << " pos fix, offset_dist " << offset_dist << " pos fixup " + << new_pos_fixup << " was " << mPositionConstraintFixup << LL_ENDL; + LL_DEBUGS("ConstraintFix") << "vol_pos " << vol_pos << LL_ENDL; + LL_DEBUGS("ConstraintFix") << "extents " << extents[0] << " " << extents[1] << LL_ENDL; + LL_DEBUGS("ConstraintFix") << "unshift_extents " << unshift_extents[0] << " " << unshift_extents[1] << LL_ENDL; + + } + } + if (box_size/mScaleConstraintFixup > MAX_LEGAL_SIZE) + { + new_scale_fixup = mScaleConstraintFixup* MAX_LEGAL_SIZE /box_size; + LL_DEBUGS("ConstraintFix") << getFullname() << " scale fix, box_size " << box_size << " fixup " + << mScaleConstraintFixup << " max legal " << MAX_LEGAL_SIZE + << " -> new scale " << new_scale_fixup << LL_ENDL; + } + } +} + +void LLControlAvatar::matchVolumeTransform() +{ + if (mRootVolp) + { + LLVector3 new_pos_fixup; + F32 new_scale_fixup; + if (mRegionChanged) + { + new_scale_fixup = mScaleConstraintFixup; + new_pos_fixup = mPositionConstraintFixup; + mRegionChanged = false; + } + else + { + getNewConstraintFixups(new_pos_fixup, new_scale_fixup); + } + mPositionConstraintFixup = new_pos_fixup; + mScaleConstraintFixup = new_scale_fixup; + + if (mRootVolp->isAttachment()) + { + LLVOAvatar *attached_av = getAttachedAvatar(); + if (attached_av) + { + LLViewerJointAttachment *attach = attached_av->getTargetAttachmentPoint(mRootVolp); + if (getRegion() && !isDead()) + { + setPositionAgent(mRootVolp->getRenderPosition()); + } + attach->updateWorldPRSParent(); + LLVector3 joint_pos = attach->getWorldPosition(); + LLQuaternion joint_rot = attach->getWorldRotation(); + LLVector3 obj_pos = mRootVolp->mDrawable->getPosition(); + LLQuaternion obj_rot = mRootVolp->mDrawable->getRotation(); + obj_pos.rotVec(joint_rot); + mRoot->setWorldPosition(obj_pos + joint_pos); + mRoot->setWorldRotation(obj_rot * joint_rot); + setRotation(mRoot->getRotation()); + + setGlobalScale(mScaleConstraintFixup); + } + else + { + LL_WARNS_ONCE() << "can't find attached av!" << LL_ENDL; + } + } + else + { + LLVector3 vol_pos = mRootVolp->getRenderPosition(); + + // FIXME: Currently if you're doing something like playing an + // animation that moves the pelvis (on an avatar or + // animated object), the name tag and debug text will be + // left behind. Ideally setPosition() would follow the + // skeleton around in a smarter way, so name tags, + // complexity info and such line up better. Should defer + // this until avatars also get fixed. + + LLQuaternion obj_rot; + if (mRootVolp->mDrawable) + { + obj_rot = mRootVolp->mDrawable->getRotation(); + } + else + { + obj_rot = mRootVolp->getRotation(); + } + + LLMatrix3 bind_mat; + + LLQuaternion bind_rot; +#define MATCH_BIND_SHAPE +#ifdef MATCH_BIND_SHAPE + // MAINT-8671 - based on a patch from Beq Janus + const LLMeshSkinInfo* skin_info = mRootVolp->getSkinInfo(); + if (skin_info) + { + LL_DEBUGS("BindShape") << getFullname() << " bind shape " << skin_info->mBindShapeMatrix << LL_ENDL; + bind_rot = LLSkinningUtil::getUnscaledQuaternion(LLMatrix4(skin_info->mBindShapeMatrix)); + } +#endif + setRotation(bind_rot*obj_rot); + mRoot->setWorldRotation(bind_rot*obj_rot); + if (getRegion() && !isDead()) + { + setPositionAgent(vol_pos); + } + mRoot->setPosition(vol_pos + mPositionConstraintFixup); + + setGlobalScale(mScaleConstraintFixup); + } + } +} + +void LLControlAvatar::setGlobalScale(F32 scale) +{ + if (scale <= 0.0) + { + LL_WARNS() << "invalid global scale " << scale << LL_ENDL; + return; + } + if (scale != mGlobalScale) + { + F32 adjust_scale = scale/mGlobalScale; + LL_INFOS() << "scale " << scale << " adjustment " << adjust_scale << LL_ENDL; + // should we be scaling from the pelvis or the root? + recursiveScaleJoint(mPelvisp,adjust_scale); + mGlobalScale = scale; + } +} + +void LLControlAvatar::recursiveScaleJoint(LLJoint* joint, F32 factor) +{ + joint->setScale(factor * joint->getScale()); + + for (LLJoint::joints_t::iterator iter = joint->mChildren.begin(); + iter != joint->mChildren.end(); ++iter) + { + LLJoint* child = *iter; + recursiveScaleJoint(child, factor); + } +} + +// Based on LLViewerJointAttachment::setupDrawable(), without the attaching part. +void LLControlAvatar::updateVolumeGeom() +{ + if (!mRootVolp->mDrawable) + return; + if (mRootVolp->mDrawable->isActive()) + { + mRootVolp->mDrawable->makeStatic(false); + } + mRootVolp->mDrawable->makeActive(); + gPipeline.markMoved(mRootVolp->mDrawable); + gPipeline.markTextured(mRootVolp->mDrawable); // face may need to change draw pool to/from POOL_HUD + + LLViewerObject::const_child_list_t& child_list = mRootVolp->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); ++iter) + { + LLViewerObject* childp = *iter; + if (childp && childp->mDrawable.notNull()) + { + gPipeline.markTextured(childp->mDrawable); // face may need to change draw pool to/from POOL_HUD + gPipeline.markMoved(childp->mDrawable); + } + } + + gPipeline.markRebuild(mRootVolp->mDrawable, LLDrawable::REBUILD_ALL); + mRootVolp->markForUpdate(); + + // Note that attachment overrides aren't needed here, have already + // been applied at the time the mControlAvatar was created, in + // llvovolume.cpp. + + matchVolumeTransform(); + + // Initial exploration of allowing scaling skeleton to match root + // prim bounding box. If enabled, would probably be controlled by + // an additional checkbox and default to off. Not enabled for + // initial release. + + // What should the scale be? What we really want is the ratio + // between the scale at which the object was originally designed + // and rigged, and the scale to which it has been subsequently + // modified - for example, if the object has been scaled down by a + // factor of 2 then we should use 0.5 as the global scale. But we + // don't have the original scale stored anywhere, just the current + // scale. Possibilities - 1) remember the original scale + // somewhere, 2) add another field to let the user specify the + // global scale, 3) approximate the original scale by looking at + // the proportions of the skeleton after joint positions have + // been applied + + //LLVector3 obj_scale = obj->getScale(); + //F32 obj_scale_z = llmax(obj_scale[2],0.1f); + //setGlobalScale(obj_scale_z/2.0f); // roughly fit avatar height range (2m) into object height +} + +LLControlAvatar *LLControlAvatar::createControlAvatar(LLVOVolume *obj) +{ + LLControlAvatar *cav = (LLControlAvatar*)gObjectList.createObjectViewer(LL_PCODE_LEGACY_AVATAR, gAgent.getRegion(), CO_FLAG_CONTROL_AVATAR); + + if (cav) + { + cav->mRootVolp = obj; + + // Sync up position/rotation with object + cav->matchVolumeTransform(); + } + + return cav; +} + +void LLControlAvatar::markForDeath() +{ + mMarkedForDeath = true; + // object unlinked cav and might be dead already + // might need to clean mControlAVBridge here as well + mRootVolp = NULL; +} + +void LLControlAvatar::idleUpdate(LLAgent &agent, const F64 &time) +{ + if (mMarkedForDeath) + { + markDead(); + mMarkedForDeath = false; + } + else + { + LLVOAvatar::idleUpdate(agent,time); + } +} + +void LLControlAvatar::markDead() +{ + mRootVolp = NULL; + super::markDead(); + mControlAVBridge = NULL; +} + +bool LLControlAvatar::computeNeedsUpdate() +{ + computeUpdatePeriod(); + + // Animesh attachments are a special case. Should have the same update cadence as their attached parent avatar. + LLVOAvatar *attached_av = getAttachedAvatar(); + if (attached_av) + { + // Have to run computeNeedsUpdate() for attached av in + // case it hasn't run updateCharacter() already this + // frame. Note this means that the attached av will + // run computeNeedsUpdate() multiple times per frame + // if it has animesh attachments. Results will be + // consistent except for the corner case of exceeding + // MAX_IMPOSTOR_INTERVAL in one call but not another, + // which should be rare. + attached_av->computeNeedsUpdate(); + mNeedsImpostorUpdate = attached_av->mNeedsImpostorUpdate; + if (mNeedsImpostorUpdate) + { + mLastImpostorUpdateReason = 12; + } + return mNeedsImpostorUpdate; + } + return LLVOAvatar::computeNeedsUpdate(); +} + +bool LLControlAvatar::updateCharacter(LLAgent &agent) +{ + return LLVOAvatar::updateCharacter(agent); +} + +//virtual +void LLControlAvatar::updateDebugText() +{ + if (gSavedSettings.getBOOL("DebugAnimatedObjects")) + { + S32 total_linkset_count = 0; + if (mRootVolp) + { + total_linkset_count = 1 + mRootVolp->getChildren().size(); + } + std::vector volumes; + getAnimatedVolumes(volumes); + S32 animated_volume_count = volumes.size(); + std::string active_string; + std::string type_string; + std::string lod_string; + std::string animated_object_flag_string; + S32 total_tris = 0; + S32 total_verts = 0; + F32 est_tris = 0.f; + F32 est_streaming_tris = 0.f; + F32 streaming_cost = 0.f; + std::string cam_dist_string = ""; + S32 cam_dist_count = 0; + F32 lod_radius = mRootVolp ? mRootVolp->mLODRadius : 0.f; + + for (std::vector::iterator it = volumes.begin(); + it != volumes.end(); ++it) + { + LLVOVolume *volp = *it; + S32 verts = 0; + total_tris += volp->getTriangleCount(&verts); + total_verts += verts; + est_tris += volp->getEstTrianglesMax(); + est_streaming_tris += volp->getEstTrianglesStreamingCost(); + streaming_cost += volp->getStreamingCost(); + lod_string += llformat("%d",volp->getLOD()); + if (volp && volp->mDrawable) + { + bool is_animated_flag = volp->getExtendedMeshFlags() & LLExtendedMeshParams::ANIMATED_MESH_ENABLED_FLAG; + if (is_animated_flag) + { + animated_object_flag_string += "1"; + } + else + { + animated_object_flag_string += "0"; + } + if (volp->mDrawable->isActive()) + { + active_string += "A"; + } + else + { + active_string += "S"; + } + if (volp->isRiggedMesh()) + { + // Rigged/animatable mesh + type_string += "R"; + lod_radius = volp->mLODRadius; + } + else if (volp->isMesh()) + { + // Static mesh + type_string += "M"; + } + else + { + // Any other prim + type_string += "P"; + } + if (cam_dist_count < 4) + { + cam_dist_string += LLStringOps::getReadableNumber(volp->mLODDistance) + "/" + + LLStringOps::getReadableNumber(volp->mLODAdjustedDistance) + " "; + cam_dist_count++; + } + } + else + { + active_string += "-"; + type_string += "-"; + } + } + addDebugText(llformat("CAV obj %d anim %d active %s impost %d upprd %d strcst %f", + total_linkset_count, animated_volume_count, + active_string.c_str(), (S32) isImpostor(), getUpdatePeriod(), streaming_cost)); + addDebugText(llformat("types %s lods %s", type_string.c_str(), lod_string.c_str())); + addDebugText(llformat("flags %s", animated_object_flag_string.c_str())); + addDebugText(llformat("tris %d (est %.1f, streaming %.1f), verts %d", total_tris, est_tris, est_streaming_tris, total_verts)); + addDebugText(llformat("pxarea %s rank %d", LLStringOps::getReadableNumber(getPixelArea()).c_str(), getVisibilityRank())); + addDebugText(llformat("lod_radius %s dists %s", LLStringOps::getReadableNumber(lod_radius).c_str(),cam_dist_string.c_str())); + if (mPositionConstraintFixup.length() > 0.0f || mScaleConstraintFixup != 1.0f) + { + addDebugText(llformat("pos fix (%.1f %.1f %.1f) scale %f", + mPositionConstraintFixup[0], + mPositionConstraintFixup[1], + mPositionConstraintFixup[2], + mScaleConstraintFixup)); + } + +#if 0 + std::string region_name = "no region"; + if (mRootVolp->getRegion()) + { + region_name = mRootVolp->getRegion()->getName(); + } + std::string skel_region_name = "skel no region"; + if (getRegion()) + { + skel_region_name = getRegion()->getName(); + } + addDebugText(llformat("region %x %s skel %x %s", + mRootVolp->getRegion(), region_name.c_str(), + getRegion(), skel_region_name.c_str())); +#endif + + } + LLVOAvatar::updateDebugText(); +} + +void LLControlAvatar::getAnimatedVolumes(std::vector& volumes) +{ + if (!mRootVolp) + { + return; + } + + volumes.push_back(mRootVolp); + + LLViewerObject::const_child_list_t& child_list = mRootVolp->getChildren(); + for (LLViewerObject::const_child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); ++iter) + { + LLViewerObject* childp = *iter; + LLVOVolume *child_volp = dynamic_cast(childp); + if (child_volp && child_volp->isAnimatedObject()) + { + volumes.push_back(child_volp); + } + } +} + +// This is called after an associated object receives an animation +// message. Combine the signaled animations for all associated objects +// and process any resulting state changes. +void LLControlAvatar::updateAnimations() +{ + if (!mRootVolp) + { + LL_WARNS_ONCE("AnimatedObjectsNotify") << "No root vol" << LL_ENDL; + return; + } + + std::vector volumes; + getAnimatedVolumes(volumes); + + // Rebuild mSignaledAnimations from the associated volumes. + std::map anims; + for (std::vector::iterator vol_it = volumes.begin(); vol_it != volumes.end(); ++vol_it) + { + LLVOVolume *volp = *vol_it; + //LL_INFOS("AnimatedObjects") << "updating anim for vol " << volp->getID() << " root " << mRootVolp->getID() << LL_ENDL; + signaled_animation_map_t& signaled_animations = LLObjectSignaledAnimationMap::instance().getMap()[volp->getID()]; + for (std::map::iterator anim_it = signaled_animations.begin(); + anim_it != signaled_animations.end(); + ++anim_it) + { + std::map::iterator found_anim_it = anims.find(anim_it->first); + if (found_anim_it != anims.end()) + { + // Animation already present, use the larger sequence id + anims[anim_it->first] = llmax(found_anim_it->second, anim_it->second); + } + else + { + // Animation not already present, use this sequence id. + anims[anim_it->first] = anim_it->second; + } + LL_DEBUGS("AnimatedObjectsNotify") << "found anim for vol " << volp->getID() << " anim " << anim_it->first << " root " << mRootVolp->getID() << LL_ENDL; + } + } + if (!mPlaying) + { + mPlaying = true; + //if (!mRootVolp->isAnySelected()) + { + updateVolumeGeom(); + mRootVolp->recursiveMarkForUpdate(); + } + } + + mSignaledAnimations = anims; + processAnimationStateChanges(); +} + +// virtual +LLViewerObject* LLControlAvatar::lineSegmentIntersectRiggedAttachments(const LLVector4a& start, const LLVector4a& end, + S32 face, + bool pick_transparent, + bool pick_rigged, + bool pick_unselectable, + S32* face_hit, + LLVector4a* intersection, + LLVector2* tex_coord, + LLVector4a* normal, + LLVector4a* tangent) +{ + if (!mRootVolp) + { + return NULL; + } + + LLViewerObject* hit = NULL; + + if (lineSegmentBoundingBox(start, end)) + { + LLVector4a local_end = end; + LLVector4a local_intersection; + if (mRootVolp->lineSegmentIntersect(start, local_end, face, pick_transparent, pick_rigged, pick_unselectable, face_hit, &local_intersection, tex_coord, normal, tangent)) + { + local_end = local_intersection; + if (intersection) + { + *intersection = local_intersection; + } + hit = mRootVolp; + } + else + { + std::vector volumes; + getAnimatedVolumes(volumes); + + for (std::vector::iterator vol_it = volumes.begin(); vol_it != volumes.end(); ++vol_it) + { + LLVOVolume *volp = *vol_it; + if (mRootVolp != volp && volp->lineSegmentIntersect(start, local_end, face, pick_transparent, pick_rigged, pick_unselectable, face_hit, &local_intersection, tex_coord, normal, tangent)) + { + local_end = local_intersection; + if (intersection) + { + *intersection = local_intersection; + } + hit = volp; + break; + } + } + } + } + + return hit; +} + +// virtual +std::string LLControlAvatar::getFullname() const +{ + if (mRootVolp) + { + return "AO_" + mRootVolp->getID().getString(); + } + else + { + return "AO_no_root_vol"; + } +} + +// virtual +bool LLControlAvatar::shouldRenderRigged() const +{ + const LLVOAvatar *attached_av = getAttachedAvatar(); + if (attached_av) + { + return attached_av->shouldRenderRigged(); + } + return true; +} + +// virtual +bool LLControlAvatar::isImpostor() +{ + // Attached animated objects should match state of their attached av. + LLVOAvatar *attached_av = getAttachedAvatar(); + if (attached_av) + { + return attached_av->isImpostor(); + } + return LLVOAvatar::isImpostor(); +} + +//static +void LLControlAvatar::onRegionChanged() +{ + std::vector::iterator it = LLCharacter::sInstances.begin(); + for ( ; it != LLCharacter::sInstances.end(); ++it) + { + LLControlAvatar* cav = dynamic_cast(*it); + if (!cav) continue; + cav->mRegionChanged = true; + } +} diff --git a/indra/newview/llcontrolavatar.h b/indra/newview/llcontrolavatar.h index ec185e2f6b..0d94fe08ee 100644 --- a/indra/newview/llcontrolavatar.h +++ b/indra/newview/llcontrolavatar.h @@ -1,124 +1,124 @@ -/** - * @file llcontrolavatar.h - * @brief Special dummy avatar used to drive rigged meshes. - * - * $LicenseInfo:firstyear=2017&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2017, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_CONTROLAVATAR_H -#define LL_CONTROLAVATAR_H - -#include "llvoavatar.h" -#include "llvovolume.h" - -class LLControlAvatar: - public LLVOAvatar -{ - LOG_CLASS(LLControlAvatar); - - using super = LLVOAvatar; - -public: - LLControlAvatar(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); - virtual void initInstance(); // Called after construction to initialize the class. - virtual void markDead(); - virtual ~LLControlAvatar(); - - // If this is an attachment, return the avatar it is attached to. Otherwise NULL. - virtual const LLVOAvatar *getAttachedAvatar() const; - virtual LLVOAvatar *getAttachedAvatar(); - - void getNewConstraintFixups(LLVector3& new_pos_constraint, F32& new_scale_constraint) const; - void matchVolumeTransform(); - void updateVolumeGeom(); - - void setGlobalScale(F32 scale); - void recursiveScaleJoint(LLJoint *joint, F32 factor); - static LLControlAvatar *createControlAvatar(LLVOVolume *obj); - - // Delayed kill so we don't make graphics pipeline unhappy calling - // markDead() inside other graphics pipeline operations. - void markForDeath(); - - virtual void idleUpdate(LLAgent &agent, const F64 &time); - virtual bool computeNeedsUpdate(); - virtual bool updateCharacter(LLAgent &agent); - - void getAnimatedVolumes(std::vector& volumes); - void updateAnimations(); - - virtual LLViewerObject* lineSegmentIntersectRiggedAttachments( - const LLVector4a& start, const LLVector4a& end, - S32 face = -1, // which face to check, -1 = ALL_SIDES - bool pick_transparent = false, - bool pick_rigged = false, - bool pick_unselectable = true, - S32* face_hit = NULL, // which face was hit - LLVector4a* intersection = NULL, // return the intersection point - LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point - LLVector4a* normal = NULL, // return the surface normal at the intersection point - LLVector4a* tangent = NULL); // return the surface tangent at the intersection point - - virtual void updateDebugText(); - - virtual std::string getFullname() const; - - virtual bool shouldRenderRigged() const; - - virtual bool isImpostor(); - virtual bool isBuddy() const { return false; } - - bool mPlaying; - - F32 mGlobalScale; - - LLVOVolume *mRootVolp; - class LLControlAVBridge* mControlAVBridge; - - bool mMarkedForDeath; - - LLVector3 mPositionConstraintFixup; - F32 mScaleConstraintFixup; - - static const F32 MAX_LEGAL_OFFSET; - static const F32 MAX_LEGAL_SIZE; - - static void onRegionChanged(); - bool mRegionChanged; - static boost::signals2::connection sRegionChangedSlot; -}; - -typedef std::map signaled_animation_map_t; -typedef std::map object_signaled_animation_map_t; - -// Stores information about previously requested animations, by object id. -class LLObjectSignaledAnimationMap: public LLSingleton -{ - LLSINGLETON_EMPTY_CTOR(LLObjectSignaledAnimationMap); - -public: - object_signaled_animation_map_t mMap; - - object_signaled_animation_map_t& getMap() { return mMap; } -}; - -#endif //LL_CONTROLAVATAR_H +/** + * @file llcontrolavatar.h + * @brief Special dummy avatar used to drive rigged meshes. + * + * $LicenseInfo:firstyear=2017&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2017, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_CONTROLAVATAR_H +#define LL_CONTROLAVATAR_H + +#include "llvoavatar.h" +#include "llvovolume.h" + +class LLControlAvatar: + public LLVOAvatar +{ + LOG_CLASS(LLControlAvatar); + + using super = LLVOAvatar; + +public: + LLControlAvatar(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); + virtual void initInstance(); // Called after construction to initialize the class. + virtual void markDead(); + virtual ~LLControlAvatar(); + + // If this is an attachment, return the avatar it is attached to. Otherwise NULL. + virtual const LLVOAvatar *getAttachedAvatar() const; + virtual LLVOAvatar *getAttachedAvatar(); + + void getNewConstraintFixups(LLVector3& new_pos_constraint, F32& new_scale_constraint) const; + void matchVolumeTransform(); + void updateVolumeGeom(); + + void setGlobalScale(F32 scale); + void recursiveScaleJoint(LLJoint *joint, F32 factor); + static LLControlAvatar *createControlAvatar(LLVOVolume *obj); + + // Delayed kill so we don't make graphics pipeline unhappy calling + // markDead() inside other graphics pipeline operations. + void markForDeath(); + + virtual void idleUpdate(LLAgent &agent, const F64 &time); + virtual bool computeNeedsUpdate(); + virtual bool updateCharacter(LLAgent &agent); + + void getAnimatedVolumes(std::vector& volumes); + void updateAnimations(); + + virtual LLViewerObject* lineSegmentIntersectRiggedAttachments( + const LLVector4a& start, const LLVector4a& end, + S32 face = -1, // which face to check, -1 = ALL_SIDES + bool pick_transparent = false, + bool pick_rigged = false, + bool pick_unselectable = true, + S32* face_hit = NULL, // which face was hit + LLVector4a* intersection = NULL, // return the intersection point + LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point + LLVector4a* normal = NULL, // return the surface normal at the intersection point + LLVector4a* tangent = NULL); // return the surface tangent at the intersection point + + virtual void updateDebugText(); + + virtual std::string getFullname() const; + + virtual bool shouldRenderRigged() const; + + virtual bool isImpostor(); + virtual bool isBuddy() const { return false; } + + bool mPlaying; + + F32 mGlobalScale; + + LLVOVolume *mRootVolp; + class LLControlAVBridge* mControlAVBridge; + + bool mMarkedForDeath; + + LLVector3 mPositionConstraintFixup; + F32 mScaleConstraintFixup; + + static const F32 MAX_LEGAL_OFFSET; + static const F32 MAX_LEGAL_SIZE; + + static void onRegionChanged(); + bool mRegionChanged; + static boost::signals2::connection sRegionChangedSlot; +}; + +typedef std::map signaled_animation_map_t; +typedef std::map object_signaled_animation_map_t; + +// Stores information about previously requested animations, by object id. +class LLObjectSignaledAnimationMap: public LLSingleton +{ + LLSINGLETON_EMPTY_CTOR(LLObjectSignaledAnimationMap); + +public: + object_signaled_animation_map_t mMap; + + object_signaled_animation_map_t& getMap() { return mMap; } +}; + +#endif //LL_CONTROLAVATAR_H diff --git a/indra/newview/llconversationlog.cpp b/indra/newview/llconversationlog.cpp index 9ee2be4c24..ed563cbec9 100644 --- a/indra/newview/llconversationlog.cpp +++ b/indra/newview/llconversationlog.cpp @@ -1,653 +1,653 @@ -/** - * @file llconversationlog.h - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llagent.h" -#include "llavatarnamecache.h" -#include "llconversationlog.h" -#include "lldiriterator.h" -#include "llnotificationsutil.h" -#include "lltrans.h" - -#include "boost/lexical_cast.hpp" - -const S32Days CONVERSATION_LIFETIME = (S32Days)30; // lifetime of LLConversation is 30 days by spec - -struct ConversationParams : public LLInitParam::Block -{ - Mandatory time; - Mandatory timestamp; - Mandatory conversation_type; - Mandatory conversation_name, - history_filename; - Mandatory session_id, - participant_id; - Mandatory has_offline_ims; -}; - -/************************************************************************/ -/* LLConversation implementation */ -/************************************************************************/ - -LLConversation::LLConversation(const ConversationParams& params) -: mTime(params.time), - mTimestamp(params.timestamp.isProvided() ? params.timestamp : createTimestamp(params.time)), - mConversationType(params.conversation_type), - mConversationName(params.conversation_name), - mHistoryFileName(params.history_filename), - mSessionID(params.session_id), - mParticipantID(params.participant_id), - mHasOfflineIMs(params.has_offline_ims) -{ - setListenIMFloaterOpened(); -} - -LLConversation::LLConversation(const LLIMModel::LLIMSession& session) -: mTime(time_corrected()), - mTimestamp(createTimestamp(mTime)), - mConversationType(session.mSessionType), - mConversationName(session.mName), - mHistoryFileName(session.mHistoryFileName), - mSessionID(session.isOutgoingAdHoc() ? session.generateOutgoingAdHocHash() : session.mSessionID), - mParticipantID(session.mOtherParticipantID), - mHasOfflineIMs(session.mHasOfflineMessage) -{ - setListenIMFloaterOpened(); -} - -LLConversation::LLConversation(const LLConversation& conversation) -{ - mTime = conversation.getTime(); - mTimestamp = conversation.getTimestamp(); - mConversationType = conversation.getConversationType(); - mConversationName = conversation.getConversationName(); - mHistoryFileName = conversation.getHistoryFileName(); - mSessionID = conversation.getSessionID(); - mParticipantID = conversation.getParticipantID(); - mHasOfflineIMs = conversation.hasOfflineMessages(); - - setListenIMFloaterOpened(); -} - -LLConversation::~LLConversation() -{ - mIMFloaterShowedConnection.disconnect(); -} - -void LLConversation::updateTimestamp() -{ - mTime = (U64Seconds)time_corrected(); - mTimestamp = createTimestamp(mTime); -} - -void LLConversation::onIMFloaterShown(const LLUUID& session_id) -{ - if (mSessionID == session_id) - { - mHasOfflineIMs = false; - } -} - -// static -const std::string LLConversation::createTimestamp(const U64Seconds& utc_time) -{ - std::string timeStr; - LLSD substitution; - substitution["datetime"] = (S32)utc_time.value(); - - timeStr = "["+LLTrans::getString ("TimeMonth")+"]/[" - +LLTrans::getString ("TimeDay")+"]/[" - +LLTrans::getString ("TimeYear")+"] [" - +LLTrans::getString ("TimeHour")+"]:[" - +LLTrans::getString ("TimeMin")+"]"; - - - LLStringUtil::format (timeStr, substitution); - return timeStr; -} - -bool LLConversation::isOlderThan(U32Days days) const -{ - U64Seconds now(time_corrected()); - U32Days age = now - mTime; - - return age > days; -} - -void LLConversation::setListenIMFloaterOpened() -{ - LLFloaterIMSession* floater = LLFloaterIMSession::findInstance(mSessionID); - - bool offline_ims_visible = LLFloaterIMSession::isVisible(floater) && floater->hasFocus(); - - // we don't need to listen for im floater with this conversation is opened - // if floater is already opened or this conversation doesn't have unread offline messages - if (mHasOfflineIMs && !offline_ims_visible) - { - mIMFloaterShowedConnection = LLFloaterIMSession::setIMFloaterShowedCallback(boost::bind(&LLConversation::onIMFloaterShown, this, _1)); - } - else - { - mHasOfflineIMs = false; - } -} - -/************************************************************************/ -/* LLConversationLogFriendObserver implementation */ -/************************************************************************/ - -// Note : An LLSingleton like LLConversationLog cannot be an LLFriendObserver -// at the same time. -// This is because avatar observers are deleted by the observed object which -// conflicts with the way LLSingleton are deleted. - -class LLConversationLogFriendObserver : public LLFriendObserver -{ -public: - LLConversationLogFriendObserver() {} - virtual ~LLConversationLogFriendObserver() {} - virtual void changed(U32 mask); -}; - -void LLConversationLogFriendObserver::changed(U32 mask) -{ - if (mask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE)) - { - LLConversationLog::instance().notifyObservers(); - } -} - -/************************************************************************/ -/* LLConversationLog implementation */ -/************************************************************************/ - -LLConversationLog::LLConversationLog() : - mAvatarNameCacheConnection(), - mLoggingEnabled(false) -{ -} - -void LLConversationLog::enableLogging(S32 log_mode) -{ - mLoggingEnabled = log_mode > 0; - if (log_mode > 0) - { - mConversations.clear(); - loadFromFile(getFileName()); - LLIMMgr::instance().addSessionObserver(this); - mNewMessageSignalConnection = LLIMModel::instance().addNewMsgCallback(boost::bind(&LLConversationLog::onNewMessageReceived, this, _1)); - - mFriendObserver = new LLConversationLogFriendObserver; - LLAvatarTracker::instance().addObserver(mFriendObserver); - } - else - { - saveToFile(getFileName()); - - LLIMMgr::instance().removeSessionObserver(this); - mNewMessageSignalConnection.disconnect(); - LLAvatarTracker::instance().removeObserver(mFriendObserver); - } - - notifyObservers(); -} - -void LLConversationLog::logConversation(const LLUUID& session_id, bool has_offline_msg) -{ - const LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(session_id); - LLConversation* conversation = findConversation(session); - - if (session && session->mOtherParticipantID != gAgentID) - { - if (conversation) - { - if(has_offline_msg) - { - updateOfflineIMs(session, has_offline_msg); - } - updateConversationTimestamp(conversation); - } - else - { - createConversation(session); - } - } -} - -void LLConversationLog::createConversation(const LLIMModel::LLIMSession* session) -{ - if (session) - { - LLConversation conversation(*session); - mConversations.push_back(conversation); - - if (LLIMModel::LLIMSession::P2P_SESSION == session->mSessionType) - { - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - mAvatarNameCacheConnection = LLAvatarNameCache::get(session->mOtherParticipantID, boost::bind(&LLConversationLog::onAvatarNameCache, this, _1, _2, session)); - } - - notifyObservers(); - } -} - -void LLConversationLog::updateConversationName(const LLIMModel::LLIMSession* session, const std::string& name) -{ - if (!session) - { - return; - } - - LLConversation* conversation = findConversation(session); - if (conversation) - { - conversation->setConversationName(name); - notifyParticularConversationObservers(conversation->getSessionID(), LLConversationLogObserver::CHANGED_NAME); - } -} - -void LLConversationLog::updateOfflineIMs(const LLIMModel::LLIMSession* session, bool new_messages) -{ - if (!session) - { - return; - } - - LLConversation* conversation = findConversation(session); - if (conversation) - { - conversation->setOfflineMessages(new_messages); - notifyParticularConversationObservers(conversation->getSessionID(), LLConversationLogObserver::CHANGED_OfflineIMs); - } -} - -void LLConversationLog::updateConversationTimestamp(LLConversation* conversation) -{ - if (conversation) - { - conversation->updateTimestamp(); - notifyParticularConversationObservers(conversation->getSessionID(), LLConversationLogObserver::CHANGED_TIME); - } -} - -LLConversation* LLConversationLog::findConversation(const LLIMModel::LLIMSession* session) -{ - if (session) - { - const LLUUID session_id = session->isOutgoingAdHoc() ? session->generateOutgoingAdHocHash() : session->mSessionID; - - conversations_vec_t::iterator conv_it = mConversations.begin(); - for(; conv_it != mConversations.end(); ++conv_it) - { - if (conv_it->getSessionID() == session_id) - { - return &*conv_it; - } - } - } - - return NULL; -} - -void LLConversationLog::removeConversation(const LLConversation& conversation) -{ - conversations_vec_t::iterator conv_it = mConversations.begin(); - for(; conv_it != mConversations.end(); ++conv_it) - { - if (conv_it->getSessionID() == conversation.getSessionID() && conv_it->getTime() == conversation.getTime()) - { - mConversations.erase(conv_it); - notifyObservers(); - cache(); - return; - } - } -} - -const LLConversation* LLConversationLog::getConversation(const LLUUID& session_id) -{ - conversations_vec_t::const_iterator conv_it = mConversations.begin(); - for(; conv_it != mConversations.end(); ++conv_it) - { - if (conv_it->getSessionID() == session_id) - { - return &*conv_it; - } - } - - return NULL; -} - -void LLConversationLog::addObserver(LLConversationLogObserver* observer) -{ - mObservers.insert(observer); -} - -void LLConversationLog::removeObserver(LLConversationLogObserver* observer) -{ - mObservers.erase(observer); -} - -void LLConversationLog::sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id, bool has_offline_msg) -{ - logConversation(session_id, has_offline_msg); -} - -void LLConversationLog::cache() -{ - if (gSavedPerAccountSettings.getS32("KeepConversationLogTranscripts") > 0) - { - saveToFile(getFileName()); - } -} - -void LLConversationLog::getListOfBackupLogs(std::vector& list_of_backup_logs) -{ - // get Users log directory - std::string dirname = gDirUtilp->getPerAccountChatLogsDir(); - - // add final OS dependent delimiter - dirname += gDirUtilp->getDirDelimiter(); - - // create search pattern - std::string pattern = "conversation.log.backup*"; - - LLDirIterator iter(dirname, pattern); - std::string filename; - while (iter.next(filename)) - { - list_of_backup_logs.push_back(gDirUtilp->add(dirname, filename)); - } -} - -void LLConversationLog::deleteBackupLogs() -{ - std::vector backup_logs; - getListOfBackupLogs(backup_logs); - - for (const std::string& fullpath : backup_logs) - { - LLFile::remove(fullpath); - } -} - -void LLConversationLog::verifyFilename(const LLUUID& session_id, const std::string &expected_filename, const std::string &new_session_name) -{ - conversations_vec_t::iterator conv_it = mConversations.begin(); - for (; conv_it != mConversations.end(); ++conv_it) - { - if (conv_it->getSessionID() == session_id) - { - if (conv_it->getHistoryFileName() != expected_filename) - { - LLLogChat::renameLogFile(conv_it->getHistoryFileName(), expected_filename); - conv_it->updateHistoryFileName(expected_filename); - conv_it->setConversationName(new_session_name); - } - break; - } - } -} - -bool LLConversationLog::moveLog(const std::string &originDirectory, const std::string &targetDirectory) -{ - - std::string backupFileName; - unsigned backupFileCount = 0; - - //Does the file exist in the current path, if it does lets move it - if(LLFile::isfile(originDirectory)) - { - //The target directory contains that file already, so lets store it - if(LLFile::isfile(targetDirectory)) - { - backupFileName = targetDirectory + ".backup"; - - //If needed store backup file as .backup1 etc. - while(LLFile::isfile(backupFileName)) - { - ++backupFileCount; - backupFileName = targetDirectory + ".backup" + std::to_string(backupFileCount); - } - - //Rename the file to its backup name so it is not overwritten - LLFile::rename(targetDirectory, backupFileName); - } - - //Move the file from the current path to target path - if(LLFile::rename(originDirectory, targetDirectory) != 0) - { - return false; - } - } - - return true; -} - -void LLConversationLog::initLoggingState() -{ - if (gSavedPerAccountSettings.controlExists("KeepConversationLogTranscripts")) - { - LLControlVariable * keep_log_ctrlp = gSavedPerAccountSettings.getControl("KeepConversationLogTranscripts").get(); - S32 log_mode = keep_log_ctrlp->getValue(); - keep_log_ctrlp->getSignal()->connect(boost::bind(&LLConversationLog::enableLogging, this, _2)); - if (log_mode > 0) - { - enableLogging(log_mode); - } - } -} - -std::string LLConversationLog::getFileName() -{ - std::string filename = "conversation"; - std::string log_address = gDirUtilp->getExpandedFilename(LL_PATH_PER_ACCOUNT_CHAT_LOGS, filename); - if (!log_address.empty()) - { - log_address += ".log"; - } - return log_address; -} - -bool LLConversationLog::saveToFile(const std::string& filename) -{ - if (!filename.size()) - { - LL_WARNS() << "Call log list filename is empty!" << LL_ENDL; - return false; - } - - LLFILE* fp = LLFile::fopen(filename, "wb"); - if (!fp) - { - LL_WARNS() << "Couldn't open call log list" << filename << LL_ENDL; - return false; - } - - std::string participant_id; - std::string conversation_id; - - conversations_vec_t::const_iterator conv_it = mConversations.begin(); - for (; conv_it != mConversations.end(); ++conv_it) - { - conv_it->getSessionID().toString(conversation_id); - conv_it->getParticipantID().toString(participant_id); - - bool is_adhoc = (conv_it->getConversationType() == LLIMModel::LLIMSession::ADHOC_SESSION); - std::string conv_name = is_adhoc ? conv_it->getConversationName() : LLURI::escape(conv_it->getConversationName()); - std::string file_name = is_adhoc ? conv_it->getHistoryFileName() : LLURI::escape(conv_it->getHistoryFileName()); - - // examples of two file entries - // [1343221177] 0 1 0 John Doe| 7e4ec5be-783f-49f5-71dz-16c58c64c145 4ec62a74-c246-0d25-2af6-846beac2aa55 john.doe| - // [1343222639] 2 0 0 Ad-hoc Conference| c3g67c89-c479-4c97-b21d-32869bcfe8rc 68f1c33e-4135-3e3e-a897-8c9b23115c09 Ad-hoc Conference hash597394a0-9982-766d-27b8-c75560213b9a| - fprintf(fp, "[%lld] %d %d %d %s| %s %s %s|\n", - (S64)conv_it->getTime().value(), - (S32)conv_it->getConversationType(), - (S32)0, - (S32)conv_it->hasOfflineMessages(), - conv_name.c_str(), - participant_id.c_str(), - conversation_id.c_str(), - file_name.c_str()); - } - fclose(fp); - return true; -} -bool LLConversationLog::loadFromFile(const std::string& filename) -{ - if(!filename.size()) - { - LL_WARNS() << "Call log list filename is empty!" << LL_ENDL; - return false; - } - - LLFILE* fp = LLFile::fopen(filename, "rb"); - if (!fp) - { - LL_WARNS() << "Couldn't open call log list" << filename << LL_ENDL; - return false; - } - bool purge_required = false; - - static constexpr int UTF_BUFFER{ 1024 }; // long enough to handle the most extreme Unicode nonsense and some to spare - - char buffer[UTF_BUFFER]; - char conv_name_buffer[MAX_STRING]; - char part_id_buffer[MAX_STRING]; - char conv_id_buffer[MAX_STRING]; - char history_file_name[MAX_STRING]; - S32 has_offline_ims; - S32 stype; - S64 time; - // before CHUI-348 it was a flag of conversation voice state - int prereserved_unused; - - memset(buffer, '\0', UTF_BUFFER); - while (!feof(fp) && fgets(buffer, UTF_BUFFER, fp)) - { - // force blank for added safety - memset(conv_name_buffer, '\0', MAX_STRING); - memset(part_id_buffer, '\0', MAX_STRING); - memset(conv_id_buffer, '\0', MAX_STRING); - memset(history_file_name, '\0', MAX_STRING); - - sscanf(buffer, "[%lld] %d %d %d %[^|]| %s %s %[^|]|", - &time, - &stype, - &prereserved_unused, - &has_offline_ims, - conv_name_buffer, - part_id_buffer, - conv_id_buffer, - history_file_name); - - bool is_adhoc = ((SessionType)stype == LLIMModel::LLIMSession::ADHOC_SESSION); - std::string conv_name = is_adhoc ? conv_name_buffer : LLURI::unescape(conv_name_buffer); - std::string file_name = is_adhoc ? history_file_name : LLURI::unescape(history_file_name); - - ConversationParams params; - params.time(LLUnits::Seconds::fromValue(time)) - .conversation_type((SessionType)stype) - .has_offline_ims(has_offline_ims) - .conversation_name(conv_name) - .participant_id(LLUUID(part_id_buffer)) - .session_id(LLUUID(conv_id_buffer)) - .history_filename(file_name); - - LLConversation conversation(params); - - // CHUI-325 - // The conversation log should be capped to the last 30 days. Conversations with the last utterance - // being over 30 days old should be purged from the conversation log text file on login. - if (conversation.isOlderThan(CONVERSATION_LIFETIME)) - { - purge_required = true; - continue; - } - - mConversations.push_back(conversation); - memset(buffer, '\0', UTF_BUFFER); - } - fclose(fp); - - if(purge_required) - { - LLFile::remove(filename); - cache(); - } - - notifyObservers(); - return true; -} - -void LLConversationLog::notifyObservers() -{ - std::set::const_iterator iter = mObservers.begin(); - for (; iter != mObservers.end(); ++iter) - { - (*iter)->changed(); - } -} - -void LLConversationLog::notifyParticularConversationObservers(const LLUUID& session_id, U32 mask) -{ - std::set::const_iterator iter = mObservers.begin(); - for (; iter != mObservers.end(); ++iter) - { - (*iter)->changed(session_id, mask); - } -} - -void LLConversationLog::onNewMessageReceived(const LLSD& data) -{ - const LLUUID session_id = data["session_id"].asUUID(); - logConversation(session_id, false); -} - -void LLConversationLog::onAvatarNameCache(const LLUUID& participant_id, const LLAvatarName& av_name, const LLIMModel::LLIMSession* session) -{ - mAvatarNameCacheConnection.disconnect(); - updateConversationName(session, av_name.getCompleteName()); -} - -void LLConversationLog::onClearLog() -{ - LLNotificationsUtil::add("PreferenceChatClearLog", LLSD(), LLSD(), boost::bind(&LLConversationLog::onClearLogResponse, this, _1, _2)); -} - -void LLConversationLog::onClearLogResponse(const LLSD& notification, const LLSD& response) -{ - if (0 == LLNotificationsUtil::getSelectedOption(notification, response)) - { - mConversations.clear(); - notifyObservers(); - cache(); - deleteBackupLogs(); - } -} +/** + * @file llconversationlog.h + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llagent.h" +#include "llavatarnamecache.h" +#include "llconversationlog.h" +#include "lldiriterator.h" +#include "llnotificationsutil.h" +#include "lltrans.h" + +#include "boost/lexical_cast.hpp" + +const S32Days CONVERSATION_LIFETIME = (S32Days)30; // lifetime of LLConversation is 30 days by spec + +struct ConversationParams : public LLInitParam::Block +{ + Mandatory time; + Mandatory timestamp; + Mandatory conversation_type; + Mandatory conversation_name, + history_filename; + Mandatory session_id, + participant_id; + Mandatory has_offline_ims; +}; + +/************************************************************************/ +/* LLConversation implementation */ +/************************************************************************/ + +LLConversation::LLConversation(const ConversationParams& params) +: mTime(params.time), + mTimestamp(params.timestamp.isProvided() ? params.timestamp : createTimestamp(params.time)), + mConversationType(params.conversation_type), + mConversationName(params.conversation_name), + mHistoryFileName(params.history_filename), + mSessionID(params.session_id), + mParticipantID(params.participant_id), + mHasOfflineIMs(params.has_offline_ims) +{ + setListenIMFloaterOpened(); +} + +LLConversation::LLConversation(const LLIMModel::LLIMSession& session) +: mTime(time_corrected()), + mTimestamp(createTimestamp(mTime)), + mConversationType(session.mSessionType), + mConversationName(session.mName), + mHistoryFileName(session.mHistoryFileName), + mSessionID(session.isOutgoingAdHoc() ? session.generateOutgoingAdHocHash() : session.mSessionID), + mParticipantID(session.mOtherParticipantID), + mHasOfflineIMs(session.mHasOfflineMessage) +{ + setListenIMFloaterOpened(); +} + +LLConversation::LLConversation(const LLConversation& conversation) +{ + mTime = conversation.getTime(); + mTimestamp = conversation.getTimestamp(); + mConversationType = conversation.getConversationType(); + mConversationName = conversation.getConversationName(); + mHistoryFileName = conversation.getHistoryFileName(); + mSessionID = conversation.getSessionID(); + mParticipantID = conversation.getParticipantID(); + mHasOfflineIMs = conversation.hasOfflineMessages(); + + setListenIMFloaterOpened(); +} + +LLConversation::~LLConversation() +{ + mIMFloaterShowedConnection.disconnect(); +} + +void LLConversation::updateTimestamp() +{ + mTime = (U64Seconds)time_corrected(); + mTimestamp = createTimestamp(mTime); +} + +void LLConversation::onIMFloaterShown(const LLUUID& session_id) +{ + if (mSessionID == session_id) + { + mHasOfflineIMs = false; + } +} + +// static +const std::string LLConversation::createTimestamp(const U64Seconds& utc_time) +{ + std::string timeStr; + LLSD substitution; + substitution["datetime"] = (S32)utc_time.value(); + + timeStr = "["+LLTrans::getString ("TimeMonth")+"]/[" + +LLTrans::getString ("TimeDay")+"]/[" + +LLTrans::getString ("TimeYear")+"] [" + +LLTrans::getString ("TimeHour")+"]:[" + +LLTrans::getString ("TimeMin")+"]"; + + + LLStringUtil::format (timeStr, substitution); + return timeStr; +} + +bool LLConversation::isOlderThan(U32Days days) const +{ + U64Seconds now(time_corrected()); + U32Days age = now - mTime; + + return age > days; +} + +void LLConversation::setListenIMFloaterOpened() +{ + LLFloaterIMSession* floater = LLFloaterIMSession::findInstance(mSessionID); + + bool offline_ims_visible = LLFloaterIMSession::isVisible(floater) && floater->hasFocus(); + + // we don't need to listen for im floater with this conversation is opened + // if floater is already opened or this conversation doesn't have unread offline messages + if (mHasOfflineIMs && !offline_ims_visible) + { + mIMFloaterShowedConnection = LLFloaterIMSession::setIMFloaterShowedCallback(boost::bind(&LLConversation::onIMFloaterShown, this, _1)); + } + else + { + mHasOfflineIMs = false; + } +} + +/************************************************************************/ +/* LLConversationLogFriendObserver implementation */ +/************************************************************************/ + +// Note : An LLSingleton like LLConversationLog cannot be an LLFriendObserver +// at the same time. +// This is because avatar observers are deleted by the observed object which +// conflicts with the way LLSingleton are deleted. + +class LLConversationLogFriendObserver : public LLFriendObserver +{ +public: + LLConversationLogFriendObserver() {} + virtual ~LLConversationLogFriendObserver() {} + virtual void changed(U32 mask); +}; + +void LLConversationLogFriendObserver::changed(U32 mask) +{ + if (mask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE)) + { + LLConversationLog::instance().notifyObservers(); + } +} + +/************************************************************************/ +/* LLConversationLog implementation */ +/************************************************************************/ + +LLConversationLog::LLConversationLog() : + mAvatarNameCacheConnection(), + mLoggingEnabled(false) +{ +} + +void LLConversationLog::enableLogging(S32 log_mode) +{ + mLoggingEnabled = log_mode > 0; + if (log_mode > 0) + { + mConversations.clear(); + loadFromFile(getFileName()); + LLIMMgr::instance().addSessionObserver(this); + mNewMessageSignalConnection = LLIMModel::instance().addNewMsgCallback(boost::bind(&LLConversationLog::onNewMessageReceived, this, _1)); + + mFriendObserver = new LLConversationLogFriendObserver; + LLAvatarTracker::instance().addObserver(mFriendObserver); + } + else + { + saveToFile(getFileName()); + + LLIMMgr::instance().removeSessionObserver(this); + mNewMessageSignalConnection.disconnect(); + LLAvatarTracker::instance().removeObserver(mFriendObserver); + } + + notifyObservers(); +} + +void LLConversationLog::logConversation(const LLUUID& session_id, bool has_offline_msg) +{ + const LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(session_id); + LLConversation* conversation = findConversation(session); + + if (session && session->mOtherParticipantID != gAgentID) + { + if (conversation) + { + if(has_offline_msg) + { + updateOfflineIMs(session, has_offline_msg); + } + updateConversationTimestamp(conversation); + } + else + { + createConversation(session); + } + } +} + +void LLConversationLog::createConversation(const LLIMModel::LLIMSession* session) +{ + if (session) + { + LLConversation conversation(*session); + mConversations.push_back(conversation); + + if (LLIMModel::LLIMSession::P2P_SESSION == session->mSessionType) + { + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + mAvatarNameCacheConnection = LLAvatarNameCache::get(session->mOtherParticipantID, boost::bind(&LLConversationLog::onAvatarNameCache, this, _1, _2, session)); + } + + notifyObservers(); + } +} + +void LLConversationLog::updateConversationName(const LLIMModel::LLIMSession* session, const std::string& name) +{ + if (!session) + { + return; + } + + LLConversation* conversation = findConversation(session); + if (conversation) + { + conversation->setConversationName(name); + notifyParticularConversationObservers(conversation->getSessionID(), LLConversationLogObserver::CHANGED_NAME); + } +} + +void LLConversationLog::updateOfflineIMs(const LLIMModel::LLIMSession* session, bool new_messages) +{ + if (!session) + { + return; + } + + LLConversation* conversation = findConversation(session); + if (conversation) + { + conversation->setOfflineMessages(new_messages); + notifyParticularConversationObservers(conversation->getSessionID(), LLConversationLogObserver::CHANGED_OfflineIMs); + } +} + +void LLConversationLog::updateConversationTimestamp(LLConversation* conversation) +{ + if (conversation) + { + conversation->updateTimestamp(); + notifyParticularConversationObservers(conversation->getSessionID(), LLConversationLogObserver::CHANGED_TIME); + } +} + +LLConversation* LLConversationLog::findConversation(const LLIMModel::LLIMSession* session) +{ + if (session) + { + const LLUUID session_id = session->isOutgoingAdHoc() ? session->generateOutgoingAdHocHash() : session->mSessionID; + + conversations_vec_t::iterator conv_it = mConversations.begin(); + for(; conv_it != mConversations.end(); ++conv_it) + { + if (conv_it->getSessionID() == session_id) + { + return &*conv_it; + } + } + } + + return NULL; +} + +void LLConversationLog::removeConversation(const LLConversation& conversation) +{ + conversations_vec_t::iterator conv_it = mConversations.begin(); + for(; conv_it != mConversations.end(); ++conv_it) + { + if (conv_it->getSessionID() == conversation.getSessionID() && conv_it->getTime() == conversation.getTime()) + { + mConversations.erase(conv_it); + notifyObservers(); + cache(); + return; + } + } +} + +const LLConversation* LLConversationLog::getConversation(const LLUUID& session_id) +{ + conversations_vec_t::const_iterator conv_it = mConversations.begin(); + for(; conv_it != mConversations.end(); ++conv_it) + { + if (conv_it->getSessionID() == session_id) + { + return &*conv_it; + } + } + + return NULL; +} + +void LLConversationLog::addObserver(LLConversationLogObserver* observer) +{ + mObservers.insert(observer); +} + +void LLConversationLog::removeObserver(LLConversationLogObserver* observer) +{ + mObservers.erase(observer); +} + +void LLConversationLog::sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id, bool has_offline_msg) +{ + logConversation(session_id, has_offline_msg); +} + +void LLConversationLog::cache() +{ + if (gSavedPerAccountSettings.getS32("KeepConversationLogTranscripts") > 0) + { + saveToFile(getFileName()); + } +} + +void LLConversationLog::getListOfBackupLogs(std::vector& list_of_backup_logs) +{ + // get Users log directory + std::string dirname = gDirUtilp->getPerAccountChatLogsDir(); + + // add final OS dependent delimiter + dirname += gDirUtilp->getDirDelimiter(); + + // create search pattern + std::string pattern = "conversation.log.backup*"; + + LLDirIterator iter(dirname, pattern); + std::string filename; + while (iter.next(filename)) + { + list_of_backup_logs.push_back(gDirUtilp->add(dirname, filename)); + } +} + +void LLConversationLog::deleteBackupLogs() +{ + std::vector backup_logs; + getListOfBackupLogs(backup_logs); + + for (const std::string& fullpath : backup_logs) + { + LLFile::remove(fullpath); + } +} + +void LLConversationLog::verifyFilename(const LLUUID& session_id, const std::string &expected_filename, const std::string &new_session_name) +{ + conversations_vec_t::iterator conv_it = mConversations.begin(); + for (; conv_it != mConversations.end(); ++conv_it) + { + if (conv_it->getSessionID() == session_id) + { + if (conv_it->getHistoryFileName() != expected_filename) + { + LLLogChat::renameLogFile(conv_it->getHistoryFileName(), expected_filename); + conv_it->updateHistoryFileName(expected_filename); + conv_it->setConversationName(new_session_name); + } + break; + } + } +} + +bool LLConversationLog::moveLog(const std::string &originDirectory, const std::string &targetDirectory) +{ + + std::string backupFileName; + unsigned backupFileCount = 0; + + //Does the file exist in the current path, if it does lets move it + if(LLFile::isfile(originDirectory)) + { + //The target directory contains that file already, so lets store it + if(LLFile::isfile(targetDirectory)) + { + backupFileName = targetDirectory + ".backup"; + + //If needed store backup file as .backup1 etc. + while(LLFile::isfile(backupFileName)) + { + ++backupFileCount; + backupFileName = targetDirectory + ".backup" + std::to_string(backupFileCount); + } + + //Rename the file to its backup name so it is not overwritten + LLFile::rename(targetDirectory, backupFileName); + } + + //Move the file from the current path to target path + if(LLFile::rename(originDirectory, targetDirectory) != 0) + { + return false; + } + } + + return true; +} + +void LLConversationLog::initLoggingState() +{ + if (gSavedPerAccountSettings.controlExists("KeepConversationLogTranscripts")) + { + LLControlVariable * keep_log_ctrlp = gSavedPerAccountSettings.getControl("KeepConversationLogTranscripts").get(); + S32 log_mode = keep_log_ctrlp->getValue(); + keep_log_ctrlp->getSignal()->connect(boost::bind(&LLConversationLog::enableLogging, this, _2)); + if (log_mode > 0) + { + enableLogging(log_mode); + } + } +} + +std::string LLConversationLog::getFileName() +{ + std::string filename = "conversation"; + std::string log_address = gDirUtilp->getExpandedFilename(LL_PATH_PER_ACCOUNT_CHAT_LOGS, filename); + if (!log_address.empty()) + { + log_address += ".log"; + } + return log_address; +} + +bool LLConversationLog::saveToFile(const std::string& filename) +{ + if (!filename.size()) + { + LL_WARNS() << "Call log list filename is empty!" << LL_ENDL; + return false; + } + + LLFILE* fp = LLFile::fopen(filename, "wb"); + if (!fp) + { + LL_WARNS() << "Couldn't open call log list" << filename << LL_ENDL; + return false; + } + + std::string participant_id; + std::string conversation_id; + + conversations_vec_t::const_iterator conv_it = mConversations.begin(); + for (; conv_it != mConversations.end(); ++conv_it) + { + conv_it->getSessionID().toString(conversation_id); + conv_it->getParticipantID().toString(participant_id); + + bool is_adhoc = (conv_it->getConversationType() == LLIMModel::LLIMSession::ADHOC_SESSION); + std::string conv_name = is_adhoc ? conv_it->getConversationName() : LLURI::escape(conv_it->getConversationName()); + std::string file_name = is_adhoc ? conv_it->getHistoryFileName() : LLURI::escape(conv_it->getHistoryFileName()); + + // examples of two file entries + // [1343221177] 0 1 0 John Doe| 7e4ec5be-783f-49f5-71dz-16c58c64c145 4ec62a74-c246-0d25-2af6-846beac2aa55 john.doe| + // [1343222639] 2 0 0 Ad-hoc Conference| c3g67c89-c479-4c97-b21d-32869bcfe8rc 68f1c33e-4135-3e3e-a897-8c9b23115c09 Ad-hoc Conference hash597394a0-9982-766d-27b8-c75560213b9a| + fprintf(fp, "[%lld] %d %d %d %s| %s %s %s|\n", + (S64)conv_it->getTime().value(), + (S32)conv_it->getConversationType(), + (S32)0, + (S32)conv_it->hasOfflineMessages(), + conv_name.c_str(), + participant_id.c_str(), + conversation_id.c_str(), + file_name.c_str()); + } + fclose(fp); + return true; +} +bool LLConversationLog::loadFromFile(const std::string& filename) +{ + if(!filename.size()) + { + LL_WARNS() << "Call log list filename is empty!" << LL_ENDL; + return false; + } + + LLFILE* fp = LLFile::fopen(filename, "rb"); + if (!fp) + { + LL_WARNS() << "Couldn't open call log list" << filename << LL_ENDL; + return false; + } + bool purge_required = false; + + static constexpr int UTF_BUFFER{ 1024 }; // long enough to handle the most extreme Unicode nonsense and some to spare + + char buffer[UTF_BUFFER]; + char conv_name_buffer[MAX_STRING]; + char part_id_buffer[MAX_STRING]; + char conv_id_buffer[MAX_STRING]; + char history_file_name[MAX_STRING]; + S32 has_offline_ims; + S32 stype; + S64 time; + // before CHUI-348 it was a flag of conversation voice state + int prereserved_unused; + + memset(buffer, '\0', UTF_BUFFER); + while (!feof(fp) && fgets(buffer, UTF_BUFFER, fp)) + { + // force blank for added safety + memset(conv_name_buffer, '\0', MAX_STRING); + memset(part_id_buffer, '\0', MAX_STRING); + memset(conv_id_buffer, '\0', MAX_STRING); + memset(history_file_name, '\0', MAX_STRING); + + sscanf(buffer, "[%lld] %d %d %d %[^|]| %s %s %[^|]|", + &time, + &stype, + &prereserved_unused, + &has_offline_ims, + conv_name_buffer, + part_id_buffer, + conv_id_buffer, + history_file_name); + + bool is_adhoc = ((SessionType)stype == LLIMModel::LLIMSession::ADHOC_SESSION); + std::string conv_name = is_adhoc ? conv_name_buffer : LLURI::unescape(conv_name_buffer); + std::string file_name = is_adhoc ? history_file_name : LLURI::unescape(history_file_name); + + ConversationParams params; + params.time(LLUnits::Seconds::fromValue(time)) + .conversation_type((SessionType)stype) + .has_offline_ims(has_offline_ims) + .conversation_name(conv_name) + .participant_id(LLUUID(part_id_buffer)) + .session_id(LLUUID(conv_id_buffer)) + .history_filename(file_name); + + LLConversation conversation(params); + + // CHUI-325 + // The conversation log should be capped to the last 30 days. Conversations with the last utterance + // being over 30 days old should be purged from the conversation log text file on login. + if (conversation.isOlderThan(CONVERSATION_LIFETIME)) + { + purge_required = true; + continue; + } + + mConversations.push_back(conversation); + memset(buffer, '\0', UTF_BUFFER); + } + fclose(fp); + + if(purge_required) + { + LLFile::remove(filename); + cache(); + } + + notifyObservers(); + return true; +} + +void LLConversationLog::notifyObservers() +{ + std::set::const_iterator iter = mObservers.begin(); + for (; iter != mObservers.end(); ++iter) + { + (*iter)->changed(); + } +} + +void LLConversationLog::notifyParticularConversationObservers(const LLUUID& session_id, U32 mask) +{ + std::set::const_iterator iter = mObservers.begin(); + for (; iter != mObservers.end(); ++iter) + { + (*iter)->changed(session_id, mask); + } +} + +void LLConversationLog::onNewMessageReceived(const LLSD& data) +{ + const LLUUID session_id = data["session_id"].asUUID(); + logConversation(session_id, false); +} + +void LLConversationLog::onAvatarNameCache(const LLUUID& participant_id, const LLAvatarName& av_name, const LLIMModel::LLIMSession* session) +{ + mAvatarNameCacheConnection.disconnect(); + updateConversationName(session, av_name.getCompleteName()); +} + +void LLConversationLog::onClearLog() +{ + LLNotificationsUtil::add("PreferenceChatClearLog", LLSD(), LLSD(), boost::bind(&LLConversationLog::onClearLogResponse, this, _1, _2)); +} + +void LLConversationLog::onClearLogResponse(const LLSD& notification, const LLSD& response) +{ + if (0 == LLNotificationsUtil::getSelectedOption(notification, response)) + { + mConversations.clear(); + notifyObservers(); + cache(); + deleteBackupLogs(); + } +} diff --git a/indra/newview/llconversationlog.h b/indra/newview/llconversationlog.h index ec1f72ffcd..b3a5be321e 100644 --- a/indra/newview/llconversationlog.h +++ b/indra/newview/llconversationlog.h @@ -1,226 +1,226 @@ -/** - * @file llconversationlog.h - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLCONVERSATIONLOG_H_ -#define LLCONVERSATIONLOG_H_ - -#include "llcallingcard.h" -#include "llfloaterimsession.h" -#include "llimview.h" - -class LLConversationLogObserver; -struct ConversationParams; - -typedef LLIMModel::LLIMSession::SType SessionType; - -/* - * This class represents a particular session(conversation) of any type(im/voice/p2p/group/...) by storing some of session's data. - * Each LLConversation object has a corresponding visual representation in a form of LLConversationLogListItem. - */ -class LLConversation -{ -public: - - LLConversation(const ConversationParams& params); - LLConversation(const LLIMModel::LLIMSession& session); - LLConversation(const LLConversation& conversation); - - ~LLConversation(); - - const SessionType& getConversationType() const { return mConversationType; } - const std::string& getConversationName() const { return mConversationName; } - const std::string& getHistoryFileName() const { return mHistoryFileName; } - const LLUUID& getSessionID() const { return mSessionID; } - const LLUUID& getParticipantID() const { return mParticipantID; } - const std::string& getTimestamp() const { return mTimestamp; } - const U64Seconds& - getTime() const { return mTime; } - bool hasOfflineMessages() const { return mHasOfflineIMs; } - - void setConversationName(const std::string &conv_name) { mConversationName = conv_name; } - void setOfflineMessages(bool new_messages) { mHasOfflineIMs = new_messages; } - bool isOlderThan(U32Days days) const; - - /* - * updates last interaction time - */ - void updateTimestamp(); - - void updateHistoryFileName(const std::string &new_name) { mHistoryFileName = new_name; } - - /* - * Resets flag of unread offline message to false when im floater with this conversation is opened. - */ - void onIMFloaterShown(const LLUUID& session_id); - - /* - * returns string representation(in form of: mm/dd/yyyy hh:mm) of time when conversation was started - */ - static const std::string createTimestamp(const U64Seconds& utc_time); - -private: - - /* - * If conversation has unread offline messages sets callback for opening LLFloaterIMSession - * with this conversation. - */ - void setListenIMFloaterOpened(); - - boost::signals2::connection mIMFloaterShowedConnection; - - U64Seconds mTime; // last interaction time - SessionType mConversationType; - std::string mConversationName; - std::string mHistoryFileName; - LLUUID mSessionID; - LLUUID mParticipantID; - bool mHasOfflineIMs; - std::string mTimestamp; // last interaction time in form of: mm/dd/yyyy hh:mm -}; - -/** - * LLConversationLog stores all agent's conversations. - * This class is responsible for creating and storing LLConversation objects when im or voice session starts. - * Also this class saves/retrieves conversations to/from file. - * - * Also please note that it may be several conversations with the same sessionID stored in the conversation log. - * To distinguish two conversations with the same sessionID it's also needed to compare their creation date. - */ - -class LLConversationLog : public LLSingleton, LLIMSessionObserver -{ - LLSINGLETON(LLConversationLog); -public: - void removeConversation(const LLConversation& conversation); - - /** - * Returns first conversation with matched session_id - */ - const LLConversation* getConversation(const LLUUID& session_id); - const std::vector& getConversations() { return mConversations; } - - void addObserver(LLConversationLogObserver* observer); - void removeObserver(LLConversationLogObserver* observer); - - // LLIMSessionObserver triggers - virtual void sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id, bool has_offline_msg) override; - virtual void sessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id) override {}; // Stub - virtual void sessionRemoved(const LLUUID& session_id) override{} // Stub - virtual void sessionVoiceOrIMStarted(const LLUUID& session_id) override{}; // Stub - virtual void sessionIDUpdated(const LLUUID& old_session_id, const LLUUID& new_session_id) override{}; // Stub - - void notifyObservers(); - - void onNewMessageReceived(const LLSD& data); - - /** - * public method which is called on viewer exit to save conversation log - */ - void cache(); - // will check if current name is edentical with the one on disk and will rename the one on disk if it isn't - void verifyFilename(const LLUUID& session_id, const std::string &expected_filename, const std::string &new_session_name); - bool moveLog(const std::string &originDirectory, const std::string &targetDirectory); - void getListOfBackupLogs(std::vector& list_of_backup_logs); - void deleteBackupLogs(); - - void onClearLog(); - void onClearLogResponse(const LLSD& notification, const LLSD& response); - - bool getIsLoggingEnabled() { return mLoggingEnabled; } - bool isLogEmpty() { return mConversations.empty(); } - - /** - * inits connection to per account settings, - * loads saved file and inits enabled state - */ - void initLoggingState(); - - /** - * constructs file name in which conversations log will be saved - * file name is conversation.log - */ - std::string getFileName(); - LLConversation* findConversation(const LLIMModel::LLIMSession* session); - -private: - - virtual ~LLConversationLog() - { - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - } - - void enableLogging(S32 log_mode); - - /** - * adds conversation to the conversation list and notifies observers - */ - void logConversation(const LLUUID& session_id, bool has_offline_msg); - - void notifyParticularConversationObservers(const LLUUID& session_id, U32 mask); - - bool saveToFile(const std::string& filename); - bool loadFromFile(const std::string& filename); - - void onAvatarNameCache(const LLUUID& participant_id, const LLAvatarName& av_name, const LLIMModel::LLIMSession* session); - - void createConversation(const LLIMModel::LLIMSession* session); - void updateConversationTimestamp(LLConversation* conversation); - void updateConversationName(const LLIMModel::LLIMSession* session, const std::string& name); - void updateOfflineIMs(const LLIMModel::LLIMSession* session, bool new_messages); - - - - typedef std::vector conversations_vec_t; - std::vector mConversations; - std::set mObservers; - - LLFriendObserver* mFriendObserver; // Observer of the LLAvatarTracker instance - - boost::signals2::connection mNewMessageSignalConnection; - boost::signals2::connection mAvatarNameCacheConnection; - - bool mLoggingEnabled; -}; - -class LLConversationLogObserver -{ -public: - - enum EConversationChange - { - CHANGED_TIME = 1, // last interaction time changed - CHANGED_NAME = 2, // conversation name changed - CHANGED_OfflineIMs = 3 - }; - - virtual ~LLConversationLogObserver(){} - virtual void changed() = 0; - virtual void changed(const LLUUID& session_id, U32 mask){}; -}; - -#endif /* LLCONVERSATIONLOG_H_ */ +/** + * @file llconversationlog.h + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLCONVERSATIONLOG_H_ +#define LLCONVERSATIONLOG_H_ + +#include "llcallingcard.h" +#include "llfloaterimsession.h" +#include "llimview.h" + +class LLConversationLogObserver; +struct ConversationParams; + +typedef LLIMModel::LLIMSession::SType SessionType; + +/* + * This class represents a particular session(conversation) of any type(im/voice/p2p/group/...) by storing some of session's data. + * Each LLConversation object has a corresponding visual representation in a form of LLConversationLogListItem. + */ +class LLConversation +{ +public: + + LLConversation(const ConversationParams& params); + LLConversation(const LLIMModel::LLIMSession& session); + LLConversation(const LLConversation& conversation); + + ~LLConversation(); + + const SessionType& getConversationType() const { return mConversationType; } + const std::string& getConversationName() const { return mConversationName; } + const std::string& getHistoryFileName() const { return mHistoryFileName; } + const LLUUID& getSessionID() const { return mSessionID; } + const LLUUID& getParticipantID() const { return mParticipantID; } + const std::string& getTimestamp() const { return mTimestamp; } + const U64Seconds& + getTime() const { return mTime; } + bool hasOfflineMessages() const { return mHasOfflineIMs; } + + void setConversationName(const std::string &conv_name) { mConversationName = conv_name; } + void setOfflineMessages(bool new_messages) { mHasOfflineIMs = new_messages; } + bool isOlderThan(U32Days days) const; + + /* + * updates last interaction time + */ + void updateTimestamp(); + + void updateHistoryFileName(const std::string &new_name) { mHistoryFileName = new_name; } + + /* + * Resets flag of unread offline message to false when im floater with this conversation is opened. + */ + void onIMFloaterShown(const LLUUID& session_id); + + /* + * returns string representation(in form of: mm/dd/yyyy hh:mm) of time when conversation was started + */ + static const std::string createTimestamp(const U64Seconds& utc_time); + +private: + + /* + * If conversation has unread offline messages sets callback for opening LLFloaterIMSession + * with this conversation. + */ + void setListenIMFloaterOpened(); + + boost::signals2::connection mIMFloaterShowedConnection; + + U64Seconds mTime; // last interaction time + SessionType mConversationType; + std::string mConversationName; + std::string mHistoryFileName; + LLUUID mSessionID; + LLUUID mParticipantID; + bool mHasOfflineIMs; + std::string mTimestamp; // last interaction time in form of: mm/dd/yyyy hh:mm +}; + +/** + * LLConversationLog stores all agent's conversations. + * This class is responsible for creating and storing LLConversation objects when im or voice session starts. + * Also this class saves/retrieves conversations to/from file. + * + * Also please note that it may be several conversations with the same sessionID stored in the conversation log. + * To distinguish two conversations with the same sessionID it's also needed to compare their creation date. + */ + +class LLConversationLog : public LLSingleton, LLIMSessionObserver +{ + LLSINGLETON(LLConversationLog); +public: + void removeConversation(const LLConversation& conversation); + + /** + * Returns first conversation with matched session_id + */ + const LLConversation* getConversation(const LLUUID& session_id); + const std::vector& getConversations() { return mConversations; } + + void addObserver(LLConversationLogObserver* observer); + void removeObserver(LLConversationLogObserver* observer); + + // LLIMSessionObserver triggers + virtual void sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id, bool has_offline_msg) override; + virtual void sessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id) override {}; // Stub + virtual void sessionRemoved(const LLUUID& session_id) override{} // Stub + virtual void sessionVoiceOrIMStarted(const LLUUID& session_id) override{}; // Stub + virtual void sessionIDUpdated(const LLUUID& old_session_id, const LLUUID& new_session_id) override{}; // Stub + + void notifyObservers(); + + void onNewMessageReceived(const LLSD& data); + + /** + * public method which is called on viewer exit to save conversation log + */ + void cache(); + // will check if current name is edentical with the one on disk and will rename the one on disk if it isn't + void verifyFilename(const LLUUID& session_id, const std::string &expected_filename, const std::string &new_session_name); + bool moveLog(const std::string &originDirectory, const std::string &targetDirectory); + void getListOfBackupLogs(std::vector& list_of_backup_logs); + void deleteBackupLogs(); + + void onClearLog(); + void onClearLogResponse(const LLSD& notification, const LLSD& response); + + bool getIsLoggingEnabled() { return mLoggingEnabled; } + bool isLogEmpty() { return mConversations.empty(); } + + /** + * inits connection to per account settings, + * loads saved file and inits enabled state + */ + void initLoggingState(); + + /** + * constructs file name in which conversations log will be saved + * file name is conversation.log + */ + std::string getFileName(); + LLConversation* findConversation(const LLIMModel::LLIMSession* session); + +private: + + virtual ~LLConversationLog() + { + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + } + + void enableLogging(S32 log_mode); + + /** + * adds conversation to the conversation list and notifies observers + */ + void logConversation(const LLUUID& session_id, bool has_offline_msg); + + void notifyParticularConversationObservers(const LLUUID& session_id, U32 mask); + + bool saveToFile(const std::string& filename); + bool loadFromFile(const std::string& filename); + + void onAvatarNameCache(const LLUUID& participant_id, const LLAvatarName& av_name, const LLIMModel::LLIMSession* session); + + void createConversation(const LLIMModel::LLIMSession* session); + void updateConversationTimestamp(LLConversation* conversation); + void updateConversationName(const LLIMModel::LLIMSession* session, const std::string& name); + void updateOfflineIMs(const LLIMModel::LLIMSession* session, bool new_messages); + + + + typedef std::vector conversations_vec_t; + std::vector mConversations; + std::set mObservers; + + LLFriendObserver* mFriendObserver; // Observer of the LLAvatarTracker instance + + boost::signals2::connection mNewMessageSignalConnection; + boost::signals2::connection mAvatarNameCacheConnection; + + bool mLoggingEnabled; +}; + +class LLConversationLogObserver +{ +public: + + enum EConversationChange + { + CHANGED_TIME = 1, // last interaction time changed + CHANGED_NAME = 2, // conversation name changed + CHANGED_OfflineIMs = 3 + }; + + virtual ~LLConversationLogObserver(){} + virtual void changed() = 0; + virtual void changed(const LLUUID& session_id, U32 mask){}; +}; + +#endif /* LLCONVERSATIONLOG_H_ */ diff --git a/indra/newview/llconversationloglist.cpp b/indra/newview/llconversationloglist.cpp index 6050f6849d..65863f0a5e 100644 --- a/indra/newview/llconversationloglist.cpp +++ b/indra/newview/llconversationloglist.cpp @@ -1,543 +1,543 @@ -/** - * @file llconversationloglist.cpp - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llavataractions.h" -#include "llagent.h" -#include "llfloaterreg.h" -#include "llfloaterconversationpreview.h" -#include "llgroupactions.h" -#include "llconversationloglist.h" -#include "llconversationloglistitem.h" -#include "llviewermenu.h" -#include "lltrans.h" - -static LLDefaultChildRegistry::Register r("conversation_log_list"); - -static LLConversationLogListNameComparator NAME_COMPARATOR; -static LLConversationLogListDateComparator DATE_COMPARATOR; - -LLConversationLogList::LLConversationLogList(const Params& p) -: LLFlatListViewEx(p), - mIsDirty(true) -{ - LLConversationLog::instance().addObserver(this); - - // Set up context menu. - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - LLUICtrl::EnableCallbackRegistry::ScopedRegistrar check_registrar; - LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; - - registrar.add ("Calllog.Action", boost::bind(&LLConversationLogList::onCustomAction, this, _2)); - check_registrar.add ("Calllog.Check", boost::bind(&LLConversationLogList::isActionChecked,this, _2)); - enable_registrar.add("Calllog.Enable", boost::bind(&LLConversationLogList::isActionEnabled,this, _2)); - - LLToggleableMenu* context_menu = LLUICtrlFactory::getInstance()->createFromFile( - "menu_conversation_log_gear.xml", - gMenuHolder, - LLViewerMenuHolderGL::child_registry_t::instance()); - if(context_menu) - { - mContextMenu = context_menu->getHandle(); - } - - mIsFriendsOnTop = gSavedSettings.getBOOL("SortFriendsFirst"); -} - -LLConversationLogList::~LLConversationLogList() -{ - if (mContextMenu.get()) - { - mContextMenu.get()->die(); - } - - LLConversationLog::instance().removeObserver(this); -} - -void LLConversationLogList::draw() -{ - if (mIsDirty) - { - refresh(); - } - LLFlatListViewEx::draw(); -} - -bool LLConversationLogList::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = LLUICtrl::handleRightMouseDown(x, y, mask); - - LLToggleableMenu* context_menu = mContextMenu.get(); - if (context_menu && size()) - { - context_menu->buildDrawLabels(); - context_menu->updateParent(LLMenuGL::sMenuContainer); - LLMenuGL::showPopup(this, context_menu, x, y); - } - - return handled; -} - -void LLConversationLogList::setNameFilter(const std::string& filter) -{ - std::string filter_upper = filter; - LLStringUtil::toUpper(filter_upper); - if (mNameFilter != filter_upper) - { - mNameFilter = filter_upper; - setDirty(); - } -} - -bool LLConversationLogList::findInsensitive(std::string haystack, const std::string& needle_upper) -{ - LLStringUtil::toUpper(haystack); - return haystack.find(needle_upper) != std::string::npos; -} - -void LLConversationLogList::sortByName() -{ - setComparator(&NAME_COMPARATOR); - sort(); -} - -void LLConversationLogList::sortByDate() -{ - setComparator(&DATE_COMPARATOR); - sort(); -} - -void LLConversationLogList::toggleSortFriendsOnTop() -{ - mIsFriendsOnTop = !mIsFriendsOnTop; - gSavedSettings.setBOOL("SortFriendsFirst", mIsFriendsOnTop); - sort(); -} - -void LLConversationLogList::changed() -{ - refresh(); -} - -void LLConversationLogList::changed(const LLUUID& session_id, U32 mask) -{ - LLConversationLogListItem* item = getConversationLogListItem(session_id); - - if (!item) - { - return; - } - - if (mask & LLConversationLogObserver::CHANGED_TIME) - { - item->updateTimestamp(); - - // if list is sorted by date and a date of some item has changed, - // than the whole list should be rebuilt - if (E_SORT_BY_DATE == getSortOrder()) - { - mIsDirty = true; - } - } - else if (mask & LLConversationLogObserver::CHANGED_NAME) - { - item->updateName(); - // if list is sorted by name and a name of some item has changed, - // than the whole list should be rebuilt - if (E_SORT_BY_DATE == getSortOrder()) - { - mIsDirty = true; - } - } - else if (mask & LLConversationLogObserver::CHANGED_OfflineIMs) - { - item->updateOfflineIMs(); - } -} - -void LLConversationLogList::addNewItem(const LLConversation* conversation) -{ - LLConversationLogListItem* item = new LLConversationLogListItem(&*conversation); - if (!mNameFilter.empty()) - { - item->highlightNameDate(mNameFilter); - } - addItem(item, conversation->getSessionID(), ADD_TOP); -} - -void LLConversationLogList::refresh() -{ - rebuildList(); - sort(); - - mIsDirty = false; -} - -void LLConversationLogList::rebuildList() -{ - const LLConversation * selected_conversationp = getSelectedConversation(); - - clear(); - - bool have_filter = !mNameFilter.empty(); - LLConversationLog &log_instance = LLConversationLog::instance(); - - const std::vector& conversations = log_instance.getConversations(); - std::vector::const_iterator iter = conversations.begin(); - - for (; iter != conversations.end(); ++iter) - { - bool not_found = have_filter && !findInsensitive(iter->getConversationName(), mNameFilter) && !findInsensitive(iter->getTimestamp(), mNameFilter); - if (not_found) - continue; - - addNewItem(&*iter); - } - - // try to restore selection of item - if (NULL != selected_conversationp) - { - selectItemByUUID(selected_conversationp->getSessionID()); - } - - bool logging_enabled = log_instance.getIsLoggingEnabled(); - bool log_empty = log_instance.isLogEmpty(); - if (!logging_enabled && log_empty) - { - setNoItemsCommentText(LLTrans::getString("logging_calls_disabled_log_empty")); - } - else if (!logging_enabled && !log_empty) - { - setNoItemsCommentText(LLTrans::getString("logging_calls_disabled_log_not_empty")); - } - else if (logging_enabled && log_empty) - { - setNoItemsCommentText(LLTrans::getString("logging_calls_enabled_log_empty")); - } - else if (logging_enabled && !log_empty) - { - setNoItemsCommentText(""); - } -} - -void LLConversationLogList::onCustomAction(const LLSD& userdata) -{ - const LLConversation * selected_conversationp = getSelectedConversation(); - - if (NULL == selected_conversationp) - { - return; - } - - const std::string command_name = userdata.asString(); - const LLUUID& selected_conversation_participant_id = selected_conversationp->getParticipantID(); - const LLUUID& selected_conversation_session_id = selected_conversationp->getSessionID(); - LLIMModel::LLIMSession::SType stype = getSelectedSessionType(); - - if ("im" == command_name) - { - switch (stype) - { - case LLIMModel::LLIMSession::P2P_SESSION: - LLAvatarActions::startIM(selected_conversation_participant_id); - break; - - case LLIMModel::LLIMSession::GROUP_SESSION: - LLGroupActions::startIM(selected_conversation_session_id); - break; - - default: - break; - } - } - else if ("call" == command_name) - { - switch (stype) - { - case LLIMModel::LLIMSession::P2P_SESSION: - LLAvatarActions::startCall(selected_conversation_participant_id); - break; - - case LLIMModel::LLIMSession::GROUP_SESSION: - LLGroupActions::startCall(selected_conversation_session_id); - break; - - default: - break; - } - } - else if ("view_profile" == command_name) - { - switch (stype) - { - case LLIMModel::LLIMSession::P2P_SESSION: - LLAvatarActions::showProfile(selected_conversation_participant_id); - break; - - case LLIMModel::LLIMSession::GROUP_SESSION: - LLGroupActions::show(selected_conversation_session_id); - break; - - default: - break; - } - } - else if ("chat_history" == command_name) - { - LLFloaterReg::showInstance("preview_conversation", selected_conversation_session_id, true); - } - else if ("offer_teleport" == command_name) - { - LLAvatarActions::offerTeleport(selected_conversation_participant_id); - } - else if ("request_teleport" == command_name) - { - LLAvatarActions::teleportRequest(selected_conversation_participant_id); - } - else if("add_friend" == command_name) - { - if (!LLAvatarActions::isFriend(selected_conversation_participant_id)) - { - LLAvatarActions::requestFriendshipDialog(selected_conversation_participant_id); - } - } - else if("remove_friend" == command_name) - { - if (LLAvatarActions::isFriend(selected_conversation_participant_id)) - { - LLAvatarActions::removeFriendDialog(selected_conversation_participant_id); - } - } - else if ("invite_to_group" == command_name) - { - LLAvatarActions::inviteToGroup(selected_conversation_participant_id); - } - else if ("show_on_map" == command_name) - { - LLAvatarActions::showOnMap(selected_conversation_participant_id); - } - else if ("share" == command_name) - { - LLAvatarActions::share(selected_conversation_participant_id); - } - else if ("pay" == command_name) - { - LLAvatarActions::pay(selected_conversation_participant_id); - } - else if ("block" == command_name) - { - LLAvatarActions::toggleBlock(selected_conversation_participant_id); - } -} - -bool LLConversationLogList::isActionEnabled(const LLSD& userdata) -{ - const LLConversation * selected_conversationp = getSelectedConversation(); - - if (NULL == selected_conversationp || numSelected() > 1) - { - return false; - } - - const std::string command_name = userdata.asString(); - - LLIMModel::LLIMSession::SType stype = getSelectedSessionType(); - const LLUUID& selected_id = selected_conversationp->getParticipantID(); - - bool is_p2p = LLIMModel::LLIMSession::P2P_SESSION == stype; - bool is_group = LLIMModel::LLIMSession::GROUP_SESSION == stype; - bool is_group_member = is_group && gAgent.isInGroup(selected_id, true); - - if ("can_im" == command_name) - { - return is_p2p || is_group_member; - } - else if ("can_view_profile" == command_name) - { - return is_p2p || is_group; - } - else if ("can_view_chat_history" == command_name) - { - return true; - } - else if ("can_call" == command_name) - { - return (is_p2p || is_group_member) && LLAvatarActions::canCall(); - } - else if ("add_rem_friend" == command_name || - "can_invite_to_group" == command_name || - "can_share" == command_name || - "can_block" == command_name || - "can_pay" == command_name || - "report_abuse" == command_name) - { - return is_p2p; - } - else if("can_offer_teleport" == command_name) - { - return is_p2p && LLAvatarActions::canOfferTeleport(selected_id); - } - else if ("can_show_on_map" == command_name) - { - return is_p2p && ((LLAvatarTracker::instance().isBuddyOnline(selected_id) && is_agent_mappable(selected_id)) || gAgent.isGodlike()); - } - - return false; -} - -bool LLConversationLogList::isActionChecked(const LLSD& userdata) -{ - const LLConversation * selected_conversationp = getSelectedConversation(); - - if (NULL == selected_conversationp) - { - return false; - } - - const std::string command_name = userdata.asString(); - - const LLUUID& selected_id = selected_conversationp->getParticipantID(); - bool is_p2p = LLIMModel::LLIMSession::P2P_SESSION == getSelectedSessionType(); - - if ("is_blocked" == command_name) - { - return is_p2p && LLAvatarActions::isBlocked(selected_id); - } - else if ("is_friend" == command_name) - { - return is_p2p && LLAvatarActions::isFriend(selected_id); - } - else if ("is_not_friend" == command_name) - { - return is_p2p && !LLAvatarActions::isFriend(selected_id); - } - - return false; -} - -LLIMModel::LLIMSession::SType LLConversationLogList::getSelectedSessionType() -{ - const LLConversationLogListItem* item = getSelectedConversationPanel(); - - if (item) - { - return item->getConversation()->getConversationType(); - } - - return LLIMModel::LLIMSession::NONE_SESSION; -} - -const LLConversationLogListItem* LLConversationLogList::getSelectedConversationPanel() -{ - LLPanel* panel = LLFlatListViewEx::getSelectedItem(); - LLConversationLogListItem* conv_panel = dynamic_cast(panel); - - return conv_panel; -} - -const LLConversation* LLConversationLogList::getSelectedConversation() -{ - const LLConversationLogListItem* panel = getSelectedConversationPanel(); - - if (panel) - { - return panel->getConversation(); - } - - return NULL; -} - -LLConversationLogListItem* LLConversationLogList::getConversationLogListItem(const LLUUID& session_id) -{ - std::vector panels; - LLFlatListViewEx::getItems(panels); - std::vector::iterator iter = panels.begin(); - - for (; iter != panels.end(); ++iter) - { - LLConversationLogListItem* item = dynamic_cast(*iter); - if (item && session_id == item->getConversation()->getSessionID()) - { - return item; - } - } - - return NULL; -} - -LLConversationLogList::ESortOrder LLConversationLogList::getSortOrder() -{ - return static_cast(gSavedSettings.getU32("CallLogSortOrder")); -} - -bool LLConversationLogListItemComparator::compare(const LLPanel* item1, const LLPanel* item2) const -{ - const LLConversationLogListItem* conversation_item1 = dynamic_cast(item1); - const LLConversationLogListItem* conversation_item2 = dynamic_cast(item2); - - if (!conversation_item1 || !conversation_item2) - { - LL_ERRS() << "conversation_item1 and conversation_item2 cannot be null" << LL_ENDL; - return true; - } - - return doCompare(conversation_item1, conversation_item2); -} - -bool LLConversationLogListNameComparator::doCompare(const LLConversationLogListItem* conversation1, const LLConversationLogListItem* conversation2) const -{ - std::string name1 = conversation1->getConversation()->getConversationName(); - std::string name2 = conversation2->getConversation()->getConversationName(); - const LLUUID& id1 = conversation1->getConversation()->getParticipantID(); - const LLUUID& id2 = conversation2->getConversation()->getParticipantID(); - - LLStringUtil::toUpper(name1); - LLStringUtil::toUpper(name2); - - bool friends_first = gSavedSettings.getBOOL("SortFriendsFirst"); - if (friends_first && (LLAvatarActions::isFriend(id1) ^ LLAvatarActions::isFriend(id2))) - { - return LLAvatarActions::isFriend(id1); - } - - return name1 < name2; -} - -bool LLConversationLogListDateComparator::doCompare(const LLConversationLogListItem* conversation1, const LLConversationLogListItem* conversation2) const -{ - U64Seconds date1 = conversation1->getConversation()->getTime(); - U64Seconds date2 = conversation2->getConversation()->getTime(); - const LLUUID& id1 = conversation1->getConversation()->getParticipantID(); - const LLUUID& id2 = conversation2->getConversation()->getParticipantID(); - - bool friends_first = gSavedSettings.getBOOL("SortFriendsFirst"); - if (friends_first && (LLAvatarActions::isFriend(id1) ^ LLAvatarActions::isFriend(id2))) - { - return LLAvatarActions::isFriend(id1); - } - - return date1 > date2; -} +/** + * @file llconversationloglist.cpp + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llavataractions.h" +#include "llagent.h" +#include "llfloaterreg.h" +#include "llfloaterconversationpreview.h" +#include "llgroupactions.h" +#include "llconversationloglist.h" +#include "llconversationloglistitem.h" +#include "llviewermenu.h" +#include "lltrans.h" + +static LLDefaultChildRegistry::Register r("conversation_log_list"); + +static LLConversationLogListNameComparator NAME_COMPARATOR; +static LLConversationLogListDateComparator DATE_COMPARATOR; + +LLConversationLogList::LLConversationLogList(const Params& p) +: LLFlatListViewEx(p), + mIsDirty(true) +{ + LLConversationLog::instance().addObserver(this); + + // Set up context menu. + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar check_registrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + + registrar.add ("Calllog.Action", boost::bind(&LLConversationLogList::onCustomAction, this, _2)); + check_registrar.add ("Calllog.Check", boost::bind(&LLConversationLogList::isActionChecked,this, _2)); + enable_registrar.add("Calllog.Enable", boost::bind(&LLConversationLogList::isActionEnabled,this, _2)); + + LLToggleableMenu* context_menu = LLUICtrlFactory::getInstance()->createFromFile( + "menu_conversation_log_gear.xml", + gMenuHolder, + LLViewerMenuHolderGL::child_registry_t::instance()); + if(context_menu) + { + mContextMenu = context_menu->getHandle(); + } + + mIsFriendsOnTop = gSavedSettings.getBOOL("SortFriendsFirst"); +} + +LLConversationLogList::~LLConversationLogList() +{ + if (mContextMenu.get()) + { + mContextMenu.get()->die(); + } + + LLConversationLog::instance().removeObserver(this); +} + +void LLConversationLogList::draw() +{ + if (mIsDirty) + { + refresh(); + } + LLFlatListViewEx::draw(); +} + +bool LLConversationLogList::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = LLUICtrl::handleRightMouseDown(x, y, mask); + + LLToggleableMenu* context_menu = mContextMenu.get(); + if (context_menu && size()) + { + context_menu->buildDrawLabels(); + context_menu->updateParent(LLMenuGL::sMenuContainer); + LLMenuGL::showPopup(this, context_menu, x, y); + } + + return handled; +} + +void LLConversationLogList::setNameFilter(const std::string& filter) +{ + std::string filter_upper = filter; + LLStringUtil::toUpper(filter_upper); + if (mNameFilter != filter_upper) + { + mNameFilter = filter_upper; + setDirty(); + } +} + +bool LLConversationLogList::findInsensitive(std::string haystack, const std::string& needle_upper) +{ + LLStringUtil::toUpper(haystack); + return haystack.find(needle_upper) != std::string::npos; +} + +void LLConversationLogList::sortByName() +{ + setComparator(&NAME_COMPARATOR); + sort(); +} + +void LLConversationLogList::sortByDate() +{ + setComparator(&DATE_COMPARATOR); + sort(); +} + +void LLConversationLogList::toggleSortFriendsOnTop() +{ + mIsFriendsOnTop = !mIsFriendsOnTop; + gSavedSettings.setBOOL("SortFriendsFirst", mIsFriendsOnTop); + sort(); +} + +void LLConversationLogList::changed() +{ + refresh(); +} + +void LLConversationLogList::changed(const LLUUID& session_id, U32 mask) +{ + LLConversationLogListItem* item = getConversationLogListItem(session_id); + + if (!item) + { + return; + } + + if (mask & LLConversationLogObserver::CHANGED_TIME) + { + item->updateTimestamp(); + + // if list is sorted by date and a date of some item has changed, + // than the whole list should be rebuilt + if (E_SORT_BY_DATE == getSortOrder()) + { + mIsDirty = true; + } + } + else if (mask & LLConversationLogObserver::CHANGED_NAME) + { + item->updateName(); + // if list is sorted by name and a name of some item has changed, + // than the whole list should be rebuilt + if (E_SORT_BY_DATE == getSortOrder()) + { + mIsDirty = true; + } + } + else if (mask & LLConversationLogObserver::CHANGED_OfflineIMs) + { + item->updateOfflineIMs(); + } +} + +void LLConversationLogList::addNewItem(const LLConversation* conversation) +{ + LLConversationLogListItem* item = new LLConversationLogListItem(&*conversation); + if (!mNameFilter.empty()) + { + item->highlightNameDate(mNameFilter); + } + addItem(item, conversation->getSessionID(), ADD_TOP); +} + +void LLConversationLogList::refresh() +{ + rebuildList(); + sort(); + + mIsDirty = false; +} + +void LLConversationLogList::rebuildList() +{ + const LLConversation * selected_conversationp = getSelectedConversation(); + + clear(); + + bool have_filter = !mNameFilter.empty(); + LLConversationLog &log_instance = LLConversationLog::instance(); + + const std::vector& conversations = log_instance.getConversations(); + std::vector::const_iterator iter = conversations.begin(); + + for (; iter != conversations.end(); ++iter) + { + bool not_found = have_filter && !findInsensitive(iter->getConversationName(), mNameFilter) && !findInsensitive(iter->getTimestamp(), mNameFilter); + if (not_found) + continue; + + addNewItem(&*iter); + } + + // try to restore selection of item + if (NULL != selected_conversationp) + { + selectItemByUUID(selected_conversationp->getSessionID()); + } + + bool logging_enabled = log_instance.getIsLoggingEnabled(); + bool log_empty = log_instance.isLogEmpty(); + if (!logging_enabled && log_empty) + { + setNoItemsCommentText(LLTrans::getString("logging_calls_disabled_log_empty")); + } + else if (!logging_enabled && !log_empty) + { + setNoItemsCommentText(LLTrans::getString("logging_calls_disabled_log_not_empty")); + } + else if (logging_enabled && log_empty) + { + setNoItemsCommentText(LLTrans::getString("logging_calls_enabled_log_empty")); + } + else if (logging_enabled && !log_empty) + { + setNoItemsCommentText(""); + } +} + +void LLConversationLogList::onCustomAction(const LLSD& userdata) +{ + const LLConversation * selected_conversationp = getSelectedConversation(); + + if (NULL == selected_conversationp) + { + return; + } + + const std::string command_name = userdata.asString(); + const LLUUID& selected_conversation_participant_id = selected_conversationp->getParticipantID(); + const LLUUID& selected_conversation_session_id = selected_conversationp->getSessionID(); + LLIMModel::LLIMSession::SType stype = getSelectedSessionType(); + + if ("im" == command_name) + { + switch (stype) + { + case LLIMModel::LLIMSession::P2P_SESSION: + LLAvatarActions::startIM(selected_conversation_participant_id); + break; + + case LLIMModel::LLIMSession::GROUP_SESSION: + LLGroupActions::startIM(selected_conversation_session_id); + break; + + default: + break; + } + } + else if ("call" == command_name) + { + switch (stype) + { + case LLIMModel::LLIMSession::P2P_SESSION: + LLAvatarActions::startCall(selected_conversation_participant_id); + break; + + case LLIMModel::LLIMSession::GROUP_SESSION: + LLGroupActions::startCall(selected_conversation_session_id); + break; + + default: + break; + } + } + else if ("view_profile" == command_name) + { + switch (stype) + { + case LLIMModel::LLIMSession::P2P_SESSION: + LLAvatarActions::showProfile(selected_conversation_participant_id); + break; + + case LLIMModel::LLIMSession::GROUP_SESSION: + LLGroupActions::show(selected_conversation_session_id); + break; + + default: + break; + } + } + else if ("chat_history" == command_name) + { + LLFloaterReg::showInstance("preview_conversation", selected_conversation_session_id, true); + } + else if ("offer_teleport" == command_name) + { + LLAvatarActions::offerTeleport(selected_conversation_participant_id); + } + else if ("request_teleport" == command_name) + { + LLAvatarActions::teleportRequest(selected_conversation_participant_id); + } + else if("add_friend" == command_name) + { + if (!LLAvatarActions::isFriend(selected_conversation_participant_id)) + { + LLAvatarActions::requestFriendshipDialog(selected_conversation_participant_id); + } + } + else if("remove_friend" == command_name) + { + if (LLAvatarActions::isFriend(selected_conversation_participant_id)) + { + LLAvatarActions::removeFriendDialog(selected_conversation_participant_id); + } + } + else if ("invite_to_group" == command_name) + { + LLAvatarActions::inviteToGroup(selected_conversation_participant_id); + } + else if ("show_on_map" == command_name) + { + LLAvatarActions::showOnMap(selected_conversation_participant_id); + } + else if ("share" == command_name) + { + LLAvatarActions::share(selected_conversation_participant_id); + } + else if ("pay" == command_name) + { + LLAvatarActions::pay(selected_conversation_participant_id); + } + else if ("block" == command_name) + { + LLAvatarActions::toggleBlock(selected_conversation_participant_id); + } +} + +bool LLConversationLogList::isActionEnabled(const LLSD& userdata) +{ + const LLConversation * selected_conversationp = getSelectedConversation(); + + if (NULL == selected_conversationp || numSelected() > 1) + { + return false; + } + + const std::string command_name = userdata.asString(); + + LLIMModel::LLIMSession::SType stype = getSelectedSessionType(); + const LLUUID& selected_id = selected_conversationp->getParticipantID(); + + bool is_p2p = LLIMModel::LLIMSession::P2P_SESSION == stype; + bool is_group = LLIMModel::LLIMSession::GROUP_SESSION == stype; + bool is_group_member = is_group && gAgent.isInGroup(selected_id, true); + + if ("can_im" == command_name) + { + return is_p2p || is_group_member; + } + else if ("can_view_profile" == command_name) + { + return is_p2p || is_group; + } + else if ("can_view_chat_history" == command_name) + { + return true; + } + else if ("can_call" == command_name) + { + return (is_p2p || is_group_member) && LLAvatarActions::canCall(); + } + else if ("add_rem_friend" == command_name || + "can_invite_to_group" == command_name || + "can_share" == command_name || + "can_block" == command_name || + "can_pay" == command_name || + "report_abuse" == command_name) + { + return is_p2p; + } + else if("can_offer_teleport" == command_name) + { + return is_p2p && LLAvatarActions::canOfferTeleport(selected_id); + } + else if ("can_show_on_map" == command_name) + { + return is_p2p && ((LLAvatarTracker::instance().isBuddyOnline(selected_id) && is_agent_mappable(selected_id)) || gAgent.isGodlike()); + } + + return false; +} + +bool LLConversationLogList::isActionChecked(const LLSD& userdata) +{ + const LLConversation * selected_conversationp = getSelectedConversation(); + + if (NULL == selected_conversationp) + { + return false; + } + + const std::string command_name = userdata.asString(); + + const LLUUID& selected_id = selected_conversationp->getParticipantID(); + bool is_p2p = LLIMModel::LLIMSession::P2P_SESSION == getSelectedSessionType(); + + if ("is_blocked" == command_name) + { + return is_p2p && LLAvatarActions::isBlocked(selected_id); + } + else if ("is_friend" == command_name) + { + return is_p2p && LLAvatarActions::isFriend(selected_id); + } + else if ("is_not_friend" == command_name) + { + return is_p2p && !LLAvatarActions::isFriend(selected_id); + } + + return false; +} + +LLIMModel::LLIMSession::SType LLConversationLogList::getSelectedSessionType() +{ + const LLConversationLogListItem* item = getSelectedConversationPanel(); + + if (item) + { + return item->getConversation()->getConversationType(); + } + + return LLIMModel::LLIMSession::NONE_SESSION; +} + +const LLConversationLogListItem* LLConversationLogList::getSelectedConversationPanel() +{ + LLPanel* panel = LLFlatListViewEx::getSelectedItem(); + LLConversationLogListItem* conv_panel = dynamic_cast(panel); + + return conv_panel; +} + +const LLConversation* LLConversationLogList::getSelectedConversation() +{ + const LLConversationLogListItem* panel = getSelectedConversationPanel(); + + if (panel) + { + return panel->getConversation(); + } + + return NULL; +} + +LLConversationLogListItem* LLConversationLogList::getConversationLogListItem(const LLUUID& session_id) +{ + std::vector panels; + LLFlatListViewEx::getItems(panels); + std::vector::iterator iter = panels.begin(); + + for (; iter != panels.end(); ++iter) + { + LLConversationLogListItem* item = dynamic_cast(*iter); + if (item && session_id == item->getConversation()->getSessionID()) + { + return item; + } + } + + return NULL; +} + +LLConversationLogList::ESortOrder LLConversationLogList::getSortOrder() +{ + return static_cast(gSavedSettings.getU32("CallLogSortOrder")); +} + +bool LLConversationLogListItemComparator::compare(const LLPanel* item1, const LLPanel* item2) const +{ + const LLConversationLogListItem* conversation_item1 = dynamic_cast(item1); + const LLConversationLogListItem* conversation_item2 = dynamic_cast(item2); + + if (!conversation_item1 || !conversation_item2) + { + LL_ERRS() << "conversation_item1 and conversation_item2 cannot be null" << LL_ENDL; + return true; + } + + return doCompare(conversation_item1, conversation_item2); +} + +bool LLConversationLogListNameComparator::doCompare(const LLConversationLogListItem* conversation1, const LLConversationLogListItem* conversation2) const +{ + std::string name1 = conversation1->getConversation()->getConversationName(); + std::string name2 = conversation2->getConversation()->getConversationName(); + const LLUUID& id1 = conversation1->getConversation()->getParticipantID(); + const LLUUID& id2 = conversation2->getConversation()->getParticipantID(); + + LLStringUtil::toUpper(name1); + LLStringUtil::toUpper(name2); + + bool friends_first = gSavedSettings.getBOOL("SortFriendsFirst"); + if (friends_first && (LLAvatarActions::isFriend(id1) ^ LLAvatarActions::isFriend(id2))) + { + return LLAvatarActions::isFriend(id1); + } + + return name1 < name2; +} + +bool LLConversationLogListDateComparator::doCompare(const LLConversationLogListItem* conversation1, const LLConversationLogListItem* conversation2) const +{ + U64Seconds date1 = conversation1->getConversation()->getTime(); + U64Seconds date2 = conversation2->getConversation()->getTime(); + const LLUUID& id1 = conversation1->getConversation()->getParticipantID(); + const LLUUID& id2 = conversation2->getConversation()->getParticipantID(); + + bool friends_first = gSavedSettings.getBOOL("SortFriendsFirst"); + if (friends_first && (LLAvatarActions::isFriend(id1) ^ LLAvatarActions::isFriend(id2))) + { + return LLAvatarActions::isFriend(id1); + } + + return date1 > date2; +} diff --git a/indra/newview/llconversationloglist.h b/indra/newview/llconversationloglist.h index 29cc082505..6372f6c9a3 100644 --- a/indra/newview/llconversationloglist.h +++ b/indra/newview/llconversationloglist.h @@ -1,153 +1,153 @@ -/** - * @file llconversationloglist.h - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLCONVERSATIONLOGLIST_H_ -#define LLCONVERSATIONLOGLIST_H_ - -#include "llconversationlog.h" -#include "llflatlistview.h" -#include "lltoggleablemenu.h" - -class LLConversationLogListItem; - -/** - * List of all agent's conversations. I.e. history of conversations. - * This list represents contents of the LLConversationLog. - * Each change in LLConversationLog leads to rebuilding this list, so - * it's always in actual state. - */ - -class LLConversationLogList: public LLFlatListViewEx, public LLConversationLogObserver -{ - LOG_CLASS(LLConversationLogList); -public: - - typedef enum e_sort_oder{ - E_SORT_BY_NAME = 0, - E_SORT_BY_DATE = 1, - } ESortOrder; - - struct Params : public LLInitParam::Block - { - Params(){}; - }; - - LLConversationLogList(const Params& p); - virtual ~LLConversationLogList(); - - virtual void draw(); - - virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); - - LLToggleableMenu* getContextMenu() const { return mContextMenu.get(); } - - void addNewItem(const LLConversation* conversation); - void setNameFilter(const std::string& filter); - void sortByName(); - void sortByDate(); - void toggleSortFriendsOnTop(); - bool getSortFriendsOnTop() const { return mIsFriendsOnTop; } - - /** - * Changes from LLConversationLogObserver - */ - virtual void changed(); - virtual void changed(const LLUUID& session_id, U32 mask); - -private: - - void setDirty(bool dirty = true) { mIsDirty = dirty; } - void refresh(); - - /** - * Clears list and re-adds items from LLConverstationLog - * If filter is not empty re-adds items which match the filter - */ - void rebuildList(); - - bool findInsensitive(std::string haystack, const std::string& needle_upper); - - void onCustomAction (const LLSD& userdata); - bool isActionEnabled(const LLSD& userdata); - bool isActionChecked(const LLSD& userdata); - - LLIMModel::LLIMSession::SType getSelectedSessionType(); - const LLConversationLogListItem* getSelectedConversationPanel(); - const LLConversation* getSelectedConversation(); - LLConversationLogListItem* getConversationLogListItem(const LLUUID& session_id); - - ESortOrder getSortOrder(); - - LLHandle mContextMenu; - bool mIsDirty; - bool mIsFriendsOnTop; - std::string mNameFilter; -}; - -/** - * Abstract comparator for ConversationLogList items - */ -class LLConversationLogListItemComparator : public LLFlatListView::ItemComparator -{ - LOG_CLASS(LLConversationLogListItemComparator); - -public: - LLConversationLogListItemComparator() {}; - virtual ~LLConversationLogListItemComparator() {}; - - virtual bool compare(const LLPanel* item1, const LLPanel* item2) const; - -protected: - - virtual bool doCompare(const LLConversationLogListItem* conversation1, const LLConversationLogListItem* conversation2) const = 0; -}; - -class LLConversationLogListNameComparator : public LLConversationLogListItemComparator -{ - LOG_CLASS(LLConversationLogListNameComparator); - -public: - LLConversationLogListNameComparator() {}; - virtual ~LLConversationLogListNameComparator() {}; - -protected: - - virtual bool doCompare(const LLConversationLogListItem* conversation1, const LLConversationLogListItem* conversation2) const; -}; - -class LLConversationLogListDateComparator : public LLConversationLogListItemComparator -{ - LOG_CLASS(LLConversationLogListDateComparator); - -public: - LLConversationLogListDateComparator() {}; - virtual ~LLConversationLogListDateComparator() {}; - -protected: - - virtual bool doCompare(const LLConversationLogListItem* conversation1, const LLConversationLogListItem* conversation2) const; -}; - -#endif /* LLCONVERSATIONLOGLIST_H_ */ +/** + * @file llconversationloglist.h + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLCONVERSATIONLOGLIST_H_ +#define LLCONVERSATIONLOGLIST_H_ + +#include "llconversationlog.h" +#include "llflatlistview.h" +#include "lltoggleablemenu.h" + +class LLConversationLogListItem; + +/** + * List of all agent's conversations. I.e. history of conversations. + * This list represents contents of the LLConversationLog. + * Each change in LLConversationLog leads to rebuilding this list, so + * it's always in actual state. + */ + +class LLConversationLogList: public LLFlatListViewEx, public LLConversationLogObserver +{ + LOG_CLASS(LLConversationLogList); +public: + + typedef enum e_sort_oder{ + E_SORT_BY_NAME = 0, + E_SORT_BY_DATE = 1, + } ESortOrder; + + struct Params : public LLInitParam::Block + { + Params(){}; + }; + + LLConversationLogList(const Params& p); + virtual ~LLConversationLogList(); + + virtual void draw(); + + virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); + + LLToggleableMenu* getContextMenu() const { return mContextMenu.get(); } + + void addNewItem(const LLConversation* conversation); + void setNameFilter(const std::string& filter); + void sortByName(); + void sortByDate(); + void toggleSortFriendsOnTop(); + bool getSortFriendsOnTop() const { return mIsFriendsOnTop; } + + /** + * Changes from LLConversationLogObserver + */ + virtual void changed(); + virtual void changed(const LLUUID& session_id, U32 mask); + +private: + + void setDirty(bool dirty = true) { mIsDirty = dirty; } + void refresh(); + + /** + * Clears list and re-adds items from LLConverstationLog + * If filter is not empty re-adds items which match the filter + */ + void rebuildList(); + + bool findInsensitive(std::string haystack, const std::string& needle_upper); + + void onCustomAction (const LLSD& userdata); + bool isActionEnabled(const LLSD& userdata); + bool isActionChecked(const LLSD& userdata); + + LLIMModel::LLIMSession::SType getSelectedSessionType(); + const LLConversationLogListItem* getSelectedConversationPanel(); + const LLConversation* getSelectedConversation(); + LLConversationLogListItem* getConversationLogListItem(const LLUUID& session_id); + + ESortOrder getSortOrder(); + + LLHandle mContextMenu; + bool mIsDirty; + bool mIsFriendsOnTop; + std::string mNameFilter; +}; + +/** + * Abstract comparator for ConversationLogList items + */ +class LLConversationLogListItemComparator : public LLFlatListView::ItemComparator +{ + LOG_CLASS(LLConversationLogListItemComparator); + +public: + LLConversationLogListItemComparator() {}; + virtual ~LLConversationLogListItemComparator() {}; + + virtual bool compare(const LLPanel* item1, const LLPanel* item2) const; + +protected: + + virtual bool doCompare(const LLConversationLogListItem* conversation1, const LLConversationLogListItem* conversation2) const = 0; +}; + +class LLConversationLogListNameComparator : public LLConversationLogListItemComparator +{ + LOG_CLASS(LLConversationLogListNameComparator); + +public: + LLConversationLogListNameComparator() {}; + virtual ~LLConversationLogListNameComparator() {}; + +protected: + + virtual bool doCompare(const LLConversationLogListItem* conversation1, const LLConversationLogListItem* conversation2) const; +}; + +class LLConversationLogListDateComparator : public LLConversationLogListItemComparator +{ + LOG_CLASS(LLConversationLogListDateComparator); + +public: + LLConversationLogListDateComparator() {}; + virtual ~LLConversationLogListDateComparator() {}; + +protected: + + virtual bool doCompare(const LLConversationLogListItem* conversation1, const LLConversationLogListItem* conversation2) const; +}; + +#endif /* LLCONVERSATIONLOGLIST_H_ */ diff --git a/indra/newview/llconversationloglistitem.cpp b/indra/newview/llconversationloglistitem.cpp index 17322b2bd8..20d5b0175b 100644 --- a/indra/newview/llconversationloglistitem.cpp +++ b/indra/newview/llconversationloglistitem.cpp @@ -1,184 +1,184 @@ -/** - * @file llconversationloglistitem.cpp - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -// llui -#include "lliconctrl.h" -#include "lltextbox.h" -#include "lltextutil.h" - -// newview -#include "llavataractions.h" -#include "llavatariconctrl.h" -#include "llconversationlog.h" -#include "llconversationloglistitem.h" -#include "llgroupactions.h" -#include "llgroupiconctrl.h" -#include "llinventoryicon.h" - -LLConversationLogListItem::LLConversationLogListItem(const LLConversation* conversation) -: LLPanel(), - mConversation(conversation), - mConversationName(NULL), - mConversationDate(NULL) -{ - buildFromFile("panel_conversation_log_list_item.xml"); - - LLFloaterIMSession* floater = LLFloaterIMSession::findInstance(mConversation->getSessionID()); - - bool ims_are_read = LLFloaterIMSession::isVisible(floater) && floater->hasFocus(); - - if (mConversation->hasOfflineMessages() && !ims_are_read) - { - mIMFloaterShowedConnection = LLFloaterIMSession::setIMFloaterShowedCallback(boost::bind(&LLConversationLogListItem::onIMFloaterShown, this, _1)); - } -} - -LLConversationLogListItem::~LLConversationLogListItem() -{ - mIMFloaterShowedConnection.disconnect(); -} - -bool LLConversationLogListItem::postBuild() -{ - initIcons(); - - // set conversation name - mConversationName = getChild("conversation_name"); - mConversationName->setValue(mConversation->getConversationName()); - - // set conversation date and time - mConversationDate = getChild("date_time"); - mConversationDate->setValue(mConversation->getTimestamp()); - - getChild("delete_btn")->setClickedCallback(boost::bind(&LLConversationLogListItem::onRemoveBtnClicked, this)); - setDoubleClickCallback(boost::bind(&LLConversationLogListItem::onDoubleClick, this)); - - return true; -} - -void LLConversationLogListItem::initIcons() -{ - switch (mConversation->getConversationType()) - { - case LLIMModel::LLIMSession::P2P_SESSION: - case LLIMModel::LLIMSession::ADHOC_SESSION: - { - LLAvatarIconCtrl* avatar_icon = getChild("avatar_icon"); - avatar_icon->setVisible(true); - avatar_icon->setValue(mConversation->getParticipantID()); - break; - } - case LLIMModel::LLIMSession::GROUP_SESSION: - { - LLGroupIconCtrl* group_icon = getChild("group_icon"); - group_icon->setVisible(true); - group_icon->setValue(mConversation->getSessionID()); - break; - } - default: - break; - } - - if (mConversation->hasOfflineMessages()) - { - getChild("unread_ims_icon")->setVisible(true); - } -} - -void LLConversationLogListItem::updateTimestamp() -{ - mConversationDate->setValue(mConversation->getTimestamp()); -} - -void LLConversationLogListItem::updateName() -{ - mConversationName->setValue(mConversation->getConversationName()); -} - -void LLConversationLogListItem::updateOfflineIMs() -{ - getChild("unread_ims_icon")->setVisible(mConversation->hasOfflineMessages()); -} - -void LLConversationLogListItem::onMouseEnter(S32 x, S32 y, MASK mask) -{ - getChildView("hovered_icon")->setVisible(true); - LLPanel::onMouseEnter(x, y, mask); -} - -void LLConversationLogListItem::onMouseLeave(S32 x, S32 y, MASK mask) -{ - getChildView("hovered_icon")->setVisible(false); - LLPanel::onMouseLeave(x, y, mask); -} - -void LLConversationLogListItem::setValue(const LLSD& value) -{ - if (!value.isMap() || !value.has("selected")) - { - return; - } - - getChildView("selected_icon")->setVisible(value["selected"]); -} - -void LLConversationLogListItem::onIMFloaterShown(const LLUUID& session_id) -{ - if (mConversation->getSessionID() == session_id) - { - getChild("unread_ims_icon")->setVisible(false); - } -} - -void LLConversationLogListItem::onRemoveBtnClicked() -{ - LLConversationLog::instance().removeConversation(*mConversation); -} - -void LLConversationLogListItem::highlightNameDate(const std::string& highlited_text) -{ - LLStyle::Params params; - LLTextUtil::textboxSetHighlightedVal(mConversationName, params, mConversation->getConversationName(), highlited_text); - LLTextUtil::textboxSetHighlightedVal(mConversationDate, params, mConversation->getTimestamp(), highlited_text); -} - -void LLConversationLogListItem::onDoubleClick() -{ - switch (mConversation->getConversationType()) - { - case LLIMModel::LLIMSession::P2P_SESSION: - LLAvatarActions::startIM(mConversation->getParticipantID()); - break; - - case LLIMModel::LLIMSession::GROUP_SESSION: - LLGroupActions::startIM(mConversation->getSessionID()); - break; - - default: - break; - } -} +/** + * @file llconversationloglistitem.cpp + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +// llui +#include "lliconctrl.h" +#include "lltextbox.h" +#include "lltextutil.h" + +// newview +#include "llavataractions.h" +#include "llavatariconctrl.h" +#include "llconversationlog.h" +#include "llconversationloglistitem.h" +#include "llgroupactions.h" +#include "llgroupiconctrl.h" +#include "llinventoryicon.h" + +LLConversationLogListItem::LLConversationLogListItem(const LLConversation* conversation) +: LLPanel(), + mConversation(conversation), + mConversationName(NULL), + mConversationDate(NULL) +{ + buildFromFile("panel_conversation_log_list_item.xml"); + + LLFloaterIMSession* floater = LLFloaterIMSession::findInstance(mConversation->getSessionID()); + + bool ims_are_read = LLFloaterIMSession::isVisible(floater) && floater->hasFocus(); + + if (mConversation->hasOfflineMessages() && !ims_are_read) + { + mIMFloaterShowedConnection = LLFloaterIMSession::setIMFloaterShowedCallback(boost::bind(&LLConversationLogListItem::onIMFloaterShown, this, _1)); + } +} + +LLConversationLogListItem::~LLConversationLogListItem() +{ + mIMFloaterShowedConnection.disconnect(); +} + +bool LLConversationLogListItem::postBuild() +{ + initIcons(); + + // set conversation name + mConversationName = getChild("conversation_name"); + mConversationName->setValue(mConversation->getConversationName()); + + // set conversation date and time + mConversationDate = getChild("date_time"); + mConversationDate->setValue(mConversation->getTimestamp()); + + getChild("delete_btn")->setClickedCallback(boost::bind(&LLConversationLogListItem::onRemoveBtnClicked, this)); + setDoubleClickCallback(boost::bind(&LLConversationLogListItem::onDoubleClick, this)); + + return true; +} + +void LLConversationLogListItem::initIcons() +{ + switch (mConversation->getConversationType()) + { + case LLIMModel::LLIMSession::P2P_SESSION: + case LLIMModel::LLIMSession::ADHOC_SESSION: + { + LLAvatarIconCtrl* avatar_icon = getChild("avatar_icon"); + avatar_icon->setVisible(true); + avatar_icon->setValue(mConversation->getParticipantID()); + break; + } + case LLIMModel::LLIMSession::GROUP_SESSION: + { + LLGroupIconCtrl* group_icon = getChild("group_icon"); + group_icon->setVisible(true); + group_icon->setValue(mConversation->getSessionID()); + break; + } + default: + break; + } + + if (mConversation->hasOfflineMessages()) + { + getChild("unread_ims_icon")->setVisible(true); + } +} + +void LLConversationLogListItem::updateTimestamp() +{ + mConversationDate->setValue(mConversation->getTimestamp()); +} + +void LLConversationLogListItem::updateName() +{ + mConversationName->setValue(mConversation->getConversationName()); +} + +void LLConversationLogListItem::updateOfflineIMs() +{ + getChild("unread_ims_icon")->setVisible(mConversation->hasOfflineMessages()); +} + +void LLConversationLogListItem::onMouseEnter(S32 x, S32 y, MASK mask) +{ + getChildView("hovered_icon")->setVisible(true); + LLPanel::onMouseEnter(x, y, mask); +} + +void LLConversationLogListItem::onMouseLeave(S32 x, S32 y, MASK mask) +{ + getChildView("hovered_icon")->setVisible(false); + LLPanel::onMouseLeave(x, y, mask); +} + +void LLConversationLogListItem::setValue(const LLSD& value) +{ + if (!value.isMap() || !value.has("selected")) + { + return; + } + + getChildView("selected_icon")->setVisible(value["selected"]); +} + +void LLConversationLogListItem::onIMFloaterShown(const LLUUID& session_id) +{ + if (mConversation->getSessionID() == session_id) + { + getChild("unread_ims_icon")->setVisible(false); + } +} + +void LLConversationLogListItem::onRemoveBtnClicked() +{ + LLConversationLog::instance().removeConversation(*mConversation); +} + +void LLConversationLogListItem::highlightNameDate(const std::string& highlited_text) +{ + LLStyle::Params params; + LLTextUtil::textboxSetHighlightedVal(mConversationName, params, mConversation->getConversationName(), highlited_text); + LLTextUtil::textboxSetHighlightedVal(mConversationDate, params, mConversation->getTimestamp(), highlited_text); +} + +void LLConversationLogListItem::onDoubleClick() +{ + switch (mConversation->getConversationType()) + { + case LLIMModel::LLIMSession::P2P_SESSION: + LLAvatarActions::startIM(mConversation->getParticipantID()); + break; + + case LLIMModel::LLIMSession::GROUP_SESSION: + LLGroupActions::startIM(mConversation->getSessionID()); + break; + + default: + break; + } +} diff --git a/indra/newview/llconversationloglistitem.h b/indra/newview/llconversationloglistitem.h index 2b9f480a14..31436c5c24 100644 --- a/indra/newview/llconversationloglistitem.h +++ b/indra/newview/llconversationloglistitem.h @@ -1,86 +1,86 @@ -/** - * @file llconversationloglistitem.h - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLCONVERSATIONLOGLISTITEM_H_ -#define LLCONVERSATIONLOGLISTITEM_H_ - -#include "llfloaterimsession.h" -#include "llpanel.h" - -class LLTextBox; -class LLConversation; - -/** - * This class is a visual representation of LLConversation, each of which is LLConversationLog entry. - * LLConversationLogList consists of these LLConversationLogListItems. - * LLConversationLogListItem consists of: - * conversaion_type_icon - * conversaion_name - * conversaion_date - * Also LLConversationLogListItem holds pointer to its LLConversationLog. - */ - -class LLConversationLogListItem : public LLPanel -{ -public: - LLConversationLogListItem(const LLConversation* conversation); - virtual ~LLConversationLogListItem(); - - void onMouseEnter(S32 x, S32 y, MASK mask); - void onMouseLeave(S32 x, S32 y, MASK mask); - - virtual void setValue(const LLSD& value); - - virtual bool postBuild(); - - void onIMFloaterShown(const LLUUID& session_id); - void onRemoveBtnClicked(); - - const LLConversation* getConversation() const { return mConversation; } - - void highlightNameDate(const std::string& highlited_text); - - void onDoubleClick(); - - /** - * updates string value of last interaction time from conversation - */ - void updateTimestamp(); - void updateName(); - void updateOfflineIMs(); - -private: - - void initIcons(); - - const LLConversation* mConversation; - - LLTextBox* mConversationName; - LLTextBox* mConversationDate; - - boost::signals2::connection mIMFloaterShowedConnection; -}; - -#endif /* LLCONVERSATIONLOGITEM_H_ */ +/** + * @file llconversationloglistitem.h + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLCONVERSATIONLOGLISTITEM_H_ +#define LLCONVERSATIONLOGLISTITEM_H_ + +#include "llfloaterimsession.h" +#include "llpanel.h" + +class LLTextBox; +class LLConversation; + +/** + * This class is a visual representation of LLConversation, each of which is LLConversationLog entry. + * LLConversationLogList consists of these LLConversationLogListItems. + * LLConversationLogListItem consists of: + * conversaion_type_icon + * conversaion_name + * conversaion_date + * Also LLConversationLogListItem holds pointer to its LLConversationLog. + */ + +class LLConversationLogListItem : public LLPanel +{ +public: + LLConversationLogListItem(const LLConversation* conversation); + virtual ~LLConversationLogListItem(); + + void onMouseEnter(S32 x, S32 y, MASK mask); + void onMouseLeave(S32 x, S32 y, MASK mask); + + virtual void setValue(const LLSD& value); + + virtual bool postBuild(); + + void onIMFloaterShown(const LLUUID& session_id); + void onRemoveBtnClicked(); + + const LLConversation* getConversation() const { return mConversation; } + + void highlightNameDate(const std::string& highlited_text); + + void onDoubleClick(); + + /** + * updates string value of last interaction time from conversation + */ + void updateTimestamp(); + void updateName(); + void updateOfflineIMs(); + +private: + + void initIcons(); + + const LLConversation* mConversation; + + LLTextBox* mConversationName; + LLTextBox* mConversationDate; + + boost::signals2::connection mIMFloaterShowedConnection; +}; + +#endif /* LLCONVERSATIONLOGITEM_H_ */ diff --git a/indra/newview/llconversationmodel.cpp b/indra/newview/llconversationmodel.cpp index a616f308c7..4cd85ac756 100644 --- a/indra/newview/llconversationmodel.cpp +++ b/indra/newview/llconversationmodel.cpp @@ -1,765 +1,765 @@ -/** - * @file llconversationmodel.cpp - * @brief Implementation of conversations list - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#include "llviewerprecompiledheaders.h" - -#include "llagent.h" -#include "llavatarnamecache.h" -#include "llavataractions.h" -#include "llevents.h" -#include "llfloaterimsession.h" -#include "llsdutil.h" -#include "llconversationmodel.h" -#include "llimview.h" //For LLIMModel -#include "lltrans.h" - -// -// Conversation items : common behaviors -// - -LLConversationItem::LLConversationItem(std::string display_name, const LLUUID& uuid, LLFolderViewModelInterface& root_view_model) : - LLFolderViewModelItemCommon(root_view_model), - mName(display_name), - mUUID(uuid), - mNeedsRefresh(true), - mConvType(CONV_UNKNOWN), - mLastActiveTime(0.0), - mDisplayModeratorOptions(false), - mDisplayGroupBanOptions(false), - mAvatarNameCacheConnection() -{ -} - -LLConversationItem::LLConversationItem(const LLUUID& uuid, LLFolderViewModelInterface& root_view_model) : - LLFolderViewModelItemCommon(root_view_model), - mName(""), - mUUID(uuid), - mNeedsRefresh(true), - mConvType(CONV_UNKNOWN), - mLastActiveTime(0.0), - mDisplayModeratorOptions(false), - mDisplayGroupBanOptions(false), - mAvatarNameCacheConnection() -{ -} - -LLConversationItem::LLConversationItem(LLFolderViewModelInterface& root_view_model) : - LLFolderViewModelItemCommon(root_view_model), - mName(""), - mUUID(), - mNeedsRefresh(true), - mConvType(CONV_UNKNOWN), - mLastActiveTime(0.0), - mDisplayModeratorOptions(false), - mDisplayGroupBanOptions(false), - mAvatarNameCacheConnection() -{ -} - -LLConversationItem::~LLConversationItem() -{ - // Disconnect any previous avatar name cache connection to ensure - // that the callback method is not called after destruction - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - - clearChildren(); -} - -//virtual -void LLConversationItem::addChild(LLFolderViewModelItem* child) -{ - // Avoid duplicates: bail out if that child is already present in the list - // Note: this happens when models are created and 'parented' before views - // This is performance unfriendly, but conversation can addToFolder multiple times - child_list_t::const_iterator iter; - for (iter = mChildren.begin(); iter != mChildren.end(); iter++) - { - if (child == *iter) - { - return; - } - } - LLFolderViewModelItemCommon::addChild(child); -} - -void LLConversationItem::postEvent(const std::string& event_type, LLConversationItemSession* session, LLConversationItemParticipant* participant) -{ - LLUUID session_id = (session ? session->getUUID() : LLUUID()); - LLUUID participant_id = (participant ? participant->getUUID() : LLUUID()); - LLSD event(LLSDMap("type", event_type)("session_uuid", session_id)("participant_uuid", participant_id)); - LLEventPumps::instance().obtain("ConversationsEvents").post(event); -} - -// Virtual action callbacks -void LLConversationItem::performAction(LLInventoryModel* model, std::string action) -{ -} - -void LLConversationItem::openItem( void ) -{ -} - -void LLConversationItem::closeItem( void ) -{ -} - -void LLConversationItem::previewItem( void ) -{ -} - -void LLConversationItem::showProperties(void) -{ -} - -void LLConversationItem::buildParticipantMenuOptions(menuentry_vec_t& items, U32 flags) -{ - if (flags & ITEM_IN_MULTI_SELECTION) - { - items.push_back(std::string("im")); - items.push_back(std::string("offer_teleport")); - items.push_back(std::string("voice_call")); - items.push_back(std::string("remove_friends")); - } - else - { - items.push_back(std::string("view_profile")); - items.push_back(std::string("im")); - items.push_back(std::string("offer_teleport")); - items.push_back(std::string("request_teleport")); - - if (getType() != CONV_SESSION_1_ON_1) - { - items.push_back(std::string("voice_call")); - } - else - { - LLVoiceChannel* voice_channel = LLIMModel::getInstance() ? LLIMModel::getInstance()->getVoiceChannel(this->getUUID()) : NULL; - if(voice_channel != LLVoiceChannel::getCurrentVoiceChannel()) - { - items.push_back(std::string("voice_call")); - } - else - { - items.push_back(std::string("disconnect_from_voice")); - } - } - - items.push_back(std::string("chat_history")); - items.push_back(std::string("separator_chat_history")); - items.push_back(std::string("add_friend")); - items.push_back(std::string("remove_friend")); - items.push_back(std::string("invite_to_group")); - items.push_back(std::string("separator_invite_to_group")); - if (static_cast(mParent)->getType() == CONV_SESSION_NEARBY) - items.push_back(std::string("zoom_in")); - items.push_back(std::string("map")); - items.push_back(std::string("share")); - items.push_back(std::string("pay")); - items.push_back(std::string("report_abuse")); - items.push_back(std::string("block_unblock")); - items.push_back(std::string("MuteText")); - - if ((getType() != CONV_SESSION_1_ON_1) && mDisplayModeratorOptions) - { - items.push_back(std::string("Moderator Options Separator")); - items.push_back(std::string("Moderator Options")); - items.push_back(std::string("AllowTextChat")); - items.push_back(std::string("moderate_voice_separator")); - items.push_back(std::string("ModerateVoiceMuteSelected")); - items.push_back(std::string("ModerateVoiceUnMuteSelected")); - items.push_back(std::string("ModerateVoiceMute")); - items.push_back(std::string("ModerateVoiceUnmute")); - } - - if ((getType() != CONV_SESSION_1_ON_1) && mDisplayGroupBanOptions) - { - items.push_back(std::string("Group Ban Separator")); - items.push_back(std::string("BanMember")); - } - } -} - -// method does subscription to changes in avatar name cache for current session/participant conversation item. -void LLConversationItem::fetchAvatarName(bool isParticipant /*= true*/) -{ - LLUUID item_id = getUUID(); - - // item should not be null for participants - if (isParticipant) - { - llassert(item_id.notNull()); - } - - // disconnect any previous avatar name cache connection - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - - // exclude nearby chat item - if (item_id.notNull()) - { - // for P2P session item, override it as item of called agent - if (CONV_SESSION_1_ON_1 == getType()) - { - item_id = LLIMModel::getInstance()->getOtherParticipantID(item_id); - } - - // subscribe on avatar name cache changes for participant and session items - mAvatarNameCacheConnection = LLAvatarNameCache::get(item_id, boost::bind(&LLConversationItem::onAvatarNameCache, this, _2)); - } -} - -// -// LLConversationItemSession -// - -LLConversationItemSession::LLConversationItemSession(std::string display_name, const LLUUID& uuid, LLFolderViewModelInterface& root_view_model) : - LLConversationItem(display_name,uuid,root_view_model), - mIsLoaded(false) -{ - mConvType = CONV_SESSION_UNKNOWN; -} - -LLConversationItemSession::LLConversationItemSession(const LLUUID& uuid, LLFolderViewModelInterface& root_view_model) : - LLConversationItem(uuid,root_view_model) -{ - mConvType = CONV_SESSION_UNKNOWN; -} - -LLConversationItemSession::~LLConversationItemSession() -{ - clearAndDeparentModels(); -} - -bool LLConversationItemSession::hasChildren() const -{ - return getChildrenCount() > 0; -} - -void LLConversationItemSession::addParticipant(LLConversationItemParticipant* participant) -{ - addChild(participant); - mIsLoaded = true; - mNeedsRefresh = true; - updateName(participant); - postEvent("add_participant", this, participant); -} - -void LLConversationItemSession::updateName(LLConversationItemParticipant* participant) -{ - EConversationType conversation_type = getType(); - // We modify the session name only in the case of an ad-hoc session or P2P session, exit otherwise (nothing to do) - if ((conversation_type != CONV_SESSION_AD_HOC) && (conversation_type != CONV_SESSION_1_ON_1)) - { - return; - } - - // Avoid changing the default name if no participant present yet - if (mChildren.size() == 0) - { - return; - } - - uuid_vec_t temp_uuids; // uuids vector for building the added participants' names string - if (conversation_type == CONV_SESSION_AD_HOC || conversation_type == CONV_SESSION_1_ON_1) - { - // Build a string containing the participants UUIDs (minus own agent) and check if ready for display (we don't want "(waiting)" in there) - // Note: we don't bind ourselves to the LLAvatarNameCache event as updateParticipantName() is called by - // onAvatarNameCache() which is itself attached to the same event. - - // In the case of a P2P conversation, we need to grab the name of the other participant in the session instance itself - // as we do not create participants for such a session. - - for (auto itemp : mChildren) - { - LLConversationItem* current_participant = dynamic_cast(itemp); - // Add the avatar uuid to the list (except if it's the own agent uuid) - if (current_participant->getUUID() != gAgentID) - { - LLAvatarName av_name; - if (LLAvatarNameCache::get(current_participant->getUUID(), &av_name)) - { - temp_uuids.push_back(current_participant->getUUID()); - - if (conversation_type == CONV_SESSION_1_ON_1) - { - break; - } - } - } - } - } - - if (temp_uuids.size() != 0) - { - std::string new_session_name; - LLAvatarActions::buildResidentsString(temp_uuids, new_session_name); - renameItem(new_session_name); - postEvent("update_session", this, NULL); - } -} - -void LLConversationItemSession::removeParticipant(LLConversationItemParticipant* participant) -{ - removeChild(participant); - mNeedsRefresh = true; - updateName(participant); - postEvent("remove_participant", this, participant); -} - -void LLConversationItemSession::removeParticipant(const LLUUID& participant_id) -{ - LLConversationItemParticipant* participant = findParticipant(participant_id); - if (participant) - { - removeParticipant(participant); - } -} - -void LLConversationItemSession::clearParticipants() -{ - // clearParticipants function potentially is malfunctioning since it only cleans children of models, - // it does nothing to views that own those models (listeners) - // probably needs to post some kind of 'remove all participants' event - clearChildren(); - mIsLoaded = false; - mNeedsRefresh = true; -} - - -void LLConversationItemSession::clearAndDeparentModels() -{ - for (LLFolderViewModelItem* child : mChildren) - { - if (child->getNumRefs() == 0) - { - // LLConversationItemParticipant can be created but not assigned to any view, - // it was waiting for an "add_participant" event to be processed - delete child; - } - else - { - // Model is still assigned to some view/widget - child->setParent(NULL); - } - } - mChildren.clear(); -} - -LLConversationItemParticipant* LLConversationItemSession::findParticipant(const LLUUID& participant_id) -{ - // This is *not* a general tree parsing algorithm. It assumes that a session contains only - // items (LLConversationItemParticipant) that have themselve no children. - LLConversationItemParticipant* participant = NULL; - child_list_t::iterator iter; - for (iter = mChildren.begin(); iter != mChildren.end(); iter++) - { - participant = dynamic_cast(*iter); - if (participant && participant->hasSameValue(participant_id)) - { - break; - } - } - return (iter == mChildren.end() ? NULL : participant); -} - -void LLConversationItemSession::setParticipantIsMuted(const LLUUID& participant_id, bool is_muted) -{ - LLConversationItemParticipant* participant = findParticipant(participant_id); - if (participant) - { - participant->moderateVoice(is_muted); - } -} - -void LLConversationItemSession::setParticipantIsModerator(const LLUUID& participant_id, bool is_moderator) -{ - LLConversationItemParticipant* participant = findParticipant(participant_id); - if (participant) - { - participant->setIsModerator(is_moderator); - } -} - -void LLConversationItemSession::setTimeNow(const LLUUID& participant_id) -{ - mLastActiveTime = LLFrameTimer::getElapsedSeconds(); - mNeedsRefresh = true; - LLConversationItemParticipant* participant = findParticipant(participant_id); - if (participant) - { - participant->setTimeNow(); - } -} - -void LLConversationItemSession::setDistance(const LLUUID& participant_id, F64 dist) -{ - LLConversationItemParticipant* participant = findParticipant(participant_id); - if (participant) - { - participant->setDistance(dist); - mNeedsRefresh = true; - } -} - -void LLConversationItemSession::buildContextMenu(LLMenuGL& menu, U32 flags) -{ - LL_DEBUGS() << "LLConversationItemParticipant::buildContextMenu()" << LL_ENDL; - menuentry_vec_t items; - menuentry_vec_t disabled_items; - if((flags & ITEM_IN_MULTI_SELECTION) && (this->getType() != CONV_SESSION_NEARBY)) - { - items.push_back(std::string("close_selected_conversations")); - } - if(this->getType() == CONV_SESSION_1_ON_1) - { - items.push_back(std::string("close_conversation")); - items.push_back(std::string("separator_disconnect_from_voice")); - buildParticipantMenuOptions(items, flags); - } - else if(this->getType() == CONV_SESSION_GROUP) - { - items.push_back(std::string("close_conversation")); - addVoiceOptions(items); - items.push_back(std::string("chat_history")); - items.push_back(std::string("separator_chat_history")); - items.push_back(std::string("group_profile")); - items.push_back(std::string("activate_group")); - items.push_back(std::string("leave_group")); - } - else if(this->getType() == CONV_SESSION_AD_HOC) - { - items.push_back(std::string("close_conversation")); - addVoiceOptions(items); - items.push_back(std::string("chat_history")); - } - else if(this->getType() == CONV_SESSION_NEARBY) - { - items.push_back(std::string("chat_history")); - } - - hide_context_entries(menu, items, disabled_items); -} - -void LLConversationItemSession::addVoiceOptions(menuentry_vec_t& items) -{ - LLVoiceChannel* voice_channel = LLIMModel::getInstance() ? LLIMModel::getInstance()->getVoiceChannel(this->getUUID()) : NULL; - - if(voice_channel != LLVoiceChannel::getCurrentVoiceChannel()) - { - items.push_back(std::string("open_voice_conversation")); - } - else - { - items.push_back(std::string("disconnect_from_voice")); - } -} - -// The time of activity of a session is the time of the most recent activity, session and participants included -const bool LLConversationItemSession::getTime(F64& time) const -{ - F64 most_recent_time = mLastActiveTime; - bool has_time = (most_recent_time > 0.1); - LLConversationItemParticipant* participant = NULL; - child_list_t::const_iterator iter; - for (iter = mChildren.begin(); iter != mChildren.end(); iter++) - { - participant = dynamic_cast(*iter); - F64 participant_time; - if (participant && participant->getTime(participant_time)) - { - has_time = true; - most_recent_time = llmax(most_recent_time,participant_time); - } - } - if (has_time) - { - time = most_recent_time; - } - return has_time; -} - -void LLConversationItemSession::dumpDebugData(bool dump_children) -{ - // Session info - LL_INFOS() << "Merov debug : session " << this << ", uuid = " << mUUID << ", name = " << mName << ", is loaded = " << mIsLoaded << LL_ENDL; - // Children info - if (dump_children) - { - for (child_list_t::iterator iter = mChildren.begin(); iter != mChildren.end(); iter++) - { - LLConversationItemParticipant* participant = dynamic_cast(*iter); - if (participant) - { - participant->dumpDebugData(); - } - } - } -} - -// should be invoked only for P2P sessions -void LLConversationItemSession::onAvatarNameCache(const LLAvatarName& av_name) -{ - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - - renameItem(av_name.getDisplayName()); - postEvent("update_session", this, NULL); -} - -// -// LLConversationItemParticipant -// - -LLConversationItemParticipant::LLConversationItemParticipant(std::string display_name, const LLUUID& uuid, LLFolderViewModelInterface& root_view_model) : - LLConversationItem(display_name,uuid,root_view_model), - mIsModeratorMuted(false), - mIsModerator(false), - mDisplayModeratorLabel(false), - mDistToAgent(-1.0) -{ - mDisplayName = display_name; - mConvType = CONV_PARTICIPANT; -} - -LLConversationItemParticipant::LLConversationItemParticipant(const LLUUID& uuid, LLFolderViewModelInterface& root_view_model) : - LLConversationItem(uuid,root_view_model), - mIsModeratorMuted(false), - mIsModerator(false), - mDisplayModeratorLabel(false), - mDistToAgent(-1.0) -{ - mConvType = CONV_PARTICIPANT; -} - -void LLConversationItemParticipant::updateName() -{ - llassert(getUUID().notNull()); - if (getUUID().notNull()) - { - LLAvatarName av_name; - if (LLAvatarNameCache::get(getUUID(),&av_name)) - { - updateName(av_name); - } - } -} - -void LLConversationItemParticipant::onAvatarNameCache(const LLAvatarName& av_name) -{ - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - - updateName(av_name); -} - -void LLConversationItemParticipant::updateName(const LLAvatarName& av_name) -{ - mName = av_name.getUserName(); - mDisplayName = av_name.getDisplayName(); - - if (mDisplayModeratorLabel) - { - mDisplayName += " " + LLTrans::getString("IM_moderator_label"); - } - - renameItem(mDisplayName); - if (mParent != NULL) - { - LLConversationItemSession* parent_session = dynamic_cast(mParent); - if (parent_session != NULL) - { - parent_session->requestSort(); - parent_session->updateName(this); - postEvent("update_participant", parent_session, this); - } - } -} - -void LLConversationItemParticipant::buildContextMenu(LLMenuGL& menu, U32 flags) -{ - menuentry_vec_t items; - menuentry_vec_t disabled_items; - - buildParticipantMenuOptions(items, flags); - - hide_context_entries(menu, items, disabled_items); -} - -LLConversationItemSession* LLConversationItemParticipant::getParentSession() -{ - LLConversationItemSession* parent_session = NULL; - if (hasParent()) - { - parent_session = dynamic_cast(mParent); - } - return parent_session; -} - -void LLConversationItemParticipant::dumpDebugData() -{ - LL_INFOS() << "Merov debug : participant, uuid = " << mUUID << ", name = " << mName << ", display name = " << mDisplayName << ", muted = " << isVoiceMuted() << ", moderator = " << mIsModerator << LL_ENDL; -} - -void LLConversationItemParticipant::setDisplayModeratorRole(bool displayRole) -{ - if (displayRole != mDisplayModeratorLabel) - { - mDisplayModeratorLabel = displayRole; - updateName(); - } -} - -bool LLConversationItemParticipant::isVoiceMuted() -{ - return mIsModeratorMuted || LLMuteList::getInstance()->isMuted(mUUID, LLMute::flagVoiceChat); -} - -// -// LLConversationSort -// - -// Comparison operator: returns "true" is a comes before b, "false" otherwise -bool LLConversationSort::operator()(const LLConversationItem* const& a, const LLConversationItem* const& b) const -{ - LLConversationItem::EConversationType type_a = a->getType(); - LLConversationItem::EConversationType type_b = b->getType(); - - if ((type_a == LLConversationItem::CONV_PARTICIPANT) && (type_b == LLConversationItem::CONV_PARTICIPANT)) - { - // If both items are participants - U32 sort_order = getSortOrderParticipants(); - if (sort_order == LLConversationFilter::SO_DATE) - { - F64 time_a = 0.0; - F64 time_b = 0.0; - bool has_time_a = a->getTime(time_a); - bool has_time_b = b->getTime(time_b); - if (has_time_a && has_time_b) - { - // Most recent comes first - return (time_a > time_b); - } - else if (has_time_a || has_time_b) - { - // If we have only one time available, the element with time must come first - return has_time_a; - } - // If no time available, we'll default to sort by name at the end of this method - } - else if (sort_order == LLConversationFilter::SO_DISTANCE) - { - F64 dist_a = 0.0; - F64 dist_b = 0.0; - bool has_dist_a = a->getDistanceToAgent(dist_a); - bool has_dist_b = b->getDistanceToAgent(dist_b); - if (has_dist_a && has_dist_b) - { - // Closest comes first - return (dist_a < dist_b); - } - else if (has_dist_a || has_dist_b) - { - // If we have only one distance available, the element with it must come first - return has_dist_a; - } - // If no distance available, we'll default to sort by name at the end of this method - } - } - else if ((type_a > LLConversationItem::CONV_PARTICIPANT) && (type_b > LLConversationItem::CONV_PARTICIPANT)) - { - // If both are sessions - U32 sort_order = getSortOrderSessions(); - - if (sort_order == LLConversationFilter::SO_DATE) - { - // Sort by time - F64 time_a = 0.0; - F64 time_b = 0.0; - bool has_time_a = a->getTime(time_a); - bool has_time_b = b->getTime(time_b); - if (has_time_a && has_time_b) - { - // Most recent comes first - return (time_a > time_b); - } - else if (has_time_a || has_time_b) - { - // If we have only one time available, the element with time must come first - return has_time_a; - } - // If no time available, we'll default to sort by name at the end of this method - } - else - { - if ((type_a == LLConversationItem::CONV_SESSION_NEARBY) || (type_b == LLConversationItem::CONV_SESSION_NEARBY)) - { - // If one is the nearby session, put nearby session *always* last - return (type_b == LLConversationItem::CONV_SESSION_NEARBY); - } - else if (sort_order == LLConversationFilter::SO_SESSION_TYPE) - { - if (type_a != type_b) - { - // Lowest types come first. See LLConversationItem definition of types - return (type_a < type_b); - } - // If types are identical, we'll default to sort by name at the end of this method - } - } - } - else - { - // If one item is a participant and the other a session, the session comes before the participant - // so we simply compare the type - // Notes: as a consequence, CONV_UNKNOWN (which should never get created...) always come first - return (type_a > type_b); - } - // By default, in all other possible cases (including sort order type LLConversationFilter::SO_NAME of course), - // we sort by name - S32 compare = LLStringUtil::compareDict(a->getName(), b->getName()); - return (compare < 0); -} - -// -// LLConversationViewModel -// - -void LLConversationViewModel::sort(LLFolderViewFolder* folder) -{ - base_t::sort(folder); -} - -// EOF +/** + * @file llconversationmodel.cpp + * @brief Implementation of conversations list + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#include "llviewerprecompiledheaders.h" + +#include "llagent.h" +#include "llavatarnamecache.h" +#include "llavataractions.h" +#include "llevents.h" +#include "llfloaterimsession.h" +#include "llsdutil.h" +#include "llconversationmodel.h" +#include "llimview.h" //For LLIMModel +#include "lltrans.h" + +// +// Conversation items : common behaviors +// + +LLConversationItem::LLConversationItem(std::string display_name, const LLUUID& uuid, LLFolderViewModelInterface& root_view_model) : + LLFolderViewModelItemCommon(root_view_model), + mName(display_name), + mUUID(uuid), + mNeedsRefresh(true), + mConvType(CONV_UNKNOWN), + mLastActiveTime(0.0), + mDisplayModeratorOptions(false), + mDisplayGroupBanOptions(false), + mAvatarNameCacheConnection() +{ +} + +LLConversationItem::LLConversationItem(const LLUUID& uuid, LLFolderViewModelInterface& root_view_model) : + LLFolderViewModelItemCommon(root_view_model), + mName(""), + mUUID(uuid), + mNeedsRefresh(true), + mConvType(CONV_UNKNOWN), + mLastActiveTime(0.0), + mDisplayModeratorOptions(false), + mDisplayGroupBanOptions(false), + mAvatarNameCacheConnection() +{ +} + +LLConversationItem::LLConversationItem(LLFolderViewModelInterface& root_view_model) : + LLFolderViewModelItemCommon(root_view_model), + mName(""), + mUUID(), + mNeedsRefresh(true), + mConvType(CONV_UNKNOWN), + mLastActiveTime(0.0), + mDisplayModeratorOptions(false), + mDisplayGroupBanOptions(false), + mAvatarNameCacheConnection() +{ +} + +LLConversationItem::~LLConversationItem() +{ + // Disconnect any previous avatar name cache connection to ensure + // that the callback method is not called after destruction + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + + clearChildren(); +} + +//virtual +void LLConversationItem::addChild(LLFolderViewModelItem* child) +{ + // Avoid duplicates: bail out if that child is already present in the list + // Note: this happens when models are created and 'parented' before views + // This is performance unfriendly, but conversation can addToFolder multiple times + child_list_t::const_iterator iter; + for (iter = mChildren.begin(); iter != mChildren.end(); iter++) + { + if (child == *iter) + { + return; + } + } + LLFolderViewModelItemCommon::addChild(child); +} + +void LLConversationItem::postEvent(const std::string& event_type, LLConversationItemSession* session, LLConversationItemParticipant* participant) +{ + LLUUID session_id = (session ? session->getUUID() : LLUUID()); + LLUUID participant_id = (participant ? participant->getUUID() : LLUUID()); + LLSD event(LLSDMap("type", event_type)("session_uuid", session_id)("participant_uuid", participant_id)); + LLEventPumps::instance().obtain("ConversationsEvents").post(event); +} + +// Virtual action callbacks +void LLConversationItem::performAction(LLInventoryModel* model, std::string action) +{ +} + +void LLConversationItem::openItem( void ) +{ +} + +void LLConversationItem::closeItem( void ) +{ +} + +void LLConversationItem::previewItem( void ) +{ +} + +void LLConversationItem::showProperties(void) +{ +} + +void LLConversationItem::buildParticipantMenuOptions(menuentry_vec_t& items, U32 flags) +{ + if (flags & ITEM_IN_MULTI_SELECTION) + { + items.push_back(std::string("im")); + items.push_back(std::string("offer_teleport")); + items.push_back(std::string("voice_call")); + items.push_back(std::string("remove_friends")); + } + else + { + items.push_back(std::string("view_profile")); + items.push_back(std::string("im")); + items.push_back(std::string("offer_teleport")); + items.push_back(std::string("request_teleport")); + + if (getType() != CONV_SESSION_1_ON_1) + { + items.push_back(std::string("voice_call")); + } + else + { + LLVoiceChannel* voice_channel = LLIMModel::getInstance() ? LLIMModel::getInstance()->getVoiceChannel(this->getUUID()) : NULL; + if(voice_channel != LLVoiceChannel::getCurrentVoiceChannel()) + { + items.push_back(std::string("voice_call")); + } + else + { + items.push_back(std::string("disconnect_from_voice")); + } + } + + items.push_back(std::string("chat_history")); + items.push_back(std::string("separator_chat_history")); + items.push_back(std::string("add_friend")); + items.push_back(std::string("remove_friend")); + items.push_back(std::string("invite_to_group")); + items.push_back(std::string("separator_invite_to_group")); + if (static_cast(mParent)->getType() == CONV_SESSION_NEARBY) + items.push_back(std::string("zoom_in")); + items.push_back(std::string("map")); + items.push_back(std::string("share")); + items.push_back(std::string("pay")); + items.push_back(std::string("report_abuse")); + items.push_back(std::string("block_unblock")); + items.push_back(std::string("MuteText")); + + if ((getType() != CONV_SESSION_1_ON_1) && mDisplayModeratorOptions) + { + items.push_back(std::string("Moderator Options Separator")); + items.push_back(std::string("Moderator Options")); + items.push_back(std::string("AllowTextChat")); + items.push_back(std::string("moderate_voice_separator")); + items.push_back(std::string("ModerateVoiceMuteSelected")); + items.push_back(std::string("ModerateVoiceUnMuteSelected")); + items.push_back(std::string("ModerateVoiceMute")); + items.push_back(std::string("ModerateVoiceUnmute")); + } + + if ((getType() != CONV_SESSION_1_ON_1) && mDisplayGroupBanOptions) + { + items.push_back(std::string("Group Ban Separator")); + items.push_back(std::string("BanMember")); + } + } +} + +// method does subscription to changes in avatar name cache for current session/participant conversation item. +void LLConversationItem::fetchAvatarName(bool isParticipant /*= true*/) +{ + LLUUID item_id = getUUID(); + + // item should not be null for participants + if (isParticipant) + { + llassert(item_id.notNull()); + } + + // disconnect any previous avatar name cache connection + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + + // exclude nearby chat item + if (item_id.notNull()) + { + // for P2P session item, override it as item of called agent + if (CONV_SESSION_1_ON_1 == getType()) + { + item_id = LLIMModel::getInstance()->getOtherParticipantID(item_id); + } + + // subscribe on avatar name cache changes for participant and session items + mAvatarNameCacheConnection = LLAvatarNameCache::get(item_id, boost::bind(&LLConversationItem::onAvatarNameCache, this, _2)); + } +} + +// +// LLConversationItemSession +// + +LLConversationItemSession::LLConversationItemSession(std::string display_name, const LLUUID& uuid, LLFolderViewModelInterface& root_view_model) : + LLConversationItem(display_name,uuid,root_view_model), + mIsLoaded(false) +{ + mConvType = CONV_SESSION_UNKNOWN; +} + +LLConversationItemSession::LLConversationItemSession(const LLUUID& uuid, LLFolderViewModelInterface& root_view_model) : + LLConversationItem(uuid,root_view_model) +{ + mConvType = CONV_SESSION_UNKNOWN; +} + +LLConversationItemSession::~LLConversationItemSession() +{ + clearAndDeparentModels(); +} + +bool LLConversationItemSession::hasChildren() const +{ + return getChildrenCount() > 0; +} + +void LLConversationItemSession::addParticipant(LLConversationItemParticipant* participant) +{ + addChild(participant); + mIsLoaded = true; + mNeedsRefresh = true; + updateName(participant); + postEvent("add_participant", this, participant); +} + +void LLConversationItemSession::updateName(LLConversationItemParticipant* participant) +{ + EConversationType conversation_type = getType(); + // We modify the session name only in the case of an ad-hoc session or P2P session, exit otherwise (nothing to do) + if ((conversation_type != CONV_SESSION_AD_HOC) && (conversation_type != CONV_SESSION_1_ON_1)) + { + return; + } + + // Avoid changing the default name if no participant present yet + if (mChildren.size() == 0) + { + return; + } + + uuid_vec_t temp_uuids; // uuids vector for building the added participants' names string + if (conversation_type == CONV_SESSION_AD_HOC || conversation_type == CONV_SESSION_1_ON_1) + { + // Build a string containing the participants UUIDs (minus own agent) and check if ready for display (we don't want "(waiting)" in there) + // Note: we don't bind ourselves to the LLAvatarNameCache event as updateParticipantName() is called by + // onAvatarNameCache() which is itself attached to the same event. + + // In the case of a P2P conversation, we need to grab the name of the other participant in the session instance itself + // as we do not create participants for such a session. + + for (auto itemp : mChildren) + { + LLConversationItem* current_participant = dynamic_cast(itemp); + // Add the avatar uuid to the list (except if it's the own agent uuid) + if (current_participant->getUUID() != gAgentID) + { + LLAvatarName av_name; + if (LLAvatarNameCache::get(current_participant->getUUID(), &av_name)) + { + temp_uuids.push_back(current_participant->getUUID()); + + if (conversation_type == CONV_SESSION_1_ON_1) + { + break; + } + } + } + } + } + + if (temp_uuids.size() != 0) + { + std::string new_session_name; + LLAvatarActions::buildResidentsString(temp_uuids, new_session_name); + renameItem(new_session_name); + postEvent("update_session", this, NULL); + } +} + +void LLConversationItemSession::removeParticipant(LLConversationItemParticipant* participant) +{ + removeChild(participant); + mNeedsRefresh = true; + updateName(participant); + postEvent("remove_participant", this, participant); +} + +void LLConversationItemSession::removeParticipant(const LLUUID& participant_id) +{ + LLConversationItemParticipant* participant = findParticipant(participant_id); + if (participant) + { + removeParticipant(participant); + } +} + +void LLConversationItemSession::clearParticipants() +{ + // clearParticipants function potentially is malfunctioning since it only cleans children of models, + // it does nothing to views that own those models (listeners) + // probably needs to post some kind of 'remove all participants' event + clearChildren(); + mIsLoaded = false; + mNeedsRefresh = true; +} + + +void LLConversationItemSession::clearAndDeparentModels() +{ + for (LLFolderViewModelItem* child : mChildren) + { + if (child->getNumRefs() == 0) + { + // LLConversationItemParticipant can be created but not assigned to any view, + // it was waiting for an "add_participant" event to be processed + delete child; + } + else + { + // Model is still assigned to some view/widget + child->setParent(NULL); + } + } + mChildren.clear(); +} + +LLConversationItemParticipant* LLConversationItemSession::findParticipant(const LLUUID& participant_id) +{ + // This is *not* a general tree parsing algorithm. It assumes that a session contains only + // items (LLConversationItemParticipant) that have themselve no children. + LLConversationItemParticipant* participant = NULL; + child_list_t::iterator iter; + for (iter = mChildren.begin(); iter != mChildren.end(); iter++) + { + participant = dynamic_cast(*iter); + if (participant && participant->hasSameValue(participant_id)) + { + break; + } + } + return (iter == mChildren.end() ? NULL : participant); +} + +void LLConversationItemSession::setParticipantIsMuted(const LLUUID& participant_id, bool is_muted) +{ + LLConversationItemParticipant* participant = findParticipant(participant_id); + if (participant) + { + participant->moderateVoice(is_muted); + } +} + +void LLConversationItemSession::setParticipantIsModerator(const LLUUID& participant_id, bool is_moderator) +{ + LLConversationItemParticipant* participant = findParticipant(participant_id); + if (participant) + { + participant->setIsModerator(is_moderator); + } +} + +void LLConversationItemSession::setTimeNow(const LLUUID& participant_id) +{ + mLastActiveTime = LLFrameTimer::getElapsedSeconds(); + mNeedsRefresh = true; + LLConversationItemParticipant* participant = findParticipant(participant_id); + if (participant) + { + participant->setTimeNow(); + } +} + +void LLConversationItemSession::setDistance(const LLUUID& participant_id, F64 dist) +{ + LLConversationItemParticipant* participant = findParticipant(participant_id); + if (participant) + { + participant->setDistance(dist); + mNeedsRefresh = true; + } +} + +void LLConversationItemSession::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + LL_DEBUGS() << "LLConversationItemParticipant::buildContextMenu()" << LL_ENDL; + menuentry_vec_t items; + menuentry_vec_t disabled_items; + if((flags & ITEM_IN_MULTI_SELECTION) && (this->getType() != CONV_SESSION_NEARBY)) + { + items.push_back(std::string("close_selected_conversations")); + } + if(this->getType() == CONV_SESSION_1_ON_1) + { + items.push_back(std::string("close_conversation")); + items.push_back(std::string("separator_disconnect_from_voice")); + buildParticipantMenuOptions(items, flags); + } + else if(this->getType() == CONV_SESSION_GROUP) + { + items.push_back(std::string("close_conversation")); + addVoiceOptions(items); + items.push_back(std::string("chat_history")); + items.push_back(std::string("separator_chat_history")); + items.push_back(std::string("group_profile")); + items.push_back(std::string("activate_group")); + items.push_back(std::string("leave_group")); + } + else if(this->getType() == CONV_SESSION_AD_HOC) + { + items.push_back(std::string("close_conversation")); + addVoiceOptions(items); + items.push_back(std::string("chat_history")); + } + else if(this->getType() == CONV_SESSION_NEARBY) + { + items.push_back(std::string("chat_history")); + } + + hide_context_entries(menu, items, disabled_items); +} + +void LLConversationItemSession::addVoiceOptions(menuentry_vec_t& items) +{ + LLVoiceChannel* voice_channel = LLIMModel::getInstance() ? LLIMModel::getInstance()->getVoiceChannel(this->getUUID()) : NULL; + + if(voice_channel != LLVoiceChannel::getCurrentVoiceChannel()) + { + items.push_back(std::string("open_voice_conversation")); + } + else + { + items.push_back(std::string("disconnect_from_voice")); + } +} + +// The time of activity of a session is the time of the most recent activity, session and participants included +const bool LLConversationItemSession::getTime(F64& time) const +{ + F64 most_recent_time = mLastActiveTime; + bool has_time = (most_recent_time > 0.1); + LLConversationItemParticipant* participant = NULL; + child_list_t::const_iterator iter; + for (iter = mChildren.begin(); iter != mChildren.end(); iter++) + { + participant = dynamic_cast(*iter); + F64 participant_time; + if (participant && participant->getTime(participant_time)) + { + has_time = true; + most_recent_time = llmax(most_recent_time,participant_time); + } + } + if (has_time) + { + time = most_recent_time; + } + return has_time; +} + +void LLConversationItemSession::dumpDebugData(bool dump_children) +{ + // Session info + LL_INFOS() << "Merov debug : session " << this << ", uuid = " << mUUID << ", name = " << mName << ", is loaded = " << mIsLoaded << LL_ENDL; + // Children info + if (dump_children) + { + for (child_list_t::iterator iter = mChildren.begin(); iter != mChildren.end(); iter++) + { + LLConversationItemParticipant* participant = dynamic_cast(*iter); + if (participant) + { + participant->dumpDebugData(); + } + } + } +} + +// should be invoked only for P2P sessions +void LLConversationItemSession::onAvatarNameCache(const LLAvatarName& av_name) +{ + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + + renameItem(av_name.getDisplayName()); + postEvent("update_session", this, NULL); +} + +// +// LLConversationItemParticipant +// + +LLConversationItemParticipant::LLConversationItemParticipant(std::string display_name, const LLUUID& uuid, LLFolderViewModelInterface& root_view_model) : + LLConversationItem(display_name,uuid,root_view_model), + mIsModeratorMuted(false), + mIsModerator(false), + mDisplayModeratorLabel(false), + mDistToAgent(-1.0) +{ + mDisplayName = display_name; + mConvType = CONV_PARTICIPANT; +} + +LLConversationItemParticipant::LLConversationItemParticipant(const LLUUID& uuid, LLFolderViewModelInterface& root_view_model) : + LLConversationItem(uuid,root_view_model), + mIsModeratorMuted(false), + mIsModerator(false), + mDisplayModeratorLabel(false), + mDistToAgent(-1.0) +{ + mConvType = CONV_PARTICIPANT; +} + +void LLConversationItemParticipant::updateName() +{ + llassert(getUUID().notNull()); + if (getUUID().notNull()) + { + LLAvatarName av_name; + if (LLAvatarNameCache::get(getUUID(),&av_name)) + { + updateName(av_name); + } + } +} + +void LLConversationItemParticipant::onAvatarNameCache(const LLAvatarName& av_name) +{ + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + + updateName(av_name); +} + +void LLConversationItemParticipant::updateName(const LLAvatarName& av_name) +{ + mName = av_name.getUserName(); + mDisplayName = av_name.getDisplayName(); + + if (mDisplayModeratorLabel) + { + mDisplayName += " " + LLTrans::getString("IM_moderator_label"); + } + + renameItem(mDisplayName); + if (mParent != NULL) + { + LLConversationItemSession* parent_session = dynamic_cast(mParent); + if (parent_session != NULL) + { + parent_session->requestSort(); + parent_session->updateName(this); + postEvent("update_participant", parent_session, this); + } + } +} + +void LLConversationItemParticipant::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + menuentry_vec_t items; + menuentry_vec_t disabled_items; + + buildParticipantMenuOptions(items, flags); + + hide_context_entries(menu, items, disabled_items); +} + +LLConversationItemSession* LLConversationItemParticipant::getParentSession() +{ + LLConversationItemSession* parent_session = NULL; + if (hasParent()) + { + parent_session = dynamic_cast(mParent); + } + return parent_session; +} + +void LLConversationItemParticipant::dumpDebugData() +{ + LL_INFOS() << "Merov debug : participant, uuid = " << mUUID << ", name = " << mName << ", display name = " << mDisplayName << ", muted = " << isVoiceMuted() << ", moderator = " << mIsModerator << LL_ENDL; +} + +void LLConversationItemParticipant::setDisplayModeratorRole(bool displayRole) +{ + if (displayRole != mDisplayModeratorLabel) + { + mDisplayModeratorLabel = displayRole; + updateName(); + } +} + +bool LLConversationItemParticipant::isVoiceMuted() +{ + return mIsModeratorMuted || LLMuteList::getInstance()->isMuted(mUUID, LLMute::flagVoiceChat); +} + +// +// LLConversationSort +// + +// Comparison operator: returns "true" is a comes before b, "false" otherwise +bool LLConversationSort::operator()(const LLConversationItem* const& a, const LLConversationItem* const& b) const +{ + LLConversationItem::EConversationType type_a = a->getType(); + LLConversationItem::EConversationType type_b = b->getType(); + + if ((type_a == LLConversationItem::CONV_PARTICIPANT) && (type_b == LLConversationItem::CONV_PARTICIPANT)) + { + // If both items are participants + U32 sort_order = getSortOrderParticipants(); + if (sort_order == LLConversationFilter::SO_DATE) + { + F64 time_a = 0.0; + F64 time_b = 0.0; + bool has_time_a = a->getTime(time_a); + bool has_time_b = b->getTime(time_b); + if (has_time_a && has_time_b) + { + // Most recent comes first + return (time_a > time_b); + } + else if (has_time_a || has_time_b) + { + // If we have only one time available, the element with time must come first + return has_time_a; + } + // If no time available, we'll default to sort by name at the end of this method + } + else if (sort_order == LLConversationFilter::SO_DISTANCE) + { + F64 dist_a = 0.0; + F64 dist_b = 0.0; + bool has_dist_a = a->getDistanceToAgent(dist_a); + bool has_dist_b = b->getDistanceToAgent(dist_b); + if (has_dist_a && has_dist_b) + { + // Closest comes first + return (dist_a < dist_b); + } + else if (has_dist_a || has_dist_b) + { + // If we have only one distance available, the element with it must come first + return has_dist_a; + } + // If no distance available, we'll default to sort by name at the end of this method + } + } + else if ((type_a > LLConversationItem::CONV_PARTICIPANT) && (type_b > LLConversationItem::CONV_PARTICIPANT)) + { + // If both are sessions + U32 sort_order = getSortOrderSessions(); + + if (sort_order == LLConversationFilter::SO_DATE) + { + // Sort by time + F64 time_a = 0.0; + F64 time_b = 0.0; + bool has_time_a = a->getTime(time_a); + bool has_time_b = b->getTime(time_b); + if (has_time_a && has_time_b) + { + // Most recent comes first + return (time_a > time_b); + } + else if (has_time_a || has_time_b) + { + // If we have only one time available, the element with time must come first + return has_time_a; + } + // If no time available, we'll default to sort by name at the end of this method + } + else + { + if ((type_a == LLConversationItem::CONV_SESSION_NEARBY) || (type_b == LLConversationItem::CONV_SESSION_NEARBY)) + { + // If one is the nearby session, put nearby session *always* last + return (type_b == LLConversationItem::CONV_SESSION_NEARBY); + } + else if (sort_order == LLConversationFilter::SO_SESSION_TYPE) + { + if (type_a != type_b) + { + // Lowest types come first. See LLConversationItem definition of types + return (type_a < type_b); + } + // If types are identical, we'll default to sort by name at the end of this method + } + } + } + else + { + // If one item is a participant and the other a session, the session comes before the participant + // so we simply compare the type + // Notes: as a consequence, CONV_UNKNOWN (which should never get created...) always come first + return (type_a > type_b); + } + // By default, in all other possible cases (including sort order type LLConversationFilter::SO_NAME of course), + // we sort by name + S32 compare = LLStringUtil::compareDict(a->getName(), b->getName()); + return (compare < 0); +} + +// +// LLConversationViewModel +// + +void LLConversationViewModel::sort(LLFolderViewFolder* folder) +{ + base_t::sort(folder); +} + +// EOF diff --git a/indra/newview/llconversationmodel.h b/indra/newview/llconversationmodel.h index 87a52a3dd3..c1e48c63a9 100644 --- a/indra/newview/llconversationmodel.h +++ b/indra/newview/llconversationmodel.h @@ -1,326 +1,326 @@ -/** - * @file llconversationmodel.h - * @brief Implementation of conversations list - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLCONVERSATIONMODEL_H -#define LL_LLCONVERSATIONMODEL_H - -#include - -#include "llavatarname.h" -#include "../llui/llfolderviewitem.h" -#include "../llui/llfolderviewmodel.h" -#include "llviewerfoldertype.h" - -// Implementation of conversations list - -class LLConversationItem; -class LLConversationItemSession; -class LLConversationItemParticipant; - -typedef std::map > conversations_items_map; -typedef std::map conversations_widgets_map; - -typedef std::vector menuentry_vec_t; - -// Conversation items: we hold a list of those and create an LLFolderViewItem widget for each -// that we tuck into the mConversationsListPanel. -class LLConversationItem : public LLFolderViewModelItemCommon -{ -public: - enum EConversationType - { - CONV_UNKNOWN = 0, - CONV_PARTICIPANT = 1, - CONV_SESSION_NEARBY = 2, // The order counts here as it is used to sort sessions by type - CONV_SESSION_1_ON_1 = 3, - CONV_SESSION_AD_HOC = 4, - CONV_SESSION_GROUP = 5, - CONV_SESSION_UNKNOWN = 6 - }; - - LLConversationItem(std::string display_name, const LLUUID& uuid, LLFolderViewModelInterface& root_view_model); - LLConversationItem(const LLUUID& uuid, LLFolderViewModelInterface& root_view_model); - LLConversationItem(LLFolderViewModelInterface& root_view_model); - virtual ~LLConversationItem(); - - // Stub those things we won't really be using in this conversation context - virtual const std::string& getName() const { return mName; } - virtual const std::string& getDisplayName() const { return mName; } - virtual const std::string& getSearchableName() const { return mName; } - virtual std::string getSearchableDescription() const { return LLStringUtil::null; } - virtual std::string getSearchableCreatorName() const { return LLStringUtil::null; } - virtual std::string getSearchableUUIDString() const {return LLStringUtil::null;} - virtual const LLUUID& getUUID() const { return mUUID; } - virtual time_t getCreationDate() const { return 0; } - virtual LLPointer getIcon() const { return NULL; } - virtual LLPointer getOpenIcon() const { return getIcon(); } - virtual LLFontGL::StyleFlags getLabelStyle() const { return LLFontGL::NORMAL; } - virtual std::string getLabelSuffix() const { return LLStringUtil::null; } - virtual bool isItemRenameable() const { return true; } - virtual bool renameItem(const std::string& new_name) { mName = new_name; mNeedsRefresh = true; return true; } - virtual bool isItemMovable( void ) const { return false; } - virtual bool isItemRemovable(bool check_worn = true) const { return false; } - virtual bool isItemInTrash( void) const { return false; } - virtual bool removeItem() { return false; } - virtual void removeBatch(std::vector& batch) { } - virtual void move( LLFolderViewModelItem* parent_listener ) { } - virtual bool isItemCopyable(bool can_copy_as_link = true) const { return false; } - virtual bool copyToClipboard() const { return false; } - virtual bool cutToClipboard() { return false; } - virtual bool isClipboardPasteable() const { return false; } - virtual void pasteFromClipboard() { } - virtual void pasteLinkFromClipboard() { } - virtual void buildContextMenu(LLMenuGL& menu, U32 flags) { } - virtual bool isUpToDate() const { return true; } - virtual bool hasChildren() const { return false; } - virtual void addChild(LLFolderViewModelItem* child); - - virtual bool potentiallyVisible() { return true; } - virtual bool filter( LLFolderViewFilter& filter) { return false; } - virtual bool descendantsPassedFilter(S32 filter_generation = -1) { return true; } - virtual void setPassedFilter(bool passed, S32 filter_generation, std::string::size_type string_offset = std::string::npos, std::string::size_type string_size = 0) { } - virtual bool passedFilter(S32 filter_generation = -1) { return true; } - - // The action callbacks - virtual void performAction(LLInventoryModel* model, std::string action); - virtual void openItem( void ); - virtual void closeItem( void ); - virtual void previewItem( void ); - virtual void selectItem(void) { } - virtual void showProperties(void); - virtual void navigateToFolder(bool new_window = false, bool change_mode = false) {} - - // Methods used in sorting (see LLConversationSort::operator()) - EConversationType const getType() const { return mConvType; } - virtual const bool getTime(F64& time) const { time = mLastActiveTime; return (time > 0.1); } - virtual const bool getDistanceToAgent(F64& distance) const { return false; } - - // This method will be called to determine if a drop can be - // performed, and will set drop to true if a drop is - // requested. - // Returns true if a drop is possible/happened, false otherwise. - virtual bool dragOrDrop(MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - std::string& tooltip_msg) { return false; } - -// bool hasSameValues(std::string name, const LLUUID& uuid) { return ((name == mName) && (uuid == mUUID)); } - bool hasSameValue(const LLUUID& uuid) { return (uuid == mUUID); } - - void resetRefresh() { mNeedsRefresh = false; } - bool needsRefresh() { return mNeedsRefresh; } - - void postEvent(const std::string& event_type, LLConversationItemSession* session, LLConversationItemParticipant* participant); - - void buildParticipantMenuOptions(menuentry_vec_t& items, U32 flags); - - void fetchAvatarName(bool isParticipant = true); // fetch and update the avatar name - -protected: - virtual void onAvatarNameCache(const LLAvatarName& av_name) {} - - std::string mName; // Name of the session or the participant - LLUUID mUUID; // UUID of the session or the participant - EConversationType mConvType; // Type of conversation item - bool mNeedsRefresh; // Flag signaling to the view that something changed for this item - F64 mLastActiveTime; - bool mDisplayModeratorOptions; - bool mDisplayGroupBanOptions; - boost::signals2::connection mAvatarNameCacheConnection; -}; - -class LLConversationItemSession : public LLConversationItem -{ -public: - LLConversationItemSession(std::string display_name, const LLUUID& uuid, LLFolderViewModelInterface& root_view_model); - LLConversationItemSession(const LLUUID& uuid, LLFolderViewModelInterface& root_view_model); - ~LLConversationItemSession(); - - /*virtual*/ bool hasChildren() const; - LLPointer getIcon() const { return nullptr; } - void setSessionID(const LLUUID& session_id) { mUUID = session_id; mNeedsRefresh = true; } - void addParticipant(LLConversationItemParticipant* participant); - void updateName(LLConversationItemParticipant* participant); - void removeParticipant(LLConversationItemParticipant* participant); - void removeParticipant(const LLUUID& participant_id); - void clearParticipants(); - void clearAndDeparentModels(); // will delete unowned models and deparent owned ones - LLConversationItemParticipant* findParticipant(const LLUUID& participant_id); - - void setParticipantIsMuted(const LLUUID& participant_id, bool is_muted); - void setParticipantIsModerator(const LLUUID& participant_id, bool is_moderator); - void setTimeNow(const LLUUID& participant_id); - void setDistance(const LLUUID& participant_id, F64 dist); - - bool isLoaded() { return mIsLoaded; } - - void buildContextMenu(LLMenuGL& menu, U32 flags); - void addVoiceOptions(menuentry_vec_t& items); - virtual const bool getTime(F64& time) const; - - void dumpDebugData(bool dump_children = false); - -private: - /*virtual*/ void onAvatarNameCache(const LLAvatarName& av_name); - - bool mIsLoaded; // true if at least one participant has been added to the session, false otherwise -}; - -class LLConversationItemParticipant : public LLConversationItem -{ -public: - LLConversationItemParticipant(std::string display_name, const LLUUID& uuid, LLFolderViewModelInterface& root_view_model); - LLConversationItemParticipant(const LLUUID& uuid, LLFolderViewModelInterface& root_view_model); - - virtual const std::string& getDisplayName() const { return mDisplayName; } - - bool isVoiceMuted(); - bool isModeratorMuted() { return mIsModeratorMuted; } - bool isModerator() const { return mIsModerator; } - void moderateVoice(bool mute_voice) { mIsModeratorMuted = mute_voice; } - void setIsModerator(bool is_moderator) { mIsModerator = is_moderator; mNeedsRefresh = true; } - void setTimeNow() { mLastActiveTime = LLFrameTimer::getElapsedSeconds(); mNeedsRefresh = true; } - void setDistance(F64 dist) { mDistToAgent = dist; mNeedsRefresh = true; } - - void buildContextMenu(LLMenuGL& menu, U32 flags); - - virtual const bool getDistanceToAgent(F64& dist) const { dist = mDistToAgent; return (dist >= 0.0); } - - void updateName(); // get from the cache (do *not* fetch) and update the avatar name - LLConversationItemSession* getParentSession(); - - void dumpDebugData(); - void setModeratorOptionsVisible(bool visible) { mDisplayModeratorOptions = visible; } - void setDisplayModeratorRole(bool displayRole); - void setGroupBanVisible(bool visible) { mDisplayGroupBanOptions = visible; } - -private: - void onAvatarNameCache(const LLAvatarName& av_name); // callback used by fetchAvatarName - void updateName(const LLAvatarName& av_name); - - bool mIsModeratorMuted; // default is false - bool mIsModerator; // default is false - bool mDisplayModeratorLabel; // default is false - std::string mDisplayName; - F64 mDistToAgent; // Distance to the agent. A negative (meaningless) value means the distance has not been set. - boost::signals2::connection mAvatarNameCacheConnection; -}; - -// We don't want to ever filter conversations but we need to declare that class to create a conversation view model. -// We just stubb everything for the moment. -class LLConversationFilter : public LLFolderViewFilter -{ -public: - - enum ESortOrderType - { - SO_NAME = 0, // Sort by name - SO_DATE = 0x1, // Sort by date (most recent) - SO_SESSION_TYPE = 0x2, // Sort by type (valid only for sessions) - SO_DISTANCE = 0x3, // Sort by distance (valid only for participants in nearby chat) - }; - // Default sort order is by type for sessions and by date for participants - static const U32 SO_DEFAULT = (SO_SESSION_TYPE << 16) | (SO_DATE); - - LLConversationFilter() { mEmpty = ""; } - ~LLConversationFilter() {} - - bool check(const LLFolderViewModelItem* item) { return true; } - bool checkFolder(const LLFolderViewModelItem* folder) const { return true; } - void setEmptyLookupMessage(const std::string& message) { } - std::string getEmptyLookupMessage(bool is_empty_folder = false) const { return mEmpty; } - bool showAllResults() const { return true; } - std::string::size_type getStringMatchOffset(LLFolderViewModelItem* item) const { return std::string::npos; } - std::string::size_type getFilterStringSize() const { return 0; } - - bool isActive() const { return false; } - bool isModified() const { return false; } - void clearModified() { } - const std::string& getName() const { return mEmpty; } - const std::string& getFilterText() { return mEmpty; } - void setModified(EFilterModified behavior = FILTER_RESTART) { } - - void resetTime(S32 timeout) { } - bool isTimedOut() { return false; } - - bool isDefault() const { return true; } - bool isNotDefault() const { return false; } - void markDefault() { } - void resetDefault() { } - - S32 getCurrentGeneration() const { return 0; } - S32 getFirstSuccessGeneration() const { return 0; } - S32 getFirstRequiredGeneration() const { return 0; } -private: - std::string mEmpty; -}; - -class LLConversationSort -{ -public: - LLConversationSort(U32 order = LLConversationFilter::SO_DEFAULT) : mSortOrder(order) { } - - // 16 LSB bits used for participants, 16 MSB bits for sessions - U32 getSortOrderSessions() const { return ((mSortOrder >> 16) & 0xFFFF); } - U32 getSortOrderParticipants() const { return (mSortOrder & 0xFFFF); } - void setSortOrderSessions(LLConversationFilter::ESortOrderType session) { mSortOrder = ((session & 0xFFFF) << 16) | (mSortOrder & 0xFFFF); } - void setSortOrderParticipants(LLConversationFilter::ESortOrderType participant) { mSortOrder = (mSortOrder & 0xFFFF0000) | (participant & 0xFFFF); } - - bool operator()(const LLConversationItem* const& a, const LLConversationItem* const& b) const; - operator U32() const { return mSortOrder; } -private: - // Note: we're treating this value as a sort order bitmask as done in other places in the code (e.g. inventory) - U32 mSortOrder; -}; - -class LLConversationViewModel -: public LLFolderViewModel -{ -public: - typedef LLFolderViewModel base_t; - LLConversationViewModel() - : base_t(new LLConversationSort(), new LLConversationFilter()) - {} - - void sort(LLFolderViewFolder* folder); - bool contentsReady() { return true; } // *TODO : we need to check that participants names are available somewhat - bool startDrag(std::vector& items) { return false; } // We do not allow drag of conversation items - -private: -}; - -// Utility function to hide all entries except those in the list -// Can be called multiple times on the same menu (e.g. if multiple items -// are selected). If "append" is false, then only common enabled items -// are set as enabled. - -//(defined in inventorybridge.cpp) -//TODO: Gilbert Linden - Refactor to make this function non-global -void hide_context_entries(LLMenuGL& menu, - const menuentry_vec_t &entries_to_show, - const menuentry_vec_t &disabled_entries); - -#endif // LL_LLCONVERSATIONMODEL_H +/** + * @file llconversationmodel.h + * @brief Implementation of conversations list + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLCONVERSATIONMODEL_H +#define LL_LLCONVERSATIONMODEL_H + +#include + +#include "llavatarname.h" +#include "../llui/llfolderviewitem.h" +#include "../llui/llfolderviewmodel.h" +#include "llviewerfoldertype.h" + +// Implementation of conversations list + +class LLConversationItem; +class LLConversationItemSession; +class LLConversationItemParticipant; + +typedef std::map > conversations_items_map; +typedef std::map conversations_widgets_map; + +typedef std::vector menuentry_vec_t; + +// Conversation items: we hold a list of those and create an LLFolderViewItem widget for each +// that we tuck into the mConversationsListPanel. +class LLConversationItem : public LLFolderViewModelItemCommon +{ +public: + enum EConversationType + { + CONV_UNKNOWN = 0, + CONV_PARTICIPANT = 1, + CONV_SESSION_NEARBY = 2, // The order counts here as it is used to sort sessions by type + CONV_SESSION_1_ON_1 = 3, + CONV_SESSION_AD_HOC = 4, + CONV_SESSION_GROUP = 5, + CONV_SESSION_UNKNOWN = 6 + }; + + LLConversationItem(std::string display_name, const LLUUID& uuid, LLFolderViewModelInterface& root_view_model); + LLConversationItem(const LLUUID& uuid, LLFolderViewModelInterface& root_view_model); + LLConversationItem(LLFolderViewModelInterface& root_view_model); + virtual ~LLConversationItem(); + + // Stub those things we won't really be using in this conversation context + virtual const std::string& getName() const { return mName; } + virtual const std::string& getDisplayName() const { return mName; } + virtual const std::string& getSearchableName() const { return mName; } + virtual std::string getSearchableDescription() const { return LLStringUtil::null; } + virtual std::string getSearchableCreatorName() const { return LLStringUtil::null; } + virtual std::string getSearchableUUIDString() const {return LLStringUtil::null;} + virtual const LLUUID& getUUID() const { return mUUID; } + virtual time_t getCreationDate() const { return 0; } + virtual LLPointer getIcon() const { return NULL; } + virtual LLPointer getOpenIcon() const { return getIcon(); } + virtual LLFontGL::StyleFlags getLabelStyle() const { return LLFontGL::NORMAL; } + virtual std::string getLabelSuffix() const { return LLStringUtil::null; } + virtual bool isItemRenameable() const { return true; } + virtual bool renameItem(const std::string& new_name) { mName = new_name; mNeedsRefresh = true; return true; } + virtual bool isItemMovable( void ) const { return false; } + virtual bool isItemRemovable(bool check_worn = true) const { return false; } + virtual bool isItemInTrash( void) const { return false; } + virtual bool removeItem() { return false; } + virtual void removeBatch(std::vector& batch) { } + virtual void move( LLFolderViewModelItem* parent_listener ) { } + virtual bool isItemCopyable(bool can_copy_as_link = true) const { return false; } + virtual bool copyToClipboard() const { return false; } + virtual bool cutToClipboard() { return false; } + virtual bool isClipboardPasteable() const { return false; } + virtual void pasteFromClipboard() { } + virtual void pasteLinkFromClipboard() { } + virtual void buildContextMenu(LLMenuGL& menu, U32 flags) { } + virtual bool isUpToDate() const { return true; } + virtual bool hasChildren() const { return false; } + virtual void addChild(LLFolderViewModelItem* child); + + virtual bool potentiallyVisible() { return true; } + virtual bool filter( LLFolderViewFilter& filter) { return false; } + virtual bool descendantsPassedFilter(S32 filter_generation = -1) { return true; } + virtual void setPassedFilter(bool passed, S32 filter_generation, std::string::size_type string_offset = std::string::npos, std::string::size_type string_size = 0) { } + virtual bool passedFilter(S32 filter_generation = -1) { return true; } + + // The action callbacks + virtual void performAction(LLInventoryModel* model, std::string action); + virtual void openItem( void ); + virtual void closeItem( void ); + virtual void previewItem( void ); + virtual void selectItem(void) { } + virtual void showProperties(void); + virtual void navigateToFolder(bool new_window = false, bool change_mode = false) {} + + // Methods used in sorting (see LLConversationSort::operator()) + EConversationType const getType() const { return mConvType; } + virtual const bool getTime(F64& time) const { time = mLastActiveTime; return (time > 0.1); } + virtual const bool getDistanceToAgent(F64& distance) const { return false; } + + // This method will be called to determine if a drop can be + // performed, and will set drop to true if a drop is + // requested. + // Returns true if a drop is possible/happened, false otherwise. + virtual bool dragOrDrop(MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + std::string& tooltip_msg) { return false; } + +// bool hasSameValues(std::string name, const LLUUID& uuid) { return ((name == mName) && (uuid == mUUID)); } + bool hasSameValue(const LLUUID& uuid) { return (uuid == mUUID); } + + void resetRefresh() { mNeedsRefresh = false; } + bool needsRefresh() { return mNeedsRefresh; } + + void postEvent(const std::string& event_type, LLConversationItemSession* session, LLConversationItemParticipant* participant); + + void buildParticipantMenuOptions(menuentry_vec_t& items, U32 flags); + + void fetchAvatarName(bool isParticipant = true); // fetch and update the avatar name + +protected: + virtual void onAvatarNameCache(const LLAvatarName& av_name) {} + + std::string mName; // Name of the session or the participant + LLUUID mUUID; // UUID of the session or the participant + EConversationType mConvType; // Type of conversation item + bool mNeedsRefresh; // Flag signaling to the view that something changed for this item + F64 mLastActiveTime; + bool mDisplayModeratorOptions; + bool mDisplayGroupBanOptions; + boost::signals2::connection mAvatarNameCacheConnection; +}; + +class LLConversationItemSession : public LLConversationItem +{ +public: + LLConversationItemSession(std::string display_name, const LLUUID& uuid, LLFolderViewModelInterface& root_view_model); + LLConversationItemSession(const LLUUID& uuid, LLFolderViewModelInterface& root_view_model); + ~LLConversationItemSession(); + + /*virtual*/ bool hasChildren() const; + LLPointer getIcon() const { return nullptr; } + void setSessionID(const LLUUID& session_id) { mUUID = session_id; mNeedsRefresh = true; } + void addParticipant(LLConversationItemParticipant* participant); + void updateName(LLConversationItemParticipant* participant); + void removeParticipant(LLConversationItemParticipant* participant); + void removeParticipant(const LLUUID& participant_id); + void clearParticipants(); + void clearAndDeparentModels(); // will delete unowned models and deparent owned ones + LLConversationItemParticipant* findParticipant(const LLUUID& participant_id); + + void setParticipantIsMuted(const LLUUID& participant_id, bool is_muted); + void setParticipantIsModerator(const LLUUID& participant_id, bool is_moderator); + void setTimeNow(const LLUUID& participant_id); + void setDistance(const LLUUID& participant_id, F64 dist); + + bool isLoaded() { return mIsLoaded; } + + void buildContextMenu(LLMenuGL& menu, U32 flags); + void addVoiceOptions(menuentry_vec_t& items); + virtual const bool getTime(F64& time) const; + + void dumpDebugData(bool dump_children = false); + +private: + /*virtual*/ void onAvatarNameCache(const LLAvatarName& av_name); + + bool mIsLoaded; // true if at least one participant has been added to the session, false otherwise +}; + +class LLConversationItemParticipant : public LLConversationItem +{ +public: + LLConversationItemParticipant(std::string display_name, const LLUUID& uuid, LLFolderViewModelInterface& root_view_model); + LLConversationItemParticipant(const LLUUID& uuid, LLFolderViewModelInterface& root_view_model); + + virtual const std::string& getDisplayName() const { return mDisplayName; } + + bool isVoiceMuted(); + bool isModeratorMuted() { return mIsModeratorMuted; } + bool isModerator() const { return mIsModerator; } + void moderateVoice(bool mute_voice) { mIsModeratorMuted = mute_voice; } + void setIsModerator(bool is_moderator) { mIsModerator = is_moderator; mNeedsRefresh = true; } + void setTimeNow() { mLastActiveTime = LLFrameTimer::getElapsedSeconds(); mNeedsRefresh = true; } + void setDistance(F64 dist) { mDistToAgent = dist; mNeedsRefresh = true; } + + void buildContextMenu(LLMenuGL& menu, U32 flags); + + virtual const bool getDistanceToAgent(F64& dist) const { dist = mDistToAgent; return (dist >= 0.0); } + + void updateName(); // get from the cache (do *not* fetch) and update the avatar name + LLConversationItemSession* getParentSession(); + + void dumpDebugData(); + void setModeratorOptionsVisible(bool visible) { mDisplayModeratorOptions = visible; } + void setDisplayModeratorRole(bool displayRole); + void setGroupBanVisible(bool visible) { mDisplayGroupBanOptions = visible; } + +private: + void onAvatarNameCache(const LLAvatarName& av_name); // callback used by fetchAvatarName + void updateName(const LLAvatarName& av_name); + + bool mIsModeratorMuted; // default is false + bool mIsModerator; // default is false + bool mDisplayModeratorLabel; // default is false + std::string mDisplayName; + F64 mDistToAgent; // Distance to the agent. A negative (meaningless) value means the distance has not been set. + boost::signals2::connection mAvatarNameCacheConnection; +}; + +// We don't want to ever filter conversations but we need to declare that class to create a conversation view model. +// We just stubb everything for the moment. +class LLConversationFilter : public LLFolderViewFilter +{ +public: + + enum ESortOrderType + { + SO_NAME = 0, // Sort by name + SO_DATE = 0x1, // Sort by date (most recent) + SO_SESSION_TYPE = 0x2, // Sort by type (valid only for sessions) + SO_DISTANCE = 0x3, // Sort by distance (valid only for participants in nearby chat) + }; + // Default sort order is by type for sessions and by date for participants + static const U32 SO_DEFAULT = (SO_SESSION_TYPE << 16) | (SO_DATE); + + LLConversationFilter() { mEmpty = ""; } + ~LLConversationFilter() {} + + bool check(const LLFolderViewModelItem* item) { return true; } + bool checkFolder(const LLFolderViewModelItem* folder) const { return true; } + void setEmptyLookupMessage(const std::string& message) { } + std::string getEmptyLookupMessage(bool is_empty_folder = false) const { return mEmpty; } + bool showAllResults() const { return true; } + std::string::size_type getStringMatchOffset(LLFolderViewModelItem* item) const { return std::string::npos; } + std::string::size_type getFilterStringSize() const { return 0; } + + bool isActive() const { return false; } + bool isModified() const { return false; } + void clearModified() { } + const std::string& getName() const { return mEmpty; } + const std::string& getFilterText() { return mEmpty; } + void setModified(EFilterModified behavior = FILTER_RESTART) { } + + void resetTime(S32 timeout) { } + bool isTimedOut() { return false; } + + bool isDefault() const { return true; } + bool isNotDefault() const { return false; } + void markDefault() { } + void resetDefault() { } + + S32 getCurrentGeneration() const { return 0; } + S32 getFirstSuccessGeneration() const { return 0; } + S32 getFirstRequiredGeneration() const { return 0; } +private: + std::string mEmpty; +}; + +class LLConversationSort +{ +public: + LLConversationSort(U32 order = LLConversationFilter::SO_DEFAULT) : mSortOrder(order) { } + + // 16 LSB bits used for participants, 16 MSB bits for sessions + U32 getSortOrderSessions() const { return ((mSortOrder >> 16) & 0xFFFF); } + U32 getSortOrderParticipants() const { return (mSortOrder & 0xFFFF); } + void setSortOrderSessions(LLConversationFilter::ESortOrderType session) { mSortOrder = ((session & 0xFFFF) << 16) | (mSortOrder & 0xFFFF); } + void setSortOrderParticipants(LLConversationFilter::ESortOrderType participant) { mSortOrder = (mSortOrder & 0xFFFF0000) | (participant & 0xFFFF); } + + bool operator()(const LLConversationItem* const& a, const LLConversationItem* const& b) const; + operator U32() const { return mSortOrder; } +private: + // Note: we're treating this value as a sort order bitmask as done in other places in the code (e.g. inventory) + U32 mSortOrder; +}; + +class LLConversationViewModel +: public LLFolderViewModel +{ +public: + typedef LLFolderViewModel base_t; + LLConversationViewModel() + : base_t(new LLConversationSort(), new LLConversationFilter()) + {} + + void sort(LLFolderViewFolder* folder); + bool contentsReady() { return true; } // *TODO : we need to check that participants names are available somewhat + bool startDrag(std::vector& items) { return false; } // We do not allow drag of conversation items + +private: +}; + +// Utility function to hide all entries except those in the list +// Can be called multiple times on the same menu (e.g. if multiple items +// are selected). If "append" is false, then only common enabled items +// are set as enabled. + +//(defined in inventorybridge.cpp) +//TODO: Gilbert Linden - Refactor to make this function non-global +void hide_context_entries(LLMenuGL& menu, + const menuentry_vec_t &entries_to_show, + const menuentry_vec_t &disabled_entries); + +#endif // LL_LLCONVERSATIONMODEL_H diff --git a/indra/newview/llconversationview.cpp b/indra/newview/llconversationview.cpp index 0242e4d9eb..a5b2ee29c7 100644 --- a/indra/newview/llconversationview.cpp +++ b/indra/newview/llconversationview.cpp @@ -1,886 +1,886 @@ -/** - * @file llconversationview.cpp - * @brief Implementation of conversations list widgets and views - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#include "llviewerprecompiledheaders.h" - -#include "llconversationview.h" - -#include -#include "llagentdata.h" -#include "llavataractions.h" -#include "llconversationmodel.h" -#include "llfloaterimsession.h" -#include "llfloaterimnearbychat.h" -#include "llfloaterimsessiontab.h" -#include "llfloaterimcontainer.h" -#include "llfloaterreg.h" -#include "llgroupiconctrl.h" -#include "lluictrlfactory.h" -#include "lltoolbarview.h" - -// -// Implementation of conversations list session widgets -// -static LLDefaultChildRegistry::Register r_conversation_view_session("conversation_view_session"); - -const LLColor4U DEFAULT_WHITE(255, 255, 255); - -class LLNearbyVoiceClientStatusObserver : public LLVoiceClientStatusObserver -{ -public: - - LLNearbyVoiceClientStatusObserver(LLConversationViewSession* conv) - : conversation(conv) - {} - - virtual void onChange(EStatusType status, const std::string &channelURI, bool proximal) - { - conversation->showVoiceIndicator(conversation - && status != STATUS_JOINING - && status != STATUS_LEFT_CHANNEL - && LLVoiceClient::getInstance()->voiceEnabled() - && LLVoiceClient::getInstance()->isVoiceWorking()); - } - -private: - LLConversationViewSession* conversation; -}; - -LLConversationViewSession::Params::Params() : - container() -{} - -LLConversationViewSession::LLConversationViewSession(const LLConversationViewSession::Params& p): - LLFolderViewFolder(p), - mContainer(p.container), - mItemPanel(NULL), - mCallIconLayoutPanel(NULL), - mSessionTitle(NULL), - mSpeakingIndicator(NULL), - mVoiceClientObserver(NULL), - mCollapsedMode(false), - mHasArrow(true), - mIsInActiveVoiceChannel(false), - mFlashStateOn(false), - mFlashStarted(false) -{ - mFlashTimer = new LLFlashTimer(); - mAreChildrenInited = true; // inventory only -} - -LLConversationViewSession::~LLConversationViewSession() -{ - mActiveVoiceChannelConnection.disconnect(); - - if (mVoiceClientObserver) - { - if (LLVoiceClient::instanceExists()) - { - LLVoiceClient::getInstance()->removeObserver(mVoiceClientObserver); - } - delete mVoiceClientObserver; - } - - mFlashTimer->unset(); - delete mFlashTimer; - mFlashStateOn = false; -} - -void LLConversationViewSession::destroyView() -{ - // Chat can create and parent models(listeners) to session's model before creating - // coresponding views, such participant's models normally will wait for idle cycles - // but since we are deleting session and won't be processing any more events, make - // sure unowned LLConversationItemParticipant models are removed as well. - - LLConversationItemSession* vmi = dynamic_cast(getViewModelItem()); - - // CONV_SESSION_1_ON_1 stores participants as two models that belong to views independent - // from session (nasty! These views are widgets in LLFloaterIMSessionTab, see buildConversationViewParticipant) - if (vmi && vmi->getType() != LLConversationItem::CONV_SESSION_1_ON_1) - { - // Destroy existing views - while (!mItems.empty()) - { - LLFolderViewItem *itemp = mItems.back(); - mItems.pop_back(); - - LLFolderViewModelItem* item_vmi = itemp->getViewModelItem(); - if (item_vmi) // supposed to exist - { - // unparent to remove from child list - vmi->removeChild(item_vmi); - } - itemp->destroyView(); - } - - // Not needed in scope of sessions, but just in case - while (!mFolders.empty()) - { - LLFolderViewFolder *folderp = mFolders.back(); - mFolders.pop_back(); - - LLFolderViewModelItem* folder_vmi = folderp->getViewModelItem(); - if (folder_vmi) - { - vmi->removeChild(folder_vmi); - } - folderp->destroyView(); - } - - // Now everything that is left in model(listener) is not owned by views, - // only by sessions, deparent so it won't point to soon to be dead model - vmi->clearAndDeparentModels(); - } - - LLFolderViewFolder::destroyView(); -} - -void LLConversationViewSession::setFlashState(bool flash_state) -{ - if (flash_state && !mFlashStateOn) - { - // flash chat toolbar button if scrolled out of sight (because flashing will not be visible) - if (mContainer->isScrolledOutOfSight(this)) - { - gToolBarView->flashCommand(LLCommandId("chat"), true); - } - } - - mFlashStateOn = flash_state; - mFlashStarted = false; - mFlashTimer->stopFlashing(); -} - -void LLConversationViewSession::setHighlightState(bool hihglight_state) -{ - mFlashStateOn = hihglight_state; - mFlashStarted = true; - mFlashTimer->stopFlashing(); -} - -void LLConversationViewSession::startFlashing() -{ - // Need to start flashing only when "Conversations" is opened or brought on top - if (isInVisibleChain() - && mFlashStateOn - && !mFlashStarted - && ! LLFloaterReg::getTypedInstance("im_container")->isMinimized() ) - { - mFlashStarted = true; - mFlashTimer->startFlashing(); - } -} - -bool LLConversationViewSession::isHighlightAllowed() -{ - return mFlashStateOn || mIsSelected; -} - -bool LLConversationViewSession::isHighlightActive() -{ - return (mFlashStateOn ? (mFlashTimer->isFlashingInProgress() ? mFlashTimer->isCurrentlyHighlighted() : true) : mIsCurSelection); -} - -bool LLConversationViewSession::postBuild() -{ - LLFolderViewItem::postBuild(); - - mItemPanel = LLUICtrlFactory::getInstance()->createFromFile("panel_conversation_list_item.xml", NULL, LLPanel::child_registry_t::instance()); - addChild(mItemPanel); - - mCallIconLayoutPanel = mItemPanel->getChild("call_icon_panel"); - mSessionTitle = mItemPanel->getChild("conversation_title"); - - mActiveVoiceChannelConnection = LLVoiceChannel::setCurrentVoiceChannelChangedCallback(boost::bind(&LLConversationViewSession::onCurrentVoiceSessionChanged, this, _1)); - mSpeakingIndicator = getChild("speaking_indicator"); - - LLConversationItem* vmi = dynamic_cast(getViewModelItem()); - if (vmi) - { - switch(vmi->getType()) - { - case LLConversationItem::CONV_PARTICIPANT: - case LLConversationItem::CONV_SESSION_1_ON_1: - { - LLIMModel::LLIMSession* session= LLIMModel::instance().findIMSession(vmi->getUUID()); - if (session) - { - LLAvatarIconCtrl* icon = mItemPanel->getChild("avatar_icon"); - icon->setVisible(true); - icon->setValue(session->mOtherParticipantID); - mSpeakingIndicator->setSpeakerId(session->mOtherParticipantID, session->mSessionID, true); - mHasArrow = false; - } - break; - } - case LLConversationItem::CONV_SESSION_AD_HOC: - { - LLGroupIconCtrl* icon = mItemPanel->getChild("group_icon"); - icon->setVisible(true); - mSpeakingIndicator->setSpeakerId(gAgentID, vmi->getUUID(), true); - break; - } - case LLConversationItem::CONV_SESSION_GROUP: - { - LLGroupIconCtrl* icon = mItemPanel->getChild("group_icon"); - icon->setVisible(true); - icon->setValue(vmi->getUUID()); - mSpeakingIndicator->setSpeakerId(gAgentID, vmi->getUUID(), true); - break; - } - case LLConversationItem::CONV_SESSION_NEARBY: - { - LLIconCtrl* icon = mItemPanel->getChild("nearby_chat_icon"); - icon->setVisible(true); - mSpeakingIndicator->setSpeakerId(gAgentID, LLUUID::null, true); - mIsInActiveVoiceChannel = true; - if(LLVoiceClient::instanceExists()) - { - if (mVoiceClientObserver) - { - LLVoiceClient::getInstance()->removeObserver(mVoiceClientObserver); - delete mVoiceClientObserver; - } - mVoiceClientObserver = new LLNearbyVoiceClientStatusObserver(this); - LLVoiceClient::getInstance()->addObserver(mVoiceClientObserver); - } - break; - } - default: - break; - } - - refresh(); // requires vmi - } - - return true; -} - -void LLConversationViewSession::draw() -{ - getViewModelItem()->update(); - - const LLFolderViewItem::Params& default_params = LLUICtrlFactory::getDefaultParams(); - const bool show_context = (getRoot() ? getRoot()->getShowSelectionContext() : false); - - // Indicate that flash can start (moot operation if already started, done or not flashing) - startFlashing(); - - // draw highlight for selected items - drawHighlight(show_context, true, sHighlightBgColor, sFlashBgColor, sFocusOutlineColor, sMouseOverColor); - - // Draw children if root folder, or any other folder that is open. Do not draw children when animating to closed state or you get rendering overlap. - bool draw_children = getRoot() == static_cast(this) || isOpen(); - - // Todo/fix this: arrange hides children 'out of bonds', session 'slowly' adjusts container size, unhides children - // this process repeats until children fit - for (folders_t::iterator iter = mFolders.begin(); - iter != mFolders.end();) - { - folders_t::iterator fit = iter++; - (*fit)->setVisible(draw_children); - } - for (items_t::iterator iter = mItems.begin(); - iter != mItems.end();) - { - items_t::iterator iit = iter++; - (*iit)->setVisible(draw_children); - } - - // we don't draw the open folder arrow in minimized mode - if (mHasArrow && !mCollapsedMode) - { - // update the rotation angle of open folder arrow - updateLabelRotation(); - drawOpenFolderArrow(default_params, sFgColor); - } - LLView::draw(); -} - -bool LLConversationViewSession::handleMouseDown( S32 x, S32 y, MASK mask ) -{ - //Will try to select a child node and then itself (if a child was not selected) - bool result = LLFolderViewFolder::handleMouseDown(x, y, mask); - - //This node (conversation) was selected and a child (participant) was not - if(result && getRoot()) - { - if(getRoot()->getCurSelectedItem() == this) - { - LLConversationItem* item = dynamic_cast(getViewModelItem()); - LLUUID session_id = item? item->getUUID() : LLUUID(); - - LLFloaterIMContainer *im_container = LLFloaterReg::getTypedInstance("im_container"); - if (im_container->isConversationsPaneCollapsed() && im_container->getSelectedSession() == session_id) - { - im_container->collapseMessagesPane(!im_container->isMessagesPaneCollapsed()); - } - else - { - im_container->collapseMessagesPane(false); - } - } - selectConversationItem(); - } - - return result; -} - -bool LLConversationViewSession::handleMouseUp( S32 x, S32 y, MASK mask ) -{ - bool result = LLFolderViewFolder::handleMouseUp(x, y, mask); - - LLFloater* volume_floater = LLFloaterReg::findInstance("floater_voice_volume"); - LLFloater* chat_volume_floater = LLFloaterReg::findInstance("chat_voice"); - if (result - && getRoot() && (getRoot()->getCurSelectedItem() == this) - && !(volume_floater && volume_floater->isShown() && volume_floater->hasFocus()) - && !(chat_volume_floater && chat_volume_floater->isShown() && chat_volume_floater->hasFocus())) - { - LLConversationItem* item = dynamic_cast(getViewModelItem()); - LLUUID session_id = item? item->getUUID() : LLUUID(); - LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(session_id); - if(session_floater && !session_floater->hasFocus()) - { - session_floater->setFocus(true); - } - } - - return result; -} - -bool LLConversationViewSession::handleRightMouseDown( S32 x, S32 y, MASK mask ) -{ - bool result = LLFolderViewFolder::handleRightMouseDown(x, y, mask); - - if(result) - { - selectConversationItem(); - } - - return result; -} - -void LLConversationViewSession::selectConversationItem() -{ - if(getRoot()->getCurSelectedItem() == this) - { - LLConversationItem* item = dynamic_cast(getViewModelItem()); - LLUUID session_id = item? item->getUUID() : LLUUID(); - - LLFloaterIMContainer *im_container = LLFloaterReg::getTypedInstance("im_container"); - im_container->flashConversationItemWidget(session_id,false); - im_container->selectConversationPair(session_id, false); - } -} - -// virtual -S32 LLConversationViewSession::arrange(S32* width, S32* height) -{ - //LLFolderViewFolder::arrange computes value for getIndentation() function below - S32 arranged = LLFolderViewFolder::arrange(width, height); - - S32 h_pad = mHasArrow ? getIndentation() + mArrowSize : getIndentation(); - - LLRect rect(mCollapsedMode ? getLocalRect().mLeft : h_pad, - getLocalRect().mTop, - getLocalRect().mRight, - getLocalRect().mTop - getItemHeight()); - mItemPanel->setShape(rect); - - return arranged; -} - -// virtual -void LLConversationViewSession::toggleOpen() -{ - // conversations should not be opened while in minimized mode - if (!mCollapsedMode) - { - LLFolderViewFolder::toggleOpen(); - - // do item's selection when opened - if (LLFolderViewFolder::isOpen()) - { - getParentFolder()->setSelection(this, true); - } - mContainer->reSelectConversation(); - } -} - -void LLConversationViewSession::toggleCollapsedMode(bool is_collapsed) -{ - mCollapsedMode = is_collapsed; - - // hide the layout stack which contains all item's child widgets - // except for the icon which we display in minimized mode - getChild("conversation_item_stack")->setVisible(!mCollapsedMode); - - S32 h_pad = mHasArrow ? getIndentation() + mArrowSize : getIndentation(); - - mItemPanel->translate(mCollapsedMode ? -h_pad : h_pad, 0); -} - -void LLConversationViewSession::setVisibleIfDetached(bool visible) -{ - // Do this only if the conversation floater has been torn off (i.e. no multi floater host) and is not minimized - // Note: minimized dockable floaters are brought to front hence unminimized when made visible and we don't want that here - LLFloater* session_floater = getSessionFloater(); - if (session_floater && session_floater->isDetachedAndNotMinimized()) - { - session_floater->setVisible(visible); - } -} - -LLFloater* LLConversationViewSession::getSessionFloater() -{ - LLFolderViewModelItem* item = mViewModelItem; - LLUUID session_uuid = dynamic_cast(item)->getUUID(); - return LLFloaterIMSessionTab::getConversation(session_uuid); -} - -LLConversationViewParticipant* LLConversationViewSession::findParticipant(const LLUUID& participant_id) -{ - // This is *not* a general tree parsing algorithm. We search only in the mItems list - // assuming there is no mFolders which makes sense for sessions (sessions don't contain - // sessions). - LLConversationViewParticipant* participant = NULL; - items_t::const_iterator iter; - for (iter = getItemsBegin(); iter != getItemsEnd(); iter++) - { - participant = dynamic_cast(*iter); - if (participant->hasSameValue(participant_id)) - { - break; - } - } - return (iter == getItemsEnd() ? NULL : participant); -} - -void LLConversationViewSession::showVoiceIndicator(bool visible) -{ - mCallIconLayoutPanel->setVisible(visible && LLVoiceChannel::getCurrentVoiceChannel()->getSessionID().isNull()); - requestArrange(); -} - -void LLConversationViewSession::refresh() -{ - // Refresh the session view from its model data - LLConversationItem* vmi = dynamic_cast(getViewModelItem()); - if (vmi) - { - vmi->resetRefresh(); - - if (mSessionTitle) - { - if (!highlightFriendTitle(vmi)) - { - LLStyle::Params title_style; - title_style.color = LLUIColorTable::instance().getColor("LabelTextColor"); - mSessionTitle->setText(vmi->getDisplayName(), title_style); - } - } - } - - // Update all speaking indicators - LLSpeakingIndicatorManager::updateSpeakingIndicators(); - - // we should show indicator for specified voice session only if this is current channel. EXT-5562. - if (mSpeakingIndicator) - { - mSpeakingIndicator->setIsActiveChannel(mIsInActiveVoiceChannel); - mSpeakingIndicator->setShowParticipantsSpeaking(mIsInActiveVoiceChannel); - } - - LLConversationViewParticipant* participant = NULL; - items_t::const_iterator iter; - for (iter = getItemsBegin(); iter != getItemsEnd(); iter++) - { - participant = dynamic_cast(*iter); - if (participant) - { - participant->allowSpeakingIndicator(mIsInActiveVoiceChannel); - } - } - - requestArrange(); - if (vmi) - { - // Do the regular upstream refresh - LLFolderViewFolder::refresh(); - } -} - -void LLConversationViewSession::onCurrentVoiceSessionChanged(const LLUUID& session_id) -{ - LLConversationItem* vmi = dynamic_cast(getViewModelItem()); - - if (vmi) - { - bool old_value = mIsInActiveVoiceChannel; - mIsInActiveVoiceChannel = vmi->getUUID() == session_id; - mCallIconLayoutPanel->setVisible(mIsInActiveVoiceChannel); - if (old_value != mIsInActiveVoiceChannel) - { - refresh(); - } - } -} - -bool LLConversationViewSession::highlightFriendTitle(LLConversationItem* vmi) -{ - if(vmi->getType() == LLConversationItem::CONV_PARTICIPANT || vmi->getType() == LLConversationItem::CONV_SESSION_1_ON_1) - { - LLIMModel::LLIMSession* session= LLIMModel::instance().findIMSession(vmi->getUUID()); - if (session && LLAvatarActions::isFriend(session->mOtherParticipantID)) - { - LLStyle::Params title_style; - title_style.color = LLUIColorTable::instance().getColor("ConversationFriendColor"); - mSessionTitle->setText(vmi->getDisplayName(), title_style); - return true; - } - } - return false; -} - -// -// Implementation of conversations list participant (avatar) widgets -// - -static LLDefaultChildRegistry::Register r("conversation_view_participant"); -bool LLConversationViewParticipant::sStaticInitialized = false; -S32 LLConversationViewParticipant::sChildrenWidths[LLConversationViewParticipant::ALIC_COUNT]; - -LLConversationViewParticipant::Params::Params() : -container(), -participant_id(), -avatar_icon("avatar_icon"), -info_button("info_button"), -output_monitor("output_monitor") -{} - -LLConversationViewParticipant::LLConversationViewParticipant( const LLConversationViewParticipant::Params& p ): - LLFolderViewItem(p), - mAvatarIcon(NULL), - mInfoBtn(NULL), - mSpeakingIndicator(NULL), - mUUID(p.participant_id) -{ -} - -LLConversationViewParticipant::~LLConversationViewParticipant() -{ - mActiveVoiceChannelConnection.disconnect(); -} - -void LLConversationViewParticipant::initFromParams(const LLConversationViewParticipant::Params& params) -{ - LLAvatarIconCtrl::Params avatar_icon_params(params.avatar_icon()); - applyXUILayout(avatar_icon_params, this); - LLAvatarIconCtrl * avatarIcon = LLUICtrlFactory::create(avatar_icon_params); - addChild(avatarIcon); - - LLButton::Params info_button_params(params.info_button()); - applyXUILayout(info_button_params, this); - LLButton * button = LLUICtrlFactory::create(info_button_params); - addChild(button); - - LLOutputMonitorCtrl::Params output_monitor_params(params.output_monitor()); - applyXUILayout(output_monitor_params, this); - LLOutputMonitorCtrl * outputMonitor = LLUICtrlFactory::create(output_monitor_params); - addChild(outputMonitor); -} - -bool LLConversationViewParticipant::postBuild() -{ - mAvatarIcon = getChild("avatar_icon"); - - mInfoBtn = getChild("info_btn"); - mInfoBtn->setClickedCallback(boost::bind(&LLConversationViewParticipant::onInfoBtnClick, this)); - mInfoBtn->setVisible(false); - - mSpeakingIndicator = getChild("speaking_indicator"); - - if (!sStaticInitialized) - { - // Remember children widths including their padding from the next sibling, - // so that we can hide and show them again later. - initChildrenWidths(this); - sStaticInitialized = true; - } - - updateChildren(); - if (getViewModelItem()) - { - LLFolderViewItem::postBuild(); - refresh(); - } - return true; -} - -void LLConversationViewParticipant::draw() -{ - static LLUIColor sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE); - static LLUIColor sFgDisabledColor = LLUIColorTable::instance().getColor("MenuItemDisabledColor", DEFAULT_WHITE); - static LLUIColor sHighlightFgColor = LLUIColorTable::instance().getColor("MenuItemHighlightFgColor", DEFAULT_WHITE); - static LLUIColor sHighlightBgColor = LLUIColorTable::instance().getColor("MenuItemHighlightBgColor", DEFAULT_WHITE); - static LLUIColor sFlashBgColor = LLUIColorTable::instance().getColor("MenuItemFlashBgColor", DEFAULT_WHITE); - static LLUIColor sFocusOutlineColor = LLUIColorTable::instance().getColor("InventoryFocusOutlineColor", DEFAULT_WHITE); - static LLUIColor sMouseOverColor = LLUIColorTable::instance().getColor("InventoryMouseOverColor", DEFAULT_WHITE); - - const bool show_context = (getRoot() ? getRoot()->getShowSelectionContext() : false); - - const LLFontGL* font = getLabelFontForStyle(mLabelStyle); - F32 right_x = 0; - - F32 y = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad; - F32 text_left = (F32)getLabelXPos(); - - LLColor4 color; - - LLLocalSpeakerMgr *speakerMgr = LLLocalSpeakerMgr::getInstance(); - - if (speakerMgr && speakerMgr->isSpeakerToBeRemoved(mUUID)) - { - color = sFgDisabledColor; - } - else - { - if (LLAvatarActions::isFriend(mUUID)) - { - color = LLUIColorTable::instance().getColor("ConversationFriendColor"); - } - else - { - color = mIsSelected ? sHighlightFgColor : sFgColor; - } - } - - LLConversationItemParticipant* participant_model = dynamic_cast(getViewModelItem()); - if (participant_model) - { - mSpeakingIndicator->setIsModeratorMuted(participant_model->isModeratorMuted()); - } - - drawHighlight(show_context, mIsSelected, sHighlightBgColor, sFlashBgColor, sFocusOutlineColor, sMouseOverColor); - drawLabel(font, text_left, y, color, right_x); - - LLView::draw(); -} - -// virtual -S32 LLConversationViewParticipant::arrange(S32* width, S32* height) -{ - //Need to call arrange first since it computes value used in getIndentation() - S32 arranged = LLFolderViewItem::arrange(width, height); - - //Adjusts the avatar icon based upon the indentation - LLRect avatarRect(getIndentation(), - mAvatarIcon->getRect().mTop, - getIndentation() + mAvatarIcon->getRect().getWidth(), - mAvatarIcon->getRect().mBottom); - mAvatarIcon->setShape(avatarRect); - - //Since dimensions changed, adjust the children (info button, speaker indicator) - updateChildren(); - - return arranged; -} - -// virtual -void LLConversationViewParticipant::refresh() -{ - // Refresh the participant view from its model data - LLConversationItemParticipant* participant_model = dynamic_cast(getViewModelItem()); - if (participant_model) - { - participant_model->resetRefresh(); - - // *TODO: We should also do something with vmi->isModerator() to echo that state in the UI somewhat - mSpeakingIndicator->setIsModeratorMuted(participant_model->isModeratorMuted()); - - // Do the regular upstream refresh - LLFolderViewItem::refresh(); - } -} - -void LLConversationViewParticipant::addToFolder(LLFolderViewFolder* folder) -{ - // Add the item to the folder (conversation) - LLFolderViewItem::addToFolder(folder); - - // Retrieve the folder (conversation) UUID, which is also the speaker session UUID - LLFolderViewFolder *prnt = getParentFolder(); - if (prnt) - { - LLConversationItem* vmi = dynamic_cast(prnt->getViewModelItem()); - if (vmi) - { - addToSession(vmi->getUUID()); - } - LLConversationViewSession* session = dynamic_cast(prnt); - if (session) - { - allowSpeakingIndicator(session->isInActiveVoiceChannel()); - } - } -} - -void LLConversationViewParticipant::addToSession(const LLUUID& session_id) -{ - //Allows speaking icon image to be loaded based on mUUID - mAvatarIcon->setValue(mUUID); - - //Allows the speaker indicator to be activated based on the user and conversation - mSpeakingIndicator->setSpeakerId(mUUID, session_id); -} - -void LLConversationViewParticipant::onInfoBtnClick() -{ - LLFloaterReg::showInstance("inspect_avatar", LLSD().with("avatar_id", mUUID)); -} - -bool LLConversationViewParticipant::handleMouseDown( S32 x, S32 y, MASK mask ) -{ - bool result = LLFolderViewItem::handleMouseDown(x, y, mask); - - if(result && getRoot()) - { - if(getRoot()->getCurSelectedItem() == this) - { - LLConversationItem* vmi = getParentFolder() ? dynamic_cast(getParentFolder()->getViewModelItem()) : NULL; - LLUUID session_id = vmi? vmi->getUUID() : LLUUID(); - - LLFloaterIMContainer *im_container = LLFloaterReg::getTypedInstance("im_container"); - LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(session_id); - im_container->setSelectedSession(session_id); - im_container->flashConversationItemWidget(session_id,false); - im_container->selectFloater(session_floater); - im_container->collapseMessagesPane(false); - } - } - return result; -} - -void LLConversationViewParticipant::onMouseEnter(S32 x, S32 y, MASK mask) -{ - mInfoBtn->setVisible(true); - updateChildren(); - LLFolderViewItem::onMouseEnter(x, y, mask); -} - -void LLConversationViewParticipant::onMouseLeave(S32 x, S32 y, MASK mask) -{ - mInfoBtn->setVisible(false); - updateChildren(); - LLFolderViewItem::onMouseLeave(x, y, mask); -} - -S32 LLConversationViewParticipant::getLabelXPos() -{ - return getIndentation() + mAvatarIcon->getRect().getWidth() + mIconPad; -} - -// static -void LLConversationViewParticipant::initChildrenWidths(LLConversationViewParticipant* self) -{ - //speaking indicator width + padding - S32 speaking_indicator_width = self->getRect().getWidth() - self->mSpeakingIndicator->getRect().mLeft; - - //info btn width + padding - S32 info_btn_width = self->mSpeakingIndicator->getRect().mLeft - self->mInfoBtn->getRect().mLeft; - - S32 index = ALIC_COUNT; - sChildrenWidths[--index] = info_btn_width; - sChildrenWidths[--index] = speaking_indicator_width; - llassert(index == 0); -} - -void LLConversationViewParticipant::updateChildren() -{ - mLabelPaddingRight = DEFAULT_LABEL_PADDING_RIGHT; - LLView* control; - S32 ctrl_width; - LLRect controlRect; - - //Cycles through controls starting from right to left - for (S32 i = 0; i < ALIC_COUNT; ++i) - { - control = getItemChildView((EAvatarListItemChildIndex)i); - - // skip invisible views - if (!control->getVisible()) continue; - - //Get current pos/dimensions - controlRect = control->getRect(); - - ctrl_width = sChildrenWidths[i]; // including space between current & left controls - // accumulate the amount of space taken by the controls - mLabelPaddingRight += ctrl_width; - - //Reposition visible controls in case adjacent controls to the right are hidden. - controlRect.setLeftTopAndSize( - getLocalRect().getWidth() - mLabelPaddingRight, - controlRect.mTop, - controlRect.getWidth(), - controlRect.getHeight()); - - //Sets the new position - control->setShape(controlRect); - } -} - -LLView* LLConversationViewParticipant::getItemChildView(EAvatarListItemChildIndex child_view_index) -{ - LLView* child_view = NULL; - - switch (child_view_index) - { - case ALIC_SPEAKER_INDICATOR: - child_view = mSpeakingIndicator; - break; - case ALIC_INFO_BUTTON: - child_view = mInfoBtn; - break; - default: - LL_WARNS("AvatarItemReshape") << "Unexpected child view index is passed: " << child_view_index << LL_ENDL; - llassert(0); - break; - // leave child_view untouched - } - - return child_view; -} - -void LLConversationViewParticipant::allowSpeakingIndicator(bool val) -{ - mSpeakingIndicator->setIsActiveChannel(val); -} - -// EOF - +/** + * @file llconversationview.cpp + * @brief Implementation of conversations list widgets and views + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#include "llviewerprecompiledheaders.h" + +#include "llconversationview.h" + +#include +#include "llagentdata.h" +#include "llavataractions.h" +#include "llconversationmodel.h" +#include "llfloaterimsession.h" +#include "llfloaterimnearbychat.h" +#include "llfloaterimsessiontab.h" +#include "llfloaterimcontainer.h" +#include "llfloaterreg.h" +#include "llgroupiconctrl.h" +#include "lluictrlfactory.h" +#include "lltoolbarview.h" + +// +// Implementation of conversations list session widgets +// +static LLDefaultChildRegistry::Register r_conversation_view_session("conversation_view_session"); + +const LLColor4U DEFAULT_WHITE(255, 255, 255); + +class LLNearbyVoiceClientStatusObserver : public LLVoiceClientStatusObserver +{ +public: + + LLNearbyVoiceClientStatusObserver(LLConversationViewSession* conv) + : conversation(conv) + {} + + virtual void onChange(EStatusType status, const std::string &channelURI, bool proximal) + { + conversation->showVoiceIndicator(conversation + && status != STATUS_JOINING + && status != STATUS_LEFT_CHANNEL + && LLVoiceClient::getInstance()->voiceEnabled() + && LLVoiceClient::getInstance()->isVoiceWorking()); + } + +private: + LLConversationViewSession* conversation; +}; + +LLConversationViewSession::Params::Params() : + container() +{} + +LLConversationViewSession::LLConversationViewSession(const LLConversationViewSession::Params& p): + LLFolderViewFolder(p), + mContainer(p.container), + mItemPanel(NULL), + mCallIconLayoutPanel(NULL), + mSessionTitle(NULL), + mSpeakingIndicator(NULL), + mVoiceClientObserver(NULL), + mCollapsedMode(false), + mHasArrow(true), + mIsInActiveVoiceChannel(false), + mFlashStateOn(false), + mFlashStarted(false) +{ + mFlashTimer = new LLFlashTimer(); + mAreChildrenInited = true; // inventory only +} + +LLConversationViewSession::~LLConversationViewSession() +{ + mActiveVoiceChannelConnection.disconnect(); + + if (mVoiceClientObserver) + { + if (LLVoiceClient::instanceExists()) + { + LLVoiceClient::getInstance()->removeObserver(mVoiceClientObserver); + } + delete mVoiceClientObserver; + } + + mFlashTimer->unset(); + delete mFlashTimer; + mFlashStateOn = false; +} + +void LLConversationViewSession::destroyView() +{ + // Chat can create and parent models(listeners) to session's model before creating + // coresponding views, such participant's models normally will wait for idle cycles + // but since we are deleting session and won't be processing any more events, make + // sure unowned LLConversationItemParticipant models are removed as well. + + LLConversationItemSession* vmi = dynamic_cast(getViewModelItem()); + + // CONV_SESSION_1_ON_1 stores participants as two models that belong to views independent + // from session (nasty! These views are widgets in LLFloaterIMSessionTab, see buildConversationViewParticipant) + if (vmi && vmi->getType() != LLConversationItem::CONV_SESSION_1_ON_1) + { + // Destroy existing views + while (!mItems.empty()) + { + LLFolderViewItem *itemp = mItems.back(); + mItems.pop_back(); + + LLFolderViewModelItem* item_vmi = itemp->getViewModelItem(); + if (item_vmi) // supposed to exist + { + // unparent to remove from child list + vmi->removeChild(item_vmi); + } + itemp->destroyView(); + } + + // Not needed in scope of sessions, but just in case + while (!mFolders.empty()) + { + LLFolderViewFolder *folderp = mFolders.back(); + mFolders.pop_back(); + + LLFolderViewModelItem* folder_vmi = folderp->getViewModelItem(); + if (folder_vmi) + { + vmi->removeChild(folder_vmi); + } + folderp->destroyView(); + } + + // Now everything that is left in model(listener) is not owned by views, + // only by sessions, deparent so it won't point to soon to be dead model + vmi->clearAndDeparentModels(); + } + + LLFolderViewFolder::destroyView(); +} + +void LLConversationViewSession::setFlashState(bool flash_state) +{ + if (flash_state && !mFlashStateOn) + { + // flash chat toolbar button if scrolled out of sight (because flashing will not be visible) + if (mContainer->isScrolledOutOfSight(this)) + { + gToolBarView->flashCommand(LLCommandId("chat"), true); + } + } + + mFlashStateOn = flash_state; + mFlashStarted = false; + mFlashTimer->stopFlashing(); +} + +void LLConversationViewSession::setHighlightState(bool hihglight_state) +{ + mFlashStateOn = hihglight_state; + mFlashStarted = true; + mFlashTimer->stopFlashing(); +} + +void LLConversationViewSession::startFlashing() +{ + // Need to start flashing only when "Conversations" is opened or brought on top + if (isInVisibleChain() + && mFlashStateOn + && !mFlashStarted + && ! LLFloaterReg::getTypedInstance("im_container")->isMinimized() ) + { + mFlashStarted = true; + mFlashTimer->startFlashing(); + } +} + +bool LLConversationViewSession::isHighlightAllowed() +{ + return mFlashStateOn || mIsSelected; +} + +bool LLConversationViewSession::isHighlightActive() +{ + return (mFlashStateOn ? (mFlashTimer->isFlashingInProgress() ? mFlashTimer->isCurrentlyHighlighted() : true) : mIsCurSelection); +} + +bool LLConversationViewSession::postBuild() +{ + LLFolderViewItem::postBuild(); + + mItemPanel = LLUICtrlFactory::getInstance()->createFromFile("panel_conversation_list_item.xml", NULL, LLPanel::child_registry_t::instance()); + addChild(mItemPanel); + + mCallIconLayoutPanel = mItemPanel->getChild("call_icon_panel"); + mSessionTitle = mItemPanel->getChild("conversation_title"); + + mActiveVoiceChannelConnection = LLVoiceChannel::setCurrentVoiceChannelChangedCallback(boost::bind(&LLConversationViewSession::onCurrentVoiceSessionChanged, this, _1)); + mSpeakingIndicator = getChild("speaking_indicator"); + + LLConversationItem* vmi = dynamic_cast(getViewModelItem()); + if (vmi) + { + switch(vmi->getType()) + { + case LLConversationItem::CONV_PARTICIPANT: + case LLConversationItem::CONV_SESSION_1_ON_1: + { + LLIMModel::LLIMSession* session= LLIMModel::instance().findIMSession(vmi->getUUID()); + if (session) + { + LLAvatarIconCtrl* icon = mItemPanel->getChild("avatar_icon"); + icon->setVisible(true); + icon->setValue(session->mOtherParticipantID); + mSpeakingIndicator->setSpeakerId(session->mOtherParticipantID, session->mSessionID, true); + mHasArrow = false; + } + break; + } + case LLConversationItem::CONV_SESSION_AD_HOC: + { + LLGroupIconCtrl* icon = mItemPanel->getChild("group_icon"); + icon->setVisible(true); + mSpeakingIndicator->setSpeakerId(gAgentID, vmi->getUUID(), true); + break; + } + case LLConversationItem::CONV_SESSION_GROUP: + { + LLGroupIconCtrl* icon = mItemPanel->getChild("group_icon"); + icon->setVisible(true); + icon->setValue(vmi->getUUID()); + mSpeakingIndicator->setSpeakerId(gAgentID, vmi->getUUID(), true); + break; + } + case LLConversationItem::CONV_SESSION_NEARBY: + { + LLIconCtrl* icon = mItemPanel->getChild("nearby_chat_icon"); + icon->setVisible(true); + mSpeakingIndicator->setSpeakerId(gAgentID, LLUUID::null, true); + mIsInActiveVoiceChannel = true; + if(LLVoiceClient::instanceExists()) + { + if (mVoiceClientObserver) + { + LLVoiceClient::getInstance()->removeObserver(mVoiceClientObserver); + delete mVoiceClientObserver; + } + mVoiceClientObserver = new LLNearbyVoiceClientStatusObserver(this); + LLVoiceClient::getInstance()->addObserver(mVoiceClientObserver); + } + break; + } + default: + break; + } + + refresh(); // requires vmi + } + + return true; +} + +void LLConversationViewSession::draw() +{ + getViewModelItem()->update(); + + const LLFolderViewItem::Params& default_params = LLUICtrlFactory::getDefaultParams(); + const bool show_context = (getRoot() ? getRoot()->getShowSelectionContext() : false); + + // Indicate that flash can start (moot operation if already started, done or not flashing) + startFlashing(); + + // draw highlight for selected items + drawHighlight(show_context, true, sHighlightBgColor, sFlashBgColor, sFocusOutlineColor, sMouseOverColor); + + // Draw children if root folder, or any other folder that is open. Do not draw children when animating to closed state or you get rendering overlap. + bool draw_children = getRoot() == static_cast(this) || isOpen(); + + // Todo/fix this: arrange hides children 'out of bonds', session 'slowly' adjusts container size, unhides children + // this process repeats until children fit + for (folders_t::iterator iter = mFolders.begin(); + iter != mFolders.end();) + { + folders_t::iterator fit = iter++; + (*fit)->setVisible(draw_children); + } + for (items_t::iterator iter = mItems.begin(); + iter != mItems.end();) + { + items_t::iterator iit = iter++; + (*iit)->setVisible(draw_children); + } + + // we don't draw the open folder arrow in minimized mode + if (mHasArrow && !mCollapsedMode) + { + // update the rotation angle of open folder arrow + updateLabelRotation(); + drawOpenFolderArrow(default_params, sFgColor); + } + LLView::draw(); +} + +bool LLConversationViewSession::handleMouseDown( S32 x, S32 y, MASK mask ) +{ + //Will try to select a child node and then itself (if a child was not selected) + bool result = LLFolderViewFolder::handleMouseDown(x, y, mask); + + //This node (conversation) was selected and a child (participant) was not + if(result && getRoot()) + { + if(getRoot()->getCurSelectedItem() == this) + { + LLConversationItem* item = dynamic_cast(getViewModelItem()); + LLUUID session_id = item? item->getUUID() : LLUUID(); + + LLFloaterIMContainer *im_container = LLFloaterReg::getTypedInstance("im_container"); + if (im_container->isConversationsPaneCollapsed() && im_container->getSelectedSession() == session_id) + { + im_container->collapseMessagesPane(!im_container->isMessagesPaneCollapsed()); + } + else + { + im_container->collapseMessagesPane(false); + } + } + selectConversationItem(); + } + + return result; +} + +bool LLConversationViewSession::handleMouseUp( S32 x, S32 y, MASK mask ) +{ + bool result = LLFolderViewFolder::handleMouseUp(x, y, mask); + + LLFloater* volume_floater = LLFloaterReg::findInstance("floater_voice_volume"); + LLFloater* chat_volume_floater = LLFloaterReg::findInstance("chat_voice"); + if (result + && getRoot() && (getRoot()->getCurSelectedItem() == this) + && !(volume_floater && volume_floater->isShown() && volume_floater->hasFocus()) + && !(chat_volume_floater && chat_volume_floater->isShown() && chat_volume_floater->hasFocus())) + { + LLConversationItem* item = dynamic_cast(getViewModelItem()); + LLUUID session_id = item? item->getUUID() : LLUUID(); + LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(session_id); + if(session_floater && !session_floater->hasFocus()) + { + session_floater->setFocus(true); + } + } + + return result; +} + +bool LLConversationViewSession::handleRightMouseDown( S32 x, S32 y, MASK mask ) +{ + bool result = LLFolderViewFolder::handleRightMouseDown(x, y, mask); + + if(result) + { + selectConversationItem(); + } + + return result; +} + +void LLConversationViewSession::selectConversationItem() +{ + if(getRoot()->getCurSelectedItem() == this) + { + LLConversationItem* item = dynamic_cast(getViewModelItem()); + LLUUID session_id = item? item->getUUID() : LLUUID(); + + LLFloaterIMContainer *im_container = LLFloaterReg::getTypedInstance("im_container"); + im_container->flashConversationItemWidget(session_id,false); + im_container->selectConversationPair(session_id, false); + } +} + +// virtual +S32 LLConversationViewSession::arrange(S32* width, S32* height) +{ + //LLFolderViewFolder::arrange computes value for getIndentation() function below + S32 arranged = LLFolderViewFolder::arrange(width, height); + + S32 h_pad = mHasArrow ? getIndentation() + mArrowSize : getIndentation(); + + LLRect rect(mCollapsedMode ? getLocalRect().mLeft : h_pad, + getLocalRect().mTop, + getLocalRect().mRight, + getLocalRect().mTop - getItemHeight()); + mItemPanel->setShape(rect); + + return arranged; +} + +// virtual +void LLConversationViewSession::toggleOpen() +{ + // conversations should not be opened while in minimized mode + if (!mCollapsedMode) + { + LLFolderViewFolder::toggleOpen(); + + // do item's selection when opened + if (LLFolderViewFolder::isOpen()) + { + getParentFolder()->setSelection(this, true); + } + mContainer->reSelectConversation(); + } +} + +void LLConversationViewSession::toggleCollapsedMode(bool is_collapsed) +{ + mCollapsedMode = is_collapsed; + + // hide the layout stack which contains all item's child widgets + // except for the icon which we display in minimized mode + getChild("conversation_item_stack")->setVisible(!mCollapsedMode); + + S32 h_pad = mHasArrow ? getIndentation() + mArrowSize : getIndentation(); + + mItemPanel->translate(mCollapsedMode ? -h_pad : h_pad, 0); +} + +void LLConversationViewSession::setVisibleIfDetached(bool visible) +{ + // Do this only if the conversation floater has been torn off (i.e. no multi floater host) and is not minimized + // Note: minimized dockable floaters are brought to front hence unminimized when made visible and we don't want that here + LLFloater* session_floater = getSessionFloater(); + if (session_floater && session_floater->isDetachedAndNotMinimized()) + { + session_floater->setVisible(visible); + } +} + +LLFloater* LLConversationViewSession::getSessionFloater() +{ + LLFolderViewModelItem* item = mViewModelItem; + LLUUID session_uuid = dynamic_cast(item)->getUUID(); + return LLFloaterIMSessionTab::getConversation(session_uuid); +} + +LLConversationViewParticipant* LLConversationViewSession::findParticipant(const LLUUID& participant_id) +{ + // This is *not* a general tree parsing algorithm. We search only in the mItems list + // assuming there is no mFolders which makes sense for sessions (sessions don't contain + // sessions). + LLConversationViewParticipant* participant = NULL; + items_t::const_iterator iter; + for (iter = getItemsBegin(); iter != getItemsEnd(); iter++) + { + participant = dynamic_cast(*iter); + if (participant->hasSameValue(participant_id)) + { + break; + } + } + return (iter == getItemsEnd() ? NULL : participant); +} + +void LLConversationViewSession::showVoiceIndicator(bool visible) +{ + mCallIconLayoutPanel->setVisible(visible && LLVoiceChannel::getCurrentVoiceChannel()->getSessionID().isNull()); + requestArrange(); +} + +void LLConversationViewSession::refresh() +{ + // Refresh the session view from its model data + LLConversationItem* vmi = dynamic_cast(getViewModelItem()); + if (vmi) + { + vmi->resetRefresh(); + + if (mSessionTitle) + { + if (!highlightFriendTitle(vmi)) + { + LLStyle::Params title_style; + title_style.color = LLUIColorTable::instance().getColor("LabelTextColor"); + mSessionTitle->setText(vmi->getDisplayName(), title_style); + } + } + } + + // Update all speaking indicators + LLSpeakingIndicatorManager::updateSpeakingIndicators(); + + // we should show indicator for specified voice session only if this is current channel. EXT-5562. + if (mSpeakingIndicator) + { + mSpeakingIndicator->setIsActiveChannel(mIsInActiveVoiceChannel); + mSpeakingIndicator->setShowParticipantsSpeaking(mIsInActiveVoiceChannel); + } + + LLConversationViewParticipant* participant = NULL; + items_t::const_iterator iter; + for (iter = getItemsBegin(); iter != getItemsEnd(); iter++) + { + participant = dynamic_cast(*iter); + if (participant) + { + participant->allowSpeakingIndicator(mIsInActiveVoiceChannel); + } + } + + requestArrange(); + if (vmi) + { + // Do the regular upstream refresh + LLFolderViewFolder::refresh(); + } +} + +void LLConversationViewSession::onCurrentVoiceSessionChanged(const LLUUID& session_id) +{ + LLConversationItem* vmi = dynamic_cast(getViewModelItem()); + + if (vmi) + { + bool old_value = mIsInActiveVoiceChannel; + mIsInActiveVoiceChannel = vmi->getUUID() == session_id; + mCallIconLayoutPanel->setVisible(mIsInActiveVoiceChannel); + if (old_value != mIsInActiveVoiceChannel) + { + refresh(); + } + } +} + +bool LLConversationViewSession::highlightFriendTitle(LLConversationItem* vmi) +{ + if(vmi->getType() == LLConversationItem::CONV_PARTICIPANT || vmi->getType() == LLConversationItem::CONV_SESSION_1_ON_1) + { + LLIMModel::LLIMSession* session= LLIMModel::instance().findIMSession(vmi->getUUID()); + if (session && LLAvatarActions::isFriend(session->mOtherParticipantID)) + { + LLStyle::Params title_style; + title_style.color = LLUIColorTable::instance().getColor("ConversationFriendColor"); + mSessionTitle->setText(vmi->getDisplayName(), title_style); + return true; + } + } + return false; +} + +// +// Implementation of conversations list participant (avatar) widgets +// + +static LLDefaultChildRegistry::Register r("conversation_view_participant"); +bool LLConversationViewParticipant::sStaticInitialized = false; +S32 LLConversationViewParticipant::sChildrenWidths[LLConversationViewParticipant::ALIC_COUNT]; + +LLConversationViewParticipant::Params::Params() : +container(), +participant_id(), +avatar_icon("avatar_icon"), +info_button("info_button"), +output_monitor("output_monitor") +{} + +LLConversationViewParticipant::LLConversationViewParticipant( const LLConversationViewParticipant::Params& p ): + LLFolderViewItem(p), + mAvatarIcon(NULL), + mInfoBtn(NULL), + mSpeakingIndicator(NULL), + mUUID(p.participant_id) +{ +} + +LLConversationViewParticipant::~LLConversationViewParticipant() +{ + mActiveVoiceChannelConnection.disconnect(); +} + +void LLConversationViewParticipant::initFromParams(const LLConversationViewParticipant::Params& params) +{ + LLAvatarIconCtrl::Params avatar_icon_params(params.avatar_icon()); + applyXUILayout(avatar_icon_params, this); + LLAvatarIconCtrl * avatarIcon = LLUICtrlFactory::create(avatar_icon_params); + addChild(avatarIcon); + + LLButton::Params info_button_params(params.info_button()); + applyXUILayout(info_button_params, this); + LLButton * button = LLUICtrlFactory::create(info_button_params); + addChild(button); + + LLOutputMonitorCtrl::Params output_monitor_params(params.output_monitor()); + applyXUILayout(output_monitor_params, this); + LLOutputMonitorCtrl * outputMonitor = LLUICtrlFactory::create(output_monitor_params); + addChild(outputMonitor); +} + +bool LLConversationViewParticipant::postBuild() +{ + mAvatarIcon = getChild("avatar_icon"); + + mInfoBtn = getChild("info_btn"); + mInfoBtn->setClickedCallback(boost::bind(&LLConversationViewParticipant::onInfoBtnClick, this)); + mInfoBtn->setVisible(false); + + mSpeakingIndicator = getChild("speaking_indicator"); + + if (!sStaticInitialized) + { + // Remember children widths including their padding from the next sibling, + // so that we can hide and show them again later. + initChildrenWidths(this); + sStaticInitialized = true; + } + + updateChildren(); + if (getViewModelItem()) + { + LLFolderViewItem::postBuild(); + refresh(); + } + return true; +} + +void LLConversationViewParticipant::draw() +{ + static LLUIColor sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE); + static LLUIColor sFgDisabledColor = LLUIColorTable::instance().getColor("MenuItemDisabledColor", DEFAULT_WHITE); + static LLUIColor sHighlightFgColor = LLUIColorTable::instance().getColor("MenuItemHighlightFgColor", DEFAULT_WHITE); + static LLUIColor sHighlightBgColor = LLUIColorTable::instance().getColor("MenuItemHighlightBgColor", DEFAULT_WHITE); + static LLUIColor sFlashBgColor = LLUIColorTable::instance().getColor("MenuItemFlashBgColor", DEFAULT_WHITE); + static LLUIColor sFocusOutlineColor = LLUIColorTable::instance().getColor("InventoryFocusOutlineColor", DEFAULT_WHITE); + static LLUIColor sMouseOverColor = LLUIColorTable::instance().getColor("InventoryMouseOverColor", DEFAULT_WHITE); + + const bool show_context = (getRoot() ? getRoot()->getShowSelectionContext() : false); + + const LLFontGL* font = getLabelFontForStyle(mLabelStyle); + F32 right_x = 0; + + F32 y = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad; + F32 text_left = (F32)getLabelXPos(); + + LLColor4 color; + + LLLocalSpeakerMgr *speakerMgr = LLLocalSpeakerMgr::getInstance(); + + if (speakerMgr && speakerMgr->isSpeakerToBeRemoved(mUUID)) + { + color = sFgDisabledColor; + } + else + { + if (LLAvatarActions::isFriend(mUUID)) + { + color = LLUIColorTable::instance().getColor("ConversationFriendColor"); + } + else + { + color = mIsSelected ? sHighlightFgColor : sFgColor; + } + } + + LLConversationItemParticipant* participant_model = dynamic_cast(getViewModelItem()); + if (participant_model) + { + mSpeakingIndicator->setIsModeratorMuted(participant_model->isModeratorMuted()); + } + + drawHighlight(show_context, mIsSelected, sHighlightBgColor, sFlashBgColor, sFocusOutlineColor, sMouseOverColor); + drawLabel(font, text_left, y, color, right_x); + + LLView::draw(); +} + +// virtual +S32 LLConversationViewParticipant::arrange(S32* width, S32* height) +{ + //Need to call arrange first since it computes value used in getIndentation() + S32 arranged = LLFolderViewItem::arrange(width, height); + + //Adjusts the avatar icon based upon the indentation + LLRect avatarRect(getIndentation(), + mAvatarIcon->getRect().mTop, + getIndentation() + mAvatarIcon->getRect().getWidth(), + mAvatarIcon->getRect().mBottom); + mAvatarIcon->setShape(avatarRect); + + //Since dimensions changed, adjust the children (info button, speaker indicator) + updateChildren(); + + return arranged; +} + +// virtual +void LLConversationViewParticipant::refresh() +{ + // Refresh the participant view from its model data + LLConversationItemParticipant* participant_model = dynamic_cast(getViewModelItem()); + if (participant_model) + { + participant_model->resetRefresh(); + + // *TODO: We should also do something with vmi->isModerator() to echo that state in the UI somewhat + mSpeakingIndicator->setIsModeratorMuted(participant_model->isModeratorMuted()); + + // Do the regular upstream refresh + LLFolderViewItem::refresh(); + } +} + +void LLConversationViewParticipant::addToFolder(LLFolderViewFolder* folder) +{ + // Add the item to the folder (conversation) + LLFolderViewItem::addToFolder(folder); + + // Retrieve the folder (conversation) UUID, which is also the speaker session UUID + LLFolderViewFolder *prnt = getParentFolder(); + if (prnt) + { + LLConversationItem* vmi = dynamic_cast(prnt->getViewModelItem()); + if (vmi) + { + addToSession(vmi->getUUID()); + } + LLConversationViewSession* session = dynamic_cast(prnt); + if (session) + { + allowSpeakingIndicator(session->isInActiveVoiceChannel()); + } + } +} + +void LLConversationViewParticipant::addToSession(const LLUUID& session_id) +{ + //Allows speaking icon image to be loaded based on mUUID + mAvatarIcon->setValue(mUUID); + + //Allows the speaker indicator to be activated based on the user and conversation + mSpeakingIndicator->setSpeakerId(mUUID, session_id); +} + +void LLConversationViewParticipant::onInfoBtnClick() +{ + LLFloaterReg::showInstance("inspect_avatar", LLSD().with("avatar_id", mUUID)); +} + +bool LLConversationViewParticipant::handleMouseDown( S32 x, S32 y, MASK mask ) +{ + bool result = LLFolderViewItem::handleMouseDown(x, y, mask); + + if(result && getRoot()) + { + if(getRoot()->getCurSelectedItem() == this) + { + LLConversationItem* vmi = getParentFolder() ? dynamic_cast(getParentFolder()->getViewModelItem()) : NULL; + LLUUID session_id = vmi? vmi->getUUID() : LLUUID(); + + LLFloaterIMContainer *im_container = LLFloaterReg::getTypedInstance("im_container"); + LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(session_id); + im_container->setSelectedSession(session_id); + im_container->flashConversationItemWidget(session_id,false); + im_container->selectFloater(session_floater); + im_container->collapseMessagesPane(false); + } + } + return result; +} + +void LLConversationViewParticipant::onMouseEnter(S32 x, S32 y, MASK mask) +{ + mInfoBtn->setVisible(true); + updateChildren(); + LLFolderViewItem::onMouseEnter(x, y, mask); +} + +void LLConversationViewParticipant::onMouseLeave(S32 x, S32 y, MASK mask) +{ + mInfoBtn->setVisible(false); + updateChildren(); + LLFolderViewItem::onMouseLeave(x, y, mask); +} + +S32 LLConversationViewParticipant::getLabelXPos() +{ + return getIndentation() + mAvatarIcon->getRect().getWidth() + mIconPad; +} + +// static +void LLConversationViewParticipant::initChildrenWidths(LLConversationViewParticipant* self) +{ + //speaking indicator width + padding + S32 speaking_indicator_width = self->getRect().getWidth() - self->mSpeakingIndicator->getRect().mLeft; + + //info btn width + padding + S32 info_btn_width = self->mSpeakingIndicator->getRect().mLeft - self->mInfoBtn->getRect().mLeft; + + S32 index = ALIC_COUNT; + sChildrenWidths[--index] = info_btn_width; + sChildrenWidths[--index] = speaking_indicator_width; + llassert(index == 0); +} + +void LLConversationViewParticipant::updateChildren() +{ + mLabelPaddingRight = DEFAULT_LABEL_PADDING_RIGHT; + LLView* control; + S32 ctrl_width; + LLRect controlRect; + + //Cycles through controls starting from right to left + for (S32 i = 0; i < ALIC_COUNT; ++i) + { + control = getItemChildView((EAvatarListItemChildIndex)i); + + // skip invisible views + if (!control->getVisible()) continue; + + //Get current pos/dimensions + controlRect = control->getRect(); + + ctrl_width = sChildrenWidths[i]; // including space between current & left controls + // accumulate the amount of space taken by the controls + mLabelPaddingRight += ctrl_width; + + //Reposition visible controls in case adjacent controls to the right are hidden. + controlRect.setLeftTopAndSize( + getLocalRect().getWidth() - mLabelPaddingRight, + controlRect.mTop, + controlRect.getWidth(), + controlRect.getHeight()); + + //Sets the new position + control->setShape(controlRect); + } +} + +LLView* LLConversationViewParticipant::getItemChildView(EAvatarListItemChildIndex child_view_index) +{ + LLView* child_view = NULL; + + switch (child_view_index) + { + case ALIC_SPEAKER_INDICATOR: + child_view = mSpeakingIndicator; + break; + case ALIC_INFO_BUTTON: + child_view = mInfoBtn; + break; + default: + LL_WARNS("AvatarItemReshape") << "Unexpected child view index is passed: " << child_view_index << LL_ENDL; + llassert(0); + break; + // leave child_view untouched + } + + return child_view; +} + +void LLConversationViewParticipant::allowSpeakingIndicator(bool val) +{ + mSpeakingIndicator->setIsActiveChannel(val); +} + +// EOF + diff --git a/indra/newview/llconversationview.h b/indra/newview/llconversationview.h index fe980091fb..8eb6392121 100644 --- a/indra/newview/llconversationview.h +++ b/indra/newview/llconversationview.h @@ -1,189 +1,189 @@ -/** - * @file llconversationview.h - * @brief Implementation of conversations list widgets and views - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLCONVERSATIONVIEW_H -#define LL_LLCONVERSATIONVIEW_H - -#include "../llui/llfolderviewitem.h" - -#include "llavatariconctrl.h" -#include "../llui/llbutton.h" -#include "lloutputmonitorctrl.h" - -class LLTextBox; -class LLFloater; -class LLFloaterIMContainer; -class LLConversationItem; -class LLConversationViewSession; -class LLConversationViewParticipant; - -class LLVoiceClientStatusObserver; - -// Implementation of conversations list session widgets - -class LLConversationViewSession : public LLFolderViewFolder -{ -public: - struct Params : public LLInitParam::Block - { - Optional container; - - Params(); - }; - -protected: - friend class LLUICtrlFactory; - LLConversationViewSession( const Params& p ); - - /*virtual*/ bool isHighlightAllowed(); - /*virtual*/ bool isHighlightActive(); - /*virtual*/ bool isFlashing() { return mFlashStateOn; } - - LLFloaterIMContainer* mContainer; - -public: - virtual ~LLConversationViewSession(); - - /*virtual*/ void destroyView(); - - /*virtual*/ bool postBuild(); - /*virtual*/ void draw(); - /*virtual*/ bool handleMouseDown( S32 x, S32 y, MASK mask ); - /*virtual*/ bool handleRightMouseDown( S32 x, S32 y, MASK mask ); - /*virtual*/ bool handleMouseUp( S32 x, S32 y, MASK mask ); - - /*virtual*/ S32 arrange(S32* width, S32* height); - - /*virtual*/ void toggleOpen(); - - /*virtual*/ bool isCollapsed() { return mCollapsedMode; } - - void toggleCollapsedMode(bool is_collapsed); - - void setVisibleIfDetached(bool visible); - LLConversationViewParticipant* findParticipant(const LLUUID& participant_id); - - void showVoiceIndicator(bool visible); - - virtual void refresh(); - - /*virtual*/ void setFlashState(bool flash_state); - void setHighlightState(bool hihglight_state); - - LLFloater* getSessionFloater(); - bool isInActiveVoiceChannel() { return mIsInActiveVoiceChannel; } - - bool highlightFriendTitle(LLConversationItem* vmi); - -private: - - void onCurrentVoiceSessionChanged(const LLUUID& session_id); - void startFlashing(); - void selectConversationItem(); - - LLPanel* mItemPanel; - LLPanel* mCallIconLayoutPanel; - LLTextBox* mSessionTitle; - LLOutputMonitorCtrl* mSpeakingIndicator; - LLFlashTimer* mFlashTimer; - bool mFlashStateOn; - bool mFlashStarted; - - bool mCollapsedMode; - bool mHasArrow; - - bool mIsInActiveVoiceChannel; - - LLVoiceClientStatusObserver* mVoiceClientObserver; - - boost::signals2::connection mActiveVoiceChannelConnection; -}; - -// Implementation of conversations list participant (avatar) widgets - -class LLConversationViewParticipant : public LLFolderViewItem -{ - -public: - - struct Params : public LLInitParam::Block - { - Optional container; - Optional participant_id; - Optional avatar_icon; - Optional info_button; - Optional output_monitor; - - Params(); - }; - - virtual ~LLConversationViewParticipant( void ); - - bool hasSameValue(const LLUUID& uuid) { return (uuid == mUUID); } - /*virtual*/ void refresh(); - void addToFolder(LLFolderViewFolder* folder); - void addToSession(const LLUUID& session_id); - - void onMouseEnter(S32 x, S32 y, MASK mask); - void onMouseLeave(S32 x, S32 y, MASK mask); - - /*virtual*/ S32 getLabelXPos(); - /*virtual*/ bool handleMouseDown( S32 x, S32 y, MASK mask ); - void allowSpeakingIndicator(bool val); - -protected: - friend class LLUICtrlFactory; - LLConversationViewParticipant( const Params& p ); - void initFromParams(const Params& params); - bool postBuild(); - /*virtual*/ void draw(); - /*virtual*/ S32 arrange(S32* width, S32* height); - - void onInfoBtnClick(); - -private: - - LLAvatarIconCtrl* mAvatarIcon; - LLButton * mInfoBtn; - LLOutputMonitorCtrl* mSpeakingIndicator; - LLUUID mUUID; // UUID of the participant - - typedef enum e_avatar_item_child { - ALIC_SPEAKER_INDICATOR, - ALIC_INFO_BUTTON, - ALIC_COUNT, - } EAvatarListItemChildIndex; - - static bool sStaticInitialized; // this variable is introduced to improve code readability - static S32 sChildrenWidths[ALIC_COUNT]; - static void initChildrenWidths(LLConversationViewParticipant* self); - void updateChildren(); - LLView* getItemChildView(EAvatarListItemChildIndex child_view_index); - - boost::signals2::connection mActiveVoiceChannelConnection; -}; - -#endif // LL_LLCONVERSATIONVIEW_H +/** + * @file llconversationview.h + * @brief Implementation of conversations list widgets and views + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLCONVERSATIONVIEW_H +#define LL_LLCONVERSATIONVIEW_H + +#include "../llui/llfolderviewitem.h" + +#include "llavatariconctrl.h" +#include "../llui/llbutton.h" +#include "lloutputmonitorctrl.h" + +class LLTextBox; +class LLFloater; +class LLFloaterIMContainer; +class LLConversationItem; +class LLConversationViewSession; +class LLConversationViewParticipant; + +class LLVoiceClientStatusObserver; + +// Implementation of conversations list session widgets + +class LLConversationViewSession : public LLFolderViewFolder +{ +public: + struct Params : public LLInitParam::Block + { + Optional container; + + Params(); + }; + +protected: + friend class LLUICtrlFactory; + LLConversationViewSession( const Params& p ); + + /*virtual*/ bool isHighlightAllowed(); + /*virtual*/ bool isHighlightActive(); + /*virtual*/ bool isFlashing() { return mFlashStateOn; } + + LLFloaterIMContainer* mContainer; + +public: + virtual ~LLConversationViewSession(); + + /*virtual*/ void destroyView(); + + /*virtual*/ bool postBuild(); + /*virtual*/ void draw(); + /*virtual*/ bool handleMouseDown( S32 x, S32 y, MASK mask ); + /*virtual*/ bool handleRightMouseDown( S32 x, S32 y, MASK mask ); + /*virtual*/ bool handleMouseUp( S32 x, S32 y, MASK mask ); + + /*virtual*/ S32 arrange(S32* width, S32* height); + + /*virtual*/ void toggleOpen(); + + /*virtual*/ bool isCollapsed() { return mCollapsedMode; } + + void toggleCollapsedMode(bool is_collapsed); + + void setVisibleIfDetached(bool visible); + LLConversationViewParticipant* findParticipant(const LLUUID& participant_id); + + void showVoiceIndicator(bool visible); + + virtual void refresh(); + + /*virtual*/ void setFlashState(bool flash_state); + void setHighlightState(bool hihglight_state); + + LLFloater* getSessionFloater(); + bool isInActiveVoiceChannel() { return mIsInActiveVoiceChannel; } + + bool highlightFriendTitle(LLConversationItem* vmi); + +private: + + void onCurrentVoiceSessionChanged(const LLUUID& session_id); + void startFlashing(); + void selectConversationItem(); + + LLPanel* mItemPanel; + LLPanel* mCallIconLayoutPanel; + LLTextBox* mSessionTitle; + LLOutputMonitorCtrl* mSpeakingIndicator; + LLFlashTimer* mFlashTimer; + bool mFlashStateOn; + bool mFlashStarted; + + bool mCollapsedMode; + bool mHasArrow; + + bool mIsInActiveVoiceChannel; + + LLVoiceClientStatusObserver* mVoiceClientObserver; + + boost::signals2::connection mActiveVoiceChannelConnection; +}; + +// Implementation of conversations list participant (avatar) widgets + +class LLConversationViewParticipant : public LLFolderViewItem +{ + +public: + + struct Params : public LLInitParam::Block + { + Optional container; + Optional participant_id; + Optional avatar_icon; + Optional info_button; + Optional output_monitor; + + Params(); + }; + + virtual ~LLConversationViewParticipant( void ); + + bool hasSameValue(const LLUUID& uuid) { return (uuid == mUUID); } + /*virtual*/ void refresh(); + void addToFolder(LLFolderViewFolder* folder); + void addToSession(const LLUUID& session_id); + + void onMouseEnter(S32 x, S32 y, MASK mask); + void onMouseLeave(S32 x, S32 y, MASK mask); + + /*virtual*/ S32 getLabelXPos(); + /*virtual*/ bool handleMouseDown( S32 x, S32 y, MASK mask ); + void allowSpeakingIndicator(bool val); + +protected: + friend class LLUICtrlFactory; + LLConversationViewParticipant( const Params& p ); + void initFromParams(const Params& params); + bool postBuild(); + /*virtual*/ void draw(); + /*virtual*/ S32 arrange(S32* width, S32* height); + + void onInfoBtnClick(); + +private: + + LLAvatarIconCtrl* mAvatarIcon; + LLButton * mInfoBtn; + LLOutputMonitorCtrl* mSpeakingIndicator; + LLUUID mUUID; // UUID of the participant + + typedef enum e_avatar_item_child { + ALIC_SPEAKER_INDICATOR, + ALIC_INFO_BUTTON, + ALIC_COUNT, + } EAvatarListItemChildIndex; + + static bool sStaticInitialized; // this variable is introduced to improve code readability + static S32 sChildrenWidths[ALIC_COUNT]; + static void initChildrenWidths(LLConversationViewParticipant* self); + void updateChildren(); + LLView* getItemChildView(EAvatarListItemChildIndex child_view_index); + + boost::signals2::connection mActiveVoiceChannelConnection; +}; + +#endif // LL_LLCONVERSATIONVIEW_H diff --git a/indra/newview/llcurrencyuimanager.cpp b/indra/newview/llcurrencyuimanager.cpp index 60a821fdca..06c87343e2 100644 --- a/indra/newview/llcurrencyuimanager.cpp +++ b/indra/newview/llcurrencyuimanager.cpp @@ -1,633 +1,633 @@ -/** - * @file llcurrencyuimanager.cpp - * @brief LLCurrencyUIManager class implementation - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lluictrlfactory.h" -#include "lltextbox.h" -#include "lllineeditor.h" -#include "llresmgr.h" // for LLLocale -#include "lltrans.h" -#include "llviewercontrol.h" -#include "llversioninfo.h" - -#include "llcurrencyuimanager.h" - -// viewer includes -#include "llagent.h" -#include "llconfirmationmanager.h" -#include "llframetimer.h" -#include "lllineeditor.h" -#include "llviewchildren.h" -#include "llxmlrpctransaction.h" -#include "llviewernetwork.h" -#include "llpanel.h" -#include "stringize.h" - - -const F64 CURRENCY_ESTIMATE_FREQUENCY = 2.0; - // how long of a pause in typing a currency buy amount before an - // esimate is fetched from the server - -class LLCurrencyUIManager::Impl -{ -public: - Impl(LLPanel& dialog); - virtual ~Impl(); - - LLPanel& mPanel; - - bool mHidden; - - bool mError; - std::string mErrorMessage; - std::string mErrorURI; - - std::string mZeroMessage; - - // user's choices - S32 mUserCurrencyBuy; - bool mUserEnteredCurrencyBuy; - - // from website - - // pre-viewer 2.0, the server returned estimates as an - // integer US cents value, e.g., "1000" for $10.00 - // post-viewer 2.0, the server may also return estimates - // as a string with currency embedded, e.g., "10.00 Euros" - bool mUSDCurrencyEstimated; - S32 mUSDCurrencyEstimatedCost; - bool mLocalCurrencyEstimated; - std::string mLocalCurrencyEstimatedCost; - bool mSupportsInternationalBilling; - std::string mSiteConfirm; - - bool mBought; - - enum TransactionType - { - TransactionNone, - TransactionCurrency, - TransactionBuy - }; - - TransactionType mTransactionType; - LLXMLRPCTransaction* mTransaction; - - bool mCurrencyChanged; - LLFrameTimer mCurrencyKeyTimer; - - - void updateCurrencyInfo(); - void finishCurrencyInfo(); - - void startCurrencyBuy(const std::string& password); - void finishCurrencyBuy(); - - void clearEstimate(); - bool hasEstimate() const; - std::string getLocalEstimate() const; - - void startTransaction(TransactionType type, - const char* method, LLXMLRPCValue params); - bool checkTransaction(); - // return true if update needed - - void setError(const std::string& message, const std::string& uri); - void clearError(); - - bool considerUpdateCurrency(); - // return true if update needed - void currencyKey(S32); - static void onCurrencyKey(LLLineEditor* caller, void* data); - - void prepare(); - void updateUI(); -}; - -// is potentially not fully constructed. -LLCurrencyUIManager::Impl::Impl(LLPanel& dialog) - : mPanel(dialog), - mHidden(false), - mError(false), - mUserCurrencyBuy(2000), // note, this is a default, real value set in llfloaterbuycurrency.cpp - mUserEnteredCurrencyBuy(false), - mSupportsInternationalBilling(false), - mBought(false), - mTransactionType(TransactionNone), mTransaction(0), - mCurrencyChanged(false) -{ - clearEstimate(); -} - -LLCurrencyUIManager::Impl::~Impl() -{ - delete mTransaction; -} - -void LLCurrencyUIManager::Impl::updateCurrencyInfo() -{ - clearEstimate(); - mBought = false; - mCurrencyChanged = false; - - if (mUserCurrencyBuy == 0) - { - mLocalCurrencyEstimated = true; - return; - } - - LLXMLRPCValue keywordArgs = LLXMLRPCValue::createStruct(); - keywordArgs.appendString("agentId", gAgent.getID().asString()); - keywordArgs.appendString( - "secureSessionId", - gAgent.getSecureSessionID().asString()); - keywordArgs.appendString("language", LLUI::getLanguage()); - keywordArgs.appendInt("currencyBuy", mUserCurrencyBuy); - keywordArgs.appendString("viewerChannel", LLVersionInfo::instance().getChannel()); - keywordArgs.appendInt("viewerMajorVersion", LLVersionInfo::instance().getMajor()); - keywordArgs.appendInt("viewerMinorVersion", LLVersionInfo::instance().getMinor()); - keywordArgs.appendInt("viewerPatchVersion", LLVersionInfo::instance().getPatch()); - // With GitHub builds, the build number is too big to fit in a 32-bit int, - // and XMLRPC_VALUE doesn't deal with integers wider than int. Use string. - keywordArgs.appendString("viewerBuildVersion", stringize(LLVersionInfo::instance().getBuild())); - - LLXMLRPCValue params = LLXMLRPCValue::createArray(); - params.append(keywordArgs); - - startTransaction(TransactionCurrency, "getCurrencyQuote", params); -} - -void LLCurrencyUIManager::Impl::finishCurrencyInfo() -{ - LLXMLRPCValue result = mTransaction->responseValue(); - - bool success = result["success"].asBool(); - if (!success) - { - setError( - result["errorMessage"].asString(), - result["errorURI"].asString() - ); - return; - } - - LLXMLRPCValue currency = result["currency"]; - - // old XML-RPC server: estimatedCost = value in US cents - mUSDCurrencyEstimated = currency["estimatedCost"].isValid(); - if (mUSDCurrencyEstimated) - { - mUSDCurrencyEstimatedCost = currency["estimatedCost"].asInt(); - } - - // newer XML-RPC server: estimatedLocalCost = local currency string - mLocalCurrencyEstimated = currency["estimatedLocalCost"].isValid(); - if (mLocalCurrencyEstimated) - { - mLocalCurrencyEstimatedCost = currency["estimatedLocalCost"].asString(); - mSupportsInternationalBilling = true; - } - - S32 newCurrencyBuy = currency["currencyBuy"].asInt(); - if (newCurrencyBuy != mUserCurrencyBuy) - { - mUserCurrencyBuy = newCurrencyBuy; - mUserEnteredCurrencyBuy = false; - } - - mSiteConfirm = result["confirm"].asString(); -} - -void LLCurrencyUIManager::Impl::startCurrencyBuy(const std::string& password) -{ - LLXMLRPCValue keywordArgs = LLXMLRPCValue::createStruct(); - keywordArgs.appendString("agentId", gAgent.getID().asString()); - keywordArgs.appendString( - "secureSessionId", - gAgent.getSecureSessionID().asString()); - keywordArgs.appendString("language", LLUI::getLanguage()); - keywordArgs.appendInt("currencyBuy", mUserCurrencyBuy); - if (mUSDCurrencyEstimated) - { - keywordArgs.appendInt("estimatedCost", mUSDCurrencyEstimatedCost); - } - if (mLocalCurrencyEstimated) - { - keywordArgs.appendString("estimatedLocalCost", mLocalCurrencyEstimatedCost); - } - keywordArgs.appendString("confirm", mSiteConfirm); - if (!password.empty()) - { - keywordArgs.appendString("password", password); - } - keywordArgs.appendString("viewerChannel", LLVersionInfo::instance().getChannel()); - keywordArgs.appendInt("viewerMajorVersion", LLVersionInfo::instance().getMajor()); - keywordArgs.appendInt("viewerMinorVersion", LLVersionInfo::instance().getMinor()); - keywordArgs.appendInt("viewerPatchVersion", LLVersionInfo::instance().getPatch()); - // With GitHub builds, the build number is too big to fit in a 32-bit int, - // and XMLRPC_VALUE doesn't deal with integers wider than int. Use string. - keywordArgs.appendString("viewerBuildVersion", stringize(LLVersionInfo::instance().getBuild())); - - LLXMLRPCValue params = LLXMLRPCValue::createArray(); - params.append(keywordArgs); - - startTransaction(TransactionBuy, "buyCurrency", params); - - clearEstimate(); - mCurrencyChanged = false; -} - -void LLCurrencyUIManager::Impl::finishCurrencyBuy() -{ - LLXMLRPCValue result = mTransaction->responseValue(); - - bool success = result["success"].asBool(); - if (!success) - { - setError( - result["errorMessage"].asString(), - result["errorURI"].asString() - ); - } - else - { - mUserCurrencyBuy = 0; - mUserEnteredCurrencyBuy = false; - mBought = true; - } -} - -void LLCurrencyUIManager::Impl::startTransaction(TransactionType type, - const char* method, LLXMLRPCValue params) -{ - static std::string transactionURI; - if (transactionURI.empty()) - { - transactionURI = LLGridManager::getInstance()->getHelperURI() + "currency.php"; - } - - delete mTransaction; - - mTransactionType = type; - mTransaction = new LLXMLRPCTransaction( - transactionURI, - method, - params, - false /* don't use gzip */ - ); - - clearError(); -} - -void LLCurrencyUIManager::Impl::clearEstimate() -{ - mUSDCurrencyEstimated = false; - mUSDCurrencyEstimatedCost = 0; - mLocalCurrencyEstimated = false; - mLocalCurrencyEstimatedCost = "0"; -} - -bool LLCurrencyUIManager::Impl::hasEstimate() const -{ - return (mUSDCurrencyEstimated || mLocalCurrencyEstimated); -} - -std::string LLCurrencyUIManager::Impl::getLocalEstimate() const -{ - if (mLocalCurrencyEstimated) - { - // we have the new-style local currency string - return mLocalCurrencyEstimatedCost; - } - if (mUSDCurrencyEstimated) - { - // we have the old-style USD-specific value - LLStringUtil::format_map_t args; - { - LLLocale locale_override(LLStringUtil::getLocale()); - args["[AMOUNT]"] = llformat("%#.2f", mUSDCurrencyEstimatedCost / 100.0); - } - return LLTrans::getString("LocalEstimateUSD", args); - } - return ""; -} - -bool LLCurrencyUIManager::Impl::checkTransaction() -{ - if (!mTransaction) - { - return false; - } - - if (!mTransaction->process()) - { - return false; - } - - if (mTransaction->status(NULL) != LLXMLRPCTransaction::StatusComplete) - { - setError(mTransaction->statusMessage(), mTransaction->statusURI()); - } - else { - switch (mTransactionType) - { - case TransactionCurrency: finishCurrencyInfo(); break; - case TransactionBuy: finishCurrencyBuy(); break; - default: ; - } - } - - delete mTransaction; - mTransaction = NULL; - mTransactionType = TransactionNone; - - return true; -} - -void LLCurrencyUIManager::Impl::setError( - const std::string& message, const std::string& uri) -{ - mError = true; - mErrorMessage = message; - mErrorURI = uri; -} - -void LLCurrencyUIManager::Impl::clearError() -{ - mError = false; - mErrorMessage.clear(); - mErrorURI.clear(); -} - -bool LLCurrencyUIManager::Impl::considerUpdateCurrency() -{ - if (mCurrencyChanged - && !mTransaction - && mCurrencyKeyTimer.getElapsedTimeF32() >= CURRENCY_ESTIMATE_FREQUENCY) - { - updateCurrencyInfo(); - return true; - } - - return false; -} - -void LLCurrencyUIManager::Impl::currencyKey(S32 value) -{ - mUserEnteredCurrencyBuy = true; - mCurrencyKeyTimer.reset(); - - if (mUserCurrencyBuy == value) - { - return; - } - - mUserCurrencyBuy = value; - - if (hasEstimate()) { - clearEstimate(); - //cannot just simply refresh the whole UI, as the edit field will - // get reset and the cursor will change... - - mPanel.getChildView("currency_est")->setVisible(false); - mPanel.getChildView("getting_data")->setVisible(true); - } - - mCurrencyChanged = true; -} - -// static -void LLCurrencyUIManager::Impl::onCurrencyKey( - LLLineEditor* caller, void* data) -{ - S32 value = atoi(caller->getText().c_str()); - LLCurrencyUIManager::Impl* self = (LLCurrencyUIManager::Impl*)data; - self->currencyKey(value); -} - -void LLCurrencyUIManager::Impl::prepare() -{ - LLLineEditor* lindenAmount = mPanel.getChild("currency_amt"); - if (lindenAmount) - { - lindenAmount->setPrevalidate(LLTextValidate::validateNonNegativeS32); - lindenAmount->setKeystrokeCallback(onCurrencyKey, this); - } -} - -void LLCurrencyUIManager::Impl::updateUI() -{ - if (mHidden) - { - mPanel.getChildView("currency_action")->setVisible(false); - mPanel.getChildView("currency_amt")->setVisible(false); - mPanel.getChildView("currency_est")->setVisible(false); - return; - } - - mPanel.getChildView("currency_action")->setVisible(true); - - LLLineEditor* lindenAmount = mPanel.getChild("currency_amt"); - if (lindenAmount) - { - lindenAmount->setVisible(true); - lindenAmount->setLabel(mZeroMessage); - - if (!mUserEnteredCurrencyBuy) - { - if (mUserCurrencyBuy == 0) - { - lindenAmount->setText(LLStringUtil::null); - } - else - { - lindenAmount->setText(llformat("%d", mUserCurrencyBuy)); - } - - lindenAmount->selectAll(); - } - } - - std::string estimated = (mUserCurrencyBuy == 0) ? mPanel.getString("estimated_zero") : getLocalEstimate(); - mPanel.getChild("currency_est")->setTextArg("[LOCALAMOUNT]", estimated); - mPanel.getChildView("currency_est")->setVisible( hasEstimate() || mUserCurrencyBuy == 0); - - mPanel.getChildView("currency_links")->setVisible( mSupportsInternationalBilling); - mPanel.getChildView("exchange_rate_note")->setVisible( mSupportsInternationalBilling); - - if (mPanel.getChildView("buy_btn")->getEnabled() - ||mPanel.getChildView("currency_est")->getVisible() - || mPanel.getChildView("error_web")->getVisible()) - { - mPanel.getChildView("getting_data")->setVisible(false); - } -} - - - -LLCurrencyUIManager::LLCurrencyUIManager(LLPanel& dialog) - : impl(* new Impl(dialog)) -{ -} - -LLCurrencyUIManager::~LLCurrencyUIManager() -{ - delete &impl; -} - -void LLCurrencyUIManager::setAmount(int amount, bool noEstimate) -{ - impl.mUserCurrencyBuy = amount; - impl.mUserEnteredCurrencyBuy = false; - impl.updateUI(); - - impl.mCurrencyChanged = !noEstimate; -} - -int LLCurrencyUIManager::getAmount() -{ - return impl.mUserCurrencyBuy; -} - -void LLCurrencyUIManager::setZeroMessage(const std::string& message) -{ - impl.mZeroMessage = message; -} - -void LLCurrencyUIManager::setUSDEstimate(int amount) -{ - impl.mUSDCurrencyEstimatedCost = amount; - impl.mUSDCurrencyEstimated = true; - impl.updateUI(); - - impl.mCurrencyChanged = false; -} - -int LLCurrencyUIManager::getUSDEstimate() -{ - return impl.mUSDCurrencyEstimated ? impl.mUSDCurrencyEstimatedCost : 0; -} - -void LLCurrencyUIManager::setLocalEstimate(const std::string &amount) -{ - impl.mLocalCurrencyEstimatedCost = amount; - impl.mLocalCurrencyEstimated = true; - impl.updateUI(); - - impl.mCurrencyChanged = false; -} - -std::string LLCurrencyUIManager::getLocalEstimate() const -{ - return impl.getLocalEstimate(); -} - -void LLCurrencyUIManager::prepare() -{ - impl.prepare(); -} - -void LLCurrencyUIManager::updateUI(bool show) -{ - impl.mHidden = !show; - impl.updateUI(); -} - -bool LLCurrencyUIManager::process() -{ - bool changed = false; - changed |= impl.checkTransaction(); - changed |= impl.considerUpdateCurrency(); - return changed; -} - -void LLCurrencyUIManager::buy(const std::string& buy_msg) -{ - if (!canBuy()) - { - return; - } - - LLUIString msg = buy_msg; - msg.setArg("[LINDENS]", llformat("%d", impl.mUserCurrencyBuy)); - msg.setArg("[LOCALAMOUNT]", getLocalEstimate()); - LLConfirmationManager::confirm(impl.mSiteConfirm, - msg, - impl, - &LLCurrencyUIManager::Impl::startCurrencyBuy); -} - - -bool LLCurrencyUIManager::inProcess() -{ - return impl.mTransactionType != Impl::TransactionNone; -} - -bool LLCurrencyUIManager::canCancel() -{ - return impl.mTransactionType != Impl::TransactionBuy; -} - -bool LLCurrencyUIManager::canBuy() -{ - return impl.mTransactionType == Impl::TransactionNone - && impl.hasEstimate() - && impl.mUserCurrencyBuy > 0; -} - -bool LLCurrencyUIManager::buying() -{ - return impl.mTransactionType == Impl::TransactionBuy; -} - -bool LLCurrencyUIManager::bought() -{ - return impl.mBought; -} - -void LLCurrencyUIManager::clearError() -{ - impl.clearError(); -} - -bool LLCurrencyUIManager::hasError() -{ - return impl.mError; -} - -std::string LLCurrencyUIManager::errorMessage() -{ - return impl.mErrorMessage; -} - -std::string LLCurrencyUIManager::errorURI() -{ - return impl.mErrorURI; -} - - - +/** + * @file llcurrencyuimanager.cpp + * @brief LLCurrencyUIManager class implementation + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lluictrlfactory.h" +#include "lltextbox.h" +#include "lllineeditor.h" +#include "llresmgr.h" // for LLLocale +#include "lltrans.h" +#include "llviewercontrol.h" +#include "llversioninfo.h" + +#include "llcurrencyuimanager.h" + +// viewer includes +#include "llagent.h" +#include "llconfirmationmanager.h" +#include "llframetimer.h" +#include "lllineeditor.h" +#include "llviewchildren.h" +#include "llxmlrpctransaction.h" +#include "llviewernetwork.h" +#include "llpanel.h" +#include "stringize.h" + + +const F64 CURRENCY_ESTIMATE_FREQUENCY = 2.0; + // how long of a pause in typing a currency buy amount before an + // esimate is fetched from the server + +class LLCurrencyUIManager::Impl +{ +public: + Impl(LLPanel& dialog); + virtual ~Impl(); + + LLPanel& mPanel; + + bool mHidden; + + bool mError; + std::string mErrorMessage; + std::string mErrorURI; + + std::string mZeroMessage; + + // user's choices + S32 mUserCurrencyBuy; + bool mUserEnteredCurrencyBuy; + + // from website + + // pre-viewer 2.0, the server returned estimates as an + // integer US cents value, e.g., "1000" for $10.00 + // post-viewer 2.0, the server may also return estimates + // as a string with currency embedded, e.g., "10.00 Euros" + bool mUSDCurrencyEstimated; + S32 mUSDCurrencyEstimatedCost; + bool mLocalCurrencyEstimated; + std::string mLocalCurrencyEstimatedCost; + bool mSupportsInternationalBilling; + std::string mSiteConfirm; + + bool mBought; + + enum TransactionType + { + TransactionNone, + TransactionCurrency, + TransactionBuy + }; + + TransactionType mTransactionType; + LLXMLRPCTransaction* mTransaction; + + bool mCurrencyChanged; + LLFrameTimer mCurrencyKeyTimer; + + + void updateCurrencyInfo(); + void finishCurrencyInfo(); + + void startCurrencyBuy(const std::string& password); + void finishCurrencyBuy(); + + void clearEstimate(); + bool hasEstimate() const; + std::string getLocalEstimate() const; + + void startTransaction(TransactionType type, + const char* method, LLXMLRPCValue params); + bool checkTransaction(); + // return true if update needed + + void setError(const std::string& message, const std::string& uri); + void clearError(); + + bool considerUpdateCurrency(); + // return true if update needed + void currencyKey(S32); + static void onCurrencyKey(LLLineEditor* caller, void* data); + + void prepare(); + void updateUI(); +}; + +// is potentially not fully constructed. +LLCurrencyUIManager::Impl::Impl(LLPanel& dialog) + : mPanel(dialog), + mHidden(false), + mError(false), + mUserCurrencyBuy(2000), // note, this is a default, real value set in llfloaterbuycurrency.cpp + mUserEnteredCurrencyBuy(false), + mSupportsInternationalBilling(false), + mBought(false), + mTransactionType(TransactionNone), mTransaction(0), + mCurrencyChanged(false) +{ + clearEstimate(); +} + +LLCurrencyUIManager::Impl::~Impl() +{ + delete mTransaction; +} + +void LLCurrencyUIManager::Impl::updateCurrencyInfo() +{ + clearEstimate(); + mBought = false; + mCurrencyChanged = false; + + if (mUserCurrencyBuy == 0) + { + mLocalCurrencyEstimated = true; + return; + } + + LLXMLRPCValue keywordArgs = LLXMLRPCValue::createStruct(); + keywordArgs.appendString("agentId", gAgent.getID().asString()); + keywordArgs.appendString( + "secureSessionId", + gAgent.getSecureSessionID().asString()); + keywordArgs.appendString("language", LLUI::getLanguage()); + keywordArgs.appendInt("currencyBuy", mUserCurrencyBuy); + keywordArgs.appendString("viewerChannel", LLVersionInfo::instance().getChannel()); + keywordArgs.appendInt("viewerMajorVersion", LLVersionInfo::instance().getMajor()); + keywordArgs.appendInt("viewerMinorVersion", LLVersionInfo::instance().getMinor()); + keywordArgs.appendInt("viewerPatchVersion", LLVersionInfo::instance().getPatch()); + // With GitHub builds, the build number is too big to fit in a 32-bit int, + // and XMLRPC_VALUE doesn't deal with integers wider than int. Use string. + keywordArgs.appendString("viewerBuildVersion", stringize(LLVersionInfo::instance().getBuild())); + + LLXMLRPCValue params = LLXMLRPCValue::createArray(); + params.append(keywordArgs); + + startTransaction(TransactionCurrency, "getCurrencyQuote", params); +} + +void LLCurrencyUIManager::Impl::finishCurrencyInfo() +{ + LLXMLRPCValue result = mTransaction->responseValue(); + + bool success = result["success"].asBool(); + if (!success) + { + setError( + result["errorMessage"].asString(), + result["errorURI"].asString() + ); + return; + } + + LLXMLRPCValue currency = result["currency"]; + + // old XML-RPC server: estimatedCost = value in US cents + mUSDCurrencyEstimated = currency["estimatedCost"].isValid(); + if (mUSDCurrencyEstimated) + { + mUSDCurrencyEstimatedCost = currency["estimatedCost"].asInt(); + } + + // newer XML-RPC server: estimatedLocalCost = local currency string + mLocalCurrencyEstimated = currency["estimatedLocalCost"].isValid(); + if (mLocalCurrencyEstimated) + { + mLocalCurrencyEstimatedCost = currency["estimatedLocalCost"].asString(); + mSupportsInternationalBilling = true; + } + + S32 newCurrencyBuy = currency["currencyBuy"].asInt(); + if (newCurrencyBuy != mUserCurrencyBuy) + { + mUserCurrencyBuy = newCurrencyBuy; + mUserEnteredCurrencyBuy = false; + } + + mSiteConfirm = result["confirm"].asString(); +} + +void LLCurrencyUIManager::Impl::startCurrencyBuy(const std::string& password) +{ + LLXMLRPCValue keywordArgs = LLXMLRPCValue::createStruct(); + keywordArgs.appendString("agentId", gAgent.getID().asString()); + keywordArgs.appendString( + "secureSessionId", + gAgent.getSecureSessionID().asString()); + keywordArgs.appendString("language", LLUI::getLanguage()); + keywordArgs.appendInt("currencyBuy", mUserCurrencyBuy); + if (mUSDCurrencyEstimated) + { + keywordArgs.appendInt("estimatedCost", mUSDCurrencyEstimatedCost); + } + if (mLocalCurrencyEstimated) + { + keywordArgs.appendString("estimatedLocalCost", mLocalCurrencyEstimatedCost); + } + keywordArgs.appendString("confirm", mSiteConfirm); + if (!password.empty()) + { + keywordArgs.appendString("password", password); + } + keywordArgs.appendString("viewerChannel", LLVersionInfo::instance().getChannel()); + keywordArgs.appendInt("viewerMajorVersion", LLVersionInfo::instance().getMajor()); + keywordArgs.appendInt("viewerMinorVersion", LLVersionInfo::instance().getMinor()); + keywordArgs.appendInt("viewerPatchVersion", LLVersionInfo::instance().getPatch()); + // With GitHub builds, the build number is too big to fit in a 32-bit int, + // and XMLRPC_VALUE doesn't deal with integers wider than int. Use string. + keywordArgs.appendString("viewerBuildVersion", stringize(LLVersionInfo::instance().getBuild())); + + LLXMLRPCValue params = LLXMLRPCValue::createArray(); + params.append(keywordArgs); + + startTransaction(TransactionBuy, "buyCurrency", params); + + clearEstimate(); + mCurrencyChanged = false; +} + +void LLCurrencyUIManager::Impl::finishCurrencyBuy() +{ + LLXMLRPCValue result = mTransaction->responseValue(); + + bool success = result["success"].asBool(); + if (!success) + { + setError( + result["errorMessage"].asString(), + result["errorURI"].asString() + ); + } + else + { + mUserCurrencyBuy = 0; + mUserEnteredCurrencyBuy = false; + mBought = true; + } +} + +void LLCurrencyUIManager::Impl::startTransaction(TransactionType type, + const char* method, LLXMLRPCValue params) +{ + static std::string transactionURI; + if (transactionURI.empty()) + { + transactionURI = LLGridManager::getInstance()->getHelperURI() + "currency.php"; + } + + delete mTransaction; + + mTransactionType = type; + mTransaction = new LLXMLRPCTransaction( + transactionURI, + method, + params, + false /* don't use gzip */ + ); + + clearError(); +} + +void LLCurrencyUIManager::Impl::clearEstimate() +{ + mUSDCurrencyEstimated = false; + mUSDCurrencyEstimatedCost = 0; + mLocalCurrencyEstimated = false; + mLocalCurrencyEstimatedCost = "0"; +} + +bool LLCurrencyUIManager::Impl::hasEstimate() const +{ + return (mUSDCurrencyEstimated || mLocalCurrencyEstimated); +} + +std::string LLCurrencyUIManager::Impl::getLocalEstimate() const +{ + if (mLocalCurrencyEstimated) + { + // we have the new-style local currency string + return mLocalCurrencyEstimatedCost; + } + if (mUSDCurrencyEstimated) + { + // we have the old-style USD-specific value + LLStringUtil::format_map_t args; + { + LLLocale locale_override(LLStringUtil::getLocale()); + args["[AMOUNT]"] = llformat("%#.2f", mUSDCurrencyEstimatedCost / 100.0); + } + return LLTrans::getString("LocalEstimateUSD", args); + } + return ""; +} + +bool LLCurrencyUIManager::Impl::checkTransaction() +{ + if (!mTransaction) + { + return false; + } + + if (!mTransaction->process()) + { + return false; + } + + if (mTransaction->status(NULL) != LLXMLRPCTransaction::StatusComplete) + { + setError(mTransaction->statusMessage(), mTransaction->statusURI()); + } + else { + switch (mTransactionType) + { + case TransactionCurrency: finishCurrencyInfo(); break; + case TransactionBuy: finishCurrencyBuy(); break; + default: ; + } + } + + delete mTransaction; + mTransaction = NULL; + mTransactionType = TransactionNone; + + return true; +} + +void LLCurrencyUIManager::Impl::setError( + const std::string& message, const std::string& uri) +{ + mError = true; + mErrorMessage = message; + mErrorURI = uri; +} + +void LLCurrencyUIManager::Impl::clearError() +{ + mError = false; + mErrorMessage.clear(); + mErrorURI.clear(); +} + +bool LLCurrencyUIManager::Impl::considerUpdateCurrency() +{ + if (mCurrencyChanged + && !mTransaction + && mCurrencyKeyTimer.getElapsedTimeF32() >= CURRENCY_ESTIMATE_FREQUENCY) + { + updateCurrencyInfo(); + return true; + } + + return false; +} + +void LLCurrencyUIManager::Impl::currencyKey(S32 value) +{ + mUserEnteredCurrencyBuy = true; + mCurrencyKeyTimer.reset(); + + if (mUserCurrencyBuy == value) + { + return; + } + + mUserCurrencyBuy = value; + + if (hasEstimate()) { + clearEstimate(); + //cannot just simply refresh the whole UI, as the edit field will + // get reset and the cursor will change... + + mPanel.getChildView("currency_est")->setVisible(false); + mPanel.getChildView("getting_data")->setVisible(true); + } + + mCurrencyChanged = true; +} + +// static +void LLCurrencyUIManager::Impl::onCurrencyKey( + LLLineEditor* caller, void* data) +{ + S32 value = atoi(caller->getText().c_str()); + LLCurrencyUIManager::Impl* self = (LLCurrencyUIManager::Impl*)data; + self->currencyKey(value); +} + +void LLCurrencyUIManager::Impl::prepare() +{ + LLLineEditor* lindenAmount = mPanel.getChild("currency_amt"); + if (lindenAmount) + { + lindenAmount->setPrevalidate(LLTextValidate::validateNonNegativeS32); + lindenAmount->setKeystrokeCallback(onCurrencyKey, this); + } +} + +void LLCurrencyUIManager::Impl::updateUI() +{ + if (mHidden) + { + mPanel.getChildView("currency_action")->setVisible(false); + mPanel.getChildView("currency_amt")->setVisible(false); + mPanel.getChildView("currency_est")->setVisible(false); + return; + } + + mPanel.getChildView("currency_action")->setVisible(true); + + LLLineEditor* lindenAmount = mPanel.getChild("currency_amt"); + if (lindenAmount) + { + lindenAmount->setVisible(true); + lindenAmount->setLabel(mZeroMessage); + + if (!mUserEnteredCurrencyBuy) + { + if (mUserCurrencyBuy == 0) + { + lindenAmount->setText(LLStringUtil::null); + } + else + { + lindenAmount->setText(llformat("%d", mUserCurrencyBuy)); + } + + lindenAmount->selectAll(); + } + } + + std::string estimated = (mUserCurrencyBuy == 0) ? mPanel.getString("estimated_zero") : getLocalEstimate(); + mPanel.getChild("currency_est")->setTextArg("[LOCALAMOUNT]", estimated); + mPanel.getChildView("currency_est")->setVisible( hasEstimate() || mUserCurrencyBuy == 0); + + mPanel.getChildView("currency_links")->setVisible( mSupportsInternationalBilling); + mPanel.getChildView("exchange_rate_note")->setVisible( mSupportsInternationalBilling); + + if (mPanel.getChildView("buy_btn")->getEnabled() + ||mPanel.getChildView("currency_est")->getVisible() + || mPanel.getChildView("error_web")->getVisible()) + { + mPanel.getChildView("getting_data")->setVisible(false); + } +} + + + +LLCurrencyUIManager::LLCurrencyUIManager(LLPanel& dialog) + : impl(* new Impl(dialog)) +{ +} + +LLCurrencyUIManager::~LLCurrencyUIManager() +{ + delete &impl; +} + +void LLCurrencyUIManager::setAmount(int amount, bool noEstimate) +{ + impl.mUserCurrencyBuy = amount; + impl.mUserEnteredCurrencyBuy = false; + impl.updateUI(); + + impl.mCurrencyChanged = !noEstimate; +} + +int LLCurrencyUIManager::getAmount() +{ + return impl.mUserCurrencyBuy; +} + +void LLCurrencyUIManager::setZeroMessage(const std::string& message) +{ + impl.mZeroMessage = message; +} + +void LLCurrencyUIManager::setUSDEstimate(int amount) +{ + impl.mUSDCurrencyEstimatedCost = amount; + impl.mUSDCurrencyEstimated = true; + impl.updateUI(); + + impl.mCurrencyChanged = false; +} + +int LLCurrencyUIManager::getUSDEstimate() +{ + return impl.mUSDCurrencyEstimated ? impl.mUSDCurrencyEstimatedCost : 0; +} + +void LLCurrencyUIManager::setLocalEstimate(const std::string &amount) +{ + impl.mLocalCurrencyEstimatedCost = amount; + impl.mLocalCurrencyEstimated = true; + impl.updateUI(); + + impl.mCurrencyChanged = false; +} + +std::string LLCurrencyUIManager::getLocalEstimate() const +{ + return impl.getLocalEstimate(); +} + +void LLCurrencyUIManager::prepare() +{ + impl.prepare(); +} + +void LLCurrencyUIManager::updateUI(bool show) +{ + impl.mHidden = !show; + impl.updateUI(); +} + +bool LLCurrencyUIManager::process() +{ + bool changed = false; + changed |= impl.checkTransaction(); + changed |= impl.considerUpdateCurrency(); + return changed; +} + +void LLCurrencyUIManager::buy(const std::string& buy_msg) +{ + if (!canBuy()) + { + return; + } + + LLUIString msg = buy_msg; + msg.setArg("[LINDENS]", llformat("%d", impl.mUserCurrencyBuy)); + msg.setArg("[LOCALAMOUNT]", getLocalEstimate()); + LLConfirmationManager::confirm(impl.mSiteConfirm, + msg, + impl, + &LLCurrencyUIManager::Impl::startCurrencyBuy); +} + + +bool LLCurrencyUIManager::inProcess() +{ + return impl.mTransactionType != Impl::TransactionNone; +} + +bool LLCurrencyUIManager::canCancel() +{ + return impl.mTransactionType != Impl::TransactionBuy; +} + +bool LLCurrencyUIManager::canBuy() +{ + return impl.mTransactionType == Impl::TransactionNone + && impl.hasEstimate() + && impl.mUserCurrencyBuy > 0; +} + +bool LLCurrencyUIManager::buying() +{ + return impl.mTransactionType == Impl::TransactionBuy; +} + +bool LLCurrencyUIManager::bought() +{ + return impl.mBought; +} + +void LLCurrencyUIManager::clearError() +{ + impl.clearError(); +} + +bool LLCurrencyUIManager::hasError() +{ + return impl.mError; +} + +std::string LLCurrencyUIManager::errorMessage() +{ + return impl.mErrorMessage; +} + +std::string LLCurrencyUIManager::errorURI() +{ + return impl.mErrorURI; +} + + + diff --git a/indra/newview/lldebugmessagebox.cpp b/indra/newview/lldebugmessagebox.cpp index 1637b9928b..91ea36706e 100644 --- a/indra/newview/lldebugmessagebox.cpp +++ b/indra/newview/lldebugmessagebox.cpp @@ -1,291 +1,291 @@ -/** - * @file lldebugmessagebox.cpp - * @brief Implementation of a simple, non-modal message box. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lldebugmessagebox.h" - -#include "llresmgr.h" -#include "llfontgl.h" -#include "llbutton.h" -#include "llsliderctrl.h" -#include "llcheckboxctrl.h" -#include "lltextbox.h" -#include "lllineeditor.h" -#include "llfocusmgr.h" - -///---------------------------------------------------------------------------- -/// Class LLDebugVarMessageBox -///---------------------------------------------------------------------------- - -std::map LLDebugVarMessageBox::sInstances; - -LLDebugVarMessageBox::LLDebugVarMessageBox(const std::string& title, EDebugVarType var_type, void *var) : - LLFloater(LLSD()), - mVarType(var_type), mVarData(var), mAnimate(false) -{ - setRect(LLRect(10,160,400,10)); - - LLSliderCtrl::Params slider_p; - slider_p.label(title); - slider_p.label_width(70); - slider_p.text_width(40); - slider_p.can_edit_text(true); - slider_p.show_text(true); - - mSlider1 = NULL; - mSlider2 = NULL; - mSlider3 = NULL; - - switch(var_type) - { - case VAR_TYPE_F32: - slider_p.name("slider 1"); - slider_p.rect(LLRect(20,130,190,110)); - slider_p.initial_value(*((F32*)var)); - slider_p.min_value(-100.f); - slider_p.max_value(100.f); - slider_p.increment(0.1f); - slider_p.decimal_digits(3); - mSlider1 = LLUICtrlFactory::create(slider_p); - addChild(mSlider1); - break; - case VAR_TYPE_S32: - slider_p.name("slider 1"); - slider_p.rect(LLRect(20,100,190,80)); - slider_p.initial_value((F32)*((S32*)var)); - slider_p.min_value(-255.f); - slider_p.max_value(255.f); - slider_p.increment(1.f); - slider_p.decimal_digits(0); - mSlider1 = LLUICtrlFactory::create(slider_p); - addChild(mSlider1); - break; - case VAR_TYPE_VEC3: - slider_p.name("slider 1"); - slider_p.label("x: "); - slider_p.rect(LLRect(20,130,190,110)); - slider_p.initial_value(((LLVector3*)var)->mV[VX]); - slider_p.min_value(-100.f); - slider_p.max_value(100.f); - slider_p.increment(0.1f); - slider_p.decimal_digits(3); - mSlider1 = LLUICtrlFactory::create(slider_p); - - slider_p.name("slider 2"); - slider_p.label("y: "); - slider_p.rect(LLRect(20,100,190,80)); - slider_p.initial_value(((LLVector3*)var)->mV[VY]); - mSlider2 = LLUICtrlFactory::create(slider_p); - - slider_p.name("slider 3"); - slider_p.label("z: "); - slider_p.rect(LLRect(20,70,190,50)); - slider_p.initial_value(((LLVector3*)var)->mV[VZ]); - mSlider2 = LLUICtrlFactory::create(slider_p); - - addChild(mSlider1); - addChild(mSlider2); - addChild(mSlider3); - break; - default: - LL_WARNS() << "Unhandled var type " << var_type << LL_ENDL; - break; - } - - LLButton::Params p; - p.name(std::string("Animate")); - p.label(std::string("Animate")); - p.rect(LLRect(20, 45, 180, 25)); - p.click_callback.function(boost::bind(&LLDebugVarMessageBox::onAnimateClicked, this, _2)); - mAnimateButton = LLUICtrlFactory::create(p); - addChild(mAnimateButton); - - LLTextBox::Params params; - params.name("value"); - params.initial_value(params.name()); - params.rect(LLRect(20,20,190,0)); - mText = LLUICtrlFactory::create (params); - addChild(mText); - - //disable hitting enter closes dialog - setDefaultBtn(); -} - -LLDebugVarMessageBox::~LLDebugVarMessageBox() -{ - sInstances.erase(mTitle); -} - -void LLDebugVarMessageBox::show(const std::string& title, F32 *var, F32 max_value, F32 increment) -{ -#ifndef LL_RELEASE_FOR_DOWNLOAD - LLDebugVarMessageBox* box = show(title, VAR_TYPE_F32, (void*)var); - max_value = llabs(max_value); - box->mSlider1->setMaxValue(max_value); - box->mSlider1->setMinValue(-max_value); - box->mSlider1->setIncrement(increment); - if (!gFocusMgr.childHasKeyboardFocus(box)) - { - box->mSlider1->setValue(*var); - } - box->mSlider1->setCommitCallback(boost::bind(&LLDebugVarMessageBox::sliderChanged, box, _2)); -#endif -} - -void LLDebugVarMessageBox::show(const std::string& title, S32 *var, S32 max_value, S32 increment) -{ -#ifndef LL_RELEASE_FOR_DOWNLOAD - LLDebugVarMessageBox* box = show(title, VAR_TYPE_S32, (void*)var); - F32 max_val = llabs((F32)max_value); - box->mSlider1->setMaxValue(max_val); - box->mSlider1->setMinValue(-max_val); - box->mSlider1->setIncrement((F32)increment); - if (!gFocusMgr.childHasKeyboardFocus(box)) - { - box->mSlider1->setValue((F32)*var); - } - box->mSlider1->setCommitCallback(boost::bind(&LLDebugVarMessageBox::sliderChanged, box, _2)); -#endif -} - -void LLDebugVarMessageBox::show(const std::string& title, LLVector3 *var, LLVector3 max_value, LLVector3 increment) -{ -#ifndef LL_RELEASE_FOR_DOWNLOAD - LLDebugVarMessageBox* box = show(title, VAR_TYPE_VEC3, (LLVector3*)var); - max_value.abs(); - box->mSlider1->setMaxValue(max_value.mV[VX]); - box->mSlider1->setMinValue(-max_value.mV[VX]); - box->mSlider1->setIncrement(increment.mV[VX]); - box->mSlider1->setCommitCallback(boost::bind(&LLDebugVarMessageBox::sliderChanged, box, _2)); - - box->mSlider2->setMaxValue(max_value.mV[VX]); - box->mSlider2->setMinValue(-max_value.mV[VX]); - box->mSlider2->setIncrement(increment.mV[VX]); - box->mSlider2->setCommitCallback(boost::bind(&LLDebugVarMessageBox::sliderChanged, box, _2)); - - box->mSlider3->setMaxValue(max_value.mV[VX]); - box->mSlider3->setMinValue(-max_value.mV[VX]); - box->mSlider3->setIncrement(increment.mV[VX]); - box->mSlider3->setCommitCallback(boost::bind(&LLDebugVarMessageBox::sliderChanged, box, _2)); -#endif -} - -LLDebugVarMessageBox* LLDebugVarMessageBox::show(const std::string& title, EDebugVarType var_type, void *var) -{ - std::string title_string(title); - - LLDebugVarMessageBox *box = sInstances[title_string]; - if (!box) - { - box = new LLDebugVarMessageBox(title, var_type, var); - sInstances[title_string] = box; - gFloaterView->addChild(box); - box->reshape(200,150); - box->openFloater(); - box->mTitle = title_string; - } - - return box; -} - -void LLDebugVarMessageBox::sliderChanged(const LLSD& data) -{ - if (!mVarData) - return; - - switch(mVarType) - { - case VAR_TYPE_F32: - *((F32*)mVarData) = (F32)mSlider1->getValue().asReal(); - break; - case VAR_TYPE_S32: - *((S32*)mVarData) = (S32)mSlider1->getValue().asInteger(); - break; - case VAR_TYPE_VEC3: - { - LLVector3* vec_p = (LLVector3*)mVarData; - vec_p->setVec((F32)mSlider1->getValue().asReal(), - (F32)mSlider2->getValue().asReal(), - (F32)mSlider3->getValue().asReal()); - break; - } - default: - LL_WARNS() << "Unhandled var type " << mVarType << LL_ENDL; - break; - } -} - -void LLDebugVarMessageBox::onAnimateClicked(const LLSD& data) -{ - mAnimate = !mAnimate; - mAnimateButton->setToggleState(mAnimate); -} - -void LLDebugVarMessageBox::draw() -{ - std::string text; - switch(mVarType) - { - case VAR_TYPE_F32: - text = llformat("%.3f", *((F32*)mVarData)); - break; - case VAR_TYPE_S32: - text = llformat("%d", *((S32*)mVarData)); - break; - case VAR_TYPE_VEC3: - { - LLVector3* vec_p = (LLVector3*)mVarData; - text= llformat("%.3f %.3f %.3f", vec_p->mV[VX], vec_p->mV[VY], vec_p->mV[VZ]); - break; - } - default: - LL_WARNS() << "Unhandled var type " << mVarType << LL_ENDL; - break; - } - mText->setText(text); - - if(mAnimate) - { - if (mSlider1) - { - F32 animated_val = clamp_rescale(fmodf((F32)LLFrameTimer::getElapsedSeconds() / 5.f, 1.f), 0.f, 1.f, 0.f, mSlider1->getMaxValue()); - mSlider1->setValue(animated_val); - sliderChanged(LLSD()); - if (mSlider2) - { - mSlider2->setValue(animated_val); - sliderChanged(LLSD()); - } - if (mSlider3) - { - mSlider3->setValue(animated_val); - sliderChanged(LLSD()); - } - } - } - LLFloater::draw(); -} +/** + * @file lldebugmessagebox.cpp + * @brief Implementation of a simple, non-modal message box. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lldebugmessagebox.h" + +#include "llresmgr.h" +#include "llfontgl.h" +#include "llbutton.h" +#include "llsliderctrl.h" +#include "llcheckboxctrl.h" +#include "lltextbox.h" +#include "lllineeditor.h" +#include "llfocusmgr.h" + +///---------------------------------------------------------------------------- +/// Class LLDebugVarMessageBox +///---------------------------------------------------------------------------- + +std::map LLDebugVarMessageBox::sInstances; + +LLDebugVarMessageBox::LLDebugVarMessageBox(const std::string& title, EDebugVarType var_type, void *var) : + LLFloater(LLSD()), + mVarType(var_type), mVarData(var), mAnimate(false) +{ + setRect(LLRect(10,160,400,10)); + + LLSliderCtrl::Params slider_p; + slider_p.label(title); + slider_p.label_width(70); + slider_p.text_width(40); + slider_p.can_edit_text(true); + slider_p.show_text(true); + + mSlider1 = NULL; + mSlider2 = NULL; + mSlider3 = NULL; + + switch(var_type) + { + case VAR_TYPE_F32: + slider_p.name("slider 1"); + slider_p.rect(LLRect(20,130,190,110)); + slider_p.initial_value(*((F32*)var)); + slider_p.min_value(-100.f); + slider_p.max_value(100.f); + slider_p.increment(0.1f); + slider_p.decimal_digits(3); + mSlider1 = LLUICtrlFactory::create(slider_p); + addChild(mSlider1); + break; + case VAR_TYPE_S32: + slider_p.name("slider 1"); + slider_p.rect(LLRect(20,100,190,80)); + slider_p.initial_value((F32)*((S32*)var)); + slider_p.min_value(-255.f); + slider_p.max_value(255.f); + slider_p.increment(1.f); + slider_p.decimal_digits(0); + mSlider1 = LLUICtrlFactory::create(slider_p); + addChild(mSlider1); + break; + case VAR_TYPE_VEC3: + slider_p.name("slider 1"); + slider_p.label("x: "); + slider_p.rect(LLRect(20,130,190,110)); + slider_p.initial_value(((LLVector3*)var)->mV[VX]); + slider_p.min_value(-100.f); + slider_p.max_value(100.f); + slider_p.increment(0.1f); + slider_p.decimal_digits(3); + mSlider1 = LLUICtrlFactory::create(slider_p); + + slider_p.name("slider 2"); + slider_p.label("y: "); + slider_p.rect(LLRect(20,100,190,80)); + slider_p.initial_value(((LLVector3*)var)->mV[VY]); + mSlider2 = LLUICtrlFactory::create(slider_p); + + slider_p.name("slider 3"); + slider_p.label("z: "); + slider_p.rect(LLRect(20,70,190,50)); + slider_p.initial_value(((LLVector3*)var)->mV[VZ]); + mSlider2 = LLUICtrlFactory::create(slider_p); + + addChild(mSlider1); + addChild(mSlider2); + addChild(mSlider3); + break; + default: + LL_WARNS() << "Unhandled var type " << var_type << LL_ENDL; + break; + } + + LLButton::Params p; + p.name(std::string("Animate")); + p.label(std::string("Animate")); + p.rect(LLRect(20, 45, 180, 25)); + p.click_callback.function(boost::bind(&LLDebugVarMessageBox::onAnimateClicked, this, _2)); + mAnimateButton = LLUICtrlFactory::create(p); + addChild(mAnimateButton); + + LLTextBox::Params params; + params.name("value"); + params.initial_value(params.name()); + params.rect(LLRect(20,20,190,0)); + mText = LLUICtrlFactory::create (params); + addChild(mText); + + //disable hitting enter closes dialog + setDefaultBtn(); +} + +LLDebugVarMessageBox::~LLDebugVarMessageBox() +{ + sInstances.erase(mTitle); +} + +void LLDebugVarMessageBox::show(const std::string& title, F32 *var, F32 max_value, F32 increment) +{ +#ifndef LL_RELEASE_FOR_DOWNLOAD + LLDebugVarMessageBox* box = show(title, VAR_TYPE_F32, (void*)var); + max_value = llabs(max_value); + box->mSlider1->setMaxValue(max_value); + box->mSlider1->setMinValue(-max_value); + box->mSlider1->setIncrement(increment); + if (!gFocusMgr.childHasKeyboardFocus(box)) + { + box->mSlider1->setValue(*var); + } + box->mSlider1->setCommitCallback(boost::bind(&LLDebugVarMessageBox::sliderChanged, box, _2)); +#endif +} + +void LLDebugVarMessageBox::show(const std::string& title, S32 *var, S32 max_value, S32 increment) +{ +#ifndef LL_RELEASE_FOR_DOWNLOAD + LLDebugVarMessageBox* box = show(title, VAR_TYPE_S32, (void*)var); + F32 max_val = llabs((F32)max_value); + box->mSlider1->setMaxValue(max_val); + box->mSlider1->setMinValue(-max_val); + box->mSlider1->setIncrement((F32)increment); + if (!gFocusMgr.childHasKeyboardFocus(box)) + { + box->mSlider1->setValue((F32)*var); + } + box->mSlider1->setCommitCallback(boost::bind(&LLDebugVarMessageBox::sliderChanged, box, _2)); +#endif +} + +void LLDebugVarMessageBox::show(const std::string& title, LLVector3 *var, LLVector3 max_value, LLVector3 increment) +{ +#ifndef LL_RELEASE_FOR_DOWNLOAD + LLDebugVarMessageBox* box = show(title, VAR_TYPE_VEC3, (LLVector3*)var); + max_value.abs(); + box->mSlider1->setMaxValue(max_value.mV[VX]); + box->mSlider1->setMinValue(-max_value.mV[VX]); + box->mSlider1->setIncrement(increment.mV[VX]); + box->mSlider1->setCommitCallback(boost::bind(&LLDebugVarMessageBox::sliderChanged, box, _2)); + + box->mSlider2->setMaxValue(max_value.mV[VX]); + box->mSlider2->setMinValue(-max_value.mV[VX]); + box->mSlider2->setIncrement(increment.mV[VX]); + box->mSlider2->setCommitCallback(boost::bind(&LLDebugVarMessageBox::sliderChanged, box, _2)); + + box->mSlider3->setMaxValue(max_value.mV[VX]); + box->mSlider3->setMinValue(-max_value.mV[VX]); + box->mSlider3->setIncrement(increment.mV[VX]); + box->mSlider3->setCommitCallback(boost::bind(&LLDebugVarMessageBox::sliderChanged, box, _2)); +#endif +} + +LLDebugVarMessageBox* LLDebugVarMessageBox::show(const std::string& title, EDebugVarType var_type, void *var) +{ + std::string title_string(title); + + LLDebugVarMessageBox *box = sInstances[title_string]; + if (!box) + { + box = new LLDebugVarMessageBox(title, var_type, var); + sInstances[title_string] = box; + gFloaterView->addChild(box); + box->reshape(200,150); + box->openFloater(); + box->mTitle = title_string; + } + + return box; +} + +void LLDebugVarMessageBox::sliderChanged(const LLSD& data) +{ + if (!mVarData) + return; + + switch(mVarType) + { + case VAR_TYPE_F32: + *((F32*)mVarData) = (F32)mSlider1->getValue().asReal(); + break; + case VAR_TYPE_S32: + *((S32*)mVarData) = (S32)mSlider1->getValue().asInteger(); + break; + case VAR_TYPE_VEC3: + { + LLVector3* vec_p = (LLVector3*)mVarData; + vec_p->setVec((F32)mSlider1->getValue().asReal(), + (F32)mSlider2->getValue().asReal(), + (F32)mSlider3->getValue().asReal()); + break; + } + default: + LL_WARNS() << "Unhandled var type " << mVarType << LL_ENDL; + break; + } +} + +void LLDebugVarMessageBox::onAnimateClicked(const LLSD& data) +{ + mAnimate = !mAnimate; + mAnimateButton->setToggleState(mAnimate); +} + +void LLDebugVarMessageBox::draw() +{ + std::string text; + switch(mVarType) + { + case VAR_TYPE_F32: + text = llformat("%.3f", *((F32*)mVarData)); + break; + case VAR_TYPE_S32: + text = llformat("%d", *((S32*)mVarData)); + break; + case VAR_TYPE_VEC3: + { + LLVector3* vec_p = (LLVector3*)mVarData; + text= llformat("%.3f %.3f %.3f", vec_p->mV[VX], vec_p->mV[VY], vec_p->mV[VZ]); + break; + } + default: + LL_WARNS() << "Unhandled var type " << mVarType << LL_ENDL; + break; + } + mText->setText(text); + + if(mAnimate) + { + if (mSlider1) + { + F32 animated_val = clamp_rescale(fmodf((F32)LLFrameTimer::getElapsedSeconds() / 5.f, 1.f), 0.f, 1.f, 0.f, mSlider1->getMaxValue()); + mSlider1->setValue(animated_val); + sliderChanged(LLSD()); + if (mSlider2) + { + mSlider2->setValue(animated_val); + sliderChanged(LLSD()); + } + if (mSlider3) + { + mSlider3->setValue(animated_val); + sliderChanged(LLSD()); + } + } + } + LLFloater::draw(); +} diff --git a/indra/newview/lldebugmessagebox.h b/indra/newview/lldebugmessagebox.h index 53afae40fe..3160ee24b5 100644 --- a/indra/newview/lldebugmessagebox.h +++ b/indra/newview/lldebugmessagebox.h @@ -1,88 +1,88 @@ -/** - * @file lldebugmessagebox.h - * @brief Debug message box. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#ifndef LL_LLDEBUGMESSAGEBOX_H -#define LL_LLDEBUGMESSAGEBOX_H - -#include "llfloater.h" -#include "v3math.h" -#include "lltextbox.h" -#include "llstring.h" -#include "llframetimer.h" -#include -#include - -class LLSliderCtrl; - -//---------------------------------------------------------------------------- -// LLDebugVarMessageBox -//---------------------------------------------------------------------------- - -typedef enum e_debug_var_type -{ - VAR_TYPE_F32, - VAR_TYPE_S32, - VAR_TYPE_VEC2, - VAR_TYPE_VEC3, - VAR_TYPE_VEC4, - VAR_TYPE_COUNT -} EDebugVarType; - -class LLDebugVarMessageBox : public LLFloater -{ -protected: - LLDebugVarMessageBox(const std::string& title, EDebugVarType var_type, void *var); - ~LLDebugVarMessageBox(); - - static LLDebugVarMessageBox* show(const std::string& title, EDebugVarType var_type, void *var); - void sliderChanged(const LLSD& data); - void onAnimateClicked(const LLSD& data); - -public: - static void show(const std::string& title, F32 *var, F32 max_value = 100.f, F32 increment = 0.1f); - static void show(const std::string& title, S32 *var, S32 max_value = 255, S32 increment = 1); - static void show(const std::string& title, LLVector2 *var, LLVector2 max_value = LLVector2(100.f, 100.f), LLVector2 increment = LLVector2(0.1f, 0.1f)); - static void show(const std::string& title, LLVector3 *var, LLVector3 max_value = LLVector3(100.f, 100.f, 100.f), LLVector3 increment = LLVector3(0.1f, 0.1f, 0.1f)); - //static void show(const std::string& title, LLVector4 *var, LLVector4 max_value = LLVector4(100.f, 100.f, 100.f, 100.f), LLVector4 increment = LLVector4(0.1f, 0.1f, 0.1f, 0.1f)); - - virtual void draw(); - -protected: - EDebugVarType mVarType; - void* mVarData; - LLSliderCtrl* mSlider1; - LLSliderCtrl* mSlider2; - LLSliderCtrl* mSlider3; - LLButton* mAnimateButton; - LLTextBox* mText; - std::string mTitle; - bool mAnimate; - - static std::map sInstances; -}; - -#endif // LL_LLMESSAGEBOX_H +/** + * @file lldebugmessagebox.h + * @brief Debug message box. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#ifndef LL_LLDEBUGMESSAGEBOX_H +#define LL_LLDEBUGMESSAGEBOX_H + +#include "llfloater.h" +#include "v3math.h" +#include "lltextbox.h" +#include "llstring.h" +#include "llframetimer.h" +#include +#include + +class LLSliderCtrl; + +//---------------------------------------------------------------------------- +// LLDebugVarMessageBox +//---------------------------------------------------------------------------- + +typedef enum e_debug_var_type +{ + VAR_TYPE_F32, + VAR_TYPE_S32, + VAR_TYPE_VEC2, + VAR_TYPE_VEC3, + VAR_TYPE_VEC4, + VAR_TYPE_COUNT +} EDebugVarType; + +class LLDebugVarMessageBox : public LLFloater +{ +protected: + LLDebugVarMessageBox(const std::string& title, EDebugVarType var_type, void *var); + ~LLDebugVarMessageBox(); + + static LLDebugVarMessageBox* show(const std::string& title, EDebugVarType var_type, void *var); + void sliderChanged(const LLSD& data); + void onAnimateClicked(const LLSD& data); + +public: + static void show(const std::string& title, F32 *var, F32 max_value = 100.f, F32 increment = 0.1f); + static void show(const std::string& title, S32 *var, S32 max_value = 255, S32 increment = 1); + static void show(const std::string& title, LLVector2 *var, LLVector2 max_value = LLVector2(100.f, 100.f), LLVector2 increment = LLVector2(0.1f, 0.1f)); + static void show(const std::string& title, LLVector3 *var, LLVector3 max_value = LLVector3(100.f, 100.f, 100.f), LLVector3 increment = LLVector3(0.1f, 0.1f, 0.1f)); + //static void show(const std::string& title, LLVector4 *var, LLVector4 max_value = LLVector4(100.f, 100.f, 100.f, 100.f), LLVector4 increment = LLVector4(0.1f, 0.1f, 0.1f, 0.1f)); + + virtual void draw(); + +protected: + EDebugVarType mVarType; + void* mVarData; + LLSliderCtrl* mSlider1; + LLSliderCtrl* mSlider2; + LLSliderCtrl* mSlider3; + LLButton* mAnimateButton; + LLTextBox* mText; + std::string mTitle; + bool mAnimate; + + static std::map sInstances; +}; + +#endif // LL_LLMESSAGEBOX_H diff --git a/indra/newview/lldebugview.cpp b/indra/newview/lldebugview.cpp index 3ef5242de0..b88d11886a 100644 --- a/indra/newview/lldebugview.cpp +++ b/indra/newview/lldebugview.cpp @@ -1,134 +1,134 @@ -/** - * @file lldebugview.cpp - * @brief A view containing UI elements only visible in build mode. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lldebugview.h" - -// library includes -#include "llfasttimerview.h" -#include "llconsole.h" -#include "lltextureview.h" -#include "llresmgr.h" -#include "llviewercontrol.h" -#include "llviewerwindow.h" -#include "llappviewer.h" -#include "llsceneview.h" -#include "llviewertexture.h" -#include "llfloaterreg.h" -#include "llscenemonitor.h" -// -// Globals -// - -LLDebugView* gDebugView = NULL; - -// -// Methods -// -static LLDefaultChildRegistry::Register r("debug_view"); - -LLDebugView::LLDebugView(const LLDebugView::Params& p) -: LLView(p), - mFastTimerView(NULL), - mDebugConsolep(NULL), - mFloaterSnapRegion(NULL) -{} - -LLDebugView::~LLDebugView() -{ - // These have already been deleted. Fix the globals appropriately. - gDebugView = NULL; - gTextureView = NULL; - gSceneView = NULL; - gSceneMonitorView = NULL; -} - -void LLDebugView::init() -{ - LLRect r; - LLRect rect = getLocalRect(); - - // Rectangle to draw debug data in (full height, 3/4 width) - r.set(10, rect.getHeight() - 100, ((rect.getWidth()*3)/4), 100); - LLConsole::Params cp; - cp.name("debug console"); - cp.max_lines(20); - cp.rect(r); - cp.font(LLFontGL::getFontMonospace()); - cp.follows.flags(FOLLOWS_BOTTOM | FOLLOWS_LEFT); - cp.visible(false); - mDebugConsolep = LLUICtrlFactory::create(cp); - addChild(mDebugConsolep); - - r.set(150 - 25, rect.getHeight() - 50, rect.getWidth()/2 - 25, rect.getHeight() - 450); - - r.setLeftTopAndSize(25, rect.getHeight() - 50, (S32) (gViewerWindow->getWindowRectScaled().getWidth() * 0.75f), - (S32) (gViewerWindow->getWindowRectScaled().getHeight() * 0.75f)); - - mFastTimerView = dynamic_cast(LLFloaterReg::getInstance("block_timers")); - - gSceneView = new LLSceneView(r); - gSceneView->setFollowsTop(); - gSceneView->setFollowsLeft(); - gSceneView->setVisible(false); - addChild(gSceneView); - gSceneView->setRect(rect); - - gSceneMonitorView = new LLSceneMonitorView(r); - gSceneMonitorView->setFollowsTop(); - gSceneMonitorView->setFollowsLeft(); - gSceneMonitorView->setVisible(false); - addChild(gSceneMonitorView); - gSceneMonitorView->setRect(rect); - - r.setLeftTopAndSize(25, rect.getHeight() - 50, (S32) (gViewerWindow->getWindowRectScaled().getWidth() * 0.75f), - (S32) (gViewerWindow->getWindowRectScaled().getHeight() * 0.75f)); - - r.set(150, rect.getHeight() - 50, 820, 100); - LLTextureView::Params tvp; - tvp.name("gTextureView"); - tvp.rect(r); - tvp.follows.flags(FOLLOWS_TOP|FOLLOWS_LEFT); - tvp.visible(false); - gTextureView = LLUICtrlFactory::create(tvp); - addChild(gTextureView); - //gTextureView->reshape(r.getWidth(), r.getHeight(), true); -} - -void LLDebugView::draw() -{ - if (mFloaterSnapRegion == NULL) - { - mFloaterSnapRegion = getRootView()->getChildView("floater_snap_region"); - } - - LLRect debug_rect; - mFloaterSnapRegion->localRectToOtherView(mFloaterSnapRegion->getLocalRect(), &debug_rect, getParent()); - - setShape(debug_rect); - LLView::draw(); -} +/** + * @file lldebugview.cpp + * @brief A view containing UI elements only visible in build mode. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lldebugview.h" + +// library includes +#include "llfasttimerview.h" +#include "llconsole.h" +#include "lltextureview.h" +#include "llresmgr.h" +#include "llviewercontrol.h" +#include "llviewerwindow.h" +#include "llappviewer.h" +#include "llsceneview.h" +#include "llviewertexture.h" +#include "llfloaterreg.h" +#include "llscenemonitor.h" +// +// Globals +// + +LLDebugView* gDebugView = NULL; + +// +// Methods +// +static LLDefaultChildRegistry::Register r("debug_view"); + +LLDebugView::LLDebugView(const LLDebugView::Params& p) +: LLView(p), + mFastTimerView(NULL), + mDebugConsolep(NULL), + mFloaterSnapRegion(NULL) +{} + +LLDebugView::~LLDebugView() +{ + // These have already been deleted. Fix the globals appropriately. + gDebugView = NULL; + gTextureView = NULL; + gSceneView = NULL; + gSceneMonitorView = NULL; +} + +void LLDebugView::init() +{ + LLRect r; + LLRect rect = getLocalRect(); + + // Rectangle to draw debug data in (full height, 3/4 width) + r.set(10, rect.getHeight() - 100, ((rect.getWidth()*3)/4), 100); + LLConsole::Params cp; + cp.name("debug console"); + cp.max_lines(20); + cp.rect(r); + cp.font(LLFontGL::getFontMonospace()); + cp.follows.flags(FOLLOWS_BOTTOM | FOLLOWS_LEFT); + cp.visible(false); + mDebugConsolep = LLUICtrlFactory::create(cp); + addChild(mDebugConsolep); + + r.set(150 - 25, rect.getHeight() - 50, rect.getWidth()/2 - 25, rect.getHeight() - 450); + + r.setLeftTopAndSize(25, rect.getHeight() - 50, (S32) (gViewerWindow->getWindowRectScaled().getWidth() * 0.75f), + (S32) (gViewerWindow->getWindowRectScaled().getHeight() * 0.75f)); + + mFastTimerView = dynamic_cast(LLFloaterReg::getInstance("block_timers")); + + gSceneView = new LLSceneView(r); + gSceneView->setFollowsTop(); + gSceneView->setFollowsLeft(); + gSceneView->setVisible(false); + addChild(gSceneView); + gSceneView->setRect(rect); + + gSceneMonitorView = new LLSceneMonitorView(r); + gSceneMonitorView->setFollowsTop(); + gSceneMonitorView->setFollowsLeft(); + gSceneMonitorView->setVisible(false); + addChild(gSceneMonitorView); + gSceneMonitorView->setRect(rect); + + r.setLeftTopAndSize(25, rect.getHeight() - 50, (S32) (gViewerWindow->getWindowRectScaled().getWidth() * 0.75f), + (S32) (gViewerWindow->getWindowRectScaled().getHeight() * 0.75f)); + + r.set(150, rect.getHeight() - 50, 820, 100); + LLTextureView::Params tvp; + tvp.name("gTextureView"); + tvp.rect(r); + tvp.follows.flags(FOLLOWS_TOP|FOLLOWS_LEFT); + tvp.visible(false); + gTextureView = LLUICtrlFactory::create(tvp); + addChild(gTextureView); + //gTextureView->reshape(r.getWidth(), r.getHeight(), true); +} + +void LLDebugView::draw() +{ + if (mFloaterSnapRegion == NULL) + { + mFloaterSnapRegion = getRootView()->getChildView("floater_snap_region"); + } + + LLRect debug_rect; + mFloaterSnapRegion->localRectToOtherView(mFloaterSnapRegion->getLocalRect(), &debug_rect, getParent()); + + setShape(debug_rect); + LLView::draw(); +} diff --git a/indra/newview/lldebugview.h b/indra/newview/lldebugview.h index aca08d4b23..8fa2acc3c9 100644 --- a/indra/newview/lldebugview.h +++ b/indra/newview/lldebugview.h @@ -1,69 +1,69 @@ -/** - * @file lldebugview.h - * @brief A view containing debug UI elements - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLDEBUGVIEW_H -#define LL_LLDEBUGVIEW_H - -// requires: -// stdtypes.h - -#include "llview.h" - -// declarations -class LLButton; -class LLStatusPanel; -class LLFastTimerView; -class LLConsole; -class LLTextureView; -class LLFloaterStats; - -class LLDebugView : public LLView -{ -public: - struct Params : public LLInitParam::Block - { - Params() - { - changeDefault(mouse_opaque, false); - } - }; - - LLDebugView(const Params&); - ~LLDebugView(); - - void init(); - void draw(); - - void setStatsVisible(bool visible); - - LLFastTimerView* mFastTimerView; - LLConsole* mDebugConsolep; - LLView* mFloaterSnapRegion; -}; - -extern LLDebugView* gDebugView; - -#endif +/** + * @file lldebugview.h + * @brief A view containing debug UI elements + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLDEBUGVIEW_H +#define LL_LLDEBUGVIEW_H + +// requires: +// stdtypes.h + +#include "llview.h" + +// declarations +class LLButton; +class LLStatusPanel; +class LLFastTimerView; +class LLConsole; +class LLTextureView; +class LLFloaterStats; + +class LLDebugView : public LLView +{ +public: + struct Params : public LLInitParam::Block + { + Params() + { + changeDefault(mouse_opaque, false); + } + }; + + LLDebugView(const Params&); + ~LLDebugView(); + + void init(); + void draw(); + + void setStatsVisible(bool visible); + + LLFastTimerView* mFastTimerView; + LLConsole* mDebugConsolep; + LLView* mFloaterSnapRegion; +}; + +extern LLDebugView* gDebugView; + +#endif diff --git a/indra/newview/lldirpicker.cpp b/indra/newview/lldirpicker.cpp index b33467ca6c..1423ca1b9b 100644 --- a/indra/newview/lldirpicker.cpp +++ b/indra/newview/lldirpicker.cpp @@ -1,389 +1,389 @@ -/** - * @file lldirpicker.cpp - * @brief OS-specific file picker - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lldirpicker.h" -#include "llworld.h" -#include "llviewerwindow.h" -#include "llkeyboard.h" -#include "lldir.h" -#include "llframetimer.h" -#include "lltrans.h" -#include "llwindow.h" // beforeDialog() -#include "llviewercontrol.h" -#include "llwin32headerslean.h" - -#if LL_LINUX || LL_DARWIN -# include "llfilepicker.h" -#endif - -#if LL_WINDOWS -#include -#endif - -// -// Implementation -// - -// utility function to check if access to local file system via file browser -// is enabled and if not, tidy up and indicate we're not allowed to do this. -bool LLDirPicker::check_local_file_access_enabled() -{ - // if local file browsing is turned off, return without opening dialog - bool local_file_system_browsing_enabled = gSavedSettings.getBOOL("LocalFileSystemBrowsingEnabled"); - if ( ! local_file_system_browsing_enabled ) - { - mDir.clear(); // Windows - mFileName = NULL; // Mac/Linux - return false; - } - - return true; -} - -#if LL_WINDOWS - -LLDirPicker::LLDirPicker() : - mFileName(NULL), - mLocked(false), - pDialog(NULL) -{ -} - -LLDirPicker::~LLDirPicker() -{ - mEventListener.disconnect(); -} - -void LLDirPicker::reset() -{ - if (pDialog) - { - IFileDialog* p_file_dialog = (IFileDialog*)pDialog; - p_file_dialog->Close(S_FALSE); - pDialog = NULL; - } -} - -bool LLDirPicker::getDir(std::string* filename, bool blocking) -{ - if (mLocked) - { - return false; - } - - // if local file browsing is turned off, return without opening dialog - if (!check_local_file_access_enabled()) - { - return false; - } - - bool success = false; - - if (blocking) - { - // Modal, so pause agent - send_agent_pause(); - } - else if (!mEventListener.connected()) - { - mEventListener = LLEventPumps::instance().obtain("LLApp").listen( - "DirPicker", - [this](const LLSD& stat) - { - std::string status(stat["status"]); - if (status != "running") - { - reset(); - } - return false; - }); - } - - ::OleInitialize(NULL); - - IFileDialog* p_file_dialog; - if (SUCCEEDED(CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&p_file_dialog)))) - { - DWORD dwOptions; - if (SUCCEEDED(p_file_dialog->GetOptions(&dwOptions))) - { - p_file_dialog->SetOptions(dwOptions | FOS_PICKFOLDERS); - } - HWND owner = (HWND)gViewerWindow->getPlatformWindow(); - pDialog = p_file_dialog; - if (SUCCEEDED(p_file_dialog->Show(owner))) - { - IShellItem* psi; - if (SUCCEEDED(p_file_dialog->GetResult(&psi))) - { - wchar_t* pwstr = NULL; - if (SUCCEEDED(psi->GetDisplayName(SIGDN_FILESYSPATH, &pwstr))) - { - mDir = ll_convert_wide_to_string(pwstr); - CoTaskMemFree(pwstr); - success = true; - } - psi->Release(); - } - } - pDialog = NULL; - p_file_dialog->Release(); - } - - ::OleUninitialize(); - - if (blocking) - { - send_agent_resume(); - } - - // Account for the fact that the app has been stalled. - LLFrameTimer::updateFrameTime(); - return success; -} - -std::string LLDirPicker::getDirName() -{ - return mDir; -} - -/////////////////////////////////////////////DARWIN -#elif LL_DARWIN - -LLDirPicker::LLDirPicker() : -mFileName(NULL), -mLocked(false) -{ - mFilePicker = new LLFilePicker(); - reset(); -} - -LLDirPicker::~LLDirPicker() -{ - delete mFilePicker; -} - -void LLDirPicker::reset() -{ - if (mFilePicker) - mFilePicker->reset(); -} - - -//static -bool LLDirPicker::getDir(std::string* filename, bool blocking) -{ - LLFilePicker::ELoadFilter filter=LLFilePicker::FFLOAD_DIRECTORY; - - return mFilePicker->getOpenFile(filter, true); -} - -std::string LLDirPicker::getDirName() -{ - return mFilePicker->getFirstFile(); -} - -#elif LL_LINUX - -LLDirPicker::LLDirPicker() : - mFileName(NULL), - mLocked(false) -{ - mFilePicker = new LLFilePicker(); - reset(); -} - -LLDirPicker::~LLDirPicker() -{ - delete mFilePicker; -} - - -void LLDirPicker::reset() -{ - if (mFilePicker) - mFilePicker->reset(); -} - -bool LLDirPicker::getDir(std::string* filename, bool blocking) -{ - reset(); - - // if local file browsing is turned off, return without opening dialog - if (!check_local_file_access_enabled()) - { - return false; - } - -#if !LL_MESA_HEADLESS - - if (mFilePicker) - { - GtkWindow* picker = mFilePicker->buildFilePicker(false, true, - "dirpicker"); - - if (picker) - { - gtk_window_set_title(GTK_WINDOW(picker), LLTrans::getString("choose_the_directory").c_str()); - gtk_widget_show_all(GTK_WIDGET(picker)); - gtk_main(); - return (!mFilePicker->getFirstFile().empty()); - } - } -#endif // !LL_MESA_HEADLESS - - return false; -} - -std::string LLDirPicker::getDirName() -{ - if (mFilePicker) - { - return mFilePicker->getFirstFile(); - } - return ""; -} - -#else // not implemented - -LLDirPicker::LLDirPicker() -{ - reset(); -} - -LLDirPicker::~LLDirPicker() -{ -} - - -void LLDirPicker::reset() -{ -} - -bool LLDirPicker::getDir(std::string* filename, bool blocking) -{ - return false; -} - -std::string LLDirPicker::getDirName() -{ - return ""; -} - -#endif - - -LLMutex* LLDirPickerThread::sMutex = NULL; -std::queue LLDirPickerThread::sDeadQ; - -void LLDirPickerThread::getFile() -{ -#if LL_WINDOWS - start(); -#else - run(); -#endif -} - -//virtual -void LLDirPickerThread::run() -{ -#if LL_WINDOWS - bool blocking = false; -#else - bool blocking = true; // modal -#endif - - LLDirPicker picker; - - if (picker.getDir(&mProposedName, blocking)) - { - mResponses.push_back(picker.getDirName()); - } - - { - LLMutexLock lock(sMutex); - sDeadQ.push(this); - } - -} - -//static -void LLDirPickerThread::initClass() -{ - sMutex = new LLMutex(); -} - -//static -void LLDirPickerThread::cleanupClass() -{ - clearDead(); - - delete sMutex; - sMutex = NULL; -} - -//static -void LLDirPickerThread::clearDead() -{ - if (!sDeadQ.empty()) - { - LLMutexLock lock(sMutex); - while (!sDeadQ.empty()) - { - LLDirPickerThread* thread = sDeadQ.front(); - thread->notify(thread->mResponses); - delete thread; - sDeadQ.pop(); - } - } -} - -LLDirPickerThread::LLDirPickerThread(const dir_picked_signal_t::slot_type& cb, const std::string &proposed_name) - : LLThread("dir picker"), - mFilePickedSignal(NULL) -{ - mFilePickedSignal = new dir_picked_signal_t(); - mFilePickedSignal->connect(cb); -} - -LLDirPickerThread::~LLDirPickerThread() -{ - delete mFilePickedSignal; -} - -void LLDirPickerThread::notify(const std::vector& filenames) -{ - if (!filenames.empty()) - { - if (mFilePickedSignal) - { - (*mFilePickedSignal)(filenames, mProposedName); - } - } -} +/** + * @file lldirpicker.cpp + * @brief OS-specific file picker + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lldirpicker.h" +#include "llworld.h" +#include "llviewerwindow.h" +#include "llkeyboard.h" +#include "lldir.h" +#include "llframetimer.h" +#include "lltrans.h" +#include "llwindow.h" // beforeDialog() +#include "llviewercontrol.h" +#include "llwin32headerslean.h" + +#if LL_LINUX || LL_DARWIN +# include "llfilepicker.h" +#endif + +#if LL_WINDOWS +#include +#endif + +// +// Implementation +// + +// utility function to check if access to local file system via file browser +// is enabled and if not, tidy up and indicate we're not allowed to do this. +bool LLDirPicker::check_local_file_access_enabled() +{ + // if local file browsing is turned off, return without opening dialog + bool local_file_system_browsing_enabled = gSavedSettings.getBOOL("LocalFileSystemBrowsingEnabled"); + if ( ! local_file_system_browsing_enabled ) + { + mDir.clear(); // Windows + mFileName = NULL; // Mac/Linux + return false; + } + + return true; +} + +#if LL_WINDOWS + +LLDirPicker::LLDirPicker() : + mFileName(NULL), + mLocked(false), + pDialog(NULL) +{ +} + +LLDirPicker::~LLDirPicker() +{ + mEventListener.disconnect(); +} + +void LLDirPicker::reset() +{ + if (pDialog) + { + IFileDialog* p_file_dialog = (IFileDialog*)pDialog; + p_file_dialog->Close(S_FALSE); + pDialog = NULL; + } +} + +bool LLDirPicker::getDir(std::string* filename, bool blocking) +{ + if (mLocked) + { + return false; + } + + // if local file browsing is turned off, return without opening dialog + if (!check_local_file_access_enabled()) + { + return false; + } + + bool success = false; + + if (blocking) + { + // Modal, so pause agent + send_agent_pause(); + } + else if (!mEventListener.connected()) + { + mEventListener = LLEventPumps::instance().obtain("LLApp").listen( + "DirPicker", + [this](const LLSD& stat) + { + std::string status(stat["status"]); + if (status != "running") + { + reset(); + } + return false; + }); + } + + ::OleInitialize(NULL); + + IFileDialog* p_file_dialog; + if (SUCCEEDED(CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&p_file_dialog)))) + { + DWORD dwOptions; + if (SUCCEEDED(p_file_dialog->GetOptions(&dwOptions))) + { + p_file_dialog->SetOptions(dwOptions | FOS_PICKFOLDERS); + } + HWND owner = (HWND)gViewerWindow->getPlatformWindow(); + pDialog = p_file_dialog; + if (SUCCEEDED(p_file_dialog->Show(owner))) + { + IShellItem* psi; + if (SUCCEEDED(p_file_dialog->GetResult(&psi))) + { + wchar_t* pwstr = NULL; + if (SUCCEEDED(psi->GetDisplayName(SIGDN_FILESYSPATH, &pwstr))) + { + mDir = ll_convert_wide_to_string(pwstr); + CoTaskMemFree(pwstr); + success = true; + } + psi->Release(); + } + } + pDialog = NULL; + p_file_dialog->Release(); + } + + ::OleUninitialize(); + + if (blocking) + { + send_agent_resume(); + } + + // Account for the fact that the app has been stalled. + LLFrameTimer::updateFrameTime(); + return success; +} + +std::string LLDirPicker::getDirName() +{ + return mDir; +} + +/////////////////////////////////////////////DARWIN +#elif LL_DARWIN + +LLDirPicker::LLDirPicker() : +mFileName(NULL), +mLocked(false) +{ + mFilePicker = new LLFilePicker(); + reset(); +} + +LLDirPicker::~LLDirPicker() +{ + delete mFilePicker; +} + +void LLDirPicker::reset() +{ + if (mFilePicker) + mFilePicker->reset(); +} + + +//static +bool LLDirPicker::getDir(std::string* filename, bool blocking) +{ + LLFilePicker::ELoadFilter filter=LLFilePicker::FFLOAD_DIRECTORY; + + return mFilePicker->getOpenFile(filter, true); +} + +std::string LLDirPicker::getDirName() +{ + return mFilePicker->getFirstFile(); +} + +#elif LL_LINUX + +LLDirPicker::LLDirPicker() : + mFileName(NULL), + mLocked(false) +{ + mFilePicker = new LLFilePicker(); + reset(); +} + +LLDirPicker::~LLDirPicker() +{ + delete mFilePicker; +} + + +void LLDirPicker::reset() +{ + if (mFilePicker) + mFilePicker->reset(); +} + +bool LLDirPicker::getDir(std::string* filename, bool blocking) +{ + reset(); + + // if local file browsing is turned off, return without opening dialog + if (!check_local_file_access_enabled()) + { + return false; + } + +#if !LL_MESA_HEADLESS + + if (mFilePicker) + { + GtkWindow* picker = mFilePicker->buildFilePicker(false, true, + "dirpicker"); + + if (picker) + { + gtk_window_set_title(GTK_WINDOW(picker), LLTrans::getString("choose_the_directory").c_str()); + gtk_widget_show_all(GTK_WIDGET(picker)); + gtk_main(); + return (!mFilePicker->getFirstFile().empty()); + } + } +#endif // !LL_MESA_HEADLESS + + return false; +} + +std::string LLDirPicker::getDirName() +{ + if (mFilePicker) + { + return mFilePicker->getFirstFile(); + } + return ""; +} + +#else // not implemented + +LLDirPicker::LLDirPicker() +{ + reset(); +} + +LLDirPicker::~LLDirPicker() +{ +} + + +void LLDirPicker::reset() +{ +} + +bool LLDirPicker::getDir(std::string* filename, bool blocking) +{ + return false; +} + +std::string LLDirPicker::getDirName() +{ + return ""; +} + +#endif + + +LLMutex* LLDirPickerThread::sMutex = NULL; +std::queue LLDirPickerThread::sDeadQ; + +void LLDirPickerThread::getFile() +{ +#if LL_WINDOWS + start(); +#else + run(); +#endif +} + +//virtual +void LLDirPickerThread::run() +{ +#if LL_WINDOWS + bool blocking = false; +#else + bool blocking = true; // modal +#endif + + LLDirPicker picker; + + if (picker.getDir(&mProposedName, blocking)) + { + mResponses.push_back(picker.getDirName()); + } + + { + LLMutexLock lock(sMutex); + sDeadQ.push(this); + } + +} + +//static +void LLDirPickerThread::initClass() +{ + sMutex = new LLMutex(); +} + +//static +void LLDirPickerThread::cleanupClass() +{ + clearDead(); + + delete sMutex; + sMutex = NULL; +} + +//static +void LLDirPickerThread::clearDead() +{ + if (!sDeadQ.empty()) + { + LLMutexLock lock(sMutex); + while (!sDeadQ.empty()) + { + LLDirPickerThread* thread = sDeadQ.front(); + thread->notify(thread->mResponses); + delete thread; + sDeadQ.pop(); + } + } +} + +LLDirPickerThread::LLDirPickerThread(const dir_picked_signal_t::slot_type& cb, const std::string &proposed_name) + : LLThread("dir picker"), + mFilePickedSignal(NULL) +{ + mFilePickedSignal = new dir_picked_signal_t(); + mFilePickedSignal->connect(cb); +} + +LLDirPickerThread::~LLDirPickerThread() +{ + delete mFilePickedSignal; +} + +void LLDirPickerThread::notify(const std::vector& filenames) +{ + if (!filenames.empty()) + { + if (mFilePickedSignal) + { + (*mFilePickedSignal)(filenames, mProposedName); + } + } +} diff --git a/indra/newview/lldirpicker.h b/indra/newview/lldirpicker.h index eb8e91a4a5..dc740caab2 100644 --- a/indra/newview/lldirpicker.h +++ b/indra/newview/lldirpicker.h @@ -1,125 +1,125 @@ -/** - * @dir lldirpicker.h - * @brief OS-specific dir picker - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -// OS specific dir selection dialog. This is implemented as a -// singleton class, so call the instance() method to get the working -// instance. - -#ifndef LL_LLDIRPICKER_H -#define LL_LLDIRPICKER_H - -#include "stdtypes.h" - -#include "llthread.h" -#include - -#if LL_WINDOWS -#include -#endif - -#if LL_DARWIN - -// AssertMacros.h does bad things. -#undef verify -#undef check -#undef require - -#include -#include "llstring.h" - -#endif - -class LLFilePicker; - -class LLDirPicker -{ -public: - bool getDir(std::string* filename, bool blocking = true); - std::string getDirName(); - - // clear any lists of buffers or whatever, and make sure the dir - // picker isn't locked. - void reset(); - -private: - enum - { - SINGLE_DIRNAME_BUFFER_SIZE = 1024, - //DIRNAME_BUFFER_SIZE = 65536 - DIRNAME_BUFFER_SIZE = 65000 - }; - - void buildDirname( void ); - bool check_local_file_access_enabled(); - -#if LL_LINUX || LL_DARWIN - // On Linux we just implement LLDirPicker on top of LLFilePicker - LLFilePicker *mFilePicker; -#endif - - - std::string* mFileName; - std::string mDir; - bool mLocked; - void *pDialog; - boost::signals2::connection mEventListener; - -public: - // don't call these directly please. - LLDirPicker(); - ~LLDirPicker(); -}; - -class LLDirPickerThread : public LLThread -{ -public: - - static std::queue sDeadQ; - static LLMutex* sMutex; - - static void initClass(); - static void cleanupClass(); - static void clearDead(); - - std::vector mResponses; - std::string mProposedName; - - typedef boost::signals2::signal& filenames, std::string proposed_name)> dir_picked_signal_t; - - LLDirPickerThread(const dir_picked_signal_t::slot_type& cb, const std::string &proposed_name); - ~LLDirPickerThread(); - - void getFile(); - - virtual void run(); - - virtual void notify(const std::vector& filenames); - -private: - dir_picked_signal_t* mFilePickedSignal; -}; - -#endif +/** + * @dir lldirpicker.h + * @brief OS-specific dir picker + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// OS specific dir selection dialog. This is implemented as a +// singleton class, so call the instance() method to get the working +// instance. + +#ifndef LL_LLDIRPICKER_H +#define LL_LLDIRPICKER_H + +#include "stdtypes.h" + +#include "llthread.h" +#include + +#if LL_WINDOWS +#include +#endif + +#if LL_DARWIN + +// AssertMacros.h does bad things. +#undef verify +#undef check +#undef require + +#include +#include "llstring.h" + +#endif + +class LLFilePicker; + +class LLDirPicker +{ +public: + bool getDir(std::string* filename, bool blocking = true); + std::string getDirName(); + + // clear any lists of buffers or whatever, and make sure the dir + // picker isn't locked. + void reset(); + +private: + enum + { + SINGLE_DIRNAME_BUFFER_SIZE = 1024, + //DIRNAME_BUFFER_SIZE = 65536 + DIRNAME_BUFFER_SIZE = 65000 + }; + + void buildDirname( void ); + bool check_local_file_access_enabled(); + +#if LL_LINUX || LL_DARWIN + // On Linux we just implement LLDirPicker on top of LLFilePicker + LLFilePicker *mFilePicker; +#endif + + + std::string* mFileName; + std::string mDir; + bool mLocked; + void *pDialog; + boost::signals2::connection mEventListener; + +public: + // don't call these directly please. + LLDirPicker(); + ~LLDirPicker(); +}; + +class LLDirPickerThread : public LLThread +{ +public: + + static std::queue sDeadQ; + static LLMutex* sMutex; + + static void initClass(); + static void cleanupClass(); + static void clearDead(); + + std::vector mResponses; + std::string mProposedName; + + typedef boost::signals2::signal& filenames, std::string proposed_name)> dir_picked_signal_t; + + LLDirPickerThread(const dir_picked_signal_t::slot_type& cb, const std::string &proposed_name); + ~LLDirPickerThread(); + + void getFile(); + + virtual void run(); + + virtual void notify(const std::vector& filenames); + +private: + dir_picked_signal_t* mFilePickedSignal; +}; + +#endif diff --git a/indra/newview/lldndbutton.cpp b/indra/newview/lldndbutton.cpp index ca2183449a..6ba2de1589 100644 --- a/indra/newview/lldndbutton.cpp +++ b/indra/newview/lldndbutton.cpp @@ -1,47 +1,47 @@ -/** - * @file lldndbutton.cpp - * @brief Implementation of the drag-n-drop button. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lldndbutton.h" - - -static LLDefaultChildRegistry::Register r("dnd_button"); - -LLDragAndDropButton::LLDragAndDropButton(const Params& params) -: LLButton(params) -{} - -bool LLDragAndDropButton::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) -{ - if (mDragDropHandler) - { - return mDragDropHandler(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); - } - return false; -} - -// EOF +/** + * @file lldndbutton.cpp + * @brief Implementation of the drag-n-drop button. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lldndbutton.h" + + +static LLDefaultChildRegistry::Register r("dnd_button"); + +LLDragAndDropButton::LLDragAndDropButton(const Params& params) +: LLButton(params) +{} + +bool LLDragAndDropButton::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) +{ + if (mDragDropHandler) + { + return mDragDropHandler(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); + } + return false; +} + +// EOF diff --git a/indra/newview/lldndbutton.h b/indra/newview/lldndbutton.h index e486c105c4..277c2aad69 100644 --- a/indra/newview/lldndbutton.h +++ b/indra/newview/lldndbutton.h @@ -1,80 +1,80 @@ -/** - * @file lldndbutton.h - * @brief Declaration of the drag-n-drop button. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLDNDBUTTON_H -#define LL_LLDNDBUTTON_H - -#include "llbutton.h" - -/** - * Class representing a button which can handle Drag-And-Drop event. - * - * LLDragAndDropButton does not contain any logic to handle Drag-And-Drop itself. - * Instead it provides drag_drop_handler_t which can be set to the button. - * Then each Drag-And-Drop will be delegated to this handler without any pre/post processing. - * - * All xml parameters are the same as LLButton has. - * - * @see LLLandmarksPanel for example of usage of this class. - */ -class LLDragAndDropButton : public LLButton -{ -public: - struct Params : public LLInitParam::Block {}; - - LLDragAndDropButton(const Params& params); - - typedef boost::function drag_drop_handler_t; - - - /** - * Sets a handler which should process Drag-And-Drop. - */ - void setDragAndDropHandler(drag_drop_handler_t handler) { mDragDropHandler = handler; } - - - /** - * Process Drag-And-Drop by delegating the event to drag_drop_handler_t. - * - * @return bool - value returned by drag_drop_handler_t if it is set, false otherwise. - */ - /*virtual*/ bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg); - -private: - drag_drop_handler_t mDragDropHandler; -}; - - -#endif // LL_LLDNDBUTTON_H +/** + * @file lldndbutton.h + * @brief Declaration of the drag-n-drop button. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLDNDBUTTON_H +#define LL_LLDNDBUTTON_H + +#include "llbutton.h" + +/** + * Class representing a button which can handle Drag-And-Drop event. + * + * LLDragAndDropButton does not contain any logic to handle Drag-And-Drop itself. + * Instead it provides drag_drop_handler_t which can be set to the button. + * Then each Drag-And-Drop will be delegated to this handler without any pre/post processing. + * + * All xml parameters are the same as LLButton has. + * + * @see LLLandmarksPanel for example of usage of this class. + */ +class LLDragAndDropButton : public LLButton +{ +public: + struct Params : public LLInitParam::Block {}; + + LLDragAndDropButton(const Params& params); + + typedef boost::function drag_drop_handler_t; + + + /** + * Sets a handler which should process Drag-And-Drop. + */ + void setDragAndDropHandler(drag_drop_handler_t handler) { mDragDropHandler = handler; } + + + /** + * Process Drag-And-Drop by delegating the event to drag_drop_handler_t. + * + * @return bool - value returned by drag_drop_handler_t if it is set, false otherwise. + */ + /*virtual*/ bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + +private: + drag_drop_handler_t mDragDropHandler; +}; + + +#endif // LL_LLDNDBUTTON_H diff --git a/indra/newview/lldonotdisturbnotificationstorage.cpp b/indra/newview/lldonotdisturbnotificationstorage.cpp index ffda275ee8..b4ced668d0 100644 --- a/indra/newview/lldonotdisturbnotificationstorage.cpp +++ b/indra/newview/lldonotdisturbnotificationstorage.cpp @@ -1,345 +1,345 @@ -/** -* @file lldonotdisturbnotificationstorage.cpp -* @brief Implementation of lldonotdisturbnotificationstorage -* @author Stinson@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#include "llviewerprecompiledheaders.h" - -#include "lldonotdisturbnotificationstorage.h" - -#include "llcommunicationchannel.h" -#include "lldir.h" -#include "llerror.h" -#include "llfloaterreg.h" -#include "llimview.h" -#include "llnotifications.h" -#include "llnotificationhandler.h" -#include "llnotificationstorage.h" -#include "llscriptfloater.h" -#include "llsd.h" -#include "llsingleton.h" -#include "lluuid.h" - -static const F32 DND_TIMER = 3.0; -const char * LLDoNotDisturbNotificationStorage::toastName = "IMToast"; -const char * LLDoNotDisturbNotificationStorage::offerName = "UserGiveItem"; - -LLDoNotDisturbNotificationStorageTimer::LLDoNotDisturbNotificationStorageTimer() : LLEventTimer(DND_TIMER) -{ -} - -LLDoNotDisturbNotificationStorageTimer::~LLDoNotDisturbNotificationStorageTimer() -{ - -} - -bool LLDoNotDisturbNotificationStorageTimer::tick() -{ - LLDoNotDisturbNotificationStorage * doNotDisturbNotificationStorage = LLDoNotDisturbNotificationStorage::getInstance(); - - if(doNotDisturbNotificationStorage - && doNotDisturbNotificationStorage->getDirty()) - { - doNotDisturbNotificationStorage->saveNotifications(); - } - return false; -} - -LLDoNotDisturbNotificationStorage::LLDoNotDisturbNotificationStorage() - : LLNotificationStorage("") - , mDirty(false) -{ - nameToPayloadParameterMap[toastName] = "SESSION_ID"; - nameToPayloadParameterMap[offerName] = "object_id"; - initialize(); -} - -LLDoNotDisturbNotificationStorage::~LLDoNotDisturbNotificationStorage() -{ -} - -void LLDoNotDisturbNotificationStorage::reset() -{ - setFileName(gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "dnd_notifications.xml")); -} - -void LLDoNotDisturbNotificationStorage::initialize() -{ - reset(); - getCommunicationChannel()->connectFailedFilter(boost::bind(&LLDoNotDisturbNotificationStorage::onChannelChanged, this, _1)); -} - -bool LLDoNotDisturbNotificationStorage::getDirty() -{ - return mDirty; -} - -void LLDoNotDisturbNotificationStorage::resetDirty() -{ - mDirty = false; -} - -void LLDoNotDisturbNotificationStorage::saveNotifications() -{ - LL_PROFILE_ZONE_SCOPED; - - LLNotificationChannelPtr channelPtr = getCommunicationChannel(); - const LLCommunicationChannel *commChannel = dynamic_cast(channelPtr.get()); - llassert(commChannel != NULL); - - LLSD output = LLSD::emptyMap(); - LLSD& data = output["data"]; - data = LLSD::emptyArray(); - - for (LLCommunicationChannel::history_list_t::const_iterator historyIter = commChannel->beginHistory(); - historyIter != commChannel->endHistory(); ++historyIter) - { - LLNotificationPtr notificationPtr = historyIter->second; - - if (!notificationPtr->isRespondedTo() && !notificationPtr->isCancelled() && - !notificationPtr->isExpired() && !notificationPtr->isPersistent()) - { - data.append(notificationPtr->asLLSD(true)); - } - } - - writeNotifications(output); - - resetDirty(); -} - -static LLTrace::BlockTimerStatHandle FTM_LOAD_DND_NOTIFICATIONS("Load DND Notifications"); - -void LLDoNotDisturbNotificationStorage::loadNotifications() -{ - LL_RECORD_BLOCK_TIME(FTM_LOAD_DND_NOTIFICATIONS); - - LL_INFOS("LLDoNotDisturbNotificationStorage") << "start loading notifications" << LL_ENDL; - - LLSD input; - if (!readNotifications(input) ||input.isUndefined()) - { - return; - } - - LLSD& data = input["data"]; - if (data.isUndefined()) - { - return; - } - - LLNotifications& instance = LLNotifications::instance(); - bool imToastExists = false; - bool group_ad_hoc_toast_exists = false; - S32 toastSessionType; - bool offerExists = false; - - for (LLSD::array_const_iterator notification_it = data.beginArray(); - notification_it != data.endArray(); - ++notification_it) - { - LLSD notification_params = *notification_it; - const LLUUID& notificationID = notification_params["id"]; - std::string notificationName = notification_params["name"]; - LLNotificationPtr notification = instance.find(notificationID); - - if(notificationName == toastName) - { - toastSessionType = notification_params["payload"]["SESSION_TYPE"]; - if(toastSessionType == LLIMModel::LLIMSession::P2P_SESSION) - { - imToastExists = true; - } - //Don't add group/ad-hoc messages to the notification system because - //this means the group/ad-hoc session has to be re-created - else if(toastSessionType == LLIMModel::LLIMSession::GROUP_SESSION - || toastSessionType == LLIMModel::LLIMSession::ADHOC_SESSION) - { - //Just allows opening the conversation log for group/ad-hoc messages upon startup - group_ad_hoc_toast_exists = true; - continue; - } - } - else if(notificationName == offerName) - { - offerExists = true; - } - - //Notification already exists due to persistent storage adding it first into the notification system - if(notification) - { - notification->setDND(true); - instance.update(instance.find(notificationID)); - } - //New notification needs to be added - else - { - notification = (LLNotificationPtr) new LLNotification(notification_params.with("is_dnd", true)); - LLNotificationResponderInterface* responder = createResponder(notification_params["responder_sd"]["responder_type"], notification_params["responder_sd"]); - if (responder == NULL) - { - LL_WARNS("LLDoNotDisturbNotificationStorage") << "cannot create responder for notification of type '" - << notification->getType() << "'" << LL_ENDL; - } - else - { - LLNotificationResponderPtr responderPtr(responder); - notification->setResponseFunctor(responderPtr); - } - - instance.add(notification); - } - - } - - bool isConversationLoggingAllowed = gSavedPerAccountSettings.getS32("KeepConversationLogTranscripts") > 0; - if(group_ad_hoc_toast_exists && isConversationLoggingAllowed) - { - LLFloaterReg::showInstance("conversation"); - } - - if(imToastExists || group_ad_hoc_toast_exists || offerExists) - { - make_ui_sound_deferred("UISndNewIncomingIMSession"); - } - - //writes out empty .xml file (since LLCommunicationChannel::mHistory is empty) - saveNotifications(); - - LL_INFOS("LLDoNotDisturbNotificationStorage") << "finished loading notifications" << LL_ENDL; -} - -void LLDoNotDisturbNotificationStorage::updateNotifications() -{ - - LLNotificationChannelPtr channelPtr = getCommunicationChannel(); - LLCommunicationChannel *commChannel = dynamic_cast(channelPtr.get()); - llassert(commChannel != NULL); - - LLNotifications& instance = LLNotifications::instance(); - bool imToastExists = false; - bool offerExists = false; - - for (LLCommunicationChannel::history_list_t::const_iterator it = commChannel->beginHistory(); - it != commChannel->endHistory(); - ++it) - { - LLNotificationPtr notification = it->second; - std::string notificationName = notification->getName(); - - if(notificationName == toastName) - { - imToastExists = true; - } - else if(notificationName == offerName) - { - offerExists = true; - } - - //Notification already exists in notification pipeline (same instance of app running) - if (notification) - { - notification->setDND(true); - instance.update(notification); - } - } - - if(imToastExists || offerExists) - { - make_ui_sound("UISndNewIncomingIMSession"); - } - - //When exit DND mode, write empty notifications file - if(commChannel->getHistorySize()) - { - commChannel->clearHistory(); - saveNotifications(); - } -} - -LLNotificationChannelPtr LLDoNotDisturbNotificationStorage::getCommunicationChannel() const -{ - LLNotificationChannelPtr channelPtr = LLNotifications::getInstance()->getChannel("Communication"); - llassert(channelPtr); - return channelPtr; -} - -void LLDoNotDisturbNotificationStorage::removeNotification(const char * name, const LLUUID& id) -{ - LLNotifications& instance = LLNotifications::instance(); - LLNotificationChannelPtr channelPtr = getCommunicationChannel(); - LLCommunicationChannel *commChannel = dynamic_cast(channelPtr.get()); - LLNotificationPtr notification; - LLSD payload; - LLUUID notificationObjectID; - std::string notificationName; - std::string payloadVariable = nameToPayloadParameterMap[name]; - LLCommunicationChannel::history_list_t::iterator it; - std::vector itemsToRemove; - - //Find notification with the matching session id - for (it = commChannel->beginHistory(); - it != commChannel->endHistory(); - ++it) - { - notification = it->second; - payload = notification->getPayload(); - notificationObjectID = payload[payloadVariable].asUUID(); - notificationName = notification->getName(); - - if((notificationName == name) - && id == notificationObjectID) - { - itemsToRemove.push_back(it); - } - } - - - //Remove the notifications - if(itemsToRemove.size()) - { - while(itemsToRemove.size()) - { - it = itemsToRemove.back(); - notification = it->second; - commChannel->removeItemFromHistory(notification); - instance.cancel(notification); - itemsToRemove.pop_back(); - } - //Trigger saving of notifications to xml once all have been removed - saveNotifications(); - } -} - - -bool LLDoNotDisturbNotificationStorage::onChannelChanged(const LLSD& pPayload) -{ - if (pPayload["sigtype"].asString() != "load") - { - mDirty = true; - } - - return false; -} +/** +* @file lldonotdisturbnotificationstorage.cpp +* @brief Implementation of lldonotdisturbnotificationstorage +* @author Stinson@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" + +#include "lldonotdisturbnotificationstorage.h" + +#include "llcommunicationchannel.h" +#include "lldir.h" +#include "llerror.h" +#include "llfloaterreg.h" +#include "llimview.h" +#include "llnotifications.h" +#include "llnotificationhandler.h" +#include "llnotificationstorage.h" +#include "llscriptfloater.h" +#include "llsd.h" +#include "llsingleton.h" +#include "lluuid.h" + +static const F32 DND_TIMER = 3.0; +const char * LLDoNotDisturbNotificationStorage::toastName = "IMToast"; +const char * LLDoNotDisturbNotificationStorage::offerName = "UserGiveItem"; + +LLDoNotDisturbNotificationStorageTimer::LLDoNotDisturbNotificationStorageTimer() : LLEventTimer(DND_TIMER) +{ +} + +LLDoNotDisturbNotificationStorageTimer::~LLDoNotDisturbNotificationStorageTimer() +{ + +} + +bool LLDoNotDisturbNotificationStorageTimer::tick() +{ + LLDoNotDisturbNotificationStorage * doNotDisturbNotificationStorage = LLDoNotDisturbNotificationStorage::getInstance(); + + if(doNotDisturbNotificationStorage + && doNotDisturbNotificationStorage->getDirty()) + { + doNotDisturbNotificationStorage->saveNotifications(); + } + return false; +} + +LLDoNotDisturbNotificationStorage::LLDoNotDisturbNotificationStorage() + : LLNotificationStorage("") + , mDirty(false) +{ + nameToPayloadParameterMap[toastName] = "SESSION_ID"; + nameToPayloadParameterMap[offerName] = "object_id"; + initialize(); +} + +LLDoNotDisturbNotificationStorage::~LLDoNotDisturbNotificationStorage() +{ +} + +void LLDoNotDisturbNotificationStorage::reset() +{ + setFileName(gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "dnd_notifications.xml")); +} + +void LLDoNotDisturbNotificationStorage::initialize() +{ + reset(); + getCommunicationChannel()->connectFailedFilter(boost::bind(&LLDoNotDisturbNotificationStorage::onChannelChanged, this, _1)); +} + +bool LLDoNotDisturbNotificationStorage::getDirty() +{ + return mDirty; +} + +void LLDoNotDisturbNotificationStorage::resetDirty() +{ + mDirty = false; +} + +void LLDoNotDisturbNotificationStorage::saveNotifications() +{ + LL_PROFILE_ZONE_SCOPED; + + LLNotificationChannelPtr channelPtr = getCommunicationChannel(); + const LLCommunicationChannel *commChannel = dynamic_cast(channelPtr.get()); + llassert(commChannel != NULL); + + LLSD output = LLSD::emptyMap(); + LLSD& data = output["data"]; + data = LLSD::emptyArray(); + + for (LLCommunicationChannel::history_list_t::const_iterator historyIter = commChannel->beginHistory(); + historyIter != commChannel->endHistory(); ++historyIter) + { + LLNotificationPtr notificationPtr = historyIter->second; + + if (!notificationPtr->isRespondedTo() && !notificationPtr->isCancelled() && + !notificationPtr->isExpired() && !notificationPtr->isPersistent()) + { + data.append(notificationPtr->asLLSD(true)); + } + } + + writeNotifications(output); + + resetDirty(); +} + +static LLTrace::BlockTimerStatHandle FTM_LOAD_DND_NOTIFICATIONS("Load DND Notifications"); + +void LLDoNotDisturbNotificationStorage::loadNotifications() +{ + LL_RECORD_BLOCK_TIME(FTM_LOAD_DND_NOTIFICATIONS); + + LL_INFOS("LLDoNotDisturbNotificationStorage") << "start loading notifications" << LL_ENDL; + + LLSD input; + if (!readNotifications(input) ||input.isUndefined()) + { + return; + } + + LLSD& data = input["data"]; + if (data.isUndefined()) + { + return; + } + + LLNotifications& instance = LLNotifications::instance(); + bool imToastExists = false; + bool group_ad_hoc_toast_exists = false; + S32 toastSessionType; + bool offerExists = false; + + for (LLSD::array_const_iterator notification_it = data.beginArray(); + notification_it != data.endArray(); + ++notification_it) + { + LLSD notification_params = *notification_it; + const LLUUID& notificationID = notification_params["id"]; + std::string notificationName = notification_params["name"]; + LLNotificationPtr notification = instance.find(notificationID); + + if(notificationName == toastName) + { + toastSessionType = notification_params["payload"]["SESSION_TYPE"]; + if(toastSessionType == LLIMModel::LLIMSession::P2P_SESSION) + { + imToastExists = true; + } + //Don't add group/ad-hoc messages to the notification system because + //this means the group/ad-hoc session has to be re-created + else if(toastSessionType == LLIMModel::LLIMSession::GROUP_SESSION + || toastSessionType == LLIMModel::LLIMSession::ADHOC_SESSION) + { + //Just allows opening the conversation log for group/ad-hoc messages upon startup + group_ad_hoc_toast_exists = true; + continue; + } + } + else if(notificationName == offerName) + { + offerExists = true; + } + + //Notification already exists due to persistent storage adding it first into the notification system + if(notification) + { + notification->setDND(true); + instance.update(instance.find(notificationID)); + } + //New notification needs to be added + else + { + notification = (LLNotificationPtr) new LLNotification(notification_params.with("is_dnd", true)); + LLNotificationResponderInterface* responder = createResponder(notification_params["responder_sd"]["responder_type"], notification_params["responder_sd"]); + if (responder == NULL) + { + LL_WARNS("LLDoNotDisturbNotificationStorage") << "cannot create responder for notification of type '" + << notification->getType() << "'" << LL_ENDL; + } + else + { + LLNotificationResponderPtr responderPtr(responder); + notification->setResponseFunctor(responderPtr); + } + + instance.add(notification); + } + + } + + bool isConversationLoggingAllowed = gSavedPerAccountSettings.getS32("KeepConversationLogTranscripts") > 0; + if(group_ad_hoc_toast_exists && isConversationLoggingAllowed) + { + LLFloaterReg::showInstance("conversation"); + } + + if(imToastExists || group_ad_hoc_toast_exists || offerExists) + { + make_ui_sound_deferred("UISndNewIncomingIMSession"); + } + + //writes out empty .xml file (since LLCommunicationChannel::mHistory is empty) + saveNotifications(); + + LL_INFOS("LLDoNotDisturbNotificationStorage") << "finished loading notifications" << LL_ENDL; +} + +void LLDoNotDisturbNotificationStorage::updateNotifications() +{ + + LLNotificationChannelPtr channelPtr = getCommunicationChannel(); + LLCommunicationChannel *commChannel = dynamic_cast(channelPtr.get()); + llassert(commChannel != NULL); + + LLNotifications& instance = LLNotifications::instance(); + bool imToastExists = false; + bool offerExists = false; + + for (LLCommunicationChannel::history_list_t::const_iterator it = commChannel->beginHistory(); + it != commChannel->endHistory(); + ++it) + { + LLNotificationPtr notification = it->second; + std::string notificationName = notification->getName(); + + if(notificationName == toastName) + { + imToastExists = true; + } + else if(notificationName == offerName) + { + offerExists = true; + } + + //Notification already exists in notification pipeline (same instance of app running) + if (notification) + { + notification->setDND(true); + instance.update(notification); + } + } + + if(imToastExists || offerExists) + { + make_ui_sound("UISndNewIncomingIMSession"); + } + + //When exit DND mode, write empty notifications file + if(commChannel->getHistorySize()) + { + commChannel->clearHistory(); + saveNotifications(); + } +} + +LLNotificationChannelPtr LLDoNotDisturbNotificationStorage::getCommunicationChannel() const +{ + LLNotificationChannelPtr channelPtr = LLNotifications::getInstance()->getChannel("Communication"); + llassert(channelPtr); + return channelPtr; +} + +void LLDoNotDisturbNotificationStorage::removeNotification(const char * name, const LLUUID& id) +{ + LLNotifications& instance = LLNotifications::instance(); + LLNotificationChannelPtr channelPtr = getCommunicationChannel(); + LLCommunicationChannel *commChannel = dynamic_cast(channelPtr.get()); + LLNotificationPtr notification; + LLSD payload; + LLUUID notificationObjectID; + std::string notificationName; + std::string payloadVariable = nameToPayloadParameterMap[name]; + LLCommunicationChannel::history_list_t::iterator it; + std::vector itemsToRemove; + + //Find notification with the matching session id + for (it = commChannel->beginHistory(); + it != commChannel->endHistory(); + ++it) + { + notification = it->second; + payload = notification->getPayload(); + notificationObjectID = payload[payloadVariable].asUUID(); + notificationName = notification->getName(); + + if((notificationName == name) + && id == notificationObjectID) + { + itemsToRemove.push_back(it); + } + } + + + //Remove the notifications + if(itemsToRemove.size()) + { + while(itemsToRemove.size()) + { + it = itemsToRemove.back(); + notification = it->second; + commChannel->removeItemFromHistory(notification); + instance.cancel(notification); + itemsToRemove.pop_back(); + } + //Trigger saving of notifications to xml once all have been removed + saveNotifications(); + } +} + + +bool LLDoNotDisturbNotificationStorage::onChannelChanged(const LLSD& pPayload) +{ + if (pPayload["sigtype"].asString() != "load") + { + mDirty = true; + } + + return false; +} diff --git a/indra/newview/lldonotdisturbnotificationstorage.h b/indra/newview/lldonotdisturbnotificationstorage.h index 260502b49b..6683646a9b 100644 --- a/indra/newview/lldonotdisturbnotificationstorage.h +++ b/indra/newview/lldonotdisturbnotificationstorage.h @@ -1,80 +1,80 @@ -/** -* @file lldonotdisturbnotificationstorage.h -* @brief Header file for lldonotdisturbnotificationstorage -* @author Stinson@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ -#ifndef LL_LLDONOTDISTURBNOTIFICATIONSTORAGE_H -#define LL_LLDONOTDISTURBNOTIFICATIONSTORAGE_H - -#include "llerror.h" -#include "lleventtimer.h" -#include "llnotifications.h" -#include "llnotificationstorage.h" -#include "llsingleton.h" - -class LLSD; - -class LLDoNotDisturbNotificationStorageTimer : public LLEventTimer -{ -public: - LLDoNotDisturbNotificationStorageTimer(); - ~LLDoNotDisturbNotificationStorageTimer(); - -public: - bool tick(); -}; - -class LLDoNotDisturbNotificationStorage : public LLParamSingleton, public LLNotificationStorage -{ - LLSINGLETON(LLDoNotDisturbNotificationStorage); - ~LLDoNotDisturbNotificationStorage(); - - LOG_CLASS(LLDoNotDisturbNotificationStorage); -public: - static const char * toastName; - static const char * offerName; - - bool getDirty(); - void resetDirty(); - void saveNotifications(); - void loadNotifications(); - void updateNotifications(); - void removeNotification(const char * name, const LLUUID& id); - void reset(); - -protected: - -private: - void initialize(); - - bool mDirty; - LLDoNotDisturbNotificationStorageTimer mTimer; - - LLNotificationChannelPtr getCommunicationChannel() const; - bool onChannelChanged(const LLSD& pPayload); - std::map nameToPayloadParameterMap; -}; - -#endif // LL_LLDONOTDISTURBNOTIFICATIONSTORAGE_H - +/** +* @file lldonotdisturbnotificationstorage.h +* @brief Header file for lldonotdisturbnotificationstorage +* @author Stinson@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LL_LLDONOTDISTURBNOTIFICATIONSTORAGE_H +#define LL_LLDONOTDISTURBNOTIFICATIONSTORAGE_H + +#include "llerror.h" +#include "lleventtimer.h" +#include "llnotifications.h" +#include "llnotificationstorage.h" +#include "llsingleton.h" + +class LLSD; + +class LLDoNotDisturbNotificationStorageTimer : public LLEventTimer +{ +public: + LLDoNotDisturbNotificationStorageTimer(); + ~LLDoNotDisturbNotificationStorageTimer(); + +public: + bool tick(); +}; + +class LLDoNotDisturbNotificationStorage : public LLParamSingleton, public LLNotificationStorage +{ + LLSINGLETON(LLDoNotDisturbNotificationStorage); + ~LLDoNotDisturbNotificationStorage(); + + LOG_CLASS(LLDoNotDisturbNotificationStorage); +public: + static const char * toastName; + static const char * offerName; + + bool getDirty(); + void resetDirty(); + void saveNotifications(); + void loadNotifications(); + void updateNotifications(); + void removeNotification(const char * name, const LLUUID& id); + void reset(); + +protected: + +private: + void initialize(); + + bool mDirty; + LLDoNotDisturbNotificationStorageTimer mTimer; + + LLNotificationChannelPtr getCommunicationChannel() const; + bool onChannelChanged(const LLSD& pPayload); + std::map nameToPayloadParameterMap; +}; + +#endif // LL_LLDONOTDISTURBNOTIFICATIONSTORAGE_H + diff --git a/indra/newview/lldrawable.cpp b/indra/newview/lldrawable.cpp index 1becd02ae7..ab7255d34e 100644 --- a/indra/newview/lldrawable.cpp +++ b/indra/newview/lldrawable.cpp @@ -1,1795 +1,1795 @@ -/** - * @file lldrawable.cpp - * @brief LLDrawable class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lldrawable.h" - -// library includes -#include "material_codes.h" - -// viewer includes -#include "llagent.h" -#include "llcriticaldamp.h" -#include "llface.h" -#include "lllightconstants.h" -#include "llmatrix4a.h" -#include "llsky.h" -#include "llsurfacepatch.h" -#include "llviewercamera.h" -#include "llviewerregion.h" -#include "llvolume.h" -#include "llvoavatar.h" -#include "llvovolume.h" -#include "llvosurfacepatch.h" // for debugging -#include "llworld.h" -#include "pipeline.h" -#include "llspatialpartition.h" -#include "llviewerobjectlist.h" -#include "llviewerwindow.h" -#include "llvocache.h" -#include "llcontrolavatar.h" -#include "lldrawpoolavatar.h" - -const F32 MIN_INTERPOLATE_DISTANCE_SQUARED = 0.001f * 0.001f; -const F32 MAX_INTERPOLATE_DISTANCE_SQUARED = 10.f * 10.f; -const F32 OBJECT_DAMPING_TIME_CONSTANT = 0.06f; - -extern bool gShiftFrame; - - -//////////////////////// -// -// Inline implementations. -// -// - - - -////////////////////////////// -// -// Drawable code -// -// - -// static -U32 LLDrawable::sNumZombieDrawables = 0; -F32 LLDrawable::sCurPixelAngle = 0; -std::vector > LLDrawable::sDeadList; - -#define FORCE_INVISIBLE_AREA 16.f - -// static -void LLDrawable::incrementVisible() -{ - LLViewerOctreeEntryData::incrementVisible(); - sCurPixelAngle = (F32) gViewerWindow->getWindowHeightRaw()/LLViewerCamera::getInstance()->getView(); -} - -LLDrawable::LLDrawable(LLViewerObject *vobj, bool new_entry) -: LLViewerOctreeEntryData(LLViewerOctreeEntry::LLDRAWABLE), - mVObjp(vobj) -{ - init(new_entry); -} - -void LLDrawable::init(bool new_entry) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE - - // mXform - mParent = NULL; - mRenderType = 0; - mCurrentScale = LLVector3(1,1,1); - mDistanceWRTCamera = 0.0f; - mState = 0; - - // mFaces - mRadius = 0.f; - mGeneration = -1; - mSpatialBridge = NULL; - - LLViewerOctreeEntry* entry = NULL; - LLVOCacheEntry* vo_entry = NULL; - if(!new_entry && mVObjp && getRegion() != NULL) - { - vo_entry = getRegion()->getCacheEntryForOctree(mVObjp->getLocalID()); - if(vo_entry) - { - entry = vo_entry->getEntry(); - } - } - setOctreeEntry(entry); - if(vo_entry) - { - if(!entry) - { - vo_entry->setOctreeEntry(mEntry); - } - - getRegion()->addActiveCacheEntry(vo_entry); - - if(vo_entry->getNumOfChildren() > 0) - { - getRegion()->addVisibleChildCacheEntry(vo_entry, NULL); //to load all children. - } - - llassert(!vo_entry->getGroup()); //not in the object cache octree. - } - - llassert(!vo_entry || vo_entry->getEntry() == mEntry); - - initVisible(sCurVisible - 2);//invisible for the current frame and the last frame. -} - -void LLDrawable::unload() -{ - LLVOVolume *pVVol = getVOVolume(); - pVVol->setNoLOD(); - pVVol->markForUpdate(); -} - -// static -void LLDrawable::initClass() -{ -} - - -void LLDrawable::destroy() -{ - if (gDebugGL) - { - gPipeline.checkReferences(this); - } - - if (isDead()) - { - sNumZombieDrawables--; - } - - // Attempt to catch violations of this in debug, - // knowing that some false alarms may result - // - llassert(!LLSpatialGroup::sNoDelete); - - /* cannot be guaranteed and causes crashes on false alarms - if (LLSpatialGroup::sNoDelete) - { - LL_ERRS() << "Illegal deletion of LLDrawable!" << LL_ENDL; - }*/ - - std::for_each(mFaces.begin(), mFaces.end(), DeletePointer()); - mFaces.clear(); - - - /*if (!(sNumZombieDrawables % 10)) - { - LL_INFOS() << "- Zombie drawables: " << sNumZombieDrawables << LL_ENDL; - }*/ - -} - -void LLDrawable::markDead() -{ - if (isDead()) - { - LL_WARNS() << "Warning! Marking dead multiple times!" << LL_ENDL; - return; - } - setState(DEAD); - - if (mSpatialBridge) - { - mSpatialBridge->markDead(); - mSpatialBridge = NULL; - } - - sNumZombieDrawables++; - - // We're dead. Free up all of our references to other objects - cleanupReferences(); -// sDeadList.push_back(this); -} - -LLVOVolume* LLDrawable::getVOVolume() const -{ - LLViewerObject* objectp = mVObjp; - if ( !isDead() && objectp && (objectp->getPCode() == LL_PCODE_VOLUME)) - { - return ((LLVOVolume*)objectp); - } - else - { - return NULL; - } -} - -const LLMatrix4& LLDrawable::getRenderMatrix() const -{ - return isRoot() ? getWorldMatrix() : getParent()->getWorldMatrix(); -} - -bool LLDrawable::isLight() const -{ - LLViewerObject* objectp = mVObjp; - if ( objectp && (objectp->getPCode() == LL_PCODE_VOLUME) && !isDead()) - { - return ((LLVOVolume*)objectp)->getIsLight(); - } - else - { - return false; - } -} - -void LLDrawable::cleanupReferences() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE; - - std::for_each(mFaces.begin(), mFaces.end(), DeletePointer()); - mFaces.clear(); - - gPipeline.unlinkDrawable(this); - - removeFromOctree(); - - // Cleanup references to other objects - mVObjp = NULL; - mParent = NULL; -} - -void LLDrawable::removeFromOctree() -{ - if(!mEntry) - { - return; - } - - mEntry->removeData(this); - if(mEntry->hasVOCacheEntry()) - { - getRegion()->removeActiveCacheEntry((LLVOCacheEntry*)mEntry->getVOCacheEntry(), this); - } - mEntry = NULL; -} - -void LLDrawable::cleanupDeadDrawables() -{ - /* - S32 i; - for (i = 0; i < sDeadList.size(); i++) - { - if (sDeadList[i]->getNumRefs() > 1) - { - LL_WARNS() << "Dead drawable has " << sDeadList[i]->getNumRefs() << " remaining refs" << LL_ENDL; - gPipeline.findReferences(sDeadList[i]); - } - } - */ - sDeadList.clear(); -} - -S32 LLDrawable::findReferences(LLDrawable *drawablep) -{ - S32 count = 0; - if (mParent == drawablep) - { - LL_INFOS() << this << ": parent reference" << LL_ENDL; - count++; - } - return count; -} - -LLFace* LLDrawable::addFace(LLFacePool *poolp, LLViewerTexture *texturep) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE - - LLFace *face; - { - face = new LLFace(this, mVObjp); - } - - if (!face) LL_ERRS() << "Allocating new Face: " << mFaces.size() << LL_ENDL; - - if (face) - { - mFaces.push_back(face); - - if (poolp) - { - face->setPool(poolp, texturep); - } - - if (isState(UNLIT)) - { - face->setState(LLFace::FULLBRIGHT); - } - } - return face; -} - -LLFace* LLDrawable::addFace(const LLTextureEntry *te, LLViewerTexture *texturep) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE - - LLFace *face; - - face = new LLFace(this, mVObjp); - - face->setTEOffset(mFaces.size()); - face->setTexture(texturep); - face->setPoolType(gPipeline.getPoolTypeFromTE(te, texturep)); - - mFaces.push_back(face); - - if (isState(UNLIT)) - { - face->setState(LLFace::FULLBRIGHT); - } - - return face; - -} - -LLFace* LLDrawable::addFace(const LLTextureEntry *te, LLViewerTexture *texturep, LLViewerTexture *normalp) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE - - LLFace *face; - face = new LLFace(this, mVObjp); - - face->setTEOffset(mFaces.size()); - face->setTexture(texturep); - face->setNormalMap(normalp); - face->setPoolType(gPipeline.getPoolTypeFromTE(te, texturep)); - - mFaces.push_back(face); - - if (isState(UNLIT)) - { - face->setState(LLFace::FULLBRIGHT); - } - - return face; - -} - -LLFace* LLDrawable::addFace(const LLTextureEntry *te, LLViewerTexture *texturep, LLViewerTexture *normalp, LLViewerTexture *specularp) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE - - LLFace *face; - face = new LLFace(this, mVObjp); - - face->setTEOffset(mFaces.size()); - face->setTexture(texturep); - face->setNormalMap(normalp); - face->setSpecularMap(specularp); - face->setPoolType(gPipeline.getPoolTypeFromTE(te, texturep)); - - mFaces.push_back(face); - - if (isState(UNLIT)) - { - face->setState(LLFace::FULLBRIGHT); - } - - return face; - -} - -void LLDrawable::setNumFaces(const S32 newFaces, LLFacePool *poolp, LLViewerTexture *texturep) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE - - if (newFaces == (S32)mFaces.size()) - { - return; - } - else if (newFaces < (S32)mFaces.size()) - { - std::for_each(mFaces.begin() + newFaces, mFaces.end(), DeletePointer()); - mFaces.erase(mFaces.begin() + newFaces, mFaces.end()); - } - else // (newFaces > mFaces.size()) - { - mFaces.reserve(newFaces); - for (int i = mFaces.size(); i= (S32)mFaces.size()/2) - { - return; - } - else if (newFaces < (S32)mFaces.size()) - { - std::for_each(mFaces.begin() + newFaces, mFaces.end(), DeletePointer()); - mFaces.erase(mFaces.begin() + newFaces, mFaces.end()); - } - else // (newFaces > mFaces.size()) - { - mFaces.reserve(newFaces); - for (int i = mFaces.size(); imFaces.size(); - - mFaces.reserve(face_count); - for (U32 i = 0; i < src->mFaces.size(); i++) - { - LLFace* facep = src->mFaces[i]; - facep->setDrawable(this); - mFaces.push_back(facep); - } - src->mFaces.clear(); -} - -void LLDrawable::deleteFaces(S32 offset, S32 count) -{ - face_list_t::iterator face_begin = mFaces.begin() + offset; - face_list_t::iterator face_end = face_begin + count; - - std::for_each(face_begin, face_end, DeletePointer()); - mFaces.erase(face_begin, face_end); -} - -void LLDrawable::update() -{ - LL_ERRS() << "Shouldn't be called!" << LL_ENDL; -} - - -void LLDrawable::updateMaterial() -{ -} - -void LLDrawable::makeActive() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE - -#if !LL_RELEASE_FOR_DOWNLOAD - if (mVObjp.notNull()) - { - U32 pcode = mVObjp->getPCode(); - if (pcode == LLViewerObject::LL_VO_WATER || - pcode == LLViewerObject::LL_VO_VOID_WATER || - pcode == LLViewerObject::LL_VO_SURFACE_PATCH || - pcode == LLViewerObject::LL_VO_PART_GROUP || - pcode == LLViewerObject::LL_VO_HUD_PART_GROUP || - pcode == LLViewerObject::LL_VO_SKY) - { - LL_ERRS() << "Static viewer object has active drawable!" << LL_ENDL; - } - } -#endif - - if (!isState(ACTIVE)) // && mGeneration > 0) - { - setState(ACTIVE); - - //parent must be made active first - if (!isRoot() && !mParent->isActive()) - { - mParent->makeActive(); - //NOTE: linked set will now NEVER become static - mParent->setState(LLDrawable::ACTIVE_CHILD); - } - - //all child objects must also be active - llassert_always(mVObjp); - - LLViewerObject::const_child_list_t& child_list = mVObjp->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); iter++) - { - LLViewerObject* child = *iter; - LLDrawable* drawable = child->mDrawable; - if (drawable) - { - drawable->makeActive(); - } - } - - if (mVObjp->getPCode() == LL_PCODE_VOLUME) - { - gPipeline.markRebuild(this, LLDrawable::REBUILD_VOLUME); - } - updatePartition(); - } - else if (!isRoot() && !mParent->isActive()) //this should not happen, but occasionally it does... - { - mParent->makeActive(); - //NOTE: linked set will now NEVER become static - mParent->setState(LLDrawable::ACTIVE_CHILD); - } - - llassert(isAvatar() || isRoot() || mParent->isActive()); -} - - -void LLDrawable::makeStatic(bool warning_enabled) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE - - if (isState(ACTIVE) && - !isState(ACTIVE_CHILD) && - !mVObjp->isAttachment() && - !mVObjp->isFlexible() && - !mVObjp->isAnimatedObject()) - { - clearState(ACTIVE | ANIMATED_CHILD); - - //drawable became static with active parent, not acceptable - llassert(mParent.isNull() || !mParent->isActive() || !warning_enabled); - - LLViewerObject::const_child_list_t& child_list = mVObjp->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); iter++) - { - LLViewerObject* child = *iter; - LLDrawable* child_drawable = child->mDrawable; - if (child_drawable) - { - if (child_drawable->getParent() != this) - { - LL_WARNS() << "Child drawable has unknown parent." << LL_ENDL; - } - child_drawable->makeStatic(warning_enabled); - } - } - - if (mVObjp->getPCode() == LL_PCODE_VOLUME) - { - gPipeline.markRebuild(this, LLDrawable::REBUILD_VOLUME); - } - - if (mSpatialBridge) - { - mSpatialBridge->markDead(); - setSpatialBridge(NULL); - } - updatePartition(); - } - - llassert(isAvatar() || isRoot() || mParent->isStatic()); -} - -// Returns "distance" between target destination and resulting xfrom -F32 LLDrawable::updateXform(bool undamped) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE - - bool damped = !undamped; - - // Position - const LLVector3 old_pos(mXform.getPosition()); - LLVector3 target_pos; - if (mXform.isRoot()) - { - // get root position in your agent's region - target_pos = mVObjp->getPositionAgent(); - } - else - { - // parent-relative position - target_pos = mVObjp->getPosition(); - } - - // Rotation - const LLQuaternion old_rot(mXform.getRotation()); - LLQuaternion target_rot = mVObjp->getRotation(); - //scaling - LLVector3 target_scale = mVObjp->getScale(); - LLVector3 old_scale = mCurrentScale; - - // Damping - F32 dist_squared = 0.f; - F32 camdist2 = (mDistanceWRTCamera * mDistanceWRTCamera); - - if (damped && isVisible()) - { - F32 lerp_amt = llclamp(LLSmoothInterpolation::getInterpolant(OBJECT_DAMPING_TIME_CONSTANT), 0.f, 1.f); - LLVector3 new_pos = lerp(old_pos, target_pos, lerp_amt); - dist_squared = dist_vec_squared(new_pos, target_pos); - - LLQuaternion new_rot = nlerp(lerp_amt, old_rot, target_rot); - // FIXME: This can be negative! It is be possible for some rots to 'cancel out' pos or size changes. - dist_squared += (1.f - dot(new_rot, target_rot)) * 10.f; - - LLVector3 new_scale = lerp(old_scale, target_scale, lerp_amt); - dist_squared += dist_vec_squared(new_scale, target_scale); - - if ((dist_squared >= MIN_INTERPOLATE_DISTANCE_SQUARED * camdist2) && - (dist_squared <= MAX_INTERPOLATE_DISTANCE_SQUARED)) - { - // interpolate - target_pos = new_pos; - target_rot = new_rot; - target_scale = new_scale; - } - else if (mVObjp->getAngularVelocity().isExactlyZero()) - { - // snap to final position (only if no target omega is applied) - dist_squared = 0.0f; - //set target scale here, because of dist_squared = 0.0f remove object from move list - mCurrentScale = target_scale; - - if (getVOVolume() && !isRoot()) - { //child prim snapping to some position, needs a rebuild - gPipeline.markRebuild(this, LLDrawable::REBUILD_POSITION); - } - } - } - else - { - // The following fixes MAINT-1742 but breaks vehicles similar to MAINT-2275 - // dist_squared = dist_vec_squared(old_pos, target_pos); - - // The following fixes MAINT-2247 but causes MAINT-2275 - //dist_squared += (1.f - dot(old_rot, target_rot)) * 10.f; - //dist_squared += dist_vec_squared(old_scale, target_scale); - } - - const LLVector3 vec = mCurrentScale-target_scale; - - //It's a very important on each cycle on Drawable::update form(), when object remained in move - //, list update the CurrentScale member, because if do not do that, it remained in this list forever - //or when the delta time between two frames a become a sufficiently large (due to interpolation) - //for overcome the MIN_INTERPOLATE_DISTANCE_SQUARED. - mCurrentScale = target_scale; - - if (vec*vec > MIN_INTERPOLATE_DISTANCE_SQUARED) - { //scale change requires immediate rebuild - gPipeline.markRebuild(this, LLDrawable::REBUILD_POSITION); - } - else if (!isRoot() && - (!mVObjp->getAngularVelocity().isExactlyZero() || - dist_squared > 0.f)) - { //child prim moving relative to parent, tag as needing to be rendered atomically and rebuild - dist_squared = 1.f; //keep this object on the move list - if (!isState(LLDrawable::ANIMATED_CHILD)) - { - setState(LLDrawable::ANIMATED_CHILD); - gPipeline.markRebuild(this, LLDrawable::REBUILD_ALL); - mVObjp->dirtySpatialGroup(); - } - } - else if (!isRoot() && - ((dist_vec_squared(old_pos, target_pos) > 0.f) - || (1.f - dot(old_rot, target_rot)) > 0.f)) - { //fix for BUG-840, MAINT-2275, MAINT-1742, MAINT-2247 - mVObjp->shrinkWrap(); - gPipeline.markRebuild(this, LLDrawable::REBUILD_POSITION); - } - else if (!getVOVolume() && !isAvatar()) - { - movePartition(); - } - - // Update - mXform.setPosition(target_pos); - mXform.setRotation(target_rot); - mXform.setScale(LLVector3(1,1,1)); //no scale in drawable transforms (IT'S A RULE!) - mXform.updateMatrix(); - if (isRoot() && mVObjp->isAnimatedObject() && mVObjp->getControlAvatar()) - { - mVObjp->getControlAvatar()->matchVolumeTransform(); - } - - if (mSpatialBridge) - { - gPipeline.markMoved(mSpatialBridge, false); - } - return dist_squared; -} - -void LLDrawable::setRadius(F32 radius) -{ - if (mRadius != radius) - { - mRadius = radius; - } -} - -void LLDrawable::moveUpdatePipeline(bool moved) -{ - if (moved) - { - makeActive(); - } - - // Update the face centers. - for (S32 i = 0; i < getNumFaces(); i++) - { - LLFace* face = getFace(i); - if (face) - { - face->updateCenterAgent(); - } - } -} - -void LLDrawable::movePartition() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE - - LLSpatialPartition* part = getSpatialPartition(); - if (part) - { - part->move(this, getSpatialGroup()); - } -} - -bool LLDrawable::updateMove() -{ - if (isDead()) - { - LL_WARNS() << "Update move on dead drawable!" << LL_ENDL; - return true; - } - - if (mVObjp.isNull()) - { - return false; - } - - makeActive(); - - return isState(MOVE_UNDAMPED) ? updateMoveUndamped() : updateMoveDamped(); -} - -bool LLDrawable::updateMoveUndamped() -{ - F32 dist_squared = updateXform(true); - - mGeneration++; - - if (!isState(LLDrawable::INVISIBLE)) - { - bool moved = (dist_squared > 0.001f && dist_squared < 255.99f); - moveUpdatePipeline(moved); - mVObjp->updateText(); - } - - mVObjp->clearChanged(LLXform::MOVED); - return true; -} - -void LLDrawable::updatePartition() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE - - if (!getVOVolume()) - { - movePartition(); - } - else if (mSpatialBridge) - { - gPipeline.markMoved(mSpatialBridge, false); - } - else - { - //a child prim moved and needs its verts regenerated - gPipeline.markRebuild(this, LLDrawable::REBUILD_POSITION); - } -} - -bool LLDrawable::updateMoveDamped() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE - - F32 dist_squared = updateXform(false); - - mGeneration++; - - if (!isState(LLDrawable::INVISIBLE)) - { - bool moved = (dist_squared > 0.001f && dist_squared < 128.0f); - moveUpdatePipeline(moved); - mVObjp->updateText(); - } - - bool done_moving = dist_squared == 0.0f; - - if (done_moving) - { - mVObjp->clearChanged(LLXform::MOVED); - } - - return done_moving; -} - -void LLDrawable::updateDistance(LLCamera& camera, bool force_update) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE - - if (LLViewerCamera::sCurCameraID != LLViewerCamera::CAMERA_WORLD) - { - LL_WARNS() << "Attempted to update distance for non-world camera." << LL_ENDL; - return; - } - - if (gShiftFrame) - { - return; - } - - //switch LOD with the spatial group to avoid artifacts - //LLSpatialGroup* sg = getSpatialGroup(); - - LLVector3 pos; - - //if (!sg || sg->changeLOD()) - { - LLVOVolume* volume = getVOVolume(); - if (volume) - { - if (getGroup()) - { - pos.set(getPositionGroup().getF32ptr()); - } - else - { - pos = getPositionAgent(); - } - - if (isState(LLDrawable::HAS_ALPHA)) - { - for (S32 i = 0; i < getNumFaces(); i++) - { - LLFace* facep = getFace(i); - if (facep && - (force_update || facep->isInAlphaPool())) - { - LLVector4a box; - box.setSub(facep->mExtents[1], facep->mExtents[0]); - box.mul(0.25f); - LLVector3 v = (facep->mCenterLocal-camera.getOrigin()); - const LLVector3& at = camera.getAtAxis(); - for (U32 j = 0; j < 3; j++) - { - v.mV[j] -= box[j] * at.mV[j]; - } - facep->mDistance = v * camera.getAtAxis(); - } - } - } - - - // MAINT-7926 Handle volumes in an animated object as a special case - // SL-937: add dynamic box handling for rigged mesh on regular avatars. - //if (volume->getAvatar() && volume->getAvatar()->isControlAvatar()) - if (volume->getAvatar()) - { - const LLVector3* av_box = volume->getAvatar()->getLastAnimExtents(); - LLVector3 cam_pos_from_agent = LLViewerCamera::getInstance()->getOrigin(); - LLVector3 cam_to_box_offset = point_to_box_offset(cam_pos_from_agent, av_box); - mDistanceWRTCamera = llmax(0.01f, ll_round(cam_to_box_offset.magVec(), 0.01f)); - mVObjp->updateLOD(); - return; - } - } - else - { - pos = LLVector3(getPositionGroup().getF32ptr()); - } - - pos -= camera.getOrigin(); - mDistanceWRTCamera = ll_round(pos.magVec(), 0.01f); - mVObjp->updateLOD(); - } -} - -void LLDrawable::updateTexture() -{ - if (isDead()) - { - LL_WARNS() << "Dead drawable updating texture!" << LL_ENDL; - return; - } - - if (getNumFaces() != mVObjp->getNumTEs()) - { //drawable is transitioning its face count - return; - } - - if (getVOVolume()) - { - gPipeline.markRebuild(this, LLDrawable::REBUILD_MATERIAL); - } -} - -bool LLDrawable::updateGeometry() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE - - llassert(mVObjp.notNull()); - bool res = mVObjp && mVObjp->updateGeometry(this); - return res; -} - -void LLDrawable::shiftPos(const LLVector4a &shift_vector) -{ - if (isDead()) - { - LL_WARNS() << "Shifting dead drawable" << LL_ENDL; - return; - } - - if (mParent) - { - mXform.setPosition(mVObjp->getPosition()); - } - else - { - mXform.setPosition(mVObjp->getPositionAgent()); - } - - mXform.updateMatrix(); - - if (isStatic()) - { - LLVOVolume* volume = getVOVolume(); - - bool rebuild = (!volume && - getRenderType() != LLPipeline::RENDER_TYPE_TREE && - getRenderType() != LLPipeline::RENDER_TYPE_TERRAIN && - getRenderType() != LLPipeline::RENDER_TYPE_SKY); - - if (rebuild) - { - gPipeline.markRebuild(this, LLDrawable::REBUILD_ALL); - } - - for (S32 i = 0; i < getNumFaces(); i++) - { - LLFace *facep = getFace(i); - if (facep) - { - facep->mCenterAgent += LLVector3(shift_vector.getF32ptr()); - facep->mExtents[0].add(shift_vector); - facep->mExtents[1].add(shift_vector); - - if (rebuild && facep->hasGeometry()) - { - facep->clearVertexBuffer(); - } - } - } - - shift(shift_vector); - } - else if (mSpatialBridge) - { - mSpatialBridge->shiftPos(shift_vector); - } - else if (isAvatar()) - { - shift(shift_vector); - } - - mVObjp->onShift(shift_vector); -} - -const LLVector3& LLDrawable::getBounds(LLVector3& min, LLVector3& max) const -{ - mXform.getMinMax(min,max); - return mXform.getPositionW(); -} - -void LLDrawable::updateSpatialExtents() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE - - if (mVObjp) - { - const LLVector4a* exts = getSpatialExtents(); - LLVector4a extents[2] = { exts[0], exts[1] }; - - mVObjp->updateSpatialExtents(extents[0], extents[1]); - setSpatialExtents(extents[0], extents[1]); - } - - updateBinRadius(); - - if (mSpatialBridge.notNull()) - { - getGroupPosition().splat(0.f); - } -} - - -void LLDrawable::updateBinRadius() -{ - if (mVObjp.notNull()) - { - setBinRadius(llmin(mVObjp->getBinRadius(), 256.f)); - } - else - { - setBinRadius(llmin(getRadius()*4.f, 256.f)); - } -} - -void LLDrawable::updateSpecialHoverCursor(bool enabled) -{ - // TODO: maintain a list of objects that have special - // hover cursors, then use that list for per-frame - // hover cursor selection. JC -} - -F32 LLDrawable::getVisibilityRadius() const -{ - if (isDead()) - { - return 0.f; - } - else if (isLight()) - { - const LLVOVolume *vov = getVOVolume(); - if (vov) - { - return llmax(getRadius(), vov->getLightRadius()); - } else { - // LL_WARNS() ? - } - } - return getRadius(); -} - -void LLDrawable::updateUVMinMax() -{ -} - -//virtual -bool LLDrawable::isVisible() const -{ - if (LLViewerOctreeEntryData::isVisible()) - { - return true; - } - - { - LLViewerOctreeGroup* group = mEntry->getGroup(); - if (group && group->isVisible()) - { - LLViewerOctreeEntryData::setVisible(); - return true; - } - } - - return false; -} - -//virtual -bool LLDrawable::isRecentlyVisible() const -{ - //currently visible or visible in the previous frame. - bool vis = LLViewerOctreeEntryData::isRecentlyVisible(); - - if(!vis) - { - const U32 MIN_VIS_FRAME_RANGE = 2 ; //two frames:the current one and the last one. - vis = (sCurVisible - getVisible() < MIN_VIS_FRAME_RANGE); - } - - return vis ; -} - -void LLDrawable::setGroup(LLViewerOctreeGroup *groupp) - { - LLSpatialGroup* cur_groupp = (LLSpatialGroup*)getGroup(); - - //precondition: mGroupp MUST be null or DEAD or mGroupp MUST NOT contain this - //llassert(!cur_groupp || cur_groupp->isDead() || !cur_groupp->hasElement(this)); - - //precondition: groupp MUST be null or groupp MUST contain this - llassert(!groupp || (LLSpatialGroup*)groupp->hasElement(this)); - - if (cur_groupp != groupp && getVOVolume()) - { - //NULL out vertex buffer references for volumes on spatial group change to maintain - //requirement that every face vertex buffer is either NULL or points to a vertex buffer - //contained by its drawable's spatial group - for (S32 i = 0; i < getNumFaces(); ++i) - { - LLFace* facep = getFace(i); - if (facep) - { - facep->clearVertexBuffer(); - } - } - } - - //postcondition: if next group is NULL, previous group must be dead OR NULL OR binIndex must be -1 - //postcondition: if next group is NOT NULL, binIndex must not be -1 - //llassert(groupp == NULL ? (cur_groupp == NULL || cur_groupp->isDead()) || (!getEntry() || getEntry()->getBinIndex() == -1) : - // (getEntry() && getEntry()->getBinIndex() != -1)); - - LLViewerOctreeEntryData::setGroup(groupp); -} - -/* -* Get the SpatialPartition this Drawable should use. -* Checks current SpatialPartition assignment and corrects if it is invalid. -*/ -LLSpatialPartition* LLDrawable::getSpatialPartition() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE - - LLSpatialPartition* retval = NULL; - - if (!mVObjp || - !getVOVolume() || - isStatic()) - { - retval = gPipeline.getSpatialPartition((LLViewerObject*)mVObjp); - } - else if (isRoot()) - { - // determine if the spatial bridge has changed - if (mSpatialBridge) - { - U32 partition_type = mSpatialBridge->asPartition()->mPartitionType; - bool is_hud = mVObjp->isHUDAttachment(); - bool is_animesh = mVObjp->isAnimatedObject() && mVObjp->getControlAvatar() != NULL; - bool is_attachment = mVObjp->isAttachment() && !is_hud && !is_animesh; - if ((partition_type == LLViewerRegion::PARTITION_HUD) != is_hud) - { - // Was/became HUD - // remove obsolete bridge - mSpatialBridge->markDead(); - setSpatialBridge(NULL); - } - else if ((partition_type == LLViewerRegion::PARTITION_CONTROL_AV) != is_animesh) - { - // Was/became part of animesh - // remove obsolete bridge - mSpatialBridge->markDead(); - setSpatialBridge(NULL); - } - else if ((partition_type == LLViewerRegion::PARTITION_AVATAR) != is_attachment) - { - // Was/became part of avatar - // remove obsolete bridge - mSpatialBridge->markDead(); - setSpatialBridge(NULL); - } - } - //must be an active volume - if (!mSpatialBridge) - { - if (mVObjp->isHUDAttachment()) - { - setSpatialBridge(new LLHUDBridge(this, getRegion())); - } - else if (mVObjp->isAnimatedObject() && mVObjp->getControlAvatar()) - { - setSpatialBridge(new LLControlAVBridge(this, getRegion())); - mVObjp->getControlAvatar()->mControlAVBridge = (LLControlAVBridge*)getSpatialBridge(); - } - // check HUD first, because HUD is also attachment - else if (mVObjp->isAttachment()) - { - // Attachment - // Use AvatarBridge of root object in attachment linkset - setSpatialBridge(new LLAvatarBridge(this, getRegion())); - } - else - { - // Moving linkset, use VolumeBridge of root object in linkset - setSpatialBridge(new LLVolumeBridge(this, getRegion())); - } - } - return mSpatialBridge->asPartition(); - } - else - { - retval = getParent()->getSpatialPartition(); - } - - if (retval && mSpatialBridge.notNull()) - { - mSpatialBridge->markDead(); - setSpatialBridge(NULL); - } - - return retval; -} - -//======================================= -// Spatial Partition Bridging Drawable -//======================================= - -LLSpatialBridge::LLSpatialBridge(LLDrawable* root, bool render_by_group, U32 data_mask, LLViewerRegion* regionp) : - LLDrawable(root->getVObj(), true), - LLSpatialPartition(data_mask, render_by_group, regionp) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE; - mOcclusionEnabled = false; - mBridge = this; - mDrawable = root; - root->setSpatialBridge(this); - - mRenderType = mDrawable->mRenderType; - mDrawableType = mDrawable->mRenderType; - - mPartitionType = LLViewerRegion::PARTITION_VOLUME; - - mOctree->balance(); - - llassert(mDrawable); - llassert(mDrawable->getRegion()); - LLSpatialPartition *part = mDrawable->getRegion()->getSpatialPartition(mPartitionType); - llassert(part); - - if (part) - { - part->put(this); - } -} - -LLSpatialBridge::~LLSpatialBridge() -{ - if(mEntry) - { - LLSpatialGroup* group = getSpatialGroup(); - if (group) - { - group->getSpatialPartition()->remove(this, group); - } - } - - //delete octree here so listeners will still be able to access bridge specific state - destroyTree(); -} - -void LLSpatialBridge::destroyTree() -{ - delete mOctree; - mOctree = NULL; -} - -void LLSpatialBridge::updateSpatialExtents() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE - - LLSpatialGroup* root = (LLSpatialGroup*) mOctree->getListener(0); - - root->rebound(); - - const LLVector4a* root_bounds = root->getBounds(); - LLVector4a offset; - LLVector4a size = root_bounds[1]; - - //VECTORIZE THIS - LLMatrix4a mat; - mat.loadu(mDrawable->getXform()->getWorldMatrix()); - - LLVector4a t; - t.splat(0.f); - - LLVector4a center; - mat.affineTransform(t, center); - - mat.rotate(root_bounds[0], offset); - center.add(offset); - - LLVector4a v[4]; - - //get 4 corners of bounding box - mat.rotate(size,v[0]); - - LLVector4a scale; - - scale.set(-1.f, -1.f, 1.f); - scale.mul(size); - mat.rotate(scale, v[1]); - - scale.set(1.f, -1.f, -1.f); - scale.mul(size); - mat.rotate(scale, v[2]); - - scale.set(-1.f, 1.f, -1.f); - scale.mul(size); - mat.rotate(scale, v[3]); - - LLVector4a newMin; - LLVector4a newMax; - newMin = newMax = center; - for (U32 i = 0; i < 4; i++) - { - LLVector4a delta; - delta.setAbs(v[i]); - LLVector4a min; - min.setSub(center, delta); - LLVector4a max; - max.setAdd(center, delta); - - newMin.setMin(newMin, min); - newMax.setMax(newMax, max); - } - setSpatialExtents(newMin, newMax); - - LLVector4a diagonal; - diagonal.setSub(newMax, newMin); - mRadius = diagonal.getLength3().getF32() * 0.5f; - - LLVector4a& pos = getGroupPosition(); - pos.setAdd(newMin,newMax); - pos.mul(0.5f); - updateBinRadius(); -} - -void LLSpatialBridge::updateBinRadius() -{ - setBinRadius(llmin( mOctree->getSize()[0]*0.5f, 256.f)); -} - -LLCamera LLSpatialBridge::transformCamera(LLCamera& camera) -{ - LLCamera ret = camera; - LLXformMatrix* mat = mDrawable->getXform(); - LLVector3 center = LLVector3(0,0,0) * mat->getWorldMatrix(); - - LLVector3 delta = ret.getOrigin() - center; - LLQuaternion rot = ~mat->getRotation(); - - delta *= rot; - LLVector3 lookAt = ret.getAtAxis(); - LLVector3 up_axis = ret.getUpAxis(); - LLVector3 left_axis = ret.getLeftAxis(); - - lookAt *= rot; - up_axis *= rot; - left_axis *= rot; - - if (!delta.isFinite()) - { - delta.clearVec(); - } - - ret.setOrigin(delta); - ret.setAxes(lookAt, left_axis, up_axis); - - return ret; -} - -void LLSpatialBridge::transformExtents(const LLVector4a* src, LLVector4a* dst) -{ - LLMatrix4 mat = mDrawable->getXform()->getWorldMatrix(); - mat.invert(); - - LLMatrix4a world_to_bridge(mat); - - matMulBoundBox(world_to_bridge, src, dst); -} - - -void LLDrawable::setVisible(LLCamera& camera, std::vector* results, bool for_select) -{ - LLViewerOctreeEntryData::setVisible(); - -#if 0 && !LL_RELEASE_FOR_DOWNLOAD - //crazy paranoid rules checking - if (getVOVolume()) - { - if (!isRoot()) - { - if (isActive() && !mParent->isActive()) - { - LL_ERRS() << "Active drawable has static parent!" << LL_ENDL; - } - - if (isStatic() && !mParent->isStatic()) - { - LL_ERRS() << "Static drawable has active parent!" << LL_ENDL; - } - - if (mSpatialBridge) - { - LL_ERRS() << "Child drawable has spatial bridge!" << LL_ENDL; - } - } - else if (isActive() && !mSpatialBridge) - { - LL_ERRS() << "Active root drawable has no spatial bridge!" << LL_ENDL; - } - else if (isStatic() && mSpatialBridge.notNull()) - { - LL_ERRS() << "Static drawable has spatial bridge!" << LL_ENDL; - } - } -#endif -} - -class LLOctreeMarkNotCulled: public OctreeTraveler -{ -public: - LLCamera* mCamera; - - LLOctreeMarkNotCulled(LLCamera* camera_in) : mCamera(camera_in) { } - - virtual void traverse(const OctreeNode* node) - { - LLSpatialGroup* group = (LLSpatialGroup*) node->getListener(0); - group->setVisible(); - OctreeTraveler::traverse(node); - } - - void visit(const OctreeNode* branch) - { - gPipeline.markNotCulled((LLSpatialGroup*) branch->getListener(0), *mCamera); - } -}; - -void LLSpatialBridge::setVisible(LLCamera& camera_in, std::vector* results, bool for_select) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE - - if (!gPipeline.hasRenderType(mDrawableType)) - { - return; - } - - - //HACK don't draw attachments for avatars that haven't been visible in more than a frame - LLViewerObject *vobj = mDrawable->getVObj(); - if (vobj && vobj->isAttachment() && !vobj->isHUDAttachment()) - { - LLDrawable* av; - LLDrawable* parent = mDrawable->getParent(); - - if (parent) - { - LLViewerObject* objparent = parent->getVObj(); - av = objparent->mDrawable; - LLSpatialGroup* group = av->getSpatialGroup(); - - bool impostor = false; - bool loaded = false; - if (objparent->isAvatar()) - { - LLVOAvatar* avatarp = (LLVOAvatar*) objparent; - if (avatarp->isVisible()) - { - impostor = objparent->isAvatar() && !LLPipeline::sImpostorRender && ((LLVOAvatar*) objparent)->isImpostor(); - loaded = objparent->isAvatar() && ((LLVOAvatar*) objparent)->isFullyLoaded(); - } - else - { - return; - } - } - - if (!group || - LLDrawable::getCurrentFrame() - av->getVisible() > 1 || - impostor || - !loaded) - { - return; - } - } - } - - - LLSpatialGroup* group = (LLSpatialGroup*) mOctree->getListener(0); - group->rebound(); - - LLVector4a center; - const LLVector4a* exts = getSpatialExtents(); - center.setAdd(exts[0], exts[1]); - center.mul(0.5f); - LLVector4a size; - size.setSub(exts[1], exts[0]); - size.mul(0.5f); - - if ((LLPipeline::sShadowRender && camera_in.AABBInFrustum(center, size)) || - LLPipeline::sImpostorRender || - (camera_in.AABBInFrustumNoFarClip(center, size) && - AABBSphereIntersect(exts[0], exts[1], camera_in.getOrigin(), camera_in.mFrustumCornerDist))) - { - if (!LLPipeline::sImpostorRender && - !LLPipeline::sShadowRender && - LLPipeline::calcPixelArea(center, size, camera_in) < FORCE_INVISIBLE_AREA) - { - return; - } - - LLDrawable::setVisible(camera_in); - - if (for_select) - { - results->push_back(mDrawable); - if (mDrawable->getVObj()) - { - LLViewerObject::const_child_list_t& child_list = mDrawable->getVObj()->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); iter++) - { - LLViewerObject* child = *iter; - LLDrawable* drawable = child->mDrawable; - results->push_back(drawable); - } - } - } - else - { - LLCamera trans_camera = transformCamera(camera_in); - LLOctreeMarkNotCulled culler(&trans_camera); - culler.traverse(mOctree); - } - } -} - -void LLSpatialBridge::updateDistance(LLCamera& camera_in, bool force_update) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE - - if (mDrawable == NULL) - { - markDead(); - return; - } - - if (gShiftFrame) - { - return; - } - - if (mDrawable->getVObj()) - { - // Don't update if we are part of impostor, unles it's an impostor pass - if (!LLPipeline::sImpostorRender && mDrawable->getVObj()->isAttachment()) - { - LLDrawable* parent = mDrawable->getParent(); - if (parent && parent->getVObj()) - { - LLVOAvatar* av = parent->getVObj()->asAvatar(); - if (av && av->isImpostor()) - { - return; - } - } - } - - LLCamera camera = transformCamera(camera_in); - - mDrawable->updateDistance(camera, force_update); - - LLViewerObject::const_child_list_t& child_list = mDrawable->getVObj()->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); iter++) - { - LLViewerObject* child = *iter; - LLDrawable* drawable = child->mDrawable; - if (!drawable) - { - continue; - } - - if (!drawable->isAvatar()) - { - drawable->updateDistance(camera, force_update); - } - } - } -} - -void LLSpatialBridge::makeActive() -{ //it is an error to make a spatial bridge active (it's already active) - LL_ERRS() << "makeActive called on spatial bridge" << LL_ENDL; -} - -void LLSpatialBridge::move(LLDrawable *drawablep, LLSpatialGroup *curp, bool immediate) -{ - LLSpatialPartition::move(drawablep, curp, immediate); - gPipeline.markMoved(this, false); -} - -bool LLSpatialBridge::updateMove() -{ - llassert_always(mDrawable); - llassert_always(mDrawable->mVObjp); - llassert_always(mDrawable->getRegion()); - LLSpatialPartition* part = mDrawable->getRegion()->getSpatialPartition(mPartitionType); - llassert_always(part); - - mOctree->balance(); - if (part) - { - part->move(this, getSpatialGroup(), true); - } - return true; -} - -void LLSpatialBridge::shiftPos(const LLVector4a& vec) -{ - LLDrawable::shift(vec); -} - -void LLSpatialBridge::cleanupReferences() -{ - LLDrawable::cleanupReferences(); - if (mDrawable) - { - mDrawable->setGroup(NULL); - - if (mDrawable->getVObj()) - { - LLViewerObject::const_child_list_t& child_list = mDrawable->getVObj()->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); iter++) - { - LLViewerObject* child = *iter; - LLDrawable* drawable = child->mDrawable; - if (drawable) - { - drawable->setGroup(NULL); - } - } - } - - LLDrawable* drawablep = mDrawable; - mDrawable = NULL; - drawablep->setSpatialBridge(NULL); - } -} - -const LLVector3 LLDrawable::getPositionAgent() const -{ - if (getVOVolume()) - { - if (isActive()) - { - LLVector3 pos(0,0,0); - if (!isRoot()) - { - pos = mVObjp->getPosition(); - } - return pos * getRenderMatrix(); - } - else - { - return mVObjp->getPositionAgent(); - } - } - else - { - return getWorldPosition(); - } -} - -bool LLDrawable::isAnimating() const -{ - if (!getVObj()) - { - return true; - } - - if (getScale() != mVObjp->getScale()) - { - return true; - } - - if (mVObjp->getPCode() == LLViewerObject::LL_VO_PART_GROUP) - { - return true; - } - if (mVObjp->getPCode() == LLViewerObject::LL_VO_HUD_PART_GROUP) - { - return true; - } - - /*if (!isRoot() && !mVObjp->getAngularVelocity().isExactlyZero()) - { //target omega - return true; - }*/ - - return false; -} - -void LLDrawable::updateFaceSize(S32 idx) -{ - if (mVObjp.notNull()) - { - mVObjp->updateFaceSize(idx); - } -} - -LLDrawable* LLDrawable::getRoot() -{ - LLDrawable* ret = this; - while (!ret->isRoot()) - { - ret = ret->getParent(); - } - - return ret; -} - -LLBridgePartition::LLBridgePartition(LLViewerRegion* regionp) -: LLSpatialPartition(0, false, regionp) -{ - mDrawableType = LLPipeline::RENDER_TYPE_VOLUME; - mPartitionType = LLViewerRegion::PARTITION_BRIDGE; - mLODPeriod = 16; - mSlopRatio = 0.25f; -} - -LLAvatarPartition::LLAvatarPartition(LLViewerRegion* regionp) - : LLBridgePartition(regionp) -{ - mDrawableType = LLPipeline::RENDER_TYPE_AVATAR; - mPartitionType = LLViewerRegion::PARTITION_AVATAR; -} - -LLControlAVPartition::LLControlAVPartition(LLViewerRegion* regionp) - : LLBridgePartition(regionp) -{ - mDrawableType = LLPipeline::RENDER_TYPE_CONTROL_AV; - mPartitionType = LLViewerRegion::PARTITION_CONTROL_AV; -} - -LLHUDBridge::LLHUDBridge(LLDrawable* drawablep, LLViewerRegion* regionp) -: LLVolumeBridge(drawablep, regionp) -{ - mDrawableType = LLPipeline::RENDER_TYPE_HUD; - mPartitionType = LLViewerRegion::PARTITION_HUD; - mSlopRatio = 0.0f; -} - -F32 LLHUDBridge::calcPixelArea(LLSpatialGroup* group, LLCamera& camera) -{ - return 1024.f; -} - - -void LLHUDBridge::shiftPos(const LLVector4a& vec) -{ - //don't shift hud bridges on region crossing -} - +/** + * @file lldrawable.cpp + * @brief LLDrawable class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lldrawable.h" + +// library includes +#include "material_codes.h" + +// viewer includes +#include "llagent.h" +#include "llcriticaldamp.h" +#include "llface.h" +#include "lllightconstants.h" +#include "llmatrix4a.h" +#include "llsky.h" +#include "llsurfacepatch.h" +#include "llviewercamera.h" +#include "llviewerregion.h" +#include "llvolume.h" +#include "llvoavatar.h" +#include "llvovolume.h" +#include "llvosurfacepatch.h" // for debugging +#include "llworld.h" +#include "pipeline.h" +#include "llspatialpartition.h" +#include "llviewerobjectlist.h" +#include "llviewerwindow.h" +#include "llvocache.h" +#include "llcontrolavatar.h" +#include "lldrawpoolavatar.h" + +const F32 MIN_INTERPOLATE_DISTANCE_SQUARED = 0.001f * 0.001f; +const F32 MAX_INTERPOLATE_DISTANCE_SQUARED = 10.f * 10.f; +const F32 OBJECT_DAMPING_TIME_CONSTANT = 0.06f; + +extern bool gShiftFrame; + + +//////////////////////// +// +// Inline implementations. +// +// + + + +////////////////////////////// +// +// Drawable code +// +// + +// static +U32 LLDrawable::sNumZombieDrawables = 0; +F32 LLDrawable::sCurPixelAngle = 0; +std::vector > LLDrawable::sDeadList; + +#define FORCE_INVISIBLE_AREA 16.f + +// static +void LLDrawable::incrementVisible() +{ + LLViewerOctreeEntryData::incrementVisible(); + sCurPixelAngle = (F32) gViewerWindow->getWindowHeightRaw()/LLViewerCamera::getInstance()->getView(); +} + +LLDrawable::LLDrawable(LLViewerObject *vobj, bool new_entry) +: LLViewerOctreeEntryData(LLViewerOctreeEntry::LLDRAWABLE), + mVObjp(vobj) +{ + init(new_entry); +} + +void LLDrawable::init(bool new_entry) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE + + // mXform + mParent = NULL; + mRenderType = 0; + mCurrentScale = LLVector3(1,1,1); + mDistanceWRTCamera = 0.0f; + mState = 0; + + // mFaces + mRadius = 0.f; + mGeneration = -1; + mSpatialBridge = NULL; + + LLViewerOctreeEntry* entry = NULL; + LLVOCacheEntry* vo_entry = NULL; + if(!new_entry && mVObjp && getRegion() != NULL) + { + vo_entry = getRegion()->getCacheEntryForOctree(mVObjp->getLocalID()); + if(vo_entry) + { + entry = vo_entry->getEntry(); + } + } + setOctreeEntry(entry); + if(vo_entry) + { + if(!entry) + { + vo_entry->setOctreeEntry(mEntry); + } + + getRegion()->addActiveCacheEntry(vo_entry); + + if(vo_entry->getNumOfChildren() > 0) + { + getRegion()->addVisibleChildCacheEntry(vo_entry, NULL); //to load all children. + } + + llassert(!vo_entry->getGroup()); //not in the object cache octree. + } + + llassert(!vo_entry || vo_entry->getEntry() == mEntry); + + initVisible(sCurVisible - 2);//invisible for the current frame and the last frame. +} + +void LLDrawable::unload() +{ + LLVOVolume *pVVol = getVOVolume(); + pVVol->setNoLOD(); + pVVol->markForUpdate(); +} + +// static +void LLDrawable::initClass() +{ +} + + +void LLDrawable::destroy() +{ + if (gDebugGL) + { + gPipeline.checkReferences(this); + } + + if (isDead()) + { + sNumZombieDrawables--; + } + + // Attempt to catch violations of this in debug, + // knowing that some false alarms may result + // + llassert(!LLSpatialGroup::sNoDelete); + + /* cannot be guaranteed and causes crashes on false alarms + if (LLSpatialGroup::sNoDelete) + { + LL_ERRS() << "Illegal deletion of LLDrawable!" << LL_ENDL; + }*/ + + std::for_each(mFaces.begin(), mFaces.end(), DeletePointer()); + mFaces.clear(); + + + /*if (!(sNumZombieDrawables % 10)) + { + LL_INFOS() << "- Zombie drawables: " << sNumZombieDrawables << LL_ENDL; + }*/ + +} + +void LLDrawable::markDead() +{ + if (isDead()) + { + LL_WARNS() << "Warning! Marking dead multiple times!" << LL_ENDL; + return; + } + setState(DEAD); + + if (mSpatialBridge) + { + mSpatialBridge->markDead(); + mSpatialBridge = NULL; + } + + sNumZombieDrawables++; + + // We're dead. Free up all of our references to other objects + cleanupReferences(); +// sDeadList.push_back(this); +} + +LLVOVolume* LLDrawable::getVOVolume() const +{ + LLViewerObject* objectp = mVObjp; + if ( !isDead() && objectp && (objectp->getPCode() == LL_PCODE_VOLUME)) + { + return ((LLVOVolume*)objectp); + } + else + { + return NULL; + } +} + +const LLMatrix4& LLDrawable::getRenderMatrix() const +{ + return isRoot() ? getWorldMatrix() : getParent()->getWorldMatrix(); +} + +bool LLDrawable::isLight() const +{ + LLViewerObject* objectp = mVObjp; + if ( objectp && (objectp->getPCode() == LL_PCODE_VOLUME) && !isDead()) + { + return ((LLVOVolume*)objectp)->getIsLight(); + } + else + { + return false; + } +} + +void LLDrawable::cleanupReferences() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE; + + std::for_each(mFaces.begin(), mFaces.end(), DeletePointer()); + mFaces.clear(); + + gPipeline.unlinkDrawable(this); + + removeFromOctree(); + + // Cleanup references to other objects + mVObjp = NULL; + mParent = NULL; +} + +void LLDrawable::removeFromOctree() +{ + if(!mEntry) + { + return; + } + + mEntry->removeData(this); + if(mEntry->hasVOCacheEntry()) + { + getRegion()->removeActiveCacheEntry((LLVOCacheEntry*)mEntry->getVOCacheEntry(), this); + } + mEntry = NULL; +} + +void LLDrawable::cleanupDeadDrawables() +{ + /* + S32 i; + for (i = 0; i < sDeadList.size(); i++) + { + if (sDeadList[i]->getNumRefs() > 1) + { + LL_WARNS() << "Dead drawable has " << sDeadList[i]->getNumRefs() << " remaining refs" << LL_ENDL; + gPipeline.findReferences(sDeadList[i]); + } + } + */ + sDeadList.clear(); +} + +S32 LLDrawable::findReferences(LLDrawable *drawablep) +{ + S32 count = 0; + if (mParent == drawablep) + { + LL_INFOS() << this << ": parent reference" << LL_ENDL; + count++; + } + return count; +} + +LLFace* LLDrawable::addFace(LLFacePool *poolp, LLViewerTexture *texturep) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE + + LLFace *face; + { + face = new LLFace(this, mVObjp); + } + + if (!face) LL_ERRS() << "Allocating new Face: " << mFaces.size() << LL_ENDL; + + if (face) + { + mFaces.push_back(face); + + if (poolp) + { + face->setPool(poolp, texturep); + } + + if (isState(UNLIT)) + { + face->setState(LLFace::FULLBRIGHT); + } + } + return face; +} + +LLFace* LLDrawable::addFace(const LLTextureEntry *te, LLViewerTexture *texturep) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE + + LLFace *face; + + face = new LLFace(this, mVObjp); + + face->setTEOffset(mFaces.size()); + face->setTexture(texturep); + face->setPoolType(gPipeline.getPoolTypeFromTE(te, texturep)); + + mFaces.push_back(face); + + if (isState(UNLIT)) + { + face->setState(LLFace::FULLBRIGHT); + } + + return face; + +} + +LLFace* LLDrawable::addFace(const LLTextureEntry *te, LLViewerTexture *texturep, LLViewerTexture *normalp) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE + + LLFace *face; + face = new LLFace(this, mVObjp); + + face->setTEOffset(mFaces.size()); + face->setTexture(texturep); + face->setNormalMap(normalp); + face->setPoolType(gPipeline.getPoolTypeFromTE(te, texturep)); + + mFaces.push_back(face); + + if (isState(UNLIT)) + { + face->setState(LLFace::FULLBRIGHT); + } + + return face; + +} + +LLFace* LLDrawable::addFace(const LLTextureEntry *te, LLViewerTexture *texturep, LLViewerTexture *normalp, LLViewerTexture *specularp) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE + + LLFace *face; + face = new LLFace(this, mVObjp); + + face->setTEOffset(mFaces.size()); + face->setTexture(texturep); + face->setNormalMap(normalp); + face->setSpecularMap(specularp); + face->setPoolType(gPipeline.getPoolTypeFromTE(te, texturep)); + + mFaces.push_back(face); + + if (isState(UNLIT)) + { + face->setState(LLFace::FULLBRIGHT); + } + + return face; + +} + +void LLDrawable::setNumFaces(const S32 newFaces, LLFacePool *poolp, LLViewerTexture *texturep) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE + + if (newFaces == (S32)mFaces.size()) + { + return; + } + else if (newFaces < (S32)mFaces.size()) + { + std::for_each(mFaces.begin() + newFaces, mFaces.end(), DeletePointer()); + mFaces.erase(mFaces.begin() + newFaces, mFaces.end()); + } + else // (newFaces > mFaces.size()) + { + mFaces.reserve(newFaces); + for (int i = mFaces.size(); i= (S32)mFaces.size()/2) + { + return; + } + else if (newFaces < (S32)mFaces.size()) + { + std::for_each(mFaces.begin() + newFaces, mFaces.end(), DeletePointer()); + mFaces.erase(mFaces.begin() + newFaces, mFaces.end()); + } + else // (newFaces > mFaces.size()) + { + mFaces.reserve(newFaces); + for (int i = mFaces.size(); imFaces.size(); + + mFaces.reserve(face_count); + for (U32 i = 0; i < src->mFaces.size(); i++) + { + LLFace* facep = src->mFaces[i]; + facep->setDrawable(this); + mFaces.push_back(facep); + } + src->mFaces.clear(); +} + +void LLDrawable::deleteFaces(S32 offset, S32 count) +{ + face_list_t::iterator face_begin = mFaces.begin() + offset; + face_list_t::iterator face_end = face_begin + count; + + std::for_each(face_begin, face_end, DeletePointer()); + mFaces.erase(face_begin, face_end); +} + +void LLDrawable::update() +{ + LL_ERRS() << "Shouldn't be called!" << LL_ENDL; +} + + +void LLDrawable::updateMaterial() +{ +} + +void LLDrawable::makeActive() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE + +#if !LL_RELEASE_FOR_DOWNLOAD + if (mVObjp.notNull()) + { + U32 pcode = mVObjp->getPCode(); + if (pcode == LLViewerObject::LL_VO_WATER || + pcode == LLViewerObject::LL_VO_VOID_WATER || + pcode == LLViewerObject::LL_VO_SURFACE_PATCH || + pcode == LLViewerObject::LL_VO_PART_GROUP || + pcode == LLViewerObject::LL_VO_HUD_PART_GROUP || + pcode == LLViewerObject::LL_VO_SKY) + { + LL_ERRS() << "Static viewer object has active drawable!" << LL_ENDL; + } + } +#endif + + if (!isState(ACTIVE)) // && mGeneration > 0) + { + setState(ACTIVE); + + //parent must be made active first + if (!isRoot() && !mParent->isActive()) + { + mParent->makeActive(); + //NOTE: linked set will now NEVER become static + mParent->setState(LLDrawable::ACTIVE_CHILD); + } + + //all child objects must also be active + llassert_always(mVObjp); + + LLViewerObject::const_child_list_t& child_list = mVObjp->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); iter++) + { + LLViewerObject* child = *iter; + LLDrawable* drawable = child->mDrawable; + if (drawable) + { + drawable->makeActive(); + } + } + + if (mVObjp->getPCode() == LL_PCODE_VOLUME) + { + gPipeline.markRebuild(this, LLDrawable::REBUILD_VOLUME); + } + updatePartition(); + } + else if (!isRoot() && !mParent->isActive()) //this should not happen, but occasionally it does... + { + mParent->makeActive(); + //NOTE: linked set will now NEVER become static + mParent->setState(LLDrawable::ACTIVE_CHILD); + } + + llassert(isAvatar() || isRoot() || mParent->isActive()); +} + + +void LLDrawable::makeStatic(bool warning_enabled) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE + + if (isState(ACTIVE) && + !isState(ACTIVE_CHILD) && + !mVObjp->isAttachment() && + !mVObjp->isFlexible() && + !mVObjp->isAnimatedObject()) + { + clearState(ACTIVE | ANIMATED_CHILD); + + //drawable became static with active parent, not acceptable + llassert(mParent.isNull() || !mParent->isActive() || !warning_enabled); + + LLViewerObject::const_child_list_t& child_list = mVObjp->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); iter++) + { + LLViewerObject* child = *iter; + LLDrawable* child_drawable = child->mDrawable; + if (child_drawable) + { + if (child_drawable->getParent() != this) + { + LL_WARNS() << "Child drawable has unknown parent." << LL_ENDL; + } + child_drawable->makeStatic(warning_enabled); + } + } + + if (mVObjp->getPCode() == LL_PCODE_VOLUME) + { + gPipeline.markRebuild(this, LLDrawable::REBUILD_VOLUME); + } + + if (mSpatialBridge) + { + mSpatialBridge->markDead(); + setSpatialBridge(NULL); + } + updatePartition(); + } + + llassert(isAvatar() || isRoot() || mParent->isStatic()); +} + +// Returns "distance" between target destination and resulting xfrom +F32 LLDrawable::updateXform(bool undamped) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE + + bool damped = !undamped; + + // Position + const LLVector3 old_pos(mXform.getPosition()); + LLVector3 target_pos; + if (mXform.isRoot()) + { + // get root position in your agent's region + target_pos = mVObjp->getPositionAgent(); + } + else + { + // parent-relative position + target_pos = mVObjp->getPosition(); + } + + // Rotation + const LLQuaternion old_rot(mXform.getRotation()); + LLQuaternion target_rot = mVObjp->getRotation(); + //scaling + LLVector3 target_scale = mVObjp->getScale(); + LLVector3 old_scale = mCurrentScale; + + // Damping + F32 dist_squared = 0.f; + F32 camdist2 = (mDistanceWRTCamera * mDistanceWRTCamera); + + if (damped && isVisible()) + { + F32 lerp_amt = llclamp(LLSmoothInterpolation::getInterpolant(OBJECT_DAMPING_TIME_CONSTANT), 0.f, 1.f); + LLVector3 new_pos = lerp(old_pos, target_pos, lerp_amt); + dist_squared = dist_vec_squared(new_pos, target_pos); + + LLQuaternion new_rot = nlerp(lerp_amt, old_rot, target_rot); + // FIXME: This can be negative! It is be possible for some rots to 'cancel out' pos or size changes. + dist_squared += (1.f - dot(new_rot, target_rot)) * 10.f; + + LLVector3 new_scale = lerp(old_scale, target_scale, lerp_amt); + dist_squared += dist_vec_squared(new_scale, target_scale); + + if ((dist_squared >= MIN_INTERPOLATE_DISTANCE_SQUARED * camdist2) && + (dist_squared <= MAX_INTERPOLATE_DISTANCE_SQUARED)) + { + // interpolate + target_pos = new_pos; + target_rot = new_rot; + target_scale = new_scale; + } + else if (mVObjp->getAngularVelocity().isExactlyZero()) + { + // snap to final position (only if no target omega is applied) + dist_squared = 0.0f; + //set target scale here, because of dist_squared = 0.0f remove object from move list + mCurrentScale = target_scale; + + if (getVOVolume() && !isRoot()) + { //child prim snapping to some position, needs a rebuild + gPipeline.markRebuild(this, LLDrawable::REBUILD_POSITION); + } + } + } + else + { + // The following fixes MAINT-1742 but breaks vehicles similar to MAINT-2275 + // dist_squared = dist_vec_squared(old_pos, target_pos); + + // The following fixes MAINT-2247 but causes MAINT-2275 + //dist_squared += (1.f - dot(old_rot, target_rot)) * 10.f; + //dist_squared += dist_vec_squared(old_scale, target_scale); + } + + const LLVector3 vec = mCurrentScale-target_scale; + + //It's a very important on each cycle on Drawable::update form(), when object remained in move + //, list update the CurrentScale member, because if do not do that, it remained in this list forever + //or when the delta time between two frames a become a sufficiently large (due to interpolation) + //for overcome the MIN_INTERPOLATE_DISTANCE_SQUARED. + mCurrentScale = target_scale; + + if (vec*vec > MIN_INTERPOLATE_DISTANCE_SQUARED) + { //scale change requires immediate rebuild + gPipeline.markRebuild(this, LLDrawable::REBUILD_POSITION); + } + else if (!isRoot() && + (!mVObjp->getAngularVelocity().isExactlyZero() || + dist_squared > 0.f)) + { //child prim moving relative to parent, tag as needing to be rendered atomically and rebuild + dist_squared = 1.f; //keep this object on the move list + if (!isState(LLDrawable::ANIMATED_CHILD)) + { + setState(LLDrawable::ANIMATED_CHILD); + gPipeline.markRebuild(this, LLDrawable::REBUILD_ALL); + mVObjp->dirtySpatialGroup(); + } + } + else if (!isRoot() && + ((dist_vec_squared(old_pos, target_pos) > 0.f) + || (1.f - dot(old_rot, target_rot)) > 0.f)) + { //fix for BUG-840, MAINT-2275, MAINT-1742, MAINT-2247 + mVObjp->shrinkWrap(); + gPipeline.markRebuild(this, LLDrawable::REBUILD_POSITION); + } + else if (!getVOVolume() && !isAvatar()) + { + movePartition(); + } + + // Update + mXform.setPosition(target_pos); + mXform.setRotation(target_rot); + mXform.setScale(LLVector3(1,1,1)); //no scale in drawable transforms (IT'S A RULE!) + mXform.updateMatrix(); + if (isRoot() && mVObjp->isAnimatedObject() && mVObjp->getControlAvatar()) + { + mVObjp->getControlAvatar()->matchVolumeTransform(); + } + + if (mSpatialBridge) + { + gPipeline.markMoved(mSpatialBridge, false); + } + return dist_squared; +} + +void LLDrawable::setRadius(F32 radius) +{ + if (mRadius != radius) + { + mRadius = radius; + } +} + +void LLDrawable::moveUpdatePipeline(bool moved) +{ + if (moved) + { + makeActive(); + } + + // Update the face centers. + for (S32 i = 0; i < getNumFaces(); i++) + { + LLFace* face = getFace(i); + if (face) + { + face->updateCenterAgent(); + } + } +} + +void LLDrawable::movePartition() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE + + LLSpatialPartition* part = getSpatialPartition(); + if (part) + { + part->move(this, getSpatialGroup()); + } +} + +bool LLDrawable::updateMove() +{ + if (isDead()) + { + LL_WARNS() << "Update move on dead drawable!" << LL_ENDL; + return true; + } + + if (mVObjp.isNull()) + { + return false; + } + + makeActive(); + + return isState(MOVE_UNDAMPED) ? updateMoveUndamped() : updateMoveDamped(); +} + +bool LLDrawable::updateMoveUndamped() +{ + F32 dist_squared = updateXform(true); + + mGeneration++; + + if (!isState(LLDrawable::INVISIBLE)) + { + bool moved = (dist_squared > 0.001f && dist_squared < 255.99f); + moveUpdatePipeline(moved); + mVObjp->updateText(); + } + + mVObjp->clearChanged(LLXform::MOVED); + return true; +} + +void LLDrawable::updatePartition() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE + + if (!getVOVolume()) + { + movePartition(); + } + else if (mSpatialBridge) + { + gPipeline.markMoved(mSpatialBridge, false); + } + else + { + //a child prim moved and needs its verts regenerated + gPipeline.markRebuild(this, LLDrawable::REBUILD_POSITION); + } +} + +bool LLDrawable::updateMoveDamped() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE + + F32 dist_squared = updateXform(false); + + mGeneration++; + + if (!isState(LLDrawable::INVISIBLE)) + { + bool moved = (dist_squared > 0.001f && dist_squared < 128.0f); + moveUpdatePipeline(moved); + mVObjp->updateText(); + } + + bool done_moving = dist_squared == 0.0f; + + if (done_moving) + { + mVObjp->clearChanged(LLXform::MOVED); + } + + return done_moving; +} + +void LLDrawable::updateDistance(LLCamera& camera, bool force_update) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE + + if (LLViewerCamera::sCurCameraID != LLViewerCamera::CAMERA_WORLD) + { + LL_WARNS() << "Attempted to update distance for non-world camera." << LL_ENDL; + return; + } + + if (gShiftFrame) + { + return; + } + + //switch LOD with the spatial group to avoid artifacts + //LLSpatialGroup* sg = getSpatialGroup(); + + LLVector3 pos; + + //if (!sg || sg->changeLOD()) + { + LLVOVolume* volume = getVOVolume(); + if (volume) + { + if (getGroup()) + { + pos.set(getPositionGroup().getF32ptr()); + } + else + { + pos = getPositionAgent(); + } + + if (isState(LLDrawable::HAS_ALPHA)) + { + for (S32 i = 0; i < getNumFaces(); i++) + { + LLFace* facep = getFace(i); + if (facep && + (force_update || facep->isInAlphaPool())) + { + LLVector4a box; + box.setSub(facep->mExtents[1], facep->mExtents[0]); + box.mul(0.25f); + LLVector3 v = (facep->mCenterLocal-camera.getOrigin()); + const LLVector3& at = camera.getAtAxis(); + for (U32 j = 0; j < 3; j++) + { + v.mV[j] -= box[j] * at.mV[j]; + } + facep->mDistance = v * camera.getAtAxis(); + } + } + } + + + // MAINT-7926 Handle volumes in an animated object as a special case + // SL-937: add dynamic box handling for rigged mesh on regular avatars. + //if (volume->getAvatar() && volume->getAvatar()->isControlAvatar()) + if (volume->getAvatar()) + { + const LLVector3* av_box = volume->getAvatar()->getLastAnimExtents(); + LLVector3 cam_pos_from_agent = LLViewerCamera::getInstance()->getOrigin(); + LLVector3 cam_to_box_offset = point_to_box_offset(cam_pos_from_agent, av_box); + mDistanceWRTCamera = llmax(0.01f, ll_round(cam_to_box_offset.magVec(), 0.01f)); + mVObjp->updateLOD(); + return; + } + } + else + { + pos = LLVector3(getPositionGroup().getF32ptr()); + } + + pos -= camera.getOrigin(); + mDistanceWRTCamera = ll_round(pos.magVec(), 0.01f); + mVObjp->updateLOD(); + } +} + +void LLDrawable::updateTexture() +{ + if (isDead()) + { + LL_WARNS() << "Dead drawable updating texture!" << LL_ENDL; + return; + } + + if (getNumFaces() != mVObjp->getNumTEs()) + { //drawable is transitioning its face count + return; + } + + if (getVOVolume()) + { + gPipeline.markRebuild(this, LLDrawable::REBUILD_MATERIAL); + } +} + +bool LLDrawable::updateGeometry() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE + + llassert(mVObjp.notNull()); + bool res = mVObjp && mVObjp->updateGeometry(this); + return res; +} + +void LLDrawable::shiftPos(const LLVector4a &shift_vector) +{ + if (isDead()) + { + LL_WARNS() << "Shifting dead drawable" << LL_ENDL; + return; + } + + if (mParent) + { + mXform.setPosition(mVObjp->getPosition()); + } + else + { + mXform.setPosition(mVObjp->getPositionAgent()); + } + + mXform.updateMatrix(); + + if (isStatic()) + { + LLVOVolume* volume = getVOVolume(); + + bool rebuild = (!volume && + getRenderType() != LLPipeline::RENDER_TYPE_TREE && + getRenderType() != LLPipeline::RENDER_TYPE_TERRAIN && + getRenderType() != LLPipeline::RENDER_TYPE_SKY); + + if (rebuild) + { + gPipeline.markRebuild(this, LLDrawable::REBUILD_ALL); + } + + for (S32 i = 0; i < getNumFaces(); i++) + { + LLFace *facep = getFace(i); + if (facep) + { + facep->mCenterAgent += LLVector3(shift_vector.getF32ptr()); + facep->mExtents[0].add(shift_vector); + facep->mExtents[1].add(shift_vector); + + if (rebuild && facep->hasGeometry()) + { + facep->clearVertexBuffer(); + } + } + } + + shift(shift_vector); + } + else if (mSpatialBridge) + { + mSpatialBridge->shiftPos(shift_vector); + } + else if (isAvatar()) + { + shift(shift_vector); + } + + mVObjp->onShift(shift_vector); +} + +const LLVector3& LLDrawable::getBounds(LLVector3& min, LLVector3& max) const +{ + mXform.getMinMax(min,max); + return mXform.getPositionW(); +} + +void LLDrawable::updateSpatialExtents() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE + + if (mVObjp) + { + const LLVector4a* exts = getSpatialExtents(); + LLVector4a extents[2] = { exts[0], exts[1] }; + + mVObjp->updateSpatialExtents(extents[0], extents[1]); + setSpatialExtents(extents[0], extents[1]); + } + + updateBinRadius(); + + if (mSpatialBridge.notNull()) + { + getGroupPosition().splat(0.f); + } +} + + +void LLDrawable::updateBinRadius() +{ + if (mVObjp.notNull()) + { + setBinRadius(llmin(mVObjp->getBinRadius(), 256.f)); + } + else + { + setBinRadius(llmin(getRadius()*4.f, 256.f)); + } +} + +void LLDrawable::updateSpecialHoverCursor(bool enabled) +{ + // TODO: maintain a list of objects that have special + // hover cursors, then use that list for per-frame + // hover cursor selection. JC +} + +F32 LLDrawable::getVisibilityRadius() const +{ + if (isDead()) + { + return 0.f; + } + else if (isLight()) + { + const LLVOVolume *vov = getVOVolume(); + if (vov) + { + return llmax(getRadius(), vov->getLightRadius()); + } else { + // LL_WARNS() ? + } + } + return getRadius(); +} + +void LLDrawable::updateUVMinMax() +{ +} + +//virtual +bool LLDrawable::isVisible() const +{ + if (LLViewerOctreeEntryData::isVisible()) + { + return true; + } + + { + LLViewerOctreeGroup* group = mEntry->getGroup(); + if (group && group->isVisible()) + { + LLViewerOctreeEntryData::setVisible(); + return true; + } + } + + return false; +} + +//virtual +bool LLDrawable::isRecentlyVisible() const +{ + //currently visible or visible in the previous frame. + bool vis = LLViewerOctreeEntryData::isRecentlyVisible(); + + if(!vis) + { + const U32 MIN_VIS_FRAME_RANGE = 2 ; //two frames:the current one and the last one. + vis = (sCurVisible - getVisible() < MIN_VIS_FRAME_RANGE); + } + + return vis ; +} + +void LLDrawable::setGroup(LLViewerOctreeGroup *groupp) + { + LLSpatialGroup* cur_groupp = (LLSpatialGroup*)getGroup(); + + //precondition: mGroupp MUST be null or DEAD or mGroupp MUST NOT contain this + //llassert(!cur_groupp || cur_groupp->isDead() || !cur_groupp->hasElement(this)); + + //precondition: groupp MUST be null or groupp MUST contain this + llassert(!groupp || (LLSpatialGroup*)groupp->hasElement(this)); + + if (cur_groupp != groupp && getVOVolume()) + { + //NULL out vertex buffer references for volumes on spatial group change to maintain + //requirement that every face vertex buffer is either NULL or points to a vertex buffer + //contained by its drawable's spatial group + for (S32 i = 0; i < getNumFaces(); ++i) + { + LLFace* facep = getFace(i); + if (facep) + { + facep->clearVertexBuffer(); + } + } + } + + //postcondition: if next group is NULL, previous group must be dead OR NULL OR binIndex must be -1 + //postcondition: if next group is NOT NULL, binIndex must not be -1 + //llassert(groupp == NULL ? (cur_groupp == NULL || cur_groupp->isDead()) || (!getEntry() || getEntry()->getBinIndex() == -1) : + // (getEntry() && getEntry()->getBinIndex() != -1)); + + LLViewerOctreeEntryData::setGroup(groupp); +} + +/* +* Get the SpatialPartition this Drawable should use. +* Checks current SpatialPartition assignment and corrects if it is invalid. +*/ +LLSpatialPartition* LLDrawable::getSpatialPartition() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE + + LLSpatialPartition* retval = NULL; + + if (!mVObjp || + !getVOVolume() || + isStatic()) + { + retval = gPipeline.getSpatialPartition((LLViewerObject*)mVObjp); + } + else if (isRoot()) + { + // determine if the spatial bridge has changed + if (mSpatialBridge) + { + U32 partition_type = mSpatialBridge->asPartition()->mPartitionType; + bool is_hud = mVObjp->isHUDAttachment(); + bool is_animesh = mVObjp->isAnimatedObject() && mVObjp->getControlAvatar() != NULL; + bool is_attachment = mVObjp->isAttachment() && !is_hud && !is_animesh; + if ((partition_type == LLViewerRegion::PARTITION_HUD) != is_hud) + { + // Was/became HUD + // remove obsolete bridge + mSpatialBridge->markDead(); + setSpatialBridge(NULL); + } + else if ((partition_type == LLViewerRegion::PARTITION_CONTROL_AV) != is_animesh) + { + // Was/became part of animesh + // remove obsolete bridge + mSpatialBridge->markDead(); + setSpatialBridge(NULL); + } + else if ((partition_type == LLViewerRegion::PARTITION_AVATAR) != is_attachment) + { + // Was/became part of avatar + // remove obsolete bridge + mSpatialBridge->markDead(); + setSpatialBridge(NULL); + } + } + //must be an active volume + if (!mSpatialBridge) + { + if (mVObjp->isHUDAttachment()) + { + setSpatialBridge(new LLHUDBridge(this, getRegion())); + } + else if (mVObjp->isAnimatedObject() && mVObjp->getControlAvatar()) + { + setSpatialBridge(new LLControlAVBridge(this, getRegion())); + mVObjp->getControlAvatar()->mControlAVBridge = (LLControlAVBridge*)getSpatialBridge(); + } + // check HUD first, because HUD is also attachment + else if (mVObjp->isAttachment()) + { + // Attachment + // Use AvatarBridge of root object in attachment linkset + setSpatialBridge(new LLAvatarBridge(this, getRegion())); + } + else + { + // Moving linkset, use VolumeBridge of root object in linkset + setSpatialBridge(new LLVolumeBridge(this, getRegion())); + } + } + return mSpatialBridge->asPartition(); + } + else + { + retval = getParent()->getSpatialPartition(); + } + + if (retval && mSpatialBridge.notNull()) + { + mSpatialBridge->markDead(); + setSpatialBridge(NULL); + } + + return retval; +} + +//======================================= +// Spatial Partition Bridging Drawable +//======================================= + +LLSpatialBridge::LLSpatialBridge(LLDrawable* root, bool render_by_group, U32 data_mask, LLViewerRegion* regionp) : + LLDrawable(root->getVObj(), true), + LLSpatialPartition(data_mask, render_by_group, regionp) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE; + mOcclusionEnabled = false; + mBridge = this; + mDrawable = root; + root->setSpatialBridge(this); + + mRenderType = mDrawable->mRenderType; + mDrawableType = mDrawable->mRenderType; + + mPartitionType = LLViewerRegion::PARTITION_VOLUME; + + mOctree->balance(); + + llassert(mDrawable); + llassert(mDrawable->getRegion()); + LLSpatialPartition *part = mDrawable->getRegion()->getSpatialPartition(mPartitionType); + llassert(part); + + if (part) + { + part->put(this); + } +} + +LLSpatialBridge::~LLSpatialBridge() +{ + if(mEntry) + { + LLSpatialGroup* group = getSpatialGroup(); + if (group) + { + group->getSpatialPartition()->remove(this, group); + } + } + + //delete octree here so listeners will still be able to access bridge specific state + destroyTree(); +} + +void LLSpatialBridge::destroyTree() +{ + delete mOctree; + mOctree = NULL; +} + +void LLSpatialBridge::updateSpatialExtents() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE + + LLSpatialGroup* root = (LLSpatialGroup*) mOctree->getListener(0); + + root->rebound(); + + const LLVector4a* root_bounds = root->getBounds(); + LLVector4a offset; + LLVector4a size = root_bounds[1]; + + //VECTORIZE THIS + LLMatrix4a mat; + mat.loadu(mDrawable->getXform()->getWorldMatrix()); + + LLVector4a t; + t.splat(0.f); + + LLVector4a center; + mat.affineTransform(t, center); + + mat.rotate(root_bounds[0], offset); + center.add(offset); + + LLVector4a v[4]; + + //get 4 corners of bounding box + mat.rotate(size,v[0]); + + LLVector4a scale; + + scale.set(-1.f, -1.f, 1.f); + scale.mul(size); + mat.rotate(scale, v[1]); + + scale.set(1.f, -1.f, -1.f); + scale.mul(size); + mat.rotate(scale, v[2]); + + scale.set(-1.f, 1.f, -1.f); + scale.mul(size); + mat.rotate(scale, v[3]); + + LLVector4a newMin; + LLVector4a newMax; + newMin = newMax = center; + for (U32 i = 0; i < 4; i++) + { + LLVector4a delta; + delta.setAbs(v[i]); + LLVector4a min; + min.setSub(center, delta); + LLVector4a max; + max.setAdd(center, delta); + + newMin.setMin(newMin, min); + newMax.setMax(newMax, max); + } + setSpatialExtents(newMin, newMax); + + LLVector4a diagonal; + diagonal.setSub(newMax, newMin); + mRadius = diagonal.getLength3().getF32() * 0.5f; + + LLVector4a& pos = getGroupPosition(); + pos.setAdd(newMin,newMax); + pos.mul(0.5f); + updateBinRadius(); +} + +void LLSpatialBridge::updateBinRadius() +{ + setBinRadius(llmin( mOctree->getSize()[0]*0.5f, 256.f)); +} + +LLCamera LLSpatialBridge::transformCamera(LLCamera& camera) +{ + LLCamera ret = camera; + LLXformMatrix* mat = mDrawable->getXform(); + LLVector3 center = LLVector3(0,0,0) * mat->getWorldMatrix(); + + LLVector3 delta = ret.getOrigin() - center; + LLQuaternion rot = ~mat->getRotation(); + + delta *= rot; + LLVector3 lookAt = ret.getAtAxis(); + LLVector3 up_axis = ret.getUpAxis(); + LLVector3 left_axis = ret.getLeftAxis(); + + lookAt *= rot; + up_axis *= rot; + left_axis *= rot; + + if (!delta.isFinite()) + { + delta.clearVec(); + } + + ret.setOrigin(delta); + ret.setAxes(lookAt, left_axis, up_axis); + + return ret; +} + +void LLSpatialBridge::transformExtents(const LLVector4a* src, LLVector4a* dst) +{ + LLMatrix4 mat = mDrawable->getXform()->getWorldMatrix(); + mat.invert(); + + LLMatrix4a world_to_bridge(mat); + + matMulBoundBox(world_to_bridge, src, dst); +} + + +void LLDrawable::setVisible(LLCamera& camera, std::vector* results, bool for_select) +{ + LLViewerOctreeEntryData::setVisible(); + +#if 0 && !LL_RELEASE_FOR_DOWNLOAD + //crazy paranoid rules checking + if (getVOVolume()) + { + if (!isRoot()) + { + if (isActive() && !mParent->isActive()) + { + LL_ERRS() << "Active drawable has static parent!" << LL_ENDL; + } + + if (isStatic() && !mParent->isStatic()) + { + LL_ERRS() << "Static drawable has active parent!" << LL_ENDL; + } + + if (mSpatialBridge) + { + LL_ERRS() << "Child drawable has spatial bridge!" << LL_ENDL; + } + } + else if (isActive() && !mSpatialBridge) + { + LL_ERRS() << "Active root drawable has no spatial bridge!" << LL_ENDL; + } + else if (isStatic() && mSpatialBridge.notNull()) + { + LL_ERRS() << "Static drawable has spatial bridge!" << LL_ENDL; + } + } +#endif +} + +class LLOctreeMarkNotCulled: public OctreeTraveler +{ +public: + LLCamera* mCamera; + + LLOctreeMarkNotCulled(LLCamera* camera_in) : mCamera(camera_in) { } + + virtual void traverse(const OctreeNode* node) + { + LLSpatialGroup* group = (LLSpatialGroup*) node->getListener(0); + group->setVisible(); + OctreeTraveler::traverse(node); + } + + void visit(const OctreeNode* branch) + { + gPipeline.markNotCulled((LLSpatialGroup*) branch->getListener(0), *mCamera); + } +}; + +void LLSpatialBridge::setVisible(LLCamera& camera_in, std::vector* results, bool for_select) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE + + if (!gPipeline.hasRenderType(mDrawableType)) + { + return; + } + + + //HACK don't draw attachments for avatars that haven't been visible in more than a frame + LLViewerObject *vobj = mDrawable->getVObj(); + if (vobj && vobj->isAttachment() && !vobj->isHUDAttachment()) + { + LLDrawable* av; + LLDrawable* parent = mDrawable->getParent(); + + if (parent) + { + LLViewerObject* objparent = parent->getVObj(); + av = objparent->mDrawable; + LLSpatialGroup* group = av->getSpatialGroup(); + + bool impostor = false; + bool loaded = false; + if (objparent->isAvatar()) + { + LLVOAvatar* avatarp = (LLVOAvatar*) objparent; + if (avatarp->isVisible()) + { + impostor = objparent->isAvatar() && !LLPipeline::sImpostorRender && ((LLVOAvatar*) objparent)->isImpostor(); + loaded = objparent->isAvatar() && ((LLVOAvatar*) objparent)->isFullyLoaded(); + } + else + { + return; + } + } + + if (!group || + LLDrawable::getCurrentFrame() - av->getVisible() > 1 || + impostor || + !loaded) + { + return; + } + } + } + + + LLSpatialGroup* group = (LLSpatialGroup*) mOctree->getListener(0); + group->rebound(); + + LLVector4a center; + const LLVector4a* exts = getSpatialExtents(); + center.setAdd(exts[0], exts[1]); + center.mul(0.5f); + LLVector4a size; + size.setSub(exts[1], exts[0]); + size.mul(0.5f); + + if ((LLPipeline::sShadowRender && camera_in.AABBInFrustum(center, size)) || + LLPipeline::sImpostorRender || + (camera_in.AABBInFrustumNoFarClip(center, size) && + AABBSphereIntersect(exts[0], exts[1], camera_in.getOrigin(), camera_in.mFrustumCornerDist))) + { + if (!LLPipeline::sImpostorRender && + !LLPipeline::sShadowRender && + LLPipeline::calcPixelArea(center, size, camera_in) < FORCE_INVISIBLE_AREA) + { + return; + } + + LLDrawable::setVisible(camera_in); + + if (for_select) + { + results->push_back(mDrawable); + if (mDrawable->getVObj()) + { + LLViewerObject::const_child_list_t& child_list = mDrawable->getVObj()->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); iter++) + { + LLViewerObject* child = *iter; + LLDrawable* drawable = child->mDrawable; + results->push_back(drawable); + } + } + } + else + { + LLCamera trans_camera = transformCamera(camera_in); + LLOctreeMarkNotCulled culler(&trans_camera); + culler.traverse(mOctree); + } + } +} + +void LLSpatialBridge::updateDistance(LLCamera& camera_in, bool force_update) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE + + if (mDrawable == NULL) + { + markDead(); + return; + } + + if (gShiftFrame) + { + return; + } + + if (mDrawable->getVObj()) + { + // Don't update if we are part of impostor, unles it's an impostor pass + if (!LLPipeline::sImpostorRender && mDrawable->getVObj()->isAttachment()) + { + LLDrawable* parent = mDrawable->getParent(); + if (parent && parent->getVObj()) + { + LLVOAvatar* av = parent->getVObj()->asAvatar(); + if (av && av->isImpostor()) + { + return; + } + } + } + + LLCamera camera = transformCamera(camera_in); + + mDrawable->updateDistance(camera, force_update); + + LLViewerObject::const_child_list_t& child_list = mDrawable->getVObj()->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); iter++) + { + LLViewerObject* child = *iter; + LLDrawable* drawable = child->mDrawable; + if (!drawable) + { + continue; + } + + if (!drawable->isAvatar()) + { + drawable->updateDistance(camera, force_update); + } + } + } +} + +void LLSpatialBridge::makeActive() +{ //it is an error to make a spatial bridge active (it's already active) + LL_ERRS() << "makeActive called on spatial bridge" << LL_ENDL; +} + +void LLSpatialBridge::move(LLDrawable *drawablep, LLSpatialGroup *curp, bool immediate) +{ + LLSpatialPartition::move(drawablep, curp, immediate); + gPipeline.markMoved(this, false); +} + +bool LLSpatialBridge::updateMove() +{ + llassert_always(mDrawable); + llassert_always(mDrawable->mVObjp); + llassert_always(mDrawable->getRegion()); + LLSpatialPartition* part = mDrawable->getRegion()->getSpatialPartition(mPartitionType); + llassert_always(part); + + mOctree->balance(); + if (part) + { + part->move(this, getSpatialGroup(), true); + } + return true; +} + +void LLSpatialBridge::shiftPos(const LLVector4a& vec) +{ + LLDrawable::shift(vec); +} + +void LLSpatialBridge::cleanupReferences() +{ + LLDrawable::cleanupReferences(); + if (mDrawable) + { + mDrawable->setGroup(NULL); + + if (mDrawable->getVObj()) + { + LLViewerObject::const_child_list_t& child_list = mDrawable->getVObj()->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); iter++) + { + LLViewerObject* child = *iter; + LLDrawable* drawable = child->mDrawable; + if (drawable) + { + drawable->setGroup(NULL); + } + } + } + + LLDrawable* drawablep = mDrawable; + mDrawable = NULL; + drawablep->setSpatialBridge(NULL); + } +} + +const LLVector3 LLDrawable::getPositionAgent() const +{ + if (getVOVolume()) + { + if (isActive()) + { + LLVector3 pos(0,0,0); + if (!isRoot()) + { + pos = mVObjp->getPosition(); + } + return pos * getRenderMatrix(); + } + else + { + return mVObjp->getPositionAgent(); + } + } + else + { + return getWorldPosition(); + } +} + +bool LLDrawable::isAnimating() const +{ + if (!getVObj()) + { + return true; + } + + if (getScale() != mVObjp->getScale()) + { + return true; + } + + if (mVObjp->getPCode() == LLViewerObject::LL_VO_PART_GROUP) + { + return true; + } + if (mVObjp->getPCode() == LLViewerObject::LL_VO_HUD_PART_GROUP) + { + return true; + } + + /*if (!isRoot() && !mVObjp->getAngularVelocity().isExactlyZero()) + { //target omega + return true; + }*/ + + return false; +} + +void LLDrawable::updateFaceSize(S32 idx) +{ + if (mVObjp.notNull()) + { + mVObjp->updateFaceSize(idx); + } +} + +LLDrawable* LLDrawable::getRoot() +{ + LLDrawable* ret = this; + while (!ret->isRoot()) + { + ret = ret->getParent(); + } + + return ret; +} + +LLBridgePartition::LLBridgePartition(LLViewerRegion* regionp) +: LLSpatialPartition(0, false, regionp) +{ + mDrawableType = LLPipeline::RENDER_TYPE_VOLUME; + mPartitionType = LLViewerRegion::PARTITION_BRIDGE; + mLODPeriod = 16; + mSlopRatio = 0.25f; +} + +LLAvatarPartition::LLAvatarPartition(LLViewerRegion* regionp) + : LLBridgePartition(regionp) +{ + mDrawableType = LLPipeline::RENDER_TYPE_AVATAR; + mPartitionType = LLViewerRegion::PARTITION_AVATAR; +} + +LLControlAVPartition::LLControlAVPartition(LLViewerRegion* regionp) + : LLBridgePartition(regionp) +{ + mDrawableType = LLPipeline::RENDER_TYPE_CONTROL_AV; + mPartitionType = LLViewerRegion::PARTITION_CONTROL_AV; +} + +LLHUDBridge::LLHUDBridge(LLDrawable* drawablep, LLViewerRegion* regionp) +: LLVolumeBridge(drawablep, regionp) +{ + mDrawableType = LLPipeline::RENDER_TYPE_HUD; + mPartitionType = LLViewerRegion::PARTITION_HUD; + mSlopRatio = 0.0f; +} + +F32 LLHUDBridge::calcPixelArea(LLSpatialGroup* group, LLCamera& camera) +{ + return 1024.f; +} + + +void LLHUDBridge::shiftPos(const LLVector4a& vec) +{ + //don't shift hud bridges on region crossing +} + diff --git a/indra/newview/lldrawable.h b/indra/newview/lldrawable.h index 89da7be964..38fd8d1f80 100644 --- a/indra/newview/lldrawable.h +++ b/indra/newview/lldrawable.h @@ -1,345 +1,345 @@ -/** - * @file lldrawable.h - * @brief LLDrawable class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_DRAWABLE_H -#define LL_DRAWABLE_H - -#include -#include - -#include "v2math.h" -#include "v3math.h" -#include "v4math.h" -#include "m4math.h" -#include "v4coloru.h" -#include "llvector4a.h" -#include "llquaternion.h" -#include "xform.h" -#include "llviewerobject.h" -#include "llrect.h" -#include "llappviewer.h" // for gFrameTimeSeconds -#include "llvieweroctree.h" -#include - -class LLCamera; -class LLDrawPool; -class LLDrawable; -class LLFace; -class LLFacePool; -class LLSpatialGroup; -class LLSpatialBridge; -class LLSpatialPartition; -class LLVOVolume; -class LLViewerTexture; - -// Can have multiple silhouettes for each object -const U32 SILHOUETTE_HIGHLIGHT = 0; - -// All data for new renderer goes into this class. -LL_ALIGN_PREFIX(16) -class LLDrawable - : public LLViewerOctreeEntryData -{ - LL_ALIGN_NEW; -public: - typedef std::vector face_list_t; - - LLDrawable(const LLDrawable& rhs) - : LLViewerOctreeEntryData(rhs) - { - *this = rhs; - } - - const LLDrawable& operator=(const LLDrawable& rhs) - { - LL_ERRS() << "Illegal operation!" << LL_ENDL; - return *this; - } - - static void initClass(); - - LLDrawable(LLViewerObject *vobj, bool new_entry = false); - - void markDead(); // Mark this drawable as dead - bool isDead() const { return isState(DEAD); } - bool isNew() const { return !isState(BUILT); } - bool isUnload() const { return isState(FOR_UNLOAD); } - - bool isLight() const; - - virtual void setVisible(LLCamera& camera_in, std::vector* results = NULL, bool for_select = false); - - LLSpatialGroup* getSpatialGroup()const {return (LLSpatialGroup*)getGroup();} - LLViewerRegion* getRegion() const { return mVObjp->getRegion(); } - const LLTextureEntry* getTextureEntry(U8 which) const { return mVObjp->getTE(which); } - LLPointer& getVObj() { return mVObjp; } - const LLViewerObject *getVObj() const { return mVObjp; } - LLVOVolume* getVOVolume() const; // cast mVObjp tp LLVOVolume if OK - - const LLMatrix4& getWorldMatrix() const { return mXform.getWorldMatrix(); } - const LLMatrix4& getRenderMatrix() const; - void setPosition(LLVector3 v) const { } - const LLVector3& getPosition() const { return mXform.getPosition(); } - const LLVector3& getWorldPosition() const { return mXform.getPositionW(); } - const LLVector3 getPositionAgent() const; - const LLVector3& getScale() const { return mCurrentScale; } - void setScale(const LLVector3& scale) { mCurrentScale = scale; } - const LLQuaternion& getWorldRotation() const { return mXform.getWorldRotation(); } - const LLQuaternion& getRotation() const { return mXform.getRotation(); } - F32 getIntensity() const { return llmin(mXform.getScale().mV[0], 4.f); } - S32 getLOD() const { return mVObjp ? mVObjp->getLOD() : 1; } - - void getMinMax(LLVector3& min,LLVector3& max) const { mXform.getMinMax(min,max); } - LLXformMatrix* getXform() { return &mXform; } - - U32 getState() const { return mState; } - bool isState (U32 bits) const { return ((mState & bits) != 0); } - void setState (U32 bits) { mState |= bits; } - void clearState(U32 bits) { mState &= ~bits; } - - bool isAvatar() const { return mVObjp.notNull() && mVObjp->isAvatar(); } - bool isRoot() const { return !mParent || mParent->isAvatar(); } - LLDrawable* getRoot(); - bool isSpatialRoot() const { return !mParent || mParent->isAvatar(); } - virtual bool isSpatialBridge() const { return false; } - virtual LLSpatialPartition* asPartition() { return NULL; } - LLDrawable* getParent() const { return mParent; } - - // must set parent through LLViewerObject:: () - //bool setParent(LLDrawable *parent); - - inline LLFace* getFace(const S32 i) const; - inline S32 getNumFaces() const; - face_list_t& getFaces() { return mFaces; } - const face_list_t& getFaces() const { return mFaces; } - - //void removeFace(const S32 i); // SJB: Avoid using this, it's slow - LLFace* addFace(LLFacePool *poolp, LLViewerTexture *texturep); - LLFace* addFace(const LLTextureEntry *te, LLViewerTexture *texturep); - LLFace* addFace(const LLTextureEntry *te, LLViewerTexture *texturep, LLViewerTexture *normalp); - LLFace* addFace(const LLTextureEntry *te, LLViewerTexture *texturep, LLViewerTexture *normalp, LLViewerTexture *specularp); - void deleteFaces(S32 offset, S32 count); - void setNumFaces(const S32 numFaces, LLFacePool *poolp, LLViewerTexture *texturep); - void setNumFacesFast(const S32 numFaces, LLFacePool *poolp, LLViewerTexture *texturep); - void mergeFaces(LLDrawable* src); - - void init(bool new_entry); - void unload(); - void destroy(); - - void update(); - F32 updateXform(bool undamped); - - virtual void makeActive(); - /*virtual*/ void makeStatic(bool warning_enabled = true); - - bool isActive() const { return isState(ACTIVE); } - bool isStatic() const { return !isActive(); } - bool isAnimating() const; - - virtual bool updateMove(); - virtual void movePartition(); - - void updateTexture(); - void updateMaterial(); - virtual void updateDistance(LLCamera& camera, bool force_update); - bool updateGeometry(); - void updateFaceSize(S32 idx); - - void updateSpecialHoverCursor(bool enabled); - - virtual void shiftPos(const LLVector4a &shift_vector); - - S32 getGeneration() const { return mGeneration; } - - bool getLit() const { return !isState(UNLIT); } - void setLit(bool lit) { lit ? clearState(UNLIT) : setState(UNLIT); } - - bool isVisible() const; - bool isRecentlyVisible() const; - - virtual void cleanupReferences(); - - void setGroup(LLViewerOctreeGroup* group); - void setRadius(const F32 radius); - F32 getRadius() const { return mRadius; } - F32 getVisibilityRadius() const; - - void updateUVMinMax(); // Updates the cache of sun space bounding box. - - const LLVector3& getBounds(LLVector3& min, LLVector3& max) const; - virtual void updateSpatialExtents(); - virtual void updateBinRadius(); - - void setRenderType(S32 type) { mRenderType = type; } - bool isRenderType(S32 type) { return mRenderType == type; } - S32 getRenderType() { return mRenderType; } - - // Debugging methods - S32 findReferences(LLDrawable *drawablep); // Not const because of @#$! iterators... - - LLSpatialPartition* getSpatialPartition(); - - void removeFromOctree(); - - void setSpatialBridge(LLSpatialBridge* bridge) { mSpatialBridge = (LLDrawable*) bridge; } - LLSpatialBridge* getSpatialBridge() { return (LLSpatialBridge*) (LLDrawable*) mSpatialBridge; } - - // Statics - static void incrementVisible(); - static void cleanupDeadDrawables(); - -protected: - ~LLDrawable() { destroy(); } - void moveUpdatePipeline(bool moved); - void updatePartition(); - bool updateMoveDamped(); - bool updateMoveUndamped(); - -public: - friend class LLPipeline; - friend class LLDrawPool; - friend class LLSpatialBridge; - - typedef std::unordered_set > drawable_set_t; - typedef std::set > ordered_drawable_set_t; - typedef std::vector > drawable_vector_t; - typedef std::list > drawable_list_t; - typedef std::queue > drawable_queue_t; - - struct CompareDistanceGreater - { - bool operator()(const LLDrawable* const& lhs, const LLDrawable* const& rhs) - { - return lhs->mDistanceWRTCamera < rhs->mDistanceWRTCamera; // farthest = last - } - }; - - struct CompareDistanceGreaterVisibleFirst - { - bool operator()(const LLDrawable* const& lhs, const LLDrawable* const& rhs) - { - if (lhs->isVisible() && !rhs->isVisible()) - { - return true; //visible things come first - } - else if (!lhs->isVisible() && rhs->isVisible()) - { - return false; //rhs is visible, comes first - } - - return lhs->mDistanceWRTCamera < rhs->mDistanceWRTCamera; // farthest = last - } - }; - - typedef enum e_drawable_flags - { - IN_REBUILD_Q = 0x00000001, - EARLY_MOVE = 0x00000004, - MOVE_UNDAMPED = 0x00000008, - ON_MOVE_LIST = 0x00000010, - UV = 0x00000020, - UNLIT = 0x00000040, - LIGHT = 0x00000080, - REBUILD_VOLUME = 0x00000100, //volume changed LOD or parameters, or vertex buffer changed - REBUILD_TCOORD = 0x00000200, //texture coordinates changed - REBUILD_COLOR = 0x00000400, //color changed - REBUILD_POSITION= 0x00000800, //vertex positions/normals changed - REBUILD_GEOMETRY= REBUILD_POSITION|REBUILD_TCOORD|REBUILD_COLOR, - REBUILD_MATERIAL= REBUILD_TCOORD|REBUILD_COLOR, - REBUILD_ALL = REBUILD_GEOMETRY|REBUILD_VOLUME, - REBUILD_RIGGED = 0x00001000, - ON_SHIFT_LIST = 0x00002000, - ACTIVE = 0x00004000, - DEAD = 0x00008000, - INVISIBLE = 0x00010000, // stay invisible until flag is cleared - NEARBY_LIGHT = 0x00020000, // In gPipeline.mNearbyLightSet - BUILT = 0x00040000, - FORCE_INVISIBLE = 0x00080000, // stay invis until CLEAR_INVISIBLE is set (set of orphaned) - HAS_ALPHA = 0x00100000, - RIGGED = 0x00200000, //has a rigged face - RIGGED_CHILD = 0x00400000, //has a child with a rigged face - PARTITION_MOVE = 0x00800000, - ANIMATED_CHILD = 0x01000000, - ACTIVE_CHILD = 0x02000000, - FOR_UNLOAD = 0x04000000, //should be unload from memory - } EDrawableFlags; - -public: - LLXformMatrix mXform; - - // vis data - LLPointer mParent; - - F32 mDistanceWRTCamera; - - static F32 sCurPixelAngle; //current pixels per radian - -private: - U32 mState; - S32 mRenderType; - LLPointer mVObjp; - face_list_t mFaces; - LLPointer mSpatialBridge; - - F32 mRadius; - S32 mGeneration; - - LLVector3 mCurrentScale; - - static U32 sNumZombieDrawables; - static std::vector > sDeadList; -} LL_ALIGN_POSTFIX(16); - - -inline LLFace* LLDrawable::getFace(const S32 i) const -{ - //switch these asserts to LL_ERRS() -- davep - //llassert((U32)i < mFaces.size()); - //llassert(mFaces[i]); - - if ((U32) i >= mFaces.size()) - { - LL_WARNS() << "Invalid face index." << LL_ENDL; - return NULL; - } - - if (!mFaces[i]) - { - LL_WARNS() << "Null face found." << LL_ENDL; - return NULL; - } - - return mFaces[i]; -} - - -inline S32 LLDrawable::getNumFaces()const -{ - return (S32)mFaces.size(); -} - -#endif +/** + * @file lldrawable.h + * @brief LLDrawable class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_DRAWABLE_H +#define LL_DRAWABLE_H + +#include +#include + +#include "v2math.h" +#include "v3math.h" +#include "v4math.h" +#include "m4math.h" +#include "v4coloru.h" +#include "llvector4a.h" +#include "llquaternion.h" +#include "xform.h" +#include "llviewerobject.h" +#include "llrect.h" +#include "llappviewer.h" // for gFrameTimeSeconds +#include "llvieweroctree.h" +#include + +class LLCamera; +class LLDrawPool; +class LLDrawable; +class LLFace; +class LLFacePool; +class LLSpatialGroup; +class LLSpatialBridge; +class LLSpatialPartition; +class LLVOVolume; +class LLViewerTexture; + +// Can have multiple silhouettes for each object +const U32 SILHOUETTE_HIGHLIGHT = 0; + +// All data for new renderer goes into this class. +LL_ALIGN_PREFIX(16) +class LLDrawable + : public LLViewerOctreeEntryData +{ + LL_ALIGN_NEW; +public: + typedef std::vector face_list_t; + + LLDrawable(const LLDrawable& rhs) + : LLViewerOctreeEntryData(rhs) + { + *this = rhs; + } + + const LLDrawable& operator=(const LLDrawable& rhs) + { + LL_ERRS() << "Illegal operation!" << LL_ENDL; + return *this; + } + + static void initClass(); + + LLDrawable(LLViewerObject *vobj, bool new_entry = false); + + void markDead(); // Mark this drawable as dead + bool isDead() const { return isState(DEAD); } + bool isNew() const { return !isState(BUILT); } + bool isUnload() const { return isState(FOR_UNLOAD); } + + bool isLight() const; + + virtual void setVisible(LLCamera& camera_in, std::vector* results = NULL, bool for_select = false); + + LLSpatialGroup* getSpatialGroup()const {return (LLSpatialGroup*)getGroup();} + LLViewerRegion* getRegion() const { return mVObjp->getRegion(); } + const LLTextureEntry* getTextureEntry(U8 which) const { return mVObjp->getTE(which); } + LLPointer& getVObj() { return mVObjp; } + const LLViewerObject *getVObj() const { return mVObjp; } + LLVOVolume* getVOVolume() const; // cast mVObjp tp LLVOVolume if OK + + const LLMatrix4& getWorldMatrix() const { return mXform.getWorldMatrix(); } + const LLMatrix4& getRenderMatrix() const; + void setPosition(LLVector3 v) const { } + const LLVector3& getPosition() const { return mXform.getPosition(); } + const LLVector3& getWorldPosition() const { return mXform.getPositionW(); } + const LLVector3 getPositionAgent() const; + const LLVector3& getScale() const { return mCurrentScale; } + void setScale(const LLVector3& scale) { mCurrentScale = scale; } + const LLQuaternion& getWorldRotation() const { return mXform.getWorldRotation(); } + const LLQuaternion& getRotation() const { return mXform.getRotation(); } + F32 getIntensity() const { return llmin(mXform.getScale().mV[0], 4.f); } + S32 getLOD() const { return mVObjp ? mVObjp->getLOD() : 1; } + + void getMinMax(LLVector3& min,LLVector3& max) const { mXform.getMinMax(min,max); } + LLXformMatrix* getXform() { return &mXform; } + + U32 getState() const { return mState; } + bool isState (U32 bits) const { return ((mState & bits) != 0); } + void setState (U32 bits) { mState |= bits; } + void clearState(U32 bits) { mState &= ~bits; } + + bool isAvatar() const { return mVObjp.notNull() && mVObjp->isAvatar(); } + bool isRoot() const { return !mParent || mParent->isAvatar(); } + LLDrawable* getRoot(); + bool isSpatialRoot() const { return !mParent || mParent->isAvatar(); } + virtual bool isSpatialBridge() const { return false; } + virtual LLSpatialPartition* asPartition() { return NULL; } + LLDrawable* getParent() const { return mParent; } + + // must set parent through LLViewerObject:: () + //bool setParent(LLDrawable *parent); + + inline LLFace* getFace(const S32 i) const; + inline S32 getNumFaces() const; + face_list_t& getFaces() { return mFaces; } + const face_list_t& getFaces() const { return mFaces; } + + //void removeFace(const S32 i); // SJB: Avoid using this, it's slow + LLFace* addFace(LLFacePool *poolp, LLViewerTexture *texturep); + LLFace* addFace(const LLTextureEntry *te, LLViewerTexture *texturep); + LLFace* addFace(const LLTextureEntry *te, LLViewerTexture *texturep, LLViewerTexture *normalp); + LLFace* addFace(const LLTextureEntry *te, LLViewerTexture *texturep, LLViewerTexture *normalp, LLViewerTexture *specularp); + void deleteFaces(S32 offset, S32 count); + void setNumFaces(const S32 numFaces, LLFacePool *poolp, LLViewerTexture *texturep); + void setNumFacesFast(const S32 numFaces, LLFacePool *poolp, LLViewerTexture *texturep); + void mergeFaces(LLDrawable* src); + + void init(bool new_entry); + void unload(); + void destroy(); + + void update(); + F32 updateXform(bool undamped); + + virtual void makeActive(); + /*virtual*/ void makeStatic(bool warning_enabled = true); + + bool isActive() const { return isState(ACTIVE); } + bool isStatic() const { return !isActive(); } + bool isAnimating() const; + + virtual bool updateMove(); + virtual void movePartition(); + + void updateTexture(); + void updateMaterial(); + virtual void updateDistance(LLCamera& camera, bool force_update); + bool updateGeometry(); + void updateFaceSize(S32 idx); + + void updateSpecialHoverCursor(bool enabled); + + virtual void shiftPos(const LLVector4a &shift_vector); + + S32 getGeneration() const { return mGeneration; } + + bool getLit() const { return !isState(UNLIT); } + void setLit(bool lit) { lit ? clearState(UNLIT) : setState(UNLIT); } + + bool isVisible() const; + bool isRecentlyVisible() const; + + virtual void cleanupReferences(); + + void setGroup(LLViewerOctreeGroup* group); + void setRadius(const F32 radius); + F32 getRadius() const { return mRadius; } + F32 getVisibilityRadius() const; + + void updateUVMinMax(); // Updates the cache of sun space bounding box. + + const LLVector3& getBounds(LLVector3& min, LLVector3& max) const; + virtual void updateSpatialExtents(); + virtual void updateBinRadius(); + + void setRenderType(S32 type) { mRenderType = type; } + bool isRenderType(S32 type) { return mRenderType == type; } + S32 getRenderType() { return mRenderType; } + + // Debugging methods + S32 findReferences(LLDrawable *drawablep); // Not const because of @#$! iterators... + + LLSpatialPartition* getSpatialPartition(); + + void removeFromOctree(); + + void setSpatialBridge(LLSpatialBridge* bridge) { mSpatialBridge = (LLDrawable*) bridge; } + LLSpatialBridge* getSpatialBridge() { return (LLSpatialBridge*) (LLDrawable*) mSpatialBridge; } + + // Statics + static void incrementVisible(); + static void cleanupDeadDrawables(); + +protected: + ~LLDrawable() { destroy(); } + void moveUpdatePipeline(bool moved); + void updatePartition(); + bool updateMoveDamped(); + bool updateMoveUndamped(); + +public: + friend class LLPipeline; + friend class LLDrawPool; + friend class LLSpatialBridge; + + typedef std::unordered_set > drawable_set_t; + typedef std::set > ordered_drawable_set_t; + typedef std::vector > drawable_vector_t; + typedef std::list > drawable_list_t; + typedef std::queue > drawable_queue_t; + + struct CompareDistanceGreater + { + bool operator()(const LLDrawable* const& lhs, const LLDrawable* const& rhs) + { + return lhs->mDistanceWRTCamera < rhs->mDistanceWRTCamera; // farthest = last + } + }; + + struct CompareDistanceGreaterVisibleFirst + { + bool operator()(const LLDrawable* const& lhs, const LLDrawable* const& rhs) + { + if (lhs->isVisible() && !rhs->isVisible()) + { + return true; //visible things come first + } + else if (!lhs->isVisible() && rhs->isVisible()) + { + return false; //rhs is visible, comes first + } + + return lhs->mDistanceWRTCamera < rhs->mDistanceWRTCamera; // farthest = last + } + }; + + typedef enum e_drawable_flags + { + IN_REBUILD_Q = 0x00000001, + EARLY_MOVE = 0x00000004, + MOVE_UNDAMPED = 0x00000008, + ON_MOVE_LIST = 0x00000010, + UV = 0x00000020, + UNLIT = 0x00000040, + LIGHT = 0x00000080, + REBUILD_VOLUME = 0x00000100, //volume changed LOD or parameters, or vertex buffer changed + REBUILD_TCOORD = 0x00000200, //texture coordinates changed + REBUILD_COLOR = 0x00000400, //color changed + REBUILD_POSITION= 0x00000800, //vertex positions/normals changed + REBUILD_GEOMETRY= REBUILD_POSITION|REBUILD_TCOORD|REBUILD_COLOR, + REBUILD_MATERIAL= REBUILD_TCOORD|REBUILD_COLOR, + REBUILD_ALL = REBUILD_GEOMETRY|REBUILD_VOLUME, + REBUILD_RIGGED = 0x00001000, + ON_SHIFT_LIST = 0x00002000, + ACTIVE = 0x00004000, + DEAD = 0x00008000, + INVISIBLE = 0x00010000, // stay invisible until flag is cleared + NEARBY_LIGHT = 0x00020000, // In gPipeline.mNearbyLightSet + BUILT = 0x00040000, + FORCE_INVISIBLE = 0x00080000, // stay invis until CLEAR_INVISIBLE is set (set of orphaned) + HAS_ALPHA = 0x00100000, + RIGGED = 0x00200000, //has a rigged face + RIGGED_CHILD = 0x00400000, //has a child with a rigged face + PARTITION_MOVE = 0x00800000, + ANIMATED_CHILD = 0x01000000, + ACTIVE_CHILD = 0x02000000, + FOR_UNLOAD = 0x04000000, //should be unload from memory + } EDrawableFlags; + +public: + LLXformMatrix mXform; + + // vis data + LLPointer mParent; + + F32 mDistanceWRTCamera; + + static F32 sCurPixelAngle; //current pixels per radian + +private: + U32 mState; + S32 mRenderType; + LLPointer mVObjp; + face_list_t mFaces; + LLPointer mSpatialBridge; + + F32 mRadius; + S32 mGeneration; + + LLVector3 mCurrentScale; + + static U32 sNumZombieDrawables; + static std::vector > sDeadList; +} LL_ALIGN_POSTFIX(16); + + +inline LLFace* LLDrawable::getFace(const S32 i) const +{ + //switch these asserts to LL_ERRS() -- davep + //llassert((U32)i < mFaces.size()); + //llassert(mFaces[i]); + + if ((U32) i >= mFaces.size()) + { + LL_WARNS() << "Invalid face index." << LL_ENDL; + return NULL; + } + + if (!mFaces[i]) + { + LL_WARNS() << "Null face found." << LL_ENDL; + return NULL; + } + + return mFaces[i]; +} + + +inline S32 LLDrawable::getNumFaces()const +{ + return (S32)mFaces.size(); +} + +#endif diff --git a/indra/newview/lldrawpool.cpp b/indra/newview/lldrawpool.cpp index 366e1556e0..55479af047 100644 --- a/indra/newview/lldrawpool.cpp +++ b/indra/newview/lldrawpool.cpp @@ -1,851 +1,851 @@ -/** - * @file lldrawpool.cpp - * @brief LLDrawPool class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lldrawpool.h" -#include "llrender.h" -#include "llfasttimer.h" -#include "llviewercontrol.h" - -#include "lldrawable.h" -#include "lldrawpoolalpha.h" -#include "lldrawpoolavatar.h" -#include "lldrawpoolbump.h" -#include "lldrawpoolmaterials.h" -#include "lldrawpoolpbropaque.h" -#include "lldrawpoolsimple.h" -#include "lldrawpoolsky.h" -#include "lldrawpooltree.h" -#include "lldrawpoolterrain.h" -#include "lldrawpoolwater.h" -#include "llface.h" -#include "llviewerobjectlist.h" // For debug listing. -#include "pipeline.h" -#include "llspatialpartition.h" -#include "llviewercamera.h" -#include "lldrawpoolwlsky.h" -#include "llglslshader.h" -#include "llglcommonfunc.h" -#include "llvoavatar.h" -#include "llviewershadermgr.h" - -S32 LLDrawPool::sNumDrawPools = 0; - -//============================= -// Draw Pool Implementation -//============================= -LLDrawPool *LLDrawPool::createPool(const U32 type, LLViewerTexture *tex0) -{ - LLDrawPool *poolp = NULL; - switch (type) - { - case POOL_SIMPLE: - poolp = new LLDrawPoolSimple(); - break; - case POOL_GRASS: - poolp = new LLDrawPoolGrass(); - break; - case POOL_ALPHA_MASK: - poolp = new LLDrawPoolAlphaMask(); - break; - case POOL_FULLBRIGHT_ALPHA_MASK: - poolp = new LLDrawPoolFullbrightAlphaMask(); - break; - case POOL_FULLBRIGHT: - poolp = new LLDrawPoolFullbright(); - break; - case POOL_GLOW: - poolp = new LLDrawPoolGlow(); - break; - case POOL_ALPHA_PRE_WATER: - poolp = new LLDrawPoolAlpha(LLDrawPool::POOL_ALPHA_PRE_WATER); - break; - case POOL_ALPHA_POST_WATER: - poolp = new LLDrawPoolAlpha(LLDrawPool::POOL_ALPHA_POST_WATER); - break; - case POOL_AVATAR: - case POOL_CONTROL_AV: - poolp = new LLDrawPoolAvatar(type); - break; - case POOL_TREE: - poolp = new LLDrawPoolTree(tex0); - break; - case POOL_TERRAIN: - poolp = new LLDrawPoolTerrain(tex0); - break; - case POOL_SKY: - poolp = new LLDrawPoolSky(); - break; - case POOL_VOIDWATER: - case POOL_WATER: - poolp = new LLDrawPoolWater(); - break; - case POOL_BUMP: - poolp = new LLDrawPoolBump(); - break; - case POOL_MATERIALS: - poolp = new LLDrawPoolMaterials(); - break; - case POOL_WL_SKY: - poolp = new LLDrawPoolWLSky(); - break; - case POOL_GLTF_PBR: - poolp = new LLDrawPoolGLTFPBR(); - break; - case POOL_GLTF_PBR_ALPHA_MASK: - poolp = new LLDrawPoolGLTFPBR(LLDrawPool::POOL_GLTF_PBR_ALPHA_MASK); - break; - default: - LL_ERRS() << "Unknown draw pool type!" << LL_ENDL; - return NULL; - } - - llassert(poolp->mType == type); - return poolp; -} - -LLDrawPool::LLDrawPool(const U32 type) -{ - mType = type; - sNumDrawPools++; - mId = sNumDrawPools; - mShaderLevel = 0; - mSkipRender = false; -} - -LLDrawPool::~LLDrawPool() -{ - -} - -LLViewerTexture *LLDrawPool::getDebugTexture() -{ - return NULL; -} - -//virtual -void LLDrawPool::beginRenderPass( S32 pass ) -{ -} - -//virtual -S32 LLDrawPool::getNumPasses() -{ - return 1; -} - -//virtual -void LLDrawPool::beginDeferredPass(S32 pass) -{ - -} - -//virtual -void LLDrawPool::endDeferredPass(S32 pass) -{ - -} - -//virtual -S32 LLDrawPool::getNumDeferredPasses() -{ - return 0; -} - -//virtual -void LLDrawPool::renderDeferred(S32 pass) -{ - -} - -//virtual -void LLDrawPool::beginPostDeferredPass(S32 pass) -{ - -} - -//virtual -void LLDrawPool::endPostDeferredPass(S32 pass) -{ - -} - -//virtual -S32 LLDrawPool::getNumPostDeferredPasses() -{ - return 0; -} - -//virtual -void LLDrawPool::renderPostDeferred(S32 pass) -{ - -} - -//virtual -void LLDrawPool::endRenderPass( S32 pass ) -{ - //make sure channel 0 is active channel - gGL.getTexUnit(0)->activate(); -} - -//virtual -void LLDrawPool::beginShadowPass(S32 pass) -{ - -} - -//virtual -void LLDrawPool::endShadowPass(S32 pass) -{ - -} - -//virtual -S32 LLDrawPool::getNumShadowPasses() -{ - return 0; -} - -//virtual -void LLDrawPool::renderShadow(S32 pass) -{ - -} - -//============================= -// Face Pool Implementation -//============================= -LLFacePool::LLFacePool(const U32 type) -: LLDrawPool(type) -{ - resetDrawOrders(); -} - -LLFacePool::~LLFacePool() -{ - destroy(); -} - -void LLFacePool::destroy() -{ - if (!mReferences.empty()) - { - LL_INFOS() << mReferences.size() << " references left on deletion of draw pool!" << LL_ENDL; - } -} - -void LLFacePool::dirtyTextures(const std::set& textures) -{ -} - -void LLFacePool::enqueue(LLFace* facep) -{ - mDrawFace.push_back(facep); -} - -// virtual -bool LLFacePool::addFace(LLFace *facep) -{ - addFaceReference(facep); - return true; -} - -// virtual -bool LLFacePool::removeFace(LLFace *facep) -{ - removeFaceReference(facep); - - vector_replace_with_last(mDrawFace, facep); - - return true; -} - -// Not absolutely sure if we should be resetting all of the chained pools as well - djs -void LLFacePool::resetDrawOrders() -{ - mDrawFace.resize(0); -} - -LLViewerTexture *LLFacePool::getTexture() -{ - return NULL; -} - -void LLFacePool::removeFaceReference(LLFace *facep) -{ - if (facep->getReferenceIndex() != -1) - { - if (facep->getReferenceIndex() != (S32)mReferences.size()) - { - LLFace *back = mReferences.back(); - mReferences[facep->getReferenceIndex()] = back; - back->setReferenceIndex(facep->getReferenceIndex()); - } - mReferences.pop_back(); - } - facep->setReferenceIndex(-1); -} - -void LLFacePool::addFaceReference(LLFace *facep) -{ - if (-1 == facep->getReferenceIndex()) - { - facep->setReferenceIndex(mReferences.size()); - mReferences.push_back(facep); - } -} - -void LLFacePool::pushFaceGeometry() -{ - for (LLFace* const& face : mDrawFace) - { - face->renderIndexed(); - } -} - -bool LLFacePool::verify() const -{ - bool ok = true; - - for (std::vector::const_iterator iter = mDrawFace.begin(); - iter != mDrawFace.end(); iter++) - { - const LLFace* facep = *iter; - if (facep->getPool() != this) - { - LL_INFOS() << "Face in wrong pool!" << LL_ENDL; - facep->printDebugInfo(); - ok = false; - } - else if (!facep->verify()) - { - ok = false; - } - } - - return ok; -} - -void LLFacePool::printDebugInfo() const -{ - LL_INFOS() << "Pool " << this << " Type: " << getType() << LL_ENDL; -} - -bool LLFacePool::LLOverrideFaceColor::sOverrideFaceColor = false; - -void LLFacePool::LLOverrideFaceColor::setColor(const LLColor4& color) -{ - gGL.diffuseColor4fv(color.mV); -} - -void LLFacePool::LLOverrideFaceColor::setColor(const LLColor4U& color) -{ - glColor4ubv(color.mV); -} - -void LLFacePool::LLOverrideFaceColor::setColor(F32 r, F32 g, F32 b, F32 a) -{ - gGL.diffuseColor4f(r,g,b,a); -} - - -//============================= -// Render Pass Implementation -//============================= -LLRenderPass::LLRenderPass(const U32 type) -: LLDrawPool(type) -{ - -} - -LLRenderPass::~LLRenderPass() -{ - -} - -void LLRenderPass::renderGroup(LLSpatialGroup* group, U32 type, bool texture) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - LLSpatialGroup::drawmap_elem_t& draw_info = group->mDrawMap[type]; - - for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k) - { - LLDrawInfo *pparams = *k; - if (pparams) - { - pushBatch(*pparams, texture); - } - } -} - -void LLRenderPass::renderRiggedGroup(LLSpatialGroup* group, U32 type, bool texture) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - LLSpatialGroup::drawmap_elem_t& draw_info = group->mDrawMap[type]; - LLVOAvatar* lastAvatar = nullptr; - U64 lastMeshId = 0; - - for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k) - { - LLDrawInfo* pparams = *k; - if (pparams) - { - if (lastAvatar != pparams->mAvatar || lastMeshId != pparams->mSkinInfo->mHash) - { - uploadMatrixPalette(*pparams); - lastAvatar = pparams->mAvatar; - lastMeshId = pparams->mSkinInfo->mHash; - } - - pushBatch(*pparams, texture); - } - } -} - -void LLRenderPass::pushBatches(U32 type, bool texture, bool batch_textures) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - if (texture) - { - auto* begin = gPipeline.beginRenderMap(type); - auto* end = gPipeline.endRenderMap(type); - for (LLCullResult::drawinfo_iterator i = begin; i != end; ) - { - LLDrawInfo* pparams = *i; - LLCullResult::increment_iterator(i, end); - - pushBatch(*pparams, texture, batch_textures); - } - } - else - { - pushUntexturedBatches(type); - } -} - -void LLRenderPass::pushUntexturedBatches(U32 type) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - auto* begin = gPipeline.beginRenderMap(type); - auto* end = gPipeline.endRenderMap(type); - for (LLCullResult::drawinfo_iterator i = begin; i != end; ) - { - LLDrawInfo* pparams = *i; - LLCullResult::increment_iterator(i, end); - - pushUntexturedBatch(*pparams); - } -} - -void LLRenderPass::pushRiggedBatches(U32 type, bool texture, bool batch_textures) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - - if (texture) - { - LLVOAvatar* lastAvatar = nullptr; - U64 lastMeshId = 0; - auto* begin = gPipeline.beginRenderMap(type); - auto* end = gPipeline.endRenderMap(type); - for (LLCullResult::drawinfo_iterator i = begin; i != end; ) - { - LLDrawInfo* pparams = *i; - LLCullResult::increment_iterator(i, end); - - if (pparams->mAvatar.notNull() && (lastAvatar != pparams->mAvatar || lastMeshId != pparams->mSkinInfo->mHash)) - { - uploadMatrixPalette(*pparams); - lastAvatar = pparams->mAvatar; - lastMeshId = pparams->mSkinInfo->mHash; - } - - pushBatch(*pparams, texture, batch_textures); - } - } - else - { - pushUntexturedRiggedBatches(type); - } -} - -void LLRenderPass::pushUntexturedRiggedBatches(U32 type) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - LLVOAvatar* lastAvatar = nullptr; - U64 lastMeshId = 0; - auto* begin = gPipeline.beginRenderMap(type); - auto* end = gPipeline.endRenderMap(type); - for (LLCullResult::drawinfo_iterator i = begin; i != end; ) - { - LLDrawInfo* pparams = *i; - LLCullResult::increment_iterator(i, end); - - if (pparams->mAvatar.notNull() && (lastAvatar != pparams->mAvatar || lastMeshId != pparams->mSkinInfo->mHash)) - { - uploadMatrixPalette(*pparams); - lastAvatar = pparams->mAvatar; - lastMeshId = pparams->mSkinInfo->mHash; - } - - pushUntexturedBatch(*pparams); - } -} - -void LLRenderPass::pushMaskBatches(U32 type, bool texture, bool batch_textures) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - auto* begin = gPipeline.beginRenderMap(type); - auto* end = gPipeline.endRenderMap(type); - for (LLCullResult::drawinfo_iterator i = begin; i != end; ) - { - LLDrawInfo* pparams = *i; - LLCullResult::increment_iterator(i, end); - LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(pparams->mAlphaMaskCutoff); - pushBatch(*pparams, texture, batch_textures); - } -} - -void LLRenderPass::pushRiggedMaskBatches(U32 type, bool texture, bool batch_textures) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - LLVOAvatar* lastAvatar = nullptr; - U64 lastMeshId = 0; - auto* begin = gPipeline.beginRenderMap(type); - auto* end = gPipeline.endRenderMap(type); - for (LLCullResult::drawinfo_iterator i = begin; i != end; ) - { - LLDrawInfo* pparams = *i; - - LLCullResult::increment_iterator(i, end); - - llassert(pparams); - - if (LLGLSLShader::sCurBoundShaderPtr) - { - LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(pparams->mAlphaMaskCutoff); - } - else - { - gGL.flush(); - } - - if (lastAvatar != pparams->mAvatar || lastMeshId != pparams->mSkinInfo->mHash) - { - uploadMatrixPalette(*pparams); - lastAvatar = pparams->mAvatar; - lastMeshId = pparams->mSkinInfo->mHash; - } - - pushBatch(*pparams, texture, batch_textures); - } -} - -void LLRenderPass::applyModelMatrix(const LLDrawInfo& params) -{ - if (params.mModelMatrix != gGLLastMatrix) - { - gGLLastMatrix = params.mModelMatrix; - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.loadMatrix(gGLModelView); - if (params.mModelMatrix) - { - gGL.multMatrix((GLfloat*) params.mModelMatrix->mMatrix); - } - gPipeline.mMatrixOpCount++; - } -} - -void LLRenderPass::pushBatch(LLDrawInfo& params, bool texture, bool batch_textures) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - llassert(texture); - - if (!params.mCount) - { - return; - } - - applyModelMatrix(params); - - bool tex_setup = false; - - { - if (batch_textures && params.mTextureList.size() > 1) - { - for (U32 i = 0; i < params.mTextureList.size(); ++i) - { - if (params.mTextureList[i].notNull()) - { - gGL.getTexUnit(i)->bindFast(params.mTextureList[i]); - } - } - } - else - { //not batching textures or batch has only 1 texture -- might need a texture matrix - if (params.mTexture.notNull()) - { - gGL.getTexUnit(0)->bindFast(params.mTexture); - if (params.mTextureMatrix) - { - tex_setup = true; - gGL.getTexUnit(0)->activate(); - gGL.matrixMode(LLRender::MM_TEXTURE); - gGL.loadMatrix((GLfloat*) params.mTextureMatrix->mMatrix); - gPipeline.mTextureMatrixOps++; - } - } - else - { - gGL.getTexUnit(0)->unbindFast(LLTexUnit::TT_TEXTURE); - } - } - } - - params.mVertexBuffer->setBuffer(); - params.mVertexBuffer->drawRange(LLRender::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset); - - if (tex_setup) - { - gGL.matrixMode(LLRender::MM_TEXTURE0); - gGL.loadIdentity(); - gGL.matrixMode(LLRender::MM_MODELVIEW); - } -} - -void LLRenderPass::pushUntexturedBatch(LLDrawInfo& params) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - - if (!params.mCount) - { - return; - } - - applyModelMatrix(params); - - params.mVertexBuffer->setBuffer(); - params.mVertexBuffer->drawRange(LLRender::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset); -} - -// static -bool LLRenderPass::uploadMatrixPalette(LLDrawInfo& params) -{ - // upload matrix palette to shader - return uploadMatrixPalette(params.mAvatar, params.mSkinInfo); -} - -//static -bool LLRenderPass::uploadMatrixPalette(LLVOAvatar* avatar, LLMeshSkinInfo* skinInfo) -{ - if (!avatar) - { - return false; - } - const LLVOAvatar::MatrixPaletteCache& mpc = avatar->updateSkinInfoMatrixPalette(skinInfo); - U32 count = mpc.mMatrixPalette.size(); - - if (count == 0) - { - //skin info not loaded yet, don't render - return false; - } - - LLGLSLShader::sCurBoundShaderPtr->uniformMatrix3x4fv(LLViewerShaderMgr::AVATAR_MATRIX, - count, - false, - (GLfloat*)&(mpc.mGLMp[0])); - - return true; -} - -void setup_texture_matrix(LLDrawInfo& params) -{ - if (params.mTextureMatrix) - { //special case implementation of texture animation here because of special handling of textures for PBR batches - gGL.getTexUnit(0)->activate(); - gGL.matrixMode(LLRender::MM_TEXTURE); - gGL.loadMatrix((GLfloat*)params.mTextureMatrix->mMatrix); - gPipeline.mTextureMatrixOps++; - } -} - -void teardown_texture_matrix(LLDrawInfo& params) -{ - if (params.mTextureMatrix) - { - gGL.matrixMode(LLRender::MM_TEXTURE0); - gGL.loadIdentity(); - gGL.matrixMode(LLRender::MM_MODELVIEW); - } -} - -void LLRenderPass::pushGLTFBatches(U32 type, bool textured) -{ - if (textured) - { - pushGLTFBatches(type); - } - else - { - pushRiggedGLTFBatches(type); - } -} - -void LLRenderPass::pushGLTFBatches(U32 type) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - auto* begin = gPipeline.beginRenderMap(type); - auto* end = gPipeline.endRenderMap(type); - for (LLCullResult::drawinfo_iterator i = begin; i != end; ) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("pushGLTFBatch"); - LLDrawInfo& params = **i; - LLCullResult::increment_iterator(i, end); - - pushGLTFBatch(params); - } -} - -void LLRenderPass::pushUntexturedGLTFBatches(U32 type) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - auto* begin = gPipeline.beginRenderMap(type); - auto* end = gPipeline.endRenderMap(type); - for (LLCullResult::drawinfo_iterator i = begin; i != end; ) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("pushGLTFBatch"); - LLDrawInfo& params = **i; - LLCullResult::increment_iterator(i, end); - - pushUntexturedGLTFBatch(params); - } -} - -void LLRenderPass::pushGLTFBatch(LLDrawInfo& params) -{ - auto& mat = params.mGLTFMaterial; - - mat->bind(params.mTexture); - - LLGLDisable cull_face(mat->mDoubleSided ? GL_CULL_FACE : 0); - - setup_texture_matrix(params); - - applyModelMatrix(params); - - params.mVertexBuffer->setBuffer(); - params.mVertexBuffer->drawRange(LLRender::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset); - - teardown_texture_matrix(params); -} - -void LLRenderPass::pushUntexturedGLTFBatch(LLDrawInfo& params) -{ - auto& mat = params.mGLTFMaterial; - - LLGLDisable cull_face(mat->mDoubleSided ? GL_CULL_FACE : 0); - - applyModelMatrix(params); - - params.mVertexBuffer->setBuffer(); - params.mVertexBuffer->drawRange(LLRender::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset); -} - -void LLRenderPass::pushRiggedGLTFBatches(U32 type, bool textured) -{ - if (textured) - { - pushRiggedGLTFBatches(type); - } - else - { - pushUntexturedRiggedGLTFBatches(type); - } -} - -void LLRenderPass::pushRiggedGLTFBatches(U32 type) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - LLVOAvatar* lastAvatar = nullptr; - U64 lastMeshId = 0; - - auto* begin = gPipeline.beginRenderMap(type); - auto* end = gPipeline.endRenderMap(type); - for (LLCullResult::drawinfo_iterator i = begin; i != end; ) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("pushRiggedGLTFBatch"); - LLDrawInfo& params = **i; - LLCullResult::increment_iterator(i, end); - - pushRiggedGLTFBatch(params, lastAvatar, lastMeshId); - } -} - -void LLRenderPass::pushUntexturedRiggedGLTFBatches(U32 type) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - LLVOAvatar* lastAvatar = nullptr; - U64 lastMeshId = 0; - - auto* begin = gPipeline.beginRenderMap(type); - auto* end = gPipeline.endRenderMap(type); - for (LLCullResult::drawinfo_iterator i = begin; i != end; ) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("pushRiggedGLTFBatch"); - LLDrawInfo& params = **i; - LLCullResult::increment_iterator(i, end); - - pushUntexturedRiggedGLTFBatch(params, lastAvatar, lastMeshId); - } -} - - -void LLRenderPass::pushRiggedGLTFBatch(LLDrawInfo& params, LLVOAvatar*& lastAvatar, U64& lastMeshId) -{ - if (params.mAvatar.notNull() && (lastAvatar != params.mAvatar || lastMeshId != params.mSkinInfo->mHash)) - { - uploadMatrixPalette(params); - lastAvatar = params.mAvatar; - lastMeshId = params.mSkinInfo->mHash; - } - - pushGLTFBatch(params); -} - -void LLRenderPass::pushUntexturedRiggedGLTFBatch(LLDrawInfo& params, LLVOAvatar*& lastAvatar, U64& lastMeshId) -{ - if (params.mAvatar.notNull() && (lastAvatar != params.mAvatar || lastMeshId != params.mSkinInfo->mHash)) - { - uploadMatrixPalette(params); - lastAvatar = params.mAvatar; - lastMeshId = params.mSkinInfo->mHash; - } - - pushUntexturedGLTFBatch(params); -} - +/** + * @file lldrawpool.cpp + * @brief LLDrawPool class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lldrawpool.h" +#include "llrender.h" +#include "llfasttimer.h" +#include "llviewercontrol.h" + +#include "lldrawable.h" +#include "lldrawpoolalpha.h" +#include "lldrawpoolavatar.h" +#include "lldrawpoolbump.h" +#include "lldrawpoolmaterials.h" +#include "lldrawpoolpbropaque.h" +#include "lldrawpoolsimple.h" +#include "lldrawpoolsky.h" +#include "lldrawpooltree.h" +#include "lldrawpoolterrain.h" +#include "lldrawpoolwater.h" +#include "llface.h" +#include "llviewerobjectlist.h" // For debug listing. +#include "pipeline.h" +#include "llspatialpartition.h" +#include "llviewercamera.h" +#include "lldrawpoolwlsky.h" +#include "llglslshader.h" +#include "llglcommonfunc.h" +#include "llvoavatar.h" +#include "llviewershadermgr.h" + +S32 LLDrawPool::sNumDrawPools = 0; + +//============================= +// Draw Pool Implementation +//============================= +LLDrawPool *LLDrawPool::createPool(const U32 type, LLViewerTexture *tex0) +{ + LLDrawPool *poolp = NULL; + switch (type) + { + case POOL_SIMPLE: + poolp = new LLDrawPoolSimple(); + break; + case POOL_GRASS: + poolp = new LLDrawPoolGrass(); + break; + case POOL_ALPHA_MASK: + poolp = new LLDrawPoolAlphaMask(); + break; + case POOL_FULLBRIGHT_ALPHA_MASK: + poolp = new LLDrawPoolFullbrightAlphaMask(); + break; + case POOL_FULLBRIGHT: + poolp = new LLDrawPoolFullbright(); + break; + case POOL_GLOW: + poolp = new LLDrawPoolGlow(); + break; + case POOL_ALPHA_PRE_WATER: + poolp = new LLDrawPoolAlpha(LLDrawPool::POOL_ALPHA_PRE_WATER); + break; + case POOL_ALPHA_POST_WATER: + poolp = new LLDrawPoolAlpha(LLDrawPool::POOL_ALPHA_POST_WATER); + break; + case POOL_AVATAR: + case POOL_CONTROL_AV: + poolp = new LLDrawPoolAvatar(type); + break; + case POOL_TREE: + poolp = new LLDrawPoolTree(tex0); + break; + case POOL_TERRAIN: + poolp = new LLDrawPoolTerrain(tex0); + break; + case POOL_SKY: + poolp = new LLDrawPoolSky(); + break; + case POOL_VOIDWATER: + case POOL_WATER: + poolp = new LLDrawPoolWater(); + break; + case POOL_BUMP: + poolp = new LLDrawPoolBump(); + break; + case POOL_MATERIALS: + poolp = new LLDrawPoolMaterials(); + break; + case POOL_WL_SKY: + poolp = new LLDrawPoolWLSky(); + break; + case POOL_GLTF_PBR: + poolp = new LLDrawPoolGLTFPBR(); + break; + case POOL_GLTF_PBR_ALPHA_MASK: + poolp = new LLDrawPoolGLTFPBR(LLDrawPool::POOL_GLTF_PBR_ALPHA_MASK); + break; + default: + LL_ERRS() << "Unknown draw pool type!" << LL_ENDL; + return NULL; + } + + llassert(poolp->mType == type); + return poolp; +} + +LLDrawPool::LLDrawPool(const U32 type) +{ + mType = type; + sNumDrawPools++; + mId = sNumDrawPools; + mShaderLevel = 0; + mSkipRender = false; +} + +LLDrawPool::~LLDrawPool() +{ + +} + +LLViewerTexture *LLDrawPool::getDebugTexture() +{ + return NULL; +} + +//virtual +void LLDrawPool::beginRenderPass( S32 pass ) +{ +} + +//virtual +S32 LLDrawPool::getNumPasses() +{ + return 1; +} + +//virtual +void LLDrawPool::beginDeferredPass(S32 pass) +{ + +} + +//virtual +void LLDrawPool::endDeferredPass(S32 pass) +{ + +} + +//virtual +S32 LLDrawPool::getNumDeferredPasses() +{ + return 0; +} + +//virtual +void LLDrawPool::renderDeferred(S32 pass) +{ + +} + +//virtual +void LLDrawPool::beginPostDeferredPass(S32 pass) +{ + +} + +//virtual +void LLDrawPool::endPostDeferredPass(S32 pass) +{ + +} + +//virtual +S32 LLDrawPool::getNumPostDeferredPasses() +{ + return 0; +} + +//virtual +void LLDrawPool::renderPostDeferred(S32 pass) +{ + +} + +//virtual +void LLDrawPool::endRenderPass( S32 pass ) +{ + //make sure channel 0 is active channel + gGL.getTexUnit(0)->activate(); +} + +//virtual +void LLDrawPool::beginShadowPass(S32 pass) +{ + +} + +//virtual +void LLDrawPool::endShadowPass(S32 pass) +{ + +} + +//virtual +S32 LLDrawPool::getNumShadowPasses() +{ + return 0; +} + +//virtual +void LLDrawPool::renderShadow(S32 pass) +{ + +} + +//============================= +// Face Pool Implementation +//============================= +LLFacePool::LLFacePool(const U32 type) +: LLDrawPool(type) +{ + resetDrawOrders(); +} + +LLFacePool::~LLFacePool() +{ + destroy(); +} + +void LLFacePool::destroy() +{ + if (!mReferences.empty()) + { + LL_INFOS() << mReferences.size() << " references left on deletion of draw pool!" << LL_ENDL; + } +} + +void LLFacePool::dirtyTextures(const std::set& textures) +{ +} + +void LLFacePool::enqueue(LLFace* facep) +{ + mDrawFace.push_back(facep); +} + +// virtual +bool LLFacePool::addFace(LLFace *facep) +{ + addFaceReference(facep); + return true; +} + +// virtual +bool LLFacePool::removeFace(LLFace *facep) +{ + removeFaceReference(facep); + + vector_replace_with_last(mDrawFace, facep); + + return true; +} + +// Not absolutely sure if we should be resetting all of the chained pools as well - djs +void LLFacePool::resetDrawOrders() +{ + mDrawFace.resize(0); +} + +LLViewerTexture *LLFacePool::getTexture() +{ + return NULL; +} + +void LLFacePool::removeFaceReference(LLFace *facep) +{ + if (facep->getReferenceIndex() != -1) + { + if (facep->getReferenceIndex() != (S32)mReferences.size()) + { + LLFace *back = mReferences.back(); + mReferences[facep->getReferenceIndex()] = back; + back->setReferenceIndex(facep->getReferenceIndex()); + } + mReferences.pop_back(); + } + facep->setReferenceIndex(-1); +} + +void LLFacePool::addFaceReference(LLFace *facep) +{ + if (-1 == facep->getReferenceIndex()) + { + facep->setReferenceIndex(mReferences.size()); + mReferences.push_back(facep); + } +} + +void LLFacePool::pushFaceGeometry() +{ + for (LLFace* const& face : mDrawFace) + { + face->renderIndexed(); + } +} + +bool LLFacePool::verify() const +{ + bool ok = true; + + for (std::vector::const_iterator iter = mDrawFace.begin(); + iter != mDrawFace.end(); iter++) + { + const LLFace* facep = *iter; + if (facep->getPool() != this) + { + LL_INFOS() << "Face in wrong pool!" << LL_ENDL; + facep->printDebugInfo(); + ok = false; + } + else if (!facep->verify()) + { + ok = false; + } + } + + return ok; +} + +void LLFacePool::printDebugInfo() const +{ + LL_INFOS() << "Pool " << this << " Type: " << getType() << LL_ENDL; +} + +bool LLFacePool::LLOverrideFaceColor::sOverrideFaceColor = false; + +void LLFacePool::LLOverrideFaceColor::setColor(const LLColor4& color) +{ + gGL.diffuseColor4fv(color.mV); +} + +void LLFacePool::LLOverrideFaceColor::setColor(const LLColor4U& color) +{ + glColor4ubv(color.mV); +} + +void LLFacePool::LLOverrideFaceColor::setColor(F32 r, F32 g, F32 b, F32 a) +{ + gGL.diffuseColor4f(r,g,b,a); +} + + +//============================= +// Render Pass Implementation +//============================= +LLRenderPass::LLRenderPass(const U32 type) +: LLDrawPool(type) +{ + +} + +LLRenderPass::~LLRenderPass() +{ + +} + +void LLRenderPass::renderGroup(LLSpatialGroup* group, U32 type, bool texture) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + LLSpatialGroup::drawmap_elem_t& draw_info = group->mDrawMap[type]; + + for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k) + { + LLDrawInfo *pparams = *k; + if (pparams) + { + pushBatch(*pparams, texture); + } + } +} + +void LLRenderPass::renderRiggedGroup(LLSpatialGroup* group, U32 type, bool texture) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + LLSpatialGroup::drawmap_elem_t& draw_info = group->mDrawMap[type]; + LLVOAvatar* lastAvatar = nullptr; + U64 lastMeshId = 0; + + for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k) + { + LLDrawInfo* pparams = *k; + if (pparams) + { + if (lastAvatar != pparams->mAvatar || lastMeshId != pparams->mSkinInfo->mHash) + { + uploadMatrixPalette(*pparams); + lastAvatar = pparams->mAvatar; + lastMeshId = pparams->mSkinInfo->mHash; + } + + pushBatch(*pparams, texture); + } + } +} + +void LLRenderPass::pushBatches(U32 type, bool texture, bool batch_textures) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + if (texture) + { + auto* begin = gPipeline.beginRenderMap(type); + auto* end = gPipeline.endRenderMap(type); + for (LLCullResult::drawinfo_iterator i = begin; i != end; ) + { + LLDrawInfo* pparams = *i; + LLCullResult::increment_iterator(i, end); + + pushBatch(*pparams, texture, batch_textures); + } + } + else + { + pushUntexturedBatches(type); + } +} + +void LLRenderPass::pushUntexturedBatches(U32 type) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + auto* begin = gPipeline.beginRenderMap(type); + auto* end = gPipeline.endRenderMap(type); + for (LLCullResult::drawinfo_iterator i = begin; i != end; ) + { + LLDrawInfo* pparams = *i; + LLCullResult::increment_iterator(i, end); + + pushUntexturedBatch(*pparams); + } +} + +void LLRenderPass::pushRiggedBatches(U32 type, bool texture, bool batch_textures) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + + if (texture) + { + LLVOAvatar* lastAvatar = nullptr; + U64 lastMeshId = 0; + auto* begin = gPipeline.beginRenderMap(type); + auto* end = gPipeline.endRenderMap(type); + for (LLCullResult::drawinfo_iterator i = begin; i != end; ) + { + LLDrawInfo* pparams = *i; + LLCullResult::increment_iterator(i, end); + + if (pparams->mAvatar.notNull() && (lastAvatar != pparams->mAvatar || lastMeshId != pparams->mSkinInfo->mHash)) + { + uploadMatrixPalette(*pparams); + lastAvatar = pparams->mAvatar; + lastMeshId = pparams->mSkinInfo->mHash; + } + + pushBatch(*pparams, texture, batch_textures); + } + } + else + { + pushUntexturedRiggedBatches(type); + } +} + +void LLRenderPass::pushUntexturedRiggedBatches(U32 type) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + LLVOAvatar* lastAvatar = nullptr; + U64 lastMeshId = 0; + auto* begin = gPipeline.beginRenderMap(type); + auto* end = gPipeline.endRenderMap(type); + for (LLCullResult::drawinfo_iterator i = begin; i != end; ) + { + LLDrawInfo* pparams = *i; + LLCullResult::increment_iterator(i, end); + + if (pparams->mAvatar.notNull() && (lastAvatar != pparams->mAvatar || lastMeshId != pparams->mSkinInfo->mHash)) + { + uploadMatrixPalette(*pparams); + lastAvatar = pparams->mAvatar; + lastMeshId = pparams->mSkinInfo->mHash; + } + + pushUntexturedBatch(*pparams); + } +} + +void LLRenderPass::pushMaskBatches(U32 type, bool texture, bool batch_textures) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + auto* begin = gPipeline.beginRenderMap(type); + auto* end = gPipeline.endRenderMap(type); + for (LLCullResult::drawinfo_iterator i = begin; i != end; ) + { + LLDrawInfo* pparams = *i; + LLCullResult::increment_iterator(i, end); + LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(pparams->mAlphaMaskCutoff); + pushBatch(*pparams, texture, batch_textures); + } +} + +void LLRenderPass::pushRiggedMaskBatches(U32 type, bool texture, bool batch_textures) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + LLVOAvatar* lastAvatar = nullptr; + U64 lastMeshId = 0; + auto* begin = gPipeline.beginRenderMap(type); + auto* end = gPipeline.endRenderMap(type); + for (LLCullResult::drawinfo_iterator i = begin; i != end; ) + { + LLDrawInfo* pparams = *i; + + LLCullResult::increment_iterator(i, end); + + llassert(pparams); + + if (LLGLSLShader::sCurBoundShaderPtr) + { + LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(pparams->mAlphaMaskCutoff); + } + else + { + gGL.flush(); + } + + if (lastAvatar != pparams->mAvatar || lastMeshId != pparams->mSkinInfo->mHash) + { + uploadMatrixPalette(*pparams); + lastAvatar = pparams->mAvatar; + lastMeshId = pparams->mSkinInfo->mHash; + } + + pushBatch(*pparams, texture, batch_textures); + } +} + +void LLRenderPass::applyModelMatrix(const LLDrawInfo& params) +{ + if (params.mModelMatrix != gGLLastMatrix) + { + gGLLastMatrix = params.mModelMatrix; + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.loadMatrix(gGLModelView); + if (params.mModelMatrix) + { + gGL.multMatrix((GLfloat*) params.mModelMatrix->mMatrix); + } + gPipeline.mMatrixOpCount++; + } +} + +void LLRenderPass::pushBatch(LLDrawInfo& params, bool texture, bool batch_textures) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + llassert(texture); + + if (!params.mCount) + { + return; + } + + applyModelMatrix(params); + + bool tex_setup = false; + + { + if (batch_textures && params.mTextureList.size() > 1) + { + for (U32 i = 0; i < params.mTextureList.size(); ++i) + { + if (params.mTextureList[i].notNull()) + { + gGL.getTexUnit(i)->bindFast(params.mTextureList[i]); + } + } + } + else + { //not batching textures or batch has only 1 texture -- might need a texture matrix + if (params.mTexture.notNull()) + { + gGL.getTexUnit(0)->bindFast(params.mTexture); + if (params.mTextureMatrix) + { + tex_setup = true; + gGL.getTexUnit(0)->activate(); + gGL.matrixMode(LLRender::MM_TEXTURE); + gGL.loadMatrix((GLfloat*) params.mTextureMatrix->mMatrix); + gPipeline.mTextureMatrixOps++; + } + } + else + { + gGL.getTexUnit(0)->unbindFast(LLTexUnit::TT_TEXTURE); + } + } + } + + params.mVertexBuffer->setBuffer(); + params.mVertexBuffer->drawRange(LLRender::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset); + + if (tex_setup) + { + gGL.matrixMode(LLRender::MM_TEXTURE0); + gGL.loadIdentity(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + } +} + +void LLRenderPass::pushUntexturedBatch(LLDrawInfo& params) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + + if (!params.mCount) + { + return; + } + + applyModelMatrix(params); + + params.mVertexBuffer->setBuffer(); + params.mVertexBuffer->drawRange(LLRender::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset); +} + +// static +bool LLRenderPass::uploadMatrixPalette(LLDrawInfo& params) +{ + // upload matrix palette to shader + return uploadMatrixPalette(params.mAvatar, params.mSkinInfo); +} + +//static +bool LLRenderPass::uploadMatrixPalette(LLVOAvatar* avatar, LLMeshSkinInfo* skinInfo) +{ + if (!avatar) + { + return false; + } + const LLVOAvatar::MatrixPaletteCache& mpc = avatar->updateSkinInfoMatrixPalette(skinInfo); + U32 count = mpc.mMatrixPalette.size(); + + if (count == 0) + { + //skin info not loaded yet, don't render + return false; + } + + LLGLSLShader::sCurBoundShaderPtr->uniformMatrix3x4fv(LLViewerShaderMgr::AVATAR_MATRIX, + count, + false, + (GLfloat*)&(mpc.mGLMp[0])); + + return true; +} + +void setup_texture_matrix(LLDrawInfo& params) +{ + if (params.mTextureMatrix) + { //special case implementation of texture animation here because of special handling of textures for PBR batches + gGL.getTexUnit(0)->activate(); + gGL.matrixMode(LLRender::MM_TEXTURE); + gGL.loadMatrix((GLfloat*)params.mTextureMatrix->mMatrix); + gPipeline.mTextureMatrixOps++; + } +} + +void teardown_texture_matrix(LLDrawInfo& params) +{ + if (params.mTextureMatrix) + { + gGL.matrixMode(LLRender::MM_TEXTURE0); + gGL.loadIdentity(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + } +} + +void LLRenderPass::pushGLTFBatches(U32 type, bool textured) +{ + if (textured) + { + pushGLTFBatches(type); + } + else + { + pushRiggedGLTFBatches(type); + } +} + +void LLRenderPass::pushGLTFBatches(U32 type) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + auto* begin = gPipeline.beginRenderMap(type); + auto* end = gPipeline.endRenderMap(type); + for (LLCullResult::drawinfo_iterator i = begin; i != end; ) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("pushGLTFBatch"); + LLDrawInfo& params = **i; + LLCullResult::increment_iterator(i, end); + + pushGLTFBatch(params); + } +} + +void LLRenderPass::pushUntexturedGLTFBatches(U32 type) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + auto* begin = gPipeline.beginRenderMap(type); + auto* end = gPipeline.endRenderMap(type); + for (LLCullResult::drawinfo_iterator i = begin; i != end; ) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("pushGLTFBatch"); + LLDrawInfo& params = **i; + LLCullResult::increment_iterator(i, end); + + pushUntexturedGLTFBatch(params); + } +} + +void LLRenderPass::pushGLTFBatch(LLDrawInfo& params) +{ + auto& mat = params.mGLTFMaterial; + + mat->bind(params.mTexture); + + LLGLDisable cull_face(mat->mDoubleSided ? GL_CULL_FACE : 0); + + setup_texture_matrix(params); + + applyModelMatrix(params); + + params.mVertexBuffer->setBuffer(); + params.mVertexBuffer->drawRange(LLRender::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset); + + teardown_texture_matrix(params); +} + +void LLRenderPass::pushUntexturedGLTFBatch(LLDrawInfo& params) +{ + auto& mat = params.mGLTFMaterial; + + LLGLDisable cull_face(mat->mDoubleSided ? GL_CULL_FACE : 0); + + applyModelMatrix(params); + + params.mVertexBuffer->setBuffer(); + params.mVertexBuffer->drawRange(LLRender::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset); +} + +void LLRenderPass::pushRiggedGLTFBatches(U32 type, bool textured) +{ + if (textured) + { + pushRiggedGLTFBatches(type); + } + else + { + pushUntexturedRiggedGLTFBatches(type); + } +} + +void LLRenderPass::pushRiggedGLTFBatches(U32 type) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + LLVOAvatar* lastAvatar = nullptr; + U64 lastMeshId = 0; + + auto* begin = gPipeline.beginRenderMap(type); + auto* end = gPipeline.endRenderMap(type); + for (LLCullResult::drawinfo_iterator i = begin; i != end; ) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("pushRiggedGLTFBatch"); + LLDrawInfo& params = **i; + LLCullResult::increment_iterator(i, end); + + pushRiggedGLTFBatch(params, lastAvatar, lastMeshId); + } +} + +void LLRenderPass::pushUntexturedRiggedGLTFBatches(U32 type) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + LLVOAvatar* lastAvatar = nullptr; + U64 lastMeshId = 0; + + auto* begin = gPipeline.beginRenderMap(type); + auto* end = gPipeline.endRenderMap(type); + for (LLCullResult::drawinfo_iterator i = begin; i != end; ) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("pushRiggedGLTFBatch"); + LLDrawInfo& params = **i; + LLCullResult::increment_iterator(i, end); + + pushUntexturedRiggedGLTFBatch(params, lastAvatar, lastMeshId); + } +} + + +void LLRenderPass::pushRiggedGLTFBatch(LLDrawInfo& params, LLVOAvatar*& lastAvatar, U64& lastMeshId) +{ + if (params.mAvatar.notNull() && (lastAvatar != params.mAvatar || lastMeshId != params.mSkinInfo->mHash)) + { + uploadMatrixPalette(params); + lastAvatar = params.mAvatar; + lastMeshId = params.mSkinInfo->mHash; + } + + pushGLTFBatch(params); +} + +void LLRenderPass::pushUntexturedRiggedGLTFBatch(LLDrawInfo& params, LLVOAvatar*& lastAvatar, U64& lastMeshId) +{ + if (params.mAvatar.notNull() && (lastAvatar != params.mAvatar || lastMeshId != params.mSkinInfo->mHash)) + { + uploadMatrixPalette(params); + lastAvatar = params.mAvatar; + lastMeshId = params.mSkinInfo->mHash; + } + + pushUntexturedGLTFBatch(params); +} + diff --git a/indra/newview/lldrawpool.h b/indra/newview/lldrawpool.h index 8627b171de..1f1d9e2121 100644 --- a/indra/newview/lldrawpool.h +++ b/indra/newview/lldrawpool.h @@ -1,483 +1,483 @@ -/** - * @file lldrawpool.h - * @brief LLDrawPool class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLDRAWPOOL_H -#define LL_LLDRAWPOOL_H - -#include "v4coloru.h" -#include "v2math.h" -#include "v3math.h" -#include "llvertexbuffer.h" - -class LLFace; -class LLViewerTexture; -class LLViewerFetchedTexture; -class LLSpatialGroup; -class LLDrawInfo; -class LLVOAvatar; -class LLMeshSkinInfo; - -class LLDrawPool -{ -public: - static S32 sNumDrawPools; - - enum - { - // Correspond to LLPipeline render type - // Also controls render order, so passes that don't use alpha masking/blending should come before - // other passes to preserve hierarchical Z for occlusion queries. Occlusion queries happen just - // before grass, so grass should be the first alpha masked pool. Other ordering should be done - // based on fill rate and likelihood to occlude future passes (faster, large occluders first). - // - POOL_SKY = 1, - POOL_WL_SKY, - POOL_SIMPLE, - POOL_FULLBRIGHT, - POOL_BUMP, - POOL_TERRAIN, - POOL_MATERIALS, - POOL_GLTF_PBR, - POOL_GRASS, - POOL_GLTF_PBR_ALPHA_MASK, - POOL_TREE, - POOL_ALPHA_MASK, - POOL_FULLBRIGHT_ALPHA_MASK, - POOL_AVATAR, - POOL_CONTROL_AV, // Animesh - POOL_GLOW, - POOL_ALPHA_PRE_WATER, - POOL_VOIDWATER, - POOL_WATER, - POOL_ALPHA_POST_WATER, - POOL_ALPHA, // note there is no actual "POOL_ALPHA" but pre-water and post-water pools consume POOL_ALPHA faces - NUM_POOL_TYPES, - }; - - LLDrawPool(const U32 type); - virtual ~LLDrawPool(); - - virtual bool isDead() = 0; - - S32 getId() const { return mId; } - U32 getType() const { return mType; } - - bool getSkipRenderFlag() const { return mSkipRender;} - void setSkipRenderFlag( bool flag ) { mSkipRender = flag; } - - virtual LLViewerTexture *getDebugTexture(); - virtual void beginRenderPass( S32 pass ); - virtual void endRenderPass( S32 pass ); - virtual S32 getNumPasses(); - - virtual void beginDeferredPass(S32 pass); - virtual void endDeferredPass(S32 pass); - virtual S32 getNumDeferredPasses(); - virtual void renderDeferred(S32 pass = 0); - - virtual void beginPostDeferredPass(S32 pass); - virtual void endPostDeferredPass(S32 pass); - virtual S32 getNumPostDeferredPasses(); - virtual void renderPostDeferred(S32 pass = 0); - - virtual void beginShadowPass(S32 pass); - virtual void endShadowPass(S32 pass); - virtual S32 getNumShadowPasses(); - virtual void renderShadow(S32 pass = 0); - - virtual void render(S32 pass = 0) {}; - virtual void prerender() {}; - virtual U32 getVertexDataMask() { return 0; } // DEPRECATED -- draw pool doesn't actually determine vertex data mask any more - virtual bool verify() const { return true; } // Verify that all data in the draw pool is correct! - virtual S32 getShaderLevel() const { return mShaderLevel; } - - static LLDrawPool* createPool(const U32 type, LLViewerTexture *tex0 = NULL); - virtual LLViewerTexture* getTexture() = 0; - virtual bool isFacePool() { return false; } - virtual void resetDrawOrders() = 0; - virtual void pushFaceGeometry() {} - - S32 mShaderLevel; - S32 mId; - U32 mType; // Type of draw pool - bool mSkipRender; -}; - -class LLRenderPass : public LLDrawPool -{ -public: - // list of possible LLRenderPass types to assign a render batch to - // NOTE: "rigged" variant MUST be non-rigged variant + 1 - // see LLVolumeGeometryManager::registerFace() - enum - { - PASS_SIMPLE = NUM_POOL_TYPES, - PASS_SIMPLE_RIGGED, - PASS_GRASS, - PASS_FULLBRIGHT, - PASS_FULLBRIGHT_RIGGED, - PASS_INVISIBLE, - PASS_INVISIBLE_RIGGED, - PASS_INVISI_SHINY, - PASS_INVISI_SHINY_RIGGED, - PASS_FULLBRIGHT_SHINY, - PASS_FULLBRIGHT_SHINY_RIGGED, - PASS_SHINY, - PASS_SHINY_RIGGED, - PASS_BUMP, - PASS_BUMP_RIGGED, - PASS_POST_BUMP, - PASS_POST_BUMP_RIGGED, - PASS_MATERIAL, - PASS_MATERIAL_RIGGED, - PASS_MATERIAL_ALPHA, - PASS_MATERIAL_ALPHA_RIGGED, - PASS_MATERIAL_ALPHA_MASK, // Diffuse texture used as alpha mask - PASS_MATERIAL_ALPHA_MASK_RIGGED, - PASS_MATERIAL_ALPHA_EMISSIVE, - PASS_MATERIAL_ALPHA_EMISSIVE_RIGGED, - PASS_SPECMAP, - PASS_SPECMAP_RIGGED, - PASS_SPECMAP_BLEND, - PASS_SPECMAP_BLEND_RIGGED, - PASS_SPECMAP_MASK, // Diffuse texture used as alpha mask and specular texture(map) - PASS_SPECMAP_MASK_RIGGED, - PASS_SPECMAP_EMISSIVE, - PASS_SPECMAP_EMISSIVE_RIGGED, - PASS_NORMMAP, - PASS_NORMMAP_RIGGED, - PASS_NORMMAP_BLEND, - PASS_NORMMAP_BLEND_RIGGED, - PASS_NORMMAP_MASK, // Diffuse texture used as alpha mask and normal map - PASS_NORMMAP_MASK_RIGGED, - PASS_NORMMAP_EMISSIVE, - PASS_NORMMAP_EMISSIVE_RIGGED, - PASS_NORMSPEC, - PASS_NORMSPEC_RIGGED, - PASS_NORMSPEC_BLEND, - PASS_NORMSPEC_BLEND_RIGGED, - PASS_NORMSPEC_MASK, // Diffuse texture used as alpha mask with normal and specular map - PASS_NORMSPEC_MASK_RIGGED, - PASS_NORMSPEC_EMISSIVE, - PASS_NORMSPEC_EMISSIVE_RIGGED, - PASS_GLOW, - PASS_GLOW_RIGGED, - PASS_GLTF_GLOW, - PASS_GLTF_GLOW_RIGGED, - PASS_ALPHA, - PASS_ALPHA_RIGGED, - PASS_ALPHA_MASK, - PASS_ALPHA_MASK_RIGGED, - PASS_FULLBRIGHT_ALPHA_MASK, // Diffuse texture used as alpha mask and fullbright - PASS_FULLBRIGHT_ALPHA_MASK_RIGGED, - PASS_ALPHA_INVISIBLE, - PASS_ALPHA_INVISIBLE_RIGGED, - PASS_GLTF_PBR, - PASS_GLTF_PBR_RIGGED, - PASS_GLTF_PBR_ALPHA_MASK, - PASS_GLTF_PBR_ALPHA_MASK_RIGGED, - NUM_RENDER_TYPES, - }; - - #ifdef LL_PROFILER_ENABLE_RENDER_DOC - static inline const char* lookupPassName(U32 pass) - { - switch (pass) - { - case PASS_SIMPLE: - return "PASS_SIMPLE"; - case PASS_SIMPLE_RIGGED: - return "PASS_SIMPLE_RIGGED"; - case PASS_GRASS: - return "PASS_GRASS"; - case PASS_FULLBRIGHT: - return "PASS_FULLBRIGHT"; - case PASS_FULLBRIGHT_RIGGED: - return "PASS_FULLBRIGHT_RIGGED"; - case PASS_INVISIBLE: - return "PASS_INVISIBLE"; - case PASS_INVISIBLE_RIGGED: - return "PASS_INVISIBLE_RIGGED"; - case PASS_INVISI_SHINY: - return "PASS_INVISI_SHINY"; - case PASS_INVISI_SHINY_RIGGED: - return "PASS_INVISI_SHINY_RIGGED"; - case PASS_FULLBRIGHT_SHINY: - return "PASS_FULLBRIGHT_SHINY"; - case PASS_FULLBRIGHT_SHINY_RIGGED: - return "PASS_FULLBRIGHT_SHINY_RIGGED"; - case PASS_SHINY: - return "PASS_SHINY"; - case PASS_SHINY_RIGGED: - return "PASS_SHINY_RIGGED"; - case PASS_BUMP: - return "PASS_BUMP"; - case PASS_BUMP_RIGGED: - return "PASS_BUMP_RIGGED"; - case PASS_POST_BUMP: - return "PASS_POST_BUMP"; - case PASS_POST_BUMP_RIGGED: - return "PASS_POST_BUMP_RIGGED"; - case PASS_MATERIAL: - return "PASS_MATERIAL"; - case PASS_MATERIAL_RIGGED: - return "PASS_MATERIAL_RIGGED"; - case PASS_MATERIAL_ALPHA: - return "PASS_MATERIAL_ALPHA"; - case PASS_MATERIAL_ALPHA_RIGGED: - return "PASS_MATERIAL_ALPHA_RIGGED"; - case PASS_MATERIAL_ALPHA_MASK: - return "PASS_MATERIAL_ALPHA_MASK"; - case PASS_MATERIAL_ALPHA_MASK_RIGGED: - return "PASS_MATERIAL_ALPHA_MASK_RIGGED"; - case PASS_MATERIAL_ALPHA_EMISSIVE: - return "PASS_MATERIAL_ALPHA_EMISSIVE"; - case PASS_MATERIAL_ALPHA_EMISSIVE_RIGGED: - return "PASS_MATERIAL_ALPHA_EMISSIVE_RIGGED"; - case PASS_SPECMAP: - return "PASS_SPECMAP"; - case PASS_SPECMAP_RIGGED: - return "PASS_SPECMAP_RIGGED"; - case PASS_SPECMAP_BLEND: - return "PASS_SPECMAP_BLEND"; - case PASS_SPECMAP_BLEND_RIGGED: - return "PASS_SPECMAP_BLEND_RIGGED"; - case PASS_SPECMAP_MASK: - return "PASS_SPECMAP_MASK"; - case PASS_SPECMAP_MASK_RIGGED: - return "PASS_SPECMAP_MASK_RIGGED"; - case PASS_SPECMAP_EMISSIVE: - return "PASS_SPECMAP_EMISSIVE"; - case PASS_SPECMAP_EMISSIVE_RIGGED: - return "PASS_SPECMAP_EMISSIVE_RIGGED"; - case PASS_NORMMAP: - return "PASS_NORMAMAP"; - case PASS_NORMMAP_RIGGED: - return "PASS_NORMMAP_RIGGED"; - case PASS_NORMMAP_BLEND: - return "PASS_NORMMAP_BLEND"; - case PASS_NORMMAP_BLEND_RIGGED: - return "PASS_NORMMAP_BLEND_RIGGED"; - case PASS_NORMMAP_MASK: - return "PASS_NORMMAP_MASK"; - case PASS_NORMMAP_MASK_RIGGED: - return "PASS_NORMMAP_MASK_RIGGED"; - case PASS_NORMMAP_EMISSIVE: - return "PASS_NORMMAP_EMISSIVE"; - case PASS_NORMMAP_EMISSIVE_RIGGED: - return "PASS_NORMMAP_EMISSIVE_RIGGED"; - case PASS_NORMSPEC: - return "PASS_NORMSPEC"; - case PASS_NORMSPEC_RIGGED: - return "PASS_NORMSPEC_RIGGED"; - case PASS_NORMSPEC_BLEND: - return "PASS_NORMSPEC_BLEND"; - case PASS_NORMSPEC_BLEND_RIGGED: - return "PASS_NORMSPEC_BLEND_RIGGED"; - case PASS_NORMSPEC_MASK: - return "PASS_NORMSPEC_MASK"; - case PASS_NORMSPEC_MASK_RIGGED: - return "PASS_NORMSPEC_MASK_RIGGED"; - case PASS_NORMSPEC_EMISSIVE: - return "PASS_NORMSPEC_EMISSIVE"; - case PASS_NORMSPEC_EMISSIVE_RIGGED: - return "PASS_NORMSPEC_EMISSIVE_RIGGED"; - case PASS_GLOW: - return "PASS_GLOW"; - case PASS_GLOW_RIGGED: - return "PASS_GLOW_RIGGED"; - case PASS_ALPHA: - return "PASS_ALPHA"; - case PASS_ALPHA_RIGGED: - return "PASS_ALPHA_RIGGED"; - case PASS_ALPHA_MASK: - return "PASS_ALPHA_MASK"; - case PASS_ALPHA_MASK_RIGGED: - return "PASS_ALPHA_MASK_RIGGED"; - case PASS_FULLBRIGHT_ALPHA_MASK: - return "PASS_FULLBRIGHT_ALPHA_MASK"; - case PASS_FULLBRIGHT_ALPHA_MASK_RIGGED: - return "PASS_FULLBRIGHT_ALPHA_MASK_RIGGED"; - case PASS_ALPHA_INVISIBLE: - return "PASS_ALPHA_INVISIBLE"; - case PASS_ALPHA_INVISIBLE_RIGGED: - return "PASS_ALPHA_INVISIBLE_RIGGED"; - case PASS_GLTF_PBR: - return "PASS_GLTF_PBR"; - case PASS_GLTF_PBR_RIGGED: - return "PASS_GLTF_PBR_RIGGED"; - case PASS_GLTF_PBR_ALPHA_MASK: - return "PASS_GLTF_PBR_ALPHA_MASK"; - case PASS_GLTF_PBR_ALPHA_MASK_RIGGED: - return "PASS_GLTF_PBR_ALPHA_MASK_RIGGED"; - default: - return "Unknown pass"; - } - } - #else - static inline const char* lookupPass(U32 pass) { return ""; } - #endif - - LLRenderPass(const U32 type); - virtual ~LLRenderPass(); - /*virtual*/ LLViewerTexture* getDebugTexture() { return NULL; } - LLViewerTexture* getTexture() { return NULL; } - bool isDead() { return false; } - void resetDrawOrders() { } - - static void applyModelMatrix(const LLDrawInfo& params); - // Use before a non-GLTF batch if it is interleaved with GLTF batches that share the same shader - static void resetGLTFTextureTransform(); - void pushBatches(U32 type, bool texture = true, bool batch_textures = false); - void pushUntexturedBatches(U32 type); - - void pushRiggedBatches(U32 type, bool texture = true, bool batch_textures = false); - void pushUntexturedRiggedBatches(U32 type); - - // push full GLTF batches - // assumes draw infos of given type have valid GLTF materials - void pushGLTFBatches(U32 type); - - // like pushGLTFBatches, but will not bind textures or set up texture transforms - void pushUntexturedGLTFBatches(U32 type); - - // helper function for dispatching to textured or untextured pass based on bool - void pushGLTFBatches(U32 type, bool textured); - - - // rigged variants of above - void pushRiggedGLTFBatches(U32 type); - void pushRiggedGLTFBatches(U32 type, bool textured); - void pushUntexturedRiggedGLTFBatches(U32 type); - - // push a single GLTF draw call - void pushGLTFBatch(LLDrawInfo& params); - void pushRiggedGLTFBatch(LLDrawInfo& params, LLVOAvatar*& lastAvatar, U64& lastMeshId); - void pushUntexturedGLTFBatch(LLDrawInfo& params); - void pushUntexturedRiggedGLTFBatch(LLDrawInfo& params, LLVOAvatar*& lastAvatar, U64& lastMeshId); - - void pushMaskBatches(U32 type, bool texture = true, bool batch_textures = false); - void pushRiggedMaskBatches(U32 type, bool texture = true, bool batch_textures = false); - void pushBatch(LLDrawInfo& params, bool texture, bool batch_textures = false); - void pushUntexturedBatch(LLDrawInfo& params); - void pushBumpBatch(LLDrawInfo& params, bool texture, bool batch_textures = false); - static bool uploadMatrixPalette(LLDrawInfo& params); - static bool uploadMatrixPalette(LLVOAvatar* avatar, LLMeshSkinInfo* skinInfo); - virtual void renderGroup(LLSpatialGroup* group, U32 type, bool texture = true); - virtual void renderRiggedGroup(LLSpatialGroup* group, U32 type, bool texture = true); -}; - -class LLFacePool : public LLDrawPool -{ -public: - typedef std::vector face_array_t; - - enum - { - SHADER_LEVEL_SCATTERING = 2 - }; - -public: - LLFacePool(const U32 type); - virtual ~LLFacePool(); - - bool isDead() { return mReferences.empty(); } - - virtual LLViewerTexture *getTexture(); - virtual void dirtyTextures(const std::set& textures); - - virtual void enqueue(LLFace *face); - virtual bool addFace(LLFace *face); - virtual bool removeFace(LLFace *face); - - virtual bool verify() const; // Verify that all data in the draw pool is correct! - - virtual void resetDrawOrders(); - void resetAll(); - - void destroy(); - - void buildEdges(); - - void addFaceReference(LLFace *facep); - void removeFaceReference(LLFace *facep); - - void printDebugInfo() const; - - bool isFacePool() { return true; } - - // call drawIndexed on every draw face - void pushFaceGeometry(); - - friend class LLFace; - friend class LLPipeline; -public: - face_array_t mDrawFace; - face_array_t mMoveFace; - face_array_t mReferences; - -public: - class LLOverrideFaceColor - { - public: - LLOverrideFaceColor(LLDrawPool* pool) - : mOverride(sOverrideFaceColor), mPool(pool) - { - sOverrideFaceColor = true; - } - LLOverrideFaceColor(LLDrawPool* pool, const LLColor4& color) - : mOverride(sOverrideFaceColor), mPool(pool) - { - sOverrideFaceColor = true; - setColor(color); - } - LLOverrideFaceColor(LLDrawPool* pool, const LLColor4U& color) - : mOverride(sOverrideFaceColor), mPool(pool) - { - sOverrideFaceColor = true; - setColor(color); - } - LLOverrideFaceColor(LLDrawPool* pool, F32 r, F32 g, F32 b, F32 a) - : mOverride(sOverrideFaceColor), mPool(pool) - { - sOverrideFaceColor = true; - setColor(r, g, b, a); - } - ~LLOverrideFaceColor() - { - sOverrideFaceColor = mOverride; - } - void setColor(const LLColor4& color); - void setColor(const LLColor4U& color); - void setColor(F32 r, F32 g, F32 b, F32 a); - bool mOverride; - LLDrawPool* mPool; - static bool sOverrideFaceColor; - }; -}; - - -#endif //LL_LLDRAWPOOL_H +/** + * @file lldrawpool.h + * @brief LLDrawPool class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLDRAWPOOL_H +#define LL_LLDRAWPOOL_H + +#include "v4coloru.h" +#include "v2math.h" +#include "v3math.h" +#include "llvertexbuffer.h" + +class LLFace; +class LLViewerTexture; +class LLViewerFetchedTexture; +class LLSpatialGroup; +class LLDrawInfo; +class LLVOAvatar; +class LLMeshSkinInfo; + +class LLDrawPool +{ +public: + static S32 sNumDrawPools; + + enum + { + // Correspond to LLPipeline render type + // Also controls render order, so passes that don't use alpha masking/blending should come before + // other passes to preserve hierarchical Z for occlusion queries. Occlusion queries happen just + // before grass, so grass should be the first alpha masked pool. Other ordering should be done + // based on fill rate and likelihood to occlude future passes (faster, large occluders first). + // + POOL_SKY = 1, + POOL_WL_SKY, + POOL_SIMPLE, + POOL_FULLBRIGHT, + POOL_BUMP, + POOL_TERRAIN, + POOL_MATERIALS, + POOL_GLTF_PBR, + POOL_GRASS, + POOL_GLTF_PBR_ALPHA_MASK, + POOL_TREE, + POOL_ALPHA_MASK, + POOL_FULLBRIGHT_ALPHA_MASK, + POOL_AVATAR, + POOL_CONTROL_AV, // Animesh + POOL_GLOW, + POOL_ALPHA_PRE_WATER, + POOL_VOIDWATER, + POOL_WATER, + POOL_ALPHA_POST_WATER, + POOL_ALPHA, // note there is no actual "POOL_ALPHA" but pre-water and post-water pools consume POOL_ALPHA faces + NUM_POOL_TYPES, + }; + + LLDrawPool(const U32 type); + virtual ~LLDrawPool(); + + virtual bool isDead() = 0; + + S32 getId() const { return mId; } + U32 getType() const { return mType; } + + bool getSkipRenderFlag() const { return mSkipRender;} + void setSkipRenderFlag( bool flag ) { mSkipRender = flag; } + + virtual LLViewerTexture *getDebugTexture(); + virtual void beginRenderPass( S32 pass ); + virtual void endRenderPass( S32 pass ); + virtual S32 getNumPasses(); + + virtual void beginDeferredPass(S32 pass); + virtual void endDeferredPass(S32 pass); + virtual S32 getNumDeferredPasses(); + virtual void renderDeferred(S32 pass = 0); + + virtual void beginPostDeferredPass(S32 pass); + virtual void endPostDeferredPass(S32 pass); + virtual S32 getNumPostDeferredPasses(); + virtual void renderPostDeferred(S32 pass = 0); + + virtual void beginShadowPass(S32 pass); + virtual void endShadowPass(S32 pass); + virtual S32 getNumShadowPasses(); + virtual void renderShadow(S32 pass = 0); + + virtual void render(S32 pass = 0) {}; + virtual void prerender() {}; + virtual U32 getVertexDataMask() { return 0; } // DEPRECATED -- draw pool doesn't actually determine vertex data mask any more + virtual bool verify() const { return true; } // Verify that all data in the draw pool is correct! + virtual S32 getShaderLevel() const { return mShaderLevel; } + + static LLDrawPool* createPool(const U32 type, LLViewerTexture *tex0 = NULL); + virtual LLViewerTexture* getTexture() = 0; + virtual bool isFacePool() { return false; } + virtual void resetDrawOrders() = 0; + virtual void pushFaceGeometry() {} + + S32 mShaderLevel; + S32 mId; + U32 mType; // Type of draw pool + bool mSkipRender; +}; + +class LLRenderPass : public LLDrawPool +{ +public: + // list of possible LLRenderPass types to assign a render batch to + // NOTE: "rigged" variant MUST be non-rigged variant + 1 + // see LLVolumeGeometryManager::registerFace() + enum + { + PASS_SIMPLE = NUM_POOL_TYPES, + PASS_SIMPLE_RIGGED, + PASS_GRASS, + PASS_FULLBRIGHT, + PASS_FULLBRIGHT_RIGGED, + PASS_INVISIBLE, + PASS_INVISIBLE_RIGGED, + PASS_INVISI_SHINY, + PASS_INVISI_SHINY_RIGGED, + PASS_FULLBRIGHT_SHINY, + PASS_FULLBRIGHT_SHINY_RIGGED, + PASS_SHINY, + PASS_SHINY_RIGGED, + PASS_BUMP, + PASS_BUMP_RIGGED, + PASS_POST_BUMP, + PASS_POST_BUMP_RIGGED, + PASS_MATERIAL, + PASS_MATERIAL_RIGGED, + PASS_MATERIAL_ALPHA, + PASS_MATERIAL_ALPHA_RIGGED, + PASS_MATERIAL_ALPHA_MASK, // Diffuse texture used as alpha mask + PASS_MATERIAL_ALPHA_MASK_RIGGED, + PASS_MATERIAL_ALPHA_EMISSIVE, + PASS_MATERIAL_ALPHA_EMISSIVE_RIGGED, + PASS_SPECMAP, + PASS_SPECMAP_RIGGED, + PASS_SPECMAP_BLEND, + PASS_SPECMAP_BLEND_RIGGED, + PASS_SPECMAP_MASK, // Diffuse texture used as alpha mask and specular texture(map) + PASS_SPECMAP_MASK_RIGGED, + PASS_SPECMAP_EMISSIVE, + PASS_SPECMAP_EMISSIVE_RIGGED, + PASS_NORMMAP, + PASS_NORMMAP_RIGGED, + PASS_NORMMAP_BLEND, + PASS_NORMMAP_BLEND_RIGGED, + PASS_NORMMAP_MASK, // Diffuse texture used as alpha mask and normal map + PASS_NORMMAP_MASK_RIGGED, + PASS_NORMMAP_EMISSIVE, + PASS_NORMMAP_EMISSIVE_RIGGED, + PASS_NORMSPEC, + PASS_NORMSPEC_RIGGED, + PASS_NORMSPEC_BLEND, + PASS_NORMSPEC_BLEND_RIGGED, + PASS_NORMSPEC_MASK, // Diffuse texture used as alpha mask with normal and specular map + PASS_NORMSPEC_MASK_RIGGED, + PASS_NORMSPEC_EMISSIVE, + PASS_NORMSPEC_EMISSIVE_RIGGED, + PASS_GLOW, + PASS_GLOW_RIGGED, + PASS_GLTF_GLOW, + PASS_GLTF_GLOW_RIGGED, + PASS_ALPHA, + PASS_ALPHA_RIGGED, + PASS_ALPHA_MASK, + PASS_ALPHA_MASK_RIGGED, + PASS_FULLBRIGHT_ALPHA_MASK, // Diffuse texture used as alpha mask and fullbright + PASS_FULLBRIGHT_ALPHA_MASK_RIGGED, + PASS_ALPHA_INVISIBLE, + PASS_ALPHA_INVISIBLE_RIGGED, + PASS_GLTF_PBR, + PASS_GLTF_PBR_RIGGED, + PASS_GLTF_PBR_ALPHA_MASK, + PASS_GLTF_PBR_ALPHA_MASK_RIGGED, + NUM_RENDER_TYPES, + }; + + #ifdef LL_PROFILER_ENABLE_RENDER_DOC + static inline const char* lookupPassName(U32 pass) + { + switch (pass) + { + case PASS_SIMPLE: + return "PASS_SIMPLE"; + case PASS_SIMPLE_RIGGED: + return "PASS_SIMPLE_RIGGED"; + case PASS_GRASS: + return "PASS_GRASS"; + case PASS_FULLBRIGHT: + return "PASS_FULLBRIGHT"; + case PASS_FULLBRIGHT_RIGGED: + return "PASS_FULLBRIGHT_RIGGED"; + case PASS_INVISIBLE: + return "PASS_INVISIBLE"; + case PASS_INVISIBLE_RIGGED: + return "PASS_INVISIBLE_RIGGED"; + case PASS_INVISI_SHINY: + return "PASS_INVISI_SHINY"; + case PASS_INVISI_SHINY_RIGGED: + return "PASS_INVISI_SHINY_RIGGED"; + case PASS_FULLBRIGHT_SHINY: + return "PASS_FULLBRIGHT_SHINY"; + case PASS_FULLBRIGHT_SHINY_RIGGED: + return "PASS_FULLBRIGHT_SHINY_RIGGED"; + case PASS_SHINY: + return "PASS_SHINY"; + case PASS_SHINY_RIGGED: + return "PASS_SHINY_RIGGED"; + case PASS_BUMP: + return "PASS_BUMP"; + case PASS_BUMP_RIGGED: + return "PASS_BUMP_RIGGED"; + case PASS_POST_BUMP: + return "PASS_POST_BUMP"; + case PASS_POST_BUMP_RIGGED: + return "PASS_POST_BUMP_RIGGED"; + case PASS_MATERIAL: + return "PASS_MATERIAL"; + case PASS_MATERIAL_RIGGED: + return "PASS_MATERIAL_RIGGED"; + case PASS_MATERIAL_ALPHA: + return "PASS_MATERIAL_ALPHA"; + case PASS_MATERIAL_ALPHA_RIGGED: + return "PASS_MATERIAL_ALPHA_RIGGED"; + case PASS_MATERIAL_ALPHA_MASK: + return "PASS_MATERIAL_ALPHA_MASK"; + case PASS_MATERIAL_ALPHA_MASK_RIGGED: + return "PASS_MATERIAL_ALPHA_MASK_RIGGED"; + case PASS_MATERIAL_ALPHA_EMISSIVE: + return "PASS_MATERIAL_ALPHA_EMISSIVE"; + case PASS_MATERIAL_ALPHA_EMISSIVE_RIGGED: + return "PASS_MATERIAL_ALPHA_EMISSIVE_RIGGED"; + case PASS_SPECMAP: + return "PASS_SPECMAP"; + case PASS_SPECMAP_RIGGED: + return "PASS_SPECMAP_RIGGED"; + case PASS_SPECMAP_BLEND: + return "PASS_SPECMAP_BLEND"; + case PASS_SPECMAP_BLEND_RIGGED: + return "PASS_SPECMAP_BLEND_RIGGED"; + case PASS_SPECMAP_MASK: + return "PASS_SPECMAP_MASK"; + case PASS_SPECMAP_MASK_RIGGED: + return "PASS_SPECMAP_MASK_RIGGED"; + case PASS_SPECMAP_EMISSIVE: + return "PASS_SPECMAP_EMISSIVE"; + case PASS_SPECMAP_EMISSIVE_RIGGED: + return "PASS_SPECMAP_EMISSIVE_RIGGED"; + case PASS_NORMMAP: + return "PASS_NORMAMAP"; + case PASS_NORMMAP_RIGGED: + return "PASS_NORMMAP_RIGGED"; + case PASS_NORMMAP_BLEND: + return "PASS_NORMMAP_BLEND"; + case PASS_NORMMAP_BLEND_RIGGED: + return "PASS_NORMMAP_BLEND_RIGGED"; + case PASS_NORMMAP_MASK: + return "PASS_NORMMAP_MASK"; + case PASS_NORMMAP_MASK_RIGGED: + return "PASS_NORMMAP_MASK_RIGGED"; + case PASS_NORMMAP_EMISSIVE: + return "PASS_NORMMAP_EMISSIVE"; + case PASS_NORMMAP_EMISSIVE_RIGGED: + return "PASS_NORMMAP_EMISSIVE_RIGGED"; + case PASS_NORMSPEC: + return "PASS_NORMSPEC"; + case PASS_NORMSPEC_RIGGED: + return "PASS_NORMSPEC_RIGGED"; + case PASS_NORMSPEC_BLEND: + return "PASS_NORMSPEC_BLEND"; + case PASS_NORMSPEC_BLEND_RIGGED: + return "PASS_NORMSPEC_BLEND_RIGGED"; + case PASS_NORMSPEC_MASK: + return "PASS_NORMSPEC_MASK"; + case PASS_NORMSPEC_MASK_RIGGED: + return "PASS_NORMSPEC_MASK_RIGGED"; + case PASS_NORMSPEC_EMISSIVE: + return "PASS_NORMSPEC_EMISSIVE"; + case PASS_NORMSPEC_EMISSIVE_RIGGED: + return "PASS_NORMSPEC_EMISSIVE_RIGGED"; + case PASS_GLOW: + return "PASS_GLOW"; + case PASS_GLOW_RIGGED: + return "PASS_GLOW_RIGGED"; + case PASS_ALPHA: + return "PASS_ALPHA"; + case PASS_ALPHA_RIGGED: + return "PASS_ALPHA_RIGGED"; + case PASS_ALPHA_MASK: + return "PASS_ALPHA_MASK"; + case PASS_ALPHA_MASK_RIGGED: + return "PASS_ALPHA_MASK_RIGGED"; + case PASS_FULLBRIGHT_ALPHA_MASK: + return "PASS_FULLBRIGHT_ALPHA_MASK"; + case PASS_FULLBRIGHT_ALPHA_MASK_RIGGED: + return "PASS_FULLBRIGHT_ALPHA_MASK_RIGGED"; + case PASS_ALPHA_INVISIBLE: + return "PASS_ALPHA_INVISIBLE"; + case PASS_ALPHA_INVISIBLE_RIGGED: + return "PASS_ALPHA_INVISIBLE_RIGGED"; + case PASS_GLTF_PBR: + return "PASS_GLTF_PBR"; + case PASS_GLTF_PBR_RIGGED: + return "PASS_GLTF_PBR_RIGGED"; + case PASS_GLTF_PBR_ALPHA_MASK: + return "PASS_GLTF_PBR_ALPHA_MASK"; + case PASS_GLTF_PBR_ALPHA_MASK_RIGGED: + return "PASS_GLTF_PBR_ALPHA_MASK_RIGGED"; + default: + return "Unknown pass"; + } + } + #else + static inline const char* lookupPass(U32 pass) { return ""; } + #endif + + LLRenderPass(const U32 type); + virtual ~LLRenderPass(); + /*virtual*/ LLViewerTexture* getDebugTexture() { return NULL; } + LLViewerTexture* getTexture() { return NULL; } + bool isDead() { return false; } + void resetDrawOrders() { } + + static void applyModelMatrix(const LLDrawInfo& params); + // Use before a non-GLTF batch if it is interleaved with GLTF batches that share the same shader + static void resetGLTFTextureTransform(); + void pushBatches(U32 type, bool texture = true, bool batch_textures = false); + void pushUntexturedBatches(U32 type); + + void pushRiggedBatches(U32 type, bool texture = true, bool batch_textures = false); + void pushUntexturedRiggedBatches(U32 type); + + // push full GLTF batches + // assumes draw infos of given type have valid GLTF materials + void pushGLTFBatches(U32 type); + + // like pushGLTFBatches, but will not bind textures or set up texture transforms + void pushUntexturedGLTFBatches(U32 type); + + // helper function for dispatching to textured or untextured pass based on bool + void pushGLTFBatches(U32 type, bool textured); + + + // rigged variants of above + void pushRiggedGLTFBatches(U32 type); + void pushRiggedGLTFBatches(U32 type, bool textured); + void pushUntexturedRiggedGLTFBatches(U32 type); + + // push a single GLTF draw call + void pushGLTFBatch(LLDrawInfo& params); + void pushRiggedGLTFBatch(LLDrawInfo& params, LLVOAvatar*& lastAvatar, U64& lastMeshId); + void pushUntexturedGLTFBatch(LLDrawInfo& params); + void pushUntexturedRiggedGLTFBatch(LLDrawInfo& params, LLVOAvatar*& lastAvatar, U64& lastMeshId); + + void pushMaskBatches(U32 type, bool texture = true, bool batch_textures = false); + void pushRiggedMaskBatches(U32 type, bool texture = true, bool batch_textures = false); + void pushBatch(LLDrawInfo& params, bool texture, bool batch_textures = false); + void pushUntexturedBatch(LLDrawInfo& params); + void pushBumpBatch(LLDrawInfo& params, bool texture, bool batch_textures = false); + static bool uploadMatrixPalette(LLDrawInfo& params); + static bool uploadMatrixPalette(LLVOAvatar* avatar, LLMeshSkinInfo* skinInfo); + virtual void renderGroup(LLSpatialGroup* group, U32 type, bool texture = true); + virtual void renderRiggedGroup(LLSpatialGroup* group, U32 type, bool texture = true); +}; + +class LLFacePool : public LLDrawPool +{ +public: + typedef std::vector face_array_t; + + enum + { + SHADER_LEVEL_SCATTERING = 2 + }; + +public: + LLFacePool(const U32 type); + virtual ~LLFacePool(); + + bool isDead() { return mReferences.empty(); } + + virtual LLViewerTexture *getTexture(); + virtual void dirtyTextures(const std::set& textures); + + virtual void enqueue(LLFace *face); + virtual bool addFace(LLFace *face); + virtual bool removeFace(LLFace *face); + + virtual bool verify() const; // Verify that all data in the draw pool is correct! + + virtual void resetDrawOrders(); + void resetAll(); + + void destroy(); + + void buildEdges(); + + void addFaceReference(LLFace *facep); + void removeFaceReference(LLFace *facep); + + void printDebugInfo() const; + + bool isFacePool() { return true; } + + // call drawIndexed on every draw face + void pushFaceGeometry(); + + friend class LLFace; + friend class LLPipeline; +public: + face_array_t mDrawFace; + face_array_t mMoveFace; + face_array_t mReferences; + +public: + class LLOverrideFaceColor + { + public: + LLOverrideFaceColor(LLDrawPool* pool) + : mOverride(sOverrideFaceColor), mPool(pool) + { + sOverrideFaceColor = true; + } + LLOverrideFaceColor(LLDrawPool* pool, const LLColor4& color) + : mOverride(sOverrideFaceColor), mPool(pool) + { + sOverrideFaceColor = true; + setColor(color); + } + LLOverrideFaceColor(LLDrawPool* pool, const LLColor4U& color) + : mOverride(sOverrideFaceColor), mPool(pool) + { + sOverrideFaceColor = true; + setColor(color); + } + LLOverrideFaceColor(LLDrawPool* pool, F32 r, F32 g, F32 b, F32 a) + : mOverride(sOverrideFaceColor), mPool(pool) + { + sOverrideFaceColor = true; + setColor(r, g, b, a); + } + ~LLOverrideFaceColor() + { + sOverrideFaceColor = mOverride; + } + void setColor(const LLColor4& color); + void setColor(const LLColor4U& color); + void setColor(F32 r, F32 g, F32 b, F32 a); + bool mOverride; + LLDrawPool* mPool; + static bool sOverrideFaceColor; + }; +}; + + +#endif //LL_LLDRAWPOOL_H diff --git a/indra/newview/lldrawpoolalpha.cpp b/indra/newview/lldrawpoolalpha.cpp index 7e4c1287ff..7093560644 100644 --- a/indra/newview/lldrawpoolalpha.cpp +++ b/indra/newview/lldrawpoolalpha.cpp @@ -1,935 +1,935 @@ -/** - * @file lldrawpoolalpha.cpp - * @brief LLDrawPoolAlpha class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lldrawpoolalpha.h" - -#include "llglheaders.h" -#include "llviewercontrol.h" -#include "llcriticaldamp.h" -#include "llfasttimer.h" -#include "llrender.h" - -#include "llcubemap.h" -#include "llsky.h" -#include "lldrawable.h" -#include "llface.h" -#include "llviewercamera.h" -#include "llviewertexturelist.h" // For debugging -#include "llviewerobjectlist.h" // For debugging -#include "llviewerwindow.h" -#include "pipeline.h" -#include "llviewershadermgr.h" -#include "llviewerregion.h" -#include "lldrawpoolwater.h" -#include "llspatialpartition.h" -#include "llglcommonfunc.h" -#include "llvoavatar.h" - -#include "llenvironment.h" - -bool LLDrawPoolAlpha::sShowDebugAlpha = false; - -#define current_shader (LLGLSLShader::sCurBoundShaderPtr) - -LLVector4 LLDrawPoolAlpha::sWaterPlane; - -// minimum alpha before discarding a fragment -static const F32 MINIMUM_ALPHA = 0.004f; // ~ 1/255 - -// minimum alpha before discarding a fragment when rendering impostors -static const F32 MINIMUM_IMPOSTOR_ALPHA = 0.1f; - -LLDrawPoolAlpha::LLDrawPoolAlpha(U32 type) : - LLRenderPass(type), target_shader(NULL), - mColorSFactor(LLRender::BF_UNDEF), mColorDFactor(LLRender::BF_UNDEF), - mAlphaSFactor(LLRender::BF_UNDEF), mAlphaDFactor(LLRender::BF_UNDEF) -{ - -} - -LLDrawPoolAlpha::~LLDrawPoolAlpha() -{ -} - - -void LLDrawPoolAlpha::prerender() -{ - mShaderLevel = LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_OBJECT); - - // TODO: is this even necessay? These are probably set to never discard - LLViewerFetchedTexture::sFlatNormalImagep->addTextureStats(1024.f*1024.f); - LLViewerFetchedTexture::sWhiteImagep->addTextureStats(1024.f * 1024.f); -} - -S32 LLDrawPoolAlpha::getNumPostDeferredPasses() -{ - return 1; -} - -// set some common parameters on the given shader to prepare for alpha rendering -static void prepare_alpha_shader(LLGLSLShader* shader, bool textureGamma, bool deferredEnvironment, F32 water_sign) -{ - static LLCachedControl displayGamma(gSavedSettings, "RenderDeferredDisplayGamma"); - F32 gamma = displayGamma; - - static LLStaticHashedString waterSign("waterSign"); - - // Does this deferred shader need environment uniforms set such as sun_dir, etc. ? - // NOTE: We don't actually need a gbuffer since we are doing forward rendering (for transparency) post deferred rendering - // TODO: bindDeferredShader() probably should have the updating of the environment uniforms factored out into updateShaderEnvironmentUniforms() - // i.e. shaders\class1\deferred\alphaF.glsl - if (deferredEnvironment) - { - shader->mCanBindFast = false; - } - - shader->bind(); - shader->uniform1f(LLShaderMgr::DISPLAY_GAMMA, (gamma > 0.1f) ? 1.0f / gamma : (1.0f / 2.2f)); - - if (LLPipeline::sRenderingHUDs) - { // for HUD attachments, only the pre-water pass is executed and we never want to clip anything - LLVector4 near_clip(0, 0, -1, 0); - shader->uniform1f(waterSign, 1.f); - shader->uniform4fv(LLShaderMgr::WATER_WATERPLANE, 1, near_clip.mV); - } - else - { - shader->uniform1f(waterSign, water_sign); - shader->uniform4fv(LLShaderMgr::WATER_WATERPLANE, 1, LLDrawPoolAlpha::sWaterPlane.mV); - } - - if (LLPipeline::sImpostorRender) - { - shader->setMinimumAlpha(MINIMUM_IMPOSTOR_ALPHA); - } - else - { - shader->setMinimumAlpha(MINIMUM_ALPHA); - } - if (textureGamma) - { - shader->uniform1f(LLShaderMgr::TEXTURE_GAMMA, 2.2f); - } - - //also prepare rigged variant - if (shader->mRiggedVariant && shader->mRiggedVariant != shader) - { - prepare_alpha_shader(shader->mRiggedVariant, textureGamma, deferredEnvironment, water_sign); - } -} - -extern bool gCubeSnapshot; - -void LLDrawPoolAlpha::renderPostDeferred(S32 pass) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - - if (LLPipeline::isWaterClip() && getType() == LLDrawPool::POOL_ALPHA_PRE_WATER) - { // don't render alpha objects on the other side of the water plane if water is opaque - return; - } - - F32 water_sign = 1.f; - - if (getType() == LLDrawPool::POOL_ALPHA_PRE_WATER) - { - water_sign = -1.f; - } - - if (LLPipeline::sUnderWaterRender) - { - water_sign *= -1.f; - } - - // prepare shaders - llassert(LLPipeline::sRenderDeferred); - - emissive_shader = &gDeferredEmissiveProgram; - prepare_alpha_shader(emissive_shader, true, false, water_sign); - - pbr_emissive_shader = &gPBRGlowProgram; - prepare_alpha_shader(pbr_emissive_shader, true, false, water_sign); - - - fullbright_shader = - (LLPipeline::sImpostorRender) ? &gDeferredFullbrightAlphaMaskProgram : - (LLPipeline::sRenderingHUDs) ? &gHUDFullbrightAlphaMaskAlphaProgram : - &gDeferredFullbrightAlphaMaskAlphaProgram; - prepare_alpha_shader(fullbright_shader, true, true, water_sign); - - simple_shader = - (LLPipeline::sImpostorRender) ? &gDeferredAlphaImpostorProgram : - (LLPipeline::sRenderingHUDs) ? &gHUDAlphaProgram : - &gDeferredAlphaProgram; - - prepare_alpha_shader(simple_shader, false, true, water_sign); //prime simple shader (loads shadow relevant uniforms) - - LLGLSLShader* materialShader = gDeferredMaterialProgram; - for (int i = 0; i < LLMaterial::SHADER_COUNT*2; ++i) - { - prepare_alpha_shader(&materialShader[i], false, true, water_sign); - } - - pbr_shader = - (LLPipeline::sRenderingHUDs) ? &gHUDPBRAlphaProgram : - &gDeferredPBRAlphaProgram; - - prepare_alpha_shader(pbr_shader, false, true, water_sign); - - // explicitly unbind here so render loop doesn't make assumptions about the last shader - // already being setup for rendering - LLGLSLShader::unbind(); - - if (!LLPipeline::sRenderingHUDs) - { - // first pass, render rigged objects only and render to depth buffer - forwardRender(true); - } - - // second pass, regular forward alpha rendering - forwardRender(); - - // final pass, render to depth for depth of field effects - if (!LLPipeline::sImpostorRender && gSavedSettings.getBOOL("RenderDepthOfField") && !gCubeSnapshot && !LLPipeline::sRenderingHUDs && getType() == LLDrawPool::POOL_ALPHA_POST_WATER) - { - //update depth buffer sampler - simple_shader = fullbright_shader = &gDeferredFullbrightAlphaMaskProgram; - - simple_shader->bind(); - simple_shader->setMinimumAlpha(0.33f); - - // mask off color buffer writes as we're only writing to depth buffer - gGL.setColorMask(false, false); - - // If the face is more than 90% transparent, then don't update the Depth buffer for Dof - // We don't want the nearly invisible objects to cause of DoF effects - renderAlpha(getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX | LLVertexBuffer::MAP_TANGENT | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TEXCOORD2, - true); // <--- discard mostly transparent faces - - gGL.setColorMask(true, false); - } -} - -void LLDrawPoolAlpha::forwardRender(bool rigged) -{ - gPipeline.enableLightsDynamic(); - - LLGLSPipelineAlpha gls_pipeline_alpha; - - //enable writing to alpha for emissive effects - gGL.setColorMask(true, true); - - bool write_depth = rigged || - LLDrawPoolWater::sSkipScreenCopy - // we want depth written so that rendered alpha will - // contribute to the alpha mask used for impostors - || LLPipeline::sImpostorRenderAlphaDepthPass - || getType() == LLDrawPoolAlpha::POOL_ALPHA_PRE_WATER; // needed for accurate water fog - - - LLGLDepthTest depth(GL_TRUE, write_depth ? GL_TRUE : GL_FALSE); - - mColorSFactor = LLRender::BF_SOURCE_ALPHA; // } regular alpha blend - mColorDFactor = LLRender::BF_ONE_MINUS_SOURCE_ALPHA; // } - mAlphaSFactor = LLRender::BF_ZERO; // } glow suppression - mAlphaDFactor = LLRender::BF_ONE_MINUS_SOURCE_ALPHA; // } - gGL.blendFunc(mColorSFactor, mColorDFactor, mAlphaSFactor, mAlphaDFactor); - - // If the face is more than 90% transparent, then don't update the Depth buffer for Dof - // We don't want the nearly invisible objects to cause of DoF effects - renderAlpha(getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX | LLVertexBuffer::MAP_TANGENT | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TEXCOORD2, false, rigged); - - gGL.setColorMask(true, false); - - if (!rigged) - { //render "highlight alpha" on final non-rigged pass - // NOTE -- hacky call here protected by !rigged instead of alongside "forwardRender" - // so renderDebugAlpha is executed while gls_pipeline_alpha and depth GL state - // variables above are still in scope - renderDebugAlpha(); - } -} - -void LLDrawPoolAlpha::renderDebugAlpha() -{ - if (sShowDebugAlpha) - { - gHighlightProgram.bind(); - gGL.diffuseColor4f(1, 0, 0, 1); - gGL.getTexUnit(0)->bindFast(LLViewerFetchedTexture::getSmokeImage()); - - - renderAlphaHighlight(); - - pushUntexturedBatches(LLRenderPass::PASS_ALPHA_MASK); - pushUntexturedBatches(LLRenderPass::PASS_ALPHA_INVISIBLE); - - // Material alpha mask - gGL.diffuseColor4f(0, 0, 1, 1); - pushUntexturedBatches(LLRenderPass::PASS_MATERIAL_ALPHA_MASK); - pushUntexturedBatches(LLRenderPass::PASS_NORMMAP_MASK); - pushUntexturedBatches(LLRenderPass::PASS_SPECMAP_MASK); - pushUntexturedBatches(LLRenderPass::PASS_NORMSPEC_MASK); - pushUntexturedBatches(LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK); - pushUntexturedBatches(LLRenderPass::PASS_GLTF_PBR_ALPHA_MASK); - - gGL.diffuseColor4f(0, 1, 0, 1); - pushUntexturedBatches(LLRenderPass::PASS_INVISIBLE); - - gHighlightProgram.mRiggedVariant->bind(); - gGL.diffuseColor4f(1, 0, 0, 1); - - pushRiggedBatches(LLRenderPass::PASS_ALPHA_MASK_RIGGED, false); - pushRiggedBatches(LLRenderPass::PASS_ALPHA_INVISIBLE_RIGGED, false); - - // Material alpha mask - gGL.diffuseColor4f(0, 0, 1, 1); - pushRiggedBatches(LLRenderPass::PASS_MATERIAL_ALPHA_MASK_RIGGED, false); - pushRiggedBatches(LLRenderPass::PASS_NORMMAP_MASK_RIGGED, false); - pushRiggedBatches(LLRenderPass::PASS_SPECMAP_MASK_RIGGED, false); - pushRiggedBatches(LLRenderPass::PASS_NORMSPEC_MASK_RIGGED, false); - pushRiggedBatches(LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK_RIGGED, false); - pushRiggedBatches(LLRenderPass::PASS_GLTF_PBR_ALPHA_MASK_RIGGED, false); - - gGL.diffuseColor4f(0, 1, 0, 1); - pushRiggedBatches(LLRenderPass::PASS_INVISIBLE_RIGGED, false); - LLGLSLShader::sCurBoundShaderPtr->unbind(); - } -} - -void LLDrawPoolAlpha::renderAlphaHighlight() -{ - for (int pass = 0; pass < 2; ++pass) - { //two passes, one rigged and one not - LLVOAvatar* lastAvatar = nullptr; - U64 lastMeshId = 0; - - LLCullResult::sg_iterator begin = pass == 0 ? gPipeline.beginAlphaGroups() : gPipeline.beginRiggedAlphaGroups(); - LLCullResult::sg_iterator end = pass == 0 ? gPipeline.endAlphaGroups() : gPipeline.endRiggedAlphaGroups(); - - for (LLCullResult::sg_iterator i = begin; i != end; ++i) - { - LLSpatialGroup* group = *i; - if (group->getSpatialPartition()->mRenderByGroup && - !group->isDead()) - { - LLSpatialGroup::drawmap_elem_t& draw_info = group->mDrawMap[LLRenderPass::PASS_ALPHA+pass]; // <-- hacky + pass to use PASS_ALPHA_RIGGED on second pass - - for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k) - { - LLDrawInfo& params = **k; - - bool rigged = (params.mAvatar != nullptr); - gHighlightProgram.bind(rigged); - gGL.diffuseColor4f(1, 0, 0, 1); - - if (rigged) - { - if (lastAvatar != params.mAvatar || - lastMeshId != params.mSkinInfo->mHash) - { - if (!uploadMatrixPalette(params)) - { - continue; - } - lastAvatar = params.mAvatar; - lastMeshId = params.mSkinInfo->mHash; - } - } - - LLRenderPass::applyModelMatrix(params); - params.mVertexBuffer->setBuffer(); - params.mVertexBuffer->drawRange(LLRender::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset); - } - } - } - } - - // make sure static version of highlight shader is bound before returning - gHighlightProgram.bind(); -} - -inline bool IsFullbright(LLDrawInfo& params) -{ - return params.mFullbright; -} - -inline bool IsMaterial(LLDrawInfo& params) -{ - return params.mMaterial != nullptr; -} - -inline bool IsEmissive(LLDrawInfo& params) -{ - return params.mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_EMISSIVE); -} - -inline void Draw(LLDrawInfo* draw, U32 mask) -{ - draw->mVertexBuffer->setBuffer(); - LLRenderPass::applyModelMatrix(*draw); - draw->mVertexBuffer->drawRange(LLRender::TRIANGLES, draw->mStart, draw->mEnd, draw->mCount, draw->mOffset); -} - -bool LLDrawPoolAlpha::TexSetup(LLDrawInfo* draw, bool use_material) -{ - bool tex_setup = false; - - if (draw->mGLTFMaterial) - { - if (draw->mTextureMatrix) - { - tex_setup = true; - gGL.getTexUnit(0)->activate(); - gGL.matrixMode(LLRender::MM_TEXTURE); - gGL.loadMatrix((GLfloat*)draw->mTextureMatrix->mMatrix); - gPipeline.mTextureMatrixOps++; - } - } - else - { - if (!LLPipeline::sRenderingHUDs && use_material && current_shader) - { - if (draw->mNormalMap) - { - current_shader->bindTexture(LLShaderMgr::BUMP_MAP, draw->mNormalMap); - } - - if (draw->mSpecularMap) - { - current_shader->bindTexture(LLShaderMgr::SPECULAR_MAP, draw->mSpecularMap); - } - } - else if (current_shader == simple_shader || current_shader == simple_shader->mRiggedVariant) - { - current_shader->bindTexture(LLShaderMgr::BUMP_MAP, LLViewerFetchedTexture::sFlatNormalImagep); - current_shader->bindTexture(LLShaderMgr::SPECULAR_MAP, LLViewerFetchedTexture::sWhiteImagep); - } - if (draw->mTextureList.size() > 1) - { - for (U32 i = 0; i < draw->mTextureList.size(); ++i) - { - if (draw->mTextureList[i].notNull()) - { - gGL.getTexUnit(i)->bindFast(draw->mTextureList[i]); - } - } - } - else - { //not batching textures or batch has only 1 texture -- might need a texture matrix - if (draw->mTexture.notNull()) - { - if (use_material) - { - current_shader->bindTexture(LLShaderMgr::DIFFUSE_MAP, draw->mTexture); - } - else - { - gGL.getTexUnit(0)->bindFast(draw->mTexture); - } - - if (draw->mTextureMatrix) - { - tex_setup = true; - gGL.getTexUnit(0)->activate(); - gGL.matrixMode(LLRender::MM_TEXTURE); - gGL.loadMatrix((GLfloat*)draw->mTextureMatrix->mMatrix); - gPipeline.mTextureMatrixOps++; - } - } - else - { - gGL.getTexUnit(0)->unbindFast(LLTexUnit::TT_TEXTURE); - } - } - } - - return tex_setup; -} - -void LLDrawPoolAlpha::RestoreTexSetup(bool tex_setup) -{ - if (tex_setup) - { - gGL.getTexUnit(0)->activate(); - gGL.matrixMode(LLRender::MM_TEXTURE); - gGL.loadIdentity(); - gGL.matrixMode(LLRender::MM_MODELVIEW); - } -} - -void LLDrawPoolAlpha::drawEmissive(LLDrawInfo* draw) -{ - LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::EMISSIVE_BRIGHTNESS, 1.f); - draw->mVertexBuffer->setBuffer(); - draw->mVertexBuffer->drawRange(LLRender::TRIANGLES, draw->mStart, draw->mEnd, draw->mCount, draw->mOffset); -} - - -void LLDrawPoolAlpha::renderEmissives(std::vector& emissives) -{ - emissive_shader->bind(); - emissive_shader->uniform1f(LLShaderMgr::EMISSIVE_BRIGHTNESS, 1.f); - - for (LLDrawInfo* draw : emissives) - { - bool tex_setup = TexSetup(draw, false); - drawEmissive(draw); - RestoreTexSetup(tex_setup); - } -} - -void LLDrawPoolAlpha::renderPbrEmissives(std::vector& emissives) -{ - pbr_emissive_shader->bind(); - - for (LLDrawInfo* draw : emissives) - { - llassert(draw->mGLTFMaterial); - LLGLDisable cull_face(draw->mGLTFMaterial->mDoubleSided ? GL_CULL_FACE : 0); - draw->mGLTFMaterial->bind(draw->mTexture); - draw->mVertexBuffer->setBuffer(); - draw->mVertexBuffer->drawRange(LLRender::TRIANGLES, draw->mStart, draw->mEnd, draw->mCount, draw->mOffset); - } -} - -void LLDrawPoolAlpha::renderRiggedEmissives(std::vector& emissives) -{ - LLGLDepthTest depth(GL_TRUE, GL_FALSE); //disable depth writes since "emissive" is additive so sorting doesn't matter - LLGLSLShader* shader = emissive_shader->mRiggedVariant; - shader->bind(); - shader->uniform1f(LLShaderMgr::EMISSIVE_BRIGHTNESS, 1.f); - - LLVOAvatar* lastAvatar = nullptr; - U64 lastMeshId = 0; - - for (LLDrawInfo* draw : emissives) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("Emissives"); - - bool tex_setup = TexSetup(draw, false); - if (lastAvatar != draw->mAvatar || lastMeshId != draw->mSkinInfo->mHash) - { - if (!uploadMatrixPalette(*draw)) - { // failed to upload matrix palette, skip rendering - continue; - } - lastAvatar = draw->mAvatar; - lastMeshId = draw->mSkinInfo->mHash; - } - drawEmissive(draw); - RestoreTexSetup(tex_setup); - } -} - -void LLDrawPoolAlpha::renderRiggedPbrEmissives(std::vector& emissives) -{ - LLGLDepthTest depth(GL_TRUE, GL_FALSE); //disable depth writes since "emissive" is additive so sorting doesn't matter - pbr_emissive_shader->bind(true); - - LLVOAvatar* lastAvatar = nullptr; - U64 lastMeshId = 0; - - for (LLDrawInfo* draw : emissives) - { - if (lastAvatar != draw->mAvatar || lastMeshId != draw->mSkinInfo->mHash) - { - if (!uploadMatrixPalette(*draw)) - { // failed to upload matrix palette, skip rendering - continue; - } - lastAvatar = draw->mAvatar; - lastMeshId = draw->mSkinInfo->mHash; - } - - LLGLDisable cull_face(draw->mGLTFMaterial->mDoubleSided ? GL_CULL_FACE : 0); - draw->mGLTFMaterial->bind(draw->mTexture); - draw->mVertexBuffer->setBuffer(); - draw->mVertexBuffer->drawRange(LLRender::TRIANGLES, draw->mStart, draw->mEnd, draw->mCount, draw->mOffset); - } -} - -void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only, bool rigged) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - bool initialized_lighting = false; - bool light_enabled = true; - - LLVOAvatar* lastAvatar = nullptr; - U64 lastMeshId = 0; - LLGLSLShader* lastAvatarShader = nullptr; - - LLCullResult::sg_iterator begin; - LLCullResult::sg_iterator end; - - if (rigged) - { - begin = gPipeline.beginRiggedAlphaGroups(); - end = gPipeline.endRiggedAlphaGroups(); - } - else - { - begin = gPipeline.beginAlphaGroups(); - end = gPipeline.endAlphaGroups(); - } - - LLEnvironment& env = LLEnvironment::instance(); - F32 water_height = env.getWaterHeight(); - - bool above_water = getType() == LLDrawPool::POOL_ALPHA_POST_WATER; - if (LLPipeline::sUnderWaterRender) - { - above_water = !above_water; - } - - - for (LLCullResult::sg_iterator i = begin; i != end; ++i) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("renderAlpha - group"); - LLSpatialGroup* group = *i; - llassert(group); - llassert(group->getSpatialPartition()); - - if (group->getSpatialPartition()->mRenderByGroup && - !group->isDead()) - { - - LLSpatialBridge* bridge = group->getSpatialPartition()->asBridge(); - const LLVector4a* ext = bridge ? bridge->getSpatialExtents() : group->getExtents(); - - if (!LLPipeline::sRenderingHUDs) // ignore above/below water for HUD render - { - if (above_water) - { // reject any spatial groups that have no part above water - if (ext[1].getF32ptr()[2] < water_height) - { - continue; - } - } - else - { // reject any spatial groups that he no part below water - if (ext[0].getF32ptr()[2] > water_height) - { - continue; - } - } - } - - static std::vector emissives; - static std::vector rigged_emissives; - static std::vector pbr_emissives; - static std::vector pbr_rigged_emissives; - - emissives.resize(0); - rigged_emissives.resize(0); - pbr_emissives.resize(0); - pbr_rigged_emissives.resize(0); - - bool is_particle_or_hud_particle = group->getSpatialPartition()->mPartitionType == LLViewerRegion::PARTITION_PARTICLE - || group->getSpatialPartition()->mPartitionType == LLViewerRegion::PARTITION_HUD_PARTICLE; - - bool disable_cull = is_particle_or_hud_particle; - LLGLDisable cull(disable_cull ? GL_CULL_FACE : 0); - - LLSpatialGroup::drawmap_elem_t& draw_info = rigged ? group->mDrawMap[LLRenderPass::PASS_ALPHA_RIGGED] : group->mDrawMap[LLRenderPass::PASS_ALPHA]; - - for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k) - { - LLDrawInfo& params = **k; - if ((bool)params.mAvatar != rigged) - { - continue; - } - - LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("ra - push batch"); - - LLRenderPass::applyModelMatrix(params); - - LLMaterial* mat = NULL; - LLGLTFMaterial *gltf_mat = params.mGLTFMaterial; - - LLGLDisable cull_face(gltf_mat && gltf_mat->mDoubleSided ? GL_CULL_FACE : 0); - - if (gltf_mat && gltf_mat->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_BLEND) - { - target_shader = pbr_shader; - if (params.mAvatar != nullptr) - { - target_shader = target_shader->mRiggedVariant; - } - - // shader must be bound before LLGLTFMaterial::bind - if (current_shader != target_shader) - { - gPipeline.bindDeferredShaderFast(*target_shader); - } - - params.mGLTFMaterial->bind(params.mTexture); - } - else - { - mat = LLPipeline::sRenderingHUDs ? nullptr : params.mMaterial; - - if (params.mFullbright) - { - // Turn off lighting if it hasn't already been so. - if (light_enabled || !initialized_lighting) - { - initialized_lighting = true; - target_shader = fullbright_shader; - - light_enabled = false; - } - } - // Turn on lighting if it isn't already. - else if (!light_enabled || !initialized_lighting) - { - initialized_lighting = true; - target_shader = simple_shader; - light_enabled = true; - } - - if (LLPipeline::sRenderingHUDs) - { - target_shader = fullbright_shader; - } - else if (mat) - { - U32 mask = params.mShaderMask; - - llassert(mask < LLMaterial::SHADER_COUNT); - target_shader = &(gDeferredMaterialProgram[mask]); - } - else if (!params.mFullbright) - { - target_shader = simple_shader; - } - else - { - target_shader = fullbright_shader; - } - - if (params.mAvatar != nullptr) - { - llassert(target_shader->mRiggedVariant != nullptr); - target_shader = target_shader->mRiggedVariant; - } - - if (current_shader != target_shader) - {// If we need shaders, and we're not ALREADY using the proper shader, then bind it - // (this way we won't rebind shaders unnecessarily). - gPipeline.bindDeferredShaderFast(*target_shader); - - if (params.mFullbright) - { // make sure the bind the exposure map for fullbright shaders so they can cancel out exposure - S32 channel = target_shader->enableTexture(LLShaderMgr::EXPOSURE_MAP); - if (channel > -1) - { - gGL.getTexUnit(channel)->bind(&gPipeline.mExposureMap); - } - } - } - - LLVector4 spec_color(1, 1, 1, 1); - F32 env_intensity = 0.0f; - F32 brightness = 1.0f; - - // We have a material. Supply the appropriate data here. - if (mat) - { - spec_color = params.mSpecColor; - env_intensity = params.mEnvIntensity; - brightness = params.mFullbright ? 1.f : 0.f; - } - - if (current_shader) - { - current_shader->uniform4f(LLShaderMgr::SPECULAR_COLOR, spec_color.mV[0], spec_color.mV[1], spec_color.mV[2], spec_color.mV[3]); - current_shader->uniform1f(LLShaderMgr::ENVIRONMENT_INTENSITY, env_intensity); - current_shader->uniform1f(LLShaderMgr::EMISSIVE_BRIGHTNESS, brightness); - } - } - - if (params.mAvatar != nullptr) - { - if (lastAvatar != params.mAvatar || - lastMeshId != params.mSkinInfo->mHash || - lastAvatarShader != LLGLSLShader::sCurBoundShaderPtr) - { - if (!uploadMatrixPalette(params)) - { - continue; - } - lastAvatar = params.mAvatar; - lastMeshId = params.mSkinInfo->mHash; - lastAvatarShader = LLGLSLShader::sCurBoundShaderPtr; - } - } - - bool tex_setup = TexSetup(¶ms, (mat != nullptr)); - - { - gGL.blendFunc((LLRender::eBlendFactor) params.mBlendFuncSrc, (LLRender::eBlendFactor) params.mBlendFuncDst, mAlphaSFactor, mAlphaDFactor); - - bool reset_minimum_alpha = false; - if (!LLPipeline::sImpostorRender && - params.mBlendFuncDst != LLRender::BF_SOURCE_ALPHA && - params.mBlendFuncSrc != LLRender::BF_SOURCE_ALPHA) - { // this draw call has a custom blend function that may require rendering of "invisible" fragments - current_shader->setMinimumAlpha(0.f); - reset_minimum_alpha = true; - } - - params.mVertexBuffer->setBuffer(); - params.mVertexBuffer->drawRange(LLRender::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset); - - if (reset_minimum_alpha) - { - current_shader->setMinimumAlpha(MINIMUM_ALPHA); - } - } - - // If this alpha mesh has glow, then draw it a second time to add the destination-alpha (=glow). Interleaving these state-changing calls is expensive, but glow must be drawn Z-sorted with alpha. - if (getType() != LLDrawPool::POOL_ALPHA_PRE_WATER && - params.mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_EMISSIVE)) - { - if (params.mAvatar != nullptr) - { - if (params.mGLTFMaterial.isNull()) - { - rigged_emissives.push_back(¶ms); - } - else - { - pbr_rigged_emissives.push_back(¶ms); - } - } - else - { - if (params.mGLTFMaterial.isNull()) - { - emissives.push_back(¶ms); - } - else - { - pbr_emissives.push_back(¶ms); - } - } - } - - if (tex_setup) - { - gGL.getTexUnit(0)->activate(); - gGL.matrixMode(LLRender::MM_TEXTURE); - gGL.loadIdentity(); - gGL.matrixMode(LLRender::MM_MODELVIEW); - } - } - - // render emissive faces into alpha channel for bloom effects - if (!depth_only) - { - gPipeline.enableLightsDynamic(); - - // install glow-accumulating blend mode - // don't touch color, add to alpha (glow) - gGL.blendFunc(LLRender::BF_ZERO, LLRender::BF_ONE, LLRender::BF_ONE, LLRender::BF_ONE); - - bool rebind = false; - LLGLSLShader* lastShader = current_shader; - if (!emissives.empty()) - { - light_enabled = true; - renderEmissives(emissives); - rebind = true; - } - - if (!pbr_emissives.empty()) - { - light_enabled = true; - renderPbrEmissives(pbr_emissives); - rebind = true; - } - - if (!rigged_emissives.empty()) - { - light_enabled = true; - renderRiggedEmissives(rigged_emissives); - rebind = true; - } - - if (!pbr_rigged_emissives.empty()) - { - light_enabled = true; - renderRiggedPbrEmissives(pbr_rigged_emissives); - rebind = true; - } - - // restore our alpha blend mode - gGL.blendFunc(mColorSFactor, mColorDFactor, mAlphaSFactor, mAlphaDFactor); - - if (lastShader && rebind) - { - lastShader->bind(); - } - } - } - } - - gGL.setSceneBlendType(LLRender::BT_ALPHA); - - LLVertexBuffer::unbind(); - - if (!light_enabled) - { - gPipeline.enableLightsDynamic(); - } -} - -bool LLDrawPoolAlpha::uploadMatrixPalette(const LLDrawInfo& params) -{ - if (params.mAvatar.isNull()) - { - return false; - } - const LLVOAvatar::MatrixPaletteCache& mpc = params.mAvatar.get()->updateSkinInfoMatrixPalette(params.mSkinInfo); - U32 count = mpc.mMatrixPalette.size(); - - if (count == 0) - { - //skin info not loaded yet, don't render - return false; - } - - LLGLSLShader::sCurBoundShaderPtr->uniformMatrix3x4fv(LLViewerShaderMgr::AVATAR_MATRIX, - count, - false, - (GLfloat*)&(mpc.mGLMp[0])); - - return true; -} +/** + * @file lldrawpoolalpha.cpp + * @brief LLDrawPoolAlpha class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lldrawpoolalpha.h" + +#include "llglheaders.h" +#include "llviewercontrol.h" +#include "llcriticaldamp.h" +#include "llfasttimer.h" +#include "llrender.h" + +#include "llcubemap.h" +#include "llsky.h" +#include "lldrawable.h" +#include "llface.h" +#include "llviewercamera.h" +#include "llviewertexturelist.h" // For debugging +#include "llviewerobjectlist.h" // For debugging +#include "llviewerwindow.h" +#include "pipeline.h" +#include "llviewershadermgr.h" +#include "llviewerregion.h" +#include "lldrawpoolwater.h" +#include "llspatialpartition.h" +#include "llglcommonfunc.h" +#include "llvoavatar.h" + +#include "llenvironment.h" + +bool LLDrawPoolAlpha::sShowDebugAlpha = false; + +#define current_shader (LLGLSLShader::sCurBoundShaderPtr) + +LLVector4 LLDrawPoolAlpha::sWaterPlane; + +// minimum alpha before discarding a fragment +static const F32 MINIMUM_ALPHA = 0.004f; // ~ 1/255 + +// minimum alpha before discarding a fragment when rendering impostors +static const F32 MINIMUM_IMPOSTOR_ALPHA = 0.1f; + +LLDrawPoolAlpha::LLDrawPoolAlpha(U32 type) : + LLRenderPass(type), target_shader(NULL), + mColorSFactor(LLRender::BF_UNDEF), mColorDFactor(LLRender::BF_UNDEF), + mAlphaSFactor(LLRender::BF_UNDEF), mAlphaDFactor(LLRender::BF_UNDEF) +{ + +} + +LLDrawPoolAlpha::~LLDrawPoolAlpha() +{ +} + + +void LLDrawPoolAlpha::prerender() +{ + mShaderLevel = LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_OBJECT); + + // TODO: is this even necessay? These are probably set to never discard + LLViewerFetchedTexture::sFlatNormalImagep->addTextureStats(1024.f*1024.f); + LLViewerFetchedTexture::sWhiteImagep->addTextureStats(1024.f * 1024.f); +} + +S32 LLDrawPoolAlpha::getNumPostDeferredPasses() +{ + return 1; +} + +// set some common parameters on the given shader to prepare for alpha rendering +static void prepare_alpha_shader(LLGLSLShader* shader, bool textureGamma, bool deferredEnvironment, F32 water_sign) +{ + static LLCachedControl displayGamma(gSavedSettings, "RenderDeferredDisplayGamma"); + F32 gamma = displayGamma; + + static LLStaticHashedString waterSign("waterSign"); + + // Does this deferred shader need environment uniforms set such as sun_dir, etc. ? + // NOTE: We don't actually need a gbuffer since we are doing forward rendering (for transparency) post deferred rendering + // TODO: bindDeferredShader() probably should have the updating of the environment uniforms factored out into updateShaderEnvironmentUniforms() + // i.e. shaders\class1\deferred\alphaF.glsl + if (deferredEnvironment) + { + shader->mCanBindFast = false; + } + + shader->bind(); + shader->uniform1f(LLShaderMgr::DISPLAY_GAMMA, (gamma > 0.1f) ? 1.0f / gamma : (1.0f / 2.2f)); + + if (LLPipeline::sRenderingHUDs) + { // for HUD attachments, only the pre-water pass is executed and we never want to clip anything + LLVector4 near_clip(0, 0, -1, 0); + shader->uniform1f(waterSign, 1.f); + shader->uniform4fv(LLShaderMgr::WATER_WATERPLANE, 1, near_clip.mV); + } + else + { + shader->uniform1f(waterSign, water_sign); + shader->uniform4fv(LLShaderMgr::WATER_WATERPLANE, 1, LLDrawPoolAlpha::sWaterPlane.mV); + } + + if (LLPipeline::sImpostorRender) + { + shader->setMinimumAlpha(MINIMUM_IMPOSTOR_ALPHA); + } + else + { + shader->setMinimumAlpha(MINIMUM_ALPHA); + } + if (textureGamma) + { + shader->uniform1f(LLShaderMgr::TEXTURE_GAMMA, 2.2f); + } + + //also prepare rigged variant + if (shader->mRiggedVariant && shader->mRiggedVariant != shader) + { + prepare_alpha_shader(shader->mRiggedVariant, textureGamma, deferredEnvironment, water_sign); + } +} + +extern bool gCubeSnapshot; + +void LLDrawPoolAlpha::renderPostDeferred(S32 pass) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + + if (LLPipeline::isWaterClip() && getType() == LLDrawPool::POOL_ALPHA_PRE_WATER) + { // don't render alpha objects on the other side of the water plane if water is opaque + return; + } + + F32 water_sign = 1.f; + + if (getType() == LLDrawPool::POOL_ALPHA_PRE_WATER) + { + water_sign = -1.f; + } + + if (LLPipeline::sUnderWaterRender) + { + water_sign *= -1.f; + } + + // prepare shaders + llassert(LLPipeline::sRenderDeferred); + + emissive_shader = &gDeferredEmissiveProgram; + prepare_alpha_shader(emissive_shader, true, false, water_sign); + + pbr_emissive_shader = &gPBRGlowProgram; + prepare_alpha_shader(pbr_emissive_shader, true, false, water_sign); + + + fullbright_shader = + (LLPipeline::sImpostorRender) ? &gDeferredFullbrightAlphaMaskProgram : + (LLPipeline::sRenderingHUDs) ? &gHUDFullbrightAlphaMaskAlphaProgram : + &gDeferredFullbrightAlphaMaskAlphaProgram; + prepare_alpha_shader(fullbright_shader, true, true, water_sign); + + simple_shader = + (LLPipeline::sImpostorRender) ? &gDeferredAlphaImpostorProgram : + (LLPipeline::sRenderingHUDs) ? &gHUDAlphaProgram : + &gDeferredAlphaProgram; + + prepare_alpha_shader(simple_shader, false, true, water_sign); //prime simple shader (loads shadow relevant uniforms) + + LLGLSLShader* materialShader = gDeferredMaterialProgram; + for (int i = 0; i < LLMaterial::SHADER_COUNT*2; ++i) + { + prepare_alpha_shader(&materialShader[i], false, true, water_sign); + } + + pbr_shader = + (LLPipeline::sRenderingHUDs) ? &gHUDPBRAlphaProgram : + &gDeferredPBRAlphaProgram; + + prepare_alpha_shader(pbr_shader, false, true, water_sign); + + // explicitly unbind here so render loop doesn't make assumptions about the last shader + // already being setup for rendering + LLGLSLShader::unbind(); + + if (!LLPipeline::sRenderingHUDs) + { + // first pass, render rigged objects only and render to depth buffer + forwardRender(true); + } + + // second pass, regular forward alpha rendering + forwardRender(); + + // final pass, render to depth for depth of field effects + if (!LLPipeline::sImpostorRender && gSavedSettings.getBOOL("RenderDepthOfField") && !gCubeSnapshot && !LLPipeline::sRenderingHUDs && getType() == LLDrawPool::POOL_ALPHA_POST_WATER) + { + //update depth buffer sampler + simple_shader = fullbright_shader = &gDeferredFullbrightAlphaMaskProgram; + + simple_shader->bind(); + simple_shader->setMinimumAlpha(0.33f); + + // mask off color buffer writes as we're only writing to depth buffer + gGL.setColorMask(false, false); + + // If the face is more than 90% transparent, then don't update the Depth buffer for Dof + // We don't want the nearly invisible objects to cause of DoF effects + renderAlpha(getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX | LLVertexBuffer::MAP_TANGENT | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TEXCOORD2, + true); // <--- discard mostly transparent faces + + gGL.setColorMask(true, false); + } +} + +void LLDrawPoolAlpha::forwardRender(bool rigged) +{ + gPipeline.enableLightsDynamic(); + + LLGLSPipelineAlpha gls_pipeline_alpha; + + //enable writing to alpha for emissive effects + gGL.setColorMask(true, true); + + bool write_depth = rigged || + LLDrawPoolWater::sSkipScreenCopy + // we want depth written so that rendered alpha will + // contribute to the alpha mask used for impostors + || LLPipeline::sImpostorRenderAlphaDepthPass + || getType() == LLDrawPoolAlpha::POOL_ALPHA_PRE_WATER; // needed for accurate water fog + + + LLGLDepthTest depth(GL_TRUE, write_depth ? GL_TRUE : GL_FALSE); + + mColorSFactor = LLRender::BF_SOURCE_ALPHA; // } regular alpha blend + mColorDFactor = LLRender::BF_ONE_MINUS_SOURCE_ALPHA; // } + mAlphaSFactor = LLRender::BF_ZERO; // } glow suppression + mAlphaDFactor = LLRender::BF_ONE_MINUS_SOURCE_ALPHA; // } + gGL.blendFunc(mColorSFactor, mColorDFactor, mAlphaSFactor, mAlphaDFactor); + + // If the face is more than 90% transparent, then don't update the Depth buffer for Dof + // We don't want the nearly invisible objects to cause of DoF effects + renderAlpha(getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX | LLVertexBuffer::MAP_TANGENT | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TEXCOORD2, false, rigged); + + gGL.setColorMask(true, false); + + if (!rigged) + { //render "highlight alpha" on final non-rigged pass + // NOTE -- hacky call here protected by !rigged instead of alongside "forwardRender" + // so renderDebugAlpha is executed while gls_pipeline_alpha and depth GL state + // variables above are still in scope + renderDebugAlpha(); + } +} + +void LLDrawPoolAlpha::renderDebugAlpha() +{ + if (sShowDebugAlpha) + { + gHighlightProgram.bind(); + gGL.diffuseColor4f(1, 0, 0, 1); + gGL.getTexUnit(0)->bindFast(LLViewerFetchedTexture::getSmokeImage()); + + + renderAlphaHighlight(); + + pushUntexturedBatches(LLRenderPass::PASS_ALPHA_MASK); + pushUntexturedBatches(LLRenderPass::PASS_ALPHA_INVISIBLE); + + // Material alpha mask + gGL.diffuseColor4f(0, 0, 1, 1); + pushUntexturedBatches(LLRenderPass::PASS_MATERIAL_ALPHA_MASK); + pushUntexturedBatches(LLRenderPass::PASS_NORMMAP_MASK); + pushUntexturedBatches(LLRenderPass::PASS_SPECMAP_MASK); + pushUntexturedBatches(LLRenderPass::PASS_NORMSPEC_MASK); + pushUntexturedBatches(LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK); + pushUntexturedBatches(LLRenderPass::PASS_GLTF_PBR_ALPHA_MASK); + + gGL.diffuseColor4f(0, 1, 0, 1); + pushUntexturedBatches(LLRenderPass::PASS_INVISIBLE); + + gHighlightProgram.mRiggedVariant->bind(); + gGL.diffuseColor4f(1, 0, 0, 1); + + pushRiggedBatches(LLRenderPass::PASS_ALPHA_MASK_RIGGED, false); + pushRiggedBatches(LLRenderPass::PASS_ALPHA_INVISIBLE_RIGGED, false); + + // Material alpha mask + gGL.diffuseColor4f(0, 0, 1, 1); + pushRiggedBatches(LLRenderPass::PASS_MATERIAL_ALPHA_MASK_RIGGED, false); + pushRiggedBatches(LLRenderPass::PASS_NORMMAP_MASK_RIGGED, false); + pushRiggedBatches(LLRenderPass::PASS_SPECMAP_MASK_RIGGED, false); + pushRiggedBatches(LLRenderPass::PASS_NORMSPEC_MASK_RIGGED, false); + pushRiggedBatches(LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK_RIGGED, false); + pushRiggedBatches(LLRenderPass::PASS_GLTF_PBR_ALPHA_MASK_RIGGED, false); + + gGL.diffuseColor4f(0, 1, 0, 1); + pushRiggedBatches(LLRenderPass::PASS_INVISIBLE_RIGGED, false); + LLGLSLShader::sCurBoundShaderPtr->unbind(); + } +} + +void LLDrawPoolAlpha::renderAlphaHighlight() +{ + for (int pass = 0; pass < 2; ++pass) + { //two passes, one rigged and one not + LLVOAvatar* lastAvatar = nullptr; + U64 lastMeshId = 0; + + LLCullResult::sg_iterator begin = pass == 0 ? gPipeline.beginAlphaGroups() : gPipeline.beginRiggedAlphaGroups(); + LLCullResult::sg_iterator end = pass == 0 ? gPipeline.endAlphaGroups() : gPipeline.endRiggedAlphaGroups(); + + for (LLCullResult::sg_iterator i = begin; i != end; ++i) + { + LLSpatialGroup* group = *i; + if (group->getSpatialPartition()->mRenderByGroup && + !group->isDead()) + { + LLSpatialGroup::drawmap_elem_t& draw_info = group->mDrawMap[LLRenderPass::PASS_ALPHA+pass]; // <-- hacky + pass to use PASS_ALPHA_RIGGED on second pass + + for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k) + { + LLDrawInfo& params = **k; + + bool rigged = (params.mAvatar != nullptr); + gHighlightProgram.bind(rigged); + gGL.diffuseColor4f(1, 0, 0, 1); + + if (rigged) + { + if (lastAvatar != params.mAvatar || + lastMeshId != params.mSkinInfo->mHash) + { + if (!uploadMatrixPalette(params)) + { + continue; + } + lastAvatar = params.mAvatar; + lastMeshId = params.mSkinInfo->mHash; + } + } + + LLRenderPass::applyModelMatrix(params); + params.mVertexBuffer->setBuffer(); + params.mVertexBuffer->drawRange(LLRender::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset); + } + } + } + } + + // make sure static version of highlight shader is bound before returning + gHighlightProgram.bind(); +} + +inline bool IsFullbright(LLDrawInfo& params) +{ + return params.mFullbright; +} + +inline bool IsMaterial(LLDrawInfo& params) +{ + return params.mMaterial != nullptr; +} + +inline bool IsEmissive(LLDrawInfo& params) +{ + return params.mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_EMISSIVE); +} + +inline void Draw(LLDrawInfo* draw, U32 mask) +{ + draw->mVertexBuffer->setBuffer(); + LLRenderPass::applyModelMatrix(*draw); + draw->mVertexBuffer->drawRange(LLRender::TRIANGLES, draw->mStart, draw->mEnd, draw->mCount, draw->mOffset); +} + +bool LLDrawPoolAlpha::TexSetup(LLDrawInfo* draw, bool use_material) +{ + bool tex_setup = false; + + if (draw->mGLTFMaterial) + { + if (draw->mTextureMatrix) + { + tex_setup = true; + gGL.getTexUnit(0)->activate(); + gGL.matrixMode(LLRender::MM_TEXTURE); + gGL.loadMatrix((GLfloat*)draw->mTextureMatrix->mMatrix); + gPipeline.mTextureMatrixOps++; + } + } + else + { + if (!LLPipeline::sRenderingHUDs && use_material && current_shader) + { + if (draw->mNormalMap) + { + current_shader->bindTexture(LLShaderMgr::BUMP_MAP, draw->mNormalMap); + } + + if (draw->mSpecularMap) + { + current_shader->bindTexture(LLShaderMgr::SPECULAR_MAP, draw->mSpecularMap); + } + } + else if (current_shader == simple_shader || current_shader == simple_shader->mRiggedVariant) + { + current_shader->bindTexture(LLShaderMgr::BUMP_MAP, LLViewerFetchedTexture::sFlatNormalImagep); + current_shader->bindTexture(LLShaderMgr::SPECULAR_MAP, LLViewerFetchedTexture::sWhiteImagep); + } + if (draw->mTextureList.size() > 1) + { + for (U32 i = 0; i < draw->mTextureList.size(); ++i) + { + if (draw->mTextureList[i].notNull()) + { + gGL.getTexUnit(i)->bindFast(draw->mTextureList[i]); + } + } + } + else + { //not batching textures or batch has only 1 texture -- might need a texture matrix + if (draw->mTexture.notNull()) + { + if (use_material) + { + current_shader->bindTexture(LLShaderMgr::DIFFUSE_MAP, draw->mTexture); + } + else + { + gGL.getTexUnit(0)->bindFast(draw->mTexture); + } + + if (draw->mTextureMatrix) + { + tex_setup = true; + gGL.getTexUnit(0)->activate(); + gGL.matrixMode(LLRender::MM_TEXTURE); + gGL.loadMatrix((GLfloat*)draw->mTextureMatrix->mMatrix); + gPipeline.mTextureMatrixOps++; + } + } + else + { + gGL.getTexUnit(0)->unbindFast(LLTexUnit::TT_TEXTURE); + } + } + } + + return tex_setup; +} + +void LLDrawPoolAlpha::RestoreTexSetup(bool tex_setup) +{ + if (tex_setup) + { + gGL.getTexUnit(0)->activate(); + gGL.matrixMode(LLRender::MM_TEXTURE); + gGL.loadIdentity(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + } +} + +void LLDrawPoolAlpha::drawEmissive(LLDrawInfo* draw) +{ + LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::EMISSIVE_BRIGHTNESS, 1.f); + draw->mVertexBuffer->setBuffer(); + draw->mVertexBuffer->drawRange(LLRender::TRIANGLES, draw->mStart, draw->mEnd, draw->mCount, draw->mOffset); +} + + +void LLDrawPoolAlpha::renderEmissives(std::vector& emissives) +{ + emissive_shader->bind(); + emissive_shader->uniform1f(LLShaderMgr::EMISSIVE_BRIGHTNESS, 1.f); + + for (LLDrawInfo* draw : emissives) + { + bool tex_setup = TexSetup(draw, false); + drawEmissive(draw); + RestoreTexSetup(tex_setup); + } +} + +void LLDrawPoolAlpha::renderPbrEmissives(std::vector& emissives) +{ + pbr_emissive_shader->bind(); + + for (LLDrawInfo* draw : emissives) + { + llassert(draw->mGLTFMaterial); + LLGLDisable cull_face(draw->mGLTFMaterial->mDoubleSided ? GL_CULL_FACE : 0); + draw->mGLTFMaterial->bind(draw->mTexture); + draw->mVertexBuffer->setBuffer(); + draw->mVertexBuffer->drawRange(LLRender::TRIANGLES, draw->mStart, draw->mEnd, draw->mCount, draw->mOffset); + } +} + +void LLDrawPoolAlpha::renderRiggedEmissives(std::vector& emissives) +{ + LLGLDepthTest depth(GL_TRUE, GL_FALSE); //disable depth writes since "emissive" is additive so sorting doesn't matter + LLGLSLShader* shader = emissive_shader->mRiggedVariant; + shader->bind(); + shader->uniform1f(LLShaderMgr::EMISSIVE_BRIGHTNESS, 1.f); + + LLVOAvatar* lastAvatar = nullptr; + U64 lastMeshId = 0; + + for (LLDrawInfo* draw : emissives) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("Emissives"); + + bool tex_setup = TexSetup(draw, false); + if (lastAvatar != draw->mAvatar || lastMeshId != draw->mSkinInfo->mHash) + { + if (!uploadMatrixPalette(*draw)) + { // failed to upload matrix palette, skip rendering + continue; + } + lastAvatar = draw->mAvatar; + lastMeshId = draw->mSkinInfo->mHash; + } + drawEmissive(draw); + RestoreTexSetup(tex_setup); + } +} + +void LLDrawPoolAlpha::renderRiggedPbrEmissives(std::vector& emissives) +{ + LLGLDepthTest depth(GL_TRUE, GL_FALSE); //disable depth writes since "emissive" is additive so sorting doesn't matter + pbr_emissive_shader->bind(true); + + LLVOAvatar* lastAvatar = nullptr; + U64 lastMeshId = 0; + + for (LLDrawInfo* draw : emissives) + { + if (lastAvatar != draw->mAvatar || lastMeshId != draw->mSkinInfo->mHash) + { + if (!uploadMatrixPalette(*draw)) + { // failed to upload matrix palette, skip rendering + continue; + } + lastAvatar = draw->mAvatar; + lastMeshId = draw->mSkinInfo->mHash; + } + + LLGLDisable cull_face(draw->mGLTFMaterial->mDoubleSided ? GL_CULL_FACE : 0); + draw->mGLTFMaterial->bind(draw->mTexture); + draw->mVertexBuffer->setBuffer(); + draw->mVertexBuffer->drawRange(LLRender::TRIANGLES, draw->mStart, draw->mEnd, draw->mCount, draw->mOffset); + } +} + +void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only, bool rigged) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + bool initialized_lighting = false; + bool light_enabled = true; + + LLVOAvatar* lastAvatar = nullptr; + U64 lastMeshId = 0; + LLGLSLShader* lastAvatarShader = nullptr; + + LLCullResult::sg_iterator begin; + LLCullResult::sg_iterator end; + + if (rigged) + { + begin = gPipeline.beginRiggedAlphaGroups(); + end = gPipeline.endRiggedAlphaGroups(); + } + else + { + begin = gPipeline.beginAlphaGroups(); + end = gPipeline.endAlphaGroups(); + } + + LLEnvironment& env = LLEnvironment::instance(); + F32 water_height = env.getWaterHeight(); + + bool above_water = getType() == LLDrawPool::POOL_ALPHA_POST_WATER; + if (LLPipeline::sUnderWaterRender) + { + above_water = !above_water; + } + + + for (LLCullResult::sg_iterator i = begin; i != end; ++i) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("renderAlpha - group"); + LLSpatialGroup* group = *i; + llassert(group); + llassert(group->getSpatialPartition()); + + if (group->getSpatialPartition()->mRenderByGroup && + !group->isDead()) + { + + LLSpatialBridge* bridge = group->getSpatialPartition()->asBridge(); + const LLVector4a* ext = bridge ? bridge->getSpatialExtents() : group->getExtents(); + + if (!LLPipeline::sRenderingHUDs) // ignore above/below water for HUD render + { + if (above_water) + { // reject any spatial groups that have no part above water + if (ext[1].getF32ptr()[2] < water_height) + { + continue; + } + } + else + { // reject any spatial groups that he no part below water + if (ext[0].getF32ptr()[2] > water_height) + { + continue; + } + } + } + + static std::vector emissives; + static std::vector rigged_emissives; + static std::vector pbr_emissives; + static std::vector pbr_rigged_emissives; + + emissives.resize(0); + rigged_emissives.resize(0); + pbr_emissives.resize(0); + pbr_rigged_emissives.resize(0); + + bool is_particle_or_hud_particle = group->getSpatialPartition()->mPartitionType == LLViewerRegion::PARTITION_PARTICLE + || group->getSpatialPartition()->mPartitionType == LLViewerRegion::PARTITION_HUD_PARTICLE; + + bool disable_cull = is_particle_or_hud_particle; + LLGLDisable cull(disable_cull ? GL_CULL_FACE : 0); + + LLSpatialGroup::drawmap_elem_t& draw_info = rigged ? group->mDrawMap[LLRenderPass::PASS_ALPHA_RIGGED] : group->mDrawMap[LLRenderPass::PASS_ALPHA]; + + for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k) + { + LLDrawInfo& params = **k; + if ((bool)params.mAvatar != rigged) + { + continue; + } + + LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("ra - push batch"); + + LLRenderPass::applyModelMatrix(params); + + LLMaterial* mat = NULL; + LLGLTFMaterial *gltf_mat = params.mGLTFMaterial; + + LLGLDisable cull_face(gltf_mat && gltf_mat->mDoubleSided ? GL_CULL_FACE : 0); + + if (gltf_mat && gltf_mat->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_BLEND) + { + target_shader = pbr_shader; + if (params.mAvatar != nullptr) + { + target_shader = target_shader->mRiggedVariant; + } + + // shader must be bound before LLGLTFMaterial::bind + if (current_shader != target_shader) + { + gPipeline.bindDeferredShaderFast(*target_shader); + } + + params.mGLTFMaterial->bind(params.mTexture); + } + else + { + mat = LLPipeline::sRenderingHUDs ? nullptr : params.mMaterial; + + if (params.mFullbright) + { + // Turn off lighting if it hasn't already been so. + if (light_enabled || !initialized_lighting) + { + initialized_lighting = true; + target_shader = fullbright_shader; + + light_enabled = false; + } + } + // Turn on lighting if it isn't already. + else if (!light_enabled || !initialized_lighting) + { + initialized_lighting = true; + target_shader = simple_shader; + light_enabled = true; + } + + if (LLPipeline::sRenderingHUDs) + { + target_shader = fullbright_shader; + } + else if (mat) + { + U32 mask = params.mShaderMask; + + llassert(mask < LLMaterial::SHADER_COUNT); + target_shader = &(gDeferredMaterialProgram[mask]); + } + else if (!params.mFullbright) + { + target_shader = simple_shader; + } + else + { + target_shader = fullbright_shader; + } + + if (params.mAvatar != nullptr) + { + llassert(target_shader->mRiggedVariant != nullptr); + target_shader = target_shader->mRiggedVariant; + } + + if (current_shader != target_shader) + {// If we need shaders, and we're not ALREADY using the proper shader, then bind it + // (this way we won't rebind shaders unnecessarily). + gPipeline.bindDeferredShaderFast(*target_shader); + + if (params.mFullbright) + { // make sure the bind the exposure map for fullbright shaders so they can cancel out exposure + S32 channel = target_shader->enableTexture(LLShaderMgr::EXPOSURE_MAP); + if (channel > -1) + { + gGL.getTexUnit(channel)->bind(&gPipeline.mExposureMap); + } + } + } + + LLVector4 spec_color(1, 1, 1, 1); + F32 env_intensity = 0.0f; + F32 brightness = 1.0f; + + // We have a material. Supply the appropriate data here. + if (mat) + { + spec_color = params.mSpecColor; + env_intensity = params.mEnvIntensity; + brightness = params.mFullbright ? 1.f : 0.f; + } + + if (current_shader) + { + current_shader->uniform4f(LLShaderMgr::SPECULAR_COLOR, spec_color.mV[0], spec_color.mV[1], spec_color.mV[2], spec_color.mV[3]); + current_shader->uniform1f(LLShaderMgr::ENVIRONMENT_INTENSITY, env_intensity); + current_shader->uniform1f(LLShaderMgr::EMISSIVE_BRIGHTNESS, brightness); + } + } + + if (params.mAvatar != nullptr) + { + if (lastAvatar != params.mAvatar || + lastMeshId != params.mSkinInfo->mHash || + lastAvatarShader != LLGLSLShader::sCurBoundShaderPtr) + { + if (!uploadMatrixPalette(params)) + { + continue; + } + lastAvatar = params.mAvatar; + lastMeshId = params.mSkinInfo->mHash; + lastAvatarShader = LLGLSLShader::sCurBoundShaderPtr; + } + } + + bool tex_setup = TexSetup(¶ms, (mat != nullptr)); + + { + gGL.blendFunc((LLRender::eBlendFactor) params.mBlendFuncSrc, (LLRender::eBlendFactor) params.mBlendFuncDst, mAlphaSFactor, mAlphaDFactor); + + bool reset_minimum_alpha = false; + if (!LLPipeline::sImpostorRender && + params.mBlendFuncDst != LLRender::BF_SOURCE_ALPHA && + params.mBlendFuncSrc != LLRender::BF_SOURCE_ALPHA) + { // this draw call has a custom blend function that may require rendering of "invisible" fragments + current_shader->setMinimumAlpha(0.f); + reset_minimum_alpha = true; + } + + params.mVertexBuffer->setBuffer(); + params.mVertexBuffer->drawRange(LLRender::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset); + + if (reset_minimum_alpha) + { + current_shader->setMinimumAlpha(MINIMUM_ALPHA); + } + } + + // If this alpha mesh has glow, then draw it a second time to add the destination-alpha (=glow). Interleaving these state-changing calls is expensive, but glow must be drawn Z-sorted with alpha. + if (getType() != LLDrawPool::POOL_ALPHA_PRE_WATER && + params.mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_EMISSIVE)) + { + if (params.mAvatar != nullptr) + { + if (params.mGLTFMaterial.isNull()) + { + rigged_emissives.push_back(¶ms); + } + else + { + pbr_rigged_emissives.push_back(¶ms); + } + } + else + { + if (params.mGLTFMaterial.isNull()) + { + emissives.push_back(¶ms); + } + else + { + pbr_emissives.push_back(¶ms); + } + } + } + + if (tex_setup) + { + gGL.getTexUnit(0)->activate(); + gGL.matrixMode(LLRender::MM_TEXTURE); + gGL.loadIdentity(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + } + } + + // render emissive faces into alpha channel for bloom effects + if (!depth_only) + { + gPipeline.enableLightsDynamic(); + + // install glow-accumulating blend mode + // don't touch color, add to alpha (glow) + gGL.blendFunc(LLRender::BF_ZERO, LLRender::BF_ONE, LLRender::BF_ONE, LLRender::BF_ONE); + + bool rebind = false; + LLGLSLShader* lastShader = current_shader; + if (!emissives.empty()) + { + light_enabled = true; + renderEmissives(emissives); + rebind = true; + } + + if (!pbr_emissives.empty()) + { + light_enabled = true; + renderPbrEmissives(pbr_emissives); + rebind = true; + } + + if (!rigged_emissives.empty()) + { + light_enabled = true; + renderRiggedEmissives(rigged_emissives); + rebind = true; + } + + if (!pbr_rigged_emissives.empty()) + { + light_enabled = true; + renderRiggedPbrEmissives(pbr_rigged_emissives); + rebind = true; + } + + // restore our alpha blend mode + gGL.blendFunc(mColorSFactor, mColorDFactor, mAlphaSFactor, mAlphaDFactor); + + if (lastShader && rebind) + { + lastShader->bind(); + } + } + } + } + + gGL.setSceneBlendType(LLRender::BT_ALPHA); + + LLVertexBuffer::unbind(); + + if (!light_enabled) + { + gPipeline.enableLightsDynamic(); + } +} + +bool LLDrawPoolAlpha::uploadMatrixPalette(const LLDrawInfo& params) +{ + if (params.mAvatar.isNull()) + { + return false; + } + const LLVOAvatar::MatrixPaletteCache& mpc = params.mAvatar.get()->updateSkinInfoMatrixPalette(params.mSkinInfo); + U32 count = mpc.mMatrixPalette.size(); + + if (count == 0) + { + //skin info not loaded yet, don't render + return false; + } + + LLGLSLShader::sCurBoundShaderPtr->uniformMatrix3x4fv(LLViewerShaderMgr::AVATAR_MATRIX, + count, + false, + (GLfloat*)&(mpc.mGLMp[0])); + + return true; +} diff --git a/indra/newview/lldrawpoolalpha.h b/indra/newview/lldrawpoolalpha.h index 1b687f5ee2..0abe001714 100644 --- a/indra/newview/lldrawpoolalpha.h +++ b/indra/newview/lldrawpoolalpha.h @@ -1,101 +1,101 @@ -/** - * @file lldrawpoolalpha.h - * @brief LLDrawPoolAlpha class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLDRAWPOOLALPHA_H -#define LL_LLDRAWPOOLALPHA_H - -#include "lldrawpool.h" -#include "llrender.h" -#include "llframetimer.h" - -class LLFace; -class LLColor4; -class LLGLSLShader; - -class LLDrawPoolAlpha final: public LLRenderPass -{ -public: - - // set by llsettingsvo so lldrawpoolalpha has quick access to the water plane in eye space - static LLVector4 sWaterPlane; - - enum - { - VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX | - LLVertexBuffer::MAP_NORMAL | - LLVertexBuffer::MAP_COLOR | - LLVertexBuffer::MAP_TEXCOORD0 - }; - virtual U32 getVertexDataMask() { return VERTEX_DATA_MASK; } - - LLDrawPoolAlpha(U32 type); - /*virtual*/ ~LLDrawPoolAlpha(); - - /*virtual*/ S32 getNumPostDeferredPasses(); - /*virtual*/ void renderPostDeferred(S32 pass); - /*virtual*/ S32 getNumPasses() { return 1; } - - void forwardRender(bool write_depth = false); - /*virtual*/ void prerender(); - - void renderDebugAlpha(); - - void renderGroupAlpha(LLSpatialGroup* group, U32 type, U32 mask, bool texture = true); - void renderAlpha(U32 mask, bool depth_only = false, bool rigged = false); - void renderAlphaHighlight(); - bool uploadMatrixPalette(const LLDrawInfo& params); - - static bool sShowDebugAlpha; - -private: - LLGLSLShader* target_shader; - - // setup by beginFooPass, [0] is static variant, [1] is rigged variant - LLGLSLShader* simple_shader = nullptr; - LLGLSLShader* fullbright_shader = nullptr; - LLGLSLShader* emissive_shader = nullptr; - LLGLSLShader* pbr_emissive_shader = nullptr; - LLGLSLShader* pbr_shader = nullptr; - - void drawEmissive(LLDrawInfo* draw); - void renderEmissives(std::vector& emissives); - void renderRiggedEmissives(std::vector& emissives); - void renderPbrEmissives(std::vector& emissives); - void renderRiggedPbrEmissives(std::vector& emissives); - bool TexSetup(LLDrawInfo* draw, bool use_material); - void RestoreTexSetup(bool tex_setup); - - // our 'normal' alpha blend function for this pass - LLRender::eBlendFactor mColorSFactor; - LLRender::eBlendFactor mColorDFactor; - LLRender::eBlendFactor mAlphaSFactor; - LLRender::eBlendFactor mAlphaDFactor; - - // if true, we're executing a rigged render pass - bool mRigged = false; -}; - -#endif // LL_LLDRAWPOOLALPHA_H +/** + * @file lldrawpoolalpha.h + * @brief LLDrawPoolAlpha class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLDRAWPOOLALPHA_H +#define LL_LLDRAWPOOLALPHA_H + +#include "lldrawpool.h" +#include "llrender.h" +#include "llframetimer.h" + +class LLFace; +class LLColor4; +class LLGLSLShader; + +class LLDrawPoolAlpha final: public LLRenderPass +{ +public: + + // set by llsettingsvo so lldrawpoolalpha has quick access to the water plane in eye space + static LLVector4 sWaterPlane; + + enum + { + VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX | + LLVertexBuffer::MAP_NORMAL | + LLVertexBuffer::MAP_COLOR | + LLVertexBuffer::MAP_TEXCOORD0 + }; + virtual U32 getVertexDataMask() { return VERTEX_DATA_MASK; } + + LLDrawPoolAlpha(U32 type); + /*virtual*/ ~LLDrawPoolAlpha(); + + /*virtual*/ S32 getNumPostDeferredPasses(); + /*virtual*/ void renderPostDeferred(S32 pass); + /*virtual*/ S32 getNumPasses() { return 1; } + + void forwardRender(bool write_depth = false); + /*virtual*/ void prerender(); + + void renderDebugAlpha(); + + void renderGroupAlpha(LLSpatialGroup* group, U32 type, U32 mask, bool texture = true); + void renderAlpha(U32 mask, bool depth_only = false, bool rigged = false); + void renderAlphaHighlight(); + bool uploadMatrixPalette(const LLDrawInfo& params); + + static bool sShowDebugAlpha; + +private: + LLGLSLShader* target_shader; + + // setup by beginFooPass, [0] is static variant, [1] is rigged variant + LLGLSLShader* simple_shader = nullptr; + LLGLSLShader* fullbright_shader = nullptr; + LLGLSLShader* emissive_shader = nullptr; + LLGLSLShader* pbr_emissive_shader = nullptr; + LLGLSLShader* pbr_shader = nullptr; + + void drawEmissive(LLDrawInfo* draw); + void renderEmissives(std::vector& emissives); + void renderRiggedEmissives(std::vector& emissives); + void renderPbrEmissives(std::vector& emissives); + void renderRiggedPbrEmissives(std::vector& emissives); + bool TexSetup(LLDrawInfo* draw, bool use_material); + void RestoreTexSetup(bool tex_setup); + + // our 'normal' alpha blend function for this pass + LLRender::eBlendFactor mColorSFactor; + LLRender::eBlendFactor mColorDFactor; + LLRender::eBlendFactor mAlphaSFactor; + LLRender::eBlendFactor mAlphaDFactor; + + // if true, we're executing a rigged render pass + bool mRigged = false; +}; + +#endif // LL_LLDRAWPOOLALPHA_H diff --git a/indra/newview/lldrawpoolavatar.cpp b/indra/newview/lldrawpoolavatar.cpp index 94d59dfdb3..25bce7bced 100644 --- a/indra/newview/lldrawpoolavatar.cpp +++ b/indra/newview/lldrawpoolavatar.cpp @@ -1,862 +1,862 @@ -/** - * @file lldrawpoolavatar.cpp - * @brief LLDrawPoolAvatar class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lldrawpoolavatar.h" -#include "llskinningutil.h" -#include "llrender.h" - -#include "llvoavatar.h" -#include "m3math.h" -#include "llmatrix4a.h" - -#include "llagent.h" //for gAgent.needsRenderAvatar() -#include "lldrawable.h" -#include "lldrawpoolbump.h" -#include "llface.h" -#include "llmeshrepository.h" -#include "llsky.h" -#include "llviewercamera.h" -#include "llviewerregion.h" -#include "noise.h" -#include "pipeline.h" -#include "llviewershadermgr.h" -#include "llvovolume.h" -#include "llvolume.h" -#include "llappviewer.h" -#include "llrendersphere.h" -#include "llviewerpartsim.h" -#include "llviewercontrol.h" // for gSavedSettings -#include "llviewertexturelist.h" - -static U32 sShaderLevel = 0; - -LLGLSLShader* LLDrawPoolAvatar::sVertexProgram = NULL; -bool LLDrawPoolAvatar::sSkipOpaque = false; -bool LLDrawPoolAvatar::sSkipTransparent = false; -S32 LLDrawPoolAvatar::sShadowPass = -1; -S32 LLDrawPoolAvatar::sDiffuseChannel = 0; -F32 LLDrawPoolAvatar::sMinimumAlpha = 0.2f; - -LLUUID gBlackSquareID; - -static bool is_deferred_render = false; -static bool is_post_deferred_render = false; - -extern bool gUseGLPick; - -F32 CLOTHING_GRAVITY_EFFECT = 0.7f; -F32 CLOTHING_ACCEL_FORCE_FACTOR = 0.2f; - -// Format for gAGPVertices -// vertex format for bumpmapping: -// vertices 12 -// pad 4 -// normals 12 -// pad 4 -// texcoords0 8 -// texcoords1 8 -// total 48 -// -// for no bumpmapping -// vertices 12 -// texcoords 8 -// normals 12 -// total 32 -// - -S32 AVATAR_OFFSET_POS = 0; -S32 AVATAR_OFFSET_NORMAL = 16; -S32 AVATAR_OFFSET_TEX0 = 32; -S32 AVATAR_OFFSET_TEX1 = 40; -S32 AVATAR_VERTEX_BYTES = 48; - -bool gAvatarEmbossBumpMap = false; -static bool sRenderingSkinned = false; -S32 normal_channel = -1; -S32 specular_channel = -1; -S32 cube_channel = -1; - -LLDrawPoolAvatar::LLDrawPoolAvatar(U32 type) : - LLFacePool(type) -{ -} - -LLDrawPoolAvatar::~LLDrawPoolAvatar() -{ - if (!isDead()) - { - LL_WARNS() << "Destroying avatar drawpool that still contains faces" << LL_ENDL; - } -} - -// virtual -bool LLDrawPoolAvatar::isDead() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR - - if (!LLFacePool::isDead()) - { - return false; - } - - return true; -} - -S32 LLDrawPoolAvatar::getShaderLevel() const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR - - return (S32) LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_AVATAR); -} - -void LLDrawPoolAvatar::prerender() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR - - mShaderLevel = LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_AVATAR); - - sShaderLevel = mShaderLevel; -} - -LLMatrix4& LLDrawPoolAvatar::getModelView() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR - - static LLMatrix4 ret; - - ret.initRows(LLVector4(gGLModelView+0), - LLVector4(gGLModelView+4), - LLVector4(gGLModelView+8), - LLVector4(gGLModelView+12)); - - return ret; -} - -//----------------------------------------------------------------------------- -// render() -//----------------------------------------------------------------------------- - - - -void LLDrawPoolAvatar::beginDeferredPass(S32 pass) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - - sSkipTransparent = true; - is_deferred_render = true; - - if (LLPipeline::sImpostorRender) - { //impostor pass does not have impostor rendering - ++pass; - } - - switch (pass) - { - case 0: - beginDeferredImpostor(); - break; - case 1: - beginDeferredRigid(); - break; - case 2: - beginDeferredSkinned(); - break; - } -} - -void LLDrawPoolAvatar::endDeferredPass(S32 pass) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - - sSkipTransparent = false; - is_deferred_render = false; - - if (LLPipeline::sImpostorRender) - { - ++pass; - } - - switch (pass) - { - case 0: - endDeferredImpostor(); - break; - case 1: - endDeferredRigid(); - break; - case 2: - endDeferredSkinned(); - break; - } -} - -void LLDrawPoolAvatar::renderDeferred(S32 pass) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR - - render(pass); -} - -S32 LLDrawPoolAvatar::getNumPostDeferredPasses() -{ - return 1; -} - -void LLDrawPoolAvatar::beginPostDeferredPass(S32 pass) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR - - sSkipOpaque = true; - sShaderLevel = mShaderLevel; - sVertexProgram = &gDeferredAvatarAlphaProgram; - sRenderingSkinned = true; - - gPipeline.bindDeferredShader(*sVertexProgram); - - sVertexProgram->setMinimumAlpha(LLDrawPoolAvatar::sMinimumAlpha); - - sDiffuseChannel = sVertexProgram->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); -} - -void LLDrawPoolAvatar::endPostDeferredPass(S32 pass) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR - // if we're in software-blending, remember to set the fence _after_ we draw so we wait till this rendering is done - sRenderingSkinned = false; - sSkipOpaque = false; - - gPipeline.unbindDeferredShader(*sVertexProgram); - sDiffuseChannel = 0; - sShaderLevel = mShaderLevel; -} - -void LLDrawPoolAvatar::renderPostDeferred(S32 pass) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR - - is_post_deferred_render = true; - if (LLPipeline::sImpostorRender) - { //HACK for impostors so actual pass ends up being proper pass - render(0); - } - else - { - render(2); - } - is_post_deferred_render = false; -} - - -S32 LLDrawPoolAvatar::getNumShadowPasses() -{ - // avatars opaque, avatar alpha, avatar alpha mask, alpha attachments, alpha mask attachments, opaque attachments... - return NUM_SHADOW_PASSES; -} - -void LLDrawPoolAvatar::beginShadowPass(S32 pass) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - - if (pass == SHADOW_PASS_AVATAR_OPAQUE) - { - sVertexProgram = &gDeferredAvatarShadowProgram; - - if ((sShaderLevel > 0)) // for hardware blending - { - sRenderingSkinned = true; - sVertexProgram->bind(); - } - - gGL.diffuseColor4f(1, 1, 1, 1); - } - else if (pass == SHADOW_PASS_AVATAR_ALPHA_BLEND) - { - sVertexProgram = &gDeferredAvatarAlphaShadowProgram; - - // bind diffuse tex so we can reference the alpha channel... - S32 loc = sVertexProgram->getUniformLocation(LLViewerShaderMgr::DIFFUSE_MAP); - sDiffuseChannel = 0; - if (loc != -1) - { - sDiffuseChannel = sVertexProgram->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); - } - - if ((sShaderLevel > 0)) // for hardware blending - { - sRenderingSkinned = true; - sVertexProgram->bind(); - } - - gGL.diffuseColor4f(1, 1, 1, 1); - } - else if (pass == SHADOW_PASS_AVATAR_ALPHA_MASK) - { - sVertexProgram = &gDeferredAvatarAlphaMaskShadowProgram; - - // bind diffuse tex so we can reference the alpha channel... - S32 loc = sVertexProgram->getUniformLocation(LLViewerShaderMgr::DIFFUSE_MAP); - sDiffuseChannel = 0; - if (loc != -1) - { - sDiffuseChannel = sVertexProgram->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); - } - - if ((sShaderLevel > 0)) // for hardware blending - { - sRenderingSkinned = true; - sVertexProgram->bind(); - } - - gGL.diffuseColor4f(1, 1, 1, 1); - } -} - -void LLDrawPoolAvatar::endShadowPass(S32 pass) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - - if (sShaderLevel > 0) - { - sVertexProgram->unbind(); - } - sVertexProgram = NULL; - sRenderingSkinned = false; - LLDrawPoolAvatar::sShadowPass = -1; -} - -void LLDrawPoolAvatar::renderShadow(S32 pass) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - - if (mDrawFace.empty()) - { - return; - } - - const LLFace *facep = mDrawFace[0]; - if (!facep->getDrawable()) - { - return; - } - LLVOAvatar *avatarp = (LLVOAvatar *)facep->getDrawable()->getVObj().get(); - - if (avatarp->isDead() || avatarp->isUIAvatar() || avatarp->mDrawable.isNull()) - { - return; - } - - static LLCachedControl friends_only(gSavedSettings, "RenderAvatarFriendsOnly", false); - if (friends_only() - && !avatarp->isControlAvatar() - && !avatarp->isSelf() - && !avatarp->isBuddy()) - { - return; - } - - LLVOAvatar::AvatarOverallAppearance oa = avatarp->getOverallAppearance(); - bool impostor = !LLPipeline::sImpostorRender && avatarp->isImpostor(); - // no shadows if the shadows are causing this avatar to breach the limit. - if (avatarp->isTooSlow() || impostor || (oa == LLVOAvatar::AOA_INVISIBLE)) - { - // No shadows for impostored (including jellydolled) or invisible avs. - return; - } - - LLDrawPoolAvatar::sShadowPass = pass; - - if (pass == SHADOW_PASS_AVATAR_OPAQUE) - { - LLDrawPoolAvatar::sSkipTransparent = true; - avatarp->renderSkinned(); - LLDrawPoolAvatar::sSkipTransparent = false; - } - else if (pass == SHADOW_PASS_AVATAR_ALPHA_BLEND) - { - LLDrawPoolAvatar::sSkipOpaque = true; - avatarp->renderSkinned(); - LLDrawPoolAvatar::sSkipOpaque = false; - } - else if (pass == SHADOW_PASS_AVATAR_ALPHA_MASK) - { - LLDrawPoolAvatar::sSkipOpaque = true; - avatarp->renderSkinned(); - LLDrawPoolAvatar::sSkipOpaque = false; - } -} - -S32 LLDrawPoolAvatar::getNumPasses() -{ - return 3; -} - - -S32 LLDrawPoolAvatar::getNumDeferredPasses() -{ - return 3; -} - - -void LLDrawPoolAvatar::render(S32 pass) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - if (LLPipeline::sImpostorRender) - { - renderAvatars(NULL, ++pass); - return; - } - - renderAvatars(NULL, pass); // render all avatars -} - -void LLDrawPoolAvatar::beginRenderPass(S32 pass) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - //reset vertex buffer mappings - LLVertexBuffer::unbind(); - - if (LLPipeline::sImpostorRender) - { //impostor render does not have impostors or rigid rendering - ++pass; - } - - switch (pass) - { - case 0: - beginImpostor(); - break; - case 1: - beginRigid(); - break; - case 2: - beginSkinned(); - break; - } - - if (pass == 0) - { //make sure no stale colors are left over from a previous render - gGL.diffuseColor4f(1,1,1,1); - } -} - -void LLDrawPoolAvatar::endRenderPass(S32 pass) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - - if (LLPipeline::sImpostorRender) - { - ++pass; - } - - switch (pass) - { - case 0: - endImpostor(); - break; - case 1: - endRigid(); - break; - case 2: - endSkinned(); - break; - } -} - -void LLDrawPoolAvatar::beginImpostor() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR - - if (!LLPipeline::sReflectionRender) - { - LLVOAvatar::sRenderDistance = llclamp(LLVOAvatar::sRenderDistance, 16.f, 256.f); - LLVOAvatar::sNumVisibleAvatars = 0; - } - - gImpostorProgram.bind(); - gImpostorProgram.setMinimumAlpha(0.01f); - - gPipeline.enableLightsFullbright(); - sDiffuseChannel = 0; -} - -void LLDrawPoolAvatar::endImpostor() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR - - gImpostorProgram.unbind(); - gPipeline.enableLightsDynamic(); -} - -void LLDrawPoolAvatar::beginRigid() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR - - if (gPipeline.shadersLoaded()) - { - sVertexProgram = &gObjectAlphaMaskNoColorProgram; - - if (sVertexProgram != NULL) - { //eyeballs render with the specular shader - sVertexProgram->bind(); - sVertexProgram->setMinimumAlpha(LLDrawPoolAvatar::sMinimumAlpha); - } - } - else - { - sVertexProgram = NULL; - } -} - -void LLDrawPoolAvatar::endRigid() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR - - sShaderLevel = mShaderLevel; - if (sVertexProgram != NULL) - { - sVertexProgram->unbind(); - } -} - -void LLDrawPoolAvatar::beginDeferredImpostor() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR - - if (!LLPipeline::sReflectionRender) - { - LLVOAvatar::sRenderDistance = llclamp(LLVOAvatar::sRenderDistance, 16.f, 256.f); - LLVOAvatar::sNumVisibleAvatars = 0; - } - - sVertexProgram = &gDeferredImpostorProgram; - specular_channel = sVertexProgram->enableTexture(LLViewerShaderMgr::SPECULAR_MAP); - normal_channel = sVertexProgram->enableTexture(LLViewerShaderMgr::DEFERRED_NORMAL); - sDiffuseChannel = sVertexProgram->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); - sVertexProgram->bind(); - sVertexProgram->setMinimumAlpha(0.01f); -} - -void LLDrawPoolAvatar::endDeferredImpostor() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR - - sShaderLevel = mShaderLevel; - sVertexProgram->disableTexture(LLViewerShaderMgr::DEFERRED_NORMAL); - sVertexProgram->disableTexture(LLViewerShaderMgr::SPECULAR_MAP); - sVertexProgram->disableTexture(LLViewerShaderMgr::DIFFUSE_MAP); - gPipeline.unbindDeferredShader(*sVertexProgram); - sVertexProgram = NULL; - sDiffuseChannel = 0; -} - -void LLDrawPoolAvatar::beginDeferredRigid() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR - - sVertexProgram = &gDeferredNonIndexedDiffuseAlphaMaskNoColorProgram; - sDiffuseChannel = sVertexProgram->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); - sVertexProgram->bind(); - sVertexProgram->setMinimumAlpha(LLDrawPoolAvatar::sMinimumAlpha); -} - -void LLDrawPoolAvatar::endDeferredRigid() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR - - sShaderLevel = mShaderLevel; - sVertexProgram->disableTexture(LLViewerShaderMgr::DIFFUSE_MAP); - sVertexProgram->unbind(); - gGL.getTexUnit(0)->activate(); -} - - -void LLDrawPoolAvatar::beginSkinned() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR - - // used for preview only - - sVertexProgram = &gAvatarProgram; - - sRenderingSkinned = true; - - sVertexProgram->bind(); - sVertexProgram->setMinimumAlpha(LLDrawPoolAvatar::sMinimumAlpha); -} - -void LLDrawPoolAvatar::endSkinned() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR - - // if we're in software-blending, remember to set the fence _after_ we draw so we wait till this rendering is done - if (sShaderLevel > 0) - { - sRenderingSkinned = false; - sVertexProgram->disableTexture(LLViewerShaderMgr::BUMP_MAP); - gGL.getTexUnit(0)->activate(); - sVertexProgram->unbind(); - sShaderLevel = mShaderLevel; - } - else - { - if(gPipeline.shadersLoaded()) - { - // software skinning, use a basic shader for windlight. - // TODO: find a better fallback method for software skinning. - sVertexProgram->unbind(); - } - } - - gGL.getTexUnit(0)->activate(); -} - -void LLDrawPoolAvatar::beginDeferredSkinned() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR - - sShaderLevel = mShaderLevel; - sVertexProgram = &gDeferredAvatarProgram; - sRenderingSkinned = true; - - sVertexProgram->bind(); - sVertexProgram->setMinimumAlpha(LLDrawPoolAvatar::sMinimumAlpha); - sDiffuseChannel = sVertexProgram->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); - gGL.getTexUnit(0)->activate(); -} - -void LLDrawPoolAvatar::endDeferredSkinned() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR - - // if we're in software-blending, remember to set the fence _after_ we draw so we wait till this rendering is done - sRenderingSkinned = false; - sVertexProgram->unbind(); - - sVertexProgram->disableTexture(LLViewerShaderMgr::DIFFUSE_MAP); - - sShaderLevel = mShaderLevel; - - gGL.getTexUnit(0)->activate(); -} - -void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; //LL_RECORD_BLOCK_TIME(FTM_RENDER_CHARACTERS); - - if (pass == -1) - { - for (S32 i = 1; i < getNumPasses(); i++) - { //skip impostor pass - prerender(); - beginRenderPass(i); - renderAvatars(single_avatar, i); - endRenderPass(i); - } - - return; - } - - if (mDrawFace.empty() && !single_avatar) - { - return; - } - - LLVOAvatar *avatarp = NULL; - - if (single_avatar) - { - avatarp = single_avatar; - } - else - { - const LLFace *facep = mDrawFace[0]; - if (!facep->getDrawable()) - { - return; - } - avatarp = (LLVOAvatar *)facep->getDrawable()->getVObj().get(); - } - - if (avatarp->isDead() || avatarp->mDrawable.isNull()) - { - return; - } - - if (!single_avatar && !avatarp->isFullyLoaded() ) - { - if (pass==0 && (!gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_PARTICLES) || LLViewerPartSim::getMaxPartCount() <= 0)) - { - // debug code to draw a sphere in place of avatar - gGL.getTexUnit(0)->bind(LLViewerFetchedTexture::sWhiteImagep); - gGL.setColorMask(true, true); - LLVector3 pos = avatarp->getPositionAgent(); - gGL.color4f(1.0f, 1.0f, 1.0f, 0.7f); - - gGL.pushMatrix(); - gGL.translatef((F32)(pos.mV[VX]), - (F32)(pos.mV[VY]), - (F32)(pos.mV[VZ])); - gGL.scalef(0.15f, 0.15f, 0.3f); - - gSphere.renderGGL(); - - gGL.popMatrix(); - gGL.setColorMask(true, false); - } - // don't render please - return; - } - - static LLCachedControl friends_only(gSavedSettings, "RenderAvatarFriendsOnly", false); - if (!single_avatar - && friends_only() - && !avatarp->isUIAvatar() - && !avatarp->isControlAvatar() - && !avatarp->isSelf() - && !avatarp->isBuddy()) - { - return; - } - - bool impostor = !LLPipeline::sImpostorRender && avatarp->isImpostor() && !single_avatar; - - if (( avatarp->isInMuteList() - || impostor - || (LLVOAvatar::AOA_NORMAL != avatarp->getOverallAppearance() && !avatarp->needsImpostorUpdate()) ) && pass != 0) -// || (LLVOAvatar::AV_DO_NOT_RENDER == avatarp->getVisualMuteSettings() && !avatarp->needsImpostorUpdate()) ) && pass != 0) - { //don't draw anything but the impostor for impostored avatars - return; - } - - if (pass == 0 && !impostor && LLPipeline::sUnderWaterRender) - { //don't draw foot shadows under water - return; - } - - LLVOAvatar *attached_av = avatarp->getAttachedAvatar(); - if (attached_av && (LLVOAvatar::AOA_NORMAL != attached_av->getOverallAppearance() || !gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_AVATAR))) - { - // Animesh attachment of a jellydolled or invisible parent - don't show - return; - } - - if (pass == 0) - { - if (!LLPipeline::sReflectionRender) - { - LLVOAvatar::sNumVisibleAvatars++; - } - -// if (impostor || (LLVOAvatar::AV_DO_NOT_RENDER == avatarp->getVisualMuteSettings() && !avatarp->needsImpostorUpdate())) - if (impostor || (LLVOAvatar::AOA_NORMAL != avatarp->getOverallAppearance() && !avatarp->needsImpostorUpdate())) - { - if (LLPipeline::sRenderDeferred && !LLPipeline::sReflectionRender && avatarp->mImpostor.isComplete()) - { - if (normal_channel > -1) - { - avatarp->mImpostor.bindTexture(2, normal_channel); - } - if (specular_channel > -1) - { - avatarp->mImpostor.bindTexture(1, specular_channel); - } - } - avatarp->renderImpostor(avatarp->getMutedAVColor(), sDiffuseChannel); - } - return; - } - - if (pass == 1) - { - // render rigid meshes (eyeballs) first - avatarp->renderRigid(); - return; - } - - if ((sShaderLevel >= SHADER_LEVEL_CLOTH)) - { - LLMatrix4 rot_mat; - LLViewerCamera::getInstance()->getMatrixToLocal(rot_mat); - LLMatrix4 cfr(OGL_TO_CFR_ROTATION); - rot_mat *= cfr; - - LLVector4 wind; - wind.setVec(avatarp->mWindVec); - wind.mV[VW] = 0; - wind = wind * rot_mat; - wind.mV[VW] = avatarp->mWindVec.mV[VW]; - - sVertexProgram->uniform4fv(LLViewerShaderMgr::AVATAR_WIND, 1, wind.mV); - F32 phase = -1.f * (avatarp->mRipplePhase); - - F32 freq = 7.f + (noise1(avatarp->mRipplePhase) * 2.f); - LLVector4 sin_params(freq, freq, freq, phase); - sVertexProgram->uniform4fv(LLViewerShaderMgr::AVATAR_SINWAVE, 1, sin_params.mV); - - LLVector4 gravity(0.f, 0.f, -CLOTHING_GRAVITY_EFFECT, 0.f); - gravity = gravity * rot_mat; - sVertexProgram->uniform4fv(LLViewerShaderMgr::AVATAR_GRAVITY, 1, gravity.mV); - } - - if( !single_avatar || (avatarp == single_avatar) ) - { - avatarp->renderSkinned(); - } -} - -static LLTrace::BlockTimerStatHandle FTM_RIGGED_VBO("Rigged VBO"); - -//----------------------------------------------------------------------------- -// getDebugTexture() -//----------------------------------------------------------------------------- -LLViewerTexture *LLDrawPoolAvatar::getDebugTexture() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR - - if (mReferences.empty()) - { - return NULL; - } - LLFace *face = mReferences[0]; - if (!face->getDrawable()) - { - return NULL; - } - const LLViewerObject *objectp = face->getDrawable()->getVObj(); - - // Avatar should always have at least 1 (maybe 3?) TE's. - return objectp->getTEImage(0); -} - - -LLColor3 LLDrawPoolAvatar::getDebugColor() const -{ - return LLColor3(0.f, 1.f, 0.f); -} - - +/** + * @file lldrawpoolavatar.cpp + * @brief LLDrawPoolAvatar class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lldrawpoolavatar.h" +#include "llskinningutil.h" +#include "llrender.h" + +#include "llvoavatar.h" +#include "m3math.h" +#include "llmatrix4a.h" + +#include "llagent.h" //for gAgent.needsRenderAvatar() +#include "lldrawable.h" +#include "lldrawpoolbump.h" +#include "llface.h" +#include "llmeshrepository.h" +#include "llsky.h" +#include "llviewercamera.h" +#include "llviewerregion.h" +#include "noise.h" +#include "pipeline.h" +#include "llviewershadermgr.h" +#include "llvovolume.h" +#include "llvolume.h" +#include "llappviewer.h" +#include "llrendersphere.h" +#include "llviewerpartsim.h" +#include "llviewercontrol.h" // for gSavedSettings +#include "llviewertexturelist.h" + +static U32 sShaderLevel = 0; + +LLGLSLShader* LLDrawPoolAvatar::sVertexProgram = NULL; +bool LLDrawPoolAvatar::sSkipOpaque = false; +bool LLDrawPoolAvatar::sSkipTransparent = false; +S32 LLDrawPoolAvatar::sShadowPass = -1; +S32 LLDrawPoolAvatar::sDiffuseChannel = 0; +F32 LLDrawPoolAvatar::sMinimumAlpha = 0.2f; + +LLUUID gBlackSquareID; + +static bool is_deferred_render = false; +static bool is_post_deferred_render = false; + +extern bool gUseGLPick; + +F32 CLOTHING_GRAVITY_EFFECT = 0.7f; +F32 CLOTHING_ACCEL_FORCE_FACTOR = 0.2f; + +// Format for gAGPVertices +// vertex format for bumpmapping: +// vertices 12 +// pad 4 +// normals 12 +// pad 4 +// texcoords0 8 +// texcoords1 8 +// total 48 +// +// for no bumpmapping +// vertices 12 +// texcoords 8 +// normals 12 +// total 32 +// + +S32 AVATAR_OFFSET_POS = 0; +S32 AVATAR_OFFSET_NORMAL = 16; +S32 AVATAR_OFFSET_TEX0 = 32; +S32 AVATAR_OFFSET_TEX1 = 40; +S32 AVATAR_VERTEX_BYTES = 48; + +bool gAvatarEmbossBumpMap = false; +static bool sRenderingSkinned = false; +S32 normal_channel = -1; +S32 specular_channel = -1; +S32 cube_channel = -1; + +LLDrawPoolAvatar::LLDrawPoolAvatar(U32 type) : + LLFacePool(type) +{ +} + +LLDrawPoolAvatar::~LLDrawPoolAvatar() +{ + if (!isDead()) + { + LL_WARNS() << "Destroying avatar drawpool that still contains faces" << LL_ENDL; + } +} + +// virtual +bool LLDrawPoolAvatar::isDead() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR + + if (!LLFacePool::isDead()) + { + return false; + } + + return true; +} + +S32 LLDrawPoolAvatar::getShaderLevel() const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR + + return (S32) LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_AVATAR); +} + +void LLDrawPoolAvatar::prerender() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR + + mShaderLevel = LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_AVATAR); + + sShaderLevel = mShaderLevel; +} + +LLMatrix4& LLDrawPoolAvatar::getModelView() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR + + static LLMatrix4 ret; + + ret.initRows(LLVector4(gGLModelView+0), + LLVector4(gGLModelView+4), + LLVector4(gGLModelView+8), + LLVector4(gGLModelView+12)); + + return ret; +} + +//----------------------------------------------------------------------------- +// render() +//----------------------------------------------------------------------------- + + + +void LLDrawPoolAvatar::beginDeferredPass(S32 pass) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + + sSkipTransparent = true; + is_deferred_render = true; + + if (LLPipeline::sImpostorRender) + { //impostor pass does not have impostor rendering + ++pass; + } + + switch (pass) + { + case 0: + beginDeferredImpostor(); + break; + case 1: + beginDeferredRigid(); + break; + case 2: + beginDeferredSkinned(); + break; + } +} + +void LLDrawPoolAvatar::endDeferredPass(S32 pass) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + + sSkipTransparent = false; + is_deferred_render = false; + + if (LLPipeline::sImpostorRender) + { + ++pass; + } + + switch (pass) + { + case 0: + endDeferredImpostor(); + break; + case 1: + endDeferredRigid(); + break; + case 2: + endDeferredSkinned(); + break; + } +} + +void LLDrawPoolAvatar::renderDeferred(S32 pass) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR + + render(pass); +} + +S32 LLDrawPoolAvatar::getNumPostDeferredPasses() +{ + return 1; +} + +void LLDrawPoolAvatar::beginPostDeferredPass(S32 pass) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR + + sSkipOpaque = true; + sShaderLevel = mShaderLevel; + sVertexProgram = &gDeferredAvatarAlphaProgram; + sRenderingSkinned = true; + + gPipeline.bindDeferredShader(*sVertexProgram); + + sVertexProgram->setMinimumAlpha(LLDrawPoolAvatar::sMinimumAlpha); + + sDiffuseChannel = sVertexProgram->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); +} + +void LLDrawPoolAvatar::endPostDeferredPass(S32 pass) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR + // if we're in software-blending, remember to set the fence _after_ we draw so we wait till this rendering is done + sRenderingSkinned = false; + sSkipOpaque = false; + + gPipeline.unbindDeferredShader(*sVertexProgram); + sDiffuseChannel = 0; + sShaderLevel = mShaderLevel; +} + +void LLDrawPoolAvatar::renderPostDeferred(S32 pass) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR + + is_post_deferred_render = true; + if (LLPipeline::sImpostorRender) + { //HACK for impostors so actual pass ends up being proper pass + render(0); + } + else + { + render(2); + } + is_post_deferred_render = false; +} + + +S32 LLDrawPoolAvatar::getNumShadowPasses() +{ + // avatars opaque, avatar alpha, avatar alpha mask, alpha attachments, alpha mask attachments, opaque attachments... + return NUM_SHADOW_PASSES; +} + +void LLDrawPoolAvatar::beginShadowPass(S32 pass) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + + if (pass == SHADOW_PASS_AVATAR_OPAQUE) + { + sVertexProgram = &gDeferredAvatarShadowProgram; + + if ((sShaderLevel > 0)) // for hardware blending + { + sRenderingSkinned = true; + sVertexProgram->bind(); + } + + gGL.diffuseColor4f(1, 1, 1, 1); + } + else if (pass == SHADOW_PASS_AVATAR_ALPHA_BLEND) + { + sVertexProgram = &gDeferredAvatarAlphaShadowProgram; + + // bind diffuse tex so we can reference the alpha channel... + S32 loc = sVertexProgram->getUniformLocation(LLViewerShaderMgr::DIFFUSE_MAP); + sDiffuseChannel = 0; + if (loc != -1) + { + sDiffuseChannel = sVertexProgram->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); + } + + if ((sShaderLevel > 0)) // for hardware blending + { + sRenderingSkinned = true; + sVertexProgram->bind(); + } + + gGL.diffuseColor4f(1, 1, 1, 1); + } + else if (pass == SHADOW_PASS_AVATAR_ALPHA_MASK) + { + sVertexProgram = &gDeferredAvatarAlphaMaskShadowProgram; + + // bind diffuse tex so we can reference the alpha channel... + S32 loc = sVertexProgram->getUniformLocation(LLViewerShaderMgr::DIFFUSE_MAP); + sDiffuseChannel = 0; + if (loc != -1) + { + sDiffuseChannel = sVertexProgram->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); + } + + if ((sShaderLevel > 0)) // for hardware blending + { + sRenderingSkinned = true; + sVertexProgram->bind(); + } + + gGL.diffuseColor4f(1, 1, 1, 1); + } +} + +void LLDrawPoolAvatar::endShadowPass(S32 pass) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + + if (sShaderLevel > 0) + { + sVertexProgram->unbind(); + } + sVertexProgram = NULL; + sRenderingSkinned = false; + LLDrawPoolAvatar::sShadowPass = -1; +} + +void LLDrawPoolAvatar::renderShadow(S32 pass) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + + if (mDrawFace.empty()) + { + return; + } + + const LLFace *facep = mDrawFace[0]; + if (!facep->getDrawable()) + { + return; + } + LLVOAvatar *avatarp = (LLVOAvatar *)facep->getDrawable()->getVObj().get(); + + if (avatarp->isDead() || avatarp->isUIAvatar() || avatarp->mDrawable.isNull()) + { + return; + } + + static LLCachedControl friends_only(gSavedSettings, "RenderAvatarFriendsOnly", false); + if (friends_only() + && !avatarp->isControlAvatar() + && !avatarp->isSelf() + && !avatarp->isBuddy()) + { + return; + } + + LLVOAvatar::AvatarOverallAppearance oa = avatarp->getOverallAppearance(); + bool impostor = !LLPipeline::sImpostorRender && avatarp->isImpostor(); + // no shadows if the shadows are causing this avatar to breach the limit. + if (avatarp->isTooSlow() || impostor || (oa == LLVOAvatar::AOA_INVISIBLE)) + { + // No shadows for impostored (including jellydolled) or invisible avs. + return; + } + + LLDrawPoolAvatar::sShadowPass = pass; + + if (pass == SHADOW_PASS_AVATAR_OPAQUE) + { + LLDrawPoolAvatar::sSkipTransparent = true; + avatarp->renderSkinned(); + LLDrawPoolAvatar::sSkipTransparent = false; + } + else if (pass == SHADOW_PASS_AVATAR_ALPHA_BLEND) + { + LLDrawPoolAvatar::sSkipOpaque = true; + avatarp->renderSkinned(); + LLDrawPoolAvatar::sSkipOpaque = false; + } + else if (pass == SHADOW_PASS_AVATAR_ALPHA_MASK) + { + LLDrawPoolAvatar::sSkipOpaque = true; + avatarp->renderSkinned(); + LLDrawPoolAvatar::sSkipOpaque = false; + } +} + +S32 LLDrawPoolAvatar::getNumPasses() +{ + return 3; +} + + +S32 LLDrawPoolAvatar::getNumDeferredPasses() +{ + return 3; +} + + +void LLDrawPoolAvatar::render(S32 pass) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + if (LLPipeline::sImpostorRender) + { + renderAvatars(NULL, ++pass); + return; + } + + renderAvatars(NULL, pass); // render all avatars +} + +void LLDrawPoolAvatar::beginRenderPass(S32 pass) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + //reset vertex buffer mappings + LLVertexBuffer::unbind(); + + if (LLPipeline::sImpostorRender) + { //impostor render does not have impostors or rigid rendering + ++pass; + } + + switch (pass) + { + case 0: + beginImpostor(); + break; + case 1: + beginRigid(); + break; + case 2: + beginSkinned(); + break; + } + + if (pass == 0) + { //make sure no stale colors are left over from a previous render + gGL.diffuseColor4f(1,1,1,1); + } +} + +void LLDrawPoolAvatar::endRenderPass(S32 pass) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + + if (LLPipeline::sImpostorRender) + { + ++pass; + } + + switch (pass) + { + case 0: + endImpostor(); + break; + case 1: + endRigid(); + break; + case 2: + endSkinned(); + break; + } +} + +void LLDrawPoolAvatar::beginImpostor() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR + + if (!LLPipeline::sReflectionRender) + { + LLVOAvatar::sRenderDistance = llclamp(LLVOAvatar::sRenderDistance, 16.f, 256.f); + LLVOAvatar::sNumVisibleAvatars = 0; + } + + gImpostorProgram.bind(); + gImpostorProgram.setMinimumAlpha(0.01f); + + gPipeline.enableLightsFullbright(); + sDiffuseChannel = 0; +} + +void LLDrawPoolAvatar::endImpostor() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR + + gImpostorProgram.unbind(); + gPipeline.enableLightsDynamic(); +} + +void LLDrawPoolAvatar::beginRigid() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR + + if (gPipeline.shadersLoaded()) + { + sVertexProgram = &gObjectAlphaMaskNoColorProgram; + + if (sVertexProgram != NULL) + { //eyeballs render with the specular shader + sVertexProgram->bind(); + sVertexProgram->setMinimumAlpha(LLDrawPoolAvatar::sMinimumAlpha); + } + } + else + { + sVertexProgram = NULL; + } +} + +void LLDrawPoolAvatar::endRigid() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR + + sShaderLevel = mShaderLevel; + if (sVertexProgram != NULL) + { + sVertexProgram->unbind(); + } +} + +void LLDrawPoolAvatar::beginDeferredImpostor() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR + + if (!LLPipeline::sReflectionRender) + { + LLVOAvatar::sRenderDistance = llclamp(LLVOAvatar::sRenderDistance, 16.f, 256.f); + LLVOAvatar::sNumVisibleAvatars = 0; + } + + sVertexProgram = &gDeferredImpostorProgram; + specular_channel = sVertexProgram->enableTexture(LLViewerShaderMgr::SPECULAR_MAP); + normal_channel = sVertexProgram->enableTexture(LLViewerShaderMgr::DEFERRED_NORMAL); + sDiffuseChannel = sVertexProgram->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); + sVertexProgram->bind(); + sVertexProgram->setMinimumAlpha(0.01f); +} + +void LLDrawPoolAvatar::endDeferredImpostor() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR + + sShaderLevel = mShaderLevel; + sVertexProgram->disableTexture(LLViewerShaderMgr::DEFERRED_NORMAL); + sVertexProgram->disableTexture(LLViewerShaderMgr::SPECULAR_MAP); + sVertexProgram->disableTexture(LLViewerShaderMgr::DIFFUSE_MAP); + gPipeline.unbindDeferredShader(*sVertexProgram); + sVertexProgram = NULL; + sDiffuseChannel = 0; +} + +void LLDrawPoolAvatar::beginDeferredRigid() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR + + sVertexProgram = &gDeferredNonIndexedDiffuseAlphaMaskNoColorProgram; + sDiffuseChannel = sVertexProgram->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); + sVertexProgram->bind(); + sVertexProgram->setMinimumAlpha(LLDrawPoolAvatar::sMinimumAlpha); +} + +void LLDrawPoolAvatar::endDeferredRigid() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR + + sShaderLevel = mShaderLevel; + sVertexProgram->disableTexture(LLViewerShaderMgr::DIFFUSE_MAP); + sVertexProgram->unbind(); + gGL.getTexUnit(0)->activate(); +} + + +void LLDrawPoolAvatar::beginSkinned() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR + + // used for preview only + + sVertexProgram = &gAvatarProgram; + + sRenderingSkinned = true; + + sVertexProgram->bind(); + sVertexProgram->setMinimumAlpha(LLDrawPoolAvatar::sMinimumAlpha); +} + +void LLDrawPoolAvatar::endSkinned() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR + + // if we're in software-blending, remember to set the fence _after_ we draw so we wait till this rendering is done + if (sShaderLevel > 0) + { + sRenderingSkinned = false; + sVertexProgram->disableTexture(LLViewerShaderMgr::BUMP_MAP); + gGL.getTexUnit(0)->activate(); + sVertexProgram->unbind(); + sShaderLevel = mShaderLevel; + } + else + { + if(gPipeline.shadersLoaded()) + { + // software skinning, use a basic shader for windlight. + // TODO: find a better fallback method for software skinning. + sVertexProgram->unbind(); + } + } + + gGL.getTexUnit(0)->activate(); +} + +void LLDrawPoolAvatar::beginDeferredSkinned() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR + + sShaderLevel = mShaderLevel; + sVertexProgram = &gDeferredAvatarProgram; + sRenderingSkinned = true; + + sVertexProgram->bind(); + sVertexProgram->setMinimumAlpha(LLDrawPoolAvatar::sMinimumAlpha); + sDiffuseChannel = sVertexProgram->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); + gGL.getTexUnit(0)->activate(); +} + +void LLDrawPoolAvatar::endDeferredSkinned() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR + + // if we're in software-blending, remember to set the fence _after_ we draw so we wait till this rendering is done + sRenderingSkinned = false; + sVertexProgram->unbind(); + + sVertexProgram->disableTexture(LLViewerShaderMgr::DIFFUSE_MAP); + + sShaderLevel = mShaderLevel; + + gGL.getTexUnit(0)->activate(); +} + +void LLDrawPoolAvatar::renderAvatars(LLVOAvatar* single_avatar, S32 pass) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; //LL_RECORD_BLOCK_TIME(FTM_RENDER_CHARACTERS); + + if (pass == -1) + { + for (S32 i = 1; i < getNumPasses(); i++) + { //skip impostor pass + prerender(); + beginRenderPass(i); + renderAvatars(single_avatar, i); + endRenderPass(i); + } + + return; + } + + if (mDrawFace.empty() && !single_avatar) + { + return; + } + + LLVOAvatar *avatarp = NULL; + + if (single_avatar) + { + avatarp = single_avatar; + } + else + { + const LLFace *facep = mDrawFace[0]; + if (!facep->getDrawable()) + { + return; + } + avatarp = (LLVOAvatar *)facep->getDrawable()->getVObj().get(); + } + + if (avatarp->isDead() || avatarp->mDrawable.isNull()) + { + return; + } + + if (!single_avatar && !avatarp->isFullyLoaded() ) + { + if (pass==0 && (!gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_PARTICLES) || LLViewerPartSim::getMaxPartCount() <= 0)) + { + // debug code to draw a sphere in place of avatar + gGL.getTexUnit(0)->bind(LLViewerFetchedTexture::sWhiteImagep); + gGL.setColorMask(true, true); + LLVector3 pos = avatarp->getPositionAgent(); + gGL.color4f(1.0f, 1.0f, 1.0f, 0.7f); + + gGL.pushMatrix(); + gGL.translatef((F32)(pos.mV[VX]), + (F32)(pos.mV[VY]), + (F32)(pos.mV[VZ])); + gGL.scalef(0.15f, 0.15f, 0.3f); + + gSphere.renderGGL(); + + gGL.popMatrix(); + gGL.setColorMask(true, false); + } + // don't render please + return; + } + + static LLCachedControl friends_only(gSavedSettings, "RenderAvatarFriendsOnly", false); + if (!single_avatar + && friends_only() + && !avatarp->isUIAvatar() + && !avatarp->isControlAvatar() + && !avatarp->isSelf() + && !avatarp->isBuddy()) + { + return; + } + + bool impostor = !LLPipeline::sImpostorRender && avatarp->isImpostor() && !single_avatar; + + if (( avatarp->isInMuteList() + || impostor + || (LLVOAvatar::AOA_NORMAL != avatarp->getOverallAppearance() && !avatarp->needsImpostorUpdate()) ) && pass != 0) +// || (LLVOAvatar::AV_DO_NOT_RENDER == avatarp->getVisualMuteSettings() && !avatarp->needsImpostorUpdate()) ) && pass != 0) + { //don't draw anything but the impostor for impostored avatars + return; + } + + if (pass == 0 && !impostor && LLPipeline::sUnderWaterRender) + { //don't draw foot shadows under water + return; + } + + LLVOAvatar *attached_av = avatarp->getAttachedAvatar(); + if (attached_av && (LLVOAvatar::AOA_NORMAL != attached_av->getOverallAppearance() || !gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_AVATAR))) + { + // Animesh attachment of a jellydolled or invisible parent - don't show + return; + } + + if (pass == 0) + { + if (!LLPipeline::sReflectionRender) + { + LLVOAvatar::sNumVisibleAvatars++; + } + +// if (impostor || (LLVOAvatar::AV_DO_NOT_RENDER == avatarp->getVisualMuteSettings() && !avatarp->needsImpostorUpdate())) + if (impostor || (LLVOAvatar::AOA_NORMAL != avatarp->getOverallAppearance() && !avatarp->needsImpostorUpdate())) + { + if (LLPipeline::sRenderDeferred && !LLPipeline::sReflectionRender && avatarp->mImpostor.isComplete()) + { + if (normal_channel > -1) + { + avatarp->mImpostor.bindTexture(2, normal_channel); + } + if (specular_channel > -1) + { + avatarp->mImpostor.bindTexture(1, specular_channel); + } + } + avatarp->renderImpostor(avatarp->getMutedAVColor(), sDiffuseChannel); + } + return; + } + + if (pass == 1) + { + // render rigid meshes (eyeballs) first + avatarp->renderRigid(); + return; + } + + if ((sShaderLevel >= SHADER_LEVEL_CLOTH)) + { + LLMatrix4 rot_mat; + LLViewerCamera::getInstance()->getMatrixToLocal(rot_mat); + LLMatrix4 cfr(OGL_TO_CFR_ROTATION); + rot_mat *= cfr; + + LLVector4 wind; + wind.setVec(avatarp->mWindVec); + wind.mV[VW] = 0; + wind = wind * rot_mat; + wind.mV[VW] = avatarp->mWindVec.mV[VW]; + + sVertexProgram->uniform4fv(LLViewerShaderMgr::AVATAR_WIND, 1, wind.mV); + F32 phase = -1.f * (avatarp->mRipplePhase); + + F32 freq = 7.f + (noise1(avatarp->mRipplePhase) * 2.f); + LLVector4 sin_params(freq, freq, freq, phase); + sVertexProgram->uniform4fv(LLViewerShaderMgr::AVATAR_SINWAVE, 1, sin_params.mV); + + LLVector4 gravity(0.f, 0.f, -CLOTHING_GRAVITY_EFFECT, 0.f); + gravity = gravity * rot_mat; + sVertexProgram->uniform4fv(LLViewerShaderMgr::AVATAR_GRAVITY, 1, gravity.mV); + } + + if( !single_avatar || (avatarp == single_avatar) ) + { + avatarp->renderSkinned(); + } +} + +static LLTrace::BlockTimerStatHandle FTM_RIGGED_VBO("Rigged VBO"); + +//----------------------------------------------------------------------------- +// getDebugTexture() +//----------------------------------------------------------------------------- +LLViewerTexture *LLDrawPoolAvatar::getDebugTexture() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR + + if (mReferences.empty()) + { + return NULL; + } + LLFace *face = mReferences[0]; + if (!face->getDrawable()) + { + return NULL; + } + const LLViewerObject *objectp = face->getDrawable()->getVObj(); + + // Avatar should always have at least 1 (maybe 3?) TE's. + return objectp->getTEImage(0); +} + + +LLColor3 LLDrawPoolAvatar::getDebugColor() const +{ + return LLColor3(0.f, 1.f, 0.f); +} + + diff --git a/indra/newview/lldrawpoolavatar.h b/indra/newview/lldrawpoolavatar.h index ea86f63e3e..1a53861a03 100644 --- a/indra/newview/lldrawpoolavatar.h +++ b/indra/newview/lldrawpoolavatar.h @@ -1,140 +1,140 @@ - /** - * @file lldrawpoolavatar.h - * @brief LLDrawPoolAvatar class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLDRAWPOOLAVATAR_H -#define LL_LLDRAWPOOLAVATAR_H - -#include "lldrawpool.h" -#include "llmodel.h" - -#include - -class LLVOAvatar; -class LLVOVolume; -class LLGLSLShader; -class LLFace; -class LLVolume; -class LLVolumeFace; - -extern U32 gFrameCount; - -class LLDrawPoolAvatar : public LLFacePool -{ -public: - enum - { - SHADER_LEVEL_BUMP = 2, - SHADER_LEVEL_CLOTH = 3 - }; - - enum - { - VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX | - LLVertexBuffer::MAP_NORMAL | - LLVertexBuffer::MAP_TEXCOORD0 | - LLVertexBuffer::MAP_WEIGHT | - LLVertexBuffer::MAP_CLOTHWEIGHT - }; - - ~LLDrawPoolAvatar(); - /*virtual*/ bool isDead(); - -typedef enum - { - SHADOW_PASS_AVATAR_OPAQUE, - SHADOW_PASS_AVATAR_ALPHA_BLEND, - SHADOW_PASS_AVATAR_ALPHA_MASK, - NUM_SHADOW_PASSES - } eShadowPass; - - virtual U32 getVertexDataMask() { return VERTEX_DATA_MASK; } - - virtual S32 getShaderLevel() const; - - LLDrawPoolAvatar(U32 type); - - static LLMatrix4& getModelView(); - - /*virtual*/ S32 getNumPasses(); - /*virtual*/ void beginRenderPass(S32 pass); - /*virtual*/ void endRenderPass(S32 pass); - /*virtual*/ void prerender(); - /*virtual*/ void render(S32 pass = 0); - - /*virtual*/ S32 getNumDeferredPasses(); - /*virtual*/ void beginDeferredPass(S32 pass); - /*virtual*/ void endDeferredPass(S32 pass); - /*virtual*/ void renderDeferred(S32 pass); - - /*virtual*/ S32 getNumPostDeferredPasses(); - /*virtual*/ void beginPostDeferredPass(S32 pass); - /*virtual*/ void endPostDeferredPass(S32 pass); - /*virtual*/ void renderPostDeferred(S32 pass); - - /*virtual*/ S32 getNumShadowPasses(); - /*virtual*/ void beginShadowPass(S32 pass); - /*virtual*/ void endShadowPass(S32 pass); - /*virtual*/ void renderShadow(S32 pass); - - void beginRigid(); - void beginImpostor(); - void beginSkinned(); - - void endRigid(); - void endImpostor(); - void endSkinned(); - - void beginDeferredRigid(); - void beginDeferredImpostor(); - void beginDeferredSkinned(); - - void endDeferredRigid(); - void endDeferredImpostor(); - void endDeferredSkinned(); - - /*virtual*/ LLViewerTexture *getDebugTexture(); - /*virtual*/ LLColor3 getDebugColor() const; // For AGP debug display - - void renderAvatars(LLVOAvatar *single_avatar, S32 pass = -1); // renders only one avatar if single_avatar is not null. - - static bool sSkipOpaque; - static bool sSkipTransparent; - static S32 sShadowPass; - static S32 sDiffuseChannel; - static F32 sMinimumAlpha; - - static LLGLSLShader* sVertexProgram; -}; - -extern S32 AVATAR_OFFSET_POS; -extern S32 AVATAR_OFFSET_NORMAL; -extern S32 AVATAR_OFFSET_TEX0; -extern S32 AVATAR_OFFSET_TEX1; -extern S32 AVATAR_VERTEX_BYTES; -const S32 AVATAR_BUFFER_ELEMENTS = 8192; // Needs to be enough to store all avatar vertices. - -extern bool gAvatarEmbossBumpMap; -#endif // LL_LLDRAWPOOLAVATAR_H + /** + * @file lldrawpoolavatar.h + * @brief LLDrawPoolAvatar class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLDRAWPOOLAVATAR_H +#define LL_LLDRAWPOOLAVATAR_H + +#include "lldrawpool.h" +#include "llmodel.h" + +#include + +class LLVOAvatar; +class LLVOVolume; +class LLGLSLShader; +class LLFace; +class LLVolume; +class LLVolumeFace; + +extern U32 gFrameCount; + +class LLDrawPoolAvatar : public LLFacePool +{ +public: + enum + { + SHADER_LEVEL_BUMP = 2, + SHADER_LEVEL_CLOTH = 3 + }; + + enum + { + VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX | + LLVertexBuffer::MAP_NORMAL | + LLVertexBuffer::MAP_TEXCOORD0 | + LLVertexBuffer::MAP_WEIGHT | + LLVertexBuffer::MAP_CLOTHWEIGHT + }; + + ~LLDrawPoolAvatar(); + /*virtual*/ bool isDead(); + +typedef enum + { + SHADOW_PASS_AVATAR_OPAQUE, + SHADOW_PASS_AVATAR_ALPHA_BLEND, + SHADOW_PASS_AVATAR_ALPHA_MASK, + NUM_SHADOW_PASSES + } eShadowPass; + + virtual U32 getVertexDataMask() { return VERTEX_DATA_MASK; } + + virtual S32 getShaderLevel() const; + + LLDrawPoolAvatar(U32 type); + + static LLMatrix4& getModelView(); + + /*virtual*/ S32 getNumPasses(); + /*virtual*/ void beginRenderPass(S32 pass); + /*virtual*/ void endRenderPass(S32 pass); + /*virtual*/ void prerender(); + /*virtual*/ void render(S32 pass = 0); + + /*virtual*/ S32 getNumDeferredPasses(); + /*virtual*/ void beginDeferredPass(S32 pass); + /*virtual*/ void endDeferredPass(S32 pass); + /*virtual*/ void renderDeferred(S32 pass); + + /*virtual*/ S32 getNumPostDeferredPasses(); + /*virtual*/ void beginPostDeferredPass(S32 pass); + /*virtual*/ void endPostDeferredPass(S32 pass); + /*virtual*/ void renderPostDeferred(S32 pass); + + /*virtual*/ S32 getNumShadowPasses(); + /*virtual*/ void beginShadowPass(S32 pass); + /*virtual*/ void endShadowPass(S32 pass); + /*virtual*/ void renderShadow(S32 pass); + + void beginRigid(); + void beginImpostor(); + void beginSkinned(); + + void endRigid(); + void endImpostor(); + void endSkinned(); + + void beginDeferredRigid(); + void beginDeferredImpostor(); + void beginDeferredSkinned(); + + void endDeferredRigid(); + void endDeferredImpostor(); + void endDeferredSkinned(); + + /*virtual*/ LLViewerTexture *getDebugTexture(); + /*virtual*/ LLColor3 getDebugColor() const; // For AGP debug display + + void renderAvatars(LLVOAvatar *single_avatar, S32 pass = -1); // renders only one avatar if single_avatar is not null. + + static bool sSkipOpaque; + static bool sSkipTransparent; + static S32 sShadowPass; + static S32 sDiffuseChannel; + static F32 sMinimumAlpha; + + static LLGLSLShader* sVertexProgram; +}; + +extern S32 AVATAR_OFFSET_POS; +extern S32 AVATAR_OFFSET_NORMAL; +extern S32 AVATAR_OFFSET_TEX0; +extern S32 AVATAR_OFFSET_TEX1; +extern S32 AVATAR_VERTEX_BYTES; +const S32 AVATAR_BUFFER_ELEMENTS = 8192; // Needs to be enough to store all avatar vertices. + +extern bool gAvatarEmbossBumpMap; +#endif // LL_LLDRAWPOOLAVATAR_H diff --git a/indra/newview/lldrawpoolbump.cpp b/indra/newview/lldrawpoolbump.cpp index 28a1077177..e26d98789e 100644 --- a/indra/newview/lldrawpoolbump.cpp +++ b/indra/newview/lldrawpoolbump.cpp @@ -1,1304 +1,1304 @@ -/** - * @file lldrawpoolbump.cpp - * @brief LLDrawPoolBump class implementation - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lldrawpoolbump.h" - -#include "llstl.h" -#include "llviewercontrol.h" -#include "lldir.h" -#include "m3math.h" -#include "m4math.h" -#include "v4math.h" -#include "llglheaders.h" -#include "llrender.h" - -#include "llcubemap.h" -#include "lldrawable.h" -#include "llface.h" -#include "llsky.h" -#include "llstartup.h" -#include "lltextureentry.h" -#include "llviewercamera.h" -#include "llviewertexturelist.h" -#include "pipeline.h" -#include "llspatialpartition.h" -#include "llviewershadermgr.h" -#include "llmodel.h" - -//#include "llimagebmp.h" -//#include "../tools/imdebug/imdebug.h" - -// static -LLStandardBumpmap gStandardBumpmapList[TEM_BUMPMAP_COUNT]; -LL::WorkQueue::weak_t LLBumpImageList::sMainQueue; -LL::WorkQueue::weak_t LLBumpImageList::sTexUpdateQueue; -LLRenderTarget LLBumpImageList::sRenderTarget; - -// static -U32 LLStandardBumpmap::sStandardBumpmapCount = 0; - -// static -LLBumpImageList gBumpImageList; - -const S32 STD_BUMP_LATEST_FILE_VERSION = 1; - -const U32 VERTEX_MASK_SHINY = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_COLOR; -const U32 VERTEX_MASK_BUMP = LLVertexBuffer::MAP_VERTEX |LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_TEXCOORD1; - -U32 LLDrawPoolBump::sVertexMask = VERTEX_MASK_SHINY; - - -static LLGLSLShader* shader = NULL; -static S32 cube_channel = -1; -static S32 diffuse_channel = -1; -static S32 bump_channel = -1; -static bool shiny = false; - -// Enabled after changing LLViewerTexture::mNeedsCreateTexture to an -// LLAtomicBool; this should work just fine, now. HB -#define LL_BUMPLIST_MULTITHREADED 1 - - -// static -void LLStandardBumpmap::shutdown() -{ - LLStandardBumpmap::destroyGL(); -} - -// static -void LLStandardBumpmap::restoreGL() -{ - addstandard(); -} - -// static -void LLStandardBumpmap::addstandard() -{ - if(!gTextureList.isInitialized()) - { - //Note: loading pre-configuration sometimes triggers this call. - //But it is safe to return here because bump images will be reloaded during initialization later. - return ; - } - - if (LLStartUp::getStartupState() < STATE_SEED_CAP_GRANTED) - { - // Not ready, need caps for images - return; - } - - // can't assert; we destroyGL and restoreGL a lot during *first* startup, which populates this list already, THEN we explicitly init the list as part of *normal* startup. Sigh. So clear the list every time before we (re-)add the standard bumpmaps. - //llassert( LLStandardBumpmap::sStandardBumpmapCount == 0 ); - clear(); - LL_INFOS() << "Adding standard bumpmaps." << LL_ENDL; - gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount++] = LLStandardBumpmap("None"); // BE_NO_BUMP - gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount++] = LLStandardBumpmap("Brightness"); // BE_BRIGHTNESS - gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount++] = LLStandardBumpmap("Darkness"); // BE_DARKNESS - - std::string file_name = gDirUtilp->getExpandedFilename( LL_PATH_APP_SETTINGS, "std_bump.ini" ); - LLFILE* file = LLFile::fopen( file_name, "rt" ); /*Flawfinder: ignore*/ - if( !file ) - { - LL_WARNS() << "Could not open std_bump <" << file_name << ">" << LL_ENDL; - return; - } - - S32 file_version = 0; - - S32 fields_read = fscanf( file, "LLStandardBumpmap version %d", &file_version ); - if( fields_read != 1 ) - { - LL_WARNS() << "Bad LLStandardBumpmap header" << LL_ENDL; - return; - } - - if( file_version > STD_BUMP_LATEST_FILE_VERSION ) - { - LL_WARNS() << "LLStandardBumpmap has newer version (" << file_version << ") than viewer (" << STD_BUMP_LATEST_FILE_VERSION << ")" << LL_ENDL; - return; - } - - while( !feof(file) && (LLStandardBumpmap::sStandardBumpmapCount < (U32)TEM_BUMPMAP_COUNT) ) - { - // *NOTE: This buffer size is hard coded into scanf() below. - char label[2048] = ""; /* Flawfinder: ignore */ - char bump_image_id[2048] = ""; /* Flawfinder: ignore */ - fields_read = fscanf( /* Flawfinder: ignore */ - file, "\n%2047s %2047s", label, bump_image_id); - if( EOF == fields_read ) - { - break; - } - if( fields_read != 2 ) - { - LL_WARNS() << "Bad LLStandardBumpmap entry" << LL_ENDL; - return; - } - -// LL_INFOS() << "Loading bumpmap: " << bump_image_id << " from viewerart" << LL_ENDL; - gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount].mLabel = label; - gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount].mImage = - LLViewerTextureManager::getFetchedTexture(LLUUID(bump_image_id)); - gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount].mImage->setBoostLevel(LLGLTexture::LOCAL) ; - gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount].mImage->setLoadedCallback(LLBumpImageList::onSourceStandardLoaded, 0, true, false, NULL, NULL ); - gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount].mImage->forceToSaveRawImage(0, 30.f) ; - LLStandardBumpmap::sStandardBumpmapCount++; - } - - fclose( file ); -} - -// static -void LLStandardBumpmap::clear() -{ - LL_INFOS() << "Clearing standard bumpmaps." << LL_ENDL; - for( U32 i = 0; i < LLStandardBumpmap::sStandardBumpmapCount; i++ ) - { - gStandardBumpmapList[i].mLabel.assign(""); - gStandardBumpmapList[i].mImage = NULL; - } - sStandardBumpmapCount = 0; -} - -// static -void LLStandardBumpmap::destroyGL() -{ - clear(); -} - - - -//////////////////////////////////////////////////////////////// - -LLDrawPoolBump::LLDrawPoolBump() -: LLRenderPass(LLDrawPool::POOL_BUMP) -{ - shiny = false; -} - - -void LLDrawPoolBump::prerender() -{ - mShaderLevel = LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_OBJECT); -} - -// static -S32 LLDrawPoolBump::numBumpPasses() -{ - return 1; -} - - -//static -void LLDrawPoolBump::bindCubeMap(LLGLSLShader* shader, S32 shader_level, S32& diffuse_channel, S32& cube_channel) -{ - LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL; - if( cube_map && !LLPipeline::sReflectionProbesEnabled ) - { - if (shader ) - { - LLMatrix4 mat; - mat.initRows(LLVector4(gGLModelView+0), - LLVector4(gGLModelView+4), - LLVector4(gGLModelView+8), - LLVector4(gGLModelView+12)); - LLVector3 vec = LLVector3(gShinyOrigin) * mat; - LLVector4 vec4(vec, gShinyOrigin.mV[3]); - shader->uniform4fv(LLViewerShaderMgr::SHINY_ORIGIN, 1, vec4.mV); - if (shader_level > 1) - { - cube_map->setMatrix(1); - // Make sure that texture coord generation happens for tex unit 1, as that's the one we use for - // the cube map in the one pass shiny shaders - cube_channel = shader->enableTexture(LLViewerShaderMgr::ENVIRONMENT_MAP, LLTexUnit::TT_CUBE_MAP); - cube_map->enableTexture(cube_channel); - diffuse_channel = shader->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); - } - else - { - cube_map->setMatrix(0); - cube_channel = shader->enableTexture(LLViewerShaderMgr::ENVIRONMENT_MAP, LLTexUnit::TT_CUBE_MAP); - diffuse_channel = -1; - cube_map->enable(cube_channel); - } - gGL.getTexUnit(cube_channel)->bind(cube_map); - gGL.getTexUnit(0)->activate(); - } - else - { - cube_channel = 0; - diffuse_channel = -1; - gGL.getTexUnit(0)->disable(); - cube_map->enable(0); - cube_map->setMatrix(0); - gGL.getTexUnit(0)->bind(cube_map); - } - } -} - -//static -void LLDrawPoolBump::unbindCubeMap(LLGLSLShader* shader, S32 shader_level, S32& diffuse_channel, S32& cube_channel) -{ - LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL; - if( cube_map && !LLPipeline::sReflectionProbesEnabled) - { - if (shader_level > 1) - { - shader->disableTexture(LLViewerShaderMgr::ENVIRONMENT_MAP, LLTexUnit::TT_CUBE_MAP); - - if (LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_OBJECT) > 0) - { - if (diffuse_channel != 0) - { - shader->disableTexture(LLViewerShaderMgr::DIFFUSE_MAP); - } - } - } - // Moved below shader->disableTexture call to avoid false alarms from auto-re-enable of textures on stage 0 - // MAINT-755 - cube_map->disable(); - cube_map->restoreMatrix(); - } -} - -void LLDrawPoolBump::beginFullbrightShiny() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; //LL_RECORD_BLOCK_TIME(FTM_RENDER_SHINY); - - sVertexMask = VERTEX_MASK_SHINY | LLVertexBuffer::MAP_TEXCOORD0; - - // Second pass: environment map - shader = &gDeferredFullbrightShinyProgram; - if (LLPipeline::sRenderingHUDs) - { - shader = &gHUDFullbrightShinyProgram; - } - - if (mRigged) - { - llassert(shader->mRiggedVariant); - shader = shader->mRiggedVariant; - } - - // bind exposure map so fullbright shader can cancel out exposure - S32 channel = shader->enableTexture(LLShaderMgr::EXPOSURE_MAP); - if (channel > -1) - { - gGL.getTexUnit(channel)->bind(&gPipeline.mExposureMap); - } - - LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL; - - if (cube_map && !LLPipeline::sReflectionProbesEnabled) - { - // Make sure that texture coord generation happens for tex unit 1, as that's the one we use for - // the cube map in the one pass shiny shaders - gGL.getTexUnit(1)->disable(); - cube_channel = shader->enableTexture(LLViewerShaderMgr::ENVIRONMENT_MAP, LLTexUnit::TT_CUBE_MAP); - cube_map->enableTexture(cube_channel); - diffuse_channel = shader->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); - - gGL.getTexUnit(cube_channel)->bind(cube_map); - gGL.getTexUnit(0)->activate(); - } - - { - LLMatrix4 mat; - mat.initRows(LLVector4(gGLModelView+0), - LLVector4(gGLModelView+4), - LLVector4(gGLModelView+8), - LLVector4(gGLModelView+12)); - shader->bind(); - - LLVector3 vec = LLVector3(gShinyOrigin) * mat; - LLVector4 vec4(vec, gShinyOrigin.mV[3]); - shader->uniform4fv(LLViewerShaderMgr::SHINY_ORIGIN, 1, vec4.mV); - - if (LLPipeline::sReflectionProbesEnabled) - { - gPipeline.bindReflectionProbes(*shader); - } - else - { - gPipeline.setEnvMat(*shader); - } - } - - if (mShaderLevel > 1) - { //indexed texture rendering, channel 0 is always diffuse - diffuse_channel = 0; - } - - shiny = true; -} - -void LLDrawPoolBump::renderFullbrightShiny() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; //LL_RECORD_BLOCK_TIME(FTM_RENDER_SHINY); - - { - LLGLEnable blend_enable(GL_BLEND); - - if (mShaderLevel > 1) - { - if (mRigged) - { - LLRenderPass::pushRiggedBatches(LLRenderPass::PASS_FULLBRIGHT_SHINY_RIGGED, true, true); - } - else - { - LLRenderPass::pushBatches(LLRenderPass::PASS_FULLBRIGHT_SHINY, true, true); - } - } - else - { - if (mRigged) - { - LLRenderPass::pushRiggedBatches(LLRenderPass::PASS_FULLBRIGHT_SHINY_RIGGED); - } - else - { - LLRenderPass::pushBatches(LLRenderPass::PASS_FULLBRIGHT_SHINY); - } - } - } -} - -void LLDrawPoolBump::endFullbrightShiny() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; //LL_RECORD_BLOCK_TIME(FTM_RENDER_SHINY); - - LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL; - if( cube_map && !LLPipeline::sReflectionProbesEnabled ) - { - cube_map->disable(); - if (shader->mFeatures.hasReflectionProbes) - { - gPipeline.unbindReflectionProbes(*shader); - } - shader->unbind(); - } - - diffuse_channel = -1; - cube_channel = 0; - shiny = false; -} - -void LLDrawPoolBump::renderGroup(LLSpatialGroup* group, U32 type, bool texture = true) -{ - LLSpatialGroup::drawmap_elem_t& draw_info = group->mDrawMap[type]; - - for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k) - { - LLDrawInfo& params = **k; - - applyModelMatrix(params); - - params.mVertexBuffer->setBuffer(); - params.mVertexBuffer->drawRange(LLRender::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset); - } -} - - -// static -bool LLDrawPoolBump::bindBumpMap(LLDrawInfo& params, S32 channel) -{ - U8 bump_code = params.mBump; - - return bindBumpMap(bump_code, params.mTexture, channel); -} - -//static -bool LLDrawPoolBump::bindBumpMap(LLFace* face, S32 channel) -{ - const LLTextureEntry* te = face->getTextureEntry(); - if (te) - { - U8 bump_code = te->getBumpmap(); - return bindBumpMap(bump_code, face->getTexture(), channel); - } - - return false; -} - -//static -bool LLDrawPoolBump::bindBumpMap(U8 bump_code, LLViewerTexture* texture, S32 channel) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - //Note: texture atlas does not support bump texture now. - LLViewerFetchedTexture* tex = LLViewerTextureManager::staticCastToFetchedTexture(texture) ; - if(!tex) - { - //if the texture is not a fetched texture - return false; - } - - LLViewerTexture* bump = NULL; - - switch( bump_code ) - { - case BE_NO_BUMP: - break; - case BE_BRIGHTNESS: - case BE_DARKNESS: - bump = gBumpImageList.getBrightnessDarknessImage( tex, bump_code ); - break; - - default: - if( bump_code < LLStandardBumpmap::sStandardBumpmapCount ) - { - bump = gStandardBumpmapList[bump_code].mImage; - gBumpImageList.addTextureStats(bump_code, tex->getID(), tex->getMaxVirtualSize()); - } - break; - } - - if (bump) - { - if (channel == -2) - { - gGL.getTexUnit(1)->bindFast(bump); - gGL.getTexUnit(0)->bindFast(bump); - } - else - { - // NOTE: do not use bindFast here (see SL-16222) - gGL.getTexUnit(channel)->bind(bump); - } - - return true; - } - - return false; -} - -//static -void LLDrawPoolBump::beginBump() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; //LL_RECORD_BLOCK_TIME(FTM_RENDER_BUMP); - sVertexMask = VERTEX_MASK_BUMP; - // Optional second pass: emboss bump map - stop_glerror(); - - shader = &gObjectBumpProgram; - - if (mRigged) - { - llassert(shader->mRiggedVariant); - shader = shader->mRiggedVariant; - } - - shader->bind(); - - gGL.setSceneBlendType(LLRender::BT_MULT_X2); - stop_glerror(); -} - -//static -void LLDrawPoolBump::renderBump(U32 pass) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; //LL_RECORD_BLOCK_TIME(FTM_RENDER_BUMP); - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE, GL_LEQUAL); - LLGLEnable blend(GL_BLEND); - gGL.diffuseColor4f(1,1,1,1); - /// Get rid of z-fighting with non-bump pass. - LLGLEnable polyOffset(GL_POLYGON_OFFSET_FILL); - glPolygonOffset(-1.0f, -1.0f); - pushBumpBatches(pass); -} - -//static -void LLDrawPoolBump::endBump(U32 pass) -{ - LLGLSLShader::unbind(); - - gGL.setSceneBlendType(LLRender::BT_ALPHA); -} - -S32 LLDrawPoolBump::getNumDeferredPasses() -{ - return 1; -} - -void LLDrawPoolBump::renderDeferred(S32 pass) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; //LL_RECORD_BLOCK_TIME(FTM_RENDER_BUMP); - - shiny = true; - for (int i = 0; i < 2; ++i) - { - bool rigged = i == 1; - gDeferredBumpProgram.bind(rigged); - diffuse_channel = LLGLSLShader::sCurBoundShaderPtr->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); - bump_channel = LLGLSLShader::sCurBoundShaderPtr->enableTexture(LLViewerShaderMgr::BUMP_MAP); - gGL.getTexUnit(diffuse_channel)->unbind(LLTexUnit::TT_TEXTURE); - gGL.getTexUnit(bump_channel)->unbind(LLTexUnit::TT_TEXTURE); - - U32 type = rigged ? LLRenderPass::PASS_BUMP_RIGGED : LLRenderPass::PASS_BUMP; - LLCullResult::drawinfo_iterator begin = gPipeline.beginRenderMap(type); - LLCullResult::drawinfo_iterator end = gPipeline.endRenderMap(type); - - LLVOAvatar* avatar = nullptr; - U64 skin = 0; - - for (LLCullResult::drawinfo_iterator i = begin; i != end; ) - { - LLDrawInfo& params = **i; - - LLCullResult::increment_iterator(i, end); - - LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(params.mAlphaMaskCutoff); - LLDrawPoolBump::bindBumpMap(params, bump_channel); - - if (rigged) - { - if (avatar != params.mAvatar || skin != params.mSkinInfo->mHash) - { - uploadMatrixPalette(params); - avatar = params.mAvatar; - skin = params.mSkinInfo->mHash; - } - pushBumpBatch(params, true, false); - } - else - { - pushBumpBatch(params, true, false); - } - } - - LLGLSLShader::sCurBoundShaderPtr->disableTexture(LLViewerShaderMgr::DIFFUSE_MAP); - LLGLSLShader::sCurBoundShaderPtr->disableTexture(LLViewerShaderMgr::BUMP_MAP); - LLGLSLShader::sCurBoundShaderPtr->unbind(); - gGL.getTexUnit(0)->activate(); - } - - shiny = false; -} - - -void LLDrawPoolBump::renderPostDeferred(S32 pass) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - - S32 num_passes = LLPipeline::sRenderingHUDs ? 1 : 2; // skip rigged pass when rendering HUDs - - for (int i = 0; i < num_passes; ++i) - { // two passes -- static and rigged - mRigged = (i == 1); - - // render shiny - beginFullbrightShiny(); - renderFullbrightShiny(); - endFullbrightShiny(); - - //render bump - beginBump(); - renderBump(LLRenderPass::PASS_POST_BUMP); - endBump(); - } -} - - -//////////////////////////////////////////////////////////////// -// List of bump-maps created from other textures. - - -//const LLUUID TEST_BUMP_ID("3d33eaf2-459c-6f97-fd76-5fce3fc29447"); - -void LLBumpImageList::init() -{ - llassert( mBrightnessEntries.size() == 0 ); - llassert( mDarknessEntries.size() == 0 ); - - LLStandardBumpmap::restoreGL(); - sMainQueue = LL::WorkQueue::getInstance("mainloop"); - sTexUpdateQueue = LL::WorkQueue::getInstance("LLImageGL"); // Share work queue with tex loader. -} - -void LLBumpImageList::clear() -{ - LL_INFOS() << "Clearing dynamic bumpmaps." << LL_ENDL; - // these will be re-populated on-demand - mBrightnessEntries.clear(); - mDarknessEntries.clear(); - - sRenderTarget.release(); - - LLStandardBumpmap::clear(); -} - -void LLBumpImageList::shutdown() -{ - clear(); - LLStandardBumpmap::shutdown(); -} - -void LLBumpImageList::destroyGL() -{ - clear(); - LLStandardBumpmap::destroyGL(); -} - -void LLBumpImageList::restoreGL() -{ - if(!gTextureList.isInitialized()) - { - //safe to return here because bump images will be reloaded during initialization later. - return ; - } - - LLStandardBumpmap::restoreGL(); - // Images will be recreated as they are needed. -} - - -LLBumpImageList::~LLBumpImageList() -{ - // Shutdown should have already been called. - llassert( mBrightnessEntries.size() == 0 ); - llassert( mDarknessEntries.size() == 0 ); -} - - -// Note: Does nothing for entries in gStandardBumpmapList that are not actually standard bump images (e.g. none, brightness, and darkness) -void LLBumpImageList::addTextureStats(U8 bump, const LLUUID& base_image_id, F32 virtual_size) -{ - bump &= TEM_BUMP_MASK; - LLViewerFetchedTexture* bump_image = gStandardBumpmapList[bump].mImage; - if( bump_image ) - { - bump_image->addTextureStats(virtual_size); - } -} - - -void LLBumpImageList::updateImages() -{ - for (bump_image_map_t::iterator iter = mBrightnessEntries.begin(); iter != mBrightnessEntries.end(); ) - { - bump_image_map_t::iterator curiter = iter++; - LLViewerTexture* image = curiter->second; - if( image ) - { - bool destroy = true; - if( image->hasGLTexture()) - { - if( image->getBoundRecently() ) - { - destroy = false; - } - else - { - image->destroyGLTexture(); - } - } - - if( destroy ) - { - //LL_INFOS() << "*** Destroying bright " << (void*)image << LL_ENDL; - mBrightnessEntries.erase(curiter); // deletes the image thanks to reference counting - } - } - } - - for (bump_image_map_t::iterator iter = mDarknessEntries.begin(); iter != mDarknessEntries.end(); ) - { - bump_image_map_t::iterator curiter = iter++; - LLViewerTexture* image = curiter->second; - if( image ) - { - bool destroy = true; - if( image->hasGLTexture()) - { - if( image->getBoundRecently() ) - { - destroy = false; - } - else - { - image->destroyGLTexture(); - } - } - - if( destroy ) - { - //LL_INFOS() << "*** Destroying dark " << (void*)image << LL_ENDL;; - mDarknessEntries.erase(curiter); // deletes the image thanks to reference counting - } - } - } -} - - -// Note: the caller SHOULD NOT keep the pointer that this function returns. It may be updated as more data arrives. -LLViewerTexture* LLBumpImageList::getBrightnessDarknessImage(LLViewerFetchedTexture* src_image, U8 bump_code ) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - llassert( (bump_code == BE_BRIGHTNESS) || (bump_code == BE_DARKNESS) ); - - LLViewerTexture* bump = NULL; - - bump_image_map_t* entries_list = NULL; - void (*callback_func)( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata ) = NULL; - - switch( bump_code ) - { - case BE_BRIGHTNESS: - entries_list = &mBrightnessEntries; - callback_func = LLBumpImageList::onSourceBrightnessLoaded; - break; - case BE_DARKNESS: - entries_list = &mDarknessEntries; - callback_func = LLBumpImageList::onSourceDarknessLoaded; - break; - default: - llassert(0); - return NULL; - } - - bump_image_map_t::iterator iter = entries_list->find(src_image->getID()); - if (iter != entries_list->end() && iter->second.notNull()) - { - bump = iter->second; - } - else - { - (*entries_list)[src_image->getID()] = LLViewerTextureManager::getLocalTexture( true ); - bump = (*entries_list)[src_image->getID()]; // In case callback was called immediately and replaced the image - } - - if (!src_image->hasCallbacks()) - { //if image has no callbacks but resolutions don't match, trigger raw image loaded callback again - if (src_image->getWidth() != bump->getWidth() || - src_image->getHeight() != bump->getHeight())// || - //(LLPipeline::sRenderDeferred && bump->getComponents() != 4)) - { - src_image->setBoostLevel(LLGLTexture::BOOST_BUMP) ; - src_image->setLoadedCallback( callback_func, 0, true, false, new LLUUID(src_image->getID()), NULL ); - src_image->forceToSaveRawImage(0) ; - } - } - - return bump; -} - - -// static -void LLBumpImageList::onSourceBrightnessLoaded( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata ) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - LLUUID* source_asset_id = (LLUUID*)userdata; - LLBumpImageList::onSourceLoaded( success, src_vi, src, *source_asset_id, BE_BRIGHTNESS ); - if( final ) - { - delete source_asset_id; - } -} - -// static -void LLBumpImageList::onSourceDarknessLoaded( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata ) -{ - LLUUID* source_asset_id = (LLUUID*)userdata; - LLBumpImageList::onSourceLoaded( success, src_vi, src, *source_asset_id, BE_DARKNESS ); - if( final ) - { - delete source_asset_id; - } -} - -void LLBumpImageList::onSourceStandardLoaded( bool success, LLViewerFetchedTexture* src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata) -{ - if (success && LLPipeline::sRenderDeferred) - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - LLPointer nrm_image = new LLImageRaw(src->getWidth(), src->getHeight(), 4); - { - generateNormalMapFromAlpha(src, nrm_image); - } - src_vi->setExplicitFormat(GL_RGBA, GL_RGBA); - { - src_vi->createGLTexture(src_vi->getDiscardLevel(), nrm_image); - } - } -} - -void LLBumpImageList::generateNormalMapFromAlpha(LLImageRaw* src, LLImageRaw* nrm_image) -{ - LLImageDataSharedLock lockIn(src); - LLImageDataLock lockOut(nrm_image); - - U8* nrm_data = nrm_image->getData(); - S32 resX = src->getWidth(); - S32 resY = src->getHeight(); - - const U8* src_data = src->getData(); - - S32 src_cmp = src->getComponents(); - - F32 norm_scale = gSavedSettings.getF32("RenderNormalMapScale"); - - U32 idx = 0; - //generate normal map from pseudo-heightfield - for (S32 j = 0; j < resY; ++j) - { - for (S32 i = 0; i < resX; ++i) - { - S32 rX = (i+1)%resX; - S32 rY = (j+1)%resY; - S32 lX = (i-1)%resX; - S32 lY = (j-1)%resY; - - if (lX < 0) - { - lX += resX; - } - if (lY < 0) - { - lY += resY; - } - - F32 cH = (F32) src_data[(j*resX+i)*src_cmp+src_cmp-1]; - - LLVector3 right = LLVector3(norm_scale, 0, (F32) src_data[(j*resX+rX)*src_cmp+src_cmp-1]-cH); - LLVector3 left = LLVector3(-norm_scale, 0, (F32) src_data[(j*resX+lX)*src_cmp+src_cmp-1]-cH); - LLVector3 up = LLVector3(0, -norm_scale, (F32) src_data[(lY*resX+i)*src_cmp+src_cmp-1]-cH); - LLVector3 down = LLVector3(0, norm_scale, (F32) src_data[(rY*resX+i)*src_cmp+src_cmp-1]-cH); - - LLVector3 norm = right%down + down%left + left%up + up%right; - - norm.normVec(); - - norm *= 0.5f; - norm += LLVector3(0.5f,0.5f,0.5f); - - idx = (j*resX+i)*4; - nrm_data[idx+0]= (U8) (norm.mV[0]*255); - nrm_data[idx+1]= (U8) (norm.mV[1]*255); - nrm_data[idx+2]= (U8) (norm.mV[2]*255); - nrm_data[idx+3]= src_data[(j*resX+i)*src_cmp+src_cmp-1]; - } - } -} - -// static -void LLBumpImageList::onSourceLoaded( bool success, LLViewerTexture *src_vi, LLImageRaw* src, LLUUID& source_asset_id, EBumpEffect bump_code ) -{ - LL_PROFILE_ZONE_SCOPED; - - if( success ) - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - - LLImageDataSharedLock lock(src); - - bump_image_map_t& entries_list(bump_code == BE_BRIGHTNESS ? gBumpImageList.mBrightnessEntries : gBumpImageList.mDarknessEntries ); - bump_image_map_t::iterator iter = entries_list.find(source_asset_id); - - { - if (iter == entries_list.end() || - iter->second.isNull() || - iter->second->getWidth() != src->getWidth() || - iter->second->getHeight() != src->getHeight()) // bump not cached yet or has changed resolution - { //make sure an entry exists for this image - entries_list[src_vi->getID()] = LLViewerTextureManager::getLocalTexture(true); - iter = entries_list.find(src_vi->getID()); - } - } - - if (iter->second->getWidth() != src->getWidth() || - iter->second->getHeight() != src->getHeight()) // bump not cached yet or has changed resolution - { - LLPointer dst_image = new LLImageRaw(src->getWidth(), src->getHeight(), 1); - U8* dst_data = dst_image->getData(); - S32 dst_data_size = dst_image->getDataSize(); - - const U8* src_data = src->getData(); - S32 src_data_size = src->getDataSize(); - - S32 src_components = src->getComponents(); - - // Convert to luminance and then scale and bias that to get ready for - // embossed bump mapping. (0-255 maps to 127-255) - - // Convert to fixed point so we don't have to worry about precision/clamping. - const S32 FIXED_PT = 8; - const S32 R_WEIGHT = S32(0.2995f * (1< maximum ) - { - maximum = dst_data[i]; - } - } - } - else - { - llassert(0); - dst_image->clear(); - } - } - break; - case 3: - case 4: - { - if( src_data_size == dst_data_size * src_components ) - { - for( S32 i = 0, j=0; i < dst_data_size; i++, j+= src_components ) - { - // RGB to luminance - dst_data[i] = (R_WEIGHT * src_data[j] + G_WEIGHT * src_data[j+1] + B_WEIGHT * src_data[j+2]) >> FIXED_PT; - //llassert( dst_data[i] <= 255 );true because it's 8bit - if( dst_data[i] < minimum ) - { - minimum = dst_data[i]; - } - if( dst_data[i] > maximum ) - { - maximum = dst_data[i]; - } - } - } - else - { - llassert(0); - dst_image->clear(); - } - } - break; - default: - llassert(0); - dst_image->clear(); - break; - } - - if( maximum > minimum ) - { - U8 bias_and_scale_lut[256]; - F32 twice_one_over_range = 2.f / (maximum - minimum); - S32 i; - - const F32 ARTIFICIAL_SCALE = 2.f; // Advantage: exaggerates the effect in midrange. Disadvantage: clamps at the extremes. - if (BE_DARKNESS == bump_code) - { - for( i = minimum; i <= maximum; i++ ) - { - F32 minus_one_to_one = F32(maximum - i) * twice_one_over_range - 1.f; - bias_and_scale_lut[i] = llclampb(ll_round(127 * minus_one_to_one * ARTIFICIAL_SCALE + 128)); - } - } - else - { - for( i = minimum; i <= maximum; i++ ) - { - F32 minus_one_to_one = F32(i - minimum) * twice_one_over_range - 1.f; - bias_and_scale_lut[i] = llclampb(ll_round(127 * minus_one_to_one * ARTIFICIAL_SCALE + 128)); - } - } - - for( i = 0; i < dst_data_size; i++ ) - { - dst_data[i] = bias_and_scale_lut[dst_data[i]]; - } - } - - //--------------------------------------------------- - // immediately assign bump to a smart pointer in case some local smart pointer - // accidentally releases it. - LLPointer bump = iter->second; - - if (!LLPipeline::sRenderDeferred) - { - bump->setExplicitFormat(GL_ALPHA8, GL_ALPHA); - -#if LL_BUMPLIST_MULTITHREADED - auto tex_queue = LLImageGLThread::sEnabledTextures ? sTexUpdateQueue.lock() : nullptr; - - if (tex_queue) - { //dispatch creation to background thread - LLImageRaw* dst_ptr = dst_image; - LLViewerTexture* bump_ptr = bump; - dst_ptr->ref(); - bump_ptr->ref(); - tex_queue->post( - [=]() - { - LL_PROFILE_ZONE_NAMED("bil - create texture"); - bump_ptr->createGLTexture(0, dst_ptr); - bump_ptr->unref(); - dst_ptr->unref(); - }); - - } - else -#endif - { - bump->createGLTexture(0, dst_image); - } - } - else - { //convert to normal map - LL_PROFILE_ZONE_NAMED("bil - create normal map"); - LLImageGL* img = bump->getGLTexture(); - LLImageRaw* dst_ptr = dst_image.get(); - LLGLTexture* bump_ptr = bump.get(); - - dst_ptr->ref(); - img->ref(); - bump_ptr->ref(); - auto create_func = [=]() - { - img->setUseMipMaps(true); - // upload dst_image to GPU (greyscale in red channel) - img->setExplicitFormat(GL_RED, GL_RED); - - bump_ptr->createGLTexture(0, dst_ptr); - dst_ptr->unref(); - }; - - auto generate_func = [=]() - { - // Allocate an empty RGBA texture at "tex_name" the same size as bump - // Note: bump will still point at GPU copy of dst_image - bump_ptr->setExplicitFormat(GL_RGBA, GL_RGBA); - LLGLuint tex_name; - img->createGLTexture(0, nullptr, false, 0, true, &tex_name); - - // point render target at empty buffer - sRenderTarget.setColorAttachment(img, tex_name); - - // generate normal map in empty texture - { - sRenderTarget.bindTarget(); - - LLGLDepthTest depth(GL_FALSE); - LLGLDisable cull(GL_CULL_FACE); - LLGLDisable blend(GL_BLEND); - gGL.setColorMask(true, true); - - gNormalMapGenProgram.bind(); - - static LLStaticHashedString sNormScale("norm_scale"); - static LLStaticHashedString sStepX("stepX"); - static LLStaticHashedString sStepY("stepY"); - - gNormalMapGenProgram.uniform1f(sNormScale, gSavedSettings.getF32("RenderNormalMapScale")); - gNormalMapGenProgram.uniform1f(sStepX, 1.f / bump_ptr->getWidth()); - gNormalMapGenProgram.uniform1f(sStepY, 1.f / bump_ptr->getHeight()); - - gGL.getTexUnit(0)->bind(bump_ptr); - - gGL.begin(LLRender::TRIANGLE_STRIP); - gGL.texCoord2f(0, 0); - gGL.vertex2f(0, 0); - - gGL.texCoord2f(0, 1); - gGL.vertex2f(0, 1); - - gGL.texCoord2f(1, 0); - gGL.vertex2f(1, 0); - - gGL.texCoord2f(1, 1); - gGL.vertex2f(1, 1); - - gGL.end(); - - gGL.flush(); - - gNormalMapGenProgram.unbind(); - - sRenderTarget.flush(); - sRenderTarget.releaseColorAttachment(); - } - - // point bump at normal map and free gpu copy of dst_image - img->syncTexName(tex_name); - - // generate mipmap - gGL.getTexUnit(0)->bind(img); - glGenerateMipmap(GL_TEXTURE_2D); - gGL.getTexUnit(0)->disable(); - - bump_ptr->unref(); - img->unref(); - }; - -#if LL_BUMPLIST_MULTITHREADED - auto main_queue = LLImageGLThread::sEnabledTextures ? sMainQueue.lock() : nullptr; - - if (main_queue) - { //dispatch texture upload to background thread, issue GPU commands to generate normal map on main thread - main_queue->postTo( - sTexUpdateQueue, - create_func, - generate_func); - } - else -#endif - { // immediate upload texture and generate normal map - create_func(); - generate_func(); - } - - - } - - iter->second = bump; // derefs (and deletes) old image - //--------------------------------------------------- - } - } -} - -void LLDrawPoolBump::pushBumpBatches(U32 type) -{ - LLVOAvatar* avatar = nullptr; - U64 skin = 0; - - if (mRigged) - { // nudge type enum and include skinweights for rigged pass - type += 1; - } - - LLCullResult::drawinfo_iterator begin = gPipeline.beginRenderMap(type); - LLCullResult::drawinfo_iterator end = gPipeline.endRenderMap(type); - - for (LLCullResult::drawinfo_iterator i = begin; i != end; ++i) - { - LLDrawInfo& params = **i; - - if (LLDrawPoolBump::bindBumpMap(params)) - { - if (mRigged) - { - if (avatar != params.mAvatar || skin != params.mSkinInfo->mHash) - { - if (uploadMatrixPalette(params)) - { - avatar = params.mAvatar; - skin = params.mSkinInfo->mHash; - } - else - { - continue; - } - } - } - pushBumpBatch(params, false); - } - } -} - -void LLRenderPass::pushBumpBatch(LLDrawInfo& params, bool texture, bool batch_textures) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - applyModelMatrix(params); - - bool tex_setup = false; - - if (batch_textures && params.mTextureList.size() > 1) - { - for (U32 i = 0; i < params.mTextureList.size(); ++i) - { - if (params.mTextureList[i].notNull()) - { - gGL.getTexUnit(i)->bindFast(params.mTextureList[i]); - } - } - } - else - { //not batching textures or batch has only 1 texture -- might need a texture matrix - if (params.mTextureMatrix) - { - if (shiny) - { - gGL.getTexUnit(0)->activate(); - gGL.matrixMode(LLRender::MM_TEXTURE); - } - else - { - gGL.getTexUnit(0)->activate(); - gGL.matrixMode(LLRender::MM_TEXTURE); - gGL.loadMatrix((GLfloat*) params.mTextureMatrix->mMatrix); - gPipeline.mTextureMatrixOps++; - } - - gGL.loadMatrix((GLfloat*) params.mTextureMatrix->mMatrix); - gPipeline.mTextureMatrixOps++; - - tex_setup = true; - } - - if (shiny && mShaderLevel > 1 && texture) - { - if (params.mTexture.notNull()) - { - gGL.getTexUnit(diffuse_channel)->bindFast(params.mTexture); - } - else - { - gGL.getTexUnit(diffuse_channel)->unbind(LLTexUnit::TT_TEXTURE); - } - } - } - - params.mVertexBuffer->setBuffer(); - params.mVertexBuffer->drawRange(LLRender::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset); - - if (tex_setup) - { - if (shiny) - { - gGL.getTexUnit(0)->activate(); - } - else - { - gGL.getTexUnit(0)->activate(); - gGL.matrixMode(LLRender::MM_TEXTURE); - } - gGL.loadIdentity(); - gGL.matrixMode(LLRender::MM_MODELVIEW); - } -} - +/** + * @file lldrawpoolbump.cpp + * @brief LLDrawPoolBump class implementation + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lldrawpoolbump.h" + +#include "llstl.h" +#include "llviewercontrol.h" +#include "lldir.h" +#include "m3math.h" +#include "m4math.h" +#include "v4math.h" +#include "llglheaders.h" +#include "llrender.h" + +#include "llcubemap.h" +#include "lldrawable.h" +#include "llface.h" +#include "llsky.h" +#include "llstartup.h" +#include "lltextureentry.h" +#include "llviewercamera.h" +#include "llviewertexturelist.h" +#include "pipeline.h" +#include "llspatialpartition.h" +#include "llviewershadermgr.h" +#include "llmodel.h" + +//#include "llimagebmp.h" +//#include "../tools/imdebug/imdebug.h" + +// static +LLStandardBumpmap gStandardBumpmapList[TEM_BUMPMAP_COUNT]; +LL::WorkQueue::weak_t LLBumpImageList::sMainQueue; +LL::WorkQueue::weak_t LLBumpImageList::sTexUpdateQueue; +LLRenderTarget LLBumpImageList::sRenderTarget; + +// static +U32 LLStandardBumpmap::sStandardBumpmapCount = 0; + +// static +LLBumpImageList gBumpImageList; + +const S32 STD_BUMP_LATEST_FILE_VERSION = 1; + +const U32 VERTEX_MASK_SHINY = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_COLOR; +const U32 VERTEX_MASK_BUMP = LLVertexBuffer::MAP_VERTEX |LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_TEXCOORD1; + +U32 LLDrawPoolBump::sVertexMask = VERTEX_MASK_SHINY; + + +static LLGLSLShader* shader = NULL; +static S32 cube_channel = -1; +static S32 diffuse_channel = -1; +static S32 bump_channel = -1; +static bool shiny = false; + +// Enabled after changing LLViewerTexture::mNeedsCreateTexture to an +// LLAtomicBool; this should work just fine, now. HB +#define LL_BUMPLIST_MULTITHREADED 1 + + +// static +void LLStandardBumpmap::shutdown() +{ + LLStandardBumpmap::destroyGL(); +} + +// static +void LLStandardBumpmap::restoreGL() +{ + addstandard(); +} + +// static +void LLStandardBumpmap::addstandard() +{ + if(!gTextureList.isInitialized()) + { + //Note: loading pre-configuration sometimes triggers this call. + //But it is safe to return here because bump images will be reloaded during initialization later. + return ; + } + + if (LLStartUp::getStartupState() < STATE_SEED_CAP_GRANTED) + { + // Not ready, need caps for images + return; + } + + // can't assert; we destroyGL and restoreGL a lot during *first* startup, which populates this list already, THEN we explicitly init the list as part of *normal* startup. Sigh. So clear the list every time before we (re-)add the standard bumpmaps. + //llassert( LLStandardBumpmap::sStandardBumpmapCount == 0 ); + clear(); + LL_INFOS() << "Adding standard bumpmaps." << LL_ENDL; + gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount++] = LLStandardBumpmap("None"); // BE_NO_BUMP + gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount++] = LLStandardBumpmap("Brightness"); // BE_BRIGHTNESS + gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount++] = LLStandardBumpmap("Darkness"); // BE_DARKNESS + + std::string file_name = gDirUtilp->getExpandedFilename( LL_PATH_APP_SETTINGS, "std_bump.ini" ); + LLFILE* file = LLFile::fopen( file_name, "rt" ); /*Flawfinder: ignore*/ + if( !file ) + { + LL_WARNS() << "Could not open std_bump <" << file_name << ">" << LL_ENDL; + return; + } + + S32 file_version = 0; + + S32 fields_read = fscanf( file, "LLStandardBumpmap version %d", &file_version ); + if( fields_read != 1 ) + { + LL_WARNS() << "Bad LLStandardBumpmap header" << LL_ENDL; + return; + } + + if( file_version > STD_BUMP_LATEST_FILE_VERSION ) + { + LL_WARNS() << "LLStandardBumpmap has newer version (" << file_version << ") than viewer (" << STD_BUMP_LATEST_FILE_VERSION << ")" << LL_ENDL; + return; + } + + while( !feof(file) && (LLStandardBumpmap::sStandardBumpmapCount < (U32)TEM_BUMPMAP_COUNT) ) + { + // *NOTE: This buffer size is hard coded into scanf() below. + char label[2048] = ""; /* Flawfinder: ignore */ + char bump_image_id[2048] = ""; /* Flawfinder: ignore */ + fields_read = fscanf( /* Flawfinder: ignore */ + file, "\n%2047s %2047s", label, bump_image_id); + if( EOF == fields_read ) + { + break; + } + if( fields_read != 2 ) + { + LL_WARNS() << "Bad LLStandardBumpmap entry" << LL_ENDL; + return; + } + +// LL_INFOS() << "Loading bumpmap: " << bump_image_id << " from viewerart" << LL_ENDL; + gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount].mLabel = label; + gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount].mImage = + LLViewerTextureManager::getFetchedTexture(LLUUID(bump_image_id)); + gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount].mImage->setBoostLevel(LLGLTexture::LOCAL) ; + gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount].mImage->setLoadedCallback(LLBumpImageList::onSourceStandardLoaded, 0, true, false, NULL, NULL ); + gStandardBumpmapList[LLStandardBumpmap::sStandardBumpmapCount].mImage->forceToSaveRawImage(0, 30.f) ; + LLStandardBumpmap::sStandardBumpmapCount++; + } + + fclose( file ); +} + +// static +void LLStandardBumpmap::clear() +{ + LL_INFOS() << "Clearing standard bumpmaps." << LL_ENDL; + for( U32 i = 0; i < LLStandardBumpmap::sStandardBumpmapCount; i++ ) + { + gStandardBumpmapList[i].mLabel.assign(""); + gStandardBumpmapList[i].mImage = NULL; + } + sStandardBumpmapCount = 0; +} + +// static +void LLStandardBumpmap::destroyGL() +{ + clear(); +} + + + +//////////////////////////////////////////////////////////////// + +LLDrawPoolBump::LLDrawPoolBump() +: LLRenderPass(LLDrawPool::POOL_BUMP) +{ + shiny = false; +} + + +void LLDrawPoolBump::prerender() +{ + mShaderLevel = LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_OBJECT); +} + +// static +S32 LLDrawPoolBump::numBumpPasses() +{ + return 1; +} + + +//static +void LLDrawPoolBump::bindCubeMap(LLGLSLShader* shader, S32 shader_level, S32& diffuse_channel, S32& cube_channel) +{ + LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL; + if( cube_map && !LLPipeline::sReflectionProbesEnabled ) + { + if (shader ) + { + LLMatrix4 mat; + mat.initRows(LLVector4(gGLModelView+0), + LLVector4(gGLModelView+4), + LLVector4(gGLModelView+8), + LLVector4(gGLModelView+12)); + LLVector3 vec = LLVector3(gShinyOrigin) * mat; + LLVector4 vec4(vec, gShinyOrigin.mV[3]); + shader->uniform4fv(LLViewerShaderMgr::SHINY_ORIGIN, 1, vec4.mV); + if (shader_level > 1) + { + cube_map->setMatrix(1); + // Make sure that texture coord generation happens for tex unit 1, as that's the one we use for + // the cube map in the one pass shiny shaders + cube_channel = shader->enableTexture(LLViewerShaderMgr::ENVIRONMENT_MAP, LLTexUnit::TT_CUBE_MAP); + cube_map->enableTexture(cube_channel); + diffuse_channel = shader->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); + } + else + { + cube_map->setMatrix(0); + cube_channel = shader->enableTexture(LLViewerShaderMgr::ENVIRONMENT_MAP, LLTexUnit::TT_CUBE_MAP); + diffuse_channel = -1; + cube_map->enable(cube_channel); + } + gGL.getTexUnit(cube_channel)->bind(cube_map); + gGL.getTexUnit(0)->activate(); + } + else + { + cube_channel = 0; + diffuse_channel = -1; + gGL.getTexUnit(0)->disable(); + cube_map->enable(0); + cube_map->setMatrix(0); + gGL.getTexUnit(0)->bind(cube_map); + } + } +} + +//static +void LLDrawPoolBump::unbindCubeMap(LLGLSLShader* shader, S32 shader_level, S32& diffuse_channel, S32& cube_channel) +{ + LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL; + if( cube_map && !LLPipeline::sReflectionProbesEnabled) + { + if (shader_level > 1) + { + shader->disableTexture(LLViewerShaderMgr::ENVIRONMENT_MAP, LLTexUnit::TT_CUBE_MAP); + + if (LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_OBJECT) > 0) + { + if (diffuse_channel != 0) + { + shader->disableTexture(LLViewerShaderMgr::DIFFUSE_MAP); + } + } + } + // Moved below shader->disableTexture call to avoid false alarms from auto-re-enable of textures on stage 0 + // MAINT-755 + cube_map->disable(); + cube_map->restoreMatrix(); + } +} + +void LLDrawPoolBump::beginFullbrightShiny() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; //LL_RECORD_BLOCK_TIME(FTM_RENDER_SHINY); + + sVertexMask = VERTEX_MASK_SHINY | LLVertexBuffer::MAP_TEXCOORD0; + + // Second pass: environment map + shader = &gDeferredFullbrightShinyProgram; + if (LLPipeline::sRenderingHUDs) + { + shader = &gHUDFullbrightShinyProgram; + } + + if (mRigged) + { + llassert(shader->mRiggedVariant); + shader = shader->mRiggedVariant; + } + + // bind exposure map so fullbright shader can cancel out exposure + S32 channel = shader->enableTexture(LLShaderMgr::EXPOSURE_MAP); + if (channel > -1) + { + gGL.getTexUnit(channel)->bind(&gPipeline.mExposureMap); + } + + LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL; + + if (cube_map && !LLPipeline::sReflectionProbesEnabled) + { + // Make sure that texture coord generation happens for tex unit 1, as that's the one we use for + // the cube map in the one pass shiny shaders + gGL.getTexUnit(1)->disable(); + cube_channel = shader->enableTexture(LLViewerShaderMgr::ENVIRONMENT_MAP, LLTexUnit::TT_CUBE_MAP); + cube_map->enableTexture(cube_channel); + diffuse_channel = shader->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); + + gGL.getTexUnit(cube_channel)->bind(cube_map); + gGL.getTexUnit(0)->activate(); + } + + { + LLMatrix4 mat; + mat.initRows(LLVector4(gGLModelView+0), + LLVector4(gGLModelView+4), + LLVector4(gGLModelView+8), + LLVector4(gGLModelView+12)); + shader->bind(); + + LLVector3 vec = LLVector3(gShinyOrigin) * mat; + LLVector4 vec4(vec, gShinyOrigin.mV[3]); + shader->uniform4fv(LLViewerShaderMgr::SHINY_ORIGIN, 1, vec4.mV); + + if (LLPipeline::sReflectionProbesEnabled) + { + gPipeline.bindReflectionProbes(*shader); + } + else + { + gPipeline.setEnvMat(*shader); + } + } + + if (mShaderLevel > 1) + { //indexed texture rendering, channel 0 is always diffuse + diffuse_channel = 0; + } + + shiny = true; +} + +void LLDrawPoolBump::renderFullbrightShiny() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; //LL_RECORD_BLOCK_TIME(FTM_RENDER_SHINY); + + { + LLGLEnable blend_enable(GL_BLEND); + + if (mShaderLevel > 1) + { + if (mRigged) + { + LLRenderPass::pushRiggedBatches(LLRenderPass::PASS_FULLBRIGHT_SHINY_RIGGED, true, true); + } + else + { + LLRenderPass::pushBatches(LLRenderPass::PASS_FULLBRIGHT_SHINY, true, true); + } + } + else + { + if (mRigged) + { + LLRenderPass::pushRiggedBatches(LLRenderPass::PASS_FULLBRIGHT_SHINY_RIGGED); + } + else + { + LLRenderPass::pushBatches(LLRenderPass::PASS_FULLBRIGHT_SHINY); + } + } + } +} + +void LLDrawPoolBump::endFullbrightShiny() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; //LL_RECORD_BLOCK_TIME(FTM_RENDER_SHINY); + + LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL; + if( cube_map && !LLPipeline::sReflectionProbesEnabled ) + { + cube_map->disable(); + if (shader->mFeatures.hasReflectionProbes) + { + gPipeline.unbindReflectionProbes(*shader); + } + shader->unbind(); + } + + diffuse_channel = -1; + cube_channel = 0; + shiny = false; +} + +void LLDrawPoolBump::renderGroup(LLSpatialGroup* group, U32 type, bool texture = true) +{ + LLSpatialGroup::drawmap_elem_t& draw_info = group->mDrawMap[type]; + + for (LLSpatialGroup::drawmap_elem_t::iterator k = draw_info.begin(); k != draw_info.end(); ++k) + { + LLDrawInfo& params = **k; + + applyModelMatrix(params); + + params.mVertexBuffer->setBuffer(); + params.mVertexBuffer->drawRange(LLRender::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset); + } +} + + +// static +bool LLDrawPoolBump::bindBumpMap(LLDrawInfo& params, S32 channel) +{ + U8 bump_code = params.mBump; + + return bindBumpMap(bump_code, params.mTexture, channel); +} + +//static +bool LLDrawPoolBump::bindBumpMap(LLFace* face, S32 channel) +{ + const LLTextureEntry* te = face->getTextureEntry(); + if (te) + { + U8 bump_code = te->getBumpmap(); + return bindBumpMap(bump_code, face->getTexture(), channel); + } + + return false; +} + +//static +bool LLDrawPoolBump::bindBumpMap(U8 bump_code, LLViewerTexture* texture, S32 channel) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + //Note: texture atlas does not support bump texture now. + LLViewerFetchedTexture* tex = LLViewerTextureManager::staticCastToFetchedTexture(texture) ; + if(!tex) + { + //if the texture is not a fetched texture + return false; + } + + LLViewerTexture* bump = NULL; + + switch( bump_code ) + { + case BE_NO_BUMP: + break; + case BE_BRIGHTNESS: + case BE_DARKNESS: + bump = gBumpImageList.getBrightnessDarknessImage( tex, bump_code ); + break; + + default: + if( bump_code < LLStandardBumpmap::sStandardBumpmapCount ) + { + bump = gStandardBumpmapList[bump_code].mImage; + gBumpImageList.addTextureStats(bump_code, tex->getID(), tex->getMaxVirtualSize()); + } + break; + } + + if (bump) + { + if (channel == -2) + { + gGL.getTexUnit(1)->bindFast(bump); + gGL.getTexUnit(0)->bindFast(bump); + } + else + { + // NOTE: do not use bindFast here (see SL-16222) + gGL.getTexUnit(channel)->bind(bump); + } + + return true; + } + + return false; +} + +//static +void LLDrawPoolBump::beginBump() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; //LL_RECORD_BLOCK_TIME(FTM_RENDER_BUMP); + sVertexMask = VERTEX_MASK_BUMP; + // Optional second pass: emboss bump map + stop_glerror(); + + shader = &gObjectBumpProgram; + + if (mRigged) + { + llassert(shader->mRiggedVariant); + shader = shader->mRiggedVariant; + } + + shader->bind(); + + gGL.setSceneBlendType(LLRender::BT_MULT_X2); + stop_glerror(); +} + +//static +void LLDrawPoolBump::renderBump(U32 pass) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; //LL_RECORD_BLOCK_TIME(FTM_RENDER_BUMP); + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE, GL_LEQUAL); + LLGLEnable blend(GL_BLEND); + gGL.diffuseColor4f(1,1,1,1); + /// Get rid of z-fighting with non-bump pass. + LLGLEnable polyOffset(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(-1.0f, -1.0f); + pushBumpBatches(pass); +} + +//static +void LLDrawPoolBump::endBump(U32 pass) +{ + LLGLSLShader::unbind(); + + gGL.setSceneBlendType(LLRender::BT_ALPHA); +} + +S32 LLDrawPoolBump::getNumDeferredPasses() +{ + return 1; +} + +void LLDrawPoolBump::renderDeferred(S32 pass) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; //LL_RECORD_BLOCK_TIME(FTM_RENDER_BUMP); + + shiny = true; + for (int i = 0; i < 2; ++i) + { + bool rigged = i == 1; + gDeferredBumpProgram.bind(rigged); + diffuse_channel = LLGLSLShader::sCurBoundShaderPtr->enableTexture(LLViewerShaderMgr::DIFFUSE_MAP); + bump_channel = LLGLSLShader::sCurBoundShaderPtr->enableTexture(LLViewerShaderMgr::BUMP_MAP); + gGL.getTexUnit(diffuse_channel)->unbind(LLTexUnit::TT_TEXTURE); + gGL.getTexUnit(bump_channel)->unbind(LLTexUnit::TT_TEXTURE); + + U32 type = rigged ? LLRenderPass::PASS_BUMP_RIGGED : LLRenderPass::PASS_BUMP; + LLCullResult::drawinfo_iterator begin = gPipeline.beginRenderMap(type); + LLCullResult::drawinfo_iterator end = gPipeline.endRenderMap(type); + + LLVOAvatar* avatar = nullptr; + U64 skin = 0; + + for (LLCullResult::drawinfo_iterator i = begin; i != end; ) + { + LLDrawInfo& params = **i; + + LLCullResult::increment_iterator(i, end); + + LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(params.mAlphaMaskCutoff); + LLDrawPoolBump::bindBumpMap(params, bump_channel); + + if (rigged) + { + if (avatar != params.mAvatar || skin != params.mSkinInfo->mHash) + { + uploadMatrixPalette(params); + avatar = params.mAvatar; + skin = params.mSkinInfo->mHash; + } + pushBumpBatch(params, true, false); + } + else + { + pushBumpBatch(params, true, false); + } + } + + LLGLSLShader::sCurBoundShaderPtr->disableTexture(LLViewerShaderMgr::DIFFUSE_MAP); + LLGLSLShader::sCurBoundShaderPtr->disableTexture(LLViewerShaderMgr::BUMP_MAP); + LLGLSLShader::sCurBoundShaderPtr->unbind(); + gGL.getTexUnit(0)->activate(); + } + + shiny = false; +} + + +void LLDrawPoolBump::renderPostDeferred(S32 pass) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + + S32 num_passes = LLPipeline::sRenderingHUDs ? 1 : 2; // skip rigged pass when rendering HUDs + + for (int i = 0; i < num_passes; ++i) + { // two passes -- static and rigged + mRigged = (i == 1); + + // render shiny + beginFullbrightShiny(); + renderFullbrightShiny(); + endFullbrightShiny(); + + //render bump + beginBump(); + renderBump(LLRenderPass::PASS_POST_BUMP); + endBump(); + } +} + + +//////////////////////////////////////////////////////////////// +// List of bump-maps created from other textures. + + +//const LLUUID TEST_BUMP_ID("3d33eaf2-459c-6f97-fd76-5fce3fc29447"); + +void LLBumpImageList::init() +{ + llassert( mBrightnessEntries.size() == 0 ); + llassert( mDarknessEntries.size() == 0 ); + + LLStandardBumpmap::restoreGL(); + sMainQueue = LL::WorkQueue::getInstance("mainloop"); + sTexUpdateQueue = LL::WorkQueue::getInstance("LLImageGL"); // Share work queue with tex loader. +} + +void LLBumpImageList::clear() +{ + LL_INFOS() << "Clearing dynamic bumpmaps." << LL_ENDL; + // these will be re-populated on-demand + mBrightnessEntries.clear(); + mDarknessEntries.clear(); + + sRenderTarget.release(); + + LLStandardBumpmap::clear(); +} + +void LLBumpImageList::shutdown() +{ + clear(); + LLStandardBumpmap::shutdown(); +} + +void LLBumpImageList::destroyGL() +{ + clear(); + LLStandardBumpmap::destroyGL(); +} + +void LLBumpImageList::restoreGL() +{ + if(!gTextureList.isInitialized()) + { + //safe to return here because bump images will be reloaded during initialization later. + return ; + } + + LLStandardBumpmap::restoreGL(); + // Images will be recreated as they are needed. +} + + +LLBumpImageList::~LLBumpImageList() +{ + // Shutdown should have already been called. + llassert( mBrightnessEntries.size() == 0 ); + llassert( mDarknessEntries.size() == 0 ); +} + + +// Note: Does nothing for entries in gStandardBumpmapList that are not actually standard bump images (e.g. none, brightness, and darkness) +void LLBumpImageList::addTextureStats(U8 bump, const LLUUID& base_image_id, F32 virtual_size) +{ + bump &= TEM_BUMP_MASK; + LLViewerFetchedTexture* bump_image = gStandardBumpmapList[bump].mImage; + if( bump_image ) + { + bump_image->addTextureStats(virtual_size); + } +} + + +void LLBumpImageList::updateImages() +{ + for (bump_image_map_t::iterator iter = mBrightnessEntries.begin(); iter != mBrightnessEntries.end(); ) + { + bump_image_map_t::iterator curiter = iter++; + LLViewerTexture* image = curiter->second; + if( image ) + { + bool destroy = true; + if( image->hasGLTexture()) + { + if( image->getBoundRecently() ) + { + destroy = false; + } + else + { + image->destroyGLTexture(); + } + } + + if( destroy ) + { + //LL_INFOS() << "*** Destroying bright " << (void*)image << LL_ENDL; + mBrightnessEntries.erase(curiter); // deletes the image thanks to reference counting + } + } + } + + for (bump_image_map_t::iterator iter = mDarknessEntries.begin(); iter != mDarknessEntries.end(); ) + { + bump_image_map_t::iterator curiter = iter++; + LLViewerTexture* image = curiter->second; + if( image ) + { + bool destroy = true; + if( image->hasGLTexture()) + { + if( image->getBoundRecently() ) + { + destroy = false; + } + else + { + image->destroyGLTexture(); + } + } + + if( destroy ) + { + //LL_INFOS() << "*** Destroying dark " << (void*)image << LL_ENDL;; + mDarknessEntries.erase(curiter); // deletes the image thanks to reference counting + } + } + } +} + + +// Note: the caller SHOULD NOT keep the pointer that this function returns. It may be updated as more data arrives. +LLViewerTexture* LLBumpImageList::getBrightnessDarknessImage(LLViewerFetchedTexture* src_image, U8 bump_code ) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + llassert( (bump_code == BE_BRIGHTNESS) || (bump_code == BE_DARKNESS) ); + + LLViewerTexture* bump = NULL; + + bump_image_map_t* entries_list = NULL; + void (*callback_func)( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata ) = NULL; + + switch( bump_code ) + { + case BE_BRIGHTNESS: + entries_list = &mBrightnessEntries; + callback_func = LLBumpImageList::onSourceBrightnessLoaded; + break; + case BE_DARKNESS: + entries_list = &mDarknessEntries; + callback_func = LLBumpImageList::onSourceDarknessLoaded; + break; + default: + llassert(0); + return NULL; + } + + bump_image_map_t::iterator iter = entries_list->find(src_image->getID()); + if (iter != entries_list->end() && iter->second.notNull()) + { + bump = iter->second; + } + else + { + (*entries_list)[src_image->getID()] = LLViewerTextureManager::getLocalTexture( true ); + bump = (*entries_list)[src_image->getID()]; // In case callback was called immediately and replaced the image + } + + if (!src_image->hasCallbacks()) + { //if image has no callbacks but resolutions don't match, trigger raw image loaded callback again + if (src_image->getWidth() != bump->getWidth() || + src_image->getHeight() != bump->getHeight())// || + //(LLPipeline::sRenderDeferred && bump->getComponents() != 4)) + { + src_image->setBoostLevel(LLGLTexture::BOOST_BUMP) ; + src_image->setLoadedCallback( callback_func, 0, true, false, new LLUUID(src_image->getID()), NULL ); + src_image->forceToSaveRawImage(0) ; + } + } + + return bump; +} + + +// static +void LLBumpImageList::onSourceBrightnessLoaded( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata ) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + LLUUID* source_asset_id = (LLUUID*)userdata; + LLBumpImageList::onSourceLoaded( success, src_vi, src, *source_asset_id, BE_BRIGHTNESS ); + if( final ) + { + delete source_asset_id; + } +} + +// static +void LLBumpImageList::onSourceDarknessLoaded( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata ) +{ + LLUUID* source_asset_id = (LLUUID*)userdata; + LLBumpImageList::onSourceLoaded( success, src_vi, src, *source_asset_id, BE_DARKNESS ); + if( final ) + { + delete source_asset_id; + } +} + +void LLBumpImageList::onSourceStandardLoaded( bool success, LLViewerFetchedTexture* src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata) +{ + if (success && LLPipeline::sRenderDeferred) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + LLPointer nrm_image = new LLImageRaw(src->getWidth(), src->getHeight(), 4); + { + generateNormalMapFromAlpha(src, nrm_image); + } + src_vi->setExplicitFormat(GL_RGBA, GL_RGBA); + { + src_vi->createGLTexture(src_vi->getDiscardLevel(), nrm_image); + } + } +} + +void LLBumpImageList::generateNormalMapFromAlpha(LLImageRaw* src, LLImageRaw* nrm_image) +{ + LLImageDataSharedLock lockIn(src); + LLImageDataLock lockOut(nrm_image); + + U8* nrm_data = nrm_image->getData(); + S32 resX = src->getWidth(); + S32 resY = src->getHeight(); + + const U8* src_data = src->getData(); + + S32 src_cmp = src->getComponents(); + + F32 norm_scale = gSavedSettings.getF32("RenderNormalMapScale"); + + U32 idx = 0; + //generate normal map from pseudo-heightfield + for (S32 j = 0; j < resY; ++j) + { + for (S32 i = 0; i < resX; ++i) + { + S32 rX = (i+1)%resX; + S32 rY = (j+1)%resY; + S32 lX = (i-1)%resX; + S32 lY = (j-1)%resY; + + if (lX < 0) + { + lX += resX; + } + if (lY < 0) + { + lY += resY; + } + + F32 cH = (F32) src_data[(j*resX+i)*src_cmp+src_cmp-1]; + + LLVector3 right = LLVector3(norm_scale, 0, (F32) src_data[(j*resX+rX)*src_cmp+src_cmp-1]-cH); + LLVector3 left = LLVector3(-norm_scale, 0, (F32) src_data[(j*resX+lX)*src_cmp+src_cmp-1]-cH); + LLVector3 up = LLVector3(0, -norm_scale, (F32) src_data[(lY*resX+i)*src_cmp+src_cmp-1]-cH); + LLVector3 down = LLVector3(0, norm_scale, (F32) src_data[(rY*resX+i)*src_cmp+src_cmp-1]-cH); + + LLVector3 norm = right%down + down%left + left%up + up%right; + + norm.normVec(); + + norm *= 0.5f; + norm += LLVector3(0.5f,0.5f,0.5f); + + idx = (j*resX+i)*4; + nrm_data[idx+0]= (U8) (norm.mV[0]*255); + nrm_data[idx+1]= (U8) (norm.mV[1]*255); + nrm_data[idx+2]= (U8) (norm.mV[2]*255); + nrm_data[idx+3]= src_data[(j*resX+i)*src_cmp+src_cmp-1]; + } + } +} + +// static +void LLBumpImageList::onSourceLoaded( bool success, LLViewerTexture *src_vi, LLImageRaw* src, LLUUID& source_asset_id, EBumpEffect bump_code ) +{ + LL_PROFILE_ZONE_SCOPED; + + if( success ) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + + LLImageDataSharedLock lock(src); + + bump_image_map_t& entries_list(bump_code == BE_BRIGHTNESS ? gBumpImageList.mBrightnessEntries : gBumpImageList.mDarknessEntries ); + bump_image_map_t::iterator iter = entries_list.find(source_asset_id); + + { + if (iter == entries_list.end() || + iter->second.isNull() || + iter->second->getWidth() != src->getWidth() || + iter->second->getHeight() != src->getHeight()) // bump not cached yet or has changed resolution + { //make sure an entry exists for this image + entries_list[src_vi->getID()] = LLViewerTextureManager::getLocalTexture(true); + iter = entries_list.find(src_vi->getID()); + } + } + + if (iter->second->getWidth() != src->getWidth() || + iter->second->getHeight() != src->getHeight()) // bump not cached yet or has changed resolution + { + LLPointer dst_image = new LLImageRaw(src->getWidth(), src->getHeight(), 1); + U8* dst_data = dst_image->getData(); + S32 dst_data_size = dst_image->getDataSize(); + + const U8* src_data = src->getData(); + S32 src_data_size = src->getDataSize(); + + S32 src_components = src->getComponents(); + + // Convert to luminance and then scale and bias that to get ready for + // embossed bump mapping. (0-255 maps to 127-255) + + // Convert to fixed point so we don't have to worry about precision/clamping. + const S32 FIXED_PT = 8; + const S32 R_WEIGHT = S32(0.2995f * (1< maximum ) + { + maximum = dst_data[i]; + } + } + } + else + { + llassert(0); + dst_image->clear(); + } + } + break; + case 3: + case 4: + { + if( src_data_size == dst_data_size * src_components ) + { + for( S32 i = 0, j=0; i < dst_data_size; i++, j+= src_components ) + { + // RGB to luminance + dst_data[i] = (R_WEIGHT * src_data[j] + G_WEIGHT * src_data[j+1] + B_WEIGHT * src_data[j+2]) >> FIXED_PT; + //llassert( dst_data[i] <= 255 );true because it's 8bit + if( dst_data[i] < minimum ) + { + minimum = dst_data[i]; + } + if( dst_data[i] > maximum ) + { + maximum = dst_data[i]; + } + } + } + else + { + llassert(0); + dst_image->clear(); + } + } + break; + default: + llassert(0); + dst_image->clear(); + break; + } + + if( maximum > minimum ) + { + U8 bias_and_scale_lut[256]; + F32 twice_one_over_range = 2.f / (maximum - minimum); + S32 i; + + const F32 ARTIFICIAL_SCALE = 2.f; // Advantage: exaggerates the effect in midrange. Disadvantage: clamps at the extremes. + if (BE_DARKNESS == bump_code) + { + for( i = minimum; i <= maximum; i++ ) + { + F32 minus_one_to_one = F32(maximum - i) * twice_one_over_range - 1.f; + bias_and_scale_lut[i] = llclampb(ll_round(127 * minus_one_to_one * ARTIFICIAL_SCALE + 128)); + } + } + else + { + for( i = minimum; i <= maximum; i++ ) + { + F32 minus_one_to_one = F32(i - minimum) * twice_one_over_range - 1.f; + bias_and_scale_lut[i] = llclampb(ll_round(127 * minus_one_to_one * ARTIFICIAL_SCALE + 128)); + } + } + + for( i = 0; i < dst_data_size; i++ ) + { + dst_data[i] = bias_and_scale_lut[dst_data[i]]; + } + } + + //--------------------------------------------------- + // immediately assign bump to a smart pointer in case some local smart pointer + // accidentally releases it. + LLPointer bump = iter->second; + + if (!LLPipeline::sRenderDeferred) + { + bump->setExplicitFormat(GL_ALPHA8, GL_ALPHA); + +#if LL_BUMPLIST_MULTITHREADED + auto tex_queue = LLImageGLThread::sEnabledTextures ? sTexUpdateQueue.lock() : nullptr; + + if (tex_queue) + { //dispatch creation to background thread + LLImageRaw* dst_ptr = dst_image; + LLViewerTexture* bump_ptr = bump; + dst_ptr->ref(); + bump_ptr->ref(); + tex_queue->post( + [=]() + { + LL_PROFILE_ZONE_NAMED("bil - create texture"); + bump_ptr->createGLTexture(0, dst_ptr); + bump_ptr->unref(); + dst_ptr->unref(); + }); + + } + else +#endif + { + bump->createGLTexture(0, dst_image); + } + } + else + { //convert to normal map + LL_PROFILE_ZONE_NAMED("bil - create normal map"); + LLImageGL* img = bump->getGLTexture(); + LLImageRaw* dst_ptr = dst_image.get(); + LLGLTexture* bump_ptr = bump.get(); + + dst_ptr->ref(); + img->ref(); + bump_ptr->ref(); + auto create_func = [=]() + { + img->setUseMipMaps(true); + // upload dst_image to GPU (greyscale in red channel) + img->setExplicitFormat(GL_RED, GL_RED); + + bump_ptr->createGLTexture(0, dst_ptr); + dst_ptr->unref(); + }; + + auto generate_func = [=]() + { + // Allocate an empty RGBA texture at "tex_name" the same size as bump + // Note: bump will still point at GPU copy of dst_image + bump_ptr->setExplicitFormat(GL_RGBA, GL_RGBA); + LLGLuint tex_name; + img->createGLTexture(0, nullptr, false, 0, true, &tex_name); + + // point render target at empty buffer + sRenderTarget.setColorAttachment(img, tex_name); + + // generate normal map in empty texture + { + sRenderTarget.bindTarget(); + + LLGLDepthTest depth(GL_FALSE); + LLGLDisable cull(GL_CULL_FACE); + LLGLDisable blend(GL_BLEND); + gGL.setColorMask(true, true); + + gNormalMapGenProgram.bind(); + + static LLStaticHashedString sNormScale("norm_scale"); + static LLStaticHashedString sStepX("stepX"); + static LLStaticHashedString sStepY("stepY"); + + gNormalMapGenProgram.uniform1f(sNormScale, gSavedSettings.getF32("RenderNormalMapScale")); + gNormalMapGenProgram.uniform1f(sStepX, 1.f / bump_ptr->getWidth()); + gNormalMapGenProgram.uniform1f(sStepY, 1.f / bump_ptr->getHeight()); + + gGL.getTexUnit(0)->bind(bump_ptr); + + gGL.begin(LLRender::TRIANGLE_STRIP); + gGL.texCoord2f(0, 0); + gGL.vertex2f(0, 0); + + gGL.texCoord2f(0, 1); + gGL.vertex2f(0, 1); + + gGL.texCoord2f(1, 0); + gGL.vertex2f(1, 0); + + gGL.texCoord2f(1, 1); + gGL.vertex2f(1, 1); + + gGL.end(); + + gGL.flush(); + + gNormalMapGenProgram.unbind(); + + sRenderTarget.flush(); + sRenderTarget.releaseColorAttachment(); + } + + // point bump at normal map and free gpu copy of dst_image + img->syncTexName(tex_name); + + // generate mipmap + gGL.getTexUnit(0)->bind(img); + glGenerateMipmap(GL_TEXTURE_2D); + gGL.getTexUnit(0)->disable(); + + bump_ptr->unref(); + img->unref(); + }; + +#if LL_BUMPLIST_MULTITHREADED + auto main_queue = LLImageGLThread::sEnabledTextures ? sMainQueue.lock() : nullptr; + + if (main_queue) + { //dispatch texture upload to background thread, issue GPU commands to generate normal map on main thread + main_queue->postTo( + sTexUpdateQueue, + create_func, + generate_func); + } + else +#endif + { // immediate upload texture and generate normal map + create_func(); + generate_func(); + } + + + } + + iter->second = bump; // derefs (and deletes) old image + //--------------------------------------------------- + } + } +} + +void LLDrawPoolBump::pushBumpBatches(U32 type) +{ + LLVOAvatar* avatar = nullptr; + U64 skin = 0; + + if (mRigged) + { // nudge type enum and include skinweights for rigged pass + type += 1; + } + + LLCullResult::drawinfo_iterator begin = gPipeline.beginRenderMap(type); + LLCullResult::drawinfo_iterator end = gPipeline.endRenderMap(type); + + for (LLCullResult::drawinfo_iterator i = begin; i != end; ++i) + { + LLDrawInfo& params = **i; + + if (LLDrawPoolBump::bindBumpMap(params)) + { + if (mRigged) + { + if (avatar != params.mAvatar || skin != params.mSkinInfo->mHash) + { + if (uploadMatrixPalette(params)) + { + avatar = params.mAvatar; + skin = params.mSkinInfo->mHash; + } + else + { + continue; + } + } + } + pushBumpBatch(params, false); + } + } +} + +void LLRenderPass::pushBumpBatch(LLDrawInfo& params, bool texture, bool batch_textures) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + applyModelMatrix(params); + + bool tex_setup = false; + + if (batch_textures && params.mTextureList.size() > 1) + { + for (U32 i = 0; i < params.mTextureList.size(); ++i) + { + if (params.mTextureList[i].notNull()) + { + gGL.getTexUnit(i)->bindFast(params.mTextureList[i]); + } + } + } + else + { //not batching textures or batch has only 1 texture -- might need a texture matrix + if (params.mTextureMatrix) + { + if (shiny) + { + gGL.getTexUnit(0)->activate(); + gGL.matrixMode(LLRender::MM_TEXTURE); + } + else + { + gGL.getTexUnit(0)->activate(); + gGL.matrixMode(LLRender::MM_TEXTURE); + gGL.loadMatrix((GLfloat*) params.mTextureMatrix->mMatrix); + gPipeline.mTextureMatrixOps++; + } + + gGL.loadMatrix((GLfloat*) params.mTextureMatrix->mMatrix); + gPipeline.mTextureMatrixOps++; + + tex_setup = true; + } + + if (shiny && mShaderLevel > 1 && texture) + { + if (params.mTexture.notNull()) + { + gGL.getTexUnit(diffuse_channel)->bindFast(params.mTexture); + } + else + { + gGL.getTexUnit(diffuse_channel)->unbind(LLTexUnit::TT_TEXTURE); + } + } + } + + params.mVertexBuffer->setBuffer(); + params.mVertexBuffer->drawRange(LLRender::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset); + + if (tex_setup) + { + if (shiny) + { + gGL.getTexUnit(0)->activate(); + } + else + { + gGL.getTexUnit(0)->activate(); + gGL.matrixMode(LLRender::MM_TEXTURE); + } + gGL.loadIdentity(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + } +} + diff --git a/indra/newview/lldrawpoolbump.h b/indra/newview/lldrawpoolbump.h index c43995007a..65cb9af015 100644 --- a/indra/newview/lldrawpoolbump.h +++ b/indra/newview/lldrawpoolbump.h @@ -1,163 +1,163 @@ -/** - * @file lldrawpoolbump.h - * @brief LLDrawPoolBump class definition - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLDRAWPOOLBUMP_H -#define LL_LLDRAWPOOLBUMP_H - -#include "lldrawpool.h" -#include "llstring.h" -#include "lltextureentry.h" -#include "lluuid.h" - -#include - -class LLImageRaw; -class LLSpatialGroup; -class LLDrawInfo; -class LLGLSLShader; -class LLViewerFetchedTexture; - -class LLDrawPoolBump : public LLRenderPass -{ -protected : - LLDrawPoolBump(const U32 type):LLRenderPass(type) { mShiny = false; } -public: - static U32 sVertexMask; - bool mShiny; - - virtual U32 getVertexDataMask() override { return sVertexMask; } - - LLDrawPoolBump(); - - /*virtual*/ void prerender() override; - - void pushBumpBatches(U32 type); - void renderGroup(LLSpatialGroup* group, U32 type, bool texture) override; - - S32 numBumpPasses(); - - void beginFullbrightShiny(); - void renderFullbrightShiny(); - void endFullbrightShiny(); - - void beginBump(); - void renderBump(U32 pass = LLRenderPass::PASS_BUMP); - void endBump(U32 pass = LLRenderPass::PASS_BUMP); - - static void bindCubeMap(LLGLSLShader* shader, S32 shader_level, S32& diffuse_channel, S32& cube_channel); - static void unbindCubeMap(LLGLSLShader* shader, S32 shader_level, S32& diffuse_channel, S32& cube_channel); - - virtual S32 getNumDeferredPasses() override; - /*virtual*/ void renderDeferred(S32 pass) override; - - virtual S32 getNumPostDeferredPasses() override { return 1; } - /*virtual*/ void renderPostDeferred(S32 pass) override; - - static bool bindBumpMap(LLDrawInfo& params, S32 channel = -2); - static bool bindBumpMap(LLFace* face, S32 channel = -2); - -private: - static bool bindBumpMap(U8 bump_code, LLViewerTexture* tex, S32 channel); - bool mRigged = false; // if true, doing a rigged pass - -}; - -enum EBumpEffect -{ - BE_NO_BUMP = 0, - BE_BRIGHTNESS = 1, - BE_DARKNESS = 2, - BE_STANDARD_0 = 3, // Standard must always be the last one - BE_COUNT = 4 -}; - -//////////////////////////////////////////////////////////////// -// List of standard bumpmaps that are specificed by LLTextureEntry::mBump's lower bits - -class LLStandardBumpmap -{ -public: - LLStandardBumpmap() : mLabel() {} - LLStandardBumpmap( const std::string& label ) : mLabel(label) {} - - std::string mLabel; - LLPointer mImage; - - static U32 sStandardBumpmapCount; // Number of valid values in gStandardBumpmapList[] - - static void clear(); - static void addstandard(); - - static void shutdown(); - static void restoreGL(); - static void destroyGL(); -}; - -extern LLStandardBumpmap gStandardBumpmapList[TEM_BUMPMAP_COUNT]; - -//////////////////////////////////////////////////////////////// -// List of one-component bump-maps created from other texures. - -struct LLBumpImageEntry; - -class LLBumpImageList -{ -public: - LLBumpImageList() {} - ~LLBumpImageList(); - - void init(); - void shutdown(); - void clear(); - void destroyGL(); - void restoreGL(); - void updateImages(); - - - LLViewerTexture* getBrightnessDarknessImage(LLViewerFetchedTexture* src_image, U8 bump_code); - void addTextureStats(U8 bump, const LLUUID& base_image_id, F32 virtual_size); - - static void onSourceBrightnessLoaded( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata ); - static void onSourceDarknessLoaded( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata ); - static void onSourceStandardLoaded( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata ); - static void generateNormalMapFromAlpha(LLImageRaw* src, LLImageRaw* nrm_image); - - -private: - static void onSourceLoaded( bool success, LLViewerTexture *src_vi, LLImageRaw* src, LLUUID& source_asset_id, EBumpEffect bump ); - -private: - typedef std::unordered_map > bump_image_map_t; - bump_image_map_t mBrightnessEntries; - bump_image_map_t mDarknessEntries; - static LL::WorkQueue::weak_t sMainQueue; - static LL::WorkQueue::weak_t sTexUpdateQueue; - static LLRenderTarget sRenderTarget; -}; - -extern LLBumpImageList gBumpImageList; - -#endif // LL_LLDRAWPOOLBUMP_H +/** + * @file lldrawpoolbump.h + * @brief LLDrawPoolBump class definition + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLDRAWPOOLBUMP_H +#define LL_LLDRAWPOOLBUMP_H + +#include "lldrawpool.h" +#include "llstring.h" +#include "lltextureentry.h" +#include "lluuid.h" + +#include + +class LLImageRaw; +class LLSpatialGroup; +class LLDrawInfo; +class LLGLSLShader; +class LLViewerFetchedTexture; + +class LLDrawPoolBump : public LLRenderPass +{ +protected : + LLDrawPoolBump(const U32 type):LLRenderPass(type) { mShiny = false; } +public: + static U32 sVertexMask; + bool mShiny; + + virtual U32 getVertexDataMask() override { return sVertexMask; } + + LLDrawPoolBump(); + + /*virtual*/ void prerender() override; + + void pushBumpBatches(U32 type); + void renderGroup(LLSpatialGroup* group, U32 type, bool texture) override; + + S32 numBumpPasses(); + + void beginFullbrightShiny(); + void renderFullbrightShiny(); + void endFullbrightShiny(); + + void beginBump(); + void renderBump(U32 pass = LLRenderPass::PASS_BUMP); + void endBump(U32 pass = LLRenderPass::PASS_BUMP); + + static void bindCubeMap(LLGLSLShader* shader, S32 shader_level, S32& diffuse_channel, S32& cube_channel); + static void unbindCubeMap(LLGLSLShader* shader, S32 shader_level, S32& diffuse_channel, S32& cube_channel); + + virtual S32 getNumDeferredPasses() override; + /*virtual*/ void renderDeferred(S32 pass) override; + + virtual S32 getNumPostDeferredPasses() override { return 1; } + /*virtual*/ void renderPostDeferred(S32 pass) override; + + static bool bindBumpMap(LLDrawInfo& params, S32 channel = -2); + static bool bindBumpMap(LLFace* face, S32 channel = -2); + +private: + static bool bindBumpMap(U8 bump_code, LLViewerTexture* tex, S32 channel); + bool mRigged = false; // if true, doing a rigged pass + +}; + +enum EBumpEffect +{ + BE_NO_BUMP = 0, + BE_BRIGHTNESS = 1, + BE_DARKNESS = 2, + BE_STANDARD_0 = 3, // Standard must always be the last one + BE_COUNT = 4 +}; + +//////////////////////////////////////////////////////////////// +// List of standard bumpmaps that are specificed by LLTextureEntry::mBump's lower bits + +class LLStandardBumpmap +{ +public: + LLStandardBumpmap() : mLabel() {} + LLStandardBumpmap( const std::string& label ) : mLabel(label) {} + + std::string mLabel; + LLPointer mImage; + + static U32 sStandardBumpmapCount; // Number of valid values in gStandardBumpmapList[] + + static void clear(); + static void addstandard(); + + static void shutdown(); + static void restoreGL(); + static void destroyGL(); +}; + +extern LLStandardBumpmap gStandardBumpmapList[TEM_BUMPMAP_COUNT]; + +//////////////////////////////////////////////////////////////// +// List of one-component bump-maps created from other texures. + +struct LLBumpImageEntry; + +class LLBumpImageList +{ +public: + LLBumpImageList() {} + ~LLBumpImageList(); + + void init(); + void shutdown(); + void clear(); + void destroyGL(); + void restoreGL(); + void updateImages(); + + + LLViewerTexture* getBrightnessDarknessImage(LLViewerFetchedTexture* src_image, U8 bump_code); + void addTextureStats(U8 bump, const LLUUID& base_image_id, F32 virtual_size); + + static void onSourceBrightnessLoaded( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata ); + static void onSourceDarknessLoaded( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata ); + static void onSourceStandardLoaded( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata ); + static void generateNormalMapFromAlpha(LLImageRaw* src, LLImageRaw* nrm_image); + + +private: + static void onSourceLoaded( bool success, LLViewerTexture *src_vi, LLImageRaw* src, LLUUID& source_asset_id, EBumpEffect bump ); + +private: + typedef std::unordered_map > bump_image_map_t; + bump_image_map_t mBrightnessEntries; + bump_image_map_t mDarknessEntries; + static LL::WorkQueue::weak_t sMainQueue; + static LL::WorkQueue::weak_t sTexUpdateQueue; + static LLRenderTarget sRenderTarget; +}; + +extern LLBumpImageList gBumpImageList; + +#endif // LL_LLDRAWPOOLBUMP_H diff --git a/indra/newview/lldrawpoolmaterials.cpp b/indra/newview/lldrawpoolmaterials.cpp index c1cea4e2af..07834c58b4 100644 --- a/indra/newview/lldrawpoolmaterials.cpp +++ b/indra/newview/lldrawpoolmaterials.cpp @@ -1,299 +1,299 @@ -/** - * @file lldrawpool.cpp - * @brief LLDrawPoolMaterials class implementation - * @author Jonathan "Geenz" Goodman - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2013, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lldrawpoolmaterials.h" -#include "llviewershadermgr.h" -#include "pipeline.h" -#include "llglcommonfunc.h" -#include "llvoavatar.h" - -LLDrawPoolMaterials::LLDrawPoolMaterials() -: LLRenderPass(LLDrawPool::POOL_MATERIALS) -{ - -} - -void LLDrawPoolMaterials::prerender() -{ - mShaderLevel = LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_OBJECT); -} - -S32 LLDrawPoolMaterials::getNumDeferredPasses() -{ - // 12 render passes times 2 (one for each rigged and non rigged) - return 12*2; -} - -void LLDrawPoolMaterials::beginDeferredPass(S32 pass) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MATERIAL; - - bool rigged = false; - if (pass >= 12) - { - rigged = true; - pass -= 12; - } - U32 shader_idx[] = - { - 0, //LLRenderPass::PASS_MATERIAL, - //1, //LLRenderPass::PASS_MATERIAL_ALPHA, - 2, //LLRenderPass::PASS_MATERIAL_ALPHA_MASK, - 3, //LLRenderPass::PASS_MATERIAL_ALPHA_GLOW, - 4, //LLRenderPass::PASS_SPECMAP, - //5, //LLRenderPass::PASS_SPECMAP_BLEND, - 6, //LLRenderPass::PASS_SPECMAP_MASK, - 7, //LLRenderPass::PASS_SPECMAP_GLOW, - 8, //LLRenderPass::PASS_NORMMAP, - //9, //LLRenderPass::PASS_NORMMAP_BLEND, - 10, //LLRenderPass::PASS_NORMMAP_MASK, - 11, //LLRenderPass::PASS_NORMMAP_GLOW, - 12, //LLRenderPass::PASS_NORMSPEC, - //13, //LLRenderPass::PASS_NORMSPEC_BLEND, - 14, //LLRenderPass::PASS_NORMSPEC_MASK, - 15, //LLRenderPass::PASS_NORMSPEC_GLOW, - }; - - U32 idx = shader_idx[pass]; - - mShader = &(gDeferredMaterialProgram[idx]); - - if (rigged) - { - llassert(mShader->mRiggedVariant != nullptr); - mShader = mShader->mRiggedVariant; - } - - gPipeline.bindDeferredShader(*mShader); -} - -void LLDrawPoolMaterials::endDeferredPass(S32 pass) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MATERIAL; - - mShader->unbind(); - - LLRenderPass::endRenderPass(pass); -} - -void LLDrawPoolMaterials::renderDeferred(S32 pass) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MATERIAL; - static const U32 type_list[] = - { - LLRenderPass::PASS_MATERIAL, - //LLRenderPass::PASS_MATERIAL_ALPHA, - LLRenderPass::PASS_MATERIAL_ALPHA_MASK, - LLRenderPass::PASS_MATERIAL_ALPHA_EMISSIVE, - LLRenderPass::PASS_SPECMAP, - //LLRenderPass::PASS_SPECMAP_BLEND, - LLRenderPass::PASS_SPECMAP_MASK, - LLRenderPass::PASS_SPECMAP_EMISSIVE, - LLRenderPass::PASS_NORMMAP, - //LLRenderPass::PASS_NORMMAP_BLEND, - LLRenderPass::PASS_NORMMAP_MASK, - LLRenderPass::PASS_NORMMAP_EMISSIVE, - LLRenderPass::PASS_NORMSPEC, - //LLRenderPass::PASS_NORMSPEC_BLEND, - LLRenderPass::PASS_NORMSPEC_MASK, - LLRenderPass::PASS_NORMSPEC_EMISSIVE, - }; - - bool rigged = false; - if (pass >= 12) - { - rigged = true; - pass -= 12; - } - - llassert(pass < sizeof(type_list)/sizeof(U32)); - - U32 type = type_list[pass]; - if (rigged) - { - type += 1; - } - - LLCullResult::drawinfo_iterator begin = gPipeline.beginRenderMap(type); - LLCullResult::drawinfo_iterator end = gPipeline.endRenderMap(type); - - F32 lastIntensity = 0.f; - F32 lastFullbright = 0.f; - F32 lastMinimumAlpha = 0.f; - LLVector4 lastSpecular = LLVector4(0, 0, 0, 0); - - GLint intensity = mShader->getUniformLocation(LLShaderMgr::ENVIRONMENT_INTENSITY); - GLint brightness = mShader->getUniformLocation(LLShaderMgr::EMISSIVE_BRIGHTNESS); - GLint minAlpha = mShader->getUniformLocation(LLShaderMgr::MINIMUM_ALPHA); - GLint specular = mShader->getUniformLocation(LLShaderMgr::SPECULAR_COLOR); - - GLint diffuseChannel = mShader->enableTexture(LLShaderMgr::DIFFUSE_MAP); - GLint specChannel = mShader->enableTexture(LLShaderMgr::SPECULAR_MAP); - GLint normChannel = mShader->enableTexture(LLShaderMgr::BUMP_MAP); - - LLTexture* lastNormalMap = nullptr; - LLTexture* lastSpecMap = nullptr; - LLTexture* lastDiffuse = nullptr; - - gGL.getTexUnit(diffuseChannel)->unbindFast(LLTexUnit::TT_TEXTURE); - - if (intensity > -1) - { - glUniform1f(intensity, lastIntensity); - } - - if (brightness > -1) - { - glUniform1f(brightness, lastFullbright); - } - - if (minAlpha > -1) - { - glUniform1f(minAlpha, lastMinimumAlpha); - } - - if (specular > -1) - { - glUniform4fv(specular, 1, lastSpecular.mV); - } - - LLVOAvatar* lastAvatar = nullptr; - - for (LLCullResult::drawinfo_iterator i = begin; i != end; ) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_MATERIAL("materials draw loop"); - LLDrawInfo& params = **i; - - LLCullResult::increment_iterator(i, end); - - if (specular > -1 && params.mSpecColor != lastSpecular) - { - lastSpecular = params.mSpecColor; - glUniform4fv(specular, 1, lastSpecular.mV); - } - - if (intensity != -1 && lastIntensity != params.mEnvIntensity) - { - lastIntensity = params.mEnvIntensity; - glUniform1f(intensity, lastIntensity); - } - - if (minAlpha > -1 && lastMinimumAlpha != params.mAlphaMaskCutoff) - { - lastMinimumAlpha = params.mAlphaMaskCutoff; - glUniform1f(minAlpha, lastMinimumAlpha); - } - - F32 fullbright = params.mFullbright ? 1.f : 0.f; - if (brightness > -1 && lastFullbright != fullbright) - { - lastFullbright = fullbright; - glUniform1f(brightness, lastFullbright); - } - - if (normChannel > -1 && params.mNormalMap != lastNormalMap) - { - lastNormalMap = params.mNormalMap; - llassert(lastNormalMap); - gGL.getTexUnit(normChannel)->bindFast(lastNormalMap); - } - - if (specChannel > -1 && params.mSpecularMap != lastSpecMap) - { - lastSpecMap = params.mSpecularMap; - llassert(lastSpecMap); - gGL.getTexUnit(specChannel)->bindFast(lastSpecMap); - } - - if (params.mTexture != lastDiffuse) - { - lastDiffuse = params.mTexture; - if (lastDiffuse) - { - gGL.getTexUnit(diffuseChannel)->bindFast(lastDiffuse); - } - else - { - gGL.getTexUnit(diffuseChannel)->unbindFast(LLTexUnit::TT_TEXTURE); - } - } - - // upload matrix palette to shader - if (rigged && params.mAvatar.notNull()) - { - if (params.mAvatar != lastAvatar) - { - const LLVOAvatar::MatrixPaletteCache& mpc = params.mAvatar->updateSkinInfoMatrixPalette(params.mSkinInfo); - U32 count = mpc.mMatrixPalette.size(); - - if (count == 0) - { - //skin info not loaded yet, don't render - return; - } - - mShader->uniformMatrix3x4fv(LLViewerShaderMgr::AVATAR_MATRIX, - count, - false, - (GLfloat*)&(mpc.mGLMp[0])); - } - } - - applyModelMatrix(params); - - bool tex_setup = false; - - //not batching textures or batch has only 1 texture -- might need a texture matrix - if (params.mTextureMatrix) - { - gGL.getTexUnit(0)->activate(); - gGL.matrixMode(LLRender::MM_TEXTURE); - - gGL.loadMatrix((GLfloat*)params.mTextureMatrix->mMatrix); - gPipeline.mTextureMatrixOps++; - - tex_setup = true; - } - - /*if (params.mGroup) // TOO LATE - { - params.mGroup->rebuildMesh(); - }*/ - - params.mVertexBuffer->setBuffer(); - params.mVertexBuffer->drawRange(LLRender::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset); - - if (tex_setup) - { - gGL.getTexUnit(0)->activate(); - gGL.loadIdentity(); - gGL.matrixMode(LLRender::MM_MODELVIEW); - } - } -} +/** + * @file lldrawpool.cpp + * @brief LLDrawPoolMaterials class implementation + * @author Jonathan "Geenz" Goodman + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lldrawpoolmaterials.h" +#include "llviewershadermgr.h" +#include "pipeline.h" +#include "llglcommonfunc.h" +#include "llvoavatar.h" + +LLDrawPoolMaterials::LLDrawPoolMaterials() +: LLRenderPass(LLDrawPool::POOL_MATERIALS) +{ + +} + +void LLDrawPoolMaterials::prerender() +{ + mShaderLevel = LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_OBJECT); +} + +S32 LLDrawPoolMaterials::getNumDeferredPasses() +{ + // 12 render passes times 2 (one for each rigged and non rigged) + return 12*2; +} + +void LLDrawPoolMaterials::beginDeferredPass(S32 pass) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MATERIAL; + + bool rigged = false; + if (pass >= 12) + { + rigged = true; + pass -= 12; + } + U32 shader_idx[] = + { + 0, //LLRenderPass::PASS_MATERIAL, + //1, //LLRenderPass::PASS_MATERIAL_ALPHA, + 2, //LLRenderPass::PASS_MATERIAL_ALPHA_MASK, + 3, //LLRenderPass::PASS_MATERIAL_ALPHA_GLOW, + 4, //LLRenderPass::PASS_SPECMAP, + //5, //LLRenderPass::PASS_SPECMAP_BLEND, + 6, //LLRenderPass::PASS_SPECMAP_MASK, + 7, //LLRenderPass::PASS_SPECMAP_GLOW, + 8, //LLRenderPass::PASS_NORMMAP, + //9, //LLRenderPass::PASS_NORMMAP_BLEND, + 10, //LLRenderPass::PASS_NORMMAP_MASK, + 11, //LLRenderPass::PASS_NORMMAP_GLOW, + 12, //LLRenderPass::PASS_NORMSPEC, + //13, //LLRenderPass::PASS_NORMSPEC_BLEND, + 14, //LLRenderPass::PASS_NORMSPEC_MASK, + 15, //LLRenderPass::PASS_NORMSPEC_GLOW, + }; + + U32 idx = shader_idx[pass]; + + mShader = &(gDeferredMaterialProgram[idx]); + + if (rigged) + { + llassert(mShader->mRiggedVariant != nullptr); + mShader = mShader->mRiggedVariant; + } + + gPipeline.bindDeferredShader(*mShader); +} + +void LLDrawPoolMaterials::endDeferredPass(S32 pass) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MATERIAL; + + mShader->unbind(); + + LLRenderPass::endRenderPass(pass); +} + +void LLDrawPoolMaterials::renderDeferred(S32 pass) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MATERIAL; + static const U32 type_list[] = + { + LLRenderPass::PASS_MATERIAL, + //LLRenderPass::PASS_MATERIAL_ALPHA, + LLRenderPass::PASS_MATERIAL_ALPHA_MASK, + LLRenderPass::PASS_MATERIAL_ALPHA_EMISSIVE, + LLRenderPass::PASS_SPECMAP, + //LLRenderPass::PASS_SPECMAP_BLEND, + LLRenderPass::PASS_SPECMAP_MASK, + LLRenderPass::PASS_SPECMAP_EMISSIVE, + LLRenderPass::PASS_NORMMAP, + //LLRenderPass::PASS_NORMMAP_BLEND, + LLRenderPass::PASS_NORMMAP_MASK, + LLRenderPass::PASS_NORMMAP_EMISSIVE, + LLRenderPass::PASS_NORMSPEC, + //LLRenderPass::PASS_NORMSPEC_BLEND, + LLRenderPass::PASS_NORMSPEC_MASK, + LLRenderPass::PASS_NORMSPEC_EMISSIVE, + }; + + bool rigged = false; + if (pass >= 12) + { + rigged = true; + pass -= 12; + } + + llassert(pass < sizeof(type_list)/sizeof(U32)); + + U32 type = type_list[pass]; + if (rigged) + { + type += 1; + } + + LLCullResult::drawinfo_iterator begin = gPipeline.beginRenderMap(type); + LLCullResult::drawinfo_iterator end = gPipeline.endRenderMap(type); + + F32 lastIntensity = 0.f; + F32 lastFullbright = 0.f; + F32 lastMinimumAlpha = 0.f; + LLVector4 lastSpecular = LLVector4(0, 0, 0, 0); + + GLint intensity = mShader->getUniformLocation(LLShaderMgr::ENVIRONMENT_INTENSITY); + GLint brightness = mShader->getUniformLocation(LLShaderMgr::EMISSIVE_BRIGHTNESS); + GLint minAlpha = mShader->getUniformLocation(LLShaderMgr::MINIMUM_ALPHA); + GLint specular = mShader->getUniformLocation(LLShaderMgr::SPECULAR_COLOR); + + GLint diffuseChannel = mShader->enableTexture(LLShaderMgr::DIFFUSE_MAP); + GLint specChannel = mShader->enableTexture(LLShaderMgr::SPECULAR_MAP); + GLint normChannel = mShader->enableTexture(LLShaderMgr::BUMP_MAP); + + LLTexture* lastNormalMap = nullptr; + LLTexture* lastSpecMap = nullptr; + LLTexture* lastDiffuse = nullptr; + + gGL.getTexUnit(diffuseChannel)->unbindFast(LLTexUnit::TT_TEXTURE); + + if (intensity > -1) + { + glUniform1f(intensity, lastIntensity); + } + + if (brightness > -1) + { + glUniform1f(brightness, lastFullbright); + } + + if (minAlpha > -1) + { + glUniform1f(minAlpha, lastMinimumAlpha); + } + + if (specular > -1) + { + glUniform4fv(specular, 1, lastSpecular.mV); + } + + LLVOAvatar* lastAvatar = nullptr; + + for (LLCullResult::drawinfo_iterator i = begin; i != end; ) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_MATERIAL("materials draw loop"); + LLDrawInfo& params = **i; + + LLCullResult::increment_iterator(i, end); + + if (specular > -1 && params.mSpecColor != lastSpecular) + { + lastSpecular = params.mSpecColor; + glUniform4fv(specular, 1, lastSpecular.mV); + } + + if (intensity != -1 && lastIntensity != params.mEnvIntensity) + { + lastIntensity = params.mEnvIntensity; + glUniform1f(intensity, lastIntensity); + } + + if (minAlpha > -1 && lastMinimumAlpha != params.mAlphaMaskCutoff) + { + lastMinimumAlpha = params.mAlphaMaskCutoff; + glUniform1f(minAlpha, lastMinimumAlpha); + } + + F32 fullbright = params.mFullbright ? 1.f : 0.f; + if (brightness > -1 && lastFullbright != fullbright) + { + lastFullbright = fullbright; + glUniform1f(brightness, lastFullbright); + } + + if (normChannel > -1 && params.mNormalMap != lastNormalMap) + { + lastNormalMap = params.mNormalMap; + llassert(lastNormalMap); + gGL.getTexUnit(normChannel)->bindFast(lastNormalMap); + } + + if (specChannel > -1 && params.mSpecularMap != lastSpecMap) + { + lastSpecMap = params.mSpecularMap; + llassert(lastSpecMap); + gGL.getTexUnit(specChannel)->bindFast(lastSpecMap); + } + + if (params.mTexture != lastDiffuse) + { + lastDiffuse = params.mTexture; + if (lastDiffuse) + { + gGL.getTexUnit(diffuseChannel)->bindFast(lastDiffuse); + } + else + { + gGL.getTexUnit(diffuseChannel)->unbindFast(LLTexUnit::TT_TEXTURE); + } + } + + // upload matrix palette to shader + if (rigged && params.mAvatar.notNull()) + { + if (params.mAvatar != lastAvatar) + { + const LLVOAvatar::MatrixPaletteCache& mpc = params.mAvatar->updateSkinInfoMatrixPalette(params.mSkinInfo); + U32 count = mpc.mMatrixPalette.size(); + + if (count == 0) + { + //skin info not loaded yet, don't render + return; + } + + mShader->uniformMatrix3x4fv(LLViewerShaderMgr::AVATAR_MATRIX, + count, + false, + (GLfloat*)&(mpc.mGLMp[0])); + } + } + + applyModelMatrix(params); + + bool tex_setup = false; + + //not batching textures or batch has only 1 texture -- might need a texture matrix + if (params.mTextureMatrix) + { + gGL.getTexUnit(0)->activate(); + gGL.matrixMode(LLRender::MM_TEXTURE); + + gGL.loadMatrix((GLfloat*)params.mTextureMatrix->mMatrix); + gPipeline.mTextureMatrixOps++; + + tex_setup = true; + } + + /*if (params.mGroup) // TOO LATE + { + params.mGroup->rebuildMesh(); + }*/ + + params.mVertexBuffer->setBuffer(); + params.mVertexBuffer->drawRange(LLRender::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset); + + if (tex_setup) + { + gGL.getTexUnit(0)->activate(); + gGL.loadIdentity(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + } + } +} diff --git a/indra/newview/lldrawpooltree.cpp b/indra/newview/lldrawpooltree.cpp index dbe8c9934f..5694a77c9b 100644 --- a/indra/newview/lldrawpooltree.cpp +++ b/indra/newview/lldrawpooltree.cpp @@ -1,164 +1,164 @@ -/** - * @file lldrawpooltree.cpp - * @brief LLDrawPoolTree class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lldrawpooltree.h" - -#include "lldrawable.h" -#include "llface.h" -#include "llsky.h" -#include "llvotree.h" -#include "pipeline.h" -#include "llviewercamera.h" -#include "llviewershadermgr.h" -#include "llrender.h" -#include "llviewercontrol.h" -#include "llviewerregion.h" -#include "llenvironment.h" - -S32 LLDrawPoolTree::sDiffTex = 0; -static LLGLSLShader* shader = NULL; - -LLDrawPoolTree::LLDrawPoolTree(LLViewerTexture *texturep) : - LLFacePool(POOL_TREE), - mTexturep(texturep) -{ - mTexturep->setAddressMode(LLTexUnit::TAM_WRAP); -} - -//============================================ -// deferred implementation -//============================================ -void LLDrawPoolTree::beginDeferredPass(S32 pass) -{ - LL_RECORD_BLOCK_TIME(FTM_RENDER_TREES); - - shader = &gDeferredTreeProgram; - shader->bind(); - shader->setMinimumAlpha(0.5f); -} - -void LLDrawPoolTree::renderDeferred(S32 pass) -{ - LL_PROFILE_ZONE_SCOPED; - - if (mDrawFace.empty()) - { - return; - } - - - gGL.getTexUnit(sDiffTex)->bindFast(mTexturep); - mTexturep->addTextureStats(1024.f * 1024.f); // <=== keep Linden tree textures at full res - - for (std::vector::iterator iter = mDrawFace.begin(); - iter != mDrawFace.end(); iter++) - { - LLFace* face = *iter; - LLVertexBuffer* buff = face->getVertexBuffer(); - - if (buff) - { - LLMatrix4* model_matrix = &(face->getDrawable()->getRegion()->mRenderMatrix); - - if (model_matrix != gGLLastMatrix) - { - gGLLastMatrix = model_matrix; - gGL.loadMatrix(gGLModelView); - if (model_matrix) - { - llassert(gGL.getMatrixMode() == LLRender::MM_MODELVIEW); - gGL.multMatrix((GLfloat*)model_matrix->mMatrix); - } - gPipeline.mMatrixOpCount++; - } - - buff->setBuffer(); - buff->drawRange(LLRender::TRIANGLES, 0, buff->getNumVerts() - 1, buff->getNumIndices(), 0); - } - } -} - -void LLDrawPoolTree::endDeferredPass(S32 pass) -{ - LL_RECORD_BLOCK_TIME(FTM_RENDER_TREES); - - shader->unbind(); -} - -//============================================ -// shadow implementation -//============================================ -void LLDrawPoolTree::beginShadowPass(S32 pass) -{ - LL_PROFILE_ZONE_SCOPED; - - glPolygonOffset(gSavedSettings.getF32("RenderDeferredTreeShadowOffset"), - gSavedSettings.getF32("RenderDeferredTreeShadowBias")); - - LLEnvironment& environment = LLEnvironment::instance(); - - gDeferredTreeShadowProgram.bind(); - gDeferredTreeShadowProgram.uniform1i(LLShaderMgr::SUN_UP_FACTOR, environment.getIsSunUp() ? 1 : 0); - gDeferredTreeShadowProgram.setMinimumAlpha(0.5f); -} - -void LLDrawPoolTree::renderShadow(S32 pass) -{ - renderDeferred(pass); -} - -void LLDrawPoolTree::endShadowPass(S32 pass) -{ - LL_PROFILE_ZONE_SCOPED; - - glPolygonOffset(gSavedSettings.getF32("RenderDeferredSpotShadowOffset"), - gSavedSettings.getF32("RenderDeferredSpotShadowBias")); - gDeferredTreeShadowProgram.unbind(); -} - -bool LLDrawPoolTree::verify() const -{ - return true; -} - -LLViewerTexture *LLDrawPoolTree::getTexture() -{ - return mTexturep; -} - -LLViewerTexture *LLDrawPoolTree::getDebugTexture() -{ - return mTexturep; -} - - -LLColor3 LLDrawPoolTree::getDebugColor() const -{ - return LLColor3(1.f, 0.f, 1.f); -} - +/** + * @file lldrawpooltree.cpp + * @brief LLDrawPoolTree class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lldrawpooltree.h" + +#include "lldrawable.h" +#include "llface.h" +#include "llsky.h" +#include "llvotree.h" +#include "pipeline.h" +#include "llviewercamera.h" +#include "llviewershadermgr.h" +#include "llrender.h" +#include "llviewercontrol.h" +#include "llviewerregion.h" +#include "llenvironment.h" + +S32 LLDrawPoolTree::sDiffTex = 0; +static LLGLSLShader* shader = NULL; + +LLDrawPoolTree::LLDrawPoolTree(LLViewerTexture *texturep) : + LLFacePool(POOL_TREE), + mTexturep(texturep) +{ + mTexturep->setAddressMode(LLTexUnit::TAM_WRAP); +} + +//============================================ +// deferred implementation +//============================================ +void LLDrawPoolTree::beginDeferredPass(S32 pass) +{ + LL_RECORD_BLOCK_TIME(FTM_RENDER_TREES); + + shader = &gDeferredTreeProgram; + shader->bind(); + shader->setMinimumAlpha(0.5f); +} + +void LLDrawPoolTree::renderDeferred(S32 pass) +{ + LL_PROFILE_ZONE_SCOPED; + + if (mDrawFace.empty()) + { + return; + } + + + gGL.getTexUnit(sDiffTex)->bindFast(mTexturep); + mTexturep->addTextureStats(1024.f * 1024.f); // <=== keep Linden tree textures at full res + + for (std::vector::iterator iter = mDrawFace.begin(); + iter != mDrawFace.end(); iter++) + { + LLFace* face = *iter; + LLVertexBuffer* buff = face->getVertexBuffer(); + + if (buff) + { + LLMatrix4* model_matrix = &(face->getDrawable()->getRegion()->mRenderMatrix); + + if (model_matrix != gGLLastMatrix) + { + gGLLastMatrix = model_matrix; + gGL.loadMatrix(gGLModelView); + if (model_matrix) + { + llassert(gGL.getMatrixMode() == LLRender::MM_MODELVIEW); + gGL.multMatrix((GLfloat*)model_matrix->mMatrix); + } + gPipeline.mMatrixOpCount++; + } + + buff->setBuffer(); + buff->drawRange(LLRender::TRIANGLES, 0, buff->getNumVerts() - 1, buff->getNumIndices(), 0); + } + } +} + +void LLDrawPoolTree::endDeferredPass(S32 pass) +{ + LL_RECORD_BLOCK_TIME(FTM_RENDER_TREES); + + shader->unbind(); +} + +//============================================ +// shadow implementation +//============================================ +void LLDrawPoolTree::beginShadowPass(S32 pass) +{ + LL_PROFILE_ZONE_SCOPED; + + glPolygonOffset(gSavedSettings.getF32("RenderDeferredTreeShadowOffset"), + gSavedSettings.getF32("RenderDeferredTreeShadowBias")); + + LLEnvironment& environment = LLEnvironment::instance(); + + gDeferredTreeShadowProgram.bind(); + gDeferredTreeShadowProgram.uniform1i(LLShaderMgr::SUN_UP_FACTOR, environment.getIsSunUp() ? 1 : 0); + gDeferredTreeShadowProgram.setMinimumAlpha(0.5f); +} + +void LLDrawPoolTree::renderShadow(S32 pass) +{ + renderDeferred(pass); +} + +void LLDrawPoolTree::endShadowPass(S32 pass) +{ + LL_PROFILE_ZONE_SCOPED; + + glPolygonOffset(gSavedSettings.getF32("RenderDeferredSpotShadowOffset"), + gSavedSettings.getF32("RenderDeferredSpotShadowBias")); + gDeferredTreeShadowProgram.unbind(); +} + +bool LLDrawPoolTree::verify() const +{ + return true; +} + +LLViewerTexture *LLDrawPoolTree::getTexture() +{ + return mTexturep; +} + +LLViewerTexture *LLDrawPoolTree::getDebugTexture() +{ + return mTexturep; +} + + +LLColor3 LLDrawPoolTree::getDebugColor() const +{ + return LLColor3(1.f, 0.f, 1.f); +} + diff --git a/indra/newview/lldrawpooltree.h b/indra/newview/lldrawpooltree.h index fa2bd0c894..b13ae6c16d 100644 --- a/indra/newview/lldrawpooltree.h +++ b/indra/newview/lldrawpooltree.h @@ -1,66 +1,66 @@ -/** - * @file lldrawpooltree.h - * @brief LLDrawPoolTree class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLDRAWPOOLTREE_H -#define LL_LLDRAWPOOLTREE_H - -#include "lldrawpool.h" - -class LLDrawPoolTree : public LLFacePool -{ - LLPointer mTexturep; -public: - enum - { - VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX | - LLVertexBuffer::MAP_NORMAL | - LLVertexBuffer::MAP_COLOR | - LLVertexBuffer::MAP_TEXCOORD0 - }; - - virtual U32 getVertexDataMask() { return VERTEX_DATA_MASK; } - - LLDrawPoolTree(LLViewerTexture *texturep); - - /*virtual*/ S32 getNumDeferredPasses() { return 1; } - /*virtual*/ void beginDeferredPass(S32 pass); - /*virtual*/ void endDeferredPass(S32 pass); - /*virtual*/ void renderDeferred(S32 pass); - - /*virtual*/ S32 getNumShadowPasses() { return 1; } - /*virtual*/ void beginShadowPass(S32 pass); - /*virtual*/ void endShadowPass(S32 pass); - /*virtual*/ void renderShadow(S32 pass); - - /*virtual*/ bool verify() const; - /*virtual*/ LLViewerTexture *getTexture(); - /*virtual*/ LLViewerTexture *getDebugTexture(); - /*virtual*/ LLColor3 getDebugColor() const; // For AGP debug display - - static S32 sDiffTex; -}; - -#endif // LL_LLDRAWPOOLTREE_H +/** + * @file lldrawpooltree.h + * @brief LLDrawPoolTree class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLDRAWPOOLTREE_H +#define LL_LLDRAWPOOLTREE_H + +#include "lldrawpool.h" + +class LLDrawPoolTree : public LLFacePool +{ + LLPointer mTexturep; +public: + enum + { + VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX | + LLVertexBuffer::MAP_NORMAL | + LLVertexBuffer::MAP_COLOR | + LLVertexBuffer::MAP_TEXCOORD0 + }; + + virtual U32 getVertexDataMask() { return VERTEX_DATA_MASK; } + + LLDrawPoolTree(LLViewerTexture *texturep); + + /*virtual*/ S32 getNumDeferredPasses() { return 1; } + /*virtual*/ void beginDeferredPass(S32 pass); + /*virtual*/ void endDeferredPass(S32 pass); + /*virtual*/ void renderDeferred(S32 pass); + + /*virtual*/ S32 getNumShadowPasses() { return 1; } + /*virtual*/ void beginShadowPass(S32 pass); + /*virtual*/ void endShadowPass(S32 pass); + /*virtual*/ void renderShadow(S32 pass); + + /*virtual*/ bool verify() const; + /*virtual*/ LLViewerTexture *getTexture(); + /*virtual*/ LLViewerTexture *getDebugTexture(); + /*virtual*/ LLColor3 getDebugColor() const; // For AGP debug display + + static S32 sDiffTex; +}; + +#endif // LL_LLDRAWPOOLTREE_H diff --git a/indra/newview/lldrawpoolwater.cpp b/indra/newview/lldrawpoolwater.cpp index 7d86055543..793d84652b 100644 --- a/indra/newview/lldrawpoolwater.cpp +++ b/indra/newview/lldrawpoolwater.cpp @@ -1,361 +1,361 @@ -/** - * @file lldrawpoolwater.cpp - * @brief LLDrawPoolWater class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llfeaturemanager.h" -#include "lldrawpoolwater.h" - -#include "llviewercontrol.h" -#include "lldir.h" -#include "llerror.h" -#include "m3math.h" -#include "llrender.h" - -#include "llagent.h" // for gAgent for getRegion for getWaterHeight -#include "llcubemap.h" -#include "lldrawable.h" -#include "llface.h" -#include "llsky.h" -#include "llviewertexturelist.h" -#include "llviewerregion.h" -#include "llvowater.h" -#include "llworld.h" -#include "pipeline.h" -#include "llviewershadermgr.h" -#include "llenvironment.h" -#include "llsettingssky.h" -#include "llsettingswater.h" - -bool LLDrawPoolWater::sSkipScreenCopy = false; -bool LLDrawPoolWater::sNeedsReflectionUpdate = true; -bool LLDrawPoolWater::sNeedsDistortionUpdate = true; -F32 LLDrawPoolWater::sWaterFogEnd = 0.f; - -extern bool gCubeSnapshot; - -LLDrawPoolWater::LLDrawPoolWater() : LLFacePool(POOL_WATER) -{ -} - -LLDrawPoolWater::~LLDrawPoolWater() -{ -} - -void LLDrawPoolWater::setTransparentTextures(const LLUUID& transparentTextureId, const LLUUID& nextTransparentTextureId) -{ - LLSettingsWater::ptr_t pwater = LLEnvironment::instance().getCurrentWater(); - mWaterImagep[0] = LLViewerTextureManager::getFetchedTexture(!transparentTextureId.isNull() ? transparentTextureId : pwater->GetDefaultTransparentTextureAssetId()); - mWaterImagep[1] = LLViewerTextureManager::getFetchedTexture(!nextTransparentTextureId.isNull() ? nextTransparentTextureId : (!transparentTextureId.isNull() ? transparentTextureId : pwater->GetDefaultTransparentTextureAssetId())); - mWaterImagep[0]->addTextureStats(1024.f*1024.f); - mWaterImagep[1]->addTextureStats(1024.f*1024.f); -} - -void LLDrawPoolWater::setOpaqueTexture(const LLUUID& opaqueTextureId) -{ - LLSettingsWater::ptr_t pwater = LLEnvironment::instance().getCurrentWater(); - mOpaqueWaterImagep = LLViewerTextureManager::getFetchedTexture(opaqueTextureId); - mOpaqueWaterImagep->addTextureStats(1024.f*1024.f); -} - -void LLDrawPoolWater::setNormalMaps(const LLUUID& normalMapId, const LLUUID& nextNormalMapId) -{ - LLSettingsWater::ptr_t pwater = LLEnvironment::instance().getCurrentWater(); - mWaterNormp[0] = LLViewerTextureManager::getFetchedTexture(!normalMapId.isNull() ? normalMapId : pwater->GetDefaultWaterNormalAssetId()); - mWaterNormp[1] = LLViewerTextureManager::getFetchedTexture(!nextNormalMapId.isNull() ? nextNormalMapId : (!normalMapId.isNull() ? normalMapId : pwater->GetDefaultWaterNormalAssetId())); - mWaterNormp[0]->addTextureStats(1024.f*1024.f); - mWaterNormp[1]->addTextureStats(1024.f*1024.f); -} - -void LLDrawPoolWater::prerender() -{ - mShaderLevel = LLCubeMap::sUseCubeMaps ? LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_WATER) : 0; -} - -S32 LLDrawPoolWater::getNumPostDeferredPasses() -{ - if (LLViewerCamera::getInstance()->getOrigin().mV[2] < 1024.f) - { - return 1; - } - - return 0; -} - -void LLDrawPoolWater::beginPostDeferredPass(S32 pass) -{ - LL_PROFILE_GPU_ZONE("water beginPostDeferredPass") - gGL.setColorMask(true, true); - - if (LLPipeline::sRenderTransparentWater) - { - // copy framebuffer contents so far to a texture to be used for - // reflections and refractions - LLGLDepthTest depth(GL_TRUE, GL_TRUE, GL_ALWAYS); - - LLRenderTarget& src = gPipeline.mRT->screen; - LLRenderTarget& depth_src = gPipeline.mRT->deferredScreen; - LLRenderTarget& dst = gPipeline.mWaterDis; - - dst.bindTarget(); - gCopyDepthProgram.bind(); - - S32 diff_map = gCopyDepthProgram.getTextureChannel(LLShaderMgr::DIFFUSE_MAP); - S32 depth_map = gCopyDepthProgram.getTextureChannel(LLShaderMgr::DEFERRED_DEPTH); - - gGL.getTexUnit(diff_map)->bind(&src); - gGL.getTexUnit(depth_map)->bind(&depth_src, true); - - gPipeline.mScreenTriangleVB->setBuffer(); - gPipeline.mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - - dst.flush(); - } -} - -void LLDrawPoolWater::renderPostDeferred(S32 pass) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - LLGLDisable blend(GL_BLEND); - - gGL.setColorMask(true, true); - - LLColor3 light_diffuse(0, 0, 0); - F32 light_exp = 0.0f; - - LLEnvironment& environment = LLEnvironment::instance(); - LLSettingsWater::ptr_t pwater = environment.getCurrentWater(); - LLSettingsSky::ptr_t psky = environment.getCurrentSky(); - LLVector3 light_dir = environment.getLightDirection(); - bool sun_up = environment.getIsSunUp(); - bool moon_up = environment.getIsMoonUp(); - bool has_normal_mips = gSavedSettings.getBOOL("RenderWaterMipNormal"); - bool underwater = LLViewerCamera::getInstance()->cameraUnderWater(); - LLColor4 fog_color = LLColor4(pwater->getWaterFogColor(), 0.f); - LLColor3 fog_color_linear = linearColor3(fog_color); - - if (sun_up) - { - light_diffuse += psky->getSunlightColor(); - } - // moonlight is several orders of magnitude less bright than sunlight, - // so only use this color when the moon alone is showing - else if (moon_up) - { - light_diffuse += psky->getMoonlightColor(); - } - - // Apply magic numbers translating light direction into intensities - light_dir.normalize(); - F32 ground_proj_sq = light_dir.mV[0] * light_dir.mV[0] + light_dir.mV[1] * light_dir.mV[1]; - light_exp = llmax(32.f, 256.f * powf(ground_proj_sq, 16.0f)); - if (0.f < light_diffuse.normalize()) // Normalizing a color? Puzzling... - { - light_diffuse *= (1.5f + (6.f * ground_proj_sq)); - } - - // set up normal maps filtering - for (auto norm_map : mWaterNormp) - { - if (norm_map) norm_map->setFilteringOption(has_normal_mips ? LLTexUnit::TFO_ANISOTROPIC : LLTexUnit::TFO_POINT); - } - - LLColor4 specular(sun_up ? psky->getSunlightColor() : psky->getMoonlightColor()); - F32 phase_time = (F32) LLFrameTimer::getElapsedSeconds() * 0.5f; - LLGLSLShader *shader = nullptr; - - // two passes, first with standard water shader bound, second with edge water shader bound - for (int edge = 0; edge < 2; edge++) - { - // select shader - if (underwater) - { - shader = &gUnderWaterProgram; - } - else - { - if (edge) - { - shader = &gWaterEdgeProgram; - } - else - { - shader = &gWaterProgram; - } - } - - gPipeline.bindDeferredShader(*shader, nullptr, &gPipeline.mWaterDis); - - //bind normal map - S32 bumpTex = shader->enableTexture(LLViewerShaderMgr::BUMP_MAP); - S32 bumpTex2 = shader->enableTexture(LLViewerShaderMgr::BUMP_MAP2); - - LLViewerTexture* tex_a = mWaterNormp[0]; - LLViewerTexture* tex_b = mWaterNormp[1]; - - F32 blend_factor = pwater->getBlendFactor(); - - gGL.getTexUnit(bumpTex)->unbind(LLTexUnit::TT_TEXTURE); - gGL.getTexUnit(bumpTex2)->unbind(LLTexUnit::TT_TEXTURE); - - if (tex_a && (!tex_b || (tex_a == tex_b))) - { - gGL.getTexUnit(bumpTex)->bind(tex_a); - blend_factor = 0; // only one tex provided, no blending - } - else if (tex_b && !tex_a) - { - gGL.getTexUnit(bumpTex)->bind(tex_b); - blend_factor = 0; // only one tex provided, no blending - } - else if (tex_b != tex_a) - { - gGL.getTexUnit(bumpTex)->bind(tex_a); - gGL.getTexUnit(bumpTex2)->bind(tex_b); - } - - // bind reflection texture from RenderTarget - S32 screentex = shader->enableTexture(LLShaderMgr::WATER_SCREENTEX); - - F32 screenRes[] = { 1.f / gGLViewport[2], 1.f / gGLViewport[3] }; - - S32 diffTex = shader->enableTexture(LLShaderMgr::DIFFUSE_MAP); - - shader->uniform2fv(LLShaderMgr::DEFERRED_SCREEN_RES, 1, screenRes); - shader->uniform1f(LLShaderMgr::BLEND_FACTOR, blend_factor); - - F32 fog_density = pwater->getModifiedWaterFogDensity(underwater); - - if (screentex > -1) - { - shader->uniform1f(LLShaderMgr::WATER_FOGDENSITY, fog_density); - gGL.getTexUnit(screentex)->bind(&gPipeline.mWaterDis); - } - - if (mShaderLevel == 1) - { - fog_color.mV[VW] = log(fog_density) / log(2); - } - - F32 water_height = environment.getWaterHeight(); - F32 camera_height = LLViewerCamera::getInstance()->getOrigin().mV[2]; - shader->uniform1f(LLShaderMgr::WATER_WATERHEIGHT, camera_height - water_height); - shader->uniform1f(LLShaderMgr::WATER_TIME, phase_time); - shader->uniform3fv(LLShaderMgr::WATER_EYEVEC, 1, LLViewerCamera::getInstance()->getOrigin().mV); - - shader->uniform4fv(LLShaderMgr::SPECULAR_COLOR, 1, specular.mV); - shader->uniform4fv(LLShaderMgr::WATER_FOGCOLOR, 1, fog_color.mV); - shader->uniform3fv(LLShaderMgr::WATER_FOGCOLOR_LINEAR, 1, fog_color_linear.mV); - - shader->uniform3fv(LLShaderMgr::WATER_SPECULAR, 1, light_diffuse.mV); - shader->uniform1f(LLShaderMgr::WATER_SPECULAR_EXP, light_exp); - - shader->uniform2fv(LLShaderMgr::WATER_WAVE_DIR1, 1, pwater->getWave1Dir().mV); - shader->uniform2fv(LLShaderMgr::WATER_WAVE_DIR2, 1, pwater->getWave2Dir().mV); - - shader->uniform3fv(LLShaderMgr::WATER_LIGHT_DIR, 1, light_dir.mV); - - shader->uniform3fv(LLShaderMgr::WATER_NORM_SCALE, 1, pwater->getNormalScale().mV); - shader->uniform1f(LLShaderMgr::WATER_FRESNEL_SCALE, pwater->getFresnelScale()); - shader->uniform1f(LLShaderMgr::WATER_FRESNEL_OFFSET, pwater->getFresnelOffset()); - shader->uniform1f(LLShaderMgr::WATER_BLUR_MULTIPLIER, pwater->getBlurMultiplier()); - - F32 sunAngle = llmax(0.f, light_dir.mV[1]); - F32 scaledAngle = 1.f - sunAngle; - - shader->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up ? 1 : 0); - shader->uniform1f(LLShaderMgr::WATER_SUN_ANGLE, sunAngle); - shader->uniform1f(LLShaderMgr::WATER_SCALED_ANGLE, scaledAngle); - shader->uniform1f(LLShaderMgr::WATER_SUN_ANGLE2, 0.1f + 0.2f * sunAngle); - shader->uniform1i(LLShaderMgr::WATER_EDGE_FACTOR, edge ? 1 : 0); - - // SL-15861 This was changed from getRotatedLightNorm() as it was causing - // lightnorm in shaders\class1\windlight\atmosphericsFuncs.glsl in have inconsistent additive lighting for 180 degrees of the FOV. - LLVector4 rotated_light_direction = LLEnvironment::instance().getClampedLightNorm(); - shader->uniform3fv(LLViewerShaderMgr::LIGHTNORM, 1, rotated_light_direction.mV); - - shader->uniform3fv(LLShaderMgr::WL_CAMPOSLOCAL, 1, LLViewerCamera::getInstance()->getOrigin().mV); - - if (LLViewerCamera::getInstance()->cameraUnderWater()) - { - shader->uniform1f(LLShaderMgr::WATER_REFSCALE, pwater->getScaleBelow()); - } - else - { - shader->uniform1f(LLShaderMgr::WATER_REFSCALE, pwater->getScaleAbove()); - } - - LLGLDisable cullface(GL_CULL_FACE); - - LLVOWater* water = nullptr; - for (LLFace* const& face : mDrawFace) - { - if (!face) continue; - water = static_cast(face->getViewerObject()); - if (!water) continue; - - gGL.getTexUnit(diffTex)->bind(face->getTexture()); - - if ((bool)edge == (bool)water->getIsEdgePatch()) - { - face->renderIndexed(); - - // Note non-void water being drawn, updates required - if (!edge) // SL-16461 remove !LLPipeline::sUseOcclusion check - { - sNeedsReflectionUpdate = true; - sNeedsDistortionUpdate = true; - } - } - } - - shader->disableTexture(LLShaderMgr::ENVIRONMENT_MAP, LLTexUnit::TT_CUBE_MAP); - shader->disableTexture(LLShaderMgr::WATER_SCREENTEX); - shader->disableTexture(LLShaderMgr::BUMP_MAP); - shader->disableTexture(LLShaderMgr::DIFFUSE_MAP); - shader->disableTexture(LLShaderMgr::WATER_REFTEX); - - // clean up - gPipeline.unbindDeferredShader(*shader); - - gGL.getTexUnit(bumpTex)->unbind(LLTexUnit::TT_TEXTURE); - gGL.getTexUnit(bumpTex2)->unbind(LLTexUnit::TT_TEXTURE); - } - - gGL.getTexUnit(0)->activate(); - gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE); - - gGL.setColorMask(true, false); -} - -LLViewerTexture *LLDrawPoolWater::getDebugTexture() -{ - return LLViewerTextureManager::getFetchedTexture(IMG_SMOKE); -} - -LLColor3 LLDrawPoolWater::getDebugColor() const -{ - return LLColor3(0.f, 1.f, 1.f); -} +/** + * @file lldrawpoolwater.cpp + * @brief LLDrawPoolWater class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llfeaturemanager.h" +#include "lldrawpoolwater.h" + +#include "llviewercontrol.h" +#include "lldir.h" +#include "llerror.h" +#include "m3math.h" +#include "llrender.h" + +#include "llagent.h" // for gAgent for getRegion for getWaterHeight +#include "llcubemap.h" +#include "lldrawable.h" +#include "llface.h" +#include "llsky.h" +#include "llviewertexturelist.h" +#include "llviewerregion.h" +#include "llvowater.h" +#include "llworld.h" +#include "pipeline.h" +#include "llviewershadermgr.h" +#include "llenvironment.h" +#include "llsettingssky.h" +#include "llsettingswater.h" + +bool LLDrawPoolWater::sSkipScreenCopy = false; +bool LLDrawPoolWater::sNeedsReflectionUpdate = true; +bool LLDrawPoolWater::sNeedsDistortionUpdate = true; +F32 LLDrawPoolWater::sWaterFogEnd = 0.f; + +extern bool gCubeSnapshot; + +LLDrawPoolWater::LLDrawPoolWater() : LLFacePool(POOL_WATER) +{ +} + +LLDrawPoolWater::~LLDrawPoolWater() +{ +} + +void LLDrawPoolWater::setTransparentTextures(const LLUUID& transparentTextureId, const LLUUID& nextTransparentTextureId) +{ + LLSettingsWater::ptr_t pwater = LLEnvironment::instance().getCurrentWater(); + mWaterImagep[0] = LLViewerTextureManager::getFetchedTexture(!transparentTextureId.isNull() ? transparentTextureId : pwater->GetDefaultTransparentTextureAssetId()); + mWaterImagep[1] = LLViewerTextureManager::getFetchedTexture(!nextTransparentTextureId.isNull() ? nextTransparentTextureId : (!transparentTextureId.isNull() ? transparentTextureId : pwater->GetDefaultTransparentTextureAssetId())); + mWaterImagep[0]->addTextureStats(1024.f*1024.f); + mWaterImagep[1]->addTextureStats(1024.f*1024.f); +} + +void LLDrawPoolWater::setOpaqueTexture(const LLUUID& opaqueTextureId) +{ + LLSettingsWater::ptr_t pwater = LLEnvironment::instance().getCurrentWater(); + mOpaqueWaterImagep = LLViewerTextureManager::getFetchedTexture(opaqueTextureId); + mOpaqueWaterImagep->addTextureStats(1024.f*1024.f); +} + +void LLDrawPoolWater::setNormalMaps(const LLUUID& normalMapId, const LLUUID& nextNormalMapId) +{ + LLSettingsWater::ptr_t pwater = LLEnvironment::instance().getCurrentWater(); + mWaterNormp[0] = LLViewerTextureManager::getFetchedTexture(!normalMapId.isNull() ? normalMapId : pwater->GetDefaultWaterNormalAssetId()); + mWaterNormp[1] = LLViewerTextureManager::getFetchedTexture(!nextNormalMapId.isNull() ? nextNormalMapId : (!normalMapId.isNull() ? normalMapId : pwater->GetDefaultWaterNormalAssetId())); + mWaterNormp[0]->addTextureStats(1024.f*1024.f); + mWaterNormp[1]->addTextureStats(1024.f*1024.f); +} + +void LLDrawPoolWater::prerender() +{ + mShaderLevel = LLCubeMap::sUseCubeMaps ? LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_WATER) : 0; +} + +S32 LLDrawPoolWater::getNumPostDeferredPasses() +{ + if (LLViewerCamera::getInstance()->getOrigin().mV[2] < 1024.f) + { + return 1; + } + + return 0; +} + +void LLDrawPoolWater::beginPostDeferredPass(S32 pass) +{ + LL_PROFILE_GPU_ZONE("water beginPostDeferredPass") + gGL.setColorMask(true, true); + + if (LLPipeline::sRenderTransparentWater) + { + // copy framebuffer contents so far to a texture to be used for + // reflections and refractions + LLGLDepthTest depth(GL_TRUE, GL_TRUE, GL_ALWAYS); + + LLRenderTarget& src = gPipeline.mRT->screen; + LLRenderTarget& depth_src = gPipeline.mRT->deferredScreen; + LLRenderTarget& dst = gPipeline.mWaterDis; + + dst.bindTarget(); + gCopyDepthProgram.bind(); + + S32 diff_map = gCopyDepthProgram.getTextureChannel(LLShaderMgr::DIFFUSE_MAP); + S32 depth_map = gCopyDepthProgram.getTextureChannel(LLShaderMgr::DEFERRED_DEPTH); + + gGL.getTexUnit(diff_map)->bind(&src); + gGL.getTexUnit(depth_map)->bind(&depth_src, true); + + gPipeline.mScreenTriangleVB->setBuffer(); + gPipeline.mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + + dst.flush(); + } +} + +void LLDrawPoolWater::renderPostDeferred(S32 pass) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + LLGLDisable blend(GL_BLEND); + + gGL.setColorMask(true, true); + + LLColor3 light_diffuse(0, 0, 0); + F32 light_exp = 0.0f; + + LLEnvironment& environment = LLEnvironment::instance(); + LLSettingsWater::ptr_t pwater = environment.getCurrentWater(); + LLSettingsSky::ptr_t psky = environment.getCurrentSky(); + LLVector3 light_dir = environment.getLightDirection(); + bool sun_up = environment.getIsSunUp(); + bool moon_up = environment.getIsMoonUp(); + bool has_normal_mips = gSavedSettings.getBOOL("RenderWaterMipNormal"); + bool underwater = LLViewerCamera::getInstance()->cameraUnderWater(); + LLColor4 fog_color = LLColor4(pwater->getWaterFogColor(), 0.f); + LLColor3 fog_color_linear = linearColor3(fog_color); + + if (sun_up) + { + light_diffuse += psky->getSunlightColor(); + } + // moonlight is several orders of magnitude less bright than sunlight, + // so only use this color when the moon alone is showing + else if (moon_up) + { + light_diffuse += psky->getMoonlightColor(); + } + + // Apply magic numbers translating light direction into intensities + light_dir.normalize(); + F32 ground_proj_sq = light_dir.mV[0] * light_dir.mV[0] + light_dir.mV[1] * light_dir.mV[1]; + light_exp = llmax(32.f, 256.f * powf(ground_proj_sq, 16.0f)); + if (0.f < light_diffuse.normalize()) // Normalizing a color? Puzzling... + { + light_diffuse *= (1.5f + (6.f * ground_proj_sq)); + } + + // set up normal maps filtering + for (auto norm_map : mWaterNormp) + { + if (norm_map) norm_map->setFilteringOption(has_normal_mips ? LLTexUnit::TFO_ANISOTROPIC : LLTexUnit::TFO_POINT); + } + + LLColor4 specular(sun_up ? psky->getSunlightColor() : psky->getMoonlightColor()); + F32 phase_time = (F32) LLFrameTimer::getElapsedSeconds() * 0.5f; + LLGLSLShader *shader = nullptr; + + // two passes, first with standard water shader bound, second with edge water shader bound + for (int edge = 0; edge < 2; edge++) + { + // select shader + if (underwater) + { + shader = &gUnderWaterProgram; + } + else + { + if (edge) + { + shader = &gWaterEdgeProgram; + } + else + { + shader = &gWaterProgram; + } + } + + gPipeline.bindDeferredShader(*shader, nullptr, &gPipeline.mWaterDis); + + //bind normal map + S32 bumpTex = shader->enableTexture(LLViewerShaderMgr::BUMP_MAP); + S32 bumpTex2 = shader->enableTexture(LLViewerShaderMgr::BUMP_MAP2); + + LLViewerTexture* tex_a = mWaterNormp[0]; + LLViewerTexture* tex_b = mWaterNormp[1]; + + F32 blend_factor = pwater->getBlendFactor(); + + gGL.getTexUnit(bumpTex)->unbind(LLTexUnit::TT_TEXTURE); + gGL.getTexUnit(bumpTex2)->unbind(LLTexUnit::TT_TEXTURE); + + if (tex_a && (!tex_b || (tex_a == tex_b))) + { + gGL.getTexUnit(bumpTex)->bind(tex_a); + blend_factor = 0; // only one tex provided, no blending + } + else if (tex_b && !tex_a) + { + gGL.getTexUnit(bumpTex)->bind(tex_b); + blend_factor = 0; // only one tex provided, no blending + } + else if (tex_b != tex_a) + { + gGL.getTexUnit(bumpTex)->bind(tex_a); + gGL.getTexUnit(bumpTex2)->bind(tex_b); + } + + // bind reflection texture from RenderTarget + S32 screentex = shader->enableTexture(LLShaderMgr::WATER_SCREENTEX); + + F32 screenRes[] = { 1.f / gGLViewport[2], 1.f / gGLViewport[3] }; + + S32 diffTex = shader->enableTexture(LLShaderMgr::DIFFUSE_MAP); + + shader->uniform2fv(LLShaderMgr::DEFERRED_SCREEN_RES, 1, screenRes); + shader->uniform1f(LLShaderMgr::BLEND_FACTOR, blend_factor); + + F32 fog_density = pwater->getModifiedWaterFogDensity(underwater); + + if (screentex > -1) + { + shader->uniform1f(LLShaderMgr::WATER_FOGDENSITY, fog_density); + gGL.getTexUnit(screentex)->bind(&gPipeline.mWaterDis); + } + + if (mShaderLevel == 1) + { + fog_color.mV[VW] = log(fog_density) / log(2); + } + + F32 water_height = environment.getWaterHeight(); + F32 camera_height = LLViewerCamera::getInstance()->getOrigin().mV[2]; + shader->uniform1f(LLShaderMgr::WATER_WATERHEIGHT, camera_height - water_height); + shader->uniform1f(LLShaderMgr::WATER_TIME, phase_time); + shader->uniform3fv(LLShaderMgr::WATER_EYEVEC, 1, LLViewerCamera::getInstance()->getOrigin().mV); + + shader->uniform4fv(LLShaderMgr::SPECULAR_COLOR, 1, specular.mV); + shader->uniform4fv(LLShaderMgr::WATER_FOGCOLOR, 1, fog_color.mV); + shader->uniform3fv(LLShaderMgr::WATER_FOGCOLOR_LINEAR, 1, fog_color_linear.mV); + + shader->uniform3fv(LLShaderMgr::WATER_SPECULAR, 1, light_diffuse.mV); + shader->uniform1f(LLShaderMgr::WATER_SPECULAR_EXP, light_exp); + + shader->uniform2fv(LLShaderMgr::WATER_WAVE_DIR1, 1, pwater->getWave1Dir().mV); + shader->uniform2fv(LLShaderMgr::WATER_WAVE_DIR2, 1, pwater->getWave2Dir().mV); + + shader->uniform3fv(LLShaderMgr::WATER_LIGHT_DIR, 1, light_dir.mV); + + shader->uniform3fv(LLShaderMgr::WATER_NORM_SCALE, 1, pwater->getNormalScale().mV); + shader->uniform1f(LLShaderMgr::WATER_FRESNEL_SCALE, pwater->getFresnelScale()); + shader->uniform1f(LLShaderMgr::WATER_FRESNEL_OFFSET, pwater->getFresnelOffset()); + shader->uniform1f(LLShaderMgr::WATER_BLUR_MULTIPLIER, pwater->getBlurMultiplier()); + + F32 sunAngle = llmax(0.f, light_dir.mV[1]); + F32 scaledAngle = 1.f - sunAngle; + + shader->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up ? 1 : 0); + shader->uniform1f(LLShaderMgr::WATER_SUN_ANGLE, sunAngle); + shader->uniform1f(LLShaderMgr::WATER_SCALED_ANGLE, scaledAngle); + shader->uniform1f(LLShaderMgr::WATER_SUN_ANGLE2, 0.1f + 0.2f * sunAngle); + shader->uniform1i(LLShaderMgr::WATER_EDGE_FACTOR, edge ? 1 : 0); + + // SL-15861 This was changed from getRotatedLightNorm() as it was causing + // lightnorm in shaders\class1\windlight\atmosphericsFuncs.glsl in have inconsistent additive lighting for 180 degrees of the FOV. + LLVector4 rotated_light_direction = LLEnvironment::instance().getClampedLightNorm(); + shader->uniform3fv(LLViewerShaderMgr::LIGHTNORM, 1, rotated_light_direction.mV); + + shader->uniform3fv(LLShaderMgr::WL_CAMPOSLOCAL, 1, LLViewerCamera::getInstance()->getOrigin().mV); + + if (LLViewerCamera::getInstance()->cameraUnderWater()) + { + shader->uniform1f(LLShaderMgr::WATER_REFSCALE, pwater->getScaleBelow()); + } + else + { + shader->uniform1f(LLShaderMgr::WATER_REFSCALE, pwater->getScaleAbove()); + } + + LLGLDisable cullface(GL_CULL_FACE); + + LLVOWater* water = nullptr; + for (LLFace* const& face : mDrawFace) + { + if (!face) continue; + water = static_cast(face->getViewerObject()); + if (!water) continue; + + gGL.getTexUnit(diffTex)->bind(face->getTexture()); + + if ((bool)edge == (bool)water->getIsEdgePatch()) + { + face->renderIndexed(); + + // Note non-void water being drawn, updates required + if (!edge) // SL-16461 remove !LLPipeline::sUseOcclusion check + { + sNeedsReflectionUpdate = true; + sNeedsDistortionUpdate = true; + } + } + } + + shader->disableTexture(LLShaderMgr::ENVIRONMENT_MAP, LLTexUnit::TT_CUBE_MAP); + shader->disableTexture(LLShaderMgr::WATER_SCREENTEX); + shader->disableTexture(LLShaderMgr::BUMP_MAP); + shader->disableTexture(LLShaderMgr::DIFFUSE_MAP); + shader->disableTexture(LLShaderMgr::WATER_REFTEX); + + // clean up + gPipeline.unbindDeferredShader(*shader); + + gGL.getTexUnit(bumpTex)->unbind(LLTexUnit::TT_TEXTURE); + gGL.getTexUnit(bumpTex2)->unbind(LLTexUnit::TT_TEXTURE); + } + + gGL.getTexUnit(0)->activate(); + gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE); + + gGL.setColorMask(true, false); +} + +LLViewerTexture *LLDrawPoolWater::getDebugTexture() +{ + return LLViewerTextureManager::getFetchedTexture(IMG_SMOKE); +} + +LLColor3 LLDrawPoolWater::getDebugColor() const +{ + return LLColor3(0.f, 1.f, 1.f); +} diff --git a/indra/newview/lldrawpoolwater.h b/indra/newview/lldrawpoolwater.h index df678b14e5..f64477a059 100644 --- a/indra/newview/lldrawpoolwater.h +++ b/indra/newview/lldrawpoolwater.h @@ -1,83 +1,83 @@ -/** - * @file lldrawpoolwater.h - * @brief LLDrawPoolWater class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLDRAWPOOLWATER_H -#define LL_LLDRAWPOOLWATER_H - -#include "lldrawpool.h" - - -class LLFace; -class LLHeavenBody; -class LLWaterSurface; -class LLGLSLShader; - -class LLDrawPoolWater final: public LLFacePool -{ -protected: - LLPointer mWaterImagep[2]; - LLPointer mWaterNormp[2]; - - LLPointer mOpaqueWaterImagep; - -public: - static bool sSkipScreenCopy; - static bool sNeedsReflectionUpdate; - static bool sNeedsDistortionUpdate; - static F32 sWaterFogEnd; - - enum - { - VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX | - LLVertexBuffer::MAP_NORMAL | - LLVertexBuffer::MAP_TEXCOORD0 - }; - - virtual U32 getVertexDataMask() override { return VERTEX_DATA_MASK; } - - LLDrawPoolWater(); - /*virtual*/ ~LLDrawPoolWater(); - - S32 getNumPostDeferredPasses() override; - void beginPostDeferredPass(S32 pass) override; - void renderPostDeferred(S32 pass) override; - - void prerender() override; - - LLViewerTexture *getDebugTexture() override; - LLColor3 getDebugColor() const; // For AGP debug display - - void setTransparentTextures(const LLUUID& transparentTextureId, const LLUUID& nextTransparentTextureId); - void setOpaqueTexture(const LLUUID& opaqueTextureId); - void setNormalMaps(const LLUUID& normalMapId, const LLUUID& nextNormalMapId); - -protected: - void renderOpaqueLegacyWater(); -}; - -void cgErrorCallback(); - -#endif // LL_LLDRAWPOOLWATER_H +/** + * @file lldrawpoolwater.h + * @brief LLDrawPoolWater class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLDRAWPOOLWATER_H +#define LL_LLDRAWPOOLWATER_H + +#include "lldrawpool.h" + + +class LLFace; +class LLHeavenBody; +class LLWaterSurface; +class LLGLSLShader; + +class LLDrawPoolWater final: public LLFacePool +{ +protected: + LLPointer mWaterImagep[2]; + LLPointer mWaterNormp[2]; + + LLPointer mOpaqueWaterImagep; + +public: + static bool sSkipScreenCopy; + static bool sNeedsReflectionUpdate; + static bool sNeedsDistortionUpdate; + static F32 sWaterFogEnd; + + enum + { + VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX | + LLVertexBuffer::MAP_NORMAL | + LLVertexBuffer::MAP_TEXCOORD0 + }; + + virtual U32 getVertexDataMask() override { return VERTEX_DATA_MASK; } + + LLDrawPoolWater(); + /*virtual*/ ~LLDrawPoolWater(); + + S32 getNumPostDeferredPasses() override; + void beginPostDeferredPass(S32 pass) override; + void renderPostDeferred(S32 pass) override; + + void prerender() override; + + LLViewerTexture *getDebugTexture() override; + LLColor3 getDebugColor() const; // For AGP debug display + + void setTransparentTextures(const LLUUID& transparentTextureId, const LLUUID& nextTransparentTextureId); + void setOpaqueTexture(const LLUUID& opaqueTextureId); + void setNormalMaps(const LLUUID& normalMapId, const LLUUID& nextNormalMapId); + +protected: + void renderOpaqueLegacyWater(); +}; + +void cgErrorCallback(); + +#endif // LL_LLDRAWPOOLWATER_H diff --git a/indra/newview/lldrawpoolwlsky.cpp b/indra/newview/lldrawpoolwlsky.cpp index 95ab8e9218..0eeabdd795 100644 --- a/indra/newview/lldrawpoolwlsky.cpp +++ b/indra/newview/lldrawpoolwlsky.cpp @@ -1,481 +1,481 @@ -/** - * @file lldrawpoolwlsky.cpp - * @brief LLDrawPoolWLSky class implementation - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lldrawpoolwlsky.h" - -#include "llerror.h" -#include "llface.h" -#include "llimage.h" -#include "llrender.h" -#include "llatmosphere.h" -#include "llenvironment.h" -#include "llglslshader.h" -#include "llgl.h" - -#include "llviewerregion.h" -#include "llviewershadermgr.h" -#include "llviewercamera.h" -#include "pipeline.h" -#include "llsky.h" -#include "llvowlsky.h" -#include "llsettingsvo.h" - -extern bool gCubeSnapshot; - -static LLStaticHashedString sCamPosLocal("camPosLocal"); -static LLStaticHashedString sCustomAlpha("custom_alpha"); - -static LLGLSLShader* cloud_shader = NULL; -static LLGLSLShader* sky_shader = NULL; -static LLGLSLShader* sun_shader = NULL; -static LLGLSLShader* moon_shader = NULL; - -static float sStarTime; - -LLDrawPoolWLSky::LLDrawPoolWLSky(void) : - LLDrawPool(POOL_WL_SKY) -{ -} - -LLDrawPoolWLSky::~LLDrawPoolWLSky() -{ -} - -LLViewerTexture *LLDrawPoolWLSky::getDebugTexture() -{ - return NULL; -} - -void LLDrawPoolWLSky::beginDeferredPass(S32 pass) -{ - sky_shader = &gDeferredWLSkyProgram; - cloud_shader = &gDeferredWLCloudProgram; - - sun_shader = &gDeferredWLSunProgram; - - moon_shader = &gDeferredWLMoonProgram; -} - -void LLDrawPoolWLSky::endDeferredPass(S32 pass) -{ - sky_shader = nullptr; - cloud_shader = nullptr; - sun_shader = nullptr; - moon_shader = nullptr; - - // clear the depth buffer so haze shaders can use unwritten depth as a mask - glClear(GL_DEPTH_BUFFER_BIT); -} - -void LLDrawPoolWLSky::renderDome(const LLVector3& camPosLocal, F32 camHeightLocal, LLGLSLShader * shader) const -{ - llassert_always(NULL != shader); - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - - //chop off translation - if (LLPipeline::sReflectionRender && camPosLocal.mV[2] > 256.f) - { - gGL.translatef(camPosLocal.mV[0], camPosLocal.mV[1], 256.f-camPosLocal.mV[2]*0.5f); - } - else - { - gGL.translatef(camPosLocal.mV[0], camPosLocal.mV[1], camPosLocal.mV[2]); - } - - - // the windlight sky dome works most conveniently in a coordinate system - // where Y is up, so permute our basis vectors accordingly. - gGL.rotatef(120.f, 1.f / F_SQRT3, 1.f / F_SQRT3, 1.f / F_SQRT3); - - gGL.scalef(0.333f, 0.333f, 0.333f); - - gGL.translatef(0.f,-camHeightLocal, 0.f); - - // Draw WL Sky - shader->uniform3f(sCamPosLocal, 0.f, camHeightLocal, 0.f); - - gSky.mVOWLSkyp->drawDome(); - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.popMatrix(); -} - -void LLDrawPoolWLSky::renderSkyHazeDeferred(const LLVector3& camPosLocal, F32 camHeightLocal) const -{ - if (!gSky.mVOSkyp) - { - return; - } - - LLVector3 const & origin = LLViewerCamera::getInstance()->getOrigin(); - - if (gPipeline.canUseWindLightShaders() && gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_SKY)) - { - LLGLSPipelineDepthTestSkyBox sky(true, true); - - sky_shader->bind(); - - sky_shader->uniform1i(LLShaderMgr::CUBE_SNAPSHOT, gCubeSnapshot ? 1 : 0); - - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - - LLViewerTexture* rainbow_tex = gSky.mVOSkyp->getRainbowTex(); - LLViewerTexture* halo_tex = gSky.mVOSkyp->getHaloTex(); - - sky_shader->bindTexture(LLShaderMgr::RAINBOW_MAP, rainbow_tex); - sky_shader->bindTexture(LLShaderMgr::HALO_MAP, halo_tex); - - F32 moisture_level = (float)psky->getSkyMoistureLevel(); - F32 droplet_radius = (float)psky->getSkyDropletRadius(); - F32 ice_level = (float)psky->getSkyIceLevel(); - - // hobble halos and rainbows when there's no light source to generate them - if (!psky->getIsSunUp() && !psky->getIsMoonUp()) - { - moisture_level = 0.0f; - ice_level = 0.0f; - } - - sky_shader->uniform1f(LLShaderMgr::MOISTURE_LEVEL, moisture_level); - sky_shader->uniform1f(LLShaderMgr::DROPLET_RADIUS, droplet_radius); - sky_shader->uniform1f(LLShaderMgr::ICE_LEVEL, ice_level); - - sky_shader->uniform1f(LLShaderMgr::SUN_MOON_GLOW_FACTOR, psky->getSunMoonGlowFactor()); - - sky_shader->uniform1i(LLShaderMgr::SUN_UP_FACTOR, psky->getIsSunUp() ? 1 : 0); - - /// Render the skydome - renderDome(origin, camHeightLocal, sky_shader); - - sky_shader->unbind(); - } -} - -void LLDrawPoolWLSky::renderStarsDeferred(const LLVector3& camPosLocal) const -{ - if (!gSky.mVOSkyp) - { - return; - } - - LLGLSPipelineBlendSkyBox gls_sky(true, false); - - gGL.setSceneBlendType(LLRender::BT_ADD_WITH_ALPHA); - - F32 star_alpha = LLEnvironment::instance().getCurrentSky()->getStarBrightness() / 500.0f; - - // If start_brightness is not set, exit - if(star_alpha < 0.001f) - { - LL_DEBUGS("SKY") << "star_brightness below threshold." << LL_ENDL; - return; - } - - gDeferredStarProgram.bind(); - - LLViewerTexture* tex_a = gSky.mVOSkyp->getBloomTex(); - LLViewerTexture* tex_b = gSky.mVOSkyp->getBloomTexNext(); - - F32 blend_factor = LLEnvironment::instance().getCurrentSky()->getBlendFactor(); - - if (tex_a && (!tex_b || (tex_a == tex_b))) - { - // Bind current and next sun textures - gGL.getTexUnit(0)->bind(tex_a); - gGL.getTexUnit(1)->unbind(LLTexUnit::TT_TEXTURE); - blend_factor = 0; - } - else if (tex_b && !tex_a) - { - gGL.getTexUnit(0)->bind(tex_b); - gGL.getTexUnit(1)->unbind(LLTexUnit::TT_TEXTURE); - blend_factor = 0; - } - else if (tex_b != tex_a) - { - gGL.getTexUnit(0)->bind(tex_a); - gGL.getTexUnit(1)->bind(tex_b); - } - - gGL.pushMatrix(); - gGL.translatef(camPosLocal.mV[0], camPosLocal.mV[1], camPosLocal.mV[2]); - gGL.rotatef(gFrameTimeSeconds*0.01f, 0.f, 0.f, 1.f); - gDeferredStarProgram.uniform1f(LLShaderMgr::BLEND_FACTOR, blend_factor); - - if (LLPipeline::sReflectionRender) - { - star_alpha = 1.0f; - } - gDeferredStarProgram.uniform1f(sCustomAlpha, star_alpha); - - sStarTime = (F32)LLFrameTimer::getElapsedSeconds() * 0.5f; - - gDeferredStarProgram.uniform1f(LLShaderMgr::WATER_TIME, sStarTime); - - gSky.mVOWLSkyp->drawStars(); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.getTexUnit(1)->unbind(LLTexUnit::TT_TEXTURE); - - gDeferredStarProgram.unbind(); - - gGL.popMatrix(); -} - -void LLDrawPoolWLSky::renderSkyCloudsDeferred(const LLVector3& camPosLocal, F32 camHeightLocal, LLGLSLShader* cloudshader) const -{ - if (gPipeline.canUseWindLightShaders() && gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_CLOUDS) && gSky.mVOSkyp && gSky.mVOSkyp->getCloudNoiseTex()) - { - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - - LLGLSPipelineBlendSkyBox pipeline(true, true); - - cloudshader->bind(); - - LLPointer cloud_noise = gSky.mVOSkyp->getCloudNoiseTex(); - LLPointer cloud_noise_next = gSky.mVOSkyp->getCloudNoiseTexNext(); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.getTexUnit(1)->unbind(LLTexUnit::TT_TEXTURE); - - F32 cloud_variance = psky ? psky->getCloudVariance() : 0.0f; - F32 blend_factor = psky ? psky->getBlendFactor() : 0.0f; - - if (psky->getCloudScrollRate().isExactlyZero()) - { - blend_factor = 0.f; - } - - // if we even have sun disc textures to work with... - if (cloud_noise || cloud_noise_next) - { - if (cloud_noise && (!cloud_noise_next || (cloud_noise == cloud_noise_next))) - { - // Bind current and next sun textures - cloudshader->bindTexture(LLShaderMgr::CLOUD_NOISE_MAP, cloud_noise, LLTexUnit::TT_TEXTURE); - blend_factor = 0; - } - else if (cloud_noise_next && !cloud_noise) - { - cloudshader->bindTexture(LLShaderMgr::CLOUD_NOISE_MAP, cloud_noise_next, LLTexUnit::TT_TEXTURE); - blend_factor = 0; - } - else if (cloud_noise_next != cloud_noise) - { - cloudshader->bindTexture(LLShaderMgr::CLOUD_NOISE_MAP, cloud_noise, LLTexUnit::TT_TEXTURE); - cloudshader->bindTexture(LLShaderMgr::CLOUD_NOISE_MAP_NEXT, cloud_noise_next, LLTexUnit::TT_TEXTURE); - } - } - - cloudshader->uniform1f(LLShaderMgr::BLEND_FACTOR, blend_factor); - cloudshader->uniform1f(LLShaderMgr::CLOUD_VARIANCE, cloud_variance); - cloudshader->uniform1f(LLShaderMgr::SUN_MOON_GLOW_FACTOR, psky->getSunMoonGlowFactor()); - - /// Render the skydome - renderDome(camPosLocal, camHeightLocal, cloudshader); - - cloudshader->unbind(); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.getTexUnit(1)->unbind(LLTexUnit::TT_TEXTURE); - } -} - -void LLDrawPoolWLSky::renderHeavenlyBodies() -{ - if (!gSky.mVOSkyp) return; - - LLGLSPipelineBlendSkyBox gls_skybox(true, true); // SL-14113 we need moon to write to depth to clip stars behind - - LLVector3 const & origin = LLViewerCamera::getInstance()->getOrigin(); - gGL.pushMatrix(); - gGL.translatef(origin.mV[0], origin.mV[1], origin.mV[2]); - - LLFace * face = gSky.mVOSkyp->mFace[LLVOSky::FACE_SUN]; - - F32 blend_factor = LLEnvironment::instance().getCurrentSky()->getBlendFactor(); - bool can_use_vertex_shaders = gPipeline.shadersLoaded(); - bool can_use_windlight_shaders = gPipeline.canUseWindLightShaders(); - - - if (gSky.mVOSkyp->getSun().getDraw() && face && face->getGeomCount()) - { - LLPointer tex_a = face->getTexture(LLRender::DIFFUSE_MAP); - LLPointer tex_b = face->getTexture(LLRender::ALTERNATE_DIFFUSE_MAP); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.getTexUnit(1)->unbind(LLTexUnit::TT_TEXTURE); - - // if we even have sun disc textures to work with... - if (tex_a || tex_b) - { - // if and only if we have a texture defined, render the sun disc - if (can_use_vertex_shaders && can_use_windlight_shaders) - { - sun_shader->bind(); - - if (tex_a && (!tex_b || (tex_a == tex_b))) - { - // Bind current and next sun textures - sun_shader->bindTexture(LLShaderMgr::DIFFUSE_MAP, tex_a, LLTexUnit::TT_TEXTURE); - blend_factor = 0; - } - else if (tex_b && !tex_a) - { - sun_shader->bindTexture(LLShaderMgr::DIFFUSE_MAP, tex_b, LLTexUnit::TT_TEXTURE); - blend_factor = 0; - } - else if (tex_b != tex_a) - { - sun_shader->bindTexture(LLShaderMgr::DIFFUSE_MAP, tex_a, LLTexUnit::TT_TEXTURE); - sun_shader->bindTexture(LLShaderMgr::ALTERNATE_DIFFUSE_MAP, tex_b, LLTexUnit::TT_TEXTURE); - } - - LLColor4 color(gSky.mVOSkyp->getSun().getInterpColor()); - - sun_shader->uniform4fv(LLShaderMgr::DIFFUSE_COLOR, 1, color.mV); - sun_shader->uniform1f(LLShaderMgr::BLEND_FACTOR, blend_factor); - - face->renderIndexed(); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.getTexUnit(1)->unbind(LLTexUnit::TT_TEXTURE); - - sun_shader->unbind(); - } - } - } - - face = gSky.mVOSkyp->mFace[LLVOSky::FACE_MOON]; - - if (gSky.mVOSkyp->getMoon().getDraw() && face && face->getTexture(LLRender::DIFFUSE_MAP) && face->getGeomCount() && moon_shader) - { - LLViewerTexture* tex_a = face->getTexture(LLRender::DIFFUSE_MAP); - LLViewerTexture* tex_b = face->getTexture(LLRender::ALTERNATE_DIFFUSE_MAP); - - LLColor4 color(gSky.mVOSkyp->getMoon().getInterpColor()); - - if (can_use_vertex_shaders && can_use_windlight_shaders && (tex_a || tex_b)) - { - moon_shader->bind(); - - if (tex_a && (!tex_b || (tex_a == tex_b))) - { - // Bind current and next sun textures - moon_shader->bindTexture(LLShaderMgr::DIFFUSE_MAP, tex_a, LLTexUnit::TT_TEXTURE); - //blend_factor = 0; - } - else if (tex_b && !tex_a) - { - moon_shader->bindTexture(LLShaderMgr::DIFFUSE_MAP, tex_b, LLTexUnit::TT_TEXTURE); - //blend_factor = 0; - } - else if (tex_b != tex_a) - { - moon_shader->bindTexture(LLShaderMgr::DIFFUSE_MAP, tex_a, LLTexUnit::TT_TEXTURE); - //moon_shader->bindTexture(LLShaderMgr::ALTERNATE_DIFFUSE_MAP, tex_b, LLTexUnit::TT_TEXTURE); - } - - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - - F32 moon_brightness = (float)psky->getMoonBrightness(); - - moon_shader->uniform1f(LLShaderMgr::MOON_BRIGHTNESS, moon_brightness); - moon_shader->uniform3fv(LLShaderMgr::MOONLIGHT_COLOR, 1, gSky.mVOSkyp->getMoon().getColor().mV); - moon_shader->uniform4fv(LLShaderMgr::DIFFUSE_COLOR, 1, color.mV); - //moon_shader->uniform1f(LLShaderMgr::BLEND_FACTOR, blend_factor); - moon_shader->uniform3fv(LLShaderMgr::DEFERRED_MOON_DIR, 1, psky->getMoonDirection().mV); // shader: moon_dir - - face->renderIndexed(); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.getTexUnit(1)->unbind(LLTexUnit::TT_TEXTURE); - - moon_shader->unbind(); - } - } - - gGL.popMatrix(); -} - -void LLDrawPoolWLSky::renderDeferred(S32 pass) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; //LL_RECORD_BLOCK_TIME(FTM_RENDER_WL_SKY); - if (!gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_SKY) || gSky.mVOSkyp.isNull()) - { - return; - } - - // TODO: remove gSky.mVOSkyp and fold sun/moon into LLVOWLSky - gSky.mVOSkyp->updateGeometry(gSky.mVOSkyp->mDrawable); - - const F32 camHeightLocal = LLEnvironment::instance().getCamHeight(); - - gGL.setColorMask(true, false); - - LLVector3 const & origin = LLViewerCamera::getInstance()->getOrigin(); - - if (gPipeline.canUseWindLightShaders()) - { - renderSkyHazeDeferred(origin, camHeightLocal); - renderHeavenlyBodies(); - if (!gCubeSnapshot) - { - renderStarsDeferred(origin); - } - - if (!gCubeSnapshot || gPipeline.mReflectionMapManager.isRadiancePass()) // don't draw clouds in irradiance maps to avoid popping - { - renderSkyCloudsDeferred(origin, camHeightLocal, cloud_shader); - } - } - gGL.setColorMask(true, true); -} - - - -LLViewerTexture* LLDrawPoolWLSky::getTexture() -{ - return NULL; -} - -void LLDrawPoolWLSky::resetDrawOrders() -{ -} - -//static -void LLDrawPoolWLSky::cleanupGL() -{ -} - -//static -void LLDrawPoolWLSky::restoreGL() -{ -} +/** + * @file lldrawpoolwlsky.cpp + * @brief LLDrawPoolWLSky class implementation + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lldrawpoolwlsky.h" + +#include "llerror.h" +#include "llface.h" +#include "llimage.h" +#include "llrender.h" +#include "llatmosphere.h" +#include "llenvironment.h" +#include "llglslshader.h" +#include "llgl.h" + +#include "llviewerregion.h" +#include "llviewershadermgr.h" +#include "llviewercamera.h" +#include "pipeline.h" +#include "llsky.h" +#include "llvowlsky.h" +#include "llsettingsvo.h" + +extern bool gCubeSnapshot; + +static LLStaticHashedString sCamPosLocal("camPosLocal"); +static LLStaticHashedString sCustomAlpha("custom_alpha"); + +static LLGLSLShader* cloud_shader = NULL; +static LLGLSLShader* sky_shader = NULL; +static LLGLSLShader* sun_shader = NULL; +static LLGLSLShader* moon_shader = NULL; + +static float sStarTime; + +LLDrawPoolWLSky::LLDrawPoolWLSky(void) : + LLDrawPool(POOL_WL_SKY) +{ +} + +LLDrawPoolWLSky::~LLDrawPoolWLSky() +{ +} + +LLViewerTexture *LLDrawPoolWLSky::getDebugTexture() +{ + return NULL; +} + +void LLDrawPoolWLSky::beginDeferredPass(S32 pass) +{ + sky_shader = &gDeferredWLSkyProgram; + cloud_shader = &gDeferredWLCloudProgram; + + sun_shader = &gDeferredWLSunProgram; + + moon_shader = &gDeferredWLMoonProgram; +} + +void LLDrawPoolWLSky::endDeferredPass(S32 pass) +{ + sky_shader = nullptr; + cloud_shader = nullptr; + sun_shader = nullptr; + moon_shader = nullptr; + + // clear the depth buffer so haze shaders can use unwritten depth as a mask + glClear(GL_DEPTH_BUFFER_BIT); +} + +void LLDrawPoolWLSky::renderDome(const LLVector3& camPosLocal, F32 camHeightLocal, LLGLSLShader * shader) const +{ + llassert_always(NULL != shader); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + + //chop off translation + if (LLPipeline::sReflectionRender && camPosLocal.mV[2] > 256.f) + { + gGL.translatef(camPosLocal.mV[0], camPosLocal.mV[1], 256.f-camPosLocal.mV[2]*0.5f); + } + else + { + gGL.translatef(camPosLocal.mV[0], camPosLocal.mV[1], camPosLocal.mV[2]); + } + + + // the windlight sky dome works most conveniently in a coordinate system + // where Y is up, so permute our basis vectors accordingly. + gGL.rotatef(120.f, 1.f / F_SQRT3, 1.f / F_SQRT3, 1.f / F_SQRT3); + + gGL.scalef(0.333f, 0.333f, 0.333f); + + gGL.translatef(0.f,-camHeightLocal, 0.f); + + // Draw WL Sky + shader->uniform3f(sCamPosLocal, 0.f, camHeightLocal, 0.f); + + gSky.mVOWLSkyp->drawDome(); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.popMatrix(); +} + +void LLDrawPoolWLSky::renderSkyHazeDeferred(const LLVector3& camPosLocal, F32 camHeightLocal) const +{ + if (!gSky.mVOSkyp) + { + return; + } + + LLVector3 const & origin = LLViewerCamera::getInstance()->getOrigin(); + + if (gPipeline.canUseWindLightShaders() && gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_SKY)) + { + LLGLSPipelineDepthTestSkyBox sky(true, true); + + sky_shader->bind(); + + sky_shader->uniform1i(LLShaderMgr::CUBE_SNAPSHOT, gCubeSnapshot ? 1 : 0); + + LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); + + LLViewerTexture* rainbow_tex = gSky.mVOSkyp->getRainbowTex(); + LLViewerTexture* halo_tex = gSky.mVOSkyp->getHaloTex(); + + sky_shader->bindTexture(LLShaderMgr::RAINBOW_MAP, rainbow_tex); + sky_shader->bindTexture(LLShaderMgr::HALO_MAP, halo_tex); + + F32 moisture_level = (float)psky->getSkyMoistureLevel(); + F32 droplet_radius = (float)psky->getSkyDropletRadius(); + F32 ice_level = (float)psky->getSkyIceLevel(); + + // hobble halos and rainbows when there's no light source to generate them + if (!psky->getIsSunUp() && !psky->getIsMoonUp()) + { + moisture_level = 0.0f; + ice_level = 0.0f; + } + + sky_shader->uniform1f(LLShaderMgr::MOISTURE_LEVEL, moisture_level); + sky_shader->uniform1f(LLShaderMgr::DROPLET_RADIUS, droplet_radius); + sky_shader->uniform1f(LLShaderMgr::ICE_LEVEL, ice_level); + + sky_shader->uniform1f(LLShaderMgr::SUN_MOON_GLOW_FACTOR, psky->getSunMoonGlowFactor()); + + sky_shader->uniform1i(LLShaderMgr::SUN_UP_FACTOR, psky->getIsSunUp() ? 1 : 0); + + /// Render the skydome + renderDome(origin, camHeightLocal, sky_shader); + + sky_shader->unbind(); + } +} + +void LLDrawPoolWLSky::renderStarsDeferred(const LLVector3& camPosLocal) const +{ + if (!gSky.mVOSkyp) + { + return; + } + + LLGLSPipelineBlendSkyBox gls_sky(true, false); + + gGL.setSceneBlendType(LLRender::BT_ADD_WITH_ALPHA); + + F32 star_alpha = LLEnvironment::instance().getCurrentSky()->getStarBrightness() / 500.0f; + + // If start_brightness is not set, exit + if(star_alpha < 0.001f) + { + LL_DEBUGS("SKY") << "star_brightness below threshold." << LL_ENDL; + return; + } + + gDeferredStarProgram.bind(); + + LLViewerTexture* tex_a = gSky.mVOSkyp->getBloomTex(); + LLViewerTexture* tex_b = gSky.mVOSkyp->getBloomTexNext(); + + F32 blend_factor = LLEnvironment::instance().getCurrentSky()->getBlendFactor(); + + if (tex_a && (!tex_b || (tex_a == tex_b))) + { + // Bind current and next sun textures + gGL.getTexUnit(0)->bind(tex_a); + gGL.getTexUnit(1)->unbind(LLTexUnit::TT_TEXTURE); + blend_factor = 0; + } + else if (tex_b && !tex_a) + { + gGL.getTexUnit(0)->bind(tex_b); + gGL.getTexUnit(1)->unbind(LLTexUnit::TT_TEXTURE); + blend_factor = 0; + } + else if (tex_b != tex_a) + { + gGL.getTexUnit(0)->bind(tex_a); + gGL.getTexUnit(1)->bind(tex_b); + } + + gGL.pushMatrix(); + gGL.translatef(camPosLocal.mV[0], camPosLocal.mV[1], camPosLocal.mV[2]); + gGL.rotatef(gFrameTimeSeconds*0.01f, 0.f, 0.f, 1.f); + gDeferredStarProgram.uniform1f(LLShaderMgr::BLEND_FACTOR, blend_factor); + + if (LLPipeline::sReflectionRender) + { + star_alpha = 1.0f; + } + gDeferredStarProgram.uniform1f(sCustomAlpha, star_alpha); + + sStarTime = (F32)LLFrameTimer::getElapsedSeconds() * 0.5f; + + gDeferredStarProgram.uniform1f(LLShaderMgr::WATER_TIME, sStarTime); + + gSky.mVOWLSkyp->drawStars(); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gGL.getTexUnit(1)->unbind(LLTexUnit::TT_TEXTURE); + + gDeferredStarProgram.unbind(); + + gGL.popMatrix(); +} + +void LLDrawPoolWLSky::renderSkyCloudsDeferred(const LLVector3& camPosLocal, F32 camHeightLocal, LLGLSLShader* cloudshader) const +{ + if (gPipeline.canUseWindLightShaders() && gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_CLOUDS) && gSky.mVOSkyp && gSky.mVOSkyp->getCloudNoiseTex()) + { + LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); + + LLGLSPipelineBlendSkyBox pipeline(true, true); + + cloudshader->bind(); + + LLPointer cloud_noise = gSky.mVOSkyp->getCloudNoiseTex(); + LLPointer cloud_noise_next = gSky.mVOSkyp->getCloudNoiseTexNext(); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gGL.getTexUnit(1)->unbind(LLTexUnit::TT_TEXTURE); + + F32 cloud_variance = psky ? psky->getCloudVariance() : 0.0f; + F32 blend_factor = psky ? psky->getBlendFactor() : 0.0f; + + if (psky->getCloudScrollRate().isExactlyZero()) + { + blend_factor = 0.f; + } + + // if we even have sun disc textures to work with... + if (cloud_noise || cloud_noise_next) + { + if (cloud_noise && (!cloud_noise_next || (cloud_noise == cloud_noise_next))) + { + // Bind current and next sun textures + cloudshader->bindTexture(LLShaderMgr::CLOUD_NOISE_MAP, cloud_noise, LLTexUnit::TT_TEXTURE); + blend_factor = 0; + } + else if (cloud_noise_next && !cloud_noise) + { + cloudshader->bindTexture(LLShaderMgr::CLOUD_NOISE_MAP, cloud_noise_next, LLTexUnit::TT_TEXTURE); + blend_factor = 0; + } + else if (cloud_noise_next != cloud_noise) + { + cloudshader->bindTexture(LLShaderMgr::CLOUD_NOISE_MAP, cloud_noise, LLTexUnit::TT_TEXTURE); + cloudshader->bindTexture(LLShaderMgr::CLOUD_NOISE_MAP_NEXT, cloud_noise_next, LLTexUnit::TT_TEXTURE); + } + } + + cloudshader->uniform1f(LLShaderMgr::BLEND_FACTOR, blend_factor); + cloudshader->uniform1f(LLShaderMgr::CLOUD_VARIANCE, cloud_variance); + cloudshader->uniform1f(LLShaderMgr::SUN_MOON_GLOW_FACTOR, psky->getSunMoonGlowFactor()); + + /// Render the skydome + renderDome(camPosLocal, camHeightLocal, cloudshader); + + cloudshader->unbind(); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gGL.getTexUnit(1)->unbind(LLTexUnit::TT_TEXTURE); + } +} + +void LLDrawPoolWLSky::renderHeavenlyBodies() +{ + if (!gSky.mVOSkyp) return; + + LLGLSPipelineBlendSkyBox gls_skybox(true, true); // SL-14113 we need moon to write to depth to clip stars behind + + LLVector3 const & origin = LLViewerCamera::getInstance()->getOrigin(); + gGL.pushMatrix(); + gGL.translatef(origin.mV[0], origin.mV[1], origin.mV[2]); + + LLFace * face = gSky.mVOSkyp->mFace[LLVOSky::FACE_SUN]; + + F32 blend_factor = LLEnvironment::instance().getCurrentSky()->getBlendFactor(); + bool can_use_vertex_shaders = gPipeline.shadersLoaded(); + bool can_use_windlight_shaders = gPipeline.canUseWindLightShaders(); + + + if (gSky.mVOSkyp->getSun().getDraw() && face && face->getGeomCount()) + { + LLPointer tex_a = face->getTexture(LLRender::DIFFUSE_MAP); + LLPointer tex_b = face->getTexture(LLRender::ALTERNATE_DIFFUSE_MAP); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gGL.getTexUnit(1)->unbind(LLTexUnit::TT_TEXTURE); + + // if we even have sun disc textures to work with... + if (tex_a || tex_b) + { + // if and only if we have a texture defined, render the sun disc + if (can_use_vertex_shaders && can_use_windlight_shaders) + { + sun_shader->bind(); + + if (tex_a && (!tex_b || (tex_a == tex_b))) + { + // Bind current and next sun textures + sun_shader->bindTexture(LLShaderMgr::DIFFUSE_MAP, tex_a, LLTexUnit::TT_TEXTURE); + blend_factor = 0; + } + else if (tex_b && !tex_a) + { + sun_shader->bindTexture(LLShaderMgr::DIFFUSE_MAP, tex_b, LLTexUnit::TT_TEXTURE); + blend_factor = 0; + } + else if (tex_b != tex_a) + { + sun_shader->bindTexture(LLShaderMgr::DIFFUSE_MAP, tex_a, LLTexUnit::TT_TEXTURE); + sun_shader->bindTexture(LLShaderMgr::ALTERNATE_DIFFUSE_MAP, tex_b, LLTexUnit::TT_TEXTURE); + } + + LLColor4 color(gSky.mVOSkyp->getSun().getInterpColor()); + + sun_shader->uniform4fv(LLShaderMgr::DIFFUSE_COLOR, 1, color.mV); + sun_shader->uniform1f(LLShaderMgr::BLEND_FACTOR, blend_factor); + + face->renderIndexed(); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gGL.getTexUnit(1)->unbind(LLTexUnit::TT_TEXTURE); + + sun_shader->unbind(); + } + } + } + + face = gSky.mVOSkyp->mFace[LLVOSky::FACE_MOON]; + + if (gSky.mVOSkyp->getMoon().getDraw() && face && face->getTexture(LLRender::DIFFUSE_MAP) && face->getGeomCount() && moon_shader) + { + LLViewerTexture* tex_a = face->getTexture(LLRender::DIFFUSE_MAP); + LLViewerTexture* tex_b = face->getTexture(LLRender::ALTERNATE_DIFFUSE_MAP); + + LLColor4 color(gSky.mVOSkyp->getMoon().getInterpColor()); + + if (can_use_vertex_shaders && can_use_windlight_shaders && (tex_a || tex_b)) + { + moon_shader->bind(); + + if (tex_a && (!tex_b || (tex_a == tex_b))) + { + // Bind current and next sun textures + moon_shader->bindTexture(LLShaderMgr::DIFFUSE_MAP, tex_a, LLTexUnit::TT_TEXTURE); + //blend_factor = 0; + } + else if (tex_b && !tex_a) + { + moon_shader->bindTexture(LLShaderMgr::DIFFUSE_MAP, tex_b, LLTexUnit::TT_TEXTURE); + //blend_factor = 0; + } + else if (tex_b != tex_a) + { + moon_shader->bindTexture(LLShaderMgr::DIFFUSE_MAP, tex_a, LLTexUnit::TT_TEXTURE); + //moon_shader->bindTexture(LLShaderMgr::ALTERNATE_DIFFUSE_MAP, tex_b, LLTexUnit::TT_TEXTURE); + } + + LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); + + F32 moon_brightness = (float)psky->getMoonBrightness(); + + moon_shader->uniform1f(LLShaderMgr::MOON_BRIGHTNESS, moon_brightness); + moon_shader->uniform3fv(LLShaderMgr::MOONLIGHT_COLOR, 1, gSky.mVOSkyp->getMoon().getColor().mV); + moon_shader->uniform4fv(LLShaderMgr::DIFFUSE_COLOR, 1, color.mV); + //moon_shader->uniform1f(LLShaderMgr::BLEND_FACTOR, blend_factor); + moon_shader->uniform3fv(LLShaderMgr::DEFERRED_MOON_DIR, 1, psky->getMoonDirection().mV); // shader: moon_dir + + face->renderIndexed(); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gGL.getTexUnit(1)->unbind(LLTexUnit::TT_TEXTURE); + + moon_shader->unbind(); + } + } + + gGL.popMatrix(); +} + +void LLDrawPoolWLSky::renderDeferred(S32 pass) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; //LL_RECORD_BLOCK_TIME(FTM_RENDER_WL_SKY); + if (!gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_SKY) || gSky.mVOSkyp.isNull()) + { + return; + } + + // TODO: remove gSky.mVOSkyp and fold sun/moon into LLVOWLSky + gSky.mVOSkyp->updateGeometry(gSky.mVOSkyp->mDrawable); + + const F32 camHeightLocal = LLEnvironment::instance().getCamHeight(); + + gGL.setColorMask(true, false); + + LLVector3 const & origin = LLViewerCamera::getInstance()->getOrigin(); + + if (gPipeline.canUseWindLightShaders()) + { + renderSkyHazeDeferred(origin, camHeightLocal); + renderHeavenlyBodies(); + if (!gCubeSnapshot) + { + renderStarsDeferred(origin); + } + + if (!gCubeSnapshot || gPipeline.mReflectionMapManager.isRadiancePass()) // don't draw clouds in irradiance maps to avoid popping + { + renderSkyCloudsDeferred(origin, camHeightLocal, cloud_shader); + } + } + gGL.setColorMask(true, true); +} + + + +LLViewerTexture* LLDrawPoolWLSky::getTexture() +{ + return NULL; +} + +void LLDrawPoolWLSky::resetDrawOrders() +{ +} + +//static +void LLDrawPoolWLSky::cleanupGL() +{ +} + +//static +void LLDrawPoolWLSky::restoreGL() +{ +} diff --git a/indra/newview/lldrawpoolwlsky.h b/indra/newview/lldrawpoolwlsky.h index 83ebefa9bb..3c4b94b467 100644 --- a/indra/newview/lldrawpoolwlsky.h +++ b/indra/newview/lldrawpoolwlsky.h @@ -1,76 +1,76 @@ -/** - * @file lldrawpoolwlsky.h - * @brief LLDrawPoolWLSky class definition - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_DRAWPOOLWLSKY_H -#define LL_DRAWPOOLWLSKY_H - -#include "lldrawpool.h" - -class LLGLSLShader; - -class LLDrawPoolWLSky : public LLDrawPool { -public: - - static const U32 SKY_VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX | - LLVertexBuffer::MAP_TEXCOORD0; - static const U32 STAR_VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX | - LLVertexBuffer::MAP_COLOR | LLVertexBuffer::MAP_TEXCOORD0; - static const U32 ADV_ATMO_SKY_VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX - | LLVertexBuffer::MAP_TEXCOORD0; - LLDrawPoolWLSky(void); - /*virtual*/ ~LLDrawPoolWLSky(); - - /*virtual*/ bool isDead() { return false; } - - /*virtual*/ S32 getNumDeferredPasses() { return 1; } - /*virtual*/ void beginDeferredPass(S32 pass); - /*virtual*/ void endDeferredPass(S32 pass); - /*virtual*/ void renderDeferred(S32 pass); - - /*virtual*/ LLViewerTexture *getDebugTexture(); - /*virtual*/ U32 getVertexDataMask() { return SKY_VERTEX_DATA_MASK; } - /*virtual*/ bool verify() const { return true; } // Verify that all data in the draw pool is correct! - /*virtual*/ S32 getShaderLevel() const { return mShaderLevel; } - - //static LLDrawPool* createPool(const U32 type, LLViewerTexture *tex0 = NULL); - - /*virtual*/ LLViewerTexture* getTexture(); - /*virtual*/ bool isFacePool() { return false; } - /*virtual*/ void resetDrawOrders(); - - static void cleanupGL(); - static void restoreGL(); -private: - void renderDome(const LLVector3& camPosLocal, F32 camHeightLocal, LLGLSLShader * shader) const; - - void renderSkyHazeDeferred(const LLVector3& camPosLocal, F32 camHeightLocal) const; - void renderSkyCloudsDeferred(const LLVector3& camPosLocal, F32 camHeightLocal, LLGLSLShader* cloudshader) const; - - void renderStarsDeferred(const LLVector3& camPosLocal) const; - void renderHeavenlyBodies(); -}; - -#endif // LL_DRAWPOOLWLSKY_H +/** + * @file lldrawpoolwlsky.h + * @brief LLDrawPoolWLSky class definition + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_DRAWPOOLWLSKY_H +#define LL_DRAWPOOLWLSKY_H + +#include "lldrawpool.h" + +class LLGLSLShader; + +class LLDrawPoolWLSky : public LLDrawPool { +public: + + static const U32 SKY_VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX | + LLVertexBuffer::MAP_TEXCOORD0; + static const U32 STAR_VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX | + LLVertexBuffer::MAP_COLOR | LLVertexBuffer::MAP_TEXCOORD0; + static const U32 ADV_ATMO_SKY_VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX + | LLVertexBuffer::MAP_TEXCOORD0; + LLDrawPoolWLSky(void); + /*virtual*/ ~LLDrawPoolWLSky(); + + /*virtual*/ bool isDead() { return false; } + + /*virtual*/ S32 getNumDeferredPasses() { return 1; } + /*virtual*/ void beginDeferredPass(S32 pass); + /*virtual*/ void endDeferredPass(S32 pass); + /*virtual*/ void renderDeferred(S32 pass); + + /*virtual*/ LLViewerTexture *getDebugTexture(); + /*virtual*/ U32 getVertexDataMask() { return SKY_VERTEX_DATA_MASK; } + /*virtual*/ bool verify() const { return true; } // Verify that all data in the draw pool is correct! + /*virtual*/ S32 getShaderLevel() const { return mShaderLevel; } + + //static LLDrawPool* createPool(const U32 type, LLViewerTexture *tex0 = NULL); + + /*virtual*/ LLViewerTexture* getTexture(); + /*virtual*/ bool isFacePool() { return false; } + /*virtual*/ void resetDrawOrders(); + + static void cleanupGL(); + static void restoreGL(); +private: + void renderDome(const LLVector3& camPosLocal, F32 camHeightLocal, LLGLSLShader * shader) const; + + void renderSkyHazeDeferred(const LLVector3& camPosLocal, F32 camHeightLocal) const; + void renderSkyCloudsDeferred(const LLVector3& camPosLocal, F32 camHeightLocal, LLGLSLShader* cloudshader) const; + + void renderStarsDeferred(const LLVector3& camPosLocal) const; + void renderHeavenlyBodies(); +}; + +#endif // LL_DRAWPOOLWLSKY_H diff --git a/indra/newview/lldynamictexture.cpp b/indra/newview/lldynamictexture.cpp index 937bd35fee..22e23727b8 100644 --- a/indra/newview/lldynamictexture.cpp +++ b/indra/newview/lldynamictexture.cpp @@ -1,279 +1,279 @@ -/** - * @file lldynamictexture.cpp - * @brief Implementation of LLViewerDynamicTexture class - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lldynamictexture.h" - -// Linden library includes -#include "llglheaders.h" -#include "llwindow.h" // getPosition() - -// Viewer includes -#include "llviewerwindow.h" -#include "llviewercamera.h" -#include "llviewercontrol.h" -#include "llviewertexture.h" -#include "llvertexbuffer.h" -#include "llviewerdisplay.h" -#include "llrender.h" -#include "pipeline.h" -#include "llglslshader.h" - -// static -LLViewerDynamicTexture::instance_list_t LLViewerDynamicTexture::sInstances[ LLViewerDynamicTexture::ORDER_COUNT ]; -S32 LLViewerDynamicTexture::sNumRenders = 0; - -//----------------------------------------------------------------------------- -// LLViewerDynamicTexture() -//----------------------------------------------------------------------------- -LLViewerDynamicTexture::LLViewerDynamicTexture(S32 width, S32 height, S32 components, EOrder order, bool clamp) : - LLViewerTexture(width, height, components, false), - mClamp(clamp) -{ - llassert((1 <= components) && (components <= 4)); - - generateGLTexture(); - - llassert( 0 <= order && order < ORDER_COUNT ); - LLViewerDynamicTexture::sInstances[ order ].insert(this); -} - -//----------------------------------------------------------------------------- -// LLViewerDynamicTexture() -//----------------------------------------------------------------------------- -LLViewerDynamicTexture::~LLViewerDynamicTexture() -{ - for( S32 order = 0; order < ORDER_COUNT; order++ ) - { - LLViewerDynamicTexture::sInstances[order].erase(this); // will fail in all but one case. - } -} - -//virtual -S8 LLViewerDynamicTexture::getType() const -{ - return LLViewerTexture::DYNAMIC_TEXTURE ; -} - -//----------------------------------------------------------------------------- -// generateGLTexture() -//----------------------------------------------------------------------------- -void LLViewerDynamicTexture::generateGLTexture() -{ - LLViewerTexture::generateGLTexture() ; - generateGLTexture(-1, 0, 0, false); -} - -void LLViewerDynamicTexture::generateGLTexture(LLGLint internal_format, LLGLenum primary_format, LLGLenum type_format, bool swap_bytes) -{ - if (mComponents < 1 || mComponents > 4) - { - LL_ERRS() << "Bad number of components in dynamic texture: " << mComponents << LL_ENDL; - } - - LLPointer raw_image = new LLImageRaw(mFullWidth, mFullHeight, mComponents); - if (internal_format >= 0) - { - setExplicitFormat(internal_format, primary_format, type_format, swap_bytes); - } - createGLTexture(0, raw_image, 0, true, LLGLTexture::DYNAMIC_TEX); - setAddressMode((mClamp) ? LLTexUnit::TAM_CLAMP : LLTexUnit::TAM_WRAP); - mGLTexturep->setGLTextureCreated(false); -} - -//----------------------------------------------------------------------------- -// render() -//----------------------------------------------------------------------------- -bool LLViewerDynamicTexture::render() -{ - return false; -} - -//----------------------------------------------------------------------------- -// preRender() -//----------------------------------------------------------------------------- -void LLViewerDynamicTexture::preRender(bool clear_depth) -{ - //use the bottom left corner - mOrigin.set(0, 0); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - // Set up camera - LLViewerCamera* camera = LLViewerCamera::getInstance(); - mCamera.setOrigin(*camera); - mCamera.setAxes(*camera); - mCamera.setAspect(camera->getAspect()); - mCamera.setView(camera->getView()); - mCamera.setNear(camera->getNear()); - - glViewport(mOrigin.mX, mOrigin.mY, mFullWidth, mFullHeight); - if (clear_depth) - { - glClear(GL_DEPTH_BUFFER_BIT); - } -} - -//----------------------------------------------------------------------------- -// postRender() -//----------------------------------------------------------------------------- -void LLViewerDynamicTexture::postRender(bool success) -{ - { - if (success) - { - if(mGLTexturep.isNull()) - { - generateGLTexture() ; - } - else if(!mGLTexturep->getHasGLTexture()) - { - generateGLTexture() ; - } - else if(mGLTexturep->getDiscardLevel() != 0)//do not know how it happens, but regenerate one if it does. - { - generateGLTexture() ; - } - - success = mGLTexturep->setSubImageFromFrameBuffer(0, 0, mOrigin.mX, mOrigin.mY, mFullWidth, mFullHeight); - } - } - - // restore viewport - gViewerWindow->setup2DViewport(); - - // restore camera - LLViewerCamera* camera = LLViewerCamera::getInstance(); - camera->setOrigin(mCamera); - camera->setAxes(mCamera); - camera->setAspect(mCamera.getAspect()); - camera->setViewNoBroadcast(mCamera.getView()); - camera->setNear(mCamera.getNear()); -} - -//----------------------------------------------------------------------------- -// static -// updateDynamicTextures() -// Calls update on each dynamic texture. Calls each group in order: "first," then "middle," then "last." -//----------------------------------------------------------------------------- -bool LLViewerDynamicTexture::updateAllInstances() -{ - sNumRenders = 0; - if (gGLManager.mIsDisabled) - { - return true; - } - - bool use_fbo = gPipeline.mBake.isComplete() && !gGLManager.mIsAMD; - - if (use_fbo) - { - gPipeline.mBake.bindTarget(); - gPipeline.mBake.clear(); - } - - LLGLSLShader::unbind(); - LLVertexBuffer::unbind(); - - bool result = false; - bool ret = false ; - for( S32 order = 0; order < ORDER_COUNT; order++ ) - { - for (instance_list_t::iterator iter = LLViewerDynamicTexture::sInstances[order].begin(); - iter != LLViewerDynamicTexture::sInstances[order].end(); ++iter) - { - LLViewerDynamicTexture *dynamicTexture = *iter; - if (dynamicTexture->needsRender()) - { - glClear(GL_DEPTH_BUFFER_BIT); - gDepthDirty = true; - - gGL.color4f(1,1,1,1); - dynamicTexture->setBoundTarget(use_fbo ? &gPipeline.mBake : nullptr); - dynamicTexture->preRender(); // Must be called outside of startRender() - result = false; - if (dynamicTexture->render()) - { - ret = true ; - result = true; - sNumRenders++; - } - gGL.flush(); - LLVertexBuffer::unbind(); - dynamicTexture->setBoundTarget(nullptr); - dynamicTexture->postRender(result); - } - } - } - - if (use_fbo) - { - gPipeline.mBake.flush(); - } - - gGL.flush(); - - return ret; -} - -//----------------------------------------------------------------------------- -// static -// destroyGL() -//----------------------------------------------------------------------------- -void LLViewerDynamicTexture::destroyGL() -{ - for( S32 order = 0; order < ORDER_COUNT; order++ ) - { - for (instance_list_t::iterator iter = LLViewerDynamicTexture::sInstances[order].begin(); - iter != LLViewerDynamicTexture::sInstances[order].end(); ++iter) - { - LLViewerDynamicTexture *dynamicTexture = *iter; - dynamicTexture->destroyGLTexture() ; - } - } -} - -//----------------------------------------------------------------------------- -// static -// restoreGL() -//----------------------------------------------------------------------------- -void LLViewerDynamicTexture::restoreGL() -{ - if (gGLManager.mIsDisabled) - { - return ; - } - - for( S32 order = 0; order < ORDER_COUNT; order++ ) - { - for (instance_list_t::iterator iter = LLViewerDynamicTexture::sInstances[order].begin(); - iter != LLViewerDynamicTexture::sInstances[order].end(); ++iter) - { - LLViewerDynamicTexture *dynamicTexture = *iter; - dynamicTexture->restoreGLTexture() ; - } - } -} +/** + * @file lldynamictexture.cpp + * @brief Implementation of LLViewerDynamicTexture class + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lldynamictexture.h" + +// Linden library includes +#include "llglheaders.h" +#include "llwindow.h" // getPosition() + +// Viewer includes +#include "llviewerwindow.h" +#include "llviewercamera.h" +#include "llviewercontrol.h" +#include "llviewertexture.h" +#include "llvertexbuffer.h" +#include "llviewerdisplay.h" +#include "llrender.h" +#include "pipeline.h" +#include "llglslshader.h" + +// static +LLViewerDynamicTexture::instance_list_t LLViewerDynamicTexture::sInstances[ LLViewerDynamicTexture::ORDER_COUNT ]; +S32 LLViewerDynamicTexture::sNumRenders = 0; + +//----------------------------------------------------------------------------- +// LLViewerDynamicTexture() +//----------------------------------------------------------------------------- +LLViewerDynamicTexture::LLViewerDynamicTexture(S32 width, S32 height, S32 components, EOrder order, bool clamp) : + LLViewerTexture(width, height, components, false), + mClamp(clamp) +{ + llassert((1 <= components) && (components <= 4)); + + generateGLTexture(); + + llassert( 0 <= order && order < ORDER_COUNT ); + LLViewerDynamicTexture::sInstances[ order ].insert(this); +} + +//----------------------------------------------------------------------------- +// LLViewerDynamicTexture() +//----------------------------------------------------------------------------- +LLViewerDynamicTexture::~LLViewerDynamicTexture() +{ + for( S32 order = 0; order < ORDER_COUNT; order++ ) + { + LLViewerDynamicTexture::sInstances[order].erase(this); // will fail in all but one case. + } +} + +//virtual +S8 LLViewerDynamicTexture::getType() const +{ + return LLViewerTexture::DYNAMIC_TEXTURE ; +} + +//----------------------------------------------------------------------------- +// generateGLTexture() +//----------------------------------------------------------------------------- +void LLViewerDynamicTexture::generateGLTexture() +{ + LLViewerTexture::generateGLTexture() ; + generateGLTexture(-1, 0, 0, false); +} + +void LLViewerDynamicTexture::generateGLTexture(LLGLint internal_format, LLGLenum primary_format, LLGLenum type_format, bool swap_bytes) +{ + if (mComponents < 1 || mComponents > 4) + { + LL_ERRS() << "Bad number of components in dynamic texture: " << mComponents << LL_ENDL; + } + + LLPointer raw_image = new LLImageRaw(mFullWidth, mFullHeight, mComponents); + if (internal_format >= 0) + { + setExplicitFormat(internal_format, primary_format, type_format, swap_bytes); + } + createGLTexture(0, raw_image, 0, true, LLGLTexture::DYNAMIC_TEX); + setAddressMode((mClamp) ? LLTexUnit::TAM_CLAMP : LLTexUnit::TAM_WRAP); + mGLTexturep->setGLTextureCreated(false); +} + +//----------------------------------------------------------------------------- +// render() +//----------------------------------------------------------------------------- +bool LLViewerDynamicTexture::render() +{ + return false; +} + +//----------------------------------------------------------------------------- +// preRender() +//----------------------------------------------------------------------------- +void LLViewerDynamicTexture::preRender(bool clear_depth) +{ + //use the bottom left corner + mOrigin.set(0, 0); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + // Set up camera + LLViewerCamera* camera = LLViewerCamera::getInstance(); + mCamera.setOrigin(*camera); + mCamera.setAxes(*camera); + mCamera.setAspect(camera->getAspect()); + mCamera.setView(camera->getView()); + mCamera.setNear(camera->getNear()); + + glViewport(mOrigin.mX, mOrigin.mY, mFullWidth, mFullHeight); + if (clear_depth) + { + glClear(GL_DEPTH_BUFFER_BIT); + } +} + +//----------------------------------------------------------------------------- +// postRender() +//----------------------------------------------------------------------------- +void LLViewerDynamicTexture::postRender(bool success) +{ + { + if (success) + { + if(mGLTexturep.isNull()) + { + generateGLTexture() ; + } + else if(!mGLTexturep->getHasGLTexture()) + { + generateGLTexture() ; + } + else if(mGLTexturep->getDiscardLevel() != 0)//do not know how it happens, but regenerate one if it does. + { + generateGLTexture() ; + } + + success = mGLTexturep->setSubImageFromFrameBuffer(0, 0, mOrigin.mX, mOrigin.mY, mFullWidth, mFullHeight); + } + } + + // restore viewport + gViewerWindow->setup2DViewport(); + + // restore camera + LLViewerCamera* camera = LLViewerCamera::getInstance(); + camera->setOrigin(mCamera); + camera->setAxes(mCamera); + camera->setAspect(mCamera.getAspect()); + camera->setViewNoBroadcast(mCamera.getView()); + camera->setNear(mCamera.getNear()); +} + +//----------------------------------------------------------------------------- +// static +// updateDynamicTextures() +// Calls update on each dynamic texture. Calls each group in order: "first," then "middle," then "last." +//----------------------------------------------------------------------------- +bool LLViewerDynamicTexture::updateAllInstances() +{ + sNumRenders = 0; + if (gGLManager.mIsDisabled) + { + return true; + } + + bool use_fbo = gPipeline.mBake.isComplete() && !gGLManager.mIsAMD; + + if (use_fbo) + { + gPipeline.mBake.bindTarget(); + gPipeline.mBake.clear(); + } + + LLGLSLShader::unbind(); + LLVertexBuffer::unbind(); + + bool result = false; + bool ret = false ; + for( S32 order = 0; order < ORDER_COUNT; order++ ) + { + for (instance_list_t::iterator iter = LLViewerDynamicTexture::sInstances[order].begin(); + iter != LLViewerDynamicTexture::sInstances[order].end(); ++iter) + { + LLViewerDynamicTexture *dynamicTexture = *iter; + if (dynamicTexture->needsRender()) + { + glClear(GL_DEPTH_BUFFER_BIT); + gDepthDirty = true; + + gGL.color4f(1,1,1,1); + dynamicTexture->setBoundTarget(use_fbo ? &gPipeline.mBake : nullptr); + dynamicTexture->preRender(); // Must be called outside of startRender() + result = false; + if (dynamicTexture->render()) + { + ret = true ; + result = true; + sNumRenders++; + } + gGL.flush(); + LLVertexBuffer::unbind(); + dynamicTexture->setBoundTarget(nullptr); + dynamicTexture->postRender(result); + } + } + } + + if (use_fbo) + { + gPipeline.mBake.flush(); + } + + gGL.flush(); + + return ret; +} + +//----------------------------------------------------------------------------- +// static +// destroyGL() +//----------------------------------------------------------------------------- +void LLViewerDynamicTexture::destroyGL() +{ + for( S32 order = 0; order < ORDER_COUNT; order++ ) + { + for (instance_list_t::iterator iter = LLViewerDynamicTexture::sInstances[order].begin(); + iter != LLViewerDynamicTexture::sInstances[order].end(); ++iter) + { + LLViewerDynamicTexture *dynamicTexture = *iter; + dynamicTexture->destroyGLTexture() ; + } + } +} + +//----------------------------------------------------------------------------- +// static +// restoreGL() +//----------------------------------------------------------------------------- +void LLViewerDynamicTexture::restoreGL() +{ + if (gGLManager.mIsDisabled) + { + return ; + } + + for( S32 order = 0; order < ORDER_COUNT; order++ ) + { + for (instance_list_t::iterator iter = LLViewerDynamicTexture::sInstances[order].begin(); + iter != LLViewerDynamicTexture::sInstances[order].end(); ++iter) + { + LLViewerDynamicTexture *dynamicTexture = *iter; + dynamicTexture->restoreGLTexture() ; + } + } +} diff --git a/indra/newview/lldynamictexture.h b/indra/newview/lldynamictexture.h index d002f154a5..375828fefa 100644 --- a/indra/newview/lldynamictexture.h +++ b/indra/newview/lldynamictexture.h @@ -1,102 +1,102 @@ -/** - * @file lldynamictexture.h - * @brief Implementation of LLViewerDynamicTexture class - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLDYNAMICTEXTURE_H -#define LL_LLDYNAMICTEXTURE_H - -#include "llcamera.h" -#include "llgl.h" -#include "llcoord.h" -#include "llviewertexture.h" -#include "llcamera.h" - -class LLViewerDynamicTexture : public LLViewerTexture -{ - LL_ALIGN_NEW -public: - - enum - { - LL_VIEWER_DYNAMIC_TEXTURE = LLViewerTexture::DYNAMIC_TEXTURE, - LL_TEX_LAYER_SET_BUFFER = LLViewerTexture::INVALID_TEXTURE_TYPE + 1, - LL_VISUAL_PARAM_HINT, - LL_VISUAL_PARAM_RESET, - LL_PREVIEW_ANIMATION, - LL_IMAGE_PREVIEW_SCULPTED, - LL_IMAGE_PREVIEW_AVATAR, - INVALID_DYNAMIC_TEXTURE - }; - -protected: - /*virtual*/ ~LLViewerDynamicTexture(); - -public: - enum EOrder { ORDER_FIRST = 0, ORDER_MIDDLE = 1, ORDER_LAST = 2, ORDER_RESET = 3, ORDER_COUNT = 4 }; - - LLViewerDynamicTexture(S32 width, - S32 height, - S32 components, // = 4, - EOrder order, // = ORDER_MIDDLE, - bool clamp); - - /*virtual*/ S8 getType() const ; - - S32 getOriginX() const { return mOrigin.mX; } - S32 getOriginY() const { return mOrigin.mY; } - - S32 getSize() { return mFullWidth * mFullHeight * mComponents; } - - virtual bool needsRender() { return true; } - virtual void preRender(bool clear_depth = true); - virtual bool render(); - virtual void postRender(bool success); - - virtual void restoreGLTexture() {} - virtual void destroyGLTexture() {} - - static bool updateAllInstances(); - static void destroyGL() ; - static void restoreGL() ; - - void setBoundTarget(LLRenderTarget* target) { mBoundTarget = target; } - -protected: - void generateGLTexture(); - void generateGLTexture(LLGLint internal_format, LLGLenum primary_format, LLGLenum type_format, bool swap_bytes = false); - -protected: - bool mClamp; - LLCoordGL mOrigin; - LL_ALIGN_16(LLCamera mCamera); - - LLRenderTarget* mBoundTarget; - - typedef std::set instance_list_t; - static instance_list_t sInstances[ LLViewerDynamicTexture::ORDER_COUNT ]; - static S32 sNumRenders; -}; - -#endif +/** + * @file lldynamictexture.h + * @brief Implementation of LLViewerDynamicTexture class + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLDYNAMICTEXTURE_H +#define LL_LLDYNAMICTEXTURE_H + +#include "llcamera.h" +#include "llgl.h" +#include "llcoord.h" +#include "llviewertexture.h" +#include "llcamera.h" + +class LLViewerDynamicTexture : public LLViewerTexture +{ + LL_ALIGN_NEW +public: + + enum + { + LL_VIEWER_DYNAMIC_TEXTURE = LLViewerTexture::DYNAMIC_TEXTURE, + LL_TEX_LAYER_SET_BUFFER = LLViewerTexture::INVALID_TEXTURE_TYPE + 1, + LL_VISUAL_PARAM_HINT, + LL_VISUAL_PARAM_RESET, + LL_PREVIEW_ANIMATION, + LL_IMAGE_PREVIEW_SCULPTED, + LL_IMAGE_PREVIEW_AVATAR, + INVALID_DYNAMIC_TEXTURE + }; + +protected: + /*virtual*/ ~LLViewerDynamicTexture(); + +public: + enum EOrder { ORDER_FIRST = 0, ORDER_MIDDLE = 1, ORDER_LAST = 2, ORDER_RESET = 3, ORDER_COUNT = 4 }; + + LLViewerDynamicTexture(S32 width, + S32 height, + S32 components, // = 4, + EOrder order, // = ORDER_MIDDLE, + bool clamp); + + /*virtual*/ S8 getType() const ; + + S32 getOriginX() const { return mOrigin.mX; } + S32 getOriginY() const { return mOrigin.mY; } + + S32 getSize() { return mFullWidth * mFullHeight * mComponents; } + + virtual bool needsRender() { return true; } + virtual void preRender(bool clear_depth = true); + virtual bool render(); + virtual void postRender(bool success); + + virtual void restoreGLTexture() {} + virtual void destroyGLTexture() {} + + static bool updateAllInstances(); + static void destroyGL() ; + static void restoreGL() ; + + void setBoundTarget(LLRenderTarget* target) { mBoundTarget = target; } + +protected: + void generateGLTexture(); + void generateGLTexture(LLGLint internal_format, LLGLenum primary_format, LLGLenum type_format, bool swap_bytes = false); + +protected: + bool mClamp; + LLCoordGL mOrigin; + LL_ALIGN_16(LLCamera mCamera); + + LLRenderTarget* mBoundTarget; + + typedef std::set instance_list_t; + static instance_list_t sInstances[ LLViewerDynamicTexture::ORDER_COUNT ]; + static S32 sNumRenders; +}; + +#endif diff --git a/indra/newview/llemote.cpp b/indra/newview/llemote.cpp index 777ee43e46..e952a29c54 100644 --- a/indra/newview/llemote.cpp +++ b/indra/newview/llemote.cpp @@ -1,143 +1,143 @@ -/** - * @file llemote.cpp - * @brief Implementation of LLEmote class - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -//----------------------------------------------------------------------------- -// Header Files -//----------------------------------------------------------------------------- -#include "llviewerprecompiledheaders.h" - -#include "llemote.h" -#include "llcharacter.h" -#include "m3math.h" -#include "llvoavatar.h" - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- - -//----------------------------------------------------------------------------- -// LLEmote() -// Class Constructor -//----------------------------------------------------------------------------- -LLEmote::LLEmote(const LLUUID &id) : LLMotion(id) -{ - mCharacter = NULL; - - //RN: flag face joint as highest priority for now, until we implement a proper animation track - mJointSignature[0][LL_FACE_JOINT_NUM] = 0xff; - mJointSignature[1][LL_FACE_JOINT_NUM] = 0xff; - mJointSignature[2][LL_FACE_JOINT_NUM] = 0xff; -} - - -//----------------------------------------------------------------------------- -// ~LLEmote() -// Class Destructor -//----------------------------------------------------------------------------- -LLEmote::~LLEmote() -{ -} - -//----------------------------------------------------------------------------- -// LLEmote::onInitialize(LLCharacter *character) -//----------------------------------------------------------------------------- -LLMotion::LLMotionInitStatus LLEmote::onInitialize(LLCharacter *character) -{ - mCharacter = character; - return STATUS_SUCCESS; -} - - -//----------------------------------------------------------------------------- -// LLEmote::onActivate() -//----------------------------------------------------------------------------- -bool LLEmote::onActivate() -{ - LLVisualParam* default_param = mCharacter->getVisualParam( "Express_Closed_Mouth" ); - if( default_param ) - { - default_param->setWeight( default_param->getMaxWeight()); - } - - mParam = mCharacter->getVisualParam(mName.c_str()); - if (mParam) - { - mParam->setWeight(0.f); - mCharacter->updateVisualParams(); - } - - return true; -} - - -//----------------------------------------------------------------------------- -// LLEmote::onUpdate() -//----------------------------------------------------------------------------- -bool LLEmote::onUpdate(F32 time, U8* joint_mask) -{ - if( mParam ) - { - F32 weight = mParam->getMinWeight() + mPose.getWeight() * (mParam->getMaxWeight() - mParam->getMinWeight()); - mParam->setWeight(weight); - - // Cross fade against the default parameter - LLVisualParam* default_param = mCharacter->getVisualParam( "Express_Closed_Mouth" ); - if( default_param ) - { - F32 default_param_weight = default_param->getMinWeight() + - (1.f - mPose.getWeight()) * ( default_param->getMaxWeight() - default_param->getMinWeight() ); - - default_param->setWeight( default_param_weight); - } - - mCharacter->updateVisualParams(); - } - - return true; -} - - -//----------------------------------------------------------------------------- -// LLEmote::onDeactivate() -//----------------------------------------------------------------------------- -void LLEmote::onDeactivate() -{ - if( mParam ) - { - mParam->setWeight( mParam->getDefaultWeight()); - } - - LLVisualParam* default_param = mCharacter->getVisualParam( "Express_Closed_Mouth" ); - if( default_param ) - { - default_param->setWeight( default_param->getMaxWeight()); - } - - mCharacter->updateVisualParams(); -} - - -// End +/** + * @file llemote.cpp + * @brief Implementation of LLEmote class + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "llviewerprecompiledheaders.h" + +#include "llemote.h" +#include "llcharacter.h" +#include "m3math.h" +#include "llvoavatar.h" + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// LLEmote() +// Class Constructor +//----------------------------------------------------------------------------- +LLEmote::LLEmote(const LLUUID &id) : LLMotion(id) +{ + mCharacter = NULL; + + //RN: flag face joint as highest priority for now, until we implement a proper animation track + mJointSignature[0][LL_FACE_JOINT_NUM] = 0xff; + mJointSignature[1][LL_FACE_JOINT_NUM] = 0xff; + mJointSignature[2][LL_FACE_JOINT_NUM] = 0xff; +} + + +//----------------------------------------------------------------------------- +// ~LLEmote() +// Class Destructor +//----------------------------------------------------------------------------- +LLEmote::~LLEmote() +{ +} + +//----------------------------------------------------------------------------- +// LLEmote::onInitialize(LLCharacter *character) +//----------------------------------------------------------------------------- +LLMotion::LLMotionInitStatus LLEmote::onInitialize(LLCharacter *character) +{ + mCharacter = character; + return STATUS_SUCCESS; +} + + +//----------------------------------------------------------------------------- +// LLEmote::onActivate() +//----------------------------------------------------------------------------- +bool LLEmote::onActivate() +{ + LLVisualParam* default_param = mCharacter->getVisualParam( "Express_Closed_Mouth" ); + if( default_param ) + { + default_param->setWeight( default_param->getMaxWeight()); + } + + mParam = mCharacter->getVisualParam(mName.c_str()); + if (mParam) + { + mParam->setWeight(0.f); + mCharacter->updateVisualParams(); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// LLEmote::onUpdate() +//----------------------------------------------------------------------------- +bool LLEmote::onUpdate(F32 time, U8* joint_mask) +{ + if( mParam ) + { + F32 weight = mParam->getMinWeight() + mPose.getWeight() * (mParam->getMaxWeight() - mParam->getMinWeight()); + mParam->setWeight(weight); + + // Cross fade against the default parameter + LLVisualParam* default_param = mCharacter->getVisualParam( "Express_Closed_Mouth" ); + if( default_param ) + { + F32 default_param_weight = default_param->getMinWeight() + + (1.f - mPose.getWeight()) * ( default_param->getMaxWeight() - default_param->getMinWeight() ); + + default_param->setWeight( default_param_weight); + } + + mCharacter->updateVisualParams(); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// LLEmote::onDeactivate() +//----------------------------------------------------------------------------- +void LLEmote::onDeactivate() +{ + if( mParam ) + { + mParam->setWeight( mParam->getDefaultWeight()); + } + + LLVisualParam* default_param = mCharacter->getVisualParam( "Express_Closed_Mouth" ); + if( default_param ) + { + default_param->setWeight( default_param->getMaxWeight()); + } + + mCharacter->updateVisualParams(); +} + + +// End diff --git a/indra/newview/llemote.h b/indra/newview/llemote.h index b6fa3f8372..3c453fee7b 100644 --- a/indra/newview/llemote.h +++ b/indra/newview/llemote.h @@ -1,122 +1,122 @@ -/** - * @file llemote.h - * @brief Definition of LLEmote class - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLEMOTE_H -#define LL_LLEMOTE_H - -//----------------------------------------------------------------------------- -// Header files -//----------------------------------------------------------------------------- -#include "llmotion.h" -#include "lltimer.h" - -#define MIN_REQUIRED_PIXEL_AREA_EMOTE 2000.f - -#define EMOTE_MORPH_FADEIN_TIME 0.3f -#define EMOTE_MORPH_IN_TIME 1.1f -#define EMOTE_MORPH_FADEOUT_TIME 1.4f - -class LLVisualParam; - -//----------------------------------------------------------------------------- -// class LLEmote -//----------------------------------------------------------------------------- -class LLEmote : - public LLMotion -{ -public: - // Constructor - LLEmote(const LLUUID &id); - - // Destructor - virtual ~LLEmote(); - -public: - //------------------------------------------------------------------------- - // functions to support MotionController and MotionRegistry - //------------------------------------------------------------------------- - - // static constructor - // all subclasses must implement such a function and register it - static LLMotion *create(const LLUUID &id) { return new LLEmote(id); } - -public: - //------------------------------------------------------------------------- - // animation callbacks to be implemented by subclasses - //------------------------------------------------------------------------- - - // motions must specify whether or not they loop - virtual bool getLoop() { return false; } - - // motions must report their total duration - virtual F32 getDuration() { return EMOTE_MORPH_FADEIN_TIME + EMOTE_MORPH_IN_TIME + EMOTE_MORPH_FADEOUT_TIME; } - - // motions must report their "ease in" duration - virtual F32 getEaseInDuration() { return EMOTE_MORPH_FADEIN_TIME; } - - // motions must report their "ease out" duration. - virtual F32 getEaseOutDuration() { return EMOTE_MORPH_FADEOUT_TIME; } - - // called to determine when a motion should be activated/deactivated based on avatar pixel coverage - virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_EMOTE; } - - // motions must report their priority - virtual LLJoint::JointPriority getPriority() { return LLJoint::MEDIUM_PRIORITY; } - - virtual LLMotionBlendType getBlendType() { return NORMAL_BLEND; } - - // run-time (post constructor) initialization, - // called after parameters have been set - // must return true to indicate success and be available for activation - virtual LLMotionInitStatus onInitialize(LLCharacter *character); - - // called when a motion is activated - // must return true to indicate success, or else - // it will be deactivated - virtual bool onActivate(); - - // called per time step - // must return true while it is active, and - // must return false when the motion is completed. - virtual bool onUpdate(F32 time, U8* joint_mask); - - // called when a motion is deactivated - virtual void onDeactivate(); - - virtual bool canDeprecate() { return false; } - -protected: - - LLCharacter* mCharacter; - - LLVisualParam* mParam; -}; - - - -#endif // LL_LLEMOTE_H - - +/** + * @file llemote.h + * @brief Definition of LLEmote class + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLEMOTE_H +#define LL_LLEMOTE_H + +//----------------------------------------------------------------------------- +// Header files +//----------------------------------------------------------------------------- +#include "llmotion.h" +#include "lltimer.h" + +#define MIN_REQUIRED_PIXEL_AREA_EMOTE 2000.f + +#define EMOTE_MORPH_FADEIN_TIME 0.3f +#define EMOTE_MORPH_IN_TIME 1.1f +#define EMOTE_MORPH_FADEOUT_TIME 1.4f + +class LLVisualParam; + +//----------------------------------------------------------------------------- +// class LLEmote +//----------------------------------------------------------------------------- +class LLEmote : + public LLMotion +{ +public: + // Constructor + LLEmote(const LLUUID &id); + + // Destructor + virtual ~LLEmote(); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return new LLEmote(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + + // motions must specify whether or not they loop + virtual bool getLoop() { return false; } + + // motions must report their total duration + virtual F32 getDuration() { return EMOTE_MORPH_FADEIN_TIME + EMOTE_MORPH_IN_TIME + EMOTE_MORPH_FADEOUT_TIME; } + + // motions must report their "ease in" duration + virtual F32 getEaseInDuration() { return EMOTE_MORPH_FADEIN_TIME; } + + // motions must report their "ease out" duration. + virtual F32 getEaseOutDuration() { return EMOTE_MORPH_FADEOUT_TIME; } + + // called to determine when a motion should be activated/deactivated based on avatar pixel coverage + virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_EMOTE; } + + // motions must report their priority + virtual LLJoint::JointPriority getPriority() { return LLJoint::MEDIUM_PRIORITY; } + + virtual LLMotionBlendType getBlendType() { return NORMAL_BLEND; } + + // run-time (post constructor) initialization, + // called after parameters have been set + // must return true to indicate success and be available for activation + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + + // called when a motion is activated + // must return true to indicate success, or else + // it will be deactivated + virtual bool onActivate(); + + // called per time step + // must return true while it is active, and + // must return false when the motion is completed. + virtual bool onUpdate(F32 time, U8* joint_mask); + + // called when a motion is deactivated + virtual void onDeactivate(); + + virtual bool canDeprecate() { return false; } + +protected: + + LLCharacter* mCharacter; + + LLVisualParam* mParam; +}; + + + +#endif // LL_LLEMOTE_H + + diff --git a/indra/newview/llenvironment.cpp b/indra/newview/llenvironment.cpp index 234f0cebf7..775bcb3535 100644 --- a/indra/newview/llenvironment.cpp +++ b/indra/newview/llenvironment.cpp @@ -1,3722 +1,3722 @@ -/** - * @file llenvmanager.cpp - * @brief Implementation of classes managing WindLight and water settings. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llenvironment.h" - -#include - -#include "llagent.h" -#include "llviewercontrol.h" // for gSavedSettings -#include "llviewerregion.h" -#include "llwlhandlers.h" -#include "lltrans.h" -#include "lltrace.h" -#include "llfasttimer.h" -#include "llviewercamera.h" -#include "pipeline.h" -#include "llsky.h" - -#include "llviewershadermgr.h" - -#include "llparcel.h" -#include "llviewerparcelmgr.h" - -#include "llsdserialize.h" -#include "lldiriterator.h" - -#include "llsettingsvo.h" -#include "llnotificationsutil.h" - -#include "llregioninfomodel.h" - -#include - -#include "llatmosphere.h" -#include "llagent.h" -#include "roles_constants.h" -#include "llestateinfomodel.h" - -#include "lldispatcher.h" -#include "llviewergenericmessage.h" -#include "llexperiencelog.h" - -//========================================================================= -namespace -{ - const std::string KEY_ENVIRONMENT("environment"); - const std::string KEY_DAYASSET("day_asset"); - const std::string KEY_DAYCYCLE("day_cycle"); - const std::string KEY_DAYHASH("day_hash"); - const std::string KEY_DAYLENGTH("day_length"); - const std::string KEY_DAYNAME("day_name"); - const std::string KEY_DAYNAMES("day_names"); - const std::string KEY_DAYOFFSET("day_offset"); - const std::string KEY_ENVVERSION("env_version"); - const std::string KEY_ISDEFAULT("is_default"); - const std::string KEY_PARCELID("parcel_id"); - const std::string KEY_REGIONID("region_id"); - const std::string KEY_TRACKALTS("track_altitudes"); - const std::string KEY_FLAGS("flags"); - - const std::string MESSAGE_PUSHENVIRONMENT("PushExpEnvironment"); - - const std::string ACTION_CLEARENVIRONMENT("ClearEnvironment"); - const std::string ACTION_PUSHFULLENVIRONMENT("PushFullEnvironment"); - const std::string ACTION_PUSHPARTIALENVIRONMENT("PushPartialEnvironment"); - - const std::string KEY_ASSETID("asset_id"); - const std::string KEY_TRANSITIONTIME("transition_time"); - const std::string KEY_ACTION("action"); - const std::string KEY_ACTIONDATA("action_data"); - const std::string KEY_EXPERIENCEID("public_id"); - const std::string KEY_OBJECTNAME("ObjectName"); // some of these do not conform to the '_' format. - const std::string KEY_PARCELNAME("ParcelName"); // But changing these would also alter the Experience Log requirements. - const std::string KEY_COUNT("Count"); - - const std::string LISTENER_NAME("LLEnvironmentSingleton"); - const std::string PUMP_EXPERIENCE("experience_permission"); - - const std::string LOCAL_ENV_STORAGE_FILE("local_environment_data.bin"); - - //--------------------------------------------------------------------- - LLTrace::BlockTimerStatHandle FTM_ENVIRONMENT_UPDATE("Update Environment Tick"); - - LLSettingsBase::Seconds DEFAULT_UPDATE_THRESHOLD(10.0); - const LLSettingsBase::Seconds MINIMUM_SPANLENGTH(0.01f); - - //--------------------------------------------------------------------- - inline LLSettingsBase::TrackPosition get_wrapping_distance(LLSettingsBase::TrackPosition begin, LLSettingsBase::TrackPosition end) - { - if (begin < end) - { - return end - begin; - } - else if (begin > end) - { - return LLSettingsBase::TrackPosition(1.0) - (begin - end); - } - - return 1.0f; - } - - LLSettingsDay::CycleTrack_t::iterator get_wrapping_atafter(LLSettingsDay::CycleTrack_t &collection, const LLSettingsBase::TrackPosition& key) - { - if (collection.empty()) - return collection.end(); - - LLSettingsDay::CycleTrack_t::iterator it = collection.upper_bound(key); - - if (it == collection.end()) - { // wrap around - it = collection.begin(); - } - - return it; - } - - LLSettingsDay::CycleTrack_t::iterator get_wrapping_atbefore(LLSettingsDay::CycleTrack_t &collection, const LLSettingsBase::TrackPosition& key) - { - if (collection.empty()) - return collection.end(); - - LLSettingsDay::CycleTrack_t::iterator it = collection.lower_bound(key); - - if (it == collection.end()) - { // all keyframes are lower, take the last one. - --it; // we know the range is not empty - } - else if ((*it).first > key) - { // the keyframe we are interested in is smaller than the found. - if (it == collection.begin()) - it = collection.end(); - --it; - } - - return it; - } - - LLSettingsDay::TrackBound_t get_bounding_entries(LLSettingsDay::CycleTrack_t &track, const LLSettingsBase::TrackPosition& keyframe) - { - return LLSettingsDay::TrackBound_t(get_wrapping_atbefore(track, keyframe), get_wrapping_atafter(track, keyframe)); - } - - // Find normalized track position of given time along full length of cycle - inline LLSettingsBase::TrackPosition convert_time_to_position(const LLSettingsBase::Seconds& time, const LLSettingsBase::Seconds& len) - { - // early out to avoid divide by zero. if len is zero then jump to end position - if (len == 0.f) return 1.f; - - LLSettingsBase::TrackPosition position = LLSettingsBase::TrackPosition(fmod((F64)time, (F64)len) / (F64)len); - return llclamp(position, 0.0f, 1.0f); - } - - inline LLSettingsBase::BlendFactor convert_time_to_blend_factor(const LLSettingsBase::Seconds& time, const LLSettingsBase::Seconds& len, LLSettingsDay::CycleTrack_t &track) - { - LLSettingsBase::TrackPosition position = convert_time_to_position(time, len); - LLSettingsDay::TrackBound_t bounds(get_bounding_entries(track, position)); - - LLSettingsBase::TrackPosition spanlength(get_wrapping_distance((*bounds.first).first, (*bounds.second).first)); - if (position < (*bounds.first).first) - position += 1.0; - - LLSettingsBase::TrackPosition start = position - (*bounds.first).first; - - return static_cast(start / spanlength); - } - - //--------------------------------------------------------------------- - class LLTrackBlenderLoopingTime : public LLSettingsBlenderTimeDelta - { - public: - LLTrackBlenderLoopingTime(const LLSettingsBase::ptr_t &target, const LLSettingsDay::ptr_t &day, S32 trackno, - LLSettingsBase::Seconds cyclelength, LLSettingsBase::Seconds cycleoffset, LLSettingsBase::Seconds updateThreshold) : - LLSettingsBlenderTimeDelta(target, LLSettingsBase::ptr_t(), LLSettingsBase::ptr_t(), LLSettingsBase::Seconds(1.0)), - mDay(day), - mTrackNo(0), - mCycleLength(cyclelength), - mCycleOffset(cycleoffset) - { - // must happen prior to getBoundingEntries call... - mTrackNo = selectTrackNumber(trackno); - - LLSettingsBase::Seconds now(getAdjustedNow()); - LLSettingsDay::TrackBound_t initial = getBoundingEntries(now); - - mInitial = (*initial.first).second; - mFinal = (*initial.second).second; - mBlendSpan = getSpanTime(initial); - - initializeTarget(now); - setOnFinished([this](const LLSettingsBlender::ptr_t &){ onFinishedSpan(); }); - } - - void switchTrack(S32 trackno, const LLSettingsBase::TrackPosition&) override - { - S32 use_trackno = selectTrackNumber(trackno); - - if (use_trackno == mTrackNo) - { // results in no change - return; - } - - LLSettingsBase::ptr_t pstartsetting = mTarget->buildDerivedClone(); - mTrackNo = use_trackno; - - LLSettingsBase::Seconds now = getAdjustedNow() + LLEnvironment::TRANSITION_ALTITUDE; - LLSettingsDay::TrackBound_t bounds = getBoundingEntries(now); - - LLSettingsBase::ptr_t pendsetting = (*bounds.first).second->buildDerivedClone(); - LLSettingsBase::TrackPosition targetpos = convert_time_to_position(now, mCycleLength) - (*bounds.first).first; - LLSettingsBase::TrackPosition targetspan = get_wrapping_distance((*bounds.first).first, (*bounds.second).first); - - LLSettingsBase::BlendFactor blendf = calculateBlend(targetpos, targetspan); - pendsetting->blend((*bounds.second).second, blendf); - - reset(pstartsetting, pendsetting, LLEnvironment::TRANSITION_ALTITUDE); - } - - protected: - S32 selectTrackNumber(S32 trackno) - { - if (trackno == 0) - { // We are dealing with the water track. There is only ever one. - return trackno; - } - - for (S32 test = trackno; test != 0; --test) - { // Find the track below the requested one with data. - LLSettingsDay::CycleTrack_t &track = mDay->getCycleTrack(test); - - if (!track.empty()) - return test; - } - - return 1; - } - - LLSettingsDay::TrackBound_t getBoundingEntries(LLSettingsBase::Seconds time) - { - LLSettingsDay::CycleTrack_t &wtrack = mDay->getCycleTrack(mTrackNo); - LLSettingsBase::TrackPosition position = convert_time_to_position(time, mCycleLength); - LLSettingsDay::TrackBound_t bounds = get_bounding_entries(wtrack, position); - return bounds; - } - - void initializeTarget(LLSettingsBase::Seconds time) - { - LLSettingsBase::BlendFactor blendf(convert_time_to_blend_factor(time, mCycleLength, mDay->getCycleTrack(mTrackNo))); - - blendf = llclamp(blendf, 0.0, 0.999); - setTimeSpent(LLSettingsBase::Seconds(blendf * mBlendSpan)); - - setBlendFactor(blendf); - } - - LLSettingsBase::Seconds getAdjustedNow() const - { - LLSettingsBase::Seconds now(LLDate::now().secondsSinceEpoch()); - - return (now + mCycleOffset); - } - - LLSettingsBase::Seconds getSpanTime(const LLSettingsDay::TrackBound_t &bounds) const - { - LLSettingsBase::Seconds span = mCycleLength * get_wrapping_distance((*bounds.first).first, (*bounds.second).first); - if (span < MINIMUM_SPANLENGTH) // for very short spans set a minimum length. - span = MINIMUM_SPANLENGTH; - return span; - } - - private: - LLSettingsDay::ptr_t mDay; - S32 mTrackNo; - LLSettingsBase::Seconds mCycleLength; - LLSettingsBase::Seconds mCycleOffset; - - void onFinishedSpan() - { - LLSettingsBase::Seconds adjusted_now = getAdjustedNow(); - LLSettingsDay::TrackBound_t next = getBoundingEntries(adjusted_now); - LLSettingsBase::Seconds nextspan = getSpanTime(next); - - reset((*next.first).second, (*next.second).second, nextspan); - - // Recalculate (reinitialize) position. Because: - // - 'delta' from applyTimeDelta accumulates errors (probably should be fixed/changed to absolute time) - // - freezes and lag can result in reset being called too late, so we need to add missed time - // - occasional time corrections can happen - // - some transition switches can happen outside applyTimeDelta thus causing 'desync' from 'delta' (can be fixed by getting rid of delta) - initializeTarget(adjusted_now); - } - }; - - class LLEnvironmentPushDispatchHandler : public LLDispatchHandler - { - public: - virtual bool operator()(const LLDispatcher *, const std::string& key, const LLUUID& invoice, const sparam_t& strings) override - { - LLSD message; - sparam_t::const_iterator it = strings.begin(); - - if (it != strings.end()) - { - const std::string& llsdRaw = *it++; - std::istringstream llsdData(llsdRaw); - if (!LLSDSerialize::deserialize(message, llsdData, llsdRaw.length())) - { - LL_WARNS() << "LLEnvironmentPushDispatchHandler: Attempted to read parameter data into LLSD but failed:" << llsdRaw << LL_ENDL; - } - } - - message[KEY_EXPERIENCEID] = invoice; - // Object Name - if (it != strings.end()) - { - message[KEY_OBJECTNAME] = *it++; - } - - // parcel Name - if (it != strings.end()) - { - message[KEY_PARCELNAME] = *it++; - } - message[KEY_COUNT] = 1; - - LLEnvironment::instance().handleEnvironmentPush(message); - return true; - } - }; - - LLEnvironmentPushDispatchHandler environment_push_dispatch_handler; - - template - class LLSettingsInjected : public SETTINGT - { - public: - typedef std::shared_ptr > ptr_t; - - LLSettingsInjected(typename SETTINGT::ptr_t source) : - SETTINGT(), - mSource(source), - mLastSourceHash(0), - mLastHash(0) - {} - - virtual ~LLSettingsInjected() {}; - - typename SETTINGT::ptr_t getSource() const { return this->mSource; } - void setSource(const typename SETTINGT::ptr_t &source) - { - if (source.get() == this) // do not set a source to itself. - return; - this->mSource = source; - this->setDirtyFlag(true); - this->mLastSourceHash = 0; - } - - virtual bool isDirty() const override { return SETTINGT::isDirty() || (this->mSource->isDirty()); } - virtual bool isVeryDirty() const override { return SETTINGT::isVeryDirty() || (this->mSource->isVeryDirty()); } - - void injectSetting(const std::string keyname, LLSD value, LLUUID experience_id, F32Seconds transition) - { - if (transition > 0.1) - { - typename Injection::ptr_t injection = std::make_shared(transition, keyname, value, true, experience_id); - - mInjections.push_back(injection); - std::stable_sort(mInjections.begin(), mInjections.end(), [](const typename Injection::ptr_t &a, const typename Injection::ptr_t &b) { return a->mTimeRemaining < b->mTimeRemaining; }); - } - else - { - mOverrideValues[keyname] = value; - mOverrideExps[keyname] = experience_id; - this->setDirtyFlag(true); - } - } - - void removeInjection(const std::string keyname, LLUUID experience, LLSettingsBase::Seconds transition) - { - injections_t injections_buf; - for (auto it = mInjections.begin(); it != mInjections.end(); it++) - { - if ((keyname.empty() || ((*it)->mKeyName == keyname)) && - (experience.isNull() || (experience == (*it)->mExperience))) - { - if (transition != LLEnvironment::TRANSITION_INSTANT) - { - typename Injection::ptr_t injection = std::make_shared(transition, keyname, (*it)->mLastValue, false, LLUUID::null); - injections_buf.push_front(injection); - } - } - else - { - injections_buf.push_front(*it); - } - } - mInjections.clear(); - mInjections = injections_buf; - - for (auto itexp = mOverrideExps.begin(); itexp != mOverrideExps.end();) - { - if (experience.isNull() || ((*itexp).second == experience)) - { - if (transition != LLEnvironment::TRANSITION_INSTANT) - { - typename Injection::ptr_t injection = std::make_shared(transition, (*itexp).first, mOverrideValues[(*itexp).first], false, LLUUID::null); - mInjections.push_front(injection); - } - mOverrideValues.erase((*itexp).first); - mOverrideExps.erase(itexp++); - } - else - ++itexp; - } - std::stable_sort(mInjections.begin(), mInjections.end(), [](const typename Injection::ptr_t &a, const typename Injection::ptr_t &b) { return a->mTimeRemaining < b->mTimeRemaining; }); - } - - void removeInjections(LLUUID experience_id, LLSettingsBase::Seconds transition) - { - removeInjection(std::string(), experience_id, transition); - } - - void injectExperienceValues(LLSD values, LLUUID experience_id, typename LLSettingsBase::Seconds transition) - { - for (auto it = values.beginMap(); it != values.endMap(); ++it) - { - injectSetting((*it).first, (*it).second, experience_id, transition); - } - this->setDirtyFlag(true); - } - - void applyInjections(LLSettingsBase::Seconds delta) - { - this->mSettings = this->mSource->getSettings(); - - for (auto ito = mOverrideValues.beginMap(); ito != mOverrideValues.endMap(); ++ito) - { - this->mSettings[(*ito).first] = (*ito).second; - } - - const LLSettingsBase::stringset_t &slerps = this->getSlerpKeys(); - const LLSettingsBase::stringset_t &skips = this->getSkipInterpolateKeys(); - const LLSettingsBase::stringset_t &specials = this->getSpecialKeys(); - - typename injections_t::iterator it; - for (it = mInjections.begin(); it != mInjections.end(); ++it) - { - std::string key_name = (*it)->mKeyName; - - LLSD value = this->mSettings[key_name]; - LLSD target = (*it)->mValue; - - if ((*it)->mFirstTime) - (*it)->mFirstTime = false; - else - (*it)->mTimeRemaining -= delta; - - typename LLSettingsBase::BlendFactor mix = 1.0f - ((*it)->mTimeRemaining.value() / (*it)->mTransition.value()); - - if (mix >= 1.0) - { - if ((*it)->mBlendIn) - { - mOverrideValues[key_name] = target; - mOverrideExps[key_name] = (*it)->mExperience; - this->mSettings[key_name] = target; - } - else - { - this->mSettings.erase(key_name); - } - } - else if (specials.find(key_name) != specials.end()) - { - updateSpecial(*it, mix); - } - else if (skips.find(key_name) == skips.end()) - { - if (!(*it)->mBlendIn) - mix = 1.0 - mix; - (*it)->mLastValue = this->interpolateSDValue(key_name, value, target, this->getParameterMap(), mix, slerps); - this->mSettings[key_name] = (*it)->mLastValue; - } - } - - size_t hash = this->getHash(); - - if (hash != mLastHash) - { - this->setDirtyFlag(true); - mLastHash = hash; - } - - it = mInjections.begin(); - it = std::find_if(mInjections.begin(), mInjections.end(), [](const typename Injection::ptr_t &a) { return a->mTimeRemaining > 0.0f; }); - - if (it != mInjections.begin()) - { - mInjections.erase(mInjections.begin(), mInjections.end()); - } - - } - - bool hasInjections() const - { - return (!mInjections.empty() || (mOverrideValues.size() > 0)); - } - - protected: - struct Injection - { - Injection(typename LLSettingsBase::Seconds transition, const std::string &keyname, LLSD value, bool blendin, LLUUID experince, S32 index = -1) : - mTransition(transition), - mTimeRemaining(transition), - mKeyName(keyname), - mValue(value), - mExperience(experince), - mIndex(index), - mBlendIn(blendin), - mFirstTime(true) - {} - - typename LLSettingsBase::Seconds mTransition; - typename LLSettingsBase::Seconds mTimeRemaining; - std::string mKeyName; - LLSD mValue; - LLSD mLastValue; - LLUUID mExperience; - S32 mIndex; - bool mBlendIn; - bool mFirstTime; - - typedef std::shared_ptr ptr_t; - }; - - - virtual void updateSettings() override - { - static LLFrameTimer timer; - - if (!this->mSource) - return; - - // clears the dirty flag on this object. Need to prevent triggering - // more calls into this updateSettings - LLSettingsBase::updateSettings(); - - resetSpecial(); - - if (this->mSource->isDirty()) - { - this->mSource->updateSettings(); - } - - typename LLSettingsBase::Seconds delta(timer.getElapsedTimeAndResetF32()); - - - SETTINGT::updateSettings(); - - if (!mInjections.empty()) - this->setDirtyFlag(true); - } - - LLSettingsBase::stringset_t getSpecialKeys() const; - void resetSpecial(); - void updateSpecial(const typename Injection::ptr_t &injection, typename LLSettingsBase::BlendFactor mix); - - private: - typedef std::map key_to_expid_t; - typedef std::deque injections_t; - - size_t mLastSourceHash; - size_t mLastHash; - typename SETTINGT::ptr_t mSource; - injections_t mInjections; - LLSD mOverrideValues; - key_to_expid_t mOverrideExps; - }; - - template<> - LLSettingsBase::stringset_t LLSettingsInjected::getSpecialKeys() const - { - static LLSettingsBase::stringset_t specialSet; - - if (specialSet.empty()) - { - specialSet.insert(SETTING_BLOOM_TEXTUREID); - specialSet.insert(SETTING_RAINBOW_TEXTUREID); - specialSet.insert(SETTING_HALO_TEXTUREID); - specialSet.insert(SETTING_CLOUD_TEXTUREID); - specialSet.insert(SETTING_MOON_TEXTUREID); - specialSet.insert(SETTING_SUN_TEXTUREID); - specialSet.insert(SETTING_CLOUD_SHADOW); // due to being part of skips - } - return specialSet; - } - - template<> - LLSettingsBase::stringset_t LLSettingsInjected::getSpecialKeys() const - { - static stringset_t specialSet; - - if (specialSet.empty()) - { - specialSet.insert(SETTING_TRANSPARENT_TEXTURE); - specialSet.insert(SETTING_NORMAL_MAP); - } - return specialSet; - } - - template<> - void LLSettingsInjected::resetSpecial() - { - mNextSunTextureId.setNull(); - mNextMoonTextureId.setNull(); - mNextCloudTextureId.setNull(); - mNextBloomTextureId.setNull(); - mNextRainbowTextureId.setNull(); - mNextHaloTextureId.setNull(); - setBlendFactor(0.0f); - } - - template<> - void LLSettingsInjected::resetSpecial() - { - mNextNormalMapID.setNull(); - mNextTransparentTextureID.setNull(); - setBlendFactor(0.0f); - } - - template<> - void LLSettingsInjected::updateSpecial(const typename LLSettingsInjected::Injection::ptr_t &injection, typename LLSettingsBase::BlendFactor mix) - { - bool is_texture = true; - if (injection->mKeyName == SETTING_SUN_TEXTUREID) - { - mNextSunTextureId = injection->mValue.asUUID(); - } - else if (injection->mKeyName == SETTING_MOON_TEXTUREID) - { - mNextMoonTextureId = injection->mValue.asUUID(); - } - else if (injection->mKeyName == SETTING_CLOUD_TEXTUREID) - { - mNextCloudTextureId = injection->mValue.asUUID(); - } - else if (injection->mKeyName == SETTING_BLOOM_TEXTUREID) - { - mNextBloomTextureId = injection->mValue.asUUID(); - } - else if (injection->mKeyName == SETTING_RAINBOW_TEXTUREID) - { - mNextRainbowTextureId = injection->mValue.asUUID(); - } - else if (injection->mKeyName == SETTING_HALO_TEXTUREID) - { - mNextHaloTextureId = injection->mValue.asUUID(); - } - else if (injection->mKeyName == LLSettingsSky::SETTING_CLOUD_SHADOW) - { - // Special case due to being texture dependent and part of skips - is_texture = false; - if (!injection->mBlendIn) - mix = 1.0 - mix; - stringset_t dummy; - F64 value = this->mSettings[injection->mKeyName].asReal(); - if (this->getCloudNoiseTextureId().isNull()) - { - value = 0; // there was no texture so start from zero coverage - } - // Ideally we need to check for texture in injection, but - // in this case user is setting value explicitly, potentially - // with different transitions, don't ignore it - F64 result = lerp(value, injection->mValue.asReal(), mix); - injection->mLastValue = LLSD::Real(result); - this->mSettings[injection->mKeyName] = injection->mLastValue; - } - - // Unfortunately I don't have a per texture blend factor. We'll just pick the one that is furthest along. - if (is_texture && getBlendFactor() < mix) - { - setBlendFactor(mix); - } - } - - template<> - void LLSettingsInjected::updateSpecial(const typename LLSettingsInjected::Injection::ptr_t &injection, typename LLSettingsBase::BlendFactor mix) - { - if (injection->mKeyName == SETTING_NORMAL_MAP) - { - mNextNormalMapID = injection->mValue.asUUID(); - } - else if (injection->mKeyName == SETTING_TRANSPARENT_TEXTURE) - { - mNextTransparentTextureID = injection->mValue.asUUID(); - } - - // Unfortunately I don't have a per texture blend factor. We'll just pick the one that is furthest along. - if (getBlendFactor() < mix) - { - setBlendFactor(mix); - } - } - - typedef LLSettingsInjected LLSettingsInjectedSky; - typedef LLSettingsInjected LLSettingsInjectedWater; - - //===================================================================== - class DayInjection : public LLEnvironment::DayInstance - { - friend class InjectedTransition; - - public: - typedef std::shared_ptr ptr_t; - typedef std::weak_ptr wptr_t; - - DayInjection(LLEnvironment::EnvSelection_t env); - virtual ~DayInjection(); - - virtual bool applyTimeDelta(const LLSettingsBase::Seconds& delta) override; - - void setInjectedDay(const LLSettingsDay::ptr_t &pday, LLUUID experience_id, LLSettingsBase::Seconds transition); - void setInjectedSky(const LLSettingsSky::ptr_t &psky, LLUUID experience_id, LLSettingsBase::Seconds transition); - void setInjectedWater(const LLSettingsWater::ptr_t &pwater, LLUUID experience_id, LLSettingsBase::Seconds transition); - - void injectSkySettings(LLSD settings, LLUUID experience_id, LLSettingsBase::Seconds transition); - void injectWaterSettings(LLSD settings, LLUUID experience_id, LLSettingsBase::Seconds transition); - - void clearInjections(LLUUID experience_id, LLSettingsBase::Seconds transition_time); - - virtual void animate() override; - - LLEnvironment::DayInstance::ptr_t getBaseDayInstance() const { return mBaseDayInstance; } - void setBaseDayInstance(const LLEnvironment::DayInstance::ptr_t &baseday); - - S32 countExperiencesActive() const { return mActiveExperiences.size(); } - - bool isOverriddenSky() const { return !mSkyExperience.isNull(); } - bool isOverriddenWater() const { return !mWaterExperience.isNull(); } - - bool hasInjections() const; - - void testExperiencesOnParcel(S32 parcel_id); - private: - static void testExperiencesOnParcelCoro(wptr_t that, S32 parcel_id); - - - void animateSkyChange(LLSettingsSky::ptr_t psky, LLSettingsBase::Seconds transition); - void animateWaterChange(LLSettingsWater::ptr_t pwater, LLSettingsBase::Seconds transition); - - void onEnvironmentChanged(LLEnvironment::EnvSelection_t env); - void onParcelChange(); - - void checkExperience(); - - - LLEnvironment::DayInstance::ptr_t mBaseDayInstance; - - LLSettingsInjectedSky::ptr_t mInjectedSky; - LLSettingsInjectedWater::ptr_t mInjectedWater; - std::set mActiveExperiences; - LLUUID mDayExperience; - LLUUID mSkyExperience; - LLUUID mWaterExperience; - LLEnvironment::connection_t mEnvChangeConnection; - boost::signals2::connection mParcelChangeConnection; - }; - - class InjectedTransition : public LLEnvironment::DayTransition - { - public: - InjectedTransition(const DayInjection::ptr_t &injection, const LLSettingsSky::ptr_t &skystart, const LLSettingsWater::ptr_t &waterstart, DayInstance::ptr_t &end, LLSettingsDay::Seconds time): - LLEnvironment::DayTransition(skystart, waterstart, end, time), - mInjection(injection) - { } - virtual ~InjectedTransition() { }; - - virtual void animate() override; - - protected: - DayInjection::ptr_t mInjection; - }; - -} - -//========================================================================= -const F64Seconds LLEnvironment::TRANSITION_INSTANT(0.0f); -const F64Seconds LLEnvironment::TRANSITION_FAST(1.0f); -const F64Seconds LLEnvironment::TRANSITION_DEFAULT(5.0f); -const F64Seconds LLEnvironment::TRANSITION_SLOW(10.0f); -const F64Seconds LLEnvironment::TRANSITION_ALTITUDE(5.0f); - -const LLUUID LLEnvironment::KNOWN_SKY_SUNRISE("01e41537-ff51-2f1f-8ef7-17e4df760bfb"); -const LLUUID LLEnvironment::KNOWN_SKY_MIDDAY("c46226b4-0e43-5a56-9708-d27ca1df3292"); -const LLUUID LLEnvironment::KNOWN_SKY_LEGACY_MIDDAY("cef49723-0292-af49-9b14-9598a616b8a3"); -const LLUUID LLEnvironment::KNOWN_SKY_SUNSET("084e26cd-a900-28e8-08d0-64a9de5c15e2"); -const LLUUID LLEnvironment::KNOWN_SKY_MIDNIGHT("8a01b97a-cb20-c1ea-ac63-f7ea84ad0090"); - -const S32 LLEnvironment::NO_TRACK(-1); -const S32 LLEnvironment::NO_VERSION(-3); // For viewer sided change, like ENV_LOCAL. -3 since -1 and -2 are taken by parcel initial server/viewer version -const S32 LLEnvironment::VERSION_CLEANUP(-4); // for cleanups - -const F32 LLEnvironment::SUN_DELTA_YAW(F_PI); // 180deg - - -const U32 LLEnvironment::DayInstance::NO_ANIMATE_SKY(0x01); -const U32 LLEnvironment::DayInstance::NO_ANIMATE_WATER(0x02); - -std::string env_selection_to_string(LLEnvironment::EnvSelection_t sel) -{ -#define RTNENUM(E) case LLEnvironment::E: return #E - switch (sel){ - RTNENUM(ENV_EDIT); - RTNENUM(ENV_LOCAL); - RTNENUM(ENV_PUSH); - RTNENUM(ENV_PARCEL); - RTNENUM(ENV_REGION); - RTNENUM(ENV_DEFAULT); - RTNENUM(ENV_END); - RTNENUM(ENV_CURRENT); - RTNENUM(ENV_NONE); - default: - return llformat("Unknown(%d)", sel); - } -#undef RTNENUM -} - -//------------------------------------------------------------------------- -LLEnvironment::LLEnvironment(): - mCloudScrollDelta(), - mCloudScrollPaused(false), - mSelectedSky(), - mSelectedWater(), - mSelectedDay(), - mSelectedEnvironment(LLEnvironment::ENV_LOCAL), - mCurrentTrack(1), - mEditorCounter(0), - mShowSunBeacon(false), - mShowMoonBeacon(false) -{ -} - -void LLEnvironment::initSingleton() -{ - LLSettingsSky::ptr_t p_default_sky = LLSettingsVOSky::buildDefaultSky(); - LLSettingsWater::ptr_t p_default_water = LLSettingsVOWater::buildDefaultWater(); - - mCurrentEnvironment = std::make_shared(ENV_DEFAULT); - mCurrentEnvironment->setSky(p_default_sky); - mCurrentEnvironment->setWater(p_default_water); - - mEnvironments[ENV_DEFAULT] = mCurrentEnvironment; - - requestRegion(); - - if (!mParcelCallbackConnection.connected()) - { - mParcelCallbackConnection = gAgent.addParcelChangedCallback([this]() { onParcelChange(); }); - - //TODO: This frequently results in one more request than we need. It isn't breaking, but should be nicer. - // We need to know new env version to fix this, without it we can only do full re-request - // Happens: on updates, on opening LLFloaterRegionInfo, on region crossing if info floater is open - mRegionUpdateCallbackConnection = LLRegionInfoModel::instance().setUpdateCallback([this]() { requestRegion(); }); - mRegionChangeCallbackConnection = gAgent.addRegionChangedCallback([this]() { onRegionChange(); }); - - mPositionCallbackConnection = gAgent.whenPositionChanged([this](const LLVector3 &localpos, const LLVector3d &) { onAgentPositionHasChanged(localpos); }); - } - - if (!gGenericDispatcher.isHandlerPresent(MESSAGE_PUSHENVIRONMENT)) - { - gGenericDispatcher.addHandler(MESSAGE_PUSHENVIRONMENT, &environment_push_dispatch_handler); - } - - gSavedSettings.getControl("RenderSkyAutoAdjustProbeAmbiance")->getSignal()->connect( - [](LLControlVariable*, const LLSD& new_val, const LLSD& old_val) - { - LLSettingsSky::sAutoAdjustProbeAmbiance = new_val.asReal(); - } - ); - LLSettingsSky::sAutoAdjustProbeAmbiance = gSavedSettings.getF32("RenderSkyAutoAdjustProbeAmbiance"); - - LLEventPumps::instance().obtain(PUMP_EXPERIENCE).stopListening(LISTENER_NAME); - LLEventPumps::instance().obtain(PUMP_EXPERIENCE).listen(LISTENER_NAME, [this](LLSD message) { listenExperiencePump(message); return false; }); -} - -void LLEnvironment::cleanupSingleton() -{ - if (mParcelCallbackConnection.connected()) - { - mParcelCallbackConnection.disconnect(); - mRegionUpdateCallbackConnection.disconnect(); - mRegionChangeCallbackConnection.disconnect(); - mPositionCallbackConnection.disconnect(); - } - LLEventPumps::instance().obtain(PUMP_EXPERIENCE).stopListening(LISTENER_NAME); -} - -LLEnvironment::~LLEnvironment() -{ - cleanupSingleton(); -} - -bool LLEnvironment::canEdit() const -{ - return true; -} - -LLSettingsSky::ptr_t LLEnvironment::getCurrentSky() const -{ - LLSettingsSky::ptr_t psky = mCurrentEnvironment->getSky(); - - if (!psky && mCurrentEnvironment->getEnvironmentSelection() >= ENV_EDIT) - { - for (int idx = 0; idx < ENV_END; ++idx) - { - if (mEnvironments[idx]->getSky()) - { - psky = mEnvironments[idx]->getSky(); - break; - } - } - } - return psky; -} - -LLSettingsWater::ptr_t LLEnvironment::getCurrentWater() const -{ - LLSettingsWater::ptr_t pwater = mCurrentEnvironment->getWater(); - - if (!pwater && mCurrentEnvironment->getEnvironmentSelection() >= ENV_EDIT) - { - for (int idx = 0; idx < ENV_END; ++idx) - { - if (mEnvironments[idx]->getWater()) - { - pwater = mEnvironments[idx]->getWater(); - break; - } - } - } - return pwater; -} - -void LayerConfigToDensityLayer(const LLSD& layerConfig, DensityLayer& layerOut) -{ - layerOut.constant_term = layerConfig[LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM].asReal(); - layerOut.exp_scale = layerConfig[LLSettingsSky::SETTING_DENSITY_PROFILE_EXP_SCALE_FACTOR].asReal(); - layerOut.exp_term = layerConfig[LLSettingsSky::SETTING_DENSITY_PROFILE_EXP_TERM].asReal(); - layerOut.linear_term = layerConfig[LLSettingsSky::SETTING_DENSITY_PROFILE_LINEAR_TERM].asReal(); - layerOut.width = layerConfig[LLSettingsSky::SETTING_DENSITY_PROFILE_WIDTH].asReal(); -} - -void LLEnvironment::getAtmosphericModelSettings(AtmosphericModelSettings& settingsOut, const LLSettingsSky::ptr_t &psky) -{ - settingsOut.m_skyBottomRadius = psky->getSkyBottomRadius(); - settingsOut.m_skyTopRadius = psky->getSkyTopRadius(); - settingsOut.m_sunArcRadians = psky->getSunArcRadians(); - settingsOut.m_mieAnisotropy = psky->getMieAnisotropy(); - - LLSD rayleigh = psky->getRayleighConfigs(); - settingsOut.m_rayleighProfile.clear(); - for (LLSD::array_iterator itf = rayleigh.beginArray(); itf != rayleigh.endArray(); ++itf) - { - DensityLayer layer; - LLSD& layerConfig = (*itf); - LayerConfigToDensityLayer(layerConfig, layer); - settingsOut.m_rayleighProfile.push_back(layer); - } - - LLSD mie = psky->getMieConfigs(); - settingsOut.m_mieProfile.clear(); - for (LLSD::array_iterator itf = mie.beginArray(); itf != mie.endArray(); ++itf) - { - DensityLayer layer; - LLSD& layerConfig = (*itf); - LayerConfigToDensityLayer(layerConfig, layer); - settingsOut.m_mieProfile.push_back(layer); - } - settingsOut.m_mieAnisotropy = psky->getMieAnisotropy(); - - LLSD absorption = psky->getAbsorptionConfigs(); - settingsOut.m_absorptionProfile.clear(); - for (LLSD::array_iterator itf = absorption.beginArray(); itf != absorption.endArray(); ++itf) - { - DensityLayer layer; - LLSD& layerConfig = (*itf); - LayerConfigToDensityLayer(layerConfig, layer); - settingsOut.m_absorptionProfile.push_back(layer); - } -} - -bool LLEnvironment::canAgentUpdateParcelEnvironment() const -{ - LLParcel *parcel(LLViewerParcelMgr::instance().getAgentOrSelectedParcel()); - - return canAgentUpdateParcelEnvironment(parcel); -} - - -bool LLEnvironment::canAgentUpdateParcelEnvironment(LLParcel *parcel) const -{ - if (!parcel) - return false; - - if (!LLEnvironment::instance().isExtendedEnvironmentEnabled()) - return false; - - if (gAgent.isGodlike()) - return true; - - if (!parcel->getRegionAllowEnvironmentOverride()) - return false; - - return LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_ALLOW_ENVIRONMENT); -} - -bool LLEnvironment::canAgentUpdateRegionEnvironment() const -{ - if (gAgent.isGodlike()) - return true; - - return gAgent.canManageEstate(); -} - -bool LLEnvironment::isExtendedEnvironmentEnabled() const -{ - return !gAgent.getRegionCapability("ExtEnvironment").empty(); -} - -bool LLEnvironment::isInventoryEnabled() const -{ - return (!gAgent.getRegionCapability("UpdateSettingsAgentInventory").empty() && - !gAgent.getRegionCapability("UpdateSettingsTaskInventory").empty()); -} - -void LLEnvironment::onRegionChange() -{ -// if (gAgent.getRegionCapability("ExperienceQuery").empty()) -// { -// // for now environmental experiences do not survive region crossings - clearExperienceEnvironment(LLUUID::null, TRANSITION_DEFAULT); -// } - - LLViewerRegion* cur_region = gAgent.getRegion(); - if (!cur_region) - { - return; - } - if (!cur_region->capabilitiesReceived()) - { - cur_region->setCapabilitiesReceivedCallback([](const LLUUID ®ion_id, LLViewerRegion* regionp) { LLEnvironment::instance().requestRegion(); }); - return; - } - requestRegion(); -} - -void LLEnvironment::onParcelChange() -{ - S32 parcel_id(INVALID_PARCEL_ID); - LLParcel* parcel = LLViewerParcelMgr::instance().getAgentParcel(); - - if (parcel) - { - parcel_id = parcel->getLocalID(); - } - - requestParcel(parcel_id); -} - -//------------------------------------------------------------------------- -F32 LLEnvironment::getCamHeight() const -{ - return (mCurrentEnvironment->getSky()->getDomeOffset() * mCurrentEnvironment->getSky()->getDomeRadius()); -} - -F32 LLEnvironment::getWaterHeight() const -{ - LLViewerRegion* cur_region = gAgent.getRegion(); - return cur_region ? cur_region->getWaterHeight() : DEFAULT_WATER_HEIGHT; -} - -bool LLEnvironment::getIsSunUp() const -{ - if (!mCurrentEnvironment || !mCurrentEnvironment->getSky()) - return false; - return mCurrentEnvironment->getSky()->getIsSunUp(); -} - -bool LLEnvironment::getIsMoonUp() const -{ - if (!mCurrentEnvironment || !mCurrentEnvironment->getSky()) - return false; - return mCurrentEnvironment->getSky()->getIsMoonUp(); -} - -//------------------------------------------------------------------------- -void LLEnvironment::setSelectedEnvironment(LLEnvironment::EnvSelection_t env, LLSettingsBase::Seconds transition, bool forced) -{ - mSelectedEnvironment = env; - updateEnvironment(transition, forced); - LL_DEBUGS("ENVIRONMENT") << "Setting environment " << env_selection_to_string(env) << " with transition: " << transition << LL_ENDL; -} - -bool LLEnvironment::hasEnvironment(LLEnvironment::EnvSelection_t env) -{ - if ((env < ENV_EDIT) || (env >= ENV_DEFAULT) || (!mEnvironments[env])) - { - return false; - } - - return true; -} - -LLEnvironment::DayInstance::ptr_t LLEnvironment::getEnvironmentInstance(LLEnvironment::EnvSelection_t env, bool create /*= false*/) -{ - DayInstance::ptr_t environment = mEnvironments[env]; - if (create) - { - if (environment) - environment = environment->clone(); - else - { - if (env == ENV_PUSH) - environment = std::make_shared(env); - else - environment = std::make_shared(env); - } - mEnvironments[env] = environment; - } - - return environment; -} - - -void LLEnvironment::setEnvironment(LLEnvironment::EnvSelection_t env, const LLSettingsDay::ptr_t &pday, LLSettingsDay::Seconds daylength, LLSettingsDay::Seconds dayoffset, S32 env_version) -{ - if ((env < ENV_EDIT) || (env >= ENV_DEFAULT)) - { - LL_WARNS("ENVIRONMENT") << "Attempt to change invalid environment selection (" << env_selection_to_string(env) << ")." << LL_ENDL; - return; - } - - logEnvironment(env, pday, env_version); - - DayInstance::ptr_t environment = getEnvironmentInstance(env, true); - - environment->clear(); - environment->setDay(pday, daylength, dayoffset); - environment->setSkyTrack(mCurrentTrack); - environment->animate(); - - if (!mSignalEnvChanged.empty()) - mSignalEnvChanged(env, env_version); -} - -void LLEnvironment::setCurrentEnvironmentSelection(LLEnvironment::EnvSelection_t env) -{ - mCurrentEnvironment->setEnvironmentSelection(env); -} - -void LLEnvironment::setEnvironment(LLEnvironment::EnvSelection_t env, LLEnvironment::fixedEnvironment_t fixed, S32 env_version) -{ - if ((env < ENV_EDIT) || (env >= ENV_DEFAULT)) - { - LL_WARNS("ENVIRONMENT") << "Attempt to change invalid environment selection (" << env_selection_to_string(env) << ")." << LL_ENDL; - return; - } - - bool reset_probes = false; - - DayInstance::ptr_t environment = getEnvironmentInstance(env, true); - - if (fixed.first) - { - logEnvironment(env, fixed.first, env_version); - reset_probes = environment->setSky(fixed.first); - environment->setFlags(DayInstance::NO_ANIMATE_SKY); - } - else if (!environment->getSky()) - { - if (mCurrentEnvironment->getEnvironmentSelection() != ENV_NONE) - { - // Note: This looks suspicious. Shouldn't we assign whole day if mCurrentEnvironment has whole day? - // and then add water/sky on top - // This looks like it will result in sky using single keyframe instead of whole day if day is present - // when setting static water without static sky - reset_probes = environment->setSky(mCurrentEnvironment->getSky()); - environment->setFlags(DayInstance::NO_ANIMATE_SKY); - } - else - { - // Environment is not properly initialized yet, but we should have environment by this point - DayInstance::ptr_t substitute = getEnvironmentInstance(ENV_PARCEL, true); - if (!substitute || !substitute->getSky()) - { - substitute = getEnvironmentInstance(ENV_REGION, true); - } - if (!substitute || !substitute->getSky()) - { - substitute = getEnvironmentInstance(ENV_DEFAULT, true); - } - - if (substitute && substitute->getSky()) - { - reset_probes = environment->setSky(substitute->getSky()); - environment->setFlags(DayInstance::NO_ANIMATE_SKY); - } - else - { - LL_WARNS("ENVIRONMENT") << "Failed to assign substitute water/sky, environment is not properly initialized" << LL_ENDL; - } - } - } - - if (fixed.second) - { - logEnvironment(env, fixed.second, env_version); - environment->setWater(fixed.second); - environment->setFlags(DayInstance::NO_ANIMATE_WATER); - } - else if (!environment->getWater()) - { - if (mCurrentEnvironment->getEnvironmentSelection() != ENV_NONE) - { - // Note: This looks suspicious. Shouldn't we assign whole day if mCurrentEnvironment has whole day? - // and then add water/sky on top - // This looks like it will result in water using single keyframe instead of whole day if day is present - // when setting static sky without static water - environment->setWater(mCurrentEnvironment->getWater()); - environment->setFlags(DayInstance::NO_ANIMATE_WATER); - } - else - { - // Environment is not properly initialized yet, but we should have environment by this point - DayInstance::ptr_t substitute = getEnvironmentInstance(ENV_PARCEL, true); - if (!substitute || !substitute->getWater()) - { - substitute = getEnvironmentInstance(ENV_REGION, true); - } - if (!substitute || !substitute->getWater()) - { - substitute = getEnvironmentInstance(ENV_DEFAULT, true); - } - - if (substitute && substitute->getWater()) - { - environment->setWater(substitute->getWater()); - environment->setFlags(DayInstance::NO_ANIMATE_WATER); - } - else - { - LL_WARNS("ENVIRONMENT") << "Failed to assign substitute water/sky, environment is not properly initialized" << LL_ENDL; - } - } - } - - if (reset_probes) - { // the sky changed in a way that merits a reset of reflection probes - gPipeline.mReflectionMapManager.reset(); - } - - if (!mSignalEnvChanged.empty()) - mSignalEnvChanged(env, env_version); -} - -void LLEnvironment::setEnvironment(LLEnvironment::EnvSelection_t env, const LLSettingsBase::ptr_t &settings, S32 env_version) -{ - DayInstance::ptr_t environment = getEnvironmentInstance(env); - - if (env == ENV_DEFAULT) - { - LL_WARNS("ENVIRONMENT") << "Attempt to set default environment. Not allowed." << LL_ENDL; - return; - } - - if (!settings) - { - clearEnvironment(env); - return; - } - - if (settings->getSettingsType() == "daycycle") - { - LLSettingsDay::Seconds daylength(LLSettingsDay::DEFAULT_DAYLENGTH); - LLSettingsDay::Seconds dayoffset(LLSettingsDay::DEFAULT_DAYOFFSET); - if (environment) - { - daylength = environment->getDayLength(); - dayoffset = environment->getDayOffset(); - } - setEnvironment(env, std::static_pointer_cast(settings), daylength, dayoffset); - } - else if (settings->getSettingsType() == "sky") - { - fixedEnvironment_t fixedenv(std::static_pointer_cast(settings), LLSettingsWater::ptr_t()); - setEnvironment(env, fixedenv); - } - else if (settings->getSettingsType() == "water") - { - fixedEnvironment_t fixedenv(LLSettingsSky::ptr_t(), std::static_pointer_cast(settings)); - setEnvironment(env, fixedenv); - } -} - -void LLEnvironment::setEnvironment(EnvSelection_t env, const LLUUID &assetId, S32 env_version) -{ - setEnvironment(env, assetId, TRANSITION_DEFAULT, env_version); -} - -void LLEnvironment::setEnvironment(EnvSelection_t env, - const LLUUID &assetId, - LLSettingsBase::Seconds transition, - S32 env_version) -{ - LLSettingsVOBase::getSettingsAsset(assetId, - [this, env, env_version, transition](LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status, LLExtStat) - { - onSetEnvAssetLoaded(env, asset_id, settings, transition, status, env_version); - }); -} - -void LLEnvironment::onSetEnvAssetLoaded(EnvSelection_t env, - LLUUID asset_id, - LLSettingsBase::ptr_t settings, - LLSettingsBase::Seconds transition, - S32 status, - S32 env_version) -{ - if (!settings || status) - { - LLSD args; - args["NAME"] = asset_id.asString(); - LLNotificationsUtil::add("FailedToFindSettings", args); - LL_DEBUGS("ENVIRONMENT") << "Failed to find settings for " << env_selection_to_string(env) << ", asset_id: " << asset_id << LL_ENDL; - return; - } - LL_DEBUGS("ENVIRONMENT") << "Loaded asset: " << asset_id << LL_ENDL; - - setEnvironment(env, settings); - updateEnvironment(transition); -} - -void LLEnvironment::clearEnvironment(LLEnvironment::EnvSelection_t env) -{ - if ((env < ENV_EDIT) || (env >= ENV_DEFAULT)) - { - LL_WARNS("ENVIRONMENT") << "Attempt to change invalid environment selection." << LL_ENDL; - return; - } - - LL_DEBUGS("ENVIRONMENT") << "Cleaning environment " << env_selection_to_string(env) << LL_ENDL; - - mEnvironments[env].reset(); - - if (!mSignalEnvChanged.empty()) - mSignalEnvChanged(env, VERSION_CLEANUP); -} - -void LLEnvironment::logEnvironment(EnvSelection_t env, const LLSettingsBase::ptr_t &settings, S32 env_version) -{ - LL_DEBUGS("ENVIRONMENT") << "Setting Day environment " << env_selection_to_string(env) << " with version(update type): " << env_version << LL_NEWLINE; - // code between LL_DEBUGS and LL_ENDL won't execute unless log is enabled - if (settings) - { - LLUUID asset_id = settings->getAssetId(); - if (asset_id.notNull()) - { - LL_CONT << "Asset id: " << asset_id << LL_NEWLINE; - } - - LLUUID id = settings->getId(); // Not in use? - if (id.notNull()) - { - LL_CONT << "Settings id: " << id << LL_NEWLINE; - } - - LL_CONT << "Name: " << settings->getName() << LL_NEWLINE - << "Type: " << settings->getSettingsType() << LL_NEWLINE - << "Flags: " << settings->getFlags(); // Not in use? - } - else - { - LL_CONT << "Empty settings!"; - } - LL_CONT << LL_ENDL; -} - -LLSettingsDay::ptr_t LLEnvironment::getEnvironmentDay(LLEnvironment::EnvSelection_t env) -{ - if ((env < ENV_EDIT) || (env > ENV_DEFAULT)) - { - LL_WARNS("ENVIRONMENT") << "Attempt to retrieve invalid environment selection (" << env_selection_to_string(env) << ")." << LL_ENDL; - return LLSettingsDay::ptr_t(); - } - - DayInstance::ptr_t environment = getEnvironmentInstance(env); - - if (environment) - return environment->getDayCycle(); - - return LLSettingsDay::ptr_t(); -} - -LLSettingsDay::Seconds LLEnvironment::getEnvironmentDayLength(EnvSelection_t env) -{ - if ((env < ENV_EDIT) || (env > ENV_DEFAULT)) - { - LL_WARNS("ENVIRONMENT") << "Attempt to retrieve invalid environment selection (" << env_selection_to_string(env) << ")." << LL_ENDL; - return LLSettingsDay::Seconds(0); - } - - DayInstance::ptr_t environment = getEnvironmentInstance(env); - - if (environment) - return environment->getDayLength(); - - return LLSettingsDay::Seconds(0); -} - -LLSettingsDay::Seconds LLEnvironment::getEnvironmentDayOffset(EnvSelection_t env) -{ - if ((env < ENV_EDIT) || (env > ENV_DEFAULT)) - { - LL_WARNS("ENVIRONMENT") << "Attempt to retrieve invalid environment selection (" << env_selection_to_string(env) << ")." << LL_ENDL; - return LLSettingsDay::Seconds(0); - } - - DayInstance::ptr_t environment = getEnvironmentInstance(env); - if (environment) - return environment->getDayOffset(); - - return LLSettingsDay::Seconds(0); -} - - -LLEnvironment::fixedEnvironment_t LLEnvironment::getEnvironmentFixed(LLEnvironment::EnvSelection_t env, bool resolve) -{ - if ((env == ENV_CURRENT) || resolve) - { - fixedEnvironment_t fixed; - for (S32 idx = ((resolve) ? env : mSelectedEnvironment); idx < ENV_END; ++idx) - { - if (fixed.first && fixed.second) - break; - - if (idx == ENV_EDIT) - continue; // skip the edit environment. - - DayInstance::ptr_t environment = getEnvironmentInstance(static_cast(idx)); - if (environment) - { - if (!fixed.first) - fixed.first = environment->getSky(); - if (!fixed.second) - fixed.second = environment->getWater(); - } - } - - if (!fixed.first || !fixed.second) - LL_WARNS("ENVIRONMENT") << "Can not construct complete fixed environment. Missing Sky and/or Water." << LL_ENDL; - - return fixed; - } - - if ((env < ENV_EDIT) || (env > ENV_DEFAULT)) - { - LL_WARNS("ENVIRONMENT") << "Attempt to retrieve invalid environment selection (" << env_selection_to_string(env) << ")." << LL_ENDL; - return fixedEnvironment_t(); - } - - DayInstance::ptr_t environment = getEnvironmentInstance(env); - - if (environment) - return fixedEnvironment_t(environment->getSky(), environment->getWater()); - - return fixedEnvironment_t(); -} - -LLEnvironment::DayInstance::ptr_t LLEnvironment::getSelectedEnvironmentInstance() -{ - for (S32 idx = mSelectedEnvironment; idx < ENV_DEFAULT; ++idx) - { - if (mEnvironments[idx]) - return mEnvironments[idx]; - } - - return mEnvironments[ENV_DEFAULT]; -} - -LLEnvironment::DayInstance::ptr_t LLEnvironment::getSharedEnvironmentInstance() -{ - for (S32 idx = ENV_PARCEL; idx < ENV_DEFAULT; ++idx) - { - if (mEnvironments[idx]) - return mEnvironments[idx]; - } - - return mEnvironments[ENV_DEFAULT]; -} - -void LLEnvironment::updateEnvironment(LLSettingsBase::Seconds transition, bool forced) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT; - DayInstance::ptr_t pinstance = getSelectedEnvironmentInstance(); - - if ((mCurrentEnvironment != pinstance) || forced) - { - if (transition != TRANSITION_INSTANT) - { - DayInstance::ptr_t trans = std::make_shared( - mCurrentEnvironment->getSky(), mCurrentEnvironment->getWater(), pinstance, transition); - - trans->animate(); - - mCurrentEnvironment = trans; - } - else - { - mCurrentEnvironment = pinstance; - } - - updateSettingsUniforms(); - } -} - -LLVector4 LLEnvironment::toCFR(const LLVector3 vec) const -{ - LLVector4 vec_cfr(vec.mV[1], vec.mV[0], vec.mV[2], 0.0f); - return vec_cfr; -} - -LLVector4 LLEnvironment::toLightNorm(const LLVector3 vec) const -{ - LLVector4 vec_ogl(vec.mV[1], vec.mV[2], vec.mV[0], 0.0f); - return vec_ogl; -} - -LLVector3 LLEnvironment::getLightDirection() const -{ - LLSettingsSky::ptr_t psky = mCurrentEnvironment->getSky(); - if (!psky) - { - return LLVector3(0, 0, 1); - } - return psky->getLightDirection(); -} - -LLVector3 LLEnvironment::getSunDirection() const -{ - LLSettingsSky::ptr_t psky = mCurrentEnvironment->getSky(); - if (!psky) - { - return LLVector3(0, 0, 1); - } - return psky->getSunDirection(); -} - -LLVector3 LLEnvironment::getMoonDirection() const -{ - LLSettingsSky::ptr_t psky = mCurrentEnvironment->getSky(); - if (!psky) - { - return LLVector3(0, 0, -1); - } - return psky->getMoonDirection(); -} - -LLVector4 LLEnvironment::getLightDirectionCFR() const -{ - LLVector3 light_direction = getLightDirection(); - LLVector4 light_direction_cfr = toCFR(light_direction); - return light_direction_cfr; -} - -LLVector4 LLEnvironment::getSunDirectionCFR() const -{ - LLVector3 light_direction = getSunDirection(); - LLVector4 light_direction_cfr = toCFR(light_direction); - return light_direction_cfr; -} - -LLVector4 LLEnvironment::getMoonDirectionCFR() const -{ - LLVector3 light_direction = getMoonDirection(); - LLVector4 light_direction_cfr = toCFR(light_direction); - return light_direction_cfr; -} - -LLVector4 LLEnvironment::getClampedLightNorm() const -{ - LLVector3 light_direction = getLightDirection(); - if (light_direction.mV[2] < -0.1f) - { - light_direction.mV[2] = -0.1f; - } - return toLightNorm(light_direction); -} - -LLVector4 LLEnvironment::getClampedSunNorm() const -{ - LLVector3 light_direction = getSunDirection(); - if (light_direction.mV[2] < -0.1f) - { - light_direction.mV[2] = -0.1f; - } - return toLightNorm(light_direction); -} - -LLVector4 LLEnvironment::getClampedMoonNorm() const -{ - LLVector3 light_direction = getMoonDirection(); - if (light_direction.mV[2] < -0.1f) - { - light_direction.mV[2] = -0.1f; - } - return toLightNorm(light_direction); -} - -LLVector4 LLEnvironment::getRotatedLightNorm() const -{ - LLVector3 light_direction = getLightDirection(); - light_direction *= LLQuaternion(-mLastCamYaw, LLVector3(0.f, 1.f, 0.f)); - return toLightNorm(light_direction); -} - -extern bool gCubeSnapshot; - -//------------------------------------------------------------------------- -void LLEnvironment::update(const LLViewerCamera * cam) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT; //LL_RECORD_BLOCK_TIME(FTM_ENVIRONMENT_UPDATE); - //F32Seconds now(LLDate::now().secondsSinceEpoch()); - if (!gCubeSnapshot) - { - static LLFrameTimer timer; - - F32Seconds delta(timer.getElapsedTimeAndResetF32()); - - { - DayInstance::ptr_t keeper = mCurrentEnvironment; - // make sure the current environment does not go away until applyTimeDelta is done. - mCurrentEnvironment->applyTimeDelta(delta); - - } - // update clouds, sun, and general - updateCloudScroll(); - - // cache this for use in rotating the rotated light vec for shader param updates later... - mLastCamYaw = cam->getYaw() + SUN_DELTA_YAW; - } - - updateSettingsUniforms(); - - // *TODO: potential optimization - this block may only need to be - // executed some of the time. For example for water shaders only. - { - LLViewerShaderMgr::shader_iter shaders_iter, end_shaders; - end_shaders = LLViewerShaderMgr::instance()->endShaders(); - for (shaders_iter = LLViewerShaderMgr::instance()->beginShaders(); shaders_iter != end_shaders; ++shaders_iter) - { - if ((shaders_iter->mProgramObject != 0) - && (gPipeline.canUseWindLightShaders() - || shaders_iter->mShaderGroup == LLGLSLShader::SG_WATER)) - { - shaders_iter->mUniformsDirty = true; - } - } - } -} - -void LLEnvironment::updateCloudScroll() -{ - // This is a function of the environment rather than the sky, since it should - // persist through sky transitions. - static LLTimer s_cloud_timer; - - F64 delta_t = s_cloud_timer.getElapsedTimeAndResetF64(); - - if (mCurrentEnvironment->getSky() && !mCloudScrollPaused) - { - LLVector2 rate = mCurrentEnvironment->getSky()->getCloudScrollRate(); - if (rate.isExactlyZero()) - { - mCloudScrollDelta.setZero(); - } - else - { - LLVector2 cloud_delta = static_cast(delta_t) * (mCurrentEnvironment->getSky()->getCloudScrollRate()) / 100.0; - mCloudScrollDelta += cloud_delta; - } - } - -} - -// static -void LLEnvironment::updateGLVariablesForSettings(LLShaderUniforms* uniforms, const LLSettingsBase::ptr_t &psetting) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_SHADER; - - for (int i = 0; i < LLGLSLShader::SG_COUNT; ++i) - { - uniforms[i].clear(); - } - - LLShaderUniforms* shader = &uniforms[LLGLSLShader::SG_ANY]; - //_WARNS("RIDER") << "----------------------------------------------------------------" << LL_ENDL; - LLSettingsBase::parammapping_t params = psetting->getParameterMap(); - for (auto &it: params) - { - LLSD value; - // legacy first since it contains ambient color and we prioritize value from legacy, see getAmbientColor() - if (psetting->mSettings.has(LLSettingsSky::SETTING_LEGACY_HAZE) && psetting->mSettings[LLSettingsSky::SETTING_LEGACY_HAZE].has(it.first)) - { - value = psetting->mSettings[LLSettingsSky::SETTING_LEGACY_HAZE][it.first]; - } - else if (psetting->mSettings.has(it.first)) - { - value = psetting->mSettings[it.first]; - } - else - { - // We need to reset shaders, use defaults - value = it.second.getDefaultValue(); - } - - LLSD::Type setting_type = value.type(); - stop_glerror(); - switch (setting_type) - { - case LLSD::TypeInteger: - shader->uniform1i(it.second.getShaderKey(), value.asInteger()); - //_WARNS("RIDER") << "pushing '" << (*it).first << "' as " << value << LL_ENDL; - break; - case LLSD::TypeReal: - shader->uniform1f(it.second.getShaderKey(), value.asReal()); - //_WARNS("RIDER") << "pushing '" << (*it).first << "' as " << value << LL_ENDL; - break; - - case LLSD::TypeBoolean: - shader->uniform1i(it.second.getShaderKey(), value.asBoolean() ? 1 : 0); - //_WARNS("RIDER") << "pushing '" << (*it).first << "' as " << value << LL_ENDL; - break; - - case LLSD::TypeArray: - { - LLVector4 vect4(value); - - if (gCubeSnapshot && !gPipeline.mReflectionMapManager.isRadiancePass()) - { // maximize and remove tinting if this is an irradiance map render pass and the parameter feeds into the sky background color - auto max_vec = [](LLVector4 col) - { - LLColor3 color(col); - F32 h, s, l; - color.calcHSL(&h, &s, &l); - - col.mV[0] = col.mV[1] = col.mV[2] = l; - return col; - }; - - switch (it.second.getShaderKey()) - { - case LLShaderMgr::BLUE_HORIZON: - case LLShaderMgr::BLUE_DENSITY: - vect4 = max_vec(vect4); - break; - } - } - - //_WARNS("RIDER") << "pushing '" << (*it).first << "' as " << vect4 << LL_ENDL; - shader->uniform3fv(it.second.getShaderKey(), LLVector3(vect4.mV) ); - break; - } - - // case LLSD::TypeMap: - // case LLSD::TypeString: - // case LLSD::TypeUUID: - // case LLSD::TypeURI: - // case LLSD::TypeBinary: - // case LLSD::TypeDate: - default: - break; - } - } - //_WARNS("RIDER") << "----------------------------------------------------------------" << LL_ENDL; - - psetting->applySpecial(uniforms); -} - -void LLEnvironment::updateShaderUniforms(LLGLSLShader* shader) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_SHADER; - - // apply uniforms that should be applied to all shaders - mSkyUniforms[LLGLSLShader::SG_ANY].apply(shader); - mWaterUniforms[LLGLSLShader::SG_ANY].apply(shader); - - // apply uniforms specific to the given shader's shader group - auto group = shader->mShaderGroup; - mSkyUniforms[group].apply(shader); - mWaterUniforms[group].apply(shader); -} - -void LLEnvironment::updateSettingsUniforms() -{ - if (mCurrentEnvironment->getWater()) - { - updateGLVariablesForSettings(mWaterUniforms, mCurrentEnvironment->getWater()); - } - else - { - LL_WARNS("ENVIRONMENT") << "Failed to update GL variable for water settings, environment is not properly set" << LL_ENDL; - } - if (mCurrentEnvironment->getSky()) - { - updateGLVariablesForSettings(mSkyUniforms, mCurrentEnvironment->getSky()); - } - else - { - LL_WARNS("ENVIRONMENT") << "Failed to update GL variable for sky settings, environment is not properly set" << LL_ENDL; - } -} - -void LLEnvironment::recordEnvironment(S32 parcel_id, LLEnvironment::EnvironmentInfo::ptr_t envinfo, LLSettingsBase::Seconds transition) -{ - if (!gAgent.getRegion()) - { - return; - } - // mRegionId id can be null, no specification as to why and if it's valid so check valid ids only - if (gAgent.getRegion()->getRegionID() != envinfo->mRegionId && envinfo->mRegionId.notNull()) - { - LL_INFOS("ENVIRONMENT") << "Requested environmend region id: " << envinfo->mRegionId << " agent is on: " << gAgent.getRegion()->getRegionID() << LL_ENDL; - return; - } - - if (envinfo->mParcelId == INVALID_PARCEL_ID) - { - // the returned info applies to an entire region. - if (!envinfo->mDayCycle) - { - clearEnvironment(ENV_PARCEL); - setEnvironment(ENV_REGION, LLSettingsDay::GetDefaultAssetId(), TRANSITION_DEFAULT, envinfo->mEnvVersion); - updateEnvironment(); - } - else if (envinfo->mDayCycle->isTrackEmpty(LLSettingsDay::TRACK_WATER) - || envinfo->mDayCycle->isTrackEmpty(LLSettingsDay::TRACK_GROUND_LEVEL)) - { - LL_WARNS("ENVIRONMENT") << "Invalid day cycle for region" << LL_ENDL; - clearEnvironment(ENV_PARCEL); - setEnvironment(ENV_REGION, LLSettingsDay::GetDefaultAssetId(), TRANSITION_DEFAULT, envinfo->mEnvVersion); - updateEnvironment(); - } - else - { - mTrackAltitudes = envinfo->mAltitudes; - // update track selection based on new altitudes - mCurrentTrack = calculateSkyTrackForAltitude(gAgent.getPositionAgent().mV[VZ]); - - setEnvironment(ENV_REGION, envinfo->mDayCycle, envinfo->mDayLength, envinfo->mDayOffset, envinfo->mEnvVersion); - } - - LL_DEBUGS("ENVIRONMENT") << "Altitudes set to {" << mTrackAltitudes[0] << ", "<< mTrackAltitudes[1] << ", " << mTrackAltitudes[2] << ", " << mTrackAltitudes[3] << LL_ENDL; - } - else - { - LLParcel *parcel = LLViewerParcelMgr::instance().getAgentParcel(); - LL_DEBUGS("ENVIRONMENT") << "Have parcel environment #" << envinfo->mParcelId << LL_ENDL; - if (parcel && (parcel->getLocalID() != parcel_id)) - { - LL_DEBUGS("ENVIRONMENT") << "Requested parcel #" << parcel_id << " agent is on " << parcel->getLocalID() << LL_ENDL; - return; - } - - if (!envinfo->mDayCycle) - { - LL_DEBUGS("ENVIRONMENT") << "Clearing environment on parcel #" << parcel_id << LL_ENDL; - clearEnvironment(ENV_PARCEL); - } - else if (envinfo->mDayCycle->isTrackEmpty(LLSettingsDay::TRACK_WATER) - || envinfo->mDayCycle->isTrackEmpty(LLSettingsDay::TRACK_GROUND_LEVEL)) - { - LL_WARNS("ENVIRONMENT") << "Invalid day cycle for parcel #" << parcel_id << LL_ENDL; - clearEnvironment(ENV_PARCEL); - } - else - { - setEnvironment(ENV_PARCEL, envinfo->mDayCycle, envinfo->mDayLength, envinfo->mDayOffset, envinfo->mEnvVersion); - } - } - - updateEnvironment(transition); -} - -void LLEnvironment::adjustRegionOffset(F32 adjust) -{ - if (isExtendedEnvironmentEnabled()) - { - LL_WARNS("ENVIRONMENT") << "Attempt to adjust region offset on EEP region. Legacy regions only." << LL_ENDL; - } - - if (mEnvironments[ENV_REGION]) - { - F32 day_length = mEnvironments[ENV_REGION]->getDayLength(); - F32 day_offset = mEnvironments[ENV_REGION]->getDayOffset(); - - F32 day_adjustment = adjust * day_length; - - day_offset += day_adjustment; - if (day_offset < 0.0f) - day_offset = day_length + day_offset; - mEnvironments[ENV_REGION]->setDayOffset(LLSettingsBase::Seconds(day_offset)); - } -} - -//========================================================================= -void LLEnvironment::requestRegion(environment_apply_fn cb) -{ - requestParcel(INVALID_PARCEL_ID, cb); -} - -void LLEnvironment::updateRegion(const LLSettingsDay::ptr_t &pday, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) -{ - updateParcel(INVALID_PARCEL_ID, pday, day_length, day_offset, altitudes, cb); -} - -void LLEnvironment::updateRegion(const LLUUID &asset_id, std::string display_name, S32 track_num, S32 day_length, S32 day_offset, U32 flags, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) -{ - if (!isExtendedEnvironmentEnabled()) - { - LL_WARNS("ENVIRONMENT") << "attempt to apply asset id to region not supporting it." << LL_ENDL; - LLNotificationsUtil::add("NoEnvironmentSettings"); - return; - } - - updateParcel(INVALID_PARCEL_ID, asset_id, display_name, track_num, day_length, day_offset, flags, altitudes, cb); -} - -void LLEnvironment::updateRegion(const LLSettingsSky::ptr_t &psky, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) -{ - updateParcel(INVALID_PARCEL_ID, psky, day_length, day_offset, altitudes, cb); -} - -void LLEnvironment::updateRegion(const LLSettingsWater::ptr_t &pwater, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) -{ - updateParcel(INVALID_PARCEL_ID, pwater, day_length, day_offset, altitudes, cb); -} - - -void LLEnvironment::resetRegion(environment_apply_fn cb) -{ - resetParcel(INVALID_PARCEL_ID, cb); -} - -void LLEnvironment::requestParcel(S32 parcel_id, environment_apply_fn cb) -{ - if (!isExtendedEnvironmentEnabled()) - { /*TODO: When EEP is live on the entire grid, this can go away. */ - if (parcel_id == INVALID_PARCEL_ID) - { - if (!cb) - { - LLSettingsBase::Seconds transition = LLViewerParcelMgr::getInstance()->getTeleportInProgress() ? TRANSITION_FAST : TRANSITION_DEFAULT; - cb = [this, transition](S32 pid, EnvironmentInfo::ptr_t envinfo) - { - clearEnvironment(ENV_PARCEL); - recordEnvironment(pid, envinfo, transition); - }; - } - - LLEnvironmentRequest::initiate(cb); - } - else if (cb) - cb(parcel_id, EnvironmentInfo::ptr_t()); - return; - } - - if (!cb) - { - LLSettingsBase::Seconds transition = LLViewerParcelMgr::getInstance()->getTeleportInProgress() ? TRANSITION_FAST : TRANSITION_DEFAULT; - cb = [this, transition](S32 pid, EnvironmentInfo::ptr_t envinfo) { recordEnvironment(pid, envinfo, transition); }; - } - - std::string coroname = - LLCoros::instance().launch("LLEnvironment::coroRequestEnvironment", - [this, parcel_id, cb]() { coroRequestEnvironment(parcel_id, cb); }); -} - -void LLEnvironment::updateParcel(S32 parcel_id, const LLUUID &asset_id, std::string display_name, S32 track_num, S32 day_length, S32 day_offset, U32 flags, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) -{ - UpdateInfo::ptr_t updates(std::make_shared(asset_id, display_name, day_length, day_offset, altitudes, flags)); - std::string coroname = - LLCoros::instance().launch("LLEnvironment::coroUpdateEnvironment", - [this, parcel_id, track_num, updates, cb]() { coroUpdateEnvironment(parcel_id, track_num, updates, cb); }); -} - -void LLEnvironment::onUpdateParcelAssetLoaded(LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status, S32 parcel_id, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes) -{ - if (status) - { - LL_WARNS("ENVIRONMENT") << "Unable to get settings asset with id " << asset_id << "!" << LL_ENDL; - LLNotificationsUtil::add("FailedToLoadSettingsApply"); - return; - } - - LLSettingsDay::ptr_t pday; - - if (settings->getSettingsType() == "daycycle") - pday = std::static_pointer_cast(settings); - else - { - pday = createDayCycleFromEnvironment( (parcel_id == INVALID_PARCEL_ID) ? ENV_REGION : ENV_PARCEL, settings); - } - - if (!pday) - { - LL_WARNS("ENVIRONMENT") << "Unable to construct day around " << asset_id << "!" << LL_ENDL; - LLNotificationsUtil::add("FailedToBuildSettingsDay"); - return; - } - - updateParcel(parcel_id, pday, day_length, day_offset, altitudes); -} - -void LLEnvironment::updateParcel(S32 parcel_id, const LLSettingsSky::ptr_t &psky, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) -{ - LLSettingsDay::ptr_t pday = createDayCycleFromEnvironment((parcel_id == INVALID_PARCEL_ID) ? ENV_REGION : ENV_PARCEL, psky); - pday->setFlag(psky->getFlags()); - updateParcel(parcel_id, pday, day_length, day_offset, altitudes, cb); -} - -void LLEnvironment::updateParcel(S32 parcel_id, const LLSettingsWater::ptr_t &pwater, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) -{ - LLSettingsDay::ptr_t pday = createDayCycleFromEnvironment((parcel_id == INVALID_PARCEL_ID) ? ENV_REGION : ENV_PARCEL, pwater); - pday->setFlag(pwater->getFlags()); - updateParcel(parcel_id, pday, day_length, day_offset, altitudes, cb); -} - -void LLEnvironment::updateParcel(S32 parcel_id, const LLSettingsDay::ptr_t &pday, S32 track_num, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) -{ - UpdateInfo::ptr_t updates(std::make_shared(pday, day_length, day_offset, altitudes)); - - std::string coroname = - LLCoros::instance().launch("LLEnvironment::coroUpdateEnvironment", - [this, parcel_id, track_num, updates, cb]() { coroUpdateEnvironment(parcel_id, track_num, updates, cb); }); -} - -void LLEnvironment::updateParcel(S32 parcel_id, const LLSettingsDay::ptr_t &pday, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) -{ - updateParcel(parcel_id, pday, NO_TRACK, day_length, day_offset, altitudes, cb); -} - - - -void LLEnvironment::resetParcel(S32 parcel_id, environment_apply_fn cb) -{ - std::string coroname = - LLCoros::instance().launch("LLEnvironment::coroResetEnvironment", - [this, parcel_id, cb]() { coroResetEnvironment(parcel_id, NO_TRACK, cb); }); -} - -void LLEnvironment::coroRequestEnvironment(S32 parcel_id, LLEnvironment::environment_apply_fn apply) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ResetEnvironment", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - std::string url = gAgent.getRegionCapability("ExtEnvironment"); - if (url.empty()) - return; - - LL_DEBUGS("ENVIRONMENT") << "Requesting for parcel_id=" << parcel_id << LL_ENDL; - - if (parcel_id != INVALID_PARCEL_ID) - { - std::stringstream query; - - query << "?parcelid=" << parcel_id; - url += query.str(); - } - - LLSD result = httpAdapter->getAndSuspend(httpRequest, url); - // results that come back may contain the new settings - - LLSD httpResults = result["http_result"]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - if (!status) - { - LL_WARNS("ENVIRONMENT") << "Couldn't retrieve environment settings for " << ((parcel_id == INVALID_PARCEL_ID) ? ("region!") : ("parcel!")) << LL_ENDL; - } - else if (LLApp::isExiting() || gDisconnected) - { - return; - } - else - { - LLSD environment = result[KEY_ENVIRONMENT]; - if (environment.isDefined() && apply) - { - EnvironmentInfo::ptr_t envinfo = LLEnvironment::EnvironmentInfo::extract(environment); - apply(parcel_id, envinfo); - } - } - -} - -void LLEnvironment::coroUpdateEnvironment(S32 parcel_id, S32 track_no, UpdateInfo::ptr_t updates, environment_apply_fn apply) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ResetEnvironment", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - std::string url = gAgent.getRegionCapability("ExtEnvironment"); - if (url.empty()) - return; - - LLSD body(LLSD::emptyMap()); - body[KEY_ENVIRONMENT] = LLSD::emptyMap(); - - if (track_no == NO_TRACK) - { // day length and offset are only applicable if we are addressing the entire day cycle. - if (updates->mDayLength > 0) - body[KEY_ENVIRONMENT][KEY_DAYLENGTH] = updates->mDayLength; - if (updates->mDayOffset > 0) - body[KEY_ENVIRONMENT][KEY_DAYOFFSET] = updates->mDayOffset; - - if ((parcel_id == INVALID_PARCEL_ID) && (updates->mAltitudes.size() == 3)) - { // only test for altitude changes if we are changing the region. - body[KEY_ENVIRONMENT][KEY_TRACKALTS] = LLSD::emptyArray(); - for (S32 i = 0; i < 3; ++i) - { - body[KEY_ENVIRONMENT][KEY_TRACKALTS][i] = updates->mAltitudes[i]; - } - } - } - - if (updates->mDayp) - body[KEY_ENVIRONMENT][KEY_DAYCYCLE] = updates->mDayp->getSettings(); - else if (!updates->mSettingsAsset.isNull()) - { - body[KEY_ENVIRONMENT][KEY_DAYASSET] = updates->mSettingsAsset; - if (!updates->mDayName.empty()) - body[KEY_ENVIRONMENT][KEY_DAYNAME] = updates->mDayName; - } - - body[KEY_ENVIRONMENT][KEY_FLAGS] = LLSD::Integer(updates->mFlags); - //_WARNS("ENVIRONMENT") << "Body = " << body << LL_ENDL; - - if ((parcel_id != INVALID_PARCEL_ID) || (track_no != NO_TRACK)) - { - std::stringstream query; - query << "?"; - - if (parcel_id != INVALID_PARCEL_ID) - { - query << "parcelid=" << parcel_id; - - if (track_no != NO_TRACK) - query << "&"; - } - if (track_no != NO_TRACK) - { - query << "trackno=" << track_no; - } - url += query.str(); - } - - LLSD result = httpAdapter->putAndSuspend(httpRequest, url, body); - // results that come back may contain the new settings - - LLSD notify; - - LLSD httpResults = result["http_result"]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if ((!status) || !result["success"].asBoolean()) - { - LL_WARNS("ENVIRONMENT") << "Couldn't update Windlight settings for " << ((parcel_id == INVALID_PARCEL_ID) ? ("region!") : ("parcel!")) << LL_ENDL; - - notify = LLSD::emptyMap(); - std::string reason = result["message"].asString(); - if (reason.empty()) - { - notify["FAIL_REASON"] = status.toString(); - } - else - { - notify["FAIL_REASON"] = reason; - } - } - else if (LLApp::isExiting()) - { - return; - } - else - { - LLSD environment = result[KEY_ENVIRONMENT]; - if (environment.isDefined() && apply) - { - EnvironmentInfo::ptr_t envinfo = LLEnvironment::EnvironmentInfo::extract(environment); - apply(parcel_id, envinfo); - } - } - - if (!notify.isUndefined()) - { - LLNotificationsUtil::add("WLRegionApplyFail", notify); - //LLEnvManagerNew::instance().onRegionSettingsApplyResponse(false); - } -} - -void LLEnvironment::coroResetEnvironment(S32 parcel_id, S32 track_no, environment_apply_fn apply) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ResetEnvironment", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - std::string url = gAgent.getRegionCapability("ExtEnvironment"); - if (url.empty()) - return; - - if ((parcel_id != INVALID_PARCEL_ID) || (track_no != NO_TRACK)) - { - std::stringstream query; - query << "?"; - - if (parcel_id != INVALID_PARCEL_ID) - { - query << "parcelid=" << parcel_id; - - if (track_no != NO_TRACK) - query << "&"; - } - if (track_no != NO_TRACK) - { - query << "trackno=" << track_no; - } - url += query.str(); - } - - LLSD result = httpAdapter->deleteAndSuspend(httpRequest, url); - // results that come back may contain the new settings - - LLSD notify; - - LLSD httpResults = result["http_result"]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if ((!status) || !result["success"].asBoolean()) - { - LL_WARNS("ENVIRONMENT") << "Couldn't reset Windlight settings in " << ((parcel_id == INVALID_PARCEL_ID) ? ("region!") : ("parcel!")) << LL_ENDL; - - notify = LLSD::emptyMap(); - std::string reason = result["message"].asString(); - if (reason.empty()) - { - notify["FAIL_REASON"] = status.toString(); - } - else - { - notify["FAIL_REASON"] = reason; - } - } - else if (LLApp::isExiting()) - { - return; - } - else - { - LLSD environment = result[KEY_ENVIRONMENT]; - if (environment.isDefined() && apply) - { - EnvironmentInfo::ptr_t envinfo = LLEnvironment::EnvironmentInfo::extract(environment); - apply(parcel_id, envinfo); - } - } - - if (!notify.isUndefined()) - { - LLNotificationsUtil::add("WLRegionApplyFail", notify); - //LLEnvManagerNew::instance().onRegionSettingsApplyResponse(false); - } - -} - - -//========================================================================= - -LLEnvironment::EnvironmentInfo::EnvironmentInfo(): - mParcelId(INVALID_PARCEL_ID), - mRegionId(), - mDayLength(0), - mDayOffset(0), - mDayHash(0), - mDayCycle(), - mAltitudes({ { 0.0, 0.0, 0.0, 0.0 } }), - mIsDefault(false), - mIsLegacy(false), - mDayCycleName(), - mNameList(), - mEnvVersion(INVALID_PARCEL_ENVIRONMENT_VERSION) -{ -} - -LLEnvironment::EnvironmentInfo::ptr_t LLEnvironment::EnvironmentInfo::extract(LLSD environment) -{ - ptr_t pinfo = std::make_shared(); - - pinfo->mIsDefault = environment.has(KEY_ISDEFAULT) ? environment[KEY_ISDEFAULT].asBoolean() : true; - pinfo->mParcelId = environment.has(KEY_PARCELID) ? environment[KEY_PARCELID].asInteger() : INVALID_PARCEL_ID; - pinfo->mRegionId = environment.has(KEY_REGIONID) ? environment[KEY_REGIONID].asUUID() : LLUUID::null; - pinfo->mIsLegacy = false; - - if (environment.has(KEY_TRACKALTS)) - { - for (int idx = 0; idx < 3; idx++) - { - pinfo->mAltitudes[idx+1] = environment[KEY_TRACKALTS][idx].asReal(); - } - pinfo->mAltitudes[0] = 0; - } - - if (environment.has(KEY_DAYCYCLE)) - { - pinfo->mDayCycle = LLSettingsVODay::buildFromEnvironmentMessage(environment[KEY_DAYCYCLE]); - pinfo->mDayLength = LLSettingsDay::Seconds(environment.has(KEY_DAYLENGTH) ? environment[KEY_DAYLENGTH].asInteger() : -1); - pinfo->mDayOffset = LLSettingsDay::Seconds(environment.has(KEY_DAYOFFSET) ? environment[KEY_DAYOFFSET].asInteger() : -1); - pinfo->mDayHash = environment.has(KEY_DAYHASH) ? environment[KEY_DAYHASH].asInteger() : 0; - } - else - { - pinfo->mDayLength = LLEnvironment::instance().getEnvironmentDayLength(ENV_REGION); - pinfo->mDayOffset = LLEnvironment::instance().getEnvironmentDayOffset(ENV_REGION); - } - - if (environment.has(KEY_DAYASSET)) - { - pinfo->mAssetId = environment[KEY_DAYASSET].asUUID(); - } - - if (environment.has(KEY_DAYNAMES)) - { - LLSD daynames = environment[KEY_DAYNAMES]; - if (daynames.isArray()) - { - pinfo->mDayCycleName.clear(); - for (S32 index = 0; index < pinfo->mNameList.size(); ++index) - { - pinfo->mNameList[index] = daynames[index].asString(); - } - } - else if (daynames.isString()) - { - for (std::string &name: pinfo->mNameList) - { - name.clear(); - } - - pinfo->mDayCycleName = daynames.asString(); - } - } - else if (pinfo->mDayCycle) - { - pinfo->mDayCycleName = pinfo->mDayCycle->getName(); - } - - - if (environment.has(KEY_ENVVERSION)) - { - LLSD version = environment[KEY_ENVVERSION]; - pinfo->mEnvVersion = version.asInteger(); - } - else - { - // can be used for region, but versions should be same - pinfo->mEnvVersion = pinfo->mIsDefault ? UNSET_PARCEL_ENVIRONMENT_VERSION : INVALID_PARCEL_ENVIRONMENT_VERSION; - } - - return pinfo; -} - - -LLEnvironment::EnvironmentInfo::ptr_t LLEnvironment::EnvironmentInfo::extractLegacy(LLSD legacy) -{ - if (!legacy.isArray() || !legacy[0].has("regionID")) - { - LL_WARNS("ENVIRONMENT") << "Invalid legacy settings for environment: " << legacy << LL_ENDL; - return ptr_t(); - } - - ptr_t pinfo = std::make_shared(); - - pinfo->mIsDefault = false; - pinfo->mParcelId = INVALID_PARCEL_ID; - pinfo->mRegionId = legacy[0]["regionID"].asUUID(); - pinfo->mIsLegacy = true; - - pinfo->mDayLength = LLSettingsDay::DEFAULT_DAYLENGTH; - pinfo->mDayOffset = LLSettingsDay::DEFAULT_DAYOFFSET; - pinfo->mDayCycle = LLSettingsVODay::buildFromLegacyMessage(pinfo->mRegionId, legacy[1], legacy[2], legacy[3]); - if (pinfo->mDayCycle) - pinfo->mDayHash = pinfo->mDayCycle->getHash(); - - pinfo->mAltitudes[0] = 0; - pinfo->mAltitudes[2] = 10001; - pinfo->mAltitudes[3] = 10002; - pinfo->mAltitudes[4] = 10003; - - return pinfo; -} - -//========================================================================= -LLSettingsWater::ptr_t LLEnvironment::createWaterFromLegacyPreset(const std::string filename, LLSD &messages) -{ - std::string name(gDirUtilp->getBaseFileName(filename, true)); - std::string path(gDirUtilp->getDirName(filename)); - - LLSettingsWater::ptr_t water = LLSettingsVOWater::buildFromLegacyPresetFile(name, path, messages); - - if (!water) - { - messages["NAME"] = name; - messages["FILE"] = filename; - } - return water; -} - -LLSettingsSky::ptr_t LLEnvironment::createSkyFromLegacyPreset(const std::string filename, LLSD &messages) -{ - std::string name(gDirUtilp->getBaseFileName(filename, true)); - std::string path(gDirUtilp->getDirName(filename)); - - LLSettingsSky::ptr_t sky = LLSettingsVOSky::buildFromLegacyPresetFile(name, path, messages); - if (!sky) - { - messages["NAME"] = name; - messages["FILE"] = filename; - } - return sky; -} - -LLSettingsDay::ptr_t LLEnvironment::createDayCycleFromLegacyPreset(const std::string filename, LLSD &messages) -{ - std::string name(gDirUtilp->getBaseFileName(filename, true)); - std::string path(gDirUtilp->getDirName(filename)); - - LLSettingsDay::ptr_t day = LLSettingsVODay::buildFromLegacyPresetFile(name, path, messages); - if (!day) - { - messages["NAME"] = name; - messages["FILE"] = filename; - } - return day; -} - -LLSettingsDay::ptr_t LLEnvironment::createDayCycleFromEnvironment(EnvSelection_t env, LLSettingsBase::ptr_t settings) -{ - std::string type(settings->getSettingsType()); - - if (type == "daycycle") - return std::static_pointer_cast(settings); - - if ((env != ENV_PARCEL) && (env != ENV_REGION)) - { - LL_WARNS("ENVIRONMENT") << "May only create from parcel or region environment." << LL_ENDL; - return LLSettingsDay::ptr_t(); - } - - LLSettingsDay::ptr_t day = this->getEnvironmentDay(env); - if (!day && (env == ENV_PARCEL)) - { - day = this->getEnvironmentDay(ENV_REGION); - } - - if (!day) - { - LL_WARNS("ENVIRONMENT") << "Could not retrieve existing day settings." << LL_ENDL; - return LLSettingsDay::ptr_t(); - } - - day = day->buildClone(); - - if (type == "sky") - { - for (S32 idx = 1; idx < LLSettingsDay::TRACK_MAX; ++idx) - day->clearCycleTrack(idx); - day->setSettingsAtKeyframe(settings, 0.0f, 1); - } - else if (type == "water") - { - day->clearCycleTrack(LLSettingsDay::TRACK_WATER); - day->setSettingsAtKeyframe(settings, 0.0f, LLSettingsDay::TRACK_WATER); - } - - return day; -} - -void LLEnvironment::onAgentPositionHasChanged(const LLVector3 &localpos) -{ - S32 trackno = calculateSkyTrackForAltitude(localpos.mV[VZ]); - if (trackno == mCurrentTrack) - return; - - mCurrentTrack = trackno; - - LLViewerRegion* cur_region = gAgent.getRegion(); - if (!cur_region || !cur_region->capabilitiesReceived()) - { - // Environment not ready, environment will be updated later, don't cause 'blend' yet. - // But keep mCurrentTrack updated in case we won't get new altitudes for some reason - return; - } - - for (S32 env = ENV_LOCAL; env < ENV_DEFAULT; ++env) - { - if (mEnvironments[env]) - mEnvironments[env]->setSkyTrack(mCurrentTrack); - } -} - -S32 LLEnvironment::calculateSkyTrackForAltitude(F64 altitude) -{ - auto it = std::find_if_not(mTrackAltitudes.begin(), mTrackAltitudes.end(), [altitude](F32 test) { return altitude > test; }); - - if (it == mTrackAltitudes.begin()) - return 1; - else if (it == mTrackAltitudes.end()) - return 4; - - return std::min(static_cast(std::distance(mTrackAltitudes.begin(), it)), 4); -} - -//------------------------------------------------------------------------- -void LLEnvironment::handleEnvironmentPush(LLSD &message) -{ - // Log the experience message - LLExperienceLog::instance().handleExperienceMessage(message); - - std::string action = message[KEY_ACTION].asString(); - LLUUID experience_id = message[KEY_EXPERIENCEID].asUUID(); - LLSD action_data = message[KEY_ACTIONDATA]; - F32 transition_time = action_data[KEY_TRANSITIONTIME].asReal(); - - //TODO: Check here that the viewer thinks the experience is still valid. - - - if (action == ACTION_CLEARENVIRONMENT) - { - handleEnvironmentPushClear(experience_id, action_data, transition_time); - } - else if (action == ACTION_PUSHFULLENVIRONMENT) - { - handleEnvironmentPushFull(experience_id, action_data, transition_time); - } - else if (action == ACTION_PUSHPARTIALENVIRONMENT) - { - handleEnvironmentPushPartial(experience_id, action_data, transition_time); - } - else - { - LL_WARNS("ENVIRONMENT", "GENERICMESSAGES") << "Unknown environment push action '" << action << "'" << LL_ENDL; - } -} - -void LLEnvironment::handleEnvironmentPushClear(LLUUID experience_id, LLSD &message, F32 transition) -{ - clearExperienceEnvironment(experience_id, LLSettingsBase::Seconds(transition)); -} - -void LLEnvironment::handleEnvironmentPushFull(LLUUID experience_id, LLSD &message, F32 transition) -{ - LLUUID asset_id(message[KEY_ASSETID].asUUID()); - - setExperienceEnvironment(experience_id, asset_id, LLSettingsBase::Seconds(transition)); -} - -void LLEnvironment::handleEnvironmentPushPartial(LLUUID experience_id, LLSD &message, F32 transition) -{ - LLSD settings(message["settings"]); - - if (settings.isUndefined()) - return; - - setExperienceEnvironment(experience_id, settings, LLSettingsBase::Seconds(transition)); -} - -void LLEnvironment::clearExperienceEnvironment(LLUUID experience_id, LLSettingsBase::Seconds transition_time) -{ - DayInjection::ptr_t injection = std::dynamic_pointer_cast(getEnvironmentInstance(ENV_PUSH)); - if (injection) - { - injection->clearInjections(experience_id, transition_time); - } - -} - -void LLEnvironment::setSharedEnvironment() -{ - clearEnvironment(LLEnvironment::ENV_LOCAL); - setSelectedEnvironment(LLEnvironment::ENV_LOCAL); - updateEnvironment(); -} - -void LLEnvironment::setExperienceEnvironment(LLUUID experience_id, LLUUID asset_id, F32 transition_time) -{ - LLSettingsVOBase::getSettingsAsset(asset_id, - [this, experience_id, transition_time](LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status, LLExtStat) - { - onSetExperienceEnvAssetLoaded(experience_id, settings, transition_time, status); - }); - - -} - -void LLEnvironment::onSetExperienceEnvAssetLoaded(LLUUID experience_id, LLSettingsBase::ptr_t settings, F32 transition_time, S32 status) -{ - DayInjection::ptr_t environment = std::dynamic_pointer_cast(getEnvironmentInstance(ENV_PUSH)); - bool updateenvironment(false); - - if (!settings || status) - { - LLSD args; - args["NAME"] = experience_id.asString(); - LLNotificationsUtil::add("FailedToFindSettings", args); - return; - } - - if (!environment) - { - environment = std::dynamic_pointer_cast(getEnvironmentInstance(ENV_PUSH, true)); - updateenvironment = true; - } - - if (settings->getSettingsType() == "daycycle") - { - environment->setInjectedDay(std::static_pointer_cast(settings), experience_id, LLSettingsBase::Seconds(transition_time)); - } - else if (settings->getSettingsType() == "sky") - { - environment->setInjectedSky(std::static_pointer_cast(settings), experience_id, LLSettingsBase::Seconds(transition_time)); - } - else if (settings->getSettingsType() == "water") - { - environment->setInjectedWater(std::static_pointer_cast(settings), experience_id, LLSettingsBase::Seconds(transition_time)); - } - - if (updateenvironment) - updateEnvironment(TRANSITION_INSTANT, true); -} - - -void LLEnvironment::setExperienceEnvironment(LLUUID experience_id, LLSD data, F32 transition_time) -{ - LLSD sky(data["sky"]); - LLSD water(data["water"]); - - if (sky.isUndefined() && water.isUndefined()) - { - clearExperienceEnvironment(experience_id, LLSettingsBase::Seconds(transition_time)); - return; - } - - DayInjection::ptr_t environment = std::dynamic_pointer_cast(getEnvironmentInstance(ENV_PUSH)); - bool updateenvironment(false); - - if (!environment) - { - environment = std::dynamic_pointer_cast(getEnvironmentInstance(ENV_PUSH, true)); - updateenvironment = true; - } - - if (!sky.isUndefined()) - { - environment->injectSkySettings(sky, experience_id, LLSettingsBase::Seconds(transition_time)); - } - - if (!water.isUndefined()) - { - environment->injectWaterSettings(water, experience_id, LLSettingsBase::Seconds(transition_time)); - } - - if (updateenvironment) - updateEnvironment(TRANSITION_INSTANT, true); - -} - -void LLEnvironment::listenExperiencePump(const LLSD &message) -{ - LLUUID experience_id = message["experience"]; - LLSD data = message[experience_id.asString()]; - std::string permission(data["permission"].asString()); - - if ((permission == "Forget") || (permission == "Block")) - { - clearExperienceEnvironment(experience_id, (permission == "Block") ? TRANSITION_INSTANT : TRANSITION_FAST); - } -} - -//========================================================================= -LLEnvironment::DayInstance::DayInstance(EnvSelection_t env) : - mDayCycle(), - mSky(), - mWater(), - mDayLength(LLSettingsDay::DEFAULT_DAYLENGTH), - mDayOffset(LLSettingsDay::DEFAULT_DAYOFFSET), - mBlenderSky(), - mBlenderWater(), - mInitialized(false), - mSkyTrack(1), - mEnv(env), - mAnimateFlags(0) -{ } - - -LLEnvironment::DayInstance::ptr_t LLEnvironment::DayInstance::clone() const -{ - ptr_t environment = std::make_shared(mEnv); - - environment->mDayCycle = mDayCycle; - environment->mSky = mSky; - environment->mWater = mWater; - environment->mDayLength = mDayLength; - environment->mDayOffset = mDayOffset; - environment->mBlenderSky = mBlenderSky; - environment->mBlenderWater = mBlenderWater; - environment->mInitialized = mInitialized; - environment->mSkyTrack = mSkyTrack; - environment->mAnimateFlags = mAnimateFlags; - - return environment; -} - -bool LLEnvironment::DayInstance::applyTimeDelta(const LLSettingsBase::Seconds& delta) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT; - ptr_t keeper(shared_from_this()); // makes sure that this does not go away while it is being worked on. - - bool changed(false); - if (!mInitialized) - initialize(); - - if (mBlenderSky) - changed |= mBlenderSky->applyTimeDelta(delta); - if (mBlenderWater) - changed |= mBlenderWater->applyTimeDelta(delta); - return changed; -} - -void LLEnvironment::DayInstance::setDay(const LLSettingsDay::ptr_t &pday, LLSettingsDay::Seconds daylength, LLSettingsDay::Seconds dayoffset) -{ - mInitialized = false; - - mAnimateFlags = 0; - - mDayCycle = pday; - mDayLength = daylength; - mDayOffset = dayoffset; - - mBlenderSky.reset(); - mBlenderWater.reset(); - - mSky = LLSettingsVOSky::buildDefaultSky(); - mWater = LLSettingsVOWater::buildDefaultWater(); - - animate(); -} - - -bool LLEnvironment::DayInstance::setSky(const LLSettingsSky::ptr_t &psky) -{ - mInitialized = false; - - bool changed = psky == nullptr || mSky == nullptr || mSky->getHash() != psky->getHash(); - - bool different_sky = mSky != psky; - - mSky = psky; - mSky->mReplaced |= different_sky; - mSky->update(); - mBlenderSky.reset(); - - if (gAtmosphere) - { - AtmosphericModelSettings settings; - LLEnvironment::getAtmosphericModelSettings(settings, psky); - gAtmosphere->configureAtmosphericModel(settings); - } - - return changed; -} - -void LLEnvironment::DayInstance::setWater(const LLSettingsWater::ptr_t &pwater) -{ - mInitialized = false; - - bool different_water = mWater != pwater; - mWater = pwater; - mWater->mReplaced |= different_water; - mWater->update(); - mBlenderWater.reset(); -} - -void LLEnvironment::DayInstance::initialize() -{ - mInitialized = true; - - if (!mWater) - mWater = LLSettingsVOWater::buildDefaultWater(); - if (!mSky) - mSky = LLSettingsVOSky::buildDefaultSky(); -} - -void LLEnvironment::DayInstance::clear() -{ - mDayCycle.reset(); - mSky.reset(); - mWater.reset(); - mDayLength = LLSettingsDay::DEFAULT_DAYLENGTH; - mDayOffset = LLSettingsDay::DEFAULT_DAYOFFSET; - mBlenderSky.reset(); - mBlenderWater.reset(); - mSkyTrack = 1; -} - -void LLEnvironment::DayInstance::setSkyTrack(S32 trackno) -{ - mSkyTrack = trackno; - if (mBlenderSky) - { - mBlenderSky->switchTrack(trackno, 0.0); - } -} - -void LLEnvironment::DayInstance::setBlenders(const LLSettingsBlender::ptr_t &skyblend, const LLSettingsBlender::ptr_t &waterblend) -{ - mBlenderSky = skyblend; - mBlenderWater = waterblend; -} - -LLSettingsBase::TrackPosition LLEnvironment::DayInstance::getProgress() const -{ - LLSettingsBase::Seconds now(LLDate::now().secondsSinceEpoch()); - now += mDayOffset; - - if ((mDayLength <= 0) || !mDayCycle) - return -1.0f; // no actual day cycle. - - return convert_time_to_position(now, mDayLength); -} - -LLSettingsBase::TrackPosition LLEnvironment::DayInstance::secondsToKeyframe(LLSettingsDay::Seconds seconds) -{ - return convert_time_to_position(seconds, mDayLength); -} - -void LLEnvironment::DayInstance::animate() -{ - LLSettingsBase::Seconds now(LLDate::now().secondsSinceEpoch()); - - now += mDayOffset; - - if (!mDayCycle) - return; - - if (!(mAnimateFlags & NO_ANIMATE_WATER)) - { - LLSettingsDay::CycleTrack_t &wtrack = mDayCycle->getCycleTrack(0); - - if (wtrack.empty()) - { - mWater.reset(); - mBlenderWater.reset(); - } - else - { - mWater = LLSettingsVOWater::buildDefaultWater(); - mBlenderWater = std::make_shared(mWater, mDayCycle, 0, - mDayLength, mDayOffset, DEFAULT_UPDATE_THRESHOLD); - } - } - - if (!(mAnimateFlags & NO_ANIMATE_SKY)) - { - // sky, initialize to track 1 - LLSettingsDay::CycleTrack_t &track = mDayCycle->getCycleTrack(1); - - if (track.empty()) - { - mSky.reset(); - mBlenderSky.reset(); - } - else - { - mSky = LLSettingsVOSky::buildDefaultSky(); - mBlenderSky = std::make_shared(mSky, mDayCycle, 1, - mDayLength, mDayOffset, DEFAULT_UPDATE_THRESHOLD); - mBlenderSky->switchTrack(mSkyTrack, 0.0); - } - } -} - -//------------------------------------------------------------------------- -LLEnvironment::DayTransition::DayTransition(const LLSettingsSky::ptr_t &skystart, - const LLSettingsWater::ptr_t &waterstart, LLEnvironment::DayInstance::ptr_t &end, LLSettingsDay::Seconds time) : - DayInstance(ENV_NONE), - mStartSky(skystart), - mStartWater(waterstart), - mNextInstance(end), - mTransitionTime(time) -{ - -} - -bool LLEnvironment::DayTransition::applyTimeDelta(const LLSettingsBase::Seconds& delta) -{ - bool changed(false); - - changed = mNextInstance->applyTimeDelta(delta); - changed |= DayInstance::applyTimeDelta(delta); - return changed; -} - -void LLEnvironment::DayTransition::animate() -{ - mNextInstance->animate(); - - mWater = mStartWater->buildClone(); - mBlenderWater = std::make_shared(mWater, mStartWater, mNextInstance->getWater(), mTransitionTime); - mBlenderWater->setOnFinished( - [this](LLSettingsBlender::ptr_t blender) { - mBlenderWater.reset(); - - if (!mBlenderSky && !mBlenderWater) - LLEnvironment::instance().mCurrentEnvironment = mNextInstance; - else - setWater(mNextInstance->getWater()); - }); - - - // pause probe updates and reset reflection maps on sky change - gPipeline.mReflectionMapManager.pause(); - gPipeline.mReflectionMapManager.reset(); - - mSky = mStartSky->buildClone(); - mBlenderSky = std::make_shared(mSky, mStartSky, mNextInstance->getSky(), mTransitionTime); - mBlenderSky->setOnFinished( - [this](LLSettingsBlender::ptr_t blender) { - mBlenderSky.reset(); - - // resume reflection probe updates - gPipeline.mReflectionMapManager.resume(); - - if (!mBlenderSky && !mBlenderWater) - LLEnvironment::instance().mCurrentEnvironment = mNextInstance; - else - setSky(mNextInstance->getSky()); - }); -} - -void LLEnvironment::saveToSettings() -{ - std::string user_dir = gDirUtilp->getLindenUserDir(); - if (user_dir.empty()) - { - // not logged in - return; - } - bool has_data = false; - - if (gSavedSettings.getBOOL("EnvironmentPersistAcrossLogin")) - { - DayInstance::ptr_t environment = getEnvironmentInstance(ENV_LOCAL); - if (environment) - { - // Environment is 'layered'. No data in ENV_LOCAL means we are using parcel/region - // Store local environment for next session - LLSD env_data; - - LLSettingsDay::ptr_t day = environment->getDayCycle(); - if (day) - { - const std::string name = day->getName(); - const LLUUID asset_id = day->getAssetId(); - if (asset_id.notNull()) - { - // just save the id - env_data["day_id"] = asset_id; - env_data["day_length"] = LLSD::Integer(environment->getDayLength()); - env_data["day_offset"] = LLSD::Integer(environment->getDayOffset()); - has_data = true; - } - else if (!name.empty() && name != LLSettingsBase::DEFAULT_SETTINGS_NAME) - { - // This setting was created locally and was not saved - // The only option is to save the whole thing - env_data["day_llsd"] = day->getSettings(); - env_data["day_length"] = LLSD::Integer(environment->getDayLength()); - env_data["day_offset"] = LLSD::Integer(environment->getDayOffset()); - has_data = true; - } - } - - LLSettingsSky::ptr_t sky = environment->getSky(); - if ((environment->getFlags() & DayInstance::NO_ANIMATE_SKY) && sky) - { - const std::string name = sky->getName(); - const LLUUID asset_id = sky->getAssetId(); - if (asset_id.notNull()) - { - // just save the id - env_data["sky_id"] = asset_id; - has_data = true; - } - else if (!name.empty() && name != LLSettingsBase::DEFAULT_SETTINGS_NAME) - { - // This setting was created locally and was not saved - // The only option is to save the whole thing - env_data["sky_llsd"] = sky->getSettings(); - has_data = true; - } - has_data = true; - } - - LLSettingsWater::ptr_t water = environment->getWater(); - if ((environment->getFlags() & DayInstance::NO_ANIMATE_WATER) && water) - { - const std::string name = water->getName(); - const LLUUID asset_id = water->getAssetId(); - if (asset_id.notNull()) - { - // just save the id - env_data["water_id"] = asset_id; - has_data = true; - } - else if (!name.empty() && name != LLSettingsBase::DEFAULT_SETTINGS_NAME) - { - // This setting was created locally and was not saved - // The only option is to save the whole thing - env_data["water_llsd"] = water->getSettings(); - has_data = true; - } - } - - std::string user_filepath = user_dir + gDirUtilp->getDirDelimiter() + LOCAL_ENV_STORAGE_FILE; - llofstream out(user_filepath.c_str(), std::ios_base::out | std::ios_base::binary); - if (out.good()) - { - LLSDSerialize::toBinary(env_data, out); - out.close(); - } - else - { - LL_WARNS("ENVIRONMENT") << "Unable to open " << user_filepath << " for output." << LL_ENDL; - } - } - } - - if (!has_data) - { - LLFile::remove(user_dir + gDirUtilp->getDirDelimiter() + LOCAL_ENV_STORAGE_FILE, ENOENT); - } -} - -void LLEnvironment::loadSkyWaterFromSettings(const LLSD &env_data, bool &valid, bool &assets_present) -{ - if (env_data.has("sky_id")) - { - // causes asset loaded callback and an update - setEnvironment(ENV_LOCAL, env_data["sky_id"].asUUID()); - valid = true; - assets_present = true; - } - else if (env_data.has("sky_llsd")) - { - LLSettingsSky::ptr_t sky = LLSettingsVOSky::buildSky(env_data["sky_llsd"]); - setEnvironment(ENV_LOCAL, sky); - valid = true; - } - - if (env_data.has("water_id")) - { - // causes asset loaded callback and an update - setEnvironment(ENV_LOCAL, env_data["water_id"].asUUID()); - valid = true; - assets_present = true; - } - else if (env_data.has("water_llsd")) - { - LLSettingsWater::ptr_t sky = LLSettingsVOWater::buildWater(env_data["water_llsd"]); - setEnvironment(ENV_LOCAL, sky); - valid = true; - } -} - -bool LLEnvironment::loadFromSettings() -{ - if (!gSavedSettings.getBOOL("EnvironmentPersistAcrossLogin")) - { - return false; - } - - std::string user_path = gDirUtilp->getLindenUserDir(); - if (user_path.empty()) - { - LL_WARNS("ENVIRONMENT") << "Can't load previous environment, Environment was initialized before user logged in" << LL_ENDL; - return false; - } - std::string user_filepath(user_path + gDirUtilp->getDirDelimiter() + LOCAL_ENV_STORAGE_FILE); - if (!gDirUtilp->fileExists(user_filepath)) - { - // No previous environment - return false; - } - - LLSD env_data; - llifstream file(user_filepath.c_str(), std::ios_base::in | std::ios_base::binary); - if (file.is_open()) - { - LLSDSerialize::fromBinary(env_data, file, LLSDSerialize::SIZE_UNLIMITED); - if (env_data.isUndefined()) - { - LL_WARNS("ENVIRONMENT") << "error loading " << user_filepath << LL_ENDL; - return false; - } - else - { - LL_INFOS("ENVIRONMENT") << "Loaded previous session environment from: " << user_filepath << LL_ENDL; - } - file.close(); - } - else - { - LL_INFOS("ENVIRONMENT") << "Unable to open previous session environment file " << user_filepath << LL_ENDL; - } - - if (!env_data.isMap() || (env_data.size() == 0)) - { - LL_DEBUGS("ENVIRONMENT") << "Empty map loaded from: " << user_filepath << LL_ENDL; - return false; - } - - bool valid = false; - bool has_assets = false; - - if (env_data.has("day_id")) - { - LLUUID assetId = env_data["day_id"].asUUID(); - - LLSettingsVOBase::getSettingsAsset(assetId, - [this, env_data](LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status, LLExtStat) - { - // Day should be always applied first, - // otherwise it will override sky or water that was set earlier - // so wait for asset to load before applying sky/water - onSetEnvAssetLoaded(ENV_LOCAL, asset_id, settings, TRANSITION_DEFAULT, status, NO_VERSION); - bool valid = false, has_assets = false; - loadSkyWaterFromSettings(env_data, valid, has_assets); - if (!has_assets && valid) - { - // Settings were loaded from file without having an asset, needs update - // otherwise update will be done by asset callback - updateEnvironment(TRANSITION_DEFAULT, true); - } - }); - // bail early, everything have to be done at callback - return true; - } - else if (env_data.has("day_llsd")) - { - S32 length = env_data["day_length"].asInteger(); - S32 offset = env_data["day_offset"].asInteger(); - LLSettingsDay::ptr_t pday = LLSettingsVODay::buildDay(env_data["day_llsd"]); - setEnvironment(ENV_LOCAL, pday, LLSettingsDay::Seconds(length), LLSettingsDay::Seconds(offset)); - valid = true; - } - - loadSkyWaterFromSettings(env_data, valid, has_assets); - - if (valid && !has_assets) - { - // Settings were loaded from file without having an asset, needs update - // otherwise update will be done by asset callback - updateEnvironment(TRANSITION_DEFAULT, true); - } - return valid; -} - -void LLEnvironment::saveBeaconsState() -{ - if (mEditorCounter == 0) - { - mShowSunBeacon = gSavedSettings.getBOOL("sunbeacon"); - mShowMoonBeacon = gSavedSettings.getBOOL("moonbeacon"); - } - ++mEditorCounter; -} -void LLEnvironment::revertBeaconsState() -{ - --mEditorCounter; - if (mEditorCounter == 0) - { - gSavedSettings.setBOOL("sunbeacon", mShowSunBeacon && gSavedSettings.getBOOL("sunbeacon")); - gSavedSettings.setBOOL("moonbeacon", mShowMoonBeacon && gSavedSettings.getBOOL("moonbeacon")); - } -} - -//========================================================================= -LLTrackBlenderLoopingManual::LLTrackBlenderLoopingManual(const LLSettingsBase::ptr_t &target, const LLSettingsDay::ptr_t &day, S32 trackno) : - LLSettingsBlender(target, LLSettingsBase::ptr_t(), LLSettingsBase::ptr_t()), - mDay(day), - mTrackNo(trackno), - mPosition(0.0) -{ - LLSettingsDay::TrackBound_t initial = getBoundingEntries(mPosition); - - if (initial.first != mEndMarker) - { // No frames in track - mInitial = (*initial.first).second; - mFinal = (*initial.second).second; - - LLSD initSettings = mInitial->getSettings(); - mTarget->replaceSettings(initSettings); - } -} - -LLSettingsBase::BlendFactor LLTrackBlenderLoopingManual::setPosition(const LLSettingsBase::TrackPosition& position) -{ - mPosition = llclamp(position, 0.0f, 1.0f); - - LLSettingsDay::TrackBound_t bounds = getBoundingEntries(mPosition); - - if (bounds.first == mEndMarker) - { // No frames in track. - return 0.0; - } - - mInitial = (*bounds.first).second; - mFinal = (*bounds.second).second; - - F64 spanLength = getSpanLength(bounds); - - F64 spanPos = ((mPosition < (*bounds.first).first) ? (mPosition + 1.0) : mPosition) - (*bounds.first).first; - - if (spanPos > spanLength) - { - // we are clamping position to 0-1 and spanLength is 1 - // so don't account for case of spanPos == spanLength - spanPos = fmod(spanPos, spanLength); - } - - F64 blendf = spanPos / spanLength; - return LLSettingsBlender::setBlendFactor(blendf); -} - -void LLTrackBlenderLoopingManual::switchTrack(S32 trackno, const LLSettingsBase::TrackPosition& position) -{ - mTrackNo = trackno; - - LLSettingsBase::TrackPosition useposition = (position < 0.0) ? mPosition : position; - - setPosition(useposition); -} - -LLSettingsDay::TrackBound_t LLTrackBlenderLoopingManual::getBoundingEntries(F64 position) -{ - LLSettingsDay::CycleTrack_t &wtrack = mDay->getCycleTrack(mTrackNo); - - mEndMarker = wtrack.end(); - - LLSettingsDay::TrackBound_t bounds = get_bounding_entries(wtrack, position); - return bounds; -} - -F64 LLTrackBlenderLoopingManual::getSpanLength(const LLSettingsDay::TrackBound_t &bounds) const -{ - return get_wrapping_distance((*bounds.first).first, (*bounds.second).first); -} - -//========================================================================= -namespace -{ - DayInjection::DayInjection(LLEnvironment::EnvSelection_t env): - LLEnvironment::DayInstance(env), - mBaseDayInstance(), - mInjectedSky(), - mInjectedWater(), - mActiveExperiences(), - mDayExperience(), - mSkyExperience(), - mWaterExperience(), - mEnvChangeConnection(), - mParcelChangeConnection() - { - mInjectedSky = std::make_shared(LLEnvironment::instance().getCurrentSky()); - mInjectedWater = std::make_shared(LLEnvironment::instance().getCurrentWater()); - mBaseDayInstance = LLEnvironment::instance().getSharedEnvironmentInstance(); - mSky = mInjectedSky; - mWater = mInjectedWater; - - mEnvChangeConnection = LLEnvironment::instance().setEnvironmentChanged([this](LLEnvironment::EnvSelection_t env, S32) { onEnvironmentChanged(env); }); - mParcelChangeConnection = gAgent.addParcelChangedCallback([this]() { onParcelChange(); }); - } - - DayInjection::~DayInjection() - { - if (mEnvChangeConnection.connected()) - mEnvChangeConnection.disconnect(); - if (mParcelChangeConnection.connected()) - mParcelChangeConnection.disconnect(); - } - - - bool DayInjection::applyTimeDelta(const LLSettingsBase::Seconds& delta) - { - bool changed(false); - - if (mBaseDayInstance) - changed |= mBaseDayInstance->applyTimeDelta(delta); - mInjectedSky->applyInjections(delta); - mInjectedWater->applyInjections(delta); - changed |= LLEnvironment::DayInstance::applyTimeDelta(delta); - if (changed) - { - mInjectedSky->setDirtyFlag(true); - mInjectedWater->setDirtyFlag(true); - } - mInjectedSky->update(); - mInjectedWater->update(); - - if (!hasInjections()) - { // There are no injections being managed. This should really go away. - LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_PUSH); - LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT); - } - - return changed; - } - - void DayInjection::setBaseDayInstance(const LLEnvironment::DayInstance::ptr_t &baseday) - { - mBaseDayInstance = baseday; - - if (mSkyExperience.isNull()) - mInjectedSky->setSource(mBaseDayInstance->getSky()); - if (mWaterExperience.isNull()) - mInjectedWater->setSource(mBaseDayInstance->getWater()); - } - - - bool DayInjection::hasInjections() const - { - return (!mSkyExperience.isNull() || !mWaterExperience.isNull() || !mDayExperience.isNull() || - mBlenderSky || mBlenderWater || mInjectedSky->hasInjections() || mInjectedWater->hasInjections()); - } - - - void DayInjection::testExperiencesOnParcel(S32 parcel_id) - { - LLCoros::instance().launch("DayInjection::testExperiencesOnParcel", - [this, parcel_id]() { DayInjection::testExperiencesOnParcelCoro(std::static_pointer_cast(this->shared_from_this()), parcel_id); }); - - } - - void DayInjection::setInjectedDay(const LLSettingsDay::ptr_t &pday, LLUUID experience_id, LLSettingsBase::Seconds transition) - { - mSkyExperience = experience_id; - mWaterExperience = experience_id; - mDayExperience = experience_id; - - mBaseDayInstance = mBaseDayInstance->clone(); - mBaseDayInstance->setEnvironmentSelection(LLEnvironment::ENV_NONE); - mBaseDayInstance->setDay(pday, mBaseDayInstance->getDayLength(), mBaseDayInstance->getDayOffset()); - animateSkyChange(mBaseDayInstance->getSky(), transition); - animateWaterChange(mBaseDayInstance->getWater(), transition); - - mActiveExperiences.insert(experience_id); - } - - void DayInjection::setInjectedSky(const LLSettingsSky::ptr_t &psky, LLUUID experience_id, LLSettingsBase::Seconds transition) - { - mSkyExperience = experience_id; - mActiveExperiences.insert(experience_id); - checkExperience(); - animateSkyChange(psky, transition); - } - - void DayInjection::setInjectedWater(const LLSettingsWater::ptr_t &pwater, LLUUID experience_id, LLSettingsBase::Seconds transition) - { - mWaterExperience = experience_id; - mActiveExperiences.insert(experience_id); - checkExperience(); - animateWaterChange(pwater, transition); - } - - void DayInjection::injectSkySettings(LLSD settings, LLUUID experience_id, LLSettingsBase::Seconds transition) - { - mInjectedSky->injectExperienceValues(settings, experience_id, transition); - mActiveExperiences.insert(experience_id); - } - - void DayInjection::injectWaterSettings(LLSD settings, LLUUID experience_id, LLSettingsBase::Seconds transition) - { - mInjectedWater->injectExperienceValues(settings, experience_id, transition); - mActiveExperiences.insert(experience_id); - } - - void DayInjection::clearInjections(LLUUID experience_id, LLSettingsBase::Seconds transition_time) - { - if ((experience_id.isNull() && !mDayExperience.isNull()) || (experience_id == mDayExperience)) - { - mDayExperience.setNull(); - if (mSkyExperience == experience_id) - mSkyExperience.setNull(); - if (mWaterExperience == experience_id) - mWaterExperience.setNull(); - - mBaseDayInstance = LLEnvironment::instance().getSharedEnvironmentInstance(); - - if (mSkyExperience.isNull()) - animateSkyChange(mBaseDayInstance->getSky(), transition_time); - if (mWaterExperience.isNull()) - animateWaterChange(mBaseDayInstance->getWater(), transition_time); - } - - if ((experience_id.isNull() && !mSkyExperience.isNull()) || (experience_id == mSkyExperience)) - { - mSkyExperience.setNull(); - animateSkyChange(mBaseDayInstance->getSky(), transition_time); - } - if ((experience_id.isNull() && !mWaterExperience.isNull()) || (experience_id == mWaterExperience)) - { - mWaterExperience.setNull(); - animateWaterChange(mBaseDayInstance->getWater(), transition_time); - } - - mInjectedSky->removeInjections(experience_id, transition_time); - mInjectedWater->removeInjections(experience_id, transition_time); - - if (experience_id.isNull()) - mActiveExperiences.clear(); - else - mActiveExperiences.erase(experience_id); - - if ((transition_time == LLEnvironment::TRANSITION_INSTANT) && (countExperiencesActive() == 0)) - { // Only do this if instant and there are no other experiences injecting values. - // (otherwise will be handled after transition) - LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_PUSH); - LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT); - } - } - - - void DayInjection::testExperiencesOnParcelCoro(wptr_t that, S32 parcel_id) - { - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("testExperiencesOnParcelCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - std::string url = gAgent.getRegionCapability("ExperienceQuery"); - - if (url.empty()) - { - LL_WARNS("ENVIRONMENT") << "No experience query cap." << LL_ENDL; - return; // no checking in this region. - } - - { - ptr_t thatlock(that); - std::stringstream fullurl; - - if (!thatlock) - return; - - fullurl << url << "?"; - fullurl << "parcelid=" << parcel_id; - - for (auto it = thatlock->mActiveExperiences.begin(); it != thatlock->mActiveExperiences.end(); ++it) - { - if (it != thatlock->mActiveExperiences.begin()) - fullurl << ","; - else - fullurl << "&experiences="; - fullurl << (*it).asString(); - } - url = fullurl.str(); - } - - LLSD result = httpAdapter->getAndSuspend(httpRequest, url); - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS() << "Unable to retrieve experience status for parcel." << LL_ENDL; - return; - } - - { - LLParcel* parcel = LLViewerParcelMgr::instance().getAgentParcel(); - if (!parcel) - return; - - if (parcel_id != parcel->getLocalID()) - { - // Agent no longer on queried parcel. - return; - } - } - - - LLSD experiences = result["experiences"]; - { - ptr_t thatlock(that); - if (!thatlock) - return; - - for (LLSD::map_iterator itr = experiences.beginMap(); itr != experiences.endMap(); ++itr) - { - if (!((*itr).second.asBoolean())) - thatlock->clearInjections(LLUUID((*itr).first), LLEnvironment::TRANSITION_FAST); - - } - } - } - - void DayInjection::animateSkyChange(LLSettingsSky::ptr_t psky, LLSettingsBase::Seconds transition) - { - if (mInjectedSky.get() == psky.get()) - { // An attempt to animate to itself... don't do it. - return; - } - if (transition == LLEnvironment::TRANSITION_INSTANT) - { - mBlenderSky.reset(); - mInjectedSky->setSource(psky); - } - else - { - LLSettingsSky::ptr_t start_sky(mInjectedSky->getSource()->buildClone()); - LLSettingsSky::ptr_t target_sky(start_sky->buildClone()); - mInjectedSky->setSource(target_sky); - - // clear reflection probes and pause updates during sky change - gPipeline.mReflectionMapManager.pause(); - gPipeline.mReflectionMapManager.reset(); - - mBlenderSky = std::make_shared(target_sky, start_sky, psky, transition); - mBlenderSky->setOnFinished( - [this, psky](LLSettingsBlender::ptr_t blender) - { - mBlenderSky.reset(); - mInjectedSky->setSource(psky); - - // resume updating reflection probes when done animating sky - gPipeline.mReflectionMapManager.resume(); - setSky(mInjectedSky); - if (!mBlenderWater && (countExperiencesActive() == 0)) - { - LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_PUSH); - LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT); - } - }); - } - } - - void DayInjection::animateWaterChange(LLSettingsWater::ptr_t pwater, LLSettingsBase::Seconds transition) - { - if (mInjectedWater.get() == pwater.get()) - { // An attempt to animate to itself. Bad idea. - return; - } - if (transition == LLEnvironment::TRANSITION_INSTANT) - { - mBlenderWater.reset(); - mInjectedWater->setSource(pwater); - } - else - { - LLSettingsWater::ptr_t start_Water(mInjectedWater->getSource()->buildClone()); - LLSettingsWater::ptr_t scratch_Water(start_Water->buildClone()); - mInjectedWater->setSource(scratch_Water); - - mBlenderWater = std::make_shared(scratch_Water, start_Water, pwater, transition); - mBlenderWater->setOnFinished( - [this, pwater](LLSettingsBlender::ptr_t blender) - { - mBlenderWater.reset(); - mInjectedWater->setSource(pwater); - setWater(mInjectedWater); - if (!mBlenderSky && (countExperiencesActive() == 0)) - { - LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_PUSH); - LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT); - } - }); - } - } - - void DayInjection::onEnvironmentChanged(LLEnvironment::EnvSelection_t env) - { - if (env >= LLEnvironment::ENV_PARCEL) - { - LLEnvironment::EnvSelection_t base_env(mBaseDayInstance->getEnvironmentSelection()); - LLEnvironment::DayInstance::ptr_t nextbase = LLEnvironment::instance().getSharedEnvironmentInstance(); - - if ((base_env == LLEnvironment::ENV_NONE) || (nextbase == mBaseDayInstance) || - (!mSkyExperience.isNull() && !mWaterExperience.isNull())) - { // base instance completely overridden, or not changed no transition will happen - return; - } - - LL_WARNS("PUSHENV", "ENVIRONMENT") << "Underlying environment has changed (" << env << ")! Base env is type " << base_env << LL_ENDL; - - LLEnvironment::DayInstance::ptr_t trans = std::make_shared(std::static_pointer_cast(shared_from_this()), - mBaseDayInstance->getSky(), mBaseDayInstance->getWater(), nextbase, LLEnvironment::TRANSITION_DEFAULT); - - trans->animate(); - setBaseDayInstance(trans); - } - } - - void DayInjection::onParcelChange() - { - S32 parcel_id(INVALID_PARCEL_ID); - LLParcel* parcel = LLViewerParcelMgr::instance().getAgentParcel(); - - if (!parcel) - return; - - parcel_id = parcel->getLocalID(); - - testExperiencesOnParcel(parcel_id); - } - - void DayInjection::checkExperience() - { - if ((!mDayExperience.isNull()) && (mSkyExperience != mDayExperience) && (mWaterExperience != mDayExperience)) - { // There was a day experience but we've replaced it with a water and a sky experience. - mDayExperience.setNull(); - mBaseDayInstance = LLEnvironment::instance().getSharedEnvironmentInstance(); - } - } - - void DayInjection::animate() - { - - } - - void InjectedTransition::animate() - { - mNextInstance->animate(); - - if (!mInjection->isOverriddenSky()) - { - mSky = mStartSky->buildClone(); - mBlenderSky = std::make_shared(mSky, mStartSky, mNextInstance->getSky(), mTransitionTime); - mBlenderSky->setOnFinished( - [this](LLSettingsBlender::ptr_t blender) { - mBlenderSky.reset(); - - if (!mBlenderSky && !mBlenderSky) - mInjection->setBaseDayInstance(mNextInstance); - else - mInjection->mInjectedSky->setSource(mNextInstance->getSky()); - }); - } - else - { - mSky = mInjection->getSky(); - mBlenderSky.reset(); - } - - if (!mInjection->isOverriddenWater()) - { - mWater = mStartWater->buildClone(); - mBlenderWater = std::make_shared(mWater, mStartWater, mNextInstance->getWater(), mTransitionTime); - mBlenderWater->setOnFinished( - [this](LLSettingsBlender::ptr_t blender) { - mBlenderWater.reset(); - - if (!mBlenderSky && !mBlenderWater) - mInjection->setBaseDayInstance(mNextInstance); - else - mInjection->mInjectedWater->setSource(mNextInstance->getWater()); - }); - } - else - { - mWater = mInjection->getWater(); - mBlenderWater.reset(); - } - - } - -} - +/** + * @file llenvmanager.cpp + * @brief Implementation of classes managing WindLight and water settings. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llenvironment.h" + +#include + +#include "llagent.h" +#include "llviewercontrol.h" // for gSavedSettings +#include "llviewerregion.h" +#include "llwlhandlers.h" +#include "lltrans.h" +#include "lltrace.h" +#include "llfasttimer.h" +#include "llviewercamera.h" +#include "pipeline.h" +#include "llsky.h" + +#include "llviewershadermgr.h" + +#include "llparcel.h" +#include "llviewerparcelmgr.h" + +#include "llsdserialize.h" +#include "lldiriterator.h" + +#include "llsettingsvo.h" +#include "llnotificationsutil.h" + +#include "llregioninfomodel.h" + +#include + +#include "llatmosphere.h" +#include "llagent.h" +#include "roles_constants.h" +#include "llestateinfomodel.h" + +#include "lldispatcher.h" +#include "llviewergenericmessage.h" +#include "llexperiencelog.h" + +//========================================================================= +namespace +{ + const std::string KEY_ENVIRONMENT("environment"); + const std::string KEY_DAYASSET("day_asset"); + const std::string KEY_DAYCYCLE("day_cycle"); + const std::string KEY_DAYHASH("day_hash"); + const std::string KEY_DAYLENGTH("day_length"); + const std::string KEY_DAYNAME("day_name"); + const std::string KEY_DAYNAMES("day_names"); + const std::string KEY_DAYOFFSET("day_offset"); + const std::string KEY_ENVVERSION("env_version"); + const std::string KEY_ISDEFAULT("is_default"); + const std::string KEY_PARCELID("parcel_id"); + const std::string KEY_REGIONID("region_id"); + const std::string KEY_TRACKALTS("track_altitudes"); + const std::string KEY_FLAGS("flags"); + + const std::string MESSAGE_PUSHENVIRONMENT("PushExpEnvironment"); + + const std::string ACTION_CLEARENVIRONMENT("ClearEnvironment"); + const std::string ACTION_PUSHFULLENVIRONMENT("PushFullEnvironment"); + const std::string ACTION_PUSHPARTIALENVIRONMENT("PushPartialEnvironment"); + + const std::string KEY_ASSETID("asset_id"); + const std::string KEY_TRANSITIONTIME("transition_time"); + const std::string KEY_ACTION("action"); + const std::string KEY_ACTIONDATA("action_data"); + const std::string KEY_EXPERIENCEID("public_id"); + const std::string KEY_OBJECTNAME("ObjectName"); // some of these do not conform to the '_' format. + const std::string KEY_PARCELNAME("ParcelName"); // But changing these would also alter the Experience Log requirements. + const std::string KEY_COUNT("Count"); + + const std::string LISTENER_NAME("LLEnvironmentSingleton"); + const std::string PUMP_EXPERIENCE("experience_permission"); + + const std::string LOCAL_ENV_STORAGE_FILE("local_environment_data.bin"); + + //--------------------------------------------------------------------- + LLTrace::BlockTimerStatHandle FTM_ENVIRONMENT_UPDATE("Update Environment Tick"); + + LLSettingsBase::Seconds DEFAULT_UPDATE_THRESHOLD(10.0); + const LLSettingsBase::Seconds MINIMUM_SPANLENGTH(0.01f); + + //--------------------------------------------------------------------- + inline LLSettingsBase::TrackPosition get_wrapping_distance(LLSettingsBase::TrackPosition begin, LLSettingsBase::TrackPosition end) + { + if (begin < end) + { + return end - begin; + } + else if (begin > end) + { + return LLSettingsBase::TrackPosition(1.0) - (begin - end); + } + + return 1.0f; + } + + LLSettingsDay::CycleTrack_t::iterator get_wrapping_atafter(LLSettingsDay::CycleTrack_t &collection, const LLSettingsBase::TrackPosition& key) + { + if (collection.empty()) + return collection.end(); + + LLSettingsDay::CycleTrack_t::iterator it = collection.upper_bound(key); + + if (it == collection.end()) + { // wrap around + it = collection.begin(); + } + + return it; + } + + LLSettingsDay::CycleTrack_t::iterator get_wrapping_atbefore(LLSettingsDay::CycleTrack_t &collection, const LLSettingsBase::TrackPosition& key) + { + if (collection.empty()) + return collection.end(); + + LLSettingsDay::CycleTrack_t::iterator it = collection.lower_bound(key); + + if (it == collection.end()) + { // all keyframes are lower, take the last one. + --it; // we know the range is not empty + } + else if ((*it).first > key) + { // the keyframe we are interested in is smaller than the found. + if (it == collection.begin()) + it = collection.end(); + --it; + } + + return it; + } + + LLSettingsDay::TrackBound_t get_bounding_entries(LLSettingsDay::CycleTrack_t &track, const LLSettingsBase::TrackPosition& keyframe) + { + return LLSettingsDay::TrackBound_t(get_wrapping_atbefore(track, keyframe), get_wrapping_atafter(track, keyframe)); + } + + // Find normalized track position of given time along full length of cycle + inline LLSettingsBase::TrackPosition convert_time_to_position(const LLSettingsBase::Seconds& time, const LLSettingsBase::Seconds& len) + { + // early out to avoid divide by zero. if len is zero then jump to end position + if (len == 0.f) return 1.f; + + LLSettingsBase::TrackPosition position = LLSettingsBase::TrackPosition(fmod((F64)time, (F64)len) / (F64)len); + return llclamp(position, 0.0f, 1.0f); + } + + inline LLSettingsBase::BlendFactor convert_time_to_blend_factor(const LLSettingsBase::Seconds& time, const LLSettingsBase::Seconds& len, LLSettingsDay::CycleTrack_t &track) + { + LLSettingsBase::TrackPosition position = convert_time_to_position(time, len); + LLSettingsDay::TrackBound_t bounds(get_bounding_entries(track, position)); + + LLSettingsBase::TrackPosition spanlength(get_wrapping_distance((*bounds.first).first, (*bounds.second).first)); + if (position < (*bounds.first).first) + position += 1.0; + + LLSettingsBase::TrackPosition start = position - (*bounds.first).first; + + return static_cast(start / spanlength); + } + + //--------------------------------------------------------------------- + class LLTrackBlenderLoopingTime : public LLSettingsBlenderTimeDelta + { + public: + LLTrackBlenderLoopingTime(const LLSettingsBase::ptr_t &target, const LLSettingsDay::ptr_t &day, S32 trackno, + LLSettingsBase::Seconds cyclelength, LLSettingsBase::Seconds cycleoffset, LLSettingsBase::Seconds updateThreshold) : + LLSettingsBlenderTimeDelta(target, LLSettingsBase::ptr_t(), LLSettingsBase::ptr_t(), LLSettingsBase::Seconds(1.0)), + mDay(day), + mTrackNo(0), + mCycleLength(cyclelength), + mCycleOffset(cycleoffset) + { + // must happen prior to getBoundingEntries call... + mTrackNo = selectTrackNumber(trackno); + + LLSettingsBase::Seconds now(getAdjustedNow()); + LLSettingsDay::TrackBound_t initial = getBoundingEntries(now); + + mInitial = (*initial.first).second; + mFinal = (*initial.second).second; + mBlendSpan = getSpanTime(initial); + + initializeTarget(now); + setOnFinished([this](const LLSettingsBlender::ptr_t &){ onFinishedSpan(); }); + } + + void switchTrack(S32 trackno, const LLSettingsBase::TrackPosition&) override + { + S32 use_trackno = selectTrackNumber(trackno); + + if (use_trackno == mTrackNo) + { // results in no change + return; + } + + LLSettingsBase::ptr_t pstartsetting = mTarget->buildDerivedClone(); + mTrackNo = use_trackno; + + LLSettingsBase::Seconds now = getAdjustedNow() + LLEnvironment::TRANSITION_ALTITUDE; + LLSettingsDay::TrackBound_t bounds = getBoundingEntries(now); + + LLSettingsBase::ptr_t pendsetting = (*bounds.first).second->buildDerivedClone(); + LLSettingsBase::TrackPosition targetpos = convert_time_to_position(now, mCycleLength) - (*bounds.first).first; + LLSettingsBase::TrackPosition targetspan = get_wrapping_distance((*bounds.first).first, (*bounds.second).first); + + LLSettingsBase::BlendFactor blendf = calculateBlend(targetpos, targetspan); + pendsetting->blend((*bounds.second).second, blendf); + + reset(pstartsetting, pendsetting, LLEnvironment::TRANSITION_ALTITUDE); + } + + protected: + S32 selectTrackNumber(S32 trackno) + { + if (trackno == 0) + { // We are dealing with the water track. There is only ever one. + return trackno; + } + + for (S32 test = trackno; test != 0; --test) + { // Find the track below the requested one with data. + LLSettingsDay::CycleTrack_t &track = mDay->getCycleTrack(test); + + if (!track.empty()) + return test; + } + + return 1; + } + + LLSettingsDay::TrackBound_t getBoundingEntries(LLSettingsBase::Seconds time) + { + LLSettingsDay::CycleTrack_t &wtrack = mDay->getCycleTrack(mTrackNo); + LLSettingsBase::TrackPosition position = convert_time_to_position(time, mCycleLength); + LLSettingsDay::TrackBound_t bounds = get_bounding_entries(wtrack, position); + return bounds; + } + + void initializeTarget(LLSettingsBase::Seconds time) + { + LLSettingsBase::BlendFactor blendf(convert_time_to_blend_factor(time, mCycleLength, mDay->getCycleTrack(mTrackNo))); + + blendf = llclamp(blendf, 0.0, 0.999); + setTimeSpent(LLSettingsBase::Seconds(blendf * mBlendSpan)); + + setBlendFactor(blendf); + } + + LLSettingsBase::Seconds getAdjustedNow() const + { + LLSettingsBase::Seconds now(LLDate::now().secondsSinceEpoch()); + + return (now + mCycleOffset); + } + + LLSettingsBase::Seconds getSpanTime(const LLSettingsDay::TrackBound_t &bounds) const + { + LLSettingsBase::Seconds span = mCycleLength * get_wrapping_distance((*bounds.first).first, (*bounds.second).first); + if (span < MINIMUM_SPANLENGTH) // for very short spans set a minimum length. + span = MINIMUM_SPANLENGTH; + return span; + } + + private: + LLSettingsDay::ptr_t mDay; + S32 mTrackNo; + LLSettingsBase::Seconds mCycleLength; + LLSettingsBase::Seconds mCycleOffset; + + void onFinishedSpan() + { + LLSettingsBase::Seconds adjusted_now = getAdjustedNow(); + LLSettingsDay::TrackBound_t next = getBoundingEntries(adjusted_now); + LLSettingsBase::Seconds nextspan = getSpanTime(next); + + reset((*next.first).second, (*next.second).second, nextspan); + + // Recalculate (reinitialize) position. Because: + // - 'delta' from applyTimeDelta accumulates errors (probably should be fixed/changed to absolute time) + // - freezes and lag can result in reset being called too late, so we need to add missed time + // - occasional time corrections can happen + // - some transition switches can happen outside applyTimeDelta thus causing 'desync' from 'delta' (can be fixed by getting rid of delta) + initializeTarget(adjusted_now); + } + }; + + class LLEnvironmentPushDispatchHandler : public LLDispatchHandler + { + public: + virtual bool operator()(const LLDispatcher *, const std::string& key, const LLUUID& invoice, const sparam_t& strings) override + { + LLSD message; + sparam_t::const_iterator it = strings.begin(); + + if (it != strings.end()) + { + const std::string& llsdRaw = *it++; + std::istringstream llsdData(llsdRaw); + if (!LLSDSerialize::deserialize(message, llsdData, llsdRaw.length())) + { + LL_WARNS() << "LLEnvironmentPushDispatchHandler: Attempted to read parameter data into LLSD but failed:" << llsdRaw << LL_ENDL; + } + } + + message[KEY_EXPERIENCEID] = invoice; + // Object Name + if (it != strings.end()) + { + message[KEY_OBJECTNAME] = *it++; + } + + // parcel Name + if (it != strings.end()) + { + message[KEY_PARCELNAME] = *it++; + } + message[KEY_COUNT] = 1; + + LLEnvironment::instance().handleEnvironmentPush(message); + return true; + } + }; + + LLEnvironmentPushDispatchHandler environment_push_dispatch_handler; + + template + class LLSettingsInjected : public SETTINGT + { + public: + typedef std::shared_ptr > ptr_t; + + LLSettingsInjected(typename SETTINGT::ptr_t source) : + SETTINGT(), + mSource(source), + mLastSourceHash(0), + mLastHash(0) + {} + + virtual ~LLSettingsInjected() {}; + + typename SETTINGT::ptr_t getSource() const { return this->mSource; } + void setSource(const typename SETTINGT::ptr_t &source) + { + if (source.get() == this) // do not set a source to itself. + return; + this->mSource = source; + this->setDirtyFlag(true); + this->mLastSourceHash = 0; + } + + virtual bool isDirty() const override { return SETTINGT::isDirty() || (this->mSource->isDirty()); } + virtual bool isVeryDirty() const override { return SETTINGT::isVeryDirty() || (this->mSource->isVeryDirty()); } + + void injectSetting(const std::string keyname, LLSD value, LLUUID experience_id, F32Seconds transition) + { + if (transition > 0.1) + { + typename Injection::ptr_t injection = std::make_shared(transition, keyname, value, true, experience_id); + + mInjections.push_back(injection); + std::stable_sort(mInjections.begin(), mInjections.end(), [](const typename Injection::ptr_t &a, const typename Injection::ptr_t &b) { return a->mTimeRemaining < b->mTimeRemaining; }); + } + else + { + mOverrideValues[keyname] = value; + mOverrideExps[keyname] = experience_id; + this->setDirtyFlag(true); + } + } + + void removeInjection(const std::string keyname, LLUUID experience, LLSettingsBase::Seconds transition) + { + injections_t injections_buf; + for (auto it = mInjections.begin(); it != mInjections.end(); it++) + { + if ((keyname.empty() || ((*it)->mKeyName == keyname)) && + (experience.isNull() || (experience == (*it)->mExperience))) + { + if (transition != LLEnvironment::TRANSITION_INSTANT) + { + typename Injection::ptr_t injection = std::make_shared(transition, keyname, (*it)->mLastValue, false, LLUUID::null); + injections_buf.push_front(injection); + } + } + else + { + injections_buf.push_front(*it); + } + } + mInjections.clear(); + mInjections = injections_buf; + + for (auto itexp = mOverrideExps.begin(); itexp != mOverrideExps.end();) + { + if (experience.isNull() || ((*itexp).second == experience)) + { + if (transition != LLEnvironment::TRANSITION_INSTANT) + { + typename Injection::ptr_t injection = std::make_shared(transition, (*itexp).first, mOverrideValues[(*itexp).first], false, LLUUID::null); + mInjections.push_front(injection); + } + mOverrideValues.erase((*itexp).first); + mOverrideExps.erase(itexp++); + } + else + ++itexp; + } + std::stable_sort(mInjections.begin(), mInjections.end(), [](const typename Injection::ptr_t &a, const typename Injection::ptr_t &b) { return a->mTimeRemaining < b->mTimeRemaining; }); + } + + void removeInjections(LLUUID experience_id, LLSettingsBase::Seconds transition) + { + removeInjection(std::string(), experience_id, transition); + } + + void injectExperienceValues(LLSD values, LLUUID experience_id, typename LLSettingsBase::Seconds transition) + { + for (auto it = values.beginMap(); it != values.endMap(); ++it) + { + injectSetting((*it).first, (*it).second, experience_id, transition); + } + this->setDirtyFlag(true); + } + + void applyInjections(LLSettingsBase::Seconds delta) + { + this->mSettings = this->mSource->getSettings(); + + for (auto ito = mOverrideValues.beginMap(); ito != mOverrideValues.endMap(); ++ito) + { + this->mSettings[(*ito).first] = (*ito).second; + } + + const LLSettingsBase::stringset_t &slerps = this->getSlerpKeys(); + const LLSettingsBase::stringset_t &skips = this->getSkipInterpolateKeys(); + const LLSettingsBase::stringset_t &specials = this->getSpecialKeys(); + + typename injections_t::iterator it; + for (it = mInjections.begin(); it != mInjections.end(); ++it) + { + std::string key_name = (*it)->mKeyName; + + LLSD value = this->mSettings[key_name]; + LLSD target = (*it)->mValue; + + if ((*it)->mFirstTime) + (*it)->mFirstTime = false; + else + (*it)->mTimeRemaining -= delta; + + typename LLSettingsBase::BlendFactor mix = 1.0f - ((*it)->mTimeRemaining.value() / (*it)->mTransition.value()); + + if (mix >= 1.0) + { + if ((*it)->mBlendIn) + { + mOverrideValues[key_name] = target; + mOverrideExps[key_name] = (*it)->mExperience; + this->mSettings[key_name] = target; + } + else + { + this->mSettings.erase(key_name); + } + } + else if (specials.find(key_name) != specials.end()) + { + updateSpecial(*it, mix); + } + else if (skips.find(key_name) == skips.end()) + { + if (!(*it)->mBlendIn) + mix = 1.0 - mix; + (*it)->mLastValue = this->interpolateSDValue(key_name, value, target, this->getParameterMap(), mix, slerps); + this->mSettings[key_name] = (*it)->mLastValue; + } + } + + size_t hash = this->getHash(); + + if (hash != mLastHash) + { + this->setDirtyFlag(true); + mLastHash = hash; + } + + it = mInjections.begin(); + it = std::find_if(mInjections.begin(), mInjections.end(), [](const typename Injection::ptr_t &a) { return a->mTimeRemaining > 0.0f; }); + + if (it != mInjections.begin()) + { + mInjections.erase(mInjections.begin(), mInjections.end()); + } + + } + + bool hasInjections() const + { + return (!mInjections.empty() || (mOverrideValues.size() > 0)); + } + + protected: + struct Injection + { + Injection(typename LLSettingsBase::Seconds transition, const std::string &keyname, LLSD value, bool blendin, LLUUID experince, S32 index = -1) : + mTransition(transition), + mTimeRemaining(transition), + mKeyName(keyname), + mValue(value), + mExperience(experince), + mIndex(index), + mBlendIn(blendin), + mFirstTime(true) + {} + + typename LLSettingsBase::Seconds mTransition; + typename LLSettingsBase::Seconds mTimeRemaining; + std::string mKeyName; + LLSD mValue; + LLSD mLastValue; + LLUUID mExperience; + S32 mIndex; + bool mBlendIn; + bool mFirstTime; + + typedef std::shared_ptr ptr_t; + }; + + + virtual void updateSettings() override + { + static LLFrameTimer timer; + + if (!this->mSource) + return; + + // clears the dirty flag on this object. Need to prevent triggering + // more calls into this updateSettings + LLSettingsBase::updateSettings(); + + resetSpecial(); + + if (this->mSource->isDirty()) + { + this->mSource->updateSettings(); + } + + typename LLSettingsBase::Seconds delta(timer.getElapsedTimeAndResetF32()); + + + SETTINGT::updateSettings(); + + if (!mInjections.empty()) + this->setDirtyFlag(true); + } + + LLSettingsBase::stringset_t getSpecialKeys() const; + void resetSpecial(); + void updateSpecial(const typename Injection::ptr_t &injection, typename LLSettingsBase::BlendFactor mix); + + private: + typedef std::map key_to_expid_t; + typedef std::deque injections_t; + + size_t mLastSourceHash; + size_t mLastHash; + typename SETTINGT::ptr_t mSource; + injections_t mInjections; + LLSD mOverrideValues; + key_to_expid_t mOverrideExps; + }; + + template<> + LLSettingsBase::stringset_t LLSettingsInjected::getSpecialKeys() const + { + static LLSettingsBase::stringset_t specialSet; + + if (specialSet.empty()) + { + specialSet.insert(SETTING_BLOOM_TEXTUREID); + specialSet.insert(SETTING_RAINBOW_TEXTUREID); + specialSet.insert(SETTING_HALO_TEXTUREID); + specialSet.insert(SETTING_CLOUD_TEXTUREID); + specialSet.insert(SETTING_MOON_TEXTUREID); + specialSet.insert(SETTING_SUN_TEXTUREID); + specialSet.insert(SETTING_CLOUD_SHADOW); // due to being part of skips + } + return specialSet; + } + + template<> + LLSettingsBase::stringset_t LLSettingsInjected::getSpecialKeys() const + { + static stringset_t specialSet; + + if (specialSet.empty()) + { + specialSet.insert(SETTING_TRANSPARENT_TEXTURE); + specialSet.insert(SETTING_NORMAL_MAP); + } + return specialSet; + } + + template<> + void LLSettingsInjected::resetSpecial() + { + mNextSunTextureId.setNull(); + mNextMoonTextureId.setNull(); + mNextCloudTextureId.setNull(); + mNextBloomTextureId.setNull(); + mNextRainbowTextureId.setNull(); + mNextHaloTextureId.setNull(); + setBlendFactor(0.0f); + } + + template<> + void LLSettingsInjected::resetSpecial() + { + mNextNormalMapID.setNull(); + mNextTransparentTextureID.setNull(); + setBlendFactor(0.0f); + } + + template<> + void LLSettingsInjected::updateSpecial(const typename LLSettingsInjected::Injection::ptr_t &injection, typename LLSettingsBase::BlendFactor mix) + { + bool is_texture = true; + if (injection->mKeyName == SETTING_SUN_TEXTUREID) + { + mNextSunTextureId = injection->mValue.asUUID(); + } + else if (injection->mKeyName == SETTING_MOON_TEXTUREID) + { + mNextMoonTextureId = injection->mValue.asUUID(); + } + else if (injection->mKeyName == SETTING_CLOUD_TEXTUREID) + { + mNextCloudTextureId = injection->mValue.asUUID(); + } + else if (injection->mKeyName == SETTING_BLOOM_TEXTUREID) + { + mNextBloomTextureId = injection->mValue.asUUID(); + } + else if (injection->mKeyName == SETTING_RAINBOW_TEXTUREID) + { + mNextRainbowTextureId = injection->mValue.asUUID(); + } + else if (injection->mKeyName == SETTING_HALO_TEXTUREID) + { + mNextHaloTextureId = injection->mValue.asUUID(); + } + else if (injection->mKeyName == LLSettingsSky::SETTING_CLOUD_SHADOW) + { + // Special case due to being texture dependent and part of skips + is_texture = false; + if (!injection->mBlendIn) + mix = 1.0 - mix; + stringset_t dummy; + F64 value = this->mSettings[injection->mKeyName].asReal(); + if (this->getCloudNoiseTextureId().isNull()) + { + value = 0; // there was no texture so start from zero coverage + } + // Ideally we need to check for texture in injection, but + // in this case user is setting value explicitly, potentially + // with different transitions, don't ignore it + F64 result = lerp(value, injection->mValue.asReal(), mix); + injection->mLastValue = LLSD::Real(result); + this->mSettings[injection->mKeyName] = injection->mLastValue; + } + + // Unfortunately I don't have a per texture blend factor. We'll just pick the one that is furthest along. + if (is_texture && getBlendFactor() < mix) + { + setBlendFactor(mix); + } + } + + template<> + void LLSettingsInjected::updateSpecial(const typename LLSettingsInjected::Injection::ptr_t &injection, typename LLSettingsBase::BlendFactor mix) + { + if (injection->mKeyName == SETTING_NORMAL_MAP) + { + mNextNormalMapID = injection->mValue.asUUID(); + } + else if (injection->mKeyName == SETTING_TRANSPARENT_TEXTURE) + { + mNextTransparentTextureID = injection->mValue.asUUID(); + } + + // Unfortunately I don't have a per texture blend factor. We'll just pick the one that is furthest along. + if (getBlendFactor() < mix) + { + setBlendFactor(mix); + } + } + + typedef LLSettingsInjected LLSettingsInjectedSky; + typedef LLSettingsInjected LLSettingsInjectedWater; + + //===================================================================== + class DayInjection : public LLEnvironment::DayInstance + { + friend class InjectedTransition; + + public: + typedef std::shared_ptr ptr_t; + typedef std::weak_ptr wptr_t; + + DayInjection(LLEnvironment::EnvSelection_t env); + virtual ~DayInjection(); + + virtual bool applyTimeDelta(const LLSettingsBase::Seconds& delta) override; + + void setInjectedDay(const LLSettingsDay::ptr_t &pday, LLUUID experience_id, LLSettingsBase::Seconds transition); + void setInjectedSky(const LLSettingsSky::ptr_t &psky, LLUUID experience_id, LLSettingsBase::Seconds transition); + void setInjectedWater(const LLSettingsWater::ptr_t &pwater, LLUUID experience_id, LLSettingsBase::Seconds transition); + + void injectSkySettings(LLSD settings, LLUUID experience_id, LLSettingsBase::Seconds transition); + void injectWaterSettings(LLSD settings, LLUUID experience_id, LLSettingsBase::Seconds transition); + + void clearInjections(LLUUID experience_id, LLSettingsBase::Seconds transition_time); + + virtual void animate() override; + + LLEnvironment::DayInstance::ptr_t getBaseDayInstance() const { return mBaseDayInstance; } + void setBaseDayInstance(const LLEnvironment::DayInstance::ptr_t &baseday); + + S32 countExperiencesActive() const { return mActiveExperiences.size(); } + + bool isOverriddenSky() const { return !mSkyExperience.isNull(); } + bool isOverriddenWater() const { return !mWaterExperience.isNull(); } + + bool hasInjections() const; + + void testExperiencesOnParcel(S32 parcel_id); + private: + static void testExperiencesOnParcelCoro(wptr_t that, S32 parcel_id); + + + void animateSkyChange(LLSettingsSky::ptr_t psky, LLSettingsBase::Seconds transition); + void animateWaterChange(LLSettingsWater::ptr_t pwater, LLSettingsBase::Seconds transition); + + void onEnvironmentChanged(LLEnvironment::EnvSelection_t env); + void onParcelChange(); + + void checkExperience(); + + + LLEnvironment::DayInstance::ptr_t mBaseDayInstance; + + LLSettingsInjectedSky::ptr_t mInjectedSky; + LLSettingsInjectedWater::ptr_t mInjectedWater; + std::set mActiveExperiences; + LLUUID mDayExperience; + LLUUID mSkyExperience; + LLUUID mWaterExperience; + LLEnvironment::connection_t mEnvChangeConnection; + boost::signals2::connection mParcelChangeConnection; + }; + + class InjectedTransition : public LLEnvironment::DayTransition + { + public: + InjectedTransition(const DayInjection::ptr_t &injection, const LLSettingsSky::ptr_t &skystart, const LLSettingsWater::ptr_t &waterstart, DayInstance::ptr_t &end, LLSettingsDay::Seconds time): + LLEnvironment::DayTransition(skystart, waterstart, end, time), + mInjection(injection) + { } + virtual ~InjectedTransition() { }; + + virtual void animate() override; + + protected: + DayInjection::ptr_t mInjection; + }; + +} + +//========================================================================= +const F64Seconds LLEnvironment::TRANSITION_INSTANT(0.0f); +const F64Seconds LLEnvironment::TRANSITION_FAST(1.0f); +const F64Seconds LLEnvironment::TRANSITION_DEFAULT(5.0f); +const F64Seconds LLEnvironment::TRANSITION_SLOW(10.0f); +const F64Seconds LLEnvironment::TRANSITION_ALTITUDE(5.0f); + +const LLUUID LLEnvironment::KNOWN_SKY_SUNRISE("01e41537-ff51-2f1f-8ef7-17e4df760bfb"); +const LLUUID LLEnvironment::KNOWN_SKY_MIDDAY("c46226b4-0e43-5a56-9708-d27ca1df3292"); +const LLUUID LLEnvironment::KNOWN_SKY_LEGACY_MIDDAY("cef49723-0292-af49-9b14-9598a616b8a3"); +const LLUUID LLEnvironment::KNOWN_SKY_SUNSET("084e26cd-a900-28e8-08d0-64a9de5c15e2"); +const LLUUID LLEnvironment::KNOWN_SKY_MIDNIGHT("8a01b97a-cb20-c1ea-ac63-f7ea84ad0090"); + +const S32 LLEnvironment::NO_TRACK(-1); +const S32 LLEnvironment::NO_VERSION(-3); // For viewer sided change, like ENV_LOCAL. -3 since -1 and -2 are taken by parcel initial server/viewer version +const S32 LLEnvironment::VERSION_CLEANUP(-4); // for cleanups + +const F32 LLEnvironment::SUN_DELTA_YAW(F_PI); // 180deg + + +const U32 LLEnvironment::DayInstance::NO_ANIMATE_SKY(0x01); +const U32 LLEnvironment::DayInstance::NO_ANIMATE_WATER(0x02); + +std::string env_selection_to_string(LLEnvironment::EnvSelection_t sel) +{ +#define RTNENUM(E) case LLEnvironment::E: return #E + switch (sel){ + RTNENUM(ENV_EDIT); + RTNENUM(ENV_LOCAL); + RTNENUM(ENV_PUSH); + RTNENUM(ENV_PARCEL); + RTNENUM(ENV_REGION); + RTNENUM(ENV_DEFAULT); + RTNENUM(ENV_END); + RTNENUM(ENV_CURRENT); + RTNENUM(ENV_NONE); + default: + return llformat("Unknown(%d)", sel); + } +#undef RTNENUM +} + +//------------------------------------------------------------------------- +LLEnvironment::LLEnvironment(): + mCloudScrollDelta(), + mCloudScrollPaused(false), + mSelectedSky(), + mSelectedWater(), + mSelectedDay(), + mSelectedEnvironment(LLEnvironment::ENV_LOCAL), + mCurrentTrack(1), + mEditorCounter(0), + mShowSunBeacon(false), + mShowMoonBeacon(false) +{ +} + +void LLEnvironment::initSingleton() +{ + LLSettingsSky::ptr_t p_default_sky = LLSettingsVOSky::buildDefaultSky(); + LLSettingsWater::ptr_t p_default_water = LLSettingsVOWater::buildDefaultWater(); + + mCurrentEnvironment = std::make_shared(ENV_DEFAULT); + mCurrentEnvironment->setSky(p_default_sky); + mCurrentEnvironment->setWater(p_default_water); + + mEnvironments[ENV_DEFAULT] = mCurrentEnvironment; + + requestRegion(); + + if (!mParcelCallbackConnection.connected()) + { + mParcelCallbackConnection = gAgent.addParcelChangedCallback([this]() { onParcelChange(); }); + + //TODO: This frequently results in one more request than we need. It isn't breaking, but should be nicer. + // We need to know new env version to fix this, without it we can only do full re-request + // Happens: on updates, on opening LLFloaterRegionInfo, on region crossing if info floater is open + mRegionUpdateCallbackConnection = LLRegionInfoModel::instance().setUpdateCallback([this]() { requestRegion(); }); + mRegionChangeCallbackConnection = gAgent.addRegionChangedCallback([this]() { onRegionChange(); }); + + mPositionCallbackConnection = gAgent.whenPositionChanged([this](const LLVector3 &localpos, const LLVector3d &) { onAgentPositionHasChanged(localpos); }); + } + + if (!gGenericDispatcher.isHandlerPresent(MESSAGE_PUSHENVIRONMENT)) + { + gGenericDispatcher.addHandler(MESSAGE_PUSHENVIRONMENT, &environment_push_dispatch_handler); + } + + gSavedSettings.getControl("RenderSkyAutoAdjustProbeAmbiance")->getSignal()->connect( + [](LLControlVariable*, const LLSD& new_val, const LLSD& old_val) + { + LLSettingsSky::sAutoAdjustProbeAmbiance = new_val.asReal(); + } + ); + LLSettingsSky::sAutoAdjustProbeAmbiance = gSavedSettings.getF32("RenderSkyAutoAdjustProbeAmbiance"); + + LLEventPumps::instance().obtain(PUMP_EXPERIENCE).stopListening(LISTENER_NAME); + LLEventPumps::instance().obtain(PUMP_EXPERIENCE).listen(LISTENER_NAME, [this](LLSD message) { listenExperiencePump(message); return false; }); +} + +void LLEnvironment::cleanupSingleton() +{ + if (mParcelCallbackConnection.connected()) + { + mParcelCallbackConnection.disconnect(); + mRegionUpdateCallbackConnection.disconnect(); + mRegionChangeCallbackConnection.disconnect(); + mPositionCallbackConnection.disconnect(); + } + LLEventPumps::instance().obtain(PUMP_EXPERIENCE).stopListening(LISTENER_NAME); +} + +LLEnvironment::~LLEnvironment() +{ + cleanupSingleton(); +} + +bool LLEnvironment::canEdit() const +{ + return true; +} + +LLSettingsSky::ptr_t LLEnvironment::getCurrentSky() const +{ + LLSettingsSky::ptr_t psky = mCurrentEnvironment->getSky(); + + if (!psky && mCurrentEnvironment->getEnvironmentSelection() >= ENV_EDIT) + { + for (int idx = 0; idx < ENV_END; ++idx) + { + if (mEnvironments[idx]->getSky()) + { + psky = mEnvironments[idx]->getSky(); + break; + } + } + } + return psky; +} + +LLSettingsWater::ptr_t LLEnvironment::getCurrentWater() const +{ + LLSettingsWater::ptr_t pwater = mCurrentEnvironment->getWater(); + + if (!pwater && mCurrentEnvironment->getEnvironmentSelection() >= ENV_EDIT) + { + for (int idx = 0; idx < ENV_END; ++idx) + { + if (mEnvironments[idx]->getWater()) + { + pwater = mEnvironments[idx]->getWater(); + break; + } + } + } + return pwater; +} + +void LayerConfigToDensityLayer(const LLSD& layerConfig, DensityLayer& layerOut) +{ + layerOut.constant_term = layerConfig[LLSettingsSky::SETTING_DENSITY_PROFILE_CONSTANT_TERM].asReal(); + layerOut.exp_scale = layerConfig[LLSettingsSky::SETTING_DENSITY_PROFILE_EXP_SCALE_FACTOR].asReal(); + layerOut.exp_term = layerConfig[LLSettingsSky::SETTING_DENSITY_PROFILE_EXP_TERM].asReal(); + layerOut.linear_term = layerConfig[LLSettingsSky::SETTING_DENSITY_PROFILE_LINEAR_TERM].asReal(); + layerOut.width = layerConfig[LLSettingsSky::SETTING_DENSITY_PROFILE_WIDTH].asReal(); +} + +void LLEnvironment::getAtmosphericModelSettings(AtmosphericModelSettings& settingsOut, const LLSettingsSky::ptr_t &psky) +{ + settingsOut.m_skyBottomRadius = psky->getSkyBottomRadius(); + settingsOut.m_skyTopRadius = psky->getSkyTopRadius(); + settingsOut.m_sunArcRadians = psky->getSunArcRadians(); + settingsOut.m_mieAnisotropy = psky->getMieAnisotropy(); + + LLSD rayleigh = psky->getRayleighConfigs(); + settingsOut.m_rayleighProfile.clear(); + for (LLSD::array_iterator itf = rayleigh.beginArray(); itf != rayleigh.endArray(); ++itf) + { + DensityLayer layer; + LLSD& layerConfig = (*itf); + LayerConfigToDensityLayer(layerConfig, layer); + settingsOut.m_rayleighProfile.push_back(layer); + } + + LLSD mie = psky->getMieConfigs(); + settingsOut.m_mieProfile.clear(); + for (LLSD::array_iterator itf = mie.beginArray(); itf != mie.endArray(); ++itf) + { + DensityLayer layer; + LLSD& layerConfig = (*itf); + LayerConfigToDensityLayer(layerConfig, layer); + settingsOut.m_mieProfile.push_back(layer); + } + settingsOut.m_mieAnisotropy = psky->getMieAnisotropy(); + + LLSD absorption = psky->getAbsorptionConfigs(); + settingsOut.m_absorptionProfile.clear(); + for (LLSD::array_iterator itf = absorption.beginArray(); itf != absorption.endArray(); ++itf) + { + DensityLayer layer; + LLSD& layerConfig = (*itf); + LayerConfigToDensityLayer(layerConfig, layer); + settingsOut.m_absorptionProfile.push_back(layer); + } +} + +bool LLEnvironment::canAgentUpdateParcelEnvironment() const +{ + LLParcel *parcel(LLViewerParcelMgr::instance().getAgentOrSelectedParcel()); + + return canAgentUpdateParcelEnvironment(parcel); +} + + +bool LLEnvironment::canAgentUpdateParcelEnvironment(LLParcel *parcel) const +{ + if (!parcel) + return false; + + if (!LLEnvironment::instance().isExtendedEnvironmentEnabled()) + return false; + + if (gAgent.isGodlike()) + return true; + + if (!parcel->getRegionAllowEnvironmentOverride()) + return false; + + return LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_ALLOW_ENVIRONMENT); +} + +bool LLEnvironment::canAgentUpdateRegionEnvironment() const +{ + if (gAgent.isGodlike()) + return true; + + return gAgent.canManageEstate(); +} + +bool LLEnvironment::isExtendedEnvironmentEnabled() const +{ + return !gAgent.getRegionCapability("ExtEnvironment").empty(); +} + +bool LLEnvironment::isInventoryEnabled() const +{ + return (!gAgent.getRegionCapability("UpdateSettingsAgentInventory").empty() && + !gAgent.getRegionCapability("UpdateSettingsTaskInventory").empty()); +} + +void LLEnvironment::onRegionChange() +{ +// if (gAgent.getRegionCapability("ExperienceQuery").empty()) +// { +// // for now environmental experiences do not survive region crossings + clearExperienceEnvironment(LLUUID::null, TRANSITION_DEFAULT); +// } + + LLViewerRegion* cur_region = gAgent.getRegion(); + if (!cur_region) + { + return; + } + if (!cur_region->capabilitiesReceived()) + { + cur_region->setCapabilitiesReceivedCallback([](const LLUUID ®ion_id, LLViewerRegion* regionp) { LLEnvironment::instance().requestRegion(); }); + return; + } + requestRegion(); +} + +void LLEnvironment::onParcelChange() +{ + S32 parcel_id(INVALID_PARCEL_ID); + LLParcel* parcel = LLViewerParcelMgr::instance().getAgentParcel(); + + if (parcel) + { + parcel_id = parcel->getLocalID(); + } + + requestParcel(parcel_id); +} + +//------------------------------------------------------------------------- +F32 LLEnvironment::getCamHeight() const +{ + return (mCurrentEnvironment->getSky()->getDomeOffset() * mCurrentEnvironment->getSky()->getDomeRadius()); +} + +F32 LLEnvironment::getWaterHeight() const +{ + LLViewerRegion* cur_region = gAgent.getRegion(); + return cur_region ? cur_region->getWaterHeight() : DEFAULT_WATER_HEIGHT; +} + +bool LLEnvironment::getIsSunUp() const +{ + if (!mCurrentEnvironment || !mCurrentEnvironment->getSky()) + return false; + return mCurrentEnvironment->getSky()->getIsSunUp(); +} + +bool LLEnvironment::getIsMoonUp() const +{ + if (!mCurrentEnvironment || !mCurrentEnvironment->getSky()) + return false; + return mCurrentEnvironment->getSky()->getIsMoonUp(); +} + +//------------------------------------------------------------------------- +void LLEnvironment::setSelectedEnvironment(LLEnvironment::EnvSelection_t env, LLSettingsBase::Seconds transition, bool forced) +{ + mSelectedEnvironment = env; + updateEnvironment(transition, forced); + LL_DEBUGS("ENVIRONMENT") << "Setting environment " << env_selection_to_string(env) << " with transition: " << transition << LL_ENDL; +} + +bool LLEnvironment::hasEnvironment(LLEnvironment::EnvSelection_t env) +{ + if ((env < ENV_EDIT) || (env >= ENV_DEFAULT) || (!mEnvironments[env])) + { + return false; + } + + return true; +} + +LLEnvironment::DayInstance::ptr_t LLEnvironment::getEnvironmentInstance(LLEnvironment::EnvSelection_t env, bool create /*= false*/) +{ + DayInstance::ptr_t environment = mEnvironments[env]; + if (create) + { + if (environment) + environment = environment->clone(); + else + { + if (env == ENV_PUSH) + environment = std::make_shared(env); + else + environment = std::make_shared(env); + } + mEnvironments[env] = environment; + } + + return environment; +} + + +void LLEnvironment::setEnvironment(LLEnvironment::EnvSelection_t env, const LLSettingsDay::ptr_t &pday, LLSettingsDay::Seconds daylength, LLSettingsDay::Seconds dayoffset, S32 env_version) +{ + if ((env < ENV_EDIT) || (env >= ENV_DEFAULT)) + { + LL_WARNS("ENVIRONMENT") << "Attempt to change invalid environment selection (" << env_selection_to_string(env) << ")." << LL_ENDL; + return; + } + + logEnvironment(env, pday, env_version); + + DayInstance::ptr_t environment = getEnvironmentInstance(env, true); + + environment->clear(); + environment->setDay(pday, daylength, dayoffset); + environment->setSkyTrack(mCurrentTrack); + environment->animate(); + + if (!mSignalEnvChanged.empty()) + mSignalEnvChanged(env, env_version); +} + +void LLEnvironment::setCurrentEnvironmentSelection(LLEnvironment::EnvSelection_t env) +{ + mCurrentEnvironment->setEnvironmentSelection(env); +} + +void LLEnvironment::setEnvironment(LLEnvironment::EnvSelection_t env, LLEnvironment::fixedEnvironment_t fixed, S32 env_version) +{ + if ((env < ENV_EDIT) || (env >= ENV_DEFAULT)) + { + LL_WARNS("ENVIRONMENT") << "Attempt to change invalid environment selection (" << env_selection_to_string(env) << ")." << LL_ENDL; + return; + } + + bool reset_probes = false; + + DayInstance::ptr_t environment = getEnvironmentInstance(env, true); + + if (fixed.first) + { + logEnvironment(env, fixed.first, env_version); + reset_probes = environment->setSky(fixed.first); + environment->setFlags(DayInstance::NO_ANIMATE_SKY); + } + else if (!environment->getSky()) + { + if (mCurrentEnvironment->getEnvironmentSelection() != ENV_NONE) + { + // Note: This looks suspicious. Shouldn't we assign whole day if mCurrentEnvironment has whole day? + // and then add water/sky on top + // This looks like it will result in sky using single keyframe instead of whole day if day is present + // when setting static water without static sky + reset_probes = environment->setSky(mCurrentEnvironment->getSky()); + environment->setFlags(DayInstance::NO_ANIMATE_SKY); + } + else + { + // Environment is not properly initialized yet, but we should have environment by this point + DayInstance::ptr_t substitute = getEnvironmentInstance(ENV_PARCEL, true); + if (!substitute || !substitute->getSky()) + { + substitute = getEnvironmentInstance(ENV_REGION, true); + } + if (!substitute || !substitute->getSky()) + { + substitute = getEnvironmentInstance(ENV_DEFAULT, true); + } + + if (substitute && substitute->getSky()) + { + reset_probes = environment->setSky(substitute->getSky()); + environment->setFlags(DayInstance::NO_ANIMATE_SKY); + } + else + { + LL_WARNS("ENVIRONMENT") << "Failed to assign substitute water/sky, environment is not properly initialized" << LL_ENDL; + } + } + } + + if (fixed.second) + { + logEnvironment(env, fixed.second, env_version); + environment->setWater(fixed.second); + environment->setFlags(DayInstance::NO_ANIMATE_WATER); + } + else if (!environment->getWater()) + { + if (mCurrentEnvironment->getEnvironmentSelection() != ENV_NONE) + { + // Note: This looks suspicious. Shouldn't we assign whole day if mCurrentEnvironment has whole day? + // and then add water/sky on top + // This looks like it will result in water using single keyframe instead of whole day if day is present + // when setting static sky without static water + environment->setWater(mCurrentEnvironment->getWater()); + environment->setFlags(DayInstance::NO_ANIMATE_WATER); + } + else + { + // Environment is not properly initialized yet, but we should have environment by this point + DayInstance::ptr_t substitute = getEnvironmentInstance(ENV_PARCEL, true); + if (!substitute || !substitute->getWater()) + { + substitute = getEnvironmentInstance(ENV_REGION, true); + } + if (!substitute || !substitute->getWater()) + { + substitute = getEnvironmentInstance(ENV_DEFAULT, true); + } + + if (substitute && substitute->getWater()) + { + environment->setWater(substitute->getWater()); + environment->setFlags(DayInstance::NO_ANIMATE_WATER); + } + else + { + LL_WARNS("ENVIRONMENT") << "Failed to assign substitute water/sky, environment is not properly initialized" << LL_ENDL; + } + } + } + + if (reset_probes) + { // the sky changed in a way that merits a reset of reflection probes + gPipeline.mReflectionMapManager.reset(); + } + + if (!mSignalEnvChanged.empty()) + mSignalEnvChanged(env, env_version); +} + +void LLEnvironment::setEnvironment(LLEnvironment::EnvSelection_t env, const LLSettingsBase::ptr_t &settings, S32 env_version) +{ + DayInstance::ptr_t environment = getEnvironmentInstance(env); + + if (env == ENV_DEFAULT) + { + LL_WARNS("ENVIRONMENT") << "Attempt to set default environment. Not allowed." << LL_ENDL; + return; + } + + if (!settings) + { + clearEnvironment(env); + return; + } + + if (settings->getSettingsType() == "daycycle") + { + LLSettingsDay::Seconds daylength(LLSettingsDay::DEFAULT_DAYLENGTH); + LLSettingsDay::Seconds dayoffset(LLSettingsDay::DEFAULT_DAYOFFSET); + if (environment) + { + daylength = environment->getDayLength(); + dayoffset = environment->getDayOffset(); + } + setEnvironment(env, std::static_pointer_cast(settings), daylength, dayoffset); + } + else if (settings->getSettingsType() == "sky") + { + fixedEnvironment_t fixedenv(std::static_pointer_cast(settings), LLSettingsWater::ptr_t()); + setEnvironment(env, fixedenv); + } + else if (settings->getSettingsType() == "water") + { + fixedEnvironment_t fixedenv(LLSettingsSky::ptr_t(), std::static_pointer_cast(settings)); + setEnvironment(env, fixedenv); + } +} + +void LLEnvironment::setEnvironment(EnvSelection_t env, const LLUUID &assetId, S32 env_version) +{ + setEnvironment(env, assetId, TRANSITION_DEFAULT, env_version); +} + +void LLEnvironment::setEnvironment(EnvSelection_t env, + const LLUUID &assetId, + LLSettingsBase::Seconds transition, + S32 env_version) +{ + LLSettingsVOBase::getSettingsAsset(assetId, + [this, env, env_version, transition](LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status, LLExtStat) + { + onSetEnvAssetLoaded(env, asset_id, settings, transition, status, env_version); + }); +} + +void LLEnvironment::onSetEnvAssetLoaded(EnvSelection_t env, + LLUUID asset_id, + LLSettingsBase::ptr_t settings, + LLSettingsBase::Seconds transition, + S32 status, + S32 env_version) +{ + if (!settings || status) + { + LLSD args; + args["NAME"] = asset_id.asString(); + LLNotificationsUtil::add("FailedToFindSettings", args); + LL_DEBUGS("ENVIRONMENT") << "Failed to find settings for " << env_selection_to_string(env) << ", asset_id: " << asset_id << LL_ENDL; + return; + } + LL_DEBUGS("ENVIRONMENT") << "Loaded asset: " << asset_id << LL_ENDL; + + setEnvironment(env, settings); + updateEnvironment(transition); +} + +void LLEnvironment::clearEnvironment(LLEnvironment::EnvSelection_t env) +{ + if ((env < ENV_EDIT) || (env >= ENV_DEFAULT)) + { + LL_WARNS("ENVIRONMENT") << "Attempt to change invalid environment selection." << LL_ENDL; + return; + } + + LL_DEBUGS("ENVIRONMENT") << "Cleaning environment " << env_selection_to_string(env) << LL_ENDL; + + mEnvironments[env].reset(); + + if (!mSignalEnvChanged.empty()) + mSignalEnvChanged(env, VERSION_CLEANUP); +} + +void LLEnvironment::logEnvironment(EnvSelection_t env, const LLSettingsBase::ptr_t &settings, S32 env_version) +{ + LL_DEBUGS("ENVIRONMENT") << "Setting Day environment " << env_selection_to_string(env) << " with version(update type): " << env_version << LL_NEWLINE; + // code between LL_DEBUGS and LL_ENDL won't execute unless log is enabled + if (settings) + { + LLUUID asset_id = settings->getAssetId(); + if (asset_id.notNull()) + { + LL_CONT << "Asset id: " << asset_id << LL_NEWLINE; + } + + LLUUID id = settings->getId(); // Not in use? + if (id.notNull()) + { + LL_CONT << "Settings id: " << id << LL_NEWLINE; + } + + LL_CONT << "Name: " << settings->getName() << LL_NEWLINE + << "Type: " << settings->getSettingsType() << LL_NEWLINE + << "Flags: " << settings->getFlags(); // Not in use? + } + else + { + LL_CONT << "Empty settings!"; + } + LL_CONT << LL_ENDL; +} + +LLSettingsDay::ptr_t LLEnvironment::getEnvironmentDay(LLEnvironment::EnvSelection_t env) +{ + if ((env < ENV_EDIT) || (env > ENV_DEFAULT)) + { + LL_WARNS("ENVIRONMENT") << "Attempt to retrieve invalid environment selection (" << env_selection_to_string(env) << ")." << LL_ENDL; + return LLSettingsDay::ptr_t(); + } + + DayInstance::ptr_t environment = getEnvironmentInstance(env); + + if (environment) + return environment->getDayCycle(); + + return LLSettingsDay::ptr_t(); +} + +LLSettingsDay::Seconds LLEnvironment::getEnvironmentDayLength(EnvSelection_t env) +{ + if ((env < ENV_EDIT) || (env > ENV_DEFAULT)) + { + LL_WARNS("ENVIRONMENT") << "Attempt to retrieve invalid environment selection (" << env_selection_to_string(env) << ")." << LL_ENDL; + return LLSettingsDay::Seconds(0); + } + + DayInstance::ptr_t environment = getEnvironmentInstance(env); + + if (environment) + return environment->getDayLength(); + + return LLSettingsDay::Seconds(0); +} + +LLSettingsDay::Seconds LLEnvironment::getEnvironmentDayOffset(EnvSelection_t env) +{ + if ((env < ENV_EDIT) || (env > ENV_DEFAULT)) + { + LL_WARNS("ENVIRONMENT") << "Attempt to retrieve invalid environment selection (" << env_selection_to_string(env) << ")." << LL_ENDL; + return LLSettingsDay::Seconds(0); + } + + DayInstance::ptr_t environment = getEnvironmentInstance(env); + if (environment) + return environment->getDayOffset(); + + return LLSettingsDay::Seconds(0); +} + + +LLEnvironment::fixedEnvironment_t LLEnvironment::getEnvironmentFixed(LLEnvironment::EnvSelection_t env, bool resolve) +{ + if ((env == ENV_CURRENT) || resolve) + { + fixedEnvironment_t fixed; + for (S32 idx = ((resolve) ? env : mSelectedEnvironment); idx < ENV_END; ++idx) + { + if (fixed.first && fixed.second) + break; + + if (idx == ENV_EDIT) + continue; // skip the edit environment. + + DayInstance::ptr_t environment = getEnvironmentInstance(static_cast(idx)); + if (environment) + { + if (!fixed.first) + fixed.first = environment->getSky(); + if (!fixed.second) + fixed.second = environment->getWater(); + } + } + + if (!fixed.first || !fixed.second) + LL_WARNS("ENVIRONMENT") << "Can not construct complete fixed environment. Missing Sky and/or Water." << LL_ENDL; + + return fixed; + } + + if ((env < ENV_EDIT) || (env > ENV_DEFAULT)) + { + LL_WARNS("ENVIRONMENT") << "Attempt to retrieve invalid environment selection (" << env_selection_to_string(env) << ")." << LL_ENDL; + return fixedEnvironment_t(); + } + + DayInstance::ptr_t environment = getEnvironmentInstance(env); + + if (environment) + return fixedEnvironment_t(environment->getSky(), environment->getWater()); + + return fixedEnvironment_t(); +} + +LLEnvironment::DayInstance::ptr_t LLEnvironment::getSelectedEnvironmentInstance() +{ + for (S32 idx = mSelectedEnvironment; idx < ENV_DEFAULT; ++idx) + { + if (mEnvironments[idx]) + return mEnvironments[idx]; + } + + return mEnvironments[ENV_DEFAULT]; +} + +LLEnvironment::DayInstance::ptr_t LLEnvironment::getSharedEnvironmentInstance() +{ + for (S32 idx = ENV_PARCEL; idx < ENV_DEFAULT; ++idx) + { + if (mEnvironments[idx]) + return mEnvironments[idx]; + } + + return mEnvironments[ENV_DEFAULT]; +} + +void LLEnvironment::updateEnvironment(LLSettingsBase::Seconds transition, bool forced) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT; + DayInstance::ptr_t pinstance = getSelectedEnvironmentInstance(); + + if ((mCurrentEnvironment != pinstance) || forced) + { + if (transition != TRANSITION_INSTANT) + { + DayInstance::ptr_t trans = std::make_shared( + mCurrentEnvironment->getSky(), mCurrentEnvironment->getWater(), pinstance, transition); + + trans->animate(); + + mCurrentEnvironment = trans; + } + else + { + mCurrentEnvironment = pinstance; + } + + updateSettingsUniforms(); + } +} + +LLVector4 LLEnvironment::toCFR(const LLVector3 vec) const +{ + LLVector4 vec_cfr(vec.mV[1], vec.mV[0], vec.mV[2], 0.0f); + return vec_cfr; +} + +LLVector4 LLEnvironment::toLightNorm(const LLVector3 vec) const +{ + LLVector4 vec_ogl(vec.mV[1], vec.mV[2], vec.mV[0], 0.0f); + return vec_ogl; +} + +LLVector3 LLEnvironment::getLightDirection() const +{ + LLSettingsSky::ptr_t psky = mCurrentEnvironment->getSky(); + if (!psky) + { + return LLVector3(0, 0, 1); + } + return psky->getLightDirection(); +} + +LLVector3 LLEnvironment::getSunDirection() const +{ + LLSettingsSky::ptr_t psky = mCurrentEnvironment->getSky(); + if (!psky) + { + return LLVector3(0, 0, 1); + } + return psky->getSunDirection(); +} + +LLVector3 LLEnvironment::getMoonDirection() const +{ + LLSettingsSky::ptr_t psky = mCurrentEnvironment->getSky(); + if (!psky) + { + return LLVector3(0, 0, -1); + } + return psky->getMoonDirection(); +} + +LLVector4 LLEnvironment::getLightDirectionCFR() const +{ + LLVector3 light_direction = getLightDirection(); + LLVector4 light_direction_cfr = toCFR(light_direction); + return light_direction_cfr; +} + +LLVector4 LLEnvironment::getSunDirectionCFR() const +{ + LLVector3 light_direction = getSunDirection(); + LLVector4 light_direction_cfr = toCFR(light_direction); + return light_direction_cfr; +} + +LLVector4 LLEnvironment::getMoonDirectionCFR() const +{ + LLVector3 light_direction = getMoonDirection(); + LLVector4 light_direction_cfr = toCFR(light_direction); + return light_direction_cfr; +} + +LLVector4 LLEnvironment::getClampedLightNorm() const +{ + LLVector3 light_direction = getLightDirection(); + if (light_direction.mV[2] < -0.1f) + { + light_direction.mV[2] = -0.1f; + } + return toLightNorm(light_direction); +} + +LLVector4 LLEnvironment::getClampedSunNorm() const +{ + LLVector3 light_direction = getSunDirection(); + if (light_direction.mV[2] < -0.1f) + { + light_direction.mV[2] = -0.1f; + } + return toLightNorm(light_direction); +} + +LLVector4 LLEnvironment::getClampedMoonNorm() const +{ + LLVector3 light_direction = getMoonDirection(); + if (light_direction.mV[2] < -0.1f) + { + light_direction.mV[2] = -0.1f; + } + return toLightNorm(light_direction); +} + +LLVector4 LLEnvironment::getRotatedLightNorm() const +{ + LLVector3 light_direction = getLightDirection(); + light_direction *= LLQuaternion(-mLastCamYaw, LLVector3(0.f, 1.f, 0.f)); + return toLightNorm(light_direction); +} + +extern bool gCubeSnapshot; + +//------------------------------------------------------------------------- +void LLEnvironment::update(const LLViewerCamera * cam) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT; //LL_RECORD_BLOCK_TIME(FTM_ENVIRONMENT_UPDATE); + //F32Seconds now(LLDate::now().secondsSinceEpoch()); + if (!gCubeSnapshot) + { + static LLFrameTimer timer; + + F32Seconds delta(timer.getElapsedTimeAndResetF32()); + + { + DayInstance::ptr_t keeper = mCurrentEnvironment; + // make sure the current environment does not go away until applyTimeDelta is done. + mCurrentEnvironment->applyTimeDelta(delta); + + } + // update clouds, sun, and general + updateCloudScroll(); + + // cache this for use in rotating the rotated light vec for shader param updates later... + mLastCamYaw = cam->getYaw() + SUN_DELTA_YAW; + } + + updateSettingsUniforms(); + + // *TODO: potential optimization - this block may only need to be + // executed some of the time. For example for water shaders only. + { + LLViewerShaderMgr::shader_iter shaders_iter, end_shaders; + end_shaders = LLViewerShaderMgr::instance()->endShaders(); + for (shaders_iter = LLViewerShaderMgr::instance()->beginShaders(); shaders_iter != end_shaders; ++shaders_iter) + { + if ((shaders_iter->mProgramObject != 0) + && (gPipeline.canUseWindLightShaders() + || shaders_iter->mShaderGroup == LLGLSLShader::SG_WATER)) + { + shaders_iter->mUniformsDirty = true; + } + } + } +} + +void LLEnvironment::updateCloudScroll() +{ + // This is a function of the environment rather than the sky, since it should + // persist through sky transitions. + static LLTimer s_cloud_timer; + + F64 delta_t = s_cloud_timer.getElapsedTimeAndResetF64(); + + if (mCurrentEnvironment->getSky() && !mCloudScrollPaused) + { + LLVector2 rate = mCurrentEnvironment->getSky()->getCloudScrollRate(); + if (rate.isExactlyZero()) + { + mCloudScrollDelta.setZero(); + } + else + { + LLVector2 cloud_delta = static_cast(delta_t) * (mCurrentEnvironment->getSky()->getCloudScrollRate()) / 100.0; + mCloudScrollDelta += cloud_delta; + } + } + +} + +// static +void LLEnvironment::updateGLVariablesForSettings(LLShaderUniforms* uniforms, const LLSettingsBase::ptr_t &psetting) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_SHADER; + + for (int i = 0; i < LLGLSLShader::SG_COUNT; ++i) + { + uniforms[i].clear(); + } + + LLShaderUniforms* shader = &uniforms[LLGLSLShader::SG_ANY]; + //_WARNS("RIDER") << "----------------------------------------------------------------" << LL_ENDL; + LLSettingsBase::parammapping_t params = psetting->getParameterMap(); + for (auto &it: params) + { + LLSD value; + // legacy first since it contains ambient color and we prioritize value from legacy, see getAmbientColor() + if (psetting->mSettings.has(LLSettingsSky::SETTING_LEGACY_HAZE) && psetting->mSettings[LLSettingsSky::SETTING_LEGACY_HAZE].has(it.first)) + { + value = psetting->mSettings[LLSettingsSky::SETTING_LEGACY_HAZE][it.first]; + } + else if (psetting->mSettings.has(it.first)) + { + value = psetting->mSettings[it.first]; + } + else + { + // We need to reset shaders, use defaults + value = it.second.getDefaultValue(); + } + + LLSD::Type setting_type = value.type(); + stop_glerror(); + switch (setting_type) + { + case LLSD::TypeInteger: + shader->uniform1i(it.second.getShaderKey(), value.asInteger()); + //_WARNS("RIDER") << "pushing '" << (*it).first << "' as " << value << LL_ENDL; + break; + case LLSD::TypeReal: + shader->uniform1f(it.second.getShaderKey(), value.asReal()); + //_WARNS("RIDER") << "pushing '" << (*it).first << "' as " << value << LL_ENDL; + break; + + case LLSD::TypeBoolean: + shader->uniform1i(it.second.getShaderKey(), value.asBoolean() ? 1 : 0); + //_WARNS("RIDER") << "pushing '" << (*it).first << "' as " << value << LL_ENDL; + break; + + case LLSD::TypeArray: + { + LLVector4 vect4(value); + + if (gCubeSnapshot && !gPipeline.mReflectionMapManager.isRadiancePass()) + { // maximize and remove tinting if this is an irradiance map render pass and the parameter feeds into the sky background color + auto max_vec = [](LLVector4 col) + { + LLColor3 color(col); + F32 h, s, l; + color.calcHSL(&h, &s, &l); + + col.mV[0] = col.mV[1] = col.mV[2] = l; + return col; + }; + + switch (it.second.getShaderKey()) + { + case LLShaderMgr::BLUE_HORIZON: + case LLShaderMgr::BLUE_DENSITY: + vect4 = max_vec(vect4); + break; + } + } + + //_WARNS("RIDER") << "pushing '" << (*it).first << "' as " << vect4 << LL_ENDL; + shader->uniform3fv(it.second.getShaderKey(), LLVector3(vect4.mV) ); + break; + } + + // case LLSD::TypeMap: + // case LLSD::TypeString: + // case LLSD::TypeUUID: + // case LLSD::TypeURI: + // case LLSD::TypeBinary: + // case LLSD::TypeDate: + default: + break; + } + } + //_WARNS("RIDER") << "----------------------------------------------------------------" << LL_ENDL; + + psetting->applySpecial(uniforms); +} + +void LLEnvironment::updateShaderUniforms(LLGLSLShader* shader) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_SHADER; + + // apply uniforms that should be applied to all shaders + mSkyUniforms[LLGLSLShader::SG_ANY].apply(shader); + mWaterUniforms[LLGLSLShader::SG_ANY].apply(shader); + + // apply uniforms specific to the given shader's shader group + auto group = shader->mShaderGroup; + mSkyUniforms[group].apply(shader); + mWaterUniforms[group].apply(shader); +} + +void LLEnvironment::updateSettingsUniforms() +{ + if (mCurrentEnvironment->getWater()) + { + updateGLVariablesForSettings(mWaterUniforms, mCurrentEnvironment->getWater()); + } + else + { + LL_WARNS("ENVIRONMENT") << "Failed to update GL variable for water settings, environment is not properly set" << LL_ENDL; + } + if (mCurrentEnvironment->getSky()) + { + updateGLVariablesForSettings(mSkyUniforms, mCurrentEnvironment->getSky()); + } + else + { + LL_WARNS("ENVIRONMENT") << "Failed to update GL variable for sky settings, environment is not properly set" << LL_ENDL; + } +} + +void LLEnvironment::recordEnvironment(S32 parcel_id, LLEnvironment::EnvironmentInfo::ptr_t envinfo, LLSettingsBase::Seconds transition) +{ + if (!gAgent.getRegion()) + { + return; + } + // mRegionId id can be null, no specification as to why and if it's valid so check valid ids only + if (gAgent.getRegion()->getRegionID() != envinfo->mRegionId && envinfo->mRegionId.notNull()) + { + LL_INFOS("ENVIRONMENT") << "Requested environmend region id: " << envinfo->mRegionId << " agent is on: " << gAgent.getRegion()->getRegionID() << LL_ENDL; + return; + } + + if (envinfo->mParcelId == INVALID_PARCEL_ID) + { + // the returned info applies to an entire region. + if (!envinfo->mDayCycle) + { + clearEnvironment(ENV_PARCEL); + setEnvironment(ENV_REGION, LLSettingsDay::GetDefaultAssetId(), TRANSITION_DEFAULT, envinfo->mEnvVersion); + updateEnvironment(); + } + else if (envinfo->mDayCycle->isTrackEmpty(LLSettingsDay::TRACK_WATER) + || envinfo->mDayCycle->isTrackEmpty(LLSettingsDay::TRACK_GROUND_LEVEL)) + { + LL_WARNS("ENVIRONMENT") << "Invalid day cycle for region" << LL_ENDL; + clearEnvironment(ENV_PARCEL); + setEnvironment(ENV_REGION, LLSettingsDay::GetDefaultAssetId(), TRANSITION_DEFAULT, envinfo->mEnvVersion); + updateEnvironment(); + } + else + { + mTrackAltitudes = envinfo->mAltitudes; + // update track selection based on new altitudes + mCurrentTrack = calculateSkyTrackForAltitude(gAgent.getPositionAgent().mV[VZ]); + + setEnvironment(ENV_REGION, envinfo->mDayCycle, envinfo->mDayLength, envinfo->mDayOffset, envinfo->mEnvVersion); + } + + LL_DEBUGS("ENVIRONMENT") << "Altitudes set to {" << mTrackAltitudes[0] << ", "<< mTrackAltitudes[1] << ", " << mTrackAltitudes[2] << ", " << mTrackAltitudes[3] << LL_ENDL; + } + else + { + LLParcel *parcel = LLViewerParcelMgr::instance().getAgentParcel(); + LL_DEBUGS("ENVIRONMENT") << "Have parcel environment #" << envinfo->mParcelId << LL_ENDL; + if (parcel && (parcel->getLocalID() != parcel_id)) + { + LL_DEBUGS("ENVIRONMENT") << "Requested parcel #" << parcel_id << " agent is on " << parcel->getLocalID() << LL_ENDL; + return; + } + + if (!envinfo->mDayCycle) + { + LL_DEBUGS("ENVIRONMENT") << "Clearing environment on parcel #" << parcel_id << LL_ENDL; + clearEnvironment(ENV_PARCEL); + } + else if (envinfo->mDayCycle->isTrackEmpty(LLSettingsDay::TRACK_WATER) + || envinfo->mDayCycle->isTrackEmpty(LLSettingsDay::TRACK_GROUND_LEVEL)) + { + LL_WARNS("ENVIRONMENT") << "Invalid day cycle for parcel #" << parcel_id << LL_ENDL; + clearEnvironment(ENV_PARCEL); + } + else + { + setEnvironment(ENV_PARCEL, envinfo->mDayCycle, envinfo->mDayLength, envinfo->mDayOffset, envinfo->mEnvVersion); + } + } + + updateEnvironment(transition); +} + +void LLEnvironment::adjustRegionOffset(F32 adjust) +{ + if (isExtendedEnvironmentEnabled()) + { + LL_WARNS("ENVIRONMENT") << "Attempt to adjust region offset on EEP region. Legacy regions only." << LL_ENDL; + } + + if (mEnvironments[ENV_REGION]) + { + F32 day_length = mEnvironments[ENV_REGION]->getDayLength(); + F32 day_offset = mEnvironments[ENV_REGION]->getDayOffset(); + + F32 day_adjustment = adjust * day_length; + + day_offset += day_adjustment; + if (day_offset < 0.0f) + day_offset = day_length + day_offset; + mEnvironments[ENV_REGION]->setDayOffset(LLSettingsBase::Seconds(day_offset)); + } +} + +//========================================================================= +void LLEnvironment::requestRegion(environment_apply_fn cb) +{ + requestParcel(INVALID_PARCEL_ID, cb); +} + +void LLEnvironment::updateRegion(const LLSettingsDay::ptr_t &pday, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) +{ + updateParcel(INVALID_PARCEL_ID, pday, day_length, day_offset, altitudes, cb); +} + +void LLEnvironment::updateRegion(const LLUUID &asset_id, std::string display_name, S32 track_num, S32 day_length, S32 day_offset, U32 flags, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) +{ + if (!isExtendedEnvironmentEnabled()) + { + LL_WARNS("ENVIRONMENT") << "attempt to apply asset id to region not supporting it." << LL_ENDL; + LLNotificationsUtil::add("NoEnvironmentSettings"); + return; + } + + updateParcel(INVALID_PARCEL_ID, asset_id, display_name, track_num, day_length, day_offset, flags, altitudes, cb); +} + +void LLEnvironment::updateRegion(const LLSettingsSky::ptr_t &psky, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) +{ + updateParcel(INVALID_PARCEL_ID, psky, day_length, day_offset, altitudes, cb); +} + +void LLEnvironment::updateRegion(const LLSettingsWater::ptr_t &pwater, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) +{ + updateParcel(INVALID_PARCEL_ID, pwater, day_length, day_offset, altitudes, cb); +} + + +void LLEnvironment::resetRegion(environment_apply_fn cb) +{ + resetParcel(INVALID_PARCEL_ID, cb); +} + +void LLEnvironment::requestParcel(S32 parcel_id, environment_apply_fn cb) +{ + if (!isExtendedEnvironmentEnabled()) + { /*TODO: When EEP is live on the entire grid, this can go away. */ + if (parcel_id == INVALID_PARCEL_ID) + { + if (!cb) + { + LLSettingsBase::Seconds transition = LLViewerParcelMgr::getInstance()->getTeleportInProgress() ? TRANSITION_FAST : TRANSITION_DEFAULT; + cb = [this, transition](S32 pid, EnvironmentInfo::ptr_t envinfo) + { + clearEnvironment(ENV_PARCEL); + recordEnvironment(pid, envinfo, transition); + }; + } + + LLEnvironmentRequest::initiate(cb); + } + else if (cb) + cb(parcel_id, EnvironmentInfo::ptr_t()); + return; + } + + if (!cb) + { + LLSettingsBase::Seconds transition = LLViewerParcelMgr::getInstance()->getTeleportInProgress() ? TRANSITION_FAST : TRANSITION_DEFAULT; + cb = [this, transition](S32 pid, EnvironmentInfo::ptr_t envinfo) { recordEnvironment(pid, envinfo, transition); }; + } + + std::string coroname = + LLCoros::instance().launch("LLEnvironment::coroRequestEnvironment", + [this, parcel_id, cb]() { coroRequestEnvironment(parcel_id, cb); }); +} + +void LLEnvironment::updateParcel(S32 parcel_id, const LLUUID &asset_id, std::string display_name, S32 track_num, S32 day_length, S32 day_offset, U32 flags, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) +{ + UpdateInfo::ptr_t updates(std::make_shared(asset_id, display_name, day_length, day_offset, altitudes, flags)); + std::string coroname = + LLCoros::instance().launch("LLEnvironment::coroUpdateEnvironment", + [this, parcel_id, track_num, updates, cb]() { coroUpdateEnvironment(parcel_id, track_num, updates, cb); }); +} + +void LLEnvironment::onUpdateParcelAssetLoaded(LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status, S32 parcel_id, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes) +{ + if (status) + { + LL_WARNS("ENVIRONMENT") << "Unable to get settings asset with id " << asset_id << "!" << LL_ENDL; + LLNotificationsUtil::add("FailedToLoadSettingsApply"); + return; + } + + LLSettingsDay::ptr_t pday; + + if (settings->getSettingsType() == "daycycle") + pday = std::static_pointer_cast(settings); + else + { + pday = createDayCycleFromEnvironment( (parcel_id == INVALID_PARCEL_ID) ? ENV_REGION : ENV_PARCEL, settings); + } + + if (!pday) + { + LL_WARNS("ENVIRONMENT") << "Unable to construct day around " << asset_id << "!" << LL_ENDL; + LLNotificationsUtil::add("FailedToBuildSettingsDay"); + return; + } + + updateParcel(parcel_id, pday, day_length, day_offset, altitudes); +} + +void LLEnvironment::updateParcel(S32 parcel_id, const LLSettingsSky::ptr_t &psky, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) +{ + LLSettingsDay::ptr_t pday = createDayCycleFromEnvironment((parcel_id == INVALID_PARCEL_ID) ? ENV_REGION : ENV_PARCEL, psky); + pday->setFlag(psky->getFlags()); + updateParcel(parcel_id, pday, day_length, day_offset, altitudes, cb); +} + +void LLEnvironment::updateParcel(S32 parcel_id, const LLSettingsWater::ptr_t &pwater, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) +{ + LLSettingsDay::ptr_t pday = createDayCycleFromEnvironment((parcel_id == INVALID_PARCEL_ID) ? ENV_REGION : ENV_PARCEL, pwater); + pday->setFlag(pwater->getFlags()); + updateParcel(parcel_id, pday, day_length, day_offset, altitudes, cb); +} + +void LLEnvironment::updateParcel(S32 parcel_id, const LLSettingsDay::ptr_t &pday, S32 track_num, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) +{ + UpdateInfo::ptr_t updates(std::make_shared(pday, day_length, day_offset, altitudes)); + + std::string coroname = + LLCoros::instance().launch("LLEnvironment::coroUpdateEnvironment", + [this, parcel_id, track_num, updates, cb]() { coroUpdateEnvironment(parcel_id, track_num, updates, cb); }); +} + +void LLEnvironment::updateParcel(S32 parcel_id, const LLSettingsDay::ptr_t &pday, S32 day_length, S32 day_offset, LLEnvironment::altitudes_vect_t altitudes, environment_apply_fn cb) +{ + updateParcel(parcel_id, pday, NO_TRACK, day_length, day_offset, altitudes, cb); +} + + + +void LLEnvironment::resetParcel(S32 parcel_id, environment_apply_fn cb) +{ + std::string coroname = + LLCoros::instance().launch("LLEnvironment::coroResetEnvironment", + [this, parcel_id, cb]() { coroResetEnvironment(parcel_id, NO_TRACK, cb); }); +} + +void LLEnvironment::coroRequestEnvironment(S32 parcel_id, LLEnvironment::environment_apply_fn apply) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ResetEnvironment", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + std::string url = gAgent.getRegionCapability("ExtEnvironment"); + if (url.empty()) + return; + + LL_DEBUGS("ENVIRONMENT") << "Requesting for parcel_id=" << parcel_id << LL_ENDL; + + if (parcel_id != INVALID_PARCEL_ID) + { + std::stringstream query; + + query << "?parcelid=" << parcel_id; + url += query.str(); + } + + LLSD result = httpAdapter->getAndSuspend(httpRequest, url); + // results that come back may contain the new settings + + LLSD httpResults = result["http_result"]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + if (!status) + { + LL_WARNS("ENVIRONMENT") << "Couldn't retrieve environment settings for " << ((parcel_id == INVALID_PARCEL_ID) ? ("region!") : ("parcel!")) << LL_ENDL; + } + else if (LLApp::isExiting() || gDisconnected) + { + return; + } + else + { + LLSD environment = result[KEY_ENVIRONMENT]; + if (environment.isDefined() && apply) + { + EnvironmentInfo::ptr_t envinfo = LLEnvironment::EnvironmentInfo::extract(environment); + apply(parcel_id, envinfo); + } + } + +} + +void LLEnvironment::coroUpdateEnvironment(S32 parcel_id, S32 track_no, UpdateInfo::ptr_t updates, environment_apply_fn apply) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ResetEnvironment", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + std::string url = gAgent.getRegionCapability("ExtEnvironment"); + if (url.empty()) + return; + + LLSD body(LLSD::emptyMap()); + body[KEY_ENVIRONMENT] = LLSD::emptyMap(); + + if (track_no == NO_TRACK) + { // day length and offset are only applicable if we are addressing the entire day cycle. + if (updates->mDayLength > 0) + body[KEY_ENVIRONMENT][KEY_DAYLENGTH] = updates->mDayLength; + if (updates->mDayOffset > 0) + body[KEY_ENVIRONMENT][KEY_DAYOFFSET] = updates->mDayOffset; + + if ((parcel_id == INVALID_PARCEL_ID) && (updates->mAltitudes.size() == 3)) + { // only test for altitude changes if we are changing the region. + body[KEY_ENVIRONMENT][KEY_TRACKALTS] = LLSD::emptyArray(); + for (S32 i = 0; i < 3; ++i) + { + body[KEY_ENVIRONMENT][KEY_TRACKALTS][i] = updates->mAltitudes[i]; + } + } + } + + if (updates->mDayp) + body[KEY_ENVIRONMENT][KEY_DAYCYCLE] = updates->mDayp->getSettings(); + else if (!updates->mSettingsAsset.isNull()) + { + body[KEY_ENVIRONMENT][KEY_DAYASSET] = updates->mSettingsAsset; + if (!updates->mDayName.empty()) + body[KEY_ENVIRONMENT][KEY_DAYNAME] = updates->mDayName; + } + + body[KEY_ENVIRONMENT][KEY_FLAGS] = LLSD::Integer(updates->mFlags); + //_WARNS("ENVIRONMENT") << "Body = " << body << LL_ENDL; + + if ((parcel_id != INVALID_PARCEL_ID) || (track_no != NO_TRACK)) + { + std::stringstream query; + query << "?"; + + if (parcel_id != INVALID_PARCEL_ID) + { + query << "parcelid=" << parcel_id; + + if (track_no != NO_TRACK) + query << "&"; + } + if (track_no != NO_TRACK) + { + query << "trackno=" << track_no; + } + url += query.str(); + } + + LLSD result = httpAdapter->putAndSuspend(httpRequest, url, body); + // results that come back may contain the new settings + + LLSD notify; + + LLSD httpResults = result["http_result"]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if ((!status) || !result["success"].asBoolean()) + { + LL_WARNS("ENVIRONMENT") << "Couldn't update Windlight settings for " << ((parcel_id == INVALID_PARCEL_ID) ? ("region!") : ("parcel!")) << LL_ENDL; + + notify = LLSD::emptyMap(); + std::string reason = result["message"].asString(); + if (reason.empty()) + { + notify["FAIL_REASON"] = status.toString(); + } + else + { + notify["FAIL_REASON"] = reason; + } + } + else if (LLApp::isExiting()) + { + return; + } + else + { + LLSD environment = result[KEY_ENVIRONMENT]; + if (environment.isDefined() && apply) + { + EnvironmentInfo::ptr_t envinfo = LLEnvironment::EnvironmentInfo::extract(environment); + apply(parcel_id, envinfo); + } + } + + if (!notify.isUndefined()) + { + LLNotificationsUtil::add("WLRegionApplyFail", notify); + //LLEnvManagerNew::instance().onRegionSettingsApplyResponse(false); + } +} + +void LLEnvironment::coroResetEnvironment(S32 parcel_id, S32 track_no, environment_apply_fn apply) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ResetEnvironment", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + std::string url = gAgent.getRegionCapability("ExtEnvironment"); + if (url.empty()) + return; + + if ((parcel_id != INVALID_PARCEL_ID) || (track_no != NO_TRACK)) + { + std::stringstream query; + query << "?"; + + if (parcel_id != INVALID_PARCEL_ID) + { + query << "parcelid=" << parcel_id; + + if (track_no != NO_TRACK) + query << "&"; + } + if (track_no != NO_TRACK) + { + query << "trackno=" << track_no; + } + url += query.str(); + } + + LLSD result = httpAdapter->deleteAndSuspend(httpRequest, url); + // results that come back may contain the new settings + + LLSD notify; + + LLSD httpResults = result["http_result"]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if ((!status) || !result["success"].asBoolean()) + { + LL_WARNS("ENVIRONMENT") << "Couldn't reset Windlight settings in " << ((parcel_id == INVALID_PARCEL_ID) ? ("region!") : ("parcel!")) << LL_ENDL; + + notify = LLSD::emptyMap(); + std::string reason = result["message"].asString(); + if (reason.empty()) + { + notify["FAIL_REASON"] = status.toString(); + } + else + { + notify["FAIL_REASON"] = reason; + } + } + else if (LLApp::isExiting()) + { + return; + } + else + { + LLSD environment = result[KEY_ENVIRONMENT]; + if (environment.isDefined() && apply) + { + EnvironmentInfo::ptr_t envinfo = LLEnvironment::EnvironmentInfo::extract(environment); + apply(parcel_id, envinfo); + } + } + + if (!notify.isUndefined()) + { + LLNotificationsUtil::add("WLRegionApplyFail", notify); + //LLEnvManagerNew::instance().onRegionSettingsApplyResponse(false); + } + +} + + +//========================================================================= + +LLEnvironment::EnvironmentInfo::EnvironmentInfo(): + mParcelId(INVALID_PARCEL_ID), + mRegionId(), + mDayLength(0), + mDayOffset(0), + mDayHash(0), + mDayCycle(), + mAltitudes({ { 0.0, 0.0, 0.0, 0.0 } }), + mIsDefault(false), + mIsLegacy(false), + mDayCycleName(), + mNameList(), + mEnvVersion(INVALID_PARCEL_ENVIRONMENT_VERSION) +{ +} + +LLEnvironment::EnvironmentInfo::ptr_t LLEnvironment::EnvironmentInfo::extract(LLSD environment) +{ + ptr_t pinfo = std::make_shared(); + + pinfo->mIsDefault = environment.has(KEY_ISDEFAULT) ? environment[KEY_ISDEFAULT].asBoolean() : true; + pinfo->mParcelId = environment.has(KEY_PARCELID) ? environment[KEY_PARCELID].asInteger() : INVALID_PARCEL_ID; + pinfo->mRegionId = environment.has(KEY_REGIONID) ? environment[KEY_REGIONID].asUUID() : LLUUID::null; + pinfo->mIsLegacy = false; + + if (environment.has(KEY_TRACKALTS)) + { + for (int idx = 0; idx < 3; idx++) + { + pinfo->mAltitudes[idx+1] = environment[KEY_TRACKALTS][idx].asReal(); + } + pinfo->mAltitudes[0] = 0; + } + + if (environment.has(KEY_DAYCYCLE)) + { + pinfo->mDayCycle = LLSettingsVODay::buildFromEnvironmentMessage(environment[KEY_DAYCYCLE]); + pinfo->mDayLength = LLSettingsDay::Seconds(environment.has(KEY_DAYLENGTH) ? environment[KEY_DAYLENGTH].asInteger() : -1); + pinfo->mDayOffset = LLSettingsDay::Seconds(environment.has(KEY_DAYOFFSET) ? environment[KEY_DAYOFFSET].asInteger() : -1); + pinfo->mDayHash = environment.has(KEY_DAYHASH) ? environment[KEY_DAYHASH].asInteger() : 0; + } + else + { + pinfo->mDayLength = LLEnvironment::instance().getEnvironmentDayLength(ENV_REGION); + pinfo->mDayOffset = LLEnvironment::instance().getEnvironmentDayOffset(ENV_REGION); + } + + if (environment.has(KEY_DAYASSET)) + { + pinfo->mAssetId = environment[KEY_DAYASSET].asUUID(); + } + + if (environment.has(KEY_DAYNAMES)) + { + LLSD daynames = environment[KEY_DAYNAMES]; + if (daynames.isArray()) + { + pinfo->mDayCycleName.clear(); + for (S32 index = 0; index < pinfo->mNameList.size(); ++index) + { + pinfo->mNameList[index] = daynames[index].asString(); + } + } + else if (daynames.isString()) + { + for (std::string &name: pinfo->mNameList) + { + name.clear(); + } + + pinfo->mDayCycleName = daynames.asString(); + } + } + else if (pinfo->mDayCycle) + { + pinfo->mDayCycleName = pinfo->mDayCycle->getName(); + } + + + if (environment.has(KEY_ENVVERSION)) + { + LLSD version = environment[KEY_ENVVERSION]; + pinfo->mEnvVersion = version.asInteger(); + } + else + { + // can be used for region, but versions should be same + pinfo->mEnvVersion = pinfo->mIsDefault ? UNSET_PARCEL_ENVIRONMENT_VERSION : INVALID_PARCEL_ENVIRONMENT_VERSION; + } + + return pinfo; +} + + +LLEnvironment::EnvironmentInfo::ptr_t LLEnvironment::EnvironmentInfo::extractLegacy(LLSD legacy) +{ + if (!legacy.isArray() || !legacy[0].has("regionID")) + { + LL_WARNS("ENVIRONMENT") << "Invalid legacy settings for environment: " << legacy << LL_ENDL; + return ptr_t(); + } + + ptr_t pinfo = std::make_shared(); + + pinfo->mIsDefault = false; + pinfo->mParcelId = INVALID_PARCEL_ID; + pinfo->mRegionId = legacy[0]["regionID"].asUUID(); + pinfo->mIsLegacy = true; + + pinfo->mDayLength = LLSettingsDay::DEFAULT_DAYLENGTH; + pinfo->mDayOffset = LLSettingsDay::DEFAULT_DAYOFFSET; + pinfo->mDayCycle = LLSettingsVODay::buildFromLegacyMessage(pinfo->mRegionId, legacy[1], legacy[2], legacy[3]); + if (pinfo->mDayCycle) + pinfo->mDayHash = pinfo->mDayCycle->getHash(); + + pinfo->mAltitudes[0] = 0; + pinfo->mAltitudes[2] = 10001; + pinfo->mAltitudes[3] = 10002; + pinfo->mAltitudes[4] = 10003; + + return pinfo; +} + +//========================================================================= +LLSettingsWater::ptr_t LLEnvironment::createWaterFromLegacyPreset(const std::string filename, LLSD &messages) +{ + std::string name(gDirUtilp->getBaseFileName(filename, true)); + std::string path(gDirUtilp->getDirName(filename)); + + LLSettingsWater::ptr_t water = LLSettingsVOWater::buildFromLegacyPresetFile(name, path, messages); + + if (!water) + { + messages["NAME"] = name; + messages["FILE"] = filename; + } + return water; +} + +LLSettingsSky::ptr_t LLEnvironment::createSkyFromLegacyPreset(const std::string filename, LLSD &messages) +{ + std::string name(gDirUtilp->getBaseFileName(filename, true)); + std::string path(gDirUtilp->getDirName(filename)); + + LLSettingsSky::ptr_t sky = LLSettingsVOSky::buildFromLegacyPresetFile(name, path, messages); + if (!sky) + { + messages["NAME"] = name; + messages["FILE"] = filename; + } + return sky; +} + +LLSettingsDay::ptr_t LLEnvironment::createDayCycleFromLegacyPreset(const std::string filename, LLSD &messages) +{ + std::string name(gDirUtilp->getBaseFileName(filename, true)); + std::string path(gDirUtilp->getDirName(filename)); + + LLSettingsDay::ptr_t day = LLSettingsVODay::buildFromLegacyPresetFile(name, path, messages); + if (!day) + { + messages["NAME"] = name; + messages["FILE"] = filename; + } + return day; +} + +LLSettingsDay::ptr_t LLEnvironment::createDayCycleFromEnvironment(EnvSelection_t env, LLSettingsBase::ptr_t settings) +{ + std::string type(settings->getSettingsType()); + + if (type == "daycycle") + return std::static_pointer_cast(settings); + + if ((env != ENV_PARCEL) && (env != ENV_REGION)) + { + LL_WARNS("ENVIRONMENT") << "May only create from parcel or region environment." << LL_ENDL; + return LLSettingsDay::ptr_t(); + } + + LLSettingsDay::ptr_t day = this->getEnvironmentDay(env); + if (!day && (env == ENV_PARCEL)) + { + day = this->getEnvironmentDay(ENV_REGION); + } + + if (!day) + { + LL_WARNS("ENVIRONMENT") << "Could not retrieve existing day settings." << LL_ENDL; + return LLSettingsDay::ptr_t(); + } + + day = day->buildClone(); + + if (type == "sky") + { + for (S32 idx = 1; idx < LLSettingsDay::TRACK_MAX; ++idx) + day->clearCycleTrack(idx); + day->setSettingsAtKeyframe(settings, 0.0f, 1); + } + else if (type == "water") + { + day->clearCycleTrack(LLSettingsDay::TRACK_WATER); + day->setSettingsAtKeyframe(settings, 0.0f, LLSettingsDay::TRACK_WATER); + } + + return day; +} + +void LLEnvironment::onAgentPositionHasChanged(const LLVector3 &localpos) +{ + S32 trackno = calculateSkyTrackForAltitude(localpos.mV[VZ]); + if (trackno == mCurrentTrack) + return; + + mCurrentTrack = trackno; + + LLViewerRegion* cur_region = gAgent.getRegion(); + if (!cur_region || !cur_region->capabilitiesReceived()) + { + // Environment not ready, environment will be updated later, don't cause 'blend' yet. + // But keep mCurrentTrack updated in case we won't get new altitudes for some reason + return; + } + + for (S32 env = ENV_LOCAL; env < ENV_DEFAULT; ++env) + { + if (mEnvironments[env]) + mEnvironments[env]->setSkyTrack(mCurrentTrack); + } +} + +S32 LLEnvironment::calculateSkyTrackForAltitude(F64 altitude) +{ + auto it = std::find_if_not(mTrackAltitudes.begin(), mTrackAltitudes.end(), [altitude](F32 test) { return altitude > test; }); + + if (it == mTrackAltitudes.begin()) + return 1; + else if (it == mTrackAltitudes.end()) + return 4; + + return std::min(static_cast(std::distance(mTrackAltitudes.begin(), it)), 4); +} + +//------------------------------------------------------------------------- +void LLEnvironment::handleEnvironmentPush(LLSD &message) +{ + // Log the experience message + LLExperienceLog::instance().handleExperienceMessage(message); + + std::string action = message[KEY_ACTION].asString(); + LLUUID experience_id = message[KEY_EXPERIENCEID].asUUID(); + LLSD action_data = message[KEY_ACTIONDATA]; + F32 transition_time = action_data[KEY_TRANSITIONTIME].asReal(); + + //TODO: Check here that the viewer thinks the experience is still valid. + + + if (action == ACTION_CLEARENVIRONMENT) + { + handleEnvironmentPushClear(experience_id, action_data, transition_time); + } + else if (action == ACTION_PUSHFULLENVIRONMENT) + { + handleEnvironmentPushFull(experience_id, action_data, transition_time); + } + else if (action == ACTION_PUSHPARTIALENVIRONMENT) + { + handleEnvironmentPushPartial(experience_id, action_data, transition_time); + } + else + { + LL_WARNS("ENVIRONMENT", "GENERICMESSAGES") << "Unknown environment push action '" << action << "'" << LL_ENDL; + } +} + +void LLEnvironment::handleEnvironmentPushClear(LLUUID experience_id, LLSD &message, F32 transition) +{ + clearExperienceEnvironment(experience_id, LLSettingsBase::Seconds(transition)); +} + +void LLEnvironment::handleEnvironmentPushFull(LLUUID experience_id, LLSD &message, F32 transition) +{ + LLUUID asset_id(message[KEY_ASSETID].asUUID()); + + setExperienceEnvironment(experience_id, asset_id, LLSettingsBase::Seconds(transition)); +} + +void LLEnvironment::handleEnvironmentPushPartial(LLUUID experience_id, LLSD &message, F32 transition) +{ + LLSD settings(message["settings"]); + + if (settings.isUndefined()) + return; + + setExperienceEnvironment(experience_id, settings, LLSettingsBase::Seconds(transition)); +} + +void LLEnvironment::clearExperienceEnvironment(LLUUID experience_id, LLSettingsBase::Seconds transition_time) +{ + DayInjection::ptr_t injection = std::dynamic_pointer_cast(getEnvironmentInstance(ENV_PUSH)); + if (injection) + { + injection->clearInjections(experience_id, transition_time); + } + +} + +void LLEnvironment::setSharedEnvironment() +{ + clearEnvironment(LLEnvironment::ENV_LOCAL); + setSelectedEnvironment(LLEnvironment::ENV_LOCAL); + updateEnvironment(); +} + +void LLEnvironment::setExperienceEnvironment(LLUUID experience_id, LLUUID asset_id, F32 transition_time) +{ + LLSettingsVOBase::getSettingsAsset(asset_id, + [this, experience_id, transition_time](LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status, LLExtStat) + { + onSetExperienceEnvAssetLoaded(experience_id, settings, transition_time, status); + }); + + +} + +void LLEnvironment::onSetExperienceEnvAssetLoaded(LLUUID experience_id, LLSettingsBase::ptr_t settings, F32 transition_time, S32 status) +{ + DayInjection::ptr_t environment = std::dynamic_pointer_cast(getEnvironmentInstance(ENV_PUSH)); + bool updateenvironment(false); + + if (!settings || status) + { + LLSD args; + args["NAME"] = experience_id.asString(); + LLNotificationsUtil::add("FailedToFindSettings", args); + return; + } + + if (!environment) + { + environment = std::dynamic_pointer_cast(getEnvironmentInstance(ENV_PUSH, true)); + updateenvironment = true; + } + + if (settings->getSettingsType() == "daycycle") + { + environment->setInjectedDay(std::static_pointer_cast(settings), experience_id, LLSettingsBase::Seconds(transition_time)); + } + else if (settings->getSettingsType() == "sky") + { + environment->setInjectedSky(std::static_pointer_cast(settings), experience_id, LLSettingsBase::Seconds(transition_time)); + } + else if (settings->getSettingsType() == "water") + { + environment->setInjectedWater(std::static_pointer_cast(settings), experience_id, LLSettingsBase::Seconds(transition_time)); + } + + if (updateenvironment) + updateEnvironment(TRANSITION_INSTANT, true); +} + + +void LLEnvironment::setExperienceEnvironment(LLUUID experience_id, LLSD data, F32 transition_time) +{ + LLSD sky(data["sky"]); + LLSD water(data["water"]); + + if (sky.isUndefined() && water.isUndefined()) + { + clearExperienceEnvironment(experience_id, LLSettingsBase::Seconds(transition_time)); + return; + } + + DayInjection::ptr_t environment = std::dynamic_pointer_cast(getEnvironmentInstance(ENV_PUSH)); + bool updateenvironment(false); + + if (!environment) + { + environment = std::dynamic_pointer_cast(getEnvironmentInstance(ENV_PUSH, true)); + updateenvironment = true; + } + + if (!sky.isUndefined()) + { + environment->injectSkySettings(sky, experience_id, LLSettingsBase::Seconds(transition_time)); + } + + if (!water.isUndefined()) + { + environment->injectWaterSettings(water, experience_id, LLSettingsBase::Seconds(transition_time)); + } + + if (updateenvironment) + updateEnvironment(TRANSITION_INSTANT, true); + +} + +void LLEnvironment::listenExperiencePump(const LLSD &message) +{ + LLUUID experience_id = message["experience"]; + LLSD data = message[experience_id.asString()]; + std::string permission(data["permission"].asString()); + + if ((permission == "Forget") || (permission == "Block")) + { + clearExperienceEnvironment(experience_id, (permission == "Block") ? TRANSITION_INSTANT : TRANSITION_FAST); + } +} + +//========================================================================= +LLEnvironment::DayInstance::DayInstance(EnvSelection_t env) : + mDayCycle(), + mSky(), + mWater(), + mDayLength(LLSettingsDay::DEFAULT_DAYLENGTH), + mDayOffset(LLSettingsDay::DEFAULT_DAYOFFSET), + mBlenderSky(), + mBlenderWater(), + mInitialized(false), + mSkyTrack(1), + mEnv(env), + mAnimateFlags(0) +{ } + + +LLEnvironment::DayInstance::ptr_t LLEnvironment::DayInstance::clone() const +{ + ptr_t environment = std::make_shared(mEnv); + + environment->mDayCycle = mDayCycle; + environment->mSky = mSky; + environment->mWater = mWater; + environment->mDayLength = mDayLength; + environment->mDayOffset = mDayOffset; + environment->mBlenderSky = mBlenderSky; + environment->mBlenderWater = mBlenderWater; + environment->mInitialized = mInitialized; + environment->mSkyTrack = mSkyTrack; + environment->mAnimateFlags = mAnimateFlags; + + return environment; +} + +bool LLEnvironment::DayInstance::applyTimeDelta(const LLSettingsBase::Seconds& delta) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT; + ptr_t keeper(shared_from_this()); // makes sure that this does not go away while it is being worked on. + + bool changed(false); + if (!mInitialized) + initialize(); + + if (mBlenderSky) + changed |= mBlenderSky->applyTimeDelta(delta); + if (mBlenderWater) + changed |= mBlenderWater->applyTimeDelta(delta); + return changed; +} + +void LLEnvironment::DayInstance::setDay(const LLSettingsDay::ptr_t &pday, LLSettingsDay::Seconds daylength, LLSettingsDay::Seconds dayoffset) +{ + mInitialized = false; + + mAnimateFlags = 0; + + mDayCycle = pday; + mDayLength = daylength; + mDayOffset = dayoffset; + + mBlenderSky.reset(); + mBlenderWater.reset(); + + mSky = LLSettingsVOSky::buildDefaultSky(); + mWater = LLSettingsVOWater::buildDefaultWater(); + + animate(); +} + + +bool LLEnvironment::DayInstance::setSky(const LLSettingsSky::ptr_t &psky) +{ + mInitialized = false; + + bool changed = psky == nullptr || mSky == nullptr || mSky->getHash() != psky->getHash(); + + bool different_sky = mSky != psky; + + mSky = psky; + mSky->mReplaced |= different_sky; + mSky->update(); + mBlenderSky.reset(); + + if (gAtmosphere) + { + AtmosphericModelSettings settings; + LLEnvironment::getAtmosphericModelSettings(settings, psky); + gAtmosphere->configureAtmosphericModel(settings); + } + + return changed; +} + +void LLEnvironment::DayInstance::setWater(const LLSettingsWater::ptr_t &pwater) +{ + mInitialized = false; + + bool different_water = mWater != pwater; + mWater = pwater; + mWater->mReplaced |= different_water; + mWater->update(); + mBlenderWater.reset(); +} + +void LLEnvironment::DayInstance::initialize() +{ + mInitialized = true; + + if (!mWater) + mWater = LLSettingsVOWater::buildDefaultWater(); + if (!mSky) + mSky = LLSettingsVOSky::buildDefaultSky(); +} + +void LLEnvironment::DayInstance::clear() +{ + mDayCycle.reset(); + mSky.reset(); + mWater.reset(); + mDayLength = LLSettingsDay::DEFAULT_DAYLENGTH; + mDayOffset = LLSettingsDay::DEFAULT_DAYOFFSET; + mBlenderSky.reset(); + mBlenderWater.reset(); + mSkyTrack = 1; +} + +void LLEnvironment::DayInstance::setSkyTrack(S32 trackno) +{ + mSkyTrack = trackno; + if (mBlenderSky) + { + mBlenderSky->switchTrack(trackno, 0.0); + } +} + +void LLEnvironment::DayInstance::setBlenders(const LLSettingsBlender::ptr_t &skyblend, const LLSettingsBlender::ptr_t &waterblend) +{ + mBlenderSky = skyblend; + mBlenderWater = waterblend; +} + +LLSettingsBase::TrackPosition LLEnvironment::DayInstance::getProgress() const +{ + LLSettingsBase::Seconds now(LLDate::now().secondsSinceEpoch()); + now += mDayOffset; + + if ((mDayLength <= 0) || !mDayCycle) + return -1.0f; // no actual day cycle. + + return convert_time_to_position(now, mDayLength); +} + +LLSettingsBase::TrackPosition LLEnvironment::DayInstance::secondsToKeyframe(LLSettingsDay::Seconds seconds) +{ + return convert_time_to_position(seconds, mDayLength); +} + +void LLEnvironment::DayInstance::animate() +{ + LLSettingsBase::Seconds now(LLDate::now().secondsSinceEpoch()); + + now += mDayOffset; + + if (!mDayCycle) + return; + + if (!(mAnimateFlags & NO_ANIMATE_WATER)) + { + LLSettingsDay::CycleTrack_t &wtrack = mDayCycle->getCycleTrack(0); + + if (wtrack.empty()) + { + mWater.reset(); + mBlenderWater.reset(); + } + else + { + mWater = LLSettingsVOWater::buildDefaultWater(); + mBlenderWater = std::make_shared(mWater, mDayCycle, 0, + mDayLength, mDayOffset, DEFAULT_UPDATE_THRESHOLD); + } + } + + if (!(mAnimateFlags & NO_ANIMATE_SKY)) + { + // sky, initialize to track 1 + LLSettingsDay::CycleTrack_t &track = mDayCycle->getCycleTrack(1); + + if (track.empty()) + { + mSky.reset(); + mBlenderSky.reset(); + } + else + { + mSky = LLSettingsVOSky::buildDefaultSky(); + mBlenderSky = std::make_shared(mSky, mDayCycle, 1, + mDayLength, mDayOffset, DEFAULT_UPDATE_THRESHOLD); + mBlenderSky->switchTrack(mSkyTrack, 0.0); + } + } +} + +//------------------------------------------------------------------------- +LLEnvironment::DayTransition::DayTransition(const LLSettingsSky::ptr_t &skystart, + const LLSettingsWater::ptr_t &waterstart, LLEnvironment::DayInstance::ptr_t &end, LLSettingsDay::Seconds time) : + DayInstance(ENV_NONE), + mStartSky(skystart), + mStartWater(waterstart), + mNextInstance(end), + mTransitionTime(time) +{ + +} + +bool LLEnvironment::DayTransition::applyTimeDelta(const LLSettingsBase::Seconds& delta) +{ + bool changed(false); + + changed = mNextInstance->applyTimeDelta(delta); + changed |= DayInstance::applyTimeDelta(delta); + return changed; +} + +void LLEnvironment::DayTransition::animate() +{ + mNextInstance->animate(); + + mWater = mStartWater->buildClone(); + mBlenderWater = std::make_shared(mWater, mStartWater, mNextInstance->getWater(), mTransitionTime); + mBlenderWater->setOnFinished( + [this](LLSettingsBlender::ptr_t blender) { + mBlenderWater.reset(); + + if (!mBlenderSky && !mBlenderWater) + LLEnvironment::instance().mCurrentEnvironment = mNextInstance; + else + setWater(mNextInstance->getWater()); + }); + + + // pause probe updates and reset reflection maps on sky change + gPipeline.mReflectionMapManager.pause(); + gPipeline.mReflectionMapManager.reset(); + + mSky = mStartSky->buildClone(); + mBlenderSky = std::make_shared(mSky, mStartSky, mNextInstance->getSky(), mTransitionTime); + mBlenderSky->setOnFinished( + [this](LLSettingsBlender::ptr_t blender) { + mBlenderSky.reset(); + + // resume reflection probe updates + gPipeline.mReflectionMapManager.resume(); + + if (!mBlenderSky && !mBlenderWater) + LLEnvironment::instance().mCurrentEnvironment = mNextInstance; + else + setSky(mNextInstance->getSky()); + }); +} + +void LLEnvironment::saveToSettings() +{ + std::string user_dir = gDirUtilp->getLindenUserDir(); + if (user_dir.empty()) + { + // not logged in + return; + } + bool has_data = false; + + if (gSavedSettings.getBOOL("EnvironmentPersistAcrossLogin")) + { + DayInstance::ptr_t environment = getEnvironmentInstance(ENV_LOCAL); + if (environment) + { + // Environment is 'layered'. No data in ENV_LOCAL means we are using parcel/region + // Store local environment for next session + LLSD env_data; + + LLSettingsDay::ptr_t day = environment->getDayCycle(); + if (day) + { + const std::string name = day->getName(); + const LLUUID asset_id = day->getAssetId(); + if (asset_id.notNull()) + { + // just save the id + env_data["day_id"] = asset_id; + env_data["day_length"] = LLSD::Integer(environment->getDayLength()); + env_data["day_offset"] = LLSD::Integer(environment->getDayOffset()); + has_data = true; + } + else if (!name.empty() && name != LLSettingsBase::DEFAULT_SETTINGS_NAME) + { + // This setting was created locally and was not saved + // The only option is to save the whole thing + env_data["day_llsd"] = day->getSettings(); + env_data["day_length"] = LLSD::Integer(environment->getDayLength()); + env_data["day_offset"] = LLSD::Integer(environment->getDayOffset()); + has_data = true; + } + } + + LLSettingsSky::ptr_t sky = environment->getSky(); + if ((environment->getFlags() & DayInstance::NO_ANIMATE_SKY) && sky) + { + const std::string name = sky->getName(); + const LLUUID asset_id = sky->getAssetId(); + if (asset_id.notNull()) + { + // just save the id + env_data["sky_id"] = asset_id; + has_data = true; + } + else if (!name.empty() && name != LLSettingsBase::DEFAULT_SETTINGS_NAME) + { + // This setting was created locally and was not saved + // The only option is to save the whole thing + env_data["sky_llsd"] = sky->getSettings(); + has_data = true; + } + has_data = true; + } + + LLSettingsWater::ptr_t water = environment->getWater(); + if ((environment->getFlags() & DayInstance::NO_ANIMATE_WATER) && water) + { + const std::string name = water->getName(); + const LLUUID asset_id = water->getAssetId(); + if (asset_id.notNull()) + { + // just save the id + env_data["water_id"] = asset_id; + has_data = true; + } + else if (!name.empty() && name != LLSettingsBase::DEFAULT_SETTINGS_NAME) + { + // This setting was created locally and was not saved + // The only option is to save the whole thing + env_data["water_llsd"] = water->getSettings(); + has_data = true; + } + } + + std::string user_filepath = user_dir + gDirUtilp->getDirDelimiter() + LOCAL_ENV_STORAGE_FILE; + llofstream out(user_filepath.c_str(), std::ios_base::out | std::ios_base::binary); + if (out.good()) + { + LLSDSerialize::toBinary(env_data, out); + out.close(); + } + else + { + LL_WARNS("ENVIRONMENT") << "Unable to open " << user_filepath << " for output." << LL_ENDL; + } + } + } + + if (!has_data) + { + LLFile::remove(user_dir + gDirUtilp->getDirDelimiter() + LOCAL_ENV_STORAGE_FILE, ENOENT); + } +} + +void LLEnvironment::loadSkyWaterFromSettings(const LLSD &env_data, bool &valid, bool &assets_present) +{ + if (env_data.has("sky_id")) + { + // causes asset loaded callback and an update + setEnvironment(ENV_LOCAL, env_data["sky_id"].asUUID()); + valid = true; + assets_present = true; + } + else if (env_data.has("sky_llsd")) + { + LLSettingsSky::ptr_t sky = LLSettingsVOSky::buildSky(env_data["sky_llsd"]); + setEnvironment(ENV_LOCAL, sky); + valid = true; + } + + if (env_data.has("water_id")) + { + // causes asset loaded callback and an update + setEnvironment(ENV_LOCAL, env_data["water_id"].asUUID()); + valid = true; + assets_present = true; + } + else if (env_data.has("water_llsd")) + { + LLSettingsWater::ptr_t sky = LLSettingsVOWater::buildWater(env_data["water_llsd"]); + setEnvironment(ENV_LOCAL, sky); + valid = true; + } +} + +bool LLEnvironment::loadFromSettings() +{ + if (!gSavedSettings.getBOOL("EnvironmentPersistAcrossLogin")) + { + return false; + } + + std::string user_path = gDirUtilp->getLindenUserDir(); + if (user_path.empty()) + { + LL_WARNS("ENVIRONMENT") << "Can't load previous environment, Environment was initialized before user logged in" << LL_ENDL; + return false; + } + std::string user_filepath(user_path + gDirUtilp->getDirDelimiter() + LOCAL_ENV_STORAGE_FILE); + if (!gDirUtilp->fileExists(user_filepath)) + { + // No previous environment + return false; + } + + LLSD env_data; + llifstream file(user_filepath.c_str(), std::ios_base::in | std::ios_base::binary); + if (file.is_open()) + { + LLSDSerialize::fromBinary(env_data, file, LLSDSerialize::SIZE_UNLIMITED); + if (env_data.isUndefined()) + { + LL_WARNS("ENVIRONMENT") << "error loading " << user_filepath << LL_ENDL; + return false; + } + else + { + LL_INFOS("ENVIRONMENT") << "Loaded previous session environment from: " << user_filepath << LL_ENDL; + } + file.close(); + } + else + { + LL_INFOS("ENVIRONMENT") << "Unable to open previous session environment file " << user_filepath << LL_ENDL; + } + + if (!env_data.isMap() || (env_data.size() == 0)) + { + LL_DEBUGS("ENVIRONMENT") << "Empty map loaded from: " << user_filepath << LL_ENDL; + return false; + } + + bool valid = false; + bool has_assets = false; + + if (env_data.has("day_id")) + { + LLUUID assetId = env_data["day_id"].asUUID(); + + LLSettingsVOBase::getSettingsAsset(assetId, + [this, env_data](LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status, LLExtStat) + { + // Day should be always applied first, + // otherwise it will override sky or water that was set earlier + // so wait for asset to load before applying sky/water + onSetEnvAssetLoaded(ENV_LOCAL, asset_id, settings, TRANSITION_DEFAULT, status, NO_VERSION); + bool valid = false, has_assets = false; + loadSkyWaterFromSettings(env_data, valid, has_assets); + if (!has_assets && valid) + { + // Settings were loaded from file without having an asset, needs update + // otherwise update will be done by asset callback + updateEnvironment(TRANSITION_DEFAULT, true); + } + }); + // bail early, everything have to be done at callback + return true; + } + else if (env_data.has("day_llsd")) + { + S32 length = env_data["day_length"].asInteger(); + S32 offset = env_data["day_offset"].asInteger(); + LLSettingsDay::ptr_t pday = LLSettingsVODay::buildDay(env_data["day_llsd"]); + setEnvironment(ENV_LOCAL, pday, LLSettingsDay::Seconds(length), LLSettingsDay::Seconds(offset)); + valid = true; + } + + loadSkyWaterFromSettings(env_data, valid, has_assets); + + if (valid && !has_assets) + { + // Settings were loaded from file without having an asset, needs update + // otherwise update will be done by asset callback + updateEnvironment(TRANSITION_DEFAULT, true); + } + return valid; +} + +void LLEnvironment::saveBeaconsState() +{ + if (mEditorCounter == 0) + { + mShowSunBeacon = gSavedSettings.getBOOL("sunbeacon"); + mShowMoonBeacon = gSavedSettings.getBOOL("moonbeacon"); + } + ++mEditorCounter; +} +void LLEnvironment::revertBeaconsState() +{ + --mEditorCounter; + if (mEditorCounter == 0) + { + gSavedSettings.setBOOL("sunbeacon", mShowSunBeacon && gSavedSettings.getBOOL("sunbeacon")); + gSavedSettings.setBOOL("moonbeacon", mShowMoonBeacon && gSavedSettings.getBOOL("moonbeacon")); + } +} + +//========================================================================= +LLTrackBlenderLoopingManual::LLTrackBlenderLoopingManual(const LLSettingsBase::ptr_t &target, const LLSettingsDay::ptr_t &day, S32 trackno) : + LLSettingsBlender(target, LLSettingsBase::ptr_t(), LLSettingsBase::ptr_t()), + mDay(day), + mTrackNo(trackno), + mPosition(0.0) +{ + LLSettingsDay::TrackBound_t initial = getBoundingEntries(mPosition); + + if (initial.first != mEndMarker) + { // No frames in track + mInitial = (*initial.first).second; + mFinal = (*initial.second).second; + + LLSD initSettings = mInitial->getSettings(); + mTarget->replaceSettings(initSettings); + } +} + +LLSettingsBase::BlendFactor LLTrackBlenderLoopingManual::setPosition(const LLSettingsBase::TrackPosition& position) +{ + mPosition = llclamp(position, 0.0f, 1.0f); + + LLSettingsDay::TrackBound_t bounds = getBoundingEntries(mPosition); + + if (bounds.first == mEndMarker) + { // No frames in track. + return 0.0; + } + + mInitial = (*bounds.first).second; + mFinal = (*bounds.second).second; + + F64 spanLength = getSpanLength(bounds); + + F64 spanPos = ((mPosition < (*bounds.first).first) ? (mPosition + 1.0) : mPosition) - (*bounds.first).first; + + if (spanPos > spanLength) + { + // we are clamping position to 0-1 and spanLength is 1 + // so don't account for case of spanPos == spanLength + spanPos = fmod(spanPos, spanLength); + } + + F64 blendf = spanPos / spanLength; + return LLSettingsBlender::setBlendFactor(blendf); +} + +void LLTrackBlenderLoopingManual::switchTrack(S32 trackno, const LLSettingsBase::TrackPosition& position) +{ + mTrackNo = trackno; + + LLSettingsBase::TrackPosition useposition = (position < 0.0) ? mPosition : position; + + setPosition(useposition); +} + +LLSettingsDay::TrackBound_t LLTrackBlenderLoopingManual::getBoundingEntries(F64 position) +{ + LLSettingsDay::CycleTrack_t &wtrack = mDay->getCycleTrack(mTrackNo); + + mEndMarker = wtrack.end(); + + LLSettingsDay::TrackBound_t bounds = get_bounding_entries(wtrack, position); + return bounds; +} + +F64 LLTrackBlenderLoopingManual::getSpanLength(const LLSettingsDay::TrackBound_t &bounds) const +{ + return get_wrapping_distance((*bounds.first).first, (*bounds.second).first); +} + +//========================================================================= +namespace +{ + DayInjection::DayInjection(LLEnvironment::EnvSelection_t env): + LLEnvironment::DayInstance(env), + mBaseDayInstance(), + mInjectedSky(), + mInjectedWater(), + mActiveExperiences(), + mDayExperience(), + mSkyExperience(), + mWaterExperience(), + mEnvChangeConnection(), + mParcelChangeConnection() + { + mInjectedSky = std::make_shared(LLEnvironment::instance().getCurrentSky()); + mInjectedWater = std::make_shared(LLEnvironment::instance().getCurrentWater()); + mBaseDayInstance = LLEnvironment::instance().getSharedEnvironmentInstance(); + mSky = mInjectedSky; + mWater = mInjectedWater; + + mEnvChangeConnection = LLEnvironment::instance().setEnvironmentChanged([this](LLEnvironment::EnvSelection_t env, S32) { onEnvironmentChanged(env); }); + mParcelChangeConnection = gAgent.addParcelChangedCallback([this]() { onParcelChange(); }); + } + + DayInjection::~DayInjection() + { + if (mEnvChangeConnection.connected()) + mEnvChangeConnection.disconnect(); + if (mParcelChangeConnection.connected()) + mParcelChangeConnection.disconnect(); + } + + + bool DayInjection::applyTimeDelta(const LLSettingsBase::Seconds& delta) + { + bool changed(false); + + if (mBaseDayInstance) + changed |= mBaseDayInstance->applyTimeDelta(delta); + mInjectedSky->applyInjections(delta); + mInjectedWater->applyInjections(delta); + changed |= LLEnvironment::DayInstance::applyTimeDelta(delta); + if (changed) + { + mInjectedSky->setDirtyFlag(true); + mInjectedWater->setDirtyFlag(true); + } + mInjectedSky->update(); + mInjectedWater->update(); + + if (!hasInjections()) + { // There are no injections being managed. This should really go away. + LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_PUSH); + LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT); + } + + return changed; + } + + void DayInjection::setBaseDayInstance(const LLEnvironment::DayInstance::ptr_t &baseday) + { + mBaseDayInstance = baseday; + + if (mSkyExperience.isNull()) + mInjectedSky->setSource(mBaseDayInstance->getSky()); + if (mWaterExperience.isNull()) + mInjectedWater->setSource(mBaseDayInstance->getWater()); + } + + + bool DayInjection::hasInjections() const + { + return (!mSkyExperience.isNull() || !mWaterExperience.isNull() || !mDayExperience.isNull() || + mBlenderSky || mBlenderWater || mInjectedSky->hasInjections() || mInjectedWater->hasInjections()); + } + + + void DayInjection::testExperiencesOnParcel(S32 parcel_id) + { + LLCoros::instance().launch("DayInjection::testExperiencesOnParcel", + [this, parcel_id]() { DayInjection::testExperiencesOnParcelCoro(std::static_pointer_cast(this->shared_from_this()), parcel_id); }); + + } + + void DayInjection::setInjectedDay(const LLSettingsDay::ptr_t &pday, LLUUID experience_id, LLSettingsBase::Seconds transition) + { + mSkyExperience = experience_id; + mWaterExperience = experience_id; + mDayExperience = experience_id; + + mBaseDayInstance = mBaseDayInstance->clone(); + mBaseDayInstance->setEnvironmentSelection(LLEnvironment::ENV_NONE); + mBaseDayInstance->setDay(pday, mBaseDayInstance->getDayLength(), mBaseDayInstance->getDayOffset()); + animateSkyChange(mBaseDayInstance->getSky(), transition); + animateWaterChange(mBaseDayInstance->getWater(), transition); + + mActiveExperiences.insert(experience_id); + } + + void DayInjection::setInjectedSky(const LLSettingsSky::ptr_t &psky, LLUUID experience_id, LLSettingsBase::Seconds transition) + { + mSkyExperience = experience_id; + mActiveExperiences.insert(experience_id); + checkExperience(); + animateSkyChange(psky, transition); + } + + void DayInjection::setInjectedWater(const LLSettingsWater::ptr_t &pwater, LLUUID experience_id, LLSettingsBase::Seconds transition) + { + mWaterExperience = experience_id; + mActiveExperiences.insert(experience_id); + checkExperience(); + animateWaterChange(pwater, transition); + } + + void DayInjection::injectSkySettings(LLSD settings, LLUUID experience_id, LLSettingsBase::Seconds transition) + { + mInjectedSky->injectExperienceValues(settings, experience_id, transition); + mActiveExperiences.insert(experience_id); + } + + void DayInjection::injectWaterSettings(LLSD settings, LLUUID experience_id, LLSettingsBase::Seconds transition) + { + mInjectedWater->injectExperienceValues(settings, experience_id, transition); + mActiveExperiences.insert(experience_id); + } + + void DayInjection::clearInjections(LLUUID experience_id, LLSettingsBase::Seconds transition_time) + { + if ((experience_id.isNull() && !mDayExperience.isNull()) || (experience_id == mDayExperience)) + { + mDayExperience.setNull(); + if (mSkyExperience == experience_id) + mSkyExperience.setNull(); + if (mWaterExperience == experience_id) + mWaterExperience.setNull(); + + mBaseDayInstance = LLEnvironment::instance().getSharedEnvironmentInstance(); + + if (mSkyExperience.isNull()) + animateSkyChange(mBaseDayInstance->getSky(), transition_time); + if (mWaterExperience.isNull()) + animateWaterChange(mBaseDayInstance->getWater(), transition_time); + } + + if ((experience_id.isNull() && !mSkyExperience.isNull()) || (experience_id == mSkyExperience)) + { + mSkyExperience.setNull(); + animateSkyChange(mBaseDayInstance->getSky(), transition_time); + } + if ((experience_id.isNull() && !mWaterExperience.isNull()) || (experience_id == mWaterExperience)) + { + mWaterExperience.setNull(); + animateWaterChange(mBaseDayInstance->getWater(), transition_time); + } + + mInjectedSky->removeInjections(experience_id, transition_time); + mInjectedWater->removeInjections(experience_id, transition_time); + + if (experience_id.isNull()) + mActiveExperiences.clear(); + else + mActiveExperiences.erase(experience_id); + + if ((transition_time == LLEnvironment::TRANSITION_INSTANT) && (countExperiencesActive() == 0)) + { // Only do this if instant and there are no other experiences injecting values. + // (otherwise will be handled after transition) + LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_PUSH); + LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT); + } + } + + + void DayInjection::testExperiencesOnParcelCoro(wptr_t that, S32 parcel_id) + { + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("testExperiencesOnParcelCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + std::string url = gAgent.getRegionCapability("ExperienceQuery"); + + if (url.empty()) + { + LL_WARNS("ENVIRONMENT") << "No experience query cap." << LL_ENDL; + return; // no checking in this region. + } + + { + ptr_t thatlock(that); + std::stringstream fullurl; + + if (!thatlock) + return; + + fullurl << url << "?"; + fullurl << "parcelid=" << parcel_id; + + for (auto it = thatlock->mActiveExperiences.begin(); it != thatlock->mActiveExperiences.end(); ++it) + { + if (it != thatlock->mActiveExperiences.begin()) + fullurl << ","; + else + fullurl << "&experiences="; + fullurl << (*it).asString(); + } + url = fullurl.str(); + } + + LLSD result = httpAdapter->getAndSuspend(httpRequest, url); + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS() << "Unable to retrieve experience status for parcel." << LL_ENDL; + return; + } + + { + LLParcel* parcel = LLViewerParcelMgr::instance().getAgentParcel(); + if (!parcel) + return; + + if (parcel_id != parcel->getLocalID()) + { + // Agent no longer on queried parcel. + return; + } + } + + + LLSD experiences = result["experiences"]; + { + ptr_t thatlock(that); + if (!thatlock) + return; + + for (LLSD::map_iterator itr = experiences.beginMap(); itr != experiences.endMap(); ++itr) + { + if (!((*itr).second.asBoolean())) + thatlock->clearInjections(LLUUID((*itr).first), LLEnvironment::TRANSITION_FAST); + + } + } + } + + void DayInjection::animateSkyChange(LLSettingsSky::ptr_t psky, LLSettingsBase::Seconds transition) + { + if (mInjectedSky.get() == psky.get()) + { // An attempt to animate to itself... don't do it. + return; + } + if (transition == LLEnvironment::TRANSITION_INSTANT) + { + mBlenderSky.reset(); + mInjectedSky->setSource(psky); + } + else + { + LLSettingsSky::ptr_t start_sky(mInjectedSky->getSource()->buildClone()); + LLSettingsSky::ptr_t target_sky(start_sky->buildClone()); + mInjectedSky->setSource(target_sky); + + // clear reflection probes and pause updates during sky change + gPipeline.mReflectionMapManager.pause(); + gPipeline.mReflectionMapManager.reset(); + + mBlenderSky = std::make_shared(target_sky, start_sky, psky, transition); + mBlenderSky->setOnFinished( + [this, psky](LLSettingsBlender::ptr_t blender) + { + mBlenderSky.reset(); + mInjectedSky->setSource(psky); + + // resume updating reflection probes when done animating sky + gPipeline.mReflectionMapManager.resume(); + setSky(mInjectedSky); + if (!mBlenderWater && (countExperiencesActive() == 0)) + { + LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_PUSH); + LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT); + } + }); + } + } + + void DayInjection::animateWaterChange(LLSettingsWater::ptr_t pwater, LLSettingsBase::Seconds transition) + { + if (mInjectedWater.get() == pwater.get()) + { // An attempt to animate to itself. Bad idea. + return; + } + if (transition == LLEnvironment::TRANSITION_INSTANT) + { + mBlenderWater.reset(); + mInjectedWater->setSource(pwater); + } + else + { + LLSettingsWater::ptr_t start_Water(mInjectedWater->getSource()->buildClone()); + LLSettingsWater::ptr_t scratch_Water(start_Water->buildClone()); + mInjectedWater->setSource(scratch_Water); + + mBlenderWater = std::make_shared(scratch_Water, start_Water, pwater, transition); + mBlenderWater->setOnFinished( + [this, pwater](LLSettingsBlender::ptr_t blender) + { + mBlenderWater.reset(); + mInjectedWater->setSource(pwater); + setWater(mInjectedWater); + if (!mBlenderSky && (countExperiencesActive() == 0)) + { + LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_PUSH); + LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT); + } + }); + } + } + + void DayInjection::onEnvironmentChanged(LLEnvironment::EnvSelection_t env) + { + if (env >= LLEnvironment::ENV_PARCEL) + { + LLEnvironment::EnvSelection_t base_env(mBaseDayInstance->getEnvironmentSelection()); + LLEnvironment::DayInstance::ptr_t nextbase = LLEnvironment::instance().getSharedEnvironmentInstance(); + + if ((base_env == LLEnvironment::ENV_NONE) || (nextbase == mBaseDayInstance) || + (!mSkyExperience.isNull() && !mWaterExperience.isNull())) + { // base instance completely overridden, or not changed no transition will happen + return; + } + + LL_WARNS("PUSHENV", "ENVIRONMENT") << "Underlying environment has changed (" << env << ")! Base env is type " << base_env << LL_ENDL; + + LLEnvironment::DayInstance::ptr_t trans = std::make_shared(std::static_pointer_cast(shared_from_this()), + mBaseDayInstance->getSky(), mBaseDayInstance->getWater(), nextbase, LLEnvironment::TRANSITION_DEFAULT); + + trans->animate(); + setBaseDayInstance(trans); + } + } + + void DayInjection::onParcelChange() + { + S32 parcel_id(INVALID_PARCEL_ID); + LLParcel* parcel = LLViewerParcelMgr::instance().getAgentParcel(); + + if (!parcel) + return; + + parcel_id = parcel->getLocalID(); + + testExperiencesOnParcel(parcel_id); + } + + void DayInjection::checkExperience() + { + if ((!mDayExperience.isNull()) && (mSkyExperience != mDayExperience) && (mWaterExperience != mDayExperience)) + { // There was a day experience but we've replaced it with a water and a sky experience. + mDayExperience.setNull(); + mBaseDayInstance = LLEnvironment::instance().getSharedEnvironmentInstance(); + } + } + + void DayInjection::animate() + { + + } + + void InjectedTransition::animate() + { + mNextInstance->animate(); + + if (!mInjection->isOverriddenSky()) + { + mSky = mStartSky->buildClone(); + mBlenderSky = std::make_shared(mSky, mStartSky, mNextInstance->getSky(), mTransitionTime); + mBlenderSky->setOnFinished( + [this](LLSettingsBlender::ptr_t blender) { + mBlenderSky.reset(); + + if (!mBlenderSky && !mBlenderSky) + mInjection->setBaseDayInstance(mNextInstance); + else + mInjection->mInjectedSky->setSource(mNextInstance->getSky()); + }); + } + else + { + mSky = mInjection->getSky(); + mBlenderSky.reset(); + } + + if (!mInjection->isOverriddenWater()) + { + mWater = mStartWater->buildClone(); + mBlenderWater = std::make_shared(mWater, mStartWater, mNextInstance->getWater(), mTransitionTime); + mBlenderWater->setOnFinished( + [this](LLSettingsBlender::ptr_t blender) { + mBlenderWater.reset(); + + if (!mBlenderSky && !mBlenderWater) + mInjection->setBaseDayInstance(mNextInstance); + else + mInjection->mInjectedWater->setSource(mNextInstance->getWater()); + }); + } + else + { + mWater = mInjection->getWater(); + mBlenderWater.reset(); + } + + } + +} + diff --git a/indra/newview/lleventnotifier.cpp b/indra/newview/lleventnotifier.cpp index 49ba96781d..25983f4add 100644 --- a/indra/newview/lleventnotifier.cpp +++ b/indra/newview/lleventnotifier.cpp @@ -1,313 +1,313 @@ -/** - * @file lleventnotifier.cpp - * @brief Viewer code for managing event notifications - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lleventnotifier.h" - -#include "llnotificationsutil.h" -#include "message.h" - -#include "llfloaterreg.h" -#include "llfloaterworldmap.h" -#include "llfloaterevent.h" -#include "llagent.h" -#include "llcommandhandler.h" // secondlife:///app/... support -#include "lltrans.h" - -class LLEventHandler : public LLCommandHandler -{ -public: - // requires trusted browser to trigger - LLEventHandler() : LLCommandHandler("event", UNTRUSTED_THROTTLE) { } - bool handle(const LLSD& params, - const LLSD& query_map, - const std::string& grid, - LLMediaCtrl* web) - { - if (params.size() < 2) - { - return false; - } - std::string event_command = params[1].asString(); - S32 event_id = params[0].asInteger(); - if(event_command == "details") - { - LLFloaterEvent* floater = LLFloaterReg::getTypedInstance("event"); - if (floater) - { - floater->setEventID(event_id); - LLFloaterReg::showTypedInstance("event"); - return true; - } - } - else if(event_command == "notify") - { - // we're adding or removing a notification, so grab the date, name and notification bool - if (params.size() < 3) - { - return false; - } - if(params[2].asString() == "enable") - { - gEventNotifier.add(event_id); - // tell the server to modify the database as this was a slurl event notification command - gEventNotifier.serverPushRequest(event_id, true); - - } - else - { - gEventNotifier.remove(event_id); - } - return true; - } - - - return false; - } -}; -LLEventHandler gEventHandler; - - -LLEventNotifier gEventNotifier; - -LLEventNotifier::LLEventNotifier() -{ -} - - -LLEventNotifier::~LLEventNotifier() -{ - en_map::iterator iter; - - for (iter = mEventNotifications.begin(); - iter != mEventNotifications.end(); - iter++) - { - delete iter->second; - } -} - - -void LLEventNotifier::update() -{ - if (mNotificationTimer.getElapsedTimeF32() > 30.f) - { - // Check our notifications again and send out updates - // if they happen. - - F64 alert_time = LLDate::now().secondsSinceEpoch() + 5 * 60; - en_map::iterator iter; - for (iter = mEventNotifications.begin(); - iter != mEventNotifications.end();) - { - LLEventNotification *np = iter->second; - - iter++; - if (np->getEventDateEpoch() < alert_time) - { - LLSD args; - args["NAME"] = np->getEventName(); - - args["DATE"] = np->getEventDateStr(); - LLNotificationsUtil::add("EventNotification", args, LLSD(), - boost::bind(&LLEventNotifier::handleResponse, this, np->getEventID(), _1, _2)); - remove(np->getEventID()); - - } - } - mNotificationTimer.reset(); - } -} - - - -bool LLEventNotifier::handleResponse(U32 eventId, const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - switch (option) - { - case 0: - { - LLFloaterEvent* floater = LLFloaterReg::getTypedInstance("event"); - if (floater) - { - floater->setEventID(eventId); - LLFloaterReg::showTypedInstance("event"); - } - break; - } - case 1: - break; - } - return true; -} - -bool LLEventNotifier::add(U32 eventId, F64 eventEpoch, const std::string& eventDateStr, const std::string &eventName) -{ - LLEventNotification *new_enp = new LLEventNotification(eventId, eventEpoch, eventDateStr, eventName); - - LL_INFOS() << "Add event " << eventName << " id " << eventId << " date " << eventDateStr << LL_ENDL; - if(!new_enp->isValid()) - { - delete new_enp; - return false; - } - - mEventNotifications[new_enp->getEventID()] = new_enp; - return true; - -} - -void LLEventNotifier::add(U32 eventId) -{ - - gMessageSystem->newMessageFast(_PREHASH_EventInfoRequest); - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); - gMessageSystem->nextBlockFast(_PREHASH_EventData); - gMessageSystem->addU32Fast(_PREHASH_EventID, eventId); - gAgent.sendReliableMessage(); - -} - -//static -void LLEventNotifier::processEventInfoReply(LLMessageSystem *msg, void **) -{ - // extract the agent id - LLUUID agent_id; - U32 event_id; - std::string event_name; - std::string eventd_date; - U32 event_time_utc; - - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); - msg->getU32("EventData", "EventID", event_id); - msg->getString("EventData", "Name", event_name); - msg->getString("EventData", "Date", eventd_date); - msg->getU32("EventData", "DateUTC", event_time_utc); - - gEventNotifier.add(event_id, (F64)event_time_utc, eventd_date, event_name); -} - - -void LLEventNotifier::load(const LLSD& event_options) -{ - for(LLSD::array_const_iterator resp_it = event_options.beginArray(), - end = event_options.endArray(); resp_it != end; ++resp_it) - { - LLSD response = *resp_it; - LLDate date; - bool is_iso8601_date = false; - - if (response["event_date"].isDate()) - { - date = response["event_date"].asDate(); - is_iso8601_date = true; - } - else if (date.fromString(response["event_date"].asString())) - { - is_iso8601_date = true; - } - - if (is_iso8601_date) - { - std::string dateStr; - - dateStr = "[" + LLTrans::getString("LTimeYear") + "]-[" - + LLTrans::getString("LTimeMthNum") + "]-[" - + LLTrans::getString("LTimeDay") + "] [" - + LLTrans::getString("LTimeHour") + "]:[" - + LLTrans::getString("LTimeMin") + "]:[" - + LLTrans::getString("LTimeSec") + "]"; - - LLSD substitution; - substitution["datetime"] = date; - LLStringUtil::format(dateStr, substitution); - - add(response["event_id"].asInteger(), response["event_date_ut"], dateStr, response["event_name"].asString()); - } - else - { - add(response["event_id"].asInteger(), response["event_date_ut"], response["event_date"].asString(), response["event_name"].asString()); - } - } -} - - -bool LLEventNotifier::hasNotification(const U32 event_id) -{ - if (mEventNotifications.find(event_id) != mEventNotifications.end()) - { - return true; - } - return false; -} - -void LLEventNotifier::remove(const U32 event_id) -{ - en_map::iterator iter; - iter = mEventNotifications.find(event_id); - if (iter == mEventNotifications.end()) - { - // We don't have a notification for this event, don't bother. - return; - } - - serverPushRequest(event_id, false); - delete iter->second; - mEventNotifications.erase(iter); -} - - -void LLEventNotifier::serverPushRequest(U32 event_id, bool add) -{ - // Push up a message to tell the server we have this notification. - gMessageSystem->newMessage(add?"EventNotificationAddRequest":"EventNotificationRemoveRequest"); - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - gMessageSystem->nextBlock("EventData"); - gMessageSystem->addU32("EventID", event_id); - gAgent.sendReliableMessage(); -} - - -LLEventNotification::LLEventNotification(U32 eventId, F64 eventEpoch, const std::string& eventDateStr, const std::string &eventName) : - mEventID(eventId), - mEventName(eventName), - mEventDateEpoch(eventEpoch), - mEventDateStr(eventDateStr) -{ - -} - - - - - - +/** + * @file lleventnotifier.cpp + * @brief Viewer code for managing event notifications + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lleventnotifier.h" + +#include "llnotificationsutil.h" +#include "message.h" + +#include "llfloaterreg.h" +#include "llfloaterworldmap.h" +#include "llfloaterevent.h" +#include "llagent.h" +#include "llcommandhandler.h" // secondlife:///app/... support +#include "lltrans.h" + +class LLEventHandler : public LLCommandHandler +{ +public: + // requires trusted browser to trigger + LLEventHandler() : LLCommandHandler("event", UNTRUSTED_THROTTLE) { } + bool handle(const LLSD& params, + const LLSD& query_map, + const std::string& grid, + LLMediaCtrl* web) + { + if (params.size() < 2) + { + return false; + } + std::string event_command = params[1].asString(); + S32 event_id = params[0].asInteger(); + if(event_command == "details") + { + LLFloaterEvent* floater = LLFloaterReg::getTypedInstance("event"); + if (floater) + { + floater->setEventID(event_id); + LLFloaterReg::showTypedInstance("event"); + return true; + } + } + else if(event_command == "notify") + { + // we're adding or removing a notification, so grab the date, name and notification bool + if (params.size() < 3) + { + return false; + } + if(params[2].asString() == "enable") + { + gEventNotifier.add(event_id); + // tell the server to modify the database as this was a slurl event notification command + gEventNotifier.serverPushRequest(event_id, true); + + } + else + { + gEventNotifier.remove(event_id); + } + return true; + } + + + return false; + } +}; +LLEventHandler gEventHandler; + + +LLEventNotifier gEventNotifier; + +LLEventNotifier::LLEventNotifier() +{ +} + + +LLEventNotifier::~LLEventNotifier() +{ + en_map::iterator iter; + + for (iter = mEventNotifications.begin(); + iter != mEventNotifications.end(); + iter++) + { + delete iter->second; + } +} + + +void LLEventNotifier::update() +{ + if (mNotificationTimer.getElapsedTimeF32() > 30.f) + { + // Check our notifications again and send out updates + // if they happen. + + F64 alert_time = LLDate::now().secondsSinceEpoch() + 5 * 60; + en_map::iterator iter; + for (iter = mEventNotifications.begin(); + iter != mEventNotifications.end();) + { + LLEventNotification *np = iter->second; + + iter++; + if (np->getEventDateEpoch() < alert_time) + { + LLSD args; + args["NAME"] = np->getEventName(); + + args["DATE"] = np->getEventDateStr(); + LLNotificationsUtil::add("EventNotification", args, LLSD(), + boost::bind(&LLEventNotifier::handleResponse, this, np->getEventID(), _1, _2)); + remove(np->getEventID()); + + } + } + mNotificationTimer.reset(); + } +} + + + +bool LLEventNotifier::handleResponse(U32 eventId, const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + switch (option) + { + case 0: + { + LLFloaterEvent* floater = LLFloaterReg::getTypedInstance("event"); + if (floater) + { + floater->setEventID(eventId); + LLFloaterReg::showTypedInstance("event"); + } + break; + } + case 1: + break; + } + return true; +} + +bool LLEventNotifier::add(U32 eventId, F64 eventEpoch, const std::string& eventDateStr, const std::string &eventName) +{ + LLEventNotification *new_enp = new LLEventNotification(eventId, eventEpoch, eventDateStr, eventName); + + LL_INFOS() << "Add event " << eventName << " id " << eventId << " date " << eventDateStr << LL_ENDL; + if(!new_enp->isValid()) + { + delete new_enp; + return false; + } + + mEventNotifications[new_enp->getEventID()] = new_enp; + return true; + +} + +void LLEventNotifier::add(U32 eventId) +{ + + gMessageSystem->newMessageFast(_PREHASH_EventInfoRequest); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); + gMessageSystem->nextBlockFast(_PREHASH_EventData); + gMessageSystem->addU32Fast(_PREHASH_EventID, eventId); + gAgent.sendReliableMessage(); + +} + +//static +void LLEventNotifier::processEventInfoReply(LLMessageSystem *msg, void **) +{ + // extract the agent id + LLUUID agent_id; + U32 event_id; + std::string event_name; + std::string eventd_date; + U32 event_time_utc; + + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); + msg->getU32("EventData", "EventID", event_id); + msg->getString("EventData", "Name", event_name); + msg->getString("EventData", "Date", eventd_date); + msg->getU32("EventData", "DateUTC", event_time_utc); + + gEventNotifier.add(event_id, (F64)event_time_utc, eventd_date, event_name); +} + + +void LLEventNotifier::load(const LLSD& event_options) +{ + for(LLSD::array_const_iterator resp_it = event_options.beginArray(), + end = event_options.endArray(); resp_it != end; ++resp_it) + { + LLSD response = *resp_it; + LLDate date; + bool is_iso8601_date = false; + + if (response["event_date"].isDate()) + { + date = response["event_date"].asDate(); + is_iso8601_date = true; + } + else if (date.fromString(response["event_date"].asString())) + { + is_iso8601_date = true; + } + + if (is_iso8601_date) + { + std::string dateStr; + + dateStr = "[" + LLTrans::getString("LTimeYear") + "]-[" + + LLTrans::getString("LTimeMthNum") + "]-[" + + LLTrans::getString("LTimeDay") + "] [" + + LLTrans::getString("LTimeHour") + "]:[" + + LLTrans::getString("LTimeMin") + "]:[" + + LLTrans::getString("LTimeSec") + "]"; + + LLSD substitution; + substitution["datetime"] = date; + LLStringUtil::format(dateStr, substitution); + + add(response["event_id"].asInteger(), response["event_date_ut"], dateStr, response["event_name"].asString()); + } + else + { + add(response["event_id"].asInteger(), response["event_date_ut"], response["event_date"].asString(), response["event_name"].asString()); + } + } +} + + +bool LLEventNotifier::hasNotification(const U32 event_id) +{ + if (mEventNotifications.find(event_id) != mEventNotifications.end()) + { + return true; + } + return false; +} + +void LLEventNotifier::remove(const U32 event_id) +{ + en_map::iterator iter; + iter = mEventNotifications.find(event_id); + if (iter == mEventNotifications.end()) + { + // We don't have a notification for this event, don't bother. + return; + } + + serverPushRequest(event_id, false); + delete iter->second; + mEventNotifications.erase(iter); +} + + +void LLEventNotifier::serverPushRequest(U32 event_id, bool add) +{ + // Push up a message to tell the server we have this notification. + gMessageSystem->newMessage(add?"EventNotificationAddRequest":"EventNotificationRemoveRequest"); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + gMessageSystem->nextBlock("EventData"); + gMessageSystem->addU32("EventID", event_id); + gAgent.sendReliableMessage(); +} + + +LLEventNotification::LLEventNotification(U32 eventId, F64 eventEpoch, const std::string& eventDateStr, const std::string &eventName) : + mEventID(eventId), + mEventName(eventName), + mEventDateEpoch(eventEpoch), + mEventDateStr(eventDateStr) +{ + +} + + + + + + diff --git a/indra/newview/lleventnotifier.h b/indra/newview/lleventnotifier.h index ada439b0b3..030c9abb87 100644 --- a/indra/newview/lleventnotifier.h +++ b/indra/newview/lleventnotifier.h @@ -1,87 +1,87 @@ -/** - * @file lleventnotifier.h - * @brief Viewer code for managing event notifications - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLEVENTNOTIFIER_H -#define LL_LLEVENTNOTIFIER_H - -#include "llframetimer.h" -#include "v3dmath.h" - -class LLEventNotification; -class LLMessageSystem; - - -class LLEventNotifier -{ -public: - LLEventNotifier(); - virtual ~LLEventNotifier(); - - void update(); // Notify the user of the event if it's coming up - bool add(U32 eventId, F64 eventEpoch, const std::string& eventDateStr, const std::string &eventName); - void add(U32 eventId); - - - void load(const LLSD& event_options); // In the format that it comes in from login - void remove(U32 event_id); - - bool hasNotification(const U32 event_id); - void serverPushRequest(U32 event_id, bool add); - - typedef std::map en_map; - bool handleResponse(U32 eventId, const LLSD& notification, const LLSD& response); - - static void processEventInfoReply(LLMessageSystem *msg, void **); - -protected: - en_map mEventNotifications; - LLFrameTimer mNotificationTimer; -}; - - -class LLEventNotification -{ -public: - LLEventNotification(U32 eventId, F64 eventEpoch, const std::string& eventDateStr, const std::string &eventName); - - - U32 getEventID() const { return mEventID; } - const std::string &getEventName() const { return mEventName; } - bool isValid() const { return mEventID > 0 && mEventDateEpoch != 0 && mEventName.size() > 0; } - const F64 &getEventDateEpoch() const { return mEventDateEpoch; } - const std::string &getEventDateStr() const { return mEventDateStr; } - - -protected: - U32 mEventID; // EventID for this event - std::string mEventName; - F64 mEventDateEpoch; - std::string mEventDateStr; -}; - -extern LLEventNotifier gEventNotifier; - -#endif //LL_LLEVENTNOTIFIER_H +/** + * @file lleventnotifier.h + * @brief Viewer code for managing event notifications + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLEVENTNOTIFIER_H +#define LL_LLEVENTNOTIFIER_H + +#include "llframetimer.h" +#include "v3dmath.h" + +class LLEventNotification; +class LLMessageSystem; + + +class LLEventNotifier +{ +public: + LLEventNotifier(); + virtual ~LLEventNotifier(); + + void update(); // Notify the user of the event if it's coming up + bool add(U32 eventId, F64 eventEpoch, const std::string& eventDateStr, const std::string &eventName); + void add(U32 eventId); + + + void load(const LLSD& event_options); // In the format that it comes in from login + void remove(U32 event_id); + + bool hasNotification(const U32 event_id); + void serverPushRequest(U32 event_id, bool add); + + typedef std::map en_map; + bool handleResponse(U32 eventId, const LLSD& notification, const LLSD& response); + + static void processEventInfoReply(LLMessageSystem *msg, void **); + +protected: + en_map mEventNotifications; + LLFrameTimer mNotificationTimer; +}; + + +class LLEventNotification +{ +public: + LLEventNotification(U32 eventId, F64 eventEpoch, const std::string& eventDateStr, const std::string &eventName); + + + U32 getEventID() const { return mEventID; } + const std::string &getEventName() const { return mEventName; } + bool isValid() const { return mEventID > 0 && mEventDateEpoch != 0 && mEventName.size() > 0; } + const F64 &getEventDateEpoch() const { return mEventDateEpoch; } + const std::string &getEventDateStr() const { return mEventDateStr; } + + +protected: + U32 mEventID; // EventID for this event + std::string mEventName; + F64 mEventDateEpoch; + std::string mEventDateStr; +}; + +extern LLEventNotifier gEventNotifier; + +#endif //LL_LLEVENTNOTIFIER_H diff --git a/indra/newview/llexpandabletextbox.cpp b/indra/newview/llexpandabletextbox.cpp index dc9374b849..4c105176b6 100644 --- a/indra/newview/llexpandabletextbox.cpp +++ b/indra/newview/llexpandabletextbox.cpp @@ -1,468 +1,468 @@ -/** - * @file llexpandabletextbox.cpp - * @brief LLExpandableTextBox and related class implementations - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llexpandabletextbox.h" - -#include "llscrollcontainer.h" -#include "lltrans.h" -#include "llwindow.h" -#include "llviewerwindow.h" - -static LLDefaultChildRegistry::Register t1("expandable_text"); - -class LLExpanderSegment : public LLTextSegment -{ -public: - LLExpanderSegment(const LLStyleSP& style, S32 start, S32 end, const std::string& more_text, LLTextBase& editor ) - : LLTextSegment(start, end), - mEditor(editor), - mStyle(style), - mExpanderLabel(more_text) - {} - - /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const - { - // more label always spans width of text box - if (num_chars == 0) - { - width = 0; - height = 0; - } - else - { - width = mEditor.getDocumentView()->getRect().getWidth() - mEditor.getHPad(); - height = mStyle->getFont()->getLineHeight(); - } - return true; - } - /*virtual*/ S32 getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const - { - return start_offset; - } - /*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const - { - // require full line to ourselves - if (line_offset == 0) - { - // print all our text - return getEnd() - getStart(); - } - else - { - // wait for next line - return 0; - } - } - /*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) - { - F32 right_x; - mStyle->getFont()->renderUTF8(mExpanderLabel, start, - draw_rect.mRight, draw_rect.mTop, - mStyle->getColor(), - LLFontGL::RIGHT, LLFontGL::TOP, - 0, - mStyle->getShadowType(), - end - start, draw_rect.getWidth(), - &right_x, - mEditor.getUseEllipses(), mEditor.getUseColor()); - return right_x; - } - /*virtual*/ bool canEdit() const { return false; } - // eat handleMouseDown event so we get the mouseup event - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask) { return true; } - /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask) { mEditor.onCommit(); return true; } - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask) - { - LLUI::getInstance()->getWindow()->setCursor(UI_CURSOR_HAND); - return true; - } -private: - LLTextBase& mEditor; - LLStyleSP mStyle; - std::string mExpanderLabel; -}; - -LLExpandableTextBox::LLTextBoxEx::Params::Params() -{ -} - -LLExpandableTextBox::LLTextBoxEx::LLTextBoxEx(const Params& p) -: LLTextEditor(p), - mExpanderLabel(p.label.isProvided() ? p.label : LLTrans::getString("More")), - mExpanderVisible(false) -{ - setIsChrome(true); - setMaxTextLength(p.max_text_length); -} - -void LLExpandableTextBox::LLTextBoxEx::reshape(S32 width, S32 height, bool called_from_parent) -{ - LLTextEditor::reshape(width, height, called_from_parent); -} - -void LLExpandableTextBox::LLTextBoxEx::setText(const LLStringExplicit& text,const LLStyle::Params& input_params) -{ - // LLTextBox::setText will obliterate the expander segment, so make sure - // we generate it again by clearing mExpanderVisible - mExpanderVisible = false; - LLTextEditor::setText(text, input_params); - - hideOrShowExpandTextAsNeeded(); -} - - -void LLExpandableTextBox::LLTextBoxEx::showExpandText() -{ - if (!mExpanderVisible) - { - // make sure we're scrolled to top when collapsing - if (mScroller) - { - mScroller->goToTop(); - } - // get fully visible lines - std::pair visible_lines = getVisibleLines(true); - S32 last_line = visible_lines.second - 1; - - LLStyle::Params expander_style(getStyleParams()); - expander_style.font.style = "UNDERLINE"; - expander_style.color = LLUIColorTable::instance().getColor("HTMLLinkColor"); - LLExpanderSegment* expanderp = new LLExpanderSegment(new LLStyle(expander_style), getLineStart(last_line), getLength() + 1, mExpanderLabel, *this); - insertSegment(expanderp); - mExpanderVisible = true; - } - -} - -//NOTE: obliterates existing styles (including hyperlinks) -void LLExpandableTextBox::LLTextBoxEx::hideExpandText() -{ - if (mExpanderVisible) - { - // this will overwrite the expander segment and all text styling with a single style - LLStyleConstSP sp(new LLStyle(getStyleParams())); - LLNormalTextSegment* segmentp = new LLNormalTextSegment(sp, 0, getLength() + 1, *this); - insertSegment(segmentp); - - mExpanderVisible = false; - } -} - -S32 LLExpandableTextBox::LLTextBoxEx::getVerticalTextDelta() -{ - S32 text_height = getTextPixelHeight(); - S32 textbox_height = getRect().getHeight(); - - return text_height - textbox_height; -} - -S32 LLExpandableTextBox::LLTextBoxEx::getTextPixelHeight() -{ - return getTextBoundingRect().getHeight(); -} - -void LLExpandableTextBox::LLTextBoxEx::hideOrShowExpandTextAsNeeded() -{ - // Restore the text box contents to calculate the text height properly, - // otherwise if a part of the text is hidden under "More" link - // getTextPixelHeight() returns only the height of currently visible text - // including the "More" link. See STORM-250. - hideExpandText(); - - // Show the expander a.k.a. "More" link if we need it, depending on text - // contents height. If not, keep it hidden. - if (getTextPixelHeight() > getRect().getHeight()) - { - showExpandText(); - } -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -LLExpandableTextBox::Params::Params() -: textbox("textbox"), - scroll("scroll"), - max_height("max_height", 0), - bg_visible("bg_visible", false), - expanded_bg_visible("expanded_bg_visible", true), - bg_color("bg_color", LLColor4::black), - expanded_bg_color("expanded_bg_color", LLColor4::black) -{ -} - -LLExpandableTextBox::LLExpandableTextBox(const Params& p) -: LLUICtrl(p), - mMaxHeight(p.max_height), - mBGVisible(p.bg_visible), - mExpandedBGVisible(p.expanded_bg_visible), - mBGColor(p.bg_color), - mExpandedBGColor(p.expanded_bg_color), - mExpanded(false) -{ - LLRect rc = getLocalRect(); - - LLScrollContainer::Params scroll_params = p.scroll; - scroll_params.rect(rc); - mScroll = LLUICtrlFactory::create(scroll_params); - addChild(mScroll); - - LLTextBoxEx::Params textbox_params = p.textbox; - textbox_params.rect(rc); - mTextBox = LLUICtrlFactory::create(textbox_params); - mTextBox->setContentTrusted(false); - mScroll->addChild(mTextBox); - - updateTextBoxRect(); - - mTextBox->setCommitCallback(boost::bind(&LLExpandableTextBox::onExpandClicked, this)); -} - - -LLExpandableTextBox::~LLExpandableTextBox() -{ - gViewerWindow->removePopup(this); -} - -void LLExpandableTextBox::draw() -{ - if(mBGVisible && !mExpanded) - { - gl_rect_2d(getLocalRect(), mBGColor.get(), true); - } - if(mExpandedBGVisible && mExpanded) - { - gl_rect_2d(getLocalRect(), mExpandedBGColor.get(), true); - } - - collapseIfPosChanged(); - - LLUICtrl::draw(); -} - -void LLExpandableTextBox::setContentTrusted(bool trusted_content) -{ - mTextBox->setContentTrusted(trusted_content); -} - -void LLExpandableTextBox::collapseIfPosChanged() -{ - if(mExpanded) - { - LLView* parentp = getParent(); - LLRect parent_rect = parentp->getRect(); - parentp->localRectToOtherView(parent_rect, &parent_rect, getRootView()); - - if(parent_rect.mLeft != mParentRect.mLeft - || parent_rect.mTop != mParentRect.mTop) - { - collapseTextBox(); - } - } -} - -void LLExpandableTextBox::onExpandClicked() -{ - expandTextBox(); -} - -void LLExpandableTextBox::updateTextBoxRect() -{ - LLRect rc = getLocalRect(); - - rc.mLeft += mScroll->getBorderWidth(); - rc.mRight -= mScroll->getBorderWidth(); - rc.mTop -= mScroll->getBorderWidth(); - rc.mBottom += mScroll->getBorderWidth(); - - mTextBox->reshape(rc.getWidth(), rc.getHeight()); - mTextBox->setRect(rc); - // *HACK - // hideExpandText brakes text styles (replaces hyper-links with plain text), see ticket EXT-3290 - // Also text segments are not removed properly. Same issue at expandTextBox(). - // So set text again to make text box re-apply styles and clear segments. - // *TODO Find a solution that does not involve text segment. - mTextBox->setText(mText); -} - -S32 LLExpandableTextBox::recalculateTextDelta(S32 text_delta) -{ - LLRect expanded_rect = getLocalRect(); - LLView* root_view = getRootView(); - LLRect window_rect = root_view->getRect(); - - LLRect expanded_screen_rect; - localRectToOtherView(expanded_rect, &expanded_screen_rect, root_view); - - // don't allow expanded text box bottom go off screen - if(expanded_screen_rect.mBottom - text_delta < window_rect.mBottom) - { - text_delta = expanded_screen_rect.mBottom - window_rect.mBottom; - } - // show scroll bar if max_height is valid - // and expanded size is greater that max_height - else if(mMaxHeight > 0 && expanded_rect.getHeight() + text_delta > mMaxHeight) - { - text_delta = mMaxHeight - expanded_rect.getHeight(); - } - - return text_delta; -} - -void LLExpandableTextBox::expandTextBox() -{ - // hide "more" link, and show full text contents - mTextBox->hideExpandText(); - - // *HACK dz - // hideExpandText brakes text styles (replaces hyper-links with plain text), see ticket EXT-3290 - // Set text again to make text box re-apply styles. - // *TODO Find proper solution to fix this issue. - // Maybe add removeSegment to LLTextBase - mTextBox->setTextBase(mText); - - S32 text_delta = mTextBox->getVerticalTextDelta(); - text_delta += mTextBox->getVPad() * 2; - text_delta += mScroll->getBorderWidth() * 2; - // no need to expand - if(text_delta <= 0) - { - return; - } - - saveCollapsedState(); - - LLRect expanded_rect = getLocalRect(); - LLRect expanded_screen_rect; - - S32 updated_text_delta = recalculateTextDelta(text_delta); - // actual expand - expanded_rect.mBottom -= updated_text_delta; - - LLRect text_box_rect = mTextBox->getRect(); - - // check if we need to show scrollbar - if(text_delta != updated_text_delta) - { - static LLUICachedControl scrollbar_size ("UIScrollbarSize", 0); - - // disable horizontal scrollbar - text_box_rect.mRight -= scrollbar_size; - - // text box size has changed - redo text wrap - // Should be handled automatically in reshape() below. JC - //mTextBox->setWrappedText(mText, text_box_rect.getWidth()); - - // recalculate text delta since text wrap changed text height - text_delta = mTextBox->getVerticalTextDelta() + mTextBox->getVPad() * 2; - } - - // expand text - text_box_rect.mBottom -= text_delta; - mTextBox->reshape(text_box_rect.getWidth(), text_box_rect.getHeight()); - mTextBox->setRect(text_box_rect); - - // expand text box - localRectToOtherView(expanded_rect, &expanded_screen_rect, getParent()); - reshape(expanded_screen_rect.getWidth(), expanded_screen_rect.getHeight(), false); - setRect(expanded_screen_rect); - - setFocus(true); - // this lets us receive top_lost event(needed to collapse text box) - // it also draws text box above all other ui elements - gViewerWindow->addPopup(this); - - mExpanded = true; -} - -void LLExpandableTextBox::collapseTextBox() -{ - if(!mExpanded) - { - return; - } - - mExpanded = false; - - reshape(mCollapsedRect.getWidth(), mCollapsedRect.getHeight(), false); - setRect(mCollapsedRect); - - updateTextBoxRect(); - gViewerWindow->removePopup(this); -} - -void LLExpandableTextBox::onFocusLost() -{ - collapseTextBox(); - - LLUICtrl::onFocusLost(); -} - -void LLExpandableTextBox::onTopLost() -{ - collapseTextBox(); - - LLUICtrl::onTopLost(); -} - -void LLExpandableTextBox::updateTextShape() -{ - llassert(!mExpanded); - updateTextBoxRect(); -} - -void LLExpandableTextBox::reshape(S32 width, S32 height, bool called_from_parent) -{ - mExpanded = false; - LLUICtrl::reshape(width, height, called_from_parent); - updateTextBoxRect(); -} - -void LLExpandableTextBox::setValue(const LLSD& value) -{ - collapseTextBox(); - mText = value.asString(); - mTextBox->setValue(value); -} - -void LLExpandableTextBox::setText(const std::string& str) -{ - collapseTextBox(); - mText = str; - mTextBox->setText(str); -} - -void LLExpandableTextBox::saveCollapsedState() -{ - mCollapsedRect = getRect(); - - mParentRect = getParent()->getRect(); - // convert parent rect to screen coordinates, - // this will allow to track parent's position change - getParent()->localRectToOtherView(mParentRect, &mParentRect, getRootView()); -} +/** + * @file llexpandabletextbox.cpp + * @brief LLExpandableTextBox and related class implementations + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llexpandabletextbox.h" + +#include "llscrollcontainer.h" +#include "lltrans.h" +#include "llwindow.h" +#include "llviewerwindow.h" + +static LLDefaultChildRegistry::Register t1("expandable_text"); + +class LLExpanderSegment : public LLTextSegment +{ +public: + LLExpanderSegment(const LLStyleSP& style, S32 start, S32 end, const std::string& more_text, LLTextBase& editor ) + : LLTextSegment(start, end), + mEditor(editor), + mStyle(style), + mExpanderLabel(more_text) + {} + + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const + { + // more label always spans width of text box + if (num_chars == 0) + { + width = 0; + height = 0; + } + else + { + width = mEditor.getDocumentView()->getRect().getWidth() - mEditor.getHPad(); + height = mStyle->getFont()->getLineHeight(); + } + return true; + } + /*virtual*/ S32 getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const + { + return start_offset; + } + /*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const + { + // require full line to ourselves + if (line_offset == 0) + { + // print all our text + return getEnd() - getStart(); + } + else + { + // wait for next line + return 0; + } + } + /*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) + { + F32 right_x; + mStyle->getFont()->renderUTF8(mExpanderLabel, start, + draw_rect.mRight, draw_rect.mTop, + mStyle->getColor(), + LLFontGL::RIGHT, LLFontGL::TOP, + 0, + mStyle->getShadowType(), + end - start, draw_rect.getWidth(), + &right_x, + mEditor.getUseEllipses(), mEditor.getUseColor()); + return right_x; + } + /*virtual*/ bool canEdit() const { return false; } + // eat handleMouseDown event so we get the mouseup event + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask) { return true; } + /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask) { mEditor.onCommit(); return true; } + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask) + { + LLUI::getInstance()->getWindow()->setCursor(UI_CURSOR_HAND); + return true; + } +private: + LLTextBase& mEditor; + LLStyleSP mStyle; + std::string mExpanderLabel; +}; + +LLExpandableTextBox::LLTextBoxEx::Params::Params() +{ +} + +LLExpandableTextBox::LLTextBoxEx::LLTextBoxEx(const Params& p) +: LLTextEditor(p), + mExpanderLabel(p.label.isProvided() ? p.label : LLTrans::getString("More")), + mExpanderVisible(false) +{ + setIsChrome(true); + setMaxTextLength(p.max_text_length); +} + +void LLExpandableTextBox::LLTextBoxEx::reshape(S32 width, S32 height, bool called_from_parent) +{ + LLTextEditor::reshape(width, height, called_from_parent); +} + +void LLExpandableTextBox::LLTextBoxEx::setText(const LLStringExplicit& text,const LLStyle::Params& input_params) +{ + // LLTextBox::setText will obliterate the expander segment, so make sure + // we generate it again by clearing mExpanderVisible + mExpanderVisible = false; + LLTextEditor::setText(text, input_params); + + hideOrShowExpandTextAsNeeded(); +} + + +void LLExpandableTextBox::LLTextBoxEx::showExpandText() +{ + if (!mExpanderVisible) + { + // make sure we're scrolled to top when collapsing + if (mScroller) + { + mScroller->goToTop(); + } + // get fully visible lines + std::pair visible_lines = getVisibleLines(true); + S32 last_line = visible_lines.second - 1; + + LLStyle::Params expander_style(getStyleParams()); + expander_style.font.style = "UNDERLINE"; + expander_style.color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + LLExpanderSegment* expanderp = new LLExpanderSegment(new LLStyle(expander_style), getLineStart(last_line), getLength() + 1, mExpanderLabel, *this); + insertSegment(expanderp); + mExpanderVisible = true; + } + +} + +//NOTE: obliterates existing styles (including hyperlinks) +void LLExpandableTextBox::LLTextBoxEx::hideExpandText() +{ + if (mExpanderVisible) + { + // this will overwrite the expander segment and all text styling with a single style + LLStyleConstSP sp(new LLStyle(getStyleParams())); + LLNormalTextSegment* segmentp = new LLNormalTextSegment(sp, 0, getLength() + 1, *this); + insertSegment(segmentp); + + mExpanderVisible = false; + } +} + +S32 LLExpandableTextBox::LLTextBoxEx::getVerticalTextDelta() +{ + S32 text_height = getTextPixelHeight(); + S32 textbox_height = getRect().getHeight(); + + return text_height - textbox_height; +} + +S32 LLExpandableTextBox::LLTextBoxEx::getTextPixelHeight() +{ + return getTextBoundingRect().getHeight(); +} + +void LLExpandableTextBox::LLTextBoxEx::hideOrShowExpandTextAsNeeded() +{ + // Restore the text box contents to calculate the text height properly, + // otherwise if a part of the text is hidden under "More" link + // getTextPixelHeight() returns only the height of currently visible text + // including the "More" link. See STORM-250. + hideExpandText(); + + // Show the expander a.k.a. "More" link if we need it, depending on text + // contents height. If not, keep it hidden. + if (getTextPixelHeight() > getRect().getHeight()) + { + showExpandText(); + } +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +LLExpandableTextBox::Params::Params() +: textbox("textbox"), + scroll("scroll"), + max_height("max_height", 0), + bg_visible("bg_visible", false), + expanded_bg_visible("expanded_bg_visible", true), + bg_color("bg_color", LLColor4::black), + expanded_bg_color("expanded_bg_color", LLColor4::black) +{ +} + +LLExpandableTextBox::LLExpandableTextBox(const Params& p) +: LLUICtrl(p), + mMaxHeight(p.max_height), + mBGVisible(p.bg_visible), + mExpandedBGVisible(p.expanded_bg_visible), + mBGColor(p.bg_color), + mExpandedBGColor(p.expanded_bg_color), + mExpanded(false) +{ + LLRect rc = getLocalRect(); + + LLScrollContainer::Params scroll_params = p.scroll; + scroll_params.rect(rc); + mScroll = LLUICtrlFactory::create(scroll_params); + addChild(mScroll); + + LLTextBoxEx::Params textbox_params = p.textbox; + textbox_params.rect(rc); + mTextBox = LLUICtrlFactory::create(textbox_params); + mTextBox->setContentTrusted(false); + mScroll->addChild(mTextBox); + + updateTextBoxRect(); + + mTextBox->setCommitCallback(boost::bind(&LLExpandableTextBox::onExpandClicked, this)); +} + + +LLExpandableTextBox::~LLExpandableTextBox() +{ + gViewerWindow->removePopup(this); +} + +void LLExpandableTextBox::draw() +{ + if(mBGVisible && !mExpanded) + { + gl_rect_2d(getLocalRect(), mBGColor.get(), true); + } + if(mExpandedBGVisible && mExpanded) + { + gl_rect_2d(getLocalRect(), mExpandedBGColor.get(), true); + } + + collapseIfPosChanged(); + + LLUICtrl::draw(); +} + +void LLExpandableTextBox::setContentTrusted(bool trusted_content) +{ + mTextBox->setContentTrusted(trusted_content); +} + +void LLExpandableTextBox::collapseIfPosChanged() +{ + if(mExpanded) + { + LLView* parentp = getParent(); + LLRect parent_rect = parentp->getRect(); + parentp->localRectToOtherView(parent_rect, &parent_rect, getRootView()); + + if(parent_rect.mLeft != mParentRect.mLeft + || parent_rect.mTop != mParentRect.mTop) + { + collapseTextBox(); + } + } +} + +void LLExpandableTextBox::onExpandClicked() +{ + expandTextBox(); +} + +void LLExpandableTextBox::updateTextBoxRect() +{ + LLRect rc = getLocalRect(); + + rc.mLeft += mScroll->getBorderWidth(); + rc.mRight -= mScroll->getBorderWidth(); + rc.mTop -= mScroll->getBorderWidth(); + rc.mBottom += mScroll->getBorderWidth(); + + mTextBox->reshape(rc.getWidth(), rc.getHeight()); + mTextBox->setRect(rc); + // *HACK + // hideExpandText brakes text styles (replaces hyper-links with plain text), see ticket EXT-3290 + // Also text segments are not removed properly. Same issue at expandTextBox(). + // So set text again to make text box re-apply styles and clear segments. + // *TODO Find a solution that does not involve text segment. + mTextBox->setText(mText); +} + +S32 LLExpandableTextBox::recalculateTextDelta(S32 text_delta) +{ + LLRect expanded_rect = getLocalRect(); + LLView* root_view = getRootView(); + LLRect window_rect = root_view->getRect(); + + LLRect expanded_screen_rect; + localRectToOtherView(expanded_rect, &expanded_screen_rect, root_view); + + // don't allow expanded text box bottom go off screen + if(expanded_screen_rect.mBottom - text_delta < window_rect.mBottom) + { + text_delta = expanded_screen_rect.mBottom - window_rect.mBottom; + } + // show scroll bar if max_height is valid + // and expanded size is greater that max_height + else if(mMaxHeight > 0 && expanded_rect.getHeight() + text_delta > mMaxHeight) + { + text_delta = mMaxHeight - expanded_rect.getHeight(); + } + + return text_delta; +} + +void LLExpandableTextBox::expandTextBox() +{ + // hide "more" link, and show full text contents + mTextBox->hideExpandText(); + + // *HACK dz + // hideExpandText brakes text styles (replaces hyper-links with plain text), see ticket EXT-3290 + // Set text again to make text box re-apply styles. + // *TODO Find proper solution to fix this issue. + // Maybe add removeSegment to LLTextBase + mTextBox->setTextBase(mText); + + S32 text_delta = mTextBox->getVerticalTextDelta(); + text_delta += mTextBox->getVPad() * 2; + text_delta += mScroll->getBorderWidth() * 2; + // no need to expand + if(text_delta <= 0) + { + return; + } + + saveCollapsedState(); + + LLRect expanded_rect = getLocalRect(); + LLRect expanded_screen_rect; + + S32 updated_text_delta = recalculateTextDelta(text_delta); + // actual expand + expanded_rect.mBottom -= updated_text_delta; + + LLRect text_box_rect = mTextBox->getRect(); + + // check if we need to show scrollbar + if(text_delta != updated_text_delta) + { + static LLUICachedControl scrollbar_size ("UIScrollbarSize", 0); + + // disable horizontal scrollbar + text_box_rect.mRight -= scrollbar_size; + + // text box size has changed - redo text wrap + // Should be handled automatically in reshape() below. JC + //mTextBox->setWrappedText(mText, text_box_rect.getWidth()); + + // recalculate text delta since text wrap changed text height + text_delta = mTextBox->getVerticalTextDelta() + mTextBox->getVPad() * 2; + } + + // expand text + text_box_rect.mBottom -= text_delta; + mTextBox->reshape(text_box_rect.getWidth(), text_box_rect.getHeight()); + mTextBox->setRect(text_box_rect); + + // expand text box + localRectToOtherView(expanded_rect, &expanded_screen_rect, getParent()); + reshape(expanded_screen_rect.getWidth(), expanded_screen_rect.getHeight(), false); + setRect(expanded_screen_rect); + + setFocus(true); + // this lets us receive top_lost event(needed to collapse text box) + // it also draws text box above all other ui elements + gViewerWindow->addPopup(this); + + mExpanded = true; +} + +void LLExpandableTextBox::collapseTextBox() +{ + if(!mExpanded) + { + return; + } + + mExpanded = false; + + reshape(mCollapsedRect.getWidth(), mCollapsedRect.getHeight(), false); + setRect(mCollapsedRect); + + updateTextBoxRect(); + gViewerWindow->removePopup(this); +} + +void LLExpandableTextBox::onFocusLost() +{ + collapseTextBox(); + + LLUICtrl::onFocusLost(); +} + +void LLExpandableTextBox::onTopLost() +{ + collapseTextBox(); + + LLUICtrl::onTopLost(); +} + +void LLExpandableTextBox::updateTextShape() +{ + llassert(!mExpanded); + updateTextBoxRect(); +} + +void LLExpandableTextBox::reshape(S32 width, S32 height, bool called_from_parent) +{ + mExpanded = false; + LLUICtrl::reshape(width, height, called_from_parent); + updateTextBoxRect(); +} + +void LLExpandableTextBox::setValue(const LLSD& value) +{ + collapseTextBox(); + mText = value.asString(); + mTextBox->setValue(value); +} + +void LLExpandableTextBox::setText(const std::string& str) +{ + collapseTextBox(); + mText = str; + mTextBox->setText(str); +} + +void LLExpandableTextBox::saveCollapsedState() +{ + mCollapsedRect = getRect(); + + mParentRect = getParent()->getRect(); + // convert parent rect to screen coordinates, + // this will allow to track parent's position change + getParent()->localRectToOtherView(mParentRect, &mParentRect, getRootView()); +} diff --git a/indra/newview/llexpandabletextbox.h b/indra/newview/llexpandabletextbox.h index a5ee76a44d..f074b0e6ad 100644 --- a/indra/newview/llexpandabletextbox.h +++ b/indra/newview/llexpandabletextbox.h @@ -1,219 +1,219 @@ -/** - * @file llexpandabletextbox.h - * @brief LLExpandableTextBox and related class definitions - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLEXPANDABLETEXTBOX_H -#define LL_LLEXPANDABLETEXTBOX_H - -#include "lltexteditor.h" -#include "llscrollcontainer.h" - -/** - * LLExpandableTextBox is a text box control that will show "More" link at end of text - * if text doesn't fit into text box. After pressing "More" the text box will expand to show - * all text. If text is still too big, a scroll bar will appear inside expanded text box. - */ -class LLExpandableTextBox : public LLUICtrl -{ -protected: - - /** - * Extended text box. "More" link will appear at end of text if - * text is too long to fit into text box size. - */ - class LLTextBoxEx : public LLTextEditor - { - public: - struct Params : public LLInitParam::Block - { - Params(); - }; - - // adds or removes "More" link as needed - /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); - /*virtual*/ void setText(const LLStringExplicit& text, const LLStyle::Params& input_params = LLStyle::Params()); - void setTextBase(const std::string& text) { LLTextBase::setText(text); } - - /** - * Returns difference between text box height and text height. - * Value is positive if text height is greater than text box height. - */ - virtual S32 getVerticalTextDelta(); - - /** - * Returns the height of text rect. - */ - S32 getTextPixelHeight(); - - /** - * Shows "More" link - */ - void showExpandText(); - - /** - * Hides "More" link - */ - void hideExpandText(); - - /** - * Shows the "More" link if the text is too high to be completely - * visible without expanding the text box. Hides that link otherwise. - */ - void hideOrShowExpandTextAsNeeded(); - - protected: - - LLTextBoxEx(const Params& p); - friend class LLUICtrlFactory; - - private: - std::string mExpanderLabel; - - bool mExpanderVisible; - }; - -public: - - struct Params : public LLInitParam::Block - { - Optional textbox; - - Optional scroll; - - Optional max_height; - - Optional bg_visible, - expanded_bg_visible; - - Optional bg_color, - expanded_bg_color; - - Params(); - }; - - /** - * Sets text - */ - virtual void setText(const std::string& str); - - /** - * Returns text - */ - virtual const std::string& getText() const { return mText; } - - /** - * Sets text - */ - /*virtual*/ void setValue(const LLSD& value); - - /** - * Returns text - */ - /*virtual*/ LLSD getValue() const { return mText; } - - /** - * Collapses text box on focus_lost event - */ - /*virtual*/ void onFocusLost(); - - /** - * Collapses text box on top_lost event - */ - /*virtual*/ void onTopLost(); - - /** - * *HACK: Update the inner textbox shape. - */ - void updateTextShape(); - virtual void reshape(S32 width, S32 height, bool called_from_parent = true); - - /** - * Draws text box, collapses text box if its expanded and its parent's position changed - */ - /*virtual*/ void draw(); - - virtual ~LLExpandableTextBox(); - -protected: - - LLExpandableTextBox(const Params& p); - friend class LLUICtrlFactory; - - /** - * Expands text box. - * A scroll bar will appear if expanded height is greater than max_height - */ - virtual void expandTextBox(); - - /** - * Collapses text box. - */ - virtual void collapseTextBox(); - - /** - * Collapses text box if it is expanded and its parent's position changed - */ - virtual void collapseIfPosChanged(); - - /** - * Updates text box rect to avoid horizontal scroll bar - */ - virtual void updateTextBoxRect(); - - /** - * User clicked on "More" link - expand text box - */ - virtual void onExpandClicked(); - - /** - * Saves collapsed text box's states(rect, parent rect...) - */ - virtual void saveCollapsedState(); - - /** - * Recalculate text delta considering min_height and window rect. - */ - virtual S32 recalculateTextDelta(S32 text_delta); - - void setContentTrusted(bool trusted_content); - -protected: - - std::string mText; - LLTextBoxEx* mTextBox; - LLScrollContainer* mScroll; - - S32 mMaxHeight; - LLRect mCollapsedRect; - bool mExpanded; - LLRect mParentRect; - - bool mBGVisible; - bool mExpandedBGVisible; - LLUIColor mBGColor; - LLUIColor mExpandedBGColor; -}; - -#endif //LL_LLEXPANDABLETEXTBOX_H +/** + * @file llexpandabletextbox.h + * @brief LLExpandableTextBox and related class definitions + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLEXPANDABLETEXTBOX_H +#define LL_LLEXPANDABLETEXTBOX_H + +#include "lltexteditor.h" +#include "llscrollcontainer.h" + +/** + * LLExpandableTextBox is a text box control that will show "More" link at end of text + * if text doesn't fit into text box. After pressing "More" the text box will expand to show + * all text. If text is still too big, a scroll bar will appear inside expanded text box. + */ +class LLExpandableTextBox : public LLUICtrl +{ +protected: + + /** + * Extended text box. "More" link will appear at end of text if + * text is too long to fit into text box size. + */ + class LLTextBoxEx : public LLTextEditor + { + public: + struct Params : public LLInitParam::Block + { + Params(); + }; + + // adds or removes "More" link as needed + /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); + /*virtual*/ void setText(const LLStringExplicit& text, const LLStyle::Params& input_params = LLStyle::Params()); + void setTextBase(const std::string& text) { LLTextBase::setText(text); } + + /** + * Returns difference between text box height and text height. + * Value is positive if text height is greater than text box height. + */ + virtual S32 getVerticalTextDelta(); + + /** + * Returns the height of text rect. + */ + S32 getTextPixelHeight(); + + /** + * Shows "More" link + */ + void showExpandText(); + + /** + * Hides "More" link + */ + void hideExpandText(); + + /** + * Shows the "More" link if the text is too high to be completely + * visible without expanding the text box. Hides that link otherwise. + */ + void hideOrShowExpandTextAsNeeded(); + + protected: + + LLTextBoxEx(const Params& p); + friend class LLUICtrlFactory; + + private: + std::string mExpanderLabel; + + bool mExpanderVisible; + }; + +public: + + struct Params : public LLInitParam::Block + { + Optional textbox; + + Optional scroll; + + Optional max_height; + + Optional bg_visible, + expanded_bg_visible; + + Optional bg_color, + expanded_bg_color; + + Params(); + }; + + /** + * Sets text + */ + virtual void setText(const std::string& str); + + /** + * Returns text + */ + virtual const std::string& getText() const { return mText; } + + /** + * Sets text + */ + /*virtual*/ void setValue(const LLSD& value); + + /** + * Returns text + */ + /*virtual*/ LLSD getValue() const { return mText; } + + /** + * Collapses text box on focus_lost event + */ + /*virtual*/ void onFocusLost(); + + /** + * Collapses text box on top_lost event + */ + /*virtual*/ void onTopLost(); + + /** + * *HACK: Update the inner textbox shape. + */ + void updateTextShape(); + virtual void reshape(S32 width, S32 height, bool called_from_parent = true); + + /** + * Draws text box, collapses text box if its expanded and its parent's position changed + */ + /*virtual*/ void draw(); + + virtual ~LLExpandableTextBox(); + +protected: + + LLExpandableTextBox(const Params& p); + friend class LLUICtrlFactory; + + /** + * Expands text box. + * A scroll bar will appear if expanded height is greater than max_height + */ + virtual void expandTextBox(); + + /** + * Collapses text box. + */ + virtual void collapseTextBox(); + + /** + * Collapses text box if it is expanded and its parent's position changed + */ + virtual void collapseIfPosChanged(); + + /** + * Updates text box rect to avoid horizontal scroll bar + */ + virtual void updateTextBoxRect(); + + /** + * User clicked on "More" link - expand text box + */ + virtual void onExpandClicked(); + + /** + * Saves collapsed text box's states(rect, parent rect...) + */ + virtual void saveCollapsedState(); + + /** + * Recalculate text delta considering min_height and window rect. + */ + virtual S32 recalculateTextDelta(S32 text_delta); + + void setContentTrusted(bool trusted_content); + +protected: + + std::string mText; + LLTextBoxEx* mTextBox; + LLScrollContainer* mScroll; + + S32 mMaxHeight; + LLRect mCollapsedRect; + bool mExpanded; + LLRect mParentRect; + + bool mBGVisible; + bool mExpandedBGVisible; + LLUIColor mBGColor; + LLUIColor mExpandedBGColor; +}; + +#endif //LL_LLEXPANDABLETEXTBOX_H diff --git a/indra/newview/llexternaleditor.cpp b/indra/newview/llexternaleditor.cpp index 1d43162f3e..3decd15bbd 100644 --- a/indra/newview/llexternaleditor.cpp +++ b/indra/newview/llexternaleditor.cpp @@ -1,203 +1,203 @@ -/** - * @file llexternaleditor.cpp - * @brief A convenient class to run external editor. - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llexternaleditor.h" - -#include "lltrans.h" -#include "llui.h" -#include "llprocess.h" -#include "llsdutil.h" -#include "llstring.h" - -// static -const std::string LLExternalEditor::sFilenameMarker = "%s"; - -// static -const std::string LLExternalEditor::sSetting = "ExternalEditor"; - -LLExternalEditor::EErrorCode LLExternalEditor::setCommand(const std::string& env_var, const std::string& override) -{ - std::string cmd = findCommand(env_var, override); - if (cmd.empty()) - { - LL_WARNS() << "Editor command is empty or not set" << LL_ENDL; - return EC_NOT_SPECIFIED; - } - - string_vec_t tokens; - tokenize(tokens, cmd); - - // Check executable for existence. - std::string bin_path = tokens[0]; - if (!LLFile::isfile(bin_path)) - { - LL_WARNS() << "Editor binary [" << bin_path << "] not found" << LL_ENDL; - return EC_BINARY_NOT_FOUND; - } - - // Save command. - mProcessParams = LLProcess::Params(); - mProcessParams.executable = bin_path; - for (size_t i = 1; i < tokens.size(); ++i) - { - mProcessParams.args.add(tokens[i]); - } - - // Add the filename marker if missing. - if (cmd.find(sFilenameMarker) == std::string::npos) - { - mProcessParams.args.add(sFilenameMarker); - LL_INFOS() << "Adding the filename marker (" << sFilenameMarker << ")" << LL_ENDL; - } - - LL_INFOS() << "Setting command [" << mProcessParams << "]" << LL_ENDL; - - return EC_SUCCESS; -} - -LLExternalEditor::EErrorCode LLExternalEditor::run(const std::string& file_path) -{ - if (std::string(mProcessParams.executable).empty() || mProcessParams.args.empty()) - { - LL_WARNS() << "Editor command not set" << LL_ENDL; - return EC_NOT_SPECIFIED; - } - - // Copy params block so we can replace sFilenameMarker - LLProcess::Params params; - params.executable = mProcessParams.executable; - - // Substitute the filename marker in the command with the actual passed file name. - for (const std::string& arg : mProcessParams.args) - { - std::string fixed(arg); - LLStringUtil::replaceString(fixed, sFilenameMarker, file_path); - params.args.add(fixed); - } - - // Run the editor. Prevent killing the process in destructor. - params.autokill = false; - return LLProcess::create(params) ? EC_SUCCESS : EC_FAILED_TO_RUN; -} - -// static -std::string LLExternalEditor::getErrorMessage(EErrorCode code) -{ - switch (code) - { - case EC_SUCCESS: return LLTrans::getString("ok"); - case EC_NOT_SPECIFIED: return LLTrans::getString("ExternalEditorNotSet"); - case EC_PARSE_ERROR: return LLTrans::getString("ExternalEditorCommandParseError"); - case EC_BINARY_NOT_FOUND: return LLTrans::getString("ExternalEditorNotFound"); - case EC_FAILED_TO_RUN: return LLTrans::getString("ExternalEditorFailedToRun"); - } - - return LLTrans::getString("Unknown"); -} - -// TODO: -// - Unit-test this with tests like LLStringUtil::getTokens() (the -// command-line overload that supports quoted tokens) -// - Unless there are significant semantic differences, eliminate this method -// and use LLStringUtil::getTokens() instead. - -// static -size_t LLExternalEditor::tokenize(string_vec_t& tokens, const std::string& str) -{ - tokens.clear(); - - // Split the argument string into separate strings for each argument - typedef boost::tokenizer< boost::char_separator > tokenizer; - boost::char_separator sep("", "\" ", boost::drop_empty_tokens); - - tokenizer tokens_list(str, sep); - tokenizer::iterator token_iter; - bool inside_quotes = false; - bool last_was_space = false; - for (token_iter = tokens_list.begin(); token_iter != tokens_list.end(); ++token_iter) - { - if (!strncmp("\"",(*token_iter).c_str(),2)) - { - inside_quotes = !inside_quotes; - } - else if (!strncmp(" ",(*token_iter).c_str(),2)) - { - if(inside_quotes) - { - tokens.back().append(std::string(" ")); - last_was_space = true; - } - } - else - { - std::string to_push = *token_iter; - if (last_was_space) - { - tokens.back().append(to_push); - last_was_space = false; - } - else - { - tokens.push_back(to_push); - } - } - } - - return tokens.size(); -} - -// static -std::string LLExternalEditor::findCommand( - const std::string& env_var, - const std::string& override) -{ - std::string cmd; - - // Get executable path. - if (!override.empty()) // try the supplied override first - { - cmd = override; - LL_INFOS() << "Using override" << LL_ENDL; - } - else if (!LLUI::getInstance()->mSettingGroups["config"]->getString(sSetting).empty()) - { - cmd = LLUI::getInstance()->mSettingGroups["config"]->getString(sSetting); - LL_INFOS() << "Using setting" << LL_ENDL; - } - else // otherwise use the path specified by the environment variable - { - auto env_var_val(LLStringUtil::getoptenv(env_var)); - if (env_var_val) - { - cmd = *env_var_val; - LL_INFOS() << "Using env var " << env_var << LL_ENDL; - } - } - - LL_INFOS() << "Found command [" << cmd << "]" << LL_ENDL; - return cmd; -} +/** + * @file llexternaleditor.cpp + * @brief A convenient class to run external editor. + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llexternaleditor.h" + +#include "lltrans.h" +#include "llui.h" +#include "llprocess.h" +#include "llsdutil.h" +#include "llstring.h" + +// static +const std::string LLExternalEditor::sFilenameMarker = "%s"; + +// static +const std::string LLExternalEditor::sSetting = "ExternalEditor"; + +LLExternalEditor::EErrorCode LLExternalEditor::setCommand(const std::string& env_var, const std::string& override) +{ + std::string cmd = findCommand(env_var, override); + if (cmd.empty()) + { + LL_WARNS() << "Editor command is empty or not set" << LL_ENDL; + return EC_NOT_SPECIFIED; + } + + string_vec_t tokens; + tokenize(tokens, cmd); + + // Check executable for existence. + std::string bin_path = tokens[0]; + if (!LLFile::isfile(bin_path)) + { + LL_WARNS() << "Editor binary [" << bin_path << "] not found" << LL_ENDL; + return EC_BINARY_NOT_FOUND; + } + + // Save command. + mProcessParams = LLProcess::Params(); + mProcessParams.executable = bin_path; + for (size_t i = 1; i < tokens.size(); ++i) + { + mProcessParams.args.add(tokens[i]); + } + + // Add the filename marker if missing. + if (cmd.find(sFilenameMarker) == std::string::npos) + { + mProcessParams.args.add(sFilenameMarker); + LL_INFOS() << "Adding the filename marker (" << sFilenameMarker << ")" << LL_ENDL; + } + + LL_INFOS() << "Setting command [" << mProcessParams << "]" << LL_ENDL; + + return EC_SUCCESS; +} + +LLExternalEditor::EErrorCode LLExternalEditor::run(const std::string& file_path) +{ + if (std::string(mProcessParams.executable).empty() || mProcessParams.args.empty()) + { + LL_WARNS() << "Editor command not set" << LL_ENDL; + return EC_NOT_SPECIFIED; + } + + // Copy params block so we can replace sFilenameMarker + LLProcess::Params params; + params.executable = mProcessParams.executable; + + // Substitute the filename marker in the command with the actual passed file name. + for (const std::string& arg : mProcessParams.args) + { + std::string fixed(arg); + LLStringUtil::replaceString(fixed, sFilenameMarker, file_path); + params.args.add(fixed); + } + + // Run the editor. Prevent killing the process in destructor. + params.autokill = false; + return LLProcess::create(params) ? EC_SUCCESS : EC_FAILED_TO_RUN; +} + +// static +std::string LLExternalEditor::getErrorMessage(EErrorCode code) +{ + switch (code) + { + case EC_SUCCESS: return LLTrans::getString("ok"); + case EC_NOT_SPECIFIED: return LLTrans::getString("ExternalEditorNotSet"); + case EC_PARSE_ERROR: return LLTrans::getString("ExternalEditorCommandParseError"); + case EC_BINARY_NOT_FOUND: return LLTrans::getString("ExternalEditorNotFound"); + case EC_FAILED_TO_RUN: return LLTrans::getString("ExternalEditorFailedToRun"); + } + + return LLTrans::getString("Unknown"); +} + +// TODO: +// - Unit-test this with tests like LLStringUtil::getTokens() (the +// command-line overload that supports quoted tokens) +// - Unless there are significant semantic differences, eliminate this method +// and use LLStringUtil::getTokens() instead. + +// static +size_t LLExternalEditor::tokenize(string_vec_t& tokens, const std::string& str) +{ + tokens.clear(); + + // Split the argument string into separate strings for each argument + typedef boost::tokenizer< boost::char_separator > tokenizer; + boost::char_separator sep("", "\" ", boost::drop_empty_tokens); + + tokenizer tokens_list(str, sep); + tokenizer::iterator token_iter; + bool inside_quotes = false; + bool last_was_space = false; + for (token_iter = tokens_list.begin(); token_iter != tokens_list.end(); ++token_iter) + { + if (!strncmp("\"",(*token_iter).c_str(),2)) + { + inside_quotes = !inside_quotes; + } + else if (!strncmp(" ",(*token_iter).c_str(),2)) + { + if(inside_quotes) + { + tokens.back().append(std::string(" ")); + last_was_space = true; + } + } + else + { + std::string to_push = *token_iter; + if (last_was_space) + { + tokens.back().append(to_push); + last_was_space = false; + } + else + { + tokens.push_back(to_push); + } + } + } + + return tokens.size(); +} + +// static +std::string LLExternalEditor::findCommand( + const std::string& env_var, + const std::string& override) +{ + std::string cmd; + + // Get executable path. + if (!override.empty()) // try the supplied override first + { + cmd = override; + LL_INFOS() << "Using override" << LL_ENDL; + } + else if (!LLUI::getInstance()->mSettingGroups["config"]->getString(sSetting).empty()) + { + cmd = LLUI::getInstance()->mSettingGroups["config"]->getString(sSetting); + LL_INFOS() << "Using setting" << LL_ENDL; + } + else // otherwise use the path specified by the environment variable + { + auto env_var_val(LLStringUtil::getoptenv(env_var)); + if (env_var_val) + { + cmd = *env_var_val; + LL_INFOS() << "Using env var " << env_var << LL_ENDL; + } + } + + LL_INFOS() << "Found command [" << cmd << "]" << LL_ENDL; + return cmd; +} diff --git a/indra/newview/llface.cpp b/indra/newview/llface.cpp index 0750a72b05..9a654ee370 100644 --- a/indra/newview/llface.cpp +++ b/indra/newview/llface.cpp @@ -1,2452 +1,2452 @@ -/** - * @file llface.cpp - * @brief LLFace class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lldrawable.h" // lldrawable needs to be included before llface -#include "llface.h" -#include "llviewertextureanim.h" - -#include "llviewercontrol.h" -#include "llvolume.h" -#include "m3math.h" -#include "llmatrix4a.h" -#include "v3color.h" - -#include "lldefs.h" - -#include "lldrawpoolavatar.h" -#include "lldrawpoolbump.h" -#include "llgl.h" -#include "llrender.h" -#include "lllightconstants.h" -#include "llsky.h" -#include "llviewercamera.h" -#include "llviewertexturelist.h" -#include "llvopartgroup.h" -#include "llvovolume.h" -#include "pipeline.h" -#include "llviewerregion.h" -#include "llviewerwindow.h" -#include "llviewershadermgr.h" -#include "llviewertexture.h" -#include "llvoavatar.h" -#include "llsculptidsize.h" -#include "llmeshrepository.h" - -#if LL_LINUX -// Work-around spurious used before init warning on Vector4a -// -#pragma GCC diagnostic ignored "-Wuninitialized" -#endif - -#define LL_MAX_INDICES_COUNT 1000000 - -static LLStaticHashedString sTextureIndexIn("texture_index_in"); -static LLStaticHashedString sColorIn("color_in"); - -bool LLFace::sSafeRenderSelect = true; // false - - -#define DOTVEC(a,b) (a.mV[0]*b.mV[0] + a.mV[1]*b.mV[1] + a.mV[2]*b.mV[2]) - -/* -For each vertex, given: - B - binormal - T - tangent - N - normal - P - position - -The resulting texture coordinate is: - - u = 2(B dot P) - v = 2(T dot P) -*/ -void planarProjection(LLVector2 &tc, const LLVector4a& normal, - const LLVector4a ¢er, const LLVector4a& vec) -{ - LLVector4a binormal; - F32 d = normal[0]; - - if (d >= 0.5f || d <= -0.5f) - { - if (d < 0) - { - binormal.set(0,-1,0); - } - else - { - binormal.set(0, 1, 0); - } - } - else - { - if (normal[1] > 0) - { - binormal.set(-1,0,0); - } - else - { - binormal.set(1,0,0); - } - } - LLVector4a tangent; - tangent.setCross3(binormal,normal); - - tc.mV[1] = -((tangent.dot3(vec).getF32())*2 - 0.5f); - tc.mV[0] = 1.0f+((binormal.dot3(vec).getF32())*2 - 0.5f); -} - -//////////////////// -// -// LLFace implementation -// - -void LLFace::init(LLDrawable* drawablep, LLViewerObject* objp) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_FACE; - mLastUpdateTime = gFrameTimeSeconds; - mLastMoveTime = 0.f; - mLastSkinTime = gFrameTimeSeconds; - mVSize = 0.f; - mPixelArea = 16.f; - mState = GLOBAL; - mDrawPoolp = NULL; - mPoolType = 0; - mCenterLocal = objp->getPosition(); - mCenterAgent = drawablep->getPositionAgent(); - mDistance = 0.f; - - mGeomCount = 0; - mGeomIndex = 0; - mIndicesCount = 0; - - //special value to indicate uninitialized position - mIndicesIndex = 0xFFFFFFFF; - - for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) - { - mIndexInTex[i] = 0; - mTexture[i] = NULL; - } - - mTEOffset = -1; - mTextureIndex = FACE_DO_NOT_BATCH_TEXTURES; - - setDrawable(drawablep); - mVObjp = objp; - - mReferenceIndex = -1; - - mTextureMatrix = NULL; - mDrawInfo = NULL; - - mFaceColor = LLColor4(1,0,0,1); - - mImportanceToCamera = 0.f ; - mBoundingSphereRadius = 0.0f ; - - mHasMedia = false ; - mIsMediaAllowed = true; -} - -void LLFace::destroy() -{ - if (gDebugGL) - { - gPipeline.checkReferences(this); - } - - for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) - { - if(mTexture[i].notNull()) - { - mTexture[i]->removeFace(i, this) ; - mTexture[i] = NULL; - } - } - - if (isState(LLFace::PARTICLE)) - { - clearState(LLFace::PARTICLE); - } - - if (mDrawPoolp) - { - mDrawPoolp->removeFace(this); - mDrawPoolp = NULL; - } - - if (mTextureMatrix) - { - delete mTextureMatrix; - mTextureMatrix = NULL; - - if (mDrawablep) - { - LLSpatialGroup* group = mDrawablep->getSpatialGroup(); - if (group) - { - group->dirtyGeom(); - gPipeline.markRebuild(group); - } - } - } - - setDrawInfo(NULL); - - mDrawablep = NULL; - mVObjp = NULL; -} - -void LLFace::setWorldMatrix(const LLMatrix4 &mat) -{ - LL_ERRS() << "Faces on this drawable are not independently modifiable\n" << LL_ENDL; -} - -void LLFace::setPool(LLFacePool* pool) -{ - mDrawPoolp = pool; -} - -void LLFace::setPool(LLFacePool* new_pool, LLViewerTexture *texturep) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_FACE - - if (!new_pool) - { - LL_ERRS() << "Setting pool to null!" << LL_ENDL; - } - - if (new_pool != mDrawPoolp) - { - // Remove from old pool - if (mDrawPoolp) - { - mDrawPoolp->removeFace(this); - - if (mDrawablep) - { - gPipeline.markRebuild(mDrawablep, LLDrawable::REBUILD_ALL); - } - } - mGeomIndex = 0; - - // Add to new pool - if (new_pool) - { - new_pool->addFace(this); - } - mDrawPoolp = new_pool; - } - - setTexture(texturep) ; -} - -void LLFace::setTexture(U32 ch, LLViewerTexture* tex) -{ - llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); - - if(mTexture[ch] == tex) - { - return ; - } - - if(mTexture[ch].notNull()) - { - mTexture[ch]->removeFace(ch, this) ; - } - - if(tex) - { - tex->addFace(ch, this) ; - } - - mTexture[ch] = tex ; -} - -void LLFace::setTexture(LLViewerTexture* tex) -{ - setDiffuseMap(tex); -} - -void LLFace::setDiffuseMap(LLViewerTexture* tex) -{ - setTexture(LLRender::DIFFUSE_MAP, tex); -} - -void LLFace::setAlternateDiffuseMap(LLViewerTexture* tex) -{ - setTexture(LLRender::ALTERNATE_DIFFUSE_MAP, tex); -} - -void LLFace::setNormalMap(LLViewerTexture* tex) -{ - setTexture(LLRender::NORMAL_MAP, tex); -} - -void LLFace::setSpecularMap(LLViewerTexture* tex) -{ - setTexture(LLRender::SPECULAR_MAP, tex); -} - -void LLFace::dirtyTexture() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_FACE - - LLDrawable* drawablep = getDrawable(); - - if (mVObjp.notNull() && mVObjp->getVolume()) - { - for (U32 ch = 0; ch < LLRender::NUM_TEXTURE_CHANNELS; ++ch) - { - if (mTexture[ch].notNull() && mTexture[ch]->getComponents() == 4) - { //dirty texture on an alpha object should be treated as an LoD update - LLVOVolume* vobj = drawablep->getVOVolume(); - if (vobj) - { - vobj->mLODChanged = true; - - vobj->updateVisualComplexity(); - } - gPipeline.markRebuild(drawablep, LLDrawable::REBUILD_VOLUME); - } - } - } - - gPipeline.markTextured(drawablep); -} - -void LLFace::switchTexture(U32 ch, LLViewerTexture* new_texture) -{ - llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); - - if(mTexture[ch] == new_texture) - { - return ; - } - - if(!new_texture) - { - LL_ERRS() << "Can not switch to a null texture." << LL_ENDL; - return; - } - - llassert(mTexture[ch].notNull()); - - if (ch == LLRender::DIFFUSE_MAP) - { - getViewerObject()->changeTEImage(mTEOffset, new_texture) ; - } - - setTexture(ch, new_texture) ; - dirtyTexture(); -} - -void LLFace::setTEOffset(const S32 te_offset) -{ - mTEOffset = te_offset; -} - - -void LLFace::setFaceColor(const LLColor4& color) -{ - mFaceColor = color; - setState(USE_FACE_COLOR); -} - -void LLFace::unsetFaceColor() -{ - clearState(USE_FACE_COLOR); -} - -void LLFace::setDrawable(LLDrawable *drawable) -{ - mDrawablep = drawable; - mXform = &drawable->mXform; -} - -void LLFace::setSize(S32 num_vertices, S32 num_indices, bool align) -{ - if (align) - { - //allocate vertices in blocks of 4 for alignment - num_vertices = (num_vertices + 0x3) & ~0x3; - } - - if (mGeomCount != num_vertices || - mIndicesCount != num_indices) - { - mGeomCount = num_vertices; - mIndicesCount = num_indices; - mVertexBuffer = NULL; - } - - llassert(verify()); -} - -void LLFace::setGeomIndex(U16 idx) -{ - if (mGeomIndex != idx) - { - mGeomIndex = idx; - mVertexBuffer = NULL; - } -} - -void LLFace::setTextureIndex(U8 index) -{ - if (index != mTextureIndex) - { - mTextureIndex = index; - - if (mTextureIndex != FACE_DO_NOT_BATCH_TEXTURES) - { - mDrawablep->setState(LLDrawable::REBUILD_POSITION); - } - else - { - if (mDrawInfo && !mDrawInfo->mTextureList.empty()) - { - LL_ERRS() << "Face with no texture index references indexed texture draw info." << LL_ENDL; - } - } - } -} - -void LLFace::setIndicesIndex(S32 idx) -{ - if (mIndicesIndex != idx) - { - mIndicesIndex = idx; - mVertexBuffer = NULL; - } -} - -//============================================================================ - -U16 LLFace::getGeometryAvatar( - LLStrider &vertices, - LLStrider &normals, - LLStrider &tex_coords, - LLStrider &vertex_weights, - LLStrider &clothing_weights) -{ - if (mVertexBuffer.notNull()) - { - mVertexBuffer->getVertexStrider (vertices, mGeomIndex, mGeomCount); - mVertexBuffer->getNormalStrider (normals, mGeomIndex, mGeomCount); - mVertexBuffer->getTexCoord0Strider (tex_coords, mGeomIndex, mGeomCount); - mVertexBuffer->getWeightStrider(vertex_weights, mGeomIndex, mGeomCount); - mVertexBuffer->getClothWeightStrider(clothing_weights, mGeomIndex, mGeomCount); - } - - return mGeomIndex; -} - -U16 LLFace::getGeometry(LLStrider &vertices, LLStrider &normals, - LLStrider &tex_coords, LLStrider &indicesp) -{ - if (mVertexBuffer.notNull()) - { - mVertexBuffer->getVertexStrider(vertices, mGeomIndex, mGeomCount); - if (mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_NORMAL)) - { - mVertexBuffer->getNormalStrider(normals, mGeomIndex, mGeomCount); - } - if (mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_TEXCOORD0)) - { - mVertexBuffer->getTexCoord0Strider(tex_coords, mGeomIndex, mGeomCount); - } - - mVertexBuffer->getIndexStrider(indicesp, mIndicesIndex, mIndicesCount); - } - - return mGeomIndex; -} - -void LLFace::updateCenterAgent() -{ - if (mDrawablep->isActive()) - { - mCenterAgent = mCenterLocal * getRenderMatrix(); - } - else - { - mCenterAgent = mCenterLocal; - } -} - -void LLFace::renderSelected(LLViewerTexture *imagep, const LLColor4& color) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_FACE - - if (mDrawablep == NULL || mDrawablep->getSpatialGroup() == NULL) - { - return; - } - - mDrawablep->getSpatialGroup()->rebuildGeom(); - mDrawablep->getSpatialGroup()->rebuildMesh(); - - if(mVertexBuffer.isNull()) - { - return; - } - - if (mGeomCount > 0 && mIndicesCount > 0) - { - gGL.getTexUnit(0)->bind(imagep); - - gGL.pushMatrix(); - if (mDrawablep->isActive()) - { - gGL.multMatrix((GLfloat*)mDrawablep->getRenderMatrix().mMatrix); - } - else - { - gGL.multMatrix((GLfloat*)mDrawablep->getRegion()->mRenderMatrix.mMatrix); - } - - gGL.diffuseColor4fv(color.mV); - - if (mDrawablep->isState(LLDrawable::RIGGED)) - { -#if 0 // TODO -- there is no way this won't destroy our GL machine as implemented, rewrite it to not rely on software skinning - LLVOVolume* volume = mDrawablep->getVOVolume(); - if (volume) - { - LLRiggedVolume* rigged = volume->getRiggedVolume(); - if (rigged) - { - // called when selecting a face during edit of a mesh object - LLGLEnable offset(GL_POLYGON_OFFSET_FILL); - glPolygonOffset(-1.f, -1.f); - gGL.multMatrix((F32*) volume->getRelativeXform().mMatrix); - const LLVolumeFace& vol_face = rigged->getVolumeFace(getTEOffset()); - LLVertexBuffer::unbind(); - glVertexPointer(3, GL_FLOAT, 16, vol_face.mPositions); - if (vol_face.mTexCoords) - { - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - glTexCoordPointer(2, GL_FLOAT, 8, vol_face.mTexCoords); - } - gGL.syncMatrices(); - glDrawElements(GL_TRIANGLES, vol_face.mNumIndices, GL_UNSIGNED_SHORT, vol_face.mIndices); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - } - } -#endif - } - else - { - // cheaters sometimes prosper... - // - mVertexBuffer->setBuffer(); - mVertexBuffer->draw(LLRender::TRIANGLES, mIndicesCount, mIndicesIndex); - } - - gGL.popMatrix(); - } -} - - -void renderFace(LLDrawable* drawable, LLFace *face) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_FACE - - LLVOVolume* vobj = drawable->getVOVolume(); - if (vobj) - { - LLVolume* volume = NULL; - - if (drawable->isState(LLDrawable::RIGGED)) - { - volume = vobj->getRiggedVolume(); - } - else - { - volume = vobj->getVolume(); - } - - if (volume) - { - const LLVolumeFace& vol_face = volume->getVolumeFace(face->getTEOffset()); - LLVertexBuffer::drawElements(LLRender::TRIANGLES, vol_face.mPositions, NULL, vol_face.mNumIndices, vol_face.mIndices); - } - } -} - -void LLFace::renderOneWireframe(const LLColor4 &color, F32 fogCfx, bool wireframe_selection, bool bRenderHiddenSelections, bool shader) -{ - if (bRenderHiddenSelections) - { - gGL.blendFunc(LLRender::BF_SOURCE_COLOR, LLRender::BF_ONE); - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE, GL_GEQUAL); - if (shader) - { - gGL.diffuseColor4f(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], 0.4f); - renderFace(mDrawablep, this); - } - else - { - gGL.flush(); - { - gGL.diffuseColor4f(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], 0.4f); - renderFace(mDrawablep, this); - } - } - } - - gGL.flush(); - gGL.setSceneBlendType(LLRender::BT_ALPHA); - - gGL.diffuseColor4f(color.mV[VRED] * 2, color.mV[VGREEN] * 2, color.mV[VBLUE] * 2, color.mV[VALPHA]); - - { - LLGLDisable depth(wireframe_selection ? 0 : GL_BLEND); - //LLGLEnable stencil(wireframe_selection ? 0 : GL_STENCIL_TEST); - - if (!wireframe_selection) - { //modify wireframe into outline selection mode - glStencilFunc(GL_NOTEQUAL, 2, 0xffff); - glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); - } - - LLGLEnable offset(GL_POLYGON_OFFSET_LINE); - glPolygonOffset(3.f, 3.f); - glLineWidth(5.f); - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - renderFace(mDrawablep, this); - } -} - -void LLFace::setDrawInfo(LLDrawInfo* draw_info) -{ - mDrawInfo = draw_info; -} - -void LLFace::printDebugInfo() const -{ - LLFacePool *poolp = getPool(); - LL_INFOS() << "Object: " << getViewerObject()->mID << LL_ENDL; - if (getDrawable()) - { - LL_INFOS() << "Type: " << LLPrimitive::pCodeToString(getDrawable()->getVObj()->getPCode()) << LL_ENDL; - } - if (getTexture()) - { - LL_INFOS() << "Texture: " << getTexture() << " Comps: " << (U32)getTexture()->getComponents() << LL_ENDL; - } - else - { - LL_INFOS() << "No texture: " << LL_ENDL; - } - - LL_INFOS() << "Face: " << this << LL_ENDL; - LL_INFOS() << "State: " << getState() << LL_ENDL; - LL_INFOS() << "Geom Index Data:" << LL_ENDL; - LL_INFOS() << "--------------------" << LL_ENDL; - LL_INFOS() << "GI: " << mGeomIndex << " Count:" << mGeomCount << LL_ENDL; - LL_INFOS() << "Face Index Data:" << LL_ENDL; - LL_INFOS() << "--------------------" << LL_ENDL; - LL_INFOS() << "II: " << mIndicesIndex << " Count:" << mIndicesCount << LL_ENDL; - LL_INFOS() << LL_ENDL; - - if (poolp) - { - poolp->printDebugInfo(); - - S32 pool_references = 0; - for (std::vector::iterator iter = poolp->mReferences.begin(); - iter != poolp->mReferences.end(); iter++) - { - LLFace *facep = *iter; - if (facep == this) - { - LL_INFOS() << "Pool reference: " << pool_references << LL_ENDL; - pool_references++; - } - } - - if (pool_references != 1) - { - LL_INFOS() << "Incorrect number of pool references!" << LL_ENDL; - } - } - -#if 0 - LL_INFOS() << "Indices:" << LL_ENDL; - LL_INFOS() << "--------------------" << LL_ENDL; - - const U32 *indicesp = getRawIndices(); - S32 indices_count = getIndicesCount(); - S32 geom_start = getGeomStart(); - - for (S32 i = 0; i < indices_count; i++) - { - LL_INFOS() << i << ":" << indicesp[i] << ":" << (S32)(indicesp[i] - geom_start) << LL_ENDL; - } - LL_INFOS() << LL_ENDL; - - LL_INFOS() << "Vertices:" << LL_ENDL; - LL_INFOS() << "--------------------" << LL_ENDL; - for (S32 i = 0; i < mGeomCount; i++) - { - LL_INFOS() << mGeomIndex + i << ":" << poolp->getVertex(mGeomIndex + i) << LL_ENDL; - } - LL_INFOS() << LL_ENDL; -#endif -} - -// Transform the texture coordinates for this face. -static void xform(LLVector2 &tex_coord, F32 cosAng, F32 sinAng, F32 offS, F32 offT, F32 magS, F32 magT) -{ - // New, good way - F32 s = tex_coord.mV[0]; - F32 t = tex_coord.mV[1]; - - // Texture transforms are done about the center of the face. - s -= 0.5; - t -= 0.5; - - // Handle rotation - F32 temp = s; - s = s * cosAng + t * sinAng; - t = -temp * sinAng + t * cosAng; - - // Then scale - s *= magS; - t *= magT; - - // Then offset - s += offS + 0.5f; - t += offT + 0.5f; - - tex_coord.mV[0] = s; - tex_coord.mV[1] = t; -} - -// Transform the texture coordinates for this face. -static void xform4a(LLVector4a &tex_coord, const LLVector4a& trans, const LLVector4Logical& mask, const LLVector4a& rot0, const LLVector4a& rot1, const LLVector4a& offset, const LLVector4a& scale) -{ - //tex coord is two coords, - LLVector4a st; - - // Texture transforms are done about the center of the face. - st.setAdd(tex_coord, trans); - - // Handle rotation - LLVector4a rot_st; - - // - LLVector4a s0; - s0.splat(st, 0); - LLVector4a s1; - s1.splat(st, 2); - LLVector4a ss; - ss.setSelectWithMask(mask, s1, s0); - - LLVector4a a; - a.setMul(rot0, ss); - - // - LLVector4a t0; - t0.splat(st, 1); - LLVector4a t1; - t1.splat(st, 3); - LLVector4a tt; - tt.setSelectWithMask(mask, t1, t0); - - LLVector4a b; - b.setMul(rot1, tt); - - st.setAdd(a,b); - - // Then scale - st.mul(scale); - - // Then offset - tex_coord.setAdd(st, offset); -} - - -bool less_than_max_mag(const LLVector4a& vec) -{ - LLVector4a MAX_MAG; - MAX_MAG.splat(1024.f*1024.f); - - LLVector4a val; - val.setAbs(vec); - - S32 lt = val.lessThan(MAX_MAG).getGatheredBits() & 0x7; - - return lt == 0x7; -} - -bool LLFace::genVolumeBBoxes(const LLVolume &volume, S32 f, - const LLMatrix4& mat_vert_in, bool global_volume) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_FACE - - //get bounding box - if (mDrawablep->isState(LLDrawable::REBUILD_VOLUME | LLDrawable::REBUILD_POSITION | LLDrawable::REBUILD_RIGGED)) - { - if (f >= volume.getNumVolumeFaces()) - { - LL_WARNS() << "Generating bounding box for invalid face index!" << LL_ENDL; - f = 0; - } - - const LLVolumeFace &face = volume.getVolumeFace(f); - - LL_DEBUGS("RiggedBox") << "updating extents for face " << f - << " starting extents " << mExtents[0] << ", " << mExtents[1] - << " starting vf extents " << face.mExtents[0] << ", " << face.mExtents[1] - << " num verts " << face.mNumVertices << LL_ENDL; - - // MAINT-8264 - stray vertices, especially in low LODs, cause bounding box errors. - if (face.mNumVertices < 3) - { - LL_DEBUGS("RiggedBox") << "skipping face " << f << ", bad num vertices " - << face.mNumVertices << " " << face.mNumIndices << " " << face.mWeights << LL_ENDL; - return false; - } - - //VECTORIZE THIS - LLMatrix4a mat_vert; - mat_vert.loadu(mat_vert_in); - LLVector4a new_extents[2]; - - llassert(less_than_max_mag(face.mExtents[0])); - llassert(less_than_max_mag(face.mExtents[1])); - - matMulBoundBox(mat_vert, face.mExtents, mExtents); - - LL_DEBUGS("RiggedBox") << "updated extents for face " << f - << " bbox gave extents " << mExtents[0] << ", " << mExtents[1] << LL_ENDL; - - if (!mDrawablep->isActive()) - { // Shift position for region - LLVector4a offset; - offset.load3(mDrawablep->getRegion()->getOriginAgent().mV); - mExtents[0].add(offset); - mExtents[1].add(offset); - LL_DEBUGS("RiggedBox") << "updating extents for face " << f - << " not active, added offset " << offset << LL_ENDL; - } - - LL_DEBUGS("RiggedBox") << "updated extents for face " << f - << " to " << mExtents[0] << ", " << mExtents[1] << LL_ENDL; - LLVector4a t; - t.setAdd(mExtents[0],mExtents[1]); - t.mul(0.5f); - - mCenterLocal.set(t.getF32ptr()); - - t.setSub(mExtents[1],mExtents[0]); - mBoundingSphereRadius = t.getLength3().getF32()*0.5f; - - updateCenterAgent(); - } - - return true; -} - - - -// convert surface coordinates to texture coordinates, based on -// the values in the texture entry. probably should be -// integrated with getGeometryVolume() for its texture coordinate -// generation - but i'll leave that to someone more familiar -// with the implications. -LLVector2 LLFace::surfaceToTexture(LLVector2 surface_coord, const LLVector4a& position, const LLVector4a& normal) -{ - LLVector2 tc = surface_coord; - - const LLTextureEntry *tep = getTextureEntry(); - - if (tep == NULL) - { - // can't do much without the texture entry - return surface_coord; - } - - //VECTORIZE THIS - // see if we have a non-default mapping - U8 texgen = getTextureEntry()->getTexGen(); - if (texgen != LLTextureEntry::TEX_GEN_DEFAULT) - { - LLVector4a& center = *(mDrawablep->getVOVolume()->getVolume()->getVolumeFace(mTEOffset).mCenter); - - LLVector4a volume_position; - LLVector3 v_position(position.getF32ptr()); - - volume_position.load3(mDrawablep->getVOVolume()->agentPositionToVolume(v_position).mV); - - if (!mDrawablep->getVOVolume()->isVolumeGlobal()) - { - LLVector4a scale; - scale.load3(mVObjp->getScale().mV); - volume_position.mul(scale); - } - - LLVector4a volume_normal; - LLVector3 v_normal(normal.getF32ptr()); - volume_normal.load3(mDrawablep->getVOVolume()->agentDirectionToVolume(v_normal).mV); - volume_normal.normalize3fast(); - - if (texgen == LLTextureEntry::TEX_GEN_PLANAR) - { - planarProjection(tc, volume_normal, center, volume_position); - } - } - - if (mTextureMatrix) // if we have a texture matrix, use it - { - LLVector3 tc3(tc); - tc3 = tc3 * *mTextureMatrix; - tc = LLVector2(tc3); - } - - else // otherwise use the texture entry parameters - { - xform(tc, cos(tep->getRotation()), sin(tep->getRotation()), - tep->mOffsetS, tep->mOffsetT, tep->mScaleS, tep->mScaleT); - } - - - return tc; -} - -// Returns scale compared to default texgen, and face orientation as calculated -// by planarProjection(). This is needed to match planar texgen parameters. -void LLFace::getPlanarProjectedParams(LLQuaternion* face_rot, LLVector3* face_pos, F32* scale) const -{ - const LLMatrix4& vol_mat = getWorldMatrix(); - const LLVolumeFace& vf = getViewerObject()->getVolume()->getVolumeFace(mTEOffset); - if (! (vf.mNormals && vf.mTangents)) - { - return; - } - const LLVector4a& normal4a = *vf.mNormals; - const LLVector4a& tangent = *vf.mTangents; - - LLVector4a binormal4a; - binormal4a.setCross3(normal4a, tangent); - binormal4a.mul(tangent.getF32ptr()[3]); - - LLVector2 projected_binormal; - planarProjection(projected_binormal, normal4a, *vf.mCenter, binormal4a); - projected_binormal -= LLVector2(0.5f, 0.5f); // this normally happens in xform() - *scale = projected_binormal.length(); - // rotate binormal to match what planarProjection() thinks it is, - // then find rotation from that: - projected_binormal.normalize(); - F32 ang = acos(projected_binormal.mV[VY]); - ang = (projected_binormal.mV[VX] < 0.f) ? -ang : ang; - - //VECTORIZE THIS - LLVector3 binormal(binormal4a.getF32ptr()); - LLVector3 normal(normal4a.getF32ptr()); - binormal.rotVec(ang, normal); - LLQuaternion local_rot( binormal % normal, binormal, normal ); - *face_rot = local_rot * vol_mat.quaternion(); - *face_pos = vol_mat.getTranslation(); -} - -// Returns the necessary texture transform to align this face's TE to align_to's TE -bool LLFace::calcAlignedPlanarTE(const LLFace* align_to, LLVector2* res_st_offset, - LLVector2* res_st_scale, F32* res_st_rot, LLRender::eTexIndex map) const -{ - if (!align_to) - { - return false; - } - const LLTextureEntry *orig_tep = align_to->getTextureEntry(); - if ((orig_tep->getTexGen() != LLTextureEntry::TEX_GEN_PLANAR) || - (getTextureEntry()->getTexGen() != LLTextureEntry::TEX_GEN_PLANAR)) - { - return false; - } - - F32 map_rot = 0.f, map_scaleS = 0.f, map_scaleT = 0.f, map_offsS = 0.f, map_offsT = 0.f; - - LLMaterial* mat = orig_tep->getMaterialParams(); - if (!mat && map != LLRender::DIFFUSE_MAP) - { - LL_WARNS_ONCE("llface") << "Face is set to use specular or normal map but has no material, defaulting to diffuse" << LL_ENDL; - map = LLRender::DIFFUSE_MAP; - } - - switch (map) - { - case LLRender::DIFFUSE_MAP: - map_rot = orig_tep->getRotation(); - map_scaleS = orig_tep->mScaleS; - map_scaleT = orig_tep->mScaleT; - map_offsS = orig_tep->mOffsetS; - map_offsT = orig_tep->mOffsetT; - break; - case LLRender::NORMAL_MAP: - if (mat->getNormalID().isNull()) - { - return false; - } - map_rot = mat->getNormalRotation(); - map_scaleS = mat->getNormalRepeatX(); - map_scaleT = mat->getNormalRepeatY(); - map_offsS = mat->getNormalOffsetX(); - map_offsT = mat->getNormalOffsetY(); - break; - case LLRender::SPECULAR_MAP: - if (mat->getSpecularID().isNull()) - { - return false; - } - map_rot = mat->getSpecularRotation(); - map_scaleS = mat->getSpecularRepeatX(); - map_scaleT = mat->getSpecularRepeatY(); - map_offsS = mat->getSpecularOffsetX(); - map_offsT = mat->getSpecularOffsetY(); - break; - default: /*make compiler happy*/ - break; - } - - LLVector3 orig_pos, this_pos; - LLQuaternion orig_face_rot, this_face_rot; - F32 orig_proj_scale, this_proj_scale; - align_to->getPlanarProjectedParams(&orig_face_rot, &orig_pos, &orig_proj_scale); - getPlanarProjectedParams(&this_face_rot, &this_pos, &this_proj_scale); - - // The rotation of "this face's" texture: - LLQuaternion orig_st_rot = LLQuaternion(map_rot, LLVector3::z_axis) * orig_face_rot; - LLQuaternion this_st_rot = orig_st_rot * ~this_face_rot; - F32 x_ang, y_ang, z_ang; - this_st_rot.getEulerAngles(&x_ang, &y_ang, &z_ang); - *res_st_rot = z_ang; - - // Offset and scale of "this face's" texture: - LLVector3 centers_dist = (this_pos - orig_pos) * ~orig_st_rot; - LLVector3 st_scale(map_scaleS, map_scaleT, 1.f); - st_scale *= orig_proj_scale; - centers_dist.scaleVec(st_scale); - LLVector2 orig_st_offset(map_offsS, map_offsT); - - *res_st_offset = orig_st_offset + (LLVector2)centers_dist; - res_st_offset->mV[VX] -= (S32)res_st_offset->mV[VX]; - res_st_offset->mV[VY] -= (S32)res_st_offset->mV[VY]; - - st_scale /= this_proj_scale; - *res_st_scale = (LLVector2)st_scale; - return true; -} - -void LLFace::updateRebuildFlags() -{ - if (mDrawablep->isState(LLDrawable::REBUILD_VOLUME)) - { //this rebuild is zero overhead (direct consequence of some change that affects this face) - mLastUpdateTime = gFrameTimeSeconds; - } - else - { //this rebuild is overhead (side effect of some change that does not affect this face) - mLastMoveTime = gFrameTimeSeconds; - } -} - - -bool LLFace::canRenderAsMask() -{ - const LLTextureEntry* te = getTextureEntry(); - if( !te || !getViewerObject() || !getTexture() ) - { - return false; - } - - if (te->getGLTFRenderMaterial()) - { - return false; - } - - if (LLPipeline::sNoAlpha) - { - return true; - } - - if (isState(LLFace::RIGGED)) - { // never auto alpha-mask rigged faces - return false; - } - - - LLMaterial* mat = te->getMaterialParams(); - if (mat && mat->getDiffuseAlphaMode() == LLMaterial::DIFFUSE_ALPHA_MODE_BLEND) - { - return false; - } - - if ((te->getColor().mV[3] == 1.0f) && // can't treat as mask if we have face alpha - (te->getGlow() == 0.f) && // glowing masks are hard to implement - don't mask - getTexture()->getIsAlphaMask()) // texture actually qualifies for masking (lazily recalculated but expensive) - { - if (getViewerObject()->isHUDAttachment() || te->getFullbright()) - { //hud attachments and fullbright objects are NOT subject to the deferred rendering pipe - return LLPipeline::sAutoMaskAlphaNonDeferred; - } - else - { - return LLPipeline::sAutoMaskAlphaDeferred; - } - } - - return false; -} - -//helper function for pushing primitives for transform shaders and cleaning up -//uninitialized data on the tail, plus tracking number of expected primitives -void push_for_transform(LLVertexBuffer* buff, U32 source_count, U32 dest_count) -{ - if (source_count > 0 && dest_count >= source_count) //protect against possible U32 wrapping - { - //push source primitives - buff->drawArrays(LLRender::POINTS, 0, source_count); - U32 tail = dest_count-source_count; - for (U32 i = 0; i < tail; ++i) - { //copy last source primitive into each element in tail - buff->drawArrays(LLRender::POINTS, source_count-1, 1); - } - gPipeline.mTransformFeedbackPrimitives += dest_count; - } -} - -bool LLFace::getGeometryVolume(const LLVolume& volume, - S32 face_index, - const LLMatrix4& mat_vert_in, - const LLMatrix3& mat_norm_in, - U16 index_offset, - bool force_rebuild, - bool no_debug_assert) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_FACE; - llassert(verify()); - - if (face_index < 0 || face_index >= volume.getNumVolumeFaces()) - { - if (gDebugGL) - { - LL_WARNS() << "Face index is out of bounds!" << LL_ENDL; - LL_WARNS() << "Attempt get volume face out of range!" - " Total Faces: " << volume.getNumVolumeFaces() << - " Attempt get access to: " << face_index << LL_ENDL; - llassert(no_debug_assert); - } - return false; - } - - bool rigged = isState(RIGGED); - - const LLVolumeFace &vf = volume.getVolumeFace(face_index); - S32 num_vertices = (S32)vf.mNumVertices; - S32 num_indices = (S32) vf.mNumIndices; - - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_OCTREE)) - { - updateRebuildFlags(); - } - - if (mVertexBuffer.notNull()) - { - if (num_indices + (S32) mIndicesIndex > mVertexBuffer->getNumIndices()) - { - if (gDebugGL) - { - LL_WARNS() << "Index buffer overflow!" << LL_ENDL; - LL_WARNS() << "Indices Count: " << mIndicesCount - << " VF Num Indices: " << num_indices - << " Indices Index: " << mIndicesIndex - << " VB Num Indices: " << mVertexBuffer->getNumIndices() << LL_ENDL; - LL_WARNS() << " Face Index: " << face_index - << " Pool Type: " << mPoolType << LL_ENDL; - llassert(no_debug_assert); - } - return false; - } - - if (num_vertices + mGeomIndex > mVertexBuffer->getNumVerts()) - { - if (gDebugGL) - { - LL_WARNS() << "Vertex buffer overflow!" << LL_ENDL; - llassert(no_debug_assert); - } - return false; - } - } - - LLStrider vert; - LLStrider tex_coords0; - LLStrider tex_coords1; - LLStrider tex_coords2; - LLStrider norm; - LLStrider colors; - LLStrider tangent; - LLStrider indicesp; - LLStrider wght; - - bool full_rebuild = force_rebuild || mDrawablep->isState(LLDrawable::REBUILD_VOLUME); - - bool global_volume = mDrawablep->getVOVolume()->isVolumeGlobal(); - LLVector3 scale; - if (global_volume) - { - scale.setVec(1,1,1); - } - else - { - scale = mVObjp->getScale(); - } - - bool rebuild_pos = full_rebuild || mDrawablep->isState(LLDrawable::REBUILD_POSITION); - bool rebuild_color = full_rebuild || mDrawablep->isState(LLDrawable::REBUILD_COLOR); - bool rebuild_emissive = rebuild_color && mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_EMISSIVE); - bool rebuild_tcoord = full_rebuild || mDrawablep->isState(LLDrawable::REBUILD_TCOORD); - bool rebuild_normal = rebuild_pos && mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_NORMAL); - bool rebuild_tangent = rebuild_pos && mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_TANGENT); - bool rebuild_weights = rebuild_pos && mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_WEIGHT4); - - const LLTextureEntry *tep = mVObjp->getTE(face_index); - const U8 bump_code = tep ? tep->getBumpmap() : 0; - - bool is_static = mDrawablep->isStatic(); - bool is_global = is_static; - - LLVector3 center_sum(0.f, 0.f, 0.f); - - if (is_global) - { - setState(GLOBAL); - } - else - { - clearState(GLOBAL); - } - - LLColor4U color = tep->getColor(); - - if (tep->getGLTFRenderMaterial()) - { - color = tep->getGLTFRenderMaterial()->mBaseColor; - } - - if (rebuild_color) - { //decide if shiny goes in alpha channel of color - if (tep && - !isInAlphaPool()) // <--- alpha channel MUST contain transparency, not shiny - { - LLMaterial* mat = tep->getMaterialParams().get(); - - bool shiny_in_alpha = false; - - //store shiny in alpha if we don't have a specular map - if (!mat || mat->getSpecularID().isNull()) - { - shiny_in_alpha = true; - } - - if (shiny_in_alpha) - { - static const GLfloat SHININESS_TO_ALPHA[4] = - { - 0.0000f, - 0.25f, - 0.5f, - 0.75f - }; - - llassert(tep->getShiny() <= 3); - color.mV[3] = U8 (SHININESS_TO_ALPHA[tep->getShiny()] * 255); - } - } - } - - // INDICES - if (full_rebuild) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("getGeometryVolume - indices"); - mVertexBuffer->getIndexStrider(indicesp, mIndicesIndex, mIndicesCount); - - volatile __m128i* dst = (__m128i*) indicesp.get(); - __m128i* src = (__m128i*) vf.mIndices; - __m128i offset = _mm_set1_epi16(index_offset); - - S32 end = num_indices/8; - - for (S32 i = 0; i < end; i++) - { - __m128i res = _mm_add_epi16(src[i], offset); - _mm_storeu_si128((__m128i*) dst++, res); - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("getGeometryVolume - indices tail"); - U16* idx = (U16*) dst; - - for (S32 i = end*8; i < num_indices; ++i) - { - *idx++ = vf.mIndices[i]+index_offset; - } - } - } - - - LLMaterial* mat = tep->getMaterialParams().get(); - LLGLTFMaterial* gltf_mat = tep->getGLTFRenderMaterial(); - - F32 r = 0, os = 0, ot = 0, ms = 0, mt = 0, cos_ang = 0, sin_ang = 0; - - constexpr S32 XFORM_NONE = 0; - constexpr S32 XFORM_BLINNPHONG_COLOR = 1; - constexpr S32 XFORM_BLINNPHONG_NORMAL = 1 << 1; - constexpr S32 XFORM_BLINNPHONG_SPECULAR = 1 << 2; - - S32 xforms = XFORM_NONE; - // For GLTF, transforms will be applied later - if (rebuild_tcoord && tep && !gltf_mat) - { - r = tep->getRotation(); - os = tep->mOffsetS; - ot = tep->mOffsetT; - ms = tep->mScaleS; - mt = tep->mScaleT; - cos_ang = cos(r); - sin_ang = sin(r); - - if (cos_ang != 1.f || - sin_ang != 0.f || - os != 0.f || - ot != 0.f || - ms != 1.f || - mt != 1.f) - { - xforms |= XFORM_BLINNPHONG_COLOR; - } - if (mat) - { - F32 r_norm = 0, os_norm = 0, ot_norm = 0, ms_norm = 0, mt_norm = 0, cos_ang_norm = 0, sin_ang_norm = 0; - mat->getNormalOffset(os_norm, ot_norm); - mat->getNormalRepeat(ms_norm, mt_norm); - r_norm = mat->getNormalRotation(); - cos_ang_norm = cos(r_norm); - sin_ang_norm = sin(r_norm); - if (cos_ang_norm != 1.f || - sin_ang_norm != 0.f || - os_norm != 0.f || - ot_norm != 0.f || - ms_norm != 1.f || - mt_norm != 1.f) - { - xforms |= XFORM_BLINNPHONG_NORMAL; - } - } - if (mat) - { - F32 r_spec = 0, os_spec = 0, ot_spec = 0, ms_spec = 0, mt_spec = 0, cos_ang_spec = 0, sin_ang_spec = 0; - mat->getSpecularOffset(os_spec, ot_spec); - mat->getSpecularRepeat(ms_spec, mt_spec); - r_spec = mat->getSpecularRotation(); - cos_ang_spec = cos(r_spec); - sin_ang_spec = sin(r_spec); - if (cos_ang_spec != 1.f || - sin_ang_spec != 0.f || - os_spec != 0.f || - ot_spec != 0.f || - ms_spec != 1.f || - mt_spec != 1.f) - { - xforms |= XFORM_BLINNPHONG_SPECULAR; - } - } - } - - const LLMeshSkinInfo* skin = nullptr; - LLMatrix4a mat_vert; - LLMatrix4a mat_normal; - - // prepare mat_vert - if (rebuild_pos) - { - if (rigged) - { //override with bind shape matrix if rigged - skin = mSkinInfo; - mat_vert = skin->mBindShapeMatrix; - } - else - { - mat_vert.loadu(mat_vert_in); - } - } - - if (rebuild_normal || rebuild_tangent) - { //override mat_normal with inverse of skin->mBindShapeMatrix - LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("getGeometryVolume - norm mat override"); - if (rigged) - { - if (skin == nullptr) - { - skin = mSkinInfo; - } - - //TODO -- cache this (check profile marker above)? - glh::matrix4f m((F32*) skin->mBindShapeMatrix.getF32ptr()); - m = m.inverse().transpose(); - mat_normal.loadu(m.m); - } - else - { - mat_normal.loadu(mat_norm_in); - } - } - - { - //if it's not fullbright and has no normals, bake sunlight based on face normal - //bool bake_sunlight = !getTextureEntry()->getFullbright() && - // !mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_NORMAL); - - if (rebuild_tcoord) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("getGeometryVolume - tcoord"); - - //bump setup - LLVector4a binormal_dir( -sin_ang, cos_ang, 0.f ); - LLVector4a bump_s_primary_light_ray(0.f, 0.f, 0.f); - LLVector4a bump_t_primary_light_ray(0.f, 0.f, 0.f); - - LLQuaternion bump_quat; - if (mDrawablep->isActive()) - { - bump_quat = LLQuaternion(mDrawablep->getRenderMatrix()); - } - - if (bump_code) - { - mVObjp->getVolume()->genTangents(face_index); - F32 offset_multiple; - switch( bump_code ) - { - case BE_NO_BUMP: - offset_multiple = 0.f; - break; - case BE_BRIGHTNESS: - case BE_DARKNESS: - if( mTexture[LLRender::DIFFUSE_MAP].notNull() && mTexture[LLRender::DIFFUSE_MAP]->hasGLTexture()) - { - // Offset by approximately one texel - S32 cur_discard = mTexture[LLRender::DIFFUSE_MAP]->getDiscardLevel(); - S32 max_size = llmax( mTexture[LLRender::DIFFUSE_MAP]->getWidth(), mTexture[LLRender::DIFFUSE_MAP]->getHeight() ); - max_size <<= cur_discard; - const F32 ARTIFICIAL_OFFSET = 2.f; - offset_multiple = ARTIFICIAL_OFFSET / (F32)max_size; - } - else - { - offset_multiple = 1.f/256; - } - break; - - default: // Standard bumpmap textures. Assumed to be 256x256 - offset_multiple = 1.f / 256; - break; - } - - F32 s_scale = 1.f; - F32 t_scale = 1.f; - if( tep ) - { - tep->getScale( &s_scale, &t_scale ); - } - // Use the nudged south when coming from above sun angle, such - // that emboss mapping always shows up on the upward faces of cubes when - // it's noon (since a lot of builders build with the sun forced to noon). - LLVector3 sun_ray = gSky.mVOSkyp->mBumpSunDir; - LLVector3 moon_ray = gSky.mVOSkyp->getMoon().getDirection(); - LLVector3& primary_light_ray = (sun_ray.mV[VZ] > 0) ? sun_ray : moon_ray; - - bump_s_primary_light_ray.load3((offset_multiple * s_scale * primary_light_ray).mV); - bump_t_primary_light_ray.load3((offset_multiple * t_scale * primary_light_ray).mV); - } - - U8 texgen = getTextureEntry()->getTexGen(); - if (rebuild_tcoord && texgen != LLTextureEntry::TEX_GEN_DEFAULT) - { //planar texgen needs binormals - mVObjp->getVolume()->genTangents(face_index); - } - - U8 tex_mode = 0; - - bool tex_anim = false; - - LLVOVolume* vobj = (LLVOVolume*) (LLViewerObject*) mVObjp; - tex_mode = vobj->mTexAnimMode; - - if (vobj->mTextureAnimp) - { //texture animation is in play, override specular and normal map tex coords with diffuse texcoords - tex_anim = true; - } - - if (isState(TEXTURE_ANIM)) - { - if (!tex_mode) - { - clearState(TEXTURE_ANIM); - } - else - { - os = ot = 0.f; - r = 0.f; - cos_ang = 1.f; - sin_ang = 0.f; - ms = mt = 1.f; - - xforms = XFORM_NONE; - } - - if (getVirtualSize() >= MIN_TEX_ANIM_SIZE) // || isState(LLFace::RIGGED)) - { //don't override texture transform during tc bake - tex_mode = 0; - } - } - - LLVector4a scalea; - scalea.load3(scale.mV); - - bool do_bump = bump_code && mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_TEXCOORD1); - - if ((mat || gltf_mat) && !do_bump) - { - do_bump = mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_TEXCOORD1) - || mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_TEXCOORD2); - } - - // For GLTF materials: Transforms will be applied later - bool do_tex_mat = tex_mode && mTextureMatrix && !gltf_mat; - - if (!do_bump) - { //not bump mapped, might be able to do a cheap update - mVertexBuffer->getTexCoord0Strider(tex_coords0, mGeomIndex, mGeomCount); - - if (texgen != LLTextureEntry::TEX_GEN_PLANAR) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("getGeometryVolume - texgen"); - if (!do_tex_mat) - { - if (xforms == XFORM_NONE) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("ggv - texgen 1"); - S32 tc_size = (num_vertices*2*sizeof(F32)+0xF) & ~0xF; - LLVector4a::memcpyNonAliased16((F32*) tex_coords0.get(), (F32*) vf.mTexCoords, tc_size); - } - else - { - LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("ggv - texgen 2"); - F32* dst = (F32*) tex_coords0.get(); - LLVector4a* src = (LLVector4a*) vf.mTexCoords; - - LLVector4a trans; - trans.splat(-0.5f); - - LLVector4a rot0; - rot0.set(cos_ang, -sin_ang, cos_ang, -sin_ang); - - LLVector4a rot1; - rot1.set(sin_ang, cos_ang, sin_ang, cos_ang); - - LLVector4a scale; - scale.set(ms, mt, ms, mt); - - LLVector4a offset; - offset.set(os+0.5f, ot+0.5f, os+0.5f, ot+0.5f); - - LLVector4Logical mask; - mask.clear(); - mask.setElement<2>(); - mask.setElement<3>(); - - U32 count = num_vertices/2 + num_vertices%2; - - for (S32 i = 0; i < count; i++) - { - LLVector4a res = *src++; - xform4a(res, trans, mask, rot0, rot1, offset, scale); - res.store4a(dst); - dst += 4; - } - } - } - else - { //do tex mat, no texgen, no bump - for (S32 i = 0; i < num_vertices; i++) - { - LLVector2 tc(vf.mTexCoords[i]); - - LLVector3 tmp(tc.mV[0], tc.mV[1], 0.f); - tmp = tmp * *mTextureMatrix; - tc.mV[0] = tmp.mV[0]; - tc.mV[1] = tmp.mV[1]; - *tex_coords0++ = tc; - } - } - } - else - { //no bump, tex gen planar - LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("getGeometryVolume - texgen planar"); - if (do_tex_mat) - { - for (S32 i = 0; i < num_vertices; i++) - { - LLVector2 tc(vf.mTexCoords[i]); - LLVector4a& norm = vf.mNormals[i]; - LLVector4a& center = *(vf.mCenter); - LLVector4a vec = vf.mPositions[i]; - vec.mul(scalea); - planarProjection(tc, norm, center, vec); - - LLVector3 tmp(tc.mV[0], tc.mV[1], 0.f); - tmp = tmp * *mTextureMatrix; - tc.mV[0] = tmp.mV[0]; - tc.mV[1] = tmp.mV[1]; - - *tex_coords0++ = tc; - } - } - else if (xforms != XFORM_NONE) - { - for (S32 i = 0; i < num_vertices; i++) - { - LLVector2 tc(vf.mTexCoords[i]); - LLVector4a& norm = vf.mNormals[i]; - LLVector4a& center = *(vf.mCenter); - LLVector4a vec = vf.mPositions[i]; - vec.mul(scalea); - planarProjection(tc, norm, center, vec); - - xform(tc, cos_ang, sin_ang, os, ot, ms, mt); - - *tex_coords0++ = tc; - } - } - else - { - for (S32 i = 0; i < num_vertices; i++) - { - LLVector2 tc(vf.mTexCoords[i]); - LLVector4a& norm = vf.mNormals[i]; - LLVector4a& center = *(vf.mCenter); - LLVector4a vec = vf.mPositions[i]; - vec.mul(scalea); - planarProjection(tc, norm, center, vec); - - *tex_coords0++ = tc; - } - } - } - } - else - { //bump mapped or has material, just do the whole expensive loop - LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("getGeometryVolume - texgen default"); - - std::vector bump_tc; - - if (mat && !mat->getNormalID().isNull()) - { //writing out normal and specular texture coordinates, not bump offsets - do_bump = false; - } - - LLStrider dst; - - for (U32 ch = 0; ch < 3; ++ch) - { - S32 xform_channel = XFORM_NONE; - switch (ch) - { - case 0: - xform_channel = XFORM_BLINNPHONG_COLOR; - mVertexBuffer->getTexCoord0Strider(dst, mGeomIndex, mGeomCount); - break; - case 1: - xform_channel = XFORM_BLINNPHONG_NORMAL; - if (mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_TEXCOORD1)) - { - mVertexBuffer->getTexCoord1Strider(dst, mGeomIndex, mGeomCount); - if (mat && !tex_anim) - { - r = mat->getNormalRotation(); - mat->getNormalOffset(os, ot); - mat->getNormalRepeat(ms, mt); - - cos_ang = cos(r); - sin_ang = sin(r); - - } - } - else - { - continue; - } - break; - case 2: - xform_channel = XFORM_BLINNPHONG_SPECULAR; - if (mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_TEXCOORD2)) - { - mVertexBuffer->getTexCoord2Strider(dst, mGeomIndex, mGeomCount); - if (mat && !tex_anim) - { - r = mat->getSpecularRotation(); - mat->getSpecularOffset(os, ot); - mat->getSpecularRepeat(ms, mt); - - cos_ang = cos(r); - sin_ang = sin(r); - } - } - else - { - continue; - } - break; - } - const bool do_xform = (xforms & xform_channel) != XFORM_NONE; - - - for (S32 i = 0; i < num_vertices; i++) - { - LLVector2 tc(vf.mTexCoords[i]); - - LLVector4a& norm = vf.mNormals[i]; - - LLVector4a& center = *(vf.mCenter); - - if (texgen != LLTextureEntry::TEX_GEN_DEFAULT) - { - LLVector4a vec = vf.mPositions[i]; - - vec.mul(scalea); - - if (texgen == LLTextureEntry::TEX_GEN_PLANAR) - { - planarProjection(tc, norm, center, vec); - } - } - - if (tex_mode && mTextureMatrix) - { - LLVector3 tmp(tc.mV[0], tc.mV[1], 0.f); - tmp = tmp * *mTextureMatrix; - tc.mV[0] = tmp.mV[0]; - tc.mV[1] = tmp.mV[1]; - } - else if (do_xform) - { - xform(tc, cos_ang, sin_ang, os, ot, ms, mt); - } - - *dst++ = tc; - if (do_bump) - { - bump_tc.push_back(tc); - } - } - } - - if ((!mat && !gltf_mat) && do_bump) - { - mVertexBuffer->getTexCoord1Strider(tex_coords1, mGeomIndex, mGeomCount); - - mVObjp->getVolume()->genTangents(face_index); - - for (S32 i = 0; i < num_vertices; i++) - { - LLVector4a tangent = vf.mTangents[i]; - - LLVector4a binorm; - binorm.setCross3(vf.mNormals[i], tangent); - binorm.mul(tangent.getF32ptr()[3]); - - LLMatrix4a tangent_to_object; - tangent_to_object.setRows(tangent, binorm, vf.mNormals[i]); - LLVector4a t; - tangent_to_object.rotate(binormal_dir, t); - LLVector4a binormal; - mat_normal.rotate(t, binormal); - - //VECTORIZE THIS - if (mDrawablep->isActive()) - { - LLVector3 t; - t.set(binormal.getF32ptr()); - t *= bump_quat; - binormal.load3(t.mV); - } - - binormal.normalize3fast(); - - LLVector2 tc = bump_tc[i]; - tc += LLVector2( bump_s_primary_light_ray.dot3(tangent).getF32(), bump_t_primary_light_ray.dot3(binormal).getF32() ); - - *tex_coords1++ = tc; - } - } - } - } - - if (rebuild_pos) - { - LLVector4a* src = vf.mPositions; - - //_mm_prefetch((char*)src, _MM_HINT_T0); - - LLVector4a* end = src+num_vertices; - //LLVector4a* end_64 = end-4; - - llassert(num_vertices > 0); - - mVertexBuffer->getVertexStrider(vert, mGeomIndex, mGeomCount); - - - F32* dst = (F32*) vert.get(); - F32* end_f32 = dst+mGeomCount*4; - - //_mm_prefetch((char*)dst, _MM_HINT_NTA); - //_mm_prefetch((char*)src, _MM_HINT_NTA); - - //_mm_prefetch((char*)dst, _MM_HINT_NTA); - - - LLVector4a res0; //,res1,res2,res3; - - LLVector4a texIdx; - - S32 index = mTextureIndex < FACE_DO_NOT_BATCH_TEXTURES ? mTextureIndex : 0; - - F32 val = 0.f; - S32* vp = (S32*) &val; - *vp = index; - - llassert(index <= LLGLSLShader::sIndexedTextureChannels-1); - - LLVector4Logical mask; - mask.clear(); - mask.setElement<3>(); - - texIdx.set(0,0,0,val); - - LLVector4a tmp; - - - while (src < end) - { - mat_vert.affineTransform(*src++, res0); - tmp.setSelectWithMask(mask, texIdx, res0); - tmp.store4a((F32*) dst); - dst += 4; - } - - while (dst < end_f32) - { - res0.store4a((F32*) dst); - dst += 4; - } - } - - if (rebuild_normal) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("getGeometryVolume - normal"); - - mVertexBuffer->getNormalStrider(norm, mGeomIndex, mGeomCount); - F32* normals = (F32*) norm.get(); - LLVector4a* src = vf.mNormals; - LLVector4a* end = src+num_vertices; - - while (src < end) - { - LLVector4a normal; - mat_normal.rotate(*src++, normal); - normal.store4a(normals); - normals += 4; - } - } - - if (rebuild_tangent) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("getGeometryVolume - tangent"); - mVertexBuffer->getTangentStrider(tangent, mGeomIndex, mGeomCount); - F32* tangents = (F32*) tangent.get(); - - mVObjp->getVolume()->genTangents(face_index); - - LLVector4Logical mask; - mask.clear(); - mask.setElement<3>(); - - LLVector4a* src = vf.mTangents; - LLVector4a* end = vf.mTangents +num_vertices; - - while (src < end) - { - LLVector4a tangent_out; - mat_normal.rotate(*src, tangent_out); - tangent_out.setSelectWithMask(mask, *src, tangent_out); - tangent_out.store4a(tangents); - - src++; - tangents += 4; - } - } - - if (rebuild_weights && vf.mWeights) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("getGeometryVolume - weight"); - mVertexBuffer->getWeight4Strider(wght, mGeomIndex, mGeomCount); - F32* weights = (F32*) wght.get(); - LLVector4a::memcpyNonAliased16(weights, (F32*) vf.mWeights, num_vertices*4*sizeof(F32)); - } - - if (rebuild_color && mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_COLOR) ) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("getGeometryVolume - color"); - mVertexBuffer->getColorStrider(colors, mGeomIndex, mGeomCount); - - LLVector4a src; - - U32 vec[4]; - vec[0] = vec[1] = vec[2] = vec[3] = color.asRGBA(); - - src.loadua((F32*) vec); - - F32* dst = (F32*) colors.get(); - S32 num_vecs = num_vertices/4; - if (num_vertices%4 > 0) - { - ++num_vecs; - } - - for (S32 i = 0; i < num_vecs; i++) - { - src.store4a(dst); - dst += 4; - } - } - - if (rebuild_emissive) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("getGeometryVolume - emissive"); - LLStrider emissive; - mVertexBuffer->getEmissiveStrider(emissive, mGeomIndex, mGeomCount); - - U8 glow = (U8) llclamp((S32) (getTextureEntry()->getGlow()*255), 0, 255); - - LLVector4a src; - - - LLColor4U glow4u = LLColor4U(0,0,0,glow); - - U32 glow32 = glow4u.asRGBA(); - - U32 vec[4]; - vec[0] = vec[1] = vec[2] = vec[3] = glow32; - - src.loadua((F32*) vec); - - F32* dst = (F32*) emissive.get(); - S32 num_vecs = num_vertices/4; - if (num_vertices%4 > 0) - { - ++num_vecs; - } - - for (S32 i = 0; i < num_vecs; i++) - { - src.store4a(dst); - dst += 4; - } - } - } - - if (rebuild_tcoord) - { - mTexExtents[0].setVec(0,0); - mTexExtents[1].setVec(1,1); - xform(mTexExtents[0], cos_ang, sin_ang, os, ot, ms, mt); - xform(mTexExtents[1], cos_ang, sin_ang, os, ot, ms, mt); - - F32 es = vf.mTexCoordExtents[1].mV[0] - vf.mTexCoordExtents[0].mV[0] ; - F32 et = vf.mTexCoordExtents[1].mV[1] - vf.mTexCoordExtents[0].mV[1] ; - mTexExtents[0][0] *= es ; - mTexExtents[1][0] *= es ; - mTexExtents[0][1] *= et ; - mTexExtents[1][1] *= et ; - } - - - return true; -} - -void LLFace::renderIndexed() -{ - if (mVertexBuffer.notNull()) - { - mVertexBuffer->setBuffer(); - mVertexBuffer->drawRange(LLRender::TRIANGLES, getGeomIndex(), getGeomIndex() + getGeomCount()-1, getIndicesCount(), getIndicesStart()); - } -} - -//check if the face has a media -bool LLFace::hasMedia() const -{ - if(mHasMedia) - { - return true ; - } - if(mTexture[LLRender::DIFFUSE_MAP].notNull()) - { - return mTexture[LLRender::DIFFUSE_MAP]->hasParcelMedia() ; //if has a parcel media - } - - return false ; //no media. -} - -const F32 LEAST_IMPORTANCE = 0.05f ; -const F32 LEAST_IMPORTANCE_FOR_LARGE_IMAGE = 0.3f ; - -void LLFace::resetVirtualSize() -{ - setVirtualSize(0.f); - mImportanceToCamera = 0.f; -} - -F32 LLFace::getTextureVirtualSize() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - F32 radius; - F32 cos_angle_to_view_dir; - bool in_frustum = calcPixelArea(cos_angle_to_view_dir, radius); - - if (mPixelArea < F_ALMOST_ZERO || !in_frustum) - { - setVirtualSize(0.f) ; - return 0.f; - } - - //get area of circle in texture space - LLVector2 tdim = mTexExtents[1] - mTexExtents[0]; - F32 texel_area = (tdim * 0.5f).lengthSquared()*3.14159f; - if (texel_area <= 0) - { - // Probably animated, use default - texel_area = 1.f; - } - - F32 face_area; - if (mVObjp->isSculpted() && texel_area > 1.f) - { - //sculpts can break assumptions about texel area - face_area = mPixelArea; - } - else - { - //apply texel area to face area to get accurate ratio - //face_area /= llclamp(texel_area, 1.f/64.f, 16.f); - face_area = mPixelArea / llclamp(texel_area, 0.015625f, 128.f); - } - - face_area = LLFace::adjustPixelArea(mImportanceToCamera, face_area) ; - if(face_area > LLViewerTexture::sMinLargeImageSize) //if is large image, shrink face_area by considering the partial overlapping. - { - if(mImportanceToCamera > LEAST_IMPORTANCE_FOR_LARGE_IMAGE && mTexture[LLRender::DIFFUSE_MAP].notNull() && mTexture[LLRender::DIFFUSE_MAP]->isLargeImage()) - { - face_area *= adjustPartialOverlapPixelArea(cos_angle_to_view_dir, radius ); - } - } - - setVirtualSize(face_area) ; - - return face_area; -} - -bool LLFace::calcPixelArea(F32& cos_angle_to_view_dir, F32& radius) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_FACE; - - //VECTORIZE THIS - //get area of circle around face - - LLVector4a center; - LLVector4a size; - - - if (isState(LLFace::RIGGED)) - { - //override with avatar bounding box - LLVOAvatar* avatar = mVObjp->getAvatar(); - if (avatar && avatar->mDrawable) - { - center.load3(avatar->getPositionAgent().mV); - const LLVector4a* exts = avatar->mDrawable->getSpatialExtents(); - size.setSub(exts[1], exts[0]); - } - else - { - return false; - } - } - else - { - center.load3(getPositionAgent().mV); - size.setSub(mExtents[1], mExtents[0]); - } - size.mul(0.5f); - - LLViewerCamera* camera = LLViewerCamera::getInstance(); - - F32 size_squared = size.dot3(size).getF32(); - LLVector4a lookAt; - LLVector4a t; - t.load3(camera->getOrigin().mV); - lookAt.setSub(center, t); - - F32 dist = lookAt.getLength3().getF32(); - dist = llmax(dist-size.getLength3().getF32(), 0.001f); - //ramp down distance for nearby objects - if (dist < 16.f) - { - dist /= 16.f; - dist *= dist; - dist *= 16.f; - } - - lookAt.normalize3fast() ; - - //get area of circle around node - F32 app_angle = atanf((F32) sqrt(size_squared) / dist); - radius = app_angle*LLDrawable::sCurPixelAngle; - mPixelArea = radius*radius * 3.14159f; - LLVector4a x_axis; - x_axis.load3(camera->getXAxis().mV); - cos_angle_to_view_dir = lookAt.dot3(x_axis).getF32(); - - //if has media, check if the face is out of the view frustum. - if(hasMedia()) - { - if(!camera->AABBInFrustum(center, size)) - { - mImportanceToCamera = 0.f ; - return false ; - } - if(cos_angle_to_view_dir > camera->getCosHalfFov()) //the center is within the view frustum - { - cos_angle_to_view_dir = 1.0f ; - } - else - { - LLVector4a d; - d.setSub(lookAt, x_axis); - - if(dist * dist * d.dot3(d) < size_squared) - { - cos_angle_to_view_dir = 1.0f ; - } - } - } - - if(dist < mBoundingSphereRadius) //camera is very close - { - cos_angle_to_view_dir = 1.0f ; - mImportanceToCamera = 1.0f ; - } - else - { - mImportanceToCamera = LLFace::calcImportanceToCamera(cos_angle_to_view_dir, dist) ; - } - - return true ; -} - -//the projection of the face partially overlaps with the screen -F32 LLFace::adjustPartialOverlapPixelArea(F32 cos_angle_to_view_dir, F32 radius ) -{ - F32 screen_radius = (F32)llmax(gViewerWindow->getWindowWidthRaw(), gViewerWindow->getWindowHeightRaw()) ; - F32 center_angle = acosf(cos_angle_to_view_dir) ; - F32 d = center_angle * LLDrawable::sCurPixelAngle ; - - if(d + radius > screen_radius + 5.f) - { - //---------------------------------------------- - //calculate the intersection area of two circles - //F32 radius_square = radius * radius ; - //F32 d_square = d * d ; - //F32 screen_radius_square = screen_radius * screen_radius ; - //face_area = - // radius_square * acosf((d_square + radius_square - screen_radius_square)/(2 * d * radius)) + - // screen_radius_square * acosf((d_square + screen_radius_square - radius_square)/(2 * d * screen_radius)) - - // 0.5f * sqrtf((-d + radius + screen_radius) * (d + radius - screen_radius) * (d - radius + screen_radius) * (d + radius + screen_radius)) ; - //---------------------------------------------- - - //the above calculation is too expensive - //the below is a good estimation: bounding box of the bounding sphere: - F32 alpha = 0.5f * (radius + screen_radius - d) / radius ; - alpha = llclamp(alpha, 0.f, 1.f) ; - return alpha * alpha ; - } - return 1.0f ; -} - -const S8 FACE_IMPORTANCE_LEVEL = 4 ; -const F32 FACE_IMPORTANCE_TO_CAMERA_OVER_DISTANCE[FACE_IMPORTANCE_LEVEL][2] = //{distance, importance_weight} - {{16.1f, 1.0f}, {32.1f, 0.5f}, {48.1f, 0.2f}, {96.1f, 0.05f} } ; -const F32 FACE_IMPORTANCE_TO_CAMERA_OVER_ANGLE[FACE_IMPORTANCE_LEVEL][2] = //{cos(angle), importance_weight} - {{0.985f /*cos(10 degrees)*/, 1.0f}, {0.94f /*cos(20 degrees)*/, 0.8f}, {0.866f /*cos(30 degrees)*/, 0.64f}, {0.0f, 0.36f}} ; - -//static -F32 LLFace::calcImportanceToCamera(F32 cos_angle_to_view_dir, F32 dist) -{ - F32 importance = 0.f ; - - if(cos_angle_to_view_dir > LLViewerCamera::getInstance()->getCosHalfFov() && - dist < FACE_IMPORTANCE_TO_CAMERA_OVER_DISTANCE[FACE_IMPORTANCE_LEVEL - 1][0]) - { - LLViewerCamera* camera = LLViewerCamera::getInstance(); - F32 camera_moving_speed = camera->getAverageSpeed() ; - F32 camera_angular_speed = camera->getAverageAngularSpeed(); - - if(camera_moving_speed > 10.0f || camera_angular_speed > 1.0f) - { - //if camera moves or rotates too fast, ignore the importance factor - return 0.f ; - } - - S32 i = 0 ; - for(i = 0; i < FACE_IMPORTANCE_LEVEL && dist > FACE_IMPORTANCE_TO_CAMERA_OVER_DISTANCE[i][0]; ++i); - i = llmin(i, FACE_IMPORTANCE_LEVEL - 1) ; - F32 dist_factor = FACE_IMPORTANCE_TO_CAMERA_OVER_DISTANCE[i][1] ; - - for(i = 0; i < FACE_IMPORTANCE_LEVEL && cos_angle_to_view_dir < FACE_IMPORTANCE_TO_CAMERA_OVER_ANGLE[i][0] ; ++i) ; - i = llmin(i, FACE_IMPORTANCE_LEVEL - 1) ; - importance = dist_factor * FACE_IMPORTANCE_TO_CAMERA_OVER_ANGLE[i][1] ; - } - - return importance ; -} - -//static -F32 LLFace::adjustPixelArea(F32 importance, F32 pixel_area) -{ - if(pixel_area > LLViewerTexture::sMaxSmallImageSize) - { - if(importance < LEAST_IMPORTANCE) //if the face is not important, do not load hi-res. - { - static const F32 MAX_LEAST_IMPORTANCE_IMAGE_SIZE = 128.0f * 128.0f ; - pixel_area = llmin(pixel_area * 0.5f, MAX_LEAST_IMPORTANCE_IMAGE_SIZE) ; - } - else if(pixel_area > LLViewerTexture::sMinLargeImageSize) //if is large image, shrink face_area by considering the partial overlapping. - { - if(importance < LEAST_IMPORTANCE_FOR_LARGE_IMAGE)//if the face is not important, do not load hi-res. - { - pixel_area = LLViewerTexture::sMinLargeImageSize ; - } - } - } - - return pixel_area ; -} - -bool LLFace::verify(const U32* indices_array) const -{ - bool ok = true; - - if( mVertexBuffer.isNull() ) - { //no vertex buffer, face is implicitly valid - return true; - } - - // First, check whether the face data fits within the pool's range. - if ((mGeomIndex + mGeomCount) > mVertexBuffer->getNumVerts()) - { - ok = false; - LL_INFOS() << "Face references invalid vertices!" << LL_ENDL; - } - - S32 indices_count = (S32)getIndicesCount(); - - if (!indices_count) - { - return true; - } - - if (indices_count > LL_MAX_INDICES_COUNT) - { - ok = false; - LL_INFOS() << "Face has bogus indices count" << LL_ENDL; - } - - if (mIndicesIndex + mIndicesCount > mVertexBuffer->getNumIndices()) - { - ok = false; - LL_INFOS() << "Face references invalid indices!" << LL_ENDL; - } - -#if 0 - S32 geom_start = getGeomStart(); - S32 geom_count = mGeomCount; - - const U32 *indicesp = indices_array ? indices_array + mIndicesIndex : getRawIndices(); - - for (S32 i = 0; i < indices_count; i++) - { - S32 delta = indicesp[i] - geom_start; - if (0 > delta) - { - LL_WARNS() << "Face index too low!" << LL_ENDL; - LL_INFOS() << "i:" << i << " Index:" << indicesp[i] << " GStart: " << geom_start << LL_ENDL; - ok = false; - } - else if (delta >= geom_count) - { - LL_WARNS() << "Face index too high!" << LL_ENDL; - LL_INFOS() << "i:" << i << " Index:" << indicesp[i] << " GEnd: " << geom_start + geom_count << LL_ENDL; - ok = false; - } - } -#endif - - if (!ok) - { - printDebugInfo(); - } - return ok; -} - - -void LLFace::setViewerObject(LLViewerObject* objp) -{ - mVObjp = objp; -} - - -const LLMatrix4& LLFace::getRenderMatrix() const -{ - return mDrawablep->getRenderMatrix(); -} - -//============================================================================ -// From llface.inl - -S32 LLFace::getColors(LLStrider &colors) -{ - if (!mGeomCount) - { - return -1; - } - - // llassert(mGeomIndex >= 0); - mVertexBuffer->getColorStrider(colors, mGeomIndex, mGeomCount); - return mGeomIndex; -} - -S32 LLFace::getIndices(LLStrider &indicesp) -{ - mVertexBuffer->getIndexStrider(indicesp, mIndicesIndex, mIndicesCount); - llassert(indicesp[0] != indicesp[1]); - return mIndicesIndex; -} - -LLVector3 LLFace::getPositionAgent() const -{ - if (mDrawablep->isStatic()) - { - return mCenterAgent; - } - else - { - return mCenterLocal * getRenderMatrix(); - } -} - -LLViewerTexture* LLFace::getTexture(U32 ch) const -{ - llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); - - return mTexture[ch] ; -} - -void LLFace::setVertexBuffer(LLVertexBuffer* buffer) -{ - if (buffer) - { - LLSculptIDSize::instance().inc(mDrawablep, buffer->getSize() + buffer->getIndicesSize()); - } - - if (mVertexBuffer) - { - LLSculptIDSize::instance().dec(mDrawablep); - } - - mVertexBuffer = buffer; - llassert(verify()); -} - -void LLFace::clearVertexBuffer() -{ - if (mVertexBuffer) - { - LLSculptIDSize::instance().dec(mDrawablep); - } - - mVertexBuffer = NULL; -} - -S32 LLFace::getRiggedIndex(U32 type) const -{ - if (mRiggedIndex.empty()) - { - return -1; - } - - llassert(type < mRiggedIndex.size()); - - return mRiggedIndex[type]; -} - -U64 LLFace::getSkinHash() -{ - return mSkinInfo ? mSkinInfo->mHash : 0; -} - -bool LLFace::isInAlphaPool() const -{ - return getPoolType() == LLDrawPool::POOL_ALPHA || - getPoolType() == LLDrawPool::POOL_ALPHA_PRE_WATER || - getPoolType() == LLDrawPool::POOL_ALPHA_POST_WATER; -} +/** + * @file llface.cpp + * @brief LLFace class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lldrawable.h" // lldrawable needs to be included before llface +#include "llface.h" +#include "llviewertextureanim.h" + +#include "llviewercontrol.h" +#include "llvolume.h" +#include "m3math.h" +#include "llmatrix4a.h" +#include "v3color.h" + +#include "lldefs.h" + +#include "lldrawpoolavatar.h" +#include "lldrawpoolbump.h" +#include "llgl.h" +#include "llrender.h" +#include "lllightconstants.h" +#include "llsky.h" +#include "llviewercamera.h" +#include "llviewertexturelist.h" +#include "llvopartgroup.h" +#include "llvovolume.h" +#include "pipeline.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" +#include "llviewershadermgr.h" +#include "llviewertexture.h" +#include "llvoavatar.h" +#include "llsculptidsize.h" +#include "llmeshrepository.h" + +#if LL_LINUX +// Work-around spurious used before init warning on Vector4a +// +#pragma GCC diagnostic ignored "-Wuninitialized" +#endif + +#define LL_MAX_INDICES_COUNT 1000000 + +static LLStaticHashedString sTextureIndexIn("texture_index_in"); +static LLStaticHashedString sColorIn("color_in"); + +bool LLFace::sSafeRenderSelect = true; // false + + +#define DOTVEC(a,b) (a.mV[0]*b.mV[0] + a.mV[1]*b.mV[1] + a.mV[2]*b.mV[2]) + +/* +For each vertex, given: + B - binormal + T - tangent + N - normal + P - position + +The resulting texture coordinate is: + + u = 2(B dot P) + v = 2(T dot P) +*/ +void planarProjection(LLVector2 &tc, const LLVector4a& normal, + const LLVector4a ¢er, const LLVector4a& vec) +{ + LLVector4a binormal; + F32 d = normal[0]; + + if (d >= 0.5f || d <= -0.5f) + { + if (d < 0) + { + binormal.set(0,-1,0); + } + else + { + binormal.set(0, 1, 0); + } + } + else + { + if (normal[1] > 0) + { + binormal.set(-1,0,0); + } + else + { + binormal.set(1,0,0); + } + } + LLVector4a tangent; + tangent.setCross3(binormal,normal); + + tc.mV[1] = -((tangent.dot3(vec).getF32())*2 - 0.5f); + tc.mV[0] = 1.0f+((binormal.dot3(vec).getF32())*2 - 0.5f); +} + +//////////////////// +// +// LLFace implementation +// + +void LLFace::init(LLDrawable* drawablep, LLViewerObject* objp) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_FACE; + mLastUpdateTime = gFrameTimeSeconds; + mLastMoveTime = 0.f; + mLastSkinTime = gFrameTimeSeconds; + mVSize = 0.f; + mPixelArea = 16.f; + mState = GLOBAL; + mDrawPoolp = NULL; + mPoolType = 0; + mCenterLocal = objp->getPosition(); + mCenterAgent = drawablep->getPositionAgent(); + mDistance = 0.f; + + mGeomCount = 0; + mGeomIndex = 0; + mIndicesCount = 0; + + //special value to indicate uninitialized position + mIndicesIndex = 0xFFFFFFFF; + + for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) + { + mIndexInTex[i] = 0; + mTexture[i] = NULL; + } + + mTEOffset = -1; + mTextureIndex = FACE_DO_NOT_BATCH_TEXTURES; + + setDrawable(drawablep); + mVObjp = objp; + + mReferenceIndex = -1; + + mTextureMatrix = NULL; + mDrawInfo = NULL; + + mFaceColor = LLColor4(1,0,0,1); + + mImportanceToCamera = 0.f ; + mBoundingSphereRadius = 0.0f ; + + mHasMedia = false ; + mIsMediaAllowed = true; +} + +void LLFace::destroy() +{ + if (gDebugGL) + { + gPipeline.checkReferences(this); + } + + for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) + { + if(mTexture[i].notNull()) + { + mTexture[i]->removeFace(i, this) ; + mTexture[i] = NULL; + } + } + + if (isState(LLFace::PARTICLE)) + { + clearState(LLFace::PARTICLE); + } + + if (mDrawPoolp) + { + mDrawPoolp->removeFace(this); + mDrawPoolp = NULL; + } + + if (mTextureMatrix) + { + delete mTextureMatrix; + mTextureMatrix = NULL; + + if (mDrawablep) + { + LLSpatialGroup* group = mDrawablep->getSpatialGroup(); + if (group) + { + group->dirtyGeom(); + gPipeline.markRebuild(group); + } + } + } + + setDrawInfo(NULL); + + mDrawablep = NULL; + mVObjp = NULL; +} + +void LLFace::setWorldMatrix(const LLMatrix4 &mat) +{ + LL_ERRS() << "Faces on this drawable are not independently modifiable\n" << LL_ENDL; +} + +void LLFace::setPool(LLFacePool* pool) +{ + mDrawPoolp = pool; +} + +void LLFace::setPool(LLFacePool* new_pool, LLViewerTexture *texturep) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_FACE + + if (!new_pool) + { + LL_ERRS() << "Setting pool to null!" << LL_ENDL; + } + + if (new_pool != mDrawPoolp) + { + // Remove from old pool + if (mDrawPoolp) + { + mDrawPoolp->removeFace(this); + + if (mDrawablep) + { + gPipeline.markRebuild(mDrawablep, LLDrawable::REBUILD_ALL); + } + } + mGeomIndex = 0; + + // Add to new pool + if (new_pool) + { + new_pool->addFace(this); + } + mDrawPoolp = new_pool; + } + + setTexture(texturep) ; +} + +void LLFace::setTexture(U32 ch, LLViewerTexture* tex) +{ + llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); + + if(mTexture[ch] == tex) + { + return ; + } + + if(mTexture[ch].notNull()) + { + mTexture[ch]->removeFace(ch, this) ; + } + + if(tex) + { + tex->addFace(ch, this) ; + } + + mTexture[ch] = tex ; +} + +void LLFace::setTexture(LLViewerTexture* tex) +{ + setDiffuseMap(tex); +} + +void LLFace::setDiffuseMap(LLViewerTexture* tex) +{ + setTexture(LLRender::DIFFUSE_MAP, tex); +} + +void LLFace::setAlternateDiffuseMap(LLViewerTexture* tex) +{ + setTexture(LLRender::ALTERNATE_DIFFUSE_MAP, tex); +} + +void LLFace::setNormalMap(LLViewerTexture* tex) +{ + setTexture(LLRender::NORMAL_MAP, tex); +} + +void LLFace::setSpecularMap(LLViewerTexture* tex) +{ + setTexture(LLRender::SPECULAR_MAP, tex); +} + +void LLFace::dirtyTexture() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_FACE + + LLDrawable* drawablep = getDrawable(); + + if (mVObjp.notNull() && mVObjp->getVolume()) + { + for (U32 ch = 0; ch < LLRender::NUM_TEXTURE_CHANNELS; ++ch) + { + if (mTexture[ch].notNull() && mTexture[ch]->getComponents() == 4) + { //dirty texture on an alpha object should be treated as an LoD update + LLVOVolume* vobj = drawablep->getVOVolume(); + if (vobj) + { + vobj->mLODChanged = true; + + vobj->updateVisualComplexity(); + } + gPipeline.markRebuild(drawablep, LLDrawable::REBUILD_VOLUME); + } + } + } + + gPipeline.markTextured(drawablep); +} + +void LLFace::switchTexture(U32 ch, LLViewerTexture* new_texture) +{ + llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); + + if(mTexture[ch] == new_texture) + { + return ; + } + + if(!new_texture) + { + LL_ERRS() << "Can not switch to a null texture." << LL_ENDL; + return; + } + + llassert(mTexture[ch].notNull()); + + if (ch == LLRender::DIFFUSE_MAP) + { + getViewerObject()->changeTEImage(mTEOffset, new_texture) ; + } + + setTexture(ch, new_texture) ; + dirtyTexture(); +} + +void LLFace::setTEOffset(const S32 te_offset) +{ + mTEOffset = te_offset; +} + + +void LLFace::setFaceColor(const LLColor4& color) +{ + mFaceColor = color; + setState(USE_FACE_COLOR); +} + +void LLFace::unsetFaceColor() +{ + clearState(USE_FACE_COLOR); +} + +void LLFace::setDrawable(LLDrawable *drawable) +{ + mDrawablep = drawable; + mXform = &drawable->mXform; +} + +void LLFace::setSize(S32 num_vertices, S32 num_indices, bool align) +{ + if (align) + { + //allocate vertices in blocks of 4 for alignment + num_vertices = (num_vertices + 0x3) & ~0x3; + } + + if (mGeomCount != num_vertices || + mIndicesCount != num_indices) + { + mGeomCount = num_vertices; + mIndicesCount = num_indices; + mVertexBuffer = NULL; + } + + llassert(verify()); +} + +void LLFace::setGeomIndex(U16 idx) +{ + if (mGeomIndex != idx) + { + mGeomIndex = idx; + mVertexBuffer = NULL; + } +} + +void LLFace::setTextureIndex(U8 index) +{ + if (index != mTextureIndex) + { + mTextureIndex = index; + + if (mTextureIndex != FACE_DO_NOT_BATCH_TEXTURES) + { + mDrawablep->setState(LLDrawable::REBUILD_POSITION); + } + else + { + if (mDrawInfo && !mDrawInfo->mTextureList.empty()) + { + LL_ERRS() << "Face with no texture index references indexed texture draw info." << LL_ENDL; + } + } + } +} + +void LLFace::setIndicesIndex(S32 idx) +{ + if (mIndicesIndex != idx) + { + mIndicesIndex = idx; + mVertexBuffer = NULL; + } +} + +//============================================================================ + +U16 LLFace::getGeometryAvatar( + LLStrider &vertices, + LLStrider &normals, + LLStrider &tex_coords, + LLStrider &vertex_weights, + LLStrider &clothing_weights) +{ + if (mVertexBuffer.notNull()) + { + mVertexBuffer->getVertexStrider (vertices, mGeomIndex, mGeomCount); + mVertexBuffer->getNormalStrider (normals, mGeomIndex, mGeomCount); + mVertexBuffer->getTexCoord0Strider (tex_coords, mGeomIndex, mGeomCount); + mVertexBuffer->getWeightStrider(vertex_weights, mGeomIndex, mGeomCount); + mVertexBuffer->getClothWeightStrider(clothing_weights, mGeomIndex, mGeomCount); + } + + return mGeomIndex; +} + +U16 LLFace::getGeometry(LLStrider &vertices, LLStrider &normals, + LLStrider &tex_coords, LLStrider &indicesp) +{ + if (mVertexBuffer.notNull()) + { + mVertexBuffer->getVertexStrider(vertices, mGeomIndex, mGeomCount); + if (mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_NORMAL)) + { + mVertexBuffer->getNormalStrider(normals, mGeomIndex, mGeomCount); + } + if (mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_TEXCOORD0)) + { + mVertexBuffer->getTexCoord0Strider(tex_coords, mGeomIndex, mGeomCount); + } + + mVertexBuffer->getIndexStrider(indicesp, mIndicesIndex, mIndicesCount); + } + + return mGeomIndex; +} + +void LLFace::updateCenterAgent() +{ + if (mDrawablep->isActive()) + { + mCenterAgent = mCenterLocal * getRenderMatrix(); + } + else + { + mCenterAgent = mCenterLocal; + } +} + +void LLFace::renderSelected(LLViewerTexture *imagep, const LLColor4& color) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_FACE + + if (mDrawablep == NULL || mDrawablep->getSpatialGroup() == NULL) + { + return; + } + + mDrawablep->getSpatialGroup()->rebuildGeom(); + mDrawablep->getSpatialGroup()->rebuildMesh(); + + if(mVertexBuffer.isNull()) + { + return; + } + + if (mGeomCount > 0 && mIndicesCount > 0) + { + gGL.getTexUnit(0)->bind(imagep); + + gGL.pushMatrix(); + if (mDrawablep->isActive()) + { + gGL.multMatrix((GLfloat*)mDrawablep->getRenderMatrix().mMatrix); + } + else + { + gGL.multMatrix((GLfloat*)mDrawablep->getRegion()->mRenderMatrix.mMatrix); + } + + gGL.diffuseColor4fv(color.mV); + + if (mDrawablep->isState(LLDrawable::RIGGED)) + { +#if 0 // TODO -- there is no way this won't destroy our GL machine as implemented, rewrite it to not rely on software skinning + LLVOVolume* volume = mDrawablep->getVOVolume(); + if (volume) + { + LLRiggedVolume* rigged = volume->getRiggedVolume(); + if (rigged) + { + // called when selecting a face during edit of a mesh object + LLGLEnable offset(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(-1.f, -1.f); + gGL.multMatrix((F32*) volume->getRelativeXform().mMatrix); + const LLVolumeFace& vol_face = rigged->getVolumeFace(getTEOffset()); + LLVertexBuffer::unbind(); + glVertexPointer(3, GL_FLOAT, 16, vol_face.mPositions); + if (vol_face.mTexCoords) + { + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(2, GL_FLOAT, 8, vol_face.mTexCoords); + } + gGL.syncMatrices(); + glDrawElements(GL_TRIANGLES, vol_face.mNumIndices, GL_UNSIGNED_SHORT, vol_face.mIndices); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + } + } +#endif + } + else + { + // cheaters sometimes prosper... + // + mVertexBuffer->setBuffer(); + mVertexBuffer->draw(LLRender::TRIANGLES, mIndicesCount, mIndicesIndex); + } + + gGL.popMatrix(); + } +} + + +void renderFace(LLDrawable* drawable, LLFace *face) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_FACE + + LLVOVolume* vobj = drawable->getVOVolume(); + if (vobj) + { + LLVolume* volume = NULL; + + if (drawable->isState(LLDrawable::RIGGED)) + { + volume = vobj->getRiggedVolume(); + } + else + { + volume = vobj->getVolume(); + } + + if (volume) + { + const LLVolumeFace& vol_face = volume->getVolumeFace(face->getTEOffset()); + LLVertexBuffer::drawElements(LLRender::TRIANGLES, vol_face.mPositions, NULL, vol_face.mNumIndices, vol_face.mIndices); + } + } +} + +void LLFace::renderOneWireframe(const LLColor4 &color, F32 fogCfx, bool wireframe_selection, bool bRenderHiddenSelections, bool shader) +{ + if (bRenderHiddenSelections) + { + gGL.blendFunc(LLRender::BF_SOURCE_COLOR, LLRender::BF_ONE); + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE, GL_GEQUAL); + if (shader) + { + gGL.diffuseColor4f(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], 0.4f); + renderFace(mDrawablep, this); + } + else + { + gGL.flush(); + { + gGL.diffuseColor4f(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], 0.4f); + renderFace(mDrawablep, this); + } + } + } + + gGL.flush(); + gGL.setSceneBlendType(LLRender::BT_ALPHA); + + gGL.diffuseColor4f(color.mV[VRED] * 2, color.mV[VGREEN] * 2, color.mV[VBLUE] * 2, color.mV[VALPHA]); + + { + LLGLDisable depth(wireframe_selection ? 0 : GL_BLEND); + //LLGLEnable stencil(wireframe_selection ? 0 : GL_STENCIL_TEST); + + if (!wireframe_selection) + { //modify wireframe into outline selection mode + glStencilFunc(GL_NOTEQUAL, 2, 0xffff); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + } + + LLGLEnable offset(GL_POLYGON_OFFSET_LINE); + glPolygonOffset(3.f, 3.f); + glLineWidth(5.f); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + renderFace(mDrawablep, this); + } +} + +void LLFace::setDrawInfo(LLDrawInfo* draw_info) +{ + mDrawInfo = draw_info; +} + +void LLFace::printDebugInfo() const +{ + LLFacePool *poolp = getPool(); + LL_INFOS() << "Object: " << getViewerObject()->mID << LL_ENDL; + if (getDrawable()) + { + LL_INFOS() << "Type: " << LLPrimitive::pCodeToString(getDrawable()->getVObj()->getPCode()) << LL_ENDL; + } + if (getTexture()) + { + LL_INFOS() << "Texture: " << getTexture() << " Comps: " << (U32)getTexture()->getComponents() << LL_ENDL; + } + else + { + LL_INFOS() << "No texture: " << LL_ENDL; + } + + LL_INFOS() << "Face: " << this << LL_ENDL; + LL_INFOS() << "State: " << getState() << LL_ENDL; + LL_INFOS() << "Geom Index Data:" << LL_ENDL; + LL_INFOS() << "--------------------" << LL_ENDL; + LL_INFOS() << "GI: " << mGeomIndex << " Count:" << mGeomCount << LL_ENDL; + LL_INFOS() << "Face Index Data:" << LL_ENDL; + LL_INFOS() << "--------------------" << LL_ENDL; + LL_INFOS() << "II: " << mIndicesIndex << " Count:" << mIndicesCount << LL_ENDL; + LL_INFOS() << LL_ENDL; + + if (poolp) + { + poolp->printDebugInfo(); + + S32 pool_references = 0; + for (std::vector::iterator iter = poolp->mReferences.begin(); + iter != poolp->mReferences.end(); iter++) + { + LLFace *facep = *iter; + if (facep == this) + { + LL_INFOS() << "Pool reference: " << pool_references << LL_ENDL; + pool_references++; + } + } + + if (pool_references != 1) + { + LL_INFOS() << "Incorrect number of pool references!" << LL_ENDL; + } + } + +#if 0 + LL_INFOS() << "Indices:" << LL_ENDL; + LL_INFOS() << "--------------------" << LL_ENDL; + + const U32 *indicesp = getRawIndices(); + S32 indices_count = getIndicesCount(); + S32 geom_start = getGeomStart(); + + for (S32 i = 0; i < indices_count; i++) + { + LL_INFOS() << i << ":" << indicesp[i] << ":" << (S32)(indicesp[i] - geom_start) << LL_ENDL; + } + LL_INFOS() << LL_ENDL; + + LL_INFOS() << "Vertices:" << LL_ENDL; + LL_INFOS() << "--------------------" << LL_ENDL; + for (S32 i = 0; i < mGeomCount; i++) + { + LL_INFOS() << mGeomIndex + i << ":" << poolp->getVertex(mGeomIndex + i) << LL_ENDL; + } + LL_INFOS() << LL_ENDL; +#endif +} + +// Transform the texture coordinates for this face. +static void xform(LLVector2 &tex_coord, F32 cosAng, F32 sinAng, F32 offS, F32 offT, F32 magS, F32 magT) +{ + // New, good way + F32 s = tex_coord.mV[0]; + F32 t = tex_coord.mV[1]; + + // Texture transforms are done about the center of the face. + s -= 0.5; + t -= 0.5; + + // Handle rotation + F32 temp = s; + s = s * cosAng + t * sinAng; + t = -temp * sinAng + t * cosAng; + + // Then scale + s *= magS; + t *= magT; + + // Then offset + s += offS + 0.5f; + t += offT + 0.5f; + + tex_coord.mV[0] = s; + tex_coord.mV[1] = t; +} + +// Transform the texture coordinates for this face. +static void xform4a(LLVector4a &tex_coord, const LLVector4a& trans, const LLVector4Logical& mask, const LLVector4a& rot0, const LLVector4a& rot1, const LLVector4a& offset, const LLVector4a& scale) +{ + //tex coord is two coords, + LLVector4a st; + + // Texture transforms are done about the center of the face. + st.setAdd(tex_coord, trans); + + // Handle rotation + LLVector4a rot_st; + + // + LLVector4a s0; + s0.splat(st, 0); + LLVector4a s1; + s1.splat(st, 2); + LLVector4a ss; + ss.setSelectWithMask(mask, s1, s0); + + LLVector4a a; + a.setMul(rot0, ss); + + // + LLVector4a t0; + t0.splat(st, 1); + LLVector4a t1; + t1.splat(st, 3); + LLVector4a tt; + tt.setSelectWithMask(mask, t1, t0); + + LLVector4a b; + b.setMul(rot1, tt); + + st.setAdd(a,b); + + // Then scale + st.mul(scale); + + // Then offset + tex_coord.setAdd(st, offset); +} + + +bool less_than_max_mag(const LLVector4a& vec) +{ + LLVector4a MAX_MAG; + MAX_MAG.splat(1024.f*1024.f); + + LLVector4a val; + val.setAbs(vec); + + S32 lt = val.lessThan(MAX_MAG).getGatheredBits() & 0x7; + + return lt == 0x7; +} + +bool LLFace::genVolumeBBoxes(const LLVolume &volume, S32 f, + const LLMatrix4& mat_vert_in, bool global_volume) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_FACE + + //get bounding box + if (mDrawablep->isState(LLDrawable::REBUILD_VOLUME | LLDrawable::REBUILD_POSITION | LLDrawable::REBUILD_RIGGED)) + { + if (f >= volume.getNumVolumeFaces()) + { + LL_WARNS() << "Generating bounding box for invalid face index!" << LL_ENDL; + f = 0; + } + + const LLVolumeFace &face = volume.getVolumeFace(f); + + LL_DEBUGS("RiggedBox") << "updating extents for face " << f + << " starting extents " << mExtents[0] << ", " << mExtents[1] + << " starting vf extents " << face.mExtents[0] << ", " << face.mExtents[1] + << " num verts " << face.mNumVertices << LL_ENDL; + + // MAINT-8264 - stray vertices, especially in low LODs, cause bounding box errors. + if (face.mNumVertices < 3) + { + LL_DEBUGS("RiggedBox") << "skipping face " << f << ", bad num vertices " + << face.mNumVertices << " " << face.mNumIndices << " " << face.mWeights << LL_ENDL; + return false; + } + + //VECTORIZE THIS + LLMatrix4a mat_vert; + mat_vert.loadu(mat_vert_in); + LLVector4a new_extents[2]; + + llassert(less_than_max_mag(face.mExtents[0])); + llassert(less_than_max_mag(face.mExtents[1])); + + matMulBoundBox(mat_vert, face.mExtents, mExtents); + + LL_DEBUGS("RiggedBox") << "updated extents for face " << f + << " bbox gave extents " << mExtents[0] << ", " << mExtents[1] << LL_ENDL; + + if (!mDrawablep->isActive()) + { // Shift position for region + LLVector4a offset; + offset.load3(mDrawablep->getRegion()->getOriginAgent().mV); + mExtents[0].add(offset); + mExtents[1].add(offset); + LL_DEBUGS("RiggedBox") << "updating extents for face " << f + << " not active, added offset " << offset << LL_ENDL; + } + + LL_DEBUGS("RiggedBox") << "updated extents for face " << f + << " to " << mExtents[0] << ", " << mExtents[1] << LL_ENDL; + LLVector4a t; + t.setAdd(mExtents[0],mExtents[1]); + t.mul(0.5f); + + mCenterLocal.set(t.getF32ptr()); + + t.setSub(mExtents[1],mExtents[0]); + mBoundingSphereRadius = t.getLength3().getF32()*0.5f; + + updateCenterAgent(); + } + + return true; +} + + + +// convert surface coordinates to texture coordinates, based on +// the values in the texture entry. probably should be +// integrated with getGeometryVolume() for its texture coordinate +// generation - but i'll leave that to someone more familiar +// with the implications. +LLVector2 LLFace::surfaceToTexture(LLVector2 surface_coord, const LLVector4a& position, const LLVector4a& normal) +{ + LLVector2 tc = surface_coord; + + const LLTextureEntry *tep = getTextureEntry(); + + if (tep == NULL) + { + // can't do much without the texture entry + return surface_coord; + } + + //VECTORIZE THIS + // see if we have a non-default mapping + U8 texgen = getTextureEntry()->getTexGen(); + if (texgen != LLTextureEntry::TEX_GEN_DEFAULT) + { + LLVector4a& center = *(mDrawablep->getVOVolume()->getVolume()->getVolumeFace(mTEOffset).mCenter); + + LLVector4a volume_position; + LLVector3 v_position(position.getF32ptr()); + + volume_position.load3(mDrawablep->getVOVolume()->agentPositionToVolume(v_position).mV); + + if (!mDrawablep->getVOVolume()->isVolumeGlobal()) + { + LLVector4a scale; + scale.load3(mVObjp->getScale().mV); + volume_position.mul(scale); + } + + LLVector4a volume_normal; + LLVector3 v_normal(normal.getF32ptr()); + volume_normal.load3(mDrawablep->getVOVolume()->agentDirectionToVolume(v_normal).mV); + volume_normal.normalize3fast(); + + if (texgen == LLTextureEntry::TEX_GEN_PLANAR) + { + planarProjection(tc, volume_normal, center, volume_position); + } + } + + if (mTextureMatrix) // if we have a texture matrix, use it + { + LLVector3 tc3(tc); + tc3 = tc3 * *mTextureMatrix; + tc = LLVector2(tc3); + } + + else // otherwise use the texture entry parameters + { + xform(tc, cos(tep->getRotation()), sin(tep->getRotation()), + tep->mOffsetS, tep->mOffsetT, tep->mScaleS, tep->mScaleT); + } + + + return tc; +} + +// Returns scale compared to default texgen, and face orientation as calculated +// by planarProjection(). This is needed to match planar texgen parameters. +void LLFace::getPlanarProjectedParams(LLQuaternion* face_rot, LLVector3* face_pos, F32* scale) const +{ + const LLMatrix4& vol_mat = getWorldMatrix(); + const LLVolumeFace& vf = getViewerObject()->getVolume()->getVolumeFace(mTEOffset); + if (! (vf.mNormals && vf.mTangents)) + { + return; + } + const LLVector4a& normal4a = *vf.mNormals; + const LLVector4a& tangent = *vf.mTangents; + + LLVector4a binormal4a; + binormal4a.setCross3(normal4a, tangent); + binormal4a.mul(tangent.getF32ptr()[3]); + + LLVector2 projected_binormal; + planarProjection(projected_binormal, normal4a, *vf.mCenter, binormal4a); + projected_binormal -= LLVector2(0.5f, 0.5f); // this normally happens in xform() + *scale = projected_binormal.length(); + // rotate binormal to match what planarProjection() thinks it is, + // then find rotation from that: + projected_binormal.normalize(); + F32 ang = acos(projected_binormal.mV[VY]); + ang = (projected_binormal.mV[VX] < 0.f) ? -ang : ang; + + //VECTORIZE THIS + LLVector3 binormal(binormal4a.getF32ptr()); + LLVector3 normal(normal4a.getF32ptr()); + binormal.rotVec(ang, normal); + LLQuaternion local_rot( binormal % normal, binormal, normal ); + *face_rot = local_rot * vol_mat.quaternion(); + *face_pos = vol_mat.getTranslation(); +} + +// Returns the necessary texture transform to align this face's TE to align_to's TE +bool LLFace::calcAlignedPlanarTE(const LLFace* align_to, LLVector2* res_st_offset, + LLVector2* res_st_scale, F32* res_st_rot, LLRender::eTexIndex map) const +{ + if (!align_to) + { + return false; + } + const LLTextureEntry *orig_tep = align_to->getTextureEntry(); + if ((orig_tep->getTexGen() != LLTextureEntry::TEX_GEN_PLANAR) || + (getTextureEntry()->getTexGen() != LLTextureEntry::TEX_GEN_PLANAR)) + { + return false; + } + + F32 map_rot = 0.f, map_scaleS = 0.f, map_scaleT = 0.f, map_offsS = 0.f, map_offsT = 0.f; + + LLMaterial* mat = orig_tep->getMaterialParams(); + if (!mat && map != LLRender::DIFFUSE_MAP) + { + LL_WARNS_ONCE("llface") << "Face is set to use specular or normal map but has no material, defaulting to diffuse" << LL_ENDL; + map = LLRender::DIFFUSE_MAP; + } + + switch (map) + { + case LLRender::DIFFUSE_MAP: + map_rot = orig_tep->getRotation(); + map_scaleS = orig_tep->mScaleS; + map_scaleT = orig_tep->mScaleT; + map_offsS = orig_tep->mOffsetS; + map_offsT = orig_tep->mOffsetT; + break; + case LLRender::NORMAL_MAP: + if (mat->getNormalID().isNull()) + { + return false; + } + map_rot = mat->getNormalRotation(); + map_scaleS = mat->getNormalRepeatX(); + map_scaleT = mat->getNormalRepeatY(); + map_offsS = mat->getNormalOffsetX(); + map_offsT = mat->getNormalOffsetY(); + break; + case LLRender::SPECULAR_MAP: + if (mat->getSpecularID().isNull()) + { + return false; + } + map_rot = mat->getSpecularRotation(); + map_scaleS = mat->getSpecularRepeatX(); + map_scaleT = mat->getSpecularRepeatY(); + map_offsS = mat->getSpecularOffsetX(); + map_offsT = mat->getSpecularOffsetY(); + break; + default: /*make compiler happy*/ + break; + } + + LLVector3 orig_pos, this_pos; + LLQuaternion orig_face_rot, this_face_rot; + F32 orig_proj_scale, this_proj_scale; + align_to->getPlanarProjectedParams(&orig_face_rot, &orig_pos, &orig_proj_scale); + getPlanarProjectedParams(&this_face_rot, &this_pos, &this_proj_scale); + + // The rotation of "this face's" texture: + LLQuaternion orig_st_rot = LLQuaternion(map_rot, LLVector3::z_axis) * orig_face_rot; + LLQuaternion this_st_rot = orig_st_rot * ~this_face_rot; + F32 x_ang, y_ang, z_ang; + this_st_rot.getEulerAngles(&x_ang, &y_ang, &z_ang); + *res_st_rot = z_ang; + + // Offset and scale of "this face's" texture: + LLVector3 centers_dist = (this_pos - orig_pos) * ~orig_st_rot; + LLVector3 st_scale(map_scaleS, map_scaleT, 1.f); + st_scale *= orig_proj_scale; + centers_dist.scaleVec(st_scale); + LLVector2 orig_st_offset(map_offsS, map_offsT); + + *res_st_offset = orig_st_offset + (LLVector2)centers_dist; + res_st_offset->mV[VX] -= (S32)res_st_offset->mV[VX]; + res_st_offset->mV[VY] -= (S32)res_st_offset->mV[VY]; + + st_scale /= this_proj_scale; + *res_st_scale = (LLVector2)st_scale; + return true; +} + +void LLFace::updateRebuildFlags() +{ + if (mDrawablep->isState(LLDrawable::REBUILD_VOLUME)) + { //this rebuild is zero overhead (direct consequence of some change that affects this face) + mLastUpdateTime = gFrameTimeSeconds; + } + else + { //this rebuild is overhead (side effect of some change that does not affect this face) + mLastMoveTime = gFrameTimeSeconds; + } +} + + +bool LLFace::canRenderAsMask() +{ + const LLTextureEntry* te = getTextureEntry(); + if( !te || !getViewerObject() || !getTexture() ) + { + return false; + } + + if (te->getGLTFRenderMaterial()) + { + return false; + } + + if (LLPipeline::sNoAlpha) + { + return true; + } + + if (isState(LLFace::RIGGED)) + { // never auto alpha-mask rigged faces + return false; + } + + + LLMaterial* mat = te->getMaterialParams(); + if (mat && mat->getDiffuseAlphaMode() == LLMaterial::DIFFUSE_ALPHA_MODE_BLEND) + { + return false; + } + + if ((te->getColor().mV[3] == 1.0f) && // can't treat as mask if we have face alpha + (te->getGlow() == 0.f) && // glowing masks are hard to implement - don't mask + getTexture()->getIsAlphaMask()) // texture actually qualifies for masking (lazily recalculated but expensive) + { + if (getViewerObject()->isHUDAttachment() || te->getFullbright()) + { //hud attachments and fullbright objects are NOT subject to the deferred rendering pipe + return LLPipeline::sAutoMaskAlphaNonDeferred; + } + else + { + return LLPipeline::sAutoMaskAlphaDeferred; + } + } + + return false; +} + +//helper function for pushing primitives for transform shaders and cleaning up +//uninitialized data on the tail, plus tracking number of expected primitives +void push_for_transform(LLVertexBuffer* buff, U32 source_count, U32 dest_count) +{ + if (source_count > 0 && dest_count >= source_count) //protect against possible U32 wrapping + { + //push source primitives + buff->drawArrays(LLRender::POINTS, 0, source_count); + U32 tail = dest_count-source_count; + for (U32 i = 0; i < tail; ++i) + { //copy last source primitive into each element in tail + buff->drawArrays(LLRender::POINTS, source_count-1, 1); + } + gPipeline.mTransformFeedbackPrimitives += dest_count; + } +} + +bool LLFace::getGeometryVolume(const LLVolume& volume, + S32 face_index, + const LLMatrix4& mat_vert_in, + const LLMatrix3& mat_norm_in, + U16 index_offset, + bool force_rebuild, + bool no_debug_assert) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_FACE; + llassert(verify()); + + if (face_index < 0 || face_index >= volume.getNumVolumeFaces()) + { + if (gDebugGL) + { + LL_WARNS() << "Face index is out of bounds!" << LL_ENDL; + LL_WARNS() << "Attempt get volume face out of range!" + " Total Faces: " << volume.getNumVolumeFaces() << + " Attempt get access to: " << face_index << LL_ENDL; + llassert(no_debug_assert); + } + return false; + } + + bool rigged = isState(RIGGED); + + const LLVolumeFace &vf = volume.getVolumeFace(face_index); + S32 num_vertices = (S32)vf.mNumVertices; + S32 num_indices = (S32) vf.mNumIndices; + + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_OCTREE)) + { + updateRebuildFlags(); + } + + if (mVertexBuffer.notNull()) + { + if (num_indices + (S32) mIndicesIndex > mVertexBuffer->getNumIndices()) + { + if (gDebugGL) + { + LL_WARNS() << "Index buffer overflow!" << LL_ENDL; + LL_WARNS() << "Indices Count: " << mIndicesCount + << " VF Num Indices: " << num_indices + << " Indices Index: " << mIndicesIndex + << " VB Num Indices: " << mVertexBuffer->getNumIndices() << LL_ENDL; + LL_WARNS() << " Face Index: " << face_index + << " Pool Type: " << mPoolType << LL_ENDL; + llassert(no_debug_assert); + } + return false; + } + + if (num_vertices + mGeomIndex > mVertexBuffer->getNumVerts()) + { + if (gDebugGL) + { + LL_WARNS() << "Vertex buffer overflow!" << LL_ENDL; + llassert(no_debug_assert); + } + return false; + } + } + + LLStrider vert; + LLStrider tex_coords0; + LLStrider tex_coords1; + LLStrider tex_coords2; + LLStrider norm; + LLStrider colors; + LLStrider tangent; + LLStrider indicesp; + LLStrider wght; + + bool full_rebuild = force_rebuild || mDrawablep->isState(LLDrawable::REBUILD_VOLUME); + + bool global_volume = mDrawablep->getVOVolume()->isVolumeGlobal(); + LLVector3 scale; + if (global_volume) + { + scale.setVec(1,1,1); + } + else + { + scale = mVObjp->getScale(); + } + + bool rebuild_pos = full_rebuild || mDrawablep->isState(LLDrawable::REBUILD_POSITION); + bool rebuild_color = full_rebuild || mDrawablep->isState(LLDrawable::REBUILD_COLOR); + bool rebuild_emissive = rebuild_color && mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_EMISSIVE); + bool rebuild_tcoord = full_rebuild || mDrawablep->isState(LLDrawable::REBUILD_TCOORD); + bool rebuild_normal = rebuild_pos && mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_NORMAL); + bool rebuild_tangent = rebuild_pos && mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_TANGENT); + bool rebuild_weights = rebuild_pos && mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_WEIGHT4); + + const LLTextureEntry *tep = mVObjp->getTE(face_index); + const U8 bump_code = tep ? tep->getBumpmap() : 0; + + bool is_static = mDrawablep->isStatic(); + bool is_global = is_static; + + LLVector3 center_sum(0.f, 0.f, 0.f); + + if (is_global) + { + setState(GLOBAL); + } + else + { + clearState(GLOBAL); + } + + LLColor4U color = tep->getColor(); + + if (tep->getGLTFRenderMaterial()) + { + color = tep->getGLTFRenderMaterial()->mBaseColor; + } + + if (rebuild_color) + { //decide if shiny goes in alpha channel of color + if (tep && + !isInAlphaPool()) // <--- alpha channel MUST contain transparency, not shiny + { + LLMaterial* mat = tep->getMaterialParams().get(); + + bool shiny_in_alpha = false; + + //store shiny in alpha if we don't have a specular map + if (!mat || mat->getSpecularID().isNull()) + { + shiny_in_alpha = true; + } + + if (shiny_in_alpha) + { + static const GLfloat SHININESS_TO_ALPHA[4] = + { + 0.0000f, + 0.25f, + 0.5f, + 0.75f + }; + + llassert(tep->getShiny() <= 3); + color.mV[3] = U8 (SHININESS_TO_ALPHA[tep->getShiny()] * 255); + } + } + } + + // INDICES + if (full_rebuild) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("getGeometryVolume - indices"); + mVertexBuffer->getIndexStrider(indicesp, mIndicesIndex, mIndicesCount); + + volatile __m128i* dst = (__m128i*) indicesp.get(); + __m128i* src = (__m128i*) vf.mIndices; + __m128i offset = _mm_set1_epi16(index_offset); + + S32 end = num_indices/8; + + for (S32 i = 0; i < end; i++) + { + __m128i res = _mm_add_epi16(src[i], offset); + _mm_storeu_si128((__m128i*) dst++, res); + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("getGeometryVolume - indices tail"); + U16* idx = (U16*) dst; + + for (S32 i = end*8; i < num_indices; ++i) + { + *idx++ = vf.mIndices[i]+index_offset; + } + } + } + + + LLMaterial* mat = tep->getMaterialParams().get(); + LLGLTFMaterial* gltf_mat = tep->getGLTFRenderMaterial(); + + F32 r = 0, os = 0, ot = 0, ms = 0, mt = 0, cos_ang = 0, sin_ang = 0; + + constexpr S32 XFORM_NONE = 0; + constexpr S32 XFORM_BLINNPHONG_COLOR = 1; + constexpr S32 XFORM_BLINNPHONG_NORMAL = 1 << 1; + constexpr S32 XFORM_BLINNPHONG_SPECULAR = 1 << 2; + + S32 xforms = XFORM_NONE; + // For GLTF, transforms will be applied later + if (rebuild_tcoord && tep && !gltf_mat) + { + r = tep->getRotation(); + os = tep->mOffsetS; + ot = tep->mOffsetT; + ms = tep->mScaleS; + mt = tep->mScaleT; + cos_ang = cos(r); + sin_ang = sin(r); + + if (cos_ang != 1.f || + sin_ang != 0.f || + os != 0.f || + ot != 0.f || + ms != 1.f || + mt != 1.f) + { + xforms |= XFORM_BLINNPHONG_COLOR; + } + if (mat) + { + F32 r_norm = 0, os_norm = 0, ot_norm = 0, ms_norm = 0, mt_norm = 0, cos_ang_norm = 0, sin_ang_norm = 0; + mat->getNormalOffset(os_norm, ot_norm); + mat->getNormalRepeat(ms_norm, mt_norm); + r_norm = mat->getNormalRotation(); + cos_ang_norm = cos(r_norm); + sin_ang_norm = sin(r_norm); + if (cos_ang_norm != 1.f || + sin_ang_norm != 0.f || + os_norm != 0.f || + ot_norm != 0.f || + ms_norm != 1.f || + mt_norm != 1.f) + { + xforms |= XFORM_BLINNPHONG_NORMAL; + } + } + if (mat) + { + F32 r_spec = 0, os_spec = 0, ot_spec = 0, ms_spec = 0, mt_spec = 0, cos_ang_spec = 0, sin_ang_spec = 0; + mat->getSpecularOffset(os_spec, ot_spec); + mat->getSpecularRepeat(ms_spec, mt_spec); + r_spec = mat->getSpecularRotation(); + cos_ang_spec = cos(r_spec); + sin_ang_spec = sin(r_spec); + if (cos_ang_spec != 1.f || + sin_ang_spec != 0.f || + os_spec != 0.f || + ot_spec != 0.f || + ms_spec != 1.f || + mt_spec != 1.f) + { + xforms |= XFORM_BLINNPHONG_SPECULAR; + } + } + } + + const LLMeshSkinInfo* skin = nullptr; + LLMatrix4a mat_vert; + LLMatrix4a mat_normal; + + // prepare mat_vert + if (rebuild_pos) + { + if (rigged) + { //override with bind shape matrix if rigged + skin = mSkinInfo; + mat_vert = skin->mBindShapeMatrix; + } + else + { + mat_vert.loadu(mat_vert_in); + } + } + + if (rebuild_normal || rebuild_tangent) + { //override mat_normal with inverse of skin->mBindShapeMatrix + LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("getGeometryVolume - norm mat override"); + if (rigged) + { + if (skin == nullptr) + { + skin = mSkinInfo; + } + + //TODO -- cache this (check profile marker above)? + glh::matrix4f m((F32*) skin->mBindShapeMatrix.getF32ptr()); + m = m.inverse().transpose(); + mat_normal.loadu(m.m); + } + else + { + mat_normal.loadu(mat_norm_in); + } + } + + { + //if it's not fullbright and has no normals, bake sunlight based on face normal + //bool bake_sunlight = !getTextureEntry()->getFullbright() && + // !mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_NORMAL); + + if (rebuild_tcoord) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("getGeometryVolume - tcoord"); + + //bump setup + LLVector4a binormal_dir( -sin_ang, cos_ang, 0.f ); + LLVector4a bump_s_primary_light_ray(0.f, 0.f, 0.f); + LLVector4a bump_t_primary_light_ray(0.f, 0.f, 0.f); + + LLQuaternion bump_quat; + if (mDrawablep->isActive()) + { + bump_quat = LLQuaternion(mDrawablep->getRenderMatrix()); + } + + if (bump_code) + { + mVObjp->getVolume()->genTangents(face_index); + F32 offset_multiple; + switch( bump_code ) + { + case BE_NO_BUMP: + offset_multiple = 0.f; + break; + case BE_BRIGHTNESS: + case BE_DARKNESS: + if( mTexture[LLRender::DIFFUSE_MAP].notNull() && mTexture[LLRender::DIFFUSE_MAP]->hasGLTexture()) + { + // Offset by approximately one texel + S32 cur_discard = mTexture[LLRender::DIFFUSE_MAP]->getDiscardLevel(); + S32 max_size = llmax( mTexture[LLRender::DIFFUSE_MAP]->getWidth(), mTexture[LLRender::DIFFUSE_MAP]->getHeight() ); + max_size <<= cur_discard; + const F32 ARTIFICIAL_OFFSET = 2.f; + offset_multiple = ARTIFICIAL_OFFSET / (F32)max_size; + } + else + { + offset_multiple = 1.f/256; + } + break; + + default: // Standard bumpmap textures. Assumed to be 256x256 + offset_multiple = 1.f / 256; + break; + } + + F32 s_scale = 1.f; + F32 t_scale = 1.f; + if( tep ) + { + tep->getScale( &s_scale, &t_scale ); + } + // Use the nudged south when coming from above sun angle, such + // that emboss mapping always shows up on the upward faces of cubes when + // it's noon (since a lot of builders build with the sun forced to noon). + LLVector3 sun_ray = gSky.mVOSkyp->mBumpSunDir; + LLVector3 moon_ray = gSky.mVOSkyp->getMoon().getDirection(); + LLVector3& primary_light_ray = (sun_ray.mV[VZ] > 0) ? sun_ray : moon_ray; + + bump_s_primary_light_ray.load3((offset_multiple * s_scale * primary_light_ray).mV); + bump_t_primary_light_ray.load3((offset_multiple * t_scale * primary_light_ray).mV); + } + + U8 texgen = getTextureEntry()->getTexGen(); + if (rebuild_tcoord && texgen != LLTextureEntry::TEX_GEN_DEFAULT) + { //planar texgen needs binormals + mVObjp->getVolume()->genTangents(face_index); + } + + U8 tex_mode = 0; + + bool tex_anim = false; + + LLVOVolume* vobj = (LLVOVolume*) (LLViewerObject*) mVObjp; + tex_mode = vobj->mTexAnimMode; + + if (vobj->mTextureAnimp) + { //texture animation is in play, override specular and normal map tex coords with diffuse texcoords + tex_anim = true; + } + + if (isState(TEXTURE_ANIM)) + { + if (!tex_mode) + { + clearState(TEXTURE_ANIM); + } + else + { + os = ot = 0.f; + r = 0.f; + cos_ang = 1.f; + sin_ang = 0.f; + ms = mt = 1.f; + + xforms = XFORM_NONE; + } + + if (getVirtualSize() >= MIN_TEX_ANIM_SIZE) // || isState(LLFace::RIGGED)) + { //don't override texture transform during tc bake + tex_mode = 0; + } + } + + LLVector4a scalea; + scalea.load3(scale.mV); + + bool do_bump = bump_code && mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_TEXCOORD1); + + if ((mat || gltf_mat) && !do_bump) + { + do_bump = mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_TEXCOORD1) + || mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_TEXCOORD2); + } + + // For GLTF materials: Transforms will be applied later + bool do_tex_mat = tex_mode && mTextureMatrix && !gltf_mat; + + if (!do_bump) + { //not bump mapped, might be able to do a cheap update + mVertexBuffer->getTexCoord0Strider(tex_coords0, mGeomIndex, mGeomCount); + + if (texgen != LLTextureEntry::TEX_GEN_PLANAR) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("getGeometryVolume - texgen"); + if (!do_tex_mat) + { + if (xforms == XFORM_NONE) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("ggv - texgen 1"); + S32 tc_size = (num_vertices*2*sizeof(F32)+0xF) & ~0xF; + LLVector4a::memcpyNonAliased16((F32*) tex_coords0.get(), (F32*) vf.mTexCoords, tc_size); + } + else + { + LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("ggv - texgen 2"); + F32* dst = (F32*) tex_coords0.get(); + LLVector4a* src = (LLVector4a*) vf.mTexCoords; + + LLVector4a trans; + trans.splat(-0.5f); + + LLVector4a rot0; + rot0.set(cos_ang, -sin_ang, cos_ang, -sin_ang); + + LLVector4a rot1; + rot1.set(sin_ang, cos_ang, sin_ang, cos_ang); + + LLVector4a scale; + scale.set(ms, mt, ms, mt); + + LLVector4a offset; + offset.set(os+0.5f, ot+0.5f, os+0.5f, ot+0.5f); + + LLVector4Logical mask; + mask.clear(); + mask.setElement<2>(); + mask.setElement<3>(); + + U32 count = num_vertices/2 + num_vertices%2; + + for (S32 i = 0; i < count; i++) + { + LLVector4a res = *src++; + xform4a(res, trans, mask, rot0, rot1, offset, scale); + res.store4a(dst); + dst += 4; + } + } + } + else + { //do tex mat, no texgen, no bump + for (S32 i = 0; i < num_vertices; i++) + { + LLVector2 tc(vf.mTexCoords[i]); + + LLVector3 tmp(tc.mV[0], tc.mV[1], 0.f); + tmp = tmp * *mTextureMatrix; + tc.mV[0] = tmp.mV[0]; + tc.mV[1] = tmp.mV[1]; + *tex_coords0++ = tc; + } + } + } + else + { //no bump, tex gen planar + LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("getGeometryVolume - texgen planar"); + if (do_tex_mat) + { + for (S32 i = 0; i < num_vertices; i++) + { + LLVector2 tc(vf.mTexCoords[i]); + LLVector4a& norm = vf.mNormals[i]; + LLVector4a& center = *(vf.mCenter); + LLVector4a vec = vf.mPositions[i]; + vec.mul(scalea); + planarProjection(tc, norm, center, vec); + + LLVector3 tmp(tc.mV[0], tc.mV[1], 0.f); + tmp = tmp * *mTextureMatrix; + tc.mV[0] = tmp.mV[0]; + tc.mV[1] = tmp.mV[1]; + + *tex_coords0++ = tc; + } + } + else if (xforms != XFORM_NONE) + { + for (S32 i = 0; i < num_vertices; i++) + { + LLVector2 tc(vf.mTexCoords[i]); + LLVector4a& norm = vf.mNormals[i]; + LLVector4a& center = *(vf.mCenter); + LLVector4a vec = vf.mPositions[i]; + vec.mul(scalea); + planarProjection(tc, norm, center, vec); + + xform(tc, cos_ang, sin_ang, os, ot, ms, mt); + + *tex_coords0++ = tc; + } + } + else + { + for (S32 i = 0; i < num_vertices; i++) + { + LLVector2 tc(vf.mTexCoords[i]); + LLVector4a& norm = vf.mNormals[i]; + LLVector4a& center = *(vf.mCenter); + LLVector4a vec = vf.mPositions[i]; + vec.mul(scalea); + planarProjection(tc, norm, center, vec); + + *tex_coords0++ = tc; + } + } + } + } + else + { //bump mapped or has material, just do the whole expensive loop + LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("getGeometryVolume - texgen default"); + + std::vector bump_tc; + + if (mat && !mat->getNormalID().isNull()) + { //writing out normal and specular texture coordinates, not bump offsets + do_bump = false; + } + + LLStrider dst; + + for (U32 ch = 0; ch < 3; ++ch) + { + S32 xform_channel = XFORM_NONE; + switch (ch) + { + case 0: + xform_channel = XFORM_BLINNPHONG_COLOR; + mVertexBuffer->getTexCoord0Strider(dst, mGeomIndex, mGeomCount); + break; + case 1: + xform_channel = XFORM_BLINNPHONG_NORMAL; + if (mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_TEXCOORD1)) + { + mVertexBuffer->getTexCoord1Strider(dst, mGeomIndex, mGeomCount); + if (mat && !tex_anim) + { + r = mat->getNormalRotation(); + mat->getNormalOffset(os, ot); + mat->getNormalRepeat(ms, mt); + + cos_ang = cos(r); + sin_ang = sin(r); + + } + } + else + { + continue; + } + break; + case 2: + xform_channel = XFORM_BLINNPHONG_SPECULAR; + if (mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_TEXCOORD2)) + { + mVertexBuffer->getTexCoord2Strider(dst, mGeomIndex, mGeomCount); + if (mat && !tex_anim) + { + r = mat->getSpecularRotation(); + mat->getSpecularOffset(os, ot); + mat->getSpecularRepeat(ms, mt); + + cos_ang = cos(r); + sin_ang = sin(r); + } + } + else + { + continue; + } + break; + } + const bool do_xform = (xforms & xform_channel) != XFORM_NONE; + + + for (S32 i = 0; i < num_vertices; i++) + { + LLVector2 tc(vf.mTexCoords[i]); + + LLVector4a& norm = vf.mNormals[i]; + + LLVector4a& center = *(vf.mCenter); + + if (texgen != LLTextureEntry::TEX_GEN_DEFAULT) + { + LLVector4a vec = vf.mPositions[i]; + + vec.mul(scalea); + + if (texgen == LLTextureEntry::TEX_GEN_PLANAR) + { + planarProjection(tc, norm, center, vec); + } + } + + if (tex_mode && mTextureMatrix) + { + LLVector3 tmp(tc.mV[0], tc.mV[1], 0.f); + tmp = tmp * *mTextureMatrix; + tc.mV[0] = tmp.mV[0]; + tc.mV[1] = tmp.mV[1]; + } + else if (do_xform) + { + xform(tc, cos_ang, sin_ang, os, ot, ms, mt); + } + + *dst++ = tc; + if (do_bump) + { + bump_tc.push_back(tc); + } + } + } + + if ((!mat && !gltf_mat) && do_bump) + { + mVertexBuffer->getTexCoord1Strider(tex_coords1, mGeomIndex, mGeomCount); + + mVObjp->getVolume()->genTangents(face_index); + + for (S32 i = 0; i < num_vertices; i++) + { + LLVector4a tangent = vf.mTangents[i]; + + LLVector4a binorm; + binorm.setCross3(vf.mNormals[i], tangent); + binorm.mul(tangent.getF32ptr()[3]); + + LLMatrix4a tangent_to_object; + tangent_to_object.setRows(tangent, binorm, vf.mNormals[i]); + LLVector4a t; + tangent_to_object.rotate(binormal_dir, t); + LLVector4a binormal; + mat_normal.rotate(t, binormal); + + //VECTORIZE THIS + if (mDrawablep->isActive()) + { + LLVector3 t; + t.set(binormal.getF32ptr()); + t *= bump_quat; + binormal.load3(t.mV); + } + + binormal.normalize3fast(); + + LLVector2 tc = bump_tc[i]; + tc += LLVector2( bump_s_primary_light_ray.dot3(tangent).getF32(), bump_t_primary_light_ray.dot3(binormal).getF32() ); + + *tex_coords1++ = tc; + } + } + } + } + + if (rebuild_pos) + { + LLVector4a* src = vf.mPositions; + + //_mm_prefetch((char*)src, _MM_HINT_T0); + + LLVector4a* end = src+num_vertices; + //LLVector4a* end_64 = end-4; + + llassert(num_vertices > 0); + + mVertexBuffer->getVertexStrider(vert, mGeomIndex, mGeomCount); + + + F32* dst = (F32*) vert.get(); + F32* end_f32 = dst+mGeomCount*4; + + //_mm_prefetch((char*)dst, _MM_HINT_NTA); + //_mm_prefetch((char*)src, _MM_HINT_NTA); + + //_mm_prefetch((char*)dst, _MM_HINT_NTA); + + + LLVector4a res0; //,res1,res2,res3; + + LLVector4a texIdx; + + S32 index = mTextureIndex < FACE_DO_NOT_BATCH_TEXTURES ? mTextureIndex : 0; + + F32 val = 0.f; + S32* vp = (S32*) &val; + *vp = index; + + llassert(index <= LLGLSLShader::sIndexedTextureChannels-1); + + LLVector4Logical mask; + mask.clear(); + mask.setElement<3>(); + + texIdx.set(0,0,0,val); + + LLVector4a tmp; + + + while (src < end) + { + mat_vert.affineTransform(*src++, res0); + tmp.setSelectWithMask(mask, texIdx, res0); + tmp.store4a((F32*) dst); + dst += 4; + } + + while (dst < end_f32) + { + res0.store4a((F32*) dst); + dst += 4; + } + } + + if (rebuild_normal) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("getGeometryVolume - normal"); + + mVertexBuffer->getNormalStrider(norm, mGeomIndex, mGeomCount); + F32* normals = (F32*) norm.get(); + LLVector4a* src = vf.mNormals; + LLVector4a* end = src+num_vertices; + + while (src < end) + { + LLVector4a normal; + mat_normal.rotate(*src++, normal); + normal.store4a(normals); + normals += 4; + } + } + + if (rebuild_tangent) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("getGeometryVolume - tangent"); + mVertexBuffer->getTangentStrider(tangent, mGeomIndex, mGeomCount); + F32* tangents = (F32*) tangent.get(); + + mVObjp->getVolume()->genTangents(face_index); + + LLVector4Logical mask; + mask.clear(); + mask.setElement<3>(); + + LLVector4a* src = vf.mTangents; + LLVector4a* end = vf.mTangents +num_vertices; + + while (src < end) + { + LLVector4a tangent_out; + mat_normal.rotate(*src, tangent_out); + tangent_out.setSelectWithMask(mask, *src, tangent_out); + tangent_out.store4a(tangents); + + src++; + tangents += 4; + } + } + + if (rebuild_weights && vf.mWeights) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("getGeometryVolume - weight"); + mVertexBuffer->getWeight4Strider(wght, mGeomIndex, mGeomCount); + F32* weights = (F32*) wght.get(); + LLVector4a::memcpyNonAliased16(weights, (F32*) vf.mWeights, num_vertices*4*sizeof(F32)); + } + + if (rebuild_color && mVertexBuffer->hasDataType(LLVertexBuffer::TYPE_COLOR) ) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("getGeometryVolume - color"); + mVertexBuffer->getColorStrider(colors, mGeomIndex, mGeomCount); + + LLVector4a src; + + U32 vec[4]; + vec[0] = vec[1] = vec[2] = vec[3] = color.asRGBA(); + + src.loadua((F32*) vec); + + F32* dst = (F32*) colors.get(); + S32 num_vecs = num_vertices/4; + if (num_vertices%4 > 0) + { + ++num_vecs; + } + + for (S32 i = 0; i < num_vecs; i++) + { + src.store4a(dst); + dst += 4; + } + } + + if (rebuild_emissive) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_FACE("getGeometryVolume - emissive"); + LLStrider emissive; + mVertexBuffer->getEmissiveStrider(emissive, mGeomIndex, mGeomCount); + + U8 glow = (U8) llclamp((S32) (getTextureEntry()->getGlow()*255), 0, 255); + + LLVector4a src; + + + LLColor4U glow4u = LLColor4U(0,0,0,glow); + + U32 glow32 = glow4u.asRGBA(); + + U32 vec[4]; + vec[0] = vec[1] = vec[2] = vec[3] = glow32; + + src.loadua((F32*) vec); + + F32* dst = (F32*) emissive.get(); + S32 num_vecs = num_vertices/4; + if (num_vertices%4 > 0) + { + ++num_vecs; + } + + for (S32 i = 0; i < num_vecs; i++) + { + src.store4a(dst); + dst += 4; + } + } + } + + if (rebuild_tcoord) + { + mTexExtents[0].setVec(0,0); + mTexExtents[1].setVec(1,1); + xform(mTexExtents[0], cos_ang, sin_ang, os, ot, ms, mt); + xform(mTexExtents[1], cos_ang, sin_ang, os, ot, ms, mt); + + F32 es = vf.mTexCoordExtents[1].mV[0] - vf.mTexCoordExtents[0].mV[0] ; + F32 et = vf.mTexCoordExtents[1].mV[1] - vf.mTexCoordExtents[0].mV[1] ; + mTexExtents[0][0] *= es ; + mTexExtents[1][0] *= es ; + mTexExtents[0][1] *= et ; + mTexExtents[1][1] *= et ; + } + + + return true; +} + +void LLFace::renderIndexed() +{ + if (mVertexBuffer.notNull()) + { + mVertexBuffer->setBuffer(); + mVertexBuffer->drawRange(LLRender::TRIANGLES, getGeomIndex(), getGeomIndex() + getGeomCount()-1, getIndicesCount(), getIndicesStart()); + } +} + +//check if the face has a media +bool LLFace::hasMedia() const +{ + if(mHasMedia) + { + return true ; + } + if(mTexture[LLRender::DIFFUSE_MAP].notNull()) + { + return mTexture[LLRender::DIFFUSE_MAP]->hasParcelMedia() ; //if has a parcel media + } + + return false ; //no media. +} + +const F32 LEAST_IMPORTANCE = 0.05f ; +const F32 LEAST_IMPORTANCE_FOR_LARGE_IMAGE = 0.3f ; + +void LLFace::resetVirtualSize() +{ + setVirtualSize(0.f); + mImportanceToCamera = 0.f; +} + +F32 LLFace::getTextureVirtualSize() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + F32 radius; + F32 cos_angle_to_view_dir; + bool in_frustum = calcPixelArea(cos_angle_to_view_dir, radius); + + if (mPixelArea < F_ALMOST_ZERO || !in_frustum) + { + setVirtualSize(0.f) ; + return 0.f; + } + + //get area of circle in texture space + LLVector2 tdim = mTexExtents[1] - mTexExtents[0]; + F32 texel_area = (tdim * 0.5f).lengthSquared()*3.14159f; + if (texel_area <= 0) + { + // Probably animated, use default + texel_area = 1.f; + } + + F32 face_area; + if (mVObjp->isSculpted() && texel_area > 1.f) + { + //sculpts can break assumptions about texel area + face_area = mPixelArea; + } + else + { + //apply texel area to face area to get accurate ratio + //face_area /= llclamp(texel_area, 1.f/64.f, 16.f); + face_area = mPixelArea / llclamp(texel_area, 0.015625f, 128.f); + } + + face_area = LLFace::adjustPixelArea(mImportanceToCamera, face_area) ; + if(face_area > LLViewerTexture::sMinLargeImageSize) //if is large image, shrink face_area by considering the partial overlapping. + { + if(mImportanceToCamera > LEAST_IMPORTANCE_FOR_LARGE_IMAGE && mTexture[LLRender::DIFFUSE_MAP].notNull() && mTexture[LLRender::DIFFUSE_MAP]->isLargeImage()) + { + face_area *= adjustPartialOverlapPixelArea(cos_angle_to_view_dir, radius ); + } + } + + setVirtualSize(face_area) ; + + return face_area; +} + +bool LLFace::calcPixelArea(F32& cos_angle_to_view_dir, F32& radius) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_FACE; + + //VECTORIZE THIS + //get area of circle around face + + LLVector4a center; + LLVector4a size; + + + if (isState(LLFace::RIGGED)) + { + //override with avatar bounding box + LLVOAvatar* avatar = mVObjp->getAvatar(); + if (avatar && avatar->mDrawable) + { + center.load3(avatar->getPositionAgent().mV); + const LLVector4a* exts = avatar->mDrawable->getSpatialExtents(); + size.setSub(exts[1], exts[0]); + } + else + { + return false; + } + } + else + { + center.load3(getPositionAgent().mV); + size.setSub(mExtents[1], mExtents[0]); + } + size.mul(0.5f); + + LLViewerCamera* camera = LLViewerCamera::getInstance(); + + F32 size_squared = size.dot3(size).getF32(); + LLVector4a lookAt; + LLVector4a t; + t.load3(camera->getOrigin().mV); + lookAt.setSub(center, t); + + F32 dist = lookAt.getLength3().getF32(); + dist = llmax(dist-size.getLength3().getF32(), 0.001f); + //ramp down distance for nearby objects + if (dist < 16.f) + { + dist /= 16.f; + dist *= dist; + dist *= 16.f; + } + + lookAt.normalize3fast() ; + + //get area of circle around node + F32 app_angle = atanf((F32) sqrt(size_squared) / dist); + radius = app_angle*LLDrawable::sCurPixelAngle; + mPixelArea = radius*radius * 3.14159f; + LLVector4a x_axis; + x_axis.load3(camera->getXAxis().mV); + cos_angle_to_view_dir = lookAt.dot3(x_axis).getF32(); + + //if has media, check if the face is out of the view frustum. + if(hasMedia()) + { + if(!camera->AABBInFrustum(center, size)) + { + mImportanceToCamera = 0.f ; + return false ; + } + if(cos_angle_to_view_dir > camera->getCosHalfFov()) //the center is within the view frustum + { + cos_angle_to_view_dir = 1.0f ; + } + else + { + LLVector4a d; + d.setSub(lookAt, x_axis); + + if(dist * dist * d.dot3(d) < size_squared) + { + cos_angle_to_view_dir = 1.0f ; + } + } + } + + if(dist < mBoundingSphereRadius) //camera is very close + { + cos_angle_to_view_dir = 1.0f ; + mImportanceToCamera = 1.0f ; + } + else + { + mImportanceToCamera = LLFace::calcImportanceToCamera(cos_angle_to_view_dir, dist) ; + } + + return true ; +} + +//the projection of the face partially overlaps with the screen +F32 LLFace::adjustPartialOverlapPixelArea(F32 cos_angle_to_view_dir, F32 radius ) +{ + F32 screen_radius = (F32)llmax(gViewerWindow->getWindowWidthRaw(), gViewerWindow->getWindowHeightRaw()) ; + F32 center_angle = acosf(cos_angle_to_view_dir) ; + F32 d = center_angle * LLDrawable::sCurPixelAngle ; + + if(d + radius > screen_radius + 5.f) + { + //---------------------------------------------- + //calculate the intersection area of two circles + //F32 radius_square = radius * radius ; + //F32 d_square = d * d ; + //F32 screen_radius_square = screen_radius * screen_radius ; + //face_area = + // radius_square * acosf((d_square + radius_square - screen_radius_square)/(2 * d * radius)) + + // screen_radius_square * acosf((d_square + screen_radius_square - radius_square)/(2 * d * screen_radius)) - + // 0.5f * sqrtf((-d + radius + screen_radius) * (d + radius - screen_radius) * (d - radius + screen_radius) * (d + radius + screen_radius)) ; + //---------------------------------------------- + + //the above calculation is too expensive + //the below is a good estimation: bounding box of the bounding sphere: + F32 alpha = 0.5f * (radius + screen_radius - d) / radius ; + alpha = llclamp(alpha, 0.f, 1.f) ; + return alpha * alpha ; + } + return 1.0f ; +} + +const S8 FACE_IMPORTANCE_LEVEL = 4 ; +const F32 FACE_IMPORTANCE_TO_CAMERA_OVER_DISTANCE[FACE_IMPORTANCE_LEVEL][2] = //{distance, importance_weight} + {{16.1f, 1.0f}, {32.1f, 0.5f}, {48.1f, 0.2f}, {96.1f, 0.05f} } ; +const F32 FACE_IMPORTANCE_TO_CAMERA_OVER_ANGLE[FACE_IMPORTANCE_LEVEL][2] = //{cos(angle), importance_weight} + {{0.985f /*cos(10 degrees)*/, 1.0f}, {0.94f /*cos(20 degrees)*/, 0.8f}, {0.866f /*cos(30 degrees)*/, 0.64f}, {0.0f, 0.36f}} ; + +//static +F32 LLFace::calcImportanceToCamera(F32 cos_angle_to_view_dir, F32 dist) +{ + F32 importance = 0.f ; + + if(cos_angle_to_view_dir > LLViewerCamera::getInstance()->getCosHalfFov() && + dist < FACE_IMPORTANCE_TO_CAMERA_OVER_DISTANCE[FACE_IMPORTANCE_LEVEL - 1][0]) + { + LLViewerCamera* camera = LLViewerCamera::getInstance(); + F32 camera_moving_speed = camera->getAverageSpeed() ; + F32 camera_angular_speed = camera->getAverageAngularSpeed(); + + if(camera_moving_speed > 10.0f || camera_angular_speed > 1.0f) + { + //if camera moves or rotates too fast, ignore the importance factor + return 0.f ; + } + + S32 i = 0 ; + for(i = 0; i < FACE_IMPORTANCE_LEVEL && dist > FACE_IMPORTANCE_TO_CAMERA_OVER_DISTANCE[i][0]; ++i); + i = llmin(i, FACE_IMPORTANCE_LEVEL - 1) ; + F32 dist_factor = FACE_IMPORTANCE_TO_CAMERA_OVER_DISTANCE[i][1] ; + + for(i = 0; i < FACE_IMPORTANCE_LEVEL && cos_angle_to_view_dir < FACE_IMPORTANCE_TO_CAMERA_OVER_ANGLE[i][0] ; ++i) ; + i = llmin(i, FACE_IMPORTANCE_LEVEL - 1) ; + importance = dist_factor * FACE_IMPORTANCE_TO_CAMERA_OVER_ANGLE[i][1] ; + } + + return importance ; +} + +//static +F32 LLFace::adjustPixelArea(F32 importance, F32 pixel_area) +{ + if(pixel_area > LLViewerTexture::sMaxSmallImageSize) + { + if(importance < LEAST_IMPORTANCE) //if the face is not important, do not load hi-res. + { + static const F32 MAX_LEAST_IMPORTANCE_IMAGE_SIZE = 128.0f * 128.0f ; + pixel_area = llmin(pixel_area * 0.5f, MAX_LEAST_IMPORTANCE_IMAGE_SIZE) ; + } + else if(pixel_area > LLViewerTexture::sMinLargeImageSize) //if is large image, shrink face_area by considering the partial overlapping. + { + if(importance < LEAST_IMPORTANCE_FOR_LARGE_IMAGE)//if the face is not important, do not load hi-res. + { + pixel_area = LLViewerTexture::sMinLargeImageSize ; + } + } + } + + return pixel_area ; +} + +bool LLFace::verify(const U32* indices_array) const +{ + bool ok = true; + + if( mVertexBuffer.isNull() ) + { //no vertex buffer, face is implicitly valid + return true; + } + + // First, check whether the face data fits within the pool's range. + if ((mGeomIndex + mGeomCount) > mVertexBuffer->getNumVerts()) + { + ok = false; + LL_INFOS() << "Face references invalid vertices!" << LL_ENDL; + } + + S32 indices_count = (S32)getIndicesCount(); + + if (!indices_count) + { + return true; + } + + if (indices_count > LL_MAX_INDICES_COUNT) + { + ok = false; + LL_INFOS() << "Face has bogus indices count" << LL_ENDL; + } + + if (mIndicesIndex + mIndicesCount > mVertexBuffer->getNumIndices()) + { + ok = false; + LL_INFOS() << "Face references invalid indices!" << LL_ENDL; + } + +#if 0 + S32 geom_start = getGeomStart(); + S32 geom_count = mGeomCount; + + const U32 *indicesp = indices_array ? indices_array + mIndicesIndex : getRawIndices(); + + for (S32 i = 0; i < indices_count; i++) + { + S32 delta = indicesp[i] - geom_start; + if (0 > delta) + { + LL_WARNS() << "Face index too low!" << LL_ENDL; + LL_INFOS() << "i:" << i << " Index:" << indicesp[i] << " GStart: " << geom_start << LL_ENDL; + ok = false; + } + else if (delta >= geom_count) + { + LL_WARNS() << "Face index too high!" << LL_ENDL; + LL_INFOS() << "i:" << i << " Index:" << indicesp[i] << " GEnd: " << geom_start + geom_count << LL_ENDL; + ok = false; + } + } +#endif + + if (!ok) + { + printDebugInfo(); + } + return ok; +} + + +void LLFace::setViewerObject(LLViewerObject* objp) +{ + mVObjp = objp; +} + + +const LLMatrix4& LLFace::getRenderMatrix() const +{ + return mDrawablep->getRenderMatrix(); +} + +//============================================================================ +// From llface.inl + +S32 LLFace::getColors(LLStrider &colors) +{ + if (!mGeomCount) + { + return -1; + } + + // llassert(mGeomIndex >= 0); + mVertexBuffer->getColorStrider(colors, mGeomIndex, mGeomCount); + return mGeomIndex; +} + +S32 LLFace::getIndices(LLStrider &indicesp) +{ + mVertexBuffer->getIndexStrider(indicesp, mIndicesIndex, mIndicesCount); + llassert(indicesp[0] != indicesp[1]); + return mIndicesIndex; +} + +LLVector3 LLFace::getPositionAgent() const +{ + if (mDrawablep->isStatic()) + { + return mCenterAgent; + } + else + { + return mCenterLocal * getRenderMatrix(); + } +} + +LLViewerTexture* LLFace::getTexture(U32 ch) const +{ + llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); + + return mTexture[ch] ; +} + +void LLFace::setVertexBuffer(LLVertexBuffer* buffer) +{ + if (buffer) + { + LLSculptIDSize::instance().inc(mDrawablep, buffer->getSize() + buffer->getIndicesSize()); + } + + if (mVertexBuffer) + { + LLSculptIDSize::instance().dec(mDrawablep); + } + + mVertexBuffer = buffer; + llassert(verify()); +} + +void LLFace::clearVertexBuffer() +{ + if (mVertexBuffer) + { + LLSculptIDSize::instance().dec(mDrawablep); + } + + mVertexBuffer = NULL; +} + +S32 LLFace::getRiggedIndex(U32 type) const +{ + if (mRiggedIndex.empty()) + { + return -1; + } + + llassert(type < mRiggedIndex.size()); + + return mRiggedIndex[type]; +} + +U64 LLFace::getSkinHash() +{ + return mSkinInfo ? mSkinInfo->mHash : 0; +} + +bool LLFace::isInAlphaPool() const +{ + return getPoolType() == LLDrawPool::POOL_ALPHA || + getPoolType() == LLDrawPool::POOL_ALPHA_PRE_WATER || + getPoolType() == LLDrawPool::POOL_ALPHA_POST_WATER; +} diff --git a/indra/newview/llface.h b/indra/newview/llface.h index 254942248f..917f3aa0b2 100644 --- a/indra/newview/llface.h +++ b/indra/newview/llface.h @@ -1,380 +1,380 @@ -/** - * @file llface.h - * @brief LLFace class definition - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFACE_H -#define LL_LLFACE_H - -#include "llstrider.h" -#include "llrender.h" -#include "v2math.h" -#include "v3math.h" -#include "v4math.h" -#include "m4math.h" -#include "v4coloru.h" -#include "llquaternion.h" -#include "xform.h" -#include "llvertexbuffer.h" -#include "llviewertexture.h" -#include "lldrawable.h" - -class LLFacePool; -class LLVolume; -class LLViewerTexture; -class LLTextureEntry; -class LLVertexProgram; -class LLViewerTexture; -class LLGeometryManager; -class LLDrawInfo; -class LLMeshSkinInfo; - -const F32 MIN_ALPHA_SIZE = 1024.f; -const F32 MIN_TEX_ANIM_SIZE = 512.f; -const U8 FACE_DO_NOT_BATCH_TEXTURES = 255; - -class alignas(16) LLFace -{ - LL_ALIGN_NEW -public: - LLFace(const LLFace& rhs) - { - *this = rhs; - } - - const LLFace& operator=(const LLFace& rhs) - { - LL_ERRS() << "Illegal operation!" << LL_ENDL; - return *this; - } - - enum EMasks - { - LIGHT = 0x0001, - GLOBAL = 0x0002, - FULLBRIGHT = 0x0004, - HUD_RENDER = 0x0008, - USE_FACE_COLOR = 0x0010, - TEXTURE_ANIM = 0x0020, - RIGGED = 0x0040, - PARTICLE = 0x0080, - }; - -public: - LLFace(LLDrawable* drawablep, LLViewerObject* objp) - { - LL_PROFILE_ZONE_SCOPED; - init(drawablep, objp); - } - ~LLFace() { destroy(); } - - const LLMatrix4& getWorldMatrix() const { return mVObjp->getWorldMatrix(mXform); } - const LLMatrix4& getRenderMatrix() const; - U32 getIndicesCount() const { return mIndicesCount; }; - S32 getIndicesStart() const { return mIndicesIndex; }; - U16 getGeomCount() const { return mGeomCount; } // vertex count for this face - U16 getGeomIndex() const { return mGeomIndex; } // index into draw pool - U16 getGeomStart() const { return mGeomIndex; } // index into draw pool - void setTextureIndex(U8 index); - U8 getTextureIndex() const { return mTextureIndex; } - void setTexture(U32 ch, LLViewerTexture* tex); - void setTexture(LLViewerTexture* tex) ; - void setDiffuseMap(LLViewerTexture* tex); - void setNormalMap(LLViewerTexture* tex); - void setSpecularMap(LLViewerTexture* tex); - void setAlternateDiffuseMap(LLViewerTexture* tex); - void switchTexture(U32 ch, LLViewerTexture* new_texture); - void dirtyTexture(); - LLXformMatrix* getXform() const { return mXform; } - bool hasGeometry() const { return mGeomCount > 0; } - LLVector3 getPositionAgent() const; - LLVector2 surfaceToTexture(LLVector2 surface_coord, const LLVector4a& position, const LLVector4a& normal); - void getPlanarProjectedParams(LLQuaternion* face_rot, LLVector3* face_pos, F32* scale) const; - bool calcAlignedPlanarTE(const LLFace* align_to, LLVector2* st_offset, - LLVector2* st_scale, F32* st_rot, LLRender::eTexIndex map = LLRender::DIFFUSE_MAP) const; - - U32 getState() const { return mState; } - void setState(U32 state) { mState |= state; } - void clearState(U32 state) { mState &= ~state; } - bool isState(U32 state) const { return (mState & state) != 0; } - void setVirtualSize(F32 size) { mVSize = size; } - void setPixelArea(F32 area) { mPixelArea = area; } - F32 getVirtualSize() const { return mVSize; } - F32 getPixelArea() const { return mPixelArea; } - - S32 getIndexInTex(U32 ch) const { llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); return mIndexInTex[ch]; } - void setIndexInTex(U32 ch, S32 index) { llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); mIndexInTex[ch] = index; } - - void setWorldMatrix(const LLMatrix4& mat); - const LLTextureEntry* getTextureEntry() const { return mVObjp->getTE(mTEOffset); } - - LLFacePool* getPool() const { return mDrawPoolp; } - U32 getPoolType() const { return mPoolType; } - LLDrawable* getDrawable() const { return mDrawablep; } - LLViewerObject* getViewerObject() const { return mVObjp; } - S32 getLOD() const { return mVObjp.notNull() ? mVObjp->getLOD() : 0; } - void setPoolType(U32 type) { mPoolType = type; } - S32 getTEOffset() const { return mTEOffset; } - LLViewerTexture* getTexture(U32 ch = LLRender::DIFFUSE_MAP) const; - - void setViewerObject(LLViewerObject* object); - void setPool(LLFacePool *pool, LLViewerTexture *texturep); - void setPool(LLFacePool* pool); - - void setDrawable(LLDrawable *drawable); - void setTEOffset(const S32 te_offset); - - void renderIndexed(); - - void setFaceColor(const LLColor4& color); // override material color - void unsetFaceColor(); // switch back to material color - const LLColor4& getFaceColor() const { return mFaceColor; } - - - //for volumes - void updateRebuildFlags(); - bool canRenderAsMask(); // logic helper - bool getGeometryVolume(const LLVolume& volume, - S32 face_index, - const LLMatrix4& mat_vert, - const LLMatrix3& mat_normal, - U16 index_offset, - bool force_rebuild = false, - bool no_debug_assert = false); - - // For avatar - U16 getGeometryAvatar( - LLStrider &vertices, - LLStrider &normals, - LLStrider &texCoords, - LLStrider &vertex_weights, - LLStrider &clothing_weights); - - // For volumes, etc. - U16 getGeometry(LLStrider &vertices, - LLStrider &normals, - LLStrider &texCoords, - LLStrider &indices); - - S32 getColors(LLStrider &colors); - S32 getIndices(LLStrider &indices); - - void setSize(S32 numVertices, S32 num_indices = 0, bool align = false); - - bool genVolumeBBoxes(const LLVolume &volume, S32 f, - const LLMatrix4& mat_vert_in, bool global_volume = false); - - void init(LLDrawable* drawablep, LLViewerObject* objp); - void destroy(); - void update(); - - void updateCenterAgent(); // Update center when xform has changed. - void renderSelectedUV(); - - void renderSelected(LLViewerTexture *image, const LLColor4 &color); - void renderOneWireframe(const LLColor4 &color, F32 fogCfx, bool wireframe_selection, bool bRenderHiddenSelections, bool shader); - - F32 getKey() const { return mDistance; } - - S32 getReferenceIndex() const { return mReferenceIndex; } - void setReferenceIndex(const S32 index) { mReferenceIndex = index; } - - bool verify(const U32* indices_array = NULL) const; - void printDebugInfo() const; - - void setGeomIndex(U16 idx); - void setIndicesIndex(S32 idx); - void setDrawInfo(LLDrawInfo* draw_info); - - F32 getTextureVirtualSize() ; - F32 getImportanceToCamera()const {return mImportanceToCamera ;} - void resetVirtualSize(); - - void setHasMedia(bool has_media) { mHasMedia = has_media ;} - bool hasMedia() const ; - - void setMediaAllowed(bool is_media_allowed) { mIsMediaAllowed = is_media_allowed; } - bool isMediaAllowed() const { return mIsMediaAllowed; } - - bool switchTexture() ; - - //vertex buffer tracking - void setVertexBuffer(LLVertexBuffer* buffer); - void clearVertexBuffer(); //sets mVertexBuffer to NULL - LLVertexBuffer* getVertexBuffer() const { return mVertexBuffer; } - S32 getRiggedIndex(U32 type) const; - - // used to preserve draw order of faces that are batched together. - // Allows content creators to manipulate linked sets and face ordering - // for consistent alpha sorting results, particularly for rigged attachments - void setDrawOrderIndex(U32 index) { mDrawOrderIndex = index; } - U32 getDrawOrderIndex() const { return mDrawOrderIndex; } - - // return true if this face is in an alpha draw pool - bool isInAlphaPool() const; -public: //aligned members - LLVector4a mExtents[2]; - -private: - friend class LLViewerTextureList; - F32 adjustPartialOverlapPixelArea(F32 cos_angle_to_view_dir, F32 radius ); - bool calcPixelArea(F32& cos_angle_to_view_dir, F32& radius) ; -public: - static F32 calcImportanceToCamera(F32 to_view_dir, F32 dist); - static F32 adjustPixelArea(F32 importance, F32 pixel_area) ; - -public: - - LLVector3 mCenterLocal; - LLVector3 mCenterAgent; - - LLVector2 mTexExtents[2]; - F32 mDistance; - F32 mLastUpdateTime; - F32 mLastSkinTime; - F32 mLastMoveTime; - LLMatrix4* mTextureMatrix; - LLMatrix4* mSpecMapMatrix; - LLMatrix4* mNormalMapMatrix; - LLDrawInfo* mDrawInfo; - LLVOAvatar* mAvatar = nullptr; - LLMeshSkinInfo* mSkinInfo = nullptr; - - // return mSkinInfo->mHash or 0 if mSkinInfo is null - U64 getSkinHash(); - -private: - LLPointer mVertexBuffer; - - U32 mState; - LLFacePool* mDrawPoolp; - U32 mPoolType; - LLColor4 mFaceColor; // overrides material color if state |= USE_FACE_COLOR - - U16 mGeomCount; // vertex count for this face - U16 mGeomIndex; // starting index into mVertexBuffer's vertex array - U8 mTextureIndex; // index of texture channel to use for pseudo-atlasing - U32 mIndicesCount; - U32 mIndicesIndex; // index into mVertexBuffer's index array - S32 mIndexInTex[LLRender::NUM_TEXTURE_CHANNELS]; - - LLXformMatrix* mXform; - - LLPointer mTexture[LLRender::NUM_TEXTURE_CHANNELS]; - - // mDrawablep is not supposed to be null, don't use LLPointer because - // mDrawablep owns LLFace and LLPointer is a good way to either cause a - // memory leak or a 'delete each other' situation if something deletes - // drawable wrongly. - LLDrawable* mDrawablep; - // LLViewerObject technically owns drawable, but also it should be strictly managed - LLPointer mVObjp; - S32 mTEOffset; - - S32 mReferenceIndex; - std::vector mRiggedIndex; - - F32 mVSize; - F32 mPixelArea; - - //importance factor, in the range [0, 1.0]. - //1.0: the most important. - //based on the distance from the face to the view point and the angle from the face center to the view direction. - F32 mImportanceToCamera ; - F32 mBoundingSphereRadius ; - bool mHasMedia ; - bool mIsMediaAllowed; - - U32 mDrawOrderIndex = 0; // see setDrawOrderIndex - -protected: - static bool sSafeRenderSelect; - -public: - struct CompareDistanceGreater - { - bool operator()(const LLFace* const& lhs, const LLFace* const& rhs) - { - return !lhs || (rhs && (lhs->mDistance > rhs->mDistance)); // farthest = first - } - }; - - struct CompareTexture - { - bool operator()(const LLFace* const& lhs, const LLFace* const& rhs) - { - return lhs->getTexture() < rhs->getTexture(); - } - }; - - struct CompareBatchBreaker - { - bool operator()(const LLFace* const& lhs, const LLFace* const& rhs) - { - const LLTextureEntry* lte = lhs->getTextureEntry(); - const LLTextureEntry* rte = rhs->getTextureEntry(); - - if(lhs->getTexture() != rhs->getTexture()) - { - return lhs->getTexture() < rhs->getTexture(); - } - else - { - return lte->getBumpShinyFullbright() < rte->getBumpShinyFullbright(); - } - } - }; - - struct CompareTextureAndGeomCount - { - bool operator()(const LLFace* const& lhs, const LLFace* const& rhs) - { - return lhs->getTexture() == rhs->getTexture() ? - lhs->getGeomCount() < rhs->getGeomCount() : //smallest = first - lhs->getTexture() > rhs->getTexture(); - } - }; - - struct CompareTextureAndLOD - { - bool operator()(const LLFace* const& lhs, const LLFace* const& rhs) - { - return lhs->getTexture() == rhs->getTexture() ? - lhs->getLOD() < rhs->getLOD() : - lhs->getTexture() < rhs->getTexture(); - } - }; - - struct CompareTextureAndTime - { - bool operator()(const LLFace* const& lhs, const LLFace* const& rhs) - { - return lhs->getTexture() == rhs->getTexture() ? - lhs->mLastUpdateTime < rhs->mLastUpdateTime : - lhs->getTexture() < rhs->getTexture(); - } - }; -}; - -#endif // LL_LLFACE_H +/** + * @file llface.h + * @brief LLFace class definition + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFACE_H +#define LL_LLFACE_H + +#include "llstrider.h" +#include "llrender.h" +#include "v2math.h" +#include "v3math.h" +#include "v4math.h" +#include "m4math.h" +#include "v4coloru.h" +#include "llquaternion.h" +#include "xform.h" +#include "llvertexbuffer.h" +#include "llviewertexture.h" +#include "lldrawable.h" + +class LLFacePool; +class LLVolume; +class LLViewerTexture; +class LLTextureEntry; +class LLVertexProgram; +class LLViewerTexture; +class LLGeometryManager; +class LLDrawInfo; +class LLMeshSkinInfo; + +const F32 MIN_ALPHA_SIZE = 1024.f; +const F32 MIN_TEX_ANIM_SIZE = 512.f; +const U8 FACE_DO_NOT_BATCH_TEXTURES = 255; + +class alignas(16) LLFace +{ + LL_ALIGN_NEW +public: + LLFace(const LLFace& rhs) + { + *this = rhs; + } + + const LLFace& operator=(const LLFace& rhs) + { + LL_ERRS() << "Illegal operation!" << LL_ENDL; + return *this; + } + + enum EMasks + { + LIGHT = 0x0001, + GLOBAL = 0x0002, + FULLBRIGHT = 0x0004, + HUD_RENDER = 0x0008, + USE_FACE_COLOR = 0x0010, + TEXTURE_ANIM = 0x0020, + RIGGED = 0x0040, + PARTICLE = 0x0080, + }; + +public: + LLFace(LLDrawable* drawablep, LLViewerObject* objp) + { + LL_PROFILE_ZONE_SCOPED; + init(drawablep, objp); + } + ~LLFace() { destroy(); } + + const LLMatrix4& getWorldMatrix() const { return mVObjp->getWorldMatrix(mXform); } + const LLMatrix4& getRenderMatrix() const; + U32 getIndicesCount() const { return mIndicesCount; }; + S32 getIndicesStart() const { return mIndicesIndex; }; + U16 getGeomCount() const { return mGeomCount; } // vertex count for this face + U16 getGeomIndex() const { return mGeomIndex; } // index into draw pool + U16 getGeomStart() const { return mGeomIndex; } // index into draw pool + void setTextureIndex(U8 index); + U8 getTextureIndex() const { return mTextureIndex; } + void setTexture(U32 ch, LLViewerTexture* tex); + void setTexture(LLViewerTexture* tex) ; + void setDiffuseMap(LLViewerTexture* tex); + void setNormalMap(LLViewerTexture* tex); + void setSpecularMap(LLViewerTexture* tex); + void setAlternateDiffuseMap(LLViewerTexture* tex); + void switchTexture(U32 ch, LLViewerTexture* new_texture); + void dirtyTexture(); + LLXformMatrix* getXform() const { return mXform; } + bool hasGeometry() const { return mGeomCount > 0; } + LLVector3 getPositionAgent() const; + LLVector2 surfaceToTexture(LLVector2 surface_coord, const LLVector4a& position, const LLVector4a& normal); + void getPlanarProjectedParams(LLQuaternion* face_rot, LLVector3* face_pos, F32* scale) const; + bool calcAlignedPlanarTE(const LLFace* align_to, LLVector2* st_offset, + LLVector2* st_scale, F32* st_rot, LLRender::eTexIndex map = LLRender::DIFFUSE_MAP) const; + + U32 getState() const { return mState; } + void setState(U32 state) { mState |= state; } + void clearState(U32 state) { mState &= ~state; } + bool isState(U32 state) const { return (mState & state) != 0; } + void setVirtualSize(F32 size) { mVSize = size; } + void setPixelArea(F32 area) { mPixelArea = area; } + F32 getVirtualSize() const { return mVSize; } + F32 getPixelArea() const { return mPixelArea; } + + S32 getIndexInTex(U32 ch) const { llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); return mIndexInTex[ch]; } + void setIndexInTex(U32 ch, S32 index) { llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); mIndexInTex[ch] = index; } + + void setWorldMatrix(const LLMatrix4& mat); + const LLTextureEntry* getTextureEntry() const { return mVObjp->getTE(mTEOffset); } + + LLFacePool* getPool() const { return mDrawPoolp; } + U32 getPoolType() const { return mPoolType; } + LLDrawable* getDrawable() const { return mDrawablep; } + LLViewerObject* getViewerObject() const { return mVObjp; } + S32 getLOD() const { return mVObjp.notNull() ? mVObjp->getLOD() : 0; } + void setPoolType(U32 type) { mPoolType = type; } + S32 getTEOffset() const { return mTEOffset; } + LLViewerTexture* getTexture(U32 ch = LLRender::DIFFUSE_MAP) const; + + void setViewerObject(LLViewerObject* object); + void setPool(LLFacePool *pool, LLViewerTexture *texturep); + void setPool(LLFacePool* pool); + + void setDrawable(LLDrawable *drawable); + void setTEOffset(const S32 te_offset); + + void renderIndexed(); + + void setFaceColor(const LLColor4& color); // override material color + void unsetFaceColor(); // switch back to material color + const LLColor4& getFaceColor() const { return mFaceColor; } + + + //for volumes + void updateRebuildFlags(); + bool canRenderAsMask(); // logic helper + bool getGeometryVolume(const LLVolume& volume, + S32 face_index, + const LLMatrix4& mat_vert, + const LLMatrix3& mat_normal, + U16 index_offset, + bool force_rebuild = false, + bool no_debug_assert = false); + + // For avatar + U16 getGeometryAvatar( + LLStrider &vertices, + LLStrider &normals, + LLStrider &texCoords, + LLStrider &vertex_weights, + LLStrider &clothing_weights); + + // For volumes, etc. + U16 getGeometry(LLStrider &vertices, + LLStrider &normals, + LLStrider &texCoords, + LLStrider &indices); + + S32 getColors(LLStrider &colors); + S32 getIndices(LLStrider &indices); + + void setSize(S32 numVertices, S32 num_indices = 0, bool align = false); + + bool genVolumeBBoxes(const LLVolume &volume, S32 f, + const LLMatrix4& mat_vert_in, bool global_volume = false); + + void init(LLDrawable* drawablep, LLViewerObject* objp); + void destroy(); + void update(); + + void updateCenterAgent(); // Update center when xform has changed. + void renderSelectedUV(); + + void renderSelected(LLViewerTexture *image, const LLColor4 &color); + void renderOneWireframe(const LLColor4 &color, F32 fogCfx, bool wireframe_selection, bool bRenderHiddenSelections, bool shader); + + F32 getKey() const { return mDistance; } + + S32 getReferenceIndex() const { return mReferenceIndex; } + void setReferenceIndex(const S32 index) { mReferenceIndex = index; } + + bool verify(const U32* indices_array = NULL) const; + void printDebugInfo() const; + + void setGeomIndex(U16 idx); + void setIndicesIndex(S32 idx); + void setDrawInfo(LLDrawInfo* draw_info); + + F32 getTextureVirtualSize() ; + F32 getImportanceToCamera()const {return mImportanceToCamera ;} + void resetVirtualSize(); + + void setHasMedia(bool has_media) { mHasMedia = has_media ;} + bool hasMedia() const ; + + void setMediaAllowed(bool is_media_allowed) { mIsMediaAllowed = is_media_allowed; } + bool isMediaAllowed() const { return mIsMediaAllowed; } + + bool switchTexture() ; + + //vertex buffer tracking + void setVertexBuffer(LLVertexBuffer* buffer); + void clearVertexBuffer(); //sets mVertexBuffer to NULL + LLVertexBuffer* getVertexBuffer() const { return mVertexBuffer; } + S32 getRiggedIndex(U32 type) const; + + // used to preserve draw order of faces that are batched together. + // Allows content creators to manipulate linked sets and face ordering + // for consistent alpha sorting results, particularly for rigged attachments + void setDrawOrderIndex(U32 index) { mDrawOrderIndex = index; } + U32 getDrawOrderIndex() const { return mDrawOrderIndex; } + + // return true if this face is in an alpha draw pool + bool isInAlphaPool() const; +public: //aligned members + LLVector4a mExtents[2]; + +private: + friend class LLViewerTextureList; + F32 adjustPartialOverlapPixelArea(F32 cos_angle_to_view_dir, F32 radius ); + bool calcPixelArea(F32& cos_angle_to_view_dir, F32& radius) ; +public: + static F32 calcImportanceToCamera(F32 to_view_dir, F32 dist); + static F32 adjustPixelArea(F32 importance, F32 pixel_area) ; + +public: + + LLVector3 mCenterLocal; + LLVector3 mCenterAgent; + + LLVector2 mTexExtents[2]; + F32 mDistance; + F32 mLastUpdateTime; + F32 mLastSkinTime; + F32 mLastMoveTime; + LLMatrix4* mTextureMatrix; + LLMatrix4* mSpecMapMatrix; + LLMatrix4* mNormalMapMatrix; + LLDrawInfo* mDrawInfo; + LLVOAvatar* mAvatar = nullptr; + LLMeshSkinInfo* mSkinInfo = nullptr; + + // return mSkinInfo->mHash or 0 if mSkinInfo is null + U64 getSkinHash(); + +private: + LLPointer mVertexBuffer; + + U32 mState; + LLFacePool* mDrawPoolp; + U32 mPoolType; + LLColor4 mFaceColor; // overrides material color if state |= USE_FACE_COLOR + + U16 mGeomCount; // vertex count for this face + U16 mGeomIndex; // starting index into mVertexBuffer's vertex array + U8 mTextureIndex; // index of texture channel to use for pseudo-atlasing + U32 mIndicesCount; + U32 mIndicesIndex; // index into mVertexBuffer's index array + S32 mIndexInTex[LLRender::NUM_TEXTURE_CHANNELS]; + + LLXformMatrix* mXform; + + LLPointer mTexture[LLRender::NUM_TEXTURE_CHANNELS]; + + // mDrawablep is not supposed to be null, don't use LLPointer because + // mDrawablep owns LLFace and LLPointer is a good way to either cause a + // memory leak or a 'delete each other' situation if something deletes + // drawable wrongly. + LLDrawable* mDrawablep; + // LLViewerObject technically owns drawable, but also it should be strictly managed + LLPointer mVObjp; + S32 mTEOffset; + + S32 mReferenceIndex; + std::vector mRiggedIndex; + + F32 mVSize; + F32 mPixelArea; + + //importance factor, in the range [0, 1.0]. + //1.0: the most important. + //based on the distance from the face to the view point and the angle from the face center to the view direction. + F32 mImportanceToCamera ; + F32 mBoundingSphereRadius ; + bool mHasMedia ; + bool mIsMediaAllowed; + + U32 mDrawOrderIndex = 0; // see setDrawOrderIndex + +protected: + static bool sSafeRenderSelect; + +public: + struct CompareDistanceGreater + { + bool operator()(const LLFace* const& lhs, const LLFace* const& rhs) + { + return !lhs || (rhs && (lhs->mDistance > rhs->mDistance)); // farthest = first + } + }; + + struct CompareTexture + { + bool operator()(const LLFace* const& lhs, const LLFace* const& rhs) + { + return lhs->getTexture() < rhs->getTexture(); + } + }; + + struct CompareBatchBreaker + { + bool operator()(const LLFace* const& lhs, const LLFace* const& rhs) + { + const LLTextureEntry* lte = lhs->getTextureEntry(); + const LLTextureEntry* rte = rhs->getTextureEntry(); + + if(lhs->getTexture() != rhs->getTexture()) + { + return lhs->getTexture() < rhs->getTexture(); + } + else + { + return lte->getBumpShinyFullbright() < rte->getBumpShinyFullbright(); + } + } + }; + + struct CompareTextureAndGeomCount + { + bool operator()(const LLFace* const& lhs, const LLFace* const& rhs) + { + return lhs->getTexture() == rhs->getTexture() ? + lhs->getGeomCount() < rhs->getGeomCount() : //smallest = first + lhs->getTexture() > rhs->getTexture(); + } + }; + + struct CompareTextureAndLOD + { + bool operator()(const LLFace* const& lhs, const LLFace* const& rhs) + { + return lhs->getTexture() == rhs->getTexture() ? + lhs->getLOD() < rhs->getLOD() : + lhs->getTexture() < rhs->getTexture(); + } + }; + + struct CompareTextureAndTime + { + bool operator()(const LLFace* const& lhs, const LLFace* const& rhs) + { + return lhs->getTexture() == rhs->getTexture() ? + lhs->mLastUpdateTime < rhs->mLastUpdateTime : + lhs->getTexture() < rhs->getTexture(); + } + }; +}; + +#endif // LL_LLFACE_H diff --git a/indra/newview/llfasttimerview.cpp b/indra/newview/llfasttimerview.cpp index 74345841cb..0658d84bb8 100644 --- a/indra/newview/llfasttimerview.cpp +++ b/indra/newview/llfasttimerview.cpp @@ -1,1667 +1,1667 @@ -/** - * @file llfasttimerview.cpp - * @brief LLFastTimerView class implementation - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfasttimerview.h" - -#include "llviewerwindow.h" -#include "llrect.h" -#include "llcombobox.h" -#include "llerror.h" -#include "llgl.h" -#include "llimagepng.h" -#include "llrender.h" -#include "llrendertarget.h" -#include "lllocalcliprect.h" -#include "lllayoutstack.h" -#include "llmath.h" -#include "llfontgl.h" -#include "llsdserialize.h" -#include "lltooltip.h" -#include "llbutton.h" -#include "llscrollbar.h" - -#include "llappviewer.h" -#include "llviewertexturelist.h" -#include "llui.h" -#include "llviewercontrol.h" - -#include "llfasttimer.h" -#include "lltreeiterators.h" -#include "llmetricperformancetester.h" -#include "llviewerstats.h" - -////////////////////////////////////////////////////////////////////////////// - -using namespace LLTrace; - -static constexpr S32 MAX_VISIBLE_HISTORY = 12; -static constexpr S32 LINE_GRAPH_HEIGHT = 240; -static constexpr S32 MIN_BAR_HEIGHT = 3; -static constexpr S32 RUNNING_AVERAGE_WIDTH = 100; -static constexpr S32 NUM_FRAMES_HISTORY = 200; - -std::vector ft_display_idx; // line of table entry for display purposes (for collapse) - -bool LLFastTimerView::sAnalyzePerformance = false; - -S32 get_depth(const BlockTimerStatHandle* blockp) -{ - S32 depth = 0; - BlockTimerStatHandle* timerp = blockp->getParent(); - while(timerp) - { - depth++; - if (timerp->getParent() == timerp) break; - timerp = timerp->getParent(); - } - return depth; -} - -LLFastTimerView::LLFastTimerView(const LLSD& key) -: LLFloater(key), - mHoverTimer(NULL), - mDisplayMode(0), - mDisplayType(DISPLAY_TIME), - mScrollIndex(0), - mHoverID(NULL), - mHoverBarIndex(-1), - mStatsIndex(-1), - mPauseHistory(false), - mRecording(NUM_FRAMES_HISTORY) -{ - mTimerBarRows.resize(NUM_FRAMES_HISTORY); -} - -LLFastTimerView::~LLFastTimerView() -{ -} - -void LLFastTimerView::onPause() -{ - setPauseState(!mPauseHistory); -} - -void LLFastTimerView::setPauseState(bool pause_state) -{ - if (pause_state == mPauseHistory) return; - - // reset scroll to bottom when unpausing - if (!pause_state) - { - - getChild("pause_btn")->setLabel(getString("pause")); - } - else - { - mScrollIndex = 0; - - getChild("pause_btn")->setLabel(getString("run")); - } - - mPauseHistory = pause_state; -} - -bool LLFastTimerView::postBuild() -{ - LLButton& pause_btn = getChildRef("pause_btn"); - mScrollBar = getChild("scroll_vert"); - - pause_btn.setCommitCallback(boost::bind(&LLFastTimerView::onPause, this)); - return true; -} - -bool LLFastTimerView::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - if (mHoverTimer ) - { - // right click collapses timers - if (!mHoverTimer->getTreeNode().mCollapsed) - { - mHoverTimer->getTreeNode().mCollapsed = true; - } - else if (mHoverTimer->getParent()) - { - mHoverTimer->getParent()->getTreeNode().mCollapsed = true; - } - return true; - } - else if (mBarRect.pointInRect(x, y)) - { - S32 bar_idx = MAX_VISIBLE_HISTORY - ((y - mBarRect.mBottom) * (MAX_VISIBLE_HISTORY + 2) / mBarRect.getHeight()); - bar_idx = llclamp(bar_idx, 0, MAX_VISIBLE_HISTORY); - mStatsIndex = mScrollIndex + bar_idx; - return true; - } - return LLFloater::handleRightMouseDown(x, y, mask); -} - -BlockTimerStatHandle* LLFastTimerView::getLegendID(S32 y) -{ - S32 idx = (mLegendRect.mTop - y) / (LLFontGL::getFontMonospace()->getLineHeight() + 2); - - if (idx >= 0 && idx < (S32)ft_display_idx.size()) - { - return ft_display_idx[idx]; - } - - return NULL; -} - -bool LLFastTimerView::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - for(LLTrace::block_timer_tree_df_iterator_t it = LLTrace::begin_block_timer_tree_df(FTM_FRAME); - it != LLTrace::end_block_timer_tree_df(); - ++it) - { - (*it)->getTreeNode().mCollapsed = false; - } - return true; -} - -bool LLFastTimerView::handleMouseDown(S32 x, S32 y, MASK mask) -{ - if (x < mScrollBar->getRect().mLeft) - { - BlockTimerStatHandle* idp = getLegendID(y); - if (idp) - { - idp->getTreeNode().mCollapsed = !idp->getTreeNode().mCollapsed; - } - } - else if (mHoverTimer) - { - //left click drills down by expanding timers - mHoverTimer->getTreeNode().mCollapsed = false; - } - else if (mGraphRect.pointInRect(x, y)) - { - gFocusMgr.setMouseCapture(this); - return true; - } - - return LLFloater::handleMouseDown(x, y, mask); -} - -bool LLFastTimerView::handleMouseUp(S32 x, S32 y, MASK mask) -{ - if (hasMouseCapture()) - { - gFocusMgr.setMouseCapture(NULL); - } - return LLFloater::handleMouseUp(x, y, mask);; -} - -bool LLFastTimerView::handleHover(S32 x, S32 y, MASK mask) -{ - if (hasMouseCapture()) - { - F32 lerp = llclamp(1.f - (F32) (x - mGraphRect.mLeft) / (F32) mGraphRect.getWidth(), 0.f, 1.f); - mScrollIndex = ll_round( lerp * (F32)(mRecording.getNumRecordedPeriods() - MAX_VISIBLE_HISTORY)); - mScrollIndex = llclamp( mScrollIndex, 0, (S32)mRecording.getNumRecordedPeriods()); - return true; - } - mHoverTimer = NULL; - mHoverID = NULL; - - if(mPauseHistory && mBarRect.pointInRect(x, y)) - { - //const S32 bars_top = mBarRect.mTop; - const S32 bars_top = mBarRect.mTop - ((S32)LLFontGL::getFontMonospace()->getLineHeight() + 4); - - mHoverBarIndex = llmin((bars_top - y) / (mBarRect.getHeight() / (MAX_VISIBLE_HISTORY + 2)) - 1, - (S32)mRecording.getNumRecordedPeriods() - 1, - MAX_VISIBLE_HISTORY); - if (mHoverBarIndex == 0) - { - return true; - } - else if (mHoverBarIndex < 0) - { - mHoverBarIndex = 0; - } - - TimerBarRow& row = mHoverBarIndex == 0 ? mAverageTimerRow : mTimerBarRows[mScrollIndex + mHoverBarIndex - 1]; - - TimerBar* hover_bar = NULL; - F32Seconds mouse_time_offset = ((F32)(x - mBarRect.mLeft) / (F32)mBarRect.getWidth()) * mTotalTimeDisplay; - for (int bar_index = 0, end_index = LLTrace::BlockTimerStatHandle::instance_tracker_t::instanceCount(); - bar_index < end_index; - ++bar_index) - { - TimerBar& bar = row.mBars[bar_index]; - if (bar.mSelfStart > mouse_time_offset) - { - break; - } - if (bar.mSelfEnd > mouse_time_offset) - { - hover_bar = &bar; - if (bar.mTimeBlock->getTreeNode().mCollapsed) - { - // stop on first collapsed BlockTimerStatHandle, since we can't select any children - break; - } - } - } - - if (hover_bar) - { - mHoverID = hover_bar->mTimeBlock; - if (mHoverTimer != mHoverID) - { - // could be that existing tooltip is for a parent and is thus - // covering region for this new timer, go ahead and unblock - // so we can create a new tooltip - LLToolTipMgr::instance().unblockToolTips(); - mHoverTimer = mHoverID; - mToolTipRect.set(mBarRect.mLeft + (hover_bar->mSelfStart / mTotalTimeDisplay) * mBarRect.getWidth(), - row.mTop, - mBarRect.mLeft + (hover_bar->mSelfEnd / mTotalTimeDisplay) * mBarRect.getWidth(), - row.mBottom); - } - } - } - else if (x < mScrollBar->getRect().mLeft) - { - BlockTimerStatHandle* timer_id = getLegendID(y); - if (timer_id) - { - mHoverID = timer_id; - } - } - - return LLFloater::handleHover(x, y, mask); -} - - -static std::string get_tooltip(BlockTimerStatHandle& timer, S32 history_index, PeriodicRecording& frame_recording) -{ - std::string tooltip; - if (history_index == 0) - { - // by default, show average number of call - tooltip = llformat("%s (%d ms, %d calls)", timer.getName().c_str(), (S32)F64Milliseconds(frame_recording.getPeriodMean (timer, RUNNING_AVERAGE_WIDTH)).value(), (S32)frame_recording.getPeriodMean(timer.callCount(), RUNNING_AVERAGE_WIDTH)); - } - else - { - tooltip = llformat("%s (%d ms, %d calls)", timer.getName().c_str(), (S32)F64Milliseconds(frame_recording.getPrevRecording(history_index).getSum(timer)).value(), (S32)frame_recording.getPrevRecording(history_index).getSum(timer.callCount())); - } - return tooltip; -} - -bool LLFastTimerView::handleToolTip(S32 x, S32 y, MASK mask) -{ - if(mPauseHistory && mBarRect.pointInRect(x, y)) - { - // tooltips for timer bars - if (mHoverTimer) - { - LLRect screen_rect; - localRectToScreen(mToolTipRect, &screen_rect); - - std::string tooltip = get_tooltip(*mHoverTimer, mHoverBarIndex > 0 ? mScrollIndex + mHoverBarIndex : 0, mRecording); - - LLToolTipMgr::instance().show(LLToolTip::Params() - .message(tooltip) - .sticky_rect(screen_rect) - .delay_time(0.f)); - - return true; - } - } - else - { - // tooltips for timer legend - if (x < mScrollBar->getRect().mLeft) - { - BlockTimerStatHandle* idp = getLegendID(y); - if (idp) - { - LLToolTipMgr::instance().show(get_tooltip(*idp, 0, mRecording)); - - return true; - } - } - } - - return LLFloater::handleToolTip(x, y, mask); -} - -bool LLFastTimerView::handleScrollWheel(S32 x, S32 y, S32 clicks) -{ - if (x < mBarRect.mLeft) - { - // Inside mScrollBar and list of timers - mScrollBar->handleScrollWheel(x,y,clicks); - } - else - { - setPauseState(true); - mScrollIndex = llclamp( mScrollIndex + clicks, - 0, - llmin((S32)mRecording.getNumRecordedPeriods(), (S32)mRecording.getNumRecordedPeriods() - MAX_VISIBLE_HISTORY)); - } - return true; -} - -static BlockTimerStatHandle FTM_RENDER_TIMER("Timers"); -static const S32 MARGIN = 10; - -static std::vector sTimerColors; - -void LLFastTimerView::draw() -{ - LL_RECORD_BLOCK_TIME(FTM_RENDER_TIMER); - - if (!mPauseHistory) - { - mRecording.appendRecording(LLTrace::get_frame_recording().getLastRecording()); - mTimerBarRows.pop_back(); - mTimerBarRows.push_front(TimerBarRow()); - } - - mDisplayMode = llclamp(getChild("time_scale_combo")->getCurrentIndex(), 0, 3); - mDisplayType = (EDisplayType)llclamp(getChild("metric_combo")->getCurrentIndex(), 0, 2); - - generateUniqueColors(); - - LLView::drawChildren(); - //getChild("timer_bars_stack")->updateLayout(); - //getChild("legend_stack")->updateLayout(); - LLView* bars_panel = getChildView("bars_panel"); - bars_panel->localRectToOtherView(bars_panel->getLocalRect(), &mBarRect, this); - - LLView* lines_panel = getChildView("lines_panel"); - lines_panel->localRectToOtherView(lines_panel->getLocalRect(), &mGraphRect, this); - - LLView* legend_panel = getChildView("legend"); - legend_panel->localRectToOtherView(legend_panel->getLocalRect(), &mLegendRect, this); - - // Draw the window background - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gl_rect_2d(getLocalRect(), LLColor4(0.f, 0.f, 0.f, 0.25f)); - - drawHelp(getRect().getHeight() - MARGIN); - drawLegend(); - - //mBarRect.mLeft = MARGIN + LEGEND_WIDTH + 8; - //mBarRect.mTop = y; - //mBarRect.mRight = getRect().getWidth() - MARGIN; - //mBarRect.mBottom = MARGIN + LINE_GRAPH_HEIGHT; - - drawBars(); - drawLineGraph(); - printLineStats(); - LLView::draw(); - - mAllTimeMax = llmax(mAllTimeMax, mRecording.getLastRecording().getSum(FTM_FRAME)); - mHoverID = NULL; - mHoverBarIndex = -1; -} - -void LLFastTimerView::onOpen(const LLSD& key) -{ - setPauseState(false); - mRecording.reset(); - mRecording.appendPeriodicRecording(LLTrace::get_frame_recording()); - for(std::deque::iterator it = mTimerBarRows.begin(), end_it = mTimerBarRows.end(); - it != end_it; - ++it) - { - delete []it->mBars; - it->mBars = NULL; - } -} - -void LLFastTimerView::onClose(bool app_quitting) -{ - setVisible(false); -} - -void saveChart(const std::string& label, const char* suffix, LLImageRaw* scratch) -{ - // disable use of glReadPixels which messes up nVidia nSight graphics debugging - if (!LLRender::sNsightDebugSupport) - { - LLImageDataSharedLock lock(scratch); - - //read result back into raw image - glReadPixels(0, 0, 1024, 512, GL_RGB, GL_UNSIGNED_BYTE, scratch->getData()); - - //write results to disk - LLPointer result = new LLImagePNG(); - result->encode(scratch, 0.f); - - std::string ext = result->getExtension(); - std::string filename = llformat("%s_%s.%s", label.c_str(), suffix, ext.c_str()); - - std::string out_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, filename); - result->save(out_file); - } -} - -//static -void LLFastTimerView::exportCharts(const std::string& base, const std::string& target) -{ - //allocate render target for drawing charts - LLRenderTarget buffer; - buffer.allocate(1024,512, GL_RGB); - - - LLSD cur; - - LLSD base_data; - - { //read base log into memory - S32 i = 0; - llifstream is(base.c_str()); - while (!is.eof() && LLSDParser::PARSE_FAILURE != LLSDSerialize::fromXML(cur, is)) - { - base_data[i++] = cur; - } - is.close(); - } - - LLSD cur_data; - std::set chart_names; - - { //read current log into memory - S32 i = 0; - llifstream is(target.c_str()); - while (!is.eof() && LLSDParser::PARSE_FAILURE != LLSDSerialize::fromXML(cur, is)) - { - cur_data[i++] = cur; - - for (LLSD::map_iterator iter = cur.beginMap(); iter != cur.endMap(); ++iter) - { - std::string label = iter->first; - chart_names.insert(label); - } - } - is.close(); - } - - //allocate raw scratch space - LLPointer scratch = new LLImageRaw(1024, 512, 3); - - gGL.pushMatrix(); - gGL.loadIdentity(); - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.loadIdentity(); - gGL.ortho(-0.05f, 1.05f, -0.05f, 1.05f, -1.0f, 1.0f); - - //render charts - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - buffer.bindTarget(); - - for (std::set::iterator iter = chart_names.begin(); iter != chart_names.end(); ++iter) - { - std::string label = *iter; - - LLSD::Real max_time = 0.0; - LLSD::Integer max_calls = 0; - LLSD::Real max_execution = 0.0; - - std::vector cur_execution; - std::vector cur_times; - std::vector cur_calls; - - std::vector base_execution; - std::vector base_times; - std::vector base_calls; - - for (U32 i = 0; i < cur_data.size(); ++i) - { - LLSD::Real time = cur_data[i][label]["Time"].asReal(); - LLSD::Integer calls = cur_data[i][label]["Calls"].asInteger(); - - LLSD::Real execution = 0.0; - if (calls > 0) - { - execution = time/calls; - cur_execution.push_back(execution); - cur_times.push_back(time); - } - - cur_calls.push_back(calls); - } - - for (U32 i = 0; i < base_data.size(); ++i) - { - LLSD::Real time = base_data[i][label]["Time"].asReal(); - LLSD::Integer calls = base_data[i][label]["Calls"].asInteger(); - - LLSD::Real execution = 0.0; - if (calls > 0) - { - execution = time/calls; - base_execution.push_back(execution); - base_times.push_back(time); - } - - base_calls.push_back(calls); - } - - std::sort(base_calls.begin(), base_calls.end()); - std::sort(base_times.begin(), base_times.end()); - std::sort(base_execution.begin(), base_execution.end()); - - std::sort(cur_calls.begin(), cur_calls.end()); - std::sort(cur_times.begin(), cur_times.end()); - std::sort(cur_execution.begin(), cur_execution.end()); - - //remove outliers - const U32 OUTLIER_CUTOFF = 512; - if (base_times.size() > OUTLIER_CUTOFF) - { - ll_remove_outliers(base_times, 1.f); - } - - if (base_execution.size() > OUTLIER_CUTOFF) - { - ll_remove_outliers(base_execution, 1.f); - } - - if (cur_times.size() > OUTLIER_CUTOFF) - { - ll_remove_outliers(cur_times, 1.f); - } - - if (cur_execution.size() > OUTLIER_CUTOFF) - { - ll_remove_outliers(cur_execution, 1.f); - } - - - max_time = llmax(base_times.empty() ? 0.0 : *base_times.rbegin(), cur_times.empty() ? 0.0 : *cur_times.rbegin()); - max_calls = llmax(base_calls.empty() ? 0 : *base_calls.rbegin(), cur_calls.empty() ? 0 : *cur_calls.rbegin()); - max_execution = llmax(base_execution.empty() ? 0.0 : *base_execution.rbegin(), cur_execution.empty() ? 0.0 : *cur_execution.rbegin()); - - - LLVector3 last_p; - - //==================================== - // basic - //==================================== - buffer.clear(); - - last_p.clear(); - - LLGLDisable cull(GL_CULL_FACE); - - LLVector3 base_col(0, 0.7f, 0.f); - LLVector3 cur_col(1.f, 0.f, 0.f); - - gGL.setSceneBlendType(LLRender::BT_ADD); - - gGL.color3fv(base_col.mV); - for (U32 i = 0; i < base_times.size(); ++i) - { - gGL.begin(LLRender::TRIANGLE_STRIP); - gGL.vertex3fv(last_p.mV); - gGL.vertex3f(last_p.mV[0], 0.f, 0.f); - last_p.set((F32)i/(F32) base_times.size(), base_times[i]/max_time, 0.f); - gGL.vertex3fv(last_p.mV); - gGL.vertex3f(last_p.mV[0], 0.f, 0.f); - gGL.end(); - } - - gGL.flush(); - - - last_p.clear(); - { - LLGLEnable blend(GL_BLEND); - - gGL.color3fv(cur_col.mV); - for (U32 i = 0; i < cur_times.size(); ++i) - { - gGL.begin(LLRender::TRIANGLE_STRIP); - gGL.vertex3f(last_p.mV[0], 0.f, 0.f); - gGL.vertex3fv(last_p.mV); - last_p.set((F32) i / (F32) cur_times.size(), cur_times[i]/max_time, 0.f); - gGL.vertex3f(last_p.mV[0], 0.f, 0.f); - gGL.vertex3fv(last_p.mV); - gGL.end(); - } - - gGL.flush(); - } - - saveChart(label, "time", scratch); - - //====================================== - // calls - //====================================== - buffer.clear(); - - last_p.clear(); - - gGL.color3fv(base_col.mV); - for (U32 i = 0; i < base_calls.size(); ++i) - { - gGL.begin(LLRender::TRIANGLE_STRIP); - gGL.vertex3fv(last_p.mV); - gGL.vertex3f(last_p.mV[0], 0.f, 0.f); - last_p.set((F32) i / (F32) base_calls.size(), (F32)base_calls[i]/max_calls, 0.f); - gGL.vertex3fv(last_p.mV); - gGL.vertex3f(last_p.mV[0], 0.f, 0.f); - gGL.end(); - } - - gGL.flush(); - - { - LLGLEnable blend(GL_BLEND); - gGL.color3fv(cur_col.mV); - last_p.clear(); - - for (U32 i = 0; i < cur_calls.size(); ++i) - { - gGL.begin(LLRender::TRIANGLE_STRIP); - gGL.vertex3f(last_p.mV[0], 0.f, 0.f); - gGL.vertex3fv(last_p.mV); - last_p.set((F32) i / (F32) cur_calls.size(), (F32) cur_calls[i]/max_calls, 0.f); - gGL.vertex3f(last_p.mV[0], 0.f, 0.f); - gGL.vertex3fv(last_p.mV); - gGL.end(); - - } - - gGL.flush(); - } - - saveChart(label, "calls", scratch); - - //====================================== - // execution - //====================================== - buffer.clear(); - - gGL.color3fv(base_col.mV); - U32 count = 0; - U32 total_count = base_execution.size(); - - last_p.clear(); - - for (std::vector::iterator iter = base_execution.begin(); iter != base_execution.end(); ++iter) - { - gGL.begin(LLRender::TRIANGLE_STRIP); - gGL.vertex3fv(last_p.mV); - gGL.vertex3f(last_p.mV[0], 0.f, 0.f); - last_p.set((F32)count/(F32)total_count, *iter/max_execution, 0.f); - gGL.vertex3fv(last_p.mV); - gGL.vertex3f(last_p.mV[0], 0.f, 0.f); - gGL.end(); - count++; - } - - last_p.clear(); - - { - LLGLEnable blend(GL_BLEND); - gGL.color3fv(cur_col.mV); - count = 0; - total_count = cur_execution.size(); - - for (std::vector::iterator iter = cur_execution.begin(); iter != cur_execution.end(); ++iter) - { - gGL.begin(LLRender::TRIANGLE_STRIP); - gGL.vertex3f(last_p.mV[0], 0.f, 0.f); - gGL.vertex3fv(last_p.mV); - last_p.set((F32)count/(F32)total_count, *iter/max_execution, 0.f); - gGL.vertex3f(last_p.mV[0], 0.f, 0.f); - gGL.vertex3fv(last_p.mV); - gGL.end(); - count++; - } - - gGL.flush(); - } - - saveChart(label, "execution", scratch); - } - - buffer.flush(); - - gGL.popMatrix(); - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.popMatrix(); -} - -//static -LLSD LLFastTimerView::analyzePerformanceLogDefault(std::istream& is) -{ - LLSD ret; - - LLSD cur; - - LLSD::Real total_time = 0.0; - LLSD::Integer total_frames = 0; - - typedef std::map stats_map_t; - stats_map_t time_stats; - stats_map_t sample_stats; - - while (!is.eof() && LLSDParser::PARSE_FAILURE != LLSDSerialize::fromXML(cur, is)) - { - for (LLSD::map_iterator iter = cur.beginMap(); iter != cur.endMap(); ++iter) - { - std::string label = iter->first; - - F64 time = iter->second["Time"].asReal(); - - // Skip the total figure - if(label.compare("Total") != 0) - { - total_time += time; - } - - if (time > 0.0) - { - LLSD::Integer samples = iter->second["Calls"].asInteger(); - - time_stats[label].push(time); - sample_stats[label].push(samples); - } - } - total_frames++; - } - - for(stats_map_t::iterator it = time_stats.begin(); it != time_stats.end(); ++it) - { - std::string label = it->first; - ret[label]["TotalTime"] = time_stats[label].getSum(); - ret[label]["MeanTime"] = time_stats[label].getMean(); - ret[label]["MaxTime"] = time_stats[label].getMaxValue(); - ret[label]["MinTime"] = time_stats[label].getMinValue(); - ret[label]["StdDevTime"] = time_stats[label].getStdDev(); - - ret[label]["Samples"] = sample_stats[label].getSum(); - ret[label]["MaxSamples"] = sample_stats[label].getMaxValue(); - ret[label]["MinSamples"] = sample_stats[label].getMinValue(); - ret[label]["StdDevSamples"] = sample_stats[label].getStdDev(); - - ret[label]["Frames"] = (LLSD::Integer)time_stats[label].getCount(); - } - - ret["SessionTime"] = total_time; - ret["FrameCount"] = total_frames; - - return ret; - -} - -//static -void LLFastTimerView::doAnalysisDefault(std::string baseline, std::string target, std::string output) -{ - // Open baseline and current target, exit if one is inexistent - llifstream base_is(baseline.c_str()); - llifstream target_is(target.c_str()); - if (!base_is.is_open() || !target_is.is_open()) - { - LL_WARNS() << "'-analyzeperformance' error : baseline or current target file inexistent" << LL_ENDL; - base_is.close(); - target_is.close(); - return; - } - - //analyze baseline - LLSD base = analyzePerformanceLogDefault(base_is); - base_is.close(); - - //analyze current - LLSD current = analyzePerformanceLogDefault(target_is); - target_is.close(); - - //output comparison - llofstream os(output.c_str()); - - LLSD::Real session_time = current["SessionTime"].asReal(); - os << - "Label, " - "% Change, " - "% of Session, " - "Cur Min, " - "Cur Max, " - "Cur Mean/sample, " - "Cur Mean/frame, " - "Cur StdDev/frame, " - "Cur Total, " - "Cur Frames, " - "Cur Samples, " - "Base Min, " - "Base Max, " - "Base Mean/sample, " - "Base Mean/frame, " - "Base StdDev/frame, " - "Base Total, " - "Base Frames, " - "Base Samples\n"; - - for (LLSD::map_iterator iter = base.beginMap(); iter != base.endMap(); ++iter) - { - LLSD::String label = iter->first; - - if (current[label]["Samples"].asInteger() == 0 || - base[label]["Samples"].asInteger() == 0) - { - //cannot compare - continue; - } - LLSD::Real a = base[label]["TotalTime"].asReal() / base[label]["Samples"].asReal(); - LLSD::Real b = current[label]["TotalTime"].asReal() / current[label]["Samples"].asReal(); - - LLSD::Real diff = b-a; - - LLSD::Real perc = diff/a * 100; - - os << llformat("%s, %.2f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %d, %d, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %d, %d\n", - label.c_str(), - (F32) perc, - (F32) (current[label]["TotalTime"].asReal()/session_time * 100.0), - - (F32) current[label]["MinTime"].asReal(), - (F32) current[label]["MaxTime"].asReal(), - (F32) b, - (F32) current[label]["MeanTime"].asReal(), - (F32) current[label]["StdDevTime"].asReal(), - (F32) current[label]["TotalTime"].asReal(), - current[label]["Frames"].asInteger(), - current[label]["Samples"].asInteger(), - (F32) base[label]["MinTime"].asReal(), - (F32) base[label]["MaxTime"].asReal(), - (F32) a, - (F32) base[label]["MeanTime"].asReal(), - (F32) base[label]["StdDevTime"].asReal(), - (F32) base[label]["TotalTime"].asReal(), - base[label]["Frames"].asInteger(), - base[label]["Samples"].asInteger()); - } - - exportCharts(baseline, target); - - os.flush(); - os.close(); -} - -//static -void LLFastTimerView::outputAllMetrics() -{ - if (LLMetricPerformanceTesterBasic::hasMetricPerformanceTesters()) - { - for (LLMetricPerformanceTesterBasic::name_tester_map_t::iterator iter = LLMetricPerformanceTesterBasic::sTesterMap.begin(); - iter != LLMetricPerformanceTesterBasic::sTesterMap.end(); ++iter) - { - LLMetricPerformanceTesterBasic* tester = ((LLMetricPerformanceTesterBasic*)iter->second); - tester->outputTestResults(); - } - } -} - -//static -void LLFastTimerView::doAnalysis(std::string baseline, std::string target, std::string output) -{ - if(BlockTimer::sLog) - { - doAnalysisDefault(baseline, target, output) ; - return ; - } - - if(BlockTimer::sMetricLog) - { - LLMetricPerformanceTesterBasic::doAnalysisMetrics(baseline, target, output) ; - return ; - } -} - - -void LLFastTimerView::printLineStats() -{ - // Output stats for clicked bar to log - if (mStatsIndex >= 0) - { - std::string legend_stat; - bool first = true; - for(LLTrace::block_timer_tree_df_iterator_t it = LLTrace::begin_block_timer_tree_df(FTM_FRAME); - it != LLTrace::end_block_timer_tree_df(); - ++it) - { - BlockTimerStatHandle* idp = (*it); - - if (!first) - { - legend_stat += ", "; - } - first = false; - legend_stat += idp->getName(); - - //if (idp->getTreeNode().mCollapsed) - //{ - // it.skipDescendants(); - //} - } - LL_INFOS() << legend_stat << LL_ENDL; - - std::string timer_stat; - first = true; - for(LLTrace::block_timer_tree_df_iterator_t it = LLTrace::begin_block_timer_tree_df(FTM_FRAME); - it != LLTrace::end_block_timer_tree_df(); - ++it) - { - BlockTimerStatHandle* idp = (*it); - - if (!first) - { - timer_stat += ", "; - } - first = false; - - F32Seconds ticks; - if (mStatsIndex == 0) - { - ticks = mRecording.getPeriodMean(*idp, RUNNING_AVERAGE_WIDTH); - } - else - { - ticks = mRecording.getPrevRecording(mStatsIndex).getSum(*idp); - } - F32Milliseconds ms = ticks; - - timer_stat += llformat("%.1f",ms.value()); - - //if (idp->getTreeNode().mCollapsed) - //{ - // it.skipDescendants(); - //} - } - LL_INFOS() << timer_stat << LL_ENDL; - mStatsIndex = -1; - } -} - -void LLFastTimerView::drawLineGraph() -{ - LL_PROFILE_ZONE_SCOPED; - //draw line graph history - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - LLLocalClipRect clip(mGraphRect); - - //normalize based on last frame's maximum - static F32Seconds max_time(0.000001); - static U32 max_calls = 0; - static F32 alpha_interp = 0.f; - - //highlight visible range - { - S32 first_frame = mRecording.getNumRecordedPeriods() - mScrollIndex; - S32 last_frame = first_frame - MAX_VISIBLE_HISTORY; - - F32 frame_delta = ((F32) (mGraphRect.getWidth()))/(mRecording.getNumRecordedPeriods()-1); - - F32 right = (F32) mGraphRect.mLeft + frame_delta*first_frame; - F32 left = (F32) mGraphRect.mLeft + frame_delta*last_frame; - - gGL.color4f(0.5f,0.5f,0.5f,0.3f); - gl_rect_2d((S32) left, mGraphRect.mTop, (S32) right, mGraphRect.mBottom); - - if (mHoverBarIndex > 0) - { - S32 bar_frame = first_frame - (mScrollIndex + mHoverBarIndex) - 1; - F32 bar = (F32) mGraphRect.mLeft + frame_delta*bar_frame; - - gGL.color4f(0.5f,0.5f,0.5f,1); - - gGL.begin(LLRender::LINES); - gGL.vertex2i((S32)bar, mGraphRect.mBottom); - gGL.vertex2i((S32)bar, mGraphRect.mTop); - gGL.end(); - } - } - - F32Seconds cur_max(0); - U32 cur_max_calls = 0; - - for(LLTrace::block_timer_tree_df_iterator_t it = LLTrace::begin_block_timer_tree_df(FTM_FRAME); - it != LLTrace::end_block_timer_tree_df(); - ++it) - { - BlockTimerStatHandle* idp = (*it); - - //fatten highlighted timer - if (mHoverID == idp) - { - gGL.flush(); - glLineWidth(3); - } - - llassert(idp->getIndex() < sTimerColors.size()); - const F32 * col = sTimerColors[idp->getIndex()].mV;// ft_display_table[idx].color->mV; - - F32 alpha = 1.f; - bool is_hover_timer = true; - - if (mHoverID != NULL && - mHoverID != idp) - { //fade out non-highlighted timers - if (idp->getParent() != mHoverID) - { - alpha = alpha_interp; - is_hover_timer = false; - } - } - - gGL.color4f(col[0], col[1], col[2], alpha); - gGL.begin(LLRender::TRIANGLE_STRIP); - F32 call_scale_factor = (F32)mGraphRect.getHeight() / (F32)max_calls; - F32 time_scale_factor = (F32)mGraphRect.getHeight() / max_time.value(); - F32 hz_scale_factor = (F32) mGraphRect.getHeight() / (1.f / max_time.value()); - - for (U32 j = mRecording.getNumRecordedPeriods(); - j > 0; - j--) - { - LLTrace::Recording& recording = mRecording.getPrevRecording(j); - F32Seconds time = llmax(recording.getSum(*idp), F64Seconds(0.000001)); - U32 calls = recording.getSum(idp->callCount()); - - if (is_hover_timer) - { - //normalize to highlighted timer - cur_max = llmax(cur_max, time); - cur_max_calls = llmax(cur_max_calls, calls); - } - F32 x = mGraphRect.mRight - j * (F32)(mGraphRect.getWidth())/(mRecording.getNumRecordedPeriods()-1); - F32 y; - switch(mDisplayType) -{ - case DISPLAY_TIME: - y = mGraphRect.mBottom + time.value() * time_scale_factor; - break; - case DISPLAY_CALLS: - y = mGraphRect.mBottom + (F32)calls * call_scale_factor; - break; - case DISPLAY_HZ: - y = mGraphRect.mBottom + (1.f / time.value()) * hz_scale_factor; - break; - } - gGL.vertex2f(x,y); - gGL.vertex2f(x,mGraphRect.mBottom); - } - gGL.end(); - - if (mHoverID == idp) - { - gGL.flush(); - glLineWidth(1); - } - - if (idp->getTreeNode().mCollapsed) - { - //skip hidden timers - it.skipDescendants(); - } - } - - //interpolate towards new maximum - max_time = (F32Seconds)lerp(max_time.value(), cur_max.value(), LLSmoothInterpolation::getInterpolant(0.1f)); - if (llabs((max_time - cur_max).value()) <= 1) - { - max_time = llmax(F32Microseconds(1.f), F32Microseconds(cur_max)); - } - - max_calls = ll_round(lerp((F32)max_calls, (F32) cur_max_calls, LLSmoothInterpolation::getInterpolant(0.1f))); - if (llabs((S32)(max_calls - cur_max_calls)) <= 1) - { - max_calls = cur_max_calls; - } - - // TODO: make sure alpha is correct in DisplayHz mode - F32 alpha_target = (max_time > cur_max) - ? llmin(max_time / cur_max - 1.f,1.f) - : llmin(cur_max/ max_time - 1.f,1.f); - alpha_interp = lerp(alpha_interp, alpha_target, LLSmoothInterpolation::getInterpolant(0.1f)); - - if (mHoverID != NULL) - { - S32 x = (mGraphRect.mRight + mGraphRect.mLeft)/2; - S32 y = mGraphRect.mBottom + 8; - - LLFontGL::getFontMonospace()->renderUTF8( - mHoverID->getName(), - 0, - x, y, - LLColor4::white, - LLFontGL::LEFT, LLFontGL::BOTTOM); - } - - //display y-axis range - std::string axis_label; - switch(mDisplayType) - { - case DISPLAY_TIME: - axis_label = llformat("%4.2f ms", F32Milliseconds(max_time).value()); - break; - case DISPLAY_CALLS: - axis_label = llformat("%d calls", (int)max_calls); - break; - case DISPLAY_HZ: - axis_label = llformat("%4.2f Hz", max_time.value() ? 1.f / max_time.value() : 0.f); - break; - } - - LLFontGL* font = LLFontGL::getFontMonospace(); - S32 x = mGraphRect.mRight - font->getWidth(axis_label)-5; - S32 y = mGraphRect.mTop - font->getLineHeight();; - - font->renderUTF8(axis_label, 0, x, y, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP); -} - -void LLFastTimerView::drawLegend() -{ - // draw legend - S32 dx; - S32 x = mLegendRect.mLeft; - S32 y = mLegendRect.mTop; - const S32 TEXT_HEIGHT = (S32)LLFontGL::getFontMonospace()->getLineHeight(); - - { - LLLocalClipRect clip(mLegendRect); - S32 cur_line = 0; - S32 scroll_offset = 0; // element's y offset from top of the inner scroll's rect - ft_display_idx.clear(); - std::map display_line; - for (LLTrace::block_timer_tree_df_iterator_t it = LLTrace::begin_block_timer_tree_df(FTM_FRAME); - it != LLTrace::end_block_timer_tree_df(); - ++it) - { - BlockTimerStatHandle* idp = (*it); - // Needed to figure out offsets and parenting - display_line[idp] = cur_line; - cur_line++; - if (scroll_offset < mScrollBar->getDocPos()) - { - // only offset for visible items - scroll_offset += TEXT_HEIGHT + 2; - if (idp->getTreeNode().mCollapsed) - { - it.skipDescendants(); - } - continue; - } - - // used for mouse clicks - ft_display_idx.push_back(idp); - - // Actual draw, first bar (square), then text - x = MARGIN; - - LLRect bar_rect(x, y, x + TEXT_HEIGHT, y - TEXT_HEIGHT); - S32 scale_offset = 0; - if (idp == mHoverID) - { - scale_offset = llfloor(sinf(mHighlightTimer.getElapsedTimeF32() * 6.f) * 2.f); - } - bar_rect.stretch(scale_offset); - llassert(idp->getIndex() < sTimerColors.size()); - gl_rect_2d(bar_rect, sTimerColors[idp->getIndex()]); - - F32Milliseconds ms(0); - S32 calls = 0; - if (mHoverBarIndex > 0 && mHoverID) - { - S32 hidx = mScrollIndex + mHoverBarIndex; - ms = mRecording.getPrevRecording(hidx).getSum(*idp); - calls = mRecording.getPrevRecording(hidx).getSum(idp->callCount()); - } - else - { - ms = F64Seconds(mRecording.getPeriodMean(*idp, RUNNING_AVERAGE_WIDTH)); - calls = (S32)mRecording.getPeriodMean(idp->callCount(), RUNNING_AVERAGE_WIDTH); - } - - std::string timer_label; - switch(mDisplayType) - { - case DISPLAY_TIME: - timer_label = llformat("%s [%.1f]",idp->getName().c_str(),ms.value()); - break; - case DISPLAY_CALLS: - timer_label = llformat("%s (%d)",idp->getName().c_str(),calls); - break; - case DISPLAY_HZ: - timer_label = llformat("%.1f", ms.value() ? (1.f / ms.value()) : 0.f); - break; - } - dx = (TEXT_HEIGHT+4) + get_depth(idp)*8; - - LLColor4 color = LLColor4::white; - if (get_depth(idp) > 0) - { - S32 line_start_y = bar_rect.getCenterY(); - S32 line_end_y = line_start_y + ((TEXT_HEIGHT + 2) * (cur_line - display_line[idp->getParent()])) - TEXT_HEIGHT; - gl_line_2d(x + dx - 8, line_start_y, x + dx, line_start_y, color); - S32 line_x = x + (TEXT_HEIGHT + 4) + ((get_depth(idp) - 1) * 8); - gl_line_2d(line_x, line_start_y, line_x, line_end_y, color); - if (idp->getTreeNode().mCollapsed && !idp->getChildren().empty()) - { - gl_line_2d(line_x+4, line_start_y-3, line_x+4, line_start_y+4, color); - } - } - - x += dx; - bool is_child_of_hover_item = (idp == mHoverID); - BlockTimerStatHandle* next_parent = idp->getParent(); - while(!is_child_of_hover_item && next_parent) - { - is_child_of_hover_item = (mHoverID == next_parent); - if (next_parent->getParent() == next_parent) break; - next_parent = next_parent->getParent(); - } - - LLFontGL::getFontMonospace()->renderUTF8(timer_label, 0, - x, y, - color, - LLFontGL::LEFT, LLFontGL::TOP, - is_child_of_hover_item ? LLFontGL::BOLD : LLFontGL::NORMAL); - - y -= (TEXT_HEIGHT + 2); - - scroll_offset += TEXT_HEIGHT + 2; - if (idp->getTreeNode().mCollapsed) - { - it.skipDescendants(); - } - } - // Recalculate scroll size - mScrollBar->setDocSize(scroll_offset - mLegendRect.getHeight()); - } -} - -void LLFastTimerView::generateUniqueColors() -{ - // generate unique colors - { - sTimerColors.resize(LLTrace::BlockTimerStatHandle::getNumIndices()); - sTimerColors[FTM_FRAME.getIndex()] = LLColor4::grey; - - F32 hue = 0.f; - - for (LLTrace::block_timer_tree_df_iterator_t it = LLTrace::begin_block_timer_tree_df(FTM_FRAME); - it != LLTrace::end_block_timer_tree_df(); - ++it) - { - BlockTimerStatHandle* idp = (*it); - - const F32 HUE_INCREMENT = 0.23f; - hue = fmodf(hue + HUE_INCREMENT, 1.f); - // saturation increases with depth - F32 saturation = clamp_rescale((F32)get_depth(idp), 0.f, 3.f, 0.f, 1.f); - // lightness alternates with depth - F32 lightness = get_depth(idp) % 2 ? 0.5f : 0.6f; - - LLColor4 child_color; - child_color.setHSL(hue, saturation, lightness); - - llassert(idp->getIndex() < sTimerColors.size()); - sTimerColors[idp->getIndex()] = child_color; - } - } -} - -void LLFastTimerView::drawHelp( S32 y ) -{ - // Draw some help - const S32 texth = (S32)LLFontGL::getFontMonospace()->getLineHeight(); - - y -= (texth + 2); - y -= (texth + 2); - - LLFontGL::getFontMonospace()->renderUTF8(std::string("[Right-Click log selected]"), - 0, MARGIN, y, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP); -} - -void LLFastTimerView::drawTicks() -{ - // Draw MS ticks - { - U32Milliseconds ms = mTotalTimeDisplay; - std::string tick_label; - S32 x; - S32 barw = mBarRect.getWidth(); - - tick_label = llformat("%.1f ms |", (F32)ms.value()*.25f); - x = mBarRect.mLeft + barw/4 - LLFontGL::getFontMonospace()->getWidth(tick_label); - LLFontGL::getFontMonospace()->renderUTF8(tick_label, 0, x, mBarRect.mTop, LLColor4::white, - LLFontGL::LEFT, LLFontGL::TOP); - - tick_label = llformat("%.1f ms |", (F32)ms.value()*.50f); - x = mBarRect.mLeft + barw/2 - LLFontGL::getFontMonospace()->getWidth(tick_label); - LLFontGL::getFontMonospace()->renderUTF8(tick_label, 0, x, mBarRect.mTop, LLColor4::white, - LLFontGL::LEFT, LLFontGL::TOP); - - tick_label = llformat("%.1f ms |", (F32)ms.value()*.75f); - x = mBarRect.mLeft + (barw*3)/4 - LLFontGL::getFontMonospace()->getWidth(tick_label); - LLFontGL::getFontMonospace()->renderUTF8(tick_label, 0, x, mBarRect.mTop, LLColor4::white, - LLFontGL::LEFT, LLFontGL::TOP); - - tick_label = llformat( "%d ms |", (U32)ms.value()); - x = mBarRect.mLeft + barw - LLFontGL::getFontMonospace()->getWidth(tick_label); - LLFontGL::getFontMonospace()->renderUTF8(tick_label, 0, x, mBarRect.mTop, LLColor4::white, - LLFontGL::LEFT, LLFontGL::TOP); - } -} - -void LLFastTimerView::drawBorders( S32 y, const S32 x_start, S32 bar_height, S32 dy ) -{ - // Draw borders - { - S32 by = y + 6 + (S32)LLFontGL::getFontMonospace()->getLineHeight(); - - //heading - gl_rect_2d(x_start-5, by, getRect().getWidth()-5, y+5, LLColor4::grey, false); - - //tree view - gl_rect_2d(5, by, x_start-10, 5, LLColor4::grey, false); - - by = y + 5; - //average bar - gl_rect_2d(x_start-5, by, getRect().getWidth()-5, by-bar_height-dy-5, LLColor4::grey, false); - - by -= bar_height*2+dy; - - //current frame bar - gl_rect_2d(x_start-5, by, getRect().getWidth()-5, by-bar_height-dy-2, LLColor4::grey, false); - - by -= bar_height+dy+1; - - //history bars - gl_rect_2d(x_start-5, by, getRect().getWidth()-5, LINE_GRAPH_HEIGHT-bar_height-dy-2, LLColor4::grey, false); - - by = LINE_GRAPH_HEIGHT-dy; - - //line graph - //mGraphRect = LLRect(x_start-5, by, getRect().getWidth()-5, 5); - - gl_rect_2d(mGraphRect, false); - } -} - -void LLFastTimerView::updateTotalTime() -{ - switch(mDisplayMode) - { - case 0: - mTotalTimeDisplay = mRecording.getPeriodMean(FTM_FRAME, RUNNING_AVERAGE_WIDTH)*2; - break; - case 1: - mTotalTimeDisplay = mRecording.getPeriodMax(FTM_FRAME); - break; - case 2: - // Calculate the max total ticks for the current history - mTotalTimeDisplay = mRecording.getPeriodMax(FTM_FRAME, 20); - break; - default: - mTotalTimeDisplay = F64Milliseconds(100); - break; - } - - mTotalTimeDisplay = LLUnits::Milliseconds::fromValue(llceil(mTotalTimeDisplay.valueInUnits() / 20.f) * 20.f); -} - -void LLFastTimerView::drawBars() -{ - LL_PROFILE_ZONE_SCOPED; - LLLocalClipRect clip(mBarRect); - - S32 bar_height = mBarRect.getHeight() / (MAX_VISIBLE_HISTORY + 2); - const S32 vpad = llmax(1, bar_height / 4); // spacing between bars - bar_height -= vpad; - - updateTotalTime(); - if (mTotalTimeDisplay <= (F32Seconds)0.0) return; - - drawTicks(); - const S32 bars_top = mBarRect.mTop - ((S32)LLFontGL::getFontMonospace()->getLineHeight() + 4); - drawBorders(bars_top, mBarRect.mLeft, bar_height, vpad); - - // Draw bars for each history entry - // Special: 0 = show running average - LLPointer bar_image = LLUI::getUIImage("Rounded_Square"); - - const S32 image_width = bar_image->getTextureWidth(); - const S32 image_height = bar_image->getTextureHeight(); - - gGL.getTexUnit(0)->bind(bar_image->getImage()); - { - const S32 histmax = (S32)mRecording.getNumRecordedPeriods(); - - // update widths - if (!mPauseHistory) - { - U32 bar_index = 0; - if (!mAverageTimerRow.mBars) - { - mAverageTimerRow.mBars = new TimerBar[LLTrace::BlockTimerStatHandle::instance_tracker_t::instanceCount()]; - } - updateTimerBarWidths(&FTM_FRAME, mAverageTimerRow, -1, bar_index); - updateTimerBarOffsets(&FTM_FRAME, mAverageTimerRow); - - for (S32 history_index = 1; history_index <= histmax; history_index++) - { - llassert(history_index <= mTimerBarRows.size()); - TimerBarRow& row = mTimerBarRows[history_index - 1]; - bar_index = 0; - if (!row.mBars) - { - row.mBars = new TimerBar[LLTrace::BlockTimerStatHandle::instance_tracker_t::instanceCount()]; - updateTimerBarWidths(&FTM_FRAME, row, history_index, bar_index); - updateTimerBarOffsets(&FTM_FRAME, row); - } - } - } - - // draw bars - LLRect frame_bar_rect; - frame_bar_rect.setLeftTopAndSize(mBarRect.mLeft, - bars_top, - ll_round((mAverageTimerRow.mBars[0].mTotalTime / mTotalTimeDisplay) * mBarRect.getWidth()), - bar_height); - mAverageTimerRow.mTop = frame_bar_rect.mTop; - mAverageTimerRow.mBottom = frame_bar_rect.mBottom; - drawBar(frame_bar_rect, mAverageTimerRow, image_width, image_height); - frame_bar_rect.translate(0, -(bar_height + vpad + bar_height)); - - for(S32 bar_index = mScrollIndex; bar_index < llmin(histmax, mScrollIndex + MAX_VISIBLE_HISTORY); ++bar_index) - { - llassert(bar_index < mTimerBarRows.size()); - TimerBarRow& row = mTimerBarRows[bar_index]; - row.mTop = frame_bar_rect.mTop; - row.mBottom = frame_bar_rect.mBottom; - frame_bar_rect.mRight = frame_bar_rect.mLeft - + ll_round((row.mBars[0].mTotalTime / mTotalTimeDisplay) * mBarRect.getWidth()); - drawBar(frame_bar_rect, row, image_width, image_height); - - frame_bar_rect.translate(0, -(bar_height + vpad)); - } - - } - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); -} - -F32Seconds LLFastTimerView::updateTimerBarWidths(LLTrace::BlockTimerStatHandle* time_block, TimerBarRow& row, S32 history_index, U32& bar_index) -{ - LL_PROFILE_ZONE_SCOPED; - const F32Seconds self_time = history_index == -1 - ? mRecording.getPeriodMean(time_block->selfTime(), RUNNING_AVERAGE_WIDTH) - : mRecording.getPrevRecording(history_index).getSum(time_block->selfTime()); - - F32Seconds full_time = self_time; - - // reserve a spot for this bar to be rendered before its children - // even though we don't know its size yet - TimerBar& timer_bar = row.mBars[bar_index]; - bar_index++; - - for (BlockTimerStatHandle::child_iter it = time_block->beginChildren(), end_it = time_block->endChildren(); it != end_it; ++it) - { - full_time += updateTimerBarWidths(*it, row, history_index, bar_index); - } - - timer_bar.mTotalTime = full_time; - timer_bar.mSelfTime = self_time; - timer_bar.mTimeBlock = time_block; - - return full_time; -} - -S32 LLFastTimerView::updateTimerBarOffsets(LLTrace::BlockTimerStatHandle* time_block, TimerBarRow& row, S32 timer_bar_index) -{ - LL_PROFILE_ZONE_SCOPED; - - TimerBar& timer_bar = row.mBars[timer_bar_index]; - const F32Seconds bar_time = timer_bar.mTotalTime - timer_bar.mSelfTime; - timer_bar.mChildrenStart = timer_bar.mSelfStart + timer_bar.mSelfTime / 2; - timer_bar.mChildrenEnd = timer_bar.mChildrenStart + timer_bar.mTotalTime - timer_bar.mSelfTime; - - if (timer_bar_index == 0) - { - timer_bar.mSelfStart = F32Seconds(0.f); - timer_bar.mSelfEnd = bar_time; - } - - //now loop through children and figure out portion of bar image covered by each bar, now that we know the - //sum of all children - F32 bar_fraction_start = 0.f; - TimerBar* last_child_timer_bar = NULL; - - bool first_child = true; - for (BlockTimerStatHandle::child_iter it = time_block->beginChildren(), end_it = time_block->endChildren(); - it != end_it; - ++it) - { - timer_bar_index++; - - TimerBar& child_timer_bar = row.mBars[timer_bar_index]; - BlockTimerStatHandle* child_time_block = *it; - - if (last_child_timer_bar) - { - last_child_timer_bar->mLastChild = false; - } - child_timer_bar.mLastChild = true; - last_child_timer_bar = &child_timer_bar; - - child_timer_bar.mFirstChild = first_child; - if (first_child) - { - first_child = false; - } - - child_timer_bar.mStartFraction = bar_fraction_start; - child_timer_bar.mEndFraction = bar_time > (S32Seconds)0 - ? bar_fraction_start + child_timer_bar.mTotalTime / bar_time - : 1.f; - child_timer_bar.mSelfStart = timer_bar.mChildrenStart - + child_timer_bar.mStartFraction - * (timer_bar.mChildrenEnd - timer_bar.mChildrenStart); - child_timer_bar.mSelfEnd = timer_bar.mChildrenStart - + child_timer_bar.mEndFraction - * (timer_bar.mChildrenEnd - timer_bar.mChildrenStart); - - timer_bar_index = updateTimerBarOffsets(child_time_block, row, timer_bar_index); - - bar_fraction_start = child_timer_bar.mEndFraction; - } - return timer_bar_index; -} - -S32 LLFastTimerView::drawBar(LLRect bar_rect, TimerBarRow& row, S32 image_width, S32 image_height, bool hovered, bool visible, S32 bar_index) -{ - LL_PROFILE_ZONE_SCOPED; - TimerBar& timer_bar = row.mBars[bar_index]; - LLTrace::BlockTimerStatHandle* time_block = timer_bar.mTimeBlock; - - hovered |= mHoverID == time_block; - - // animate scale of bar when hovering over that particular timer - if (visible && (F32)bar_rect.getWidth() * (timer_bar.mEndFraction - timer_bar.mStartFraction) > 2.f) - { - LLRect render_rect(bar_rect); - S32 scale_offset = 0; - if (mHoverID == time_block) - { - scale_offset = llfloor(sinf(mHighlightTimer.getElapsedTimeF32() * 6.f) * 3.f); - render_rect.mTop += scale_offset; - render_rect.mBottom -= scale_offset; - } - - llassert(time_block->getIndex() < sTimerColors.size()); - LLColor4 color = sTimerColors[time_block->getIndex()]; - if (!hovered) color = lerp(color, LLColor4::grey, 0.2f); - gGL.color4fv(color.mV); - gl_segmented_rect_2d_fragment_tex(render_rect, - image_width, image_height, - 16, - timer_bar.mStartFraction, timer_bar.mEndFraction); - } - - LLRect children_rect; - children_rect.mLeft = ll_round(timer_bar.mChildrenStart / mTotalTimeDisplay * (F32)mBarRect.getWidth()) + mBarRect.mLeft; - children_rect.mRight = ll_round(timer_bar.mChildrenEnd / mTotalTimeDisplay * (F32)mBarRect.getWidth()) + mBarRect.mLeft; - - if (bar_rect.getHeight() > MIN_BAR_HEIGHT) - { - // shrink as we go down a level - children_rect.mTop = bar_rect.mTop - 1; - children_rect.mBottom = bar_rect.mBottom + 1; - } - else - { - children_rect.mTop = bar_rect.mTop; - children_rect.mBottom = bar_rect.mBottom; - } - - bool children_visible = visible && !time_block->getTreeNode().mCollapsed; - - bar_index++; - const U32 num_bars = LLTrace::BlockTimerStatHandle::instance_tracker_t::instanceCount(); - if (bar_index < num_bars && row.mBars[bar_index].mFirstChild) - { - bool is_last = false; - do - { - is_last = row.mBars[bar_index].mLastChild; - bar_index = drawBar(children_rect, row, image_width, image_height, hovered, children_visible, bar_index); - } - while(!is_last && bar_index < num_bars); - } - - return bar_index; -} +/** + * @file llfasttimerview.cpp + * @brief LLFastTimerView class implementation + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfasttimerview.h" + +#include "llviewerwindow.h" +#include "llrect.h" +#include "llcombobox.h" +#include "llerror.h" +#include "llgl.h" +#include "llimagepng.h" +#include "llrender.h" +#include "llrendertarget.h" +#include "lllocalcliprect.h" +#include "lllayoutstack.h" +#include "llmath.h" +#include "llfontgl.h" +#include "llsdserialize.h" +#include "lltooltip.h" +#include "llbutton.h" +#include "llscrollbar.h" + +#include "llappviewer.h" +#include "llviewertexturelist.h" +#include "llui.h" +#include "llviewercontrol.h" + +#include "llfasttimer.h" +#include "lltreeiterators.h" +#include "llmetricperformancetester.h" +#include "llviewerstats.h" + +////////////////////////////////////////////////////////////////////////////// + +using namespace LLTrace; + +static constexpr S32 MAX_VISIBLE_HISTORY = 12; +static constexpr S32 LINE_GRAPH_HEIGHT = 240; +static constexpr S32 MIN_BAR_HEIGHT = 3; +static constexpr S32 RUNNING_AVERAGE_WIDTH = 100; +static constexpr S32 NUM_FRAMES_HISTORY = 200; + +std::vector ft_display_idx; // line of table entry for display purposes (for collapse) + +bool LLFastTimerView::sAnalyzePerformance = false; + +S32 get_depth(const BlockTimerStatHandle* blockp) +{ + S32 depth = 0; + BlockTimerStatHandle* timerp = blockp->getParent(); + while(timerp) + { + depth++; + if (timerp->getParent() == timerp) break; + timerp = timerp->getParent(); + } + return depth; +} + +LLFastTimerView::LLFastTimerView(const LLSD& key) +: LLFloater(key), + mHoverTimer(NULL), + mDisplayMode(0), + mDisplayType(DISPLAY_TIME), + mScrollIndex(0), + mHoverID(NULL), + mHoverBarIndex(-1), + mStatsIndex(-1), + mPauseHistory(false), + mRecording(NUM_FRAMES_HISTORY) +{ + mTimerBarRows.resize(NUM_FRAMES_HISTORY); +} + +LLFastTimerView::~LLFastTimerView() +{ +} + +void LLFastTimerView::onPause() +{ + setPauseState(!mPauseHistory); +} + +void LLFastTimerView::setPauseState(bool pause_state) +{ + if (pause_state == mPauseHistory) return; + + // reset scroll to bottom when unpausing + if (!pause_state) + { + + getChild("pause_btn")->setLabel(getString("pause")); + } + else + { + mScrollIndex = 0; + + getChild("pause_btn")->setLabel(getString("run")); + } + + mPauseHistory = pause_state; +} + +bool LLFastTimerView::postBuild() +{ + LLButton& pause_btn = getChildRef("pause_btn"); + mScrollBar = getChild("scroll_vert"); + + pause_btn.setCommitCallback(boost::bind(&LLFastTimerView::onPause, this)); + return true; +} + +bool LLFastTimerView::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + if (mHoverTimer ) + { + // right click collapses timers + if (!mHoverTimer->getTreeNode().mCollapsed) + { + mHoverTimer->getTreeNode().mCollapsed = true; + } + else if (mHoverTimer->getParent()) + { + mHoverTimer->getParent()->getTreeNode().mCollapsed = true; + } + return true; + } + else if (mBarRect.pointInRect(x, y)) + { + S32 bar_idx = MAX_VISIBLE_HISTORY - ((y - mBarRect.mBottom) * (MAX_VISIBLE_HISTORY + 2) / mBarRect.getHeight()); + bar_idx = llclamp(bar_idx, 0, MAX_VISIBLE_HISTORY); + mStatsIndex = mScrollIndex + bar_idx; + return true; + } + return LLFloater::handleRightMouseDown(x, y, mask); +} + +BlockTimerStatHandle* LLFastTimerView::getLegendID(S32 y) +{ + S32 idx = (mLegendRect.mTop - y) / (LLFontGL::getFontMonospace()->getLineHeight() + 2); + + if (idx >= 0 && idx < (S32)ft_display_idx.size()) + { + return ft_display_idx[idx]; + } + + return NULL; +} + +bool LLFastTimerView::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + for(LLTrace::block_timer_tree_df_iterator_t it = LLTrace::begin_block_timer_tree_df(FTM_FRAME); + it != LLTrace::end_block_timer_tree_df(); + ++it) + { + (*it)->getTreeNode().mCollapsed = false; + } + return true; +} + +bool LLFastTimerView::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (x < mScrollBar->getRect().mLeft) + { + BlockTimerStatHandle* idp = getLegendID(y); + if (idp) + { + idp->getTreeNode().mCollapsed = !idp->getTreeNode().mCollapsed; + } + } + else if (mHoverTimer) + { + //left click drills down by expanding timers + mHoverTimer->getTreeNode().mCollapsed = false; + } + else if (mGraphRect.pointInRect(x, y)) + { + gFocusMgr.setMouseCapture(this); + return true; + } + + return LLFloater::handleMouseDown(x, y, mask); +} + +bool LLFastTimerView::handleMouseUp(S32 x, S32 y, MASK mask) +{ + if (hasMouseCapture()) + { + gFocusMgr.setMouseCapture(NULL); + } + return LLFloater::handleMouseUp(x, y, mask);; +} + +bool LLFastTimerView::handleHover(S32 x, S32 y, MASK mask) +{ + if (hasMouseCapture()) + { + F32 lerp = llclamp(1.f - (F32) (x - mGraphRect.mLeft) / (F32) mGraphRect.getWidth(), 0.f, 1.f); + mScrollIndex = ll_round( lerp * (F32)(mRecording.getNumRecordedPeriods() - MAX_VISIBLE_HISTORY)); + mScrollIndex = llclamp( mScrollIndex, 0, (S32)mRecording.getNumRecordedPeriods()); + return true; + } + mHoverTimer = NULL; + mHoverID = NULL; + + if(mPauseHistory && mBarRect.pointInRect(x, y)) + { + //const S32 bars_top = mBarRect.mTop; + const S32 bars_top = mBarRect.mTop - ((S32)LLFontGL::getFontMonospace()->getLineHeight() + 4); + + mHoverBarIndex = llmin((bars_top - y) / (mBarRect.getHeight() / (MAX_VISIBLE_HISTORY + 2)) - 1, + (S32)mRecording.getNumRecordedPeriods() - 1, + MAX_VISIBLE_HISTORY); + if (mHoverBarIndex == 0) + { + return true; + } + else if (mHoverBarIndex < 0) + { + mHoverBarIndex = 0; + } + + TimerBarRow& row = mHoverBarIndex == 0 ? mAverageTimerRow : mTimerBarRows[mScrollIndex + mHoverBarIndex - 1]; + + TimerBar* hover_bar = NULL; + F32Seconds mouse_time_offset = ((F32)(x - mBarRect.mLeft) / (F32)mBarRect.getWidth()) * mTotalTimeDisplay; + for (int bar_index = 0, end_index = LLTrace::BlockTimerStatHandle::instance_tracker_t::instanceCount(); + bar_index < end_index; + ++bar_index) + { + TimerBar& bar = row.mBars[bar_index]; + if (bar.mSelfStart > mouse_time_offset) + { + break; + } + if (bar.mSelfEnd > mouse_time_offset) + { + hover_bar = &bar; + if (bar.mTimeBlock->getTreeNode().mCollapsed) + { + // stop on first collapsed BlockTimerStatHandle, since we can't select any children + break; + } + } + } + + if (hover_bar) + { + mHoverID = hover_bar->mTimeBlock; + if (mHoverTimer != mHoverID) + { + // could be that existing tooltip is for a parent and is thus + // covering region for this new timer, go ahead and unblock + // so we can create a new tooltip + LLToolTipMgr::instance().unblockToolTips(); + mHoverTimer = mHoverID; + mToolTipRect.set(mBarRect.mLeft + (hover_bar->mSelfStart / mTotalTimeDisplay) * mBarRect.getWidth(), + row.mTop, + mBarRect.mLeft + (hover_bar->mSelfEnd / mTotalTimeDisplay) * mBarRect.getWidth(), + row.mBottom); + } + } + } + else if (x < mScrollBar->getRect().mLeft) + { + BlockTimerStatHandle* timer_id = getLegendID(y); + if (timer_id) + { + mHoverID = timer_id; + } + } + + return LLFloater::handleHover(x, y, mask); +} + + +static std::string get_tooltip(BlockTimerStatHandle& timer, S32 history_index, PeriodicRecording& frame_recording) +{ + std::string tooltip; + if (history_index == 0) + { + // by default, show average number of call + tooltip = llformat("%s (%d ms, %d calls)", timer.getName().c_str(), (S32)F64Milliseconds(frame_recording.getPeriodMean (timer, RUNNING_AVERAGE_WIDTH)).value(), (S32)frame_recording.getPeriodMean(timer.callCount(), RUNNING_AVERAGE_WIDTH)); + } + else + { + tooltip = llformat("%s (%d ms, %d calls)", timer.getName().c_str(), (S32)F64Milliseconds(frame_recording.getPrevRecording(history_index).getSum(timer)).value(), (S32)frame_recording.getPrevRecording(history_index).getSum(timer.callCount())); + } + return tooltip; +} + +bool LLFastTimerView::handleToolTip(S32 x, S32 y, MASK mask) +{ + if(mPauseHistory && mBarRect.pointInRect(x, y)) + { + // tooltips for timer bars + if (mHoverTimer) + { + LLRect screen_rect; + localRectToScreen(mToolTipRect, &screen_rect); + + std::string tooltip = get_tooltip(*mHoverTimer, mHoverBarIndex > 0 ? mScrollIndex + mHoverBarIndex : 0, mRecording); + + LLToolTipMgr::instance().show(LLToolTip::Params() + .message(tooltip) + .sticky_rect(screen_rect) + .delay_time(0.f)); + + return true; + } + } + else + { + // tooltips for timer legend + if (x < mScrollBar->getRect().mLeft) + { + BlockTimerStatHandle* idp = getLegendID(y); + if (idp) + { + LLToolTipMgr::instance().show(get_tooltip(*idp, 0, mRecording)); + + return true; + } + } + } + + return LLFloater::handleToolTip(x, y, mask); +} + +bool LLFastTimerView::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + if (x < mBarRect.mLeft) + { + // Inside mScrollBar and list of timers + mScrollBar->handleScrollWheel(x,y,clicks); + } + else + { + setPauseState(true); + mScrollIndex = llclamp( mScrollIndex + clicks, + 0, + llmin((S32)mRecording.getNumRecordedPeriods(), (S32)mRecording.getNumRecordedPeriods() - MAX_VISIBLE_HISTORY)); + } + return true; +} + +static BlockTimerStatHandle FTM_RENDER_TIMER("Timers"); +static const S32 MARGIN = 10; + +static std::vector sTimerColors; + +void LLFastTimerView::draw() +{ + LL_RECORD_BLOCK_TIME(FTM_RENDER_TIMER); + + if (!mPauseHistory) + { + mRecording.appendRecording(LLTrace::get_frame_recording().getLastRecording()); + mTimerBarRows.pop_back(); + mTimerBarRows.push_front(TimerBarRow()); + } + + mDisplayMode = llclamp(getChild("time_scale_combo")->getCurrentIndex(), 0, 3); + mDisplayType = (EDisplayType)llclamp(getChild("metric_combo")->getCurrentIndex(), 0, 2); + + generateUniqueColors(); + + LLView::drawChildren(); + //getChild("timer_bars_stack")->updateLayout(); + //getChild("legend_stack")->updateLayout(); + LLView* bars_panel = getChildView("bars_panel"); + bars_panel->localRectToOtherView(bars_panel->getLocalRect(), &mBarRect, this); + + LLView* lines_panel = getChildView("lines_panel"); + lines_panel->localRectToOtherView(lines_panel->getLocalRect(), &mGraphRect, this); + + LLView* legend_panel = getChildView("legend"); + legend_panel->localRectToOtherView(legend_panel->getLocalRect(), &mLegendRect, this); + + // Draw the window background + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gl_rect_2d(getLocalRect(), LLColor4(0.f, 0.f, 0.f, 0.25f)); + + drawHelp(getRect().getHeight() - MARGIN); + drawLegend(); + + //mBarRect.mLeft = MARGIN + LEGEND_WIDTH + 8; + //mBarRect.mTop = y; + //mBarRect.mRight = getRect().getWidth() - MARGIN; + //mBarRect.mBottom = MARGIN + LINE_GRAPH_HEIGHT; + + drawBars(); + drawLineGraph(); + printLineStats(); + LLView::draw(); + + mAllTimeMax = llmax(mAllTimeMax, mRecording.getLastRecording().getSum(FTM_FRAME)); + mHoverID = NULL; + mHoverBarIndex = -1; +} + +void LLFastTimerView::onOpen(const LLSD& key) +{ + setPauseState(false); + mRecording.reset(); + mRecording.appendPeriodicRecording(LLTrace::get_frame_recording()); + for(std::deque::iterator it = mTimerBarRows.begin(), end_it = mTimerBarRows.end(); + it != end_it; + ++it) + { + delete []it->mBars; + it->mBars = NULL; + } +} + +void LLFastTimerView::onClose(bool app_quitting) +{ + setVisible(false); +} + +void saveChart(const std::string& label, const char* suffix, LLImageRaw* scratch) +{ + // disable use of glReadPixels which messes up nVidia nSight graphics debugging + if (!LLRender::sNsightDebugSupport) + { + LLImageDataSharedLock lock(scratch); + + //read result back into raw image + glReadPixels(0, 0, 1024, 512, GL_RGB, GL_UNSIGNED_BYTE, scratch->getData()); + + //write results to disk + LLPointer result = new LLImagePNG(); + result->encode(scratch, 0.f); + + std::string ext = result->getExtension(); + std::string filename = llformat("%s_%s.%s", label.c_str(), suffix, ext.c_str()); + + std::string out_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, filename); + result->save(out_file); + } +} + +//static +void LLFastTimerView::exportCharts(const std::string& base, const std::string& target) +{ + //allocate render target for drawing charts + LLRenderTarget buffer; + buffer.allocate(1024,512, GL_RGB); + + + LLSD cur; + + LLSD base_data; + + { //read base log into memory + S32 i = 0; + llifstream is(base.c_str()); + while (!is.eof() && LLSDParser::PARSE_FAILURE != LLSDSerialize::fromXML(cur, is)) + { + base_data[i++] = cur; + } + is.close(); + } + + LLSD cur_data; + std::set chart_names; + + { //read current log into memory + S32 i = 0; + llifstream is(target.c_str()); + while (!is.eof() && LLSDParser::PARSE_FAILURE != LLSDSerialize::fromXML(cur, is)) + { + cur_data[i++] = cur; + + for (LLSD::map_iterator iter = cur.beginMap(); iter != cur.endMap(); ++iter) + { + std::string label = iter->first; + chart_names.insert(label); + } + } + is.close(); + } + + //allocate raw scratch space + LLPointer scratch = new LLImageRaw(1024, 512, 3); + + gGL.pushMatrix(); + gGL.loadIdentity(); + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.loadIdentity(); + gGL.ortho(-0.05f, 1.05f, -0.05f, 1.05f, -1.0f, 1.0f); + + //render charts + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + buffer.bindTarget(); + + for (std::set::iterator iter = chart_names.begin(); iter != chart_names.end(); ++iter) + { + std::string label = *iter; + + LLSD::Real max_time = 0.0; + LLSD::Integer max_calls = 0; + LLSD::Real max_execution = 0.0; + + std::vector cur_execution; + std::vector cur_times; + std::vector cur_calls; + + std::vector base_execution; + std::vector base_times; + std::vector base_calls; + + for (U32 i = 0; i < cur_data.size(); ++i) + { + LLSD::Real time = cur_data[i][label]["Time"].asReal(); + LLSD::Integer calls = cur_data[i][label]["Calls"].asInteger(); + + LLSD::Real execution = 0.0; + if (calls > 0) + { + execution = time/calls; + cur_execution.push_back(execution); + cur_times.push_back(time); + } + + cur_calls.push_back(calls); + } + + for (U32 i = 0; i < base_data.size(); ++i) + { + LLSD::Real time = base_data[i][label]["Time"].asReal(); + LLSD::Integer calls = base_data[i][label]["Calls"].asInteger(); + + LLSD::Real execution = 0.0; + if (calls > 0) + { + execution = time/calls; + base_execution.push_back(execution); + base_times.push_back(time); + } + + base_calls.push_back(calls); + } + + std::sort(base_calls.begin(), base_calls.end()); + std::sort(base_times.begin(), base_times.end()); + std::sort(base_execution.begin(), base_execution.end()); + + std::sort(cur_calls.begin(), cur_calls.end()); + std::sort(cur_times.begin(), cur_times.end()); + std::sort(cur_execution.begin(), cur_execution.end()); + + //remove outliers + const U32 OUTLIER_CUTOFF = 512; + if (base_times.size() > OUTLIER_CUTOFF) + { + ll_remove_outliers(base_times, 1.f); + } + + if (base_execution.size() > OUTLIER_CUTOFF) + { + ll_remove_outliers(base_execution, 1.f); + } + + if (cur_times.size() > OUTLIER_CUTOFF) + { + ll_remove_outliers(cur_times, 1.f); + } + + if (cur_execution.size() > OUTLIER_CUTOFF) + { + ll_remove_outliers(cur_execution, 1.f); + } + + + max_time = llmax(base_times.empty() ? 0.0 : *base_times.rbegin(), cur_times.empty() ? 0.0 : *cur_times.rbegin()); + max_calls = llmax(base_calls.empty() ? 0 : *base_calls.rbegin(), cur_calls.empty() ? 0 : *cur_calls.rbegin()); + max_execution = llmax(base_execution.empty() ? 0.0 : *base_execution.rbegin(), cur_execution.empty() ? 0.0 : *cur_execution.rbegin()); + + + LLVector3 last_p; + + //==================================== + // basic + //==================================== + buffer.clear(); + + last_p.clear(); + + LLGLDisable cull(GL_CULL_FACE); + + LLVector3 base_col(0, 0.7f, 0.f); + LLVector3 cur_col(1.f, 0.f, 0.f); + + gGL.setSceneBlendType(LLRender::BT_ADD); + + gGL.color3fv(base_col.mV); + for (U32 i = 0; i < base_times.size(); ++i) + { + gGL.begin(LLRender::TRIANGLE_STRIP); + gGL.vertex3fv(last_p.mV); + gGL.vertex3f(last_p.mV[0], 0.f, 0.f); + last_p.set((F32)i/(F32) base_times.size(), base_times[i]/max_time, 0.f); + gGL.vertex3fv(last_p.mV); + gGL.vertex3f(last_p.mV[0], 0.f, 0.f); + gGL.end(); + } + + gGL.flush(); + + + last_p.clear(); + { + LLGLEnable blend(GL_BLEND); + + gGL.color3fv(cur_col.mV); + for (U32 i = 0; i < cur_times.size(); ++i) + { + gGL.begin(LLRender::TRIANGLE_STRIP); + gGL.vertex3f(last_p.mV[0], 0.f, 0.f); + gGL.vertex3fv(last_p.mV); + last_p.set((F32) i / (F32) cur_times.size(), cur_times[i]/max_time, 0.f); + gGL.vertex3f(last_p.mV[0], 0.f, 0.f); + gGL.vertex3fv(last_p.mV); + gGL.end(); + } + + gGL.flush(); + } + + saveChart(label, "time", scratch); + + //====================================== + // calls + //====================================== + buffer.clear(); + + last_p.clear(); + + gGL.color3fv(base_col.mV); + for (U32 i = 0; i < base_calls.size(); ++i) + { + gGL.begin(LLRender::TRIANGLE_STRIP); + gGL.vertex3fv(last_p.mV); + gGL.vertex3f(last_p.mV[0], 0.f, 0.f); + last_p.set((F32) i / (F32) base_calls.size(), (F32)base_calls[i]/max_calls, 0.f); + gGL.vertex3fv(last_p.mV); + gGL.vertex3f(last_p.mV[0], 0.f, 0.f); + gGL.end(); + } + + gGL.flush(); + + { + LLGLEnable blend(GL_BLEND); + gGL.color3fv(cur_col.mV); + last_p.clear(); + + for (U32 i = 0; i < cur_calls.size(); ++i) + { + gGL.begin(LLRender::TRIANGLE_STRIP); + gGL.vertex3f(last_p.mV[0], 0.f, 0.f); + gGL.vertex3fv(last_p.mV); + last_p.set((F32) i / (F32) cur_calls.size(), (F32) cur_calls[i]/max_calls, 0.f); + gGL.vertex3f(last_p.mV[0], 0.f, 0.f); + gGL.vertex3fv(last_p.mV); + gGL.end(); + + } + + gGL.flush(); + } + + saveChart(label, "calls", scratch); + + //====================================== + // execution + //====================================== + buffer.clear(); + + gGL.color3fv(base_col.mV); + U32 count = 0; + U32 total_count = base_execution.size(); + + last_p.clear(); + + for (std::vector::iterator iter = base_execution.begin(); iter != base_execution.end(); ++iter) + { + gGL.begin(LLRender::TRIANGLE_STRIP); + gGL.vertex3fv(last_p.mV); + gGL.vertex3f(last_p.mV[0], 0.f, 0.f); + last_p.set((F32)count/(F32)total_count, *iter/max_execution, 0.f); + gGL.vertex3fv(last_p.mV); + gGL.vertex3f(last_p.mV[0], 0.f, 0.f); + gGL.end(); + count++; + } + + last_p.clear(); + + { + LLGLEnable blend(GL_BLEND); + gGL.color3fv(cur_col.mV); + count = 0; + total_count = cur_execution.size(); + + for (std::vector::iterator iter = cur_execution.begin(); iter != cur_execution.end(); ++iter) + { + gGL.begin(LLRender::TRIANGLE_STRIP); + gGL.vertex3f(last_p.mV[0], 0.f, 0.f); + gGL.vertex3fv(last_p.mV); + last_p.set((F32)count/(F32)total_count, *iter/max_execution, 0.f); + gGL.vertex3f(last_p.mV[0], 0.f, 0.f); + gGL.vertex3fv(last_p.mV); + gGL.end(); + count++; + } + + gGL.flush(); + } + + saveChart(label, "execution", scratch); + } + + buffer.flush(); + + gGL.popMatrix(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.popMatrix(); +} + +//static +LLSD LLFastTimerView::analyzePerformanceLogDefault(std::istream& is) +{ + LLSD ret; + + LLSD cur; + + LLSD::Real total_time = 0.0; + LLSD::Integer total_frames = 0; + + typedef std::map stats_map_t; + stats_map_t time_stats; + stats_map_t sample_stats; + + while (!is.eof() && LLSDParser::PARSE_FAILURE != LLSDSerialize::fromXML(cur, is)) + { + for (LLSD::map_iterator iter = cur.beginMap(); iter != cur.endMap(); ++iter) + { + std::string label = iter->first; + + F64 time = iter->second["Time"].asReal(); + + // Skip the total figure + if(label.compare("Total") != 0) + { + total_time += time; + } + + if (time > 0.0) + { + LLSD::Integer samples = iter->second["Calls"].asInteger(); + + time_stats[label].push(time); + sample_stats[label].push(samples); + } + } + total_frames++; + } + + for(stats_map_t::iterator it = time_stats.begin(); it != time_stats.end(); ++it) + { + std::string label = it->first; + ret[label]["TotalTime"] = time_stats[label].getSum(); + ret[label]["MeanTime"] = time_stats[label].getMean(); + ret[label]["MaxTime"] = time_stats[label].getMaxValue(); + ret[label]["MinTime"] = time_stats[label].getMinValue(); + ret[label]["StdDevTime"] = time_stats[label].getStdDev(); + + ret[label]["Samples"] = sample_stats[label].getSum(); + ret[label]["MaxSamples"] = sample_stats[label].getMaxValue(); + ret[label]["MinSamples"] = sample_stats[label].getMinValue(); + ret[label]["StdDevSamples"] = sample_stats[label].getStdDev(); + + ret[label]["Frames"] = (LLSD::Integer)time_stats[label].getCount(); + } + + ret["SessionTime"] = total_time; + ret["FrameCount"] = total_frames; + + return ret; + +} + +//static +void LLFastTimerView::doAnalysisDefault(std::string baseline, std::string target, std::string output) +{ + // Open baseline and current target, exit if one is inexistent + llifstream base_is(baseline.c_str()); + llifstream target_is(target.c_str()); + if (!base_is.is_open() || !target_is.is_open()) + { + LL_WARNS() << "'-analyzeperformance' error : baseline or current target file inexistent" << LL_ENDL; + base_is.close(); + target_is.close(); + return; + } + + //analyze baseline + LLSD base = analyzePerformanceLogDefault(base_is); + base_is.close(); + + //analyze current + LLSD current = analyzePerformanceLogDefault(target_is); + target_is.close(); + + //output comparison + llofstream os(output.c_str()); + + LLSD::Real session_time = current["SessionTime"].asReal(); + os << + "Label, " + "% Change, " + "% of Session, " + "Cur Min, " + "Cur Max, " + "Cur Mean/sample, " + "Cur Mean/frame, " + "Cur StdDev/frame, " + "Cur Total, " + "Cur Frames, " + "Cur Samples, " + "Base Min, " + "Base Max, " + "Base Mean/sample, " + "Base Mean/frame, " + "Base StdDev/frame, " + "Base Total, " + "Base Frames, " + "Base Samples\n"; + + for (LLSD::map_iterator iter = base.beginMap(); iter != base.endMap(); ++iter) + { + LLSD::String label = iter->first; + + if (current[label]["Samples"].asInteger() == 0 || + base[label]["Samples"].asInteger() == 0) + { + //cannot compare + continue; + } + LLSD::Real a = base[label]["TotalTime"].asReal() / base[label]["Samples"].asReal(); + LLSD::Real b = current[label]["TotalTime"].asReal() / current[label]["Samples"].asReal(); + + LLSD::Real diff = b-a; + + LLSD::Real perc = diff/a * 100; + + os << llformat("%s, %.2f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %d, %d, %.4f, %.4f, %.4f, %.4f, %.4f, %.4f, %d, %d\n", + label.c_str(), + (F32) perc, + (F32) (current[label]["TotalTime"].asReal()/session_time * 100.0), + + (F32) current[label]["MinTime"].asReal(), + (F32) current[label]["MaxTime"].asReal(), + (F32) b, + (F32) current[label]["MeanTime"].asReal(), + (F32) current[label]["StdDevTime"].asReal(), + (F32) current[label]["TotalTime"].asReal(), + current[label]["Frames"].asInteger(), + current[label]["Samples"].asInteger(), + (F32) base[label]["MinTime"].asReal(), + (F32) base[label]["MaxTime"].asReal(), + (F32) a, + (F32) base[label]["MeanTime"].asReal(), + (F32) base[label]["StdDevTime"].asReal(), + (F32) base[label]["TotalTime"].asReal(), + base[label]["Frames"].asInteger(), + base[label]["Samples"].asInteger()); + } + + exportCharts(baseline, target); + + os.flush(); + os.close(); +} + +//static +void LLFastTimerView::outputAllMetrics() +{ + if (LLMetricPerformanceTesterBasic::hasMetricPerformanceTesters()) + { + for (LLMetricPerformanceTesterBasic::name_tester_map_t::iterator iter = LLMetricPerformanceTesterBasic::sTesterMap.begin(); + iter != LLMetricPerformanceTesterBasic::sTesterMap.end(); ++iter) + { + LLMetricPerformanceTesterBasic* tester = ((LLMetricPerformanceTesterBasic*)iter->second); + tester->outputTestResults(); + } + } +} + +//static +void LLFastTimerView::doAnalysis(std::string baseline, std::string target, std::string output) +{ + if(BlockTimer::sLog) + { + doAnalysisDefault(baseline, target, output) ; + return ; + } + + if(BlockTimer::sMetricLog) + { + LLMetricPerformanceTesterBasic::doAnalysisMetrics(baseline, target, output) ; + return ; + } +} + + +void LLFastTimerView::printLineStats() +{ + // Output stats for clicked bar to log + if (mStatsIndex >= 0) + { + std::string legend_stat; + bool first = true; + for(LLTrace::block_timer_tree_df_iterator_t it = LLTrace::begin_block_timer_tree_df(FTM_FRAME); + it != LLTrace::end_block_timer_tree_df(); + ++it) + { + BlockTimerStatHandle* idp = (*it); + + if (!first) + { + legend_stat += ", "; + } + first = false; + legend_stat += idp->getName(); + + //if (idp->getTreeNode().mCollapsed) + //{ + // it.skipDescendants(); + //} + } + LL_INFOS() << legend_stat << LL_ENDL; + + std::string timer_stat; + first = true; + for(LLTrace::block_timer_tree_df_iterator_t it = LLTrace::begin_block_timer_tree_df(FTM_FRAME); + it != LLTrace::end_block_timer_tree_df(); + ++it) + { + BlockTimerStatHandle* idp = (*it); + + if (!first) + { + timer_stat += ", "; + } + first = false; + + F32Seconds ticks; + if (mStatsIndex == 0) + { + ticks = mRecording.getPeriodMean(*idp, RUNNING_AVERAGE_WIDTH); + } + else + { + ticks = mRecording.getPrevRecording(mStatsIndex).getSum(*idp); + } + F32Milliseconds ms = ticks; + + timer_stat += llformat("%.1f",ms.value()); + + //if (idp->getTreeNode().mCollapsed) + //{ + // it.skipDescendants(); + //} + } + LL_INFOS() << timer_stat << LL_ENDL; + mStatsIndex = -1; + } +} + +void LLFastTimerView::drawLineGraph() +{ + LL_PROFILE_ZONE_SCOPED; + //draw line graph history + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + LLLocalClipRect clip(mGraphRect); + + //normalize based on last frame's maximum + static F32Seconds max_time(0.000001); + static U32 max_calls = 0; + static F32 alpha_interp = 0.f; + + //highlight visible range + { + S32 first_frame = mRecording.getNumRecordedPeriods() - mScrollIndex; + S32 last_frame = first_frame - MAX_VISIBLE_HISTORY; + + F32 frame_delta = ((F32) (mGraphRect.getWidth()))/(mRecording.getNumRecordedPeriods()-1); + + F32 right = (F32) mGraphRect.mLeft + frame_delta*first_frame; + F32 left = (F32) mGraphRect.mLeft + frame_delta*last_frame; + + gGL.color4f(0.5f,0.5f,0.5f,0.3f); + gl_rect_2d((S32) left, mGraphRect.mTop, (S32) right, mGraphRect.mBottom); + + if (mHoverBarIndex > 0) + { + S32 bar_frame = first_frame - (mScrollIndex + mHoverBarIndex) - 1; + F32 bar = (F32) mGraphRect.mLeft + frame_delta*bar_frame; + + gGL.color4f(0.5f,0.5f,0.5f,1); + + gGL.begin(LLRender::LINES); + gGL.vertex2i((S32)bar, mGraphRect.mBottom); + gGL.vertex2i((S32)bar, mGraphRect.mTop); + gGL.end(); + } + } + + F32Seconds cur_max(0); + U32 cur_max_calls = 0; + + for(LLTrace::block_timer_tree_df_iterator_t it = LLTrace::begin_block_timer_tree_df(FTM_FRAME); + it != LLTrace::end_block_timer_tree_df(); + ++it) + { + BlockTimerStatHandle* idp = (*it); + + //fatten highlighted timer + if (mHoverID == idp) + { + gGL.flush(); + glLineWidth(3); + } + + llassert(idp->getIndex() < sTimerColors.size()); + const F32 * col = sTimerColors[idp->getIndex()].mV;// ft_display_table[idx].color->mV; + + F32 alpha = 1.f; + bool is_hover_timer = true; + + if (mHoverID != NULL && + mHoverID != idp) + { //fade out non-highlighted timers + if (idp->getParent() != mHoverID) + { + alpha = alpha_interp; + is_hover_timer = false; + } + } + + gGL.color4f(col[0], col[1], col[2], alpha); + gGL.begin(LLRender::TRIANGLE_STRIP); + F32 call_scale_factor = (F32)mGraphRect.getHeight() / (F32)max_calls; + F32 time_scale_factor = (F32)mGraphRect.getHeight() / max_time.value(); + F32 hz_scale_factor = (F32) mGraphRect.getHeight() / (1.f / max_time.value()); + + for (U32 j = mRecording.getNumRecordedPeriods(); + j > 0; + j--) + { + LLTrace::Recording& recording = mRecording.getPrevRecording(j); + F32Seconds time = llmax(recording.getSum(*idp), F64Seconds(0.000001)); + U32 calls = recording.getSum(idp->callCount()); + + if (is_hover_timer) + { + //normalize to highlighted timer + cur_max = llmax(cur_max, time); + cur_max_calls = llmax(cur_max_calls, calls); + } + F32 x = mGraphRect.mRight - j * (F32)(mGraphRect.getWidth())/(mRecording.getNumRecordedPeriods()-1); + F32 y; + switch(mDisplayType) +{ + case DISPLAY_TIME: + y = mGraphRect.mBottom + time.value() * time_scale_factor; + break; + case DISPLAY_CALLS: + y = mGraphRect.mBottom + (F32)calls * call_scale_factor; + break; + case DISPLAY_HZ: + y = mGraphRect.mBottom + (1.f / time.value()) * hz_scale_factor; + break; + } + gGL.vertex2f(x,y); + gGL.vertex2f(x,mGraphRect.mBottom); + } + gGL.end(); + + if (mHoverID == idp) + { + gGL.flush(); + glLineWidth(1); + } + + if (idp->getTreeNode().mCollapsed) + { + //skip hidden timers + it.skipDescendants(); + } + } + + //interpolate towards new maximum + max_time = (F32Seconds)lerp(max_time.value(), cur_max.value(), LLSmoothInterpolation::getInterpolant(0.1f)); + if (llabs((max_time - cur_max).value()) <= 1) + { + max_time = llmax(F32Microseconds(1.f), F32Microseconds(cur_max)); + } + + max_calls = ll_round(lerp((F32)max_calls, (F32) cur_max_calls, LLSmoothInterpolation::getInterpolant(0.1f))); + if (llabs((S32)(max_calls - cur_max_calls)) <= 1) + { + max_calls = cur_max_calls; + } + + // TODO: make sure alpha is correct in DisplayHz mode + F32 alpha_target = (max_time > cur_max) + ? llmin(max_time / cur_max - 1.f,1.f) + : llmin(cur_max/ max_time - 1.f,1.f); + alpha_interp = lerp(alpha_interp, alpha_target, LLSmoothInterpolation::getInterpolant(0.1f)); + + if (mHoverID != NULL) + { + S32 x = (mGraphRect.mRight + mGraphRect.mLeft)/2; + S32 y = mGraphRect.mBottom + 8; + + LLFontGL::getFontMonospace()->renderUTF8( + mHoverID->getName(), + 0, + x, y, + LLColor4::white, + LLFontGL::LEFT, LLFontGL::BOTTOM); + } + + //display y-axis range + std::string axis_label; + switch(mDisplayType) + { + case DISPLAY_TIME: + axis_label = llformat("%4.2f ms", F32Milliseconds(max_time).value()); + break; + case DISPLAY_CALLS: + axis_label = llformat("%d calls", (int)max_calls); + break; + case DISPLAY_HZ: + axis_label = llformat("%4.2f Hz", max_time.value() ? 1.f / max_time.value() : 0.f); + break; + } + + LLFontGL* font = LLFontGL::getFontMonospace(); + S32 x = mGraphRect.mRight - font->getWidth(axis_label)-5; + S32 y = mGraphRect.mTop - font->getLineHeight();; + + font->renderUTF8(axis_label, 0, x, y, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP); +} + +void LLFastTimerView::drawLegend() +{ + // draw legend + S32 dx; + S32 x = mLegendRect.mLeft; + S32 y = mLegendRect.mTop; + const S32 TEXT_HEIGHT = (S32)LLFontGL::getFontMonospace()->getLineHeight(); + + { + LLLocalClipRect clip(mLegendRect); + S32 cur_line = 0; + S32 scroll_offset = 0; // element's y offset from top of the inner scroll's rect + ft_display_idx.clear(); + std::map display_line; + for (LLTrace::block_timer_tree_df_iterator_t it = LLTrace::begin_block_timer_tree_df(FTM_FRAME); + it != LLTrace::end_block_timer_tree_df(); + ++it) + { + BlockTimerStatHandle* idp = (*it); + // Needed to figure out offsets and parenting + display_line[idp] = cur_line; + cur_line++; + if (scroll_offset < mScrollBar->getDocPos()) + { + // only offset for visible items + scroll_offset += TEXT_HEIGHT + 2; + if (idp->getTreeNode().mCollapsed) + { + it.skipDescendants(); + } + continue; + } + + // used for mouse clicks + ft_display_idx.push_back(idp); + + // Actual draw, first bar (square), then text + x = MARGIN; + + LLRect bar_rect(x, y, x + TEXT_HEIGHT, y - TEXT_HEIGHT); + S32 scale_offset = 0; + if (idp == mHoverID) + { + scale_offset = llfloor(sinf(mHighlightTimer.getElapsedTimeF32() * 6.f) * 2.f); + } + bar_rect.stretch(scale_offset); + llassert(idp->getIndex() < sTimerColors.size()); + gl_rect_2d(bar_rect, sTimerColors[idp->getIndex()]); + + F32Milliseconds ms(0); + S32 calls = 0; + if (mHoverBarIndex > 0 && mHoverID) + { + S32 hidx = mScrollIndex + mHoverBarIndex; + ms = mRecording.getPrevRecording(hidx).getSum(*idp); + calls = mRecording.getPrevRecording(hidx).getSum(idp->callCount()); + } + else + { + ms = F64Seconds(mRecording.getPeriodMean(*idp, RUNNING_AVERAGE_WIDTH)); + calls = (S32)mRecording.getPeriodMean(idp->callCount(), RUNNING_AVERAGE_WIDTH); + } + + std::string timer_label; + switch(mDisplayType) + { + case DISPLAY_TIME: + timer_label = llformat("%s [%.1f]",idp->getName().c_str(),ms.value()); + break; + case DISPLAY_CALLS: + timer_label = llformat("%s (%d)",idp->getName().c_str(),calls); + break; + case DISPLAY_HZ: + timer_label = llformat("%.1f", ms.value() ? (1.f / ms.value()) : 0.f); + break; + } + dx = (TEXT_HEIGHT+4) + get_depth(idp)*8; + + LLColor4 color = LLColor4::white; + if (get_depth(idp) > 0) + { + S32 line_start_y = bar_rect.getCenterY(); + S32 line_end_y = line_start_y + ((TEXT_HEIGHT + 2) * (cur_line - display_line[idp->getParent()])) - TEXT_HEIGHT; + gl_line_2d(x + dx - 8, line_start_y, x + dx, line_start_y, color); + S32 line_x = x + (TEXT_HEIGHT + 4) + ((get_depth(idp) - 1) * 8); + gl_line_2d(line_x, line_start_y, line_x, line_end_y, color); + if (idp->getTreeNode().mCollapsed && !idp->getChildren().empty()) + { + gl_line_2d(line_x+4, line_start_y-3, line_x+4, line_start_y+4, color); + } + } + + x += dx; + bool is_child_of_hover_item = (idp == mHoverID); + BlockTimerStatHandle* next_parent = idp->getParent(); + while(!is_child_of_hover_item && next_parent) + { + is_child_of_hover_item = (mHoverID == next_parent); + if (next_parent->getParent() == next_parent) break; + next_parent = next_parent->getParent(); + } + + LLFontGL::getFontMonospace()->renderUTF8(timer_label, 0, + x, y, + color, + LLFontGL::LEFT, LLFontGL::TOP, + is_child_of_hover_item ? LLFontGL::BOLD : LLFontGL::NORMAL); + + y -= (TEXT_HEIGHT + 2); + + scroll_offset += TEXT_HEIGHT + 2; + if (idp->getTreeNode().mCollapsed) + { + it.skipDescendants(); + } + } + // Recalculate scroll size + mScrollBar->setDocSize(scroll_offset - mLegendRect.getHeight()); + } +} + +void LLFastTimerView::generateUniqueColors() +{ + // generate unique colors + { + sTimerColors.resize(LLTrace::BlockTimerStatHandle::getNumIndices()); + sTimerColors[FTM_FRAME.getIndex()] = LLColor4::grey; + + F32 hue = 0.f; + + for (LLTrace::block_timer_tree_df_iterator_t it = LLTrace::begin_block_timer_tree_df(FTM_FRAME); + it != LLTrace::end_block_timer_tree_df(); + ++it) + { + BlockTimerStatHandle* idp = (*it); + + const F32 HUE_INCREMENT = 0.23f; + hue = fmodf(hue + HUE_INCREMENT, 1.f); + // saturation increases with depth + F32 saturation = clamp_rescale((F32)get_depth(idp), 0.f, 3.f, 0.f, 1.f); + // lightness alternates with depth + F32 lightness = get_depth(idp) % 2 ? 0.5f : 0.6f; + + LLColor4 child_color; + child_color.setHSL(hue, saturation, lightness); + + llassert(idp->getIndex() < sTimerColors.size()); + sTimerColors[idp->getIndex()] = child_color; + } + } +} + +void LLFastTimerView::drawHelp( S32 y ) +{ + // Draw some help + const S32 texth = (S32)LLFontGL::getFontMonospace()->getLineHeight(); + + y -= (texth + 2); + y -= (texth + 2); + + LLFontGL::getFontMonospace()->renderUTF8(std::string("[Right-Click log selected]"), + 0, MARGIN, y, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP); +} + +void LLFastTimerView::drawTicks() +{ + // Draw MS ticks + { + U32Milliseconds ms = mTotalTimeDisplay; + std::string tick_label; + S32 x; + S32 barw = mBarRect.getWidth(); + + tick_label = llformat("%.1f ms |", (F32)ms.value()*.25f); + x = mBarRect.mLeft + barw/4 - LLFontGL::getFontMonospace()->getWidth(tick_label); + LLFontGL::getFontMonospace()->renderUTF8(tick_label, 0, x, mBarRect.mTop, LLColor4::white, + LLFontGL::LEFT, LLFontGL::TOP); + + tick_label = llformat("%.1f ms |", (F32)ms.value()*.50f); + x = mBarRect.mLeft + barw/2 - LLFontGL::getFontMonospace()->getWidth(tick_label); + LLFontGL::getFontMonospace()->renderUTF8(tick_label, 0, x, mBarRect.mTop, LLColor4::white, + LLFontGL::LEFT, LLFontGL::TOP); + + tick_label = llformat("%.1f ms |", (F32)ms.value()*.75f); + x = mBarRect.mLeft + (barw*3)/4 - LLFontGL::getFontMonospace()->getWidth(tick_label); + LLFontGL::getFontMonospace()->renderUTF8(tick_label, 0, x, mBarRect.mTop, LLColor4::white, + LLFontGL::LEFT, LLFontGL::TOP); + + tick_label = llformat( "%d ms |", (U32)ms.value()); + x = mBarRect.mLeft + barw - LLFontGL::getFontMonospace()->getWidth(tick_label); + LLFontGL::getFontMonospace()->renderUTF8(tick_label, 0, x, mBarRect.mTop, LLColor4::white, + LLFontGL::LEFT, LLFontGL::TOP); + } +} + +void LLFastTimerView::drawBorders( S32 y, const S32 x_start, S32 bar_height, S32 dy ) +{ + // Draw borders + { + S32 by = y + 6 + (S32)LLFontGL::getFontMonospace()->getLineHeight(); + + //heading + gl_rect_2d(x_start-5, by, getRect().getWidth()-5, y+5, LLColor4::grey, false); + + //tree view + gl_rect_2d(5, by, x_start-10, 5, LLColor4::grey, false); + + by = y + 5; + //average bar + gl_rect_2d(x_start-5, by, getRect().getWidth()-5, by-bar_height-dy-5, LLColor4::grey, false); + + by -= bar_height*2+dy; + + //current frame bar + gl_rect_2d(x_start-5, by, getRect().getWidth()-5, by-bar_height-dy-2, LLColor4::grey, false); + + by -= bar_height+dy+1; + + //history bars + gl_rect_2d(x_start-5, by, getRect().getWidth()-5, LINE_GRAPH_HEIGHT-bar_height-dy-2, LLColor4::grey, false); + + by = LINE_GRAPH_HEIGHT-dy; + + //line graph + //mGraphRect = LLRect(x_start-5, by, getRect().getWidth()-5, 5); + + gl_rect_2d(mGraphRect, false); + } +} + +void LLFastTimerView::updateTotalTime() +{ + switch(mDisplayMode) + { + case 0: + mTotalTimeDisplay = mRecording.getPeriodMean(FTM_FRAME, RUNNING_AVERAGE_WIDTH)*2; + break; + case 1: + mTotalTimeDisplay = mRecording.getPeriodMax(FTM_FRAME); + break; + case 2: + // Calculate the max total ticks for the current history + mTotalTimeDisplay = mRecording.getPeriodMax(FTM_FRAME, 20); + break; + default: + mTotalTimeDisplay = F64Milliseconds(100); + break; + } + + mTotalTimeDisplay = LLUnits::Milliseconds::fromValue(llceil(mTotalTimeDisplay.valueInUnits() / 20.f) * 20.f); +} + +void LLFastTimerView::drawBars() +{ + LL_PROFILE_ZONE_SCOPED; + LLLocalClipRect clip(mBarRect); + + S32 bar_height = mBarRect.getHeight() / (MAX_VISIBLE_HISTORY + 2); + const S32 vpad = llmax(1, bar_height / 4); // spacing between bars + bar_height -= vpad; + + updateTotalTime(); + if (mTotalTimeDisplay <= (F32Seconds)0.0) return; + + drawTicks(); + const S32 bars_top = mBarRect.mTop - ((S32)LLFontGL::getFontMonospace()->getLineHeight() + 4); + drawBorders(bars_top, mBarRect.mLeft, bar_height, vpad); + + // Draw bars for each history entry + // Special: 0 = show running average + LLPointer bar_image = LLUI::getUIImage("Rounded_Square"); + + const S32 image_width = bar_image->getTextureWidth(); + const S32 image_height = bar_image->getTextureHeight(); + + gGL.getTexUnit(0)->bind(bar_image->getImage()); + { + const S32 histmax = (S32)mRecording.getNumRecordedPeriods(); + + // update widths + if (!mPauseHistory) + { + U32 bar_index = 0; + if (!mAverageTimerRow.mBars) + { + mAverageTimerRow.mBars = new TimerBar[LLTrace::BlockTimerStatHandle::instance_tracker_t::instanceCount()]; + } + updateTimerBarWidths(&FTM_FRAME, mAverageTimerRow, -1, bar_index); + updateTimerBarOffsets(&FTM_FRAME, mAverageTimerRow); + + for (S32 history_index = 1; history_index <= histmax; history_index++) + { + llassert(history_index <= mTimerBarRows.size()); + TimerBarRow& row = mTimerBarRows[history_index - 1]; + bar_index = 0; + if (!row.mBars) + { + row.mBars = new TimerBar[LLTrace::BlockTimerStatHandle::instance_tracker_t::instanceCount()]; + updateTimerBarWidths(&FTM_FRAME, row, history_index, bar_index); + updateTimerBarOffsets(&FTM_FRAME, row); + } + } + } + + // draw bars + LLRect frame_bar_rect; + frame_bar_rect.setLeftTopAndSize(mBarRect.mLeft, + bars_top, + ll_round((mAverageTimerRow.mBars[0].mTotalTime / mTotalTimeDisplay) * mBarRect.getWidth()), + bar_height); + mAverageTimerRow.mTop = frame_bar_rect.mTop; + mAverageTimerRow.mBottom = frame_bar_rect.mBottom; + drawBar(frame_bar_rect, mAverageTimerRow, image_width, image_height); + frame_bar_rect.translate(0, -(bar_height + vpad + bar_height)); + + for(S32 bar_index = mScrollIndex; bar_index < llmin(histmax, mScrollIndex + MAX_VISIBLE_HISTORY); ++bar_index) + { + llassert(bar_index < mTimerBarRows.size()); + TimerBarRow& row = mTimerBarRows[bar_index]; + row.mTop = frame_bar_rect.mTop; + row.mBottom = frame_bar_rect.mBottom; + frame_bar_rect.mRight = frame_bar_rect.mLeft + + ll_round((row.mBars[0].mTotalTime / mTotalTimeDisplay) * mBarRect.getWidth()); + drawBar(frame_bar_rect, row, image_width, image_height); + + frame_bar_rect.translate(0, -(bar_height + vpad)); + } + + } + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); +} + +F32Seconds LLFastTimerView::updateTimerBarWidths(LLTrace::BlockTimerStatHandle* time_block, TimerBarRow& row, S32 history_index, U32& bar_index) +{ + LL_PROFILE_ZONE_SCOPED; + const F32Seconds self_time = history_index == -1 + ? mRecording.getPeriodMean(time_block->selfTime(), RUNNING_AVERAGE_WIDTH) + : mRecording.getPrevRecording(history_index).getSum(time_block->selfTime()); + + F32Seconds full_time = self_time; + + // reserve a spot for this bar to be rendered before its children + // even though we don't know its size yet + TimerBar& timer_bar = row.mBars[bar_index]; + bar_index++; + + for (BlockTimerStatHandle::child_iter it = time_block->beginChildren(), end_it = time_block->endChildren(); it != end_it; ++it) + { + full_time += updateTimerBarWidths(*it, row, history_index, bar_index); + } + + timer_bar.mTotalTime = full_time; + timer_bar.mSelfTime = self_time; + timer_bar.mTimeBlock = time_block; + + return full_time; +} + +S32 LLFastTimerView::updateTimerBarOffsets(LLTrace::BlockTimerStatHandle* time_block, TimerBarRow& row, S32 timer_bar_index) +{ + LL_PROFILE_ZONE_SCOPED; + + TimerBar& timer_bar = row.mBars[timer_bar_index]; + const F32Seconds bar_time = timer_bar.mTotalTime - timer_bar.mSelfTime; + timer_bar.mChildrenStart = timer_bar.mSelfStart + timer_bar.mSelfTime / 2; + timer_bar.mChildrenEnd = timer_bar.mChildrenStart + timer_bar.mTotalTime - timer_bar.mSelfTime; + + if (timer_bar_index == 0) + { + timer_bar.mSelfStart = F32Seconds(0.f); + timer_bar.mSelfEnd = bar_time; + } + + //now loop through children and figure out portion of bar image covered by each bar, now that we know the + //sum of all children + F32 bar_fraction_start = 0.f; + TimerBar* last_child_timer_bar = NULL; + + bool first_child = true; + for (BlockTimerStatHandle::child_iter it = time_block->beginChildren(), end_it = time_block->endChildren(); + it != end_it; + ++it) + { + timer_bar_index++; + + TimerBar& child_timer_bar = row.mBars[timer_bar_index]; + BlockTimerStatHandle* child_time_block = *it; + + if (last_child_timer_bar) + { + last_child_timer_bar->mLastChild = false; + } + child_timer_bar.mLastChild = true; + last_child_timer_bar = &child_timer_bar; + + child_timer_bar.mFirstChild = first_child; + if (first_child) + { + first_child = false; + } + + child_timer_bar.mStartFraction = bar_fraction_start; + child_timer_bar.mEndFraction = bar_time > (S32Seconds)0 + ? bar_fraction_start + child_timer_bar.mTotalTime / bar_time + : 1.f; + child_timer_bar.mSelfStart = timer_bar.mChildrenStart + + child_timer_bar.mStartFraction + * (timer_bar.mChildrenEnd - timer_bar.mChildrenStart); + child_timer_bar.mSelfEnd = timer_bar.mChildrenStart + + child_timer_bar.mEndFraction + * (timer_bar.mChildrenEnd - timer_bar.mChildrenStart); + + timer_bar_index = updateTimerBarOffsets(child_time_block, row, timer_bar_index); + + bar_fraction_start = child_timer_bar.mEndFraction; + } + return timer_bar_index; +} + +S32 LLFastTimerView::drawBar(LLRect bar_rect, TimerBarRow& row, S32 image_width, S32 image_height, bool hovered, bool visible, S32 bar_index) +{ + LL_PROFILE_ZONE_SCOPED; + TimerBar& timer_bar = row.mBars[bar_index]; + LLTrace::BlockTimerStatHandle* time_block = timer_bar.mTimeBlock; + + hovered |= mHoverID == time_block; + + // animate scale of bar when hovering over that particular timer + if (visible && (F32)bar_rect.getWidth() * (timer_bar.mEndFraction - timer_bar.mStartFraction) > 2.f) + { + LLRect render_rect(bar_rect); + S32 scale_offset = 0; + if (mHoverID == time_block) + { + scale_offset = llfloor(sinf(mHighlightTimer.getElapsedTimeF32() * 6.f) * 3.f); + render_rect.mTop += scale_offset; + render_rect.mBottom -= scale_offset; + } + + llassert(time_block->getIndex() < sTimerColors.size()); + LLColor4 color = sTimerColors[time_block->getIndex()]; + if (!hovered) color = lerp(color, LLColor4::grey, 0.2f); + gGL.color4fv(color.mV); + gl_segmented_rect_2d_fragment_tex(render_rect, + image_width, image_height, + 16, + timer_bar.mStartFraction, timer_bar.mEndFraction); + } + + LLRect children_rect; + children_rect.mLeft = ll_round(timer_bar.mChildrenStart / mTotalTimeDisplay * (F32)mBarRect.getWidth()) + mBarRect.mLeft; + children_rect.mRight = ll_round(timer_bar.mChildrenEnd / mTotalTimeDisplay * (F32)mBarRect.getWidth()) + mBarRect.mLeft; + + if (bar_rect.getHeight() > MIN_BAR_HEIGHT) + { + // shrink as we go down a level + children_rect.mTop = bar_rect.mTop - 1; + children_rect.mBottom = bar_rect.mBottom + 1; + } + else + { + children_rect.mTop = bar_rect.mTop; + children_rect.mBottom = bar_rect.mBottom; + } + + bool children_visible = visible && !time_block->getTreeNode().mCollapsed; + + bar_index++; + const U32 num_bars = LLTrace::BlockTimerStatHandle::instance_tracker_t::instanceCount(); + if (bar_index < num_bars && row.mBars[bar_index].mFirstChild) + { + bool is_last = false; + do + { + is_last = row.mBars[bar_index].mLastChild; + bar_index = drawBar(children_rect, row, image_width, image_height, hovered, children_visible, bar_index); + } + while(!is_last && bar_index < num_bars); + } + + return bar_index; +} diff --git a/indra/newview/llfasttimerview.h b/indra/newview/llfasttimerview.h index 35158b1bdd..e0ef52f166 100644 --- a/indra/newview/llfasttimerview.h +++ b/indra/newview/llfasttimerview.h @@ -1,151 +1,151 @@ -/** - * @file llfasttimerview.h - * @brief LLFastTimerView class definition - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFASTTIMERVIEW_H -#define LL_LLFASTTIMERVIEW_H - -#include "llfloater.h" -#include "llfasttimer.h" -#include "llunits.h" -#include "lltracerecording.h" -#include - -class LLScrollbar; - -class LLFastTimerView : public LLFloater -{ -public: - LLFastTimerView(const LLSD&); - ~LLFastTimerView(); - bool postBuild(); - - static bool sAnalyzePerformance; - - static void outputAllMetrics(); - static void doAnalysis(std::string baseline, std::string target, std::string output); - -private: - static void doAnalysisDefault(std::string baseline, std::string target, std::string output) ; - static LLSD analyzePerformanceLogDefault(std::istream& is) ; - static void exportCharts(const std::string& base, const std::string& target); - void onPause(); - -public: - - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleDoubleClick(S32 x, S32 y, MASK mask); - virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - virtual bool handleHover(S32 x, S32 y, MASK mask); - virtual bool handleToolTip(S32 x, S32 y, MASK mask); - virtual bool handleScrollWheel(S32 x, S32 y, S32 clicks); - virtual void draw(); - virtual void onOpen(const LLSD& key); - virtual void onClose(bool app_quitting); - LLTrace::BlockTimerStatHandle* getLegendID(S32 y); - -private: - void drawTicks(); - void drawLineGraph(); - void drawLegend(); - void drawHelp(S32 y); - void drawBorders( S32 y, const S32 x_start, S32 barh, S32 dy); - void drawBars(); - - void printLineStats(); - void generateUniqueColors(); - void updateTotalTime(); - - struct TimerBar - { - TimerBar() - : mTotalTime(0), - mSelfTime(0), - mStartFraction(0.f), - mEndFraction(1.f), - mFirstChild(false), - mLastChild(false) - {} - F32Seconds mTotalTime, - mSelfTime, - mChildrenStart, - mChildrenEnd, - mSelfStart, - mSelfEnd; - LLTrace::BlockTimerStatHandle* mTimeBlock; - bool mVisible, - mFirstChild, - mLastChild; - F32 mStartFraction, - mEndFraction; - }; - - struct TimerBarRow - { - TimerBarRow() - : mBottom(0), - mTop(0), - mBars(NULL) - {} - S32 mBottom, - mTop; - TimerBar* mBars; - }; - - F32Seconds updateTimerBarWidths(LLTrace::BlockTimerStatHandle* time_block, TimerBarRow& row, S32 history_index, U32& bar_index); - S32 updateTimerBarOffsets(LLTrace::BlockTimerStatHandle* time_block, TimerBarRow& row, S32 timer_bar_index = 0); - S32 drawBar(LLRect bar_rect, TimerBarRow& row, S32 image_width, S32 image_height, bool hovered = false, bool visible = true, S32 bar_index = 0); - void setPauseState(bool pause_state); - - std::deque mTimerBarRows; - TimerBarRow mAverageTimerRow; - - enum EDisplayType - { - DISPLAY_TIME, - DISPLAY_CALLS, - DISPLAY_HZ - } mDisplayType; - bool mPauseHistory; - F64Seconds mAllTimeMax, - mTotalTimeDisplay; - S32 mScrollIndex, - mHoverBarIndex, - mStatsIndex; - S32 mDisplayMode; - LLTrace::BlockTimerStatHandle* mHoverID; - LLTrace::BlockTimerStatHandle* mHoverTimer; - LLRect mToolTipRect, - mGraphRect, - mBarRect, - mLegendRect; - LLFrameTimer mHighlightTimer; - LLTrace::PeriodicRecording mRecording; - - LLScrollbar* mScrollBar; -}; - -#endif +/** + * @file llfasttimerview.h + * @brief LLFastTimerView class definition + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFASTTIMERVIEW_H +#define LL_LLFASTTIMERVIEW_H + +#include "llfloater.h" +#include "llfasttimer.h" +#include "llunits.h" +#include "lltracerecording.h" +#include + +class LLScrollbar; + +class LLFastTimerView : public LLFloater +{ +public: + LLFastTimerView(const LLSD&); + ~LLFastTimerView(); + bool postBuild(); + + static bool sAnalyzePerformance; + + static void outputAllMetrics(); + static void doAnalysis(std::string baseline, std::string target, std::string output); + +private: + static void doAnalysisDefault(std::string baseline, std::string target, std::string output) ; + static LLSD analyzePerformanceLogDefault(std::istream& is) ; + static void exportCharts(const std::string& base, const std::string& target); + void onPause(); + +public: + + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleDoubleClick(S32 x, S32 y, MASK mask); + virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + virtual bool handleHover(S32 x, S32 y, MASK mask); + virtual bool handleToolTip(S32 x, S32 y, MASK mask); + virtual bool handleScrollWheel(S32 x, S32 y, S32 clicks); + virtual void draw(); + virtual void onOpen(const LLSD& key); + virtual void onClose(bool app_quitting); + LLTrace::BlockTimerStatHandle* getLegendID(S32 y); + +private: + void drawTicks(); + void drawLineGraph(); + void drawLegend(); + void drawHelp(S32 y); + void drawBorders( S32 y, const S32 x_start, S32 barh, S32 dy); + void drawBars(); + + void printLineStats(); + void generateUniqueColors(); + void updateTotalTime(); + + struct TimerBar + { + TimerBar() + : mTotalTime(0), + mSelfTime(0), + mStartFraction(0.f), + mEndFraction(1.f), + mFirstChild(false), + mLastChild(false) + {} + F32Seconds mTotalTime, + mSelfTime, + mChildrenStart, + mChildrenEnd, + mSelfStart, + mSelfEnd; + LLTrace::BlockTimerStatHandle* mTimeBlock; + bool mVisible, + mFirstChild, + mLastChild; + F32 mStartFraction, + mEndFraction; + }; + + struct TimerBarRow + { + TimerBarRow() + : mBottom(0), + mTop(0), + mBars(NULL) + {} + S32 mBottom, + mTop; + TimerBar* mBars; + }; + + F32Seconds updateTimerBarWidths(LLTrace::BlockTimerStatHandle* time_block, TimerBarRow& row, S32 history_index, U32& bar_index); + S32 updateTimerBarOffsets(LLTrace::BlockTimerStatHandle* time_block, TimerBarRow& row, S32 timer_bar_index = 0); + S32 drawBar(LLRect bar_rect, TimerBarRow& row, S32 image_width, S32 image_height, bool hovered = false, bool visible = true, S32 bar_index = 0); + void setPauseState(bool pause_state); + + std::deque mTimerBarRows; + TimerBarRow mAverageTimerRow; + + enum EDisplayType + { + DISPLAY_TIME, + DISPLAY_CALLS, + DISPLAY_HZ + } mDisplayType; + bool mPauseHistory; + F64Seconds mAllTimeMax, + mTotalTimeDisplay; + S32 mScrollIndex, + mHoverBarIndex, + mStatsIndex; + S32 mDisplayMode; + LLTrace::BlockTimerStatHandle* mHoverID; + LLTrace::BlockTimerStatHandle* mHoverTimer; + LLRect mToolTipRect, + mGraphRect, + mBarRect, + mLegendRect; + LLFrameTimer mHighlightTimer; + LLTrace::PeriodicRecording mRecording; + + LLScrollbar* mScrollBar; +}; + +#endif diff --git a/indra/newview/llfavoritesbar.cpp b/indra/newview/llfavoritesbar.cpp index 8207ec86b7..906d04691f 100644 --- a/indra/newview/llfavoritesbar.cpp +++ b/indra/newview/llfavoritesbar.cpp @@ -1,2268 +1,2268 @@ -/** - * @file llfavoritesbar.cpp - * @brief LLFavoritesBarCtrl class implementation - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llfavoritesbar.h" - -#include "llfloaterreg.h" -#include "llfocusmgr.h" -#include "llinventory.h" -#include "lllandmarkactions.h" -#include "lltoolbarview.h" -#include "lltrans.h" -#include "lluictrlfactory.h" -#include "llmenugl.h" -#include "lltooltip.h" - -#include "llagent.h" -#include "llagentpicksinfo.h" -#include "llavatarnamecache.h" -#include "llclipboard.h" -#include "llinventorybridge.h" -#include "llinventoryfunctions.h" -#include "llfloatersidepanelcontainer.h" -#include "llfloaterworldmap.h" -#include "lllandmarkactions.h" -#include "lllogininstance.h" -#include "llnotificationsutil.h" -#include "lltoggleablemenu.h" -#include "llviewerinventory.h" -#include "llviewermenu.h" -#include "llviewernetwork.h" -#include "lltooldraganddrop.h" -#include "llsdserialize.h" - -static LLDefaultChildRegistry::Register r("favorites_bar"); - -const S32 DROP_DOWN_MENU_WIDTH = 250; -const S32 DROP_DOWN_MENU_TOP_PAD = 13; - -/** - * Helper for LLFavoriteLandmarkButton and LLFavoriteLandmarkMenuItem. - * Performing requests for SLURL for given Landmark ID - */ -class LLLandmarkInfoGetter -{ -public: - LLLandmarkInfoGetter() - : mLandmarkID(LLUUID::null), - mName("(Loading...)"), - mPosX(0), - mPosY(0), - mPosZ(0), - mLoaded(false) - { - mHandle.bind(this); - } - - void setLandmarkID(const LLUUID& id) { mLandmarkID = id; } - const LLUUID& getLandmarkID() const { return mLandmarkID; } - - const std::string& getName() - { - if(!mLoaded) - requestNameAndPos(); - - return mName; - } - - S32 getPosX() - { - if (!mLoaded) - requestNameAndPos(); - return mPosX; - } - - S32 getPosY() - { - if (!mLoaded) - requestNameAndPos(); - return mPosY; - } - - S32 getPosZ() - { - if (!mLoaded) - requestNameAndPos(); - return mPosZ; - } - -private: - /** - * Requests landmark data from server. - */ - void requestNameAndPos() - { - if (mLandmarkID.isNull()) - return; - - LLVector3d g_pos; - if(LLLandmarkActions::getLandmarkGlobalPos(mLandmarkID, g_pos)) - { - LLLandmarkActions::getRegionNameAndCoordsFromPosGlobal(g_pos, - boost::bind(&LLLandmarkInfoGetter::landmarkNameCallback, static_cast >(mHandle), _1, _2, _3, _4)); - } - } - - static void landmarkNameCallback(LLHandle handle, const std::string& name, S32 x, S32 y, S32 z) - { - LLLandmarkInfoGetter* getter = handle.get(); - if (getter) - { - getter->mPosX = x; - getter->mPosY = y; - getter->mPosZ = z; - getter->mName = name; - getter->mLoaded = true; - } - } - - LLUUID mLandmarkID; - std::string mName; - S32 mPosX; - S32 mPosY; - S32 mPosZ; - bool mLoaded; - LLRootHandle mHandle; -}; - -/** - * This class is needed to override LLButton default handleToolTip function and - * show SLURL as button tooltip. - * *NOTE: dzaporozhan: This is a workaround. We could set tooltips for buttons - * in createButtons function but landmark data is not available when Favorites Bar is - * created. Thats why we are requesting landmark data after - */ -class LLFavoriteLandmarkButton : public LLButton -{ -public: - - bool handleToolTip(S32 x, S32 y, MASK mask) - { - std::string region_name = mLandmarkInfoGetter.getName(); - - if (!region_name.empty()) - { - std::string extra_message = llformat("%s (%d, %d, %d)", region_name.c_str(), - mLandmarkInfoGetter.getPosX(), mLandmarkInfoGetter.getPosY(), mLandmarkInfoGetter.getPosZ()); - - LLToolTip::Params params; - params.message = llformat("%s\n%s", getLabelSelected().c_str(), extra_message.c_str()); - params.max_width = 1000; - params.sticky_rect = calcScreenRect(); - - LLToolTipMgr::instance().show(params); - } - return true; - } - - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask) - { - LLFavoritesBarCtrl* fb = dynamic_cast(getParent()); - - if (fb) - { - fb->handleHover(x, y, mask); - } - - return LLButton::handleHover(x, y, mask); - } - - void setLandmarkID(const LLUUID& id){ mLandmarkInfoGetter.setLandmarkID(id); } - const LLUUID& getLandmarkID() const { return mLandmarkInfoGetter.getLandmarkID(); } - - void onMouseEnter(S32 x, S32 y, MASK mask) - { - if (LLToolDragAndDrop::getInstance()->hasMouseCapture()) - { - LLUICtrl::onMouseEnter(x, y, mask); - } - else - { - LLButton::onMouseEnter(x, y, mask); - } - } - -protected: - LLFavoriteLandmarkButton(const LLButton::Params& p) : LLButton(p) {} - friend class LLUICtrlFactory; - -private: - LLLandmarkInfoGetter mLandmarkInfoGetter; -}; - -/** - * This class is needed to override LLMenuItemCallGL default handleToolTip function and - * show SLURL as button tooltip. - * *NOTE: dzaporozhan: This is a workaround. We could set tooltips for buttons - * in showDropDownMenu function but landmark data is not available when Favorites Bar is - * created. Thats why we are requesting landmark data after - */ -class LLFavoriteLandmarkMenuItem : public LLMenuItemCallGL -{ -public: - bool handleToolTip(S32 x, S32 y, MASK mask) - { - std::string region_name = mLandmarkInfoGetter.getName(); - if (!region_name.empty()) - { - LLToolTip::Params params; - params.message = llformat("%s\n%s (%d, %d)", getLabel().c_str(), region_name.c_str(), mLandmarkInfoGetter.getPosX(), mLandmarkInfoGetter.getPosY()); - params.sticky_rect = calcScreenRect(); - LLToolTipMgr::instance().show(params); - } - return true; - } - - const LLUUID& getLandmarkID() const { return mLandmarkInfoGetter.getLandmarkID(); } - void setLandmarkID(const LLUUID& id) { mLandmarkInfoGetter.setLandmarkID(id); } - - virtual bool handleMouseDown(S32 x, S32 y, MASK mask) - { - if (mMouseDownSignal) - (*mMouseDownSignal)(this, x, y, mask); - return LLMenuItemCallGL::handleMouseDown(x, y, mask); - } - - virtual bool handleMouseUp(S32 x, S32 y, MASK mask) - { - if (mMouseUpSignal) - (*mMouseUpSignal)(this, x, y, mask); - return LLMenuItemCallGL::handleMouseUp(x, y, mask); - } - - virtual bool handleHover(S32 x, S32 y, MASK mask) - { - if (fb) - { - fb->handleHover(x, y, mask); - } - - return true; - } - - void initFavoritesBarPointer(LLFavoritesBarCtrl* fb) { this->fb = fb; } - -protected: - - LLFavoriteLandmarkMenuItem(const LLMenuItemCallGL::Params& p) : LLMenuItemCallGL(p) {} - friend class LLUICtrlFactory; - -private: - LLLandmarkInfoGetter mLandmarkInfoGetter; - LLFavoritesBarCtrl* fb; -}; - -/** - * This class was introduced just for fixing the following issue: - * EXT-836 Nav bar: Favorites overflow menu passes left-mouse click through. - * We must explicitly handle drag and drop event by returning true - * because otherwise LLToolDragAndDrop will initiate drag and drop operation - * with the world. - */ -class LLFavoriteLandmarkToggleableMenu : public LLToggleableMenu -{ -public: - // virtual - bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, - void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) override - { - mToolbar->handleDragAndDropToMenu(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); - return true; - } - - // virtual - bool handleHover(S32 x, S32 y, MASK mask) override - { - mIsHovering = true; - LLToggleableMenu::handleHover(x, y, mask); - mIsHovering = false; - return true; - } - - // virtual - void setVisible(bool visible) override - { - // Avoid of hiding the menu during hovering - if (visible || !mIsHovering) - { - LLToggleableMenu::setVisible(visible); - } - } - - void setToolbar(LLFavoritesBarCtrl* toolbar) - { - mToolbar = toolbar; - } - - ~LLFavoriteLandmarkToggleableMenu() - { - // Enable subsequent setVisible(false) - mIsHovering = false; - setVisible(false); - } - -protected: - LLFavoriteLandmarkToggleableMenu(const LLToggleableMenu::Params& p): - LLToggleableMenu(p) - { - } - -private: - LLFavoritesBarCtrl* mToolbar { nullptr }; - bool mIsHovering { false }; - - friend class LLUICtrlFactory; -}; - -/** - * This class is needed to update an item being copied to the favorites folder - * with a sort field value (required to save favorites bar's tabs order). - * See method handleNewFavoriteDragAndDrop for more details on how this class is used. - */ -class LLItemCopiedCallback : public LLInventoryCallback -{ -public: - LLItemCopiedCallback(S32 sortField): mSortField(sortField) {} - - virtual void fire(const LLUUID& inv_item) - { - LLViewerInventoryItem* item = gInventory.getItem(inv_item); - - if (item) - { - LLFavoritesBarCtrl::sWaitingForCallabck = 0.f; - LLFavoritesOrderStorage::instance().setSortIndex(item, mSortField); - - item->setComplete(true); - item->updateServer(false); - - gInventory.updateItem(item); - gInventory.notifyObservers(); - LLFavoritesOrderStorage::instance().saveOrder(); - } - - LLView::getWindow()->setCursor(UI_CURSOR_ARROW); - } - -private: - S32 mSortField; -}; - -// updateButtons's helper -struct LLFavoritesSort -{ - // Sorting by creation date and name - // TODO - made it customizible using gSavedSettings - bool operator()(const LLViewerInventoryItem* const& a, const LLViewerInventoryItem* const& b) - { - S32 sortField1 = LLFavoritesOrderStorage::instance().getSortIndex(a->getUUID()); - S32 sortField2 = LLFavoritesOrderStorage::instance().getSortIndex(b->getUUID()); - - if (!(sortField1 < 0 && sortField2 < 0)) - { - return sortField2 > sortField1; - } - - time_t first_create = a->getCreationDate(); - time_t second_create = b->getCreationDate(); - if (first_create == second_create) - { - return (LLStringUtil::compareDict(a->getName(), b->getName()) < 0); - } - else - { - return (first_create > second_create); - } - } -}; - - -F64 LLFavoritesBarCtrl::sWaitingForCallabck = 0.f; - -LLFavoritesBarCtrl::Params::Params() -: image_drag_indication("image_drag_indication"), - more_button("more_button"), - label("label") -{ -} - -LLFavoritesBarCtrl::LLFavoritesBarCtrl(const LLFavoritesBarCtrl::Params& p) -: LLUICtrl(p), - mFont(p.font.isProvided() ? p.font() : LLFontGL::getFontSansSerifSmall()), - mOverflowMenuHandle(), - mContextMenuHandle(), - mImageDragIndication(p.image_drag_indication), - mShowDragMarker(false), - mLandingTab(NULL), - mLastTab(NULL), - mItemsListDirty(false), - mUpdateDropDownItems(true), - mRestoreOverflowMenu(false), - mDragToOverflowMenu(false), - mGetPrevItems(true), - mMouseX(0), - mMouseY(0), - mItemsChangedTimer() -{ - // Register callback for menus with current registrar (will be parent panel's registrar) - LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Favorites.DoToSelected", - boost::bind(&LLFavoritesBarCtrl::doToSelected, this, _2)); - - // Add this if we need to selectively enable items - LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Favorites.EnableSelected", - boost::bind(&LLFavoritesBarCtrl::enableSelected, this, _2)); - - gInventory.addObserver(this); - - //make chevron button - LLTextBox::Params more_button_params(p.more_button); - mMoreTextBox = LLUICtrlFactory::create (more_button_params); - mMoreTextBox->setClickedCallback(boost::bind(&LLFavoritesBarCtrl::onMoreTextBoxClicked, this)); - addChild(mMoreTextBox); - LLRect rect = mMoreTextBox->getRect(); - mMoreTextBox->setRect(LLRect(rect.mLeft - rect.getWidth(), rect.mTop, rect.mRight, rect.mBottom)); - - mDropDownItemsCount = 0; - - LLTextBox::Params label_param(p.label); - mBarLabel = LLUICtrlFactory::create (label_param); - addChild(mBarLabel); -} - -LLFavoritesBarCtrl::~LLFavoritesBarCtrl() -{ - gInventory.removeObserver(this); - - if (mOverflowMenuHandle.get()) - mOverflowMenuHandle.get()->die(); - if (mContextMenuHandle.get()) - mContextMenuHandle.get()->die(); -} - -bool LLFavoritesBarCtrl::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) -{ - *accept = ACCEPT_NO; - - LLToolDragAndDrop::ESource source = LLToolDragAndDrop::getInstance()->getSource(); - if (LLToolDragAndDrop::SOURCE_AGENT != source && LLToolDragAndDrop::SOURCE_LIBRARY != source) return false; - - switch (cargo_type) - { - - case DAD_LANDMARK: - { - /* - * add a callback to the end drag event. - * the callback will disconnet itself immediately after execution - * this is done because LLToolDragAndDrop is a common tool so it shouldn't - * be overloaded with redundant callbacks. - */ - if (!mEndDragConnection.connected()) - { - mEndDragConnection = LLToolDragAndDrop::getInstance()->setEndDragCallback(boost::bind(&LLFavoritesBarCtrl::onEndDrag, this)); - } - - // Copy the item into the favorites folder (if it's not already there). - LLInventoryItem *item = (LLInventoryItem *)cargo_data; - - if (mDragToOverflowMenu) - { - LLView* overflow_menu = mOverflowMenuHandle.get(); - if (overflow_menu && !overflow_menu->isDead() && overflow_menu->getVisible()) - { - overflow_menu->handleHover(x, y, mask); - } - } - else // Drag to the toolbar itself - { - // Drag to a landmark button? - if (LLFavoriteLandmarkButton* dest = dynamic_cast(findChildByLocalCoords(x, y))) - { - setLandingTab(dest); - } - else - { - // Drag to the "More" button? - if (mMoreTextBox && mMoreTextBox->getVisible() && mMoreTextBox->getRect().pointInRect(x, y)) - { - LLView* overflow_menu = mOverflowMenuHandle.get(); - if (!overflow_menu || overflow_menu->isDead() || !overflow_menu->getVisible()) - { - showDropDownMenu(); - } - } - - // Drag to the right of the last landmark button? - if (mLastTab && (x >= mLastTab->getRect().mRight)) - { - /* - * the condition dest == NULL can be satisfied not only in the case - * of dragging to the right from the last tab of the favbar. there is a - * small gap between each tab. if the user drags something exactly there - * then mLandingTab will be set to NULL and the dragged item will be pushed - * to the end of the favorites bar. this is incorrect behavior. that's why - * we need an additional check which excludes the case described previously - * making sure that the mouse pointer is beyond the last tab. - */ - setLandingTab(NULL); - } - } - } - - // Check whether we are dragging an existing item from the favorites bar - bool existing_item = false; - if (item && mDragItemId == item->getUUID()) - { - // There is a chance of mDragItemId being obsolete - // ex: can happen if something interrupts viewer, which - // results in viewer not geting a 'mouse up' signal - for (LLInventoryModel::item_array_t::iterator i = mItems.begin(); i != mItems.end(); ++i) - { - if ((*i)->getUUID() == mDragItemId) - { - existing_item = true; - break; - } - } - } - - if (existing_item) - { - *accept = ACCEPT_YES_SINGLE; - - showDragMarker(true); - - if (drop) - { - handleExistingFavoriteDragAndDrop(x, y); - } - } - else - { - const LLUUID favorites_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); - if (item->getParentUUID() == favorites_id) - { - LL_WARNS("FavoritesBar") << "Attemt to copy a favorite item into the same folder." << LL_ENDL; - break; - } - - *accept = ACCEPT_YES_COPY_MULTI; - - showDragMarker(true); - - if (drop) - { - if (mItems.empty()) - { - setLandingTab(NULL); - mLastTab = NULL; - } - handleNewFavoriteDragAndDrop(item, favorites_id, x, y); - } - } - } - break; - default: - break; - } - - return true; -} - -bool LLFavoritesBarCtrl::handleDragAndDropToMenu(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) -{ - mDragToOverflowMenu = true; - bool handled = handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); - mDragToOverflowMenu = false; - return handled; -} - -void LLFavoritesBarCtrl::handleExistingFavoriteDragAndDrop(S32 x, S32 y) -{ - if (LL_UNLIKELY(mItems.empty())) - return; - - LLUUID target_id; - bool insert_before = false; - if (!findDragAndDropTarget(target_id, insert_before, x, y)) - return; - - // There is no need to handle if an item was dragged onto itself - if (target_id == mDragItemId) - return; - - // Move the dragged item to the right place in the array - LLInventoryModel::updateItemsOrder(mItems, mDragItemId, target_id, insert_before); - LLFavoritesOrderStorage::instance().saveItemsOrder(mItems); - - LLView* menu = mOverflowMenuHandle.get(); - if (menu && !menu->isDead() && menu->getVisible()) - { - updateOverflowMenuItems(); - positionAndShowOverflowMenu(); - } -} - -void LLFavoritesBarCtrl::handleNewFavoriteDragAndDrop(LLInventoryItem *item, const LLUUID& favorites_id, S32 x, S32 y) -{ - // Identify the button hovered and the side to drop - LLUUID target_id; - bool insert_before = false; - // There is no need to handle if an item was dragged onto itself - if (findDragAndDropTarget(target_id, insert_before, x, y) && (target_id == mDragItemId)) - return; - - LLPointer viewer_item = new LLViewerInventoryItem(item); - - // Insert the dragged item to the right place - if (target_id.notNull()) - { - insertItem(mItems, target_id, viewer_item, insert_before); - } - else - { - // This can happen when the item list is empty - mItems.push_back(viewer_item); - } - - int sortField = 0; - LLPointer cb; - - const F64 CALLBACK_WAIT_TIME = 30.f; - sWaitingForCallabck = LLTimer::getTotalSeconds() + CALLBACK_WAIT_TIME; - - // current order is saved by setting incremental values (1, 2, 3, ...) for the sort field - for (LLInventoryModel::item_array_t::iterator i = mItems.begin(); i != mItems.end(); ++i) - { - LLViewerInventoryItem* currItem = *i; - - if (currItem->getUUID() == item->getUUID()) - { - cb = new LLItemCopiedCallback(++sortField); - } - else - { - LLFavoritesOrderStorage::instance().setSortIndex(currItem, ++sortField); - - currItem->setComplete(true); - currItem->updateServer(false); - - gInventory.updateItem(currItem); - } - } - - LLToolDragAndDrop* tool_dad = LLToolDragAndDrop::getInstance(); - if (tool_dad->getSource() == LLToolDragAndDrop::SOURCE_NOTECARD) - { - viewer_item->setType(LLAssetType::AT_LANDMARK); - copy_inventory_from_notecard(favorites_id, - tool_dad->getObjectID(), - tool_dad->getSourceID(), - viewer_item.get(), - gInventoryCallbacks.registerCB(cb)); - } - else - { - copy_inventory_item( - gAgent.getID(), - item->getPermissions().getOwner(), - item->getUUID(), - favorites_id, - std::string(), - cb); - } - - // [MAINT-2386] Ensure the favorite button has been created and is valid. - // This also ensures that mLastTab will be valid when dropping multiple - // landmarks to an empty favorites bar. - updateButtons(); - - LLView* overflow_menu = mOverflowMenuHandle.get(); - if (overflow_menu && !overflow_menu->isDead() && overflow_menu->getVisible()) - { - updateOverflowMenuItems(); - positionAndShowOverflowMenu(); - } - - LL_INFOS("FavoritesBar") << "Copied inventory item #" << item->getUUID() << " to favorites." << LL_ENDL; -} - -bool LLFavoritesBarCtrl::findDragAndDropTarget(LLUUID& target_id, bool& insert_before, S32 x, S32 y) -{ - if (mItems.empty()) - return false; - - if (mDragToOverflowMenu) - { - LLView* overflow_menu = mOverflowMenuHandle.get(); - if (LL_UNLIKELY(!overflow_menu || overflow_menu->isDead() || !overflow_menu->getVisible())) - return false; - - // Identify the menu item hovered and the side to drop - LLFavoriteLandmarkMenuItem* target_item = dynamic_cast(overflow_menu->childFromPoint(x, y)); - if (target_item) - { - insert_before = true; - } - else - { - // Choose the bottom landmark menu item - auto begin = overflow_menu->getChildList()->begin(); - auto end = overflow_menu->getChildList()->end(); - auto check = [](const LLView* child) -> bool - { - return dynamic_cast(child); - }; - // Menu items are placed in the backward order, so the bottom goes first - auto it = std::find_if(begin, end, check); - if (LL_UNLIKELY(it == end)) - return false; - target_item = (LLFavoriteLandmarkMenuItem*)*it; - insert_before = false; - } - target_id = target_item->getLandmarkID(); - } - else - { - // Identify the button hovered and the side to drop - LLFavoriteLandmarkButton* hovered_button = dynamic_cast(mLandingTab); - if (hovered_button) - { - insert_before = true; - } - else - { - // Choose the right landmark button - hovered_button = dynamic_cast(mLastTab); - if (LL_UNLIKELY(!hovered_button)) - return false; - - insert_before = false; - } - target_id = hovered_button->getLandmarkID(); - } - - return true; -} - -//virtual -void LLFavoritesBarCtrl::changed(U32 mask) -{ - if (mFavoriteFolderId.isNull()) - { - mFavoriteFolderId = gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); - - if (mFavoriteFolderId.notNull()) - { - gInventory.fetchDescendentsOf(mFavoriteFolderId); - } - } - else - { - LLInventoryModel::item_array_t items; - LLInventoryModel::cat_array_t cats; - LLIsType is_type(LLAssetType::AT_LANDMARK); - gInventory.collectDescendentsIf(mFavoriteFolderId, cats, items, LLInventoryModel::EXCLUDE_TRASH, is_type); - - for (LLInventoryModel::item_array_t::iterator i = items.begin(); i != items.end(); ++i) - { - LLFavoritesOrderStorage::instance().getSLURL((*i)->getAssetUUID()); - } - - if (sWaitingForCallabck < LLTimer::getTotalSeconds()) - { - updateButtons(); - if (!mItemsChangedTimer.getStarted()) - { - mItemsChangedTimer.start(); - } - else - { - mItemsChangedTimer.reset(); - } - } - else - { - mItemsListDirty = true; - } - } -} - -//virtual -void LLFavoritesBarCtrl::reshape(S32 width, S32 height, bool called_from_parent) -{ - S32 delta_width = width - getRect().getWidth(); - S32 delta_height = height - getRect().getHeight(); - - bool force_update = delta_width || delta_height || sForceReshape; - LLUICtrl::reshape(width, height, called_from_parent); - updateButtons(force_update); -} - -void LLFavoritesBarCtrl::draw() -{ - LLUICtrl::draw(); - - if (mShowDragMarker) - { - S32 w = mImageDragIndication->getWidth(); - S32 h = mImageDragIndication->getHeight(); - - if (mLandingTab) - { - // mouse pointer hovers over an existing tab - LLRect rect = mLandingTab->getRect(); - mImageDragIndication->draw(rect.mLeft, rect.getHeight(), w, h); - } - else if (mLastTab) - { - // mouse pointer hovers over the favbar empty space (right to the last tab) - LLRect rect = mLastTab->getRect(); - mImageDragIndication->draw(rect.mRight, rect.getHeight(), w, h); - } - // Once drawn, mark this false so we won't draw it again (unless we hit the favorite bar again) - mShowDragMarker = false; - } - if (mItemsChangedTimer.getStarted()) - { - if (mItemsChangedTimer.getElapsedTimeF32() > 1.f) - { - LLFavoritesOrderStorage::instance().saveFavoritesRecord(); - mItemsChangedTimer.stop(); - } - } - - if(!mItemsChangedTimer.getStarted() && LLFavoritesOrderStorage::instance().mUpdateRequired) - { - LLFavoritesOrderStorage::instance().mUpdateRequired = false; - mItemsChangedTimer.start(); - } - - if (mItemsListDirty && sWaitingForCallabck < LLTimer::getTotalSeconds()) - { - updateButtons(); - if (!mItemsChangedTimer.getStarted()) - { - mItemsChangedTimer.start(); - } - else - { - mItemsChangedTimer.reset(); - } - } -} - -const LLButton::Params& LLFavoritesBarCtrl::getButtonParams() -{ - static LLButton::Params button_params; - static bool params_initialized = false; - - if (!params_initialized) - { - LLXMLNodePtr button_xml_node; - if(LLUICtrlFactory::getLayeredXMLNode("favorites_bar_button.xml", button_xml_node)) - { - LLXUIParser parser; - parser.readXUI(button_xml_node, button_params, "favorites_bar_button.xml"); - } - params_initialized = true; - } - - return button_params; -} - -void LLFavoritesBarCtrl::updateButtons(bool force_update) -{ - if (LLApp::isExiting()) - { - return; - } - - mItemsListDirty = false; - mItems.clear(); - - if (!collectFavoriteItems(mItems)) - { - return; - } - - if(mGetPrevItems && gInventory.isCategoryComplete(mFavoriteFolderId)) - { - for (LLInventoryModel::item_array_t::iterator it = mItems.begin(); it != mItems.end(); it++) - { - LLFavoritesOrderStorage::instance().mFavoriteNames[(*it)->getUUID()]= (*it)->getName(); - } - LLFavoritesOrderStorage::instance().mPrevFavorites = mItems; - mGetPrevItems = false; - - if (LLFavoritesOrderStorage::instance().isStorageUpdateNeeded()) - { - if (!mItemsChangedTimer.getStarted()) - { - mItemsChangedTimer.start(); - } - } - } - - const LLButton::Params& button_params = getButtonParams(); - - if(mItems.empty()) - { - mBarLabel->setVisible(true); - mLastTab = NULL; - } - else - { - mBarLabel->setVisible(false); - } - const child_list_t* childs = getChildList(); - child_list_const_iter_t child_it = childs->begin(); - int first_changed_item_index = 0; - if (!force_update) - { - //lets find first changed button - while (child_it != childs->end() && first_changed_item_index < mItems.size()) - { - LLFavoriteLandmarkButton* button = dynamic_cast (*child_it); - if (button) - { - const LLViewerInventoryItem *item = mItems[first_changed_item_index].get(); - if (item) - { - // an child's order and mItems should be same - if (button->getLandmarkID() != item->getUUID() // sort order has been changed - || button->getLabelSelected() != item->getName()) // favorite's name has been changed - { - break; - } - } - first_changed_item_index++; - } - child_it++; - } - } - // now first_changed_item_index should contains a number of button that need to change - - if (first_changed_item_index <= mItems.size()) - { - // Rebuild the buttons only - // child_list_t is a linked list, so safe to erase from the middle if we pre-increment the iterator - - while (child_it != childs->end()) - { - //lets remove other landmarks button and rebuild it - child_list_const_iter_t cur_it = child_it++; - LLFavoriteLandmarkButton* button = - dynamic_cast (*cur_it); - if (button) - { - if (mLastTab == button) - { - mLastTab = NULL; - } - removeChild(button); - delete button; - } - } - // we have to remove ChevronButton to make sure that the last item will be LandmarkButton to get the right aligning - // keep in mind that we are cutting all buttons in space between the last visible child of favbar and ChevronButton - if (mMoreTextBox->getParent() == this) - { - removeChild(mMoreTextBox); - } - int last_right_edge = 0; - //calculate new buttons offset - if (getChildList()->size() > 0) - { - //find last visible child to get the rightest button offset - child_list_const_reverse_iter_t last_visible_it = - std::find_if( - childs->rbegin(), childs->rend(), - [](const child_list_t::value_type& child) - { return child->getVisible(); }); - if(last_visible_it != childs->rend()) - { - last_right_edge = (*last_visible_it)->getRect().mRight; - } - } - //last_right_edge is saving coordinates - LLButton* last_new_button = NULL; - int j = first_changed_item_index; - for (; j < mItems.size(); j++) - { - last_new_button = createButton(mItems[j], button_params, last_right_edge); - if (!last_new_button) - { - break; - } - sendChildToBack(last_new_button); - last_right_edge = last_new_button->getRect().mRight; - - mLastTab = last_new_button; - } - if (!mLastTab && mItems.size() > 0) - { - // mMoreTextBox was removed, so LLFavoriteLandmarkButtons - // should be the only ones in the list - mLastTab = dynamic_cast(childs->back()); - } - - mFirstDropDownItem = j; - // Chevron button - if (mFirstDropDownItem < mItems.size()) - { - // if updateButton had been called it means: - // or there are some new favorites, or width had been changed - // so if we need to display chevron button, we must update dropdown items too. - mUpdateDropDownItems = true; - S32 buttonHGap = button_params.rect.left; // default value - // Chevron button should stay right aligned - LLRect rect(mMoreTextBox->getRect()); - rect.translate(getRect().mRight - rect.mRight - buttonHGap, 0); - - addChild(mMoreTextBox); - mMoreTextBox->setRect(rect); - mMoreTextBox->setVisible(true); - } - // Update overflow menu - LLToggleableMenu* overflow_menu = static_cast (mOverflowMenuHandle.get()); - if (overflow_menu && overflow_menu->getVisible() && (overflow_menu->getItemCount() != mDropDownItemsCount)) - { - overflow_menu->setVisible(false); - if (mUpdateDropDownItems) - { - showDropDownMenu(); - } - } - } - else - { - mUpdateDropDownItems = false; - } - -} - -LLButton* LLFavoritesBarCtrl::createButton(const LLPointer item, const LLButton::Params& button_params, S32 x_offset) -{ - S32 def_button_width = button_params.rect.width; - S32 button_x_delta = button_params.rect.left; // default value - S32 curr_x = x_offset; - - /** - * WORKAROUND: - * There are some problem with displaying of fonts in buttons. - * Empty space or ellipsis might be displayed instead of last symbols, even though the width of the button is enough. - * The problem disappears if we pad the button with 20 pixels. - */ - int required_width = mFont->getWidth(item->getName()) + 20; - int width = required_width > def_button_width? def_button_width : required_width; - LLFavoriteLandmarkButton* fav_btn = NULL; - - // do we have a place for next button + double buttonHGap + mMoreTextBox ? - if(curr_x + width + 2*button_x_delta + mMoreTextBox->getRect().getWidth() > getRect().mRight ) - { - return NULL; - } - LLButton::Params fav_btn_params(button_params); - fav_btn = LLUICtrlFactory::create(fav_btn_params); - if (NULL == fav_btn) - { - LL_WARNS("FavoritesBar") << "Unable to create LLFavoriteLandmarkButton widget: " << item->getName() << LL_ENDL; - return NULL; - } - - addChild(fav_btn); - - LLRect butt_rect (fav_btn->getRect()); - fav_btn->setLandmarkID(item->getUUID()); - butt_rect.setOriginAndSize(curr_x + button_x_delta, fav_btn->getRect().mBottom, width, fav_btn->getRect().getHeight()); - - fav_btn->setRect(butt_rect); - // change only left and save bottom - fav_btn->setFont(mFont); - fav_btn->setLabel(item->getName()); - fav_btn->setToolTip(item->getName()); - fav_btn->setCommitCallback(boost::bind(&LLFavoritesBarCtrl::onButtonClick, this, item->getUUID())); - fav_btn->setRightMouseDownCallback(boost::bind(&LLFavoritesBarCtrl::onButtonRightClick, this, item->getUUID(), _1, _2, _3,_4 )); - - fav_btn->LLUICtrl::setMouseDownCallback(boost::bind(&LLFavoritesBarCtrl::onButtonMouseDown, this, item->getUUID(), _1, _2, _3, _4)); - fav_btn->LLUICtrl::setMouseUpCallback(boost::bind(&LLFavoritesBarCtrl::onButtonMouseUp, this, item->getUUID(), _1, _2, _3, _4)); - - return fav_btn; -} - - -bool LLFavoritesBarCtrl::postBuild() -{ - // make the popup menu available - LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile("menu_favorites.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - if (!menu) - { - menu = LLUICtrlFactory::getDefaultWidget("inventory_menu"); - } - menu->setBackgroundColor(LLUIColorTable::instance().getColor("MenuPopupBgColor")); - mContextMenuHandle = menu->getHandle(); - - return true; -} - -bool LLFavoritesBarCtrl::collectFavoriteItems(LLInventoryModel::item_array_t &items) -{ - - if (mFavoriteFolderId.isNull()) - return false; - - - LLInventoryModel::cat_array_t cats; - - LLIsType is_type(LLAssetType::AT_LANDMARK); - gInventory.collectDescendentsIf(mFavoriteFolderId, cats, items, LLInventoryModel::EXCLUDE_TRASH, is_type); - - std::sort(items.begin(), items.end(), LLFavoritesSort()); - - if (needToSaveItemsOrder(items)) - { - S32 sortField = 0; - for (LLInventoryModel::item_array_t::iterator i = items.begin(); i != items.end(); ++i) - { - LLFavoritesOrderStorage::instance().setSortIndex((*i), ++sortField); - } - LLFavoritesOrderStorage::instance().mSaveOnExit = true; - } - - return true; -} - -void LLFavoritesBarCtrl::onMoreTextBoxClicked() -{ - LLUI::getInstance()->getMousePositionScreen(&mMouseX, &mMouseY); - showDropDownMenu(); -} - -void LLFavoritesBarCtrl::showDropDownMenu() -{ - if (mOverflowMenuHandle.isDead()) - { - createOverflowMenu(); - } - - LLToggleableMenu* menu = (LLToggleableMenu*)mOverflowMenuHandle.get(); - if (menu && menu->toggleVisibility()) - { - if (mUpdateDropDownItems) - { - updateOverflowMenuItems(); - } - else - { - menu->buildDrawLabels(); - } - - menu->updateParent(LLMenuGL::sMenuContainer); - menu->setButtonRect(mMoreTextBox->getRect(), this); - positionAndShowOverflowMenu(); - } -} - -void LLFavoritesBarCtrl::createOverflowMenu() -{ - LLToggleableMenu::Params menu_p; - menu_p.name("favorites menu"); - menu_p.can_tear_off(false); - menu_p.visible(false); - menu_p.scrollable(true); - menu_p.max_scrollable_items = 10; - menu_p.preferred_width = DROP_DOWN_MENU_WIDTH; - - LLFavoriteLandmarkToggleableMenu* menu = LLUICtrlFactory::create(menu_p); - menu->setToolbar(this); - mOverflowMenuHandle = menu->getHandle(); -} - -void LLFavoritesBarCtrl::updateOverflowMenuItems() -{ - LLToggleableMenu* menu = (LLToggleableMenu*)mOverflowMenuHandle.get(); - menu->empty(); - - U32 widest_item = 0; - - for (S32 i = mFirstDropDownItem; i < mItems.size(); i++) - { - LLViewerInventoryItem* item = mItems.at(i); - const std::string& item_name = item->getName(); - - LLFavoriteLandmarkMenuItem::Params item_params; - item_params.name(item_name); - item_params.label(item_name); - item_params.on_click.function(boost::bind(&LLFavoritesBarCtrl::onButtonClick, this, item->getUUID())); - - LLFavoriteLandmarkMenuItem *menu_item = LLUICtrlFactory::create(item_params); - menu_item->initFavoritesBarPointer(this); - menu_item->setRightMouseDownCallback(boost::bind(&LLFavoritesBarCtrl::onButtonRightClick, this, item->getUUID(), _1, _2, _3, _4)); - menu_item->LLUICtrl::setMouseDownCallback(boost::bind(&LLFavoritesBarCtrl::onButtonMouseDown, this, item->getUUID(), _1, _2, _3, _4)); - menu_item->LLUICtrl::setMouseUpCallback(boost::bind(&LLFavoritesBarCtrl::onButtonMouseUp, this, item->getUUID(), _1, _2, _3, _4)); - menu_item->setLandmarkID(item->getUUID()); - - fitLabelWidth(menu_item); - - widest_item = llmax(widest_item, menu_item->getNominalWidth()); - - menu->addChild(menu_item); - } - - menu->buildDrawLabels(); - mDropDownItemsCount = menu->getItemCount(); - addOpenLandmarksMenuItem(menu); - mUpdateDropDownItems = false; -} - -void LLFavoritesBarCtrl::fitLabelWidth(LLMenuItemCallGL* menu_item) -{ - U32 max_width = llmin(DROP_DOWN_MENU_WIDTH, getRect().getWidth()); - std::string item_name = menu_item->getName(); - - // Check whether item name wider than menu - if (menu_item->getNominalWidth() > max_width) - { - S32 chars_total = item_name.length(); - S32 chars_fitted = 1; - menu_item->setLabel(LLStringExplicit("")); - S32 label_space = max_width - menu_item->getFont()->getWidth("...") - - menu_item->getNominalWidth();// This returns width of menu item with empty label (pad pixels) - - while (chars_fitted < chars_total - && menu_item->getFont()->getWidth(item_name, 0, chars_fitted) < label_space) - { - chars_fitted++; - } - chars_fitted--; // Rolling back one char, that doesn't fit - - menu_item->setLabel(item_name.substr(0, chars_fitted) + "..."); - } -} - -void LLFavoritesBarCtrl::addOpenLandmarksMenuItem(LLToggleableMenu* menu) -{ - std::string label_untrans = "Open landmarks"; - std::string label_transl; - bool translated = LLTrans::findString(label_transl, label_untrans); - - LLMenuItemCallGL::Params item_params; - item_params.name("open_my_landmarks"); - item_params.label(translated ? label_transl: label_untrans); - LLSD key; - key["type"] = "open_landmark_tab"; - item_params.on_click.function(boost::bind(&LLFloaterSidePanelContainer::showPanel, "places", key)); - LLMenuItemCallGL* menu_item = LLUICtrlFactory::create(item_params); - - fitLabelWidth(menu_item); - - LLMenuItemSeparatorGL::Params sep_params; - sep_params.enabled_color=LLUIColorTable::instance().getColor("MenuItemEnabledColor"); - sep_params.disabled_color=LLUIColorTable::instance().getColor("MenuItemDisabledColor"); - sep_params.highlight_bg_color=LLUIColorTable::instance().getColor("MenuItemHighlightBgColor"); - sep_params.highlight_fg_color=LLUIColorTable::instance().getColor("MenuItemHighlightFgColor"); - LLMenuItemSeparatorGL* separator = LLUICtrlFactory::create(sep_params); - - menu->addChild(separator); - menu->addChild(menu_item); -} - -void LLFavoritesBarCtrl::positionAndShowOverflowMenu() -{ - LLToggleableMenu* menu = (LLToggleableMenu*)mOverflowMenuHandle.get(); - U32 max_width = llmin(DROP_DOWN_MENU_WIDTH, getRect().getWidth()); - - S32 menu_x = getRect().getWidth() - max_width; - S32 menu_y = getParent()->getRect().mBottom - DROP_DOWN_MENU_TOP_PAD; - - // the menu should be offset of the right edge of the window - // so it's no covered by buttons in the right-side toolbar. - LLToolBar* right_toolbar = gToolBarView->getChild("toolbar_right"); - if (right_toolbar && right_toolbar->hasButtons()) - { - S32 toolbar_top = 0; - - if (LLView* top_border_panel = right_toolbar->getChild("button_panel")) - { - toolbar_top = top_border_panel->calcScreenRect().mTop; - } - - // Calculating the bottom (in screen coord) of the drop down menu - S32 menu_top = getParent()->getRect().mBottom - DROP_DOWN_MENU_TOP_PAD; - S32 menu_bottom = menu_top - menu->getRect().getHeight(); - S32 menu_bottom_screen = 0; - - localPointToScreen(0, menu_bottom, &menu_top, &menu_bottom_screen); - - if (menu_bottom_screen < toolbar_top) - { - menu_x -= right_toolbar->getRect().getWidth(); - } - } - - LLMenuGL::showPopup(this, menu, menu_x, menu_y, mMouseX, mMouseY); -} - -void LLFavoritesBarCtrl::onButtonClick(LLUUID item_id) -{ - // We only have one Inventory, gInventory. Some day this should be better abstracted. - LLInvFVBridgeAction::doAction(item_id,&gInventory); -} - -void LLFavoritesBarCtrl::onButtonRightClick( LLUUID item_id,LLView* fav_button,S32 x,S32 y,MASK mask) -{ - mSelectedItemID = item_id; - - LLMenuGL* menu = (LLMenuGL*)mContextMenuHandle.get(); - if (!menu) - { - return; - } - - // Remember that the context menu was shown simultaneously with the overflow menu, - // so that we can restore the overflow menu when user clicks a context menu item - // (which hides the overflow menu). - { - LLView* overflow_menu = mOverflowMenuHandle.get(); - mRestoreOverflowMenu = overflow_menu && overflow_menu->getVisible(); - } - - // Release mouse capture so hover events go to the popup menu - // because this is happening during a mouse down. - gFocusMgr.setMouseCapture(NULL); - - menu->updateParent(LLMenuGL::sMenuContainer); - LLMenuGL::showPopup(fav_button, menu, x, y); -} - -bool LLFavoritesBarCtrl::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = childrenHandleRightMouseDown( x, y, mask) != NULL; - if(!handled && !gMenuHolder->hasVisibleMenu()) - { - show_navbar_context_menu(this,x,y); - handled = true; - } - - return handled; -} -void copy_slurl_to_clipboard_cb(std::string& slurl) -{ - LLClipboard::instance().copyToClipboard(utf8str_to_wstring(slurl),0,slurl.size()); - - LLSD args; - args["SLURL"] = slurl; - LLNotificationsUtil::add("CopySLURL", args); -} - - -bool LLFavoritesBarCtrl::enableSelected(const LLSD& userdata) -{ - std::string param = userdata.asString(); - - if (param == std::string("can_paste")) - { - return isClipboardPasteable(); - } - else if (param == "create_pick") - { - return !LLAgentPicksInfo::getInstance()->isPickLimitReached(); - } - - return false; -} - -void LLFavoritesBarCtrl::doToSelected(const LLSD& userdata) -{ - std::string action = userdata.asString(); - LL_INFOS("FavoritesBar") << "Action = " << action << " Item = " << mSelectedItemID.asString() << LL_ENDL; - - LLViewerInventoryItem* item = gInventory.getItem(mSelectedItemID); - if (!item) - return; - - if (action == "open") - { - onButtonClick(item->getUUID()); - } - else if (action == "about") - { - LLSD key; - key["type"] = "landmark"; - key["id"] = mSelectedItemID; - - LLFloaterSidePanelContainer::showPanel("places", key); - } - else if (action == "copy_slurl") - { - LLVector3d posGlobal; - LLLandmarkActions::getLandmarkGlobalPos(mSelectedItemID, posGlobal); - - if (!posGlobal.isExactlyZero()) - { - LLLandmarkActions::getSLURLfromPosGlobal(posGlobal, copy_slurl_to_clipboard_cb); - } - } - else if (action == "show_on_map") - { - LLFloaterWorldMap* worldmap_instance = LLFloaterWorldMap::getInstance(); - - LLVector3d posGlobal; - LLLandmarkActions::getLandmarkGlobalPos(mSelectedItemID, posGlobal); - - if (!posGlobal.isExactlyZero() && worldmap_instance) - { - worldmap_instance->trackLocation(posGlobal); - LLFloaterReg::showInstance("world_map", "center"); - } - } - else if (action == "create_pick") - { - LLSD args; - args["type"] = "create_pick"; - args["item_id"] = item->getUUID(); - LLFloaterSidePanelContainer::showPanel("places", args); - } - else if (action == "cut") - { - } - else if (action == "copy") - { - LLClipboard::instance().copyToClipboard(mSelectedItemID, LLAssetType::AT_LANDMARK); - } - else if (action == "paste") - { - pasteFromClipboard(); - } - else if (action == "delete") - { - gInventory.removeItem(mSelectedItemID); - } - else if (action == "rename") - { - LLSD args; - args["NAME"] = item->getName(); - - LLSD payload; - payload["id"] = mSelectedItemID; - - LLNotificationsUtil::add("RenameLandmark", args, payload, boost::bind(onRenameCommit, _1, _2)); - } - else if (action == "move_to_landmarks") - { - change_item_parent(mSelectedItemID, gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK)); - } - - // Pop-up the overflow menu again (it gets hidden whenever the user clicks a context menu item). - // See EXT-4217 and STORM-207. - LLToggleableMenu* menu = (LLToggleableMenu*) mOverflowMenuHandle.get(); - if (mRestoreOverflowMenu && menu && !menu->getVisible()) - { - menu->resetScrollPositionOnShow(false); - showDropDownMenu(); - menu->resetScrollPositionOnShow(true); - } -} - -bool LLFavoritesBarCtrl::onRenameCommit(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (0 == option) - { - LLUUID id = notification["payload"]["id"].asUUID(); - LLInventoryItem *item = gInventory.getItem(id); - std::string landmark_name = response["new_name"].asString(); - LLStringUtil::trim(landmark_name); - - if (!landmark_name.empty() && item && item->getName() != landmark_name) - { - LLPointer new_item = new LLViewerInventoryItem(item); - new_item->rename(landmark_name); - new_item->updateServer(false); - gInventory.updateItem(new_item); - } - } - - return false; -} - -bool LLFavoritesBarCtrl::isClipboardPasteable() const -{ - if (!LLClipboard::instance().hasContents()) - { - return false; - } - - std::vector objects; - LLClipboard::instance().pasteFromClipboard(objects); - S32 count = objects.size(); - for(S32 i = 0; i < count; i++) - { - const LLUUID &item_id = objects.at(i); - - // Can't paste folders - const LLInventoryCategory *cat = gInventory.getCategory(item_id); - if (cat) - { - return false; - } - - const LLInventoryItem *item = gInventory.getItem(item_id); - if (item && LLAssetType::AT_LANDMARK != item->getType()) - { - return false; - } - } - return true; -} - -void LLFavoritesBarCtrl::pasteFromClipboard() const -{ - LLInventoryModel* model = &gInventory; - if(model && isClipboardPasteable()) - { - LLInventoryItem* item = NULL; - std::vector objects; - LLClipboard::instance().pasteFromClipboard(objects); - S32 count = objects.size(); - LLUUID parent_id(mFavoriteFolderId); - for(S32 i = 0; i < count; i++) - { - item = model->getItem(objects.at(i)); - if (item) - { - copy_inventory_item( - gAgent.getID(), - item->getPermissions().getOwner(), - item->getUUID(), - parent_id, - std::string(), - LLPointer(NULL)); - } - } - } -} - -void LLFavoritesBarCtrl::onButtonMouseDown(LLUUID id, LLUICtrl* ctrl, S32 x, S32 y, MASK mask) -{ - // EXT-6997 (Fav bar: Pop-up menu for LM in overflow dropdown is kept after LM was dragged away) - // mContextMenuHandle.get() - is a pop-up menu (of items) in already opened dropdown menu. - // We have to check and set visibility of pop-up menu in such a way instead of using - // LLMenuHolderGL::hideMenus() because it will close both menus(dropdown and pop-up), but - // we need to close only pop-up menu while dropdown one should be still opened. - LLMenuGL* menu = (LLMenuGL*)mContextMenuHandle.get(); - if(menu && menu->getVisible()) - { - menu->setVisible(false); - } - - mDragItemId = id; - mStartDrag = true; - - S32 screenX, screenY; - localPointToScreen(x, y, &screenX, &screenY); - - LLToolDragAndDrop::getInstance()->setDragStart(screenX, screenY); -} - -void LLFavoritesBarCtrl::onButtonMouseUp(LLUUID id, LLUICtrl* ctrl, S32 x, S32 y, MASK mask) -{ - mStartDrag = false; - mDragItemId = LLUUID::null; -} - -void LLFavoritesBarCtrl::onEndDrag() -{ - mEndDragConnection.disconnect(); - - showDragMarker(false); - mDragItemId = LLUUID::null; - LLView::getWindow()->setCursor(UI_CURSOR_ARROW); -} - -bool LLFavoritesBarCtrl::handleHover(S32 x, S32 y, MASK mask) -{ - if (mDragItemId != LLUUID::null && mStartDrag) - { - S32 screenX, screenY; - localPointToScreen(x, y, &screenX, &screenY); - - if(LLToolDragAndDrop::getInstance()->isOverThreshold(screenX, screenY)) - { - LLToolDragAndDrop::getInstance()->beginDrag( - DAD_LANDMARK, mDragItemId, - LLToolDragAndDrop::SOURCE_LIBRARY); - - mStartDrag = false; - - return LLToolDragAndDrop::getInstance()->handleHover(x, y, mask); - } - } - - return true; -} - -LLUICtrl* LLFavoritesBarCtrl::findChildByLocalCoords(S32 x, S32 y) -{ - LLUICtrl* ctrl = NULL; - const child_list_t* list = getChildList(); - - for (child_list_const_iter_t i = list->begin(); i != list->end(); ++i) - { - // Look only for children that are favorite buttons - if ((*i)->getName() == "favorites_bar_btn") - { - LLRect rect = (*i)->getRect(); - // We consider a button hit if the cursor is left of the right side - // This makes the hit a bit less finicky than hitting directly on the button itself - if (x <= rect.mRight) - { - ctrl = dynamic_cast(*i); - break; - } - } - } - - return ctrl; -} - -bool LLFavoritesBarCtrl::needToSaveItemsOrder(const LLInventoryModel::item_array_t& items) -{ - bool result = false; - - // if there is an item without sort order field set, we need to save items order - for (LLInventoryModel::item_array_t::const_iterator i = items.begin(); i != items.end(); ++i) - { - if (LLFavoritesOrderStorage::instance().getSortIndex((*i)->getUUID()) < 0) - { - result = true; - break; - } - } - - return result; -} - -void LLFavoritesBarCtrl::insertItem(LLInventoryModel::item_array_t& items, const LLUUID& dest_item_id, LLViewerInventoryItem* insertedItem, bool insert_before) -{ - // Get the iterator to the destination item - LLInventoryModel::item_array_t::iterator it_dest = LLInventoryModel::findItemIterByUUID(items, dest_item_id); - if (it_dest == items.end()) - return; - - // Go to the next element if one wishes to insert after the dest element - if (!insert_before) - { - ++it_dest; - } - - // Insert the source item in the right place - if (it_dest != items.end()) - { - items.insert(it_dest, insertedItem); - } - else - { - // Append to the list if it_dest reached the end - items.push_back(insertedItem); - } -} - -const std::string LLFavoritesOrderStorage::SORTING_DATA_FILE_NAME = "landmarks_sorting.xml"; -const S32 LLFavoritesOrderStorage::NO_INDEX = -1; -bool LLFavoritesOrderStorage::mSaveOnExit = false; - -void LLFavoritesOrderStorage::setSortIndex(const LLViewerInventoryItem* inv_item, S32 sort_index) -{ - mSortIndexes[inv_item->getUUID()] = sort_index; - mIsDirty = true; - getSLURL(inv_item->getAssetUUID()); -} - -S32 LLFavoritesOrderStorage::getSortIndex(const LLUUID& inv_item_id) -{ - sort_index_map_t::const_iterator it = mSortIndexes.find(inv_item_id); - if (it != mSortIndexes.end()) - { - return it->second; - } - return NO_INDEX; -} - -void LLFavoritesOrderStorage::removeSortIndex(const LLUUID& inv_item_id) -{ - mSortIndexes.erase(inv_item_id); - mIsDirty = true; -} - -void LLFavoritesOrderStorage::getSLURL(const LLUUID& asset_id) -{ - slurls_map_t::iterator slurl_iter = mSLURLs.find(asset_id); - if (slurl_iter != mSLURLs.end()) return; // SLURL for current landmark is already cached - - LLLandmark* lm = gLandmarkList.getAsset(asset_id, - boost::bind(&LLFavoritesOrderStorage::onLandmarkLoaded, this, asset_id, _1)); - if (lm) - { - LL_DEBUGS("FavoritesBar") << "landmark for " << asset_id << " already loaded" << LL_ENDL; - onLandmarkLoaded(asset_id, lm); - } - return; -} - -// static -std::string LLFavoritesOrderStorage::getStoredFavoritesFilename(const std::string &grid) -{ - std::string user_dir = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, ""); - - return (user_dir.empty() ? "" - : gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, - "stored_favorites_" - + grid - + ".xml") - ); -} - -// static -std::string LLFavoritesOrderStorage::getStoredFavoritesFilename() -{ - return getStoredFavoritesFilename(LLGridManager::getInstance()->getGrid()); -} - -// static -void LLFavoritesOrderStorage::destroyClass() -{ - LLFavoritesOrderStorage::instance().cleanup(); - - - std::string old_filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "stored_favorites.xml"); - llifstream file; - file.open(old_filename.c_str()); - if (file.is_open()) - { - file.close(); - std::string new_filename = getStoredFavoritesFilename(); - LL_INFOS("FavoritesBar") << "moving favorites from old name '" << old_filename - << "' to new name '" << new_filename << "'" - << LL_ENDL; - LLFile::copy(old_filename,new_filename); - LLFile::remove(old_filename); - } - - std::string filename = getSavedOrderFileName(); - file.open(filename.c_str()); - if (file.is_open()) - { - file.close(); - LLFile::remove(filename); - } - if(mSaveOnExit || gSavedSettings.getBOOL("UpdateRememberPasswordSetting")) - { - LLFavoritesOrderStorage::instance().saveFavoritesRecord(true); - } -} - -std::string LLFavoritesOrderStorage::getSavedOrderFileName() -{ - // If we quit from the login screen we will not have an SL account - // name. Don't try to save, otherwise we'll dump a file in - // C:\Program Files\SecondLife\ or similar. JC - std::string user_dir = gDirUtilp->getLindenUserDir(); - return (user_dir.empty() ? "" : gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, SORTING_DATA_FILE_NAME)); -} - -void LLFavoritesOrderStorage::load() -{ - std::string filename = getSavedOrderFileName(); - LLSD settings_llsd; - llifstream file; - file.open(filename.c_str()); - if (file.is_open()) - { - LLSDSerialize::fromXML(settings_llsd, file); - LL_INFOS("FavoritesBar") << "loaded favorites order from '" << filename << "' " - << (settings_llsd.isMap() ? "" : "un") << "successfully" - << LL_ENDL; - file.close(); - mSaveOnExit = true; - - for (LLSD::map_const_iterator iter = settings_llsd.beginMap(); - iter != settings_llsd.endMap(); ++iter) - { - mSortIndexes.insert(std::make_pair(LLUUID(iter->first), (S32)iter->second.asInteger())); - } - } - else - { - filename = getStoredFavoritesFilename(); - if (!filename.empty()) - { - llifstream in_file; - in_file.open(filename.c_str()); - LLSD fav_llsd; - if (in_file.is_open()) - { - LLSDSerialize::fromXML(fav_llsd, in_file); - LL_INFOS("FavoritesBar") << "loaded favorites from '" << filename << "' " - << (fav_llsd.isMap() ? "" : "un") << "successfully" - << LL_ENDL; - in_file.close(); - if (fav_llsd.isMap() && fav_llsd.has(gAgentUsername)) - { - mStorageFavorites = fav_llsd[gAgentUsername]; - - S32 index = 0; - bool needs_validation = gSavedPerAccountSettings.getBOOL("ShowFavoritesOnLogin"); - for (LLSD::array_iterator iter = mStorageFavorites.beginArray(); - iter != mStorageFavorites.endArray(); ++iter) - { - // Validation - LLUUID fv_id = iter->get("id").asUUID(); - if (needs_validation - && (fv_id.isNull() - || iter->get("asset_id").asUUID().isNull() - || iter->get("name").asString().empty() - || iter->get("slurl").asString().empty())) - { - mRecreateFavoriteStorage = true; - } - - mSortIndexes.insert(std::make_pair(fv_id, index)); - index++; - } - } - } - else - { - LL_WARNS("FavoritesBar") << "unable to open favorites from '" << filename << "'" << LL_ENDL; - } - } - } -} - -// static -void LLFavoritesOrderStorage::removeFavoritesRecordOfUser(const std::string &user, const std::string &grid) -{ - std::string filename = getStoredFavoritesFilename(grid); - if (!filename.empty()) - { - LLSD fav_llsd; - llifstream file; - file.open(filename.c_str()); - if (file.is_open()) - { - LLSDSerialize::fromXML(fav_llsd, file); - file.close(); - - // Note : use the "John Doe" and not the "john.doe" version of the name. - // See saveFavoritesSLURLs() here above for the reason why. - if (fav_llsd.has(user)) - { - LLSD user_llsd = fav_llsd[user]; - - if ((user_llsd.beginArray() != user_llsd.endArray()) && user_llsd.beginArray()->has("id")) - { - for (LLSD::array_iterator iter = user_llsd.beginArray(); iter != user_llsd.endArray(); ++iter) - { - LLSD value; - value["id"] = iter->get("id").asUUID(); - iter->assign(value); - } - fav_llsd[user] = user_llsd; - llofstream file; - file.open(filename.c_str()); - if (file.is_open()) - { - LLSDSerialize::toPrettyXML(fav_llsd, file); - file.close(); - } - } - else - { - LL_INFOS("FavoritesBar") << "Removed favorites for " << user << LL_ENDL; - fav_llsd.erase(user); - } - } - - llofstream out_file; - out_file.open(filename.c_str()); - if (out_file.is_open()) - { - LLSDSerialize::toPrettyXML(fav_llsd, out_file); - LL_INFOS("FavoritesBar") << "saved favorites to '" << filename << "' " - << LL_ENDL; - out_file.close(); - } - } - } -} - -// static -void LLFavoritesOrderStorage::removeFavoritesRecordOfUser() -{ - std::string filename = getStoredFavoritesFilename(); - if (!filename.empty()) - { - LLSD fav_llsd; - llifstream file; - file.open(filename.c_str()); - if (file.is_open()) - { - LLSDSerialize::fromXML(fav_llsd, file); - file.close(); - - LLAvatarName av_name; - LLAvatarNameCache::get( gAgentID, &av_name ); - // Note : use the "John Doe" and not the "john.doe" version of the name. - // See saveFavoritesSLURLs() here above for the reason why. - if (fav_llsd.has(av_name.getUserName())) - { - LLSD user_llsd = fav_llsd[av_name.getUserName()]; - - if ((user_llsd.beginArray()!= user_llsd.endArray()) && user_llsd.beginArray()->has("id")) - { - for (LLSD::array_iterator iter = user_llsd.beginArray();iter != user_llsd.endArray(); ++iter) - { - LLSD value; - value["id"]= iter->get("id").asUUID(); - iter->assign(value); - } - fav_llsd[av_name.getUserName()] = user_llsd; - llofstream file; - file.open(filename.c_str()); - if ( file.is_open() ) - { - LLSDSerialize::toPrettyXML(fav_llsd, file); - file.close(); - } - } - else - { - LL_INFOS("FavoritesBar") << "Removed favorites for " << av_name.getUserName() << LL_ENDL; - fav_llsd.erase(av_name.getUserName()); - } - } - - llofstream out_file; - out_file.open(filename.c_str()); - if ( out_file.is_open() ) - { - LLSDSerialize::toPrettyXML(fav_llsd, out_file); - LL_INFOS("FavoritesBar") << "saved favorites to '" << filename << "' " - << LL_ENDL; - out_file.close(); - } - } - } -} - -void LLFavoritesOrderStorage::onLandmarkLoaded(const LLUUID& asset_id, LLLandmark* landmark) -{ - if (landmark) - { - LL_DEBUGS("FavoritesBar") << "landmark for " << asset_id << " loaded" << LL_ENDL; - LLVector3d pos_global; - if (!landmark->getGlobalPos(pos_global)) - { - // If global position was unknown on first getGlobalPos() call - // it should be set for the subsequent calls. - landmark->getGlobalPos(pos_global); - } - - if (!pos_global.isExactlyZero()) - { - LL_DEBUGS("FavoritesBar") << "requesting slurl for landmark " << asset_id << LL_ENDL; - LLLandmarkActions::getSLURLfromPosGlobal(pos_global, - boost::bind(&LLFavoritesOrderStorage::storeFavoriteSLURL, this, asset_id, _1)); - } - } -} - -void LLFavoritesOrderStorage::storeFavoriteSLURL(const LLUUID& asset_id, std::string& slurl) -{ - LL_DEBUGS("FavoritesBar") << "Saving landmark SLURL '" << slurl << "' for " << asset_id << LL_ENDL; - mSLURLs[asset_id] = slurl; -} - -void LLFavoritesOrderStorage::cleanup() -{ - // nothing to clean - if (!mIsDirty) return; - - const LLUUID fav_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - gInventory.collectDescendents(fav_id, cats, items, LLInventoryModel::EXCLUDE_TRASH); - - IsNotInFavorites is_not_in_fav(items); - - sort_index_map_t aTempMap; - //copy unremoved values from mSortIndexes to aTempMap - std::remove_copy_if(mSortIndexes.begin(), mSortIndexes.end(), - inserter(aTempMap, aTempMap.begin()), - is_not_in_fav); - - //Swap the contents of mSortIndexes and aTempMap - mSortIndexes.swap(aTempMap); -} - -// See also LLInventorySort where landmarks in the Favorites folder are sorted. -class LLViewerInventoryItemSort -{ -public: - bool operator()(const LLPointer& a, const LLPointer& b) - { - return LLFavoritesOrderStorage::instance().getSortIndex(a->getUUID()) - < LLFavoritesOrderStorage::instance().getSortIndex(b->getUUID()); - } -}; - -void LLFavoritesOrderStorage::saveOrder() -{ - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - LLIsType is_type(LLAssetType::AT_LANDMARK); - LLUUID favorites_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); - gInventory.collectDescendentsIf(favorites_id, cats, items, LLInventoryModel::EXCLUDE_TRASH, is_type); - std::sort(items.begin(), items.end(), LLViewerInventoryItemSort()); - saveItemsOrder(items); -} - -void LLFavoritesOrderStorage::saveItemsOrder( const LLInventoryModel::item_array_t& items ) -{ - - int sortField = 0; - // current order is saved by setting incremental values (1, 2, 3, ...) for the sort field - for (LLInventoryModel::item_array_t::const_iterator i = items.begin(); i != items.end(); ++i) - { - LLViewerInventoryItem* item = *i; - - setSortIndex(item, ++sortField); - - item->setComplete(true); - item->updateServer(false); - - gInventory.updateItem(item); - - // Tell the parent folder to refresh its sort order. - gInventory.addChangedMask(LLInventoryObserver::SORT, item->getParentUUID()); - } - - gInventory.notifyObservers(); -} - - -// * @param source_item_id - LLUUID of the source item to be moved into new position -// * @param target_item_id - LLUUID of the target item before which source item should be placed. -void LLFavoritesOrderStorage::rearrangeFavoriteLandmarks(const LLUUID& source_item_id, const LLUUID& target_item_id) -{ - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - LLIsType is_type(LLAssetType::AT_LANDMARK); - LLUUID favorites_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); - gInventory.collectDescendentsIf(favorites_id, cats, items, LLInventoryModel::EXCLUDE_TRASH, is_type); - - // ensure items are sorted properly before changing order. EXT-3498 - std::sort(items.begin(), items.end(), LLViewerInventoryItemSort()); - - // update order - gInventory.updateItemsOrder(items, source_item_id, target_item_id); - - saveItemsOrder(items); -} - -bool LLFavoritesOrderStorage::saveFavoritesRecord(bool pref_changed) -{ - pref_changed |= mRecreateFavoriteStorage; - mRecreateFavoriteStorage = false; - - // Can get called before inventory is done initializing. - if (!gInventory.isInventoryUsable()) - { - return false; - } - - LLUUID favorite_folder= gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); - if (favorite_folder.isNull()) - { - return false; - } - - LLInventoryModel::item_array_t items; - LLInventoryModel::cat_array_t cats; - - LLIsType is_type(LLAssetType::AT_LANDMARK); - gInventory.collectDescendentsIf(favorite_folder, cats, items, LLInventoryModel::EXCLUDE_TRASH, is_type); - - std::sort(items.begin(), items.end(), LLFavoritesSort()); - bool name_changed = false; - - for (LLInventoryModel::item_array_t::iterator it = items.begin(); it != items.end(); it++) - { - if(mFavoriteNames[(*it)->getUUID()] != ((*it)->getName())) - { - mFavoriteNames[(*it)->getUUID()] = (*it)->getName(); - name_changed = true; - } - } - - for (std::set::iterator it = mMissingSLURLs.begin(); it != mMissingSLURLs.end(); it++) - { - slurls_map_t::iterator slurl_iter = mSLURLs.find(*it); - if (slurl_iter != mSLURLs.end()) - { - pref_changed = true; - break; - } - } - - if((items != mPrevFavorites) || name_changed || pref_changed || gSavedSettings.getBOOL("UpdateRememberPasswordSetting")) - { - std::string filename = getStoredFavoritesFilename(); - if (!filename.empty()) - { - llifstream in_file; - in_file.open(filename.c_str()); - LLSD fav_llsd; - if (in_file.is_open()) - { - LLSDSerialize::fromXML(fav_llsd, in_file); - in_file.close(); - } - else - { - LL_WARNS("FavoritesBar") << "unable to open favorites from '" << filename << "'" << LL_ENDL; - } - - LLSD user_llsd; - S32 fav_iter = 0; - mMissingSLURLs.clear(); - - LLSD save_pass; - save_pass["save_password"] = gSavedSettings.getBOOL("RememberPassword"); - user_llsd[fav_iter] = save_pass; - fav_iter++; - - for (LLInventoryModel::item_array_t::iterator it = items.begin(); it != items.end(); it++) - { - LLSD value; - if (gSavedPerAccountSettings.getBOOL("ShowFavoritesOnLogin")) - { - value["name"] = (*it)->getName(); - value["asset_id"] = (*it)->getAssetUUID(); - value["id"] = (*it)->getUUID(); - slurls_map_t::iterator slurl_iter = mSLURLs.find(value["asset_id"]); - if (slurl_iter != mSLURLs.end()) - { - value["slurl"] = slurl_iter->second; - user_llsd[fav_iter] = value; - } - else - { - getSLURL((*it)->getAssetUUID()); - value["slurl"] = ""; - user_llsd[fav_iter] = value; - mUpdateRequired = true; - mMissingSLURLs.insert((*it)->getAssetUUID()); - } - } - else - { - value["id"] = (*it)->getUUID(); - user_llsd[fav_iter] = value; - } - - fav_iter ++; - } - - LLAvatarName av_name; - LLAvatarNameCache::get( gAgentID, &av_name ); - // Note : use the "John Doe" and not the "john.doe" version of the name - // as we'll compare it with the stored credentials in the login panel. - fav_llsd[av_name.getUserName()] = user_llsd; - llofstream file; - file.open(filename.c_str()); - if ( file.is_open() ) - { - LLSDSerialize::toPrettyXML(fav_llsd, file); - file.close(); - mSaveOnExit = false; - } - else - { - LL_WARNS("FavoritesBar") << "unable to open favorites storage for '" << av_name.getUserName() - << "' at '" << filename << "' " << LL_ENDL; - } - } - mPrevFavorites = items; - } - - return true; - -} - -void LLFavoritesOrderStorage::showFavoritesOnLoginChanged(bool show) -{ - if (show) - { - saveFavoritesRecord(true); - } - else - { - removeFavoritesRecordOfUser(); - } -} - -bool LLFavoritesOrderStorage::isStorageUpdateNeeded() -{ - if (!mRecreateFavoriteStorage) - { - for (LLSD::array_iterator iter = mStorageFavorites.beginArray(); - iter != mStorageFavorites.endArray(); ++iter) - { - if (mFavoriteNames[iter->get("id").asUUID()] != iter->get("name").asString()) - { - mRecreateFavoriteStorage = true; - return true; - } - } - } - return false; -} - -void AddFavoriteLandmarkCallback::fire(const LLUUID& inv_item_id) -{ - if (!mTargetLandmarkId.isNull()) - { - LLFavoritesOrderStorage::instance().rearrangeFavoriteLandmarks(inv_item_id, mTargetLandmarkId); - } -} - -// EOF +/** + * @file llfavoritesbar.cpp + * @brief LLFavoritesBarCtrl class implementation + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llfavoritesbar.h" + +#include "llfloaterreg.h" +#include "llfocusmgr.h" +#include "llinventory.h" +#include "lllandmarkactions.h" +#include "lltoolbarview.h" +#include "lltrans.h" +#include "lluictrlfactory.h" +#include "llmenugl.h" +#include "lltooltip.h" + +#include "llagent.h" +#include "llagentpicksinfo.h" +#include "llavatarnamecache.h" +#include "llclipboard.h" +#include "llinventorybridge.h" +#include "llinventoryfunctions.h" +#include "llfloatersidepanelcontainer.h" +#include "llfloaterworldmap.h" +#include "lllandmarkactions.h" +#include "lllogininstance.h" +#include "llnotificationsutil.h" +#include "lltoggleablemenu.h" +#include "llviewerinventory.h" +#include "llviewermenu.h" +#include "llviewernetwork.h" +#include "lltooldraganddrop.h" +#include "llsdserialize.h" + +static LLDefaultChildRegistry::Register r("favorites_bar"); + +const S32 DROP_DOWN_MENU_WIDTH = 250; +const S32 DROP_DOWN_MENU_TOP_PAD = 13; + +/** + * Helper for LLFavoriteLandmarkButton and LLFavoriteLandmarkMenuItem. + * Performing requests for SLURL for given Landmark ID + */ +class LLLandmarkInfoGetter +{ +public: + LLLandmarkInfoGetter() + : mLandmarkID(LLUUID::null), + mName("(Loading...)"), + mPosX(0), + mPosY(0), + mPosZ(0), + mLoaded(false) + { + mHandle.bind(this); + } + + void setLandmarkID(const LLUUID& id) { mLandmarkID = id; } + const LLUUID& getLandmarkID() const { return mLandmarkID; } + + const std::string& getName() + { + if(!mLoaded) + requestNameAndPos(); + + return mName; + } + + S32 getPosX() + { + if (!mLoaded) + requestNameAndPos(); + return mPosX; + } + + S32 getPosY() + { + if (!mLoaded) + requestNameAndPos(); + return mPosY; + } + + S32 getPosZ() + { + if (!mLoaded) + requestNameAndPos(); + return mPosZ; + } + +private: + /** + * Requests landmark data from server. + */ + void requestNameAndPos() + { + if (mLandmarkID.isNull()) + return; + + LLVector3d g_pos; + if(LLLandmarkActions::getLandmarkGlobalPos(mLandmarkID, g_pos)) + { + LLLandmarkActions::getRegionNameAndCoordsFromPosGlobal(g_pos, + boost::bind(&LLLandmarkInfoGetter::landmarkNameCallback, static_cast >(mHandle), _1, _2, _3, _4)); + } + } + + static void landmarkNameCallback(LLHandle handle, const std::string& name, S32 x, S32 y, S32 z) + { + LLLandmarkInfoGetter* getter = handle.get(); + if (getter) + { + getter->mPosX = x; + getter->mPosY = y; + getter->mPosZ = z; + getter->mName = name; + getter->mLoaded = true; + } + } + + LLUUID mLandmarkID; + std::string mName; + S32 mPosX; + S32 mPosY; + S32 mPosZ; + bool mLoaded; + LLRootHandle mHandle; +}; + +/** + * This class is needed to override LLButton default handleToolTip function and + * show SLURL as button tooltip. + * *NOTE: dzaporozhan: This is a workaround. We could set tooltips for buttons + * in createButtons function but landmark data is not available when Favorites Bar is + * created. Thats why we are requesting landmark data after + */ +class LLFavoriteLandmarkButton : public LLButton +{ +public: + + bool handleToolTip(S32 x, S32 y, MASK mask) + { + std::string region_name = mLandmarkInfoGetter.getName(); + + if (!region_name.empty()) + { + std::string extra_message = llformat("%s (%d, %d, %d)", region_name.c_str(), + mLandmarkInfoGetter.getPosX(), mLandmarkInfoGetter.getPosY(), mLandmarkInfoGetter.getPosZ()); + + LLToolTip::Params params; + params.message = llformat("%s\n%s", getLabelSelected().c_str(), extra_message.c_str()); + params.max_width = 1000; + params.sticky_rect = calcScreenRect(); + + LLToolTipMgr::instance().show(params); + } + return true; + } + + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask) + { + LLFavoritesBarCtrl* fb = dynamic_cast(getParent()); + + if (fb) + { + fb->handleHover(x, y, mask); + } + + return LLButton::handleHover(x, y, mask); + } + + void setLandmarkID(const LLUUID& id){ mLandmarkInfoGetter.setLandmarkID(id); } + const LLUUID& getLandmarkID() const { return mLandmarkInfoGetter.getLandmarkID(); } + + void onMouseEnter(S32 x, S32 y, MASK mask) + { + if (LLToolDragAndDrop::getInstance()->hasMouseCapture()) + { + LLUICtrl::onMouseEnter(x, y, mask); + } + else + { + LLButton::onMouseEnter(x, y, mask); + } + } + +protected: + LLFavoriteLandmarkButton(const LLButton::Params& p) : LLButton(p) {} + friend class LLUICtrlFactory; + +private: + LLLandmarkInfoGetter mLandmarkInfoGetter; +}; + +/** + * This class is needed to override LLMenuItemCallGL default handleToolTip function and + * show SLURL as button tooltip. + * *NOTE: dzaporozhan: This is a workaround. We could set tooltips for buttons + * in showDropDownMenu function but landmark data is not available when Favorites Bar is + * created. Thats why we are requesting landmark data after + */ +class LLFavoriteLandmarkMenuItem : public LLMenuItemCallGL +{ +public: + bool handleToolTip(S32 x, S32 y, MASK mask) + { + std::string region_name = mLandmarkInfoGetter.getName(); + if (!region_name.empty()) + { + LLToolTip::Params params; + params.message = llformat("%s\n%s (%d, %d)", getLabel().c_str(), region_name.c_str(), mLandmarkInfoGetter.getPosX(), mLandmarkInfoGetter.getPosY()); + params.sticky_rect = calcScreenRect(); + LLToolTipMgr::instance().show(params); + } + return true; + } + + const LLUUID& getLandmarkID() const { return mLandmarkInfoGetter.getLandmarkID(); } + void setLandmarkID(const LLUUID& id) { mLandmarkInfoGetter.setLandmarkID(id); } + + virtual bool handleMouseDown(S32 x, S32 y, MASK mask) + { + if (mMouseDownSignal) + (*mMouseDownSignal)(this, x, y, mask); + return LLMenuItemCallGL::handleMouseDown(x, y, mask); + } + + virtual bool handleMouseUp(S32 x, S32 y, MASK mask) + { + if (mMouseUpSignal) + (*mMouseUpSignal)(this, x, y, mask); + return LLMenuItemCallGL::handleMouseUp(x, y, mask); + } + + virtual bool handleHover(S32 x, S32 y, MASK mask) + { + if (fb) + { + fb->handleHover(x, y, mask); + } + + return true; + } + + void initFavoritesBarPointer(LLFavoritesBarCtrl* fb) { this->fb = fb; } + +protected: + + LLFavoriteLandmarkMenuItem(const LLMenuItemCallGL::Params& p) : LLMenuItemCallGL(p) {} + friend class LLUICtrlFactory; + +private: + LLLandmarkInfoGetter mLandmarkInfoGetter; + LLFavoritesBarCtrl* fb; +}; + +/** + * This class was introduced just for fixing the following issue: + * EXT-836 Nav bar: Favorites overflow menu passes left-mouse click through. + * We must explicitly handle drag and drop event by returning true + * because otherwise LLToolDragAndDrop will initiate drag and drop operation + * with the world. + */ +class LLFavoriteLandmarkToggleableMenu : public LLToggleableMenu +{ +public: + // virtual + bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, + void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) override + { + mToolbar->handleDragAndDropToMenu(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); + return true; + } + + // virtual + bool handleHover(S32 x, S32 y, MASK mask) override + { + mIsHovering = true; + LLToggleableMenu::handleHover(x, y, mask); + mIsHovering = false; + return true; + } + + // virtual + void setVisible(bool visible) override + { + // Avoid of hiding the menu during hovering + if (visible || !mIsHovering) + { + LLToggleableMenu::setVisible(visible); + } + } + + void setToolbar(LLFavoritesBarCtrl* toolbar) + { + mToolbar = toolbar; + } + + ~LLFavoriteLandmarkToggleableMenu() + { + // Enable subsequent setVisible(false) + mIsHovering = false; + setVisible(false); + } + +protected: + LLFavoriteLandmarkToggleableMenu(const LLToggleableMenu::Params& p): + LLToggleableMenu(p) + { + } + +private: + LLFavoritesBarCtrl* mToolbar { nullptr }; + bool mIsHovering { false }; + + friend class LLUICtrlFactory; +}; + +/** + * This class is needed to update an item being copied to the favorites folder + * with a sort field value (required to save favorites bar's tabs order). + * See method handleNewFavoriteDragAndDrop for more details on how this class is used. + */ +class LLItemCopiedCallback : public LLInventoryCallback +{ +public: + LLItemCopiedCallback(S32 sortField): mSortField(sortField) {} + + virtual void fire(const LLUUID& inv_item) + { + LLViewerInventoryItem* item = gInventory.getItem(inv_item); + + if (item) + { + LLFavoritesBarCtrl::sWaitingForCallabck = 0.f; + LLFavoritesOrderStorage::instance().setSortIndex(item, mSortField); + + item->setComplete(true); + item->updateServer(false); + + gInventory.updateItem(item); + gInventory.notifyObservers(); + LLFavoritesOrderStorage::instance().saveOrder(); + } + + LLView::getWindow()->setCursor(UI_CURSOR_ARROW); + } + +private: + S32 mSortField; +}; + +// updateButtons's helper +struct LLFavoritesSort +{ + // Sorting by creation date and name + // TODO - made it customizible using gSavedSettings + bool operator()(const LLViewerInventoryItem* const& a, const LLViewerInventoryItem* const& b) + { + S32 sortField1 = LLFavoritesOrderStorage::instance().getSortIndex(a->getUUID()); + S32 sortField2 = LLFavoritesOrderStorage::instance().getSortIndex(b->getUUID()); + + if (!(sortField1 < 0 && sortField2 < 0)) + { + return sortField2 > sortField1; + } + + time_t first_create = a->getCreationDate(); + time_t second_create = b->getCreationDate(); + if (first_create == second_create) + { + return (LLStringUtil::compareDict(a->getName(), b->getName()) < 0); + } + else + { + return (first_create > second_create); + } + } +}; + + +F64 LLFavoritesBarCtrl::sWaitingForCallabck = 0.f; + +LLFavoritesBarCtrl::Params::Params() +: image_drag_indication("image_drag_indication"), + more_button("more_button"), + label("label") +{ +} + +LLFavoritesBarCtrl::LLFavoritesBarCtrl(const LLFavoritesBarCtrl::Params& p) +: LLUICtrl(p), + mFont(p.font.isProvided() ? p.font() : LLFontGL::getFontSansSerifSmall()), + mOverflowMenuHandle(), + mContextMenuHandle(), + mImageDragIndication(p.image_drag_indication), + mShowDragMarker(false), + mLandingTab(NULL), + mLastTab(NULL), + mItemsListDirty(false), + mUpdateDropDownItems(true), + mRestoreOverflowMenu(false), + mDragToOverflowMenu(false), + mGetPrevItems(true), + mMouseX(0), + mMouseY(0), + mItemsChangedTimer() +{ + // Register callback for menus with current registrar (will be parent panel's registrar) + LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Favorites.DoToSelected", + boost::bind(&LLFavoritesBarCtrl::doToSelected, this, _2)); + + // Add this if we need to selectively enable items + LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Favorites.EnableSelected", + boost::bind(&LLFavoritesBarCtrl::enableSelected, this, _2)); + + gInventory.addObserver(this); + + //make chevron button + LLTextBox::Params more_button_params(p.more_button); + mMoreTextBox = LLUICtrlFactory::create (more_button_params); + mMoreTextBox->setClickedCallback(boost::bind(&LLFavoritesBarCtrl::onMoreTextBoxClicked, this)); + addChild(mMoreTextBox); + LLRect rect = mMoreTextBox->getRect(); + mMoreTextBox->setRect(LLRect(rect.mLeft - rect.getWidth(), rect.mTop, rect.mRight, rect.mBottom)); + + mDropDownItemsCount = 0; + + LLTextBox::Params label_param(p.label); + mBarLabel = LLUICtrlFactory::create (label_param); + addChild(mBarLabel); +} + +LLFavoritesBarCtrl::~LLFavoritesBarCtrl() +{ + gInventory.removeObserver(this); + + if (mOverflowMenuHandle.get()) + mOverflowMenuHandle.get()->die(); + if (mContextMenuHandle.get()) + mContextMenuHandle.get()->die(); +} + +bool LLFavoritesBarCtrl::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) +{ + *accept = ACCEPT_NO; + + LLToolDragAndDrop::ESource source = LLToolDragAndDrop::getInstance()->getSource(); + if (LLToolDragAndDrop::SOURCE_AGENT != source && LLToolDragAndDrop::SOURCE_LIBRARY != source) return false; + + switch (cargo_type) + { + + case DAD_LANDMARK: + { + /* + * add a callback to the end drag event. + * the callback will disconnet itself immediately after execution + * this is done because LLToolDragAndDrop is a common tool so it shouldn't + * be overloaded with redundant callbacks. + */ + if (!mEndDragConnection.connected()) + { + mEndDragConnection = LLToolDragAndDrop::getInstance()->setEndDragCallback(boost::bind(&LLFavoritesBarCtrl::onEndDrag, this)); + } + + // Copy the item into the favorites folder (if it's not already there). + LLInventoryItem *item = (LLInventoryItem *)cargo_data; + + if (mDragToOverflowMenu) + { + LLView* overflow_menu = mOverflowMenuHandle.get(); + if (overflow_menu && !overflow_menu->isDead() && overflow_menu->getVisible()) + { + overflow_menu->handleHover(x, y, mask); + } + } + else // Drag to the toolbar itself + { + // Drag to a landmark button? + if (LLFavoriteLandmarkButton* dest = dynamic_cast(findChildByLocalCoords(x, y))) + { + setLandingTab(dest); + } + else + { + // Drag to the "More" button? + if (mMoreTextBox && mMoreTextBox->getVisible() && mMoreTextBox->getRect().pointInRect(x, y)) + { + LLView* overflow_menu = mOverflowMenuHandle.get(); + if (!overflow_menu || overflow_menu->isDead() || !overflow_menu->getVisible()) + { + showDropDownMenu(); + } + } + + // Drag to the right of the last landmark button? + if (mLastTab && (x >= mLastTab->getRect().mRight)) + { + /* + * the condition dest == NULL can be satisfied not only in the case + * of dragging to the right from the last tab of the favbar. there is a + * small gap between each tab. if the user drags something exactly there + * then mLandingTab will be set to NULL and the dragged item will be pushed + * to the end of the favorites bar. this is incorrect behavior. that's why + * we need an additional check which excludes the case described previously + * making sure that the mouse pointer is beyond the last tab. + */ + setLandingTab(NULL); + } + } + } + + // Check whether we are dragging an existing item from the favorites bar + bool existing_item = false; + if (item && mDragItemId == item->getUUID()) + { + // There is a chance of mDragItemId being obsolete + // ex: can happen if something interrupts viewer, which + // results in viewer not geting a 'mouse up' signal + for (LLInventoryModel::item_array_t::iterator i = mItems.begin(); i != mItems.end(); ++i) + { + if ((*i)->getUUID() == mDragItemId) + { + existing_item = true; + break; + } + } + } + + if (existing_item) + { + *accept = ACCEPT_YES_SINGLE; + + showDragMarker(true); + + if (drop) + { + handleExistingFavoriteDragAndDrop(x, y); + } + } + else + { + const LLUUID favorites_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); + if (item->getParentUUID() == favorites_id) + { + LL_WARNS("FavoritesBar") << "Attemt to copy a favorite item into the same folder." << LL_ENDL; + break; + } + + *accept = ACCEPT_YES_COPY_MULTI; + + showDragMarker(true); + + if (drop) + { + if (mItems.empty()) + { + setLandingTab(NULL); + mLastTab = NULL; + } + handleNewFavoriteDragAndDrop(item, favorites_id, x, y); + } + } + } + break; + default: + break; + } + + return true; +} + +bool LLFavoritesBarCtrl::handleDragAndDropToMenu(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) +{ + mDragToOverflowMenu = true; + bool handled = handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); + mDragToOverflowMenu = false; + return handled; +} + +void LLFavoritesBarCtrl::handleExistingFavoriteDragAndDrop(S32 x, S32 y) +{ + if (LL_UNLIKELY(mItems.empty())) + return; + + LLUUID target_id; + bool insert_before = false; + if (!findDragAndDropTarget(target_id, insert_before, x, y)) + return; + + // There is no need to handle if an item was dragged onto itself + if (target_id == mDragItemId) + return; + + // Move the dragged item to the right place in the array + LLInventoryModel::updateItemsOrder(mItems, mDragItemId, target_id, insert_before); + LLFavoritesOrderStorage::instance().saveItemsOrder(mItems); + + LLView* menu = mOverflowMenuHandle.get(); + if (menu && !menu->isDead() && menu->getVisible()) + { + updateOverflowMenuItems(); + positionAndShowOverflowMenu(); + } +} + +void LLFavoritesBarCtrl::handleNewFavoriteDragAndDrop(LLInventoryItem *item, const LLUUID& favorites_id, S32 x, S32 y) +{ + // Identify the button hovered and the side to drop + LLUUID target_id; + bool insert_before = false; + // There is no need to handle if an item was dragged onto itself + if (findDragAndDropTarget(target_id, insert_before, x, y) && (target_id == mDragItemId)) + return; + + LLPointer viewer_item = new LLViewerInventoryItem(item); + + // Insert the dragged item to the right place + if (target_id.notNull()) + { + insertItem(mItems, target_id, viewer_item, insert_before); + } + else + { + // This can happen when the item list is empty + mItems.push_back(viewer_item); + } + + int sortField = 0; + LLPointer cb; + + const F64 CALLBACK_WAIT_TIME = 30.f; + sWaitingForCallabck = LLTimer::getTotalSeconds() + CALLBACK_WAIT_TIME; + + // current order is saved by setting incremental values (1, 2, 3, ...) for the sort field + for (LLInventoryModel::item_array_t::iterator i = mItems.begin(); i != mItems.end(); ++i) + { + LLViewerInventoryItem* currItem = *i; + + if (currItem->getUUID() == item->getUUID()) + { + cb = new LLItemCopiedCallback(++sortField); + } + else + { + LLFavoritesOrderStorage::instance().setSortIndex(currItem, ++sortField); + + currItem->setComplete(true); + currItem->updateServer(false); + + gInventory.updateItem(currItem); + } + } + + LLToolDragAndDrop* tool_dad = LLToolDragAndDrop::getInstance(); + if (tool_dad->getSource() == LLToolDragAndDrop::SOURCE_NOTECARD) + { + viewer_item->setType(LLAssetType::AT_LANDMARK); + copy_inventory_from_notecard(favorites_id, + tool_dad->getObjectID(), + tool_dad->getSourceID(), + viewer_item.get(), + gInventoryCallbacks.registerCB(cb)); + } + else + { + copy_inventory_item( + gAgent.getID(), + item->getPermissions().getOwner(), + item->getUUID(), + favorites_id, + std::string(), + cb); + } + + // [MAINT-2386] Ensure the favorite button has been created and is valid. + // This also ensures that mLastTab will be valid when dropping multiple + // landmarks to an empty favorites bar. + updateButtons(); + + LLView* overflow_menu = mOverflowMenuHandle.get(); + if (overflow_menu && !overflow_menu->isDead() && overflow_menu->getVisible()) + { + updateOverflowMenuItems(); + positionAndShowOverflowMenu(); + } + + LL_INFOS("FavoritesBar") << "Copied inventory item #" << item->getUUID() << " to favorites." << LL_ENDL; +} + +bool LLFavoritesBarCtrl::findDragAndDropTarget(LLUUID& target_id, bool& insert_before, S32 x, S32 y) +{ + if (mItems.empty()) + return false; + + if (mDragToOverflowMenu) + { + LLView* overflow_menu = mOverflowMenuHandle.get(); + if (LL_UNLIKELY(!overflow_menu || overflow_menu->isDead() || !overflow_menu->getVisible())) + return false; + + // Identify the menu item hovered and the side to drop + LLFavoriteLandmarkMenuItem* target_item = dynamic_cast(overflow_menu->childFromPoint(x, y)); + if (target_item) + { + insert_before = true; + } + else + { + // Choose the bottom landmark menu item + auto begin = overflow_menu->getChildList()->begin(); + auto end = overflow_menu->getChildList()->end(); + auto check = [](const LLView* child) -> bool + { + return dynamic_cast(child); + }; + // Menu items are placed in the backward order, so the bottom goes first + auto it = std::find_if(begin, end, check); + if (LL_UNLIKELY(it == end)) + return false; + target_item = (LLFavoriteLandmarkMenuItem*)*it; + insert_before = false; + } + target_id = target_item->getLandmarkID(); + } + else + { + // Identify the button hovered and the side to drop + LLFavoriteLandmarkButton* hovered_button = dynamic_cast(mLandingTab); + if (hovered_button) + { + insert_before = true; + } + else + { + // Choose the right landmark button + hovered_button = dynamic_cast(mLastTab); + if (LL_UNLIKELY(!hovered_button)) + return false; + + insert_before = false; + } + target_id = hovered_button->getLandmarkID(); + } + + return true; +} + +//virtual +void LLFavoritesBarCtrl::changed(U32 mask) +{ + if (mFavoriteFolderId.isNull()) + { + mFavoriteFolderId = gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); + + if (mFavoriteFolderId.notNull()) + { + gInventory.fetchDescendentsOf(mFavoriteFolderId); + } + } + else + { + LLInventoryModel::item_array_t items; + LLInventoryModel::cat_array_t cats; + LLIsType is_type(LLAssetType::AT_LANDMARK); + gInventory.collectDescendentsIf(mFavoriteFolderId, cats, items, LLInventoryModel::EXCLUDE_TRASH, is_type); + + for (LLInventoryModel::item_array_t::iterator i = items.begin(); i != items.end(); ++i) + { + LLFavoritesOrderStorage::instance().getSLURL((*i)->getAssetUUID()); + } + + if (sWaitingForCallabck < LLTimer::getTotalSeconds()) + { + updateButtons(); + if (!mItemsChangedTimer.getStarted()) + { + mItemsChangedTimer.start(); + } + else + { + mItemsChangedTimer.reset(); + } + } + else + { + mItemsListDirty = true; + } + } +} + +//virtual +void LLFavoritesBarCtrl::reshape(S32 width, S32 height, bool called_from_parent) +{ + S32 delta_width = width - getRect().getWidth(); + S32 delta_height = height - getRect().getHeight(); + + bool force_update = delta_width || delta_height || sForceReshape; + LLUICtrl::reshape(width, height, called_from_parent); + updateButtons(force_update); +} + +void LLFavoritesBarCtrl::draw() +{ + LLUICtrl::draw(); + + if (mShowDragMarker) + { + S32 w = mImageDragIndication->getWidth(); + S32 h = mImageDragIndication->getHeight(); + + if (mLandingTab) + { + // mouse pointer hovers over an existing tab + LLRect rect = mLandingTab->getRect(); + mImageDragIndication->draw(rect.mLeft, rect.getHeight(), w, h); + } + else if (mLastTab) + { + // mouse pointer hovers over the favbar empty space (right to the last tab) + LLRect rect = mLastTab->getRect(); + mImageDragIndication->draw(rect.mRight, rect.getHeight(), w, h); + } + // Once drawn, mark this false so we won't draw it again (unless we hit the favorite bar again) + mShowDragMarker = false; + } + if (mItemsChangedTimer.getStarted()) + { + if (mItemsChangedTimer.getElapsedTimeF32() > 1.f) + { + LLFavoritesOrderStorage::instance().saveFavoritesRecord(); + mItemsChangedTimer.stop(); + } + } + + if(!mItemsChangedTimer.getStarted() && LLFavoritesOrderStorage::instance().mUpdateRequired) + { + LLFavoritesOrderStorage::instance().mUpdateRequired = false; + mItemsChangedTimer.start(); + } + + if (mItemsListDirty && sWaitingForCallabck < LLTimer::getTotalSeconds()) + { + updateButtons(); + if (!mItemsChangedTimer.getStarted()) + { + mItemsChangedTimer.start(); + } + else + { + mItemsChangedTimer.reset(); + } + } +} + +const LLButton::Params& LLFavoritesBarCtrl::getButtonParams() +{ + static LLButton::Params button_params; + static bool params_initialized = false; + + if (!params_initialized) + { + LLXMLNodePtr button_xml_node; + if(LLUICtrlFactory::getLayeredXMLNode("favorites_bar_button.xml", button_xml_node)) + { + LLXUIParser parser; + parser.readXUI(button_xml_node, button_params, "favorites_bar_button.xml"); + } + params_initialized = true; + } + + return button_params; +} + +void LLFavoritesBarCtrl::updateButtons(bool force_update) +{ + if (LLApp::isExiting()) + { + return; + } + + mItemsListDirty = false; + mItems.clear(); + + if (!collectFavoriteItems(mItems)) + { + return; + } + + if(mGetPrevItems && gInventory.isCategoryComplete(mFavoriteFolderId)) + { + for (LLInventoryModel::item_array_t::iterator it = mItems.begin(); it != mItems.end(); it++) + { + LLFavoritesOrderStorage::instance().mFavoriteNames[(*it)->getUUID()]= (*it)->getName(); + } + LLFavoritesOrderStorage::instance().mPrevFavorites = mItems; + mGetPrevItems = false; + + if (LLFavoritesOrderStorage::instance().isStorageUpdateNeeded()) + { + if (!mItemsChangedTimer.getStarted()) + { + mItemsChangedTimer.start(); + } + } + } + + const LLButton::Params& button_params = getButtonParams(); + + if(mItems.empty()) + { + mBarLabel->setVisible(true); + mLastTab = NULL; + } + else + { + mBarLabel->setVisible(false); + } + const child_list_t* childs = getChildList(); + child_list_const_iter_t child_it = childs->begin(); + int first_changed_item_index = 0; + if (!force_update) + { + //lets find first changed button + while (child_it != childs->end() && first_changed_item_index < mItems.size()) + { + LLFavoriteLandmarkButton* button = dynamic_cast (*child_it); + if (button) + { + const LLViewerInventoryItem *item = mItems[first_changed_item_index].get(); + if (item) + { + // an child's order and mItems should be same + if (button->getLandmarkID() != item->getUUID() // sort order has been changed + || button->getLabelSelected() != item->getName()) // favorite's name has been changed + { + break; + } + } + first_changed_item_index++; + } + child_it++; + } + } + // now first_changed_item_index should contains a number of button that need to change + + if (first_changed_item_index <= mItems.size()) + { + // Rebuild the buttons only + // child_list_t is a linked list, so safe to erase from the middle if we pre-increment the iterator + + while (child_it != childs->end()) + { + //lets remove other landmarks button and rebuild it + child_list_const_iter_t cur_it = child_it++; + LLFavoriteLandmarkButton* button = + dynamic_cast (*cur_it); + if (button) + { + if (mLastTab == button) + { + mLastTab = NULL; + } + removeChild(button); + delete button; + } + } + // we have to remove ChevronButton to make sure that the last item will be LandmarkButton to get the right aligning + // keep in mind that we are cutting all buttons in space between the last visible child of favbar and ChevronButton + if (mMoreTextBox->getParent() == this) + { + removeChild(mMoreTextBox); + } + int last_right_edge = 0; + //calculate new buttons offset + if (getChildList()->size() > 0) + { + //find last visible child to get the rightest button offset + child_list_const_reverse_iter_t last_visible_it = + std::find_if( + childs->rbegin(), childs->rend(), + [](const child_list_t::value_type& child) + { return child->getVisible(); }); + if(last_visible_it != childs->rend()) + { + last_right_edge = (*last_visible_it)->getRect().mRight; + } + } + //last_right_edge is saving coordinates + LLButton* last_new_button = NULL; + int j = first_changed_item_index; + for (; j < mItems.size(); j++) + { + last_new_button = createButton(mItems[j], button_params, last_right_edge); + if (!last_new_button) + { + break; + } + sendChildToBack(last_new_button); + last_right_edge = last_new_button->getRect().mRight; + + mLastTab = last_new_button; + } + if (!mLastTab && mItems.size() > 0) + { + // mMoreTextBox was removed, so LLFavoriteLandmarkButtons + // should be the only ones in the list + mLastTab = dynamic_cast(childs->back()); + } + + mFirstDropDownItem = j; + // Chevron button + if (mFirstDropDownItem < mItems.size()) + { + // if updateButton had been called it means: + // or there are some new favorites, or width had been changed + // so if we need to display chevron button, we must update dropdown items too. + mUpdateDropDownItems = true; + S32 buttonHGap = button_params.rect.left; // default value + // Chevron button should stay right aligned + LLRect rect(mMoreTextBox->getRect()); + rect.translate(getRect().mRight - rect.mRight - buttonHGap, 0); + + addChild(mMoreTextBox); + mMoreTextBox->setRect(rect); + mMoreTextBox->setVisible(true); + } + // Update overflow menu + LLToggleableMenu* overflow_menu = static_cast (mOverflowMenuHandle.get()); + if (overflow_menu && overflow_menu->getVisible() && (overflow_menu->getItemCount() != mDropDownItemsCount)) + { + overflow_menu->setVisible(false); + if (mUpdateDropDownItems) + { + showDropDownMenu(); + } + } + } + else + { + mUpdateDropDownItems = false; + } + +} + +LLButton* LLFavoritesBarCtrl::createButton(const LLPointer item, const LLButton::Params& button_params, S32 x_offset) +{ + S32 def_button_width = button_params.rect.width; + S32 button_x_delta = button_params.rect.left; // default value + S32 curr_x = x_offset; + + /** + * WORKAROUND: + * There are some problem with displaying of fonts in buttons. + * Empty space or ellipsis might be displayed instead of last symbols, even though the width of the button is enough. + * The problem disappears if we pad the button with 20 pixels. + */ + int required_width = mFont->getWidth(item->getName()) + 20; + int width = required_width > def_button_width? def_button_width : required_width; + LLFavoriteLandmarkButton* fav_btn = NULL; + + // do we have a place for next button + double buttonHGap + mMoreTextBox ? + if(curr_x + width + 2*button_x_delta + mMoreTextBox->getRect().getWidth() > getRect().mRight ) + { + return NULL; + } + LLButton::Params fav_btn_params(button_params); + fav_btn = LLUICtrlFactory::create(fav_btn_params); + if (NULL == fav_btn) + { + LL_WARNS("FavoritesBar") << "Unable to create LLFavoriteLandmarkButton widget: " << item->getName() << LL_ENDL; + return NULL; + } + + addChild(fav_btn); + + LLRect butt_rect (fav_btn->getRect()); + fav_btn->setLandmarkID(item->getUUID()); + butt_rect.setOriginAndSize(curr_x + button_x_delta, fav_btn->getRect().mBottom, width, fav_btn->getRect().getHeight()); + + fav_btn->setRect(butt_rect); + // change only left and save bottom + fav_btn->setFont(mFont); + fav_btn->setLabel(item->getName()); + fav_btn->setToolTip(item->getName()); + fav_btn->setCommitCallback(boost::bind(&LLFavoritesBarCtrl::onButtonClick, this, item->getUUID())); + fav_btn->setRightMouseDownCallback(boost::bind(&LLFavoritesBarCtrl::onButtonRightClick, this, item->getUUID(), _1, _2, _3,_4 )); + + fav_btn->LLUICtrl::setMouseDownCallback(boost::bind(&LLFavoritesBarCtrl::onButtonMouseDown, this, item->getUUID(), _1, _2, _3, _4)); + fav_btn->LLUICtrl::setMouseUpCallback(boost::bind(&LLFavoritesBarCtrl::onButtonMouseUp, this, item->getUUID(), _1, _2, _3, _4)); + + return fav_btn; +} + + +bool LLFavoritesBarCtrl::postBuild() +{ + // make the popup menu available + LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile("menu_favorites.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + if (!menu) + { + menu = LLUICtrlFactory::getDefaultWidget("inventory_menu"); + } + menu->setBackgroundColor(LLUIColorTable::instance().getColor("MenuPopupBgColor")); + mContextMenuHandle = menu->getHandle(); + + return true; +} + +bool LLFavoritesBarCtrl::collectFavoriteItems(LLInventoryModel::item_array_t &items) +{ + + if (mFavoriteFolderId.isNull()) + return false; + + + LLInventoryModel::cat_array_t cats; + + LLIsType is_type(LLAssetType::AT_LANDMARK); + gInventory.collectDescendentsIf(mFavoriteFolderId, cats, items, LLInventoryModel::EXCLUDE_TRASH, is_type); + + std::sort(items.begin(), items.end(), LLFavoritesSort()); + + if (needToSaveItemsOrder(items)) + { + S32 sortField = 0; + for (LLInventoryModel::item_array_t::iterator i = items.begin(); i != items.end(); ++i) + { + LLFavoritesOrderStorage::instance().setSortIndex((*i), ++sortField); + } + LLFavoritesOrderStorage::instance().mSaveOnExit = true; + } + + return true; +} + +void LLFavoritesBarCtrl::onMoreTextBoxClicked() +{ + LLUI::getInstance()->getMousePositionScreen(&mMouseX, &mMouseY); + showDropDownMenu(); +} + +void LLFavoritesBarCtrl::showDropDownMenu() +{ + if (mOverflowMenuHandle.isDead()) + { + createOverflowMenu(); + } + + LLToggleableMenu* menu = (LLToggleableMenu*)mOverflowMenuHandle.get(); + if (menu && menu->toggleVisibility()) + { + if (mUpdateDropDownItems) + { + updateOverflowMenuItems(); + } + else + { + menu->buildDrawLabels(); + } + + menu->updateParent(LLMenuGL::sMenuContainer); + menu->setButtonRect(mMoreTextBox->getRect(), this); + positionAndShowOverflowMenu(); + } +} + +void LLFavoritesBarCtrl::createOverflowMenu() +{ + LLToggleableMenu::Params menu_p; + menu_p.name("favorites menu"); + menu_p.can_tear_off(false); + menu_p.visible(false); + menu_p.scrollable(true); + menu_p.max_scrollable_items = 10; + menu_p.preferred_width = DROP_DOWN_MENU_WIDTH; + + LLFavoriteLandmarkToggleableMenu* menu = LLUICtrlFactory::create(menu_p); + menu->setToolbar(this); + mOverflowMenuHandle = menu->getHandle(); +} + +void LLFavoritesBarCtrl::updateOverflowMenuItems() +{ + LLToggleableMenu* menu = (LLToggleableMenu*)mOverflowMenuHandle.get(); + menu->empty(); + + U32 widest_item = 0; + + for (S32 i = mFirstDropDownItem; i < mItems.size(); i++) + { + LLViewerInventoryItem* item = mItems.at(i); + const std::string& item_name = item->getName(); + + LLFavoriteLandmarkMenuItem::Params item_params; + item_params.name(item_name); + item_params.label(item_name); + item_params.on_click.function(boost::bind(&LLFavoritesBarCtrl::onButtonClick, this, item->getUUID())); + + LLFavoriteLandmarkMenuItem *menu_item = LLUICtrlFactory::create(item_params); + menu_item->initFavoritesBarPointer(this); + menu_item->setRightMouseDownCallback(boost::bind(&LLFavoritesBarCtrl::onButtonRightClick, this, item->getUUID(), _1, _2, _3, _4)); + menu_item->LLUICtrl::setMouseDownCallback(boost::bind(&LLFavoritesBarCtrl::onButtonMouseDown, this, item->getUUID(), _1, _2, _3, _4)); + menu_item->LLUICtrl::setMouseUpCallback(boost::bind(&LLFavoritesBarCtrl::onButtonMouseUp, this, item->getUUID(), _1, _2, _3, _4)); + menu_item->setLandmarkID(item->getUUID()); + + fitLabelWidth(menu_item); + + widest_item = llmax(widest_item, menu_item->getNominalWidth()); + + menu->addChild(menu_item); + } + + menu->buildDrawLabels(); + mDropDownItemsCount = menu->getItemCount(); + addOpenLandmarksMenuItem(menu); + mUpdateDropDownItems = false; +} + +void LLFavoritesBarCtrl::fitLabelWidth(LLMenuItemCallGL* menu_item) +{ + U32 max_width = llmin(DROP_DOWN_MENU_WIDTH, getRect().getWidth()); + std::string item_name = menu_item->getName(); + + // Check whether item name wider than menu + if (menu_item->getNominalWidth() > max_width) + { + S32 chars_total = item_name.length(); + S32 chars_fitted = 1; + menu_item->setLabel(LLStringExplicit("")); + S32 label_space = max_width - menu_item->getFont()->getWidth("...") - + menu_item->getNominalWidth();// This returns width of menu item with empty label (pad pixels) + + while (chars_fitted < chars_total + && menu_item->getFont()->getWidth(item_name, 0, chars_fitted) < label_space) + { + chars_fitted++; + } + chars_fitted--; // Rolling back one char, that doesn't fit + + menu_item->setLabel(item_name.substr(0, chars_fitted) + "..."); + } +} + +void LLFavoritesBarCtrl::addOpenLandmarksMenuItem(LLToggleableMenu* menu) +{ + std::string label_untrans = "Open landmarks"; + std::string label_transl; + bool translated = LLTrans::findString(label_transl, label_untrans); + + LLMenuItemCallGL::Params item_params; + item_params.name("open_my_landmarks"); + item_params.label(translated ? label_transl: label_untrans); + LLSD key; + key["type"] = "open_landmark_tab"; + item_params.on_click.function(boost::bind(&LLFloaterSidePanelContainer::showPanel, "places", key)); + LLMenuItemCallGL* menu_item = LLUICtrlFactory::create(item_params); + + fitLabelWidth(menu_item); + + LLMenuItemSeparatorGL::Params sep_params; + sep_params.enabled_color=LLUIColorTable::instance().getColor("MenuItemEnabledColor"); + sep_params.disabled_color=LLUIColorTable::instance().getColor("MenuItemDisabledColor"); + sep_params.highlight_bg_color=LLUIColorTable::instance().getColor("MenuItemHighlightBgColor"); + sep_params.highlight_fg_color=LLUIColorTable::instance().getColor("MenuItemHighlightFgColor"); + LLMenuItemSeparatorGL* separator = LLUICtrlFactory::create(sep_params); + + menu->addChild(separator); + menu->addChild(menu_item); +} + +void LLFavoritesBarCtrl::positionAndShowOverflowMenu() +{ + LLToggleableMenu* menu = (LLToggleableMenu*)mOverflowMenuHandle.get(); + U32 max_width = llmin(DROP_DOWN_MENU_WIDTH, getRect().getWidth()); + + S32 menu_x = getRect().getWidth() - max_width; + S32 menu_y = getParent()->getRect().mBottom - DROP_DOWN_MENU_TOP_PAD; + + // the menu should be offset of the right edge of the window + // so it's no covered by buttons in the right-side toolbar. + LLToolBar* right_toolbar = gToolBarView->getChild("toolbar_right"); + if (right_toolbar && right_toolbar->hasButtons()) + { + S32 toolbar_top = 0; + + if (LLView* top_border_panel = right_toolbar->getChild("button_panel")) + { + toolbar_top = top_border_panel->calcScreenRect().mTop; + } + + // Calculating the bottom (in screen coord) of the drop down menu + S32 menu_top = getParent()->getRect().mBottom - DROP_DOWN_MENU_TOP_PAD; + S32 menu_bottom = menu_top - menu->getRect().getHeight(); + S32 menu_bottom_screen = 0; + + localPointToScreen(0, menu_bottom, &menu_top, &menu_bottom_screen); + + if (menu_bottom_screen < toolbar_top) + { + menu_x -= right_toolbar->getRect().getWidth(); + } + } + + LLMenuGL::showPopup(this, menu, menu_x, menu_y, mMouseX, mMouseY); +} + +void LLFavoritesBarCtrl::onButtonClick(LLUUID item_id) +{ + // We only have one Inventory, gInventory. Some day this should be better abstracted. + LLInvFVBridgeAction::doAction(item_id,&gInventory); +} + +void LLFavoritesBarCtrl::onButtonRightClick( LLUUID item_id,LLView* fav_button,S32 x,S32 y,MASK mask) +{ + mSelectedItemID = item_id; + + LLMenuGL* menu = (LLMenuGL*)mContextMenuHandle.get(); + if (!menu) + { + return; + } + + // Remember that the context menu was shown simultaneously with the overflow menu, + // so that we can restore the overflow menu when user clicks a context menu item + // (which hides the overflow menu). + { + LLView* overflow_menu = mOverflowMenuHandle.get(); + mRestoreOverflowMenu = overflow_menu && overflow_menu->getVisible(); + } + + // Release mouse capture so hover events go to the popup menu + // because this is happening during a mouse down. + gFocusMgr.setMouseCapture(NULL); + + menu->updateParent(LLMenuGL::sMenuContainer); + LLMenuGL::showPopup(fav_button, menu, x, y); +} + +bool LLFavoritesBarCtrl::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = childrenHandleRightMouseDown( x, y, mask) != NULL; + if(!handled && !gMenuHolder->hasVisibleMenu()) + { + show_navbar_context_menu(this,x,y); + handled = true; + } + + return handled; +} +void copy_slurl_to_clipboard_cb(std::string& slurl) +{ + LLClipboard::instance().copyToClipboard(utf8str_to_wstring(slurl),0,slurl.size()); + + LLSD args; + args["SLURL"] = slurl; + LLNotificationsUtil::add("CopySLURL", args); +} + + +bool LLFavoritesBarCtrl::enableSelected(const LLSD& userdata) +{ + std::string param = userdata.asString(); + + if (param == std::string("can_paste")) + { + return isClipboardPasteable(); + } + else if (param == "create_pick") + { + return !LLAgentPicksInfo::getInstance()->isPickLimitReached(); + } + + return false; +} + +void LLFavoritesBarCtrl::doToSelected(const LLSD& userdata) +{ + std::string action = userdata.asString(); + LL_INFOS("FavoritesBar") << "Action = " << action << " Item = " << mSelectedItemID.asString() << LL_ENDL; + + LLViewerInventoryItem* item = gInventory.getItem(mSelectedItemID); + if (!item) + return; + + if (action == "open") + { + onButtonClick(item->getUUID()); + } + else if (action == "about") + { + LLSD key; + key["type"] = "landmark"; + key["id"] = mSelectedItemID; + + LLFloaterSidePanelContainer::showPanel("places", key); + } + else if (action == "copy_slurl") + { + LLVector3d posGlobal; + LLLandmarkActions::getLandmarkGlobalPos(mSelectedItemID, posGlobal); + + if (!posGlobal.isExactlyZero()) + { + LLLandmarkActions::getSLURLfromPosGlobal(posGlobal, copy_slurl_to_clipboard_cb); + } + } + else if (action == "show_on_map") + { + LLFloaterWorldMap* worldmap_instance = LLFloaterWorldMap::getInstance(); + + LLVector3d posGlobal; + LLLandmarkActions::getLandmarkGlobalPos(mSelectedItemID, posGlobal); + + if (!posGlobal.isExactlyZero() && worldmap_instance) + { + worldmap_instance->trackLocation(posGlobal); + LLFloaterReg::showInstance("world_map", "center"); + } + } + else if (action == "create_pick") + { + LLSD args; + args["type"] = "create_pick"; + args["item_id"] = item->getUUID(); + LLFloaterSidePanelContainer::showPanel("places", args); + } + else if (action == "cut") + { + } + else if (action == "copy") + { + LLClipboard::instance().copyToClipboard(mSelectedItemID, LLAssetType::AT_LANDMARK); + } + else if (action == "paste") + { + pasteFromClipboard(); + } + else if (action == "delete") + { + gInventory.removeItem(mSelectedItemID); + } + else if (action == "rename") + { + LLSD args; + args["NAME"] = item->getName(); + + LLSD payload; + payload["id"] = mSelectedItemID; + + LLNotificationsUtil::add("RenameLandmark", args, payload, boost::bind(onRenameCommit, _1, _2)); + } + else if (action == "move_to_landmarks") + { + change_item_parent(mSelectedItemID, gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK)); + } + + // Pop-up the overflow menu again (it gets hidden whenever the user clicks a context menu item). + // See EXT-4217 and STORM-207. + LLToggleableMenu* menu = (LLToggleableMenu*) mOverflowMenuHandle.get(); + if (mRestoreOverflowMenu && menu && !menu->getVisible()) + { + menu->resetScrollPositionOnShow(false); + showDropDownMenu(); + menu->resetScrollPositionOnShow(true); + } +} + +bool LLFavoritesBarCtrl::onRenameCommit(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (0 == option) + { + LLUUID id = notification["payload"]["id"].asUUID(); + LLInventoryItem *item = gInventory.getItem(id); + std::string landmark_name = response["new_name"].asString(); + LLStringUtil::trim(landmark_name); + + if (!landmark_name.empty() && item && item->getName() != landmark_name) + { + LLPointer new_item = new LLViewerInventoryItem(item); + new_item->rename(landmark_name); + new_item->updateServer(false); + gInventory.updateItem(new_item); + } + } + + return false; +} + +bool LLFavoritesBarCtrl::isClipboardPasteable() const +{ + if (!LLClipboard::instance().hasContents()) + { + return false; + } + + std::vector objects; + LLClipboard::instance().pasteFromClipboard(objects); + S32 count = objects.size(); + for(S32 i = 0; i < count; i++) + { + const LLUUID &item_id = objects.at(i); + + // Can't paste folders + const LLInventoryCategory *cat = gInventory.getCategory(item_id); + if (cat) + { + return false; + } + + const LLInventoryItem *item = gInventory.getItem(item_id); + if (item && LLAssetType::AT_LANDMARK != item->getType()) + { + return false; + } + } + return true; +} + +void LLFavoritesBarCtrl::pasteFromClipboard() const +{ + LLInventoryModel* model = &gInventory; + if(model && isClipboardPasteable()) + { + LLInventoryItem* item = NULL; + std::vector objects; + LLClipboard::instance().pasteFromClipboard(objects); + S32 count = objects.size(); + LLUUID parent_id(mFavoriteFolderId); + for(S32 i = 0; i < count; i++) + { + item = model->getItem(objects.at(i)); + if (item) + { + copy_inventory_item( + gAgent.getID(), + item->getPermissions().getOwner(), + item->getUUID(), + parent_id, + std::string(), + LLPointer(NULL)); + } + } + } +} + +void LLFavoritesBarCtrl::onButtonMouseDown(LLUUID id, LLUICtrl* ctrl, S32 x, S32 y, MASK mask) +{ + // EXT-6997 (Fav bar: Pop-up menu for LM in overflow dropdown is kept after LM was dragged away) + // mContextMenuHandle.get() - is a pop-up menu (of items) in already opened dropdown menu. + // We have to check and set visibility of pop-up menu in such a way instead of using + // LLMenuHolderGL::hideMenus() because it will close both menus(dropdown and pop-up), but + // we need to close only pop-up menu while dropdown one should be still opened. + LLMenuGL* menu = (LLMenuGL*)mContextMenuHandle.get(); + if(menu && menu->getVisible()) + { + menu->setVisible(false); + } + + mDragItemId = id; + mStartDrag = true; + + S32 screenX, screenY; + localPointToScreen(x, y, &screenX, &screenY); + + LLToolDragAndDrop::getInstance()->setDragStart(screenX, screenY); +} + +void LLFavoritesBarCtrl::onButtonMouseUp(LLUUID id, LLUICtrl* ctrl, S32 x, S32 y, MASK mask) +{ + mStartDrag = false; + mDragItemId = LLUUID::null; +} + +void LLFavoritesBarCtrl::onEndDrag() +{ + mEndDragConnection.disconnect(); + + showDragMarker(false); + mDragItemId = LLUUID::null; + LLView::getWindow()->setCursor(UI_CURSOR_ARROW); +} + +bool LLFavoritesBarCtrl::handleHover(S32 x, S32 y, MASK mask) +{ + if (mDragItemId != LLUUID::null && mStartDrag) + { + S32 screenX, screenY; + localPointToScreen(x, y, &screenX, &screenY); + + if(LLToolDragAndDrop::getInstance()->isOverThreshold(screenX, screenY)) + { + LLToolDragAndDrop::getInstance()->beginDrag( + DAD_LANDMARK, mDragItemId, + LLToolDragAndDrop::SOURCE_LIBRARY); + + mStartDrag = false; + + return LLToolDragAndDrop::getInstance()->handleHover(x, y, mask); + } + } + + return true; +} + +LLUICtrl* LLFavoritesBarCtrl::findChildByLocalCoords(S32 x, S32 y) +{ + LLUICtrl* ctrl = NULL; + const child_list_t* list = getChildList(); + + for (child_list_const_iter_t i = list->begin(); i != list->end(); ++i) + { + // Look only for children that are favorite buttons + if ((*i)->getName() == "favorites_bar_btn") + { + LLRect rect = (*i)->getRect(); + // We consider a button hit if the cursor is left of the right side + // This makes the hit a bit less finicky than hitting directly on the button itself + if (x <= rect.mRight) + { + ctrl = dynamic_cast(*i); + break; + } + } + } + + return ctrl; +} + +bool LLFavoritesBarCtrl::needToSaveItemsOrder(const LLInventoryModel::item_array_t& items) +{ + bool result = false; + + // if there is an item without sort order field set, we need to save items order + for (LLInventoryModel::item_array_t::const_iterator i = items.begin(); i != items.end(); ++i) + { + if (LLFavoritesOrderStorage::instance().getSortIndex((*i)->getUUID()) < 0) + { + result = true; + break; + } + } + + return result; +} + +void LLFavoritesBarCtrl::insertItem(LLInventoryModel::item_array_t& items, const LLUUID& dest_item_id, LLViewerInventoryItem* insertedItem, bool insert_before) +{ + // Get the iterator to the destination item + LLInventoryModel::item_array_t::iterator it_dest = LLInventoryModel::findItemIterByUUID(items, dest_item_id); + if (it_dest == items.end()) + return; + + // Go to the next element if one wishes to insert after the dest element + if (!insert_before) + { + ++it_dest; + } + + // Insert the source item in the right place + if (it_dest != items.end()) + { + items.insert(it_dest, insertedItem); + } + else + { + // Append to the list if it_dest reached the end + items.push_back(insertedItem); + } +} + +const std::string LLFavoritesOrderStorage::SORTING_DATA_FILE_NAME = "landmarks_sorting.xml"; +const S32 LLFavoritesOrderStorage::NO_INDEX = -1; +bool LLFavoritesOrderStorage::mSaveOnExit = false; + +void LLFavoritesOrderStorage::setSortIndex(const LLViewerInventoryItem* inv_item, S32 sort_index) +{ + mSortIndexes[inv_item->getUUID()] = sort_index; + mIsDirty = true; + getSLURL(inv_item->getAssetUUID()); +} + +S32 LLFavoritesOrderStorage::getSortIndex(const LLUUID& inv_item_id) +{ + sort_index_map_t::const_iterator it = mSortIndexes.find(inv_item_id); + if (it != mSortIndexes.end()) + { + return it->second; + } + return NO_INDEX; +} + +void LLFavoritesOrderStorage::removeSortIndex(const LLUUID& inv_item_id) +{ + mSortIndexes.erase(inv_item_id); + mIsDirty = true; +} + +void LLFavoritesOrderStorage::getSLURL(const LLUUID& asset_id) +{ + slurls_map_t::iterator slurl_iter = mSLURLs.find(asset_id); + if (slurl_iter != mSLURLs.end()) return; // SLURL for current landmark is already cached + + LLLandmark* lm = gLandmarkList.getAsset(asset_id, + boost::bind(&LLFavoritesOrderStorage::onLandmarkLoaded, this, asset_id, _1)); + if (lm) + { + LL_DEBUGS("FavoritesBar") << "landmark for " << asset_id << " already loaded" << LL_ENDL; + onLandmarkLoaded(asset_id, lm); + } + return; +} + +// static +std::string LLFavoritesOrderStorage::getStoredFavoritesFilename(const std::string &grid) +{ + std::string user_dir = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, ""); + + return (user_dir.empty() ? "" + : gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, + "stored_favorites_" + + grid + + ".xml") + ); +} + +// static +std::string LLFavoritesOrderStorage::getStoredFavoritesFilename() +{ + return getStoredFavoritesFilename(LLGridManager::getInstance()->getGrid()); +} + +// static +void LLFavoritesOrderStorage::destroyClass() +{ + LLFavoritesOrderStorage::instance().cleanup(); + + + std::string old_filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "stored_favorites.xml"); + llifstream file; + file.open(old_filename.c_str()); + if (file.is_open()) + { + file.close(); + std::string new_filename = getStoredFavoritesFilename(); + LL_INFOS("FavoritesBar") << "moving favorites from old name '" << old_filename + << "' to new name '" << new_filename << "'" + << LL_ENDL; + LLFile::copy(old_filename,new_filename); + LLFile::remove(old_filename); + } + + std::string filename = getSavedOrderFileName(); + file.open(filename.c_str()); + if (file.is_open()) + { + file.close(); + LLFile::remove(filename); + } + if(mSaveOnExit || gSavedSettings.getBOOL("UpdateRememberPasswordSetting")) + { + LLFavoritesOrderStorage::instance().saveFavoritesRecord(true); + } +} + +std::string LLFavoritesOrderStorage::getSavedOrderFileName() +{ + // If we quit from the login screen we will not have an SL account + // name. Don't try to save, otherwise we'll dump a file in + // C:\Program Files\SecondLife\ or similar. JC + std::string user_dir = gDirUtilp->getLindenUserDir(); + return (user_dir.empty() ? "" : gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, SORTING_DATA_FILE_NAME)); +} + +void LLFavoritesOrderStorage::load() +{ + std::string filename = getSavedOrderFileName(); + LLSD settings_llsd; + llifstream file; + file.open(filename.c_str()); + if (file.is_open()) + { + LLSDSerialize::fromXML(settings_llsd, file); + LL_INFOS("FavoritesBar") << "loaded favorites order from '" << filename << "' " + << (settings_llsd.isMap() ? "" : "un") << "successfully" + << LL_ENDL; + file.close(); + mSaveOnExit = true; + + for (LLSD::map_const_iterator iter = settings_llsd.beginMap(); + iter != settings_llsd.endMap(); ++iter) + { + mSortIndexes.insert(std::make_pair(LLUUID(iter->first), (S32)iter->second.asInteger())); + } + } + else + { + filename = getStoredFavoritesFilename(); + if (!filename.empty()) + { + llifstream in_file; + in_file.open(filename.c_str()); + LLSD fav_llsd; + if (in_file.is_open()) + { + LLSDSerialize::fromXML(fav_llsd, in_file); + LL_INFOS("FavoritesBar") << "loaded favorites from '" << filename << "' " + << (fav_llsd.isMap() ? "" : "un") << "successfully" + << LL_ENDL; + in_file.close(); + if (fav_llsd.isMap() && fav_llsd.has(gAgentUsername)) + { + mStorageFavorites = fav_llsd[gAgentUsername]; + + S32 index = 0; + bool needs_validation = gSavedPerAccountSettings.getBOOL("ShowFavoritesOnLogin"); + for (LLSD::array_iterator iter = mStorageFavorites.beginArray(); + iter != mStorageFavorites.endArray(); ++iter) + { + // Validation + LLUUID fv_id = iter->get("id").asUUID(); + if (needs_validation + && (fv_id.isNull() + || iter->get("asset_id").asUUID().isNull() + || iter->get("name").asString().empty() + || iter->get("slurl").asString().empty())) + { + mRecreateFavoriteStorage = true; + } + + mSortIndexes.insert(std::make_pair(fv_id, index)); + index++; + } + } + } + else + { + LL_WARNS("FavoritesBar") << "unable to open favorites from '" << filename << "'" << LL_ENDL; + } + } + } +} + +// static +void LLFavoritesOrderStorage::removeFavoritesRecordOfUser(const std::string &user, const std::string &grid) +{ + std::string filename = getStoredFavoritesFilename(grid); + if (!filename.empty()) + { + LLSD fav_llsd; + llifstream file; + file.open(filename.c_str()); + if (file.is_open()) + { + LLSDSerialize::fromXML(fav_llsd, file); + file.close(); + + // Note : use the "John Doe" and not the "john.doe" version of the name. + // See saveFavoritesSLURLs() here above for the reason why. + if (fav_llsd.has(user)) + { + LLSD user_llsd = fav_llsd[user]; + + if ((user_llsd.beginArray() != user_llsd.endArray()) && user_llsd.beginArray()->has("id")) + { + for (LLSD::array_iterator iter = user_llsd.beginArray(); iter != user_llsd.endArray(); ++iter) + { + LLSD value; + value["id"] = iter->get("id").asUUID(); + iter->assign(value); + } + fav_llsd[user] = user_llsd; + llofstream file; + file.open(filename.c_str()); + if (file.is_open()) + { + LLSDSerialize::toPrettyXML(fav_llsd, file); + file.close(); + } + } + else + { + LL_INFOS("FavoritesBar") << "Removed favorites for " << user << LL_ENDL; + fav_llsd.erase(user); + } + } + + llofstream out_file; + out_file.open(filename.c_str()); + if (out_file.is_open()) + { + LLSDSerialize::toPrettyXML(fav_llsd, out_file); + LL_INFOS("FavoritesBar") << "saved favorites to '" << filename << "' " + << LL_ENDL; + out_file.close(); + } + } + } +} + +// static +void LLFavoritesOrderStorage::removeFavoritesRecordOfUser() +{ + std::string filename = getStoredFavoritesFilename(); + if (!filename.empty()) + { + LLSD fav_llsd; + llifstream file; + file.open(filename.c_str()); + if (file.is_open()) + { + LLSDSerialize::fromXML(fav_llsd, file); + file.close(); + + LLAvatarName av_name; + LLAvatarNameCache::get( gAgentID, &av_name ); + // Note : use the "John Doe" and not the "john.doe" version of the name. + // See saveFavoritesSLURLs() here above for the reason why. + if (fav_llsd.has(av_name.getUserName())) + { + LLSD user_llsd = fav_llsd[av_name.getUserName()]; + + if ((user_llsd.beginArray()!= user_llsd.endArray()) && user_llsd.beginArray()->has("id")) + { + for (LLSD::array_iterator iter = user_llsd.beginArray();iter != user_llsd.endArray(); ++iter) + { + LLSD value; + value["id"]= iter->get("id").asUUID(); + iter->assign(value); + } + fav_llsd[av_name.getUserName()] = user_llsd; + llofstream file; + file.open(filename.c_str()); + if ( file.is_open() ) + { + LLSDSerialize::toPrettyXML(fav_llsd, file); + file.close(); + } + } + else + { + LL_INFOS("FavoritesBar") << "Removed favorites for " << av_name.getUserName() << LL_ENDL; + fav_llsd.erase(av_name.getUserName()); + } + } + + llofstream out_file; + out_file.open(filename.c_str()); + if ( out_file.is_open() ) + { + LLSDSerialize::toPrettyXML(fav_llsd, out_file); + LL_INFOS("FavoritesBar") << "saved favorites to '" << filename << "' " + << LL_ENDL; + out_file.close(); + } + } + } +} + +void LLFavoritesOrderStorage::onLandmarkLoaded(const LLUUID& asset_id, LLLandmark* landmark) +{ + if (landmark) + { + LL_DEBUGS("FavoritesBar") << "landmark for " << asset_id << " loaded" << LL_ENDL; + LLVector3d pos_global; + if (!landmark->getGlobalPos(pos_global)) + { + // If global position was unknown on first getGlobalPos() call + // it should be set for the subsequent calls. + landmark->getGlobalPos(pos_global); + } + + if (!pos_global.isExactlyZero()) + { + LL_DEBUGS("FavoritesBar") << "requesting slurl for landmark " << asset_id << LL_ENDL; + LLLandmarkActions::getSLURLfromPosGlobal(pos_global, + boost::bind(&LLFavoritesOrderStorage::storeFavoriteSLURL, this, asset_id, _1)); + } + } +} + +void LLFavoritesOrderStorage::storeFavoriteSLURL(const LLUUID& asset_id, std::string& slurl) +{ + LL_DEBUGS("FavoritesBar") << "Saving landmark SLURL '" << slurl << "' for " << asset_id << LL_ENDL; + mSLURLs[asset_id] = slurl; +} + +void LLFavoritesOrderStorage::cleanup() +{ + // nothing to clean + if (!mIsDirty) return; + + const LLUUID fav_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + gInventory.collectDescendents(fav_id, cats, items, LLInventoryModel::EXCLUDE_TRASH); + + IsNotInFavorites is_not_in_fav(items); + + sort_index_map_t aTempMap; + //copy unremoved values from mSortIndexes to aTempMap + std::remove_copy_if(mSortIndexes.begin(), mSortIndexes.end(), + inserter(aTempMap, aTempMap.begin()), + is_not_in_fav); + + //Swap the contents of mSortIndexes and aTempMap + mSortIndexes.swap(aTempMap); +} + +// See also LLInventorySort where landmarks in the Favorites folder are sorted. +class LLViewerInventoryItemSort +{ +public: + bool operator()(const LLPointer& a, const LLPointer& b) + { + return LLFavoritesOrderStorage::instance().getSortIndex(a->getUUID()) + < LLFavoritesOrderStorage::instance().getSortIndex(b->getUUID()); + } +}; + +void LLFavoritesOrderStorage::saveOrder() +{ + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLIsType is_type(LLAssetType::AT_LANDMARK); + LLUUID favorites_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); + gInventory.collectDescendentsIf(favorites_id, cats, items, LLInventoryModel::EXCLUDE_TRASH, is_type); + std::sort(items.begin(), items.end(), LLViewerInventoryItemSort()); + saveItemsOrder(items); +} + +void LLFavoritesOrderStorage::saveItemsOrder( const LLInventoryModel::item_array_t& items ) +{ + + int sortField = 0; + // current order is saved by setting incremental values (1, 2, 3, ...) for the sort field + for (LLInventoryModel::item_array_t::const_iterator i = items.begin(); i != items.end(); ++i) + { + LLViewerInventoryItem* item = *i; + + setSortIndex(item, ++sortField); + + item->setComplete(true); + item->updateServer(false); + + gInventory.updateItem(item); + + // Tell the parent folder to refresh its sort order. + gInventory.addChangedMask(LLInventoryObserver::SORT, item->getParentUUID()); + } + + gInventory.notifyObservers(); +} + + +// * @param source_item_id - LLUUID of the source item to be moved into new position +// * @param target_item_id - LLUUID of the target item before which source item should be placed. +void LLFavoritesOrderStorage::rearrangeFavoriteLandmarks(const LLUUID& source_item_id, const LLUUID& target_item_id) +{ + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLIsType is_type(LLAssetType::AT_LANDMARK); + LLUUID favorites_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); + gInventory.collectDescendentsIf(favorites_id, cats, items, LLInventoryModel::EXCLUDE_TRASH, is_type); + + // ensure items are sorted properly before changing order. EXT-3498 + std::sort(items.begin(), items.end(), LLViewerInventoryItemSort()); + + // update order + gInventory.updateItemsOrder(items, source_item_id, target_item_id); + + saveItemsOrder(items); +} + +bool LLFavoritesOrderStorage::saveFavoritesRecord(bool pref_changed) +{ + pref_changed |= mRecreateFavoriteStorage; + mRecreateFavoriteStorage = false; + + // Can get called before inventory is done initializing. + if (!gInventory.isInventoryUsable()) + { + return false; + } + + LLUUID favorite_folder= gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); + if (favorite_folder.isNull()) + { + return false; + } + + LLInventoryModel::item_array_t items; + LLInventoryModel::cat_array_t cats; + + LLIsType is_type(LLAssetType::AT_LANDMARK); + gInventory.collectDescendentsIf(favorite_folder, cats, items, LLInventoryModel::EXCLUDE_TRASH, is_type); + + std::sort(items.begin(), items.end(), LLFavoritesSort()); + bool name_changed = false; + + for (LLInventoryModel::item_array_t::iterator it = items.begin(); it != items.end(); it++) + { + if(mFavoriteNames[(*it)->getUUID()] != ((*it)->getName())) + { + mFavoriteNames[(*it)->getUUID()] = (*it)->getName(); + name_changed = true; + } + } + + for (std::set::iterator it = mMissingSLURLs.begin(); it != mMissingSLURLs.end(); it++) + { + slurls_map_t::iterator slurl_iter = mSLURLs.find(*it); + if (slurl_iter != mSLURLs.end()) + { + pref_changed = true; + break; + } + } + + if((items != mPrevFavorites) || name_changed || pref_changed || gSavedSettings.getBOOL("UpdateRememberPasswordSetting")) + { + std::string filename = getStoredFavoritesFilename(); + if (!filename.empty()) + { + llifstream in_file; + in_file.open(filename.c_str()); + LLSD fav_llsd; + if (in_file.is_open()) + { + LLSDSerialize::fromXML(fav_llsd, in_file); + in_file.close(); + } + else + { + LL_WARNS("FavoritesBar") << "unable to open favorites from '" << filename << "'" << LL_ENDL; + } + + LLSD user_llsd; + S32 fav_iter = 0; + mMissingSLURLs.clear(); + + LLSD save_pass; + save_pass["save_password"] = gSavedSettings.getBOOL("RememberPassword"); + user_llsd[fav_iter] = save_pass; + fav_iter++; + + for (LLInventoryModel::item_array_t::iterator it = items.begin(); it != items.end(); it++) + { + LLSD value; + if (gSavedPerAccountSettings.getBOOL("ShowFavoritesOnLogin")) + { + value["name"] = (*it)->getName(); + value["asset_id"] = (*it)->getAssetUUID(); + value["id"] = (*it)->getUUID(); + slurls_map_t::iterator slurl_iter = mSLURLs.find(value["asset_id"]); + if (slurl_iter != mSLURLs.end()) + { + value["slurl"] = slurl_iter->second; + user_llsd[fav_iter] = value; + } + else + { + getSLURL((*it)->getAssetUUID()); + value["slurl"] = ""; + user_llsd[fav_iter] = value; + mUpdateRequired = true; + mMissingSLURLs.insert((*it)->getAssetUUID()); + } + } + else + { + value["id"] = (*it)->getUUID(); + user_llsd[fav_iter] = value; + } + + fav_iter ++; + } + + LLAvatarName av_name; + LLAvatarNameCache::get( gAgentID, &av_name ); + // Note : use the "John Doe" and not the "john.doe" version of the name + // as we'll compare it with the stored credentials in the login panel. + fav_llsd[av_name.getUserName()] = user_llsd; + llofstream file; + file.open(filename.c_str()); + if ( file.is_open() ) + { + LLSDSerialize::toPrettyXML(fav_llsd, file); + file.close(); + mSaveOnExit = false; + } + else + { + LL_WARNS("FavoritesBar") << "unable to open favorites storage for '" << av_name.getUserName() + << "' at '" << filename << "' " << LL_ENDL; + } + } + mPrevFavorites = items; + } + + return true; + +} + +void LLFavoritesOrderStorage::showFavoritesOnLoginChanged(bool show) +{ + if (show) + { + saveFavoritesRecord(true); + } + else + { + removeFavoritesRecordOfUser(); + } +} + +bool LLFavoritesOrderStorage::isStorageUpdateNeeded() +{ + if (!mRecreateFavoriteStorage) + { + for (LLSD::array_iterator iter = mStorageFavorites.beginArray(); + iter != mStorageFavorites.endArray(); ++iter) + { + if (mFavoriteNames[iter->get("id").asUUID()] != iter->get("name").asString()) + { + mRecreateFavoriteStorage = true; + return true; + } + } + } + return false; +} + +void AddFavoriteLandmarkCallback::fire(const LLUUID& inv_item_id) +{ + if (!mTargetLandmarkId.isNull()) + { + LLFavoritesOrderStorage::instance().rearrangeFavoriteLandmarks(inv_item_id, mTargetLandmarkId); + } +} + +// EOF diff --git a/indra/newview/llfavoritesbar.h b/indra/newview/llfavoritesbar.h index 62d6bb8973..ee18f3ed45 100644 --- a/indra/newview/llfavoritesbar.h +++ b/indra/newview/llfavoritesbar.h @@ -1,297 +1,297 @@ -/** - * @file llfavoritesbar.h - * @brief LLFavoritesBarCtrl base class - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFAVORITESBARCTRL_H -#define LL_LLFAVORITESBARCTRL_H - -#include "llbutton.h" -#include "lluictrl.h" -#include "lltextbox.h" - -#include "llinventoryobserver.h" -#include "llinventorymodel.h" -#include "llviewerinventory.h" -#include "llinitdestroyclass.h" - -class LLMenuItemCallGL; -class LLToggleableMenu; - -class LLFavoritesBarCtrl : public LLUICtrl, public LLInventoryObserver -{ -public: - struct Params : public LLInitParam::Block - { - Optional image_drag_indication; - Optional more_button; - Optional label; - Params(); - }; - -protected: - LLFavoritesBarCtrl(const Params&); - friend class LLUICtrlFactory; - friend class LLItemCopiedCallback; -public: - virtual ~LLFavoritesBarCtrl(); - - /*virtual*/ bool postBuild() override; - - /*virtual*/ bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) override; - bool handleDragAndDropToMenu(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg); - - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask) override; - // LLInventoryObserver observer trigger - /*virtual*/ void changed(U32 mask) override; - /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true) override; - /*virtual*/ void draw() override; - - void showDragMarker(bool show) { mShowDragMarker = show; } - void setLandingTab(LLUICtrl* tab) { mLandingTab = tab; } - -protected: - void updateButtons(bool force_update = false); - LLButton* createButton(const LLPointer item, const LLButton::Params& button_params, S32 x_offset ); - const LLButton::Params& getButtonParams(); - bool collectFavoriteItems(LLInventoryModel::item_array_t &items); - - void onButtonClick(LLUUID id); - void onButtonRightClick(LLUUID id,LLView* button,S32 x,S32 y,MASK mask); - - void onButtonMouseDown(LLUUID id, LLUICtrl* button, S32 x, S32 y, MASK mask); - void onButtonMouseUp(LLUUID id, LLUICtrl* button, S32 x, S32 y, MASK mask); - - void onEndDrag(); - - bool enableSelected(const LLSD& userdata); - void doToSelected(const LLSD& userdata); - static bool onRenameCommit(const LLSD& notification, const LLSD& response); - bool isClipboardPasteable() const; - void pasteFromClipboard() const; - - void showDropDownMenu(); - - void onMoreTextBoxClicked(); - - LLHandle mOverflowMenuHandle; - LLHandle mContextMenuHandle; - - LLUUID mFavoriteFolderId; - const LLFontGL *mFont; - S32 mFirstDropDownItem; - S32 mDropDownItemsCount; - bool mUpdateDropDownItems; - bool mRestoreOverflowMenu; - bool mDragToOverflowMenu; - bool mGetPrevItems; - - LLUUID mSelectedItemID; - LLFrameTimer mItemsChangedTimer; - LLUIImage* mImageDragIndication; - -private: - /* - * Helper function to make code more readable. It handles all drag and drop - * operations of the existing favorites items to the favorites bar to on the overflow menu. - */ - void handleExistingFavoriteDragAndDrop(S32 x, S32 y); - - /* - * Helper function to make code more readable. It handles all drag and drop - * operations of the new landmark to the favorites bar or to the overflow menu. - */ - void handleNewFavoriteDragAndDrop(LLInventoryItem *item, const LLUUID& favorites_id, S32 x, S32 y); - - // finds a control under the specified LOCAL point - LLUICtrl* findChildByLocalCoords(S32 x, S32 y); - - bool findDragAndDropTarget(LLUUID &target_id, bool &insert_before, S32 x, S32 y); - - // checks if the current order of the favorites items must be saved - bool needToSaveItemsOrder(const LLInventoryModel::item_array_t& items); - - /** - * inserts an item identified by insertedItemId BEFORE an item identified by beforeItemId. - * this function assumes that an item identified by insertedItemId doesn't exist in items array. - */ - void insertItem(LLInventoryModel::item_array_t& items, const LLUUID& dest_item_id, LLViewerInventoryItem* insertedItem, bool insert_before); - - // finds an item by it's UUID in the items array - LLInventoryModel::item_array_t::iterator findItemByUUID(LLInventoryModel::item_array_t& items, const LLUUID& id); - - void createOverflowMenu(); - - void updateOverflowMenuItems(); - - // Fits menu item label width with favorites menu width - void fitLabelWidth(LLMenuItemCallGL* menu_item); - - void addOpenLandmarksMenuItem(LLToggleableMenu* menu); - - void positionAndShowOverflowMenu(); - - bool mShowDragMarker; - LLUICtrl* mLandingTab; - LLUICtrl* mLastTab; - LLTextBox* mMoreTextBox; - LLTextBox* mBarLabel; - - LLUUID mDragItemId; - bool mStartDrag; - LLInventoryModel::item_array_t mItems; - - static F64 sWaitingForCallabck; - bool mItemsListDirty; - - S32 mMouseX; - S32 mMouseY; - - boost::signals2::connection mEndDragConnection; -}; - -/** - * Class to store sorting order of favorites landmarks in a local file. EXT-3985. - * It replaced previously implemented solution to store sort index in landmark's name as a "@" prefix. - * Data are stored in user home directory. - */ -class LLFavoritesOrderStorage : public LLSingleton - , public LLDestroyClass -{ - LLSINGLETON(LLFavoritesOrderStorage); - LOG_CLASS(LLFavoritesOrderStorage); -public: - /** - * Sets sort index for specified with LLUUID favorite landmark - */ - void setSortIndex(const LLViewerInventoryItem* inv_item, S32 sort_index); - - /** - * Gets sort index for specified with LLUUID favorite landmark - */ - S32 getSortIndex(const LLUUID& inv_item_id); - void removeSortIndex(const LLUUID& inv_item_id); - - void getSLURL(const LLUUID& asset_id); - - // Saves current order of the passed items using inventory item sort field. - // Resets 'items' sort fields and saves them on server. - // Is used to save order for Favorites folder. - void saveItemsOrder(const LLInventoryModel::item_array_t& items); - - void saveOrder(); - - void rearrangeFavoriteLandmarks(const LLUUID& source_item_id, const LLUUID& target_item_id); - - /** - * Implementation of LLDestroyClass. Calls cleanup() instance method. - * - * It is important this callback is called before gInventory is cleaned. - * For now it is called from LLAppViewer::cleanup() -> LLAppViewer::disconnectViewer(), - * Inventory is cleaned later from LLAppViewer::cleanup() after LLAppViewer::disconnectViewer() is called. - * @see cleanup() - */ - static void destroyClass(); - static std::string getStoredFavoritesFilename(const std::string &grid); - static std::string getStoredFavoritesFilename(); - static std::string getSavedOrderFileName(); - - // Remove record of specified user's favorites from file on disk. - static void removeFavoritesRecordOfUser(const std::string &user, const std::string &grid); - // Remove record of current user's favorites from file on disk. - static void removeFavoritesRecordOfUser(); - - bool saveFavoritesRecord(bool pref_changed = false); - void showFavoritesOnLoginChanged(bool show); - - bool isStorageUpdateNeeded(); - - LLInventoryModel::item_array_t mPrevFavorites; - LLSD mStorageFavorites; - bool mRecreateFavoriteStorage; - - const static S32 NO_INDEX; - static bool mSaveOnExit; - bool mUpdateRequired; - std::map mFavoriteNames; - -private: - /** - * Removes sort indexes for items which are not in Favorites bar for now. - */ - void cleanup(); - - const static std::string SORTING_DATA_FILE_NAME; - - void load(); - - void onLandmarkLoaded(const LLUUID& asset_id, class LLLandmark* landmark); - void storeFavoriteSLURL(const LLUUID& asset_id, std::string& slurl); - - typedef std::map sort_index_map_t; - sort_index_map_t mSortIndexes; - - typedef std::map slurls_map_t; - slurls_map_t mSLURLs; - std::set mMissingSLURLs; - bool mIsDirty; - - struct IsNotInFavorites - { - IsNotInFavorites(const LLInventoryModel::item_array_t& items) - : mFavoriteItems(items) - { - - } - - /** - * Returns true if specified item is not found among inventory items - */ - bool operator()(const sort_index_map_t::value_type& id_index_pair) const - { - LLPointer item = gInventory.getItem(id_index_pair.first); - if (item.isNull()) return true; - - LLInventoryModel::item_array_t::const_iterator found_it = - std::find(mFavoriteItems.begin(), mFavoriteItems.end(), item); - - return found_it == mFavoriteItems.end(); - } - private: - LLInventoryModel::item_array_t mFavoriteItems; - }; - -}; - -inline -LLFavoritesOrderStorage::LLFavoritesOrderStorage() : - mIsDirty(false), - mUpdateRequired(false), - mRecreateFavoriteStorage(false) -{ load(); } - -#endif // LL_LLFAVORITESBARCTRL_H +/** + * @file llfavoritesbar.h + * @brief LLFavoritesBarCtrl base class + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFAVORITESBARCTRL_H +#define LL_LLFAVORITESBARCTRL_H + +#include "llbutton.h" +#include "lluictrl.h" +#include "lltextbox.h" + +#include "llinventoryobserver.h" +#include "llinventorymodel.h" +#include "llviewerinventory.h" +#include "llinitdestroyclass.h" + +class LLMenuItemCallGL; +class LLToggleableMenu; + +class LLFavoritesBarCtrl : public LLUICtrl, public LLInventoryObserver +{ +public: + struct Params : public LLInitParam::Block + { + Optional image_drag_indication; + Optional more_button; + Optional label; + Params(); + }; + +protected: + LLFavoritesBarCtrl(const Params&); + friend class LLUICtrlFactory; + friend class LLItemCopiedCallback; +public: + virtual ~LLFavoritesBarCtrl(); + + /*virtual*/ bool postBuild() override; + + /*virtual*/ bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) override; + bool handleDragAndDropToMenu(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept, std::string& tooltip_msg); + + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask) override; + // LLInventoryObserver observer trigger + /*virtual*/ void changed(U32 mask) override; + /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true) override; + /*virtual*/ void draw() override; + + void showDragMarker(bool show) { mShowDragMarker = show; } + void setLandingTab(LLUICtrl* tab) { mLandingTab = tab; } + +protected: + void updateButtons(bool force_update = false); + LLButton* createButton(const LLPointer item, const LLButton::Params& button_params, S32 x_offset ); + const LLButton::Params& getButtonParams(); + bool collectFavoriteItems(LLInventoryModel::item_array_t &items); + + void onButtonClick(LLUUID id); + void onButtonRightClick(LLUUID id,LLView* button,S32 x,S32 y,MASK mask); + + void onButtonMouseDown(LLUUID id, LLUICtrl* button, S32 x, S32 y, MASK mask); + void onButtonMouseUp(LLUUID id, LLUICtrl* button, S32 x, S32 y, MASK mask); + + void onEndDrag(); + + bool enableSelected(const LLSD& userdata); + void doToSelected(const LLSD& userdata); + static bool onRenameCommit(const LLSD& notification, const LLSD& response); + bool isClipboardPasteable() const; + void pasteFromClipboard() const; + + void showDropDownMenu(); + + void onMoreTextBoxClicked(); + + LLHandle mOverflowMenuHandle; + LLHandle mContextMenuHandle; + + LLUUID mFavoriteFolderId; + const LLFontGL *mFont; + S32 mFirstDropDownItem; + S32 mDropDownItemsCount; + bool mUpdateDropDownItems; + bool mRestoreOverflowMenu; + bool mDragToOverflowMenu; + bool mGetPrevItems; + + LLUUID mSelectedItemID; + LLFrameTimer mItemsChangedTimer; + LLUIImage* mImageDragIndication; + +private: + /* + * Helper function to make code more readable. It handles all drag and drop + * operations of the existing favorites items to the favorites bar to on the overflow menu. + */ + void handleExistingFavoriteDragAndDrop(S32 x, S32 y); + + /* + * Helper function to make code more readable. It handles all drag and drop + * operations of the new landmark to the favorites bar or to the overflow menu. + */ + void handleNewFavoriteDragAndDrop(LLInventoryItem *item, const LLUUID& favorites_id, S32 x, S32 y); + + // finds a control under the specified LOCAL point + LLUICtrl* findChildByLocalCoords(S32 x, S32 y); + + bool findDragAndDropTarget(LLUUID &target_id, bool &insert_before, S32 x, S32 y); + + // checks if the current order of the favorites items must be saved + bool needToSaveItemsOrder(const LLInventoryModel::item_array_t& items); + + /** + * inserts an item identified by insertedItemId BEFORE an item identified by beforeItemId. + * this function assumes that an item identified by insertedItemId doesn't exist in items array. + */ + void insertItem(LLInventoryModel::item_array_t& items, const LLUUID& dest_item_id, LLViewerInventoryItem* insertedItem, bool insert_before); + + // finds an item by it's UUID in the items array + LLInventoryModel::item_array_t::iterator findItemByUUID(LLInventoryModel::item_array_t& items, const LLUUID& id); + + void createOverflowMenu(); + + void updateOverflowMenuItems(); + + // Fits menu item label width with favorites menu width + void fitLabelWidth(LLMenuItemCallGL* menu_item); + + void addOpenLandmarksMenuItem(LLToggleableMenu* menu); + + void positionAndShowOverflowMenu(); + + bool mShowDragMarker; + LLUICtrl* mLandingTab; + LLUICtrl* mLastTab; + LLTextBox* mMoreTextBox; + LLTextBox* mBarLabel; + + LLUUID mDragItemId; + bool mStartDrag; + LLInventoryModel::item_array_t mItems; + + static F64 sWaitingForCallabck; + bool mItemsListDirty; + + S32 mMouseX; + S32 mMouseY; + + boost::signals2::connection mEndDragConnection; +}; + +/** + * Class to store sorting order of favorites landmarks in a local file. EXT-3985. + * It replaced previously implemented solution to store sort index in landmark's name as a "@" prefix. + * Data are stored in user home directory. + */ +class LLFavoritesOrderStorage : public LLSingleton + , public LLDestroyClass +{ + LLSINGLETON(LLFavoritesOrderStorage); + LOG_CLASS(LLFavoritesOrderStorage); +public: + /** + * Sets sort index for specified with LLUUID favorite landmark + */ + void setSortIndex(const LLViewerInventoryItem* inv_item, S32 sort_index); + + /** + * Gets sort index for specified with LLUUID favorite landmark + */ + S32 getSortIndex(const LLUUID& inv_item_id); + void removeSortIndex(const LLUUID& inv_item_id); + + void getSLURL(const LLUUID& asset_id); + + // Saves current order of the passed items using inventory item sort field. + // Resets 'items' sort fields and saves them on server. + // Is used to save order for Favorites folder. + void saveItemsOrder(const LLInventoryModel::item_array_t& items); + + void saveOrder(); + + void rearrangeFavoriteLandmarks(const LLUUID& source_item_id, const LLUUID& target_item_id); + + /** + * Implementation of LLDestroyClass. Calls cleanup() instance method. + * + * It is important this callback is called before gInventory is cleaned. + * For now it is called from LLAppViewer::cleanup() -> LLAppViewer::disconnectViewer(), + * Inventory is cleaned later from LLAppViewer::cleanup() after LLAppViewer::disconnectViewer() is called. + * @see cleanup() + */ + static void destroyClass(); + static std::string getStoredFavoritesFilename(const std::string &grid); + static std::string getStoredFavoritesFilename(); + static std::string getSavedOrderFileName(); + + // Remove record of specified user's favorites from file on disk. + static void removeFavoritesRecordOfUser(const std::string &user, const std::string &grid); + // Remove record of current user's favorites from file on disk. + static void removeFavoritesRecordOfUser(); + + bool saveFavoritesRecord(bool pref_changed = false); + void showFavoritesOnLoginChanged(bool show); + + bool isStorageUpdateNeeded(); + + LLInventoryModel::item_array_t mPrevFavorites; + LLSD mStorageFavorites; + bool mRecreateFavoriteStorage; + + const static S32 NO_INDEX; + static bool mSaveOnExit; + bool mUpdateRequired; + std::map mFavoriteNames; + +private: + /** + * Removes sort indexes for items which are not in Favorites bar for now. + */ + void cleanup(); + + const static std::string SORTING_DATA_FILE_NAME; + + void load(); + + void onLandmarkLoaded(const LLUUID& asset_id, class LLLandmark* landmark); + void storeFavoriteSLURL(const LLUUID& asset_id, std::string& slurl); + + typedef std::map sort_index_map_t; + sort_index_map_t mSortIndexes; + + typedef std::map slurls_map_t; + slurls_map_t mSLURLs; + std::set mMissingSLURLs; + bool mIsDirty; + + struct IsNotInFavorites + { + IsNotInFavorites(const LLInventoryModel::item_array_t& items) + : mFavoriteItems(items) + { + + } + + /** + * Returns true if specified item is not found among inventory items + */ + bool operator()(const sort_index_map_t::value_type& id_index_pair) const + { + LLPointer item = gInventory.getItem(id_index_pair.first); + if (item.isNull()) return true; + + LLInventoryModel::item_array_t::const_iterator found_it = + std::find(mFavoriteItems.begin(), mFavoriteItems.end(), item); + + return found_it == mFavoriteItems.end(); + } + private: + LLInventoryModel::item_array_t mFavoriteItems; + }; + +}; + +inline +LLFavoritesOrderStorage::LLFavoritesOrderStorage() : + mIsDirty(false), + mUpdateRequired(false), + mRecreateFavoriteStorage(false) +{ load(); } + +#endif // LL_LLFAVORITESBARCTRL_H diff --git a/indra/newview/llfeaturemanager.cpp b/indra/newview/llfeaturemanager.cpp index b27c84dce1..046bfc2f3e 100644 --- a/indra/newview/llfeaturemanager.cpp +++ b/indra/newview/llfeaturemanager.cpp @@ -1,755 +1,755 @@ -/** - * @file llfeaturemanager.cpp - * @brief LLFeatureManager class implementation - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include -#include - -#include -#include - -#include "llfeaturemanager.h" -#include "lldir.h" - -#include "llsys.h" -#include "llgl.h" - -#include "llappviewer.h" -#include "llbufferstream.h" -#include "llnotificationsutil.h" -#include "llviewercontrol.h" -#include "llworld.h" -#include "lldrawpoolterrain.h" -#include "llviewertexturelist.h" -#include "llversioninfo.h" -#include "llwindow.h" -#include "llui.h" -#include "llcontrol.h" -#include "llboost.h" -#include "llweb.h" -#include "llviewershadermgr.h" -#include "llstring.h" -#include "stringize.h" -#include "llcorehttputil.h" - -#if LL_WINDOWS -#include "lldxhardware.h" -#endif - -#if LL_DARWIN -const char FEATURE_TABLE_FILENAME[] = "featuretable_mac.txt"; -#elif LL_LINUX -const char FEATURE_TABLE_FILENAME[] = "featuretable_linux.txt"; -#else -const char FEATURE_TABLE_FILENAME[] = "featuretable.txt"; -#endif - -#if 0 // consuming code in #if 0 below -#endif -LLFeatureInfo::LLFeatureInfo(const std::string& name, const bool available, const F32 level) - : mValid(true), mName(name), mAvailable(available), mRecommendedLevel(level) -{ -} - -LLFeatureList::LLFeatureList(const std::string& name) - : mName(name) -{ -} - -LLFeatureList::~LLFeatureList() -{ -} - -void LLFeatureList::addFeature(const std::string& name, const bool available, const F32 level) -{ - if (mFeatures.count(name)) - { - LL_WARNS("RenderInit") << "LLFeatureList::Attempting to add preexisting feature " << name << LL_ENDL; - } - - LLFeatureInfo fi(name, available, level); - LL_DEBUGS_ONCE("RenderInit") << "Feature '" << name << "' " - << (available ? "" : "not " ) << "available" - << " at " << level - << LL_ENDL; - mFeatures[name] = fi; -} - -bool LLFeatureList::isFeatureAvailable(const std::string& name) -{ - if (mFeatures.count(name)) - { - return mFeatures[name].mAvailable; - } - - LL_WARNS_ONCE("RenderInit") << "Feature " << name << " not on feature list!" << LL_ENDL; - - // changing this to true so you have to explicitly disable - // something for it to be disabled - return true; -} - -F32 LLFeatureList::getRecommendedValue(const std::string& name) -{ - if (mFeatures.count(name) && isFeatureAvailable(name)) - { - LL_DEBUGS_ONCE("RenderInit") << "Setting '" << name << "' to recommended value " << mFeatures[name].mRecommendedLevel << LL_ENDL; - return mFeatures[name].mRecommendedLevel; - } - - LL_WARNS_ONCE("RenderInit") << "Feature " << name << " not on feature list or not available!" << LL_ENDL; - return 0; -} - -bool LLFeatureList::maskList(LLFeatureList &mask) -{ - LL_DEBUGS_ONCE() << "Masking with " << mask.mName << LL_ENDL; - // - // Lookup the specified feature mask, and overlay it on top of the - // current feature mask. - // - - LLFeatureInfo mask_fi; - - feature_map_t::iterator feature_it; - for (feature_it = mask.mFeatures.begin(); feature_it != mask.mFeatures.end(); ++feature_it) - { - mask_fi = feature_it->second; - // - // Look for the corresponding feature - // - if (!mFeatures.count(mask_fi.mName)) - { - LL_WARNS("RenderInit") << "Feature " << mask_fi.mName << " in mask not in top level!" << LL_ENDL; - continue; - } - - LLFeatureInfo &cur_fi = mFeatures[mask_fi.mName]; - if (mask_fi.mAvailable && !cur_fi.mAvailable) - { - LL_WARNS("RenderInit") << "Mask attempting to reenabling disabled feature, ignoring " << cur_fi.mName << LL_ENDL; - continue; - } - cur_fi.mAvailable = mask_fi.mAvailable; - cur_fi.mRecommendedLevel = llmin(cur_fi.mRecommendedLevel, mask_fi.mRecommendedLevel); - LL_DEBUGS("RenderInit") << "Feature mask " << mask.mName - << " Feature " << mask_fi.mName - << " Mask: " << mask_fi.mRecommendedLevel - << " Now: " << cur_fi.mRecommendedLevel << LL_ENDL; - } - - LL_DEBUGS("RenderInit") << "After applying mask " << mask.mName << std::endl; - // Will conditionally call dump only if the above message will be logged, thanks - // to it being wrapped by the LL_DEBUGS and LL_ENDL macros. - dump(); - LL_CONT << LL_ENDL; - - return true; -} - -void LLFeatureList::dump() -{ - LL_DEBUGS("RenderInit") << "Feature list: " << mName << LL_ENDL; - - LLFeatureInfo fi; - feature_map_t::iterator feature_it; - for (feature_it = mFeatures.begin(); feature_it != mFeatures.end(); ++feature_it) - { - fi = feature_it->second; - LL_DEBUGS("RenderInit") << "With " << mName << " feature " << fi.mName << " " << fi.mAvailable << ":" << fi.mRecommendedLevel << LL_ENDL; - } -} - -static const std::vector sGraphicsLevelNames = boost::assign::list_of - ("Low") - ("LowMid") - ("Mid") - ("MidHigh") - ("High") - ("HighUltra") - ("Ultra") -; - -U32 LLFeatureManager::getMaxGraphicsLevel() const -{ - return sGraphicsLevelNames.size() - 1; -} - -bool LLFeatureManager::isValidGraphicsLevel(U32 level) const -{ - return (level <= getMaxGraphicsLevel()); -} - -std::string LLFeatureManager::getNameForGraphicsLevel(U32 level) const -{ - if (isValidGraphicsLevel(level)) - { - return sGraphicsLevelNames[level]; - } - return STRINGIZE("Invalid graphics level " << level << ", valid are 0 .. " - << getMaxGraphicsLevel()); -} - -S32 LLFeatureManager::getGraphicsLevelForName(const std::string& name) const -{ - const std::string FixedFunction("FixedFunction"); - std::string rname(name); - if (LLStringUtil::endsWith(rname, FixedFunction)) - { - // chop off any "FixedFunction" suffix - rname = rname.substr(0, rname.length() - FixedFunction.length()); - } - for (S32 i(0), iend(getMaxGraphicsLevel()); i <= iend; ++i) - { - if (sGraphicsLevelNames[i] == rname) - { - return i; - } - } - return -1; -} - -LLFeatureList *LLFeatureManager::findMask(const std::string& name) -{ - if (mMaskList.count(name)) - { - return mMaskList[name]; - } - - return NULL; -} - -bool LLFeatureManager::maskFeatures(const std::string& name) -{ - LLFeatureList *maskp = findMask(name); - if (!maskp) - { - LL_DEBUGS("RenderInit") << "Unknown feature mask " << name << LL_ENDL; - return false; - } - LL_INFOS("RenderInit") << "Applying GPU Feature list: " << name << LL_ENDL; - return maskList(*maskp); -} - -bool LLFeatureManager::loadFeatureTables() -{ - // *TODO - if I or anyone else adds something else to the skipped list - // make this data driven. Put it in the feature table and parse it - // correctly - mSkippedFeatures.insert("RenderAnisotropic"); - mSkippedFeatures.insert("RenderGamma"); - mSkippedFeatures.insert("RenderVBOEnable"); - mSkippedFeatures.insert("RenderFogRatio"); - - // first table is install with app - std::string app_path = gDirUtilp->getAppRODataDir(); - app_path += gDirUtilp->getDirDelimiter(); - - std::string filename; - filename = FEATURE_TABLE_FILENAME; - - app_path += filename; - - bool parse_ok = parseFeatureTable(app_path); - - return parse_ok; -} - - -bool LLFeatureManager::parseFeatureTable(std::string filename) -{ - LL_INFOS("RenderInit") << "Attempting to parse feature table from " << filename << LL_ENDL; - - llifstream file; - std::string name; - U32 version; - - cleanupFeatureTables(); // in case an earlier attempt left partial results - file.open(filename.c_str()); /*Flawfinder: ignore*/ - - if (!file) - { - LL_WARNS("RenderInit") << "Unable to open feature table " << filename << LL_ENDL; - return false; - } - - // Check file version - file >> name; - if (name != "version") - { - LL_WARNS("RenderInit") << filename << " does not appear to be a valid feature table!" << LL_ENDL; - return false; - } - file >> version; - - mTableVersion = version; - LL_INFOS("RenderInit") << "Found feature table version " << version << LL_ENDL; - - LLFeatureList *flp = NULL; - bool parse_ok = true; - while (parse_ok && file >> name ) - { - char buffer[MAX_STRING]; /*Flawfinder: ignore*/ - - if (name.substr(0,2) == "//") - { - // This is a comment. - file.getline(buffer, MAX_STRING); - continue; - } - - if (name == "list") - { - LL_DEBUGS("RenderInit") << "Before new list" << std::endl; - if (flp) - { - flp->dump(); - } - else - { - LL_CONT << "No current list"; - } - LL_CONT << LL_ENDL; - - // It's a new mask, create it. - file >> name; - if (!mMaskList.count(name)) - { - flp = new LLFeatureList(name); - mMaskList[name] = flp; - } - else - { - LL_WARNS("RenderInit") << "Overriding mask '" << name << "'; this is invalid!" << LL_ENDL; - parse_ok = false; - } - } - else - { - if (flp) - { - S32 available; - F32 recommended; - file >> available >> recommended; - flp->addFeature(name, available, recommended); - } - else - { - LL_WARNS("RenderInit") << "Specified parameter before keyword!" << LL_ENDL; - parse_ok = false; - } - } - } - file.close(); - - if (!parse_ok) - { - LL_WARNS("RenderInit") << "Discarding feature table data from " << filename << LL_ENDL; - cleanupFeatureTables(); - } - - return parse_ok; -} - -F32 gpu_benchmark(); - -#if LL_WINDOWS - -F32 logExceptionBenchmark() -{ - // FIXME: gpu_benchmark uses many C++ classes on the stack to control state. - // SEH exceptions with our current exception handling options do not call - // destructors for these classes, resulting in an undefined state should - // this handler be invoked. - F32 gbps = -1; - __try - { - gbps = gpu_benchmark(); - } - __except (msc_exception_filter(GetExceptionCode(), GetExceptionInformation())) - { - // HACK - ensure that profiling is disabled - LLGLSLShader::finishProfile(false); - - // convert to C++ styled exception - char integer_string[32]; - sprintf(integer_string, "SEH, code: %lu\n", GetExceptionCode()); - throw std::exception(integer_string); - } - return gbps; -} -#endif - -bool LLFeatureManager::loadGPUClass() -{ - if (!gSavedSettings.getBOOL("SkipBenchmark")) - { - F32 class1_gbps = gSavedSettings.getF32("RenderClass1MemoryBandwidth"); - //get memory bandwidth from benchmark - F32 gbps; - try - { -#if LL_WINDOWS - gbps = logExceptionBenchmark(); -#else - gbps = gpu_benchmark(); -#endif - } - catch (const std::exception& e) - { - gbps = -1.f; - LL_WARNS("RenderInit") << "GPU benchmark failed: " << e.what() << LL_ENDL; - } - - mGPUMemoryBandwidth = gbps; - - // bias by CPU speed - F32 cpu_basis_mhz = gSavedSettings.getF32("RenderCPUBasis"); - F32 cpu_mhz = (F32) gSysCPU.getMHz(); - F32 cpu_bias = llclamp(cpu_mhz / cpu_basis_mhz, 0.5f, 1.f); - gbps *= cpu_bias; - - if (gbps < 0.f) - { //couldn't bench, default to Low - #if LL_DARWIN - //GLVersion is misleading on OSX, just default to class 3 if we can't bench - LL_WARNS("RenderInit") << "Unable to get an accurate benchmark; defaulting to class 3" << LL_ENDL; - mGPUClass = GPU_CLASS_3; - #else - mGPUClass = GPU_CLASS_0; - #endif - } - else if (gbps <= class1_gbps) - { - mGPUClass = GPU_CLASS_1; - } - else if (gbps <= class1_gbps *2.f) - { - mGPUClass = GPU_CLASS_2; - } - else if (gbps <= class1_gbps*4.f) - { - mGPUClass = GPU_CLASS_3; - } - else if (gbps <= class1_gbps*8.f) - { - mGPUClass = GPU_CLASS_4; - } - else - { - mGPUClass = GPU_CLASS_5; - } - - #if LL_WINDOWS - const F32Gigabytes MIN_PHYSICAL_MEMORY(2); - - LLMemory::updateMemoryInfo(); - F32Gigabytes physical_mem = LLMemory::getMaxMemKB(); - if (MIN_PHYSICAL_MEMORY > physical_mem && mGPUClass > GPU_CLASS_1) - { - // reduce quality on systems that don't have enough memory - mGPUClass = (EGPUClass)(mGPUClass - 1); - } - #endif //LL_WINDOWS - } //end if benchmark - else - { - //setting says don't benchmark MAINT-7558 - LL_WARNS("RenderInit") << "Setting 'SkipBenchmark' is true; defaulting to class 1 (may be required for some GPUs)" << LL_ENDL; - - mGPUClass = GPU_CLASS_1; - } - - // defaults - mGPUString = gGLManager.getRawGLString(); - mGPUSupported = true; - - return true; // indicates that a gpu value was established -} - -void LLFeatureManager::cleanupFeatureTables() -{ - std::for_each(mMaskList.begin(), mMaskList.end(), DeletePairedPointer()); - mMaskList.clear(); -} - -void LLFeatureManager::initSingleton() -{ - // load the tables - loadFeatureTables(); - - // get the gpu class - loadGPUClass(); - - // apply the base masks, so we know if anything is disabled - applyBaseMasks(); -} - -void LLFeatureManager::applyRecommendedSettings() -{ - // apply saved settings - // cap the level at 2 (high) - U32 level = llmax(GPU_CLASS_0, llmin(mGPUClass, GPU_CLASS_5)); - - LL_INFOS("RenderInit") << "Applying Recommended Features for level " << level << LL_ENDL; - - setGraphicsLevel(level, false); - gSavedSettings.setU32("RenderQualityPerformance", level); - - // now apply the tweaks to draw distance - // these are double negatives, because feature masks only work by - // downgrading values, so i needed to make a true value go to false - // for certain cards, thus the awkward name, "Disregard..." - if(!gSavedSettings.getBOOL("Disregard96DefaultDrawDistance")) - { - gSavedSettings.setF32("RenderFarClip", 96.0f); - } - else if(!gSavedSettings.getBOOL("Disregard128DefaultDrawDistance")) - { - gSavedSettings.setF32("RenderFarClip", 128.0f); - } -} - -void LLFeatureManager::applyFeatures(bool skipFeatures) -{ - // see featuretable.txt / featuretable_linux.txt / featuretable_mac.txt - -#ifndef LL_RELEASE_FOR_DOWNLOAD - dump(); -#endif - - // scroll through all of these and set their corresponding control value - for(feature_map_t::iterator mIt = mFeatures.begin(); - mIt != mFeatures.end(); - ++mIt) - { - // skip features you want to skip - // do this for when you don't want to change certain settings - if(skipFeatures) - { - if(mSkippedFeatures.find(mIt->first) != mSkippedFeatures.end()) - { - continue; - } - } - - // get the control setting - LLControlVariable* ctrl = gSavedSettings.getControl(mIt->first); - if(ctrl == NULL) - { - LL_WARNS("RenderInit") << "AHHH! Control setting " << mIt->first << " does not exist!" << LL_ENDL; - continue; - } - - // handle all the different types - if(ctrl->isType(TYPE_BOOLEAN)) - { - gSavedSettings.setBOOL(mIt->first, (bool)getRecommendedValue(mIt->first)); - } - else if (ctrl->isType(TYPE_S32)) - { - gSavedSettings.setS32(mIt->first, (S32)getRecommendedValue(mIt->first)); - } - else if (ctrl->isType(TYPE_U32)) - { - gSavedSettings.setU32(mIt->first, (U32)getRecommendedValue(mIt->first)); - } - else if (ctrl->isType(TYPE_F32)) - { - gSavedSettings.setF32(mIt->first, (F32)getRecommendedValue(mIt->first)); - } - else - { - LL_WARNS("RenderInit") << "AHHH! Control variable is not a numeric type!" << LL_ENDL; - } - } -} - -void LLFeatureManager::setGraphicsLevel(U32 level, bool skipFeatures) -{ - LLViewerShaderMgr::sSkipReload = true; - flush_glerror(); // Whatever may have already happened (e.g., to cause us to change), don't let confuse it with new initializations. - applyBaseMasks(); - - // if we're passed an invalid level, default to "Low" - std::string features(isValidGraphicsLevel(level)? getNameForGraphicsLevel(level) : "Low"); - - maskFeatures(features); - - applyFeatures(skipFeatures); - - LLViewerShaderMgr::sSkipReload = false; - LLViewerShaderMgr::instance()->setShaders(); - gPipeline.refreshCachedSettings(); -} - -void LLFeatureManager::applyBaseMasks() -{ - // reapply masks - mFeatures.clear(); - - LLFeatureList* maskp = findMask("all"); - if(maskp == NULL) - { - LL_WARNS("RenderInit") << "AHH! No \"all\" in feature table!" << LL_ENDL; - return; - } - - mFeatures = maskp->getFeatures(); - - // mask class - if (mGPUClass >= 0 && mGPUClass < 6) - { - const char* class_table[] = - { - "Class0", - "Class1", - "Class2", - "Class3", - "Class4", - "Class5", - }; - - LL_INFOS("RenderInit") << "Setting GPU Class to " << class_table[mGPUClass] << LL_ENDL; - maskFeatures(class_table[mGPUClass]); - } - else - { - LL_INFOS("RenderInit") << "Setting GPU Class to Unknown" << LL_ENDL; - maskFeatures("Unknown"); - } - - // now all those wacky ones - if (gGLManager.mIsNVIDIA) - { - maskFeatures("NVIDIA"); - } - if (gGLManager.mIsAMD) - { - maskFeatures("AMD"); - } - if (gGLManager.mIsIntel) - { - maskFeatures("Intel"); - } - if (gGLManager.mGLVersion < 3.f) - { - maskFeatures("OpenGLPre30"); - } - if (gGLManager.mNumTextureImageUnits <= 8) - { - maskFeatures("TexUnit8orLess"); - } - if (gGLManager.mVRAM > 512) - { - maskFeatures("VRAMGT512"); - } - if (gGLManager.mVRAM < 2048) - { - maskFeatures("VRAMLT2GB"); - } - if (gGLManager.mGLVersion < 3.99f) - { - maskFeatures("GL3"); - } - - // now mask by gpu string - // Replaces ' ' with '_' in mGPUString to deal with inability for parser to handle spaces - std::string gpustr = mGPUString; - for (std::string::iterator iter = gpustr.begin(); iter != gpustr.end(); ++iter) - { - if (*iter == ' ') - { - *iter = '_'; - } - } - - //LL_INFOS() << "Masking features from gpu table match: " << gpustr << LL_ENDL; - maskFeatures(gpustr); - - if (isSafe()) - { - maskFeatures("safe"); - } -} - -LLSD LLFeatureManager::getRecommendedSettingsMap() -{ - // Create the map and fill it with the hardware recommended settings. - // It's needed to create an initial Default graphics preset (MAINT-6435). - // The process is similar to the one LLFeatureManager::applyRecommendedSettings() does. - - LLSD map(LLSD::emptyMap()); - - U32 level = llmax(GPU_CLASS_0, llmin(mGPUClass, GPU_CLASS_5)); - LL_INFOS("RenderInit") << "Getting the map of recommended settings for level " << level << LL_ENDL; - - std::string features(isValidGraphicsLevel(level) ? getNameForGraphicsLevel(level) : "Low"); - - maskFeatures(features); - - LLControlVariable* ctrl = gSavedSettings.getControl("RenderQualityPerformance"); // include the quality value for correct preset loading - map["RenderQualityPerformance"]["Value"] = (LLSD::Integer)level; - map["RenderQualityPerformance"]["Comment"] = ctrl->getComment();; - map["RenderQualityPerformance"]["Persist"] = 1; - map["RenderQualityPerformance"]["Type"] = LLControlGroup::typeEnumToString(ctrl->type()); - - - - for (feature_map_t::iterator mIt = mFeatures.begin(); mIt != mFeatures.end(); ++mIt) - { - LLControlVariable* ctrl = gSavedSettings.getControl(mIt->first); - if (ctrl == NULL) - { - LL_WARNS("RenderInit") << "AHHH! Control setting " << mIt->first << " does not exist!" << LL_ENDL; - continue; - } - - if (ctrl->isType(TYPE_BOOLEAN)) - { - map[mIt->first]["Value"] = (LLSD::Boolean)getRecommendedValue(mIt->first); - } - else if (ctrl->isType(TYPE_S32) || ctrl->isType(TYPE_U32)) - { - map[mIt->first]["Value"] = (LLSD::Integer)getRecommendedValue(mIt->first); - } - else if (ctrl->isType(TYPE_F32)) - { - map[mIt->first]["Value"] = (LLSD::Real)getRecommendedValue(mIt->first); - } - else - { - LL_WARNS("RenderInit") << "AHHH! Control variable is not a numeric type!" << LL_ENDL; - continue; - } - map[mIt->first]["Comment"] = ctrl->getComment();; - map[mIt->first]["Persist"] = 1; - map[mIt->first]["Type"] = LLControlGroup::typeEnumToString(ctrl->type()); - } - - return map; -} +/** + * @file llfeaturemanager.cpp + * @brief LLFeatureManager class implementation + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include +#include + +#include +#include + +#include "llfeaturemanager.h" +#include "lldir.h" + +#include "llsys.h" +#include "llgl.h" + +#include "llappviewer.h" +#include "llbufferstream.h" +#include "llnotificationsutil.h" +#include "llviewercontrol.h" +#include "llworld.h" +#include "lldrawpoolterrain.h" +#include "llviewertexturelist.h" +#include "llversioninfo.h" +#include "llwindow.h" +#include "llui.h" +#include "llcontrol.h" +#include "llboost.h" +#include "llweb.h" +#include "llviewershadermgr.h" +#include "llstring.h" +#include "stringize.h" +#include "llcorehttputil.h" + +#if LL_WINDOWS +#include "lldxhardware.h" +#endif + +#if LL_DARWIN +const char FEATURE_TABLE_FILENAME[] = "featuretable_mac.txt"; +#elif LL_LINUX +const char FEATURE_TABLE_FILENAME[] = "featuretable_linux.txt"; +#else +const char FEATURE_TABLE_FILENAME[] = "featuretable.txt"; +#endif + +#if 0 // consuming code in #if 0 below +#endif +LLFeatureInfo::LLFeatureInfo(const std::string& name, const bool available, const F32 level) + : mValid(true), mName(name), mAvailable(available), mRecommendedLevel(level) +{ +} + +LLFeatureList::LLFeatureList(const std::string& name) + : mName(name) +{ +} + +LLFeatureList::~LLFeatureList() +{ +} + +void LLFeatureList::addFeature(const std::string& name, const bool available, const F32 level) +{ + if (mFeatures.count(name)) + { + LL_WARNS("RenderInit") << "LLFeatureList::Attempting to add preexisting feature " << name << LL_ENDL; + } + + LLFeatureInfo fi(name, available, level); + LL_DEBUGS_ONCE("RenderInit") << "Feature '" << name << "' " + << (available ? "" : "not " ) << "available" + << " at " << level + << LL_ENDL; + mFeatures[name] = fi; +} + +bool LLFeatureList::isFeatureAvailable(const std::string& name) +{ + if (mFeatures.count(name)) + { + return mFeatures[name].mAvailable; + } + + LL_WARNS_ONCE("RenderInit") << "Feature " << name << " not on feature list!" << LL_ENDL; + + // changing this to true so you have to explicitly disable + // something for it to be disabled + return true; +} + +F32 LLFeatureList::getRecommendedValue(const std::string& name) +{ + if (mFeatures.count(name) && isFeatureAvailable(name)) + { + LL_DEBUGS_ONCE("RenderInit") << "Setting '" << name << "' to recommended value " << mFeatures[name].mRecommendedLevel << LL_ENDL; + return mFeatures[name].mRecommendedLevel; + } + + LL_WARNS_ONCE("RenderInit") << "Feature " << name << " not on feature list or not available!" << LL_ENDL; + return 0; +} + +bool LLFeatureList::maskList(LLFeatureList &mask) +{ + LL_DEBUGS_ONCE() << "Masking with " << mask.mName << LL_ENDL; + // + // Lookup the specified feature mask, and overlay it on top of the + // current feature mask. + // + + LLFeatureInfo mask_fi; + + feature_map_t::iterator feature_it; + for (feature_it = mask.mFeatures.begin(); feature_it != mask.mFeatures.end(); ++feature_it) + { + mask_fi = feature_it->second; + // + // Look for the corresponding feature + // + if (!mFeatures.count(mask_fi.mName)) + { + LL_WARNS("RenderInit") << "Feature " << mask_fi.mName << " in mask not in top level!" << LL_ENDL; + continue; + } + + LLFeatureInfo &cur_fi = mFeatures[mask_fi.mName]; + if (mask_fi.mAvailable && !cur_fi.mAvailable) + { + LL_WARNS("RenderInit") << "Mask attempting to reenabling disabled feature, ignoring " << cur_fi.mName << LL_ENDL; + continue; + } + cur_fi.mAvailable = mask_fi.mAvailable; + cur_fi.mRecommendedLevel = llmin(cur_fi.mRecommendedLevel, mask_fi.mRecommendedLevel); + LL_DEBUGS("RenderInit") << "Feature mask " << mask.mName + << " Feature " << mask_fi.mName + << " Mask: " << mask_fi.mRecommendedLevel + << " Now: " << cur_fi.mRecommendedLevel << LL_ENDL; + } + + LL_DEBUGS("RenderInit") << "After applying mask " << mask.mName << std::endl; + // Will conditionally call dump only if the above message will be logged, thanks + // to it being wrapped by the LL_DEBUGS and LL_ENDL macros. + dump(); + LL_CONT << LL_ENDL; + + return true; +} + +void LLFeatureList::dump() +{ + LL_DEBUGS("RenderInit") << "Feature list: " << mName << LL_ENDL; + + LLFeatureInfo fi; + feature_map_t::iterator feature_it; + for (feature_it = mFeatures.begin(); feature_it != mFeatures.end(); ++feature_it) + { + fi = feature_it->second; + LL_DEBUGS("RenderInit") << "With " << mName << " feature " << fi.mName << " " << fi.mAvailable << ":" << fi.mRecommendedLevel << LL_ENDL; + } +} + +static const std::vector sGraphicsLevelNames = boost::assign::list_of + ("Low") + ("LowMid") + ("Mid") + ("MidHigh") + ("High") + ("HighUltra") + ("Ultra") +; + +U32 LLFeatureManager::getMaxGraphicsLevel() const +{ + return sGraphicsLevelNames.size() - 1; +} + +bool LLFeatureManager::isValidGraphicsLevel(U32 level) const +{ + return (level <= getMaxGraphicsLevel()); +} + +std::string LLFeatureManager::getNameForGraphicsLevel(U32 level) const +{ + if (isValidGraphicsLevel(level)) + { + return sGraphicsLevelNames[level]; + } + return STRINGIZE("Invalid graphics level " << level << ", valid are 0 .. " + << getMaxGraphicsLevel()); +} + +S32 LLFeatureManager::getGraphicsLevelForName(const std::string& name) const +{ + const std::string FixedFunction("FixedFunction"); + std::string rname(name); + if (LLStringUtil::endsWith(rname, FixedFunction)) + { + // chop off any "FixedFunction" suffix + rname = rname.substr(0, rname.length() - FixedFunction.length()); + } + for (S32 i(0), iend(getMaxGraphicsLevel()); i <= iend; ++i) + { + if (sGraphicsLevelNames[i] == rname) + { + return i; + } + } + return -1; +} + +LLFeatureList *LLFeatureManager::findMask(const std::string& name) +{ + if (mMaskList.count(name)) + { + return mMaskList[name]; + } + + return NULL; +} + +bool LLFeatureManager::maskFeatures(const std::string& name) +{ + LLFeatureList *maskp = findMask(name); + if (!maskp) + { + LL_DEBUGS("RenderInit") << "Unknown feature mask " << name << LL_ENDL; + return false; + } + LL_INFOS("RenderInit") << "Applying GPU Feature list: " << name << LL_ENDL; + return maskList(*maskp); +} + +bool LLFeatureManager::loadFeatureTables() +{ + // *TODO - if I or anyone else adds something else to the skipped list + // make this data driven. Put it in the feature table and parse it + // correctly + mSkippedFeatures.insert("RenderAnisotropic"); + mSkippedFeatures.insert("RenderGamma"); + mSkippedFeatures.insert("RenderVBOEnable"); + mSkippedFeatures.insert("RenderFogRatio"); + + // first table is install with app + std::string app_path = gDirUtilp->getAppRODataDir(); + app_path += gDirUtilp->getDirDelimiter(); + + std::string filename; + filename = FEATURE_TABLE_FILENAME; + + app_path += filename; + + bool parse_ok = parseFeatureTable(app_path); + + return parse_ok; +} + + +bool LLFeatureManager::parseFeatureTable(std::string filename) +{ + LL_INFOS("RenderInit") << "Attempting to parse feature table from " << filename << LL_ENDL; + + llifstream file; + std::string name; + U32 version; + + cleanupFeatureTables(); // in case an earlier attempt left partial results + file.open(filename.c_str()); /*Flawfinder: ignore*/ + + if (!file) + { + LL_WARNS("RenderInit") << "Unable to open feature table " << filename << LL_ENDL; + return false; + } + + // Check file version + file >> name; + if (name != "version") + { + LL_WARNS("RenderInit") << filename << " does not appear to be a valid feature table!" << LL_ENDL; + return false; + } + file >> version; + + mTableVersion = version; + LL_INFOS("RenderInit") << "Found feature table version " << version << LL_ENDL; + + LLFeatureList *flp = NULL; + bool parse_ok = true; + while (parse_ok && file >> name ) + { + char buffer[MAX_STRING]; /*Flawfinder: ignore*/ + + if (name.substr(0,2) == "//") + { + // This is a comment. + file.getline(buffer, MAX_STRING); + continue; + } + + if (name == "list") + { + LL_DEBUGS("RenderInit") << "Before new list" << std::endl; + if (flp) + { + flp->dump(); + } + else + { + LL_CONT << "No current list"; + } + LL_CONT << LL_ENDL; + + // It's a new mask, create it. + file >> name; + if (!mMaskList.count(name)) + { + flp = new LLFeatureList(name); + mMaskList[name] = flp; + } + else + { + LL_WARNS("RenderInit") << "Overriding mask '" << name << "'; this is invalid!" << LL_ENDL; + parse_ok = false; + } + } + else + { + if (flp) + { + S32 available; + F32 recommended; + file >> available >> recommended; + flp->addFeature(name, available, recommended); + } + else + { + LL_WARNS("RenderInit") << "Specified parameter before keyword!" << LL_ENDL; + parse_ok = false; + } + } + } + file.close(); + + if (!parse_ok) + { + LL_WARNS("RenderInit") << "Discarding feature table data from " << filename << LL_ENDL; + cleanupFeatureTables(); + } + + return parse_ok; +} + +F32 gpu_benchmark(); + +#if LL_WINDOWS + +F32 logExceptionBenchmark() +{ + // FIXME: gpu_benchmark uses many C++ classes on the stack to control state. + // SEH exceptions with our current exception handling options do not call + // destructors for these classes, resulting in an undefined state should + // this handler be invoked. + F32 gbps = -1; + __try + { + gbps = gpu_benchmark(); + } + __except (msc_exception_filter(GetExceptionCode(), GetExceptionInformation())) + { + // HACK - ensure that profiling is disabled + LLGLSLShader::finishProfile(false); + + // convert to C++ styled exception + char integer_string[32]; + sprintf(integer_string, "SEH, code: %lu\n", GetExceptionCode()); + throw std::exception(integer_string); + } + return gbps; +} +#endif + +bool LLFeatureManager::loadGPUClass() +{ + if (!gSavedSettings.getBOOL("SkipBenchmark")) + { + F32 class1_gbps = gSavedSettings.getF32("RenderClass1MemoryBandwidth"); + //get memory bandwidth from benchmark + F32 gbps; + try + { +#if LL_WINDOWS + gbps = logExceptionBenchmark(); +#else + gbps = gpu_benchmark(); +#endif + } + catch (const std::exception& e) + { + gbps = -1.f; + LL_WARNS("RenderInit") << "GPU benchmark failed: " << e.what() << LL_ENDL; + } + + mGPUMemoryBandwidth = gbps; + + // bias by CPU speed + F32 cpu_basis_mhz = gSavedSettings.getF32("RenderCPUBasis"); + F32 cpu_mhz = (F32) gSysCPU.getMHz(); + F32 cpu_bias = llclamp(cpu_mhz / cpu_basis_mhz, 0.5f, 1.f); + gbps *= cpu_bias; + + if (gbps < 0.f) + { //couldn't bench, default to Low + #if LL_DARWIN + //GLVersion is misleading on OSX, just default to class 3 if we can't bench + LL_WARNS("RenderInit") << "Unable to get an accurate benchmark; defaulting to class 3" << LL_ENDL; + mGPUClass = GPU_CLASS_3; + #else + mGPUClass = GPU_CLASS_0; + #endif + } + else if (gbps <= class1_gbps) + { + mGPUClass = GPU_CLASS_1; + } + else if (gbps <= class1_gbps *2.f) + { + mGPUClass = GPU_CLASS_2; + } + else if (gbps <= class1_gbps*4.f) + { + mGPUClass = GPU_CLASS_3; + } + else if (gbps <= class1_gbps*8.f) + { + mGPUClass = GPU_CLASS_4; + } + else + { + mGPUClass = GPU_CLASS_5; + } + + #if LL_WINDOWS + const F32Gigabytes MIN_PHYSICAL_MEMORY(2); + + LLMemory::updateMemoryInfo(); + F32Gigabytes physical_mem = LLMemory::getMaxMemKB(); + if (MIN_PHYSICAL_MEMORY > physical_mem && mGPUClass > GPU_CLASS_1) + { + // reduce quality on systems that don't have enough memory + mGPUClass = (EGPUClass)(mGPUClass - 1); + } + #endif //LL_WINDOWS + } //end if benchmark + else + { + //setting says don't benchmark MAINT-7558 + LL_WARNS("RenderInit") << "Setting 'SkipBenchmark' is true; defaulting to class 1 (may be required for some GPUs)" << LL_ENDL; + + mGPUClass = GPU_CLASS_1; + } + + // defaults + mGPUString = gGLManager.getRawGLString(); + mGPUSupported = true; + + return true; // indicates that a gpu value was established +} + +void LLFeatureManager::cleanupFeatureTables() +{ + std::for_each(mMaskList.begin(), mMaskList.end(), DeletePairedPointer()); + mMaskList.clear(); +} + +void LLFeatureManager::initSingleton() +{ + // load the tables + loadFeatureTables(); + + // get the gpu class + loadGPUClass(); + + // apply the base masks, so we know if anything is disabled + applyBaseMasks(); +} + +void LLFeatureManager::applyRecommendedSettings() +{ + // apply saved settings + // cap the level at 2 (high) + U32 level = llmax(GPU_CLASS_0, llmin(mGPUClass, GPU_CLASS_5)); + + LL_INFOS("RenderInit") << "Applying Recommended Features for level " << level << LL_ENDL; + + setGraphicsLevel(level, false); + gSavedSettings.setU32("RenderQualityPerformance", level); + + // now apply the tweaks to draw distance + // these are double negatives, because feature masks only work by + // downgrading values, so i needed to make a true value go to false + // for certain cards, thus the awkward name, "Disregard..." + if(!gSavedSettings.getBOOL("Disregard96DefaultDrawDistance")) + { + gSavedSettings.setF32("RenderFarClip", 96.0f); + } + else if(!gSavedSettings.getBOOL("Disregard128DefaultDrawDistance")) + { + gSavedSettings.setF32("RenderFarClip", 128.0f); + } +} + +void LLFeatureManager::applyFeatures(bool skipFeatures) +{ + // see featuretable.txt / featuretable_linux.txt / featuretable_mac.txt + +#ifndef LL_RELEASE_FOR_DOWNLOAD + dump(); +#endif + + // scroll through all of these and set their corresponding control value + for(feature_map_t::iterator mIt = mFeatures.begin(); + mIt != mFeatures.end(); + ++mIt) + { + // skip features you want to skip + // do this for when you don't want to change certain settings + if(skipFeatures) + { + if(mSkippedFeatures.find(mIt->first) != mSkippedFeatures.end()) + { + continue; + } + } + + // get the control setting + LLControlVariable* ctrl = gSavedSettings.getControl(mIt->first); + if(ctrl == NULL) + { + LL_WARNS("RenderInit") << "AHHH! Control setting " << mIt->first << " does not exist!" << LL_ENDL; + continue; + } + + // handle all the different types + if(ctrl->isType(TYPE_BOOLEAN)) + { + gSavedSettings.setBOOL(mIt->first, (bool)getRecommendedValue(mIt->first)); + } + else if (ctrl->isType(TYPE_S32)) + { + gSavedSettings.setS32(mIt->first, (S32)getRecommendedValue(mIt->first)); + } + else if (ctrl->isType(TYPE_U32)) + { + gSavedSettings.setU32(mIt->first, (U32)getRecommendedValue(mIt->first)); + } + else if (ctrl->isType(TYPE_F32)) + { + gSavedSettings.setF32(mIt->first, (F32)getRecommendedValue(mIt->first)); + } + else + { + LL_WARNS("RenderInit") << "AHHH! Control variable is not a numeric type!" << LL_ENDL; + } + } +} + +void LLFeatureManager::setGraphicsLevel(U32 level, bool skipFeatures) +{ + LLViewerShaderMgr::sSkipReload = true; + flush_glerror(); // Whatever may have already happened (e.g., to cause us to change), don't let confuse it with new initializations. + applyBaseMasks(); + + // if we're passed an invalid level, default to "Low" + std::string features(isValidGraphicsLevel(level)? getNameForGraphicsLevel(level) : "Low"); + + maskFeatures(features); + + applyFeatures(skipFeatures); + + LLViewerShaderMgr::sSkipReload = false; + LLViewerShaderMgr::instance()->setShaders(); + gPipeline.refreshCachedSettings(); +} + +void LLFeatureManager::applyBaseMasks() +{ + // reapply masks + mFeatures.clear(); + + LLFeatureList* maskp = findMask("all"); + if(maskp == NULL) + { + LL_WARNS("RenderInit") << "AHH! No \"all\" in feature table!" << LL_ENDL; + return; + } + + mFeatures = maskp->getFeatures(); + + // mask class + if (mGPUClass >= 0 && mGPUClass < 6) + { + const char* class_table[] = + { + "Class0", + "Class1", + "Class2", + "Class3", + "Class4", + "Class5", + }; + + LL_INFOS("RenderInit") << "Setting GPU Class to " << class_table[mGPUClass] << LL_ENDL; + maskFeatures(class_table[mGPUClass]); + } + else + { + LL_INFOS("RenderInit") << "Setting GPU Class to Unknown" << LL_ENDL; + maskFeatures("Unknown"); + } + + // now all those wacky ones + if (gGLManager.mIsNVIDIA) + { + maskFeatures("NVIDIA"); + } + if (gGLManager.mIsAMD) + { + maskFeatures("AMD"); + } + if (gGLManager.mIsIntel) + { + maskFeatures("Intel"); + } + if (gGLManager.mGLVersion < 3.f) + { + maskFeatures("OpenGLPre30"); + } + if (gGLManager.mNumTextureImageUnits <= 8) + { + maskFeatures("TexUnit8orLess"); + } + if (gGLManager.mVRAM > 512) + { + maskFeatures("VRAMGT512"); + } + if (gGLManager.mVRAM < 2048) + { + maskFeatures("VRAMLT2GB"); + } + if (gGLManager.mGLVersion < 3.99f) + { + maskFeatures("GL3"); + } + + // now mask by gpu string + // Replaces ' ' with '_' in mGPUString to deal with inability for parser to handle spaces + std::string gpustr = mGPUString; + for (std::string::iterator iter = gpustr.begin(); iter != gpustr.end(); ++iter) + { + if (*iter == ' ') + { + *iter = '_'; + } + } + + //LL_INFOS() << "Masking features from gpu table match: " << gpustr << LL_ENDL; + maskFeatures(gpustr); + + if (isSafe()) + { + maskFeatures("safe"); + } +} + +LLSD LLFeatureManager::getRecommendedSettingsMap() +{ + // Create the map and fill it with the hardware recommended settings. + // It's needed to create an initial Default graphics preset (MAINT-6435). + // The process is similar to the one LLFeatureManager::applyRecommendedSettings() does. + + LLSD map(LLSD::emptyMap()); + + U32 level = llmax(GPU_CLASS_0, llmin(mGPUClass, GPU_CLASS_5)); + LL_INFOS("RenderInit") << "Getting the map of recommended settings for level " << level << LL_ENDL; + + std::string features(isValidGraphicsLevel(level) ? getNameForGraphicsLevel(level) : "Low"); + + maskFeatures(features); + + LLControlVariable* ctrl = gSavedSettings.getControl("RenderQualityPerformance"); // include the quality value for correct preset loading + map["RenderQualityPerformance"]["Value"] = (LLSD::Integer)level; + map["RenderQualityPerformance"]["Comment"] = ctrl->getComment();; + map["RenderQualityPerformance"]["Persist"] = 1; + map["RenderQualityPerformance"]["Type"] = LLControlGroup::typeEnumToString(ctrl->type()); + + + + for (feature_map_t::iterator mIt = mFeatures.begin(); mIt != mFeatures.end(); ++mIt) + { + LLControlVariable* ctrl = gSavedSettings.getControl(mIt->first); + if (ctrl == NULL) + { + LL_WARNS("RenderInit") << "AHHH! Control setting " << mIt->first << " does not exist!" << LL_ENDL; + continue; + } + + if (ctrl->isType(TYPE_BOOLEAN)) + { + map[mIt->first]["Value"] = (LLSD::Boolean)getRecommendedValue(mIt->first); + } + else if (ctrl->isType(TYPE_S32) || ctrl->isType(TYPE_U32)) + { + map[mIt->first]["Value"] = (LLSD::Integer)getRecommendedValue(mIt->first); + } + else if (ctrl->isType(TYPE_F32)) + { + map[mIt->first]["Value"] = (LLSD::Real)getRecommendedValue(mIt->first); + } + else + { + LL_WARNS("RenderInit") << "AHHH! Control variable is not a numeric type!" << LL_ENDL; + continue; + } + map[mIt->first]["Comment"] = ctrl->getComment();; + map[mIt->first]["Persist"] = 1; + map[mIt->first]["Type"] = LLControlGroup::typeEnumToString(ctrl->type()); + } + + return map; +} diff --git a/indra/newview/llfeaturemanager.h b/indra/newview/llfeaturemanager.h index 7c0cd99e43..22de6afbae 100644 --- a/indra/newview/llfeaturemanager.h +++ b/indra/newview/llfeaturemanager.h @@ -1,188 +1,188 @@ -/** - * @file llfeaturemanager.h - * @brief The feature manager is responsible for determining what features are turned on/off in the app. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFEATUREMANAGER_H -#define LL_LLFEATUREMANAGER_H - -#include "stdtypes.h" - -#include "llsingleton.h" -#include "llstring.h" -#include -#include "llcoros.h" -#include "lleventcoro.h" - -typedef enum EGPUClass -{ - GPU_CLASS_UNKNOWN = -1, - GPU_CLASS_0 = 0, - GPU_CLASS_1 = 1, - GPU_CLASS_2 = 2, - GPU_CLASS_3 = 3, - GPU_CLASS_4 = 4, - GPU_CLASS_5 = 5 -} EGPUClass; - - -class LLFeatureInfo -{ -public: - LLFeatureInfo() : mValid(false), mAvailable(false), mRecommendedLevel(-1) {} - LLFeatureInfo(const std::string& name, const bool available, const F32 level); - - bool isValid() const { return mValid; }; - -public: - bool mValid; - std::string mName; - bool mAvailable; - F32 mRecommendedLevel; -}; - - -class LLFeatureList -{ -public: - typedef std::map feature_map_t; - - LLFeatureList(const std::string& name); - virtual ~LLFeatureList(); - - bool isFeatureAvailable(const std::string& name); - F32 getRecommendedValue(const std::string& name); - - void setFeatureAvailable(const std::string& name, const bool available); - void setRecommendedLevel(const std::string& name, const F32 level); - - bool loadFeatureList(LLFILE *fp); - - bool maskList(LLFeatureList &mask); - - void addFeature(const std::string& name, const bool available, const F32 level); - - feature_map_t& getFeatures() - { - return mFeatures; - } - - void dump(); -protected: - std::string mName; - feature_map_t mFeatures; -}; - - -class LLFeatureManager : public LLFeatureList, public LLSingleton -{ - LLSINGLETON(LLFeatureManager); - ~LLFeatureManager() {cleanupFeatureTables();} - - // initialize this by loading feature table and gpu table - void initSingleton() override; - -public: - - void maskCurrentList(const std::string& name); // Mask the current feature list with the named list - - bool loadFeatureTables(); - - EGPUClass getGPUClass() { return mGPUClass; } - std::string& getGPUString() { return mGPUString; } - - // get the measured GPU memory bandwidth in GB/sec - // may return 0 of benchmark has not been run or failed to run - F32 getGPUMemoryBandwidth() { return mGPUMemoryBandwidth; } - bool isGPUSupported() { return mGPUSupported; } - F32 getExpectedGLVersion() { return mExpectedGLVersion; } - - void cleanupFeatureTables(); - - S32 getVersion() const { return mTableVersion; } - void setSafe(const bool safe) { mSafe = safe; } - bool isSafe() const { return mSafe; } - - LLFeatureList *findMask(const std::string& name); - bool maskFeatures(const std::string& name); - - // set the graphics to low, medium, high, or ultra. - // skipFeatures forces skipping of mostly hardware settings - // that we don't want to change when we change graphics - // settings - void setGraphicsLevel(U32 level, bool skipFeatures); - - // What 'level' values are valid to pass to setGraphicsLevel()? - // 0 is the low end... - U32 getMaxGraphicsLevel() const; - bool isValidGraphicsLevel(U32 level) const; - - // setGraphicsLevel() levels have names. - std::string getNameForGraphicsLevel(U32 level) const; - // returns -1 for unrecognized name (hence S32 rather than U32) - S32 getGraphicsLevelForName(const std::string& name) const; - - void applyBaseMasks(); - void applyRecommendedSettings(); - - // apply the basic masks. Also, skip one saved - // in the skip list if true - void applyFeatures(bool skipFeatures); - - LLSD getRecommendedSettingsMap(); - -protected: - bool loadGPUClass(); - - bool parseFeatureTable(std::string filename); - ///< @returns true is file parsed correctly, false if not - - void initBaseMask(); - - std::map mMaskList; - std::set mSkippedFeatures; - bool mInited; - S32 mTableVersion; - bool mSafe; // Reinitialize everything to the "safe" mask - EGPUClass mGPUClass; - F32 mGPUMemoryBandwidth = 0.f; // measured memory bandwidth of GPU in GB/second - F32 mExpectedGLVersion; //expected GL version according to gpu table - std::string mGPUString; - bool mGPUSupported; -}; - -inline -LLFeatureManager::LLFeatureManager() -: LLFeatureList("default"), - - mInited(false), - mTableVersion(0), - mSafe(false), - mGPUClass(GPU_CLASS_UNKNOWN), - mExpectedGLVersion(0.f), - mGPUSupported(false) -{ -} - -#endif // LL_LLFEATUREMANAGER_H +/** + * @file llfeaturemanager.h + * @brief The feature manager is responsible for determining what features are turned on/off in the app. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFEATUREMANAGER_H +#define LL_LLFEATUREMANAGER_H + +#include "stdtypes.h" + +#include "llsingleton.h" +#include "llstring.h" +#include +#include "llcoros.h" +#include "lleventcoro.h" + +typedef enum EGPUClass +{ + GPU_CLASS_UNKNOWN = -1, + GPU_CLASS_0 = 0, + GPU_CLASS_1 = 1, + GPU_CLASS_2 = 2, + GPU_CLASS_3 = 3, + GPU_CLASS_4 = 4, + GPU_CLASS_5 = 5 +} EGPUClass; + + +class LLFeatureInfo +{ +public: + LLFeatureInfo() : mValid(false), mAvailable(false), mRecommendedLevel(-1) {} + LLFeatureInfo(const std::string& name, const bool available, const F32 level); + + bool isValid() const { return mValid; }; + +public: + bool mValid; + std::string mName; + bool mAvailable; + F32 mRecommendedLevel; +}; + + +class LLFeatureList +{ +public: + typedef std::map feature_map_t; + + LLFeatureList(const std::string& name); + virtual ~LLFeatureList(); + + bool isFeatureAvailable(const std::string& name); + F32 getRecommendedValue(const std::string& name); + + void setFeatureAvailable(const std::string& name, const bool available); + void setRecommendedLevel(const std::string& name, const F32 level); + + bool loadFeatureList(LLFILE *fp); + + bool maskList(LLFeatureList &mask); + + void addFeature(const std::string& name, const bool available, const F32 level); + + feature_map_t& getFeatures() + { + return mFeatures; + } + + void dump(); +protected: + std::string mName; + feature_map_t mFeatures; +}; + + +class LLFeatureManager : public LLFeatureList, public LLSingleton +{ + LLSINGLETON(LLFeatureManager); + ~LLFeatureManager() {cleanupFeatureTables();} + + // initialize this by loading feature table and gpu table + void initSingleton() override; + +public: + + void maskCurrentList(const std::string& name); // Mask the current feature list with the named list + + bool loadFeatureTables(); + + EGPUClass getGPUClass() { return mGPUClass; } + std::string& getGPUString() { return mGPUString; } + + // get the measured GPU memory bandwidth in GB/sec + // may return 0 of benchmark has not been run or failed to run + F32 getGPUMemoryBandwidth() { return mGPUMemoryBandwidth; } + bool isGPUSupported() { return mGPUSupported; } + F32 getExpectedGLVersion() { return mExpectedGLVersion; } + + void cleanupFeatureTables(); + + S32 getVersion() const { return mTableVersion; } + void setSafe(const bool safe) { mSafe = safe; } + bool isSafe() const { return mSafe; } + + LLFeatureList *findMask(const std::string& name); + bool maskFeatures(const std::string& name); + + // set the graphics to low, medium, high, or ultra. + // skipFeatures forces skipping of mostly hardware settings + // that we don't want to change when we change graphics + // settings + void setGraphicsLevel(U32 level, bool skipFeatures); + + // What 'level' values are valid to pass to setGraphicsLevel()? + // 0 is the low end... + U32 getMaxGraphicsLevel() const; + bool isValidGraphicsLevel(U32 level) const; + + // setGraphicsLevel() levels have names. + std::string getNameForGraphicsLevel(U32 level) const; + // returns -1 for unrecognized name (hence S32 rather than U32) + S32 getGraphicsLevelForName(const std::string& name) const; + + void applyBaseMasks(); + void applyRecommendedSettings(); + + // apply the basic masks. Also, skip one saved + // in the skip list if true + void applyFeatures(bool skipFeatures); + + LLSD getRecommendedSettingsMap(); + +protected: + bool loadGPUClass(); + + bool parseFeatureTable(std::string filename); + ///< @returns true is file parsed correctly, false if not + + void initBaseMask(); + + std::map mMaskList; + std::set mSkippedFeatures; + bool mInited; + S32 mTableVersion; + bool mSafe; // Reinitialize everything to the "safe" mask + EGPUClass mGPUClass; + F32 mGPUMemoryBandwidth = 0.f; // measured memory bandwidth of GPU in GB/second + F32 mExpectedGLVersion; //expected GL version according to gpu table + std::string mGPUString; + bool mGPUSupported; +}; + +inline +LLFeatureManager::LLFeatureManager() +: LLFeatureList("default"), + + mInited(false), + mTableVersion(0), + mSafe(false), + mGPUClass(GPU_CLASS_UNKNOWN), + mExpectedGLVersion(0.f), + mGPUSupported(false) +{ +} + +#endif // LL_LLFEATUREMANAGER_H diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp index 2916dfa7df..b6ce31423f 100644 --- a/indra/newview/llfilepicker.cpp +++ b/indra/newview/llfilepicker.cpp @@ -1,1689 +1,1689 @@ -/** - * @file llfilepicker.cpp - * @brief OS-specific file picker - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfilepicker.h" -#include "llworld.h" -#include "llviewerwindow.h" -#include "llkeyboard.h" -#include "lldir.h" -#include "llframetimer.h" -#include "lltrans.h" -#include "llviewercontrol.h" -#include "llwindow.h" // beforeDialog() - -#if LL_SDL -#include "llwindowsdl.h" // for some X/GTK utils to help with filepickers -#endif // LL_SDL - -#if LL_LINUX -#include "llhttpconstants.h" // file picker uses some of thes constants on Linux -#endif - -// -// Globals -// - -LLFilePicker LLFilePicker::sInstance; - -#if LL_WINDOWS -#define SOUND_FILTER L"Sounds (*.wav)\0*.wav\0" -#define IMAGE_FILTER L"Images (*.tga; *.bmp; *.jpg; *.jpeg; *.png)\0*.tga;*.bmp;*.jpg;*.jpeg;*.png\0" -#define ANIM_FILTER L"Animations (*.bvh; *.anim)\0*.bvh;*.anim\0" -#define COLLADA_FILTER L"Scene (*.dae)\0*.dae\0" -#define GLTF_FILTER L"glTF (*.gltf; *.glb)\0*.gltf;*.glb\0" -#define XML_FILTER L"XML files (*.xml)\0*.xml\0" -#define SLOBJECT_FILTER L"Objects (*.slobject)\0*.slobject\0" -#define RAW_FILTER L"RAW files (*.raw)\0*.raw\0" -#define MODEL_FILTER L"Model files (*.dae)\0*.dae\0" -#define MATERIAL_FILTER L"GLTF Files (*.gltf; *.glb)\0*.gltf;*.glb\0" -#define MATERIAL_TEXTURES_FILTER L"GLTF Import (*.gltf; *.glb; *.tga; *.bmp; *.jpg; *.jpeg; *.png)\0*.gltf;*.glb;*.tga;*.bmp;*.jpg;*.jpeg;*.png\0" -#define SCRIPT_FILTER L"Script files (*.lsl)\0*.lsl\0" -#define DICTIONARY_FILTER L"Dictionary files (*.dic; *.xcu)\0*.dic;*.xcu\0" -#endif - -#ifdef LL_DARWIN -#include "llfilepicker_mac.h" -//#include -#endif - -// -// Implementation -// -LLFilePicker::LLFilePicker() - : mCurrentFile(0), - mLocked(false) - -{ - reset(); - -#if LL_WINDOWS - mOFN.lStructSize = sizeof(OPENFILENAMEW); - mOFN.hwndOwner = NULL; // Set later - mOFN.hInstance = NULL; - mOFN.lpstrCustomFilter = NULL; - mOFN.nMaxCustFilter = 0; - mOFN.lpstrFile = NULL; // set in open and close - mOFN.nMaxFile = LL_MAX_PATH; - mOFN.lpstrFileTitle = NULL; - mOFN.nMaxFileTitle = 0; - mOFN.lpstrInitialDir = NULL; - mOFN.lpstrTitle = NULL; - mOFN.Flags = 0; // set in open and close - mOFN.nFileOffset = 0; - mOFN.nFileExtension = 0; - mOFN.lpstrDefExt = NULL; - mOFN.lCustData = 0L; - mOFN.lpfnHook = NULL; - mOFN.lpTemplateName = NULL; - mFilesW[0] = '\0'; -#elif LL_DARWIN - mPickOptions = 0; -#endif - -} - -LLFilePicker::~LLFilePicker() -{ - // nothing -} - -// utility function to check if access to local file system via file browser -// is enabled and if not, tidy up and indicate we're not allowed to do this. -bool LLFilePicker::check_local_file_access_enabled() -{ - // if local file browsing is turned off, return without opening dialog - bool local_file_system_browsing_enabled = gSavedSettings.getBOOL("LocalFileSystemBrowsingEnabled"); - if ( ! local_file_system_browsing_enabled ) - { - mFiles.clear(); - return false; - } - - return true; -} - -const std::string LLFilePicker::getFirstFile() -{ - mCurrentFile = 0; - return getNextFile(); -} - -const std::string LLFilePicker::getNextFile() -{ - if (mCurrentFile >= getFileCount()) - { - mLocked = false; - return std::string(); - } - else - { - return mFiles[mCurrentFile++]; - } -} - -const std::string LLFilePicker::getCurFile() -{ - if (mCurrentFile >= getFileCount()) - { - mLocked = false; - return std::string(); - } - else - { - return mFiles[mCurrentFile]; - } -} - -void LLFilePicker::reset() -{ - mLocked = false; - mFiles.clear(); - mCurrentFile = 0; -} - -#if LL_WINDOWS - -bool LLFilePicker::setupFilter(ELoadFilter filter) -{ - bool res = true; - switch (filter) - { - case FFLOAD_ALL: - case FFLOAD_EXE: - mOFN.lpstrFilter = L"All Files (*.*)\0*.*\0" \ - SOUND_FILTER \ - IMAGE_FILTER \ - ANIM_FILTER \ - MATERIAL_FILTER \ - L"\0"; - break; - case FFLOAD_WAV: - mOFN.lpstrFilter = SOUND_FILTER \ - L"\0"; - break; - case FFLOAD_IMAGE: - mOFN.lpstrFilter = IMAGE_FILTER \ - L"\0"; - break; - case FFLOAD_ANIM: - mOFN.lpstrFilter = ANIM_FILTER \ - L"\0"; - break; - case FFLOAD_GLTF: - mOFN.lpstrFilter = GLTF_FILTER \ - L"\0"; - break; - case FFLOAD_COLLADA: - mOFN.lpstrFilter = COLLADA_FILTER \ - L"\0"; - break; - case FFLOAD_XML: - mOFN.lpstrFilter = XML_FILTER \ - L"\0"; - break; - case FFLOAD_SLOBJECT: - mOFN.lpstrFilter = SLOBJECT_FILTER \ - L"\0"; - break; - case FFLOAD_RAW: - mOFN.lpstrFilter = RAW_FILTER \ - L"\0"; - break; - case FFLOAD_MODEL: - mOFN.lpstrFilter = MODEL_FILTER \ - L"\0"; - break; - case FFLOAD_MATERIAL: - mOFN.lpstrFilter = MATERIAL_FILTER \ - L"\0"; - break; - case FFLOAD_MATERIAL_TEXTURE: - mOFN.lpstrFilter = MATERIAL_TEXTURES_FILTER \ - MATERIAL_FILTER \ - IMAGE_FILTER \ - L"\0"; - break; - case FFLOAD_SCRIPT: - mOFN.lpstrFilter = SCRIPT_FILTER \ - L"\0"; - break; - case FFLOAD_DICTIONARY: - mOFN.lpstrFilter = DICTIONARY_FILTER \ - L"\0"; - break; - default: - res = false; - break; - } - return res; -} - -bool LLFilePicker::getOpenFile(ELoadFilter filter, bool blocking) -{ - if (mLocked) - { - return false; - } - bool success = false; - - // if local file browsing is turned off, return without opening dialog - if (!check_local_file_access_enabled()) - { - return false; - } - - // don't provide default file selection - mFilesW[0] = '\0'; - - mOFN.hwndOwner = (HWND)gViewerWindow->getPlatformWindow(); - mOFN.lpstrFile = mFilesW; - mOFN.nMaxFile = SINGLE_FILENAME_BUFFER_SIZE; - mOFN.Flags = OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR ; - mOFN.nFilterIndex = 1; - - setupFilter(filter); - - if (blocking) - { - // Modal, so pause agent - send_agent_pause(); - } - - reset(); - - // NOTA BENE: hitting the file dialog triggers a window focus event, destroying the selection manager!! - success = GetOpenFileName(&mOFN); - if (success) - { - std::string filename = utf16str_to_utf8str(llutf16string(mFilesW)); - mFiles.push_back(filename); - } - - if (blocking) - { - send_agent_resume(); - // Account for the fact that the app has been stalled. - LLFrameTimer::updateFrameTime(); - } - - return success; -} - -bool LLFilePicker::getOpenFileModeless(ELoadFilter filter, - void (*callback)(bool, std::vector &, void*), - void *userdata) -{ - // not supposed to be used yet, use LLFilePickerThread - LL_ERRS() << "NOT IMPLEMENTED" << LL_ENDL; - return false; -} - -bool LLFilePicker::getMultipleOpenFiles(ELoadFilter filter, bool blocking) -{ - if( mLocked ) - { - return false; - } - bool success = false; - - // if local file browsing is turned off, return without opening dialog - if (!check_local_file_access_enabled()) - { - return false; - } - - // don't provide default file selection - mFilesW[0] = '\0'; - - mOFN.hwndOwner = (HWND)gViewerWindow->getPlatformWindow(); - mOFN.lpstrFile = mFilesW; - mOFN.nFilterIndex = 1; - mOFN.nMaxFile = FILENAME_BUFFER_SIZE; - mOFN.Flags = OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR | - OFN_EXPLORER | OFN_ALLOWMULTISELECT; - - setupFilter(filter); - - reset(); - - if (blocking) - { - // Modal, so pause agent - send_agent_pause(); - } - - // NOTA BENE: hitting the file dialog triggers a window focus event, destroying the selection manager!! - success = GetOpenFileName(&mOFN); // pauses until ok or cancel. - if( success ) - { - // The getopenfilename api doesn't tell us if we got more than - // one file, so we have to test manually by checking string - // lengths. - if( wcslen(mOFN.lpstrFile) > mOFN.nFileOffset ) /*Flawfinder: ignore*/ - { - std::string filename = utf16str_to_utf8str(llutf16string(mFilesW)); - mFiles.push_back(filename); - } - else - { - mLocked = true; - WCHAR* tptrw = mFilesW; - std::string dirname; - while(1) - { - if (*tptrw == 0 && *(tptrw+1) == 0) // double '\0' - break; - if (*tptrw == 0) - tptrw++; // shouldn't happen? - std::string filename = utf16str_to_utf8str(llutf16string(tptrw)); - if (dirname.empty()) - dirname = filename + "\\"; - else - mFiles.push_back(dirname + filename); - tptrw += wcslen(tptrw); - } - } - } - - if (blocking) - { - send_agent_resume(); - } - - // Account for the fact that the app has been stalled. - LLFrameTimer::updateFrameTime(); - return success; -} - -bool LLFilePicker::getMultipleOpenFilesModeless(ELoadFilter filter, - void (*callback)(bool, std::vector &, void*), - void *userdata ) -{ - // not supposed to be used yet, use LLFilePickerThread - LL_ERRS() << "NOT IMPLEMENTED" << LL_ENDL; - return false; -} - -bool LLFilePicker::getSaveFile(ESaveFilter filter, const std::string& filename, bool blocking) -{ - if( mLocked ) - { - return false; - } - bool success = false; - - // if local file browsing is turned off, return without opening dialog - if (!check_local_file_access_enabled()) - { - return false; - } - - mOFN.lpstrFile = mFilesW; - if (!filename.empty()) - { - llutf16string tstring = utf8str_to_utf16str(filename); - wcsncpy(mFilesW, tstring.c_str(), FILENAME_BUFFER_SIZE); } /*Flawfinder: ignore*/ - else - { - mFilesW[0] = '\0'; - } - mOFN.hwndOwner = (HWND)gViewerWindow->getPlatformWindow(); - - switch( filter ) - { - case FFSAVE_ALL: - mOFN.lpstrDefExt = NULL; - mOFN.lpstrFilter = - L"All Files (*.*)\0*.*\0" \ - L"WAV Sounds (*.wav)\0*.wav\0" \ - L"Targa, Bitmap Images (*.tga; *.bmp)\0*.tga;*.bmp\0" \ - L"\0"; - break; - case FFSAVE_WAV: - if (filename.empty()) - { - wcsncpy( mFilesW,L"untitled.wav", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ - } - mOFN.lpstrDefExt = L"wav"; - mOFN.lpstrFilter = - L"WAV Sounds (*.wav)\0*.wav\0" \ - L"\0"; - break; - case FFSAVE_TGA: - if (filename.empty()) - { - wcsncpy( mFilesW,L"untitled.tga", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ - } - mOFN.lpstrDefExt = L"tga"; - mOFN.lpstrFilter = - L"Targa Images (*.tga)\0*.tga\0" \ - L"\0"; - break; - case FFSAVE_BMP: - if (filename.empty()) - { - wcsncpy( mFilesW,L"untitled.bmp", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ - } - mOFN.lpstrDefExt = L"bmp"; - mOFN.lpstrFilter = - L"Bitmap Images (*.bmp)\0*.bmp\0" \ - L"\0"; - break; - case FFSAVE_PNG: - if (filename.empty()) - { - wcsncpy( mFilesW,L"untitled.png", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ - } - mOFN.lpstrDefExt = L"png"; - mOFN.lpstrFilter = - L"PNG Images (*.png)\0*.png\0" \ - L"\0"; - break; - case FFSAVE_TGAPNG: - if (filename.empty()) - { - wcsncpy( mFilesW,L"untitled.png", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ - //PNG by default - } - mOFN.lpstrDefExt = L"png"; - mOFN.lpstrFilter = - L"PNG Images (*.png)\0*.png\0" \ - L"Targa Images (*.tga)\0*.tga\0" \ - L"\0"; - break; - - case FFSAVE_JPEG: - if (filename.empty()) - { - wcsncpy( mFilesW,L"untitled.jpeg", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ - } - mOFN.lpstrDefExt = L"jpg"; - mOFN.lpstrFilter = - L"JPEG Images (*.jpg *.jpeg)\0*.jpg;*.jpeg\0" \ - L"\0"; - break; - case FFSAVE_AVI: - if (filename.empty()) - { - wcsncpy( mFilesW,L"untitled.avi", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ - } - mOFN.lpstrDefExt = L"avi"; - mOFN.lpstrFilter = - L"AVI Movie File (*.avi)\0*.avi\0" \ - L"\0"; - break; - case FFSAVE_ANIM: - if (filename.empty()) - { - wcsncpy( mFilesW,L"untitled.xaf", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ - } - mOFN.lpstrDefExt = L"xaf"; - mOFN.lpstrFilter = - L"XAF Anim File (*.xaf)\0*.xaf\0" \ - L"\0"; - break; - case FFSAVE_GLTF: - if (filename.empty()) - { - wcsncpy( mFilesW,L"untitled.glb", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ - } - mOFN.lpstrDefExt = L"glb"; - mOFN.lpstrFilter = - L"glTF Asset File (*.gltf *.glb)\0*.gltf;*.glb\0" \ - L"\0"; - break; - case FFSAVE_XML: - if (filename.empty()) - { - wcsncpy( mFilesW,L"untitled.xml", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ - } - - mOFN.lpstrDefExt = L"xml"; - mOFN.lpstrFilter = - L"XML File (*.xml)\0*.xml\0" \ - L"\0"; - break; - case FFSAVE_COLLADA: - if (filename.empty()) - { - wcsncpy( mFilesW,L"untitled.collada", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ - } - mOFN.lpstrDefExt = L"collada"; - mOFN.lpstrFilter = - L"COLLADA File (*.collada)\0*.collada\0" \ - L"\0"; - break; - case FFSAVE_RAW: - if (filename.empty()) - { - wcsncpy( mFilesW,L"untitled.raw", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ - } - mOFN.lpstrDefExt = L"raw"; - mOFN.lpstrFilter = RAW_FILTER \ - L"\0"; - break; - case FFSAVE_J2C: - if (filename.empty()) - { - wcsncpy( mFilesW,L"untitled.j2c", FILENAME_BUFFER_SIZE); - } - mOFN.lpstrDefExt = L"j2c"; - mOFN.lpstrFilter = - L"Compressed Images (*.j2c)\0*.j2c\0" \ - L"\0"; - break; - case FFSAVE_SCRIPT: - if (filename.empty()) - { - wcsncpy( mFilesW,L"untitled.lsl", FILENAME_BUFFER_SIZE); - } - mOFN.lpstrDefExt = L"txt"; - mOFN.lpstrFilter = L"LSL Files (*.lsl)\0*.lsl\0" L"\0"; - break; - default: - return false; - } - - - mOFN.nMaxFile = SINGLE_FILENAME_BUFFER_SIZE; - mOFN.Flags = OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST; - - reset(); - - if (blocking) - { - // Modal, so pause agent - send_agent_pause(); - } - - { - // NOTA BENE: hitting the file dialog triggers a window focus event, destroying the selection manager!! - try - { - success = GetSaveFileName(&mOFN); - if (success) - { - std::string filename = utf16str_to_utf8str(llutf16string(mFilesW)); - mFiles.push_back(filename); - } - } - catch (...) - { - LOG_UNHANDLED_EXCEPTION(""); - } - gKeyboard->resetKeys(); - } - - if (blocking) - { - send_agent_resume(); - } - - // Account for the fact that the app has been stalled. - LLFrameTimer::updateFrameTime(); - return success; -} - -bool LLFilePicker::getSaveFileModeless(ESaveFilter filter, - const std::string& filename, - void (*callback)(bool, std::string&, void*), - void *userdata) -{ - // not supposed to be used yet, use LLFilePickerThread - LL_ERRS() << "NOT IMPLEMENTED" << LL_ENDL; - return false; -} - -#elif LL_DARWIN - -std::unique_ptr> LLFilePicker::navOpenFilterProc(ELoadFilter filter) //(AEDesc *theItem, void *info, void *callBackUD, NavFilterModes filterMode) -{ - std::unique_ptr> allowedv(new std::vector< std::string >); - switch(filter) - { - case FFLOAD_ALL: - case FFLOAD_EXE: - allowedv->push_back("app"); - allowedv->push_back("exe"); - allowedv->push_back("wav"); - allowedv->push_back("bvh"); - allowedv->push_back("anim"); - allowedv->push_back("dae"); - allowedv->push_back("raw"); - allowedv->push_back("lsl"); - allowedv->push_back("dic"); - allowedv->push_back("xcu"); - allowedv->push_back("gif"); - allowedv->push_back("gltf"); - allowedv->push_back("glb"); - case FFLOAD_IMAGE: - allowedv->push_back("jpg"); - allowedv->push_back("jpeg"); - allowedv->push_back("bmp"); - allowedv->push_back("tga"); - allowedv->push_back("bmpf"); - allowedv->push_back("tpic"); - allowedv->push_back("png"); - break; - break; - case FFLOAD_WAV: - allowedv->push_back("wav"); - break; - case FFLOAD_ANIM: - allowedv->push_back("bvh"); - allowedv->push_back("anim"); - break; - case FFLOAD_GLTF: - case FFLOAD_MATERIAL: - allowedv->push_back("gltf"); - allowedv->push_back("glb"); - break; - case FFLOAD_COLLADA: - allowedv->push_back("dae"); - break; - case FFLOAD_XML: - allowedv->push_back("xml"); - break; - case FFLOAD_RAW: - allowedv->push_back("raw"); - break; - case FFLOAD_SCRIPT: - allowedv->push_back("lsl"); - break; - case FFLOAD_DICTIONARY: - allowedv->push_back("dic"); - allowedv->push_back("xcu"); - break; - case FFLOAD_DIRECTORY: - break; - default: - LL_WARNS() << "Unsupported format." << LL_ENDL; - } - - return allowedv; -} - -bool LLFilePicker::doNavChooseDialog(ELoadFilter filter) -{ - // if local file browsing is turned off, return without opening dialog - if (!check_local_file_access_enabled()) - { - return false; - } - - gViewerWindow->getWindow()->beforeDialog(); - - std::unique_ptr> allowed_types = navOpenFilterProc(filter); - - std::unique_ptr> filev = doLoadDialog(allowed_types.get(), - mPickOptions); - - gViewerWindow->getWindow()->afterDialog(); - - - if (filev && filev->size() > 0) - { - mFiles.insert(mFiles.end(), filev->begin(), filev->end()); - return true; - } - - return false; -} - -bool LLFilePicker::doNavChooseDialogModeless(ELoadFilter filter, - void (*callback)(bool, std::vector &,void*), - void *userdata) -{ - // if local file browsing is turned off, return without opening dialog - if (!check_local_file_access_enabled()) - { - return false; - } - - std::unique_ptr> allowed_types=navOpenFilterProc(filter); - - doLoadDialogModeless(allowed_types.get(), - mPickOptions, - callback, - userdata); - - return true; -} - -void set_nav_save_data(LLFilePicker::ESaveFilter filter, std::string &extension, std::string &type, std::string &creator) -{ - switch (filter) - { - case LLFilePicker::FFSAVE_WAV: - type = "WAVE"; - creator = "TVOD"; - extension = "wav"; - break; - case LLFilePicker::FFSAVE_TGA: - type = "TPIC"; - creator = "prvw"; - extension = "tga"; - break; - case LLFilePicker::FFSAVE_TGAPNG: - type = "PNG"; - creator = "prvw"; - extension = "png,tga"; - break; - case LLFilePicker::FFSAVE_BMP: - type = "BMPf"; - creator = "prvw"; - extension = "bmp"; - break; - case LLFilePicker::FFSAVE_JPEG: - type = "JPEG"; - creator = "prvw"; - extension = "jpeg"; - break; - case LLFilePicker::FFSAVE_PNG: - type = "PNG "; - creator = "prvw"; - extension = "png"; - break; - case LLFilePicker::FFSAVE_AVI: - type = "\?\?\?\?"; - creator = "\?\?\?\?"; - extension = "mov"; - break; - - case LLFilePicker::FFSAVE_ANIM: - type = "\?\?\?\?"; - creator = "\?\?\?\?"; - extension = "xaf"; - break; - case LLFilePicker::FFSAVE_GLTF: - type = "\?\?\?\?"; - creator = "\?\?\?\?"; - extension = "glb"; - break; - - case LLFilePicker::FFSAVE_XML: - type = "\?\?\?\?"; - creator = "\?\?\?\?"; - extension = "xml"; - break; - - case LLFilePicker::FFSAVE_RAW: - type = "\?\?\?\?"; - creator = "\?\?\?\?"; - extension = "raw"; - break; - - case LLFilePicker::FFSAVE_J2C: - type = "\?\?\?\?"; - creator = "prvw"; - extension = "j2c"; - break; - - case LLFilePicker::FFSAVE_SCRIPT: - type = "LSL "; - creator = "\?\?\?\?"; - extension = "lsl"; - break; - - case LLFilePicker::FFSAVE_ALL: - default: - type = "\?\?\?\?"; - creator = "\?\?\?\?"; - extension = ""; - break; - } -} - -bool LLFilePicker::doNavSaveDialog(ESaveFilter filter, const std::string& filename) -{ - // Setup the type, creator, and extension - std::string extension, type, creator; - - set_nav_save_data(filter, extension, type, creator); - - std::string namestring = filename; - if (namestring.empty()) namestring="Untitled"; - - gViewerWindow->getWindow()->beforeDialog(); - - // Run the dialog - std::unique_ptr filev = doSaveDialog(&namestring, - &type, - &creator, - &extension, - mPickOptions); - - gViewerWindow->getWindow()->afterDialog(); - - if ( filev && !filev->empty() ) - { - mFiles.push_back(*filev); - return true; - } - - return false; -} - -bool LLFilePicker::doNavSaveDialogModeless(ESaveFilter filter, - const std::string& filename, - void (*callback)(bool, std::string&, void*), - void *userdata) -{ - // Setup the type, creator, and extension - std::string extension, type, creator; - - set_nav_save_data(filter, extension, type, creator); - - std::string namestring = filename; - if (namestring.empty()) namestring="Untitled"; - - // Run the dialog - doSaveDialogModeless(&namestring, - &type, - &creator, - &extension, - mPickOptions, - callback, - userdata); - return true; -} - -bool LLFilePicker::getOpenFile(ELoadFilter filter, bool blocking) -{ - if( mLocked ) - return false; - - bool success = false; - - // if local file browsing is turned off, return without opening dialog - if (!check_local_file_access_enabled()) - { - return false; - } - - reset(); - - mPickOptions &= ~F_MULTIPLE; - mPickOptions |= F_FILE; - - if (filter == FFLOAD_DIRECTORY) //This should only be called from lldirpicker. - { - mPickOptions |= ( F_NAV_SUPPORT | F_DIRECTORY ); - mPickOptions &= ~F_FILE; - } - - if (filter == FFLOAD_ALL) // allow application bundles etc. to be traversed; important for DEV-16869, but generally useful - { - mPickOptions |= F_NAV_SUPPORT; - } - - if (blocking) // always true for linux/mac - { - // Modal, so pause agent - send_agent_pause(); - } - - - success = doNavChooseDialog(filter); - - if (success) - { - if (!getFileCount()) - success = false; - } - - if (blocking) - { - send_agent_resume(); - // Account for the fact that the app has been stalled. - LLFrameTimer::updateFrameTime(); - } - - return success; -} - - -bool LLFilePicker::getOpenFileModeless(ELoadFilter filter, - void (*callback)(bool, std::vector &, void*), - void *userdata) -{ - if (mLocked) - return false; - - // if local file browsing is turned off, return without opening dialog - if (!check_local_file_access_enabled()) - { - return false; - } - - reset(); - - mPickOptions &= ~F_MULTIPLE; - mPickOptions |= F_FILE; - - if (filter == FFLOAD_DIRECTORY) //This should only be called from lldirpicker. - { - - mPickOptions |= ( F_NAV_SUPPORT | F_DIRECTORY ); - mPickOptions &= ~F_FILE; - } - - if (filter == FFLOAD_ALL) // allow application bundles etc. to be traversed; important for DEV-16869, but generally useful - { - mPickOptions |= F_NAV_SUPPORT; - } - - return doNavChooseDialogModeless(filter, callback, userdata); -} - -bool LLFilePicker::getMultipleOpenFiles(ELoadFilter filter, bool blocking) -{ - if (mLocked) - return false; - - // if local file browsing is turned off, return without opening dialog - if (!check_local_file_access_enabled()) - { - return false; - } - - bool success = false; - - reset(); - - mPickOptions |= F_FILE; - - mPickOptions |= F_MULTIPLE; - - if (blocking) // always true for linux/mac - { - // Modal, so pause agent - send_agent_pause(); - } - - success = doNavChooseDialog(filter); - - if (blocking) - { - send_agent_resume(); - } - - if (success) - { - if (!getFileCount()) - success = false; - if (getFileCount() > 1) - mLocked = true; - } - - // Account for the fact that the app has been stalled. - LLFrameTimer::updateFrameTime(); - return success; -} - - -bool LLFilePicker::getMultipleOpenFilesModeless(ELoadFilter filter, - void (*callback)(bool, std::vector &, void*), - void *userdata ) -{ - if (mLocked) - return false; - - // if local file browsing is turned off, return without opening dialog - if (!check_local_file_access_enabled()) - { - return false; - } - - reset(); - - mPickOptions |= F_FILE; - - mPickOptions |= F_MULTIPLE; - - return doNavChooseDialogModeless(filter, callback, userdata); -} - -bool LLFilePicker::getSaveFile(ESaveFilter filter, const std::string& filename, bool blocking) -{ - - if (mLocked) - return false; - - bool success = false; - - // if local file browsing is turned off, return without opening dialog - if (!check_local_file_access_enabled()) - { - return false; - } - - reset(); - - mPickOptions &= ~F_MULTIPLE; - - if (blocking) - { - // Modal, so pause agent - send_agent_pause(); - } - - success = doNavSaveDialog(filter, filename); - - if (success) - { - if (!getFileCount()) - success = false; - } - - if (blocking) - { - send_agent_resume(); - } - - // Account for the fact that the app has been stalled. - LLFrameTimer::updateFrameTime(); - return success; -} - -bool LLFilePicker::getSaveFileModeless(ESaveFilter filter, - const std::string& filename, - void (*callback)(bool, std::string&, void*), - void *userdata) -{ - if (mLocked) - return false; - - // if local file browsing is turned off, return without opening dialog - if (!check_local_file_access_enabled()) - { - return false; - } - - reset(); - - mPickOptions &= ~F_MULTIPLE; - - return doNavSaveDialogModeless(filter, filename, callback, userdata); -} -//END LL_DARWIN - -#elif LL_LINUX - -# if LL_GTK - -// static -void LLFilePicker::add_to_selectedfiles(gpointer data, gpointer user_data) -{ - // We need to run g_filename_to_utf8 in the user's locale - std::string saved_locale(setlocale(LC_ALL, NULL)); - setlocale(LC_ALL, ""); - - LLFilePicker* picker = (LLFilePicker*) user_data; - GError *error = NULL; - gchar* filename_utf8 = g_filename_to_utf8((gchar*)data, - -1, NULL, NULL, &error); - if (error) - { - // *FIXME. - // This condition should really be notified to the user, e.g. - // through a message box. Just logging it is inappropriate. - - // g_filename_display_name is ideal, but >= glib 2.6, so: - // a hand-rolled hacky makeASCII which disallows control chars - std::string display_name; - for (const gchar *str = (const gchar *)data; *str; str++) - { - display_name += (char)((*str >= 0x20 && *str <= 0x7E) ? *str : '?'); - } - LL_WARNS() << "g_filename_to_utf8 failed on \"" << display_name << "\": " << error->message << LL_ENDL; - } - - if (filename_utf8) - { - picker->mFiles.push_back(std::string(filename_utf8)); - LL_DEBUGS() << "ADDED FILE " << filename_utf8 << LL_ENDL; - g_free(filename_utf8); - } - - setlocale(LC_ALL, saved_locale.c_str()); -} - -// static -void LLFilePicker::chooser_responder(GtkWidget *widget, gint response, gpointer user_data) -{ - LLFilePicker* picker = (LLFilePicker*)user_data; - - LL_DEBUGS() << "GTK DIALOG RESPONSE " << response << LL_ENDL; - - if (response == GTK_RESPONSE_ACCEPT) - { - GSList *file_list = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(widget)); - g_slist_foreach(file_list, (GFunc)add_to_selectedfiles, user_data); - g_slist_foreach(file_list, (GFunc)g_free, NULL); - g_slist_free (file_list); - } - - // let's save the extension of the last added file(considering current filter) - GtkFileFilter *gfilter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(widget)); - if(gfilter) - { - std::string filter = gtk_file_filter_get_name(gfilter); - - if(filter == LLTrans::getString("png_image_files")) - { - picker->mCurrentExtension = ".png"; - } - else if(filter == LLTrans::getString("targa_image_files")) - { - picker->mCurrentExtension = ".tga"; - } - } - - // set the default path for this usage context. - const char* cur_folder = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(widget)); - if (cur_folder != NULL) - { - picker->mContextToPathMap[picker->mCurContextName] = cur_folder; - } - - gtk_widget_destroy(widget); - gtk_main_quit(); -} - - -GtkWindow* LLFilePicker::buildFilePicker(bool is_save, bool is_folder, std::string context) -{ -#ifndef LL_MESA_HEADLESS - if (LLWindowSDL::ll_try_gtk_init()) - { - GtkWidget *win = NULL; - GtkFileChooserAction pickertype = - is_save? - (is_folder? - GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER : - GTK_FILE_CHOOSER_ACTION_SAVE) : - (is_folder? - GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER : - GTK_FILE_CHOOSER_ACTION_OPEN); - - win = gtk_file_chooser_dialog_new(NULL, NULL, - pickertype, - GTK_STOCK_CANCEL, - GTK_RESPONSE_CANCEL, - is_folder ? - GTK_STOCK_APPLY : - (is_save ? - GTK_STOCK_SAVE : - GTK_STOCK_OPEN), - GTK_RESPONSE_ACCEPT, - (gchar *)NULL); - mCurContextName = context; - - // get the default path for this usage context if it's been - // seen before. - std::map::iterator - this_path = mContextToPathMap.find(context); - if (this_path != mContextToPathMap.end()) - { - gtk_file_chooser_set_current_folder - (GTK_FILE_CHOOSER(win), - this_path->second.c_str()); - } - -# if LL_X11 - // Make GTK tell the window manager to associate this - // dialog with our non-GTK raw X11 window, which should try - // to keep it on top etc. - Window XWindowID = LLWindowSDL::get_SDL_XWindowID(); - if (None != XWindowID) - { - gtk_widget_realize(GTK_WIDGET(win)); // so we can get its gdkwin - GdkWindow *gdkwin = gdk_window_foreign_new(XWindowID); - gdk_window_set_transient_for(GTK_WIDGET(win)->window, - gdkwin); - } - else - { - LL_WARNS() << "Hmm, couldn't get xwid to use for transient." << LL_ENDL; - } -# endif //LL_X11 - - g_signal_connect (GTK_FILE_CHOOSER(win), - "response", - G_CALLBACK(LLFilePicker::chooser_responder), - this); - - gtk_window_set_modal(GTK_WINDOW(win), TRUE); - - /* GTK 2.6: if (is_folder) - gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(win), - TRUE); */ - - return GTK_WINDOW(win); - } - else - { - return NULL; - } -#else - return NULL; -#endif //LL_MESA_HEADLESS -} - -static void add_common_filters_to_gtkchooser(GtkFileFilter *gfilter, - GtkWindow *picker, - std::string filtername) -{ - gtk_file_filter_set_name(gfilter, filtername.c_str()); - gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(picker), - gfilter); - GtkFileFilter *allfilter = gtk_file_filter_new(); - gtk_file_filter_add_pattern(allfilter, "*"); - gtk_file_filter_set_name(allfilter, LLTrans::getString("all_files").c_str()); - gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(picker), allfilter); - gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(picker), gfilter); -} - -static std::string add_simple_pattern_filter_to_gtkchooser(GtkWindow *picker, - std::string pattern, - std::string filtername) -{ - GtkFileFilter *gfilter = gtk_file_filter_new(); - gtk_file_filter_add_pattern(gfilter, pattern.c_str()); - add_common_filters_to_gtkchooser(gfilter, picker, filtername); - return filtername; -} - -static std::string add_simple_mime_filter_to_gtkchooser(GtkWindow *picker, - std::string mime, - std::string filtername) -{ - GtkFileFilter *gfilter = gtk_file_filter_new(); - gtk_file_filter_add_mime_type(gfilter, mime.c_str()); - add_common_filters_to_gtkchooser(gfilter, picker, filtername); - return filtername; -} - -static std::string add_wav_filter_to_gtkchooser(GtkWindow *picker) -{ - return add_simple_mime_filter_to_gtkchooser(picker, "audio/x-wav", - LLTrans::getString("sound_files") + " (*.wav)"); -} - -static std::string add_anim_filter_to_gtkchooser(GtkWindow *picker) -{ - GtkFileFilter *gfilter = gtk_file_filter_new(); - gtk_file_filter_add_pattern(gfilter, "*.bvh"); - gtk_file_filter_add_pattern(gfilter, "*.anim"); - std::string filtername = LLTrans::getString("animation_files") + " (*.bvh; *.anim)"; - add_common_filters_to_gtkchooser(gfilter, picker, filtername); - return filtername; -} - -static std::string add_xml_filter_to_gtkchooser(GtkWindow *picker) -{ - return add_simple_pattern_filter_to_gtkchooser(picker, "*.xml", - LLTrans::getString("xml_files") + " (*.xml)"); -} - -static std::string add_collada_filter_to_gtkchooser(GtkWindow *picker) -{ - return add_simple_pattern_filter_to_gtkchooser(picker, "*.dae", - LLTrans::getString("scene_files") + " (*.dae)"); -} - -static std::string add_imageload_filter_to_gtkchooser(GtkWindow *picker) -{ - GtkFileFilter *gfilter = gtk_file_filter_new(); - gtk_file_filter_add_pattern(gfilter, "*.tga"); - gtk_file_filter_add_mime_type(gfilter, HTTP_CONTENT_IMAGE_JPEG.c_str()); - gtk_file_filter_add_mime_type(gfilter, HTTP_CONTENT_IMAGE_PNG.c_str()); - gtk_file_filter_add_mime_type(gfilter, HTTP_CONTENT_IMAGE_BMP.c_str()); - std::string filtername = LLTrans::getString("image_files") + " (*.tga; *.bmp; *.jpg; *.png)"; - add_common_filters_to_gtkchooser(gfilter, picker, filtername); - return filtername; -} - -static std::string add_script_filter_to_gtkchooser(GtkWindow *picker) -{ - return add_simple_mime_filter_to_gtkchooser(picker, HTTP_CONTENT_TEXT_PLAIN, - LLTrans::getString("script_files") + " (*.lsl)"); -} - -static std::string add_dictionary_filter_to_gtkchooser(GtkWindow *picker) -{ - return add_simple_mime_filter_to_gtkchooser(picker, HTTP_CONTENT_TEXT_PLAIN, - LLTrans::getString("dictionary_files") + " (*.dic; *.xcu)"); -} - -static std::string add_save_texture_filter_to_gtkchooser(GtkWindow *picker) -{ - GtkFileFilter *gfilter_tga = gtk_file_filter_new(); - GtkFileFilter *gfilter_png = gtk_file_filter_new(); - - gtk_file_filter_add_pattern(gfilter_tga, "*.tga"); - gtk_file_filter_add_mime_type(gfilter_png, "image/png"); - std::string caption = LLTrans::getString("save_texture_image_files") + " (*.tga; *.png)"; - gtk_file_filter_set_name(gfilter_tga, LLTrans::getString("targa_image_files").c_str()); - gtk_file_filter_set_name(gfilter_png, LLTrans::getString("png_image_files").c_str()); - - gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(picker), - gfilter_png); - gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(picker), - gfilter_tga); - return caption; -} - -bool LLFilePicker::getSaveFile( ESaveFilter filter, const std::string& filename, bool blocking ) -{ - bool rtn = false; - - // if local file browsing is turned off, return without opening dialog - if (!check_local_file_access_enabled()) - { - return false; - } - - gViewerWindow->getWindow()->beforeDialog(); - - reset(); - - GtkWindow* picker = buildFilePicker(true, false, "savefile"); - - if (picker) - { - std::string suggest_name = "untitled"; - std::string suggest_ext = ""; - std::string caption = LLTrans::getString("save_file_verb") + " "; - switch (filter) - { - case FFSAVE_WAV: - caption += add_wav_filter_to_gtkchooser(picker); - suggest_ext = ".wav"; - break; - case FFSAVE_TGA: - caption += add_simple_pattern_filter_to_gtkchooser - (picker, "*.tga", LLTrans::getString("targa_image_files") + " (*.tga)"); - suggest_ext = ".tga"; - break; - case FFSAVE_BMP: - caption += add_simple_mime_filter_to_gtkchooser - (picker, HTTP_CONTENT_IMAGE_BMP, LLTrans::getString("bitmap_image_files") + " (*.bmp)"); - suggest_ext = ".bmp"; - break; - case FFSAVE_PNG: - caption += add_simple_mime_filter_to_gtkchooser - (picker, "image/png", LLTrans::getString("png_image_files") + " (*.png)"); - suggest_ext = ".png"; - break; - case FFSAVE_TGAPNG: - caption += add_save_texture_filter_to_gtkchooser(picker); - suggest_ext = ".png"; - break; - case FFSAVE_AVI: - caption += add_simple_mime_filter_to_gtkchooser - (picker, "video/x-msvideo", - LLTrans::getString("avi_movie_file") + " (*.avi)"); - suggest_ext = ".avi"; - break; - case FFSAVE_ANIM: - caption += add_simple_pattern_filter_to_gtkchooser - (picker, "*.xaf", LLTrans::getString("xaf_animation_file") + " (*.xaf)"); - suggest_ext = ".xaf"; - break; - case FFSAVE_XML: - caption += add_simple_pattern_filter_to_gtkchooser - (picker, "*.xml", LLTrans::getString("xml_file") + " (*.xml)"); - suggest_ext = ".xml"; - break; - case FFSAVE_RAW: - caption += add_simple_pattern_filter_to_gtkchooser - (picker, "*.raw", LLTrans::getString("raw_file") + " (*.raw)"); - suggest_ext = ".raw"; - break; - case FFSAVE_J2C: - // *TODO: Should this be 'image/j2c' ? - caption += add_simple_mime_filter_to_gtkchooser - (picker, "images/jp2", - LLTrans::getString("compressed_image_files") + " (*.j2c)"); - suggest_ext = ".j2c"; - break; - case FFSAVE_SCRIPT: - caption += add_script_filter_to_gtkchooser(picker); - suggest_ext = ".lsl"; - break; - default:; - break; - } - - gtk_window_set_title(GTK_WINDOW(picker), caption.c_str()); - - if (filename.empty()) - { - suggest_name += suggest_ext; - - gtk_file_chooser_set_current_name - (GTK_FILE_CHOOSER(picker), - suggest_name.c_str()); - } - else - { - gtk_file_chooser_set_current_name - (GTK_FILE_CHOOSER(picker), filename.c_str()); - } - - gtk_widget_show_all(GTK_WIDGET(picker)); - - gtk_main(); - - rtn = (getFileCount() == 1); - - if(rtn && filter == FFSAVE_TGAPNG) - { - std::string selected_file = mFiles.back(); - mFiles.pop_back(); - mFiles.push_back(selected_file + mCurrentExtension); - } - } - - gViewerWindow->getWindow()->afterDialog(); - - return rtn; -} - -bool LLFilePicker::getOpenFile( ELoadFilter filter, bool blocking ) -{ - bool rtn = false; - - // if local file browsing is turned off, return without opening dialog - if (!check_local_file_access_enabled()) - { - return false; - } - - gViewerWindow->getWindow()->beforeDialog(); - - reset(); - - GtkWindow* picker = buildFilePicker(false, false, "openfile"); - - if (picker) - { - std::string caption = LLTrans::getString("load_file_verb") + " "; - std::string filtername = ""; - switch (filter) - { - case FFLOAD_WAV: - filtername = add_wav_filter_to_gtkchooser(picker); - break; - case FFLOAD_ANIM: - filtername = add_anim_filter_to_gtkchooser(picker); - break; - case FFLOAD_XML: - filtername = add_xml_filter_to_gtkchooser(picker); - break; - case FFLOAD_GLTF: - filtername = dead_code_should_blow_up_here(picker); - break; - case FFLOAD_COLLADA: - filtername = add_collada_filter_to_gtkchooser(picker); - break; - case FFLOAD_IMAGE: - filtername = add_imageload_filter_to_gtkchooser(picker); - break; - case FFLOAD_SCRIPT: - filtername = add_script_filter_to_gtkchooser(picker); - break; - case FFLOAD_DICTIONARY: - filtername = add_dictionary_filter_to_gtkchooser(picker); - break; - default:; - break; - } - - caption += filtername; - - gtk_window_set_title(GTK_WINDOW(picker), caption.c_str()); - - gtk_widget_show_all(GTK_WIDGET(picker)); - gtk_main(); - - rtn = (getFileCount() == 1); - } - - gViewerWindow->getWindow()->afterDialog(); - - return rtn; -} - -bool LLFilePicker::getMultipleOpenFiles( ELoadFilter filter, bool blocking) -{ - bool rtn = false; - - // if local file browsing is turned off, return without opening dialog - if (!check_local_file_access_enabled()) - { - return false; - } - - gViewerWindow->getWindow()->beforeDialog(); - - reset(); - - GtkWindow* picker = buildFilePicker(false, false, "openfile"); - - if (picker) - { - gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER(picker), - TRUE); - - gtk_window_set_title(GTK_WINDOW(picker), LLTrans::getString("load_files").c_str()); - - gtk_widget_show_all(GTK_WIDGET(picker)); - gtk_main(); - rtn = !mFiles.empty(); - } - - gViewerWindow->getWindow()->afterDialog(); - - return rtn; -} - -# else // LL_GTK - -// Hacky stubs designed to facilitate fake getSaveFile and getOpenFile with -// static results, when we don't have a real filepicker. - -bool LLFilePicker::getSaveFile( ESaveFilter filter, const std::string& filename, bool blocking ) -{ - // if local file browsing is turned off, return without opening dialog - // (Even though this is a stub, I think we still should not return anything at all) - if (!check_local_file_access_enabled()) - { - return false; - } - - reset(); - - LL_INFOS() << "getSaveFile suggested filename is [" << filename - << "]" << LL_ENDL; - if (!filename.empty()) - { - mFiles.push_back(gDirUtilp->getLindenUserDir() + gDirUtilp->getDirDelimiter() + filename); - return true; - } - return false; -} - -bool LLFilePicker::getSaveFileModeless(ESaveFilter filter, - const std::string& filename, - void (*callback)(bool, std::string&, void*), - void *userdata) -{ - LL_ERRS() << "NOT IMPLEMENTED" << LL_ENDL; - return false; -} - -bool LLFilePicker::getOpenFile( ELoadFilter filter, bool blocking ) -{ - // if local file browsing is turned off, return without opening dialog - // (Even though this is a stub, I think we still should not return anything at all) - if (!check_local_file_access_enabled()) - { - return false; - } - - reset(); - - // HACK: Static filenames for 'open' until we implement filepicker - std::string filename = gDirUtilp->getLindenUserDir() + gDirUtilp->getDirDelimiter() + "upload"; - switch (filter) - { - case FFLOAD_WAV: filename += ".wav"; break; - case FFLOAD_IMAGE: filename += ".tga"; break; - case FFLOAD_ANIM: filename += ".bvh"; break; - default: break; - } - mFiles.push_back(filename); - LL_INFOS() << "getOpenFile: Will try to open file: " << filename << LL_ENDL; - return true; -} - -bool LLFilePicker::getOpenFileModeless(ELoadFilter filter, - void (*callback)(bool, std::vector &, void*), - void *userdata) -{ - LL_ERRS() << "NOT IMPLEMENTED" << LL_ENDL; - return false; -} - -bool LLFilePicker::getMultipleOpenFiles( ELoadFilter filter, bool blocking) -{ - // if local file browsing is turned off, return without opening dialog - // (Even though this is a stub, I think we still should not return anything at all) - if (!check_local_file_access_enabled()) - { - return false; - } - - reset(); - return false; -} - -bool LLFilePicker::getMultipleOpenFilesModeless(ELoadFilter filter, - void (*callback)(bool, std::vector &, void*), - void *userdata ) -{ - LL_ERRS() << "NOT IMPLEMENTED" << LL_ENDL; - return false; -} - -#endif // LL_GTK - -#else // not implemented - -bool LLFilePicker::getSaveFile( ESaveFilter filter, const std::string& filename ) -{ - reset(); - return false; -} - -bool LLFilePicker::getOpenFile( ELoadFilter filter ) -{ - reset(); - return false; -} - -bool LLFilePicker::getMultipleOpenFiles( ELoadFilter filter, bool blocking) -{ - reset(); - return false; -} - -#endif // LL_LINUX +/** + * @file llfilepicker.cpp + * @brief OS-specific file picker + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfilepicker.h" +#include "llworld.h" +#include "llviewerwindow.h" +#include "llkeyboard.h" +#include "lldir.h" +#include "llframetimer.h" +#include "lltrans.h" +#include "llviewercontrol.h" +#include "llwindow.h" // beforeDialog() + +#if LL_SDL +#include "llwindowsdl.h" // for some X/GTK utils to help with filepickers +#endif // LL_SDL + +#if LL_LINUX +#include "llhttpconstants.h" // file picker uses some of thes constants on Linux +#endif + +// +// Globals +// + +LLFilePicker LLFilePicker::sInstance; + +#if LL_WINDOWS +#define SOUND_FILTER L"Sounds (*.wav)\0*.wav\0" +#define IMAGE_FILTER L"Images (*.tga; *.bmp; *.jpg; *.jpeg; *.png)\0*.tga;*.bmp;*.jpg;*.jpeg;*.png\0" +#define ANIM_FILTER L"Animations (*.bvh; *.anim)\0*.bvh;*.anim\0" +#define COLLADA_FILTER L"Scene (*.dae)\0*.dae\0" +#define GLTF_FILTER L"glTF (*.gltf; *.glb)\0*.gltf;*.glb\0" +#define XML_FILTER L"XML files (*.xml)\0*.xml\0" +#define SLOBJECT_FILTER L"Objects (*.slobject)\0*.slobject\0" +#define RAW_FILTER L"RAW files (*.raw)\0*.raw\0" +#define MODEL_FILTER L"Model files (*.dae)\0*.dae\0" +#define MATERIAL_FILTER L"GLTF Files (*.gltf; *.glb)\0*.gltf;*.glb\0" +#define MATERIAL_TEXTURES_FILTER L"GLTF Import (*.gltf; *.glb; *.tga; *.bmp; *.jpg; *.jpeg; *.png)\0*.gltf;*.glb;*.tga;*.bmp;*.jpg;*.jpeg;*.png\0" +#define SCRIPT_FILTER L"Script files (*.lsl)\0*.lsl\0" +#define DICTIONARY_FILTER L"Dictionary files (*.dic; *.xcu)\0*.dic;*.xcu\0" +#endif + +#ifdef LL_DARWIN +#include "llfilepicker_mac.h" +//#include +#endif + +// +// Implementation +// +LLFilePicker::LLFilePicker() + : mCurrentFile(0), + mLocked(false) + +{ + reset(); + +#if LL_WINDOWS + mOFN.lStructSize = sizeof(OPENFILENAMEW); + mOFN.hwndOwner = NULL; // Set later + mOFN.hInstance = NULL; + mOFN.lpstrCustomFilter = NULL; + mOFN.nMaxCustFilter = 0; + mOFN.lpstrFile = NULL; // set in open and close + mOFN.nMaxFile = LL_MAX_PATH; + mOFN.lpstrFileTitle = NULL; + mOFN.nMaxFileTitle = 0; + mOFN.lpstrInitialDir = NULL; + mOFN.lpstrTitle = NULL; + mOFN.Flags = 0; // set in open and close + mOFN.nFileOffset = 0; + mOFN.nFileExtension = 0; + mOFN.lpstrDefExt = NULL; + mOFN.lCustData = 0L; + mOFN.lpfnHook = NULL; + mOFN.lpTemplateName = NULL; + mFilesW[0] = '\0'; +#elif LL_DARWIN + mPickOptions = 0; +#endif + +} + +LLFilePicker::~LLFilePicker() +{ + // nothing +} + +// utility function to check if access to local file system via file browser +// is enabled and if not, tidy up and indicate we're not allowed to do this. +bool LLFilePicker::check_local_file_access_enabled() +{ + // if local file browsing is turned off, return without opening dialog + bool local_file_system_browsing_enabled = gSavedSettings.getBOOL("LocalFileSystemBrowsingEnabled"); + if ( ! local_file_system_browsing_enabled ) + { + mFiles.clear(); + return false; + } + + return true; +} + +const std::string LLFilePicker::getFirstFile() +{ + mCurrentFile = 0; + return getNextFile(); +} + +const std::string LLFilePicker::getNextFile() +{ + if (mCurrentFile >= getFileCount()) + { + mLocked = false; + return std::string(); + } + else + { + return mFiles[mCurrentFile++]; + } +} + +const std::string LLFilePicker::getCurFile() +{ + if (mCurrentFile >= getFileCount()) + { + mLocked = false; + return std::string(); + } + else + { + return mFiles[mCurrentFile]; + } +} + +void LLFilePicker::reset() +{ + mLocked = false; + mFiles.clear(); + mCurrentFile = 0; +} + +#if LL_WINDOWS + +bool LLFilePicker::setupFilter(ELoadFilter filter) +{ + bool res = true; + switch (filter) + { + case FFLOAD_ALL: + case FFLOAD_EXE: + mOFN.lpstrFilter = L"All Files (*.*)\0*.*\0" \ + SOUND_FILTER \ + IMAGE_FILTER \ + ANIM_FILTER \ + MATERIAL_FILTER \ + L"\0"; + break; + case FFLOAD_WAV: + mOFN.lpstrFilter = SOUND_FILTER \ + L"\0"; + break; + case FFLOAD_IMAGE: + mOFN.lpstrFilter = IMAGE_FILTER \ + L"\0"; + break; + case FFLOAD_ANIM: + mOFN.lpstrFilter = ANIM_FILTER \ + L"\0"; + break; + case FFLOAD_GLTF: + mOFN.lpstrFilter = GLTF_FILTER \ + L"\0"; + break; + case FFLOAD_COLLADA: + mOFN.lpstrFilter = COLLADA_FILTER \ + L"\0"; + break; + case FFLOAD_XML: + mOFN.lpstrFilter = XML_FILTER \ + L"\0"; + break; + case FFLOAD_SLOBJECT: + mOFN.lpstrFilter = SLOBJECT_FILTER \ + L"\0"; + break; + case FFLOAD_RAW: + mOFN.lpstrFilter = RAW_FILTER \ + L"\0"; + break; + case FFLOAD_MODEL: + mOFN.lpstrFilter = MODEL_FILTER \ + L"\0"; + break; + case FFLOAD_MATERIAL: + mOFN.lpstrFilter = MATERIAL_FILTER \ + L"\0"; + break; + case FFLOAD_MATERIAL_TEXTURE: + mOFN.lpstrFilter = MATERIAL_TEXTURES_FILTER \ + MATERIAL_FILTER \ + IMAGE_FILTER \ + L"\0"; + break; + case FFLOAD_SCRIPT: + mOFN.lpstrFilter = SCRIPT_FILTER \ + L"\0"; + break; + case FFLOAD_DICTIONARY: + mOFN.lpstrFilter = DICTIONARY_FILTER \ + L"\0"; + break; + default: + res = false; + break; + } + return res; +} + +bool LLFilePicker::getOpenFile(ELoadFilter filter, bool blocking) +{ + if (mLocked) + { + return false; + } + bool success = false; + + // if local file browsing is turned off, return without opening dialog + if (!check_local_file_access_enabled()) + { + return false; + } + + // don't provide default file selection + mFilesW[0] = '\0'; + + mOFN.hwndOwner = (HWND)gViewerWindow->getPlatformWindow(); + mOFN.lpstrFile = mFilesW; + mOFN.nMaxFile = SINGLE_FILENAME_BUFFER_SIZE; + mOFN.Flags = OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR ; + mOFN.nFilterIndex = 1; + + setupFilter(filter); + + if (blocking) + { + // Modal, so pause agent + send_agent_pause(); + } + + reset(); + + // NOTA BENE: hitting the file dialog triggers a window focus event, destroying the selection manager!! + success = GetOpenFileName(&mOFN); + if (success) + { + std::string filename = utf16str_to_utf8str(llutf16string(mFilesW)); + mFiles.push_back(filename); + } + + if (blocking) + { + send_agent_resume(); + // Account for the fact that the app has been stalled. + LLFrameTimer::updateFrameTime(); + } + + return success; +} + +bool LLFilePicker::getOpenFileModeless(ELoadFilter filter, + void (*callback)(bool, std::vector &, void*), + void *userdata) +{ + // not supposed to be used yet, use LLFilePickerThread + LL_ERRS() << "NOT IMPLEMENTED" << LL_ENDL; + return false; +} + +bool LLFilePicker::getMultipleOpenFiles(ELoadFilter filter, bool blocking) +{ + if( mLocked ) + { + return false; + } + bool success = false; + + // if local file browsing is turned off, return without opening dialog + if (!check_local_file_access_enabled()) + { + return false; + } + + // don't provide default file selection + mFilesW[0] = '\0'; + + mOFN.hwndOwner = (HWND)gViewerWindow->getPlatformWindow(); + mOFN.lpstrFile = mFilesW; + mOFN.nFilterIndex = 1; + mOFN.nMaxFile = FILENAME_BUFFER_SIZE; + mOFN.Flags = OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR | + OFN_EXPLORER | OFN_ALLOWMULTISELECT; + + setupFilter(filter); + + reset(); + + if (blocking) + { + // Modal, so pause agent + send_agent_pause(); + } + + // NOTA BENE: hitting the file dialog triggers a window focus event, destroying the selection manager!! + success = GetOpenFileName(&mOFN); // pauses until ok or cancel. + if( success ) + { + // The getopenfilename api doesn't tell us if we got more than + // one file, so we have to test manually by checking string + // lengths. + if( wcslen(mOFN.lpstrFile) > mOFN.nFileOffset ) /*Flawfinder: ignore*/ + { + std::string filename = utf16str_to_utf8str(llutf16string(mFilesW)); + mFiles.push_back(filename); + } + else + { + mLocked = true; + WCHAR* tptrw = mFilesW; + std::string dirname; + while(1) + { + if (*tptrw == 0 && *(tptrw+1) == 0) // double '\0' + break; + if (*tptrw == 0) + tptrw++; // shouldn't happen? + std::string filename = utf16str_to_utf8str(llutf16string(tptrw)); + if (dirname.empty()) + dirname = filename + "\\"; + else + mFiles.push_back(dirname + filename); + tptrw += wcslen(tptrw); + } + } + } + + if (blocking) + { + send_agent_resume(); + } + + // Account for the fact that the app has been stalled. + LLFrameTimer::updateFrameTime(); + return success; +} + +bool LLFilePicker::getMultipleOpenFilesModeless(ELoadFilter filter, + void (*callback)(bool, std::vector &, void*), + void *userdata ) +{ + // not supposed to be used yet, use LLFilePickerThread + LL_ERRS() << "NOT IMPLEMENTED" << LL_ENDL; + return false; +} + +bool LLFilePicker::getSaveFile(ESaveFilter filter, const std::string& filename, bool blocking) +{ + if( mLocked ) + { + return false; + } + bool success = false; + + // if local file browsing is turned off, return without opening dialog + if (!check_local_file_access_enabled()) + { + return false; + } + + mOFN.lpstrFile = mFilesW; + if (!filename.empty()) + { + llutf16string tstring = utf8str_to_utf16str(filename); + wcsncpy(mFilesW, tstring.c_str(), FILENAME_BUFFER_SIZE); } /*Flawfinder: ignore*/ + else + { + mFilesW[0] = '\0'; + } + mOFN.hwndOwner = (HWND)gViewerWindow->getPlatformWindow(); + + switch( filter ) + { + case FFSAVE_ALL: + mOFN.lpstrDefExt = NULL; + mOFN.lpstrFilter = + L"All Files (*.*)\0*.*\0" \ + L"WAV Sounds (*.wav)\0*.wav\0" \ + L"Targa, Bitmap Images (*.tga; *.bmp)\0*.tga;*.bmp\0" \ + L"\0"; + break; + case FFSAVE_WAV: + if (filename.empty()) + { + wcsncpy( mFilesW,L"untitled.wav", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ + } + mOFN.lpstrDefExt = L"wav"; + mOFN.lpstrFilter = + L"WAV Sounds (*.wav)\0*.wav\0" \ + L"\0"; + break; + case FFSAVE_TGA: + if (filename.empty()) + { + wcsncpy( mFilesW,L"untitled.tga", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ + } + mOFN.lpstrDefExt = L"tga"; + mOFN.lpstrFilter = + L"Targa Images (*.tga)\0*.tga\0" \ + L"\0"; + break; + case FFSAVE_BMP: + if (filename.empty()) + { + wcsncpy( mFilesW,L"untitled.bmp", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ + } + mOFN.lpstrDefExt = L"bmp"; + mOFN.lpstrFilter = + L"Bitmap Images (*.bmp)\0*.bmp\0" \ + L"\0"; + break; + case FFSAVE_PNG: + if (filename.empty()) + { + wcsncpy( mFilesW,L"untitled.png", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ + } + mOFN.lpstrDefExt = L"png"; + mOFN.lpstrFilter = + L"PNG Images (*.png)\0*.png\0" \ + L"\0"; + break; + case FFSAVE_TGAPNG: + if (filename.empty()) + { + wcsncpy( mFilesW,L"untitled.png", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ + //PNG by default + } + mOFN.lpstrDefExt = L"png"; + mOFN.lpstrFilter = + L"PNG Images (*.png)\0*.png\0" \ + L"Targa Images (*.tga)\0*.tga\0" \ + L"\0"; + break; + + case FFSAVE_JPEG: + if (filename.empty()) + { + wcsncpy( mFilesW,L"untitled.jpeg", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ + } + mOFN.lpstrDefExt = L"jpg"; + mOFN.lpstrFilter = + L"JPEG Images (*.jpg *.jpeg)\0*.jpg;*.jpeg\0" \ + L"\0"; + break; + case FFSAVE_AVI: + if (filename.empty()) + { + wcsncpy( mFilesW,L"untitled.avi", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ + } + mOFN.lpstrDefExt = L"avi"; + mOFN.lpstrFilter = + L"AVI Movie File (*.avi)\0*.avi\0" \ + L"\0"; + break; + case FFSAVE_ANIM: + if (filename.empty()) + { + wcsncpy( mFilesW,L"untitled.xaf", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ + } + mOFN.lpstrDefExt = L"xaf"; + mOFN.lpstrFilter = + L"XAF Anim File (*.xaf)\0*.xaf\0" \ + L"\0"; + break; + case FFSAVE_GLTF: + if (filename.empty()) + { + wcsncpy( mFilesW,L"untitled.glb", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ + } + mOFN.lpstrDefExt = L"glb"; + mOFN.lpstrFilter = + L"glTF Asset File (*.gltf *.glb)\0*.gltf;*.glb\0" \ + L"\0"; + break; + case FFSAVE_XML: + if (filename.empty()) + { + wcsncpy( mFilesW,L"untitled.xml", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ + } + + mOFN.lpstrDefExt = L"xml"; + mOFN.lpstrFilter = + L"XML File (*.xml)\0*.xml\0" \ + L"\0"; + break; + case FFSAVE_COLLADA: + if (filename.empty()) + { + wcsncpy( mFilesW,L"untitled.collada", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ + } + mOFN.lpstrDefExt = L"collada"; + mOFN.lpstrFilter = + L"COLLADA File (*.collada)\0*.collada\0" \ + L"\0"; + break; + case FFSAVE_RAW: + if (filename.empty()) + { + wcsncpy( mFilesW,L"untitled.raw", FILENAME_BUFFER_SIZE); /*Flawfinder: ignore*/ + } + mOFN.lpstrDefExt = L"raw"; + mOFN.lpstrFilter = RAW_FILTER \ + L"\0"; + break; + case FFSAVE_J2C: + if (filename.empty()) + { + wcsncpy( mFilesW,L"untitled.j2c", FILENAME_BUFFER_SIZE); + } + mOFN.lpstrDefExt = L"j2c"; + mOFN.lpstrFilter = + L"Compressed Images (*.j2c)\0*.j2c\0" \ + L"\0"; + break; + case FFSAVE_SCRIPT: + if (filename.empty()) + { + wcsncpy( mFilesW,L"untitled.lsl", FILENAME_BUFFER_SIZE); + } + mOFN.lpstrDefExt = L"txt"; + mOFN.lpstrFilter = L"LSL Files (*.lsl)\0*.lsl\0" L"\0"; + break; + default: + return false; + } + + + mOFN.nMaxFile = SINGLE_FILENAME_BUFFER_SIZE; + mOFN.Flags = OFN_OVERWRITEPROMPT | OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST; + + reset(); + + if (blocking) + { + // Modal, so pause agent + send_agent_pause(); + } + + { + // NOTA BENE: hitting the file dialog triggers a window focus event, destroying the selection manager!! + try + { + success = GetSaveFileName(&mOFN); + if (success) + { + std::string filename = utf16str_to_utf8str(llutf16string(mFilesW)); + mFiles.push_back(filename); + } + } + catch (...) + { + LOG_UNHANDLED_EXCEPTION(""); + } + gKeyboard->resetKeys(); + } + + if (blocking) + { + send_agent_resume(); + } + + // Account for the fact that the app has been stalled. + LLFrameTimer::updateFrameTime(); + return success; +} + +bool LLFilePicker::getSaveFileModeless(ESaveFilter filter, + const std::string& filename, + void (*callback)(bool, std::string&, void*), + void *userdata) +{ + // not supposed to be used yet, use LLFilePickerThread + LL_ERRS() << "NOT IMPLEMENTED" << LL_ENDL; + return false; +} + +#elif LL_DARWIN + +std::unique_ptr> LLFilePicker::navOpenFilterProc(ELoadFilter filter) //(AEDesc *theItem, void *info, void *callBackUD, NavFilterModes filterMode) +{ + std::unique_ptr> allowedv(new std::vector< std::string >); + switch(filter) + { + case FFLOAD_ALL: + case FFLOAD_EXE: + allowedv->push_back("app"); + allowedv->push_back("exe"); + allowedv->push_back("wav"); + allowedv->push_back("bvh"); + allowedv->push_back("anim"); + allowedv->push_back("dae"); + allowedv->push_back("raw"); + allowedv->push_back("lsl"); + allowedv->push_back("dic"); + allowedv->push_back("xcu"); + allowedv->push_back("gif"); + allowedv->push_back("gltf"); + allowedv->push_back("glb"); + case FFLOAD_IMAGE: + allowedv->push_back("jpg"); + allowedv->push_back("jpeg"); + allowedv->push_back("bmp"); + allowedv->push_back("tga"); + allowedv->push_back("bmpf"); + allowedv->push_back("tpic"); + allowedv->push_back("png"); + break; + break; + case FFLOAD_WAV: + allowedv->push_back("wav"); + break; + case FFLOAD_ANIM: + allowedv->push_back("bvh"); + allowedv->push_back("anim"); + break; + case FFLOAD_GLTF: + case FFLOAD_MATERIAL: + allowedv->push_back("gltf"); + allowedv->push_back("glb"); + break; + case FFLOAD_COLLADA: + allowedv->push_back("dae"); + break; + case FFLOAD_XML: + allowedv->push_back("xml"); + break; + case FFLOAD_RAW: + allowedv->push_back("raw"); + break; + case FFLOAD_SCRIPT: + allowedv->push_back("lsl"); + break; + case FFLOAD_DICTIONARY: + allowedv->push_back("dic"); + allowedv->push_back("xcu"); + break; + case FFLOAD_DIRECTORY: + break; + default: + LL_WARNS() << "Unsupported format." << LL_ENDL; + } + + return allowedv; +} + +bool LLFilePicker::doNavChooseDialog(ELoadFilter filter) +{ + // if local file browsing is turned off, return without opening dialog + if (!check_local_file_access_enabled()) + { + return false; + } + + gViewerWindow->getWindow()->beforeDialog(); + + std::unique_ptr> allowed_types = navOpenFilterProc(filter); + + std::unique_ptr> filev = doLoadDialog(allowed_types.get(), + mPickOptions); + + gViewerWindow->getWindow()->afterDialog(); + + + if (filev && filev->size() > 0) + { + mFiles.insert(mFiles.end(), filev->begin(), filev->end()); + return true; + } + + return false; +} + +bool LLFilePicker::doNavChooseDialogModeless(ELoadFilter filter, + void (*callback)(bool, std::vector &,void*), + void *userdata) +{ + // if local file browsing is turned off, return without opening dialog + if (!check_local_file_access_enabled()) + { + return false; + } + + std::unique_ptr> allowed_types=navOpenFilterProc(filter); + + doLoadDialogModeless(allowed_types.get(), + mPickOptions, + callback, + userdata); + + return true; +} + +void set_nav_save_data(LLFilePicker::ESaveFilter filter, std::string &extension, std::string &type, std::string &creator) +{ + switch (filter) + { + case LLFilePicker::FFSAVE_WAV: + type = "WAVE"; + creator = "TVOD"; + extension = "wav"; + break; + case LLFilePicker::FFSAVE_TGA: + type = "TPIC"; + creator = "prvw"; + extension = "tga"; + break; + case LLFilePicker::FFSAVE_TGAPNG: + type = "PNG"; + creator = "prvw"; + extension = "png,tga"; + break; + case LLFilePicker::FFSAVE_BMP: + type = "BMPf"; + creator = "prvw"; + extension = "bmp"; + break; + case LLFilePicker::FFSAVE_JPEG: + type = "JPEG"; + creator = "prvw"; + extension = "jpeg"; + break; + case LLFilePicker::FFSAVE_PNG: + type = "PNG "; + creator = "prvw"; + extension = "png"; + break; + case LLFilePicker::FFSAVE_AVI: + type = "\?\?\?\?"; + creator = "\?\?\?\?"; + extension = "mov"; + break; + + case LLFilePicker::FFSAVE_ANIM: + type = "\?\?\?\?"; + creator = "\?\?\?\?"; + extension = "xaf"; + break; + case LLFilePicker::FFSAVE_GLTF: + type = "\?\?\?\?"; + creator = "\?\?\?\?"; + extension = "glb"; + break; + + case LLFilePicker::FFSAVE_XML: + type = "\?\?\?\?"; + creator = "\?\?\?\?"; + extension = "xml"; + break; + + case LLFilePicker::FFSAVE_RAW: + type = "\?\?\?\?"; + creator = "\?\?\?\?"; + extension = "raw"; + break; + + case LLFilePicker::FFSAVE_J2C: + type = "\?\?\?\?"; + creator = "prvw"; + extension = "j2c"; + break; + + case LLFilePicker::FFSAVE_SCRIPT: + type = "LSL "; + creator = "\?\?\?\?"; + extension = "lsl"; + break; + + case LLFilePicker::FFSAVE_ALL: + default: + type = "\?\?\?\?"; + creator = "\?\?\?\?"; + extension = ""; + break; + } +} + +bool LLFilePicker::doNavSaveDialog(ESaveFilter filter, const std::string& filename) +{ + // Setup the type, creator, and extension + std::string extension, type, creator; + + set_nav_save_data(filter, extension, type, creator); + + std::string namestring = filename; + if (namestring.empty()) namestring="Untitled"; + + gViewerWindow->getWindow()->beforeDialog(); + + // Run the dialog + std::unique_ptr filev = doSaveDialog(&namestring, + &type, + &creator, + &extension, + mPickOptions); + + gViewerWindow->getWindow()->afterDialog(); + + if ( filev && !filev->empty() ) + { + mFiles.push_back(*filev); + return true; + } + + return false; +} + +bool LLFilePicker::doNavSaveDialogModeless(ESaveFilter filter, + const std::string& filename, + void (*callback)(bool, std::string&, void*), + void *userdata) +{ + // Setup the type, creator, and extension + std::string extension, type, creator; + + set_nav_save_data(filter, extension, type, creator); + + std::string namestring = filename; + if (namestring.empty()) namestring="Untitled"; + + // Run the dialog + doSaveDialogModeless(&namestring, + &type, + &creator, + &extension, + mPickOptions, + callback, + userdata); + return true; +} + +bool LLFilePicker::getOpenFile(ELoadFilter filter, bool blocking) +{ + if( mLocked ) + return false; + + bool success = false; + + // if local file browsing is turned off, return without opening dialog + if (!check_local_file_access_enabled()) + { + return false; + } + + reset(); + + mPickOptions &= ~F_MULTIPLE; + mPickOptions |= F_FILE; + + if (filter == FFLOAD_DIRECTORY) //This should only be called from lldirpicker. + { + mPickOptions |= ( F_NAV_SUPPORT | F_DIRECTORY ); + mPickOptions &= ~F_FILE; + } + + if (filter == FFLOAD_ALL) // allow application bundles etc. to be traversed; important for DEV-16869, but generally useful + { + mPickOptions |= F_NAV_SUPPORT; + } + + if (blocking) // always true for linux/mac + { + // Modal, so pause agent + send_agent_pause(); + } + + + success = doNavChooseDialog(filter); + + if (success) + { + if (!getFileCount()) + success = false; + } + + if (blocking) + { + send_agent_resume(); + // Account for the fact that the app has been stalled. + LLFrameTimer::updateFrameTime(); + } + + return success; +} + + +bool LLFilePicker::getOpenFileModeless(ELoadFilter filter, + void (*callback)(bool, std::vector &, void*), + void *userdata) +{ + if (mLocked) + return false; + + // if local file browsing is turned off, return without opening dialog + if (!check_local_file_access_enabled()) + { + return false; + } + + reset(); + + mPickOptions &= ~F_MULTIPLE; + mPickOptions |= F_FILE; + + if (filter == FFLOAD_DIRECTORY) //This should only be called from lldirpicker. + { + + mPickOptions |= ( F_NAV_SUPPORT | F_DIRECTORY ); + mPickOptions &= ~F_FILE; + } + + if (filter == FFLOAD_ALL) // allow application bundles etc. to be traversed; important for DEV-16869, but generally useful + { + mPickOptions |= F_NAV_SUPPORT; + } + + return doNavChooseDialogModeless(filter, callback, userdata); +} + +bool LLFilePicker::getMultipleOpenFiles(ELoadFilter filter, bool blocking) +{ + if (mLocked) + return false; + + // if local file browsing is turned off, return without opening dialog + if (!check_local_file_access_enabled()) + { + return false; + } + + bool success = false; + + reset(); + + mPickOptions |= F_FILE; + + mPickOptions |= F_MULTIPLE; + + if (blocking) // always true for linux/mac + { + // Modal, so pause agent + send_agent_pause(); + } + + success = doNavChooseDialog(filter); + + if (blocking) + { + send_agent_resume(); + } + + if (success) + { + if (!getFileCount()) + success = false; + if (getFileCount() > 1) + mLocked = true; + } + + // Account for the fact that the app has been stalled. + LLFrameTimer::updateFrameTime(); + return success; +} + + +bool LLFilePicker::getMultipleOpenFilesModeless(ELoadFilter filter, + void (*callback)(bool, std::vector &, void*), + void *userdata ) +{ + if (mLocked) + return false; + + // if local file browsing is turned off, return without opening dialog + if (!check_local_file_access_enabled()) + { + return false; + } + + reset(); + + mPickOptions |= F_FILE; + + mPickOptions |= F_MULTIPLE; + + return doNavChooseDialogModeless(filter, callback, userdata); +} + +bool LLFilePicker::getSaveFile(ESaveFilter filter, const std::string& filename, bool blocking) +{ + + if (mLocked) + return false; + + bool success = false; + + // if local file browsing is turned off, return without opening dialog + if (!check_local_file_access_enabled()) + { + return false; + } + + reset(); + + mPickOptions &= ~F_MULTIPLE; + + if (blocking) + { + // Modal, so pause agent + send_agent_pause(); + } + + success = doNavSaveDialog(filter, filename); + + if (success) + { + if (!getFileCount()) + success = false; + } + + if (blocking) + { + send_agent_resume(); + } + + // Account for the fact that the app has been stalled. + LLFrameTimer::updateFrameTime(); + return success; +} + +bool LLFilePicker::getSaveFileModeless(ESaveFilter filter, + const std::string& filename, + void (*callback)(bool, std::string&, void*), + void *userdata) +{ + if (mLocked) + return false; + + // if local file browsing is turned off, return without opening dialog + if (!check_local_file_access_enabled()) + { + return false; + } + + reset(); + + mPickOptions &= ~F_MULTIPLE; + + return doNavSaveDialogModeless(filter, filename, callback, userdata); +} +//END LL_DARWIN + +#elif LL_LINUX + +# if LL_GTK + +// static +void LLFilePicker::add_to_selectedfiles(gpointer data, gpointer user_data) +{ + // We need to run g_filename_to_utf8 in the user's locale + std::string saved_locale(setlocale(LC_ALL, NULL)); + setlocale(LC_ALL, ""); + + LLFilePicker* picker = (LLFilePicker*) user_data; + GError *error = NULL; + gchar* filename_utf8 = g_filename_to_utf8((gchar*)data, + -1, NULL, NULL, &error); + if (error) + { + // *FIXME. + // This condition should really be notified to the user, e.g. + // through a message box. Just logging it is inappropriate. + + // g_filename_display_name is ideal, but >= glib 2.6, so: + // a hand-rolled hacky makeASCII which disallows control chars + std::string display_name; + for (const gchar *str = (const gchar *)data; *str; str++) + { + display_name += (char)((*str >= 0x20 && *str <= 0x7E) ? *str : '?'); + } + LL_WARNS() << "g_filename_to_utf8 failed on \"" << display_name << "\": " << error->message << LL_ENDL; + } + + if (filename_utf8) + { + picker->mFiles.push_back(std::string(filename_utf8)); + LL_DEBUGS() << "ADDED FILE " << filename_utf8 << LL_ENDL; + g_free(filename_utf8); + } + + setlocale(LC_ALL, saved_locale.c_str()); +} + +// static +void LLFilePicker::chooser_responder(GtkWidget *widget, gint response, gpointer user_data) +{ + LLFilePicker* picker = (LLFilePicker*)user_data; + + LL_DEBUGS() << "GTK DIALOG RESPONSE " << response << LL_ENDL; + + if (response == GTK_RESPONSE_ACCEPT) + { + GSList *file_list = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(widget)); + g_slist_foreach(file_list, (GFunc)add_to_selectedfiles, user_data); + g_slist_foreach(file_list, (GFunc)g_free, NULL); + g_slist_free (file_list); + } + + // let's save the extension of the last added file(considering current filter) + GtkFileFilter *gfilter = gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(widget)); + if(gfilter) + { + std::string filter = gtk_file_filter_get_name(gfilter); + + if(filter == LLTrans::getString("png_image_files")) + { + picker->mCurrentExtension = ".png"; + } + else if(filter == LLTrans::getString("targa_image_files")) + { + picker->mCurrentExtension = ".tga"; + } + } + + // set the default path for this usage context. + const char* cur_folder = gtk_file_chooser_get_current_folder(GTK_FILE_CHOOSER(widget)); + if (cur_folder != NULL) + { + picker->mContextToPathMap[picker->mCurContextName] = cur_folder; + } + + gtk_widget_destroy(widget); + gtk_main_quit(); +} + + +GtkWindow* LLFilePicker::buildFilePicker(bool is_save, bool is_folder, std::string context) +{ +#ifndef LL_MESA_HEADLESS + if (LLWindowSDL::ll_try_gtk_init()) + { + GtkWidget *win = NULL; + GtkFileChooserAction pickertype = + is_save? + (is_folder? + GTK_FILE_CHOOSER_ACTION_CREATE_FOLDER : + GTK_FILE_CHOOSER_ACTION_SAVE) : + (is_folder? + GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER : + GTK_FILE_CHOOSER_ACTION_OPEN); + + win = gtk_file_chooser_dialog_new(NULL, NULL, + pickertype, + GTK_STOCK_CANCEL, + GTK_RESPONSE_CANCEL, + is_folder ? + GTK_STOCK_APPLY : + (is_save ? + GTK_STOCK_SAVE : + GTK_STOCK_OPEN), + GTK_RESPONSE_ACCEPT, + (gchar *)NULL); + mCurContextName = context; + + // get the default path for this usage context if it's been + // seen before. + std::map::iterator + this_path = mContextToPathMap.find(context); + if (this_path != mContextToPathMap.end()) + { + gtk_file_chooser_set_current_folder + (GTK_FILE_CHOOSER(win), + this_path->second.c_str()); + } + +# if LL_X11 + // Make GTK tell the window manager to associate this + // dialog with our non-GTK raw X11 window, which should try + // to keep it on top etc. + Window XWindowID = LLWindowSDL::get_SDL_XWindowID(); + if (None != XWindowID) + { + gtk_widget_realize(GTK_WIDGET(win)); // so we can get its gdkwin + GdkWindow *gdkwin = gdk_window_foreign_new(XWindowID); + gdk_window_set_transient_for(GTK_WIDGET(win)->window, + gdkwin); + } + else + { + LL_WARNS() << "Hmm, couldn't get xwid to use for transient." << LL_ENDL; + } +# endif //LL_X11 + + g_signal_connect (GTK_FILE_CHOOSER(win), + "response", + G_CALLBACK(LLFilePicker::chooser_responder), + this); + + gtk_window_set_modal(GTK_WINDOW(win), TRUE); + + /* GTK 2.6: if (is_folder) + gtk_file_chooser_set_show_hidden(GTK_FILE_CHOOSER(win), + TRUE); */ + + return GTK_WINDOW(win); + } + else + { + return NULL; + } +#else + return NULL; +#endif //LL_MESA_HEADLESS +} + +static void add_common_filters_to_gtkchooser(GtkFileFilter *gfilter, + GtkWindow *picker, + std::string filtername) +{ + gtk_file_filter_set_name(gfilter, filtername.c_str()); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(picker), + gfilter); + GtkFileFilter *allfilter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(allfilter, "*"); + gtk_file_filter_set_name(allfilter, LLTrans::getString("all_files").c_str()); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(picker), allfilter); + gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(picker), gfilter); +} + +static std::string add_simple_pattern_filter_to_gtkchooser(GtkWindow *picker, + std::string pattern, + std::string filtername) +{ + GtkFileFilter *gfilter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(gfilter, pattern.c_str()); + add_common_filters_to_gtkchooser(gfilter, picker, filtername); + return filtername; +} + +static std::string add_simple_mime_filter_to_gtkchooser(GtkWindow *picker, + std::string mime, + std::string filtername) +{ + GtkFileFilter *gfilter = gtk_file_filter_new(); + gtk_file_filter_add_mime_type(gfilter, mime.c_str()); + add_common_filters_to_gtkchooser(gfilter, picker, filtername); + return filtername; +} + +static std::string add_wav_filter_to_gtkchooser(GtkWindow *picker) +{ + return add_simple_mime_filter_to_gtkchooser(picker, "audio/x-wav", + LLTrans::getString("sound_files") + " (*.wav)"); +} + +static std::string add_anim_filter_to_gtkchooser(GtkWindow *picker) +{ + GtkFileFilter *gfilter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(gfilter, "*.bvh"); + gtk_file_filter_add_pattern(gfilter, "*.anim"); + std::string filtername = LLTrans::getString("animation_files") + " (*.bvh; *.anim)"; + add_common_filters_to_gtkchooser(gfilter, picker, filtername); + return filtername; +} + +static std::string add_xml_filter_to_gtkchooser(GtkWindow *picker) +{ + return add_simple_pattern_filter_to_gtkchooser(picker, "*.xml", + LLTrans::getString("xml_files") + " (*.xml)"); +} + +static std::string add_collada_filter_to_gtkchooser(GtkWindow *picker) +{ + return add_simple_pattern_filter_to_gtkchooser(picker, "*.dae", + LLTrans::getString("scene_files") + " (*.dae)"); +} + +static std::string add_imageload_filter_to_gtkchooser(GtkWindow *picker) +{ + GtkFileFilter *gfilter = gtk_file_filter_new(); + gtk_file_filter_add_pattern(gfilter, "*.tga"); + gtk_file_filter_add_mime_type(gfilter, HTTP_CONTENT_IMAGE_JPEG.c_str()); + gtk_file_filter_add_mime_type(gfilter, HTTP_CONTENT_IMAGE_PNG.c_str()); + gtk_file_filter_add_mime_type(gfilter, HTTP_CONTENT_IMAGE_BMP.c_str()); + std::string filtername = LLTrans::getString("image_files") + " (*.tga; *.bmp; *.jpg; *.png)"; + add_common_filters_to_gtkchooser(gfilter, picker, filtername); + return filtername; +} + +static std::string add_script_filter_to_gtkchooser(GtkWindow *picker) +{ + return add_simple_mime_filter_to_gtkchooser(picker, HTTP_CONTENT_TEXT_PLAIN, + LLTrans::getString("script_files") + " (*.lsl)"); +} + +static std::string add_dictionary_filter_to_gtkchooser(GtkWindow *picker) +{ + return add_simple_mime_filter_to_gtkchooser(picker, HTTP_CONTENT_TEXT_PLAIN, + LLTrans::getString("dictionary_files") + " (*.dic; *.xcu)"); +} + +static std::string add_save_texture_filter_to_gtkchooser(GtkWindow *picker) +{ + GtkFileFilter *gfilter_tga = gtk_file_filter_new(); + GtkFileFilter *gfilter_png = gtk_file_filter_new(); + + gtk_file_filter_add_pattern(gfilter_tga, "*.tga"); + gtk_file_filter_add_mime_type(gfilter_png, "image/png"); + std::string caption = LLTrans::getString("save_texture_image_files") + " (*.tga; *.png)"; + gtk_file_filter_set_name(gfilter_tga, LLTrans::getString("targa_image_files").c_str()); + gtk_file_filter_set_name(gfilter_png, LLTrans::getString("png_image_files").c_str()); + + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(picker), + gfilter_png); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(picker), + gfilter_tga); + return caption; +} + +bool LLFilePicker::getSaveFile( ESaveFilter filter, const std::string& filename, bool blocking ) +{ + bool rtn = false; + + // if local file browsing is turned off, return without opening dialog + if (!check_local_file_access_enabled()) + { + return false; + } + + gViewerWindow->getWindow()->beforeDialog(); + + reset(); + + GtkWindow* picker = buildFilePicker(true, false, "savefile"); + + if (picker) + { + std::string suggest_name = "untitled"; + std::string suggest_ext = ""; + std::string caption = LLTrans::getString("save_file_verb") + " "; + switch (filter) + { + case FFSAVE_WAV: + caption += add_wav_filter_to_gtkchooser(picker); + suggest_ext = ".wav"; + break; + case FFSAVE_TGA: + caption += add_simple_pattern_filter_to_gtkchooser + (picker, "*.tga", LLTrans::getString("targa_image_files") + " (*.tga)"); + suggest_ext = ".tga"; + break; + case FFSAVE_BMP: + caption += add_simple_mime_filter_to_gtkchooser + (picker, HTTP_CONTENT_IMAGE_BMP, LLTrans::getString("bitmap_image_files") + " (*.bmp)"); + suggest_ext = ".bmp"; + break; + case FFSAVE_PNG: + caption += add_simple_mime_filter_to_gtkchooser + (picker, "image/png", LLTrans::getString("png_image_files") + " (*.png)"); + suggest_ext = ".png"; + break; + case FFSAVE_TGAPNG: + caption += add_save_texture_filter_to_gtkchooser(picker); + suggest_ext = ".png"; + break; + case FFSAVE_AVI: + caption += add_simple_mime_filter_to_gtkchooser + (picker, "video/x-msvideo", + LLTrans::getString("avi_movie_file") + " (*.avi)"); + suggest_ext = ".avi"; + break; + case FFSAVE_ANIM: + caption += add_simple_pattern_filter_to_gtkchooser + (picker, "*.xaf", LLTrans::getString("xaf_animation_file") + " (*.xaf)"); + suggest_ext = ".xaf"; + break; + case FFSAVE_XML: + caption += add_simple_pattern_filter_to_gtkchooser + (picker, "*.xml", LLTrans::getString("xml_file") + " (*.xml)"); + suggest_ext = ".xml"; + break; + case FFSAVE_RAW: + caption += add_simple_pattern_filter_to_gtkchooser + (picker, "*.raw", LLTrans::getString("raw_file") + " (*.raw)"); + suggest_ext = ".raw"; + break; + case FFSAVE_J2C: + // *TODO: Should this be 'image/j2c' ? + caption += add_simple_mime_filter_to_gtkchooser + (picker, "images/jp2", + LLTrans::getString("compressed_image_files") + " (*.j2c)"); + suggest_ext = ".j2c"; + break; + case FFSAVE_SCRIPT: + caption += add_script_filter_to_gtkchooser(picker); + suggest_ext = ".lsl"; + break; + default:; + break; + } + + gtk_window_set_title(GTK_WINDOW(picker), caption.c_str()); + + if (filename.empty()) + { + suggest_name += suggest_ext; + + gtk_file_chooser_set_current_name + (GTK_FILE_CHOOSER(picker), + suggest_name.c_str()); + } + else + { + gtk_file_chooser_set_current_name + (GTK_FILE_CHOOSER(picker), filename.c_str()); + } + + gtk_widget_show_all(GTK_WIDGET(picker)); + + gtk_main(); + + rtn = (getFileCount() == 1); + + if(rtn && filter == FFSAVE_TGAPNG) + { + std::string selected_file = mFiles.back(); + mFiles.pop_back(); + mFiles.push_back(selected_file + mCurrentExtension); + } + } + + gViewerWindow->getWindow()->afterDialog(); + + return rtn; +} + +bool LLFilePicker::getOpenFile( ELoadFilter filter, bool blocking ) +{ + bool rtn = false; + + // if local file browsing is turned off, return without opening dialog + if (!check_local_file_access_enabled()) + { + return false; + } + + gViewerWindow->getWindow()->beforeDialog(); + + reset(); + + GtkWindow* picker = buildFilePicker(false, false, "openfile"); + + if (picker) + { + std::string caption = LLTrans::getString("load_file_verb") + " "; + std::string filtername = ""; + switch (filter) + { + case FFLOAD_WAV: + filtername = add_wav_filter_to_gtkchooser(picker); + break; + case FFLOAD_ANIM: + filtername = add_anim_filter_to_gtkchooser(picker); + break; + case FFLOAD_XML: + filtername = add_xml_filter_to_gtkchooser(picker); + break; + case FFLOAD_GLTF: + filtername = dead_code_should_blow_up_here(picker); + break; + case FFLOAD_COLLADA: + filtername = add_collada_filter_to_gtkchooser(picker); + break; + case FFLOAD_IMAGE: + filtername = add_imageload_filter_to_gtkchooser(picker); + break; + case FFLOAD_SCRIPT: + filtername = add_script_filter_to_gtkchooser(picker); + break; + case FFLOAD_DICTIONARY: + filtername = add_dictionary_filter_to_gtkchooser(picker); + break; + default:; + break; + } + + caption += filtername; + + gtk_window_set_title(GTK_WINDOW(picker), caption.c_str()); + + gtk_widget_show_all(GTK_WIDGET(picker)); + gtk_main(); + + rtn = (getFileCount() == 1); + } + + gViewerWindow->getWindow()->afterDialog(); + + return rtn; +} + +bool LLFilePicker::getMultipleOpenFiles( ELoadFilter filter, bool blocking) +{ + bool rtn = false; + + // if local file browsing is turned off, return without opening dialog + if (!check_local_file_access_enabled()) + { + return false; + } + + gViewerWindow->getWindow()->beforeDialog(); + + reset(); + + GtkWindow* picker = buildFilePicker(false, false, "openfile"); + + if (picker) + { + gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER(picker), + TRUE); + + gtk_window_set_title(GTK_WINDOW(picker), LLTrans::getString("load_files").c_str()); + + gtk_widget_show_all(GTK_WIDGET(picker)); + gtk_main(); + rtn = !mFiles.empty(); + } + + gViewerWindow->getWindow()->afterDialog(); + + return rtn; +} + +# else // LL_GTK + +// Hacky stubs designed to facilitate fake getSaveFile and getOpenFile with +// static results, when we don't have a real filepicker. + +bool LLFilePicker::getSaveFile( ESaveFilter filter, const std::string& filename, bool blocking ) +{ + // if local file browsing is turned off, return without opening dialog + // (Even though this is a stub, I think we still should not return anything at all) + if (!check_local_file_access_enabled()) + { + return false; + } + + reset(); + + LL_INFOS() << "getSaveFile suggested filename is [" << filename + << "]" << LL_ENDL; + if (!filename.empty()) + { + mFiles.push_back(gDirUtilp->getLindenUserDir() + gDirUtilp->getDirDelimiter() + filename); + return true; + } + return false; +} + +bool LLFilePicker::getSaveFileModeless(ESaveFilter filter, + const std::string& filename, + void (*callback)(bool, std::string&, void*), + void *userdata) +{ + LL_ERRS() << "NOT IMPLEMENTED" << LL_ENDL; + return false; +} + +bool LLFilePicker::getOpenFile( ELoadFilter filter, bool blocking ) +{ + // if local file browsing is turned off, return without opening dialog + // (Even though this is a stub, I think we still should not return anything at all) + if (!check_local_file_access_enabled()) + { + return false; + } + + reset(); + + // HACK: Static filenames for 'open' until we implement filepicker + std::string filename = gDirUtilp->getLindenUserDir() + gDirUtilp->getDirDelimiter() + "upload"; + switch (filter) + { + case FFLOAD_WAV: filename += ".wav"; break; + case FFLOAD_IMAGE: filename += ".tga"; break; + case FFLOAD_ANIM: filename += ".bvh"; break; + default: break; + } + mFiles.push_back(filename); + LL_INFOS() << "getOpenFile: Will try to open file: " << filename << LL_ENDL; + return true; +} + +bool LLFilePicker::getOpenFileModeless(ELoadFilter filter, + void (*callback)(bool, std::vector &, void*), + void *userdata) +{ + LL_ERRS() << "NOT IMPLEMENTED" << LL_ENDL; + return false; +} + +bool LLFilePicker::getMultipleOpenFiles( ELoadFilter filter, bool blocking) +{ + // if local file browsing is turned off, return without opening dialog + // (Even though this is a stub, I think we still should not return anything at all) + if (!check_local_file_access_enabled()) + { + return false; + } + + reset(); + return false; +} + +bool LLFilePicker::getMultipleOpenFilesModeless(ELoadFilter filter, + void (*callback)(bool, std::vector &, void*), + void *userdata ) +{ + LL_ERRS() << "NOT IMPLEMENTED" << LL_ENDL; + return false; +} + +#endif // LL_GTK + +#else // not implemented + +bool LLFilePicker::getSaveFile( ESaveFilter filter, const std::string& filename ) +{ + reset(); + return false; +} + +bool LLFilePicker::getOpenFile( ELoadFilter filter ) +{ + reset(); + return false; +} + +bool LLFilePicker::getMultipleOpenFiles( ELoadFilter filter, bool blocking) +{ + reset(); + return false; +} + +#endif // LL_LINUX diff --git a/indra/newview/llfilepicker.h b/indra/newview/llfilepicker.h index 542f04d3a2..266c5217c8 100644 --- a/indra/newview/llfilepicker.h +++ b/indra/newview/llfilepicker.h @@ -1,214 +1,214 @@ -/** - * @file llfilepicker.h - * @brief OS-specific file picker - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -// OS specific file selection dialog. This is implemented as a -// singleton class, so call the instance() method to get the working -// instance. When you call getMultipleOpenFile(), it locks the picker -// until you iterate to the end of the list of selected files with -// getNextFile() or call reset(). - -#ifndef LL_LLFILEPICKER_H -#define LL_LLFILEPICKER_H - -#include "stdtypes.h" - -#if LL_DARWIN -#include - -// AssertMacros.h does bad things. -#undef verify -#undef check -#undef require - -#include -#include "llstring.h" - -#endif - -// Need commdlg.h for OPENFILENAMEA -#ifdef LL_WINDOWS -#include "llwin32headers.h" -#include -#endif - -extern "C" { -// mostly for Linux, possible on others -#if LL_GTK -# include "gtk/gtk.h" -#endif // LL_GTK -} - -class LLFilePicker -{ -#ifdef LL_GTK - friend class LLDirPicker; - friend void chooser_responder(GtkWidget *, gint, gpointer); -#endif // LL_GTK -public: - // calling this before main() is undefined - static LLFilePicker& instance( void ) { return sInstance; } - - enum ELoadFilter - { - FFLOAD_ALL = 1, - FFLOAD_WAV = 2, - FFLOAD_IMAGE = 3, - FFLOAD_ANIM = 4, - FFLOAD_GLTF = 5, - FFLOAD_XML = 6, - FFLOAD_SLOBJECT = 7, - FFLOAD_RAW = 8, - FFLOAD_MODEL = 9, - FFLOAD_COLLADA = 10, - FFLOAD_SCRIPT = 11, - FFLOAD_DICTIONARY = 12, - FFLOAD_DIRECTORY = 13, // To call from lldirpicker. - FFLOAD_EXE = 14, // Note: EXE will be treated as ALL on Windows and Linux but not on Darwin - FFLOAD_MATERIAL = 15, - FFLOAD_MATERIAL_TEXTURE = 16, - }; - - enum ESaveFilter - { - FFSAVE_ALL = 1, - FFSAVE_WAV = 3, - FFSAVE_TGA = 4, - FFSAVE_BMP = 5, - FFSAVE_AVI = 6, - FFSAVE_ANIM = 7, - FFSAVE_GLTF = 8, - FFSAVE_XML = 9, - FFSAVE_COLLADA = 10, - FFSAVE_RAW = 11, - FFSAVE_J2C = 12, - FFSAVE_PNG = 13, - FFSAVE_JPEG = 14, - FFSAVE_SCRIPT = 15, - FFSAVE_TGAPNG = 16 - }; - - // open the dialog. This is a modal operation - bool getSaveFile( ESaveFilter filter = FFSAVE_ALL, const std::string& filename = LLStringUtil::null, bool blocking = true); - bool getSaveFileModeless(ESaveFilter filter, - const std::string& filename, - void (*callback)(bool, std::string&, void*), - void *userdata); - bool getOpenFile( ELoadFilter filter = FFLOAD_ALL, bool blocking = true ); - // Todo: implement getOpenFileModeless and getMultipleOpenFilesModeless - // for windows and use directly instead of ugly LLFilePickerThread - bool getOpenFileModeless( ELoadFilter filter, void (*callback)(bool, std::vector &, void*), void *userdata); // MAC only. - bool getMultipleOpenFiles( ELoadFilter filter = FFLOAD_ALL, bool blocking = true ); - bool getMultipleOpenFilesModeless( ELoadFilter filter, void (*callback)(bool, std::vector &, void*), void *userdata ); // MAC only - - // Get the filename(s) found. getFirstFile() sets the pointer to - // the start of the structure and allows the start of iteration. - const std::string getFirstFile(); - - // getNextFile() increments the internal representation and - // returns the next file specified by the user. Returns NULL when - // no more files are left. Further calls to getNextFile() are - // undefined. - const std::string getNextFile(); - - // This utility function extracts the current file name without - // doing any incrementing. - const std::string getCurFile(); - - // Returns the index of the current file. - S32 getCurFileNum() const { return mCurrentFile; } - - S32 getFileCount() const { return (S32)mFiles.size(); } - - // see lldir.h : getBaseFileName and getDirName to extract base or directory names - - // clear any lists of buffers or whatever, and make sure the file - // picker isn't locked. - void reset(); - -private: - enum - { - SINGLE_FILENAME_BUFFER_SIZE = 1024, - //FILENAME_BUFFER_SIZE = 65536 - FILENAME_BUFFER_SIZE = 65000 - }; - - // utility function to check if access to local file system via file browser - // is enabled and if not, tidy up and indicate we're not allowed to do this. - bool check_local_file_access_enabled(); - -#if LL_WINDOWS - OPENFILENAMEW mOFN; // for open and save dialogs - WCHAR mFilesW[FILENAME_BUFFER_SIZE]; - - bool setupFilter(ELoadFilter filter); -#endif - -#if LL_DARWIN - S32 mPickOptions; - std::vector mFileVector; - - bool doNavChooseDialog(ELoadFilter filter); - bool doNavChooseDialogModeless(ELoadFilter filter, - void (*callback)(bool, std::vector&, void*), - void *userdata); - bool doNavSaveDialog(ESaveFilter filter, const std::string& filename); - std::unique_ptr> navOpenFilterProc(ELoadFilter filter); - bool doNavSaveDialogModeless(ESaveFilter filter, - const std::string& filename, - void (*callback)(bool, std::string&, void*), - void *userdata); -#endif - -#if LL_GTK - static void add_to_selectedfiles(gpointer data, gpointer user_data); - static void chooser_responder(GtkWidget *widget, gint response, gpointer user_data); - // we remember the last path that was accessed for a particular usage - std::map mContextToPathMap; - std::string mCurContextName; - // we also remember the extension of the last added file. - std::string mCurrentExtension; -#endif - - std::vector mFiles; - S32 mCurrentFile; - bool mLocked; - - static LLFilePicker sInstance; - -protected: -#if LL_GTK - GtkWindow* buildFilePicker(bool is_save, bool is_folder, - std::string context = "generic"); -#endif - -public: - // don't call these directly please. - LLFilePicker(); - ~LLFilePicker(); -}; - -#endif +/** + * @file llfilepicker.h + * @brief OS-specific file picker + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// OS specific file selection dialog. This is implemented as a +// singleton class, so call the instance() method to get the working +// instance. When you call getMultipleOpenFile(), it locks the picker +// until you iterate to the end of the list of selected files with +// getNextFile() or call reset(). + +#ifndef LL_LLFILEPICKER_H +#define LL_LLFILEPICKER_H + +#include "stdtypes.h" + +#if LL_DARWIN +#include + +// AssertMacros.h does bad things. +#undef verify +#undef check +#undef require + +#include +#include "llstring.h" + +#endif + +// Need commdlg.h for OPENFILENAMEA +#ifdef LL_WINDOWS +#include "llwin32headers.h" +#include +#endif + +extern "C" { +// mostly for Linux, possible on others +#if LL_GTK +# include "gtk/gtk.h" +#endif // LL_GTK +} + +class LLFilePicker +{ +#ifdef LL_GTK + friend class LLDirPicker; + friend void chooser_responder(GtkWidget *, gint, gpointer); +#endif // LL_GTK +public: + // calling this before main() is undefined + static LLFilePicker& instance( void ) { return sInstance; } + + enum ELoadFilter + { + FFLOAD_ALL = 1, + FFLOAD_WAV = 2, + FFLOAD_IMAGE = 3, + FFLOAD_ANIM = 4, + FFLOAD_GLTF = 5, + FFLOAD_XML = 6, + FFLOAD_SLOBJECT = 7, + FFLOAD_RAW = 8, + FFLOAD_MODEL = 9, + FFLOAD_COLLADA = 10, + FFLOAD_SCRIPT = 11, + FFLOAD_DICTIONARY = 12, + FFLOAD_DIRECTORY = 13, // To call from lldirpicker. + FFLOAD_EXE = 14, // Note: EXE will be treated as ALL on Windows and Linux but not on Darwin + FFLOAD_MATERIAL = 15, + FFLOAD_MATERIAL_TEXTURE = 16, + }; + + enum ESaveFilter + { + FFSAVE_ALL = 1, + FFSAVE_WAV = 3, + FFSAVE_TGA = 4, + FFSAVE_BMP = 5, + FFSAVE_AVI = 6, + FFSAVE_ANIM = 7, + FFSAVE_GLTF = 8, + FFSAVE_XML = 9, + FFSAVE_COLLADA = 10, + FFSAVE_RAW = 11, + FFSAVE_J2C = 12, + FFSAVE_PNG = 13, + FFSAVE_JPEG = 14, + FFSAVE_SCRIPT = 15, + FFSAVE_TGAPNG = 16 + }; + + // open the dialog. This is a modal operation + bool getSaveFile( ESaveFilter filter = FFSAVE_ALL, const std::string& filename = LLStringUtil::null, bool blocking = true); + bool getSaveFileModeless(ESaveFilter filter, + const std::string& filename, + void (*callback)(bool, std::string&, void*), + void *userdata); + bool getOpenFile( ELoadFilter filter = FFLOAD_ALL, bool blocking = true ); + // Todo: implement getOpenFileModeless and getMultipleOpenFilesModeless + // for windows and use directly instead of ugly LLFilePickerThread + bool getOpenFileModeless( ELoadFilter filter, void (*callback)(bool, std::vector &, void*), void *userdata); // MAC only. + bool getMultipleOpenFiles( ELoadFilter filter = FFLOAD_ALL, bool blocking = true ); + bool getMultipleOpenFilesModeless( ELoadFilter filter, void (*callback)(bool, std::vector &, void*), void *userdata ); // MAC only + + // Get the filename(s) found. getFirstFile() sets the pointer to + // the start of the structure and allows the start of iteration. + const std::string getFirstFile(); + + // getNextFile() increments the internal representation and + // returns the next file specified by the user. Returns NULL when + // no more files are left. Further calls to getNextFile() are + // undefined. + const std::string getNextFile(); + + // This utility function extracts the current file name without + // doing any incrementing. + const std::string getCurFile(); + + // Returns the index of the current file. + S32 getCurFileNum() const { return mCurrentFile; } + + S32 getFileCount() const { return (S32)mFiles.size(); } + + // see lldir.h : getBaseFileName and getDirName to extract base or directory names + + // clear any lists of buffers or whatever, and make sure the file + // picker isn't locked. + void reset(); + +private: + enum + { + SINGLE_FILENAME_BUFFER_SIZE = 1024, + //FILENAME_BUFFER_SIZE = 65536 + FILENAME_BUFFER_SIZE = 65000 + }; + + // utility function to check if access to local file system via file browser + // is enabled and if not, tidy up and indicate we're not allowed to do this. + bool check_local_file_access_enabled(); + +#if LL_WINDOWS + OPENFILENAMEW mOFN; // for open and save dialogs + WCHAR mFilesW[FILENAME_BUFFER_SIZE]; + + bool setupFilter(ELoadFilter filter); +#endif + +#if LL_DARWIN + S32 mPickOptions; + std::vector mFileVector; + + bool doNavChooseDialog(ELoadFilter filter); + bool doNavChooseDialogModeless(ELoadFilter filter, + void (*callback)(bool, std::vector&, void*), + void *userdata); + bool doNavSaveDialog(ESaveFilter filter, const std::string& filename); + std::unique_ptr> navOpenFilterProc(ELoadFilter filter); + bool doNavSaveDialogModeless(ESaveFilter filter, + const std::string& filename, + void (*callback)(bool, std::string&, void*), + void *userdata); +#endif + +#if LL_GTK + static void add_to_selectedfiles(gpointer data, gpointer user_data); + static void chooser_responder(GtkWidget *widget, gint response, gpointer user_data); + // we remember the last path that was accessed for a particular usage + std::map mContextToPathMap; + std::string mCurContextName; + // we also remember the extension of the last added file. + std::string mCurrentExtension; +#endif + + std::vector mFiles; + S32 mCurrentFile; + bool mLocked; + + static LLFilePicker sInstance; + +protected: +#if LL_GTK + GtkWindow* buildFilePicker(bool is_save, bool is_folder, + std::string context = "generic"); +#endif + +public: + // don't call these directly please. + LLFilePicker(); + ~LLFilePicker(); +}; + +#endif diff --git a/indra/newview/llfirstuse.cpp b/indra/newview/llfirstuse.cpp index 436f1a5c64..ca42958c41 100644 --- a/indra/newview/llfirstuse.cpp +++ b/indra/newview/llfirstuse.cpp @@ -1,176 +1,176 @@ -/** - * @file llfirstuse.cpp - * @brief Methods that spawn "first-use" dialogs - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfirstuse.h" - -// library includes -#include "indra_constants.h" -#include "llnotifications.h" - -// viewer includes -#include "llagent.h" // for gAgent.inPrelude() -#include "llviewercontrol.h" -#include "llui.h" -#include "llappviewer.h" -#include "lltracker.h" - - -// static -void LLFirstUse::otherAvatarChatFirst(bool enable) -{ - firstUseNotification("FirstOtherChatBeforeUser", enable, "HintChat", LLSD(), LLSD().with("target", "nearby_chat").with("direction", "top_right").with("distance", 24)); -} - -// static -void LLFirstUse::speak(bool enable) -{ - firstUseNotification("FirstSpeak", enable, "HintSpeak", LLSD(), LLSD().with("target", "speak_btn").with("direction", "top")); -} - -// static -void LLFirstUse::sit(bool enable) -{ - firstUseNotification("FirstSit", enable, "HintSit", LLSD(), LLSD().with("target", "stand_btn").with("direction", "top")); -} - -// static -void LLFirstUse::newInventory(bool enable) -{ - // turning this off until bug EXP-62 can be fixed (inventory hint appears for new users when their initial inventory is acquired) - // firstUseNotification("FirstInventoryOffer", enable, "HintInventory", LLSD(), LLSD().with("target", "inventory_btn").with("direction", "left")); -} - -// first clean starts at 3 AM -const S32 SANDBOX_FIRST_CLEAN_HOUR = 3; -// clean every hours -const S32 SANDBOX_CLEAN_FREQ = 12; - -// static -void LLFirstUse::useSandbox() -{ - firstUseNotification("FirstSandbox", true, "FirstSandbox", LLSD().with("HOURS", SANDBOX_CLEAN_FREQ).with("TIME", SANDBOX_FIRST_CLEAN_HOUR)); -} - -// static -void LLFirstUse::notUsingDestinationGuide(bool enable) -{ - // not doing this yet - firstUseNotification("FirstNotUseDestinationGuide", enable, "HintDestinationGuide", LLSD(), LLSD().with("target", "dest_guide_btn").with("direction", "top")); -} - -// static -void LLFirstUse::notUsingSidePanel(bool enable) -{ - // not doing this yet - //firstUseNotification("FirstNotUseSidePanel", enable, "HintSidePanel", LLSD(), LLSD().with("target", "side_panel_btn").with("direction", "left")); -} - -// static -void LLFirstUse::notMoving(bool enable) -{ - // fire off 2 notifications and rely on filtering to select the relevant one - firstUseNotification("FirstNotMoving", enable, "HintMove", LLSD(), LLSD().with("target", "move_btn").with("direction", "top")); - firstUseNotification("FirstNotMoving", enable, "HintMoveClick", LLSD(), LLSD() - .with("target", "nav_bar") - .with("direction", "bottom") - .with("hint_image", "click_to_move.png") - .with("up_arrow", "")); -} - -// static -void LLFirstUse::viewPopup(bool enable) -{ -// firstUseNotification("FirstViewPopup", enable, "HintView", LLSD(), LLSD().with("target", "view_popup").with("direction", "right")); -} - -// static -void LLFirstUse::setDisplayName(bool enable) -{ - firstUseNotification("FirstDisplayName", enable, "HintDisplayName", LLSD(), LLSD().with("target", "set_display_name").with("direction", "left")); -} - -// static -void LLFirstUse::receiveLindens(bool enable) -{ - firstUseNotification("FirstReceiveLindens", enable, "HintLindenDollar", LLSD(), LLSD().with("target", "linden_balance").with("direction", "bottom")); -} - - -//static -void LLFirstUse::firstUseNotification(const std::string& control_var, bool enable, const std::string& notification_name, LLSD args, LLSD payload) -{ - init(); - - if (enable) - { - if (gSavedSettings.getBOOL("EnableUIHints")) - { - LL_DEBUGS("LLFirstUse") << "Trigger first use notification " << notification_name << LL_ENDL; - - // if notification doesn't already exist and this notification hasn't been disabled... - if (gWarningSettings.getBOOL(control_var)) - { // create new notification - LLNotifications::instance().add(LLNotification::Params().name(notification_name).substitutions(args).payload(payload.with("control_var", control_var))); - } - } - } - else - { - LL_DEBUGS("LLFirstUse") << "Disabling first use notification " << notification_name << LL_ENDL; - LLNotifications::instance().cancelByName(notification_name); - // redundantly clear settings var here, in case there are no notifications to cancel - gWarningSettings.setBOOL(control_var, false); - } - -} - -// static -void LLFirstUse::init() -{ - static bool initialized = false; - if (!initialized) - { - LLNotifications::instance().getChannel("Hints")->connectChanged(&processNotification); - } - initialized = true; -} - -//static -bool LLFirstUse::processNotification(const LLSD& notify) -{ - if (notify["sigtype"].asString() == "delete") - { - LLNotificationPtr notification = LLNotifications::instance().find(notify["id"].asUUID()); - if (notification) - { - // disable any future notifications - gWarningSettings.setBOOL((std::string)notification->getPayload()["control_var"], false); - } - } - return false; -} +/** + * @file llfirstuse.cpp + * @brief Methods that spawn "first-use" dialogs + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfirstuse.h" + +// library includes +#include "indra_constants.h" +#include "llnotifications.h" + +// viewer includes +#include "llagent.h" // for gAgent.inPrelude() +#include "llviewercontrol.h" +#include "llui.h" +#include "llappviewer.h" +#include "lltracker.h" + + +// static +void LLFirstUse::otherAvatarChatFirst(bool enable) +{ + firstUseNotification("FirstOtherChatBeforeUser", enable, "HintChat", LLSD(), LLSD().with("target", "nearby_chat").with("direction", "top_right").with("distance", 24)); +} + +// static +void LLFirstUse::speak(bool enable) +{ + firstUseNotification("FirstSpeak", enable, "HintSpeak", LLSD(), LLSD().with("target", "speak_btn").with("direction", "top")); +} + +// static +void LLFirstUse::sit(bool enable) +{ + firstUseNotification("FirstSit", enable, "HintSit", LLSD(), LLSD().with("target", "stand_btn").with("direction", "top")); +} + +// static +void LLFirstUse::newInventory(bool enable) +{ + // turning this off until bug EXP-62 can be fixed (inventory hint appears for new users when their initial inventory is acquired) + // firstUseNotification("FirstInventoryOffer", enable, "HintInventory", LLSD(), LLSD().with("target", "inventory_btn").with("direction", "left")); +} + +// first clean starts at 3 AM +const S32 SANDBOX_FIRST_CLEAN_HOUR = 3; +// clean every hours +const S32 SANDBOX_CLEAN_FREQ = 12; + +// static +void LLFirstUse::useSandbox() +{ + firstUseNotification("FirstSandbox", true, "FirstSandbox", LLSD().with("HOURS", SANDBOX_CLEAN_FREQ).with("TIME", SANDBOX_FIRST_CLEAN_HOUR)); +} + +// static +void LLFirstUse::notUsingDestinationGuide(bool enable) +{ + // not doing this yet + firstUseNotification("FirstNotUseDestinationGuide", enable, "HintDestinationGuide", LLSD(), LLSD().with("target", "dest_guide_btn").with("direction", "top")); +} + +// static +void LLFirstUse::notUsingSidePanel(bool enable) +{ + // not doing this yet + //firstUseNotification("FirstNotUseSidePanel", enable, "HintSidePanel", LLSD(), LLSD().with("target", "side_panel_btn").with("direction", "left")); +} + +// static +void LLFirstUse::notMoving(bool enable) +{ + // fire off 2 notifications and rely on filtering to select the relevant one + firstUseNotification("FirstNotMoving", enable, "HintMove", LLSD(), LLSD().with("target", "move_btn").with("direction", "top")); + firstUseNotification("FirstNotMoving", enable, "HintMoveClick", LLSD(), LLSD() + .with("target", "nav_bar") + .with("direction", "bottom") + .with("hint_image", "click_to_move.png") + .with("up_arrow", "")); +} + +// static +void LLFirstUse::viewPopup(bool enable) +{ +// firstUseNotification("FirstViewPopup", enable, "HintView", LLSD(), LLSD().with("target", "view_popup").with("direction", "right")); +} + +// static +void LLFirstUse::setDisplayName(bool enable) +{ + firstUseNotification("FirstDisplayName", enable, "HintDisplayName", LLSD(), LLSD().with("target", "set_display_name").with("direction", "left")); +} + +// static +void LLFirstUse::receiveLindens(bool enable) +{ + firstUseNotification("FirstReceiveLindens", enable, "HintLindenDollar", LLSD(), LLSD().with("target", "linden_balance").with("direction", "bottom")); +} + + +//static +void LLFirstUse::firstUseNotification(const std::string& control_var, bool enable, const std::string& notification_name, LLSD args, LLSD payload) +{ + init(); + + if (enable) + { + if (gSavedSettings.getBOOL("EnableUIHints")) + { + LL_DEBUGS("LLFirstUse") << "Trigger first use notification " << notification_name << LL_ENDL; + + // if notification doesn't already exist and this notification hasn't been disabled... + if (gWarningSettings.getBOOL(control_var)) + { // create new notification + LLNotifications::instance().add(LLNotification::Params().name(notification_name).substitutions(args).payload(payload.with("control_var", control_var))); + } + } + } + else + { + LL_DEBUGS("LLFirstUse") << "Disabling first use notification " << notification_name << LL_ENDL; + LLNotifications::instance().cancelByName(notification_name); + // redundantly clear settings var here, in case there are no notifications to cancel + gWarningSettings.setBOOL(control_var, false); + } + +} + +// static +void LLFirstUse::init() +{ + static bool initialized = false; + if (!initialized) + { + LLNotifications::instance().getChannel("Hints")->connectChanged(&processNotification); + } + initialized = true; +} + +//static +bool LLFirstUse::processNotification(const LLSD& notify) +{ + if (notify["sigtype"].asString() == "delete") + { + LLNotificationPtr notification = LLNotifications::instance().find(notify["id"].asUUID()); + if (notification) + { + // disable any future notifications + gWarningSettings.setBOOL((std::string)notification->getPayload()["control_var"], false); + } + } + return false; +} diff --git a/indra/newview/llflexibleobject.cpp b/indra/newview/llflexibleobject.cpp index cef4bd284d..4d82611def 100644 --- a/indra/newview/llflexibleobject.cpp +++ b/indra/newview/llflexibleobject.cpp @@ -1,939 +1,939 @@ -/** - * @file llflexibleobject.cpp - * @brief Flexible object implementation - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "pipeline.h" -#include "lldrawpoolbump.h" -#include "llface.h" -#include "llflexibleobject.h" -#include "llglheaders.h" -#include "llrendersphere.h" -#include "llviewerobject.h" -#include "llagent.h" -#include "llsky.h" -#include "llviewercamera.h" -#include "llviewertexturelist.h" -#include "llviewercontrol.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llworld.h" -#include "llvoavatar.h" - -static const F32 SEC_PER_FLEXI_FRAME = 1.f / 60.f; // 60 flexi updates per second -/*static*/ F32 LLVolumeImplFlexible::sUpdateFactor = 1.0f; -std::vector LLVolumeImplFlexible::sInstanceList; - -// LLFlexibleObjectData::pack/unpack now in llprimitive.cpp - -//----------------------------------------------- -// constructor -//----------------------------------------------- -LLVolumeImplFlexible::LLVolumeImplFlexible(LLViewerObject* vo, LLFlexibleObjectData* attributes) : - mVO(vo), - mAttributes(attributes), - mLastFrameNum(0), - mLastUpdatePeriod(0) -{ - static U32 seed = 0; - mID = seed++; - mInitialized = false; - mUpdated = false; - mInitializedRes = -1; - mSimulateRes = 0; - mCollisionSphereRadius = 0.f; - mRenderRes = -1; - - if(mVO->mDrawable.notNull()) - { - mVO->mDrawable->makeActive() ; - } - - mInstanceIndex = sInstanceList.size(); - sInstanceList.push_back(this); -}//----------------------------------------------- - -LLVolumeImplFlexible::~LLVolumeImplFlexible() -{ - S32 end_idx = sInstanceList.size()-1; - - if (end_idx != mInstanceIndex) - { - sInstanceList[mInstanceIndex] = sInstanceList[end_idx]; - sInstanceList[mInstanceIndex]->mInstanceIndex = mInstanceIndex; - } - - sInstanceList.pop_back(); -} - -//static -void LLVolumeImplFlexible::updateClass() -{ - LL_PROFILE_ZONE_SCOPED; - - U64 virtual_frame_num = LLTimer::getElapsedSeconds() / SEC_PER_FLEXI_FRAME; - for (std::vector::iterator iter = sInstanceList.begin(); - iter != sInstanceList.end(); - ++iter) - { - // Note: by now update period might have changed - if ((*iter)->mRenderRes == -1 - || (*iter)->mLastFrameNum + (*iter)->mLastUpdatePeriod <= virtual_frame_num - || (*iter)->mLastFrameNum > virtual_frame_num) //time issues, overflow - { - (*iter)->doIdleUpdate(); - } - } -} - -LLVector3 LLVolumeImplFlexible::getFramePosition() const -{ - return mVO->getRenderPosition(); -} - -LLQuaternion LLVolumeImplFlexible::getFrameRotation() const -{ - return mVO->getRenderRotation(); -} - -void LLVolumeImplFlexible::onParameterChanged(U16 param_type, LLNetworkData *data, bool in_use, bool local_origin) -{ - if (param_type == LLNetworkData::PARAMS_FLEXIBLE) - { - mAttributes = (LLFlexibleObjectData*)data; - setAttributesOfAllSections(); - } -} - -void LLVolumeImplFlexible::onShift(const LLVector4a &shift_vector) -{ - //VECTORIZE THIS - LLVector3 shift(shift_vector.getF32ptr()); - for (int section = 0; section < (1<mDrawable->getScale(); - F32 source_section_length = scale.mV[VZ] / (F32)(1< dest_sections) - { - // Copy, skipping sections - - S32 num_steps = 1<<(source_sections-dest_sections); - - // Copy from left to right since it may be an in-place computation - for (S32 section=0; section=0; section -= num_steps) - { - LLFlexibleObjectSection *last_source_section = &source[section>>step_shift]; - LLFlexibleObjectSection *source_section = &source[(section>>step_shift)+1]; - - // Cubic interpolation of position - // At^3 + Bt^2 + Ct + D = f(t) - LLVector3 D = last_source_section->mPosition; - LLVector3 C = last_source_section->mdPosition * source_section_length; - LLVector3 Y = source_section->mdPosition * source_section_length - C; // Helper var - LLVector3 X = (source_section->mPosition - D - C); // Helper var - LLVector3 A = Y - 2*X; - LLVector3 B = X - A; - - F32 t_inc = 1.f/F32(num_steps); - F32 t = t_inc; - for (S32 step=1; stepmScale, source_section->mScale, t); - dest[section+step].mAxisRotation = - slerp(t, last_source_section->mAxisRotation, source_section->mAxisRotation); - - // Evaluate output interpolated values - F32 t_sq = t*t; - dest[section+step].mPosition = t_sq*(t*A + B) + t*C + D; - dest[section+step].mRotation = - slerp(t, last_source_section->mRotation, source_section->mRotation); - dest[section+step].mVelocity = lerp(last_source_section->mVelocity, source_section->mVelocity, t); - dest[section+step].mDirection = lerp(last_source_section->mDirection, source_section->mDirection, t); - dest[section+step].mdPosition = lerp(last_source_section->mdPosition, source_section->mdPosition, t); - dest[section+num_steps] = *source_section; - t += t_inc; - } - } - dest[0] = source[0]; - } - else - { - // numbers are equal. copy info - for (S32 section=0; section <= num_output_sections; ++section) - { - dest[section] = source[section]; - } - } -} - -//----------------------------------------------------------------------------- -void LLVolumeImplFlexible::setAttributesOfAllSections(LLVector3* inScale) -{ - LLVector2 bottom_scale, top_scale; - F32 begin_rot = 0, end_rot = 0; - if (mVO->getVolume()) - { - const LLPathParams ¶ms = mVO->getVolume()->getParams().getPathParams(); - bottom_scale = params.getBeginScale(); - top_scale = params.getEndScale(); - begin_rot = F_PI * params.getTwistBegin(); - end_rot = F_PI * params.getTwist(); - } - - if (!mVO->mDrawable) - { - return; - } - - S32 num_sections = 1 << mSimulateRes; - - LLVector3 scale; - if (inScale == (LLVector3*)NULL) - { - scale = mVO->mDrawable->getScale(); - } - else - { - scale = *inScale; - } - - mSection[0].mPosition = getAnchorPosition(); - mSection[0].mDirection = LLVector3::z_axis * getFrameRotation(); - mSection[0].mdPosition = mSection[0].mDirection; - mSection[0].mScale.setVec(scale.mV[VX]*bottom_scale.mV[0], scale.mV[VY]*bottom_scale.mV[1]); - mSection[0].mVelocity.setVec(0,0,0); - mSection[0].mAxisRotation.setQuat(begin_rot,0,0,1); - - remapSections(mSection, mInitializedRes, mSection, mSimulateRes); - mInitializedRes = mSimulateRes; - - F32 t_inc = 1.f/F32(num_sections); - F32 t = t_inc; - - for ( int i=1; i<= num_sections; i++) - { - mSection[i].mAxisRotation.setQuat(lerp(begin_rot,end_rot,t),0,0,1); - mSection[i].mScale = LLVector2( - scale.mV[VX] * lerp(bottom_scale.mV[0], top_scale.mV[0], t), - scale.mV[VY] * lerp(bottom_scale.mV[1], top_scale.mV[1], t)); - t += t_inc; - } -}//----------------------------------------------------------------------------------- - - -void LLVolumeImplFlexible::onSetVolume(const LLVolumeParams &volume_params, const S32 detail) -{ -} - - -void LLVolumeImplFlexible::updateRenderRes() -{ - if (!mAttributes) - return; - - LLDrawable* drawablep = mVO->mDrawable; - - S32 new_res = mAttributes->getSimulateLOD(); - -#if 1 //optimal approximation of previous behavior that doesn't rely on atan2 - F32 app_angle = mVO->getScale().mV[2]/drawablep->mDistanceWRTCamera; - - // Rendering sections increases with visible angle on the screen - mRenderRes = (S32) (12.f*app_angle); -#else //legacy behavior - //number of segments only cares about z axis - F32 app_angle = ll_round((F32) atan2( mVO->getScale().mV[2]*2.f, drawablep->mDistanceWRTCamera) * RAD_TO_DEG, 0.01f); - - // Rendering sections increases with visible angle on the screen - mRenderRes = (S32)(FLEXIBLE_OBJECT_MAX_SECTIONS*4*app_angle*DEG_TO_RAD/LLViewerCamera::getInstance()->getView()); -#endif - - mRenderRes = llclamp(mRenderRes, new_res-1, (S32) FLEXIBLE_OBJECT_MAX_SECTIONS); - - // Throttle back simulation of segments we're not rendering - if (mRenderRes < new_res) - { - new_res = mRenderRes; - } - - if (!mInitialized || (mSimulateRes != new_res)) - { - mSimulateRes = new_res; - setAttributesOfAllSections(); - mInitialized = true; - } -} -//--------------------------------------------------------------------------------- -// This calculates the physics of the flexible object. Note that it has to be 0 -// updated every time step. In the future, perhaps there could be an -// optimization similar to what Havok does for objects that are stationary. -//--------------------------------------------------------------------------------- -void LLVolumeImplFlexible::doIdleUpdate() -{ - LLDrawable* drawablep = mVO->mDrawable; - - if (drawablep) - { - //ensure drawable is active - drawablep->makeActive(); - - if (gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_FLEXIBLE)) - { - bool visible = drawablep->isVisible(); - - if (mRenderRes == -1) - { - updateRenderRes(); - gPipeline.markRebuild(drawablep, LLDrawable::REBUILD_POSITION); - } - else - { - F32 pixel_area = mVO->getPixelArea(); - - // Note: Flexies afar will be rarely updated, closer ones will be updated more frequently. - // But frequency differences are extremely noticeable, so consider modifying update factor, - // or at least clamping value a bit more from both sides. - U32 update_period = (U32) (llmax((S32) (LLViewerCamera::getInstance()->getScreenPixelArea()*0.01f/(pixel_area*(sUpdateFactor+1.f))),0)+1); - // MAINT-1890 Clamp the update period to ensure that the update_period is no greater than 32 frames - update_period = llclamp(update_period, 1U, 32U); - - // We control how fast flexies update, buy splitting updates among frames - U64 virtual_frame_num = LLTimer::getElapsedSeconds() / SEC_PER_FLEXI_FRAME; - - if (visible) - { - if (!drawablep->isState(LLDrawable::IN_REBUILD_Q) && - pixel_area > 256.f) - { - U32 id; - if (mVO->isRootEdit()) - { - id = mID; - } - else - { - LLVOVolume* parent = (LLVOVolume*)mVO->getParent(); - id = parent->getVolumeInterfaceID(); - } - - - // Throttle flexies and spread load by preventing flexies from updating in same frame - // Shows how many frames we need to wait before next update - U64 throttling_delay = (virtual_frame_num + id) % update_period; - - if ((throttling_delay == 0 && mLastFrameNum < virtual_frame_num) //one or more virtual frames per frame - || (mLastFrameNum + update_period < virtual_frame_num) // missed virtual frame - || mLastFrameNum > virtual_frame_num) // overflow - { - // We need mLastFrameNum to compensate for 'unreliable time' and to filter 'duplicate' frames - // If happened too late, subtract throttling_delay (it is zero otherwise) - mLastFrameNum = virtual_frame_num - throttling_delay; - - // Store update period for updateClass() - // Note: Consider substituting update_period with mLastUpdatePeriod everywhere. - mLastUpdatePeriod = update_period; - - updateRenderRes(); - - mVO->shrinkWrap(); - gPipeline.markRebuild(drawablep, LLDrawable::REBUILD_POSITION); - } - } - } - else - { - mLastFrameNum = virtual_frame_num; - mLastUpdatePeriod = update_period; - } - } - - } - } -} - -inline S32 log2(S32 x) -{ - S32 ret = 0; - while (x > 1) - { - ++ret; - x >>= 1; - } - return ret; -} - -void LLVolumeImplFlexible::doFlexibleUpdate() -{ - LL_PROFILE_ZONE_SCOPED; - LLVolume* volume = mVO->getVolume(); - LLPath *path = &volume->getPath(); - if ((mSimulateRes == 0 || !mInitialized) && mVO->mDrawable->isVisible()) - { - bool force_update = mSimulateRes == 0; - doIdleUpdate(); - - if (!force_update || !gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_FLEXIBLE)) - { - return; // we did not get updated or initialized, proceeding without can be dangerous - } - } - - if (!mInitialized || !mAttributes) - { - //the object is not visible - return; - } - - // Fix for MAINT-1894 - // Skipping the flexible update if render res is negative. If we were to continue with a negative value, - // the subsequent S32 num_render_sections = 1< 0.2f) - { - secondsThisFrame = 0.2f; - } - - LLVector3 BasePosition = getFramePosition(); - LLQuaternion BaseRotation = getFrameRotation(); - LLQuaternion parentSegmentRotation = BaseRotation; - LLVector3 anchorDirectionRotated = LLVector3::z_axis * parentSegmentRotation; - LLVector3 anchorScale = mVO->mDrawable->getScale(); - - F32 section_length = anchorScale.mV[VZ] / (F32)num_sections; - F32 inv_section_length = 1.f / section_length; - - S32 i; - - // ANCHOR position is offset from BASE position (centroid) by half the length - LLVector3 AnchorPosition = BasePosition - (anchorScale.mV[VZ]/2 * anchorDirectionRotated); - - mSection[0].mPosition = AnchorPosition; - mSection[0].mDirection = anchorDirectionRotated; - mSection[0].mRotation = BaseRotation; - - LLQuaternion deltaRotation; - - LLVector3 lastPosition; - - // Coefficients which are constant across sections - F32 t_factor = mAttributes->getTension() * 0.1f; - t_factor = t_factor*(1 - pow(0.85f, secondsThisFrame*30)); - if ( t_factor > FLEXIBLE_OBJECT_MAX_INTERNAL_TENSION_FORCE ) - { - t_factor = FLEXIBLE_OBJECT_MAX_INTERNAL_TENSION_FORCE; - } - - F32 friction_coeff = (mAttributes->getAirFriction()*2+1); - friction_coeff = pow(10.f, friction_coeff*secondsThisFrame); - friction_coeff = (friction_coeff > 1) ? friction_coeff : 1; - F32 momentum = 1.0f / friction_coeff; - - F32 wind_factor = (mAttributes->getWindSensitivity()*0.1f) * section_length * secondsThisFrame; - F32 max_angle = atan(section_length*2.f); - - F32 force_factor = section_length * secondsThisFrame; - - // Update simulated sections - for (i=1; i<=num_sections; ++i) - { - LLVector3 parentSectionVector; - LLVector3 parentSectionPosition; - LLVector3 parentDirection; - - //--------------------------------------------------- - // save value of position as lastPosition - //--------------------------------------------------- - lastPosition = mSection[i].mPosition; - - //------------------------------------------------------------------------------------------ - // gravity - //------------------------------------------------------------------------------------------ - mSection[i].mPosition.mV[2] -= mAttributes->getGravity() * force_factor; - - //------------------------------------------------------------------------------------------ - // wind force - //------------------------------------------------------------------------------------------ - if (mAttributes->getWindSensitivity() > 0.001f) - { - mSection[i].mPosition += gAgent.getRegion()->mWind.getVelocity( mSection[i].mPosition ) * wind_factor; - } - - //------------------------------------------------------------------------------------------ - // user-defined force - //------------------------------------------------------------------------------------------ - mSection[i].mPosition += mAttributes->getUserForce() * force_factor; - - //--------------------------------------------------- - // tension (rigidity, stiffness) - //--------------------------------------------------- - parentSectionPosition = mSection[i-1].mPosition; - parentDirection = mSection[i-1].mDirection; - - if ( i == 1 ) - { - parentSectionVector = mSection[0].mDirection; - } - else - { - parentSectionVector = mSection[i-2].mDirection; - } - - LLVector3 currentVector = mSection[i].mPosition - parentSectionPosition; - - LLVector3 difference = (parentSectionVector*section_length) - currentVector; - LLVector3 tensionForce = difference * t_factor; - - mSection[i].mPosition += tensionForce; - - //------------------------------------------------------------------------------------------ - // sphere collision, currently not used - //------------------------------------------------------------------------------------------ - /*if ( mAttributes->mUsingCollisionSphere ) - { - LLVector3 vectorToCenterOfCollisionSphere = mCollisionSpherePosition - mSection[i].mPosition; - if ( vectorToCenterOfCollisionSphere.magVecSquared() < mCollisionSphereRadius * mCollisionSphereRadius ) - { - F32 distanceToCenterOfCollisionSphere = vectorToCenterOfCollisionSphere.magVec(); - F32 penetration = mCollisionSphereRadius - distanceToCenterOfCollisionSphere; - - LLVector3 normalToCenterOfCollisionSphere; - - if ( distanceToCenterOfCollisionSphere > 0.0f ) - { - normalToCenterOfCollisionSphere = vectorToCenterOfCollisionSphere / distanceToCenterOfCollisionSphere; - } - else // rare - { - normalToCenterOfCollisionSphere = LLVector3::x_axis; // arbitrary - } - - // push the position out to the surface of the collision sphere - mSection[i].mPosition -= normalToCenterOfCollisionSphere * penetration; - } - }*/ - - //------------------------------------------------------------------------------------------ - // inertia - //------------------------------------------------------------------------------------------ - mSection[i].mPosition += mSection[i].mVelocity * momentum; - - //------------------------------------------------------------------------------------------ - // clamp length & rotation - //------------------------------------------------------------------------------------------ - mSection[i].mDirection = mSection[i].mPosition - parentSectionPosition; - mSection[i].mDirection.normVec(); - deltaRotation.shortestArc( parentDirection, mSection[i].mDirection ); - - F32 angle; - LLVector3 axis; - deltaRotation.getAngleAxis(&angle, axis); - if (angle > F_PI) angle -= 2.f*F_PI; - if (angle < -F_PI) angle += 2.f*F_PI; - if (angle > max_angle) - { - //angle = 0.5f*(angle+max_angle); - deltaRotation.setQuat(max_angle, axis); - } else if (angle < -max_angle) - { - //angle = 0.5f*(angle-max_angle); - deltaRotation.setQuat(-max_angle, axis); - } - LLQuaternion segment_rotation = parentSegmentRotation * deltaRotation; - parentSegmentRotation = segment_rotation; - - mSection[i].mDirection = (parentDirection * deltaRotation); - mSection[i].mPosition = parentSectionPosition + mSection[i].mDirection * section_length; - mSection[i].mRotation = segment_rotation; - - if (i > 1) - { - // Propogate half the rotation up to the parent - LLQuaternion halfDeltaRotation(angle/2, axis); - mSection[i-1].mRotation = mSection[i-1].mRotation * halfDeltaRotation; - } - - //------------------------------------------------------------------------------------------ - // calculate velocity - //------------------------------------------------------------------------------------------ - mSection[i].mVelocity = mSection[i].mPosition - lastPosition; - if (mSection[i].mVelocity.magVecSquared() > 1.f) - { - mSection[i].mVelocity.normVec(); - } - } - - // Calculate derivatives (not necessary until normals are automagically generated) - mSection[0].mdPosition = (mSection[1].mPosition - mSection[0].mPosition) * inv_section_length; - // i = 1..NumSections-1 - for (i=1; i -1); - S32 num_render_sections = 1<getPathLength() != num_render_sections+1) - { - ((LLVOVolume*) mVO)->mVolumeChanged = true; - volume->resizePath(num_render_sections+1); - } - - LLPath::PathPt *new_point; - - LLFlexibleObjectSection newSection[ (1<mPath[i]; - LLVector3 pos = newSection[i].mPosition * rel_xform; - LLQuaternion rot = mSection[i].mAxisRotation * newSection[i].mRotation * delta_rot; - - LLVector3 np(new_point->mPos.getF32ptr()); - - if (!mUpdated || (np-pos).magVec()/mVO->mDrawable->mDistanceWRTCamera > 0.001f) - { - new_point->mPos.load3((newSection[i].mPosition * rel_xform).mV); - mUpdated = false; - } - - new_point->mRot.loadu(LLMatrix3(rot)); - new_point->mScale.set(newSection[i].mScale.mV[0], newSection[i].mScale.mV[1], 0,1); - new_point->mTexT = ((F32)i)/(num_render_sections); - } - LL_CHECK_MEMORY - mLastSegmentRotation = parentSegmentRotation; -} - - -void LLVolumeImplFlexible::preRebuild() -{ - if (!mUpdated) - { - LL_PROFILE_ZONE_SCOPED; - doFlexibleRebuild(false); - } -} - -void LLVolumeImplFlexible::doFlexibleRebuild(bool rebuild_volume) -{ - LLVolume* volume = mVO->getVolume(); - if (volume) - { - if (rebuild_volume) - { - volume->setDirty(); - } - volume->regen(); - } - - mUpdated = true; -} - -//------------------------------------------------------------------ - -void LLVolumeImplFlexible::onSetScale(const LLVector3& scale, bool damped) -{ - setAttributesOfAllSections((LLVector3*) &scale); -} - -bool LLVolumeImplFlexible::doUpdateGeometry(LLDrawable *drawable) -{ - LL_PROFILE_ZONE_SCOPED; - LLVOVolume *volume = (LLVOVolume*)mVO; - - if (mVO->isAttachment()) - { //don't update flexible attachments for impostored avatars unless the - //impostor is being updated this frame (w00!) - LLViewerObject* parent = (LLViewerObject*) mVO->getParent(); - while (parent && !parent->isAvatar()) - { - parent = (LLViewerObject*) parent->getParent(); - } - - if (parent) - { - LLVOAvatar* avatar = (LLVOAvatar*) parent; - if (avatar->isImpostor() && !avatar->needsImpostorUpdate()) - { - return true; - } - } - } - - if (volume->mDrawable.isNull()) - { - return true; // No update to complete - } - - if (volume->mLODChanged) - { - LLVolumeParams volume_params = volume->getVolume()->getParams(); - volume->setVolume(volume_params, 0); - mUpdated = false; - } - - volume->updateRelativeXform(); - - doFlexibleUpdate(); - - // Object may have been rotated, which means it needs a rebuild. See SL-47220 - bool rotated = false; - LLQuaternion cur_rotation = getFrameRotation(); - if ( cur_rotation != mLastFrameRotation ) - { - mLastFrameRotation = cur_rotation; - rotated = true; - } - - if (volume->mLODChanged || volume->mFaceMappingChanged || - volume->mVolumeChanged || drawable->isState(LLDrawable::REBUILD_MATERIAL)) - { - volume->regenFaces(); - volume->mDrawable->setState(LLDrawable::REBUILD_VOLUME); - volume->dirtySpatialGroup(); - { - doFlexibleRebuild(volume->mVolumeChanged); - } - volume->genBBoxes(isVolumeGlobal()); - } - else if (!mUpdated || rotated) - { - volume->mDrawable->setState(LLDrawable::REBUILD_POSITION); - LLSpatialGroup* group = volume->mDrawable->getSpatialGroup(); - if (group) - { - group->dirtyMesh(); - } - volume->genBBoxes(isVolumeGlobal()); - } - - volume->mVolumeChanged = false; - volume->mLODChanged = false; - volume->mFaceMappingChanged = false; - - // clear UV flag - drawable->clearState(LLDrawable::UV); - - return true; -} - -//---------------------------------------------------------------------------------- -void LLVolumeImplFlexible::setCollisionSphere( LLVector3 p, F32 r ) -{ - mCollisionSpherePosition = p; - mCollisionSphereRadius = r; - -}//------------------------------------------------------------------ - - -//---------------------------------------------------------------------------------- -void LLVolumeImplFlexible::setUsingCollisionSphere( bool u ) -{ -}//------------------------------------------------------------------ - - -//---------------------------------------------------------------------------------- -void LLVolumeImplFlexible::setRenderingCollisionSphere( bool r ) -{ -}//------------------------------------------------------------------ - -//------------------------------------------------------------------ -LLVector3 LLVolumeImplFlexible::getEndPosition() -{ - S32 num_sections = 1 << mAttributes->getSimulateLOD(); - return mSection[ num_sections ].mPosition; - -}//------------------------------------------------------------------ - - -//------------------------------------------------------------------ -LLVector3 LLVolumeImplFlexible::getNodePosition( int nodeIndex ) -{ - S32 num_sections = 1 << mAttributes->getSimulateLOD(); - if ( nodeIndex > num_sections - 1 ) - { - nodeIndex = num_sections - 1; - } - else if ( nodeIndex < 0 ) - { - nodeIndex = 0; - } - - return mSection[ nodeIndex ].mPosition; - -}//------------------------------------------------------------------ - -LLVector3 LLVolumeImplFlexible::getPivotPosition() const -{ - return getAnchorPosition(); -} - -//------------------------------------------------------------------ -LLVector3 LLVolumeImplFlexible::getAnchorPosition() const -{ - LLVector3 BasePosition = getFramePosition(); - LLQuaternion parentSegmentRotation = getFrameRotation(); - LLVector3 anchorDirectionRotated = LLVector3::z_axis * parentSegmentRotation; - LLVector3 anchorScale = mVO->mDrawable->getScale(); - return BasePosition - (anchorScale.mV[VZ]/2 * anchorDirectionRotated); - -}//------------------------------------------------------------------ - - -//------------------------------------------------------------------ -LLQuaternion LLVolumeImplFlexible::getEndRotation() -{ - return mLastSegmentRotation; - -}//------------------------------------------------------------------ - - -void LLVolumeImplFlexible::updateRelativeXform(bool force_identity) -{ - LLQuaternion delta_rot; - LLVector3 delta_pos, delta_scale; - LLVOVolume* vo = (LLVOVolume*) mVO; - - bool use_identity = vo->mDrawable->isSpatialRoot() || force_identity; - - //matrix from local space to parent relative/global space - delta_rot = use_identity ? LLQuaternion() : vo->mDrawable->getRotation(); - delta_pos = use_identity ? LLVector3(0,0,0) : vo->mDrawable->getPosition(); - delta_scale = LLVector3(1,1,1); - - // Vertex transform (4x4) - LLVector3 x_axis = LLVector3(delta_scale.mV[VX], 0.f, 0.f) * delta_rot; - LLVector3 y_axis = LLVector3(0.f, delta_scale.mV[VY], 0.f) * delta_rot; - LLVector3 z_axis = LLVector3(0.f, 0.f, delta_scale.mV[VZ]) * delta_rot; - - vo->mRelativeXform.initRows(LLVector4(x_axis, 0.f), - LLVector4(y_axis, 0.f), - LLVector4(z_axis, 0.f), - LLVector4(delta_pos, 1.f)); - - x_axis.normVec(); - y_axis.normVec(); - z_axis.normVec(); - - vo->mRelativeXformInvTrans.setRows(x_axis, y_axis, z_axis); -} - -const LLMatrix4& LLVolumeImplFlexible::getWorldMatrix(LLXformMatrix* xform) const -{ - return xform->getWorldMatrix(); -} +/** + * @file llflexibleobject.cpp + * @brief Flexible object implementation + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "pipeline.h" +#include "lldrawpoolbump.h" +#include "llface.h" +#include "llflexibleobject.h" +#include "llglheaders.h" +#include "llrendersphere.h" +#include "llviewerobject.h" +#include "llagent.h" +#include "llsky.h" +#include "llviewercamera.h" +#include "llviewertexturelist.h" +#include "llviewercontrol.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llworld.h" +#include "llvoavatar.h" + +static const F32 SEC_PER_FLEXI_FRAME = 1.f / 60.f; // 60 flexi updates per second +/*static*/ F32 LLVolumeImplFlexible::sUpdateFactor = 1.0f; +std::vector LLVolumeImplFlexible::sInstanceList; + +// LLFlexibleObjectData::pack/unpack now in llprimitive.cpp + +//----------------------------------------------- +// constructor +//----------------------------------------------- +LLVolumeImplFlexible::LLVolumeImplFlexible(LLViewerObject* vo, LLFlexibleObjectData* attributes) : + mVO(vo), + mAttributes(attributes), + mLastFrameNum(0), + mLastUpdatePeriod(0) +{ + static U32 seed = 0; + mID = seed++; + mInitialized = false; + mUpdated = false; + mInitializedRes = -1; + mSimulateRes = 0; + mCollisionSphereRadius = 0.f; + mRenderRes = -1; + + if(mVO->mDrawable.notNull()) + { + mVO->mDrawable->makeActive() ; + } + + mInstanceIndex = sInstanceList.size(); + sInstanceList.push_back(this); +}//----------------------------------------------- + +LLVolumeImplFlexible::~LLVolumeImplFlexible() +{ + S32 end_idx = sInstanceList.size()-1; + + if (end_idx != mInstanceIndex) + { + sInstanceList[mInstanceIndex] = sInstanceList[end_idx]; + sInstanceList[mInstanceIndex]->mInstanceIndex = mInstanceIndex; + } + + sInstanceList.pop_back(); +} + +//static +void LLVolumeImplFlexible::updateClass() +{ + LL_PROFILE_ZONE_SCOPED; + + U64 virtual_frame_num = LLTimer::getElapsedSeconds() / SEC_PER_FLEXI_FRAME; + for (std::vector::iterator iter = sInstanceList.begin(); + iter != sInstanceList.end(); + ++iter) + { + // Note: by now update period might have changed + if ((*iter)->mRenderRes == -1 + || (*iter)->mLastFrameNum + (*iter)->mLastUpdatePeriod <= virtual_frame_num + || (*iter)->mLastFrameNum > virtual_frame_num) //time issues, overflow + { + (*iter)->doIdleUpdate(); + } + } +} + +LLVector3 LLVolumeImplFlexible::getFramePosition() const +{ + return mVO->getRenderPosition(); +} + +LLQuaternion LLVolumeImplFlexible::getFrameRotation() const +{ + return mVO->getRenderRotation(); +} + +void LLVolumeImplFlexible::onParameterChanged(U16 param_type, LLNetworkData *data, bool in_use, bool local_origin) +{ + if (param_type == LLNetworkData::PARAMS_FLEXIBLE) + { + mAttributes = (LLFlexibleObjectData*)data; + setAttributesOfAllSections(); + } +} + +void LLVolumeImplFlexible::onShift(const LLVector4a &shift_vector) +{ + //VECTORIZE THIS + LLVector3 shift(shift_vector.getF32ptr()); + for (int section = 0; section < (1<mDrawable->getScale(); + F32 source_section_length = scale.mV[VZ] / (F32)(1< dest_sections) + { + // Copy, skipping sections + + S32 num_steps = 1<<(source_sections-dest_sections); + + // Copy from left to right since it may be an in-place computation + for (S32 section=0; section=0; section -= num_steps) + { + LLFlexibleObjectSection *last_source_section = &source[section>>step_shift]; + LLFlexibleObjectSection *source_section = &source[(section>>step_shift)+1]; + + // Cubic interpolation of position + // At^3 + Bt^2 + Ct + D = f(t) + LLVector3 D = last_source_section->mPosition; + LLVector3 C = last_source_section->mdPosition * source_section_length; + LLVector3 Y = source_section->mdPosition * source_section_length - C; // Helper var + LLVector3 X = (source_section->mPosition - D - C); // Helper var + LLVector3 A = Y - 2*X; + LLVector3 B = X - A; + + F32 t_inc = 1.f/F32(num_steps); + F32 t = t_inc; + for (S32 step=1; stepmScale, source_section->mScale, t); + dest[section+step].mAxisRotation = + slerp(t, last_source_section->mAxisRotation, source_section->mAxisRotation); + + // Evaluate output interpolated values + F32 t_sq = t*t; + dest[section+step].mPosition = t_sq*(t*A + B) + t*C + D; + dest[section+step].mRotation = + slerp(t, last_source_section->mRotation, source_section->mRotation); + dest[section+step].mVelocity = lerp(last_source_section->mVelocity, source_section->mVelocity, t); + dest[section+step].mDirection = lerp(last_source_section->mDirection, source_section->mDirection, t); + dest[section+step].mdPosition = lerp(last_source_section->mdPosition, source_section->mdPosition, t); + dest[section+num_steps] = *source_section; + t += t_inc; + } + } + dest[0] = source[0]; + } + else + { + // numbers are equal. copy info + for (S32 section=0; section <= num_output_sections; ++section) + { + dest[section] = source[section]; + } + } +} + +//----------------------------------------------------------------------------- +void LLVolumeImplFlexible::setAttributesOfAllSections(LLVector3* inScale) +{ + LLVector2 bottom_scale, top_scale; + F32 begin_rot = 0, end_rot = 0; + if (mVO->getVolume()) + { + const LLPathParams ¶ms = mVO->getVolume()->getParams().getPathParams(); + bottom_scale = params.getBeginScale(); + top_scale = params.getEndScale(); + begin_rot = F_PI * params.getTwistBegin(); + end_rot = F_PI * params.getTwist(); + } + + if (!mVO->mDrawable) + { + return; + } + + S32 num_sections = 1 << mSimulateRes; + + LLVector3 scale; + if (inScale == (LLVector3*)NULL) + { + scale = mVO->mDrawable->getScale(); + } + else + { + scale = *inScale; + } + + mSection[0].mPosition = getAnchorPosition(); + mSection[0].mDirection = LLVector3::z_axis * getFrameRotation(); + mSection[0].mdPosition = mSection[0].mDirection; + mSection[0].mScale.setVec(scale.mV[VX]*bottom_scale.mV[0], scale.mV[VY]*bottom_scale.mV[1]); + mSection[0].mVelocity.setVec(0,0,0); + mSection[0].mAxisRotation.setQuat(begin_rot,0,0,1); + + remapSections(mSection, mInitializedRes, mSection, mSimulateRes); + mInitializedRes = mSimulateRes; + + F32 t_inc = 1.f/F32(num_sections); + F32 t = t_inc; + + for ( int i=1; i<= num_sections; i++) + { + mSection[i].mAxisRotation.setQuat(lerp(begin_rot,end_rot,t),0,0,1); + mSection[i].mScale = LLVector2( + scale.mV[VX] * lerp(bottom_scale.mV[0], top_scale.mV[0], t), + scale.mV[VY] * lerp(bottom_scale.mV[1], top_scale.mV[1], t)); + t += t_inc; + } +}//----------------------------------------------------------------------------------- + + +void LLVolumeImplFlexible::onSetVolume(const LLVolumeParams &volume_params, const S32 detail) +{ +} + + +void LLVolumeImplFlexible::updateRenderRes() +{ + if (!mAttributes) + return; + + LLDrawable* drawablep = mVO->mDrawable; + + S32 new_res = mAttributes->getSimulateLOD(); + +#if 1 //optimal approximation of previous behavior that doesn't rely on atan2 + F32 app_angle = mVO->getScale().mV[2]/drawablep->mDistanceWRTCamera; + + // Rendering sections increases with visible angle on the screen + mRenderRes = (S32) (12.f*app_angle); +#else //legacy behavior + //number of segments only cares about z axis + F32 app_angle = ll_round((F32) atan2( mVO->getScale().mV[2]*2.f, drawablep->mDistanceWRTCamera) * RAD_TO_DEG, 0.01f); + + // Rendering sections increases with visible angle on the screen + mRenderRes = (S32)(FLEXIBLE_OBJECT_MAX_SECTIONS*4*app_angle*DEG_TO_RAD/LLViewerCamera::getInstance()->getView()); +#endif + + mRenderRes = llclamp(mRenderRes, new_res-1, (S32) FLEXIBLE_OBJECT_MAX_SECTIONS); + + // Throttle back simulation of segments we're not rendering + if (mRenderRes < new_res) + { + new_res = mRenderRes; + } + + if (!mInitialized || (mSimulateRes != new_res)) + { + mSimulateRes = new_res; + setAttributesOfAllSections(); + mInitialized = true; + } +} +//--------------------------------------------------------------------------------- +// This calculates the physics of the flexible object. Note that it has to be 0 +// updated every time step. In the future, perhaps there could be an +// optimization similar to what Havok does for objects that are stationary. +//--------------------------------------------------------------------------------- +void LLVolumeImplFlexible::doIdleUpdate() +{ + LLDrawable* drawablep = mVO->mDrawable; + + if (drawablep) + { + //ensure drawable is active + drawablep->makeActive(); + + if (gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_FLEXIBLE)) + { + bool visible = drawablep->isVisible(); + + if (mRenderRes == -1) + { + updateRenderRes(); + gPipeline.markRebuild(drawablep, LLDrawable::REBUILD_POSITION); + } + else + { + F32 pixel_area = mVO->getPixelArea(); + + // Note: Flexies afar will be rarely updated, closer ones will be updated more frequently. + // But frequency differences are extremely noticeable, so consider modifying update factor, + // or at least clamping value a bit more from both sides. + U32 update_period = (U32) (llmax((S32) (LLViewerCamera::getInstance()->getScreenPixelArea()*0.01f/(pixel_area*(sUpdateFactor+1.f))),0)+1); + // MAINT-1890 Clamp the update period to ensure that the update_period is no greater than 32 frames + update_period = llclamp(update_period, 1U, 32U); + + // We control how fast flexies update, buy splitting updates among frames + U64 virtual_frame_num = LLTimer::getElapsedSeconds() / SEC_PER_FLEXI_FRAME; + + if (visible) + { + if (!drawablep->isState(LLDrawable::IN_REBUILD_Q) && + pixel_area > 256.f) + { + U32 id; + if (mVO->isRootEdit()) + { + id = mID; + } + else + { + LLVOVolume* parent = (LLVOVolume*)mVO->getParent(); + id = parent->getVolumeInterfaceID(); + } + + + // Throttle flexies and spread load by preventing flexies from updating in same frame + // Shows how many frames we need to wait before next update + U64 throttling_delay = (virtual_frame_num + id) % update_period; + + if ((throttling_delay == 0 && mLastFrameNum < virtual_frame_num) //one or more virtual frames per frame + || (mLastFrameNum + update_period < virtual_frame_num) // missed virtual frame + || mLastFrameNum > virtual_frame_num) // overflow + { + // We need mLastFrameNum to compensate for 'unreliable time' and to filter 'duplicate' frames + // If happened too late, subtract throttling_delay (it is zero otherwise) + mLastFrameNum = virtual_frame_num - throttling_delay; + + // Store update period for updateClass() + // Note: Consider substituting update_period with mLastUpdatePeriod everywhere. + mLastUpdatePeriod = update_period; + + updateRenderRes(); + + mVO->shrinkWrap(); + gPipeline.markRebuild(drawablep, LLDrawable::REBUILD_POSITION); + } + } + } + else + { + mLastFrameNum = virtual_frame_num; + mLastUpdatePeriod = update_period; + } + } + + } + } +} + +inline S32 log2(S32 x) +{ + S32 ret = 0; + while (x > 1) + { + ++ret; + x >>= 1; + } + return ret; +} + +void LLVolumeImplFlexible::doFlexibleUpdate() +{ + LL_PROFILE_ZONE_SCOPED; + LLVolume* volume = mVO->getVolume(); + LLPath *path = &volume->getPath(); + if ((mSimulateRes == 0 || !mInitialized) && mVO->mDrawable->isVisible()) + { + bool force_update = mSimulateRes == 0; + doIdleUpdate(); + + if (!force_update || !gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_FLEXIBLE)) + { + return; // we did not get updated or initialized, proceeding without can be dangerous + } + } + + if (!mInitialized || !mAttributes) + { + //the object is not visible + return; + } + + // Fix for MAINT-1894 + // Skipping the flexible update if render res is negative. If we were to continue with a negative value, + // the subsequent S32 num_render_sections = 1< 0.2f) + { + secondsThisFrame = 0.2f; + } + + LLVector3 BasePosition = getFramePosition(); + LLQuaternion BaseRotation = getFrameRotation(); + LLQuaternion parentSegmentRotation = BaseRotation; + LLVector3 anchorDirectionRotated = LLVector3::z_axis * parentSegmentRotation; + LLVector3 anchorScale = mVO->mDrawable->getScale(); + + F32 section_length = anchorScale.mV[VZ] / (F32)num_sections; + F32 inv_section_length = 1.f / section_length; + + S32 i; + + // ANCHOR position is offset from BASE position (centroid) by half the length + LLVector3 AnchorPosition = BasePosition - (anchorScale.mV[VZ]/2 * anchorDirectionRotated); + + mSection[0].mPosition = AnchorPosition; + mSection[0].mDirection = anchorDirectionRotated; + mSection[0].mRotation = BaseRotation; + + LLQuaternion deltaRotation; + + LLVector3 lastPosition; + + // Coefficients which are constant across sections + F32 t_factor = mAttributes->getTension() * 0.1f; + t_factor = t_factor*(1 - pow(0.85f, secondsThisFrame*30)); + if ( t_factor > FLEXIBLE_OBJECT_MAX_INTERNAL_TENSION_FORCE ) + { + t_factor = FLEXIBLE_OBJECT_MAX_INTERNAL_TENSION_FORCE; + } + + F32 friction_coeff = (mAttributes->getAirFriction()*2+1); + friction_coeff = pow(10.f, friction_coeff*secondsThisFrame); + friction_coeff = (friction_coeff > 1) ? friction_coeff : 1; + F32 momentum = 1.0f / friction_coeff; + + F32 wind_factor = (mAttributes->getWindSensitivity()*0.1f) * section_length * secondsThisFrame; + F32 max_angle = atan(section_length*2.f); + + F32 force_factor = section_length * secondsThisFrame; + + // Update simulated sections + for (i=1; i<=num_sections; ++i) + { + LLVector3 parentSectionVector; + LLVector3 parentSectionPosition; + LLVector3 parentDirection; + + //--------------------------------------------------- + // save value of position as lastPosition + //--------------------------------------------------- + lastPosition = mSection[i].mPosition; + + //------------------------------------------------------------------------------------------ + // gravity + //------------------------------------------------------------------------------------------ + mSection[i].mPosition.mV[2] -= mAttributes->getGravity() * force_factor; + + //------------------------------------------------------------------------------------------ + // wind force + //------------------------------------------------------------------------------------------ + if (mAttributes->getWindSensitivity() > 0.001f) + { + mSection[i].mPosition += gAgent.getRegion()->mWind.getVelocity( mSection[i].mPosition ) * wind_factor; + } + + //------------------------------------------------------------------------------------------ + // user-defined force + //------------------------------------------------------------------------------------------ + mSection[i].mPosition += mAttributes->getUserForce() * force_factor; + + //--------------------------------------------------- + // tension (rigidity, stiffness) + //--------------------------------------------------- + parentSectionPosition = mSection[i-1].mPosition; + parentDirection = mSection[i-1].mDirection; + + if ( i == 1 ) + { + parentSectionVector = mSection[0].mDirection; + } + else + { + parentSectionVector = mSection[i-2].mDirection; + } + + LLVector3 currentVector = mSection[i].mPosition - parentSectionPosition; + + LLVector3 difference = (parentSectionVector*section_length) - currentVector; + LLVector3 tensionForce = difference * t_factor; + + mSection[i].mPosition += tensionForce; + + //------------------------------------------------------------------------------------------ + // sphere collision, currently not used + //------------------------------------------------------------------------------------------ + /*if ( mAttributes->mUsingCollisionSphere ) + { + LLVector3 vectorToCenterOfCollisionSphere = mCollisionSpherePosition - mSection[i].mPosition; + if ( vectorToCenterOfCollisionSphere.magVecSquared() < mCollisionSphereRadius * mCollisionSphereRadius ) + { + F32 distanceToCenterOfCollisionSphere = vectorToCenterOfCollisionSphere.magVec(); + F32 penetration = mCollisionSphereRadius - distanceToCenterOfCollisionSphere; + + LLVector3 normalToCenterOfCollisionSphere; + + if ( distanceToCenterOfCollisionSphere > 0.0f ) + { + normalToCenterOfCollisionSphere = vectorToCenterOfCollisionSphere / distanceToCenterOfCollisionSphere; + } + else // rare + { + normalToCenterOfCollisionSphere = LLVector3::x_axis; // arbitrary + } + + // push the position out to the surface of the collision sphere + mSection[i].mPosition -= normalToCenterOfCollisionSphere * penetration; + } + }*/ + + //------------------------------------------------------------------------------------------ + // inertia + //------------------------------------------------------------------------------------------ + mSection[i].mPosition += mSection[i].mVelocity * momentum; + + //------------------------------------------------------------------------------------------ + // clamp length & rotation + //------------------------------------------------------------------------------------------ + mSection[i].mDirection = mSection[i].mPosition - parentSectionPosition; + mSection[i].mDirection.normVec(); + deltaRotation.shortestArc( parentDirection, mSection[i].mDirection ); + + F32 angle; + LLVector3 axis; + deltaRotation.getAngleAxis(&angle, axis); + if (angle > F_PI) angle -= 2.f*F_PI; + if (angle < -F_PI) angle += 2.f*F_PI; + if (angle > max_angle) + { + //angle = 0.5f*(angle+max_angle); + deltaRotation.setQuat(max_angle, axis); + } else if (angle < -max_angle) + { + //angle = 0.5f*(angle-max_angle); + deltaRotation.setQuat(-max_angle, axis); + } + LLQuaternion segment_rotation = parentSegmentRotation * deltaRotation; + parentSegmentRotation = segment_rotation; + + mSection[i].mDirection = (parentDirection * deltaRotation); + mSection[i].mPosition = parentSectionPosition + mSection[i].mDirection * section_length; + mSection[i].mRotation = segment_rotation; + + if (i > 1) + { + // Propogate half the rotation up to the parent + LLQuaternion halfDeltaRotation(angle/2, axis); + mSection[i-1].mRotation = mSection[i-1].mRotation * halfDeltaRotation; + } + + //------------------------------------------------------------------------------------------ + // calculate velocity + //------------------------------------------------------------------------------------------ + mSection[i].mVelocity = mSection[i].mPosition - lastPosition; + if (mSection[i].mVelocity.magVecSquared() > 1.f) + { + mSection[i].mVelocity.normVec(); + } + } + + // Calculate derivatives (not necessary until normals are automagically generated) + mSection[0].mdPosition = (mSection[1].mPosition - mSection[0].mPosition) * inv_section_length; + // i = 1..NumSections-1 + for (i=1; i -1); + S32 num_render_sections = 1<getPathLength() != num_render_sections+1) + { + ((LLVOVolume*) mVO)->mVolumeChanged = true; + volume->resizePath(num_render_sections+1); + } + + LLPath::PathPt *new_point; + + LLFlexibleObjectSection newSection[ (1<mPath[i]; + LLVector3 pos = newSection[i].mPosition * rel_xform; + LLQuaternion rot = mSection[i].mAxisRotation * newSection[i].mRotation * delta_rot; + + LLVector3 np(new_point->mPos.getF32ptr()); + + if (!mUpdated || (np-pos).magVec()/mVO->mDrawable->mDistanceWRTCamera > 0.001f) + { + new_point->mPos.load3((newSection[i].mPosition * rel_xform).mV); + mUpdated = false; + } + + new_point->mRot.loadu(LLMatrix3(rot)); + new_point->mScale.set(newSection[i].mScale.mV[0], newSection[i].mScale.mV[1], 0,1); + new_point->mTexT = ((F32)i)/(num_render_sections); + } + LL_CHECK_MEMORY + mLastSegmentRotation = parentSegmentRotation; +} + + +void LLVolumeImplFlexible::preRebuild() +{ + if (!mUpdated) + { + LL_PROFILE_ZONE_SCOPED; + doFlexibleRebuild(false); + } +} + +void LLVolumeImplFlexible::doFlexibleRebuild(bool rebuild_volume) +{ + LLVolume* volume = mVO->getVolume(); + if (volume) + { + if (rebuild_volume) + { + volume->setDirty(); + } + volume->regen(); + } + + mUpdated = true; +} + +//------------------------------------------------------------------ + +void LLVolumeImplFlexible::onSetScale(const LLVector3& scale, bool damped) +{ + setAttributesOfAllSections((LLVector3*) &scale); +} + +bool LLVolumeImplFlexible::doUpdateGeometry(LLDrawable *drawable) +{ + LL_PROFILE_ZONE_SCOPED; + LLVOVolume *volume = (LLVOVolume*)mVO; + + if (mVO->isAttachment()) + { //don't update flexible attachments for impostored avatars unless the + //impostor is being updated this frame (w00!) + LLViewerObject* parent = (LLViewerObject*) mVO->getParent(); + while (parent && !parent->isAvatar()) + { + parent = (LLViewerObject*) parent->getParent(); + } + + if (parent) + { + LLVOAvatar* avatar = (LLVOAvatar*) parent; + if (avatar->isImpostor() && !avatar->needsImpostorUpdate()) + { + return true; + } + } + } + + if (volume->mDrawable.isNull()) + { + return true; // No update to complete + } + + if (volume->mLODChanged) + { + LLVolumeParams volume_params = volume->getVolume()->getParams(); + volume->setVolume(volume_params, 0); + mUpdated = false; + } + + volume->updateRelativeXform(); + + doFlexibleUpdate(); + + // Object may have been rotated, which means it needs a rebuild. See SL-47220 + bool rotated = false; + LLQuaternion cur_rotation = getFrameRotation(); + if ( cur_rotation != mLastFrameRotation ) + { + mLastFrameRotation = cur_rotation; + rotated = true; + } + + if (volume->mLODChanged || volume->mFaceMappingChanged || + volume->mVolumeChanged || drawable->isState(LLDrawable::REBUILD_MATERIAL)) + { + volume->regenFaces(); + volume->mDrawable->setState(LLDrawable::REBUILD_VOLUME); + volume->dirtySpatialGroup(); + { + doFlexibleRebuild(volume->mVolumeChanged); + } + volume->genBBoxes(isVolumeGlobal()); + } + else if (!mUpdated || rotated) + { + volume->mDrawable->setState(LLDrawable::REBUILD_POSITION); + LLSpatialGroup* group = volume->mDrawable->getSpatialGroup(); + if (group) + { + group->dirtyMesh(); + } + volume->genBBoxes(isVolumeGlobal()); + } + + volume->mVolumeChanged = false; + volume->mLODChanged = false; + volume->mFaceMappingChanged = false; + + // clear UV flag + drawable->clearState(LLDrawable::UV); + + return true; +} + +//---------------------------------------------------------------------------------- +void LLVolumeImplFlexible::setCollisionSphere( LLVector3 p, F32 r ) +{ + mCollisionSpherePosition = p; + mCollisionSphereRadius = r; + +}//------------------------------------------------------------------ + + +//---------------------------------------------------------------------------------- +void LLVolumeImplFlexible::setUsingCollisionSphere( bool u ) +{ +}//------------------------------------------------------------------ + + +//---------------------------------------------------------------------------------- +void LLVolumeImplFlexible::setRenderingCollisionSphere( bool r ) +{ +}//------------------------------------------------------------------ + +//------------------------------------------------------------------ +LLVector3 LLVolumeImplFlexible::getEndPosition() +{ + S32 num_sections = 1 << mAttributes->getSimulateLOD(); + return mSection[ num_sections ].mPosition; + +}//------------------------------------------------------------------ + + +//------------------------------------------------------------------ +LLVector3 LLVolumeImplFlexible::getNodePosition( int nodeIndex ) +{ + S32 num_sections = 1 << mAttributes->getSimulateLOD(); + if ( nodeIndex > num_sections - 1 ) + { + nodeIndex = num_sections - 1; + } + else if ( nodeIndex < 0 ) + { + nodeIndex = 0; + } + + return mSection[ nodeIndex ].mPosition; + +}//------------------------------------------------------------------ + +LLVector3 LLVolumeImplFlexible::getPivotPosition() const +{ + return getAnchorPosition(); +} + +//------------------------------------------------------------------ +LLVector3 LLVolumeImplFlexible::getAnchorPosition() const +{ + LLVector3 BasePosition = getFramePosition(); + LLQuaternion parentSegmentRotation = getFrameRotation(); + LLVector3 anchorDirectionRotated = LLVector3::z_axis * parentSegmentRotation; + LLVector3 anchorScale = mVO->mDrawable->getScale(); + return BasePosition - (anchorScale.mV[VZ]/2 * anchorDirectionRotated); + +}//------------------------------------------------------------------ + + +//------------------------------------------------------------------ +LLQuaternion LLVolumeImplFlexible::getEndRotation() +{ + return mLastSegmentRotation; + +}//------------------------------------------------------------------ + + +void LLVolumeImplFlexible::updateRelativeXform(bool force_identity) +{ + LLQuaternion delta_rot; + LLVector3 delta_pos, delta_scale; + LLVOVolume* vo = (LLVOVolume*) mVO; + + bool use_identity = vo->mDrawable->isSpatialRoot() || force_identity; + + //matrix from local space to parent relative/global space + delta_rot = use_identity ? LLQuaternion() : vo->mDrawable->getRotation(); + delta_pos = use_identity ? LLVector3(0,0,0) : vo->mDrawable->getPosition(); + delta_scale = LLVector3(1,1,1); + + // Vertex transform (4x4) + LLVector3 x_axis = LLVector3(delta_scale.mV[VX], 0.f, 0.f) * delta_rot; + LLVector3 y_axis = LLVector3(0.f, delta_scale.mV[VY], 0.f) * delta_rot; + LLVector3 z_axis = LLVector3(0.f, 0.f, delta_scale.mV[VZ]) * delta_rot; + + vo->mRelativeXform.initRows(LLVector4(x_axis, 0.f), + LLVector4(y_axis, 0.f), + LLVector4(z_axis, 0.f), + LLVector4(delta_pos, 1.f)); + + x_axis.normVec(); + y_axis.normVec(); + z_axis.normVec(); + + vo->mRelativeXformInvTrans.setRows(x_axis, y_axis, z_axis); +} + +const LLMatrix4& LLVolumeImplFlexible::getWorldMatrix(LLXformMatrix* xform) const +{ + return xform->getWorldMatrix(); +} diff --git a/indra/newview/llflexibleobject.h b/indra/newview/llflexibleobject.h index 86ab88b62a..958bf9029c 100644 --- a/indra/newview/llflexibleobject.h +++ b/indra/newview/llflexibleobject.h @@ -1,156 +1,156 @@ -/** - * @file llflexibleobject.h - * @author JJ Ventrella, Andrew Meadows, Tom Yedwab - * @brief Flexible object definition - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/** - * This is for specifying objects in the world that are animated and - * rendered locally - on the viewer. Flexible Objects are linear arrays - * of positions, which stay at a fixed distance from each other. One - * position is fixed as an "anchor" and is attached to some other object - * in the world, determined by the server. All the other positions are - * updated according to local physics. - */ - -#ifndef LL_LLFLEXIBLEOBJECT_H -#define LL_LLFLEXIBLEOBJECT_H - -#include "llprimitive.h" -#include "llvovolume.h" -#include "llwind.h" - -// 10 ms for the whole thing! -const F32 FLEXIBLE_OBJECT_TIMESLICE = 0.003f; -const U32 FLEXIBLE_OBJECT_MAX_LOD = 10; - -// See llprimitive.h for LLFlexibleObjectData and DEFAULT/MIN/MAX values - -//------------------------------------------------------------------- - -struct LLFlexibleObjectSection -{ - // Input parameters - LLVector2 mScale; - LLQuaternion mAxisRotation; - // Simulated state - LLVector3 mPosition; - LLVector3 mVelocity; - LLVector3 mDirection; - LLQuaternion mRotation; - // Derivatives (Not all currently used, will come back with LLVolume changes to automagically generate normals) - LLVector3 mdPosition; - //LLMatrix4 mRotScale; - //LLMatrix4 mdRotScale; -}; - -//--------------------------------------------------------- -// The LLVolumeImplFlexible class -//--------------------------------------------------------- -class LLVolumeImplFlexible : public LLVolumeInterface -{ -private: - static std::vector sInstanceList; - S32 mInstanceIndex; - - public: - static void updateClass(); - - LLVolumeImplFlexible(LLViewerObject* volume, LLFlexibleObjectData* attributes); - ~LLVolumeImplFlexible(); - - // Implements LLVolumeInterface - U32 getID() const { return mID; } - LLVector3 getFramePosition() const; - LLQuaternion getFrameRotation() const; - LLVolumeInterfaceType getInterfaceType() const { return INTERFACE_FLEXIBLE; } - void updateRenderRes(); - void doIdleUpdate(); - bool doUpdateGeometry(LLDrawable *drawable); - LLVector3 getPivotPosition() const; - void onSetVolume(const LLVolumeParams &volume_params, const S32 detail); - void onSetScale(const LLVector3 &scale, bool damped); - void onParameterChanged(U16 param_type, LLNetworkData *data, bool in_use, bool local_origin); - void onShift(const LLVector4a &shift_vector); - bool isVolumeUnique() const { return true; } - bool isVolumeGlobal() const { return true; } - bool isActive() const { return true; } - const LLMatrix4& getWorldMatrix(LLXformMatrix* xform) const; - void updateRelativeXform(bool force_identity); - void doFlexibleUpdate(); // Called to update the simulation - void doFlexibleRebuild(bool rebuild_volume); // Called to rebuild the geometry - void preRebuild(); - - //void setAttributes( LLFlexibleObjectData ); - void setParentPositionAndRotationDirectly( LLVector3 p, LLQuaternion r ); - void setUsingCollisionSphere( bool u ); - void setCollisionSphere( LLVector3 position, F32 radius ); - void setRenderingCollisionSphere( bool r); - - LLVector3 getEndPosition(); - LLQuaternion getEndRotation(); - LLVector3 getNodePosition( int nodeIndex ); - LLVector3 getAnchorPosition() const; - - private: - //-------------------------------------- - // private members - //-------------------------------------- - // Backlink only; don't make this an LLPointer. - LLViewerObject* mVO; - LLTimer mTimer; - LLVector3 mAnchorPosition; - LLVector3 mParentPosition; - LLQuaternion mParentRotation; - LLQuaternion mLastFrameRotation; - LLQuaternion mLastSegmentRotation; - bool mInitialized; - bool mUpdated; - LLFlexibleObjectData* mAttributes; - LLFlexibleObjectSection mSection [ (1< sInstanceList; + S32 mInstanceIndex; + + public: + static void updateClass(); + + LLVolumeImplFlexible(LLViewerObject* volume, LLFlexibleObjectData* attributes); + ~LLVolumeImplFlexible(); + + // Implements LLVolumeInterface + U32 getID() const { return mID; } + LLVector3 getFramePosition() const; + LLQuaternion getFrameRotation() const; + LLVolumeInterfaceType getInterfaceType() const { return INTERFACE_FLEXIBLE; } + void updateRenderRes(); + void doIdleUpdate(); + bool doUpdateGeometry(LLDrawable *drawable); + LLVector3 getPivotPosition() const; + void onSetVolume(const LLVolumeParams &volume_params, const S32 detail); + void onSetScale(const LLVector3 &scale, bool damped); + void onParameterChanged(U16 param_type, LLNetworkData *data, bool in_use, bool local_origin); + void onShift(const LLVector4a &shift_vector); + bool isVolumeUnique() const { return true; } + bool isVolumeGlobal() const { return true; } + bool isActive() const { return true; } + const LLMatrix4& getWorldMatrix(LLXformMatrix* xform) const; + void updateRelativeXform(bool force_identity); + void doFlexibleUpdate(); // Called to update the simulation + void doFlexibleRebuild(bool rebuild_volume); // Called to rebuild the geometry + void preRebuild(); + + //void setAttributes( LLFlexibleObjectData ); + void setParentPositionAndRotationDirectly( LLVector3 p, LLQuaternion r ); + void setUsingCollisionSphere( bool u ); + void setCollisionSphere( LLVector3 position, F32 radius ); + void setRenderingCollisionSphere( bool r); + + LLVector3 getEndPosition(); + LLQuaternion getEndRotation(); + LLVector3 getNodePosition( int nodeIndex ); + LLVector3 getAnchorPosition() const; + + private: + //-------------------------------------- + // private members + //-------------------------------------- + // Backlink only; don't make this an LLPointer. + LLViewerObject* mVO; + LLTimer mTimer; + LLVector3 mAnchorPosition; + LLVector3 mParentPosition; + LLQuaternion mParentRotation; + LLQuaternion mLastFrameRotation; + LLQuaternion mLastSegmentRotation; + bool mInitialized; + bool mUpdated; + LLFlexibleObjectData* mAttributes; + LLFlexibleObjectSection mSection [ (1<About - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include -#include - -#include "llfloaterabout.h" - -// Viewer includes -#include "llagent.h" -#include "llagentui.h" -#include "llappviewer.h" -#include "llnotificationsutil.h" -#include "llslurl.h" -#include "llvoiceclient.h" -#include "lluictrlfactory.h" -#include "llviewertexteditor.h" -#include "llviewercontrol.h" -#include "llviewerstats.h" -#include "llviewerregion.h" -#include "llversioninfo.h" -#include "llweb.h" - -// Linden library includes -#include "llaudioengine.h" -#include "llbutton.h" -#include "llglheaders.h" -#include "llfloater.h" -#include "llfloaterreg.h" -#include "llimagej2c.h" -#include "llsys.h" -#include "lltrans.h" -#include "lluri.h" -#include "v3dmath.h" -#include "llwindow.h" -#include "stringize.h" -#include "llsdutil_math.h" -#include "lleventapi.h" -#include "llcorehttputil.h" -#include "lldir.h" - -#if LL_WINDOWS -#include "lldxhardware.h" -#endif - -extern LLMemoryInfo gSysMemory; -extern U32 gPacketsIn; - -///---------------------------------------------------------------------------- -/// Class LLFloaterAbout -///---------------------------------------------------------------------------- -class LLFloaterAbout - : public LLFloater -{ - friend class LLFloaterReg; -private: - LLFloaterAbout(const LLSD& key); - virtual ~LLFloaterAbout(); - -public: - bool postBuild() override; - - /// Obtain the data used to fill out the contents string. This is - /// separated so that we can programmatically access the same info. - static LLSD getInfo(); - void onClickCopyToClipboard(); - void onClickUpdateCheck(); - static void setUpdateListener(); - -private: - void setSupportText(const std::string& server_release_notes_url); - - // notifications for user requested checks - static void showCheckUpdateNotification(S32 state); - - // callback method for manual checks - static bool callbackCheckUpdate(LLSD const & event); - - // listener name for update checks - static const std::string sCheckUpdateListenerName; - - static void startFetchServerReleaseNotes(); - static void fetchServerReleaseNotesCoro(const std::string& cap_url); - static void handleServerReleaseNotes(LLSD results); -}; - - -// Default constructor -LLFloaterAbout::LLFloaterAbout(const LLSD& key) -: LLFloater(key) -{ - -} - -// Destroys the object -LLFloaterAbout::~LLFloaterAbout() -{ -} - -bool LLFloaterAbout::postBuild() -{ - center(); - LLViewerTextEditor *support_widget = - getChild("support_editor", true); - - LLViewerTextEditor *contrib_names_widget = - getChild("contrib_names", true); - - LLViewerTextEditor *licenses_widget = - getChild("licenses_editor", true); - - getChild("copy_btn")->setCommitCallback( - boost::bind(&LLFloaterAbout::onClickCopyToClipboard, this)); - - getChild("update_btn")->setCommitCallback( - boost::bind(&LLFloaterAbout::onClickUpdateCheck, this)); - - static const LLUIColor about_color = LLUIColorTable::instance().getColor("TextFgReadOnlyColor"); - - if (gAgent.getRegion()) - { - // start fetching server release notes URL - setSupportText(LLTrans::getString("RetrievingData")); - startFetchServerReleaseNotes(); - } - else // not logged in - { - LL_DEBUGS("ViewerInfo") << "cannot display region info when not connected" << LL_ENDL; - setSupportText(LLTrans::getString("NotConnected")); - } - - support_widget->blockUndo(); - - // Fix views - support_widget->setEnabled(false); - support_widget->startOfDoc(); - - // Get the names of contributors, extracted from .../doc/contributions.txt by viewer_manifest.py at build time - std::string contributors_path = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS,"contributors.txt"); - llifstream contrib_file; - std::string contributors; - contrib_file.open(contributors_path.c_str()); /* Flawfinder: ignore */ - if (contrib_file.is_open()) - { - std::getline(contrib_file, contributors); // all names are on a single line - contrib_file.close(); - } - else - { - LL_WARNS("AboutInit") << "Could not read contributors file at " << contributors_path << LL_ENDL; - } - contrib_names_widget->setText(contributors); - contrib_names_widget->setEnabled(false); - contrib_names_widget->startOfDoc(); - - // Get the Versions and Copyrights, created at build time - std::string licenses_path = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS,"packages-info.txt"); - llifstream licenses_file; - licenses_file.open(licenses_path.c_str()); /* Flawfinder: ignore */ - if (licenses_file.is_open()) - { - std::string license_line; - licenses_widget->clear(); - while ( std::getline(licenses_file, license_line) ) - { - licenses_widget->appendText(license_line+"\n", false, - LLStyle::Params() .color(about_color)); - } - licenses_file.close(); - } - else - { - // this case will use the (out of date) hard coded value from the XUI - LL_INFOS("AboutInit") << "Could not read licenses file at " << licenses_path << LL_ENDL; - } - licenses_widget->setEnabled(false); - licenses_widget->startOfDoc(); - - return true; -} - -LLSD LLFloaterAbout::getInfo() -{ - return LLAppViewer::instance()->getViewerInfo(); -} - -/*static*/ -void LLFloaterAbout::startFetchServerReleaseNotes() -{ - LLViewerRegion* region = gAgent.getRegion(); - if (!region) return; - - // We cannot display the URL returned by the ServerReleaseNotes capability - // because opening it in an external browser will trigger a warning about untrusted - // SSL certificate. - // So we query the URL ourselves, expecting to find - // an URL suitable for external browsers in the "Location:" HTTP header. - std::string cap_url = region->getCapability("ServerReleaseNotes"); - - LLCoros::instance().launch("fetchServerReleaseNotesCoro", boost::bind(&LLFloaterAbout::fetchServerReleaseNotesCoro, cap_url)); - -} - -/*static*/ -void LLFloaterAbout::fetchServerReleaseNotesCoro(const std::string& cap_url) -{ - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("fetchServerReleaseNotesCoro", LLCore::HttpRequest::DEFAULT_POLICY_ID)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); - - httpOpts->setWantHeaders(true); - httpOpts->setFollowRedirects(false); - httpOpts->setSSLVerifyPeer(false); // We want this data even if SSL verification fails - - LLSD result = httpAdapter->getAndSuspend(httpRequest, cap_url, httpOpts); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - handleServerReleaseNotes(httpResults); - } - else - { - handleServerReleaseNotes(result); - } -} - -/*static*/ -void LLFloaterAbout::handleServerReleaseNotes(LLSD results) -{ - LLSD http_headers; - if (results.has(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS)) - { - LLSD http_results = results[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - http_headers = http_results[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS]; - } - else - { - http_headers = results[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS]; - } - - std::string location = http_headers[HTTP_IN_HEADER_LOCATION].asString(); - if (location.empty()) - { - location = LLTrans::getString("ErrorFetchingServerReleaseNotesURL"); - } - LLAppViewer::instance()->setServerReleaseNotesURL(location); - - LLFloaterAbout* floater_about = LLFloaterReg::findTypedInstance("sl_about"); - if (floater_about) - { - floater_about->setSupportText(location); - } -} - -class LLFloaterAboutListener: public LLEventAPI -{ -public: - LLFloaterAboutListener(): - LLEventAPI("LLFloaterAbout", - "LLFloaterAbout listener to retrieve About box info") - { - add("getInfo", - "Request an LLSD::Map containing information used to populate About box", - &LLFloaterAboutListener::getInfo, - LLSD().with("reply", LLSD())); - } - -private: - void getInfo(const LLSD& request) const - { - LLReqID reqid(request); - LLSD reply(LLFloaterAbout::getInfo()); - reqid.stamp(reply); - LLEventPumps::instance().obtain(request["reply"]).post(reply); - } -}; - -static LLFloaterAboutListener floaterAboutListener; - -void LLFloaterAbout::onClickCopyToClipboard() -{ - LLViewerTextEditor *support_widget = - getChild("support_editor", true); - support_widget->selectAll(); - support_widget->copy(); - support_widget->deselect(); -} - -void LLFloaterAbout::onClickUpdateCheck() -{ - setUpdateListener(); -} - -void LLFloaterAbout::setSupportText(const std::string& server_release_notes_url) -{ -#if LL_WINDOWS - getWindow()->incBusyCount(); - getWindow()->setCursor(UI_CURSOR_ARROW); -#endif -#if LL_WINDOWS - getWindow()->decBusyCount(); - getWindow()->setCursor(UI_CURSOR_ARROW); -#endif - - LLViewerTextEditor *support_widget = - getChild("support_editor", true); - - LLUIColor about_color = LLUIColorTable::instance().getColor("TextFgReadOnlyColor"); - support_widget->clear(); - support_widget->appendText(LLAppViewer::instance()->getViewerInfoString(), - false, LLStyle::Params() .color(about_color)); -} - -//This is bound as a callback in postBuild() -void LLFloaterAbout::setUpdateListener() -{ - typedef std::vector vec; - - //There are four possibilities: - //no downloads directory or version directory in "getOSUserAppDir()/downloads" - // => no update - //version directory exists and .done file is not present - // => download in progress - //version directory exists and .done file exists - // => update ready for install - //version directory, .done file and either .skip or .next file exists - // => update deferred - bool downloads = false; - std::string downloadDir = ""; - bool done = false; - bool next = false; - bool skip = false; - - LLSD info(LLFloaterAbout::getInfo()); - std::string version = info["VIEWER_VERSION_STR"].asString(); - std::string appDir = gDirUtilp->getOSUserAppDir(); - - //drop down two directory levels so we aren't searching for markers among the log files and crash dumps - //or among other possible viewer upgrade directories if the resident is running multiple viewer versions - //we should end up with a path like ../downloads/1.2.3.456789 - vec file_vec = gDirUtilp->getFilesInDir(appDir); - - for(vec::const_iterator iter=file_vec.begin(); iter!=file_vec.end(); ++iter) - { - if ( (iter->rfind("downloads") ) ) - { - vec dir_vec = gDirUtilp->getFilesInDir(*iter); - for(vec::const_iterator dir_iter=dir_vec.begin(); dir_iter!=dir_vec.end(); ++dir_iter) - { - if ( (dir_iter->rfind(version))) - { - downloads = true; - downloadDir = *dir_iter; - } - } - } - } - - if ( downloads ) - { - for(vec::const_iterator iter=file_vec.begin(); iter!=file_vec.end(); ++iter) - { - if ( (iter->rfind(version))) - { - if ( (iter->rfind(".done") ) ) - { - done = true; - } - else if ( (iter->rfind(".next") ) ) - { - next = true; - } - else if ( (iter->rfind(".skip") ) ) - { - skip = true; - } - } - } - } - - if ( !downloads ) - { - LLNotificationsUtil::add("UpdateViewerUpToDate"); - } - else - { - if ( !done ) - { - LLNotificationsUtil::add("UpdateDownloadInProgress"); - } - else if ( (!next) && (!skip) ) - { - LLNotificationsUtil::add("UpdateDownloadComplete"); - } - else //done and there is a next or skip - { - LLNotificationsUtil::add("UpdateDeferred"); - } - } -} - -///---------------------------------------------------------------------------- -/// LLFloaterAboutUtil -///---------------------------------------------------------------------------- -void LLFloaterAboutUtil::registerFloater() -{ - LLFloaterReg::add("sl_about", "floater_about.xml", - &LLFloaterReg::build); -} - -void LLFloaterAboutUtil::checkUpdatesAndNotify() -{ - LLFloaterAbout::setUpdateListener(); -} - +/** + * @file llfloaterabout.cpp + * @author James Cook + * @brief The about box from Help->About + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include +#include + +#include "llfloaterabout.h" + +// Viewer includes +#include "llagent.h" +#include "llagentui.h" +#include "llappviewer.h" +#include "llnotificationsutil.h" +#include "llslurl.h" +#include "llvoiceclient.h" +#include "lluictrlfactory.h" +#include "llviewertexteditor.h" +#include "llviewercontrol.h" +#include "llviewerstats.h" +#include "llviewerregion.h" +#include "llversioninfo.h" +#include "llweb.h" + +// Linden library includes +#include "llaudioengine.h" +#include "llbutton.h" +#include "llglheaders.h" +#include "llfloater.h" +#include "llfloaterreg.h" +#include "llimagej2c.h" +#include "llsys.h" +#include "lltrans.h" +#include "lluri.h" +#include "v3dmath.h" +#include "llwindow.h" +#include "stringize.h" +#include "llsdutil_math.h" +#include "lleventapi.h" +#include "llcorehttputil.h" +#include "lldir.h" + +#if LL_WINDOWS +#include "lldxhardware.h" +#endif + +extern LLMemoryInfo gSysMemory; +extern U32 gPacketsIn; + +///---------------------------------------------------------------------------- +/// Class LLFloaterAbout +///---------------------------------------------------------------------------- +class LLFloaterAbout + : public LLFloater +{ + friend class LLFloaterReg; +private: + LLFloaterAbout(const LLSD& key); + virtual ~LLFloaterAbout(); + +public: + bool postBuild() override; + + /// Obtain the data used to fill out the contents string. This is + /// separated so that we can programmatically access the same info. + static LLSD getInfo(); + void onClickCopyToClipboard(); + void onClickUpdateCheck(); + static void setUpdateListener(); + +private: + void setSupportText(const std::string& server_release_notes_url); + + // notifications for user requested checks + static void showCheckUpdateNotification(S32 state); + + // callback method for manual checks + static bool callbackCheckUpdate(LLSD const & event); + + // listener name for update checks + static const std::string sCheckUpdateListenerName; + + static void startFetchServerReleaseNotes(); + static void fetchServerReleaseNotesCoro(const std::string& cap_url); + static void handleServerReleaseNotes(LLSD results); +}; + + +// Default constructor +LLFloaterAbout::LLFloaterAbout(const LLSD& key) +: LLFloater(key) +{ + +} + +// Destroys the object +LLFloaterAbout::~LLFloaterAbout() +{ +} + +bool LLFloaterAbout::postBuild() +{ + center(); + LLViewerTextEditor *support_widget = + getChild("support_editor", true); + + LLViewerTextEditor *contrib_names_widget = + getChild("contrib_names", true); + + LLViewerTextEditor *licenses_widget = + getChild("licenses_editor", true); + + getChild("copy_btn")->setCommitCallback( + boost::bind(&LLFloaterAbout::onClickCopyToClipboard, this)); + + getChild("update_btn")->setCommitCallback( + boost::bind(&LLFloaterAbout::onClickUpdateCheck, this)); + + static const LLUIColor about_color = LLUIColorTable::instance().getColor("TextFgReadOnlyColor"); + + if (gAgent.getRegion()) + { + // start fetching server release notes URL + setSupportText(LLTrans::getString("RetrievingData")); + startFetchServerReleaseNotes(); + } + else // not logged in + { + LL_DEBUGS("ViewerInfo") << "cannot display region info when not connected" << LL_ENDL; + setSupportText(LLTrans::getString("NotConnected")); + } + + support_widget->blockUndo(); + + // Fix views + support_widget->setEnabled(false); + support_widget->startOfDoc(); + + // Get the names of contributors, extracted from .../doc/contributions.txt by viewer_manifest.py at build time + std::string contributors_path = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS,"contributors.txt"); + llifstream contrib_file; + std::string contributors; + contrib_file.open(contributors_path.c_str()); /* Flawfinder: ignore */ + if (contrib_file.is_open()) + { + std::getline(contrib_file, contributors); // all names are on a single line + contrib_file.close(); + } + else + { + LL_WARNS("AboutInit") << "Could not read contributors file at " << contributors_path << LL_ENDL; + } + contrib_names_widget->setText(contributors); + contrib_names_widget->setEnabled(false); + contrib_names_widget->startOfDoc(); + + // Get the Versions and Copyrights, created at build time + std::string licenses_path = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS,"packages-info.txt"); + llifstream licenses_file; + licenses_file.open(licenses_path.c_str()); /* Flawfinder: ignore */ + if (licenses_file.is_open()) + { + std::string license_line; + licenses_widget->clear(); + while ( std::getline(licenses_file, license_line) ) + { + licenses_widget->appendText(license_line+"\n", false, + LLStyle::Params() .color(about_color)); + } + licenses_file.close(); + } + else + { + // this case will use the (out of date) hard coded value from the XUI + LL_INFOS("AboutInit") << "Could not read licenses file at " << licenses_path << LL_ENDL; + } + licenses_widget->setEnabled(false); + licenses_widget->startOfDoc(); + + return true; +} + +LLSD LLFloaterAbout::getInfo() +{ + return LLAppViewer::instance()->getViewerInfo(); +} + +/*static*/ +void LLFloaterAbout::startFetchServerReleaseNotes() +{ + LLViewerRegion* region = gAgent.getRegion(); + if (!region) return; + + // We cannot display the URL returned by the ServerReleaseNotes capability + // because opening it in an external browser will trigger a warning about untrusted + // SSL certificate. + // So we query the URL ourselves, expecting to find + // an URL suitable for external browsers in the "Location:" HTTP header. + std::string cap_url = region->getCapability("ServerReleaseNotes"); + + LLCoros::instance().launch("fetchServerReleaseNotesCoro", boost::bind(&LLFloaterAbout::fetchServerReleaseNotesCoro, cap_url)); + +} + +/*static*/ +void LLFloaterAbout::fetchServerReleaseNotesCoro(const std::string& cap_url) +{ + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("fetchServerReleaseNotesCoro", LLCore::HttpRequest::DEFAULT_POLICY_ID)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + + httpOpts->setWantHeaders(true); + httpOpts->setFollowRedirects(false); + httpOpts->setSSLVerifyPeer(false); // We want this data even if SSL verification fails + + LLSD result = httpAdapter->getAndSuspend(httpRequest, cap_url, httpOpts); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + handleServerReleaseNotes(httpResults); + } + else + { + handleServerReleaseNotes(result); + } +} + +/*static*/ +void LLFloaterAbout::handleServerReleaseNotes(LLSD results) +{ + LLSD http_headers; + if (results.has(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS)) + { + LLSD http_results = results[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + http_headers = http_results[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS]; + } + else + { + http_headers = results[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS]; + } + + std::string location = http_headers[HTTP_IN_HEADER_LOCATION].asString(); + if (location.empty()) + { + location = LLTrans::getString("ErrorFetchingServerReleaseNotesURL"); + } + LLAppViewer::instance()->setServerReleaseNotesURL(location); + + LLFloaterAbout* floater_about = LLFloaterReg::findTypedInstance("sl_about"); + if (floater_about) + { + floater_about->setSupportText(location); + } +} + +class LLFloaterAboutListener: public LLEventAPI +{ +public: + LLFloaterAboutListener(): + LLEventAPI("LLFloaterAbout", + "LLFloaterAbout listener to retrieve About box info") + { + add("getInfo", + "Request an LLSD::Map containing information used to populate About box", + &LLFloaterAboutListener::getInfo, + LLSD().with("reply", LLSD())); + } + +private: + void getInfo(const LLSD& request) const + { + LLReqID reqid(request); + LLSD reply(LLFloaterAbout::getInfo()); + reqid.stamp(reply); + LLEventPumps::instance().obtain(request["reply"]).post(reply); + } +}; + +static LLFloaterAboutListener floaterAboutListener; + +void LLFloaterAbout::onClickCopyToClipboard() +{ + LLViewerTextEditor *support_widget = + getChild("support_editor", true); + support_widget->selectAll(); + support_widget->copy(); + support_widget->deselect(); +} + +void LLFloaterAbout::onClickUpdateCheck() +{ + setUpdateListener(); +} + +void LLFloaterAbout::setSupportText(const std::string& server_release_notes_url) +{ +#if LL_WINDOWS + getWindow()->incBusyCount(); + getWindow()->setCursor(UI_CURSOR_ARROW); +#endif +#if LL_WINDOWS + getWindow()->decBusyCount(); + getWindow()->setCursor(UI_CURSOR_ARROW); +#endif + + LLViewerTextEditor *support_widget = + getChild("support_editor", true); + + LLUIColor about_color = LLUIColorTable::instance().getColor("TextFgReadOnlyColor"); + support_widget->clear(); + support_widget->appendText(LLAppViewer::instance()->getViewerInfoString(), + false, LLStyle::Params() .color(about_color)); +} + +//This is bound as a callback in postBuild() +void LLFloaterAbout::setUpdateListener() +{ + typedef std::vector vec; + + //There are four possibilities: + //no downloads directory or version directory in "getOSUserAppDir()/downloads" + // => no update + //version directory exists and .done file is not present + // => download in progress + //version directory exists and .done file exists + // => update ready for install + //version directory, .done file and either .skip or .next file exists + // => update deferred + bool downloads = false; + std::string downloadDir = ""; + bool done = false; + bool next = false; + bool skip = false; + + LLSD info(LLFloaterAbout::getInfo()); + std::string version = info["VIEWER_VERSION_STR"].asString(); + std::string appDir = gDirUtilp->getOSUserAppDir(); + + //drop down two directory levels so we aren't searching for markers among the log files and crash dumps + //or among other possible viewer upgrade directories if the resident is running multiple viewer versions + //we should end up with a path like ../downloads/1.2.3.456789 + vec file_vec = gDirUtilp->getFilesInDir(appDir); + + for(vec::const_iterator iter=file_vec.begin(); iter!=file_vec.end(); ++iter) + { + if ( (iter->rfind("downloads") ) ) + { + vec dir_vec = gDirUtilp->getFilesInDir(*iter); + for(vec::const_iterator dir_iter=dir_vec.begin(); dir_iter!=dir_vec.end(); ++dir_iter) + { + if ( (dir_iter->rfind(version))) + { + downloads = true; + downloadDir = *dir_iter; + } + } + } + } + + if ( downloads ) + { + for(vec::const_iterator iter=file_vec.begin(); iter!=file_vec.end(); ++iter) + { + if ( (iter->rfind(version))) + { + if ( (iter->rfind(".done") ) ) + { + done = true; + } + else if ( (iter->rfind(".next") ) ) + { + next = true; + } + else if ( (iter->rfind(".skip") ) ) + { + skip = true; + } + } + } + } + + if ( !downloads ) + { + LLNotificationsUtil::add("UpdateViewerUpToDate"); + } + else + { + if ( !done ) + { + LLNotificationsUtil::add("UpdateDownloadInProgress"); + } + else if ( (!next) && (!skip) ) + { + LLNotificationsUtil::add("UpdateDownloadComplete"); + } + else //done and there is a next or skip + { + LLNotificationsUtil::add("UpdateDeferred"); + } + } +} + +///---------------------------------------------------------------------------- +/// LLFloaterAboutUtil +///---------------------------------------------------------------------------- +void LLFloaterAboutUtil::registerFloater() +{ + LLFloaterReg::add("sl_about", "floater_about.xml", + &LLFloaterReg::build); +} + +void LLFloaterAboutUtil::checkUpdatesAndNotify() +{ + LLFloaterAbout::setUpdateListener(); +} + diff --git a/indra/newview/llfloateraddpaymentmethod.cpp b/indra/newview/llfloateraddpaymentmethod.cpp index d63defaf17..fc962ce89f 100644 --- a/indra/newview/llfloateraddpaymentmethod.cpp +++ b/indra/newview/llfloateraddpaymentmethod.cpp @@ -1,81 +1,81 @@ -/** - * @file llfloateraddpaymentmethod.cpp - * @brief LLFloaterAddPaymentMethod class implementation - * - * $LicenseInfo:firstyear=2020&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2020, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloateraddpaymentmethod.h" -#include "llnotificationsutil.h" -#include "lluictrlfactory.h" -#include "llweb.h" - - -LLFloaterAddPaymentMethod::LLFloaterAddPaymentMethod(const LLSD& key) - : LLFloater(key) -{ -} - -LLFloaterAddPaymentMethod::~LLFloaterAddPaymentMethod() -{ -} - -bool LLFloaterAddPaymentMethod::postBuild() -{ - setCanDrag(false); - getChild("continue_btn")->setCommitCallback(boost::bind(&LLFloaterAddPaymentMethod::onContinueBtn, this)); - getChild("close_btn")->setCommitCallback(boost::bind(&LLFloaterAddPaymentMethod::onCloseBtn, this)); - return true; -} - -void LLFloaterAddPaymentMethod::onOpen(const LLSD& key) -{ - centerOnScreen(); -} - -void LLFloaterAddPaymentMethod::onContinueBtn() -{ - closeFloater(); - LLNotificationsUtil::add("AddPaymentMethod", LLSD(), LLSD(), - [this](const LLSD¬if, const LLSD&resp) - { - S32 opt = LLNotificationsUtil::getSelectedOption(notif, resp); - if (opt == 0) - { - LLWeb::loadURL(this->getString("continue_url")); - } - }); -} - -void LLFloaterAddPaymentMethod::onCloseBtn() -{ - closeFloater(); -} - -void LLFloaterAddPaymentMethod::centerOnScreen() -{ - LLVector2 window_size = LLUI::getInstance()->getWindowSize(); - centerWithin(LLRect(0, 0, ll_round(window_size.mV[VX]), ll_round(window_size.mV[VY]))); -} - +/** + * @file llfloateraddpaymentmethod.cpp + * @brief LLFloaterAddPaymentMethod class implementation + * + * $LicenseInfo:firstyear=2020&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2020, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloateraddpaymentmethod.h" +#include "llnotificationsutil.h" +#include "lluictrlfactory.h" +#include "llweb.h" + + +LLFloaterAddPaymentMethod::LLFloaterAddPaymentMethod(const LLSD& key) + : LLFloater(key) +{ +} + +LLFloaterAddPaymentMethod::~LLFloaterAddPaymentMethod() +{ +} + +bool LLFloaterAddPaymentMethod::postBuild() +{ + setCanDrag(false); + getChild("continue_btn")->setCommitCallback(boost::bind(&LLFloaterAddPaymentMethod::onContinueBtn, this)); + getChild("close_btn")->setCommitCallback(boost::bind(&LLFloaterAddPaymentMethod::onCloseBtn, this)); + return true; +} + +void LLFloaterAddPaymentMethod::onOpen(const LLSD& key) +{ + centerOnScreen(); +} + +void LLFloaterAddPaymentMethod::onContinueBtn() +{ + closeFloater(); + LLNotificationsUtil::add("AddPaymentMethod", LLSD(), LLSD(), + [this](const LLSD¬if, const LLSD&resp) + { + S32 opt = LLNotificationsUtil::getSelectedOption(notif, resp); + if (opt == 0) + { + LLWeb::loadURL(this->getString("continue_url")); + } + }); +} + +void LLFloaterAddPaymentMethod::onCloseBtn() +{ + closeFloater(); +} + +void LLFloaterAddPaymentMethod::centerOnScreen() +{ + LLVector2 window_size = LLUI::getInstance()->getWindowSize(); + centerWithin(LLRect(0, 0, ll_round(window_size.mV[VX]), ll_round(window_size.mV[VY]))); +} + diff --git a/indra/newview/llfloateraddpaymentmethod.h b/indra/newview/llfloateraddpaymentmethod.h index b4cb0ad182..252caf8f5b 100644 --- a/indra/newview/llfloateraddpaymentmethod.h +++ b/indra/newview/llfloateraddpaymentmethod.h @@ -1,52 +1,52 @@ -/** - * @file llfloateraddpaymentmethod.h - * @brief LLFloaterAddPaymentMethod class definition - * - * $LicenseInfo:firstyear=2020&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2020, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_FLOATER_ADDPAYMENTMETHOD_H -#define LL_FLOATER_ADDPAYMENTMETHOD_H - -#include "llfloater.h" - -class LLFloaterAddPaymentMethod: - public LLFloater -{ - friend class LLFloaterReg; -public: - bool postBuild() override; - void onOpen(const LLSD& key) override; - -private: - LLFloaterAddPaymentMethod(const LLSD& key); - - void centerOnScreen(); - - void onCloseBtn(); - void onContinueBtn(); - - /*virtual*/ ~LLFloaterAddPaymentMethod(); - -}; - -#endif +/** + * @file llfloateraddpaymentmethod.h + * @brief LLFloaterAddPaymentMethod class definition + * + * $LicenseInfo:firstyear=2020&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2020, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_FLOATER_ADDPAYMENTMETHOD_H +#define LL_FLOATER_ADDPAYMENTMETHOD_H + +#include "llfloater.h" + +class LLFloaterAddPaymentMethod: + public LLFloater +{ + friend class LLFloaterReg; +public: + bool postBuild() override; + void onOpen(const LLSD& key) override; + +private: + LLFloaterAddPaymentMethod(const LLSD& key); + + void centerOnScreen(); + + void onCloseBtn(); + void onContinueBtn(); + + /*virtual*/ ~LLFloaterAddPaymentMethod(); + +}; + +#endif diff --git a/indra/newview/llfloaterauction.cpp b/indra/newview/llfloaterauction.cpp index a466622b7a..a87ddfd76e 100644 --- a/indra/newview/llfloaterauction.cpp +++ b/indra/newview/llfloaterauction.cpp @@ -1,559 +1,559 @@ -/** - * @file llfloaterauction.cpp - * @author James Cook, Ian Wilkes - * @brief Implementation of the auction floater. - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llfloaterauction.h" - -#include "llgl.h" -#include "llimagej2c.h" -#include "llimagetga.h" -#include "llparcel.h" -#include "llfilesystem.h" -#include "llwindow.h" -#include "message.h" - -#include "llagent.h" -#include "llassetstorage.h" -#include "llcombobox.h" -#include "llestateinfomodel.h" -#include "llmimetypes.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "llviewertexturelist.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" -#include "lluictrlfactory.h" -#include "llviewerwindow.h" -#include "llviewerdisplay.h" -#include "llviewercontrol.h" -#include "llui.h" -#include "llrender.h" -#include "llsdutil.h" -#include "llsdutil_math.h" -#include "lltrans.h" -#include "llcorehttputil.h" - -///---------------------------------------------------------------------------- -/// Local function declarations, constants, enums, and typedefs -///---------------------------------------------------------------------------- - -void auction_j2c_upload_done(const LLUUID& asset_id, - void* user_data, S32 status, LLExtStat ext_status); -void auction_tga_upload_done(const LLUUID& asset_id, - void* user_data, S32 status, LLExtStat ext_status); - -///---------------------------------------------------------------------------- -/// Class llfloaterauction -///---------------------------------------------------------------------------- - -// Default constructor -LLFloaterAuction::LLFloaterAuction(const LLSD& key) - : LLFloater(key), - mParcelID(-1) -{ - mCommitCallbackRegistrar.add("ClickSnapshot", boost::bind(&LLFloaterAuction::onClickSnapshot, this)); - mCommitCallbackRegistrar.add("ClickSellToAnyone", boost::bind(&LLFloaterAuction::onClickSellToAnyone, this)); - mCommitCallbackRegistrar.add("ClickStartAuction", boost::bind(&LLFloaterAuction::onClickStartAuction, this)); - mCommitCallbackRegistrar.add("ClickResetParcel", boost::bind(&LLFloaterAuction::onClickResetParcel, this)); -} - -// Destroys the object -LLFloaterAuction::~LLFloaterAuction() -{ -} - -bool LLFloaterAuction::postBuild() -{ - return true; -} - -void LLFloaterAuction::onOpen(const LLSD& key) -{ - initialize(); -} - -void LLFloaterAuction::initialize() -{ - mParcelUpdateCapUrl.clear(); - - mParcelp = LLViewerParcelMgr::getInstance()->getParcelSelection(); - LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); - LLParcel* parcelp = mParcelp->getParcel(); - if(parcelp && region && !parcelp->getForSale()) - { - mParcelHost = region->getHost(); - mParcelID = parcelp->getLocalID(); - mParcelUpdateCapUrl = region->getCapability("ParcelPropertiesUpdate"); - - getChild("parcel_text")->setValue(parcelp->getName()); - getChildView("snapshot_btn")->setEnabled(true); - getChildView("reset_parcel_btn")->setEnabled(true); - getChildView("start_auction_btn")->setEnabled(true); - - U32 estate_id = LLEstateInfoModel::instance().getID(); - // Only enable "Sell to Anyone" on Teen grid or if we don't know the ID yet - getChildView("sell_to_anyone_btn")->setEnabled(estate_id == ESTATE_TEEN || estate_id == 0); - } - else - { - mParcelHost.invalidate(); - if(parcelp && parcelp->getForSale()) - { - getChild("parcel_text")->setValue(getString("already for sale")); - } - else - { - getChild("parcel_text")->setValue(LLStringUtil::null); - } - mParcelID = -1; - getChildView("snapshot_btn")->setEnabled(false); - getChildView("reset_parcel_btn")->setEnabled(false); - getChildView("sell_to_anyone_btn")->setEnabled(false); - getChildView("start_auction_btn")->setEnabled(false); - } - - mImageID.setNull(); - mImage = NULL; -} - -void LLFloaterAuction::draw() -{ - LLFloater::draw(); - - if(!isMinimized() && mImage.notNull()) - { - LLView* snapshot_icon = findChildView("snapshot_icon"); - if (snapshot_icon) - { - LLRect rect = snapshot_icon->getRect(); - { - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gl_rect_2d(rect, LLColor4(0.f, 0.f, 0.f, 1.f)); - rect.stretch(-1); - } - { - LLGLSUIDefault gls_ui; - gGL.color3f(1.f, 1.f, 1.f); - gl_draw_scaled_image(rect.mLeft, - rect.mBottom, - rect.getWidth(), - rect.getHeight(), - mImage); - } - } - } -} - - -// static -void LLFloaterAuction::onClickSnapshot(void* data) -{ - LLFloaterAuction* self = (LLFloaterAuction*)(data); - - LLPointer raw = new LLImageRaw; - - gForceRenderLandFence = self->getChild("fence_check")->getValue().asBoolean(); - bool success = gViewerWindow->rawSnapshot(raw, - gViewerWindow->getWindowWidthScaled(), - gViewerWindow->getWindowHeightScaled(), - true, - false, - false, //UI - false, //HUD - false); - gForceRenderLandFence = false; - - if (success) - { - LLImageDataLock lock(raw); - - self->mTransactionID.generate(); - self->mImageID = self->mTransactionID.makeAssetID(gAgent.getSecureSessionID()); - - if(!gSavedSettings.getBOOL("QuietSnapshotsToDisk")) - { - gViewerWindow->playSnapshotAnimAndSound(); - } - LL_INFOS() << "Writing TGA..." << LL_ENDL; - - LLPointer tga = new LLImageTGA; - tga->encode(raw); - - LLFileSystem tga_file(self->mImageID, LLAssetType::AT_IMAGE_TGA, LLFileSystem::WRITE); - tga_file.write(tga->getData(), tga->getDataSize()); - - raw->biasedScaleToPowerOfTwo(LLViewerTexture::MAX_IMAGE_SIZE_DEFAULT); - - LL_INFOS() << "Writing J2C..." << LL_ENDL; - - LLPointer j2c = new LLImageJ2C; - j2c->encode(raw, 0.0f); - - LLFileSystem j2c_file(self->mImageID, LLAssetType::AT_TEXTURE, LLFileSystem::WRITE); - j2c_file.write(j2c->getData(), j2c->getDataSize()); - - self->mImage = LLViewerTextureManager::getLocalTexture((LLImageRaw*)raw, false); - gGL.getTexUnit(0)->bind(self->mImage); - self->mImage->setAddressMode(LLTexUnit::TAM_CLAMP); - } - else - { - LL_WARNS() << "Unable to take snapshot" << LL_ENDL; - } -} - -// static -void LLFloaterAuction::onClickStartAuction(void* data) -{ - LLFloaterAuction* self = (LLFloaterAuction*)(data); - - if(self->mImageID.notNull()) - { - LLSD parcel_name = self->getChild("parcel_text")->getValue(); - - // create the asset - std::string* name = new std::string(parcel_name.asString()); - gAssetStorage->storeAssetData(self->mTransactionID, LLAssetType::AT_IMAGE_TGA, - &auction_tga_upload_done, - (void*)name, - false); - self->getWindow()->incBusyCount(); - - std::string* j2c_name = new std::string(parcel_name.asString()); - gAssetStorage->storeAssetData(self->mTransactionID, LLAssetType::AT_TEXTURE, - &auction_j2c_upload_done, - (void*)j2c_name, - false); - self->getWindow()->incBusyCount(); - - LLNotificationsUtil::add("UploadingAuctionSnapshot"); - - } - LLMessageSystem* msg = gMessageSystem; - - msg->newMessage("ViewerStartAuction"); - - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->nextBlock("ParcelData"); - msg->addS32("LocalID", self->mParcelID); - msg->addUUID("SnapshotID", self->mImageID); - msg->sendReliable(self->mParcelHost); - - // clean up floater, and get out - self->cleanupAndClose(); -} - - -void LLFloaterAuction::cleanupAndClose() -{ - mImageID.setNull(); - mImage = NULL; - mParcelID = -1; - mParcelHost.invalidate(); - closeFloater(); -} - - - -// static glue -void LLFloaterAuction::onClickResetParcel(void* data) -{ - LLFloaterAuction* self = (LLFloaterAuction*)(data); - if (self) - { - self->doResetParcel(); - } -} - - -// Reset all the values for the parcel in preparation for a sale -void LLFloaterAuction::doResetParcel() -{ - LLParcel* parcelp = mParcelp->getParcel(); - LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); - - if (parcelp - && region - && !mParcelUpdateCapUrl.empty()) - { - LLSD body; - std::string empty; - - // request new properties update from simulator - U32 message_flags = 0x01; - body["flags"] = ll_sd_from_U32(message_flags); - - // Set all the default parcel properties for auction - body["local_id"] = parcelp->getLocalID(); - - U32 parcel_flags = PF_ALLOW_LANDMARK | - PF_ALLOW_FLY | - PF_CREATE_GROUP_OBJECTS | - PF_ALLOW_ALL_OBJECT_ENTRY | - PF_ALLOW_GROUP_OBJECT_ENTRY | - PF_ALLOW_GROUP_SCRIPTS | - PF_RESTRICT_PUSHOBJECT | - PF_SOUND_LOCAL | - PF_ALLOW_VOICE_CHAT | - PF_USE_ESTATE_VOICE_CHAN; - - body["parcel_flags"] = ll_sd_from_U32(parcel_flags); - - // Build a parcel name like "Ahern (128,128) PG 4032m" - std::ostringstream parcel_name; - LLVector3 center_point( parcelp->getCenterpoint() ); - center_point.snap(0); // Get rid of fractions - parcel_name << region->getName() - << " (" - << (S32) center_point.mV[VX] - << "," - << (S32) center_point.mV[VY] - << ") " - << region->getSimAccessString() - << " " - << parcelp->getArea() - << "m"; - - std::string new_name(parcel_name.str().c_str()); - body["name"] = new_name; - getChild("parcel_text")->setValue(new_name); // Set name in dialog as well, since it won't get updated otherwise - - body["sale_price"] = (S32) 0; - body["description"] = empty; - body["music_url"] = empty; - body["media_url"] = empty; - body["media_desc"] = empty; - body["media_type"] = LLMIMETypes::getDefaultMimeType(); - body["media_width"] = (S32) 0; - body["media_height"] = (S32) 0; - body["auto_scale"] = (S32) 0; - body["media_loop"] = (S32) 0; - body["obscure_media"] = (S32) 0; // OBSOLETE - no longer used - body["obscure_music"] = (S32) 0; // OBSOLETE - no longer used - body["media_id"] = LLUUID::null; - body["group_id"] = MAINTENANCE_GROUP_ID; // Use maintenance group - body["pass_price"] = (S32) 10; // Defaults to $10 - body["pass_hours"] = 0.0f; - body["category"] = (U8) LLParcel::C_NONE; - body["auth_buyer_id"] = LLUUID::null; - body["snapshot_id"] = LLUUID::null; - body["user_location"] = ll_sd_from_vector3( LLVector3::zero ); - body["user_look_at"] = ll_sd_from_vector3( LLVector3::zero ); - body["landing_type"] = (U8) LLParcel::L_DIRECT; - - LL_INFOS() << "Sending parcel update to reset for auction via capability to: " - << mParcelUpdateCapUrl << LL_ENDL; - - LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(mParcelUpdateCapUrl, body, - "Parcel reset for auction", - "Parcel not set for auction."); - - // Send a message to clear the object return time - LLMessageSystem *msg = gMessageSystem; - msg->newMessageFast(_PREHASH_ParcelSetOtherCleanTime); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ParcelData); - msg->addS32Fast(_PREHASH_LocalID, parcelp->getLocalID()); - msg->addS32Fast(_PREHASH_OtherCleanTime, 5); // 5 minute object auto-return - - msg->sendReliable(region->getHost()); - - // Clear the access lists - clearParcelAccessList(parcelp, region, AL_ACCESS); - clearParcelAccessList(parcelp, region, AL_BAN); - clearParcelAccessList(parcelp, region, AL_ALLOW_EXPERIENCE); - clearParcelAccessList(parcelp, region, AL_BLOCK_EXPERIENCE); - } -} - - - -void LLFloaterAuction::clearParcelAccessList(LLParcel* parcel, LLViewerRegion* region, U32 list) -{ - if (!region || !parcel) return; - - LLUUID transactionUUID; - transactionUUID.generate(); - - LLMessageSystem* msg = gMessageSystem; - - msg->newMessageFast(_PREHASH_ParcelAccessListUpdate); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); - msg->nextBlockFast(_PREHASH_Data); - msg->addU32Fast(_PREHASH_Flags, list); - msg->addS32(_PREHASH_LocalID, parcel->getLocalID() ); - msg->addUUIDFast(_PREHASH_TransactionID, transactionUUID); - msg->addS32Fast(_PREHASH_SequenceID, 1); // sequence_id - msg->addS32Fast(_PREHASH_Sections, 0); // num_sections - - // pack an empty block since there will be no data - msg->nextBlockFast(_PREHASH_List); - msg->addUUIDFast(_PREHASH_ID, LLUUID::null ); - msg->addS32Fast(_PREHASH_Time, 0 ); - msg->addU32Fast(_PREHASH_Flags, 0 ); - - msg->sendReliable( region->getHost() ); -} - - - -// static - 'Sell to Anyone' clicked, throw up a confirmation dialog -void LLFloaterAuction::onClickSellToAnyone(void* data) -{ - LLFloaterAuction* self = (LLFloaterAuction*)(data); - if (self) - { - LLParcel* parcelp = self->mParcelp->getParcel(); - - // Do a confirmation - S32 sale_price = parcelp->getArea(); // Selling for L$1 per meter - S32 area = parcelp->getArea(); - - LLSD args; - args["LAND_SIZE"] = llformat("%d", area); - args["SALE_PRICE"] = llformat("%d", sale_price); - args["NAME"] = LLTrans::getString("Anyone"); - - LLNotification::Params params("ConfirmLandSaleChange"); // Re-use existing dialog - params.substitutions(args) - .functor.function(boost::bind(&LLFloaterAuction::onSellToAnyoneConfirmed, self, _1, _2)); - - params.name("ConfirmLandSaleToAnyoneChange"); - - // ask away - LLNotifications::instance().add(params); - } -} - - -// Sell confirmation clicked -bool LLFloaterAuction::onSellToAnyoneConfirmed(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) - { - doSellToAnyone(); - } - - return false; -} - - - -// Reset all the values for the parcel in preparation for a sale -void LLFloaterAuction::doSellToAnyone() -{ - LLParcel* parcelp = mParcelp->getParcel(); - LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); - - if (parcelp - && region - && !mParcelUpdateCapUrl.empty()) - { - LLSD body; - std::string empty; - - // request new properties update from simulator - U32 message_flags = 0x01; - body["flags"] = ll_sd_from_U32(message_flags); - - // Set all the default parcel properties for auction - body["local_id"] = parcelp->getLocalID(); - - // Set 'for sale' flag - U32 parcel_flags = parcelp->getParcelFlags() | PF_FOR_SALE; - // Ensure objects not included - parcel_flags &= ~PF_FOR_SALE_OBJECTS; - body["parcel_flags"] = ll_sd_from_U32(parcel_flags); - - body["sale_price"] = parcelp->getArea(); // Sell for L$1 per square meter - body["auth_buyer_id"] = LLUUID::null; // To anyone - - LL_INFOS() << "Sending parcel update to sell to anyone for L$1 via capability to: " - << mParcelUpdateCapUrl << LL_ENDL; - - LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(mParcelUpdateCapUrl, body, - "Parcel set as sell to everyone.", - "Parcel sell to everyone failed."); - - // clean up floater, and get out - cleanupAndClose(); - } -} - - -///---------------------------------------------------------------------------- -/// Local function definitions -///---------------------------------------------------------------------------- - -void auction_tga_upload_done(const LLUUID& asset_id, void* user_data, S32 status, LLExtStat ext_status) // StoreAssetData callback (fixed) -{ - std::string* name = (std::string*)(user_data); - LL_INFOS() << "Upload of asset '" << *name << "' " << asset_id - << " returned " << status << LL_ENDL; - delete name; - - gViewerWindow->getWindow()->decBusyCount(); - - if (0 == status) - { - LLNotificationsUtil::add("UploadWebSnapshotDone"); - } - else - { - LLSD args; - args["REASON"] = std::string(LLAssetStorage::getErrorString(status)); - LLNotificationsUtil::add("UploadAuctionSnapshotFail", args); - } -} - -void auction_j2c_upload_done(const LLUUID& asset_id, void* user_data, S32 status, LLExtStat ext_status) // StoreAssetData callback (fixed) -{ - std::string* name = (std::string*)(user_data); - LL_INFOS() << "Upload of asset '" << *name << "' " << asset_id - << " returned " << status << LL_ENDL; - delete name; - - gViewerWindow->getWindow()->decBusyCount(); - - if (0 == status) - { - LLNotificationsUtil::add("UploadSnapshotDone"); - } - else - { - LLSD args; - args["REASON"] = std::string(LLAssetStorage::getErrorString(status)); - LLNotificationsUtil::add("UploadAuctionSnapshotFail", args); - } -} +/** + * @file llfloaterauction.cpp + * @author James Cook, Ian Wilkes + * @brief Implementation of the auction floater. + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llfloaterauction.h" + +#include "llgl.h" +#include "llimagej2c.h" +#include "llimagetga.h" +#include "llparcel.h" +#include "llfilesystem.h" +#include "llwindow.h" +#include "message.h" + +#include "llagent.h" +#include "llassetstorage.h" +#include "llcombobox.h" +#include "llestateinfomodel.h" +#include "llmimetypes.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "llviewertexturelist.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "lluictrlfactory.h" +#include "llviewerwindow.h" +#include "llviewerdisplay.h" +#include "llviewercontrol.h" +#include "llui.h" +#include "llrender.h" +#include "llsdutil.h" +#include "llsdutil_math.h" +#include "lltrans.h" +#include "llcorehttputil.h" + +///---------------------------------------------------------------------------- +/// Local function declarations, constants, enums, and typedefs +///---------------------------------------------------------------------------- + +void auction_j2c_upload_done(const LLUUID& asset_id, + void* user_data, S32 status, LLExtStat ext_status); +void auction_tga_upload_done(const LLUUID& asset_id, + void* user_data, S32 status, LLExtStat ext_status); + +///---------------------------------------------------------------------------- +/// Class llfloaterauction +///---------------------------------------------------------------------------- + +// Default constructor +LLFloaterAuction::LLFloaterAuction(const LLSD& key) + : LLFloater(key), + mParcelID(-1) +{ + mCommitCallbackRegistrar.add("ClickSnapshot", boost::bind(&LLFloaterAuction::onClickSnapshot, this)); + mCommitCallbackRegistrar.add("ClickSellToAnyone", boost::bind(&LLFloaterAuction::onClickSellToAnyone, this)); + mCommitCallbackRegistrar.add("ClickStartAuction", boost::bind(&LLFloaterAuction::onClickStartAuction, this)); + mCommitCallbackRegistrar.add("ClickResetParcel", boost::bind(&LLFloaterAuction::onClickResetParcel, this)); +} + +// Destroys the object +LLFloaterAuction::~LLFloaterAuction() +{ +} + +bool LLFloaterAuction::postBuild() +{ + return true; +} + +void LLFloaterAuction::onOpen(const LLSD& key) +{ + initialize(); +} + +void LLFloaterAuction::initialize() +{ + mParcelUpdateCapUrl.clear(); + + mParcelp = LLViewerParcelMgr::getInstance()->getParcelSelection(); + LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); + LLParcel* parcelp = mParcelp->getParcel(); + if(parcelp && region && !parcelp->getForSale()) + { + mParcelHost = region->getHost(); + mParcelID = parcelp->getLocalID(); + mParcelUpdateCapUrl = region->getCapability("ParcelPropertiesUpdate"); + + getChild("parcel_text")->setValue(parcelp->getName()); + getChildView("snapshot_btn")->setEnabled(true); + getChildView("reset_parcel_btn")->setEnabled(true); + getChildView("start_auction_btn")->setEnabled(true); + + U32 estate_id = LLEstateInfoModel::instance().getID(); + // Only enable "Sell to Anyone" on Teen grid or if we don't know the ID yet + getChildView("sell_to_anyone_btn")->setEnabled(estate_id == ESTATE_TEEN || estate_id == 0); + } + else + { + mParcelHost.invalidate(); + if(parcelp && parcelp->getForSale()) + { + getChild("parcel_text")->setValue(getString("already for sale")); + } + else + { + getChild("parcel_text")->setValue(LLStringUtil::null); + } + mParcelID = -1; + getChildView("snapshot_btn")->setEnabled(false); + getChildView("reset_parcel_btn")->setEnabled(false); + getChildView("sell_to_anyone_btn")->setEnabled(false); + getChildView("start_auction_btn")->setEnabled(false); + } + + mImageID.setNull(); + mImage = NULL; +} + +void LLFloaterAuction::draw() +{ + LLFloater::draw(); + + if(!isMinimized() && mImage.notNull()) + { + LLView* snapshot_icon = findChildView("snapshot_icon"); + if (snapshot_icon) + { + LLRect rect = snapshot_icon->getRect(); + { + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gl_rect_2d(rect, LLColor4(0.f, 0.f, 0.f, 1.f)); + rect.stretch(-1); + } + { + LLGLSUIDefault gls_ui; + gGL.color3f(1.f, 1.f, 1.f); + gl_draw_scaled_image(rect.mLeft, + rect.mBottom, + rect.getWidth(), + rect.getHeight(), + mImage); + } + } + } +} + + +// static +void LLFloaterAuction::onClickSnapshot(void* data) +{ + LLFloaterAuction* self = (LLFloaterAuction*)(data); + + LLPointer raw = new LLImageRaw; + + gForceRenderLandFence = self->getChild("fence_check")->getValue().asBoolean(); + bool success = gViewerWindow->rawSnapshot(raw, + gViewerWindow->getWindowWidthScaled(), + gViewerWindow->getWindowHeightScaled(), + true, + false, + false, //UI + false, //HUD + false); + gForceRenderLandFence = false; + + if (success) + { + LLImageDataLock lock(raw); + + self->mTransactionID.generate(); + self->mImageID = self->mTransactionID.makeAssetID(gAgent.getSecureSessionID()); + + if(!gSavedSettings.getBOOL("QuietSnapshotsToDisk")) + { + gViewerWindow->playSnapshotAnimAndSound(); + } + LL_INFOS() << "Writing TGA..." << LL_ENDL; + + LLPointer tga = new LLImageTGA; + tga->encode(raw); + + LLFileSystem tga_file(self->mImageID, LLAssetType::AT_IMAGE_TGA, LLFileSystem::WRITE); + tga_file.write(tga->getData(), tga->getDataSize()); + + raw->biasedScaleToPowerOfTwo(LLViewerTexture::MAX_IMAGE_SIZE_DEFAULT); + + LL_INFOS() << "Writing J2C..." << LL_ENDL; + + LLPointer j2c = new LLImageJ2C; + j2c->encode(raw, 0.0f); + + LLFileSystem j2c_file(self->mImageID, LLAssetType::AT_TEXTURE, LLFileSystem::WRITE); + j2c_file.write(j2c->getData(), j2c->getDataSize()); + + self->mImage = LLViewerTextureManager::getLocalTexture((LLImageRaw*)raw, false); + gGL.getTexUnit(0)->bind(self->mImage); + self->mImage->setAddressMode(LLTexUnit::TAM_CLAMP); + } + else + { + LL_WARNS() << "Unable to take snapshot" << LL_ENDL; + } +} + +// static +void LLFloaterAuction::onClickStartAuction(void* data) +{ + LLFloaterAuction* self = (LLFloaterAuction*)(data); + + if(self->mImageID.notNull()) + { + LLSD parcel_name = self->getChild("parcel_text")->getValue(); + + // create the asset + std::string* name = new std::string(parcel_name.asString()); + gAssetStorage->storeAssetData(self->mTransactionID, LLAssetType::AT_IMAGE_TGA, + &auction_tga_upload_done, + (void*)name, + false); + self->getWindow()->incBusyCount(); + + std::string* j2c_name = new std::string(parcel_name.asString()); + gAssetStorage->storeAssetData(self->mTransactionID, LLAssetType::AT_TEXTURE, + &auction_j2c_upload_done, + (void*)j2c_name, + false); + self->getWindow()->incBusyCount(); + + LLNotificationsUtil::add("UploadingAuctionSnapshot"); + + } + LLMessageSystem* msg = gMessageSystem; + + msg->newMessage("ViewerStartAuction"); + + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->nextBlock("ParcelData"); + msg->addS32("LocalID", self->mParcelID); + msg->addUUID("SnapshotID", self->mImageID); + msg->sendReliable(self->mParcelHost); + + // clean up floater, and get out + self->cleanupAndClose(); +} + + +void LLFloaterAuction::cleanupAndClose() +{ + mImageID.setNull(); + mImage = NULL; + mParcelID = -1; + mParcelHost.invalidate(); + closeFloater(); +} + + + +// static glue +void LLFloaterAuction::onClickResetParcel(void* data) +{ + LLFloaterAuction* self = (LLFloaterAuction*)(data); + if (self) + { + self->doResetParcel(); + } +} + + +// Reset all the values for the parcel in preparation for a sale +void LLFloaterAuction::doResetParcel() +{ + LLParcel* parcelp = mParcelp->getParcel(); + LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); + + if (parcelp + && region + && !mParcelUpdateCapUrl.empty()) + { + LLSD body; + std::string empty; + + // request new properties update from simulator + U32 message_flags = 0x01; + body["flags"] = ll_sd_from_U32(message_flags); + + // Set all the default parcel properties for auction + body["local_id"] = parcelp->getLocalID(); + + U32 parcel_flags = PF_ALLOW_LANDMARK | + PF_ALLOW_FLY | + PF_CREATE_GROUP_OBJECTS | + PF_ALLOW_ALL_OBJECT_ENTRY | + PF_ALLOW_GROUP_OBJECT_ENTRY | + PF_ALLOW_GROUP_SCRIPTS | + PF_RESTRICT_PUSHOBJECT | + PF_SOUND_LOCAL | + PF_ALLOW_VOICE_CHAT | + PF_USE_ESTATE_VOICE_CHAN; + + body["parcel_flags"] = ll_sd_from_U32(parcel_flags); + + // Build a parcel name like "Ahern (128,128) PG 4032m" + std::ostringstream parcel_name; + LLVector3 center_point( parcelp->getCenterpoint() ); + center_point.snap(0); // Get rid of fractions + parcel_name << region->getName() + << " (" + << (S32) center_point.mV[VX] + << "," + << (S32) center_point.mV[VY] + << ") " + << region->getSimAccessString() + << " " + << parcelp->getArea() + << "m"; + + std::string new_name(parcel_name.str().c_str()); + body["name"] = new_name; + getChild("parcel_text")->setValue(new_name); // Set name in dialog as well, since it won't get updated otherwise + + body["sale_price"] = (S32) 0; + body["description"] = empty; + body["music_url"] = empty; + body["media_url"] = empty; + body["media_desc"] = empty; + body["media_type"] = LLMIMETypes::getDefaultMimeType(); + body["media_width"] = (S32) 0; + body["media_height"] = (S32) 0; + body["auto_scale"] = (S32) 0; + body["media_loop"] = (S32) 0; + body["obscure_media"] = (S32) 0; // OBSOLETE - no longer used + body["obscure_music"] = (S32) 0; // OBSOLETE - no longer used + body["media_id"] = LLUUID::null; + body["group_id"] = MAINTENANCE_GROUP_ID; // Use maintenance group + body["pass_price"] = (S32) 10; // Defaults to $10 + body["pass_hours"] = 0.0f; + body["category"] = (U8) LLParcel::C_NONE; + body["auth_buyer_id"] = LLUUID::null; + body["snapshot_id"] = LLUUID::null; + body["user_location"] = ll_sd_from_vector3( LLVector3::zero ); + body["user_look_at"] = ll_sd_from_vector3( LLVector3::zero ); + body["landing_type"] = (U8) LLParcel::L_DIRECT; + + LL_INFOS() << "Sending parcel update to reset for auction via capability to: " + << mParcelUpdateCapUrl << LL_ENDL; + + LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(mParcelUpdateCapUrl, body, + "Parcel reset for auction", + "Parcel not set for auction."); + + // Send a message to clear the object return time + LLMessageSystem *msg = gMessageSystem; + msg->newMessageFast(_PREHASH_ParcelSetOtherCleanTime); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ParcelData); + msg->addS32Fast(_PREHASH_LocalID, parcelp->getLocalID()); + msg->addS32Fast(_PREHASH_OtherCleanTime, 5); // 5 minute object auto-return + + msg->sendReliable(region->getHost()); + + // Clear the access lists + clearParcelAccessList(parcelp, region, AL_ACCESS); + clearParcelAccessList(parcelp, region, AL_BAN); + clearParcelAccessList(parcelp, region, AL_ALLOW_EXPERIENCE); + clearParcelAccessList(parcelp, region, AL_BLOCK_EXPERIENCE); + } +} + + + +void LLFloaterAuction::clearParcelAccessList(LLParcel* parcel, LLViewerRegion* region, U32 list) +{ + if (!region || !parcel) return; + + LLUUID transactionUUID; + transactionUUID.generate(); + + LLMessageSystem* msg = gMessageSystem; + + msg->newMessageFast(_PREHASH_ParcelAccessListUpdate); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); + msg->nextBlockFast(_PREHASH_Data); + msg->addU32Fast(_PREHASH_Flags, list); + msg->addS32(_PREHASH_LocalID, parcel->getLocalID() ); + msg->addUUIDFast(_PREHASH_TransactionID, transactionUUID); + msg->addS32Fast(_PREHASH_SequenceID, 1); // sequence_id + msg->addS32Fast(_PREHASH_Sections, 0); // num_sections + + // pack an empty block since there will be no data + msg->nextBlockFast(_PREHASH_List); + msg->addUUIDFast(_PREHASH_ID, LLUUID::null ); + msg->addS32Fast(_PREHASH_Time, 0 ); + msg->addU32Fast(_PREHASH_Flags, 0 ); + + msg->sendReliable( region->getHost() ); +} + + + +// static - 'Sell to Anyone' clicked, throw up a confirmation dialog +void LLFloaterAuction::onClickSellToAnyone(void* data) +{ + LLFloaterAuction* self = (LLFloaterAuction*)(data); + if (self) + { + LLParcel* parcelp = self->mParcelp->getParcel(); + + // Do a confirmation + S32 sale_price = parcelp->getArea(); // Selling for L$1 per meter + S32 area = parcelp->getArea(); + + LLSD args; + args["LAND_SIZE"] = llformat("%d", area); + args["SALE_PRICE"] = llformat("%d", sale_price); + args["NAME"] = LLTrans::getString("Anyone"); + + LLNotification::Params params("ConfirmLandSaleChange"); // Re-use existing dialog + params.substitutions(args) + .functor.function(boost::bind(&LLFloaterAuction::onSellToAnyoneConfirmed, self, _1, _2)); + + params.name("ConfirmLandSaleToAnyoneChange"); + + // ask away + LLNotifications::instance().add(params); + } +} + + +// Sell confirmation clicked +bool LLFloaterAuction::onSellToAnyoneConfirmed(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) + { + doSellToAnyone(); + } + + return false; +} + + + +// Reset all the values for the parcel in preparation for a sale +void LLFloaterAuction::doSellToAnyone() +{ + LLParcel* parcelp = mParcelp->getParcel(); + LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); + + if (parcelp + && region + && !mParcelUpdateCapUrl.empty()) + { + LLSD body; + std::string empty; + + // request new properties update from simulator + U32 message_flags = 0x01; + body["flags"] = ll_sd_from_U32(message_flags); + + // Set all the default parcel properties for auction + body["local_id"] = parcelp->getLocalID(); + + // Set 'for sale' flag + U32 parcel_flags = parcelp->getParcelFlags() | PF_FOR_SALE; + // Ensure objects not included + parcel_flags &= ~PF_FOR_SALE_OBJECTS; + body["parcel_flags"] = ll_sd_from_U32(parcel_flags); + + body["sale_price"] = parcelp->getArea(); // Sell for L$1 per square meter + body["auth_buyer_id"] = LLUUID::null; // To anyone + + LL_INFOS() << "Sending parcel update to sell to anyone for L$1 via capability to: " + << mParcelUpdateCapUrl << LL_ENDL; + + LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(mParcelUpdateCapUrl, body, + "Parcel set as sell to everyone.", + "Parcel sell to everyone failed."); + + // clean up floater, and get out + cleanupAndClose(); + } +} + + +///---------------------------------------------------------------------------- +/// Local function definitions +///---------------------------------------------------------------------------- + +void auction_tga_upload_done(const LLUUID& asset_id, void* user_data, S32 status, LLExtStat ext_status) // StoreAssetData callback (fixed) +{ + std::string* name = (std::string*)(user_data); + LL_INFOS() << "Upload of asset '" << *name << "' " << asset_id + << " returned " << status << LL_ENDL; + delete name; + + gViewerWindow->getWindow()->decBusyCount(); + + if (0 == status) + { + LLNotificationsUtil::add("UploadWebSnapshotDone"); + } + else + { + LLSD args; + args["REASON"] = std::string(LLAssetStorage::getErrorString(status)); + LLNotificationsUtil::add("UploadAuctionSnapshotFail", args); + } +} + +void auction_j2c_upload_done(const LLUUID& asset_id, void* user_data, S32 status, LLExtStat ext_status) // StoreAssetData callback (fixed) +{ + std::string* name = (std::string*)(user_data); + LL_INFOS() << "Upload of asset '" << *name << "' " << asset_id + << " returned " << status << LL_ENDL; + delete name; + + gViewerWindow->getWindow()->decBusyCount(); + + if (0 == status) + { + LLNotificationsUtil::add("UploadSnapshotDone"); + } + else + { + LLSD args; + args["REASON"] = std::string(LLAssetStorage::getErrorString(status)); + LLNotificationsUtil::add("UploadAuctionSnapshotFail", args); + } +} diff --git a/indra/newview/llfloaterauction.h b/indra/newview/llfloaterauction.h index eaf32063c5..d736b36db1 100644 --- a/indra/newview/llfloaterauction.h +++ b/indra/newview/llfloaterauction.h @@ -1,86 +1,86 @@ -/** - * @file llfloaterauction.h - * @author James Cook, Ian Wilkes - * @brief llfloaterauction class header file - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERAUCTION_H -#define LL_LLFLOATERAUCTION_H - -#include "llfloater.h" -#include "lluuid.h" -#include "llpointer.h" -#include "llviewertexture.h" - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLFloaterAuction -// -// Class which holds the functionality to start auctions. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLParcelSelection; -class LLParcel; -class LLViewerRegion; - -class LLFloaterAuction : public LLFloater -{ - friend class LLFloaterReg; -public: - // LLFloater interface - void onOpen(const LLSD& key) override; - void draw() override; - -private: - - LLFloaterAuction(const LLSD& key); - ~LLFloaterAuction(); - - void initialize(); - - static void onClickSnapshot(void* data); - static void onClickResetParcel(void* data); - static void onClickSellToAnyone(void* data); // Sell to anyone clicked - bool onSellToAnyoneConfirmed(const LLSD& notification, const LLSD& response); // Sell confirmation clicked - static void onClickStartAuction(void* data); - - bool postBuild() override; - - void doResetParcel(); - void doSellToAnyone(); - void clearParcelAccessList( LLParcel* parcel, LLViewerRegion* region, U32 list); - void cleanupAndClose(); - -private: - - LLTransactionID mTransactionID; - LLAssetID mImageID; - LLPointer mImage; - LLSafeHandle mParcelp; - S32 mParcelID; - LLHost mParcelHost; - - std::string mParcelUpdateCapUrl; // "ParcelPropertiesUpdate" capability -}; - - -#endif // LL_LLFLOATERAUCTION_H +/** + * @file llfloaterauction.h + * @author James Cook, Ian Wilkes + * @brief llfloaterauction class header file + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERAUCTION_H +#define LL_LLFLOATERAUCTION_H + +#include "llfloater.h" +#include "lluuid.h" +#include "llpointer.h" +#include "llviewertexture.h" + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLFloaterAuction +// +// Class which holds the functionality to start auctions. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLParcelSelection; +class LLParcel; +class LLViewerRegion; + +class LLFloaterAuction : public LLFloater +{ + friend class LLFloaterReg; +public: + // LLFloater interface + void onOpen(const LLSD& key) override; + void draw() override; + +private: + + LLFloaterAuction(const LLSD& key); + ~LLFloaterAuction(); + + void initialize(); + + static void onClickSnapshot(void* data); + static void onClickResetParcel(void* data); + static void onClickSellToAnyone(void* data); // Sell to anyone clicked + bool onSellToAnyoneConfirmed(const LLSD& notification, const LLSD& response); // Sell confirmation clicked + static void onClickStartAuction(void* data); + + bool postBuild() override; + + void doResetParcel(); + void doSellToAnyone(); + void clearParcelAccessList( LLParcel* parcel, LLViewerRegion* region, U32 list); + void cleanupAndClose(); + +private: + + LLTransactionID mTransactionID; + LLAssetID mImageID; + LLPointer mImage; + LLSafeHandle mParcelp; + S32 mParcelID; + LLHost mParcelHost; + + std::string mParcelUpdateCapUrl; // "ParcelPropertiesUpdate" capability +}; + + +#endif // LL_LLFLOATERAUCTION_H diff --git a/indra/newview/llfloaterautoreplacesettings.cpp b/indra/newview/llfloaterautoreplacesettings.cpp index fe3713f2a1..99f24e161e 100644 --- a/indra/newview/llfloaterautoreplacesettings.cpp +++ b/indra/newview/llfloaterautoreplacesettings.cpp @@ -1,662 +1,662 @@ -/** - * @file llfloaterautoreplacesettings.cpp - * @brief Auto Replace List floater - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterautoreplacesettings.h" - -#include "llagentdata.h" -#include "llcommandhandler.h" -#include "llfloater.h" -#include "lluictrlfactory.h" -#include "llagent.h" -#include "llpanel.h" -#include "llbutton.h" -#include "llcolorswatch.h" -#include "llcombobox.h" -#include "llview.h" -#include "llbufferstream.h" -#include "llcheckboxctrl.h" -#include "llviewercontrol.h" - -#include "llui.h" -#include "llcontrol.h" -#include "llscrollingpanellist.h" -#include "llautoreplace.h" -#include "llfilepicker.h" -#include "llfile.h" -#include "llsdserialize.h" -#include "llsdutil.h" - -#include "llchat.h" -#include "llinventorymodel.h" -#include "llhost.h" -#include "llassetstorage.h" -#include "roles_constants.h" -#include "llviewermenufile.h" // LLFilePickerReplyThread -#include "llviewertexteditor.h" -#include - -#include -#include "llfloaterreg.h" -#include "llinspecttoast.h" -#include "llnotificationhandler.h" -#include "llnotificationmanager.h" -#include "llnotificationsutil.h" - - -LLFloaterAutoReplaceSettings::LLFloaterAutoReplaceSettings(const LLSD& key) - : LLFloater(key) - , mSelectedListName("") - , mListNames(NULL) - , mReplacementsList(NULL) - , mKeyword(NULL) - , mPreviousKeyword("") - , mReplacement(NULL) -{ -} - -void LLFloaterAutoReplaceSettings::onClose(bool app_quitting) -{ - cleanUp(); -} - -bool LLFloaterAutoReplaceSettings::postBuild(void) -{ - // get copies of the current settings that we will operate on - mEnabled = gSavedSettings.getBOOL("AutoReplace"); - LL_DEBUGS("AutoReplace") << ( mEnabled ? "enabled" : "disabled") << LL_ENDL; - - mSettings = LLAutoReplace::getInstance()->getSettings(); - - // global checkbox for whether or not autoreplace is active - LLUICtrl* enabledCheckbox = getChild("autoreplace_enable"); - enabledCheckbox->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onAutoReplaceToggled, this)); - enabledCheckbox->setValue(LLSD(mEnabled)); - - // top row list creation and deletion - getChild("autoreplace_import_list")->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onImportList,this)); - getChild("autoreplace_export_list")->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onExportList,this)); - getChild("autoreplace_new_list")->setCommitCallback( boost::bind(&LLFloaterAutoReplaceSettings::onNewList,this)); - getChild("autoreplace_delete_list")->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onDeleteList,this)); - - // the list of keyword->replacement lists - mListNames = getChild("autoreplace_list_name"); - mListNames->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onSelectList, this)); - mListNames->setCommitOnSelectionChange(true); - - // list ordering - getChild("autoreplace_list_up")->setCommitCallback( boost::bind(&LLFloaterAutoReplaceSettings::onListUp,this)); - getChild("autoreplace_list_down")->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onListDown,this)); - - // keyword->replacement entry add / delete - getChild("autoreplace_add_entry")->setCommitCallback( boost::bind(&LLFloaterAutoReplaceSettings::onAddEntry,this)); - getChild("autoreplace_delete_entry")->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onDeleteEntry,this)); - - // entry edits - mKeyword = getChild("autoreplace_keyword"); - mReplacement = getChild("autoreplace_replacement"); - getChild("autoreplace_save_entry")->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onSaveEntry, this)); - - // dialog termination ( Save Changes / Cancel ) - getChild("autoreplace_save_changes")->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onSaveChanges, this)); - getChild("autoreplace_cancel")->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onCancel, this)); - - // the list of keyword->replacement pairs - mReplacementsList = getChild("autoreplace_list_replacements"); - mReplacementsList->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onSelectEntry, this)); - mReplacementsList->setCommitOnSelectionChange(true); - - center(); - - mSelectedListName.clear(); - updateListNames(); - updateListNamesControls(); - updateReplacementsList(); - - return true; -} - - -void LLFloaterAutoReplaceSettings::updateListNames() -{ - mListNames->deleteAllItems(); // start from scratch - - LLSD listNames = mSettings.getListNames(); // Array of Strings - - for ( LLSD::array_const_iterator entry = listNames.beginArray(), end = listNames.endArray(); - entry != end; - ++entry - ) - { - const std::string& listName = entry->asString(); - mListNames->addSimpleElement(listName); - } - - if (!mSelectedListName.empty()) - { - mListNames->setSelectedByValue( LLSD(mSelectedListName), true ); - } -} - -void LLFloaterAutoReplaceSettings::updateListNamesControls() -{ - if ( mSelectedListName.empty() ) - { - // There is no selected list - - // Disable all controls that operate on the selected list - getChild("autoreplace_export_list")->setEnabled(false); - getChild("autoreplace_delete_list")->setEnabled(false); - getChild("autoreplace_list_up")->setEnabled(false); - getChild("autoreplace_list_down")->setEnabled(false); - - mReplacementsList->deleteAllItems(); - } - else - { - // Enable the controls that operate on the selected list - getChild("autoreplace_export_list")->setEnabled(true); - getChild("autoreplace_delete_list")->setEnabled(true); - getChild("autoreplace_list_up")->setEnabled(!selectedListIsFirst()); - getChild("autoreplace_list_down")->setEnabled(!selectedListIsLast()); - } -} - -void LLFloaterAutoReplaceSettings::onSelectList() -{ - std::string previousSelectedListName = mSelectedListName; - // only one selection allowed - LLSD selected = mListNames->getSelectedValue(); - if (selected.isDefined()) - { - mSelectedListName = selected.asString(); - LL_DEBUGS("AutoReplace")<<"selected list '"<getSelectedValue(); - if (selectedRow.isDefined()) - { - mPreviousKeyword = selectedRow.asString(); - LL_DEBUGS("AutoReplace")<<"selected entry '"<setValue(selectedRow); - std::string replacement = mSettings.replacementFor(mPreviousKeyword, mSelectedListName ); - mReplacement->setValue(replacement); - enableReplacementEntry(); - mReplacement->setFocus(true); - } - else - { - // no entry selection, so the entry panel should be off - disableReplacementEntry(); - LL_DEBUGS("AutoReplace")<<"no row selected"<deleteAllItems(); - - if ( mSelectedListName.empty() ) - { - mReplacementsList->setEnabled(false); - getChild("autoreplace_add_entry")->setEnabled(false); - disableReplacementEntry(); - } - else - { - // Populate the keyword->replacement list from the selected list - const LLSD* mappings = mSettings.getListEntries(mSelectedListName); - for ( LLSD::map_const_iterator entry = mappings->beginMap(), end = mappings->endMap(); - entry != end; - entry++ - ) - { - LLSD row; - row["id"] = entry->first; - row["columns"][0]["column"] = "keyword"; - row["columns"][0]["value"] = entry->first; - row["columns"][1]["column"] = "replacement"; - row["columns"][1]["value"] = entry->second; - - mReplacementsList->addElement(row, ADD_BOTTOM); - } - - mReplacementsList->deselectAllItems(false /* don't call commit */); - mReplacementsList->setEnabled(true); - - getChild("autoreplace_add_entry")->setEnabled(true); - disableReplacementEntry(); - } -} - -void LLFloaterAutoReplaceSettings::enableReplacementEntry() -{ - LL_DEBUGS("AutoReplace")<setEnabled(true); - mReplacement->setEnabled(true); - getChild("autoreplace_save_entry")->setEnabled(true); - getChild("autoreplace_delete_entry")->setEnabled(true); -} - -void LLFloaterAutoReplaceSettings::disableReplacementEntry() -{ - LL_DEBUGS("AutoReplace")<clear(); - mKeyword->setEnabled(false); - mReplacement->clear(); - mReplacement->setEnabled(false); - getChild("autoreplace_save_entry")->setEnabled(false); - getChild("autoreplace_delete_entry")->setEnabled(false); -} - -// called when the global settings checkbox is changed -void LLFloaterAutoReplaceSettings::onAutoReplaceToggled() -{ - // set our local copy of the flag, copied to the global preference in onOk - mEnabled = childGetValue("autoreplace_enable").asBoolean(); - LL_DEBUGS("AutoReplace")<< "autoreplace_enable " << ( mEnabled ? "on" : "off" ) << LL_ENDL; -} - -// called when the List Up button is pressed -void LLFloaterAutoReplaceSettings::onListUp() -{ - S32 selectedRow = mListNames->getFirstSelectedIndex(); - LLSD selectedName = mListNames->getSelectedValue().asString(); - - if ( mSettings.increaseListPriority(selectedName) ) - { - updateListNames(); - updateListNamesControls(); - } - else - { - LL_WARNS("AutoReplace") - << "invalid row ("<getFirstSelectedIndex(); - std::string selectedName = mListNames->getSelectedValue().asString(); - - if ( mSettings.decreaseListPriority(selectedName) ) - { - updateListNames(); - updateListNamesControls(); - } - else - { - LL_WARNS("AutoReplace") - << "invalid row ("<getSelectedValue(); - if (selectedRow.isDefined()) - { - std::string keyword = selectedRow.asString(); - mReplacementsList->deleteSelectedItems(); // delete from the control - mSettings.removeEntryFromList(keyword, mSelectedListName); // delete from the local settings copy - disableReplacementEntry(); // no selection active, so turn off the buttons - } -} - -// called when the Import List button is pressed -void LLFloaterAutoReplaceSettings::onImportList() -{ - LLFilePickerReplyThread::startPicker(boost::bind(&LLFloaterAutoReplaceSettings::loadListFromFile, this, _1), LLFilePicker::FFLOAD_XML, false); -} - -void LLFloaterAutoReplaceSettings::loadListFromFile(const std::vector& filenames) -{ - llifstream file; - file.open(filenames[0].c_str()); - LLSD newList; - if (file.is_open()) - { - LLSDSerialize::fromXMLDocument(newList, file); - } - file.close(); - - switch ( mSettings.addList(newList) ) - { - case LLAutoReplaceSettings::AddListOk: - mSelectedListName = LLAutoReplaceSettings::getListName(newList); - - updateListNames(); - updateListNamesControls(); - updateReplacementsList(); - break; - - case LLAutoReplaceSettings::AddListDuplicateName: - { - std::string newName = LLAutoReplaceSettings::getListName(newList); - LL_WARNS("AutoReplace")<<"name '"<getSelectedValue().asString(); - if ( ! listName.empty() ) - { - if ( mSettings.removeReplacementList(listName) ) - { - LL_INFOS("AutoReplace")<<"deleted list '"<deleteSelectedItems(); // remove from the scrolling list - mSelectedListName.clear(); - updateListNames(); - updateListNamesControls(); - updateReplacementsList(); - } - else - { - LL_WARNS("AutoReplace")<<"failed to delete list '"<getFirstSelected()->getColumn(0)->getValue().asString(); - std::string listFileName = listName + ".xml"; - LLFilePickerReplyThread::startPicker(boost::bind(&LLFloaterAutoReplaceSettings::saveListToFile, this, _1, listName), LLFilePicker::FFSAVE_XML, listFileName); -} - -void LLFloaterAutoReplaceSettings::saveListToFile(const std::vector& filenames, std::string listName) -{ - llofstream file; - const LLSD* list = mSettings.exportList(listName); - file.open(filenames[0].c_str()); - LLSDSerialize::toPrettyXML(*list, file); - file.close(); -} - -void LLFloaterAutoReplaceSettings::onAddEntry() -{ - mPreviousKeyword.clear(); - mReplacementsList->deselectAllItems(false /* don't call commit */); - mKeyword->clear(); - mReplacement->clear(); - enableReplacementEntry(); - mKeyword->setFocus(true); -} - -void LLFloaterAutoReplaceSettings::onSaveEntry() -{ - LL_DEBUGS("AutoReplace")<<"called"<getWText(); - LLWString replacement = mReplacement->getWText(); - if ( mSettings.addEntryToList(keyword, replacement, mSelectedListName) ) - { - // insert the new keyword->replacement pair - LL_INFOS("AutoReplace") - << "list '" << mSelectedListName << "' " - << "added '" << wstring_to_utf8str(keyword) - << "' -> '" << wstring_to_utf8str(replacement) - << "'" << LL_ENDL; - - updateReplacementsList(); - } - else - { - LLNotificationsUtil::add("InvalidAutoReplaceEntry"); - LL_WARNS("AutoReplace")<<"invalid entry " - << "keyword '" << wstring_to_utf8str(keyword) - << "' replacement '" << wstring_to_utf8str(replacement) - << "'" << LL_ENDL; - } -} - -void LLFloaterAutoReplaceSettings::onCancel() -{ - cleanUp(); - closeFloater(false /* not quitting */); -} - -void LLFloaterAutoReplaceSettings::onSaveChanges() -{ - // put our local copy of the settings into the active copy - LLAutoReplace::getInstance()->setSettings( mSettings ); - // save our local copy of the global feature enable/disable value - gSavedSettings.setBOOL("AutoReplace", mEnabled); - cleanUp(); - closeFloater(false /* not quitting */); -} - -void LLFloaterAutoReplaceSettings::cleanUp() -{ - -} - -bool LLFloaterAutoReplaceSettings::selectedListIsFirst() -{ - bool isFirst = false; - - if (!mSelectedListName.empty()) - { - LLSD lists = mSettings.getListNames(); // an Array of Strings - LLSD first = lists.get(0); - if ( first.isString() && first.asString() == mSelectedListName ) - { - isFirst = true; - } - } - return isFirst; -} - -bool LLFloaterAutoReplaceSettings::selectedListIsLast() -{ - bool isLast = false; - - if (!mSelectedListName.empty()) - { - LLSD last; - LLSD lists = mSettings.getListNames(); // an Array of Strings - for ( LLSD::array_const_iterator list = lists.beginArray(), listEnd = lists.endArray(); - list != listEnd; - list++ - ) - { - last = *list; - } - if ( last.isString() && last.asString() == mSelectedListName ) - { - isLast = true; - } - } - return isLast; -} - -/* TBD -mOldText = getChild("autoreplace_old_text"); -mNewText = getChild("autoreplace_new_text"); -*/ +/** + * @file llfloaterautoreplacesettings.cpp + * @brief Auto Replace List floater + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterautoreplacesettings.h" + +#include "llagentdata.h" +#include "llcommandhandler.h" +#include "llfloater.h" +#include "lluictrlfactory.h" +#include "llagent.h" +#include "llpanel.h" +#include "llbutton.h" +#include "llcolorswatch.h" +#include "llcombobox.h" +#include "llview.h" +#include "llbufferstream.h" +#include "llcheckboxctrl.h" +#include "llviewercontrol.h" + +#include "llui.h" +#include "llcontrol.h" +#include "llscrollingpanellist.h" +#include "llautoreplace.h" +#include "llfilepicker.h" +#include "llfile.h" +#include "llsdserialize.h" +#include "llsdutil.h" + +#include "llchat.h" +#include "llinventorymodel.h" +#include "llhost.h" +#include "llassetstorage.h" +#include "roles_constants.h" +#include "llviewermenufile.h" // LLFilePickerReplyThread +#include "llviewertexteditor.h" +#include + +#include +#include "llfloaterreg.h" +#include "llinspecttoast.h" +#include "llnotificationhandler.h" +#include "llnotificationmanager.h" +#include "llnotificationsutil.h" + + +LLFloaterAutoReplaceSettings::LLFloaterAutoReplaceSettings(const LLSD& key) + : LLFloater(key) + , mSelectedListName("") + , mListNames(NULL) + , mReplacementsList(NULL) + , mKeyword(NULL) + , mPreviousKeyword("") + , mReplacement(NULL) +{ +} + +void LLFloaterAutoReplaceSettings::onClose(bool app_quitting) +{ + cleanUp(); +} + +bool LLFloaterAutoReplaceSettings::postBuild(void) +{ + // get copies of the current settings that we will operate on + mEnabled = gSavedSettings.getBOOL("AutoReplace"); + LL_DEBUGS("AutoReplace") << ( mEnabled ? "enabled" : "disabled") << LL_ENDL; + + mSettings = LLAutoReplace::getInstance()->getSettings(); + + // global checkbox for whether or not autoreplace is active + LLUICtrl* enabledCheckbox = getChild("autoreplace_enable"); + enabledCheckbox->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onAutoReplaceToggled, this)); + enabledCheckbox->setValue(LLSD(mEnabled)); + + // top row list creation and deletion + getChild("autoreplace_import_list")->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onImportList,this)); + getChild("autoreplace_export_list")->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onExportList,this)); + getChild("autoreplace_new_list")->setCommitCallback( boost::bind(&LLFloaterAutoReplaceSettings::onNewList,this)); + getChild("autoreplace_delete_list")->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onDeleteList,this)); + + // the list of keyword->replacement lists + mListNames = getChild("autoreplace_list_name"); + mListNames->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onSelectList, this)); + mListNames->setCommitOnSelectionChange(true); + + // list ordering + getChild("autoreplace_list_up")->setCommitCallback( boost::bind(&LLFloaterAutoReplaceSettings::onListUp,this)); + getChild("autoreplace_list_down")->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onListDown,this)); + + // keyword->replacement entry add / delete + getChild("autoreplace_add_entry")->setCommitCallback( boost::bind(&LLFloaterAutoReplaceSettings::onAddEntry,this)); + getChild("autoreplace_delete_entry")->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onDeleteEntry,this)); + + // entry edits + mKeyword = getChild("autoreplace_keyword"); + mReplacement = getChild("autoreplace_replacement"); + getChild("autoreplace_save_entry")->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onSaveEntry, this)); + + // dialog termination ( Save Changes / Cancel ) + getChild("autoreplace_save_changes")->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onSaveChanges, this)); + getChild("autoreplace_cancel")->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onCancel, this)); + + // the list of keyword->replacement pairs + mReplacementsList = getChild("autoreplace_list_replacements"); + mReplacementsList->setCommitCallback(boost::bind(&LLFloaterAutoReplaceSettings::onSelectEntry, this)); + mReplacementsList->setCommitOnSelectionChange(true); + + center(); + + mSelectedListName.clear(); + updateListNames(); + updateListNamesControls(); + updateReplacementsList(); + + return true; +} + + +void LLFloaterAutoReplaceSettings::updateListNames() +{ + mListNames->deleteAllItems(); // start from scratch + + LLSD listNames = mSettings.getListNames(); // Array of Strings + + for ( LLSD::array_const_iterator entry = listNames.beginArray(), end = listNames.endArray(); + entry != end; + ++entry + ) + { + const std::string& listName = entry->asString(); + mListNames->addSimpleElement(listName); + } + + if (!mSelectedListName.empty()) + { + mListNames->setSelectedByValue( LLSD(mSelectedListName), true ); + } +} + +void LLFloaterAutoReplaceSettings::updateListNamesControls() +{ + if ( mSelectedListName.empty() ) + { + // There is no selected list + + // Disable all controls that operate on the selected list + getChild("autoreplace_export_list")->setEnabled(false); + getChild("autoreplace_delete_list")->setEnabled(false); + getChild("autoreplace_list_up")->setEnabled(false); + getChild("autoreplace_list_down")->setEnabled(false); + + mReplacementsList->deleteAllItems(); + } + else + { + // Enable the controls that operate on the selected list + getChild("autoreplace_export_list")->setEnabled(true); + getChild("autoreplace_delete_list")->setEnabled(true); + getChild("autoreplace_list_up")->setEnabled(!selectedListIsFirst()); + getChild("autoreplace_list_down")->setEnabled(!selectedListIsLast()); + } +} + +void LLFloaterAutoReplaceSettings::onSelectList() +{ + std::string previousSelectedListName = mSelectedListName; + // only one selection allowed + LLSD selected = mListNames->getSelectedValue(); + if (selected.isDefined()) + { + mSelectedListName = selected.asString(); + LL_DEBUGS("AutoReplace")<<"selected list '"<getSelectedValue(); + if (selectedRow.isDefined()) + { + mPreviousKeyword = selectedRow.asString(); + LL_DEBUGS("AutoReplace")<<"selected entry '"<setValue(selectedRow); + std::string replacement = mSettings.replacementFor(mPreviousKeyword, mSelectedListName ); + mReplacement->setValue(replacement); + enableReplacementEntry(); + mReplacement->setFocus(true); + } + else + { + // no entry selection, so the entry panel should be off + disableReplacementEntry(); + LL_DEBUGS("AutoReplace")<<"no row selected"<deleteAllItems(); + + if ( mSelectedListName.empty() ) + { + mReplacementsList->setEnabled(false); + getChild("autoreplace_add_entry")->setEnabled(false); + disableReplacementEntry(); + } + else + { + // Populate the keyword->replacement list from the selected list + const LLSD* mappings = mSettings.getListEntries(mSelectedListName); + for ( LLSD::map_const_iterator entry = mappings->beginMap(), end = mappings->endMap(); + entry != end; + entry++ + ) + { + LLSD row; + row["id"] = entry->first; + row["columns"][0]["column"] = "keyword"; + row["columns"][0]["value"] = entry->first; + row["columns"][1]["column"] = "replacement"; + row["columns"][1]["value"] = entry->second; + + mReplacementsList->addElement(row, ADD_BOTTOM); + } + + mReplacementsList->deselectAllItems(false /* don't call commit */); + mReplacementsList->setEnabled(true); + + getChild("autoreplace_add_entry")->setEnabled(true); + disableReplacementEntry(); + } +} + +void LLFloaterAutoReplaceSettings::enableReplacementEntry() +{ + LL_DEBUGS("AutoReplace")<setEnabled(true); + mReplacement->setEnabled(true); + getChild("autoreplace_save_entry")->setEnabled(true); + getChild("autoreplace_delete_entry")->setEnabled(true); +} + +void LLFloaterAutoReplaceSettings::disableReplacementEntry() +{ + LL_DEBUGS("AutoReplace")<clear(); + mKeyword->setEnabled(false); + mReplacement->clear(); + mReplacement->setEnabled(false); + getChild("autoreplace_save_entry")->setEnabled(false); + getChild("autoreplace_delete_entry")->setEnabled(false); +} + +// called when the global settings checkbox is changed +void LLFloaterAutoReplaceSettings::onAutoReplaceToggled() +{ + // set our local copy of the flag, copied to the global preference in onOk + mEnabled = childGetValue("autoreplace_enable").asBoolean(); + LL_DEBUGS("AutoReplace")<< "autoreplace_enable " << ( mEnabled ? "on" : "off" ) << LL_ENDL; +} + +// called when the List Up button is pressed +void LLFloaterAutoReplaceSettings::onListUp() +{ + S32 selectedRow = mListNames->getFirstSelectedIndex(); + LLSD selectedName = mListNames->getSelectedValue().asString(); + + if ( mSettings.increaseListPriority(selectedName) ) + { + updateListNames(); + updateListNamesControls(); + } + else + { + LL_WARNS("AutoReplace") + << "invalid row ("<getFirstSelectedIndex(); + std::string selectedName = mListNames->getSelectedValue().asString(); + + if ( mSettings.decreaseListPriority(selectedName) ) + { + updateListNames(); + updateListNamesControls(); + } + else + { + LL_WARNS("AutoReplace") + << "invalid row ("<getSelectedValue(); + if (selectedRow.isDefined()) + { + std::string keyword = selectedRow.asString(); + mReplacementsList->deleteSelectedItems(); // delete from the control + mSettings.removeEntryFromList(keyword, mSelectedListName); // delete from the local settings copy + disableReplacementEntry(); // no selection active, so turn off the buttons + } +} + +// called when the Import List button is pressed +void LLFloaterAutoReplaceSettings::onImportList() +{ + LLFilePickerReplyThread::startPicker(boost::bind(&LLFloaterAutoReplaceSettings::loadListFromFile, this, _1), LLFilePicker::FFLOAD_XML, false); +} + +void LLFloaterAutoReplaceSettings::loadListFromFile(const std::vector& filenames) +{ + llifstream file; + file.open(filenames[0].c_str()); + LLSD newList; + if (file.is_open()) + { + LLSDSerialize::fromXMLDocument(newList, file); + } + file.close(); + + switch ( mSettings.addList(newList) ) + { + case LLAutoReplaceSettings::AddListOk: + mSelectedListName = LLAutoReplaceSettings::getListName(newList); + + updateListNames(); + updateListNamesControls(); + updateReplacementsList(); + break; + + case LLAutoReplaceSettings::AddListDuplicateName: + { + std::string newName = LLAutoReplaceSettings::getListName(newList); + LL_WARNS("AutoReplace")<<"name '"<getSelectedValue().asString(); + if ( ! listName.empty() ) + { + if ( mSettings.removeReplacementList(listName) ) + { + LL_INFOS("AutoReplace")<<"deleted list '"<deleteSelectedItems(); // remove from the scrolling list + mSelectedListName.clear(); + updateListNames(); + updateListNamesControls(); + updateReplacementsList(); + } + else + { + LL_WARNS("AutoReplace")<<"failed to delete list '"<getFirstSelected()->getColumn(0)->getValue().asString(); + std::string listFileName = listName + ".xml"; + LLFilePickerReplyThread::startPicker(boost::bind(&LLFloaterAutoReplaceSettings::saveListToFile, this, _1, listName), LLFilePicker::FFSAVE_XML, listFileName); +} + +void LLFloaterAutoReplaceSettings::saveListToFile(const std::vector& filenames, std::string listName) +{ + llofstream file; + const LLSD* list = mSettings.exportList(listName); + file.open(filenames[0].c_str()); + LLSDSerialize::toPrettyXML(*list, file); + file.close(); +} + +void LLFloaterAutoReplaceSettings::onAddEntry() +{ + mPreviousKeyword.clear(); + mReplacementsList->deselectAllItems(false /* don't call commit */); + mKeyword->clear(); + mReplacement->clear(); + enableReplacementEntry(); + mKeyword->setFocus(true); +} + +void LLFloaterAutoReplaceSettings::onSaveEntry() +{ + LL_DEBUGS("AutoReplace")<<"called"<getWText(); + LLWString replacement = mReplacement->getWText(); + if ( mSettings.addEntryToList(keyword, replacement, mSelectedListName) ) + { + // insert the new keyword->replacement pair + LL_INFOS("AutoReplace") + << "list '" << mSelectedListName << "' " + << "added '" << wstring_to_utf8str(keyword) + << "' -> '" << wstring_to_utf8str(replacement) + << "'" << LL_ENDL; + + updateReplacementsList(); + } + else + { + LLNotificationsUtil::add("InvalidAutoReplaceEntry"); + LL_WARNS("AutoReplace")<<"invalid entry " + << "keyword '" << wstring_to_utf8str(keyword) + << "' replacement '" << wstring_to_utf8str(replacement) + << "'" << LL_ENDL; + } +} + +void LLFloaterAutoReplaceSettings::onCancel() +{ + cleanUp(); + closeFloater(false /* not quitting */); +} + +void LLFloaterAutoReplaceSettings::onSaveChanges() +{ + // put our local copy of the settings into the active copy + LLAutoReplace::getInstance()->setSettings( mSettings ); + // save our local copy of the global feature enable/disable value + gSavedSettings.setBOOL("AutoReplace", mEnabled); + cleanUp(); + closeFloater(false /* not quitting */); +} + +void LLFloaterAutoReplaceSettings::cleanUp() +{ + +} + +bool LLFloaterAutoReplaceSettings::selectedListIsFirst() +{ + bool isFirst = false; + + if (!mSelectedListName.empty()) + { + LLSD lists = mSettings.getListNames(); // an Array of Strings + LLSD first = lists.get(0); + if ( first.isString() && first.asString() == mSelectedListName ) + { + isFirst = true; + } + } + return isFirst; +} + +bool LLFloaterAutoReplaceSettings::selectedListIsLast() +{ + bool isLast = false; + + if (!mSelectedListName.empty()) + { + LLSD last; + LLSD lists = mSettings.getListNames(); // an Array of Strings + for ( LLSD::array_const_iterator list = lists.beginArray(), listEnd = lists.endArray(); + list != listEnd; + list++ + ) + { + last = *list; + } + if ( last.isString() && last.asString() == mSelectedListName ) + { + isLast = true; + } + } + return isLast; +} + +/* TBD +mOldText = getChild("autoreplace_old_text"); +mNewText = getChild("autoreplace_new_text"); +*/ diff --git a/indra/newview/llfloaterautoreplacesettings.h b/indra/newview/llfloaterautoreplacesettings.h index d9aea782bb..94a7c00c15 100644 --- a/indra/newview/llfloaterautoreplacesettings.h +++ b/indra/newview/llfloaterautoreplacesettings.h @@ -1,118 +1,118 @@ -/** - * @file llfloaterautoreplacesettings.h - * @brief Auto Replace List floater - * @copyright Copyright (c) 2011 LordGregGreg Back - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * $/LicenseInfo$ - */ - -#ifndef LLFLOATERAUTOREPLACESETTINGS_H -#define LLFLOATERAUTOREPLACESETTINGS_H - -#include "llfloater.h" -#include "llmediactrl.h" -#include "llscrolllistctrl.h" -#include "lllineeditor.h" - -#include "llviewerinventory.h" -#include -#include "llautoreplace.h" - -class LLFloaterAutoReplaceSettings : public LLFloater -{ -public: - LLFloaterAutoReplaceSettings(const LLSD& key); - - bool postBuild() override; - void onClose(bool app_quitting) override; - -private: - - /** @{ @name Local Copies of Settings - * These are populated in the postBuild method with the values - * current when the floater is instantiated, and then either - * discarded when Cancel is pressed, or copied back to the active - * settings if Ok is pressed. - */ - bool mEnabled; ///< the global preference for AutoReplace - LLAutoReplaceSettings mSettings; ///< settings being modified - /** @} */ - - /// convenience variable - the name of the currently selected list (if any) - std::string mSelectedListName; - /// the scrolling list of list names (one column, no headings, order manually controlled) - LLScrollListCtrl* mListNames; - /// the scroling list of keyword->replacement pairs - LLScrollListCtrl* mReplacementsList; - - /// the keyword for the entry editing pane - LLLineEditor* mKeyword; - /// saved keyword value - std::string mPreviousKeyword; - /// the replacement for the entry editing pane - LLLineEditor* mReplacement; - - /// callback for when the feature enable/disable checkbox changes - void onAutoReplaceToggled(); - /// callback for when an entry in the list of list names is selected - void onSelectList(); - - void onImportList(); - void onExportList(); - void onNewList(); - void onDeleteList(); - - void onListUp(); - void onListDown(); - - void onSelectEntry(); - void onAddEntry(); - void onDeleteEntry(); - void onSaveEntry(); - - void onSaveChanges(); - void onCancel(); - - /// updates the contents of the mListNames - void updateListNames(); - /// updates the controls associated with mListNames (depends on whether a name is selected or not) - void updateListNamesControls(); - /// updates the contents of the mReplacementsList - void updateReplacementsList(); - /// enables the components that should only be active when a keyword is selected - void enableReplacementEntry(); - /// disables the components that should only be active when a keyword is selected - void disableReplacementEntry(); - - /// called from the AddAutoReplaceList notification dialog - bool callbackNewListName(const LLSD& notification, const LLSD& response); - /// called from the RenameAutoReplaceList notification dialog - bool callbackListNameConflict(const LLSD& notification, const LLSD& response); - - bool selectedListIsFirst(); - bool selectedListIsLast(); - - void cleanUp(); - - void loadListFromFile(const std::vector& filenames); - void saveListToFile(const std::vector& filenames, std::string listName); -}; - -#endif // LLFLOATERAUTOREPLACESETTINGS_H +/** + * @file llfloaterautoreplacesettings.h + * @brief Auto Replace List floater + * @copyright Copyright (c) 2011 LordGregGreg Back + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * $/LicenseInfo$ + */ + +#ifndef LLFLOATERAUTOREPLACESETTINGS_H +#define LLFLOATERAUTOREPLACESETTINGS_H + +#include "llfloater.h" +#include "llmediactrl.h" +#include "llscrolllistctrl.h" +#include "lllineeditor.h" + +#include "llviewerinventory.h" +#include +#include "llautoreplace.h" + +class LLFloaterAutoReplaceSettings : public LLFloater +{ +public: + LLFloaterAutoReplaceSettings(const LLSD& key); + + bool postBuild() override; + void onClose(bool app_quitting) override; + +private: + + /** @{ @name Local Copies of Settings + * These are populated in the postBuild method with the values + * current when the floater is instantiated, and then either + * discarded when Cancel is pressed, or copied back to the active + * settings if Ok is pressed. + */ + bool mEnabled; ///< the global preference for AutoReplace + LLAutoReplaceSettings mSettings; ///< settings being modified + /** @} */ + + /// convenience variable - the name of the currently selected list (if any) + std::string mSelectedListName; + /// the scrolling list of list names (one column, no headings, order manually controlled) + LLScrollListCtrl* mListNames; + /// the scroling list of keyword->replacement pairs + LLScrollListCtrl* mReplacementsList; + + /// the keyword for the entry editing pane + LLLineEditor* mKeyword; + /// saved keyword value + std::string mPreviousKeyword; + /// the replacement for the entry editing pane + LLLineEditor* mReplacement; + + /// callback for when the feature enable/disable checkbox changes + void onAutoReplaceToggled(); + /// callback for when an entry in the list of list names is selected + void onSelectList(); + + void onImportList(); + void onExportList(); + void onNewList(); + void onDeleteList(); + + void onListUp(); + void onListDown(); + + void onSelectEntry(); + void onAddEntry(); + void onDeleteEntry(); + void onSaveEntry(); + + void onSaveChanges(); + void onCancel(); + + /// updates the contents of the mListNames + void updateListNames(); + /// updates the controls associated with mListNames (depends on whether a name is selected or not) + void updateListNamesControls(); + /// updates the contents of the mReplacementsList + void updateReplacementsList(); + /// enables the components that should only be active when a keyword is selected + void enableReplacementEntry(); + /// disables the components that should only be active when a keyword is selected + void disableReplacementEntry(); + + /// called from the AddAutoReplaceList notification dialog + bool callbackNewListName(const LLSD& notification, const LLSD& response); + /// called from the RenameAutoReplaceList notification dialog + bool callbackListNameConflict(const LLSD& notification, const LLSD& response); + + bool selectedListIsFirst(); + bool selectedListIsLast(); + + void cleanUp(); + + void loadListFromFile(const std::vector& filenames); + void saveListToFile(const std::vector& filenames, std::string listName); +}; + +#endif // LLFLOATERAUTOREPLACESETTINGS_H diff --git a/indra/newview/llfloateravatar.cpp b/indra/newview/llfloateravatar.cpp index 226d615ca4..6a38d7549c 100644 --- a/indra/newview/llfloateravatar.cpp +++ b/indra/newview/llfloateravatar.cpp @@ -1,66 +1,66 @@ -/** - * @file llfloateravatar.h - * @author Leyla Farazha - * @brief floater for the avatar changer - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/** - * Floater that appears when buying an object, giving a preview - * of its contents and their permissions. - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloateravatar.h" -#include "lluictrlfactory.h" -#include "llmediactrl.h" - - -LLFloaterAvatar::LLFloaterAvatar(const LLSD& key) - : LLFloater(key) -{ -} - -LLFloaterAvatar::~LLFloaterAvatar() -{ - if (mAvatarPicker) - { - mAvatarPicker->navigateStop(); - mAvatarPicker->clearCache(); //images are reloading each time already - mAvatarPicker->unloadMediaSource(); - } -} - -bool LLFloaterAvatar::postBuild() -{ - mAvatarPicker = findChild("avatar_picker_contents"); - if (mAvatarPicker) - { - mAvatarPicker->clearCache(); - } - enableResizeCtrls(true, true, false); - return true; -} - - +/** + * @file llfloateravatar.h + * @author Leyla Farazha + * @brief floater for the avatar changer + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/** + * Floater that appears when buying an object, giving a preview + * of its contents and their permissions. + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloateravatar.h" +#include "lluictrlfactory.h" +#include "llmediactrl.h" + + +LLFloaterAvatar::LLFloaterAvatar(const LLSD& key) + : LLFloater(key) +{ +} + +LLFloaterAvatar::~LLFloaterAvatar() +{ + if (mAvatarPicker) + { + mAvatarPicker->navigateStop(); + mAvatarPicker->clearCache(); //images are reloading each time already + mAvatarPicker->unloadMediaSource(); + } +} + +bool LLFloaterAvatar::postBuild() +{ + mAvatarPicker = findChild("avatar_picker_contents"); + if (mAvatarPicker) + { + mAvatarPicker->clearCache(); + } + enableResizeCtrls(true, true, false); + return true; +} + + diff --git a/indra/newview/llfloateravatar.h b/indra/newview/llfloateravatar.h index f57258b806..fb591c8306 100644 --- a/indra/newview/llfloateravatar.h +++ b/indra/newview/llfloateravatar.h @@ -1,46 +1,46 @@ -/** - * @file llfloateravatar.h - * @author Leyla Farazha - * @brief floater for the avatar changer - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_FLOATER_AVATAR_H -#define LL_FLOATER_AVATAR_H - -#include "llfloater.h" -class LLMediaCtrl; - -class LLFloaterAvatar: - public LLFloater -{ - friend class LLFloaterReg; -private: - LLFloaterAvatar(const LLSD& key); - ~LLFloaterAvatar(); - bool postBuild() override; - - LLMediaCtrl* mAvatarPicker; -}; - -#endif +/** + * @file llfloateravatar.h + * @author Leyla Farazha + * @brief floater for the avatar changer + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_FLOATER_AVATAR_H +#define LL_FLOATER_AVATAR_H + +#include "llfloater.h" +class LLMediaCtrl; + +class LLFloaterAvatar: + public LLFloater +{ + friend class LLFloaterReg; +private: + LLFloaterAvatar(const LLSD& key); + ~LLFloaterAvatar(); + bool postBuild() override; + + LLMediaCtrl* mAvatarPicker; +}; + +#endif diff --git a/indra/newview/llfloateravatarpicker.cpp b/indra/newview/llfloateravatarpicker.cpp index 22dc17e217..6087e6c0ee 100644 --- a/indra/newview/llfloateravatarpicker.cpp +++ b/indra/newview/llfloateravatarpicker.cpp @@ -1,814 +1,814 @@ -/** - * @file llfloateravatarpicker.cpp - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloateravatarpicker.h" - -// Viewer includes -#include "llagent.h" -#include "llcallingcard.h" -#include "llfocusmgr.h" -#include "llfloaterreg.h" -#include "llimview.h" // for gIMMgr -#include "lltooldraganddrop.h" // for LLToolDragAndDrop -#include "llviewercontrol.h" -#include "llviewerregion.h" // getCapability() -#include "llworld.h" - -// Linden libraries -#include "llavatarnamecache.h" // IDEVO -#include "llbutton.h" -#include "llcachename.h" -#include "lllineeditor.h" -#include "llscrolllistctrl.h" -#include "llscrolllistitem.h" -#include "llscrolllistcell.h" -#include "lltabcontainer.h" -#include "lluictrlfactory.h" -#include "llfocusmgr.h" -#include "lldraghandle.h" -#include "message.h" -#include "llcorehttputil.h" - -//#include "llsdserialize.h" - -static const U32 AVATAR_PICKER_SEARCH_TIMEOUT = 180U; - -//put it back as a member once the legacy path is out? -static std::map sAvatarNameMap; - -LLFloaterAvatarPicker* LLFloaterAvatarPicker::show(select_callback_t callback, - bool allow_multiple, - bool closeOnSelect, - bool skip_agent, - const std::string& name, - LLView * frustumOrigin) -{ - // *TODO: Use a key to allow this not to be an effective singleton - LLFloaterAvatarPicker* floater = - LLFloaterReg::showTypedInstance("avatar_picker", LLSD(name)); - if (!floater) - { - LL_WARNS() << "Cannot instantiate avatar picker" << LL_ENDL; - return NULL; - } - - floater->mSelectionCallback = callback; - floater->setAllowMultiple(allow_multiple); - floater->mNearMeListComplete = false; - floater->mCloseOnSelect = closeOnSelect; - floater->mExcludeAgentFromSearchResults = skip_agent; - - if (!closeOnSelect) - { - // Use Select/Close - std::string select_string = floater->getString("Select"); - std::string close_string = floater->getString("Close"); - floater->getChild("ok_btn")->setLabel(select_string); - floater->getChild("cancel_btn")->setLabel(close_string); - } - - if(frustumOrigin) - { - floater->mFrustumOrigin = frustumOrigin->getHandle(); - } - - return floater; -} - -// Default constructor -LLFloaterAvatarPicker::LLFloaterAvatarPicker(const LLSD& key) - : LLFloater(key), - mNumResultsReturned(0), - mNearMeListComplete(false), - mCloseOnSelect(false), - mExcludeAgentFromSearchResults(false), - mContextConeOpacity (0.f), - mContextConeInAlpha(CONTEXT_CONE_IN_ALPHA), - mContextConeOutAlpha(CONTEXT_CONE_OUT_ALPHA), - mContextConeFadeTime(CONTEXT_CONE_FADE_TIME) -{ - mCommitCallbackRegistrar.add("Refresh.FriendList", boost::bind(&LLFloaterAvatarPicker::populateFriend, this)); -} - -bool LLFloaterAvatarPicker::postBuild() -{ - getChild("Edit")->setKeystrokeCallback( boost::bind(&LLFloaterAvatarPicker::editKeystroke, this, _1, _2),NULL); - - childSetAction("Find", boost::bind(&LLFloaterAvatarPicker::onBtnFind, this)); - getChildView("Find")->setEnabled(false); - childSetAction("Refresh", boost::bind(&LLFloaterAvatarPicker::onBtnRefresh, this)); - getChild("near_me_range")->setCommitCallback(boost::bind(&LLFloaterAvatarPicker::onRangeAdjust, this)); - - LLScrollListCtrl* searchresults = getChild("SearchResults"); - searchresults->setDoubleClickCallback( boost::bind(&LLFloaterAvatarPicker::onBtnSelect, this)); - searchresults->setCommitCallback(boost::bind(&LLFloaterAvatarPicker::onList, this)); - getChildView("SearchResults")->setEnabled(false); - - LLScrollListCtrl* nearme = getChild("NearMe"); - nearme->setDoubleClickCallback(boost::bind(&LLFloaterAvatarPicker::onBtnSelect, this)); - nearme->setCommitCallback(boost::bind(&LLFloaterAvatarPicker::onList, this)); - - LLScrollListCtrl* friends = getChild("Friends"); - friends->setDoubleClickCallback(boost::bind(&LLFloaterAvatarPicker::onBtnSelect, this)); - getChild("Friends")->setCommitCallback(boost::bind(&LLFloaterAvatarPicker::onList, this)); - - childSetAction("ok_btn", boost::bind(&LLFloaterAvatarPicker::onBtnSelect, this)); - getChildView("ok_btn")->setEnabled(false); - childSetAction("cancel_btn", boost::bind(&LLFloaterAvatarPicker::onBtnClose, this)); - - getChild("Edit")->setFocus(true); - - LLPanel* search_panel = getChild("SearchPanel"); - if (search_panel) - { - // Start searching when Return is pressed in the line editor. - search_panel->setDefaultBtn("Find"); - } - - getChild("SearchResults")->setCommentText(getString("no_results")); - - getChild("ResidentChooserTabs")->setCommitCallback( - boost::bind(&LLFloaterAvatarPicker::onTabChanged, this)); - - setAllowMultiple(false); - - center(); - - populateFriend(); - - return true; -} - -void LLFloaterAvatarPicker::setOkBtnEnableCb(validate_callback_t cb) -{ - mOkButtonValidateSignal.connect(cb); -} - -void LLFloaterAvatarPicker::onTabChanged() -{ - getChildView("ok_btn")->setEnabled(isSelectBtnEnabled()); -} - -// Destroys the object -LLFloaterAvatarPicker::~LLFloaterAvatarPicker() -{ - gFocusMgr.releaseFocusIfNeeded( this ); -} - -void LLFloaterAvatarPicker::onBtnFind() -{ - find(); -} - -static void getSelectedAvatarData(const LLScrollListCtrl* from, uuid_vec_t& avatar_ids, std::vector& avatar_names) -{ - std::vector items = from->getAllSelected(); - for (std::vector::iterator iter = items.begin(); iter != items.end(); ++iter) - { - LLScrollListItem* item = *iter; - if (item->getUUID().notNull()) - { - avatar_ids.push_back(item->getUUID()); - - std::map::iterator iter = sAvatarNameMap.find(item->getUUID()); - if (iter != sAvatarNameMap.end()) - { - avatar_names.push_back(iter->second); - } - else - { - // the only case where it isn't in the name map is friends - // but it should be in the name cache - LLAvatarName av_name; - LLAvatarNameCache::get(item->getUUID(), &av_name); - avatar_names.push_back(av_name); - } - } - } -} - -void LLFloaterAvatarPicker::onBtnSelect() -{ - - // If select btn not enabled then do not callback - if (!isSelectBtnEnabled()) - return; - - if(mSelectionCallback) - { - std::string acvtive_panel_name; - LLScrollListCtrl* list = NULL; - LLPanel* active_panel = getChild("ResidentChooserTabs")->getCurrentPanel(); - if(active_panel) - { - acvtive_panel_name = active_panel->getName(); - } - if(acvtive_panel_name == "SearchPanel") - { - list = getChild("SearchResults"); - } - else if(acvtive_panel_name == "NearMePanel") - { - list = getChild("NearMe"); - } - else if (acvtive_panel_name == "FriendsPanel") - { - list = getChild("Friends"); - } - - if(list) - { - uuid_vec_t avatar_ids; - std::vector avatar_names; - getSelectedAvatarData(list, avatar_ids, avatar_names); - mSelectionCallback(avatar_ids, avatar_names); - } - } - getChild("SearchResults")->deselectAllItems(true); - getChild("NearMe")->deselectAllItems(true); - getChild("Friends")->deselectAllItems(true); - if(mCloseOnSelect) - { - mCloseOnSelect = false; - closeFloater(); - } -} - -void LLFloaterAvatarPicker::onBtnRefresh() -{ - getChild("NearMe")->deleteAllItems(); - getChild("NearMe")->setCommentText(getString("searching")); - mNearMeListComplete = false; -} - -void LLFloaterAvatarPicker::onBtnClose() -{ - closeFloater(); -} - -void LLFloaterAvatarPicker::onRangeAdjust() -{ - onBtnRefresh(); -} - -void LLFloaterAvatarPicker::onList() -{ - getChildView("ok_btn")->setEnabled(isSelectBtnEnabled()); -} - -void LLFloaterAvatarPicker::populateNearMe() -{ - bool all_loaded = true; - bool empty = true; - LLScrollListCtrl* near_me_scroller = getChild("NearMe"); - near_me_scroller->deleteAllItems(); - - uuid_vec_t avatar_ids; - LLWorld::getInstance()->getAvatars(&avatar_ids, NULL, gAgent.getPositionGlobal(), gSavedSettings.getF32("NearMeRange")); - for(U32 i=0; iaddElement(element); - empty = false; - } - - if (empty) - { - getChildView("NearMe")->setEnabled(false); - getChildView("ok_btn")->setEnabled(false); - near_me_scroller->setCommentText(getString("no_one_near")); - } - else - { - getChildView("NearMe")->setEnabled(true); - getChildView("ok_btn")->setEnabled(true); - near_me_scroller->selectFirstItem(); - onList(); - near_me_scroller->setFocus(true); - } - - if (all_loaded) - { - mNearMeListComplete = true; - } -} - -void LLFloaterAvatarPicker::populateFriend() -{ - LLScrollListCtrl* friends_scroller = getChild("Friends"); - friends_scroller->deleteAllItems(); - LLCollectAllBuddies collector; - LLAvatarTracker::instance().applyFunctor(collector); - LLCollectAllBuddies::buddy_map_t::iterator it; - - for(it = collector.mOnline.begin(); it!=collector.mOnline.end(); it++) - { - friends_scroller->addStringUUIDItem(it->second, it->first); - } - for(it = collector.mOffline.begin(); it!=collector.mOffline.end(); it++) - { - friends_scroller->addStringUUIDItem(it->second, it->first); - } - friends_scroller->sortByColumnIndex(0, true); -} - -void LLFloaterAvatarPicker::drawFrustum() -{ - static LLCachedControl max_opacity(gSavedSettings, "PickerContextOpacity", 0.4f); - drawConeToOwner(mContextConeOpacity, max_opacity, mFrustumOrigin.get(), mContextConeFadeTime, mContextConeInAlpha, mContextConeOutAlpha); -} - -void LLFloaterAvatarPicker::draw() -{ - drawFrustum(); - - // sometimes it is hard to determine when Select/Ok button should be disabled (see LLAvatarActions::shareWithAvatars). - // lets check this via mOkButtonValidateSignal callback periodically. - static LLFrameTimer timer; - if (timer.hasExpired()) - { - timer.setTimerExpirySec(0.33f); // three times per second should be enough. - - // simulate list changes. - onList(); - timer.start(); - } - - LLFloater::draw(); - if (!mNearMeListComplete && getChild("ResidentChooserTabs")->getCurrentPanel() == getChild("NearMePanel")) - { - populateNearMe(); - } -} - -bool LLFloaterAvatarPicker::visibleItemsSelected() const -{ - LLPanel* active_panel = getChild("ResidentChooserTabs")->getCurrentPanel(); - - if(active_panel == getChild("SearchPanel")) - { - return getChild("SearchResults")->getFirstSelectedIndex() >= 0; - } - else if(active_panel == getChild("NearMePanel")) - { - return getChild("NearMe")->getFirstSelectedIndex() >= 0; - } - else if(active_panel == getChild("FriendsPanel")) - { - return getChild("Friends")->getFirstSelectedIndex() >= 0; - } - return false; -} - -/*static*/ -void LLFloaterAvatarPicker::findCoro(std::string url, LLUUID queryID, std::string name) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("genericPostCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); - - LL_INFOS("HttpCoroutineAdapter", "genericPostCoro") << "Generic POST for " << url << LL_ENDL; - - httpOpts->setTimeout(AVATAR_PICKER_SEARCH_TIMEOUT); - - LLSD result = httpAdapter->getAndSuspend(httpRequest, url, httpOpts); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (status || (status == LLCore::HttpStatus(HTTP_BAD_REQUEST))) - { - result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); - } - else - { - result["failure_reason"] = status.toString(); - } - - LLFloaterAvatarPicker* floater = - LLFloaterReg::findTypedInstance("avatar_picker", name); - if (floater) - { - floater->processResponse(queryID, result); - } -} - - -void LLFloaterAvatarPicker::find() -{ - //clear our stored LLAvatarNames - sAvatarNameMap.clear(); - - std::string text = getChild("Edit")->getValue().asString(); - - size_t separator_index = text.find_first_of(" ._"); - if (separator_index != text.npos) - { - std::string first = text.substr(0, separator_index); - std::string last = text.substr(separator_index+1, text.npos); - LLStringUtil::trim(last); - if("Resident" == last) - { - text = first; - } - } - - mQueryID.generate(); - - std::string url; - url.reserve(128); // avoid a memory allocation or two - - LLViewerRegion* region = gAgent.getRegion(); - if(region) - { - url = region->getCapability("AvatarPickerSearch"); - // Prefer use of capabilities to search on both SLID and display name - if (!url.empty()) - { - // capability urls don't end in '/', but we need one to parse - // query parameters correctly - if (url.size() > 0 && url[url.size()-1] != '/') - { - url += "/"; - } - url += "?page_size=100&names="; - std::replace(text.begin(), text.end(), '.', ' '); - url += LLURI::escape(text); - LL_INFOS() << "avatar picker " << url << LL_ENDL; - - LLCoros::instance().launch("LLFloaterAvatarPicker::findCoro", - boost::bind(&LLFloaterAvatarPicker::findCoro, url, mQueryID, getKey().asString())); - } - else - { - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("AvatarPickerRequest"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->addUUID("QueryID", mQueryID); // not used right now - msg->nextBlock("Data"); - msg->addString("Name", text); - gAgent.sendReliableMessage(); - } - } - getChild("SearchResults")->deleteAllItems(); - getChild("SearchResults")->setCommentText(getString("searching")); - - getChildView("ok_btn")->setEnabled(false); - mNumResultsReturned = 0; -} - -void LLFloaterAvatarPicker::setAllowMultiple(bool allow_multiple) -{ - getChild("SearchResults")->setAllowMultipleSelection(allow_multiple); - getChild("NearMe")->setAllowMultipleSelection(allow_multiple); - getChild("Friends")->setAllowMultipleSelection(allow_multiple); -} - -LLScrollListCtrl* LLFloaterAvatarPicker::getActiveList() -{ - std::string acvtive_panel_name; - LLScrollListCtrl* list = NULL; - LLPanel* active_panel = getChild("ResidentChooserTabs")->getCurrentPanel(); - if(active_panel) - { - acvtive_panel_name = active_panel->getName(); - } - if(acvtive_panel_name == "SearchPanel") - { - list = getChild("SearchResults"); - } - else if(acvtive_panel_name == "NearMePanel") - { - list = getChild("NearMe"); - } - else if (acvtive_panel_name == "FriendsPanel") - { - list = getChild("Friends"); - } - return list; -} - -bool LLFloaterAvatarPicker::handleDragAndDrop(S32 x, S32 y, MASK mask, - bool drop, EDragAndDropType cargo_type, - void *cargo_data, EAcceptance *accept, - std::string& tooltip_msg) -{ - LLScrollListCtrl* list = getActiveList(); - if(list) - { - LLRect rc_list; - LLRect rc_point(x,y,x,y); - if (localRectToOtherView(rc_point, &rc_list, list)) - { - // Keep selected only one item - list->deselectAllItems(true); - list->selectItemAt(rc_list.mLeft, rc_list.mBottom, mask); - LLScrollListItem* selection = list->getFirstSelected(); - if (selection) - { - LLUUID session_id = LLUUID::null; - LLUUID dest_agent_id = selection->getUUID(); - std::string avatar_name = selection->getColumn(0)->getValue().asString(); - if (dest_agent_id.notNull() && dest_agent_id != gAgentID) - { - if (drop) - { - // Start up IM before give the item - session_id = gIMMgr->addSession(avatar_name, IM_NOTHING_SPECIAL, dest_agent_id); - } - return LLToolDragAndDrop::handleGiveDragAndDrop(dest_agent_id, session_id, drop, - cargo_type, cargo_data, accept, getName()); - } - } - } - } - *accept = ACCEPT_NO; - return true; -} - - -void LLFloaterAvatarPicker::openFriendsTab() -{ - LLTabContainer* tab_container = getChild("ResidentChooserTabs"); - if (tab_container == NULL) - { - llassert(tab_container != NULL); - return; - } - - tab_container->selectTabByName("FriendsPanel"); -} - -// static -void LLFloaterAvatarPicker::processAvatarPickerReply(LLMessageSystem* msg, void**) -{ - LLUUID agent_id; - LLUUID query_id; - LLUUID avatar_id; - std::string first_name; - std::string last_name; - - msg->getUUID("AgentData", "AgentID", agent_id); - msg->getUUID("AgentData", "QueryID", query_id); - - // Not for us - if (agent_id != gAgent.getID()) return; - - LLFloaterAvatarPicker* floater = LLFloaterReg::findTypedInstance("avatar_picker"); - - // floater is closed or these are not results from our last request - if (NULL == floater || query_id != floater->mQueryID) - { - return; - } - - LLScrollListCtrl* search_results = floater->getChild("SearchResults"); - - // clear "Searching" label on first results - if (floater->mNumResultsReturned++ == 0) - { - search_results->deleteAllItems(); - } - - bool found_one = false; - S32 num_new_rows = msg->getNumberOfBlocks("Data"); - for (S32 i = 0; i < num_new_rows; i++) - { - msg->getUUIDFast( _PREHASH_Data,_PREHASH_AvatarID, avatar_id, i); - msg->getStringFast(_PREHASH_Data,_PREHASH_FirstName, first_name, i); - msg->getStringFast(_PREHASH_Data,_PREHASH_LastName, last_name, i); - - if (avatar_id != agent_id || !floater->isExcludeAgentFromSearchResults()) // exclude agent from search results? - { - std::string avatar_name; - if (avatar_id.isNull()) - { - LLStringUtil::format_map_t map; - map["[TEXT]"] = floater->getChild("Edit")->getValue().asString(); - avatar_name = floater->getString("not_found", map); - search_results->setEnabled(false); - floater->getChildView("ok_btn")->setEnabled(false); - } - else - { - avatar_name = LLCacheName::buildFullName(first_name, last_name); - search_results->setEnabled(true); - found_one = true; - - LLAvatarName av_name; - av_name.fromString(avatar_name); - const LLUUID& agent_id = avatar_id; - sAvatarNameMap[agent_id] = av_name; - - } - LLSD element; - element["id"] = avatar_id; // value - element["columns"][0]["column"] = "name"; - element["columns"][0]["value"] = avatar_name; - search_results->addElement(element); - } - } - - if (found_one) - { - floater->getChildView("ok_btn")->setEnabled(true); - search_results->selectFirstItem(); - floater->onList(); - search_results->setFocus(true); - } -} - -void LLFloaterAvatarPicker::processResponse(const LLUUID& query_id, const LLSD& content) -{ - // Check for out-of-date query - if (query_id == mQueryID) - { - LLScrollListCtrl* search_results = getChild("SearchResults"); - - // clear "Searching" label on first results - search_results->deleteAllItems(); - - if (content.has("failure_reason")) - { - getChild("SearchResults")->setCommentText(content["failure_reason"].asString()); - getChildView("ok_btn")->setEnabled(false); - } - else - { - LLSD agents = content["agents"]; - - LLSD item; - LLSD::array_const_iterator it = agents.beginArray(); - for (; it != agents.endArray(); ++it) - { - const LLSD& row = *it; - if (row["id"].asUUID() != gAgent.getID() || !mExcludeAgentFromSearchResults) - { - item["id"] = row["id"]; - LLSD& columns = item["columns"]; - columns[0]["column"] = "name"; - columns[0]["value"] = row["display_name"]; - columns[1]["column"] = "username"; - columns[1]["value"] = row["username"]; - search_results->addElement(item); - - // add the avatar name to our list - LLAvatarName avatar_name; - avatar_name.fromLLSD(row); - sAvatarNameMap[row["id"].asUUID()] = avatar_name; - } - } - - if (search_results->isEmpty()) - { - std::string name = "'" + getChild("Edit")->getValue().asString() + "'"; - LLSD item; - item["id"] = LLUUID::null; - item["columns"][0]["column"] = "name"; - item["columns"][0]["value"] = name; - item["columns"][1]["column"] = "username"; - item["columns"][1]["value"] = getString("not_found_text"); - search_results->addElement(item); - search_results->setEnabled(false); - getChildView("ok_btn")->setEnabled(false); - } - else - { - getChildView("ok_btn")->setEnabled(true); - search_results->setEnabled(true); - search_results->sortByColumnIndex(1, true); - std::string text = getChild("Edit")->getValue().asString(); - if (!search_results->selectItemByLabel(text, true, 1)) - { - search_results->selectFirstItem(); - } - onList(); - search_results->setFocus(true); - } - } - } -} - -void LLFloaterAvatarPicker::editKeystroke(LLLineEditor* caller, void* user_data) -{ - getChildView("Find")->setEnabled(caller->getText().size() > 0); -} - -// virtual -bool LLFloaterAvatarPicker::handleKeyHere(KEY key, MASK mask) -{ - if (key == KEY_RETURN && mask == MASK_NONE) - { - if (getChild("Edit")->hasFocus()) - { - onBtnFind(); - } - else - { - onBtnSelect(); - } - return true; - } - else if (key == KEY_ESCAPE && mask == MASK_NONE) - { - closeFloater(); - return true; - } - - return LLFloater::handleKeyHere(key, mask); -} - -bool LLFloaterAvatarPicker::isSelectBtnEnabled() -{ - bool ret_val = visibleItemsSelected(); - - if ( ret_val && !isMinimized()) - { - std::string acvtive_panel_name; - LLScrollListCtrl* list = NULL; - LLPanel* active_panel = getChild("ResidentChooserTabs")->getCurrentPanel(); - - if(active_panel) - { - acvtive_panel_name = active_panel->getName(); - } - - if(acvtive_panel_name == "SearchPanel") - { - list = getChild("SearchResults"); - } - else if(acvtive_panel_name == "NearMePanel") - { - list = getChild("NearMe"); - } - else if (acvtive_panel_name == "FriendsPanel") - { - list = getChild("Friends"); - } - - if(list) - { - uuid_vec_t avatar_ids; - std::vector avatar_names; - getSelectedAvatarData(list, avatar_ids, avatar_names); - if (avatar_ids.size() >= 1) - { - ret_val = mOkButtonValidateSignal.num_slots()?mOkButtonValidateSignal(avatar_ids):true; - } - else - { - ret_val = false; - } - } - } - - return ret_val; -} +/** + * @file llfloateravatarpicker.cpp + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloateravatarpicker.h" + +// Viewer includes +#include "llagent.h" +#include "llcallingcard.h" +#include "llfocusmgr.h" +#include "llfloaterreg.h" +#include "llimview.h" // for gIMMgr +#include "lltooldraganddrop.h" // for LLToolDragAndDrop +#include "llviewercontrol.h" +#include "llviewerregion.h" // getCapability() +#include "llworld.h" + +// Linden libraries +#include "llavatarnamecache.h" // IDEVO +#include "llbutton.h" +#include "llcachename.h" +#include "lllineeditor.h" +#include "llscrolllistctrl.h" +#include "llscrolllistitem.h" +#include "llscrolllistcell.h" +#include "lltabcontainer.h" +#include "lluictrlfactory.h" +#include "llfocusmgr.h" +#include "lldraghandle.h" +#include "message.h" +#include "llcorehttputil.h" + +//#include "llsdserialize.h" + +static const U32 AVATAR_PICKER_SEARCH_TIMEOUT = 180U; + +//put it back as a member once the legacy path is out? +static std::map sAvatarNameMap; + +LLFloaterAvatarPicker* LLFloaterAvatarPicker::show(select_callback_t callback, + bool allow_multiple, + bool closeOnSelect, + bool skip_agent, + const std::string& name, + LLView * frustumOrigin) +{ + // *TODO: Use a key to allow this not to be an effective singleton + LLFloaterAvatarPicker* floater = + LLFloaterReg::showTypedInstance("avatar_picker", LLSD(name)); + if (!floater) + { + LL_WARNS() << "Cannot instantiate avatar picker" << LL_ENDL; + return NULL; + } + + floater->mSelectionCallback = callback; + floater->setAllowMultiple(allow_multiple); + floater->mNearMeListComplete = false; + floater->mCloseOnSelect = closeOnSelect; + floater->mExcludeAgentFromSearchResults = skip_agent; + + if (!closeOnSelect) + { + // Use Select/Close + std::string select_string = floater->getString("Select"); + std::string close_string = floater->getString("Close"); + floater->getChild("ok_btn")->setLabel(select_string); + floater->getChild("cancel_btn")->setLabel(close_string); + } + + if(frustumOrigin) + { + floater->mFrustumOrigin = frustumOrigin->getHandle(); + } + + return floater; +} + +// Default constructor +LLFloaterAvatarPicker::LLFloaterAvatarPicker(const LLSD& key) + : LLFloater(key), + mNumResultsReturned(0), + mNearMeListComplete(false), + mCloseOnSelect(false), + mExcludeAgentFromSearchResults(false), + mContextConeOpacity (0.f), + mContextConeInAlpha(CONTEXT_CONE_IN_ALPHA), + mContextConeOutAlpha(CONTEXT_CONE_OUT_ALPHA), + mContextConeFadeTime(CONTEXT_CONE_FADE_TIME) +{ + mCommitCallbackRegistrar.add("Refresh.FriendList", boost::bind(&LLFloaterAvatarPicker::populateFriend, this)); +} + +bool LLFloaterAvatarPicker::postBuild() +{ + getChild("Edit")->setKeystrokeCallback( boost::bind(&LLFloaterAvatarPicker::editKeystroke, this, _1, _2),NULL); + + childSetAction("Find", boost::bind(&LLFloaterAvatarPicker::onBtnFind, this)); + getChildView("Find")->setEnabled(false); + childSetAction("Refresh", boost::bind(&LLFloaterAvatarPicker::onBtnRefresh, this)); + getChild("near_me_range")->setCommitCallback(boost::bind(&LLFloaterAvatarPicker::onRangeAdjust, this)); + + LLScrollListCtrl* searchresults = getChild("SearchResults"); + searchresults->setDoubleClickCallback( boost::bind(&LLFloaterAvatarPicker::onBtnSelect, this)); + searchresults->setCommitCallback(boost::bind(&LLFloaterAvatarPicker::onList, this)); + getChildView("SearchResults")->setEnabled(false); + + LLScrollListCtrl* nearme = getChild("NearMe"); + nearme->setDoubleClickCallback(boost::bind(&LLFloaterAvatarPicker::onBtnSelect, this)); + nearme->setCommitCallback(boost::bind(&LLFloaterAvatarPicker::onList, this)); + + LLScrollListCtrl* friends = getChild("Friends"); + friends->setDoubleClickCallback(boost::bind(&LLFloaterAvatarPicker::onBtnSelect, this)); + getChild("Friends")->setCommitCallback(boost::bind(&LLFloaterAvatarPicker::onList, this)); + + childSetAction("ok_btn", boost::bind(&LLFloaterAvatarPicker::onBtnSelect, this)); + getChildView("ok_btn")->setEnabled(false); + childSetAction("cancel_btn", boost::bind(&LLFloaterAvatarPicker::onBtnClose, this)); + + getChild("Edit")->setFocus(true); + + LLPanel* search_panel = getChild("SearchPanel"); + if (search_panel) + { + // Start searching when Return is pressed in the line editor. + search_panel->setDefaultBtn("Find"); + } + + getChild("SearchResults")->setCommentText(getString("no_results")); + + getChild("ResidentChooserTabs")->setCommitCallback( + boost::bind(&LLFloaterAvatarPicker::onTabChanged, this)); + + setAllowMultiple(false); + + center(); + + populateFriend(); + + return true; +} + +void LLFloaterAvatarPicker::setOkBtnEnableCb(validate_callback_t cb) +{ + mOkButtonValidateSignal.connect(cb); +} + +void LLFloaterAvatarPicker::onTabChanged() +{ + getChildView("ok_btn")->setEnabled(isSelectBtnEnabled()); +} + +// Destroys the object +LLFloaterAvatarPicker::~LLFloaterAvatarPicker() +{ + gFocusMgr.releaseFocusIfNeeded( this ); +} + +void LLFloaterAvatarPicker::onBtnFind() +{ + find(); +} + +static void getSelectedAvatarData(const LLScrollListCtrl* from, uuid_vec_t& avatar_ids, std::vector& avatar_names) +{ + std::vector items = from->getAllSelected(); + for (std::vector::iterator iter = items.begin(); iter != items.end(); ++iter) + { + LLScrollListItem* item = *iter; + if (item->getUUID().notNull()) + { + avatar_ids.push_back(item->getUUID()); + + std::map::iterator iter = sAvatarNameMap.find(item->getUUID()); + if (iter != sAvatarNameMap.end()) + { + avatar_names.push_back(iter->second); + } + else + { + // the only case where it isn't in the name map is friends + // but it should be in the name cache + LLAvatarName av_name; + LLAvatarNameCache::get(item->getUUID(), &av_name); + avatar_names.push_back(av_name); + } + } + } +} + +void LLFloaterAvatarPicker::onBtnSelect() +{ + + // If select btn not enabled then do not callback + if (!isSelectBtnEnabled()) + return; + + if(mSelectionCallback) + { + std::string acvtive_panel_name; + LLScrollListCtrl* list = NULL; + LLPanel* active_panel = getChild("ResidentChooserTabs")->getCurrentPanel(); + if(active_panel) + { + acvtive_panel_name = active_panel->getName(); + } + if(acvtive_panel_name == "SearchPanel") + { + list = getChild("SearchResults"); + } + else if(acvtive_panel_name == "NearMePanel") + { + list = getChild("NearMe"); + } + else if (acvtive_panel_name == "FriendsPanel") + { + list = getChild("Friends"); + } + + if(list) + { + uuid_vec_t avatar_ids; + std::vector avatar_names; + getSelectedAvatarData(list, avatar_ids, avatar_names); + mSelectionCallback(avatar_ids, avatar_names); + } + } + getChild("SearchResults")->deselectAllItems(true); + getChild("NearMe")->deselectAllItems(true); + getChild("Friends")->deselectAllItems(true); + if(mCloseOnSelect) + { + mCloseOnSelect = false; + closeFloater(); + } +} + +void LLFloaterAvatarPicker::onBtnRefresh() +{ + getChild("NearMe")->deleteAllItems(); + getChild("NearMe")->setCommentText(getString("searching")); + mNearMeListComplete = false; +} + +void LLFloaterAvatarPicker::onBtnClose() +{ + closeFloater(); +} + +void LLFloaterAvatarPicker::onRangeAdjust() +{ + onBtnRefresh(); +} + +void LLFloaterAvatarPicker::onList() +{ + getChildView("ok_btn")->setEnabled(isSelectBtnEnabled()); +} + +void LLFloaterAvatarPicker::populateNearMe() +{ + bool all_loaded = true; + bool empty = true; + LLScrollListCtrl* near_me_scroller = getChild("NearMe"); + near_me_scroller->deleteAllItems(); + + uuid_vec_t avatar_ids; + LLWorld::getInstance()->getAvatars(&avatar_ids, NULL, gAgent.getPositionGlobal(), gSavedSettings.getF32("NearMeRange")); + for(U32 i=0; iaddElement(element); + empty = false; + } + + if (empty) + { + getChildView("NearMe")->setEnabled(false); + getChildView("ok_btn")->setEnabled(false); + near_me_scroller->setCommentText(getString("no_one_near")); + } + else + { + getChildView("NearMe")->setEnabled(true); + getChildView("ok_btn")->setEnabled(true); + near_me_scroller->selectFirstItem(); + onList(); + near_me_scroller->setFocus(true); + } + + if (all_loaded) + { + mNearMeListComplete = true; + } +} + +void LLFloaterAvatarPicker::populateFriend() +{ + LLScrollListCtrl* friends_scroller = getChild("Friends"); + friends_scroller->deleteAllItems(); + LLCollectAllBuddies collector; + LLAvatarTracker::instance().applyFunctor(collector); + LLCollectAllBuddies::buddy_map_t::iterator it; + + for(it = collector.mOnline.begin(); it!=collector.mOnline.end(); it++) + { + friends_scroller->addStringUUIDItem(it->second, it->first); + } + for(it = collector.mOffline.begin(); it!=collector.mOffline.end(); it++) + { + friends_scroller->addStringUUIDItem(it->second, it->first); + } + friends_scroller->sortByColumnIndex(0, true); +} + +void LLFloaterAvatarPicker::drawFrustum() +{ + static LLCachedControl max_opacity(gSavedSettings, "PickerContextOpacity", 0.4f); + drawConeToOwner(mContextConeOpacity, max_opacity, mFrustumOrigin.get(), mContextConeFadeTime, mContextConeInAlpha, mContextConeOutAlpha); +} + +void LLFloaterAvatarPicker::draw() +{ + drawFrustum(); + + // sometimes it is hard to determine when Select/Ok button should be disabled (see LLAvatarActions::shareWithAvatars). + // lets check this via mOkButtonValidateSignal callback periodically. + static LLFrameTimer timer; + if (timer.hasExpired()) + { + timer.setTimerExpirySec(0.33f); // three times per second should be enough. + + // simulate list changes. + onList(); + timer.start(); + } + + LLFloater::draw(); + if (!mNearMeListComplete && getChild("ResidentChooserTabs")->getCurrentPanel() == getChild("NearMePanel")) + { + populateNearMe(); + } +} + +bool LLFloaterAvatarPicker::visibleItemsSelected() const +{ + LLPanel* active_panel = getChild("ResidentChooserTabs")->getCurrentPanel(); + + if(active_panel == getChild("SearchPanel")) + { + return getChild("SearchResults")->getFirstSelectedIndex() >= 0; + } + else if(active_panel == getChild("NearMePanel")) + { + return getChild("NearMe")->getFirstSelectedIndex() >= 0; + } + else if(active_panel == getChild("FriendsPanel")) + { + return getChild("Friends")->getFirstSelectedIndex() >= 0; + } + return false; +} + +/*static*/ +void LLFloaterAvatarPicker::findCoro(std::string url, LLUUID queryID, std::string name) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("genericPostCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + + LL_INFOS("HttpCoroutineAdapter", "genericPostCoro") << "Generic POST for " << url << LL_ENDL; + + httpOpts->setTimeout(AVATAR_PICKER_SEARCH_TIMEOUT); + + LLSD result = httpAdapter->getAndSuspend(httpRequest, url, httpOpts); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (status || (status == LLCore::HttpStatus(HTTP_BAD_REQUEST))) + { + result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); + } + else + { + result["failure_reason"] = status.toString(); + } + + LLFloaterAvatarPicker* floater = + LLFloaterReg::findTypedInstance("avatar_picker", name); + if (floater) + { + floater->processResponse(queryID, result); + } +} + + +void LLFloaterAvatarPicker::find() +{ + //clear our stored LLAvatarNames + sAvatarNameMap.clear(); + + std::string text = getChild("Edit")->getValue().asString(); + + size_t separator_index = text.find_first_of(" ._"); + if (separator_index != text.npos) + { + std::string first = text.substr(0, separator_index); + std::string last = text.substr(separator_index+1, text.npos); + LLStringUtil::trim(last); + if("Resident" == last) + { + text = first; + } + } + + mQueryID.generate(); + + std::string url; + url.reserve(128); // avoid a memory allocation or two + + LLViewerRegion* region = gAgent.getRegion(); + if(region) + { + url = region->getCapability("AvatarPickerSearch"); + // Prefer use of capabilities to search on both SLID and display name + if (!url.empty()) + { + // capability urls don't end in '/', but we need one to parse + // query parameters correctly + if (url.size() > 0 && url[url.size()-1] != '/') + { + url += "/"; + } + url += "?page_size=100&names="; + std::replace(text.begin(), text.end(), '.', ' '); + url += LLURI::escape(text); + LL_INFOS() << "avatar picker " << url << LL_ENDL; + + LLCoros::instance().launch("LLFloaterAvatarPicker::findCoro", + boost::bind(&LLFloaterAvatarPicker::findCoro, url, mQueryID, getKey().asString())); + } + else + { + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("AvatarPickerRequest"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->addUUID("QueryID", mQueryID); // not used right now + msg->nextBlock("Data"); + msg->addString("Name", text); + gAgent.sendReliableMessage(); + } + } + getChild("SearchResults")->deleteAllItems(); + getChild("SearchResults")->setCommentText(getString("searching")); + + getChildView("ok_btn")->setEnabled(false); + mNumResultsReturned = 0; +} + +void LLFloaterAvatarPicker::setAllowMultiple(bool allow_multiple) +{ + getChild("SearchResults")->setAllowMultipleSelection(allow_multiple); + getChild("NearMe")->setAllowMultipleSelection(allow_multiple); + getChild("Friends")->setAllowMultipleSelection(allow_multiple); +} + +LLScrollListCtrl* LLFloaterAvatarPicker::getActiveList() +{ + std::string acvtive_panel_name; + LLScrollListCtrl* list = NULL; + LLPanel* active_panel = getChild("ResidentChooserTabs")->getCurrentPanel(); + if(active_panel) + { + acvtive_panel_name = active_panel->getName(); + } + if(acvtive_panel_name == "SearchPanel") + { + list = getChild("SearchResults"); + } + else if(acvtive_panel_name == "NearMePanel") + { + list = getChild("NearMe"); + } + else if (acvtive_panel_name == "FriendsPanel") + { + list = getChild("Friends"); + } + return list; +} + +bool LLFloaterAvatarPicker::handleDragAndDrop(S32 x, S32 y, MASK mask, + bool drop, EDragAndDropType cargo_type, + void *cargo_data, EAcceptance *accept, + std::string& tooltip_msg) +{ + LLScrollListCtrl* list = getActiveList(); + if(list) + { + LLRect rc_list; + LLRect rc_point(x,y,x,y); + if (localRectToOtherView(rc_point, &rc_list, list)) + { + // Keep selected only one item + list->deselectAllItems(true); + list->selectItemAt(rc_list.mLeft, rc_list.mBottom, mask); + LLScrollListItem* selection = list->getFirstSelected(); + if (selection) + { + LLUUID session_id = LLUUID::null; + LLUUID dest_agent_id = selection->getUUID(); + std::string avatar_name = selection->getColumn(0)->getValue().asString(); + if (dest_agent_id.notNull() && dest_agent_id != gAgentID) + { + if (drop) + { + // Start up IM before give the item + session_id = gIMMgr->addSession(avatar_name, IM_NOTHING_SPECIAL, dest_agent_id); + } + return LLToolDragAndDrop::handleGiveDragAndDrop(dest_agent_id, session_id, drop, + cargo_type, cargo_data, accept, getName()); + } + } + } + } + *accept = ACCEPT_NO; + return true; +} + + +void LLFloaterAvatarPicker::openFriendsTab() +{ + LLTabContainer* tab_container = getChild("ResidentChooserTabs"); + if (tab_container == NULL) + { + llassert(tab_container != NULL); + return; + } + + tab_container->selectTabByName("FriendsPanel"); +} + +// static +void LLFloaterAvatarPicker::processAvatarPickerReply(LLMessageSystem* msg, void**) +{ + LLUUID agent_id; + LLUUID query_id; + LLUUID avatar_id; + std::string first_name; + std::string last_name; + + msg->getUUID("AgentData", "AgentID", agent_id); + msg->getUUID("AgentData", "QueryID", query_id); + + // Not for us + if (agent_id != gAgent.getID()) return; + + LLFloaterAvatarPicker* floater = LLFloaterReg::findTypedInstance("avatar_picker"); + + // floater is closed or these are not results from our last request + if (NULL == floater || query_id != floater->mQueryID) + { + return; + } + + LLScrollListCtrl* search_results = floater->getChild("SearchResults"); + + // clear "Searching" label on first results + if (floater->mNumResultsReturned++ == 0) + { + search_results->deleteAllItems(); + } + + bool found_one = false; + S32 num_new_rows = msg->getNumberOfBlocks("Data"); + for (S32 i = 0; i < num_new_rows; i++) + { + msg->getUUIDFast( _PREHASH_Data,_PREHASH_AvatarID, avatar_id, i); + msg->getStringFast(_PREHASH_Data,_PREHASH_FirstName, first_name, i); + msg->getStringFast(_PREHASH_Data,_PREHASH_LastName, last_name, i); + + if (avatar_id != agent_id || !floater->isExcludeAgentFromSearchResults()) // exclude agent from search results? + { + std::string avatar_name; + if (avatar_id.isNull()) + { + LLStringUtil::format_map_t map; + map["[TEXT]"] = floater->getChild("Edit")->getValue().asString(); + avatar_name = floater->getString("not_found", map); + search_results->setEnabled(false); + floater->getChildView("ok_btn")->setEnabled(false); + } + else + { + avatar_name = LLCacheName::buildFullName(first_name, last_name); + search_results->setEnabled(true); + found_one = true; + + LLAvatarName av_name; + av_name.fromString(avatar_name); + const LLUUID& agent_id = avatar_id; + sAvatarNameMap[agent_id] = av_name; + + } + LLSD element; + element["id"] = avatar_id; // value + element["columns"][0]["column"] = "name"; + element["columns"][0]["value"] = avatar_name; + search_results->addElement(element); + } + } + + if (found_one) + { + floater->getChildView("ok_btn")->setEnabled(true); + search_results->selectFirstItem(); + floater->onList(); + search_results->setFocus(true); + } +} + +void LLFloaterAvatarPicker::processResponse(const LLUUID& query_id, const LLSD& content) +{ + // Check for out-of-date query + if (query_id == mQueryID) + { + LLScrollListCtrl* search_results = getChild("SearchResults"); + + // clear "Searching" label on first results + search_results->deleteAllItems(); + + if (content.has("failure_reason")) + { + getChild("SearchResults")->setCommentText(content["failure_reason"].asString()); + getChildView("ok_btn")->setEnabled(false); + } + else + { + LLSD agents = content["agents"]; + + LLSD item; + LLSD::array_const_iterator it = agents.beginArray(); + for (; it != agents.endArray(); ++it) + { + const LLSD& row = *it; + if (row["id"].asUUID() != gAgent.getID() || !mExcludeAgentFromSearchResults) + { + item["id"] = row["id"]; + LLSD& columns = item["columns"]; + columns[0]["column"] = "name"; + columns[0]["value"] = row["display_name"]; + columns[1]["column"] = "username"; + columns[1]["value"] = row["username"]; + search_results->addElement(item); + + // add the avatar name to our list + LLAvatarName avatar_name; + avatar_name.fromLLSD(row); + sAvatarNameMap[row["id"].asUUID()] = avatar_name; + } + } + + if (search_results->isEmpty()) + { + std::string name = "'" + getChild("Edit")->getValue().asString() + "'"; + LLSD item; + item["id"] = LLUUID::null; + item["columns"][0]["column"] = "name"; + item["columns"][0]["value"] = name; + item["columns"][1]["column"] = "username"; + item["columns"][1]["value"] = getString("not_found_text"); + search_results->addElement(item); + search_results->setEnabled(false); + getChildView("ok_btn")->setEnabled(false); + } + else + { + getChildView("ok_btn")->setEnabled(true); + search_results->setEnabled(true); + search_results->sortByColumnIndex(1, true); + std::string text = getChild("Edit")->getValue().asString(); + if (!search_results->selectItemByLabel(text, true, 1)) + { + search_results->selectFirstItem(); + } + onList(); + search_results->setFocus(true); + } + } + } +} + +void LLFloaterAvatarPicker::editKeystroke(LLLineEditor* caller, void* user_data) +{ + getChildView("Find")->setEnabled(caller->getText().size() > 0); +} + +// virtual +bool LLFloaterAvatarPicker::handleKeyHere(KEY key, MASK mask) +{ + if (key == KEY_RETURN && mask == MASK_NONE) + { + if (getChild("Edit")->hasFocus()) + { + onBtnFind(); + } + else + { + onBtnSelect(); + } + return true; + } + else if (key == KEY_ESCAPE && mask == MASK_NONE) + { + closeFloater(); + return true; + } + + return LLFloater::handleKeyHere(key, mask); +} + +bool LLFloaterAvatarPicker::isSelectBtnEnabled() +{ + bool ret_val = visibleItemsSelected(); + + if ( ret_val && !isMinimized()) + { + std::string acvtive_panel_name; + LLScrollListCtrl* list = NULL; + LLPanel* active_panel = getChild("ResidentChooserTabs")->getCurrentPanel(); + + if(active_panel) + { + acvtive_panel_name = active_panel->getName(); + } + + if(acvtive_panel_name == "SearchPanel") + { + list = getChild("SearchResults"); + } + else if(acvtive_panel_name == "NearMePanel") + { + list = getChild("NearMe"); + } + else if (acvtive_panel_name == "FriendsPanel") + { + list = getChild("Friends"); + } + + if(list) + { + uuid_vec_t avatar_ids; + std::vector avatar_names; + getSelectedAvatarData(list, avatar_ids, avatar_names); + if (avatar_ids.size() >= 1) + { + ret_val = mOkButtonValidateSignal.num_slots()?mOkButtonValidateSignal(avatar_ids):true; + } + else + { + ret_val = false; + } + } + } + + return ret_val; +} diff --git a/indra/newview/llfloateravatarpicker.h b/indra/newview/llfloateravatarpicker.h index 7d72076ae2..330f1a1226 100644 --- a/indra/newview/llfloateravatarpicker.h +++ b/indra/newview/llfloateravatarpicker.h @@ -1,113 +1,113 @@ -/** - * @file llfloateravatarpicker.h - * @brief was llavatarpicker.h - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLFLOATERAVATARPICKER_H -#define LLFLOATERAVATARPICKER_H - -#include "llfloater.h" -#include "lleventcoro.h" -#include "llcoros.h" - -#include - -class LLAvatarName; -class LLScrollListCtrl; - -class LLFloaterAvatarPicker :public LLFloater -{ -public: - typedef boost::signals2::signal validate_signal_t; - typedef validate_signal_t::slot_type validate_callback_t; - - // The callback function will be called with an avatar name and UUID. - typedef boost::function&)> select_callback_t; - // Call this to select an avatar. - static LLFloaterAvatarPicker* show(select_callback_t callback, - bool allow_multiple = false, - bool closeOnSelect = false, - bool skip_agent = false, - const std::string& name = "", - LLView * frustumOrigin = NULL); - - LLFloaterAvatarPicker(const LLSD& key); - virtual ~LLFloaterAvatarPicker(); - - virtual bool postBuild(); - - void setOkBtnEnableCb(validate_callback_t cb); - - static void processAvatarPickerReply(class LLMessageSystem* msg, void**); - void processResponse(const LLUUID& query_id, const LLSD& content); - - bool handleDragAndDrop(S32 x, S32 y, MASK mask, - bool drop, EDragAndDropType cargo_type, - void *cargo_data, EAcceptance *accept, - std::string& tooltip_msg); - - void openFriendsTab(); - bool isExcludeAgentFromSearchResults() {return mExcludeAgentFromSearchResults;} - -private: - void editKeystroke(class LLLineEditor* caller, void* user_data); - - void onBtnFind(); - void onBtnSelect(); - void onBtnRefresh(); - void onRangeAdjust(); - void onBtnClose(); - void onList(); - void onTabChanged(); - bool isSelectBtnEnabled(); - - void populateNearMe(); - void populateFriend(); - bool visibleItemsSelected() const; // Returns true if any items in the current tab are selected. - - static void findCoro(std::string url, LLUUID mQueryID, std::string mName); - void find(); - void setAllowMultiple(bool allow_multiple); - LLScrollListCtrl* getActiveList(); - - void drawFrustum(); - virtual void draw(); - virtual bool handleKeyHere(KEY key, MASK mask); - - LLUUID mQueryID; - int mNumResultsReturned; - bool mNearMeListComplete; - bool mCloseOnSelect; - bool mExcludeAgentFromSearchResults; - LLHandle mFrustumOrigin; - F32 mContextConeOpacity; - F32 mContextConeInAlpha; - F32 mContextConeOutAlpha; - F32 mContextConeFadeTime; - - validate_signal_t mOkButtonValidateSignal; - select_callback_t mSelectionCallback; -}; - -#endif +/** + * @file llfloateravatarpicker.h + * @brief was llavatarpicker.h + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLFLOATERAVATARPICKER_H +#define LLFLOATERAVATARPICKER_H + +#include "llfloater.h" +#include "lleventcoro.h" +#include "llcoros.h" + +#include + +class LLAvatarName; +class LLScrollListCtrl; + +class LLFloaterAvatarPicker :public LLFloater +{ +public: + typedef boost::signals2::signal validate_signal_t; + typedef validate_signal_t::slot_type validate_callback_t; + + // The callback function will be called with an avatar name and UUID. + typedef boost::function&)> select_callback_t; + // Call this to select an avatar. + static LLFloaterAvatarPicker* show(select_callback_t callback, + bool allow_multiple = false, + bool closeOnSelect = false, + bool skip_agent = false, + const std::string& name = "", + LLView * frustumOrigin = NULL); + + LLFloaterAvatarPicker(const LLSD& key); + virtual ~LLFloaterAvatarPicker(); + + virtual bool postBuild(); + + void setOkBtnEnableCb(validate_callback_t cb); + + static void processAvatarPickerReply(class LLMessageSystem* msg, void**); + void processResponse(const LLUUID& query_id, const LLSD& content); + + bool handleDragAndDrop(S32 x, S32 y, MASK mask, + bool drop, EDragAndDropType cargo_type, + void *cargo_data, EAcceptance *accept, + std::string& tooltip_msg); + + void openFriendsTab(); + bool isExcludeAgentFromSearchResults() {return mExcludeAgentFromSearchResults;} + +private: + void editKeystroke(class LLLineEditor* caller, void* user_data); + + void onBtnFind(); + void onBtnSelect(); + void onBtnRefresh(); + void onRangeAdjust(); + void onBtnClose(); + void onList(); + void onTabChanged(); + bool isSelectBtnEnabled(); + + void populateNearMe(); + void populateFriend(); + bool visibleItemsSelected() const; // Returns true if any items in the current tab are selected. + + static void findCoro(std::string url, LLUUID mQueryID, std::string mName); + void find(); + void setAllowMultiple(bool allow_multiple); + LLScrollListCtrl* getActiveList(); + + void drawFrustum(); + virtual void draw(); + virtual bool handleKeyHere(KEY key, MASK mask); + + LLUUID mQueryID; + int mNumResultsReturned; + bool mNearMeListComplete; + bool mCloseOnSelect; + bool mExcludeAgentFromSearchResults; + LLHandle mFrustumOrigin; + F32 mContextConeOpacity; + F32 mContextConeInAlpha; + F32 mContextConeOutAlpha; + F32 mContextConeFadeTime; + + validate_signal_t mOkButtonValidateSignal; + select_callback_t mSelectionCallback; +}; + +#endif diff --git a/indra/newview/llfloateravatarrendersettings.cpp b/indra/newview/llfloateravatarrendersettings.cpp index 4b3a0abc89..9101e6eb29 100644 --- a/indra/newview/llfloateravatarrendersettings.cpp +++ b/indra/newview/llfloateravatarrendersettings.cpp @@ -1,286 +1,286 @@ -/** - * @file llfloateravatarrendersettings.cpp - * @brief Shows the list of avatars with non-default rendering settings - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2017, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ -#include "llviewerprecompiledheaders.h" - -#include "llfloateravatarrendersettings.h" - -#include "llagent.h" -#include "llavatarnamecache.h" -#include "llfloateravatarpicker.h" -#include "llfiltereditor.h" -#include "llfloaterreg.h" -#include "llnamelistctrl.h" -#include "llnotificationsutil.h" -#include "llmenugl.h" -#include "lltrans.h" -#include "llviewerobjectlist.h" -#include "llvoavatar.h" - -class LLSettingsContextMenu : public LLListContextMenu - -{ -public: - LLSettingsContextMenu(LLFloaterAvatarRenderSettings* floater_settings) - : mFloaterSettings(floater_settings) - {} -protected: - LLContextMenu* createMenu() - { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; - registrar.add("Settings.SetRendering", boost::bind(&LLFloaterAvatarRenderSettings::onCustomAction, mFloaterSettings, _2, mUUIDs.front())); - enable_registrar.add("Settings.IsSelected", boost::bind(&LLFloaterAvatarRenderSettings::isActionChecked, mFloaterSettings, _2, mUUIDs.front())); - LLContextMenu* menu = createFromFile("menu_avatar_rendering_settings.xml"); - - return menu; - } - - LLFloaterAvatarRenderSettings* mFloaterSettings; -}; - -class LLAvatarRenderMuteListObserver : public LLMuteListObserver -{ - /* virtual */ void onChange() { LLFloaterAvatarRenderSettings::setNeedsUpdate();} -}; - -static LLAvatarRenderMuteListObserver sAvatarRenderMuteListObserver; - -LLFloaterAvatarRenderSettings::LLFloaterAvatarRenderSettings(const LLSD& key) -: LLFloater(key), - mAvatarSettingsList(NULL), - mNeedsUpdate(false) -{ - mContextMenu = new LLSettingsContextMenu(this); - LLRenderMuteList::getInstance()->addObserver(&sAvatarRenderMuteListObserver); - mCommitCallbackRegistrar.add("Settings.AddNewEntry", boost::bind(&LLFloaterAvatarRenderSettings::onClickAdd, this, _2)); -} - -LLFloaterAvatarRenderSettings::~LLFloaterAvatarRenderSettings() -{ - delete mContextMenu; - LLRenderMuteList::getInstance()->removeObserver(&sAvatarRenderMuteListObserver); -} - -bool LLFloaterAvatarRenderSettings::postBuild() -{ - LLFloater::postBuild(); - mAvatarSettingsList = getChild("render_settings_list"); - mAvatarSettingsList->setRightMouseDownCallback(boost::bind(&LLFloaterAvatarRenderSettings::onAvatarListRightClick, this, _1, _2, _3)); - - return true; -} - -void LLFloaterAvatarRenderSettings::draw() -{ - if(mNeedsUpdate) - { - updateList(); - mNeedsUpdate = false; - } - - LLFloater::draw(); -} - -void LLFloaterAvatarRenderSettings::onAvatarListRightClick(LLUICtrl* ctrl, S32 x, S32 y) -{ - LLNameListCtrl* list = dynamic_cast(ctrl); - if (!list) return; - list->selectItemAt(x, y, MASK_NONE); - uuid_vec_t selected_uuids; - - if(list->getCurrentID().notNull()) - { - selected_uuids.push_back(list->getCurrentID()); - mContextMenu->show(ctrl, selected_uuids, x, y); - } -} - -void LLFloaterAvatarRenderSettings::onOpen(const LLSD& key) -{ - updateList(); -} - -void LLFloaterAvatarRenderSettings::updateList() -{ - mAvatarSettingsList->deleteAllItems(); - LLAvatarName av_name; - LLNameListCtrl::NameItem item_params; - for (std::map::iterator iter = LLRenderMuteList::getInstance()->sVisuallyMuteSettingsMap.begin(); iter != LLRenderMuteList::getInstance()->sVisuallyMuteSettingsMap.end(); iter++) - { - item_params.value = iter->first; - LLAvatarNameCache::get(iter->first, &av_name); - item_params.columns.add().value(av_name.getCompleteName()).column("name"); - std::string setting = getString(iter->second == 1 ? "av_never_render" : "av_always_render"); - item_params.columns.add().value(setting).column("setting"); - mAvatarSettingsList->addNameItemRow(item_params); - } -} - -static LLVOAvatar* find_avatar(const LLUUID& id) -{ - LLViewerObject *obj = gObjectList.findObject(id); - while (obj && obj->isAttachment()) - { - obj = (LLViewerObject *)obj->getParent(); - } - - if (obj && obj->isAvatar()) - { - return (LLVOAvatar*)obj; - } - else - { - return NULL; - } -} - - -void LLFloaterAvatarRenderSettings::onCustomAction (const LLSD& userdata, const LLUUID& av_id) -{ - const std::string command_name = userdata.asString(); - - S32 new_setting = 0; - if ("default" == command_name) - { - new_setting = S32(LLVOAvatar::AV_RENDER_NORMALLY); - } - else if ("never" == command_name) - { - new_setting = S32(LLVOAvatar::AV_DO_NOT_RENDER); - } - else if ("always" == command_name) - { - new_setting = S32(LLVOAvatar::AV_ALWAYS_RENDER); - } - - setAvatarRenderSetting(av_id, new_setting); -} - - -bool LLFloaterAvatarRenderSettings::isActionChecked(const LLSD& userdata, const LLUUID& av_id) -{ - const std::string command_name = userdata.asString(); - - S32 visual_setting = LLRenderMuteList::getInstance()->getSavedVisualMuteSetting(av_id); - if ("default" == command_name) - { - return (visual_setting == S32(LLVOAvatar::AV_RENDER_NORMALLY)); - } - else if ("non_default" == command_name) - { - return (visual_setting != S32(LLVOAvatar::AV_RENDER_NORMALLY)); - } - else if ("never" == command_name) - { - return (visual_setting == S32(LLVOAvatar::AV_DO_NOT_RENDER)); - } - else if ("always" == command_name) - { - return (visual_setting == S32(LLVOAvatar::AV_ALWAYS_RENDER)); - } - return false; -} - -void LLFloaterAvatarRenderSettings::setNeedsUpdate() -{ - LLFloaterAvatarRenderSettings* instance = LLFloaterReg::getTypedInstance("avatar_render_settings"); - if(!instance) return; - instance->mNeedsUpdate = true; -} - -void LLFloaterAvatarRenderSettings::onClickAdd(const LLSD& userdata) -{ - const std::string command_name = userdata.asString(); - S32 visual_setting = 0; - if ("never" == command_name) - { - visual_setting = S32(LLVOAvatar::AV_DO_NOT_RENDER); - } - else if ("always" == command_name) - { - visual_setting = S32(LLVOAvatar::AV_ALWAYS_RENDER); - } - - LLView * button = findChild("plus_btn", true); - LLFloater* root_floater = gFloaterView->getParentFloater(this); - LLFloaterAvatarPicker * picker = LLFloaterAvatarPicker::show(boost::bind(&LLFloaterAvatarRenderSettings::callbackAvatarPicked, this, _1, visual_setting), - false, true, false, root_floater->getName(), button); - - if (root_floater) - { - root_floater->addDependentFloater(picker); - } -} - -void LLFloaterAvatarRenderSettings::callbackAvatarPicked(const uuid_vec_t& ids, S32 visual_setting) -{ - if (ids.empty()) return; - if(ids[0] == gAgentID) - { - LLNotificationsUtil::add("AddSelfRenderExceptions"); - return; - } - setAvatarRenderSetting(ids[0], visual_setting); -} - -void LLFloaterAvatarRenderSettings::setAvatarRenderSetting(const LLUUID& av_id, S32 new_setting) -{ - LLVOAvatar *avatarp = find_avatar(av_id); - if (avatarp) - { - avatarp->setVisualMuteSettings(LLVOAvatar::VisualMuteSettings(new_setting)); - } - else - { - LLRenderMuteList::getInstance()->saveVisualMuteSetting(av_id, new_setting); - } -} - -bool LLFloaterAvatarRenderSettings::handleKeyHere(KEY key, MASK mask ) -{ - bool handled = false; - - if (KEY_DELETE == key) - { - setAvatarRenderSetting(mAvatarSettingsList->getCurrentID(), (S32)LLVOAvatar::AV_RENDER_NORMALLY); - handled = true; - } - return handled; -} - -std::string LLFloaterAvatarRenderSettings::createTimestamp(S32 datetime) -{ - std::string timeStr; - LLSD substitution; - substitution["datetime"] = datetime; - - timeStr = "["+LLTrans::getString ("TimeMonth")+"]/[" - +LLTrans::getString ("TimeDay")+"]/[" - +LLTrans::getString ("TimeYear")+"]"; - - LLStringUtil::format (timeStr, substitution); - return timeStr; -} +/** + * @file llfloateravatarrendersettings.cpp + * @brief Shows the list of avatars with non-default rendering settings + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2017, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#include "llviewerprecompiledheaders.h" + +#include "llfloateravatarrendersettings.h" + +#include "llagent.h" +#include "llavatarnamecache.h" +#include "llfloateravatarpicker.h" +#include "llfiltereditor.h" +#include "llfloaterreg.h" +#include "llnamelistctrl.h" +#include "llnotificationsutil.h" +#include "llmenugl.h" +#include "lltrans.h" +#include "llviewerobjectlist.h" +#include "llvoavatar.h" + +class LLSettingsContextMenu : public LLListContextMenu + +{ +public: + LLSettingsContextMenu(LLFloaterAvatarRenderSettings* floater_settings) + : mFloaterSettings(floater_settings) + {} +protected: + LLContextMenu* createMenu() + { + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + registrar.add("Settings.SetRendering", boost::bind(&LLFloaterAvatarRenderSettings::onCustomAction, mFloaterSettings, _2, mUUIDs.front())); + enable_registrar.add("Settings.IsSelected", boost::bind(&LLFloaterAvatarRenderSettings::isActionChecked, mFloaterSettings, _2, mUUIDs.front())); + LLContextMenu* menu = createFromFile("menu_avatar_rendering_settings.xml"); + + return menu; + } + + LLFloaterAvatarRenderSettings* mFloaterSettings; +}; + +class LLAvatarRenderMuteListObserver : public LLMuteListObserver +{ + /* virtual */ void onChange() { LLFloaterAvatarRenderSettings::setNeedsUpdate();} +}; + +static LLAvatarRenderMuteListObserver sAvatarRenderMuteListObserver; + +LLFloaterAvatarRenderSettings::LLFloaterAvatarRenderSettings(const LLSD& key) +: LLFloater(key), + mAvatarSettingsList(NULL), + mNeedsUpdate(false) +{ + mContextMenu = new LLSettingsContextMenu(this); + LLRenderMuteList::getInstance()->addObserver(&sAvatarRenderMuteListObserver); + mCommitCallbackRegistrar.add("Settings.AddNewEntry", boost::bind(&LLFloaterAvatarRenderSettings::onClickAdd, this, _2)); +} + +LLFloaterAvatarRenderSettings::~LLFloaterAvatarRenderSettings() +{ + delete mContextMenu; + LLRenderMuteList::getInstance()->removeObserver(&sAvatarRenderMuteListObserver); +} + +bool LLFloaterAvatarRenderSettings::postBuild() +{ + LLFloater::postBuild(); + mAvatarSettingsList = getChild("render_settings_list"); + mAvatarSettingsList->setRightMouseDownCallback(boost::bind(&LLFloaterAvatarRenderSettings::onAvatarListRightClick, this, _1, _2, _3)); + + return true; +} + +void LLFloaterAvatarRenderSettings::draw() +{ + if(mNeedsUpdate) + { + updateList(); + mNeedsUpdate = false; + } + + LLFloater::draw(); +} + +void LLFloaterAvatarRenderSettings::onAvatarListRightClick(LLUICtrl* ctrl, S32 x, S32 y) +{ + LLNameListCtrl* list = dynamic_cast(ctrl); + if (!list) return; + list->selectItemAt(x, y, MASK_NONE); + uuid_vec_t selected_uuids; + + if(list->getCurrentID().notNull()) + { + selected_uuids.push_back(list->getCurrentID()); + mContextMenu->show(ctrl, selected_uuids, x, y); + } +} + +void LLFloaterAvatarRenderSettings::onOpen(const LLSD& key) +{ + updateList(); +} + +void LLFloaterAvatarRenderSettings::updateList() +{ + mAvatarSettingsList->deleteAllItems(); + LLAvatarName av_name; + LLNameListCtrl::NameItem item_params; + for (std::map::iterator iter = LLRenderMuteList::getInstance()->sVisuallyMuteSettingsMap.begin(); iter != LLRenderMuteList::getInstance()->sVisuallyMuteSettingsMap.end(); iter++) + { + item_params.value = iter->first; + LLAvatarNameCache::get(iter->first, &av_name); + item_params.columns.add().value(av_name.getCompleteName()).column("name"); + std::string setting = getString(iter->second == 1 ? "av_never_render" : "av_always_render"); + item_params.columns.add().value(setting).column("setting"); + mAvatarSettingsList->addNameItemRow(item_params); + } +} + +static LLVOAvatar* find_avatar(const LLUUID& id) +{ + LLViewerObject *obj = gObjectList.findObject(id); + while (obj && obj->isAttachment()) + { + obj = (LLViewerObject *)obj->getParent(); + } + + if (obj && obj->isAvatar()) + { + return (LLVOAvatar*)obj; + } + else + { + return NULL; + } +} + + +void LLFloaterAvatarRenderSettings::onCustomAction (const LLSD& userdata, const LLUUID& av_id) +{ + const std::string command_name = userdata.asString(); + + S32 new_setting = 0; + if ("default" == command_name) + { + new_setting = S32(LLVOAvatar::AV_RENDER_NORMALLY); + } + else if ("never" == command_name) + { + new_setting = S32(LLVOAvatar::AV_DO_NOT_RENDER); + } + else if ("always" == command_name) + { + new_setting = S32(LLVOAvatar::AV_ALWAYS_RENDER); + } + + setAvatarRenderSetting(av_id, new_setting); +} + + +bool LLFloaterAvatarRenderSettings::isActionChecked(const LLSD& userdata, const LLUUID& av_id) +{ + const std::string command_name = userdata.asString(); + + S32 visual_setting = LLRenderMuteList::getInstance()->getSavedVisualMuteSetting(av_id); + if ("default" == command_name) + { + return (visual_setting == S32(LLVOAvatar::AV_RENDER_NORMALLY)); + } + else if ("non_default" == command_name) + { + return (visual_setting != S32(LLVOAvatar::AV_RENDER_NORMALLY)); + } + else if ("never" == command_name) + { + return (visual_setting == S32(LLVOAvatar::AV_DO_NOT_RENDER)); + } + else if ("always" == command_name) + { + return (visual_setting == S32(LLVOAvatar::AV_ALWAYS_RENDER)); + } + return false; +} + +void LLFloaterAvatarRenderSettings::setNeedsUpdate() +{ + LLFloaterAvatarRenderSettings* instance = LLFloaterReg::getTypedInstance("avatar_render_settings"); + if(!instance) return; + instance->mNeedsUpdate = true; +} + +void LLFloaterAvatarRenderSettings::onClickAdd(const LLSD& userdata) +{ + const std::string command_name = userdata.asString(); + S32 visual_setting = 0; + if ("never" == command_name) + { + visual_setting = S32(LLVOAvatar::AV_DO_NOT_RENDER); + } + else if ("always" == command_name) + { + visual_setting = S32(LLVOAvatar::AV_ALWAYS_RENDER); + } + + LLView * button = findChild("plus_btn", true); + LLFloater* root_floater = gFloaterView->getParentFloater(this); + LLFloaterAvatarPicker * picker = LLFloaterAvatarPicker::show(boost::bind(&LLFloaterAvatarRenderSettings::callbackAvatarPicked, this, _1, visual_setting), + false, true, false, root_floater->getName(), button); + + if (root_floater) + { + root_floater->addDependentFloater(picker); + } +} + +void LLFloaterAvatarRenderSettings::callbackAvatarPicked(const uuid_vec_t& ids, S32 visual_setting) +{ + if (ids.empty()) return; + if(ids[0] == gAgentID) + { + LLNotificationsUtil::add("AddSelfRenderExceptions"); + return; + } + setAvatarRenderSetting(ids[0], visual_setting); +} + +void LLFloaterAvatarRenderSettings::setAvatarRenderSetting(const LLUUID& av_id, S32 new_setting) +{ + LLVOAvatar *avatarp = find_avatar(av_id); + if (avatarp) + { + avatarp->setVisualMuteSettings(LLVOAvatar::VisualMuteSettings(new_setting)); + } + else + { + LLRenderMuteList::getInstance()->saveVisualMuteSetting(av_id, new_setting); + } +} + +bool LLFloaterAvatarRenderSettings::handleKeyHere(KEY key, MASK mask ) +{ + bool handled = false; + + if (KEY_DELETE == key) + { + setAvatarRenderSetting(mAvatarSettingsList->getCurrentID(), (S32)LLVOAvatar::AV_RENDER_NORMALLY); + handled = true; + } + return handled; +} + +std::string LLFloaterAvatarRenderSettings::createTimestamp(S32 datetime) +{ + std::string timeStr; + LLSD substitution; + substitution["datetime"] = datetime; + + timeStr = "["+LLTrans::getString ("TimeMonth")+"]/[" + +LLTrans::getString ("TimeDay")+"]/[" + +LLTrans::getString ("TimeYear")+"]"; + + LLStringUtil::format (timeStr, substitution); + return timeStr; +} diff --git a/indra/newview/llfloateravatartextures.cpp b/indra/newview/llfloateravatartextures.cpp index 75d0e5a869..b85e3cb267 100644 --- a/indra/newview/llfloateravatartextures.cpp +++ b/indra/newview/llfloateravatartextures.cpp @@ -1,203 +1,203 @@ -/** - * @file llfloateravatartextures.cpp - * @brief Debugging view showing underlying avatar textures and baked textures. - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llfloateravatartextures.h" - -// library headers -#include "llavatarnamecache.h" - -#include "llagent.h" -#include "llagentwearables.h" -#include "llviewerwearable.h" -#include "lltexturectrl.h" -#include "lluictrlfactory.h" -#include "llviewerobjectlist.h" -#include "llvoavatarself.h" -#include "lllocaltextureobject.h" - -using namespace LLAvatarAppearanceDefines; - -LLFloaterAvatarTextures::LLFloaterAvatarTextures(const LLSD& id) - : LLFloater(id), - mID(id.asUUID()) -{ -} - -LLFloaterAvatarTextures::~LLFloaterAvatarTextures() -{ -} - -bool LLFloaterAvatarTextures::postBuild() -{ - for (U32 i=0; i < TEX_NUM_INDICES; i++) - { - const std::string tex_name = LLAvatarAppearance::getDictionary()->getTexture(ETextureIndex(i))->mName; - mTextures[i] = getChild(tex_name); - } - mTitle = getTitle(); - - childSetAction("Dump", onClickDump, this); - - refresh(); - return true; -} - -void LLFloaterAvatarTextures::draw() -{ - refresh(); - LLFloater::draw(); -} - -static void update_texture_ctrl(LLVOAvatar* avatarp, - LLTextureCtrl* ctrl, - ETextureIndex te) -{ - LLUUID id = IMG_DEFAULT_AVATAR; - const LLAvatarAppearanceDictionary::TextureEntry* tex_entry = LLAvatarAppearance::getDictionary()->getTexture(te); - if (tex_entry && tex_entry->mIsLocalTexture) - { - if (avatarp->isSelf()) - { - const LLWearableType::EType wearable_type = tex_entry->mWearableType; - LLViewerWearable *wearable = gAgentWearables.getViewerWearable(wearable_type, 0); - if (wearable) - { - LLLocalTextureObject *lto = wearable->getLocalTextureObject(te); - if (lto) - { - id = lto->getID(); - } - } - } - } - else - { - id = tex_entry ? avatarp->getTE(te)->getID() : IMG_DEFAULT_AVATAR; - } - //id = avatarp->getTE(te)->getID(); - if (id == IMG_DEFAULT_AVATAR) - { - ctrl->setImageAssetID(LLUUID::null); - ctrl->setToolTip(tex_entry->mName + " : " + std::string("IMG_DEFAULT_AVATAR")); - } - else - { - ctrl->setImageAssetID(id); - ctrl->setToolTip(tex_entry->mName + " : " + id.asString()); - } -} - -static LLVOAvatar* find_avatar(const LLUUID& id) -{ - LLViewerObject *obj = gObjectList.findObject(id); - while (obj && obj->isAttachment()) - { - obj = (LLViewerObject *)obj->getParent(); - } - - if (obj && obj->isAvatar()) - { - return (LLVOAvatar*)obj; - } - else - { - return NULL; - } -} - -void LLFloaterAvatarTextures::refresh() -{ - if (gAgent.isGodlike()) - { - LLVOAvatar *avatarp = find_avatar(mID); - if (avatarp) - { - LLAvatarName av_name; - if (LLAvatarNameCache::get(avatarp->getID(), &av_name)) - { - setTitle(mTitle + ": " + av_name.getCompleteName()); - } - for (U32 i=0; i < TEX_NUM_INDICES; i++) - { - update_texture_ctrl(avatarp, mTextures[i], ETextureIndex(i)); - } - } - else - { - setTitle(mTitle + ": " + getString("InvalidAvatar") + " (" + mID.asString() + ")"); - } - } -} - -// static -void LLFloaterAvatarTextures::onClickDump(void* data) -{ - if (gAgent.isGodlike()) - { - const LLVOAvatarSelf* avatarp = gAgentAvatarp; - if (!avatarp) return; - for (S32 i = 0; i < avatarp->getNumTEs(); i++) - { - const LLTextureEntry* te = avatarp->getTE(i); - if (!te) continue; - - const LLAvatarAppearanceDictionary::TextureEntry* tex_entry = LLAvatarAppearance::getDictionary()->getTexture((ETextureIndex)(i)); - if (!tex_entry) - continue; - - if (LLVOAvatar::isIndexLocalTexture((ETextureIndex)i)) - { - LLUUID id = IMG_DEFAULT_AVATAR; - LLWearableType::EType wearable_type = LLAvatarAppearance::getDictionary()->getTEWearableType((ETextureIndex)i); - if (avatarp->isSelf()) - { - LLViewerWearable *wearable = gAgentWearables.getViewerWearable(wearable_type, 0); - if (wearable) - { - LLLocalTextureObject *lto = wearable->getLocalTextureObject(i); - if (lto) - { - id = lto->getID(); - } - } - } - if (id != IMG_DEFAULT_AVATAR) - { - LL_INFOS() << "TE " << i << " name:" << tex_entry->mName << " id:" << id << LL_ENDL; - } - else - { - LL_INFOS() << "TE " << i << " name:" << tex_entry->mName << " id:" << "" << LL_ENDL; - } - } - else - { - LL_INFOS() << "TE " << i << " name:" << tex_entry->mName << " id:" << te->getID() << LL_ENDL; - } - } - } -} +/** + * @file llfloateravatartextures.cpp + * @brief Debugging view showing underlying avatar textures and baked textures. + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llfloateravatartextures.h" + +// library headers +#include "llavatarnamecache.h" + +#include "llagent.h" +#include "llagentwearables.h" +#include "llviewerwearable.h" +#include "lltexturectrl.h" +#include "lluictrlfactory.h" +#include "llviewerobjectlist.h" +#include "llvoavatarself.h" +#include "lllocaltextureobject.h" + +using namespace LLAvatarAppearanceDefines; + +LLFloaterAvatarTextures::LLFloaterAvatarTextures(const LLSD& id) + : LLFloater(id), + mID(id.asUUID()) +{ +} + +LLFloaterAvatarTextures::~LLFloaterAvatarTextures() +{ +} + +bool LLFloaterAvatarTextures::postBuild() +{ + for (U32 i=0; i < TEX_NUM_INDICES; i++) + { + const std::string tex_name = LLAvatarAppearance::getDictionary()->getTexture(ETextureIndex(i))->mName; + mTextures[i] = getChild(tex_name); + } + mTitle = getTitle(); + + childSetAction("Dump", onClickDump, this); + + refresh(); + return true; +} + +void LLFloaterAvatarTextures::draw() +{ + refresh(); + LLFloater::draw(); +} + +static void update_texture_ctrl(LLVOAvatar* avatarp, + LLTextureCtrl* ctrl, + ETextureIndex te) +{ + LLUUID id = IMG_DEFAULT_AVATAR; + const LLAvatarAppearanceDictionary::TextureEntry* tex_entry = LLAvatarAppearance::getDictionary()->getTexture(te); + if (tex_entry && tex_entry->mIsLocalTexture) + { + if (avatarp->isSelf()) + { + const LLWearableType::EType wearable_type = tex_entry->mWearableType; + LLViewerWearable *wearable = gAgentWearables.getViewerWearable(wearable_type, 0); + if (wearable) + { + LLLocalTextureObject *lto = wearable->getLocalTextureObject(te); + if (lto) + { + id = lto->getID(); + } + } + } + } + else + { + id = tex_entry ? avatarp->getTE(te)->getID() : IMG_DEFAULT_AVATAR; + } + //id = avatarp->getTE(te)->getID(); + if (id == IMG_DEFAULT_AVATAR) + { + ctrl->setImageAssetID(LLUUID::null); + ctrl->setToolTip(tex_entry->mName + " : " + std::string("IMG_DEFAULT_AVATAR")); + } + else + { + ctrl->setImageAssetID(id); + ctrl->setToolTip(tex_entry->mName + " : " + id.asString()); + } +} + +static LLVOAvatar* find_avatar(const LLUUID& id) +{ + LLViewerObject *obj = gObjectList.findObject(id); + while (obj && obj->isAttachment()) + { + obj = (LLViewerObject *)obj->getParent(); + } + + if (obj && obj->isAvatar()) + { + return (LLVOAvatar*)obj; + } + else + { + return NULL; + } +} + +void LLFloaterAvatarTextures::refresh() +{ + if (gAgent.isGodlike()) + { + LLVOAvatar *avatarp = find_avatar(mID); + if (avatarp) + { + LLAvatarName av_name; + if (LLAvatarNameCache::get(avatarp->getID(), &av_name)) + { + setTitle(mTitle + ": " + av_name.getCompleteName()); + } + for (U32 i=0; i < TEX_NUM_INDICES; i++) + { + update_texture_ctrl(avatarp, mTextures[i], ETextureIndex(i)); + } + } + else + { + setTitle(mTitle + ": " + getString("InvalidAvatar") + " (" + mID.asString() + ")"); + } + } +} + +// static +void LLFloaterAvatarTextures::onClickDump(void* data) +{ + if (gAgent.isGodlike()) + { + const LLVOAvatarSelf* avatarp = gAgentAvatarp; + if (!avatarp) return; + for (S32 i = 0; i < avatarp->getNumTEs(); i++) + { + const LLTextureEntry* te = avatarp->getTE(i); + if (!te) continue; + + const LLAvatarAppearanceDictionary::TextureEntry* tex_entry = LLAvatarAppearance::getDictionary()->getTexture((ETextureIndex)(i)); + if (!tex_entry) + continue; + + if (LLVOAvatar::isIndexLocalTexture((ETextureIndex)i)) + { + LLUUID id = IMG_DEFAULT_AVATAR; + LLWearableType::EType wearable_type = LLAvatarAppearance::getDictionary()->getTEWearableType((ETextureIndex)i); + if (avatarp->isSelf()) + { + LLViewerWearable *wearable = gAgentWearables.getViewerWearable(wearable_type, 0); + if (wearable) + { + LLLocalTextureObject *lto = wearable->getLocalTextureObject(i); + if (lto) + { + id = lto->getID(); + } + } + } + if (id != IMG_DEFAULT_AVATAR) + { + LL_INFOS() << "TE " << i << " name:" << tex_entry->mName << " id:" << id << LL_ENDL; + } + else + { + LL_INFOS() << "TE " << i << " name:" << tex_entry->mName << " id:" << "" << LL_ENDL; + } + } + else + { + LL_INFOS() << "TE " << i << " name:" << tex_entry->mName << " id:" << te->getID() << LL_ENDL; + } + } + } +} diff --git a/indra/newview/llfloateravatartextures.h b/indra/newview/llfloateravatartextures.h index f4f17701df..b92992e163 100644 --- a/indra/newview/llfloateravatartextures.h +++ b/indra/newview/llfloateravatartextures.h @@ -1,57 +1,57 @@ -/** - * @file llfloateravatartextures.h - * @brief Debugging view showing underlying avatar textures and baked textures. - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERAVATARTEXTURES_H -#define LL_LLFLOATERAVATARTEXTURES_H - -#include "llfloater.h" -#include "lluuid.h" -#include "llstring.h" -#include "llavatarappearancedefines.h" - -class LLTextureCtrl; - -class LLFloaterAvatarTextures : public LLFloater -{ -public: - LLFloaterAvatarTextures(const LLSD& id); - virtual ~LLFloaterAvatarTextures(); - - bool postBuild() override; - void draw() override; - - void refresh() override; - -private: - static void onClickDump(void*); - -private: - LLUUID mID; - std::string mTitle; - LLTextureCtrl* mTextures[LLAvatarAppearanceDefines::TEX_NUM_INDICES]; -}; - -#endif +/** + * @file llfloateravatartextures.h + * @brief Debugging view showing underlying avatar textures and baked textures. + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERAVATARTEXTURES_H +#define LL_LLFLOATERAVATARTEXTURES_H + +#include "llfloater.h" +#include "lluuid.h" +#include "llstring.h" +#include "llavatarappearancedefines.h" + +class LLTextureCtrl; + +class LLFloaterAvatarTextures : public LLFloater +{ +public: + LLFloaterAvatarTextures(const LLSD& id); + virtual ~LLFloaterAvatarTextures(); + + bool postBuild() override; + void draw() override; + + void refresh() override; + +private: + static void onClickDump(void*); + +private: + LLUUID mID; + std::string mTitle; + LLTextureCtrl* mTextures[LLAvatarAppearanceDefines::TEX_NUM_INDICES]; +}; + +#endif diff --git a/indra/newview/llfloaterbeacons.cpp b/indra/newview/llfloaterbeacons.cpp index d597a0cde4..11c1c2a9f2 100644 --- a/indra/newview/llfloaterbeacons.cpp +++ b/indra/newview/llfloaterbeacons.cpp @@ -1,131 +1,131 @@ -/** - * @file llfloaterbeacons.cpp - * @brief Front-end to LLPipeline controls for highlighting various kinds of objects. - * @author Coco - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llfloaterbeacons.h" -#include "llviewercontrol.h" -#include "lluictrlfactory.h" -#include "llcheckboxctrl.h" -#include "pipeline.h" - - -LLFloaterBeacons::LLFloaterBeacons(const LLSD& seed) -: LLFloater(seed) -{ - // Initialize pipeline states from saved settings. - // OK to do at floater constructor time because beacons do not display unless the floater is open - // therefore it is OK to not initialize the pipeline state before needed. - // Note also that we should replace those pipeline statics with direct lookup of the saved settings - // eliminating the need to keep these states in sync. - LLPipeline::setRenderScriptedTouchBeacons(gSavedSettings.getBOOL("scripttouchbeacon")); - LLPipeline::setRenderScriptedBeacons( gSavedSettings.getBOOL("scriptsbeacon")); - LLPipeline::setRenderPhysicalBeacons( gSavedSettings.getBOOL("physicalbeacon")); - LLPipeline::setRenderSoundBeacons( gSavedSettings.getBOOL("soundsbeacon")); - LLPipeline::setRenderParticleBeacons( gSavedSettings.getBOOL("particlesbeacon")); - LLPipeline::setRenderHighlights( gSavedSettings.getBOOL("renderhighlights")); - LLPipeline::setRenderBeacons( gSavedSettings.getBOOL("renderbeacons")); - LLPipeline::setRenderMOAPBeacons( gSavedSettings.getBOOL("moapbeacon")); - mCommitCallbackRegistrar.add("Beacons.UICheck", boost::bind(&LLFloaterBeacons::onClickUICheck, this,_1)); -} - -bool LLFloaterBeacons::postBuild() -{ - return true; -} - -// Callback attached to each check box control to both affect their main purpose -// and to implement the couple screwy interdependency rules that some have. - -void LLFloaterBeacons::onClickUICheck(LLUICtrl *ctrl) -{ - LLCheckBoxCtrl *check = (LLCheckBoxCtrl *)ctrl; - std::string name = check->getName(); - if(name == "touch_only") - { - LLPipeline::toggleRenderScriptedTouchBeacons(); - // Don't allow both to be ON at the same time. Toggle the other one off if both now on. - if ( - LLPipeline::getRenderScriptedTouchBeacons() && - LLPipeline::getRenderScriptedBeacons() ) - { - LLPipeline::setRenderScriptedBeacons(false); - getChild("scripted")->setControlValue(LLSD(false)); - getChild("scripted")->setValue(false); - getChild("touch_only")->setControlValue(LLSD(true)); // just to be sure it's in sync with llpipeline - getChild("touch_only")->setValue(true); - } - } - else if(name == "scripted") - { - LLPipeline::toggleRenderScriptedBeacons(); - // Don't allow both to be ON at the same time. Toggle the other one off if both now on. - if ( - LLPipeline::getRenderScriptedTouchBeacons() && - LLPipeline::getRenderScriptedBeacons() ) - { - LLPipeline::setRenderScriptedTouchBeacons(false); - getChild("touch_only")->setControlValue(LLSD(false)); - getChild("touch_only")->setValue(false); - getChild("scripted")->setControlValue(LLSD(true)); // just to be sure it's in sync with llpipeline - getChild("scripted")->setValue(true); - } - } - else if(name == "physical") LLPipeline::setRenderPhysicalBeacons(check->get()); - else if(name == "sounds") LLPipeline::setRenderSoundBeacons(check->get()); - else if(name == "particles") LLPipeline::setRenderParticleBeacons(check->get()); - else if(name == "moapbeacon") LLPipeline::setRenderMOAPBeacons(check->get()); - else if(name == "highlights") - { - LLPipeline::toggleRenderHighlights(); - // Don't allow both to be OFF at the same time. Toggle the other one on if both now off. - if ( - !LLPipeline::getRenderBeacons() && - !LLPipeline::getRenderHighlights() ) - { - LLPipeline::setRenderBeacons(true); - getChild("beacons")->setControlValue(LLSD(true)); - getChild("beacons")->setValue(true); - getChild("highlights")->setControlValue(LLSD(false)); // just to be sure it's in sync with llpipeline - getChild("highlights")->setValue(false); - } - } - else if(name == "beacons") - { - LLPipeline::toggleRenderBeacons(); - // Don't allow both to be OFF at the same time. Toggle the other one on if both now off. - if ( - !LLPipeline::getRenderBeacons() && - !LLPipeline::getRenderHighlights() ) - { - LLPipeline::setRenderHighlights(true); - getChild("highlights")->setControlValue(LLSD(true)); - getChild("highlights")->setValue(true); - getChild("beacons")->setControlValue(LLSD(false)); // just to be sure it's in sync with llpipeline - getChild("beacons")->setValue(false); - } - } -} +/** + * @file llfloaterbeacons.cpp + * @brief Front-end to LLPipeline controls for highlighting various kinds of objects. + * @author Coco + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llfloaterbeacons.h" +#include "llviewercontrol.h" +#include "lluictrlfactory.h" +#include "llcheckboxctrl.h" +#include "pipeline.h" + + +LLFloaterBeacons::LLFloaterBeacons(const LLSD& seed) +: LLFloater(seed) +{ + // Initialize pipeline states from saved settings. + // OK to do at floater constructor time because beacons do not display unless the floater is open + // therefore it is OK to not initialize the pipeline state before needed. + // Note also that we should replace those pipeline statics with direct lookup of the saved settings + // eliminating the need to keep these states in sync. + LLPipeline::setRenderScriptedTouchBeacons(gSavedSettings.getBOOL("scripttouchbeacon")); + LLPipeline::setRenderScriptedBeacons( gSavedSettings.getBOOL("scriptsbeacon")); + LLPipeline::setRenderPhysicalBeacons( gSavedSettings.getBOOL("physicalbeacon")); + LLPipeline::setRenderSoundBeacons( gSavedSettings.getBOOL("soundsbeacon")); + LLPipeline::setRenderParticleBeacons( gSavedSettings.getBOOL("particlesbeacon")); + LLPipeline::setRenderHighlights( gSavedSettings.getBOOL("renderhighlights")); + LLPipeline::setRenderBeacons( gSavedSettings.getBOOL("renderbeacons")); + LLPipeline::setRenderMOAPBeacons( gSavedSettings.getBOOL("moapbeacon")); + mCommitCallbackRegistrar.add("Beacons.UICheck", boost::bind(&LLFloaterBeacons::onClickUICheck, this,_1)); +} + +bool LLFloaterBeacons::postBuild() +{ + return true; +} + +// Callback attached to each check box control to both affect their main purpose +// and to implement the couple screwy interdependency rules that some have. + +void LLFloaterBeacons::onClickUICheck(LLUICtrl *ctrl) +{ + LLCheckBoxCtrl *check = (LLCheckBoxCtrl *)ctrl; + std::string name = check->getName(); + if(name == "touch_only") + { + LLPipeline::toggleRenderScriptedTouchBeacons(); + // Don't allow both to be ON at the same time. Toggle the other one off if both now on. + if ( + LLPipeline::getRenderScriptedTouchBeacons() && + LLPipeline::getRenderScriptedBeacons() ) + { + LLPipeline::setRenderScriptedBeacons(false); + getChild("scripted")->setControlValue(LLSD(false)); + getChild("scripted")->setValue(false); + getChild("touch_only")->setControlValue(LLSD(true)); // just to be sure it's in sync with llpipeline + getChild("touch_only")->setValue(true); + } + } + else if(name == "scripted") + { + LLPipeline::toggleRenderScriptedBeacons(); + // Don't allow both to be ON at the same time. Toggle the other one off if both now on. + if ( + LLPipeline::getRenderScriptedTouchBeacons() && + LLPipeline::getRenderScriptedBeacons() ) + { + LLPipeline::setRenderScriptedTouchBeacons(false); + getChild("touch_only")->setControlValue(LLSD(false)); + getChild("touch_only")->setValue(false); + getChild("scripted")->setControlValue(LLSD(true)); // just to be sure it's in sync with llpipeline + getChild("scripted")->setValue(true); + } + } + else if(name == "physical") LLPipeline::setRenderPhysicalBeacons(check->get()); + else if(name == "sounds") LLPipeline::setRenderSoundBeacons(check->get()); + else if(name == "particles") LLPipeline::setRenderParticleBeacons(check->get()); + else if(name == "moapbeacon") LLPipeline::setRenderMOAPBeacons(check->get()); + else if(name == "highlights") + { + LLPipeline::toggleRenderHighlights(); + // Don't allow both to be OFF at the same time. Toggle the other one on if both now off. + if ( + !LLPipeline::getRenderBeacons() && + !LLPipeline::getRenderHighlights() ) + { + LLPipeline::setRenderBeacons(true); + getChild("beacons")->setControlValue(LLSD(true)); + getChild("beacons")->setValue(true); + getChild("highlights")->setControlValue(LLSD(false)); // just to be sure it's in sync with llpipeline + getChild("highlights")->setValue(false); + } + } + else if(name == "beacons") + { + LLPipeline::toggleRenderBeacons(); + // Don't allow both to be OFF at the same time. Toggle the other one on if both now off. + if ( + !LLPipeline::getRenderBeacons() && + !LLPipeline::getRenderHighlights() ) + { + LLPipeline::setRenderHighlights(true); + getChild("highlights")->setControlValue(LLSD(true)); + getChild("highlights")->setValue(true); + getChild("beacons")->setControlValue(LLSD(false)); // just to be sure it's in sync with llpipeline + getChild("beacons")->setValue(false); + } + } +} diff --git a/indra/newview/llfloaterbeacons.h b/indra/newview/llfloaterbeacons.h index 1c85ef38bf..f54fb2a50a 100644 --- a/indra/newview/llfloaterbeacons.h +++ b/indra/newview/llfloaterbeacons.h @@ -1,49 +1,49 @@ -/** - * @file llfloaterbeacons.h - * @brief Front-end to LLPipeline controls for highlighting various kinds of objects. - * @author Coco - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERBEACONS_H -#define LL_LLFLOATERBEACONS_H - -#include "llfloater.h" - -class LLFloaterBeacons : public LLFloater -{ - friend class LLFloaterReg; - -public: - - bool postBuild() override; - - // Needed to make the floater visibility toggle the beacons. - // Too bad we can't just add control_name="BeaconAlwaysOn" to the XML. - void onClickUICheck(LLUICtrl *ctrl); - -private: - LLFloaterBeacons(const LLSD& seed); -}; - -#endif +/** + * @file llfloaterbeacons.h + * @brief Front-end to LLPipeline controls for highlighting various kinds of objects. + * @author Coco + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERBEACONS_H +#define LL_LLFLOATERBEACONS_H + +#include "llfloater.h" + +class LLFloaterBeacons : public LLFloater +{ + friend class LLFloaterReg; + +public: + + bool postBuild() override; + + // Needed to make the floater visibility toggle the beacons. + // Too bad we can't just add control_name="BeaconAlwaysOn" to the XML. + void onClickUICheck(LLUICtrl *ctrl); + +private: + LLFloaterBeacons(const LLSD& seed); +}; + +#endif diff --git a/indra/newview/llfloaterbigpreview.cpp b/indra/newview/llfloaterbigpreview.cpp index 492d39a685..ba682494bb 100644 --- a/indra/newview/llfloaterbigpreview.cpp +++ b/indra/newview/llfloaterbigpreview.cpp @@ -1,110 +1,110 @@ -/** -* @file llfloaterbigpreview.cpp -* @brief Display of extended (big) preview for snapshots and SL Share -* @author merov@lindenlab.com -* -* $LicenseInfo:firstyear=2013&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2013, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterbigpreview.h" -#include "llsnapshotlivepreview.h" - -/////////////////////// -//LLFloaterBigPreview// -/////////////////////// - -LLFloaterBigPreview::LLFloaterBigPreview(const LLSD& key) : LLFloater(key), - mPreviewPlaceholder(NULL), - mFloaterOwner(NULL) -{ -} - -LLFloaterBigPreview::~LLFloaterBigPreview() -{ - if (mPreviewHandle.get()) - { - mPreviewHandle.get()->die(); - } -} - -void LLFloaterBigPreview::onCancel() -{ - closeFloater(); -} - -void LLFloaterBigPreview::closeOnFloaterOwnerClosing(LLFloater* floaterp) -{ - if (isFloaterOwner(floaterp)) - { - closeFloater(); - } -} - -bool LLFloaterBigPreview::postBuild() -{ - mPreviewPlaceholder = getChild("big_preview_placeholder"); - return LLFloater::postBuild(); -} - -void LLFloaterBigPreview::draw() -{ - LLFloater::draw(); - - LLSnapshotLivePreview * previewp = static_cast(mPreviewHandle.get()); - - // Display the preview if one is available - if (previewp && previewp->getBigThumbnailImage()) - { - // Get the preview rect - const LLRect& preview_rect = mPreviewPlaceholder->getRect(); - - // Get the preview texture size - S32 thumbnail_w = previewp->getBigThumbnailWidth(); - S32 thumbnail_h = previewp->getBigThumbnailHeight(); - - // Compute the scaling ratio and the size of the final texture in the rect: we want to prevent anisotropic scaling (distorted in x and y) - F32 ratio = llmax((F32)(thumbnail_w)/(F32)(preview_rect.getWidth()), (F32)(thumbnail_h)/(F32)(preview_rect.getHeight())); - thumbnail_w = (S32)((F32)(thumbnail_w)/ratio); - thumbnail_h = (S32)((F32)(thumbnail_h)/ratio); - - // Compute the preview offset within the preview rect: we want to center that preview in the available rect - const S32 local_offset_x = (preview_rect.getWidth() - thumbnail_w) / 2 ; - const S32 local_offset_y = (preview_rect.getHeight() - thumbnail_h) / 2 ; - - // Compute preview offset within the floater rect - S32 offset_x = preview_rect.mLeft + local_offset_x; - S32 offset_y = preview_rect.mBottom + local_offset_y; - - gGL.matrixMode(LLRender::MM_MODELVIEW); - // Apply floater transparency to the texture unless the floater is focused. - F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); - LLColor4 color = LLColor4::white; - - // Draw the preview texture - gl_draw_scaled_image(offset_x, offset_y, - thumbnail_w, thumbnail_h, - previewp->getBigThumbnailImage(), color % alpha); - } -} - +/** +* @file llfloaterbigpreview.cpp +* @brief Display of extended (big) preview for snapshots and SL Share +* @author merov@lindenlab.com +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2013, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterbigpreview.h" +#include "llsnapshotlivepreview.h" + +/////////////////////// +//LLFloaterBigPreview// +/////////////////////// + +LLFloaterBigPreview::LLFloaterBigPreview(const LLSD& key) : LLFloater(key), + mPreviewPlaceholder(NULL), + mFloaterOwner(NULL) +{ +} + +LLFloaterBigPreview::~LLFloaterBigPreview() +{ + if (mPreviewHandle.get()) + { + mPreviewHandle.get()->die(); + } +} + +void LLFloaterBigPreview::onCancel() +{ + closeFloater(); +} + +void LLFloaterBigPreview::closeOnFloaterOwnerClosing(LLFloater* floaterp) +{ + if (isFloaterOwner(floaterp)) + { + closeFloater(); + } +} + +bool LLFloaterBigPreview::postBuild() +{ + mPreviewPlaceholder = getChild("big_preview_placeholder"); + return LLFloater::postBuild(); +} + +void LLFloaterBigPreview::draw() +{ + LLFloater::draw(); + + LLSnapshotLivePreview * previewp = static_cast(mPreviewHandle.get()); + + // Display the preview if one is available + if (previewp && previewp->getBigThumbnailImage()) + { + // Get the preview rect + const LLRect& preview_rect = mPreviewPlaceholder->getRect(); + + // Get the preview texture size + S32 thumbnail_w = previewp->getBigThumbnailWidth(); + S32 thumbnail_h = previewp->getBigThumbnailHeight(); + + // Compute the scaling ratio and the size of the final texture in the rect: we want to prevent anisotropic scaling (distorted in x and y) + F32 ratio = llmax((F32)(thumbnail_w)/(F32)(preview_rect.getWidth()), (F32)(thumbnail_h)/(F32)(preview_rect.getHeight())); + thumbnail_w = (S32)((F32)(thumbnail_w)/ratio); + thumbnail_h = (S32)((F32)(thumbnail_h)/ratio); + + // Compute the preview offset within the preview rect: we want to center that preview in the available rect + const S32 local_offset_x = (preview_rect.getWidth() - thumbnail_w) / 2 ; + const S32 local_offset_y = (preview_rect.getHeight() - thumbnail_h) / 2 ; + + // Compute preview offset within the floater rect + S32 offset_x = preview_rect.mLeft + local_offset_x; + S32 offset_y = preview_rect.mBottom + local_offset_y; + + gGL.matrixMode(LLRender::MM_MODELVIEW); + // Apply floater transparency to the texture unless the floater is focused. + F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); + LLColor4 color = LLColor4::white; + + // Draw the preview texture + gl_draw_scaled_image(offset_x, offset_y, + thumbnail_w, thumbnail_h, + previewp->getBigThumbnailImage(), color % alpha); + } +} + diff --git a/indra/newview/llfloaterbigpreview.h b/indra/newview/llfloaterbigpreview.h index 3a75d1c83a..1d5804acf5 100644 --- a/indra/newview/llfloaterbigpreview.h +++ b/indra/newview/llfloaterbigpreview.h @@ -1,54 +1,54 @@ -/** -* @file llfloaterbigpreview.h -* @brief Display of extended (big) preview for snapshots -* @author merov@lindenlab.com -* -* $LicenseInfo:firstyear=2013&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2013, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ -#ifndef LL_LLFLOATERBIGPREVIEW_H -#define LL_LLFLOATERBIGPREVIEW_H - -#include "llfloater.h" - -class LLFloaterBigPreview : public LLFloater -{ -public: - LLFloaterBigPreview(const LLSD& key); - ~LLFloaterBigPreview(); - - bool postBuild(); - void draw(); - void onCancel(); - - void setPreview(LLView* previewp) { mPreviewHandle = previewp->getHandle(); } - void setFloaterOwner(LLFloater* floaterp) { mFloaterOwner = floaterp; } - bool isFloaterOwner(LLFloater* floaterp) const { return (mFloaterOwner == floaterp); } - void closeOnFloaterOwnerClosing(LLFloater* floaterp); - -private: - LLHandle mPreviewHandle; - LLUICtrl* mPreviewPlaceholder; - LLFloater* mFloaterOwner; -}; - -#endif // LL_LLFLOATERBIGPREVIEW_H - +/** +* @file llfloaterbigpreview.h +* @brief Display of extended (big) preview for snapshots +* @author merov@lindenlab.com +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2013, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LL_LLFLOATERBIGPREVIEW_H +#define LL_LLFLOATERBIGPREVIEW_H + +#include "llfloater.h" + +class LLFloaterBigPreview : public LLFloater +{ +public: + LLFloaterBigPreview(const LLSD& key); + ~LLFloaterBigPreview(); + + bool postBuild(); + void draw(); + void onCancel(); + + void setPreview(LLView* previewp) { mPreviewHandle = previewp->getHandle(); } + void setFloaterOwner(LLFloater* floaterp) { mFloaterOwner = floaterp; } + bool isFloaterOwner(LLFloater* floaterp) const { return (mFloaterOwner == floaterp); } + void closeOnFloaterOwnerClosing(LLFloater* floaterp); + +private: + LLHandle mPreviewHandle; + LLUICtrl* mPreviewPlaceholder; + LLFloater* mFloaterOwner; +}; + +#endif // LL_LLFLOATERBIGPREVIEW_H + diff --git a/indra/newview/llfloaterbuildoptions.cpp b/indra/newview/llfloaterbuildoptions.cpp index ee6af5628f..cf3c4d29f3 100644 --- a/indra/newview/llfloaterbuildoptions.cpp +++ b/indra/newview/llfloaterbuildoptions.cpp @@ -1,67 +1,67 @@ -/** - * @file llfloaterbuildoptions.cpp - * @brief LLFloaterBuildOptions class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/** - * Panel for setting global object-editing options, specifically - * grid size and spacing. - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterbuildoptions.h" -#include "lluictrlfactory.h" - -#include "llcombobox.h" -#include "llselectmgr.h" - -// -// Methods -// - -LLFloaterBuildOptions::LLFloaterBuildOptions(const LLSD& key) - : LLFloater(key) -{ -} - -LLFloaterBuildOptions::~LLFloaterBuildOptions() -{} - -bool LLFloaterBuildOptions::postBuild() -{ - return true; -} - -// virtual -void LLFloaterBuildOptions::onOpen(const LLSD& key) -{ - mObjectSelection = LLSelectMgr::getInstance()->getEditSelection(); -} - -// virtual -void LLFloaterBuildOptions::onClose(bool app_quitting) -{ - mObjectSelection = nullptr; -} +/** + * @file llfloaterbuildoptions.cpp + * @brief LLFloaterBuildOptions class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/** + * Panel for setting global object-editing options, specifically + * grid size and spacing. + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterbuildoptions.h" +#include "lluictrlfactory.h" + +#include "llcombobox.h" +#include "llselectmgr.h" + +// +// Methods +// + +LLFloaterBuildOptions::LLFloaterBuildOptions(const LLSD& key) + : LLFloater(key) +{ +} + +LLFloaterBuildOptions::~LLFloaterBuildOptions() +{} + +bool LLFloaterBuildOptions::postBuild() +{ + return true; +} + +// virtual +void LLFloaterBuildOptions::onOpen(const LLSD& key) +{ + mObjectSelection = LLSelectMgr::getInstance()->getEditSelection(); +} + +// virtual +void LLFloaterBuildOptions::onClose(bool app_quitting) +{ + mObjectSelection = nullptr; +} diff --git a/indra/newview/llfloaterbuildoptions.h b/indra/newview/llfloaterbuildoptions.h index b15dc1569c..9b87e4d764 100644 --- a/indra/newview/llfloaterbuildoptions.h +++ b/indra/newview/llfloaterbuildoptions.h @@ -1,59 +1,59 @@ -/** - * @file llfloaterbuildoptions.h - * @brief LLFloaterBuildOptions class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/** - * Panel for setting global object-editing options, specifically - * grid size and spacing. - */ - -#ifndef LL_LLFLOATERBUILDOPTIONS_H -#define LL_LLFLOATERBUILDOPTIONS_H - -#include "llfloater.h" -#include "llselectmgr.h" - -class LLObjectSelection; - -typedef LLSafeHandle LLObjectSelectionHandle; - -class LLFloaterBuildOptions - : public LLFloater -{ -public: - bool postBuild() override; - - void onOpen(const LLSD& key) override; - void onClose(bool app_quitting) override; - -private: - friend class LLFloaterReg; - - LLFloaterBuildOptions(const LLSD& key); - ~LLFloaterBuildOptions(); - - LLObjectSelectionHandle mObjectSelection; -}; -#endif +/** + * @file llfloaterbuildoptions.h + * @brief LLFloaterBuildOptions class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/** + * Panel for setting global object-editing options, specifically + * grid size and spacing. + */ + +#ifndef LL_LLFLOATERBUILDOPTIONS_H +#define LL_LLFLOATERBUILDOPTIONS_H + +#include "llfloater.h" +#include "llselectmgr.h" + +class LLObjectSelection; + +typedef LLSafeHandle LLObjectSelectionHandle; + +class LLFloaterBuildOptions + : public LLFloater +{ +public: + bool postBuild() override; + + void onOpen(const LLSD& key) override; + void onClose(bool app_quitting) override; + +private: + friend class LLFloaterReg; + + LLFloaterBuildOptions(const LLSD& key); + ~LLFloaterBuildOptions(); + + LLObjectSelectionHandle mObjectSelection; +}; +#endif diff --git a/indra/newview/llfloaterbulkpermission.cpp b/indra/newview/llfloaterbulkpermission.cpp index c57975194b..497fe3b9ba 100644 --- a/indra/newview/llfloaterbulkpermission.cpp +++ b/indra/newview/llfloaterbulkpermission.cpp @@ -1,417 +1,417 @@ -/** - * @file llfloaterbulkpermissions.cpp - * @author Michelle2 Zenovka - * @brief A floater which allows task inventory item's properties to be changed on mass. - * - * $LicenseInfo:firstyear=2008&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#include "llviewerprecompiledheaders.h" -#include "llfloaterbulkpermission.h" -#include "llfloaterperms.h" // for utilities -#include "llagent.h" -#include "llchat.h" -#include "llinventorydefines.h" -#include "llviewerwindow.h" -#include "llviewerobject.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llviewercontrol.h" -#include "llviewerinventory.h" -#include "llviewerobject.h" -#include "llviewerregion.h" -#include "llresmgr.h" -#include "llbutton.h" -#include "lldir.h" -#include "llviewerstats.h" -#include "lluictrlfactory.h" -#include "llselectmgr.h" -#include "llcheckboxctrl.h" - -#include "roles_constants.h" // for GP_OBJECT_MANIPULATE - - -LLFloaterBulkPermission::LLFloaterBulkPermission(const LLSD& seed) -: LLFloater(seed), - mDone(false) -{ - mID.generate(); - mCommitCallbackRegistrar.add("BulkPermission.Ok", boost::bind(&LLFloaterBulkPermission::onOkBtn, this)); - mCommitCallbackRegistrar.add("BulkPermission.Apply", boost::bind(&LLFloaterBulkPermission::onApplyBtn, this)); - mCommitCallbackRegistrar.add("BulkPermission.Close", boost::bind(&LLFloaterBulkPermission::onCloseBtn, this)); - mCommitCallbackRegistrar.add("BulkPermission.CheckAll", boost::bind(&LLFloaterBulkPermission::onCheckAll, this)); - mCommitCallbackRegistrar.add("BulkPermission.UncheckAll", boost::bind(&LLFloaterBulkPermission::onUncheckAll, this)); - mCommitCallbackRegistrar.add("BulkPermission.CommitCopy", boost::bind(&LLFloaterBulkPermission::onCommitCopy, this)); -} - -bool LLFloaterBulkPermission::postBuild() -{ - mBulkChangeIncludeAnimations = gSavedSettings.getBOOL("BulkChangeIncludeAnimations"); - mBulkChangeIncludeBodyParts = gSavedSettings.getBOOL("BulkChangeIncludeBodyParts"); - mBulkChangeIncludeClothing = gSavedSettings.getBOOL("BulkChangeIncludeClothing"); - mBulkChangeIncludeGestures = gSavedSettings.getBOOL("BulkChangeIncludeGestures"); - mBulkChangeIncludeNotecards = gSavedSettings.getBOOL("BulkChangeIncludeNotecards"); - mBulkChangeIncludeObjects = gSavedSettings.getBOOL("BulkChangeIncludeObjects"); - mBulkChangeIncludeScripts = gSavedSettings.getBOOL("BulkChangeIncludeScripts"); - mBulkChangeIncludeSounds = gSavedSettings.getBOOL("BulkChangeIncludeSounds"); - mBulkChangeIncludeTextures = gSavedSettings.getBOOL("BulkChangeIncludeTextures"); - mBulkChangeIncludeSettings = gSavedSettings.getBOOL("BulkChangeIncludeSettings"); - mBulkChangeIncludeMaterials = gSavedSettings.getBOOL("BulkChangeIncludeMaterials"); - - mBulkChangeShareWithGroup = gSavedSettings.getBOOL("BulkChangeShareWithGroup"); - mBulkChangeEveryoneCopy = gSavedSettings.getBOOL("BulkChangeEveryoneCopy"); - mBulkChangeNextOwnerModify = gSavedSettings.getBOOL("BulkChangeNextOwnerModify"); - mBulkChangeNextOwnerCopy = gSavedSettings.getBOOL("BulkChangeNextOwnerCopy"); - mBulkChangeNextOwnerTransfer = gSavedSettings.getBOOL("BulkChangeNextOwnerTransfer"); - - // fix invalid permissions case (in case initial settings were generated by a viewer affected by MAINT-3339) - if( !mBulkChangeNextOwnerTransfer && !mBulkChangeEveryoneCopy) - { - mBulkChangeNextOwnerTransfer = true; - } - return true; -} - -void LLFloaterBulkPermission::doApply() -{ - // Inspects a stream of selected object contents and adds modifiable ones to the given array. - class ModifiableGatherer : public LLSelectedNodeFunctor - { - public: - ModifiableGatherer(std::vector& q) : mQueue(q) { mQueue.reserve(32); } - virtual bool apply(LLSelectNode* node) - { - if( node->allowOperationOnNode(PERM_MODIFY, GP_OBJECT_MANIPULATE) ) - { - mQueue.push_back(node->getObject()->getID()); - } - return true; - } - private: - std::vector& mQueue; - }; - LLScrollListCtrl* list = getChild("queue output"); - list->deleteAllItems(); - ModifiableGatherer gatherer(mObjectIDs); - LLSelectMgr::getInstance()->getSelection()->applyToNodes(&gatherer); - if(mObjectIDs.empty()) - { - list->setCommentText(getString("nothing_to_modify_text")); - } - else - { - mDone = false; - if (!start()) - { - LL_WARNS() << "Unexpected bulk permission change failure." << LL_ENDL; - } - } -} - - -// This is the callback method for the viewer object currently being -// worked on. -// NOT static, virtual! -void LLFloaterBulkPermission::inventoryChanged(LLViewerObject* viewer_object, - LLInventoryObject::object_list_t* inv, - S32, - void* q_id) -{ - //LL_INFOS() << "changed object: " << viewer_object->getID() << LL_ENDL; - - //Remove this listener from the object since its - //listener callback is now being executed. - - //We remove the listener here because the function - //removeVOInventoryListener removes the listener from a ViewerObject - //which it internally stores. - - //If we call this further down in the function, calls to handleInventory - //and nextObject may update the interally stored viewer object causing - //the removal of the incorrect listener from an incorrect object. - - //Fixes SL-6119:Recompile scripts fails to complete - removeVOInventoryListener(); - - if (viewer_object && inv && (viewer_object->getID() == mCurrentObjectID) ) - { - handleInventory(viewer_object, inv); - } - else - { - // something went wrong... - // note that we're not working on this one, and move onto the - // next object in the list. - LL_WARNS() << "No inventory for " << mCurrentObjectID << LL_ENDL; - nextObject(); - } -} - -void LLFloaterBulkPermission::onOkBtn() -{ - doApply(); - closeFloater(); -} - -void LLFloaterBulkPermission::onApplyBtn() -{ - doApply(); -} - -void LLFloaterBulkPermission::onCloseBtn() -{ - gSavedSettings.setBOOL("BulkChangeIncludeAnimations", mBulkChangeIncludeAnimations); - gSavedSettings.setBOOL("BulkChangeIncludeBodyParts", mBulkChangeIncludeBodyParts); - gSavedSettings.setBOOL("BulkChangeIncludeClothing", mBulkChangeIncludeClothing); - gSavedSettings.setBOOL("BulkChangeIncludeGestures", mBulkChangeIncludeGestures); - gSavedSettings.setBOOL("BulkChangeIncludeNotecards", mBulkChangeIncludeNotecards); - gSavedSettings.setBOOL("BulkChangeIncludeObjects", mBulkChangeIncludeObjects); - gSavedSettings.setBOOL("BulkChangeIncludeScripts", mBulkChangeIncludeScripts); - gSavedSettings.setBOOL("BulkChangeIncludeSounds", mBulkChangeIncludeSounds); - gSavedSettings.setBOOL("BulkChangeIncludeTextures", mBulkChangeIncludeTextures); - gSavedSettings.setBOOL("BulkChangeIncludeSettings", mBulkChangeIncludeSettings); - gSavedSettings.setBOOL("BulkChangeIncludeMaterials", mBulkChangeIncludeMaterials); - - gSavedSettings.setBOOL("BulkChangeShareWithGroup", mBulkChangeShareWithGroup); - gSavedSettings.setBOOL("BulkChangeEveryoneCopy", mBulkChangeEveryoneCopy); - gSavedSettings.setBOOL("BulkChangeNextOwnerModify", mBulkChangeNextOwnerModify); - gSavedSettings.setBOOL("BulkChangeNextOwnerCopy", mBulkChangeNextOwnerCopy); - gSavedSettings.setBOOL("BulkChangeNextOwnerTransfer", mBulkChangeNextOwnerTransfer); - closeFloater(); -} - -//static -void LLFloaterBulkPermission::onCommitCopy() -{ - // Implements fair use - bool copyable = gSavedSettings.getBOOL("BulkChangeNextOwnerCopy"); - if(!copyable) - { - gSavedSettings.setBOOL("BulkChangeNextOwnerTransfer", true); - } - LLCheckBoxCtrl* xfer =getChild("next_owner_transfer"); - xfer->setEnabled(copyable); -} - -bool LLFloaterBulkPermission::start() -{ - // note: number of top-level objects to modify is mObjectIDs.size(). - getChild("queue output")->setCommentText(getString("start_text")); - return nextObject(); -} - -// Go to the next object and start if found. Returns false if no objects left, true otherwise. -bool LLFloaterBulkPermission::nextObject() -{ - S32 count; - bool successful_start = false; - do - { - count = mObjectIDs.size(); - //LL_INFOS() << "Objects left to process = " << count << LL_ENDL; - mCurrentObjectID.setNull(); - if(count > 0) - { - successful_start = popNext(); - //LL_INFOS() << (successful_start ? "successful" : "unsuccessful") << LL_ENDL; - } - } while((mObjectIDs.size() > 0) && !successful_start); - - if(isDone() && !mDone) - { - getChild("queue output")->setCommentText(getString("done_text")); - mDone = true; - } - return successful_start; -} - -// Pop the top object off of the queue. -// Return true if the queue has started, otherwise false. -bool LLFloaterBulkPermission::popNext() -{ - // get the head element from the container, and attempt to get its inventory. - bool rv = false; - S32 count = mObjectIDs.size(); - if(mCurrentObjectID.isNull() && (count > 0)) - { - mCurrentObjectID = mObjectIDs.at(0); - //LL_INFOS() << "mCurrentID: " << mCurrentObjectID << LL_ENDL; - mObjectIDs.erase(mObjectIDs.begin()); - LLViewerObject* obj = gObjectList.findObject(mCurrentObjectID); - if(obj) - { - //LL_INFOS() << "requesting inv for " << mCurrentObjectID << LL_ENDL; - LLUUID* id = new LLUUID(mID); - registerVOInventoryListener(obj,id); - requestVOInventory(); - rv = true; - } - else - { - LL_INFOS()<<"NULL LLViewerObject" <("queue output"); - - LLInventoryObject::object_list_t::const_iterator it = inv->begin(); - LLInventoryObject::object_list_t::const_iterator end = inv->end(); - for ( ; it != end; ++it) - { - LLAssetType::EType asstype = (*it)->getType(); - if( - ( asstype == LLAssetType::AT_ANIMATION && gSavedSettings.getBOOL("BulkChangeIncludeAnimations")) || - ( asstype == LLAssetType::AT_BODYPART && gSavedSettings.getBOOL("BulkChangeIncludeBodyParts" )) || - ( asstype == LLAssetType::AT_CLOTHING && gSavedSettings.getBOOL("BulkChangeIncludeClothing" )) || - ( asstype == LLAssetType::AT_GESTURE && gSavedSettings.getBOOL("BulkChangeIncludeGestures" )) || - ( asstype == LLAssetType::AT_NOTECARD && gSavedSettings.getBOOL("BulkChangeIncludeNotecards" )) || - ( asstype == LLAssetType::AT_OBJECT && gSavedSettings.getBOOL("BulkChangeIncludeObjects" )) || - ( asstype == LLAssetType::AT_LSL_TEXT && gSavedSettings.getBOOL("BulkChangeIncludeScripts" )) || - ( asstype == LLAssetType::AT_SOUND && gSavedSettings.getBOOL("BulkChangeIncludeSounds" )) || - ( asstype == LLAssetType::AT_SETTINGS && gSavedSettings.getBOOL("BulkChangeIncludeSettings" )) || - ( asstype == LLAssetType::AT_MATERIAL && gSavedSettings.getBOOL("BulkChangeIncludeMaterials")) || - ( asstype == LLAssetType::AT_TEXTURE && gSavedSettings.getBOOL("BulkChangeIncludeTextures" ))) - { - LLViewerObject* object = gObjectList.findObject(viewer_obj->getID()); - - if (object) - { - LLInventoryItem* item = (LLInventoryItem*)((LLInventoryObject*)(*it)); - LLViewerInventoryItem* new_item = (LLViewerInventoryItem*)item; - LLPermissions perm(new_item->getPermissions()); - - // chomp the inventory name so it fits in the scroll window nicely - // and the user can see the [OK] - std::string invname; - invname=item->getName().substr(0,item->getName().size() < 30 ? item->getName().size() : 30 ); - - LLUIString status_text = getString("status_text"); - status_text.setArg("[NAME]", invname.c_str()); - // Check whether we appear to have the appropriate permissions to change permission on this item. - // Although the server will disallow any forbidden changes, it is a good idea to guess correctly - // so that we can warn the user. The risk of getting this check wrong is therefore the possibility - // of incorrectly choosing to not attempt to make a valid change. - // - // Trouble is this is extremely difficult to do and even when we know the results - // it is difficult to design the best messaging. Therefore in this initial implementation - // we'll always try to set the requested permissions and consider all cases successful - // and perhaps later try to implement a smarter, friendlier solution. -MG - if(true - //gAgent.allowOperation(PERM_MODIFY, perm, GP_OBJECT_MANIPULATE) // for group and everyone masks - //|| something else // for next owner perms - ) - { - U32 mask_next = LLFloaterPerms::getNextOwnerPerms("BulkChange"); - if (asstype == LLAssetType::AT_SETTINGS) - { - mask_next |= PERM_COPY; - } - perm.setMaskNext(mask_next); - perm.setMaskEveryone(LLFloaterPerms::getEveryonePerms("BulkChange")); - perm.setMaskGroup(LLFloaterPerms::getGroupPerms("BulkChange")); - new_item->setPermissions(perm); // here's the beef - updateInventory(object,new_item,TASK_INVENTORY_ITEM_KEY,false); - //status_text.setArg("[STATUS]", getString("status_ok_text")); - status_text.setArg("[STATUS]", ""); - } - else - { - //status_text.setArg("[STATUS]", getString("status_bad_text")); - status_text.setArg("[STATUS]", ""); - } - - list->setCommentText(status_text.getString()); - - //TODO if we are an object inside an object we should check a recuse flag and if set - //open the inventory of the object and recurse - Michelle2 Zenovka - - // if(recurse && ( (*it)->getType() == LLAssetType::AT_OBJECT && processObject)) - // { - // I think we need to get the UUID of the object inside the inventory - // call item->fetchFromServer(); - // we need a call back to say item has arrived *sigh* - // we then need to do something like - // LLUUID* id = new LLUUID(mID); - // registerVOInventoryListener(obj,id); - // requestVOInventory(); - // } - } - } - } - - nextObject(); -} - - -// Avoid inventory callbacks etc by just fire and forgetting the message with the permissions update -// we could do this via LLViewerObject::updateInventory but that uses inventory call backs and buggers -// us up and we would have a dodgy item iterator - -void LLFloaterBulkPermission::updateInventory(LLViewerObject* object, LLViewerInventoryItem* item, U8 key, bool is_new) -{ - // This slices the object into what we're concerned about on the viewer. - // The simulator will take the permissions and transfer ownership. - LLPointer task_item = - new LLViewerInventoryItem(item->getUUID(), mID, item->getPermissions(), - item->getAssetUUID(), item->getType(), - item->getInventoryType(), - item->getName(), item->getDescription(), - item->getSaleInfo(), - item->getFlags(), - item->getCreationDate()); - task_item->setTransactionID(item->getTransactionID()); - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_UpdateTaskInventory); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_UpdateData); - msg->addU32Fast(_PREHASH_LocalID, object->mLocalID); - msg->addU8Fast(_PREHASH_Key, key); - msg->nextBlockFast(_PREHASH_InventoryData); - task_item->packMessage(msg); - msg->sendReliable(object->getRegion()->getHost()); -} - +/** + * @file llfloaterbulkpermissions.cpp + * @author Michelle2 Zenovka + * @brief A floater which allows task inventory item's properties to be changed on mass. + * + * $LicenseInfo:firstyear=2008&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#include "llviewerprecompiledheaders.h" +#include "llfloaterbulkpermission.h" +#include "llfloaterperms.h" // for utilities +#include "llagent.h" +#include "llchat.h" +#include "llinventorydefines.h" +#include "llviewerwindow.h" +#include "llviewerobject.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llviewercontrol.h" +#include "llviewerinventory.h" +#include "llviewerobject.h" +#include "llviewerregion.h" +#include "llresmgr.h" +#include "llbutton.h" +#include "lldir.h" +#include "llviewerstats.h" +#include "lluictrlfactory.h" +#include "llselectmgr.h" +#include "llcheckboxctrl.h" + +#include "roles_constants.h" // for GP_OBJECT_MANIPULATE + + +LLFloaterBulkPermission::LLFloaterBulkPermission(const LLSD& seed) +: LLFloater(seed), + mDone(false) +{ + mID.generate(); + mCommitCallbackRegistrar.add("BulkPermission.Ok", boost::bind(&LLFloaterBulkPermission::onOkBtn, this)); + mCommitCallbackRegistrar.add("BulkPermission.Apply", boost::bind(&LLFloaterBulkPermission::onApplyBtn, this)); + mCommitCallbackRegistrar.add("BulkPermission.Close", boost::bind(&LLFloaterBulkPermission::onCloseBtn, this)); + mCommitCallbackRegistrar.add("BulkPermission.CheckAll", boost::bind(&LLFloaterBulkPermission::onCheckAll, this)); + mCommitCallbackRegistrar.add("BulkPermission.UncheckAll", boost::bind(&LLFloaterBulkPermission::onUncheckAll, this)); + mCommitCallbackRegistrar.add("BulkPermission.CommitCopy", boost::bind(&LLFloaterBulkPermission::onCommitCopy, this)); +} + +bool LLFloaterBulkPermission::postBuild() +{ + mBulkChangeIncludeAnimations = gSavedSettings.getBOOL("BulkChangeIncludeAnimations"); + mBulkChangeIncludeBodyParts = gSavedSettings.getBOOL("BulkChangeIncludeBodyParts"); + mBulkChangeIncludeClothing = gSavedSettings.getBOOL("BulkChangeIncludeClothing"); + mBulkChangeIncludeGestures = gSavedSettings.getBOOL("BulkChangeIncludeGestures"); + mBulkChangeIncludeNotecards = gSavedSettings.getBOOL("BulkChangeIncludeNotecards"); + mBulkChangeIncludeObjects = gSavedSettings.getBOOL("BulkChangeIncludeObjects"); + mBulkChangeIncludeScripts = gSavedSettings.getBOOL("BulkChangeIncludeScripts"); + mBulkChangeIncludeSounds = gSavedSettings.getBOOL("BulkChangeIncludeSounds"); + mBulkChangeIncludeTextures = gSavedSettings.getBOOL("BulkChangeIncludeTextures"); + mBulkChangeIncludeSettings = gSavedSettings.getBOOL("BulkChangeIncludeSettings"); + mBulkChangeIncludeMaterials = gSavedSettings.getBOOL("BulkChangeIncludeMaterials"); + + mBulkChangeShareWithGroup = gSavedSettings.getBOOL("BulkChangeShareWithGroup"); + mBulkChangeEveryoneCopy = gSavedSettings.getBOOL("BulkChangeEveryoneCopy"); + mBulkChangeNextOwnerModify = gSavedSettings.getBOOL("BulkChangeNextOwnerModify"); + mBulkChangeNextOwnerCopy = gSavedSettings.getBOOL("BulkChangeNextOwnerCopy"); + mBulkChangeNextOwnerTransfer = gSavedSettings.getBOOL("BulkChangeNextOwnerTransfer"); + + // fix invalid permissions case (in case initial settings were generated by a viewer affected by MAINT-3339) + if( !mBulkChangeNextOwnerTransfer && !mBulkChangeEveryoneCopy) + { + mBulkChangeNextOwnerTransfer = true; + } + return true; +} + +void LLFloaterBulkPermission::doApply() +{ + // Inspects a stream of selected object contents and adds modifiable ones to the given array. + class ModifiableGatherer : public LLSelectedNodeFunctor + { + public: + ModifiableGatherer(std::vector& q) : mQueue(q) { mQueue.reserve(32); } + virtual bool apply(LLSelectNode* node) + { + if( node->allowOperationOnNode(PERM_MODIFY, GP_OBJECT_MANIPULATE) ) + { + mQueue.push_back(node->getObject()->getID()); + } + return true; + } + private: + std::vector& mQueue; + }; + LLScrollListCtrl* list = getChild("queue output"); + list->deleteAllItems(); + ModifiableGatherer gatherer(mObjectIDs); + LLSelectMgr::getInstance()->getSelection()->applyToNodes(&gatherer); + if(mObjectIDs.empty()) + { + list->setCommentText(getString("nothing_to_modify_text")); + } + else + { + mDone = false; + if (!start()) + { + LL_WARNS() << "Unexpected bulk permission change failure." << LL_ENDL; + } + } +} + + +// This is the callback method for the viewer object currently being +// worked on. +// NOT static, virtual! +void LLFloaterBulkPermission::inventoryChanged(LLViewerObject* viewer_object, + LLInventoryObject::object_list_t* inv, + S32, + void* q_id) +{ + //LL_INFOS() << "changed object: " << viewer_object->getID() << LL_ENDL; + + //Remove this listener from the object since its + //listener callback is now being executed. + + //We remove the listener here because the function + //removeVOInventoryListener removes the listener from a ViewerObject + //which it internally stores. + + //If we call this further down in the function, calls to handleInventory + //and nextObject may update the interally stored viewer object causing + //the removal of the incorrect listener from an incorrect object. + + //Fixes SL-6119:Recompile scripts fails to complete + removeVOInventoryListener(); + + if (viewer_object && inv && (viewer_object->getID() == mCurrentObjectID) ) + { + handleInventory(viewer_object, inv); + } + else + { + // something went wrong... + // note that we're not working on this one, and move onto the + // next object in the list. + LL_WARNS() << "No inventory for " << mCurrentObjectID << LL_ENDL; + nextObject(); + } +} + +void LLFloaterBulkPermission::onOkBtn() +{ + doApply(); + closeFloater(); +} + +void LLFloaterBulkPermission::onApplyBtn() +{ + doApply(); +} + +void LLFloaterBulkPermission::onCloseBtn() +{ + gSavedSettings.setBOOL("BulkChangeIncludeAnimations", mBulkChangeIncludeAnimations); + gSavedSettings.setBOOL("BulkChangeIncludeBodyParts", mBulkChangeIncludeBodyParts); + gSavedSettings.setBOOL("BulkChangeIncludeClothing", mBulkChangeIncludeClothing); + gSavedSettings.setBOOL("BulkChangeIncludeGestures", mBulkChangeIncludeGestures); + gSavedSettings.setBOOL("BulkChangeIncludeNotecards", mBulkChangeIncludeNotecards); + gSavedSettings.setBOOL("BulkChangeIncludeObjects", mBulkChangeIncludeObjects); + gSavedSettings.setBOOL("BulkChangeIncludeScripts", mBulkChangeIncludeScripts); + gSavedSettings.setBOOL("BulkChangeIncludeSounds", mBulkChangeIncludeSounds); + gSavedSettings.setBOOL("BulkChangeIncludeTextures", mBulkChangeIncludeTextures); + gSavedSettings.setBOOL("BulkChangeIncludeSettings", mBulkChangeIncludeSettings); + gSavedSettings.setBOOL("BulkChangeIncludeMaterials", mBulkChangeIncludeMaterials); + + gSavedSettings.setBOOL("BulkChangeShareWithGroup", mBulkChangeShareWithGroup); + gSavedSettings.setBOOL("BulkChangeEveryoneCopy", mBulkChangeEveryoneCopy); + gSavedSettings.setBOOL("BulkChangeNextOwnerModify", mBulkChangeNextOwnerModify); + gSavedSettings.setBOOL("BulkChangeNextOwnerCopy", mBulkChangeNextOwnerCopy); + gSavedSettings.setBOOL("BulkChangeNextOwnerTransfer", mBulkChangeNextOwnerTransfer); + closeFloater(); +} + +//static +void LLFloaterBulkPermission::onCommitCopy() +{ + // Implements fair use + bool copyable = gSavedSettings.getBOOL("BulkChangeNextOwnerCopy"); + if(!copyable) + { + gSavedSettings.setBOOL("BulkChangeNextOwnerTransfer", true); + } + LLCheckBoxCtrl* xfer =getChild("next_owner_transfer"); + xfer->setEnabled(copyable); +} + +bool LLFloaterBulkPermission::start() +{ + // note: number of top-level objects to modify is mObjectIDs.size(). + getChild("queue output")->setCommentText(getString("start_text")); + return nextObject(); +} + +// Go to the next object and start if found. Returns false if no objects left, true otherwise. +bool LLFloaterBulkPermission::nextObject() +{ + S32 count; + bool successful_start = false; + do + { + count = mObjectIDs.size(); + //LL_INFOS() << "Objects left to process = " << count << LL_ENDL; + mCurrentObjectID.setNull(); + if(count > 0) + { + successful_start = popNext(); + //LL_INFOS() << (successful_start ? "successful" : "unsuccessful") << LL_ENDL; + } + } while((mObjectIDs.size() > 0) && !successful_start); + + if(isDone() && !mDone) + { + getChild("queue output")->setCommentText(getString("done_text")); + mDone = true; + } + return successful_start; +} + +// Pop the top object off of the queue. +// Return true if the queue has started, otherwise false. +bool LLFloaterBulkPermission::popNext() +{ + // get the head element from the container, and attempt to get its inventory. + bool rv = false; + S32 count = mObjectIDs.size(); + if(mCurrentObjectID.isNull() && (count > 0)) + { + mCurrentObjectID = mObjectIDs.at(0); + //LL_INFOS() << "mCurrentID: " << mCurrentObjectID << LL_ENDL; + mObjectIDs.erase(mObjectIDs.begin()); + LLViewerObject* obj = gObjectList.findObject(mCurrentObjectID); + if(obj) + { + //LL_INFOS() << "requesting inv for " << mCurrentObjectID << LL_ENDL; + LLUUID* id = new LLUUID(mID); + registerVOInventoryListener(obj,id); + requestVOInventory(); + rv = true; + } + else + { + LL_INFOS()<<"NULL LLViewerObject" <("queue output"); + + LLInventoryObject::object_list_t::const_iterator it = inv->begin(); + LLInventoryObject::object_list_t::const_iterator end = inv->end(); + for ( ; it != end; ++it) + { + LLAssetType::EType asstype = (*it)->getType(); + if( + ( asstype == LLAssetType::AT_ANIMATION && gSavedSettings.getBOOL("BulkChangeIncludeAnimations")) || + ( asstype == LLAssetType::AT_BODYPART && gSavedSettings.getBOOL("BulkChangeIncludeBodyParts" )) || + ( asstype == LLAssetType::AT_CLOTHING && gSavedSettings.getBOOL("BulkChangeIncludeClothing" )) || + ( asstype == LLAssetType::AT_GESTURE && gSavedSettings.getBOOL("BulkChangeIncludeGestures" )) || + ( asstype == LLAssetType::AT_NOTECARD && gSavedSettings.getBOOL("BulkChangeIncludeNotecards" )) || + ( asstype == LLAssetType::AT_OBJECT && gSavedSettings.getBOOL("BulkChangeIncludeObjects" )) || + ( asstype == LLAssetType::AT_LSL_TEXT && gSavedSettings.getBOOL("BulkChangeIncludeScripts" )) || + ( asstype == LLAssetType::AT_SOUND && gSavedSettings.getBOOL("BulkChangeIncludeSounds" )) || + ( asstype == LLAssetType::AT_SETTINGS && gSavedSettings.getBOOL("BulkChangeIncludeSettings" )) || + ( asstype == LLAssetType::AT_MATERIAL && gSavedSettings.getBOOL("BulkChangeIncludeMaterials")) || + ( asstype == LLAssetType::AT_TEXTURE && gSavedSettings.getBOOL("BulkChangeIncludeTextures" ))) + { + LLViewerObject* object = gObjectList.findObject(viewer_obj->getID()); + + if (object) + { + LLInventoryItem* item = (LLInventoryItem*)((LLInventoryObject*)(*it)); + LLViewerInventoryItem* new_item = (LLViewerInventoryItem*)item; + LLPermissions perm(new_item->getPermissions()); + + // chomp the inventory name so it fits in the scroll window nicely + // and the user can see the [OK] + std::string invname; + invname=item->getName().substr(0,item->getName().size() < 30 ? item->getName().size() : 30 ); + + LLUIString status_text = getString("status_text"); + status_text.setArg("[NAME]", invname.c_str()); + // Check whether we appear to have the appropriate permissions to change permission on this item. + // Although the server will disallow any forbidden changes, it is a good idea to guess correctly + // so that we can warn the user. The risk of getting this check wrong is therefore the possibility + // of incorrectly choosing to not attempt to make a valid change. + // + // Trouble is this is extremely difficult to do and even when we know the results + // it is difficult to design the best messaging. Therefore in this initial implementation + // we'll always try to set the requested permissions and consider all cases successful + // and perhaps later try to implement a smarter, friendlier solution. -MG + if(true + //gAgent.allowOperation(PERM_MODIFY, perm, GP_OBJECT_MANIPULATE) // for group and everyone masks + //|| something else // for next owner perms + ) + { + U32 mask_next = LLFloaterPerms::getNextOwnerPerms("BulkChange"); + if (asstype == LLAssetType::AT_SETTINGS) + { + mask_next |= PERM_COPY; + } + perm.setMaskNext(mask_next); + perm.setMaskEveryone(LLFloaterPerms::getEveryonePerms("BulkChange")); + perm.setMaskGroup(LLFloaterPerms::getGroupPerms("BulkChange")); + new_item->setPermissions(perm); // here's the beef + updateInventory(object,new_item,TASK_INVENTORY_ITEM_KEY,false); + //status_text.setArg("[STATUS]", getString("status_ok_text")); + status_text.setArg("[STATUS]", ""); + } + else + { + //status_text.setArg("[STATUS]", getString("status_bad_text")); + status_text.setArg("[STATUS]", ""); + } + + list->setCommentText(status_text.getString()); + + //TODO if we are an object inside an object we should check a recuse flag and if set + //open the inventory of the object and recurse - Michelle2 Zenovka + + // if(recurse && ( (*it)->getType() == LLAssetType::AT_OBJECT && processObject)) + // { + // I think we need to get the UUID of the object inside the inventory + // call item->fetchFromServer(); + // we need a call back to say item has arrived *sigh* + // we then need to do something like + // LLUUID* id = new LLUUID(mID); + // registerVOInventoryListener(obj,id); + // requestVOInventory(); + // } + } + } + } + + nextObject(); +} + + +// Avoid inventory callbacks etc by just fire and forgetting the message with the permissions update +// we could do this via LLViewerObject::updateInventory but that uses inventory call backs and buggers +// us up and we would have a dodgy item iterator + +void LLFloaterBulkPermission::updateInventory(LLViewerObject* object, LLViewerInventoryItem* item, U8 key, bool is_new) +{ + // This slices the object into what we're concerned about on the viewer. + // The simulator will take the permissions and transfer ownership. + LLPointer task_item = + new LLViewerInventoryItem(item->getUUID(), mID, item->getPermissions(), + item->getAssetUUID(), item->getType(), + item->getInventoryType(), + item->getName(), item->getDescription(), + item->getSaleInfo(), + item->getFlags(), + item->getCreationDate()); + task_item->setTransactionID(item->getTransactionID()); + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_UpdateTaskInventory); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_UpdateData); + msg->addU32Fast(_PREHASH_LocalID, object->mLocalID); + msg->addU8Fast(_PREHASH_Key, key); + msg->nextBlockFast(_PREHASH_InventoryData); + task_item->packMessage(msg); + msg->sendReliable(object->getRegion()->getHost()); +} + diff --git a/indra/newview/llfloaterbulkpermission.h b/indra/newview/llfloaterbulkpermission.h index 6bf30f001d..23ca45b611 100644 --- a/indra/newview/llfloaterbulkpermission.h +++ b/indra/newview/llfloaterbulkpermission.h @@ -1,120 +1,120 @@ -/** - * @file llfloaterbulkpermissions.h - * @brief Allow multiple task inventory properties to be set in one go. - * @author Michelle2 Zenovka - * - * $LicenseInfo:firstyear=2008&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLBULKPERMISSION_H -#define LL_LLBULKPERMISSION_H - -#include "llinventory.h" -#include "llviewerobject.h" -#include "llvoinventorylistener.h" -#include "lluuid.h" - -#include "llfloater.h" -#include "llscrolllistctrl.h" - -class LLFloaterBulkPermission : public LLFloater, public LLVOInventoryListener -{ - friend class LLFloaterReg; -public: - - bool postBuild(); - -private: - - LLFloaterBulkPermission(const LLSD& seed); - virtual ~LLFloaterBulkPermission() {} - - bool start(); // returns true if the queue has started, otherwise false. - bool nextObject(); - bool popNext(); - - // This is the callback method for the viewer object currently - // being worked on. - /*virtual*/ void inventoryChanged(LLViewerObject* obj, - LLInventoryObject::object_list_t* inv, - S32 serial_num, - void* queue); - - // This is called by inventoryChanged - void handleInventory(LLViewerObject* viewer_obj, - LLInventoryObject::object_list_t* inv); - - - void updateInventory(LLViewerObject* object, - LLViewerInventoryItem* item, - U8 key, - bool is_new); - - void onCloseBtn(); - void onOkBtn(); - void onApplyBtn(); - void onCommitCopy(); - void onCheckAll() { doCheckUncheckAll(true); } - void onUncheckAll() { doCheckUncheckAll(false); } - - // returns true if this is done - bool isDone() const { return (mCurrentObjectID.isNull() || (mObjectIDs.size() == 0)); } - - //Read the settings and Apply the permissions - void doApply(); - void doCheckUncheckAll(bool check); - -private: - // UI - LLScrollListCtrl* mMessages; - LLButton* mCloseBtn; - - // Object Queue - std::vector mObjectIDs; - LLUUID mCurrentObjectID; - bool mDone; - - bool mBulkChangeIncludeAnimations; - bool mBulkChangeIncludeBodyParts; - bool mBulkChangeIncludeClothing; - bool mBulkChangeIncludeGestures; - bool mBulkChangeIncludeNotecards; - bool mBulkChangeIncludeObjects; - bool mBulkChangeIncludeScripts; - bool mBulkChangeIncludeSounds; - bool mBulkChangeIncludeTextures; - bool mBulkChangeIncludeSettings; - bool mBulkChangeIncludeMaterials; - - bool mBulkChangeShareWithGroup; - bool mBulkChangeEveryoneCopy; - bool mBulkChangeNextOwnerModify; - bool mBulkChangeNextOwnerCopy; - bool mBulkChangeNextOwnerTransfer; - - LLUUID mID; - - const char* mStartString; -}; - -#endif - +/** + * @file llfloaterbulkpermissions.h + * @brief Allow multiple task inventory properties to be set in one go. + * @author Michelle2 Zenovka + * + * $LicenseInfo:firstyear=2008&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLBULKPERMISSION_H +#define LL_LLBULKPERMISSION_H + +#include "llinventory.h" +#include "llviewerobject.h" +#include "llvoinventorylistener.h" +#include "lluuid.h" + +#include "llfloater.h" +#include "llscrolllistctrl.h" + +class LLFloaterBulkPermission : public LLFloater, public LLVOInventoryListener +{ + friend class LLFloaterReg; +public: + + bool postBuild(); + +private: + + LLFloaterBulkPermission(const LLSD& seed); + virtual ~LLFloaterBulkPermission() {} + + bool start(); // returns true if the queue has started, otherwise false. + bool nextObject(); + bool popNext(); + + // This is the callback method for the viewer object currently + // being worked on. + /*virtual*/ void inventoryChanged(LLViewerObject* obj, + LLInventoryObject::object_list_t* inv, + S32 serial_num, + void* queue); + + // This is called by inventoryChanged + void handleInventory(LLViewerObject* viewer_obj, + LLInventoryObject::object_list_t* inv); + + + void updateInventory(LLViewerObject* object, + LLViewerInventoryItem* item, + U8 key, + bool is_new); + + void onCloseBtn(); + void onOkBtn(); + void onApplyBtn(); + void onCommitCopy(); + void onCheckAll() { doCheckUncheckAll(true); } + void onUncheckAll() { doCheckUncheckAll(false); } + + // returns true if this is done + bool isDone() const { return (mCurrentObjectID.isNull() || (mObjectIDs.size() == 0)); } + + //Read the settings and Apply the permissions + void doApply(); + void doCheckUncheckAll(bool check); + +private: + // UI + LLScrollListCtrl* mMessages; + LLButton* mCloseBtn; + + // Object Queue + std::vector mObjectIDs; + LLUUID mCurrentObjectID; + bool mDone; + + bool mBulkChangeIncludeAnimations; + bool mBulkChangeIncludeBodyParts; + bool mBulkChangeIncludeClothing; + bool mBulkChangeIncludeGestures; + bool mBulkChangeIncludeNotecards; + bool mBulkChangeIncludeObjects; + bool mBulkChangeIncludeScripts; + bool mBulkChangeIncludeSounds; + bool mBulkChangeIncludeTextures; + bool mBulkChangeIncludeSettings; + bool mBulkChangeIncludeMaterials; + + bool mBulkChangeShareWithGroup; + bool mBulkChangeEveryoneCopy; + bool mBulkChangeNextOwnerModify; + bool mBulkChangeNextOwnerCopy; + bool mBulkChangeNextOwnerTransfer; + + LLUUID mID; + + const char* mStartString; +}; + +#endif + diff --git a/indra/newview/llfloaterbump.cpp b/indra/newview/llfloaterbump.cpp index f3c1b1eea5..162ad5e108 100644 --- a/indra/newview/llfloaterbump.cpp +++ b/indra/newview/llfloaterbump.cpp @@ -1,274 +1,274 @@ -/** - * @file llfloaterbump.cpp - * @brief Floater showing recent bumps, hits with objects, pushes, etc. - * @author Cory Ondrejka, James Cook - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llsd.h" -#include "mean_collision_data.h" - -#include "llavataractions.h" -#include "llfloaterbump.h" -#include "llfloaterreg.h" -#include "llfloaterreporter.h" -#include "llmutelist.h" -#include "llpanelblockedlist.h" -#include "llscrolllistctrl.h" -#include "lltrans.h" -#include "lluictrlfactory.h" -#include "llviewermessage.h" -#include "llviewermenu.h" -#include "llviewerobjectlist.h" - -///---------------------------------------------------------------------------- -/// Class LLFloaterBump -///---------------------------------------------------------------------------- - -// Default constructor -LLFloaterBump::LLFloaterBump(const LLSD& key) -: LLFloater(key) -{ - mCommitCallbackRegistrar.add("Avatar.SendIM", boost::bind(&LLFloaterBump::startIM, this)); - mCommitCallbackRegistrar.add("Avatar.ReportAbuse", boost::bind(&LLFloaterBump::reportAbuse, this)); - mCommitCallbackRegistrar.add("ShowAgentProfile", boost::bind(&LLFloaterBump::showProfile, this)); - mCommitCallbackRegistrar.add("Avatar.InviteToGroup", boost::bind(&LLFloaterBump::inviteToGroup, this)); - mCommitCallbackRegistrar.add("Avatar.Call", boost::bind(&LLFloaterBump::startCall, this)); - mEnableCallbackRegistrar.add("Avatar.EnableCall", boost::bind(&LLAvatarActions::canCall)); - mCommitCallbackRegistrar.add("Avatar.AddFriend", boost::bind(&LLFloaterBump::addFriend, this)); - mEnableCallbackRegistrar.add("Avatar.EnableAddFriend", boost::bind(&LLFloaterBump::enableAddFriend, this)); - mCommitCallbackRegistrar.add("Avatar.Mute", boost::bind(&LLFloaterBump::muteAvatar, this)); - mEnableCallbackRegistrar.add("Avatar.EnableMute", boost::bind(&LLFloaterBump::enableMute, this)); - mCommitCallbackRegistrar.add("PayObject", boost::bind(&LLFloaterBump::payAvatar, this)); - mCommitCallbackRegistrar.add("Tools.LookAtSelection", boost::bind(&LLFloaterBump::zoomInAvatar, this)); -} - - -// Destroys the object -LLFloaterBump::~LLFloaterBump() -{ - auto menu = mPopupMenuHandle.get(); - if (menu) - { - menu->die(); - mPopupMenuHandle.markDead(); - } -} - -bool LLFloaterBump::postBuild() -{ - mList = getChild("bump_list"); - mList->setAllowMultipleSelection(false); - mList->setRightMouseDownCallback(boost::bind(&LLFloaterBump::onScrollListRightClicked, this, _1, _2, _3)); - - LLContextMenu* menu = LLUICtrlFactory::getInstance()->createFromFile("menu_avatar_other.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - if (menu) - { - mPopupMenuHandle = menu->getHandle(); - menu->setItemVisible(std::string("Normal"), false); - menu->setItemVisible(std::string("Always use impostor"), false); - menu->setItemVisible(std::string("Never use impostor"), false); - menu->setItemVisible(std::string("Impostor seperator"), false); - } - - return true; -} -// virtual -void LLFloaterBump::onOpen(const LLSD& key) -{ - if (gMeanCollisionList.empty()) - { - mNames.clear(); - mList->deleteAllItems(); - - std::string none_detected = getString("none_detected"); - LLSD row; - row["columns"][0]["value"] = none_detected; - row["columns"][0]["font"] = "SansSerifBold"; - mList->addElement(row); - } - else - { - populateCollisionList(); - } -} - -void LLFloaterBump::populateCollisionList() -{ - mNames.clear(); - mList->deleteAllItems(); - - for (mean_collision_list_t::iterator iter = gMeanCollisionList.begin(); - iter != gMeanCollisionList.end(); ++iter) - { - LLMeanCollisionData *mcd = *iter; - add(mList, mcd); - } -} - -void LLFloaterBump::add(LLScrollListCtrl* list, LLMeanCollisionData* mcd) -{ - if (mcd->mFullName.empty() || list->getItemCount() >= 20) - { - return; - } - - std::string timeStr = getString ("timeStr"); - LLSD substitution; - - substitution["datetime"] = (S32) mcd->mTime; - LLStringUtil::format (timeStr, substitution); - - std::string action; - switch(mcd->mType) - { - case MEAN_BUMP: - action = "bump"; - break; - case MEAN_LLPUSHOBJECT: - action = "llpushobject"; - break; - case MEAN_SELECTED_OBJECT_COLLIDE: - action = "selected_object_collide"; - break; - case MEAN_SCRIPTED_OBJECT_COLLIDE: - action = "scripted_object_collide"; - break; - case MEAN_PHYSICAL_OBJECT_COLLIDE: - action = "physical_object_collide"; - break; - default: - LL_INFOS() << "LLFloaterBump::add unknown mean collision type " - << mcd->mType << LL_ENDL; - return; - } - - // All above action strings are in XML file - LLUIString text = getString(action); - text.setArg("[TIME]", timeStr); - text.setArg("[NAME]", mcd->mFullName); - - LLSD row; - row["id"] = mcd->mPerp; - row["columns"][0]["value"] = text; - row["columns"][0]["font"] = "SansSerifBold"; - list->addElement(row); - - - mNames[mcd->mPerp] = mcd->mFullName; -} - - -void LLFloaterBump::onScrollListRightClicked(LLUICtrl* ctrl, S32 x, S32 y) -{ - if (!gMeanCollisionList.empty()) - { - LLScrollListItem* item = mList->hitItem(x, y); - auto menu = mPopupMenuHandle.get(); - if (item && menu) - { - mItemUUID = item->getUUID(); - menu->buildDrawLabels(); - menu->updateParent(LLMenuGL::sMenuContainer); - - std::string mute_msg = (LLMuteList::getInstance()->isMuted(mItemUUID, mNames[mItemUUID])) ? "UnmuteAvatar" : "MuteAvatar"; - menu->getChild("Avatar Mute")->setValue(LLTrans::getString(mute_msg)); - menu->setItemEnabled(std::string("Zoom In"), bool(gObjectList.findObject(mItemUUID))); - - menu->show(x, y); - LLMenuGL::showPopup(ctrl, menu, x, y); - } - } -} - - -void LLFloaterBump::startIM() -{ - LLAvatarActions::startIM(mItemUUID); -} - -void LLFloaterBump::startCall() -{ - LLAvatarActions::startCall(mItemUUID); -} - -void LLFloaterBump::reportAbuse() -{ - LLFloaterReporter::showFromAvatar(mItemUUID, "av_name"); -} - -void LLFloaterBump::showProfile() -{ - LLAvatarActions::showProfile(mItemUUID); -} - -void LLFloaterBump::addFriend() -{ - LLAvatarActions::requestFriendshipDialog(mItemUUID); -} - -bool LLFloaterBump::enableAddFriend() -{ - return !LLAvatarActions::isFriend(mItemUUID); -} - -void LLFloaterBump::muteAvatar() -{ - LLMute mute(mItemUUID, mNames[mItemUUID], LLMute::AGENT); - if (LLMuteList::getInstance()->isMuted(mute.mID)) - { - LLMuteList::getInstance()->remove(mute); - } - else - { - LLMuteList::getInstance()->add(mute); - LLPanelBlockedList::showPanelAndSelect(mute.mID); - } -} - -void LLFloaterBump::payAvatar() -{ - LLAvatarActions::pay(mItemUUID); -} - -void LLFloaterBump::zoomInAvatar() -{ - handle_zoom_to_object(mItemUUID); -} - -bool LLFloaterBump::enableMute() -{ - return LLAvatarActions::canBlock(mItemUUID); -} - -void LLFloaterBump::inviteToGroup() -{ - LLAvatarActions::inviteToGroup(mItemUUID); -} - -LLFloaterBump* LLFloaterBump::getInstance() -{ - return LLFloaterReg::getTypedInstance("bumps"); -} +/** + * @file llfloaterbump.cpp + * @brief Floater showing recent bumps, hits with objects, pushes, etc. + * @author Cory Ondrejka, James Cook + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llsd.h" +#include "mean_collision_data.h" + +#include "llavataractions.h" +#include "llfloaterbump.h" +#include "llfloaterreg.h" +#include "llfloaterreporter.h" +#include "llmutelist.h" +#include "llpanelblockedlist.h" +#include "llscrolllistctrl.h" +#include "lltrans.h" +#include "lluictrlfactory.h" +#include "llviewermessage.h" +#include "llviewermenu.h" +#include "llviewerobjectlist.h" + +///---------------------------------------------------------------------------- +/// Class LLFloaterBump +///---------------------------------------------------------------------------- + +// Default constructor +LLFloaterBump::LLFloaterBump(const LLSD& key) +: LLFloater(key) +{ + mCommitCallbackRegistrar.add("Avatar.SendIM", boost::bind(&LLFloaterBump::startIM, this)); + mCommitCallbackRegistrar.add("Avatar.ReportAbuse", boost::bind(&LLFloaterBump::reportAbuse, this)); + mCommitCallbackRegistrar.add("ShowAgentProfile", boost::bind(&LLFloaterBump::showProfile, this)); + mCommitCallbackRegistrar.add("Avatar.InviteToGroup", boost::bind(&LLFloaterBump::inviteToGroup, this)); + mCommitCallbackRegistrar.add("Avatar.Call", boost::bind(&LLFloaterBump::startCall, this)); + mEnableCallbackRegistrar.add("Avatar.EnableCall", boost::bind(&LLAvatarActions::canCall)); + mCommitCallbackRegistrar.add("Avatar.AddFriend", boost::bind(&LLFloaterBump::addFriend, this)); + mEnableCallbackRegistrar.add("Avatar.EnableAddFriend", boost::bind(&LLFloaterBump::enableAddFriend, this)); + mCommitCallbackRegistrar.add("Avatar.Mute", boost::bind(&LLFloaterBump::muteAvatar, this)); + mEnableCallbackRegistrar.add("Avatar.EnableMute", boost::bind(&LLFloaterBump::enableMute, this)); + mCommitCallbackRegistrar.add("PayObject", boost::bind(&LLFloaterBump::payAvatar, this)); + mCommitCallbackRegistrar.add("Tools.LookAtSelection", boost::bind(&LLFloaterBump::zoomInAvatar, this)); +} + + +// Destroys the object +LLFloaterBump::~LLFloaterBump() +{ + auto menu = mPopupMenuHandle.get(); + if (menu) + { + menu->die(); + mPopupMenuHandle.markDead(); + } +} + +bool LLFloaterBump::postBuild() +{ + mList = getChild("bump_list"); + mList->setAllowMultipleSelection(false); + mList->setRightMouseDownCallback(boost::bind(&LLFloaterBump::onScrollListRightClicked, this, _1, _2, _3)); + + LLContextMenu* menu = LLUICtrlFactory::getInstance()->createFromFile("menu_avatar_other.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + if (menu) + { + mPopupMenuHandle = menu->getHandle(); + menu->setItemVisible(std::string("Normal"), false); + menu->setItemVisible(std::string("Always use impostor"), false); + menu->setItemVisible(std::string("Never use impostor"), false); + menu->setItemVisible(std::string("Impostor seperator"), false); + } + + return true; +} +// virtual +void LLFloaterBump::onOpen(const LLSD& key) +{ + if (gMeanCollisionList.empty()) + { + mNames.clear(); + mList->deleteAllItems(); + + std::string none_detected = getString("none_detected"); + LLSD row; + row["columns"][0]["value"] = none_detected; + row["columns"][0]["font"] = "SansSerifBold"; + mList->addElement(row); + } + else + { + populateCollisionList(); + } +} + +void LLFloaterBump::populateCollisionList() +{ + mNames.clear(); + mList->deleteAllItems(); + + for (mean_collision_list_t::iterator iter = gMeanCollisionList.begin(); + iter != gMeanCollisionList.end(); ++iter) + { + LLMeanCollisionData *mcd = *iter; + add(mList, mcd); + } +} + +void LLFloaterBump::add(LLScrollListCtrl* list, LLMeanCollisionData* mcd) +{ + if (mcd->mFullName.empty() || list->getItemCount() >= 20) + { + return; + } + + std::string timeStr = getString ("timeStr"); + LLSD substitution; + + substitution["datetime"] = (S32) mcd->mTime; + LLStringUtil::format (timeStr, substitution); + + std::string action; + switch(mcd->mType) + { + case MEAN_BUMP: + action = "bump"; + break; + case MEAN_LLPUSHOBJECT: + action = "llpushobject"; + break; + case MEAN_SELECTED_OBJECT_COLLIDE: + action = "selected_object_collide"; + break; + case MEAN_SCRIPTED_OBJECT_COLLIDE: + action = "scripted_object_collide"; + break; + case MEAN_PHYSICAL_OBJECT_COLLIDE: + action = "physical_object_collide"; + break; + default: + LL_INFOS() << "LLFloaterBump::add unknown mean collision type " + << mcd->mType << LL_ENDL; + return; + } + + // All above action strings are in XML file + LLUIString text = getString(action); + text.setArg("[TIME]", timeStr); + text.setArg("[NAME]", mcd->mFullName); + + LLSD row; + row["id"] = mcd->mPerp; + row["columns"][0]["value"] = text; + row["columns"][0]["font"] = "SansSerifBold"; + list->addElement(row); + + + mNames[mcd->mPerp] = mcd->mFullName; +} + + +void LLFloaterBump::onScrollListRightClicked(LLUICtrl* ctrl, S32 x, S32 y) +{ + if (!gMeanCollisionList.empty()) + { + LLScrollListItem* item = mList->hitItem(x, y); + auto menu = mPopupMenuHandle.get(); + if (item && menu) + { + mItemUUID = item->getUUID(); + menu->buildDrawLabels(); + menu->updateParent(LLMenuGL::sMenuContainer); + + std::string mute_msg = (LLMuteList::getInstance()->isMuted(mItemUUID, mNames[mItemUUID])) ? "UnmuteAvatar" : "MuteAvatar"; + menu->getChild("Avatar Mute")->setValue(LLTrans::getString(mute_msg)); + menu->setItemEnabled(std::string("Zoom In"), bool(gObjectList.findObject(mItemUUID))); + + menu->show(x, y); + LLMenuGL::showPopup(ctrl, menu, x, y); + } + } +} + + +void LLFloaterBump::startIM() +{ + LLAvatarActions::startIM(mItemUUID); +} + +void LLFloaterBump::startCall() +{ + LLAvatarActions::startCall(mItemUUID); +} + +void LLFloaterBump::reportAbuse() +{ + LLFloaterReporter::showFromAvatar(mItemUUID, "av_name"); +} + +void LLFloaterBump::showProfile() +{ + LLAvatarActions::showProfile(mItemUUID); +} + +void LLFloaterBump::addFriend() +{ + LLAvatarActions::requestFriendshipDialog(mItemUUID); +} + +bool LLFloaterBump::enableAddFriend() +{ + return !LLAvatarActions::isFriend(mItemUUID); +} + +void LLFloaterBump::muteAvatar() +{ + LLMute mute(mItemUUID, mNames[mItemUUID], LLMute::AGENT); + if (LLMuteList::getInstance()->isMuted(mute.mID)) + { + LLMuteList::getInstance()->remove(mute); + } + else + { + LLMuteList::getInstance()->add(mute); + LLPanelBlockedList::showPanelAndSelect(mute.mID); + } +} + +void LLFloaterBump::payAvatar() +{ + LLAvatarActions::pay(mItemUUID); +} + +void LLFloaterBump::zoomInAvatar() +{ + handle_zoom_to_object(mItemUUID); +} + +bool LLFloaterBump::enableMute() +{ + return LLAvatarActions::canBlock(mItemUUID); +} + +void LLFloaterBump::inviteToGroup() +{ + LLAvatarActions::inviteToGroup(mItemUUID); +} + +LLFloaterBump* LLFloaterBump::getInstance() +{ + return LLFloaterReg::getTypedInstance("bumps"); +} diff --git a/indra/newview/llfloaterbump.h b/indra/newview/llfloaterbump.h index 90a0601a90..53e730d73f 100644 --- a/indra/newview/llfloaterbump.h +++ b/indra/newview/llfloaterbump.h @@ -1,79 +1,79 @@ -/** - * @file llfloaterbump.h - * @brief Floater showing recent bumps, hits with objects, pushes, etc. - * @author Cory Ondrejka, James Cook - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERBUMP_H -#define LL_LLFLOATERBUMP_H - -#include "llfloater.h" -#include "llmenugl.h" - -class LLMeanCollisionData; -class LLScrollListCtrl; - -class LLFloaterBump -: public LLFloater -{ - friend class LLFloaterReg; -protected: - void add(LLScrollListCtrl* list, LLMeanCollisionData *mcd); - void onScrollListRightClicked(LLUICtrl* ctrl, S32 x, S32 y); - -public: - bool postBuild() override; - void onOpen(const LLSD& key) override; - - static LLFloaterBump* getInstance(); - - void populateCollisionList(); - - void startIM(); - void startCall(); - void reportAbuse(); - void showProfile(); - void addFriend(); - void inviteToGroup(); - bool enableAddFriend(); - void muteAvatar(); - void payAvatar(); - void zoomInAvatar(); - bool enableMute(); - -private: - - LLFloaterBump(const LLSD& key); - virtual ~LLFloaterBump(); - - LLScrollListCtrl* mList; - LLHandle mPopupMenuHandle; - LLUUID mItemUUID; - - typedef std::map uuid_map_t; - uuid_map_t mNames; - -}; - -#endif +/** + * @file llfloaterbump.h + * @brief Floater showing recent bumps, hits with objects, pushes, etc. + * @author Cory Ondrejka, James Cook + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERBUMP_H +#define LL_LLFLOATERBUMP_H + +#include "llfloater.h" +#include "llmenugl.h" + +class LLMeanCollisionData; +class LLScrollListCtrl; + +class LLFloaterBump +: public LLFloater +{ + friend class LLFloaterReg; +protected: + void add(LLScrollListCtrl* list, LLMeanCollisionData *mcd); + void onScrollListRightClicked(LLUICtrl* ctrl, S32 x, S32 y); + +public: + bool postBuild() override; + void onOpen(const LLSD& key) override; + + static LLFloaterBump* getInstance(); + + void populateCollisionList(); + + void startIM(); + void startCall(); + void reportAbuse(); + void showProfile(); + void addFriend(); + void inviteToGroup(); + bool enableAddFriend(); + void muteAvatar(); + void payAvatar(); + void zoomInAvatar(); + bool enableMute(); + +private: + + LLFloaterBump(const LLSD& key); + virtual ~LLFloaterBump(); + + LLScrollListCtrl* mList; + LLHandle mPopupMenuHandle; + LLUUID mItemUUID; + + typedef std::map uuid_map_t; + uuid_map_t mNames; + +}; + +#endif diff --git a/indra/newview/llfloaterbuy.cpp b/indra/newview/llfloaterbuy.cpp index 80e3738863..7d2d836689 100644 --- a/indra/newview/llfloaterbuy.cpp +++ b/indra/newview/llfloaterbuy.cpp @@ -1,344 +1,344 @@ -/** - * @file llfloaterbuy.cpp - * @author James Cook - * @brief LLFloaterBuy class implementation - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/** - * Floater that appears when buying an object, giving a preview - * of its contents and their permissions. - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterbuy.h" - -#include "llagent.h" // for agent id -#include "llinventorymodel.h" // for gInventory -#include "llfloaterreg.h" -#include "llinventoryicon.h" -#include "llinventorydefines.h" -#include "llinventoryfunctions.h" -#include "llnotificationsutil.h" -#include "llselectmgr.h" -#include "llscrolllistctrl.h" -#include "llviewerobject.h" -#include "lluictrlfactory.h" -#include "llviewerwindow.h" -#include "lltrans.h" - -LLFloaterBuy::LLFloaterBuy(const LLSD& key) -: LLFloater(key), - mSelectionUpdateSlot() -{ -} - -bool LLFloaterBuy::postBuild() -{ - getChildView("object_list")->setEnabled(false); - getChildView("item_list")->setEnabled(false); - - getChild("cancel_btn")->setCommitCallback( boost::bind(&LLFloaterBuy::onClickCancel, this)); - getChild("buy_btn")->setCommitCallback( boost::bind(&LLFloaterBuy::onClickBuy, this)); - - setDefaultBtn("cancel_btn"); // to avoid accidental buy (SL-43130) - - // Always center the dialog. User can change the size, - // but purchases are important and should be center screen. - // This also avoids problems where the user resizes the application window - // mid-session and the saved rect is off-center. - center(); - - return true; -} - -LLFloaterBuy::~LLFloaterBuy() -{ - mObjectSelection = nullptr; -} - -void LLFloaterBuy::reset() -{ - LLScrollListCtrl* object_list = getChild("object_list"); - if (object_list) object_list->deleteAllItems(); - - LLScrollListCtrl* item_list = getChild("item_list"); - if (item_list) item_list->deleteAllItems(); -} - -// static -void LLFloaterBuy::show(const LLSaleInfo& sale_info) -{ - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - - if (selection->getRootObjectCount() != 1) - { - LLNotificationsUtil::add("BuyOneObjectOnly"); - return; - } - - LLFloaterBuy* floater = LLFloaterReg::showTypedInstance("buy_object"); - if (!floater) - return; - - // Clean up the lists... - floater->reset(); - floater->mSaleInfo = sale_info; - floater->mObjectSelection = LLSelectMgr::getInstance()->getEditSelection(); - - LLSelectNode* node = selection->getFirstRootNode(); - if (!node) - return; - - // Set title based on sale type - LLUIString title; - switch (sale_info.getSaleType()) - { - case LLSaleInfo::FS_ORIGINAL: - title = floater->getString("title_buy_text"); - break; - case LLSaleInfo::FS_COPY: - default: - title = floater->getString("title_buy_copy_text"); - break; - } - title.setArg("[NAME]", node->mName); - floater->setTitle(title); - - LLUUID owner_id; - std::string owner_name; - bool owners_identical = LLSelectMgr::getInstance()->selectGetOwner(owner_id, owner_name); - if (!owners_identical) - { - LLNotificationsUtil::add("BuyObjectOneOwner"); - return; - } - - LLCtrlListInterface *object_list = floater->childGetListInterface("object_list"); - if (!object_list) - { - return; - } - - // Update the display - // Display next owner permissions - LLSD row; - - // Compute icon for this item - std::string icon_name = LLInventoryIcon::getIconName(LLAssetType::AT_OBJECT, - LLInventoryType::IT_OBJECT); - - row["columns"][0]["column"] = "icon"; - row["columns"][0]["type"] = "icon"; - row["columns"][0]["value"] = icon_name; - - // Append the permissions that you will acquire (not the current - // permissions). - U32 next_owner_mask = node->mPermissions->getMaskNextOwner(); - std::string text = node->mName; - if (!(next_owner_mask & PERM_COPY)) - { - text.append(floater->getString("no_copy_text")); - } - if (!(next_owner_mask & PERM_MODIFY)) - { - text.append(floater->getString("no_modify_text")); - } - if (!(next_owner_mask & PERM_TRANSFER)) - { - text.append(floater->getString("no_transfer_text")); - } - - row["columns"][1]["column"] = "text"; - row["columns"][1]["value"] = text; - row["columns"][1]["font"] = "SANSSERIF"; - - // Add after columns added so appropriate heights are correct. - object_list->addElement(row); - - floater->getChild("buy_text")->setTextArg("[AMOUNT]", llformat("%d", sale_info.getSalePrice())); - floater->getChild("buy_name_text")->setTextArg("[NAME]", owner_name); - - floater->showViews(true); - - // Must do this after the floater is created, because - // sometimes the inventory is already there and - // the callback is called immediately. - LLViewerObject* obj = selection->getFirstRootObject(); - floater->registerVOInventoryListener(obj,NULL); - floater->requestVOInventory(); - - if (!floater->mSelectionUpdateSlot.connected()) - { - floater->mSelectionUpdateSlot = LLSelectMgr::getInstance()->mUpdateSignal.connect(boost::bind(&LLFloaterBuy::onSelectionChanged, floater)); - } -} - -void LLFloaterBuy::inventoryChanged(LLViewerObject* obj, - LLInventoryObject::object_list_t* inv, - S32 serial_num, - void* data) -{ - if (!obj) - { - LL_WARNS() << "No object in LLFloaterBuy::inventoryChanged" << LL_ENDL; - return; - } - - if (!inv) - { - LL_WARNS() << "No inventory in LLFloaterBuy::inventoryChanged" - << LL_ENDL; - removeVOInventoryListener(); - return; - } - - LLCtrlListInterface *item_list = childGetListInterface("item_list"); - if (!item_list) - { - removeVOInventoryListener(); - return; - } - - LLInventoryObject::object_list_t::const_iterator it = inv->begin(); - LLInventoryObject::object_list_t::const_iterator end = inv->end(); - for ( ; it != end; ++it ) - { - LLInventoryObject* obj = (LLInventoryObject*)(*it); - - // Skip folders, so we know we have inventory items only - if (obj->getType() == LLAssetType::AT_CATEGORY) - continue; - - // Skip the mysterious blank InventoryObject - if (obj->getType() == LLAssetType::AT_NONE) - continue; - - - LLInventoryItem* inv_item = (LLInventoryItem*)(obj); - - // Skip items we can't transfer - if (!inv_item->getPermissions().allowTransferTo(gAgent.getID())) - continue; - - // Create the line in the list - LLSD row; - - // Compute icon for this item - bool item_is_multi = false; - if (( inv_item->getFlags() & LLInventoryItemFlags::II_FLAGS_LANDMARK_VISITED - || inv_item->getFlags() & LLInventoryItemFlags::II_FLAGS_OBJECT_HAS_MULTIPLE_ITEMS) - && !(inv_item->getFlags() & LLInventoryItemFlags::II_FLAGS_SUBTYPE_MASK)) - { - item_is_multi = true; - } - - std::string icon_name = LLInventoryIcon::getIconName(inv_item->getType(), - inv_item->getInventoryType(), - inv_item->getFlags(), - item_is_multi); - row["columns"][0]["column"] = "icon"; - row["columns"][0]["type"] = "icon"; - row["columns"][0]["value"] = icon_name; - - // Append the permissions that you will acquire (not the current - // permissions). - U32 next_owner_mask = inv_item->getPermissions().getMaskNextOwner(); - std::string text = obj->getName(); - if (!(next_owner_mask & PERM_COPY)) - { - text.append(LLTrans::getString("no_copy")); - } - if (!(next_owner_mask & PERM_MODIFY)) - { - text.append(LLTrans::getString("no_modify")); - } - if (!(next_owner_mask & PERM_TRANSFER)) - { - text.append(LLTrans::getString("no_transfer")); - } - - row["columns"][1]["column"] = "text"; - row["columns"][1]["value"] = text; - row["columns"][1]["font"] = "SANSSERIF"; - - item_list->addElement(row); - } - removeVOInventoryListener(); -} - -void LLFloaterBuy::onSelectionChanged() -{ - - if (LLSelectMgr::getInstance()->getEditSelection()->getRootObjectCount() == 0) - { - removeVOInventoryListener(); - closeFloater(); - } - else if (LLSelectMgr::getInstance()->getEditSelection()->getRootObjectCount() > 1) - { - removeVOInventoryListener(); - showViews(false); - reset(); - setTitle(getString("mupliple_selected")); - } -} - -void LLFloaterBuy::showViews(bool show) -{ - getChild("buy_btn")->setEnabled(show); - getChild("buy_text")->setVisible(show); - getChild("buy_name_text")->setVisible(show); -} - -void LLFloaterBuy::onClickBuy() -{ - // Put the items where we put new folders. - LLUUID category_id; - category_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_OBJECT); - - // *NOTE: doesn't work for multiple object buy, which UI does not - // currently support sale info is used for verification only, if - // it doesn't match region info then sale is canceled. - LLSelectMgr::getInstance()->sendBuy(gAgent.getID(), category_id, mSaleInfo ); - - closeFloater(); -} - - -void LLFloaterBuy::onClickCancel() -{ - closeFloater(); -} - -// virtual -void LLFloaterBuy::onClose(bool app_quitting) -{ - if (mSelectionUpdateSlot.connected()) - { - mSelectionUpdateSlot.disconnect(); - } - - mObjectSelection.clear(); -} +/** + * @file llfloaterbuy.cpp + * @author James Cook + * @brief LLFloaterBuy class implementation + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/** + * Floater that appears when buying an object, giving a preview + * of its contents and their permissions. + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterbuy.h" + +#include "llagent.h" // for agent id +#include "llinventorymodel.h" // for gInventory +#include "llfloaterreg.h" +#include "llinventoryicon.h" +#include "llinventorydefines.h" +#include "llinventoryfunctions.h" +#include "llnotificationsutil.h" +#include "llselectmgr.h" +#include "llscrolllistctrl.h" +#include "llviewerobject.h" +#include "lluictrlfactory.h" +#include "llviewerwindow.h" +#include "lltrans.h" + +LLFloaterBuy::LLFloaterBuy(const LLSD& key) +: LLFloater(key), + mSelectionUpdateSlot() +{ +} + +bool LLFloaterBuy::postBuild() +{ + getChildView("object_list")->setEnabled(false); + getChildView("item_list")->setEnabled(false); + + getChild("cancel_btn")->setCommitCallback( boost::bind(&LLFloaterBuy::onClickCancel, this)); + getChild("buy_btn")->setCommitCallback( boost::bind(&LLFloaterBuy::onClickBuy, this)); + + setDefaultBtn("cancel_btn"); // to avoid accidental buy (SL-43130) + + // Always center the dialog. User can change the size, + // but purchases are important and should be center screen. + // This also avoids problems where the user resizes the application window + // mid-session and the saved rect is off-center. + center(); + + return true; +} + +LLFloaterBuy::~LLFloaterBuy() +{ + mObjectSelection = nullptr; +} + +void LLFloaterBuy::reset() +{ + LLScrollListCtrl* object_list = getChild("object_list"); + if (object_list) object_list->deleteAllItems(); + + LLScrollListCtrl* item_list = getChild("item_list"); + if (item_list) item_list->deleteAllItems(); +} + +// static +void LLFloaterBuy::show(const LLSaleInfo& sale_info) +{ + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + + if (selection->getRootObjectCount() != 1) + { + LLNotificationsUtil::add("BuyOneObjectOnly"); + return; + } + + LLFloaterBuy* floater = LLFloaterReg::showTypedInstance("buy_object"); + if (!floater) + return; + + // Clean up the lists... + floater->reset(); + floater->mSaleInfo = sale_info; + floater->mObjectSelection = LLSelectMgr::getInstance()->getEditSelection(); + + LLSelectNode* node = selection->getFirstRootNode(); + if (!node) + return; + + // Set title based on sale type + LLUIString title; + switch (sale_info.getSaleType()) + { + case LLSaleInfo::FS_ORIGINAL: + title = floater->getString("title_buy_text"); + break; + case LLSaleInfo::FS_COPY: + default: + title = floater->getString("title_buy_copy_text"); + break; + } + title.setArg("[NAME]", node->mName); + floater->setTitle(title); + + LLUUID owner_id; + std::string owner_name; + bool owners_identical = LLSelectMgr::getInstance()->selectGetOwner(owner_id, owner_name); + if (!owners_identical) + { + LLNotificationsUtil::add("BuyObjectOneOwner"); + return; + } + + LLCtrlListInterface *object_list = floater->childGetListInterface("object_list"); + if (!object_list) + { + return; + } + + // Update the display + // Display next owner permissions + LLSD row; + + // Compute icon for this item + std::string icon_name = LLInventoryIcon::getIconName(LLAssetType::AT_OBJECT, + LLInventoryType::IT_OBJECT); + + row["columns"][0]["column"] = "icon"; + row["columns"][0]["type"] = "icon"; + row["columns"][0]["value"] = icon_name; + + // Append the permissions that you will acquire (not the current + // permissions). + U32 next_owner_mask = node->mPermissions->getMaskNextOwner(); + std::string text = node->mName; + if (!(next_owner_mask & PERM_COPY)) + { + text.append(floater->getString("no_copy_text")); + } + if (!(next_owner_mask & PERM_MODIFY)) + { + text.append(floater->getString("no_modify_text")); + } + if (!(next_owner_mask & PERM_TRANSFER)) + { + text.append(floater->getString("no_transfer_text")); + } + + row["columns"][1]["column"] = "text"; + row["columns"][1]["value"] = text; + row["columns"][1]["font"] = "SANSSERIF"; + + // Add after columns added so appropriate heights are correct. + object_list->addElement(row); + + floater->getChild("buy_text")->setTextArg("[AMOUNT]", llformat("%d", sale_info.getSalePrice())); + floater->getChild("buy_name_text")->setTextArg("[NAME]", owner_name); + + floater->showViews(true); + + // Must do this after the floater is created, because + // sometimes the inventory is already there and + // the callback is called immediately. + LLViewerObject* obj = selection->getFirstRootObject(); + floater->registerVOInventoryListener(obj,NULL); + floater->requestVOInventory(); + + if (!floater->mSelectionUpdateSlot.connected()) + { + floater->mSelectionUpdateSlot = LLSelectMgr::getInstance()->mUpdateSignal.connect(boost::bind(&LLFloaterBuy::onSelectionChanged, floater)); + } +} + +void LLFloaterBuy::inventoryChanged(LLViewerObject* obj, + LLInventoryObject::object_list_t* inv, + S32 serial_num, + void* data) +{ + if (!obj) + { + LL_WARNS() << "No object in LLFloaterBuy::inventoryChanged" << LL_ENDL; + return; + } + + if (!inv) + { + LL_WARNS() << "No inventory in LLFloaterBuy::inventoryChanged" + << LL_ENDL; + removeVOInventoryListener(); + return; + } + + LLCtrlListInterface *item_list = childGetListInterface("item_list"); + if (!item_list) + { + removeVOInventoryListener(); + return; + } + + LLInventoryObject::object_list_t::const_iterator it = inv->begin(); + LLInventoryObject::object_list_t::const_iterator end = inv->end(); + for ( ; it != end; ++it ) + { + LLInventoryObject* obj = (LLInventoryObject*)(*it); + + // Skip folders, so we know we have inventory items only + if (obj->getType() == LLAssetType::AT_CATEGORY) + continue; + + // Skip the mysterious blank InventoryObject + if (obj->getType() == LLAssetType::AT_NONE) + continue; + + + LLInventoryItem* inv_item = (LLInventoryItem*)(obj); + + // Skip items we can't transfer + if (!inv_item->getPermissions().allowTransferTo(gAgent.getID())) + continue; + + // Create the line in the list + LLSD row; + + // Compute icon for this item + bool item_is_multi = false; + if (( inv_item->getFlags() & LLInventoryItemFlags::II_FLAGS_LANDMARK_VISITED + || inv_item->getFlags() & LLInventoryItemFlags::II_FLAGS_OBJECT_HAS_MULTIPLE_ITEMS) + && !(inv_item->getFlags() & LLInventoryItemFlags::II_FLAGS_SUBTYPE_MASK)) + { + item_is_multi = true; + } + + std::string icon_name = LLInventoryIcon::getIconName(inv_item->getType(), + inv_item->getInventoryType(), + inv_item->getFlags(), + item_is_multi); + row["columns"][0]["column"] = "icon"; + row["columns"][0]["type"] = "icon"; + row["columns"][0]["value"] = icon_name; + + // Append the permissions that you will acquire (not the current + // permissions). + U32 next_owner_mask = inv_item->getPermissions().getMaskNextOwner(); + std::string text = obj->getName(); + if (!(next_owner_mask & PERM_COPY)) + { + text.append(LLTrans::getString("no_copy")); + } + if (!(next_owner_mask & PERM_MODIFY)) + { + text.append(LLTrans::getString("no_modify")); + } + if (!(next_owner_mask & PERM_TRANSFER)) + { + text.append(LLTrans::getString("no_transfer")); + } + + row["columns"][1]["column"] = "text"; + row["columns"][1]["value"] = text; + row["columns"][1]["font"] = "SANSSERIF"; + + item_list->addElement(row); + } + removeVOInventoryListener(); +} + +void LLFloaterBuy::onSelectionChanged() +{ + + if (LLSelectMgr::getInstance()->getEditSelection()->getRootObjectCount() == 0) + { + removeVOInventoryListener(); + closeFloater(); + } + else if (LLSelectMgr::getInstance()->getEditSelection()->getRootObjectCount() > 1) + { + removeVOInventoryListener(); + showViews(false); + reset(); + setTitle(getString("mupliple_selected")); + } +} + +void LLFloaterBuy::showViews(bool show) +{ + getChild("buy_btn")->setEnabled(show); + getChild("buy_text")->setVisible(show); + getChild("buy_name_text")->setVisible(show); +} + +void LLFloaterBuy::onClickBuy() +{ + // Put the items where we put new folders. + LLUUID category_id; + category_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_OBJECT); + + // *NOTE: doesn't work for multiple object buy, which UI does not + // currently support sale info is used for verification only, if + // it doesn't match region info then sale is canceled. + LLSelectMgr::getInstance()->sendBuy(gAgent.getID(), category_id, mSaleInfo ); + + closeFloater(); +} + + +void LLFloaterBuy::onClickCancel() +{ + closeFloater(); +} + +// virtual +void LLFloaterBuy::onClose(bool app_quitting) +{ + if (mSelectionUpdateSlot.connected()) + { + mSelectionUpdateSlot.disconnect(); + } + + mObjectSelection.clear(); +} diff --git a/indra/newview/llfloaterbuy.h b/indra/newview/llfloaterbuy.h index 940a34f398..8c8a3efc64 100644 --- a/indra/newview/llfloaterbuy.h +++ b/indra/newview/llfloaterbuy.h @@ -1,78 +1,78 @@ -/** - * @file llfloaterbuy.h - * @author James Cook - * @brief LLFloaterBuy class definition - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/** - * Floater that appears when buying an object, giving a preview - * of its contents and their permissions. - */ - -#ifndef LL_LLFLOATERBUY_H -#define LL_LLFLOATERBUY_H - -#include "llfloater.h" -#include "llvoinventorylistener.h" -#include "llsaleinfo.h" -#include "llinventory.h" - -class LLViewerObject; -class LLSaleInfo; -class LLObjectSelection; - -class LLFloaterBuy -: public LLFloater, public LLVOInventoryListener -{ -public: - LLFloaterBuy(const LLSD& key); - ~LLFloaterBuy(); - - bool postBuild() override; - void onClose(bool app_quitting) override; - - static void show(const LLSaleInfo& sale_info); - -protected: - void reset(); - - void inventoryChanged(LLViewerObject* obj, - LLInventoryObject::object_list_t* inv, - S32 serial_num, - void* data) override; - - void onSelectionChanged(); - void showViews(bool show); - - void onClickBuy(); - void onClickCancel(); - -private: - LLSafeHandle mObjectSelection; - LLSaleInfo mSaleInfo; - - boost::signals2::connection mSelectionUpdateSlot; -}; - -#endif +/** + * @file llfloaterbuy.h + * @author James Cook + * @brief LLFloaterBuy class definition + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/** + * Floater that appears when buying an object, giving a preview + * of its contents and their permissions. + */ + +#ifndef LL_LLFLOATERBUY_H +#define LL_LLFLOATERBUY_H + +#include "llfloater.h" +#include "llvoinventorylistener.h" +#include "llsaleinfo.h" +#include "llinventory.h" + +class LLViewerObject; +class LLSaleInfo; +class LLObjectSelection; + +class LLFloaterBuy +: public LLFloater, public LLVOInventoryListener +{ +public: + LLFloaterBuy(const LLSD& key); + ~LLFloaterBuy(); + + bool postBuild() override; + void onClose(bool app_quitting) override; + + static void show(const LLSaleInfo& sale_info); + +protected: + void reset(); + + void inventoryChanged(LLViewerObject* obj, + LLInventoryObject::object_list_t* inv, + S32 serial_num, + void* data) override; + + void onSelectionChanged(); + void showViews(bool show); + + void onClickBuy(); + void onClickCancel(); + +private: + LLSafeHandle mObjectSelection; + LLSaleInfo mSaleInfo; + + boost::signals2::connection mSelectionUpdateSlot; +}; + +#endif diff --git a/indra/newview/llfloaterbuycontents.cpp b/indra/newview/llfloaterbuycontents.cpp index 046c5821c1..ae4dfb8d42 100644 --- a/indra/newview/llfloaterbuycontents.cpp +++ b/indra/newview/llfloaterbuycontents.cpp @@ -1,301 +1,301 @@ -/** - * @file llfloaterbuycontents.cpp - * @author James Cook - * @brief LLFloaterBuyContents class implementation - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/** - * Shows the contents of an object and their permissions when you - * click "Buy..." on an object with "Sell Contents" checked. - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterbuycontents.h" - -#include "llcachename.h" - -#include "llagent.h" // for agent id -#include "llcheckboxctrl.h" -#include "llinventorydefines.h" -#include "llinventoryfunctions.h" -#include "llinventoryicon.h" -#include "llinventorymodel.h" // for gInventory -#include "llfirstuse.h" -#include "llfloaterreg.h" -#include "llnotificationsutil.h" -#include "llselectmgr.h" -#include "llscrolllistctrl.h" -#include "llviewerobject.h" -#include "llviewerregion.h" -#include "lluictrlfactory.h" -#include "llviewerwindow.h" - -LLFloaterBuyContents::LLFloaterBuyContents(const LLSD& key) -: LLFloater(key) -{ -} - -bool LLFloaterBuyContents::postBuild() -{ - - getChild("cancel_btn")->setCommitCallback( boost::bind(&LLFloaterBuyContents::onClickCancel, this)); - getChild("buy_btn")->setCommitCallback( boost::bind(&LLFloaterBuyContents::onClickBuy, this)); - - getChildView("item_list")->setEnabled(false); - getChildView("buy_btn")->setEnabled(false); - getChildView("wear_check")->setEnabled(false); - - setDefaultBtn("cancel_btn"); // to avoid accidental buy (SL-43130) - - // Always center the dialog. User can change the size, - // but purchases are important and should be center screen. - // This also avoids problems where the user resizes the application window - // mid-session and the saved rect is off-center. - center(); - - return true; -} - -LLFloaterBuyContents::~LLFloaterBuyContents() -{ - removeVOInventoryListener(); -} - - -// static -void LLFloaterBuyContents::show(const LLSaleInfo& sale_info) -{ - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - - if (selection->getRootObjectCount() != 1) - { - LLNotificationsUtil::add("BuyContentsOneOnly"); - return; - } - - LLFloaterBuyContents* floater = LLFloaterReg::showTypedInstance("buy_object_contents"); - if (!floater) - return; - - LLScrollListCtrl* list = floater->getChild("item_list"); - if (list) - list->deleteAllItems(); - - floater->mObjectSelection = LLSelectMgr::getInstance()->getEditSelection(); - - LLUUID owner_id; - std::string owner_name; - bool owners_identical = LLSelectMgr::getInstance()->selectGetOwner(owner_id, owner_name); - if (!owners_identical) - { - LLNotificationsUtil::add("BuyContentsOneOwner"); - return; - } - - floater->mSaleInfo = sale_info; - - // Update the display - LLSelectNode* node = selection->getFirstRootNode(); - if (!node) return; - if(node->mPermissions->isGroupOwned()) - { - gCacheName->getGroupName(owner_id, owner_name); - } - - floater->getChild("contains_text")->setTextArg("[NAME]", node->mName); - floater->getChild("buy_text")->setTextArg("[AMOUNT]", llformat("%d", sale_info.getSalePrice())); - floater->getChild("buy_text")->setTextArg("[NAME]", owner_name); - - // Must do this after the floater is created, because - // sometimes the inventory is already there and - // the callback is called immediately. - LLViewerObject* obj = selection->getFirstRootObject(); - floater->registerVOInventoryListener(obj,NULL); - floater->requestVOInventory(); -} - - -void LLFloaterBuyContents::inventoryChanged(LLViewerObject* obj, - LLInventoryObject::object_list_t* inv, - S32 serial_num, - void* data) -{ - if (!obj) - { - LL_WARNS() << "No object in LLFloaterBuyContents::inventoryChanged" << LL_ENDL; - return; - } - - LLScrollListCtrl* item_list = getChild("item_list"); - if (!item_list) - { - removeVOInventoryListener(); - return; - } - - item_list->deleteAllItems(); - - if (!inv) - { - LL_WARNS() << "No inventory in LLFloaterBuyContents::inventoryChanged" - << LL_ENDL; - - return; - } - - // default to turning off the buy button. - LLView* buy_btn = getChildView("buy_btn"); - buy_btn->setEnabled(false); - - LLUUID owner_id; - bool is_group_owned; - LLAssetType::EType asset_type; - LLInventoryType::EType inv_type; - S32 wearable_count = 0; - - LLInventoryObject::object_list_t::const_iterator it = inv->begin(); - LLInventoryObject::object_list_t::const_iterator end = inv->end(); - - for ( ; it != end; ++it ) - { - asset_type = (*it)->getType(); - - // Skip folders, so we know we have inventory items only - if (asset_type == LLAssetType::AT_CATEGORY) - continue; - - LLInventoryItem* inv_item = (LLInventoryItem*)((LLInventoryObject*)(*it)); - inv_type = inv_item->getInventoryType(); - - // Count clothing items for later - if (LLInventoryType::IT_WEARABLE == inv_type) - { - wearable_count++; - } - - // Skip items the object's owner can't copy (and hence can't sell) - if (!inv_item->getPermissions().getOwnership(owner_id, is_group_owned)) - continue; - - if (!inv_item->getPermissions().allowCopyBy(owner_id, owner_id)) - continue; - - // Skip items we can't transfer - if (!inv_item->getPermissions().allowTransferTo(gAgent.getID())) - continue; - - // There will be at least one item shown in the display, so go - // ahead and enable the buy button. - buy_btn->setEnabled(true); - - // Create the line in the list - LLSD row; - - bool item_is_multi = false; - if ((inv_item->getFlags() & LLInventoryItemFlags::II_FLAGS_LANDMARK_VISITED - || inv_item->getFlags() & LLInventoryItemFlags::II_FLAGS_OBJECT_HAS_MULTIPLE_ITEMS) - && !(inv_item->getFlags() & LLInventoryItemFlags::II_FLAGS_SUBTYPE_MASK)) - { - item_is_multi = true; - } - - std::string icon_name = LLInventoryIcon::getIconName(inv_item->getType(), - inv_item->getInventoryType(), - inv_item->getFlags(), - item_is_multi); - row["columns"][0]["column"] = "icon"; - row["columns"][0]["type"] = "icon"; - row["columns"][0]["value"] = icon_name; - - // Append the permissions that you will acquire (not the current - // permissions). - U32 next_owner_mask = inv_item->getPermissions().getMaskNextOwner(); - std::string text = (*it)->getName(); - - if (!(next_owner_mask & PERM_COPY)) - { - text.append(getString("no_copy_text")); - } - if (!(next_owner_mask & PERM_MODIFY)) - { - text.append(getString("no_modify_text")); - } - if (!(next_owner_mask & PERM_TRANSFER)) - { - text.append(getString("no_transfer_text")); - } - - row["columns"][1]["column"] = "text"; - row["columns"][1]["value"] = text; - row["columns"][1]["font"] = "SANSSERIF"; - - item_list->addElement(row); - } - - if (wearable_count > 0) - { - getChildView("wear_check")->setEnabled(true); - getChild("wear_check")->setValue(LLSD(false) ); - } -} - - -void LLFloaterBuyContents::onClickBuy() -{ - // Make sure this wasn't selected through other mechanisms - // (ie, being the default button and pressing enter. - if(!getChildView("buy_btn")->getEnabled()) - { - // We shouldn't be enabled. Just close. - closeFloater(); - return; - } - - // We may want to wear this item - if (getChild("wear_check")->getValue()) - { - LLInventoryState::sWearNewClothing = true; - } - - // Put the items where we put new folders. - LLUUID category_id; - category_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_ROOT_INVENTORY); - - // *NOTE: doesn't work for multiple object buy, which UI does not - // currently support sale info is used for verification only, if - // it doesn't match region info then sale is canceled. - LLSelectMgr::getInstance()->sendBuy(gAgent.getID(), category_id, mSaleInfo); - - // NOTE: do this here instead of on receipt of object, since contents are transfered - // via a generic BulkUpdateInventory message with no way of distinguishing it from - // other inventory operations - LLFirstUse::newInventory(); - closeFloater(); -} - -void LLFloaterBuyContents::onClickCancel() -{ - closeFloater(); -} +/** + * @file llfloaterbuycontents.cpp + * @author James Cook + * @brief LLFloaterBuyContents class implementation + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/** + * Shows the contents of an object and their permissions when you + * click "Buy..." on an object with "Sell Contents" checked. + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterbuycontents.h" + +#include "llcachename.h" + +#include "llagent.h" // for agent id +#include "llcheckboxctrl.h" +#include "llinventorydefines.h" +#include "llinventoryfunctions.h" +#include "llinventoryicon.h" +#include "llinventorymodel.h" // for gInventory +#include "llfirstuse.h" +#include "llfloaterreg.h" +#include "llnotificationsutil.h" +#include "llselectmgr.h" +#include "llscrolllistctrl.h" +#include "llviewerobject.h" +#include "llviewerregion.h" +#include "lluictrlfactory.h" +#include "llviewerwindow.h" + +LLFloaterBuyContents::LLFloaterBuyContents(const LLSD& key) +: LLFloater(key) +{ +} + +bool LLFloaterBuyContents::postBuild() +{ + + getChild("cancel_btn")->setCommitCallback( boost::bind(&LLFloaterBuyContents::onClickCancel, this)); + getChild("buy_btn")->setCommitCallback( boost::bind(&LLFloaterBuyContents::onClickBuy, this)); + + getChildView("item_list")->setEnabled(false); + getChildView("buy_btn")->setEnabled(false); + getChildView("wear_check")->setEnabled(false); + + setDefaultBtn("cancel_btn"); // to avoid accidental buy (SL-43130) + + // Always center the dialog. User can change the size, + // but purchases are important and should be center screen. + // This also avoids problems where the user resizes the application window + // mid-session and the saved rect is off-center. + center(); + + return true; +} + +LLFloaterBuyContents::~LLFloaterBuyContents() +{ + removeVOInventoryListener(); +} + + +// static +void LLFloaterBuyContents::show(const LLSaleInfo& sale_info) +{ + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + + if (selection->getRootObjectCount() != 1) + { + LLNotificationsUtil::add("BuyContentsOneOnly"); + return; + } + + LLFloaterBuyContents* floater = LLFloaterReg::showTypedInstance("buy_object_contents"); + if (!floater) + return; + + LLScrollListCtrl* list = floater->getChild("item_list"); + if (list) + list->deleteAllItems(); + + floater->mObjectSelection = LLSelectMgr::getInstance()->getEditSelection(); + + LLUUID owner_id; + std::string owner_name; + bool owners_identical = LLSelectMgr::getInstance()->selectGetOwner(owner_id, owner_name); + if (!owners_identical) + { + LLNotificationsUtil::add("BuyContentsOneOwner"); + return; + } + + floater->mSaleInfo = sale_info; + + // Update the display + LLSelectNode* node = selection->getFirstRootNode(); + if (!node) return; + if(node->mPermissions->isGroupOwned()) + { + gCacheName->getGroupName(owner_id, owner_name); + } + + floater->getChild("contains_text")->setTextArg("[NAME]", node->mName); + floater->getChild("buy_text")->setTextArg("[AMOUNT]", llformat("%d", sale_info.getSalePrice())); + floater->getChild("buy_text")->setTextArg("[NAME]", owner_name); + + // Must do this after the floater is created, because + // sometimes the inventory is already there and + // the callback is called immediately. + LLViewerObject* obj = selection->getFirstRootObject(); + floater->registerVOInventoryListener(obj,NULL); + floater->requestVOInventory(); +} + + +void LLFloaterBuyContents::inventoryChanged(LLViewerObject* obj, + LLInventoryObject::object_list_t* inv, + S32 serial_num, + void* data) +{ + if (!obj) + { + LL_WARNS() << "No object in LLFloaterBuyContents::inventoryChanged" << LL_ENDL; + return; + } + + LLScrollListCtrl* item_list = getChild("item_list"); + if (!item_list) + { + removeVOInventoryListener(); + return; + } + + item_list->deleteAllItems(); + + if (!inv) + { + LL_WARNS() << "No inventory in LLFloaterBuyContents::inventoryChanged" + << LL_ENDL; + + return; + } + + // default to turning off the buy button. + LLView* buy_btn = getChildView("buy_btn"); + buy_btn->setEnabled(false); + + LLUUID owner_id; + bool is_group_owned; + LLAssetType::EType asset_type; + LLInventoryType::EType inv_type; + S32 wearable_count = 0; + + LLInventoryObject::object_list_t::const_iterator it = inv->begin(); + LLInventoryObject::object_list_t::const_iterator end = inv->end(); + + for ( ; it != end; ++it ) + { + asset_type = (*it)->getType(); + + // Skip folders, so we know we have inventory items only + if (asset_type == LLAssetType::AT_CATEGORY) + continue; + + LLInventoryItem* inv_item = (LLInventoryItem*)((LLInventoryObject*)(*it)); + inv_type = inv_item->getInventoryType(); + + // Count clothing items for later + if (LLInventoryType::IT_WEARABLE == inv_type) + { + wearable_count++; + } + + // Skip items the object's owner can't copy (and hence can't sell) + if (!inv_item->getPermissions().getOwnership(owner_id, is_group_owned)) + continue; + + if (!inv_item->getPermissions().allowCopyBy(owner_id, owner_id)) + continue; + + // Skip items we can't transfer + if (!inv_item->getPermissions().allowTransferTo(gAgent.getID())) + continue; + + // There will be at least one item shown in the display, so go + // ahead and enable the buy button. + buy_btn->setEnabled(true); + + // Create the line in the list + LLSD row; + + bool item_is_multi = false; + if ((inv_item->getFlags() & LLInventoryItemFlags::II_FLAGS_LANDMARK_VISITED + || inv_item->getFlags() & LLInventoryItemFlags::II_FLAGS_OBJECT_HAS_MULTIPLE_ITEMS) + && !(inv_item->getFlags() & LLInventoryItemFlags::II_FLAGS_SUBTYPE_MASK)) + { + item_is_multi = true; + } + + std::string icon_name = LLInventoryIcon::getIconName(inv_item->getType(), + inv_item->getInventoryType(), + inv_item->getFlags(), + item_is_multi); + row["columns"][0]["column"] = "icon"; + row["columns"][0]["type"] = "icon"; + row["columns"][0]["value"] = icon_name; + + // Append the permissions that you will acquire (not the current + // permissions). + U32 next_owner_mask = inv_item->getPermissions().getMaskNextOwner(); + std::string text = (*it)->getName(); + + if (!(next_owner_mask & PERM_COPY)) + { + text.append(getString("no_copy_text")); + } + if (!(next_owner_mask & PERM_MODIFY)) + { + text.append(getString("no_modify_text")); + } + if (!(next_owner_mask & PERM_TRANSFER)) + { + text.append(getString("no_transfer_text")); + } + + row["columns"][1]["column"] = "text"; + row["columns"][1]["value"] = text; + row["columns"][1]["font"] = "SANSSERIF"; + + item_list->addElement(row); + } + + if (wearable_count > 0) + { + getChildView("wear_check")->setEnabled(true); + getChild("wear_check")->setValue(LLSD(false) ); + } +} + + +void LLFloaterBuyContents::onClickBuy() +{ + // Make sure this wasn't selected through other mechanisms + // (ie, being the default button and pressing enter. + if(!getChildView("buy_btn")->getEnabled()) + { + // We shouldn't be enabled. Just close. + closeFloater(); + return; + } + + // We may want to wear this item + if (getChild("wear_check")->getValue()) + { + LLInventoryState::sWearNewClothing = true; + } + + // Put the items where we put new folders. + LLUUID category_id; + category_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_ROOT_INVENTORY); + + // *NOTE: doesn't work for multiple object buy, which UI does not + // currently support sale info is used for verification only, if + // it doesn't match region info then sale is canceled. + LLSelectMgr::getInstance()->sendBuy(gAgent.getID(), category_id, mSaleInfo); + + // NOTE: do this here instead of on receipt of object, since contents are transfered + // via a generic BulkUpdateInventory message with no way of distinguishing it from + // other inventory operations + LLFirstUse::newInventory(); + closeFloater(); +} + +void LLFloaterBuyContents::onClickCancel() +{ + closeFloater(); +} diff --git a/indra/newview/llfloaterbuycontents.h b/indra/newview/llfloaterbuycontents.h index 98dab627d9..cbb36d446d 100644 --- a/indra/newview/llfloaterbuycontents.h +++ b/indra/newview/llfloaterbuycontents.h @@ -1,67 +1,67 @@ -/** - * @file llfloaterbuycontents.h - * @author James Cook - * @brief LLFloaterBuyContents class header file - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/** - * Shows the contents of an object and their permissions when you - * click "Buy..." on an object with "Sell Contents" checked. - */ - -#ifndef LL_LLFLOATERBUYCONTENTS_H -#define LL_LLFLOATERBUYCONTENTS_H - -#include "llfloater.h" -#include "llvoinventorylistener.h" -#include "llinventory.h" - -class LLViewerObject; -class LLObjectSelection; - -class LLFloaterBuyContents -: public LLFloater, public LLVOInventoryListener -{ -public: - static void show(const LLSaleInfo& sale_info); - - LLFloaterBuyContents(const LLSD& key); - ~LLFloaterBuyContents(); - bool postBuild() override; - -protected: - void inventoryChanged(LLViewerObject* obj, - LLInventoryObject::object_list_t* inv, - S32 serial_num, - void* data) override; - - void onClickBuy(); - void onClickCancel(); - -protected: - LLSafeHandle mObjectSelection; - LLSaleInfo mSaleInfo; -}; - -#endif +/** + * @file llfloaterbuycontents.h + * @author James Cook + * @brief LLFloaterBuyContents class header file + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/** + * Shows the contents of an object and their permissions when you + * click "Buy..." on an object with "Sell Contents" checked. + */ + +#ifndef LL_LLFLOATERBUYCONTENTS_H +#define LL_LLFLOATERBUYCONTENTS_H + +#include "llfloater.h" +#include "llvoinventorylistener.h" +#include "llinventory.h" + +class LLViewerObject; +class LLObjectSelection; + +class LLFloaterBuyContents +: public LLFloater, public LLVOInventoryListener +{ +public: + static void show(const LLSaleInfo& sale_info); + + LLFloaterBuyContents(const LLSD& key); + ~LLFloaterBuyContents(); + bool postBuild() override; + +protected: + void inventoryChanged(LLViewerObject* obj, + LLInventoryObject::object_list_t* inv, + S32 serial_num, + void* data) override; + + void onClickBuy(); + void onClickCancel(); + +protected: + LLSafeHandle mObjectSelection; + LLSaleInfo mSaleInfo; +}; + +#endif diff --git a/indra/newview/llfloaterbuycurrency.cpp b/indra/newview/llfloaterbuycurrency.cpp index 2165516260..e41f893c43 100644 --- a/indra/newview/llfloaterbuycurrency.cpp +++ b/indra/newview/llfloaterbuycurrency.cpp @@ -1,362 +1,362 @@ -/** - * @file llfloaterbuycurrency.cpp - * @brief LLFloaterBuyCurrency class implementation - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterbuycurrency.h" - -// viewer includes -#include "llcurrencyuimanager.h" -#include "llfloater.h" -#include "llfloaterreg.h" -#include "lllayoutstack.h" -#include "lliconctrl.h" -#include "llnotificationsutil.h" -#include "llstatusbar.h" -#include "lltextbox.h" -#include "llviewchildren.h" -#include "llviewerwindow.h" -#include "lluictrlfactory.h" -#include "llweb.h" -#include "llwindow.h" -#include "llappviewer.h" - -static const S32 MINIMUM_BALANCE_AMOUNT = 0; - -class LLFloaterBuyCurrencyUI -: public LLFloater -{ -public: - LLFloaterBuyCurrencyUI(const LLSD& key); - virtual ~LLFloaterBuyCurrencyUI(); - - -public: - LLViewChildren mChildren; - LLCurrencyUIManager mManager; - - bool mHasTarget; - S32 mTargetPrice; - S32 mRequiredAmount; - -public: - void noTarget(); - void target(const std::string& name, S32 price); - - virtual bool postBuild(); - - void updateUI(); - void collapsePanels(bool collapse); - - virtual void draw(); - virtual bool canClose(); - - void onClickBuy(); - void onClickCancel(); -}; - -LLFloater* LLFloaterBuyCurrency::buildFloater(const LLSD& key) -{ - LLFloaterBuyCurrencyUI* floater = new LLFloaterBuyCurrencyUI(key); - return floater; -} - -#if LL_WINDOWS -// passing 'this' during construction generates a warning. The callee -// only uses the pointer to hold a reference to 'this' which is -// already valid, so this call does the correct thing. Disable the -// warning so that we can compile without generating a warning. -#pragma warning(disable : 4355) -#endif -LLFloaterBuyCurrencyUI::LLFloaterBuyCurrencyUI(const LLSD& key) -: LLFloater(key), - mChildren(*this), - mManager(*this), - mHasTarget(false), - mTargetPrice(0) -{ -} - -LLFloaterBuyCurrencyUI::~LLFloaterBuyCurrencyUI() -{ -} - - -void LLFloaterBuyCurrencyUI::noTarget() -{ - mHasTarget = false; - mTargetPrice = 0; - mManager.setAmount(0); -} - -void LLFloaterBuyCurrencyUI::target(const std::string& name, S32 price) -{ - mHasTarget = true; - mTargetPrice = price; - - if (!name.empty()) - { - getChild("target_price_label")->setValue(name); - } - - S32 balance = gStatusBar->getBalance(); - S32 need = price - balance; - if (need < 0) - { - need = 0; - } - - mRequiredAmount = need + MINIMUM_BALANCE_AMOUNT; - mManager.setAmount(0); -} - - -// virtual -bool LLFloaterBuyCurrencyUI::postBuild() -{ - mManager.prepare(); - - getChild("buy_btn")->setCommitCallback( boost::bind(&LLFloaterBuyCurrencyUI::onClickBuy, this)); - getChild("cancel_btn")->setCommitCallback( boost::bind(&LLFloaterBuyCurrencyUI::onClickCancel, this)); - - center(); - - updateUI(); - - return true; -} - -void LLFloaterBuyCurrencyUI::draw() -{ - if (mManager.process()) - { - if (mManager.bought()) - { - LLNotificationsUtil::add("BuyLindenDollarSuccess"); - closeFloater(); - return; - } - - updateUI(); - } - - // disable the Buy button when we are not able to buy - getChildView("buy_btn")->setEnabled(mManager.canBuy()); - - LLFloater::draw(); -} - -bool LLFloaterBuyCurrencyUI::canClose() -{ - return mManager.canCancel(); -} - -void LLFloaterBuyCurrencyUI::updateUI() -{ - bool hasError = mManager.hasError(); - mManager.updateUI(!hasError && !mManager.buying()); - - // hide most widgets - we'll turn them on as needed next - getChildView("info_buying")->setVisible(false); - getChildView("info_need_more")->setVisible(false); - getChildView("purchase_warning_repurchase")->setVisible(false); - getChildView("purchase_warning_notenough")->setVisible(false); - getChildView("contacting")->setVisible(false); - - if (hasError) - { - // display an error from the server - LLSD args; - args["TITLE"] = getString("info_cannot_buy"); - args["MESSAGE"] = mManager.errorMessage(); - LLNotificationsUtil::add("CouldNotBuyCurrency", args); - mManager.clearError(); - closeFloater(); - } - else - { - // display the main Buy L$ interface - getChildView("normal_background")->setVisible(true); - - if (mHasTarget) - { - getChildView("info_need_more")->setVisible(true); - } - else - { - getChildView("info_buying")->setVisible(true); - } - - if (mManager.buying()) - { - getChildView("contacting")->setVisible( true); - } - else - { - if (mHasTarget) - { - getChild("target_price")->setTextArg("[AMT]", llformat("%d", mTargetPrice)); - getChild("required_amount")->setTextArg("[AMT]", llformat("%d", mRequiredAmount)); - } - } - - S32 balance = gStatusBar->getBalance(); - getChildView("balance_label")->setVisible(true); - getChildView("balance_amount")->setVisible(true); - getChild("balance_amount")->setTextArg("[AMT]", llformat("%d", balance)); - - S32 buying = mManager.getAmount(); - getChildView("buying_label")->setVisible(true); - getChildView("buying_amount")->setVisible(true); - getChild("buying_amount")->setTextArg("[AMT]", llformat("%d", buying)); - - S32 total = balance + buying; - getChildView("total_label")->setVisible(true); - getChildView("total_amount")->setVisible(true); - getChild("total_amount")->setTextArg("[AMT]", llformat("%d", total)); - - if (mHasTarget) - { - getChildView("purchase_warning_repurchase")->setVisible( !getChildView("currency_links")->getVisible()); - } - } - - getChildView("getting_data")->setVisible( !mManager.canBuy() && !hasError && !getChildView("currency_est")->getVisible()); -} - -void LLFloaterBuyCurrencyUI::collapsePanels(bool collapse) -{ - LLLayoutPanel* price_panel = getChild("layout_panel_price"); - - if (price_panel->isCollapsed() == collapse) - return; - - LLLayoutStack* outer_stack = getChild("outer_stack"); - LLLayoutPanel* required_panel = getChild("layout_panel_required"); - LLLayoutPanel* msg_panel = getChild("layout_panel_msg"); - - S32 delta_height = price_panel->getRect().getHeight() + required_panel->getRect().getHeight() + msg_panel->getRect().getHeight(); - delta_height *= (collapse ? -1 : 1); - - LLIconCtrl* icon = getChild("normal_background"); - LLRect rect = icon->getRect(); - icon->setRect(rect.setOriginAndSize(rect.mLeft, rect.mBottom - delta_height, rect.getWidth(), rect.getHeight() + delta_height)); - - outer_stack->collapsePanel(price_panel, collapse); - outer_stack->collapsePanel(required_panel, collapse); - outer_stack->collapsePanel(msg_panel, collapse); - - outer_stack->updateLayout(); - - LLRect floater_rect = getRect(); - floater_rect.mBottom -= delta_height; - setShape(floater_rect, false); -} - -void LLFloaterBuyCurrencyUI::onClickBuy() -{ - mManager.buy(getString("buy_currency")); - updateUI(); - // Update L$ balance - LLStatusBar::sendMoneyBalanceRequest(); -} - -void LLFloaterBuyCurrencyUI::onClickCancel() -{ - closeFloater(); - // Update L$ balance - LLStatusBar::sendMoneyBalanceRequest(); -} - -LLFetchAvatarPaymentInfo* LLFloaterBuyCurrency::sPropertiesRequest = NULL; - -// static -void LLFloaterBuyCurrency::buyCurrency() -{ - delete sPropertiesRequest; - sPropertiesRequest = new LLFetchAvatarPaymentInfo(false); -} - -// static -void LLFloaterBuyCurrency::buyCurrency(const std::string& name, S32 price) -{ - delete sPropertiesRequest; - sPropertiesRequest = new LLFetchAvatarPaymentInfo(true, name, price); -} - -// static -void LLFloaterBuyCurrency::handleBuyCurrency(bool has_piof, bool has_target, const std::string name, S32 price) -{ - delete sPropertiesRequest; - sPropertiesRequest = NULL; - - if (has_piof) - { - LLFloaterBuyCurrencyUI* ui = LLFloaterReg::showTypedInstance("buy_currency"); - if (has_target) - { - ui->target(name, price); - } - else - { - ui->noTarget(); - } - ui->updateUI(); - ui->collapsePanels(!has_target); - } - else - { - LLFloaterReg::showInstance("add_payment_method"); - } -} - -LLFetchAvatarPaymentInfo::LLFetchAvatarPaymentInfo(bool has_target, const std::string& name, S32 price) -: mAvatarID(gAgent.getID()), - mHasTarget(has_target), - mPrice(price), - mName(name) -{ - LLAvatarPropertiesProcessor* processor = LLAvatarPropertiesProcessor::getInstance(); - // register ourselves as an observer - processor->addObserver(mAvatarID, this); - // send a request (duplicates will be suppressed inside the avatar - // properties processor) - processor->sendAvatarPropertiesRequest(mAvatarID); -} - -LLFetchAvatarPaymentInfo::~LLFetchAvatarPaymentInfo() -{ - LLAvatarPropertiesProcessor::getInstance()->removeObserver(mAvatarID, this); -} - -void LLFetchAvatarPaymentInfo::processProperties(void* data, EAvatarProcessorType type) -{ - if (data && type == APT_PROPERTIES) - { - LLAvatarData* avatar_data = static_cast(data); - LLFloaterBuyCurrency::handleBuyCurrency(LLAvatarPropertiesProcessor::hasPaymentInfoOnFile(avatar_data), mHasTarget, mName, mPrice); - } -} +/** + * @file llfloaterbuycurrency.cpp + * @brief LLFloaterBuyCurrency class implementation + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterbuycurrency.h" + +// viewer includes +#include "llcurrencyuimanager.h" +#include "llfloater.h" +#include "llfloaterreg.h" +#include "lllayoutstack.h" +#include "lliconctrl.h" +#include "llnotificationsutil.h" +#include "llstatusbar.h" +#include "lltextbox.h" +#include "llviewchildren.h" +#include "llviewerwindow.h" +#include "lluictrlfactory.h" +#include "llweb.h" +#include "llwindow.h" +#include "llappviewer.h" + +static const S32 MINIMUM_BALANCE_AMOUNT = 0; + +class LLFloaterBuyCurrencyUI +: public LLFloater +{ +public: + LLFloaterBuyCurrencyUI(const LLSD& key); + virtual ~LLFloaterBuyCurrencyUI(); + + +public: + LLViewChildren mChildren; + LLCurrencyUIManager mManager; + + bool mHasTarget; + S32 mTargetPrice; + S32 mRequiredAmount; + +public: + void noTarget(); + void target(const std::string& name, S32 price); + + virtual bool postBuild(); + + void updateUI(); + void collapsePanels(bool collapse); + + virtual void draw(); + virtual bool canClose(); + + void onClickBuy(); + void onClickCancel(); +}; + +LLFloater* LLFloaterBuyCurrency::buildFloater(const LLSD& key) +{ + LLFloaterBuyCurrencyUI* floater = new LLFloaterBuyCurrencyUI(key); + return floater; +} + +#if LL_WINDOWS +// passing 'this' during construction generates a warning. The callee +// only uses the pointer to hold a reference to 'this' which is +// already valid, so this call does the correct thing. Disable the +// warning so that we can compile without generating a warning. +#pragma warning(disable : 4355) +#endif +LLFloaterBuyCurrencyUI::LLFloaterBuyCurrencyUI(const LLSD& key) +: LLFloater(key), + mChildren(*this), + mManager(*this), + mHasTarget(false), + mTargetPrice(0) +{ +} + +LLFloaterBuyCurrencyUI::~LLFloaterBuyCurrencyUI() +{ +} + + +void LLFloaterBuyCurrencyUI::noTarget() +{ + mHasTarget = false; + mTargetPrice = 0; + mManager.setAmount(0); +} + +void LLFloaterBuyCurrencyUI::target(const std::string& name, S32 price) +{ + mHasTarget = true; + mTargetPrice = price; + + if (!name.empty()) + { + getChild("target_price_label")->setValue(name); + } + + S32 balance = gStatusBar->getBalance(); + S32 need = price - balance; + if (need < 0) + { + need = 0; + } + + mRequiredAmount = need + MINIMUM_BALANCE_AMOUNT; + mManager.setAmount(0); +} + + +// virtual +bool LLFloaterBuyCurrencyUI::postBuild() +{ + mManager.prepare(); + + getChild("buy_btn")->setCommitCallback( boost::bind(&LLFloaterBuyCurrencyUI::onClickBuy, this)); + getChild("cancel_btn")->setCommitCallback( boost::bind(&LLFloaterBuyCurrencyUI::onClickCancel, this)); + + center(); + + updateUI(); + + return true; +} + +void LLFloaterBuyCurrencyUI::draw() +{ + if (mManager.process()) + { + if (mManager.bought()) + { + LLNotificationsUtil::add("BuyLindenDollarSuccess"); + closeFloater(); + return; + } + + updateUI(); + } + + // disable the Buy button when we are not able to buy + getChildView("buy_btn")->setEnabled(mManager.canBuy()); + + LLFloater::draw(); +} + +bool LLFloaterBuyCurrencyUI::canClose() +{ + return mManager.canCancel(); +} + +void LLFloaterBuyCurrencyUI::updateUI() +{ + bool hasError = mManager.hasError(); + mManager.updateUI(!hasError && !mManager.buying()); + + // hide most widgets - we'll turn them on as needed next + getChildView("info_buying")->setVisible(false); + getChildView("info_need_more")->setVisible(false); + getChildView("purchase_warning_repurchase")->setVisible(false); + getChildView("purchase_warning_notenough")->setVisible(false); + getChildView("contacting")->setVisible(false); + + if (hasError) + { + // display an error from the server + LLSD args; + args["TITLE"] = getString("info_cannot_buy"); + args["MESSAGE"] = mManager.errorMessage(); + LLNotificationsUtil::add("CouldNotBuyCurrency", args); + mManager.clearError(); + closeFloater(); + } + else + { + // display the main Buy L$ interface + getChildView("normal_background")->setVisible(true); + + if (mHasTarget) + { + getChildView("info_need_more")->setVisible(true); + } + else + { + getChildView("info_buying")->setVisible(true); + } + + if (mManager.buying()) + { + getChildView("contacting")->setVisible( true); + } + else + { + if (mHasTarget) + { + getChild("target_price")->setTextArg("[AMT]", llformat("%d", mTargetPrice)); + getChild("required_amount")->setTextArg("[AMT]", llformat("%d", mRequiredAmount)); + } + } + + S32 balance = gStatusBar->getBalance(); + getChildView("balance_label")->setVisible(true); + getChildView("balance_amount")->setVisible(true); + getChild("balance_amount")->setTextArg("[AMT]", llformat("%d", balance)); + + S32 buying = mManager.getAmount(); + getChildView("buying_label")->setVisible(true); + getChildView("buying_amount")->setVisible(true); + getChild("buying_amount")->setTextArg("[AMT]", llformat("%d", buying)); + + S32 total = balance + buying; + getChildView("total_label")->setVisible(true); + getChildView("total_amount")->setVisible(true); + getChild("total_amount")->setTextArg("[AMT]", llformat("%d", total)); + + if (mHasTarget) + { + getChildView("purchase_warning_repurchase")->setVisible( !getChildView("currency_links")->getVisible()); + } + } + + getChildView("getting_data")->setVisible( !mManager.canBuy() && !hasError && !getChildView("currency_est")->getVisible()); +} + +void LLFloaterBuyCurrencyUI::collapsePanels(bool collapse) +{ + LLLayoutPanel* price_panel = getChild("layout_panel_price"); + + if (price_panel->isCollapsed() == collapse) + return; + + LLLayoutStack* outer_stack = getChild("outer_stack"); + LLLayoutPanel* required_panel = getChild("layout_panel_required"); + LLLayoutPanel* msg_panel = getChild("layout_panel_msg"); + + S32 delta_height = price_panel->getRect().getHeight() + required_panel->getRect().getHeight() + msg_panel->getRect().getHeight(); + delta_height *= (collapse ? -1 : 1); + + LLIconCtrl* icon = getChild("normal_background"); + LLRect rect = icon->getRect(); + icon->setRect(rect.setOriginAndSize(rect.mLeft, rect.mBottom - delta_height, rect.getWidth(), rect.getHeight() + delta_height)); + + outer_stack->collapsePanel(price_panel, collapse); + outer_stack->collapsePanel(required_panel, collapse); + outer_stack->collapsePanel(msg_panel, collapse); + + outer_stack->updateLayout(); + + LLRect floater_rect = getRect(); + floater_rect.mBottom -= delta_height; + setShape(floater_rect, false); +} + +void LLFloaterBuyCurrencyUI::onClickBuy() +{ + mManager.buy(getString("buy_currency")); + updateUI(); + // Update L$ balance + LLStatusBar::sendMoneyBalanceRequest(); +} + +void LLFloaterBuyCurrencyUI::onClickCancel() +{ + closeFloater(); + // Update L$ balance + LLStatusBar::sendMoneyBalanceRequest(); +} + +LLFetchAvatarPaymentInfo* LLFloaterBuyCurrency::sPropertiesRequest = NULL; + +// static +void LLFloaterBuyCurrency::buyCurrency() +{ + delete sPropertiesRequest; + sPropertiesRequest = new LLFetchAvatarPaymentInfo(false); +} + +// static +void LLFloaterBuyCurrency::buyCurrency(const std::string& name, S32 price) +{ + delete sPropertiesRequest; + sPropertiesRequest = new LLFetchAvatarPaymentInfo(true, name, price); +} + +// static +void LLFloaterBuyCurrency::handleBuyCurrency(bool has_piof, bool has_target, const std::string name, S32 price) +{ + delete sPropertiesRequest; + sPropertiesRequest = NULL; + + if (has_piof) + { + LLFloaterBuyCurrencyUI* ui = LLFloaterReg::showTypedInstance("buy_currency"); + if (has_target) + { + ui->target(name, price); + } + else + { + ui->noTarget(); + } + ui->updateUI(); + ui->collapsePanels(!has_target); + } + else + { + LLFloaterReg::showInstance("add_payment_method"); + } +} + +LLFetchAvatarPaymentInfo::LLFetchAvatarPaymentInfo(bool has_target, const std::string& name, S32 price) +: mAvatarID(gAgent.getID()), + mHasTarget(has_target), + mPrice(price), + mName(name) +{ + LLAvatarPropertiesProcessor* processor = LLAvatarPropertiesProcessor::getInstance(); + // register ourselves as an observer + processor->addObserver(mAvatarID, this); + // send a request (duplicates will be suppressed inside the avatar + // properties processor) + processor->sendAvatarPropertiesRequest(mAvatarID); +} + +LLFetchAvatarPaymentInfo::~LLFetchAvatarPaymentInfo() +{ + LLAvatarPropertiesProcessor::getInstance()->removeObserver(mAvatarID, this); +} + +void LLFetchAvatarPaymentInfo::processProperties(void* data, EAvatarProcessorType type) +{ + if (data && type == APT_PROPERTIES) + { + LLAvatarData* avatar_data = static_cast(data); + LLFloaterBuyCurrency::handleBuyCurrency(LLAvatarPropertiesProcessor::hasPaymentInfoOnFile(avatar_data), mHasTarget, mName, mPrice); + } +} diff --git a/indra/newview/llfloaterbuycurrencyhtml.cpp b/indra/newview/llfloaterbuycurrencyhtml.cpp index dbef5b0083..d3712869b0 100644 --- a/indra/newview/llfloaterbuycurrencyhtml.cpp +++ b/indra/newview/llfloaterbuycurrencyhtml.cpp @@ -1,123 +1,123 @@ -/** - * @file llfloaterbuycurrencyhtml.cpp - * @brief buy currency implemented in HTML floater - uses embedded media browser control - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterbuycurrencyhtml.h" -#include "llhttpconstants.h" -#include "llstatusbar.h" - -//////////////////////////////////////////////////////////////////////////////// -// -LLFloaterBuyCurrencyHTML::LLFloaterBuyCurrencyHTML( const LLSD& key ): - LLFloater( key ), - mSpecificSumRequested( false ), - mMessage( "" ), - mSum( 0 ) -{ -} - -//////////////////////////////////////////////////////////////////////////////// -// -bool LLFloaterBuyCurrencyHTML::postBuild() -{ - // observer media events - mBrowser = getChild( "browser" ); - mBrowser->addObserver( this ); - - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLFloaterBuyCurrencyHTML::navigateToFinalURL() -{ - // URL for actual currency buy contents is in XUI file - std::string buy_currency_url = getString( "buy_currency_url" ); - - // replace [LANGUAGE] meta-tag with view language - LLStringUtil::format_map_t replace; - - // viewer language - replace[ "[LANGUAGE]" ] = LLUI::getLanguage(); - - // flag that specific amount requested - replace[ "[SPECIFIC_AMOUNT]" ] = ( mSpecificSumRequested ? "y":"n" ); - - // amount requested - std::ostringstream codec( "" ); - codec << mSum; - replace[ "[SUM]" ] = codec.str(); - - // users' current balance - codec.clear(); - codec.str( "" ); - codec << gStatusBar->getBalance(); - replace[ "[BAL]" ] = codec.str(); - - // message - "This cost L$x,xxx for example - replace[ "[MSG]" ] = LLURI::escape( mMessage ); - LLStringUtil::format( buy_currency_url, replace ); - - // write final URL to debug console - LL_INFOS() << "Buy currency HTML parsed URL is " << buy_currency_url << LL_ENDL; - - // kick off the navigation - mBrowser->navigateTo( buy_currency_url, HTTP_CONTENT_TEXT_HTML ); -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLFloaterBuyCurrencyHTML::handleMediaEvent( LLPluginClassMedia* self, EMediaEvent event ) -{ - // placeholder for now - just in case we want to catch media events - if ( LLPluginClassMediaOwner::MEDIA_EVENT_NAVIGATE_COMPLETE == event ) - { - // update currency after we complete a navigation since there are many ways - // this can result in a different L$ balance - LLStatusBar::sendMoneyBalanceRequest(); - }; -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLFloaterBuyCurrencyHTML::onClose( bool app_quitting ) -{ - // Update L$ balance one more time - LLStatusBar::sendMoneyBalanceRequest(); - - destroy(); -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLFloaterBuyCurrencyHTML::setParams( bool specific_sum_requested, const std::string& message, S32 sum ) -{ - // save these away - used to construct URL later - mSpecificSumRequested = specific_sum_requested; - mMessage = message; - mSum = sum; -} +/** + * @file llfloaterbuycurrencyhtml.cpp + * @brief buy currency implemented in HTML floater - uses embedded media browser control + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterbuycurrencyhtml.h" +#include "llhttpconstants.h" +#include "llstatusbar.h" + +//////////////////////////////////////////////////////////////////////////////// +// +LLFloaterBuyCurrencyHTML::LLFloaterBuyCurrencyHTML( const LLSD& key ): + LLFloater( key ), + mSpecificSumRequested( false ), + mMessage( "" ), + mSum( 0 ) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLFloaterBuyCurrencyHTML::postBuild() +{ + // observer media events + mBrowser = getChild( "browser" ); + mBrowser->addObserver( this ); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFloaterBuyCurrencyHTML::navigateToFinalURL() +{ + // URL for actual currency buy contents is in XUI file + std::string buy_currency_url = getString( "buy_currency_url" ); + + // replace [LANGUAGE] meta-tag with view language + LLStringUtil::format_map_t replace; + + // viewer language + replace[ "[LANGUAGE]" ] = LLUI::getLanguage(); + + // flag that specific amount requested + replace[ "[SPECIFIC_AMOUNT]" ] = ( mSpecificSumRequested ? "y":"n" ); + + // amount requested + std::ostringstream codec( "" ); + codec << mSum; + replace[ "[SUM]" ] = codec.str(); + + // users' current balance + codec.clear(); + codec.str( "" ); + codec << gStatusBar->getBalance(); + replace[ "[BAL]" ] = codec.str(); + + // message - "This cost L$x,xxx for example + replace[ "[MSG]" ] = LLURI::escape( mMessage ); + LLStringUtil::format( buy_currency_url, replace ); + + // write final URL to debug console + LL_INFOS() << "Buy currency HTML parsed URL is " << buy_currency_url << LL_ENDL; + + // kick off the navigation + mBrowser->navigateTo( buy_currency_url, HTTP_CONTENT_TEXT_HTML ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFloaterBuyCurrencyHTML::handleMediaEvent( LLPluginClassMedia* self, EMediaEvent event ) +{ + // placeholder for now - just in case we want to catch media events + if ( LLPluginClassMediaOwner::MEDIA_EVENT_NAVIGATE_COMPLETE == event ) + { + // update currency after we complete a navigation since there are many ways + // this can result in a different L$ balance + LLStatusBar::sendMoneyBalanceRequest(); + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFloaterBuyCurrencyHTML::onClose( bool app_quitting ) +{ + // Update L$ balance one more time + LLStatusBar::sendMoneyBalanceRequest(); + + destroy(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFloaterBuyCurrencyHTML::setParams( bool specific_sum_requested, const std::string& message, S32 sum ) +{ + // save these away - used to construct URL later + mSpecificSumRequested = specific_sum_requested; + mMessage = message; + mSum = sum; +} diff --git a/indra/newview/llfloaterbuycurrencyhtml.h b/indra/newview/llfloaterbuycurrencyhtml.h index 2671af8ed0..23a29cae4c 100644 --- a/indra/newview/llfloaterbuycurrencyhtml.h +++ b/indra/newview/llfloaterbuycurrencyhtml.h @@ -1,59 +1,59 @@ -/** - * @file llfloaterbuycurrencyhtml.h - * @brief buy currency implemented in HTML floater - uses embedded media browser control - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERBUYCURRENCYHTML_H -#define LL_LLFLOATERBUYCURRENCYHTML_H - -#include "llfloater.h" -#include "llmediactrl.h" - -class LLFloaterBuyCurrencyHTML : - public LLFloater, - public LLViewerMediaObserver -{ - public: - LLFloaterBuyCurrencyHTML( const LLSD& key ); - - /*virtual*/ bool postBuild(); - /*virtual*/ void onClose( bool app_quitting ); - - // inherited from LLViewerMediaObserver - /*virtual*/ void handleMediaEvent( LLPluginClassMedia* self, EMediaEvent event ); - - // allow our controlling parent to tell us paramters - void setParams( bool specific_sum_requested, const std::string& message, S32 sum ); - - // parse and construct URL and set browser to navigate there. - void navigateToFinalURL(); - - private: - LLMediaCtrl* mBrowser; - bool mSpecificSumRequested; - std::string mMessage; - S32 mSum; -}; - -#endif // LL_LLFLOATERBUYCURRENCYHTML_H +/** + * @file llfloaterbuycurrencyhtml.h + * @brief buy currency implemented in HTML floater - uses embedded media browser control + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERBUYCURRENCYHTML_H +#define LL_LLFLOATERBUYCURRENCYHTML_H + +#include "llfloater.h" +#include "llmediactrl.h" + +class LLFloaterBuyCurrencyHTML : + public LLFloater, + public LLViewerMediaObserver +{ + public: + LLFloaterBuyCurrencyHTML( const LLSD& key ); + + /*virtual*/ bool postBuild(); + /*virtual*/ void onClose( bool app_quitting ); + + // inherited from LLViewerMediaObserver + /*virtual*/ void handleMediaEvent( LLPluginClassMedia* self, EMediaEvent event ); + + // allow our controlling parent to tell us paramters + void setParams( bool specific_sum_requested, const std::string& message, S32 sum ); + + // parse and construct URL and set browser to navigate there. + void navigateToFinalURL(); + + private: + LLMediaCtrl* mBrowser; + bool mSpecificSumRequested; + std::string mMessage; + S32 mSum; +}; + +#endif // LL_LLFLOATERBUYCURRENCYHTML_H diff --git a/indra/newview/llfloaterbuyland.cpp b/indra/newview/llfloaterbuyland.cpp index 2287900e5f..570a223908 100644 --- a/indra/newview/llfloaterbuyland.cpp +++ b/indra/newview/llfloaterbuyland.cpp @@ -1,1379 +1,1379 @@ -/** - * @file llfloaterbuyland.cpp - * @brief LLFloaterBuyLand class implementation - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterbuyland.h" - -// viewer includes -#include "llagent.h" -#include "llbutton.h" -#include "llcachename.h" -#include "llcheckboxctrl.h" -#include "llcombobox.h" -#include "llconfirmationmanager.h" -#include "llcurrencyuimanager.h" -#include "llfloater.h" -#include "llfloaterreg.h" -#include "llfloatertools.h" -#include "llframetimer.h" -#include "lliconctrl.h" -#include "lllineeditor.h" -#include "llnotificationsutil.h" -#include "llparcel.h" -#include "llslurl.h" -#include "llstatusbar.h" -#include "lltextbox.h" -#include "lltexturectrl.h" -#include "lltrans.h" -#include "llviewchildren.h" -#include "llviewercontrol.h" -#include "lluictrlfactory.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" -#include "llviewertexteditor.h" -#include "llviewerwindow.h" -#include "llweb.h" -#include "llwindow.h" -#include "llworld.h" -#include "llxmlrpctransaction.h" -#include "llviewernetwork.h" -#include "roles_constants.h" - -// NOTE: This is duplicated in lldatamoney.cpp ... -const F32 GROUP_LAND_BONUS_FACTOR = 1.1f; - -class LLFloaterBuyLandUI -: public LLFloater -{ -public: - LLFloaterBuyLandUI(const LLSD& key); - virtual ~LLFloaterBuyLandUI(); - - /*virtual*/ void onClose(bool app_quitting); - - // Left padding for maturity rating icon. - static const S32 ICON_PAD = 2; - -private: - class SelectionObserver : public LLParcelObserver - { - public: - SelectionObserver(LLFloaterBuyLandUI* floater) : mFloater(floater) {} - virtual void changed(); - private: - LLFloaterBuyLandUI* mFloater; - }; - -private: - SelectionObserver mParcelSelectionObserver; - LLViewerRegion* mRegion; - LLParcelSelectionHandle mParcel; - bool mIsClaim; - bool mIsForGroup; - - bool mCanBuy; - bool mCannotBuyIsError; - std::string mCannotBuyReason; - std::string mCannotBuyURI; - - bool mBought; - - // information about the agent - S32 mAgentCommittedTier; - S32 mAgentCashBalance; - bool mAgentHasNeverOwnedLand; - - // information about the parcel - bool mParcelValid; - bool mParcelIsForSale; - bool mParcelIsGroupLand; - S32 mParcelGroupContribution; - S32 mParcelPrice; - S32 mParcelActualArea; - S32 mParcelBillableArea; - S32 mParcelSupportedObjects; - bool mParcelSoldWithObjects; - std::string mParcelLocation; - LLUUID mParcelSnapshot; - std::string mParcelSellerName; - - // user's choices - S32 mUserPlanChoice; - - // from website - bool mSiteValid; - bool mSiteMembershipUpgrade; - std::string mSiteMembershipAction; - std::vector - mSiteMembershipPlanIDs; - std::vector - mSiteMembershipPlanNames; - bool mSiteLandUseUpgrade; - std::string mSiteLandUseAction; - std::string mSiteConfirm; - - // values in current Preflight transaction... used to avoid extra - // preflights when the parcel manager goes update crazy - S32 mPreflightAskBillableArea; - S32 mPreflightAskCurrencyBuy; - - LLViewChildren mChildren; - LLCurrencyUIManager mCurrency; - - enum TransactionType - { - TransactionPreflight, - TransactionCurrency, - TransactionBuy - }; - LLXMLRPCTransaction* mTransaction; - TransactionType mTransactionType; - - LLViewerParcelMgr::ParcelBuyInfo* mParcelBuyInfo; - -public: - void setForGroup(bool is_for_group); - void setParcel(LLViewerRegion* region, LLParcelSelectionHandle parcel); - - void updateAgentInfo(); - void updateParcelInfo(); - void updateCovenantInfo(); - static void onChangeAgreeCovenant(LLUICtrl* ctrl, void* user_data); - void updateFloaterCovenantText(const std::string& string, const LLUUID &asset_id); - void updateFloaterEstateName(const std::string& name); - void updateFloaterLastModified(const std::string& text); - void updateFloaterEstateOwnerName(const std::string& name); - void updateWebSiteInfo(); - void finishWebSiteInfo(); - - void runWebSitePrep(const std::string& password); - void finishWebSitePrep(); - void sendBuyLand(); - - void updateNames(); - // Name cache callback - void updateGroupName(const LLUUID& id, - const std::string& name, - bool is_group); - - void refreshUI(); - - void startTransaction(TransactionType type, const LLXMLRPCValue& params); - bool checkTransaction(); - - void tellUserError(const std::string& message, const std::string& uri); - - virtual bool postBuild(); - - void startBuyPreConfirm(); - void startBuyPostConfirm(const std::string& password); - - void onClickBuy(); - void onClickCancel(); - void onClickErrorWeb(); - - virtual void draw(); - virtual bool canClose(); - - void onVisibilityChanged ( const LLSD& new_visibility ); - -}; - -// static -void LLFloaterBuyLand::buyLand( - LLViewerRegion* region, LLParcelSelectionHandle parcel, bool is_for_group) -{ - if(is_for_group && !gAgent.hasPowerInActiveGroup(GP_LAND_DEED)) - { - LLNotificationsUtil::add("OnlyOfficerCanBuyLand"); - return; - } - - LLFloaterBuyLandUI* ui = LLFloaterReg::showTypedInstance("buy_land"); - if (ui) - { - ui->setForGroup(is_for_group); - ui->setParcel(region, parcel); - } -} - -// static -void LLFloaterBuyLand::updateCovenantText(const std::string& string, const LLUUID &asset_id) -{ - LLFloaterBuyLandUI* floater = LLFloaterReg::findTypedInstance("buy_land"); - if (floater) - { - floater->updateFloaterCovenantText(string, asset_id); - } -} - -// static -void LLFloaterBuyLand::updateEstateName(const std::string& name) -{ - LLFloaterBuyLandUI* floater = LLFloaterReg::findTypedInstance("buy_land"); - if (floater) - { - floater->updateFloaterEstateName(name); - } -} - -// static -void LLFloaterBuyLand::updateLastModified(const std::string& text) -{ - LLFloaterBuyLandUI* floater = LLFloaterReg::findTypedInstance("buy_land"); - if (floater) - { - floater->updateFloaterLastModified(text); - } -} - -// static -void LLFloaterBuyLand::updateEstateOwnerName(const std::string& name) -{ - LLFloaterBuyLandUI* floater = LLFloaterReg::findTypedInstance("buy_land"); - if (floater) - { - floater->updateFloaterEstateOwnerName(name); - } -} - -// static -LLFloater* LLFloaterBuyLand::buildFloater(const LLSD& key) -{ - LLFloaterBuyLandUI* floater = new LLFloaterBuyLandUI(key); - return floater; -} - -//---------------------------------------------------------------------------- -// LLFloaterBuyLandUI -//---------------------------------------------------------------------------- - -#if LL_WINDOWS -// passing 'this' during construction generates a warning. The callee -// only uses the pointer to hold a reference to 'this' which is -// already valid, so this call does the correct thing. Disable the -// warning so that we can compile without generating a warning. -#pragma warning(disable : 4355) -#endif -LLFloaterBuyLandUI::LLFloaterBuyLandUI(const LLSD& key) -: LLFloater(LLSD()), - mParcelSelectionObserver(this), - mParcel(0), - mBought(false), - mParcelValid(false), mSiteValid(false), - mChildren(*this), mCurrency(*this), mTransaction(0), - mParcelBuyInfo(0) -{ - LLViewerParcelMgr::getInstance()->addObserver(&mParcelSelectionObserver); - -} - -LLFloaterBuyLandUI::~LLFloaterBuyLandUI() -{ - LLViewerParcelMgr::getInstance()->removeObserver(&mParcelSelectionObserver); - LLViewerParcelMgr::getInstance()->deleteParcelBuy(&mParcelBuyInfo); - - delete mTransaction; -} - -// virtual -void LLFloaterBuyLandUI::onClose(bool app_quitting) -{ - // This object holds onto observer, transactions, and parcel state. - // Despite being single_instance, destroy it to call destructors and clean - // everything up. - setVisible(false); - destroy(); -} - -void LLFloaterBuyLandUI::SelectionObserver::changed() -{ - if (LLViewerParcelMgr::getInstance()->selectionEmpty()) - { - mFloater->closeFloater(); - } - else - { - mFloater->setParcel(LLViewerParcelMgr::getInstance()->getSelectionRegion(), - LLViewerParcelMgr::getInstance()->getParcelSelection()); - } -} - - -void LLFloaterBuyLandUI::updateAgentInfo() -{ - mAgentCommittedTier = gStatusBar->getSquareMetersCommitted(); - mAgentCashBalance = gStatusBar->getBalance(); - - // *TODO: This is an approximation, we should send this value down - // to the viewer. See SL-10728 for details. - mAgentHasNeverOwnedLand = mAgentCommittedTier == 0; -} - -void LLFloaterBuyLandUI::updateParcelInfo() -{ - LLParcel* parcel = mParcel->getParcel(); - mParcelValid = parcel && mRegion; - mParcelIsForSale = false; - mParcelIsGroupLand = false; - mParcelGroupContribution = 0; - mParcelPrice = 0; - mParcelActualArea = 0; - mParcelBillableArea = 0; - mParcelSupportedObjects = 0; - mParcelSoldWithObjects = false; - mParcelLocation = ""; - mParcelSnapshot.setNull(); - mParcelSellerName = ""; - - mCanBuy = false; - mCannotBuyIsError = false; - - if (!mParcelValid) - { - mCannotBuyReason = getString("no_land_selected"); - return; - } - - if (mParcel->getMultipleOwners()) - { - mCannotBuyReason = getString("multiple_parcels_selected"); - return; - } - - const LLUUID& parcelOwner = parcel->getOwnerID(); - - mIsClaim = parcel->isPublic(); - if (!mIsClaim) - { - mParcelActualArea = parcel->getArea(); - mParcelIsForSale = parcel->getForSale(); - mParcelIsGroupLand = parcel->getIsGroupOwned(); - mParcelPrice = mParcelIsForSale ? parcel->getSalePrice() : 0; - - if (mParcelIsGroupLand) - { - LLUUID group_id = parcel->getGroupID(); - mParcelGroupContribution = gAgent.getGroupContribution(group_id); - } - } - else - { - mParcelActualArea = mParcel->getClaimableArea(); - mParcelIsForSale = true; - mParcelPrice = mParcelActualArea * parcel->getClaimPricePerMeter(); - } - - mParcelBillableArea = - ll_round(mRegion->getBillableFactor() * mParcelActualArea); - - mParcelSupportedObjects = ll_round( - parcel->getMaxPrimCapacity() * parcel->getParcelPrimBonus()); - // Can't have more than region max tasks, regardless of parcel - // object bonus factor. - LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); - if(region) - { - S32 max_tasks_per_region = (S32)region->getMaxTasks(); - mParcelSupportedObjects = llmin( - mParcelSupportedObjects, max_tasks_per_region); - } - - mParcelSoldWithObjects = parcel->getSellWithObjects(); - - - LLVector3 center = parcel->getCenterpoint(); - mParcelLocation = llformat("%s %d,%d", - mRegion->getName().c_str(), - (int)center[VX], (int)center[VY] - ); - - mParcelSnapshot = parcel->getSnapshotID(); - - updateNames(); - - bool haveEnoughCash = mParcelPrice <= mAgentCashBalance; - S32 cashBuy = haveEnoughCash ? 0 : (mParcelPrice - mAgentCashBalance); - mCurrency.setAmount(cashBuy, true); - mCurrency.setZeroMessage(haveEnoughCash ? getString("none_needed") : LLStringUtil::null); - - // checks that we can buy the land - - if(mIsForGroup && !gAgent.hasPowerInActiveGroup(GP_LAND_DEED)) - { - mCannotBuyReason = getString("cant_buy_for_group"); - return; - } - - if (!mIsClaim) - { - const LLUUID& authorizedBuyer = parcel->getAuthorizedBuyerID(); - const LLUUID buyer = gAgent.getID(); - const LLUUID newOwner = mIsForGroup ? gAgent.getGroupID() : buyer; - - if (!mParcelIsForSale - || (mParcelPrice == 0 && authorizedBuyer.isNull())) - { - - mCannotBuyReason = getString("parcel_not_for_sale"); - return; - } - - if (parcelOwner == newOwner) - { - if (mIsForGroup) - { - mCannotBuyReason = getString("group_already_owns"); - } - else - { - mCannotBuyReason = getString("you_already_own"); - } - return; - } - - if (!authorizedBuyer.isNull() && buyer != authorizedBuyer) - { - // Maybe the parcel is set for sale to a group we are in. - bool authorized_group = - gAgent.hasPowerInGroup(authorizedBuyer,GP_LAND_DEED) - && gAgent.hasPowerInGroup(authorizedBuyer,GP_LAND_SET_SALE_INFO); - - if (!authorized_group) - { - mCannotBuyReason = getString("set_to_sell_to_other"); - return; - } - } - } - else - { - if (mParcelActualArea == 0) - { - mCannotBuyReason = getString("no_public_land"); - return; - } - - if (mParcel->hasOthersSelected()) - { - // Policy: Must not have someone else's land selected - mCannotBuyReason = getString("not_owned_by_you"); - return; - } - } - - mCanBuy = true; -} - -void LLFloaterBuyLandUI::updateCovenantInfo() -{ - LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); - if(!region) return; - - U8 sim_access = region->getSimAccess(); - std::string rating = LLViewerRegion::accessToString(sim_access); - - LLTextBox* region_name = getChild("region_name_text"); - if (region_name) - { - std::string region_name_txt = region->getName() + " ("+rating +")"; - region_name->setText(region_name_txt); - - LLIconCtrl* rating_icon = getChild("rating_icon"); - LLRect rect = rating_icon->getRect(); - S32 region_name_width = llmin(region_name->getRect().getWidth(), region_name->getTextBoundingRect().getWidth()); - S32 icon_left_pad = region_name->getRect().mLeft + region_name_width + ICON_PAD; - region_name->setToolTip(region_name->getText()); - rating_icon->setRect(rect.setOriginAndSize(icon_left_pad, rect.mBottom, rect.getWidth(), rect.getHeight())); - - switch(sim_access) - { - case SIM_ACCESS_PG: - rating_icon->setValue(getString("icon_PG")); - break; - - case SIM_ACCESS_ADULT: - rating_icon->setValue(getString("icon_R")); - break; - - default: - rating_icon->setValue(getString("icon_M")); - } - } - - LLTextBox* region_type = getChild("region_type_text"); - if (region_type) - { - region_type->setText(region->getLocalizedSimProductName()); - region_type->setToolTip(region->getLocalizedSimProductName()); - } - - LLTextBox* resellable_clause = getChild("resellable_clause"); - if (resellable_clause) - { - if (region->getRegionFlag(REGION_FLAGS_BLOCK_LAND_RESELL)) - { - resellable_clause->setText(getString("can_not_resell")); - } - else - { - resellable_clause->setText(getString("can_resell")); - } - } - - LLTextBox* changeable_clause = getChild("changeable_clause"); - if (changeable_clause) - { - if (region->getRegionFlag(REGION_FLAGS_ALLOW_PARCEL_CHANGES)) - { - changeable_clause->setText(getString("can_change")); - } - else - { - changeable_clause->setText(getString("can_not_change")); - } - } - - LLCheckBoxCtrl* check = getChild("agree_covenant"); - if(check) - { - check->set(false); - check->setEnabled(true); - check->setCommitCallback(onChangeAgreeCovenant, this); - } - - LLTextBox* box = getChild("covenant_text"); - if(box) - { - box->setVisible(false); - } - - // send EstateCovenantInfo message - LLMessageSystem *msg = gMessageSystem; - msg->newMessage("EstateCovenantRequest"); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); - msg->sendReliable(region->getHost()); -} - -// static -void LLFloaterBuyLandUI::onChangeAgreeCovenant(LLUICtrl* ctrl, void* user_data) -{ - LLFloaterBuyLandUI* self = (LLFloaterBuyLandUI*)user_data; - if(self) - { - self->refreshUI(); - } -} - -void LLFloaterBuyLandUI::updateFloaterCovenantText(const std::string &string, const LLUUID& asset_id) -{ - LLViewerTextEditor* editor = getChild("covenant_editor"); - editor->setText(string); - - LLCheckBoxCtrl* check = getChild("agree_covenant"); - LLTextBox* box = getChild("covenant_text"); - if (asset_id.isNull()) - { - check->set(true); - check->setEnabled(false); - refreshUI(); - - // remove the line stating that you must agree - box->setVisible(false); - } - else - { - check->setEnabled(true); - - // remove the line stating that you must agree - box->setVisible(true); - } -} - -void LLFloaterBuyLandUI::updateFloaterEstateName(const std::string& name) -{ - LLTextBox* box = getChild("estate_name_text"); - box->setText(name); - box->setToolTip(name); -} - -void LLFloaterBuyLandUI::updateFloaterLastModified(const std::string& text) -{ - LLTextBox* editor = getChild("covenant_timestamp_text"); - if (editor) editor->setText(text); -} - -void LLFloaterBuyLandUI::updateFloaterEstateOwnerName(const std::string& name) -{ - LLTextBox* box = getChild("estate_owner_text"); - if (box) box->setText(name); -} - -void LLFloaterBuyLandUI::updateWebSiteInfo() -{ - S32 askBillableArea = mIsForGroup ? 0 : mParcelBillableArea; - S32 askCurrencyBuy = mCurrency.getAmount(); - - if (mTransaction && mTransactionType == TransactionPreflight - && mPreflightAskBillableArea == askBillableArea - && mPreflightAskCurrencyBuy == askCurrencyBuy) - { - return; - } - - mPreflightAskBillableArea = askBillableArea; - mPreflightAskCurrencyBuy = askCurrencyBuy; - -#if 0 - // enable this code if you want the details to blank while we're talking - // to the web site... it's kind of jarring - mSiteValid = false; - mSiteMembershipUpgrade = false; - mSiteMembershipAction = "(waiting)"; - mSiteMembershipPlanIDs.clear(); - mSiteMembershipPlanNames.clear(); - mSiteLandUseUpgrade = false; - mSiteLandUseAction = "(waiting)"; - mSiteCurrencyEstimated = false; - mSiteCurrencyEstimatedCost = 0; -#endif - - LLXMLRPCValue keywordArgs = LLXMLRPCValue::createStruct(); - keywordArgs.appendString("agentId", gAgent.getID().asString()); - keywordArgs.appendString( - "secureSessionId", - gAgent.getSecureSessionID().asString()); - keywordArgs.appendString("language", LLUI::getLanguage()); - keywordArgs.appendInt("billableArea", mPreflightAskBillableArea); - keywordArgs.appendInt("currencyBuy", mPreflightAskCurrencyBuy); - - LLXMLRPCValue params = LLXMLRPCValue::createArray(); - params.append(keywordArgs); - - startTransaction(TransactionPreflight, params); -} - -void LLFloaterBuyLandUI::finishWebSiteInfo() -{ - - LLXMLRPCValue result = mTransaction->responseValue(); - - mSiteValid = result["success"].asBool(); - if (!mSiteValid) - { - tellUserError( - result["errorMessage"].asString(), - result["errorURI"].asString() - ); - return; - } - - LLXMLRPCValue membership = result["membership"]; - mSiteMembershipUpgrade = membership["upgrade"].asBool(); - mSiteMembershipAction = membership["action"].asString(); - mSiteMembershipPlanIDs.clear(); - mSiteMembershipPlanNames.clear(); - LLXMLRPCValue levels = membership["levels"]; - for (LLXMLRPCValue level = levels.rewind(); - level.isValid(); - level = levels.next()) - { - mSiteMembershipPlanIDs.push_back(level["id"].asString()); - mSiteMembershipPlanNames.push_back(level["description"].asString()); - } - mUserPlanChoice = 0; - - LLXMLRPCValue landUse = result["landUse"]; - mSiteLandUseUpgrade = landUse["upgrade"].asBool(); - mSiteLandUseAction = landUse["action"].asString(); - - LLXMLRPCValue currency = result["currency"]; - if (currency["estimatedCost"].isValid()) - { - mCurrency.setUSDEstimate(currency["estimatedCost"].asInt()); - } - if (currency["estimatedLocalCost"].isValid()) - { - mCurrency.setLocalEstimate(currency["estimatedLocalCost"].asString()); - } - - mSiteConfirm = result["confirm"].asString(); -} - -void LLFloaterBuyLandUI::runWebSitePrep(const std::string& password) -{ - if (!mCanBuy) - { - return; - } - - bool remove_contribution = getChild("remove_contribution")->getValue().asBoolean(); - mParcelBuyInfo = LLViewerParcelMgr::getInstance()->setupParcelBuy(gAgent.getID(), gAgent.getSessionID(), - gAgent.getGroupID(), mIsForGroup, mIsClaim, remove_contribution); - - if (mParcelBuyInfo - && !mSiteMembershipUpgrade - && !mSiteLandUseUpgrade - && mCurrency.getAmount() == 0 - && mSiteConfirm != "password") - { - sendBuyLand(); - return; - } - - - std::string newLevel = "noChange"; - - if (mSiteMembershipUpgrade) - { - LLComboBox* levels = getChild( "account_level"); - if (levels) - { - mUserPlanChoice = levels->getCurrentIndex(); - newLevel = mSiteMembershipPlanIDs[mUserPlanChoice]; - } - } - - LLXMLRPCValue keywordArgs = LLXMLRPCValue::createStruct(); - keywordArgs.appendString("agentId", gAgent.getID().asString()); - keywordArgs.appendString( - "secureSessionId", - gAgent.getSecureSessionID().asString()); - keywordArgs.appendString("language", LLUI::getLanguage()); - keywordArgs.appendString("levelId", newLevel); - keywordArgs.appendInt("billableArea", - mIsForGroup ? 0 : mParcelBillableArea); - keywordArgs.appendInt("currencyBuy", mCurrency.getAmount()); - keywordArgs.appendInt("estimatedCost", mCurrency.getUSDEstimate()); - keywordArgs.appendString("estimatedLocalCost", mCurrency.getLocalEstimate()); - keywordArgs.appendString("confirm", mSiteConfirm); - if (!password.empty()) - { - keywordArgs.appendString("password", password); - } - - LLXMLRPCValue params = LLXMLRPCValue::createArray(); - params.append(keywordArgs); - - startTransaction(TransactionBuy, params); -} - -void LLFloaterBuyLandUI::finishWebSitePrep() -{ - LLXMLRPCValue result = mTransaction->responseValue(); - - bool success = result["success"].asBool(); - if (!success) - { - tellUserError( - result["errorMessage"].asString(), - result["errorURI"].asString() - ); - return; - } - - sendBuyLand(); -} - -void LLFloaterBuyLandUI::sendBuyLand() -{ - if (mParcelBuyInfo) - { - LLViewerParcelMgr::getInstance()->sendParcelBuy(mParcelBuyInfo); - LLViewerParcelMgr::getInstance()->deleteParcelBuy(&mParcelBuyInfo); - mBought = true; - } -} - -void LLFloaterBuyLandUI::updateNames() -{ - LLParcel* parcelp = mParcel->getParcel(); - - if (!parcelp) - { - mParcelSellerName = LLStringUtil::null; - return; - } - - if (mIsClaim) - { - mParcelSellerName = "Linden Lab"; - } - else if (parcelp->getIsGroupOwned()) - { - gCacheName->getGroup(parcelp->getGroupID(), - boost::bind(&LLFloaterBuyLandUI::updateGroupName, this, - _1, _2, _3)); - } - else - { - mParcelSellerName = LLSLURL("agent", parcelp->getOwnerID(), "completename").getSLURLString(); - } -} - -void LLFloaterBuyLandUI::updateGroupName(const LLUUID& id, - const std::string& name, - bool is_group) -{ - LLParcel* parcelp = mParcel->getParcel(); - if (parcelp - && parcelp->getGroupID() == id) - { - // request is current - mParcelSellerName = name; - } -} - -void LLFloaterBuyLandUI::startTransaction(TransactionType type, const LLXMLRPCValue& params) -{ - delete mTransaction; - mTransaction = NULL; - - mTransactionType = type; - - // Select a URI and method appropriate for the transaction type. - static std::string transaction_uri; - if (transaction_uri.empty()) - { - transaction_uri = LLGridManager::getInstance()->getHelperURI() + "landtool.php"; - } - - const char* method; - switch (mTransactionType) - { - case TransactionPreflight: - method = "preflightBuyLandPrep"; - break; - case TransactionBuy: - method = "buyLandPrep"; - break; - default: - LL_WARNS() << "LLFloaterBuyLandUI: Unknown transaction type!" << LL_ENDL; - return; - } - - mTransaction = new LLXMLRPCTransaction( - transaction_uri, - method, - params, - false /* don't use gzip */ - ); -} - -bool LLFloaterBuyLandUI::checkTransaction() -{ - if (!mTransaction) - { - return false; - } - - if (!mTransaction->process()) - { - return false; - } - - if (mTransaction->status(NULL) != LLXMLRPCTransaction::StatusComplete) - { - tellUserError(mTransaction->statusMessage(), mTransaction->statusURI()); - } - else { - switch (mTransactionType) - { - case TransactionPreflight: finishWebSiteInfo(); break; - case TransactionBuy: finishWebSitePrep(); break; - default: ; - } - } - - delete mTransaction; - mTransaction = NULL; - - return true; -} - -void LLFloaterBuyLandUI::tellUserError( - const std::string& message, const std::string& uri) -{ - mCanBuy = false; - mCannotBuyIsError = true; - mCannotBuyReason = getString("fetching_error"); - mCannotBuyReason += message; - mCannotBuyURI = uri; -} - - -// virtual -bool LLFloaterBuyLandUI::postBuild() -{ - setVisibleCallback(boost::bind(&LLFloaterBuyLandUI::onVisibilityChanged, this, _2)); - - mCurrency.prepare(); - - getChild("buy_btn")->setCommitCallback( boost::bind(&LLFloaterBuyLandUI::onClickBuy, this)); - getChild("cancel_btn")->setCommitCallback( boost::bind(&LLFloaterBuyLandUI::onClickCancel, this)); - getChild("error_web")->setCommitCallback( boost::bind(&LLFloaterBuyLandUI::onClickErrorWeb, this)); - - center(); - - return true; -} - -void LLFloaterBuyLandUI::setParcel(LLViewerRegion* region, LLParcelSelectionHandle parcel) -{ - if (mTransaction && mTransactionType == TransactionBuy) - { - // the user is buying, don't change the selection - return; - } - - mRegion = region; - mParcel = parcel; - - updateAgentInfo(); - updateParcelInfo(); - updateCovenantInfo(); - if (mCanBuy) - { - updateWebSiteInfo(); - } - refreshUI(); -} - -void LLFloaterBuyLandUI::setForGroup(bool forGroup) -{ - mIsForGroup = forGroup; -} - -void LLFloaterBuyLandUI::draw() -{ - LLFloater::draw(); - - bool needsUpdate = false; - needsUpdate |= checkTransaction(); - needsUpdate |= mCurrency.process(); - - if (mBought) - { - closeFloater(); - } - else if (needsUpdate) - { - if (mCanBuy && mCurrency.hasError()) - { - tellUserError(mCurrency.errorMessage(), mCurrency.errorURI()); - } - - refreshUI(); - } -} - -// virtual -bool LLFloaterBuyLandUI::canClose() -{ - // mTransactionType check for pre-buy estimation stage and mCurrency to allow exit after transaction - bool can_close = !mTransaction && (mTransactionType != TransactionBuy || mCurrency.canCancel()); - if (!can_close) - { - // explain to user why they can't do this, see DEV-9605 - LLNotificationsUtil::add("CannotCloseFloaterBuyLand"); - } - return can_close; -} - -void LLFloaterBuyLandUI::onVisibilityChanged ( const LLSD& new_visibility ) -{ - if (new_visibility.asBoolean()) - { - refreshUI(); - } -} - -void LLFloaterBuyLandUI::refreshUI() -{ - // section zero: title area - { - LLTextureCtrl* snapshot = getChild("info_image"); - if (snapshot) - { - snapshot->setImageAssetID( - mParcelValid ? mParcelSnapshot : LLUUID::null); - } - - if (mParcelValid) - { - getChild("info_parcel")->setValue(mParcelLocation); - - LLStringUtil::format_map_t string_args; - string_args["[AMOUNT]"] = llformat("%d", mParcelActualArea); - string_args["[AMOUNT2]"] = llformat("%d", mParcelSupportedObjects); - - getChild("info_size")->setValue(getString("meters_supports_object", string_args)); - - F32 cost_per_sqm = 0.0f; - if (mParcelActualArea > 0) - { - cost_per_sqm = (F32)mParcelPrice / (F32)mParcelActualArea; - } - - LLStringUtil::format_map_t info_price_args; - info_price_args["[PRICE]"] = llformat("%d", mParcelPrice); - info_price_args["[PRICE_PER_SQM]"] = llformat("%.1f", cost_per_sqm); - if (mParcelSoldWithObjects) - { - info_price_args["[SOLD_WITH_OBJECTS]"] = getString("sold_with_objects"); - } - else - { - info_price_args["[SOLD_WITH_OBJECTS]"] = getString("sold_without_objects"); - } - getChild("info_price")->setValue(getString("info_price_string", info_price_args)); - getChildView("info_price")->setVisible( mParcelIsForSale); - } - else - { - getChild("info_parcel")->setValue(getString("no_parcel_selected")); - getChild("info_size")->setValue(LLStringUtil::null); - getChild("info_price")->setValue(LLStringUtil::null); - } - - getChild("info_action")->setValue( - mCanBuy - ? - mIsForGroup - ? getString("buying_for_group")//"Buying land for group:" - : getString("buying_will")//"Buying this land will:" - : - mCannotBuyIsError - ? getString("cannot_buy_now")//"Cannot buy now:" - : getString("not_for_sale")//"Not for sale:" - - ); - } - - bool showingError = !mCanBuy || !mSiteValid; - - // error section - if (showingError) - { - mChildren.setBadge(std::string("step_error"), - mCannotBuyIsError - ? LLViewChildren::BADGE_ERROR - : LLViewChildren::BADGE_WARN); - - LLTextBox* message = getChild("error_message"); - if (message) - { - message->setVisible(true); - message->setValue(LLSD(!mCanBuy ? mCannotBuyReason : "(waiting for data)")); - } - - getChildView("error_web")->setVisible(mCannotBuyIsError && !mCannotBuyURI.empty()); - } - else - { - getChildView("step_error")->setVisible(false); - getChildView("error_message")->setVisible(false); - getChildView("error_web")->setVisible(false); - } - - - // section one: account - if (!showingError) - { - mChildren.setBadge(std::string("step_1"), - mSiteMembershipUpgrade - ? LLViewChildren::BADGE_NOTE - : LLViewChildren::BADGE_OK); - getChild("account_action")->setValue(mSiteMembershipAction); - getChild("account_reason")->setValue( - mSiteMembershipUpgrade - ? getString("must_upgrade") - : getString("cant_own_land") - ); - - LLComboBox* levels = getChild( "account_level"); - if (levels) - { - levels->setVisible(mSiteMembershipUpgrade); - - levels->removeall(); - for(std::vector::const_iterator i - = mSiteMembershipPlanNames.begin(); - i != mSiteMembershipPlanNames.end(); - ++i) - { - levels->add(*i); - } - - levels->setCurrentByIndex(mUserPlanChoice); - } - - getChildView("step_1")->setVisible(true); - getChildView("account_action")->setVisible(true); - getChildView("account_reason")->setVisible(true); - } - else - { - getChildView("step_1")->setVisible(false); - getChildView("account_action")->setVisible(false); - getChildView("account_reason")->setVisible(false); - getChildView("account_level")->setVisible(false); - } - - // section two: land use fees - if (!showingError) - { - mChildren.setBadge(std::string("step_2"), - mSiteLandUseUpgrade - ? LLViewChildren::BADGE_NOTE - : LLViewChildren::BADGE_OK); - getChild("land_use_action")->setValue(mSiteLandUseAction); - - std::string message; - - if (mIsForGroup) - { - LLStringUtil::format_map_t string_args; - string_args["[GROUP]"] = std::string(gAgent.getGroupName()); - - message += getString("insufficient_land_credits", string_args); - - } - else - { - LLStringUtil::format_map_t string_args; - string_args["[BUYER]"] = llformat("%d", mAgentCommittedTier); - message += getString("land_holdings", string_args); - } - - if (!mParcelValid) - { - message += LLTrans::getString("sentences_separator") + getString("no_parcel_selected"); - } - else if (mParcelBillableArea == mParcelActualArea) - { - LLStringUtil::format_map_t string_args; - string_args["[AMOUNT]"] = llformat("%d ", mParcelActualArea); - message += LLTrans::getString("sentences_separator") + getString("parcel_meters", string_args); - } - else - { - - if (mParcelBillableArea > mParcelActualArea) - { - LLStringUtil::format_map_t string_args; - string_args["[AMOUNT]"] = llformat("%d ", mParcelBillableArea); - message += LLTrans::getString("sentences_separator") + getString("premium_land", string_args); - } - else - { - LLStringUtil::format_map_t string_args; - string_args["[AMOUNT]"] = llformat("%d ", mParcelBillableArea); - message += LLTrans::getString("sentences_separator") + getString("discounted_land", string_args); - } - } - - getChild("land_use_reason")->setValue(message); - - getChildView("step_2")->setVisible(true); - getChildView("land_use_action")->setVisible(true); - getChildView("land_use_reason")->setVisible(true); - } - else - { - getChildView("step_2")->setVisible(false); - getChildView("land_use_action")->setVisible(false); - getChildView("land_use_reason")->setVisible(false); - } - - // section three: purchase & currency - S32 finalBalance = mAgentCashBalance + mCurrency.getAmount() - mParcelPrice; - bool willHaveEnough = finalBalance >= 0; - bool haveEnough = mAgentCashBalance >= mParcelPrice; - S32 minContribution = llceil((F32)mParcelBillableArea / GROUP_LAND_BONUS_FACTOR); - bool groupContributionEnough = mParcelGroupContribution >= minContribution; - - mCurrency.updateUI(!showingError && !haveEnough); - - if (!showingError) - { - mChildren.setBadge(std::string("step_3"), - !willHaveEnough - ? LLViewChildren::BADGE_WARN - : mCurrency.getAmount() > 0 - ? LLViewChildren::BADGE_NOTE - : LLViewChildren::BADGE_OK); - - LLStringUtil::format_map_t string_args; - string_args["[AMOUNT]"] = llformat("%d", mParcelPrice); - string_args["[SELLER]"] = mParcelSellerName; - getChild("purchase_action")->setValue(getString("pay_to_for_land", string_args)); - getChildView("purchase_action")->setVisible( mParcelValid); - - std::string reasonString; - - if (haveEnough) - { - LLStringUtil::format_map_t string_args; - string_args["[AMOUNT]"] = llformat("%d", mAgentCashBalance); - - getChild("currency_reason")->setValue(getString("have_enough_lindens", string_args)); - } - else - { - LLStringUtil::format_map_t string_args; - string_args["[AMOUNT]"] = llformat("%d", mAgentCashBalance); - string_args["[AMOUNT2]"] = llformat("%d", mParcelPrice - mAgentCashBalance); - - getChild("currency_reason")->setValue(getString("not_enough_lindens", string_args)); - - getChild("currency_est")->setTextArg("[LOCAL_AMOUNT]", mCurrency.getLocalEstimate()); - } - - if (willHaveEnough) - { - LLStringUtil::format_map_t string_args; - string_args["[AMOUNT]"] = llformat("%d", finalBalance); - - getChild("currency_balance")->setValue(getString("balance_left", string_args)); - - } - else - { - LLStringUtil::format_map_t string_args; - string_args["[AMOUNT]"] = llformat("%d", mParcelPrice - mAgentCashBalance); - - getChild("currency_balance")->setValue(getString("balance_needed", string_args)); - - } - - getChild("remove_contribution")->setValue(LLSD(groupContributionEnough)); - getChildView("remove_contribution")->setEnabled(groupContributionEnough); - bool showRemoveContribution = mParcelIsGroupLand - && (mParcelGroupContribution > 0); - getChildView("remove_contribution")->setLabelArg("[AMOUNT]", - llformat("%d", minContribution)); - getChildView("remove_contribution")->setVisible( showRemoveContribution); - - getChildView("step_3")->setVisible(true); - getChildView("purchase_action")->setVisible(true); - getChildView("currency_reason")->setVisible(true); - getChildView("currency_balance")->setVisible(true); - } - else - { - getChildView("step_3")->setVisible(false); - getChildView("purchase_action")->setVisible(false); - getChildView("currency_reason")->setVisible(false); - getChildView("currency_balance")->setVisible(false); - getChildView("remove_group_donation")->setVisible(false); - } - - - bool agrees_to_covenant = false; - LLCheckBoxCtrl* check = getChild("agree_covenant"); - if (check) - { - agrees_to_covenant = check->get(); - } - - getChildView("buy_btn")->setEnabled(mCanBuy && mSiteValid && willHaveEnough && !mTransaction && agrees_to_covenant); -} - -void LLFloaterBuyLandUI::startBuyPreConfirm() -{ - std::string action; - - if (mSiteMembershipUpgrade) - { - action += mSiteMembershipAction; - action += "\n"; - - LLComboBox* levels = getChild( "account_level"); - if (levels) - { - action += " * "; - action += mSiteMembershipPlanNames[levels->getCurrentIndex()]; - action += "\n"; - } - } - if (mSiteLandUseUpgrade) - { - action += mSiteLandUseAction; - action += "\n"; - } - if (mCurrency.getAmount() > 0) - { - LLStringUtil::format_map_t string_args; - string_args["[AMOUNT]"] = llformat("%d", mCurrency.getAmount()); - string_args["[LOCAL_AMOUNT]"] = mCurrency.getLocalEstimate(); - - action += getString("buy_for_US", string_args); - } - - LLStringUtil::format_map_t string_args; - string_args["[AMOUNT]"] = llformat("%d", mParcelPrice); - string_args["[SELLER]"] = mParcelSellerName; - action += getString("pay_to_for_land", string_args); - - - LLConfirmationManager::confirm(mSiteConfirm, - action, - *this, - &LLFloaterBuyLandUI::startBuyPostConfirm); -} - -void LLFloaterBuyLandUI::startBuyPostConfirm(const std::string& password) -{ - runWebSitePrep(password); - - mCanBuy = false; - mCannotBuyReason = getString("processing"); - refreshUI(); -} - - -void LLFloaterBuyLandUI::onClickBuy() -{ - startBuyPreConfirm(); -} - -void LLFloaterBuyLandUI::onClickCancel() -{ - closeFloater(); -} - -void LLFloaterBuyLandUI::onClickErrorWeb() -{ - LLWeb::loadURLExternal(mCannotBuyURI); - closeFloater(); -} - - - +/** + * @file llfloaterbuyland.cpp + * @brief LLFloaterBuyLand class implementation + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterbuyland.h" + +// viewer includes +#include "llagent.h" +#include "llbutton.h" +#include "llcachename.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "llconfirmationmanager.h" +#include "llcurrencyuimanager.h" +#include "llfloater.h" +#include "llfloaterreg.h" +#include "llfloatertools.h" +#include "llframetimer.h" +#include "lliconctrl.h" +#include "lllineeditor.h" +#include "llnotificationsutil.h" +#include "llparcel.h" +#include "llslurl.h" +#include "llstatusbar.h" +#include "lltextbox.h" +#include "lltexturectrl.h" +#include "lltrans.h" +#include "llviewchildren.h" +#include "llviewercontrol.h" +#include "lluictrlfactory.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "llviewertexteditor.h" +#include "llviewerwindow.h" +#include "llweb.h" +#include "llwindow.h" +#include "llworld.h" +#include "llxmlrpctransaction.h" +#include "llviewernetwork.h" +#include "roles_constants.h" + +// NOTE: This is duplicated in lldatamoney.cpp ... +const F32 GROUP_LAND_BONUS_FACTOR = 1.1f; + +class LLFloaterBuyLandUI +: public LLFloater +{ +public: + LLFloaterBuyLandUI(const LLSD& key); + virtual ~LLFloaterBuyLandUI(); + + /*virtual*/ void onClose(bool app_quitting); + + // Left padding for maturity rating icon. + static const S32 ICON_PAD = 2; + +private: + class SelectionObserver : public LLParcelObserver + { + public: + SelectionObserver(LLFloaterBuyLandUI* floater) : mFloater(floater) {} + virtual void changed(); + private: + LLFloaterBuyLandUI* mFloater; + }; + +private: + SelectionObserver mParcelSelectionObserver; + LLViewerRegion* mRegion; + LLParcelSelectionHandle mParcel; + bool mIsClaim; + bool mIsForGroup; + + bool mCanBuy; + bool mCannotBuyIsError; + std::string mCannotBuyReason; + std::string mCannotBuyURI; + + bool mBought; + + // information about the agent + S32 mAgentCommittedTier; + S32 mAgentCashBalance; + bool mAgentHasNeverOwnedLand; + + // information about the parcel + bool mParcelValid; + bool mParcelIsForSale; + bool mParcelIsGroupLand; + S32 mParcelGroupContribution; + S32 mParcelPrice; + S32 mParcelActualArea; + S32 mParcelBillableArea; + S32 mParcelSupportedObjects; + bool mParcelSoldWithObjects; + std::string mParcelLocation; + LLUUID mParcelSnapshot; + std::string mParcelSellerName; + + // user's choices + S32 mUserPlanChoice; + + // from website + bool mSiteValid; + bool mSiteMembershipUpgrade; + std::string mSiteMembershipAction; + std::vector + mSiteMembershipPlanIDs; + std::vector + mSiteMembershipPlanNames; + bool mSiteLandUseUpgrade; + std::string mSiteLandUseAction; + std::string mSiteConfirm; + + // values in current Preflight transaction... used to avoid extra + // preflights when the parcel manager goes update crazy + S32 mPreflightAskBillableArea; + S32 mPreflightAskCurrencyBuy; + + LLViewChildren mChildren; + LLCurrencyUIManager mCurrency; + + enum TransactionType + { + TransactionPreflight, + TransactionCurrency, + TransactionBuy + }; + LLXMLRPCTransaction* mTransaction; + TransactionType mTransactionType; + + LLViewerParcelMgr::ParcelBuyInfo* mParcelBuyInfo; + +public: + void setForGroup(bool is_for_group); + void setParcel(LLViewerRegion* region, LLParcelSelectionHandle parcel); + + void updateAgentInfo(); + void updateParcelInfo(); + void updateCovenantInfo(); + static void onChangeAgreeCovenant(LLUICtrl* ctrl, void* user_data); + void updateFloaterCovenantText(const std::string& string, const LLUUID &asset_id); + void updateFloaterEstateName(const std::string& name); + void updateFloaterLastModified(const std::string& text); + void updateFloaterEstateOwnerName(const std::string& name); + void updateWebSiteInfo(); + void finishWebSiteInfo(); + + void runWebSitePrep(const std::string& password); + void finishWebSitePrep(); + void sendBuyLand(); + + void updateNames(); + // Name cache callback + void updateGroupName(const LLUUID& id, + const std::string& name, + bool is_group); + + void refreshUI(); + + void startTransaction(TransactionType type, const LLXMLRPCValue& params); + bool checkTransaction(); + + void tellUserError(const std::string& message, const std::string& uri); + + virtual bool postBuild(); + + void startBuyPreConfirm(); + void startBuyPostConfirm(const std::string& password); + + void onClickBuy(); + void onClickCancel(); + void onClickErrorWeb(); + + virtual void draw(); + virtual bool canClose(); + + void onVisibilityChanged ( const LLSD& new_visibility ); + +}; + +// static +void LLFloaterBuyLand::buyLand( + LLViewerRegion* region, LLParcelSelectionHandle parcel, bool is_for_group) +{ + if(is_for_group && !gAgent.hasPowerInActiveGroup(GP_LAND_DEED)) + { + LLNotificationsUtil::add("OnlyOfficerCanBuyLand"); + return; + } + + LLFloaterBuyLandUI* ui = LLFloaterReg::showTypedInstance("buy_land"); + if (ui) + { + ui->setForGroup(is_for_group); + ui->setParcel(region, parcel); + } +} + +// static +void LLFloaterBuyLand::updateCovenantText(const std::string& string, const LLUUID &asset_id) +{ + LLFloaterBuyLandUI* floater = LLFloaterReg::findTypedInstance("buy_land"); + if (floater) + { + floater->updateFloaterCovenantText(string, asset_id); + } +} + +// static +void LLFloaterBuyLand::updateEstateName(const std::string& name) +{ + LLFloaterBuyLandUI* floater = LLFloaterReg::findTypedInstance("buy_land"); + if (floater) + { + floater->updateFloaterEstateName(name); + } +} + +// static +void LLFloaterBuyLand::updateLastModified(const std::string& text) +{ + LLFloaterBuyLandUI* floater = LLFloaterReg::findTypedInstance("buy_land"); + if (floater) + { + floater->updateFloaterLastModified(text); + } +} + +// static +void LLFloaterBuyLand::updateEstateOwnerName(const std::string& name) +{ + LLFloaterBuyLandUI* floater = LLFloaterReg::findTypedInstance("buy_land"); + if (floater) + { + floater->updateFloaterEstateOwnerName(name); + } +} + +// static +LLFloater* LLFloaterBuyLand::buildFloater(const LLSD& key) +{ + LLFloaterBuyLandUI* floater = new LLFloaterBuyLandUI(key); + return floater; +} + +//---------------------------------------------------------------------------- +// LLFloaterBuyLandUI +//---------------------------------------------------------------------------- + +#if LL_WINDOWS +// passing 'this' during construction generates a warning. The callee +// only uses the pointer to hold a reference to 'this' which is +// already valid, so this call does the correct thing. Disable the +// warning so that we can compile without generating a warning. +#pragma warning(disable : 4355) +#endif +LLFloaterBuyLandUI::LLFloaterBuyLandUI(const LLSD& key) +: LLFloater(LLSD()), + mParcelSelectionObserver(this), + mParcel(0), + mBought(false), + mParcelValid(false), mSiteValid(false), + mChildren(*this), mCurrency(*this), mTransaction(0), + mParcelBuyInfo(0) +{ + LLViewerParcelMgr::getInstance()->addObserver(&mParcelSelectionObserver); + +} + +LLFloaterBuyLandUI::~LLFloaterBuyLandUI() +{ + LLViewerParcelMgr::getInstance()->removeObserver(&mParcelSelectionObserver); + LLViewerParcelMgr::getInstance()->deleteParcelBuy(&mParcelBuyInfo); + + delete mTransaction; +} + +// virtual +void LLFloaterBuyLandUI::onClose(bool app_quitting) +{ + // This object holds onto observer, transactions, and parcel state. + // Despite being single_instance, destroy it to call destructors and clean + // everything up. + setVisible(false); + destroy(); +} + +void LLFloaterBuyLandUI::SelectionObserver::changed() +{ + if (LLViewerParcelMgr::getInstance()->selectionEmpty()) + { + mFloater->closeFloater(); + } + else + { + mFloater->setParcel(LLViewerParcelMgr::getInstance()->getSelectionRegion(), + LLViewerParcelMgr::getInstance()->getParcelSelection()); + } +} + + +void LLFloaterBuyLandUI::updateAgentInfo() +{ + mAgentCommittedTier = gStatusBar->getSquareMetersCommitted(); + mAgentCashBalance = gStatusBar->getBalance(); + + // *TODO: This is an approximation, we should send this value down + // to the viewer. See SL-10728 for details. + mAgentHasNeverOwnedLand = mAgentCommittedTier == 0; +} + +void LLFloaterBuyLandUI::updateParcelInfo() +{ + LLParcel* parcel = mParcel->getParcel(); + mParcelValid = parcel && mRegion; + mParcelIsForSale = false; + mParcelIsGroupLand = false; + mParcelGroupContribution = 0; + mParcelPrice = 0; + mParcelActualArea = 0; + mParcelBillableArea = 0; + mParcelSupportedObjects = 0; + mParcelSoldWithObjects = false; + mParcelLocation = ""; + mParcelSnapshot.setNull(); + mParcelSellerName = ""; + + mCanBuy = false; + mCannotBuyIsError = false; + + if (!mParcelValid) + { + mCannotBuyReason = getString("no_land_selected"); + return; + } + + if (mParcel->getMultipleOwners()) + { + mCannotBuyReason = getString("multiple_parcels_selected"); + return; + } + + const LLUUID& parcelOwner = parcel->getOwnerID(); + + mIsClaim = parcel->isPublic(); + if (!mIsClaim) + { + mParcelActualArea = parcel->getArea(); + mParcelIsForSale = parcel->getForSale(); + mParcelIsGroupLand = parcel->getIsGroupOwned(); + mParcelPrice = mParcelIsForSale ? parcel->getSalePrice() : 0; + + if (mParcelIsGroupLand) + { + LLUUID group_id = parcel->getGroupID(); + mParcelGroupContribution = gAgent.getGroupContribution(group_id); + } + } + else + { + mParcelActualArea = mParcel->getClaimableArea(); + mParcelIsForSale = true; + mParcelPrice = mParcelActualArea * parcel->getClaimPricePerMeter(); + } + + mParcelBillableArea = + ll_round(mRegion->getBillableFactor() * mParcelActualArea); + + mParcelSupportedObjects = ll_round( + parcel->getMaxPrimCapacity() * parcel->getParcelPrimBonus()); + // Can't have more than region max tasks, regardless of parcel + // object bonus factor. + LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); + if(region) + { + S32 max_tasks_per_region = (S32)region->getMaxTasks(); + mParcelSupportedObjects = llmin( + mParcelSupportedObjects, max_tasks_per_region); + } + + mParcelSoldWithObjects = parcel->getSellWithObjects(); + + + LLVector3 center = parcel->getCenterpoint(); + mParcelLocation = llformat("%s %d,%d", + mRegion->getName().c_str(), + (int)center[VX], (int)center[VY] + ); + + mParcelSnapshot = parcel->getSnapshotID(); + + updateNames(); + + bool haveEnoughCash = mParcelPrice <= mAgentCashBalance; + S32 cashBuy = haveEnoughCash ? 0 : (mParcelPrice - mAgentCashBalance); + mCurrency.setAmount(cashBuy, true); + mCurrency.setZeroMessage(haveEnoughCash ? getString("none_needed") : LLStringUtil::null); + + // checks that we can buy the land + + if(mIsForGroup && !gAgent.hasPowerInActiveGroup(GP_LAND_DEED)) + { + mCannotBuyReason = getString("cant_buy_for_group"); + return; + } + + if (!mIsClaim) + { + const LLUUID& authorizedBuyer = parcel->getAuthorizedBuyerID(); + const LLUUID buyer = gAgent.getID(); + const LLUUID newOwner = mIsForGroup ? gAgent.getGroupID() : buyer; + + if (!mParcelIsForSale + || (mParcelPrice == 0 && authorizedBuyer.isNull())) + { + + mCannotBuyReason = getString("parcel_not_for_sale"); + return; + } + + if (parcelOwner == newOwner) + { + if (mIsForGroup) + { + mCannotBuyReason = getString("group_already_owns"); + } + else + { + mCannotBuyReason = getString("you_already_own"); + } + return; + } + + if (!authorizedBuyer.isNull() && buyer != authorizedBuyer) + { + // Maybe the parcel is set for sale to a group we are in. + bool authorized_group = + gAgent.hasPowerInGroup(authorizedBuyer,GP_LAND_DEED) + && gAgent.hasPowerInGroup(authorizedBuyer,GP_LAND_SET_SALE_INFO); + + if (!authorized_group) + { + mCannotBuyReason = getString("set_to_sell_to_other"); + return; + } + } + } + else + { + if (mParcelActualArea == 0) + { + mCannotBuyReason = getString("no_public_land"); + return; + } + + if (mParcel->hasOthersSelected()) + { + // Policy: Must not have someone else's land selected + mCannotBuyReason = getString("not_owned_by_you"); + return; + } + } + + mCanBuy = true; +} + +void LLFloaterBuyLandUI::updateCovenantInfo() +{ + LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); + if(!region) return; + + U8 sim_access = region->getSimAccess(); + std::string rating = LLViewerRegion::accessToString(sim_access); + + LLTextBox* region_name = getChild("region_name_text"); + if (region_name) + { + std::string region_name_txt = region->getName() + " ("+rating +")"; + region_name->setText(region_name_txt); + + LLIconCtrl* rating_icon = getChild("rating_icon"); + LLRect rect = rating_icon->getRect(); + S32 region_name_width = llmin(region_name->getRect().getWidth(), region_name->getTextBoundingRect().getWidth()); + S32 icon_left_pad = region_name->getRect().mLeft + region_name_width + ICON_PAD; + region_name->setToolTip(region_name->getText()); + rating_icon->setRect(rect.setOriginAndSize(icon_left_pad, rect.mBottom, rect.getWidth(), rect.getHeight())); + + switch(sim_access) + { + case SIM_ACCESS_PG: + rating_icon->setValue(getString("icon_PG")); + break; + + case SIM_ACCESS_ADULT: + rating_icon->setValue(getString("icon_R")); + break; + + default: + rating_icon->setValue(getString("icon_M")); + } + } + + LLTextBox* region_type = getChild("region_type_text"); + if (region_type) + { + region_type->setText(region->getLocalizedSimProductName()); + region_type->setToolTip(region->getLocalizedSimProductName()); + } + + LLTextBox* resellable_clause = getChild("resellable_clause"); + if (resellable_clause) + { + if (region->getRegionFlag(REGION_FLAGS_BLOCK_LAND_RESELL)) + { + resellable_clause->setText(getString("can_not_resell")); + } + else + { + resellable_clause->setText(getString("can_resell")); + } + } + + LLTextBox* changeable_clause = getChild("changeable_clause"); + if (changeable_clause) + { + if (region->getRegionFlag(REGION_FLAGS_ALLOW_PARCEL_CHANGES)) + { + changeable_clause->setText(getString("can_change")); + } + else + { + changeable_clause->setText(getString("can_not_change")); + } + } + + LLCheckBoxCtrl* check = getChild("agree_covenant"); + if(check) + { + check->set(false); + check->setEnabled(true); + check->setCommitCallback(onChangeAgreeCovenant, this); + } + + LLTextBox* box = getChild("covenant_text"); + if(box) + { + box->setVisible(false); + } + + // send EstateCovenantInfo message + LLMessageSystem *msg = gMessageSystem; + msg->newMessage("EstateCovenantRequest"); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); + msg->sendReliable(region->getHost()); +} + +// static +void LLFloaterBuyLandUI::onChangeAgreeCovenant(LLUICtrl* ctrl, void* user_data) +{ + LLFloaterBuyLandUI* self = (LLFloaterBuyLandUI*)user_data; + if(self) + { + self->refreshUI(); + } +} + +void LLFloaterBuyLandUI::updateFloaterCovenantText(const std::string &string, const LLUUID& asset_id) +{ + LLViewerTextEditor* editor = getChild("covenant_editor"); + editor->setText(string); + + LLCheckBoxCtrl* check = getChild("agree_covenant"); + LLTextBox* box = getChild("covenant_text"); + if (asset_id.isNull()) + { + check->set(true); + check->setEnabled(false); + refreshUI(); + + // remove the line stating that you must agree + box->setVisible(false); + } + else + { + check->setEnabled(true); + + // remove the line stating that you must agree + box->setVisible(true); + } +} + +void LLFloaterBuyLandUI::updateFloaterEstateName(const std::string& name) +{ + LLTextBox* box = getChild("estate_name_text"); + box->setText(name); + box->setToolTip(name); +} + +void LLFloaterBuyLandUI::updateFloaterLastModified(const std::string& text) +{ + LLTextBox* editor = getChild("covenant_timestamp_text"); + if (editor) editor->setText(text); +} + +void LLFloaterBuyLandUI::updateFloaterEstateOwnerName(const std::string& name) +{ + LLTextBox* box = getChild("estate_owner_text"); + if (box) box->setText(name); +} + +void LLFloaterBuyLandUI::updateWebSiteInfo() +{ + S32 askBillableArea = mIsForGroup ? 0 : mParcelBillableArea; + S32 askCurrencyBuy = mCurrency.getAmount(); + + if (mTransaction && mTransactionType == TransactionPreflight + && mPreflightAskBillableArea == askBillableArea + && mPreflightAskCurrencyBuy == askCurrencyBuy) + { + return; + } + + mPreflightAskBillableArea = askBillableArea; + mPreflightAskCurrencyBuy = askCurrencyBuy; + +#if 0 + // enable this code if you want the details to blank while we're talking + // to the web site... it's kind of jarring + mSiteValid = false; + mSiteMembershipUpgrade = false; + mSiteMembershipAction = "(waiting)"; + mSiteMembershipPlanIDs.clear(); + mSiteMembershipPlanNames.clear(); + mSiteLandUseUpgrade = false; + mSiteLandUseAction = "(waiting)"; + mSiteCurrencyEstimated = false; + mSiteCurrencyEstimatedCost = 0; +#endif + + LLXMLRPCValue keywordArgs = LLXMLRPCValue::createStruct(); + keywordArgs.appendString("agentId", gAgent.getID().asString()); + keywordArgs.appendString( + "secureSessionId", + gAgent.getSecureSessionID().asString()); + keywordArgs.appendString("language", LLUI::getLanguage()); + keywordArgs.appendInt("billableArea", mPreflightAskBillableArea); + keywordArgs.appendInt("currencyBuy", mPreflightAskCurrencyBuy); + + LLXMLRPCValue params = LLXMLRPCValue::createArray(); + params.append(keywordArgs); + + startTransaction(TransactionPreflight, params); +} + +void LLFloaterBuyLandUI::finishWebSiteInfo() +{ + + LLXMLRPCValue result = mTransaction->responseValue(); + + mSiteValid = result["success"].asBool(); + if (!mSiteValid) + { + tellUserError( + result["errorMessage"].asString(), + result["errorURI"].asString() + ); + return; + } + + LLXMLRPCValue membership = result["membership"]; + mSiteMembershipUpgrade = membership["upgrade"].asBool(); + mSiteMembershipAction = membership["action"].asString(); + mSiteMembershipPlanIDs.clear(); + mSiteMembershipPlanNames.clear(); + LLXMLRPCValue levels = membership["levels"]; + for (LLXMLRPCValue level = levels.rewind(); + level.isValid(); + level = levels.next()) + { + mSiteMembershipPlanIDs.push_back(level["id"].asString()); + mSiteMembershipPlanNames.push_back(level["description"].asString()); + } + mUserPlanChoice = 0; + + LLXMLRPCValue landUse = result["landUse"]; + mSiteLandUseUpgrade = landUse["upgrade"].asBool(); + mSiteLandUseAction = landUse["action"].asString(); + + LLXMLRPCValue currency = result["currency"]; + if (currency["estimatedCost"].isValid()) + { + mCurrency.setUSDEstimate(currency["estimatedCost"].asInt()); + } + if (currency["estimatedLocalCost"].isValid()) + { + mCurrency.setLocalEstimate(currency["estimatedLocalCost"].asString()); + } + + mSiteConfirm = result["confirm"].asString(); +} + +void LLFloaterBuyLandUI::runWebSitePrep(const std::string& password) +{ + if (!mCanBuy) + { + return; + } + + bool remove_contribution = getChild("remove_contribution")->getValue().asBoolean(); + mParcelBuyInfo = LLViewerParcelMgr::getInstance()->setupParcelBuy(gAgent.getID(), gAgent.getSessionID(), + gAgent.getGroupID(), mIsForGroup, mIsClaim, remove_contribution); + + if (mParcelBuyInfo + && !mSiteMembershipUpgrade + && !mSiteLandUseUpgrade + && mCurrency.getAmount() == 0 + && mSiteConfirm != "password") + { + sendBuyLand(); + return; + } + + + std::string newLevel = "noChange"; + + if (mSiteMembershipUpgrade) + { + LLComboBox* levels = getChild( "account_level"); + if (levels) + { + mUserPlanChoice = levels->getCurrentIndex(); + newLevel = mSiteMembershipPlanIDs[mUserPlanChoice]; + } + } + + LLXMLRPCValue keywordArgs = LLXMLRPCValue::createStruct(); + keywordArgs.appendString("agentId", gAgent.getID().asString()); + keywordArgs.appendString( + "secureSessionId", + gAgent.getSecureSessionID().asString()); + keywordArgs.appendString("language", LLUI::getLanguage()); + keywordArgs.appendString("levelId", newLevel); + keywordArgs.appendInt("billableArea", + mIsForGroup ? 0 : mParcelBillableArea); + keywordArgs.appendInt("currencyBuy", mCurrency.getAmount()); + keywordArgs.appendInt("estimatedCost", mCurrency.getUSDEstimate()); + keywordArgs.appendString("estimatedLocalCost", mCurrency.getLocalEstimate()); + keywordArgs.appendString("confirm", mSiteConfirm); + if (!password.empty()) + { + keywordArgs.appendString("password", password); + } + + LLXMLRPCValue params = LLXMLRPCValue::createArray(); + params.append(keywordArgs); + + startTransaction(TransactionBuy, params); +} + +void LLFloaterBuyLandUI::finishWebSitePrep() +{ + LLXMLRPCValue result = mTransaction->responseValue(); + + bool success = result["success"].asBool(); + if (!success) + { + tellUserError( + result["errorMessage"].asString(), + result["errorURI"].asString() + ); + return; + } + + sendBuyLand(); +} + +void LLFloaterBuyLandUI::sendBuyLand() +{ + if (mParcelBuyInfo) + { + LLViewerParcelMgr::getInstance()->sendParcelBuy(mParcelBuyInfo); + LLViewerParcelMgr::getInstance()->deleteParcelBuy(&mParcelBuyInfo); + mBought = true; + } +} + +void LLFloaterBuyLandUI::updateNames() +{ + LLParcel* parcelp = mParcel->getParcel(); + + if (!parcelp) + { + mParcelSellerName = LLStringUtil::null; + return; + } + + if (mIsClaim) + { + mParcelSellerName = "Linden Lab"; + } + else if (parcelp->getIsGroupOwned()) + { + gCacheName->getGroup(parcelp->getGroupID(), + boost::bind(&LLFloaterBuyLandUI::updateGroupName, this, + _1, _2, _3)); + } + else + { + mParcelSellerName = LLSLURL("agent", parcelp->getOwnerID(), "completename").getSLURLString(); + } +} + +void LLFloaterBuyLandUI::updateGroupName(const LLUUID& id, + const std::string& name, + bool is_group) +{ + LLParcel* parcelp = mParcel->getParcel(); + if (parcelp + && parcelp->getGroupID() == id) + { + // request is current + mParcelSellerName = name; + } +} + +void LLFloaterBuyLandUI::startTransaction(TransactionType type, const LLXMLRPCValue& params) +{ + delete mTransaction; + mTransaction = NULL; + + mTransactionType = type; + + // Select a URI and method appropriate for the transaction type. + static std::string transaction_uri; + if (transaction_uri.empty()) + { + transaction_uri = LLGridManager::getInstance()->getHelperURI() + "landtool.php"; + } + + const char* method; + switch (mTransactionType) + { + case TransactionPreflight: + method = "preflightBuyLandPrep"; + break; + case TransactionBuy: + method = "buyLandPrep"; + break; + default: + LL_WARNS() << "LLFloaterBuyLandUI: Unknown transaction type!" << LL_ENDL; + return; + } + + mTransaction = new LLXMLRPCTransaction( + transaction_uri, + method, + params, + false /* don't use gzip */ + ); +} + +bool LLFloaterBuyLandUI::checkTransaction() +{ + if (!mTransaction) + { + return false; + } + + if (!mTransaction->process()) + { + return false; + } + + if (mTransaction->status(NULL) != LLXMLRPCTransaction::StatusComplete) + { + tellUserError(mTransaction->statusMessage(), mTransaction->statusURI()); + } + else { + switch (mTransactionType) + { + case TransactionPreflight: finishWebSiteInfo(); break; + case TransactionBuy: finishWebSitePrep(); break; + default: ; + } + } + + delete mTransaction; + mTransaction = NULL; + + return true; +} + +void LLFloaterBuyLandUI::tellUserError( + const std::string& message, const std::string& uri) +{ + mCanBuy = false; + mCannotBuyIsError = true; + mCannotBuyReason = getString("fetching_error"); + mCannotBuyReason += message; + mCannotBuyURI = uri; +} + + +// virtual +bool LLFloaterBuyLandUI::postBuild() +{ + setVisibleCallback(boost::bind(&LLFloaterBuyLandUI::onVisibilityChanged, this, _2)); + + mCurrency.prepare(); + + getChild("buy_btn")->setCommitCallback( boost::bind(&LLFloaterBuyLandUI::onClickBuy, this)); + getChild("cancel_btn")->setCommitCallback( boost::bind(&LLFloaterBuyLandUI::onClickCancel, this)); + getChild("error_web")->setCommitCallback( boost::bind(&LLFloaterBuyLandUI::onClickErrorWeb, this)); + + center(); + + return true; +} + +void LLFloaterBuyLandUI::setParcel(LLViewerRegion* region, LLParcelSelectionHandle parcel) +{ + if (mTransaction && mTransactionType == TransactionBuy) + { + // the user is buying, don't change the selection + return; + } + + mRegion = region; + mParcel = parcel; + + updateAgentInfo(); + updateParcelInfo(); + updateCovenantInfo(); + if (mCanBuy) + { + updateWebSiteInfo(); + } + refreshUI(); +} + +void LLFloaterBuyLandUI::setForGroup(bool forGroup) +{ + mIsForGroup = forGroup; +} + +void LLFloaterBuyLandUI::draw() +{ + LLFloater::draw(); + + bool needsUpdate = false; + needsUpdate |= checkTransaction(); + needsUpdate |= mCurrency.process(); + + if (mBought) + { + closeFloater(); + } + else if (needsUpdate) + { + if (mCanBuy && mCurrency.hasError()) + { + tellUserError(mCurrency.errorMessage(), mCurrency.errorURI()); + } + + refreshUI(); + } +} + +// virtual +bool LLFloaterBuyLandUI::canClose() +{ + // mTransactionType check for pre-buy estimation stage and mCurrency to allow exit after transaction + bool can_close = !mTransaction && (mTransactionType != TransactionBuy || mCurrency.canCancel()); + if (!can_close) + { + // explain to user why they can't do this, see DEV-9605 + LLNotificationsUtil::add("CannotCloseFloaterBuyLand"); + } + return can_close; +} + +void LLFloaterBuyLandUI::onVisibilityChanged ( const LLSD& new_visibility ) +{ + if (new_visibility.asBoolean()) + { + refreshUI(); + } +} + +void LLFloaterBuyLandUI::refreshUI() +{ + // section zero: title area + { + LLTextureCtrl* snapshot = getChild("info_image"); + if (snapshot) + { + snapshot->setImageAssetID( + mParcelValid ? mParcelSnapshot : LLUUID::null); + } + + if (mParcelValid) + { + getChild("info_parcel")->setValue(mParcelLocation); + + LLStringUtil::format_map_t string_args; + string_args["[AMOUNT]"] = llformat("%d", mParcelActualArea); + string_args["[AMOUNT2]"] = llformat("%d", mParcelSupportedObjects); + + getChild("info_size")->setValue(getString("meters_supports_object", string_args)); + + F32 cost_per_sqm = 0.0f; + if (mParcelActualArea > 0) + { + cost_per_sqm = (F32)mParcelPrice / (F32)mParcelActualArea; + } + + LLStringUtil::format_map_t info_price_args; + info_price_args["[PRICE]"] = llformat("%d", mParcelPrice); + info_price_args["[PRICE_PER_SQM]"] = llformat("%.1f", cost_per_sqm); + if (mParcelSoldWithObjects) + { + info_price_args["[SOLD_WITH_OBJECTS]"] = getString("sold_with_objects"); + } + else + { + info_price_args["[SOLD_WITH_OBJECTS]"] = getString("sold_without_objects"); + } + getChild("info_price")->setValue(getString("info_price_string", info_price_args)); + getChildView("info_price")->setVisible( mParcelIsForSale); + } + else + { + getChild("info_parcel")->setValue(getString("no_parcel_selected")); + getChild("info_size")->setValue(LLStringUtil::null); + getChild("info_price")->setValue(LLStringUtil::null); + } + + getChild("info_action")->setValue( + mCanBuy + ? + mIsForGroup + ? getString("buying_for_group")//"Buying land for group:" + : getString("buying_will")//"Buying this land will:" + : + mCannotBuyIsError + ? getString("cannot_buy_now")//"Cannot buy now:" + : getString("not_for_sale")//"Not for sale:" + + ); + } + + bool showingError = !mCanBuy || !mSiteValid; + + // error section + if (showingError) + { + mChildren.setBadge(std::string("step_error"), + mCannotBuyIsError + ? LLViewChildren::BADGE_ERROR + : LLViewChildren::BADGE_WARN); + + LLTextBox* message = getChild("error_message"); + if (message) + { + message->setVisible(true); + message->setValue(LLSD(!mCanBuy ? mCannotBuyReason : "(waiting for data)")); + } + + getChildView("error_web")->setVisible(mCannotBuyIsError && !mCannotBuyURI.empty()); + } + else + { + getChildView("step_error")->setVisible(false); + getChildView("error_message")->setVisible(false); + getChildView("error_web")->setVisible(false); + } + + + // section one: account + if (!showingError) + { + mChildren.setBadge(std::string("step_1"), + mSiteMembershipUpgrade + ? LLViewChildren::BADGE_NOTE + : LLViewChildren::BADGE_OK); + getChild("account_action")->setValue(mSiteMembershipAction); + getChild("account_reason")->setValue( + mSiteMembershipUpgrade + ? getString("must_upgrade") + : getString("cant_own_land") + ); + + LLComboBox* levels = getChild( "account_level"); + if (levels) + { + levels->setVisible(mSiteMembershipUpgrade); + + levels->removeall(); + for(std::vector::const_iterator i + = mSiteMembershipPlanNames.begin(); + i != mSiteMembershipPlanNames.end(); + ++i) + { + levels->add(*i); + } + + levels->setCurrentByIndex(mUserPlanChoice); + } + + getChildView("step_1")->setVisible(true); + getChildView("account_action")->setVisible(true); + getChildView("account_reason")->setVisible(true); + } + else + { + getChildView("step_1")->setVisible(false); + getChildView("account_action")->setVisible(false); + getChildView("account_reason")->setVisible(false); + getChildView("account_level")->setVisible(false); + } + + // section two: land use fees + if (!showingError) + { + mChildren.setBadge(std::string("step_2"), + mSiteLandUseUpgrade + ? LLViewChildren::BADGE_NOTE + : LLViewChildren::BADGE_OK); + getChild("land_use_action")->setValue(mSiteLandUseAction); + + std::string message; + + if (mIsForGroup) + { + LLStringUtil::format_map_t string_args; + string_args["[GROUP]"] = std::string(gAgent.getGroupName()); + + message += getString("insufficient_land_credits", string_args); + + } + else + { + LLStringUtil::format_map_t string_args; + string_args["[BUYER]"] = llformat("%d", mAgentCommittedTier); + message += getString("land_holdings", string_args); + } + + if (!mParcelValid) + { + message += LLTrans::getString("sentences_separator") + getString("no_parcel_selected"); + } + else if (mParcelBillableArea == mParcelActualArea) + { + LLStringUtil::format_map_t string_args; + string_args["[AMOUNT]"] = llformat("%d ", mParcelActualArea); + message += LLTrans::getString("sentences_separator") + getString("parcel_meters", string_args); + } + else + { + + if (mParcelBillableArea > mParcelActualArea) + { + LLStringUtil::format_map_t string_args; + string_args["[AMOUNT]"] = llformat("%d ", mParcelBillableArea); + message += LLTrans::getString("sentences_separator") + getString("premium_land", string_args); + } + else + { + LLStringUtil::format_map_t string_args; + string_args["[AMOUNT]"] = llformat("%d ", mParcelBillableArea); + message += LLTrans::getString("sentences_separator") + getString("discounted_land", string_args); + } + } + + getChild("land_use_reason")->setValue(message); + + getChildView("step_2")->setVisible(true); + getChildView("land_use_action")->setVisible(true); + getChildView("land_use_reason")->setVisible(true); + } + else + { + getChildView("step_2")->setVisible(false); + getChildView("land_use_action")->setVisible(false); + getChildView("land_use_reason")->setVisible(false); + } + + // section three: purchase & currency + S32 finalBalance = mAgentCashBalance + mCurrency.getAmount() - mParcelPrice; + bool willHaveEnough = finalBalance >= 0; + bool haveEnough = mAgentCashBalance >= mParcelPrice; + S32 minContribution = llceil((F32)mParcelBillableArea / GROUP_LAND_BONUS_FACTOR); + bool groupContributionEnough = mParcelGroupContribution >= minContribution; + + mCurrency.updateUI(!showingError && !haveEnough); + + if (!showingError) + { + mChildren.setBadge(std::string("step_3"), + !willHaveEnough + ? LLViewChildren::BADGE_WARN + : mCurrency.getAmount() > 0 + ? LLViewChildren::BADGE_NOTE + : LLViewChildren::BADGE_OK); + + LLStringUtil::format_map_t string_args; + string_args["[AMOUNT]"] = llformat("%d", mParcelPrice); + string_args["[SELLER]"] = mParcelSellerName; + getChild("purchase_action")->setValue(getString("pay_to_for_land", string_args)); + getChildView("purchase_action")->setVisible( mParcelValid); + + std::string reasonString; + + if (haveEnough) + { + LLStringUtil::format_map_t string_args; + string_args["[AMOUNT]"] = llformat("%d", mAgentCashBalance); + + getChild("currency_reason")->setValue(getString("have_enough_lindens", string_args)); + } + else + { + LLStringUtil::format_map_t string_args; + string_args["[AMOUNT]"] = llformat("%d", mAgentCashBalance); + string_args["[AMOUNT2]"] = llformat("%d", mParcelPrice - mAgentCashBalance); + + getChild("currency_reason")->setValue(getString("not_enough_lindens", string_args)); + + getChild("currency_est")->setTextArg("[LOCAL_AMOUNT]", mCurrency.getLocalEstimate()); + } + + if (willHaveEnough) + { + LLStringUtil::format_map_t string_args; + string_args["[AMOUNT]"] = llformat("%d", finalBalance); + + getChild("currency_balance")->setValue(getString("balance_left", string_args)); + + } + else + { + LLStringUtil::format_map_t string_args; + string_args["[AMOUNT]"] = llformat("%d", mParcelPrice - mAgentCashBalance); + + getChild("currency_balance")->setValue(getString("balance_needed", string_args)); + + } + + getChild("remove_contribution")->setValue(LLSD(groupContributionEnough)); + getChildView("remove_contribution")->setEnabled(groupContributionEnough); + bool showRemoveContribution = mParcelIsGroupLand + && (mParcelGroupContribution > 0); + getChildView("remove_contribution")->setLabelArg("[AMOUNT]", + llformat("%d", minContribution)); + getChildView("remove_contribution")->setVisible( showRemoveContribution); + + getChildView("step_3")->setVisible(true); + getChildView("purchase_action")->setVisible(true); + getChildView("currency_reason")->setVisible(true); + getChildView("currency_balance")->setVisible(true); + } + else + { + getChildView("step_3")->setVisible(false); + getChildView("purchase_action")->setVisible(false); + getChildView("currency_reason")->setVisible(false); + getChildView("currency_balance")->setVisible(false); + getChildView("remove_group_donation")->setVisible(false); + } + + + bool agrees_to_covenant = false; + LLCheckBoxCtrl* check = getChild("agree_covenant"); + if (check) + { + agrees_to_covenant = check->get(); + } + + getChildView("buy_btn")->setEnabled(mCanBuy && mSiteValid && willHaveEnough && !mTransaction && agrees_to_covenant); +} + +void LLFloaterBuyLandUI::startBuyPreConfirm() +{ + std::string action; + + if (mSiteMembershipUpgrade) + { + action += mSiteMembershipAction; + action += "\n"; + + LLComboBox* levels = getChild( "account_level"); + if (levels) + { + action += " * "; + action += mSiteMembershipPlanNames[levels->getCurrentIndex()]; + action += "\n"; + } + } + if (mSiteLandUseUpgrade) + { + action += mSiteLandUseAction; + action += "\n"; + } + if (mCurrency.getAmount() > 0) + { + LLStringUtil::format_map_t string_args; + string_args["[AMOUNT]"] = llformat("%d", mCurrency.getAmount()); + string_args["[LOCAL_AMOUNT]"] = mCurrency.getLocalEstimate(); + + action += getString("buy_for_US", string_args); + } + + LLStringUtil::format_map_t string_args; + string_args["[AMOUNT]"] = llformat("%d", mParcelPrice); + string_args["[SELLER]"] = mParcelSellerName; + action += getString("pay_to_for_land", string_args); + + + LLConfirmationManager::confirm(mSiteConfirm, + action, + *this, + &LLFloaterBuyLandUI::startBuyPostConfirm); +} + +void LLFloaterBuyLandUI::startBuyPostConfirm(const std::string& password) +{ + runWebSitePrep(password); + + mCanBuy = false; + mCannotBuyReason = getString("processing"); + refreshUI(); +} + + +void LLFloaterBuyLandUI::onClickBuy() +{ + startBuyPreConfirm(); +} + +void LLFloaterBuyLandUI::onClickCancel() +{ + closeFloater(); +} + +void LLFloaterBuyLandUI::onClickErrorWeb() +{ + LLWeb::loadURLExternal(mCannotBuyURI); + closeFloater(); +} + + + diff --git a/indra/newview/llfloaterbvhpreview.cpp b/indra/newview/llfloaterbvhpreview.cpp index 43e1effdd8..5330518ba5 100644 --- a/indra/newview/llfloaterbvhpreview.cpp +++ b/indra/newview/llfloaterbvhpreview.cpp @@ -1,1195 +1,1195 @@ -/** - * @file llfloaterbvhpreview.cpp - * @brief LLFloaterBvhPreview class implementation - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterbvhpreview.h" - -#include "llbvhloader.h" -#include "lldatapacker.h" -#include "lldir.h" -#include "llnotificationsutil.h" -#include "llfilesystem.h" -#include "llapr.h" -#include "llstring.h" - -#include "llagent.h" -#include "llagentbenefits.h" -#include "llanimationstates.h" -#include "llbbox.h" -#include "llbutton.h" -#include "llcheckboxctrl.h" -#include "llcombobox.h" -#include "lldrawable.h" -#include "lldrawpoolavatar.h" -#include "llrender.h" -#include "llface.h" -#include "llfocusmgr.h" -#include "llkeyframemotion.h" -#include "lllineeditor.h" -#include "llfloaterperms.h" -#include "llsliderctrl.h" -#include "llspinctrl.h" -#include "lltextbox.h" -#include "lltoolmgr.h" -#include "llui.h" -#include "llviewercamera.h" -#include "llviewerobjectlist.h" -#include "llviewerwindow.h" -#include "llviewermenufile.h" // upload_new_resource() -#include "llvoavatar.h" -#include "pipeline.h" -#include "lluictrlfactory.h" -#include "lltrans.h" - -const S32 PREVIEW_BORDER_WIDTH = 2; -const S32 PREVIEW_RESIZE_HANDLE_SIZE = S32(RESIZE_HANDLE_WIDTH * OO_SQRT2) + PREVIEW_BORDER_WIDTH; -const S32 PREVIEW_HPAD = PREVIEW_RESIZE_HANDLE_SIZE; -const S32 PREVIEW_VPAD = 35; -const S32 PREF_BUTTON_HEIGHT = 16 + 35; -const S32 PREVIEW_TEXTURE_HEIGHT = 300; - -const F32 PREVIEW_CAMERA_DISTANCE = 4.f; - -const F32 MIN_CAMERA_ZOOM = 0.5f; -const F32 MAX_CAMERA_ZOOM = 10.f; - -const F32 BASE_ANIM_TIME_OFFSET = 5.f; - -std::string STATUS[] = -{ - "E_ST_OK", - "E_ST_EOF", - "E_ST_NO_CONSTRAINT", - "E_ST_NO_FILE", - "E_ST_NO_HIER", - "E_ST_NO_JOINT", - "E_ST_NO_NAME", - "E_ST_NO_OFFSET", - "E_ST_NO_CHANNELS", - "E_ST_NO_ROTATION", - "E_ST_NO_AXIS", - "E_ST_NO_MOTION", - "E_ST_NO_FRAMES", - "E_ST_NO_FRAME_TIME", - "E_ST_NO_POS", - "E_ST_NO_ROT", - "E_ST_NO_XLT_FILE", - "E_ST_NO_XLT_HEADER", - "E_ST_NO_XLT_NAME", - "E_ST_NO_XLT_IGNORE", - "E_ST_NO_XLT_RELATIVE", - "E_ST_NO_XLT_OUTNAME", - "E_ST_NO_XLT_MATRIX", - "E_ST_NO_XLT_MERGECHILD", - "E_ST_NO_XLT_MERGEPARENT", - "E_ST_NO_XLT_PRIORITY", - "E_ST_NO_XLT_LOOP", - "E_ST_NO_XLT_EASEIN", - "E_ST_NO_XLT_EASEOUT", - "E_ST_NO_XLT_HAND", - "E_ST_NO_XLT_EMOTE", -"E_ST_BAD_ROOT" -}; - -//----------------------------------------------------------------------------- -// LLFloaterBvhPreview() -//----------------------------------------------------------------------------- -LLFloaterBvhPreview::LLFloaterBvhPreview(const std::string& filename) : - LLFloaterNameDesc(filename) -{ - mLastMouseX = 0; - mLastMouseY = 0; - - mIDList["Standing"] = ANIM_AGENT_STAND; - mIDList["Walking"] = ANIM_AGENT_FEMALE_WALK; - mIDList["Sitting"] = ANIM_AGENT_SIT_FEMALE; - mIDList["Flying"] = ANIM_AGENT_HOVER; - - mIDList["[None]"] = LLUUID::null; - mIDList["Aaaaah"] = ANIM_AGENT_EXPRESS_OPEN_MOUTH; - mIDList["Afraid"] = ANIM_AGENT_EXPRESS_AFRAID; - mIDList["Angry"] = ANIM_AGENT_EXPRESS_ANGER; - mIDList["Big Smile"] = ANIM_AGENT_EXPRESS_TOOTHSMILE; - mIDList["Bored"] = ANIM_AGENT_EXPRESS_BORED; - mIDList["Cry"] = ANIM_AGENT_EXPRESS_CRY; - mIDList["Disdain"] = ANIM_AGENT_EXPRESS_DISDAIN; - mIDList["Embarrassed"] = ANIM_AGENT_EXPRESS_EMBARRASSED; - mIDList["Frown"] = ANIM_AGENT_EXPRESS_FROWN; - mIDList["Kiss"] = ANIM_AGENT_EXPRESS_KISS; - mIDList["Laugh"] = ANIM_AGENT_EXPRESS_LAUGH; - mIDList["Plllppt"] = ANIM_AGENT_EXPRESS_TONGUE_OUT; - mIDList["Repulsed"] = ANIM_AGENT_EXPRESS_REPULSED; - mIDList["Sad"] = ANIM_AGENT_EXPRESS_SAD; - mIDList["Shrug"] = ANIM_AGENT_EXPRESS_SHRUG; - mIDList["Smile"] = ANIM_AGENT_EXPRESS_SMILE; - mIDList["Surprise"] = ANIM_AGENT_EXPRESS_SURPRISE; - mIDList["Wink"] = ANIM_AGENT_EXPRESS_WINK; - mIDList["Worry"] = ANIM_AGENT_EXPRESS_WORRY; -} - -//----------------------------------------------------------------------------- -// setAnimCallbacks() -//----------------------------------------------------------------------------- -void LLFloaterBvhPreview::setAnimCallbacks() -{ - getChild("playback_slider")->setCommitCallback(boost::bind(&LLFloaterBvhPreview::onSliderMove, this)); - - getChild("preview_base_anim")->setCommitCallback(boost::bind(&LLFloaterBvhPreview::onCommitBaseAnim, this)); - getChild("preview_base_anim")->setValue("Standing"); - - getChild("priority")->setCommitCallback(boost::bind(&LLFloaterBvhPreview::onCommitPriority, this)); - getChild("loop_check")->setCommitCallback(boost::bind(&LLFloaterBvhPreview::onCommitLoop, this)); - getChild("loop_in_point")->setCommitCallback(boost::bind(&LLFloaterBvhPreview::onCommitLoopIn, this)); - getChild("loop_in_point")->setValidateBeforeCommit( boost::bind(&LLFloaterBvhPreview::validateLoopIn, this, _1)); - getChild("loop_out_point")->setCommitCallback(boost::bind(&LLFloaterBvhPreview::onCommitLoopOut, this)); - getChild("loop_out_point")->setValidateBeforeCommit( boost::bind(&LLFloaterBvhPreview::validateLoopOut, this, _1)); - - getChild("hand_pose_combo")->setCommitCallback(boost::bind(&LLFloaterBvhPreview::onCommitHandPose, this)); - - getChild("emote_combo")->setCommitCallback(boost::bind(&LLFloaterBvhPreview::onCommitEmote, this)); - getChild("emote_combo")->setValue("[None]"); - - getChild("ease_in_time")->setCommitCallback(boost::bind(&LLFloaterBvhPreview::onCommitEaseIn, this)); - getChild("ease_in_time")->setValidateBeforeCommit( boost::bind(&LLFloaterBvhPreview::validateEaseIn, this, _1)); - getChild("ease_out_time")->setCommitCallback(boost::bind(&LLFloaterBvhPreview::onCommitEaseOut, this)); - getChild("ease_out_time")->setValidateBeforeCommit( boost::bind(&LLFloaterBvhPreview::validateEaseOut, this, _1)); -} - -std::map LLFloaterBvhPreview::getJointAliases() -{ - LLPointer av = (LLVOAvatar*)mAnimPreview->getDummyAvatar(); - return av->getJointAliases(); -} - -//----------------------------------------------------------------------------- -// postBuild() -//----------------------------------------------------------------------------- -bool LLFloaterBvhPreview::postBuild() -{ - LLKeyframeMotion* motionp = NULL; - LLBVHLoader* loaderp = NULL; - - if (!LLFloaterNameDesc::postBuild()) - { - return false; - } - - getChild("name_form")->setCommitCallback(boost::bind(&LLFloaterBvhPreview::onCommitName, this)); - - childSetAction("ok_btn", onBtnOK, this); - setDefaultBtn(); - - mPreviewRect.set(PREVIEW_HPAD, - PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD, - getRect().getWidth() - PREVIEW_HPAD, - PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); - mPreviewImageRect.set(0.f, 1.f, 1.f, 0.f); - - mPlayButton = getChild( "play_btn"); - mPlayButton->setClickedCallback(boost::bind(&LLFloaterBvhPreview::onBtnPlay, this)); - mPlayButton->setVisible(true); - - mPauseButton = getChild( "pause_btn"); - mPauseButton->setClickedCallback(boost::bind(&LLFloaterBvhPreview::onBtnPause, this)); - mPauseButton->setVisible(false); - - mStopButton = getChild( "stop_btn"); - mStopButton->setClickedCallback(boost::bind(&LLFloaterBvhPreview::onBtnStop, this)); - - getChildView("bad_animation_text")->setVisible(false); - - mAnimPreview = new LLPreviewAnimation(256, 256); - - std::string exten = gDirUtilp->getExtension(mFilename); - if (exten == "bvh") - { - // loading a bvh file - - // now load bvh file - S32 file_size; - - LLAPRFile infile ; - infile.open(mFilenameAndPath, LL_APR_RB, NULL, &file_size); - - if (!infile.getFileHandle()) - { - LL_WARNS() << "Can't open BVH file:" << mFilename << LL_ENDL; - } - else - { - char* file_buffer; - - file_buffer = new char[file_size + 1]; - - if (file_size == infile.read(file_buffer, file_size)) - { - file_buffer[file_size] = '\0'; - LL_INFOS() << "Loading BVH file " << mFilename << LL_ENDL; - ELoadStatus load_status = E_ST_OK; - S32 line_number = 0; - - std::map joint_alias_map = getJointAliases(); - - loaderp = new LLBVHLoader(file_buffer, load_status, line_number, joint_alias_map); - std::string status = getString(STATUS[load_status]); - - if(load_status == E_ST_NO_XLT_FILE) - { - LL_WARNS() << "NOTE: No translation table found." << LL_ENDL; - } - else - { - LL_WARNS() << "ERROR: [line: " << line_number << "] " << status << LL_ENDL; - } - } - - infile.close() ; - delete[] file_buffer; - } - } - - if (loaderp && loaderp->isInitialized() && loaderp->getDuration() <= MAX_ANIM_DURATION) - { - // generate unique id for this motion - mTransactionID.generate(); - mMotionID = mTransactionID.makeAssetID(gAgent.getSecureSessionID()); - - // motion will be returned, but it will be in a load-pending state, as this is a new motion - // this motion will not request an asset transfer until next update, so we have a chance to - // load the keyframe data locally - motionp = (LLKeyframeMotion*)mAnimPreview->getDummyAvatar()->createMotion(mMotionID); - - // create data buffer for keyframe initialization - S32 buffer_size = loaderp->getOutputSize(); - U8* buffer = new U8[buffer_size]; - - LLDataPackerBinaryBuffer dp(buffer, buffer_size); - - // pass animation data through memory buffer - LL_INFOS("BVH") << "Serializing loaderp" << LL_ENDL; - loaderp->serialize(dp); - dp.reset(); - LL_INFOS("BVH") << "Deserializing motionp" << LL_ENDL; - bool success = motionp && motionp->deserialize(dp, mMotionID, false); - LL_INFOS("BVH") << "Done" << LL_ENDL; - - delete []buffer; - - if (success) - { - setAnimCallbacks() ; - - const LLBBoxLocal &pelvis_bbox = motionp->getPelvisBBox(); - - LLVector3 temp = pelvis_bbox.getCenter(); - // only consider XY? - //temp.mV[VZ] = 0.f; - F32 pelvis_offset = temp.magVec(); - - temp = pelvis_bbox.getExtent(); - //temp.mV[VZ] = 0.f; - F32 pelvis_max_displacement = pelvis_offset + (temp.magVec() * 0.5f) + 1.f; - - F32 camera_zoom = LLViewerCamera::getInstance()->getDefaultFOV() / (2.f * atan(pelvis_max_displacement / PREVIEW_CAMERA_DISTANCE)); - - mAnimPreview->setZoom(camera_zoom); - - motionp->setName(getChild("name_form")->getValue().asString()); - mAnimPreview->getDummyAvatar()->startMotion(mMotionID); - - getChild("playback_slider")->setMinValue(0.0); - getChild("playback_slider")->setMaxValue(1.0); - - getChild("loop_check")->setValue(LLSD(motionp->getLoop())); - getChild("loop_in_point")->setValue(LLSD(motionp->getLoopIn() / motionp->getDuration() * 100.f)); - getChild("loop_out_point")->setValue(LLSD(motionp->getLoopOut() / motionp->getDuration() * 100.f)); - getChild("priority")->setValue(LLSD((F32)motionp->getPriority())); - getChild("hand_pose_combo")->setValue(LLHandMotion::getHandPoseName(motionp->getHandPose())); - getChild("ease_in_time")->setValue(LLSD(motionp->getEaseInDuration())); - getChild("ease_out_time")->setValue(LLSD(motionp->getEaseOutDuration())); - setEnabled(true); - std::string seconds_string; - seconds_string = llformat(" - %.2f seconds", motionp->getDuration()); - - setTitle(mFilename + std::string(seconds_string)); - } - else - { - mAnimPreview = NULL; - mMotionID.setNull(); - getChild("bad_animation_text")->setValue(getString("failed_to_initialize")); - } - } - else - { - if ( loaderp ) - { - if (loaderp->getDuration() > MAX_ANIM_DURATION) - { - LLUIString out_str = getString("anim_too_long"); - out_str.setArg("[LENGTH]", llformat("%.1f", loaderp->getDuration())); - out_str.setArg("[MAX_LENGTH]", llformat("%.1f", MAX_ANIM_DURATION)); - getChild("bad_animation_text")->setValue(out_str.getString()); - } - else - { - LLUIString out_str = getString("failed_file_read"); - out_str.setArg("[STATUS]", getString(STATUS[loaderp->getStatus()])); - getChild("bad_animation_text")->setValue(out_str.getString()); - } - } - - //setEnabled(false); - mMotionID.setNull(); - mAnimPreview = NULL; - } - - refresh(); - - delete loaderp; - - return true; -} - -//----------------------------------------------------------------------------- -// LLFloaterBvhPreview() -//----------------------------------------------------------------------------- -LLFloaterBvhPreview::~LLFloaterBvhPreview() -{ - mAnimPreview = NULL; - - setEnabled(false); -} - -//----------------------------------------------------------------------------- -// draw() -//----------------------------------------------------------------------------- -void LLFloaterBvhPreview::draw() -{ - LLFloater::draw(); - LLRect r = getRect(); - - refresh(); - - if (mMotionID.notNull() && mAnimPreview) - { - gGL.color3f(1.f, 1.f, 1.f); - - gGL.getTexUnit(0)->bind(mAnimPreview); - - gGL.begin( LLRender::QUADS ); - { - gGL.texCoord2f(0.f, 1.f); - gGL.vertex2i(PREVIEW_HPAD, PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD); - gGL.texCoord2f(0.f, 0.f); - gGL.vertex2i(PREVIEW_HPAD, PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); - gGL.texCoord2f(1.f, 0.f); - gGL.vertex2i(r.getWidth() - PREVIEW_HPAD, PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); - gGL.texCoord2f(1.f, 1.f); - gGL.vertex2i(r.getWidth() - PREVIEW_HPAD, PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD); - } - gGL.end(); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); - if (!avatarp->areAnimationsPaused()) - { - mAnimPreview->requestUpdate(); - } - } -} - -//----------------------------------------------------------------------------- -// resetMotion() -//----------------------------------------------------------------------------- -void LLFloaterBvhPreview::resetMotion() -{ - if (!mAnimPreview) - return; - - LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); - bool paused = avatarp->areAnimationsPaused(); - - LLKeyframeMotion* motionp = dynamic_cast(avatarp->findMotion(mMotionID)); - if( motionp ) - { - // Set emotion - std::string emote = getChild("emote_combo")->getValue().asString(); - motionp->setEmote(mIDList[emote]); - } - - LLUUID base_id = mIDList[getChild("preview_base_anim")->getValue().asString()]; - avatarp->deactivateAllMotions(); - avatarp->startMotion(mMotionID, 0.0f); - avatarp->startMotion(base_id, BASE_ANIM_TIME_OFFSET); - getChild("playback_slider")->setValue(0.0f); - - // Set pose - std::string handpose = getChild("hand_pose_combo")->getValue().asString(); - avatarp->startMotion( ANIM_AGENT_HAND_MOTION, 0.0f ); - - if( motionp ) - { - motionp->setHandPose(LLHandMotion::getHandPose(handpose)); - } - - if (paused) - { - mPauseRequest = avatarp->requestPause(); - } - else - { - mPauseRequest = NULL; - } -} - -//----------------------------------------------------------------------------- -// handleMouseDown() -//----------------------------------------------------------------------------- -bool LLFloaterBvhPreview::handleMouseDown(S32 x, S32 y, MASK mask) -{ - if (mPreviewRect.pointInRect(x, y)) - { - bringToFront( x, y ); - gFocusMgr.setMouseCapture(this); - gViewerWindow->hideCursor(); - mLastMouseX = x; - mLastMouseY = y; - return true; - } - - return LLFloater::handleMouseDown(x, y, mask); -} - -//----------------------------------------------------------------------------- -// handleMouseUp() -//----------------------------------------------------------------------------- -bool LLFloaterBvhPreview::handleMouseUp(S32 x, S32 y, MASK mask) -{ - gFocusMgr.setMouseCapture(nullptr); - gViewerWindow->showCursor(); - return LLFloater::handleMouseUp(x, y, mask); -} - -//----------------------------------------------------------------------------- -// handleHover() -//----------------------------------------------------------------------------- -bool LLFloaterBvhPreview::handleHover(S32 x, S32 y, MASK mask) -{ - MASK local_mask = mask & ~MASK_ALT; - - if (mAnimPreview && hasMouseCapture()) - { - if (local_mask == MASK_PAN) - { - // pan here - mAnimPreview->pan((F32)(x - mLastMouseX) * -0.005f, (F32)(y - mLastMouseY) * -0.005f); - } - else if (local_mask == MASK_ORBIT) - { - F32 yaw_radians = (F32)(x - mLastMouseX) * -0.01f; - F32 pitch_radians = (F32)(y - mLastMouseY) * 0.02f; - - mAnimPreview->rotate(yaw_radians, pitch_radians); - } - else - { - F32 yaw_radians = (F32)(x - mLastMouseX) * -0.01f; - F32 zoom_amt = (F32)(y - mLastMouseY) * 0.02f; - - mAnimPreview->rotate(yaw_radians, 0.f); - mAnimPreview->zoom(zoom_amt); - } - - mAnimPreview->requestUpdate(); - - LLUI::getInstance()->setMousePositionLocal(this, mLastMouseX, mLastMouseY); - } - - if (!mPreviewRect.pointInRect(x, y) || !mAnimPreview) - { - return LLFloater::handleHover(x, y, mask); - } - else if (local_mask == MASK_ORBIT) - { - gViewerWindow->setCursor(UI_CURSOR_TOOLCAMERA); - } - else if (local_mask == MASK_PAN) - { - gViewerWindow->setCursor(UI_CURSOR_TOOLPAN); - } - else - { - gViewerWindow->setCursor(UI_CURSOR_TOOLZOOMIN); - } - - return true; -} - -//----------------------------------------------------------------------------- -// handleScrollWheel() -//----------------------------------------------------------------------------- -bool LLFloaterBvhPreview::handleScrollWheel(S32 x, S32 y, S32 clicks) -{ - if (!mAnimPreview) - return false; - - mAnimPreview->zoom((F32)clicks * -0.2f); - mAnimPreview->requestUpdate(); - - return true; -} - -//----------------------------------------------------------------------------- -// onMouseCaptureLost() -//----------------------------------------------------------------------------- -void LLFloaterBvhPreview::onMouseCaptureLost() -{ - gViewerWindow->showCursor(); -} - -//----------------------------------------------------------------------------- -// onBtnPlay() -//----------------------------------------------------------------------------- -void LLFloaterBvhPreview::onBtnPlay() -{ - if (!getEnabled()) - return; - - if (mMotionID.notNull() && mAnimPreview) - { - LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); - - if (!avatarp->isMotionActive(mMotionID)) - { - resetMotion(); - mPauseRequest = NULL; - } - else if (avatarp->areAnimationsPaused()) - { - mPauseRequest = NULL; - } - } -} - -//----------------------------------------------------------------------------- -// onBtnPause() -//----------------------------------------------------------------------------- -void LLFloaterBvhPreview::onBtnPause() -{ - if (!getEnabled()) - return; - - if (mMotionID.notNull() && mAnimPreview) - { - LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); - - if (avatarp->isMotionActive(mMotionID)) - { - if (!avatarp->areAnimationsPaused()) - { - mPauseRequest = avatarp->requestPause(); - } - } - } -} - -//----------------------------------------------------------------------------- -// onBtnStop() -//----------------------------------------------------------------------------- -void LLFloaterBvhPreview::onBtnStop() -{ - if (!getEnabled()) - return; - - if (mMotionID.notNull() && mAnimPreview) - { - LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); - resetMotion(); - mPauseRequest = avatarp->requestPause(); - } -} - -//----------------------------------------------------------------------------- -// onSliderMove() -//----------------------------------------------------------------------------- -void LLFloaterBvhPreview::onSliderMove() -{ - if (!getEnabled()) - return; - - if (mAnimPreview) - { - LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); - F32 slider_value = (F32)getChild("playback_slider")->getValue().asReal(); - LLUUID base_id = mIDList[getChild("preview_base_anim")->getValue().asString()]; - LLMotion* motionp = avatarp->findMotion(mMotionID); - F32 duration = motionp->getDuration();// + motionp->getEaseOutDuration(); - F32 delta_time = duration * slider_value; - avatarp->deactivateAllMotions(); - avatarp->startMotion(base_id, delta_time + BASE_ANIM_TIME_OFFSET); - avatarp->startMotion(mMotionID, delta_time); - mPauseRequest = avatarp->requestPause(); - refresh(); - } - -} - -//----------------------------------------------------------------------------- -// onCommitBaseAnim() -//----------------------------------------------------------------------------- -void LLFloaterBvhPreview::onCommitBaseAnim() -{ - if (!getEnabled()) - return; - - if (mAnimPreview) - { - LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); - - bool paused = avatarp->areAnimationsPaused(); - - // stop all other possible base motions - avatarp->stopMotion(mIDList["Standing"], true); - avatarp->stopMotion(mIDList["Walking"], true); - avatarp->stopMotion(mIDList["Sitting"], true); - avatarp->stopMotion(mIDList["Flying"], true); - - resetMotion(); - - if (!paused) - { - mPauseRequest = NULL; - } - } -} - -//----------------------------------------------------------------------------- -// onCommitLoop() -//----------------------------------------------------------------------------- -void LLFloaterBvhPreview::onCommitLoop() -{ - if (!getEnabled() || !mAnimPreview) - return; - - LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); - LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(mMotionID); - - if (motionp) - { - motionp->setLoop(getChild("loop_check")->getValue().asBoolean()); - motionp->setLoopIn((F32)getChild("loop_in_point")->getValue().asReal() * 0.01f * motionp->getDuration()); - motionp->setLoopOut((F32)getChild("loop_out_point")->getValue().asReal() * 0.01f * motionp->getDuration()); - } -} - -//----------------------------------------------------------------------------- -// onCommitLoopIn() -//----------------------------------------------------------------------------- -void LLFloaterBvhPreview::onCommitLoopIn() -{ - if (!getEnabled() || !mAnimPreview) - return; - - LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); - LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(mMotionID); - - if (motionp) - { - motionp->setLoopIn((F32)getChild("loop_in_point")->getValue().asReal() / 100.f); - resetMotion(); - getChild("loop_check")->setValue(LLSD(true)); - onCommitLoop(); - } -} - -//----------------------------------------------------------------------------- -// onCommitLoopOut() -//----------------------------------------------------------------------------- -void LLFloaterBvhPreview::onCommitLoopOut() -{ - if (!getEnabled() || !mAnimPreview) - return; - - LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); - LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(mMotionID); - - if (motionp) - { - motionp->setLoopOut((F32)getChild("loop_out_point")->getValue().asReal() * 0.01f * motionp->getDuration()); - resetMotion(); - getChild("loop_check")->setValue(LLSD(true)); - onCommitLoop(); - } -} - -//----------------------------------------------------------------------------- -// onCommitName() -//----------------------------------------------------------------------------- -void LLFloaterBvhPreview::onCommitName() -{ - if (!getEnabled() || !mAnimPreview) - return; - - LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); - LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(mMotionID); - - if (motionp) - { - motionp->setName(getChild("name_form")->getValue().asString()); - } - - doCommit(); -} - -//----------------------------------------------------------------------------- -// onCommitHandPose() -//----------------------------------------------------------------------------- -void LLFloaterBvhPreview::onCommitHandPose() -{ - if (!getEnabled()) - return; - - resetMotion(); // sets hand pose -} - -//----------------------------------------------------------------------------- -// onCommitEmote() -//----------------------------------------------------------------------------- -void LLFloaterBvhPreview::onCommitEmote() -{ - if (!getEnabled()) - return; - - resetMotion(); // ssts emote -} - -//----------------------------------------------------------------------------- -// onCommitPriority() -//----------------------------------------------------------------------------- -void LLFloaterBvhPreview::onCommitPriority() -{ - if (!getEnabled() || !mAnimPreview) - return; - - LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); - LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(mMotionID); - - motionp->setPriority(llfloor((F32)getChild("priority")->getValue().asReal())); -} - -//----------------------------------------------------------------------------- -// onCommitEaseIn() -//----------------------------------------------------------------------------- -void LLFloaterBvhPreview::onCommitEaseIn() -{ - if (!getEnabled() || !mAnimPreview) - return; - - LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); - LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(mMotionID); - - motionp->setEaseIn((F32)getChild("ease_in_time")->getValue().asReal()); - resetMotion(); -} - -//----------------------------------------------------------------------------- -// onCommitEaseOut() -//----------------------------------------------------------------------------- -void LLFloaterBvhPreview::onCommitEaseOut() -{ - if (!getEnabled() || !mAnimPreview) - return; - - LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); - LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(mMotionID); - - motionp->setEaseOut((F32)getChild("ease_out_time")->getValue().asReal()); - resetMotion(); -} - -//----------------------------------------------------------------------------- -// validateEaseIn() -//----------------------------------------------------------------------------- -bool LLFloaterBvhPreview::validateEaseIn(const LLSD& data) -{ - if (!getEnabled() || !mAnimPreview) - return false; - - LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); - LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(mMotionID); - - if (!motionp->getLoop()) - { - F32 new_ease_in = llclamp((F32)getChild("ease_in_time")->getValue().asReal(), 0.f, motionp->getDuration() - motionp->getEaseOutDuration()); - getChild("ease_in_time")->setValue(LLSD(new_ease_in)); - } - - return true; -} - -//----------------------------------------------------------------------------- -// validateEaseOut() -//----------------------------------------------------------------------------- -bool LLFloaterBvhPreview::validateEaseOut(const LLSD& data) -{ - if (!getEnabled() || !mAnimPreview) - return false; - - LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); - LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(mMotionID); - - if (!motionp->getLoop()) - { - F32 new_ease_out = llclamp((F32)getChild("ease_out_time")->getValue().asReal(), 0.f, motionp->getDuration() - motionp->getEaseInDuration()); - getChild("ease_out_time")->setValue(LLSD(new_ease_out)); - } - - return true; -} - -//----------------------------------------------------------------------------- -// validateLoopIn() -//----------------------------------------------------------------------------- -bool LLFloaterBvhPreview::validateLoopIn(const LLSD& data) -{ - if (!getEnabled()) - return false; - - F32 loop_in_value = (F32)getChild("loop_in_point")->getValue().asReal(); - F32 loop_out_value = (F32)getChild("loop_out_point")->getValue().asReal(); - - if (loop_in_value < 0.f) - { - loop_in_value = 0.f; - } - else if (loop_in_value > 100.f) - { - loop_in_value = 100.f; - } - else if (loop_in_value > loop_out_value) - { - loop_in_value = loop_out_value; - } - - getChild("loop_in_point")->setValue(LLSD(loop_in_value)); - return true; -} - -//----------------------------------------------------------------------------- -// validateLoopOut() -//----------------------------------------------------------------------------- -bool LLFloaterBvhPreview::validateLoopOut(const LLSD& data) -{ - if (!getEnabled()) - return false; - - F32 loop_out_value = (F32)getChild("loop_out_point")->getValue().asReal(); - F32 loop_in_value = (F32)getChild("loop_in_point")->getValue().asReal(); - - if (loop_out_value < 0.f) - { - loop_out_value = 0.f; - } - else if (loop_out_value > 100.f) - { - loop_out_value = 100.f; - } - else if (loop_out_value < loop_in_value) - { - loop_out_value = loop_in_value; - } - - getChild("loop_out_point")->setValue(LLSD(loop_out_value)); - return true; -} - - -//----------------------------------------------------------------------------- -// refresh() -//----------------------------------------------------------------------------- -void LLFloaterBvhPreview::refresh() -{ - // Are we showing the play button (default) or the pause button? - bool show_play = true; - if (!mAnimPreview) - { - getChildView("bad_animation_text")->setVisible(true); - // play button visible but disabled - mPlayButton->setEnabled(false); - mStopButton->setEnabled(false); - getChildView("ok_btn")->setEnabled(false); - } - else - { - getChildView("bad_animation_text")->setVisible(false); - // re-enabled in case previous animation was bad - mPlayButton->setEnabled(true); - mStopButton->setEnabled(true); - LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); - if (avatarp->isMotionActive(mMotionID)) - { - mStopButton->setEnabled(true); - LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(mMotionID); - if (!avatarp->areAnimationsPaused()) - { - // animation is playing - if (motionp) - { - F32 fraction_complete = motionp->getLastUpdateTime() / motionp->getDuration(); - getChild("playback_slider")->setValue(fraction_complete); - } - show_play = false; - } - } - else - { - // Motion just finished playing - mPauseRequest = avatarp->requestPause(); - } - getChildView("ok_btn")->setEnabled(true); - mAnimPreview->requestUpdate(); - } - mPlayButton->setVisible(show_play); - mPauseButton->setVisible(!show_play); -} - -//----------------------------------------------------------------------------- -// onBtnOK() -//----------------------------------------------------------------------------- -void LLFloaterBvhPreview::onBtnOK(void* userdata) -{ - LLFloaterBvhPreview* floaterp = (LLFloaterBvhPreview*)userdata; - if (!floaterp->getEnabled()) return; - - if (floaterp->mAnimPreview) - { - LLKeyframeMotion* motionp = (LLKeyframeMotion*)floaterp->mAnimPreview->getDummyAvatar()->findMotion(floaterp->mMotionID); - - S32 file_size = motionp->getFileSize(); - U8* buffer = new U8[file_size]; - - LLDataPackerBinaryBuffer dp(buffer, file_size); - if (motionp->serialize(dp)) - { - LLFileSystem file(motionp->getID(), LLAssetType::AT_ANIMATION, LLFileSystem::APPEND); - - S32 size = dp.getCurrentSize(); - if (file.write((U8*)buffer, size)) - { - std::string name = floaterp->getChild("name_form")->getValue().asString(); - std::string desc = floaterp->getChild("description_form")->getValue().asString(); - S32 expected_upload_cost = LLAgentBenefitsMgr::current().getAnimationUploadCost(); - - LLResourceUploadInfo::ptr_t assetUploadInfo(new LLResourceUploadInfo( - floaterp->mTransactionID, LLAssetType::AT_ANIMATION, - name, desc, 0, - LLFolderType::FT_NONE, LLInventoryType::IT_ANIMATION, - LLFloaterPerms::getNextOwnerPerms("Uploads"), - LLFloaterPerms::getGroupPerms("Uploads"), - LLFloaterPerms::getEveryonePerms("Uploads"), - expected_upload_cost)); - - upload_new_resource(assetUploadInfo); - } - else - { - LL_WARNS() << "Failure writing animation data." << LL_ENDL; - LLNotificationsUtil::add("WriteAnimationFail"); - } - } - - delete [] buffer; - // clear out cache for motion data - floaterp->mAnimPreview->getDummyAvatar()->removeMotion(floaterp->mMotionID); - LLKeyframeDataCache::removeKeyframeData(floaterp->mMotionID); - } - - floaterp->closeFloater(false); -} - -//----------------------------------------------------------------------------- -// LLPreviewAnimation -//----------------------------------------------------------------------------- -LLPreviewAnimation::LLPreviewAnimation(S32 width, S32 height) : LLViewerDynamicTexture(width, height, 3, ORDER_MIDDLE, false) -{ - mNeedsUpdate = true; - mCameraDistance = PREVIEW_CAMERA_DISTANCE; - mCameraYaw = 0.f; - mCameraPitch = 0.f; - mCameraZoom = 1.f; - - mDummyAvatar = (LLVOAvatar*)gObjectList.createObjectViewer(LL_PCODE_LEGACY_AVATAR, gAgent.getRegion(), LLViewerObject::CO_FLAG_UI_AVATAR); - mDummyAvatar->mSpecialRenderMode = 1; - mDummyAvatar->startMotion(ANIM_AGENT_STAND, BASE_ANIM_TIME_OFFSET); - - // on idle overall apperance update will set skirt to visible, so either - // call early or account for mSpecialRenderMode in updateMeshVisibility - mDummyAvatar->updateOverallAppearance(); - mDummyAvatar->hideHair(); - mDummyAvatar->hideSkirt(); - - // stop extraneous animations - mDummyAvatar->stopMotion( ANIM_AGENT_HEAD_ROT, true ); - mDummyAvatar->stopMotion( ANIM_AGENT_EYE, true ); - mDummyAvatar->stopMotion( ANIM_AGENT_BODY_NOISE, true ); - mDummyAvatar->stopMotion( ANIM_AGENT_BREATHE_ROT, true ); -} - -//----------------------------------------------------------------------------- -// LLPreviewAnimation() -//----------------------------------------------------------------------------- -LLPreviewAnimation::~LLPreviewAnimation() -{ - mDummyAvatar->markDead(); -} - -//virtual -S8 LLPreviewAnimation::getType() const -{ - return LLViewerDynamicTexture::LL_PREVIEW_ANIMATION ; -} - -//----------------------------------------------------------------------------- -// update() -//----------------------------------------------------------------------------- -bool LLPreviewAnimation::render() -{ - mNeedsUpdate = false; - LLVOAvatar* avatarp = mDummyAvatar; - - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.pushMatrix(); - gGL.loadIdentity(); - gGL.ortho(0.0f, mFullWidth, 0.0f, mFullHeight, -1.0f, 1.0f); - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - gGL.loadIdentity(); - - gUIProgram.bind(); - - LLGLSUIDefault def; - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.color4f(0.15f, 0.2f, 0.3f, 1.f); - - gl_rect_2d_simple( mFullWidth, mFullHeight ); - - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.popMatrix(); - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.popMatrix(); - - gGL.flush(); - - LLVector3 target_pos = avatarp->mRoot->getWorldPosition(); - - LLQuaternion camera_rot = LLQuaternion(mCameraPitch, LLVector3::y_axis) * - LLQuaternion(mCameraYaw, LLVector3::z_axis); - - LLViewerCamera* camera = LLViewerCamera::getInstance(); - - LLQuaternion av_rot = avatarp->mRoot->getWorldRotation() * camera_rot; - camera->setOriginAndLookAt( - target_pos + ((LLVector3(mCameraDistance, 0.f, 0.f) + mCameraOffset) * av_rot), // camera - LLVector3::z_axis, // up - target_pos + (mCameraOffset * av_rot) ); // point of interest - - camera->setViewNoBroadcast(LLViewerCamera::getInstance()->getDefaultFOV() / mCameraZoom); - camera->setAspect((F32) mFullWidth / (F32) mFullHeight); - camera->setPerspective(false, mOrigin.mX, mOrigin.mY, mFullWidth, mFullHeight, false); - - //SJB: Animation is updated in LLVOAvatar::updateCharacter - - if (avatarp->mDrawable.notNull()) - { - avatarp->updateLOD(); - - LLVertexBuffer::unbind(); - LLGLDepthTest gls_depth(GL_TRUE); - - LLFace* face = avatarp->mDrawable->getFace(0); - if (face) - { - LLDrawPoolAvatar *avatarPoolp = (LLDrawPoolAvatar *)face->getPool(); - avatarp->dirtyMesh(); - gPipeline.enableLightsPreview(); - avatarPoolp->renderAvatars(avatarp); // renders only one avatar - } - } - - gGL.color4f(1,1,1,1); - return true; -} - -//----------------------------------------------------------------------------- -// requestUpdate() -//----------------------------------------------------------------------------- -void LLPreviewAnimation::requestUpdate() -{ - mNeedsUpdate = true; -} - -//----------------------------------------------------------------------------- -// rotate() -//----------------------------------------------------------------------------- -void LLPreviewAnimation::rotate(F32 yaw_radians, F32 pitch_radians) -{ - mCameraYaw = mCameraYaw + yaw_radians; - - mCameraPitch = llclamp(mCameraPitch + pitch_radians, F_PI_BY_TWO * -0.8f, F_PI_BY_TWO * 0.8f); -} - -//----------------------------------------------------------------------------- -// zoom() -//----------------------------------------------------------------------------- -void LLPreviewAnimation::zoom(F32 zoom_delta) -{ - setZoom(mCameraZoom + zoom_delta); -} - -//----------------------------------------------------------------------------- -// setZoom() -//----------------------------------------------------------------------------- -void LLPreviewAnimation::setZoom(F32 zoom_amt) -{ - mCameraZoom = llclamp(zoom_amt, MIN_CAMERA_ZOOM, MAX_CAMERA_ZOOM); -} - -//----------------------------------------------------------------------------- -// pan() -//----------------------------------------------------------------------------- -void LLPreviewAnimation::pan(F32 right, F32 up) -{ - mCameraOffset.mV[VY] = llclamp(mCameraOffset.mV[VY] + right * mCameraDistance / mCameraZoom, -1.f, 1.f); - mCameraOffset.mV[VZ] = llclamp(mCameraOffset.mV[VZ] + up * mCameraDistance / mCameraZoom, -1.f, 1.f); -} - - - +/** + * @file llfloaterbvhpreview.cpp + * @brief LLFloaterBvhPreview class implementation + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterbvhpreview.h" + +#include "llbvhloader.h" +#include "lldatapacker.h" +#include "lldir.h" +#include "llnotificationsutil.h" +#include "llfilesystem.h" +#include "llapr.h" +#include "llstring.h" + +#include "llagent.h" +#include "llagentbenefits.h" +#include "llanimationstates.h" +#include "llbbox.h" +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "lldrawable.h" +#include "lldrawpoolavatar.h" +#include "llrender.h" +#include "llface.h" +#include "llfocusmgr.h" +#include "llkeyframemotion.h" +#include "lllineeditor.h" +#include "llfloaterperms.h" +#include "llsliderctrl.h" +#include "llspinctrl.h" +#include "lltextbox.h" +#include "lltoolmgr.h" +#include "llui.h" +#include "llviewercamera.h" +#include "llviewerobjectlist.h" +#include "llviewerwindow.h" +#include "llviewermenufile.h" // upload_new_resource() +#include "llvoavatar.h" +#include "pipeline.h" +#include "lluictrlfactory.h" +#include "lltrans.h" + +const S32 PREVIEW_BORDER_WIDTH = 2; +const S32 PREVIEW_RESIZE_HANDLE_SIZE = S32(RESIZE_HANDLE_WIDTH * OO_SQRT2) + PREVIEW_BORDER_WIDTH; +const S32 PREVIEW_HPAD = PREVIEW_RESIZE_HANDLE_SIZE; +const S32 PREVIEW_VPAD = 35; +const S32 PREF_BUTTON_HEIGHT = 16 + 35; +const S32 PREVIEW_TEXTURE_HEIGHT = 300; + +const F32 PREVIEW_CAMERA_DISTANCE = 4.f; + +const F32 MIN_CAMERA_ZOOM = 0.5f; +const F32 MAX_CAMERA_ZOOM = 10.f; + +const F32 BASE_ANIM_TIME_OFFSET = 5.f; + +std::string STATUS[] = +{ + "E_ST_OK", + "E_ST_EOF", + "E_ST_NO_CONSTRAINT", + "E_ST_NO_FILE", + "E_ST_NO_HIER", + "E_ST_NO_JOINT", + "E_ST_NO_NAME", + "E_ST_NO_OFFSET", + "E_ST_NO_CHANNELS", + "E_ST_NO_ROTATION", + "E_ST_NO_AXIS", + "E_ST_NO_MOTION", + "E_ST_NO_FRAMES", + "E_ST_NO_FRAME_TIME", + "E_ST_NO_POS", + "E_ST_NO_ROT", + "E_ST_NO_XLT_FILE", + "E_ST_NO_XLT_HEADER", + "E_ST_NO_XLT_NAME", + "E_ST_NO_XLT_IGNORE", + "E_ST_NO_XLT_RELATIVE", + "E_ST_NO_XLT_OUTNAME", + "E_ST_NO_XLT_MATRIX", + "E_ST_NO_XLT_MERGECHILD", + "E_ST_NO_XLT_MERGEPARENT", + "E_ST_NO_XLT_PRIORITY", + "E_ST_NO_XLT_LOOP", + "E_ST_NO_XLT_EASEIN", + "E_ST_NO_XLT_EASEOUT", + "E_ST_NO_XLT_HAND", + "E_ST_NO_XLT_EMOTE", +"E_ST_BAD_ROOT" +}; + +//----------------------------------------------------------------------------- +// LLFloaterBvhPreview() +//----------------------------------------------------------------------------- +LLFloaterBvhPreview::LLFloaterBvhPreview(const std::string& filename) : + LLFloaterNameDesc(filename) +{ + mLastMouseX = 0; + mLastMouseY = 0; + + mIDList["Standing"] = ANIM_AGENT_STAND; + mIDList["Walking"] = ANIM_AGENT_FEMALE_WALK; + mIDList["Sitting"] = ANIM_AGENT_SIT_FEMALE; + mIDList["Flying"] = ANIM_AGENT_HOVER; + + mIDList["[None]"] = LLUUID::null; + mIDList["Aaaaah"] = ANIM_AGENT_EXPRESS_OPEN_MOUTH; + mIDList["Afraid"] = ANIM_AGENT_EXPRESS_AFRAID; + mIDList["Angry"] = ANIM_AGENT_EXPRESS_ANGER; + mIDList["Big Smile"] = ANIM_AGENT_EXPRESS_TOOTHSMILE; + mIDList["Bored"] = ANIM_AGENT_EXPRESS_BORED; + mIDList["Cry"] = ANIM_AGENT_EXPRESS_CRY; + mIDList["Disdain"] = ANIM_AGENT_EXPRESS_DISDAIN; + mIDList["Embarrassed"] = ANIM_AGENT_EXPRESS_EMBARRASSED; + mIDList["Frown"] = ANIM_AGENT_EXPRESS_FROWN; + mIDList["Kiss"] = ANIM_AGENT_EXPRESS_KISS; + mIDList["Laugh"] = ANIM_AGENT_EXPRESS_LAUGH; + mIDList["Plllppt"] = ANIM_AGENT_EXPRESS_TONGUE_OUT; + mIDList["Repulsed"] = ANIM_AGENT_EXPRESS_REPULSED; + mIDList["Sad"] = ANIM_AGENT_EXPRESS_SAD; + mIDList["Shrug"] = ANIM_AGENT_EXPRESS_SHRUG; + mIDList["Smile"] = ANIM_AGENT_EXPRESS_SMILE; + mIDList["Surprise"] = ANIM_AGENT_EXPRESS_SURPRISE; + mIDList["Wink"] = ANIM_AGENT_EXPRESS_WINK; + mIDList["Worry"] = ANIM_AGENT_EXPRESS_WORRY; +} + +//----------------------------------------------------------------------------- +// setAnimCallbacks() +//----------------------------------------------------------------------------- +void LLFloaterBvhPreview::setAnimCallbacks() +{ + getChild("playback_slider")->setCommitCallback(boost::bind(&LLFloaterBvhPreview::onSliderMove, this)); + + getChild("preview_base_anim")->setCommitCallback(boost::bind(&LLFloaterBvhPreview::onCommitBaseAnim, this)); + getChild("preview_base_anim")->setValue("Standing"); + + getChild("priority")->setCommitCallback(boost::bind(&LLFloaterBvhPreview::onCommitPriority, this)); + getChild("loop_check")->setCommitCallback(boost::bind(&LLFloaterBvhPreview::onCommitLoop, this)); + getChild("loop_in_point")->setCommitCallback(boost::bind(&LLFloaterBvhPreview::onCommitLoopIn, this)); + getChild("loop_in_point")->setValidateBeforeCommit( boost::bind(&LLFloaterBvhPreview::validateLoopIn, this, _1)); + getChild("loop_out_point")->setCommitCallback(boost::bind(&LLFloaterBvhPreview::onCommitLoopOut, this)); + getChild("loop_out_point")->setValidateBeforeCommit( boost::bind(&LLFloaterBvhPreview::validateLoopOut, this, _1)); + + getChild("hand_pose_combo")->setCommitCallback(boost::bind(&LLFloaterBvhPreview::onCommitHandPose, this)); + + getChild("emote_combo")->setCommitCallback(boost::bind(&LLFloaterBvhPreview::onCommitEmote, this)); + getChild("emote_combo")->setValue("[None]"); + + getChild("ease_in_time")->setCommitCallback(boost::bind(&LLFloaterBvhPreview::onCommitEaseIn, this)); + getChild("ease_in_time")->setValidateBeforeCommit( boost::bind(&LLFloaterBvhPreview::validateEaseIn, this, _1)); + getChild("ease_out_time")->setCommitCallback(boost::bind(&LLFloaterBvhPreview::onCommitEaseOut, this)); + getChild("ease_out_time")->setValidateBeforeCommit( boost::bind(&LLFloaterBvhPreview::validateEaseOut, this, _1)); +} + +std::map LLFloaterBvhPreview::getJointAliases() +{ + LLPointer av = (LLVOAvatar*)mAnimPreview->getDummyAvatar(); + return av->getJointAliases(); +} + +//----------------------------------------------------------------------------- +// postBuild() +//----------------------------------------------------------------------------- +bool LLFloaterBvhPreview::postBuild() +{ + LLKeyframeMotion* motionp = NULL; + LLBVHLoader* loaderp = NULL; + + if (!LLFloaterNameDesc::postBuild()) + { + return false; + } + + getChild("name_form")->setCommitCallback(boost::bind(&LLFloaterBvhPreview::onCommitName, this)); + + childSetAction("ok_btn", onBtnOK, this); + setDefaultBtn(); + + mPreviewRect.set(PREVIEW_HPAD, + PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD, + getRect().getWidth() - PREVIEW_HPAD, + PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); + mPreviewImageRect.set(0.f, 1.f, 1.f, 0.f); + + mPlayButton = getChild( "play_btn"); + mPlayButton->setClickedCallback(boost::bind(&LLFloaterBvhPreview::onBtnPlay, this)); + mPlayButton->setVisible(true); + + mPauseButton = getChild( "pause_btn"); + mPauseButton->setClickedCallback(boost::bind(&LLFloaterBvhPreview::onBtnPause, this)); + mPauseButton->setVisible(false); + + mStopButton = getChild( "stop_btn"); + mStopButton->setClickedCallback(boost::bind(&LLFloaterBvhPreview::onBtnStop, this)); + + getChildView("bad_animation_text")->setVisible(false); + + mAnimPreview = new LLPreviewAnimation(256, 256); + + std::string exten = gDirUtilp->getExtension(mFilename); + if (exten == "bvh") + { + // loading a bvh file + + // now load bvh file + S32 file_size; + + LLAPRFile infile ; + infile.open(mFilenameAndPath, LL_APR_RB, NULL, &file_size); + + if (!infile.getFileHandle()) + { + LL_WARNS() << "Can't open BVH file:" << mFilename << LL_ENDL; + } + else + { + char* file_buffer; + + file_buffer = new char[file_size + 1]; + + if (file_size == infile.read(file_buffer, file_size)) + { + file_buffer[file_size] = '\0'; + LL_INFOS() << "Loading BVH file " << mFilename << LL_ENDL; + ELoadStatus load_status = E_ST_OK; + S32 line_number = 0; + + std::map joint_alias_map = getJointAliases(); + + loaderp = new LLBVHLoader(file_buffer, load_status, line_number, joint_alias_map); + std::string status = getString(STATUS[load_status]); + + if(load_status == E_ST_NO_XLT_FILE) + { + LL_WARNS() << "NOTE: No translation table found." << LL_ENDL; + } + else + { + LL_WARNS() << "ERROR: [line: " << line_number << "] " << status << LL_ENDL; + } + } + + infile.close() ; + delete[] file_buffer; + } + } + + if (loaderp && loaderp->isInitialized() && loaderp->getDuration() <= MAX_ANIM_DURATION) + { + // generate unique id for this motion + mTransactionID.generate(); + mMotionID = mTransactionID.makeAssetID(gAgent.getSecureSessionID()); + + // motion will be returned, but it will be in a load-pending state, as this is a new motion + // this motion will not request an asset transfer until next update, so we have a chance to + // load the keyframe data locally + motionp = (LLKeyframeMotion*)mAnimPreview->getDummyAvatar()->createMotion(mMotionID); + + // create data buffer for keyframe initialization + S32 buffer_size = loaderp->getOutputSize(); + U8* buffer = new U8[buffer_size]; + + LLDataPackerBinaryBuffer dp(buffer, buffer_size); + + // pass animation data through memory buffer + LL_INFOS("BVH") << "Serializing loaderp" << LL_ENDL; + loaderp->serialize(dp); + dp.reset(); + LL_INFOS("BVH") << "Deserializing motionp" << LL_ENDL; + bool success = motionp && motionp->deserialize(dp, mMotionID, false); + LL_INFOS("BVH") << "Done" << LL_ENDL; + + delete []buffer; + + if (success) + { + setAnimCallbacks() ; + + const LLBBoxLocal &pelvis_bbox = motionp->getPelvisBBox(); + + LLVector3 temp = pelvis_bbox.getCenter(); + // only consider XY? + //temp.mV[VZ] = 0.f; + F32 pelvis_offset = temp.magVec(); + + temp = pelvis_bbox.getExtent(); + //temp.mV[VZ] = 0.f; + F32 pelvis_max_displacement = pelvis_offset + (temp.magVec() * 0.5f) + 1.f; + + F32 camera_zoom = LLViewerCamera::getInstance()->getDefaultFOV() / (2.f * atan(pelvis_max_displacement / PREVIEW_CAMERA_DISTANCE)); + + mAnimPreview->setZoom(camera_zoom); + + motionp->setName(getChild("name_form")->getValue().asString()); + mAnimPreview->getDummyAvatar()->startMotion(mMotionID); + + getChild("playback_slider")->setMinValue(0.0); + getChild("playback_slider")->setMaxValue(1.0); + + getChild("loop_check")->setValue(LLSD(motionp->getLoop())); + getChild("loop_in_point")->setValue(LLSD(motionp->getLoopIn() / motionp->getDuration() * 100.f)); + getChild("loop_out_point")->setValue(LLSD(motionp->getLoopOut() / motionp->getDuration() * 100.f)); + getChild("priority")->setValue(LLSD((F32)motionp->getPriority())); + getChild("hand_pose_combo")->setValue(LLHandMotion::getHandPoseName(motionp->getHandPose())); + getChild("ease_in_time")->setValue(LLSD(motionp->getEaseInDuration())); + getChild("ease_out_time")->setValue(LLSD(motionp->getEaseOutDuration())); + setEnabled(true); + std::string seconds_string; + seconds_string = llformat(" - %.2f seconds", motionp->getDuration()); + + setTitle(mFilename + std::string(seconds_string)); + } + else + { + mAnimPreview = NULL; + mMotionID.setNull(); + getChild("bad_animation_text")->setValue(getString("failed_to_initialize")); + } + } + else + { + if ( loaderp ) + { + if (loaderp->getDuration() > MAX_ANIM_DURATION) + { + LLUIString out_str = getString("anim_too_long"); + out_str.setArg("[LENGTH]", llformat("%.1f", loaderp->getDuration())); + out_str.setArg("[MAX_LENGTH]", llformat("%.1f", MAX_ANIM_DURATION)); + getChild("bad_animation_text")->setValue(out_str.getString()); + } + else + { + LLUIString out_str = getString("failed_file_read"); + out_str.setArg("[STATUS]", getString(STATUS[loaderp->getStatus()])); + getChild("bad_animation_text")->setValue(out_str.getString()); + } + } + + //setEnabled(false); + mMotionID.setNull(); + mAnimPreview = NULL; + } + + refresh(); + + delete loaderp; + + return true; +} + +//----------------------------------------------------------------------------- +// LLFloaterBvhPreview() +//----------------------------------------------------------------------------- +LLFloaterBvhPreview::~LLFloaterBvhPreview() +{ + mAnimPreview = NULL; + + setEnabled(false); +} + +//----------------------------------------------------------------------------- +// draw() +//----------------------------------------------------------------------------- +void LLFloaterBvhPreview::draw() +{ + LLFloater::draw(); + LLRect r = getRect(); + + refresh(); + + if (mMotionID.notNull() && mAnimPreview) + { + gGL.color3f(1.f, 1.f, 1.f); + + gGL.getTexUnit(0)->bind(mAnimPreview); + + gGL.begin( LLRender::QUADS ); + { + gGL.texCoord2f(0.f, 1.f); + gGL.vertex2i(PREVIEW_HPAD, PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD); + gGL.texCoord2f(0.f, 0.f); + gGL.vertex2i(PREVIEW_HPAD, PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); + gGL.texCoord2f(1.f, 0.f); + gGL.vertex2i(r.getWidth() - PREVIEW_HPAD, PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); + gGL.texCoord2f(1.f, 1.f); + gGL.vertex2i(r.getWidth() - PREVIEW_HPAD, PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD); + } + gGL.end(); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); + if (!avatarp->areAnimationsPaused()) + { + mAnimPreview->requestUpdate(); + } + } +} + +//----------------------------------------------------------------------------- +// resetMotion() +//----------------------------------------------------------------------------- +void LLFloaterBvhPreview::resetMotion() +{ + if (!mAnimPreview) + return; + + LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); + bool paused = avatarp->areAnimationsPaused(); + + LLKeyframeMotion* motionp = dynamic_cast(avatarp->findMotion(mMotionID)); + if( motionp ) + { + // Set emotion + std::string emote = getChild("emote_combo")->getValue().asString(); + motionp->setEmote(mIDList[emote]); + } + + LLUUID base_id = mIDList[getChild("preview_base_anim")->getValue().asString()]; + avatarp->deactivateAllMotions(); + avatarp->startMotion(mMotionID, 0.0f); + avatarp->startMotion(base_id, BASE_ANIM_TIME_OFFSET); + getChild("playback_slider")->setValue(0.0f); + + // Set pose + std::string handpose = getChild("hand_pose_combo")->getValue().asString(); + avatarp->startMotion( ANIM_AGENT_HAND_MOTION, 0.0f ); + + if( motionp ) + { + motionp->setHandPose(LLHandMotion::getHandPose(handpose)); + } + + if (paused) + { + mPauseRequest = avatarp->requestPause(); + } + else + { + mPauseRequest = NULL; + } +} + +//----------------------------------------------------------------------------- +// handleMouseDown() +//----------------------------------------------------------------------------- +bool LLFloaterBvhPreview::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (mPreviewRect.pointInRect(x, y)) + { + bringToFront( x, y ); + gFocusMgr.setMouseCapture(this); + gViewerWindow->hideCursor(); + mLastMouseX = x; + mLastMouseY = y; + return true; + } + + return LLFloater::handleMouseDown(x, y, mask); +} + +//----------------------------------------------------------------------------- +// handleMouseUp() +//----------------------------------------------------------------------------- +bool LLFloaterBvhPreview::handleMouseUp(S32 x, S32 y, MASK mask) +{ + gFocusMgr.setMouseCapture(nullptr); + gViewerWindow->showCursor(); + return LLFloater::handleMouseUp(x, y, mask); +} + +//----------------------------------------------------------------------------- +// handleHover() +//----------------------------------------------------------------------------- +bool LLFloaterBvhPreview::handleHover(S32 x, S32 y, MASK mask) +{ + MASK local_mask = mask & ~MASK_ALT; + + if (mAnimPreview && hasMouseCapture()) + { + if (local_mask == MASK_PAN) + { + // pan here + mAnimPreview->pan((F32)(x - mLastMouseX) * -0.005f, (F32)(y - mLastMouseY) * -0.005f); + } + else if (local_mask == MASK_ORBIT) + { + F32 yaw_radians = (F32)(x - mLastMouseX) * -0.01f; + F32 pitch_radians = (F32)(y - mLastMouseY) * 0.02f; + + mAnimPreview->rotate(yaw_radians, pitch_radians); + } + else + { + F32 yaw_radians = (F32)(x - mLastMouseX) * -0.01f; + F32 zoom_amt = (F32)(y - mLastMouseY) * 0.02f; + + mAnimPreview->rotate(yaw_radians, 0.f); + mAnimPreview->zoom(zoom_amt); + } + + mAnimPreview->requestUpdate(); + + LLUI::getInstance()->setMousePositionLocal(this, mLastMouseX, mLastMouseY); + } + + if (!mPreviewRect.pointInRect(x, y) || !mAnimPreview) + { + return LLFloater::handleHover(x, y, mask); + } + else if (local_mask == MASK_ORBIT) + { + gViewerWindow->setCursor(UI_CURSOR_TOOLCAMERA); + } + else if (local_mask == MASK_PAN) + { + gViewerWindow->setCursor(UI_CURSOR_TOOLPAN); + } + else + { + gViewerWindow->setCursor(UI_CURSOR_TOOLZOOMIN); + } + + return true; +} + +//----------------------------------------------------------------------------- +// handleScrollWheel() +//----------------------------------------------------------------------------- +bool LLFloaterBvhPreview::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + if (!mAnimPreview) + return false; + + mAnimPreview->zoom((F32)clicks * -0.2f); + mAnimPreview->requestUpdate(); + + return true; +} + +//----------------------------------------------------------------------------- +// onMouseCaptureLost() +//----------------------------------------------------------------------------- +void LLFloaterBvhPreview::onMouseCaptureLost() +{ + gViewerWindow->showCursor(); +} + +//----------------------------------------------------------------------------- +// onBtnPlay() +//----------------------------------------------------------------------------- +void LLFloaterBvhPreview::onBtnPlay() +{ + if (!getEnabled()) + return; + + if (mMotionID.notNull() && mAnimPreview) + { + LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); + + if (!avatarp->isMotionActive(mMotionID)) + { + resetMotion(); + mPauseRequest = NULL; + } + else if (avatarp->areAnimationsPaused()) + { + mPauseRequest = NULL; + } + } +} + +//----------------------------------------------------------------------------- +// onBtnPause() +//----------------------------------------------------------------------------- +void LLFloaterBvhPreview::onBtnPause() +{ + if (!getEnabled()) + return; + + if (mMotionID.notNull() && mAnimPreview) + { + LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); + + if (avatarp->isMotionActive(mMotionID)) + { + if (!avatarp->areAnimationsPaused()) + { + mPauseRequest = avatarp->requestPause(); + } + } + } +} + +//----------------------------------------------------------------------------- +// onBtnStop() +//----------------------------------------------------------------------------- +void LLFloaterBvhPreview::onBtnStop() +{ + if (!getEnabled()) + return; + + if (mMotionID.notNull() && mAnimPreview) + { + LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); + resetMotion(); + mPauseRequest = avatarp->requestPause(); + } +} + +//----------------------------------------------------------------------------- +// onSliderMove() +//----------------------------------------------------------------------------- +void LLFloaterBvhPreview::onSliderMove() +{ + if (!getEnabled()) + return; + + if (mAnimPreview) + { + LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); + F32 slider_value = (F32)getChild("playback_slider")->getValue().asReal(); + LLUUID base_id = mIDList[getChild("preview_base_anim")->getValue().asString()]; + LLMotion* motionp = avatarp->findMotion(mMotionID); + F32 duration = motionp->getDuration();// + motionp->getEaseOutDuration(); + F32 delta_time = duration * slider_value; + avatarp->deactivateAllMotions(); + avatarp->startMotion(base_id, delta_time + BASE_ANIM_TIME_OFFSET); + avatarp->startMotion(mMotionID, delta_time); + mPauseRequest = avatarp->requestPause(); + refresh(); + } + +} + +//----------------------------------------------------------------------------- +// onCommitBaseAnim() +//----------------------------------------------------------------------------- +void LLFloaterBvhPreview::onCommitBaseAnim() +{ + if (!getEnabled()) + return; + + if (mAnimPreview) + { + LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); + + bool paused = avatarp->areAnimationsPaused(); + + // stop all other possible base motions + avatarp->stopMotion(mIDList["Standing"], true); + avatarp->stopMotion(mIDList["Walking"], true); + avatarp->stopMotion(mIDList["Sitting"], true); + avatarp->stopMotion(mIDList["Flying"], true); + + resetMotion(); + + if (!paused) + { + mPauseRequest = NULL; + } + } +} + +//----------------------------------------------------------------------------- +// onCommitLoop() +//----------------------------------------------------------------------------- +void LLFloaterBvhPreview::onCommitLoop() +{ + if (!getEnabled() || !mAnimPreview) + return; + + LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); + LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(mMotionID); + + if (motionp) + { + motionp->setLoop(getChild("loop_check")->getValue().asBoolean()); + motionp->setLoopIn((F32)getChild("loop_in_point")->getValue().asReal() * 0.01f * motionp->getDuration()); + motionp->setLoopOut((F32)getChild("loop_out_point")->getValue().asReal() * 0.01f * motionp->getDuration()); + } +} + +//----------------------------------------------------------------------------- +// onCommitLoopIn() +//----------------------------------------------------------------------------- +void LLFloaterBvhPreview::onCommitLoopIn() +{ + if (!getEnabled() || !mAnimPreview) + return; + + LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); + LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(mMotionID); + + if (motionp) + { + motionp->setLoopIn((F32)getChild("loop_in_point")->getValue().asReal() / 100.f); + resetMotion(); + getChild("loop_check")->setValue(LLSD(true)); + onCommitLoop(); + } +} + +//----------------------------------------------------------------------------- +// onCommitLoopOut() +//----------------------------------------------------------------------------- +void LLFloaterBvhPreview::onCommitLoopOut() +{ + if (!getEnabled() || !mAnimPreview) + return; + + LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); + LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(mMotionID); + + if (motionp) + { + motionp->setLoopOut((F32)getChild("loop_out_point")->getValue().asReal() * 0.01f * motionp->getDuration()); + resetMotion(); + getChild("loop_check")->setValue(LLSD(true)); + onCommitLoop(); + } +} + +//----------------------------------------------------------------------------- +// onCommitName() +//----------------------------------------------------------------------------- +void LLFloaterBvhPreview::onCommitName() +{ + if (!getEnabled() || !mAnimPreview) + return; + + LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); + LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(mMotionID); + + if (motionp) + { + motionp->setName(getChild("name_form")->getValue().asString()); + } + + doCommit(); +} + +//----------------------------------------------------------------------------- +// onCommitHandPose() +//----------------------------------------------------------------------------- +void LLFloaterBvhPreview::onCommitHandPose() +{ + if (!getEnabled()) + return; + + resetMotion(); // sets hand pose +} + +//----------------------------------------------------------------------------- +// onCommitEmote() +//----------------------------------------------------------------------------- +void LLFloaterBvhPreview::onCommitEmote() +{ + if (!getEnabled()) + return; + + resetMotion(); // ssts emote +} + +//----------------------------------------------------------------------------- +// onCommitPriority() +//----------------------------------------------------------------------------- +void LLFloaterBvhPreview::onCommitPriority() +{ + if (!getEnabled() || !mAnimPreview) + return; + + LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); + LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(mMotionID); + + motionp->setPriority(llfloor((F32)getChild("priority")->getValue().asReal())); +} + +//----------------------------------------------------------------------------- +// onCommitEaseIn() +//----------------------------------------------------------------------------- +void LLFloaterBvhPreview::onCommitEaseIn() +{ + if (!getEnabled() || !mAnimPreview) + return; + + LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); + LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(mMotionID); + + motionp->setEaseIn((F32)getChild("ease_in_time")->getValue().asReal()); + resetMotion(); +} + +//----------------------------------------------------------------------------- +// onCommitEaseOut() +//----------------------------------------------------------------------------- +void LLFloaterBvhPreview::onCommitEaseOut() +{ + if (!getEnabled() || !mAnimPreview) + return; + + LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); + LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(mMotionID); + + motionp->setEaseOut((F32)getChild("ease_out_time")->getValue().asReal()); + resetMotion(); +} + +//----------------------------------------------------------------------------- +// validateEaseIn() +//----------------------------------------------------------------------------- +bool LLFloaterBvhPreview::validateEaseIn(const LLSD& data) +{ + if (!getEnabled() || !mAnimPreview) + return false; + + LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); + LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(mMotionID); + + if (!motionp->getLoop()) + { + F32 new_ease_in = llclamp((F32)getChild("ease_in_time")->getValue().asReal(), 0.f, motionp->getDuration() - motionp->getEaseOutDuration()); + getChild("ease_in_time")->setValue(LLSD(new_ease_in)); + } + + return true; +} + +//----------------------------------------------------------------------------- +// validateEaseOut() +//----------------------------------------------------------------------------- +bool LLFloaterBvhPreview::validateEaseOut(const LLSD& data) +{ + if (!getEnabled() || !mAnimPreview) + return false; + + LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); + LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(mMotionID); + + if (!motionp->getLoop()) + { + F32 new_ease_out = llclamp((F32)getChild("ease_out_time")->getValue().asReal(), 0.f, motionp->getDuration() - motionp->getEaseInDuration()); + getChild("ease_out_time")->setValue(LLSD(new_ease_out)); + } + + return true; +} + +//----------------------------------------------------------------------------- +// validateLoopIn() +//----------------------------------------------------------------------------- +bool LLFloaterBvhPreview::validateLoopIn(const LLSD& data) +{ + if (!getEnabled()) + return false; + + F32 loop_in_value = (F32)getChild("loop_in_point")->getValue().asReal(); + F32 loop_out_value = (F32)getChild("loop_out_point")->getValue().asReal(); + + if (loop_in_value < 0.f) + { + loop_in_value = 0.f; + } + else if (loop_in_value > 100.f) + { + loop_in_value = 100.f; + } + else if (loop_in_value > loop_out_value) + { + loop_in_value = loop_out_value; + } + + getChild("loop_in_point")->setValue(LLSD(loop_in_value)); + return true; +} + +//----------------------------------------------------------------------------- +// validateLoopOut() +//----------------------------------------------------------------------------- +bool LLFloaterBvhPreview::validateLoopOut(const LLSD& data) +{ + if (!getEnabled()) + return false; + + F32 loop_out_value = (F32)getChild("loop_out_point")->getValue().asReal(); + F32 loop_in_value = (F32)getChild("loop_in_point")->getValue().asReal(); + + if (loop_out_value < 0.f) + { + loop_out_value = 0.f; + } + else if (loop_out_value > 100.f) + { + loop_out_value = 100.f; + } + else if (loop_out_value < loop_in_value) + { + loop_out_value = loop_in_value; + } + + getChild("loop_out_point")->setValue(LLSD(loop_out_value)); + return true; +} + + +//----------------------------------------------------------------------------- +// refresh() +//----------------------------------------------------------------------------- +void LLFloaterBvhPreview::refresh() +{ + // Are we showing the play button (default) or the pause button? + bool show_play = true; + if (!mAnimPreview) + { + getChildView("bad_animation_text")->setVisible(true); + // play button visible but disabled + mPlayButton->setEnabled(false); + mStopButton->setEnabled(false); + getChildView("ok_btn")->setEnabled(false); + } + else + { + getChildView("bad_animation_text")->setVisible(false); + // re-enabled in case previous animation was bad + mPlayButton->setEnabled(true); + mStopButton->setEnabled(true); + LLVOAvatar* avatarp = mAnimPreview->getDummyAvatar(); + if (avatarp->isMotionActive(mMotionID)) + { + mStopButton->setEnabled(true); + LLKeyframeMotion* motionp = (LLKeyframeMotion*)avatarp->findMotion(mMotionID); + if (!avatarp->areAnimationsPaused()) + { + // animation is playing + if (motionp) + { + F32 fraction_complete = motionp->getLastUpdateTime() / motionp->getDuration(); + getChild("playback_slider")->setValue(fraction_complete); + } + show_play = false; + } + } + else + { + // Motion just finished playing + mPauseRequest = avatarp->requestPause(); + } + getChildView("ok_btn")->setEnabled(true); + mAnimPreview->requestUpdate(); + } + mPlayButton->setVisible(show_play); + mPauseButton->setVisible(!show_play); +} + +//----------------------------------------------------------------------------- +// onBtnOK() +//----------------------------------------------------------------------------- +void LLFloaterBvhPreview::onBtnOK(void* userdata) +{ + LLFloaterBvhPreview* floaterp = (LLFloaterBvhPreview*)userdata; + if (!floaterp->getEnabled()) return; + + if (floaterp->mAnimPreview) + { + LLKeyframeMotion* motionp = (LLKeyframeMotion*)floaterp->mAnimPreview->getDummyAvatar()->findMotion(floaterp->mMotionID); + + S32 file_size = motionp->getFileSize(); + U8* buffer = new U8[file_size]; + + LLDataPackerBinaryBuffer dp(buffer, file_size); + if (motionp->serialize(dp)) + { + LLFileSystem file(motionp->getID(), LLAssetType::AT_ANIMATION, LLFileSystem::APPEND); + + S32 size = dp.getCurrentSize(); + if (file.write((U8*)buffer, size)) + { + std::string name = floaterp->getChild("name_form")->getValue().asString(); + std::string desc = floaterp->getChild("description_form")->getValue().asString(); + S32 expected_upload_cost = LLAgentBenefitsMgr::current().getAnimationUploadCost(); + + LLResourceUploadInfo::ptr_t assetUploadInfo(new LLResourceUploadInfo( + floaterp->mTransactionID, LLAssetType::AT_ANIMATION, + name, desc, 0, + LLFolderType::FT_NONE, LLInventoryType::IT_ANIMATION, + LLFloaterPerms::getNextOwnerPerms("Uploads"), + LLFloaterPerms::getGroupPerms("Uploads"), + LLFloaterPerms::getEveryonePerms("Uploads"), + expected_upload_cost)); + + upload_new_resource(assetUploadInfo); + } + else + { + LL_WARNS() << "Failure writing animation data." << LL_ENDL; + LLNotificationsUtil::add("WriteAnimationFail"); + } + } + + delete [] buffer; + // clear out cache for motion data + floaterp->mAnimPreview->getDummyAvatar()->removeMotion(floaterp->mMotionID); + LLKeyframeDataCache::removeKeyframeData(floaterp->mMotionID); + } + + floaterp->closeFloater(false); +} + +//----------------------------------------------------------------------------- +// LLPreviewAnimation +//----------------------------------------------------------------------------- +LLPreviewAnimation::LLPreviewAnimation(S32 width, S32 height) : LLViewerDynamicTexture(width, height, 3, ORDER_MIDDLE, false) +{ + mNeedsUpdate = true; + mCameraDistance = PREVIEW_CAMERA_DISTANCE; + mCameraYaw = 0.f; + mCameraPitch = 0.f; + mCameraZoom = 1.f; + + mDummyAvatar = (LLVOAvatar*)gObjectList.createObjectViewer(LL_PCODE_LEGACY_AVATAR, gAgent.getRegion(), LLViewerObject::CO_FLAG_UI_AVATAR); + mDummyAvatar->mSpecialRenderMode = 1; + mDummyAvatar->startMotion(ANIM_AGENT_STAND, BASE_ANIM_TIME_OFFSET); + + // on idle overall apperance update will set skirt to visible, so either + // call early or account for mSpecialRenderMode in updateMeshVisibility + mDummyAvatar->updateOverallAppearance(); + mDummyAvatar->hideHair(); + mDummyAvatar->hideSkirt(); + + // stop extraneous animations + mDummyAvatar->stopMotion( ANIM_AGENT_HEAD_ROT, true ); + mDummyAvatar->stopMotion( ANIM_AGENT_EYE, true ); + mDummyAvatar->stopMotion( ANIM_AGENT_BODY_NOISE, true ); + mDummyAvatar->stopMotion( ANIM_AGENT_BREATHE_ROT, true ); +} + +//----------------------------------------------------------------------------- +// LLPreviewAnimation() +//----------------------------------------------------------------------------- +LLPreviewAnimation::~LLPreviewAnimation() +{ + mDummyAvatar->markDead(); +} + +//virtual +S8 LLPreviewAnimation::getType() const +{ + return LLViewerDynamicTexture::LL_PREVIEW_ANIMATION ; +} + +//----------------------------------------------------------------------------- +// update() +//----------------------------------------------------------------------------- +bool LLPreviewAnimation::render() +{ + mNeedsUpdate = false; + LLVOAvatar* avatarp = mDummyAvatar; + + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.pushMatrix(); + gGL.loadIdentity(); + gGL.ortho(0.0f, mFullWidth, 0.0f, mFullHeight, -1.0f, 1.0f); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + gGL.loadIdentity(); + + gUIProgram.bind(); + + LLGLSUIDefault def; + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gGL.color4f(0.15f, 0.2f, 0.3f, 1.f); + + gl_rect_2d_simple( mFullWidth, mFullHeight ); + + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.popMatrix(); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.popMatrix(); + + gGL.flush(); + + LLVector3 target_pos = avatarp->mRoot->getWorldPosition(); + + LLQuaternion camera_rot = LLQuaternion(mCameraPitch, LLVector3::y_axis) * + LLQuaternion(mCameraYaw, LLVector3::z_axis); + + LLViewerCamera* camera = LLViewerCamera::getInstance(); + + LLQuaternion av_rot = avatarp->mRoot->getWorldRotation() * camera_rot; + camera->setOriginAndLookAt( + target_pos + ((LLVector3(mCameraDistance, 0.f, 0.f) + mCameraOffset) * av_rot), // camera + LLVector3::z_axis, // up + target_pos + (mCameraOffset * av_rot) ); // point of interest + + camera->setViewNoBroadcast(LLViewerCamera::getInstance()->getDefaultFOV() / mCameraZoom); + camera->setAspect((F32) mFullWidth / (F32) mFullHeight); + camera->setPerspective(false, mOrigin.mX, mOrigin.mY, mFullWidth, mFullHeight, false); + + //SJB: Animation is updated in LLVOAvatar::updateCharacter + + if (avatarp->mDrawable.notNull()) + { + avatarp->updateLOD(); + + LLVertexBuffer::unbind(); + LLGLDepthTest gls_depth(GL_TRUE); + + LLFace* face = avatarp->mDrawable->getFace(0); + if (face) + { + LLDrawPoolAvatar *avatarPoolp = (LLDrawPoolAvatar *)face->getPool(); + avatarp->dirtyMesh(); + gPipeline.enableLightsPreview(); + avatarPoolp->renderAvatars(avatarp); // renders only one avatar + } + } + + gGL.color4f(1,1,1,1); + return true; +} + +//----------------------------------------------------------------------------- +// requestUpdate() +//----------------------------------------------------------------------------- +void LLPreviewAnimation::requestUpdate() +{ + mNeedsUpdate = true; +} + +//----------------------------------------------------------------------------- +// rotate() +//----------------------------------------------------------------------------- +void LLPreviewAnimation::rotate(F32 yaw_radians, F32 pitch_radians) +{ + mCameraYaw = mCameraYaw + yaw_radians; + + mCameraPitch = llclamp(mCameraPitch + pitch_radians, F_PI_BY_TWO * -0.8f, F_PI_BY_TWO * 0.8f); +} + +//----------------------------------------------------------------------------- +// zoom() +//----------------------------------------------------------------------------- +void LLPreviewAnimation::zoom(F32 zoom_delta) +{ + setZoom(mCameraZoom + zoom_delta); +} + +//----------------------------------------------------------------------------- +// setZoom() +//----------------------------------------------------------------------------- +void LLPreviewAnimation::setZoom(F32 zoom_amt) +{ + mCameraZoom = llclamp(zoom_amt, MIN_CAMERA_ZOOM, MAX_CAMERA_ZOOM); +} + +//----------------------------------------------------------------------------- +// pan() +//----------------------------------------------------------------------------- +void LLPreviewAnimation::pan(F32 right, F32 up) +{ + mCameraOffset.mV[VY] = llclamp(mCameraOffset.mV[VY] + right * mCameraDistance / mCameraZoom, -1.f, 1.f); + mCameraOffset.mV[VZ] = llclamp(mCameraOffset.mV[VZ] + up * mCameraDistance / mCameraZoom, -1.f, 1.f); +} + + + diff --git a/indra/newview/llfloaterbvhpreview.h b/indra/newview/llfloaterbvhpreview.h index e6a5820cfd..ae64521492 100644 --- a/indra/newview/llfloaterbvhpreview.h +++ b/indra/newview/llfloaterbvhpreview.h @@ -1,133 +1,133 @@ -/** - * @file llfloaterbvhpreview.h - * @brief LLFloaterBvhPreview class definition - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERBVHPREVIEW_H -#define LL_LLFLOATERBVHPREVIEW_H - -#include "llassettype.h" -#include "llfloaternamedesc.h" -#include "lldynamictexture.h" -#include "llcharacter.h" -#include "llquaternion.h" -#include "llextendedstatus.h" - -class LLVOAvatar; -class LLViewerJointMesh; - -class LLPreviewAnimation : public LLViewerDynamicTexture -{ -protected: - virtual ~LLPreviewAnimation(); - -public: - LLPreviewAnimation(S32 width, S32 height); - - /*virtual*/ S8 getType() const ; - - bool render(); - void requestUpdate(); - void rotate(F32 yaw_radians, F32 pitch_radians); - void zoom(F32 zoom_delta); - void setZoom(F32 zoom_amt); - void pan(F32 right, F32 up); - virtual bool needsUpdate() { return mNeedsUpdate; } - - LLVOAvatar* getDummyAvatar() { return mDummyAvatar; } - -protected: - bool mNeedsUpdate; - F32 mCameraDistance; - F32 mCameraYaw; - F32 mCameraPitch; - F32 mCameraZoom; - LLVector3 mCameraOffset; - LLPointer mDummyAvatar; -}; - -class LLFloaterBvhPreview : public LLFloaterNameDesc -{ -public: - LLFloaterBvhPreview(const std::string& filename); - virtual ~LLFloaterBvhPreview(); - - bool postBuild(); - - bool handleMouseDown(S32 x, S32 y, MASK mask); - bool handleMouseUp(S32 x, S32 y, MASK mask); - bool handleHover(S32 x, S32 y, MASK mask); - bool handleScrollWheel(S32 x, S32 y, S32 clicks); - void onMouseCaptureLost(); - - void refresh(); - - void onBtnPlay(); - void onBtnPause(); - void onBtnStop(); - void onSliderMove(); - void onCommitBaseAnim(); - void onCommitLoop(); - void onCommitLoopIn(); - void onCommitLoopOut(); - bool validateLoopIn(const LLSD& data); - bool validateLoopOut(const LLSD& data); - void onCommitName(); - void onCommitHandPose(); - void onCommitEmote(); - void onCommitPriority(); - void onCommitEaseIn(); - void onCommitEaseOut(); - bool validateEaseIn(const LLSD& data); - bool validateEaseOut(const LLSD& data); - static void onBtnOK(void*); - static void onSaveComplete(const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, - S32 status, LLExtStat ext_status); -private: - void setAnimCallbacks() ; - std::map getJointAliases(); - - -protected: - void draw(); - void resetMotion(); - - LLPointer< LLPreviewAnimation > mAnimPreview; - S32 mLastMouseX; - S32 mLastMouseY; - LLButton* mPlayButton; - LLButton* mPauseButton; - LLButton* mStopButton; - LLRect mPreviewRect; - LLRectf mPreviewImageRect; - LLAssetID mMotionID; - LLTransactionID mTransactionID; - LLAnimPauseRequest mPauseRequest; - - std::map mIDList; -}; - -#endif // LL_LLFLOATERBVHPREVIEW_H +/** + * @file llfloaterbvhpreview.h + * @brief LLFloaterBvhPreview class definition + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERBVHPREVIEW_H +#define LL_LLFLOATERBVHPREVIEW_H + +#include "llassettype.h" +#include "llfloaternamedesc.h" +#include "lldynamictexture.h" +#include "llcharacter.h" +#include "llquaternion.h" +#include "llextendedstatus.h" + +class LLVOAvatar; +class LLViewerJointMesh; + +class LLPreviewAnimation : public LLViewerDynamicTexture +{ +protected: + virtual ~LLPreviewAnimation(); + +public: + LLPreviewAnimation(S32 width, S32 height); + + /*virtual*/ S8 getType() const ; + + bool render(); + void requestUpdate(); + void rotate(F32 yaw_radians, F32 pitch_radians); + void zoom(F32 zoom_delta); + void setZoom(F32 zoom_amt); + void pan(F32 right, F32 up); + virtual bool needsUpdate() { return mNeedsUpdate; } + + LLVOAvatar* getDummyAvatar() { return mDummyAvatar; } + +protected: + bool mNeedsUpdate; + F32 mCameraDistance; + F32 mCameraYaw; + F32 mCameraPitch; + F32 mCameraZoom; + LLVector3 mCameraOffset; + LLPointer mDummyAvatar; +}; + +class LLFloaterBvhPreview : public LLFloaterNameDesc +{ +public: + LLFloaterBvhPreview(const std::string& filename); + virtual ~LLFloaterBvhPreview(); + + bool postBuild(); + + bool handleMouseDown(S32 x, S32 y, MASK mask); + bool handleMouseUp(S32 x, S32 y, MASK mask); + bool handleHover(S32 x, S32 y, MASK mask); + bool handleScrollWheel(S32 x, S32 y, S32 clicks); + void onMouseCaptureLost(); + + void refresh(); + + void onBtnPlay(); + void onBtnPause(); + void onBtnStop(); + void onSliderMove(); + void onCommitBaseAnim(); + void onCommitLoop(); + void onCommitLoopIn(); + void onCommitLoopOut(); + bool validateLoopIn(const LLSD& data); + bool validateLoopOut(const LLSD& data); + void onCommitName(); + void onCommitHandPose(); + void onCommitEmote(); + void onCommitPriority(); + void onCommitEaseIn(); + void onCommitEaseOut(); + bool validateEaseIn(const LLSD& data); + bool validateEaseOut(const LLSD& data); + static void onBtnOK(void*); + static void onSaveComplete(const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, + S32 status, LLExtStat ext_status); +private: + void setAnimCallbacks() ; + std::map getJointAliases(); + + +protected: + void draw(); + void resetMotion(); + + LLPointer< LLPreviewAnimation > mAnimPreview; + S32 mLastMouseX; + S32 mLastMouseY; + LLButton* mPlayButton; + LLButton* mPauseButton; + LLButton* mStopButton; + LLRect mPreviewRect; + LLRectf mPreviewImageRect; + LLAssetID mMotionID; + LLTransactionID mTransactionID; + LLAnimPauseRequest mPauseRequest; + + std::map mIDList; +}; + +#endif // LL_LLFLOATERBVHPREVIEW_H diff --git a/indra/newview/llfloatercamera.cpp b/indra/newview/llfloatercamera.cpp index 63b97df969..4a5a755696 100644 --- a/indra/newview/llfloatercamera.cpp +++ b/indra/newview/llfloatercamera.cpp @@ -1,738 +1,738 @@ -/** - * @file llfloatercamera.cpp - * @brief Container for camera control buttons (zoom, pan, orbit) - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloatercamera.h" - -// Library includes -#include "llfloaterreg.h" - -// Viewer includes -#include "llagent.h" -#include "llagentcamera.h" -#include "llpresetsmanager.h" -#include "lljoystickbutton.h" -#include "llviewercontrol.h" -#include "llviewercamera.h" -#include "lltoolmgr.h" -#include "lltoolfocus.h" -#include "llslider.h" -#include "llfirstuse.h" -#include "llhints.h" -#include "lltabcontainer.h" -#include "llviewercamera.h" -#include "llvoavatarself.h" - -static LLDefaultChildRegistry::Register r("panel_camera_item"); - -const F32 NUDGE_TIME = 0.25f; // in seconds -const F32 ORBIT_NUDGE_RATE = 0.05f; // fraction of normal speed - -// constants -#define ORBIT "cam_rotate_stick" -#define PAN "cam_track_stick" -#define ZOOM "zoom" -#define CONTROLS "controls" - -bool LLFloaterCamera::sFreeCamera = false; -bool LLFloaterCamera::sAppearanceEditing = false; - -// Zoom the camera in and out -class LLPanelCameraZoom -: public LLPanel -{ - LOG_CLASS(LLPanelCameraZoom); - -public: - struct Params : public LLInitParam::Block {}; - - LLPanelCameraZoom() { onCreate(); } - - /* virtual */ bool postBuild(); - /* virtual */ void draw(); - -protected: - LLPanelCameraZoom(const Params& p) { onCreate(); } - - void onCreate(); - void onZoomPlusHeldDown(); - void onZoomMinusHeldDown(); - void onSliderValueChanged(); - void onCameraTrack(); - void onCameraRotate(); - F32 getOrbitRate(F32 time); - -private: - LLButton* mPlusBtn { nullptr }; - LLButton* mMinusBtn{ nullptr }; - LLSlider* mSlider{ nullptr }; - - friend class LLUICtrlFactory; -}; - -LLPanelCameraItem::Params::Params() -: icon_over("icon_over"), - icon_selected("icon_selected"), - picture("picture"), - text("text"), - selected_picture("selected_picture"), - mousedown_callback("mousedown_callback") -{ -} - -LLPanelCameraItem::LLPanelCameraItem(const LLPanelCameraItem::Params& p) -: LLPanel(p) -{ - LLIconCtrl::Params icon_params = p.picture; - mPicture = LLUICtrlFactory::create(icon_params); - addChild(mPicture); - - icon_params = p.icon_over; - mIconOver = LLUICtrlFactory::create(icon_params); - addChild(mIconOver); - - icon_params = p.icon_selected; - mIconSelected = LLUICtrlFactory::create(icon_params); - addChild(mIconSelected); - - icon_params = p.selected_picture; - mPictureSelected = LLUICtrlFactory::create(icon_params); - addChild(mPictureSelected); - - LLTextBox::Params text_params = p.text; - mText = LLUICtrlFactory::create(text_params); - addChild(mText); - - if (p.mousedown_callback.isProvided()) - { - setCommitCallback(initCommitCallback(p.mousedown_callback)); - } -} - -void set_view_visible(LLView* parent, const std::string& name, bool visible) -{ - parent->getChildView(name)->setVisible(visible); -} - -bool LLPanelCameraItem::postBuild() -{ - setMouseEnterCallback(boost::bind(set_view_visible, this, "hovered_icon", true)); - setMouseLeaveCallback(boost::bind(set_view_visible, this, "hovered_icon", false)); - setMouseDownCallback(boost::bind(&LLPanelCameraItem::onAnyMouseClick, this)); - setRightMouseDownCallback(boost::bind(&LLPanelCameraItem::onAnyMouseClick, this)); - return true; -} - -void LLPanelCameraItem::onAnyMouseClick() -{ - if (mCommitSignal) (*mCommitSignal)(this, LLSD()); -} - -void LLPanelCameraItem::setValue(const LLSD& value) -{ - if (!value.isMap()) return;; - if (!value.has("selected")) return; - getChildView("selected_icon")->setVisible( value["selected"]); - getChildView("picture")->setVisible( !value["selected"]); - getChildView("selected_picture")->setVisible( value["selected"]); -} - -static LLPanelInjector t_camera_zoom_panel("camera_zoom_panel"); - -//------------------------------------------------------------------------------- -// LLPanelCameraZoom -//------------------------------------------------------------------------------- - -void LLPanelCameraZoom::onCreate() -{ - mCommitCallbackRegistrar.add("Zoom.minus", boost::bind(&LLPanelCameraZoom::onZoomMinusHeldDown, this)); - mCommitCallbackRegistrar.add("Zoom.plus", boost::bind(&LLPanelCameraZoom::onZoomPlusHeldDown, this)); - mCommitCallbackRegistrar.add("Slider.value_changed", boost::bind(&LLPanelCameraZoom::onSliderValueChanged, this)); - mCommitCallbackRegistrar.add("Camera.track", boost::bind(&LLPanelCameraZoom::onCameraTrack, this)); - mCommitCallbackRegistrar.add("Camera.rotate", boost::bind(&LLPanelCameraZoom::onCameraRotate, this)); -} - -bool LLPanelCameraZoom::postBuild() -{ - mPlusBtn = getChild("zoom_plus_btn"); - mMinusBtn = getChild("zoom_minus_btn"); - mSlider = getChild("zoom_slider"); - return LLPanel::postBuild(); -} - -void LLPanelCameraZoom::draw() -{ - mSlider->setValue(gAgentCamera.getCameraZoomFraction()); - LLPanel::draw(); -} - -void LLPanelCameraZoom::onZoomPlusHeldDown() -{ - F32 val = mSlider->getValueF32(); - F32 inc = mSlider->getIncrement(); - mSlider->setValue(val - inc); - F32 time = mPlusBtn->getHeldDownTime(); - gAgentCamera.unlockView(); - gAgentCamera.setOrbitInKey(getOrbitRate(time)); -} - -void LLPanelCameraZoom::onZoomMinusHeldDown() -{ - F32 val = mSlider->getValueF32(); - F32 inc = mSlider->getIncrement(); - mSlider->setValue(val + inc); - F32 time = mMinusBtn->getHeldDownTime(); - gAgentCamera.unlockView(); - gAgentCamera.setOrbitOutKey(getOrbitRate(time)); -} - -void LLPanelCameraZoom::onCameraTrack() -{ - // EXP-202 when camera panning activated, remove the hint - LLFirstUse::viewPopup( false ); -} - -void LLPanelCameraZoom::onCameraRotate() -{ - // EXP-202 when camera rotation activated, remove the hint - LLFirstUse::viewPopup( false ); -} - -F32 LLPanelCameraZoom::getOrbitRate(F32 time) -{ - if( time < NUDGE_TIME ) - { - F32 rate = ORBIT_NUDGE_RATE + time * (1 - ORBIT_NUDGE_RATE)/ NUDGE_TIME; - return rate; - } - else - { - return 1; - } -} - -void LLPanelCameraZoom::onSliderValueChanged() -{ - F32 zoom_level = mSlider->getValueF32(); - gAgentCamera.setCameraZoomFraction(zoom_level); -} - -void activate_camera_tool() -{ - LLToolMgr::getInstance()->setTransientTool(LLToolCamera::getInstance()); -}; - -class LLCameraInfoPanel : public LLPanel -{ -public: - typedef std::function get_vector_t; - - LLCameraInfoPanel( - const LLView* parent, - const char* title, - const LLCoordFrame& camera, - const get_vector_t get_focus - ) - : LLPanel([&]() -> LLPanel::Params - { - LLPanel::Params params; - params.rect = LLRect(parent->getLocalRect()); - return params; - }()) - , mTitle(title) - , mCamera(camera) - , mGetFocus(get_focus) - , mFont(LLFontGL::getFontSansSerifBig()) - { - } - - virtual void draw() override - { - LLPanel::draw(); - - static const U32 HPADDING = 10; - static const U32 VPADDING = 5; - LLVector3 focus = mGetFocus(); - LLVector3 sight = focus - mCamera.mOrigin; - std::pair const data[] = - { - { "Origin:", mCamera.mOrigin }, - { "X Axis:", mCamera.mXAxis }, - { "Y Axis:", mCamera.mYAxis }, - { "Z Axis:", mCamera.mZAxis }, - { "Focus:", focus }, - { "Sight:", sight } - }; - S32 width = getRect().getWidth(); - S32 height = getRect().getHeight(); - S32 row_count = 1 + sizeof(data) / sizeof(*data); - S32 row_height = (height - VPADDING * 2) / row_count; - S32 top = height - VPADDING - row_height / 2; - mFont->renderUTF8(mTitle, 0, HPADDING, top, LLColor4::white, LLFontGL::LEFT, LLFontGL::VCENTER); - for (const auto& row : data) - { - top -= row_height; - mFont->renderUTF8(row.first, 0, HPADDING, top, LLColor4::white, LLFontGL::LEFT, LLFontGL::VCENTER); - const LLVector3& vector = row.second; - for (S32 i = 0; i < 3; ++i) - { - std::string text = llformat("%.6f", vector[i]); - S32 right = width / 4 * (i + 2) - HPADDING; - mFont->renderUTF8(text, 0, right, top, LLColor4::white, LLFontGL::RIGHT, LLFontGL::VCENTER); - } - } - } - -private: - const char* mTitle; - const LLCoordFrame& mCamera; - const get_vector_t mGetFocus; - const LLFontGL* mFont; -}; - -// -// Member functions -// - -// static -bool LLFloaterCamera::inFreeCameraMode() -{ - LLFloaterCamera* floater_camera = LLFloaterCamera::findInstance(); - if (floater_camera && floater_camera->mCurrMode == CAMERA_CTRL_MODE_FREE_CAMERA && gAgentCamera.getCameraMode() != CAMERA_MODE_MOUSELOOK) - { - return true; - } - return false; -} - -// static -void LLFloaterCamera::resetCameraMode() -{ - LLFloaterCamera* floater_camera = LLFloaterCamera::findInstance(); - if (!floater_camera) return; - floater_camera->switchMode(CAMERA_CTRL_MODE_PAN); -} - -// static -void LLFloaterCamera::onAvatarEditingAppearance(bool editing) -{ - sAppearanceEditing = editing; - LLFloaterCamera* floater_camera = LLFloaterCamera::findInstance(); - if (!floater_camera) return; - floater_camera->handleAvatarEditingAppearance(editing); -} - -// static -void LLFloaterCamera::onDebugCameraToggled() -{ - if (LLFloaterCamera* instance = LLFloaterCamera::findInstance()) - { - instance->showDebugInfo(LLView::sDebugCamera); - } - - if (LLView::sDebugCamera) - { - LLFloaterReg::showInstanceOrBringToFront("camera"); - } -} - -void LLFloaterCamera::showDebugInfo(bool show) -{ - // Initially LLPanel contains 1 child "view_border" - if (show && mViewerCameraInfo->getChildCount() < 2) - { - mViewerCameraInfo->addChild(new LLCameraInfoPanel(mViewerCameraInfo, "Viewer Camera", *LLViewerCamera::getInstance(), - []() { return LLViewerCamera::getInstance()->getPointOfInterest(); })); - mAgentCameraInfo->addChild(new LLCameraInfoPanel(mAgentCameraInfo, "Agent Camera", gAgent.getFrameAgent(), - []() { return gAgent.getPosAgentFromGlobal(gAgentCamera.calcFocusPositionTargetGlobal()); })); - } - - mAgentCameraInfo->setVisible(show); - mViewerCameraInfo->setVisible(show); -} - -void LLFloaterCamera::handleAvatarEditingAppearance(bool editing) -{ -} - -void LLFloaterCamera::update() -{ - ECameraControlMode mode = determineMode(); - if (mode != mCurrMode) - { - setMode(mode); - } -} - - -void LLFloaterCamera::toPrevMode() -{ - switchMode(mPrevMode); -} - -// static -void LLFloaterCamera::onLeavingMouseLook() -{ - LLFloaterCamera* floater_camera = LLFloaterCamera::findInstance(); - if (floater_camera) - { - floater_camera->updateItemsSelection(); - if(floater_camera->inFreeCameraMode()) - { - activate_camera_tool(); - } - } -} - -LLFloaterCamera* LLFloaterCamera::findInstance() -{ - return LLFloaterReg::findTypedInstance("camera"); -} - -void LLFloaterCamera::onOpen(const LLSD& key) -{ - LLFirstUse::viewPopup(); - - mZoom->onOpen(key); - - // Returns to previous mode, see EXT-2727(View tool should remember state). - // In case floater was just hidden and it isn't reset the mode - // just update state to current one. Else go to previous. - if ( !mClosed ) - updateState(); - else - toPrevMode(); - mClosed = false; - - populatePresetCombo(); - - showDebugInfo(LLView::sDebugCamera); -} - -void LLFloaterCamera::onClose(bool app_quitting) -{ - //We don't care of camera mode if app is quitting - if (app_quitting) - return; - // It is necessary to reset mCurrMode to CAMERA_CTRL_MODE_PAN so - // to avoid seeing an empty floater when reopening the control. - if (mCurrMode == CAMERA_CTRL_MODE_FREE_CAMERA) - mCurrMode = CAMERA_CTRL_MODE_PAN; - // When mCurrMode is in CAMERA_CTRL_MODE_PAN - // switchMode won't modify mPrevMode, so force it here. - // It is needed to correctly return to previous mode on open, see EXT-2727. - if (mCurrMode == CAMERA_CTRL_MODE_PAN) - mPrevMode = CAMERA_CTRL_MODE_PAN; - - switchMode(CAMERA_CTRL_MODE_PAN); - mClosed = true; - - gAgent.setMovementLocked(false); -} - -LLFloaterCamera::LLFloaterCamera(const LLSD& val) -: LLFloater(val), - mClosed(false), - mCurrMode(CAMERA_CTRL_MODE_PAN), - mPrevMode(CAMERA_CTRL_MODE_PAN) -{ - LLHints::getInstance()->registerHintTarget("view_popup", getHandle()); - mCommitCallbackRegistrar.add("CameraPresets.ChangeView", boost::bind(&LLFloaterCamera::onClickCameraItem, _2)); - mCommitCallbackRegistrar.add("CameraPresets.Save", boost::bind(&LLFloaterCamera::onSavePreset, this)); - mCommitCallbackRegistrar.add("CameraPresets.ShowPresetsList", boost::bind(&LLFloaterReg::showInstance, "camera_presets", LLSD(), false)); -} - -// virtual -bool LLFloaterCamera::postBuild() -{ - updateTransparency(TT_ACTIVE); // force using active floater transparency (STORM-730) - - mControls = getChild("controls"); - mAgentCameraInfo = getChild("agent_camera_info"); - mViewerCameraInfo = getChild("viewer_camera_info"); - mRotate = getChild(ORBIT); - mZoom = getChild(ZOOM); - mTrack = getChild(PAN); - mPresetCombo = getChild("preset_combo"); - mPreciseCtrls = getChild("precise_ctrs_label"); - - mPreciseCtrls->setShowCursorHand(false); - mPreciseCtrls->setSoundFlags(LLView::MOUSE_UP); - mPreciseCtrls->setClickedCallback(boost::bind(&LLFloaterReg::showInstance, "prefs_view_advanced", LLSD(), false)); - - mPresetCombo->setCommitCallback(boost::bind(&LLFloaterCamera::onCustomPresetSelected, this)); - LLPresetsManager::getInstance()->setPresetListChangeCameraCallback(boost::bind(&LLFloaterCamera::populatePresetCombo, this)); - - update(); - - // ensure that appearance mode is handled while building. See EXT-7796. - handleAvatarEditingAppearance(sAppearanceEditing); - - return LLFloater::postBuild(); -} - -F32 LLFloaterCamera::getCurrentTransparency() -{ - - static LLCachedControl camera_opacity(gSavedSettings, "CameraOpacity"); - static LLCachedControl active_floater_transparency(gSavedSettings, "ActiveFloaterTransparency"); - return llmin(camera_opacity(), active_floater_transparency()); - -} - -void LLFloaterCamera::fillFlatlistFromPanel (LLFlatListView* list, LLPanel* panel) -{ - // copying child list and then iterating over a copy, because list itself - // is changed in process - const child_list_t child_list = *panel->getChildList(); - child_list_t::const_reverse_iterator iter = child_list.rbegin(); - child_list_t::const_reverse_iterator end = child_list.rend(); - for ( ; iter != end; ++iter) - { - LLView* view = *iter; - LLPanel* item = dynamic_cast(view); - if (panel) - list->addItem(item); - } - -} - -ECameraControlMode LLFloaterCamera::determineMode() -{ - if (sAppearanceEditing) - { - // this is the only enabled camera mode while editing agent appearance. - return CAMERA_CTRL_MODE_PAN; - } - - LLTool* curr_tool = LLToolMgr::getInstance()->getCurrentTool(); - if (curr_tool == LLToolCamera::getInstance()) - { - return CAMERA_CTRL_MODE_FREE_CAMERA; - } - - if (gAgentCamera.getCameraMode() == CAMERA_MODE_MOUSELOOK) - { - return CAMERA_CTRL_MODE_PRESETS; - } - - return CAMERA_CTRL_MODE_PAN; -} - - -void clear_camera_tool() -{ - LLToolMgr* tool_mgr = LLToolMgr::getInstance(); - if (tool_mgr->usingTransientTool() && - tool_mgr->getCurrentTool() == LLToolCamera::getInstance()) - { - tool_mgr->clearTransientTool(); - } -} - - -void LLFloaterCamera::setMode(ECameraControlMode mode) -{ - if (mode != mCurrMode) - { - mPrevMode = mCurrMode; - mCurrMode = mode; - } - - updateState(); -} - -void LLFloaterCamera::switchMode(ECameraControlMode mode) -{ - switch (mode) - { - case CAMERA_CTRL_MODE_PRESETS: - case CAMERA_CTRL_MODE_PAN: - sFreeCamera = false; - setMode(mode); // depends onto sFreeCamera - clear_camera_tool(); - break; - - case CAMERA_CTRL_MODE_FREE_CAMERA: - sFreeCamera = true; - setMode(mode); - activate_camera_tool(); - break; - - default: - //normally we won't occur here - llassert_always(false); - } -} - -void LLFloaterCamera::updateState() -{ - updateItemsSelection(); - - if (CAMERA_CTRL_MODE_FREE_CAMERA == mCurrMode) - { - return; - } - - //updating buttons - std::map::const_iterator iter = mMode2Button.begin(); - for (; iter != mMode2Button.end(); ++iter) - { - iter->second->setToggleState(iter->first == mCurrMode); - } -} - -void LLFloaterCamera::updateItemsSelection() -{ - ECameraPreset preset = (ECameraPreset) gSavedSettings.getU32("CameraPresetType"); - LLSD argument; - argument["selected"] = (preset == CAMERA_PRESET_REAR_VIEW) && !sFreeCamera; - getChild("rear_view")->setValue(argument); - argument["selected"] = (preset == CAMERA_PRESET_GROUP_VIEW) && !sFreeCamera; - getChild("group_view")->setValue(argument); - argument["selected"] = (preset == CAMERA_PRESET_FRONT_VIEW) && !sFreeCamera; - getChild("front_view")->setValue(argument); - argument["selected"] = gAgentCamera.getCameraMode() == CAMERA_MODE_MOUSELOOK; - getChild("mouselook_view")->setValue(argument); - argument["selected"] = mCurrMode == CAMERA_CTRL_MODE_FREE_CAMERA; - getChild("object_view")->setValue(argument); -} - -// static -void LLFloaterCamera::onClickCameraItem(const LLSD& param) -{ - std::string name = param.asString(); - - if ("mouselook_view" == name) - { - gAgentCamera.changeCameraToMouselook(); - } - else if ("object_view" == name) - { - LLFloaterCamera* camera_floater = LLFloaterCamera::findInstance(); - if (camera_floater) - { - camera_floater->switchMode(CAMERA_CTRL_MODE_FREE_CAMERA); - camera_floater->updateItemsSelection(); - } - } - else - { - LLFloaterCamera* camera_floater = LLFloaterCamera::findInstance(); - if (camera_floater) - camera_floater->switchMode(CAMERA_CTRL_MODE_PAN); - switchToPreset(name); - } -} - -// static -void LLFloaterCamera::switchToPreset(const std::string& name) -{ - sFreeCamera = false; - clear_camera_tool(); - if (PRESETS_REAR_VIEW == name) - { - gAgentCamera.switchCameraPreset(CAMERA_PRESET_REAR_VIEW); - } - else if (PRESETS_SIDE_VIEW == name) - { - gAgentCamera.switchCameraPreset(CAMERA_PRESET_GROUP_VIEW); - } - else if (PRESETS_FRONT_VIEW == name) - { - gAgentCamera.switchCameraPreset(CAMERA_PRESET_FRONT_VIEW); - } - else - { - gAgentCamera.switchCameraPreset(CAMERA_PRESET_CUSTOM); - } - - if (gSavedSettings.getString("PresetCameraActive") != name) - { - LLPresetsManager::getInstance()->loadPreset(PRESETS_CAMERA, name); - } - - if (isAgentAvatarValid() && gAgentAvatarp->getParent()) - { - LLQuaternion sit_rot(gSavedSettings.getLLSD("AvatarSitRotation")); - if (sit_rot != LLQuaternion()) - { - gAgent.rotate(~gAgent.getFrameAgent().getQuaternion()); - gAgent.rotate(sit_rot); - } - else - { - gAgentCamera.rotateToInitSitRot(); - } - } - gAgentCamera.resetCameraZoomFraction(); - - LLFloaterCamera* camera_floater = LLFloaterCamera::findInstance(); - if (camera_floater) - { - camera_floater->updateItemsSelection(); - camera_floater->switchMode(CAMERA_CTRL_MODE_PRESETS); - } -} - -void LLFloaterCamera::populatePresetCombo() -{ - LLPresetsManager::getInstance()->setPresetNamesInComboBox(PRESETS_CAMERA, mPresetCombo, EDefaultOptions::DEFAULT_HIDE); - std::string active_preset_name = gSavedSettings.getString("PresetCameraActive"); - if (active_preset_name.empty()) - { - gSavedSettings.setU32("CameraPresetType", CAMERA_PRESET_CUSTOM); - updateItemsSelection(); - mPresetCombo->setLabel(getString("inactive_combo_text")); - } - else if ((ECameraPreset)gSavedSettings.getU32("CameraPresetType") == CAMERA_PRESET_CUSTOM) - { - mPresetCombo->selectByValue(active_preset_name); - } - else - { - mPresetCombo->setLabel(getString("inactive_combo_text")); - } - updateItemsSelection(); -} - -void LLFloaterCamera::onSavePreset() -{ - LLFloaterReg::hideInstance("delete_pref_preset", PRESETS_CAMERA); - LLFloaterReg::hideInstance("load_pref_preset", PRESETS_CAMERA); - - LLFloaterReg::showInstance("save_camera_preset"); -} - -void LLFloaterCamera::onCustomPresetSelected() -{ - std::string selected_preset = mPresetCombo->getSelectedItemLabel(); - if (getString("inactive_combo_text") != selected_preset) - { - switchToPreset(selected_preset); - } -} +/** + * @file llfloatercamera.cpp + * @brief Container for camera control buttons (zoom, pan, orbit) + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatercamera.h" + +// Library includes +#include "llfloaterreg.h" + +// Viewer includes +#include "llagent.h" +#include "llagentcamera.h" +#include "llpresetsmanager.h" +#include "lljoystickbutton.h" +#include "llviewercontrol.h" +#include "llviewercamera.h" +#include "lltoolmgr.h" +#include "lltoolfocus.h" +#include "llslider.h" +#include "llfirstuse.h" +#include "llhints.h" +#include "lltabcontainer.h" +#include "llviewercamera.h" +#include "llvoavatarself.h" + +static LLDefaultChildRegistry::Register r("panel_camera_item"); + +const F32 NUDGE_TIME = 0.25f; // in seconds +const F32 ORBIT_NUDGE_RATE = 0.05f; // fraction of normal speed + +// constants +#define ORBIT "cam_rotate_stick" +#define PAN "cam_track_stick" +#define ZOOM "zoom" +#define CONTROLS "controls" + +bool LLFloaterCamera::sFreeCamera = false; +bool LLFloaterCamera::sAppearanceEditing = false; + +// Zoom the camera in and out +class LLPanelCameraZoom +: public LLPanel +{ + LOG_CLASS(LLPanelCameraZoom); + +public: + struct Params : public LLInitParam::Block {}; + + LLPanelCameraZoom() { onCreate(); } + + /* virtual */ bool postBuild(); + /* virtual */ void draw(); + +protected: + LLPanelCameraZoom(const Params& p) { onCreate(); } + + void onCreate(); + void onZoomPlusHeldDown(); + void onZoomMinusHeldDown(); + void onSliderValueChanged(); + void onCameraTrack(); + void onCameraRotate(); + F32 getOrbitRate(F32 time); + +private: + LLButton* mPlusBtn { nullptr }; + LLButton* mMinusBtn{ nullptr }; + LLSlider* mSlider{ nullptr }; + + friend class LLUICtrlFactory; +}; + +LLPanelCameraItem::Params::Params() +: icon_over("icon_over"), + icon_selected("icon_selected"), + picture("picture"), + text("text"), + selected_picture("selected_picture"), + mousedown_callback("mousedown_callback") +{ +} + +LLPanelCameraItem::LLPanelCameraItem(const LLPanelCameraItem::Params& p) +: LLPanel(p) +{ + LLIconCtrl::Params icon_params = p.picture; + mPicture = LLUICtrlFactory::create(icon_params); + addChild(mPicture); + + icon_params = p.icon_over; + mIconOver = LLUICtrlFactory::create(icon_params); + addChild(mIconOver); + + icon_params = p.icon_selected; + mIconSelected = LLUICtrlFactory::create(icon_params); + addChild(mIconSelected); + + icon_params = p.selected_picture; + mPictureSelected = LLUICtrlFactory::create(icon_params); + addChild(mPictureSelected); + + LLTextBox::Params text_params = p.text; + mText = LLUICtrlFactory::create(text_params); + addChild(mText); + + if (p.mousedown_callback.isProvided()) + { + setCommitCallback(initCommitCallback(p.mousedown_callback)); + } +} + +void set_view_visible(LLView* parent, const std::string& name, bool visible) +{ + parent->getChildView(name)->setVisible(visible); +} + +bool LLPanelCameraItem::postBuild() +{ + setMouseEnterCallback(boost::bind(set_view_visible, this, "hovered_icon", true)); + setMouseLeaveCallback(boost::bind(set_view_visible, this, "hovered_icon", false)); + setMouseDownCallback(boost::bind(&LLPanelCameraItem::onAnyMouseClick, this)); + setRightMouseDownCallback(boost::bind(&LLPanelCameraItem::onAnyMouseClick, this)); + return true; +} + +void LLPanelCameraItem::onAnyMouseClick() +{ + if (mCommitSignal) (*mCommitSignal)(this, LLSD()); +} + +void LLPanelCameraItem::setValue(const LLSD& value) +{ + if (!value.isMap()) return;; + if (!value.has("selected")) return; + getChildView("selected_icon")->setVisible( value["selected"]); + getChildView("picture")->setVisible( !value["selected"]); + getChildView("selected_picture")->setVisible( value["selected"]); +} + +static LLPanelInjector t_camera_zoom_panel("camera_zoom_panel"); + +//------------------------------------------------------------------------------- +// LLPanelCameraZoom +//------------------------------------------------------------------------------- + +void LLPanelCameraZoom::onCreate() +{ + mCommitCallbackRegistrar.add("Zoom.minus", boost::bind(&LLPanelCameraZoom::onZoomMinusHeldDown, this)); + mCommitCallbackRegistrar.add("Zoom.plus", boost::bind(&LLPanelCameraZoom::onZoomPlusHeldDown, this)); + mCommitCallbackRegistrar.add("Slider.value_changed", boost::bind(&LLPanelCameraZoom::onSliderValueChanged, this)); + mCommitCallbackRegistrar.add("Camera.track", boost::bind(&LLPanelCameraZoom::onCameraTrack, this)); + mCommitCallbackRegistrar.add("Camera.rotate", boost::bind(&LLPanelCameraZoom::onCameraRotate, this)); +} + +bool LLPanelCameraZoom::postBuild() +{ + mPlusBtn = getChild("zoom_plus_btn"); + mMinusBtn = getChild("zoom_minus_btn"); + mSlider = getChild("zoom_slider"); + return LLPanel::postBuild(); +} + +void LLPanelCameraZoom::draw() +{ + mSlider->setValue(gAgentCamera.getCameraZoomFraction()); + LLPanel::draw(); +} + +void LLPanelCameraZoom::onZoomPlusHeldDown() +{ + F32 val = mSlider->getValueF32(); + F32 inc = mSlider->getIncrement(); + mSlider->setValue(val - inc); + F32 time = mPlusBtn->getHeldDownTime(); + gAgentCamera.unlockView(); + gAgentCamera.setOrbitInKey(getOrbitRate(time)); +} + +void LLPanelCameraZoom::onZoomMinusHeldDown() +{ + F32 val = mSlider->getValueF32(); + F32 inc = mSlider->getIncrement(); + mSlider->setValue(val + inc); + F32 time = mMinusBtn->getHeldDownTime(); + gAgentCamera.unlockView(); + gAgentCamera.setOrbitOutKey(getOrbitRate(time)); +} + +void LLPanelCameraZoom::onCameraTrack() +{ + // EXP-202 when camera panning activated, remove the hint + LLFirstUse::viewPopup( false ); +} + +void LLPanelCameraZoom::onCameraRotate() +{ + // EXP-202 when camera rotation activated, remove the hint + LLFirstUse::viewPopup( false ); +} + +F32 LLPanelCameraZoom::getOrbitRate(F32 time) +{ + if( time < NUDGE_TIME ) + { + F32 rate = ORBIT_NUDGE_RATE + time * (1 - ORBIT_NUDGE_RATE)/ NUDGE_TIME; + return rate; + } + else + { + return 1; + } +} + +void LLPanelCameraZoom::onSliderValueChanged() +{ + F32 zoom_level = mSlider->getValueF32(); + gAgentCamera.setCameraZoomFraction(zoom_level); +} + +void activate_camera_tool() +{ + LLToolMgr::getInstance()->setTransientTool(LLToolCamera::getInstance()); +}; + +class LLCameraInfoPanel : public LLPanel +{ +public: + typedef std::function get_vector_t; + + LLCameraInfoPanel( + const LLView* parent, + const char* title, + const LLCoordFrame& camera, + const get_vector_t get_focus + ) + : LLPanel([&]() -> LLPanel::Params + { + LLPanel::Params params; + params.rect = LLRect(parent->getLocalRect()); + return params; + }()) + , mTitle(title) + , mCamera(camera) + , mGetFocus(get_focus) + , mFont(LLFontGL::getFontSansSerifBig()) + { + } + + virtual void draw() override + { + LLPanel::draw(); + + static const U32 HPADDING = 10; + static const U32 VPADDING = 5; + LLVector3 focus = mGetFocus(); + LLVector3 sight = focus - mCamera.mOrigin; + std::pair const data[] = + { + { "Origin:", mCamera.mOrigin }, + { "X Axis:", mCamera.mXAxis }, + { "Y Axis:", mCamera.mYAxis }, + { "Z Axis:", mCamera.mZAxis }, + { "Focus:", focus }, + { "Sight:", sight } + }; + S32 width = getRect().getWidth(); + S32 height = getRect().getHeight(); + S32 row_count = 1 + sizeof(data) / sizeof(*data); + S32 row_height = (height - VPADDING * 2) / row_count; + S32 top = height - VPADDING - row_height / 2; + mFont->renderUTF8(mTitle, 0, HPADDING, top, LLColor4::white, LLFontGL::LEFT, LLFontGL::VCENTER); + for (const auto& row : data) + { + top -= row_height; + mFont->renderUTF8(row.first, 0, HPADDING, top, LLColor4::white, LLFontGL::LEFT, LLFontGL::VCENTER); + const LLVector3& vector = row.second; + for (S32 i = 0; i < 3; ++i) + { + std::string text = llformat("%.6f", vector[i]); + S32 right = width / 4 * (i + 2) - HPADDING; + mFont->renderUTF8(text, 0, right, top, LLColor4::white, LLFontGL::RIGHT, LLFontGL::VCENTER); + } + } + } + +private: + const char* mTitle; + const LLCoordFrame& mCamera; + const get_vector_t mGetFocus; + const LLFontGL* mFont; +}; + +// +// Member functions +// + +// static +bool LLFloaterCamera::inFreeCameraMode() +{ + LLFloaterCamera* floater_camera = LLFloaterCamera::findInstance(); + if (floater_camera && floater_camera->mCurrMode == CAMERA_CTRL_MODE_FREE_CAMERA && gAgentCamera.getCameraMode() != CAMERA_MODE_MOUSELOOK) + { + return true; + } + return false; +} + +// static +void LLFloaterCamera::resetCameraMode() +{ + LLFloaterCamera* floater_camera = LLFloaterCamera::findInstance(); + if (!floater_camera) return; + floater_camera->switchMode(CAMERA_CTRL_MODE_PAN); +} + +// static +void LLFloaterCamera::onAvatarEditingAppearance(bool editing) +{ + sAppearanceEditing = editing; + LLFloaterCamera* floater_camera = LLFloaterCamera::findInstance(); + if (!floater_camera) return; + floater_camera->handleAvatarEditingAppearance(editing); +} + +// static +void LLFloaterCamera::onDebugCameraToggled() +{ + if (LLFloaterCamera* instance = LLFloaterCamera::findInstance()) + { + instance->showDebugInfo(LLView::sDebugCamera); + } + + if (LLView::sDebugCamera) + { + LLFloaterReg::showInstanceOrBringToFront("camera"); + } +} + +void LLFloaterCamera::showDebugInfo(bool show) +{ + // Initially LLPanel contains 1 child "view_border" + if (show && mViewerCameraInfo->getChildCount() < 2) + { + mViewerCameraInfo->addChild(new LLCameraInfoPanel(mViewerCameraInfo, "Viewer Camera", *LLViewerCamera::getInstance(), + []() { return LLViewerCamera::getInstance()->getPointOfInterest(); })); + mAgentCameraInfo->addChild(new LLCameraInfoPanel(mAgentCameraInfo, "Agent Camera", gAgent.getFrameAgent(), + []() { return gAgent.getPosAgentFromGlobal(gAgentCamera.calcFocusPositionTargetGlobal()); })); + } + + mAgentCameraInfo->setVisible(show); + mViewerCameraInfo->setVisible(show); +} + +void LLFloaterCamera::handleAvatarEditingAppearance(bool editing) +{ +} + +void LLFloaterCamera::update() +{ + ECameraControlMode mode = determineMode(); + if (mode != mCurrMode) + { + setMode(mode); + } +} + + +void LLFloaterCamera::toPrevMode() +{ + switchMode(mPrevMode); +} + +// static +void LLFloaterCamera::onLeavingMouseLook() +{ + LLFloaterCamera* floater_camera = LLFloaterCamera::findInstance(); + if (floater_camera) + { + floater_camera->updateItemsSelection(); + if(floater_camera->inFreeCameraMode()) + { + activate_camera_tool(); + } + } +} + +LLFloaterCamera* LLFloaterCamera::findInstance() +{ + return LLFloaterReg::findTypedInstance("camera"); +} + +void LLFloaterCamera::onOpen(const LLSD& key) +{ + LLFirstUse::viewPopup(); + + mZoom->onOpen(key); + + // Returns to previous mode, see EXT-2727(View tool should remember state). + // In case floater was just hidden and it isn't reset the mode + // just update state to current one. Else go to previous. + if ( !mClosed ) + updateState(); + else + toPrevMode(); + mClosed = false; + + populatePresetCombo(); + + showDebugInfo(LLView::sDebugCamera); +} + +void LLFloaterCamera::onClose(bool app_quitting) +{ + //We don't care of camera mode if app is quitting + if (app_quitting) + return; + // It is necessary to reset mCurrMode to CAMERA_CTRL_MODE_PAN so + // to avoid seeing an empty floater when reopening the control. + if (mCurrMode == CAMERA_CTRL_MODE_FREE_CAMERA) + mCurrMode = CAMERA_CTRL_MODE_PAN; + // When mCurrMode is in CAMERA_CTRL_MODE_PAN + // switchMode won't modify mPrevMode, so force it here. + // It is needed to correctly return to previous mode on open, see EXT-2727. + if (mCurrMode == CAMERA_CTRL_MODE_PAN) + mPrevMode = CAMERA_CTRL_MODE_PAN; + + switchMode(CAMERA_CTRL_MODE_PAN); + mClosed = true; + + gAgent.setMovementLocked(false); +} + +LLFloaterCamera::LLFloaterCamera(const LLSD& val) +: LLFloater(val), + mClosed(false), + mCurrMode(CAMERA_CTRL_MODE_PAN), + mPrevMode(CAMERA_CTRL_MODE_PAN) +{ + LLHints::getInstance()->registerHintTarget("view_popup", getHandle()); + mCommitCallbackRegistrar.add("CameraPresets.ChangeView", boost::bind(&LLFloaterCamera::onClickCameraItem, _2)); + mCommitCallbackRegistrar.add("CameraPresets.Save", boost::bind(&LLFloaterCamera::onSavePreset, this)); + mCommitCallbackRegistrar.add("CameraPresets.ShowPresetsList", boost::bind(&LLFloaterReg::showInstance, "camera_presets", LLSD(), false)); +} + +// virtual +bool LLFloaterCamera::postBuild() +{ + updateTransparency(TT_ACTIVE); // force using active floater transparency (STORM-730) + + mControls = getChild("controls"); + mAgentCameraInfo = getChild("agent_camera_info"); + mViewerCameraInfo = getChild("viewer_camera_info"); + mRotate = getChild(ORBIT); + mZoom = getChild(ZOOM); + mTrack = getChild(PAN); + mPresetCombo = getChild("preset_combo"); + mPreciseCtrls = getChild("precise_ctrs_label"); + + mPreciseCtrls->setShowCursorHand(false); + mPreciseCtrls->setSoundFlags(LLView::MOUSE_UP); + mPreciseCtrls->setClickedCallback(boost::bind(&LLFloaterReg::showInstance, "prefs_view_advanced", LLSD(), false)); + + mPresetCombo->setCommitCallback(boost::bind(&LLFloaterCamera::onCustomPresetSelected, this)); + LLPresetsManager::getInstance()->setPresetListChangeCameraCallback(boost::bind(&LLFloaterCamera::populatePresetCombo, this)); + + update(); + + // ensure that appearance mode is handled while building. See EXT-7796. + handleAvatarEditingAppearance(sAppearanceEditing); + + return LLFloater::postBuild(); +} + +F32 LLFloaterCamera::getCurrentTransparency() +{ + + static LLCachedControl camera_opacity(gSavedSettings, "CameraOpacity"); + static LLCachedControl active_floater_transparency(gSavedSettings, "ActiveFloaterTransparency"); + return llmin(camera_opacity(), active_floater_transparency()); + +} + +void LLFloaterCamera::fillFlatlistFromPanel (LLFlatListView* list, LLPanel* panel) +{ + // copying child list and then iterating over a copy, because list itself + // is changed in process + const child_list_t child_list = *panel->getChildList(); + child_list_t::const_reverse_iterator iter = child_list.rbegin(); + child_list_t::const_reverse_iterator end = child_list.rend(); + for ( ; iter != end; ++iter) + { + LLView* view = *iter; + LLPanel* item = dynamic_cast(view); + if (panel) + list->addItem(item); + } + +} + +ECameraControlMode LLFloaterCamera::determineMode() +{ + if (sAppearanceEditing) + { + // this is the only enabled camera mode while editing agent appearance. + return CAMERA_CTRL_MODE_PAN; + } + + LLTool* curr_tool = LLToolMgr::getInstance()->getCurrentTool(); + if (curr_tool == LLToolCamera::getInstance()) + { + return CAMERA_CTRL_MODE_FREE_CAMERA; + } + + if (gAgentCamera.getCameraMode() == CAMERA_MODE_MOUSELOOK) + { + return CAMERA_CTRL_MODE_PRESETS; + } + + return CAMERA_CTRL_MODE_PAN; +} + + +void clear_camera_tool() +{ + LLToolMgr* tool_mgr = LLToolMgr::getInstance(); + if (tool_mgr->usingTransientTool() && + tool_mgr->getCurrentTool() == LLToolCamera::getInstance()) + { + tool_mgr->clearTransientTool(); + } +} + + +void LLFloaterCamera::setMode(ECameraControlMode mode) +{ + if (mode != mCurrMode) + { + mPrevMode = mCurrMode; + mCurrMode = mode; + } + + updateState(); +} + +void LLFloaterCamera::switchMode(ECameraControlMode mode) +{ + switch (mode) + { + case CAMERA_CTRL_MODE_PRESETS: + case CAMERA_CTRL_MODE_PAN: + sFreeCamera = false; + setMode(mode); // depends onto sFreeCamera + clear_camera_tool(); + break; + + case CAMERA_CTRL_MODE_FREE_CAMERA: + sFreeCamera = true; + setMode(mode); + activate_camera_tool(); + break; + + default: + //normally we won't occur here + llassert_always(false); + } +} + +void LLFloaterCamera::updateState() +{ + updateItemsSelection(); + + if (CAMERA_CTRL_MODE_FREE_CAMERA == mCurrMode) + { + return; + } + + //updating buttons + std::map::const_iterator iter = mMode2Button.begin(); + for (; iter != mMode2Button.end(); ++iter) + { + iter->second->setToggleState(iter->first == mCurrMode); + } +} + +void LLFloaterCamera::updateItemsSelection() +{ + ECameraPreset preset = (ECameraPreset) gSavedSettings.getU32("CameraPresetType"); + LLSD argument; + argument["selected"] = (preset == CAMERA_PRESET_REAR_VIEW) && !sFreeCamera; + getChild("rear_view")->setValue(argument); + argument["selected"] = (preset == CAMERA_PRESET_GROUP_VIEW) && !sFreeCamera; + getChild("group_view")->setValue(argument); + argument["selected"] = (preset == CAMERA_PRESET_FRONT_VIEW) && !sFreeCamera; + getChild("front_view")->setValue(argument); + argument["selected"] = gAgentCamera.getCameraMode() == CAMERA_MODE_MOUSELOOK; + getChild("mouselook_view")->setValue(argument); + argument["selected"] = mCurrMode == CAMERA_CTRL_MODE_FREE_CAMERA; + getChild("object_view")->setValue(argument); +} + +// static +void LLFloaterCamera::onClickCameraItem(const LLSD& param) +{ + std::string name = param.asString(); + + if ("mouselook_view" == name) + { + gAgentCamera.changeCameraToMouselook(); + } + else if ("object_view" == name) + { + LLFloaterCamera* camera_floater = LLFloaterCamera::findInstance(); + if (camera_floater) + { + camera_floater->switchMode(CAMERA_CTRL_MODE_FREE_CAMERA); + camera_floater->updateItemsSelection(); + } + } + else + { + LLFloaterCamera* camera_floater = LLFloaterCamera::findInstance(); + if (camera_floater) + camera_floater->switchMode(CAMERA_CTRL_MODE_PAN); + switchToPreset(name); + } +} + +// static +void LLFloaterCamera::switchToPreset(const std::string& name) +{ + sFreeCamera = false; + clear_camera_tool(); + if (PRESETS_REAR_VIEW == name) + { + gAgentCamera.switchCameraPreset(CAMERA_PRESET_REAR_VIEW); + } + else if (PRESETS_SIDE_VIEW == name) + { + gAgentCamera.switchCameraPreset(CAMERA_PRESET_GROUP_VIEW); + } + else if (PRESETS_FRONT_VIEW == name) + { + gAgentCamera.switchCameraPreset(CAMERA_PRESET_FRONT_VIEW); + } + else + { + gAgentCamera.switchCameraPreset(CAMERA_PRESET_CUSTOM); + } + + if (gSavedSettings.getString("PresetCameraActive") != name) + { + LLPresetsManager::getInstance()->loadPreset(PRESETS_CAMERA, name); + } + + if (isAgentAvatarValid() && gAgentAvatarp->getParent()) + { + LLQuaternion sit_rot(gSavedSettings.getLLSD("AvatarSitRotation")); + if (sit_rot != LLQuaternion()) + { + gAgent.rotate(~gAgent.getFrameAgent().getQuaternion()); + gAgent.rotate(sit_rot); + } + else + { + gAgentCamera.rotateToInitSitRot(); + } + } + gAgentCamera.resetCameraZoomFraction(); + + LLFloaterCamera* camera_floater = LLFloaterCamera::findInstance(); + if (camera_floater) + { + camera_floater->updateItemsSelection(); + camera_floater->switchMode(CAMERA_CTRL_MODE_PRESETS); + } +} + +void LLFloaterCamera::populatePresetCombo() +{ + LLPresetsManager::getInstance()->setPresetNamesInComboBox(PRESETS_CAMERA, mPresetCombo, EDefaultOptions::DEFAULT_HIDE); + std::string active_preset_name = gSavedSettings.getString("PresetCameraActive"); + if (active_preset_name.empty()) + { + gSavedSettings.setU32("CameraPresetType", CAMERA_PRESET_CUSTOM); + updateItemsSelection(); + mPresetCombo->setLabel(getString("inactive_combo_text")); + } + else if ((ECameraPreset)gSavedSettings.getU32("CameraPresetType") == CAMERA_PRESET_CUSTOM) + { + mPresetCombo->selectByValue(active_preset_name); + } + else + { + mPresetCombo->setLabel(getString("inactive_combo_text")); + } + updateItemsSelection(); +} + +void LLFloaterCamera::onSavePreset() +{ + LLFloaterReg::hideInstance("delete_pref_preset", PRESETS_CAMERA); + LLFloaterReg::hideInstance("load_pref_preset", PRESETS_CAMERA); + + LLFloaterReg::showInstance("save_camera_preset"); +} + +void LLFloaterCamera::onCustomPresetSelected() +{ + std::string selected_preset = mPresetCombo->getSelectedItemLabel(); + if (getString("inactive_combo_text") != selected_preset) + { + switchToPreset(selected_preset); + } +} diff --git a/indra/newview/llfloatercamera.h b/indra/newview/llfloatercamera.h index ba5e54f84e..ece3d8218e 100644 --- a/indra/newview/llfloatercamera.h +++ b/indra/newview/llfloatercamera.h @@ -1,178 +1,178 @@ -/** - * @file llfloatercamera.h - * @brief Container for camera control buttons (zoom, pan, orbit) - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLFLOATERCAMERA_H -#define LLFLOATERCAMERA_H - -#include "llfloater.h" -#include "lliconctrl.h" -#include "lltextbox.h" -#include "llflatlistview.h" - -class LLJoystickCameraRotate; -class LLJoystickCameraTrack; -class LLFloaterReg; -class LLPanelCameraZoom; -class LLComboBox; - -enum ECameraControlMode -{ - CAMERA_CTRL_MODE_PAN, - CAMERA_CTRL_MODE_FREE_CAMERA, - CAMERA_CTRL_MODE_PRESETS -}; - -class LLFloaterCamera : public LLFloater -{ - friend class LLFloaterReg; - -public: - /* whether in free camera mode */ - static bool inFreeCameraMode(); - /* callback for camera items selection changing */ - static void onClickCameraItem(const LLSD& param); - - static void onLeavingMouseLook(); - - /** resets current camera mode to orbit mode */ - static void resetCameraMode(); - - /** Called when Avatar is entered/exited editing appearance mode */ - static void onAvatarEditingAppearance(bool editing); - - /** Called when opening and when "Advanced | Debug Camera" menu item is toggled */ - static void onDebugCameraToggled(); - - /* determines actual mode and updates ui */ - void update(); - - /*switch to one of the camera presets (front, rear, side)*/ - static void switchToPreset(const std::string& name); - - virtual void onOpen(const LLSD& key); - virtual void onClose(bool app_quitting); - - void onSavePreset(); - void onCustomPresetSelected(); - - void populatePresetCombo(); - - LLJoystickCameraRotate* mRotate { nullptr }; - LLPanelCameraZoom* mZoom { nullptr }; - LLJoystickCameraTrack* mTrack { nullptr }; - -private: - - LLFloaterCamera(const LLSD& val); - ~LLFloaterCamera() {}; - - /* return instance if it exists - created by LLFloaterReg */ - static LLFloaterCamera* findInstance(); - - /*virtual*/ bool postBuild(); - - F32 getCurrentTransparency(); - - void onViewButtonClick(const LLSD& user_data); - - ECameraControlMode determineMode(); - - /* resets to the previous mode */ - void toPrevMode(); - - /* sets a new mode and performs related actions */ - void switchMode(ECameraControlMode mode); - - /* sets a new mode preserving previous one and updates ui*/ - void setMode(ECameraControlMode mode); - - /* updates the state (UI) according to the current mode */ - void updateState(); - - /* update camera modes items selection and camera preset items selection according to the currently selected preset */ - void updateItemsSelection(); - - // fills flatlist with items from given panel - void fillFlatlistFromPanel (LLFlatListView* list, LLPanel* panel); - - void handleAvatarEditingAppearance(bool editing); - - void showDebugInfo(bool show); - - // set to true when free camera mode is selected in modes list - // remains true until preset camera mode is chosen, or pan button is clicked, or escape pressed - static bool sFreeCamera; - static bool sAppearanceEditing; - bool mClosed; - ECameraControlMode mPrevMode; - ECameraControlMode mCurrMode; - std::map mMode2Button; - - LLPanel* mControls { nullptr }; - LLPanel* mViewerCameraInfo { nullptr }; - LLPanel* mAgentCameraInfo { nullptr }; - LLComboBox* mPresetCombo { nullptr }; - LLTextBox* mPreciseCtrls { nullptr }; -}; - -/** - * Class used to represent widgets from panel_camera_item.xml- - * panels that contain pictures and text. Pictures are different - * for selected and unselected state (this state is nor stored- icons - * are changed in setValue()). This class doesn't implement selection logic- - * it's items are used inside of flatlist. - */ -class LLPanelCameraItem - : public LLPanel -{ -public: - struct Params : public LLInitParam::Block - { - Optional icon_over; - Optional icon_selected; - Optional picture; - Optional selected_picture; - - Optional text; - Optional mousedown_callback; - Params(); - }; - /*virtual*/ bool postBuild(); - /** setting on/off background icon to indicate selected state */ - /*virtual*/ void setValue(const LLSD& value); - // sends commit signal - void onAnyMouseClick(); -protected: - friend class LLUICtrlFactory; - LLPanelCameraItem(const Params&); - LLIconCtrl* mIconOver; - LLIconCtrl* mIconSelected; - LLIconCtrl* mPicture; - LLIconCtrl* mPictureSelected; - LLTextBox* mText; -}; - -#endif +/** + * @file llfloatercamera.h + * @brief Container for camera control buttons (zoom, pan, orbit) + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLFLOATERCAMERA_H +#define LLFLOATERCAMERA_H + +#include "llfloater.h" +#include "lliconctrl.h" +#include "lltextbox.h" +#include "llflatlistview.h" + +class LLJoystickCameraRotate; +class LLJoystickCameraTrack; +class LLFloaterReg; +class LLPanelCameraZoom; +class LLComboBox; + +enum ECameraControlMode +{ + CAMERA_CTRL_MODE_PAN, + CAMERA_CTRL_MODE_FREE_CAMERA, + CAMERA_CTRL_MODE_PRESETS +}; + +class LLFloaterCamera : public LLFloater +{ + friend class LLFloaterReg; + +public: + /* whether in free camera mode */ + static bool inFreeCameraMode(); + /* callback for camera items selection changing */ + static void onClickCameraItem(const LLSD& param); + + static void onLeavingMouseLook(); + + /** resets current camera mode to orbit mode */ + static void resetCameraMode(); + + /** Called when Avatar is entered/exited editing appearance mode */ + static void onAvatarEditingAppearance(bool editing); + + /** Called when opening and when "Advanced | Debug Camera" menu item is toggled */ + static void onDebugCameraToggled(); + + /* determines actual mode and updates ui */ + void update(); + + /*switch to one of the camera presets (front, rear, side)*/ + static void switchToPreset(const std::string& name); + + virtual void onOpen(const LLSD& key); + virtual void onClose(bool app_quitting); + + void onSavePreset(); + void onCustomPresetSelected(); + + void populatePresetCombo(); + + LLJoystickCameraRotate* mRotate { nullptr }; + LLPanelCameraZoom* mZoom { nullptr }; + LLJoystickCameraTrack* mTrack { nullptr }; + +private: + + LLFloaterCamera(const LLSD& val); + ~LLFloaterCamera() {}; + + /* return instance if it exists - created by LLFloaterReg */ + static LLFloaterCamera* findInstance(); + + /*virtual*/ bool postBuild(); + + F32 getCurrentTransparency(); + + void onViewButtonClick(const LLSD& user_data); + + ECameraControlMode determineMode(); + + /* resets to the previous mode */ + void toPrevMode(); + + /* sets a new mode and performs related actions */ + void switchMode(ECameraControlMode mode); + + /* sets a new mode preserving previous one and updates ui*/ + void setMode(ECameraControlMode mode); + + /* updates the state (UI) according to the current mode */ + void updateState(); + + /* update camera modes items selection and camera preset items selection according to the currently selected preset */ + void updateItemsSelection(); + + // fills flatlist with items from given panel + void fillFlatlistFromPanel (LLFlatListView* list, LLPanel* panel); + + void handleAvatarEditingAppearance(bool editing); + + void showDebugInfo(bool show); + + // set to true when free camera mode is selected in modes list + // remains true until preset camera mode is chosen, or pan button is clicked, or escape pressed + static bool sFreeCamera; + static bool sAppearanceEditing; + bool mClosed; + ECameraControlMode mPrevMode; + ECameraControlMode mCurrMode; + std::map mMode2Button; + + LLPanel* mControls { nullptr }; + LLPanel* mViewerCameraInfo { nullptr }; + LLPanel* mAgentCameraInfo { nullptr }; + LLComboBox* mPresetCombo { nullptr }; + LLTextBox* mPreciseCtrls { nullptr }; +}; + +/** + * Class used to represent widgets from panel_camera_item.xml- + * panels that contain pictures and text. Pictures are different + * for selected and unselected state (this state is nor stored- icons + * are changed in setValue()). This class doesn't implement selection logic- + * it's items are used inside of flatlist. + */ +class LLPanelCameraItem + : public LLPanel +{ +public: + struct Params : public LLInitParam::Block + { + Optional icon_over; + Optional icon_selected; + Optional picture; + Optional selected_picture; + + Optional text; + Optional mousedown_callback; + Params(); + }; + /*virtual*/ bool postBuild(); + /** setting on/off background icon to indicate selected state */ + /*virtual*/ void setValue(const LLSD& value); + // sends commit signal + void onAnyMouseClick(); +protected: + friend class LLUICtrlFactory; + LLPanelCameraItem(const Params&); + LLIconCtrl* mIconOver; + LLIconCtrl* mIconSelected; + LLIconCtrl* mPicture; + LLIconCtrl* mPictureSelected; + LLTextBox* mText; +}; + +#endif diff --git a/indra/newview/llfloatercamerapresets.cpp b/indra/newview/llfloatercamerapresets.cpp index 15b518f82d..b033af2564 100644 --- a/indra/newview/llfloatercamerapresets.cpp +++ b/indra/newview/llfloatercamerapresets.cpp @@ -1,161 +1,161 @@ -/** -* @file llfloatercamerapresets.cpp -* -* $LicenseInfo:firstyear=2019&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2019, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ -#include "llviewerprecompiledheaders.h" - -#include "llfloatercamera.h" -#include "llfloatercamerapresets.h" -#include "llfloaterreg.h" -#include "llnotificationsutil.h" -#include "llpresetsmanager.h" -#include "llviewercontrol.h" - -LLFloaterCameraPresets::LLFloaterCameraPresets(const LLSD& key) -: LLFloater(key) -{} - -LLFloaterCameraPresets::~LLFloaterCameraPresets() -{} - -bool LLFloaterCameraPresets::postBuild() -{ - mPresetList = getChild("preset_list"); - mPresetList->setCommitCallback(boost::bind(&LLFloaterCameraPresets::onSelectionChange, this)); - mPresetList->setCommitOnSelectionChange(true); - LLPresetsManager::getInstance()->setPresetListChangeCameraCallback(boost::bind(&LLFloaterCameraPresets::populateList, this)); - - return true; -} -void LLFloaterCameraPresets::onOpen(const LLSD& key) -{ - populateList(); -} - -void LLFloaterCameraPresets::populateList() -{ - mPresetList->clear(); - - LLPresetsManager* presetsMgr = LLPresetsManager::getInstance(); - std::list preset_names; - - presetsMgr->loadPresetNamesFromDir(PRESETS_CAMERA, preset_names, DEFAULT_BOTTOM); - std::string active_preset = gSavedSettings.getString("PresetCameraActive"); - - for (std::list::const_iterator it = preset_names.begin(); it != preset_names.end(); ++it) - { - const std::string& name = *it; - bool is_default = presetsMgr->isDefaultCameraPreset(name); - LLCameraPresetFlatItem* item = new LLCameraPresetFlatItem(name, is_default); - item->postBuild(); - mPresetList->addItem(item); - if(name == active_preset) - { - mPresetList->selectItem(item); - } - } -} - -void LLFloaterCameraPresets::onSelectionChange() -{ - LLCameraPresetFlatItem* selected_preset = dynamic_cast(mPresetList->getSelectedItem()); - if(selected_preset) - { - LLFloaterCamera::switchToPreset(selected_preset->getPresetName()); - } -} - -LLCameraPresetFlatItem::LLCameraPresetFlatItem(const std::string &preset_name, bool is_default) - : LLPanel(), - mPresetName(preset_name), - mIsDefaultPrest(is_default) -{ - mCommitCallbackRegistrar.add("CameraPresets.Delete", boost::bind(&LLCameraPresetFlatItem::onDeleteBtnClick, this)); - mCommitCallbackRegistrar.add("CameraPresets.Reset", boost::bind(&LLCameraPresetFlatItem::onResetBtnClick, this)); - buildFromFile("panel_camera_preset_item.xml"); -} - -LLCameraPresetFlatItem::~LLCameraPresetFlatItem() -{ -} - -bool LLCameraPresetFlatItem::postBuild() -{ - mDeleteBtn = getChild("delete_btn"); - mDeleteBtn->setVisible(false); - - mResetBtn = getChild("reset_btn"); - mResetBtn->setVisible(false); - - LLStyle::Params style; - LLTextBox* name_text = getChild("preset_name"); - LLFontDescriptor new_desc(name_text->getFont()->getFontDesc()); - new_desc.setStyle(mIsDefaultPrest ? LLFontGL::ITALIC : LLFontGL::NORMAL); - LLFontGL* new_font = LLFontGL::getFont(new_desc); - style.font = new_font; - name_text->setText(mPresetName, style); - - return true; -} - -void LLCameraPresetFlatItem::onMouseEnter(S32 x, S32 y, MASK mask) -{ - mDeleteBtn->setVisible(!mIsDefaultPrest); - mResetBtn->setVisible(mIsDefaultPrest); - getChildView("hovered_icon")->setVisible(true); - LLPanel::onMouseEnter(x, y, mask); -} - -void LLCameraPresetFlatItem::onMouseLeave(S32 x, S32 y, MASK mask) -{ - mDeleteBtn->setVisible(false); - mResetBtn->setVisible(false); - getChildView("hovered_icon")->setVisible(false); - LLPanel::onMouseLeave(x, y, mask); -} - -void LLCameraPresetFlatItem::setValue(const LLSD& value) -{ - if (!value.isMap()) return;; - if (!value.has("selected")) return; - getChildView("selected_icon")->setVisible(value["selected"]); -} - -void LLCameraPresetFlatItem::onDeleteBtnClick() -{ - if (!LLPresetsManager::getInstance()->deletePreset(PRESETS_CAMERA, mPresetName)) - { - LLSD args; - args["NAME"] = mPresetName; - LLNotificationsUtil::add("PresetNotDeleted", args); - } - else if (gSavedSettings.getString("PresetCameraActive") == mPresetName) - { - gSavedSettings.setString("PresetCameraActive", ""); - } -} - -void LLCameraPresetFlatItem::onResetBtnClick() -{ - LLPresetsManager::getInstance()->resetCameraPreset(mPresetName); -} +/** +* @file llfloatercamerapresets.cpp +* +* $LicenseInfo:firstyear=2019&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2019, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#include "llviewerprecompiledheaders.h" + +#include "llfloatercamera.h" +#include "llfloatercamerapresets.h" +#include "llfloaterreg.h" +#include "llnotificationsutil.h" +#include "llpresetsmanager.h" +#include "llviewercontrol.h" + +LLFloaterCameraPresets::LLFloaterCameraPresets(const LLSD& key) +: LLFloater(key) +{} + +LLFloaterCameraPresets::~LLFloaterCameraPresets() +{} + +bool LLFloaterCameraPresets::postBuild() +{ + mPresetList = getChild("preset_list"); + mPresetList->setCommitCallback(boost::bind(&LLFloaterCameraPresets::onSelectionChange, this)); + mPresetList->setCommitOnSelectionChange(true); + LLPresetsManager::getInstance()->setPresetListChangeCameraCallback(boost::bind(&LLFloaterCameraPresets::populateList, this)); + + return true; +} +void LLFloaterCameraPresets::onOpen(const LLSD& key) +{ + populateList(); +} + +void LLFloaterCameraPresets::populateList() +{ + mPresetList->clear(); + + LLPresetsManager* presetsMgr = LLPresetsManager::getInstance(); + std::list preset_names; + + presetsMgr->loadPresetNamesFromDir(PRESETS_CAMERA, preset_names, DEFAULT_BOTTOM); + std::string active_preset = gSavedSettings.getString("PresetCameraActive"); + + for (std::list::const_iterator it = preset_names.begin(); it != preset_names.end(); ++it) + { + const std::string& name = *it; + bool is_default = presetsMgr->isDefaultCameraPreset(name); + LLCameraPresetFlatItem* item = new LLCameraPresetFlatItem(name, is_default); + item->postBuild(); + mPresetList->addItem(item); + if(name == active_preset) + { + mPresetList->selectItem(item); + } + } +} + +void LLFloaterCameraPresets::onSelectionChange() +{ + LLCameraPresetFlatItem* selected_preset = dynamic_cast(mPresetList->getSelectedItem()); + if(selected_preset) + { + LLFloaterCamera::switchToPreset(selected_preset->getPresetName()); + } +} + +LLCameraPresetFlatItem::LLCameraPresetFlatItem(const std::string &preset_name, bool is_default) + : LLPanel(), + mPresetName(preset_name), + mIsDefaultPrest(is_default) +{ + mCommitCallbackRegistrar.add("CameraPresets.Delete", boost::bind(&LLCameraPresetFlatItem::onDeleteBtnClick, this)); + mCommitCallbackRegistrar.add("CameraPresets.Reset", boost::bind(&LLCameraPresetFlatItem::onResetBtnClick, this)); + buildFromFile("panel_camera_preset_item.xml"); +} + +LLCameraPresetFlatItem::~LLCameraPresetFlatItem() +{ +} + +bool LLCameraPresetFlatItem::postBuild() +{ + mDeleteBtn = getChild("delete_btn"); + mDeleteBtn->setVisible(false); + + mResetBtn = getChild("reset_btn"); + mResetBtn->setVisible(false); + + LLStyle::Params style; + LLTextBox* name_text = getChild("preset_name"); + LLFontDescriptor new_desc(name_text->getFont()->getFontDesc()); + new_desc.setStyle(mIsDefaultPrest ? LLFontGL::ITALIC : LLFontGL::NORMAL); + LLFontGL* new_font = LLFontGL::getFont(new_desc); + style.font = new_font; + name_text->setText(mPresetName, style); + + return true; +} + +void LLCameraPresetFlatItem::onMouseEnter(S32 x, S32 y, MASK mask) +{ + mDeleteBtn->setVisible(!mIsDefaultPrest); + mResetBtn->setVisible(mIsDefaultPrest); + getChildView("hovered_icon")->setVisible(true); + LLPanel::onMouseEnter(x, y, mask); +} + +void LLCameraPresetFlatItem::onMouseLeave(S32 x, S32 y, MASK mask) +{ + mDeleteBtn->setVisible(false); + mResetBtn->setVisible(false); + getChildView("hovered_icon")->setVisible(false); + LLPanel::onMouseLeave(x, y, mask); +} + +void LLCameraPresetFlatItem::setValue(const LLSD& value) +{ + if (!value.isMap()) return;; + if (!value.has("selected")) return; + getChildView("selected_icon")->setVisible(value["selected"]); +} + +void LLCameraPresetFlatItem::onDeleteBtnClick() +{ + if (!LLPresetsManager::getInstance()->deletePreset(PRESETS_CAMERA, mPresetName)) + { + LLSD args; + args["NAME"] = mPresetName; + LLNotificationsUtil::add("PresetNotDeleted", args); + } + else if (gSavedSettings.getString("PresetCameraActive") == mPresetName) + { + gSavedSettings.setString("PresetCameraActive", ""); + } +} + +void LLCameraPresetFlatItem::onResetBtnClick() +{ + LLPresetsManager::getInstance()->resetCameraPreset(mPresetName); +} diff --git a/indra/newview/llfloaterchangeitemthumbnail.h b/indra/newview/llfloaterchangeitemthumbnail.h index 29ea98b2a6..46f63801fe 100644 --- a/indra/newview/llfloaterchangeitemthumbnail.h +++ b/indra/newview/llfloaterchangeitemthumbnail.h @@ -1,142 +1,142 @@ -/** - * @file llfloaterchangeitemthumbnail.h - * @brief LLFloaterChangeItemThumbnail class definition - * - * $LicenseInfo:firstyear=2023&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2023, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERCHANGEITEMTHUMBNAIL_H -#define LL_LLFLOATERCHANGEITEMTHUMBNAIL_H - -#include "llfloater.h" -#include "llinventoryobserver.h" -#include "llvoinventorylistener.h" - -class LLButton; -class LLIconCtrl; -class LLTextBox; -class LLThumbnailCtrl; -class LLUICtrl; -class LLViewerInventoryItem; -class LLViewerFetchedTexture; - -class LLFloaterChangeItemThumbnail : public LLFloater, public LLInventoryObserver, public LLVOInventoryListener -{ -public: - LLFloaterChangeItemThumbnail(const LLSD& key); - ~LLFloaterChangeItemThumbnail(); - - bool postBuild() override; - void onOpen(const LLSD& key) override; - void onFocusReceived() override; - void onMouseEnter(S32 x, S32 y, MASK mask) override; - - bool handleDragAndDrop( - S32 x, - S32 y, - MASK mask, - bool drop, - EDragAndDropType cargo_type, - void *cargo_data, - EAcceptance *accept, - std::string& tooltip_msg) override; - - void changed(U32 mask) override; - void inventoryChanged(LLViewerObject* object, - LLInventoryObject::object_list_t* inventory, - S32 serial_num, - void* user_data) override; - - static bool validateAsset(const LLUUID &asset_id); - -private: - - LLInventoryObject* getInventoryObject(); - void refreshFromInventory(); - void refreshFromObject(LLInventoryObject* obj); - - static void onUploadLocal(void*); - static void onUploadSnapshot(void*); - static void onUseTexture(void*); - static void onCopyToClipboard(void*); - static void onPasteFromClipboard(void*); - static void onRemove(void*); - static void onRemovalConfirmation(const LLSD& notification, const LLSD& response, LLHandle handle); - - void assignAndValidateAsset(const LLUUID &asset_id, bool silent = false); - static void onImageDataLoaded(bool success, - LLViewerFetchedTexture *src_vi, - LLImageRaw* src, - LLImageRaw* aux_src, - S32 discard_level, - bool final, - void* userdata); - static void onFullImageLoaded(bool success, - LLViewerFetchedTexture* src_vi, - LLImageRaw* src, - LLImageRaw* aux_src, - S32 discard_level, - bool final, - void* userdata); - - void showTexturePicker(const LLUUID &thumbnail_id); - void onTexturePickerCommit(); - static void onUploadComplete(const LLUUID& asset_id, const LLUUID& task_id, const uuid_set_t& inventory_ids, LLHandle handle); - - void setThumbnailId(const LLUUID &new_thumbnail_id); - static void setThumbnailId(const LLUUID& new_thumbnail_id, const LLUUID& task_id, const LLUUID& inv_obj_id); - static void setThumbnailId(const LLUUID& new_thumbnail_id, const LLUUID& inv_obj_id, LLInventoryObject* obj); - - enum EToolTipState - { - TOOLTIP_NONE, - TOOLTIP_UPLOAD_LOCAL, - TOOLTIP_UPLOAD_SNAPSHOT, - TOOLTIP_USE_TEXTURE, - TOOLTIP_COPY_TO_CLIPBOARD, - TOOLTIP_COPY_FROM_CLIPBOARD, - TOOLTIP_REMOVE, - }; - - void onButtonMouseEnter(LLUICtrl* button, const LLSD& param, EToolTipState state); - void onButtonMouseLeave(LLUICtrl* button, const LLSD& param, EToolTipState state); - - bool mObserverInitialized; - bool mMultipleThumbnails; // for multiselection - EToolTipState mTooltipState; - uuid_set_t mItemList; - LLUUID mTaskId; - LLUUID mExpectingAssetId; - - LLIconCtrl *mItemTypeIcon; - LLUICtrl *mItemNameText; - LLThumbnailCtrl *mThumbnailCtrl; - LLTextBox *mToolTipTextBox; - LLTextBox *mMultipleTextBox; - LLButton *mCopyToClipboardBtn; - LLButton *mPasteFromClipboardBtn; - LLButton *mRemoveImageBtn; - - LLHandle mPickerHandle; - LLHandle mSnapshotHandle; -}; -#endif // LL_LLFLOATERCHANGEITEMTHUMBNAIL_H +/** + * @file llfloaterchangeitemthumbnail.h + * @brief LLFloaterChangeItemThumbnail class definition + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERCHANGEITEMTHUMBNAIL_H +#define LL_LLFLOATERCHANGEITEMTHUMBNAIL_H + +#include "llfloater.h" +#include "llinventoryobserver.h" +#include "llvoinventorylistener.h" + +class LLButton; +class LLIconCtrl; +class LLTextBox; +class LLThumbnailCtrl; +class LLUICtrl; +class LLViewerInventoryItem; +class LLViewerFetchedTexture; + +class LLFloaterChangeItemThumbnail : public LLFloater, public LLInventoryObserver, public LLVOInventoryListener +{ +public: + LLFloaterChangeItemThumbnail(const LLSD& key); + ~LLFloaterChangeItemThumbnail(); + + bool postBuild() override; + void onOpen(const LLSD& key) override; + void onFocusReceived() override; + void onMouseEnter(S32 x, S32 y, MASK mask) override; + + bool handleDragAndDrop( + S32 x, + S32 y, + MASK mask, + bool drop, + EDragAndDropType cargo_type, + void *cargo_data, + EAcceptance *accept, + std::string& tooltip_msg) override; + + void changed(U32 mask) override; + void inventoryChanged(LLViewerObject* object, + LLInventoryObject::object_list_t* inventory, + S32 serial_num, + void* user_data) override; + + static bool validateAsset(const LLUUID &asset_id); + +private: + + LLInventoryObject* getInventoryObject(); + void refreshFromInventory(); + void refreshFromObject(LLInventoryObject* obj); + + static void onUploadLocal(void*); + static void onUploadSnapshot(void*); + static void onUseTexture(void*); + static void onCopyToClipboard(void*); + static void onPasteFromClipboard(void*); + static void onRemove(void*); + static void onRemovalConfirmation(const LLSD& notification, const LLSD& response, LLHandle handle); + + void assignAndValidateAsset(const LLUUID &asset_id, bool silent = false); + static void onImageDataLoaded(bool success, + LLViewerFetchedTexture *src_vi, + LLImageRaw* src, + LLImageRaw* aux_src, + S32 discard_level, + bool final, + void* userdata); + static void onFullImageLoaded(bool success, + LLViewerFetchedTexture* src_vi, + LLImageRaw* src, + LLImageRaw* aux_src, + S32 discard_level, + bool final, + void* userdata); + + void showTexturePicker(const LLUUID &thumbnail_id); + void onTexturePickerCommit(); + static void onUploadComplete(const LLUUID& asset_id, const LLUUID& task_id, const uuid_set_t& inventory_ids, LLHandle handle); + + void setThumbnailId(const LLUUID &new_thumbnail_id); + static void setThumbnailId(const LLUUID& new_thumbnail_id, const LLUUID& task_id, const LLUUID& inv_obj_id); + static void setThumbnailId(const LLUUID& new_thumbnail_id, const LLUUID& inv_obj_id, LLInventoryObject* obj); + + enum EToolTipState + { + TOOLTIP_NONE, + TOOLTIP_UPLOAD_LOCAL, + TOOLTIP_UPLOAD_SNAPSHOT, + TOOLTIP_USE_TEXTURE, + TOOLTIP_COPY_TO_CLIPBOARD, + TOOLTIP_COPY_FROM_CLIPBOARD, + TOOLTIP_REMOVE, + }; + + void onButtonMouseEnter(LLUICtrl* button, const LLSD& param, EToolTipState state); + void onButtonMouseLeave(LLUICtrl* button, const LLSD& param, EToolTipState state); + + bool mObserverInitialized; + bool mMultipleThumbnails; // for multiselection + EToolTipState mTooltipState; + uuid_set_t mItemList; + LLUUID mTaskId; + LLUUID mExpectingAssetId; + + LLIconCtrl *mItemTypeIcon; + LLUICtrl *mItemNameText; + LLThumbnailCtrl *mThumbnailCtrl; + LLTextBox *mToolTipTextBox; + LLTextBox *mMultipleTextBox; + LLButton *mCopyToClipboardBtn; + LLButton *mPasteFromClipboardBtn; + LLButton *mRemoveImageBtn; + + LLHandle mPickerHandle; + LLHandle mSnapshotHandle; +}; +#endif // LL_LLFLOATERCHANGEITEMTHUMBNAIL_H diff --git a/indra/newview/llfloatercolorpicker.cpp b/indra/newview/llfloatercolorpicker.cpp index e6d6028419..603f54fb49 100644 --- a/indra/newview/llfloatercolorpicker.cpp +++ b/indra/newview/llfloatercolorpicker.cpp @@ -1,1090 +1,1090 @@ -/** - * @file llfloatercolorpicker.cpp - * @brief Generic system color picker - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloatercolorpicker.h" - -// Viewer project includes -#include "lltoolmgr.h" -#include "lltoolpipette.h" -#include "llviewercontrol.h" -#include "llworld.h" - -// Linden library includes -#include "llfontgl.h" -#include "llsys.h" -#include "llgl.h" -#include "llrender.h" -#include "v3dmath.h" -#include "lldir.h" -#include "llui.h" -#include "lllineeditor.h" -#include "v4coloru.h" -#include "llbutton.h" -#include "lluictrlfactory.h" -#include "llgl.h" -#include "llpointer.h" -#include "llimage.h" -#include "llmousehandler.h" -#include "llglheaders.h" -#include "llcheckboxctrl.h" -#include "lltextbox.h" -#include "lluiconstants.h" -#include "llfocusmgr.h" -#include "lldraghandle.h" -#include "llwindow.h" - -// System includes -#include -#include - -////////////////////////////////////////////////////////////////////////////// -// -// Class LLFloaterColorPicker -// -////////////////////////////////////////////////////////////////////////////// - -LLFloaterColorPicker::LLFloaterColorPicker (LLColorSwatchCtrl* swatch, bool show_apply_immediate ) - : LLFloater(LLSD()), - mComponents ( 3 ), - mMouseDownInLumRegion ( false ), - mMouseDownInHueRegion ( false ), - mMouseDownInSwatch ( false ), - // *TODO: Specify this in XML - mRGBViewerImageLeft ( 140 ), - mRGBViewerImageTop ( 356 ), - mRGBViewerImageWidth ( 256 ), - mRGBViewerImageHeight ( 256 ), - mLumRegionLeft ( mRGBViewerImageLeft + mRGBViewerImageWidth + 16 ), - mLumRegionTop ( mRGBViewerImageTop ), - mLumRegionWidth ( 16 ), - mLumRegionHeight ( mRGBViewerImageHeight ), - mLumMarkerSize ( 6 ), - // *TODO: Specify this in XML - mSwatchRegionLeft ( 12 ), - mSwatchRegionTop ( 190 ), - mSwatchRegionWidth ( 116 ), - mSwatchRegionHeight ( 60 ), - mSwatchView ( NULL ), - // *TODO: Specify this in XML - numPaletteColumns ( 16 ), - numPaletteRows ( 2 ), - highlightEntry ( -1 ), - mPaletteRegionLeft ( 11 ), - mPaletteRegionTop ( 100 - 8 ), - mPaletteRegionWidth ( mLumRegionLeft + mLumRegionWidth - 10 ), - mPaletteRegionHeight ( 40 ), - mSwatch ( swatch ), - mActive ( true ), - mCanApplyImmediately ( show_apply_immediate ), - mContextConeOpacity ( 0.f ), - mContextConeInAlpha (CONTEXT_CONE_IN_ALPHA), - mContextConeOutAlpha (CONTEXT_CONE_OUT_ALPHA), - mContextConeFadeTime (CONTEXT_CONE_FADE_TIME) -{ - buildFromFile ( "floater_color_picker.xml"); - - // create user interface for this picker - createUI (); - - if (!mCanApplyImmediately) - { - mApplyImmediateCheck->setEnabled(false); - mApplyImmediateCheck->set(false); - } -} - -LLFloaterColorPicker::~LLFloaterColorPicker() -{ - // destroy the UI we created - destroyUI (); -} - -////////////////////////////////////////////////////////////////////////////// -// -void LLFloaterColorPicker::createUI () -{ - // create RGB type area (not really RGB but it's got R,G & B in it.,.. - - LLPointer raw = new LLImageRaw ( mRGBViewerImageWidth, mRGBViewerImageHeight, mComponents ); - LLImageDataLock lock(raw); - - U8* bits = raw->getData(); - S32 linesize = mRGBViewerImageWidth * mComponents; - for ( S32 y = 0; y < mRGBViewerImageHeight; ++y ) - { - for ( S32 x = 0; x < linesize; x += mComponents ) - { - F32 rVal, gVal, bVal; - - hslToRgb ( (F32)x / (F32) ( linesize - 1 ), - (F32)y / (F32) ( mRGBViewerImageHeight - 1 ), - 0.5f, - rVal, - gVal, - bVal ); - - * ( bits + x + y * linesize + 0 ) = ( U8 )( rVal * 255.0f ); - * ( bits + x + y * linesize + 1 ) = ( U8 )( gVal * 255.0f ); - * ( bits + x + y * linesize + 2 ) = ( U8 )( bVal * 255.0f ); - } - } - mRGBImage = LLViewerTextureManager::getLocalTexture( (LLImageRaw*)raw, false ); - gGL.getTexUnit(0)->bind(mRGBImage); - mRGBImage->setAddressMode(LLTexUnit::TAM_CLAMP); - - // create palette - for ( S32 each = 0; each < numPaletteColumns * numPaletteRows; ++each ) - { - mPalette.push_back(new LLColor4(LLUIColorTable::instance().getColor(llformat("ColorPaletteEntry%02d", each + 1)))); - } -} - -////////////////////////////////////////////////////////////////////////////// -// -void LLFloaterColorPicker::showUI () -{ - openFloater(getKey()); - setVisible ( true ); - setFocus ( true ); - - // HACK: if system color picker is required - close the SL one we made and use default system dialog - if ( gSavedSettings.getBOOL ( "UseDefaultColorPicker" ) ) - { - LLColorSwatchCtrl* swatch = getSwatch (); - - setVisible ( false ); - - // code that will get switched in for default system color picker - if ( swatch ) - { - // Todo: this needs to be threaded for viewer not to timeout - LLColor4 curCol = swatch->get (); - send_agent_pause(); - bool commit = getWindow()->dialogColorPicker( &curCol [ 0 ], &curCol [ 1 ], &curCol [ 2 ] ); - send_agent_resume(); - - if (commit) - { - setOrigRgb(curCol[0], curCol[1], curCol[2]); - setCurRgb(curCol[0], curCol[1], curCol[2]); - - LLColorSwatchCtrl::onColorChanged(swatch, LLColorSwatchCtrl::COLOR_SELECT); - } - else - { - LLColorSwatchCtrl::onColorChanged(swatch, LLColorSwatchCtrl::COLOR_CANCEL); - } - } - - closeFloater(); - } -} - -////////////////////////////////////////////////////////////////////////////// -// called after the dialog is rendered -bool LLFloaterColorPicker::postBuild() -{ - mCancelBtn = getChild( "cancel_btn" ); - mCancelBtn->setClickedCallback ( onClickCancel, this ); - - mSelectBtn = getChild( "select_btn"); - mSelectBtn->setClickedCallback ( onClickSelect, this ); - mSelectBtn->setFocus ( true ); - - mPipetteBtn = getChild("color_pipette" ); - - mPipetteBtn->setImages(std::string("eye_button_inactive.tga"), std::string("eye_button_active.tga")); - - mPipetteBtn->setCommitCallback( boost::bind(&LLFloaterColorPicker::onClickPipette, this )); - - mApplyImmediateCheck = getChild("apply_immediate"); - mApplyImmediateCheck->set(gSavedSettings.getBOOL("ApplyColorImmediately")); - mApplyImmediateCheck->setCommitCallback(onImmediateCheck, this); - - childSetCommitCallback("rspin", onTextCommit, (void*)this ); - childSetCommitCallback("gspin", onTextCommit, (void*)this ); - childSetCommitCallback("bspin", onTextCommit, (void*)this ); - childSetCommitCallback("hspin", onTextCommit, (void*)this ); - childSetCommitCallback("sspin", onTextCommit, (void*)this ); - childSetCommitCallback("lspin", onTextCommit, (void*)this ); - - LLToolPipette::getInstance()->setToolSelectCallback(boost::bind(&LLFloaterColorPicker::onColorSelect, this, _1)); - - return true; -} - -////////////////////////////////////////////////////////////////////////////// -// -void LLFloaterColorPicker::initUI ( F32 rValIn, F32 gValIn, F32 bValIn ) -{ - // under some circumstances, we get rogue values that can be calmed by clamping... - rValIn = llclamp ( rValIn, 0.0f, 1.0f ); - gValIn = llclamp ( gValIn, 0.0f, 1.0f ); - bValIn = llclamp ( bValIn, 0.0f, 1.0f ); - - // store initial value in case cancel or revert is selected - setOrigRgb ( rValIn, gValIn, bValIn ); - - // starting point for current value to - setCurRgb ( rValIn, gValIn, bValIn ); - - // unpdate text entry fields - updateTextEntry (); -} - -////////////////////////////////////////////////////////////////////////////// -// -void LLFloaterColorPicker::destroyUI () -{ - // shut down pipette tool if active - stopUsingPipette(); - - // delete palette we created - std::vector < LLColor4* >::iterator iter = mPalette.begin (); - while ( iter != mPalette.end () ) - { - delete ( *iter ); - ++iter; - } - - if ( mSwatchView ) - { - this->removeChild ( mSwatchView ); - mSwatchView->die();; - mSwatchView = NULL; - } -} - - -////////////////////////////////////////////////////////////////////////////// -// -F32 LLFloaterColorPicker::hueToRgb ( F32 val1In, F32 val2In, F32 valHUeIn ) -{ - if ( valHUeIn < 0.0f ) valHUeIn += 1.0f; - if ( valHUeIn > 1.0f ) valHUeIn -= 1.0f; - if ( ( 6.0f * valHUeIn ) < 1.0f ) return ( val1In + ( val2In - val1In ) * 6.0f * valHUeIn ); - if ( ( 2.0f * valHUeIn ) < 1.0f ) return ( val2In ); - if ( ( 3.0f * valHUeIn ) < 2.0f ) return ( val1In + ( val2In - val1In ) * ( ( 2.0f / 3.0f ) - valHUeIn ) * 6.0f ); - return ( val1In ); -} - -////////////////////////////////////////////////////////////////////////////// -// -void LLFloaterColorPicker::hslToRgb ( F32 hValIn, F32 sValIn, F32 lValIn, F32& rValOut, F32& gValOut, F32& bValOut ) -{ - if ( sValIn < 0.00001f ) - { - rValOut = lValIn; - gValOut = lValIn; - bValOut = lValIn; - } - else - { - F32 interVal1; - F32 interVal2; - - if ( lValIn < 0.5f ) - interVal2 = lValIn * ( 1.0f + sValIn ); - else - interVal2 = ( lValIn + sValIn ) - ( sValIn * lValIn ); - - interVal1 = 2.0f * lValIn - interVal2; - - rValOut = hueToRgb ( interVal1, interVal2, hValIn + ( 1.f / 3.f ) ); - gValOut = hueToRgb ( interVal1, interVal2, hValIn ); - bValOut = hueToRgb ( interVal1, interVal2, hValIn - ( 1.f / 3.f ) ); - } -} - -////////////////////////////////////////////////////////////////////////////// -// mutator for original RGB value -void LLFloaterColorPicker::setOrigRgb ( F32 origRIn, F32 origGIn, F32 origBIn ) -{ - origR = origRIn; - origG = origGIn; - origB = origBIn; -} - -////////////////////////////////////////////////////////////////////////////// -// accessor for original RGB value -void LLFloaterColorPicker::getOrigRgb ( F32& origROut, F32& origGOut, F32& origBOut ) -{ - origROut = origR; - origGOut = origG; - origBOut = origB; -} - -////////////////////////////////////////////////////////////////////////////// -// mutator for current RGB value -void LLFloaterColorPicker::setCurRgb ( F32 curRIn, F32 curGIn, F32 curBIn ) -{ - // save current RGB - curR = curRIn; - curG = curGIn; - curB = curBIn; - - // update corresponding HSL values and - LLColor3(curRIn, curGIn, curBIn).calcHSL(&curH, &curS, &curL); - - // color changed so update text fields - updateTextEntry(); -} - -////////////////////////////////////////////////////////////////////////////// -// accessor for current RGB value -void LLFloaterColorPicker::getCurRgb ( F32& curROut, F32& curGOut, F32& curBOut ) -{ - curROut = curR; - curGOut = curG; - curBOut = curB; -} - -////////////////////////////////////////////////////////////////////////////// -// mutator for current HSL value -void LLFloaterColorPicker::setCurHsl ( F32 curHIn, F32 curSIn, F32 curLIn ) -{ - // save current HSL - curH = curHIn; - curS = curSIn; - curL = curLIn; - - // update corresponding RGB values and - hslToRgb ( curH, curS, curL, curR, curG, curB ); -} - -////////////////////////////////////////////////////////////////////////////// -// accessor for current HSL value -void LLFloaterColorPicker::getCurHsl ( F32& curHOut, F32& curSOut, F32& curLOut ) -{ - curHOut = curH; - curSOut = curS; - curLOut = curL; -} - -////////////////////////////////////////////////////////////////////////////// -// called when 'cancel' clicked -void LLFloaterColorPicker::onClickCancel ( void* data ) -{ - if (data) - { - LLFloaterColorPicker* self = ( LLFloaterColorPicker* )data; - - if ( self ) - { - self->cancelSelection(); - self->closeFloater(); - } - } -} - -////////////////////////////////////////////////////////////////////////////// -// called when 'select' clicked -void LLFloaterColorPicker::onClickSelect ( void* data ) -{ - if (data) - { - LLFloaterColorPicker* self = ( LLFloaterColorPicker* )data; - - if ( self ) - { - // apply to selection - LLColorSwatchCtrl::onColorChanged ( self->getSwatch (), LLColorSwatchCtrl::COLOR_SELECT ); - self->closeFloater(); - } - } -} - -void LLFloaterColorPicker::onClickPipette( ) -{ - bool pipette_active = mPipetteBtn->getToggleState(); - pipette_active = !pipette_active; - if (pipette_active) - { - LLToolMgr::getInstance()->setTransientTool(LLToolPipette::getInstance()); - } - else - { - LLToolMgr::getInstance()->clearTransientTool(); - } -} - -////////////////////////////////////////////////////////////////////////////// -// called when 'text is committed' - i,e. focus moves from a text field -void LLFloaterColorPicker::onTextCommit ( LLUICtrl* ctrl, void* data ) -{ - if ( data ) - { - LLFloaterColorPicker* self = ( LLFloaterColorPicker* )data; - if ( self ) - { - self->onTextEntryChanged ( ctrl ); - } - } -} - -void LLFloaterColorPicker::onImmediateCheck( LLUICtrl* ctrl, void* data) -{ - LLFloaterColorPicker* self = ( LLFloaterColorPicker* )data; - if (self) - { - gSavedSettings.setBOOL("ApplyColorImmediately", self->mApplyImmediateCheck->get()); - if (self->mApplyImmediateCheck->get() && self->isColorChanged()) - { - LLColorSwatchCtrl::onColorChanged ( self->getSwatch (), LLColorSwatchCtrl::COLOR_CHANGE ); - } - } -} - -void LLFloaterColorPicker::onColorSelect( const LLTextureEntry& te ) -{ - // Pipete - selectCurRgb(te.getColor().mV[VRED], te.getColor().mV[VGREEN], te.getColor().mV[VBLUE]); -} - -void LLFloaterColorPicker::onMouseCaptureLost() -{ - setMouseDownInHueRegion(false); - setMouseDownInLumRegion(false); -} - -F32 LLFloaterColorPicker::getSwatchTransparency() -{ - // If the floater is focused, don't apply its alpha to the color swatch (STORM-676). - return getTransparencyType() == TT_ACTIVE ? 1.f : LLFloater::getCurrentTransparency(); -} - -bool LLFloaterColorPicker::isColorChanged() -{ - return ((getOrigR() != getCurR()) || (getOrigG() != getCurG()) || (getOrigB() != getCurB())); -} - -////////////////////////////////////////////////////////////////////////////// -// -void LLFloaterColorPicker::draw() -{ - static LLCachedControl max_opacity(gSavedSettings, "PickerContextOpacity", 0.4f); - drawConeToOwner(mContextConeOpacity, max_opacity, mSwatch, mContextConeFadeTime, mContextConeInAlpha, mContextConeOutAlpha); - - mPipetteBtn->setToggleState(LLToolMgr::getInstance()->getCurrentTool() == LLToolPipette::getInstance()); - mApplyImmediateCheck->setEnabled(mActive && mCanApplyImmediately); - mSelectBtn->setEnabled(mActive); - - // base floater stuff - LLFloater::draw (); - - const F32 alpha = getSwatchTransparency(); - - // draw image for RGB area (not really RGB but you'll see what I mean... - gl_draw_image ( mRGBViewerImageLeft, mRGBViewerImageTop - mRGBViewerImageHeight, mRGBImage, LLColor4::white % alpha); - - // update 'cursor' into RGB Section - S32 xPos = ( S32 ) ( ( F32 )mRGBViewerImageWidth * getCurH () ) - 8; - S32 yPos = ( S32 ) ( ( F32 )mRGBViewerImageHeight * getCurS () ) - 8; - gl_line_2d ( mRGBViewerImageLeft + xPos, - mRGBViewerImageTop - mRGBViewerImageHeight + yPos + 8, - mRGBViewerImageLeft + xPos + 16, - mRGBViewerImageTop - mRGBViewerImageHeight + yPos + 8, - LLColor4 ( 0.0f, 0.0f, 0.0f, 1.0f ) ); - - gl_line_2d ( mRGBViewerImageLeft + xPos + 8, - mRGBViewerImageTop - mRGBViewerImageHeight + yPos, - mRGBViewerImageLeft + xPos + 8, - mRGBViewerImageTop - mRGBViewerImageHeight + yPos + 16, - LLColor4 ( 0.0f, 0.0f, 0.0f, 1.0f ) ); - - // create rgb area outline - gl_rect_2d ( mRGBViewerImageLeft, - mRGBViewerImageTop - mRGBViewerImageHeight, - mRGBViewerImageLeft + mRGBViewerImageWidth + 1, - mRGBViewerImageTop, - LLColor4 ( 0.0f, 0.0f, 0.0f, alpha ), - false ); - - // draw luminance slider - for ( S32 y = 0; y < mLumRegionHeight; ++y ) - { - F32 rValSlider, gValSlider, bValSlider; - hslToRgb ( getCurH (), getCurS (), ( F32 )y / ( F32 )mLumRegionHeight, rValSlider, gValSlider, bValSlider ); - - gl_rect_2d( mLumRegionLeft, - mLumRegionTop - mLumRegionHeight + y, - mLumRegionLeft + mLumRegionWidth, - mLumRegionTop - mLumRegionHeight + y - 1, - LLColor4 ( rValSlider, gValSlider, bValSlider, alpha ) ); - } - - - // draw luninance marker - S32 startX = mLumRegionLeft + mLumRegionWidth; - S32 startY = mLumRegionTop - mLumRegionHeight + ( S32 ) ( mLumRegionHeight * getCurL () ); - gl_triangle_2d ( startX, startY, - startX + mLumMarkerSize, startY - mLumMarkerSize, - startX + mLumMarkerSize, startY + mLumMarkerSize, - LLColor4 ( 0.75f, 0.75f, 0.75f, 1.0f ), true ); - - // draw luminance slider outline - gl_rect_2d ( mLumRegionLeft, - mLumRegionTop - mLumRegionHeight, - mLumRegionLeft + mLumRegionWidth + 1, - mLumRegionTop, - LLColor4 ( 0.0f, 0.0f, 0.0f, 1.0f ), - false ); - - // draw selected color swatch - gl_rect_2d ( mSwatchRegionLeft, - mSwatchRegionTop - mSwatchRegionHeight, - mSwatchRegionLeft + mSwatchRegionWidth, - mSwatchRegionTop, - LLColor4 ( getCurR (), getCurG (), getCurB (), alpha ), - true ); - - // draw selected color swatch outline - gl_rect_2d ( mSwatchRegionLeft, - mSwatchRegionTop - mSwatchRegionHeight, - mSwatchRegionLeft + mSwatchRegionWidth + 1, - mSwatchRegionTop, - LLColor4 ( 0.0f, 0.0f, 0.0f, 1.0f ), - false ); - - // color palette code is a little more involved so break it out into its' own method - drawPalette (); -} - -////////////////////////////////////////////////////////////////////////////// -// find a complimentary color to the one passed in that can be used to highlight -const LLColor4& LLFloaterColorPicker::getComplimentaryColor ( const LLColor4& backgroundColor ) -{ - // going to base calculation on luminance - F32 hVal, sVal, lVal; - backgroundColor.calcHSL(&hVal, &sVal, &lVal); - hVal *= 360.f; - sVal *= 100.f; - lVal *= 100.f; - - // fairly simple heuristic for now...! - if ( lVal < 0.5f ) - { - return LLColor4::white; - } - - return LLColor4::black; -} - -////////////////////////////////////////////////////////////////////////////// -// set current RGB and rise change event if needed. -void LLFloaterColorPicker::selectCurRgb ( F32 curRIn, F32 curGIn, F32 curBIn ) -{ - setCurRgb(curRIn, curGIn, curBIn); - if (mApplyImmediateCheck->get()) - { - LLColorSwatchCtrl::onColorChanged ( getSwatch (), LLColorSwatchCtrl::COLOR_CHANGE ); - } -} - -////////////////////////////////////////////////////////////////////////////// -// set current HSL and rise change event if needed. -void LLFloaterColorPicker::selectCurHsl ( F32 curHIn, F32 curSIn, F32 curLIn ) -{ - setCurHsl(curHIn, curSIn, curLIn); - if (mApplyImmediateCheck->get()) - { - LLColorSwatchCtrl::onColorChanged ( getSwatch (), LLColorSwatchCtrl::COLOR_CHANGE ); - } -} - -////////////////////////////////////////////////////////////////////////////// -// draw color palette -void LLFloaterColorPicker::drawPalette () -{ - S32 curEntry = 0; - const F32 alpha = getSwatchTransparency(); - - for ( S32 y = 0; y < numPaletteRows; ++y ) - { - for ( S32 x = 0; x < numPaletteColumns; ++x ) - { - // calculate position - S32 x1 = mPaletteRegionLeft + ( mPaletteRegionWidth * x ) / numPaletteColumns; - S32 y1 = mPaletteRegionTop - ( mPaletteRegionHeight * y ) / numPaletteRows; - S32 x2 = ( mPaletteRegionLeft + ( mPaletteRegionWidth * ( x + 1 ) ) / numPaletteColumns ); - S32 y2 = ( mPaletteRegionTop - ( mPaletteRegionHeight * ( y + 1 ) ) / numPaletteRows ); - - // draw palette entry color - if ( mPalette [ curEntry ] ) - { - gl_rect_2d ( x1 + 2, y1 - 2, x2 - 2, y2 + 2, *mPalette [ curEntry++ ] % alpha, true ); - gl_rect_2d ( x1 + 1, y1 - 1, x2 - 1, y2 + 1, LLColor4 ( 0.0f, 0.0f, 0.0f, 1.0f ), false ); - } - } - } - - // if there is something to highlight (mouse down in swatch & hovering over palette) - if ( highlightEntry >= 0 ) - { - // extract row/column from palette index - S32 entryColumn = highlightEntry % numPaletteColumns; - S32 entryRow = highlightEntry / numPaletteColumns; - - // calculate position of this entry - S32 x1 = mPaletteRegionLeft + ( mPaletteRegionWidth * entryColumn ) / numPaletteColumns; - S32 y1 = mPaletteRegionTop - ( mPaletteRegionHeight * entryRow ) / numPaletteRows; - S32 x2 = ( mPaletteRegionLeft + ( mPaletteRegionWidth * ( entryColumn + 1 ) ) / numPaletteColumns ); - S32 y2 = ( mPaletteRegionTop - ( mPaletteRegionHeight * ( entryRow + 1 ) ) / numPaletteRows ); - - // center position of entry - S32 xCenter = x1 + ( x2 - x1 ) / 2; - S32 yCenter = y1 - ( y1 - y2 ) / 2; - - // find a color that works well as a highlight color - LLColor4 hlColor ( getComplimentaryColor ( *mPalette [ highlightEntry ] ) ); - - // mark a cross for entry that is being hovered - gl_line_2d ( xCenter - 4, yCenter - 4, xCenter + 4, yCenter + 4, hlColor ); - gl_line_2d ( xCenter + 4, yCenter - 4, xCenter - 4, yCenter + 4, hlColor ); - } -} - -////////////////////////////////////////////////////////////////////////////// -// update text entry values for RGB/HSL (can't be done in ::draw () since this overwrites input -void LLFloaterColorPicker::updateTextEntry () -{ - // set values in spinners - getChild("rspin")->setValue(( getCurR () * 255.0f ) ); - getChild("gspin")->setValue(( getCurG () * 255.0f ) ); - getChild("bspin")->setValue(( getCurB () * 255.0f ) ); - getChild("hspin")->setValue(( getCurH () * 360.0f ) ); - getChild("sspin")->setValue(( getCurS () * 100.0f ) ); - getChild("lspin")->setValue(( getCurL () * 100.0f ) ); -} - -////////////////////////////////////////////////////////////////////////////// -// -void LLFloaterColorPicker::onTextEntryChanged ( LLUICtrl* ctrl ) -{ - // value in RGB boxes changed - std::string name = ctrl->getName(); - if ( ( name == "rspin" ) || ( name == "gspin" ) || ( name == "bspin" ) ) - { - // get current RGB - F32 rVal, gVal, bVal; - getCurRgb ( rVal, gVal, bVal ); - - // update component value with new value from text - if ( name == "rspin" ) - { - rVal = (F32)ctrl->getValue().asReal() / 255.0f; - } - else - if ( name == "gspin" ) - { - gVal = (F32)ctrl->getValue().asReal() / 255.0f; - } - else - if ( name == "bspin" ) - { - bVal = (F32)ctrl->getValue().asReal() / 255.0f; - } - - // update current RGB (and implicitly HSL) - selectCurRgb ( rVal, gVal, bVal ); - - updateTextEntry (); - } - else - // value in HSL boxes changed - if ( ( name == "hspin" ) || ( name == "sspin" ) || ( name == "lspin" ) ) - { - // get current HSL - F32 hVal, sVal, lVal; - getCurHsl ( hVal, sVal, lVal ); - - // update component value with new value from text - if ( name == "hspin" ) - hVal = (F32)ctrl->getValue().asReal() / 360.0f; - else - if ( name == "sspin" ) - sVal = (F32)ctrl->getValue().asReal() / 100.0f; - else - if ( name == "lspin" ) - lVal = (F32)ctrl->getValue().asReal() / 100.0f; - - // update current HSL (and implicitly RGB) - selectCurHsl ( hVal, sVal, lVal ); - - updateTextEntry (); - } -} - -////////////////////////////////////////////////////////////////////////////// -// -bool LLFloaterColorPicker::updateRgbHslFromPoint ( S32 xPosIn, S32 yPosIn ) -{ - if ( xPosIn >= mRGBViewerImageLeft && - xPosIn <= mRGBViewerImageLeft + mRGBViewerImageWidth && - yPosIn <= mRGBViewerImageTop && - yPosIn >= mRGBViewerImageTop - mRGBViewerImageHeight ) - { - // update HSL (and therefore RGB) based on new H & S and current L - selectCurHsl ( ( ( F32 )xPosIn - ( F32 )mRGBViewerImageLeft ) / ( F32 )mRGBViewerImageWidth, - ( ( F32 )yPosIn - ( ( F32 )mRGBViewerImageTop - ( F32 )mRGBViewerImageHeight ) ) / ( F32 )mRGBViewerImageHeight, - getCurL () ); - - // indicate a value changed - return true; - } - else - if ( xPosIn >= mLumRegionLeft && - xPosIn <= mLumRegionLeft + mLumRegionWidth && - yPosIn <= mLumRegionTop && - yPosIn >= mLumRegionTop - mLumRegionHeight ) - { - - // update HSL (and therefore RGB) based on current HS and new L - selectCurHsl ( getCurH (), - getCurS (), - ( ( F32 )yPosIn - ( ( F32 )mRGBViewerImageTop - ( F32 )mRGBViewerImageHeight ) ) / ( F32 )mRGBViewerImageHeight ); - - // indicate a value changed - return true; - } - - return false; -} - -////////////////////////////////////////////////////////////////////////////// -// -bool LLFloaterColorPicker::handleMouseDown ( S32 x, S32 y, MASK mask ) -{ - // make it the frontmost - gFloaterView->bringToFront(this); - - // rect containing RGB area - LLRect rgbAreaRect ( mRGBViewerImageLeft, - mRGBViewerImageTop, - mRGBViewerImageLeft + mRGBViewerImageWidth, - mRGBViewerImageTop - mRGBViewerImageHeight ); - - if ( rgbAreaRect.pointInRect ( x, y ) ) - { - gFocusMgr.setMouseCapture(this); - // mouse button down - setMouseDownInHueRegion ( true ); - - // update all values based on initial click - updateRgbHslFromPoint ( x, y ); - - // required by base class - return true; - } - - // rect containing RGB area - LLRect lumAreaRect ( mLumRegionLeft, - mLumRegionTop, - mLumRegionLeft + mLumRegionWidth + mLumMarkerSize, - mLumRegionTop - mLumRegionHeight ); - - if ( lumAreaRect.pointInRect ( x, y ) ) - { - gFocusMgr.setMouseCapture(this); - // mouse button down - setMouseDownInLumRegion ( true ); - - // required by base class - return true; - } - - // rect containing swatch area - LLRect swatchRect ( mSwatchRegionLeft, - mSwatchRegionTop, - mSwatchRegionLeft + mSwatchRegionWidth, - mSwatchRegionTop - mSwatchRegionHeight ); - - setMouseDownInSwatch( false ); - if ( swatchRect.pointInRect ( x, y ) ) - { - setMouseDownInSwatch( true ); - - // required - dont drag windows here. - return true; - } - - // rect containing palette area - LLRect paletteRect ( mPaletteRegionLeft, - mPaletteRegionTop, - mPaletteRegionLeft + mPaletteRegionWidth, - mPaletteRegionTop - mPaletteRegionHeight ); - - if ( paletteRect.pointInRect ( x, y ) ) - { - // release keyboard focus so we can change text values - if (gFocusMgr.childHasKeyboardFocus(this)) - { - mSelectBtn->setFocus(true); - } - - // calculate which palette index we selected - S32 c = ( ( x - mPaletteRegionLeft ) * numPaletteColumns ) / mPaletteRegionWidth; - S32 r = ( ( y - ( mPaletteRegionTop - mPaletteRegionHeight ) ) * numPaletteRows ) / mPaletteRegionHeight; - - U32 index = ( numPaletteRows - r - 1 ) * numPaletteColumns + c; - - if ( index <= mPalette.size () ) - { - LLColor4 selected = *mPalette [ index ]; - - selectCurRgb ( selected [ 0 ], selected [ 1 ], selected [ 2 ] ); - - if (mApplyImmediateCheck->get()) - { - LLColorSwatchCtrl::onColorChanged ( getSwatch (), LLColorSwatchCtrl::COLOR_CHANGE ); - } - - updateTextEntry (); - } - - return true; - } - - // dispatch to base class for the rest of things - - return LLFloater::handleMouseDown ( x, y, mask ); -} - -////////////////////////////////////////////////////////////////////////////// -// -bool LLFloaterColorPicker::handleHover ( S32 x, S32 y, MASK mask ) -{ - // if we're the front most window - if ( isFrontmost () ) - { - // mouse was pressed within region - if ( getMouseDownInHueRegion() || getMouseDownInLumRegion()) - { - S32 clamped_x, clamped_y; - if (getMouseDownInHueRegion()) - { - clamped_x = llclamp(x, mRGBViewerImageLeft, mRGBViewerImageLeft + mRGBViewerImageWidth); - clamped_y = llclamp(y, mRGBViewerImageTop - mRGBViewerImageHeight, mRGBViewerImageTop); - } - else - { - clamped_x = llclamp(x, mLumRegionLeft, mLumRegionLeft + mLumRegionWidth); - clamped_y = llclamp(y, mLumRegionTop - mLumRegionHeight, mLumRegionTop); - } - - // update the stored RGB/HSL values using the mouse position - returns true if RGB was updated - if ( updateRgbHslFromPoint ( clamped_x, clamped_y ) ) - { - // update text entry fields - updateTextEntry (); - - // RN: apparently changing color when dragging generates too much traffic and results in sporadic updates - //// commit changed color to swatch subject - //// REVIEW: this gets sent each time a color changes - is this okay ? - //if (mApplyImmediateCheck->get()) - //{ - // LLColorSwatchCtrl::onColorChanged ( getSwatch () ); - //} - } - } - - highlightEntry = -1; - - if ( mMouseDownInSwatch ) - { - getWindow()->setCursor ( UI_CURSOR_ARROWDRAG ); - - // if cursor if over a palette entry - LLRect paletteRect ( mPaletteRegionLeft, - mPaletteRegionTop, - mPaletteRegionLeft + mPaletteRegionWidth, - mPaletteRegionTop - mPaletteRegionHeight ); - - if ( paletteRect.pointInRect ( x, y ) ) - { - // find row/column in palette - S32 xOffset = ( ( x - mPaletteRegionLeft ) * numPaletteColumns ) / mPaletteRegionWidth; - S32 yOffset = ( ( mPaletteRegionTop - y - 1 ) * numPaletteRows ) / mPaletteRegionHeight; - - // calculate the entry 0..n-1 to highlight and set variable to next draw() picks it up - highlightEntry = xOffset + yOffset * numPaletteColumns; - } - - return true; - } - } - - // dispatch to base class for the rest of things - return LLFloater::handleHover ( x, y, mask ); -} - -////////////////////////////////////////////////////////////////////////////// -// reverts state once mouse button is released -bool LLFloaterColorPicker::handleMouseUp ( S32 x, S32 y, MASK mask ) -{ - getWindow()->setCursor ( UI_CURSOR_ARROW ); - - if (getMouseDownInHueRegion() || getMouseDownInLumRegion()) - { - if (mApplyImmediateCheck->get()) - { - LLColorSwatchCtrl::onColorChanged ( getSwatch (), LLColorSwatchCtrl::COLOR_CHANGE ); - } - } - - // rect containing palette area - LLRect paletteRect ( mPaletteRegionLeft, - mPaletteRegionTop, - mPaletteRegionLeft + mPaletteRegionWidth, - mPaletteRegionTop - mPaletteRegionHeight ); - - if ( paletteRect.pointInRect ( x, y ) ) - { - if ( mMouseDownInSwatch ) - { - S32 curEntry = 0; - for ( S32 row = 0; row < numPaletteRows; ++row ) - { - for ( S32 column = 0; column < numPaletteColumns; ++column ) - { - S32 left = mPaletteRegionLeft + ( mPaletteRegionWidth * column ) / numPaletteColumns; - S32 top = mPaletteRegionTop - ( mPaletteRegionHeight * row ) / numPaletteRows; - S32 right = ( mPaletteRegionLeft + ( mPaletteRegionWidth * ( column + 1 ) ) / numPaletteColumns ); - S32 bottom = ( mPaletteRegionTop - ( mPaletteRegionHeight * ( row + 1 ) ) / numPaletteRows ); - - // rect is flipped vertically when testing here - LLRect dropRect ( left, top, right, bottom ); - - if ( dropRect.pointInRect ( x, y ) ) - { - if ( mPalette [ curEntry ] ) - { - delete mPalette [ curEntry ]; - - mPalette [ curEntry ] = new LLColor4 ( getCurR (), getCurG (), getCurB (), 1.0f ); - - // save off color - std::ostringstream codec; - codec << "ColorPaletteEntry" << std::setfill ( '0' ) << std::setw ( 2 ) << curEntry + 1; - const std::string s ( codec.str () ); - LLUIColorTable::instance().setColor(s, *mPalette [ curEntry ] ); - } - } - - ++curEntry; - } - } - } - } - - // mouse button not down anymore - setMouseDownInHueRegion ( false ); - setMouseDownInLumRegion ( false ); - - // mouse button not down in color swatch anymore - mMouseDownInSwatch = false; - - if (hasMouseCapture()) - { - gFocusMgr.setMouseCapture(NULL); - } - - // dispatch to base class for the rest of things - return LLFloater::handleMouseUp ( x, y, mask ); -} - -////////////////////////////////////////////////////////////////////////////// -// cancel current color selection, revert to original and close picker -void LLFloaterColorPicker::cancelSelection () -{ - // restore the previous color selection - setCurRgb ( getOrigR (), getOrigG (), getOrigB () ); - - // update in world item with original color via current swatch - LLColorSwatchCtrl::onColorChanged( getSwatch(), LLColorSwatchCtrl::COLOR_CANCEL ); - - // hide picker dialog - this->setVisible ( false ); -} - -void LLFloaterColorPicker::setMouseDownInHueRegion ( bool mouse_down_in_region ) -{ - mMouseDownInHueRegion = mouse_down_in_region; - if (mouse_down_in_region) - { - if (gFocusMgr.childHasKeyboardFocus(this)) - { - // get focus out of spinners so that they can update freely - mSelectBtn->setFocus(true); - } - } -} - -void LLFloaterColorPicker::setMouseDownInLumRegion ( bool mouse_down_in_region ) -{ - mMouseDownInLumRegion = mouse_down_in_region; - if (mouse_down_in_region) - { - if (gFocusMgr.childHasKeyboardFocus(this)) - { - // get focus out of spinners so that they can update freely - mSelectBtn->setFocus(true); - } - } -} - -void LLFloaterColorPicker::setMouseDownInSwatch (bool mouse_down_in_swatch) -{ - mMouseDownInSwatch = mouse_down_in_swatch; - if (mouse_down_in_swatch) - { - if (gFocusMgr.childHasKeyboardFocus(this)) - { - // get focus out of spinners so that they can update freely - mSelectBtn->setFocus(true); - } - } -} - -void LLFloaterColorPicker::setActive(bool active) -{ - // shut down pipette tool if active - if (!active && mPipetteBtn->getToggleState()) - { - stopUsingPipette(); - } - mActive = active; -} - -void LLFloaterColorPicker::stopUsingPipette() -{ - if (LLToolMgr::getInstance()->getCurrentTool() == LLToolPipette::getInstance()) - { - LLToolMgr::getInstance()->clearTransientTool(); - } -} +/** + * @file llfloatercolorpicker.cpp + * @brief Generic system color picker + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatercolorpicker.h" + +// Viewer project includes +#include "lltoolmgr.h" +#include "lltoolpipette.h" +#include "llviewercontrol.h" +#include "llworld.h" + +// Linden library includes +#include "llfontgl.h" +#include "llsys.h" +#include "llgl.h" +#include "llrender.h" +#include "v3dmath.h" +#include "lldir.h" +#include "llui.h" +#include "lllineeditor.h" +#include "v4coloru.h" +#include "llbutton.h" +#include "lluictrlfactory.h" +#include "llgl.h" +#include "llpointer.h" +#include "llimage.h" +#include "llmousehandler.h" +#include "llglheaders.h" +#include "llcheckboxctrl.h" +#include "lltextbox.h" +#include "lluiconstants.h" +#include "llfocusmgr.h" +#include "lldraghandle.h" +#include "llwindow.h" + +// System includes +#include +#include + +////////////////////////////////////////////////////////////////////////////// +// +// Class LLFloaterColorPicker +// +////////////////////////////////////////////////////////////////////////////// + +LLFloaterColorPicker::LLFloaterColorPicker (LLColorSwatchCtrl* swatch, bool show_apply_immediate ) + : LLFloater(LLSD()), + mComponents ( 3 ), + mMouseDownInLumRegion ( false ), + mMouseDownInHueRegion ( false ), + mMouseDownInSwatch ( false ), + // *TODO: Specify this in XML + mRGBViewerImageLeft ( 140 ), + mRGBViewerImageTop ( 356 ), + mRGBViewerImageWidth ( 256 ), + mRGBViewerImageHeight ( 256 ), + mLumRegionLeft ( mRGBViewerImageLeft + mRGBViewerImageWidth + 16 ), + mLumRegionTop ( mRGBViewerImageTop ), + mLumRegionWidth ( 16 ), + mLumRegionHeight ( mRGBViewerImageHeight ), + mLumMarkerSize ( 6 ), + // *TODO: Specify this in XML + mSwatchRegionLeft ( 12 ), + mSwatchRegionTop ( 190 ), + mSwatchRegionWidth ( 116 ), + mSwatchRegionHeight ( 60 ), + mSwatchView ( NULL ), + // *TODO: Specify this in XML + numPaletteColumns ( 16 ), + numPaletteRows ( 2 ), + highlightEntry ( -1 ), + mPaletteRegionLeft ( 11 ), + mPaletteRegionTop ( 100 - 8 ), + mPaletteRegionWidth ( mLumRegionLeft + mLumRegionWidth - 10 ), + mPaletteRegionHeight ( 40 ), + mSwatch ( swatch ), + mActive ( true ), + mCanApplyImmediately ( show_apply_immediate ), + mContextConeOpacity ( 0.f ), + mContextConeInAlpha (CONTEXT_CONE_IN_ALPHA), + mContextConeOutAlpha (CONTEXT_CONE_OUT_ALPHA), + mContextConeFadeTime (CONTEXT_CONE_FADE_TIME) +{ + buildFromFile ( "floater_color_picker.xml"); + + // create user interface for this picker + createUI (); + + if (!mCanApplyImmediately) + { + mApplyImmediateCheck->setEnabled(false); + mApplyImmediateCheck->set(false); + } +} + +LLFloaterColorPicker::~LLFloaterColorPicker() +{ + // destroy the UI we created + destroyUI (); +} + +////////////////////////////////////////////////////////////////////////////// +// +void LLFloaterColorPicker::createUI () +{ + // create RGB type area (not really RGB but it's got R,G & B in it.,.. + + LLPointer raw = new LLImageRaw ( mRGBViewerImageWidth, mRGBViewerImageHeight, mComponents ); + LLImageDataLock lock(raw); + + U8* bits = raw->getData(); + S32 linesize = mRGBViewerImageWidth * mComponents; + for ( S32 y = 0; y < mRGBViewerImageHeight; ++y ) + { + for ( S32 x = 0; x < linesize; x += mComponents ) + { + F32 rVal, gVal, bVal; + + hslToRgb ( (F32)x / (F32) ( linesize - 1 ), + (F32)y / (F32) ( mRGBViewerImageHeight - 1 ), + 0.5f, + rVal, + gVal, + bVal ); + + * ( bits + x + y * linesize + 0 ) = ( U8 )( rVal * 255.0f ); + * ( bits + x + y * linesize + 1 ) = ( U8 )( gVal * 255.0f ); + * ( bits + x + y * linesize + 2 ) = ( U8 )( bVal * 255.0f ); + } + } + mRGBImage = LLViewerTextureManager::getLocalTexture( (LLImageRaw*)raw, false ); + gGL.getTexUnit(0)->bind(mRGBImage); + mRGBImage->setAddressMode(LLTexUnit::TAM_CLAMP); + + // create palette + for ( S32 each = 0; each < numPaletteColumns * numPaletteRows; ++each ) + { + mPalette.push_back(new LLColor4(LLUIColorTable::instance().getColor(llformat("ColorPaletteEntry%02d", each + 1)))); + } +} + +////////////////////////////////////////////////////////////////////////////// +// +void LLFloaterColorPicker::showUI () +{ + openFloater(getKey()); + setVisible ( true ); + setFocus ( true ); + + // HACK: if system color picker is required - close the SL one we made and use default system dialog + if ( gSavedSettings.getBOOL ( "UseDefaultColorPicker" ) ) + { + LLColorSwatchCtrl* swatch = getSwatch (); + + setVisible ( false ); + + // code that will get switched in for default system color picker + if ( swatch ) + { + // Todo: this needs to be threaded for viewer not to timeout + LLColor4 curCol = swatch->get (); + send_agent_pause(); + bool commit = getWindow()->dialogColorPicker( &curCol [ 0 ], &curCol [ 1 ], &curCol [ 2 ] ); + send_agent_resume(); + + if (commit) + { + setOrigRgb(curCol[0], curCol[1], curCol[2]); + setCurRgb(curCol[0], curCol[1], curCol[2]); + + LLColorSwatchCtrl::onColorChanged(swatch, LLColorSwatchCtrl::COLOR_SELECT); + } + else + { + LLColorSwatchCtrl::onColorChanged(swatch, LLColorSwatchCtrl::COLOR_CANCEL); + } + } + + closeFloater(); + } +} + +////////////////////////////////////////////////////////////////////////////// +// called after the dialog is rendered +bool LLFloaterColorPicker::postBuild() +{ + mCancelBtn = getChild( "cancel_btn" ); + mCancelBtn->setClickedCallback ( onClickCancel, this ); + + mSelectBtn = getChild( "select_btn"); + mSelectBtn->setClickedCallback ( onClickSelect, this ); + mSelectBtn->setFocus ( true ); + + mPipetteBtn = getChild("color_pipette" ); + + mPipetteBtn->setImages(std::string("eye_button_inactive.tga"), std::string("eye_button_active.tga")); + + mPipetteBtn->setCommitCallback( boost::bind(&LLFloaterColorPicker::onClickPipette, this )); + + mApplyImmediateCheck = getChild("apply_immediate"); + mApplyImmediateCheck->set(gSavedSettings.getBOOL("ApplyColorImmediately")); + mApplyImmediateCheck->setCommitCallback(onImmediateCheck, this); + + childSetCommitCallback("rspin", onTextCommit, (void*)this ); + childSetCommitCallback("gspin", onTextCommit, (void*)this ); + childSetCommitCallback("bspin", onTextCommit, (void*)this ); + childSetCommitCallback("hspin", onTextCommit, (void*)this ); + childSetCommitCallback("sspin", onTextCommit, (void*)this ); + childSetCommitCallback("lspin", onTextCommit, (void*)this ); + + LLToolPipette::getInstance()->setToolSelectCallback(boost::bind(&LLFloaterColorPicker::onColorSelect, this, _1)); + + return true; +} + +////////////////////////////////////////////////////////////////////////////// +// +void LLFloaterColorPicker::initUI ( F32 rValIn, F32 gValIn, F32 bValIn ) +{ + // under some circumstances, we get rogue values that can be calmed by clamping... + rValIn = llclamp ( rValIn, 0.0f, 1.0f ); + gValIn = llclamp ( gValIn, 0.0f, 1.0f ); + bValIn = llclamp ( bValIn, 0.0f, 1.0f ); + + // store initial value in case cancel or revert is selected + setOrigRgb ( rValIn, gValIn, bValIn ); + + // starting point for current value to + setCurRgb ( rValIn, gValIn, bValIn ); + + // unpdate text entry fields + updateTextEntry (); +} + +////////////////////////////////////////////////////////////////////////////// +// +void LLFloaterColorPicker::destroyUI () +{ + // shut down pipette tool if active + stopUsingPipette(); + + // delete palette we created + std::vector < LLColor4* >::iterator iter = mPalette.begin (); + while ( iter != mPalette.end () ) + { + delete ( *iter ); + ++iter; + } + + if ( mSwatchView ) + { + this->removeChild ( mSwatchView ); + mSwatchView->die();; + mSwatchView = NULL; + } +} + + +////////////////////////////////////////////////////////////////////////////// +// +F32 LLFloaterColorPicker::hueToRgb ( F32 val1In, F32 val2In, F32 valHUeIn ) +{ + if ( valHUeIn < 0.0f ) valHUeIn += 1.0f; + if ( valHUeIn > 1.0f ) valHUeIn -= 1.0f; + if ( ( 6.0f * valHUeIn ) < 1.0f ) return ( val1In + ( val2In - val1In ) * 6.0f * valHUeIn ); + if ( ( 2.0f * valHUeIn ) < 1.0f ) return ( val2In ); + if ( ( 3.0f * valHUeIn ) < 2.0f ) return ( val1In + ( val2In - val1In ) * ( ( 2.0f / 3.0f ) - valHUeIn ) * 6.0f ); + return ( val1In ); +} + +////////////////////////////////////////////////////////////////////////////// +// +void LLFloaterColorPicker::hslToRgb ( F32 hValIn, F32 sValIn, F32 lValIn, F32& rValOut, F32& gValOut, F32& bValOut ) +{ + if ( sValIn < 0.00001f ) + { + rValOut = lValIn; + gValOut = lValIn; + bValOut = lValIn; + } + else + { + F32 interVal1; + F32 interVal2; + + if ( lValIn < 0.5f ) + interVal2 = lValIn * ( 1.0f + sValIn ); + else + interVal2 = ( lValIn + sValIn ) - ( sValIn * lValIn ); + + interVal1 = 2.0f * lValIn - interVal2; + + rValOut = hueToRgb ( interVal1, interVal2, hValIn + ( 1.f / 3.f ) ); + gValOut = hueToRgb ( interVal1, interVal2, hValIn ); + bValOut = hueToRgb ( interVal1, interVal2, hValIn - ( 1.f / 3.f ) ); + } +} + +////////////////////////////////////////////////////////////////////////////// +// mutator for original RGB value +void LLFloaterColorPicker::setOrigRgb ( F32 origRIn, F32 origGIn, F32 origBIn ) +{ + origR = origRIn; + origG = origGIn; + origB = origBIn; +} + +////////////////////////////////////////////////////////////////////////////// +// accessor for original RGB value +void LLFloaterColorPicker::getOrigRgb ( F32& origROut, F32& origGOut, F32& origBOut ) +{ + origROut = origR; + origGOut = origG; + origBOut = origB; +} + +////////////////////////////////////////////////////////////////////////////// +// mutator for current RGB value +void LLFloaterColorPicker::setCurRgb ( F32 curRIn, F32 curGIn, F32 curBIn ) +{ + // save current RGB + curR = curRIn; + curG = curGIn; + curB = curBIn; + + // update corresponding HSL values and + LLColor3(curRIn, curGIn, curBIn).calcHSL(&curH, &curS, &curL); + + // color changed so update text fields + updateTextEntry(); +} + +////////////////////////////////////////////////////////////////////////////// +// accessor for current RGB value +void LLFloaterColorPicker::getCurRgb ( F32& curROut, F32& curGOut, F32& curBOut ) +{ + curROut = curR; + curGOut = curG; + curBOut = curB; +} + +////////////////////////////////////////////////////////////////////////////// +// mutator for current HSL value +void LLFloaterColorPicker::setCurHsl ( F32 curHIn, F32 curSIn, F32 curLIn ) +{ + // save current HSL + curH = curHIn; + curS = curSIn; + curL = curLIn; + + // update corresponding RGB values and + hslToRgb ( curH, curS, curL, curR, curG, curB ); +} + +////////////////////////////////////////////////////////////////////////////// +// accessor for current HSL value +void LLFloaterColorPicker::getCurHsl ( F32& curHOut, F32& curSOut, F32& curLOut ) +{ + curHOut = curH; + curSOut = curS; + curLOut = curL; +} + +////////////////////////////////////////////////////////////////////////////// +// called when 'cancel' clicked +void LLFloaterColorPicker::onClickCancel ( void* data ) +{ + if (data) + { + LLFloaterColorPicker* self = ( LLFloaterColorPicker* )data; + + if ( self ) + { + self->cancelSelection(); + self->closeFloater(); + } + } +} + +////////////////////////////////////////////////////////////////////////////// +// called when 'select' clicked +void LLFloaterColorPicker::onClickSelect ( void* data ) +{ + if (data) + { + LLFloaterColorPicker* self = ( LLFloaterColorPicker* )data; + + if ( self ) + { + // apply to selection + LLColorSwatchCtrl::onColorChanged ( self->getSwatch (), LLColorSwatchCtrl::COLOR_SELECT ); + self->closeFloater(); + } + } +} + +void LLFloaterColorPicker::onClickPipette( ) +{ + bool pipette_active = mPipetteBtn->getToggleState(); + pipette_active = !pipette_active; + if (pipette_active) + { + LLToolMgr::getInstance()->setTransientTool(LLToolPipette::getInstance()); + } + else + { + LLToolMgr::getInstance()->clearTransientTool(); + } +} + +////////////////////////////////////////////////////////////////////////////// +// called when 'text is committed' - i,e. focus moves from a text field +void LLFloaterColorPicker::onTextCommit ( LLUICtrl* ctrl, void* data ) +{ + if ( data ) + { + LLFloaterColorPicker* self = ( LLFloaterColorPicker* )data; + if ( self ) + { + self->onTextEntryChanged ( ctrl ); + } + } +} + +void LLFloaterColorPicker::onImmediateCheck( LLUICtrl* ctrl, void* data) +{ + LLFloaterColorPicker* self = ( LLFloaterColorPicker* )data; + if (self) + { + gSavedSettings.setBOOL("ApplyColorImmediately", self->mApplyImmediateCheck->get()); + if (self->mApplyImmediateCheck->get() && self->isColorChanged()) + { + LLColorSwatchCtrl::onColorChanged ( self->getSwatch (), LLColorSwatchCtrl::COLOR_CHANGE ); + } + } +} + +void LLFloaterColorPicker::onColorSelect( const LLTextureEntry& te ) +{ + // Pipete + selectCurRgb(te.getColor().mV[VRED], te.getColor().mV[VGREEN], te.getColor().mV[VBLUE]); +} + +void LLFloaterColorPicker::onMouseCaptureLost() +{ + setMouseDownInHueRegion(false); + setMouseDownInLumRegion(false); +} + +F32 LLFloaterColorPicker::getSwatchTransparency() +{ + // If the floater is focused, don't apply its alpha to the color swatch (STORM-676). + return getTransparencyType() == TT_ACTIVE ? 1.f : LLFloater::getCurrentTransparency(); +} + +bool LLFloaterColorPicker::isColorChanged() +{ + return ((getOrigR() != getCurR()) || (getOrigG() != getCurG()) || (getOrigB() != getCurB())); +} + +////////////////////////////////////////////////////////////////////////////// +// +void LLFloaterColorPicker::draw() +{ + static LLCachedControl max_opacity(gSavedSettings, "PickerContextOpacity", 0.4f); + drawConeToOwner(mContextConeOpacity, max_opacity, mSwatch, mContextConeFadeTime, mContextConeInAlpha, mContextConeOutAlpha); + + mPipetteBtn->setToggleState(LLToolMgr::getInstance()->getCurrentTool() == LLToolPipette::getInstance()); + mApplyImmediateCheck->setEnabled(mActive && mCanApplyImmediately); + mSelectBtn->setEnabled(mActive); + + // base floater stuff + LLFloater::draw (); + + const F32 alpha = getSwatchTransparency(); + + // draw image for RGB area (not really RGB but you'll see what I mean... + gl_draw_image ( mRGBViewerImageLeft, mRGBViewerImageTop - mRGBViewerImageHeight, mRGBImage, LLColor4::white % alpha); + + // update 'cursor' into RGB Section + S32 xPos = ( S32 ) ( ( F32 )mRGBViewerImageWidth * getCurH () ) - 8; + S32 yPos = ( S32 ) ( ( F32 )mRGBViewerImageHeight * getCurS () ) - 8; + gl_line_2d ( mRGBViewerImageLeft + xPos, + mRGBViewerImageTop - mRGBViewerImageHeight + yPos + 8, + mRGBViewerImageLeft + xPos + 16, + mRGBViewerImageTop - mRGBViewerImageHeight + yPos + 8, + LLColor4 ( 0.0f, 0.0f, 0.0f, 1.0f ) ); + + gl_line_2d ( mRGBViewerImageLeft + xPos + 8, + mRGBViewerImageTop - mRGBViewerImageHeight + yPos, + mRGBViewerImageLeft + xPos + 8, + mRGBViewerImageTop - mRGBViewerImageHeight + yPos + 16, + LLColor4 ( 0.0f, 0.0f, 0.0f, 1.0f ) ); + + // create rgb area outline + gl_rect_2d ( mRGBViewerImageLeft, + mRGBViewerImageTop - mRGBViewerImageHeight, + mRGBViewerImageLeft + mRGBViewerImageWidth + 1, + mRGBViewerImageTop, + LLColor4 ( 0.0f, 0.0f, 0.0f, alpha ), + false ); + + // draw luminance slider + for ( S32 y = 0; y < mLumRegionHeight; ++y ) + { + F32 rValSlider, gValSlider, bValSlider; + hslToRgb ( getCurH (), getCurS (), ( F32 )y / ( F32 )mLumRegionHeight, rValSlider, gValSlider, bValSlider ); + + gl_rect_2d( mLumRegionLeft, + mLumRegionTop - mLumRegionHeight + y, + mLumRegionLeft + mLumRegionWidth, + mLumRegionTop - mLumRegionHeight + y - 1, + LLColor4 ( rValSlider, gValSlider, bValSlider, alpha ) ); + } + + + // draw luninance marker + S32 startX = mLumRegionLeft + mLumRegionWidth; + S32 startY = mLumRegionTop - mLumRegionHeight + ( S32 ) ( mLumRegionHeight * getCurL () ); + gl_triangle_2d ( startX, startY, + startX + mLumMarkerSize, startY - mLumMarkerSize, + startX + mLumMarkerSize, startY + mLumMarkerSize, + LLColor4 ( 0.75f, 0.75f, 0.75f, 1.0f ), true ); + + // draw luminance slider outline + gl_rect_2d ( mLumRegionLeft, + mLumRegionTop - mLumRegionHeight, + mLumRegionLeft + mLumRegionWidth + 1, + mLumRegionTop, + LLColor4 ( 0.0f, 0.0f, 0.0f, 1.0f ), + false ); + + // draw selected color swatch + gl_rect_2d ( mSwatchRegionLeft, + mSwatchRegionTop - mSwatchRegionHeight, + mSwatchRegionLeft + mSwatchRegionWidth, + mSwatchRegionTop, + LLColor4 ( getCurR (), getCurG (), getCurB (), alpha ), + true ); + + // draw selected color swatch outline + gl_rect_2d ( mSwatchRegionLeft, + mSwatchRegionTop - mSwatchRegionHeight, + mSwatchRegionLeft + mSwatchRegionWidth + 1, + mSwatchRegionTop, + LLColor4 ( 0.0f, 0.0f, 0.0f, 1.0f ), + false ); + + // color palette code is a little more involved so break it out into its' own method + drawPalette (); +} + +////////////////////////////////////////////////////////////////////////////// +// find a complimentary color to the one passed in that can be used to highlight +const LLColor4& LLFloaterColorPicker::getComplimentaryColor ( const LLColor4& backgroundColor ) +{ + // going to base calculation on luminance + F32 hVal, sVal, lVal; + backgroundColor.calcHSL(&hVal, &sVal, &lVal); + hVal *= 360.f; + sVal *= 100.f; + lVal *= 100.f; + + // fairly simple heuristic for now...! + if ( lVal < 0.5f ) + { + return LLColor4::white; + } + + return LLColor4::black; +} + +////////////////////////////////////////////////////////////////////////////// +// set current RGB and rise change event if needed. +void LLFloaterColorPicker::selectCurRgb ( F32 curRIn, F32 curGIn, F32 curBIn ) +{ + setCurRgb(curRIn, curGIn, curBIn); + if (mApplyImmediateCheck->get()) + { + LLColorSwatchCtrl::onColorChanged ( getSwatch (), LLColorSwatchCtrl::COLOR_CHANGE ); + } +} + +////////////////////////////////////////////////////////////////////////////// +// set current HSL and rise change event if needed. +void LLFloaterColorPicker::selectCurHsl ( F32 curHIn, F32 curSIn, F32 curLIn ) +{ + setCurHsl(curHIn, curSIn, curLIn); + if (mApplyImmediateCheck->get()) + { + LLColorSwatchCtrl::onColorChanged ( getSwatch (), LLColorSwatchCtrl::COLOR_CHANGE ); + } +} + +////////////////////////////////////////////////////////////////////////////// +// draw color palette +void LLFloaterColorPicker::drawPalette () +{ + S32 curEntry = 0; + const F32 alpha = getSwatchTransparency(); + + for ( S32 y = 0; y < numPaletteRows; ++y ) + { + for ( S32 x = 0; x < numPaletteColumns; ++x ) + { + // calculate position + S32 x1 = mPaletteRegionLeft + ( mPaletteRegionWidth * x ) / numPaletteColumns; + S32 y1 = mPaletteRegionTop - ( mPaletteRegionHeight * y ) / numPaletteRows; + S32 x2 = ( mPaletteRegionLeft + ( mPaletteRegionWidth * ( x + 1 ) ) / numPaletteColumns ); + S32 y2 = ( mPaletteRegionTop - ( mPaletteRegionHeight * ( y + 1 ) ) / numPaletteRows ); + + // draw palette entry color + if ( mPalette [ curEntry ] ) + { + gl_rect_2d ( x1 + 2, y1 - 2, x2 - 2, y2 + 2, *mPalette [ curEntry++ ] % alpha, true ); + gl_rect_2d ( x1 + 1, y1 - 1, x2 - 1, y2 + 1, LLColor4 ( 0.0f, 0.0f, 0.0f, 1.0f ), false ); + } + } + } + + // if there is something to highlight (mouse down in swatch & hovering over palette) + if ( highlightEntry >= 0 ) + { + // extract row/column from palette index + S32 entryColumn = highlightEntry % numPaletteColumns; + S32 entryRow = highlightEntry / numPaletteColumns; + + // calculate position of this entry + S32 x1 = mPaletteRegionLeft + ( mPaletteRegionWidth * entryColumn ) / numPaletteColumns; + S32 y1 = mPaletteRegionTop - ( mPaletteRegionHeight * entryRow ) / numPaletteRows; + S32 x2 = ( mPaletteRegionLeft + ( mPaletteRegionWidth * ( entryColumn + 1 ) ) / numPaletteColumns ); + S32 y2 = ( mPaletteRegionTop - ( mPaletteRegionHeight * ( entryRow + 1 ) ) / numPaletteRows ); + + // center position of entry + S32 xCenter = x1 + ( x2 - x1 ) / 2; + S32 yCenter = y1 - ( y1 - y2 ) / 2; + + // find a color that works well as a highlight color + LLColor4 hlColor ( getComplimentaryColor ( *mPalette [ highlightEntry ] ) ); + + // mark a cross for entry that is being hovered + gl_line_2d ( xCenter - 4, yCenter - 4, xCenter + 4, yCenter + 4, hlColor ); + gl_line_2d ( xCenter + 4, yCenter - 4, xCenter - 4, yCenter + 4, hlColor ); + } +} + +////////////////////////////////////////////////////////////////////////////// +// update text entry values for RGB/HSL (can't be done in ::draw () since this overwrites input +void LLFloaterColorPicker::updateTextEntry () +{ + // set values in spinners + getChild("rspin")->setValue(( getCurR () * 255.0f ) ); + getChild("gspin")->setValue(( getCurG () * 255.0f ) ); + getChild("bspin")->setValue(( getCurB () * 255.0f ) ); + getChild("hspin")->setValue(( getCurH () * 360.0f ) ); + getChild("sspin")->setValue(( getCurS () * 100.0f ) ); + getChild("lspin")->setValue(( getCurL () * 100.0f ) ); +} + +////////////////////////////////////////////////////////////////////////////// +// +void LLFloaterColorPicker::onTextEntryChanged ( LLUICtrl* ctrl ) +{ + // value in RGB boxes changed + std::string name = ctrl->getName(); + if ( ( name == "rspin" ) || ( name == "gspin" ) || ( name == "bspin" ) ) + { + // get current RGB + F32 rVal, gVal, bVal; + getCurRgb ( rVal, gVal, bVal ); + + // update component value with new value from text + if ( name == "rspin" ) + { + rVal = (F32)ctrl->getValue().asReal() / 255.0f; + } + else + if ( name == "gspin" ) + { + gVal = (F32)ctrl->getValue().asReal() / 255.0f; + } + else + if ( name == "bspin" ) + { + bVal = (F32)ctrl->getValue().asReal() / 255.0f; + } + + // update current RGB (and implicitly HSL) + selectCurRgb ( rVal, gVal, bVal ); + + updateTextEntry (); + } + else + // value in HSL boxes changed + if ( ( name == "hspin" ) || ( name == "sspin" ) || ( name == "lspin" ) ) + { + // get current HSL + F32 hVal, sVal, lVal; + getCurHsl ( hVal, sVal, lVal ); + + // update component value with new value from text + if ( name == "hspin" ) + hVal = (F32)ctrl->getValue().asReal() / 360.0f; + else + if ( name == "sspin" ) + sVal = (F32)ctrl->getValue().asReal() / 100.0f; + else + if ( name == "lspin" ) + lVal = (F32)ctrl->getValue().asReal() / 100.0f; + + // update current HSL (and implicitly RGB) + selectCurHsl ( hVal, sVal, lVal ); + + updateTextEntry (); + } +} + +////////////////////////////////////////////////////////////////////////////// +// +bool LLFloaterColorPicker::updateRgbHslFromPoint ( S32 xPosIn, S32 yPosIn ) +{ + if ( xPosIn >= mRGBViewerImageLeft && + xPosIn <= mRGBViewerImageLeft + mRGBViewerImageWidth && + yPosIn <= mRGBViewerImageTop && + yPosIn >= mRGBViewerImageTop - mRGBViewerImageHeight ) + { + // update HSL (and therefore RGB) based on new H & S and current L + selectCurHsl ( ( ( F32 )xPosIn - ( F32 )mRGBViewerImageLeft ) / ( F32 )mRGBViewerImageWidth, + ( ( F32 )yPosIn - ( ( F32 )mRGBViewerImageTop - ( F32 )mRGBViewerImageHeight ) ) / ( F32 )mRGBViewerImageHeight, + getCurL () ); + + // indicate a value changed + return true; + } + else + if ( xPosIn >= mLumRegionLeft && + xPosIn <= mLumRegionLeft + mLumRegionWidth && + yPosIn <= mLumRegionTop && + yPosIn >= mLumRegionTop - mLumRegionHeight ) + { + + // update HSL (and therefore RGB) based on current HS and new L + selectCurHsl ( getCurH (), + getCurS (), + ( ( F32 )yPosIn - ( ( F32 )mRGBViewerImageTop - ( F32 )mRGBViewerImageHeight ) ) / ( F32 )mRGBViewerImageHeight ); + + // indicate a value changed + return true; + } + + return false; +} + +////////////////////////////////////////////////////////////////////////////// +// +bool LLFloaterColorPicker::handleMouseDown ( S32 x, S32 y, MASK mask ) +{ + // make it the frontmost + gFloaterView->bringToFront(this); + + // rect containing RGB area + LLRect rgbAreaRect ( mRGBViewerImageLeft, + mRGBViewerImageTop, + mRGBViewerImageLeft + mRGBViewerImageWidth, + mRGBViewerImageTop - mRGBViewerImageHeight ); + + if ( rgbAreaRect.pointInRect ( x, y ) ) + { + gFocusMgr.setMouseCapture(this); + // mouse button down + setMouseDownInHueRegion ( true ); + + // update all values based on initial click + updateRgbHslFromPoint ( x, y ); + + // required by base class + return true; + } + + // rect containing RGB area + LLRect lumAreaRect ( mLumRegionLeft, + mLumRegionTop, + mLumRegionLeft + mLumRegionWidth + mLumMarkerSize, + mLumRegionTop - mLumRegionHeight ); + + if ( lumAreaRect.pointInRect ( x, y ) ) + { + gFocusMgr.setMouseCapture(this); + // mouse button down + setMouseDownInLumRegion ( true ); + + // required by base class + return true; + } + + // rect containing swatch area + LLRect swatchRect ( mSwatchRegionLeft, + mSwatchRegionTop, + mSwatchRegionLeft + mSwatchRegionWidth, + mSwatchRegionTop - mSwatchRegionHeight ); + + setMouseDownInSwatch( false ); + if ( swatchRect.pointInRect ( x, y ) ) + { + setMouseDownInSwatch( true ); + + // required - dont drag windows here. + return true; + } + + // rect containing palette area + LLRect paletteRect ( mPaletteRegionLeft, + mPaletteRegionTop, + mPaletteRegionLeft + mPaletteRegionWidth, + mPaletteRegionTop - mPaletteRegionHeight ); + + if ( paletteRect.pointInRect ( x, y ) ) + { + // release keyboard focus so we can change text values + if (gFocusMgr.childHasKeyboardFocus(this)) + { + mSelectBtn->setFocus(true); + } + + // calculate which palette index we selected + S32 c = ( ( x - mPaletteRegionLeft ) * numPaletteColumns ) / mPaletteRegionWidth; + S32 r = ( ( y - ( mPaletteRegionTop - mPaletteRegionHeight ) ) * numPaletteRows ) / mPaletteRegionHeight; + + U32 index = ( numPaletteRows - r - 1 ) * numPaletteColumns + c; + + if ( index <= mPalette.size () ) + { + LLColor4 selected = *mPalette [ index ]; + + selectCurRgb ( selected [ 0 ], selected [ 1 ], selected [ 2 ] ); + + if (mApplyImmediateCheck->get()) + { + LLColorSwatchCtrl::onColorChanged ( getSwatch (), LLColorSwatchCtrl::COLOR_CHANGE ); + } + + updateTextEntry (); + } + + return true; + } + + // dispatch to base class for the rest of things + + return LLFloater::handleMouseDown ( x, y, mask ); +} + +////////////////////////////////////////////////////////////////////////////// +// +bool LLFloaterColorPicker::handleHover ( S32 x, S32 y, MASK mask ) +{ + // if we're the front most window + if ( isFrontmost () ) + { + // mouse was pressed within region + if ( getMouseDownInHueRegion() || getMouseDownInLumRegion()) + { + S32 clamped_x, clamped_y; + if (getMouseDownInHueRegion()) + { + clamped_x = llclamp(x, mRGBViewerImageLeft, mRGBViewerImageLeft + mRGBViewerImageWidth); + clamped_y = llclamp(y, mRGBViewerImageTop - mRGBViewerImageHeight, mRGBViewerImageTop); + } + else + { + clamped_x = llclamp(x, mLumRegionLeft, mLumRegionLeft + mLumRegionWidth); + clamped_y = llclamp(y, mLumRegionTop - mLumRegionHeight, mLumRegionTop); + } + + // update the stored RGB/HSL values using the mouse position - returns true if RGB was updated + if ( updateRgbHslFromPoint ( clamped_x, clamped_y ) ) + { + // update text entry fields + updateTextEntry (); + + // RN: apparently changing color when dragging generates too much traffic and results in sporadic updates + //// commit changed color to swatch subject + //// REVIEW: this gets sent each time a color changes - is this okay ? + //if (mApplyImmediateCheck->get()) + //{ + // LLColorSwatchCtrl::onColorChanged ( getSwatch () ); + //} + } + } + + highlightEntry = -1; + + if ( mMouseDownInSwatch ) + { + getWindow()->setCursor ( UI_CURSOR_ARROWDRAG ); + + // if cursor if over a palette entry + LLRect paletteRect ( mPaletteRegionLeft, + mPaletteRegionTop, + mPaletteRegionLeft + mPaletteRegionWidth, + mPaletteRegionTop - mPaletteRegionHeight ); + + if ( paletteRect.pointInRect ( x, y ) ) + { + // find row/column in palette + S32 xOffset = ( ( x - mPaletteRegionLeft ) * numPaletteColumns ) / mPaletteRegionWidth; + S32 yOffset = ( ( mPaletteRegionTop - y - 1 ) * numPaletteRows ) / mPaletteRegionHeight; + + // calculate the entry 0..n-1 to highlight and set variable to next draw() picks it up + highlightEntry = xOffset + yOffset * numPaletteColumns; + } + + return true; + } + } + + // dispatch to base class for the rest of things + return LLFloater::handleHover ( x, y, mask ); +} + +////////////////////////////////////////////////////////////////////////////// +// reverts state once mouse button is released +bool LLFloaterColorPicker::handleMouseUp ( S32 x, S32 y, MASK mask ) +{ + getWindow()->setCursor ( UI_CURSOR_ARROW ); + + if (getMouseDownInHueRegion() || getMouseDownInLumRegion()) + { + if (mApplyImmediateCheck->get()) + { + LLColorSwatchCtrl::onColorChanged ( getSwatch (), LLColorSwatchCtrl::COLOR_CHANGE ); + } + } + + // rect containing palette area + LLRect paletteRect ( mPaletteRegionLeft, + mPaletteRegionTop, + mPaletteRegionLeft + mPaletteRegionWidth, + mPaletteRegionTop - mPaletteRegionHeight ); + + if ( paletteRect.pointInRect ( x, y ) ) + { + if ( mMouseDownInSwatch ) + { + S32 curEntry = 0; + for ( S32 row = 0; row < numPaletteRows; ++row ) + { + for ( S32 column = 0; column < numPaletteColumns; ++column ) + { + S32 left = mPaletteRegionLeft + ( mPaletteRegionWidth * column ) / numPaletteColumns; + S32 top = mPaletteRegionTop - ( mPaletteRegionHeight * row ) / numPaletteRows; + S32 right = ( mPaletteRegionLeft + ( mPaletteRegionWidth * ( column + 1 ) ) / numPaletteColumns ); + S32 bottom = ( mPaletteRegionTop - ( mPaletteRegionHeight * ( row + 1 ) ) / numPaletteRows ); + + // rect is flipped vertically when testing here + LLRect dropRect ( left, top, right, bottom ); + + if ( dropRect.pointInRect ( x, y ) ) + { + if ( mPalette [ curEntry ] ) + { + delete mPalette [ curEntry ]; + + mPalette [ curEntry ] = new LLColor4 ( getCurR (), getCurG (), getCurB (), 1.0f ); + + // save off color + std::ostringstream codec; + codec << "ColorPaletteEntry" << std::setfill ( '0' ) << std::setw ( 2 ) << curEntry + 1; + const std::string s ( codec.str () ); + LLUIColorTable::instance().setColor(s, *mPalette [ curEntry ] ); + } + } + + ++curEntry; + } + } + } + } + + // mouse button not down anymore + setMouseDownInHueRegion ( false ); + setMouseDownInLumRegion ( false ); + + // mouse button not down in color swatch anymore + mMouseDownInSwatch = false; + + if (hasMouseCapture()) + { + gFocusMgr.setMouseCapture(NULL); + } + + // dispatch to base class for the rest of things + return LLFloater::handleMouseUp ( x, y, mask ); +} + +////////////////////////////////////////////////////////////////////////////// +// cancel current color selection, revert to original and close picker +void LLFloaterColorPicker::cancelSelection () +{ + // restore the previous color selection + setCurRgb ( getOrigR (), getOrigG (), getOrigB () ); + + // update in world item with original color via current swatch + LLColorSwatchCtrl::onColorChanged( getSwatch(), LLColorSwatchCtrl::COLOR_CANCEL ); + + // hide picker dialog + this->setVisible ( false ); +} + +void LLFloaterColorPicker::setMouseDownInHueRegion ( bool mouse_down_in_region ) +{ + mMouseDownInHueRegion = mouse_down_in_region; + if (mouse_down_in_region) + { + if (gFocusMgr.childHasKeyboardFocus(this)) + { + // get focus out of spinners so that they can update freely + mSelectBtn->setFocus(true); + } + } +} + +void LLFloaterColorPicker::setMouseDownInLumRegion ( bool mouse_down_in_region ) +{ + mMouseDownInLumRegion = mouse_down_in_region; + if (mouse_down_in_region) + { + if (gFocusMgr.childHasKeyboardFocus(this)) + { + // get focus out of spinners so that they can update freely + mSelectBtn->setFocus(true); + } + } +} + +void LLFloaterColorPicker::setMouseDownInSwatch (bool mouse_down_in_swatch) +{ + mMouseDownInSwatch = mouse_down_in_swatch; + if (mouse_down_in_swatch) + { + if (gFocusMgr.childHasKeyboardFocus(this)) + { + // get focus out of spinners so that they can update freely + mSelectBtn->setFocus(true); + } + } +} + +void LLFloaterColorPicker::setActive(bool active) +{ + // shut down pipette tool if active + if (!active && mPipetteBtn->getToggleState()) + { + stopUsingPipette(); + } + mActive = active; +} + +void LLFloaterColorPicker::stopUsingPipette() +{ + if (LLToolMgr::getInstance()->getCurrentTool() == LLToolPipette::getInstance()) + { + LLToolMgr::getInstance()->clearTransientTool(); + } +} diff --git a/indra/newview/llfloatercolorpicker.h b/indra/newview/llfloatercolorpicker.h index af8033fd56..5c27fffd08 100644 --- a/indra/newview/llfloatercolorpicker.h +++ b/indra/newview/llfloatercolorpicker.h @@ -1,202 +1,202 @@ -/** - * @file llfloatercolorpicker.h - * @brief Generic system color picker - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERCOLORPICKER_H -#define LL_LLFLOATERCOLORPICKER_H - -#include - -#include "llfloater.h" -#include "llpointer.h" -#include "llcolorswatch.h" -#include "llspinctrl.h" - -class LLButton; -class LLLineEditor; -class LLCheckBoxCtrl; - -////////////////////////////////////////////////////////////////////////////// -// floater class -class LLFloaterColorPicker - : public LLFloater -{ - public: - LLFloaterColorPicker (LLColorSwatchCtrl* swatch, bool show_apply_immediate = false); - virtual ~LLFloaterColorPicker (); - - // overrides - virtual bool postBuild (); - virtual void draw (); - virtual bool handleMouseDown ( S32 x, S32 y, MASK mask ); - virtual bool handleMouseUp ( S32 x, S32 y, MASK mask ); - virtual bool handleHover ( S32 x, S32 y, MASK mask ); - virtual void onMouseCaptureLost(); - virtual F32 getSwatchTransparency(); - - // implicit methods - void createUI (); - void initUI ( F32 rValIn, F32 gValIn, F32 bValIn ); - void showUI (); - void destroyUI (); - void cancelSelection (); - LLColorSwatchCtrl* getSwatch () { return mSwatch; }; - void setSwatch( LLColorSwatchCtrl* swatch) { mSwatch = swatch; } - - // mutator / accessor for original RGB value - void setOrigRgb ( F32 origRIn, F32 origGIn, F32 origBIn ); - void getOrigRgb ( F32& origROut, F32& origGOut, F32& origBOut ); - F32 getOrigR () { return origR; }; - F32 getOrigG () { return origG; }; - F32 getOrigB () { return origB; }; - - // mutator / accessors for currernt RGB value - void setCurRgb ( F32 curRIn, F32 curGIn, F32 curBIn ); - void getCurRgb ( F32& curROut, F32& curGOut, F32& curBOut ); - F32 getCurR () { return curR; }; - F32 getCurG () { return curG; }; - F32 getCurB () { return curB; }; - - // mutator / accessors for currernt HSL value - void setCurHsl ( F32 curHIn, F32 curSIn, F32 curLIn ); - void getCurHsl ( F32& curHOut, F32& curSOut, F32& curLOut ); - F32 getCurH () { return curH; }; - F32 getCurS () { return curS; }; - F32 getCurL () { return curL; }; - - // updates current RGB/HSL values based on point in picker - bool updateRgbHslFromPoint ( S32 xPosIn, S32 yPosIn ); - - // updates text entry fields with current RGB/HSL - void updateTextEntry (); - - void stopUsingPipette(); - - // mutator / accessor for mouse button pressed in region - void setMouseDownInHueRegion ( bool mouse_down_in_region ); - bool getMouseDownInHueRegion () { return mMouseDownInHueRegion; }; - - void setMouseDownInLumRegion ( bool mouse_down_in_region ); - bool getMouseDownInLumRegion () { return mMouseDownInLumRegion; }; - - void setMouseDownInSwatch (bool mouse_down_in_swatch); - bool getMouseDownInSwatch () { return mMouseDownInSwatch; } - - bool isColorChanged (); - - // called when text entries (RGB/HSL etc.) are changed by user - void onTextEntryChanged ( LLUICtrl* ctrl ); - - // convert RGB to HSL and vice-versa - void hslToRgb ( F32 hValIn, F32 sValIn, F32 lValIn, F32& rValOut, F32& gValOut, F32& bValOut ); - F32 hueToRgb ( F32 val1In, F32 val2In, F32 valHUeIn ); - - void setActive(bool active); - - protected: - // callbacks - static void onClickCancel ( void* data ); - static void onClickSelect ( void* data ); - void onClickPipette ( ); - static void onTextCommit ( LLUICtrl* ctrl, void* data ); - static void onImmediateCheck ( LLUICtrl* ctrl, void* data ); - void onColorSelect( const class LLTextureEntry& te ); - private: - // mutators for color values, can raise event to preview changes at object - void selectCurRgb ( F32 curRIn, F32 curGIn, F32 curBIn ); - void selectCurHsl ( F32 curHIn, F32 curSIn, F32 curLIn ); - // draws color selection palette - void drawPalette (); - - // find a complimentary color to the one passed in that can be used to highlight - const LLColor4& getComplimentaryColor ( const LLColor4& backgroundColor ); - - // original RGB values - F32 origR, origG, origB; - - // current RGB/HSL values - F32 curR, curG, curB; - F32 curH, curS, curL; - - const S32 mComponents; - - bool mMouseDownInLumRegion; - bool mMouseDownInHueRegion; - bool mMouseDownInSwatch; - - const S32 mRGBViewerImageLeft; - const S32 mRGBViewerImageTop; - const S32 mRGBViewerImageWidth; - const S32 mRGBViewerImageHeight; - - const S32 mLumRegionLeft; - const S32 mLumRegionTop; - const S32 mLumRegionWidth; - const S32 mLumRegionHeight; - const S32 mLumMarkerSize; - - // Preview of the current color. - const S32 mSwatchRegionLeft; - const S32 mSwatchRegionTop; - const S32 mSwatchRegionWidth; - const S32 mSwatchRegionHeight; - - LLView* mSwatchView; - - const S32 numPaletteColumns; - const S32 numPaletteRows; - std::vector < LLColor4* > mPalette; - S32 highlightEntry; - const S32 mPaletteRegionLeft; - const S32 mPaletteRegionTop; - const S32 mPaletteRegionWidth; - const S32 mPaletteRegionHeight; - - // image used to compose color grid - LLPointer mRGBImage; - - // current swatch in use - LLColorSwatchCtrl* mSwatch; - - // are we actively tied to some output? - bool mActive; - - // enable/disable immediate updates - LLCheckBoxCtrl* mApplyImmediateCheck; - bool mCanApplyImmediately; - - LLButton* mSelectBtn; - LLButton* mCancelBtn; - - LLButton* mPipetteBtn; - - F32 mContextConeOpacity; - F32 mContextConeInAlpha; - F32 mContextConeOutAlpha; - F32 mContextConeFadeTime; - -}; - -#endif // LL_LLFLOATERCOLORPICKER_H +/** + * @file llfloatercolorpicker.h + * @brief Generic system color picker + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERCOLORPICKER_H +#define LL_LLFLOATERCOLORPICKER_H + +#include + +#include "llfloater.h" +#include "llpointer.h" +#include "llcolorswatch.h" +#include "llspinctrl.h" + +class LLButton; +class LLLineEditor; +class LLCheckBoxCtrl; + +////////////////////////////////////////////////////////////////////////////// +// floater class +class LLFloaterColorPicker + : public LLFloater +{ + public: + LLFloaterColorPicker (LLColorSwatchCtrl* swatch, bool show_apply_immediate = false); + virtual ~LLFloaterColorPicker (); + + // overrides + virtual bool postBuild (); + virtual void draw (); + virtual bool handleMouseDown ( S32 x, S32 y, MASK mask ); + virtual bool handleMouseUp ( S32 x, S32 y, MASK mask ); + virtual bool handleHover ( S32 x, S32 y, MASK mask ); + virtual void onMouseCaptureLost(); + virtual F32 getSwatchTransparency(); + + // implicit methods + void createUI (); + void initUI ( F32 rValIn, F32 gValIn, F32 bValIn ); + void showUI (); + void destroyUI (); + void cancelSelection (); + LLColorSwatchCtrl* getSwatch () { return mSwatch; }; + void setSwatch( LLColorSwatchCtrl* swatch) { mSwatch = swatch; } + + // mutator / accessor for original RGB value + void setOrigRgb ( F32 origRIn, F32 origGIn, F32 origBIn ); + void getOrigRgb ( F32& origROut, F32& origGOut, F32& origBOut ); + F32 getOrigR () { return origR; }; + F32 getOrigG () { return origG; }; + F32 getOrigB () { return origB; }; + + // mutator / accessors for currernt RGB value + void setCurRgb ( F32 curRIn, F32 curGIn, F32 curBIn ); + void getCurRgb ( F32& curROut, F32& curGOut, F32& curBOut ); + F32 getCurR () { return curR; }; + F32 getCurG () { return curG; }; + F32 getCurB () { return curB; }; + + // mutator / accessors for currernt HSL value + void setCurHsl ( F32 curHIn, F32 curSIn, F32 curLIn ); + void getCurHsl ( F32& curHOut, F32& curSOut, F32& curLOut ); + F32 getCurH () { return curH; }; + F32 getCurS () { return curS; }; + F32 getCurL () { return curL; }; + + // updates current RGB/HSL values based on point in picker + bool updateRgbHslFromPoint ( S32 xPosIn, S32 yPosIn ); + + // updates text entry fields with current RGB/HSL + void updateTextEntry (); + + void stopUsingPipette(); + + // mutator / accessor for mouse button pressed in region + void setMouseDownInHueRegion ( bool mouse_down_in_region ); + bool getMouseDownInHueRegion () { return mMouseDownInHueRegion; }; + + void setMouseDownInLumRegion ( bool mouse_down_in_region ); + bool getMouseDownInLumRegion () { return mMouseDownInLumRegion; }; + + void setMouseDownInSwatch (bool mouse_down_in_swatch); + bool getMouseDownInSwatch () { return mMouseDownInSwatch; } + + bool isColorChanged (); + + // called when text entries (RGB/HSL etc.) are changed by user + void onTextEntryChanged ( LLUICtrl* ctrl ); + + // convert RGB to HSL and vice-versa + void hslToRgb ( F32 hValIn, F32 sValIn, F32 lValIn, F32& rValOut, F32& gValOut, F32& bValOut ); + F32 hueToRgb ( F32 val1In, F32 val2In, F32 valHUeIn ); + + void setActive(bool active); + + protected: + // callbacks + static void onClickCancel ( void* data ); + static void onClickSelect ( void* data ); + void onClickPipette ( ); + static void onTextCommit ( LLUICtrl* ctrl, void* data ); + static void onImmediateCheck ( LLUICtrl* ctrl, void* data ); + void onColorSelect( const class LLTextureEntry& te ); + private: + // mutators for color values, can raise event to preview changes at object + void selectCurRgb ( F32 curRIn, F32 curGIn, F32 curBIn ); + void selectCurHsl ( F32 curHIn, F32 curSIn, F32 curLIn ); + // draws color selection palette + void drawPalette (); + + // find a complimentary color to the one passed in that can be used to highlight + const LLColor4& getComplimentaryColor ( const LLColor4& backgroundColor ); + + // original RGB values + F32 origR, origG, origB; + + // current RGB/HSL values + F32 curR, curG, curB; + F32 curH, curS, curL; + + const S32 mComponents; + + bool mMouseDownInLumRegion; + bool mMouseDownInHueRegion; + bool mMouseDownInSwatch; + + const S32 mRGBViewerImageLeft; + const S32 mRGBViewerImageTop; + const S32 mRGBViewerImageWidth; + const S32 mRGBViewerImageHeight; + + const S32 mLumRegionLeft; + const S32 mLumRegionTop; + const S32 mLumRegionWidth; + const S32 mLumRegionHeight; + const S32 mLumMarkerSize; + + // Preview of the current color. + const S32 mSwatchRegionLeft; + const S32 mSwatchRegionTop; + const S32 mSwatchRegionWidth; + const S32 mSwatchRegionHeight; + + LLView* mSwatchView; + + const S32 numPaletteColumns; + const S32 numPaletteRows; + std::vector < LLColor4* > mPalette; + S32 highlightEntry; + const S32 mPaletteRegionLeft; + const S32 mPaletteRegionTop; + const S32 mPaletteRegionWidth; + const S32 mPaletteRegionHeight; + + // image used to compose color grid + LLPointer mRGBImage; + + // current swatch in use + LLColorSwatchCtrl* mSwatch; + + // are we actively tied to some output? + bool mActive; + + // enable/disable immediate updates + LLCheckBoxCtrl* mApplyImmediateCheck; + bool mCanApplyImmediately; + + LLButton* mSelectBtn; + LLButton* mCancelBtn; + + LLButton* mPipetteBtn; + + F32 mContextConeOpacity; + F32 mContextConeInAlpha; + F32 mContextConeOutAlpha; + F32 mContextConeFadeTime; + +}; + +#endif // LL_LLFLOATERCOLORPICKER_H diff --git a/indra/newview/llfloaterconversationlog.cpp b/indra/newview/llfloaterconversationlog.cpp index 40659f5746..648d3af5a5 100644 --- a/indra/newview/llfloaterconversationlog.cpp +++ b/indra/newview/llfloaterconversationlog.cpp @@ -1,134 +1,134 @@ -/** - * @file llfloaterconversationlog.cpp - * @brief Functionality of the "conversation log" floater - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ -#include "llviewerprecompiledheaders.h" - -#include "llconversationloglist.h" -#include "llfiltereditor.h" -#include "llfloaterconversationlog.h" -#include "llfloaterreg.h" -#include "llmenubutton.h" - -LLFloaterConversationLog::LLFloaterConversationLog(const LLSD& key) -: LLFloater(key), - mConversationLogList(NULL) -{ - mCommitCallbackRegistrar.add("CallLog.Action", boost::bind(&LLFloaterConversationLog::onCustomAction, this, _2)); - mEnableCallbackRegistrar.add("CallLog.Check", boost::bind(&LLFloaterConversationLog::isActionChecked, this, _2)); -} - -bool LLFloaterConversationLog::postBuild() -{ - mConversationLogList = getChild("conversation_log_list"); - - switch (gSavedSettings.getU32("CallLogSortOrder")) - { - case LLConversationLogList::E_SORT_BY_NAME: - mConversationLogList->sortByName(); - break; - - case LLConversationLogList::E_SORT_BY_DATE: - mConversationLogList->sortByDate(); - break; - } - - // Use the context menu of the Conversation list for the Conversation tab gear menu. - LLToggleableMenu* conversations_gear_menu = mConversationLogList->getContextMenu(); - if (conversations_gear_menu) - { - getChild("conversations_gear_btn")->setMenu(conversations_gear_menu, LLMenuButton::MP_BOTTOM_LEFT); - } - - getChild("people_filter_input")->setCommitCallback(boost::bind(&LLFloaterConversationLog::onFilterEdit, this, _2)); - - return LLFloater::postBuild(); -} - -void LLFloaterConversationLog::draw() -{ - getChild("conversations_gear_btn")->setEnabled(mConversationLogList->getSelectedItem() != NULL); - LLFloater::draw(); -} - -void LLFloaterConversationLog::onFilterEdit(const std::string& search_string) -{ - std::string filter = search_string; - LLStringUtil::trimHead(filter); - - mConversationLogList->setNameFilter(filter); -} - - -void LLFloaterConversationLog::onCustomAction (const LLSD& userdata) -{ - const std::string command_name = userdata.asString(); - - if ("sort_by_name" == command_name) - { - mConversationLogList->sortByName(); - gSavedSettings.setU32("CallLogSortOrder", LLConversationLogList::E_SORT_BY_NAME); - } - else if ("sort_by_date" == command_name) - { - mConversationLogList->sortByDate(); - gSavedSettings.setU32("CallLogSortOrder", LLConversationLogList::E_SORT_BY_DATE); - } - else if ("sort_friends_on_top" == command_name) - { - mConversationLogList->toggleSortFriendsOnTop(); - } - else if ("view_nearby_chat_history" == command_name) - { - LLFloaterReg::showInstance("preview_conversation", LLSD(LLUUID::null), true); - } -} - -bool LLFloaterConversationLog::isActionEnabled(const LLSD& userdata) -{ - return true; -} - -bool LLFloaterConversationLog::isActionChecked(const LLSD& userdata) -{ - const std::string command_name = userdata.asString(); - - U32 sort_order = gSavedSettings.getU32("CallLogSortOrder"); - - if ("sort_by_name" == command_name) - { - return sort_order == LLConversationLogList::E_SORT_BY_NAME; - } - else if ("sort_by_date" == command_name) - { - return sort_order == LLConversationLogList::E_SORT_BY_DATE; - } - else if ("sort_friends_on_top" == command_name) - { - return gSavedSettings.getBOOL("SortFriendsFirst"); - } - - return false; -} - +/** + * @file llfloaterconversationlog.cpp + * @brief Functionality of the "conversation log" floater + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#include "llviewerprecompiledheaders.h" + +#include "llconversationloglist.h" +#include "llfiltereditor.h" +#include "llfloaterconversationlog.h" +#include "llfloaterreg.h" +#include "llmenubutton.h" + +LLFloaterConversationLog::LLFloaterConversationLog(const LLSD& key) +: LLFloater(key), + mConversationLogList(NULL) +{ + mCommitCallbackRegistrar.add("CallLog.Action", boost::bind(&LLFloaterConversationLog::onCustomAction, this, _2)); + mEnableCallbackRegistrar.add("CallLog.Check", boost::bind(&LLFloaterConversationLog::isActionChecked, this, _2)); +} + +bool LLFloaterConversationLog::postBuild() +{ + mConversationLogList = getChild("conversation_log_list"); + + switch (gSavedSettings.getU32("CallLogSortOrder")) + { + case LLConversationLogList::E_SORT_BY_NAME: + mConversationLogList->sortByName(); + break; + + case LLConversationLogList::E_SORT_BY_DATE: + mConversationLogList->sortByDate(); + break; + } + + // Use the context menu of the Conversation list for the Conversation tab gear menu. + LLToggleableMenu* conversations_gear_menu = mConversationLogList->getContextMenu(); + if (conversations_gear_menu) + { + getChild("conversations_gear_btn")->setMenu(conversations_gear_menu, LLMenuButton::MP_BOTTOM_LEFT); + } + + getChild("people_filter_input")->setCommitCallback(boost::bind(&LLFloaterConversationLog::onFilterEdit, this, _2)); + + return LLFloater::postBuild(); +} + +void LLFloaterConversationLog::draw() +{ + getChild("conversations_gear_btn")->setEnabled(mConversationLogList->getSelectedItem() != NULL); + LLFloater::draw(); +} + +void LLFloaterConversationLog::onFilterEdit(const std::string& search_string) +{ + std::string filter = search_string; + LLStringUtil::trimHead(filter); + + mConversationLogList->setNameFilter(filter); +} + + +void LLFloaterConversationLog::onCustomAction (const LLSD& userdata) +{ + const std::string command_name = userdata.asString(); + + if ("sort_by_name" == command_name) + { + mConversationLogList->sortByName(); + gSavedSettings.setU32("CallLogSortOrder", LLConversationLogList::E_SORT_BY_NAME); + } + else if ("sort_by_date" == command_name) + { + mConversationLogList->sortByDate(); + gSavedSettings.setU32("CallLogSortOrder", LLConversationLogList::E_SORT_BY_DATE); + } + else if ("sort_friends_on_top" == command_name) + { + mConversationLogList->toggleSortFriendsOnTop(); + } + else if ("view_nearby_chat_history" == command_name) + { + LLFloaterReg::showInstance("preview_conversation", LLSD(LLUUID::null), true); + } +} + +bool LLFloaterConversationLog::isActionEnabled(const LLSD& userdata) +{ + return true; +} + +bool LLFloaterConversationLog::isActionChecked(const LLSD& userdata) +{ + const std::string command_name = userdata.asString(); + + U32 sort_order = gSavedSettings.getU32("CallLogSortOrder"); + + if ("sort_by_name" == command_name) + { + return sort_order == LLConversationLogList::E_SORT_BY_NAME; + } + else if ("sort_by_date" == command_name) + { + return sort_order == LLConversationLogList::E_SORT_BY_DATE; + } + else if ("sort_friends_on_top" == command_name) + { + return gSavedSettings.getBOOL("SortFriendsFirst"); + } + + return false; +} + diff --git a/indra/newview/llfloaterconversationlog.h b/indra/newview/llfloaterconversationlog.h index 98c998cc86..85ca37c530 100644 --- a/indra/newview/llfloaterconversationlog.h +++ b/indra/newview/llfloaterconversationlog.h @@ -1,56 +1,56 @@ -/** - * @file llfloaterconversationlog.h - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERCONVERSATIONLOG_H_ -#define LL_LLFLOATERCONVERSATIONLOG_H_ - -#include "llfloater.h" - -class LLConversationLogList; - -class LLFloaterConversationLog : public LLFloater -{ -public: - - LLFloaterConversationLog(const LLSD& key); - virtual ~LLFloaterConversationLog(){}; - - bool postBuild() override; - - void draw() override; - - void onFilterEdit(const std::string& search_string); - -private: - - void onCustomAction (const LLSD& userdata); - bool isActionEnabled(const LLSD& userdata); - bool isActionChecked(const LLSD& userdata); - - LLConversationLogList* mConversationLogList; -}; - - -#endif /* LLFLOATERCONVERSATIONLOG_H_ */ +/** + * @file llfloaterconversationlog.h + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERCONVERSATIONLOG_H_ +#define LL_LLFLOATERCONVERSATIONLOG_H_ + +#include "llfloater.h" + +class LLConversationLogList; + +class LLFloaterConversationLog : public LLFloater +{ +public: + + LLFloaterConversationLog(const LLSD& key); + virtual ~LLFloaterConversationLog(){}; + + bool postBuild() override; + + void draw() override; + + void onFilterEdit(const std::string& search_string); + +private: + + void onCustomAction (const LLSD& userdata); + bool isActionEnabled(const LLSD& userdata); + bool isActionChecked(const LLSD& userdata); + + LLConversationLogList* mConversationLogList; +}; + + +#endif /* LLFLOATERCONVERSATIONLOG_H_ */ diff --git a/indra/newview/llfloaterconversationpreview.cpp b/indra/newview/llfloaterconversationpreview.cpp index df08755b35..d6bf410c55 100644 --- a/indra/newview/llfloaterconversationpreview.cpp +++ b/indra/newview/llfloaterconversationpreview.cpp @@ -1,274 +1,274 @@ -/** - * @file llfloaterconversationpreview.cpp - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llavatarnamecache.h" -#include "llconversationlog.h" -#include "llfloaterconversationpreview.h" -#include "llimview.h" -#include "lllineeditor.h" -#include "llfloaterimnearbychat.h" -#include "llspinctrl.h" -#include "lltrans.h" -#include "llnotificationsutil.h" - -const std::string LL_FCP_COMPLETE_NAME("complete_name"); -const std::string LL_FCP_ACCOUNT_NAME("user_name"); -const S32 CONVERSATION_HISTORY_PAGE_SIZE = 100; - -LLFloaterConversationPreview::LLFloaterConversationPreview(const LLSD& session_id) -: LLFloater(session_id), - mChatHistory(NULL), - mSessionID(session_id.asUUID()), - mCurrentPage(0), - mPageSize(CONVERSATION_HISTORY_PAGE_SIZE), - mAccountName(session_id[LL_FCP_ACCOUNT_NAME]), - mCompleteName(session_id[LL_FCP_COMPLETE_NAME]), - mMutex(), - mShowHistory(false), - mMessages(NULL), - mHistoryThreadsBusy(false), - mIsGroup(false), - mOpened(false) -{ -} - -LLFloaterConversationPreview::~LLFloaterConversationPreview() -{ -} - -bool LLFloaterConversationPreview::postBuild() -{ - mChatHistory = getChild("chat_history"); - - const LLConversation* conv = LLConversationLog::instance().getConversation(mSessionID); - std::string name; - std::string file; - - if (mAccountName != "") - { - name = mCompleteName; - file = mAccountName; - } - else if (mSessionID != LLUUID::null && conv) - { - name = conv->getConversationName(); - file = conv->getHistoryFileName(); - mIsGroup = (LLIMModel::LLIMSession::GROUP_SESSION == conv->getConversationType()); - } - else - { - name = LLTrans::getString("NearbyChatTitle"); - file = "chat"; - } - mChatHistoryFileName = file; - if (mIsGroup && !LLStringUtil::endsWith(mChatHistoryFileName, GROUP_CHAT_SUFFIX)) - { - mChatHistoryFileName += GROUP_CHAT_SUFFIX; - } - LLStringUtil::format_map_t args; - args["[NAME]"] = name; - std::string title = getString("Title", args); - setTitle(title); - - return LLFloater::postBuild(); -} - -void LLFloaterConversationPreview::setPages(std::list* messages, const std::string& file_name) -{ - if(file_name == mChatHistoryFileName && messages) - { - // additional protection to avoid changes of mMessages in setPages() - LLMutexLock lock(&mMutex); - if (mMessages) - { - delete mMessages; // Clean up temporary message list with "Loading..." text - } - mMessages = messages; - mCurrentPage = (mMessages->size() ? (mMessages->size() - 1) / mPageSize : 0); - - mPageSpinner->setEnabled(true); - mPageSpinner->setMaxValue(mCurrentPage+1); - mPageSpinner->set(mCurrentPage+1); - - std::string total_page_num = llformat("/ %d", mCurrentPage+1); - getChild("page_num_label")->setValue(total_page_num); - mShowHistory = true; - } - LLLoadHistoryThread* loadThread = LLLogChat::getInstance()->getLoadHistoryThread(mSessionID); - if (loadThread) - { - loadThread->removeLoadEndSignal(boost::bind(&LLFloaterConversationPreview::setPages, this, _1, _2)); - } -} - -void LLFloaterConversationPreview::draw() -{ - if(mShowHistory) - { - showHistory(); - mShowHistory = false; - } - LLFloater::draw(); -} - -void LLFloaterConversationPreview::onOpen(const LLSD& key) -{ - if (mOpened) - { - return; - } - mOpened = true; - if (!LLLogChat::getInstance()->historyThreadsFinished(mSessionID)) - { - LLNotificationsUtil::add("ChatHistoryIsBusyAlert"); - mHistoryThreadsBusy = true; - closeFloater(); - return; - } - LLSD load_params; - load_params["load_all_history"] = true; - load_params["cut_off_todays_date"] = false; - load_params["is_group"] = mIsGroup; - - // The temporary message list with "Loading..." text - // Will be deleted upon loading completion in setPages() method - mMessages = new std::list(); - - - LLSD loading; - loading[LL_IM_TEXT] = LLTrans::getString("loading_chat_logs"); - mMessages->push_back(loading); - mPageSpinner = getChild("history_page_spin"); - mPageSpinner->setCommitCallback(boost::bind(&LLFloaterConversationPreview::onMoreHistoryBtnClick, this)); - mPageSpinner->setMinValue(1); - mPageSpinner->set(1); - mPageSpinner->setEnabled(false); - - // The actual message list to load from file - // Will be deleted in a separate thread LLDeleteHistoryThread not to freeze UI - // LLDeleteHistoryThread is started in destructor - std::list* messages = new std::list(); - - LLLogChat *log_chat_inst = LLLogChat::getInstance(); - log_chat_inst->cleanupHistoryThreads(); - - LLLoadHistoryThread* loadThread = new LLLoadHistoryThread(mChatHistoryFileName, messages, load_params); - loadThread->setLoadEndSignal(boost::bind(&LLFloaterConversationPreview::setPages, this, _1, _2)); - loadThread->start(); - log_chat_inst->addLoadHistoryThread(mSessionID, loadThread); - - LLDeleteHistoryThread* deleteThread = new LLDeleteHistoryThread(messages, loadThread); - log_chat_inst->addDeleteHistoryThread(mSessionID, deleteThread); - - mShowHistory = true; -} - -void LLFloaterConversationPreview::onClose(bool app_quitting) -{ - mOpened = false; - if (!mHistoryThreadsBusy) - { - LLDeleteHistoryThread* deleteThread = LLLogChat::getInstance()->getDeleteHistoryThread(mSessionID); - if (deleteThread) - { - deleteThread->start(); - } - } -} - -void LLFloaterConversationPreview::showHistory() -{ - // additional protection to avoid changes of mMessages in setPages - LLMutexLock lock(&mMutex); - if(mMessages == NULL || !mMessages->size() || mCurrentPage * mPageSize >= mMessages->size()) - { - return; - } - - mChatHistory->clear(); - std::ostringstream message; - std::list::const_iterator iter = mMessages->begin(); - std::advance(iter, mCurrentPage * mPageSize); - - for (int msg_num = 0; iter != mMessages->end() && msg_num < mPageSize; ++iter, ++msg_num) - { - LLSD msg = *iter; - - LLUUID from_id = LLUUID::null; - std::string time = msg["time"].asString(); - std::string from = msg["from"].asString(); - std::string message = msg["message"].asString(); - - if (msg["from_id"].isDefined()) - { - from_id = msg["from_id"].asUUID(); - } - else - { - std::string legacy_name = gCacheName->buildLegacyName(from); - from_id = LLAvatarNameCache::getInstance()->findIdByName(legacy_name); - } - - LLChat chat; - chat.mFromID = from_id; - chat.mSessionID = mSessionID; - chat.mFromName = from; - chat.mTimeStr = time; - chat.mChatStyle = CHAT_STYLE_HISTORY; - chat.mText = message; - - if (from_id.isNull() && SYSTEM_FROM == from) - { - chat.mSourceType = CHAT_SOURCE_SYSTEM; - - } - else if (from_id.isNull()) - { - chat.mSourceType = LLFloaterIMNearbyChat::isWordsName(from) ? CHAT_SOURCE_UNKNOWN : CHAT_SOURCE_OBJECT; - } - - LLSD chat_args; - chat_args["use_plain_text_chat_history"] = - gSavedSettings.getBOOL("PlainTextChatHistory"); - chat_args["show_time"] = gSavedSettings.getBOOL("IMShowTime"); - chat_args["show_names_for_p2p_conv"] = gSavedSettings.getBOOL("IMShowNamesForP2PConv"); - - mChatHistory->appendMessage(chat,chat_args); - } -} - -void LLFloaterConversationPreview::onMoreHistoryBtnClick() -{ - mCurrentPage = (int)(mPageSpinner->getValueF32()); - if (!mCurrentPage) - { - return; - } - - mCurrentPage--; - mShowHistory = true; -} +/** + * @file llfloaterconversationpreview.cpp + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llavatarnamecache.h" +#include "llconversationlog.h" +#include "llfloaterconversationpreview.h" +#include "llimview.h" +#include "lllineeditor.h" +#include "llfloaterimnearbychat.h" +#include "llspinctrl.h" +#include "lltrans.h" +#include "llnotificationsutil.h" + +const std::string LL_FCP_COMPLETE_NAME("complete_name"); +const std::string LL_FCP_ACCOUNT_NAME("user_name"); +const S32 CONVERSATION_HISTORY_PAGE_SIZE = 100; + +LLFloaterConversationPreview::LLFloaterConversationPreview(const LLSD& session_id) +: LLFloater(session_id), + mChatHistory(NULL), + mSessionID(session_id.asUUID()), + mCurrentPage(0), + mPageSize(CONVERSATION_HISTORY_PAGE_SIZE), + mAccountName(session_id[LL_FCP_ACCOUNT_NAME]), + mCompleteName(session_id[LL_FCP_COMPLETE_NAME]), + mMutex(), + mShowHistory(false), + mMessages(NULL), + mHistoryThreadsBusy(false), + mIsGroup(false), + mOpened(false) +{ +} + +LLFloaterConversationPreview::~LLFloaterConversationPreview() +{ +} + +bool LLFloaterConversationPreview::postBuild() +{ + mChatHistory = getChild("chat_history"); + + const LLConversation* conv = LLConversationLog::instance().getConversation(mSessionID); + std::string name; + std::string file; + + if (mAccountName != "") + { + name = mCompleteName; + file = mAccountName; + } + else if (mSessionID != LLUUID::null && conv) + { + name = conv->getConversationName(); + file = conv->getHistoryFileName(); + mIsGroup = (LLIMModel::LLIMSession::GROUP_SESSION == conv->getConversationType()); + } + else + { + name = LLTrans::getString("NearbyChatTitle"); + file = "chat"; + } + mChatHistoryFileName = file; + if (mIsGroup && !LLStringUtil::endsWith(mChatHistoryFileName, GROUP_CHAT_SUFFIX)) + { + mChatHistoryFileName += GROUP_CHAT_SUFFIX; + } + LLStringUtil::format_map_t args; + args["[NAME]"] = name; + std::string title = getString("Title", args); + setTitle(title); + + return LLFloater::postBuild(); +} + +void LLFloaterConversationPreview::setPages(std::list* messages, const std::string& file_name) +{ + if(file_name == mChatHistoryFileName && messages) + { + // additional protection to avoid changes of mMessages in setPages() + LLMutexLock lock(&mMutex); + if (mMessages) + { + delete mMessages; // Clean up temporary message list with "Loading..." text + } + mMessages = messages; + mCurrentPage = (mMessages->size() ? (mMessages->size() - 1) / mPageSize : 0); + + mPageSpinner->setEnabled(true); + mPageSpinner->setMaxValue(mCurrentPage+1); + mPageSpinner->set(mCurrentPage+1); + + std::string total_page_num = llformat("/ %d", mCurrentPage+1); + getChild("page_num_label")->setValue(total_page_num); + mShowHistory = true; + } + LLLoadHistoryThread* loadThread = LLLogChat::getInstance()->getLoadHistoryThread(mSessionID); + if (loadThread) + { + loadThread->removeLoadEndSignal(boost::bind(&LLFloaterConversationPreview::setPages, this, _1, _2)); + } +} + +void LLFloaterConversationPreview::draw() +{ + if(mShowHistory) + { + showHistory(); + mShowHistory = false; + } + LLFloater::draw(); +} + +void LLFloaterConversationPreview::onOpen(const LLSD& key) +{ + if (mOpened) + { + return; + } + mOpened = true; + if (!LLLogChat::getInstance()->historyThreadsFinished(mSessionID)) + { + LLNotificationsUtil::add("ChatHistoryIsBusyAlert"); + mHistoryThreadsBusy = true; + closeFloater(); + return; + } + LLSD load_params; + load_params["load_all_history"] = true; + load_params["cut_off_todays_date"] = false; + load_params["is_group"] = mIsGroup; + + // The temporary message list with "Loading..." text + // Will be deleted upon loading completion in setPages() method + mMessages = new std::list(); + + + LLSD loading; + loading[LL_IM_TEXT] = LLTrans::getString("loading_chat_logs"); + mMessages->push_back(loading); + mPageSpinner = getChild("history_page_spin"); + mPageSpinner->setCommitCallback(boost::bind(&LLFloaterConversationPreview::onMoreHistoryBtnClick, this)); + mPageSpinner->setMinValue(1); + mPageSpinner->set(1); + mPageSpinner->setEnabled(false); + + // The actual message list to load from file + // Will be deleted in a separate thread LLDeleteHistoryThread not to freeze UI + // LLDeleteHistoryThread is started in destructor + std::list* messages = new std::list(); + + LLLogChat *log_chat_inst = LLLogChat::getInstance(); + log_chat_inst->cleanupHistoryThreads(); + + LLLoadHistoryThread* loadThread = new LLLoadHistoryThread(mChatHistoryFileName, messages, load_params); + loadThread->setLoadEndSignal(boost::bind(&LLFloaterConversationPreview::setPages, this, _1, _2)); + loadThread->start(); + log_chat_inst->addLoadHistoryThread(mSessionID, loadThread); + + LLDeleteHistoryThread* deleteThread = new LLDeleteHistoryThread(messages, loadThread); + log_chat_inst->addDeleteHistoryThread(mSessionID, deleteThread); + + mShowHistory = true; +} + +void LLFloaterConversationPreview::onClose(bool app_quitting) +{ + mOpened = false; + if (!mHistoryThreadsBusy) + { + LLDeleteHistoryThread* deleteThread = LLLogChat::getInstance()->getDeleteHistoryThread(mSessionID); + if (deleteThread) + { + deleteThread->start(); + } + } +} + +void LLFloaterConversationPreview::showHistory() +{ + // additional protection to avoid changes of mMessages in setPages + LLMutexLock lock(&mMutex); + if(mMessages == NULL || !mMessages->size() || mCurrentPage * mPageSize >= mMessages->size()) + { + return; + } + + mChatHistory->clear(); + std::ostringstream message; + std::list::const_iterator iter = mMessages->begin(); + std::advance(iter, mCurrentPage * mPageSize); + + for (int msg_num = 0; iter != mMessages->end() && msg_num < mPageSize; ++iter, ++msg_num) + { + LLSD msg = *iter; + + LLUUID from_id = LLUUID::null; + std::string time = msg["time"].asString(); + std::string from = msg["from"].asString(); + std::string message = msg["message"].asString(); + + if (msg["from_id"].isDefined()) + { + from_id = msg["from_id"].asUUID(); + } + else + { + std::string legacy_name = gCacheName->buildLegacyName(from); + from_id = LLAvatarNameCache::getInstance()->findIdByName(legacy_name); + } + + LLChat chat; + chat.mFromID = from_id; + chat.mSessionID = mSessionID; + chat.mFromName = from; + chat.mTimeStr = time; + chat.mChatStyle = CHAT_STYLE_HISTORY; + chat.mText = message; + + if (from_id.isNull() && SYSTEM_FROM == from) + { + chat.mSourceType = CHAT_SOURCE_SYSTEM; + + } + else if (from_id.isNull()) + { + chat.mSourceType = LLFloaterIMNearbyChat::isWordsName(from) ? CHAT_SOURCE_UNKNOWN : CHAT_SOURCE_OBJECT; + } + + LLSD chat_args; + chat_args["use_plain_text_chat_history"] = + gSavedSettings.getBOOL("PlainTextChatHistory"); + chat_args["show_time"] = gSavedSettings.getBOOL("IMShowTime"); + chat_args["show_names_for_p2p_conv"] = gSavedSettings.getBOOL("IMShowNamesForP2PConv"); + + mChatHistory->appendMessage(chat,chat_args); + } +} + +void LLFloaterConversationPreview::onMoreHistoryBtnClick() +{ + mCurrentPage = (int)(mPageSpinner->getValueF32()); + if (!mCurrentPage) + { + return; + } + + mCurrentPage--; + mShowHistory = true; +} diff --git a/indra/newview/llfloaterconversationpreview.h b/indra/newview/llfloaterconversationpreview.h index 8ca7780e24..563592fb20 100644 --- a/indra/newview/llfloaterconversationpreview.h +++ b/indra/newview/llfloaterconversationpreview.h @@ -1,72 +1,72 @@ -/** - * @file llfloaterconversationpreview.h - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLFLOATERCONVERSATIONPREVIEW_H_ -#define LLFLOATERCONVERSATIONPREVIEW_H_ - -#include "llchathistory.h" -#include "llfloater.h" - -extern const std::string LL_FCP_COMPLETE_NAME; //"complete_name" -extern const std::string LL_FCP_ACCOUNT_NAME; //"user_name" - -class LLSpinCtrl; - -class LLFloaterConversationPreview : public LLFloater -{ -public: - - LLFloaterConversationPreview(const LLSD& session_id); - virtual ~LLFloaterConversationPreview(); - - bool postBuild() override; - void setPages(std::list* messages,const std::string& file_name); - - void draw() override; - void onOpen(const LLSD& key) override; - void onClose(bool app_quitting) override; - -private: - void onMoreHistoryBtnClick(); - void showHistory(); - - LLMutex mMutex; - LLSpinCtrl* mPageSpinner; - LLChatHistory* mChatHistory; - LLUUID mSessionID; - int mCurrentPage; - int mPageSize; - - std::list* mMessages; - std::string mAccountName; - std::string mCompleteName; - std::string mChatHistoryFileName; - bool mShowHistory; - bool mHistoryThreadsBusy; - bool mOpened; - bool mIsGroup; -}; - -#endif /* LLFLOATERCONVERSATIONPREVIEW_H_ */ +/** + * @file llfloaterconversationpreview.h + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLFLOATERCONVERSATIONPREVIEW_H_ +#define LLFLOATERCONVERSATIONPREVIEW_H_ + +#include "llchathistory.h" +#include "llfloater.h" + +extern const std::string LL_FCP_COMPLETE_NAME; //"complete_name" +extern const std::string LL_FCP_ACCOUNT_NAME; //"user_name" + +class LLSpinCtrl; + +class LLFloaterConversationPreview : public LLFloater +{ +public: + + LLFloaterConversationPreview(const LLSD& session_id); + virtual ~LLFloaterConversationPreview(); + + bool postBuild() override; + void setPages(std::list* messages,const std::string& file_name); + + void draw() override; + void onOpen(const LLSD& key) override; + void onClose(bool app_quitting) override; + +private: + void onMoreHistoryBtnClick(); + void showHistory(); + + LLMutex mMutex; + LLSpinCtrl* mPageSpinner; + LLChatHistory* mChatHistory; + LLUUID mSessionID; + int mCurrentPage; + int mPageSize; + + std::list* mMessages; + std::string mAccountName; + std::string mCompleteName; + std::string mChatHistoryFileName; + bool mShowHistory; + bool mHistoryThreadsBusy; + bool mOpened; + bool mIsGroup; +}; + +#endif /* LLFLOATERCONVERSATIONPREVIEW_H_ */ diff --git a/indra/newview/llfloatercreatelandmark.cpp b/indra/newview/llfloatercreatelandmark.cpp index 5ea0233798..2ce8a7a212 100644 --- a/indra/newview/llfloatercreatelandmark.cpp +++ b/indra/newview/llfloatercreatelandmark.cpp @@ -1,445 +1,445 @@ -/** - * @file llfloatercreatelandmark.cpp - * @brief LLFloaterCreateLandmark class implementation - * - * $LicenseInfo:firstyear=2021&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2021, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloatercreatelandmark.h" - -#include "llagent.h" -#include "llagentui.h" -#include "llcombobox.h" -#include "llinventoryfunctions.h" -#include "llinventoryobserver.h" -#include "lllandmarkactions.h" -#include "llnotificationsutil.h" -#include "llpanellandmarkinfo.h" -#include "llparcel.h" -#include "lltextbox.h" -#include "lltexteditor.h" -#include "lluictrlfactory.h" -#include "llviewermessage.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" - -typedef std::pair folder_pair_t; - -class LLLandmarksInventoryObserver : public LLInventoryObserver -{ -public: - LLLandmarksInventoryObserver(LLFloaterCreateLandmark* create_landmark_floater) : - mFloater(create_landmark_floater) - {} - - void changed(U32 mask) override - { - if (mFloater->getItem()) - { - checkChanged(mask); - } - else - { - checkCreated(mask); - } - } - -protected: - void checkCreated(U32 mask) - { - if (gInventory.getAddedIDs().empty()) - { - return; - } - - if (!(mask & LLInventoryObserver::ADD) || - !(mask & LLInventoryObserver::CREATE) || - !(mask & LLInventoryObserver::UPDATE_CREATE)) - { - return; - } - - mFloater->setItem(gInventory.getAddedIDs()); - } - - void checkChanged(U32 mask) - { - if (gInventory.getChangedIDs().empty()) - { - return; - } - - if ((mask & LLInventoryObserver::LABEL) || - (mask & LLInventoryObserver::INTERNAL) || - (mask & LLInventoryObserver::REMOVE) || - (mask & LLInventoryObserver::STRUCTURE) || - (mask & LLInventoryObserver::REBUILD)) - { - mFloater->updateItem(gInventory.getChangedIDs(), mask); - } - } - -private: - LLFloaterCreateLandmark* mFloater; -}; - -LLFloaterCreateLandmark::LLFloaterCreateLandmark(const LLSD& key) - : LLFloater("add_landmark"), - mItem(NULL) -{ - mInventoryObserver = new LLLandmarksInventoryObserver(this); -} - -LLFloaterCreateLandmark::~LLFloaterCreateLandmark() -{ - removeObserver(); -} - -bool LLFloaterCreateLandmark::postBuild() -{ - mFolderCombo = getChild("folder_combo"); - mLandmarkTitleEditor = getChild("title_editor"); - mNotesEditor = getChild("notes_editor"); - - getChild("new_folder_textbox")->setURLClickedCallback(boost::bind(&LLFloaterCreateLandmark::onCreateFolderClicked, this)); - getChild("ok_btn")->setClickedCallback(boost::bind(&LLFloaterCreateLandmark::onSaveClicked, this)); - getChild("cancel_btn")->setClickedCallback(boost::bind(&LLFloaterCreateLandmark::onCancelClicked, this)); - - mLandmarkTitleEditor->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTextChanges(); }); - mNotesEditor->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTextChanges(); }); - - mLandmarksID = gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK); - - return true; -} - -void LLFloaterCreateLandmark::removeObserver() -{ - if (gInventory.containsObserver(mInventoryObserver)) - gInventory.removeObserver(mInventoryObserver); -} - -void LLFloaterCreateLandmark::onOpen(const LLSD& key) -{ - LLUUID dest_folder = LLUUID(); - if (key.has("dest_folder")) - { - dest_folder = key["dest_folder"].asUUID(); - } - mItem = NULL; - gInventory.addObserver(mInventoryObserver); - setLandmarkInfo(dest_folder); - populateFoldersList(dest_folder); -} - -void LLFloaterCreateLandmark::setLandmarkInfo(const LLUUID &folder_id) -{ - LLViewerParcelMgr* parcel_mgr = LLViewerParcelMgr::getInstance(); - LLParcel* parcel = parcel_mgr->getAgentParcel(); - std::string name = parcel->getName(); - LLVector3 agent_pos = gAgent.getPositionAgent(); - - if (name.empty()) - { - S32 region_x = ll_round(agent_pos.mV[VX]); - S32 region_y = ll_round(agent_pos.mV[VY]); - S32 region_z = ll_round(agent_pos.mV[VZ]); - - std::string region_name; - LLViewerRegion* region = parcel_mgr->getSelectionRegion(); - if (region) - { - region_name = region->getName(); - } - else - { - std::string desc; - LLAgentUI::buildLocationString(desc, LLAgentUI::LOCATION_FORMAT_NORMAL, agent_pos); - region_name = desc; - } - - mLandmarkTitleEditor->setText(llformat("%s (%d, %d, %d)", - region_name.c_str(), region_x, region_y, region_z)); - } - else - { - mLandmarkTitleEditor->setText(name); - } - - LLLandmarkActions::createLandmarkHere(name, "", folder_id.notNull() ? folder_id : gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE)); -} - -bool cmp_folders(const folder_pair_t& left, const folder_pair_t& right) -{ - return left.second < right.second; -} - -void LLFloaterCreateLandmark::populateFoldersList(const LLUUID &folder_id) -{ - // Collect all folders that can contain landmarks. - LLInventoryModel::cat_array_t cats; - LLPanelLandmarkInfo::collectLandmarkFolders(cats); - - mFolderCombo->removeall(); - - // Put the "My Favorites" folder first in list. - LLUUID favorites_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); - LLViewerInventoryCategory* favorites_cat = gInventory.getCategory(favorites_id); - if (!favorites_cat) - { - LL_WARNS() << "Cannot find the favorites folder" << LL_ENDL; - } - else - { - mFolderCombo->add(getString("favorites_bar"), favorites_cat->getUUID()); - } - - // Add the "Landmarks" category. - const LLViewerInventoryCategory* lmcat = gInventory.getCategory(mLandmarksID); - if (!lmcat) - { - LL_WARNS() << "Cannot find the landmarks folder" << LL_ENDL; - } - else - { - std::string cat_full_name = LLPanelLandmarkInfo::getFullFolderName(lmcat); - mFolderCombo->add(cat_full_name, lmcat->getUUID()); - } - - typedef std::vector folder_vec_t; - folder_vec_t folders; - // Sort the folders by their full name. - for (S32 i = 0; i < cats.size(); i++) - { - const LLViewerInventoryCategory* cat = cats.at(i); - std::string cat_full_name = LLPanelLandmarkInfo::getFullFolderName(cat); - folders.push_back(folder_pair_t(cat->getUUID(), cat_full_name)); - } - sort(folders.begin(), folders.end(), cmp_folders); - - // Finally, populate the combobox. - for (folder_vec_t::const_iterator it = folders.begin(); it != folders.end(); it++) - mFolderCombo->add(it->second, LLSD(it->first)); - - if (folder_id.notNull()) - { - mFolderCombo->setCurrentByID(folder_id); - } -} - -void LLFloaterCreateLandmark::onCommitTextChanges() -{ - if (mItem.isNull()) - { - return; - } - std::string current_title_value = mLandmarkTitleEditor->getText(); - std::string item_title_value = mItem->getName(); - std::string current_notes_value = mNotesEditor->getText(); - std::string item_notes_value = mItem->getDescription(); - - LLStringUtil::trim(current_title_value); - LLStringUtil::trim(current_notes_value); - - if (!current_title_value.empty() && - (item_title_value != current_title_value || item_notes_value != current_notes_value)) - { - LLPointer new_item = new LLViewerInventoryItem(mItem); - new_item->rename(current_title_value); - new_item->setDescription(current_notes_value); - LLPointer cb; - LLInventoryModel::LLCategoryUpdate up(mItem->getParentUUID(), 0); - gInventory.accountForUpdate(up); - update_inventory_item(new_item, cb); - } -} - -void LLFloaterCreateLandmark::onCreateFolderClicked() -{ - LLNotificationsUtil::add("CreateLandmarkFolder", LLSD(), LLSD(), - [this](const LLSD¬if, const LLSD&resp) - { - S32 opt = LLNotificationsUtil::getSelectedOption(notif, resp); - if (opt == 0) - { - std::string folder_name = resp["message"].asString(); - if (!folder_name.empty()) - { - inventory_func_type func = boost::bind(&LLFloaterCreateLandmark::folderCreatedCallback, this, _1); - gInventory.createNewCategory(mLandmarksID, LLFolderType::FT_NONE, folder_name, func); - gInventory.notifyObservers(); - } - } - }); -} - -void LLFloaterCreateLandmark::folderCreatedCallback(LLUUID folder_id) -{ - populateFoldersList(folder_id); -} - -void LLFloaterCreateLandmark::onSaveClicked() -{ - if (mItem.isNull()) - { - closeFloater(); - return; - } - - - std::string current_title_value = mLandmarkTitleEditor->getText(); - std::string item_title_value = mItem->getName(); - std::string current_notes_value = mNotesEditor->getText(); - std::string item_notes_value = mItem->getDescription(); - - LLStringUtil::trim(current_title_value); - LLStringUtil::trim(current_notes_value); - - LLUUID folder_id = mFolderCombo->getValue().asUUID(); - bool change_parent = folder_id != mItem->getParentUUID(); - - LLPointer new_item = new LLViewerInventoryItem(mItem); - - if (!current_title_value.empty() && - (item_title_value != current_title_value || item_notes_value != current_notes_value)) - { - new_item->rename(current_title_value); - new_item->setDescription(current_notes_value); - LLPointer cb; - if (change_parent) - { - cb = new LLUpdateLandmarkParent(new_item, folder_id); - } - LLInventoryModel::LLCategoryUpdate up(mItem->getParentUUID(), 0); - gInventory.accountForUpdate(up); - update_inventory_item(new_item, cb); - } - else if (change_parent) - { - LLInventoryModel::update_list_t update; - LLInventoryModel::LLCategoryUpdate old_folder(mItem->getParentUUID(),-1); - update.push_back(old_folder); - LLInventoryModel::LLCategoryUpdate new_folder(folder_id, 1); - update.push_back(new_folder); - gInventory.accountForUpdate(update); - - new_item->setParent(folder_id); - new_item->updateParentOnServer(false); - } - - removeObserver(); - - gInventory.updateItem(new_item); - gInventory.notifyObservers(); - - closeFloater(); -} - -void LLFloaterCreateLandmark::onCancelClicked() -{ - removeObserver(); - if (!mItem.isNull()) - { - LLUUID item_id = mItem->getUUID(); - remove_inventory_item(item_id, NULL); - } - closeFloater(); -} - - -void LLFloaterCreateLandmark::setItem(const uuid_set_t& items) -{ - for (uuid_set_t::const_iterator item_iter = items.begin(); - item_iter != items.end(); - ++item_iter) - { - const LLUUID& item_id = (*item_iter); - if(!highlight_offered_object(item_id)) - { - continue; - } - - LLInventoryItem* item = gInventory.getItem(item_id); - - llassert(item); - if (item && (LLAssetType::AT_LANDMARK == item->getType()) ) - { - if(!getItem()) - { - mItem = item; - mAssetID = mItem->getAssetUUID(); - setVisibleAndFrontmost(true); - break; - } - } - } -} - -void LLFloaterCreateLandmark::updateItem(const uuid_set_t& items, U32 mask) -{ - if (!getItem()) - { - return; - } - - LLUUID landmark_id = getItem()->getUUID(); - - for (uuid_set_t::const_iterator item_iter = items.begin(); - item_iter != items.end(); - ++item_iter) - { - const LLUUID& item_id = (*item_iter); - if (landmark_id == item_id) - { - if (getItem() != gInventory.getItem(item_id)) - { - // item is obsolete or removed - closeFloater(); - } - - LLUUID folder_id = mFolderCombo->getValue().asUUID(); - if (folder_id != mItem->getParentUUID()) - { - // user moved landmark in inventory, - // assume that we are done all other changes should already be commited - closeFloater(); - } - - if ((mask & LLInventoryObserver::INTERNAL) && mAssetID != mItem->getAssetUUID()) - { - closeFloater(); - } - - if (mask & LLInventoryObserver::LABEL) - { - mLandmarkTitleEditor->setText(mItem->getName()); - } - - if (mask & LLInventoryObserver::INTERNAL) - { - mNotesEditor->setText(mItem->getDescription()); - } - } - } -} +/** + * @file llfloatercreatelandmark.cpp + * @brief LLFloaterCreateLandmark class implementation + * + * $LicenseInfo:firstyear=2021&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2021, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatercreatelandmark.h" + +#include "llagent.h" +#include "llagentui.h" +#include "llcombobox.h" +#include "llinventoryfunctions.h" +#include "llinventoryobserver.h" +#include "lllandmarkactions.h" +#include "llnotificationsutil.h" +#include "llpanellandmarkinfo.h" +#include "llparcel.h" +#include "lltextbox.h" +#include "lltexteditor.h" +#include "lluictrlfactory.h" +#include "llviewermessage.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" + +typedef std::pair folder_pair_t; + +class LLLandmarksInventoryObserver : public LLInventoryObserver +{ +public: + LLLandmarksInventoryObserver(LLFloaterCreateLandmark* create_landmark_floater) : + mFloater(create_landmark_floater) + {} + + void changed(U32 mask) override + { + if (mFloater->getItem()) + { + checkChanged(mask); + } + else + { + checkCreated(mask); + } + } + +protected: + void checkCreated(U32 mask) + { + if (gInventory.getAddedIDs().empty()) + { + return; + } + + if (!(mask & LLInventoryObserver::ADD) || + !(mask & LLInventoryObserver::CREATE) || + !(mask & LLInventoryObserver::UPDATE_CREATE)) + { + return; + } + + mFloater->setItem(gInventory.getAddedIDs()); + } + + void checkChanged(U32 mask) + { + if (gInventory.getChangedIDs().empty()) + { + return; + } + + if ((mask & LLInventoryObserver::LABEL) || + (mask & LLInventoryObserver::INTERNAL) || + (mask & LLInventoryObserver::REMOVE) || + (mask & LLInventoryObserver::STRUCTURE) || + (mask & LLInventoryObserver::REBUILD)) + { + mFloater->updateItem(gInventory.getChangedIDs(), mask); + } + } + +private: + LLFloaterCreateLandmark* mFloater; +}; + +LLFloaterCreateLandmark::LLFloaterCreateLandmark(const LLSD& key) + : LLFloater("add_landmark"), + mItem(NULL) +{ + mInventoryObserver = new LLLandmarksInventoryObserver(this); +} + +LLFloaterCreateLandmark::~LLFloaterCreateLandmark() +{ + removeObserver(); +} + +bool LLFloaterCreateLandmark::postBuild() +{ + mFolderCombo = getChild("folder_combo"); + mLandmarkTitleEditor = getChild("title_editor"); + mNotesEditor = getChild("notes_editor"); + + getChild("new_folder_textbox")->setURLClickedCallback(boost::bind(&LLFloaterCreateLandmark::onCreateFolderClicked, this)); + getChild("ok_btn")->setClickedCallback(boost::bind(&LLFloaterCreateLandmark::onSaveClicked, this)); + getChild("cancel_btn")->setClickedCallback(boost::bind(&LLFloaterCreateLandmark::onCancelClicked, this)); + + mLandmarkTitleEditor->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTextChanges(); }); + mNotesEditor->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) { onCommitTextChanges(); }); + + mLandmarksID = gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK); + + return true; +} + +void LLFloaterCreateLandmark::removeObserver() +{ + if (gInventory.containsObserver(mInventoryObserver)) + gInventory.removeObserver(mInventoryObserver); +} + +void LLFloaterCreateLandmark::onOpen(const LLSD& key) +{ + LLUUID dest_folder = LLUUID(); + if (key.has("dest_folder")) + { + dest_folder = key["dest_folder"].asUUID(); + } + mItem = NULL; + gInventory.addObserver(mInventoryObserver); + setLandmarkInfo(dest_folder); + populateFoldersList(dest_folder); +} + +void LLFloaterCreateLandmark::setLandmarkInfo(const LLUUID &folder_id) +{ + LLViewerParcelMgr* parcel_mgr = LLViewerParcelMgr::getInstance(); + LLParcel* parcel = parcel_mgr->getAgentParcel(); + std::string name = parcel->getName(); + LLVector3 agent_pos = gAgent.getPositionAgent(); + + if (name.empty()) + { + S32 region_x = ll_round(agent_pos.mV[VX]); + S32 region_y = ll_round(agent_pos.mV[VY]); + S32 region_z = ll_round(agent_pos.mV[VZ]); + + std::string region_name; + LLViewerRegion* region = parcel_mgr->getSelectionRegion(); + if (region) + { + region_name = region->getName(); + } + else + { + std::string desc; + LLAgentUI::buildLocationString(desc, LLAgentUI::LOCATION_FORMAT_NORMAL, agent_pos); + region_name = desc; + } + + mLandmarkTitleEditor->setText(llformat("%s (%d, %d, %d)", + region_name.c_str(), region_x, region_y, region_z)); + } + else + { + mLandmarkTitleEditor->setText(name); + } + + LLLandmarkActions::createLandmarkHere(name, "", folder_id.notNull() ? folder_id : gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE)); +} + +bool cmp_folders(const folder_pair_t& left, const folder_pair_t& right) +{ + return left.second < right.second; +} + +void LLFloaterCreateLandmark::populateFoldersList(const LLUUID &folder_id) +{ + // Collect all folders that can contain landmarks. + LLInventoryModel::cat_array_t cats; + LLPanelLandmarkInfo::collectLandmarkFolders(cats); + + mFolderCombo->removeall(); + + // Put the "My Favorites" folder first in list. + LLUUID favorites_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); + LLViewerInventoryCategory* favorites_cat = gInventory.getCategory(favorites_id); + if (!favorites_cat) + { + LL_WARNS() << "Cannot find the favorites folder" << LL_ENDL; + } + else + { + mFolderCombo->add(getString("favorites_bar"), favorites_cat->getUUID()); + } + + // Add the "Landmarks" category. + const LLViewerInventoryCategory* lmcat = gInventory.getCategory(mLandmarksID); + if (!lmcat) + { + LL_WARNS() << "Cannot find the landmarks folder" << LL_ENDL; + } + else + { + std::string cat_full_name = LLPanelLandmarkInfo::getFullFolderName(lmcat); + mFolderCombo->add(cat_full_name, lmcat->getUUID()); + } + + typedef std::vector folder_vec_t; + folder_vec_t folders; + // Sort the folders by their full name. + for (S32 i = 0; i < cats.size(); i++) + { + const LLViewerInventoryCategory* cat = cats.at(i); + std::string cat_full_name = LLPanelLandmarkInfo::getFullFolderName(cat); + folders.push_back(folder_pair_t(cat->getUUID(), cat_full_name)); + } + sort(folders.begin(), folders.end(), cmp_folders); + + // Finally, populate the combobox. + for (folder_vec_t::const_iterator it = folders.begin(); it != folders.end(); it++) + mFolderCombo->add(it->second, LLSD(it->first)); + + if (folder_id.notNull()) + { + mFolderCombo->setCurrentByID(folder_id); + } +} + +void LLFloaterCreateLandmark::onCommitTextChanges() +{ + if (mItem.isNull()) + { + return; + } + std::string current_title_value = mLandmarkTitleEditor->getText(); + std::string item_title_value = mItem->getName(); + std::string current_notes_value = mNotesEditor->getText(); + std::string item_notes_value = mItem->getDescription(); + + LLStringUtil::trim(current_title_value); + LLStringUtil::trim(current_notes_value); + + if (!current_title_value.empty() && + (item_title_value != current_title_value || item_notes_value != current_notes_value)) + { + LLPointer new_item = new LLViewerInventoryItem(mItem); + new_item->rename(current_title_value); + new_item->setDescription(current_notes_value); + LLPointer cb; + LLInventoryModel::LLCategoryUpdate up(mItem->getParentUUID(), 0); + gInventory.accountForUpdate(up); + update_inventory_item(new_item, cb); + } +} + +void LLFloaterCreateLandmark::onCreateFolderClicked() +{ + LLNotificationsUtil::add("CreateLandmarkFolder", LLSD(), LLSD(), + [this](const LLSD¬if, const LLSD&resp) + { + S32 opt = LLNotificationsUtil::getSelectedOption(notif, resp); + if (opt == 0) + { + std::string folder_name = resp["message"].asString(); + if (!folder_name.empty()) + { + inventory_func_type func = boost::bind(&LLFloaterCreateLandmark::folderCreatedCallback, this, _1); + gInventory.createNewCategory(mLandmarksID, LLFolderType::FT_NONE, folder_name, func); + gInventory.notifyObservers(); + } + } + }); +} + +void LLFloaterCreateLandmark::folderCreatedCallback(LLUUID folder_id) +{ + populateFoldersList(folder_id); +} + +void LLFloaterCreateLandmark::onSaveClicked() +{ + if (mItem.isNull()) + { + closeFloater(); + return; + } + + + std::string current_title_value = mLandmarkTitleEditor->getText(); + std::string item_title_value = mItem->getName(); + std::string current_notes_value = mNotesEditor->getText(); + std::string item_notes_value = mItem->getDescription(); + + LLStringUtil::trim(current_title_value); + LLStringUtil::trim(current_notes_value); + + LLUUID folder_id = mFolderCombo->getValue().asUUID(); + bool change_parent = folder_id != mItem->getParentUUID(); + + LLPointer new_item = new LLViewerInventoryItem(mItem); + + if (!current_title_value.empty() && + (item_title_value != current_title_value || item_notes_value != current_notes_value)) + { + new_item->rename(current_title_value); + new_item->setDescription(current_notes_value); + LLPointer cb; + if (change_parent) + { + cb = new LLUpdateLandmarkParent(new_item, folder_id); + } + LLInventoryModel::LLCategoryUpdate up(mItem->getParentUUID(), 0); + gInventory.accountForUpdate(up); + update_inventory_item(new_item, cb); + } + else if (change_parent) + { + LLInventoryModel::update_list_t update; + LLInventoryModel::LLCategoryUpdate old_folder(mItem->getParentUUID(),-1); + update.push_back(old_folder); + LLInventoryModel::LLCategoryUpdate new_folder(folder_id, 1); + update.push_back(new_folder); + gInventory.accountForUpdate(update); + + new_item->setParent(folder_id); + new_item->updateParentOnServer(false); + } + + removeObserver(); + + gInventory.updateItem(new_item); + gInventory.notifyObservers(); + + closeFloater(); +} + +void LLFloaterCreateLandmark::onCancelClicked() +{ + removeObserver(); + if (!mItem.isNull()) + { + LLUUID item_id = mItem->getUUID(); + remove_inventory_item(item_id, NULL); + } + closeFloater(); +} + + +void LLFloaterCreateLandmark::setItem(const uuid_set_t& items) +{ + for (uuid_set_t::const_iterator item_iter = items.begin(); + item_iter != items.end(); + ++item_iter) + { + const LLUUID& item_id = (*item_iter); + if(!highlight_offered_object(item_id)) + { + continue; + } + + LLInventoryItem* item = gInventory.getItem(item_id); + + llassert(item); + if (item && (LLAssetType::AT_LANDMARK == item->getType()) ) + { + if(!getItem()) + { + mItem = item; + mAssetID = mItem->getAssetUUID(); + setVisibleAndFrontmost(true); + break; + } + } + } +} + +void LLFloaterCreateLandmark::updateItem(const uuid_set_t& items, U32 mask) +{ + if (!getItem()) + { + return; + } + + LLUUID landmark_id = getItem()->getUUID(); + + for (uuid_set_t::const_iterator item_iter = items.begin(); + item_iter != items.end(); + ++item_iter) + { + const LLUUID& item_id = (*item_iter); + if (landmark_id == item_id) + { + if (getItem() != gInventory.getItem(item_id)) + { + // item is obsolete or removed + closeFloater(); + } + + LLUUID folder_id = mFolderCombo->getValue().asUUID(); + if (folder_id != mItem->getParentUUID()) + { + // user moved landmark in inventory, + // assume that we are done all other changes should already be commited + closeFloater(); + } + + if ((mask & LLInventoryObserver::INTERNAL) && mAssetID != mItem->getAssetUUID()) + { + closeFloater(); + } + + if (mask & LLInventoryObserver::LABEL) + { + mLandmarkTitleEditor->setText(mItem->getName()); + } + + if (mask & LLInventoryObserver::INTERNAL) + { + mNotesEditor->setText(mItem->getDescription()); + } + } + } +} diff --git a/indra/newview/llfloatercreatelandmark.h b/indra/newview/llfloatercreatelandmark.h index 1660621bc9..fa6d001b8e 100644 --- a/indra/newview/llfloatercreatelandmark.h +++ b/indra/newview/llfloatercreatelandmark.h @@ -1,77 +1,77 @@ -/** - * @file llfloatercreatelandmark.h - * @brief LLFloaterCreateLandmark class definition - * - * $LicenseInfo:firstyear=2021&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2021, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERCREATELANDMARK_H -#define LL_LLFLOATERCREATELANDMARK_H - -#include "llfloater.h" - -class LLComboBox; -class LLInventoryItem; -class LLLineEditor; -class LLTextEditor; -class LLLandmarksInventoryObserver; - -class LLFloaterCreateLandmark: - public LLFloater -{ - friend class LLFloaterReg; - -public: - - LLFloaterCreateLandmark(const LLSD& key); - ~LLFloaterCreateLandmark(); - - bool postBuild() override; - void onOpen(const LLSD& key) override; - - void setItem(const uuid_set_t& items); - void updateItem(const uuid_set_t& items, U32 mask); - - LLInventoryItem* getItem() { return mItem; } - -private: - void setLandmarkInfo(const LLUUID &folder_id); - void removeObserver(); - void populateFoldersList(const LLUUID &folder_id = LLUUID::null); - void onCommitTextChanges(); - void onCreateFolderClicked(); - void onSaveClicked(); - void onCancelClicked(); - - void folderCreatedCallback(LLUUID folder_id); - - LLComboBox* mFolderCombo; - LLLineEditor* mLandmarkTitleEditor; - LLTextEditor* mNotesEditor; - LLUUID mLandmarksID; - LLUUID mAssetID; - - LLLandmarksInventoryObserver* mInventoryObserver; - LLPointer mItem; -}; - -#endif +/** + * @file llfloatercreatelandmark.h + * @brief LLFloaterCreateLandmark class definition + * + * $LicenseInfo:firstyear=2021&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2021, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERCREATELANDMARK_H +#define LL_LLFLOATERCREATELANDMARK_H + +#include "llfloater.h" + +class LLComboBox; +class LLInventoryItem; +class LLLineEditor; +class LLTextEditor; +class LLLandmarksInventoryObserver; + +class LLFloaterCreateLandmark: + public LLFloater +{ + friend class LLFloaterReg; + +public: + + LLFloaterCreateLandmark(const LLSD& key); + ~LLFloaterCreateLandmark(); + + bool postBuild() override; + void onOpen(const LLSD& key) override; + + void setItem(const uuid_set_t& items); + void updateItem(const uuid_set_t& items, U32 mask); + + LLInventoryItem* getItem() { return mItem; } + +private: + void setLandmarkInfo(const LLUUID &folder_id); + void removeObserver(); + void populateFoldersList(const LLUUID &folder_id = LLUUID::null); + void onCommitTextChanges(); + void onCreateFolderClicked(); + void onSaveClicked(); + void onCancelClicked(); + + void folderCreatedCallback(LLUUID folder_id); + + LLComboBox* mFolderCombo; + LLLineEditor* mLandmarkTitleEditor; + LLTextEditor* mNotesEditor; + LLUUID mLandmarksID; + LLUUID mAssetID; + + LLLandmarksInventoryObserver* mInventoryObserver; + LLPointer mItem; +}; + +#endif diff --git a/indra/newview/llfloaterdeleteprefpreset.cpp b/indra/newview/llfloaterdeleteprefpreset.cpp index 4f3c2485cf..1a01122afb 100644 --- a/indra/newview/llfloaterdeleteprefpreset.cpp +++ b/indra/newview/llfloaterdeleteprefpreset.cpp @@ -1,117 +1,117 @@ -/** - * @file llfloaterdeleteprefpreset.cpp - * @brief Floater to delete a graphics / camera preset - * - * $LicenseInfo:firstyear=2014&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2014, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterdeleteprefpreset.h" - -#include "llbutton.h" -#include "llcombobox.h" -#include "llfloaterpreference.h" -#include "llnotificationsutil.h" -#include "llpresetsmanager.h" -#include "llviewercontrol.h" -#include "llfloaterreg.h" - -LLFloaterDeletePrefPreset::LLFloaterDeletePrefPreset(const LLSD &key) -: LLFloater(key) -{ -} - -// virtual -bool LLFloaterDeletePrefPreset::postBuild() -{ - LLFloaterPreference* preferences = LLFloaterReg::getTypedInstance("preferences"); - if (preferences) - { - preferences->addDependentFloater(this); - } - getChild("delete")->setCommitCallback(boost::bind(&LLFloaterDeletePrefPreset::onBtnDelete, this)); - getChild("cancel")->setCommitCallback(boost::bind(&LLFloaterDeletePrefPreset::onBtnCancel, this)); - LLPresetsManager::instance().setPresetListChangeCallback(boost::bind(&LLFloaterDeletePrefPreset::onPresetsListChange, this)); - - return true; -} - -void LLFloaterDeletePrefPreset::onOpen(const LLSD& key) -{ - mSubdirectory = key.asString(); - std::string title_type = std::string("title_") + mSubdirectory; - if (hasString(title_type)) - { - std::string floater_title = getString(title_type); - setTitle(floater_title); - } - else - { - LL_WARNS() << title_type << " not found" << LL_ENDL; - setTitle(title_type); - } - - LLComboBox* combo = getChild("preset_combo"); - EDefaultOptions option = DEFAULT_HIDE; - bool action; - action = LLPresetsManager::getInstance()->setPresetNamesInComboBox(mSubdirectory, combo, option); - - LLButton* delete_btn = getChild("delete"); - delete_btn->setEnabled(action); -} - -void LLFloaterDeletePrefPreset::onBtnDelete() -{ - LLComboBox* combo = getChild("preset_combo"); - std::string name = combo->getSimple(); - - if (!LLPresetsManager::getInstance()->deletePreset(mSubdirectory, name)) - { - LLSD args; - args["NAME"] = name; - LLNotificationsUtil::add("PresetNotDeleted", args); - } - else if (mSubdirectory == PRESETS_CAMERA) - { - if (gSavedSettings.getString("PresetCameraActive") == name) - { - gSavedSettings.setString("PresetCameraActive", ""); - } - } - - closeFloater(); -} - -void LLFloaterDeletePrefPreset::onPresetsListChange() -{ - LLComboBox* combo = getChild("preset_combo"); - - EDefaultOptions option = DEFAULT_HIDE; - - LLPresetsManager::getInstance()->setPresetNamesInComboBox(mSubdirectory, combo, option); -} - -void LLFloaterDeletePrefPreset::onBtnCancel() -{ - closeFloater(); -} +/** + * @file llfloaterdeleteprefpreset.cpp + * @brief Floater to delete a graphics / camera preset + * + * $LicenseInfo:firstyear=2014&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterdeleteprefpreset.h" + +#include "llbutton.h" +#include "llcombobox.h" +#include "llfloaterpreference.h" +#include "llnotificationsutil.h" +#include "llpresetsmanager.h" +#include "llviewercontrol.h" +#include "llfloaterreg.h" + +LLFloaterDeletePrefPreset::LLFloaterDeletePrefPreset(const LLSD &key) +: LLFloater(key) +{ +} + +// virtual +bool LLFloaterDeletePrefPreset::postBuild() +{ + LLFloaterPreference* preferences = LLFloaterReg::getTypedInstance("preferences"); + if (preferences) + { + preferences->addDependentFloater(this); + } + getChild("delete")->setCommitCallback(boost::bind(&LLFloaterDeletePrefPreset::onBtnDelete, this)); + getChild("cancel")->setCommitCallback(boost::bind(&LLFloaterDeletePrefPreset::onBtnCancel, this)); + LLPresetsManager::instance().setPresetListChangeCallback(boost::bind(&LLFloaterDeletePrefPreset::onPresetsListChange, this)); + + return true; +} + +void LLFloaterDeletePrefPreset::onOpen(const LLSD& key) +{ + mSubdirectory = key.asString(); + std::string title_type = std::string("title_") + mSubdirectory; + if (hasString(title_type)) + { + std::string floater_title = getString(title_type); + setTitle(floater_title); + } + else + { + LL_WARNS() << title_type << " not found" << LL_ENDL; + setTitle(title_type); + } + + LLComboBox* combo = getChild("preset_combo"); + EDefaultOptions option = DEFAULT_HIDE; + bool action; + action = LLPresetsManager::getInstance()->setPresetNamesInComboBox(mSubdirectory, combo, option); + + LLButton* delete_btn = getChild("delete"); + delete_btn->setEnabled(action); +} + +void LLFloaterDeletePrefPreset::onBtnDelete() +{ + LLComboBox* combo = getChild("preset_combo"); + std::string name = combo->getSimple(); + + if (!LLPresetsManager::getInstance()->deletePreset(mSubdirectory, name)) + { + LLSD args; + args["NAME"] = name; + LLNotificationsUtil::add("PresetNotDeleted", args); + } + else if (mSubdirectory == PRESETS_CAMERA) + { + if (gSavedSettings.getString("PresetCameraActive") == name) + { + gSavedSettings.setString("PresetCameraActive", ""); + } + } + + closeFloater(); +} + +void LLFloaterDeletePrefPreset::onPresetsListChange() +{ + LLComboBox* combo = getChild("preset_combo"); + + EDefaultOptions option = DEFAULT_HIDE; + + LLPresetsManager::getInstance()->setPresetNamesInComboBox(mSubdirectory, combo, option); +} + +void LLFloaterDeletePrefPreset::onBtnCancel() +{ + closeFloater(); +} diff --git a/indra/newview/llfloaterdeleteprefpreset.h b/indra/newview/llfloaterdeleteprefpreset.h index 95a4cd248f..f6ba8c2d19 100644 --- a/indra/newview/llfloaterdeleteprefpreset.h +++ b/indra/newview/llfloaterdeleteprefpreset.h @@ -1,53 +1,53 @@ -/** - * @file llfloaterdeleteprefpreset.h - * @brief Floater to delete a graphics / camera preset - - * - * $LicenseInfo:firstyear=2014&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2014, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERDELETEPREFPRESET_H -#define LL_LLFLOATERDELETEPREFPRESET_H - -#include "llfloater.h" - -class LLComboBox; - -class LLFloaterDeletePrefPreset : public LLFloater -{ - -public: - LLFloaterDeletePrefPreset(const LLSD &key); - - bool postBuild() override; - void onOpen(const LLSD& key) override; - - void onBtnDelete(); - void onBtnCancel(); - -private: - void onPresetsListChange(); - - std::string mSubdirectory; -}; - -#endif // LL_LLFLOATERDELETEPREFPRESET_H +/** + * @file llfloaterdeleteprefpreset.h + * @brief Floater to delete a graphics / camera preset + + * + * $LicenseInfo:firstyear=2014&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERDELETEPREFPRESET_H +#define LL_LLFLOATERDELETEPREFPRESET_H + +#include "llfloater.h" + +class LLComboBox; + +class LLFloaterDeletePrefPreset : public LLFloater +{ + +public: + LLFloaterDeletePrefPreset(const LLSD &key); + + bool postBuild() override; + void onOpen(const LLSD& key) override; + + void onBtnDelete(); + void onBtnCancel(); + +private: + void onPresetsListChange(); + + std::string mSubdirectory; +}; + +#endif // LL_LLFLOATERDELETEPREFPRESET_H diff --git a/indra/newview/llfloaterdestinations.cpp b/indra/newview/llfloaterdestinations.cpp index 673bbb0902..93cf02e835 100644 --- a/indra/newview/llfloaterdestinations.cpp +++ b/indra/newview/llfloaterdestinations.cpp @@ -1,54 +1,54 @@ -/** - * @file llfloaterdestinations.h - * @author Leyla Farazha - * @brief floater for the destinations guide - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/** - * Floater that appears when buying an object, giving a preview - * of its contents and their permissions. - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterdestinations.h" -#include "lluictrlfactory.h" - - -LLFloaterDestinations::LLFloaterDestinations(const LLSD& key) - : LLFloater(key) -{ -} - -LLFloaterDestinations::~LLFloaterDestinations() -{ -} - -bool LLFloaterDestinations::postBuild() -{ - enableResizeCtrls(true, true, false); - return true; -} - - +/** + * @file llfloaterdestinations.h + * @author Leyla Farazha + * @brief floater for the destinations guide + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/** + * Floater that appears when buying an object, giving a preview + * of its contents and their permissions. + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterdestinations.h" +#include "lluictrlfactory.h" + + +LLFloaterDestinations::LLFloaterDestinations(const LLSD& key) + : LLFloater(key) +{ +} + +LLFloaterDestinations::~LLFloaterDestinations() +{ +} + +bool LLFloaterDestinations::postBuild() +{ + enableResizeCtrls(true, true, false); + return true; +} + + diff --git a/indra/newview/llfloaterdestinations.h b/indra/newview/llfloaterdestinations.h index 43b8177bb4..e2bc06882a 100644 --- a/indra/newview/llfloaterdestinations.h +++ b/indra/newview/llfloaterdestinations.h @@ -1,43 +1,43 @@ -/** - * @file llfloaterdestinations.h - * @author Leyla Farazha - * @brief floater for the destinations guide - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_FLOATER_DESTINATIONS_H -#define LL_FLOATER_DESTINATIONS_H - -#include "llfloater.h" - -class LLFloaterDestinations: - public LLFloater -{ - friend class LLFloaterReg; -private: - LLFloaterDestinations(const LLSD& key); - ~LLFloaterDestinations(); - bool postBuild() override; -}; - -#endif +/** + * @file llfloaterdestinations.h + * @author Leyla Farazha + * @brief floater for the destinations guide + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_FLOATER_DESTINATIONS_H +#define LL_FLOATER_DESTINATIONS_H + +#include "llfloater.h" + +class LLFloaterDestinations: + public LLFloater +{ + friend class LLFloaterReg; +private: + LLFloaterDestinations(const LLSD& key); + ~LLFloaterDestinations(); + bool postBuild() override; +}; + +#endif diff --git a/indra/newview/llfloaterdisplayname.cpp b/indra/newview/llfloaterdisplayname.cpp index 7b0046443e..236aadfbc1 100644 --- a/indra/newview/llfloaterdisplayname.cpp +++ b/indra/newview/llfloaterdisplayname.cpp @@ -1,242 +1,242 @@ -/** - * @file llfloaterdisplayname.cpp - * @author Leyla Farazha - * @brief Implementation of the LLFloaterDisplayName class. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#include "llviewerprecompiledheaders.h" -#include "llfloaterreg.h" -#include "llfloater.h" - -#include "llnotificationsutil.h" -#include "llviewerdisplayname.h" - -#include "llnotifications.h" -#include "llfloaterdisplayname.h" -#include "llavatarnamecache.h" - -#include "llagent.h" - - -class LLFloaterDisplayName : public LLFloater -{ -public: - LLFloaterDisplayName(const LLSD& key); - virtual ~LLFloaterDisplayName() { } - /*virtual*/ bool postBuild(); - void onSave(); - void onReset(); - void onCancel(); - /*virtual*/ void onOpen(const LLSD& key); - -private: - - void onCacheSetName(bool success, - const std::string& reason, - const LLSD& content); -}; - -LLFloaterDisplayName::LLFloaterDisplayName(const LLSD& key) : - LLFloater(key) -{ -} - -void LLFloaterDisplayName::onOpen(const LLSD& key) -{ - getChild("display_name_editor")->clear(); - getChild("display_name_confirm")->clear(); - - LLAvatarName av_name; - LLAvatarNameCache::get(gAgent.getID(), &av_name); - - F64 now_secs = LLDate::now().secondsSinceEpoch(); - - if (now_secs < av_name.mNextUpdate) - { - // ...can't update until some time in the future - F64 next_update_local_secs = - av_name.mNextUpdate - LLStringOps::getLocalTimeOffset(); - LLDate next_update_local(next_update_local_secs); - // display as "July 18 12:17 PM" - std::string next_update_string = - next_update_local.toHTTPDateString("%B %d %I:%M %p"); - getChild("lockout_text")->setTextArg("[TIME]", next_update_string); - getChild("lockout_text")->setVisible(true); - getChild("save_btn")->setEnabled(false); - getChild("display_name_editor")->setEnabled(false); - getChild("display_name_confirm")->setEnabled(false); - getChild("cancel_btn")->setFocus(true); - - } - else - { - getChild("lockout_text")->setVisible(false); - getChild("save_btn")->setEnabled(true); - getChild("display_name_editor")->setEnabled(true); - getChild("display_name_confirm")->setEnabled(true); - - } -} - -bool LLFloaterDisplayName::postBuild() -{ - getChild("reset_btn")->setCommitCallback(boost::bind(&LLFloaterDisplayName::onReset, this)); - getChild("cancel_btn")->setCommitCallback(boost::bind(&LLFloaterDisplayName::onCancel, this)); - getChild("save_btn")->setCommitCallback(boost::bind(&LLFloaterDisplayName::onSave, this)); - - center(); - - return true; -} - -void LLFloaterDisplayName::onCacheSetName(bool success, - const std::string& reason, - const LLSD& content) -{ - if (success) - { - // Inform the user that the change took place, but will take a while - // to percolate. - LLSD args; - args["DISPLAY_NAME"] = content["display_name"]; - LLNotificationsUtil::add("SetDisplayNameSuccess", args); - return; - } - - // Request failed, notify the user - std::string error_tag = content["error_tag"].asString(); - LL_INFOS() << "set name failure error_tag " << error_tag << LL_ENDL; - - // We might have a localized string for this message - // error_args will usually be empty from the server. - if (!error_tag.empty() - && LLNotifications::getInstance()->templateExists(error_tag)) - { - LLNotificationsUtil::add(error_tag); - return; - } - - // The server error might have a localized message for us - std::string lang_code = LLUI::getLanguage(); - LLSD error_desc = content["error_description"]; - if (error_desc.has( lang_code )) - { - LLSD args; - args["MESSAGE"] = error_desc[lang_code].asString(); - LLNotificationsUtil::add("GenericAlert", args); - return; - } - - // No specific error, throw a generic one - LLNotificationsUtil::add("SetDisplayNameFailedGeneric"); -} - -void LLFloaterDisplayName::onCancel() -{ - setVisible(false); -} - -void LLFloaterDisplayName::onReset() -{ - LLAvatarName av_name; - if (!LLAvatarNameCache::get(gAgent.getID(), &av_name)) - { - return; - } - getChild("display_name_editor")->setValue(av_name.getUserName()); - - if (getChild("display_name_editor")->getEnabled()) - { - // UI is enabled, fill the first field - getChild("display_name_confirm")->clear(); - getChild("display_name_confirm")->setFocus(true); - } - else - { - // UI is disabled, looks like we should allow resetting - // even if user already set a display name, enable save button - getChild("display_name_confirm")->setValue(av_name.getUserName()); - getChild("save_btn")->setEnabled(true); - } -} - - -void LLFloaterDisplayName::onSave() -{ - std::string display_name_utf8 = getChild("display_name_editor")->getValue().asString(); - std::string display_name_confirm = getChild("display_name_confirm")->getValue().asString(); - - if (display_name_utf8.compare(display_name_confirm)) - { - LLNotificationsUtil::add("SetDisplayNameMismatch"); - return; - } - - LLAvatarName av_name; - if (!LLAvatarNameCache::get(gAgent.getID(), &av_name)) - { - return; - } - - std::string user_name = av_name.getUserName(); - if (display_name_utf8.compare(user_name) == 0 - && LLAvatarNameCache::getInstance()->hasNameLookupURL()) - { - // A reset - LLViewerDisplayName::set("", boost::bind(&LLFloaterDisplayName::onCacheSetName, this, _1, _2, _3)); - return; - } - - const U32 DISPLAY_NAME_MAX_LENGTH = 31; // characters, not bytes - LLWString display_name_wstr = utf8string_to_wstring(display_name_utf8); - if (display_name_wstr.size() > DISPLAY_NAME_MAX_LENGTH) - { - LLSD args; - args["LENGTH"] = llformat("%d", DISPLAY_NAME_MAX_LENGTH); - LLNotificationsUtil::add("SetDisplayNameFailedLength", args); - return; - } - - if (LLAvatarNameCache::getInstance()->hasNameLookupURL()) - { - LLViewerDisplayName::set(display_name_utf8,boost::bind(&LLFloaterDisplayName::onCacheSetName, this, _1, _2, _3)); - } - else - { - LLNotificationsUtil::add("SetDisplayNameFailedGeneric"); - } - - setVisible(false); -} - - -////////////////////////////////////////////////////////////////////////////// -// LLInspectObjectUtil -////////////////////////////////////////////////////////////////////////////// -void LLFloaterDisplayNameUtil::registerFloater() -{ - LLFloaterReg::add("display_name", "floater_display_name.xml", - &LLFloaterReg::build); -} +/** + * @file llfloaterdisplayname.cpp + * @author Leyla Farazha + * @brief Implementation of the LLFloaterDisplayName class. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#include "llviewerprecompiledheaders.h" +#include "llfloaterreg.h" +#include "llfloater.h" + +#include "llnotificationsutil.h" +#include "llviewerdisplayname.h" + +#include "llnotifications.h" +#include "llfloaterdisplayname.h" +#include "llavatarnamecache.h" + +#include "llagent.h" + + +class LLFloaterDisplayName : public LLFloater +{ +public: + LLFloaterDisplayName(const LLSD& key); + virtual ~LLFloaterDisplayName() { } + /*virtual*/ bool postBuild(); + void onSave(); + void onReset(); + void onCancel(); + /*virtual*/ void onOpen(const LLSD& key); + +private: + + void onCacheSetName(bool success, + const std::string& reason, + const LLSD& content); +}; + +LLFloaterDisplayName::LLFloaterDisplayName(const LLSD& key) : + LLFloater(key) +{ +} + +void LLFloaterDisplayName::onOpen(const LLSD& key) +{ + getChild("display_name_editor")->clear(); + getChild("display_name_confirm")->clear(); + + LLAvatarName av_name; + LLAvatarNameCache::get(gAgent.getID(), &av_name); + + F64 now_secs = LLDate::now().secondsSinceEpoch(); + + if (now_secs < av_name.mNextUpdate) + { + // ...can't update until some time in the future + F64 next_update_local_secs = + av_name.mNextUpdate - LLStringOps::getLocalTimeOffset(); + LLDate next_update_local(next_update_local_secs); + // display as "July 18 12:17 PM" + std::string next_update_string = + next_update_local.toHTTPDateString("%B %d %I:%M %p"); + getChild("lockout_text")->setTextArg("[TIME]", next_update_string); + getChild("lockout_text")->setVisible(true); + getChild("save_btn")->setEnabled(false); + getChild("display_name_editor")->setEnabled(false); + getChild("display_name_confirm")->setEnabled(false); + getChild("cancel_btn")->setFocus(true); + + } + else + { + getChild("lockout_text")->setVisible(false); + getChild("save_btn")->setEnabled(true); + getChild("display_name_editor")->setEnabled(true); + getChild("display_name_confirm")->setEnabled(true); + + } +} + +bool LLFloaterDisplayName::postBuild() +{ + getChild("reset_btn")->setCommitCallback(boost::bind(&LLFloaterDisplayName::onReset, this)); + getChild("cancel_btn")->setCommitCallback(boost::bind(&LLFloaterDisplayName::onCancel, this)); + getChild("save_btn")->setCommitCallback(boost::bind(&LLFloaterDisplayName::onSave, this)); + + center(); + + return true; +} + +void LLFloaterDisplayName::onCacheSetName(bool success, + const std::string& reason, + const LLSD& content) +{ + if (success) + { + // Inform the user that the change took place, but will take a while + // to percolate. + LLSD args; + args["DISPLAY_NAME"] = content["display_name"]; + LLNotificationsUtil::add("SetDisplayNameSuccess", args); + return; + } + + // Request failed, notify the user + std::string error_tag = content["error_tag"].asString(); + LL_INFOS() << "set name failure error_tag " << error_tag << LL_ENDL; + + // We might have a localized string for this message + // error_args will usually be empty from the server. + if (!error_tag.empty() + && LLNotifications::getInstance()->templateExists(error_tag)) + { + LLNotificationsUtil::add(error_tag); + return; + } + + // The server error might have a localized message for us + std::string lang_code = LLUI::getLanguage(); + LLSD error_desc = content["error_description"]; + if (error_desc.has( lang_code )) + { + LLSD args; + args["MESSAGE"] = error_desc[lang_code].asString(); + LLNotificationsUtil::add("GenericAlert", args); + return; + } + + // No specific error, throw a generic one + LLNotificationsUtil::add("SetDisplayNameFailedGeneric"); +} + +void LLFloaterDisplayName::onCancel() +{ + setVisible(false); +} + +void LLFloaterDisplayName::onReset() +{ + LLAvatarName av_name; + if (!LLAvatarNameCache::get(gAgent.getID(), &av_name)) + { + return; + } + getChild("display_name_editor")->setValue(av_name.getUserName()); + + if (getChild("display_name_editor")->getEnabled()) + { + // UI is enabled, fill the first field + getChild("display_name_confirm")->clear(); + getChild("display_name_confirm")->setFocus(true); + } + else + { + // UI is disabled, looks like we should allow resetting + // even if user already set a display name, enable save button + getChild("display_name_confirm")->setValue(av_name.getUserName()); + getChild("save_btn")->setEnabled(true); + } +} + + +void LLFloaterDisplayName::onSave() +{ + std::string display_name_utf8 = getChild("display_name_editor")->getValue().asString(); + std::string display_name_confirm = getChild("display_name_confirm")->getValue().asString(); + + if (display_name_utf8.compare(display_name_confirm)) + { + LLNotificationsUtil::add("SetDisplayNameMismatch"); + return; + } + + LLAvatarName av_name; + if (!LLAvatarNameCache::get(gAgent.getID(), &av_name)) + { + return; + } + + std::string user_name = av_name.getUserName(); + if (display_name_utf8.compare(user_name) == 0 + && LLAvatarNameCache::getInstance()->hasNameLookupURL()) + { + // A reset + LLViewerDisplayName::set("", boost::bind(&LLFloaterDisplayName::onCacheSetName, this, _1, _2, _3)); + return; + } + + const U32 DISPLAY_NAME_MAX_LENGTH = 31; // characters, not bytes + LLWString display_name_wstr = utf8string_to_wstring(display_name_utf8); + if (display_name_wstr.size() > DISPLAY_NAME_MAX_LENGTH) + { + LLSD args; + args["LENGTH"] = llformat("%d", DISPLAY_NAME_MAX_LENGTH); + LLNotificationsUtil::add("SetDisplayNameFailedLength", args); + return; + } + + if (LLAvatarNameCache::getInstance()->hasNameLookupURL()) + { + LLViewerDisplayName::set(display_name_utf8,boost::bind(&LLFloaterDisplayName::onCacheSetName, this, _1, _2, _3)); + } + else + { + LLNotificationsUtil::add("SetDisplayNameFailedGeneric"); + } + + setVisible(false); +} + + +////////////////////////////////////////////////////////////////////////////// +// LLInspectObjectUtil +////////////////////////////////////////////////////////////////////////////// +void LLFloaterDisplayNameUtil::registerFloater() +{ + LLFloaterReg::add("display_name", "floater_display_name.xml", + &LLFloaterReg::build); +} diff --git a/indra/newview/llfloatereditextdaycycle.cpp b/indra/newview/llfloatereditextdaycycle.cpp index aaaeab4b04..5e3e8a7838 100644 --- a/indra/newview/llfloatereditextdaycycle.cpp +++ b/indra/newview/llfloatereditextdaycycle.cpp @@ -1,1854 +1,1854 @@ -/** - * @file llfloatereditextdaycycle.cpp - * @brief Floater to create or edit a day cycle - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloatereditextdaycycle.h" - -// libs -#include "llbutton.h" -#include "llcallbacklist.h" -#include "llcheckboxctrl.h" -#include "llcombobox.h" -#include "llloadingindicator.h" -#include "lllocalbitmaps.h" -#include "llmultisliderctrl.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "llspinctrl.h" -#include "lltimectrl.h" -#include "lltabcontainer.h" -#include "llfilepicker.h" - -#include "llsettingsvo.h" -#include "llinventorymodel.h" -#include "llviewerparcelmgr.h" - -#include "llsettingspicker.h" -#include "lltrackpicker.h" - -// newview -#include "llagent.h" -#include "llappviewer.h" //gDisconected -#include "llparcel.h" -#include "llflyoutcombobtn.h" //Todo: make a proper UI element/button/panel instead -#include "llregioninfomodel.h" -#include "llviewermenufile.h" // LLFilePickerReplyThread -#include "llviewerregion.h" -#include "llpaneleditwater.h" -#include "llpaneleditsky.h" - -#include "llui.h" - -#include "llenvironment.h" -#include "lltrans.h" - -extern LLControlGroup gSavedSettings; - -//========================================================================= -namespace { - const std::string track_tabs[] = { - "water_track", - "sky1_track", - "sky2_track", - "sky3_track", - "sky4_track", - }; - - const std::string ICN_LOCK_EDIT("icn_lock_edit"); - const std::string BTN_SAVE("save_btn"); - const std::string BTN_FLYOUT("btn_flyout"); - const std::string BTN_CANCEL("cancel_btn"); - const std::string BTN_ADDFRAME("add_frame"); - const std::string BTN_DELFRAME("delete_frame"); - const std::string BTN_IMPORT("btn_import"); - const std::string BTN_LOADFRAME("btn_load_frame"); - const std::string BTN_CLONETRACK("copy_track"); - const std::string BTN_LOADTRACK("load_track"); - const std::string BTN_CLEARTRACK("clear_track"); - const std::string SLDR_TIME("WLTimeSlider"); - const std::string SLDR_KEYFRAMES("WLDayCycleFrames"); - const std::string VIEW_SKY_SETTINGS("frame_settings_sky"); - const std::string VIEW_WATER_SETTINGS("frame_settings_water"); - const std::string LBL_CURRENT_TIME("current_time"); - const std::string TXT_DAY_NAME("day_cycle_name"); - const std::string TABS_SKYS("sky_tabs"); - const std::string TABS_WATER("water_tabs"); - - // 'Play' buttons - const std::string BTN_PLAY("play_btn"); - const std::string BTN_SKIP_BACK("skip_back_btn"); - const std::string BTN_SKIP_FORWARD("skip_forward_btn"); - - const std::string EVNT_DAYTRACK("DayCycle.Track"); - const std::string EVNT_PLAY("DayCycle.PlayActions"); - - const std::string ACTION_PLAY("play"); - const std::string ACTION_PAUSE("pause"); - const std::string ACTION_FORWARD("forward"); - const std::string ACTION_BACK("back"); - - // For flyout - const std::string XML_FLYOUTMENU_FILE("menu_save_settings.xml"); - // From menu_save_settings.xml, consider moving into flyout since it should be supported by flyout either way - const std::string ACTION_SAVE("save_settings"); - const std::string ACTION_SAVEAS("save_as_new_settings"); - const std::string ACTION_COMMIT("commit_changes"); - const std::string ACTION_APPLY_LOCAL("apply_local"); - const std::string ACTION_APPLY_PARCEL("apply_parcel"); - const std::string ACTION_APPLY_REGION("apply_region"); - - const F32 DAY_CYCLE_PLAY_TIME_SECONDS = 60; - - const std::string STR_COMMIT_PARCEL("commit_parcel"); - const std::string STR_COMMIT_REGION("commit_region"); - //--------------------------------------------------------------------- - -} - -//========================================================================= -const std::string LLFloaterEditExtDayCycle::KEY_EDIT_CONTEXT("edit_context"); -const std::string LLFloaterEditExtDayCycle::KEY_DAY_LENGTH("day_length"); -const std::string LLFloaterEditExtDayCycle::KEY_CANMOD("canmod"); - -const std::string LLFloaterEditExtDayCycle::VALUE_CONTEXT_INVENTORY("inventory"); -const std::string LLFloaterEditExtDayCycle::VALUE_CONTEXT_PARCEL("parcel"); -const std::string LLFloaterEditExtDayCycle::VALUE_CONTEXT_REGION("region"); -/* -//========================================================================= - -class LLDaySettingCopiedCallback : public LLInventoryCallback -{ -public: - LLDaySettingCopiedCallback(LLHandle handle) : mHandle(handle) {} - - virtual void fire(const LLUUID& inv_item_id) - { - if (!mHandle.isDead()) - { - LLViewerInventoryItem* item = gInventory.getItem(inv_item_id); - if (item) - { - LLFloaterEditExtDayCycle* floater = (LLFloaterEditExtDayCycle*)mHandle.get(); - floater->onInventoryCreated(item->getAssetUUID(), inv_item_id); - } - } - } - -private: - LLHandle mHandle; -};*/ - -//========================================================================= - -LLFloaterEditExtDayCycle::LLFloaterEditExtDayCycle(const LLSD &key) : - LLFloaterEditEnvironmentBase(key), - mFlyoutControl(nullptr), - mDayLength(0), - mCurrentTrack(1), - mShiftCopyEnabled(false), - mTimeSlider(nullptr), - mFramesSlider(nullptr), - mCurrentTimeLabel(nullptr), - mImportButton(nullptr), - mLoadFrame(nullptr), - mSkyBlender(), - mWaterBlender(), - mScratchSky(), - mScratchWater(), - mIsPlaying(false), - mCloneTrack(nullptr), - mLoadTrack(nullptr), - mClearTrack(nullptr) -{ - - mCommitCallbackRegistrar.add(EVNT_DAYTRACK, [this](LLUICtrl *ctrl, const LLSD &data) { onTrackSelectionCallback(data); }); - mCommitCallbackRegistrar.add(EVNT_PLAY, [this](LLUICtrl *ctrl, const LLSD &data) { onPlayActionCallback(data); }); - - mScratchSky = LLSettingsVOSky::buildDefaultSky(); - mScratchWater = LLSettingsVOWater::buildDefaultWater(); - - mEditSky = mScratchSky; - mEditWater = mScratchWater; -} - -LLFloaterEditExtDayCycle::~LLFloaterEditExtDayCycle() -{ - // Todo: consider remaking mFlyoutControl into full view class that initializes intself with floater, - // complete with postbuild, e t c... - delete mFlyoutControl; -} - -// virtual -bool LLFloaterEditExtDayCycle::postBuild() -{ - getChild(TXT_DAY_NAME)->setKeystrokeCallback(boost::bind(&LLFloaterEditExtDayCycle::onCommitName, this, _1, _2), NULL); - - mAddFrameButton = getChild(BTN_ADDFRAME, true); - mDeleteFrameButton = getChild(BTN_DELFRAME, true); - mTimeSlider = getChild(SLDR_TIME); - mFramesSlider = getChild(SLDR_KEYFRAMES); - mSkyTabLayoutContainer = getChild(VIEW_SKY_SETTINGS, true); - mWaterTabLayoutContainer = getChild(VIEW_WATER_SETTINGS, true); - mCurrentTimeLabel = getChild(LBL_CURRENT_TIME, true); - mImportButton = getChild(BTN_IMPORT, true); - mLoadFrame = getChild(BTN_LOADFRAME, true); - mCloneTrack = getChild(BTN_CLONETRACK, true); - mLoadTrack = getChild(BTN_LOADTRACK, true); - mClearTrack = getChild(BTN_CLEARTRACK, true); - - mFlyoutControl = new LLFlyoutComboBtnCtrl(this, BTN_SAVE, BTN_FLYOUT, XML_FLYOUTMENU_FILE, false); - mFlyoutControl->setAction([this](LLUICtrl *ctrl, const LLSD &data) { onButtonApply(ctrl, data); }); - - getChild(BTN_CANCEL, true)->setCommitCallback([this](LLUICtrl *ctrl, const LLSD &data) { onClickCloseBtn(); }); - mTimeSlider->setCommitCallback([this](LLUICtrl *ctrl, const LLSD &data) { onTimeSliderCallback(); }); - mAddFrameButton->setCommitCallback([this](LLUICtrl *ctrl, const LLSD &data) { onAddFrame(); }); - mDeleteFrameButton->setCommitCallback([this](LLUICtrl *ctrl, const LLSD &data) { onRemoveFrame(); }); - mImportButton->setCommitCallback([this](LLUICtrl *, const LLSD &){ onButtonImport(); }); - mLoadFrame->setCommitCallback([this](LLUICtrl *, const LLSD &){ onButtonLoadFrame(); }); - - mCloneTrack->setCommitCallback([this](LLUICtrl *, const LLSD&){ onCloneTrack(); }); - mLoadTrack->setCommitCallback([this](LLUICtrl *, const LLSD&){ onLoadTrack();}); - mClearTrack->setCommitCallback([this](LLUICtrl *, const LLSD&){ onClearTrack(); }); - - - mFramesSlider->setCommitCallback([this](LLUICtrl *, const LLSD &data) { onFrameSliderCallback(data); }); - mFramesSlider->setDoubleClickCallback([this](LLUICtrl*, S32 x, S32 y, MASK mask){ onFrameSliderDoubleClick(x, y, mask); }); - mFramesSlider->setMouseDownCallback([this](LLUICtrl*, S32 x, S32 y, MASK mask){ onFrameSliderMouseDown(x, y, mask); }); - mFramesSlider->setMouseUpCallback([this](LLUICtrl*, S32 x, S32 y, MASK mask){ onFrameSliderMouseUp(x, y, mask); }); - - mTimeSlider->addSlider(0); - - LLTabContainer* tab_container = mSkyTabLayoutContainer->getChild("sky_tabs"); - S32 tab_count = tab_container->getTabCount(); - - LLSettingsEditPanel *panel = nullptr; - - for (S32 idx = 0; idx < tab_count; ++idx) - { - panel = static_cast(tab_container->getPanelByIndex(idx)); - if (panel) - panel->setOnDirtyFlagChanged([this](LLPanel *, bool val) { onPanelDirtyFlagChanged(val); }); - } - - tab_container = mWaterTabLayoutContainer->getChild("water_tabs"); - tab_count = tab_container->getTabCount(); - - for (S32 idx = 0; idx < tab_count; ++idx) - { - LLSettingsEditPanel *panel = static_cast(tab_container->getPanelByIndex(idx)); - if (panel) - panel->setOnDirtyFlagChanged([this](LLPanel *, bool val) { onPanelDirtyFlagChanged(val); }); - } - - return true; -} - -void LLFloaterEditExtDayCycle::onOpen(const LLSD& key) -{ - if (!mEditDay) - { - LLEnvironment::instance().saveBeaconsState(); - } - mEditDay.reset(); - mEditContext = CONTEXT_UNKNOWN; - if (key.has(KEY_EDIT_CONTEXT)) - { - std::string context = key[KEY_EDIT_CONTEXT].asString(); - - if (context == VALUE_CONTEXT_INVENTORY) - mEditContext = CONTEXT_INVENTORY; - else if (context == VALUE_CONTEXT_PARCEL) - mEditContext = CONTEXT_PARCEL; - else if (context == VALUE_CONTEXT_REGION) - mEditContext = CONTEXT_REGION; - } - - if (key.has(KEY_CANMOD)) - { - mCanMod = key[KEY_CANMOD].asBoolean(); - } - - if (mEditContext == CONTEXT_UNKNOWN) - { - LL_WARNS("ENVDAYEDIT") << "Unknown editing context!" << LL_ENDL; - } - - if (key.has(KEY_INVENTORY_ID)) - { - loadInventoryItem(key[KEY_INVENTORY_ID].asUUID()); - } - else - { - mCanSave = true; - mCanCopy = true; - mCanMod = true; - mCanTrans = true; - setEditDefaultDayCycle(); - } - - mDayLength.value(0); - if (key.has(KEY_DAY_LENGTH)) - { - mDayLength.value(key[KEY_DAY_LENGTH].asReal()); - } - - // Time&Percentage labels - mCurrentTimeLabel->setTextArg("[PRCNT]", std::string("0")); - const S32 max_elm = 5; - if (mDayLength.value() != 0) - { - S32Hours hrs; - S32Minutes minutes; - LLSettingsDay::Seconds total; - LLUIString formatted_label = getString("time_label"); - for (int i = 0; i < max_elm; i++) - { - total = ((mDayLength / (max_elm - 1)) * i); - hrs = total; - minutes = total - hrs; - - formatted_label.setArg("[HH]", llformat("%d", hrs.value())); - formatted_label.setArg("[MM]", llformat("%d", abs(minutes.value()))); - getChild("p" + llformat("%d", i), true)->setTextArg("[DSC]", formatted_label.getString()); - } - hrs = mDayLength; - minutes = mDayLength - hrs; - formatted_label.setArg("[HH]", llformat("%d", hrs.value())); - formatted_label.setArg("[MM]", llformat("%d", abs(minutes.value()))); - mCurrentTimeLabel->setTextArg("[DSC]", formatted_label.getString()); - } - else - { - for (int i = 0; i < max_elm; i++) - { - getChild("p" + llformat("%d", i), true)->setTextArg("[DSC]", std::string()); - } - mCurrentTimeLabel->setTextArg("[DSC]", std::string()); - } - - // Adjust Time&Percentage labels' location according to length - LLRect label_rect = getChild("p0", true)->getRect(); - F32 slider_width = mFramesSlider->getRect().getWidth(); - for (int i = 1; i < max_elm; i++) - { - LLTextBox *pcnt_label = getChild("p" + llformat("%d", i), true); - LLRect new_rect = pcnt_label->getRect(); - new_rect.mLeft = label_rect.mLeft + (S32)(slider_width * (F32)i / (F32)(max_elm - 1)) - (S32)(pcnt_label->getTextPixelWidth() / 2); - pcnt_label->setRect(new_rect); - } - - // Altitudes&Track labels - LLUIString formatted_label = getString("sky_track_label"); - const LLEnvironment::altitude_list_t &altitudes = LLEnvironment::instance().getRegionAltitudes(); - bool extended_env = LLEnvironment::instance().isExtendedEnvironmentEnabled(); - bool use_altitudes = extended_env - && altitudes.size() > 0 - && ((mEditContext == CONTEXT_PARCEL) || (mEditContext == CONTEXT_REGION)); - for (S32 idx = 1; idx < 4; ++idx) - { - std::ostringstream convert; - if (use_altitudes) - { - convert << altitudes[idx] << "m"; - } - else - { - convert << (idx + 1); - } - formatted_label.setArg("[ALT]", convert.str()); - getChild(track_tabs[idx + 1], true)->setLabel(formatted_label.getString()); - } - - for (U32 i = 2; i < LLSettingsDay::TRACK_MAX; i++) //skies #2 through #4 - { - getChild(track_tabs[i])->setEnabled(extended_env); - } - - if (mEditContext == CONTEXT_INVENTORY) - { - mFlyoutControl->setShownBtnEnabled(true); - mFlyoutControl->setSelectedItem(ACTION_SAVE); - } - else if ((mEditContext == CONTEXT_REGION) || (mEditContext == CONTEXT_PARCEL)) - { - std::string commit_str = (mEditContext == CONTEXT_PARCEL) ? STR_COMMIT_PARCEL : STR_COMMIT_REGION; - mFlyoutControl->setMenuItemLabel(ACTION_COMMIT, getString(commit_str)); - mFlyoutControl->setShownBtnEnabled(true); - mFlyoutControl->setSelectedItem(ACTION_COMMIT); - } - else - { - mFlyoutControl->setShownBtnEnabled(false); - } -} - -void LLFloaterEditExtDayCycle::onClose(bool app_quitting) -{ - doCloseInventoryFloater(app_quitting); - doCloseTrackFloater(app_quitting); - // there's no point to change environment if we're quitting - // or if we already restored environment - stopPlay(); - LLEnvironment::instance().revertBeaconsState(); - if (!app_quitting) - { - LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_FAST); - LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_EDIT); - mEditDay.reset(); - } -} - - -void LLFloaterEditExtDayCycle::onVisibilityChange(bool new_visibility) -{ -} - -void LLFloaterEditExtDayCycle::refresh() -{ - if (mEditDay) - { - LLLineEditor* name_field = getChild(TXT_DAY_NAME); - name_field->setText(mEditDay->getName()); - name_field->setEnabled(mCanMod); - } - - - bool is_inventory_avail = canUseInventory(); - - bool show_commit = ((mEditContext == CONTEXT_PARCEL) || (mEditContext == CONTEXT_REGION)); - bool show_apply = (mEditContext == CONTEXT_INVENTORY); - - if (show_commit) - { - std::string commit_text; - if (mEditContext == CONTEXT_PARCEL) - commit_text = getString(STR_COMMIT_PARCEL); - else - commit_text = getString(STR_COMMIT_REGION); - - mFlyoutControl->setMenuItemLabel(ACTION_COMMIT, commit_text); - } - - mFlyoutControl->setMenuItemVisible(ACTION_COMMIT, show_commit); - mFlyoutControl->setMenuItemVisible(ACTION_SAVE, is_inventory_avail); - mFlyoutControl->setMenuItemVisible(ACTION_SAVEAS, is_inventory_avail); - mFlyoutControl->setMenuItemVisible(ACTION_APPLY_LOCAL, true); - mFlyoutControl->setMenuItemVisible(ACTION_APPLY_PARCEL, show_apply); - mFlyoutControl->setMenuItemVisible(ACTION_APPLY_REGION, show_apply); - - mFlyoutControl->setMenuItemEnabled(ACTION_COMMIT, show_commit && !mCommitSignal.empty()); - mFlyoutControl->setMenuItemEnabled(ACTION_SAVE, is_inventory_avail && mCanMod && !mInventoryId.isNull() && mCanSave); - mFlyoutControl->setMenuItemEnabled(ACTION_SAVEAS, is_inventory_avail && mCanCopy && mCanSave); - mFlyoutControl->setMenuItemEnabled(ACTION_APPLY_LOCAL, true); - mFlyoutControl->setMenuItemEnabled(ACTION_APPLY_PARCEL, canApplyParcel() && show_apply); - mFlyoutControl->setMenuItemEnabled(ACTION_APPLY_REGION, canApplyRegion() && show_apply); - - mImportButton->setEnabled(mCanMod); - - LLFloater::refresh(); -} - -void LLFloaterEditExtDayCycle::setEditSettingsAndUpdate(const LLSettingsBase::ptr_t &settings) -{ - setEditDayCycle(std::dynamic_pointer_cast(settings)); - - showHDRNotification(std::dynamic_pointer_cast(settings)); -} - -void LLFloaterEditExtDayCycle::setEditDayCycle(const LLSettingsDay::ptr_t &pday) -{ - mExpectingAssetId.setNull(); - mEditDay = pday->buildDeepCloneAndUncompress(); - - if (mEditDay->isTrackEmpty(LLSettingsDay::TRACK_WATER)) - { - LL_WARNS("ENVDAYEDIT") << "No water frames found, generating replacement" << LL_ENDL; - mEditDay->setWaterAtKeyframe(LLSettingsVOWater::buildDefaultWater(), .5f); - } - - if (mEditDay->isTrackEmpty(LLSettingsDay::TRACK_GROUND_LEVEL)) - { - LL_WARNS("ENVDAYEDIT") << "No sky frames found, generating replacement" << LL_ENDL; - mEditDay->setSkyAtKeyframe(LLSettingsVOSky::buildDefaultSky(), .5f, LLSettingsDay::TRACK_GROUND_LEVEL); - } - - mCanSave = !pday->getFlag(LLSettingsBase::FLAG_NOSAVE); - mCanCopy = !pday->getFlag(LLSettingsBase::FLAG_NOCOPY) && mCanSave; - mCanMod = !pday->getFlag(LLSettingsBase::FLAG_NOMOD) && mCanSave; - mCanTrans = !pday->getFlag(LLSettingsBase::FLAG_NOTRANS) && mCanSave; - - updateEditEnvironment(); - LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_EDIT, LLEnvironment::TRANSITION_INSTANT); - LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT); - synchronizeTabs(); - updateTabs(); - refresh(); -} - - -void LLFloaterEditExtDayCycle::setEditDefaultDayCycle() -{ - mInventoryItem = nullptr; - mInventoryId.setNull(); - mExpectingAssetId = LLSettingsDay::GetDefaultAssetId(); - LLSettingsVOBase::getSettingsAsset(LLSettingsDay::GetDefaultAssetId(), - [this](LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status, LLExtStat) { onAssetLoaded(asset_id, settings, status); }); -} - -std::string LLFloaterEditExtDayCycle::getEditName() const -{ - if (mEditDay) - return mEditDay->getName(); - return "new"; -} - -void LLFloaterEditExtDayCycle::setEditName(const std::string &name) -{ - if (mEditDay) - mEditDay->setName(name); - getChild(TXT_DAY_NAME)->setText(name); -} - -/* virtual */ -bool LLFloaterEditExtDayCycle::handleKeyUp(KEY key, MASK mask, bool called_from_parent) -{ - if (!mEditDay) - { - mShiftCopyEnabled = false; - } - else if (mask == MASK_SHIFT && mShiftCopyEnabled) - { - mShiftCopyEnabled = false; - std::string curslider = mFramesSlider->getCurSlider(); - if (!curslider.empty()) - { - F32 sliderpos = mFramesSlider->getCurSliderValue(); - - keymap_t::iterator it = mSliderKeyMap.find(curslider); - if (it != mSliderKeyMap.end()) - { - if (mEditDay->moveTrackKeyframe(mCurrentTrack, (*it).second.mFrame, sliderpos)) - { - (*it).second.mFrame = sliderpos; - } - else - { - mFramesSlider->setCurSliderValue((*it).second.mFrame); - } - } - else - { - LL_WARNS("ENVDAYEDIT") << "Failed to find frame " << sliderpos << " for slider " << curslider << LL_ENDL; - } - } - } - return LLFloater::handleKeyUp(key, mask, called_from_parent); -} - -void LLFloaterEditExtDayCycle::onButtonApply(LLUICtrl *ctrl, const LLSD &data) -{ - std::string ctrl_action = ctrl->getName(); - - if (!mEditDay) - { - LL_WARNS("ENVDAYEDIT") << "mEditDay is null! This should never happen! Something is very very wrong" << LL_ENDL; - LLNotificationsUtil::add("EnvironmentApplyFailed"); - closeFloater(); - return; - } - - LLSettingsDay::ptr_t dayclone = mEditDay->buildClone(); // create a compressed copy - - if (!dayclone) - { - LL_WARNS("ENVDAYEDIT") << "Unable to clone daycylce from editor." << LL_ENDL; - return; - } - - // brute-force local texture scan - for (U32 i = 0; i <= LLSettingsDay::TRACK_MAX; i++) - { - LLSettingsDay::CycleTrack_t &day_track = dayclone->getCycleTrack(i); - - LLSettingsDay::CycleTrack_t::iterator iter = day_track.begin(); - LLSettingsDay::CycleTrack_t::iterator end = day_track.end(); - S32 frame_num = 0; - - while (iter != end) - { - frame_num++; - std::string desc; - bool is_local = false; // because getString can be empty - if (i == LLSettingsDay::TRACK_WATER) - { - LLSettingsWater::ptr_t water = std::static_pointer_cast(iter->second); - if (water) - { - // LLViewerFetchedTexture and check for FTT_LOCAL_FILE or check LLLocalBitmapMgr - if (LLLocalBitmapMgr::getInstance()->isLocal(water->getNormalMapID())) - { - desc = LLTrans::getString("EnvironmentNormalMap"); - is_local = true; - } - else if (LLLocalBitmapMgr::getInstance()->isLocal(water->getTransparentTextureID())) - { - desc = LLTrans::getString("EnvironmentTransparent"); - is_local = true; - } - } - } - else - { - LLSettingsSky::ptr_t sky = std::static_pointer_cast(iter->second); - if (sky) - { - if (LLLocalBitmapMgr::getInstance()->isLocal(sky->getSunTextureId())) - { - desc = LLTrans::getString("EnvironmentSun"); - is_local = true; - } - else if (LLLocalBitmapMgr::getInstance()->isLocal(sky->getMoonTextureId())) - { - desc = LLTrans::getString("EnvironmentMoon"); - is_local = true; - } - else if (LLLocalBitmapMgr::getInstance()->isLocal(sky->getCloudNoiseTextureId())) - { - desc = LLTrans::getString("EnvironmentCloudNoise"); - is_local = true; - } - else if (LLLocalBitmapMgr::getInstance()->isLocal(sky->getBloomTextureId())) - { - desc = LLTrans::getString("EnvironmentBloom"); - is_local = true; - } - } - } - - if (is_local) - { - LLSD args; - LLButton* button = getChild(track_tabs[i], true); - args["TRACK"] = button->getCurrentLabel(); - args["FRAME"] = iter->first * 100; // % - args["FIELD"] = desc; - args["FRAMENO"] = frame_num; - LLNotificationsUtil::add("WLLocalTextureDayBlock", args); - return; - } - iter++; - } - } - - if (ctrl_action == ACTION_SAVE) - { - doApplyUpdateInventory(dayclone); - clearDirtyFlag(); - } - else if (ctrl_action == ACTION_SAVEAS) - { - LLSD args; - args["DESC"] = dayclone->getName(); - LLNotificationsUtil::add("SaveSettingAs", args, LLSD(), boost::bind(&LLFloaterEditExtDayCycle::onSaveAsCommit, this, _1, _2, dayclone)); - } - else if ((ctrl_action == ACTION_APPLY_LOCAL) || - (ctrl_action == ACTION_APPLY_PARCEL) || - (ctrl_action == ACTION_APPLY_REGION)) - { - doApplyEnvironment(ctrl_action, dayclone); - } - else if (ctrl_action == ACTION_COMMIT) - { - doApplyCommit(dayclone); - } - else - { - LL_WARNS("ENVDAYEDIT") << "Unknown settings action '" << ctrl_action << "'" << LL_ENDL; - } -} - -void LLFloaterEditExtDayCycle::onButtonLoadFrame() -{ - doOpenInventoryFloater((mCurrentTrack == LLSettingsDay::TRACK_WATER) ? LLSettingsType::ST_WATER : LLSettingsType::ST_SKY, LLUUID::null); -} - -void LLFloaterEditExtDayCycle::onAddFrame() -{ - LLSettingsBase::Seconds frame(mTimeSlider->getCurSliderValue()); - LLSettingsBase::ptr_t setting; - if (!mEditDay) - { - LL_WARNS("ENVDAYEDIT") << "Attempt to add new frame while waiting for day(asset) to load." << LL_ENDL; - return; - } - if ((mEditDay->getSettingsNearKeyframe(frame, mCurrentTrack, LLSettingsDay::DEFAULT_FRAME_SLOP_FACTOR)).second) - { - LL_WARNS("ENVDAYEDIT") << "Attempt to add new frame too close to existing frame." << LL_ENDL; - return; - } - if (!mFramesSlider->canAddSliders()) - { - // Shouldn't happen, button should be disabled - LL_WARNS("ENVDAYEDIT") << "Attempt to add new frame when slider is full." << LL_ENDL; - return; - } - - if (mCurrentTrack == LLSettingsDay::TRACK_WATER) - { - // scratch water should always have the current water settings. - LLSettingsWater::ptr_t water(mScratchWater->buildClone()); - setting = water; - mEditDay->setWaterAtKeyframe( std::static_pointer_cast(setting), frame); - } - else - { - // scratch sky should always have the current sky settings. - LLSettingsSky::ptr_t sky(mScratchSky->buildClone()); - setting = sky; - mEditDay->setSkyAtKeyframe(sky, frame, mCurrentTrack); - } - setDirtyFlag(); - addSliderFrame(frame, setting); - updateTabs(); -} - -void LLFloaterEditExtDayCycle::onRemoveFrame() -{ - std::string sldr_key = mFramesSlider->getCurSlider(); - if (sldr_key.empty()) - { - return; - } - setDirtyFlag(); - removeCurrentSliderFrame(); - updateTabs(); -} - - -void LLFloaterEditExtDayCycle::onCloneTrack() -{ - if (!mEditDay) - { - LL_WARNS("ENVDAYEDIT") << "Attempt to copy track while waiting for day(asset) to load." << LL_ENDL; - return; - } - const LLEnvironment::altitude_list_t &altitudes = LLEnvironment::instance().getRegionAltitudes(); - bool use_altitudes = altitudes.size() > 0 && ((mEditContext == CONTEXT_PARCEL) || (mEditContext == CONTEXT_REGION)); - - LLSD args = LLSD::emptyArray(); - - S32 populated_counter = 0; - for (U32 i = 1; i < LLSettingsDay::TRACK_MAX; i++) - { - LLSD track; - track["id"] = LLSD::Integer(i); - bool populated = (!mEditDay->isTrackEmpty(i)) && (i != mCurrentTrack); - track["enabled"] = populated; - if (populated) - { - populated_counter++; - } - if (use_altitudes) - { - track["altitude"] = altitudes[i - 1]; - } - args.append(track); - } - - if (populated_counter > 0) - { - doOpenTrackFloater(args); - } - else - { - // Should not happen - LL_WARNS("ENVDAYEDIT") << "Tried to copy tracks, but there are no available sources" << LL_ENDL; - } -} - - -void LLFloaterEditExtDayCycle::onLoadTrack() -{ - LLUUID curitemId = mInventoryId; - - if (mCurrentEdit && curitemId.notNull()) - { - curitemId = LLFloaterSettingsPicker::findItemID(mCurrentEdit->getAssetId(), false, false); - } - - doOpenInventoryFloater(LLSettingsType::ST_DAYCYCLE, curitemId); -} - - -void LLFloaterEditExtDayCycle::onClearTrack() -{ - if (!mEditDay) - { - LL_WARNS("ENVDAYEDIT") << "Attempt to clear track while waiting for day(asset) to load." << LL_ENDL; - return; - } - - if (mCurrentTrack > 1) - mEditDay->getCycleTrack(mCurrentTrack).clear(); - else - { - LLSettingsDay::CycleTrack_t &track(mEditDay->getCycleTrack(mCurrentTrack)); - - auto first = track.begin(); - auto last = track.end(); - ++first; - track.erase(first, last); - } - - updateEditEnvironment(); - LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_EDIT, LLEnvironment::TRANSITION_INSTANT); - LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT); - synchronizeTabs(); - updateTabs(); - refresh(); -} - -void LLFloaterEditExtDayCycle::onCommitName(class LLLineEditor* caller, void* user_data) -{ - if (!mEditDay) - { - LL_WARNS("ENVDAYEDIT") << "Attempt to rename day while waiting for day(asset) to load." << LL_ENDL; - return; - } - - mEditDay->setName(caller->getText()); -} - -void LLFloaterEditExtDayCycle::onTrackSelectionCallback(const LLSD& user_data) -{ - U32 track_index = user_data.asInteger(); // 1-5 - selectTrack(track_index); -} - -void LLFloaterEditExtDayCycle::onPlayActionCallback(const LLSD& user_data) -{ - std::string action = user_data.asString(); - - F32 frame = mTimeSlider->getCurSliderValue(); - - if (action == ACTION_PLAY) - { - startPlay(); - } - else if (action == ACTION_PAUSE) - { - stopPlay(); - } - else if (mSliderKeyMap.size() != 0) - { - F32 new_frame = 0; - if (action == ACTION_FORWARD) - { - new_frame = mEditDay->getUpperBoundFrame(mCurrentTrack, frame + (mTimeSlider->getIncrement() / 2)); - } - else if (action == ACTION_BACK) - { - new_frame = mEditDay->getLowerBoundFrame(mCurrentTrack, frame - (mTimeSlider->getIncrement() / 2)); - } - selectFrame(new_frame, 0.0f); - stopPlay(); - } -} - -void LLFloaterEditExtDayCycle::onFrameSliderCallback(const LLSD &data) -{ - std::string curslider = mFramesSlider->getCurSlider(); - - if (!curslider.empty() && mEditDay) - { - F32 sliderpos = mFramesSlider->getCurSliderValue(); - - keymap_t::iterator it = mSliderKeyMap.find(curslider); - if (it != mSliderKeyMap.end()) - { - if (gKeyboard->currentMask(true) == MASK_SHIFT && mShiftCopyEnabled && mCanMod) - { - // don't move the point/frame as long as shift is pressed and user is attempting to copy - // handleKeyUp will do the move if user releases key too early. - if (!(mEditDay->getSettingsNearKeyframe(sliderpos, mCurrentTrack, LLSettingsDay::DEFAULT_FRAME_SLOP_FACTOR)).second) - { - LL_DEBUGS("ENVDAYEDIT") << "Copying frame from " << it->second.mFrame << " to " << sliderpos << LL_ENDL; - LLSettingsBase::ptr_t new_settings; - - // mEditDay still remembers old position, add copy at new position - if (mCurrentTrack == LLSettingsDay::TRACK_WATER) - { - LLSettingsWaterPtr_t water_ptr = std::dynamic_pointer_cast(it->second.pSettings)->buildClone(); - mEditDay->setWaterAtKeyframe(water_ptr, sliderpos); - new_settings = water_ptr; - } - else - { - LLSettingsSkyPtr_t sky_ptr = std::dynamic_pointer_cast(it->second.pSettings)->buildClone(); - mEditDay->setSkyAtKeyframe(sky_ptr, sliderpos, mCurrentTrack); - new_settings = sky_ptr; - } - // mSliderKeyMap still remembers old position, for simplicity, just move it to be identical to slider - F32 old_frame = it->second.mFrame; - it->second.mFrame = sliderpos; - // slider already moved old frame, create new one in old place - addSliderFrame(old_frame, new_settings, false /*because we are going to reselect new one*/); - // reselect new frame - mFramesSlider->setCurSlider(it->first); - mShiftCopyEnabled = false; - setDirtyFlag(); - } - } - else - { - // slider rounds values to nearest increments, changes can be substanntial (half increment) - if (abs(mFramesSlider->getNearestIncrement((*it).second.mFrame) - sliderpos) < F_APPROXIMATELY_ZERO) - { - // same value - mFramesSlider->setCurSliderValue((*it).second.mFrame); - } - else if (mEditDay->moveTrackKeyframe(mCurrentTrack, (*it).second.mFrame, sliderpos) && mCanMod) - { - (*it).second.mFrame = sliderpos; - setDirtyFlag(); - } - else - { - // same value, wrong track, no such value, no mod - mFramesSlider->setCurSliderValue((*it).second.mFrame); - } - - mShiftCopyEnabled = false; - } - } - } -} - -void LLFloaterEditExtDayCycle::onFrameSliderDoubleClick(S32 x, S32 y, MASK mask) -{ - stopPlay(); - onAddFrame(); -} - -void LLFloaterEditExtDayCycle::onFrameSliderMouseDown(S32 x, S32 y, MASK mask) -{ - stopPlay(); - F32 sliderpos = mFramesSlider->getSliderValueFromPos(x, y); - - std::string slidername = mFramesSlider->getCurSlider(); - - mShiftCopyEnabled = !slidername.empty() && gKeyboard->currentMask(true) == MASK_SHIFT; - - if (!slidername.empty()) - { - LLRect thumb_rect = mFramesSlider->getSliderThumbRect(slidername); - if ((x >= thumb_rect.mRight) || (x <= thumb_rect.mLeft)) - { - mFramesSlider->resetCurSlider(); - } - } - - mTimeSlider->setCurSliderValue(sliderpos); - - updateTabs(); - LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT); -} - -void LLFloaterEditExtDayCycle::onFrameSliderMouseUp(S32 x, S32 y, MASK mask) -{ - // Only happens when clicking on empty space of frameslider, not on specific frame - F32 sliderpos = mFramesSlider->getSliderValueFromPos(x, y); - - mTimeSlider->setCurSliderValue(sliderpos); - selectFrame(sliderpos, LLSettingsDay::DEFAULT_FRAME_SLOP_FACTOR); -} - -void LLFloaterEditExtDayCycle::onTimeSliderCallback() -{ - stopPlay(); - selectFrame(mTimeSlider->getCurSliderValue(), LLSettingsDay::DEFAULT_FRAME_SLOP_FACTOR); -} - -void LLFloaterEditExtDayCycle::cloneTrack(U32 source_index, U32 dest_index) -{ - cloneTrack(mEditDay, source_index, dest_index); -} - -void LLFloaterEditExtDayCycle::cloneTrack(const LLSettingsDay::ptr_t &source_day, U32 source_index, U32 dest_index) -{ - if ((source_index == LLSettingsDay::TRACK_WATER || dest_index == LLSettingsDay::TRACK_WATER) && (source_index != dest_index)) - { // one of the tracks is a water track, the other is not - LLSD args; - - LL_WARNS() << "Can not import water track into sky track or vice versa" << LL_ENDL; - - LLButton* button = getChild(track_tabs[source_index], true); - args["TRACK1"] = button->getCurrentLabel(); - button = getChild(track_tabs[dest_index], true); - args["TRACK2"] = button->getCurrentLabel(); - - LLNotificationsUtil::add("TrackLoadMismatch", args); - return; - } - - // don't use replaceCycleTrack because we will end up with references, but we need to clone - - // hold on to a backup of the - LLSettingsDay::CycleTrack_t backup_track = mEditDay->getCycleTrack(dest_index); - - mEditDay->clearCycleTrack(dest_index); // because source can be empty - LLSettingsDay::CycleTrack_t source_track = source_day->getCycleTrack(source_index); - S32 addcount(0); - for (auto &track_frame : source_track) - { - LLSettingsBase::ptr_t pframe = track_frame.second; - LLSettingsBase::ptr_t pframeclone = pframe->buildDerivedClone(); - if (pframeclone) - { - ++addcount; - mEditDay->setSettingsAtKeyframe(pframeclone, track_frame.first, dest_index); - } - } - - if (!addcount) - { // nothing was actually added. Restore the old track and issue a warning. - mEditDay->replaceCycleTrack(dest_index, backup_track); - - LLSD args; - LLButton* button = getChild(track_tabs[dest_index], true); - args["TRACK"] = button->getCurrentLabel(); - - LLNotificationsUtil::add("TrackLoadFailed", args); - } - setDirtyFlag(); - - updateSlider(); - updateTabs(); - updateButtons(); -} - -void LLFloaterEditExtDayCycle::selectTrack(U32 track_index, bool force ) -{ - if (track_index < LLSettingsDay::TRACK_MAX) - mCurrentTrack = track_index; - - LLButton* button = getChild(track_tabs[mCurrentTrack], true); - if (button->getToggleState() && !force) - { - return; - } - - for (U32 i = 0; i < LLSettingsDay::TRACK_MAX; i++) // use max value - { - getChild(track_tabs[i], true)->setToggleState(i == mCurrentTrack); - } - - bool show_water = (mCurrentTrack == LLSettingsDay::TRACK_WATER); - mSkyTabLayoutContainer->setVisible(!show_water); - mWaterTabLayoutContainer->setVisible(show_water); - - updateSlider(); - updateLabels(); -} - -void LLFloaterEditExtDayCycle::selectFrame(F32 frame, F32 slop_factor) -{ - mFramesSlider->resetCurSlider(); - - keymap_t::iterator iter = mSliderKeyMap.begin(); - keymap_t::iterator end_iter = mSliderKeyMap.end(); - while (iter != end_iter) - { - F32 keyframe = iter->second.mFrame; - F32 frame_dif = fabs(keyframe - frame); - if (frame_dif <= slop_factor) - { - keymap_t::iterator next_iter = std::next(iter); - if ((frame_dif != 0) && (next_iter != end_iter)) - { - if (fabs(next_iter->second.mFrame - frame) < frame_dif) - { - mFramesSlider->setCurSlider(next_iter->first); - frame = next_iter->second.mFrame; - break; - } - } - mFramesSlider->setCurSlider(iter->first); - frame = iter->second.mFrame; - break; - } - iter++; - } - - mTimeSlider->setCurSliderValue(frame); - // block or update tabs according to new selection - updateTabs(); -// LLEnvironment::instance().updateEnvironment(); -} - -void LLFloaterEditExtDayCycle::clearTabs() -{ - // Note: If this doesn't look good, init panels with default settings. It might be better looking - if (mCurrentTrack == LLSettingsDay::TRACK_WATER) - { - const LLSettingsWaterPtr_t p_water = LLSettingsWaterPtr_t(NULL); - updateWaterTabs(p_water); - } - else - { - const LLSettingsSkyPtr_t p_sky = LLSettingsSkyPtr_t(NULL); - updateSkyTabs(p_sky); - } - updateButtons(); - updateTimeAndLabel(); -} - -void LLFloaterEditExtDayCycle::updateTabs() -{ - reblendSettings(); - synchronizeTabs(); - - updateButtons(); - updateTimeAndLabel(); -} - -void LLFloaterEditExtDayCycle::updateWaterTabs(const LLSettingsWaterPtr_t &p_water) -{ - LLView* tab_container = mWaterTabLayoutContainer->getChild(TABS_WATER); //can't extract panels directly, since it is in 'tuple' - LLPanelSettingsWaterMainTab* panel = dynamic_cast(tab_container->findChildView("water_panel")); - if (panel) - { - panel->setWater(p_water); - } -} - -void LLFloaterEditExtDayCycle::updateSkyTabs(const LLSettingsSkyPtr_t &p_sky) -{ - LLTabContainer* tab_container = mSkyTabLayoutContainer->getChild(TABS_SKYS); //can't extract panels directly, since they are in 'tuple' - - LLPanelSettingsSky* panel; - panel = dynamic_cast(tab_container->findChildView("atmosphere_panel")); - if (panel) - { - panel->setSky(p_sky); - } - panel = dynamic_cast(tab_container->findChildView("clouds_panel")); - if (panel) - { - panel->setSky(p_sky); - } - panel = dynamic_cast(tab_container->findChildView("moon_panel")); - if (panel) - { - panel->setSky(p_sky); - } -} - -void LLFloaterEditExtDayCycle::updateLabels() -{ - std::string label_arg = (mCurrentTrack == LLSettingsDay::TRACK_WATER) ? "water_label" : "sky_label"; - - mAddFrameButton->setLabelArg("[FRAME]", getString(label_arg)); - mDeleteFrameButton->setLabelArg("[FRAME]", getString(label_arg)); - mLoadFrame->setLabelArg("[FRAME]", getString(label_arg)); -} - -void LLFloaterEditExtDayCycle::updateButtons() -{ - // This logic appears to work in reverse, the add frame button - // is only enabled when you're on an existing frame and disabled - // in all the interim positions where you'd want to add a frame... - - bool can_manipulate = mEditDay && !mIsPlaying && mCanMod; - bool can_clone(false); - bool can_clear(false); - - if (can_manipulate) - { - if (mCurrentTrack == 0) - { - can_clone = false; - } - else - { - for (S32 track = 1; track < LLSettingsDay::TRACK_MAX; ++track) - { - if (track == mCurrentTrack) - continue; - can_clone |= !mEditDay->getCycleTrack(track).empty(); - } - } - - can_clear = (mCurrentTrack > 1) ? (!mEditDay->getCycleTrack(mCurrentTrack).empty()) : (mEditDay->getCycleTrack(mCurrentTrack).size() > 1); - } - - mCloneTrack->setEnabled(can_clone); - mLoadTrack->setEnabled(can_manipulate); - mClearTrack->setEnabled(can_clear); - mAddFrameButton->setEnabled(can_manipulate && isAddingFrameAllowed()); - mDeleteFrameButton->setEnabled(can_manipulate && isRemovingFrameAllowed()); - mLoadFrame->setEnabled(can_manipulate); - - bool enable_play = (bool)mEditDay; - childSetEnabled(BTN_PLAY, enable_play); - childSetEnabled(BTN_SKIP_BACK, enable_play); - childSetEnabled(BTN_SKIP_FORWARD, enable_play); - - // update track buttons - bool extended_env = LLEnvironment::instance().isExtendedEnvironmentEnabled(); - for (S32 track = 0; track < LLSettingsDay::TRACK_MAX; ++track) - { - LLButton* button = getChild(track_tabs[track], true); - button->setEnabled(extended_env); - button->setToggleState(track == mCurrentTrack); - } -} - -void LLFloaterEditExtDayCycle::updateSlider() -{ - F32 frame_position = mTimeSlider->getCurSliderValue(); - mFramesSlider->clear(); - mSliderKeyMap.clear(); - - if (!mEditDay) - { - // floater is waiting for asset - return; - } - - LLSettingsDay::CycleTrack_t track = mEditDay->getCycleTrack(mCurrentTrack); - for (auto &track_frame : track) - { - addSliderFrame(track_frame.first, track_frame.second, false); - } - - if (mSliderKeyMap.size() > 0) - { - // update positions - mLastFrameSlider = mFramesSlider->getCurSlider(); - } - else - { - // disable panels - clearTabs(); - mLastFrameSlider.clear(); - } - - selectFrame(frame_position, LLSettingsDay::DEFAULT_FRAME_SLOP_FACTOR); -} - -void LLFloaterEditExtDayCycle::updateTimeAndLabel() -{ - F32 time = mTimeSlider->getCurSliderValue(); - mCurrentTimeLabel->setTextArg("[PRCNT]", llformat("%.0f", time * 100)); - if (mDayLength.value() != 0) - { - LLUIString formatted_label = getString("time_label"); - - LLSettingsDay::Seconds total = (mDayLength * time); - S32Hours hrs = total; - S32Minutes minutes = total - hrs; - - formatted_label.setArg("[HH]", llformat("%d", hrs.value())); - formatted_label.setArg("[MM]", llformat("%d", abs(minutes.value()))); - mCurrentTimeLabel->setTextArg("[DSC]", formatted_label.getString()); - } - else - { - mCurrentTimeLabel->setTextArg("[DSC]", std::string()); - } - - // Update blender here: -} - -void LLFloaterEditExtDayCycle::addSliderFrame(F32 frame, const LLSettingsBase::ptr_t &setting, bool update_ui) -{ - // multi slider distinguishes elements by key/name in string format - // store names to map to be able to recall dependencies - std::string new_slider = mFramesSlider->addSlider(frame); - if (!new_slider.empty()) - { - mSliderKeyMap[new_slider] = FrameData(frame, setting); - - if (update_ui) - { - mLastFrameSlider = new_slider; - mTimeSlider->setCurSliderValue(frame); - updateTabs(); - } - } -} - -void LLFloaterEditExtDayCycle::removeCurrentSliderFrame() -{ - std::string sldr = mFramesSlider->getCurSlider(); - if (sldr.empty()) - { - return; - } - mFramesSlider->deleteCurSlider(); - keymap_t::iterator iter = mSliderKeyMap.find(sldr); - if (iter != mSliderKeyMap.end()) - { - LL_DEBUGS("ENVDAYEDIT") << "Removing frame from " << iter->second.mFrame << LL_ENDL; - LLSettingsBase::Seconds seconds(iter->second.mFrame); - mEditDay->removeTrackKeyframe(mCurrentTrack, seconds); - mSliderKeyMap.erase(iter); - } - - mLastFrameSlider = mFramesSlider->getCurSlider(); - mTimeSlider->setCurSliderValue(mFramesSlider->getCurSliderValue()); - updateTabs(); -} - -void LLFloaterEditExtDayCycle::removeSliderFrame(F32 frame) -{ - keymap_t::iterator it = std::find_if(mSliderKeyMap.begin(), mSliderKeyMap.end(), - [frame](const keymap_t::value_type &value) { return fabs(value.second.mFrame - frame) < LLSettingsDay::DEFAULT_FRAME_SLOP_FACTOR; }); - - if (it != mSliderKeyMap.end()) - { - mFramesSlider->deleteSlider((*it).first); - mSliderKeyMap.erase(it); - } - -} - -//------------------------------------------------------------------------- - -LLFloaterEditExtDayCycle::connection_t LLFloaterEditExtDayCycle::setEditCommitSignal(LLFloaterEditExtDayCycle::edit_commit_signal_t::slot_type cb) -{ - return mCommitSignal.connect(cb); -} - -void LLFloaterEditExtDayCycle::updateEditEnvironment(void) -{ - if (!mEditDay) - return; - S32 skytrack = (mCurrentTrack) ? mCurrentTrack : 1; - mSkyBlender = std::make_shared(mScratchSky, mEditDay, skytrack); - mWaterBlender = std::make_shared(mScratchWater, mEditDay, LLSettingsDay::TRACK_WATER); - - if (LLEnvironment::instance().isExtendedEnvironmentEnabled()) - { - selectTrack(LLSettingsDay::TRACK_MAX, true); - } - else - { - selectTrack(1, true); - } - - reblendSettings(); - - LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_EDIT, mEditSky, mEditWater); - LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT); -} - -void LLFloaterEditExtDayCycle::synchronizeTabs() -{ - // This should probably get moved into "updateTabs" - std::string curslider = mFramesSlider->getCurSlider(); - bool canedit(false); - - LLSettingsWater::ptr_t psettingW; - LLTabContainer * tabs = mWaterTabLayoutContainer->getChild(TABS_WATER); - if (mCurrentTrack == LLSettingsDay::TRACK_WATER) - { - if (!mEditDay) - { - canedit = false; - } - else if (!curslider.empty()) - { - canedit = !mIsPlaying; - // either search mEditDay or retrieve from mSliderKeyMap - keymap_t::iterator slider_it = mSliderKeyMap.find(curslider); - if (slider_it != mSliderKeyMap.end()) - { - psettingW = std::static_pointer_cast(slider_it->second.pSettings); - } - } - mCurrentEdit = psettingW; - if (!psettingW) - { - canedit = false; - psettingW = mScratchWater; - } - - getChild(ICN_LOCK_EDIT)->setVisible(!canedit); - } - else - { - psettingW = mScratchWater; - } - mEditWater = psettingW; - - setTabsData(tabs, psettingW, canedit); - - LLSettingsSky::ptr_t psettingS; - canedit = false; - tabs = mSkyTabLayoutContainer->getChild(TABS_SKYS); - if (mCurrentTrack != LLSettingsDay::TRACK_WATER) - { - if (!mEditDay) - { - canedit = false; - } - else if (!curslider.empty()) - { - canedit = !mIsPlaying; - // either search mEditDay or retrieve from mSliderKeyMap - keymap_t::iterator slider_it = mSliderKeyMap.find(curslider); - if (slider_it != mSliderKeyMap.end()) - { - psettingS = std::static_pointer_cast(slider_it->second.pSettings); - } - } - mCurrentEdit = psettingS; - if (!psettingS) - { - canedit = false; - psettingS = mScratchSky; - } - - getChild(ICN_LOCK_EDIT)->setVisible(!canedit); - } - else - { - psettingS = mScratchSky; - } - mEditSky = psettingS; - - doCloseInventoryFloater(); - doCloseTrackFloater(); - - setTabsData(tabs, psettingS, canedit); - LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_EDIT, mEditSky, mEditWater); - LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT); -} - -void LLFloaterEditExtDayCycle::setTabsData(LLTabContainer * tabcontainer, const LLSettingsBase::ptr_t &settings, bool editable) -{ - S32 count = tabcontainer->getTabCount(); - for (S32 idx = 0; idx < count; ++idx) - { - LLSettingsEditPanel *panel = static_cast(tabcontainer->getPanelByIndex(idx)); - if (panel) - { - panel->setCanChangeSettings(editable & mCanMod); - panel->setSettings(settings); - } - } -} - - -void LLFloaterEditExtDayCycle::reblendSettings() -{ - F64 position = mTimeSlider->getCurSliderValue(); - - if (mSkyBlender) - { - if ((mSkyBlender->getTrack() != mCurrentTrack) && (mCurrentTrack != LLSettingsDay::TRACK_WATER)) - { - mSkyBlender->switchTrack(mCurrentTrack, position); - } - else - { - mSkyBlender->setPosition(position); - } - } - - if (mWaterBlender) - { - mWaterBlender->setPosition(position); - } -} - -void LLFloaterEditExtDayCycle::doApplyCommit(LLSettingsDay::ptr_t day) -{ - if (!mCommitSignal.empty()) - { - mCommitSignal(day); - - closeFloater(); - } -} - -bool LLFloaterEditExtDayCycle::isRemovingFrameAllowed() -{ - if (mFramesSlider->getCurSlider().empty()) return false; - - if (mCurrentTrack <= LLSettingsDay::TRACK_GROUND_LEVEL) - { - return (mSliderKeyMap.size() > 1); - } - else - { - return (mSliderKeyMap.size() > 0); - } -} - -bool LLFloaterEditExtDayCycle::isAddingFrameAllowed() -{ - if (!mFramesSlider->getCurSlider().empty() || !mEditDay) return false; - - LLSettingsBase::Seconds frame(mTimeSlider->getCurSliderValue()); - if ((mEditDay->getSettingsNearKeyframe(frame, mCurrentTrack, LLSettingsDay::DEFAULT_FRAME_SLOP_FACTOR)).second) - { - return false; - } - return mFramesSlider->canAddSliders(); -} - -void LLFloaterEditExtDayCycle::doImportFromDisk() -{ // Load a a legacy Windlight XML from disk. - LLFilePickerReplyThread::startPicker(boost::bind(&LLFloaterEditExtDayCycle::loadSettingFromFile, this, _1), LLFilePicker::FFLOAD_XML, false); -} - -void LLFloaterEditExtDayCycle::loadSettingFromFile(const std::vector& filenames) -{ - LLSD messages; - if (filenames.size() < 1) return; - std::string filename = filenames[0]; - LL_DEBUGS("ENVDAYEDIT") << "Selected file: " << filename << LL_ENDL; - LLSettingsDay::ptr_t legacyday = LLEnvironment::createDayCycleFromLegacyPreset(filename, messages); - - if (!legacyday) - { - LLNotificationsUtil::add("WLImportFail", messages); - return; - } - - loadInventoryItem(LLUUID::null); - - mCurrentTrack = 1; - setDirtyFlag(); - setEditDayCycle(legacyday); -} - -void LLFloaterEditExtDayCycle::startPlay() -{ - doCloseInventoryFloater(); - doCloseTrackFloater(); - - mIsPlaying = true; - mFramesSlider->resetCurSlider(); - mPlayTimer.reset(); - mPlayTimer.start(); - gIdleCallbacks.addFunction(onIdlePlay, this); - mPlayStartFrame = mTimeSlider->getCurSliderValue(); - - getChild("play_layout", true)->setVisible(false); - getChild("pause_layout", true)->setVisible(true); -} - -void LLFloaterEditExtDayCycle::stopPlay() -{ - if (!mIsPlaying) - return; - - mIsPlaying = false; - gIdleCallbacks.deleteFunction(onIdlePlay, this); - mPlayTimer.stop(); - F32 frame = mTimeSlider->getCurSliderValue(); - selectFrame(frame, LLSettingsDay::DEFAULT_FRAME_SLOP_FACTOR); - - getChild("play_layout", true)->setVisible(true); - getChild("pause_layout", true)->setVisible(false); -} - -//static -void LLFloaterEditExtDayCycle::onIdlePlay(void* user_data) -{ - if (!gDisconnected) - { - LLFloaterEditExtDayCycle* self = (LLFloaterEditExtDayCycle*)user_data; - - if (self->mSkyBlender == nullptr || self->mWaterBlender == nullptr) - { - self->stopPlay(); - } - else - { - - F32 prcnt_played = self->mPlayTimer.getElapsedTimeF32() / DAY_CYCLE_PLAY_TIME_SECONDS; - F32 new_frame = fmod(self->mPlayStartFrame + prcnt_played, 1.f); - - self->mTimeSlider->setCurSliderValue(new_frame); // will do the rounding - self->mSkyBlender->setPosition(new_frame); - self->mWaterBlender->setPosition(new_frame); - self->synchronizeTabs(); - self->updateTimeAndLabel(); - self->updateButtons(); - } - } -} - - -void LLFloaterEditExtDayCycle::clearDirtyFlag() -{ - mIsDirty = false; - - LLTabContainer* tab_container = mSkyTabLayoutContainer->getChild("sky_tabs"); - S32 tab_count = tab_container->getTabCount(); - - for (S32 idx = 0; idx < tab_count; ++idx) - { - LLSettingsEditPanel *panel = static_cast(tab_container->getPanelByIndex(idx)); - if (panel) - panel->clearIsDirty(); - } - - tab_container = mWaterTabLayoutContainer->getChild("water_tabs"); - tab_count = tab_container->getTabCount(); - - for (S32 idx = 0; idx < tab_count; ++idx) - { - LLSettingsEditPanel *panel = static_cast(tab_container->getPanelByIndex(idx)); - if (panel) - panel->clearIsDirty(); - } - -} - -void LLFloaterEditExtDayCycle::doOpenTrackFloater(const LLSD &args) -{ - LLFloaterTrackPicker *picker = static_cast(mTrackFloater.get()); - - // Show the dialog - if (!picker) - { - picker = new LLFloaterTrackPicker(this); - - mTrackFloater = picker->getHandle(); - - picker->setCommitCallback([this](LLUICtrl *, const LLSD &data){ onPickerCommitTrackId(data.asInteger()); }); - } - - picker->showPicker(args); -} - -void LLFloaterEditExtDayCycle::doCloseTrackFloater(bool quitting) -{ - LLFloater* floaterp = mTrackFloater.get(); - - if (floaterp) - { - floaterp->closeFloater(quitting); - } -} - -LLFloaterSettingsPicker * LLFloaterEditExtDayCycle::getSettingsPicker() -{ - LLFloaterSettingsPicker *picker = static_cast(mInventoryFloater.get()); - - // Show the dialog - if (!picker) - { - picker = new LLFloaterSettingsPicker(this, - LLUUID::null); - - mInventoryFloater = picker->getHandle(); - - picker->setCommitCallback([this](LLUICtrl *, const LLSD &data){ onPickerCommitSetting(data["ItemId"].asUUID(), data["Track"].asInteger()); }); - } - return picker; -} - -void LLFloaterEditExtDayCycle::onPickerCommitTrackId(U32 track_id) -{ - cloneTrack(track_id, mCurrentTrack); -} - -void LLFloaterEditExtDayCycle::doOpenInventoryFloater(LLSettingsType::type_e type, LLUUID curritem) -{ - LLFloaterSettingsPicker *picker = getSettingsPicker(); - picker->setSettingsFilter(type); - picker->setSettingsItemId(curritem); - if (type == LLSettingsType::ST_DAYCYCLE) - { - picker->setTrackMode((mCurrentTrack == LLSettingsDay::TRACK_WATER) ? LLFloaterSettingsPicker::TRACK_WATER : LLFloaterSettingsPicker::TRACK_SKY); - } - else - { - picker->setTrackMode(LLFloaterSettingsPicker::TRACK_NONE); - } - picker->openFloater(); - picker->setFocus(true); -} - -void LLFloaterEditExtDayCycle::onPickerCommitSetting(LLUUID item_id, S32 track) -{ - LLSettingsBase::TrackPosition frame(mTimeSlider->getCurSliderValue()); - LLViewerInventoryItem *itemp = gInventory.getItem(item_id); - if (itemp) - { - LLSettingsVOBase::getSettingsAsset(itemp->getAssetUUID(), - [this, track, frame, item_id](LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status, LLExtStat) { onAssetLoadedForInsertion(item_id, asset_id, settings, status, track, mCurrentTrack, frame); }); - } -} - -void LLFloaterEditExtDayCycle::showHDRNotification(const LLSettingsDay::ptr_t &pday) -{ - for (U32 i = LLSettingsDay::TRACK_GROUND_LEVEL; i <= LLSettingsDay::TRACK_MAX; i++) - { - LLSettingsDay::CycleTrack_t &day_track = pday->getCycleTrack(i); - - LLSettingsDay::CycleTrack_t::iterator iter = day_track.begin(); - LLSettingsDay::CycleTrack_t::iterator end = day_track.end(); - - while (iter != end) - { - LLSettingsSky::ptr_t sky = std::static_pointer_cast(iter->second); - if (sky - && sky->canAutoAdjust() - && sky->getReflectionProbeAmbiance(true) != 0.f) - { - LLNotificationsUtil::add("AutoAdjustHDRSky"); - return; - } - iter++; - } - } -} - -void LLFloaterEditExtDayCycle::onAssetLoadedForInsertion(LLUUID item_id, LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status, S32 source_track, S32 dest_track, LLSettingsBase::TrackPosition frame) -{ - std::function cb = [this, settings, frame, source_track, dest_track]() - { - if (settings->getSettingsType() == "daycycle") - { - // Load full track - LLSettingsDay::ptr_t pday = std::dynamic_pointer_cast(settings); - if (dest_track == LLSettingsDay::TRACK_WATER) - { - cloneTrack(pday, LLSettingsDay::TRACK_WATER, LLSettingsDay::TRACK_WATER); - } - else - { - cloneTrack(pday, source_track, dest_track); - } - } - else - { - if (!mFramesSlider->canAddSliders()) - { - LL_WARNS("ENVDAYEDIT") << "Attempt to add new frame when slider is full." << LL_ENDL; - return; - } - - // load or replace single frame - LLSettingsDay::CycleTrack_t::value_type nearest = mEditDay->getSettingsNearKeyframe(frame, dest_track, LLSettingsDay::DEFAULT_FRAME_SLOP_FACTOR); - if (nearest.first != LLSettingsDay::INVALID_TRACKPOS) - { // There is already a frame near the target location. Remove it so we can put the new one in its place. - mEditDay->removeTrackKeyframe(dest_track, nearest.first); - removeSliderFrame(nearest.first); - } - - // Don't forget to clone (we might reuse/load it couple times) - if (settings->getSettingsType() == "sky") - { - // Load sky to frame - if (dest_track != LLSettingsDay::TRACK_WATER) - { - mEditDay->setSettingsAtKeyframe(settings->buildDerivedClone(), frame, dest_track); - addSliderFrame(frame, settings, false); - } - else - { - LL_WARNS("ENVDAYEDIT") << "Trying to load day settings as sky" << LL_ENDL; - } - } - else if (settings->getSettingsType() == "water") - { - // Load water to frame - if (dest_track == LLSettingsDay::TRACK_WATER) - { - mEditDay->setSettingsAtKeyframe(settings->buildDerivedClone(), frame, dest_track); - addSliderFrame(frame, settings, false); - } - else - { - LL_WARNS("ENVDAYEDIT") << "Trying to load water settings as sky" << LL_ENDL; - } - } - } - reblendSettings(); - synchronizeTabs(); - }; - - if (!settings || status) - { - LL_WARNS("ENVDAYEDIT") << "Could not load asset " << asset_id << " into frame. status=" << status << LL_ENDL; - return; - } - - if (!mEditDay) - { - // day got reset while we were waiting for response - return; - } - - LLInventoryItem *inv_item = gInventory.getItem(item_id); - - if (inv_item - && (!inv_item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID()) - || !inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID()))) - { - // Need to check if item is already no-transfer, otherwise make it no-transfer - bool no_transfer = false; - if (mInventoryItem) - { - no_transfer = !mInventoryItem->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID()); - } - else - { - no_transfer = mEditDay->getFlag(LLSettingsBase::FLAG_NOTRANS); - } - - if (!no_transfer) - { - LLSD args; - - // create and show confirmation textbox - LLNotificationsUtil::add("SettingsMakeNoTrans", args, LLSD(), - [this, cb](const LLSD¬if, const LLSD&resp) - { - S32 opt = LLNotificationsUtil::getSelectedOption(notif, resp); - if (opt == 0) - { - mCanTrans = false; - mEditDay->setFlag(LLSettingsBase::FLAG_NOTRANS); - cb(); - } - }); - return; - } - } - - cb(); -} +/** + * @file llfloatereditextdaycycle.cpp + * @brief Floater to create or edit a day cycle + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatereditextdaycycle.h" + +// libs +#include "llbutton.h" +#include "llcallbacklist.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "llloadingindicator.h" +#include "lllocalbitmaps.h" +#include "llmultisliderctrl.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "llspinctrl.h" +#include "lltimectrl.h" +#include "lltabcontainer.h" +#include "llfilepicker.h" + +#include "llsettingsvo.h" +#include "llinventorymodel.h" +#include "llviewerparcelmgr.h" + +#include "llsettingspicker.h" +#include "lltrackpicker.h" + +// newview +#include "llagent.h" +#include "llappviewer.h" //gDisconected +#include "llparcel.h" +#include "llflyoutcombobtn.h" //Todo: make a proper UI element/button/panel instead +#include "llregioninfomodel.h" +#include "llviewermenufile.h" // LLFilePickerReplyThread +#include "llviewerregion.h" +#include "llpaneleditwater.h" +#include "llpaneleditsky.h" + +#include "llui.h" + +#include "llenvironment.h" +#include "lltrans.h" + +extern LLControlGroup gSavedSettings; + +//========================================================================= +namespace { + const std::string track_tabs[] = { + "water_track", + "sky1_track", + "sky2_track", + "sky3_track", + "sky4_track", + }; + + const std::string ICN_LOCK_EDIT("icn_lock_edit"); + const std::string BTN_SAVE("save_btn"); + const std::string BTN_FLYOUT("btn_flyout"); + const std::string BTN_CANCEL("cancel_btn"); + const std::string BTN_ADDFRAME("add_frame"); + const std::string BTN_DELFRAME("delete_frame"); + const std::string BTN_IMPORT("btn_import"); + const std::string BTN_LOADFRAME("btn_load_frame"); + const std::string BTN_CLONETRACK("copy_track"); + const std::string BTN_LOADTRACK("load_track"); + const std::string BTN_CLEARTRACK("clear_track"); + const std::string SLDR_TIME("WLTimeSlider"); + const std::string SLDR_KEYFRAMES("WLDayCycleFrames"); + const std::string VIEW_SKY_SETTINGS("frame_settings_sky"); + const std::string VIEW_WATER_SETTINGS("frame_settings_water"); + const std::string LBL_CURRENT_TIME("current_time"); + const std::string TXT_DAY_NAME("day_cycle_name"); + const std::string TABS_SKYS("sky_tabs"); + const std::string TABS_WATER("water_tabs"); + + // 'Play' buttons + const std::string BTN_PLAY("play_btn"); + const std::string BTN_SKIP_BACK("skip_back_btn"); + const std::string BTN_SKIP_FORWARD("skip_forward_btn"); + + const std::string EVNT_DAYTRACK("DayCycle.Track"); + const std::string EVNT_PLAY("DayCycle.PlayActions"); + + const std::string ACTION_PLAY("play"); + const std::string ACTION_PAUSE("pause"); + const std::string ACTION_FORWARD("forward"); + const std::string ACTION_BACK("back"); + + // For flyout + const std::string XML_FLYOUTMENU_FILE("menu_save_settings.xml"); + // From menu_save_settings.xml, consider moving into flyout since it should be supported by flyout either way + const std::string ACTION_SAVE("save_settings"); + const std::string ACTION_SAVEAS("save_as_new_settings"); + const std::string ACTION_COMMIT("commit_changes"); + const std::string ACTION_APPLY_LOCAL("apply_local"); + const std::string ACTION_APPLY_PARCEL("apply_parcel"); + const std::string ACTION_APPLY_REGION("apply_region"); + + const F32 DAY_CYCLE_PLAY_TIME_SECONDS = 60; + + const std::string STR_COMMIT_PARCEL("commit_parcel"); + const std::string STR_COMMIT_REGION("commit_region"); + //--------------------------------------------------------------------- + +} + +//========================================================================= +const std::string LLFloaterEditExtDayCycle::KEY_EDIT_CONTEXT("edit_context"); +const std::string LLFloaterEditExtDayCycle::KEY_DAY_LENGTH("day_length"); +const std::string LLFloaterEditExtDayCycle::KEY_CANMOD("canmod"); + +const std::string LLFloaterEditExtDayCycle::VALUE_CONTEXT_INVENTORY("inventory"); +const std::string LLFloaterEditExtDayCycle::VALUE_CONTEXT_PARCEL("parcel"); +const std::string LLFloaterEditExtDayCycle::VALUE_CONTEXT_REGION("region"); +/* +//========================================================================= + +class LLDaySettingCopiedCallback : public LLInventoryCallback +{ +public: + LLDaySettingCopiedCallback(LLHandle handle) : mHandle(handle) {} + + virtual void fire(const LLUUID& inv_item_id) + { + if (!mHandle.isDead()) + { + LLViewerInventoryItem* item = gInventory.getItem(inv_item_id); + if (item) + { + LLFloaterEditExtDayCycle* floater = (LLFloaterEditExtDayCycle*)mHandle.get(); + floater->onInventoryCreated(item->getAssetUUID(), inv_item_id); + } + } + } + +private: + LLHandle mHandle; +};*/ + +//========================================================================= + +LLFloaterEditExtDayCycle::LLFloaterEditExtDayCycle(const LLSD &key) : + LLFloaterEditEnvironmentBase(key), + mFlyoutControl(nullptr), + mDayLength(0), + mCurrentTrack(1), + mShiftCopyEnabled(false), + mTimeSlider(nullptr), + mFramesSlider(nullptr), + mCurrentTimeLabel(nullptr), + mImportButton(nullptr), + mLoadFrame(nullptr), + mSkyBlender(), + mWaterBlender(), + mScratchSky(), + mScratchWater(), + mIsPlaying(false), + mCloneTrack(nullptr), + mLoadTrack(nullptr), + mClearTrack(nullptr) +{ + + mCommitCallbackRegistrar.add(EVNT_DAYTRACK, [this](LLUICtrl *ctrl, const LLSD &data) { onTrackSelectionCallback(data); }); + mCommitCallbackRegistrar.add(EVNT_PLAY, [this](LLUICtrl *ctrl, const LLSD &data) { onPlayActionCallback(data); }); + + mScratchSky = LLSettingsVOSky::buildDefaultSky(); + mScratchWater = LLSettingsVOWater::buildDefaultWater(); + + mEditSky = mScratchSky; + mEditWater = mScratchWater; +} + +LLFloaterEditExtDayCycle::~LLFloaterEditExtDayCycle() +{ + // Todo: consider remaking mFlyoutControl into full view class that initializes intself with floater, + // complete with postbuild, e t c... + delete mFlyoutControl; +} + +// virtual +bool LLFloaterEditExtDayCycle::postBuild() +{ + getChild(TXT_DAY_NAME)->setKeystrokeCallback(boost::bind(&LLFloaterEditExtDayCycle::onCommitName, this, _1, _2), NULL); + + mAddFrameButton = getChild(BTN_ADDFRAME, true); + mDeleteFrameButton = getChild(BTN_DELFRAME, true); + mTimeSlider = getChild(SLDR_TIME); + mFramesSlider = getChild(SLDR_KEYFRAMES); + mSkyTabLayoutContainer = getChild(VIEW_SKY_SETTINGS, true); + mWaterTabLayoutContainer = getChild(VIEW_WATER_SETTINGS, true); + mCurrentTimeLabel = getChild(LBL_CURRENT_TIME, true); + mImportButton = getChild(BTN_IMPORT, true); + mLoadFrame = getChild(BTN_LOADFRAME, true); + mCloneTrack = getChild(BTN_CLONETRACK, true); + mLoadTrack = getChild(BTN_LOADTRACK, true); + mClearTrack = getChild(BTN_CLEARTRACK, true); + + mFlyoutControl = new LLFlyoutComboBtnCtrl(this, BTN_SAVE, BTN_FLYOUT, XML_FLYOUTMENU_FILE, false); + mFlyoutControl->setAction([this](LLUICtrl *ctrl, const LLSD &data) { onButtonApply(ctrl, data); }); + + getChild(BTN_CANCEL, true)->setCommitCallback([this](LLUICtrl *ctrl, const LLSD &data) { onClickCloseBtn(); }); + mTimeSlider->setCommitCallback([this](LLUICtrl *ctrl, const LLSD &data) { onTimeSliderCallback(); }); + mAddFrameButton->setCommitCallback([this](LLUICtrl *ctrl, const LLSD &data) { onAddFrame(); }); + mDeleteFrameButton->setCommitCallback([this](LLUICtrl *ctrl, const LLSD &data) { onRemoveFrame(); }); + mImportButton->setCommitCallback([this](LLUICtrl *, const LLSD &){ onButtonImport(); }); + mLoadFrame->setCommitCallback([this](LLUICtrl *, const LLSD &){ onButtonLoadFrame(); }); + + mCloneTrack->setCommitCallback([this](LLUICtrl *, const LLSD&){ onCloneTrack(); }); + mLoadTrack->setCommitCallback([this](LLUICtrl *, const LLSD&){ onLoadTrack();}); + mClearTrack->setCommitCallback([this](LLUICtrl *, const LLSD&){ onClearTrack(); }); + + + mFramesSlider->setCommitCallback([this](LLUICtrl *, const LLSD &data) { onFrameSliderCallback(data); }); + mFramesSlider->setDoubleClickCallback([this](LLUICtrl*, S32 x, S32 y, MASK mask){ onFrameSliderDoubleClick(x, y, mask); }); + mFramesSlider->setMouseDownCallback([this](LLUICtrl*, S32 x, S32 y, MASK mask){ onFrameSliderMouseDown(x, y, mask); }); + mFramesSlider->setMouseUpCallback([this](LLUICtrl*, S32 x, S32 y, MASK mask){ onFrameSliderMouseUp(x, y, mask); }); + + mTimeSlider->addSlider(0); + + LLTabContainer* tab_container = mSkyTabLayoutContainer->getChild("sky_tabs"); + S32 tab_count = tab_container->getTabCount(); + + LLSettingsEditPanel *panel = nullptr; + + for (S32 idx = 0; idx < tab_count; ++idx) + { + panel = static_cast(tab_container->getPanelByIndex(idx)); + if (panel) + panel->setOnDirtyFlagChanged([this](LLPanel *, bool val) { onPanelDirtyFlagChanged(val); }); + } + + tab_container = mWaterTabLayoutContainer->getChild("water_tabs"); + tab_count = tab_container->getTabCount(); + + for (S32 idx = 0; idx < tab_count; ++idx) + { + LLSettingsEditPanel *panel = static_cast(tab_container->getPanelByIndex(idx)); + if (panel) + panel->setOnDirtyFlagChanged([this](LLPanel *, bool val) { onPanelDirtyFlagChanged(val); }); + } + + return true; +} + +void LLFloaterEditExtDayCycle::onOpen(const LLSD& key) +{ + if (!mEditDay) + { + LLEnvironment::instance().saveBeaconsState(); + } + mEditDay.reset(); + mEditContext = CONTEXT_UNKNOWN; + if (key.has(KEY_EDIT_CONTEXT)) + { + std::string context = key[KEY_EDIT_CONTEXT].asString(); + + if (context == VALUE_CONTEXT_INVENTORY) + mEditContext = CONTEXT_INVENTORY; + else if (context == VALUE_CONTEXT_PARCEL) + mEditContext = CONTEXT_PARCEL; + else if (context == VALUE_CONTEXT_REGION) + mEditContext = CONTEXT_REGION; + } + + if (key.has(KEY_CANMOD)) + { + mCanMod = key[KEY_CANMOD].asBoolean(); + } + + if (mEditContext == CONTEXT_UNKNOWN) + { + LL_WARNS("ENVDAYEDIT") << "Unknown editing context!" << LL_ENDL; + } + + if (key.has(KEY_INVENTORY_ID)) + { + loadInventoryItem(key[KEY_INVENTORY_ID].asUUID()); + } + else + { + mCanSave = true; + mCanCopy = true; + mCanMod = true; + mCanTrans = true; + setEditDefaultDayCycle(); + } + + mDayLength.value(0); + if (key.has(KEY_DAY_LENGTH)) + { + mDayLength.value(key[KEY_DAY_LENGTH].asReal()); + } + + // Time&Percentage labels + mCurrentTimeLabel->setTextArg("[PRCNT]", std::string("0")); + const S32 max_elm = 5; + if (mDayLength.value() != 0) + { + S32Hours hrs; + S32Minutes minutes; + LLSettingsDay::Seconds total; + LLUIString formatted_label = getString("time_label"); + for (int i = 0; i < max_elm; i++) + { + total = ((mDayLength / (max_elm - 1)) * i); + hrs = total; + minutes = total - hrs; + + formatted_label.setArg("[HH]", llformat("%d", hrs.value())); + formatted_label.setArg("[MM]", llformat("%d", abs(minutes.value()))); + getChild("p" + llformat("%d", i), true)->setTextArg("[DSC]", formatted_label.getString()); + } + hrs = mDayLength; + minutes = mDayLength - hrs; + formatted_label.setArg("[HH]", llformat("%d", hrs.value())); + formatted_label.setArg("[MM]", llformat("%d", abs(minutes.value()))); + mCurrentTimeLabel->setTextArg("[DSC]", formatted_label.getString()); + } + else + { + for (int i = 0; i < max_elm; i++) + { + getChild("p" + llformat("%d", i), true)->setTextArg("[DSC]", std::string()); + } + mCurrentTimeLabel->setTextArg("[DSC]", std::string()); + } + + // Adjust Time&Percentage labels' location according to length + LLRect label_rect = getChild("p0", true)->getRect(); + F32 slider_width = mFramesSlider->getRect().getWidth(); + for (int i = 1; i < max_elm; i++) + { + LLTextBox *pcnt_label = getChild("p" + llformat("%d", i), true); + LLRect new_rect = pcnt_label->getRect(); + new_rect.mLeft = label_rect.mLeft + (S32)(slider_width * (F32)i / (F32)(max_elm - 1)) - (S32)(pcnt_label->getTextPixelWidth() / 2); + pcnt_label->setRect(new_rect); + } + + // Altitudes&Track labels + LLUIString formatted_label = getString("sky_track_label"); + const LLEnvironment::altitude_list_t &altitudes = LLEnvironment::instance().getRegionAltitudes(); + bool extended_env = LLEnvironment::instance().isExtendedEnvironmentEnabled(); + bool use_altitudes = extended_env + && altitudes.size() > 0 + && ((mEditContext == CONTEXT_PARCEL) || (mEditContext == CONTEXT_REGION)); + for (S32 idx = 1; idx < 4; ++idx) + { + std::ostringstream convert; + if (use_altitudes) + { + convert << altitudes[idx] << "m"; + } + else + { + convert << (idx + 1); + } + formatted_label.setArg("[ALT]", convert.str()); + getChild(track_tabs[idx + 1], true)->setLabel(formatted_label.getString()); + } + + for (U32 i = 2; i < LLSettingsDay::TRACK_MAX; i++) //skies #2 through #4 + { + getChild(track_tabs[i])->setEnabled(extended_env); + } + + if (mEditContext == CONTEXT_INVENTORY) + { + mFlyoutControl->setShownBtnEnabled(true); + mFlyoutControl->setSelectedItem(ACTION_SAVE); + } + else if ((mEditContext == CONTEXT_REGION) || (mEditContext == CONTEXT_PARCEL)) + { + std::string commit_str = (mEditContext == CONTEXT_PARCEL) ? STR_COMMIT_PARCEL : STR_COMMIT_REGION; + mFlyoutControl->setMenuItemLabel(ACTION_COMMIT, getString(commit_str)); + mFlyoutControl->setShownBtnEnabled(true); + mFlyoutControl->setSelectedItem(ACTION_COMMIT); + } + else + { + mFlyoutControl->setShownBtnEnabled(false); + } +} + +void LLFloaterEditExtDayCycle::onClose(bool app_quitting) +{ + doCloseInventoryFloater(app_quitting); + doCloseTrackFloater(app_quitting); + // there's no point to change environment if we're quitting + // or if we already restored environment + stopPlay(); + LLEnvironment::instance().revertBeaconsState(); + if (!app_quitting) + { + LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_FAST); + LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_EDIT); + mEditDay.reset(); + } +} + + +void LLFloaterEditExtDayCycle::onVisibilityChange(bool new_visibility) +{ +} + +void LLFloaterEditExtDayCycle::refresh() +{ + if (mEditDay) + { + LLLineEditor* name_field = getChild(TXT_DAY_NAME); + name_field->setText(mEditDay->getName()); + name_field->setEnabled(mCanMod); + } + + + bool is_inventory_avail = canUseInventory(); + + bool show_commit = ((mEditContext == CONTEXT_PARCEL) || (mEditContext == CONTEXT_REGION)); + bool show_apply = (mEditContext == CONTEXT_INVENTORY); + + if (show_commit) + { + std::string commit_text; + if (mEditContext == CONTEXT_PARCEL) + commit_text = getString(STR_COMMIT_PARCEL); + else + commit_text = getString(STR_COMMIT_REGION); + + mFlyoutControl->setMenuItemLabel(ACTION_COMMIT, commit_text); + } + + mFlyoutControl->setMenuItemVisible(ACTION_COMMIT, show_commit); + mFlyoutControl->setMenuItemVisible(ACTION_SAVE, is_inventory_avail); + mFlyoutControl->setMenuItemVisible(ACTION_SAVEAS, is_inventory_avail); + mFlyoutControl->setMenuItemVisible(ACTION_APPLY_LOCAL, true); + mFlyoutControl->setMenuItemVisible(ACTION_APPLY_PARCEL, show_apply); + mFlyoutControl->setMenuItemVisible(ACTION_APPLY_REGION, show_apply); + + mFlyoutControl->setMenuItemEnabled(ACTION_COMMIT, show_commit && !mCommitSignal.empty()); + mFlyoutControl->setMenuItemEnabled(ACTION_SAVE, is_inventory_avail && mCanMod && !mInventoryId.isNull() && mCanSave); + mFlyoutControl->setMenuItemEnabled(ACTION_SAVEAS, is_inventory_avail && mCanCopy && mCanSave); + mFlyoutControl->setMenuItemEnabled(ACTION_APPLY_LOCAL, true); + mFlyoutControl->setMenuItemEnabled(ACTION_APPLY_PARCEL, canApplyParcel() && show_apply); + mFlyoutControl->setMenuItemEnabled(ACTION_APPLY_REGION, canApplyRegion() && show_apply); + + mImportButton->setEnabled(mCanMod); + + LLFloater::refresh(); +} + +void LLFloaterEditExtDayCycle::setEditSettingsAndUpdate(const LLSettingsBase::ptr_t &settings) +{ + setEditDayCycle(std::dynamic_pointer_cast(settings)); + + showHDRNotification(std::dynamic_pointer_cast(settings)); +} + +void LLFloaterEditExtDayCycle::setEditDayCycle(const LLSettingsDay::ptr_t &pday) +{ + mExpectingAssetId.setNull(); + mEditDay = pday->buildDeepCloneAndUncompress(); + + if (mEditDay->isTrackEmpty(LLSettingsDay::TRACK_WATER)) + { + LL_WARNS("ENVDAYEDIT") << "No water frames found, generating replacement" << LL_ENDL; + mEditDay->setWaterAtKeyframe(LLSettingsVOWater::buildDefaultWater(), .5f); + } + + if (mEditDay->isTrackEmpty(LLSettingsDay::TRACK_GROUND_LEVEL)) + { + LL_WARNS("ENVDAYEDIT") << "No sky frames found, generating replacement" << LL_ENDL; + mEditDay->setSkyAtKeyframe(LLSettingsVOSky::buildDefaultSky(), .5f, LLSettingsDay::TRACK_GROUND_LEVEL); + } + + mCanSave = !pday->getFlag(LLSettingsBase::FLAG_NOSAVE); + mCanCopy = !pday->getFlag(LLSettingsBase::FLAG_NOCOPY) && mCanSave; + mCanMod = !pday->getFlag(LLSettingsBase::FLAG_NOMOD) && mCanSave; + mCanTrans = !pday->getFlag(LLSettingsBase::FLAG_NOTRANS) && mCanSave; + + updateEditEnvironment(); + LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_EDIT, LLEnvironment::TRANSITION_INSTANT); + LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT); + synchronizeTabs(); + updateTabs(); + refresh(); +} + + +void LLFloaterEditExtDayCycle::setEditDefaultDayCycle() +{ + mInventoryItem = nullptr; + mInventoryId.setNull(); + mExpectingAssetId = LLSettingsDay::GetDefaultAssetId(); + LLSettingsVOBase::getSettingsAsset(LLSettingsDay::GetDefaultAssetId(), + [this](LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status, LLExtStat) { onAssetLoaded(asset_id, settings, status); }); +} + +std::string LLFloaterEditExtDayCycle::getEditName() const +{ + if (mEditDay) + return mEditDay->getName(); + return "new"; +} + +void LLFloaterEditExtDayCycle::setEditName(const std::string &name) +{ + if (mEditDay) + mEditDay->setName(name); + getChild(TXT_DAY_NAME)->setText(name); +} + +/* virtual */ +bool LLFloaterEditExtDayCycle::handleKeyUp(KEY key, MASK mask, bool called_from_parent) +{ + if (!mEditDay) + { + mShiftCopyEnabled = false; + } + else if (mask == MASK_SHIFT && mShiftCopyEnabled) + { + mShiftCopyEnabled = false; + std::string curslider = mFramesSlider->getCurSlider(); + if (!curslider.empty()) + { + F32 sliderpos = mFramesSlider->getCurSliderValue(); + + keymap_t::iterator it = mSliderKeyMap.find(curslider); + if (it != mSliderKeyMap.end()) + { + if (mEditDay->moveTrackKeyframe(mCurrentTrack, (*it).second.mFrame, sliderpos)) + { + (*it).second.mFrame = sliderpos; + } + else + { + mFramesSlider->setCurSliderValue((*it).second.mFrame); + } + } + else + { + LL_WARNS("ENVDAYEDIT") << "Failed to find frame " << sliderpos << " for slider " << curslider << LL_ENDL; + } + } + } + return LLFloater::handleKeyUp(key, mask, called_from_parent); +} + +void LLFloaterEditExtDayCycle::onButtonApply(LLUICtrl *ctrl, const LLSD &data) +{ + std::string ctrl_action = ctrl->getName(); + + if (!mEditDay) + { + LL_WARNS("ENVDAYEDIT") << "mEditDay is null! This should never happen! Something is very very wrong" << LL_ENDL; + LLNotificationsUtil::add("EnvironmentApplyFailed"); + closeFloater(); + return; + } + + LLSettingsDay::ptr_t dayclone = mEditDay->buildClone(); // create a compressed copy + + if (!dayclone) + { + LL_WARNS("ENVDAYEDIT") << "Unable to clone daycylce from editor." << LL_ENDL; + return; + } + + // brute-force local texture scan + for (U32 i = 0; i <= LLSettingsDay::TRACK_MAX; i++) + { + LLSettingsDay::CycleTrack_t &day_track = dayclone->getCycleTrack(i); + + LLSettingsDay::CycleTrack_t::iterator iter = day_track.begin(); + LLSettingsDay::CycleTrack_t::iterator end = day_track.end(); + S32 frame_num = 0; + + while (iter != end) + { + frame_num++; + std::string desc; + bool is_local = false; // because getString can be empty + if (i == LLSettingsDay::TRACK_WATER) + { + LLSettingsWater::ptr_t water = std::static_pointer_cast(iter->second); + if (water) + { + // LLViewerFetchedTexture and check for FTT_LOCAL_FILE or check LLLocalBitmapMgr + if (LLLocalBitmapMgr::getInstance()->isLocal(water->getNormalMapID())) + { + desc = LLTrans::getString("EnvironmentNormalMap"); + is_local = true; + } + else if (LLLocalBitmapMgr::getInstance()->isLocal(water->getTransparentTextureID())) + { + desc = LLTrans::getString("EnvironmentTransparent"); + is_local = true; + } + } + } + else + { + LLSettingsSky::ptr_t sky = std::static_pointer_cast(iter->second); + if (sky) + { + if (LLLocalBitmapMgr::getInstance()->isLocal(sky->getSunTextureId())) + { + desc = LLTrans::getString("EnvironmentSun"); + is_local = true; + } + else if (LLLocalBitmapMgr::getInstance()->isLocal(sky->getMoonTextureId())) + { + desc = LLTrans::getString("EnvironmentMoon"); + is_local = true; + } + else if (LLLocalBitmapMgr::getInstance()->isLocal(sky->getCloudNoiseTextureId())) + { + desc = LLTrans::getString("EnvironmentCloudNoise"); + is_local = true; + } + else if (LLLocalBitmapMgr::getInstance()->isLocal(sky->getBloomTextureId())) + { + desc = LLTrans::getString("EnvironmentBloom"); + is_local = true; + } + } + } + + if (is_local) + { + LLSD args; + LLButton* button = getChild(track_tabs[i], true); + args["TRACK"] = button->getCurrentLabel(); + args["FRAME"] = iter->first * 100; // % + args["FIELD"] = desc; + args["FRAMENO"] = frame_num; + LLNotificationsUtil::add("WLLocalTextureDayBlock", args); + return; + } + iter++; + } + } + + if (ctrl_action == ACTION_SAVE) + { + doApplyUpdateInventory(dayclone); + clearDirtyFlag(); + } + else if (ctrl_action == ACTION_SAVEAS) + { + LLSD args; + args["DESC"] = dayclone->getName(); + LLNotificationsUtil::add("SaveSettingAs", args, LLSD(), boost::bind(&LLFloaterEditExtDayCycle::onSaveAsCommit, this, _1, _2, dayclone)); + } + else if ((ctrl_action == ACTION_APPLY_LOCAL) || + (ctrl_action == ACTION_APPLY_PARCEL) || + (ctrl_action == ACTION_APPLY_REGION)) + { + doApplyEnvironment(ctrl_action, dayclone); + } + else if (ctrl_action == ACTION_COMMIT) + { + doApplyCommit(dayclone); + } + else + { + LL_WARNS("ENVDAYEDIT") << "Unknown settings action '" << ctrl_action << "'" << LL_ENDL; + } +} + +void LLFloaterEditExtDayCycle::onButtonLoadFrame() +{ + doOpenInventoryFloater((mCurrentTrack == LLSettingsDay::TRACK_WATER) ? LLSettingsType::ST_WATER : LLSettingsType::ST_SKY, LLUUID::null); +} + +void LLFloaterEditExtDayCycle::onAddFrame() +{ + LLSettingsBase::Seconds frame(mTimeSlider->getCurSliderValue()); + LLSettingsBase::ptr_t setting; + if (!mEditDay) + { + LL_WARNS("ENVDAYEDIT") << "Attempt to add new frame while waiting for day(asset) to load." << LL_ENDL; + return; + } + if ((mEditDay->getSettingsNearKeyframe(frame, mCurrentTrack, LLSettingsDay::DEFAULT_FRAME_SLOP_FACTOR)).second) + { + LL_WARNS("ENVDAYEDIT") << "Attempt to add new frame too close to existing frame." << LL_ENDL; + return; + } + if (!mFramesSlider->canAddSliders()) + { + // Shouldn't happen, button should be disabled + LL_WARNS("ENVDAYEDIT") << "Attempt to add new frame when slider is full." << LL_ENDL; + return; + } + + if (mCurrentTrack == LLSettingsDay::TRACK_WATER) + { + // scratch water should always have the current water settings. + LLSettingsWater::ptr_t water(mScratchWater->buildClone()); + setting = water; + mEditDay->setWaterAtKeyframe( std::static_pointer_cast(setting), frame); + } + else + { + // scratch sky should always have the current sky settings. + LLSettingsSky::ptr_t sky(mScratchSky->buildClone()); + setting = sky; + mEditDay->setSkyAtKeyframe(sky, frame, mCurrentTrack); + } + setDirtyFlag(); + addSliderFrame(frame, setting); + updateTabs(); +} + +void LLFloaterEditExtDayCycle::onRemoveFrame() +{ + std::string sldr_key = mFramesSlider->getCurSlider(); + if (sldr_key.empty()) + { + return; + } + setDirtyFlag(); + removeCurrentSliderFrame(); + updateTabs(); +} + + +void LLFloaterEditExtDayCycle::onCloneTrack() +{ + if (!mEditDay) + { + LL_WARNS("ENVDAYEDIT") << "Attempt to copy track while waiting for day(asset) to load." << LL_ENDL; + return; + } + const LLEnvironment::altitude_list_t &altitudes = LLEnvironment::instance().getRegionAltitudes(); + bool use_altitudes = altitudes.size() > 0 && ((mEditContext == CONTEXT_PARCEL) || (mEditContext == CONTEXT_REGION)); + + LLSD args = LLSD::emptyArray(); + + S32 populated_counter = 0; + for (U32 i = 1; i < LLSettingsDay::TRACK_MAX; i++) + { + LLSD track; + track["id"] = LLSD::Integer(i); + bool populated = (!mEditDay->isTrackEmpty(i)) && (i != mCurrentTrack); + track["enabled"] = populated; + if (populated) + { + populated_counter++; + } + if (use_altitudes) + { + track["altitude"] = altitudes[i - 1]; + } + args.append(track); + } + + if (populated_counter > 0) + { + doOpenTrackFloater(args); + } + else + { + // Should not happen + LL_WARNS("ENVDAYEDIT") << "Tried to copy tracks, but there are no available sources" << LL_ENDL; + } +} + + +void LLFloaterEditExtDayCycle::onLoadTrack() +{ + LLUUID curitemId = mInventoryId; + + if (mCurrentEdit && curitemId.notNull()) + { + curitemId = LLFloaterSettingsPicker::findItemID(mCurrentEdit->getAssetId(), false, false); + } + + doOpenInventoryFloater(LLSettingsType::ST_DAYCYCLE, curitemId); +} + + +void LLFloaterEditExtDayCycle::onClearTrack() +{ + if (!mEditDay) + { + LL_WARNS("ENVDAYEDIT") << "Attempt to clear track while waiting for day(asset) to load." << LL_ENDL; + return; + } + + if (mCurrentTrack > 1) + mEditDay->getCycleTrack(mCurrentTrack).clear(); + else + { + LLSettingsDay::CycleTrack_t &track(mEditDay->getCycleTrack(mCurrentTrack)); + + auto first = track.begin(); + auto last = track.end(); + ++first; + track.erase(first, last); + } + + updateEditEnvironment(); + LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_EDIT, LLEnvironment::TRANSITION_INSTANT); + LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT); + synchronizeTabs(); + updateTabs(); + refresh(); +} + +void LLFloaterEditExtDayCycle::onCommitName(class LLLineEditor* caller, void* user_data) +{ + if (!mEditDay) + { + LL_WARNS("ENVDAYEDIT") << "Attempt to rename day while waiting for day(asset) to load." << LL_ENDL; + return; + } + + mEditDay->setName(caller->getText()); +} + +void LLFloaterEditExtDayCycle::onTrackSelectionCallback(const LLSD& user_data) +{ + U32 track_index = user_data.asInteger(); // 1-5 + selectTrack(track_index); +} + +void LLFloaterEditExtDayCycle::onPlayActionCallback(const LLSD& user_data) +{ + std::string action = user_data.asString(); + + F32 frame = mTimeSlider->getCurSliderValue(); + + if (action == ACTION_PLAY) + { + startPlay(); + } + else if (action == ACTION_PAUSE) + { + stopPlay(); + } + else if (mSliderKeyMap.size() != 0) + { + F32 new_frame = 0; + if (action == ACTION_FORWARD) + { + new_frame = mEditDay->getUpperBoundFrame(mCurrentTrack, frame + (mTimeSlider->getIncrement() / 2)); + } + else if (action == ACTION_BACK) + { + new_frame = mEditDay->getLowerBoundFrame(mCurrentTrack, frame - (mTimeSlider->getIncrement() / 2)); + } + selectFrame(new_frame, 0.0f); + stopPlay(); + } +} + +void LLFloaterEditExtDayCycle::onFrameSliderCallback(const LLSD &data) +{ + std::string curslider = mFramesSlider->getCurSlider(); + + if (!curslider.empty() && mEditDay) + { + F32 sliderpos = mFramesSlider->getCurSliderValue(); + + keymap_t::iterator it = mSliderKeyMap.find(curslider); + if (it != mSliderKeyMap.end()) + { + if (gKeyboard->currentMask(true) == MASK_SHIFT && mShiftCopyEnabled && mCanMod) + { + // don't move the point/frame as long as shift is pressed and user is attempting to copy + // handleKeyUp will do the move if user releases key too early. + if (!(mEditDay->getSettingsNearKeyframe(sliderpos, mCurrentTrack, LLSettingsDay::DEFAULT_FRAME_SLOP_FACTOR)).second) + { + LL_DEBUGS("ENVDAYEDIT") << "Copying frame from " << it->second.mFrame << " to " << sliderpos << LL_ENDL; + LLSettingsBase::ptr_t new_settings; + + // mEditDay still remembers old position, add copy at new position + if (mCurrentTrack == LLSettingsDay::TRACK_WATER) + { + LLSettingsWaterPtr_t water_ptr = std::dynamic_pointer_cast(it->second.pSettings)->buildClone(); + mEditDay->setWaterAtKeyframe(water_ptr, sliderpos); + new_settings = water_ptr; + } + else + { + LLSettingsSkyPtr_t sky_ptr = std::dynamic_pointer_cast(it->second.pSettings)->buildClone(); + mEditDay->setSkyAtKeyframe(sky_ptr, sliderpos, mCurrentTrack); + new_settings = sky_ptr; + } + // mSliderKeyMap still remembers old position, for simplicity, just move it to be identical to slider + F32 old_frame = it->second.mFrame; + it->second.mFrame = sliderpos; + // slider already moved old frame, create new one in old place + addSliderFrame(old_frame, new_settings, false /*because we are going to reselect new one*/); + // reselect new frame + mFramesSlider->setCurSlider(it->first); + mShiftCopyEnabled = false; + setDirtyFlag(); + } + } + else + { + // slider rounds values to nearest increments, changes can be substanntial (half increment) + if (abs(mFramesSlider->getNearestIncrement((*it).second.mFrame) - sliderpos) < F_APPROXIMATELY_ZERO) + { + // same value + mFramesSlider->setCurSliderValue((*it).second.mFrame); + } + else if (mEditDay->moveTrackKeyframe(mCurrentTrack, (*it).second.mFrame, sliderpos) && mCanMod) + { + (*it).second.mFrame = sliderpos; + setDirtyFlag(); + } + else + { + // same value, wrong track, no such value, no mod + mFramesSlider->setCurSliderValue((*it).second.mFrame); + } + + mShiftCopyEnabled = false; + } + } + } +} + +void LLFloaterEditExtDayCycle::onFrameSliderDoubleClick(S32 x, S32 y, MASK mask) +{ + stopPlay(); + onAddFrame(); +} + +void LLFloaterEditExtDayCycle::onFrameSliderMouseDown(S32 x, S32 y, MASK mask) +{ + stopPlay(); + F32 sliderpos = mFramesSlider->getSliderValueFromPos(x, y); + + std::string slidername = mFramesSlider->getCurSlider(); + + mShiftCopyEnabled = !slidername.empty() && gKeyboard->currentMask(true) == MASK_SHIFT; + + if (!slidername.empty()) + { + LLRect thumb_rect = mFramesSlider->getSliderThumbRect(slidername); + if ((x >= thumb_rect.mRight) || (x <= thumb_rect.mLeft)) + { + mFramesSlider->resetCurSlider(); + } + } + + mTimeSlider->setCurSliderValue(sliderpos); + + updateTabs(); + LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT); +} + +void LLFloaterEditExtDayCycle::onFrameSliderMouseUp(S32 x, S32 y, MASK mask) +{ + // Only happens when clicking on empty space of frameslider, not on specific frame + F32 sliderpos = mFramesSlider->getSliderValueFromPos(x, y); + + mTimeSlider->setCurSliderValue(sliderpos); + selectFrame(sliderpos, LLSettingsDay::DEFAULT_FRAME_SLOP_FACTOR); +} + +void LLFloaterEditExtDayCycle::onTimeSliderCallback() +{ + stopPlay(); + selectFrame(mTimeSlider->getCurSliderValue(), LLSettingsDay::DEFAULT_FRAME_SLOP_FACTOR); +} + +void LLFloaterEditExtDayCycle::cloneTrack(U32 source_index, U32 dest_index) +{ + cloneTrack(mEditDay, source_index, dest_index); +} + +void LLFloaterEditExtDayCycle::cloneTrack(const LLSettingsDay::ptr_t &source_day, U32 source_index, U32 dest_index) +{ + if ((source_index == LLSettingsDay::TRACK_WATER || dest_index == LLSettingsDay::TRACK_WATER) && (source_index != dest_index)) + { // one of the tracks is a water track, the other is not + LLSD args; + + LL_WARNS() << "Can not import water track into sky track or vice versa" << LL_ENDL; + + LLButton* button = getChild(track_tabs[source_index], true); + args["TRACK1"] = button->getCurrentLabel(); + button = getChild(track_tabs[dest_index], true); + args["TRACK2"] = button->getCurrentLabel(); + + LLNotificationsUtil::add("TrackLoadMismatch", args); + return; + } + + // don't use replaceCycleTrack because we will end up with references, but we need to clone + + // hold on to a backup of the + LLSettingsDay::CycleTrack_t backup_track = mEditDay->getCycleTrack(dest_index); + + mEditDay->clearCycleTrack(dest_index); // because source can be empty + LLSettingsDay::CycleTrack_t source_track = source_day->getCycleTrack(source_index); + S32 addcount(0); + for (auto &track_frame : source_track) + { + LLSettingsBase::ptr_t pframe = track_frame.second; + LLSettingsBase::ptr_t pframeclone = pframe->buildDerivedClone(); + if (pframeclone) + { + ++addcount; + mEditDay->setSettingsAtKeyframe(pframeclone, track_frame.first, dest_index); + } + } + + if (!addcount) + { // nothing was actually added. Restore the old track and issue a warning. + mEditDay->replaceCycleTrack(dest_index, backup_track); + + LLSD args; + LLButton* button = getChild(track_tabs[dest_index], true); + args["TRACK"] = button->getCurrentLabel(); + + LLNotificationsUtil::add("TrackLoadFailed", args); + } + setDirtyFlag(); + + updateSlider(); + updateTabs(); + updateButtons(); +} + +void LLFloaterEditExtDayCycle::selectTrack(U32 track_index, bool force ) +{ + if (track_index < LLSettingsDay::TRACK_MAX) + mCurrentTrack = track_index; + + LLButton* button = getChild(track_tabs[mCurrentTrack], true); + if (button->getToggleState() && !force) + { + return; + } + + for (U32 i = 0; i < LLSettingsDay::TRACK_MAX; i++) // use max value + { + getChild(track_tabs[i], true)->setToggleState(i == mCurrentTrack); + } + + bool show_water = (mCurrentTrack == LLSettingsDay::TRACK_WATER); + mSkyTabLayoutContainer->setVisible(!show_water); + mWaterTabLayoutContainer->setVisible(show_water); + + updateSlider(); + updateLabels(); +} + +void LLFloaterEditExtDayCycle::selectFrame(F32 frame, F32 slop_factor) +{ + mFramesSlider->resetCurSlider(); + + keymap_t::iterator iter = mSliderKeyMap.begin(); + keymap_t::iterator end_iter = mSliderKeyMap.end(); + while (iter != end_iter) + { + F32 keyframe = iter->second.mFrame; + F32 frame_dif = fabs(keyframe - frame); + if (frame_dif <= slop_factor) + { + keymap_t::iterator next_iter = std::next(iter); + if ((frame_dif != 0) && (next_iter != end_iter)) + { + if (fabs(next_iter->second.mFrame - frame) < frame_dif) + { + mFramesSlider->setCurSlider(next_iter->first); + frame = next_iter->second.mFrame; + break; + } + } + mFramesSlider->setCurSlider(iter->first); + frame = iter->second.mFrame; + break; + } + iter++; + } + + mTimeSlider->setCurSliderValue(frame); + // block or update tabs according to new selection + updateTabs(); +// LLEnvironment::instance().updateEnvironment(); +} + +void LLFloaterEditExtDayCycle::clearTabs() +{ + // Note: If this doesn't look good, init panels with default settings. It might be better looking + if (mCurrentTrack == LLSettingsDay::TRACK_WATER) + { + const LLSettingsWaterPtr_t p_water = LLSettingsWaterPtr_t(NULL); + updateWaterTabs(p_water); + } + else + { + const LLSettingsSkyPtr_t p_sky = LLSettingsSkyPtr_t(NULL); + updateSkyTabs(p_sky); + } + updateButtons(); + updateTimeAndLabel(); +} + +void LLFloaterEditExtDayCycle::updateTabs() +{ + reblendSettings(); + synchronizeTabs(); + + updateButtons(); + updateTimeAndLabel(); +} + +void LLFloaterEditExtDayCycle::updateWaterTabs(const LLSettingsWaterPtr_t &p_water) +{ + LLView* tab_container = mWaterTabLayoutContainer->getChild(TABS_WATER); //can't extract panels directly, since it is in 'tuple' + LLPanelSettingsWaterMainTab* panel = dynamic_cast(tab_container->findChildView("water_panel")); + if (panel) + { + panel->setWater(p_water); + } +} + +void LLFloaterEditExtDayCycle::updateSkyTabs(const LLSettingsSkyPtr_t &p_sky) +{ + LLTabContainer* tab_container = mSkyTabLayoutContainer->getChild(TABS_SKYS); //can't extract panels directly, since they are in 'tuple' + + LLPanelSettingsSky* panel; + panel = dynamic_cast(tab_container->findChildView("atmosphere_panel")); + if (panel) + { + panel->setSky(p_sky); + } + panel = dynamic_cast(tab_container->findChildView("clouds_panel")); + if (panel) + { + panel->setSky(p_sky); + } + panel = dynamic_cast(tab_container->findChildView("moon_panel")); + if (panel) + { + panel->setSky(p_sky); + } +} + +void LLFloaterEditExtDayCycle::updateLabels() +{ + std::string label_arg = (mCurrentTrack == LLSettingsDay::TRACK_WATER) ? "water_label" : "sky_label"; + + mAddFrameButton->setLabelArg("[FRAME]", getString(label_arg)); + mDeleteFrameButton->setLabelArg("[FRAME]", getString(label_arg)); + mLoadFrame->setLabelArg("[FRAME]", getString(label_arg)); +} + +void LLFloaterEditExtDayCycle::updateButtons() +{ + // This logic appears to work in reverse, the add frame button + // is only enabled when you're on an existing frame and disabled + // in all the interim positions where you'd want to add a frame... + + bool can_manipulate = mEditDay && !mIsPlaying && mCanMod; + bool can_clone(false); + bool can_clear(false); + + if (can_manipulate) + { + if (mCurrentTrack == 0) + { + can_clone = false; + } + else + { + for (S32 track = 1; track < LLSettingsDay::TRACK_MAX; ++track) + { + if (track == mCurrentTrack) + continue; + can_clone |= !mEditDay->getCycleTrack(track).empty(); + } + } + + can_clear = (mCurrentTrack > 1) ? (!mEditDay->getCycleTrack(mCurrentTrack).empty()) : (mEditDay->getCycleTrack(mCurrentTrack).size() > 1); + } + + mCloneTrack->setEnabled(can_clone); + mLoadTrack->setEnabled(can_manipulate); + mClearTrack->setEnabled(can_clear); + mAddFrameButton->setEnabled(can_manipulate && isAddingFrameAllowed()); + mDeleteFrameButton->setEnabled(can_manipulate && isRemovingFrameAllowed()); + mLoadFrame->setEnabled(can_manipulate); + + bool enable_play = (bool)mEditDay; + childSetEnabled(BTN_PLAY, enable_play); + childSetEnabled(BTN_SKIP_BACK, enable_play); + childSetEnabled(BTN_SKIP_FORWARD, enable_play); + + // update track buttons + bool extended_env = LLEnvironment::instance().isExtendedEnvironmentEnabled(); + for (S32 track = 0; track < LLSettingsDay::TRACK_MAX; ++track) + { + LLButton* button = getChild(track_tabs[track], true); + button->setEnabled(extended_env); + button->setToggleState(track == mCurrentTrack); + } +} + +void LLFloaterEditExtDayCycle::updateSlider() +{ + F32 frame_position = mTimeSlider->getCurSliderValue(); + mFramesSlider->clear(); + mSliderKeyMap.clear(); + + if (!mEditDay) + { + // floater is waiting for asset + return; + } + + LLSettingsDay::CycleTrack_t track = mEditDay->getCycleTrack(mCurrentTrack); + for (auto &track_frame : track) + { + addSliderFrame(track_frame.first, track_frame.second, false); + } + + if (mSliderKeyMap.size() > 0) + { + // update positions + mLastFrameSlider = mFramesSlider->getCurSlider(); + } + else + { + // disable panels + clearTabs(); + mLastFrameSlider.clear(); + } + + selectFrame(frame_position, LLSettingsDay::DEFAULT_FRAME_SLOP_FACTOR); +} + +void LLFloaterEditExtDayCycle::updateTimeAndLabel() +{ + F32 time = mTimeSlider->getCurSliderValue(); + mCurrentTimeLabel->setTextArg("[PRCNT]", llformat("%.0f", time * 100)); + if (mDayLength.value() != 0) + { + LLUIString formatted_label = getString("time_label"); + + LLSettingsDay::Seconds total = (mDayLength * time); + S32Hours hrs = total; + S32Minutes minutes = total - hrs; + + formatted_label.setArg("[HH]", llformat("%d", hrs.value())); + formatted_label.setArg("[MM]", llformat("%d", abs(minutes.value()))); + mCurrentTimeLabel->setTextArg("[DSC]", formatted_label.getString()); + } + else + { + mCurrentTimeLabel->setTextArg("[DSC]", std::string()); + } + + // Update blender here: +} + +void LLFloaterEditExtDayCycle::addSliderFrame(F32 frame, const LLSettingsBase::ptr_t &setting, bool update_ui) +{ + // multi slider distinguishes elements by key/name in string format + // store names to map to be able to recall dependencies + std::string new_slider = mFramesSlider->addSlider(frame); + if (!new_slider.empty()) + { + mSliderKeyMap[new_slider] = FrameData(frame, setting); + + if (update_ui) + { + mLastFrameSlider = new_slider; + mTimeSlider->setCurSliderValue(frame); + updateTabs(); + } + } +} + +void LLFloaterEditExtDayCycle::removeCurrentSliderFrame() +{ + std::string sldr = mFramesSlider->getCurSlider(); + if (sldr.empty()) + { + return; + } + mFramesSlider->deleteCurSlider(); + keymap_t::iterator iter = mSliderKeyMap.find(sldr); + if (iter != mSliderKeyMap.end()) + { + LL_DEBUGS("ENVDAYEDIT") << "Removing frame from " << iter->second.mFrame << LL_ENDL; + LLSettingsBase::Seconds seconds(iter->second.mFrame); + mEditDay->removeTrackKeyframe(mCurrentTrack, seconds); + mSliderKeyMap.erase(iter); + } + + mLastFrameSlider = mFramesSlider->getCurSlider(); + mTimeSlider->setCurSliderValue(mFramesSlider->getCurSliderValue()); + updateTabs(); +} + +void LLFloaterEditExtDayCycle::removeSliderFrame(F32 frame) +{ + keymap_t::iterator it = std::find_if(mSliderKeyMap.begin(), mSliderKeyMap.end(), + [frame](const keymap_t::value_type &value) { return fabs(value.second.mFrame - frame) < LLSettingsDay::DEFAULT_FRAME_SLOP_FACTOR; }); + + if (it != mSliderKeyMap.end()) + { + mFramesSlider->deleteSlider((*it).first); + mSliderKeyMap.erase(it); + } + +} + +//------------------------------------------------------------------------- + +LLFloaterEditExtDayCycle::connection_t LLFloaterEditExtDayCycle::setEditCommitSignal(LLFloaterEditExtDayCycle::edit_commit_signal_t::slot_type cb) +{ + return mCommitSignal.connect(cb); +} + +void LLFloaterEditExtDayCycle::updateEditEnvironment(void) +{ + if (!mEditDay) + return; + S32 skytrack = (mCurrentTrack) ? mCurrentTrack : 1; + mSkyBlender = std::make_shared(mScratchSky, mEditDay, skytrack); + mWaterBlender = std::make_shared(mScratchWater, mEditDay, LLSettingsDay::TRACK_WATER); + + if (LLEnvironment::instance().isExtendedEnvironmentEnabled()) + { + selectTrack(LLSettingsDay::TRACK_MAX, true); + } + else + { + selectTrack(1, true); + } + + reblendSettings(); + + LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_EDIT, mEditSky, mEditWater); + LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT); +} + +void LLFloaterEditExtDayCycle::synchronizeTabs() +{ + // This should probably get moved into "updateTabs" + std::string curslider = mFramesSlider->getCurSlider(); + bool canedit(false); + + LLSettingsWater::ptr_t psettingW; + LLTabContainer * tabs = mWaterTabLayoutContainer->getChild(TABS_WATER); + if (mCurrentTrack == LLSettingsDay::TRACK_WATER) + { + if (!mEditDay) + { + canedit = false; + } + else if (!curslider.empty()) + { + canedit = !mIsPlaying; + // either search mEditDay or retrieve from mSliderKeyMap + keymap_t::iterator slider_it = mSliderKeyMap.find(curslider); + if (slider_it != mSliderKeyMap.end()) + { + psettingW = std::static_pointer_cast(slider_it->second.pSettings); + } + } + mCurrentEdit = psettingW; + if (!psettingW) + { + canedit = false; + psettingW = mScratchWater; + } + + getChild(ICN_LOCK_EDIT)->setVisible(!canedit); + } + else + { + psettingW = mScratchWater; + } + mEditWater = psettingW; + + setTabsData(tabs, psettingW, canedit); + + LLSettingsSky::ptr_t psettingS; + canedit = false; + tabs = mSkyTabLayoutContainer->getChild(TABS_SKYS); + if (mCurrentTrack != LLSettingsDay::TRACK_WATER) + { + if (!mEditDay) + { + canedit = false; + } + else if (!curslider.empty()) + { + canedit = !mIsPlaying; + // either search mEditDay or retrieve from mSliderKeyMap + keymap_t::iterator slider_it = mSliderKeyMap.find(curslider); + if (slider_it != mSliderKeyMap.end()) + { + psettingS = std::static_pointer_cast(slider_it->second.pSettings); + } + } + mCurrentEdit = psettingS; + if (!psettingS) + { + canedit = false; + psettingS = mScratchSky; + } + + getChild(ICN_LOCK_EDIT)->setVisible(!canedit); + } + else + { + psettingS = mScratchSky; + } + mEditSky = psettingS; + + doCloseInventoryFloater(); + doCloseTrackFloater(); + + setTabsData(tabs, psettingS, canedit); + LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_EDIT, mEditSky, mEditWater); + LLEnvironment::instance().updateEnvironment(LLEnvironment::TRANSITION_INSTANT); +} + +void LLFloaterEditExtDayCycle::setTabsData(LLTabContainer * tabcontainer, const LLSettingsBase::ptr_t &settings, bool editable) +{ + S32 count = tabcontainer->getTabCount(); + for (S32 idx = 0; idx < count; ++idx) + { + LLSettingsEditPanel *panel = static_cast(tabcontainer->getPanelByIndex(idx)); + if (panel) + { + panel->setCanChangeSettings(editable & mCanMod); + panel->setSettings(settings); + } + } +} + + +void LLFloaterEditExtDayCycle::reblendSettings() +{ + F64 position = mTimeSlider->getCurSliderValue(); + + if (mSkyBlender) + { + if ((mSkyBlender->getTrack() != mCurrentTrack) && (mCurrentTrack != LLSettingsDay::TRACK_WATER)) + { + mSkyBlender->switchTrack(mCurrentTrack, position); + } + else + { + mSkyBlender->setPosition(position); + } + } + + if (mWaterBlender) + { + mWaterBlender->setPosition(position); + } +} + +void LLFloaterEditExtDayCycle::doApplyCommit(LLSettingsDay::ptr_t day) +{ + if (!mCommitSignal.empty()) + { + mCommitSignal(day); + + closeFloater(); + } +} + +bool LLFloaterEditExtDayCycle::isRemovingFrameAllowed() +{ + if (mFramesSlider->getCurSlider().empty()) return false; + + if (mCurrentTrack <= LLSettingsDay::TRACK_GROUND_LEVEL) + { + return (mSliderKeyMap.size() > 1); + } + else + { + return (mSliderKeyMap.size() > 0); + } +} + +bool LLFloaterEditExtDayCycle::isAddingFrameAllowed() +{ + if (!mFramesSlider->getCurSlider().empty() || !mEditDay) return false; + + LLSettingsBase::Seconds frame(mTimeSlider->getCurSliderValue()); + if ((mEditDay->getSettingsNearKeyframe(frame, mCurrentTrack, LLSettingsDay::DEFAULT_FRAME_SLOP_FACTOR)).second) + { + return false; + } + return mFramesSlider->canAddSliders(); +} + +void LLFloaterEditExtDayCycle::doImportFromDisk() +{ // Load a a legacy Windlight XML from disk. + LLFilePickerReplyThread::startPicker(boost::bind(&LLFloaterEditExtDayCycle::loadSettingFromFile, this, _1), LLFilePicker::FFLOAD_XML, false); +} + +void LLFloaterEditExtDayCycle::loadSettingFromFile(const std::vector& filenames) +{ + LLSD messages; + if (filenames.size() < 1) return; + std::string filename = filenames[0]; + LL_DEBUGS("ENVDAYEDIT") << "Selected file: " << filename << LL_ENDL; + LLSettingsDay::ptr_t legacyday = LLEnvironment::createDayCycleFromLegacyPreset(filename, messages); + + if (!legacyday) + { + LLNotificationsUtil::add("WLImportFail", messages); + return; + } + + loadInventoryItem(LLUUID::null); + + mCurrentTrack = 1; + setDirtyFlag(); + setEditDayCycle(legacyday); +} + +void LLFloaterEditExtDayCycle::startPlay() +{ + doCloseInventoryFloater(); + doCloseTrackFloater(); + + mIsPlaying = true; + mFramesSlider->resetCurSlider(); + mPlayTimer.reset(); + mPlayTimer.start(); + gIdleCallbacks.addFunction(onIdlePlay, this); + mPlayStartFrame = mTimeSlider->getCurSliderValue(); + + getChild("play_layout", true)->setVisible(false); + getChild("pause_layout", true)->setVisible(true); +} + +void LLFloaterEditExtDayCycle::stopPlay() +{ + if (!mIsPlaying) + return; + + mIsPlaying = false; + gIdleCallbacks.deleteFunction(onIdlePlay, this); + mPlayTimer.stop(); + F32 frame = mTimeSlider->getCurSliderValue(); + selectFrame(frame, LLSettingsDay::DEFAULT_FRAME_SLOP_FACTOR); + + getChild("play_layout", true)->setVisible(true); + getChild("pause_layout", true)->setVisible(false); +} + +//static +void LLFloaterEditExtDayCycle::onIdlePlay(void* user_data) +{ + if (!gDisconnected) + { + LLFloaterEditExtDayCycle* self = (LLFloaterEditExtDayCycle*)user_data; + + if (self->mSkyBlender == nullptr || self->mWaterBlender == nullptr) + { + self->stopPlay(); + } + else + { + + F32 prcnt_played = self->mPlayTimer.getElapsedTimeF32() / DAY_CYCLE_PLAY_TIME_SECONDS; + F32 new_frame = fmod(self->mPlayStartFrame + prcnt_played, 1.f); + + self->mTimeSlider->setCurSliderValue(new_frame); // will do the rounding + self->mSkyBlender->setPosition(new_frame); + self->mWaterBlender->setPosition(new_frame); + self->synchronizeTabs(); + self->updateTimeAndLabel(); + self->updateButtons(); + } + } +} + + +void LLFloaterEditExtDayCycle::clearDirtyFlag() +{ + mIsDirty = false; + + LLTabContainer* tab_container = mSkyTabLayoutContainer->getChild("sky_tabs"); + S32 tab_count = tab_container->getTabCount(); + + for (S32 idx = 0; idx < tab_count; ++idx) + { + LLSettingsEditPanel *panel = static_cast(tab_container->getPanelByIndex(idx)); + if (panel) + panel->clearIsDirty(); + } + + tab_container = mWaterTabLayoutContainer->getChild("water_tabs"); + tab_count = tab_container->getTabCount(); + + for (S32 idx = 0; idx < tab_count; ++idx) + { + LLSettingsEditPanel *panel = static_cast(tab_container->getPanelByIndex(idx)); + if (panel) + panel->clearIsDirty(); + } + +} + +void LLFloaterEditExtDayCycle::doOpenTrackFloater(const LLSD &args) +{ + LLFloaterTrackPicker *picker = static_cast(mTrackFloater.get()); + + // Show the dialog + if (!picker) + { + picker = new LLFloaterTrackPicker(this); + + mTrackFloater = picker->getHandle(); + + picker->setCommitCallback([this](LLUICtrl *, const LLSD &data){ onPickerCommitTrackId(data.asInteger()); }); + } + + picker->showPicker(args); +} + +void LLFloaterEditExtDayCycle::doCloseTrackFloater(bool quitting) +{ + LLFloater* floaterp = mTrackFloater.get(); + + if (floaterp) + { + floaterp->closeFloater(quitting); + } +} + +LLFloaterSettingsPicker * LLFloaterEditExtDayCycle::getSettingsPicker() +{ + LLFloaterSettingsPicker *picker = static_cast(mInventoryFloater.get()); + + // Show the dialog + if (!picker) + { + picker = new LLFloaterSettingsPicker(this, + LLUUID::null); + + mInventoryFloater = picker->getHandle(); + + picker->setCommitCallback([this](LLUICtrl *, const LLSD &data){ onPickerCommitSetting(data["ItemId"].asUUID(), data["Track"].asInteger()); }); + } + return picker; +} + +void LLFloaterEditExtDayCycle::onPickerCommitTrackId(U32 track_id) +{ + cloneTrack(track_id, mCurrentTrack); +} + +void LLFloaterEditExtDayCycle::doOpenInventoryFloater(LLSettingsType::type_e type, LLUUID curritem) +{ + LLFloaterSettingsPicker *picker = getSettingsPicker(); + picker->setSettingsFilter(type); + picker->setSettingsItemId(curritem); + if (type == LLSettingsType::ST_DAYCYCLE) + { + picker->setTrackMode((mCurrentTrack == LLSettingsDay::TRACK_WATER) ? LLFloaterSettingsPicker::TRACK_WATER : LLFloaterSettingsPicker::TRACK_SKY); + } + else + { + picker->setTrackMode(LLFloaterSettingsPicker::TRACK_NONE); + } + picker->openFloater(); + picker->setFocus(true); +} + +void LLFloaterEditExtDayCycle::onPickerCommitSetting(LLUUID item_id, S32 track) +{ + LLSettingsBase::TrackPosition frame(mTimeSlider->getCurSliderValue()); + LLViewerInventoryItem *itemp = gInventory.getItem(item_id); + if (itemp) + { + LLSettingsVOBase::getSettingsAsset(itemp->getAssetUUID(), + [this, track, frame, item_id](LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status, LLExtStat) { onAssetLoadedForInsertion(item_id, asset_id, settings, status, track, mCurrentTrack, frame); }); + } +} + +void LLFloaterEditExtDayCycle::showHDRNotification(const LLSettingsDay::ptr_t &pday) +{ + for (U32 i = LLSettingsDay::TRACK_GROUND_LEVEL; i <= LLSettingsDay::TRACK_MAX; i++) + { + LLSettingsDay::CycleTrack_t &day_track = pday->getCycleTrack(i); + + LLSettingsDay::CycleTrack_t::iterator iter = day_track.begin(); + LLSettingsDay::CycleTrack_t::iterator end = day_track.end(); + + while (iter != end) + { + LLSettingsSky::ptr_t sky = std::static_pointer_cast(iter->second); + if (sky + && sky->canAutoAdjust() + && sky->getReflectionProbeAmbiance(true) != 0.f) + { + LLNotificationsUtil::add("AutoAdjustHDRSky"); + return; + } + iter++; + } + } +} + +void LLFloaterEditExtDayCycle::onAssetLoadedForInsertion(LLUUID item_id, LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status, S32 source_track, S32 dest_track, LLSettingsBase::TrackPosition frame) +{ + std::function cb = [this, settings, frame, source_track, dest_track]() + { + if (settings->getSettingsType() == "daycycle") + { + // Load full track + LLSettingsDay::ptr_t pday = std::dynamic_pointer_cast(settings); + if (dest_track == LLSettingsDay::TRACK_WATER) + { + cloneTrack(pday, LLSettingsDay::TRACK_WATER, LLSettingsDay::TRACK_WATER); + } + else + { + cloneTrack(pday, source_track, dest_track); + } + } + else + { + if (!mFramesSlider->canAddSliders()) + { + LL_WARNS("ENVDAYEDIT") << "Attempt to add new frame when slider is full." << LL_ENDL; + return; + } + + // load or replace single frame + LLSettingsDay::CycleTrack_t::value_type nearest = mEditDay->getSettingsNearKeyframe(frame, dest_track, LLSettingsDay::DEFAULT_FRAME_SLOP_FACTOR); + if (nearest.first != LLSettingsDay::INVALID_TRACKPOS) + { // There is already a frame near the target location. Remove it so we can put the new one in its place. + mEditDay->removeTrackKeyframe(dest_track, nearest.first); + removeSliderFrame(nearest.first); + } + + // Don't forget to clone (we might reuse/load it couple times) + if (settings->getSettingsType() == "sky") + { + // Load sky to frame + if (dest_track != LLSettingsDay::TRACK_WATER) + { + mEditDay->setSettingsAtKeyframe(settings->buildDerivedClone(), frame, dest_track); + addSliderFrame(frame, settings, false); + } + else + { + LL_WARNS("ENVDAYEDIT") << "Trying to load day settings as sky" << LL_ENDL; + } + } + else if (settings->getSettingsType() == "water") + { + // Load water to frame + if (dest_track == LLSettingsDay::TRACK_WATER) + { + mEditDay->setSettingsAtKeyframe(settings->buildDerivedClone(), frame, dest_track); + addSliderFrame(frame, settings, false); + } + else + { + LL_WARNS("ENVDAYEDIT") << "Trying to load water settings as sky" << LL_ENDL; + } + } + } + reblendSettings(); + synchronizeTabs(); + }; + + if (!settings || status) + { + LL_WARNS("ENVDAYEDIT") << "Could not load asset " << asset_id << " into frame. status=" << status << LL_ENDL; + return; + } + + if (!mEditDay) + { + // day got reset while we were waiting for response + return; + } + + LLInventoryItem *inv_item = gInventory.getItem(item_id); + + if (inv_item + && (!inv_item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID()) + || !inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID()))) + { + // Need to check if item is already no-transfer, otherwise make it no-transfer + bool no_transfer = false; + if (mInventoryItem) + { + no_transfer = !mInventoryItem->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID()); + } + else + { + no_transfer = mEditDay->getFlag(LLSettingsBase::FLAG_NOTRANS); + } + + if (!no_transfer) + { + LLSD args; + + // create and show confirmation textbox + LLNotificationsUtil::add("SettingsMakeNoTrans", args, LLSD(), + [this, cb](const LLSD¬if, const LLSD&resp) + { + S32 opt = LLNotificationsUtil::getSelectedOption(notif, resp); + if (opt == 0) + { + mCanTrans = false; + mEditDay->setFlag(LLSettingsBase::FLAG_NOTRANS); + cb(); + } + }); + return; + } + } + + cb(); +} diff --git a/indra/newview/llfloatereditextdaycycle.h b/indra/newview/llfloatereditextdaycycle.h index 093b1290b0..655915b6e8 100644 --- a/indra/newview/llfloatereditextdaycycle.h +++ b/indra/newview/llfloatereditextdaycycle.h @@ -1,244 +1,244 @@ -/** - * @file llfloatereditextdaycycle.h - * @brief Floater to create or edit a day cycle - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATEREDITEXTDAYCYCLE_H -#define LL_LLFLOATEREDITEXTDAYCYCLE_H - -#include "llfloater.h" -#include "llsettingsdaycycle.h" -#include - -#include "llenvironment.h" -#include "llfloatereditenvironmentbase.h" - -class LLCheckBoxCtrl; -class LLComboBox; -class LLFlyoutComboBtnCtrl; -class LLLineEditor; -class LLMultiSliderCtrl; -class LLTextBox; -class LLTimeCtrl; -class LLTabContainer; - -class LLInventoryItem; -class LLDaySettingCopiedCallback; - -typedef std::shared_ptr LLSettingsBasePtr_t; - -/** - * Floater for creating or editing a day cycle. - */ -class LLFloaterEditExtDayCycle : public LLFloaterEditEnvironmentBase -{ - LOG_CLASS(LLFloaterEditExtDayCycle); - - friend class LLDaySettingCopiedCallback; - -public: - static const std::string KEY_EDIT_CONTEXT; - static const std::string KEY_DAY_LENGTH; - static const std::string KEY_CANMOD; - - static const std::string VALUE_CONTEXT_INVENTORY; - static const std::string VALUE_CONTEXT_PARCEL; - static const std::string VALUE_CONTEXT_REGION; - - enum edit_context_t { - CONTEXT_UNKNOWN, - CONTEXT_INVENTORY, - CONTEXT_PARCEL, - CONTEXT_REGION - }; - - typedef boost::signals2::signal edit_commit_signal_t; - typedef boost::signals2::connection connection_t; - - LLFloaterEditExtDayCycle(const LLSD &key); - virtual ~LLFloaterEditExtDayCycle(); - - virtual bool postBuild() override; - virtual void onOpen(const LLSD& key) override; - virtual void onClose(bool app_quitting) override; - //virtual void onFocusReceived() override; - //virtual void onFocusLost() override; - virtual void onVisibilityChange(bool new_visibility) override; - - connection_t setEditCommitSignal(edit_commit_signal_t::slot_type cb); - - virtual void refresh() override; - - void setEditDayCycle(const LLSettingsDay::ptr_t &pday); - void setEditDefaultDayCycle(); - std::string getEditName() const; - void setEditName(const std::string &name); - LLUUID getEditingAssetId() { return mEditDay ? mEditDay->getAssetId() : LLUUID::null; } - LLUUID getEditingInventoryId() { return mInventoryId; } - - virtual LLSettingsBase::ptr_t getEditSettings() const override { return mEditDay; } - - - bool handleKeyUp(KEY key, MASK mask, bool called_from_parent) override; - -protected: - virtual void setEditSettingsAndUpdate(const LLSettingsBase::ptr_t &settings) override; - -private: - typedef std::function on_confirm_fn; - F32 getCurrentFrame() const; - - // flyout response/click - void onButtonApply(LLUICtrl *ctrl, const LLSD &data); - //virtual void onClickCloseBtn(bool app_quitting = false) override; - //void onButtonImport(); - void onButtonLoadFrame(); - void onAddFrame(); - void onRemoveFrame(); - void onCloneTrack(); - void onLoadTrack(); - void onClearTrack(); - void onCommitName(class LLLineEditor* caller, void* user_data); - void onTrackSelectionCallback(const LLSD& user_data); - void onPlayActionCallback(const LLSD& user_data); - // time slider clicked - void onTimeSliderCallback(); - // a frame moved or frame selection changed - void onFrameSliderCallback(const LLSD &); - void onFrameSliderDoubleClick(S32 x, S32 y, MASK mask); - void onFrameSliderMouseDown(S32 x, S32 y, MASK mask); - void onFrameSliderMouseUp(S32 x, S32 y, MASK mask); - - void cloneTrack(U32 source_index, U32 dest_index); - void cloneTrack(const LLSettingsDay::ptr_t &source_day, U32 source_index, U32 dest_index); - void selectTrack(U32 track_index, bool force = false); - void selectFrame(F32 frame, F32 slop_factor); - void clearTabs(); - void updateTabs(); - void updateWaterTabs(const LLSettingsWaterPtr_t &p_water); - void updateSkyTabs(const LLSettingsSkyPtr_t &p_sky); - void updateButtons(); - void updateLabels(); - void updateSlider(); //generate sliders from current track - void updateTimeAndLabel(); - void addSliderFrame(F32 frame, const LLSettingsBase::ptr_t &setting, bool update_ui = true); - void removeCurrentSliderFrame(); - void removeSliderFrame(F32 frame); - - virtual void doImportFromDisk() override; - void loadSettingFromFile(const std::vector& filenames); - void doApplyCommit(LLSettingsDay::ptr_t day); - void onInventoryCreated(LLUUID asset_id, LLUUID inventory_id); - void onInventoryCreated(LLUUID asset_id, LLUUID inventory_id, LLSD results); - void onInventoryUpdated(LLUUID asset_id, LLUUID inventory_id, LLSD results); - - - void doOpenTrackFloater(const LLSD &args); - void doCloseTrackFloater(bool quitting = false); - virtual LLFloaterSettingsPicker* getSettingsPicker() override; - void onPickerCommitTrackId(U32 track_id); - - void doOpenInventoryFloater(LLSettingsType::type_e type, LLUUID curritem); - //void doCloseInventoryFloater(bool quitting = false); - void onPickerCommitSetting(LLUUID item_id, S32 track); - void onAssetLoadedForInsertion(LLUUID item_id, - LLUUID asset_id, - LLSettingsBase::ptr_t settings, - S32 status, - S32 source_track, - S32 dest_track, - LLSettingsBase::TrackPosition dest_frame); - - virtual void updateEditEnvironment() override; - void synchronizeTabs(); - void reblendSettings(); - - void setTabsData(LLTabContainer * tabcontainer, const LLSettingsBase::ptr_t &settings, bool editable); - - // play functions - void startPlay(); - void stopPlay(); - static void onIdlePlay(void *); - - bool getIsDirty() const { return mIsDirty; } - void setDirtyFlag() { mIsDirty = true; } - virtual void clearDirtyFlag() override; - - bool isRemovingFrameAllowed(); - bool isAddingFrameAllowed(); - - void showHDRNotification(const LLSettingsDay::ptr_t &pday); - - LLSettingsDay::ptr_t mEditDay; // edited copy - LLSettingsDay::Seconds mDayLength; - U32 mCurrentTrack; - std::string mLastFrameSlider; - bool mShiftCopyEnabled; - - LLButton* mAddFrameButton; - LLButton* mDeleteFrameButton; - LLButton* mImportButton; - LLButton* mLoadFrame; - LLButton * mCloneTrack; - LLButton * mLoadTrack; - LLButton * mClearTrack; - LLMultiSliderCtrl* mTimeSlider; - LLMultiSliderCtrl* mFramesSlider; - LLView* mSkyTabLayoutContainer; - LLView* mWaterTabLayoutContainer; - LLTextBox* mCurrentTimeLabel; - LLFlyoutComboBtnCtrl * mFlyoutControl; - - LLHandle mTrackFloater; - - LLTrackBlenderLoopingManual::ptr_t mSkyBlender; - LLTrackBlenderLoopingManual::ptr_t mWaterBlender; - LLSettingsSky::ptr_t mScratchSky; - LLSettingsWater::ptr_t mScratchWater; - LLSettingsBase::ptr_t mCurrentEdit; - LLSettingsSky::ptr_t mEditSky; - LLSettingsWater::ptr_t mEditWater; - - LLFrameTimer mPlayTimer; - F32 mPlayStartFrame; // an env frame - bool mIsPlaying; - - edit_commit_signal_t mCommitSignal; - - edit_context_t mEditContext; - - // For map of sliders to parameters - class FrameData - { - public: - FrameData() : mFrame(0) {}; - FrameData(F32 frame, LLSettingsBase::ptr_t settings) : mFrame(frame), pSettings(settings) {}; - F32 mFrame; - LLSettingsBase::ptr_t pSettings; - }; - typedef std::map keymap_t; - keymap_t mSliderKeyMap; //slider's keys vs old_frames&settings, shadows mFramesSlider -}; - -#endif // LL_LLFloaterEditExtDayCycle_H +/** + * @file llfloatereditextdaycycle.h + * @brief Floater to create or edit a day cycle + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATEREDITEXTDAYCYCLE_H +#define LL_LLFLOATEREDITEXTDAYCYCLE_H + +#include "llfloater.h" +#include "llsettingsdaycycle.h" +#include + +#include "llenvironment.h" +#include "llfloatereditenvironmentbase.h" + +class LLCheckBoxCtrl; +class LLComboBox; +class LLFlyoutComboBtnCtrl; +class LLLineEditor; +class LLMultiSliderCtrl; +class LLTextBox; +class LLTimeCtrl; +class LLTabContainer; + +class LLInventoryItem; +class LLDaySettingCopiedCallback; + +typedef std::shared_ptr LLSettingsBasePtr_t; + +/** + * Floater for creating or editing a day cycle. + */ +class LLFloaterEditExtDayCycle : public LLFloaterEditEnvironmentBase +{ + LOG_CLASS(LLFloaterEditExtDayCycle); + + friend class LLDaySettingCopiedCallback; + +public: + static const std::string KEY_EDIT_CONTEXT; + static const std::string KEY_DAY_LENGTH; + static const std::string KEY_CANMOD; + + static const std::string VALUE_CONTEXT_INVENTORY; + static const std::string VALUE_CONTEXT_PARCEL; + static const std::string VALUE_CONTEXT_REGION; + + enum edit_context_t { + CONTEXT_UNKNOWN, + CONTEXT_INVENTORY, + CONTEXT_PARCEL, + CONTEXT_REGION + }; + + typedef boost::signals2::signal edit_commit_signal_t; + typedef boost::signals2::connection connection_t; + + LLFloaterEditExtDayCycle(const LLSD &key); + virtual ~LLFloaterEditExtDayCycle(); + + virtual bool postBuild() override; + virtual void onOpen(const LLSD& key) override; + virtual void onClose(bool app_quitting) override; + //virtual void onFocusReceived() override; + //virtual void onFocusLost() override; + virtual void onVisibilityChange(bool new_visibility) override; + + connection_t setEditCommitSignal(edit_commit_signal_t::slot_type cb); + + virtual void refresh() override; + + void setEditDayCycle(const LLSettingsDay::ptr_t &pday); + void setEditDefaultDayCycle(); + std::string getEditName() const; + void setEditName(const std::string &name); + LLUUID getEditingAssetId() { return mEditDay ? mEditDay->getAssetId() : LLUUID::null; } + LLUUID getEditingInventoryId() { return mInventoryId; } + + virtual LLSettingsBase::ptr_t getEditSettings() const override { return mEditDay; } + + + bool handleKeyUp(KEY key, MASK mask, bool called_from_parent) override; + +protected: + virtual void setEditSettingsAndUpdate(const LLSettingsBase::ptr_t &settings) override; + +private: + typedef std::function on_confirm_fn; + F32 getCurrentFrame() const; + + // flyout response/click + void onButtonApply(LLUICtrl *ctrl, const LLSD &data); + //virtual void onClickCloseBtn(bool app_quitting = false) override; + //void onButtonImport(); + void onButtonLoadFrame(); + void onAddFrame(); + void onRemoveFrame(); + void onCloneTrack(); + void onLoadTrack(); + void onClearTrack(); + void onCommitName(class LLLineEditor* caller, void* user_data); + void onTrackSelectionCallback(const LLSD& user_data); + void onPlayActionCallback(const LLSD& user_data); + // time slider clicked + void onTimeSliderCallback(); + // a frame moved or frame selection changed + void onFrameSliderCallback(const LLSD &); + void onFrameSliderDoubleClick(S32 x, S32 y, MASK mask); + void onFrameSliderMouseDown(S32 x, S32 y, MASK mask); + void onFrameSliderMouseUp(S32 x, S32 y, MASK mask); + + void cloneTrack(U32 source_index, U32 dest_index); + void cloneTrack(const LLSettingsDay::ptr_t &source_day, U32 source_index, U32 dest_index); + void selectTrack(U32 track_index, bool force = false); + void selectFrame(F32 frame, F32 slop_factor); + void clearTabs(); + void updateTabs(); + void updateWaterTabs(const LLSettingsWaterPtr_t &p_water); + void updateSkyTabs(const LLSettingsSkyPtr_t &p_sky); + void updateButtons(); + void updateLabels(); + void updateSlider(); //generate sliders from current track + void updateTimeAndLabel(); + void addSliderFrame(F32 frame, const LLSettingsBase::ptr_t &setting, bool update_ui = true); + void removeCurrentSliderFrame(); + void removeSliderFrame(F32 frame); + + virtual void doImportFromDisk() override; + void loadSettingFromFile(const std::vector& filenames); + void doApplyCommit(LLSettingsDay::ptr_t day); + void onInventoryCreated(LLUUID asset_id, LLUUID inventory_id); + void onInventoryCreated(LLUUID asset_id, LLUUID inventory_id, LLSD results); + void onInventoryUpdated(LLUUID asset_id, LLUUID inventory_id, LLSD results); + + + void doOpenTrackFloater(const LLSD &args); + void doCloseTrackFloater(bool quitting = false); + virtual LLFloaterSettingsPicker* getSettingsPicker() override; + void onPickerCommitTrackId(U32 track_id); + + void doOpenInventoryFloater(LLSettingsType::type_e type, LLUUID curritem); + //void doCloseInventoryFloater(bool quitting = false); + void onPickerCommitSetting(LLUUID item_id, S32 track); + void onAssetLoadedForInsertion(LLUUID item_id, + LLUUID asset_id, + LLSettingsBase::ptr_t settings, + S32 status, + S32 source_track, + S32 dest_track, + LLSettingsBase::TrackPosition dest_frame); + + virtual void updateEditEnvironment() override; + void synchronizeTabs(); + void reblendSettings(); + + void setTabsData(LLTabContainer * tabcontainer, const LLSettingsBase::ptr_t &settings, bool editable); + + // play functions + void startPlay(); + void stopPlay(); + static void onIdlePlay(void *); + + bool getIsDirty() const { return mIsDirty; } + void setDirtyFlag() { mIsDirty = true; } + virtual void clearDirtyFlag() override; + + bool isRemovingFrameAllowed(); + bool isAddingFrameAllowed(); + + void showHDRNotification(const LLSettingsDay::ptr_t &pday); + + LLSettingsDay::ptr_t mEditDay; // edited copy + LLSettingsDay::Seconds mDayLength; + U32 mCurrentTrack; + std::string mLastFrameSlider; + bool mShiftCopyEnabled; + + LLButton* mAddFrameButton; + LLButton* mDeleteFrameButton; + LLButton* mImportButton; + LLButton* mLoadFrame; + LLButton * mCloneTrack; + LLButton * mLoadTrack; + LLButton * mClearTrack; + LLMultiSliderCtrl* mTimeSlider; + LLMultiSliderCtrl* mFramesSlider; + LLView* mSkyTabLayoutContainer; + LLView* mWaterTabLayoutContainer; + LLTextBox* mCurrentTimeLabel; + LLFlyoutComboBtnCtrl * mFlyoutControl; + + LLHandle mTrackFloater; + + LLTrackBlenderLoopingManual::ptr_t mSkyBlender; + LLTrackBlenderLoopingManual::ptr_t mWaterBlender; + LLSettingsSky::ptr_t mScratchSky; + LLSettingsWater::ptr_t mScratchWater; + LLSettingsBase::ptr_t mCurrentEdit; + LLSettingsSky::ptr_t mEditSky; + LLSettingsWater::ptr_t mEditWater; + + LLFrameTimer mPlayTimer; + F32 mPlayStartFrame; // an env frame + bool mIsPlaying; + + edit_commit_signal_t mCommitSignal; + + edit_context_t mEditContext; + + // For map of sliders to parameters + class FrameData + { + public: + FrameData() : mFrame(0) {}; + FrameData(F32 frame, LLSettingsBase::ptr_t settings) : mFrame(frame), pSettings(settings) {}; + F32 mFrame; + LLSettingsBase::ptr_t pSettings; + }; + typedef std::map keymap_t; + keymap_t mSliderKeyMap; //slider's keys vs old_frames&settings, shadows mFramesSlider +}; + +#endif // LL_LLFloaterEditExtDayCycle_H diff --git a/indra/newview/llfloateremojipicker.h b/indra/newview/llfloateremojipicker.h index 31d5b0085a..669683eb9e 100644 --- a/indra/newview/llfloateremojipicker.h +++ b/indra/newview/llfloateremojipicker.h @@ -1,123 +1,123 @@ -/** - * @file llfloateremojipicker.h - * @brief Header file for llfloateremojipicker - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLFLOATEREMOJIPICKER_H -#define LLFLOATEREMOJIPICKER_H - -#include "llfloater.h" - -class LLEmojiGridRow; -class LLEmojiGridIcon; -struct LLEmojiDescriptor; -struct LLEmojiSearchResult; - -class LLFloaterEmojiPicker : public LLFloater -{ - using super = LLFloater; - -public: - // The callback function will be called with an emoji char. - typedef boost::function pick_callback_t; - typedef boost::function close_callback_t; - - LLFloaterEmojiPicker(const LLSD& key); - - virtual bool postBuild() override; - virtual void dirtyRect() override; - virtual void goneFromFront() override; - - void hideFloater() const; - - static std::list& getRecentlyUsed(); - static void onEmojiUsed(llwchar emoji); - - static void loadState(); - static void saveState(); - -private: - void initialize(); - void fillGroups(); - void fillCategoryRecentlyUsed(std::map>& cats); - void fillCategoryFrequentlyUsed(std::map>& cats); - void fillGroupEmojis(std::map>& cats, U32 index); - void createGroupButton(LLButton::Params& params, const LLRect& rect, llwchar emoji); - void resizeGroupButtons(); - void selectEmojiGroup(U32 index); - void fillEmojis(bool fromResize = false); - void fillEmojisCategory(const std::vector& emojis, - const std::string& category, const LLPanel::Params& row_panel_params, const LLUICtrl::Params& row_list_params, - const LLPanel::Params& icon_params, const LLRect& icon_rect, S32 max_icons, const LLColor4& bg); - void createEmojiIcon(const LLEmojiSearchResult& emoji, - const std::string& category, const LLPanel::Params& row_panel_params, const LLUICtrl::Params& row_list_params, - const LLPanel::Params& icon_params, const LLRect& icon_rect, S32 max_icons, const LLColor4& bg, - LLEmojiGridRow*& row, int& icon_index); - void showPreview(bool show); - - void onGroupButtonClick(LLUICtrl* ctrl); - void onGroupButtonMouseEnter(LLUICtrl* ctrl); - void onGroupButtonMouseLeave(LLUICtrl* ctrl); - void onEmojiMouseEnter(LLUICtrl* ctrl); - void onEmojiMouseLeave(LLUICtrl* ctrl); - void onEmojiMouseDown(LLUICtrl* ctrl); - void onEmojiMouseUp(LLUICtrl* ctrl); - - void selectFocusedIcon(); - bool moveFocusedIconUp(); - bool moveFocusedIconDown(); - bool moveFocusedIconPrev(); - bool moveFocusedIconNext(); - - void selectGridIcon(LLEmojiGridIcon* icon); - void unselectGridIcon(LLEmojiGridIcon* icon); - - void onOpen(const LLSD& key) override; - void onClose(bool app_quitting) override; - virtual bool handleKey(KEY key, MASK mask, bool called_from_parent) override; - - class LLPanel* mGroups { nullptr }; - class LLPanel* mBadge { nullptr }; - class LLScrollContainer* mEmojiScroll { nullptr }; - class LLScrollingPanelList* mEmojiGrid { nullptr }; - class LLEmojiPreviewPanel* mPreview { nullptr }; - class LLTextBox* mDummy { nullptr }; - - std::vector mFilteredEmojiGroups; - std::vector>> mFilteredEmojis; - std::vector mGroupButtons; - - std::string mHint; - std::string mFilterPattern; - U32 mSelectedGroupIndex { 0 }; - S32 mRecentMaxIcons { 0 }; - S32 mFocusedIconRow { 0 }; - S32 mFocusedIconCol { 0 }; - LLEmojiGridIcon* mFocusedIcon { nullptr }; - LLEmojiGridIcon* mHoveredIcon { nullptr }; - - U64 mRecentReturnPressedMs { 0 }; -}; - -#endif +/** + * @file llfloateremojipicker.h + * @brief Header file for llfloateremojipicker + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLFLOATEREMOJIPICKER_H +#define LLFLOATEREMOJIPICKER_H + +#include "llfloater.h" + +class LLEmojiGridRow; +class LLEmojiGridIcon; +struct LLEmojiDescriptor; +struct LLEmojiSearchResult; + +class LLFloaterEmojiPicker : public LLFloater +{ + using super = LLFloater; + +public: + // The callback function will be called with an emoji char. + typedef boost::function pick_callback_t; + typedef boost::function close_callback_t; + + LLFloaterEmojiPicker(const LLSD& key); + + virtual bool postBuild() override; + virtual void dirtyRect() override; + virtual void goneFromFront() override; + + void hideFloater() const; + + static std::list& getRecentlyUsed(); + static void onEmojiUsed(llwchar emoji); + + static void loadState(); + static void saveState(); + +private: + void initialize(); + void fillGroups(); + void fillCategoryRecentlyUsed(std::map>& cats); + void fillCategoryFrequentlyUsed(std::map>& cats); + void fillGroupEmojis(std::map>& cats, U32 index); + void createGroupButton(LLButton::Params& params, const LLRect& rect, llwchar emoji); + void resizeGroupButtons(); + void selectEmojiGroup(U32 index); + void fillEmojis(bool fromResize = false); + void fillEmojisCategory(const std::vector& emojis, + const std::string& category, const LLPanel::Params& row_panel_params, const LLUICtrl::Params& row_list_params, + const LLPanel::Params& icon_params, const LLRect& icon_rect, S32 max_icons, const LLColor4& bg); + void createEmojiIcon(const LLEmojiSearchResult& emoji, + const std::string& category, const LLPanel::Params& row_panel_params, const LLUICtrl::Params& row_list_params, + const LLPanel::Params& icon_params, const LLRect& icon_rect, S32 max_icons, const LLColor4& bg, + LLEmojiGridRow*& row, int& icon_index); + void showPreview(bool show); + + void onGroupButtonClick(LLUICtrl* ctrl); + void onGroupButtonMouseEnter(LLUICtrl* ctrl); + void onGroupButtonMouseLeave(LLUICtrl* ctrl); + void onEmojiMouseEnter(LLUICtrl* ctrl); + void onEmojiMouseLeave(LLUICtrl* ctrl); + void onEmojiMouseDown(LLUICtrl* ctrl); + void onEmojiMouseUp(LLUICtrl* ctrl); + + void selectFocusedIcon(); + bool moveFocusedIconUp(); + bool moveFocusedIconDown(); + bool moveFocusedIconPrev(); + bool moveFocusedIconNext(); + + void selectGridIcon(LLEmojiGridIcon* icon); + void unselectGridIcon(LLEmojiGridIcon* icon); + + void onOpen(const LLSD& key) override; + void onClose(bool app_quitting) override; + virtual bool handleKey(KEY key, MASK mask, bool called_from_parent) override; + + class LLPanel* mGroups { nullptr }; + class LLPanel* mBadge { nullptr }; + class LLScrollContainer* mEmojiScroll { nullptr }; + class LLScrollingPanelList* mEmojiGrid { nullptr }; + class LLEmojiPreviewPanel* mPreview { nullptr }; + class LLTextBox* mDummy { nullptr }; + + std::vector mFilteredEmojiGroups; + std::vector>> mFilteredEmojis; + std::vector mGroupButtons; + + std::string mHint; + std::string mFilterPattern; + U32 mSelectedGroupIndex { 0 }; + S32 mRecentMaxIcons { 0 }; + S32 mFocusedIconRow { 0 }; + S32 mFocusedIconCol { 0 }; + LLEmojiGridIcon* mFocusedIcon { nullptr }; + LLEmojiGridIcon* mHoveredIcon { nullptr }; + + U64 mRecentReturnPressedMs { 0 }; +}; + +#endif diff --git a/indra/newview/llfloaterevent.cpp b/indra/newview/llfloaterevent.cpp index f84432b412..85d3b27f22 100644 --- a/indra/newview/llfloaterevent.cpp +++ b/indra/newview/llfloaterevent.cpp @@ -1,119 +1,119 @@ -/** - * @file llfloaterevent.cpp - * @brief Display for events in the finder - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterevent.h" - -#include "message.h" -#include "llnotificationsutil.h" -#include "llui.h" - -#include "llagent.h" -#include "llviewerwindow.h" -#include "llbutton.h" -#include "llcachename.h" -#include "llcommandhandler.h" // secondlife:///app/chat/ support -#include "lleventflags.h" -#include "llmediactrl.h" -#include "llexpandabletextbox.h" -#include "llfloater.h" -#include "llfloaterreg.h" -#include "llmediactrl.h" -#include "llfloaterworldmap.h" -#include "llinventorymodel.h" -#include "llslurl.h" -#include "lltextbox.h" -#include "lltexteditor.h" -#include "lluiconstants.h" -#include "llviewercontrol.h" -#include "llweb.h" -#include "llworldmap.h" -#include "llworldmapmessage.h" -#include "lluictrlfactory.h" -#include "lltrans.h" - - -LLFloaterEvent::LLFloaterEvent(const LLSD& key) - : LLFloater(key), - LLViewerMediaObserver(), - mBrowser(NULL), - mEventID(0) -{ -} - - -LLFloaterEvent::~LLFloaterEvent() -{ -} - - -bool LLFloaterEvent::postBuild() -{ - mBrowser = getChild("browser"); - if (mBrowser) - { - mBrowser->addObserver(this); - } - - return true; -} - -void LLFloaterEvent::handleMediaEvent(LLPluginClassMedia *self, EMediaEvent event) -{ - switch (event) - { - case MEDIA_EVENT_NAVIGATE_BEGIN: - getChild("status_text")->setValue(getString("loading_text")); - break; - - case MEDIA_EVENT_NAVIGATE_COMPLETE: - getChild("status_text")->setValue(getString("done_text")); - break; - - default: - break; - } -} - -void LLFloaterEvent::setEventID(const U32 event_id) -{ - mEventID = event_id; - - if (event_id != 0) - { - LLSD subs; - subs["EVENT_ID"] = (S32)event_id; - // get the search URL and expand all of the substitutions - // (also adds things like [LANGUAGE], [VERSION], [OS], etc.) - - std::string expanded_url = LLWeb::expandURLSubstitutions(gSavedSettings.getString("EventURL"), subs); - - // and load the URL in the web view - mBrowser->navigateTo(expanded_url); - - } -} +/** + * @file llfloaterevent.cpp + * @brief Display for events in the finder + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterevent.h" + +#include "message.h" +#include "llnotificationsutil.h" +#include "llui.h" + +#include "llagent.h" +#include "llviewerwindow.h" +#include "llbutton.h" +#include "llcachename.h" +#include "llcommandhandler.h" // secondlife:///app/chat/ support +#include "lleventflags.h" +#include "llmediactrl.h" +#include "llexpandabletextbox.h" +#include "llfloater.h" +#include "llfloaterreg.h" +#include "llmediactrl.h" +#include "llfloaterworldmap.h" +#include "llinventorymodel.h" +#include "llslurl.h" +#include "lltextbox.h" +#include "lltexteditor.h" +#include "lluiconstants.h" +#include "llviewercontrol.h" +#include "llweb.h" +#include "llworldmap.h" +#include "llworldmapmessage.h" +#include "lluictrlfactory.h" +#include "lltrans.h" + + +LLFloaterEvent::LLFloaterEvent(const LLSD& key) + : LLFloater(key), + LLViewerMediaObserver(), + mBrowser(NULL), + mEventID(0) +{ +} + + +LLFloaterEvent::~LLFloaterEvent() +{ +} + + +bool LLFloaterEvent::postBuild() +{ + mBrowser = getChild("browser"); + if (mBrowser) + { + mBrowser->addObserver(this); + } + + return true; +} + +void LLFloaterEvent::handleMediaEvent(LLPluginClassMedia *self, EMediaEvent event) +{ + switch (event) + { + case MEDIA_EVENT_NAVIGATE_BEGIN: + getChild("status_text")->setValue(getString("loading_text")); + break; + + case MEDIA_EVENT_NAVIGATE_COMPLETE: + getChild("status_text")->setValue(getString("done_text")); + break; + + default: + break; + } +} + +void LLFloaterEvent::setEventID(const U32 event_id) +{ + mEventID = event_id; + + if (event_id != 0) + { + LLSD subs; + subs["EVENT_ID"] = (S32)event_id; + // get the search URL and expand all of the substitutions + // (also adds things like [LANGUAGE], [VERSION], [OS], etc.) + + std::string expanded_url = LLWeb::expandURLSubstitutions(gSavedSettings.getString("EventURL"), subs); + + // and load the URL in the web view + mBrowser->navigateTo(expanded_url); + + } +} diff --git a/indra/newview/llfloaterevent.h b/indra/newview/llfloaterevent.h index 11c0c8504c..3854885a5f 100644 --- a/indra/newview/llfloaterevent.h +++ b/indra/newview/llfloaterevent.h @@ -1,62 +1,62 @@ -/** - * @file llfloaterevent.h - * @brief Display for events in the finder - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATEREVENT_H -#define LL_LLFLOATEREVENT_H - -#include "llfloater.h" -#include "llviewermediaobserver.h" - - -class LLMediaCtrl; -class LLButton; - -class LLFloaterEvent : public LLFloater, - public LLViewerMediaObserver - -{ -public: - LLFloaterEvent(const LLSD& key); - /*virtual*/ ~LLFloaterEvent(); - - bool postBuild() override; - - void setEventID(const U32 event_id); - - U32 getEventID() { return mEventID; } - - - -protected: - void handleMediaEvent(LLPluginClassMedia *self, EMediaEvent event) override; - - U32 mEventID; - - LLMediaCtrl* mBrowser; - -}; - -#endif // LL_LLFLOATEREVENT_H +/** + * @file llfloaterevent.h + * @brief Display for events in the finder + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATEREVENT_H +#define LL_LLFLOATEREVENT_H + +#include "llfloater.h" +#include "llviewermediaobserver.h" + + +class LLMediaCtrl; +class LLButton; + +class LLFloaterEvent : public LLFloater, + public LLViewerMediaObserver + +{ +public: + LLFloaterEvent(const LLSD& key); + /*virtual*/ ~LLFloaterEvent(); + + bool postBuild() override; + + void setEventID(const U32 event_id); + + U32 getEventID() { return mEventID; } + + + +protected: + void handleMediaEvent(LLPluginClassMedia *self, EMediaEvent event) override; + + U32 mEventID; + + LLMediaCtrl* mBrowser; + +}; + +#endif // LL_LLFLOATEREVENT_H diff --git a/indra/newview/llfloaterexperiencepicker.cpp b/indra/newview/llfloaterexperiencepicker.cpp index 11268360ee..721ffd30a0 100644 --- a/indra/newview/llfloaterexperiencepicker.cpp +++ b/indra/newview/llfloaterexperiencepicker.cpp @@ -1,108 +1,108 @@ -/** -* @file llfloaterexperiencepicker.cpp -* @brief Implementation of llfloaterexperiencepicker -* @author dolphin@lindenlab.com -* -* $LicenseInfo:firstyear=2014&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2014, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterexperiencepicker.h" - - -#include "lllineeditor.h" -#include "llfloaterreg.h" -#include "llscrolllistctrl.h" -#include "llviewerregion.h" -#include "llagent.h" -#include "llexperiencecache.h" -#include "llslurl.h" -#include "llavatarnamecache.h" -#include "llfloaterexperienceprofile.h" -#include "llcombobox.h" -#include "llviewercontrol.h" -#include "lldraghandle.h" -#include "llpanelexperiencepicker.h" - -LLFloaterExperiencePicker* LLFloaterExperiencePicker::show( select_callback_t callback, const LLUUID& key, bool allow_multiple, bool close_on_select, filter_list filters, LLView * frustumOrigin ) -{ - LLFloaterExperiencePicker* floater = - LLFloaterReg::showTypedInstance("experience_search", key); - if (!floater) - { - LL_WARNS() << "Cannot instantiate experience picker" << LL_ENDL; - return NULL; - } - - if (floater->mSearchPanel) - { - floater->mSearchPanel->mSelectionCallback = callback; - floater->mSearchPanel->mCloseOnSelect = close_on_select; - floater->mSearchPanel->setAllowMultiple(allow_multiple); - floater->mSearchPanel->setDefaultFilters(); - floater->mSearchPanel->addFilters(filters.begin(), filters.end()); - floater->mSearchPanel->filterContent(); - } - - if(frustumOrigin) - { - floater->mFrustumOrigin = frustumOrigin->getHandle(); - } - - return floater; -} - -void LLFloaterExperiencePicker::drawFrustum() -{ - static LLCachedControl max_opacity(gSavedSettings, "PickerContextOpacity", 0.4f); - drawConeToOwner(mContextConeOpacity, max_opacity, mFrustumOrigin.get(), mContextConeFadeTime, mContextConeInAlpha, mContextConeOutAlpha); -} - -void LLFloaterExperiencePicker::draw() -{ - drawFrustum(); - LLFloater::draw(); -} - -LLFloaterExperiencePicker::LLFloaterExperiencePicker( const LLSD& key ) - :LLFloater(key) - ,mSearchPanel(NULL) - ,mContextConeOpacity(0.f) - ,mContextConeInAlpha(CONTEXT_CONE_IN_ALPHA) - ,mContextConeOutAlpha(CONTEXT_CONE_OUT_ALPHA) - ,mContextConeFadeTime(CONTEXT_CONE_FADE_TIME) -{ -} - -LLFloaterExperiencePicker::~LLFloaterExperiencePicker() -{ - gFocusMgr.releaseFocusIfNeeded( this ); -} - -bool LLFloaterExperiencePicker::postBuild() -{ - mSearchPanel = new LLPanelExperiencePicker(); - addChild(mSearchPanel); - mSearchPanel->setOrigin(0, 0); - return LLFloater::postBuild(); -} +/** +* @file llfloaterexperiencepicker.cpp +* @brief Implementation of llfloaterexperiencepicker +* @author dolphin@lindenlab.com +* +* $LicenseInfo:firstyear=2014&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2014, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterexperiencepicker.h" + + +#include "lllineeditor.h" +#include "llfloaterreg.h" +#include "llscrolllistctrl.h" +#include "llviewerregion.h" +#include "llagent.h" +#include "llexperiencecache.h" +#include "llslurl.h" +#include "llavatarnamecache.h" +#include "llfloaterexperienceprofile.h" +#include "llcombobox.h" +#include "llviewercontrol.h" +#include "lldraghandle.h" +#include "llpanelexperiencepicker.h" + +LLFloaterExperiencePicker* LLFloaterExperiencePicker::show( select_callback_t callback, const LLUUID& key, bool allow_multiple, bool close_on_select, filter_list filters, LLView * frustumOrigin ) +{ + LLFloaterExperiencePicker* floater = + LLFloaterReg::showTypedInstance("experience_search", key); + if (!floater) + { + LL_WARNS() << "Cannot instantiate experience picker" << LL_ENDL; + return NULL; + } + + if (floater->mSearchPanel) + { + floater->mSearchPanel->mSelectionCallback = callback; + floater->mSearchPanel->mCloseOnSelect = close_on_select; + floater->mSearchPanel->setAllowMultiple(allow_multiple); + floater->mSearchPanel->setDefaultFilters(); + floater->mSearchPanel->addFilters(filters.begin(), filters.end()); + floater->mSearchPanel->filterContent(); + } + + if(frustumOrigin) + { + floater->mFrustumOrigin = frustumOrigin->getHandle(); + } + + return floater; +} + +void LLFloaterExperiencePicker::drawFrustum() +{ + static LLCachedControl max_opacity(gSavedSettings, "PickerContextOpacity", 0.4f); + drawConeToOwner(mContextConeOpacity, max_opacity, mFrustumOrigin.get(), mContextConeFadeTime, mContextConeInAlpha, mContextConeOutAlpha); +} + +void LLFloaterExperiencePicker::draw() +{ + drawFrustum(); + LLFloater::draw(); +} + +LLFloaterExperiencePicker::LLFloaterExperiencePicker( const LLSD& key ) + :LLFloater(key) + ,mSearchPanel(NULL) + ,mContextConeOpacity(0.f) + ,mContextConeInAlpha(CONTEXT_CONE_IN_ALPHA) + ,mContextConeOutAlpha(CONTEXT_CONE_OUT_ALPHA) + ,mContextConeFadeTime(CONTEXT_CONE_FADE_TIME) +{ +} + +LLFloaterExperiencePicker::~LLFloaterExperiencePicker() +{ + gFocusMgr.releaseFocusIfNeeded( this ); +} + +bool LLFloaterExperiencePicker::postBuild() +{ + mSearchPanel = new LLPanelExperiencePicker(); + addChild(mSearchPanel); + mSearchPanel->setOrigin(0, 0); + return LLFloater::postBuild(); +} diff --git a/indra/newview/llfloaterexperiencepicker.h b/indra/newview/llfloaterexperiencepicker.h index 6ff70ff998..0a001478f1 100644 --- a/indra/newview/llfloaterexperiencepicker.h +++ b/indra/newview/llfloaterexperiencepicker.h @@ -1,67 +1,67 @@ -/** -* @file llfloaterexperiencepicker.h -* @brief Header file for llfloaterexperiencepicker -* @author dolphin@lindenlab.com -* -* $LicenseInfo:firstyear=2014&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2014, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ -#ifndef LL_LLFLOATEREXPERIENCEPICKER_H -#define LL_LLFLOATEREXPERIENCEPICKER_H - -#include "llfloater.h" - -class LLScrollListCtrl; -class LLLineEditor; -class LLPanelExperiencePicker; - - -class LLFloaterExperiencePicker : public LLFloater -{ -public: - - typedef boost::function select_callback_t; - // filter function for experiences, return true if the experience should be hidden. - typedef boost::function filter_function; - typedef std::vector filter_list; - - static LLFloaterExperiencePicker* show( select_callback_t callback, const LLUUID& key, bool allow_multiple, bool close_on_select, filter_list filters, LLView * frustumOrigin); - - LLFloaterExperiencePicker(const LLSD& key); - virtual ~LLFloaterExperiencePicker(); - - bool postBuild() override; - - void draw() override; -private: - - LLPanelExperiencePicker* mSearchPanel; - - void drawFrustum(); - LLHandle mFrustumOrigin; - F32 mContextConeOpacity; - F32 mContextConeInAlpha; - F32 mContextConeOutAlpha; - F32 mContextConeFadeTime; -}; - -#endif // LL_LLFLOATEREXPERIENCEPICKER_H - +/** +* @file llfloaterexperiencepicker.h +* @brief Header file for llfloaterexperiencepicker +* @author dolphin@lindenlab.com +* +* $LicenseInfo:firstyear=2014&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2014, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LL_LLFLOATEREXPERIENCEPICKER_H +#define LL_LLFLOATEREXPERIENCEPICKER_H + +#include "llfloater.h" + +class LLScrollListCtrl; +class LLLineEditor; +class LLPanelExperiencePicker; + + +class LLFloaterExperiencePicker : public LLFloater +{ +public: + + typedef boost::function select_callback_t; + // filter function for experiences, return true if the experience should be hidden. + typedef boost::function filter_function; + typedef std::vector filter_list; + + static LLFloaterExperiencePicker* show( select_callback_t callback, const LLUUID& key, bool allow_multiple, bool close_on_select, filter_list filters, LLView * frustumOrigin); + + LLFloaterExperiencePicker(const LLSD& key); + virtual ~LLFloaterExperiencePicker(); + + bool postBuild() override; + + void draw() override; +private: + + LLPanelExperiencePicker* mSearchPanel; + + void drawFrustum(); + LLHandle mFrustumOrigin; + F32 mContextConeOpacity; + F32 mContextConeInAlpha; + F32 mContextConeOutAlpha; + F32 mContextConeFadeTime; +}; + +#endif // LL_LLFLOATEREXPERIENCEPICKER_H + diff --git a/indra/newview/llfloaterexperienceprofile.cpp b/indra/newview/llfloaterexperienceprofile.cpp index f177764efb..eb4976fc9e 100644 --- a/indra/newview/llfloaterexperienceprofile.cpp +++ b/indra/newview/llfloaterexperienceprofile.cpp @@ -1,934 +1,934 @@ -/** - * @file llfloaterexperienceprofile.cpp - * @brief llfloaterexperienceprofile and related class definitions - * - * $LicenseInfo:firstyear=2013&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2013, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#include "llviewerprecompiledheaders.h" -#include "llfloaterexperienceprofile.h" - -#include "llagent.h" -#include "llappviewer.h" -#include "llcheckboxctrl.h" -#include "llcombobox.h" -#include "llcommandhandler.h" -#include "llexpandabletextbox.h" -#include "llexperiencecache.h" -#include "llfloaterreg.h" -#include "lllayoutstack.h" -#include "lllineeditor.h" -#include "llnotificationsutil.h" -#include "llsdserialize.h" -#include "llslurl.h" -#include "lltabcontainer.h" -#include "lltextbox.h" -#include "lltexturectrl.h" -#include "lltrans.h" -#include "llviewerregion.h" -#include "llevents.h" -#include "llfloatergroups.h" -#include "llnotifications.h" -#include "llfloaterreporter.h" - -#define XML_PANEL_EXPERIENCE_PROFILE "floater_experienceprofile.xml" -#define TF_NAME "experience_title" -#define TF_DESC "experience_description" -#define TF_SLURL "LocationTextText" -#define TF_MRKT "marketplace" -#define TF_MATURITY "ContentRatingText" -#define TF_OWNER "OwnerText" -#define TF_GROUP "GroupText" -#define TF_GRID_WIDE "grid_wide" -#define TF_PRIVILEGED "privileged" -#define EDIT "edit_" - -#define IMG_LOGO "logo" - -#define PNL_TOP "top panel" -#define PNL_IMAGE "image_panel" -#define PNL_DESC "description panel" -#define PNL_LOC "location panel" -#define PNL_MRKT "marketplace panel" -#define PNL_GROUP "group_panel" -#define PNL_PERMS "perm panel" - -#define BTN_ALLOW "allow_btn" -#define BTN_BLOCK "block_btn" -#define BTN_CANCEL "cancel_btn" -#define BTN_CLEAR_LOCATION "clear_btn" -#define BTN_EDIT "edit_btn" -#define BTN_ENABLE "enable_btn" -#define BTN_FORGET "forget_btn" -#define BTN_PRIVATE "private_btn" -#define BTN_REPORT "report_btn" -#define BTN_SAVE "save_btn" -#define BTN_SET_GROUP "Group_btn" -#define BTN_SET_LOCATION "location_btn" - - -class LLExperienceHandler : public LLCommandHandler -{ -public: - LLExperienceHandler() : LLCommandHandler("experience", UNTRUSTED_THROTTLE) { } - - bool handle(const LLSD& params, - const LLSD& query_map, - const std::string& grid, - LLMediaCtrl* web) - { - if(params.size() != 2 || params[1].asString() != "profile") - return false; - - LLExperienceCache::instance().get(params[0].asUUID(), boost::bind(&LLExperienceHandler::experienceCallback, this, _1)); - return true; - } - - void experienceCallback(const LLSD& experienceDetails) - { - if(!experienceDetails.has(LLExperienceCache::MISSING)) - { - LLFloaterReg::showInstance("experience_profile", experienceDetails[LLExperienceCache::EXPERIENCE_ID].asUUID(), true); - } - } -}; - -LLExperienceHandler gExperienceHandler; - - -LLFloaterExperienceProfile::LLFloaterExperienceProfile(const LLSD& data) - : LLFloater(data) - , mSaveCompleteAction(NOTHING) - , mDirty(false) - , mForceClose(false) -{ - if (data.has("experience_id")) - { - mExperienceId = data["experience_id"].asUUID(); - mPostEdit = data.has("edit_experience") && data["edit_experience"].asBoolean(); - } - else - { - mExperienceId = data.asUUID(); - mPostEdit = false; - } -} - - -LLFloaterExperienceProfile::~LLFloaterExperienceProfile() -{ - -} - -bool LLFloaterExperienceProfile::postBuild() -{ - - if (mExperienceId.notNull()) - { - LLExperienceCache::instance().fetch(mExperienceId, true); - LLExperienceCache::instance().get(mExperienceId, boost::bind(&LLFloaterExperienceProfile::experienceCallback, - getDerivedHandle(), _1)); - - LLViewerRegion* region = gAgent.getRegion(); - if (region) - { - LLExperienceCache::instance().getExperienceAdmin(mExperienceId, boost::bind( - &LLFloaterExperienceProfile::experienceIsAdmin, getDerivedHandle(), _1)); - } - } - - childSetAction(BTN_EDIT, boost::bind(&LLFloaterExperienceProfile::onClickEdit, this)); - childSetAction(BTN_ALLOW, boost::bind(&LLFloaterExperienceProfile::onClickPermission, this, "Allow")); - childSetAction(BTN_FORGET, boost::bind(&LLFloaterExperienceProfile::onClickForget, this)); - childSetAction(BTN_BLOCK, boost::bind(&LLFloaterExperienceProfile::onClickPermission, this, "Block")); - childSetAction(BTN_CANCEL, boost::bind(&LLFloaterExperienceProfile::onClickCancel, this)); - childSetAction(BTN_SAVE, boost::bind(&LLFloaterExperienceProfile::onClickSave, this)); - childSetAction(BTN_SET_LOCATION, boost::bind(&LLFloaterExperienceProfile::onClickLocation, this)); - childSetAction(BTN_CLEAR_LOCATION, boost::bind(&LLFloaterExperienceProfile::onClickClear, this)); - childSetAction(BTN_SET_GROUP, boost::bind(&LLFloaterExperienceProfile::onPickGroup, this)); - childSetAction(BTN_REPORT, boost::bind(&LLFloaterExperienceProfile::onReportExperience, this)); - - getChild(EDIT TF_DESC)->setKeystrokeCallback(boost::bind(&LLFloaterExperienceProfile::onFieldChanged, this)); - getChild(EDIT TF_MATURITY)->setCommitCallback(boost::bind(&LLFloaterExperienceProfile::onFieldChanged, this)); - getChild(EDIT TF_MRKT)->setKeystrokeCallback(boost::bind(&LLFloaterExperienceProfile::onFieldChanged, this), NULL); - getChild(EDIT TF_NAME)->setKeystrokeCallback(boost::bind(&LLFloaterExperienceProfile::onFieldChanged, this), NULL); - - childSetCommitCallback(EDIT BTN_ENABLE, boost::bind(&LLFloaterExperienceProfile::onFieldChanged, this), NULL); - childSetCommitCallback(EDIT BTN_PRIVATE, boost::bind(&LLFloaterExperienceProfile::onFieldChanged, this), NULL); - - childSetCommitCallback(EDIT IMG_LOGO, boost::bind(&LLFloaterExperienceProfile::onFieldChanged, this), NULL); - - getChild(EDIT TF_DESC)->setCommitOnFocusLost(true); - - - LLEventPumps::instance().obtain("experience_permission").listen(mExperienceId.asString()+"-profile", - boost::bind(&LLFloaterExperienceProfile::experiencePermission, getDerivedHandle(this), _1)); - - if (mPostEdit && mExperienceId.notNull()) - { - mPostEdit = false; - changeToEdit(); - } - - return true; -} - -void LLFloaterExperienceProfile::experienceCallback(LLHandle handle, const LLSD& experience ) -{ - LLFloaterExperienceProfile* pllpep = handle.get(); - if(pllpep) - { - pllpep->refreshExperience(experience); - } -} - - -bool LLFloaterExperienceProfile::experiencePermission( LLHandle handle, const LLSD& permission ) -{ - LLFloaterExperienceProfile* pllpep = handle.get(); - if(pllpep) - { - pllpep->updatePermission(permission); - } - return false; -} - -bool LLFloaterExperienceProfile::matchesKey(const LLSD& key) -{ - if (key.has("experience_id")) - { - return mExperienceId == key["experience_id"].asUUID(); - } - else if (key.isUUID()) - { - return mExperienceId == key.asUUID(); - } - // Assume NULL uuid - return mExperienceId.isNull(); -} - - -void LLFloaterExperienceProfile::onClickEdit() -{ - changeToEdit(); -} - - -void LLFloaterExperienceProfile::onClickCancel() -{ - changeToView(); -} - -void LLFloaterExperienceProfile::onClickSave() -{ - doSave(NOTHING); -} - -void LLFloaterExperienceProfile::onClickPermission(const char* perm) -{ - LLViewerRegion* region = gAgent.getRegion(); - if (!region) - return; - LLExperienceCache::instance().setExperiencePermission(mExperienceId, perm, boost::bind( - &LLFloaterExperienceProfile::experiencePermissionResults, mExperienceId, _1)); -} - - -void LLFloaterExperienceProfile::onClickForget() -{ - LLViewerRegion* region = gAgent.getRegion(); - if (!region) - return; - - LLExperienceCache::instance().forgetExperiencePermission(mExperienceId, boost::bind( - &LLFloaterExperienceProfile::experiencePermissionResults, mExperienceId, _1)); -} - -bool LLFloaterExperienceProfile::setMaturityString( U8 maturity, LLTextBox* child, LLComboBox* combo ) -{ - LLStyle::Params style; - std::string access; - if(maturity <= SIM_ACCESS_PG) - { - style.image(LLUI::getUIImage(getString("maturity_icon_general"))); - access = LLTrans::getString("SIM_ACCESS_PG"); - combo->setCurrentByIndex(2); - } - else if(maturity <= SIM_ACCESS_MATURE) - { - style.image(LLUI::getUIImage(getString("maturity_icon_moderate"))); - access = LLTrans::getString("SIM_ACCESS_MATURE"); - combo->setCurrentByIndex(1); - } - else if(maturity <= SIM_ACCESS_ADULT) - { - style.image(LLUI::getUIImage(getString("maturity_icon_adult"))); - access = LLTrans::getString("SIM_ACCESS_ADULT"); - combo->setCurrentByIndex(0); - } - else - { - return false; - } - - child->setText(LLStringUtil::null); - - child->appendImageSegment(style); - - child->appendText(access, false); - - return true; -} - - -void LLFloaterExperienceProfile::refreshExperience( const LLSD& experience ) -{ - mExperienceDetails = experience; - mPackage = experience; - - - LLLayoutPanel* imagePanel = getChild(PNL_IMAGE); - LLLayoutPanel* descriptionPanel = getChild(PNL_DESC); - LLLayoutPanel* locationPanel = getChild(PNL_LOC); - LLLayoutPanel* marketplacePanel = getChild(PNL_MRKT); - LLLayoutPanel* topPanel = getChild(PNL_TOP); - - - imagePanel->setVisible(false); - descriptionPanel->setVisible(false); - locationPanel->setVisible(false); - marketplacePanel->setVisible(false); - topPanel->setVisible(false); - - - LLTextBox* child = getChild(TF_NAME); - //child->setText(experience[LLExperienceCache::NAME].asString()); - child->setText(LLSLURL("experience", experience[LLExperienceCache::EXPERIENCE_ID], "profile").getSLURLString()); - - LLLineEditor* linechild = getChild(EDIT TF_NAME); - linechild->setText(experience[LLExperienceCache::NAME].asString()); - - std::string value = experience[LLExperienceCache::DESCRIPTION].asString(); - LLExpandableTextBox* exchild = getChild(TF_DESC); - exchild->setText(value); - descriptionPanel->setVisible(value.length()>0); - - LLTextEditor* edit_child = getChild(EDIT TF_DESC); - edit_child->setText(value); - - mLocationSLURL = experience[LLExperienceCache::SLURL].asString(); - child = getChild(TF_SLURL); - bool has_slurl = mLocationSLURL.length()>0; - locationPanel->setVisible(has_slurl); - mLocationSLURL = LLSLURL(mLocationSLURL).getSLURLString(); - child->setText(mLocationSLURL); - - - child = getChild(EDIT TF_SLURL); - if(has_slurl) - { - child->setText(mLocationSLURL); - } - else - { - child->setText(getString("empty_slurl")); - } - - setMaturityString((U8)(experience[LLExperienceCache::MATURITY].asInteger()), getChild(TF_MATURITY), getChild(EDIT TF_MATURITY)); - - LLUUID id = experience[LLExperienceCache::AGENT_ID].asUUID(); - child = getChild(TF_OWNER); - value = LLSLURL("agent", id, "inspect").getSLURLString(); - child->setText(value); - - - id = experience[LLExperienceCache::GROUP_ID].asUUID(); - bool id_null = id.isNull(); - child = getChild(TF_GROUP); - value = LLSLURL("group", id, "inspect").getSLURLString(); - child->setText(value); - getChild(PNL_GROUP)->setVisible(!id_null); - - setEditGroup(id); - - getChild(BTN_SET_GROUP)->setEnabled(experience[LLExperienceCache::AGENT_ID].asUUID() == gAgent.getID()); - - LLCheckBoxCtrl* enable = getChild(EDIT BTN_ENABLE); - S32 properties = mExperienceDetails[LLExperienceCache::PROPERTIES].asInteger(); - enable->set(!(properties & LLExperienceCache::PROPERTY_DISABLED)); - - enable = getChild(EDIT BTN_PRIVATE); - enable->set(properties & LLExperienceCache::PROPERTY_PRIVATE); - - topPanel->setVisible(true); - child=getChild(TF_GRID_WIDE); - child->setVisible(true); - - if(properties & LLExperienceCache::PROPERTY_GRID) - { - child->setText(LLTrans::getString("Grid-Scope")); - } - else - { - child->setText(LLTrans::getString("Land-Scope")); - } - - if(getChild(BTN_EDIT)->getVisible()) - { - topPanel->setVisible(true); - } - - if(properties & LLExperienceCache::PROPERTY_PRIVILEGED) - { - child = getChild(TF_PRIVILEGED); - child->setVisible(true); - } - else - { - LLViewerRegion* region = gAgent.getRegion(); - if (region) - { - LLExperienceCache::instance().getExperiencePermission(mExperienceId, boost::bind( - &LLFloaterExperienceProfile::experiencePermissionResults, mExperienceId, _1)); - } - } - - value=experience[LLExperienceCache::METADATA].asString(); - if(value.empty()) - return; - - LLPointer parser = new LLSDXMLParser(); - - LLSD data; - - std::istringstream is(value); - if(LLSDParser::PARSE_FAILURE != parser->parse(is, data, value.size())) - { - value=""; - if(data.has(TF_MRKT)) - { - value=data[TF_MRKT].asString(); - - child = getChild(TF_MRKT); - child->setText(value); - if(value.size()) - { - marketplacePanel->setVisible(true); - } - else - { - marketplacePanel->setVisible(false); - } - } - else - { - marketplacePanel->setVisible(false); - } - - linechild = getChild(EDIT TF_MRKT); - linechild->setText(value); - - if(data.has(IMG_LOGO)) - { - LLTextureCtrl* logo = getChild(IMG_LOGO); - - LLUUID id = data[IMG_LOGO].asUUID(); - logo->setImageAssetID(id); - imagePanel->setVisible(true); - - logo = getChild(EDIT IMG_LOGO); - logo->setImageAssetID(data[IMG_LOGO].asUUID()); - - imagePanel->setVisible(id.notNull()); - } - } - else - { - marketplacePanel->setVisible(false); - imagePanel->setVisible(false); - } - - mDirty=false; - mForceClose = false; - getChild(BTN_SAVE)->setEnabled(mDirty); -} - -void LLFloaterExperienceProfile::setPreferences( const LLSD& content ) -{ - S32 properties = mExperienceDetails[LLExperienceCache::PROPERTIES].asInteger(); - if(properties & LLExperienceCache::PROPERTY_PRIVILEGED) - { - return; - } - - const LLSD& experiences = content["experiences"]; - const LLSD& blocked = content["blocked"]; - - - for(LLSD::array_const_iterator it = experiences.beginArray(); it != experiences.endArray() ; ++it) - { - if(it->asUUID()==mExperienceId) - { - experienceAllowed(); - return; - } - } - - for(LLSD::array_const_iterator it = blocked.beginArray(); it != blocked.endArray() ; ++it) - { - if(it->asUUID()==mExperienceId) - { - experienceBlocked(); - return; - } - } - - experienceForgotten(); -} - -void LLFloaterExperienceProfile::onFieldChanged() -{ - updatePackage(); - - if(!getChild(BTN_EDIT)->getVisible()) - { - return; - } - LLSD::map_const_iterator st = mExperienceDetails.beginMap(); - LLSD::map_const_iterator dt = mPackage.beginMap(); - - mDirty = false; - while( !mDirty && st != mExperienceDetails.endMap() && dt != mPackage.endMap()) - { - mDirty = st->first != dt->first || st->second.asString() != dt->second.asString(); - ++st;++dt; - } - - if(!mDirty && (st != mExperienceDetails.endMap() || dt != mPackage.endMap())) - { - mDirty = true; - } - - getChild(BTN_SAVE)->setEnabled(mDirty); -} - - -bool LLFloaterExperienceProfile::canClose() -{ - if(mForceClose || !mDirty) - { - return true; - } - else - { - // Bring up view-modal dialog: Save changes? Yes, No, Cancel - LLNotificationsUtil::add("SaveChanges", LLSD(), LLSD(), boost::bind(&LLFloaterExperienceProfile::handleSaveChangesDialog, this, _1, _2, CLOSE)); - return false; - } -} - -bool LLFloaterExperienceProfile::handleSaveChangesDialog( const LLSD& notification, const LLSD& response, PostSaveAction action ) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - switch( option ) - { - case 0: // "Yes" - // close after saving - doSave( action ); - break; - - case 1: // "No" - if(action != NOTHING) - { - mForceClose = true; - if(action==CLOSE) - { - closeFloater(); - } - else - { - changeToView(); - } - } - break; - - case 2: // "Cancel" - default: - // If we were quitting, we didn't really mean it. - LLAppViewer::instance()->abortQuit(); - break; - } - return false; -} - -void LLFloaterExperienceProfile::doSave( int success_action ) -{ - mSaveCompleteAction=success_action; - - LLViewerRegion* region = gAgent.getRegion(); - if (!region) - return; - - LLExperienceCache::instance().updateExperience(mPackage, boost::bind( - &LLFloaterExperienceProfile::experienceUpdateResult, - getDerivedHandle(), _1)); -} - -void LLFloaterExperienceProfile::onSaveComplete( const LLSD& content ) -{ - LLUUID id = getExperienceId(); - - if(content.has("removed")) - { - const LLSD& removed = content["removed"]; - LLSD::map_const_iterator it = removed.beginMap(); - for(/**/; it != removed.endMap(); ++it) - { - const std::string& field = it->first; - if(field == LLExperienceCache::EXPERIENCE_ID) - { - //this message should be removed by the experience api - continue; - } - const LLSD& data = it->second; - std::string error_tag = data["error_tag"].asString()+ "ExperienceProfileMessage"; - LLSD fields; - if( LLNotifications::instance().getTemplate(error_tag)) - { - fields["field"] = field; - fields["extra_info"] = data["extra_info"]; - LLNotificationsUtil::add(error_tag, fields); - } - else - { - fields["MESSAGE"]=data["en"]; - LLNotificationsUtil::add("GenericAlert", fields); - } - } - } - - if(!content.has("experience_keys")) - { - LL_WARNS() << "LLFloaterExperienceProfile::onSaveComplete called with bad content" << LL_ENDL; - return; - } - - const LLSD& experiences = content["experience_keys"]; - - LLSD::array_const_iterator it = experiences.beginArray(); - if(it == experiences.endArray()) - { - LL_WARNS() << "LLFloaterExperienceProfile::onSaveComplete called with empty content" << LL_ENDL; - return; - } - - if(!it->has(LLExperienceCache::EXPERIENCE_ID) || ((*it)[LLExperienceCache::EXPERIENCE_ID].asUUID() != id)) - { - LL_WARNS() << "LLFloaterExperienceProfile::onSaveComplete called with unexpected experience id" << LL_ENDL; - return; - } - - refreshExperience(*it); - LLExperienceCache::instance().insert(*it); - LLExperienceCache::instance().fetch(id, true); - - if(mSaveCompleteAction==VIEW) - { - LLTabContainer* tabs = getChild("tab_container"); - tabs->selectTabByName("panel_experience_info"); - } - else if(mSaveCompleteAction == CLOSE) - { - closeFloater(); - } -} - -void LLFloaterExperienceProfile::changeToView() -{ - if(mForceClose || !mDirty) - { - refreshExperience(mExperienceDetails); - LLTabContainer* tabs = getChild("tab_container"); - - tabs->selectTabByName("panel_experience_info"); - } - else - { - // Bring up view-modal dialog: Save changes? Yes, No, Cancel - LLNotificationsUtil::add("SaveChanges", LLSD(), LLSD(), boost::bind(&LLFloaterExperienceProfile::handleSaveChangesDialog, this, _1, _2, VIEW)); - } -} - -void LLFloaterExperienceProfile::changeToEdit() -{ - LLTabContainer* tabs = getChild("tab_container"); - - tabs->selectTabByName("edit_panel_experience_info"); -} - -void LLFloaterExperienceProfile::onClickLocation() -{ - LLViewerRegion* region = gAgent.getRegion(); - if(region) - { - LLTextBox* child = getChild(EDIT TF_SLURL); - mLocationSLURL = LLSLURL(region->getName(), gAgent.getPositionGlobal()).getSLURLString(); - child->setText(mLocationSLURL); - onFieldChanged(); - } -} - -void LLFloaterExperienceProfile::onClickClear() -{ - LLTextBox* child = getChild(EDIT TF_SLURL); - mLocationSLURL = ""; - child->setText(getString("empty_slurl")); - onFieldChanged(); -} - -void LLFloaterExperienceProfile::updatePermission( const LLSD& permission ) -{ - if(permission.has("experience")) - { - if(permission["experience"].asUUID() != mExperienceId) - { - return; - } - - std::string str = permission[mExperienceId.asString()]["permission"].asString(); - if(str == "Allow") - { - experienceAllowed(); - } - else if(str == "Block") - { - experienceBlocked(); - } - else if(str == "Forget") - { - experienceForgotten(); - } - } - else - { - setPreferences(permission); - } -} - -void LLFloaterExperienceProfile::experienceAllowed() -{ - LLButton* button=getChild(BTN_ALLOW); - button->setEnabled(false); - - button=getChild(BTN_FORGET); - button->setEnabled(true); - - button=getChild(BTN_BLOCK); - button->setEnabled(true); -} - -void LLFloaterExperienceProfile::experienceForgotten() -{ - LLButton* button=getChild(BTN_ALLOW); - button->setEnabled(true); - - button=getChild(BTN_FORGET); - button->setEnabled(false); - - button=getChild(BTN_BLOCK); - button->setEnabled(true); -} - -void LLFloaterExperienceProfile::experienceBlocked() -{ - LLButton* button=getChild(BTN_ALLOW); - button->setEnabled(true); - - button=getChild(BTN_FORGET); - button->setEnabled(true); - - button=getChild(BTN_BLOCK); - button->setEnabled(false); -} - -void LLFloaterExperienceProfile::onClose( bool app_quitting ) -{ - LLEventPumps::instance().obtain("experience_permission").stopListening(mExperienceId.asString()+"-profile"); - LLFloater::onClose(app_quitting); -} - -void LLFloaterExperienceProfile::updatePackage() -{ - mPackage[LLExperienceCache::NAME] = getChild(EDIT TF_NAME)->getText(); - mPackage[LLExperienceCache::DESCRIPTION] = getChild(EDIT TF_DESC)->getText(); - if(mLocationSLURL.empty()) - { - mPackage[LLExperienceCache::SLURL] = LLStringUtil::null; - } - else - { - mPackage[LLExperienceCache::SLURL] = mLocationSLURL; - } - - mPackage[LLExperienceCache::MATURITY] = getChild(EDIT TF_MATURITY)->getSelectedValue().asInteger(); - - LLSD metadata; - - metadata[TF_MRKT] = getChild(EDIT TF_MRKT)->getText(); - metadata[IMG_LOGO] = getChild(EDIT IMG_LOGO)->getImageAssetID(); - - LLPointer formatter = new LLSDXMLFormatter(); - - std::ostringstream os; - if(formatter->format(metadata, os)) - { - mPackage[LLExperienceCache::METADATA]=os.str(); - } - - int properties = mPackage[LLExperienceCache::PROPERTIES].asInteger(); - LLCheckBoxCtrl* enable = getChild(EDIT BTN_ENABLE); - if(enable->get()) - { - properties &= ~LLExperienceCache::PROPERTY_DISABLED; - } - else - { - properties |= LLExperienceCache::PROPERTY_DISABLED; - } - - enable = getChild(EDIT BTN_PRIVATE); - if(enable->get()) - { - properties |= LLExperienceCache::PROPERTY_PRIVATE; - } - else - { - properties &= ~LLExperienceCache::PROPERTY_PRIVATE; - } - - mPackage[LLExperienceCache::PROPERTIES] = properties; -} - -void LLFloaterExperienceProfile::onPickGroup() -{ - LLFloater* parent_floater = gFloaterView->getParentFloater(this); - - LLFloaterGroupPicker* widget = LLFloaterReg::showTypedInstance("group_picker", LLSD(gAgent.getID())); - if (widget) - { - widget->setSelectGroupCallback(boost::bind(&LLFloaterExperienceProfile::setEditGroup, this, _1)); - if (parent_floater) - { - LLRect new_rect = gFloaterView->findNeighboringPosition(parent_floater, widget); - widget->setOrigin(new_rect.mLeft, new_rect.mBottom); - parent_floater->addDependentFloater(widget); - } - } -} - -void LLFloaterExperienceProfile::setEditGroup( LLUUID group_id ) -{ - LLTextBox* child = getChild(EDIT TF_GROUP); - std::string value = LLSLURL("group", group_id, "inspect").getSLURLString(); - child->setText(value); - mPackage[LLExperienceCache::GROUP_ID] = group_id; - onFieldChanged(); -} - -void LLFloaterExperienceProfile::onReportExperience() -{ - LLFloaterReporter::showFromExperience(mExperienceId); -} - -/*static*/ -bool LLFloaterExperienceProfile::hasPermission(const LLSD& content, const std::string &name, const LLUUID &test) -{ - if (!content.has(name)) - return false; - - const LLSD& list = content[name]; - LLSD::array_const_iterator it = list.beginArray(); - while (it != list.endArray()) - { - if (it->asUUID() == test) - { - return true; - } - ++it; - } - return false; -} - -/*static*/ -void LLFloaterExperienceProfile::experiencePermissionResults(LLUUID exprienceId, LLSD result) -{ - std::string permission("Forget"); - if (hasPermission(result, "experiences", exprienceId)) - permission = "Allow"; - else if (hasPermission(result, "blocked", exprienceId)) - permission = "Block"; - - LLSD experience; - LLSD message; - experience["permission"] = permission; - message["experience"] = exprienceId; - message[exprienceId.asString()] = experience; - - LLEventPumps::instance().obtain("experience_permission").post(message); -} - -/*static*/ -void LLFloaterExperienceProfile::experienceIsAdmin(LLHandle handle, const LLSD &result) -{ - LLFloaterExperienceProfile* parent = handle.get(); - if (!parent) - return; - - bool enabled = true; - LLViewerRegion* region = gAgent.getRegion(); - if (!region) - { - enabled = false; - } - else - { - std::string url = region->getCapability("UpdateExperience"); - if (url.empty()) - enabled = false; - } - if (enabled && result["status"].asBoolean()) - { - parent->getChild(PNL_TOP)->setVisible(true); - parent->getChild(BTN_EDIT)->setVisible(true); - } -} - -/*static*/ -void LLFloaterExperienceProfile::experienceUpdateResult(LLHandle handle, const LLSD &result) -{ - LLFloaterExperienceProfile* parent = handle.get(); - if (parent) - { - parent->onSaveComplete(result); - } -} +/** + * @file llfloaterexperienceprofile.cpp + * @brief llfloaterexperienceprofile and related class definitions + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#include "llviewerprecompiledheaders.h" +#include "llfloaterexperienceprofile.h" + +#include "llagent.h" +#include "llappviewer.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "llcommandhandler.h" +#include "llexpandabletextbox.h" +#include "llexperiencecache.h" +#include "llfloaterreg.h" +#include "lllayoutstack.h" +#include "lllineeditor.h" +#include "llnotificationsutil.h" +#include "llsdserialize.h" +#include "llslurl.h" +#include "lltabcontainer.h" +#include "lltextbox.h" +#include "lltexturectrl.h" +#include "lltrans.h" +#include "llviewerregion.h" +#include "llevents.h" +#include "llfloatergroups.h" +#include "llnotifications.h" +#include "llfloaterreporter.h" + +#define XML_PANEL_EXPERIENCE_PROFILE "floater_experienceprofile.xml" +#define TF_NAME "experience_title" +#define TF_DESC "experience_description" +#define TF_SLURL "LocationTextText" +#define TF_MRKT "marketplace" +#define TF_MATURITY "ContentRatingText" +#define TF_OWNER "OwnerText" +#define TF_GROUP "GroupText" +#define TF_GRID_WIDE "grid_wide" +#define TF_PRIVILEGED "privileged" +#define EDIT "edit_" + +#define IMG_LOGO "logo" + +#define PNL_TOP "top panel" +#define PNL_IMAGE "image_panel" +#define PNL_DESC "description panel" +#define PNL_LOC "location panel" +#define PNL_MRKT "marketplace panel" +#define PNL_GROUP "group_panel" +#define PNL_PERMS "perm panel" + +#define BTN_ALLOW "allow_btn" +#define BTN_BLOCK "block_btn" +#define BTN_CANCEL "cancel_btn" +#define BTN_CLEAR_LOCATION "clear_btn" +#define BTN_EDIT "edit_btn" +#define BTN_ENABLE "enable_btn" +#define BTN_FORGET "forget_btn" +#define BTN_PRIVATE "private_btn" +#define BTN_REPORT "report_btn" +#define BTN_SAVE "save_btn" +#define BTN_SET_GROUP "Group_btn" +#define BTN_SET_LOCATION "location_btn" + + +class LLExperienceHandler : public LLCommandHandler +{ +public: + LLExperienceHandler() : LLCommandHandler("experience", UNTRUSTED_THROTTLE) { } + + bool handle(const LLSD& params, + const LLSD& query_map, + const std::string& grid, + LLMediaCtrl* web) + { + if(params.size() != 2 || params[1].asString() != "profile") + return false; + + LLExperienceCache::instance().get(params[0].asUUID(), boost::bind(&LLExperienceHandler::experienceCallback, this, _1)); + return true; + } + + void experienceCallback(const LLSD& experienceDetails) + { + if(!experienceDetails.has(LLExperienceCache::MISSING)) + { + LLFloaterReg::showInstance("experience_profile", experienceDetails[LLExperienceCache::EXPERIENCE_ID].asUUID(), true); + } + } +}; + +LLExperienceHandler gExperienceHandler; + + +LLFloaterExperienceProfile::LLFloaterExperienceProfile(const LLSD& data) + : LLFloater(data) + , mSaveCompleteAction(NOTHING) + , mDirty(false) + , mForceClose(false) +{ + if (data.has("experience_id")) + { + mExperienceId = data["experience_id"].asUUID(); + mPostEdit = data.has("edit_experience") && data["edit_experience"].asBoolean(); + } + else + { + mExperienceId = data.asUUID(); + mPostEdit = false; + } +} + + +LLFloaterExperienceProfile::~LLFloaterExperienceProfile() +{ + +} + +bool LLFloaterExperienceProfile::postBuild() +{ + + if (mExperienceId.notNull()) + { + LLExperienceCache::instance().fetch(mExperienceId, true); + LLExperienceCache::instance().get(mExperienceId, boost::bind(&LLFloaterExperienceProfile::experienceCallback, + getDerivedHandle(), _1)); + + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + LLExperienceCache::instance().getExperienceAdmin(mExperienceId, boost::bind( + &LLFloaterExperienceProfile::experienceIsAdmin, getDerivedHandle(), _1)); + } + } + + childSetAction(BTN_EDIT, boost::bind(&LLFloaterExperienceProfile::onClickEdit, this)); + childSetAction(BTN_ALLOW, boost::bind(&LLFloaterExperienceProfile::onClickPermission, this, "Allow")); + childSetAction(BTN_FORGET, boost::bind(&LLFloaterExperienceProfile::onClickForget, this)); + childSetAction(BTN_BLOCK, boost::bind(&LLFloaterExperienceProfile::onClickPermission, this, "Block")); + childSetAction(BTN_CANCEL, boost::bind(&LLFloaterExperienceProfile::onClickCancel, this)); + childSetAction(BTN_SAVE, boost::bind(&LLFloaterExperienceProfile::onClickSave, this)); + childSetAction(BTN_SET_LOCATION, boost::bind(&LLFloaterExperienceProfile::onClickLocation, this)); + childSetAction(BTN_CLEAR_LOCATION, boost::bind(&LLFloaterExperienceProfile::onClickClear, this)); + childSetAction(BTN_SET_GROUP, boost::bind(&LLFloaterExperienceProfile::onPickGroup, this)); + childSetAction(BTN_REPORT, boost::bind(&LLFloaterExperienceProfile::onReportExperience, this)); + + getChild(EDIT TF_DESC)->setKeystrokeCallback(boost::bind(&LLFloaterExperienceProfile::onFieldChanged, this)); + getChild(EDIT TF_MATURITY)->setCommitCallback(boost::bind(&LLFloaterExperienceProfile::onFieldChanged, this)); + getChild(EDIT TF_MRKT)->setKeystrokeCallback(boost::bind(&LLFloaterExperienceProfile::onFieldChanged, this), NULL); + getChild(EDIT TF_NAME)->setKeystrokeCallback(boost::bind(&LLFloaterExperienceProfile::onFieldChanged, this), NULL); + + childSetCommitCallback(EDIT BTN_ENABLE, boost::bind(&LLFloaterExperienceProfile::onFieldChanged, this), NULL); + childSetCommitCallback(EDIT BTN_PRIVATE, boost::bind(&LLFloaterExperienceProfile::onFieldChanged, this), NULL); + + childSetCommitCallback(EDIT IMG_LOGO, boost::bind(&LLFloaterExperienceProfile::onFieldChanged, this), NULL); + + getChild(EDIT TF_DESC)->setCommitOnFocusLost(true); + + + LLEventPumps::instance().obtain("experience_permission").listen(mExperienceId.asString()+"-profile", + boost::bind(&LLFloaterExperienceProfile::experiencePermission, getDerivedHandle(this), _1)); + + if (mPostEdit && mExperienceId.notNull()) + { + mPostEdit = false; + changeToEdit(); + } + + return true; +} + +void LLFloaterExperienceProfile::experienceCallback(LLHandle handle, const LLSD& experience ) +{ + LLFloaterExperienceProfile* pllpep = handle.get(); + if(pllpep) + { + pllpep->refreshExperience(experience); + } +} + + +bool LLFloaterExperienceProfile::experiencePermission( LLHandle handle, const LLSD& permission ) +{ + LLFloaterExperienceProfile* pllpep = handle.get(); + if(pllpep) + { + pllpep->updatePermission(permission); + } + return false; +} + +bool LLFloaterExperienceProfile::matchesKey(const LLSD& key) +{ + if (key.has("experience_id")) + { + return mExperienceId == key["experience_id"].asUUID(); + } + else if (key.isUUID()) + { + return mExperienceId == key.asUUID(); + } + // Assume NULL uuid + return mExperienceId.isNull(); +} + + +void LLFloaterExperienceProfile::onClickEdit() +{ + changeToEdit(); +} + + +void LLFloaterExperienceProfile::onClickCancel() +{ + changeToView(); +} + +void LLFloaterExperienceProfile::onClickSave() +{ + doSave(NOTHING); +} + +void LLFloaterExperienceProfile::onClickPermission(const char* perm) +{ + LLViewerRegion* region = gAgent.getRegion(); + if (!region) + return; + LLExperienceCache::instance().setExperiencePermission(mExperienceId, perm, boost::bind( + &LLFloaterExperienceProfile::experiencePermissionResults, mExperienceId, _1)); +} + + +void LLFloaterExperienceProfile::onClickForget() +{ + LLViewerRegion* region = gAgent.getRegion(); + if (!region) + return; + + LLExperienceCache::instance().forgetExperiencePermission(mExperienceId, boost::bind( + &LLFloaterExperienceProfile::experiencePermissionResults, mExperienceId, _1)); +} + +bool LLFloaterExperienceProfile::setMaturityString( U8 maturity, LLTextBox* child, LLComboBox* combo ) +{ + LLStyle::Params style; + std::string access; + if(maturity <= SIM_ACCESS_PG) + { + style.image(LLUI::getUIImage(getString("maturity_icon_general"))); + access = LLTrans::getString("SIM_ACCESS_PG"); + combo->setCurrentByIndex(2); + } + else if(maturity <= SIM_ACCESS_MATURE) + { + style.image(LLUI::getUIImage(getString("maturity_icon_moderate"))); + access = LLTrans::getString("SIM_ACCESS_MATURE"); + combo->setCurrentByIndex(1); + } + else if(maturity <= SIM_ACCESS_ADULT) + { + style.image(LLUI::getUIImage(getString("maturity_icon_adult"))); + access = LLTrans::getString("SIM_ACCESS_ADULT"); + combo->setCurrentByIndex(0); + } + else + { + return false; + } + + child->setText(LLStringUtil::null); + + child->appendImageSegment(style); + + child->appendText(access, false); + + return true; +} + + +void LLFloaterExperienceProfile::refreshExperience( const LLSD& experience ) +{ + mExperienceDetails = experience; + mPackage = experience; + + + LLLayoutPanel* imagePanel = getChild(PNL_IMAGE); + LLLayoutPanel* descriptionPanel = getChild(PNL_DESC); + LLLayoutPanel* locationPanel = getChild(PNL_LOC); + LLLayoutPanel* marketplacePanel = getChild(PNL_MRKT); + LLLayoutPanel* topPanel = getChild(PNL_TOP); + + + imagePanel->setVisible(false); + descriptionPanel->setVisible(false); + locationPanel->setVisible(false); + marketplacePanel->setVisible(false); + topPanel->setVisible(false); + + + LLTextBox* child = getChild(TF_NAME); + //child->setText(experience[LLExperienceCache::NAME].asString()); + child->setText(LLSLURL("experience", experience[LLExperienceCache::EXPERIENCE_ID], "profile").getSLURLString()); + + LLLineEditor* linechild = getChild(EDIT TF_NAME); + linechild->setText(experience[LLExperienceCache::NAME].asString()); + + std::string value = experience[LLExperienceCache::DESCRIPTION].asString(); + LLExpandableTextBox* exchild = getChild(TF_DESC); + exchild->setText(value); + descriptionPanel->setVisible(value.length()>0); + + LLTextEditor* edit_child = getChild(EDIT TF_DESC); + edit_child->setText(value); + + mLocationSLURL = experience[LLExperienceCache::SLURL].asString(); + child = getChild(TF_SLURL); + bool has_slurl = mLocationSLURL.length()>0; + locationPanel->setVisible(has_slurl); + mLocationSLURL = LLSLURL(mLocationSLURL).getSLURLString(); + child->setText(mLocationSLURL); + + + child = getChild(EDIT TF_SLURL); + if(has_slurl) + { + child->setText(mLocationSLURL); + } + else + { + child->setText(getString("empty_slurl")); + } + + setMaturityString((U8)(experience[LLExperienceCache::MATURITY].asInteger()), getChild(TF_MATURITY), getChild(EDIT TF_MATURITY)); + + LLUUID id = experience[LLExperienceCache::AGENT_ID].asUUID(); + child = getChild(TF_OWNER); + value = LLSLURL("agent", id, "inspect").getSLURLString(); + child->setText(value); + + + id = experience[LLExperienceCache::GROUP_ID].asUUID(); + bool id_null = id.isNull(); + child = getChild(TF_GROUP); + value = LLSLURL("group", id, "inspect").getSLURLString(); + child->setText(value); + getChild(PNL_GROUP)->setVisible(!id_null); + + setEditGroup(id); + + getChild(BTN_SET_GROUP)->setEnabled(experience[LLExperienceCache::AGENT_ID].asUUID() == gAgent.getID()); + + LLCheckBoxCtrl* enable = getChild(EDIT BTN_ENABLE); + S32 properties = mExperienceDetails[LLExperienceCache::PROPERTIES].asInteger(); + enable->set(!(properties & LLExperienceCache::PROPERTY_DISABLED)); + + enable = getChild(EDIT BTN_PRIVATE); + enable->set(properties & LLExperienceCache::PROPERTY_PRIVATE); + + topPanel->setVisible(true); + child=getChild(TF_GRID_WIDE); + child->setVisible(true); + + if(properties & LLExperienceCache::PROPERTY_GRID) + { + child->setText(LLTrans::getString("Grid-Scope")); + } + else + { + child->setText(LLTrans::getString("Land-Scope")); + } + + if(getChild(BTN_EDIT)->getVisible()) + { + topPanel->setVisible(true); + } + + if(properties & LLExperienceCache::PROPERTY_PRIVILEGED) + { + child = getChild(TF_PRIVILEGED); + child->setVisible(true); + } + else + { + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + LLExperienceCache::instance().getExperiencePermission(mExperienceId, boost::bind( + &LLFloaterExperienceProfile::experiencePermissionResults, mExperienceId, _1)); + } + } + + value=experience[LLExperienceCache::METADATA].asString(); + if(value.empty()) + return; + + LLPointer parser = new LLSDXMLParser(); + + LLSD data; + + std::istringstream is(value); + if(LLSDParser::PARSE_FAILURE != parser->parse(is, data, value.size())) + { + value=""; + if(data.has(TF_MRKT)) + { + value=data[TF_MRKT].asString(); + + child = getChild(TF_MRKT); + child->setText(value); + if(value.size()) + { + marketplacePanel->setVisible(true); + } + else + { + marketplacePanel->setVisible(false); + } + } + else + { + marketplacePanel->setVisible(false); + } + + linechild = getChild(EDIT TF_MRKT); + linechild->setText(value); + + if(data.has(IMG_LOGO)) + { + LLTextureCtrl* logo = getChild(IMG_LOGO); + + LLUUID id = data[IMG_LOGO].asUUID(); + logo->setImageAssetID(id); + imagePanel->setVisible(true); + + logo = getChild(EDIT IMG_LOGO); + logo->setImageAssetID(data[IMG_LOGO].asUUID()); + + imagePanel->setVisible(id.notNull()); + } + } + else + { + marketplacePanel->setVisible(false); + imagePanel->setVisible(false); + } + + mDirty=false; + mForceClose = false; + getChild(BTN_SAVE)->setEnabled(mDirty); +} + +void LLFloaterExperienceProfile::setPreferences( const LLSD& content ) +{ + S32 properties = mExperienceDetails[LLExperienceCache::PROPERTIES].asInteger(); + if(properties & LLExperienceCache::PROPERTY_PRIVILEGED) + { + return; + } + + const LLSD& experiences = content["experiences"]; + const LLSD& blocked = content["blocked"]; + + + for(LLSD::array_const_iterator it = experiences.beginArray(); it != experiences.endArray() ; ++it) + { + if(it->asUUID()==mExperienceId) + { + experienceAllowed(); + return; + } + } + + for(LLSD::array_const_iterator it = blocked.beginArray(); it != blocked.endArray() ; ++it) + { + if(it->asUUID()==mExperienceId) + { + experienceBlocked(); + return; + } + } + + experienceForgotten(); +} + +void LLFloaterExperienceProfile::onFieldChanged() +{ + updatePackage(); + + if(!getChild(BTN_EDIT)->getVisible()) + { + return; + } + LLSD::map_const_iterator st = mExperienceDetails.beginMap(); + LLSD::map_const_iterator dt = mPackage.beginMap(); + + mDirty = false; + while( !mDirty && st != mExperienceDetails.endMap() && dt != mPackage.endMap()) + { + mDirty = st->first != dt->first || st->second.asString() != dt->second.asString(); + ++st;++dt; + } + + if(!mDirty && (st != mExperienceDetails.endMap() || dt != mPackage.endMap())) + { + mDirty = true; + } + + getChild(BTN_SAVE)->setEnabled(mDirty); +} + + +bool LLFloaterExperienceProfile::canClose() +{ + if(mForceClose || !mDirty) + { + return true; + } + else + { + // Bring up view-modal dialog: Save changes? Yes, No, Cancel + LLNotificationsUtil::add("SaveChanges", LLSD(), LLSD(), boost::bind(&LLFloaterExperienceProfile::handleSaveChangesDialog, this, _1, _2, CLOSE)); + return false; + } +} + +bool LLFloaterExperienceProfile::handleSaveChangesDialog( const LLSD& notification, const LLSD& response, PostSaveAction action ) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + switch( option ) + { + case 0: // "Yes" + // close after saving + doSave( action ); + break; + + case 1: // "No" + if(action != NOTHING) + { + mForceClose = true; + if(action==CLOSE) + { + closeFloater(); + } + else + { + changeToView(); + } + } + break; + + case 2: // "Cancel" + default: + // If we were quitting, we didn't really mean it. + LLAppViewer::instance()->abortQuit(); + break; + } + return false; +} + +void LLFloaterExperienceProfile::doSave( int success_action ) +{ + mSaveCompleteAction=success_action; + + LLViewerRegion* region = gAgent.getRegion(); + if (!region) + return; + + LLExperienceCache::instance().updateExperience(mPackage, boost::bind( + &LLFloaterExperienceProfile::experienceUpdateResult, + getDerivedHandle(), _1)); +} + +void LLFloaterExperienceProfile::onSaveComplete( const LLSD& content ) +{ + LLUUID id = getExperienceId(); + + if(content.has("removed")) + { + const LLSD& removed = content["removed"]; + LLSD::map_const_iterator it = removed.beginMap(); + for(/**/; it != removed.endMap(); ++it) + { + const std::string& field = it->first; + if(field == LLExperienceCache::EXPERIENCE_ID) + { + //this message should be removed by the experience api + continue; + } + const LLSD& data = it->second; + std::string error_tag = data["error_tag"].asString()+ "ExperienceProfileMessage"; + LLSD fields; + if( LLNotifications::instance().getTemplate(error_tag)) + { + fields["field"] = field; + fields["extra_info"] = data["extra_info"]; + LLNotificationsUtil::add(error_tag, fields); + } + else + { + fields["MESSAGE"]=data["en"]; + LLNotificationsUtil::add("GenericAlert", fields); + } + } + } + + if(!content.has("experience_keys")) + { + LL_WARNS() << "LLFloaterExperienceProfile::onSaveComplete called with bad content" << LL_ENDL; + return; + } + + const LLSD& experiences = content["experience_keys"]; + + LLSD::array_const_iterator it = experiences.beginArray(); + if(it == experiences.endArray()) + { + LL_WARNS() << "LLFloaterExperienceProfile::onSaveComplete called with empty content" << LL_ENDL; + return; + } + + if(!it->has(LLExperienceCache::EXPERIENCE_ID) || ((*it)[LLExperienceCache::EXPERIENCE_ID].asUUID() != id)) + { + LL_WARNS() << "LLFloaterExperienceProfile::onSaveComplete called with unexpected experience id" << LL_ENDL; + return; + } + + refreshExperience(*it); + LLExperienceCache::instance().insert(*it); + LLExperienceCache::instance().fetch(id, true); + + if(mSaveCompleteAction==VIEW) + { + LLTabContainer* tabs = getChild("tab_container"); + tabs->selectTabByName("panel_experience_info"); + } + else if(mSaveCompleteAction == CLOSE) + { + closeFloater(); + } +} + +void LLFloaterExperienceProfile::changeToView() +{ + if(mForceClose || !mDirty) + { + refreshExperience(mExperienceDetails); + LLTabContainer* tabs = getChild("tab_container"); + + tabs->selectTabByName("panel_experience_info"); + } + else + { + // Bring up view-modal dialog: Save changes? Yes, No, Cancel + LLNotificationsUtil::add("SaveChanges", LLSD(), LLSD(), boost::bind(&LLFloaterExperienceProfile::handleSaveChangesDialog, this, _1, _2, VIEW)); + } +} + +void LLFloaterExperienceProfile::changeToEdit() +{ + LLTabContainer* tabs = getChild("tab_container"); + + tabs->selectTabByName("edit_panel_experience_info"); +} + +void LLFloaterExperienceProfile::onClickLocation() +{ + LLViewerRegion* region = gAgent.getRegion(); + if(region) + { + LLTextBox* child = getChild(EDIT TF_SLURL); + mLocationSLURL = LLSLURL(region->getName(), gAgent.getPositionGlobal()).getSLURLString(); + child->setText(mLocationSLURL); + onFieldChanged(); + } +} + +void LLFloaterExperienceProfile::onClickClear() +{ + LLTextBox* child = getChild(EDIT TF_SLURL); + mLocationSLURL = ""; + child->setText(getString("empty_slurl")); + onFieldChanged(); +} + +void LLFloaterExperienceProfile::updatePermission( const LLSD& permission ) +{ + if(permission.has("experience")) + { + if(permission["experience"].asUUID() != mExperienceId) + { + return; + } + + std::string str = permission[mExperienceId.asString()]["permission"].asString(); + if(str == "Allow") + { + experienceAllowed(); + } + else if(str == "Block") + { + experienceBlocked(); + } + else if(str == "Forget") + { + experienceForgotten(); + } + } + else + { + setPreferences(permission); + } +} + +void LLFloaterExperienceProfile::experienceAllowed() +{ + LLButton* button=getChild(BTN_ALLOW); + button->setEnabled(false); + + button=getChild(BTN_FORGET); + button->setEnabled(true); + + button=getChild(BTN_BLOCK); + button->setEnabled(true); +} + +void LLFloaterExperienceProfile::experienceForgotten() +{ + LLButton* button=getChild(BTN_ALLOW); + button->setEnabled(true); + + button=getChild(BTN_FORGET); + button->setEnabled(false); + + button=getChild(BTN_BLOCK); + button->setEnabled(true); +} + +void LLFloaterExperienceProfile::experienceBlocked() +{ + LLButton* button=getChild(BTN_ALLOW); + button->setEnabled(true); + + button=getChild(BTN_FORGET); + button->setEnabled(true); + + button=getChild(BTN_BLOCK); + button->setEnabled(false); +} + +void LLFloaterExperienceProfile::onClose( bool app_quitting ) +{ + LLEventPumps::instance().obtain("experience_permission").stopListening(mExperienceId.asString()+"-profile"); + LLFloater::onClose(app_quitting); +} + +void LLFloaterExperienceProfile::updatePackage() +{ + mPackage[LLExperienceCache::NAME] = getChild(EDIT TF_NAME)->getText(); + mPackage[LLExperienceCache::DESCRIPTION] = getChild(EDIT TF_DESC)->getText(); + if(mLocationSLURL.empty()) + { + mPackage[LLExperienceCache::SLURL] = LLStringUtil::null; + } + else + { + mPackage[LLExperienceCache::SLURL] = mLocationSLURL; + } + + mPackage[LLExperienceCache::MATURITY] = getChild(EDIT TF_MATURITY)->getSelectedValue().asInteger(); + + LLSD metadata; + + metadata[TF_MRKT] = getChild(EDIT TF_MRKT)->getText(); + metadata[IMG_LOGO] = getChild(EDIT IMG_LOGO)->getImageAssetID(); + + LLPointer formatter = new LLSDXMLFormatter(); + + std::ostringstream os; + if(formatter->format(metadata, os)) + { + mPackage[LLExperienceCache::METADATA]=os.str(); + } + + int properties = mPackage[LLExperienceCache::PROPERTIES].asInteger(); + LLCheckBoxCtrl* enable = getChild(EDIT BTN_ENABLE); + if(enable->get()) + { + properties &= ~LLExperienceCache::PROPERTY_DISABLED; + } + else + { + properties |= LLExperienceCache::PROPERTY_DISABLED; + } + + enable = getChild(EDIT BTN_PRIVATE); + if(enable->get()) + { + properties |= LLExperienceCache::PROPERTY_PRIVATE; + } + else + { + properties &= ~LLExperienceCache::PROPERTY_PRIVATE; + } + + mPackage[LLExperienceCache::PROPERTIES] = properties; +} + +void LLFloaterExperienceProfile::onPickGroup() +{ + LLFloater* parent_floater = gFloaterView->getParentFloater(this); + + LLFloaterGroupPicker* widget = LLFloaterReg::showTypedInstance("group_picker", LLSD(gAgent.getID())); + if (widget) + { + widget->setSelectGroupCallback(boost::bind(&LLFloaterExperienceProfile::setEditGroup, this, _1)); + if (parent_floater) + { + LLRect new_rect = gFloaterView->findNeighboringPosition(parent_floater, widget); + widget->setOrigin(new_rect.mLeft, new_rect.mBottom); + parent_floater->addDependentFloater(widget); + } + } +} + +void LLFloaterExperienceProfile::setEditGroup( LLUUID group_id ) +{ + LLTextBox* child = getChild(EDIT TF_GROUP); + std::string value = LLSLURL("group", group_id, "inspect").getSLURLString(); + child->setText(value); + mPackage[LLExperienceCache::GROUP_ID] = group_id; + onFieldChanged(); +} + +void LLFloaterExperienceProfile::onReportExperience() +{ + LLFloaterReporter::showFromExperience(mExperienceId); +} + +/*static*/ +bool LLFloaterExperienceProfile::hasPermission(const LLSD& content, const std::string &name, const LLUUID &test) +{ + if (!content.has(name)) + return false; + + const LLSD& list = content[name]; + LLSD::array_const_iterator it = list.beginArray(); + while (it != list.endArray()) + { + if (it->asUUID() == test) + { + return true; + } + ++it; + } + return false; +} + +/*static*/ +void LLFloaterExperienceProfile::experiencePermissionResults(LLUUID exprienceId, LLSD result) +{ + std::string permission("Forget"); + if (hasPermission(result, "experiences", exprienceId)) + permission = "Allow"; + else if (hasPermission(result, "blocked", exprienceId)) + permission = "Block"; + + LLSD experience; + LLSD message; + experience["permission"] = permission; + message["experience"] = exprienceId; + message[exprienceId.asString()] = experience; + + LLEventPumps::instance().obtain("experience_permission").post(message); +} + +/*static*/ +void LLFloaterExperienceProfile::experienceIsAdmin(LLHandle handle, const LLSD &result) +{ + LLFloaterExperienceProfile* parent = handle.get(); + if (!parent) + return; + + bool enabled = true; + LLViewerRegion* region = gAgent.getRegion(); + if (!region) + { + enabled = false; + } + else + { + std::string url = region->getCapability("UpdateExperience"); + if (url.empty()) + enabled = false; + } + if (enabled && result["status"].asBoolean()) + { + parent->getChild(PNL_TOP)->setVisible(true); + parent->getChild(BTN_EDIT)->setVisible(true); + } +} + +/*static*/ +void LLFloaterExperienceProfile::experienceUpdateResult(LLHandle handle, const LLSD &result) +{ + LLFloaterExperienceProfile* parent = handle.get(); + if (parent) + { + parent->onSaveComplete(result); + } +} diff --git a/indra/newview/llfloaterexperienceprofile.h b/indra/newview/llfloaterexperienceprofile.h index 0171bb24d7..d23a2be3ce 100644 --- a/indra/newview/llfloaterexperienceprofile.h +++ b/indra/newview/llfloaterexperienceprofile.h @@ -1,113 +1,113 @@ -/** - * @file llfloaterexperienceprofile.h - * @brief llfloaterexperienceprofile and related class definitions - * - * $LicenseInfo:firstyear=2013&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2013, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - - -#ifndef LL_LLFLOATEREXPERIENCEPROFILE_H -#define LL_LLFLOATEREXPERIENCEPROFILE_H - -#include "llfloater.h" -#include "lluuid.h" -#include "llsd.h" - -class LLLayoutPanel; -class LLTextBox; -class LLComboBox; - -class LLFloaterExperienceProfile : public LLFloater -{ - LOG_CLASS(LLFloaterExperienceProfile); -public: - enum PostSaveAction - { - NOTHING, - CLOSE, - VIEW, - }; - - - LLFloaterExperienceProfile(const LLSD& data); - virtual ~LLFloaterExperienceProfile(); - - /* virtual */ bool matchesKey(const LLSD& key); - - LLUUID getExperienceId() const { return mExperienceId; } - void setPreferences( const LLSD& content ); - - - void refreshExperience(const LLSD& experience); - void onSaveComplete( const LLSD& content ); - virtual bool canClose(); - - virtual void onClose(bool app_quitting); -protected: - void onClickEdit(); - void onClickPermission(const char* permission); - void onClickForget(); - void onClickCancel(); - void onClickSave(); - void onClickLocation(); - void onClickClear(); - void onPickGroup(); - void onFieldChanged(); - void onReportExperience(); - - void setEditGroup(LLUUID group_id); - - void changeToView(); - void changeToEdit(); - - void experienceForgotten(); - void experienceBlocked(); - void experienceAllowed(); - - static void experienceCallback(LLHandle handle, const LLSD& experience); - static bool experiencePermission(LLHandle handle, const LLSD& permission); - - bool postBuild(); - bool setMaturityString(U8 maturity, LLTextBox* child, LLComboBox* combo); - bool handleSaveChangesDialog(const LLSD& notification, const LLSD& response, PostSaveAction action); - void doSave( int success_action ); - - void updatePackage(); - - void updatePermission( const LLSD& permission ); - LLUUID mExperienceId; - LLSD mExperienceDetails; - LLSD mPackage; - std::string mLocationSLURL; - int mSaveCompleteAction; - bool mDirty; - bool mForceClose; - bool mPostEdit; // edit experience after opening and updating it -private: - static bool hasPermission(const LLSD& content, const std::string &name, const LLUUID &test); - static void experiencePermissionResults(LLUUID exprienceId, LLSD result); - static void experienceIsAdmin(LLHandle handle, const LLSD &result); - static void experienceUpdateResult(LLHandle handle, const LLSD &result); -}; - -#endif // LL_LLFLOATEREXPERIENCEPROFILE_H +/** + * @file llfloaterexperienceprofile.h + * @brief llfloaterexperienceprofile and related class definitions + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + + +#ifndef LL_LLFLOATEREXPERIENCEPROFILE_H +#define LL_LLFLOATEREXPERIENCEPROFILE_H + +#include "llfloater.h" +#include "lluuid.h" +#include "llsd.h" + +class LLLayoutPanel; +class LLTextBox; +class LLComboBox; + +class LLFloaterExperienceProfile : public LLFloater +{ + LOG_CLASS(LLFloaterExperienceProfile); +public: + enum PostSaveAction + { + NOTHING, + CLOSE, + VIEW, + }; + + + LLFloaterExperienceProfile(const LLSD& data); + virtual ~LLFloaterExperienceProfile(); + + /* virtual */ bool matchesKey(const LLSD& key); + + LLUUID getExperienceId() const { return mExperienceId; } + void setPreferences( const LLSD& content ); + + + void refreshExperience(const LLSD& experience); + void onSaveComplete( const LLSD& content ); + virtual bool canClose(); + + virtual void onClose(bool app_quitting); +protected: + void onClickEdit(); + void onClickPermission(const char* permission); + void onClickForget(); + void onClickCancel(); + void onClickSave(); + void onClickLocation(); + void onClickClear(); + void onPickGroup(); + void onFieldChanged(); + void onReportExperience(); + + void setEditGroup(LLUUID group_id); + + void changeToView(); + void changeToEdit(); + + void experienceForgotten(); + void experienceBlocked(); + void experienceAllowed(); + + static void experienceCallback(LLHandle handle, const LLSD& experience); + static bool experiencePermission(LLHandle handle, const LLSD& permission); + + bool postBuild(); + bool setMaturityString(U8 maturity, LLTextBox* child, LLComboBox* combo); + bool handleSaveChangesDialog(const LLSD& notification, const LLSD& response, PostSaveAction action); + void doSave( int success_action ); + + void updatePackage(); + + void updatePermission( const LLSD& permission ); + LLUUID mExperienceId; + LLSD mExperienceDetails; + LLSD mPackage; + std::string mLocationSLURL; + int mSaveCompleteAction; + bool mDirty; + bool mForceClose; + bool mPostEdit; // edit experience after opening and updating it +private: + static bool hasPermission(const LLSD& content, const std::string &name, const LLUUID &test); + static void experiencePermissionResults(LLUUID exprienceId, LLSD result); + static void experienceIsAdmin(LLHandle handle, const LLSD &result); + static void experienceUpdateResult(LLHandle handle, const LLSD &result); +}; + +#endif // LL_LLFLOATEREXPERIENCEPROFILE_H diff --git a/indra/newview/llfloaterexperiences.cpp b/indra/newview/llfloaterexperiences.cpp index bc754525ed..e79055fdae 100644 --- a/indra/newview/llfloaterexperiences.cpp +++ b/indra/newview/llfloaterexperiences.cpp @@ -1,417 +1,417 @@ -/** - * @file llfloaterexperiences.cpp - * @brief LLFloaterExperiences class implementation - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llfloaterexperiences.h" -#include "llfloaterreg.h" - -#include "llagent.h" -#include "llevents.h" -#include "llexperiencecache.h" -#include "llfloaterregioninfo.h" -#include "llnotificationsutil.h" -#include "llpanelexperiencelog.h" -#include "llpanelexperiencepicker.h" -#include "llpanelexperiences.h" -#include "lltabcontainer.h" -#include "lltrans.h" -#include "llviewerregion.h" - - -#define SHOW_RECENT_TAB (0) -LLFloaterExperiences::LLFloaterExperiences(const LLSD& data) - :LLFloater(data) -{ -} - -LLPanelExperiences* LLFloaterExperiences::addTab(const std::string& name, bool select) -{ - LLPanelExperiences* newPanel = LLPanelExperiences::create(name); - getChild("xp_tabs")->addTabPanel(LLTabContainer::TabPanelParams(). - panel(newPanel). - label(LLTrans::getString(name)). - select_tab(select)); - - return newPanel; -} - -bool LLFloaterExperiences::postBuild() -{ - getChild("xp_tabs")->addTabPanel(new LLPanelExperiencePicker()); - addTab("Allowed_Experiences_Tab", true); - addTab("Blocked_Experiences_Tab", false); - addTab("Admin_Experiences_Tab", false); - addTab("Contrib_Experiences_Tab", false); - LLPanelExperiences* owned = addTab("Owned_Experiences_Tab", false); - owned->setButtonAction("acquire", boost::bind(&LLFloaterExperiences::sendPurchaseRequest, this)); - owned->enableButton(false); -#if SHOW_RECENT_TAB - addTab("Recent_Experiences_Tab", false); -#endif //SHOW_RECENT_TAB - getChild("xp_tabs")->addTabPanel(new LLPanelExperienceLog()); - resizeToTabs(); - - return true; -} - - -void LLFloaterExperiences::clearFromRecent(const LLSD& ids) -{ -#if SHOW_RECENT_TAB - LLTabContainer* tabs = getChild("xp_tabs"); - - LLPanelExperiences* tab = (LLPanelExperiences*)tabs->getPanelByName("Recent_Experiences_Tab"); - if(!tab) - return; - - tab->removeExperiences(ids); -#endif // SHOW_RECENT_TAB -} - -void LLFloaterExperiences::setupRecentTabs() -{ -#if SHOW_RECENT_TAB - LLTabContainer* tabs = getChild("xp_tabs"); - - LLPanelExperiences* tab = (LLPanelExperiences*)tabs->getPanelByName("Recent_Experiences_Tab"); - if(!tab) - return; - - LLSD recent; - - const LLExperienceCache::cache_t& experiences = LLExperienceCache::getCached(); - - LLExperienceCache::cache_t::const_iterator it = experiences.begin(); - while( it != experiences.end() ) - { - if(!it->second.has(LLExperienceCache::MISSING)) - { - recent.append(it->first); - } - ++it; - } - - tab->setExperienceList(recent); -#endif // SHOW_RECENT_TAB -} - - -void LLFloaterExperiences::resizeToTabs() -{ - const S32 TAB_WIDTH_PADDING = 16; - - LLTabContainer* tabs = getChild("xp_tabs"); - LLRect rect = getRect(); - if(rect.getWidth() < tabs->getTotalTabWidth() + TAB_WIDTH_PADDING) - { - rect.mRight = rect.mLeft + tabs->getTotalTabWidth() + TAB_WIDTH_PADDING; - } - reshape(rect.getWidth(), rect.getHeight(), false); -} - -void LLFloaterExperiences::refreshContents() -{ - setupRecentTabs(); - - LLViewerRegion* region = gAgent.getRegion(); - - if (region) - { - NameMap_t tabMap; - LLHandle handle = getDerivedHandle(); - - tabMap["experiences"]="Allowed_Experiences_Tab"; - tabMap["blocked"]="Blocked_Experiences_Tab"; - tabMap["experience_ids"]="Owned_Experiences_Tab"; - - retrieveExperienceList(region->getCapability("GetExperiences"), handle, tabMap); - - updateInfo("GetAdminExperiences","Admin_Experiences_Tab"); - updateInfo("GetCreatorExperiences","Contrib_Experiences_Tab"); - - retrieveExperienceList(region->getCapability("AgentExperiences"), handle, tabMap, - "ExperienceAcquireFailed", boost::bind(&LLFloaterExperiences::checkPurchaseInfo, this, _1, _2)); - } -} - -void LLFloaterExperiences::onOpen( const LLSD& key ) -{ - LLEventPumps::instance().obtain("experience_permission").stopListening("LLFloaterExperiences"); - LLEventPumps::instance().obtain("experience_permission").listen("LLFloaterExperiences", - boost::bind(&LLFloaterExperiences::updatePermissions, this, _1)); - - LLViewerRegion* region = gAgent.getRegion(); - if(region) - { - if(region->capabilitiesReceived()) - { - refreshContents(); - return; - } - region->setCapabilitiesReceivedCallback(boost::bind(&LLFloaterExperiences::refreshContents, this)); - return; - } -} - -bool LLFloaterExperiences::updatePermissions( const LLSD& permission ) -{ - LLTabContainer* tabs = getChild("xp_tabs"); - LLUUID experience; - std::string permission_string; - if(permission.has("experience")) - { - experience = permission["experience"].asUUID(); - permission_string = permission[experience.asString()]["permission"].asString(); - - } - LLPanelExperiences* tab = (LLPanelExperiences*)tabs->getPanelByName("Allowed_Experiences_Tab"); - if(tab) - { - if(permission.has("experiences")) - { - tab->setExperienceList(permission["experiences"]); - } - else if(experience.notNull()) - { - if(permission_string != "Allow") - { - tab->removeExperience(experience); - } - else - { - tab->addExperience(experience); - } - } - } - - tab = (LLPanelExperiences*)tabs->getPanelByName("Blocked_Experiences_Tab"); - if(tab) - { - if(permission.has("blocked")) - { - tab->setExperienceList(permission["blocked"]); - } - else if(experience.notNull()) - { - if(permission_string != "Block") - { - tab->removeExperience(experience); - } - else - { - tab->addExperience(experience); - } - } - } - return false; -} - -void LLFloaterExperiences::onClose( bool app_quitting ) -{ - LLEventPumps::instance().obtain("experience_permission").stopListening("LLFloaterExperiences"); - LLFloater::onClose(app_quitting); -} - -void LLFloaterExperiences::checkPurchaseInfo(LLPanelExperiences* panel, const LLSD& content) const -{ - panel->enableButton(content.has("purchase")); - - LLFloaterExperiences::findInstance()->updateInfo("GetAdminExperiences","Admin_Experiences_Tab"); - LLFloaterExperiences::findInstance()->updateInfo("GetCreatorExperiences","Contrib_Experiences_Tab"); -} - -void LLFloaterExperiences::checkAndOpen(LLPanelExperiences* panel, const LLSD& content) const -{ - checkPurchaseInfo(panel, content); - - // determine new item - const LLSD& response_ids = content["experience_ids"]; - - if (mPrepurchaseIds.size() + 1 == response_ids.size()) - { - // we have a new element - for (LLSD::array_const_iterator it = response_ids.beginArray(); it != response_ids.endArray(); ++it) - { - LLUUID experience_id = it->asUUID(); - if (std::find(mPrepurchaseIds.begin(), mPrepurchaseIds.end(), experience_id) == mPrepurchaseIds.end()) - { - // new element found, open it - LLSD args; - args["experience_id"] = experience_id; - args["edit_experience"] = true; - LLFloaterReg::showInstance("experience_profile", args, true); - break; - } - } - } -} - -void LLFloaterExperiences::updateInfo(std::string experienceCap, std::string tab) -{ - LLViewerRegion* region = gAgent.getRegion(); - if (region) - { - NameMap_t tabMap; - LLHandle handle = getDerivedHandle(); - - tabMap["experience_ids"] = tab; - - retrieveExperienceList(region->getCapability(experienceCap), handle, tabMap); - } -} - -void LLFloaterExperiences::sendPurchaseRequest() -{ - LLViewerRegion* region = gAgent.getRegion(); - - if (region) - { - NameMap_t tabMap; - const std::string tab_owned_name = "Owned_Experiences_Tab"; - LLHandle handle = getDerivedHandle(); - - tabMap["experience_ids"] = tab_owned_name; - - // extract ids for experiences that we already have - LLTabContainer* tabs = getChild("xp_tabs"); - LLPanelExperiences* tab_owned = (LLPanelExperiences*)tabs->getPanelByName(tab_owned_name); - mPrepurchaseIds.clear(); - if (tab_owned) - { - tab_owned->getExperienceIdsList(mPrepurchaseIds); - } - - requestNewExperience(region->getCapability("AgentExperiences"), handle, tabMap, "ExperienceAcquireFailed", - boost::bind(&LLFloaterExperiences::checkAndOpen, this, _1, _2)); - } -} - -LLFloaterExperiences* LLFloaterExperiences::findInstance() -{ - return LLFloaterReg::findTypedInstance("experiences"); -} - - -void LLFloaterExperiences::retrieveExperienceList(const std::string &url, - const LLHandle &hparent, const NameMap_t &tabMapping, - const std::string &errorNotify, Callback_t cback) - -{ - invokationFn_t getFn = boost::bind( - // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. - static_cast - //---- - // _1 -> httpAdapter - // _2 -> httpRequest - // _3 -> url - // _4 -> httpOptions - // _5 -> httpHeaders - (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), _1, _2, _3, _4, _5); - - LLCoros::instance().launch("LLFloaterExperiences::retrieveExperienceList", - boost::bind(&LLFloaterExperiences::retrieveExperienceListCoro, - url, hparent, tabMapping, errorNotify, cback, getFn)); - -} - -void LLFloaterExperiences::requestNewExperience(const std::string &url, - const LLHandle &hparent, const NameMap_t &tabMapping, - const std::string &errorNotify, Callback_t cback) -{ - invokationFn_t postFn = boost::bind( - // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. - static_cast - //---- - // _1 -> httpAdapter - // _2 -> httpRequest - // _3 -> url - // _4 -> httpOptions - // _5 -> httpHeaders - (&LLCoreHttpUtil::HttpCoroutineAdapter::postAndSuspend), _1, _2, _3, LLSD(), _4, _5); - - LLCoros::instance().launch("LLFloaterExperiences::requestNewExperience", - boost::bind(&LLFloaterExperiences::retrieveExperienceListCoro, - url, hparent, tabMapping, errorNotify, cback, postFn)); - -} - - -void LLFloaterExperiences::retrieveExperienceListCoro(std::string url, - LLHandle hparent, NameMap_t tabMapping, - std::string errorNotify, Callback_t cback, invokationFn_t invoker) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("retrieveExperienceListCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOptions(new LLCore::HttpOptions); - LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); - - - if (url.empty()) - { - LL_WARNS() << "retrieveExperienceListCoro called with empty capability!" << LL_ENDL; - return; - } - - LLSD result = invoker(httpAdapter, httpRequest, url, httpOptions, httpHeaders); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LLSD subs; - subs["ERROR_MESSAGE"] = status.getType(); - LLNotificationsUtil::add(errorNotify, subs); - - return; - } - - if (hparent.isDead()) - return; - - LLFloaterExperiences* parent = hparent.get(); - LLTabContainer* tabs = parent->getChild("xp_tabs"); - - for (NameMap_t::iterator it = tabMapping.begin(); it != tabMapping.end(); ++it) - { - if (result.has(it->first)) - { - LLPanelExperiences* tab = (LLPanelExperiences*)tabs->getPanelByName(it->second); - if (tab) - { - const LLSD& ids = result[it->first]; - tab->setExperienceList(ids); - if (!cback.empty()) - { - cback(tab, result); - } - } - } - } - -} +/** + * @file llfloaterexperiences.cpp + * @brief LLFloaterExperiences class implementation + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llfloaterexperiences.h" +#include "llfloaterreg.h" + +#include "llagent.h" +#include "llevents.h" +#include "llexperiencecache.h" +#include "llfloaterregioninfo.h" +#include "llnotificationsutil.h" +#include "llpanelexperiencelog.h" +#include "llpanelexperiencepicker.h" +#include "llpanelexperiences.h" +#include "lltabcontainer.h" +#include "lltrans.h" +#include "llviewerregion.h" + + +#define SHOW_RECENT_TAB (0) +LLFloaterExperiences::LLFloaterExperiences(const LLSD& data) + :LLFloater(data) +{ +} + +LLPanelExperiences* LLFloaterExperiences::addTab(const std::string& name, bool select) +{ + LLPanelExperiences* newPanel = LLPanelExperiences::create(name); + getChild("xp_tabs")->addTabPanel(LLTabContainer::TabPanelParams(). + panel(newPanel). + label(LLTrans::getString(name)). + select_tab(select)); + + return newPanel; +} + +bool LLFloaterExperiences::postBuild() +{ + getChild("xp_tabs")->addTabPanel(new LLPanelExperiencePicker()); + addTab("Allowed_Experiences_Tab", true); + addTab("Blocked_Experiences_Tab", false); + addTab("Admin_Experiences_Tab", false); + addTab("Contrib_Experiences_Tab", false); + LLPanelExperiences* owned = addTab("Owned_Experiences_Tab", false); + owned->setButtonAction("acquire", boost::bind(&LLFloaterExperiences::sendPurchaseRequest, this)); + owned->enableButton(false); +#if SHOW_RECENT_TAB + addTab("Recent_Experiences_Tab", false); +#endif //SHOW_RECENT_TAB + getChild("xp_tabs")->addTabPanel(new LLPanelExperienceLog()); + resizeToTabs(); + + return true; +} + + +void LLFloaterExperiences::clearFromRecent(const LLSD& ids) +{ +#if SHOW_RECENT_TAB + LLTabContainer* tabs = getChild("xp_tabs"); + + LLPanelExperiences* tab = (LLPanelExperiences*)tabs->getPanelByName("Recent_Experiences_Tab"); + if(!tab) + return; + + tab->removeExperiences(ids); +#endif // SHOW_RECENT_TAB +} + +void LLFloaterExperiences::setupRecentTabs() +{ +#if SHOW_RECENT_TAB + LLTabContainer* tabs = getChild("xp_tabs"); + + LLPanelExperiences* tab = (LLPanelExperiences*)tabs->getPanelByName("Recent_Experiences_Tab"); + if(!tab) + return; + + LLSD recent; + + const LLExperienceCache::cache_t& experiences = LLExperienceCache::getCached(); + + LLExperienceCache::cache_t::const_iterator it = experiences.begin(); + while( it != experiences.end() ) + { + if(!it->second.has(LLExperienceCache::MISSING)) + { + recent.append(it->first); + } + ++it; + } + + tab->setExperienceList(recent); +#endif // SHOW_RECENT_TAB +} + + +void LLFloaterExperiences::resizeToTabs() +{ + const S32 TAB_WIDTH_PADDING = 16; + + LLTabContainer* tabs = getChild("xp_tabs"); + LLRect rect = getRect(); + if(rect.getWidth() < tabs->getTotalTabWidth() + TAB_WIDTH_PADDING) + { + rect.mRight = rect.mLeft + tabs->getTotalTabWidth() + TAB_WIDTH_PADDING; + } + reshape(rect.getWidth(), rect.getHeight(), false); +} + +void LLFloaterExperiences::refreshContents() +{ + setupRecentTabs(); + + LLViewerRegion* region = gAgent.getRegion(); + + if (region) + { + NameMap_t tabMap; + LLHandle handle = getDerivedHandle(); + + tabMap["experiences"]="Allowed_Experiences_Tab"; + tabMap["blocked"]="Blocked_Experiences_Tab"; + tabMap["experience_ids"]="Owned_Experiences_Tab"; + + retrieveExperienceList(region->getCapability("GetExperiences"), handle, tabMap); + + updateInfo("GetAdminExperiences","Admin_Experiences_Tab"); + updateInfo("GetCreatorExperiences","Contrib_Experiences_Tab"); + + retrieveExperienceList(region->getCapability("AgentExperiences"), handle, tabMap, + "ExperienceAcquireFailed", boost::bind(&LLFloaterExperiences::checkPurchaseInfo, this, _1, _2)); + } +} + +void LLFloaterExperiences::onOpen( const LLSD& key ) +{ + LLEventPumps::instance().obtain("experience_permission").stopListening("LLFloaterExperiences"); + LLEventPumps::instance().obtain("experience_permission").listen("LLFloaterExperiences", + boost::bind(&LLFloaterExperiences::updatePermissions, this, _1)); + + LLViewerRegion* region = gAgent.getRegion(); + if(region) + { + if(region->capabilitiesReceived()) + { + refreshContents(); + return; + } + region->setCapabilitiesReceivedCallback(boost::bind(&LLFloaterExperiences::refreshContents, this)); + return; + } +} + +bool LLFloaterExperiences::updatePermissions( const LLSD& permission ) +{ + LLTabContainer* tabs = getChild("xp_tabs"); + LLUUID experience; + std::string permission_string; + if(permission.has("experience")) + { + experience = permission["experience"].asUUID(); + permission_string = permission[experience.asString()]["permission"].asString(); + + } + LLPanelExperiences* tab = (LLPanelExperiences*)tabs->getPanelByName("Allowed_Experiences_Tab"); + if(tab) + { + if(permission.has("experiences")) + { + tab->setExperienceList(permission["experiences"]); + } + else if(experience.notNull()) + { + if(permission_string != "Allow") + { + tab->removeExperience(experience); + } + else + { + tab->addExperience(experience); + } + } + } + + tab = (LLPanelExperiences*)tabs->getPanelByName("Blocked_Experiences_Tab"); + if(tab) + { + if(permission.has("blocked")) + { + tab->setExperienceList(permission["blocked"]); + } + else if(experience.notNull()) + { + if(permission_string != "Block") + { + tab->removeExperience(experience); + } + else + { + tab->addExperience(experience); + } + } + } + return false; +} + +void LLFloaterExperiences::onClose( bool app_quitting ) +{ + LLEventPumps::instance().obtain("experience_permission").stopListening("LLFloaterExperiences"); + LLFloater::onClose(app_quitting); +} + +void LLFloaterExperiences::checkPurchaseInfo(LLPanelExperiences* panel, const LLSD& content) const +{ + panel->enableButton(content.has("purchase")); + + LLFloaterExperiences::findInstance()->updateInfo("GetAdminExperiences","Admin_Experiences_Tab"); + LLFloaterExperiences::findInstance()->updateInfo("GetCreatorExperiences","Contrib_Experiences_Tab"); +} + +void LLFloaterExperiences::checkAndOpen(LLPanelExperiences* panel, const LLSD& content) const +{ + checkPurchaseInfo(panel, content); + + // determine new item + const LLSD& response_ids = content["experience_ids"]; + + if (mPrepurchaseIds.size() + 1 == response_ids.size()) + { + // we have a new element + for (LLSD::array_const_iterator it = response_ids.beginArray(); it != response_ids.endArray(); ++it) + { + LLUUID experience_id = it->asUUID(); + if (std::find(mPrepurchaseIds.begin(), mPrepurchaseIds.end(), experience_id) == mPrepurchaseIds.end()) + { + // new element found, open it + LLSD args; + args["experience_id"] = experience_id; + args["edit_experience"] = true; + LLFloaterReg::showInstance("experience_profile", args, true); + break; + } + } + } +} + +void LLFloaterExperiences::updateInfo(std::string experienceCap, std::string tab) +{ + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + NameMap_t tabMap; + LLHandle handle = getDerivedHandle(); + + tabMap["experience_ids"] = tab; + + retrieveExperienceList(region->getCapability(experienceCap), handle, tabMap); + } +} + +void LLFloaterExperiences::sendPurchaseRequest() +{ + LLViewerRegion* region = gAgent.getRegion(); + + if (region) + { + NameMap_t tabMap; + const std::string tab_owned_name = "Owned_Experiences_Tab"; + LLHandle handle = getDerivedHandle(); + + tabMap["experience_ids"] = tab_owned_name; + + // extract ids for experiences that we already have + LLTabContainer* tabs = getChild("xp_tabs"); + LLPanelExperiences* tab_owned = (LLPanelExperiences*)tabs->getPanelByName(tab_owned_name); + mPrepurchaseIds.clear(); + if (tab_owned) + { + tab_owned->getExperienceIdsList(mPrepurchaseIds); + } + + requestNewExperience(region->getCapability("AgentExperiences"), handle, tabMap, "ExperienceAcquireFailed", + boost::bind(&LLFloaterExperiences::checkAndOpen, this, _1, _2)); + } +} + +LLFloaterExperiences* LLFloaterExperiences::findInstance() +{ + return LLFloaterReg::findTypedInstance("experiences"); +} + + +void LLFloaterExperiences::retrieveExperienceList(const std::string &url, + const LLHandle &hparent, const NameMap_t &tabMapping, + const std::string &errorNotify, Callback_t cback) + +{ + invokationFn_t getFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> httpOptions + // _5 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::getAndSuspend), _1, _2, _3, _4, _5); + + LLCoros::instance().launch("LLFloaterExperiences::retrieveExperienceList", + boost::bind(&LLFloaterExperiences::retrieveExperienceListCoro, + url, hparent, tabMapping, errorNotify, cback, getFn)); + +} + +void LLFloaterExperiences::requestNewExperience(const std::string &url, + const LLHandle &hparent, const NameMap_t &tabMapping, + const std::string &errorNotify, Callback_t cback) +{ + invokationFn_t postFn = boost::bind( + // Humans ignore next line. It is just a cast to specify which LLCoreHttpUtil::HttpCoroutineAdapter routine overload. + static_cast + //---- + // _1 -> httpAdapter + // _2 -> httpRequest + // _3 -> url + // _4 -> httpOptions + // _5 -> httpHeaders + (&LLCoreHttpUtil::HttpCoroutineAdapter::postAndSuspend), _1, _2, _3, LLSD(), _4, _5); + + LLCoros::instance().launch("LLFloaterExperiences::requestNewExperience", + boost::bind(&LLFloaterExperiences::retrieveExperienceListCoro, + url, hparent, tabMapping, errorNotify, cback, postFn)); + +} + + +void LLFloaterExperiences::retrieveExperienceListCoro(std::string url, + LLHandle hparent, NameMap_t tabMapping, + std::string errorNotify, Callback_t cback, invokationFn_t invoker) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("retrieveExperienceListCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOptions(new LLCore::HttpOptions); + LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); + + + if (url.empty()) + { + LL_WARNS() << "retrieveExperienceListCoro called with empty capability!" << LL_ENDL; + return; + } + + LLSD result = invoker(httpAdapter, httpRequest, url, httpOptions, httpHeaders); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LLSD subs; + subs["ERROR_MESSAGE"] = status.getType(); + LLNotificationsUtil::add(errorNotify, subs); + + return; + } + + if (hparent.isDead()) + return; + + LLFloaterExperiences* parent = hparent.get(); + LLTabContainer* tabs = parent->getChild("xp_tabs"); + + for (NameMap_t::iterator it = tabMapping.begin(); it != tabMapping.end(); ++it) + { + if (result.has(it->first)) + { + LLPanelExperiences* tab = (LLPanelExperiences*)tabs->getPanelByName(it->second); + if (tab) + { + const LLSD& ids = result[it->first]; + tab->setExperienceList(ids); + if (!cback.empty()) + { + cback(tab, result); + } + } + } + } + +} diff --git a/indra/newview/llfloaterexperiences.h b/indra/newview/llfloaterexperiences.h index 6926680a9a..5e657767d2 100644 --- a/indra/newview/llfloaterexperiences.h +++ b/indra/newview/llfloaterexperiences.h @@ -1,76 +1,76 @@ -/** - * @file llfloaterexperiences.h - * @brief LLFloaterExperiences class definition - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATEREXPERIENCES_H -#define LL_LLFLOATEREXPERIENCES_H - -#include "llfloater.h" -#include "llcorehttputil.h" - -class LLPanelExperiences; - -class LLFloaterExperiences : - public LLFloater -{ -public: - LLFloaterExperiences(const LLSD& data); - virtual void onClose(bool app_quitting); - - virtual void onOpen(const LLSD& key); - static LLFloaterExperiences* findInstance(); -protected: - typedef std::map NameMap_t; - typedef boost::function Callback_t; - - void clearFromRecent(const LLSD& ids); - void resizeToTabs(); - /*virtual*/ bool postBuild(); - void refreshContents(); - void setupRecentTabs(); - LLPanelExperiences* addTab(const std::string& name, bool select); - - bool updatePermissions(const LLSD& permission); - void sendPurchaseRequest(); - void checkPurchaseInfo(LLPanelExperiences* panel, const LLSD& content)const; - void checkAndOpen(LLPanelExperiences* panel, const LLSD& content) const; - void updateInfo(std::string experiences, std::string tab); - - void retrieveExperienceList(const std::string &url, const LLHandle &hparent, const NameMap_t &tabMapping, - const std::string &errorNotify = std::string("ErrorMessage"), Callback_t cback = Callback_t()); - - void requestNewExperience(const std::string &url, const LLHandle &hparent, const NameMap_t &tabMapping, - const std::string &errorNotify, Callback_t cback); - -private: - typedef boost::function < LLSD(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t, LLCore::HttpRequest::ptr_t, - const std::string, LLCore::HttpOptions::ptr_t, LLCore::HttpHeaders::ptr_t) > invokationFn_t; - - static void retrieveExperienceListCoro(std::string url, LLHandle hparent, - NameMap_t tabMapping, std::string errorNotify, Callback_t cback, invokationFn_t invoker); - std::vector mPrepurchaseIds; -}; - -#endif //LL_LLFLOATEREXPERIENCES_H +/** + * @file llfloaterexperiences.h + * @brief LLFloaterExperiences class definition + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATEREXPERIENCES_H +#define LL_LLFLOATEREXPERIENCES_H + +#include "llfloater.h" +#include "llcorehttputil.h" + +class LLPanelExperiences; + +class LLFloaterExperiences : + public LLFloater +{ +public: + LLFloaterExperiences(const LLSD& data); + virtual void onClose(bool app_quitting); + + virtual void onOpen(const LLSD& key); + static LLFloaterExperiences* findInstance(); +protected: + typedef std::map NameMap_t; + typedef boost::function Callback_t; + + void clearFromRecent(const LLSD& ids); + void resizeToTabs(); + /*virtual*/ bool postBuild(); + void refreshContents(); + void setupRecentTabs(); + LLPanelExperiences* addTab(const std::string& name, bool select); + + bool updatePermissions(const LLSD& permission); + void sendPurchaseRequest(); + void checkPurchaseInfo(LLPanelExperiences* panel, const LLSD& content)const; + void checkAndOpen(LLPanelExperiences* panel, const LLSD& content) const; + void updateInfo(std::string experiences, std::string tab); + + void retrieveExperienceList(const std::string &url, const LLHandle &hparent, const NameMap_t &tabMapping, + const std::string &errorNotify = std::string("ErrorMessage"), Callback_t cback = Callback_t()); + + void requestNewExperience(const std::string &url, const LLHandle &hparent, const NameMap_t &tabMapping, + const std::string &errorNotify, Callback_t cback); + +private: + typedef boost::function < LLSD(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t, LLCore::HttpRequest::ptr_t, + const std::string, LLCore::HttpOptions::ptr_t, LLCore::HttpHeaders::ptr_t) > invokationFn_t; + + static void retrieveExperienceListCoro(std::string url, LLHandle hparent, + NameMap_t tabMapping, std::string errorNotify, Callback_t cback, invokationFn_t invoker); + std::vector mPrepurchaseIds; +}; + +#endif //LL_LLFLOATEREXPERIENCES_H diff --git a/indra/newview/llfloaterfixedenvironment.h b/indra/newview/llfloaterfixedenvironment.h index 401740a4c8..ac6acdd568 100644 --- a/indra/newview/llfloaterfixedenvironment.h +++ b/indra/newview/llfloaterfixedenvironment.h @@ -1,138 +1,138 @@ -/** - * @file llfloaterfixedenvironment.h - * @brief Floaters to create and edit fixed settings for sky and water. - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_FLOATERFIXEDENVIRONMENT_H -#define LL_FLOATERFIXEDENVIRONMENT_H - -#include "llfloatereditenvironmentbase.h" -#include "llsettingsbase.h" -#include "llflyoutcombobtn.h" -#include "llinventory.h" - -#include "boost/signals2.hpp" - -class LLTabContainer; -class LLButton; -class LLLineEditor; -class LLFloaterSettingsPicker; -class LLFixedSettingCopiedCallback; - -/** - * Floater container for creating and editing fixed environment settings. - */ -class LLFloaterFixedEnvironment : public LLFloaterEditEnvironmentBase -{ - LOG_CLASS(LLFloaterFixedEnvironment); -public: - LLFloaterFixedEnvironment(const LLSD &key); - ~LLFloaterFixedEnvironment(); - - virtual bool postBuild() override; - virtual void onOpen(const LLSD& key) override; - virtual void onClose(bool app_quitting) override; - - void setEditSettings(const LLSettingsBase::ptr_t &settings) { mSettings = settings; clearDirtyFlag(); syncronizeTabs(); refresh(); } - virtual LLSettingsBase::ptr_t getEditSettings() const override { return mSettings; } - -protected: - typedef std::function on_confirm_fn; - - virtual void refresh() override; - void setEditSettingsAndUpdate(const LLSettingsBase::ptr_t &settings) override; - virtual void syncronizeTabs(); - - virtual LLFloaterSettingsPicker *getSettingsPicker() override; - - LLTabContainer * mTab; - LLLineEditor * mTxtName; - - LLSettingsBase::ptr_t mSettings; - - LLFlyoutComboBtnCtrl * mFlyoutControl; - - void onInventoryCreated(LLUUID asset_id, LLUUID inventory_id); - void onInventoryCreated(LLUUID asset_id, LLUUID inventory_id, LLSD results); - void onInventoryUpdated(LLUUID asset_id, LLUUID inventory_id, LLSD results); - - virtual void clearDirtyFlag() override; - void updatePermissionFlags(); - - void doSelectFromInventory(); - - virtual void onClickCloseBtn(bool app_quitting = false) override; - -private: - void onNameChanged(const std::string &name); - - void onButtonImport(); - void onButtonApply(LLUICtrl *ctrl, const LLSD &data); - void onButtonLoad(); - - void onPickerCommitSetting(LLUUID item_id); -}; - -class LLFloaterFixedEnvironmentWater : public LLFloaterFixedEnvironment -{ - LOG_CLASS(LLFloaterFixedEnvironmentWater); - -public: - LLFloaterFixedEnvironmentWater(const LLSD &key); - - bool postBuild() override; - - virtual void onOpen(const LLSD& key) override; - -protected: - virtual void updateEditEnvironment() override; - - virtual void doImportFromDisk() override; - void loadWaterSettingFromFile(const std::vector& filenames); - -private: -}; - -class LLFloaterFixedEnvironmentSky : public LLFloaterFixedEnvironment -{ - LOG_CLASS(LLFloaterFixedEnvironmentSky); - -public: - LLFloaterFixedEnvironmentSky(const LLSD &key); - - bool postBuild() override; - - virtual void onOpen(const LLSD& key) override; - virtual void onClose(bool app_quitting) override; - -protected: - virtual void updateEditEnvironment() override; - - virtual void doImportFromDisk() override; - void loadSkySettingFromFile(const std::vector& filenames); - -private: -}; - -#endif // LL_FLOATERFIXEDENVIRONMENT_H +/** + * @file llfloaterfixedenvironment.h + * @brief Floaters to create and edit fixed settings for sky and water. + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_FLOATERFIXEDENVIRONMENT_H +#define LL_FLOATERFIXEDENVIRONMENT_H + +#include "llfloatereditenvironmentbase.h" +#include "llsettingsbase.h" +#include "llflyoutcombobtn.h" +#include "llinventory.h" + +#include "boost/signals2.hpp" + +class LLTabContainer; +class LLButton; +class LLLineEditor; +class LLFloaterSettingsPicker; +class LLFixedSettingCopiedCallback; + +/** + * Floater container for creating and editing fixed environment settings. + */ +class LLFloaterFixedEnvironment : public LLFloaterEditEnvironmentBase +{ + LOG_CLASS(LLFloaterFixedEnvironment); +public: + LLFloaterFixedEnvironment(const LLSD &key); + ~LLFloaterFixedEnvironment(); + + virtual bool postBuild() override; + virtual void onOpen(const LLSD& key) override; + virtual void onClose(bool app_quitting) override; + + void setEditSettings(const LLSettingsBase::ptr_t &settings) { mSettings = settings; clearDirtyFlag(); syncronizeTabs(); refresh(); } + virtual LLSettingsBase::ptr_t getEditSettings() const override { return mSettings; } + +protected: + typedef std::function on_confirm_fn; + + virtual void refresh() override; + void setEditSettingsAndUpdate(const LLSettingsBase::ptr_t &settings) override; + virtual void syncronizeTabs(); + + virtual LLFloaterSettingsPicker *getSettingsPicker() override; + + LLTabContainer * mTab; + LLLineEditor * mTxtName; + + LLSettingsBase::ptr_t mSettings; + + LLFlyoutComboBtnCtrl * mFlyoutControl; + + void onInventoryCreated(LLUUID asset_id, LLUUID inventory_id); + void onInventoryCreated(LLUUID asset_id, LLUUID inventory_id, LLSD results); + void onInventoryUpdated(LLUUID asset_id, LLUUID inventory_id, LLSD results); + + virtual void clearDirtyFlag() override; + void updatePermissionFlags(); + + void doSelectFromInventory(); + + virtual void onClickCloseBtn(bool app_quitting = false) override; + +private: + void onNameChanged(const std::string &name); + + void onButtonImport(); + void onButtonApply(LLUICtrl *ctrl, const LLSD &data); + void onButtonLoad(); + + void onPickerCommitSetting(LLUUID item_id); +}; + +class LLFloaterFixedEnvironmentWater : public LLFloaterFixedEnvironment +{ + LOG_CLASS(LLFloaterFixedEnvironmentWater); + +public: + LLFloaterFixedEnvironmentWater(const LLSD &key); + + bool postBuild() override; + + virtual void onOpen(const LLSD& key) override; + +protected: + virtual void updateEditEnvironment() override; + + virtual void doImportFromDisk() override; + void loadWaterSettingFromFile(const std::vector& filenames); + +private: +}; + +class LLFloaterFixedEnvironmentSky : public LLFloaterFixedEnvironment +{ + LOG_CLASS(LLFloaterFixedEnvironmentSky); + +public: + LLFloaterFixedEnvironmentSky(const LLSD &key); + + bool postBuild() override; + + virtual void onOpen(const LLSD& key) override; + virtual void onClose(bool app_quitting) override; + +protected: + virtual void updateEditEnvironment() override; + + virtual void doImportFromDisk() override; + void loadSkySettingFromFile(const std::vector& filenames); + +private: +}; + +#endif // LL_FLOATERFIXEDENVIRONMENT_H diff --git a/indra/newview/llfloatergesture.cpp b/indra/newview/llfloatergesture.cpp index 161bacfa60..936096d8fe 100644 --- a/indra/newview/llfloatergesture.cpp +++ b/indra/newview/llfloatergesture.cpp @@ -1,713 +1,713 @@ -/** - * @file llfloatergesture.cpp - * @brief Read-only list of gestures from your inventory. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloatergesture.h" - -#include "llinventory.h" -#include "llinventorybridge.h" -#include "llinventoryfunctions.h" -#include "llinventorymodel.h" -#include "llclipboard.h" - -#include "llagent.h" -#include "llappearancemgr.h" -#include "llclipboard.h" -#include "llgesturemgr.h" -#include "llkeyboard.h" -#include "llmenugl.h" -#include "llmultigesture.h" -#include "llnotificationsutil.h" -#include "llpreviewgesture.h" -#include "llscrolllistctrl.h" -#include "lltrans.h" -#include "llviewergesture.h" -#include "llviewermenu.h" -#include "llviewerinventory.h" -#include "llviewercontrol.h" -#include "llfloaterperms.h" - -bool item_name_precedes( LLInventoryItem* a, LLInventoryItem* b ) -{ - return LLStringUtil::precedesDict( a->getName(), b->getName() ); -} - -class LLFloaterGestureObserver : public LLGestureManagerObserver -{ -public: - LLFloaterGestureObserver(LLFloaterGesture* floater) : mFloater(floater) {} - virtual ~LLFloaterGestureObserver() {} - virtual void changed() { mFloater->refreshAll(); } - -private: - LLFloaterGesture* mFloater; -}; -//----------------------------- -// GestureCallback -//----------------------------- - -class GestureShowCallback : public LLInventoryCallback -{ -public: - void fire(const LLUUID &inv_item) - { - LLPreviewGesture::show(inv_item, LLUUID::null); - - LLInventoryItem* item = gInventory.getItem(inv_item); - if (item) - { - LLPermissions perm = item->getPermissions(); - perm.setMaskNext(LLFloaterPerms::getNextOwnerPerms("Gestures")); - perm.setMaskEveryone(LLFloaterPerms::getEveryonePerms("Gestures")); - perm.setMaskGroup(LLFloaterPerms::getGroupPerms("Gestures")); - item->setPermissions(perm); - item->updateServer(false); - } - } -}; - -class GestureCopiedCallback : public LLInventoryCallback -{ -private: - LLFloaterGesture* mFloater; - -public: - GestureCopiedCallback(LLFloaterGesture* floater): mFloater(floater) - {} - void fire(const LLUUID &inv_item) - { - if(mFloater) - { - mFloater->addGesture(inv_item,NULL,mFloater->getChild("gesture_list")); - - // EXP-1909 (Pasted gesture displayed twice) - // The problem is that addGesture is called here for the second time for the same item (which is copied) - // First time addGesture is called from LLFloaterGestureObserver::changed(), which is a callback for inventory - // change. So we need to refresh the gesture list to avoid duplicates. - mFloater->refreshAll(); - } - } -}; - -//--------------------------------------------------------------------------- -// LLFloaterGesture -//--------------------------------------------------------------------------- -LLFloaterGesture::LLFloaterGesture(const LLSD& key) - : LLFloater(key) -{ - mObserver = new LLFloaterGestureObserver(this); - LLGestureMgr::instance().addObserver(mObserver); - - mCommitCallbackRegistrar.add("Gesture.Action.ToggleActiveState", boost::bind(&LLFloaterGesture::onActivateBtnClick, this)); - mCommitCallbackRegistrar.add("Gesture.Action.ShowPreview", boost::bind(&LLFloaterGesture::onClickEdit, this)); - mCommitCallbackRegistrar.add("Gesture.Action.CopyPaste", boost::bind(&LLFloaterGesture::onCopyPasteAction, this, _2)); - mCommitCallbackRegistrar.add("Gesture.Action.SaveToCOF", boost::bind(&LLFloaterGesture::addToCurrentOutFit, this)); - mCommitCallbackRegistrar.add("Gesture.Action.Rename", boost::bind(&LLFloaterGesture::onRenameSelected, this)); - - mEnableCallbackRegistrar.add("Gesture.EnableAction", boost::bind(&LLFloaterGesture::isActionEnabled, this, _2)); -} - -void LLFloaterGesture::done() -{ - //this method can be called twice: for GestureFolder and once after loading all sudir of GestureFolder - if (gInventory.isCategoryComplete(mGestureFolderID)) - { - LL_DEBUGS("Gesture")<< "mGestureFolderID loaded" << LL_ENDL; - // we load only gesture folder without childred. - LLInventoryModel::cat_array_t* categories; - LLInventoryModel::item_array_t* items; - uuid_vec_t unloaded_folders; - LL_DEBUGS("Gesture")<< "Get subdirs of Gesture Folder...." << LL_ENDL; - gInventory.getDirectDescendentsOf(mGestureFolderID, categories, items); - if (categories->empty()) - { - gInventory.removeObserver(this); - LL_INFOS("Gesture")<< "Gesture dos NOT contains sub-directories."<< LL_ENDL; - return; - } - LL_DEBUGS("Gesture")<< "There are " << categories->size() << " Folders "<< LL_ENDL; - for (LLInventoryModel::cat_array_t::iterator it = categories->begin(); it != categories->end(); it++) - { - if (!gInventory.isCategoryComplete(it->get()->getUUID())) - { - unloaded_folders.push_back(it->get()->getUUID()); - LL_DEBUGS("Gesture")<< it->get()->getName()<< " Folder added to fetchlist"<< LL_ENDL; - } - - } - if (!unloaded_folders.empty()) - { - LL_DEBUGS("Gesture")<< "Fetching subdirectories....." << LL_ENDL; - setFetchIDs(unloaded_folders); - startFetch(); - } - else - { - LL_DEBUGS("Gesture")<< "All Gesture subdirectories have been loaded."<< LL_ENDL; - gInventory.removeObserver(this); - buildGestureList(); - } - } - else - { - LL_WARNS("Gesture")<< "Gesture list was NOT loaded"<< LL_ENDL; - } -} - -// virtual -LLFloaterGesture::~LLFloaterGesture() -{ - LLGestureMgr::instance().removeObserver(mObserver); - delete mObserver; - mObserver = NULL; - gInventory.removeObserver(this); -} - -// virtual -bool LLFloaterGesture::postBuild() -{ - std::string label; - - label = getTitle(); - - setTitle(label); - mGestureList = getChild("gesture_list"); - mGestureList->setCommitCallback(boost::bind(&LLFloaterGesture::onCommitList, this)); - mGestureList->setDoubleClickCallback(boost::bind(&LLFloaterGesture::onClickPlay, this)); - - getChild("edit_btn")->setCommitCallback(boost::bind(&LLFloaterGesture::onClickEdit, this)); - - getChild("play_btn")->setCommitCallback(boost::bind(&LLFloaterGesture::onClickPlay, this)); - getChild("stop_btn")->setCommitCallback(boost::bind(&LLFloaterGesture::onClickPlay, this)); - getChild("activate_btn")->setClickedCallback(boost::bind(&LLFloaterGesture::onActivateBtnClick, this)); - - getChild("new_gesture_btn")->setCommitCallback(boost::bind(&LLFloaterGesture::onClickNew, this)); - getChild("del_btn")->setClickedCallback(boost::bind(&LLFloaterGesture::onDeleteSelected, this)); - - getChildView("play_btn")->setVisible( true); - getChildView("stop_btn")->setVisible( false); - setDefaultBtn("play_btn"); - mGestureFolderID = gInventory.findCategoryUUIDForType(LLFolderType::FT_GESTURE); - - uuid_vec_t folders; - folders.push_back(mGestureFolderID); - //perform loading Gesture directory anyway to make sure that all subdirectory are loaded too. See method done() for details. - gInventory.addObserver(this); - setFetchIDs(folders); - startFetch(); - - if (mGestureList) - { - buildGestureList(); - - mGestureList->setFocus(true); - - constexpr bool ascending = true; - mGestureList->sortByColumn(std::string("name"), ascending); - mGestureList->selectFirstItem(); - } - - // Update button labels - onCommitList(); - - return true; -} - - -void LLFloaterGesture::refreshAll() -{ - if (!mGestureList) return; - - buildGestureList(); - - if (mSelectedID.isNull()) - { - mGestureList->selectFirstItem(); - } - else - { - if (! mGestureList->setCurrentByID(mSelectedID)) - { - mGestureList->selectFirstItem(); - } - } - - // Update button labels - onCommitList(); -} - -void LLFloaterGesture::buildGestureList() -{ - S32 scroll_pos = mGestureList->getScrollPos(); - uuid_vec_t selected_items; - getSelectedIds(selected_items); - LL_DEBUGS("Gesture")<< "Rebuilding gesture list "<< LL_ENDL; - mGestureList->deleteAllItems(); - - LLGestureMgr::item_map_t::const_iterator it; - const LLGestureMgr::item_map_t& active_gestures = LLGestureMgr::instance().getActiveGestures(); - for (it = active_gestures.begin(); it != active_gestures.end(); ++it) - { - addGesture(it->first,it->second, mGestureList); - } - if (gInventory.isCategoryComplete(mGestureFolderID)) - { - LLIsType is_gesture(LLAssetType::AT_GESTURE); - LLInventoryModel::cat_array_t categories; - LLInventoryModel::item_array_t items; - gInventory.collectDescendentsIf(mGestureFolderID, categories, items, - LLInventoryModel::EXCLUDE_TRASH, is_gesture); - - for (LLInventoryModel::item_array_t::iterator it = items.begin(); it!= items.end(); ++it) - { - LLInventoryItem* item = it->get(); - if (active_gestures.find(item->getUUID()) == active_gestures.end()) - { - // if gesture wasn't loaded yet, we can display only name - addGesture(item->getUUID(), NULL, mGestureList); - } - } - } - - // attempt to preserve scroll position through re-builds - // since we do re-build whenever something gets dirty - for(uuid_vec_t::iterator it = selected_items.begin(); it != selected_items.end(); it++) - { - mGestureList->selectByID(*it); - } - mGestureList->setScrollPos(scroll_pos); -} - -void LLFloaterGesture::addGesture(const LLUUID& item_id , LLMultiGesture* gesture,LLCtrlListInterface * list ) -{ - // Note: Can have NULL item if inventory hasn't arrived yet. - static std::string item_name = getString("loading"); - LLInventoryItem* item = gInventory.getItem(item_id); - if (item) - { - item_name = item->getName(); - } - - static std::string font_style = "NORMAL"; - // If gesture is playing, bold it - - LLSD element; - element["id"] = item_id; - - if (gesture) - { - if (gesture->mPlaying) - { - font_style = "BOLD"; - } - item_name = gesture->mName; - element["columns"][0]["column"] = "trigger"; - element["columns"][0]["value"] = gesture->mTrigger; - element["columns"][0]["font"]["name"] = "SANSSERIF"; - element["columns"][0]["font"]["style"] = font_style; - - std::string key_string; - std::string buffer; - - if (gesture->mKey == KEY_NONE) - { - buffer = "---"; - key_string = "~~~"; // alphabetize to end - } - else - { - key_string = LLKeyboard::stringFromKey(gesture->mKey); - buffer = LLKeyboard::stringFromAccelerator(gesture->mMask, - gesture->mKey); - } - - element["columns"][1]["column"] = "shortcut"; - element["columns"][1]["value"] = buffer; - element["columns"][1]["font"]["name"] = "SANSSERIF"; - element["columns"][1]["font"]["style"] = font_style; - - // hidden column for sorting - element["columns"][2]["column"] = "key"; - element["columns"][2]["value"] = key_string; - element["columns"][2]["font"]["name"] = "SANSSERIF"; - element["columns"][2]["font"]["style"] = font_style; - - // Only add "playing" if we've got the name, less confusing. JC - if (item && gesture->mPlaying) - { - item_name += " " + getString("playing"); - } - element["columns"][3]["column"] = "name"; - element["columns"][3]["value"] = item_name; - element["columns"][3]["font"]["name"] = "SANSSERIF"; - element["columns"][3]["font"]["style"] = font_style; - } - else - { - element["columns"][0]["column"] = "trigger"; - element["columns"][0]["value"] = ""; - element["columns"][0]["font"]["name"] = "SANSSERIF"; - element["columns"][0]["font"]["style"] = font_style; - element["columns"][1]["column"] = "shortcut"; - element["columns"][1]["value"] = "---"; - element["columns"][1]["font"]["name"] = "SANSSERIF"; - element["columns"][1]["font"]["style"] = font_style; - element["columns"][2]["column"] = "key"; - element["columns"][2]["value"] = "~~~"; - element["columns"][2]["font"]["name"] = "SANSSERIF"; - element["columns"][2]["font"]["style"] = font_style; - element["columns"][3]["column"] = "name"; - element["columns"][3]["value"] = item_name; - element["columns"][3]["font"]["name"] = "SANSSERIF"; - element["columns"][3]["font"]["style"] = font_style; - } - - LL_DEBUGS("Gesture") << "Added gesture [" << item_name << "]" << LL_ENDL; - - LLScrollListItem* sl_item = list->addElement(element, ADD_BOTTOM); - if(sl_item) - { - LLFontGL::StyleFlags style = LLGestureMgr::getInstance()->isGestureActive(item_id) ? LLFontGL::BOLD : LLFontGL::NORMAL; - // *TODO find out why ["font"]["style"] does not affect font style - ((LLScrollListText*)sl_item->getColumn(0))->setFontStyle(style); - } -} - -void LLFloaterGesture::getSelectedIds(uuid_vec_t& ids) -{ - std::vector items = mGestureList->getAllSelected(); - for(std::vector::const_iterator it = items.begin(); it != items.end(); it++) - { - ids.push_back((*it)->getUUID()); - } -} - -bool LLFloaterGesture::isActionEnabled(const LLSD& command) -{ - // paste copy_uuid edit_gesture - std::string command_name = command.asString(); - if("paste" == command_name) - { - if(!LLClipboard::instance().hasContents()) - return false; - - std::vector ids; - LLClipboard::instance().pasteFromClipboard(ids); - for(std::vector::iterator it = ids.begin(); it != ids.end(); it++) - { - LLInventoryItem* item = gInventory.getItem(*it); - - if(item && item->getInventoryType() == LLInventoryType::IT_GESTURE) - { - return true; - } - } - return false; - } - else if("copy_uuid" == command_name || "edit_gesture" == command_name) - { - return mGestureList->getAllSelected().size() == 1; - } - else if ("rename_gesture" == command_name) - { - if (mGestureList->getAllSelected().size() == 1) - { - LLViewerInventoryItem* item = gInventory.getItem(mGestureList->getCurrentID()); - - if (item && item->getPermissions().allowModifyBy(gAgentID)) - { - return true; - } - } - return false; - } - return true; -} - -void LLFloaterGesture::onClickPlay() -{ - const LLUUID& item_id = mGestureList->getCurrentID(); - if(item_id.isNull()) return; - - LL_DEBUGS("Gesture")<<" Trying to play gesture id: "<< item_id <getAssetUUID(), inform_server, deactivate_similar); - LL_DEBUGS("Gesture")<< "Activating gesture with inventory ID: " << item_id < cb = new GestureShowCallback(); - create_inventory_item(gAgent.getID(), - gAgent.getSessionID(), - LLUUID::null, - LLTransactionID::tnull, - "New Gesture", - "", - LLAssetType::AT_GESTURE, - LLInventoryType::IT_GESTURE, - NO_INV_SUBTYPE, - PERM_MOVE | LLFloaterPerms::getNextOwnerPerms("Gestures"), - cb); -} - -void LLFloaterGesture::onActivateBtnClick() -{ - uuid_vec_t ids; - getSelectedIds(ids); - if(ids.empty()) - return; - - LLGestureMgr* gm = LLGestureMgr::getInstance(); - uuid_vec_t::const_iterator it = ids.begin(); - bool first_gesture_state = gm->isGestureActive(*it); - bool is_mixed = false; - while( ++it != ids.end() ) - { - if(first_gesture_state != gm->isGestureActive(*it)) - { - is_mixed = true; - break; - } - } - for(uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); it++) - { - if(is_mixed) - { - gm->activateGesture(*it); - } - else - { - if(first_gesture_state) - { - gm->deactivateGesture(*it); - } - else - { - gm->activateGesture(*it); - } - } - } -} - -void LLFloaterGesture::onRenameSelected() -{ - LLViewerInventoryItem* gesture = gInventory.getItem(mGestureList->getCurrentID()); - if (!gesture) - { - return; - } - - LLSD args; - args["NAME"] = gesture->getName(); - - LLSD payload; - payload["gesture_id"] = mGestureList->getCurrentID(); - - LLNotificationsUtil::add("RenameGesture", args, payload, boost::bind(onGestureRename, _1, _2)); - -} - -void LLFloaterGesture::onGestureRename(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option != 0) return; // canceled - - std::string new_name = response["new_name"].asString(); - LLInventoryObject::correctInventoryName(new_name); - if (!new_name.empty()) - { - LLUUID item_id = notification["payload"]["gesture_id"].asUUID(); - LLViewerInventoryItem* gesture = gInventory.getItem(item_id); - if (gesture && (gesture->getName() != new_name)) - { - LLSD updates; - updates["name"] = new_name; - update_inventory_item(item_id, updates, NULL); - } - } -} - -void LLFloaterGesture::onCopyPasteAction(const LLSD& command) -{ - std::string command_name = command.asString(); - // Since we select this command, the inventory items must have already arrived - if("copy_gesture" == command_name) - { - uuid_vec_t ids; - getSelectedIds(ids); - // Make sure the clipboard is empty - LLClipboard::instance().reset(); - for(uuid_vec_t::iterator it = ids.begin(); it != ids.end(); it++) - { - LLInventoryItem* item = gInventory.getItem(*it); - if(item && item->getInventoryType() == LLInventoryType::IT_GESTURE) - { - LLClipboard::instance().addToClipboard(*it); - } - } - } - else if ("paste" == command_name) - { - std::vector ids; - LLClipboard::instance().pasteFromClipboard(ids); - if(ids.empty() || !gInventory.isCategoryComplete(mGestureFolderID)) - return; - LLInventoryCategory* gesture_dir = gInventory.getCategory(mGestureFolderID); - llassert(gesture_dir); - LLPointer cb = new GestureCopiedCallback(this); - - for(std::vector::iterator it = ids.begin(); it != ids.end(); it++) - { - LLInventoryItem* item = gInventory.getItem(*it); - if(gesture_dir && item && item->getInventoryType() == LLInventoryType::IT_GESTURE) - { - LLStringUtil::format_map_t string_args; - string_args["[COPY_NAME]"] = item->getName(); - LL_DEBUGS("Gesture")<< "Copying gesture " << item->getName() << " "<< item->getUUID() << " into " - << gesture_dir->getName() << " "<< gesture_dir->getUUID() << LL_ENDL; - copy_inventory_item(gAgent.getID(), item->getPermissions().getOwner(), item->getUUID(), - gesture_dir->getUUID(), getString("copy_name", string_args), cb); - } - } - LLClipboard::instance().reset(); - } - else if ("copy_uuid" == command_name) - { - LLClipboard::instance().copyToClipboard(mGestureList->getCurrentID(),LLAssetType::AT_GESTURE); - } -} - -void LLFloaterGesture::onClickEdit() -{ - const LLUUID& item_id = mGestureList->getCurrentID(); - - LLInventoryItem* item = gInventory.getItem(item_id); - if (!item) return; - - LLPreviewGesture* previewp = LLPreviewGesture::show(item_id, LLUUID::null); - if (!previewp->getHost()) - { - previewp->setRect(gFloaterView->findNeighboringPosition(this, previewp)); - } -} - -void LLFloaterGesture::onCommitList() -{ - const LLUUID& item_id = mGestureList->getCurrentID(); - - mSelectedID = item_id; - if (LLGestureMgr::instance().isGesturePlaying(item_id)) - { - getChildView("play_btn")->setVisible( false); - getChildView("stop_btn")->setVisible( true); - } - else - { - getChildView("play_btn")->setVisible( true); - getChildView("stop_btn")->setVisible( false); - } -} - -void LLFloaterGesture::onDeleteSelected() -{ - uuid_vec_t ids; - getSelectedIds(ids); - if(ids.empty()) - return; - - const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - LLGestureMgr* gm = LLGestureMgr::getInstance(); - for(uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); it++) - { - const LLUUID& selected_item = *it; - LLInventoryItem* inv_item = gInventory.getItem(selected_item); - if (inv_item && inv_item->getInventoryType() == LLInventoryType::IT_GESTURE) - { - if(gm->isGestureActive(selected_item)) - { - gm->deactivateGesture(selected_item); - } - LLInventoryModel::update_list_t update; - LLInventoryModel::LLCategoryUpdate old_folder(inv_item->getParentUUID(), -1); - update.push_back(old_folder); - LLInventoryModel::LLCategoryUpdate new_folder(trash_id, 1); - update.push_back(new_folder); - gInventory.accountForUpdate(update); - - LLPointer new_item = new LLViewerInventoryItem(inv_item); - new_item->setParent(trash_id); - // no need to restamp it though it's a move into trash because - // it's a brand new item already. - new_item->updateParentOnServer(false); - gInventory.updateItem(new_item); - } - } - gInventory.notifyObservers(); - buildGestureList(); -} - -void LLFloaterGesture::addToCurrentOutFit() -{ - uuid_vec_t ids; - getSelectedIds(ids); - LLAppearanceMgr* am = LLAppearanceMgr::getInstance(); - LLPointer cb = new LLUpdateAppearanceOnDestroy; - for(uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); it++) - { - am->addCOFItemLink(*it, cb); - } -} - -void LLFloaterGesture::playGesture(LLUUID item_id) -{ - LL_DEBUGS("Gesture")<<"Playing gesture "<< item_id<getName(), b->getName() ); +} + +class LLFloaterGestureObserver : public LLGestureManagerObserver +{ +public: + LLFloaterGestureObserver(LLFloaterGesture* floater) : mFloater(floater) {} + virtual ~LLFloaterGestureObserver() {} + virtual void changed() { mFloater->refreshAll(); } + +private: + LLFloaterGesture* mFloater; +}; +//----------------------------- +// GestureCallback +//----------------------------- + +class GestureShowCallback : public LLInventoryCallback +{ +public: + void fire(const LLUUID &inv_item) + { + LLPreviewGesture::show(inv_item, LLUUID::null); + + LLInventoryItem* item = gInventory.getItem(inv_item); + if (item) + { + LLPermissions perm = item->getPermissions(); + perm.setMaskNext(LLFloaterPerms::getNextOwnerPerms("Gestures")); + perm.setMaskEveryone(LLFloaterPerms::getEveryonePerms("Gestures")); + perm.setMaskGroup(LLFloaterPerms::getGroupPerms("Gestures")); + item->setPermissions(perm); + item->updateServer(false); + } + } +}; + +class GestureCopiedCallback : public LLInventoryCallback +{ +private: + LLFloaterGesture* mFloater; + +public: + GestureCopiedCallback(LLFloaterGesture* floater): mFloater(floater) + {} + void fire(const LLUUID &inv_item) + { + if(mFloater) + { + mFloater->addGesture(inv_item,NULL,mFloater->getChild("gesture_list")); + + // EXP-1909 (Pasted gesture displayed twice) + // The problem is that addGesture is called here for the second time for the same item (which is copied) + // First time addGesture is called from LLFloaterGestureObserver::changed(), which is a callback for inventory + // change. So we need to refresh the gesture list to avoid duplicates. + mFloater->refreshAll(); + } + } +}; + +//--------------------------------------------------------------------------- +// LLFloaterGesture +//--------------------------------------------------------------------------- +LLFloaterGesture::LLFloaterGesture(const LLSD& key) + : LLFloater(key) +{ + mObserver = new LLFloaterGestureObserver(this); + LLGestureMgr::instance().addObserver(mObserver); + + mCommitCallbackRegistrar.add("Gesture.Action.ToggleActiveState", boost::bind(&LLFloaterGesture::onActivateBtnClick, this)); + mCommitCallbackRegistrar.add("Gesture.Action.ShowPreview", boost::bind(&LLFloaterGesture::onClickEdit, this)); + mCommitCallbackRegistrar.add("Gesture.Action.CopyPaste", boost::bind(&LLFloaterGesture::onCopyPasteAction, this, _2)); + mCommitCallbackRegistrar.add("Gesture.Action.SaveToCOF", boost::bind(&LLFloaterGesture::addToCurrentOutFit, this)); + mCommitCallbackRegistrar.add("Gesture.Action.Rename", boost::bind(&LLFloaterGesture::onRenameSelected, this)); + + mEnableCallbackRegistrar.add("Gesture.EnableAction", boost::bind(&LLFloaterGesture::isActionEnabled, this, _2)); +} + +void LLFloaterGesture::done() +{ + //this method can be called twice: for GestureFolder and once after loading all sudir of GestureFolder + if (gInventory.isCategoryComplete(mGestureFolderID)) + { + LL_DEBUGS("Gesture")<< "mGestureFolderID loaded" << LL_ENDL; + // we load only gesture folder without childred. + LLInventoryModel::cat_array_t* categories; + LLInventoryModel::item_array_t* items; + uuid_vec_t unloaded_folders; + LL_DEBUGS("Gesture")<< "Get subdirs of Gesture Folder...." << LL_ENDL; + gInventory.getDirectDescendentsOf(mGestureFolderID, categories, items); + if (categories->empty()) + { + gInventory.removeObserver(this); + LL_INFOS("Gesture")<< "Gesture dos NOT contains sub-directories."<< LL_ENDL; + return; + } + LL_DEBUGS("Gesture")<< "There are " << categories->size() << " Folders "<< LL_ENDL; + for (LLInventoryModel::cat_array_t::iterator it = categories->begin(); it != categories->end(); it++) + { + if (!gInventory.isCategoryComplete(it->get()->getUUID())) + { + unloaded_folders.push_back(it->get()->getUUID()); + LL_DEBUGS("Gesture")<< it->get()->getName()<< " Folder added to fetchlist"<< LL_ENDL; + } + + } + if (!unloaded_folders.empty()) + { + LL_DEBUGS("Gesture")<< "Fetching subdirectories....." << LL_ENDL; + setFetchIDs(unloaded_folders); + startFetch(); + } + else + { + LL_DEBUGS("Gesture")<< "All Gesture subdirectories have been loaded."<< LL_ENDL; + gInventory.removeObserver(this); + buildGestureList(); + } + } + else + { + LL_WARNS("Gesture")<< "Gesture list was NOT loaded"<< LL_ENDL; + } +} + +// virtual +LLFloaterGesture::~LLFloaterGesture() +{ + LLGestureMgr::instance().removeObserver(mObserver); + delete mObserver; + mObserver = NULL; + gInventory.removeObserver(this); +} + +// virtual +bool LLFloaterGesture::postBuild() +{ + std::string label; + + label = getTitle(); + + setTitle(label); + mGestureList = getChild("gesture_list"); + mGestureList->setCommitCallback(boost::bind(&LLFloaterGesture::onCommitList, this)); + mGestureList->setDoubleClickCallback(boost::bind(&LLFloaterGesture::onClickPlay, this)); + + getChild("edit_btn")->setCommitCallback(boost::bind(&LLFloaterGesture::onClickEdit, this)); + + getChild("play_btn")->setCommitCallback(boost::bind(&LLFloaterGesture::onClickPlay, this)); + getChild("stop_btn")->setCommitCallback(boost::bind(&LLFloaterGesture::onClickPlay, this)); + getChild("activate_btn")->setClickedCallback(boost::bind(&LLFloaterGesture::onActivateBtnClick, this)); + + getChild("new_gesture_btn")->setCommitCallback(boost::bind(&LLFloaterGesture::onClickNew, this)); + getChild("del_btn")->setClickedCallback(boost::bind(&LLFloaterGesture::onDeleteSelected, this)); + + getChildView("play_btn")->setVisible( true); + getChildView("stop_btn")->setVisible( false); + setDefaultBtn("play_btn"); + mGestureFolderID = gInventory.findCategoryUUIDForType(LLFolderType::FT_GESTURE); + + uuid_vec_t folders; + folders.push_back(mGestureFolderID); + //perform loading Gesture directory anyway to make sure that all subdirectory are loaded too. See method done() for details. + gInventory.addObserver(this); + setFetchIDs(folders); + startFetch(); + + if (mGestureList) + { + buildGestureList(); + + mGestureList->setFocus(true); + + constexpr bool ascending = true; + mGestureList->sortByColumn(std::string("name"), ascending); + mGestureList->selectFirstItem(); + } + + // Update button labels + onCommitList(); + + return true; +} + + +void LLFloaterGesture::refreshAll() +{ + if (!mGestureList) return; + + buildGestureList(); + + if (mSelectedID.isNull()) + { + mGestureList->selectFirstItem(); + } + else + { + if (! mGestureList->setCurrentByID(mSelectedID)) + { + mGestureList->selectFirstItem(); + } + } + + // Update button labels + onCommitList(); +} + +void LLFloaterGesture::buildGestureList() +{ + S32 scroll_pos = mGestureList->getScrollPos(); + uuid_vec_t selected_items; + getSelectedIds(selected_items); + LL_DEBUGS("Gesture")<< "Rebuilding gesture list "<< LL_ENDL; + mGestureList->deleteAllItems(); + + LLGestureMgr::item_map_t::const_iterator it; + const LLGestureMgr::item_map_t& active_gestures = LLGestureMgr::instance().getActiveGestures(); + for (it = active_gestures.begin(); it != active_gestures.end(); ++it) + { + addGesture(it->first,it->second, mGestureList); + } + if (gInventory.isCategoryComplete(mGestureFolderID)) + { + LLIsType is_gesture(LLAssetType::AT_GESTURE); + LLInventoryModel::cat_array_t categories; + LLInventoryModel::item_array_t items; + gInventory.collectDescendentsIf(mGestureFolderID, categories, items, + LLInventoryModel::EXCLUDE_TRASH, is_gesture); + + for (LLInventoryModel::item_array_t::iterator it = items.begin(); it!= items.end(); ++it) + { + LLInventoryItem* item = it->get(); + if (active_gestures.find(item->getUUID()) == active_gestures.end()) + { + // if gesture wasn't loaded yet, we can display only name + addGesture(item->getUUID(), NULL, mGestureList); + } + } + } + + // attempt to preserve scroll position through re-builds + // since we do re-build whenever something gets dirty + for(uuid_vec_t::iterator it = selected_items.begin(); it != selected_items.end(); it++) + { + mGestureList->selectByID(*it); + } + mGestureList->setScrollPos(scroll_pos); +} + +void LLFloaterGesture::addGesture(const LLUUID& item_id , LLMultiGesture* gesture,LLCtrlListInterface * list ) +{ + // Note: Can have NULL item if inventory hasn't arrived yet. + static std::string item_name = getString("loading"); + LLInventoryItem* item = gInventory.getItem(item_id); + if (item) + { + item_name = item->getName(); + } + + static std::string font_style = "NORMAL"; + // If gesture is playing, bold it + + LLSD element; + element["id"] = item_id; + + if (gesture) + { + if (gesture->mPlaying) + { + font_style = "BOLD"; + } + item_name = gesture->mName; + element["columns"][0]["column"] = "trigger"; + element["columns"][0]["value"] = gesture->mTrigger; + element["columns"][0]["font"]["name"] = "SANSSERIF"; + element["columns"][0]["font"]["style"] = font_style; + + std::string key_string; + std::string buffer; + + if (gesture->mKey == KEY_NONE) + { + buffer = "---"; + key_string = "~~~"; // alphabetize to end + } + else + { + key_string = LLKeyboard::stringFromKey(gesture->mKey); + buffer = LLKeyboard::stringFromAccelerator(gesture->mMask, + gesture->mKey); + } + + element["columns"][1]["column"] = "shortcut"; + element["columns"][1]["value"] = buffer; + element["columns"][1]["font"]["name"] = "SANSSERIF"; + element["columns"][1]["font"]["style"] = font_style; + + // hidden column for sorting + element["columns"][2]["column"] = "key"; + element["columns"][2]["value"] = key_string; + element["columns"][2]["font"]["name"] = "SANSSERIF"; + element["columns"][2]["font"]["style"] = font_style; + + // Only add "playing" if we've got the name, less confusing. JC + if (item && gesture->mPlaying) + { + item_name += " " + getString("playing"); + } + element["columns"][3]["column"] = "name"; + element["columns"][3]["value"] = item_name; + element["columns"][3]["font"]["name"] = "SANSSERIF"; + element["columns"][3]["font"]["style"] = font_style; + } + else + { + element["columns"][0]["column"] = "trigger"; + element["columns"][0]["value"] = ""; + element["columns"][0]["font"]["name"] = "SANSSERIF"; + element["columns"][0]["font"]["style"] = font_style; + element["columns"][1]["column"] = "shortcut"; + element["columns"][1]["value"] = "---"; + element["columns"][1]["font"]["name"] = "SANSSERIF"; + element["columns"][1]["font"]["style"] = font_style; + element["columns"][2]["column"] = "key"; + element["columns"][2]["value"] = "~~~"; + element["columns"][2]["font"]["name"] = "SANSSERIF"; + element["columns"][2]["font"]["style"] = font_style; + element["columns"][3]["column"] = "name"; + element["columns"][3]["value"] = item_name; + element["columns"][3]["font"]["name"] = "SANSSERIF"; + element["columns"][3]["font"]["style"] = font_style; + } + + LL_DEBUGS("Gesture") << "Added gesture [" << item_name << "]" << LL_ENDL; + + LLScrollListItem* sl_item = list->addElement(element, ADD_BOTTOM); + if(sl_item) + { + LLFontGL::StyleFlags style = LLGestureMgr::getInstance()->isGestureActive(item_id) ? LLFontGL::BOLD : LLFontGL::NORMAL; + // *TODO find out why ["font"]["style"] does not affect font style + ((LLScrollListText*)sl_item->getColumn(0))->setFontStyle(style); + } +} + +void LLFloaterGesture::getSelectedIds(uuid_vec_t& ids) +{ + std::vector items = mGestureList->getAllSelected(); + for(std::vector::const_iterator it = items.begin(); it != items.end(); it++) + { + ids.push_back((*it)->getUUID()); + } +} + +bool LLFloaterGesture::isActionEnabled(const LLSD& command) +{ + // paste copy_uuid edit_gesture + std::string command_name = command.asString(); + if("paste" == command_name) + { + if(!LLClipboard::instance().hasContents()) + return false; + + std::vector ids; + LLClipboard::instance().pasteFromClipboard(ids); + for(std::vector::iterator it = ids.begin(); it != ids.end(); it++) + { + LLInventoryItem* item = gInventory.getItem(*it); + + if(item && item->getInventoryType() == LLInventoryType::IT_GESTURE) + { + return true; + } + } + return false; + } + else if("copy_uuid" == command_name || "edit_gesture" == command_name) + { + return mGestureList->getAllSelected().size() == 1; + } + else if ("rename_gesture" == command_name) + { + if (mGestureList->getAllSelected().size() == 1) + { + LLViewerInventoryItem* item = gInventory.getItem(mGestureList->getCurrentID()); + + if (item && item->getPermissions().allowModifyBy(gAgentID)) + { + return true; + } + } + return false; + } + return true; +} + +void LLFloaterGesture::onClickPlay() +{ + const LLUUID& item_id = mGestureList->getCurrentID(); + if(item_id.isNull()) return; + + LL_DEBUGS("Gesture")<<" Trying to play gesture id: "<< item_id <getAssetUUID(), inform_server, deactivate_similar); + LL_DEBUGS("Gesture")<< "Activating gesture with inventory ID: " << item_id < cb = new GestureShowCallback(); + create_inventory_item(gAgent.getID(), + gAgent.getSessionID(), + LLUUID::null, + LLTransactionID::tnull, + "New Gesture", + "", + LLAssetType::AT_GESTURE, + LLInventoryType::IT_GESTURE, + NO_INV_SUBTYPE, + PERM_MOVE | LLFloaterPerms::getNextOwnerPerms("Gestures"), + cb); +} + +void LLFloaterGesture::onActivateBtnClick() +{ + uuid_vec_t ids; + getSelectedIds(ids); + if(ids.empty()) + return; + + LLGestureMgr* gm = LLGestureMgr::getInstance(); + uuid_vec_t::const_iterator it = ids.begin(); + bool first_gesture_state = gm->isGestureActive(*it); + bool is_mixed = false; + while( ++it != ids.end() ) + { + if(first_gesture_state != gm->isGestureActive(*it)) + { + is_mixed = true; + break; + } + } + for(uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); it++) + { + if(is_mixed) + { + gm->activateGesture(*it); + } + else + { + if(first_gesture_state) + { + gm->deactivateGesture(*it); + } + else + { + gm->activateGesture(*it); + } + } + } +} + +void LLFloaterGesture::onRenameSelected() +{ + LLViewerInventoryItem* gesture = gInventory.getItem(mGestureList->getCurrentID()); + if (!gesture) + { + return; + } + + LLSD args; + args["NAME"] = gesture->getName(); + + LLSD payload; + payload["gesture_id"] = mGestureList->getCurrentID(); + + LLNotificationsUtil::add("RenameGesture", args, payload, boost::bind(onGestureRename, _1, _2)); + +} + +void LLFloaterGesture::onGestureRename(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) return; // canceled + + std::string new_name = response["new_name"].asString(); + LLInventoryObject::correctInventoryName(new_name); + if (!new_name.empty()) + { + LLUUID item_id = notification["payload"]["gesture_id"].asUUID(); + LLViewerInventoryItem* gesture = gInventory.getItem(item_id); + if (gesture && (gesture->getName() != new_name)) + { + LLSD updates; + updates["name"] = new_name; + update_inventory_item(item_id, updates, NULL); + } + } +} + +void LLFloaterGesture::onCopyPasteAction(const LLSD& command) +{ + std::string command_name = command.asString(); + // Since we select this command, the inventory items must have already arrived + if("copy_gesture" == command_name) + { + uuid_vec_t ids; + getSelectedIds(ids); + // Make sure the clipboard is empty + LLClipboard::instance().reset(); + for(uuid_vec_t::iterator it = ids.begin(); it != ids.end(); it++) + { + LLInventoryItem* item = gInventory.getItem(*it); + if(item && item->getInventoryType() == LLInventoryType::IT_GESTURE) + { + LLClipboard::instance().addToClipboard(*it); + } + } + } + else if ("paste" == command_name) + { + std::vector ids; + LLClipboard::instance().pasteFromClipboard(ids); + if(ids.empty() || !gInventory.isCategoryComplete(mGestureFolderID)) + return; + LLInventoryCategory* gesture_dir = gInventory.getCategory(mGestureFolderID); + llassert(gesture_dir); + LLPointer cb = new GestureCopiedCallback(this); + + for(std::vector::iterator it = ids.begin(); it != ids.end(); it++) + { + LLInventoryItem* item = gInventory.getItem(*it); + if(gesture_dir && item && item->getInventoryType() == LLInventoryType::IT_GESTURE) + { + LLStringUtil::format_map_t string_args; + string_args["[COPY_NAME]"] = item->getName(); + LL_DEBUGS("Gesture")<< "Copying gesture " << item->getName() << " "<< item->getUUID() << " into " + << gesture_dir->getName() << " "<< gesture_dir->getUUID() << LL_ENDL; + copy_inventory_item(gAgent.getID(), item->getPermissions().getOwner(), item->getUUID(), + gesture_dir->getUUID(), getString("copy_name", string_args), cb); + } + } + LLClipboard::instance().reset(); + } + else if ("copy_uuid" == command_name) + { + LLClipboard::instance().copyToClipboard(mGestureList->getCurrentID(),LLAssetType::AT_GESTURE); + } +} + +void LLFloaterGesture::onClickEdit() +{ + const LLUUID& item_id = mGestureList->getCurrentID(); + + LLInventoryItem* item = gInventory.getItem(item_id); + if (!item) return; + + LLPreviewGesture* previewp = LLPreviewGesture::show(item_id, LLUUID::null); + if (!previewp->getHost()) + { + previewp->setRect(gFloaterView->findNeighboringPosition(this, previewp)); + } +} + +void LLFloaterGesture::onCommitList() +{ + const LLUUID& item_id = mGestureList->getCurrentID(); + + mSelectedID = item_id; + if (LLGestureMgr::instance().isGesturePlaying(item_id)) + { + getChildView("play_btn")->setVisible( false); + getChildView("stop_btn")->setVisible( true); + } + else + { + getChildView("play_btn")->setVisible( true); + getChildView("stop_btn")->setVisible( false); + } +} + +void LLFloaterGesture::onDeleteSelected() +{ + uuid_vec_t ids; + getSelectedIds(ids); + if(ids.empty()) + return; + + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + LLGestureMgr* gm = LLGestureMgr::getInstance(); + for(uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); it++) + { + const LLUUID& selected_item = *it; + LLInventoryItem* inv_item = gInventory.getItem(selected_item); + if (inv_item && inv_item->getInventoryType() == LLInventoryType::IT_GESTURE) + { + if(gm->isGestureActive(selected_item)) + { + gm->deactivateGesture(selected_item); + } + LLInventoryModel::update_list_t update; + LLInventoryModel::LLCategoryUpdate old_folder(inv_item->getParentUUID(), -1); + update.push_back(old_folder); + LLInventoryModel::LLCategoryUpdate new_folder(trash_id, 1); + update.push_back(new_folder); + gInventory.accountForUpdate(update); + + LLPointer new_item = new LLViewerInventoryItem(inv_item); + new_item->setParent(trash_id); + // no need to restamp it though it's a move into trash because + // it's a brand new item already. + new_item->updateParentOnServer(false); + gInventory.updateItem(new_item); + } + } + gInventory.notifyObservers(); + buildGestureList(); +} + +void LLFloaterGesture::addToCurrentOutFit() +{ + uuid_vec_t ids; + getSelectedIds(ids); + LLAppearanceMgr* am = LLAppearanceMgr::getInstance(); + LLPointer cb = new LLUpdateAppearanceOnDestroy; + for(uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); it++) + { + am->addCOFItemLink(*it, cb); + } +} + +void LLFloaterGesture::playGesture(LLUUID item_id) +{ + LL_DEBUGS("Gesture")<<"Playing gesture "<< item_id< - -#include "llfloater.h" -#include "llinventoryobserver.h" - -class LLScrollContainer; -class LLView; -class LLButton; -class LLLineEditor; -class LLComboBox; -class LLViewerGesture; -class LLGestureOptions; -class LLScrollListCtrl; -class LLFloaterGestureObserver; -class LLFloaterGestureInventoryObserver; -class LLMultiGesture; -class LLMenuGL; - -class LLFloaterGesture -: public LLFloater, LLInventoryFetchDescendentsObserver -{ - LOG_CLASS(LLFloaterGesture); -public: - LLFloaterGesture(const LLSD& key); - virtual ~LLFloaterGesture(); - - virtual bool postBuild(); - virtual void done (); - void refreshAll(); - /** - * @brief Add new scrolllistitem into gesture_list. - * @param item_id inventory id of gesture - * @param gesture can be NULL , if item was not loaded yet - */ - void addGesture(const LLUUID& item_id, LLMultiGesture* gesture, LLCtrlListInterface * list); - -protected: - // Reads from the gesture manager's list of active gestures - // and puts them in this list. - void buildGestureList(); - void playGesture(LLUUID item_id); -private: - void addToCurrentOutFit(); - /** - * @brief This method is using to collect selected items. - * In some places gesture_list can be rebuilt by gestureObservers during iterating data from LLScrollListCtrl::getAllSelected(). - * Therefore we have to copy these items to avoid viewer crash. - * @see LLFloaterGesture::onActivateBtnClick - */ - void getSelectedIds(uuid_vec_t& ids); - bool isActionEnabled(const LLSD& command); - /** - * @brief Activation rules: - * According to Gesture Spec: - * 1. If all selected gestures are active: set to inactive - * 2. If all selected gestures are inactive: set to active - * 3. If selected gestures are in a mixed state: set all to active - */ - void onActivateBtnClick(); - void onClickEdit(); - void onClickPlay(); - void onClickNew(); - void onCommitList(); - void onCopyPasteAction(const LLSD& command); - void onDeleteSelected(); - void onRenameSelected(); - - static void onGestureRename(const LLSD& notification, const LLSD& response); - - LLUUID mSelectedID; - LLUUID mGestureFolderID; - LLScrollListCtrl* mGestureList; - - LLFloaterGestureObserver* mObserver; -}; - - -#endif +/** + * @file llfloatergesture.h + * @brief Read-only list of gestures from your inventory. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/** + * (Also has legacy gesture editor for testing.) + */ + +#ifndef LL_LLFLOATERGESTURE_H +#define LL_LLFLOATERGESTURE_H +#include + +#include "llfloater.h" +#include "llinventoryobserver.h" + +class LLScrollContainer; +class LLView; +class LLButton; +class LLLineEditor; +class LLComboBox; +class LLViewerGesture; +class LLGestureOptions; +class LLScrollListCtrl; +class LLFloaterGestureObserver; +class LLFloaterGestureInventoryObserver; +class LLMultiGesture; +class LLMenuGL; + +class LLFloaterGesture +: public LLFloater, LLInventoryFetchDescendentsObserver +{ + LOG_CLASS(LLFloaterGesture); +public: + LLFloaterGesture(const LLSD& key); + virtual ~LLFloaterGesture(); + + virtual bool postBuild(); + virtual void done (); + void refreshAll(); + /** + * @brief Add new scrolllistitem into gesture_list. + * @param item_id inventory id of gesture + * @param gesture can be NULL , if item was not loaded yet + */ + void addGesture(const LLUUID& item_id, LLMultiGesture* gesture, LLCtrlListInterface * list); + +protected: + // Reads from the gesture manager's list of active gestures + // and puts them in this list. + void buildGestureList(); + void playGesture(LLUUID item_id); +private: + void addToCurrentOutFit(); + /** + * @brief This method is using to collect selected items. + * In some places gesture_list can be rebuilt by gestureObservers during iterating data from LLScrollListCtrl::getAllSelected(). + * Therefore we have to copy these items to avoid viewer crash. + * @see LLFloaterGesture::onActivateBtnClick + */ + void getSelectedIds(uuid_vec_t& ids); + bool isActionEnabled(const LLSD& command); + /** + * @brief Activation rules: + * According to Gesture Spec: + * 1. If all selected gestures are active: set to inactive + * 2. If all selected gestures are inactive: set to active + * 3. If selected gestures are in a mixed state: set all to active + */ + void onActivateBtnClick(); + void onClickEdit(); + void onClickPlay(); + void onClickNew(); + void onCommitList(); + void onCopyPasteAction(const LLSD& command); + void onDeleteSelected(); + void onRenameSelected(); + + static void onGestureRename(const LLSD& notification, const LLSD& response); + + LLUUID mSelectedID; + LLUUID mGestureFolderID; + LLScrollListCtrl* mGestureList; + + LLFloaterGestureObserver* mObserver; +}; + + +#endif diff --git a/indra/newview/llfloatergodtools.cpp b/indra/newview/llfloatergodtools.cpp index ee4ba45a50..6c1d5b5cca 100644 --- a/indra/newview/llfloatergodtools.cpp +++ b/indra/newview/llfloatergodtools.cpp @@ -1,1370 +1,1370 @@ -/** - * @file llfloatergodtools.cpp - * @brief The on-screen rectangle with tool options. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloatergodtools.h" - -#include "llavatarnamecache.h" -#include "llcoord.h" -#include "llfontgl.h" -#include "llframetimer.h" -#include "llgl.h" -#include "llhost.h" -#include "llnotificationsutil.h" -#include "llregionflags.h" -#include "llstring.h" -#include "message.h" - -#include "llagent.h" -#include "llbutton.h" -#include "llcheckboxctrl.h" -#include "llcombobox.h" -#include "lldraghandle.h" -#include "llfloater.h" -#include "llfloaterreg.h" -#include "llfocusmgr.h" -#include "llfloatertopobjects.h" -#include "lllineeditor.h" -#include "llmenugl.h" -#include "llresmgr.h" -#include "llselectmgr.h" -#include "llsky.h" -#include "llspinctrl.h" -#include "llstatusbar.h" -#include "lltabcontainer.h" -#include "lltextbox.h" -#include "lluictrl.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" -#include "llviewerwindow.h" -#include "llworld.h" -#include "llfloateravatarpicker.h" -#include "llxfermanager.h" -#include "llvlcomposition.h" -#include "llsurface.h" -#include "llviewercontrol.h" -#include "lluictrlfactory.h" -#include "lltrans.h" - -#include "lltransfertargetfile.h" -#include "lltransfersourcefile.h" - -const F32 SECONDS_BETWEEN_UPDATE_REQUESTS = 5.0f; - -//***************************************************************************** -// LLFloaterGodTools -//***************************************************************************** - -void LLFloaterGodTools::onOpen(const LLSD& key) -{ - center(); - setFocus(true); -// LLPanel *panel = getChild("GodTools Tabs")->getCurrentPanel(); -// if (panel) -// panel->setFocus(true); - if (mPanelObjectTools) - mPanelObjectTools->setTargetAvatar(LLUUID::null); - - if (gAgent.getRegionHost() != mCurrentHost) - { - // we're in a new region - sendRegionInfoRequest(); - } -} - - -// static -void LLFloaterGodTools::refreshAll() -{ - LLFloaterGodTools* god_tools = LLFloaterReg::getTypedInstance("god_tools"); - if (god_tools) - { - if (gAgent.getRegionHost() != god_tools->mCurrentHost) - { - // we're in a new region - god_tools->sendRegionInfoRequest(); - } - } -} - - - -LLFloaterGodTools::LLFloaterGodTools(const LLSD& key) -: LLFloater(key), - mCurrentHost(LLHost()), - mUpdateTimer() -{ - mFactoryMap["grid"] = LLCallbackMap(createPanelGrid, this); - mFactoryMap["region"] = LLCallbackMap(createPanelRegion, this); - mFactoryMap["objects"] = LLCallbackMap(createPanelObjects, this); - mFactoryMap["request"] = LLCallbackMap(createPanelRequest, this); -} - -bool LLFloaterGodTools::postBuild() -{ - sendRegionInfoRequest(); - getChild("GodTools Tabs")->selectTabByName("region"); - return true; -} -// static -void* LLFloaterGodTools::createPanelGrid(void *userdata) -{ - return new LLPanelGridTools(); -} - -// static -void* LLFloaterGodTools::createPanelRegion(void *userdata) -{ - LLFloaterGodTools* self = (LLFloaterGodTools*)userdata; - self->mPanelRegionTools = new LLPanelRegionTools(); - return self->mPanelRegionTools; -} - -// static -void* LLFloaterGodTools::createPanelObjects(void *userdata) -{ - LLFloaterGodTools* self = (LLFloaterGodTools*)userdata; - self->mPanelObjectTools = new LLPanelObjectTools(); - return self->mPanelObjectTools; -} - -// static -void* LLFloaterGodTools::createPanelRequest(void *userdata) -{ - return new LLPanelRequestTools(); -} - -LLFloaterGodTools::~LLFloaterGodTools() -{ - // children automatically deleted -} - - -U64 LLFloaterGodTools::computeRegionFlags() const -{ - U64 flags = gAgent.getRegion()->getRegionFlags(); - if (mPanelRegionTools) flags = mPanelRegionTools->computeRegionFlags(flags); - if (mPanelObjectTools) flags = mPanelObjectTools->computeRegionFlags(flags); - return flags; -} - - -void LLFloaterGodTools::updatePopup(LLCoordGL center, MASK mask) -{ -} - -// virtual -void LLFloaterGodTools::draw() -{ - if (mCurrentHost == LLHost()) - { - if (mUpdateTimer.getElapsedTimeF32() > SECONDS_BETWEEN_UPDATE_REQUESTS) - { - sendRegionInfoRequest(); - } - } - else if (gAgent.getRegionHost() != mCurrentHost) - { - sendRegionInfoRequest(); - } - LLFloater::draw(); -} - -void LLFloaterGodTools::showPanel(const std::string& panel_name) -{ - getChild("GodTools Tabs")->selectTabByName(panel_name); - openFloater(); - LLPanel *panel = getChild("GodTools Tabs")->getCurrentPanel(); - if (panel) - panel->setFocus(true); -} - -// static -void LLFloaterGodTools::processRegionInfo(LLMessageSystem* msg) -{ - llassert(msg); - if (!msg) return; - - //const S32 SIM_NAME_BUF = 256; - U64 region_flags; - U8 sim_access; - U8 agent_limit; - std::string sim_name; - U32 estate_id; - U32 parent_estate_id; - F32 water_height; - F32 billable_factor; - F32 object_bonus_factor; - F32 terrain_raise_limit; - F32 terrain_lower_limit; - S32 price_per_meter; - S32 redirect_grid_x; - S32 redirect_grid_y; - LLUUID cache_id; - - LLHost host = msg->getSender(); - - msg->getStringFast(_PREHASH_RegionInfo, _PREHASH_SimName, sim_name); - msg->getU32Fast(_PREHASH_RegionInfo, _PREHASH_EstateID, estate_id); - msg->getU32Fast(_PREHASH_RegionInfo, _PREHASH_ParentEstateID, parent_estate_id); - msg->getU8Fast(_PREHASH_RegionInfo, _PREHASH_SimAccess, sim_access); - msg->getU8Fast(_PREHASH_RegionInfo, _PREHASH_MaxAgents, agent_limit); - msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_ObjectBonusFactor, object_bonus_factor); - msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_BillableFactor, billable_factor); - msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_WaterHeight, water_height); - - if (msg->has(_PREHASH_RegionInfo3)) - { - msg->getU64Fast(_PREHASH_RegionInfo3, _PREHASH_RegionFlagsExtended, region_flags); - } - else - { - U32 flags = 0; - msg->getU32Fast(_PREHASH_RegionInfo, _PREHASH_RegionFlags, flags); - region_flags = flags; - } - - if (msg->has(_PREHASH_RegionInfo5)) - { - F32 chat_whisper_range; - F32 chat_normal_range; - F32 chat_shout_range; - F32 chat_whisper_offset; - F32 chat_normal_offset; - F32 chat_shout_offset; - U32 chat_flags; - - msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatWhisperRange, chat_whisper_range); - msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatNormalRange, chat_normal_range); - msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatShoutRange, chat_shout_range); - msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatWhisperOffset, chat_whisper_offset); - msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatNormalOffset, chat_normal_offset); - msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatShoutOffset, chat_shout_offset); - msg->getU32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatFlags, chat_flags); - - LL_INFOS() << "Whisper range: " << chat_whisper_range << " normal range: " << chat_normal_range << " shout range: " << chat_shout_range - << " whisper offset: " << chat_whisper_offset << " normal offset: " << chat_normal_offset << " shout offset: " << chat_shout_offset - << " chat flags: " << chat_flags << LL_ENDL; - } - - if (host != gAgent.getRegionHost()) - { - // Update is for a different region than the one we're in. - // Just check for a waterheight change. - LLWorld::getInstance()->waterHeightRegionInfo(sim_name, water_height); - return; - } - - msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_TerrainRaiseLimit, terrain_raise_limit); - msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_TerrainLowerLimit, terrain_lower_limit); - msg->getS32Fast(_PREHASH_RegionInfo, _PREHASH_PricePerMeter, price_per_meter); - msg->getS32Fast(_PREHASH_RegionInfo, _PREHASH_RedirectGridX, redirect_grid_x); - msg->getS32Fast(_PREHASH_RegionInfo, _PREHASH_RedirectGridY, redirect_grid_y); - - // push values to the current LLViewerRegion - LLViewerRegion *regionp = gAgent.getRegion(); - if (regionp) - { - regionp->setRegionNameAndZone(sim_name); - regionp->setRegionFlags(region_flags); - regionp->setSimAccess(sim_access); - regionp->setWaterHeight(water_height); - regionp->setBillableFactor(billable_factor); - } - - LLFloaterGodTools* god_tools = LLFloaterReg::getTypedInstance("god_tools"); - if (!god_tools) return; - - // push values to god tools, if available - if ( gAgent.isGodlike() - && LLFloaterReg::instanceVisible("god_tools") - && god_tools->mPanelRegionTools - && god_tools->mPanelObjectTools) - { - LLPanelRegionTools* rtool = god_tools->mPanelRegionTools; - god_tools->mCurrentHost = host; - - // store locally - rtool->setSimName(sim_name); - rtool->setEstateID(estate_id); - rtool->setParentEstateID(parent_estate_id); - rtool->setCheckFlags(region_flags); - rtool->setBillableFactor(billable_factor); - rtool->setPricePerMeter(price_per_meter); - rtool->setRedirectGridX(redirect_grid_x); - rtool->setRedirectGridY(redirect_grid_y); - rtool->enableAllWidgets(); - - LLPanelObjectTools *otool = god_tools->mPanelObjectTools; - otool->setCheckFlags(region_flags); - otool->enableAllWidgets(); - - LLViewerRegion *regionp = gAgent.getRegion(); - if ( !regionp ) - { - // -1 implies non-existent - rtool->setGridPosX(-1); - rtool->setGridPosY(-1); - } - else - { - //compute the grid position of the region - LLVector3d global_pos = regionp->getPosGlobalFromRegion(LLVector3::zero); - S32 grid_pos_x = (S32) (global_pos.mdV[VX] / 256.0f); - S32 grid_pos_y = (S32) (global_pos.mdV[VY] / 256.0f); - - rtool->setGridPosX(grid_pos_x); - rtool->setGridPosY(grid_pos_y); - } - } -} - - -void LLFloaterGodTools::sendRegionInfoRequest() -{ - if (mPanelRegionTools) mPanelRegionTools->clearAllWidgets(); - if (mPanelObjectTools) mPanelObjectTools->clearAllWidgets(); - mCurrentHost = LLHost(); - mUpdateTimer.reset(); - - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("RequestRegionInfo"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - gAgent.sendReliableMessage(); -} - - -void LLFloaterGodTools::sendGodUpdateRegionInfo() -{ - LLFloaterGodTools* god_tools = LLFloaterReg::getTypedInstance("god_tools"); - if (!god_tools) return; - - LLViewerRegion *regionp = gAgent.getRegion(); - if (gAgent.isGodlike() - && god_tools->mPanelRegionTools - && regionp - && gAgent.getRegionHost() == mCurrentHost) - { - LLMessageSystem *msg = gMessageSystem; - LLPanelRegionTools *rtool = god_tools->mPanelRegionTools; - - U64 region_flags = computeRegionFlags(); - msg->newMessage("GodUpdateRegionInfo"); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_RegionInfo); - msg->addStringFast(_PREHASH_SimName, rtool->getSimName()); - msg->addU32Fast(_PREHASH_EstateID, rtool->getEstateID()); - msg->addU32Fast(_PREHASH_ParentEstateID, rtool->getParentEstateID()); - // Legacy flags - msg->addU32Fast(_PREHASH_RegionFlags, U32(region_flags)); - msg->addF32Fast(_PREHASH_BillableFactor, rtool->getBillableFactor()); - msg->addS32Fast(_PREHASH_PricePerMeter, rtool->getPricePerMeter()); - msg->addS32Fast(_PREHASH_RedirectGridX, rtool->getRedirectGridX()); - msg->addS32Fast(_PREHASH_RedirectGridY, rtool->getRedirectGridY()); - msg->nextBlockFast(_PREHASH_RegionInfo2); - msg->addU64Fast(_PREHASH_RegionFlagsExtended, region_flags); - - gAgent.sendReliableMessage(); - } -} - -//***************************************************************************** -// LLPanelRegionTools -//***************************************************************************** - - -// || Region |______________________________________ -// | | -// | Sim Name: [________________________________] | -// | ^ ^ | -// | LEFT R1 Estate id: [----] | -// | Parent id: [----] | -// | [X] Prelude Grid Pos: [--] [--] | -// | [X] Visible Redirect Pos: [--] [--] | -// | [X] Damage Bill Factor [8_______] | -// | [X] Block Terraform PricePerMeter[8_______] | -// | [Apply] | -// | | -// | [Bake Terrain] [Select Region] | -// | [Revert Terrain] [Autosave Now] | -// | [Swap Terrain] | -// | | -// |________________________________________________| -// ^ ^ ^ -// LEFT R2 RIGHT - - -// Floats because spinners only support floats. JC -const F32 BILLABLE_FACTOR_DEFAULT = 1; - -// floats because spinners only understand floats. JC -const F32 PRICE_PER_METER_DEFAULT = 1.f; - -LLPanelRegionTools::LLPanelRegionTools() -: LLPanel() -{ - mCommitCallbackRegistrar.add("RegionTools.ChangeAnything", boost::bind(&LLPanelRegionTools::onChangeAnything, this)); - mCommitCallbackRegistrar.add("RegionTools.ChangePrelude", boost::bind(&LLPanelRegionTools::onChangePrelude, this)); - mCommitCallbackRegistrar.add("RegionTools.BakeTerrain", boost::bind(&LLPanelRegionTools::onBakeTerrain, this)); - mCommitCallbackRegistrar.add("RegionTools.RevertTerrain", boost::bind(&LLPanelRegionTools::onRevertTerrain, this)); - mCommitCallbackRegistrar.add("RegionTools.SwapTerrain", boost::bind(&LLPanelRegionTools::onSwapTerrain, this)); - mCommitCallbackRegistrar.add("RegionTools.Refresh", boost::bind(&LLPanelRegionTools::onRefresh, this)); - mCommitCallbackRegistrar.add("RegionTools.ApplyChanges", boost::bind(&LLPanelRegionTools::onApplyChanges, this)); - mCommitCallbackRegistrar.add("RegionTools.SelectRegion", boost::bind(&LLPanelRegionTools::onSelectRegion, this)); - mCommitCallbackRegistrar.add("RegionTools.SaveState", boost::bind(&LLPanelRegionTools::onSaveState, this)); -} - -bool LLPanelRegionTools::postBuild() -{ - getChild("region name")->setKeystrokeCallback(onChangeSimName, this); - getChild("region name")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); - getChild("estate")->setPrevalidate(&LLTextValidate::validatePositiveS32); - getChild("parentestate")->setPrevalidate(&LLTextValidate::validatePositiveS32); - getChildView("parentestate")->setEnabled(false); - getChild("gridposx")->setPrevalidate(&LLTextValidate::validatePositiveS32); - getChildView("gridposx")->setEnabled(false); - getChild("gridposy")->setPrevalidate(&LLTextValidate::validatePositiveS32); - getChildView("gridposy")->setEnabled(false); - - getChild("redirectx")->setPrevalidate(&LLTextValidate::validatePositiveS32); - getChild("redirecty")->setPrevalidate(&LLTextValidate::validatePositiveS32); - - return true; -} - -// Destroys the object -LLPanelRegionTools::~LLPanelRegionTools() -{ - // base class will take care of everything -} - -U64 LLPanelRegionTools::computeRegionFlags(U64 flags) const -{ - flags &= getRegionFlagsMask(); - flags |= getRegionFlags(); - return flags; -} - - -void LLPanelRegionTools::refresh() -{ -} - - -void LLPanelRegionTools::clearAllWidgets() -{ - // clear all widgets - getChild("region name")->setValue("unknown"); - getChild("region name")->setFocus( false); - - getChild("check prelude")->setValue(false); - getChildView("check prelude")->setEnabled(false); - - getChild("check fixed sun")->setValue(false); - getChildView("check fixed sun")->setEnabled(false); - - getChild("check reset home")->setValue(false); - getChildView("check reset home")->setEnabled(false); - - getChild("check damage")->setValue(false); - getChildView("check damage")->setEnabled(false); - - getChild("check visible")->setValue(false); - getChildView("check visible")->setEnabled(false); - - getChild("block terraform")->setValue(false); - getChildView("block terraform")->setEnabled(false); - - getChild("block dwell")->setValue(false); - getChildView("block dwell")->setEnabled(false); - - getChild("is sandbox")->setValue(false); - getChildView("is sandbox")->setEnabled(false); - - getChild("billable factor")->setValue(BILLABLE_FACTOR_DEFAULT); - getChildView("billable factor")->setEnabled(false); - - getChild("land cost")->setValue(PRICE_PER_METER_DEFAULT); - getChildView("land cost")->setEnabled(false); - - getChildView("Apply")->setEnabled(false); - getChildView("Bake Terrain")->setEnabled(false); - getChildView("Autosave now")->setEnabled(false); -} - - -void LLPanelRegionTools::enableAllWidgets() -{ - // enable all of the widgets - - getChildView("check prelude")->setEnabled(true); - getChildView("check fixed sun")->setEnabled(true); - getChildView("check reset home")->setEnabled(true); - getChildView("check damage")->setEnabled(true); - getChildView("check visible")->setEnabled(false); // use estates to update... - getChildView("block terraform")->setEnabled(true); - getChildView("block dwell")->setEnabled(true); - getChildView("is sandbox")->setEnabled(true); - - getChildView("billable factor")->setEnabled(true); - getChildView("land cost")->setEnabled(true); - - getChildView("Apply")->setEnabled(false); // don't enable this one - getChildView("Bake Terrain")->setEnabled(true); - getChildView("Autosave now")->setEnabled(true); -} - -void LLPanelRegionTools::onSaveState(void* userdata) -{ - if (gAgent.isGodlike()) - { - // Send message to save world state - gMessageSystem->newMessageFast(_PREHASH_StateSave); - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - gMessageSystem->nextBlockFast(_PREHASH_DataBlock); - gMessageSystem->addStringFast(_PREHASH_Filename, NULL); - gAgent.sendReliableMessage(); - } -} - -const std::string LLPanelRegionTools::getSimName() const -{ - return getChild("region name")->getValue(); -} - -U32 LLPanelRegionTools::getEstateID() const -{ - U32 id = (U32)getChild("estate")->getValue().asInteger(); - return id; -} - -U32 LLPanelRegionTools::getParentEstateID() const -{ - U32 id = (U32)getChild("parentestate")->getValue().asInteger(); - return id; -} - -S32 LLPanelRegionTools::getRedirectGridX() const -{ - return getChild("redirectx")->getValue().asInteger(); -} - -S32 LLPanelRegionTools::getRedirectGridY() const -{ - return getChild("redirecty")->getValue().asInteger(); -} - -S32 LLPanelRegionTools::getGridPosX() const -{ - return getChild("gridposx")->getValue().asInteger(); -} - -S32 LLPanelRegionTools::getGridPosY() const -{ - return getChild("gridposy")->getValue().asInteger(); -} - -U64 LLPanelRegionTools::getRegionFlags() const -{ - U64 flags = 0x0; - flags = getChild("check prelude")->getValue().asBoolean() - ? set_prelude_flags(flags) - : unset_prelude_flags(flags); - - // override prelude - if (getChild("check fixed sun")->getValue().asBoolean()) - { - flags |= REGION_FLAGS_SUN_FIXED; - } - if (getChild("check reset home")->getValue().asBoolean()) - { - flags |= REGION_FLAGS_RESET_HOME_ON_TELEPORT; - } - if (getChild("check visible")->getValue().asBoolean()) - { - flags |= REGION_FLAGS_EXTERNALLY_VISIBLE; - } - if (getChild("check damage")->getValue().asBoolean()) - { - flags |= REGION_FLAGS_ALLOW_DAMAGE; - } - if (getChild("block terraform")->getValue().asBoolean()) - { - flags |= REGION_FLAGS_BLOCK_TERRAFORM; - } - if (getChild("block dwell")->getValue().asBoolean()) - { - flags |= REGION_FLAGS_BLOCK_DWELL; - } - if (getChild("is sandbox")->getValue().asBoolean()) - { - flags |= REGION_FLAGS_SANDBOX; - } - return flags; -} - -U64 LLPanelRegionTools::getRegionFlagsMask() const -{ - U64 flags = 0xFFFFFFFFFFFFFFFFULL; - flags = getChild("check prelude")->getValue().asBoolean() - ? set_prelude_flags(flags) - : unset_prelude_flags(flags); - - if (!getChild("check fixed sun")->getValue().asBoolean()) - { - flags &= ~REGION_FLAGS_SUN_FIXED; - } - if (!getChild("check reset home")->getValue().asBoolean()) - { - flags &= ~REGION_FLAGS_RESET_HOME_ON_TELEPORT; - } - if (!getChild("check visible")->getValue().asBoolean()) - { - flags &= ~REGION_FLAGS_EXTERNALLY_VISIBLE; - } - if (!getChild("check damage")->getValue().asBoolean()) - { - flags &= ~REGION_FLAGS_ALLOW_DAMAGE; - } - if (!getChild("block terraform")->getValue().asBoolean()) - { - flags &= ~REGION_FLAGS_BLOCK_TERRAFORM; - } - if (!getChild("block dwell")->getValue().asBoolean()) - { - flags &= ~REGION_FLAGS_BLOCK_DWELL; - } - if (!getChild("is sandbox")->getValue().asBoolean()) - { - flags &= ~REGION_FLAGS_SANDBOX; - } - return flags; -} - -F32 LLPanelRegionTools::getBillableFactor() const -{ - return (F32)getChild("billable factor")->getValue().asReal(); -} - -S32 LLPanelRegionTools::getPricePerMeter() const -{ - return getChild("land cost")->getValue(); -} - -void LLPanelRegionTools::setSimName(const std::string& name) -{ - getChild("region name")->setValue(name); -} - -void LLPanelRegionTools::setEstateID(U32 id) -{ - getChild("estate")->setValue((S32)id); -} - -void LLPanelRegionTools::setGridPosX(S32 pos) -{ - getChild("gridposx")->setValue(pos); -} - -void LLPanelRegionTools::setGridPosY(S32 pos) -{ - getChild("gridposy")->setValue(pos); -} - -void LLPanelRegionTools::setRedirectGridX(S32 pos) -{ - getChild("redirectx")->setValue(pos); -} - -void LLPanelRegionTools::setRedirectGridY(S32 pos) -{ - getChild("redirecty")->setValue(pos); -} - -void LLPanelRegionTools::setParentEstateID(U32 id) -{ - getChild("parentestate")->setValue((S32)id); -} - -void LLPanelRegionTools::setCheckFlags(U64 flags) -{ - getChild("check prelude")->setValue(is_prelude(flags)); - getChild("check fixed sun")->setValue(is_flag_set(flags, REGION_FLAGS_SUN_FIXED)); - getChild("check reset home")->setValue(is_flag_set(flags, REGION_FLAGS_RESET_HOME_ON_TELEPORT)); - getChild("check damage")->setValue(is_flag_set(flags, REGION_FLAGS_ALLOW_DAMAGE)); - getChild("check visible")->setValue(is_flag_set(flags, REGION_FLAGS_EXTERNALLY_VISIBLE)); - getChild("block terraform")->setValue(is_flag_set(flags, REGION_FLAGS_BLOCK_TERRAFORM)); - getChild("block dwell")->setValue(is_flag_set(flags, REGION_FLAGS_BLOCK_DWELL)); - getChild("is sandbox")->setValue(is_flag_set(flags, REGION_FLAGS_SANDBOX)); -} - -void LLPanelRegionTools::setBillableFactor(F32 billable_factor) -{ - getChild("billable factor")->setValue(billable_factor); -} - -void LLPanelRegionTools::setPricePerMeter(S32 price) -{ - getChild("land cost")->setValue(price); -} - -void LLPanelRegionTools::onChangeAnything() -{ - if (gAgent.isGodlike()) - { - getChildView("Apply")->setEnabled(true); - } -} - -void LLPanelRegionTools::onChangePrelude() -{ - // checking prelude auto-checks fixed sun - if (getChild("check prelude")->getValue().asBoolean()) - { - getChild("check fixed sun")->setValue(true); - getChild("check reset home")->setValue(true); - onChangeAnything(); - } - // pass on to default onChange handler - -} - -// static -void LLPanelRegionTools::onChangeSimName(LLLineEditor* caller, void* userdata ) -{ - if (userdata && gAgent.isGodlike()) - { - LLPanelRegionTools* region_tools = (LLPanelRegionTools*) userdata; - region_tools->getChildView("Apply")->setEnabled(true); - } -} - - -void LLPanelRegionTools::onRefresh() -{ - LLFloaterGodTools* god_tools = LLFloaterReg::getTypedInstance("god_tools"); - if(!god_tools) return; - LLViewerRegion *region = gAgent.getRegion(); - if (region && gAgent.isGodlike()) - { - god_tools->sendRegionInfoRequest(); - //LLFloaterGodTools::getInstance()->sendRegionInfoRequest(); - //LLFloaterReg::getTypedInstance("god_tools")->sendRegionInfoRequest(); - } -} - -void LLPanelRegionTools::onApplyChanges() -{ - LLFloaterGodTools* god_tools = LLFloaterReg::getTypedInstance("god_tools"); - if(!god_tools) return; - LLViewerRegion *region = gAgent.getRegion(); - if (region && gAgent.isGodlike()) - { - getChildView("Apply")->setEnabled(false); - god_tools->sendGodUpdateRegionInfo(); - //LLFloaterReg::getTypedInstance("god_tools")->sendGodUpdateRegionInfo(); - } -} - -void LLPanelRegionTools::onBakeTerrain() -{ - LLPanelRequestTools::sendRequest("terrain", "bake", gAgent.getRegionHost()); -} - -void LLPanelRegionTools::onRevertTerrain() -{ - LLPanelRequestTools::sendRequest("terrain", "revert", gAgent.getRegionHost()); -} - - -void LLPanelRegionTools::onSwapTerrain() -{ - LLPanelRequestTools::sendRequest("terrain", "swap", gAgent.getRegionHost()); -} - -void LLPanelRegionTools::onSelectRegion() -{ - LL_INFOS() << "LLPanelRegionTools::onSelectRegion" << LL_ENDL; - - LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromPosGlobal(gAgent.getPositionGlobal()); - if (!regionp) - { - return; - } - - LLVector3d north_east(REGION_WIDTH_METERS, REGION_WIDTH_METERS, 0); - LLViewerParcelMgr::getInstance()->selectLand(regionp->getOriginGlobal(), - regionp->getOriginGlobal() + north_east, false); - -} - - -//***************************************************************************** -// Class LLPanelGridTools -//***************************************************************************** - -// || Grid |_____________________________________ -// | | -// | | -// | Sun Phase: >--------[]---------< [________] | -// | | -// | ^ ^ | -// | LEFT R1 | -// | | -// | [Kick all users] | -// | | -// | | -// | | -// | | -// | | -// |_______________________________________________| -// ^ ^ ^ -// LEFT R2 RIGHT - -LLPanelGridTools::LLPanelGridTools() : - LLPanel() -{ - mCommitCallbackRegistrar.add("GridTools.FlushMapVisibilityCaches", boost::bind(&LLPanelGridTools::onClickFlushMapVisibilityCaches, this)); -} - -// Destroys the object -LLPanelGridTools::~LLPanelGridTools() -{ -} - -bool LLPanelGridTools::postBuild() -{ - return true; -} - -void LLPanelGridTools::refresh() -{ -} - -void LLPanelGridTools::onClickFlushMapVisibilityCaches() -{ - LLNotificationsUtil::add("FlushMapVisibilityCaches", LLSD(), LLSD(), flushMapVisibilityCachesConfirm); -} - -// static -bool LLPanelGridTools::flushMapVisibilityCachesConfirm(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option != 0) return false; - - // HACK: Send this as an EstateOwnerRequest so it gets routed - // correctly by the spaceserver. JC - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("EstateOwnerMessage"); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addUUIDFast(_PREHASH_TransactionID, LLUUID::null); //not used - msg->nextBlock("MethodData"); - msg->addString("Method", "refreshmapvisibility"); - msg->addUUID("Invoice", LLUUID::null); - msg->nextBlock("ParamList"); - msg->addString("Parameter", gAgent.getID().asString()); - gAgent.sendReliableMessage(); - return false; -} - - -//***************************************************************************** -// LLPanelObjectTools -//***************************************************************************** - - -// || Object |_______________________________________________________ -// | | -// | Sim Name: Foo | -// | ^ ^ | -// | LEFT R1 | -// | | -// | [X] Disable Scripts [X] Disable Collisions [X] Disable Physics | -// | [ Apply ] | -// | | -// | [Set Target Avatar] Avatar Name | -// | [Delete Target's Objects on Public Land ] | -// | [Delete All Target's Objects ] | -// | [Delete All Scripted Objects on Public Land] | -// | [Get Top Colliders ] | -// | [Get Top Scripts ] | -// |_________________________________________________________________| -// ^ ^ -// LEFT RIGHT - -// Default constructor -LLPanelObjectTools::LLPanelObjectTools() - : LLPanel(), - mTargetAvatar() -{ - mCommitCallbackRegistrar.add("ObjectTools.ChangeAnything", boost::bind(&LLPanelObjectTools::onChangeAnything, this)); - mCommitCallbackRegistrar.add("ObjectTools.DeletePublicOwnedBy", boost::bind(&LLPanelObjectTools::onClickDeletePublicOwnedBy, this)); - mCommitCallbackRegistrar.add("ObjectTools.DeleteAllScriptedOwnedBy", boost::bind(&LLPanelObjectTools::onClickDeleteAllScriptedOwnedBy, this)); - mCommitCallbackRegistrar.add("ObjectTools.DeleteAllOwnedBy", boost::bind(&LLPanelObjectTools::onClickDeleteAllOwnedBy, this)); - mCommitCallbackRegistrar.add("ObjectTools.ApplyChanges", boost::bind(&LLPanelObjectTools::onApplyChanges, this)); - mCommitCallbackRegistrar.add("ObjectTools.Set", boost::bind(&LLPanelObjectTools::onClickSet, this)); - mCommitCallbackRegistrar.add("ObjectTools.GetTopColliders", boost::bind(&LLPanelObjectTools::onGetTopColliders, this)); - mCommitCallbackRegistrar.add("ObjectTools.GetTopScripts", boost::bind(&LLPanelObjectTools::onGetTopScripts, this)); - mCommitCallbackRegistrar.add("ObjectTools.GetScriptDigest", boost::bind(&LLPanelObjectTools::onGetScriptDigest, this)); -} - -// Destroys the object -LLPanelObjectTools::~LLPanelObjectTools() -{ - // base class will take care of everything -} - -bool LLPanelObjectTools::postBuild() -{ - refresh(); - return true; -} - -void LLPanelObjectTools::setTargetAvatar(const LLUUID &target_id) -{ - mTargetAvatar = target_id; - if (target_id.isNull()) - { - getChild("target_avatar_name")->setValue(getString("no_target")); - } -} - - -void LLPanelObjectTools::refresh() -{ - LLViewerRegion *regionp = gAgent.getRegion(); - if (regionp) - { - getChild("region name")->setValue(regionp->getName()); - } -} - - -U64 LLPanelObjectTools::computeRegionFlags(U64 flags) const -{ - if (getChild("disable scripts")->getValue().asBoolean()) - { - flags |= REGION_FLAGS_SKIP_SCRIPTS; - } - else - { - flags &= ~REGION_FLAGS_SKIP_SCRIPTS; - } - if (getChild("disable collisions")->getValue().asBoolean()) - { - flags |= REGION_FLAGS_SKIP_COLLISIONS; - } - else - { - flags &= ~REGION_FLAGS_SKIP_COLLISIONS; - } - if (getChild("disable physics")->getValue().asBoolean()) - { - flags |= REGION_FLAGS_SKIP_PHYSICS; - } - else - { - flags &= ~REGION_FLAGS_SKIP_PHYSICS; - } - return flags; -} - - -void LLPanelObjectTools::setCheckFlags(U64 flags) -{ - getChild("disable scripts")->setValue(is_flag_set(flags, REGION_FLAGS_SKIP_SCRIPTS)); - getChild("disable collisions")->setValue(is_flag_set(flags, REGION_FLAGS_SKIP_COLLISIONS)); - getChild("disable physics")->setValue(is_flag_set(flags, REGION_FLAGS_SKIP_PHYSICS)); -} - - -void LLPanelObjectTools::clearAllWidgets() -{ - getChild("disable scripts")->setValue(false); - getChildView("disable scripts")->setEnabled(false); - - getChildView("Apply")->setEnabled(false); - getChildView("Set Target")->setEnabled(false); - getChildView("Delete Target's Scripted Objects On Others Land")->setEnabled(false); - getChildView("Delete Target's Scripted Objects On *Any* Land")->setEnabled(false); - getChildView("Delete *ALL* Of Target's Objects")->setEnabled(false); -} - - -void LLPanelObjectTools::enableAllWidgets() -{ - getChildView("disable scripts")->setEnabled(true); - - getChildView("Apply")->setEnabled(false); // don't enable this one - getChildView("Set Target")->setEnabled(true); - getChildView("Delete Target's Scripted Objects On Others Land")->setEnabled(true); - getChildView("Delete Target's Scripted Objects On *Any* Land")->setEnabled(true); - getChildView("Delete *ALL* Of Target's Objects")->setEnabled(true); - getChildView("Get Top Colliders")->setEnabled(true); - getChildView("Get Top Scripts")->setEnabled(true); -} - - -void LLPanelObjectTools::onGetTopColliders() -{ - LLFloaterTopObjects* instance = LLFloaterReg::getTypedInstance("top_objects"); - if(!instance) return; - - if (gAgent.isGodlike()) - { - LLFloaterReg::showInstance("top_objects"); - LLFloaterTopObjects::setMode(STAT_REPORT_TOP_COLLIDERS); - instance->onRefresh(); - } -} - -void LLPanelObjectTools::onGetTopScripts() -{ - LLFloaterTopObjects* instance = LLFloaterReg::getTypedInstance("top_objects"); - if(!instance) return; - - if (gAgent.isGodlike()) - { - LLFloaterReg::showInstance("top_objects"); - LLFloaterTopObjects::setMode(STAT_REPORT_TOP_SCRIPTS); - instance->onRefresh(); - } -} - -void LLPanelObjectTools::onGetScriptDigest() -{ - if (gAgent.isGodlike()) - { - // get the list of scripts and number of occurences of each - // (useful for finding self-replicating objects) - LLPanelRequestTools::sendRequest("scriptdigest","0",gAgent.getRegionHost()); - } -} - -void LLPanelObjectTools::onClickDeletePublicOwnedBy() -{ - // Bring up view-modal dialog - - if (!mTargetAvatar.isNull()) - { - mSimWideDeletesFlags = - SWD_SCRIPTED_ONLY | SWD_OTHERS_LAND_ONLY; - - LLSD args; - args["AVATAR_NAME"] = getChild("target_avatar_name")->getValue().asString(); - LLSD payload; - payload["avatar_id"] = mTargetAvatar; - payload["flags"] = (S32)mSimWideDeletesFlags; - - LLNotificationsUtil::add( "GodDeleteAllScriptedPublicObjectsByUser", - args, - payload, - callbackSimWideDeletes); - } -} - -void LLPanelObjectTools::onClickDeleteAllScriptedOwnedBy() -{ - // Bring up view-modal dialog - if (!mTargetAvatar.isNull()) - { - mSimWideDeletesFlags = SWD_SCRIPTED_ONLY; - - LLSD args; - args["AVATAR_NAME"] = getChild("target_avatar_name")->getValue().asString(); - LLSD payload; - payload["avatar_id"] = mTargetAvatar; - payload["flags"] = (S32)mSimWideDeletesFlags; - - LLNotificationsUtil::add( "GodDeleteAllScriptedObjectsByUser", - args, - payload, - callbackSimWideDeletes); - } -} - -void LLPanelObjectTools::onClickDeleteAllOwnedBy() -{ - // Bring up view-modal dialog - if (!mTargetAvatar.isNull()) - { - mSimWideDeletesFlags = 0; - - LLSD args; - args["AVATAR_NAME"] = getChild("target_avatar_name")->getValue().asString(); - LLSD payload; - payload["avatar_id"] = mTargetAvatar; - payload["flags"] = (S32)mSimWideDeletesFlags; - - LLNotificationsUtil::add( "GodDeleteAllObjectsByUser", - args, - payload, - callbackSimWideDeletes); - } -} - -// static -bool LLPanelObjectTools::callbackSimWideDeletes( const LLSD& notification, const LLSD& response ) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) - { - if (!notification["payload"]["avatar_id"].asUUID().isNull()) - { - send_sim_wide_deletes(notification["payload"]["avatar_id"].asUUID(), - notification["payload"]["flags"].asInteger()); - } - } - return false; -} - -void LLPanelObjectTools::onClickSet() -{ - LLView * button = findChild("Set Target"); - LLFloater * root_floater = gFloaterView->getParentFloater(this); - LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(boost::bind(&LLPanelObjectTools::callbackAvatarID, this, _1,_2), false, false, false, root_floater->getName(), button); - // grandparent is a floater, which can have a dependent - if (picker) - { - root_floater->addDependentFloater(picker); - } -} - -void LLPanelObjectTools::onClickSetBySelection(void* data) -{ - LLPanelObjectTools* panelp = (LLPanelObjectTools*) data; - if (!panelp) return; - - const bool non_root_ok = true; - LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(NULL, non_root_ok); - if (!node) return; - - std::string owner_name; - LLUUID owner_id; - LLSelectMgr::getInstance()->selectGetOwner(owner_id, owner_name); - - panelp->mTargetAvatar = owner_id; - LLStringUtil::format_map_t args; - args["[OBJECT]"] = node->mName; - args["[OWNER]"] = owner_name; - std::string name = LLTrans::getString("GodToolsObjectOwnedBy", args); - panelp->getChild("target_avatar_name")->setValue(name); -} - -void LLPanelObjectTools::callbackAvatarID(const uuid_vec_t& ids, const std::vector names) -{ - if (ids.empty() || names.empty()) return; - mTargetAvatar = ids[0]; - getChild("target_avatar_name")->setValue(names[0].getCompleteName()); - refresh(); -} - -void LLPanelObjectTools::onChangeAnything() -{ - if (gAgent.isGodlike()) - { - getChildView("Apply")->setEnabled(true); - } -} - -void LLPanelObjectTools::onApplyChanges() -{ - LLFloaterGodTools* god_tools = LLFloaterReg::getTypedInstance("god_tools"); - if(!god_tools) return; - LLViewerRegion *region = gAgent.getRegion(); - if (region && gAgent.isGodlike()) - { - // TODO -- implement this - getChildView("Apply")->setEnabled(false); - god_tools->sendGodUpdateRegionInfo(); - //LLFloaterReg::getTypedInstance("god_tools")->sendGodUpdateRegionInfo(); - } -} - - -// -------------------- -// LLPanelRequestTools -// -------------------- - -const std::string SELECTION = "Selection"; -const std::string AGENT_REGION = "Agent Region"; - -LLPanelRequestTools::LLPanelRequestTools(): - LLPanel() -{ - mCommitCallbackRegistrar.add("GodTools.Request", boost::bind(&LLPanelRequestTools::onClickRequest, this)); -} - -LLPanelRequestTools::~LLPanelRequestTools() -{ -} - -bool LLPanelRequestTools::postBuild() -{ - refresh(); - - return true; -} - -void LLPanelRequestTools::refresh() -{ - std::string buffer = getChild("destination")->getValue(); - LLCtrlListInterface *list = childGetListInterface("destination"); - if (!list) return; - - S32 last_item = list->getItemCount(); - - if (last_item >=3) - { - list->selectItemRange(2,last_item); - list->operateOnSelection(LLCtrlListInterface::OP_DELETE); - } - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) - { - LLViewerRegion* regionp = *iter; - std::string name = regionp->getName(); - if (!name.empty()) - { - list->addSimpleElement(name); - } - } - if(!buffer.empty()) - { - list->selectByValue(buffer); - } - else - { - list->operateOnSelection(LLCtrlListInterface::OP_DESELECT); - } -} - - -// static -void LLPanelRequestTools::sendRequest(const std::string& request, - const std::string& parameter, - const LLHost& host) -{ - LL_INFOS() << "Sending request '" << request << "', '" - << parameter << "' to " << host << LL_ENDL; - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("GodlikeMessage"); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addUUIDFast(_PREHASH_TransactionID, LLUUID::null); //not used - msg->nextBlock("MethodData"); - msg->addString("Method", request); - msg->addUUID("Invoice", LLUUID::null); - msg->nextBlock("ParamList"); - msg->addString("Parameter", parameter); - msg->sendReliable(host); -} - -void LLPanelRequestTools::onClickRequest() -{ - const std::string dest = getChild("destination")->getValue().asString(); - if(dest == SELECTION) - { - std::string req =getChild("request")->getValue(); - req = req.substr(0, req.find_first_of(" ")); - std::string param = getChild("parameter")->getValue(); - LLSelectMgr::getInstance()->sendGodlikeRequest(req, param); - } - else if(dest == AGENT_REGION) - { - sendRequest(gAgent.getRegionHost()); - } - else - { - // find region by name - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) - { - LLViewerRegion* regionp = *iter; - if(dest == regionp->getName()) - { - // found it - sendRequest(regionp->getHost()); - } - } - } -} - -void terrain_download_done(void** data, S32 status, LLExtStat ext_status) -{ - LLNotificationsUtil::add("TerrainDownloaded"); -} - - -void test_callback(const LLTSCode status) -{ - LL_INFOS() << "Test transfer callback returned!" << LL_ENDL; -} - - -void LLPanelRequestTools::sendRequest(const LLHost& host) -{ - - // intercept viewer local actions here - std::string req = getChild("request")->getValue(); - if (req == "terrain download") - { - gXferManager->requestFile(std::string("terrain.raw"), std::string("terrain.raw"), LL_PATH_NONE, - host, - false, - terrain_download_done, - NULL); - } - else - { - req = req.substr(0, req.find_first_of(" ")); - sendRequest(req, getChild("parameter")->getValue().asString(), host); - } -} - -// Flags are SWD_ flags. -void send_sim_wide_deletes(const LLUUID& owner_id, U32 flags) -{ - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_SimWideDeletes); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_DataBlock); - msg->addUUIDFast(_PREHASH_TargetID, owner_id); - msg->addU32Fast(_PREHASH_Flags, flags); - gAgent.sendReliableMessage(); -} +/** + * @file llfloatergodtools.cpp + * @brief The on-screen rectangle with tool options. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatergodtools.h" + +#include "llavatarnamecache.h" +#include "llcoord.h" +#include "llfontgl.h" +#include "llframetimer.h" +#include "llgl.h" +#include "llhost.h" +#include "llnotificationsutil.h" +#include "llregionflags.h" +#include "llstring.h" +#include "message.h" + +#include "llagent.h" +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "lldraghandle.h" +#include "llfloater.h" +#include "llfloaterreg.h" +#include "llfocusmgr.h" +#include "llfloatertopobjects.h" +#include "lllineeditor.h" +#include "llmenugl.h" +#include "llresmgr.h" +#include "llselectmgr.h" +#include "llsky.h" +#include "llspinctrl.h" +#include "llstatusbar.h" +#include "lltabcontainer.h" +#include "lltextbox.h" +#include "lluictrl.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" +#include "llworld.h" +#include "llfloateravatarpicker.h" +#include "llxfermanager.h" +#include "llvlcomposition.h" +#include "llsurface.h" +#include "llviewercontrol.h" +#include "lluictrlfactory.h" +#include "lltrans.h" + +#include "lltransfertargetfile.h" +#include "lltransfersourcefile.h" + +const F32 SECONDS_BETWEEN_UPDATE_REQUESTS = 5.0f; + +//***************************************************************************** +// LLFloaterGodTools +//***************************************************************************** + +void LLFloaterGodTools::onOpen(const LLSD& key) +{ + center(); + setFocus(true); +// LLPanel *panel = getChild("GodTools Tabs")->getCurrentPanel(); +// if (panel) +// panel->setFocus(true); + if (mPanelObjectTools) + mPanelObjectTools->setTargetAvatar(LLUUID::null); + + if (gAgent.getRegionHost() != mCurrentHost) + { + // we're in a new region + sendRegionInfoRequest(); + } +} + + +// static +void LLFloaterGodTools::refreshAll() +{ + LLFloaterGodTools* god_tools = LLFloaterReg::getTypedInstance("god_tools"); + if (god_tools) + { + if (gAgent.getRegionHost() != god_tools->mCurrentHost) + { + // we're in a new region + god_tools->sendRegionInfoRequest(); + } + } +} + + + +LLFloaterGodTools::LLFloaterGodTools(const LLSD& key) +: LLFloater(key), + mCurrentHost(LLHost()), + mUpdateTimer() +{ + mFactoryMap["grid"] = LLCallbackMap(createPanelGrid, this); + mFactoryMap["region"] = LLCallbackMap(createPanelRegion, this); + mFactoryMap["objects"] = LLCallbackMap(createPanelObjects, this); + mFactoryMap["request"] = LLCallbackMap(createPanelRequest, this); +} + +bool LLFloaterGodTools::postBuild() +{ + sendRegionInfoRequest(); + getChild("GodTools Tabs")->selectTabByName("region"); + return true; +} +// static +void* LLFloaterGodTools::createPanelGrid(void *userdata) +{ + return new LLPanelGridTools(); +} + +// static +void* LLFloaterGodTools::createPanelRegion(void *userdata) +{ + LLFloaterGodTools* self = (LLFloaterGodTools*)userdata; + self->mPanelRegionTools = new LLPanelRegionTools(); + return self->mPanelRegionTools; +} + +// static +void* LLFloaterGodTools::createPanelObjects(void *userdata) +{ + LLFloaterGodTools* self = (LLFloaterGodTools*)userdata; + self->mPanelObjectTools = new LLPanelObjectTools(); + return self->mPanelObjectTools; +} + +// static +void* LLFloaterGodTools::createPanelRequest(void *userdata) +{ + return new LLPanelRequestTools(); +} + +LLFloaterGodTools::~LLFloaterGodTools() +{ + // children automatically deleted +} + + +U64 LLFloaterGodTools::computeRegionFlags() const +{ + U64 flags = gAgent.getRegion()->getRegionFlags(); + if (mPanelRegionTools) flags = mPanelRegionTools->computeRegionFlags(flags); + if (mPanelObjectTools) flags = mPanelObjectTools->computeRegionFlags(flags); + return flags; +} + + +void LLFloaterGodTools::updatePopup(LLCoordGL center, MASK mask) +{ +} + +// virtual +void LLFloaterGodTools::draw() +{ + if (mCurrentHost == LLHost()) + { + if (mUpdateTimer.getElapsedTimeF32() > SECONDS_BETWEEN_UPDATE_REQUESTS) + { + sendRegionInfoRequest(); + } + } + else if (gAgent.getRegionHost() != mCurrentHost) + { + sendRegionInfoRequest(); + } + LLFloater::draw(); +} + +void LLFloaterGodTools::showPanel(const std::string& panel_name) +{ + getChild("GodTools Tabs")->selectTabByName(panel_name); + openFloater(); + LLPanel *panel = getChild("GodTools Tabs")->getCurrentPanel(); + if (panel) + panel->setFocus(true); +} + +// static +void LLFloaterGodTools::processRegionInfo(LLMessageSystem* msg) +{ + llassert(msg); + if (!msg) return; + + //const S32 SIM_NAME_BUF = 256; + U64 region_flags; + U8 sim_access; + U8 agent_limit; + std::string sim_name; + U32 estate_id; + U32 parent_estate_id; + F32 water_height; + F32 billable_factor; + F32 object_bonus_factor; + F32 terrain_raise_limit; + F32 terrain_lower_limit; + S32 price_per_meter; + S32 redirect_grid_x; + S32 redirect_grid_y; + LLUUID cache_id; + + LLHost host = msg->getSender(); + + msg->getStringFast(_PREHASH_RegionInfo, _PREHASH_SimName, sim_name); + msg->getU32Fast(_PREHASH_RegionInfo, _PREHASH_EstateID, estate_id); + msg->getU32Fast(_PREHASH_RegionInfo, _PREHASH_ParentEstateID, parent_estate_id); + msg->getU8Fast(_PREHASH_RegionInfo, _PREHASH_SimAccess, sim_access); + msg->getU8Fast(_PREHASH_RegionInfo, _PREHASH_MaxAgents, agent_limit); + msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_ObjectBonusFactor, object_bonus_factor); + msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_BillableFactor, billable_factor); + msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_WaterHeight, water_height); + + if (msg->has(_PREHASH_RegionInfo3)) + { + msg->getU64Fast(_PREHASH_RegionInfo3, _PREHASH_RegionFlagsExtended, region_flags); + } + else + { + U32 flags = 0; + msg->getU32Fast(_PREHASH_RegionInfo, _PREHASH_RegionFlags, flags); + region_flags = flags; + } + + if (msg->has(_PREHASH_RegionInfo5)) + { + F32 chat_whisper_range; + F32 chat_normal_range; + F32 chat_shout_range; + F32 chat_whisper_offset; + F32 chat_normal_offset; + F32 chat_shout_offset; + U32 chat_flags; + + msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatWhisperRange, chat_whisper_range); + msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatNormalRange, chat_normal_range); + msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatShoutRange, chat_shout_range); + msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatWhisperOffset, chat_whisper_offset); + msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatNormalOffset, chat_normal_offset); + msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatShoutOffset, chat_shout_offset); + msg->getU32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatFlags, chat_flags); + + LL_INFOS() << "Whisper range: " << chat_whisper_range << " normal range: " << chat_normal_range << " shout range: " << chat_shout_range + << " whisper offset: " << chat_whisper_offset << " normal offset: " << chat_normal_offset << " shout offset: " << chat_shout_offset + << " chat flags: " << chat_flags << LL_ENDL; + } + + if (host != gAgent.getRegionHost()) + { + // Update is for a different region than the one we're in. + // Just check for a waterheight change. + LLWorld::getInstance()->waterHeightRegionInfo(sim_name, water_height); + return; + } + + msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_TerrainRaiseLimit, terrain_raise_limit); + msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_TerrainLowerLimit, terrain_lower_limit); + msg->getS32Fast(_PREHASH_RegionInfo, _PREHASH_PricePerMeter, price_per_meter); + msg->getS32Fast(_PREHASH_RegionInfo, _PREHASH_RedirectGridX, redirect_grid_x); + msg->getS32Fast(_PREHASH_RegionInfo, _PREHASH_RedirectGridY, redirect_grid_y); + + // push values to the current LLViewerRegion + LLViewerRegion *regionp = gAgent.getRegion(); + if (regionp) + { + regionp->setRegionNameAndZone(sim_name); + regionp->setRegionFlags(region_flags); + regionp->setSimAccess(sim_access); + regionp->setWaterHeight(water_height); + regionp->setBillableFactor(billable_factor); + } + + LLFloaterGodTools* god_tools = LLFloaterReg::getTypedInstance("god_tools"); + if (!god_tools) return; + + // push values to god tools, if available + if ( gAgent.isGodlike() + && LLFloaterReg::instanceVisible("god_tools") + && god_tools->mPanelRegionTools + && god_tools->mPanelObjectTools) + { + LLPanelRegionTools* rtool = god_tools->mPanelRegionTools; + god_tools->mCurrentHost = host; + + // store locally + rtool->setSimName(sim_name); + rtool->setEstateID(estate_id); + rtool->setParentEstateID(parent_estate_id); + rtool->setCheckFlags(region_flags); + rtool->setBillableFactor(billable_factor); + rtool->setPricePerMeter(price_per_meter); + rtool->setRedirectGridX(redirect_grid_x); + rtool->setRedirectGridY(redirect_grid_y); + rtool->enableAllWidgets(); + + LLPanelObjectTools *otool = god_tools->mPanelObjectTools; + otool->setCheckFlags(region_flags); + otool->enableAllWidgets(); + + LLViewerRegion *regionp = gAgent.getRegion(); + if ( !regionp ) + { + // -1 implies non-existent + rtool->setGridPosX(-1); + rtool->setGridPosY(-1); + } + else + { + //compute the grid position of the region + LLVector3d global_pos = regionp->getPosGlobalFromRegion(LLVector3::zero); + S32 grid_pos_x = (S32) (global_pos.mdV[VX] / 256.0f); + S32 grid_pos_y = (S32) (global_pos.mdV[VY] / 256.0f); + + rtool->setGridPosX(grid_pos_x); + rtool->setGridPosY(grid_pos_y); + } + } +} + + +void LLFloaterGodTools::sendRegionInfoRequest() +{ + if (mPanelRegionTools) mPanelRegionTools->clearAllWidgets(); + if (mPanelObjectTools) mPanelObjectTools->clearAllWidgets(); + mCurrentHost = LLHost(); + mUpdateTimer.reset(); + + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("RequestRegionInfo"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + gAgent.sendReliableMessage(); +} + + +void LLFloaterGodTools::sendGodUpdateRegionInfo() +{ + LLFloaterGodTools* god_tools = LLFloaterReg::getTypedInstance("god_tools"); + if (!god_tools) return; + + LLViewerRegion *regionp = gAgent.getRegion(); + if (gAgent.isGodlike() + && god_tools->mPanelRegionTools + && regionp + && gAgent.getRegionHost() == mCurrentHost) + { + LLMessageSystem *msg = gMessageSystem; + LLPanelRegionTools *rtool = god_tools->mPanelRegionTools; + + U64 region_flags = computeRegionFlags(); + msg->newMessage("GodUpdateRegionInfo"); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_RegionInfo); + msg->addStringFast(_PREHASH_SimName, rtool->getSimName()); + msg->addU32Fast(_PREHASH_EstateID, rtool->getEstateID()); + msg->addU32Fast(_PREHASH_ParentEstateID, rtool->getParentEstateID()); + // Legacy flags + msg->addU32Fast(_PREHASH_RegionFlags, U32(region_flags)); + msg->addF32Fast(_PREHASH_BillableFactor, rtool->getBillableFactor()); + msg->addS32Fast(_PREHASH_PricePerMeter, rtool->getPricePerMeter()); + msg->addS32Fast(_PREHASH_RedirectGridX, rtool->getRedirectGridX()); + msg->addS32Fast(_PREHASH_RedirectGridY, rtool->getRedirectGridY()); + msg->nextBlockFast(_PREHASH_RegionInfo2); + msg->addU64Fast(_PREHASH_RegionFlagsExtended, region_flags); + + gAgent.sendReliableMessage(); + } +} + +//***************************************************************************** +// LLPanelRegionTools +//***************************************************************************** + + +// || Region |______________________________________ +// | | +// | Sim Name: [________________________________] | +// | ^ ^ | +// | LEFT R1 Estate id: [----] | +// | Parent id: [----] | +// | [X] Prelude Grid Pos: [--] [--] | +// | [X] Visible Redirect Pos: [--] [--] | +// | [X] Damage Bill Factor [8_______] | +// | [X] Block Terraform PricePerMeter[8_______] | +// | [Apply] | +// | | +// | [Bake Terrain] [Select Region] | +// | [Revert Terrain] [Autosave Now] | +// | [Swap Terrain] | +// | | +// |________________________________________________| +// ^ ^ ^ +// LEFT R2 RIGHT + + +// Floats because spinners only support floats. JC +const F32 BILLABLE_FACTOR_DEFAULT = 1; + +// floats because spinners only understand floats. JC +const F32 PRICE_PER_METER_DEFAULT = 1.f; + +LLPanelRegionTools::LLPanelRegionTools() +: LLPanel() +{ + mCommitCallbackRegistrar.add("RegionTools.ChangeAnything", boost::bind(&LLPanelRegionTools::onChangeAnything, this)); + mCommitCallbackRegistrar.add("RegionTools.ChangePrelude", boost::bind(&LLPanelRegionTools::onChangePrelude, this)); + mCommitCallbackRegistrar.add("RegionTools.BakeTerrain", boost::bind(&LLPanelRegionTools::onBakeTerrain, this)); + mCommitCallbackRegistrar.add("RegionTools.RevertTerrain", boost::bind(&LLPanelRegionTools::onRevertTerrain, this)); + mCommitCallbackRegistrar.add("RegionTools.SwapTerrain", boost::bind(&LLPanelRegionTools::onSwapTerrain, this)); + mCommitCallbackRegistrar.add("RegionTools.Refresh", boost::bind(&LLPanelRegionTools::onRefresh, this)); + mCommitCallbackRegistrar.add("RegionTools.ApplyChanges", boost::bind(&LLPanelRegionTools::onApplyChanges, this)); + mCommitCallbackRegistrar.add("RegionTools.SelectRegion", boost::bind(&LLPanelRegionTools::onSelectRegion, this)); + mCommitCallbackRegistrar.add("RegionTools.SaveState", boost::bind(&LLPanelRegionTools::onSaveState, this)); +} + +bool LLPanelRegionTools::postBuild() +{ + getChild("region name")->setKeystrokeCallback(onChangeSimName, this); + getChild("region name")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); + getChild("estate")->setPrevalidate(&LLTextValidate::validatePositiveS32); + getChild("parentestate")->setPrevalidate(&LLTextValidate::validatePositiveS32); + getChildView("parentestate")->setEnabled(false); + getChild("gridposx")->setPrevalidate(&LLTextValidate::validatePositiveS32); + getChildView("gridposx")->setEnabled(false); + getChild("gridposy")->setPrevalidate(&LLTextValidate::validatePositiveS32); + getChildView("gridposy")->setEnabled(false); + + getChild("redirectx")->setPrevalidate(&LLTextValidate::validatePositiveS32); + getChild("redirecty")->setPrevalidate(&LLTextValidate::validatePositiveS32); + + return true; +} + +// Destroys the object +LLPanelRegionTools::~LLPanelRegionTools() +{ + // base class will take care of everything +} + +U64 LLPanelRegionTools::computeRegionFlags(U64 flags) const +{ + flags &= getRegionFlagsMask(); + flags |= getRegionFlags(); + return flags; +} + + +void LLPanelRegionTools::refresh() +{ +} + + +void LLPanelRegionTools::clearAllWidgets() +{ + // clear all widgets + getChild("region name")->setValue("unknown"); + getChild("region name")->setFocus( false); + + getChild("check prelude")->setValue(false); + getChildView("check prelude")->setEnabled(false); + + getChild("check fixed sun")->setValue(false); + getChildView("check fixed sun")->setEnabled(false); + + getChild("check reset home")->setValue(false); + getChildView("check reset home")->setEnabled(false); + + getChild("check damage")->setValue(false); + getChildView("check damage")->setEnabled(false); + + getChild("check visible")->setValue(false); + getChildView("check visible")->setEnabled(false); + + getChild("block terraform")->setValue(false); + getChildView("block terraform")->setEnabled(false); + + getChild("block dwell")->setValue(false); + getChildView("block dwell")->setEnabled(false); + + getChild("is sandbox")->setValue(false); + getChildView("is sandbox")->setEnabled(false); + + getChild("billable factor")->setValue(BILLABLE_FACTOR_DEFAULT); + getChildView("billable factor")->setEnabled(false); + + getChild("land cost")->setValue(PRICE_PER_METER_DEFAULT); + getChildView("land cost")->setEnabled(false); + + getChildView("Apply")->setEnabled(false); + getChildView("Bake Terrain")->setEnabled(false); + getChildView("Autosave now")->setEnabled(false); +} + + +void LLPanelRegionTools::enableAllWidgets() +{ + // enable all of the widgets + + getChildView("check prelude")->setEnabled(true); + getChildView("check fixed sun")->setEnabled(true); + getChildView("check reset home")->setEnabled(true); + getChildView("check damage")->setEnabled(true); + getChildView("check visible")->setEnabled(false); // use estates to update... + getChildView("block terraform")->setEnabled(true); + getChildView("block dwell")->setEnabled(true); + getChildView("is sandbox")->setEnabled(true); + + getChildView("billable factor")->setEnabled(true); + getChildView("land cost")->setEnabled(true); + + getChildView("Apply")->setEnabled(false); // don't enable this one + getChildView("Bake Terrain")->setEnabled(true); + getChildView("Autosave now")->setEnabled(true); +} + +void LLPanelRegionTools::onSaveState(void* userdata) +{ + if (gAgent.isGodlike()) + { + // Send message to save world state + gMessageSystem->newMessageFast(_PREHASH_StateSave); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + gMessageSystem->nextBlockFast(_PREHASH_DataBlock); + gMessageSystem->addStringFast(_PREHASH_Filename, NULL); + gAgent.sendReliableMessage(); + } +} + +const std::string LLPanelRegionTools::getSimName() const +{ + return getChild("region name")->getValue(); +} + +U32 LLPanelRegionTools::getEstateID() const +{ + U32 id = (U32)getChild("estate")->getValue().asInteger(); + return id; +} + +U32 LLPanelRegionTools::getParentEstateID() const +{ + U32 id = (U32)getChild("parentestate")->getValue().asInteger(); + return id; +} + +S32 LLPanelRegionTools::getRedirectGridX() const +{ + return getChild("redirectx")->getValue().asInteger(); +} + +S32 LLPanelRegionTools::getRedirectGridY() const +{ + return getChild("redirecty")->getValue().asInteger(); +} + +S32 LLPanelRegionTools::getGridPosX() const +{ + return getChild("gridposx")->getValue().asInteger(); +} + +S32 LLPanelRegionTools::getGridPosY() const +{ + return getChild("gridposy")->getValue().asInteger(); +} + +U64 LLPanelRegionTools::getRegionFlags() const +{ + U64 flags = 0x0; + flags = getChild("check prelude")->getValue().asBoolean() + ? set_prelude_flags(flags) + : unset_prelude_flags(flags); + + // override prelude + if (getChild("check fixed sun")->getValue().asBoolean()) + { + flags |= REGION_FLAGS_SUN_FIXED; + } + if (getChild("check reset home")->getValue().asBoolean()) + { + flags |= REGION_FLAGS_RESET_HOME_ON_TELEPORT; + } + if (getChild("check visible")->getValue().asBoolean()) + { + flags |= REGION_FLAGS_EXTERNALLY_VISIBLE; + } + if (getChild("check damage")->getValue().asBoolean()) + { + flags |= REGION_FLAGS_ALLOW_DAMAGE; + } + if (getChild("block terraform")->getValue().asBoolean()) + { + flags |= REGION_FLAGS_BLOCK_TERRAFORM; + } + if (getChild("block dwell")->getValue().asBoolean()) + { + flags |= REGION_FLAGS_BLOCK_DWELL; + } + if (getChild("is sandbox")->getValue().asBoolean()) + { + flags |= REGION_FLAGS_SANDBOX; + } + return flags; +} + +U64 LLPanelRegionTools::getRegionFlagsMask() const +{ + U64 flags = 0xFFFFFFFFFFFFFFFFULL; + flags = getChild("check prelude")->getValue().asBoolean() + ? set_prelude_flags(flags) + : unset_prelude_flags(flags); + + if (!getChild("check fixed sun")->getValue().asBoolean()) + { + flags &= ~REGION_FLAGS_SUN_FIXED; + } + if (!getChild("check reset home")->getValue().asBoolean()) + { + flags &= ~REGION_FLAGS_RESET_HOME_ON_TELEPORT; + } + if (!getChild("check visible")->getValue().asBoolean()) + { + flags &= ~REGION_FLAGS_EXTERNALLY_VISIBLE; + } + if (!getChild("check damage")->getValue().asBoolean()) + { + flags &= ~REGION_FLAGS_ALLOW_DAMAGE; + } + if (!getChild("block terraform")->getValue().asBoolean()) + { + flags &= ~REGION_FLAGS_BLOCK_TERRAFORM; + } + if (!getChild("block dwell")->getValue().asBoolean()) + { + flags &= ~REGION_FLAGS_BLOCK_DWELL; + } + if (!getChild("is sandbox")->getValue().asBoolean()) + { + flags &= ~REGION_FLAGS_SANDBOX; + } + return flags; +} + +F32 LLPanelRegionTools::getBillableFactor() const +{ + return (F32)getChild("billable factor")->getValue().asReal(); +} + +S32 LLPanelRegionTools::getPricePerMeter() const +{ + return getChild("land cost")->getValue(); +} + +void LLPanelRegionTools::setSimName(const std::string& name) +{ + getChild("region name")->setValue(name); +} + +void LLPanelRegionTools::setEstateID(U32 id) +{ + getChild("estate")->setValue((S32)id); +} + +void LLPanelRegionTools::setGridPosX(S32 pos) +{ + getChild("gridposx")->setValue(pos); +} + +void LLPanelRegionTools::setGridPosY(S32 pos) +{ + getChild("gridposy")->setValue(pos); +} + +void LLPanelRegionTools::setRedirectGridX(S32 pos) +{ + getChild("redirectx")->setValue(pos); +} + +void LLPanelRegionTools::setRedirectGridY(S32 pos) +{ + getChild("redirecty")->setValue(pos); +} + +void LLPanelRegionTools::setParentEstateID(U32 id) +{ + getChild("parentestate")->setValue((S32)id); +} + +void LLPanelRegionTools::setCheckFlags(U64 flags) +{ + getChild("check prelude")->setValue(is_prelude(flags)); + getChild("check fixed sun")->setValue(is_flag_set(flags, REGION_FLAGS_SUN_FIXED)); + getChild("check reset home")->setValue(is_flag_set(flags, REGION_FLAGS_RESET_HOME_ON_TELEPORT)); + getChild("check damage")->setValue(is_flag_set(flags, REGION_FLAGS_ALLOW_DAMAGE)); + getChild("check visible")->setValue(is_flag_set(flags, REGION_FLAGS_EXTERNALLY_VISIBLE)); + getChild("block terraform")->setValue(is_flag_set(flags, REGION_FLAGS_BLOCK_TERRAFORM)); + getChild("block dwell")->setValue(is_flag_set(flags, REGION_FLAGS_BLOCK_DWELL)); + getChild("is sandbox")->setValue(is_flag_set(flags, REGION_FLAGS_SANDBOX)); +} + +void LLPanelRegionTools::setBillableFactor(F32 billable_factor) +{ + getChild("billable factor")->setValue(billable_factor); +} + +void LLPanelRegionTools::setPricePerMeter(S32 price) +{ + getChild("land cost")->setValue(price); +} + +void LLPanelRegionTools::onChangeAnything() +{ + if (gAgent.isGodlike()) + { + getChildView("Apply")->setEnabled(true); + } +} + +void LLPanelRegionTools::onChangePrelude() +{ + // checking prelude auto-checks fixed sun + if (getChild("check prelude")->getValue().asBoolean()) + { + getChild("check fixed sun")->setValue(true); + getChild("check reset home")->setValue(true); + onChangeAnything(); + } + // pass on to default onChange handler + +} + +// static +void LLPanelRegionTools::onChangeSimName(LLLineEditor* caller, void* userdata ) +{ + if (userdata && gAgent.isGodlike()) + { + LLPanelRegionTools* region_tools = (LLPanelRegionTools*) userdata; + region_tools->getChildView("Apply")->setEnabled(true); + } +} + + +void LLPanelRegionTools::onRefresh() +{ + LLFloaterGodTools* god_tools = LLFloaterReg::getTypedInstance("god_tools"); + if(!god_tools) return; + LLViewerRegion *region = gAgent.getRegion(); + if (region && gAgent.isGodlike()) + { + god_tools->sendRegionInfoRequest(); + //LLFloaterGodTools::getInstance()->sendRegionInfoRequest(); + //LLFloaterReg::getTypedInstance("god_tools")->sendRegionInfoRequest(); + } +} + +void LLPanelRegionTools::onApplyChanges() +{ + LLFloaterGodTools* god_tools = LLFloaterReg::getTypedInstance("god_tools"); + if(!god_tools) return; + LLViewerRegion *region = gAgent.getRegion(); + if (region && gAgent.isGodlike()) + { + getChildView("Apply")->setEnabled(false); + god_tools->sendGodUpdateRegionInfo(); + //LLFloaterReg::getTypedInstance("god_tools")->sendGodUpdateRegionInfo(); + } +} + +void LLPanelRegionTools::onBakeTerrain() +{ + LLPanelRequestTools::sendRequest("terrain", "bake", gAgent.getRegionHost()); +} + +void LLPanelRegionTools::onRevertTerrain() +{ + LLPanelRequestTools::sendRequest("terrain", "revert", gAgent.getRegionHost()); +} + + +void LLPanelRegionTools::onSwapTerrain() +{ + LLPanelRequestTools::sendRequest("terrain", "swap", gAgent.getRegionHost()); +} + +void LLPanelRegionTools::onSelectRegion() +{ + LL_INFOS() << "LLPanelRegionTools::onSelectRegion" << LL_ENDL; + + LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromPosGlobal(gAgent.getPositionGlobal()); + if (!regionp) + { + return; + } + + LLVector3d north_east(REGION_WIDTH_METERS, REGION_WIDTH_METERS, 0); + LLViewerParcelMgr::getInstance()->selectLand(regionp->getOriginGlobal(), + regionp->getOriginGlobal() + north_east, false); + +} + + +//***************************************************************************** +// Class LLPanelGridTools +//***************************************************************************** + +// || Grid |_____________________________________ +// | | +// | | +// | Sun Phase: >--------[]---------< [________] | +// | | +// | ^ ^ | +// | LEFT R1 | +// | | +// | [Kick all users] | +// | | +// | | +// | | +// | | +// | | +// |_______________________________________________| +// ^ ^ ^ +// LEFT R2 RIGHT + +LLPanelGridTools::LLPanelGridTools() : + LLPanel() +{ + mCommitCallbackRegistrar.add("GridTools.FlushMapVisibilityCaches", boost::bind(&LLPanelGridTools::onClickFlushMapVisibilityCaches, this)); +} + +// Destroys the object +LLPanelGridTools::~LLPanelGridTools() +{ +} + +bool LLPanelGridTools::postBuild() +{ + return true; +} + +void LLPanelGridTools::refresh() +{ +} + +void LLPanelGridTools::onClickFlushMapVisibilityCaches() +{ + LLNotificationsUtil::add("FlushMapVisibilityCaches", LLSD(), LLSD(), flushMapVisibilityCachesConfirm); +} + +// static +bool LLPanelGridTools::flushMapVisibilityCachesConfirm(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) return false; + + // HACK: Send this as an EstateOwnerRequest so it gets routed + // correctly by the spaceserver. JC + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("EstateOwnerMessage"); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addUUIDFast(_PREHASH_TransactionID, LLUUID::null); //not used + msg->nextBlock("MethodData"); + msg->addString("Method", "refreshmapvisibility"); + msg->addUUID("Invoice", LLUUID::null); + msg->nextBlock("ParamList"); + msg->addString("Parameter", gAgent.getID().asString()); + gAgent.sendReliableMessage(); + return false; +} + + +//***************************************************************************** +// LLPanelObjectTools +//***************************************************************************** + + +// || Object |_______________________________________________________ +// | | +// | Sim Name: Foo | +// | ^ ^ | +// | LEFT R1 | +// | | +// | [X] Disable Scripts [X] Disable Collisions [X] Disable Physics | +// | [ Apply ] | +// | | +// | [Set Target Avatar] Avatar Name | +// | [Delete Target's Objects on Public Land ] | +// | [Delete All Target's Objects ] | +// | [Delete All Scripted Objects on Public Land] | +// | [Get Top Colliders ] | +// | [Get Top Scripts ] | +// |_________________________________________________________________| +// ^ ^ +// LEFT RIGHT + +// Default constructor +LLPanelObjectTools::LLPanelObjectTools() + : LLPanel(), + mTargetAvatar() +{ + mCommitCallbackRegistrar.add("ObjectTools.ChangeAnything", boost::bind(&LLPanelObjectTools::onChangeAnything, this)); + mCommitCallbackRegistrar.add("ObjectTools.DeletePublicOwnedBy", boost::bind(&LLPanelObjectTools::onClickDeletePublicOwnedBy, this)); + mCommitCallbackRegistrar.add("ObjectTools.DeleteAllScriptedOwnedBy", boost::bind(&LLPanelObjectTools::onClickDeleteAllScriptedOwnedBy, this)); + mCommitCallbackRegistrar.add("ObjectTools.DeleteAllOwnedBy", boost::bind(&LLPanelObjectTools::onClickDeleteAllOwnedBy, this)); + mCommitCallbackRegistrar.add("ObjectTools.ApplyChanges", boost::bind(&LLPanelObjectTools::onApplyChanges, this)); + mCommitCallbackRegistrar.add("ObjectTools.Set", boost::bind(&LLPanelObjectTools::onClickSet, this)); + mCommitCallbackRegistrar.add("ObjectTools.GetTopColliders", boost::bind(&LLPanelObjectTools::onGetTopColliders, this)); + mCommitCallbackRegistrar.add("ObjectTools.GetTopScripts", boost::bind(&LLPanelObjectTools::onGetTopScripts, this)); + mCommitCallbackRegistrar.add("ObjectTools.GetScriptDigest", boost::bind(&LLPanelObjectTools::onGetScriptDigest, this)); +} + +// Destroys the object +LLPanelObjectTools::~LLPanelObjectTools() +{ + // base class will take care of everything +} + +bool LLPanelObjectTools::postBuild() +{ + refresh(); + return true; +} + +void LLPanelObjectTools::setTargetAvatar(const LLUUID &target_id) +{ + mTargetAvatar = target_id; + if (target_id.isNull()) + { + getChild("target_avatar_name")->setValue(getString("no_target")); + } +} + + +void LLPanelObjectTools::refresh() +{ + LLViewerRegion *regionp = gAgent.getRegion(); + if (regionp) + { + getChild("region name")->setValue(regionp->getName()); + } +} + + +U64 LLPanelObjectTools::computeRegionFlags(U64 flags) const +{ + if (getChild("disable scripts")->getValue().asBoolean()) + { + flags |= REGION_FLAGS_SKIP_SCRIPTS; + } + else + { + flags &= ~REGION_FLAGS_SKIP_SCRIPTS; + } + if (getChild("disable collisions")->getValue().asBoolean()) + { + flags |= REGION_FLAGS_SKIP_COLLISIONS; + } + else + { + flags &= ~REGION_FLAGS_SKIP_COLLISIONS; + } + if (getChild("disable physics")->getValue().asBoolean()) + { + flags |= REGION_FLAGS_SKIP_PHYSICS; + } + else + { + flags &= ~REGION_FLAGS_SKIP_PHYSICS; + } + return flags; +} + + +void LLPanelObjectTools::setCheckFlags(U64 flags) +{ + getChild("disable scripts")->setValue(is_flag_set(flags, REGION_FLAGS_SKIP_SCRIPTS)); + getChild("disable collisions")->setValue(is_flag_set(flags, REGION_FLAGS_SKIP_COLLISIONS)); + getChild("disable physics")->setValue(is_flag_set(flags, REGION_FLAGS_SKIP_PHYSICS)); +} + + +void LLPanelObjectTools::clearAllWidgets() +{ + getChild("disable scripts")->setValue(false); + getChildView("disable scripts")->setEnabled(false); + + getChildView("Apply")->setEnabled(false); + getChildView("Set Target")->setEnabled(false); + getChildView("Delete Target's Scripted Objects On Others Land")->setEnabled(false); + getChildView("Delete Target's Scripted Objects On *Any* Land")->setEnabled(false); + getChildView("Delete *ALL* Of Target's Objects")->setEnabled(false); +} + + +void LLPanelObjectTools::enableAllWidgets() +{ + getChildView("disable scripts")->setEnabled(true); + + getChildView("Apply")->setEnabled(false); // don't enable this one + getChildView("Set Target")->setEnabled(true); + getChildView("Delete Target's Scripted Objects On Others Land")->setEnabled(true); + getChildView("Delete Target's Scripted Objects On *Any* Land")->setEnabled(true); + getChildView("Delete *ALL* Of Target's Objects")->setEnabled(true); + getChildView("Get Top Colliders")->setEnabled(true); + getChildView("Get Top Scripts")->setEnabled(true); +} + + +void LLPanelObjectTools::onGetTopColliders() +{ + LLFloaterTopObjects* instance = LLFloaterReg::getTypedInstance("top_objects"); + if(!instance) return; + + if (gAgent.isGodlike()) + { + LLFloaterReg::showInstance("top_objects"); + LLFloaterTopObjects::setMode(STAT_REPORT_TOP_COLLIDERS); + instance->onRefresh(); + } +} + +void LLPanelObjectTools::onGetTopScripts() +{ + LLFloaterTopObjects* instance = LLFloaterReg::getTypedInstance("top_objects"); + if(!instance) return; + + if (gAgent.isGodlike()) + { + LLFloaterReg::showInstance("top_objects"); + LLFloaterTopObjects::setMode(STAT_REPORT_TOP_SCRIPTS); + instance->onRefresh(); + } +} + +void LLPanelObjectTools::onGetScriptDigest() +{ + if (gAgent.isGodlike()) + { + // get the list of scripts and number of occurences of each + // (useful for finding self-replicating objects) + LLPanelRequestTools::sendRequest("scriptdigest","0",gAgent.getRegionHost()); + } +} + +void LLPanelObjectTools::onClickDeletePublicOwnedBy() +{ + // Bring up view-modal dialog + + if (!mTargetAvatar.isNull()) + { + mSimWideDeletesFlags = + SWD_SCRIPTED_ONLY | SWD_OTHERS_LAND_ONLY; + + LLSD args; + args["AVATAR_NAME"] = getChild("target_avatar_name")->getValue().asString(); + LLSD payload; + payload["avatar_id"] = mTargetAvatar; + payload["flags"] = (S32)mSimWideDeletesFlags; + + LLNotificationsUtil::add( "GodDeleteAllScriptedPublicObjectsByUser", + args, + payload, + callbackSimWideDeletes); + } +} + +void LLPanelObjectTools::onClickDeleteAllScriptedOwnedBy() +{ + // Bring up view-modal dialog + if (!mTargetAvatar.isNull()) + { + mSimWideDeletesFlags = SWD_SCRIPTED_ONLY; + + LLSD args; + args["AVATAR_NAME"] = getChild("target_avatar_name")->getValue().asString(); + LLSD payload; + payload["avatar_id"] = mTargetAvatar; + payload["flags"] = (S32)mSimWideDeletesFlags; + + LLNotificationsUtil::add( "GodDeleteAllScriptedObjectsByUser", + args, + payload, + callbackSimWideDeletes); + } +} + +void LLPanelObjectTools::onClickDeleteAllOwnedBy() +{ + // Bring up view-modal dialog + if (!mTargetAvatar.isNull()) + { + mSimWideDeletesFlags = 0; + + LLSD args; + args["AVATAR_NAME"] = getChild("target_avatar_name")->getValue().asString(); + LLSD payload; + payload["avatar_id"] = mTargetAvatar; + payload["flags"] = (S32)mSimWideDeletesFlags; + + LLNotificationsUtil::add( "GodDeleteAllObjectsByUser", + args, + payload, + callbackSimWideDeletes); + } +} + +// static +bool LLPanelObjectTools::callbackSimWideDeletes( const LLSD& notification, const LLSD& response ) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) + { + if (!notification["payload"]["avatar_id"].asUUID().isNull()) + { + send_sim_wide_deletes(notification["payload"]["avatar_id"].asUUID(), + notification["payload"]["flags"].asInteger()); + } + } + return false; +} + +void LLPanelObjectTools::onClickSet() +{ + LLView * button = findChild("Set Target"); + LLFloater * root_floater = gFloaterView->getParentFloater(this); + LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(boost::bind(&LLPanelObjectTools::callbackAvatarID, this, _1,_2), false, false, false, root_floater->getName(), button); + // grandparent is a floater, which can have a dependent + if (picker) + { + root_floater->addDependentFloater(picker); + } +} + +void LLPanelObjectTools::onClickSetBySelection(void* data) +{ + LLPanelObjectTools* panelp = (LLPanelObjectTools*) data; + if (!panelp) return; + + const bool non_root_ok = true; + LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(NULL, non_root_ok); + if (!node) return; + + std::string owner_name; + LLUUID owner_id; + LLSelectMgr::getInstance()->selectGetOwner(owner_id, owner_name); + + panelp->mTargetAvatar = owner_id; + LLStringUtil::format_map_t args; + args["[OBJECT]"] = node->mName; + args["[OWNER]"] = owner_name; + std::string name = LLTrans::getString("GodToolsObjectOwnedBy", args); + panelp->getChild("target_avatar_name")->setValue(name); +} + +void LLPanelObjectTools::callbackAvatarID(const uuid_vec_t& ids, const std::vector names) +{ + if (ids.empty() || names.empty()) return; + mTargetAvatar = ids[0]; + getChild("target_avatar_name")->setValue(names[0].getCompleteName()); + refresh(); +} + +void LLPanelObjectTools::onChangeAnything() +{ + if (gAgent.isGodlike()) + { + getChildView("Apply")->setEnabled(true); + } +} + +void LLPanelObjectTools::onApplyChanges() +{ + LLFloaterGodTools* god_tools = LLFloaterReg::getTypedInstance("god_tools"); + if(!god_tools) return; + LLViewerRegion *region = gAgent.getRegion(); + if (region && gAgent.isGodlike()) + { + // TODO -- implement this + getChildView("Apply")->setEnabled(false); + god_tools->sendGodUpdateRegionInfo(); + //LLFloaterReg::getTypedInstance("god_tools")->sendGodUpdateRegionInfo(); + } +} + + +// -------------------- +// LLPanelRequestTools +// -------------------- + +const std::string SELECTION = "Selection"; +const std::string AGENT_REGION = "Agent Region"; + +LLPanelRequestTools::LLPanelRequestTools(): + LLPanel() +{ + mCommitCallbackRegistrar.add("GodTools.Request", boost::bind(&LLPanelRequestTools::onClickRequest, this)); +} + +LLPanelRequestTools::~LLPanelRequestTools() +{ +} + +bool LLPanelRequestTools::postBuild() +{ + refresh(); + + return true; +} + +void LLPanelRequestTools::refresh() +{ + std::string buffer = getChild("destination")->getValue(); + LLCtrlListInterface *list = childGetListInterface("destination"); + if (!list) return; + + S32 last_item = list->getItemCount(); + + if (last_item >=3) + { + list->selectItemRange(2,last_item); + list->operateOnSelection(LLCtrlListInterface::OP_DELETE); + } + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* regionp = *iter; + std::string name = regionp->getName(); + if (!name.empty()) + { + list->addSimpleElement(name); + } + } + if(!buffer.empty()) + { + list->selectByValue(buffer); + } + else + { + list->operateOnSelection(LLCtrlListInterface::OP_DESELECT); + } +} + + +// static +void LLPanelRequestTools::sendRequest(const std::string& request, + const std::string& parameter, + const LLHost& host) +{ + LL_INFOS() << "Sending request '" << request << "', '" + << parameter << "' to " << host << LL_ENDL; + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("GodlikeMessage"); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addUUIDFast(_PREHASH_TransactionID, LLUUID::null); //not used + msg->nextBlock("MethodData"); + msg->addString("Method", request); + msg->addUUID("Invoice", LLUUID::null); + msg->nextBlock("ParamList"); + msg->addString("Parameter", parameter); + msg->sendReliable(host); +} + +void LLPanelRequestTools::onClickRequest() +{ + const std::string dest = getChild("destination")->getValue().asString(); + if(dest == SELECTION) + { + std::string req =getChild("request")->getValue(); + req = req.substr(0, req.find_first_of(" ")); + std::string param = getChild("parameter")->getValue(); + LLSelectMgr::getInstance()->sendGodlikeRequest(req, param); + } + else if(dest == AGENT_REGION) + { + sendRequest(gAgent.getRegionHost()); + } + else + { + // find region by name + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* regionp = *iter; + if(dest == regionp->getName()) + { + // found it + sendRequest(regionp->getHost()); + } + } + } +} + +void terrain_download_done(void** data, S32 status, LLExtStat ext_status) +{ + LLNotificationsUtil::add("TerrainDownloaded"); +} + + +void test_callback(const LLTSCode status) +{ + LL_INFOS() << "Test transfer callback returned!" << LL_ENDL; +} + + +void LLPanelRequestTools::sendRequest(const LLHost& host) +{ + + // intercept viewer local actions here + std::string req = getChild("request")->getValue(); + if (req == "terrain download") + { + gXferManager->requestFile(std::string("terrain.raw"), std::string("terrain.raw"), LL_PATH_NONE, + host, + false, + terrain_download_done, + NULL); + } + else + { + req = req.substr(0, req.find_first_of(" ")); + sendRequest(req, getChild("parameter")->getValue().asString(), host); + } +} + +// Flags are SWD_ flags. +void send_sim_wide_deletes(const LLUUID& owner_id, U32 flags) +{ + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_SimWideDeletes); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_DataBlock); + msg->addUUIDFast(_PREHASH_TargetID, owner_id); + msg->addU32Fast(_PREHASH_Flags, flags); + gAgent.sendReliableMessage(); +} diff --git a/indra/newview/llfloatergodtools.h b/indra/newview/llfloatergodtools.h index d0a9520a9a..03ac91404d 100644 --- a/indra/newview/llfloatergodtools.h +++ b/indra/newview/llfloatergodtools.h @@ -1,273 +1,273 @@ -/** - * @file llfloatergodtools.h - * @brief The on-screen rectangle with tool options. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERGODTOOLS_H -#define LL_LLFLOATERGODTOOLS_H - -#include "llcoord.h" -#include "llhost.h" -#include "llframetimer.h" - -#include "llfloater.h" -#include "llpanel.h" -#include - -class LLAvatarName; -class LLButton; -class LLCheckBoxCtrl; -class LLComboBox; -class LLUICtrl; -class LLLineEditor; -class LLPanelGridTools; -class LLPanelRegionTools; -class LLPanelObjectTools; -class LLPanelRequestTools; -//class LLSliderCtrl; -class LLSpinCtrl; -class LLTabContainer; -class LLTextBox; -class LLMessageSystem; - -class LLFloaterGodTools - : public LLFloater -{ - friend class LLFloaterReg; -public: - - enum EGodPanel - { - PANEL_GRID, - PANEL_REGION, - PANEL_OBJECT, - PANEL_REQUEST, - PANEL_COUNT - }; - - static void* createPanelGrid(void *userdata); - static void* createPanelRegion(void *userdata); - static void* createPanelObjects(void *userdata); - static void* createPanelRequest(void *userdata); - - static void refreshAll(); - - void showPanel(const std::string& panel_name); - - virtual void onOpen(const LLSD& key); - - virtual void draw(); - - // call this once per frame to handle visibility, rect location, - // button highlights, etc. - void updatePopup(LLCoordGL center, MASK mask); - - // Get data to populate UI. - void sendRegionInfoRequest(); - - // get and process region info if necessary. - static void processRegionInfo(LLMessageSystem* msg); - - // Send possibly changed values to simulator. - void sendGodUpdateRegionInfo(); - -private: - - LLFloaterGodTools(const LLSD& key); - ~LLFloaterGodTools(); - -protected: - U64 computeRegionFlags() const; - -protected: - - /*virtual*/ bool postBuild(); - // When the floater is going away, reset any options that need to be - // cleared. - void resetToolState(); - -public: - LLPanelRegionTools *mPanelRegionTools; - LLPanelObjectTools *mPanelObjectTools; - - LLHost mCurrentHost; - LLFrameTimer mUpdateTimer; -}; - - -//----------------------------------------------------------------------------- -// LLPanelRegionTools -//----------------------------------------------------------------------------- - -class LLPanelRegionTools -: public LLPanel -{ -public: - LLPanelRegionTools(); - /*virtual*/ ~LLPanelRegionTools(); - - bool postBuild(); - - /*virtual*/ void refresh(); - - static void onSaveState(void* userdata); - static void onChangeSimName(LLLineEditor* caller, void* userdata); - - void onChangeAnything(); - void onChangePrelude(); - void onApplyChanges(); - void onBakeTerrain(); - void onRevertTerrain(); - void onSwapTerrain(); - void onSelectRegion(); - void onRefresh(); - - // set internal checkboxes/spinners/combos - const std::string getSimName() const; - U32 getEstateID() const; - U32 getParentEstateID() const; - U64 getRegionFlags() const; - U64 getRegionFlagsMask() const; - F32 getBillableFactor() const; - S32 getPricePerMeter() const; - S32 getGridPosX() const; - S32 getGridPosY() const; - S32 getRedirectGridX() const; - S32 getRedirectGridY() const; - - // set internal checkboxes/spinners/combos - void setSimName(const std::string& name); - void setEstateID(U32 id); - void setParentEstateID(U32 id); - void setCheckFlags(U64 flags); - void setBillableFactor(F32 billable_factor); - void setPricePerMeter(S32 price); - void setGridPosX(S32 pos); - void setGridPosY(S32 pos); - void setRedirectGridX(S32 pos); - void setRedirectGridY(S32 pos); - - U64 computeRegionFlags(U64 initial_flags) const; - void clearAllWidgets(); - void enableAllWidgets(); - -protected: - // gets from internal checkboxes/spinners/combos - void updateCurrentRegion() const; -}; - - -//----------------------------------------------------------------------------- -// LLPanelGridTools -//----------------------------------------------------------------------------- - -class LLPanelGridTools -: public LLPanel -{ -public: - LLPanelGridTools(); - virtual ~LLPanelGridTools(); - - bool postBuild(); - - void refresh(); - - static void onDragSunPhase(LLUICtrl *ctrl, void *userdata); - void onClickFlushMapVisibilityCaches(); - static bool flushMapVisibilityCachesConfirm(const LLSD& notification, const LLSD& response); - -protected: - std::string mKickMessage; // Message to send on kick -}; - - -//----------------------------------------------------------------------------- -// LLPanelObjectTools -//----------------------------------------------------------------------------- - -class LLPanelObjectTools -: public LLPanel -{ -public: - LLPanelObjectTools(); - /*virtual*/ ~LLPanelObjectTools(); - - bool postBuild(); - - /*virtual*/ void refresh(); - - void setTargetAvatar(const LLUUID& target_id); - U64 computeRegionFlags(U64 initial_flags) const; - void clearAllWidgets(); - void enableAllWidgets(); - void setCheckFlags(U64 flags); - - void onChangeAnything(); - void onApplyChanges(); - void onClickSet(); - void callbackAvatarID(const uuid_vec_t& ids, const std::vector names); - void onClickDeletePublicOwnedBy(); - void onClickDeleteAllScriptedOwnedBy(); - void onClickDeleteAllOwnedBy(); - static bool callbackSimWideDeletes(const LLSD& notification, const LLSD& response); - void onGetTopColliders(); - void onGetTopScripts(); - void onGetScriptDigest(); - static void onClickSetBySelection(void* data); - -protected: - LLUUID mTargetAvatar; - - // For all delete dialogs, store flags here for message. - U32 mSimWideDeletesFlags; -}; - - -//----------------------------------------------------------------------------- -// LLPanelRequestTools -//----------------------------------------------------------------------------- - -class LLPanelRequestTools : public LLPanel -{ -public: - LLPanelRequestTools(); - /*virtual*/ ~LLPanelRequestTools(); - - bool postBuild(); - - void refresh(); - - static void sendRequest(const std::string& request, - const std::string& parameter, - const LLHost& host); - -protected: - void onClickRequest(); - void sendRequest(const LLHost& host); -}; - -// Flags are SWD_ flags. -void send_sim_wide_deletes(const LLUUID& owner_id, U32 flags); - -#endif +/** + * @file llfloatergodtools.h + * @brief The on-screen rectangle with tool options. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERGODTOOLS_H +#define LL_LLFLOATERGODTOOLS_H + +#include "llcoord.h" +#include "llhost.h" +#include "llframetimer.h" + +#include "llfloater.h" +#include "llpanel.h" +#include + +class LLAvatarName; +class LLButton; +class LLCheckBoxCtrl; +class LLComboBox; +class LLUICtrl; +class LLLineEditor; +class LLPanelGridTools; +class LLPanelRegionTools; +class LLPanelObjectTools; +class LLPanelRequestTools; +//class LLSliderCtrl; +class LLSpinCtrl; +class LLTabContainer; +class LLTextBox; +class LLMessageSystem; + +class LLFloaterGodTools + : public LLFloater +{ + friend class LLFloaterReg; +public: + + enum EGodPanel + { + PANEL_GRID, + PANEL_REGION, + PANEL_OBJECT, + PANEL_REQUEST, + PANEL_COUNT + }; + + static void* createPanelGrid(void *userdata); + static void* createPanelRegion(void *userdata); + static void* createPanelObjects(void *userdata); + static void* createPanelRequest(void *userdata); + + static void refreshAll(); + + void showPanel(const std::string& panel_name); + + virtual void onOpen(const LLSD& key); + + virtual void draw(); + + // call this once per frame to handle visibility, rect location, + // button highlights, etc. + void updatePopup(LLCoordGL center, MASK mask); + + // Get data to populate UI. + void sendRegionInfoRequest(); + + // get and process region info if necessary. + static void processRegionInfo(LLMessageSystem* msg); + + // Send possibly changed values to simulator. + void sendGodUpdateRegionInfo(); + +private: + + LLFloaterGodTools(const LLSD& key); + ~LLFloaterGodTools(); + +protected: + U64 computeRegionFlags() const; + +protected: + + /*virtual*/ bool postBuild(); + // When the floater is going away, reset any options that need to be + // cleared. + void resetToolState(); + +public: + LLPanelRegionTools *mPanelRegionTools; + LLPanelObjectTools *mPanelObjectTools; + + LLHost mCurrentHost; + LLFrameTimer mUpdateTimer; +}; + + +//----------------------------------------------------------------------------- +// LLPanelRegionTools +//----------------------------------------------------------------------------- + +class LLPanelRegionTools +: public LLPanel +{ +public: + LLPanelRegionTools(); + /*virtual*/ ~LLPanelRegionTools(); + + bool postBuild(); + + /*virtual*/ void refresh(); + + static void onSaveState(void* userdata); + static void onChangeSimName(LLLineEditor* caller, void* userdata); + + void onChangeAnything(); + void onChangePrelude(); + void onApplyChanges(); + void onBakeTerrain(); + void onRevertTerrain(); + void onSwapTerrain(); + void onSelectRegion(); + void onRefresh(); + + // set internal checkboxes/spinners/combos + const std::string getSimName() const; + U32 getEstateID() const; + U32 getParentEstateID() const; + U64 getRegionFlags() const; + U64 getRegionFlagsMask() const; + F32 getBillableFactor() const; + S32 getPricePerMeter() const; + S32 getGridPosX() const; + S32 getGridPosY() const; + S32 getRedirectGridX() const; + S32 getRedirectGridY() const; + + // set internal checkboxes/spinners/combos + void setSimName(const std::string& name); + void setEstateID(U32 id); + void setParentEstateID(U32 id); + void setCheckFlags(U64 flags); + void setBillableFactor(F32 billable_factor); + void setPricePerMeter(S32 price); + void setGridPosX(S32 pos); + void setGridPosY(S32 pos); + void setRedirectGridX(S32 pos); + void setRedirectGridY(S32 pos); + + U64 computeRegionFlags(U64 initial_flags) const; + void clearAllWidgets(); + void enableAllWidgets(); + +protected: + // gets from internal checkboxes/spinners/combos + void updateCurrentRegion() const; +}; + + +//----------------------------------------------------------------------------- +// LLPanelGridTools +//----------------------------------------------------------------------------- + +class LLPanelGridTools +: public LLPanel +{ +public: + LLPanelGridTools(); + virtual ~LLPanelGridTools(); + + bool postBuild(); + + void refresh(); + + static void onDragSunPhase(LLUICtrl *ctrl, void *userdata); + void onClickFlushMapVisibilityCaches(); + static bool flushMapVisibilityCachesConfirm(const LLSD& notification, const LLSD& response); + +protected: + std::string mKickMessage; // Message to send on kick +}; + + +//----------------------------------------------------------------------------- +// LLPanelObjectTools +//----------------------------------------------------------------------------- + +class LLPanelObjectTools +: public LLPanel +{ +public: + LLPanelObjectTools(); + /*virtual*/ ~LLPanelObjectTools(); + + bool postBuild(); + + /*virtual*/ void refresh(); + + void setTargetAvatar(const LLUUID& target_id); + U64 computeRegionFlags(U64 initial_flags) const; + void clearAllWidgets(); + void enableAllWidgets(); + void setCheckFlags(U64 flags); + + void onChangeAnything(); + void onApplyChanges(); + void onClickSet(); + void callbackAvatarID(const uuid_vec_t& ids, const std::vector names); + void onClickDeletePublicOwnedBy(); + void onClickDeleteAllScriptedOwnedBy(); + void onClickDeleteAllOwnedBy(); + static bool callbackSimWideDeletes(const LLSD& notification, const LLSD& response); + void onGetTopColliders(); + void onGetTopScripts(); + void onGetScriptDigest(); + static void onClickSetBySelection(void* data); + +protected: + LLUUID mTargetAvatar; + + // For all delete dialogs, store flags here for message. + U32 mSimWideDeletesFlags; +}; + + +//----------------------------------------------------------------------------- +// LLPanelRequestTools +//----------------------------------------------------------------------------- + +class LLPanelRequestTools : public LLPanel +{ +public: + LLPanelRequestTools(); + /*virtual*/ ~LLPanelRequestTools(); + + bool postBuild(); + + void refresh(); + + static void sendRequest(const std::string& request, + const std::string& parameter, + const LLHost& host); + +protected: + void onClickRequest(); + void sendRequest(const LLHost& host); +}; + +// Flags are SWD_ flags. +void send_sim_wide_deletes(const LLUUID& owner_id, U32 flags); + +#endif diff --git a/indra/newview/llfloatergotoline.cpp b/indra/newview/llfloatergotoline.cpp index 6c58ed5fb0..09ca0ab530 100644 --- a/indra/newview/llfloatergotoline.cpp +++ b/indra/newview/llfloatergotoline.cpp @@ -1,160 +1,160 @@ -/** - * @file llfloatergotoline.h - * @author MartinRJ - * @brief LLFloaterGotoLine class implementation - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llfloatergotoline.h" -#include "llpreviewscript.h" -#include "llfloaterreg.h" -#include "lllineeditor.h" -#include "llscripteditor.h" -#include "llviewerwindow.h" - -LLFloaterGotoLine* LLFloaterGotoLine::sInstance = NULL; - -LLFloaterGotoLine::LLFloaterGotoLine(LLScriptEdCore* editor_core) -: LLFloater(LLSD()), - mGotoBox(NULL), - mEditorCore(editor_core) -{ - buildFromFile("floater_goto_line.xml"); - - sInstance = this; - - // find floater in which script panel is embedded - LLView* viewp = (LLView*)editor_core; - while(viewp) - { - LLFloater* floaterp = dynamic_cast(viewp); - if (floaterp) - { - floaterp->addDependentFloater(this); - break; - } - viewp = viewp->getParent(); - } -} - -bool LLFloaterGotoLine::postBuild() -{ - mGotoBox = getChild("goto_line"); - mGotoBox->setCommitCallback(boost::bind(&LLFloaterGotoLine::onGotoBoxCommit, this)); - mGotoBox->setCommitOnFocusLost(false); - getChild("goto_line")->setPrevalidate(LLTextValidate::validateNonNegativeS32); - childSetAction("goto_btn", onBtnGoto,this); - setDefaultBtn("goto_btn"); - - return true; -} - -//static -void LLFloaterGotoLine::show(LLScriptEdCore* editor_core) -{ - if (sInstance && sInstance->mEditorCore && sInstance->mEditorCore != editor_core) - { - sInstance->closeFloater(); - delete sInstance; - } - - if (!sInstance) - { - // sInstance will be assigned in the constructor. - new LLFloaterGotoLine(editor_core); - } - - sInstance->openFloater(); -} - -LLFloaterGotoLine::~LLFloaterGotoLine() -{ - sInstance = NULL; -} - -// static -void LLFloaterGotoLine::onBtnGoto(void *userdata) -{ - LLFloaterGotoLine* self = (LLFloaterGotoLine*)userdata; - self->handleBtnGoto(); -} - -void LLFloaterGotoLine::handleBtnGoto() -{ - S32 row = 0; - S32 column = 0; - row = getChild("goto_line")->getValue().asInteger(); - if (row >= 0) - { - if (mEditorCore && mEditorCore->mEditor) - { - mEditorCore->mEditor->deselect(); - mEditorCore->mEditor->setCursor(row, column); - mEditorCore->mEditor->setFocus(true); - } - } -} - -bool LLFloaterGotoLine::hasAccelerators() const -{ - if (mEditorCore) - { - return mEditorCore->hasAccelerators(); - } - return false; -} - -bool LLFloaterGotoLine::handleKeyHere(KEY key, MASK mask) -{ - if (mEditorCore) - { - return mEditorCore->handleKeyHere(key, mask); - } - - return false; -} - -void LLFloaterGotoLine::onGotoBoxCommit() -{ - S32 row = 0; - S32 column = 0; - row = getChild("goto_line")->getValue().asInteger(); - if (row >= 0) - { - if (mEditorCore && mEditorCore->mEditor) - { - mEditorCore->mEditor->setCursor(row, column); - - S32 rownew = 0; - S32 columnnew = 0; - mEditorCore->mEditor->getCurrentLineAndColumn( &rownew, &columnnew, false ); // don't include wordwrap - if (rownew == row && columnnew == column) - { - mEditorCore->mEditor->deselect(); - mEditorCore->mEditor->setFocus(true); - sInstance->closeFloater(); - } //else do nothing (if the cursor-position didn't change) - } - } -} +/** + * @file llfloatergotoline.h + * @author MartinRJ + * @brief LLFloaterGotoLine class implementation + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llfloatergotoline.h" +#include "llpreviewscript.h" +#include "llfloaterreg.h" +#include "lllineeditor.h" +#include "llscripteditor.h" +#include "llviewerwindow.h" + +LLFloaterGotoLine* LLFloaterGotoLine::sInstance = NULL; + +LLFloaterGotoLine::LLFloaterGotoLine(LLScriptEdCore* editor_core) +: LLFloater(LLSD()), + mGotoBox(NULL), + mEditorCore(editor_core) +{ + buildFromFile("floater_goto_line.xml"); + + sInstance = this; + + // find floater in which script panel is embedded + LLView* viewp = (LLView*)editor_core; + while(viewp) + { + LLFloater* floaterp = dynamic_cast(viewp); + if (floaterp) + { + floaterp->addDependentFloater(this); + break; + } + viewp = viewp->getParent(); + } +} + +bool LLFloaterGotoLine::postBuild() +{ + mGotoBox = getChild("goto_line"); + mGotoBox->setCommitCallback(boost::bind(&LLFloaterGotoLine::onGotoBoxCommit, this)); + mGotoBox->setCommitOnFocusLost(false); + getChild("goto_line")->setPrevalidate(LLTextValidate::validateNonNegativeS32); + childSetAction("goto_btn", onBtnGoto,this); + setDefaultBtn("goto_btn"); + + return true; +} + +//static +void LLFloaterGotoLine::show(LLScriptEdCore* editor_core) +{ + if (sInstance && sInstance->mEditorCore && sInstance->mEditorCore != editor_core) + { + sInstance->closeFloater(); + delete sInstance; + } + + if (!sInstance) + { + // sInstance will be assigned in the constructor. + new LLFloaterGotoLine(editor_core); + } + + sInstance->openFloater(); +} + +LLFloaterGotoLine::~LLFloaterGotoLine() +{ + sInstance = NULL; +} + +// static +void LLFloaterGotoLine::onBtnGoto(void *userdata) +{ + LLFloaterGotoLine* self = (LLFloaterGotoLine*)userdata; + self->handleBtnGoto(); +} + +void LLFloaterGotoLine::handleBtnGoto() +{ + S32 row = 0; + S32 column = 0; + row = getChild("goto_line")->getValue().asInteger(); + if (row >= 0) + { + if (mEditorCore && mEditorCore->mEditor) + { + mEditorCore->mEditor->deselect(); + mEditorCore->mEditor->setCursor(row, column); + mEditorCore->mEditor->setFocus(true); + } + } +} + +bool LLFloaterGotoLine::hasAccelerators() const +{ + if (mEditorCore) + { + return mEditorCore->hasAccelerators(); + } + return false; +} + +bool LLFloaterGotoLine::handleKeyHere(KEY key, MASK mask) +{ + if (mEditorCore) + { + return mEditorCore->handleKeyHere(key, mask); + } + + return false; +} + +void LLFloaterGotoLine::onGotoBoxCommit() +{ + S32 row = 0; + S32 column = 0; + row = getChild("goto_line")->getValue().asInteger(); + if (row >= 0) + { + if (mEditorCore && mEditorCore->mEditor) + { + mEditorCore->mEditor->setCursor(row, column); + + S32 rownew = 0; + S32 columnnew = 0; + mEditorCore->mEditor->getCurrentLineAndColumn( &rownew, &columnnew, false ); // don't include wordwrap + if (rownew == row && columnnew == column) + { + mEditorCore->mEditor->deselect(); + mEditorCore->mEditor->setFocus(true); + sInstance->closeFloater(); + } //else do nothing (if the cursor-position didn't change) + } + } +} diff --git a/indra/newview/llfloatergotoline.h b/indra/newview/llfloatergotoline.h index 69348d6e0a..02f3747cb2 100644 --- a/indra/newview/llfloatergotoline.h +++ b/indra/newview/llfloatergotoline.h @@ -1,66 +1,66 @@ -/** - * @file llfloatergotoline.h - * @author MartinRJ - * @brief LLFloaterGotoLine class definition - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERGOTOLINE_H -#define LL_LLFLOATERGOTOLINE_H - -#include "llfloater.h" -#include "lllineeditor.h" -#include "llpreviewscript.h" - -class LLScriptEdCore; - -class LLFloaterGotoLine : public LLFloater -{ -public: - LLFloaterGotoLine(LLScriptEdCore* editor_core); - ~LLFloaterGotoLine(); - - bool postBuild() override; - static void show(LLScriptEdCore* editor_core); - - static void onBtnGoto(void* userdata); - void handleBtnGoto(); - - LLScriptEdCore* getEditorCore() { return mEditorCore; } - static LLFloaterGotoLine* getInstance() { return sInstance; } - - bool hasAccelerators() const override; - bool handleKeyHere(KEY key, MASK mask) override; - -private: - - LLScriptEdCore* mEditorCore; - - static LLFloaterGotoLine* sInstance; - -protected: - LLLineEditor* mGotoBox; - void onGotoBoxCommit(); -}; - -#endif // LL_LLFLOATERGOTOLINE_H +/** + * @file llfloatergotoline.h + * @author MartinRJ + * @brief LLFloaterGotoLine class definition + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERGOTOLINE_H +#define LL_LLFLOATERGOTOLINE_H + +#include "llfloater.h" +#include "lllineeditor.h" +#include "llpreviewscript.h" + +class LLScriptEdCore; + +class LLFloaterGotoLine : public LLFloater +{ +public: + LLFloaterGotoLine(LLScriptEdCore* editor_core); + ~LLFloaterGotoLine(); + + bool postBuild() override; + static void show(LLScriptEdCore* editor_core); + + static void onBtnGoto(void* userdata); + void handleBtnGoto(); + + LLScriptEdCore* getEditorCore() { return mEditorCore; } + static LLFloaterGotoLine* getInstance() { return sInstance; } + + bool hasAccelerators() const override; + bool handleKeyHere(KEY key, MASK mask) override; + +private: + + LLScriptEdCore* mEditorCore; + + static LLFloaterGotoLine* sInstance; + +protected: + LLLineEditor* mGotoBox; + void onGotoBoxCommit(); +}; + +#endif // LL_LLFLOATERGOTOLINE_H diff --git a/indra/newview/llfloatergroups.cpp b/indra/newview/llfloatergroups.cpp index cd058d2e38..a02ecaf293 100644 --- a/indra/newview/llfloatergroups.cpp +++ b/indra/newview/llfloatergroups.cpp @@ -1,395 +1,395 @@ -/** - * @file llfloatergroups.cpp - * @brief LLPanelGroups class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/* - * Shown from Edit -> Groups... - * Shows the agent's groups and allows the edit window to be invoked. - * Also overloaded to allow picking of a single group for assigning - * objects and land to groups. - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloatergroups.h" - -#include "roles_constants.h" - -#include "llagent.h" -#include "llagentbenefits.h" -#include "llbutton.h" -#include "llgroupactions.h" -#include "llscrolllistctrl.h" -#include "llstartup.h" -#include "lltextbox.h" -#include "lluictrlfactory.h" -#include "lltrans.h" - -using namespace LLOldEvents; - -// helper functions -void init_group_list(LLScrollListCtrl* ctrl, const LLUUID& highlight_id, U64 powers_mask = GP_ALL_POWERS); - -///---------------------------------------------------------------------------- -/// Class LLFloaterGroupPicker -///---------------------------------------------------------------------------- - -LLFloaterGroupPicker::LLFloaterGroupPicker(const LLSD& seed) -: LLFloater(seed), - mPowersMask(GP_ALL_POWERS), - mID(seed.asUUID()) -{ -// LLUICtrlFactory::getInstance()->buildFloater(this, "floater_choose_group.xml"); -} - -LLFloaterGroupPicker::~LLFloaterGroupPicker() -{ -} - -void LLFloaterGroupPicker::setPowersMask(U64 powers_mask) -{ - mPowersMask = powers_mask; - init_group_list(getChild("group list"), gAgent.getGroupID(), mPowersMask); -} - - -bool LLFloaterGroupPicker::postBuild() -{ - LLScrollListCtrl* list_ctrl = getChild("group list"); - if (list_ctrl) - { - init_group_list(list_ctrl, gAgent.getGroupID(), mPowersMask); - list_ctrl->setDoubleClickCallback(onBtnOK, this); - list_ctrl->setContextMenu(LLScrollListCtrl::MENU_GROUP); - } - - childSetAction("OK", onBtnOK, this); - - childSetAction("Cancel", onBtnCancel, this); - - setDefaultBtn("OK"); - - getChildView("OK")->setEnabled(true); - - return true; -} - -void LLFloaterGroupPicker::removeNoneOption() -{ - // Remove group "none" from list. Group "none" is added in init_group_list(). - // Some UI elements use group "none", we need to manually delete it here. - // Group "none" ID is LLUUID:null. - LLCtrlListInterface* group_list = getChild("group list")->getListInterface(); - if(group_list) - { - group_list->selectByValue(LLUUID::null); - group_list->operateOnSelection(LLCtrlListInterface::OP_DELETE); - } -} - - -void LLFloaterGroupPicker::onBtnOK(void* userdata) -{ - LLFloaterGroupPicker* self = (LLFloaterGroupPicker*)userdata; - if(self) self->ok(); -} - -void LLFloaterGroupPicker::onBtnCancel(void* userdata) -{ - LLFloaterGroupPicker* self = (LLFloaterGroupPicker*)userdata; - if(self) self->closeFloater(); -} - - -void LLFloaterGroupPicker::ok() -{ - LLCtrlListInterface *group_list = childGetListInterface("group list"); - LLUUID group_id; - if (group_list) - { - group_id = group_list->getCurrentID(); - } - mGroupSelectSignal(group_id); - - closeFloater(); -} - -///---------------------------------------------------------------------------- -/// Class LLPanelGroups -///---------------------------------------------------------------------------- - -//LLEventListener -//virtual -bool LLPanelGroups::handleEvent(LLPointer event, const LLSD& userdata) -{ - if (event->desc() == "new group") - { - reset(); - return true; - } - return false; -} - -// Default constructor -LLPanelGroups::LLPanelGroups() : - LLPanel() -{ - gAgent.addListener(this, "new group"); -} - -LLPanelGroups::~LLPanelGroups() -{ - gAgent.removeListener(this); -} - -// clear the group list, and get a fresh set of info. -void LLPanelGroups::reset() -{ - LLCtrlListInterface *group_list = childGetListInterface("group list"); - if (group_list) - { - group_list->operateOnAll(LLCtrlListInterface::OP_DELETE); - } - getChild("groupcount")->setTextArg("[COUNT]", llformat("%d",gAgent.mGroups.size())); - getChild("groupcount")->setTextArg("[MAX]", llformat("%d",LLAgentBenefitsMgr::current().getGroupMembershipLimit())); - - init_group_list(getChild("group list"), gAgent.getGroupID()); - enableButtons(); -} - -bool LLPanelGroups::postBuild() -{ - childSetCommitCallback("group list", onGroupList, this); - - getChild("groupcount")->setTextArg("[COUNT]", llformat("%d",gAgent.mGroups.size())); - getChild("groupcount")->setTextArg("[MAX]", llformat("%d",LLAgentBenefitsMgr::current().getGroupMembershipLimit())); - - LLScrollListCtrl *list = getChild("group list"); - if (list) - { - init_group_list(list, gAgent.getGroupID()); - list->setDoubleClickCallback(onBtnIM, this); - list->setContextMenu(LLScrollListCtrl::MENU_GROUP); - } - - childSetAction("Activate", onBtnActivate, this); - - childSetAction("Info", onBtnInfo, this); - - childSetAction("IM", onBtnIM, this); - - childSetAction("Leave", onBtnLeave, this); - - childSetAction("Create", onBtnCreate, this); - - childSetAction("Search...", onBtnSearch, this); - - setDefaultBtn("IM"); - - reset(); - - return true; -} - -void LLPanelGroups::enableButtons() -{ - LLCtrlListInterface *group_list = childGetListInterface("group list"); - LLUUID group_id; - if (group_list) - { - group_id = group_list->getCurrentID(); - } - - if(group_id != gAgent.getGroupID()) - { - getChildView("Activate")->setEnabled(true); - } - else - { - getChildView("Activate")->setEnabled(false); - } - if (group_id.notNull()) - { - getChildView("Info")->setEnabled(true); - getChildView("IM")->setEnabled(true); - getChildView("Leave")->setEnabled(true); - } - else - { - getChildView("Info")->setEnabled(false); - getChildView("IM")->setEnabled(false); - getChildView("Leave")->setEnabled(false); - } - getChildView("Create")->setEnabled(gAgent.canJoinGroups()); -} - - -void LLPanelGroups::onBtnCreate(void* userdata) -{ - LLPanelGroups* self = (LLPanelGroups*)userdata; - if(self) self->create(); -} - -void LLPanelGroups::onBtnActivate(void* userdata) -{ - LLPanelGroups* self = (LLPanelGroups*)userdata; - if(self) self->activate(); -} - -void LLPanelGroups::onBtnInfo(void* userdata) -{ - LLPanelGroups* self = (LLPanelGroups*)userdata; - if(self) self->info(); -} - -void LLPanelGroups::onBtnIM(void* userdata) -{ - LLPanelGroups* self = (LLPanelGroups*)userdata; - if(self) self->startIM(); -} - -void LLPanelGroups::onBtnLeave(void* userdata) -{ - LLPanelGroups* self = (LLPanelGroups*)userdata; - if(self) self->leave(); -} - -void LLPanelGroups::onBtnSearch(void* userdata) -{ - LLPanelGroups* self = (LLPanelGroups*)userdata; - if(self) self->search(); -} - -void LLPanelGroups::create() -{ - LLGroupActions::createGroup(); -} - -void LLPanelGroups::activate() -{ - LLCtrlListInterface *group_list = childGetListInterface("group list"); - LLUUID group_id; - if (group_list) - { - group_id = group_list->getCurrentID(); - } - LLGroupActions::activate(group_id); -} - -void LLPanelGroups::info() -{ - LLCtrlListInterface *group_list = childGetListInterface("group list"); - LLUUID group_id; - if (group_list && (group_id = group_list->getCurrentID()).notNull()) - { - LLGroupActions::show(group_id); - } -} - -void LLPanelGroups::startIM() -{ - LLCtrlListInterface *group_list = childGetListInterface("group list"); - LLUUID group_id; - - if (group_list && (group_id = group_list->getCurrentID()).notNull()) - { - LLGroupActions::startIM(group_id); - } -} - -void LLPanelGroups::leave() -{ - LLCtrlListInterface *group_list = childGetListInterface("group list"); - LLUUID group_id; - if (group_list && (group_id = group_list->getCurrentID()).notNull()) - { - LLGroupActions::leave(group_id); - } -} - -void LLPanelGroups::search() -{ - LLGroupActions::search(); -} - -void LLPanelGroups::onGroupList(LLUICtrl* ctrl, void* userdata) -{ - LLPanelGroups* self = (LLPanelGroups*)userdata; - if(self) self->enableButtons(); -} - -void init_group_list(LLScrollListCtrl* group_list, const LLUUID& highlight_id, U64 powers_mask) -{ - S32 count = gAgent.mGroups.size(); - LLUUID id; - if (!group_list) return; - - group_list->operateOnAll(LLCtrlListInterface::OP_DELETE); - - for(S32 i = 0; i < count; ++i) - { - id = gAgent.mGroups.at(i).mID; - LLGroupData* group_datap = &gAgent.mGroups.at(i); - if ((powers_mask == GP_ALL_POWERS) || ((group_datap->mPowers & powers_mask) != 0)) - { - std::string style = "NORMAL"; - if(highlight_id == id) - { - style = "BOLD"; - } - - LLSD element; - element["id"] = id; - element["columns"][0]["column"] = "name"; - element["columns"][0]["value"] = group_datap->mName; - element["columns"][0]["font"]["name"] = "SANSSERIF"; - element["columns"][0]["font"]["style"] = style; - - group_list->addElement(element); - } - } - - group_list->sortOnce(0, true); - - // add "none" to list at top - { - std::string style = "NORMAL"; - if (highlight_id.isNull()) - { - style = "BOLD"; - } - LLSD element; - element["id"] = LLUUID::null; - element["columns"][0]["column"] = "name"; - element["columns"][0]["value"] = LLTrans::getString("GroupsNone"); - element["columns"][0]["font"]["name"] = "SANSSERIF"; - element["columns"][0]["font"]["style"] = style; - - group_list->addElement(element, ADD_TOP); - } - - group_list->selectByValue(highlight_id); -} - +/** + * @file llfloatergroups.cpp + * @brief LLPanelGroups class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/* + * Shown from Edit -> Groups... + * Shows the agent's groups and allows the edit window to be invoked. + * Also overloaded to allow picking of a single group for assigning + * objects and land to groups. + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatergroups.h" + +#include "roles_constants.h" + +#include "llagent.h" +#include "llagentbenefits.h" +#include "llbutton.h" +#include "llgroupactions.h" +#include "llscrolllistctrl.h" +#include "llstartup.h" +#include "lltextbox.h" +#include "lluictrlfactory.h" +#include "lltrans.h" + +using namespace LLOldEvents; + +// helper functions +void init_group_list(LLScrollListCtrl* ctrl, const LLUUID& highlight_id, U64 powers_mask = GP_ALL_POWERS); + +///---------------------------------------------------------------------------- +/// Class LLFloaterGroupPicker +///---------------------------------------------------------------------------- + +LLFloaterGroupPicker::LLFloaterGroupPicker(const LLSD& seed) +: LLFloater(seed), + mPowersMask(GP_ALL_POWERS), + mID(seed.asUUID()) +{ +// LLUICtrlFactory::getInstance()->buildFloater(this, "floater_choose_group.xml"); +} + +LLFloaterGroupPicker::~LLFloaterGroupPicker() +{ +} + +void LLFloaterGroupPicker::setPowersMask(U64 powers_mask) +{ + mPowersMask = powers_mask; + init_group_list(getChild("group list"), gAgent.getGroupID(), mPowersMask); +} + + +bool LLFloaterGroupPicker::postBuild() +{ + LLScrollListCtrl* list_ctrl = getChild("group list"); + if (list_ctrl) + { + init_group_list(list_ctrl, gAgent.getGroupID(), mPowersMask); + list_ctrl->setDoubleClickCallback(onBtnOK, this); + list_ctrl->setContextMenu(LLScrollListCtrl::MENU_GROUP); + } + + childSetAction("OK", onBtnOK, this); + + childSetAction("Cancel", onBtnCancel, this); + + setDefaultBtn("OK"); + + getChildView("OK")->setEnabled(true); + + return true; +} + +void LLFloaterGroupPicker::removeNoneOption() +{ + // Remove group "none" from list. Group "none" is added in init_group_list(). + // Some UI elements use group "none", we need to manually delete it here. + // Group "none" ID is LLUUID:null. + LLCtrlListInterface* group_list = getChild("group list")->getListInterface(); + if(group_list) + { + group_list->selectByValue(LLUUID::null); + group_list->operateOnSelection(LLCtrlListInterface::OP_DELETE); + } +} + + +void LLFloaterGroupPicker::onBtnOK(void* userdata) +{ + LLFloaterGroupPicker* self = (LLFloaterGroupPicker*)userdata; + if(self) self->ok(); +} + +void LLFloaterGroupPicker::onBtnCancel(void* userdata) +{ + LLFloaterGroupPicker* self = (LLFloaterGroupPicker*)userdata; + if(self) self->closeFloater(); +} + + +void LLFloaterGroupPicker::ok() +{ + LLCtrlListInterface *group_list = childGetListInterface("group list"); + LLUUID group_id; + if (group_list) + { + group_id = group_list->getCurrentID(); + } + mGroupSelectSignal(group_id); + + closeFloater(); +} + +///---------------------------------------------------------------------------- +/// Class LLPanelGroups +///---------------------------------------------------------------------------- + +//LLEventListener +//virtual +bool LLPanelGroups::handleEvent(LLPointer event, const LLSD& userdata) +{ + if (event->desc() == "new group") + { + reset(); + return true; + } + return false; +} + +// Default constructor +LLPanelGroups::LLPanelGroups() : + LLPanel() +{ + gAgent.addListener(this, "new group"); +} + +LLPanelGroups::~LLPanelGroups() +{ + gAgent.removeListener(this); +} + +// clear the group list, and get a fresh set of info. +void LLPanelGroups::reset() +{ + LLCtrlListInterface *group_list = childGetListInterface("group list"); + if (group_list) + { + group_list->operateOnAll(LLCtrlListInterface::OP_DELETE); + } + getChild("groupcount")->setTextArg("[COUNT]", llformat("%d",gAgent.mGroups.size())); + getChild("groupcount")->setTextArg("[MAX]", llformat("%d",LLAgentBenefitsMgr::current().getGroupMembershipLimit())); + + init_group_list(getChild("group list"), gAgent.getGroupID()); + enableButtons(); +} + +bool LLPanelGroups::postBuild() +{ + childSetCommitCallback("group list", onGroupList, this); + + getChild("groupcount")->setTextArg("[COUNT]", llformat("%d",gAgent.mGroups.size())); + getChild("groupcount")->setTextArg("[MAX]", llformat("%d",LLAgentBenefitsMgr::current().getGroupMembershipLimit())); + + LLScrollListCtrl *list = getChild("group list"); + if (list) + { + init_group_list(list, gAgent.getGroupID()); + list->setDoubleClickCallback(onBtnIM, this); + list->setContextMenu(LLScrollListCtrl::MENU_GROUP); + } + + childSetAction("Activate", onBtnActivate, this); + + childSetAction("Info", onBtnInfo, this); + + childSetAction("IM", onBtnIM, this); + + childSetAction("Leave", onBtnLeave, this); + + childSetAction("Create", onBtnCreate, this); + + childSetAction("Search...", onBtnSearch, this); + + setDefaultBtn("IM"); + + reset(); + + return true; +} + +void LLPanelGroups::enableButtons() +{ + LLCtrlListInterface *group_list = childGetListInterface("group list"); + LLUUID group_id; + if (group_list) + { + group_id = group_list->getCurrentID(); + } + + if(group_id != gAgent.getGroupID()) + { + getChildView("Activate")->setEnabled(true); + } + else + { + getChildView("Activate")->setEnabled(false); + } + if (group_id.notNull()) + { + getChildView("Info")->setEnabled(true); + getChildView("IM")->setEnabled(true); + getChildView("Leave")->setEnabled(true); + } + else + { + getChildView("Info")->setEnabled(false); + getChildView("IM")->setEnabled(false); + getChildView("Leave")->setEnabled(false); + } + getChildView("Create")->setEnabled(gAgent.canJoinGroups()); +} + + +void LLPanelGroups::onBtnCreate(void* userdata) +{ + LLPanelGroups* self = (LLPanelGroups*)userdata; + if(self) self->create(); +} + +void LLPanelGroups::onBtnActivate(void* userdata) +{ + LLPanelGroups* self = (LLPanelGroups*)userdata; + if(self) self->activate(); +} + +void LLPanelGroups::onBtnInfo(void* userdata) +{ + LLPanelGroups* self = (LLPanelGroups*)userdata; + if(self) self->info(); +} + +void LLPanelGroups::onBtnIM(void* userdata) +{ + LLPanelGroups* self = (LLPanelGroups*)userdata; + if(self) self->startIM(); +} + +void LLPanelGroups::onBtnLeave(void* userdata) +{ + LLPanelGroups* self = (LLPanelGroups*)userdata; + if(self) self->leave(); +} + +void LLPanelGroups::onBtnSearch(void* userdata) +{ + LLPanelGroups* self = (LLPanelGroups*)userdata; + if(self) self->search(); +} + +void LLPanelGroups::create() +{ + LLGroupActions::createGroup(); +} + +void LLPanelGroups::activate() +{ + LLCtrlListInterface *group_list = childGetListInterface("group list"); + LLUUID group_id; + if (group_list) + { + group_id = group_list->getCurrentID(); + } + LLGroupActions::activate(group_id); +} + +void LLPanelGroups::info() +{ + LLCtrlListInterface *group_list = childGetListInterface("group list"); + LLUUID group_id; + if (group_list && (group_id = group_list->getCurrentID()).notNull()) + { + LLGroupActions::show(group_id); + } +} + +void LLPanelGroups::startIM() +{ + LLCtrlListInterface *group_list = childGetListInterface("group list"); + LLUUID group_id; + + if (group_list && (group_id = group_list->getCurrentID()).notNull()) + { + LLGroupActions::startIM(group_id); + } +} + +void LLPanelGroups::leave() +{ + LLCtrlListInterface *group_list = childGetListInterface("group list"); + LLUUID group_id; + if (group_list && (group_id = group_list->getCurrentID()).notNull()) + { + LLGroupActions::leave(group_id); + } +} + +void LLPanelGroups::search() +{ + LLGroupActions::search(); +} + +void LLPanelGroups::onGroupList(LLUICtrl* ctrl, void* userdata) +{ + LLPanelGroups* self = (LLPanelGroups*)userdata; + if(self) self->enableButtons(); +} + +void init_group_list(LLScrollListCtrl* group_list, const LLUUID& highlight_id, U64 powers_mask) +{ + S32 count = gAgent.mGroups.size(); + LLUUID id; + if (!group_list) return; + + group_list->operateOnAll(LLCtrlListInterface::OP_DELETE); + + for(S32 i = 0; i < count; ++i) + { + id = gAgent.mGroups.at(i).mID; + LLGroupData* group_datap = &gAgent.mGroups.at(i); + if ((powers_mask == GP_ALL_POWERS) || ((group_datap->mPowers & powers_mask) != 0)) + { + std::string style = "NORMAL"; + if(highlight_id == id) + { + style = "BOLD"; + } + + LLSD element; + element["id"] = id; + element["columns"][0]["column"] = "name"; + element["columns"][0]["value"] = group_datap->mName; + element["columns"][0]["font"]["name"] = "SANSSERIF"; + element["columns"][0]["font"]["style"] = style; + + group_list->addElement(element); + } + } + + group_list->sortOnce(0, true); + + // add "none" to list at top + { + std::string style = "NORMAL"; + if (highlight_id.isNull()) + { + style = "BOLD"; + } + LLSD element; + element["id"] = LLUUID::null; + element["columns"][0]["column"] = "name"; + element["columns"][0]["value"] = LLTrans::getString("GroupsNone"); + element["columns"][0]["font"]["name"] = "SANSSERIF"; + element["columns"][0]["font"]["style"] = style; + + group_list->addElement(element, ADD_TOP); + } + + group_list->selectByValue(highlight_id); +} + diff --git a/indra/newview/llfloatergroups.h b/indra/newview/llfloatergroups.h index 620cb61dd5..be6ced40bf 100644 --- a/indra/newview/llfloatergroups.h +++ b/indra/newview/llfloatergroups.h @@ -1,118 +1,118 @@ -/** - * @file llfloatergroups.h - * @brief LLFloaterGroups class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/* - * Shown from Edit -> Groups... - * Shows the agent's groups and allows the edit window to be invoked. - * Also overloaded to allow picking of a single group for assigning - * objects and land to groups. - */ - -#ifndef LL_LLFLOATERGROUPS_H -#define LL_LLFLOATERGROUPS_H - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class llfloatergroups -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -#include "lluuid.h" -#include "llfloater.h" -#include -#include -#include - -class LLUICtrl; -class LLTextBox; -class LLScrollListCtrl; -class LLButton; -class LLFloaterGroupPicker; - -class LLFloaterGroupPicker : public LLFloater -{ -public: - LLFloaterGroupPicker(const LLSD& seed); - ~LLFloaterGroupPicker(); - - // Note: Don't return connection; use boost::bind + boost::signals2::trackable to disconnect slots - typedef boost::signals2::signal signal_t; - void setSelectGroupCallback(const signal_t::slot_type& cb) { mGroupSelectSignal.connect(cb); } - void setPowersMask(U64 powers_mask); - bool postBuild(); - - // for cases like inviting avatar to group we don't want the none option - void removeNoneOption(); - -protected: - void ok(); - static void onBtnOK(void* userdata); - static void onBtnCancel(void* userdata); - -protected: - LLUUID mID; - U64 mPowersMask; - signal_t mGroupSelectSignal; - - typedef std::map instance_map_t; - static instance_map_t sInstances; -}; - -class LLPanelGroups : public LLPanel, public LLOldEvents::LLSimpleListener -{ -public: - LLPanelGroups(); - virtual ~LLPanelGroups(); - - //LLEventListener - /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); - - // clear the group list, and get a fresh set of info. - void reset(); - -protected: - // initialize based on the type - bool postBuild(); - - // highlight_id is a group id to highlight - void enableButtons(); - - static void onGroupList(LLUICtrl* ctrl, void* userdata); - static void onBtnCreate(void* userdata); - static void onBtnActivate(void* userdata); - static void onBtnInfo(void* userdata); - static void onBtnIM(void* userdata); - static void onBtnLeave(void* userdata); - static void onBtnSearch(void* userdata); - - void create(); - void activate(); - void info(); - void startIM(); - void leave(); - void search(); -}; - - -#endif // LL_LLFLOATERGROUPS_H +/** + * @file llfloatergroups.h + * @brief LLFloaterGroups class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/* + * Shown from Edit -> Groups... + * Shows the agent's groups and allows the edit window to be invoked. + * Also overloaded to allow picking of a single group for assigning + * objects and land to groups. + */ + +#ifndef LL_LLFLOATERGROUPS_H +#define LL_LLFLOATERGROUPS_H + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class llfloatergroups +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +#include "lluuid.h" +#include "llfloater.h" +#include +#include +#include + +class LLUICtrl; +class LLTextBox; +class LLScrollListCtrl; +class LLButton; +class LLFloaterGroupPicker; + +class LLFloaterGroupPicker : public LLFloater +{ +public: + LLFloaterGroupPicker(const LLSD& seed); + ~LLFloaterGroupPicker(); + + // Note: Don't return connection; use boost::bind + boost::signals2::trackable to disconnect slots + typedef boost::signals2::signal signal_t; + void setSelectGroupCallback(const signal_t::slot_type& cb) { mGroupSelectSignal.connect(cb); } + void setPowersMask(U64 powers_mask); + bool postBuild(); + + // for cases like inviting avatar to group we don't want the none option + void removeNoneOption(); + +protected: + void ok(); + static void onBtnOK(void* userdata); + static void onBtnCancel(void* userdata); + +protected: + LLUUID mID; + U64 mPowersMask; + signal_t mGroupSelectSignal; + + typedef std::map instance_map_t; + static instance_map_t sInstances; +}; + +class LLPanelGroups : public LLPanel, public LLOldEvents::LLSimpleListener +{ +public: + LLPanelGroups(); + virtual ~LLPanelGroups(); + + //LLEventListener + /*virtual*/ bool handleEvent(LLPointer event, const LLSD& userdata); + + // clear the group list, and get a fresh set of info. + void reset(); + +protected: + // initialize based on the type + bool postBuild(); + + // highlight_id is a group id to highlight + void enableButtons(); + + static void onGroupList(LLUICtrl* ctrl, void* userdata); + static void onBtnCreate(void* userdata); + static void onBtnActivate(void* userdata); + static void onBtnInfo(void* userdata); + static void onBtnIM(void* userdata); + static void onBtnLeave(void* userdata); + static void onBtnSearch(void* userdata); + + void create(); + void activate(); + void info(); + void startIM(); + void leave(); + void search(); +}; + + +#endif // LL_LLFLOATERGROUPS_H diff --git a/indra/newview/llfloaterhelpbrowser.cpp b/indra/newview/llfloaterhelpbrowser.cpp index 6a9d15325a..e77fa83606 100644 --- a/indra/newview/llfloaterhelpbrowser.cpp +++ b/indra/newview/llfloaterhelpbrowser.cpp @@ -1,154 +1,154 @@ -/** - * @file llfloaterhelpbrowser.cpp - * @brief HTML Help floater - uses embedded web browser control - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterhelpbrowser.h" - -#include "llfloaterreg.h" -#include "llhttpconstants.h" -#include "llpluginclassmedia.h" -#include "llmediactrl.h" -#include "llviewerwindow.h" -#include "llviewercontrol.h" -#include "llweb.h" -#include "llui.h" - -#include "llurlhistory.h" -#include "llviewermedia.h" -#include "llviewerhelp.h" - - -LLFloaterHelpBrowser::LLFloaterHelpBrowser(const LLSD& key) - : LLFloater(key) -{ -} - -bool LLFloaterHelpBrowser::postBuild() -{ - mBrowser = getChild("browser"); - mBrowser->addObserver(this); - mBrowser->setErrorPageURL(gSavedSettings.getString("GenericErrorPageURL")); - - childSetAction("open_browser", onClickOpenWebBrowser, this); - - buildURLHistory(); - return true; -} - -void LLFloaterHelpBrowser::buildURLHistory() -{ - // Get all of the entries in the "browser" collection - LLSD browser_history = LLURLHistory::getURLHistory("browser"); - - // initialize URL history in the plugin - LLPluginClassMedia *plugin = mBrowser->getMediaPlugin(); - if (plugin) - { - plugin->initializeUrlHistory(browser_history); - } -} - -void LLFloaterHelpBrowser::onOpen(const LLSD& key) -{ - gSavedSettings.setBOOL("HelpFloaterOpen", true); - - std::string topic = key.asString(); - mBrowser->navigateTo(LLViewerHelp::instance().getURL(topic)); -} - -//virtual -void LLFloaterHelpBrowser::onClose(bool app_quitting) -{ - if (!app_quitting) - { - gSavedSettings.setBOOL("HelpFloaterOpen", false); - } - // really really destroy the help browser when it's closed, it'll be recreated. - destroy(); // really destroy this dialog on closure, it's relatively heavyweight. -} - -void LLFloaterHelpBrowser::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) -{ - switch (event) - { - case MEDIA_EVENT_LOCATION_CHANGED: - setCurrentURL(self->getLocation()); - break; - - case MEDIA_EVENT_NAVIGATE_BEGIN: - getChild("status_text")->setValue(getString("loading_text")); - break; - - case MEDIA_EVENT_NAVIGATE_COMPLETE: - getChild("status_text")->setValue(getString("done_text")); - break; - - default: - break; - } -} - -void LLFloaterHelpBrowser::setCurrentURL(const std::string& url) -{ - mCurrentURL = url; - - // redirects will navigate momentarily to about:blank, don't add to history - if (mCurrentURL != "about:blank") - { - // Serialize url history - LLURLHistory::removeURL("browser", mCurrentURL); - LLURLHistory::addURL("browser", mCurrentURL); - } -} - -//static -void LLFloaterHelpBrowser::onClickClose(void* user_data) -{ - LLFloaterHelpBrowser* self = (LLFloaterHelpBrowser*)user_data; - - self->closeFloater(); -} - -//static -void LLFloaterHelpBrowser::onClickOpenWebBrowser(void* user_data) -{ - LLFloaterHelpBrowser* self = (LLFloaterHelpBrowser*)user_data; - - std::string url = self->mCurrentURL.empty() ? - self->mBrowser->getHomePageUrl() : - self->mCurrentURL; - LLWeb::loadURLExternal(url); -} - -void LLFloaterHelpBrowser::openMedia(const std::string& media_url) -{ - // explicitly make the media mime type for this floater since it will - // only ever display one type of content (Web). - mBrowser->setHomePageUrl(media_url, HTTP_CONTENT_TEXT_HTML); - mBrowser->navigateTo(media_url, HTTP_CONTENT_TEXT_HTML); - setCurrentURL(media_url); -} +/** + * @file llfloaterhelpbrowser.cpp + * @brief HTML Help floater - uses embedded web browser control + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterhelpbrowser.h" + +#include "llfloaterreg.h" +#include "llhttpconstants.h" +#include "llpluginclassmedia.h" +#include "llmediactrl.h" +#include "llviewerwindow.h" +#include "llviewercontrol.h" +#include "llweb.h" +#include "llui.h" + +#include "llurlhistory.h" +#include "llviewermedia.h" +#include "llviewerhelp.h" + + +LLFloaterHelpBrowser::LLFloaterHelpBrowser(const LLSD& key) + : LLFloater(key) +{ +} + +bool LLFloaterHelpBrowser::postBuild() +{ + mBrowser = getChild("browser"); + mBrowser->addObserver(this); + mBrowser->setErrorPageURL(gSavedSettings.getString("GenericErrorPageURL")); + + childSetAction("open_browser", onClickOpenWebBrowser, this); + + buildURLHistory(); + return true; +} + +void LLFloaterHelpBrowser::buildURLHistory() +{ + // Get all of the entries in the "browser" collection + LLSD browser_history = LLURLHistory::getURLHistory("browser"); + + // initialize URL history in the plugin + LLPluginClassMedia *plugin = mBrowser->getMediaPlugin(); + if (plugin) + { + plugin->initializeUrlHistory(browser_history); + } +} + +void LLFloaterHelpBrowser::onOpen(const LLSD& key) +{ + gSavedSettings.setBOOL("HelpFloaterOpen", true); + + std::string topic = key.asString(); + mBrowser->navigateTo(LLViewerHelp::instance().getURL(topic)); +} + +//virtual +void LLFloaterHelpBrowser::onClose(bool app_quitting) +{ + if (!app_quitting) + { + gSavedSettings.setBOOL("HelpFloaterOpen", false); + } + // really really destroy the help browser when it's closed, it'll be recreated. + destroy(); // really destroy this dialog on closure, it's relatively heavyweight. +} + +void LLFloaterHelpBrowser::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) +{ + switch (event) + { + case MEDIA_EVENT_LOCATION_CHANGED: + setCurrentURL(self->getLocation()); + break; + + case MEDIA_EVENT_NAVIGATE_BEGIN: + getChild("status_text")->setValue(getString("loading_text")); + break; + + case MEDIA_EVENT_NAVIGATE_COMPLETE: + getChild("status_text")->setValue(getString("done_text")); + break; + + default: + break; + } +} + +void LLFloaterHelpBrowser::setCurrentURL(const std::string& url) +{ + mCurrentURL = url; + + // redirects will navigate momentarily to about:blank, don't add to history + if (mCurrentURL != "about:blank") + { + // Serialize url history + LLURLHistory::removeURL("browser", mCurrentURL); + LLURLHistory::addURL("browser", mCurrentURL); + } +} + +//static +void LLFloaterHelpBrowser::onClickClose(void* user_data) +{ + LLFloaterHelpBrowser* self = (LLFloaterHelpBrowser*)user_data; + + self->closeFloater(); +} + +//static +void LLFloaterHelpBrowser::onClickOpenWebBrowser(void* user_data) +{ + LLFloaterHelpBrowser* self = (LLFloaterHelpBrowser*)user_data; + + std::string url = self->mCurrentURL.empty() ? + self->mBrowser->getHomePageUrl() : + self->mCurrentURL; + LLWeb::loadURLExternal(url); +} + +void LLFloaterHelpBrowser::openMedia(const std::string& media_url) +{ + // explicitly make the media mime type for this floater since it will + // only ever display one type of content (Web). + mBrowser->setHomePageUrl(media_url, HTTP_CONTENT_TEXT_HTML); + mBrowser->navigateTo(media_url, HTTP_CONTENT_TEXT_HTML); + setCurrentURL(media_url); +} diff --git a/indra/newview/llfloaterhelpbrowser.h b/indra/newview/llfloaterhelpbrowser.h index 9d16d4eb16..cbf3704190 100644 --- a/indra/newview/llfloaterhelpbrowser.h +++ b/indra/newview/llfloaterhelpbrowser.h @@ -1,65 +1,65 @@ -/** - * @file llfloaterhelpbrowser.h - * @brief HTML Help floater - uses embedded web browser control - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERHELPBROWSER_H -#define LL_LLFLOATERHELPBROWSER_H - -#include "llfloater.h" -#include "llmediactrl.h" - - -class LLMediaCtrl; - -class LLFloaterHelpBrowser : - public LLFloater, - public LLViewerMediaObserver -{ - public: - LLFloaterHelpBrowser(const LLSD& key); - - /*virtual*/ bool postBuild(); - /*virtual*/ void onClose(bool app_quitting); - /*virtual*/ void onOpen(const LLSD& key); - - // inherited from LLViewerMediaObserver - /*virtual*/ void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event); - - void openMedia(const std::string& media_url); - - private: - void buildURLHistory(); - void setCurrentURL(const std::string& url); - - static void onClickClose(void* user_data); - static void onClickOpenWebBrowser(void* user_data); - - private: - LLMediaCtrl* mBrowser; - std::string mCurrentURL; -}; - -#endif // LL_LLFLOATERHELPBROWSER_H - +/** + * @file llfloaterhelpbrowser.h + * @brief HTML Help floater - uses embedded web browser control + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERHELPBROWSER_H +#define LL_LLFLOATERHELPBROWSER_H + +#include "llfloater.h" +#include "llmediactrl.h" + + +class LLMediaCtrl; + +class LLFloaterHelpBrowser : + public LLFloater, + public LLViewerMediaObserver +{ + public: + LLFloaterHelpBrowser(const LLSD& key); + + /*virtual*/ bool postBuild(); + /*virtual*/ void onClose(bool app_quitting); + /*virtual*/ void onOpen(const LLSD& key); + + // inherited from LLViewerMediaObserver + /*virtual*/ void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event); + + void openMedia(const std::string& media_url); + + private: + void buildURLHistory(); + void setCurrentURL(const std::string& url); + + static void onClickClose(void* user_data); + static void onClickOpenWebBrowser(void* user_data); + + private: + LLMediaCtrl* mBrowser; + std::string mCurrentURL; +}; + +#endif // LL_LLFLOATERHELPBROWSER_H + diff --git a/indra/newview/llfloaterhoverheight.cpp b/indra/newview/llfloaterhoverheight.cpp index b5cf4ad485..ef3e306da9 100644 --- a/indra/newview/llfloaterhoverheight.cpp +++ b/indra/newview/llfloaterhoverheight.cpp @@ -1,155 +1,155 @@ -/** -* @file llfloaterhoverheight.cpp -* @brief Controller for self avatar hover height -* @author vir@lindenlab.com -* -* $LicenseInfo:firstyear=2014&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2014, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterhoverheight.h" -#include "llsliderctrl.h" -#include "llviewercontrol.h" -#include "llsdserialize.h" -#include "llagent.h" -#include "llviewerregion.h" -#include "llvoavatarself.h" - -LLFloaterHoverHeight::LLFloaterHoverHeight(const LLSD& key) : LLFloater(key) -{ -} - -void LLFloaterHoverHeight::syncFromPreferenceSetting(void *user_data, bool update_offset) -{ - F32 value = gSavedPerAccountSettings.getF32("AvatarHoverOffsetZ"); - - LLFloaterHoverHeight *self = static_cast(user_data); - LLSliderCtrl* sldrCtrl = self->getChild("HoverHeightSlider"); - sldrCtrl->setValue(value,false); - - if (isAgentAvatarValid() && update_offset) - { - LLVector3 offset(0.0, 0.0, llclamp(value,MIN_HOVER_Z,MAX_HOVER_Z)); - LL_INFOS("Avatar") << "setting hover from preference setting " << offset[2] << LL_ENDL; - gAgentAvatarp->setHoverOffset(offset); - //gAgentAvatarp->sendHoverHeight(); - } -} - -bool LLFloaterHoverHeight::postBuild() -{ - LLSliderCtrl* sldrCtrl = getChild("HoverHeightSlider"); - sldrCtrl->setMinValue(MIN_HOVER_Z); - sldrCtrl->setMaxValue(MAX_HOVER_Z); - sldrCtrl->setSliderMouseUpCallback(boost::bind(&LLFloaterHoverHeight::onFinalCommit,this)); - sldrCtrl->setSliderEditorCommitCallback(boost::bind(&LLFloaterHoverHeight::onFinalCommit,this)); - childSetCommitCallback("HoverHeightSlider", &LLFloaterHoverHeight::onSliderMoved, NULL); - - // Initialize slider from pref setting. - syncFromPreferenceSetting(this); - // Update slider on future pref changes. - if (gSavedPerAccountSettings.getControl("AvatarHoverOffsetZ")) - { - gSavedPerAccountSettings.getControl("AvatarHoverOffsetZ")->getCommitSignal()->connect(boost::bind(&syncFromPreferenceSetting, this, false)); - } - else - { - LL_WARNS() << "Control not found for AvatarHoverOffsetZ" << LL_ENDL; - } - - updateEditEnabled(); - - if (!mRegionChangedSlot.connected()) - { - mRegionChangedSlot = gAgent.addRegionChangedCallback(boost::bind(&LLFloaterHoverHeight::onRegionChanged,this)); - } - // Set up based on initial region. - onRegionChanged(); - - return true; -} - -void LLFloaterHoverHeight::onClose(bool app_quitting) -{ - if (mRegionChangedSlot.connected()) - { - mRegionChangedSlot.disconnect(); - } -} - -// static -void LLFloaterHoverHeight::onSliderMoved(LLUICtrl* ctrl, void* userData) -{ - if (isAgentAvatarValid()) - { - LLSliderCtrl* sldrCtrl = static_cast(ctrl); - F32 value = sldrCtrl->getValueF32(); - LLVector3 offset(0.0, 0.0, llclamp(value, MIN_HOVER_Z, MAX_HOVER_Z)); - LL_INFOS("Avatar") << "setting hover from slider moved" << offset[2] << LL_ENDL; - gAgentAvatarp->setHoverOffset(offset, false); - } -} - -// Do send-to-the-server work when slider drag completes, or new -// value entered as text. -void LLFloaterHoverHeight::onFinalCommit() -{ - LLSliderCtrl* sldrCtrl = getChild("HoverHeightSlider"); - F32 value = sldrCtrl->getValueF32(); - gSavedPerAccountSettings.setF32("AvatarHoverOffsetZ",value); -} - -void LLFloaterHoverHeight::onRegionChanged() -{ - LLViewerRegion *region = gAgent.getRegion(); - if (region && region->simulatorFeaturesReceived()) - { - updateEditEnabled(); - } - else if (region) - { - region->setSimulatorFeaturesReceivedCallback(boost::bind(&LLFloaterHoverHeight::onSimulatorFeaturesReceived,this,_1)); - } -} - -void LLFloaterHoverHeight::onSimulatorFeaturesReceived(const LLUUID ®ion_id) -{ - LLViewerRegion *region = gAgent.getRegion(); - if (region && (region->getRegionID()==region_id)) - { - updateEditEnabled(); - } -} - -void LLFloaterHoverHeight::updateEditEnabled() -{ - bool enabled = gAgent.getRegion() && gAgent.getRegion()->avatarHoverHeightEnabled(); - LLSliderCtrl* sldrCtrl = getChild("HoverHeightSlider"); - sldrCtrl->setEnabled(enabled); - if (enabled) - { - syncFromPreferenceSetting(this); - } -} - - +/** +* @file llfloaterhoverheight.cpp +* @brief Controller for self avatar hover height +* @author vir@lindenlab.com +* +* $LicenseInfo:firstyear=2014&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2014, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterhoverheight.h" +#include "llsliderctrl.h" +#include "llviewercontrol.h" +#include "llsdserialize.h" +#include "llagent.h" +#include "llviewerregion.h" +#include "llvoavatarself.h" + +LLFloaterHoverHeight::LLFloaterHoverHeight(const LLSD& key) : LLFloater(key) +{ +} + +void LLFloaterHoverHeight::syncFromPreferenceSetting(void *user_data, bool update_offset) +{ + F32 value = gSavedPerAccountSettings.getF32("AvatarHoverOffsetZ"); + + LLFloaterHoverHeight *self = static_cast(user_data); + LLSliderCtrl* sldrCtrl = self->getChild("HoverHeightSlider"); + sldrCtrl->setValue(value,false); + + if (isAgentAvatarValid() && update_offset) + { + LLVector3 offset(0.0, 0.0, llclamp(value,MIN_HOVER_Z,MAX_HOVER_Z)); + LL_INFOS("Avatar") << "setting hover from preference setting " << offset[2] << LL_ENDL; + gAgentAvatarp->setHoverOffset(offset); + //gAgentAvatarp->sendHoverHeight(); + } +} + +bool LLFloaterHoverHeight::postBuild() +{ + LLSliderCtrl* sldrCtrl = getChild("HoverHeightSlider"); + sldrCtrl->setMinValue(MIN_HOVER_Z); + sldrCtrl->setMaxValue(MAX_HOVER_Z); + sldrCtrl->setSliderMouseUpCallback(boost::bind(&LLFloaterHoverHeight::onFinalCommit,this)); + sldrCtrl->setSliderEditorCommitCallback(boost::bind(&LLFloaterHoverHeight::onFinalCommit,this)); + childSetCommitCallback("HoverHeightSlider", &LLFloaterHoverHeight::onSliderMoved, NULL); + + // Initialize slider from pref setting. + syncFromPreferenceSetting(this); + // Update slider on future pref changes. + if (gSavedPerAccountSettings.getControl("AvatarHoverOffsetZ")) + { + gSavedPerAccountSettings.getControl("AvatarHoverOffsetZ")->getCommitSignal()->connect(boost::bind(&syncFromPreferenceSetting, this, false)); + } + else + { + LL_WARNS() << "Control not found for AvatarHoverOffsetZ" << LL_ENDL; + } + + updateEditEnabled(); + + if (!mRegionChangedSlot.connected()) + { + mRegionChangedSlot = gAgent.addRegionChangedCallback(boost::bind(&LLFloaterHoverHeight::onRegionChanged,this)); + } + // Set up based on initial region. + onRegionChanged(); + + return true; +} + +void LLFloaterHoverHeight::onClose(bool app_quitting) +{ + if (mRegionChangedSlot.connected()) + { + mRegionChangedSlot.disconnect(); + } +} + +// static +void LLFloaterHoverHeight::onSliderMoved(LLUICtrl* ctrl, void* userData) +{ + if (isAgentAvatarValid()) + { + LLSliderCtrl* sldrCtrl = static_cast(ctrl); + F32 value = sldrCtrl->getValueF32(); + LLVector3 offset(0.0, 0.0, llclamp(value, MIN_HOVER_Z, MAX_HOVER_Z)); + LL_INFOS("Avatar") << "setting hover from slider moved" << offset[2] << LL_ENDL; + gAgentAvatarp->setHoverOffset(offset, false); + } +} + +// Do send-to-the-server work when slider drag completes, or new +// value entered as text. +void LLFloaterHoverHeight::onFinalCommit() +{ + LLSliderCtrl* sldrCtrl = getChild("HoverHeightSlider"); + F32 value = sldrCtrl->getValueF32(); + gSavedPerAccountSettings.setF32("AvatarHoverOffsetZ",value); +} + +void LLFloaterHoverHeight::onRegionChanged() +{ + LLViewerRegion *region = gAgent.getRegion(); + if (region && region->simulatorFeaturesReceived()) + { + updateEditEnabled(); + } + else if (region) + { + region->setSimulatorFeaturesReceivedCallback(boost::bind(&LLFloaterHoverHeight::onSimulatorFeaturesReceived,this,_1)); + } +} + +void LLFloaterHoverHeight::onSimulatorFeaturesReceived(const LLUUID ®ion_id) +{ + LLViewerRegion *region = gAgent.getRegion(); + if (region && (region->getRegionID()==region_id)) + { + updateEditEnabled(); + } +} + +void LLFloaterHoverHeight::updateEditEnabled() +{ + bool enabled = gAgent.getRegion() && gAgent.getRegion()->avatarHoverHeightEnabled(); + LLSliderCtrl* sldrCtrl = getChild("HoverHeightSlider"); + sldrCtrl->setEnabled(enabled); + if (enabled) + { + syncFromPreferenceSetting(this); + } +} + + diff --git a/indra/newview/llfloaterhoverheight.h b/indra/newview/llfloaterhoverheight.h index 285007d4bf..029370b26b 100644 --- a/indra/newview/llfloaterhoverheight.h +++ b/indra/newview/llfloaterhoverheight.h @@ -1,52 +1,52 @@ -/** -* @file llfloaterhoverheight.h -* @brief Controller for self avatar hover height. -* @author vir@lindenlab.com -* -* $LicenseInfo:firstyear=2014&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2014, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ -#ifndef LL_LLFLOATERHOVERHEIGHT_H -#define LL_LLFLOATERHOVERHEIGHT_H - -#include "llfloater.h" - -class LLFloaterHoverHeight: public LLFloater -{ -public: - LLFloaterHoverHeight(const LLSD& key); - bool postBuild(); - - static void onSliderMoved(LLUICtrl* ctrl, void* userData); - - void onFinalCommit(); - - static void syncFromPreferenceSetting(void *user_data, bool update_offset = true); - - void onRegionChanged(); - void onSimulatorFeaturesReceived(const LLUUID ®ion_id); - void updateEditEnabled(); - - /*virtual*/ void onClose(bool app_quitting); - boost::signals2::connection mRegionChangedSlot; -}; - -#endif +/** +* @file llfloaterhoverheight.h +* @brief Controller for self avatar hover height. +* @author vir@lindenlab.com +* +* $LicenseInfo:firstyear=2014&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2014, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LL_LLFLOATERHOVERHEIGHT_H +#define LL_LLFLOATERHOVERHEIGHT_H + +#include "llfloater.h" + +class LLFloaterHoverHeight: public LLFloater +{ +public: + LLFloaterHoverHeight(const LLSD& key); + bool postBuild(); + + static void onSliderMoved(LLUICtrl* ctrl, void* userData); + + void onFinalCommit(); + + static void syncFromPreferenceSetting(void *user_data, bool update_offset = true); + + void onRegionChanged(); + void onSimulatorFeaturesReceived(const LLUUID ®ion_id); + void updateEditEnabled(); + + /*virtual*/ void onClose(bool app_quitting); + boost::signals2::connection mRegionChangedSlot; +}; + +#endif diff --git a/indra/newview/llfloaterhowto.cpp b/indra/newview/llfloaterhowto.cpp index 643072b5e2..6a9f113d53 100644 --- a/indra/newview/llfloaterhowto.cpp +++ b/indra/newview/llfloaterhowto.cpp @@ -1,92 +1,92 @@ -/** - * @file llfloaterhowto.cpp - * @brief A variant of web floater meant to open guidebook - * - * $LicenseInfo:firstyear=2021&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2021, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterhowto.h" - -#include "llfloaterreg.h" -#include "llviewercontrol.h" -#include "llweb.h" - - -constexpr S32 STACK_WIDTH = 300; -constexpr S32 STACK_HEIGHT = 505; // content will be 500 - -LLFloaterHowTo::LLFloaterHowTo(const Params& key) : - LLFloaterWebContent(key) -{ - mShowPageTitle = false; -} - -bool LLFloaterHowTo::postBuild() -{ - LLFloaterWebContent::postBuild(); - - return true; -} - -void LLFloaterHowTo::onOpen(const LLSD& key) -{ - LLFloaterWebContent::Params p(key); - if (!p.url.isProvided() || p.url.getValue().empty()) - { - std::string url = gSavedSettings.getString("GuidebookURL"); - p.url = LLWeb::expandURLSubstitutions(url, LLSD()); - } - p.show_chrome = false; - - LLFloaterWebContent::onOpen(p); - - if (p.preferred_media_size().isEmpty()) - { - // Elements from LLFloaterWebContent did not pick up restored size (save_rect) of LLFloaterHowTo - // set the stack size and position (alternative to preferred_media_size) - LLLayoutStack *stack = getChild("stack1"); - LLRect stack_rect = stack->getRect(); - stack->reshape(STACK_WIDTH, STACK_HEIGHT); - stack->setOrigin(stack_rect.mLeft, stack_rect.mTop - STACK_HEIGHT); - stack->updateLayout(); - } -} - -LLFloaterHowTo* LLFloaterHowTo::getInstance() -{ - return LLFloaterReg::getTypedInstance("guidebook"); -} - -bool LLFloaterHowTo::handleKeyHere(KEY key, MASK mask) -{ - bool handled = false; - - if (KEY_F1 == key ) - { - closeFloater(); - handled = true; - } - - return handled; -} +/** + * @file llfloaterhowto.cpp + * @brief A variant of web floater meant to open guidebook + * + * $LicenseInfo:firstyear=2021&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2021, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterhowto.h" + +#include "llfloaterreg.h" +#include "llviewercontrol.h" +#include "llweb.h" + + +constexpr S32 STACK_WIDTH = 300; +constexpr S32 STACK_HEIGHT = 505; // content will be 500 + +LLFloaterHowTo::LLFloaterHowTo(const Params& key) : + LLFloaterWebContent(key) +{ + mShowPageTitle = false; +} + +bool LLFloaterHowTo::postBuild() +{ + LLFloaterWebContent::postBuild(); + + return true; +} + +void LLFloaterHowTo::onOpen(const LLSD& key) +{ + LLFloaterWebContent::Params p(key); + if (!p.url.isProvided() || p.url.getValue().empty()) + { + std::string url = gSavedSettings.getString("GuidebookURL"); + p.url = LLWeb::expandURLSubstitutions(url, LLSD()); + } + p.show_chrome = false; + + LLFloaterWebContent::onOpen(p); + + if (p.preferred_media_size().isEmpty()) + { + // Elements from LLFloaterWebContent did not pick up restored size (save_rect) of LLFloaterHowTo + // set the stack size and position (alternative to preferred_media_size) + LLLayoutStack *stack = getChild("stack1"); + LLRect stack_rect = stack->getRect(); + stack->reshape(STACK_WIDTH, STACK_HEIGHT); + stack->setOrigin(stack_rect.mLeft, stack_rect.mTop - STACK_HEIGHT); + stack->updateLayout(); + } +} + +LLFloaterHowTo* LLFloaterHowTo::getInstance() +{ + return LLFloaterReg::getTypedInstance("guidebook"); +} + +bool LLFloaterHowTo::handleKeyHere(KEY key, MASK mask) +{ + bool handled = false; + + if (KEY_F1 == key ) + { + closeFloater(); + handled = true; + } + + return handled; +} diff --git a/indra/newview/llfloaterhud.cpp b/indra/newview/llfloaterhud.cpp index a08967c09d..c5ef187a46 100644 --- a/indra/newview/llfloaterhud.cpp +++ b/indra/newview/llfloaterhud.cpp @@ -1,84 +1,84 @@ -/** - * @file llfloaterhud.cpp - * @brief Implementation of HUD floater - * - * $LicenseInfo:firstyear=2008&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterhud.h" - -// Viewer libs -#include "llviewercontrol.h" -#include "llmediactrl.h" - -// Linden libs -#include "llnotificationsutil.h" -#include "lluictrlfactory.h" - - -///---------------------------------------------------------------------------- -/// Class LLFloaterHUD -///---------------------------------------------------------------------------- -#define super LLFloater /* superclass */ - -// Default constructor -LLFloaterHUD::LLFloaterHUD(const LLSD& key) -: LLFloater(key), - mWebBrowser(0) -{ - // do not build the floater if there the url is empty - if (gSavedSettings.getString("TutorialURL") == "") - { - LLNotificationsUtil::add("TutorialNotFound"); - return; - } - - // Opaque background since we never get the focus - setBackgroundOpaque(true); -} - -bool LLFloaterHUD::postBuild() -{ - mWebBrowser = getChild("floater_hud_browser" ); - if (mWebBrowser) - { - // This is a "chrome" floater, so we don't want anything to - // take focus (as the user needs to be able to walk with - // arrow keys during tutorial). - mWebBrowser->setTakeFocusOnClick(false); - - std::string language = LLUI::getLanguage(); - std::string base_url = gSavedSettings.getString("TutorialURL"); - - std::string url = base_url + language + "/"; - mWebBrowser->navigateTo(url); - } - - return true; -} - -// Destructor -LLFloaterHUD::~LLFloaterHUD() -{ -} +/** + * @file llfloaterhud.cpp + * @brief Implementation of HUD floater + * + * $LicenseInfo:firstyear=2008&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterhud.h" + +// Viewer libs +#include "llviewercontrol.h" +#include "llmediactrl.h" + +// Linden libs +#include "llnotificationsutil.h" +#include "lluictrlfactory.h" + + +///---------------------------------------------------------------------------- +/// Class LLFloaterHUD +///---------------------------------------------------------------------------- +#define super LLFloater /* superclass */ + +// Default constructor +LLFloaterHUD::LLFloaterHUD(const LLSD& key) +: LLFloater(key), + mWebBrowser(0) +{ + // do not build the floater if there the url is empty + if (gSavedSettings.getString("TutorialURL") == "") + { + LLNotificationsUtil::add("TutorialNotFound"); + return; + } + + // Opaque background since we never get the focus + setBackgroundOpaque(true); +} + +bool LLFloaterHUD::postBuild() +{ + mWebBrowser = getChild("floater_hud_browser" ); + if (mWebBrowser) + { + // This is a "chrome" floater, so we don't want anything to + // take focus (as the user needs to be able to walk with + // arrow keys during tutorial). + mWebBrowser->setTakeFocusOnClick(false); + + std::string language = LLUI::getLanguage(); + std::string base_url = gSavedSettings.getString("TutorialURL"); + + std::string url = base_url + language + "/"; + mWebBrowser->navigateTo(url); + } + + return true; +} + +// Destructor +LLFloaterHUD::~LLFloaterHUD() +{ +} diff --git a/indra/newview/llfloaterhud.h b/indra/newview/llfloaterhud.h index ab7b49eb08..710a44fc57 100644 --- a/indra/newview/llfloaterhud.h +++ b/indra/newview/llfloaterhud.h @@ -1,50 +1,50 @@ -/** - * @file llfloaterhud.h - * @brief A floater showing the HUD tutorial - * - * $LicenseInfo:firstyear=2008&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERHUD_H -#define LL_LLFLOATERHUD_H - -#include "llfloater.h" - -class LLMediaCtrl; - -class LLFloaterHUD : public LLFloater -{ - friend class LLFloaterReg; -public: - - bool postBuild() override; - -private: - // Handles its own construction and destruction, so private. - LLFloaterHUD(const LLSD& key); - /*virtual*/ ~LLFloaterHUD(); - -private: - LLMediaCtrl* mWebBrowser; ///< the actual web browser control -}; - -#endif // LL_LLFLOATERHUD_H +/** + * @file llfloaterhud.h + * @brief A floater showing the HUD tutorial + * + * $LicenseInfo:firstyear=2008&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERHUD_H +#define LL_LLFLOATERHUD_H + +#include "llfloater.h" + +class LLMediaCtrl; + +class LLFloaterHUD : public LLFloater +{ + friend class LLFloaterReg; +public: + + bool postBuild() override; + +private: + // Handles its own construction and destruction, so private. + LLFloaterHUD(const LLSD& key); + /*virtual*/ ~LLFloaterHUD(); + +private: + LLMediaCtrl* mWebBrowser; ///< the actual web browser control +}; + +#endif // LL_LLFLOATERHUD_H diff --git a/indra/newview/llfloaterimagepreview.cpp b/indra/newview/llfloaterimagepreview.cpp index c727021dd6..08389b8912 100644 --- a/indra/newview/llfloaterimagepreview.cpp +++ b/indra/newview/llfloaterimagepreview.cpp @@ -1,959 +1,959 @@ -/** - * @file llfloaterimagepreview.cpp - * @brief LLFloaterImagePreview class implementation - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterimagepreview.h" - -#include "llimagebmp.h" -#include "llimagetga.h" -#include "llimagejpeg.h" -#include "llimagepng.h" - -#include "llagent.h" -#include "llbutton.h" -#include "llcheckboxctrl.h" -#include "llcombobox.h" -#include "lldrawable.h" -#include "lldrawpoolavatar.h" -#include "llrender.h" -#include "llface.h" -#include "llfocusmgr.h" -#include "lltextbox.h" -#include "lltoolmgr.h" -#include "llui.h" -#include "llviewercamera.h" -#include "llviewerwindow.h" -#include "llviewerobjectlist.h" -#include "llvoavatar.h" -#include "pipeline.h" -#include "lluictrlfactory.h" -#include "llviewershadermgr.h" -#include "llviewertexturelist.h" -#include "llstring.h" - -#include "llendianswizzle.h" - -#include "llviewercontrol.h" -#include "lltrans.h" -#include "llimagedimensionsinfo.h" - -const S32 PREVIEW_BORDER_WIDTH = 2; -const S32 PREVIEW_RESIZE_HANDLE_SIZE = S32(RESIZE_HANDLE_WIDTH * OO_SQRT2) + PREVIEW_BORDER_WIDTH; -const S32 PREVIEW_HPAD = PREVIEW_RESIZE_HANDLE_SIZE; -const S32 PREVIEW_VPAD = -24 + 35; // yuk, hard coded -const S32 PREF_BUTTON_HEIGHT = 16 + 7 + 16 + 35; -const S32 PREVIEW_TEXTURE_HEIGHT = 320; - -//----------------------------------------------------------------------------- -// LLFloaterImagePreview() -//----------------------------------------------------------------------------- -LLFloaterImagePreview::LLFloaterImagePreview(const std::string& filename) : - LLFloaterNameDesc(filename), - - mAvatarPreview(NULL), - mSculptedPreview(NULL), - mLastMouseX(0), - mLastMouseY(0), - mImagep(NULL) -{ - loadImage(mFilenameAndPath); -} - -//----------------------------------------------------------------------------- -// postBuild() -//----------------------------------------------------------------------------- -bool LLFloaterImagePreview::postBuild() -{ - if (!LLFloaterNameDesc::postBuild()) - { - return false; - } - - LLCtrlSelectionInterface* iface = childGetSelectionInterface("clothing_type_combo"); - if (iface) - { - iface->selectFirstItem(); - } - childSetCommitCallback("clothing_type_combo", onPreviewTypeCommit, this); - - mPreviewRect.set(PREVIEW_HPAD, - PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD, - getRect().getWidth() - PREVIEW_HPAD, - PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); - mPreviewImageRect.set(0.f, 1.f, 1.f, 0.f); - - getChildView("bad_image_text")->setVisible(false); - - if (mRawImagep.notNull() && gAgent.getRegion() != NULL) - { - mAvatarPreview = new LLImagePreviewAvatar(256, 256); - mAvatarPreview->setPreviewTarget("mPelvis", "mUpperBodyMesh0", mRawImagep, 2.f, false); - - mSculptedPreview = new LLImagePreviewSculpted(256, 256); - mSculptedPreview->setPreviewTarget(mRawImagep, 2.0f); - - if (mRawImagep->getWidth() * mRawImagep->getHeight() <= LL_IMAGE_REZ_LOSSLESS_CUTOFF * LL_IMAGE_REZ_LOSSLESS_CUTOFF) - { - // We want "lossless_check" to be unchecked when it is disabled, regardless of - // LosslessJ2CUpload state, so only assign control when enabling checkbox - LLCheckBoxCtrl* check_box = getChild("lossless_check"); - check_box->setEnabled(true); - check_box->setControlVariable(gSavedSettings.getControl("LosslessJ2CUpload")); - } - } - else - { - mAvatarPreview = NULL; - mSculptedPreview = NULL; - getChildView("bad_image_text")->setVisible(true); - getChildView("clothing_type_combo")->setEnabled(false); - getChildView("ok_btn")->setEnabled(false); - - if(!mImageLoadError.empty()) - { - getChild("bad_image_text")->setValue(mImageLoadError.c_str()); - } - } - - getChild("ok_btn")->setCommitCallback(boost::bind(&LLFloaterNameDesc::onBtnOK, this)); - - return true; -} - -//----------------------------------------------------------------------------- -// LLFloaterImagePreview() -//----------------------------------------------------------------------------- -LLFloaterImagePreview::~LLFloaterImagePreview() -{ - clearAllPreviewTextures(); - - mRawImagep = NULL; - mImagep = NULL ; -} - -//static -//----------------------------------------------------------------------------- -// onPreviewTypeCommit() -//----------------------------------------------------------------------------- -void LLFloaterImagePreview::onPreviewTypeCommit(LLUICtrl* ctrl, void* userdata) -{ - LLFloaterImagePreview *fp =(LLFloaterImagePreview *)userdata; - - if (!fp->mAvatarPreview || !fp->mSculptedPreview) - { - return; - } - - S32 which_mode = 0; - - LLCtrlSelectionInterface* iface = fp->childGetSelectionInterface("clothing_type_combo"); - if (iface) - { - which_mode = iface->getFirstSelectedIndex(); - } - - switch(which_mode) - { - case 0: - break; - case 1: - fp->mAvatarPreview->setPreviewTarget("mSkull", "mHairMesh0", fp->mRawImagep, 0.4f, false); - break; - case 2: - fp->mAvatarPreview->setPreviewTarget("mSkull", "mHeadMesh0", fp->mRawImagep, 0.4f, false); - break; - case 3: - fp->mAvatarPreview->setPreviewTarget("mChest", "mUpperBodyMesh0", fp->mRawImagep, 1.0f, false); - break; - case 4: - fp->mAvatarPreview->setPreviewTarget("mKneeLeft", "mLowerBodyMesh0", fp->mRawImagep, 1.2f, false); - break; - case 5: - fp->mAvatarPreview->setPreviewTarget("mSkull", "mHeadMesh0", fp->mRawImagep, 0.4f, true); - break; - case 6: - fp->mAvatarPreview->setPreviewTarget("mChest", "mUpperBodyMesh0", fp->mRawImagep, 1.2f, true); - break; - case 7: - fp->mAvatarPreview->setPreviewTarget("mKneeLeft", "mLowerBodyMesh0", fp->mRawImagep, 1.2f, true); - break; - case 8: - fp->mAvatarPreview->setPreviewTarget("mKneeLeft", "mSkirtMesh0", fp->mRawImagep, 1.3f, false); - break; - case 9: - fp->mSculptedPreview->setPreviewTarget(fp->mRawImagep, 2.0f); - break; - default: - break; - } - - fp->mAvatarPreview->refresh(); - fp->mSculptedPreview->refresh(); -} - - -//----------------------------------------------------------------------------- -// clearAllPreviewTextures() -//----------------------------------------------------------------------------- -void LLFloaterImagePreview::clearAllPreviewTextures() -{ - if (mAvatarPreview) - { - mAvatarPreview->clearPreviewTexture("mHairMesh0"); - mAvatarPreview->clearPreviewTexture("mUpperBodyMesh0"); - mAvatarPreview->clearPreviewTexture("mLowerBodyMesh0"); - mAvatarPreview->clearPreviewTexture("mHeadMesh0"); - mAvatarPreview->clearPreviewTexture("mUpperBodyMesh0"); - mAvatarPreview->clearPreviewTexture("mLowerBodyMesh0"); - mAvatarPreview->clearPreviewTexture("mSkirtMesh0"); - } -} - -//----------------------------------------------------------------------------- -// draw() -//----------------------------------------------------------------------------- -void LLFloaterImagePreview::draw() -{ - LLFloater::draw(); - LLRect r = getRect(); - - if (mRawImagep.notNull()) - { - LLCtrlSelectionInterface* iface = childGetSelectionInterface("clothing_type_combo"); - U32 selected = 0; - if (iface) - selected = iface->getFirstSelectedIndex(); - - if (selected <= 0) - { - gl_rect_2d_checkerboard(mPreviewRect); - - if(mImagep.notNull()) - { - gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, mImagep->getTexName()); - } - else - { - mImagep = LLViewerTextureManager::getLocalTexture(mRawImagep.get(), false) ; - - gGL.getTexUnit(0)->unbind(mImagep->getTarget()) ; - gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, mImagep->getTexName()); - stop_glerror(); - - gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_BILINEAR); - - gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_CLAMP); - if (mAvatarPreview) - { - mAvatarPreview->setTexture(mImagep->getTexName()); - mSculptedPreview->setTexture(mImagep->getTexName()); - } - } - - gGL.color3f(1.f, 1.f, 1.f); - gGL.begin( LLRender::QUADS ); - { - gGL.texCoord2f(mPreviewImageRect.mLeft, mPreviewImageRect.mTop); - gGL.vertex2i(PREVIEW_HPAD, PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD); - gGL.texCoord2f(mPreviewImageRect.mLeft, mPreviewImageRect.mBottom); - gGL.vertex2i(PREVIEW_HPAD, PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); - gGL.texCoord2f(mPreviewImageRect.mRight, mPreviewImageRect.mBottom); - gGL.vertex2i(r.getWidth() - PREVIEW_HPAD, PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); - gGL.texCoord2f(mPreviewImageRect.mRight, mPreviewImageRect.mTop); - gGL.vertex2i(r.getWidth() - PREVIEW_HPAD, PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD); - } - gGL.end(); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - stop_glerror(); - } - else - { - if ((mAvatarPreview) && (mSculptedPreview)) - { - gGL.color3f(1.f, 1.f, 1.f); - - if (selected == 9) - { - gGL.getTexUnit(0)->bind(mSculptedPreview); - } - else - { - gGL.getTexUnit(0)->bind(mAvatarPreview); - } - - gGL.begin( LLRender::QUADS ); - { - gGL.texCoord2f(0.f, 1.f); - gGL.vertex2i(PREVIEW_HPAD, PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD); - gGL.texCoord2f(0.f, 0.f); - gGL.vertex2i(PREVIEW_HPAD, PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); - gGL.texCoord2f(1.f, 0.f); - gGL.vertex2i(r.getWidth() - PREVIEW_HPAD, PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); - gGL.texCoord2f(1.f, 1.f); - gGL.vertex2i(r.getWidth() - PREVIEW_HPAD, PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD); - } - gGL.end(); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - } - } - } -} - - -//----------------------------------------------------------------------------- -// loadImage() -//----------------------------------------------------------------------------- -bool LLFloaterImagePreview::loadImage(const std::string& src_filename) -{ - try - { - std::string exten = gDirUtilp->getExtension(src_filename); - U32 codec = LLImageBase::getCodecFromExtension(exten); - - LLImageDimensionsInfo image_info; - if (!image_info.load(src_filename,codec)) - { - mImageLoadError = image_info.getLastError(); - return false; - } - - S32 max_width = gSavedSettings.getS32("max_texture_dimension_X"); - S32 max_height = gSavedSettings.getS32("max_texture_dimension_Y"); - - if ((image_info.getWidth() > max_width) || (image_info.getHeight() > max_height)) - { - LLStringUtil::format_map_t args; - args["WIDTH"] = llformat("%d", max_width); - args["HEIGHT"] = llformat("%d", max_height); - - mImageLoadError = LLTrans::getString("texture_load_dimensions_error", args); - return false; - } - - // Load the image - LLPointer image = LLImageFormatted::createFromType(codec); - if (image.isNull()) - { - return false; - } - if (!image->load(src_filename)) - { - return false; - } - // Decompress or expand it in a raw image structure - LLPointer raw_image = new LLImageRaw; - if (!image->decode(raw_image, 0.0f)) - { - return false; - } - // Check the image constraints - if ((image->getComponents() != 3) && (image->getComponents() != 4)) - { - image->setLastError("Image files with less than 3 or more than 4 components are not supported."); - return false; - } - - raw_image->biasedScaleToPowerOfTwo(1024); - mRawImagep = raw_image; - } - catch (...) - { - LOG_UNHANDLED_EXCEPTION(""); - return false; - } - - return true; -} - -//----------------------------------------------------------------------------- -// handleMouseDown() -//----------------------------------------------------------------------------- -bool LLFloaterImagePreview::handleMouseDown(S32 x, S32 y, MASK mask) -{ - if (mPreviewRect.pointInRect(x, y)) - { - bringToFront( x, y ); - gFocusMgr.setMouseCapture(this); - gViewerWindow->hideCursor(); - mLastMouseX = x; - mLastMouseY = y; - return true; - } - - return LLFloater::handleMouseDown(x, y, mask); -} - -//----------------------------------------------------------------------------- -// handleMouseUp() -//----------------------------------------------------------------------------- -bool LLFloaterImagePreview::handleMouseUp(S32 x, S32 y, MASK mask) -{ - gFocusMgr.setMouseCapture(nullptr); - gViewerWindow->showCursor(); - return LLFloater::handleMouseUp(x, y, mask); -} - -//----------------------------------------------------------------------------- -// handleHover() -//----------------------------------------------------------------------------- -bool LLFloaterImagePreview::handleHover(S32 x, S32 y, MASK mask) -{ - MASK local_mask = mask & ~MASK_ALT; - - if (mAvatarPreview && hasMouseCapture()) - { - if (local_mask == MASK_PAN) - { - // pan here - LLCtrlSelectionInterface* iface = childGetSelectionInterface("clothing_type_combo"); - if (iface && iface->getFirstSelectedIndex() <= 0) - { - mPreviewImageRect.translate((F32)(x - mLastMouseX) * -0.005f * mPreviewImageRect.getWidth(), - (F32)(y - mLastMouseY) * -0.005f * mPreviewImageRect.getHeight()); - } - else - { - mAvatarPreview->pan((F32)(x - mLastMouseX) * -0.005f, (F32)(y - mLastMouseY) * -0.005f); - mSculptedPreview->pan((F32)(x - mLastMouseX) * -0.005f, (F32)(y - mLastMouseY) * -0.005f); - } - } - else if (local_mask == MASK_ORBIT) - { - F32 yaw_radians = (F32)(x - mLastMouseX) * -0.01f; - F32 pitch_radians = (F32)(y - mLastMouseY) * 0.02f; - - mAvatarPreview->rotate(yaw_radians, pitch_radians); - mSculptedPreview->rotate(yaw_radians, pitch_radians); - } - else - { - LLCtrlSelectionInterface* iface = childGetSelectionInterface("clothing_type_combo"); - if (iface && iface->getFirstSelectedIndex() <= 0) - { - F32 zoom_amt = (F32)(y - mLastMouseY) * -0.002f; - mPreviewImageRect.stretch(zoom_amt); - } - else - { - F32 yaw_radians = (F32)(x - mLastMouseX) * -0.01f; - F32 zoom_amt = (F32)(y - mLastMouseY) * 0.02f; - - mAvatarPreview->rotate(yaw_radians, 0.f); - mAvatarPreview->zoom(zoom_amt); - mSculptedPreview->rotate(yaw_radians, 0.f); - mSculptedPreview->zoom(zoom_amt); - } - } - - LLCtrlSelectionInterface* iface = childGetSelectionInterface("clothing_type_combo"); - if (iface && iface->getFirstSelectedIndex() <= 0) - { - if (mPreviewImageRect.getWidth() > 1.f) - { - mPreviewImageRect.stretch((1.f - mPreviewImageRect.getWidth()) * 0.5f); - } - else if (mPreviewImageRect.getWidth() < 0.1f) - { - mPreviewImageRect.stretch((0.1f - mPreviewImageRect.getWidth()) * 0.5f); - } - - if (mPreviewImageRect.getHeight() > 1.f) - { - mPreviewImageRect.stretch((1.f - mPreviewImageRect.getHeight()) * 0.5f); - } - else if (mPreviewImageRect.getHeight() < 0.1f) - { - mPreviewImageRect.stretch((0.1f - mPreviewImageRect.getHeight()) * 0.5f); - } - - if (mPreviewImageRect.mLeft < 0.f) - { - mPreviewImageRect.translate(-mPreviewImageRect.mLeft, 0.f); - } - else if (mPreviewImageRect.mRight > 1.f) - { - mPreviewImageRect.translate(1.f - mPreviewImageRect.mRight, 0.f); - } - - if (mPreviewImageRect.mBottom < 0.f) - { - mPreviewImageRect.translate(0.f, -mPreviewImageRect.mBottom); - } - else if (mPreviewImageRect.mTop > 1.f) - { - mPreviewImageRect.translate(0.f, 1.f - mPreviewImageRect.mTop); - } - } - else - { - mAvatarPreview->refresh(); - mSculptedPreview->refresh(); - } - - LLUI::getInstance()->setMousePositionLocal(this, mLastMouseX, mLastMouseY); - } - - if (!mPreviewRect.pointInRect(x, y) || !mAvatarPreview || !mSculptedPreview) - { - return LLFloater::handleHover(x, y, mask); - } - else if (local_mask == MASK_ORBIT) - { - gViewerWindow->setCursor(UI_CURSOR_TOOLCAMERA); - } - else if (local_mask == MASK_PAN) - { - gViewerWindow->setCursor(UI_CURSOR_TOOLPAN); - } - else - { - gViewerWindow->setCursor(UI_CURSOR_TOOLZOOMIN); - } - - return true; -} - -//----------------------------------------------------------------------------- -// handleScrollWheel() -//----------------------------------------------------------------------------- -bool LLFloaterImagePreview::handleScrollWheel(S32 x, S32 y, S32 clicks) -{ - if (mPreviewRect.pointInRect(x, y) && mAvatarPreview) - { - mAvatarPreview->zoom((F32)clicks * -0.2f); - mAvatarPreview->refresh(); - - mSculptedPreview->zoom((F32)clicks * -0.2f); - mSculptedPreview->refresh(); - } - - return true; -} - -//----------------------------------------------------------------------------- -// onMouseCaptureLost() -//----------------------------------------------------------------------------- -// static -void LLFloaterImagePreview::onMouseCaptureLostImagePreview(LLMouseHandler* handler) -{ - gViewerWindow->showCursor(); -} - - -//----------------------------------------------------------------------------- -// LLImagePreviewAvatar -//----------------------------------------------------------------------------- -LLImagePreviewAvatar::LLImagePreviewAvatar(S32 width, S32 height) : LLViewerDynamicTexture(width, height, 3, ORDER_MIDDLE, false) -{ - mNeedsUpdate = true; - mTargetJoint = NULL; - mTargetMesh = NULL; - mCameraDistance = 0.f; - mCameraYaw = 0.f; - mCameraPitch = 0.f; - mCameraZoom = 1.f; - - mDummyAvatar = (LLVOAvatar*)gObjectList.createObjectViewer(LL_PCODE_LEGACY_AVATAR, gAgent.getRegion(), LLViewerObject::CO_FLAG_UI_AVATAR); - mDummyAvatar->mSpecialRenderMode = 2; - - mTextureName = 0; -} - - -LLImagePreviewAvatar::~LLImagePreviewAvatar() -{ - mDummyAvatar->markDead(); -} - -//virtual -S8 LLImagePreviewAvatar::getType() const -{ - return LLViewerDynamicTexture::LL_IMAGE_PREVIEW_AVATAR ; -} - -void LLImagePreviewAvatar::setPreviewTarget(const std::string& joint_name, const std::string& mesh_name, LLImageRaw* imagep, F32 distance, bool male) -{ - mTargetJoint = mDummyAvatar->mRoot->findJoint(joint_name); - // clear out existing test mesh - if (mTargetMesh) - { - mTargetMesh->setTestTexture(0); - } - - if (male) - { - mDummyAvatar->setVisualParamWeight( "male", 1.f ); - mDummyAvatar->updateVisualParams(); - mDummyAvatar->updateGeometry(mDummyAvatar->mDrawable); - } - else - { - mDummyAvatar->setVisualParamWeight( "male", 0.f ); - mDummyAvatar->updateVisualParams(); - mDummyAvatar->updateGeometry(mDummyAvatar->mDrawable); - } - mDummyAvatar->mRoot->setVisible(false, true); - - mTargetMesh = dynamic_cast(mDummyAvatar->mRoot->findJoint(mesh_name)); - mTargetMesh->setTestTexture(mTextureName); - mTargetMesh->setVisible(true, false); - mCameraDistance = distance; - mCameraZoom = 1.f; - mCameraPitch = 0.f; - mCameraYaw = 0.f; - mCameraOffset.clearVec(); -} - -//----------------------------------------------------------------------------- -// clearPreviewTexture() -//----------------------------------------------------------------------------- -void LLImagePreviewAvatar::clearPreviewTexture(const std::string& mesh_name) -{ - if (mDummyAvatar) - { - LLViewerJointMesh *mesh = dynamic_cast(mDummyAvatar->mRoot->findJoint(mesh_name)); - // clear out existing test mesh - if (mesh) - { - mesh->setTestTexture(0); - } - } -} - -//----------------------------------------------------------------------------- -// update() -//----------------------------------------------------------------------------- -bool LLImagePreviewAvatar::render() -{ - mNeedsUpdate = false; - LLVOAvatar* avatarp = mDummyAvatar; - - gGL.pushUIMatrix(); - gGL.loadUIIdentity(); - - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.pushMatrix(); - gGL.loadIdentity(); - gGL.ortho(0.0f, mFullWidth, 0.0f, mFullHeight, -1.0f, 1.0f); - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - gGL.loadIdentity(); - - - LLGLSUIDefault def; - gGL.color4f(0.15f, 0.2f, 0.3f, 1.f); - - gUIProgram.bind(); - - gl_rect_2d_simple( mFullWidth, mFullHeight ); - - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.popMatrix(); - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.popMatrix(); - - gGL.flush(); - LLVector3 target_pos = mTargetJoint->getWorldPosition(); - - LLQuaternion camera_rot = LLQuaternion(mCameraPitch, LLVector3::y_axis) * - LLQuaternion(mCameraYaw, LLVector3::z_axis); - - LLQuaternion av_rot = avatarp->mPelvisp->getWorldRotation() * camera_rot; - LLViewerCamera::getInstance()->setOriginAndLookAt( - target_pos + ((LLVector3(mCameraDistance, 0.f, 0.f) + mCameraOffset) * av_rot), // camera - LLVector3::z_axis, // up - target_pos + (mCameraOffset * av_rot) ); // point of interest - - stop_glerror(); - - LLViewerCamera::getInstance()->setAspect((F32)mFullWidth / mFullHeight); - LLViewerCamera::getInstance()->setView(LLViewerCamera::getInstance()->getDefaultFOV() / mCameraZoom); - LLViewerCamera::getInstance()->setPerspective(false, mOrigin.mX, mOrigin.mY, mFullWidth, mFullHeight, false); - - LLVertexBuffer::unbind(); - avatarp->updateLOD(); - - if (avatarp->mDrawable.notNull()) - { - LLGLDepthTest gls_depth(GL_TRUE, GL_TRUE); - // make sure alpha=0 shows avatar material color - LLGLDisable no_blend(GL_BLEND); - - LLFace* face = avatarp->mDrawable->getFace(0); - if (face) - { - LLDrawPoolAvatar *avatarPoolp = (LLDrawPoolAvatar *)face->getPool(); - gPipeline.enableLightsPreview(); - avatarPoolp->renderAvatars(avatarp); // renders only one avatar - } - } - - gGL.popUIMatrix(); - gGL.color4f(1,1,1,1); - return true; -} - -//----------------------------------------------------------------------------- -// refresh() -//----------------------------------------------------------------------------- -void LLImagePreviewAvatar::refresh() -{ - mNeedsUpdate = true; -} - -//----------------------------------------------------------------------------- -// rotate() -//----------------------------------------------------------------------------- -void LLImagePreviewAvatar::rotate(F32 yaw_radians, F32 pitch_radians) -{ - mCameraYaw = mCameraYaw + yaw_radians; - - mCameraPitch = llclamp(mCameraPitch + pitch_radians, F_PI_BY_TWO * -0.8f, F_PI_BY_TWO * 0.8f); -} - -//----------------------------------------------------------------------------- -// zoom() -//----------------------------------------------------------------------------- -void LLImagePreviewAvatar::zoom(F32 zoom_amt) -{ - mCameraZoom = llclamp(mCameraZoom + zoom_amt, 1.f, 10.f); -} - -void LLImagePreviewAvatar::pan(F32 right, F32 up) -{ - mCameraOffset.mV[VY] = llclamp(mCameraOffset.mV[VY] + right * mCameraDistance / mCameraZoom, -1.f, 1.f); - mCameraOffset.mV[VZ] = llclamp(mCameraOffset.mV[VZ] + up * mCameraDistance / mCameraZoom, -1.f, 1.f); -} - - -//----------------------------------------------------------------------------- -// LLImagePreviewSculpted -//----------------------------------------------------------------------------- - -LLImagePreviewSculpted::LLImagePreviewSculpted(S32 width, S32 height) : LLViewerDynamicTexture(width, height, 3, ORDER_MIDDLE, false) -{ - mNeedsUpdate = true; - mCameraDistance = 0.f; - mCameraYaw = 0.f; - mCameraPitch = 0.f; - mCameraZoom = 1.f; - mTextureName = 0; - - LLVolumeParams volume_params; - volume_params.setType(LL_PCODE_PROFILE_CIRCLE, LL_PCODE_PATH_CIRCLE); - volume_params.setSculptID(LLUUID::null, LL_SCULPT_TYPE_SPHERE); - - F32 const HIGHEST_LOD = 4.0f; - mVolume = new LLVolume(volume_params, HIGHEST_LOD); -} - - -LLImagePreviewSculpted::~LLImagePreviewSculpted() -{ -} - -//virtual -S8 LLImagePreviewSculpted::getType() const -{ - return LLViewerDynamicTexture::LL_IMAGE_PREVIEW_SCULPTED ; -} - -void LLImagePreviewSculpted::setPreviewTarget(LLImageRaw* imagep, F32 distance) -{ - mCameraDistance = distance; - mCameraZoom = 1.f; - mCameraPitch = 0.f; - mCameraYaw = 0.f; - mCameraOffset.clearVec(); - - if (imagep) - { - LLImageDataSharedLock lock(imagep); - mVolume->sculpt(imagep->getWidth(), imagep->getHeight(), imagep->getComponents(), imagep->getData(), 0, false); - } - - const LLVolumeFace &vf = mVolume->getVolumeFace(0); - U32 num_indices = vf.mNumIndices; - U32 num_vertices = vf.mNumVertices; - - mVertexBuffer = new LLVertexBuffer(LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0); - if (!mVertexBuffer->allocateBuffer(num_vertices, num_indices)) - { - LL_WARNS() << "Failed to allocate Vertex Buffer for image preview to" - << num_vertices << " vertices and " - << num_indices << " indices" << LL_ENDL; - // We are likely to crash on getTexCoord0Strider() - } - - LLStrider vertex_strider; - LLStrider normal_strider; - LLStrider tc_strider; - LLStrider index_strider; - - mVertexBuffer->getVertexStrider(vertex_strider); - mVertexBuffer->getNormalStrider(normal_strider); - mVertexBuffer->getTexCoord0Strider(tc_strider); - mVertexBuffer->getIndexStrider(index_strider); - - // build vertices and normals - LLStrider pos; - pos = (LLVector3*) vf.mPositions; pos.setStride(16); - LLStrider norm; - norm = (LLVector3*) vf.mNormals; norm.setStride(16); - LLStrider tc; - tc = (LLVector2*) vf.mTexCoords; tc.setStride(8); - - for (U32 i = 0; i < num_vertices; i++) - { - *(vertex_strider++) = *pos++; - LLVector3 normal = *norm++; - normal.normalize(); - *(normal_strider++) = normal; - *(tc_strider++) = *tc++; - } - - // build indices - for (U16 i = 0; i < num_indices; i++) - { - *(index_strider++) = vf.mIndices[i]; - } - - mVertexBuffer->unmapBuffer(); -} - - -//----------------------------------------------------------------------------- -// render() -//----------------------------------------------------------------------------- -bool LLImagePreviewSculpted::render() -{ - mNeedsUpdate = false; - LLGLSUIDefault def; - LLGLDisable no_blend(GL_BLEND); - LLGLEnable cull(GL_CULL_FACE); - LLGLDepthTest depth(GL_TRUE); - - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.pushMatrix(); - gGL.loadIdentity(); - gGL.ortho(0.0f, mFullWidth, 0.0f, mFullHeight, -1.0f, 1.0f); - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - gGL.loadIdentity(); - - gGL.color4f(0.15f, 0.2f, 0.3f, 1.f); - - gUIProgram.bind(); - - gl_rect_2d_simple( mFullWidth, mFullHeight ); - - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.popMatrix(); - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.popMatrix(); - - glClear(GL_DEPTH_BUFFER_BIT); - - LLVector3 target_pos(0, 0, 0); - - LLQuaternion camera_rot = LLQuaternion(mCameraPitch, LLVector3::y_axis) * - LLQuaternion(mCameraYaw, LLVector3::z_axis); - - LLQuaternion av_rot = camera_rot; - LLViewerCamera::getInstance()->setOriginAndLookAt( - target_pos + ((LLVector3(mCameraDistance, 0.f, 0.f) + mCameraOffset) * av_rot), // camera - LLVector3::z_axis, // up - target_pos + (mCameraOffset * av_rot) ); // point of interest - - stop_glerror(); - - LLViewerCamera::getInstance()->setAspect((F32) mFullWidth / mFullHeight); - LLViewerCamera::getInstance()->setView(LLViewerCamera::getInstance()->getDefaultFOV() / mCameraZoom); - LLViewerCamera::getInstance()->setPerspective(false, mOrigin.mX, mOrigin.mY, mFullWidth, mFullHeight, false); - - const LLVolumeFace &vf = mVolume->getVolumeFace(0); - U32 num_indices = vf.mNumIndices; - - gPipeline.enableLightsAvatar(); - - gObjectPreviewProgram.bind(); - gPipeline.enableLightsPreview(); - - gGL.pushMatrix(); - const F32 SCALE = 1.25f; - gGL.scalef(SCALE, SCALE, SCALE); - const F32 BRIGHTNESS = 0.9f; - gGL.diffuseColor3f(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS); - - mVertexBuffer->setBuffer(); - mVertexBuffer->draw(LLRender::TRIANGLES, num_indices, 0); - - gGL.popMatrix(); - - gObjectPreviewProgram.unbind(); - - return true; -} - -//----------------------------------------------------------------------------- -// refresh() -//----------------------------------------------------------------------------- -void LLImagePreviewSculpted::refresh() -{ - mNeedsUpdate = true; -} - -//----------------------------------------------------------------------------- -// rotate() -//----------------------------------------------------------------------------- -void LLImagePreviewSculpted::rotate(F32 yaw_radians, F32 pitch_radians) -{ - mCameraYaw = mCameraYaw + yaw_radians; - - mCameraPitch = llclamp(mCameraPitch + pitch_radians, F_PI_BY_TWO * -0.8f, F_PI_BY_TWO * 0.8f); -} - -//----------------------------------------------------------------------------- -// zoom() -//----------------------------------------------------------------------------- -void LLImagePreviewSculpted::zoom(F32 zoom_amt) -{ - mCameraZoom = llclamp(mCameraZoom + zoom_amt, 1.f, 10.f); -} - -void LLImagePreviewSculpted::pan(F32 right, F32 up) -{ - mCameraOffset.mV[VY] = llclamp(mCameraOffset.mV[VY] + right * mCameraDistance / mCameraZoom, -1.f, 1.f); - mCameraOffset.mV[VZ] = llclamp(mCameraOffset.mV[VZ] + up * mCameraDistance / mCameraZoom, -1.f, 1.f); -} +/** + * @file llfloaterimagepreview.cpp + * @brief LLFloaterImagePreview class implementation + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterimagepreview.h" + +#include "llimagebmp.h" +#include "llimagetga.h" +#include "llimagejpeg.h" +#include "llimagepng.h" + +#include "llagent.h" +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "lldrawable.h" +#include "lldrawpoolavatar.h" +#include "llrender.h" +#include "llface.h" +#include "llfocusmgr.h" +#include "lltextbox.h" +#include "lltoolmgr.h" +#include "llui.h" +#include "llviewercamera.h" +#include "llviewerwindow.h" +#include "llviewerobjectlist.h" +#include "llvoavatar.h" +#include "pipeline.h" +#include "lluictrlfactory.h" +#include "llviewershadermgr.h" +#include "llviewertexturelist.h" +#include "llstring.h" + +#include "llendianswizzle.h" + +#include "llviewercontrol.h" +#include "lltrans.h" +#include "llimagedimensionsinfo.h" + +const S32 PREVIEW_BORDER_WIDTH = 2; +const S32 PREVIEW_RESIZE_HANDLE_SIZE = S32(RESIZE_HANDLE_WIDTH * OO_SQRT2) + PREVIEW_BORDER_WIDTH; +const S32 PREVIEW_HPAD = PREVIEW_RESIZE_HANDLE_SIZE; +const S32 PREVIEW_VPAD = -24 + 35; // yuk, hard coded +const S32 PREF_BUTTON_HEIGHT = 16 + 7 + 16 + 35; +const S32 PREVIEW_TEXTURE_HEIGHT = 320; + +//----------------------------------------------------------------------------- +// LLFloaterImagePreview() +//----------------------------------------------------------------------------- +LLFloaterImagePreview::LLFloaterImagePreview(const std::string& filename) : + LLFloaterNameDesc(filename), + + mAvatarPreview(NULL), + mSculptedPreview(NULL), + mLastMouseX(0), + mLastMouseY(0), + mImagep(NULL) +{ + loadImage(mFilenameAndPath); +} + +//----------------------------------------------------------------------------- +// postBuild() +//----------------------------------------------------------------------------- +bool LLFloaterImagePreview::postBuild() +{ + if (!LLFloaterNameDesc::postBuild()) + { + return false; + } + + LLCtrlSelectionInterface* iface = childGetSelectionInterface("clothing_type_combo"); + if (iface) + { + iface->selectFirstItem(); + } + childSetCommitCallback("clothing_type_combo", onPreviewTypeCommit, this); + + mPreviewRect.set(PREVIEW_HPAD, + PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD, + getRect().getWidth() - PREVIEW_HPAD, + PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); + mPreviewImageRect.set(0.f, 1.f, 1.f, 0.f); + + getChildView("bad_image_text")->setVisible(false); + + if (mRawImagep.notNull() && gAgent.getRegion() != NULL) + { + mAvatarPreview = new LLImagePreviewAvatar(256, 256); + mAvatarPreview->setPreviewTarget("mPelvis", "mUpperBodyMesh0", mRawImagep, 2.f, false); + + mSculptedPreview = new LLImagePreviewSculpted(256, 256); + mSculptedPreview->setPreviewTarget(mRawImagep, 2.0f); + + if (mRawImagep->getWidth() * mRawImagep->getHeight() <= LL_IMAGE_REZ_LOSSLESS_CUTOFF * LL_IMAGE_REZ_LOSSLESS_CUTOFF) + { + // We want "lossless_check" to be unchecked when it is disabled, regardless of + // LosslessJ2CUpload state, so only assign control when enabling checkbox + LLCheckBoxCtrl* check_box = getChild("lossless_check"); + check_box->setEnabled(true); + check_box->setControlVariable(gSavedSettings.getControl("LosslessJ2CUpload")); + } + } + else + { + mAvatarPreview = NULL; + mSculptedPreview = NULL; + getChildView("bad_image_text")->setVisible(true); + getChildView("clothing_type_combo")->setEnabled(false); + getChildView("ok_btn")->setEnabled(false); + + if(!mImageLoadError.empty()) + { + getChild("bad_image_text")->setValue(mImageLoadError.c_str()); + } + } + + getChild("ok_btn")->setCommitCallback(boost::bind(&LLFloaterNameDesc::onBtnOK, this)); + + return true; +} + +//----------------------------------------------------------------------------- +// LLFloaterImagePreview() +//----------------------------------------------------------------------------- +LLFloaterImagePreview::~LLFloaterImagePreview() +{ + clearAllPreviewTextures(); + + mRawImagep = NULL; + mImagep = NULL ; +} + +//static +//----------------------------------------------------------------------------- +// onPreviewTypeCommit() +//----------------------------------------------------------------------------- +void LLFloaterImagePreview::onPreviewTypeCommit(LLUICtrl* ctrl, void* userdata) +{ + LLFloaterImagePreview *fp =(LLFloaterImagePreview *)userdata; + + if (!fp->mAvatarPreview || !fp->mSculptedPreview) + { + return; + } + + S32 which_mode = 0; + + LLCtrlSelectionInterface* iface = fp->childGetSelectionInterface("clothing_type_combo"); + if (iface) + { + which_mode = iface->getFirstSelectedIndex(); + } + + switch(which_mode) + { + case 0: + break; + case 1: + fp->mAvatarPreview->setPreviewTarget("mSkull", "mHairMesh0", fp->mRawImagep, 0.4f, false); + break; + case 2: + fp->mAvatarPreview->setPreviewTarget("mSkull", "mHeadMesh0", fp->mRawImagep, 0.4f, false); + break; + case 3: + fp->mAvatarPreview->setPreviewTarget("mChest", "mUpperBodyMesh0", fp->mRawImagep, 1.0f, false); + break; + case 4: + fp->mAvatarPreview->setPreviewTarget("mKneeLeft", "mLowerBodyMesh0", fp->mRawImagep, 1.2f, false); + break; + case 5: + fp->mAvatarPreview->setPreviewTarget("mSkull", "mHeadMesh0", fp->mRawImagep, 0.4f, true); + break; + case 6: + fp->mAvatarPreview->setPreviewTarget("mChest", "mUpperBodyMesh0", fp->mRawImagep, 1.2f, true); + break; + case 7: + fp->mAvatarPreview->setPreviewTarget("mKneeLeft", "mLowerBodyMesh0", fp->mRawImagep, 1.2f, true); + break; + case 8: + fp->mAvatarPreview->setPreviewTarget("mKneeLeft", "mSkirtMesh0", fp->mRawImagep, 1.3f, false); + break; + case 9: + fp->mSculptedPreview->setPreviewTarget(fp->mRawImagep, 2.0f); + break; + default: + break; + } + + fp->mAvatarPreview->refresh(); + fp->mSculptedPreview->refresh(); +} + + +//----------------------------------------------------------------------------- +// clearAllPreviewTextures() +//----------------------------------------------------------------------------- +void LLFloaterImagePreview::clearAllPreviewTextures() +{ + if (mAvatarPreview) + { + mAvatarPreview->clearPreviewTexture("mHairMesh0"); + mAvatarPreview->clearPreviewTexture("mUpperBodyMesh0"); + mAvatarPreview->clearPreviewTexture("mLowerBodyMesh0"); + mAvatarPreview->clearPreviewTexture("mHeadMesh0"); + mAvatarPreview->clearPreviewTexture("mUpperBodyMesh0"); + mAvatarPreview->clearPreviewTexture("mLowerBodyMesh0"); + mAvatarPreview->clearPreviewTexture("mSkirtMesh0"); + } +} + +//----------------------------------------------------------------------------- +// draw() +//----------------------------------------------------------------------------- +void LLFloaterImagePreview::draw() +{ + LLFloater::draw(); + LLRect r = getRect(); + + if (mRawImagep.notNull()) + { + LLCtrlSelectionInterface* iface = childGetSelectionInterface("clothing_type_combo"); + U32 selected = 0; + if (iface) + selected = iface->getFirstSelectedIndex(); + + if (selected <= 0) + { + gl_rect_2d_checkerboard(mPreviewRect); + + if(mImagep.notNull()) + { + gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, mImagep->getTexName()); + } + else + { + mImagep = LLViewerTextureManager::getLocalTexture(mRawImagep.get(), false) ; + + gGL.getTexUnit(0)->unbind(mImagep->getTarget()) ; + gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, mImagep->getTexName()); + stop_glerror(); + + gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_BILINEAR); + + gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_CLAMP); + if (mAvatarPreview) + { + mAvatarPreview->setTexture(mImagep->getTexName()); + mSculptedPreview->setTexture(mImagep->getTexName()); + } + } + + gGL.color3f(1.f, 1.f, 1.f); + gGL.begin( LLRender::QUADS ); + { + gGL.texCoord2f(mPreviewImageRect.mLeft, mPreviewImageRect.mTop); + gGL.vertex2i(PREVIEW_HPAD, PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD); + gGL.texCoord2f(mPreviewImageRect.mLeft, mPreviewImageRect.mBottom); + gGL.vertex2i(PREVIEW_HPAD, PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); + gGL.texCoord2f(mPreviewImageRect.mRight, mPreviewImageRect.mBottom); + gGL.vertex2i(r.getWidth() - PREVIEW_HPAD, PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); + gGL.texCoord2f(mPreviewImageRect.mRight, mPreviewImageRect.mTop); + gGL.vertex2i(r.getWidth() - PREVIEW_HPAD, PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD); + } + gGL.end(); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + stop_glerror(); + } + else + { + if ((mAvatarPreview) && (mSculptedPreview)) + { + gGL.color3f(1.f, 1.f, 1.f); + + if (selected == 9) + { + gGL.getTexUnit(0)->bind(mSculptedPreview); + } + else + { + gGL.getTexUnit(0)->bind(mAvatarPreview); + } + + gGL.begin( LLRender::QUADS ); + { + gGL.texCoord2f(0.f, 1.f); + gGL.vertex2i(PREVIEW_HPAD, PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD); + gGL.texCoord2f(0.f, 0.f); + gGL.vertex2i(PREVIEW_HPAD, PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); + gGL.texCoord2f(1.f, 0.f); + gGL.vertex2i(r.getWidth() - PREVIEW_HPAD, PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); + gGL.texCoord2f(1.f, 1.f); + gGL.vertex2i(r.getWidth() - PREVIEW_HPAD, PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD); + } + gGL.end(); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + } + } + } +} + + +//----------------------------------------------------------------------------- +// loadImage() +//----------------------------------------------------------------------------- +bool LLFloaterImagePreview::loadImage(const std::string& src_filename) +{ + try + { + std::string exten = gDirUtilp->getExtension(src_filename); + U32 codec = LLImageBase::getCodecFromExtension(exten); + + LLImageDimensionsInfo image_info; + if (!image_info.load(src_filename,codec)) + { + mImageLoadError = image_info.getLastError(); + return false; + } + + S32 max_width = gSavedSettings.getS32("max_texture_dimension_X"); + S32 max_height = gSavedSettings.getS32("max_texture_dimension_Y"); + + if ((image_info.getWidth() > max_width) || (image_info.getHeight() > max_height)) + { + LLStringUtil::format_map_t args; + args["WIDTH"] = llformat("%d", max_width); + args["HEIGHT"] = llformat("%d", max_height); + + mImageLoadError = LLTrans::getString("texture_load_dimensions_error", args); + return false; + } + + // Load the image + LLPointer image = LLImageFormatted::createFromType(codec); + if (image.isNull()) + { + return false; + } + if (!image->load(src_filename)) + { + return false; + } + // Decompress or expand it in a raw image structure + LLPointer raw_image = new LLImageRaw; + if (!image->decode(raw_image, 0.0f)) + { + return false; + } + // Check the image constraints + if ((image->getComponents() != 3) && (image->getComponents() != 4)) + { + image->setLastError("Image files with less than 3 or more than 4 components are not supported."); + return false; + } + + raw_image->biasedScaleToPowerOfTwo(1024); + mRawImagep = raw_image; + } + catch (...) + { + LOG_UNHANDLED_EXCEPTION(""); + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// handleMouseDown() +//----------------------------------------------------------------------------- +bool LLFloaterImagePreview::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (mPreviewRect.pointInRect(x, y)) + { + bringToFront( x, y ); + gFocusMgr.setMouseCapture(this); + gViewerWindow->hideCursor(); + mLastMouseX = x; + mLastMouseY = y; + return true; + } + + return LLFloater::handleMouseDown(x, y, mask); +} + +//----------------------------------------------------------------------------- +// handleMouseUp() +//----------------------------------------------------------------------------- +bool LLFloaterImagePreview::handleMouseUp(S32 x, S32 y, MASK mask) +{ + gFocusMgr.setMouseCapture(nullptr); + gViewerWindow->showCursor(); + return LLFloater::handleMouseUp(x, y, mask); +} + +//----------------------------------------------------------------------------- +// handleHover() +//----------------------------------------------------------------------------- +bool LLFloaterImagePreview::handleHover(S32 x, S32 y, MASK mask) +{ + MASK local_mask = mask & ~MASK_ALT; + + if (mAvatarPreview && hasMouseCapture()) + { + if (local_mask == MASK_PAN) + { + // pan here + LLCtrlSelectionInterface* iface = childGetSelectionInterface("clothing_type_combo"); + if (iface && iface->getFirstSelectedIndex() <= 0) + { + mPreviewImageRect.translate((F32)(x - mLastMouseX) * -0.005f * mPreviewImageRect.getWidth(), + (F32)(y - mLastMouseY) * -0.005f * mPreviewImageRect.getHeight()); + } + else + { + mAvatarPreview->pan((F32)(x - mLastMouseX) * -0.005f, (F32)(y - mLastMouseY) * -0.005f); + mSculptedPreview->pan((F32)(x - mLastMouseX) * -0.005f, (F32)(y - mLastMouseY) * -0.005f); + } + } + else if (local_mask == MASK_ORBIT) + { + F32 yaw_radians = (F32)(x - mLastMouseX) * -0.01f; + F32 pitch_radians = (F32)(y - mLastMouseY) * 0.02f; + + mAvatarPreview->rotate(yaw_radians, pitch_radians); + mSculptedPreview->rotate(yaw_radians, pitch_radians); + } + else + { + LLCtrlSelectionInterface* iface = childGetSelectionInterface("clothing_type_combo"); + if (iface && iface->getFirstSelectedIndex() <= 0) + { + F32 zoom_amt = (F32)(y - mLastMouseY) * -0.002f; + mPreviewImageRect.stretch(zoom_amt); + } + else + { + F32 yaw_radians = (F32)(x - mLastMouseX) * -0.01f; + F32 zoom_amt = (F32)(y - mLastMouseY) * 0.02f; + + mAvatarPreview->rotate(yaw_radians, 0.f); + mAvatarPreview->zoom(zoom_amt); + mSculptedPreview->rotate(yaw_radians, 0.f); + mSculptedPreview->zoom(zoom_amt); + } + } + + LLCtrlSelectionInterface* iface = childGetSelectionInterface("clothing_type_combo"); + if (iface && iface->getFirstSelectedIndex() <= 0) + { + if (mPreviewImageRect.getWidth() > 1.f) + { + mPreviewImageRect.stretch((1.f - mPreviewImageRect.getWidth()) * 0.5f); + } + else if (mPreviewImageRect.getWidth() < 0.1f) + { + mPreviewImageRect.stretch((0.1f - mPreviewImageRect.getWidth()) * 0.5f); + } + + if (mPreviewImageRect.getHeight() > 1.f) + { + mPreviewImageRect.stretch((1.f - mPreviewImageRect.getHeight()) * 0.5f); + } + else if (mPreviewImageRect.getHeight() < 0.1f) + { + mPreviewImageRect.stretch((0.1f - mPreviewImageRect.getHeight()) * 0.5f); + } + + if (mPreviewImageRect.mLeft < 0.f) + { + mPreviewImageRect.translate(-mPreviewImageRect.mLeft, 0.f); + } + else if (mPreviewImageRect.mRight > 1.f) + { + mPreviewImageRect.translate(1.f - mPreviewImageRect.mRight, 0.f); + } + + if (mPreviewImageRect.mBottom < 0.f) + { + mPreviewImageRect.translate(0.f, -mPreviewImageRect.mBottom); + } + else if (mPreviewImageRect.mTop > 1.f) + { + mPreviewImageRect.translate(0.f, 1.f - mPreviewImageRect.mTop); + } + } + else + { + mAvatarPreview->refresh(); + mSculptedPreview->refresh(); + } + + LLUI::getInstance()->setMousePositionLocal(this, mLastMouseX, mLastMouseY); + } + + if (!mPreviewRect.pointInRect(x, y) || !mAvatarPreview || !mSculptedPreview) + { + return LLFloater::handleHover(x, y, mask); + } + else if (local_mask == MASK_ORBIT) + { + gViewerWindow->setCursor(UI_CURSOR_TOOLCAMERA); + } + else if (local_mask == MASK_PAN) + { + gViewerWindow->setCursor(UI_CURSOR_TOOLPAN); + } + else + { + gViewerWindow->setCursor(UI_CURSOR_TOOLZOOMIN); + } + + return true; +} + +//----------------------------------------------------------------------------- +// handleScrollWheel() +//----------------------------------------------------------------------------- +bool LLFloaterImagePreview::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + if (mPreviewRect.pointInRect(x, y) && mAvatarPreview) + { + mAvatarPreview->zoom((F32)clicks * -0.2f); + mAvatarPreview->refresh(); + + mSculptedPreview->zoom((F32)clicks * -0.2f); + mSculptedPreview->refresh(); + } + + return true; +} + +//----------------------------------------------------------------------------- +// onMouseCaptureLost() +//----------------------------------------------------------------------------- +// static +void LLFloaterImagePreview::onMouseCaptureLostImagePreview(LLMouseHandler* handler) +{ + gViewerWindow->showCursor(); +} + + +//----------------------------------------------------------------------------- +// LLImagePreviewAvatar +//----------------------------------------------------------------------------- +LLImagePreviewAvatar::LLImagePreviewAvatar(S32 width, S32 height) : LLViewerDynamicTexture(width, height, 3, ORDER_MIDDLE, false) +{ + mNeedsUpdate = true; + mTargetJoint = NULL; + mTargetMesh = NULL; + mCameraDistance = 0.f; + mCameraYaw = 0.f; + mCameraPitch = 0.f; + mCameraZoom = 1.f; + + mDummyAvatar = (LLVOAvatar*)gObjectList.createObjectViewer(LL_PCODE_LEGACY_AVATAR, gAgent.getRegion(), LLViewerObject::CO_FLAG_UI_AVATAR); + mDummyAvatar->mSpecialRenderMode = 2; + + mTextureName = 0; +} + + +LLImagePreviewAvatar::~LLImagePreviewAvatar() +{ + mDummyAvatar->markDead(); +} + +//virtual +S8 LLImagePreviewAvatar::getType() const +{ + return LLViewerDynamicTexture::LL_IMAGE_PREVIEW_AVATAR ; +} + +void LLImagePreviewAvatar::setPreviewTarget(const std::string& joint_name, const std::string& mesh_name, LLImageRaw* imagep, F32 distance, bool male) +{ + mTargetJoint = mDummyAvatar->mRoot->findJoint(joint_name); + // clear out existing test mesh + if (mTargetMesh) + { + mTargetMesh->setTestTexture(0); + } + + if (male) + { + mDummyAvatar->setVisualParamWeight( "male", 1.f ); + mDummyAvatar->updateVisualParams(); + mDummyAvatar->updateGeometry(mDummyAvatar->mDrawable); + } + else + { + mDummyAvatar->setVisualParamWeight( "male", 0.f ); + mDummyAvatar->updateVisualParams(); + mDummyAvatar->updateGeometry(mDummyAvatar->mDrawable); + } + mDummyAvatar->mRoot->setVisible(false, true); + + mTargetMesh = dynamic_cast(mDummyAvatar->mRoot->findJoint(mesh_name)); + mTargetMesh->setTestTexture(mTextureName); + mTargetMesh->setVisible(true, false); + mCameraDistance = distance; + mCameraZoom = 1.f; + mCameraPitch = 0.f; + mCameraYaw = 0.f; + mCameraOffset.clearVec(); +} + +//----------------------------------------------------------------------------- +// clearPreviewTexture() +//----------------------------------------------------------------------------- +void LLImagePreviewAvatar::clearPreviewTexture(const std::string& mesh_name) +{ + if (mDummyAvatar) + { + LLViewerJointMesh *mesh = dynamic_cast(mDummyAvatar->mRoot->findJoint(mesh_name)); + // clear out existing test mesh + if (mesh) + { + mesh->setTestTexture(0); + } + } +} + +//----------------------------------------------------------------------------- +// update() +//----------------------------------------------------------------------------- +bool LLImagePreviewAvatar::render() +{ + mNeedsUpdate = false; + LLVOAvatar* avatarp = mDummyAvatar; + + gGL.pushUIMatrix(); + gGL.loadUIIdentity(); + + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.pushMatrix(); + gGL.loadIdentity(); + gGL.ortho(0.0f, mFullWidth, 0.0f, mFullHeight, -1.0f, 1.0f); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + gGL.loadIdentity(); + + + LLGLSUIDefault def; + gGL.color4f(0.15f, 0.2f, 0.3f, 1.f); + + gUIProgram.bind(); + + gl_rect_2d_simple( mFullWidth, mFullHeight ); + + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.popMatrix(); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.popMatrix(); + + gGL.flush(); + LLVector3 target_pos = mTargetJoint->getWorldPosition(); + + LLQuaternion camera_rot = LLQuaternion(mCameraPitch, LLVector3::y_axis) * + LLQuaternion(mCameraYaw, LLVector3::z_axis); + + LLQuaternion av_rot = avatarp->mPelvisp->getWorldRotation() * camera_rot; + LLViewerCamera::getInstance()->setOriginAndLookAt( + target_pos + ((LLVector3(mCameraDistance, 0.f, 0.f) + mCameraOffset) * av_rot), // camera + LLVector3::z_axis, // up + target_pos + (mCameraOffset * av_rot) ); // point of interest + + stop_glerror(); + + LLViewerCamera::getInstance()->setAspect((F32)mFullWidth / mFullHeight); + LLViewerCamera::getInstance()->setView(LLViewerCamera::getInstance()->getDefaultFOV() / mCameraZoom); + LLViewerCamera::getInstance()->setPerspective(false, mOrigin.mX, mOrigin.mY, mFullWidth, mFullHeight, false); + + LLVertexBuffer::unbind(); + avatarp->updateLOD(); + + if (avatarp->mDrawable.notNull()) + { + LLGLDepthTest gls_depth(GL_TRUE, GL_TRUE); + // make sure alpha=0 shows avatar material color + LLGLDisable no_blend(GL_BLEND); + + LLFace* face = avatarp->mDrawable->getFace(0); + if (face) + { + LLDrawPoolAvatar *avatarPoolp = (LLDrawPoolAvatar *)face->getPool(); + gPipeline.enableLightsPreview(); + avatarPoolp->renderAvatars(avatarp); // renders only one avatar + } + } + + gGL.popUIMatrix(); + gGL.color4f(1,1,1,1); + return true; +} + +//----------------------------------------------------------------------------- +// refresh() +//----------------------------------------------------------------------------- +void LLImagePreviewAvatar::refresh() +{ + mNeedsUpdate = true; +} + +//----------------------------------------------------------------------------- +// rotate() +//----------------------------------------------------------------------------- +void LLImagePreviewAvatar::rotate(F32 yaw_radians, F32 pitch_radians) +{ + mCameraYaw = mCameraYaw + yaw_radians; + + mCameraPitch = llclamp(mCameraPitch + pitch_radians, F_PI_BY_TWO * -0.8f, F_PI_BY_TWO * 0.8f); +} + +//----------------------------------------------------------------------------- +// zoom() +//----------------------------------------------------------------------------- +void LLImagePreviewAvatar::zoom(F32 zoom_amt) +{ + mCameraZoom = llclamp(mCameraZoom + zoom_amt, 1.f, 10.f); +} + +void LLImagePreviewAvatar::pan(F32 right, F32 up) +{ + mCameraOffset.mV[VY] = llclamp(mCameraOffset.mV[VY] + right * mCameraDistance / mCameraZoom, -1.f, 1.f); + mCameraOffset.mV[VZ] = llclamp(mCameraOffset.mV[VZ] + up * mCameraDistance / mCameraZoom, -1.f, 1.f); +} + + +//----------------------------------------------------------------------------- +// LLImagePreviewSculpted +//----------------------------------------------------------------------------- + +LLImagePreviewSculpted::LLImagePreviewSculpted(S32 width, S32 height) : LLViewerDynamicTexture(width, height, 3, ORDER_MIDDLE, false) +{ + mNeedsUpdate = true; + mCameraDistance = 0.f; + mCameraYaw = 0.f; + mCameraPitch = 0.f; + mCameraZoom = 1.f; + mTextureName = 0; + + LLVolumeParams volume_params; + volume_params.setType(LL_PCODE_PROFILE_CIRCLE, LL_PCODE_PATH_CIRCLE); + volume_params.setSculptID(LLUUID::null, LL_SCULPT_TYPE_SPHERE); + + F32 const HIGHEST_LOD = 4.0f; + mVolume = new LLVolume(volume_params, HIGHEST_LOD); +} + + +LLImagePreviewSculpted::~LLImagePreviewSculpted() +{ +} + +//virtual +S8 LLImagePreviewSculpted::getType() const +{ + return LLViewerDynamicTexture::LL_IMAGE_PREVIEW_SCULPTED ; +} + +void LLImagePreviewSculpted::setPreviewTarget(LLImageRaw* imagep, F32 distance) +{ + mCameraDistance = distance; + mCameraZoom = 1.f; + mCameraPitch = 0.f; + mCameraYaw = 0.f; + mCameraOffset.clearVec(); + + if (imagep) + { + LLImageDataSharedLock lock(imagep); + mVolume->sculpt(imagep->getWidth(), imagep->getHeight(), imagep->getComponents(), imagep->getData(), 0, false); + } + + const LLVolumeFace &vf = mVolume->getVolumeFace(0); + U32 num_indices = vf.mNumIndices; + U32 num_vertices = vf.mNumVertices; + + mVertexBuffer = new LLVertexBuffer(LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0); + if (!mVertexBuffer->allocateBuffer(num_vertices, num_indices)) + { + LL_WARNS() << "Failed to allocate Vertex Buffer for image preview to" + << num_vertices << " vertices and " + << num_indices << " indices" << LL_ENDL; + // We are likely to crash on getTexCoord0Strider() + } + + LLStrider vertex_strider; + LLStrider normal_strider; + LLStrider tc_strider; + LLStrider index_strider; + + mVertexBuffer->getVertexStrider(vertex_strider); + mVertexBuffer->getNormalStrider(normal_strider); + mVertexBuffer->getTexCoord0Strider(tc_strider); + mVertexBuffer->getIndexStrider(index_strider); + + // build vertices and normals + LLStrider pos; + pos = (LLVector3*) vf.mPositions; pos.setStride(16); + LLStrider norm; + norm = (LLVector3*) vf.mNormals; norm.setStride(16); + LLStrider tc; + tc = (LLVector2*) vf.mTexCoords; tc.setStride(8); + + for (U32 i = 0; i < num_vertices; i++) + { + *(vertex_strider++) = *pos++; + LLVector3 normal = *norm++; + normal.normalize(); + *(normal_strider++) = normal; + *(tc_strider++) = *tc++; + } + + // build indices + for (U16 i = 0; i < num_indices; i++) + { + *(index_strider++) = vf.mIndices[i]; + } + + mVertexBuffer->unmapBuffer(); +} + + +//----------------------------------------------------------------------------- +// render() +//----------------------------------------------------------------------------- +bool LLImagePreviewSculpted::render() +{ + mNeedsUpdate = false; + LLGLSUIDefault def; + LLGLDisable no_blend(GL_BLEND); + LLGLEnable cull(GL_CULL_FACE); + LLGLDepthTest depth(GL_TRUE); + + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.pushMatrix(); + gGL.loadIdentity(); + gGL.ortho(0.0f, mFullWidth, 0.0f, mFullHeight, -1.0f, 1.0f); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + gGL.loadIdentity(); + + gGL.color4f(0.15f, 0.2f, 0.3f, 1.f); + + gUIProgram.bind(); + + gl_rect_2d_simple( mFullWidth, mFullHeight ); + + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.popMatrix(); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.popMatrix(); + + glClear(GL_DEPTH_BUFFER_BIT); + + LLVector3 target_pos(0, 0, 0); + + LLQuaternion camera_rot = LLQuaternion(mCameraPitch, LLVector3::y_axis) * + LLQuaternion(mCameraYaw, LLVector3::z_axis); + + LLQuaternion av_rot = camera_rot; + LLViewerCamera::getInstance()->setOriginAndLookAt( + target_pos + ((LLVector3(mCameraDistance, 0.f, 0.f) + mCameraOffset) * av_rot), // camera + LLVector3::z_axis, // up + target_pos + (mCameraOffset * av_rot) ); // point of interest + + stop_glerror(); + + LLViewerCamera::getInstance()->setAspect((F32) mFullWidth / mFullHeight); + LLViewerCamera::getInstance()->setView(LLViewerCamera::getInstance()->getDefaultFOV() / mCameraZoom); + LLViewerCamera::getInstance()->setPerspective(false, mOrigin.mX, mOrigin.mY, mFullWidth, mFullHeight, false); + + const LLVolumeFace &vf = mVolume->getVolumeFace(0); + U32 num_indices = vf.mNumIndices; + + gPipeline.enableLightsAvatar(); + + gObjectPreviewProgram.bind(); + gPipeline.enableLightsPreview(); + + gGL.pushMatrix(); + const F32 SCALE = 1.25f; + gGL.scalef(SCALE, SCALE, SCALE); + const F32 BRIGHTNESS = 0.9f; + gGL.diffuseColor3f(BRIGHTNESS, BRIGHTNESS, BRIGHTNESS); + + mVertexBuffer->setBuffer(); + mVertexBuffer->draw(LLRender::TRIANGLES, num_indices, 0); + + gGL.popMatrix(); + + gObjectPreviewProgram.unbind(); + + return true; +} + +//----------------------------------------------------------------------------- +// refresh() +//----------------------------------------------------------------------------- +void LLImagePreviewSculpted::refresh() +{ + mNeedsUpdate = true; +} + +//----------------------------------------------------------------------------- +// rotate() +//----------------------------------------------------------------------------- +void LLImagePreviewSculpted::rotate(F32 yaw_radians, F32 pitch_radians) +{ + mCameraYaw = mCameraYaw + yaw_radians; + + mCameraPitch = llclamp(mCameraPitch + pitch_radians, F_PI_BY_TWO * -0.8f, F_PI_BY_TWO * 0.8f); +} + +//----------------------------------------------------------------------------- +// zoom() +//----------------------------------------------------------------------------- +void LLImagePreviewSculpted::zoom(F32 zoom_amt) +{ + mCameraZoom = llclamp(mCameraZoom + zoom_amt, 1.f, 10.f); +} + +void LLImagePreviewSculpted::pan(F32 right, F32 up) +{ + mCameraOffset.mV[VY] = llclamp(mCameraOffset.mV[VY] + right * mCameraDistance / mCameraZoom, -1.f, 1.f); + mCameraOffset.mV[VZ] = llclamp(mCameraOffset.mV[VZ] + up * mCameraDistance / mCameraZoom, -1.f, 1.f); +} diff --git a/indra/newview/llfloaterimagepreview.h b/indra/newview/llfloaterimagepreview.h index 786f55e55c..6facea0ad8 100644 --- a/indra/newview/llfloaterimagepreview.h +++ b/indra/newview/llfloaterimagepreview.h @@ -1,144 +1,144 @@ -/** - * @file llfloaterimagepreview.h - * @brief LLFloaterImagePreview class definition - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERIMAGEPREVIEW_H -#define LL_LLFLOATERIMAGEPREVIEW_H - -#include "llfloaternamedesc.h" -#include "lldynamictexture.h" -#include "llpointer.h" -#include "llquaternion.h" - -class LLComboBox; -class LLJoint; -class LLViewerJointMesh; -class LLVOAvatar; -class LLTextBox; -class LLVertexBuffer; -class LLVolume; - -class LLImagePreviewSculpted : public LLViewerDynamicTexture -{ -protected: - virtual ~LLImagePreviewSculpted(); - - public: - LLImagePreviewSculpted(S32 width, S32 height); - - /*virtual*/ S8 getType() const ; - - void setPreviewTarget(LLImageRaw *imagep, F32 distance); - void setTexture(U32 name) { mTextureName = name; } - - bool render(); - void refresh(); - void rotate(F32 yaw_radians, F32 pitch_radians); - void zoom(F32 zoom_amt); - void pan(F32 right, F32 up); - virtual bool needsRender() { return mNeedsUpdate; } - - protected: - bool mNeedsUpdate; - U32 mTextureName; - F32 mCameraDistance; - F32 mCameraYaw; - F32 mCameraPitch; - F32 mCameraZoom; - LLVector3 mCameraOffset; - LLPointer mVolume; - LLPointer mVertexBuffer; -}; - - -class LLImagePreviewAvatar : public LLViewerDynamicTexture -{ -protected: - virtual ~LLImagePreviewAvatar(); - -public: - LLImagePreviewAvatar(S32 width, S32 height); - - /*virtual*/ S8 getType() const ; - - void setPreviewTarget(const std::string& joint_name, const std::string& mesh_name, LLImageRaw* imagep, F32 distance, bool male); - void setTexture(U32 name) { mTextureName = name; } - void clearPreviewTexture(const std::string& mesh_name); - - bool render(); - void refresh(); - void rotate(F32 yaw_radians, F32 pitch_radians); - void zoom(F32 zoom_amt); - void pan(F32 right, F32 up); - virtual bool needsRender() { return mNeedsUpdate; } - -protected: - bool mNeedsUpdate; - LLJoint* mTargetJoint; - LLViewerJointMesh* mTargetMesh; - F32 mCameraDistance; - F32 mCameraYaw; - F32 mCameraPitch; - F32 mCameraZoom; - LLVector3 mCameraOffset; - LLPointer mDummyAvatar; - U32 mTextureName; -}; - -class LLFloaterImagePreview : public LLFloaterNameDesc -{ -public: - LLFloaterImagePreview(const std::string& filename); - virtual ~LLFloaterImagePreview(); - - virtual bool postBuild(); - - bool handleMouseDown(S32 x, S32 y, MASK mask); - bool handleMouseUp(S32 x, S32 y, MASK mask); - bool handleHover(S32 x, S32 y, MASK mask); - bool handleScrollWheel(S32 x, S32 y, S32 clicks); - - static void onMouseCaptureLostImagePreview(LLMouseHandler*); - - void clearAllPreviewTextures(); - -protected: - static void onPreviewTypeCommit(LLUICtrl*,void*); - void draw(); - bool loadImage(const std::string& filename); - - LLPointer mRawImagep; - LLPointer mAvatarPreview; - LLPointer mSculptedPreview; - S32 mLastMouseX; - S32 mLastMouseY; - LLRect mPreviewRect; - LLRectf mPreviewImageRect; - LLPointer mImagep ; - - std::string mImageLoadError; -}; - -#endif // LL_LLFLOATERIMAGEPREVIEW_H +/** + * @file llfloaterimagepreview.h + * @brief LLFloaterImagePreview class definition + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERIMAGEPREVIEW_H +#define LL_LLFLOATERIMAGEPREVIEW_H + +#include "llfloaternamedesc.h" +#include "lldynamictexture.h" +#include "llpointer.h" +#include "llquaternion.h" + +class LLComboBox; +class LLJoint; +class LLViewerJointMesh; +class LLVOAvatar; +class LLTextBox; +class LLVertexBuffer; +class LLVolume; + +class LLImagePreviewSculpted : public LLViewerDynamicTexture +{ +protected: + virtual ~LLImagePreviewSculpted(); + + public: + LLImagePreviewSculpted(S32 width, S32 height); + + /*virtual*/ S8 getType() const ; + + void setPreviewTarget(LLImageRaw *imagep, F32 distance); + void setTexture(U32 name) { mTextureName = name; } + + bool render(); + void refresh(); + void rotate(F32 yaw_radians, F32 pitch_radians); + void zoom(F32 zoom_amt); + void pan(F32 right, F32 up); + virtual bool needsRender() { return mNeedsUpdate; } + + protected: + bool mNeedsUpdate; + U32 mTextureName; + F32 mCameraDistance; + F32 mCameraYaw; + F32 mCameraPitch; + F32 mCameraZoom; + LLVector3 mCameraOffset; + LLPointer mVolume; + LLPointer mVertexBuffer; +}; + + +class LLImagePreviewAvatar : public LLViewerDynamicTexture +{ +protected: + virtual ~LLImagePreviewAvatar(); + +public: + LLImagePreviewAvatar(S32 width, S32 height); + + /*virtual*/ S8 getType() const ; + + void setPreviewTarget(const std::string& joint_name, const std::string& mesh_name, LLImageRaw* imagep, F32 distance, bool male); + void setTexture(U32 name) { mTextureName = name; } + void clearPreviewTexture(const std::string& mesh_name); + + bool render(); + void refresh(); + void rotate(F32 yaw_radians, F32 pitch_radians); + void zoom(F32 zoom_amt); + void pan(F32 right, F32 up); + virtual bool needsRender() { return mNeedsUpdate; } + +protected: + bool mNeedsUpdate; + LLJoint* mTargetJoint; + LLViewerJointMesh* mTargetMesh; + F32 mCameraDistance; + F32 mCameraYaw; + F32 mCameraPitch; + F32 mCameraZoom; + LLVector3 mCameraOffset; + LLPointer mDummyAvatar; + U32 mTextureName; +}; + +class LLFloaterImagePreview : public LLFloaterNameDesc +{ +public: + LLFloaterImagePreview(const std::string& filename); + virtual ~LLFloaterImagePreview(); + + virtual bool postBuild(); + + bool handleMouseDown(S32 x, S32 y, MASK mask); + bool handleMouseUp(S32 x, S32 y, MASK mask); + bool handleHover(S32 x, S32 y, MASK mask); + bool handleScrollWheel(S32 x, S32 y, S32 clicks); + + static void onMouseCaptureLostImagePreview(LLMouseHandler*); + + void clearAllPreviewTextures(); + +protected: + static void onPreviewTypeCommit(LLUICtrl*,void*); + void draw(); + bool loadImage(const std::string& filename); + + LLPointer mRawImagep; + LLPointer mAvatarPreview; + LLPointer mSculptedPreview; + S32 mLastMouseX; + S32 mLastMouseY; + LLRect mPreviewRect; + LLRectf mPreviewImageRect; + LLPointer mImagep ; + + std::string mImageLoadError; +}; + +#endif // LL_LLFLOATERIMAGEPREVIEW_H diff --git a/indra/newview/llfloaterimcontainer.cpp b/indra/newview/llfloaterimcontainer.cpp index 2f8ada98cb..2d398de61a 100644 --- a/indra/newview/llfloaterimcontainer.cpp +++ b/indra/newview/llfloaterimcontainer.cpp @@ -1,2500 +1,2500 @@ -/** - * @file llfloaterimcontainer.cpp - * @brief Multifloater containing active IM sessions in separate tab container tabs - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterimsession.h" -#include "llfloaterimcontainer.h" - -#include "llfloaterreg.h" -#include "lllayoutstack.h" -#include "llfloaterimnearbychat.h" - -#include "llagent.h" -#include "llavataractions.h" -#include "llavatariconctrl.h" -#include "llavatarnamecache.h" -#include "llcallbacklist.h" -#include "lldonotdisturbnotificationstorage.h" -#include "llgroupactions.h" -#include "llgroupiconctrl.h" -#include "llflashtimer.h" -#include "llfloateravatarpicker.h" -#include "llfloaterpreference.h" -#include "llfloaterreporter.h" -#include "llimview.h" -#include "llnotificationsutil.h" -#include "lltoolbarview.h" -#include "lltransientfloatermgr.h" -#include "llviewercontrol.h" -#include "llconversationview.h" -#include "llcallbacklist.h" -#include "llworld.h" -#include "llsdserialize.h" -#include "llviewermenu.h" // is_agent_mappable -#include "llviewerobjectlist.h" - - -const S32 EVENTS_PER_IDLE_LOOP_CURRENT_SESSION = 80; -const S32 EVENTS_PER_IDLE_LOOP_BACKGROUND = 40; -const F32 EVENTS_PER_IDLE_LOOP_MIN_PERCENTAGE = 0.01f; // process a minimum of 1% of total events per frame - -// -// LLFloaterIMContainer -// -LLFloaterIMContainer::LLFloaterIMContainer(const LLSD& seed, const Params& params /*= getDefaultParams()*/) -: LLMultiFloater(seed, params), - mExpandCollapseBtn(NULL), - mConversationsRoot(NULL), - mConversationsEventStream("ConversationsEvents"), - mInitialized(false), - mIsFirstLaunch(true), - mConversationEventQueue() -{ - mEnableCallbackRegistrar.add("IMFloaterContainer.Check", boost::bind(&LLFloaterIMContainer::isActionChecked, this, _2)); - mCommitCallbackRegistrar.add("IMFloaterContainer.Action", boost::bind(&LLFloaterIMContainer::onCustomAction, this, _2)); - - mEnableCallbackRegistrar.add("Avatar.CheckItem", boost::bind(&LLFloaterIMContainer::checkContextMenuItem, this, _2)); - mEnableCallbackRegistrar.add("Avatar.EnableItem", boost::bind(&LLFloaterIMContainer::enableContextMenuItem, this, _2)); - mEnableCallbackRegistrar.add("Avatar.VisibleItem", boost::bind(&LLFloaterIMContainer::visibleContextMenuItem, this, _2)); - mCommitCallbackRegistrar.add("Avatar.DoToSelected", boost::bind(&LLFloaterIMContainer::doToSelected, this, _2)); - - mCommitCallbackRegistrar.add("Group.DoToSelected", boost::bind(&LLFloaterIMContainer::doToSelectedGroup, this, _2)); - - // Firstly add our self to IMSession observers, so we catch session events - LLIMMgr::getInstance()->addSessionObserver(this); - - mAutoResize = false; - LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::IM, this); -} - -LLFloaterIMContainer::~LLFloaterIMContainer() -{ - mConversationsEventStream.stopListening("ConversationsRefresh"); - gIdleCallbacks.deleteFunction(idle, this); - mNewMessageConnection.disconnect(); - LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::IM, this); - - if (mMicroChangedSignal.connected()) - { - mMicroChangedSignal.disconnect(); - } - - gSavedPerAccountSettings.setBOOL("ConversationsListPaneCollapsed", mConversationsPane->isCollapsed()); - gSavedPerAccountSettings.setBOOL("ConversationsMessagePaneCollapsed", mMessagesPane->isCollapsed()); - gSavedPerAccountSettings.setBOOL("ConversationsParticipantListCollapsed", !isParticipantListExpanded()); - - if (LLIMMgr::instanceExists()) - { - LLIMMgr::getInstance()->removeSessionObserver(this); - } -} - -void LLFloaterIMContainer::sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id, bool has_offline_msg) -{ - addConversationListItem(session_id); - LLFloaterIMSessionTab::addToHost(session_id); -} - -void LLFloaterIMContainer::sessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id) -{ - if(!isInVisibleChain()) - { - setVisibleAndFrontmost(false); - } - selectConversationPair(session_id, true); - collapseMessagesPane(false); -} - -void LLFloaterIMContainer::sessionVoiceOrIMStarted(const LLUUID& session_id) -{ - addConversationListItem(session_id); - LLFloaterIMSessionTab::addToHost(session_id); -} - -void LLFloaterIMContainer::sessionIDUpdated(const LLUUID& old_session_id, const LLUUID& new_session_id) -{ - // The general strategy when a session id is modified is to delete all related objects and create them anew. - - // Note however that the LLFloaterIMSession has its session id updated through a call to sessionInitReplyReceived() - // and do not need to be deleted and recreated (trying this creates loads of problems). We do need however to suppress - // its related mSessions record as it's indexed with the wrong id. - // Grabbing the updated LLFloaterIMSession and readding it in mSessions will eventually be done by addConversationListItem(). - mSessions.erase(old_session_id); - - // Delete the model and participants related to the old session - bool change_focus = removeConversationListItem(old_session_id); - - // Create a new conversation with the new id - addConversationListItem(new_session_id, change_focus); - LLFloaterIMSessionTab::addToHost(new_session_id); -} - - -LLConversationItem* LLFloaterIMContainer::getSessionModel(const LLUUID& session_id) -{ - conversations_items_map::iterator iter = mConversationsItems.find(session_id); - if (iter == mConversationsItems.end()) - { - return NULL; - } - else - { - return iter->second.get(); - } -} - -void LLFloaterIMContainer::sessionRemoved(const LLUUID& session_id) -{ - removeConversationListItem(session_id); -} - -// static -void LLFloaterIMContainer::onCurrentChannelChanged(const LLUUID& session_id) -{ - if (session_id != LLUUID::null) - { - LLFloaterIMContainer::getInstance()->showConversation(session_id); - } -} - -bool LLFloaterIMContainer::postBuild() -{ - mOrigMinWidth = getMinWidth(); - mOrigMinHeight = getMinHeight(); - - mNewMessageConnection = LLIMModel::instance().mNewMsgSignal.connect(boost::bind(&LLFloaterIMContainer::onNewMessageReceived, this, _1)); - // Do not call base postBuild to not connect to mCloseSignal to not close all floaters via Close button - // mTabContainer will be initialized in LLMultiFloater::addChild() - - setTabContainer(getChild("im_box_tab_container")); - mStubPanel = getChild("stub_panel"); - mStubTextBox = getChild("stub_textbox"); - mStubTextBox->setURLClickedCallback(boost::bind(&LLFloaterIMContainer::returnFloaterToHost, this)); - - mConversationsStack = getChild("conversations_stack"); - mConversationsPane = getChild("conversations_layout_panel"); - mMessagesPane = getChild("messages_layout_panel"); - - mConversationsListPanel = getChild("conversations_list_panel"); - - // Open IM session with selected participant on double click event - mConversationsListPanel->setDoubleClickCallback(boost::bind(&LLFloaterIMContainer::doToSelected, this, LLSD("im"))); - - // The resize limits for LLFloaterIMContainer should be updated, based on current values of width of conversation and message panels - mConversationsPane->getResizeBar()->setResizeListener(boost::bind(&LLFloaterIMContainer::assignResizeLimits, this)); - - // Create the root model and view for all conversation sessions - LLConversationItem* base_item = new LLConversationItem(getRootViewModel()); - - LLFolderView::Params p(LLUICtrlFactory::getDefaultParams()); - p.name = getName(); - p.title = getLabel(); - p.rect = LLRect(0, 0, getRect().getWidth(), 0); - p.parent_panel = mConversationsListPanel; - p.tool_tip = p.name; - p.listener = base_item; - p.view_model = &mConversationViewModel; - p.root = NULL; - p.use_ellipses = true; - p.options_menu = "menu_conversation.xml"; - mConversationsRoot = LLUICtrlFactory::create(p); - mConversationsRoot->setCallbackRegistrar(&mCommitCallbackRegistrar); - mConversationsRoot->setEnableRegistrar(&mEnableCallbackRegistrar); - - // Add listener to conversation model events - mConversationsEventStream.listen("ConversationsRefresh", boost::bind(&LLFloaterIMContainer::onConversationModelEvent, this, _1)); - - // a scroller for folder view - LLRect scroller_view_rect = mConversationsListPanel->getRect(); - scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom); - scroller_view_rect.mBottom += getChild("conversations_pane_buttons_stack")->getRect().getHeight(); - LLScrollContainer::Params scroller_params(LLUICtrlFactory::getDefaultParams()); - scroller_params.rect(scroller_view_rect); - - LLScrollContainer* scroller = LLUICtrlFactory::create(scroller_params); - scroller->setFollowsAll(); - mConversationsListPanel->addChild(scroller); - scroller->addChild(mConversationsRoot); - mConversationsRoot->setScrollContainer(scroller); - mConversationsRoot->setFollowsAll(); - mConversationsRoot->addChild(mConversationsRoot->mStatusTextBox); - - addConversationListItem(LLUUID()); // manually add nearby chat - - mExpandCollapseBtn = getChild("expand_collapse_btn"); - mExpandCollapseBtn->setClickedCallback(boost::bind(&LLFloaterIMContainer::onExpandCollapseButtonClicked, this)); - mStubCollapseBtn = getChild("stub_collapse_btn"); - mStubCollapseBtn->setClickedCallback(boost::bind(&LLFloaterIMContainer::onStubCollapseButtonClicked, this)); - mSpeakBtn = getChild("speak_btn"); - - mSpeakBtn->setMouseDownCallback(boost::bind(&LLFloaterIMContainer::onSpeakButtonPressed, this)); - mSpeakBtn->setMouseUpCallback(boost::bind(&LLFloaterIMContainer::onSpeakButtonReleased, this)); - - childSetAction("add_btn", boost::bind(&LLFloaterIMContainer::onAddButtonClicked, this)); - - collapseMessagesPane(gSavedPerAccountSettings.getBOOL("ConversationsMessagePaneCollapsed")); - collapseConversationsPane(gSavedPerAccountSettings.getBOOL("ConversationsListPaneCollapsed"), false); - LLAvatarNameCache::getInstance()->addUseDisplayNamesCallback(boost::bind(&LLFloaterIMSessionTab::processChatHistoryStyleUpdate, false)); - mMicroChangedSignal = LLVoiceClient::getInstance()->MicroChangedCallback(boost::bind(&LLFloaterIMContainer::updateSpeakBtnState, this)); - - if (! mMessagesPane->isCollapsed() && ! mConversationsPane->isCollapsed()) - { - S32 conversations_panel_width = gSavedPerAccountSettings.getS32("ConversationsListPaneWidth"); - LLRect conversations_panel_rect = mConversationsPane->getRect(); - conversations_panel_rect.mRight = conversations_panel_rect.mLeft + conversations_panel_width; - mConversationsPane->handleReshape(conversations_panel_rect, true); - } - - // Init the sort order now that the root had been created - setSortOrder(LLConversationSort(gSavedSettings.getU32("ConversationSortOrder"))); - - //We should expand nearby chat participants list for the new user - if(gAgent.isFirstLogin() || !gSavedPerAccountSettings.getBOOL("ConversationsParticipantListCollapsed")) - { - expandConversation(); - } - // Keep the xml set title around for when we have to overwrite it - mGeneralTitle = getTitle(); - - mInitialized = true; - - mIsFirstOpen = true; - - // Add callbacks: - // We'll take care of view updates on idle - gIdleCallbacks.addFunction(idle, this); - // When display name option change, we need to reload all participant names - LLAvatarNameCache::getInstance()->addUseDisplayNamesCallback(boost::bind(&LLFloaterIMContainer::processParticipantsStyleUpdate, this)); - - mParticipantRefreshTimer.setTimerExpirySec(0); - mParticipantRefreshTimer.start(); - - return true; -} - -void LLFloaterIMContainer::onOpen(const LLSD& key) -{ - LLMultiFloater::onOpen(key); - reSelectConversation(); - assignResizeLimits(); - - LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::getConversation(mSelectedSession); - session_floater->onOpen(key); -} - -// virtual -void LLFloaterIMContainer::addFloater(LLFloater* floaterp, - bool select_added_floater, - LLTabContainer::eInsertionPoint insertion_point) -{ - if(!floaterp) return; - - // already here - if (floaterp->getHost() == this) - { - openFloater(floaterp->getKey()); - return; - } - - LLUUID session_id = floaterp->getKey(); - - // Add the floater - LLMultiFloater::addFloater(floaterp, select_added_floater, insertion_point); - - - - LLIconCtrl* icon = 0; - bool is_in_group = gAgent.isInGroup(session_id, true); - LLUUID icon_id; - - if (is_in_group) - { - LLGroupIconCtrl::Params icon_params; - icon_params.group_id = session_id; - icon = LLUICtrlFactory::instance().create(icon_params); - icon_id = session_id; - - mSessions[session_id] = floaterp; - floaterp->mCloseSignal.connect(boost::bind(&LLFloaterIMContainer::onCloseFloater, this, session_id)); - } - else - { LLUUID avatar_id = session_id.notNull()? - LLIMModel::getInstance()->getOtherParticipantID(session_id) : LLUUID(); - - LLAvatarIconCtrl::Params icon_params; - icon_params.avatar_id = avatar_id; - icon = LLUICtrlFactory::instance().create(icon_params); - icon_id = avatar_id; - - mSessions[session_id] = floaterp; - floaterp->mCloseSignal.connect(boost::bind(&LLFloaterIMContainer::onCloseFloater, this, session_id)); - } - - LLFloaterIMSessionTab* floater = LLFloaterIMSessionTab::getConversation(session_id); - if (floater) - { - floater->updateChatIcon(icon_id); - } - - // forced resize of the floater - LLRect wrapper_rect = this->mTabContainer->getLocalRect(); - floaterp->setRect(wrapper_rect); - - mTabContainer->setTabImage(floaterp, icon); -} - - -void LLFloaterIMContainer::onCloseFloater(LLUUID& id) -{ - mSessions.erase(id); - setFocus(true); -} - -void LLFloaterIMContainer::onNewMessageReceived(const LLSD& data) -{ - LLUUID session_id = data["session_id"].asUUID(); - LLFloater* floaterp = get_ptr_in_map(mSessions, session_id); - LLFloater* current_floater = LLMultiFloater::getActiveFloater(); - - if(floaterp && current_floater && floaterp != current_floater) - { - if(LLMultiFloater::isFloaterFlashing(floaterp)) - LLMultiFloater::setFloaterFlashing(floaterp, false); - LLMultiFloater::setFloaterFlashing(floaterp, true); - } -} - -void LLFloaterIMContainer::onStubCollapseButtonClicked() -{ - collapseMessagesPane(true); -} - -void LLFloaterIMContainer::onSpeakButtonPressed() -{ - LLVoiceClient::getInstance()->inputUserControlState(true); - updateSpeakBtnState(); -} - -void LLFloaterIMContainer::onSpeakButtonReleased() -{ - LLVoiceClient::getInstance()->inputUserControlState(false); - updateSpeakBtnState(); -} - -void LLFloaterIMContainer::onExpandCollapseButtonClicked() -{ - if (mConversationsPane->isCollapsed() && mMessagesPane->isCollapsed() - && gSavedPerAccountSettings.getBOOL("ConversationsExpandMessagePaneFirst")) - { - // Expand the messages pane from ultra minimized state - // if it was collapsed last in order. - collapseMessagesPane(false); - } - else - { - collapseConversationsPane(!mConversationsPane->isCollapsed()); - } - reSelectConversation(); -} - -LLFloaterIMContainer* LLFloaterIMContainer::findInstance() -{ - return LLFloaterReg::findTypedInstance("im_container"); -} - -LLFloaterIMContainer* LLFloaterIMContainer::getInstance() -{ - return LLFloaterReg::getTypedInstance("im_container"); -} - -// Update all participants in the conversation lists -void LLFloaterIMContainer::processParticipantsStyleUpdate() -{ - // On each session in mConversationsItems - for (conversations_items_map::iterator it_session = mConversationsItems.begin(); it_session != mConversationsItems.end(); it_session++) - { - // Get the current session descriptors - LLConversationItem* session_model = it_session->second; - // Iterate through each model participant child - LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = session_model->getChildrenBegin(); - LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = session_model->getChildrenEnd(); - while (current_participant_model != end_participant_model) - { - LLConversationItemParticipant* participant_model = dynamic_cast(*current_participant_model); - if (participant_model) - { - // Get the avatar name for this participant id from the cache and update the model - participant_model->updateName(); - } - // Next participant - current_participant_model++; - } - } -} - -// static -void LLFloaterIMContainer::idle(void* user_data) -{ - LLFloaterIMContainer* self = static_cast(user_data); - - self->idleProcessEvents(); - - if (!self->getVisible() || self->isMinimized()) - { - return; - } - self->idleUpdate(); -} - -void LLFloaterIMContainer::idleUpdate() -{ - if (mTabContainer->getTabCount() == 0) - { - // Do not close the container when every conversation is torn off because the user - // still needs the conversation list. Simply collapse the message pane in that case. - collapseMessagesPane(true); - } - - U32 sort_order = mConversationViewModel.getSorter().getSortOrderParticipants(); - - if (mParticipantRefreshTimer.hasExpired()) - { - const LLConversationItem *current_session = getCurSelectedViewModelItem(); - if (current_session) - { - if (current_session->getType() == LLConversationItem::CONV_SESSION_GROUP) - { - // Update moderator options visibility - LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = current_session->getChildrenBegin(); - LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = current_session->getChildrenEnd(); - bool is_moderator = isGroupModerator(); - bool can_ban = haveAbilityToBan(); - while (current_participant_model != end_participant_model) - { - LLConversationItemParticipant* participant_model = dynamic_cast(*current_participant_model); - if (participant_model) - { - participant_model->setModeratorOptionsVisible(is_moderator); - participant_model->setGroupBanVisible(can_ban && participant_model->getUUID() != gAgentID); - } - - current_participant_model++; - } - } - - // Update floater's title as required by the currently selected session or use the default title - LLFloaterIMSession * conversation_floaterp = LLFloaterIMSession::findInstance(current_session->getUUID()); - setTitle(conversation_floaterp && conversation_floaterp->needsTitleOverwrite() ? conversation_floaterp->getTitle() : mGeneralTitle); - } - - mParticipantRefreshTimer.setTimerExpirySec(1.0f); - } - - // Update the distance to agent in the nearby chat session if required - // Note: it makes no sense of course to update the distance in other session - if (sort_order == LLConversationFilter::SO_DISTANCE) - { - // almost real-time updates - setNearbyDistances(); //calls arrange all - } - mConversationsRoot->update(); //arranges, resizes, heavy - - // "Manually" resize of mConversationsPane: same as temporarity cancellation of the flag "auto_resize=false" for it - if (!mConversationsPane->isCollapsed() && mMessagesPane->isCollapsed()) - { - LLRect stack_rect = mConversationsStack->getRect(); - mConversationsPane->reshape(stack_rect.getWidth(), stack_rect.getHeight(), true); - } -} - -void LLFloaterIMContainer::idleProcessEvents() -{ - LLUUID current_session_id = getSelectedSession(); - conversations_items_deque::iterator iter = mConversationEventQueue.begin(); - conversations_items_deque::iterator end = mConversationEventQueue.end(); - while (iter != end) - { - std::deque &events = iter->second; - if (!events.empty()) - { - S32 events_to_handle; - S32 query_size = (S32)events.size(); - if (current_session_id == iter->first) - { - events_to_handle = EVENTS_PER_IDLE_LOOP_CURRENT_SESSION; - } - else - { - events_to_handle = EVENTS_PER_IDLE_LOOP_BACKGROUND; - } - - if (events_to_handle <= query_size) - { - // Some groups can be very large and can generate huge amount of updates, scale processing up to keep up - events_to_handle = llmax(events_to_handle, (S32)(query_size * EVENTS_PER_IDLE_LOOP_MIN_PERCENTAGE)); - } - else - { - events_to_handle = query_size; - } - - for (S32 i = 0; i < events_to_handle; i++) - { - handleConversationModelEvent(events.back()); - events.pop_back(); - } - } - iter++; - } -} - -bool LLFloaterIMContainer::onConversationModelEvent(const LLSD& event) -{ - LLUUID id = event.get("session_uuid").asUUID(); - mConversationEventQueue[id].push_front(event); - return true; -} - - -void LLFloaterIMContainer::handleConversationModelEvent(const LLSD& event) -{ - - // Note: In conversations, the model is not responsible for creating the view, which is a good thing. This means that - // the model could change substantially and the view could echo only a portion of this model (though currently the - // conversation view does echo the conversation model 1 to 1). - // Consequently, the participant views need to be created either by the session view or by the container panel. - // For the moment, we create them here, at the container level, to conform to the pattern implemented in llinventorypanel.cpp - // (see LLInventoryPanel::buildNewViews()). - - std::string type = event.get("type").asString(); - LLUUID session_id = event.get("session_uuid").asUUID(); - LLUUID participant_id = event.get("participant_uuid").asUUID(); - - LLConversationViewSession* session_view = dynamic_cast(get_ptr_in_map(mConversationsWidgets,session_id)); - if (!session_view) - { - // We skip events that are not associated with a session - return; - } - LLConversationViewParticipant* participant_view = session_view->findParticipant(participant_id); - LLFloaterIMSessionTab *conversation_floater = (session_id.isNull() ? - (LLFloaterIMSessionTab*)(LLFloaterReg::findTypedInstance("nearby_chat")) - : (LLFloaterIMSessionTab*)(LLFloaterIMSession::findInstance(session_id))); - - if (type == "remove_participant") - { - // Remove a participant view from the hierarchical conversation list - if (participant_view) - { - session_view->extractItem(participant_view); - delete participant_view; - session_view->refresh(); - mConversationsRoot->arrangeAll(); - } - // Remove a participant view from the conversation floater - if (conversation_floater) - { - conversation_floater->removeConversationViewParticipant(participant_id); - } - } - else if (type == "add_participant") - { - LLConversationItem* item = getSessionModel(session_id); - LLConversationItemSession* session_model = dynamic_cast(item); - LLConversationItemParticipant* participant_model = (session_model ? session_model->findParticipant(participant_id) : NULL); - LLIMModel::LLIMSession * im_sessionp = LLIMModel::getInstance()->findIMSession(session_id); - if (!participant_view && session_model && participant_model) - { - if (session_id.isNull() || (im_sessionp && !im_sessionp->isP2PSessionType())) - { - participant_view = createConversationViewParticipant(participant_model); - participant_view->addToFolder(session_view); - participant_view->setVisible(true); - } - } - // Add a participant view to the conversation floater - if (conversation_floater && participant_model) - { - bool skip_updating = im_sessionp && im_sessionp->isGroupChat(); - conversation_floater->addConversationViewParticipant(participant_model, !skip_updating); - } - } - else if (type == "update_participant") - { - // Update the participant view in the hierarchical conversation list - if (participant_view) - { - participant_view->refresh(); - } - // Update the participant view in the conversation floater - if (conversation_floater) - { - conversation_floater->updateConversationViewParticipant(participant_id); - } - } - else if (type == "update_session") - { - session_view->refresh(); - } - - mConversationViewModel.requestSortAll(); - mConversationsRoot->arrangeAll(); -} - -void LLFloaterIMContainer::draw() -{ - LLFloater::draw(); -} - -void LLFloaterIMContainer::tabClose() -{ - if (mTabContainer->getTabCount() == 0) - { - // Do not close the container when every conversation is torn off because the user - // still needs the conversation list. Simply collapse the message pane in that case. - collapseMessagesPane(true); - } -} - -//Shows/hides the stub panel when a conversation floater is torn off -void LLFloaterIMContainer::showStub(bool stub_is_visible) -{ - S32 tabCount = 0; - LLPanel * tabPanel = NULL; - - if(stub_is_visible) - { - tabCount = mTabContainer->getTabCount(); - - //Hide all tabs even stub - for(S32 i = 0; i < tabCount; ++i) - { - tabPanel = mTabContainer->getPanelByIndex(i); - - if(tabPanel) - { - tabPanel->setVisible(false); - } - } - - //Set the index to the stub panel since we will be showing the stub - mTabContainer->setCurrentPanelIndex(0); - } - - //Now show/hide the stub - mStubPanel->setVisible(stub_is_visible); -} - -// listener for click on mStubTextBox2 -void LLFloaterIMContainer::returnFloaterToHost() -{ - LLUUID session_id = this->getSelectedSession(); - LLFloaterIMSessionTab* floater = LLFloaterIMSessionTab::getConversation(session_id); - floater->onTearOffClicked(); -} - -void LLFloaterIMContainer::setMinimized(bool b) -{ - bool was_minimized = isMinimized(); - LLMultiFloater::setMinimized(b); - - //Switching from minimized to un-minimized - if(was_minimized && !b) - { - gToolBarView->flashCommand(LLCommandId("chat"), false); - LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(mSelectedSession); - - if(session_floater && !session_floater->isTornOff()) - { - //When in DND mode, remove stored IM notifications - //Nearby chat (Null) IMs are not stored while in DND mode, so can ignore removal - if(gAgent.isDoNotDisturb() && mSelectedSession.notNull()) - { - LLDoNotDisturbNotificationStorage::getInstance()->removeNotification(LLDoNotDisturbNotificationStorage::toastName, mSelectedSession); - } - } - } -} - -void LLFloaterIMContainer::setVisible(bool visible) -{ - LLFloaterIMNearbyChat* nearby_chat; - if (visible) - { - // Make sure we have the Nearby Chat present when showing the conversation container - nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); - if ((nearby_chat == NULL) || mIsFirstOpen) - { - mIsFirstOpen = false; - // If not found, force the creation of the nearby chat conversation panel - // *TODO: find a way to move this to XML as a default panel or something like that - LLSD name("nearby_chat"); - LLFloaterReg::toggleInstanceOrBringToFront(name); - selectConversationPair(LLUUID(NULL), false, false); - } - - flashConversationItemWidget(mSelectedSession,false); - - LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(mSelectedSession); - if(session_floater && !session_floater->isMinimized()) - { - //When in DND mode, remove stored IM notifications - //Nearby chat (Null) IMs are not stored while in DND mode, so can ignore removal - if(gAgent.isDoNotDisturb() && mSelectedSession.notNull()) - { - LLDoNotDisturbNotificationStorage::getInstance()->removeNotification(LLDoNotDisturbNotificationStorage::toastName, mSelectedSession); - } - } - } - - nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); - if (nearby_chat) - { - LLFloaterIMSessionTab::addToHost(LLUUID()); - } - - if (!LLFloater::isQuitRequested()) - { - // We need to show/hide all the associated conversations that have been torn off - // (and therefore, are not longer managed by the multifloater), - // so that they show/hide with the conversations manager. - conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin(); - for (; widget_it != mConversationsWidgets.end(); ++widget_it) - { - LLConversationViewSession* widget = dynamic_cast(widget_it->second); - if (widget) - { - LLFloater* session_floater = widget->getSessionFloater(); - if (session_floater != nearby_chat) - { - widget->setVisibleIfDetached(visible); - } - } - } - } - - // Now, do the normal multifloater show/hide - LLMultiFloater::setVisible(visible); -} - -void LLFloaterIMContainer::getDetachedConversationFloaters(floater_list_t& floaters) -{ - LLFloaterIMNearbyChat *nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); - - for (const auto& [key, fvi] : mConversationsWidgets) - { - LLConversationViewSession* widget = dynamic_cast(fvi); - if (widget) - { - LLFloater* session_floater = widget->getSessionFloater(); - - // Exclude nearby chat from output, as it should be handled separately - if (session_floater && session_floater->isDetachedAndNotMinimized() - && session_floater != nearby_chat) - { - floaters.push_back(session_floater); - } - } - } -} - -void LLFloaterIMContainer::setVisibleAndFrontmost(bool take_focus, const LLSD& key) -{ - LLMultiFloater::setVisibleAndFrontmost(take_focus, key); - // Do not select "Nearby Chat" conversation, since it will bring its window to front - // Only select other sessions - if (!getSelectedSession().isNull()) - { - selectConversationPair(getSelectedSession(), false, take_focus); - } - if (mInitialized && mIsFirstLaunch) - { - collapseMessagesPane(gSavedPerAccountSettings.getBOOL("ConversationsMessagePaneCollapsed")); - mIsFirstLaunch = false; -} -} - -void LLFloaterIMContainer::updateResizeLimits() -{ - LLMultiFloater::updateResizeLimits(); - assignResizeLimits(); -} - -bool LLFloaterIMContainer::isMessagesPaneCollapsed() -{ - return mMessagesPane->isCollapsed(); -} - -bool LLFloaterIMContainer::isConversationsPaneCollapsed() -{ - return mConversationsPane->isCollapsed(); -} - -void LLFloaterIMContainer::collapseMessagesPane(bool collapse) -{ - if (mMessagesPane->isCollapsed() == collapse) - { - return; - } - - // Save current width of panels before collapsing/expanding right pane. - S32 conv_pane_width = mConversationsPane->getRect().getWidth(); - S32 msg_pane_width = mMessagesPane->getRect().getWidth(); - - if (collapse) - { - // Save the messages pane width before collapsing it. - gSavedPerAccountSettings.setS32("ConversationsMessagePaneWidth", msg_pane_width); - - // Save the order in which the panels are closed to reverse user's last action. - gSavedPerAccountSettings.setBOOL("ConversationsExpandMessagePaneFirst", mConversationsPane->isCollapsed()); - } - - mConversationsPane->setIgnoreReshape(collapse); - - // Show/hide the messages pane. - mConversationsStack->collapsePanel(mMessagesPane, collapse); - - // Make sure layout is updated before resizing conversation pane. - mConversationsStack->updateLayout(); - - reshapeFloaterAndSetResizeLimits(collapse, gSavedPerAccountSettings.getS32("ConversationsMessagePaneWidth")); - - if (!collapse) - { - // Restore conversation's pane previous width after expanding messages pane. - mConversationsPane->setTargetDim(conv_pane_width); - } -} - -void LLFloaterIMContainer::collapseConversationsPane(bool collapse, bool save_is_allowed /*=true*/) -{ - if (mConversationsPane->isCollapsed() == collapse) - { - return; - } - - LLView* button_panel = getChild("conversations_pane_buttons_expanded"); - button_panel->setVisible(!collapse); - mExpandCollapseBtn->setImageOverlay(getString(collapse ? "expand_icon" : "collapse_icon")); - - // Save current width of Conversation panel before collapsing/expanding right pane. - S32 conv_pane_width = mConversationsPane->getRect().getWidth(); - - if (collapse && save_is_allowed) - { - // Save the conversations pane width before collapsing it. - gSavedPerAccountSettings.setS32("ConversationsListPaneWidth", conv_pane_width); - - // Save the order in which the panels are closed to reverse user's last action. - gSavedPerAccountSettings.setBOOL("ConversationsExpandMessagePaneFirst", !mMessagesPane->isCollapsed()); - } - - mConversationsStack->collapsePanel(mConversationsPane, collapse); - if (!collapse) - { - // Make sure layout is updated before resizing conversation pane. - mConversationsStack->updateLayout(); - // Restore conversation's pane previous width. - mConversationsPane->setTargetDim(gSavedPerAccountSettings.getS32("ConversationsListPaneWidth")); - } - - S32 delta_width = gSavedPerAccountSettings.getS32("ConversationsListPaneWidth") - - mConversationsPane->getMinDim() - mConversationsStack->getPanelSpacing() + 1; - - reshapeFloaterAndSetResizeLimits(collapse, delta_width); - - for (conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin(); - widget_it != mConversationsWidgets.end(); ++widget_it) - { - LLConversationViewSession* widget = dynamic_cast(widget_it->second); - if (widget) - { - widget->toggleCollapsedMode(collapse); - - // force closing all open conversations when collapsing to minimized state - if (collapse) - { - widget->setOpen(false); - } - widget->requestArrange(); - } - } -} - -void LLFloaterIMContainer::reshapeFloaterAndSetResizeLimits(bool collapse, S32 delta_width) -{ - LLRect floater_rect = getRect(); - floater_rect.mRight += ((collapse ? -1 : 1) * delta_width); - - // Set by_user = true so that reshaped rect is saved in user_settings. - setShape(floater_rect, true); - updateResizeLimits(); - - bool at_least_one_panel_is_expanded = - ! (mConversationsPane->isCollapsed() && mMessagesPane->isCollapsed()); - - setCanResize(at_least_one_panel_is_expanded); - setCanMinimize(at_least_one_panel_is_expanded); - - assignResizeLimits(); -} - -void LLFloaterIMContainer::assignResizeLimits() -{ - bool is_conv_pane_expanded = !mConversationsPane->isCollapsed(); - bool is_msg_pane_expanded = !mMessagesPane->isCollapsed(); - - S32 summary_width_of_visible_borders = (is_msg_pane_expanded ? mConversationsStack->getPanelSpacing() : 0) + 1; - - S32 conv_pane_target_width = is_conv_pane_expanded - ? ( is_msg_pane_expanded?mConversationsPane->getRect().getWidth():mConversationsPane->getExpandedMinDim() ) - : mConversationsPane->getMinDim(); - - S32 msg_pane_min_width = is_msg_pane_expanded ? mMessagesPane->getExpandedMinDim() : 0; - S32 new_min_width = conv_pane_target_width + msg_pane_min_width + summary_width_of_visible_borders; - - setResizeLimits(new_min_width, getMinHeight()); - - mConversationsStack->updateLayout(); -} - -void LLFloaterIMContainer::onAddButtonClicked() -{ - LLView * button = findChild("conversations_pane_buttons_expanded")->findChild("add_btn"); - LLFloater* root_floater = gFloaterView->getParentFloater(this); - LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(boost::bind(&LLFloaterIMContainer::onAvatarPicked, this, _1), true, true, true, root_floater->getName(), button); - - if (picker && root_floater) - { - root_floater->addDependentFloater(picker); - } -} - -void LLFloaterIMContainer::onAvatarPicked(const uuid_vec_t& ids) -{ - if (ids.size() == 1) - { - LLAvatarActions::startIM(ids.back()); - } - else - { - LLAvatarActions::startConference(ids); - } -} - -void LLFloaterIMContainer::onCustomAction(const LLSD& userdata) -{ - std::string command = userdata.asString(); - - if ("sort_sessions_by_type" == command) - { - setSortOrderSessions(LLConversationFilter::SO_SESSION_TYPE); - } - if ("sort_sessions_by_name" == command) - { - setSortOrderSessions(LLConversationFilter::SO_NAME); - } - if ("sort_sessions_by_recent" == command) - { - setSortOrderSessions(LLConversationFilter::SO_DATE); - } - if ("sort_participants_by_name" == command) - { - setSortOrderParticipants(LLConversationFilter::SO_NAME); - } - if ("sort_participants_by_recent" == command) - { - setSortOrderParticipants(LLConversationFilter::SO_DATE); - } - if ("sort_participants_by_distance" == command) - { - setSortOrderParticipants(LLConversationFilter::SO_DISTANCE); - } - if ("chat_preferences" == command) - { - LLFloaterPreference * floater_prefp = LLFloaterReg::showTypedInstance("preferences"); - if (floater_prefp) - { - floater_prefp->selectChatPanel(); - } - } - if ("privacy_preferences" == command) - { - LLFloaterPreference * floater_prefp = LLFloaterReg::showTypedInstance("preferences"); - if (floater_prefp) - { - floater_prefp->selectPrivacyPanel(); - } - } - if ("Translating.Toggle" == command) - { - gSavedSettings.setBOOL("TranslateChat", !gSavedSettings.getBOOL("TranslateChat")); - } -} - -bool LLFloaterIMContainer::isActionChecked(const LLSD& userdata) -{ - LLConversationSort order = mConversationViewModel.getSorter(); - std::string command = userdata.asString(); - if ("sort_sessions_by_type" == command) - { - return (order.getSortOrderSessions() == LLConversationFilter::SO_SESSION_TYPE); - } - if ("sort_sessions_by_name" == command) - { - return (order.getSortOrderSessions() == LLConversationFilter::SO_NAME); - } - if ("sort_sessions_by_recent" == command) - { - return (order.getSortOrderSessions() == LLConversationFilter::SO_DATE); - } - if ("sort_participants_by_name" == command) - { - return (order.getSortOrderParticipants() == LLConversationFilter::SO_NAME); - } - if ("sort_participants_by_recent" == command) - { - return (order.getSortOrderParticipants() == LLConversationFilter::SO_DATE); - } - if ("sort_participants_by_distance" == command) - { - return (order.getSortOrderParticipants() == LLConversationFilter::SO_DISTANCE); - } - if ("Translating.Enabled" == command) - { - return gSavedPerAccountSettings.getBOOL("TranslatingEnabled"); - } - if ("Translating.On" == command) - { - return gSavedSettings.getBOOL("TranslateChat"); - } - return false; -} - -void LLFloaterIMContainer::setSortOrderSessions(const LLConversationFilter::ESortOrderType order) -{ - LLConversationSort old_order = mConversationViewModel.getSorter(); - if (order != old_order.getSortOrderSessions()) - { - old_order.setSortOrderSessions(order); - setSortOrder(old_order); - } -} - -void LLFloaterIMContainer::setSortOrderParticipants(const LLConversationFilter::ESortOrderType order) -{ - LLConversationSort old_order = mConversationViewModel.getSorter(); - if (order != old_order.getSortOrderParticipants()) - { - old_order.setSortOrderParticipants(order); - setSortOrder(old_order); - } -} - -void LLFloaterIMContainer::setSortOrder(const LLConversationSort& order) -{ - mConversationViewModel.setSorter(order); - mConversationsRoot->arrangeAll(); - // try to keep selection onscreen, even if it wasn't to start with - mConversationsRoot->scrollToShowSelection(); - - // Notify all conversation (torn off or not) of the change to the sort order - // Note: For the moment, the sort order is *unique* across all conversations. That might change in the future. - for (conversations_items_map::iterator it_session = mConversationsItems.begin(); it_session != mConversationsItems.end(); it_session++) - { - LLUUID session_id = it_session->first; - LLFloaterIMSessionTab *conversation_floater = (session_id.isNull() ? (LLFloaterIMSessionTab*)(LLFloaterReg::findTypedInstance("nearby_chat")) : (LLFloaterIMSessionTab*)(LLFloaterIMSession::findInstance(session_id))); - if (conversation_floater) - { - conversation_floater->setSortOrder(order); - } - } - - gSavedSettings.setU32("ConversationSortOrder", (U32)order); -} - -void LLFloaterIMContainer::getSelectedUUIDs(uuid_vec_t& selected_uuids, bool participant_uuids/* = true*/) -{ - const std::set selectedItems = mConversationsRoot->getSelectionList(); - - std::set::const_iterator it = selectedItems.begin(); - const std::set::const_iterator it_end = selectedItems.end(); - LLConversationItem * conversationItem; - - for (; it != it_end; ++it) - { - conversationItem = static_cast((*it)->getViewModelItem()); - - //When a one-on-one conversation exists, retrieve the participant id from the conversation floater - if(conversationItem->getType() == LLConversationItem::CONV_SESSION_1_ON_1 && participant_uuids) - { - LLFloaterIMSession * conversation_floaterp = LLFloaterIMSession::findInstance(conversationItem->getUUID()); - LLUUID participant_id = conversation_floaterp->getOtherParticipantUUID(); - selected_uuids.push_back(participant_id); - } - else - { - selected_uuids.push_back(conversationItem->getUUID()); - } - } -} - -const LLConversationItem * LLFloaterIMContainer::getCurSelectedViewModelItem() -{ - LLConversationItem * conversation_item = NULL; - - if(mConversationsRoot && - mConversationsRoot->getCurSelectedItem() && - mConversationsRoot->getCurSelectedItem()->getViewModelItem()) - { - LLFloaterIMSessionTab *selected_session_floater = LLFloaterIMSessionTab::getConversation(mSelectedSession); - if (selected_session_floater && !selected_session_floater->getHost() && selected_session_floater->getCurSelectedViewModelItem()) - { - conversation_item = selected_session_floater->getCurSelectedViewModelItem(); - } - else - { - conversation_item = static_cast(mConversationsRoot->getCurSelectedItem()->getViewModelItem()); - } - } - - return conversation_item; -} - -void LLFloaterIMContainer::getParticipantUUIDs(uuid_vec_t& selected_uuids) -{ - //Find the conversation floater associated with the selected id - const LLConversationItem * conversation_item = getCurSelectedViewModelItem(); - - if (NULL == conversation_item) - { - return; - } - - getSelectedUUIDs(selected_uuids); -} - -void LLFloaterIMContainer::doToParticipants(const std::string& command, uuid_vec_t& selectedIDS) -{ - if (selectedIDS.size() == 1) - { - const LLUUID& userID = selectedIDS.front(); - if ("view_profile" == command) - { - LLAvatarActions::showProfile(userID); - } - else if ("im" == command) - { - if (gAgent.getID() != userID) - { - LLAvatarActions::startIM(userID); - } - } - else if ("offer_teleport" == command) - { - LLAvatarActions::offerTeleport(selectedIDS); - } - else if ("request_teleport" == command) - { - LLAvatarActions::teleportRequest(selectedIDS.front()); - } - else if ("voice_call" == command) - { - LLAvatarActions::startCall(userID); - } - else if ("chat_history" == command) - { - LLAvatarActions::viewChatHistory(userID); - } - else if ("add_friend" == command) - { - LLAvatarActions::requestFriendshipDialog(userID); - } - else if ("remove_friend" == command) - { - LLAvatarActions::removeFriendDialog(userID); - } - else if ("invite_to_group" == command) - { - LLAvatarActions::inviteToGroup(userID); - } - else if ("zoom_in" == command) - { - handle_zoom_to_object(userID); - } - else if ("map" == command) - { - LLAvatarActions::showOnMap(userID); - } - else if ("share" == command) - { - LLAvatarActions::share(userID); - } - else if ("pay" == command) - { - LLAvatarActions::pay(userID); - } - else if ("report_abuse" == command) - { - LLAvatarName av_name; - if (LLAvatarNameCache::get(userID, &av_name)) - { - LLFloaterReporter::showFromAvatar(userID, av_name.getCompleteName()); - } - else - { - LLFloaterReporter::showFromAvatar(userID, "not avaliable"); - } - } - else if ("block_unblock" == command) - { - LLAvatarActions::toggleMute(userID, LLMute::flagVoiceChat); - } - else if ("mute_unmute" == command) - { - LLAvatarActions::toggleMute(userID, LLMute::flagTextChat); - } - else if ("selected" == command || "mute_all" == command || "unmute_all" == command) - { - moderateVoice(command, userID); - } - else if ("toggle_allow_text_chat" == command) - { - toggleAllowTextChat(userID); - } - else if ("ban_member" == command) - { - banSelectedMember(userID); - } - } - else if (selectedIDS.size() > 1) - { - if ("im" == command) - { - LLAvatarActions::startConference(selectedIDS); - } - else if ("offer_teleport" == command) - { - LLAvatarActions::offerTeleport(selectedIDS); - } - else if ("voice_call" == command) - { - LLAvatarActions::startAdhocCall(selectedIDS); - } - else if ("remove_friend" == command) - { - LLAvatarActions::removeFriendsDialog(selectedIDS); - } - } -} - -void LLFloaterIMContainer::doToSelectedConversation(const std::string& command, uuid_vec_t& selectedIDS) -{ - //Find the conversation floater associated with the selected id - const LLConversationItem * conversationItem = getCurSelectedViewModelItem(); - LLFloaterIMSession *conversationFloater = LLFloaterIMSession::findInstance(conversationItem->getUUID()); - - if(conversationFloater) - { - //Close the selected conversation - if("close_conversation" == command) - { - LLFloater::onClickClose(conversationFloater); - } - else if("close_selected_conversations" == command) - { - getSelectedUUIDs(selectedIDS,false); - closeSelectedConversations(selectedIDS); - } - else if("open_voice_conversation" == command) - { - gIMMgr->startCall(conversationItem->getUUID()); - } - else if("disconnect_from_voice" == command) - { - gIMMgr->endCall(conversationItem->getUUID()); - } - else if("chat_history" == command) - { - if (selectedIDS.size() > 0) - { - if(conversationItem->getType() == LLConversationItem::CONV_SESSION_GROUP) - { - LLFloaterReg::showInstance("preview_conversation", conversationItem->getUUID(), true); - } - else if(conversationItem->getType() == LLConversationItem::CONV_SESSION_AD_HOC) - { - LLConversation* conv = LLConversationLog::instance().findConversation(LLIMModel::getInstance()->findIMSession(conversationItem->getUUID())); - if(conv) - { - LLFloaterReg::showInstance("preview_conversation", conv->getSessionID(), true); - } - } - else - { - LLAvatarActions::viewChatHistory(selectedIDS.front()); - } - } - } - else - { - if(conversationItem->getType() == LLConversationItem::CONV_SESSION_1_ON_1) - { - doToParticipants(command, selectedIDS); - } - } - } - //if there is no LLFloaterIMSession* instance for selected conversation it might be Nearby chat - else - { - if(conversationItem->getType() == LLConversationItem::CONV_SESSION_NEARBY) - { - if("chat_history" == command) - { - LLFloaterReg::showInstance("preview_conversation", LLSD(LLUUID::null), true); - } -} - } -} - -void LLFloaterIMContainer::doToSelected(const LLSD& userdata) -{ - std::string command = userdata.asString(); - const LLConversationItem * conversationItem = getCurSelectedViewModelItem(); - uuid_vec_t selected_uuids; - - if(conversationItem != NULL) - { - getParticipantUUIDs(selected_uuids); - - if(conversationItem->getType() == LLConversationItem::CONV_PARTICIPANT) - { - doToParticipants(command, selected_uuids); - } - else - { - doToSelectedConversation(command, selected_uuids); - } - } -} - -void LLFloaterIMContainer::doToSelectedGroup(const LLSD& userdata) -{ - std::string action = userdata.asString(); - - if (action == "group_profile") - { - LLGroupActions::show(mSelectedSession); - } - else if (action == "activate_group") - { - LLGroupActions::activate(mSelectedSession); - } - else if (action == "leave_group") - { - LLGroupActions::leave(mSelectedSession); - } -} - -bool LLFloaterIMContainer::enableContextMenuItem(const LLSD& userdata) -{ - const std::string& item = userdata.asString(); - uuid_vec_t uuids; - getParticipantUUIDs(uuids); - - - //If there is group or ad-hoc chat in multiselection, everything needs to be disabled - if(uuids.size() > 1) - { - const std::set selectedItems = mConversationsRoot->getSelectionList(); - LLConversationItem * conversationItem; - for(std::set::const_iterator it = selectedItems.begin(); it != selectedItems.end(); ++it) - { - conversationItem = static_cast((*it)->getViewModelItem()); - if((conversationItem->getType() == LLConversationItem::CONV_SESSION_GROUP) || (conversationItem->getType() == LLConversationItem::CONV_SESSION_AD_HOC)) - { - return false; - } - } - } - - if ("conversation_log" == item) - { - return gSavedPerAccountSettings.getS32("KeepConversationLogTranscripts") > 0; - } - - //Enable Chat history item for ad-hoc and group conversations - if ("can_chat_history" == item && uuids.size() > 0) - { - //Disable menu item if selected participant is user agent - if(uuids.front() != gAgentID) - { - if (getCurSelectedViewModelItem()->getType() == LLConversationItem::CONV_SESSION_NEARBY) - { - return LLLogChat::isNearbyTranscriptExist(); - } - else if (getCurSelectedViewModelItem()->getType() == LLConversationItem::CONV_SESSION_AD_HOC) - { - const LLConversation* conv = LLConversationLog::instance().findConversation(LLIMModel::getInstance()->findIMSession(uuids.front())); - if(conv) - { - return LLLogChat::isAdHocTranscriptExist(conv->getHistoryFileName()); - } - return false; - } - else - { - bool is_group = (getCurSelectedViewModelItem()->getType() == LLConversationItem::CONV_SESSION_GROUP); - return LLLogChat::isTranscriptExist(uuids.front(),is_group); - } - } - } - - // If nothing is selected(and selected item is not group chat), everything needs to be disabled - if (uuids.size() <= 0) - { - if(getCurSelectedViewModelItem()) - { - return getCurSelectedViewModelItem()->getType() == LLConversationItem::CONV_SESSION_GROUP; - } - return false; - } - - if("can_activate_group" == item) - { - LLUUID selected_group_id = getCurSelectedViewModelItem()->getUUID(); - return gAgent.getGroupID() != selected_group_id; - } - - return enableContextMenuItem(item, uuids); -} - -bool LLFloaterIMContainer::enableContextMenuItem(const std::string& item, uuid_vec_t& uuids) -{ - // Extract the single select info - bool is_single_select = (uuids.size() == 1); - const LLUUID& single_id = uuids.front(); - - if ("can_chat_history" == item && is_single_select) - { - return LLLogChat::isTranscriptExist(uuids.front(),false); - } - - // Handle options that are applicable to all including the user agent - if ("can_view_profile" == item) - { - return is_single_select; - } - - bool is_moderator_option = ("can_moderate_voice" == item) || ("can_allow_text_chat" == item) || ("can_mute" == item) || ("can_unmute" == item); - - // Beyond that point, if only the user agent is selected, everything is disabled - if (is_single_select && (single_id == gAgentID)) - { - if (is_moderator_option) - { - return enableModerateContextMenuItem(item, true); - } - else - { - return false; - } - } - - // If the user agent is selected with others, everything is disabled - for (uuid_vec_t::const_iterator id = uuids.begin(); id != uuids.end(); ++id) - { - if (gAgent.getID() == *id) - { - return false; - } - } - - // Handle all other options - if (("can_invite" == item) - || ("can_chat_history" == item) - || ("can_share" == item) - || ("can_pay" == item) - || ("report_abuse" == item)) - { - // Those menu items are enable only if a single avatar is selected - return is_single_select; - } - else if ("can_block" == item) - { - return (is_single_select ? LLAvatarActions::canBlock(single_id) : false); - } - else if ("can_add" == item) - { - // We can add friends if: - // - there is only 1 selected avatar (EXT-7389) - // - this avatar is not already a friend - return (is_single_select ? !LLAvatarActions::isFriend(single_id) : false); - } - else if ("can_delete" == item) - { - // We can remove friends if there are only friends among the selection - bool result = true; - for (uuid_vec_t::const_iterator id = uuids.begin(); id != uuids.end(); ++id) - { - result &= LLAvatarActions::isFriend(*id); - } - return result; - } - else if ("can_call" == item) - { - return LLAvatarActions::canCall(); - } - else if ("can_open_voice_conversation" == item) - { - return is_single_select && LLAvatarActions::canCall(); - } - else if ("can_open_voice_conversation" == item) - { - return is_single_select && LLAvatarActions::canCall(); - } - else if ("can_zoom_in" == item) - { - return is_single_select && gObjectList.findObject(single_id); - } - else if ("can_show_on_map" == item) - { - return (is_single_select ? (LLAvatarTracker::instance().isBuddyOnline(single_id) && is_agent_mappable(single_id)) || gAgent.isGodlike() : false); - } - else if ("can_offer_teleport" == item) - { - return LLAvatarActions::canOfferTeleport(uuids); - } - else if ("can_ban_member" == item) - { - return canBanSelectedMember(single_id); - } - else if (is_moderator_option) - { - // *TODO : get that out of here... - return enableModerateContextMenuItem(item); - } - - // By default, options that not explicitely disabled are enabled - return true; -} - -bool LLFloaterIMContainer::checkContextMenuItem(const LLSD& userdata) -{ - std::string item = userdata.asString(); - uuid_vec_t uuids; - getParticipantUUIDs(uuids); - - return checkContextMenuItem(item, uuids); -} - -bool LLFloaterIMContainer::checkContextMenuItem(const std::string& item, uuid_vec_t& uuids) -{ - if (uuids.size() == 1) - { - if ("is_blocked" == item) - { - return LLMuteList::getInstance()->isMuted(uuids.front(), LLMute::flagVoiceChat); - } - else if (item == "is_muted") - { - return LLMuteList::getInstance()->isMuted(uuids.front(), LLMute::flagTextChat); - } - else if ("is_allowed_text_chat" == item) - { - const LLSpeaker * speakerp = getSpeakerOfSelectedParticipant(getSpeakerMgrForSelectedParticipant()); - - if (NULL != speakerp) - { - return !speakerp->mModeratorMutedText; - } - } - } - - return false; -} - -bool LLFloaterIMContainer::visibleContextMenuItem(const LLSD& userdata) -{ - const LLConversationItem *conversation_item = getCurSelectedViewModelItem(); - if(!conversation_item) - { - return false; - } - - const std::string& item = userdata.asString(); - - if ("show_mute" == item) - { - return !isMuted(conversation_item->getUUID()); - } - else if ("show_unmute" == item) - { - return isMuted(conversation_item->getUUID()); - } - - return true; -} - -void LLFloaterIMContainer::showConversation(const LLUUID& session_id) -{ - setVisibleAndFrontmost(false); - selectConversationPair(session_id, true); - - LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(session_id); - if (session_floater) - { - session_floater->restoreFloater(); - } -} - -void LLFloaterIMContainer::clearAllFlashStates() -{ - conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin(); - for (;widget_it != mConversationsWidgets.end(); ++widget_it) - { - LLConversationViewSession* widget = dynamic_cast(widget_it->second); - if (widget) - { - widget->setFlashState(false); - } - } -} - -void LLFloaterIMContainer::selectConversation(const LLUUID& session_id) -{ - selectConversationPair(session_id, true); -} - -// Select the conversation *after* (or before if none after) the passed uuid conversation -// Used to change the selection on key hits -void LLFloaterIMContainer::selectNextConversationByID(const LLUUID& uuid) -{ - bool new_selection = false; - selectConversation(uuid); - new_selection = selectNextorPreviousConversation(true); - if (!new_selection) - { - selectNextorPreviousConversation(false); - } -} - -// Synchronous select the conversation item and the conversation floater -bool LLFloaterIMContainer::selectConversationPair(const LLUUID& session_id, bool select_widget, bool focus_floater/*=true*/) -{ - bool handled = true; - LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(session_id); - - /* widget processing */ - if (select_widget && mConversationsRoot->getSelectedCount() <= 1) - { - LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,session_id); - if (widget && widget->getParentFolder()) - { - widget->getParentFolder()->setSelection(widget, false, false); - mConversationsRoot->scrollToShowSelection(); - } - } - - /* floater processing */ - - if (NULL != session_floater && !session_floater->isDead()) - { - if (session_id != getSelectedSession()) - { - // Store the active session - setSelectedSession(session_id); - - - - if (session_floater->getHost()) - { - // Always expand the message pane if the panel is hosted by the container - collapseMessagesPane(false); - // Switch to the conversation floater that is being selected - selectFloater(session_floater); - } - else - { - showStub(true); - } - - //When in DND mode, remove stored IM notifications - //Nearby chat (Null) IMs are not stored while in DND mode, so can ignore removal - if(gAgent.isDoNotDisturb() && session_id.notNull()) - { - LLDoNotDisturbNotificationStorage::getInstance()->removeNotification(LLDoNotDisturbNotificationStorage::toastName, session_id); - } - } - - // Set the focus on the selected floater - if (!session_floater->hasFocus() && !session_floater->isMinimized()) - { - session_floater->setFocus(focus_floater); - } - } - flashConversationItemWidget(session_id,false); - return handled; -} - -void LLFloaterIMContainer::setTimeNow(const LLUUID& session_id, const LLUUID& participant_id) -{ - LLConversationItemSession* item = dynamic_cast(getSessionModel(session_id)); - if (item) - { - item->setTimeNow(participant_id); - mConversationViewModel.requestSortAll(); - mConversationsRoot->arrangeAll(); - } -} - -void LLFloaterIMContainer::setNearbyDistances() -{ - // Get the nearby chat session: that's the one with uuid nul - LLConversationItemSession* item = dynamic_cast(getSessionModel(LLUUID())); - if (item) - { - // Get the positions of the nearby avatars and their ids - std::vector positions; - uuid_vec_t avatar_ids; - LLWorld::getInstance()->getAvatars(&avatar_ids, &positions, gAgent.getPositionGlobal(), gSavedSettings.getF32("NearMeRange")); - // Get the position of the agent - const LLVector3d& me_pos = gAgent.getPositionGlobal(); - // For each nearby avatar, compute and update the distance - int avatar_count = positions.size(); - for (int i = 0; i < avatar_count; i++) - { - F64 dist = dist_vec_squared(positions[i], me_pos); - item->setDistance(avatar_ids[i],dist); - } - // Also does it for the agent itself - item->setDistance(gAgent.getID(),0.0f); - // Request resort - mConversationViewModel.requestSortAll(); - mConversationsRoot->arrangeAll(); - } -} - -LLConversationItem* LLFloaterIMContainer::addConversationListItem(const LLUUID& uuid, bool isWidgetSelected /*= false*/) -{ - bool is_nearby_chat = uuid.isNull(); - - // Stores the display name for the conversation line item - std::string display_name = is_nearby_chat ? LLTrans::getString("NearbyChatLabel") : LLIMModel::instance().getName(uuid); - - // Check if the item is not already in the list, exit (nothing to do) - // Note: this happens often, when reattaching a torn off conversation for instance - conversations_items_map::iterator item_it = mConversationsItems.find(uuid); - if (item_it != mConversationsItems.end()) - { - return item_it->second; - } - - // Create a conversation session model - LLConversationItemSession* item = NULL; - LLSpeakerMgr* speaker_manager = (is_nearby_chat ? (LLSpeakerMgr*)(LLLocalSpeakerMgr::getInstance()) : LLIMModel::getInstance()->getSpeakerManager(uuid)); - if (speaker_manager) - { - item = new LLParticipantList(speaker_manager, getRootViewModel()); - } - if (!item) - { - LL_WARNS() << "Couldn't create conversation session item : " << display_name << LL_ENDL; - return NULL; - } - item->renameItem(display_name); - item->updateName(NULL); - - mConversationsItems[uuid] = item; - - // Create a widget from it - LLConversationViewSession* widget = createConversationItemWidget(item); - mConversationsWidgets[uuid] = widget; - - // Add a new conversation widget to the root folder of the folder view - widget->addToFolder(mConversationsRoot); - widget->requestArrange(); - - LLIMModel::LLIMSession * im_sessionp = LLIMModel::getInstance()->findIMSession(uuid); - - // Create the participants widgets now - // Note: usually, we do not get an updated avatar list at that point - if (uuid.isNull() || (im_sessionp && !im_sessionp->isP2PSessionType())) - { - LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = item->getChildrenBegin(); - LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = item->getChildrenEnd(); - while (current_participant_model != end_participant_model) - { - LLConversationItem* participant_model = dynamic_cast(*current_participant_model); - LLConversationViewParticipant* participant_view = createConversationViewParticipant(participant_model); - participant_view->addToFolder(widget); - current_participant_model++; - } - } - - if (uuid.notNull() && im_sessionp->isP2PSessionType()) - { - item->fetchAvatarName(false); - } - - // Do that too for the conversation dialog - LLFloaterIMSessionTab *conversation_floater = (uuid.isNull() ? (LLFloaterIMSessionTab*)(LLFloaterReg::findTypedInstance("nearby_chat")) : (LLFloaterIMSessionTab*)(LLFloaterIMSession::findInstance(uuid))); - if (conversation_floater) - { - conversation_floater->buildConversationViewParticipant(); - } - - // set the widget to minimized mode if conversations pane is collapsed - widget->toggleCollapsedMode(mConversationsPane->isCollapsed()); - - if (isWidgetSelected || 0 == mConversationsRoot->getSelectedCount()) - { - selectConversationPair(uuid, true); - widget->requestArrange(); - - // scroll to newly added item - mConversationsRoot->scrollToShowSelection(); - } - - return item; -} - -bool LLFloaterIMContainer::removeConversationListItem(const LLUUID& uuid, bool change_focus) -{ - // Delete the widget and the associated conversation item - // Note : since the mConversationsItems is also the listener to the widget, deleting - // the widget will also delete its listener - bool is_widget_selected = false; - LLFolderViewItem* new_selection = NULL; - LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,uuid); - if (widget) - { - is_widget_selected = widget->isSelected(); - if (mConversationsRoot) - { - new_selection = mConversationsRoot->getNextFromChild(widget, false); - if (!new_selection) - { - new_selection = mConversationsRoot->getPreviousFromChild(widget, false); - } - } - - // Will destroy views and delete models that are not assigned to any views - widget->destroyView(); - } - - // Suppress the conversation items and widgets from their respective maps - mConversationsItems.erase(uuid); - mConversationsWidgets.erase(uuid); - // Clear event query (otherwise reopening session in some way can bombard session with stale data) - mConversationEventQueue.erase(uuid); - - // Don't let the focus fall IW, select and refocus on the first conversation in the list - if (change_focus) - { - setFocus(true); - if (new_selection) - { - if (mConversationsWidgets.size() == 1) - { - // If only one widget is left, it has to be the Nearby Chat. Select it directly. - selectConversationPair(LLUUID(NULL), true); - } - else - { - LLConversationItem* vmi = dynamic_cast(new_selection->getViewModelItem()); - if (vmi) - { - selectConversationPair(vmi->getUUID(), true); - } - } - } - } - return is_widget_selected; -} - -LLConversationViewSession* LLFloaterIMContainer::createConversationItemWidget(LLConversationItem* item) -{ - LLConversationViewSession::Params params; - - params.name = item->getDisplayName(); - params.root = mConversationsRoot; - params.listener = item; - params.tool_tip = params.name; - params.container = this; - - //Indentation for aligning the p2p converstation image with the nearby chat arrow - if(item->getType() == LLConversationItem::CONV_SESSION_1_ON_1) - { - params.folder_indentation = 3; - } - - return LLUICtrlFactory::create(params); -} - -LLConversationViewParticipant* LLFloaterIMContainer::createConversationViewParticipant(LLConversationItem* item) -{ - LLConversationViewParticipant::Params params; - LLRect panel_rect = mConversationsListPanel->getRect(); - - params.name = item->getDisplayName(); - params.root = mConversationsRoot; - params.listener = item; - - //24 is the the current hight of an item (itemHeight) loaded from conversation_view_participant.xml. - params.rect = LLRect (0, 24, panel_rect.getWidth(), 0); - params.tool_tip = params.name; - params.participant_id = item->getUUID(); - params.folder_indentation = 27; - - return LLUICtrlFactory::create(params); -} - -bool LLFloaterIMContainer::enableModerateContextMenuItem(const std::string& userdata, bool is_self) -{ - // only group moderators can perform actions related to this "enable callback" - if (!isGroupModerator()) - { - return false; - } - - LLSpeaker * speakerp = getSpeakerOfSelectedParticipant(getSpeakerMgrForSelectedParticipant()); - if (NULL == speakerp) - { - return false; - } - - bool voice_channel = speakerp->isInVoiceChannel(); - - if ("can_moderate_voice" == userdata) - { - return voice_channel; - } - else if (("can_mute" == userdata) && !is_self) - { - return voice_channel && !isMuted(getCurSelectedViewModelItem()->getUUID()); - } - else if ("can_unmute" == userdata) - { - return voice_channel && isMuted(getCurSelectedViewModelItem()->getUUID()); - } - - // The last invoke is used to check whether the "can_allow_text_chat" will enabled - return LLVoiceClient::getInstance()->isParticipantAvatar(getCurSelectedViewModelItem()->getUUID()) && !is_self; -} - -bool LLFloaterIMContainer::isGroupModerator() -{ - LLSpeakerMgr * speaker_manager = getSpeakerMgrForSelectedParticipant(); - if (NULL == speaker_manager) - { - LL_WARNS() << "Speaker manager is missing" << LL_ENDL; - return false; - } - - // Is session a group call/chat? - if(gAgent.isInGroup(speaker_manager->getSessionID())) - { - LLSpeaker * speaker = speaker_manager->findSpeaker(gAgentID).get(); - - // Is agent a moderator? - return speaker && speaker->mIsModerator; - } - - return false; -} - -bool LLFloaterIMContainer::haveAbilityToBan() -{ - LLSpeakerMgr * speaker_manager = getSpeakerMgrForSelectedParticipant(); - if (NULL == speaker_manager) - { - LL_WARNS() << "Speaker manager is missing" << LL_ENDL; - return false; - } - LLUUID group_uuid = speaker_manager->getSessionID(); - - return gAgent.isInGroup(group_uuid) && gAgent.hasPowerInGroup(group_uuid, GP_GROUP_BAN_ACCESS); -} - -bool LLFloaterIMContainer::canBanSelectedMember(const LLUUID& participant_uuid) -{ - LLSpeakerMgr * speaker_manager = getSpeakerMgrForSelectedParticipant(); - if (NULL == speaker_manager) - { - LL_WARNS() << "Speaker manager is missing" << LL_ENDL; - return false; - } - LLUUID group_uuid = speaker_manager->getSessionID(); - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_uuid); - if(!gdatap) - { - LL_WARNS("Groups") << "Unable to get group data for group " << group_uuid << LL_ENDL; - return false; - } - - if (gdatap->mPendingBanRequest) - { - return false; - } - - if (gdatap->isRoleMemberDataComplete()) - { - if (gdatap->mMembers.size()) - { - LLGroupMgrGroupData::member_list_t::iterator mi = gdatap->mMembers.find((participant_uuid)); - if (mi != gdatap->mMembers.end()) - { - LLGroupMemberData* member_data = (*mi).second; - // Is the member an owner? - if (member_data && member_data->isInRole(gdatap->mOwnerRole)) - { - return false; - } - } - } - } - - if( gAgent.hasPowerInGroup(group_uuid, GP_ROLE_REMOVE_MEMBER) && - gAgent.hasPowerInGroup(group_uuid, GP_GROUP_BAN_ACCESS) ) - { - return true; - } - - return false; -} - -void LLFloaterIMContainer::banSelectedMember(const LLUUID& participant_uuid) -{ - LLSpeakerMgr * speaker_manager = getSpeakerMgrForSelectedParticipant(); - if (NULL == speaker_manager) - { - LL_WARNS() << "Speaker manager is missing" << LL_ENDL; - return; - } - - LLUUID group_uuid = speaker_manager->getSessionID(); - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_uuid); - if(!gdatap) - { - LL_WARNS("Groups") << "Unable to get group data for group " << group_uuid << LL_ENDL; - return; - } - - gdatap->banMemberById(participant_uuid); - -} - -void LLFloaterIMContainer::moderateVoice(const std::string& command, const LLUUID& userID) -{ - if (!gAgent.getRegion()) return; - - if (command.compare("selected")) - { - moderateVoiceAllParticipants(command.compare("mute_all")); - } - else - { - moderateVoiceParticipant(userID, isMuted(userID)); - } -} - -bool LLFloaterIMContainer::isMuted(const LLUUID& avatar_id) -{ - const LLSpeaker * speakerp = getSpeakerOfSelectedParticipant(getSpeakerMgrForSelectedParticipant()); - return NULL == speakerp ? true : speakerp->mStatus == LLSpeaker::STATUS_MUTED; -} - -void LLFloaterIMContainer::moderateVoiceAllParticipants(bool unmute) -{ - LLIMSpeakerMgr * speaker_managerp = dynamic_cast(getSpeakerMgrForSelectedParticipant()); - - if (NULL != speaker_managerp) - { - if (!unmute) - { - LLSD payload; - payload["session_id"] = speaker_managerp->getSessionID(); - LLNotificationsUtil::add("ConfirmMuteAll", LLSD(), payload, confirmMuteAllCallback); - return; - } - - speaker_managerp->moderateVoiceAllParticipants(unmute); - } -} - -// static -void LLFloaterIMContainer::confirmMuteAllCallback(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - // if Cancel pressed - if (option == 1) - { - return; - } - - const LLSD& payload = notification["payload"]; - const LLUUID& session_id = payload["session_id"]; - - LLIMSpeakerMgr * speaker_manager = dynamic_cast ( - LLIMModel::getInstance()->getSpeakerManager(session_id)); - if (speaker_manager) - { - speaker_manager->moderateVoiceAllParticipants(false); - } - - return; -} - -void LLFloaterIMContainer::moderateVoiceParticipant(const LLUUID& avatar_id, bool unmute) -{ - LLIMSpeakerMgr * speaker_managerp = dynamic_cast(getSpeakerMgrForSelectedParticipant()); - - if (NULL != speaker_managerp) - { - speaker_managerp->moderateVoiceParticipant(avatar_id, unmute); - } -} - -LLSpeakerMgr * LLFloaterIMContainer::getSpeakerMgrForSelectedParticipant() -{ - LLFolderViewItem *selectedItem = mConversationsRoot->getCurSelectedItem(); - if (NULL == selectedItem) - { - LL_WARNS() << "Current selected item is null" << LL_ENDL; - return NULL; - } - - conversations_widgets_map::const_iterator iter = mConversationsWidgets.begin(); - conversations_widgets_map::const_iterator end = mConversationsWidgets.end(); - const LLUUID * conversation_uuidp = NULL; - while(iter != end) - { - if (iter->second == selectedItem || iter->second == selectedItem->getParentFolder()) - { - conversation_uuidp = &iter->first; - break; - } - ++iter; - } - if (NULL == conversation_uuidp) - { - LL_WARNS() << "Cannot find conversation item widget" << LL_ENDL; - return NULL; - } - - return conversation_uuidp->isNull() ? (LLSpeakerMgr *)LLLocalSpeakerMgr::getInstance() - : LLIMModel::getInstance()->getSpeakerManager(*conversation_uuidp); -} - -LLSpeaker * LLFloaterIMContainer::getSpeakerOfSelectedParticipant(LLSpeakerMgr * speaker_managerp) -{ - if (NULL == speaker_managerp) - { - LL_WARNS() << "Speaker manager is missing" << LL_ENDL; - return NULL; - } - - const LLConversationItem * participant_itemp = getCurSelectedViewModelItem(); - if (NULL == participant_itemp) - { - LL_WARNS() << "Cannot evaluate current selected view model item" << LL_ENDL; - return NULL; - } - - return speaker_managerp->findSpeaker(participant_itemp->getUUID()); -} - -void LLFloaterIMContainer::toggleAllowTextChat(const LLUUID& participant_uuid) -{ - LLIMSpeakerMgr * speaker_managerp = dynamic_cast(getSpeakerMgrForSelectedParticipant()); - if (NULL != speaker_managerp) - { - speaker_managerp->toggleAllowTextChat(participant_uuid); - } -} - -void LLFloaterIMContainer::openNearbyChat() -{ - // If there's only one conversation in the container and that conversation is the nearby chat - //(which it should be...), open it so to make the list of participants visible. This happens to be the most common case when opening the Chat floater. - if((mConversationsItems.size() == 1)&&(!mConversationsPane->isCollapsed())) - { - LLConversationViewSession* nearby_chat = dynamic_cast(get_ptr_in_map(mConversationsWidgets,LLUUID())); - if (nearby_chat) - { - reSelectConversation(); - nearby_chat->setOpen(true); - } - } -} - -void LLFloaterIMContainer::reSelectConversation() -{ - LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::getConversation(mSelectedSession); - if (session_floater->getHost()) - { - selectFloater(session_floater); - } -} - -void LLFloaterIMContainer::updateSpeakBtnState() -{ - mSpeakBtn->setToggleState(LLVoiceClient::getInstance()->getUserPTTState()); - mSpeakBtn->setEnabled(LLAgent::isActionAllowed("speak")); -} - -bool LLFloaterIMContainer::isConversationLoggingAllowed() -{ - return gSavedPerAccountSettings.getS32("KeepConversationLogTranscripts") > 0; -} - -void LLFloaterIMContainer::flashConversationItemWidget(const LLUUID& session_id, bool is_flashes) -{ - //Finds the conversation line item to flash using the session_id - LLConversationViewSession * widget = dynamic_cast(get_ptr_in_map(mConversationsWidgets,session_id)); - - if (widget) - { - widget->setFlashState(is_flashes); - } -} - -void LLFloaterIMContainer::highlightConversationItemWidget(const LLUUID& session_id, bool is_highlighted) -{ - //Finds the conversation line item to highlight using the session_id - LLConversationViewSession * widget = dynamic_cast(get_ptr_in_map(mConversationsWidgets,session_id)); - - if (widget) - { - widget->setHighlightState(is_highlighted); - } -} - -bool LLFloaterIMContainer::isScrolledOutOfSight(LLConversationViewSession* conversation_item_widget) -{ - llassert(conversation_item_widget != NULL); - - // make sure the widget is actually in the right spot first - mConversationsRoot->arrange(NULL, NULL); - - // check whether the widget is in the visible portion of the scroll container - LLRect widget_rect; - conversation_item_widget->localRectToOtherView(conversation_item_widget->getLocalRect(), &widget_rect, mConversationsRoot); - return !mConversationsRoot->getVisibleRect().overlaps(widget_rect); -} - -bool LLFloaterIMContainer::handleKeyHere(KEY key, MASK mask ) -{ - bool handled = false; - - if(mask == MASK_ALT) - { - if (KEY_RETURN == key ) - { - expandConversation(); - handled = true; - } - - if ((KEY_DOWN == key ) || (KEY_RIGHT == key)) - { - selectNextorPreviousConversation(true); - handled = true; - } - if ((KEY_UP == key) || (KEY_LEFT == key)) - { - selectNextorPreviousConversation(false); - handled = true; - } - } - return handled; -} - -bool LLFloaterIMContainer::selectAdjacentConversation(bool focus_selected) -{ - bool selectedAdjacentConversation = selectNextorPreviousConversation(true, focus_selected); - - if(!selectedAdjacentConversation) - { - selectedAdjacentConversation = selectNextorPreviousConversation(false, focus_selected); - } - - return selectedAdjacentConversation; -} - -bool LLFloaterIMContainer::selectNextorPreviousConversation(bool select_next, bool focus_selected) -{ - if (mConversationsWidgets.size() > 1) - { - LLFolderViewItem* new_selection = NULL; - LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,getSelectedSession()); - if (widget) - { - if(select_next) - { - new_selection = mConversationsRoot->getNextFromChild(widget, false); - } - else - { - new_selection = mConversationsRoot->getPreviousFromChild(widget, false); - } - if (new_selection) - { - LLConversationItem* vmi = dynamic_cast(new_selection->getViewModelItem()); - if (vmi) - { - selectConversationPair(vmi->getUUID(), true, focus_selected); - return true; - } - } - } - } - return false; -} - -void LLFloaterIMContainer::expandConversation() -{ - if(!mConversationsPane->isCollapsed()) - { - LLConversationViewSession* widget = dynamic_cast(get_ptr_in_map(mConversationsWidgets,getSelectedSession())); - if (widget) - { - widget->setOpen(!widget->isOpen()); - } - } -} -bool LLFloaterIMContainer::isParticipantListExpanded() -{ - bool is_expanded = false; - if(!mConversationsPane->isCollapsed()) - { - LLConversationViewSession* widget = dynamic_cast(get_ptr_in_map(mConversationsWidgets,getSelectedSession())); - if (widget) - { - is_expanded = widget->isOpen(); - } - } - return is_expanded; -} - -// By default, if torn off session is currently frontmost, LLFloater::isFrontmost() will return false, which can lead to some bugs -// So LLFloater::isFrontmost() is overriden here to check both selected session and the IM floater itself -// Exclude "Nearby Chat" session from the check, as "Nearby Chat" window and "Conversations" floater can be brought -// to front independently -/*virtual*/ -bool LLFloaterIMContainer::isFrontmost() -{ - LLFloaterIMSessionTab* selected_session = LLFloaterIMSessionTab::getConversation(mSelectedSession); - LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); - return (selected_session && selected_session->isFrontmost() && (selected_session != nearby_chat)) - || LLFloater::isFrontmost(); -} - -// For conversations, closeFloater() (linked to Ctrl-W) does not actually close the floater but the active conversation. -// This is intentional so it doesn't confuse the user. onClickCloseBtn() closes the whole floater. -void LLFloaterIMContainer::onClickCloseBtn(bool app_quitting/* = false*/) -{ - gSavedPerAccountSettings.setS32("ConversationsListPaneWidth", mConversationsPane->getRect().getWidth()); - LLMultiFloater::closeFloater(app_quitting); -} - -void LLFloaterIMContainer::closeHostedFloater() -{ - onClickCloseBtn(); -} - -void LLFloaterIMContainer::closeAllConversations() -{ - std::vector ids; - for (conversations_items_map::iterator it_session = mConversationsItems.begin(); it_session != mConversationsItems.end(); it_session++) - { - LLUUID session_id = it_session->first; - if (session_id != LLUUID()) - { - ids.push_back(session_id); - } - } - - for (std::vector::const_iterator it = ids.begin(); it != ids.end(); ++it) - { - LLFloaterIMSession *conversationFloater = LLFloaterIMSession::findInstance(*it); - LLFloater::onClickClose(conversationFloater); - } -} - -void LLFloaterIMContainer::closeSelectedConversations(const uuid_vec_t& ids) -{ - for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it) - { - //We don't need to close Nearby chat, so skip it - if (*it != LLUUID()) - { - LLFloaterIMSession *conversationFloater = LLFloaterIMSession::findInstance(*it); - if(conversationFloater) - { - LLFloater::onClickClose(conversationFloater); - } - } - } -} -void LLFloaterIMContainer::closeFloater(bool app_quitting/* = false*/) -{ - if(app_quitting) - { - closeAllConversations(); - onClickCloseBtn(app_quitting); - } - else - { - // Check for currently active session - LLUUID session_id = getSelectedSession(); - // If current session is Nearby Chat or there is only one session remaining, close the floater - if (mConversationsItems.size() == 1 || session_id == LLUUID() || app_quitting) - { - onClickCloseBtn(); - } - else - { - // Otherwise, close current conversation - LLFloaterIMSessionTab* active_conversation = LLFloaterIMSessionTab::getConversation(session_id); - if (active_conversation) - { - active_conversation->closeFloater(); - } - } - } -} - -void LLFloaterIMContainer::handleReshape(const LLRect& rect, bool by_user) -{ - LLMultiFloater::handleReshape(rect, by_user); - storeRectControl(); -} - -// EOF +/** + * @file llfloaterimcontainer.cpp + * @brief Multifloater containing active IM sessions in separate tab container tabs + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterimsession.h" +#include "llfloaterimcontainer.h" + +#include "llfloaterreg.h" +#include "lllayoutstack.h" +#include "llfloaterimnearbychat.h" + +#include "llagent.h" +#include "llavataractions.h" +#include "llavatariconctrl.h" +#include "llavatarnamecache.h" +#include "llcallbacklist.h" +#include "lldonotdisturbnotificationstorage.h" +#include "llgroupactions.h" +#include "llgroupiconctrl.h" +#include "llflashtimer.h" +#include "llfloateravatarpicker.h" +#include "llfloaterpreference.h" +#include "llfloaterreporter.h" +#include "llimview.h" +#include "llnotificationsutil.h" +#include "lltoolbarview.h" +#include "lltransientfloatermgr.h" +#include "llviewercontrol.h" +#include "llconversationview.h" +#include "llcallbacklist.h" +#include "llworld.h" +#include "llsdserialize.h" +#include "llviewermenu.h" // is_agent_mappable +#include "llviewerobjectlist.h" + + +const S32 EVENTS_PER_IDLE_LOOP_CURRENT_SESSION = 80; +const S32 EVENTS_PER_IDLE_LOOP_BACKGROUND = 40; +const F32 EVENTS_PER_IDLE_LOOP_MIN_PERCENTAGE = 0.01f; // process a minimum of 1% of total events per frame + +// +// LLFloaterIMContainer +// +LLFloaterIMContainer::LLFloaterIMContainer(const LLSD& seed, const Params& params /*= getDefaultParams()*/) +: LLMultiFloater(seed, params), + mExpandCollapseBtn(NULL), + mConversationsRoot(NULL), + mConversationsEventStream("ConversationsEvents"), + mInitialized(false), + mIsFirstLaunch(true), + mConversationEventQueue() +{ + mEnableCallbackRegistrar.add("IMFloaterContainer.Check", boost::bind(&LLFloaterIMContainer::isActionChecked, this, _2)); + mCommitCallbackRegistrar.add("IMFloaterContainer.Action", boost::bind(&LLFloaterIMContainer::onCustomAction, this, _2)); + + mEnableCallbackRegistrar.add("Avatar.CheckItem", boost::bind(&LLFloaterIMContainer::checkContextMenuItem, this, _2)); + mEnableCallbackRegistrar.add("Avatar.EnableItem", boost::bind(&LLFloaterIMContainer::enableContextMenuItem, this, _2)); + mEnableCallbackRegistrar.add("Avatar.VisibleItem", boost::bind(&LLFloaterIMContainer::visibleContextMenuItem, this, _2)); + mCommitCallbackRegistrar.add("Avatar.DoToSelected", boost::bind(&LLFloaterIMContainer::doToSelected, this, _2)); + + mCommitCallbackRegistrar.add("Group.DoToSelected", boost::bind(&LLFloaterIMContainer::doToSelectedGroup, this, _2)); + + // Firstly add our self to IMSession observers, so we catch session events + LLIMMgr::getInstance()->addSessionObserver(this); + + mAutoResize = false; + LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::IM, this); +} + +LLFloaterIMContainer::~LLFloaterIMContainer() +{ + mConversationsEventStream.stopListening("ConversationsRefresh"); + gIdleCallbacks.deleteFunction(idle, this); + mNewMessageConnection.disconnect(); + LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::IM, this); + + if (mMicroChangedSignal.connected()) + { + mMicroChangedSignal.disconnect(); + } + + gSavedPerAccountSettings.setBOOL("ConversationsListPaneCollapsed", mConversationsPane->isCollapsed()); + gSavedPerAccountSettings.setBOOL("ConversationsMessagePaneCollapsed", mMessagesPane->isCollapsed()); + gSavedPerAccountSettings.setBOOL("ConversationsParticipantListCollapsed", !isParticipantListExpanded()); + + if (LLIMMgr::instanceExists()) + { + LLIMMgr::getInstance()->removeSessionObserver(this); + } +} + +void LLFloaterIMContainer::sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id, bool has_offline_msg) +{ + addConversationListItem(session_id); + LLFloaterIMSessionTab::addToHost(session_id); +} + +void LLFloaterIMContainer::sessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id) +{ + if(!isInVisibleChain()) + { + setVisibleAndFrontmost(false); + } + selectConversationPair(session_id, true); + collapseMessagesPane(false); +} + +void LLFloaterIMContainer::sessionVoiceOrIMStarted(const LLUUID& session_id) +{ + addConversationListItem(session_id); + LLFloaterIMSessionTab::addToHost(session_id); +} + +void LLFloaterIMContainer::sessionIDUpdated(const LLUUID& old_session_id, const LLUUID& new_session_id) +{ + // The general strategy when a session id is modified is to delete all related objects and create them anew. + + // Note however that the LLFloaterIMSession has its session id updated through a call to sessionInitReplyReceived() + // and do not need to be deleted and recreated (trying this creates loads of problems). We do need however to suppress + // its related mSessions record as it's indexed with the wrong id. + // Grabbing the updated LLFloaterIMSession and readding it in mSessions will eventually be done by addConversationListItem(). + mSessions.erase(old_session_id); + + // Delete the model and participants related to the old session + bool change_focus = removeConversationListItem(old_session_id); + + // Create a new conversation with the new id + addConversationListItem(new_session_id, change_focus); + LLFloaterIMSessionTab::addToHost(new_session_id); +} + + +LLConversationItem* LLFloaterIMContainer::getSessionModel(const LLUUID& session_id) +{ + conversations_items_map::iterator iter = mConversationsItems.find(session_id); + if (iter == mConversationsItems.end()) + { + return NULL; + } + else + { + return iter->second.get(); + } +} + +void LLFloaterIMContainer::sessionRemoved(const LLUUID& session_id) +{ + removeConversationListItem(session_id); +} + +// static +void LLFloaterIMContainer::onCurrentChannelChanged(const LLUUID& session_id) +{ + if (session_id != LLUUID::null) + { + LLFloaterIMContainer::getInstance()->showConversation(session_id); + } +} + +bool LLFloaterIMContainer::postBuild() +{ + mOrigMinWidth = getMinWidth(); + mOrigMinHeight = getMinHeight(); + + mNewMessageConnection = LLIMModel::instance().mNewMsgSignal.connect(boost::bind(&LLFloaterIMContainer::onNewMessageReceived, this, _1)); + // Do not call base postBuild to not connect to mCloseSignal to not close all floaters via Close button + // mTabContainer will be initialized in LLMultiFloater::addChild() + + setTabContainer(getChild("im_box_tab_container")); + mStubPanel = getChild("stub_panel"); + mStubTextBox = getChild("stub_textbox"); + mStubTextBox->setURLClickedCallback(boost::bind(&LLFloaterIMContainer::returnFloaterToHost, this)); + + mConversationsStack = getChild("conversations_stack"); + mConversationsPane = getChild("conversations_layout_panel"); + mMessagesPane = getChild("messages_layout_panel"); + + mConversationsListPanel = getChild("conversations_list_panel"); + + // Open IM session with selected participant on double click event + mConversationsListPanel->setDoubleClickCallback(boost::bind(&LLFloaterIMContainer::doToSelected, this, LLSD("im"))); + + // The resize limits for LLFloaterIMContainer should be updated, based on current values of width of conversation and message panels + mConversationsPane->getResizeBar()->setResizeListener(boost::bind(&LLFloaterIMContainer::assignResizeLimits, this)); + + // Create the root model and view for all conversation sessions + LLConversationItem* base_item = new LLConversationItem(getRootViewModel()); + + LLFolderView::Params p(LLUICtrlFactory::getDefaultParams()); + p.name = getName(); + p.title = getLabel(); + p.rect = LLRect(0, 0, getRect().getWidth(), 0); + p.parent_panel = mConversationsListPanel; + p.tool_tip = p.name; + p.listener = base_item; + p.view_model = &mConversationViewModel; + p.root = NULL; + p.use_ellipses = true; + p.options_menu = "menu_conversation.xml"; + mConversationsRoot = LLUICtrlFactory::create(p); + mConversationsRoot->setCallbackRegistrar(&mCommitCallbackRegistrar); + mConversationsRoot->setEnableRegistrar(&mEnableCallbackRegistrar); + + // Add listener to conversation model events + mConversationsEventStream.listen("ConversationsRefresh", boost::bind(&LLFloaterIMContainer::onConversationModelEvent, this, _1)); + + // a scroller for folder view + LLRect scroller_view_rect = mConversationsListPanel->getRect(); + scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom); + scroller_view_rect.mBottom += getChild("conversations_pane_buttons_stack")->getRect().getHeight(); + LLScrollContainer::Params scroller_params(LLUICtrlFactory::getDefaultParams()); + scroller_params.rect(scroller_view_rect); + + LLScrollContainer* scroller = LLUICtrlFactory::create(scroller_params); + scroller->setFollowsAll(); + mConversationsListPanel->addChild(scroller); + scroller->addChild(mConversationsRoot); + mConversationsRoot->setScrollContainer(scroller); + mConversationsRoot->setFollowsAll(); + mConversationsRoot->addChild(mConversationsRoot->mStatusTextBox); + + addConversationListItem(LLUUID()); // manually add nearby chat + + mExpandCollapseBtn = getChild("expand_collapse_btn"); + mExpandCollapseBtn->setClickedCallback(boost::bind(&LLFloaterIMContainer::onExpandCollapseButtonClicked, this)); + mStubCollapseBtn = getChild("stub_collapse_btn"); + mStubCollapseBtn->setClickedCallback(boost::bind(&LLFloaterIMContainer::onStubCollapseButtonClicked, this)); + mSpeakBtn = getChild("speak_btn"); + + mSpeakBtn->setMouseDownCallback(boost::bind(&LLFloaterIMContainer::onSpeakButtonPressed, this)); + mSpeakBtn->setMouseUpCallback(boost::bind(&LLFloaterIMContainer::onSpeakButtonReleased, this)); + + childSetAction("add_btn", boost::bind(&LLFloaterIMContainer::onAddButtonClicked, this)); + + collapseMessagesPane(gSavedPerAccountSettings.getBOOL("ConversationsMessagePaneCollapsed")); + collapseConversationsPane(gSavedPerAccountSettings.getBOOL("ConversationsListPaneCollapsed"), false); + LLAvatarNameCache::getInstance()->addUseDisplayNamesCallback(boost::bind(&LLFloaterIMSessionTab::processChatHistoryStyleUpdate, false)); + mMicroChangedSignal = LLVoiceClient::getInstance()->MicroChangedCallback(boost::bind(&LLFloaterIMContainer::updateSpeakBtnState, this)); + + if (! mMessagesPane->isCollapsed() && ! mConversationsPane->isCollapsed()) + { + S32 conversations_panel_width = gSavedPerAccountSettings.getS32("ConversationsListPaneWidth"); + LLRect conversations_panel_rect = mConversationsPane->getRect(); + conversations_panel_rect.mRight = conversations_panel_rect.mLeft + conversations_panel_width; + mConversationsPane->handleReshape(conversations_panel_rect, true); + } + + // Init the sort order now that the root had been created + setSortOrder(LLConversationSort(gSavedSettings.getU32("ConversationSortOrder"))); + + //We should expand nearby chat participants list for the new user + if(gAgent.isFirstLogin() || !gSavedPerAccountSettings.getBOOL("ConversationsParticipantListCollapsed")) + { + expandConversation(); + } + // Keep the xml set title around for when we have to overwrite it + mGeneralTitle = getTitle(); + + mInitialized = true; + + mIsFirstOpen = true; + + // Add callbacks: + // We'll take care of view updates on idle + gIdleCallbacks.addFunction(idle, this); + // When display name option change, we need to reload all participant names + LLAvatarNameCache::getInstance()->addUseDisplayNamesCallback(boost::bind(&LLFloaterIMContainer::processParticipantsStyleUpdate, this)); + + mParticipantRefreshTimer.setTimerExpirySec(0); + mParticipantRefreshTimer.start(); + + return true; +} + +void LLFloaterIMContainer::onOpen(const LLSD& key) +{ + LLMultiFloater::onOpen(key); + reSelectConversation(); + assignResizeLimits(); + + LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::getConversation(mSelectedSession); + session_floater->onOpen(key); +} + +// virtual +void LLFloaterIMContainer::addFloater(LLFloater* floaterp, + bool select_added_floater, + LLTabContainer::eInsertionPoint insertion_point) +{ + if(!floaterp) return; + + // already here + if (floaterp->getHost() == this) + { + openFloater(floaterp->getKey()); + return; + } + + LLUUID session_id = floaterp->getKey(); + + // Add the floater + LLMultiFloater::addFloater(floaterp, select_added_floater, insertion_point); + + + + LLIconCtrl* icon = 0; + bool is_in_group = gAgent.isInGroup(session_id, true); + LLUUID icon_id; + + if (is_in_group) + { + LLGroupIconCtrl::Params icon_params; + icon_params.group_id = session_id; + icon = LLUICtrlFactory::instance().create(icon_params); + icon_id = session_id; + + mSessions[session_id] = floaterp; + floaterp->mCloseSignal.connect(boost::bind(&LLFloaterIMContainer::onCloseFloater, this, session_id)); + } + else + { LLUUID avatar_id = session_id.notNull()? + LLIMModel::getInstance()->getOtherParticipantID(session_id) : LLUUID(); + + LLAvatarIconCtrl::Params icon_params; + icon_params.avatar_id = avatar_id; + icon = LLUICtrlFactory::instance().create(icon_params); + icon_id = avatar_id; + + mSessions[session_id] = floaterp; + floaterp->mCloseSignal.connect(boost::bind(&LLFloaterIMContainer::onCloseFloater, this, session_id)); + } + + LLFloaterIMSessionTab* floater = LLFloaterIMSessionTab::getConversation(session_id); + if (floater) + { + floater->updateChatIcon(icon_id); + } + + // forced resize of the floater + LLRect wrapper_rect = this->mTabContainer->getLocalRect(); + floaterp->setRect(wrapper_rect); + + mTabContainer->setTabImage(floaterp, icon); +} + + +void LLFloaterIMContainer::onCloseFloater(LLUUID& id) +{ + mSessions.erase(id); + setFocus(true); +} + +void LLFloaterIMContainer::onNewMessageReceived(const LLSD& data) +{ + LLUUID session_id = data["session_id"].asUUID(); + LLFloater* floaterp = get_ptr_in_map(mSessions, session_id); + LLFloater* current_floater = LLMultiFloater::getActiveFloater(); + + if(floaterp && current_floater && floaterp != current_floater) + { + if(LLMultiFloater::isFloaterFlashing(floaterp)) + LLMultiFloater::setFloaterFlashing(floaterp, false); + LLMultiFloater::setFloaterFlashing(floaterp, true); + } +} + +void LLFloaterIMContainer::onStubCollapseButtonClicked() +{ + collapseMessagesPane(true); +} + +void LLFloaterIMContainer::onSpeakButtonPressed() +{ + LLVoiceClient::getInstance()->inputUserControlState(true); + updateSpeakBtnState(); +} + +void LLFloaterIMContainer::onSpeakButtonReleased() +{ + LLVoiceClient::getInstance()->inputUserControlState(false); + updateSpeakBtnState(); +} + +void LLFloaterIMContainer::onExpandCollapseButtonClicked() +{ + if (mConversationsPane->isCollapsed() && mMessagesPane->isCollapsed() + && gSavedPerAccountSettings.getBOOL("ConversationsExpandMessagePaneFirst")) + { + // Expand the messages pane from ultra minimized state + // if it was collapsed last in order. + collapseMessagesPane(false); + } + else + { + collapseConversationsPane(!mConversationsPane->isCollapsed()); + } + reSelectConversation(); +} + +LLFloaterIMContainer* LLFloaterIMContainer::findInstance() +{ + return LLFloaterReg::findTypedInstance("im_container"); +} + +LLFloaterIMContainer* LLFloaterIMContainer::getInstance() +{ + return LLFloaterReg::getTypedInstance("im_container"); +} + +// Update all participants in the conversation lists +void LLFloaterIMContainer::processParticipantsStyleUpdate() +{ + // On each session in mConversationsItems + for (conversations_items_map::iterator it_session = mConversationsItems.begin(); it_session != mConversationsItems.end(); it_session++) + { + // Get the current session descriptors + LLConversationItem* session_model = it_session->second; + // Iterate through each model participant child + LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = session_model->getChildrenBegin(); + LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = session_model->getChildrenEnd(); + while (current_participant_model != end_participant_model) + { + LLConversationItemParticipant* participant_model = dynamic_cast(*current_participant_model); + if (participant_model) + { + // Get the avatar name for this participant id from the cache and update the model + participant_model->updateName(); + } + // Next participant + current_participant_model++; + } + } +} + +// static +void LLFloaterIMContainer::idle(void* user_data) +{ + LLFloaterIMContainer* self = static_cast(user_data); + + self->idleProcessEvents(); + + if (!self->getVisible() || self->isMinimized()) + { + return; + } + self->idleUpdate(); +} + +void LLFloaterIMContainer::idleUpdate() +{ + if (mTabContainer->getTabCount() == 0) + { + // Do not close the container when every conversation is torn off because the user + // still needs the conversation list. Simply collapse the message pane in that case. + collapseMessagesPane(true); + } + + U32 sort_order = mConversationViewModel.getSorter().getSortOrderParticipants(); + + if (mParticipantRefreshTimer.hasExpired()) + { + const LLConversationItem *current_session = getCurSelectedViewModelItem(); + if (current_session) + { + if (current_session->getType() == LLConversationItem::CONV_SESSION_GROUP) + { + // Update moderator options visibility + LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = current_session->getChildrenBegin(); + LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = current_session->getChildrenEnd(); + bool is_moderator = isGroupModerator(); + bool can_ban = haveAbilityToBan(); + while (current_participant_model != end_participant_model) + { + LLConversationItemParticipant* participant_model = dynamic_cast(*current_participant_model); + if (participant_model) + { + participant_model->setModeratorOptionsVisible(is_moderator); + participant_model->setGroupBanVisible(can_ban && participant_model->getUUID() != gAgentID); + } + + current_participant_model++; + } + } + + // Update floater's title as required by the currently selected session or use the default title + LLFloaterIMSession * conversation_floaterp = LLFloaterIMSession::findInstance(current_session->getUUID()); + setTitle(conversation_floaterp && conversation_floaterp->needsTitleOverwrite() ? conversation_floaterp->getTitle() : mGeneralTitle); + } + + mParticipantRefreshTimer.setTimerExpirySec(1.0f); + } + + // Update the distance to agent in the nearby chat session if required + // Note: it makes no sense of course to update the distance in other session + if (sort_order == LLConversationFilter::SO_DISTANCE) + { + // almost real-time updates + setNearbyDistances(); //calls arrange all + } + mConversationsRoot->update(); //arranges, resizes, heavy + + // "Manually" resize of mConversationsPane: same as temporarity cancellation of the flag "auto_resize=false" for it + if (!mConversationsPane->isCollapsed() && mMessagesPane->isCollapsed()) + { + LLRect stack_rect = mConversationsStack->getRect(); + mConversationsPane->reshape(stack_rect.getWidth(), stack_rect.getHeight(), true); + } +} + +void LLFloaterIMContainer::idleProcessEvents() +{ + LLUUID current_session_id = getSelectedSession(); + conversations_items_deque::iterator iter = mConversationEventQueue.begin(); + conversations_items_deque::iterator end = mConversationEventQueue.end(); + while (iter != end) + { + std::deque &events = iter->second; + if (!events.empty()) + { + S32 events_to_handle; + S32 query_size = (S32)events.size(); + if (current_session_id == iter->first) + { + events_to_handle = EVENTS_PER_IDLE_LOOP_CURRENT_SESSION; + } + else + { + events_to_handle = EVENTS_PER_IDLE_LOOP_BACKGROUND; + } + + if (events_to_handle <= query_size) + { + // Some groups can be very large and can generate huge amount of updates, scale processing up to keep up + events_to_handle = llmax(events_to_handle, (S32)(query_size * EVENTS_PER_IDLE_LOOP_MIN_PERCENTAGE)); + } + else + { + events_to_handle = query_size; + } + + for (S32 i = 0; i < events_to_handle; i++) + { + handleConversationModelEvent(events.back()); + events.pop_back(); + } + } + iter++; + } +} + +bool LLFloaterIMContainer::onConversationModelEvent(const LLSD& event) +{ + LLUUID id = event.get("session_uuid").asUUID(); + mConversationEventQueue[id].push_front(event); + return true; +} + + +void LLFloaterIMContainer::handleConversationModelEvent(const LLSD& event) +{ + + // Note: In conversations, the model is not responsible for creating the view, which is a good thing. This means that + // the model could change substantially and the view could echo only a portion of this model (though currently the + // conversation view does echo the conversation model 1 to 1). + // Consequently, the participant views need to be created either by the session view or by the container panel. + // For the moment, we create them here, at the container level, to conform to the pattern implemented in llinventorypanel.cpp + // (see LLInventoryPanel::buildNewViews()). + + std::string type = event.get("type").asString(); + LLUUID session_id = event.get("session_uuid").asUUID(); + LLUUID participant_id = event.get("participant_uuid").asUUID(); + + LLConversationViewSession* session_view = dynamic_cast(get_ptr_in_map(mConversationsWidgets,session_id)); + if (!session_view) + { + // We skip events that are not associated with a session + return; + } + LLConversationViewParticipant* participant_view = session_view->findParticipant(participant_id); + LLFloaterIMSessionTab *conversation_floater = (session_id.isNull() ? + (LLFloaterIMSessionTab*)(LLFloaterReg::findTypedInstance("nearby_chat")) + : (LLFloaterIMSessionTab*)(LLFloaterIMSession::findInstance(session_id))); + + if (type == "remove_participant") + { + // Remove a participant view from the hierarchical conversation list + if (participant_view) + { + session_view->extractItem(participant_view); + delete participant_view; + session_view->refresh(); + mConversationsRoot->arrangeAll(); + } + // Remove a participant view from the conversation floater + if (conversation_floater) + { + conversation_floater->removeConversationViewParticipant(participant_id); + } + } + else if (type == "add_participant") + { + LLConversationItem* item = getSessionModel(session_id); + LLConversationItemSession* session_model = dynamic_cast(item); + LLConversationItemParticipant* participant_model = (session_model ? session_model->findParticipant(participant_id) : NULL); + LLIMModel::LLIMSession * im_sessionp = LLIMModel::getInstance()->findIMSession(session_id); + if (!participant_view && session_model && participant_model) + { + if (session_id.isNull() || (im_sessionp && !im_sessionp->isP2PSessionType())) + { + participant_view = createConversationViewParticipant(participant_model); + participant_view->addToFolder(session_view); + participant_view->setVisible(true); + } + } + // Add a participant view to the conversation floater + if (conversation_floater && participant_model) + { + bool skip_updating = im_sessionp && im_sessionp->isGroupChat(); + conversation_floater->addConversationViewParticipant(participant_model, !skip_updating); + } + } + else if (type == "update_participant") + { + // Update the participant view in the hierarchical conversation list + if (participant_view) + { + participant_view->refresh(); + } + // Update the participant view in the conversation floater + if (conversation_floater) + { + conversation_floater->updateConversationViewParticipant(participant_id); + } + } + else if (type == "update_session") + { + session_view->refresh(); + } + + mConversationViewModel.requestSortAll(); + mConversationsRoot->arrangeAll(); +} + +void LLFloaterIMContainer::draw() +{ + LLFloater::draw(); +} + +void LLFloaterIMContainer::tabClose() +{ + if (mTabContainer->getTabCount() == 0) + { + // Do not close the container when every conversation is torn off because the user + // still needs the conversation list. Simply collapse the message pane in that case. + collapseMessagesPane(true); + } +} + +//Shows/hides the stub panel when a conversation floater is torn off +void LLFloaterIMContainer::showStub(bool stub_is_visible) +{ + S32 tabCount = 0; + LLPanel * tabPanel = NULL; + + if(stub_is_visible) + { + tabCount = mTabContainer->getTabCount(); + + //Hide all tabs even stub + for(S32 i = 0; i < tabCount; ++i) + { + tabPanel = mTabContainer->getPanelByIndex(i); + + if(tabPanel) + { + tabPanel->setVisible(false); + } + } + + //Set the index to the stub panel since we will be showing the stub + mTabContainer->setCurrentPanelIndex(0); + } + + //Now show/hide the stub + mStubPanel->setVisible(stub_is_visible); +} + +// listener for click on mStubTextBox2 +void LLFloaterIMContainer::returnFloaterToHost() +{ + LLUUID session_id = this->getSelectedSession(); + LLFloaterIMSessionTab* floater = LLFloaterIMSessionTab::getConversation(session_id); + floater->onTearOffClicked(); +} + +void LLFloaterIMContainer::setMinimized(bool b) +{ + bool was_minimized = isMinimized(); + LLMultiFloater::setMinimized(b); + + //Switching from minimized to un-minimized + if(was_minimized && !b) + { + gToolBarView->flashCommand(LLCommandId("chat"), false); + LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(mSelectedSession); + + if(session_floater && !session_floater->isTornOff()) + { + //When in DND mode, remove stored IM notifications + //Nearby chat (Null) IMs are not stored while in DND mode, so can ignore removal + if(gAgent.isDoNotDisturb() && mSelectedSession.notNull()) + { + LLDoNotDisturbNotificationStorage::getInstance()->removeNotification(LLDoNotDisturbNotificationStorage::toastName, mSelectedSession); + } + } + } +} + +void LLFloaterIMContainer::setVisible(bool visible) +{ + LLFloaterIMNearbyChat* nearby_chat; + if (visible) + { + // Make sure we have the Nearby Chat present when showing the conversation container + nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); + if ((nearby_chat == NULL) || mIsFirstOpen) + { + mIsFirstOpen = false; + // If not found, force the creation of the nearby chat conversation panel + // *TODO: find a way to move this to XML as a default panel or something like that + LLSD name("nearby_chat"); + LLFloaterReg::toggleInstanceOrBringToFront(name); + selectConversationPair(LLUUID(NULL), false, false); + } + + flashConversationItemWidget(mSelectedSession,false); + + LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(mSelectedSession); + if(session_floater && !session_floater->isMinimized()) + { + //When in DND mode, remove stored IM notifications + //Nearby chat (Null) IMs are not stored while in DND mode, so can ignore removal + if(gAgent.isDoNotDisturb() && mSelectedSession.notNull()) + { + LLDoNotDisturbNotificationStorage::getInstance()->removeNotification(LLDoNotDisturbNotificationStorage::toastName, mSelectedSession); + } + } + } + + nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); + if (nearby_chat) + { + LLFloaterIMSessionTab::addToHost(LLUUID()); + } + + if (!LLFloater::isQuitRequested()) + { + // We need to show/hide all the associated conversations that have been torn off + // (and therefore, are not longer managed by the multifloater), + // so that they show/hide with the conversations manager. + conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin(); + for (; widget_it != mConversationsWidgets.end(); ++widget_it) + { + LLConversationViewSession* widget = dynamic_cast(widget_it->second); + if (widget) + { + LLFloater* session_floater = widget->getSessionFloater(); + if (session_floater != nearby_chat) + { + widget->setVisibleIfDetached(visible); + } + } + } + } + + // Now, do the normal multifloater show/hide + LLMultiFloater::setVisible(visible); +} + +void LLFloaterIMContainer::getDetachedConversationFloaters(floater_list_t& floaters) +{ + LLFloaterIMNearbyChat *nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); + + for (const auto& [key, fvi] : mConversationsWidgets) + { + LLConversationViewSession* widget = dynamic_cast(fvi); + if (widget) + { + LLFloater* session_floater = widget->getSessionFloater(); + + // Exclude nearby chat from output, as it should be handled separately + if (session_floater && session_floater->isDetachedAndNotMinimized() + && session_floater != nearby_chat) + { + floaters.push_back(session_floater); + } + } + } +} + +void LLFloaterIMContainer::setVisibleAndFrontmost(bool take_focus, const LLSD& key) +{ + LLMultiFloater::setVisibleAndFrontmost(take_focus, key); + // Do not select "Nearby Chat" conversation, since it will bring its window to front + // Only select other sessions + if (!getSelectedSession().isNull()) + { + selectConversationPair(getSelectedSession(), false, take_focus); + } + if (mInitialized && mIsFirstLaunch) + { + collapseMessagesPane(gSavedPerAccountSettings.getBOOL("ConversationsMessagePaneCollapsed")); + mIsFirstLaunch = false; +} +} + +void LLFloaterIMContainer::updateResizeLimits() +{ + LLMultiFloater::updateResizeLimits(); + assignResizeLimits(); +} + +bool LLFloaterIMContainer::isMessagesPaneCollapsed() +{ + return mMessagesPane->isCollapsed(); +} + +bool LLFloaterIMContainer::isConversationsPaneCollapsed() +{ + return mConversationsPane->isCollapsed(); +} + +void LLFloaterIMContainer::collapseMessagesPane(bool collapse) +{ + if (mMessagesPane->isCollapsed() == collapse) + { + return; + } + + // Save current width of panels before collapsing/expanding right pane. + S32 conv_pane_width = mConversationsPane->getRect().getWidth(); + S32 msg_pane_width = mMessagesPane->getRect().getWidth(); + + if (collapse) + { + // Save the messages pane width before collapsing it. + gSavedPerAccountSettings.setS32("ConversationsMessagePaneWidth", msg_pane_width); + + // Save the order in which the panels are closed to reverse user's last action. + gSavedPerAccountSettings.setBOOL("ConversationsExpandMessagePaneFirst", mConversationsPane->isCollapsed()); + } + + mConversationsPane->setIgnoreReshape(collapse); + + // Show/hide the messages pane. + mConversationsStack->collapsePanel(mMessagesPane, collapse); + + // Make sure layout is updated before resizing conversation pane. + mConversationsStack->updateLayout(); + + reshapeFloaterAndSetResizeLimits(collapse, gSavedPerAccountSettings.getS32("ConversationsMessagePaneWidth")); + + if (!collapse) + { + // Restore conversation's pane previous width after expanding messages pane. + mConversationsPane->setTargetDim(conv_pane_width); + } +} + +void LLFloaterIMContainer::collapseConversationsPane(bool collapse, bool save_is_allowed /*=true*/) +{ + if (mConversationsPane->isCollapsed() == collapse) + { + return; + } + + LLView* button_panel = getChild("conversations_pane_buttons_expanded"); + button_panel->setVisible(!collapse); + mExpandCollapseBtn->setImageOverlay(getString(collapse ? "expand_icon" : "collapse_icon")); + + // Save current width of Conversation panel before collapsing/expanding right pane. + S32 conv_pane_width = mConversationsPane->getRect().getWidth(); + + if (collapse && save_is_allowed) + { + // Save the conversations pane width before collapsing it. + gSavedPerAccountSettings.setS32("ConversationsListPaneWidth", conv_pane_width); + + // Save the order in which the panels are closed to reverse user's last action. + gSavedPerAccountSettings.setBOOL("ConversationsExpandMessagePaneFirst", !mMessagesPane->isCollapsed()); + } + + mConversationsStack->collapsePanel(mConversationsPane, collapse); + if (!collapse) + { + // Make sure layout is updated before resizing conversation pane. + mConversationsStack->updateLayout(); + // Restore conversation's pane previous width. + mConversationsPane->setTargetDim(gSavedPerAccountSettings.getS32("ConversationsListPaneWidth")); + } + + S32 delta_width = gSavedPerAccountSettings.getS32("ConversationsListPaneWidth") + - mConversationsPane->getMinDim() - mConversationsStack->getPanelSpacing() + 1; + + reshapeFloaterAndSetResizeLimits(collapse, delta_width); + + for (conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin(); + widget_it != mConversationsWidgets.end(); ++widget_it) + { + LLConversationViewSession* widget = dynamic_cast(widget_it->second); + if (widget) + { + widget->toggleCollapsedMode(collapse); + + // force closing all open conversations when collapsing to minimized state + if (collapse) + { + widget->setOpen(false); + } + widget->requestArrange(); + } + } +} + +void LLFloaterIMContainer::reshapeFloaterAndSetResizeLimits(bool collapse, S32 delta_width) +{ + LLRect floater_rect = getRect(); + floater_rect.mRight += ((collapse ? -1 : 1) * delta_width); + + // Set by_user = true so that reshaped rect is saved in user_settings. + setShape(floater_rect, true); + updateResizeLimits(); + + bool at_least_one_panel_is_expanded = + ! (mConversationsPane->isCollapsed() && mMessagesPane->isCollapsed()); + + setCanResize(at_least_one_panel_is_expanded); + setCanMinimize(at_least_one_panel_is_expanded); + + assignResizeLimits(); +} + +void LLFloaterIMContainer::assignResizeLimits() +{ + bool is_conv_pane_expanded = !mConversationsPane->isCollapsed(); + bool is_msg_pane_expanded = !mMessagesPane->isCollapsed(); + + S32 summary_width_of_visible_borders = (is_msg_pane_expanded ? mConversationsStack->getPanelSpacing() : 0) + 1; + + S32 conv_pane_target_width = is_conv_pane_expanded + ? ( is_msg_pane_expanded?mConversationsPane->getRect().getWidth():mConversationsPane->getExpandedMinDim() ) + : mConversationsPane->getMinDim(); + + S32 msg_pane_min_width = is_msg_pane_expanded ? mMessagesPane->getExpandedMinDim() : 0; + S32 new_min_width = conv_pane_target_width + msg_pane_min_width + summary_width_of_visible_borders; + + setResizeLimits(new_min_width, getMinHeight()); + + mConversationsStack->updateLayout(); +} + +void LLFloaterIMContainer::onAddButtonClicked() +{ + LLView * button = findChild("conversations_pane_buttons_expanded")->findChild("add_btn"); + LLFloater* root_floater = gFloaterView->getParentFloater(this); + LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(boost::bind(&LLFloaterIMContainer::onAvatarPicked, this, _1), true, true, true, root_floater->getName(), button); + + if (picker && root_floater) + { + root_floater->addDependentFloater(picker); + } +} + +void LLFloaterIMContainer::onAvatarPicked(const uuid_vec_t& ids) +{ + if (ids.size() == 1) + { + LLAvatarActions::startIM(ids.back()); + } + else + { + LLAvatarActions::startConference(ids); + } +} + +void LLFloaterIMContainer::onCustomAction(const LLSD& userdata) +{ + std::string command = userdata.asString(); + + if ("sort_sessions_by_type" == command) + { + setSortOrderSessions(LLConversationFilter::SO_SESSION_TYPE); + } + if ("sort_sessions_by_name" == command) + { + setSortOrderSessions(LLConversationFilter::SO_NAME); + } + if ("sort_sessions_by_recent" == command) + { + setSortOrderSessions(LLConversationFilter::SO_DATE); + } + if ("sort_participants_by_name" == command) + { + setSortOrderParticipants(LLConversationFilter::SO_NAME); + } + if ("sort_participants_by_recent" == command) + { + setSortOrderParticipants(LLConversationFilter::SO_DATE); + } + if ("sort_participants_by_distance" == command) + { + setSortOrderParticipants(LLConversationFilter::SO_DISTANCE); + } + if ("chat_preferences" == command) + { + LLFloaterPreference * floater_prefp = LLFloaterReg::showTypedInstance("preferences"); + if (floater_prefp) + { + floater_prefp->selectChatPanel(); + } + } + if ("privacy_preferences" == command) + { + LLFloaterPreference * floater_prefp = LLFloaterReg::showTypedInstance("preferences"); + if (floater_prefp) + { + floater_prefp->selectPrivacyPanel(); + } + } + if ("Translating.Toggle" == command) + { + gSavedSettings.setBOOL("TranslateChat", !gSavedSettings.getBOOL("TranslateChat")); + } +} + +bool LLFloaterIMContainer::isActionChecked(const LLSD& userdata) +{ + LLConversationSort order = mConversationViewModel.getSorter(); + std::string command = userdata.asString(); + if ("sort_sessions_by_type" == command) + { + return (order.getSortOrderSessions() == LLConversationFilter::SO_SESSION_TYPE); + } + if ("sort_sessions_by_name" == command) + { + return (order.getSortOrderSessions() == LLConversationFilter::SO_NAME); + } + if ("sort_sessions_by_recent" == command) + { + return (order.getSortOrderSessions() == LLConversationFilter::SO_DATE); + } + if ("sort_participants_by_name" == command) + { + return (order.getSortOrderParticipants() == LLConversationFilter::SO_NAME); + } + if ("sort_participants_by_recent" == command) + { + return (order.getSortOrderParticipants() == LLConversationFilter::SO_DATE); + } + if ("sort_participants_by_distance" == command) + { + return (order.getSortOrderParticipants() == LLConversationFilter::SO_DISTANCE); + } + if ("Translating.Enabled" == command) + { + return gSavedPerAccountSettings.getBOOL("TranslatingEnabled"); + } + if ("Translating.On" == command) + { + return gSavedSettings.getBOOL("TranslateChat"); + } + return false; +} + +void LLFloaterIMContainer::setSortOrderSessions(const LLConversationFilter::ESortOrderType order) +{ + LLConversationSort old_order = mConversationViewModel.getSorter(); + if (order != old_order.getSortOrderSessions()) + { + old_order.setSortOrderSessions(order); + setSortOrder(old_order); + } +} + +void LLFloaterIMContainer::setSortOrderParticipants(const LLConversationFilter::ESortOrderType order) +{ + LLConversationSort old_order = mConversationViewModel.getSorter(); + if (order != old_order.getSortOrderParticipants()) + { + old_order.setSortOrderParticipants(order); + setSortOrder(old_order); + } +} + +void LLFloaterIMContainer::setSortOrder(const LLConversationSort& order) +{ + mConversationViewModel.setSorter(order); + mConversationsRoot->arrangeAll(); + // try to keep selection onscreen, even if it wasn't to start with + mConversationsRoot->scrollToShowSelection(); + + // Notify all conversation (torn off or not) of the change to the sort order + // Note: For the moment, the sort order is *unique* across all conversations. That might change in the future. + for (conversations_items_map::iterator it_session = mConversationsItems.begin(); it_session != mConversationsItems.end(); it_session++) + { + LLUUID session_id = it_session->first; + LLFloaterIMSessionTab *conversation_floater = (session_id.isNull() ? (LLFloaterIMSessionTab*)(LLFloaterReg::findTypedInstance("nearby_chat")) : (LLFloaterIMSessionTab*)(LLFloaterIMSession::findInstance(session_id))); + if (conversation_floater) + { + conversation_floater->setSortOrder(order); + } + } + + gSavedSettings.setU32("ConversationSortOrder", (U32)order); +} + +void LLFloaterIMContainer::getSelectedUUIDs(uuid_vec_t& selected_uuids, bool participant_uuids/* = true*/) +{ + const std::set selectedItems = mConversationsRoot->getSelectionList(); + + std::set::const_iterator it = selectedItems.begin(); + const std::set::const_iterator it_end = selectedItems.end(); + LLConversationItem * conversationItem; + + for (; it != it_end; ++it) + { + conversationItem = static_cast((*it)->getViewModelItem()); + + //When a one-on-one conversation exists, retrieve the participant id from the conversation floater + if(conversationItem->getType() == LLConversationItem::CONV_SESSION_1_ON_1 && participant_uuids) + { + LLFloaterIMSession * conversation_floaterp = LLFloaterIMSession::findInstance(conversationItem->getUUID()); + LLUUID participant_id = conversation_floaterp->getOtherParticipantUUID(); + selected_uuids.push_back(participant_id); + } + else + { + selected_uuids.push_back(conversationItem->getUUID()); + } + } +} + +const LLConversationItem * LLFloaterIMContainer::getCurSelectedViewModelItem() +{ + LLConversationItem * conversation_item = NULL; + + if(mConversationsRoot && + mConversationsRoot->getCurSelectedItem() && + mConversationsRoot->getCurSelectedItem()->getViewModelItem()) + { + LLFloaterIMSessionTab *selected_session_floater = LLFloaterIMSessionTab::getConversation(mSelectedSession); + if (selected_session_floater && !selected_session_floater->getHost() && selected_session_floater->getCurSelectedViewModelItem()) + { + conversation_item = selected_session_floater->getCurSelectedViewModelItem(); + } + else + { + conversation_item = static_cast(mConversationsRoot->getCurSelectedItem()->getViewModelItem()); + } + } + + return conversation_item; +} + +void LLFloaterIMContainer::getParticipantUUIDs(uuid_vec_t& selected_uuids) +{ + //Find the conversation floater associated with the selected id + const LLConversationItem * conversation_item = getCurSelectedViewModelItem(); + + if (NULL == conversation_item) + { + return; + } + + getSelectedUUIDs(selected_uuids); +} + +void LLFloaterIMContainer::doToParticipants(const std::string& command, uuid_vec_t& selectedIDS) +{ + if (selectedIDS.size() == 1) + { + const LLUUID& userID = selectedIDS.front(); + if ("view_profile" == command) + { + LLAvatarActions::showProfile(userID); + } + else if ("im" == command) + { + if (gAgent.getID() != userID) + { + LLAvatarActions::startIM(userID); + } + } + else if ("offer_teleport" == command) + { + LLAvatarActions::offerTeleport(selectedIDS); + } + else if ("request_teleport" == command) + { + LLAvatarActions::teleportRequest(selectedIDS.front()); + } + else if ("voice_call" == command) + { + LLAvatarActions::startCall(userID); + } + else if ("chat_history" == command) + { + LLAvatarActions::viewChatHistory(userID); + } + else if ("add_friend" == command) + { + LLAvatarActions::requestFriendshipDialog(userID); + } + else if ("remove_friend" == command) + { + LLAvatarActions::removeFriendDialog(userID); + } + else if ("invite_to_group" == command) + { + LLAvatarActions::inviteToGroup(userID); + } + else if ("zoom_in" == command) + { + handle_zoom_to_object(userID); + } + else if ("map" == command) + { + LLAvatarActions::showOnMap(userID); + } + else if ("share" == command) + { + LLAvatarActions::share(userID); + } + else if ("pay" == command) + { + LLAvatarActions::pay(userID); + } + else if ("report_abuse" == command) + { + LLAvatarName av_name; + if (LLAvatarNameCache::get(userID, &av_name)) + { + LLFloaterReporter::showFromAvatar(userID, av_name.getCompleteName()); + } + else + { + LLFloaterReporter::showFromAvatar(userID, "not avaliable"); + } + } + else if ("block_unblock" == command) + { + LLAvatarActions::toggleMute(userID, LLMute::flagVoiceChat); + } + else if ("mute_unmute" == command) + { + LLAvatarActions::toggleMute(userID, LLMute::flagTextChat); + } + else if ("selected" == command || "mute_all" == command || "unmute_all" == command) + { + moderateVoice(command, userID); + } + else if ("toggle_allow_text_chat" == command) + { + toggleAllowTextChat(userID); + } + else if ("ban_member" == command) + { + banSelectedMember(userID); + } + } + else if (selectedIDS.size() > 1) + { + if ("im" == command) + { + LLAvatarActions::startConference(selectedIDS); + } + else if ("offer_teleport" == command) + { + LLAvatarActions::offerTeleport(selectedIDS); + } + else if ("voice_call" == command) + { + LLAvatarActions::startAdhocCall(selectedIDS); + } + else if ("remove_friend" == command) + { + LLAvatarActions::removeFriendsDialog(selectedIDS); + } + } +} + +void LLFloaterIMContainer::doToSelectedConversation(const std::string& command, uuid_vec_t& selectedIDS) +{ + //Find the conversation floater associated with the selected id + const LLConversationItem * conversationItem = getCurSelectedViewModelItem(); + LLFloaterIMSession *conversationFloater = LLFloaterIMSession::findInstance(conversationItem->getUUID()); + + if(conversationFloater) + { + //Close the selected conversation + if("close_conversation" == command) + { + LLFloater::onClickClose(conversationFloater); + } + else if("close_selected_conversations" == command) + { + getSelectedUUIDs(selectedIDS,false); + closeSelectedConversations(selectedIDS); + } + else if("open_voice_conversation" == command) + { + gIMMgr->startCall(conversationItem->getUUID()); + } + else if("disconnect_from_voice" == command) + { + gIMMgr->endCall(conversationItem->getUUID()); + } + else if("chat_history" == command) + { + if (selectedIDS.size() > 0) + { + if(conversationItem->getType() == LLConversationItem::CONV_SESSION_GROUP) + { + LLFloaterReg::showInstance("preview_conversation", conversationItem->getUUID(), true); + } + else if(conversationItem->getType() == LLConversationItem::CONV_SESSION_AD_HOC) + { + LLConversation* conv = LLConversationLog::instance().findConversation(LLIMModel::getInstance()->findIMSession(conversationItem->getUUID())); + if(conv) + { + LLFloaterReg::showInstance("preview_conversation", conv->getSessionID(), true); + } + } + else + { + LLAvatarActions::viewChatHistory(selectedIDS.front()); + } + } + } + else + { + if(conversationItem->getType() == LLConversationItem::CONV_SESSION_1_ON_1) + { + doToParticipants(command, selectedIDS); + } + } + } + //if there is no LLFloaterIMSession* instance for selected conversation it might be Nearby chat + else + { + if(conversationItem->getType() == LLConversationItem::CONV_SESSION_NEARBY) + { + if("chat_history" == command) + { + LLFloaterReg::showInstance("preview_conversation", LLSD(LLUUID::null), true); + } +} + } +} + +void LLFloaterIMContainer::doToSelected(const LLSD& userdata) +{ + std::string command = userdata.asString(); + const LLConversationItem * conversationItem = getCurSelectedViewModelItem(); + uuid_vec_t selected_uuids; + + if(conversationItem != NULL) + { + getParticipantUUIDs(selected_uuids); + + if(conversationItem->getType() == LLConversationItem::CONV_PARTICIPANT) + { + doToParticipants(command, selected_uuids); + } + else + { + doToSelectedConversation(command, selected_uuids); + } + } +} + +void LLFloaterIMContainer::doToSelectedGroup(const LLSD& userdata) +{ + std::string action = userdata.asString(); + + if (action == "group_profile") + { + LLGroupActions::show(mSelectedSession); + } + else if (action == "activate_group") + { + LLGroupActions::activate(mSelectedSession); + } + else if (action == "leave_group") + { + LLGroupActions::leave(mSelectedSession); + } +} + +bool LLFloaterIMContainer::enableContextMenuItem(const LLSD& userdata) +{ + const std::string& item = userdata.asString(); + uuid_vec_t uuids; + getParticipantUUIDs(uuids); + + + //If there is group or ad-hoc chat in multiselection, everything needs to be disabled + if(uuids.size() > 1) + { + const std::set selectedItems = mConversationsRoot->getSelectionList(); + LLConversationItem * conversationItem; + for(std::set::const_iterator it = selectedItems.begin(); it != selectedItems.end(); ++it) + { + conversationItem = static_cast((*it)->getViewModelItem()); + if((conversationItem->getType() == LLConversationItem::CONV_SESSION_GROUP) || (conversationItem->getType() == LLConversationItem::CONV_SESSION_AD_HOC)) + { + return false; + } + } + } + + if ("conversation_log" == item) + { + return gSavedPerAccountSettings.getS32("KeepConversationLogTranscripts") > 0; + } + + //Enable Chat history item for ad-hoc and group conversations + if ("can_chat_history" == item && uuids.size() > 0) + { + //Disable menu item if selected participant is user agent + if(uuids.front() != gAgentID) + { + if (getCurSelectedViewModelItem()->getType() == LLConversationItem::CONV_SESSION_NEARBY) + { + return LLLogChat::isNearbyTranscriptExist(); + } + else if (getCurSelectedViewModelItem()->getType() == LLConversationItem::CONV_SESSION_AD_HOC) + { + const LLConversation* conv = LLConversationLog::instance().findConversation(LLIMModel::getInstance()->findIMSession(uuids.front())); + if(conv) + { + return LLLogChat::isAdHocTranscriptExist(conv->getHistoryFileName()); + } + return false; + } + else + { + bool is_group = (getCurSelectedViewModelItem()->getType() == LLConversationItem::CONV_SESSION_GROUP); + return LLLogChat::isTranscriptExist(uuids.front(),is_group); + } + } + } + + // If nothing is selected(and selected item is not group chat), everything needs to be disabled + if (uuids.size() <= 0) + { + if(getCurSelectedViewModelItem()) + { + return getCurSelectedViewModelItem()->getType() == LLConversationItem::CONV_SESSION_GROUP; + } + return false; + } + + if("can_activate_group" == item) + { + LLUUID selected_group_id = getCurSelectedViewModelItem()->getUUID(); + return gAgent.getGroupID() != selected_group_id; + } + + return enableContextMenuItem(item, uuids); +} + +bool LLFloaterIMContainer::enableContextMenuItem(const std::string& item, uuid_vec_t& uuids) +{ + // Extract the single select info + bool is_single_select = (uuids.size() == 1); + const LLUUID& single_id = uuids.front(); + + if ("can_chat_history" == item && is_single_select) + { + return LLLogChat::isTranscriptExist(uuids.front(),false); + } + + // Handle options that are applicable to all including the user agent + if ("can_view_profile" == item) + { + return is_single_select; + } + + bool is_moderator_option = ("can_moderate_voice" == item) || ("can_allow_text_chat" == item) || ("can_mute" == item) || ("can_unmute" == item); + + // Beyond that point, if only the user agent is selected, everything is disabled + if (is_single_select && (single_id == gAgentID)) + { + if (is_moderator_option) + { + return enableModerateContextMenuItem(item, true); + } + else + { + return false; + } + } + + // If the user agent is selected with others, everything is disabled + for (uuid_vec_t::const_iterator id = uuids.begin(); id != uuids.end(); ++id) + { + if (gAgent.getID() == *id) + { + return false; + } + } + + // Handle all other options + if (("can_invite" == item) + || ("can_chat_history" == item) + || ("can_share" == item) + || ("can_pay" == item) + || ("report_abuse" == item)) + { + // Those menu items are enable only if a single avatar is selected + return is_single_select; + } + else if ("can_block" == item) + { + return (is_single_select ? LLAvatarActions::canBlock(single_id) : false); + } + else if ("can_add" == item) + { + // We can add friends if: + // - there is only 1 selected avatar (EXT-7389) + // - this avatar is not already a friend + return (is_single_select ? !LLAvatarActions::isFriend(single_id) : false); + } + else if ("can_delete" == item) + { + // We can remove friends if there are only friends among the selection + bool result = true; + for (uuid_vec_t::const_iterator id = uuids.begin(); id != uuids.end(); ++id) + { + result &= LLAvatarActions::isFriend(*id); + } + return result; + } + else if ("can_call" == item) + { + return LLAvatarActions::canCall(); + } + else if ("can_open_voice_conversation" == item) + { + return is_single_select && LLAvatarActions::canCall(); + } + else if ("can_open_voice_conversation" == item) + { + return is_single_select && LLAvatarActions::canCall(); + } + else if ("can_zoom_in" == item) + { + return is_single_select && gObjectList.findObject(single_id); + } + else if ("can_show_on_map" == item) + { + return (is_single_select ? (LLAvatarTracker::instance().isBuddyOnline(single_id) && is_agent_mappable(single_id)) || gAgent.isGodlike() : false); + } + else if ("can_offer_teleport" == item) + { + return LLAvatarActions::canOfferTeleport(uuids); + } + else if ("can_ban_member" == item) + { + return canBanSelectedMember(single_id); + } + else if (is_moderator_option) + { + // *TODO : get that out of here... + return enableModerateContextMenuItem(item); + } + + // By default, options that not explicitely disabled are enabled + return true; +} + +bool LLFloaterIMContainer::checkContextMenuItem(const LLSD& userdata) +{ + std::string item = userdata.asString(); + uuid_vec_t uuids; + getParticipantUUIDs(uuids); + + return checkContextMenuItem(item, uuids); +} + +bool LLFloaterIMContainer::checkContextMenuItem(const std::string& item, uuid_vec_t& uuids) +{ + if (uuids.size() == 1) + { + if ("is_blocked" == item) + { + return LLMuteList::getInstance()->isMuted(uuids.front(), LLMute::flagVoiceChat); + } + else if (item == "is_muted") + { + return LLMuteList::getInstance()->isMuted(uuids.front(), LLMute::flagTextChat); + } + else if ("is_allowed_text_chat" == item) + { + const LLSpeaker * speakerp = getSpeakerOfSelectedParticipant(getSpeakerMgrForSelectedParticipant()); + + if (NULL != speakerp) + { + return !speakerp->mModeratorMutedText; + } + } + } + + return false; +} + +bool LLFloaterIMContainer::visibleContextMenuItem(const LLSD& userdata) +{ + const LLConversationItem *conversation_item = getCurSelectedViewModelItem(); + if(!conversation_item) + { + return false; + } + + const std::string& item = userdata.asString(); + + if ("show_mute" == item) + { + return !isMuted(conversation_item->getUUID()); + } + else if ("show_unmute" == item) + { + return isMuted(conversation_item->getUUID()); + } + + return true; +} + +void LLFloaterIMContainer::showConversation(const LLUUID& session_id) +{ + setVisibleAndFrontmost(false); + selectConversationPair(session_id, true); + + LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(session_id); + if (session_floater) + { + session_floater->restoreFloater(); + } +} + +void LLFloaterIMContainer::clearAllFlashStates() +{ + conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin(); + for (;widget_it != mConversationsWidgets.end(); ++widget_it) + { + LLConversationViewSession* widget = dynamic_cast(widget_it->second); + if (widget) + { + widget->setFlashState(false); + } + } +} + +void LLFloaterIMContainer::selectConversation(const LLUUID& session_id) +{ + selectConversationPair(session_id, true); +} + +// Select the conversation *after* (or before if none after) the passed uuid conversation +// Used to change the selection on key hits +void LLFloaterIMContainer::selectNextConversationByID(const LLUUID& uuid) +{ + bool new_selection = false; + selectConversation(uuid); + new_selection = selectNextorPreviousConversation(true); + if (!new_selection) + { + selectNextorPreviousConversation(false); + } +} + +// Synchronous select the conversation item and the conversation floater +bool LLFloaterIMContainer::selectConversationPair(const LLUUID& session_id, bool select_widget, bool focus_floater/*=true*/) +{ + bool handled = true; + LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::findConversation(session_id); + + /* widget processing */ + if (select_widget && mConversationsRoot->getSelectedCount() <= 1) + { + LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,session_id); + if (widget && widget->getParentFolder()) + { + widget->getParentFolder()->setSelection(widget, false, false); + mConversationsRoot->scrollToShowSelection(); + } + } + + /* floater processing */ + + if (NULL != session_floater && !session_floater->isDead()) + { + if (session_id != getSelectedSession()) + { + // Store the active session + setSelectedSession(session_id); + + + + if (session_floater->getHost()) + { + // Always expand the message pane if the panel is hosted by the container + collapseMessagesPane(false); + // Switch to the conversation floater that is being selected + selectFloater(session_floater); + } + else + { + showStub(true); + } + + //When in DND mode, remove stored IM notifications + //Nearby chat (Null) IMs are not stored while in DND mode, so can ignore removal + if(gAgent.isDoNotDisturb() && session_id.notNull()) + { + LLDoNotDisturbNotificationStorage::getInstance()->removeNotification(LLDoNotDisturbNotificationStorage::toastName, session_id); + } + } + + // Set the focus on the selected floater + if (!session_floater->hasFocus() && !session_floater->isMinimized()) + { + session_floater->setFocus(focus_floater); + } + } + flashConversationItemWidget(session_id,false); + return handled; +} + +void LLFloaterIMContainer::setTimeNow(const LLUUID& session_id, const LLUUID& participant_id) +{ + LLConversationItemSession* item = dynamic_cast(getSessionModel(session_id)); + if (item) + { + item->setTimeNow(participant_id); + mConversationViewModel.requestSortAll(); + mConversationsRoot->arrangeAll(); + } +} + +void LLFloaterIMContainer::setNearbyDistances() +{ + // Get the nearby chat session: that's the one with uuid nul + LLConversationItemSession* item = dynamic_cast(getSessionModel(LLUUID())); + if (item) + { + // Get the positions of the nearby avatars and their ids + std::vector positions; + uuid_vec_t avatar_ids; + LLWorld::getInstance()->getAvatars(&avatar_ids, &positions, gAgent.getPositionGlobal(), gSavedSettings.getF32("NearMeRange")); + // Get the position of the agent + const LLVector3d& me_pos = gAgent.getPositionGlobal(); + // For each nearby avatar, compute and update the distance + int avatar_count = positions.size(); + for (int i = 0; i < avatar_count; i++) + { + F64 dist = dist_vec_squared(positions[i], me_pos); + item->setDistance(avatar_ids[i],dist); + } + // Also does it for the agent itself + item->setDistance(gAgent.getID(),0.0f); + // Request resort + mConversationViewModel.requestSortAll(); + mConversationsRoot->arrangeAll(); + } +} + +LLConversationItem* LLFloaterIMContainer::addConversationListItem(const LLUUID& uuid, bool isWidgetSelected /*= false*/) +{ + bool is_nearby_chat = uuid.isNull(); + + // Stores the display name for the conversation line item + std::string display_name = is_nearby_chat ? LLTrans::getString("NearbyChatLabel") : LLIMModel::instance().getName(uuid); + + // Check if the item is not already in the list, exit (nothing to do) + // Note: this happens often, when reattaching a torn off conversation for instance + conversations_items_map::iterator item_it = mConversationsItems.find(uuid); + if (item_it != mConversationsItems.end()) + { + return item_it->second; + } + + // Create a conversation session model + LLConversationItemSession* item = NULL; + LLSpeakerMgr* speaker_manager = (is_nearby_chat ? (LLSpeakerMgr*)(LLLocalSpeakerMgr::getInstance()) : LLIMModel::getInstance()->getSpeakerManager(uuid)); + if (speaker_manager) + { + item = new LLParticipantList(speaker_manager, getRootViewModel()); + } + if (!item) + { + LL_WARNS() << "Couldn't create conversation session item : " << display_name << LL_ENDL; + return NULL; + } + item->renameItem(display_name); + item->updateName(NULL); + + mConversationsItems[uuid] = item; + + // Create a widget from it + LLConversationViewSession* widget = createConversationItemWidget(item); + mConversationsWidgets[uuid] = widget; + + // Add a new conversation widget to the root folder of the folder view + widget->addToFolder(mConversationsRoot); + widget->requestArrange(); + + LLIMModel::LLIMSession * im_sessionp = LLIMModel::getInstance()->findIMSession(uuid); + + // Create the participants widgets now + // Note: usually, we do not get an updated avatar list at that point + if (uuid.isNull() || (im_sessionp && !im_sessionp->isP2PSessionType())) + { + LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = item->getChildrenBegin(); + LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = item->getChildrenEnd(); + while (current_participant_model != end_participant_model) + { + LLConversationItem* participant_model = dynamic_cast(*current_participant_model); + LLConversationViewParticipant* participant_view = createConversationViewParticipant(participant_model); + participant_view->addToFolder(widget); + current_participant_model++; + } + } + + if (uuid.notNull() && im_sessionp->isP2PSessionType()) + { + item->fetchAvatarName(false); + } + + // Do that too for the conversation dialog + LLFloaterIMSessionTab *conversation_floater = (uuid.isNull() ? (LLFloaterIMSessionTab*)(LLFloaterReg::findTypedInstance("nearby_chat")) : (LLFloaterIMSessionTab*)(LLFloaterIMSession::findInstance(uuid))); + if (conversation_floater) + { + conversation_floater->buildConversationViewParticipant(); + } + + // set the widget to minimized mode if conversations pane is collapsed + widget->toggleCollapsedMode(mConversationsPane->isCollapsed()); + + if (isWidgetSelected || 0 == mConversationsRoot->getSelectedCount()) + { + selectConversationPair(uuid, true); + widget->requestArrange(); + + // scroll to newly added item + mConversationsRoot->scrollToShowSelection(); + } + + return item; +} + +bool LLFloaterIMContainer::removeConversationListItem(const LLUUID& uuid, bool change_focus) +{ + // Delete the widget and the associated conversation item + // Note : since the mConversationsItems is also the listener to the widget, deleting + // the widget will also delete its listener + bool is_widget_selected = false; + LLFolderViewItem* new_selection = NULL; + LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,uuid); + if (widget) + { + is_widget_selected = widget->isSelected(); + if (mConversationsRoot) + { + new_selection = mConversationsRoot->getNextFromChild(widget, false); + if (!new_selection) + { + new_selection = mConversationsRoot->getPreviousFromChild(widget, false); + } + } + + // Will destroy views and delete models that are not assigned to any views + widget->destroyView(); + } + + // Suppress the conversation items and widgets from their respective maps + mConversationsItems.erase(uuid); + mConversationsWidgets.erase(uuid); + // Clear event query (otherwise reopening session in some way can bombard session with stale data) + mConversationEventQueue.erase(uuid); + + // Don't let the focus fall IW, select and refocus on the first conversation in the list + if (change_focus) + { + setFocus(true); + if (new_selection) + { + if (mConversationsWidgets.size() == 1) + { + // If only one widget is left, it has to be the Nearby Chat. Select it directly. + selectConversationPair(LLUUID(NULL), true); + } + else + { + LLConversationItem* vmi = dynamic_cast(new_selection->getViewModelItem()); + if (vmi) + { + selectConversationPair(vmi->getUUID(), true); + } + } + } + } + return is_widget_selected; +} + +LLConversationViewSession* LLFloaterIMContainer::createConversationItemWidget(LLConversationItem* item) +{ + LLConversationViewSession::Params params; + + params.name = item->getDisplayName(); + params.root = mConversationsRoot; + params.listener = item; + params.tool_tip = params.name; + params.container = this; + + //Indentation for aligning the p2p converstation image with the nearby chat arrow + if(item->getType() == LLConversationItem::CONV_SESSION_1_ON_1) + { + params.folder_indentation = 3; + } + + return LLUICtrlFactory::create(params); +} + +LLConversationViewParticipant* LLFloaterIMContainer::createConversationViewParticipant(LLConversationItem* item) +{ + LLConversationViewParticipant::Params params; + LLRect panel_rect = mConversationsListPanel->getRect(); + + params.name = item->getDisplayName(); + params.root = mConversationsRoot; + params.listener = item; + + //24 is the the current hight of an item (itemHeight) loaded from conversation_view_participant.xml. + params.rect = LLRect (0, 24, panel_rect.getWidth(), 0); + params.tool_tip = params.name; + params.participant_id = item->getUUID(); + params.folder_indentation = 27; + + return LLUICtrlFactory::create(params); +} + +bool LLFloaterIMContainer::enableModerateContextMenuItem(const std::string& userdata, bool is_self) +{ + // only group moderators can perform actions related to this "enable callback" + if (!isGroupModerator()) + { + return false; + } + + LLSpeaker * speakerp = getSpeakerOfSelectedParticipant(getSpeakerMgrForSelectedParticipant()); + if (NULL == speakerp) + { + return false; + } + + bool voice_channel = speakerp->isInVoiceChannel(); + + if ("can_moderate_voice" == userdata) + { + return voice_channel; + } + else if (("can_mute" == userdata) && !is_self) + { + return voice_channel && !isMuted(getCurSelectedViewModelItem()->getUUID()); + } + else if ("can_unmute" == userdata) + { + return voice_channel && isMuted(getCurSelectedViewModelItem()->getUUID()); + } + + // The last invoke is used to check whether the "can_allow_text_chat" will enabled + return LLVoiceClient::getInstance()->isParticipantAvatar(getCurSelectedViewModelItem()->getUUID()) && !is_self; +} + +bool LLFloaterIMContainer::isGroupModerator() +{ + LLSpeakerMgr * speaker_manager = getSpeakerMgrForSelectedParticipant(); + if (NULL == speaker_manager) + { + LL_WARNS() << "Speaker manager is missing" << LL_ENDL; + return false; + } + + // Is session a group call/chat? + if(gAgent.isInGroup(speaker_manager->getSessionID())) + { + LLSpeaker * speaker = speaker_manager->findSpeaker(gAgentID).get(); + + // Is agent a moderator? + return speaker && speaker->mIsModerator; + } + + return false; +} + +bool LLFloaterIMContainer::haveAbilityToBan() +{ + LLSpeakerMgr * speaker_manager = getSpeakerMgrForSelectedParticipant(); + if (NULL == speaker_manager) + { + LL_WARNS() << "Speaker manager is missing" << LL_ENDL; + return false; + } + LLUUID group_uuid = speaker_manager->getSessionID(); + + return gAgent.isInGroup(group_uuid) && gAgent.hasPowerInGroup(group_uuid, GP_GROUP_BAN_ACCESS); +} + +bool LLFloaterIMContainer::canBanSelectedMember(const LLUUID& participant_uuid) +{ + LLSpeakerMgr * speaker_manager = getSpeakerMgrForSelectedParticipant(); + if (NULL == speaker_manager) + { + LL_WARNS() << "Speaker manager is missing" << LL_ENDL; + return false; + } + LLUUID group_uuid = speaker_manager->getSessionID(); + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_uuid); + if(!gdatap) + { + LL_WARNS("Groups") << "Unable to get group data for group " << group_uuid << LL_ENDL; + return false; + } + + if (gdatap->mPendingBanRequest) + { + return false; + } + + if (gdatap->isRoleMemberDataComplete()) + { + if (gdatap->mMembers.size()) + { + LLGroupMgrGroupData::member_list_t::iterator mi = gdatap->mMembers.find((participant_uuid)); + if (mi != gdatap->mMembers.end()) + { + LLGroupMemberData* member_data = (*mi).second; + // Is the member an owner? + if (member_data && member_data->isInRole(gdatap->mOwnerRole)) + { + return false; + } + } + } + } + + if( gAgent.hasPowerInGroup(group_uuid, GP_ROLE_REMOVE_MEMBER) && + gAgent.hasPowerInGroup(group_uuid, GP_GROUP_BAN_ACCESS) ) + { + return true; + } + + return false; +} + +void LLFloaterIMContainer::banSelectedMember(const LLUUID& participant_uuid) +{ + LLSpeakerMgr * speaker_manager = getSpeakerMgrForSelectedParticipant(); + if (NULL == speaker_manager) + { + LL_WARNS() << "Speaker manager is missing" << LL_ENDL; + return; + } + + LLUUID group_uuid = speaker_manager->getSessionID(); + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_uuid); + if(!gdatap) + { + LL_WARNS("Groups") << "Unable to get group data for group " << group_uuid << LL_ENDL; + return; + } + + gdatap->banMemberById(participant_uuid); + +} + +void LLFloaterIMContainer::moderateVoice(const std::string& command, const LLUUID& userID) +{ + if (!gAgent.getRegion()) return; + + if (command.compare("selected")) + { + moderateVoiceAllParticipants(command.compare("mute_all")); + } + else + { + moderateVoiceParticipant(userID, isMuted(userID)); + } +} + +bool LLFloaterIMContainer::isMuted(const LLUUID& avatar_id) +{ + const LLSpeaker * speakerp = getSpeakerOfSelectedParticipant(getSpeakerMgrForSelectedParticipant()); + return NULL == speakerp ? true : speakerp->mStatus == LLSpeaker::STATUS_MUTED; +} + +void LLFloaterIMContainer::moderateVoiceAllParticipants(bool unmute) +{ + LLIMSpeakerMgr * speaker_managerp = dynamic_cast(getSpeakerMgrForSelectedParticipant()); + + if (NULL != speaker_managerp) + { + if (!unmute) + { + LLSD payload; + payload["session_id"] = speaker_managerp->getSessionID(); + LLNotificationsUtil::add("ConfirmMuteAll", LLSD(), payload, confirmMuteAllCallback); + return; + } + + speaker_managerp->moderateVoiceAllParticipants(unmute); + } +} + +// static +void LLFloaterIMContainer::confirmMuteAllCallback(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + // if Cancel pressed + if (option == 1) + { + return; + } + + const LLSD& payload = notification["payload"]; + const LLUUID& session_id = payload["session_id"]; + + LLIMSpeakerMgr * speaker_manager = dynamic_cast ( + LLIMModel::getInstance()->getSpeakerManager(session_id)); + if (speaker_manager) + { + speaker_manager->moderateVoiceAllParticipants(false); + } + + return; +} + +void LLFloaterIMContainer::moderateVoiceParticipant(const LLUUID& avatar_id, bool unmute) +{ + LLIMSpeakerMgr * speaker_managerp = dynamic_cast(getSpeakerMgrForSelectedParticipant()); + + if (NULL != speaker_managerp) + { + speaker_managerp->moderateVoiceParticipant(avatar_id, unmute); + } +} + +LLSpeakerMgr * LLFloaterIMContainer::getSpeakerMgrForSelectedParticipant() +{ + LLFolderViewItem *selectedItem = mConversationsRoot->getCurSelectedItem(); + if (NULL == selectedItem) + { + LL_WARNS() << "Current selected item is null" << LL_ENDL; + return NULL; + } + + conversations_widgets_map::const_iterator iter = mConversationsWidgets.begin(); + conversations_widgets_map::const_iterator end = mConversationsWidgets.end(); + const LLUUID * conversation_uuidp = NULL; + while(iter != end) + { + if (iter->second == selectedItem || iter->second == selectedItem->getParentFolder()) + { + conversation_uuidp = &iter->first; + break; + } + ++iter; + } + if (NULL == conversation_uuidp) + { + LL_WARNS() << "Cannot find conversation item widget" << LL_ENDL; + return NULL; + } + + return conversation_uuidp->isNull() ? (LLSpeakerMgr *)LLLocalSpeakerMgr::getInstance() + : LLIMModel::getInstance()->getSpeakerManager(*conversation_uuidp); +} + +LLSpeaker * LLFloaterIMContainer::getSpeakerOfSelectedParticipant(LLSpeakerMgr * speaker_managerp) +{ + if (NULL == speaker_managerp) + { + LL_WARNS() << "Speaker manager is missing" << LL_ENDL; + return NULL; + } + + const LLConversationItem * participant_itemp = getCurSelectedViewModelItem(); + if (NULL == participant_itemp) + { + LL_WARNS() << "Cannot evaluate current selected view model item" << LL_ENDL; + return NULL; + } + + return speaker_managerp->findSpeaker(participant_itemp->getUUID()); +} + +void LLFloaterIMContainer::toggleAllowTextChat(const LLUUID& participant_uuid) +{ + LLIMSpeakerMgr * speaker_managerp = dynamic_cast(getSpeakerMgrForSelectedParticipant()); + if (NULL != speaker_managerp) + { + speaker_managerp->toggleAllowTextChat(participant_uuid); + } +} + +void LLFloaterIMContainer::openNearbyChat() +{ + // If there's only one conversation in the container and that conversation is the nearby chat + //(which it should be...), open it so to make the list of participants visible. This happens to be the most common case when opening the Chat floater. + if((mConversationsItems.size() == 1)&&(!mConversationsPane->isCollapsed())) + { + LLConversationViewSession* nearby_chat = dynamic_cast(get_ptr_in_map(mConversationsWidgets,LLUUID())); + if (nearby_chat) + { + reSelectConversation(); + nearby_chat->setOpen(true); + } + } +} + +void LLFloaterIMContainer::reSelectConversation() +{ + LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::getConversation(mSelectedSession); + if (session_floater->getHost()) + { + selectFloater(session_floater); + } +} + +void LLFloaterIMContainer::updateSpeakBtnState() +{ + mSpeakBtn->setToggleState(LLVoiceClient::getInstance()->getUserPTTState()); + mSpeakBtn->setEnabled(LLAgent::isActionAllowed("speak")); +} + +bool LLFloaterIMContainer::isConversationLoggingAllowed() +{ + return gSavedPerAccountSettings.getS32("KeepConversationLogTranscripts") > 0; +} + +void LLFloaterIMContainer::flashConversationItemWidget(const LLUUID& session_id, bool is_flashes) +{ + //Finds the conversation line item to flash using the session_id + LLConversationViewSession * widget = dynamic_cast(get_ptr_in_map(mConversationsWidgets,session_id)); + + if (widget) + { + widget->setFlashState(is_flashes); + } +} + +void LLFloaterIMContainer::highlightConversationItemWidget(const LLUUID& session_id, bool is_highlighted) +{ + //Finds the conversation line item to highlight using the session_id + LLConversationViewSession * widget = dynamic_cast(get_ptr_in_map(mConversationsWidgets,session_id)); + + if (widget) + { + widget->setHighlightState(is_highlighted); + } +} + +bool LLFloaterIMContainer::isScrolledOutOfSight(LLConversationViewSession* conversation_item_widget) +{ + llassert(conversation_item_widget != NULL); + + // make sure the widget is actually in the right spot first + mConversationsRoot->arrange(NULL, NULL); + + // check whether the widget is in the visible portion of the scroll container + LLRect widget_rect; + conversation_item_widget->localRectToOtherView(conversation_item_widget->getLocalRect(), &widget_rect, mConversationsRoot); + return !mConversationsRoot->getVisibleRect().overlaps(widget_rect); +} + +bool LLFloaterIMContainer::handleKeyHere(KEY key, MASK mask ) +{ + bool handled = false; + + if(mask == MASK_ALT) + { + if (KEY_RETURN == key ) + { + expandConversation(); + handled = true; + } + + if ((KEY_DOWN == key ) || (KEY_RIGHT == key)) + { + selectNextorPreviousConversation(true); + handled = true; + } + if ((KEY_UP == key) || (KEY_LEFT == key)) + { + selectNextorPreviousConversation(false); + handled = true; + } + } + return handled; +} + +bool LLFloaterIMContainer::selectAdjacentConversation(bool focus_selected) +{ + bool selectedAdjacentConversation = selectNextorPreviousConversation(true, focus_selected); + + if(!selectedAdjacentConversation) + { + selectedAdjacentConversation = selectNextorPreviousConversation(false, focus_selected); + } + + return selectedAdjacentConversation; +} + +bool LLFloaterIMContainer::selectNextorPreviousConversation(bool select_next, bool focus_selected) +{ + if (mConversationsWidgets.size() > 1) + { + LLFolderViewItem* new_selection = NULL; + LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,getSelectedSession()); + if (widget) + { + if(select_next) + { + new_selection = mConversationsRoot->getNextFromChild(widget, false); + } + else + { + new_selection = mConversationsRoot->getPreviousFromChild(widget, false); + } + if (new_selection) + { + LLConversationItem* vmi = dynamic_cast(new_selection->getViewModelItem()); + if (vmi) + { + selectConversationPair(vmi->getUUID(), true, focus_selected); + return true; + } + } + } + } + return false; +} + +void LLFloaterIMContainer::expandConversation() +{ + if(!mConversationsPane->isCollapsed()) + { + LLConversationViewSession* widget = dynamic_cast(get_ptr_in_map(mConversationsWidgets,getSelectedSession())); + if (widget) + { + widget->setOpen(!widget->isOpen()); + } + } +} +bool LLFloaterIMContainer::isParticipantListExpanded() +{ + bool is_expanded = false; + if(!mConversationsPane->isCollapsed()) + { + LLConversationViewSession* widget = dynamic_cast(get_ptr_in_map(mConversationsWidgets,getSelectedSession())); + if (widget) + { + is_expanded = widget->isOpen(); + } + } + return is_expanded; +} + +// By default, if torn off session is currently frontmost, LLFloater::isFrontmost() will return false, which can lead to some bugs +// So LLFloater::isFrontmost() is overriden here to check both selected session and the IM floater itself +// Exclude "Nearby Chat" session from the check, as "Nearby Chat" window and "Conversations" floater can be brought +// to front independently +/*virtual*/ +bool LLFloaterIMContainer::isFrontmost() +{ + LLFloaterIMSessionTab* selected_session = LLFloaterIMSessionTab::getConversation(mSelectedSession); + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); + return (selected_session && selected_session->isFrontmost() && (selected_session != nearby_chat)) + || LLFloater::isFrontmost(); +} + +// For conversations, closeFloater() (linked to Ctrl-W) does not actually close the floater but the active conversation. +// This is intentional so it doesn't confuse the user. onClickCloseBtn() closes the whole floater. +void LLFloaterIMContainer::onClickCloseBtn(bool app_quitting/* = false*/) +{ + gSavedPerAccountSettings.setS32("ConversationsListPaneWidth", mConversationsPane->getRect().getWidth()); + LLMultiFloater::closeFloater(app_quitting); +} + +void LLFloaterIMContainer::closeHostedFloater() +{ + onClickCloseBtn(); +} + +void LLFloaterIMContainer::closeAllConversations() +{ + std::vector ids; + for (conversations_items_map::iterator it_session = mConversationsItems.begin(); it_session != mConversationsItems.end(); it_session++) + { + LLUUID session_id = it_session->first; + if (session_id != LLUUID()) + { + ids.push_back(session_id); + } + } + + for (std::vector::const_iterator it = ids.begin(); it != ids.end(); ++it) + { + LLFloaterIMSession *conversationFloater = LLFloaterIMSession::findInstance(*it); + LLFloater::onClickClose(conversationFloater); + } +} + +void LLFloaterIMContainer::closeSelectedConversations(const uuid_vec_t& ids) +{ + for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it) + { + //We don't need to close Nearby chat, so skip it + if (*it != LLUUID()) + { + LLFloaterIMSession *conversationFloater = LLFloaterIMSession::findInstance(*it); + if(conversationFloater) + { + LLFloater::onClickClose(conversationFloater); + } + } + } +} +void LLFloaterIMContainer::closeFloater(bool app_quitting/* = false*/) +{ + if(app_quitting) + { + closeAllConversations(); + onClickCloseBtn(app_quitting); + } + else + { + // Check for currently active session + LLUUID session_id = getSelectedSession(); + // If current session is Nearby Chat or there is only one session remaining, close the floater + if (mConversationsItems.size() == 1 || session_id == LLUUID() || app_quitting) + { + onClickCloseBtn(); + } + else + { + // Otherwise, close current conversation + LLFloaterIMSessionTab* active_conversation = LLFloaterIMSessionTab::getConversation(session_id); + if (active_conversation) + { + active_conversation->closeFloater(); + } + } + } +} + +void LLFloaterIMContainer::handleReshape(const LLRect& rect, bool by_user) +{ + LLMultiFloater::handleReshape(rect, by_user); + storeRectControl(); +} + +// EOF diff --git a/indra/newview/llfloaterimcontainer.h b/indra/newview/llfloaterimcontainer.h index 68bb9feb59..36c0d0833f 100644 --- a/indra/newview/llfloaterimcontainer.h +++ b/indra/newview/llfloaterimcontainer.h @@ -1,239 +1,239 @@ -/** - * @file llfloaterimcontainer.h - * @brief Multifloater containing active IM sessions in separate tab container tabs - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERIMCONTAINER_H -#define LL_LLFLOATERIMCONTAINER_H - -#include -#include - -#include "llimview.h" -#include "llevents.h" -#include "../llui/llfloater.h" -#include "../llui/llmultifloater.h" -#include "llavatarpropertiesprocessor.h" -#include "llgroupmgr.h" -#include "../llui/lltrans.h" -#include "llconversationmodel.h" -#include "llconversationview.h" - -class LLButton; -class LLLayoutPanel; -class LLLayoutStack; -class LLTabContainer; -class LLFloaterIMContainer; -class LLSpeaker; -class LLSpeakerMgr; - -class LLFloaterIMContainer - : public LLMultiFloater - , public LLIMSessionObserver -{ -public: - LLFloaterIMContainer(const LLSD& seed, const Params& params = getDefaultParams()); - virtual ~LLFloaterIMContainer(); - - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); - /*virtual*/ void draw(); - /*virtual*/ void setMinimized(bool b); - /*virtual*/ void setVisible(bool visible); - /*virtual*/ void setVisibleAndFrontmost(bool take_focus=true, const LLSD& key = LLSD()); - /*virtual*/ void updateResizeLimits(); - /*virtual*/ void handleReshape(const LLRect& rect, bool by_user); - - void onCloseFloater(LLUUID& id); - - /*virtual*/ void addFloater(LLFloater* floaterp, - bool select_added_floater, - LLTabContainer::eInsertionPoint insertion_point = LLTabContainer::END); - void returnFloaterToHost(); - void showConversation(const LLUUID& session_id); - void selectConversation(const LLUUID& session_id); - void selectNextConversationByID(const LLUUID& session_id); - bool selectConversationPair(const LLUUID& session_id, bool select_widget, bool focus_floater = true); - void clearAllFlashStates(); - bool selectAdjacentConversation(bool focus_selected); - bool selectNextorPreviousConversation(bool select_next, bool focus_selected = true); - void expandConversation(); - - /*virtual*/ void tabClose(); - void showStub(bool visible); - - static LLFloaterIMContainer* findInstance(); - static LLFloaterIMContainer* getInstance(); - - static void onCurrentChannelChanged(const LLUUID& session_id); - - void collapseMessagesPane(bool collapse); - bool isMessagesPaneCollapsed(); - bool isConversationsPaneCollapsed(); - - // Callbacks - static void idle(void* user_data); - - // LLIMSessionObserver observe triggers - /*virtual*/ void sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id, bool has_offline_msg); - /*virtual*/ void sessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id); - /*virtual*/ void sessionVoiceOrIMStarted(const LLUUID& session_id); - /*virtual*/ void sessionRemoved(const LLUUID& session_id); - /*virtual*/ void sessionIDUpdated(const LLUUID& old_session_id, const LLUUID& new_session_id); - - LLConversationViewModel& getRootViewModel() { return mConversationViewModel; } - LLUUID getSelectedSession() { return mSelectedSession; } - void setSelectedSession(LLUUID sessionID) { mSelectedSession = sessionID; } - LLConversationItem* getSessionModel(const LLUUID& session_id); - LLConversationSort& getSortOrder() { return mConversationViewModel.getSorter(); } - - // Handling of lists of participants is public so to be common with llfloatersessiontab - // *TODO : Find a better place for this. - bool checkContextMenuItem(const std::string& item, uuid_vec_t& selectedIDS); - bool enableContextMenuItem(const std::string& item, uuid_vec_t& selectedIDS); - void doToParticipants(const std::string& item, uuid_vec_t& selectedIDS); - - void assignResizeLimits(); - virtual bool handleKeyHere(KEY key, MASK mask ); - /*virtual*/ void closeFloater(bool app_quitting = false); - void closeAllConversations(); - void closeSelectedConversations(const uuid_vec_t& ids); - /*virtual*/ bool isFrontmost(); - - -private: - typedef std::map avatarID_panel_map_t; - avatarID_panel_map_t mSessions; - boost::signals2::connection mNewMessageConnection; - - /*virtual*/ void computeResizeLimits(S32& new_min_width, S32& new_min_height) {} - - void onNewMessageReceived(const LLSD& data); - - void onExpandCollapseButtonClicked(); - void onStubCollapseButtonClicked(); - void processParticipantsStyleUpdate(); - void onSpeakButtonPressed(); - void onSpeakButtonReleased(); - /*virtual*/ void onClickCloseBtn(bool app_quitting = false); - /*virtual*/ void closeHostedFloater(); - - void collapseConversationsPane(bool collapse, bool save_is_allowed=true); - - void reshapeFloaterAndSetResizeLimits(bool collapse, S32 delta_width); - - void onAddButtonClicked(); - void onAvatarPicked(const uuid_vec_t& ids); - - bool isActionChecked(const LLSD& userdata); - void onCustomAction (const LLSD& userdata); - void setSortOrderSessions(const LLConversationFilter::ESortOrderType order); - void setSortOrderParticipants(const LLConversationFilter::ESortOrderType order); - void setSortOrder(const LLConversationSort& order); - - void getSelectedUUIDs(uuid_vec_t& selected_uuids, bool participant_uuids = true); - const LLConversationItem * getCurSelectedViewModelItem(); - void getParticipantUUIDs(uuid_vec_t& selected_uuids); - void doToSelected(const LLSD& userdata); - bool checkContextMenuItem(const LLSD& userdata); - bool enableContextMenuItem(const LLSD& userdata); - bool visibleContextMenuItem(const LLSD& userdata); - void doToSelectedConversation(const std::string& command, uuid_vec_t& selectedIDS); - void doToSelectedGroup(const LLSD& userdata); - - static void confirmMuteAllCallback(const LLSD& notification, const LLSD& response); - bool enableModerateContextMenuItem(const std::string& userdata, bool is_self = false); - LLSpeaker * getSpeakerOfSelectedParticipant(LLSpeakerMgr * speaker_managerp); - LLSpeakerMgr * getSpeakerMgrForSelectedParticipant(); - bool isGroupModerator(); - bool haveAbilityToBan(); - bool canBanSelectedMember(const LLUUID& participant_uuid); - LLUUID getGroupUIIDForSelectedParticipant(); - bool isMuted(const LLUUID& avatar_id); - void moderateVoice(const std::string& command, const LLUUID& userID); - void moderateVoiceAllParticipants(bool unmute); - void moderateVoiceParticipant(const LLUUID& avatar_id, bool unmute); - void toggleAllowTextChat(const LLUUID& participant_uuid); - void banSelectedMember(const LLUUID& participant_uuid); - void openNearbyChat(); - bool isParticipantListExpanded(); - - void idleUpdate(); // for convenience (self) from static idle - void idleProcessEvents(); - - LLButton* mExpandCollapseBtn; - LLButton* mStubCollapseBtn; - LLButton* mSpeakBtn; - LLPanel* mStubPanel; - LLTextBox* mStubTextBox; - LLLayoutPanel* mMessagesPane; - LLLayoutPanel* mConversationsPane; - LLLayoutStack* mConversationsStack; - - bool mInitialized; - bool mIsFirstLaunch; - - bool mIsFirstOpen; - - LLUUID mSelectedSession; - std::string mGeneralTitle; - - // Conversation list implementation -public: - bool removeConversationListItem(const LLUUID& uuid, bool change_focus = true); - LLConversationItem* addConversationListItem(const LLUUID& uuid, bool isWidgetSelected = false); - void setTimeNow(const LLUUID& session_id, const LLUUID& participant_id); - void setNearbyDistances(); - void reSelectConversation(); - void updateSpeakBtnState(); - static bool isConversationLoggingAllowed(); - void flashConversationItemWidget(const LLUUID& session_id, bool is_flashes); - void highlightConversationItemWidget(const LLUUID& session_id, bool is_highlighted); - bool isScrolledOutOfSight(LLConversationViewSession* conversation_item_widget); - boost::signals2::connection mMicroChangedSignal; - S32 getConversationListItemSize() { return mConversationsWidgets.size(); } - typedef std::list floater_list_t; - void getDetachedConversationFloaters(floater_list_t& floaters); - -private: - LLConversationViewSession* createConversationItemWidget(LLConversationItem* item); - LLConversationViewParticipant* createConversationViewParticipant(LLConversationItem* item); - bool onConversationModelEvent(const LLSD& event); - void handleConversationModelEvent(const LLSD& event); - - // Conversation list data - LLPanel* mConversationsListPanel; // This is the main widget we add conversation widget to - conversations_items_map mConversationsItems; - conversations_widgets_map mConversationsWidgets; - LLConversationViewModel mConversationViewModel; - LLFolderView* mConversationsRoot; - LLEventStream mConversationsEventStream; - - typedef std::map > conversations_items_deque; - conversations_items_deque mConversationEventQueue; - - LLTimer mParticipantRefreshTimer; -}; - -#endif // LL_LLFLOATERIMCONTAINER_H +/** + * @file llfloaterimcontainer.h + * @brief Multifloater containing active IM sessions in separate tab container tabs + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERIMCONTAINER_H +#define LL_LLFLOATERIMCONTAINER_H + +#include +#include + +#include "llimview.h" +#include "llevents.h" +#include "../llui/llfloater.h" +#include "../llui/llmultifloater.h" +#include "llavatarpropertiesprocessor.h" +#include "llgroupmgr.h" +#include "../llui/lltrans.h" +#include "llconversationmodel.h" +#include "llconversationview.h" + +class LLButton; +class LLLayoutPanel; +class LLLayoutStack; +class LLTabContainer; +class LLFloaterIMContainer; +class LLSpeaker; +class LLSpeakerMgr; + +class LLFloaterIMContainer + : public LLMultiFloater + , public LLIMSessionObserver +{ +public: + LLFloaterIMContainer(const LLSD& seed, const Params& params = getDefaultParams()); + virtual ~LLFloaterIMContainer(); + + /*virtual*/ bool postBuild(); + /*virtual*/ void onOpen(const LLSD& key); + /*virtual*/ void draw(); + /*virtual*/ void setMinimized(bool b); + /*virtual*/ void setVisible(bool visible); + /*virtual*/ void setVisibleAndFrontmost(bool take_focus=true, const LLSD& key = LLSD()); + /*virtual*/ void updateResizeLimits(); + /*virtual*/ void handleReshape(const LLRect& rect, bool by_user); + + void onCloseFloater(LLUUID& id); + + /*virtual*/ void addFloater(LLFloater* floaterp, + bool select_added_floater, + LLTabContainer::eInsertionPoint insertion_point = LLTabContainer::END); + void returnFloaterToHost(); + void showConversation(const LLUUID& session_id); + void selectConversation(const LLUUID& session_id); + void selectNextConversationByID(const LLUUID& session_id); + bool selectConversationPair(const LLUUID& session_id, bool select_widget, bool focus_floater = true); + void clearAllFlashStates(); + bool selectAdjacentConversation(bool focus_selected); + bool selectNextorPreviousConversation(bool select_next, bool focus_selected = true); + void expandConversation(); + + /*virtual*/ void tabClose(); + void showStub(bool visible); + + static LLFloaterIMContainer* findInstance(); + static LLFloaterIMContainer* getInstance(); + + static void onCurrentChannelChanged(const LLUUID& session_id); + + void collapseMessagesPane(bool collapse); + bool isMessagesPaneCollapsed(); + bool isConversationsPaneCollapsed(); + + // Callbacks + static void idle(void* user_data); + + // LLIMSessionObserver observe triggers + /*virtual*/ void sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id, bool has_offline_msg); + /*virtual*/ void sessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id); + /*virtual*/ void sessionVoiceOrIMStarted(const LLUUID& session_id); + /*virtual*/ void sessionRemoved(const LLUUID& session_id); + /*virtual*/ void sessionIDUpdated(const LLUUID& old_session_id, const LLUUID& new_session_id); + + LLConversationViewModel& getRootViewModel() { return mConversationViewModel; } + LLUUID getSelectedSession() { return mSelectedSession; } + void setSelectedSession(LLUUID sessionID) { mSelectedSession = sessionID; } + LLConversationItem* getSessionModel(const LLUUID& session_id); + LLConversationSort& getSortOrder() { return mConversationViewModel.getSorter(); } + + // Handling of lists of participants is public so to be common with llfloatersessiontab + // *TODO : Find a better place for this. + bool checkContextMenuItem(const std::string& item, uuid_vec_t& selectedIDS); + bool enableContextMenuItem(const std::string& item, uuid_vec_t& selectedIDS); + void doToParticipants(const std::string& item, uuid_vec_t& selectedIDS); + + void assignResizeLimits(); + virtual bool handleKeyHere(KEY key, MASK mask ); + /*virtual*/ void closeFloater(bool app_quitting = false); + void closeAllConversations(); + void closeSelectedConversations(const uuid_vec_t& ids); + /*virtual*/ bool isFrontmost(); + + +private: + typedef std::map avatarID_panel_map_t; + avatarID_panel_map_t mSessions; + boost::signals2::connection mNewMessageConnection; + + /*virtual*/ void computeResizeLimits(S32& new_min_width, S32& new_min_height) {} + + void onNewMessageReceived(const LLSD& data); + + void onExpandCollapseButtonClicked(); + void onStubCollapseButtonClicked(); + void processParticipantsStyleUpdate(); + void onSpeakButtonPressed(); + void onSpeakButtonReleased(); + /*virtual*/ void onClickCloseBtn(bool app_quitting = false); + /*virtual*/ void closeHostedFloater(); + + void collapseConversationsPane(bool collapse, bool save_is_allowed=true); + + void reshapeFloaterAndSetResizeLimits(bool collapse, S32 delta_width); + + void onAddButtonClicked(); + void onAvatarPicked(const uuid_vec_t& ids); + + bool isActionChecked(const LLSD& userdata); + void onCustomAction (const LLSD& userdata); + void setSortOrderSessions(const LLConversationFilter::ESortOrderType order); + void setSortOrderParticipants(const LLConversationFilter::ESortOrderType order); + void setSortOrder(const LLConversationSort& order); + + void getSelectedUUIDs(uuid_vec_t& selected_uuids, bool participant_uuids = true); + const LLConversationItem * getCurSelectedViewModelItem(); + void getParticipantUUIDs(uuid_vec_t& selected_uuids); + void doToSelected(const LLSD& userdata); + bool checkContextMenuItem(const LLSD& userdata); + bool enableContextMenuItem(const LLSD& userdata); + bool visibleContextMenuItem(const LLSD& userdata); + void doToSelectedConversation(const std::string& command, uuid_vec_t& selectedIDS); + void doToSelectedGroup(const LLSD& userdata); + + static void confirmMuteAllCallback(const LLSD& notification, const LLSD& response); + bool enableModerateContextMenuItem(const std::string& userdata, bool is_self = false); + LLSpeaker * getSpeakerOfSelectedParticipant(LLSpeakerMgr * speaker_managerp); + LLSpeakerMgr * getSpeakerMgrForSelectedParticipant(); + bool isGroupModerator(); + bool haveAbilityToBan(); + bool canBanSelectedMember(const LLUUID& participant_uuid); + LLUUID getGroupUIIDForSelectedParticipant(); + bool isMuted(const LLUUID& avatar_id); + void moderateVoice(const std::string& command, const LLUUID& userID); + void moderateVoiceAllParticipants(bool unmute); + void moderateVoiceParticipant(const LLUUID& avatar_id, bool unmute); + void toggleAllowTextChat(const LLUUID& participant_uuid); + void banSelectedMember(const LLUUID& participant_uuid); + void openNearbyChat(); + bool isParticipantListExpanded(); + + void idleUpdate(); // for convenience (self) from static idle + void idleProcessEvents(); + + LLButton* mExpandCollapseBtn; + LLButton* mStubCollapseBtn; + LLButton* mSpeakBtn; + LLPanel* mStubPanel; + LLTextBox* mStubTextBox; + LLLayoutPanel* mMessagesPane; + LLLayoutPanel* mConversationsPane; + LLLayoutStack* mConversationsStack; + + bool mInitialized; + bool mIsFirstLaunch; + + bool mIsFirstOpen; + + LLUUID mSelectedSession; + std::string mGeneralTitle; + + // Conversation list implementation +public: + bool removeConversationListItem(const LLUUID& uuid, bool change_focus = true); + LLConversationItem* addConversationListItem(const LLUUID& uuid, bool isWidgetSelected = false); + void setTimeNow(const LLUUID& session_id, const LLUUID& participant_id); + void setNearbyDistances(); + void reSelectConversation(); + void updateSpeakBtnState(); + static bool isConversationLoggingAllowed(); + void flashConversationItemWidget(const LLUUID& session_id, bool is_flashes); + void highlightConversationItemWidget(const LLUUID& session_id, bool is_highlighted); + bool isScrolledOutOfSight(LLConversationViewSession* conversation_item_widget); + boost::signals2::connection mMicroChangedSignal; + S32 getConversationListItemSize() { return mConversationsWidgets.size(); } + typedef std::list floater_list_t; + void getDetachedConversationFloaters(floater_list_t& floaters); + +private: + LLConversationViewSession* createConversationItemWidget(LLConversationItem* item); + LLConversationViewParticipant* createConversationViewParticipant(LLConversationItem* item); + bool onConversationModelEvent(const LLSD& event); + void handleConversationModelEvent(const LLSD& event); + + // Conversation list data + LLPanel* mConversationsListPanel; // This is the main widget we add conversation widget to + conversations_items_map mConversationsItems; + conversations_widgets_map mConversationsWidgets; + LLConversationViewModel mConversationViewModel; + LLFolderView* mConversationsRoot; + LLEventStream mConversationsEventStream; + + typedef std::map > conversations_items_deque; + conversations_items_deque mConversationEventQueue; + + LLTimer mParticipantRefreshTimer; +}; + +#endif // LL_LLFLOATERIMCONTAINER_H diff --git a/indra/newview/llfloaterimnearbychat.cpp b/indra/newview/llfloaterimnearbychat.cpp index 753359c782..26b0836301 100644 --- a/indra/newview/llfloaterimnearbychat.cpp +++ b/indra/newview/llfloaterimnearbychat.cpp @@ -1,943 +1,943 @@ -/** - * @file LLFloaterIMNearbyChat.cpp - * @brief LLFloaterIMNearbyChat class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "message.h" - -#include "lliconctrl.h" -#include "llappviewer.h" -#include "llchatentry.h" -#include "llfloaterreg.h" -#include "lltrans.h" -#include "llfloaterimcontainer.h" -#include "llfloatersidepanelcontainer.h" -#include "llfocusmgr.h" -#include "lllogchat.h" -#include "llresizebar.h" -#include "llresizehandle.h" -#include "lldraghandle.h" -#include "llmenugl.h" -#include "llviewermenu.h" // for gMenuHolder -#include "llfloaterimnearbychathandler.h" -#include "llchannelmanager.h" -#include "llchathistory.h" -#include "llstylemap.h" -#include "llavatarnamecache.h" -#include "llfloaterreg.h" -#include "lltrans.h" - -#include "llfirstuse.h" -#include "llfloaterimnearbychat.h" -#include "llagent.h" // gAgent -#include "llgesturemgr.h" -#include "llmultigesture.h" -#include "llkeyboard.h" -#include "llanimationstates.h" -#include "llviewerstats.h" -#include "llcommandhandler.h" -#include "llviewercontrol.h" -#include "llnavigationbar.h" -#include "llwindow.h" -#include "llviewerwindow.h" -#include "llrootview.h" -#include "llviewerchat.h" -#include "lltranslate.h" -#include "llautoreplace.h" -#include "lluiusage.h" - -S32 LLFloaterIMNearbyChat::sLastSpecialChatChannel = 0; - -constexpr S32 EXPANDED_HEIGHT = 266; -constexpr S32 COLLAPSED_HEIGHT = 60; -constexpr S32 EXPANDED_MIN_HEIGHT = 150; - -// legacy callback glue -void send_chat_from_viewer(const std::string& utf8_out_text, EChatType type, S32 channel); - -struct LLChatTypeTrigger { - std::string name; - EChatType type; -}; - -static LLChatTypeTrigger sChatTypeTriggers[] = { - { "/whisper" , CHAT_TYPE_WHISPER}, - { "/shout" , CHAT_TYPE_SHOUT} -}; - -bool cb_do_nothing() -{ - return false; -} - -LLFloaterIMNearbyChat::LLFloaterIMNearbyChat(const LLSD& llsd) -: LLFloaterIMSessionTab(LLSD(LLUUID::null)), - //mOutputMonitor(NULL), - mSpeakerMgr(NULL), - mExpandedHeight(COLLAPSED_HEIGHT + EXPANDED_HEIGHT) -{ - mIsP2PChat = false; - mIsNearbyChat = true; - mSpeakerMgr = LLLocalSpeakerMgr::getInstance(); - - // Required by LLFloaterIMSessionTab::mGearBtn - // But nearby floater has no 'per agent' menu items, - mEnableCallbackRegistrar.add("Avatar.EnableGearItem", boost::bind(&cb_do_nothing)); - mCommitCallbackRegistrar.add("Avatar.GearDoToSelected", boost::bind(&cb_do_nothing)); - mEnableCallbackRegistrar.add("Avatar.CheckGearItem", boost::bind(&cb_do_nothing)); - - mMinFloaterHeight = EXPANDED_MIN_HEIGHT; -} - -//static -LLFloaterIMNearbyChat* LLFloaterIMNearbyChat::buildFloater(const LLSD& key) -{ - LLFloaterReg::getInstance("im_container"); - return new LLFloaterIMNearbyChat(key); -} - -//virtual -bool LLFloaterIMNearbyChat::postBuild() -{ - setIsSingleInstance(true); - bool result = LLFloaterIMSessionTab::postBuild(); - - mInputEditor->setAutoreplaceCallback(boost::bind(&LLAutoReplace::autoreplaceCallback, LLAutoReplace::getInstance(), _1, _2, _3, _4, _5)); - mInputEditor->setCommitCallback(boost::bind(&LLFloaterIMNearbyChat::onChatBoxCommit, this)); - mInputEditor->setKeystrokeCallback(boost::bind(&LLFloaterIMNearbyChat::onChatBoxKeystroke, this)); - mInputEditor->setFocusLostCallback(boost::bind(&LLFloaterIMNearbyChat::onChatBoxFocusLost, this)); - mInputEditor->setFocusReceivedCallback(boost::bind(&LLFloaterIMNearbyChat::onChatBoxFocusReceived, this)); - std::string nearbyChatTitle(LLTrans::getString("NearbyChatTitle")); - mInputEditor->setLabel(nearbyChatTitle); - - // Title must be defined BEFORE call to addConversationListItem() because - // it is used to show the item's name in the conversations list - setTitle(nearbyChatTitle); - - // obsolete, but may be needed for backward compatibility? - gSavedSettings.declareS32("nearbychat_showicons_and_names", 2, "NearByChat header settings", LLControlVariable::PERSIST_NONDFT); - - if (gSavedPerAccountSettings.getBOOL("LogShowHistory")) - { - loadHistory(); - } - - return result; -} - -// virtual -void LLFloaterIMNearbyChat::closeHostedFloater() -{ - // If detached from conversations window close anyway - if (!getHost()) - { - setVisible(false); - } - - // Should check how many conversations are ongoing. Select next to "Nearby Chat" in case there are some other besides. - // Close conversations window in case "Nearby Chat" is attached and the only conversation - LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); - if (floater_container->getConversationListItemSize() == 1) - { - if (getHost()) - { - floater_container->closeFloater(); - } - } - else - { - if (!getHost()) - { - floater_container->selectNextConversationByID(LLUUID()); - } - } -} - -// virtual -void LLFloaterIMNearbyChat::refresh() -{ - displaySpeakingIndicator(); - updateCallBtnState(LLVoiceClient::getInstance()->getUserPTTState()); - - // *HACK: Update transparency type depending on whether our children have focus. - // This is needed because this floater is chrome and thus cannot accept focus, so - // the transparency type setting code from LLFloater::setFocus() isn't reached. - if (getTransparencyType() != TT_DEFAULT) - { - setTransparencyType(hasFocus() ? TT_ACTIVE : TT_INACTIVE); - } -} - -void LLFloaterIMNearbyChat::reloadMessages(bool clean_messages/* = false*/) -{ - if (clean_messages) - { - mMessageArchive.clear(); - loadHistory(); - } - - mChatHistory->clear(); - - LLSD do_not_log; - do_not_log["do_not_log"] = true; - for(std::vector::iterator it = mMessageArchive.begin();it!=mMessageArchive.end();++it) - { - // Update the messages without re-writing them to a log file. - addMessage(*it,false, do_not_log); - } -} - -void LLFloaterIMNearbyChat::loadHistory() -{ - LLSD do_not_log; - do_not_log["do_not_log"] = true; - - std::list history; - LLLogChat::loadChatHistory("chat", history); - - std::list::const_iterator it = history.begin(); - while (it != history.end()) - { - const LLSD& msg = *it; - - std::string from = msg[LL_IM_FROM]; - LLUUID from_id; - if (msg[LL_IM_FROM_ID].isDefined()) - { - from_id = msg[LL_IM_FROM_ID].asUUID(); - } - else - { - std::string legacy_name = gCacheName->buildLegacyName(from); - from_id = LLAvatarNameCache::getInstance()->findIdByName(legacy_name); - } - - LLChat chat; - chat.mFromName = from; - chat.mFromID = from_id; - chat.mText = msg[LL_IM_TEXT].asString(); - chat.mTimeStr = msg[LL_IM_TIME].asString(); - chat.mChatStyle = CHAT_STYLE_HISTORY; - - chat.mSourceType = CHAT_SOURCE_AGENT; - if (from_id.isNull() && SYSTEM_FROM == from) - { - chat.mSourceType = CHAT_SOURCE_SYSTEM; - - } - else if (from_id.isNull()) - { - chat.mSourceType = isWordsName(from) ? CHAT_SOURCE_UNKNOWN : CHAT_SOURCE_OBJECT; - } - - addMessage(chat, true, do_not_log); - - it++; - } -} - -void LLFloaterIMNearbyChat::removeScreenChat() -{ - LLNotificationsUI::LLScreenChannelBase* chat_channel = LLNotificationsUI::LLChannelManager::getInstance()->findChannelByID( - LLNotificationsUI::NEARBY_CHAT_CHANNEL_UUID); - if(chat_channel) - { - chat_channel->removeToastsFromChannel(); - } -} - - -void LLFloaterIMNearbyChat::setVisible(bool visible) -{ - LLFloaterIMSessionTab::setVisible(visible); - - if(visible) - { - removeScreenChat(); - } -} - - -void LLFloaterIMNearbyChat::setVisibleAndFrontmost(bool take_focus, const LLSD& key) -{ - LLFloaterIMSessionTab::setVisibleAndFrontmost(take_focus, key); - - if(matchesKey(key)) - { - LLFloaterIMContainer::getInstance()->selectConversationPair(mSessionID, true, take_focus); - } -} - -// virtual -void LLFloaterIMNearbyChat::onTearOffClicked() -{ - LLFloaterIMSessionTab::onTearOffClicked(); - - // see CHUI-170: Save torn-off state of the nearby chat between sessions - bool in_the_multifloater(getHost()); - gSavedPerAccountSettings.setBOOL("NearbyChatIsNotTornOff", in_the_multifloater); -} - - -// virtual -void LLFloaterIMNearbyChat::onOpen(const LLSD& key) -{ - LLFloaterIMSessionTab::onOpen(key); - if(!isMessagePaneExpanded()) - { - restoreFloater(); - onCollapseToLine(this); - } -} - -// virtual -void LLFloaterIMNearbyChat::onClose(bool app_quitting) -{ - // Override LLFloaterIMSessionTab::onClose() so that Nearby Chat is not removed from the conversation floater - LLFloaterIMSessionTab::restoreFloater(); - if (app_quitting) - { - // We are starting and closing floater in "expanded" state - // Update expanded (restored) rect and position for use in next session - forceReshape(); - storeRectControl(); - } -} - -// virtual -void LLFloaterIMNearbyChat::onClickCloseBtn(bool) - -{ - if (!isTornOff()) - { - return; - } - closeHostedFloater(); -} - -void LLFloaterIMNearbyChat::onChatFontChange(LLFontGL* fontp) -{ - // Update things with the new font whohoo - if (mInputEditor) - { - mInputEditor->setFont(fontp); - } -} - - -void LLFloaterIMNearbyChat::show() -{ - openFloater(getKey()); -} - -bool LLFloaterIMNearbyChat::isChatVisible() const -{ - bool isVisible = false; - LLFloaterIMContainer* im_box = LLFloaterIMContainer::getInstance(); - // Is the IM floater container ever null? - llassert(im_box != NULL); - if (im_box != NULL) - { - isVisible = - isChatMultiTab() && gSavedPerAccountSettings.getBOOL("NearbyChatIsNotTornOff")? - im_box->getVisible() && !im_box->isMinimized() : - getVisible() && !isMinimized(); - } - - return isVisible; -} - -void LLFloaterIMNearbyChat::showHistory() -{ - openFloater(); - LLFloaterIMContainer::getInstance()->selectConversation(LLUUID(NULL)); - - if(!isMessagePaneExpanded()) - { - restoreFloater(); - setFocus(true); - } - else - { - LLFloaterIMContainer::getInstance()->setFocus(true); - } - setResizeLimits(getMinWidth(), EXPANDED_MIN_HEIGHT); -} - -std::string LLFloaterIMNearbyChat::getCurrentChat() -{ - return mInputEditor ? mInputEditor->getText() : LLStringUtil::null; -} - -// virtual -bool LLFloaterIMNearbyChat::handleKeyHere( KEY key, MASK mask ) -{ - bool handled = false; - - if( KEY_RETURN == key && mask == MASK_CONTROL) - { - // shout - sendChat(CHAT_TYPE_SHOUT); - handled = true; - } - else if (KEY_RETURN == key && mask == MASK_SHIFT) - { - // whisper - sendChat(CHAT_TYPE_WHISPER); - handled = true; - } - - - if((mask == MASK_ALT) && isTornOff()) - { - LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); - if ((KEY_UP == key) || (KEY_LEFT == key)) - { - floater_container->selectNextorPreviousConversation(false); - handled = true; - } - if ((KEY_DOWN == key ) || (KEY_RIGHT == key)) - { - floater_container->selectNextorPreviousConversation(true); - handled = true; - } - } - - return handled; -} - -bool LLFloaterIMNearbyChat::matchChatTypeTrigger(const std::string& in_str, std::string* out_str) -{ - U32 in_len = in_str.length(); - S32 cnt = sizeof(sChatTypeTriggers) / sizeof(*sChatTypeTriggers); - - bool string_was_found = false; - - for (S32 n = 0; n < cnt && !string_was_found; n++) - { - if (in_len <= sChatTypeTriggers[n].name.length()) - { - std::string trigger_trunc = sChatTypeTriggers[n].name; - LLStringUtil::truncate(trigger_trunc, in_len); - - if (!LLStringUtil::compareInsensitive(in_str, trigger_trunc)) - { - *out_str = sChatTypeTriggers[n].name; - string_was_found = true; - } - } - } - - return string_was_found; -} - -void LLFloaterIMNearbyChat::onChatBoxKeystroke() -{ - LLFloaterIMContainer* im_box = LLFloaterIMContainer::findInstance(); - if (im_box) - { - im_box->flashConversationItemWidget(mSessionID,false); - } - - LLFirstUse::otherAvatarChatFirst(false); - - LLWString raw_text = mInputEditor->getWText(); - - // Can't trim the end, because that will cause autocompletion - // to eat trailing spaces that might be part of a gesture. - LLWStringUtil::trimHead(raw_text); - - S32 length = raw_text.length(); - - if( (length > 0) && (raw_text[0] != '/') ) // forward slash is used for escape (eg. emote) sequences - { - gAgent.startTyping(); - } - else - { - gAgent.stopTyping(); - } - - /* Doesn't work -- can't tell the difference between a backspace - that killed the selection vs. backspace at the end of line. - if (length > 1 - && text[0] == '/' - && key == KEY_BACKSPACE) - { - // the selection will already be deleted, but we need to trim - // off the character before - std::string new_text = raw_text.substr(0, length-1); - mInputEditor->setText( new_text ); - mInputEditor->setCursorToEnd(); - length = length - 1; - } - */ - - KEY key = gKeyboard->currentKey(); - - // Ignore "special" keys, like backspace, arrows, etc. - if (gSavedSettings.getBOOL("ChatAutocompleteGestures") - && length > 1 - && raw_text[0] == '/' - && key < KEY_SPECIAL) - { - // we're starting a gesture, attempt to autocomplete - - std::string utf8_trigger = wstring_to_utf8str(raw_text); - std::string utf8_out_str(utf8_trigger); - - if (LLGestureMgr::instance().matchPrefix(utf8_trigger, &utf8_out_str)) - { - std::string rest_of_match = utf8_out_str.substr(utf8_trigger.size()); - if (!rest_of_match.empty()) - { - mInputEditor->setText(utf8_trigger + rest_of_match); // keep original capitalization for user-entered part - // Select to end of line, starting from the character - // after the last one the user typed. - mInputEditor->selectByCursorPosition(utf8_out_str.size()-rest_of_match.size(),utf8_out_str.size()); - } - - } - else if (matchChatTypeTrigger(utf8_trigger, &utf8_out_str)) - { - std::string rest_of_match = utf8_out_str.substr(utf8_trigger.size()); - mInputEditor->setText(utf8_trigger + rest_of_match + " "); // keep original capitalization for user-entered part - mInputEditor->endOfDoc(); - } - - //LL_INFOS() << "GESTUREDEBUG " << trigger - // << " len " << length - // << " outlen " << out_str.getLength() - // << LL_ENDL; - } -} - -// static -void LLFloaterIMNearbyChat::onChatBoxFocusLost() -{ - // stop typing animation - gAgent.stopTyping(); -} - -void LLFloaterIMNearbyChat::onChatBoxFocusReceived() -{ - mInputEditor->setEnabled(!gDisconnected); -} - -EChatType LLFloaterIMNearbyChat::processChatTypeTriggers(EChatType type, std::string &str) -{ - U32 length = str.length(); - S32 cnt = sizeof(sChatTypeTriggers) / sizeof(*sChatTypeTriggers); - - for (S32 n = 0; n < cnt; n++) - { - if (length >= sChatTypeTriggers[n].name.length()) - { - std::string trigger = str.substr(0, sChatTypeTriggers[n].name.length()); - - if (!LLStringUtil::compareInsensitive(trigger, sChatTypeTriggers[n].name)) - { - U32 trigger_length = sChatTypeTriggers[n].name.length(); - - // It's to remove space after trigger name - if (length > trigger_length && str[trigger_length] == ' ') - trigger_length++; - - str = str.substr(trigger_length, length); - - if (CHAT_TYPE_NORMAL == type) - return sChatTypeTriggers[n].type; - else - break; - } - } - } - - return type; -} - -void LLFloaterIMNearbyChat::sendChat( EChatType type ) -{ - if (mInputEditor) - { - LLWString text = mInputEditor->getWText(); - LLWStringUtil::trim(text); - LLWStringUtil::replaceChar(text,182,'\n'); // Convert paragraph symbols back into newlines. - if (!text.empty()) - { - // Check if this is destined for another channel - S32 channel = 0; - stripChannelNumber(text, &channel); - - updateUsedEmojis(text); - - std::string utf8text = wstring_to_utf8str(text); - // Try to trigger a gesture, if not chat to a script. - std::string utf8_revised_text; - if (0 == channel) - { - // discard returned "found" boolean - if(!LLGestureMgr::instance().triggerAndReviseString(utf8text, &utf8_revised_text)) - { - utf8_revised_text = utf8text; - } - } - else - { - utf8_revised_text = utf8text; - } - - utf8_revised_text = utf8str_trim(utf8_revised_text); - - type = processChatTypeTriggers(type, utf8_revised_text); - - if (!utf8_revised_text.empty()) - { - // Chat with animation - sendChatFromViewer(utf8_revised_text, type, gSavedSettings.getBOOL("PlayChatAnim")); - } - } - - mInputEditor->setText(LLStringExplicit("")); - } - - gAgent.stopTyping(); - - // If the user wants to stop chatting on hitting return, lose focus - // and go out of chat mode. - if (gSavedSettings.getBOOL("CloseChatOnReturn")) - { - stopChat(); - } -} - -void LLFloaterIMNearbyChat::addMessage(const LLChat& chat,bool archive,const LLSD &args) -{ - appendMessage(chat, args); - - if(archive) - { - mMessageArchive.push_back(chat); - if(mMessageArchive.size() > 200) - { - mMessageArchive.erase(mMessageArchive.begin()); - } - } - - // logging - if (!args["do_not_log"].asBoolean() && gSavedPerAccountSettings.getS32("KeepConversationLogTranscripts") > 1) - { - std::string from_name = chat.mFromName; - - if (chat.mSourceType == CHAT_SOURCE_AGENT) - { - // if the chat is coming from an agent, log the complete name - LLAvatarName av_name; - LLAvatarNameCache::get(chat.mFromID, &av_name); - - if (!av_name.isDisplayNameDefault()) - { - from_name = av_name.getCompleteName(); - } - } - - LLLogChat::saveHistory("chat", from_name, chat.mFromID, chat.mText); - } -} - - -void LLFloaterIMNearbyChat::onChatBoxCommit() -{ - sendChat(CHAT_TYPE_NORMAL); - - gAgent.stopTyping(); -} - -void LLFloaterIMNearbyChat::displaySpeakingIndicator() -{ - LLSpeakerMgr::speaker_list_t speaker_list; - LLUUID id; - - id.setNull(); - mSpeakerMgr->update(false); - mSpeakerMgr->getSpeakerList(&speaker_list, false); - - for (LLSpeakerMgr::speaker_list_t::iterator i = speaker_list.begin(); i != speaker_list.end(); ++i) - { - LLPointer s = *i; - if (s->mSpeechVolume > 0 || s->mStatus == LLSpeaker::STATUS_SPEAKING) - { - id = s->mID; - break; - } - } -} - -void LLFloaterIMNearbyChat::sendChatFromViewer(const std::string &utf8text, EChatType type, bool animate) -{ - sendChatFromViewer(utf8str_to_wstring(utf8text), type, animate); -} - -void LLFloaterIMNearbyChat::sendChatFromViewer(const LLWString &wtext, EChatType type, bool animate) -{ - // Look for "/20 foo" channel chats. - S32 channel = 0; - LLWString out_text = stripChannelNumber(wtext, &channel); - std::string utf8_out_text = wstring_to_utf8str(out_text); - std::string utf8_text = wstring_to_utf8str(wtext); - - utf8_text = utf8str_trim(utf8_text); - if (!utf8_text.empty()) - { - utf8_text = utf8str_truncate(utf8_text, MAX_STRING - 1); - } - - // Don't animate for chats people can't hear (chat to scripts) - if (animate && (channel == 0)) - { - if (type == CHAT_TYPE_WHISPER) - { - LL_DEBUGS() << "You whisper " << utf8_text << LL_ENDL; - gAgent.sendAnimationRequest(ANIM_AGENT_WHISPER, ANIM_REQUEST_START); - } - else if (type == CHAT_TYPE_NORMAL) - { - LL_DEBUGS() << "You say " << utf8_text << LL_ENDL; - gAgent.sendAnimationRequest(ANIM_AGENT_TALK, ANIM_REQUEST_START); - } - else if (type == CHAT_TYPE_SHOUT) - { - LL_DEBUGS() << "You shout " << utf8_text << LL_ENDL; - gAgent.sendAnimationRequest(ANIM_AGENT_SHOUT, ANIM_REQUEST_START); - } - else - { - LL_INFOS() << "send_chat_from_viewer() - invalid volume" << LL_ENDL; - return; - } - } - else - { - if (type != CHAT_TYPE_START && type != CHAT_TYPE_STOP) - { - LL_DEBUGS() << "Channel chat: " << utf8_text << LL_ENDL; - } - } - - send_chat_from_viewer(utf8_out_text, type, channel); -} - -// static -bool LLFloaterIMNearbyChat::isWordsName(const std::string& name) -{ - // checking to see if it's display name plus username in parentheses - S32 open_paren = name.find(" (", 0); - S32 close_paren = name.find(')', 0); - - if (open_paren != std::string::npos && - close_paren == name.length()-1) - { - return true; - } - else - { - //checking for a single space - S32 pos = name.find(' ', 0); - return std::string::npos != pos && name.rfind(' ', name.length()) == pos && 0 != pos && name.length()-1 != pos; - } -} - -// static -void LLFloaterIMNearbyChat::startChat(const char* line) -{ - LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); - if (nearby_chat) - { - if(!nearby_chat->isTornOff()) - { - LLFloaterIMContainer::getInstance()->selectConversation(LLUUID(NULL)); - } - if(nearby_chat->isMinimized()) - { - nearby_chat->setMinimized(false); - } - nearby_chat->show(); - nearby_chat->setFocus(true); - - if (line) - { - std::string line_string(line); - nearby_chat->mInputEditor->setText(line_string); - } - - nearby_chat->mInputEditor->endOfDoc(); - } -} - -// Exit "chat mode" and do the appropriate focus changes -// static -void LLFloaterIMNearbyChat::stopChat() -{ - LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); - if (nearby_chat) - { - nearby_chat->mInputEditor->setFocus(false); - gAgent.stopTyping(); - } -} - -// If input of the form "/20foo" or "/20 foo", returns "foo" and channel 20. -// Otherwise returns input and channel 0. -LLWString LLFloaterIMNearbyChat::stripChannelNumber(const LLWString &mesg, S32* channel) -{ - if (mesg[0] == '/' - && mesg[1] == '/') - { - // This is a "repeat channel send" - *channel = sLastSpecialChatChannel; - return mesg.substr(2, mesg.length() - 2); - } - else if (mesg[0] == '/' - && mesg[1] - && (LLStringOps::isDigit(mesg[1]) - || (mesg[1] == '-' && mesg[2] && LLStringOps::isDigit(mesg[2])))) - { - // This a special "/20" speak on a channel - S32 pos = 0; - - // Copy the channel number into a string - LLWString channel_string; - llwchar c; - do - { - c = mesg[pos+1]; - channel_string.push_back(c); - pos++; - } - while(c && pos < 64 && (LLStringOps::isDigit(c) || (pos==1 && c =='-'))); - - // Move the pointer forward to the first non-whitespace char - // Check isspace before looping, so we can handle "/33foo" - // as well as "/33 foo" - while(c && iswspace(c)) - { - c = mesg[pos+1]; - pos++; - } - - sLastSpecialChatChannel = strtol(wstring_to_utf8str(channel_string).c_str(), NULL, 10); - *channel = sLastSpecialChatChannel; - return mesg.substr(pos, mesg.length() - pos); - } - else - { - // This is normal chat. - *channel = 0; - return mesg; - } -} - -void send_chat_from_viewer(const std::string& utf8_out_text, EChatType type, S32 channel) -{ - LL_DEBUGS("UIUsage") << "Nearby chat, text " << utf8_out_text << " type " << type << " channel " << channel << LL_ENDL; - if (type != CHAT_TYPE_START && type != CHAT_TYPE_STOP) // prune back some redundant logging - { - LLUIUsage::instance().logCommand("Chat.SendNearby"); // pseuo-command - } - - LLMessageSystem* msg = gMessageSystem; - - if (channel >= 0) - { - msg->newMessageFast(_PREHASH_ChatFromViewer); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ChatData); - msg->addStringFast(_PREHASH_Message, utf8_out_text); - msg->addU8Fast(_PREHASH_Type, type); - msg->addS32("Channel", channel); - - } - else - { - // Hack: ChatFromViewer doesn't allow negative channels - msg->newMessage("ScriptDialogReply"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgentID); - msg->addUUID("SessionID", gAgentSessionID); - msg->nextBlock("Data"); - msg->addUUID("ObjectID", gAgentID); - msg->addS32("ChatChannel", channel); - msg->addS32("ButtonIndex", 0); - msg->addString("ButtonLabel", utf8_out_text); - } - - gAgent.sendReliableMessage(); - add(LLStatViewer::CHAT_COUNT, 1); -} - -class LLChatCommandHandler : public LLCommandHandler -{ -public: - // not allowed from outside the app - LLChatCommandHandler() : LLCommandHandler("chat", UNTRUSTED_BLOCK) { } - - // Your code here - bool handle(const LLSD& tokens, - const LLSD& query_map, - const std::string& grid, - LLMediaCtrl* web) - { - bool retval = false; - // Need at least 2 tokens to have a valid message. - if (tokens.size() < 2) - { - retval = false; - } - else - { - S32 channel = tokens[0].asInteger(); - // VWR-19499 Restrict function to chat channels greater than 0. - if ((channel > 0) && (channel < CHAT_CHANNEL_DEBUG)) - { - retval = true; - // Send unescaped message, see EXT-6353. - std::string unescaped_mesg (LLURI::unescape(tokens[1].asString())); - send_chat_from_viewer(unescaped_mesg, CHAT_TYPE_NORMAL, channel); - } - else - { - retval = false; - // Tell us this is an unsupported SLurl. - } - } - return retval; - } -}; - -// Creating the object registers with the dispatcher. -LLChatCommandHandler gChatHandler; +/** + * @file LLFloaterIMNearbyChat.cpp + * @brief LLFloaterIMNearbyChat class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "message.h" + +#include "lliconctrl.h" +#include "llappviewer.h" +#include "llchatentry.h" +#include "llfloaterreg.h" +#include "lltrans.h" +#include "llfloaterimcontainer.h" +#include "llfloatersidepanelcontainer.h" +#include "llfocusmgr.h" +#include "lllogchat.h" +#include "llresizebar.h" +#include "llresizehandle.h" +#include "lldraghandle.h" +#include "llmenugl.h" +#include "llviewermenu.h" // for gMenuHolder +#include "llfloaterimnearbychathandler.h" +#include "llchannelmanager.h" +#include "llchathistory.h" +#include "llstylemap.h" +#include "llavatarnamecache.h" +#include "llfloaterreg.h" +#include "lltrans.h" + +#include "llfirstuse.h" +#include "llfloaterimnearbychat.h" +#include "llagent.h" // gAgent +#include "llgesturemgr.h" +#include "llmultigesture.h" +#include "llkeyboard.h" +#include "llanimationstates.h" +#include "llviewerstats.h" +#include "llcommandhandler.h" +#include "llviewercontrol.h" +#include "llnavigationbar.h" +#include "llwindow.h" +#include "llviewerwindow.h" +#include "llrootview.h" +#include "llviewerchat.h" +#include "lltranslate.h" +#include "llautoreplace.h" +#include "lluiusage.h" + +S32 LLFloaterIMNearbyChat::sLastSpecialChatChannel = 0; + +constexpr S32 EXPANDED_HEIGHT = 266; +constexpr S32 COLLAPSED_HEIGHT = 60; +constexpr S32 EXPANDED_MIN_HEIGHT = 150; + +// legacy callback glue +void send_chat_from_viewer(const std::string& utf8_out_text, EChatType type, S32 channel); + +struct LLChatTypeTrigger { + std::string name; + EChatType type; +}; + +static LLChatTypeTrigger sChatTypeTriggers[] = { + { "/whisper" , CHAT_TYPE_WHISPER}, + { "/shout" , CHAT_TYPE_SHOUT} +}; + +bool cb_do_nothing() +{ + return false; +} + +LLFloaterIMNearbyChat::LLFloaterIMNearbyChat(const LLSD& llsd) +: LLFloaterIMSessionTab(LLSD(LLUUID::null)), + //mOutputMonitor(NULL), + mSpeakerMgr(NULL), + mExpandedHeight(COLLAPSED_HEIGHT + EXPANDED_HEIGHT) +{ + mIsP2PChat = false; + mIsNearbyChat = true; + mSpeakerMgr = LLLocalSpeakerMgr::getInstance(); + + // Required by LLFloaterIMSessionTab::mGearBtn + // But nearby floater has no 'per agent' menu items, + mEnableCallbackRegistrar.add("Avatar.EnableGearItem", boost::bind(&cb_do_nothing)); + mCommitCallbackRegistrar.add("Avatar.GearDoToSelected", boost::bind(&cb_do_nothing)); + mEnableCallbackRegistrar.add("Avatar.CheckGearItem", boost::bind(&cb_do_nothing)); + + mMinFloaterHeight = EXPANDED_MIN_HEIGHT; +} + +//static +LLFloaterIMNearbyChat* LLFloaterIMNearbyChat::buildFloater(const LLSD& key) +{ + LLFloaterReg::getInstance("im_container"); + return new LLFloaterIMNearbyChat(key); +} + +//virtual +bool LLFloaterIMNearbyChat::postBuild() +{ + setIsSingleInstance(true); + bool result = LLFloaterIMSessionTab::postBuild(); + + mInputEditor->setAutoreplaceCallback(boost::bind(&LLAutoReplace::autoreplaceCallback, LLAutoReplace::getInstance(), _1, _2, _3, _4, _5)); + mInputEditor->setCommitCallback(boost::bind(&LLFloaterIMNearbyChat::onChatBoxCommit, this)); + mInputEditor->setKeystrokeCallback(boost::bind(&LLFloaterIMNearbyChat::onChatBoxKeystroke, this)); + mInputEditor->setFocusLostCallback(boost::bind(&LLFloaterIMNearbyChat::onChatBoxFocusLost, this)); + mInputEditor->setFocusReceivedCallback(boost::bind(&LLFloaterIMNearbyChat::onChatBoxFocusReceived, this)); + std::string nearbyChatTitle(LLTrans::getString("NearbyChatTitle")); + mInputEditor->setLabel(nearbyChatTitle); + + // Title must be defined BEFORE call to addConversationListItem() because + // it is used to show the item's name in the conversations list + setTitle(nearbyChatTitle); + + // obsolete, but may be needed for backward compatibility? + gSavedSettings.declareS32("nearbychat_showicons_and_names", 2, "NearByChat header settings", LLControlVariable::PERSIST_NONDFT); + + if (gSavedPerAccountSettings.getBOOL("LogShowHistory")) + { + loadHistory(); + } + + return result; +} + +// virtual +void LLFloaterIMNearbyChat::closeHostedFloater() +{ + // If detached from conversations window close anyway + if (!getHost()) + { + setVisible(false); + } + + // Should check how many conversations are ongoing. Select next to "Nearby Chat" in case there are some other besides. + // Close conversations window in case "Nearby Chat" is attached and the only conversation + LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); + if (floater_container->getConversationListItemSize() == 1) + { + if (getHost()) + { + floater_container->closeFloater(); + } + } + else + { + if (!getHost()) + { + floater_container->selectNextConversationByID(LLUUID()); + } + } +} + +// virtual +void LLFloaterIMNearbyChat::refresh() +{ + displaySpeakingIndicator(); + updateCallBtnState(LLVoiceClient::getInstance()->getUserPTTState()); + + // *HACK: Update transparency type depending on whether our children have focus. + // This is needed because this floater is chrome and thus cannot accept focus, so + // the transparency type setting code from LLFloater::setFocus() isn't reached. + if (getTransparencyType() != TT_DEFAULT) + { + setTransparencyType(hasFocus() ? TT_ACTIVE : TT_INACTIVE); + } +} + +void LLFloaterIMNearbyChat::reloadMessages(bool clean_messages/* = false*/) +{ + if (clean_messages) + { + mMessageArchive.clear(); + loadHistory(); + } + + mChatHistory->clear(); + + LLSD do_not_log; + do_not_log["do_not_log"] = true; + for(std::vector::iterator it = mMessageArchive.begin();it!=mMessageArchive.end();++it) + { + // Update the messages without re-writing them to a log file. + addMessage(*it,false, do_not_log); + } +} + +void LLFloaterIMNearbyChat::loadHistory() +{ + LLSD do_not_log; + do_not_log["do_not_log"] = true; + + std::list history; + LLLogChat::loadChatHistory("chat", history); + + std::list::const_iterator it = history.begin(); + while (it != history.end()) + { + const LLSD& msg = *it; + + std::string from = msg[LL_IM_FROM]; + LLUUID from_id; + if (msg[LL_IM_FROM_ID].isDefined()) + { + from_id = msg[LL_IM_FROM_ID].asUUID(); + } + else + { + std::string legacy_name = gCacheName->buildLegacyName(from); + from_id = LLAvatarNameCache::getInstance()->findIdByName(legacy_name); + } + + LLChat chat; + chat.mFromName = from; + chat.mFromID = from_id; + chat.mText = msg[LL_IM_TEXT].asString(); + chat.mTimeStr = msg[LL_IM_TIME].asString(); + chat.mChatStyle = CHAT_STYLE_HISTORY; + + chat.mSourceType = CHAT_SOURCE_AGENT; + if (from_id.isNull() && SYSTEM_FROM == from) + { + chat.mSourceType = CHAT_SOURCE_SYSTEM; + + } + else if (from_id.isNull()) + { + chat.mSourceType = isWordsName(from) ? CHAT_SOURCE_UNKNOWN : CHAT_SOURCE_OBJECT; + } + + addMessage(chat, true, do_not_log); + + it++; + } +} + +void LLFloaterIMNearbyChat::removeScreenChat() +{ + LLNotificationsUI::LLScreenChannelBase* chat_channel = LLNotificationsUI::LLChannelManager::getInstance()->findChannelByID( + LLNotificationsUI::NEARBY_CHAT_CHANNEL_UUID); + if(chat_channel) + { + chat_channel->removeToastsFromChannel(); + } +} + + +void LLFloaterIMNearbyChat::setVisible(bool visible) +{ + LLFloaterIMSessionTab::setVisible(visible); + + if(visible) + { + removeScreenChat(); + } +} + + +void LLFloaterIMNearbyChat::setVisibleAndFrontmost(bool take_focus, const LLSD& key) +{ + LLFloaterIMSessionTab::setVisibleAndFrontmost(take_focus, key); + + if(matchesKey(key)) + { + LLFloaterIMContainer::getInstance()->selectConversationPair(mSessionID, true, take_focus); + } +} + +// virtual +void LLFloaterIMNearbyChat::onTearOffClicked() +{ + LLFloaterIMSessionTab::onTearOffClicked(); + + // see CHUI-170: Save torn-off state of the nearby chat between sessions + bool in_the_multifloater(getHost()); + gSavedPerAccountSettings.setBOOL("NearbyChatIsNotTornOff", in_the_multifloater); +} + + +// virtual +void LLFloaterIMNearbyChat::onOpen(const LLSD& key) +{ + LLFloaterIMSessionTab::onOpen(key); + if(!isMessagePaneExpanded()) + { + restoreFloater(); + onCollapseToLine(this); + } +} + +// virtual +void LLFloaterIMNearbyChat::onClose(bool app_quitting) +{ + // Override LLFloaterIMSessionTab::onClose() so that Nearby Chat is not removed from the conversation floater + LLFloaterIMSessionTab::restoreFloater(); + if (app_quitting) + { + // We are starting and closing floater in "expanded" state + // Update expanded (restored) rect and position for use in next session + forceReshape(); + storeRectControl(); + } +} + +// virtual +void LLFloaterIMNearbyChat::onClickCloseBtn(bool) + +{ + if (!isTornOff()) + { + return; + } + closeHostedFloater(); +} + +void LLFloaterIMNearbyChat::onChatFontChange(LLFontGL* fontp) +{ + // Update things with the new font whohoo + if (mInputEditor) + { + mInputEditor->setFont(fontp); + } +} + + +void LLFloaterIMNearbyChat::show() +{ + openFloater(getKey()); +} + +bool LLFloaterIMNearbyChat::isChatVisible() const +{ + bool isVisible = false; + LLFloaterIMContainer* im_box = LLFloaterIMContainer::getInstance(); + // Is the IM floater container ever null? + llassert(im_box != NULL); + if (im_box != NULL) + { + isVisible = + isChatMultiTab() && gSavedPerAccountSettings.getBOOL("NearbyChatIsNotTornOff")? + im_box->getVisible() && !im_box->isMinimized() : + getVisible() && !isMinimized(); + } + + return isVisible; +} + +void LLFloaterIMNearbyChat::showHistory() +{ + openFloater(); + LLFloaterIMContainer::getInstance()->selectConversation(LLUUID(NULL)); + + if(!isMessagePaneExpanded()) + { + restoreFloater(); + setFocus(true); + } + else + { + LLFloaterIMContainer::getInstance()->setFocus(true); + } + setResizeLimits(getMinWidth(), EXPANDED_MIN_HEIGHT); +} + +std::string LLFloaterIMNearbyChat::getCurrentChat() +{ + return mInputEditor ? mInputEditor->getText() : LLStringUtil::null; +} + +// virtual +bool LLFloaterIMNearbyChat::handleKeyHere( KEY key, MASK mask ) +{ + bool handled = false; + + if( KEY_RETURN == key && mask == MASK_CONTROL) + { + // shout + sendChat(CHAT_TYPE_SHOUT); + handled = true; + } + else if (KEY_RETURN == key && mask == MASK_SHIFT) + { + // whisper + sendChat(CHAT_TYPE_WHISPER); + handled = true; + } + + + if((mask == MASK_ALT) && isTornOff()) + { + LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); + if ((KEY_UP == key) || (KEY_LEFT == key)) + { + floater_container->selectNextorPreviousConversation(false); + handled = true; + } + if ((KEY_DOWN == key ) || (KEY_RIGHT == key)) + { + floater_container->selectNextorPreviousConversation(true); + handled = true; + } + } + + return handled; +} + +bool LLFloaterIMNearbyChat::matchChatTypeTrigger(const std::string& in_str, std::string* out_str) +{ + U32 in_len = in_str.length(); + S32 cnt = sizeof(sChatTypeTriggers) / sizeof(*sChatTypeTriggers); + + bool string_was_found = false; + + for (S32 n = 0; n < cnt && !string_was_found; n++) + { + if (in_len <= sChatTypeTriggers[n].name.length()) + { + std::string trigger_trunc = sChatTypeTriggers[n].name; + LLStringUtil::truncate(trigger_trunc, in_len); + + if (!LLStringUtil::compareInsensitive(in_str, trigger_trunc)) + { + *out_str = sChatTypeTriggers[n].name; + string_was_found = true; + } + } + } + + return string_was_found; +} + +void LLFloaterIMNearbyChat::onChatBoxKeystroke() +{ + LLFloaterIMContainer* im_box = LLFloaterIMContainer::findInstance(); + if (im_box) + { + im_box->flashConversationItemWidget(mSessionID,false); + } + + LLFirstUse::otherAvatarChatFirst(false); + + LLWString raw_text = mInputEditor->getWText(); + + // Can't trim the end, because that will cause autocompletion + // to eat trailing spaces that might be part of a gesture. + LLWStringUtil::trimHead(raw_text); + + S32 length = raw_text.length(); + + if( (length > 0) && (raw_text[0] != '/') ) // forward slash is used for escape (eg. emote) sequences + { + gAgent.startTyping(); + } + else + { + gAgent.stopTyping(); + } + + /* Doesn't work -- can't tell the difference between a backspace + that killed the selection vs. backspace at the end of line. + if (length > 1 + && text[0] == '/' + && key == KEY_BACKSPACE) + { + // the selection will already be deleted, but we need to trim + // off the character before + std::string new_text = raw_text.substr(0, length-1); + mInputEditor->setText( new_text ); + mInputEditor->setCursorToEnd(); + length = length - 1; + } + */ + + KEY key = gKeyboard->currentKey(); + + // Ignore "special" keys, like backspace, arrows, etc. + if (gSavedSettings.getBOOL("ChatAutocompleteGestures") + && length > 1 + && raw_text[0] == '/' + && key < KEY_SPECIAL) + { + // we're starting a gesture, attempt to autocomplete + + std::string utf8_trigger = wstring_to_utf8str(raw_text); + std::string utf8_out_str(utf8_trigger); + + if (LLGestureMgr::instance().matchPrefix(utf8_trigger, &utf8_out_str)) + { + std::string rest_of_match = utf8_out_str.substr(utf8_trigger.size()); + if (!rest_of_match.empty()) + { + mInputEditor->setText(utf8_trigger + rest_of_match); // keep original capitalization for user-entered part + // Select to end of line, starting from the character + // after the last one the user typed. + mInputEditor->selectByCursorPosition(utf8_out_str.size()-rest_of_match.size(),utf8_out_str.size()); + } + + } + else if (matchChatTypeTrigger(utf8_trigger, &utf8_out_str)) + { + std::string rest_of_match = utf8_out_str.substr(utf8_trigger.size()); + mInputEditor->setText(utf8_trigger + rest_of_match + " "); // keep original capitalization for user-entered part + mInputEditor->endOfDoc(); + } + + //LL_INFOS() << "GESTUREDEBUG " << trigger + // << " len " << length + // << " outlen " << out_str.getLength() + // << LL_ENDL; + } +} + +// static +void LLFloaterIMNearbyChat::onChatBoxFocusLost() +{ + // stop typing animation + gAgent.stopTyping(); +} + +void LLFloaterIMNearbyChat::onChatBoxFocusReceived() +{ + mInputEditor->setEnabled(!gDisconnected); +} + +EChatType LLFloaterIMNearbyChat::processChatTypeTriggers(EChatType type, std::string &str) +{ + U32 length = str.length(); + S32 cnt = sizeof(sChatTypeTriggers) / sizeof(*sChatTypeTriggers); + + for (S32 n = 0; n < cnt; n++) + { + if (length >= sChatTypeTriggers[n].name.length()) + { + std::string trigger = str.substr(0, sChatTypeTriggers[n].name.length()); + + if (!LLStringUtil::compareInsensitive(trigger, sChatTypeTriggers[n].name)) + { + U32 trigger_length = sChatTypeTriggers[n].name.length(); + + // It's to remove space after trigger name + if (length > trigger_length && str[trigger_length] == ' ') + trigger_length++; + + str = str.substr(trigger_length, length); + + if (CHAT_TYPE_NORMAL == type) + return sChatTypeTriggers[n].type; + else + break; + } + } + } + + return type; +} + +void LLFloaterIMNearbyChat::sendChat( EChatType type ) +{ + if (mInputEditor) + { + LLWString text = mInputEditor->getWText(); + LLWStringUtil::trim(text); + LLWStringUtil::replaceChar(text,182,'\n'); // Convert paragraph symbols back into newlines. + if (!text.empty()) + { + // Check if this is destined for another channel + S32 channel = 0; + stripChannelNumber(text, &channel); + + updateUsedEmojis(text); + + std::string utf8text = wstring_to_utf8str(text); + // Try to trigger a gesture, if not chat to a script. + std::string utf8_revised_text; + if (0 == channel) + { + // discard returned "found" boolean + if(!LLGestureMgr::instance().triggerAndReviseString(utf8text, &utf8_revised_text)) + { + utf8_revised_text = utf8text; + } + } + else + { + utf8_revised_text = utf8text; + } + + utf8_revised_text = utf8str_trim(utf8_revised_text); + + type = processChatTypeTriggers(type, utf8_revised_text); + + if (!utf8_revised_text.empty()) + { + // Chat with animation + sendChatFromViewer(utf8_revised_text, type, gSavedSettings.getBOOL("PlayChatAnim")); + } + } + + mInputEditor->setText(LLStringExplicit("")); + } + + gAgent.stopTyping(); + + // If the user wants to stop chatting on hitting return, lose focus + // and go out of chat mode. + if (gSavedSettings.getBOOL("CloseChatOnReturn")) + { + stopChat(); + } +} + +void LLFloaterIMNearbyChat::addMessage(const LLChat& chat,bool archive,const LLSD &args) +{ + appendMessage(chat, args); + + if(archive) + { + mMessageArchive.push_back(chat); + if(mMessageArchive.size() > 200) + { + mMessageArchive.erase(mMessageArchive.begin()); + } + } + + // logging + if (!args["do_not_log"].asBoolean() && gSavedPerAccountSettings.getS32("KeepConversationLogTranscripts") > 1) + { + std::string from_name = chat.mFromName; + + if (chat.mSourceType == CHAT_SOURCE_AGENT) + { + // if the chat is coming from an agent, log the complete name + LLAvatarName av_name; + LLAvatarNameCache::get(chat.mFromID, &av_name); + + if (!av_name.isDisplayNameDefault()) + { + from_name = av_name.getCompleteName(); + } + } + + LLLogChat::saveHistory("chat", from_name, chat.mFromID, chat.mText); + } +} + + +void LLFloaterIMNearbyChat::onChatBoxCommit() +{ + sendChat(CHAT_TYPE_NORMAL); + + gAgent.stopTyping(); +} + +void LLFloaterIMNearbyChat::displaySpeakingIndicator() +{ + LLSpeakerMgr::speaker_list_t speaker_list; + LLUUID id; + + id.setNull(); + mSpeakerMgr->update(false); + mSpeakerMgr->getSpeakerList(&speaker_list, false); + + for (LLSpeakerMgr::speaker_list_t::iterator i = speaker_list.begin(); i != speaker_list.end(); ++i) + { + LLPointer s = *i; + if (s->mSpeechVolume > 0 || s->mStatus == LLSpeaker::STATUS_SPEAKING) + { + id = s->mID; + break; + } + } +} + +void LLFloaterIMNearbyChat::sendChatFromViewer(const std::string &utf8text, EChatType type, bool animate) +{ + sendChatFromViewer(utf8str_to_wstring(utf8text), type, animate); +} + +void LLFloaterIMNearbyChat::sendChatFromViewer(const LLWString &wtext, EChatType type, bool animate) +{ + // Look for "/20 foo" channel chats. + S32 channel = 0; + LLWString out_text = stripChannelNumber(wtext, &channel); + std::string utf8_out_text = wstring_to_utf8str(out_text); + std::string utf8_text = wstring_to_utf8str(wtext); + + utf8_text = utf8str_trim(utf8_text); + if (!utf8_text.empty()) + { + utf8_text = utf8str_truncate(utf8_text, MAX_STRING - 1); + } + + // Don't animate for chats people can't hear (chat to scripts) + if (animate && (channel == 0)) + { + if (type == CHAT_TYPE_WHISPER) + { + LL_DEBUGS() << "You whisper " << utf8_text << LL_ENDL; + gAgent.sendAnimationRequest(ANIM_AGENT_WHISPER, ANIM_REQUEST_START); + } + else if (type == CHAT_TYPE_NORMAL) + { + LL_DEBUGS() << "You say " << utf8_text << LL_ENDL; + gAgent.sendAnimationRequest(ANIM_AGENT_TALK, ANIM_REQUEST_START); + } + else if (type == CHAT_TYPE_SHOUT) + { + LL_DEBUGS() << "You shout " << utf8_text << LL_ENDL; + gAgent.sendAnimationRequest(ANIM_AGENT_SHOUT, ANIM_REQUEST_START); + } + else + { + LL_INFOS() << "send_chat_from_viewer() - invalid volume" << LL_ENDL; + return; + } + } + else + { + if (type != CHAT_TYPE_START && type != CHAT_TYPE_STOP) + { + LL_DEBUGS() << "Channel chat: " << utf8_text << LL_ENDL; + } + } + + send_chat_from_viewer(utf8_out_text, type, channel); +} + +// static +bool LLFloaterIMNearbyChat::isWordsName(const std::string& name) +{ + // checking to see if it's display name plus username in parentheses + S32 open_paren = name.find(" (", 0); + S32 close_paren = name.find(')', 0); + + if (open_paren != std::string::npos && + close_paren == name.length()-1) + { + return true; + } + else + { + //checking for a single space + S32 pos = name.find(' ', 0); + return std::string::npos != pos && name.rfind(' ', name.length()) == pos && 0 != pos && name.length()-1 != pos; + } +} + +// static +void LLFloaterIMNearbyChat::startChat(const char* line) +{ + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); + if (nearby_chat) + { + if(!nearby_chat->isTornOff()) + { + LLFloaterIMContainer::getInstance()->selectConversation(LLUUID(NULL)); + } + if(nearby_chat->isMinimized()) + { + nearby_chat->setMinimized(false); + } + nearby_chat->show(); + nearby_chat->setFocus(true); + + if (line) + { + std::string line_string(line); + nearby_chat->mInputEditor->setText(line_string); + } + + nearby_chat->mInputEditor->endOfDoc(); + } +} + +// Exit "chat mode" and do the appropriate focus changes +// static +void LLFloaterIMNearbyChat::stopChat() +{ + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); + if (nearby_chat) + { + nearby_chat->mInputEditor->setFocus(false); + gAgent.stopTyping(); + } +} + +// If input of the form "/20foo" or "/20 foo", returns "foo" and channel 20. +// Otherwise returns input and channel 0. +LLWString LLFloaterIMNearbyChat::stripChannelNumber(const LLWString &mesg, S32* channel) +{ + if (mesg[0] == '/' + && mesg[1] == '/') + { + // This is a "repeat channel send" + *channel = sLastSpecialChatChannel; + return mesg.substr(2, mesg.length() - 2); + } + else if (mesg[0] == '/' + && mesg[1] + && (LLStringOps::isDigit(mesg[1]) + || (mesg[1] == '-' && mesg[2] && LLStringOps::isDigit(mesg[2])))) + { + // This a special "/20" speak on a channel + S32 pos = 0; + + // Copy the channel number into a string + LLWString channel_string; + llwchar c; + do + { + c = mesg[pos+1]; + channel_string.push_back(c); + pos++; + } + while(c && pos < 64 && (LLStringOps::isDigit(c) || (pos==1 && c =='-'))); + + // Move the pointer forward to the first non-whitespace char + // Check isspace before looping, so we can handle "/33foo" + // as well as "/33 foo" + while(c && iswspace(c)) + { + c = mesg[pos+1]; + pos++; + } + + sLastSpecialChatChannel = strtol(wstring_to_utf8str(channel_string).c_str(), NULL, 10); + *channel = sLastSpecialChatChannel; + return mesg.substr(pos, mesg.length() - pos); + } + else + { + // This is normal chat. + *channel = 0; + return mesg; + } +} + +void send_chat_from_viewer(const std::string& utf8_out_text, EChatType type, S32 channel) +{ + LL_DEBUGS("UIUsage") << "Nearby chat, text " << utf8_out_text << " type " << type << " channel " << channel << LL_ENDL; + if (type != CHAT_TYPE_START && type != CHAT_TYPE_STOP) // prune back some redundant logging + { + LLUIUsage::instance().logCommand("Chat.SendNearby"); // pseuo-command + } + + LLMessageSystem* msg = gMessageSystem; + + if (channel >= 0) + { + msg->newMessageFast(_PREHASH_ChatFromViewer); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ChatData); + msg->addStringFast(_PREHASH_Message, utf8_out_text); + msg->addU8Fast(_PREHASH_Type, type); + msg->addS32("Channel", channel); + + } + else + { + // Hack: ChatFromViewer doesn't allow negative channels + msg->newMessage("ScriptDialogReply"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgentID); + msg->addUUID("SessionID", gAgentSessionID); + msg->nextBlock("Data"); + msg->addUUID("ObjectID", gAgentID); + msg->addS32("ChatChannel", channel); + msg->addS32("ButtonIndex", 0); + msg->addString("ButtonLabel", utf8_out_text); + } + + gAgent.sendReliableMessage(); + add(LLStatViewer::CHAT_COUNT, 1); +} + +class LLChatCommandHandler : public LLCommandHandler +{ +public: + // not allowed from outside the app + LLChatCommandHandler() : LLCommandHandler("chat", UNTRUSTED_BLOCK) { } + + // Your code here + bool handle(const LLSD& tokens, + const LLSD& query_map, + const std::string& grid, + LLMediaCtrl* web) + { + bool retval = false; + // Need at least 2 tokens to have a valid message. + if (tokens.size() < 2) + { + retval = false; + } + else + { + S32 channel = tokens[0].asInteger(); + // VWR-19499 Restrict function to chat channels greater than 0. + if ((channel > 0) && (channel < CHAT_CHANNEL_DEBUG)) + { + retval = true; + // Send unescaped message, see EXT-6353. + std::string unescaped_mesg (LLURI::unescape(tokens[1].asString())); + send_chat_from_viewer(unescaped_mesg, CHAT_TYPE_NORMAL, channel); + } + else + { + retval = false; + // Tell us this is an unsupported SLurl. + } + } + return retval; + } +}; + +// Creating the object registers with the dispatcher. +LLChatCommandHandler gChatHandler; diff --git a/indra/newview/llfloaterimnearbychat.h b/indra/newview/llfloaterimnearbychat.h index 0cb546e792..c5fca94169 100644 --- a/indra/newview/llfloaterimnearbychat.h +++ b/indra/newview/llfloaterimnearbychat.h @@ -1,119 +1,119 @@ -/** - * @file llfloaterimnearbychat.h - * @brief LLFloaterIMNearbyChat class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERIMNEARBYCHAT_H -#define LL_LLFLOATERIMNEARBYCHAT_H - -#include "llfloaterimsessiontab.h" -#include "llcombobox.h" -#include "llgesturemgr.h" -#include "llchat.h" -#include "llvoiceclient.h" -#include "lloutputmonitorctrl.h" -#include "llspeakers.h" -#include "llscrollbar.h" -#include "llviewerchat.h" -#include "llpanel.h" - -class LLResizeBar; - -class LLFloaterIMNearbyChat - : public LLFloaterIMSessionTab -{ -public: - // constructor for inline chat-bars (e.g. hosted in chat history window) - LLFloaterIMNearbyChat(const LLSD& key = LLSD(LLUUID())); - ~LLFloaterIMNearbyChat() {} - - static LLFloaterIMNearbyChat* buildFloater(const LLSD& key); - - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); - /*virtual*/ void onClose(bool app_quitting); - /*virtual*/ void setVisible(bool visible); - /*virtual*/ void setVisibleAndFrontmost(bool take_focus=true, const LLSD& key = LLSD()); - /*virtual*/ void closeHostedFloater(); - - void loadHistory(); - void reloadMessages(bool clean_messages = false); - void removeScreenChat(); - - void show(); - bool isChatVisible() const; - - /** @param archive true - to save a message to the chat history log */ - void addMessage (const LLChat& message,bool archive = true, const LLSD &args = LLSD()); - - LLChatEntry* getChatBox() { return mInputEditor; } - - std::string getCurrentChat(); - S32 getMessageArchiveLength() {return mMessageArchive.size();} - - virtual bool handleKeyHere( KEY key, MASK mask ); - - static void startChat(const char* line); - static void stopChat(); - - static void sendChatFromViewer(const std::string &utf8text, EChatType type, bool animate); - static void sendChatFromViewer(const LLWString &wtext, EChatType type, bool animate); - - static bool isWordsName(const std::string& name); - - void showHistory(); - -protected: - static bool matchChatTypeTrigger(const std::string& in_str, std::string* out_str); - void onChatBoxKeystroke(); - void onChatBoxFocusLost(); - void onChatBoxFocusReceived(); - - void sendChat( EChatType type ); - void onChatBoxCommit(); - void onChatFontChange(LLFontGL* fontp); - - /*virtual*/ void onTearOffClicked(); - /*virtual*/ void onClickCloseBtn(bool app_qutting = false); - - static LLWString stripChannelNumber(const LLWString &mesg, S32* channel); - EChatType processChatTypeTriggers(EChatType type, std::string &str); - - void displaySpeakingIndicator(); - - // Which non-zero channel did we last chat on? - static S32 sLastSpecialChatChannel; - - LLOutputMonitorCtrl* mOutputMonitor; - LLLocalSpeakerMgr* mSpeakerMgr; - - S32 mExpandedHeight; - -private: - /*virtual*/ void refresh(); - - std::vector mMessageArchive; -}; - -#endif // LL_LLFLOATERIMNEARBYCHAT_H +/** + * @file llfloaterimnearbychat.h + * @brief LLFloaterIMNearbyChat class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERIMNEARBYCHAT_H +#define LL_LLFLOATERIMNEARBYCHAT_H + +#include "llfloaterimsessiontab.h" +#include "llcombobox.h" +#include "llgesturemgr.h" +#include "llchat.h" +#include "llvoiceclient.h" +#include "lloutputmonitorctrl.h" +#include "llspeakers.h" +#include "llscrollbar.h" +#include "llviewerchat.h" +#include "llpanel.h" + +class LLResizeBar; + +class LLFloaterIMNearbyChat + : public LLFloaterIMSessionTab +{ +public: + // constructor for inline chat-bars (e.g. hosted in chat history window) + LLFloaterIMNearbyChat(const LLSD& key = LLSD(LLUUID())); + ~LLFloaterIMNearbyChat() {} + + static LLFloaterIMNearbyChat* buildFloater(const LLSD& key); + + /*virtual*/ bool postBuild(); + /*virtual*/ void onOpen(const LLSD& key); + /*virtual*/ void onClose(bool app_quitting); + /*virtual*/ void setVisible(bool visible); + /*virtual*/ void setVisibleAndFrontmost(bool take_focus=true, const LLSD& key = LLSD()); + /*virtual*/ void closeHostedFloater(); + + void loadHistory(); + void reloadMessages(bool clean_messages = false); + void removeScreenChat(); + + void show(); + bool isChatVisible() const; + + /** @param archive true - to save a message to the chat history log */ + void addMessage (const LLChat& message,bool archive = true, const LLSD &args = LLSD()); + + LLChatEntry* getChatBox() { return mInputEditor; } + + std::string getCurrentChat(); + S32 getMessageArchiveLength() {return mMessageArchive.size();} + + virtual bool handleKeyHere( KEY key, MASK mask ); + + static void startChat(const char* line); + static void stopChat(); + + static void sendChatFromViewer(const std::string &utf8text, EChatType type, bool animate); + static void sendChatFromViewer(const LLWString &wtext, EChatType type, bool animate); + + static bool isWordsName(const std::string& name); + + void showHistory(); + +protected: + static bool matchChatTypeTrigger(const std::string& in_str, std::string* out_str); + void onChatBoxKeystroke(); + void onChatBoxFocusLost(); + void onChatBoxFocusReceived(); + + void sendChat( EChatType type ); + void onChatBoxCommit(); + void onChatFontChange(LLFontGL* fontp); + + /*virtual*/ void onTearOffClicked(); + /*virtual*/ void onClickCloseBtn(bool app_qutting = false); + + static LLWString stripChannelNumber(const LLWString &mesg, S32* channel); + EChatType processChatTypeTriggers(EChatType type, std::string &str); + + void displaySpeakingIndicator(); + + // Which non-zero channel did we last chat on? + static S32 sLastSpecialChatChannel; + + LLOutputMonitorCtrl* mOutputMonitor; + LLLocalSpeakerMgr* mSpeakerMgr; + + S32 mExpandedHeight; + +private: + /*virtual*/ void refresh(); + + std::vector mMessageArchive; +}; + +#endif // LL_LLFLOATERIMNEARBYCHAT_H diff --git a/indra/newview/llfloaterimnearbychathandler.cpp b/indra/newview/llfloaterimnearbychathandler.cpp index 211074d96c..ef7ec9e950 100644 --- a/indra/newview/llfloaterimnearbychathandler.cpp +++ b/indra/newview/llfloaterimnearbychathandler.cpp @@ -1,678 +1,678 @@ -/** - * @file LLFloaterIMNearbyChatHandler.cpp - * @brief Nearby chat chat managment - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llagentdata.h" // for gAgentID -#include "llfloaterimnearbychathandler.h" - -#include "llchatitemscontainerctrl.h" -#include "llfirstuse.h" -#include "llfloaterscriptdebug.h" -#include "llhints.h" -#include "llfloaterimnearbychat.h" -#include "llrecentpeople.h" - -#include "llviewercontrol.h" - -#include "llfloaterreg.h"//for LLFloaterReg::getTypedInstance -#include "llviewerwindow.h"//for screen channel position -#include "llfloaterimnearbychat.h" -#include "llfloaterimcontainer.h" -#include "llrootview.h" -#include "lllayoutstack.h" - -//add LLFloaterIMNearbyChatHandler to LLNotificationsUI namespace -using namespace LLNotificationsUI; - -static LLFloaterIMNearbyChatToastPanel* createToastPanel() -{ - LLFloaterIMNearbyChatToastPanel* item = LLFloaterIMNearbyChatToastPanel::createInstance(); - return item; -} - - -//----------------------------------------------------------------------------------------------- -//LLFloaterIMNearbyChatScreenChannel -//----------------------------------------------------------------------------------------------- - -class LLFloaterIMNearbyChatScreenChannel: public LLScreenChannelBase -{ - LOG_CLASS(LLFloaterIMNearbyChatScreenChannel); -public: - typedef std::vector > toast_vec_t; - typedef std::list > toast_list_t; - - LLFloaterIMNearbyChatScreenChannel(const Params& p) - : LLScreenChannelBase(p) - { - mStopProcessing = false; - - LLControlVariable* ctrl = gSavedSettings.getControl("NearbyToastLifeTime").get(); - if (ctrl) - { - ctrl->getSignal()->connect(boost::bind(&LLFloaterIMNearbyChatScreenChannel::updateToastsLifetime, this)); - } - - ctrl = gSavedSettings.getControl("NearbyToastFadingTime").get(); - if (ctrl) - { - ctrl->getSignal()->connect(boost::bind(&LLFloaterIMNearbyChatScreenChannel::updateToastFadingTime, this)); - } - } - - void addChat (LLSD& chat); - void arrangeToasts (); - - typedef boost::function create_toast_panel_callback_t; - void setCreatePanelCallback(create_toast_panel_callback_t value) { m_create_toast_panel_callback_t = value;} - - void onToastDestroyed (LLToast* toast, bool app_quitting); - void onToastFade (LLToast* toast); - - void redrawToasts() - { - arrangeToasts(); - } - - // hide all toasts from screen, but not remove them from a channel - // removes all toasts from a channel - virtual void removeToastsFromChannel() - { - for(toast_vec_t::iterator it = m_active_toasts.begin(); it != m_active_toasts.end(); ++it) - { - addToToastPool(it->get()); - } - m_active_toasts.clear(); - }; - - virtual void deleteAllChildren() - { - LL_DEBUGS("NearbyChat") << "Clearing toast pool" << LL_ENDL; - m_toast_pool.clear(); - m_active_toasts.clear(); - LLScreenChannelBase::deleteAllChildren(); - } - -protected: - void deactivateToast(LLToast* toast); - void addToToastPool(LLToast* toast) - { - if (!toast) return; - LL_DEBUGS("NearbyChat") << "Pooling toast" << LL_ENDL; - toast->setVisible(false); - toast->stopTimer(); - toast->setIsHidden(true); - - // Nearby chat toasts that are hidden, not destroyed. They are collected to the toast pool, so that - // they can be used next time, this is done for performance. But if the toast lifetime was changed - // (from preferences floater (STORY-36)) while it was shown (at this moment toast isn't in the pool yet) - // changes don't take affect. - // So toast's lifetime should be updated each time it's added to the pool. Otherwise viewer would have - // to be restarted so that changes take effect. - toast->setLifetime(gSavedSettings.getS32("NearbyToastLifeTime")); - toast->setFadingTime(gSavedSettings.getS32("NearbyToastFadingTime")); - m_toast_pool.push_back(toast->getHandle()); - } - - void createOverflowToast(S32 bottom, F32 timer); - - void updateToastsLifetime(); - - void updateToastFadingTime(); - - create_toast_panel_callback_t m_create_toast_panel_callback_t; - - bool createPoolToast(); - - toast_vec_t m_active_toasts; - toast_list_t m_toast_pool; - - bool mStopProcessing; - bool mChannelRect; -}; - - - -//----------------------------------------------------------------------------------------------- -// LLFloaterIMNearbyChatToast -//----------------------------------------------------------------------------------------------- - -// We're deriving from LLToast to be able to override onClose() -// in order to handle closing nearby chat toasts properly. -class LLFloaterIMNearbyChatToast : public LLToast -{ - LOG_CLASS(LLFloaterIMNearbyChatToast); -public: - LLFloaterIMNearbyChatToast(const LLToast::Params& p, LLFloaterIMNearbyChatScreenChannel* nc_channelp) - : LLToast(p), - mNearbyChatScreenChannelp(nc_channelp) - { - } - - /*virtual*/ void onClose(bool app_quitting); - -private: - LLFloaterIMNearbyChatScreenChannel* mNearbyChatScreenChannelp; -}; - -//----------------------------------------------------------------------------------------------- -// LLFloaterIMNearbyChatScreenChannel -//----------------------------------------------------------------------------------------------- - -void LLFloaterIMNearbyChatScreenChannel::deactivateToast(LLToast* toast) -{ - toast_vec_t::iterator pos = std::find(m_active_toasts.begin(), m_active_toasts.end(), toast->getHandle()); - - if (pos != m_active_toasts.end()) - { - LL_DEBUGS("NearbyChat") << "Deactivating toast" << LL_ENDL; - m_active_toasts.erase(pos); - } -} - -void LLFloaterIMNearbyChatScreenChannel::createOverflowToast(S32 bottom, F32 timer) -{ - //we don't need overflow toast in nearby chat -} - -void LLFloaterIMNearbyChatScreenChannel::onToastDestroyed(LLToast* toast, bool app_quitting) -{ - LL_DEBUGS("NearbyChat") << "Toast destroyed (app_quitting=" << app_quitting << ")" << LL_ENDL; - - if (app_quitting) - { - // Viewer is quitting. - // Immediately stop processing chat messages (EXT-1419). - mStopProcessing = true; - } - else - { - // The toast is being closed by user (STORM-192). - // Remove it from the list of active toasts to prevent - // further references to the invalid pointer. - deactivateToast(toast); - } -} - -void LLFloaterIMNearbyChatScreenChannel::onToastFade(LLToast* toast) -{ - LL_DEBUGS("NearbyChat") << "Toast fading" << LL_ENDL; - - //fade mean we put toast to toast pool - if(!toast) - return; - - deactivateToast(toast); - - addToToastPool(toast); - - arrangeToasts(); -} - -void LLFloaterIMNearbyChatScreenChannel::updateToastsLifetime() -{ - S32 seconds = gSavedSettings.getS32("NearbyToastLifeTime"); - toast_list_t::iterator it; - - for(it = m_toast_pool.begin(); it != m_toast_pool.end(); ++it) - { - (*it).get()->setLifetime(seconds); - } -} - -void LLFloaterIMNearbyChatScreenChannel::updateToastFadingTime() -{ - S32 seconds = gSavedSettings.getS32("NearbyToastFadingTime"); - toast_list_t::iterator it; - - for(it = m_toast_pool.begin(); it != m_toast_pool.end(); ++it) - { - (*it).get()->setFadingTime(seconds); - } -} - -bool LLFloaterIMNearbyChatScreenChannel::createPoolToast() -{ - LLFloaterIMNearbyChatToastPanel* panel= m_create_toast_panel_callback_t(); - if(!panel) - return false; - - LLToast::Params p; - p.panel = panel; - p.lifetime_secs = gSavedSettings.getS32("NearbyToastLifeTime"); - p.fading_time_secs = gSavedSettings.getS32("NearbyToastFadingTime"); - - LLToast* toast = new LLFloaterIMNearbyChatToast(p, this); - - - toast->setOnFadeCallback(boost::bind(&LLFloaterIMNearbyChatScreenChannel::onToastFade, this, _1)); - - // If the toast gets somehow prematurely destroyed, deactivate it to prevent crash (STORM-1352). - toast->setOnToastDestroyedCallback(boost::bind(&LLFloaterIMNearbyChatScreenChannel::onToastDestroyed, this, _1, false)); - - LL_DEBUGS("NearbyChat") << "Creating and pooling toast" << LL_ENDL; - m_toast_pool.push_back(toast->getHandle()); - return true; -} - -void LLFloaterIMNearbyChatScreenChannel::addChat(LLSD& chat) -{ - //look in pool. if there is any message - if (mStopProcessing) - return; - - if (mFloaterSnapRegion == NULL) - { - mFloaterSnapRegion = gViewerWindow->getRootView()->getChildView("floater_snap_region"); - } - LLRect channel_rect; - mFloaterSnapRegion->localRectToOtherView(mFloaterSnapRegion->getLocalRect(), &channel_rect, gFloaterView); - chat["available_height"] = channel_rect.getHeight() - channel_rect.mBottom - gSavedSettings.getS32("ToastGap") - 110;; - - /* - find last toast and check ID - */ - - if (m_active_toasts.size()) - { - LLUUID fromID = chat["from_id"].asUUID(); // agent id or object id - std::string from = chat["from"].asString(); - LLToast* toast = m_active_toasts[0].get(); - if (toast) - { - LLFloaterIMNearbyChatToastPanel* panel = dynamic_cast(toast->getPanel()); - - if (panel && panel->messageID() == fromID && panel->getFromName() == from && panel->canAddText()) - { - panel->addMessage(chat); - toast->reshapeToPanel(); - toast->startTimer(); - - arrangeToasts(); - return; - } - } - } - - - - if (m_toast_pool.empty()) - { - //"pool" is empty - create one more panel - LL_DEBUGS("NearbyChat") << "Empty pool" << LL_ENDL; - if(!createPoolToast())//created toast will go to pool. so next call will find it - return; - addChat(chat); - return; - } - - int chat_type = chat["chat_type"].asInteger(); - - if (chat_type == CHAT_TYPE_DEBUG_MSG) - { - if (!gSavedSettings.getBOOL("ShowScriptErrors")) - return; - if (gSavedSettings.getS32("ShowScriptErrorsLocation") == 1) - return; - } - - //take 1st element from pool, (re)initialize it, put it in active toasts - - LL_DEBUGS("NearbyChat") << "Getting toast from pool" << LL_ENDL; - LLToast* toast = m_toast_pool.back().get(); - - m_toast_pool.pop_back(); - - - LLFloaterIMNearbyChatToastPanel* panel = dynamic_cast(toast->getPanel()); - if (!panel) - return; - panel->init(chat); - - toast->reshapeToPanel(); - toast->startTimer(); - - m_active_toasts.push_back(toast->getHandle()); - - arrangeToasts(); -} - -static bool sort_toasts_predicate(LLHandle first, LLHandle second) -{ - if (!first.get() || !second.get()) return false; // STORM-1352 - - F32 v1 = first.get()->getTimeLeftToLive(); - F32 v2 = second.get()->getTimeLeftToLive(); - return v1 > v2; -} - -void LLFloaterIMNearbyChatScreenChannel::arrangeToasts() -{ - if(mStopProcessing || isHovering()) - return; - - if (mFloaterSnapRegion == NULL) - { - mFloaterSnapRegion = gViewerWindow->getRootView()->getChildView("floater_snap_region"); - } - - if (!getParent()) - { - // connect to floater snap region just to get resize events, we don't care about being a proper widget - mFloaterSnapRegion->addChild(this); - setFollows(FOLLOWS_ALL); - } - - LLRect toast_rect; - updateRect(); - - LLRect channel_rect; - mFloaterSnapRegion->localRectToOtherView(mFloaterSnapRegion->getLocalRect(), &channel_rect, gFloaterView); - channel_rect.mLeft += 10; - channel_rect.mRight = channel_rect.mLeft + 300; - - S32 channel_bottom = channel_rect.mBottom; - - S32 bottom = channel_bottom + 80; - S32 margin = gSavedSettings.getS32("ToastGap"); - - //sort active toasts - std::sort(m_active_toasts.begin(),m_active_toasts.end(),sort_toasts_predicate); - - //calc max visible item and hide other toasts. - - for(toast_vec_t::iterator it = m_active_toasts.begin(); it != m_active_toasts.end(); ++it) - { - LLToast* toast = it->get(); - if (!toast) - { - LL_WARNS() << "NULL found in the active chat toasts list!" << LL_ENDL; - continue; - } - - S32 toast_top = bottom + toast->getRect().getHeight() + margin; - - if(toast_top > channel_rect.getHeight()) - { - while(it!=m_active_toasts.end()) - { - addToToastPool(it->get()); - it=m_active_toasts.erase(it); - } - break; - } - - toast_rect = toast->getRect(); - toast_rect.setLeftTopAndSize(channel_rect.mLeft , bottom + toast_rect.getHeight(), toast_rect.getWidth() ,toast_rect.getHeight()); - - toast->setRect(toast_rect); - bottom += toast_rect.getHeight() - toast->getTopPad() + margin; - } - - // use reverse order to provide correct z-order and avoid toast blinking - - for(toast_vec_t::reverse_iterator it = m_active_toasts.rbegin(); it != m_active_toasts.rend(); ++it) - { - LLToast* toast = it->get(); - if (toast) - { - toast->setIsHidden(false); - toast->setVisible(true); - } - } - -} - - - -//----------------------------------------------------------------------------------------------- -//LLFloaterIMNearbyChatHandler -//----------------------------------------------------------------------------------------------- -std::unique_ptr LLFloaterIMNearbyChatHandler::sChatWatcher(new LLEventStream("LLChat")); - -LLFloaterIMNearbyChatHandler::LLFloaterIMNearbyChatHandler() -{ - // Getting a Channel for our notifications - LLFloaterIMNearbyChatScreenChannel::Params p; - p.id = NEARBY_CHAT_CHANNEL_UUID; - LLFloaterIMNearbyChatScreenChannel* channel = new LLFloaterIMNearbyChatScreenChannel(p); - - LLFloaterIMNearbyChatScreenChannel::create_toast_panel_callback_t callback = createToastPanel; - - channel->setCreatePanelCallback(callback); - - LLChannelManager::getInstance()->addChannel(channel); - - mChannel = channel->getHandle(); -} - -LLFloaterIMNearbyChatHandler::~LLFloaterIMNearbyChatHandler() -{ -} - - -void LLFloaterIMNearbyChatHandler::initChannel() -{ - //LLRect snap_rect = gFloaterView->getSnapRect(); - //mChannel->init(snap_rect.mLeft, snap_rect.mLeft + 200); -} - - - -void LLFloaterIMNearbyChatHandler::processChat(const LLChat& chat_msg, - const LLSD &args) -{ - if (chat_msg.mMuted) - return; - - if (chat_msg.mText.empty()) - return; // don't process empty messages - - LLFloaterReg::getInstance("im_container"); - LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); - - // Build notification data - LLSD chat; - chat["message"] = chat_msg.mText; - chat["from"] = chat_msg.mFromName; - chat["from_id"] = chat_msg.mFromID; - chat["time"] = chat_msg.mTime; - chat["source"] = (S32)chat_msg.mSourceType; - chat["chat_type"] = (S32)chat_msg.mChatType; - chat["chat_style"] = (S32)chat_msg.mChatStyle; - // Pass sender info so that it can be rendered properly (STORM-1021). - chat["sender_slurl"] = LLViewerChat::getSenderSLURL(chat_msg, args); - - if (chat_msg.mChatType == CHAT_TYPE_DIRECT && - chat_msg.mText.length() > 0 && - chat_msg.mText[0] == '@') - { - // Send event on to LLEventStream and exit - sChatWatcher->post(chat); - return; - } - - // don't show toast and add message to chat history on receive debug message - // with disabled setting showing script errors or enabled setting to show script - // errors in separate window. - if (chat_msg.mChatType == CHAT_TYPE_DEBUG_MSG) - { - if (LLFloater::isQuitRequested()) - return; - - if (!gSavedSettings.getBOOL("ShowScriptErrors")) - return; - - // don't process debug messages from not owned objects, see EXT-7762 - if (gAgentID != chat_msg.mOwnerID) - { - return; - } - - if (gSavedSettings.getS32("ShowScriptErrorsLocation") == 1)// show error in window //("ScriptErrorsAsChat")) - { - - LLColor4 txt_color; - - LLViewerChat::getChatColor(chat_msg,txt_color); - - LLFloaterScriptDebug::addScriptLine(chat_msg.mText, - chat_msg.mFromName, - txt_color, - chat_msg.mFromID); - return; - } - } - - nearby_chat->addMessage(chat_msg, true, args); - - if (chat_msg.mSourceType == CHAT_SOURCE_AGENT - && chat_msg.mFromID.notNull() - && chat_msg.mFromID != gAgentID) - { - LLFirstUse::otherAvatarChatFirst(); - - // Add sender to the recent people list. - LLRecentPeople::instance().add(chat_msg.mFromID); - - } - - // Send event on to LLEventStream - sChatWatcher->post(chat); - - LLFloaterIMContainer* im_box = LLFloaterReg::getTypedInstance("im_container"); - - if(( ( chat_msg.mSourceType == CHAT_SOURCE_AGENT - && gSavedSettings.getBOOL("UseChatBubbles") ) - || mChannel.isDead() - || !mChannel.get()->getShowToasts() ) - && nearby_chat->isMessagePaneExpanded()) - // to prevent toasts in Do Not Disturb mode - return;//no need in toast if chat is visible or if bubble chat is enabled - - // arrange a channel on a screen - if(!mChannel.get()->getVisible()) - { - initChannel(); - } - - /* - //comment all this due to EXT-4432 - ..may clean up after some time... - - //only messages from AGENTS - if(CHAT_SOURCE_OBJECT == chat_msg.mSourceType) - { - if(chat_msg.mChatType == CHAT_TYPE_DEBUG_MSG) - return;//ok for now we don't skip messeges from object, so skip only debug messages - } - */ - - LLFloaterIMNearbyChatScreenChannel* channel = dynamic_cast(mChannel.get()); - - if(channel) - { - // Handle IRC styled messages. - std::string toast_msg; - if (chat_msg.mChatStyle == CHAT_STYLE_IRC) - { - if (!chat_msg.mFromName.empty()) - { - toast_msg += chat_msg.mFromName; - } - toast_msg += chat_msg.mText.substr(3); - } - else - { - toast_msg = chat_msg.mText; - } - - bool chat_overlaps = false; - if(nearby_chat->getChatHistory()) - { - LLRect chat_rect = nearby_chat->getChatHistory()->calcScreenRect(); - for (std::list::const_iterator child_iter = gFloaterView->getChildList()->begin(); - child_iter != gFloaterView->getChildList()->end(); ++child_iter) - { - LLView *view = *child_iter; - const LLRect& rect = view->getRect(); - if(view->isInVisibleChain() && (rect.overlaps(chat_rect))) - { - if(!nearby_chat->getChatHistory()->hasAncestor(view)) - { - chat_overlaps = true; - } - break; - } - } - } - //Don't show nearby toast, if conversation is visible and selected - if ((nearby_chat->hasFocus()) || - (LLFloater::isVisible(nearby_chat) && nearby_chat->isTornOff() && !nearby_chat->isMinimized()) || - ((im_box->getSelectedSession().isNull() && !chat_overlaps && - ((LLFloater::isVisible(im_box) && !nearby_chat->isTornOff() && !im_box->isMinimized()) - || (LLFloater::isVisible(nearby_chat) && nearby_chat->isTornOff() && !nearby_chat->isMinimized()))))) - { - if(nearby_chat->isMessagePaneExpanded()) - { - return; - } - } - - //Will show toast when chat preference is set - if((gSavedSettings.getString("NotificationNearbyChatOptions") == "toast") || !nearby_chat->isMessagePaneExpanded()) - { - // Add a nearby chat toast. - LLUUID id; - id.generate(); - chat["id"] = id; - std::string r_color_name = "White"; - F32 r_color_alpha = 1.0f; - LLViewerChat::getChatColor( chat_msg, r_color_name, r_color_alpha); - - chat["text_color"] = r_color_name; - chat["color_alpha"] = r_color_alpha; - chat["font_size"] = (S32)LLViewerChat::getChatFontSize() ; - chat["message"] = toast_msg; - channel->addChat(chat); - } - - } -} - - -//----------------------------------------------------------------------------------------------- -// LLFloaterIMNearbyChatToast -//----------------------------------------------------------------------------------------------- - -// virtual -void LLFloaterIMNearbyChatToast::onClose(bool app_quitting) -{ - mNearbyChatScreenChannelp->onToastDestroyed(this, app_quitting); -} - -// EOF +/** + * @file LLFloaterIMNearbyChatHandler.cpp + * @brief Nearby chat chat managment + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llagentdata.h" // for gAgentID +#include "llfloaterimnearbychathandler.h" + +#include "llchatitemscontainerctrl.h" +#include "llfirstuse.h" +#include "llfloaterscriptdebug.h" +#include "llhints.h" +#include "llfloaterimnearbychat.h" +#include "llrecentpeople.h" + +#include "llviewercontrol.h" + +#include "llfloaterreg.h"//for LLFloaterReg::getTypedInstance +#include "llviewerwindow.h"//for screen channel position +#include "llfloaterimnearbychat.h" +#include "llfloaterimcontainer.h" +#include "llrootview.h" +#include "lllayoutstack.h" + +//add LLFloaterIMNearbyChatHandler to LLNotificationsUI namespace +using namespace LLNotificationsUI; + +static LLFloaterIMNearbyChatToastPanel* createToastPanel() +{ + LLFloaterIMNearbyChatToastPanel* item = LLFloaterIMNearbyChatToastPanel::createInstance(); + return item; +} + + +//----------------------------------------------------------------------------------------------- +//LLFloaterIMNearbyChatScreenChannel +//----------------------------------------------------------------------------------------------- + +class LLFloaterIMNearbyChatScreenChannel: public LLScreenChannelBase +{ + LOG_CLASS(LLFloaterIMNearbyChatScreenChannel); +public: + typedef std::vector > toast_vec_t; + typedef std::list > toast_list_t; + + LLFloaterIMNearbyChatScreenChannel(const Params& p) + : LLScreenChannelBase(p) + { + mStopProcessing = false; + + LLControlVariable* ctrl = gSavedSettings.getControl("NearbyToastLifeTime").get(); + if (ctrl) + { + ctrl->getSignal()->connect(boost::bind(&LLFloaterIMNearbyChatScreenChannel::updateToastsLifetime, this)); + } + + ctrl = gSavedSettings.getControl("NearbyToastFadingTime").get(); + if (ctrl) + { + ctrl->getSignal()->connect(boost::bind(&LLFloaterIMNearbyChatScreenChannel::updateToastFadingTime, this)); + } + } + + void addChat (LLSD& chat); + void arrangeToasts (); + + typedef boost::function create_toast_panel_callback_t; + void setCreatePanelCallback(create_toast_panel_callback_t value) { m_create_toast_panel_callback_t = value;} + + void onToastDestroyed (LLToast* toast, bool app_quitting); + void onToastFade (LLToast* toast); + + void redrawToasts() + { + arrangeToasts(); + } + + // hide all toasts from screen, but not remove them from a channel + // removes all toasts from a channel + virtual void removeToastsFromChannel() + { + for(toast_vec_t::iterator it = m_active_toasts.begin(); it != m_active_toasts.end(); ++it) + { + addToToastPool(it->get()); + } + m_active_toasts.clear(); + }; + + virtual void deleteAllChildren() + { + LL_DEBUGS("NearbyChat") << "Clearing toast pool" << LL_ENDL; + m_toast_pool.clear(); + m_active_toasts.clear(); + LLScreenChannelBase::deleteAllChildren(); + } + +protected: + void deactivateToast(LLToast* toast); + void addToToastPool(LLToast* toast) + { + if (!toast) return; + LL_DEBUGS("NearbyChat") << "Pooling toast" << LL_ENDL; + toast->setVisible(false); + toast->stopTimer(); + toast->setIsHidden(true); + + // Nearby chat toasts that are hidden, not destroyed. They are collected to the toast pool, so that + // they can be used next time, this is done for performance. But if the toast lifetime was changed + // (from preferences floater (STORY-36)) while it was shown (at this moment toast isn't in the pool yet) + // changes don't take affect. + // So toast's lifetime should be updated each time it's added to the pool. Otherwise viewer would have + // to be restarted so that changes take effect. + toast->setLifetime(gSavedSettings.getS32("NearbyToastLifeTime")); + toast->setFadingTime(gSavedSettings.getS32("NearbyToastFadingTime")); + m_toast_pool.push_back(toast->getHandle()); + } + + void createOverflowToast(S32 bottom, F32 timer); + + void updateToastsLifetime(); + + void updateToastFadingTime(); + + create_toast_panel_callback_t m_create_toast_panel_callback_t; + + bool createPoolToast(); + + toast_vec_t m_active_toasts; + toast_list_t m_toast_pool; + + bool mStopProcessing; + bool mChannelRect; +}; + + + +//----------------------------------------------------------------------------------------------- +// LLFloaterIMNearbyChatToast +//----------------------------------------------------------------------------------------------- + +// We're deriving from LLToast to be able to override onClose() +// in order to handle closing nearby chat toasts properly. +class LLFloaterIMNearbyChatToast : public LLToast +{ + LOG_CLASS(LLFloaterIMNearbyChatToast); +public: + LLFloaterIMNearbyChatToast(const LLToast::Params& p, LLFloaterIMNearbyChatScreenChannel* nc_channelp) + : LLToast(p), + mNearbyChatScreenChannelp(nc_channelp) + { + } + + /*virtual*/ void onClose(bool app_quitting); + +private: + LLFloaterIMNearbyChatScreenChannel* mNearbyChatScreenChannelp; +}; + +//----------------------------------------------------------------------------------------------- +// LLFloaterIMNearbyChatScreenChannel +//----------------------------------------------------------------------------------------------- + +void LLFloaterIMNearbyChatScreenChannel::deactivateToast(LLToast* toast) +{ + toast_vec_t::iterator pos = std::find(m_active_toasts.begin(), m_active_toasts.end(), toast->getHandle()); + + if (pos != m_active_toasts.end()) + { + LL_DEBUGS("NearbyChat") << "Deactivating toast" << LL_ENDL; + m_active_toasts.erase(pos); + } +} + +void LLFloaterIMNearbyChatScreenChannel::createOverflowToast(S32 bottom, F32 timer) +{ + //we don't need overflow toast in nearby chat +} + +void LLFloaterIMNearbyChatScreenChannel::onToastDestroyed(LLToast* toast, bool app_quitting) +{ + LL_DEBUGS("NearbyChat") << "Toast destroyed (app_quitting=" << app_quitting << ")" << LL_ENDL; + + if (app_quitting) + { + // Viewer is quitting. + // Immediately stop processing chat messages (EXT-1419). + mStopProcessing = true; + } + else + { + // The toast is being closed by user (STORM-192). + // Remove it from the list of active toasts to prevent + // further references to the invalid pointer. + deactivateToast(toast); + } +} + +void LLFloaterIMNearbyChatScreenChannel::onToastFade(LLToast* toast) +{ + LL_DEBUGS("NearbyChat") << "Toast fading" << LL_ENDL; + + //fade mean we put toast to toast pool + if(!toast) + return; + + deactivateToast(toast); + + addToToastPool(toast); + + arrangeToasts(); +} + +void LLFloaterIMNearbyChatScreenChannel::updateToastsLifetime() +{ + S32 seconds = gSavedSettings.getS32("NearbyToastLifeTime"); + toast_list_t::iterator it; + + for(it = m_toast_pool.begin(); it != m_toast_pool.end(); ++it) + { + (*it).get()->setLifetime(seconds); + } +} + +void LLFloaterIMNearbyChatScreenChannel::updateToastFadingTime() +{ + S32 seconds = gSavedSettings.getS32("NearbyToastFadingTime"); + toast_list_t::iterator it; + + for(it = m_toast_pool.begin(); it != m_toast_pool.end(); ++it) + { + (*it).get()->setFadingTime(seconds); + } +} + +bool LLFloaterIMNearbyChatScreenChannel::createPoolToast() +{ + LLFloaterIMNearbyChatToastPanel* panel= m_create_toast_panel_callback_t(); + if(!panel) + return false; + + LLToast::Params p; + p.panel = panel; + p.lifetime_secs = gSavedSettings.getS32("NearbyToastLifeTime"); + p.fading_time_secs = gSavedSettings.getS32("NearbyToastFadingTime"); + + LLToast* toast = new LLFloaterIMNearbyChatToast(p, this); + + + toast->setOnFadeCallback(boost::bind(&LLFloaterIMNearbyChatScreenChannel::onToastFade, this, _1)); + + // If the toast gets somehow prematurely destroyed, deactivate it to prevent crash (STORM-1352). + toast->setOnToastDestroyedCallback(boost::bind(&LLFloaterIMNearbyChatScreenChannel::onToastDestroyed, this, _1, false)); + + LL_DEBUGS("NearbyChat") << "Creating and pooling toast" << LL_ENDL; + m_toast_pool.push_back(toast->getHandle()); + return true; +} + +void LLFloaterIMNearbyChatScreenChannel::addChat(LLSD& chat) +{ + //look in pool. if there is any message + if (mStopProcessing) + return; + + if (mFloaterSnapRegion == NULL) + { + mFloaterSnapRegion = gViewerWindow->getRootView()->getChildView("floater_snap_region"); + } + LLRect channel_rect; + mFloaterSnapRegion->localRectToOtherView(mFloaterSnapRegion->getLocalRect(), &channel_rect, gFloaterView); + chat["available_height"] = channel_rect.getHeight() - channel_rect.mBottom - gSavedSettings.getS32("ToastGap") - 110;; + + /* + find last toast and check ID + */ + + if (m_active_toasts.size()) + { + LLUUID fromID = chat["from_id"].asUUID(); // agent id or object id + std::string from = chat["from"].asString(); + LLToast* toast = m_active_toasts[0].get(); + if (toast) + { + LLFloaterIMNearbyChatToastPanel* panel = dynamic_cast(toast->getPanel()); + + if (panel && panel->messageID() == fromID && panel->getFromName() == from && panel->canAddText()) + { + panel->addMessage(chat); + toast->reshapeToPanel(); + toast->startTimer(); + + arrangeToasts(); + return; + } + } + } + + + + if (m_toast_pool.empty()) + { + //"pool" is empty - create one more panel + LL_DEBUGS("NearbyChat") << "Empty pool" << LL_ENDL; + if(!createPoolToast())//created toast will go to pool. so next call will find it + return; + addChat(chat); + return; + } + + int chat_type = chat["chat_type"].asInteger(); + + if (chat_type == CHAT_TYPE_DEBUG_MSG) + { + if (!gSavedSettings.getBOOL("ShowScriptErrors")) + return; + if (gSavedSettings.getS32("ShowScriptErrorsLocation") == 1) + return; + } + + //take 1st element from pool, (re)initialize it, put it in active toasts + + LL_DEBUGS("NearbyChat") << "Getting toast from pool" << LL_ENDL; + LLToast* toast = m_toast_pool.back().get(); + + m_toast_pool.pop_back(); + + + LLFloaterIMNearbyChatToastPanel* panel = dynamic_cast(toast->getPanel()); + if (!panel) + return; + panel->init(chat); + + toast->reshapeToPanel(); + toast->startTimer(); + + m_active_toasts.push_back(toast->getHandle()); + + arrangeToasts(); +} + +static bool sort_toasts_predicate(LLHandle first, LLHandle second) +{ + if (!first.get() || !second.get()) return false; // STORM-1352 + + F32 v1 = first.get()->getTimeLeftToLive(); + F32 v2 = second.get()->getTimeLeftToLive(); + return v1 > v2; +} + +void LLFloaterIMNearbyChatScreenChannel::arrangeToasts() +{ + if(mStopProcessing || isHovering()) + return; + + if (mFloaterSnapRegion == NULL) + { + mFloaterSnapRegion = gViewerWindow->getRootView()->getChildView("floater_snap_region"); + } + + if (!getParent()) + { + // connect to floater snap region just to get resize events, we don't care about being a proper widget + mFloaterSnapRegion->addChild(this); + setFollows(FOLLOWS_ALL); + } + + LLRect toast_rect; + updateRect(); + + LLRect channel_rect; + mFloaterSnapRegion->localRectToOtherView(mFloaterSnapRegion->getLocalRect(), &channel_rect, gFloaterView); + channel_rect.mLeft += 10; + channel_rect.mRight = channel_rect.mLeft + 300; + + S32 channel_bottom = channel_rect.mBottom; + + S32 bottom = channel_bottom + 80; + S32 margin = gSavedSettings.getS32("ToastGap"); + + //sort active toasts + std::sort(m_active_toasts.begin(),m_active_toasts.end(),sort_toasts_predicate); + + //calc max visible item and hide other toasts. + + for(toast_vec_t::iterator it = m_active_toasts.begin(); it != m_active_toasts.end(); ++it) + { + LLToast* toast = it->get(); + if (!toast) + { + LL_WARNS() << "NULL found in the active chat toasts list!" << LL_ENDL; + continue; + } + + S32 toast_top = bottom + toast->getRect().getHeight() + margin; + + if(toast_top > channel_rect.getHeight()) + { + while(it!=m_active_toasts.end()) + { + addToToastPool(it->get()); + it=m_active_toasts.erase(it); + } + break; + } + + toast_rect = toast->getRect(); + toast_rect.setLeftTopAndSize(channel_rect.mLeft , bottom + toast_rect.getHeight(), toast_rect.getWidth() ,toast_rect.getHeight()); + + toast->setRect(toast_rect); + bottom += toast_rect.getHeight() - toast->getTopPad() + margin; + } + + // use reverse order to provide correct z-order and avoid toast blinking + + for(toast_vec_t::reverse_iterator it = m_active_toasts.rbegin(); it != m_active_toasts.rend(); ++it) + { + LLToast* toast = it->get(); + if (toast) + { + toast->setIsHidden(false); + toast->setVisible(true); + } + } + +} + + + +//----------------------------------------------------------------------------------------------- +//LLFloaterIMNearbyChatHandler +//----------------------------------------------------------------------------------------------- +std::unique_ptr LLFloaterIMNearbyChatHandler::sChatWatcher(new LLEventStream("LLChat")); + +LLFloaterIMNearbyChatHandler::LLFloaterIMNearbyChatHandler() +{ + // Getting a Channel for our notifications + LLFloaterIMNearbyChatScreenChannel::Params p; + p.id = NEARBY_CHAT_CHANNEL_UUID; + LLFloaterIMNearbyChatScreenChannel* channel = new LLFloaterIMNearbyChatScreenChannel(p); + + LLFloaterIMNearbyChatScreenChannel::create_toast_panel_callback_t callback = createToastPanel; + + channel->setCreatePanelCallback(callback); + + LLChannelManager::getInstance()->addChannel(channel); + + mChannel = channel->getHandle(); +} + +LLFloaterIMNearbyChatHandler::~LLFloaterIMNearbyChatHandler() +{ +} + + +void LLFloaterIMNearbyChatHandler::initChannel() +{ + //LLRect snap_rect = gFloaterView->getSnapRect(); + //mChannel->init(snap_rect.mLeft, snap_rect.mLeft + 200); +} + + + +void LLFloaterIMNearbyChatHandler::processChat(const LLChat& chat_msg, + const LLSD &args) +{ + if (chat_msg.mMuted) + return; + + if (chat_msg.mText.empty()) + return; // don't process empty messages + + LLFloaterReg::getInstance("im_container"); + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); + + // Build notification data + LLSD chat; + chat["message"] = chat_msg.mText; + chat["from"] = chat_msg.mFromName; + chat["from_id"] = chat_msg.mFromID; + chat["time"] = chat_msg.mTime; + chat["source"] = (S32)chat_msg.mSourceType; + chat["chat_type"] = (S32)chat_msg.mChatType; + chat["chat_style"] = (S32)chat_msg.mChatStyle; + // Pass sender info so that it can be rendered properly (STORM-1021). + chat["sender_slurl"] = LLViewerChat::getSenderSLURL(chat_msg, args); + + if (chat_msg.mChatType == CHAT_TYPE_DIRECT && + chat_msg.mText.length() > 0 && + chat_msg.mText[0] == '@') + { + // Send event on to LLEventStream and exit + sChatWatcher->post(chat); + return; + } + + // don't show toast and add message to chat history on receive debug message + // with disabled setting showing script errors or enabled setting to show script + // errors in separate window. + if (chat_msg.mChatType == CHAT_TYPE_DEBUG_MSG) + { + if (LLFloater::isQuitRequested()) + return; + + if (!gSavedSettings.getBOOL("ShowScriptErrors")) + return; + + // don't process debug messages from not owned objects, see EXT-7762 + if (gAgentID != chat_msg.mOwnerID) + { + return; + } + + if (gSavedSettings.getS32("ShowScriptErrorsLocation") == 1)// show error in window //("ScriptErrorsAsChat")) + { + + LLColor4 txt_color; + + LLViewerChat::getChatColor(chat_msg,txt_color); + + LLFloaterScriptDebug::addScriptLine(chat_msg.mText, + chat_msg.mFromName, + txt_color, + chat_msg.mFromID); + return; + } + } + + nearby_chat->addMessage(chat_msg, true, args); + + if (chat_msg.mSourceType == CHAT_SOURCE_AGENT + && chat_msg.mFromID.notNull() + && chat_msg.mFromID != gAgentID) + { + LLFirstUse::otherAvatarChatFirst(); + + // Add sender to the recent people list. + LLRecentPeople::instance().add(chat_msg.mFromID); + + } + + // Send event on to LLEventStream + sChatWatcher->post(chat); + + LLFloaterIMContainer* im_box = LLFloaterReg::getTypedInstance("im_container"); + + if(( ( chat_msg.mSourceType == CHAT_SOURCE_AGENT + && gSavedSettings.getBOOL("UseChatBubbles") ) + || mChannel.isDead() + || !mChannel.get()->getShowToasts() ) + && nearby_chat->isMessagePaneExpanded()) + // to prevent toasts in Do Not Disturb mode + return;//no need in toast if chat is visible or if bubble chat is enabled + + // arrange a channel on a screen + if(!mChannel.get()->getVisible()) + { + initChannel(); + } + + /* + //comment all this due to EXT-4432 + ..may clean up after some time... + + //only messages from AGENTS + if(CHAT_SOURCE_OBJECT == chat_msg.mSourceType) + { + if(chat_msg.mChatType == CHAT_TYPE_DEBUG_MSG) + return;//ok for now we don't skip messeges from object, so skip only debug messages + } + */ + + LLFloaterIMNearbyChatScreenChannel* channel = dynamic_cast(mChannel.get()); + + if(channel) + { + // Handle IRC styled messages. + std::string toast_msg; + if (chat_msg.mChatStyle == CHAT_STYLE_IRC) + { + if (!chat_msg.mFromName.empty()) + { + toast_msg += chat_msg.mFromName; + } + toast_msg += chat_msg.mText.substr(3); + } + else + { + toast_msg = chat_msg.mText; + } + + bool chat_overlaps = false; + if(nearby_chat->getChatHistory()) + { + LLRect chat_rect = nearby_chat->getChatHistory()->calcScreenRect(); + for (std::list::const_iterator child_iter = gFloaterView->getChildList()->begin(); + child_iter != gFloaterView->getChildList()->end(); ++child_iter) + { + LLView *view = *child_iter; + const LLRect& rect = view->getRect(); + if(view->isInVisibleChain() && (rect.overlaps(chat_rect))) + { + if(!nearby_chat->getChatHistory()->hasAncestor(view)) + { + chat_overlaps = true; + } + break; + } + } + } + //Don't show nearby toast, if conversation is visible and selected + if ((nearby_chat->hasFocus()) || + (LLFloater::isVisible(nearby_chat) && nearby_chat->isTornOff() && !nearby_chat->isMinimized()) || + ((im_box->getSelectedSession().isNull() && !chat_overlaps && + ((LLFloater::isVisible(im_box) && !nearby_chat->isTornOff() && !im_box->isMinimized()) + || (LLFloater::isVisible(nearby_chat) && nearby_chat->isTornOff() && !nearby_chat->isMinimized()))))) + { + if(nearby_chat->isMessagePaneExpanded()) + { + return; + } + } + + //Will show toast when chat preference is set + if((gSavedSettings.getString("NotificationNearbyChatOptions") == "toast") || !nearby_chat->isMessagePaneExpanded()) + { + // Add a nearby chat toast. + LLUUID id; + id.generate(); + chat["id"] = id; + std::string r_color_name = "White"; + F32 r_color_alpha = 1.0f; + LLViewerChat::getChatColor( chat_msg, r_color_name, r_color_alpha); + + chat["text_color"] = r_color_name; + chat["color_alpha"] = r_color_alpha; + chat["font_size"] = (S32)LLViewerChat::getChatFontSize() ; + chat["message"] = toast_msg; + channel->addChat(chat); + } + + } +} + + +//----------------------------------------------------------------------------------------------- +// LLFloaterIMNearbyChatToast +//----------------------------------------------------------------------------------------------- + +// virtual +void LLFloaterIMNearbyChatToast::onClose(bool app_quitting) +{ + mNearbyChatScreenChannelp->onToastDestroyed(this, app_quitting); +} + +// EOF diff --git a/indra/newview/llfloaterimnearbychatlistener.cpp b/indra/newview/llfloaterimnearbychatlistener.cpp index fb2786c2c0..43173d3680 100644 --- a/indra/newview/llfloaterimnearbychatlistener.cpp +++ b/indra/newview/llfloaterimnearbychatlistener.cpp @@ -1,100 +1,100 @@ -/** - * @file llfloaterimnearbychatlistener.cpp - * @author Dave Simmons - * @date 2011-03-15 - * @brief Implementation for LLFloaterIMNearbyChatListener. - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterimnearbychatlistener.h" -#include "llfloaterimnearbychat.h" - -#include "llagent.h" -#include "llchat.h" -#include "llviewercontrol.h" - - -LLFloaterIMNearbyChatListener::LLFloaterIMNearbyChatListener(LLFloaterIMNearbyChat & chatbar) - : LLEventAPI("LLChatBar", - "LLChatBar listener to (e.g.) sendChat, etc."), - mChatbar(chatbar) -{ - add("sendChat", - "Send chat to the simulator:\n" - "[\"message\"] chat message text [required]\n" - "[\"channel\"] chat channel number [default = 0]\n" - "[\"type\"] chat type \"whisper\", \"normal\", \"shout\" [default = \"normal\"]", - &LLFloaterIMNearbyChatListener::sendChat); -} - - -// "sendChat" command -void LLFloaterIMNearbyChatListener::sendChat(LLSD const & chat_data) const -{ - // Extract the data - std::string chat_text = chat_data["message"].asString(); - - S32 channel = 0; - if (chat_data.has("channel")) - { - channel = chat_data["channel"].asInteger(); - if (channel < 0 || channel >= CHAT_CHANNEL_DEBUG) - { // Use 0 up to (but not including) CHAT_CHANNEL_DEBUG - channel = 0; - } - } - - EChatType type_o_chat = CHAT_TYPE_NORMAL; - if (chat_data.has("type")) - { - std::string type_string = chat_data["type"].asString(); - if (type_string == "whisper") - { - type_o_chat = CHAT_TYPE_WHISPER; - } - else if (type_string == "shout") - { - type_o_chat = CHAT_TYPE_SHOUT; - } - } - - // Have to prepend /42 style channel numbers - std::string chat_to_send; - if (channel == 0) - { - chat_to_send = chat_text; - } - else - { - chat_to_send += "/"; - chat_to_send += chat_data["channel"].asString(); - chat_to_send += " "; - chat_to_send += chat_text; - } - - // Send it as if it was typed in - mChatbar.sendChatFromViewer(chat_to_send, type_o_chat, ((bool)(channel == 0)) && gSavedSettings.getBOOL("PlayChatAnim")); -} - +/** + * @file llfloaterimnearbychatlistener.cpp + * @author Dave Simmons + * @date 2011-03-15 + * @brief Implementation for LLFloaterIMNearbyChatListener. + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterimnearbychatlistener.h" +#include "llfloaterimnearbychat.h" + +#include "llagent.h" +#include "llchat.h" +#include "llviewercontrol.h" + + +LLFloaterIMNearbyChatListener::LLFloaterIMNearbyChatListener(LLFloaterIMNearbyChat & chatbar) + : LLEventAPI("LLChatBar", + "LLChatBar listener to (e.g.) sendChat, etc."), + mChatbar(chatbar) +{ + add("sendChat", + "Send chat to the simulator:\n" + "[\"message\"] chat message text [required]\n" + "[\"channel\"] chat channel number [default = 0]\n" + "[\"type\"] chat type \"whisper\", \"normal\", \"shout\" [default = \"normal\"]", + &LLFloaterIMNearbyChatListener::sendChat); +} + + +// "sendChat" command +void LLFloaterIMNearbyChatListener::sendChat(LLSD const & chat_data) const +{ + // Extract the data + std::string chat_text = chat_data["message"].asString(); + + S32 channel = 0; + if (chat_data.has("channel")) + { + channel = chat_data["channel"].asInteger(); + if (channel < 0 || channel >= CHAT_CHANNEL_DEBUG) + { // Use 0 up to (but not including) CHAT_CHANNEL_DEBUG + channel = 0; + } + } + + EChatType type_o_chat = CHAT_TYPE_NORMAL; + if (chat_data.has("type")) + { + std::string type_string = chat_data["type"].asString(); + if (type_string == "whisper") + { + type_o_chat = CHAT_TYPE_WHISPER; + } + else if (type_string == "shout") + { + type_o_chat = CHAT_TYPE_SHOUT; + } + } + + // Have to prepend /42 style channel numbers + std::string chat_to_send; + if (channel == 0) + { + chat_to_send = chat_text; + } + else + { + chat_to_send += "/"; + chat_to_send += chat_data["channel"].asString(); + chat_to_send += " "; + chat_to_send += chat_text; + } + + // Send it as if it was typed in + mChatbar.sendChatFromViewer(chat_to_send, type_o_chat, ((bool)(channel == 0)) && gSavedSettings.getBOOL("PlayChatAnim")); +} + diff --git a/indra/newview/llfloaterimsession.cpp b/indra/newview/llfloaterimsession.cpp index 5508534c25..d49352ec94 100644 --- a/indra/newview/llfloaterimsession.cpp +++ b/indra/newview/llfloaterimsession.cpp @@ -1,1356 +1,1356 @@ -/** - * @file llfloaterimsession.cpp - * @brief LLFloaterIMSession class definition - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterimsession.h" - -#include "lldraghandle.h" -#include "llnotificationsutil.h" - -#include "llagent.h" -#include "llappviewer.h" -#include "llavataractions.h" -#include "llavatarnamecache.h" -#include "llbutton.h" -#include "llchannelmanager.h" -#include "llchiclet.h" -#include "llchicletbar.h" -#include "lldonotdisturbnotificationstorage.h" -#include "llfloaterreg.h" -#include "llfloateravatarpicker.h" -#include "llfloaterimcontainer.h" // to replace separate IM Floaters with multifloater container -#include "llinventoryfunctions.h" -//#include "lllayoutstack.h" -#include "llchatentry.h" -#include "lllogchat.h" -#include "llscreenchannel.h" -#include "llsyswellwindow.h" -#include "lltrans.h" -#include "llchathistory.h" -#include "llnotifications.h" -#include "llviewerregion.h" -#include "llviewerwindow.h" -#include "lltransientfloatermgr.h" -#include "llinventorymodel.h" -#include "llrootview.h" -#include "llspeakers.h" -#include "llviewerchat.h" -#include "llnotificationmanager.h" -#include "llautoreplace.h" -#include "llcorehttputil.h" - -const F32 ME_TYPING_TIMEOUT = 4.0f; -const F32 OTHER_TYPING_TIMEOUT = 9.0f; - -floater_showed_signal_t LLFloaterIMSession::sIMFloaterShowedSignal; - -LLFloaterIMSession::LLFloaterIMSession(const LLUUID& session_id) - : LLFloaterIMSessionTab(session_id), - mLastMessageIndex(-1), - mDialog(IM_NOTHING_SPECIAL), - mTypingStart(), - mShouldSendTypingState(false), - mMeTyping(false), - mOtherTyping(false), - mSessionNameUpdatedForTyping(false), - mTypingTimer(), - mTypingTimeoutTimer(), - mPositioned(false), - mSessionInitialized(false), - mMeTypingTimer(), - mOtherTypingTimer() -{ - mIsNearbyChat = false; - - initIMSession(session_id); - - setOverlapsScreenChannel(true); - - LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::IM, this); - mEnableCallbackRegistrar.add("Avatar.EnableGearItem", boost::bind(&LLFloaterIMSession::enableGearMenuItem, this, _2)); - mCommitCallbackRegistrar.add("Avatar.GearDoToSelected", boost::bind(&LLFloaterIMSession::GearDoToSelected, this, _2)); - mEnableCallbackRegistrar.add("Avatar.CheckGearItem", boost::bind(&LLFloaterIMSession::checkGearMenuItem, this, _2)); - - setDocked(true); -} - - -// virtual -void LLFloaterIMSession::refresh() -{ - if (mMeTyping) -{ - // Send an additional Start Typing packet every ME_TYPING_TIMEOUT seconds - if (mMeTypingTimer.getElapsedTimeF32() > ME_TYPING_TIMEOUT && false == mShouldSendTypingState) - { - LL_DEBUGS("TypingMsgs") << "Send additional Start Typing packet" << LL_ENDL; - LLIMModel::instance().sendTypingState(mSessionID, mOtherParticipantUUID, true); - mMeTypingTimer.reset(); - } - - // Time out if user hasn't typed for a while. - if (mTypingTimeoutTimer.getElapsedTimeF32() > LLAgent::TYPING_TIMEOUT_SECS) - { - setTyping(false); - LL_DEBUGS("TypingMsgs") << "Send stop typing due to timeout" << LL_ENDL; - } - } - - // Clear message if no data received for OTHER_TYPING_TIMEOUT seconds - if (mOtherTyping && mOtherTypingTimer.getElapsedTimeF32() > OTHER_TYPING_TIMEOUT) - { - LL_DEBUGS("TypingMsgs") << "Received: is typing cleared due to timeout" << LL_ENDL; - removeTypingIndicator(mImFromId); - mOtherTyping = false; - } - -} - -// virtual -void LLFloaterIMSession::onTearOffClicked() -{ - LLFloaterIMSessionTab::onTearOffClicked(); -} - -// virtual -void LLFloaterIMSession::onClickCloseBtn(bool) -{ - LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(mSessionID); - - if (session != NULL) - { - bool is_call_with_chat = session->isGroupSessionType() - || session->isAdHocSessionType() || session->isP2PSessionType(); - - LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID); - - if (is_call_with_chat && voice_channel != NULL - && voice_channel->isActive()) - { - LLSD payload; - payload["session_id"] = mSessionID; - LLNotificationsUtil::add("ConfirmLeaveCall", LLSD(), payload, confirmLeaveCallCallback); - return; - } - } - else - { - LL_WARNS() << "Empty session with id: " << (mSessionID.asString()) << LL_ENDL; - } - - LLFloaterIMSessionTab::onClickCloseBtn(); -} - -/* static */ -void LLFloaterIMSession::newIMCallback(const LLSD& data) -{ - if (data["num_unread"].asInteger() > 0 || data["from_id"].asUUID().isNull()) - { - LLUUID session_id = data["session_id"].asUUID(); - - LLFloaterIMSession* floater = LLFloaterReg::findTypedInstance("impanel", session_id); - - // update if visible, otherwise will be updated when opened - if (floater && floater->isInVisibleChain()) - { - floater->updateMessages(); - } - } -} - -void LLFloaterIMSession::onVisibilityChanged(const LLSD& new_visibility) -{ - bool visible = new_visibility.asBoolean(); - - LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID); - - if (visible && voice_channel && - voice_channel->getState() == LLVoiceChannel::STATE_CONNECTED) - { - LLFloaterReg::showInstance("voice_call", mSessionID); - } - else - { - LLFloaterReg::hideInstance("voice_call", mSessionID); - } -} - -void LLFloaterIMSession::onSendMsg( LLUICtrl* ctrl, void* userdata ) -{ - LLFloaterIMSession* self = (LLFloaterIMSession*) userdata; - self->sendMsgFromInputEditor(); - self->setTyping(false); -} - -bool LLFloaterIMSession::enableGearMenuItem(const LLSD& userdata) -{ - std::string command = userdata.asString(); - uuid_vec_t selected_uuids; - selected_uuids.push_back(mOtherParticipantUUID); - - LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); - return floater_container->enableContextMenuItem(command, selected_uuids); -} - -void LLFloaterIMSession::GearDoToSelected(const LLSD& userdata) -{ - std::string command = userdata.asString(); - uuid_vec_t selected_uuids; - selected_uuids.push_back(mOtherParticipantUUID); - - LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); - floater_container->doToParticipants(command, selected_uuids); -} - -bool LLFloaterIMSession::checkGearMenuItem(const LLSD& userdata) -{ - std::string command = userdata.asString(); - uuid_vec_t selected_uuids; - selected_uuids.push_back(mOtherParticipantUUID); - - LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); - return floater_container->checkContextMenuItem(command, selected_uuids); -} - -void LLFloaterIMSession::sendMsgFromInputEditor() -{ - if (gAgent.isGodlike() - || (mDialog != IM_NOTHING_SPECIAL) - || !mOtherParticipantUUID.isNull()) - { - if (mInputEditor) - { - LLWString text = mInputEditor->getWText(); - LLWStringUtil::trim(text); - LLWStringUtil::replaceChar(text,182,'\n'); // Convert paragraph symbols back into newlines. - if(!text.empty()) - { - updateUsedEmojis(text); - - // Truncate and convert to UTF8 for transport - std::string utf8_text = wstring_to_utf8str(text); - - sendMsg(utf8_text); - - mInputEditor->setText(LLStringUtil::null); - } - } - } - else - { - LL_INFOS() << "Cannot send IM to everyone unless you're a god." << LL_ENDL; - } -} - -void LLFloaterIMSession::sendMsg(const std::string& msg) -{ - const std::string utf8_text = utf8str_truncate(msg, MAX_MSG_BUF_SIZE - 1); - - if (mSessionInitialized) - { - LLIMModel::sendMessage(utf8_text, mSessionID, mOtherParticipantUUID, mDialog); - } - else - { - //queue up the message to send once the session is initialized - mQueuedMsgsForInit.append(utf8_text); - } - - updateMessages(); -} - -LLFloaterIMSession::~LLFloaterIMSession() -{ - mVoiceChannelStateChangeConnection.disconnect(); - if(LLVoiceClient::instanceExists()) - { - LLVoiceClient::getInstance()->removeObserver(this); - } - - LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::IM, this); -} - - -void LLFloaterIMSession::initIMSession(const LLUUID& session_id) -{ - // Change the floater key to bind it to a new session. - setKey(session_id); - - mSessionID = session_id; - mSession = LLIMModel::getInstance()->findIMSession(mSessionID); - - if (mSession) - { - mIsP2PChat = mSession->isP2PSessionType(); - mSessionInitialized = mSession->mSessionInitialized; - mDialog = mSession->mType; - } -} - -void LLFloaterIMSession::initIMFloater() -{ - const LLUUID& other_party_id = - LLIMModel::getInstance()->getOtherParticipantID(mSessionID); - if (other_party_id.notNull()) - { - mOtherParticipantUUID = other_party_id; - } - - boundVoiceChannel(); - - mTypingStart = LLTrans::getString("IM_typing_start_string"); - - // Show control panel in torn off floaters only. - mParticipantListPanel->setVisible(!getHost() && gSavedSettings.getBOOL("IMShowControlPanel")); - - // Disable input editor if session cannot accept text - if ( mSession && !mSession->mTextIMPossible ) - { - mInputEditor->setEnabled(false); - mInputEditor->setLabel(LLTrans::getString("IM_unavailable_text_label")); - } - - if (!mIsP2PChat) - { - std::string session_name(LLIMModel::instance().getName(mSessionID)); - updateSessionName(session_name); - } -} - -//virtual -bool LLFloaterIMSession::postBuild() -{ - bool result = LLFloaterIMSessionTab::postBuild(); - - mInputEditor->setMaxTextLength(1023); - mInputEditor->setAutoreplaceCallback(boost::bind(&LLAutoReplace::autoreplaceCallback, LLAutoReplace::getInstance(), _1, _2, _3, _4, _5)); - mInputEditor->setFocusReceivedCallback( boost::bind(onInputEditorFocusReceived, _1, this) ); - mInputEditor->setFocusLostCallback( boost::bind(onInputEditorFocusLost, _1, this) ); - mInputEditor->setKeystrokeCallback( boost::bind(onInputEditorKeystroke, _1, this) ); - mInputEditor->setCommitCallback(boost::bind(onSendMsg, _1, this)); - - setDocked(true); - - LLButton* add_btn = getChild("add_btn"); - - // Allow to add chat participants depending on the session type - add_btn->setEnabled(isInviteAllowed()); - add_btn->setClickedCallback(boost::bind(&LLFloaterIMSession::onAddButtonClicked, this)); - - childSetAction("voice_call_btn", boost::bind(&LLFloaterIMSession::onCallButtonClicked, this)); - - LLVoiceClient::getInstance()->addObserver(this); - - //*TODO if session is not initialized yet, add some sort of a warning message like "starting session...blablabla" - //see LLFloaterIMPanel for how it is done (IB) - - initIMFloater(); - - return result; -} - -void LLFloaterIMSession::onAddButtonClicked() -{ - LLView * button = findChild("toolbar_panel")->findChild("add_btn"); - LLFloater* root_floater = gFloaterView->getParentFloater(this); - LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(boost::bind(&LLFloaterIMSession::addSessionParticipants, this, _1), true, true, false, root_floater->getName(), button); - if (!picker) - { - return; - } - - // Need to disable 'ok' button when selected users are already in conversation. - picker->setOkBtnEnableCb(boost::bind(&LLFloaterIMSession::canAddSelectedToChat, this, _1)); - - if (root_floater) - { - root_floater->addDependentFloater(picker); - } -} - -bool LLFloaterIMSession::canAddSelectedToChat(const uuid_vec_t& uuids) -{ - if (!mSession - || mDialog == IM_SESSION_GROUP_START - || (mDialog == IM_SESSION_INVITE && gAgent.isInGroup(mSessionID))) - { - return false; - } - - if (mIsP2PChat) - { - // For a P2P session just check if we are not adding the other participant. - - for (uuid_vec_t::const_iterator id = uuids.begin(); - id != uuids.end(); ++id) - { - if (*id == mOtherParticipantUUID) - { - return false; - } - } - } - else - { - // For a conference session we need to check against the list from LLSpeakerMgr, - // because this list may change when participants join or leave the session. - - LLSpeakerMgr::speaker_list_t speaker_list; - LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - if (speaker_mgr) - { - speaker_mgr->getSpeakerList(&speaker_list, true); - } - - for (uuid_vec_t::const_iterator id = uuids.begin(); - id != uuids.end(); ++id) - { - for (LLSpeakerMgr::speaker_list_t::const_iterator it = speaker_list.begin(); - it != speaker_list.end(); ++it) - { - const LLPointer& speaker = *it; - if (*id == speaker->mID) - { - return false; - } - } - } - } - - return true; -} - -void LLFloaterIMSession::addSessionParticipants(const uuid_vec_t& uuids) -{ - if (mIsP2PChat) - { - LLSD payload; - LLSD args; - - LLNotificationsUtil::add("ConfirmAddingChatParticipants", args, payload, - boost::bind(&LLFloaterIMSession::addP2PSessionParticipants, this, _1, _2, uuids)); - } - else - { - if(findInstance(mSessionID)) - { - // remember whom we have invited, to notify others later, when the invited ones actually join - mInvitedParticipants.insert(mInvitedParticipants.end(), uuids.begin(), uuids.end()); - } - - inviteToSession(uuids); - } -} - -void LLFloaterIMSession::addP2PSessionParticipants(const LLSD& notification, const LLSD& response, const uuid_vec_t& uuids) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option != 0) - { - return; - } - - LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID); - - // first check whether this is a voice session - bool is_voice_call = voice_channel != NULL && voice_channel->isActive(); - - uuid_vec_t temp_ids; - - // Add the initial participant of a P2P session - temp_ids.push_back(mOtherParticipantUUID); - temp_ids.insert(temp_ids.end(), uuids.begin(), uuids.end()); - - // then we can close the current session - if(findInstance(mSessionID)) - { - onClose(false); - - // remember whom we have invited, to notify others later, when the invited ones actually join - mInvitedParticipants.insert(mInvitedParticipants.end(), uuids.begin(), uuids.end()); - } - - // we start a new session so reset the initialization flag - mSessionInitialized = false; - - - - // Start a new ad hoc voice call if we invite new participants to a P2P call, - // or start a text chat otherwise. - if (is_voice_call) - { - LLAvatarActions::startAdhocCall(temp_ids, mSessionID); - } - else - { - LLAvatarActions::startConference(temp_ids, mSessionID); - } -} - -void LLFloaterIMSession::sendParticipantsAddedNotification(const uuid_vec_t& uuids) -{ - std::string names_string; - LLAvatarActions::buildResidentsString(uuids, names_string); - LLStringUtil::format_map_t args; - args["[NAME]"] = names_string; - - sendMsg(getString(uuids.size() > 1 ? "multiple_participants_added" : "participant_added", args)); -} - -void LLFloaterIMSession::boundVoiceChannel() -{ - LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID); - if(voice_channel) - { - mVoiceChannelStateChangeConnection = voice_channel->setStateChangedCallback( - boost::bind(&LLFloaterIMSession::onVoiceChannelStateChanged, this, _1, _2)); - - //call (either p2p, group or ad-hoc) can be already in started state - bool callIsActive = voice_channel->getState() >= LLVoiceChannel::STATE_CALL_STARTED; - updateCallBtnState(callIsActive); - } -} - -void LLFloaterIMSession::onCallButtonClicked() -{ - LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID); - if (voice_channel) - { - bool is_call_active = voice_channel->getState() >= LLVoiceChannel::STATE_CALL_STARTED; - if (is_call_active) - { - gIMMgr->endCall(mSessionID); - } - else - { - gIMMgr->startCall(mSessionID); - } - } -} - -void LLFloaterIMSession::onChange(EStatusType status, const std::string &channelURI, bool proximal) -{ - if(status != STATUS_JOINING && status != STATUS_LEFT_CHANNEL) - { - enableDisableCallBtn(); - } -} - -void LLFloaterIMSession::onVoiceChannelStateChanged( - const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state) -{ - bool callIsActive = new_state >= LLVoiceChannel::STATE_CALL_STARTED; - updateCallBtnState(callIsActive); -} - -void LLFloaterIMSession::updateSessionName(const std::string& name) -{ - if (!name.empty()) - { - LLFloaterIMSessionTab::updateSessionName(name); - mTypingStart.setArg("[NAME]", name); - setTitle (mOtherTyping ? mTypingStart.getString() : name); - mSessionNameUpdatedForTyping = mOtherTyping; - } -} - -//static -LLFloaterIMSession* LLFloaterIMSession::show(const LLUUID& session_id) -{ - closeHiddenIMToasts(); - - if (!gIMMgr->hasSession(session_id)) - return NULL; - - // Test the existence of the floater before we try to create it - bool exist = findInstance(session_id); - - // Get the floater: this will create the instance if it didn't exist - LLFloaterIMSession* floater = getInstance(session_id); - if (!floater) - return NULL; - - LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); - - // Do not add again existing floaters - if (!exist) - { - // LLTabContainer::eInsertionPoint i_pt = user_initiated ? LLTabContainer::RIGHT_OF_CURRENT : LLTabContainer::END; - // TODO: mantipov: use LLTabContainer::RIGHT_OF_CURRENT if it exists - LLTabContainer::eInsertionPoint i_pt = LLTabContainer::END; - if (floater_container) - { - floater_container->addFloater(floater, true, i_pt); - } - } - - floater->openFloater(floater->getKey()); - - floater->setVisible(true); - - return floater; -} -//static -LLFloaterIMSession* LLFloaterIMSession::findInstance(const LLUUID& session_id) -{ - LLFloaterIMSession* conversation = - LLFloaterReg::findTypedInstance("impanel", session_id); - - return conversation; -} - -LLFloaterIMSession* LLFloaterIMSession::getInstance(const LLUUID& session_id) -{ - LLFloaterIMSession* conversation = - LLFloaterReg::getTypedInstance("impanel", session_id); - - return conversation; -} - -void LLFloaterIMSession::onClose(bool app_quitting) -{ - setTyping(false); - - // The source of much argument and design thrashing - // Should the window hide or the session close when the X is clicked? - // - // Last change: - // EXT-3516 X Button should end IM session, _ button should hide - gIMMgr->leaveSession(mSessionID); - // *TODO: Study why we need to restore the floater before we close it. - // Might be because we want to save some state data in some clean open state. - LLFloaterIMSessionTab::restoreFloater(); - // Clean up the conversation *after* the session has been ended - LLFloaterIMSessionTab::onClose(app_quitting); -} - -void LLFloaterIMSession::setDocked(bool docked, bool pop_on_undock) -{ - // update notification channel state - LLNotificationsUI::LLScreenChannel* channel = static_cast - (LLNotificationsUI::LLChannelManager::getInstance()-> - findChannelByID(LLNotificationsUI::NOTIFICATION_CHANNEL_UUID)); - - if(!isChatMultiTab()) - { - LLTransientDockableFloater::setDocked(docked, pop_on_undock); - } - - // update notification channel state - if(channel) - { - channel->updateShowToastsState(); - channel->redrawToasts(); - } -} - -void LLFloaterIMSession::setMinimized(bool b) -{ - bool wasMinimized = isMinimized(); - LLFloaterIMSessionTab::setMinimized(b); - - //Switching from minimized state to un-minimized state - if(wasMinimized && !b) - { - //When in DND mode, remove stored IM notifications - //Nearby chat (Null) IMs are not stored while in DND mode, so can ignore removal - if(gAgent.isDoNotDisturb()) - { - LLDoNotDisturbNotificationStorage::getInstance()->removeNotification(LLDoNotDisturbNotificationStorage::toastName, mSessionID); - } - } -} - -void LLFloaterIMSession::setVisible(bool visible) -{ - LLNotificationsUI::LLScreenChannel* channel = static_cast - (LLNotificationsUI::LLChannelManager::getInstance()-> - findChannelByID(LLNotificationsUI::NOTIFICATION_CHANNEL_UUID)); - - LLFloaterIMSessionTab::setVisible(visible); - - // update notification channel state - if(channel) - { - channel->updateShowToastsState(); - channel->redrawToasts(); - } - - if(!visible) - { - LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); - if (NULL != chiclet_panelp) - { - LLIMChiclet * chicletp = chiclet_panelp->findChiclet(mSessionID); - if(NULL != chicletp) - { - chicletp->setToggleState(false); - } - } - } - - if (visible && isInVisibleChain()) - { - sIMFloaterShowedSignal(mSessionID); - updateMessages(); - } - -} - -bool LLFloaterIMSession::getVisible() -{ - bool visible; - - if(isChatMultiTab()) - { - LLFloaterIMContainer* im_container = - LLFloaterIMContainer::getInstance(); - - // Treat inactive floater as invisible. - bool is_active = im_container->getActiveFloater() == this; - - //torn off floater is always inactive - if (!is_active && getHost() != im_container) - { - visible = LLTransientDockableFloater::getVisible(); - } - else - { - // getVisible() returns true when Tabbed IM window is minimized. - visible = is_active && !im_container->isMinimized() - && im_container->getVisible(); - } - } - else - { - visible = LLTransientDockableFloater::getVisible(); - } - - return visible; -} - -void LLFloaterIMSession::setFocus(bool focus) -{ - LLFloaterIMSessionTab::setFocus(focus); - - //When in DND mode, remove stored IM notifications - //Nearby chat (Null) IMs are not stored while in DND mode, so can ignore removal - if(focus && gAgent.isDoNotDisturb()) - { - LLDoNotDisturbNotificationStorage::getInstance()->removeNotification(LLDoNotDisturbNotificationStorage::toastName, mSessionID); - } -} - -//static -bool LLFloaterIMSession::toggle(const LLUUID& session_id) -{ - if(!isChatMultiTab()) - { - LLFloaterIMSession* floater = LLFloaterReg::findTypedInstance( - "impanel", session_id); - if (floater && floater->getVisible() && floater->hasFocus()) - { - // clicking on chiclet to close floater just hides it to maintain existing - // scroll/text entry state - floater->setVisible(false); - return false; - } - else if(floater && ((!floater->isDocked() || floater->getVisible()) && !floater->hasFocus())) - { - floater->setVisible(true); - floater->setFocus(true); - return true; - } - } - - // ensure the list of messages is updated when floater is made visible - show(session_id); - return true; -} - -void LLFloaterIMSession::sessionInitReplyReceived(const LLUUID& im_session_id) -{ - mSessionInitialized = true; - - //will be different only for an ad-hoc im session - if (mSessionID != im_session_id) - { - initIMSession(im_session_id); - buildConversationViewParticipant(); - } - - initIMFloater(); - LLFloaterIMSessionTab::updateGearBtn(); - //*TODO here we should remove "starting session..." warning message if we added it in postBuild() (IB) - - //need to send delayed messages collected while waiting for session initialization - if (mQueuedMsgsForInit.size()) - { - LLSD::array_iterator iter; - for ( iter = mQueuedMsgsForInit.beginArray(); - iter != mQueuedMsgsForInit.endArray(); ++iter) - { - LLIMModel::sendMessage(iter->asString(), mSessionID, - mOtherParticipantUUID, mDialog); - } - - mQueuedMsgsForInit.clear(); - } -} - -void LLFloaterIMSession::updateMessages() -{ - std::list messages; - - // we shouldn't reset unread message counters if IM floater doesn't have focus - LLIMModel::instance().getMessages( - mSessionID, messages, mLastMessageIndex + 1, hasFocus()); - - if (messages.size()) - { - std::ostringstream message; - std::list::const_reverse_iterator iter = messages.rbegin(); - std::list::const_reverse_iterator iter_end = messages.rend(); - for (; iter != iter_end; ++iter) - { - LLSD msg = *iter; - - std::string time = msg["time"].asString(); - LLUUID from_id = msg["from_id"].asUUID(); - std::string from = msg["from"].asString(); - std::string message = msg["message"].asString(); - bool is_history = msg["is_history"].asBoolean(); - bool is_region_msg = msg["is_region_msg"].asBoolean(); - - LLChat chat; - chat.mFromID = from_id; - chat.mSessionID = mSessionID; - chat.mFromName = from; - chat.mTimeStr = time; - chat.mChatStyle = is_history ? CHAT_STYLE_HISTORY : chat.mChatStyle; - if (is_region_msg) - { - chat.mSourceType = CHAT_SOURCE_REGION; - } - - // process offer notification - if (msg.has("notification_id")) - { - chat.mNotifId = msg["notification_id"].asUUID(); - // if notification exists - embed it - if (LLNotificationsUtil::find(chat.mNotifId) != NULL) - { - // remove embedded notification from channel - LLNotificationsUI::LLScreenChannel* channel = static_cast - (LLNotificationsUI::LLChannelManager::getInstance()-> - findChannelByID(LLNotificationsUI::NOTIFICATION_CHANNEL_UUID)); - if (getVisible()) - { - // toast will be automatically closed since it is not storable toast - channel->hideToast(chat.mNotifId); - } - } - // if notification doesn't exist - try to use next message which should be log entry - else - { - continue; - } - } - //process text message - else - { - chat.mText = message; - } - - // Add the message to the chat log - appendMessage(chat); - mLastMessageIndex = msg["index"].asInteger(); - - // if it is a notification - next message is a notification history log, so skip it - if (chat.mNotifId.notNull() && LLNotificationsUtil::find(chat.mNotifId) != NULL) - { - if (++iter == iter_end) - { - break; - } - else - { - mLastMessageIndex++; - } - } - } - } -} - -void LLFloaterIMSession::reloadMessages(bool clean_messages/* = false*/) -{ - if (clean_messages) - { - LLIMModel::LLIMSession * sessionp = LLIMModel::instance().findIMSession(mSessionID); - - if (NULL != sessionp) - { - sessionp->loadHistory(); - } - } - - mChatHistory->clear(); - mLastMessageIndex = -1; - updateMessages(); - mInputEditor->setFont(LLViewerChat::getChatFont()); -} - -// static -void LLFloaterIMSession::onInputEditorFocusReceived( LLFocusableElement* caller, void* userdata ) -{ - LLFloaterIMSession* self= (LLFloaterIMSession*) userdata; - - // Allow enabling the LLFloaterIMSession input editor only if session can accept text - LLIMModel::LLIMSession* im_session = - LLIMModel::instance().findIMSession(self->mSessionID); - if( im_session && im_session->mTextIMPossible && !self->mInputEditor->getReadOnly()) - { - //in disconnected state IM input editor should be disabled - self->mInputEditor->setEnabled(!gDisconnected); - } -} - -// static -void LLFloaterIMSession::onInputEditorFocusLost(LLFocusableElement* caller, void* userdata) -{ - LLFloaterIMSession* self = (LLFloaterIMSession*) userdata; - self->setTyping(false); -} - -// static -void LLFloaterIMSession::onInputEditorKeystroke(LLTextEditor* caller, void* userdata) -{ - LLFloaterIMSession* self = (LLFloaterIMSession*)userdata; - LLFloaterIMContainer* im_box = LLFloaterIMContainer::findInstance(); - if (im_box) - { - im_box->flashConversationItemWidget(self->mSessionID,false); - } - std::string text = self->mInputEditor->getText(); - - // Deleting all text counts as stopping typing. - self->setTyping(!text.empty()); -} - -void LLFloaterIMSession::setTyping(bool typing) -{ - if ( typing ) - { - // Started or proceeded typing, reset the typing timeout timer - mTypingTimeoutTimer.reset(); - } - - if ( mMeTyping != typing ) - { - // Typing state is changed - mMeTyping = typing; - // So, should send current state - mShouldSendTypingState = true; - // In case typing is started, send state after some delay - mTypingTimer.reset(); - } - - // Don't want to send typing indicators to multiple people, potentially too - // much network traffic. Only send in person-to-person IMs. - if ( mShouldSendTypingState && mDialog == IM_NOTHING_SPECIAL ) - { - if ( mMeTyping ) - { - if ( mTypingTimer.getElapsedTimeF32() > 1.f ) - { - // Still typing, send 'start typing' notification - LLIMModel::instance().sendTypingState(mSessionID, mOtherParticipantUUID, true); - mShouldSendTypingState = false; - mMeTypingTimer.reset(); - } - } - else - { - // Send 'stop typing' notification immediately - LLIMModel::instance().sendTypingState(mSessionID, mOtherParticipantUUID, false); - mShouldSendTypingState = false; - } - } - - if (!mIsNearbyChat) - { - LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - if (speaker_mgr) - { - speaker_mgr->setSpeakerTyping(gAgent.getID(), false); - } - } -} - -void LLFloaterIMSession::processIMTyping(const LLUUID& from_id, bool typing) -{ - LL_DEBUGS("TypingMsgs") << "typing=" << typing << LL_ENDL; - if ( typing ) - { - // other user started typing - addTypingIndicator(from_id); - mOtherTypingTimer.reset(); - } - else - { - // other user stopped typing - removeTypingIndicator(from_id); - } -} - -void LLFloaterIMSession::processAgentListUpdates(const LLSD& body) -{ - uuid_vec_t joined_uuids; - - if (body.isMap() && body.has("agent_updates") && body["agent_updates"].isMap()) - { - LLSD::map_const_iterator update_it; - for(update_it = body["agent_updates"].beginMap(); - update_it != body["agent_updates"].endMap(); - ++update_it) - { - LLUUID agent_id(update_it->first); - LLSD agent_data = update_it->second; - - if (agent_data.isMap()) - { - // store the new participants in joined_uuids - if (agent_data.has("transition") && agent_data["transition"].asString() == "ENTER") - { - joined_uuids.push_back(agent_id); - } - - // process the moderator mutes - if (agent_id == gAgentID && agent_data.has("info") && agent_data["info"].has("mutes")) - { - bool moderator_muted_text = agent_data["info"]["mutes"]["text"].asBoolean(); - mInputEditor->setEnabled(!moderator_muted_text); - std::string label; - if (moderator_muted_text) - label = LLTrans::getString("IM_muted_text_label"); - else - label = LLTrans::getString("IM_to_label") + " " + LLIMModel::instance().getName(mSessionID); - mInputEditor->setLabel(label); - - if (moderator_muted_text) - LLNotificationsUtil::add("TextChatIsMutedByModerator"); - } - } - } - } - - // the vectors need to be sorted for computing the intersection and difference - std::sort(mInvitedParticipants.begin(), mInvitedParticipants.end()); - std::sort(joined_uuids.begin(), joined_uuids.end()); - - uuid_vec_t intersection; // uuids of invited residents who have joined the conversation - std::set_intersection(mInvitedParticipants.begin(), mInvitedParticipants.end(), - joined_uuids.begin(), joined_uuids.end(), - std::back_inserter(intersection)); - - if (intersection.size() > 0) - { - sendParticipantsAddedNotification(intersection); - } - - // Remove all joined participants from invited array. - // The difference between the two vectors (the elements in mInvitedParticipants which are not in joined_uuids) - // is placed at the beginning of mInvitedParticipants, then all other elements are erased. - mInvitedParticipants.erase(std::set_difference(mInvitedParticipants.begin(), mInvitedParticipants.end(), - joined_uuids.begin(), joined_uuids.end(), - mInvitedParticipants.begin()), - mInvitedParticipants.end()); -} - -void LLFloaterIMSession::processSessionUpdate(const LLSD& session_update) -{ - // *TODO : verify following code when moderated mode will be implemented - if ( false && session_update.has("moderated_mode") && - session_update["moderated_mode"].has("voice") ) - { - bool voice_moderated = session_update["moderated_mode"]["voice"]; - const std::string session_label = LLIMModel::instance().getName(mSessionID); - - if (voice_moderated) - { - setTitle(session_label + std::string(" ") - + LLTrans::getString("IM_moderated_chat_label")); - } - else - { - setTitle(session_label); - } - - // *TODO : uncomment this when/if LLPanelActiveSpeakers panel will be added - //update the speakers dropdown too - //mSpeakerPanel->setVoiceModerationCtrlMode(voice_moderated); - } -} - -// virtual -void LLFloaterIMSession::draw() -{ - // add people who were added via dropPerson() - if (!mPendingParticipants.empty()) - { - addSessionParticipants(mPendingParticipants); - mPendingParticipants.clear(); - } - - LLFloaterIMSessionTab::draw(); -} - -// virtual -bool LLFloaterIMSession::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - if (cargo_type == DAD_PERSON) - { - if (dropPerson(static_cast(cargo_data), drop)) - { - *accept = ACCEPT_YES_MULTI; - } - else - { - *accept = ACCEPT_NO; - } - } - else if (mDialog == IM_NOTHING_SPECIAL) - { - LLToolDragAndDrop::handleGiveDragAndDrop(mOtherParticipantUUID, mSessionID, drop, - cargo_type, cargo_data, accept); - } - - return true; -} - -bool LLFloaterIMSession::dropPerson(LLUUID* person_id, bool drop) -{ - bool res = person_id && person_id->notNull(); - if(res) - { - uuid_vec_t ids; - ids.push_back(*person_id); - - res = canAddSelectedToChat(ids); - if(res && drop) - { - // these people will be added during the next draw() call - // (so they can be added all at once) - mPendingParticipants.push_back(*person_id); - } - } - - return res; -} - -bool LLFloaterIMSession::isInviteAllowed() const -{ - return ( (IM_SESSION_CONFERENCE_START == mDialog) - || (IM_SESSION_INVITE == mDialog && !gAgent.isInGroup(mSessionID)) - || mIsP2PChat); -} - -bool LLFloaterIMSession::inviteToSession(const uuid_vec_t& ids) -{ - LLViewerRegion* region = gAgent.getRegion(); - bool is_region_exist = region != NULL; - - if (is_region_exist) - { - S32 count = ids.size(); - - if( isInviteAllowed() && (count > 0) ) - { - LL_INFOS() << "LLFloaterIMSession::inviteToSession() - inviting participants" << LL_ENDL; - - std::string url = region->getCapability("ChatSessionRequest"); - - LLSD data; - data["params"] = LLSD::emptyArray(); - for (int i = 0; i < count; i++) - { - data["params"].append(ids[i]); - } - data["method"] = "invite"; - data["session-id"] = mSessionID; - - LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data, - "Session invite sent", "Session invite failed"); - } - else - { - LL_INFOS() << "LLFloaterIMSession::inviteToSession -" - << " no need to invite agents for " - << mDialog << LL_ENDL; - // successful add, because everyone that needed to get added - // was added. - } - } - - return is_region_exist; -} - -void LLFloaterIMSession::addTypingIndicator(const LLUUID& from_id) -{ -/* Operation of " is typing" state machine: -Not Typing state: - - User types in P2P IM chat ... Send Start Typing, save Started time, - start Idle Timer (N seconds) go to Typing state - -Typing State: - - User enters a non-return character: if Now - Started > ME_TYPING_TIMEOUT, send - Start Typing, restart Idle Timer - User enters a return character: stop Idle Timer, send IM and Stop - Typing, go to Not Typing state - Idle Timer expires: send Stop Typing, go to Not Typing state - -The recipient has a complementary state machine in which a Start Typing -that is not followed by either an IM or another Start Typing within OTHER_TYPING_TIMEOUT -seconds switches the sender out of typing state. - -This has the nice quality of being self-healing for lost start/stop -messages while adding messages only for the (relatively rare) case of a -user who types a very long message (one that takes more than ME_TYPING_TIMEOUT seconds -to type). - -Note: OTHER_TYPING_TIMEOUT must be > ME_TYPING_TIMEOUT for proper operation of the state machine - -*/ - - // We may have lost a "stop-typing" packet, don't add it twice - if (from_id.notNull() && !mOtherTyping) - { - mOtherTyping = true; - mOtherTypingTimer.reset(); - // Save im_info so that removeTypingIndicator can be properly called because a timeout has occurred - mImFromId = from_id; - - // Update speaker - LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - if ( speaker_mgr ) - { - speaker_mgr->setSpeakerTyping(from_id, true); - } - } -} - -void LLFloaterIMSession::removeTypingIndicator(const LLUUID& from_id) -{ - if (mOtherTyping) - { - mOtherTyping = false; - - if (from_id.notNull()) - { - // Update speaker - LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - if (speaker_mgr) - { - speaker_mgr->setSpeakerTyping(from_id, false); - } - } - } -} - -// static -void LLFloaterIMSession::closeHiddenIMToasts() -{ - class IMToastMatcher: public LLNotificationsUI::LLScreenChannel::Matcher - { - public: - bool matches(const LLNotificationPtr notification) const - { - // "notifytoast" type of notifications is reserved for IM notifications - return "notifytoast" == notification->getType(); - } - }; - - LLNotificationsUI::LLScreenChannel* channel = - LLNotificationsUI::LLChannelManager::getNotificationScreenChannel(); - if (channel != NULL) - { - channel->closeHiddenToasts(IMToastMatcher()); - } -} -// static -void LLFloaterIMSession::confirmLeaveCallCallback(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - const LLSD& payload = notification["payload"]; - LLUUID session_id = payload["session_id"]; - - LLFloater* im_floater = findInstance(session_id); - if (option == 0 && im_floater != NULL) - { - im_floater->closeFloater(); - } - - return; -} - -// static -void LLFloaterIMSession::sRemoveTypingIndicator(const LLSD& data) -{ - LLUUID session_id = data["session_id"]; - if (session_id.isNull()) - return; - - LLUUID from_id = data["from_id"]; - if (gAgentID == from_id || LLUUID::null == from_id) - return; - - LLFloaterIMSession* floater = LLFloaterIMSession::findInstance(session_id); - if (!floater) - return; - - if (IM_NOTHING_SPECIAL != floater->mDialog) - return; - - floater->removeTypingIndicator(); -} - -// static -void LLFloaterIMSession::onIMChicletCreated( const LLUUID& session_id ) -{ - LLFloaterIMSession::addToHost(session_id); -} - -boost::signals2::connection LLFloaterIMSession::setIMFloaterShowedCallback(const floater_showed_signal_t::slot_type& cb) -{ - return LLFloaterIMSession::sIMFloaterShowedSignal.connect(cb); -} +/** + * @file llfloaterimsession.cpp + * @brief LLFloaterIMSession class definition + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterimsession.h" + +#include "lldraghandle.h" +#include "llnotificationsutil.h" + +#include "llagent.h" +#include "llappviewer.h" +#include "llavataractions.h" +#include "llavatarnamecache.h" +#include "llbutton.h" +#include "llchannelmanager.h" +#include "llchiclet.h" +#include "llchicletbar.h" +#include "lldonotdisturbnotificationstorage.h" +#include "llfloaterreg.h" +#include "llfloateravatarpicker.h" +#include "llfloaterimcontainer.h" // to replace separate IM Floaters with multifloater container +#include "llinventoryfunctions.h" +//#include "lllayoutstack.h" +#include "llchatentry.h" +#include "lllogchat.h" +#include "llscreenchannel.h" +#include "llsyswellwindow.h" +#include "lltrans.h" +#include "llchathistory.h" +#include "llnotifications.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" +#include "lltransientfloatermgr.h" +#include "llinventorymodel.h" +#include "llrootview.h" +#include "llspeakers.h" +#include "llviewerchat.h" +#include "llnotificationmanager.h" +#include "llautoreplace.h" +#include "llcorehttputil.h" + +const F32 ME_TYPING_TIMEOUT = 4.0f; +const F32 OTHER_TYPING_TIMEOUT = 9.0f; + +floater_showed_signal_t LLFloaterIMSession::sIMFloaterShowedSignal; + +LLFloaterIMSession::LLFloaterIMSession(const LLUUID& session_id) + : LLFloaterIMSessionTab(session_id), + mLastMessageIndex(-1), + mDialog(IM_NOTHING_SPECIAL), + mTypingStart(), + mShouldSendTypingState(false), + mMeTyping(false), + mOtherTyping(false), + mSessionNameUpdatedForTyping(false), + mTypingTimer(), + mTypingTimeoutTimer(), + mPositioned(false), + mSessionInitialized(false), + mMeTypingTimer(), + mOtherTypingTimer() +{ + mIsNearbyChat = false; + + initIMSession(session_id); + + setOverlapsScreenChannel(true); + + LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::IM, this); + mEnableCallbackRegistrar.add("Avatar.EnableGearItem", boost::bind(&LLFloaterIMSession::enableGearMenuItem, this, _2)); + mCommitCallbackRegistrar.add("Avatar.GearDoToSelected", boost::bind(&LLFloaterIMSession::GearDoToSelected, this, _2)); + mEnableCallbackRegistrar.add("Avatar.CheckGearItem", boost::bind(&LLFloaterIMSession::checkGearMenuItem, this, _2)); + + setDocked(true); +} + + +// virtual +void LLFloaterIMSession::refresh() +{ + if (mMeTyping) +{ + // Send an additional Start Typing packet every ME_TYPING_TIMEOUT seconds + if (mMeTypingTimer.getElapsedTimeF32() > ME_TYPING_TIMEOUT && false == mShouldSendTypingState) + { + LL_DEBUGS("TypingMsgs") << "Send additional Start Typing packet" << LL_ENDL; + LLIMModel::instance().sendTypingState(mSessionID, mOtherParticipantUUID, true); + mMeTypingTimer.reset(); + } + + // Time out if user hasn't typed for a while. + if (mTypingTimeoutTimer.getElapsedTimeF32() > LLAgent::TYPING_TIMEOUT_SECS) + { + setTyping(false); + LL_DEBUGS("TypingMsgs") << "Send stop typing due to timeout" << LL_ENDL; + } + } + + // Clear message if no data received for OTHER_TYPING_TIMEOUT seconds + if (mOtherTyping && mOtherTypingTimer.getElapsedTimeF32() > OTHER_TYPING_TIMEOUT) + { + LL_DEBUGS("TypingMsgs") << "Received: is typing cleared due to timeout" << LL_ENDL; + removeTypingIndicator(mImFromId); + mOtherTyping = false; + } + +} + +// virtual +void LLFloaterIMSession::onTearOffClicked() +{ + LLFloaterIMSessionTab::onTearOffClicked(); +} + +// virtual +void LLFloaterIMSession::onClickCloseBtn(bool) +{ + LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(mSessionID); + + if (session != NULL) + { + bool is_call_with_chat = session->isGroupSessionType() + || session->isAdHocSessionType() || session->isP2PSessionType(); + + LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID); + + if (is_call_with_chat && voice_channel != NULL + && voice_channel->isActive()) + { + LLSD payload; + payload["session_id"] = mSessionID; + LLNotificationsUtil::add("ConfirmLeaveCall", LLSD(), payload, confirmLeaveCallCallback); + return; + } + } + else + { + LL_WARNS() << "Empty session with id: " << (mSessionID.asString()) << LL_ENDL; + } + + LLFloaterIMSessionTab::onClickCloseBtn(); +} + +/* static */ +void LLFloaterIMSession::newIMCallback(const LLSD& data) +{ + if (data["num_unread"].asInteger() > 0 || data["from_id"].asUUID().isNull()) + { + LLUUID session_id = data["session_id"].asUUID(); + + LLFloaterIMSession* floater = LLFloaterReg::findTypedInstance("impanel", session_id); + + // update if visible, otherwise will be updated when opened + if (floater && floater->isInVisibleChain()) + { + floater->updateMessages(); + } + } +} + +void LLFloaterIMSession::onVisibilityChanged(const LLSD& new_visibility) +{ + bool visible = new_visibility.asBoolean(); + + LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID); + + if (visible && voice_channel && + voice_channel->getState() == LLVoiceChannel::STATE_CONNECTED) + { + LLFloaterReg::showInstance("voice_call", mSessionID); + } + else + { + LLFloaterReg::hideInstance("voice_call", mSessionID); + } +} + +void LLFloaterIMSession::onSendMsg( LLUICtrl* ctrl, void* userdata ) +{ + LLFloaterIMSession* self = (LLFloaterIMSession*) userdata; + self->sendMsgFromInputEditor(); + self->setTyping(false); +} + +bool LLFloaterIMSession::enableGearMenuItem(const LLSD& userdata) +{ + std::string command = userdata.asString(); + uuid_vec_t selected_uuids; + selected_uuids.push_back(mOtherParticipantUUID); + + LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); + return floater_container->enableContextMenuItem(command, selected_uuids); +} + +void LLFloaterIMSession::GearDoToSelected(const LLSD& userdata) +{ + std::string command = userdata.asString(); + uuid_vec_t selected_uuids; + selected_uuids.push_back(mOtherParticipantUUID); + + LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); + floater_container->doToParticipants(command, selected_uuids); +} + +bool LLFloaterIMSession::checkGearMenuItem(const LLSD& userdata) +{ + std::string command = userdata.asString(); + uuid_vec_t selected_uuids; + selected_uuids.push_back(mOtherParticipantUUID); + + LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); + return floater_container->checkContextMenuItem(command, selected_uuids); +} + +void LLFloaterIMSession::sendMsgFromInputEditor() +{ + if (gAgent.isGodlike() + || (mDialog != IM_NOTHING_SPECIAL) + || !mOtherParticipantUUID.isNull()) + { + if (mInputEditor) + { + LLWString text = mInputEditor->getWText(); + LLWStringUtil::trim(text); + LLWStringUtil::replaceChar(text,182,'\n'); // Convert paragraph symbols back into newlines. + if(!text.empty()) + { + updateUsedEmojis(text); + + // Truncate and convert to UTF8 for transport + std::string utf8_text = wstring_to_utf8str(text); + + sendMsg(utf8_text); + + mInputEditor->setText(LLStringUtil::null); + } + } + } + else + { + LL_INFOS() << "Cannot send IM to everyone unless you're a god." << LL_ENDL; + } +} + +void LLFloaterIMSession::sendMsg(const std::string& msg) +{ + const std::string utf8_text = utf8str_truncate(msg, MAX_MSG_BUF_SIZE - 1); + + if (mSessionInitialized) + { + LLIMModel::sendMessage(utf8_text, mSessionID, mOtherParticipantUUID, mDialog); + } + else + { + //queue up the message to send once the session is initialized + mQueuedMsgsForInit.append(utf8_text); + } + + updateMessages(); +} + +LLFloaterIMSession::~LLFloaterIMSession() +{ + mVoiceChannelStateChangeConnection.disconnect(); + if(LLVoiceClient::instanceExists()) + { + LLVoiceClient::getInstance()->removeObserver(this); + } + + LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::IM, this); +} + + +void LLFloaterIMSession::initIMSession(const LLUUID& session_id) +{ + // Change the floater key to bind it to a new session. + setKey(session_id); + + mSessionID = session_id; + mSession = LLIMModel::getInstance()->findIMSession(mSessionID); + + if (mSession) + { + mIsP2PChat = mSession->isP2PSessionType(); + mSessionInitialized = mSession->mSessionInitialized; + mDialog = mSession->mType; + } +} + +void LLFloaterIMSession::initIMFloater() +{ + const LLUUID& other_party_id = + LLIMModel::getInstance()->getOtherParticipantID(mSessionID); + if (other_party_id.notNull()) + { + mOtherParticipantUUID = other_party_id; + } + + boundVoiceChannel(); + + mTypingStart = LLTrans::getString("IM_typing_start_string"); + + // Show control panel in torn off floaters only. + mParticipantListPanel->setVisible(!getHost() && gSavedSettings.getBOOL("IMShowControlPanel")); + + // Disable input editor if session cannot accept text + if ( mSession && !mSession->mTextIMPossible ) + { + mInputEditor->setEnabled(false); + mInputEditor->setLabel(LLTrans::getString("IM_unavailable_text_label")); + } + + if (!mIsP2PChat) + { + std::string session_name(LLIMModel::instance().getName(mSessionID)); + updateSessionName(session_name); + } +} + +//virtual +bool LLFloaterIMSession::postBuild() +{ + bool result = LLFloaterIMSessionTab::postBuild(); + + mInputEditor->setMaxTextLength(1023); + mInputEditor->setAutoreplaceCallback(boost::bind(&LLAutoReplace::autoreplaceCallback, LLAutoReplace::getInstance(), _1, _2, _3, _4, _5)); + mInputEditor->setFocusReceivedCallback( boost::bind(onInputEditorFocusReceived, _1, this) ); + mInputEditor->setFocusLostCallback( boost::bind(onInputEditorFocusLost, _1, this) ); + mInputEditor->setKeystrokeCallback( boost::bind(onInputEditorKeystroke, _1, this) ); + mInputEditor->setCommitCallback(boost::bind(onSendMsg, _1, this)); + + setDocked(true); + + LLButton* add_btn = getChild("add_btn"); + + // Allow to add chat participants depending on the session type + add_btn->setEnabled(isInviteAllowed()); + add_btn->setClickedCallback(boost::bind(&LLFloaterIMSession::onAddButtonClicked, this)); + + childSetAction("voice_call_btn", boost::bind(&LLFloaterIMSession::onCallButtonClicked, this)); + + LLVoiceClient::getInstance()->addObserver(this); + + //*TODO if session is not initialized yet, add some sort of a warning message like "starting session...blablabla" + //see LLFloaterIMPanel for how it is done (IB) + + initIMFloater(); + + return result; +} + +void LLFloaterIMSession::onAddButtonClicked() +{ + LLView * button = findChild("toolbar_panel")->findChild("add_btn"); + LLFloater* root_floater = gFloaterView->getParentFloater(this); + LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(boost::bind(&LLFloaterIMSession::addSessionParticipants, this, _1), true, true, false, root_floater->getName(), button); + if (!picker) + { + return; + } + + // Need to disable 'ok' button when selected users are already in conversation. + picker->setOkBtnEnableCb(boost::bind(&LLFloaterIMSession::canAddSelectedToChat, this, _1)); + + if (root_floater) + { + root_floater->addDependentFloater(picker); + } +} + +bool LLFloaterIMSession::canAddSelectedToChat(const uuid_vec_t& uuids) +{ + if (!mSession + || mDialog == IM_SESSION_GROUP_START + || (mDialog == IM_SESSION_INVITE && gAgent.isInGroup(mSessionID))) + { + return false; + } + + if (mIsP2PChat) + { + // For a P2P session just check if we are not adding the other participant. + + for (uuid_vec_t::const_iterator id = uuids.begin(); + id != uuids.end(); ++id) + { + if (*id == mOtherParticipantUUID) + { + return false; + } + } + } + else + { + // For a conference session we need to check against the list from LLSpeakerMgr, + // because this list may change when participants join or leave the session. + + LLSpeakerMgr::speaker_list_t speaker_list; + LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + if (speaker_mgr) + { + speaker_mgr->getSpeakerList(&speaker_list, true); + } + + for (uuid_vec_t::const_iterator id = uuids.begin(); + id != uuids.end(); ++id) + { + for (LLSpeakerMgr::speaker_list_t::const_iterator it = speaker_list.begin(); + it != speaker_list.end(); ++it) + { + const LLPointer& speaker = *it; + if (*id == speaker->mID) + { + return false; + } + } + } + } + + return true; +} + +void LLFloaterIMSession::addSessionParticipants(const uuid_vec_t& uuids) +{ + if (mIsP2PChat) + { + LLSD payload; + LLSD args; + + LLNotificationsUtil::add("ConfirmAddingChatParticipants", args, payload, + boost::bind(&LLFloaterIMSession::addP2PSessionParticipants, this, _1, _2, uuids)); + } + else + { + if(findInstance(mSessionID)) + { + // remember whom we have invited, to notify others later, when the invited ones actually join + mInvitedParticipants.insert(mInvitedParticipants.end(), uuids.begin(), uuids.end()); + } + + inviteToSession(uuids); + } +} + +void LLFloaterIMSession::addP2PSessionParticipants(const LLSD& notification, const LLSD& response, const uuid_vec_t& uuids) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) + { + return; + } + + LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID); + + // first check whether this is a voice session + bool is_voice_call = voice_channel != NULL && voice_channel->isActive(); + + uuid_vec_t temp_ids; + + // Add the initial participant of a P2P session + temp_ids.push_back(mOtherParticipantUUID); + temp_ids.insert(temp_ids.end(), uuids.begin(), uuids.end()); + + // then we can close the current session + if(findInstance(mSessionID)) + { + onClose(false); + + // remember whom we have invited, to notify others later, when the invited ones actually join + mInvitedParticipants.insert(mInvitedParticipants.end(), uuids.begin(), uuids.end()); + } + + // we start a new session so reset the initialization flag + mSessionInitialized = false; + + + + // Start a new ad hoc voice call if we invite new participants to a P2P call, + // or start a text chat otherwise. + if (is_voice_call) + { + LLAvatarActions::startAdhocCall(temp_ids, mSessionID); + } + else + { + LLAvatarActions::startConference(temp_ids, mSessionID); + } +} + +void LLFloaterIMSession::sendParticipantsAddedNotification(const uuid_vec_t& uuids) +{ + std::string names_string; + LLAvatarActions::buildResidentsString(uuids, names_string); + LLStringUtil::format_map_t args; + args["[NAME]"] = names_string; + + sendMsg(getString(uuids.size() > 1 ? "multiple_participants_added" : "participant_added", args)); +} + +void LLFloaterIMSession::boundVoiceChannel() +{ + LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID); + if(voice_channel) + { + mVoiceChannelStateChangeConnection = voice_channel->setStateChangedCallback( + boost::bind(&LLFloaterIMSession::onVoiceChannelStateChanged, this, _1, _2)); + + //call (either p2p, group or ad-hoc) can be already in started state + bool callIsActive = voice_channel->getState() >= LLVoiceChannel::STATE_CALL_STARTED; + updateCallBtnState(callIsActive); + } +} + +void LLFloaterIMSession::onCallButtonClicked() +{ + LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(mSessionID); + if (voice_channel) + { + bool is_call_active = voice_channel->getState() >= LLVoiceChannel::STATE_CALL_STARTED; + if (is_call_active) + { + gIMMgr->endCall(mSessionID); + } + else + { + gIMMgr->startCall(mSessionID); + } + } +} + +void LLFloaterIMSession::onChange(EStatusType status, const std::string &channelURI, bool proximal) +{ + if(status != STATUS_JOINING && status != STATUS_LEFT_CHANNEL) + { + enableDisableCallBtn(); + } +} + +void LLFloaterIMSession::onVoiceChannelStateChanged( + const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state) +{ + bool callIsActive = new_state >= LLVoiceChannel::STATE_CALL_STARTED; + updateCallBtnState(callIsActive); +} + +void LLFloaterIMSession::updateSessionName(const std::string& name) +{ + if (!name.empty()) + { + LLFloaterIMSessionTab::updateSessionName(name); + mTypingStart.setArg("[NAME]", name); + setTitle (mOtherTyping ? mTypingStart.getString() : name); + mSessionNameUpdatedForTyping = mOtherTyping; + } +} + +//static +LLFloaterIMSession* LLFloaterIMSession::show(const LLUUID& session_id) +{ + closeHiddenIMToasts(); + + if (!gIMMgr->hasSession(session_id)) + return NULL; + + // Test the existence of the floater before we try to create it + bool exist = findInstance(session_id); + + // Get the floater: this will create the instance if it didn't exist + LLFloaterIMSession* floater = getInstance(session_id); + if (!floater) + return NULL; + + LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); + + // Do not add again existing floaters + if (!exist) + { + // LLTabContainer::eInsertionPoint i_pt = user_initiated ? LLTabContainer::RIGHT_OF_CURRENT : LLTabContainer::END; + // TODO: mantipov: use LLTabContainer::RIGHT_OF_CURRENT if it exists + LLTabContainer::eInsertionPoint i_pt = LLTabContainer::END; + if (floater_container) + { + floater_container->addFloater(floater, true, i_pt); + } + } + + floater->openFloater(floater->getKey()); + + floater->setVisible(true); + + return floater; +} +//static +LLFloaterIMSession* LLFloaterIMSession::findInstance(const LLUUID& session_id) +{ + LLFloaterIMSession* conversation = + LLFloaterReg::findTypedInstance("impanel", session_id); + + return conversation; +} + +LLFloaterIMSession* LLFloaterIMSession::getInstance(const LLUUID& session_id) +{ + LLFloaterIMSession* conversation = + LLFloaterReg::getTypedInstance("impanel", session_id); + + return conversation; +} + +void LLFloaterIMSession::onClose(bool app_quitting) +{ + setTyping(false); + + // The source of much argument and design thrashing + // Should the window hide or the session close when the X is clicked? + // + // Last change: + // EXT-3516 X Button should end IM session, _ button should hide + gIMMgr->leaveSession(mSessionID); + // *TODO: Study why we need to restore the floater before we close it. + // Might be because we want to save some state data in some clean open state. + LLFloaterIMSessionTab::restoreFloater(); + // Clean up the conversation *after* the session has been ended + LLFloaterIMSessionTab::onClose(app_quitting); +} + +void LLFloaterIMSession::setDocked(bool docked, bool pop_on_undock) +{ + // update notification channel state + LLNotificationsUI::LLScreenChannel* channel = static_cast + (LLNotificationsUI::LLChannelManager::getInstance()-> + findChannelByID(LLNotificationsUI::NOTIFICATION_CHANNEL_UUID)); + + if(!isChatMultiTab()) + { + LLTransientDockableFloater::setDocked(docked, pop_on_undock); + } + + // update notification channel state + if(channel) + { + channel->updateShowToastsState(); + channel->redrawToasts(); + } +} + +void LLFloaterIMSession::setMinimized(bool b) +{ + bool wasMinimized = isMinimized(); + LLFloaterIMSessionTab::setMinimized(b); + + //Switching from minimized state to un-minimized state + if(wasMinimized && !b) + { + //When in DND mode, remove stored IM notifications + //Nearby chat (Null) IMs are not stored while in DND mode, so can ignore removal + if(gAgent.isDoNotDisturb()) + { + LLDoNotDisturbNotificationStorage::getInstance()->removeNotification(LLDoNotDisturbNotificationStorage::toastName, mSessionID); + } + } +} + +void LLFloaterIMSession::setVisible(bool visible) +{ + LLNotificationsUI::LLScreenChannel* channel = static_cast + (LLNotificationsUI::LLChannelManager::getInstance()-> + findChannelByID(LLNotificationsUI::NOTIFICATION_CHANNEL_UUID)); + + LLFloaterIMSessionTab::setVisible(visible); + + // update notification channel state + if(channel) + { + channel->updateShowToastsState(); + channel->redrawToasts(); + } + + if(!visible) + { + LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); + if (NULL != chiclet_panelp) + { + LLIMChiclet * chicletp = chiclet_panelp->findChiclet(mSessionID); + if(NULL != chicletp) + { + chicletp->setToggleState(false); + } + } + } + + if (visible && isInVisibleChain()) + { + sIMFloaterShowedSignal(mSessionID); + updateMessages(); + } + +} + +bool LLFloaterIMSession::getVisible() +{ + bool visible; + + if(isChatMultiTab()) + { + LLFloaterIMContainer* im_container = + LLFloaterIMContainer::getInstance(); + + // Treat inactive floater as invisible. + bool is_active = im_container->getActiveFloater() == this; + + //torn off floater is always inactive + if (!is_active && getHost() != im_container) + { + visible = LLTransientDockableFloater::getVisible(); + } + else + { + // getVisible() returns true when Tabbed IM window is minimized. + visible = is_active && !im_container->isMinimized() + && im_container->getVisible(); + } + } + else + { + visible = LLTransientDockableFloater::getVisible(); + } + + return visible; +} + +void LLFloaterIMSession::setFocus(bool focus) +{ + LLFloaterIMSessionTab::setFocus(focus); + + //When in DND mode, remove stored IM notifications + //Nearby chat (Null) IMs are not stored while in DND mode, so can ignore removal + if(focus && gAgent.isDoNotDisturb()) + { + LLDoNotDisturbNotificationStorage::getInstance()->removeNotification(LLDoNotDisturbNotificationStorage::toastName, mSessionID); + } +} + +//static +bool LLFloaterIMSession::toggle(const LLUUID& session_id) +{ + if(!isChatMultiTab()) + { + LLFloaterIMSession* floater = LLFloaterReg::findTypedInstance( + "impanel", session_id); + if (floater && floater->getVisible() && floater->hasFocus()) + { + // clicking on chiclet to close floater just hides it to maintain existing + // scroll/text entry state + floater->setVisible(false); + return false; + } + else if(floater && ((!floater->isDocked() || floater->getVisible()) && !floater->hasFocus())) + { + floater->setVisible(true); + floater->setFocus(true); + return true; + } + } + + // ensure the list of messages is updated when floater is made visible + show(session_id); + return true; +} + +void LLFloaterIMSession::sessionInitReplyReceived(const LLUUID& im_session_id) +{ + mSessionInitialized = true; + + //will be different only for an ad-hoc im session + if (mSessionID != im_session_id) + { + initIMSession(im_session_id); + buildConversationViewParticipant(); + } + + initIMFloater(); + LLFloaterIMSessionTab::updateGearBtn(); + //*TODO here we should remove "starting session..." warning message if we added it in postBuild() (IB) + + //need to send delayed messages collected while waiting for session initialization + if (mQueuedMsgsForInit.size()) + { + LLSD::array_iterator iter; + for ( iter = mQueuedMsgsForInit.beginArray(); + iter != mQueuedMsgsForInit.endArray(); ++iter) + { + LLIMModel::sendMessage(iter->asString(), mSessionID, + mOtherParticipantUUID, mDialog); + } + + mQueuedMsgsForInit.clear(); + } +} + +void LLFloaterIMSession::updateMessages() +{ + std::list messages; + + // we shouldn't reset unread message counters if IM floater doesn't have focus + LLIMModel::instance().getMessages( + mSessionID, messages, mLastMessageIndex + 1, hasFocus()); + + if (messages.size()) + { + std::ostringstream message; + std::list::const_reverse_iterator iter = messages.rbegin(); + std::list::const_reverse_iterator iter_end = messages.rend(); + for (; iter != iter_end; ++iter) + { + LLSD msg = *iter; + + std::string time = msg["time"].asString(); + LLUUID from_id = msg["from_id"].asUUID(); + std::string from = msg["from"].asString(); + std::string message = msg["message"].asString(); + bool is_history = msg["is_history"].asBoolean(); + bool is_region_msg = msg["is_region_msg"].asBoolean(); + + LLChat chat; + chat.mFromID = from_id; + chat.mSessionID = mSessionID; + chat.mFromName = from; + chat.mTimeStr = time; + chat.mChatStyle = is_history ? CHAT_STYLE_HISTORY : chat.mChatStyle; + if (is_region_msg) + { + chat.mSourceType = CHAT_SOURCE_REGION; + } + + // process offer notification + if (msg.has("notification_id")) + { + chat.mNotifId = msg["notification_id"].asUUID(); + // if notification exists - embed it + if (LLNotificationsUtil::find(chat.mNotifId) != NULL) + { + // remove embedded notification from channel + LLNotificationsUI::LLScreenChannel* channel = static_cast + (LLNotificationsUI::LLChannelManager::getInstance()-> + findChannelByID(LLNotificationsUI::NOTIFICATION_CHANNEL_UUID)); + if (getVisible()) + { + // toast will be automatically closed since it is not storable toast + channel->hideToast(chat.mNotifId); + } + } + // if notification doesn't exist - try to use next message which should be log entry + else + { + continue; + } + } + //process text message + else + { + chat.mText = message; + } + + // Add the message to the chat log + appendMessage(chat); + mLastMessageIndex = msg["index"].asInteger(); + + // if it is a notification - next message is a notification history log, so skip it + if (chat.mNotifId.notNull() && LLNotificationsUtil::find(chat.mNotifId) != NULL) + { + if (++iter == iter_end) + { + break; + } + else + { + mLastMessageIndex++; + } + } + } + } +} + +void LLFloaterIMSession::reloadMessages(bool clean_messages/* = false*/) +{ + if (clean_messages) + { + LLIMModel::LLIMSession * sessionp = LLIMModel::instance().findIMSession(mSessionID); + + if (NULL != sessionp) + { + sessionp->loadHistory(); + } + } + + mChatHistory->clear(); + mLastMessageIndex = -1; + updateMessages(); + mInputEditor->setFont(LLViewerChat::getChatFont()); +} + +// static +void LLFloaterIMSession::onInputEditorFocusReceived( LLFocusableElement* caller, void* userdata ) +{ + LLFloaterIMSession* self= (LLFloaterIMSession*) userdata; + + // Allow enabling the LLFloaterIMSession input editor only if session can accept text + LLIMModel::LLIMSession* im_session = + LLIMModel::instance().findIMSession(self->mSessionID); + if( im_session && im_session->mTextIMPossible && !self->mInputEditor->getReadOnly()) + { + //in disconnected state IM input editor should be disabled + self->mInputEditor->setEnabled(!gDisconnected); + } +} + +// static +void LLFloaterIMSession::onInputEditorFocusLost(LLFocusableElement* caller, void* userdata) +{ + LLFloaterIMSession* self = (LLFloaterIMSession*) userdata; + self->setTyping(false); +} + +// static +void LLFloaterIMSession::onInputEditorKeystroke(LLTextEditor* caller, void* userdata) +{ + LLFloaterIMSession* self = (LLFloaterIMSession*)userdata; + LLFloaterIMContainer* im_box = LLFloaterIMContainer::findInstance(); + if (im_box) + { + im_box->flashConversationItemWidget(self->mSessionID,false); + } + std::string text = self->mInputEditor->getText(); + + // Deleting all text counts as stopping typing. + self->setTyping(!text.empty()); +} + +void LLFloaterIMSession::setTyping(bool typing) +{ + if ( typing ) + { + // Started or proceeded typing, reset the typing timeout timer + mTypingTimeoutTimer.reset(); + } + + if ( mMeTyping != typing ) + { + // Typing state is changed + mMeTyping = typing; + // So, should send current state + mShouldSendTypingState = true; + // In case typing is started, send state after some delay + mTypingTimer.reset(); + } + + // Don't want to send typing indicators to multiple people, potentially too + // much network traffic. Only send in person-to-person IMs. + if ( mShouldSendTypingState && mDialog == IM_NOTHING_SPECIAL ) + { + if ( mMeTyping ) + { + if ( mTypingTimer.getElapsedTimeF32() > 1.f ) + { + // Still typing, send 'start typing' notification + LLIMModel::instance().sendTypingState(mSessionID, mOtherParticipantUUID, true); + mShouldSendTypingState = false; + mMeTypingTimer.reset(); + } + } + else + { + // Send 'stop typing' notification immediately + LLIMModel::instance().sendTypingState(mSessionID, mOtherParticipantUUID, false); + mShouldSendTypingState = false; + } + } + + if (!mIsNearbyChat) + { + LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + if (speaker_mgr) + { + speaker_mgr->setSpeakerTyping(gAgent.getID(), false); + } + } +} + +void LLFloaterIMSession::processIMTyping(const LLUUID& from_id, bool typing) +{ + LL_DEBUGS("TypingMsgs") << "typing=" << typing << LL_ENDL; + if ( typing ) + { + // other user started typing + addTypingIndicator(from_id); + mOtherTypingTimer.reset(); + } + else + { + // other user stopped typing + removeTypingIndicator(from_id); + } +} + +void LLFloaterIMSession::processAgentListUpdates(const LLSD& body) +{ + uuid_vec_t joined_uuids; + + if (body.isMap() && body.has("agent_updates") && body["agent_updates"].isMap()) + { + LLSD::map_const_iterator update_it; + for(update_it = body["agent_updates"].beginMap(); + update_it != body["agent_updates"].endMap(); + ++update_it) + { + LLUUID agent_id(update_it->first); + LLSD agent_data = update_it->second; + + if (agent_data.isMap()) + { + // store the new participants in joined_uuids + if (agent_data.has("transition") && agent_data["transition"].asString() == "ENTER") + { + joined_uuids.push_back(agent_id); + } + + // process the moderator mutes + if (agent_id == gAgentID && agent_data.has("info") && agent_data["info"].has("mutes")) + { + bool moderator_muted_text = agent_data["info"]["mutes"]["text"].asBoolean(); + mInputEditor->setEnabled(!moderator_muted_text); + std::string label; + if (moderator_muted_text) + label = LLTrans::getString("IM_muted_text_label"); + else + label = LLTrans::getString("IM_to_label") + " " + LLIMModel::instance().getName(mSessionID); + mInputEditor->setLabel(label); + + if (moderator_muted_text) + LLNotificationsUtil::add("TextChatIsMutedByModerator"); + } + } + } + } + + // the vectors need to be sorted for computing the intersection and difference + std::sort(mInvitedParticipants.begin(), mInvitedParticipants.end()); + std::sort(joined_uuids.begin(), joined_uuids.end()); + + uuid_vec_t intersection; // uuids of invited residents who have joined the conversation + std::set_intersection(mInvitedParticipants.begin(), mInvitedParticipants.end(), + joined_uuids.begin(), joined_uuids.end(), + std::back_inserter(intersection)); + + if (intersection.size() > 0) + { + sendParticipantsAddedNotification(intersection); + } + + // Remove all joined participants from invited array. + // The difference between the two vectors (the elements in mInvitedParticipants which are not in joined_uuids) + // is placed at the beginning of mInvitedParticipants, then all other elements are erased. + mInvitedParticipants.erase(std::set_difference(mInvitedParticipants.begin(), mInvitedParticipants.end(), + joined_uuids.begin(), joined_uuids.end(), + mInvitedParticipants.begin()), + mInvitedParticipants.end()); +} + +void LLFloaterIMSession::processSessionUpdate(const LLSD& session_update) +{ + // *TODO : verify following code when moderated mode will be implemented + if ( false && session_update.has("moderated_mode") && + session_update["moderated_mode"].has("voice") ) + { + bool voice_moderated = session_update["moderated_mode"]["voice"]; + const std::string session_label = LLIMModel::instance().getName(mSessionID); + + if (voice_moderated) + { + setTitle(session_label + std::string(" ") + + LLTrans::getString("IM_moderated_chat_label")); + } + else + { + setTitle(session_label); + } + + // *TODO : uncomment this when/if LLPanelActiveSpeakers panel will be added + //update the speakers dropdown too + //mSpeakerPanel->setVoiceModerationCtrlMode(voice_moderated); + } +} + +// virtual +void LLFloaterIMSession::draw() +{ + // add people who were added via dropPerson() + if (!mPendingParticipants.empty()) + { + addSessionParticipants(mPendingParticipants); + mPendingParticipants.clear(); + } + + LLFloaterIMSessionTab::draw(); +} + +// virtual +bool LLFloaterIMSession::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + if (cargo_type == DAD_PERSON) + { + if (dropPerson(static_cast(cargo_data), drop)) + { + *accept = ACCEPT_YES_MULTI; + } + else + { + *accept = ACCEPT_NO; + } + } + else if (mDialog == IM_NOTHING_SPECIAL) + { + LLToolDragAndDrop::handleGiveDragAndDrop(mOtherParticipantUUID, mSessionID, drop, + cargo_type, cargo_data, accept); + } + + return true; +} + +bool LLFloaterIMSession::dropPerson(LLUUID* person_id, bool drop) +{ + bool res = person_id && person_id->notNull(); + if(res) + { + uuid_vec_t ids; + ids.push_back(*person_id); + + res = canAddSelectedToChat(ids); + if(res && drop) + { + // these people will be added during the next draw() call + // (so they can be added all at once) + mPendingParticipants.push_back(*person_id); + } + } + + return res; +} + +bool LLFloaterIMSession::isInviteAllowed() const +{ + return ( (IM_SESSION_CONFERENCE_START == mDialog) + || (IM_SESSION_INVITE == mDialog && !gAgent.isInGroup(mSessionID)) + || mIsP2PChat); +} + +bool LLFloaterIMSession::inviteToSession(const uuid_vec_t& ids) +{ + LLViewerRegion* region = gAgent.getRegion(); + bool is_region_exist = region != NULL; + + if (is_region_exist) + { + S32 count = ids.size(); + + if( isInviteAllowed() && (count > 0) ) + { + LL_INFOS() << "LLFloaterIMSession::inviteToSession() - inviting participants" << LL_ENDL; + + std::string url = region->getCapability("ChatSessionRequest"); + + LLSD data; + data["params"] = LLSD::emptyArray(); + for (int i = 0; i < count; i++) + { + data["params"].append(ids[i]); + } + data["method"] = "invite"; + data["session-id"] = mSessionID; + + LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data, + "Session invite sent", "Session invite failed"); + } + else + { + LL_INFOS() << "LLFloaterIMSession::inviteToSession -" + << " no need to invite agents for " + << mDialog << LL_ENDL; + // successful add, because everyone that needed to get added + // was added. + } + } + + return is_region_exist; +} + +void LLFloaterIMSession::addTypingIndicator(const LLUUID& from_id) +{ +/* Operation of " is typing" state machine: +Not Typing state: + + User types in P2P IM chat ... Send Start Typing, save Started time, + start Idle Timer (N seconds) go to Typing state + +Typing State: + + User enters a non-return character: if Now - Started > ME_TYPING_TIMEOUT, send + Start Typing, restart Idle Timer + User enters a return character: stop Idle Timer, send IM and Stop + Typing, go to Not Typing state + Idle Timer expires: send Stop Typing, go to Not Typing state + +The recipient has a complementary state machine in which a Start Typing +that is not followed by either an IM or another Start Typing within OTHER_TYPING_TIMEOUT +seconds switches the sender out of typing state. + +This has the nice quality of being self-healing for lost start/stop +messages while adding messages only for the (relatively rare) case of a +user who types a very long message (one that takes more than ME_TYPING_TIMEOUT seconds +to type). + +Note: OTHER_TYPING_TIMEOUT must be > ME_TYPING_TIMEOUT for proper operation of the state machine + +*/ + + // We may have lost a "stop-typing" packet, don't add it twice + if (from_id.notNull() && !mOtherTyping) + { + mOtherTyping = true; + mOtherTypingTimer.reset(); + // Save im_info so that removeTypingIndicator can be properly called because a timeout has occurred + mImFromId = from_id; + + // Update speaker + LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + if ( speaker_mgr ) + { + speaker_mgr->setSpeakerTyping(from_id, true); + } + } +} + +void LLFloaterIMSession::removeTypingIndicator(const LLUUID& from_id) +{ + if (mOtherTyping) + { + mOtherTyping = false; + + if (from_id.notNull()) + { + // Update speaker + LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + if (speaker_mgr) + { + speaker_mgr->setSpeakerTyping(from_id, false); + } + } + } +} + +// static +void LLFloaterIMSession::closeHiddenIMToasts() +{ + class IMToastMatcher: public LLNotificationsUI::LLScreenChannel::Matcher + { + public: + bool matches(const LLNotificationPtr notification) const + { + // "notifytoast" type of notifications is reserved for IM notifications + return "notifytoast" == notification->getType(); + } + }; + + LLNotificationsUI::LLScreenChannel* channel = + LLNotificationsUI::LLChannelManager::getNotificationScreenChannel(); + if (channel != NULL) + { + channel->closeHiddenToasts(IMToastMatcher()); + } +} +// static +void LLFloaterIMSession::confirmLeaveCallCallback(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + const LLSD& payload = notification["payload"]; + LLUUID session_id = payload["session_id"]; + + LLFloater* im_floater = findInstance(session_id); + if (option == 0 && im_floater != NULL) + { + im_floater->closeFloater(); + } + + return; +} + +// static +void LLFloaterIMSession::sRemoveTypingIndicator(const LLSD& data) +{ + LLUUID session_id = data["session_id"]; + if (session_id.isNull()) + return; + + LLUUID from_id = data["from_id"]; + if (gAgentID == from_id || LLUUID::null == from_id) + return; + + LLFloaterIMSession* floater = LLFloaterIMSession::findInstance(session_id); + if (!floater) + return; + + if (IM_NOTHING_SPECIAL != floater->mDialog) + return; + + floater->removeTypingIndicator(); +} + +// static +void LLFloaterIMSession::onIMChicletCreated( const LLUUID& session_id ) +{ + LLFloaterIMSession::addToHost(session_id); +} + +boost::signals2::connection LLFloaterIMSession::setIMFloaterShowedCallback(const floater_showed_signal_t::slot_type& cb) +{ + return LLFloaterIMSession::sIMFloaterShowedSignal.connect(cb); +} diff --git a/indra/newview/llfloaterimsession.h b/indra/newview/llfloaterimsession.h index db36f58cbc..0c48aa7728 100644 --- a/indra/newview/llfloaterimsession.h +++ b/indra/newview/llfloaterimsession.h @@ -1,205 +1,205 @@ -/** - * @file llfloaterimsession.h - * @brief LLFloaterIMSession class definition - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_FLOATERIMSESSION_H -#define LL_FLOATERIMSESSION_H - -#include "llimview.h" -#include "llfloaterimsessiontab.h" -#include "llinstantmessage.h" -#include "lllogchat.h" -#include "lltooldraganddrop.h" -#include "llvoicechannel.h" -#include "llvoiceclient.h" - -class LLAvatarName; -class LLButton; -class LLChatEntry; -class LLTextEditor; -class LLPanelChatControlPanel; -class LLChatHistory; -class LLInventoryItem; -class LLInventoryCategory; - -typedef boost::signals2::signal floater_showed_signal_t; - -/** - * Individual IM window that appears at the bottom of the screen, - * optionally "docked" to the bottom tray. - */ -class LLFloaterIMSession - : public LLVoiceClientStatusObserver - , public LLFloaterIMSessionTab -{ - LOG_CLASS(LLFloaterIMSession); -public: - LLFloaterIMSession(const LLUUID& session_id); - - virtual ~LLFloaterIMSession(); - - void initIMSession(const LLUUID& session_id); - void initIMFloater(); - - // LLView overrides - /*virtual*/ bool postBuild(); - /*virtual*/ void setMinimized(bool b); - /*virtual*/ void setVisible(bool visible); - /*virtual*/ bool getVisible(); - /*virtual*/ void setFocus(bool focus); - // Check typing timeout timer. - - /*virtual*/ void draw(); - /*virtual*/ bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg); - - static LLFloaterIMSession* findInstance(const LLUUID& session_id); - static LLFloaterIMSession* getInstance(const LLUUID& session_id); - - // LLFloater overrides - /*virtual*/ void onClose(bool app_quitting); - /*virtual*/ void setDocked(bool docked, bool pop_on_undock = true); - // Make IM conversion visible and update the message history - static LLFloaterIMSession* show(const LLUUID& session_id); - - // Toggle panel specified by session_id - // Returns true iff panel became visible - static bool toggle(const LLUUID& session_id); - - void sessionInitReplyReceived(const LLUUID& im_session_id); - - // get new messages from LLIMModel - /*virtual*/ void updateMessages(); - void reloadMessages(bool clean_messages = false); - static void onSendMsg(LLUICtrl*, void*); - void sendMsgFromInputEditor(); - void sendMsg(const std::string& msg); - - // callback for LLIMModel on new messages - // route to specific floater if it is visible - static void newIMCallback(const LLSD& data); - - // called when docked floater's position has been set by chiclet - void setPositioned(bool b) { mPositioned = b; }; - - void onVisibilityChanged(const LLSD& new_visibility); - bool enableGearMenuItem(const LLSD& userdata); - void GearDoToSelected(const LLSD& userdata); - bool checkGearMenuItem(const LLSD& userdata); - - // Implements LLVoiceClientStatusObserver::onChange() to enable the call - // button when voice is available - void onChange(EStatusType status, const std::string &channelURI, - bool proximal); - - virtual LLTransientFloaterMgr::ETransientGroup getGroup() { return LLTransientFloaterMgr::IM; } - virtual void onVoiceChannelStateChanged( - const LLVoiceChannel::EState& old_state, - const LLVoiceChannel::EState& new_state); - - void processIMTyping(const LLUUID& from_id, bool typing); - void processAgentListUpdates(const LLSD& body); - void processSessionUpdate(const LLSD& session_update); - - //used as a callback on receiving new IM message - static void sRemoveTypingIndicator(const LLSD& data); - static void onIMChicletCreated(const LLUUID& session_id); - const LLUUID& getOtherParticipantUUID() {return mOtherParticipantUUID;} - - static boost::signals2::connection setIMFloaterShowedCallback(const floater_showed_signal_t::slot_type& cb); - static floater_showed_signal_t sIMFloaterShowedSignal; - - bool needsTitleOverwrite() { return mSessionNameUpdatedForTyping && mOtherTyping; } - S32 getLastChatMessageIndex() {return mLastMessageIndex;} -private: - - /*virtual*/ void refresh(); - - /*virtual*/ void onTearOffClicked(); - /*virtual*/ void onClickCloseBtn(bool app_qutting); - - // Update the window title and input field help text - /*virtual*/ void updateSessionName(const std::string& name); - - bool dropPerson(LLUUID* person_id, bool drop); - - bool isInviteAllowed() const; - bool inviteToSession(const uuid_vec_t& agent_ids); - static void onInputEditorFocusReceived( LLFocusableElement* caller,void* userdata ); - static void onInputEditorFocusLost(LLFocusableElement* caller, void* userdata); - static void onInputEditorKeystroke(LLTextEditor* caller, void* userdata); - void setTyping(bool typing); - void onAddButtonClicked(); - void addSessionParticipants(const uuid_vec_t& uuids); - void addP2PSessionParticipants(const LLSD& notification, const LLSD& response, const uuid_vec_t& uuids); - void sendParticipantsAddedNotification(const uuid_vec_t& uuids); - bool canAddSelectedToChat(const uuid_vec_t& uuids); - - void onCallButtonClicked(); - - void boundVoiceChannel(); - - // Add the "User is typing..." indicator. - void addTypingIndicator(const LLUUID& from_id); - - // Remove the "User is typing..." indicator. - void removeTypingIndicator(const LLUUID& from_id = LLUUID::null); - - static void closeHiddenIMToasts(); - - static void confirmLeaveCallCallback(const LLSD& notification, const LLSD& response); - - S32 mLastMessageIndex; - - EInstantMessage mDialog; - LLUUID mOtherParticipantUUID; - bool mPositioned; - - LLUIString mTypingStart; - bool mMeTyping; - bool mOtherTyping; - bool mShouldSendTypingState; - LLFrameTimer mTypingTimer; - LLFrameTimer mTypingTimeoutTimer; - bool mSessionNameUpdatedForTyping; - LLFrameTimer mMeTypingTimer; - LLFrameTimer mOtherTypingTimer; - - bool mSessionInitialized; - LLSD mQueuedMsgsForInit; - - uuid_vec_t mInvitedParticipants; - uuid_vec_t mPendingParticipants; - - // connection to voice channel state change signal - boost::signals2::connection mVoiceChannelStateChangeConnection; - - LLUUID mImFromId; -}; - -#endif // LL_FLOATERIMSESSION_H +/** + * @file llfloaterimsession.h + * @brief LLFloaterIMSession class definition + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_FLOATERIMSESSION_H +#define LL_FLOATERIMSESSION_H + +#include "llimview.h" +#include "llfloaterimsessiontab.h" +#include "llinstantmessage.h" +#include "lllogchat.h" +#include "lltooldraganddrop.h" +#include "llvoicechannel.h" +#include "llvoiceclient.h" + +class LLAvatarName; +class LLButton; +class LLChatEntry; +class LLTextEditor; +class LLPanelChatControlPanel; +class LLChatHistory; +class LLInventoryItem; +class LLInventoryCategory; + +typedef boost::signals2::signal floater_showed_signal_t; + +/** + * Individual IM window that appears at the bottom of the screen, + * optionally "docked" to the bottom tray. + */ +class LLFloaterIMSession + : public LLVoiceClientStatusObserver + , public LLFloaterIMSessionTab +{ + LOG_CLASS(LLFloaterIMSession); +public: + LLFloaterIMSession(const LLUUID& session_id); + + virtual ~LLFloaterIMSession(); + + void initIMSession(const LLUUID& session_id); + void initIMFloater(); + + // LLView overrides + /*virtual*/ bool postBuild(); + /*virtual*/ void setMinimized(bool b); + /*virtual*/ void setVisible(bool visible); + /*virtual*/ bool getVisible(); + /*virtual*/ void setFocus(bool focus); + // Check typing timeout timer. + + /*virtual*/ void draw(); + /*virtual*/ bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + + static LLFloaterIMSession* findInstance(const LLUUID& session_id); + static LLFloaterIMSession* getInstance(const LLUUID& session_id); + + // LLFloater overrides + /*virtual*/ void onClose(bool app_quitting); + /*virtual*/ void setDocked(bool docked, bool pop_on_undock = true); + // Make IM conversion visible and update the message history + static LLFloaterIMSession* show(const LLUUID& session_id); + + // Toggle panel specified by session_id + // Returns true iff panel became visible + static bool toggle(const LLUUID& session_id); + + void sessionInitReplyReceived(const LLUUID& im_session_id); + + // get new messages from LLIMModel + /*virtual*/ void updateMessages(); + void reloadMessages(bool clean_messages = false); + static void onSendMsg(LLUICtrl*, void*); + void sendMsgFromInputEditor(); + void sendMsg(const std::string& msg); + + // callback for LLIMModel on new messages + // route to specific floater if it is visible + static void newIMCallback(const LLSD& data); + + // called when docked floater's position has been set by chiclet + void setPositioned(bool b) { mPositioned = b; }; + + void onVisibilityChanged(const LLSD& new_visibility); + bool enableGearMenuItem(const LLSD& userdata); + void GearDoToSelected(const LLSD& userdata); + bool checkGearMenuItem(const LLSD& userdata); + + // Implements LLVoiceClientStatusObserver::onChange() to enable the call + // button when voice is available + void onChange(EStatusType status, const std::string &channelURI, + bool proximal); + + virtual LLTransientFloaterMgr::ETransientGroup getGroup() { return LLTransientFloaterMgr::IM; } + virtual void onVoiceChannelStateChanged( + const LLVoiceChannel::EState& old_state, + const LLVoiceChannel::EState& new_state); + + void processIMTyping(const LLUUID& from_id, bool typing); + void processAgentListUpdates(const LLSD& body); + void processSessionUpdate(const LLSD& session_update); + + //used as a callback on receiving new IM message + static void sRemoveTypingIndicator(const LLSD& data); + static void onIMChicletCreated(const LLUUID& session_id); + const LLUUID& getOtherParticipantUUID() {return mOtherParticipantUUID;} + + static boost::signals2::connection setIMFloaterShowedCallback(const floater_showed_signal_t::slot_type& cb); + static floater_showed_signal_t sIMFloaterShowedSignal; + + bool needsTitleOverwrite() { return mSessionNameUpdatedForTyping && mOtherTyping; } + S32 getLastChatMessageIndex() {return mLastMessageIndex;} +private: + + /*virtual*/ void refresh(); + + /*virtual*/ void onTearOffClicked(); + /*virtual*/ void onClickCloseBtn(bool app_qutting); + + // Update the window title and input field help text + /*virtual*/ void updateSessionName(const std::string& name); + + bool dropPerson(LLUUID* person_id, bool drop); + + bool isInviteAllowed() const; + bool inviteToSession(const uuid_vec_t& agent_ids); + static void onInputEditorFocusReceived( LLFocusableElement* caller,void* userdata ); + static void onInputEditorFocusLost(LLFocusableElement* caller, void* userdata); + static void onInputEditorKeystroke(LLTextEditor* caller, void* userdata); + void setTyping(bool typing); + void onAddButtonClicked(); + void addSessionParticipants(const uuid_vec_t& uuids); + void addP2PSessionParticipants(const LLSD& notification, const LLSD& response, const uuid_vec_t& uuids); + void sendParticipantsAddedNotification(const uuid_vec_t& uuids); + bool canAddSelectedToChat(const uuid_vec_t& uuids); + + void onCallButtonClicked(); + + void boundVoiceChannel(); + + // Add the "User is typing..." indicator. + void addTypingIndicator(const LLUUID& from_id); + + // Remove the "User is typing..." indicator. + void removeTypingIndicator(const LLUUID& from_id = LLUUID::null); + + static void closeHiddenIMToasts(); + + static void confirmLeaveCallCallback(const LLSD& notification, const LLSD& response); + + S32 mLastMessageIndex; + + EInstantMessage mDialog; + LLUUID mOtherParticipantUUID; + bool mPositioned; + + LLUIString mTypingStart; + bool mMeTyping; + bool mOtherTyping; + bool mShouldSendTypingState; + LLFrameTimer mTypingTimer; + LLFrameTimer mTypingTimeoutTimer; + bool mSessionNameUpdatedForTyping; + LLFrameTimer mMeTypingTimer; + LLFrameTimer mOtherTypingTimer; + + bool mSessionInitialized; + LLSD mQueuedMsgsForInit; + + uuid_vec_t mInvitedParticipants; + uuid_vec_t mPendingParticipants; + + // connection to voice channel state change signal + boost::signals2::connection mVoiceChannelStateChangeConnection; + + LLUUID mImFromId; +}; + +#endif // LL_FLOATERIMSESSION_H diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp index 6efb0f9efb..81a4f9936c 100644 --- a/indra/newview/llfloaterimsessiontab.cpp +++ b/indra/newview/llfloaterimsessiontab.cpp @@ -1,1350 +1,1350 @@ -/** - * @file llfloaterimsessiontab.cpp - * @brief LLFloaterIMSessionTab class implements the common behavior of LNearbyChatBar - * @brief and LLFloaterIMSession for hosting both in LLIMContainer - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterimsessiontab.h" - -#include "llagent.h" -#include "llagentcamera.h" -#include "llavataractions.h" -#include "llavatariconctrl.h" -#include "llchatentry.h" -#include "llchathistory.h" -#include "llchiclet.h" -#include "llchicletbar.h" -#include "lldraghandle.h" -#include "llemojidictionary.h" -#include "llfloaterreg.h" -#include "llfloateremojipicker.h" -#include "llfloaterimsession.h" -#include "llfloaterimcontainer.h" // to replace separate IM Floaters with multifloater container -#include "llfloaterimnearbychat.h" -#include "llgroupiconctrl.h" -#include "lllayoutstack.h" -#include "llpanelemojicomplete.h" -#include "lltoolbarview.h" - -const F32 REFRESH_INTERVAL = 1.0f; -const std::string ICN_GROUP("group_chat_icon"); -const std::string ICN_NEARBY("nearby_chat_icon"); -const std::string ICN_AVATAR("avatar_icon"); - -void cb_group_do_nothing() -{ -} - -LLFloaterIMSessionTab::LLFloaterIMSessionTab(const LLSD& session_id) -: super(NULL, false, session_id), - mIsP2PChat(false), - mExpandCollapseBtn(NULL), - mTearOffBtn(NULL), - mCloseBtn(NULL), - mSessionID(session_id.asUUID()), - mConversationsRoot(NULL), - mScroller(NULL), - mChatHistory(NULL), - mInputEditor(NULL), - mInputEditorPad(0), - mRefreshTimer(new LLTimer()), - mIsHostAttached(false), - mHasVisibleBeenInitialized(false), - mIsParticipantListExpanded(true), - mChatLayoutPanel(NULL), - mInputPanels(NULL), - mChatLayoutPanelHeight(0) -{ - setAutoFocus(false); - mSession = LLIMModel::getInstance()->findIMSession(mSessionID); - - mCommitCallbackRegistrar.add("IMSession.Menu.Action", - boost::bind(&LLFloaterIMSessionTab::onIMSessionMenuItemClicked, this, _2)); - mEnableCallbackRegistrar.add("IMSession.Menu.CompactExpandedModes.CheckItem", - boost::bind(&LLFloaterIMSessionTab::onIMCompactExpandedMenuItemCheck, this, _2)); - mEnableCallbackRegistrar.add("IMSession.Menu.ShowModes.CheckItem", - boost::bind(&LLFloaterIMSessionTab::onIMShowModesMenuItemCheck, this, _2)); - mEnableCallbackRegistrar.add("IMSession.Menu.ShowModes.Enable", - boost::bind(&LLFloaterIMSessionTab::onIMShowModesMenuItemEnable, this, _2)); - - // Right click menu handling - mEnableCallbackRegistrar.add("Avatar.CheckItem", boost::bind(&LLFloaterIMSessionTab::checkContextMenuItem, this, _2)); - mEnableCallbackRegistrar.add("Avatar.EnableItem", boost::bind(&LLFloaterIMSessionTab::enableContextMenuItem, this, _2)); - mCommitCallbackRegistrar.add("Avatar.DoToSelected", boost::bind(&LLFloaterIMSessionTab::doToSelected, this, _2)); - mCommitCallbackRegistrar.add("Group.DoToSelected", boost::bind(&cb_group_do_nothing)); - - mMinFloaterHeight = getMinHeight(); -} - -LLFloaterIMSessionTab::~LLFloaterIMSessionTab() -{ - delete mRefreshTimer; - - LLFloaterIMContainer* im_container = LLFloaterIMContainer::findInstance(); - if (im_container) - { - LLParticipantList* session = dynamic_cast(im_container->getSessionModel(mSessionID)); - if (session) - { - for (const conversations_widgets_map::value_type& widget_pair : mConversationsWidgets) - { - LLFolderViewItem* widget = widget_pair.second; - LLFolderViewModelItem* item_vmi = widget->getViewModelItem(); - if (item_vmi && item_vmi->getNumRefs() == 1) - { - // This is the last pointer, remove participant from session - // before participant gets deleted on destroyView. - session->removeChild(item_vmi); - } - } - } - } -} - -// static -LLFloaterIMSessionTab* LLFloaterIMSessionTab::findConversation(const LLUUID& uuid) -{ - LLFloaterIMSessionTab* conv; - - if (uuid.isNull()) - { - conv = LLFloaterReg::findTypedInstance("nearby_chat"); - } - else - { - conv = LLFloaterReg::findTypedInstance("impanel", LLSD(uuid)); - } - - return conv; -}; - -// static -LLFloaterIMSessionTab* LLFloaterIMSessionTab::getConversation(const LLUUID& uuid) -{ - LLFloaterIMSessionTab* conv; - - if (uuid.isNull()) - { - conv = LLFloaterReg::getTypedInstance("nearby_chat"); - } - else - { - conv = LLFloaterReg::getTypedInstance("impanel", LLSD(uuid)); - conv->setOpenPositioning(LLFloaterEnums::POSITIONING_RELATIVE); - } - - return conv; - -}; - -// virtual -void LLFloaterIMSessionTab::setVisible(bool visible) -{ - if (visible && !mHasVisibleBeenInitialized) - { - mHasVisibleBeenInitialized = true; - if (!gAgentCamera.cameraMouselook()) - { - LLFloaterReg::getTypedInstance("im_container")->setVisible(true); - } - LLFloaterIMSessionTab::addToHost(mSessionID); - LLFloaterIMSessionTab* conversp = LLFloaterIMSessionTab::getConversation(mSessionID); - - if (conversp && conversp->isNearbyChat() && gSavedPerAccountSettings.getBOOL("NearbyChatIsNotCollapsed")) - { - onCollapseToLine(this); - } - mInputButtonPanel->setVisible(isTornOff()); - } - - super::setVisible(visible); -} - -// virtual -void LLFloaterIMSessionTab::setFocus(bool focus) -{ - super::setFocus(focus); - - // Redirect focus to input editor - if (focus) - { - updateMessages(); - - if (mInputEditor) - { - mInputEditor->setFocus(true); - } - } -} - -void LLFloaterIMSessionTab::addToHost(const LLUUID& session_id) -{ - if ((session_id.notNull() && !gIMMgr->hasSession(session_id)) - || !LLFloaterIMSessionTab::isChatMultiTab()) - { - return; - } - - // Get the floater: this will create the instance if it didn't exist - LLFloaterIMSessionTab* conversp = LLFloaterIMSessionTab::getConversation(session_id); - if (conversp) - { - LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); - - // Do not add again existing floaters - if (floater_container && !conversp->isHostAttached()) - { - conversp->setHostAttached(true); - - if (!conversp->isNearbyChat() - || gSavedPerAccountSettings.getBOOL("NearbyChatIsNotTornOff")) - { - floater_container->addFloater(conversp, false, LLTabContainer::RIGHT_OF_CURRENT); - } - else - { - // setting of the "potential" host for Nearby Chat: this sequence sets - // LLFloater::mHostHandle = NULL (a current host), but - // LLFloater::mLastHostHandle = floater_container (a "future" host) - conversp->setHost(floater_container); - conversp->setHost(NULL); - - conversp->forceReshape(); - } - // Added floaters share some state (like sort order) with their host - conversp->setSortOrder(floater_container->getSortOrder()); - } - } -} - -void LLFloaterIMSessionTab::assignResizeLimits() -{ - bool is_participants_pane_collapsed = mParticipantListPanel->isCollapsed(); - - // disable a layoutstack's functionality when participant list panel is collapsed - mRightPartPanel->setIgnoreReshape(is_participants_pane_collapsed); - - S32 participants_pane_target_width = is_participants_pane_collapsed? - 0 : (mParticipantListPanel->getRect().getWidth() + mParticipantListAndHistoryStack->getPanelSpacing()); - - S32 new_min_width = participants_pane_target_width + mRightPartPanel->getExpandedMinDim() + mFloaterExtraWidth; - - setResizeLimits(new_min_width, getMinHeight()); - - this->mParticipantListAndHistoryStack->updateLayout(); -} - -// virtual -bool LLFloaterIMSessionTab::postBuild() -{ - bool result; - - mBodyStack = getChild("main_stack"); - mParticipantListAndHistoryStack = getChild("im_panels"); - - mCloseBtn = getChild("close_btn"); - mCloseBtn->setCommitCallback([this](LLUICtrl*, const LLSD&) { onClickClose(this); }); - - mExpandCollapseBtn = getChild("expand_collapse_btn"); - mExpandCollapseBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onSlide(this); }); - - mExpandCollapseLineBtn = getChild("minz_btn"); - mExpandCollapseLineBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onCollapseToLine(this); }); - - mTearOffBtn = getChild("tear_off_btn"); - mTearOffBtn->setCommitCallback(boost::bind(&LLFloaterIMSessionTab::onTearOffClicked, this)); - - mEmojiRecentPanelToggleBtn = getChild("emoji_recent_panel_toggle_btn"); - mEmojiRecentPanelToggleBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiRecentPanelToggleBtnClicked(); }); - - mEmojiRecentPanel = getChild("emoji_recent_layout_panel"); - mEmojiRecentPanel->setVisible(false); - - mEmojiRecentEmptyText = getChild("emoji_recent_empty_text"); - mEmojiRecentEmptyText->setToolTip(mEmojiRecentEmptyText->getText()); - mEmojiRecentEmptyText->setVisible(false); - - mEmojiRecentContainer = getChild("emoji_recent_container"); - mEmojiRecentContainer->setVisible(false); - - mEmojiRecentIconsCtrl = getChild("emoji_recent_icons_ctrl"); - mEmojiRecentIconsCtrl->setFocusReceivedCallback([this](LLFocusableElement*) { onEmojiRecentPanelFocusReceived(); }); - mEmojiRecentIconsCtrl->setFocusLostCallback([this](LLFocusableElement*) { onEmojiRecentPanelFocusLost(); }); - mEmojiRecentIconsCtrl->setCommitCallback([this](LLUICtrl*, const LLSD& value) { onRecentEmojiPicked(value); }); - - mEmojiPickerShowBtn = getChild("emoji_picker_show_btn"); - mEmojiPickerShowBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiPickerShowBtnClicked(); }); - - mGearBtn = getChild("gear_btn"); - mAddBtn = getChild("add_btn"); - mVoiceButton = getChild("voice_call_btn"); - - mParticipantListPanel = getChild("speakers_list_panel"); - mRightPartPanel = getChild("right_part_holder"); - - mToolbarPanel = getChild("toolbar_panel"); - mContentPanel = getChild("body_panel"); - mInputButtonPanel = getChild("input_button_layout_panel"); - mInputButtonPanel->setVisible(false); - // Add a scroller for the folder (participant) view - LLRect scroller_view_rect = mParticipantListPanel->getRect(); - scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom); - LLScrollContainer::Params scroller_params(LLUICtrlFactory::getDefaultParams()); - scroller_params.rect(scroller_view_rect); - mScroller = LLUICtrlFactory::create(scroller_params); - mScroller->setFollowsAll(); - - // Insert that scroller into the panel widgets hierarchy - mParticipantListPanel->addChild(mScroller); - - mChatHistory = getChild("chat_history"); - - mInputEditor = getChild("chat_editor"); - - mChatLayoutPanel = getChild("chat_layout_panel"); - mInputPanels = getChild("input_panels"); - - mInputEditor->setTextExpandedCallback(boost::bind(&LLFloaterIMSessionTab::reshapeChatLayoutPanel, this)); - mInputEditor->setMouseUpCallback(boost::bind(&LLFloaterIMSessionTab::onInputEditorClicked, this)); - mInputEditor->setCommitOnFocusLost( false ); - mInputEditor->setPassDelete(true); - mInputEditor->setFont(LLViewerChat::getChatFont()); - - mChatLayoutPanelHeight = mChatLayoutPanel->getRect().getHeight(); - mInputEditorPad = mChatLayoutPanelHeight - mInputEditor->getRect().getHeight(); - - setOpenPositioning(LLFloaterEnums::POSITIONING_RELATIVE); - - mSaveRect = isNearbyChat() - && !gSavedPerAccountSettings.getBOOL("NearbyChatIsNotTornOff"); - initRectControl(); - - if (isChatMultiTab()) - { - result = LLFloater::postBuild(); - } - else - { - result = LLDockableFloater::postBuild(); - } - - // Create the root using an ad-hoc base item - LLConversationItem* base_item = new LLConversationItem(mSessionID, mConversationViewModel); - LLFolderView::Params p(LLUICtrlFactory::getDefaultParams()); - p.rect = LLRect(0, 0, getRect().getWidth(), 0); - p.parent_panel = mParticipantListPanel; - p.listener = base_item; - p.view_model = &mConversationViewModel; - p.root = NULL; - p.use_ellipses = true; - p.options_menu = "menu_conversation.xml"; - p.name = "root"; - mConversationsRoot = LLUICtrlFactory::create(p); - mConversationsRoot->setCallbackRegistrar(&mCommitCallbackRegistrar); - mConversationsRoot->setEnableRegistrar(&mEnableCallbackRegistrar); - // Attach that root to the scroller - mScroller->addChild(mConversationsRoot); - mConversationsRoot->setScrollContainer(mScroller); - mConversationsRoot->setFollowsAll(); - mConversationsRoot->addChild(mConversationsRoot->mStatusTextBox); - - setMessagePaneExpanded(true); - - buildConversationViewParticipant(); - refreshConversation(); - - // Zero expiry time is set only once to allow initial update. - mRefreshTimer->setTimerExpirySec(0); - mRefreshTimer->start(); - initBtns(); - - if (mIsParticipantListExpanded != (bool)gSavedSettings.getBOOL("IMShowControlPanel")) - { - LLFloaterIMSessionTab::onSlide(this); - } - - // The resize limits for LLFloaterIMSessionTab should be updated, based on current values of width of conversation and message panels - mParticipantListPanel->getResizeBar()->setResizeListener(boost::bind(&LLFloaterIMSessionTab::assignResizeLimits, this)); - mFloaterExtraWidth = - getRect().getWidth() - - mParticipantListAndHistoryStack->getRect().getWidth() - - (mParticipantListPanel->isCollapsed()? 0 : LLPANEL_BORDER_WIDTH); - - assignResizeLimits(); - - return result; -} - -LLParticipantList* LLFloaterIMSessionTab::getParticipantList() -{ - return dynamic_cast(LLFloaterIMContainer::getInstance()->getSessionModel(mSessionID)); -} - -// virtual -void LLFloaterIMSessionTab::draw() -{ - if (mRefreshTimer->hasExpired()) - { - LLParticipantList* item = getParticipantList(); - if (item) - { - // Update all model items - item->update(); - // If the model and view list diverge in count, rebuild - // Note: this happens sometimes right around init (add participant events fire but get dropped) and is the cause - // of missing participants, often, the user agent itself. As there will be no other event fired, there's - // no other choice but get those inconsistencies regularly (and lightly) checked and scrubbed. - if (item->getChildrenCount() != mConversationsWidgets.size()) - { - buildConversationViewParticipant(); - } - refreshConversation(); - } - - // Restart the refresh timer - mRefreshTimer->setTimerExpirySec(REFRESH_INTERVAL); - } - - super::draw(); -} - -void LLFloaterIMSessionTab::enableDisableCallBtn() -{ - if (LLVoiceClient::instanceExists() && mVoiceButton) - { - mVoiceButton->setEnabled( - mSessionID.notNull() - && mSession - && mSession->mSessionInitialized - && LLVoiceClient::getInstance()->voiceEnabled() - && LLVoiceClient::getInstance()->isVoiceWorking() - && mSession->mCallBackEnabled); - } -} - -// virtual -void LLFloaterIMSessionTab::onFocusReceived() -{ - setBackgroundOpaque(true); - - if (mSessionID.notNull() && isInVisibleChain()) - { - LLIMModel::instance().sendNoUnreadMessages(mSessionID); - } - - super::onFocusReceived(); -} - -// virtual -void LLFloaterIMSessionTab::onFocusLost() -{ - setBackgroundOpaque(false); - super::onFocusLost(); -} - -void LLFloaterIMSessionTab::onInputEditorClicked() -{ - LLFloaterIMContainer* im_box = LLFloaterIMContainer::findInstance(); - if (im_box) - { - im_box->flashConversationItemWidget(mSessionID,false); - } - gToolBarView->flashCommand(LLCommandId("chat"), false); -} - -void LLFloaterIMSessionTab::onEmojiRecentPanelToggleBtnClicked() -{ - bool show = !mEmojiRecentPanel->getVisible(); - if (show) - { - initEmojiRecentPanel(); - } - - mEmojiRecentPanel->setVisible(show); - mInputEditor->setFocus(true); -} - -void LLFloaterIMSessionTab::onEmojiPickerShowBtnClicked() -{ - mInputEditor->setFocus(true); - mInputEditor->showEmojiHelper(); -} - -void LLFloaterIMSessionTab::initEmojiRecentPanel() -{ - std::list& recentlyUsed = LLFloaterEmojiPicker::getRecentlyUsed(); - if (recentlyUsed.empty()) - { - mEmojiRecentEmptyText->setVisible(true); - mEmojiRecentContainer->setVisible(false); - } - else - { - LLWString emojis; - for (llwchar emoji : recentlyUsed) - { - emojis += emoji; - } - mEmojiRecentIconsCtrl->setEmojis(emojis); - mEmojiRecentEmptyText->setVisible(false); - mEmojiRecentContainer->setVisible(true); - } -} - -// static -void LLFloaterIMSessionTab::onEmojiRecentPanelFocusReceived() -{ - mEmojiRecentContainer->addBorder(); -} - -// static -void LLFloaterIMSessionTab::onEmojiRecentPanelFocusLost() -{ - mEmojiRecentContainer->removeBorder(); -} - -void LLFloaterIMSessionTab::onRecentEmojiPicked(const LLSD& value) -{ - LLSD::String str = value.asString(); - if (str.size()) - { - LLWString wstr = utf8string_to_wstring(str); - if (wstr.size()) - { - llwchar emoji = wstr[0]; - mInputEditor->insertEmoji(emoji); - } - } -} - -void LLFloaterIMSessionTab::closeFloater(bool app_quitting) -{ - LLFloaterEmojiPicker::saveState(); - super::closeFloater(app_quitting); -} - -std::string LLFloaterIMSessionTab::appendTime() -{ - std::string timeStr = "[" + LLTrans::getString("TimeHour") + "]:" - "[" + LLTrans::getString("TimeMin") + "]"; - - LLSD substitution; - substitution["datetime"] = (S32)time_corrected(); - LLStringUtil::format(timeStr, substitution); - - return timeStr; -} - -void LLFloaterIMSessionTab::appendMessage(const LLChat& chat, const LLSD& args) -{ - if (chat.mMuted || !mChatHistory) - return; - - // Update the participant activity time - LLFloaterIMContainer* im_box = LLFloaterIMContainer::findInstance(); - if (im_box) - { - im_box->setTimeNow(mSessionID, chat.mFromID); - } - - LLChat& tmp_chat = const_cast(chat); - - if (tmp_chat.mTimeStr.empty()) - tmp_chat.mTimeStr = appendTime(); - - tmp_chat.mFromName = chat.mFromName; - - LLSD chat_args = args; - chat_args["use_plain_text_chat_history"] = - gSavedSettings.getBOOL("PlainTextChatHistory"); - chat_args["show_time"] = gSavedSettings.getBOOL("IMShowTime"); - chat_args["show_names_for_p2p_conv"] = !mIsP2PChat || - gSavedSettings.getBOOL("IMShowNamesForP2PConv"); - - mChatHistory->appendMessage(chat, chat_args); -} - -void LLFloaterIMSessionTab::updateUsedEmojis(LLWString text) -{ - LLEmojiDictionary* dictionary = LLEmojiDictionary::getInstance(); - llassert_always(dictionary); - - bool emojiSent = false; - for (llwchar& c : text) - { - if (dictionary->isEmoji(c)) - { - LLFloaterEmojiPicker::onEmojiUsed(c); - emojiSent = true; - } - } - - if (!emojiSent) - return; - - LLFloaterEmojiPicker::saveState(); - - if (mEmojiRecentPanel->getVisible()) - { - initEmojiRecentPanel(); - } -} - -static LLTrace::BlockTimerStatHandle FTM_BUILD_CONVERSATION_VIEW_PARTICIPANT("Build Conversation View"); -void LLFloaterIMSessionTab::buildConversationViewParticipant() -{ - LL_RECORD_BLOCK_TIME(FTM_BUILD_CONVERSATION_VIEW_PARTICIPANT); - // Clear the widget list since we are rebuilding afresh from the model - conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin(); - while (widget_it != mConversationsWidgets.end()) - { - removeConversationViewParticipant(widget_it->first); - // Iterators are invalidated by erase so we need to pick begin again - widget_it = mConversationsWidgets.begin(); - } - - // Get the model list - LLParticipantList* item = getParticipantList(); - if (!item) - { - // Nothing to do if the model list is inexistent - return; - } - - // Create the participants widgets now - LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = item->getChildrenBegin(); - LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = item->getChildrenEnd(); - while (current_participant_model != end_participant_model) - { - LLConversationItem* participant_model = dynamic_cast(*current_participant_model); - if (participant_model) - { - addConversationViewParticipant(participant_model); - } - current_participant_model++; - } -} - -void LLFloaterIMSessionTab::addConversationViewParticipant(LLConversationItem* participant_model, bool update_view) -{ - if (!participant_model) - { - // Nothing to do if the model is inexistent - return; - } - - // Check if the model already has an associated view - LLUUID uuid = participant_model->getUUID(); - LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,uuid); - - // If not already present, create the participant view and attach it to the root, otherwise, just refresh it - if (widget) - { - if (update_view) - { - updateConversationViewParticipant(uuid); // overkill? - } - } - else - { - LLConversationViewParticipant* participant_view = createConversationViewParticipant(participant_model); - mConversationsWidgets[uuid] = participant_view; - participant_view->addToFolder(mConversationsRoot); - participant_view->addToSession(mSessionID); - participant_view->setVisible(true); - } -} - -void LLFloaterIMSessionTab::removeConversationViewParticipant(const LLUUID& participant_id) -{ - LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,participant_id); - if (widget) - { - LLFolderViewModelItem* item_vmi = widget->getViewModelItem(); - if (item_vmi && item_vmi->getNumRefs() == 1) - { - // This is the last pointer, remove participant from session - // before participant gets deleted on destroyView. - // - // Floater (widget) and participant's view can simultaneously - // co-own the model, in which case view is responsible for - // the deletion and floater is free to clear and recreate - // the list, yet there are cases where only widget owns - // the pointer so it should do the cleanup. - // See "add_participant". - // - // Todo: If it keeps causing issues turn participants - // into LLPointers in the session - LLParticipantList* session = getParticipantList(); - if (session) - { - session->removeChild(item_vmi); - } - } - widget->destroyView(); - } - mConversationsWidgets.erase(participant_id); -} - -void LLFloaterIMSessionTab::updateConversationViewParticipant(const LLUUID& participant_id) -{ - LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,participant_id); - if (widget && widget->getViewModelItem()) - { - widget->refresh(); - } -} - -void LLFloaterIMSessionTab::refreshConversation() -{ - // Note: We collect participants names to change the session name only in the case of ad-hoc conversations - bool is_ad_hoc = (mSession ? mSession->isAdHocSessionType() : false); - uuid_vec_t participants_uuids; // uuids vector for building the added participants name string - // For P2P chat, we still need to update the session name who may have changed (switch display name for instance) - if (mIsP2PChat && mSession) - { - participants_uuids.push_back(mSession->mOtherParticipantID); - } - - conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin(); - while (widget_it != mConversationsWidgets.end()) - { - // Add the participant to the list except if it's the agent itself (redundant) - if (is_ad_hoc && (widget_it->first != gAgentID)) - { - participants_uuids.push_back(widget_it->first); - } - if (widget_it->second->getViewModelItem()) - { - widget_it->second->refresh(); - widget_it->second->setVisible(true); - } - ++widget_it; - } - if (is_ad_hoc || mIsP2PChat) - { - // Build the session name and update it - std::string session_name; - if (participants_uuids.size() != 0) - { - LLAvatarActions::buildResidentsString(participants_uuids, session_name); - } - else - { - session_name = LLIMModel::instance().getName(mSessionID); - } - updateSessionName(session_name); - } - - if (mSessionID.notNull()) - { - LLParticipantList* participant_list = getParticipantList(); - if (participant_list) - { - LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = participant_list->getChildrenBegin(); - LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = participant_list->getChildrenEnd(); - LLIMSpeakerMgr *speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); - while (current_participant_model != end_participant_model) - { - LLConversationItemParticipant* participant_model = dynamic_cast(*current_participant_model); - if (speaker_mgr && participant_model) - { - LLSpeaker *participant_speaker = speaker_mgr->findSpeaker(participant_model->getUUID()); - LLSpeaker *agent_speaker = speaker_mgr->findSpeaker(gAgentID); - if (participant_speaker && agent_speaker) - { - participant_model->setDisplayModeratorRole(agent_speaker->mIsModerator && participant_speaker->mIsModerator); - } - } - current_participant_model++; - } - } - } - - mConversationViewModel.requestSortAll(); - if(mConversationsRoot != NULL) - { - mConversationsRoot->arrangeAll(); - mConversationsRoot->update(); - } - updateHeaderAndToolbar(); - refresh(); -} - -// Copied from LLFloaterIMContainer::createConversationViewParticipant(). Refactor opportunity! -LLConversationViewParticipant* LLFloaterIMSessionTab::createConversationViewParticipant(LLConversationItem* item) -{ - LLRect panel_rect = mParticipantListPanel->getRect(); - - LLConversationViewParticipant::Params params; - params.name = item->getDisplayName(); - params.root = mConversationsRoot; - params.listener = item; - params.rect = LLRect (0, 24, panel_rect.getWidth(), 0); // *TODO: use conversation_view_participant.xml itemHeight value in lieu of 24 - params.tool_tip = params.name; - params.participant_id = item->getUUID(); - - return LLUICtrlFactory::create(params); -} - -void LLFloaterIMSessionTab::setSortOrder(const LLConversationSort& order) -{ - mConversationViewModel.setSorter(order); - mConversationsRoot->arrangeAll(); - refreshConversation(); -} - -void LLFloaterIMSessionTab::onIMSessionMenuItemClicked(const LLSD& userdata) -{ - std::string item = userdata.asString(); - - if (item == "compact_view" || item == "expanded_view") - { - gSavedSettings.setBOOL("PlainTextChatHistory", item == "compact_view"); - } - else - { - bool prev_value = gSavedSettings.getBOOL(item); - gSavedSettings.setBOOL(item, !prev_value); - } - - LLFloaterIMSessionTab::processChatHistoryStyleUpdate(); -} - -bool LLFloaterIMSessionTab::onIMCompactExpandedMenuItemCheck(const LLSD& userdata) -{ - std::string item = userdata.asString(); - bool is_plain_text_mode = gSavedSettings.getBOOL("PlainTextChatHistory"); - - return is_plain_text_mode? item == "compact_view" : item == "expanded_view"; -} - - -bool LLFloaterIMSessionTab::onIMShowModesMenuItemCheck(const LLSD& userdata) -{ - return gSavedSettings.getBOOL(userdata.asString()); -} - -// enable/disable states for the "show time" and "show names" items of the show-modes menu -bool LLFloaterIMSessionTab::onIMShowModesMenuItemEnable(const LLSD& userdata) -{ - std::string item = userdata.asString(); - bool plain_text = gSavedSettings.getBOOL("PlainTextChatHistory"); - bool is_not_names = (item != "IMShowNamesForP2PConv"); - return (plain_text && (is_not_names || mIsP2PChat)); -} - -void LLFloaterIMSessionTab::hideOrShowTitle() -{ - const LLFloater::Params& default_params = LLFloater::getDefaultParams(); - S32 floater_header_size = default_params.header_height; - LLView* floater_contents = getChild("contents_view"); - - LLRect floater_rect = getLocalRect(); - S32 top_border_of_contents = floater_rect.mTop - (isTornOff()? floater_header_size : 0); - LLRect handle_rect (0, floater_rect.mTop, floater_rect.mRight, top_border_of_contents); - LLRect contents_rect (0, top_border_of_contents, floater_rect.mRight, floater_rect.mBottom); - mDragHandle->setShape(handle_rect); - mDragHandle->setVisible(isTornOff()); - floater_contents->setShape(contents_rect); -} - -void LLFloaterIMSessionTab::updateSessionName(const std::string& name) -{ - mInputEditor->setLabel(LLTrans::getString("IM_to_label") + " " + name); -} - -void LLFloaterIMSessionTab::updateChatIcon(const LLUUID& id) -{ - if (mSession) - { - if (mSession->isP2PSessionType()) - { - LLAvatarIconCtrl* icon = getChild(ICN_AVATAR); - icon->setVisible(true); - icon->setValue(id); - } - if (mSession->isAdHocSessionType()) - { - LLGroupIconCtrl* icon = getChild(ICN_GROUP); - icon->setVisible(true); - } - if (mSession->isGroupSessionType()) - { - LLGroupIconCtrl* icon = getChild(ICN_GROUP); - icon->setVisible(true); - icon->setValue(id); - } - } - else - { - if (mIsNearbyChat) - { - LLIconCtrl* icon = getChild(ICN_NEARBY); - icon->setVisible(true); - } - } - -} - -void LLFloaterIMSessionTab::hideAllStandardButtons() -{ - for (S32 i = 0; i < BUTTON_COUNT; i++) - { - if (mButtons[i]) - { - // Hide the standard header buttons in a docked IM floater. - mButtons[i]->setVisible(false); - } - } -} - -void LLFloaterIMSessionTab::updateHeaderAndToolbar() -{ - // prevent start conversation before its container - LLFloaterIMContainer::getInstance(); - - bool is_not_torn_off = !checkIfTornOff(); - if (is_not_torn_off) - { - hideAllStandardButtons(); - } - - hideOrShowTitle(); - - // Participant list should be visible only in torn off floaters. - bool is_participant_list_visible = - !is_not_torn_off - && mIsParticipantListExpanded - && !mIsP2PChat; - - mParticipantListAndHistoryStack->collapsePanel(mParticipantListPanel, !is_participant_list_visible); - mParticipantListPanel->setVisible(is_participant_list_visible); - - // Display collapse image (<<) if the floater is hosted - // or if it is torn off but has an open control panel. - bool is_expanded = is_not_torn_off || is_participant_list_visible; - - mExpandCollapseBtn->setImageOverlay(getString(is_expanded ? "collapse_icon" : "expand_icon")); - mExpandCollapseBtn->setToolTip( - is_not_torn_off? - getString("expcol_button_not_tearoff_tooltip") : - (is_expanded? - getString("expcol_button_tearoff_and_expanded_tooltip") : - getString("expcol_button_tearoff_and_collapsed_tooltip"))); - - // toggle floater's drag handle and title visibility - if (mDragHandle) - { - mDragHandle->setTitleVisible(!is_not_torn_off); - } - - // The button (>>) should be disabled for torn off P2P conversations. - mExpandCollapseBtn->setEnabled(is_not_torn_off || !mIsP2PChat); - - mTearOffBtn->setImageOverlay(getString(is_not_torn_off? "tear_off_icon" : "return_icon")); - mTearOffBtn->setToolTip(getString(is_not_torn_off? "tooltip_to_separate_window" : "tooltip_to_main_window")); - - - mCloseBtn->setVisible(is_not_torn_off && !mIsNearbyChat); - - enableDisableCallBtn(); -} - -void LLFloaterIMSessionTab::forceReshape() -{ - LLRect floater_rect = getRect(); - reshape(llmax(floater_rect.getWidth(), this->getMinWidth()), - llmax(floater_rect.getHeight(), this->getMinHeight()), - true); -} - - -void LLFloaterIMSessionTab::reshapeChatLayoutPanel() -{ - mChatLayoutPanel->reshape(mChatLayoutPanel->getRect().getWidth(), mInputEditor->getRect().getHeight() + mInputEditorPad, false); -} - -// static -void LLFloaterIMSessionTab::processChatHistoryStyleUpdate(bool clean_messages/* = false*/) -{ - LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("impanel"); - for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); - iter != inst_list.end(); ++iter) - { - LLFloaterIMSession* floater = dynamic_cast(*iter); - if (floater) - { - floater->reloadMessages(clean_messages); - } - } - - LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); - if (nearby_chat) - { - nearby_chat->reloadMessages(clean_messages); - } -} - -// static -void LLFloaterIMSessionTab::reloadEmptyFloaters() -{ - LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("impanel"); - for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); - iter != inst_list.end(); ++iter) - { - LLFloaterIMSession* floater = dynamic_cast(*iter); - if (floater && floater->getLastChatMessageIndex() == -1) - { - floater->reloadMessages(true); - } - } - - LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); - if (nearby_chat && nearby_chat->getMessageArchiveLength() == 0) - { - nearby_chat->reloadMessages(true); - } -} - -void LLFloaterIMSessionTab::updateCallBtnState(bool callIsActive) -{ - mVoiceButton->setImageOverlay(callIsActive? getString("call_btn_stop") : getString("call_btn_start")); - mVoiceButton->setToolTip(callIsActive? getString("end_call_button_tooltip") : getString("start_call_button_tooltip")); - - enableDisableCallBtn(); -} - -void LLFloaterIMSessionTab::onSlide(LLFloaterIMSessionTab* self) -{ - LLFloaterIMContainer* host_floater = dynamic_cast(self->getHost()); - bool should_be_expanded = false; - if (host_floater) - { - // Hide the messages pane if a floater is hosted in the Conversations - host_floater->collapseMessagesPane(true); - } - else ///< floater is torn off - { - if (!self->mIsP2PChat) - { - // The state must toggle the collapsed state of the panel - should_be_expanded = self->mParticipantListPanel->isCollapsed(); - - // Update the expand/collapse flag of the participant list panel and save it - gSavedSettings.setBOOL("IMShowControlPanel", should_be_expanded); - self->mIsParticipantListExpanded = should_be_expanded; - - // Refresh for immediate feedback - self->refreshConversation(); - } - } - - self->assignResizeLimits(); - if (should_be_expanded) - { - self->forceReshape(); - } -} - -void LLFloaterIMSessionTab::onCollapseToLine(LLFloaterIMSessionTab* self) -{ - LLFloaterIMContainer* host_floater = dynamic_cast(self->getHost()); - if (!host_floater) - { - bool expand = self->isMessagePaneExpanded(); - self->mExpandCollapseLineBtn->setImageOverlay(self->getString(expand ? "collapseline_icon" : "expandline_icon")); - self->mContentPanel->setVisible(!expand); - self->mToolbarPanel->setVisible(!expand); - self->mInputEditor->enableSingleLineMode(expand); - self->reshapeFloater(expand); - self->setMessagePaneExpanded(!expand); - } -} - -void LLFloaterIMSessionTab::reshapeFloater(bool collapse) -{ - LLRect floater_rect = getRect(); - - if(collapse) - { - mFloaterHeight = floater_rect.getHeight(); - S32 height = mContentPanel->getRect().getHeight() + mToolbarPanel->getRect().getHeight() - + mChatLayoutPanel->getRect().getHeight() - mChatLayoutPanelHeight + 2; - floater_rect.mTop -= height; - - setResizeLimits(getMinWidth(), floater_rect.getHeight()); - } - else - { - floater_rect.mTop = floater_rect.mBottom + mFloaterHeight; - setResizeLimits(getMinWidth(), mMinFloaterHeight); - } - - enableResizeCtrls(true, true, !collapse); - - saveCollapsedState(); - setShape(floater_rect, true); - mBodyStack->updateLayout(); -} - -void LLFloaterIMSessionTab::restoreFloater() -{ - if(!isMessagePaneExpanded()) - { - if(isMinimized()) - { - setMinimized(false); - } - mContentPanel->setVisible(true); - mToolbarPanel->setVisible(true); - LLRect floater_rect = getRect(); - floater_rect.mTop = floater_rect.mBottom + mFloaterHeight; - setShape(floater_rect, true); - mBodyStack->updateLayout(); - mExpandCollapseLineBtn->setImageOverlay(getString("expandline_icon")); - setResizeLimits(getMinWidth(), mMinFloaterHeight); - setMessagePaneExpanded(true); - saveCollapsedState(); - mInputEditor->enableSingleLineMode(false); - enableResizeCtrls(true, true, true); - } -} - -/*virtual*/ -void LLFloaterIMSessionTab::onOpen(const LLSD& key) -{ - if (!checkIfTornOff()) - { - LLFloaterIMContainer* host_floater = dynamic_cast(getHost()); - // Show the messages pane when opening a floater hosted in the Conversations - host_floater->collapseMessagesPane(false); - } - - mInputButtonPanel->setVisible(isTornOff()); - - setFocus(true); -} - - -void LLFloaterIMSessionTab::onTearOffClicked() -{ - restoreFloater(); - setFollows(isTornOff()? FOLLOWS_ALL : FOLLOWS_NONE); - mSaveRect = isTornOff(); - initRectControl(); - LLFloater::onClickTearOff(this); - LLFloaterIMContainer* container = LLFloaterReg::findTypedInstance("im_container"); - - if (isTornOff()) - { - container->selectAdjacentConversation(false); - forceReshape(); - } - //Upon re-docking the torn off floater, select the corresponding conversation line item - else - { - container->selectConversation(mSessionID); - - } - mInputButtonPanel->setVisible(isTornOff()); - - refreshConversation(); - updateGearBtn(); -} - -void LLFloaterIMSessionTab::updateGearBtn() -{ - bool prevVisibility = mGearBtn->getVisible(); - mGearBtn->setVisible(checkIfTornOff() && mIsP2PChat); - - - // Move buttons if Gear button changed visibility - if(prevVisibility != mGearBtn->getVisible()) - { - LLRect gear_btn_rect = mGearBtn->getRect(); - LLRect add_btn_rect = mAddBtn->getRect(); - LLRect call_btn_rect = mVoiceButton->getRect(); - S32 gap_width = call_btn_rect.mLeft - add_btn_rect.mRight; - S32 right_shift = gear_btn_rect.getWidth() + gap_width; - if(mGearBtn->getVisible()) - { - // Move buttons to the right to give space for Gear button - add_btn_rect.translate(right_shift,0); - call_btn_rect.translate(right_shift,0); - } - else - { - add_btn_rect.translate(-right_shift,0); - call_btn_rect.translate(-right_shift,0); - } - mAddBtn->setRect(add_btn_rect); - mVoiceButton->setRect(call_btn_rect); - } -} - -void LLFloaterIMSessionTab::initBtns() -{ - LLRect gear_btn_rect = mGearBtn->getRect(); - LLRect add_btn_rect = mAddBtn->getRect(); - LLRect call_btn_rect = mVoiceButton->getRect(); - S32 gap_width = call_btn_rect.mLeft - add_btn_rect.mRight; - S32 right_shift = gear_btn_rect.getWidth() + gap_width; - - add_btn_rect.translate(-right_shift,0); - call_btn_rect.translate(-right_shift,0); - - mAddBtn->setRect(add_btn_rect); - mVoiceButton->setRect(call_btn_rect); -} - -// static -bool LLFloaterIMSessionTab::isChatMultiTab() -{ - // Restart is required in order to change chat window type. - return true; -} - -bool LLFloaterIMSessionTab::checkIfTornOff() -{ - bool isTorn = !getHost(); - - if (isTorn != isTornOff()) - { - setTornOff(isTorn); - refreshConversation(); - } - - return isTorn; -} - -void LLFloaterIMSessionTab::doToSelected(const LLSD& userdata) -{ - // Get the list of selected items in the tab - std::string command = userdata.asString(); - uuid_vec_t selected_uuids; - getSelectedUUIDs(selected_uuids); - - // Perform the command (IM, profile, etc...) on the list using the general conversation container method - LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); - // Note: By construction, those can only be participants so we can call doToParticipants() directly - floater_container->doToParticipants(command, selected_uuids); -} - -bool LLFloaterIMSessionTab::enableContextMenuItem(const LLSD& userdata) -{ - // Get the list of selected items in the tab - std::string command = userdata.asString(); - uuid_vec_t selected_uuids; - getSelectedUUIDs(selected_uuids); - - // Perform the item enable test on the list using the general conversation container method - LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); - return floater_container->enableContextMenuItem(command, selected_uuids); -} - -bool LLFloaterIMSessionTab::checkContextMenuItem(const LLSD& userdata) -{ - // Get the list of selected items in the tab - std::string command = userdata.asString(); - uuid_vec_t selected_uuids; - getSelectedUUIDs(selected_uuids); - - // Perform the item check on the list using the general conversation container method - LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); - return floater_container->checkContextMenuItem(command, selected_uuids); -} - -void LLFloaterIMSessionTab::getSelectedUUIDs(uuid_vec_t& selected_uuids) -{ - const std::set selected_items = mConversationsRoot->getSelectionList(); - - std::set::const_iterator it = selected_items.begin(); - const std::set::const_iterator it_end = selected_items.end(); - - for (; it != it_end; ++it) - { - LLConversationItem* conversation_item = static_cast((*it)->getViewModelItem()); - if (conversation_item) - { - selected_uuids.push_back(conversation_item->getUUID()); - } - } -} - -LLConversationItem* LLFloaterIMSessionTab::getCurSelectedViewModelItem() -{ - LLConversationItem *conversationItem = NULL; - - if(mConversationsRoot && - mConversationsRoot->getCurSelectedItem() && - mConversationsRoot->getCurSelectedItem()->getViewModelItem()) - { - conversationItem = static_cast(mConversationsRoot->getCurSelectedItem()->getViewModelItem()) ; - } - - return conversationItem; -} - -void LLFloaterIMSessionTab::saveCollapsedState() -{ - LLFloaterIMSessionTab* conversp = LLFloaterIMSessionTab::getConversation(mSessionID); - if(conversp->isNearbyChat()) - { - gSavedPerAccountSettings.setBOOL("NearbyChatIsNotCollapsed", isMessagePaneExpanded()); - } -} - -LLView* LLFloaterIMSessionTab::getChatHistory() -{ - return mChatHistory; -} - -bool LLFloaterIMSessionTab::handleKeyHere(KEY key, MASK mask ) -{ - bool handled = false; - - if(mask == MASK_ALT) - { - LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); - if (KEY_RETURN == key && !isTornOff()) - { - floater_container->expandConversation(); - handled = true; - } - if ((KEY_UP == key) || (KEY_LEFT == key)) - { - floater_container->selectNextorPreviousConversation(false); - handled = true; - } - if ((KEY_DOWN == key ) || (KEY_RIGHT == key)) - { - floater_container->selectNextorPreviousConversation(true); - handled = true; - } - } - return handled; -} +/** + * @file llfloaterimsessiontab.cpp + * @brief LLFloaterIMSessionTab class implements the common behavior of LNearbyChatBar + * @brief and LLFloaterIMSession for hosting both in LLIMContainer + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterimsessiontab.h" + +#include "llagent.h" +#include "llagentcamera.h" +#include "llavataractions.h" +#include "llavatariconctrl.h" +#include "llchatentry.h" +#include "llchathistory.h" +#include "llchiclet.h" +#include "llchicletbar.h" +#include "lldraghandle.h" +#include "llemojidictionary.h" +#include "llfloaterreg.h" +#include "llfloateremojipicker.h" +#include "llfloaterimsession.h" +#include "llfloaterimcontainer.h" // to replace separate IM Floaters with multifloater container +#include "llfloaterimnearbychat.h" +#include "llgroupiconctrl.h" +#include "lllayoutstack.h" +#include "llpanelemojicomplete.h" +#include "lltoolbarview.h" + +const F32 REFRESH_INTERVAL = 1.0f; +const std::string ICN_GROUP("group_chat_icon"); +const std::string ICN_NEARBY("nearby_chat_icon"); +const std::string ICN_AVATAR("avatar_icon"); + +void cb_group_do_nothing() +{ +} + +LLFloaterIMSessionTab::LLFloaterIMSessionTab(const LLSD& session_id) +: super(NULL, false, session_id), + mIsP2PChat(false), + mExpandCollapseBtn(NULL), + mTearOffBtn(NULL), + mCloseBtn(NULL), + mSessionID(session_id.asUUID()), + mConversationsRoot(NULL), + mScroller(NULL), + mChatHistory(NULL), + mInputEditor(NULL), + mInputEditorPad(0), + mRefreshTimer(new LLTimer()), + mIsHostAttached(false), + mHasVisibleBeenInitialized(false), + mIsParticipantListExpanded(true), + mChatLayoutPanel(NULL), + mInputPanels(NULL), + mChatLayoutPanelHeight(0) +{ + setAutoFocus(false); + mSession = LLIMModel::getInstance()->findIMSession(mSessionID); + + mCommitCallbackRegistrar.add("IMSession.Menu.Action", + boost::bind(&LLFloaterIMSessionTab::onIMSessionMenuItemClicked, this, _2)); + mEnableCallbackRegistrar.add("IMSession.Menu.CompactExpandedModes.CheckItem", + boost::bind(&LLFloaterIMSessionTab::onIMCompactExpandedMenuItemCheck, this, _2)); + mEnableCallbackRegistrar.add("IMSession.Menu.ShowModes.CheckItem", + boost::bind(&LLFloaterIMSessionTab::onIMShowModesMenuItemCheck, this, _2)); + mEnableCallbackRegistrar.add("IMSession.Menu.ShowModes.Enable", + boost::bind(&LLFloaterIMSessionTab::onIMShowModesMenuItemEnable, this, _2)); + + // Right click menu handling + mEnableCallbackRegistrar.add("Avatar.CheckItem", boost::bind(&LLFloaterIMSessionTab::checkContextMenuItem, this, _2)); + mEnableCallbackRegistrar.add("Avatar.EnableItem", boost::bind(&LLFloaterIMSessionTab::enableContextMenuItem, this, _2)); + mCommitCallbackRegistrar.add("Avatar.DoToSelected", boost::bind(&LLFloaterIMSessionTab::doToSelected, this, _2)); + mCommitCallbackRegistrar.add("Group.DoToSelected", boost::bind(&cb_group_do_nothing)); + + mMinFloaterHeight = getMinHeight(); +} + +LLFloaterIMSessionTab::~LLFloaterIMSessionTab() +{ + delete mRefreshTimer; + + LLFloaterIMContainer* im_container = LLFloaterIMContainer::findInstance(); + if (im_container) + { + LLParticipantList* session = dynamic_cast(im_container->getSessionModel(mSessionID)); + if (session) + { + for (const conversations_widgets_map::value_type& widget_pair : mConversationsWidgets) + { + LLFolderViewItem* widget = widget_pair.second; + LLFolderViewModelItem* item_vmi = widget->getViewModelItem(); + if (item_vmi && item_vmi->getNumRefs() == 1) + { + // This is the last pointer, remove participant from session + // before participant gets deleted on destroyView. + session->removeChild(item_vmi); + } + } + } + } +} + +// static +LLFloaterIMSessionTab* LLFloaterIMSessionTab::findConversation(const LLUUID& uuid) +{ + LLFloaterIMSessionTab* conv; + + if (uuid.isNull()) + { + conv = LLFloaterReg::findTypedInstance("nearby_chat"); + } + else + { + conv = LLFloaterReg::findTypedInstance("impanel", LLSD(uuid)); + } + + return conv; +}; + +// static +LLFloaterIMSessionTab* LLFloaterIMSessionTab::getConversation(const LLUUID& uuid) +{ + LLFloaterIMSessionTab* conv; + + if (uuid.isNull()) + { + conv = LLFloaterReg::getTypedInstance("nearby_chat"); + } + else + { + conv = LLFloaterReg::getTypedInstance("impanel", LLSD(uuid)); + conv->setOpenPositioning(LLFloaterEnums::POSITIONING_RELATIVE); + } + + return conv; + +}; + +// virtual +void LLFloaterIMSessionTab::setVisible(bool visible) +{ + if (visible && !mHasVisibleBeenInitialized) + { + mHasVisibleBeenInitialized = true; + if (!gAgentCamera.cameraMouselook()) + { + LLFloaterReg::getTypedInstance("im_container")->setVisible(true); + } + LLFloaterIMSessionTab::addToHost(mSessionID); + LLFloaterIMSessionTab* conversp = LLFloaterIMSessionTab::getConversation(mSessionID); + + if (conversp && conversp->isNearbyChat() && gSavedPerAccountSettings.getBOOL("NearbyChatIsNotCollapsed")) + { + onCollapseToLine(this); + } + mInputButtonPanel->setVisible(isTornOff()); + } + + super::setVisible(visible); +} + +// virtual +void LLFloaterIMSessionTab::setFocus(bool focus) +{ + super::setFocus(focus); + + // Redirect focus to input editor + if (focus) + { + updateMessages(); + + if (mInputEditor) + { + mInputEditor->setFocus(true); + } + } +} + +void LLFloaterIMSessionTab::addToHost(const LLUUID& session_id) +{ + if ((session_id.notNull() && !gIMMgr->hasSession(session_id)) + || !LLFloaterIMSessionTab::isChatMultiTab()) + { + return; + } + + // Get the floater: this will create the instance if it didn't exist + LLFloaterIMSessionTab* conversp = LLFloaterIMSessionTab::getConversation(session_id); + if (conversp) + { + LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); + + // Do not add again existing floaters + if (floater_container && !conversp->isHostAttached()) + { + conversp->setHostAttached(true); + + if (!conversp->isNearbyChat() + || gSavedPerAccountSettings.getBOOL("NearbyChatIsNotTornOff")) + { + floater_container->addFloater(conversp, false, LLTabContainer::RIGHT_OF_CURRENT); + } + else + { + // setting of the "potential" host for Nearby Chat: this sequence sets + // LLFloater::mHostHandle = NULL (a current host), but + // LLFloater::mLastHostHandle = floater_container (a "future" host) + conversp->setHost(floater_container); + conversp->setHost(NULL); + + conversp->forceReshape(); + } + // Added floaters share some state (like sort order) with their host + conversp->setSortOrder(floater_container->getSortOrder()); + } + } +} + +void LLFloaterIMSessionTab::assignResizeLimits() +{ + bool is_participants_pane_collapsed = mParticipantListPanel->isCollapsed(); + + // disable a layoutstack's functionality when participant list panel is collapsed + mRightPartPanel->setIgnoreReshape(is_participants_pane_collapsed); + + S32 participants_pane_target_width = is_participants_pane_collapsed? + 0 : (mParticipantListPanel->getRect().getWidth() + mParticipantListAndHistoryStack->getPanelSpacing()); + + S32 new_min_width = participants_pane_target_width + mRightPartPanel->getExpandedMinDim() + mFloaterExtraWidth; + + setResizeLimits(new_min_width, getMinHeight()); + + this->mParticipantListAndHistoryStack->updateLayout(); +} + +// virtual +bool LLFloaterIMSessionTab::postBuild() +{ + bool result; + + mBodyStack = getChild("main_stack"); + mParticipantListAndHistoryStack = getChild("im_panels"); + + mCloseBtn = getChild("close_btn"); + mCloseBtn->setCommitCallback([this](LLUICtrl*, const LLSD&) { onClickClose(this); }); + + mExpandCollapseBtn = getChild("expand_collapse_btn"); + mExpandCollapseBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onSlide(this); }); + + mExpandCollapseLineBtn = getChild("minz_btn"); + mExpandCollapseLineBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onCollapseToLine(this); }); + + mTearOffBtn = getChild("tear_off_btn"); + mTearOffBtn->setCommitCallback(boost::bind(&LLFloaterIMSessionTab::onTearOffClicked, this)); + + mEmojiRecentPanelToggleBtn = getChild("emoji_recent_panel_toggle_btn"); + mEmojiRecentPanelToggleBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiRecentPanelToggleBtnClicked(); }); + + mEmojiRecentPanel = getChild("emoji_recent_layout_panel"); + mEmojiRecentPanel->setVisible(false); + + mEmojiRecentEmptyText = getChild("emoji_recent_empty_text"); + mEmojiRecentEmptyText->setToolTip(mEmojiRecentEmptyText->getText()); + mEmojiRecentEmptyText->setVisible(false); + + mEmojiRecentContainer = getChild("emoji_recent_container"); + mEmojiRecentContainer->setVisible(false); + + mEmojiRecentIconsCtrl = getChild("emoji_recent_icons_ctrl"); + mEmojiRecentIconsCtrl->setFocusReceivedCallback([this](LLFocusableElement*) { onEmojiRecentPanelFocusReceived(); }); + mEmojiRecentIconsCtrl->setFocusLostCallback([this](LLFocusableElement*) { onEmojiRecentPanelFocusLost(); }); + mEmojiRecentIconsCtrl->setCommitCallback([this](LLUICtrl*, const LLSD& value) { onRecentEmojiPicked(value); }); + + mEmojiPickerShowBtn = getChild("emoji_picker_show_btn"); + mEmojiPickerShowBtn->setClickedCallback([this](LLUICtrl*, const LLSD&) { onEmojiPickerShowBtnClicked(); }); + + mGearBtn = getChild("gear_btn"); + mAddBtn = getChild("add_btn"); + mVoiceButton = getChild("voice_call_btn"); + + mParticipantListPanel = getChild("speakers_list_panel"); + mRightPartPanel = getChild("right_part_holder"); + + mToolbarPanel = getChild("toolbar_panel"); + mContentPanel = getChild("body_panel"); + mInputButtonPanel = getChild("input_button_layout_panel"); + mInputButtonPanel->setVisible(false); + // Add a scroller for the folder (participant) view + LLRect scroller_view_rect = mParticipantListPanel->getRect(); + scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom); + LLScrollContainer::Params scroller_params(LLUICtrlFactory::getDefaultParams()); + scroller_params.rect(scroller_view_rect); + mScroller = LLUICtrlFactory::create(scroller_params); + mScroller->setFollowsAll(); + + // Insert that scroller into the panel widgets hierarchy + mParticipantListPanel->addChild(mScroller); + + mChatHistory = getChild("chat_history"); + + mInputEditor = getChild("chat_editor"); + + mChatLayoutPanel = getChild("chat_layout_panel"); + mInputPanels = getChild("input_panels"); + + mInputEditor->setTextExpandedCallback(boost::bind(&LLFloaterIMSessionTab::reshapeChatLayoutPanel, this)); + mInputEditor->setMouseUpCallback(boost::bind(&LLFloaterIMSessionTab::onInputEditorClicked, this)); + mInputEditor->setCommitOnFocusLost( false ); + mInputEditor->setPassDelete(true); + mInputEditor->setFont(LLViewerChat::getChatFont()); + + mChatLayoutPanelHeight = mChatLayoutPanel->getRect().getHeight(); + mInputEditorPad = mChatLayoutPanelHeight - mInputEditor->getRect().getHeight(); + + setOpenPositioning(LLFloaterEnums::POSITIONING_RELATIVE); + + mSaveRect = isNearbyChat() + && !gSavedPerAccountSettings.getBOOL("NearbyChatIsNotTornOff"); + initRectControl(); + + if (isChatMultiTab()) + { + result = LLFloater::postBuild(); + } + else + { + result = LLDockableFloater::postBuild(); + } + + // Create the root using an ad-hoc base item + LLConversationItem* base_item = new LLConversationItem(mSessionID, mConversationViewModel); + LLFolderView::Params p(LLUICtrlFactory::getDefaultParams()); + p.rect = LLRect(0, 0, getRect().getWidth(), 0); + p.parent_panel = mParticipantListPanel; + p.listener = base_item; + p.view_model = &mConversationViewModel; + p.root = NULL; + p.use_ellipses = true; + p.options_menu = "menu_conversation.xml"; + p.name = "root"; + mConversationsRoot = LLUICtrlFactory::create(p); + mConversationsRoot->setCallbackRegistrar(&mCommitCallbackRegistrar); + mConversationsRoot->setEnableRegistrar(&mEnableCallbackRegistrar); + // Attach that root to the scroller + mScroller->addChild(mConversationsRoot); + mConversationsRoot->setScrollContainer(mScroller); + mConversationsRoot->setFollowsAll(); + mConversationsRoot->addChild(mConversationsRoot->mStatusTextBox); + + setMessagePaneExpanded(true); + + buildConversationViewParticipant(); + refreshConversation(); + + // Zero expiry time is set only once to allow initial update. + mRefreshTimer->setTimerExpirySec(0); + mRefreshTimer->start(); + initBtns(); + + if (mIsParticipantListExpanded != (bool)gSavedSettings.getBOOL("IMShowControlPanel")) + { + LLFloaterIMSessionTab::onSlide(this); + } + + // The resize limits for LLFloaterIMSessionTab should be updated, based on current values of width of conversation and message panels + mParticipantListPanel->getResizeBar()->setResizeListener(boost::bind(&LLFloaterIMSessionTab::assignResizeLimits, this)); + mFloaterExtraWidth = + getRect().getWidth() + - mParticipantListAndHistoryStack->getRect().getWidth() + - (mParticipantListPanel->isCollapsed()? 0 : LLPANEL_BORDER_WIDTH); + + assignResizeLimits(); + + return result; +} + +LLParticipantList* LLFloaterIMSessionTab::getParticipantList() +{ + return dynamic_cast(LLFloaterIMContainer::getInstance()->getSessionModel(mSessionID)); +} + +// virtual +void LLFloaterIMSessionTab::draw() +{ + if (mRefreshTimer->hasExpired()) + { + LLParticipantList* item = getParticipantList(); + if (item) + { + // Update all model items + item->update(); + // If the model and view list diverge in count, rebuild + // Note: this happens sometimes right around init (add participant events fire but get dropped) and is the cause + // of missing participants, often, the user agent itself. As there will be no other event fired, there's + // no other choice but get those inconsistencies regularly (and lightly) checked and scrubbed. + if (item->getChildrenCount() != mConversationsWidgets.size()) + { + buildConversationViewParticipant(); + } + refreshConversation(); + } + + // Restart the refresh timer + mRefreshTimer->setTimerExpirySec(REFRESH_INTERVAL); + } + + super::draw(); +} + +void LLFloaterIMSessionTab::enableDisableCallBtn() +{ + if (LLVoiceClient::instanceExists() && mVoiceButton) + { + mVoiceButton->setEnabled( + mSessionID.notNull() + && mSession + && mSession->mSessionInitialized + && LLVoiceClient::getInstance()->voiceEnabled() + && LLVoiceClient::getInstance()->isVoiceWorking() + && mSession->mCallBackEnabled); + } +} + +// virtual +void LLFloaterIMSessionTab::onFocusReceived() +{ + setBackgroundOpaque(true); + + if (mSessionID.notNull() && isInVisibleChain()) + { + LLIMModel::instance().sendNoUnreadMessages(mSessionID); + } + + super::onFocusReceived(); +} + +// virtual +void LLFloaterIMSessionTab::onFocusLost() +{ + setBackgroundOpaque(false); + super::onFocusLost(); +} + +void LLFloaterIMSessionTab::onInputEditorClicked() +{ + LLFloaterIMContainer* im_box = LLFloaterIMContainer::findInstance(); + if (im_box) + { + im_box->flashConversationItemWidget(mSessionID,false); + } + gToolBarView->flashCommand(LLCommandId("chat"), false); +} + +void LLFloaterIMSessionTab::onEmojiRecentPanelToggleBtnClicked() +{ + bool show = !mEmojiRecentPanel->getVisible(); + if (show) + { + initEmojiRecentPanel(); + } + + mEmojiRecentPanel->setVisible(show); + mInputEditor->setFocus(true); +} + +void LLFloaterIMSessionTab::onEmojiPickerShowBtnClicked() +{ + mInputEditor->setFocus(true); + mInputEditor->showEmojiHelper(); +} + +void LLFloaterIMSessionTab::initEmojiRecentPanel() +{ + std::list& recentlyUsed = LLFloaterEmojiPicker::getRecentlyUsed(); + if (recentlyUsed.empty()) + { + mEmojiRecentEmptyText->setVisible(true); + mEmojiRecentContainer->setVisible(false); + } + else + { + LLWString emojis; + for (llwchar emoji : recentlyUsed) + { + emojis += emoji; + } + mEmojiRecentIconsCtrl->setEmojis(emojis); + mEmojiRecentEmptyText->setVisible(false); + mEmojiRecentContainer->setVisible(true); + } +} + +// static +void LLFloaterIMSessionTab::onEmojiRecentPanelFocusReceived() +{ + mEmojiRecentContainer->addBorder(); +} + +// static +void LLFloaterIMSessionTab::onEmojiRecentPanelFocusLost() +{ + mEmojiRecentContainer->removeBorder(); +} + +void LLFloaterIMSessionTab::onRecentEmojiPicked(const LLSD& value) +{ + LLSD::String str = value.asString(); + if (str.size()) + { + LLWString wstr = utf8string_to_wstring(str); + if (wstr.size()) + { + llwchar emoji = wstr[0]; + mInputEditor->insertEmoji(emoji); + } + } +} + +void LLFloaterIMSessionTab::closeFloater(bool app_quitting) +{ + LLFloaterEmojiPicker::saveState(); + super::closeFloater(app_quitting); +} + +std::string LLFloaterIMSessionTab::appendTime() +{ + std::string timeStr = "[" + LLTrans::getString("TimeHour") + "]:" + "[" + LLTrans::getString("TimeMin") + "]"; + + LLSD substitution; + substitution["datetime"] = (S32)time_corrected(); + LLStringUtil::format(timeStr, substitution); + + return timeStr; +} + +void LLFloaterIMSessionTab::appendMessage(const LLChat& chat, const LLSD& args) +{ + if (chat.mMuted || !mChatHistory) + return; + + // Update the participant activity time + LLFloaterIMContainer* im_box = LLFloaterIMContainer::findInstance(); + if (im_box) + { + im_box->setTimeNow(mSessionID, chat.mFromID); + } + + LLChat& tmp_chat = const_cast(chat); + + if (tmp_chat.mTimeStr.empty()) + tmp_chat.mTimeStr = appendTime(); + + tmp_chat.mFromName = chat.mFromName; + + LLSD chat_args = args; + chat_args["use_plain_text_chat_history"] = + gSavedSettings.getBOOL("PlainTextChatHistory"); + chat_args["show_time"] = gSavedSettings.getBOOL("IMShowTime"); + chat_args["show_names_for_p2p_conv"] = !mIsP2PChat || + gSavedSettings.getBOOL("IMShowNamesForP2PConv"); + + mChatHistory->appendMessage(chat, chat_args); +} + +void LLFloaterIMSessionTab::updateUsedEmojis(LLWString text) +{ + LLEmojiDictionary* dictionary = LLEmojiDictionary::getInstance(); + llassert_always(dictionary); + + bool emojiSent = false; + for (llwchar& c : text) + { + if (dictionary->isEmoji(c)) + { + LLFloaterEmojiPicker::onEmojiUsed(c); + emojiSent = true; + } + } + + if (!emojiSent) + return; + + LLFloaterEmojiPicker::saveState(); + + if (mEmojiRecentPanel->getVisible()) + { + initEmojiRecentPanel(); + } +} + +static LLTrace::BlockTimerStatHandle FTM_BUILD_CONVERSATION_VIEW_PARTICIPANT("Build Conversation View"); +void LLFloaterIMSessionTab::buildConversationViewParticipant() +{ + LL_RECORD_BLOCK_TIME(FTM_BUILD_CONVERSATION_VIEW_PARTICIPANT); + // Clear the widget list since we are rebuilding afresh from the model + conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin(); + while (widget_it != mConversationsWidgets.end()) + { + removeConversationViewParticipant(widget_it->first); + // Iterators are invalidated by erase so we need to pick begin again + widget_it = mConversationsWidgets.begin(); + } + + // Get the model list + LLParticipantList* item = getParticipantList(); + if (!item) + { + // Nothing to do if the model list is inexistent + return; + } + + // Create the participants widgets now + LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = item->getChildrenBegin(); + LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = item->getChildrenEnd(); + while (current_participant_model != end_participant_model) + { + LLConversationItem* participant_model = dynamic_cast(*current_participant_model); + if (participant_model) + { + addConversationViewParticipant(participant_model); + } + current_participant_model++; + } +} + +void LLFloaterIMSessionTab::addConversationViewParticipant(LLConversationItem* participant_model, bool update_view) +{ + if (!participant_model) + { + // Nothing to do if the model is inexistent + return; + } + + // Check if the model already has an associated view + LLUUID uuid = participant_model->getUUID(); + LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,uuid); + + // If not already present, create the participant view and attach it to the root, otherwise, just refresh it + if (widget) + { + if (update_view) + { + updateConversationViewParticipant(uuid); // overkill? + } + } + else + { + LLConversationViewParticipant* participant_view = createConversationViewParticipant(participant_model); + mConversationsWidgets[uuid] = participant_view; + participant_view->addToFolder(mConversationsRoot); + participant_view->addToSession(mSessionID); + participant_view->setVisible(true); + } +} + +void LLFloaterIMSessionTab::removeConversationViewParticipant(const LLUUID& participant_id) +{ + LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,participant_id); + if (widget) + { + LLFolderViewModelItem* item_vmi = widget->getViewModelItem(); + if (item_vmi && item_vmi->getNumRefs() == 1) + { + // This is the last pointer, remove participant from session + // before participant gets deleted on destroyView. + // + // Floater (widget) and participant's view can simultaneously + // co-own the model, in which case view is responsible for + // the deletion and floater is free to clear and recreate + // the list, yet there are cases where only widget owns + // the pointer so it should do the cleanup. + // See "add_participant". + // + // Todo: If it keeps causing issues turn participants + // into LLPointers in the session + LLParticipantList* session = getParticipantList(); + if (session) + { + session->removeChild(item_vmi); + } + } + widget->destroyView(); + } + mConversationsWidgets.erase(participant_id); +} + +void LLFloaterIMSessionTab::updateConversationViewParticipant(const LLUUID& participant_id) +{ + LLFolderViewItem* widget = get_ptr_in_map(mConversationsWidgets,participant_id); + if (widget && widget->getViewModelItem()) + { + widget->refresh(); + } +} + +void LLFloaterIMSessionTab::refreshConversation() +{ + // Note: We collect participants names to change the session name only in the case of ad-hoc conversations + bool is_ad_hoc = (mSession ? mSession->isAdHocSessionType() : false); + uuid_vec_t participants_uuids; // uuids vector for building the added participants name string + // For P2P chat, we still need to update the session name who may have changed (switch display name for instance) + if (mIsP2PChat && mSession) + { + participants_uuids.push_back(mSession->mOtherParticipantID); + } + + conversations_widgets_map::iterator widget_it = mConversationsWidgets.begin(); + while (widget_it != mConversationsWidgets.end()) + { + // Add the participant to the list except if it's the agent itself (redundant) + if (is_ad_hoc && (widget_it->first != gAgentID)) + { + participants_uuids.push_back(widget_it->first); + } + if (widget_it->second->getViewModelItem()) + { + widget_it->second->refresh(); + widget_it->second->setVisible(true); + } + ++widget_it; + } + if (is_ad_hoc || mIsP2PChat) + { + // Build the session name and update it + std::string session_name; + if (participants_uuids.size() != 0) + { + LLAvatarActions::buildResidentsString(participants_uuids, session_name); + } + else + { + session_name = LLIMModel::instance().getName(mSessionID); + } + updateSessionName(session_name); + } + + if (mSessionID.notNull()) + { + LLParticipantList* participant_list = getParticipantList(); + if (participant_list) + { + LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = participant_list->getChildrenBegin(); + LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = participant_list->getChildrenEnd(); + LLIMSpeakerMgr *speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(mSessionID); + while (current_participant_model != end_participant_model) + { + LLConversationItemParticipant* participant_model = dynamic_cast(*current_participant_model); + if (speaker_mgr && participant_model) + { + LLSpeaker *participant_speaker = speaker_mgr->findSpeaker(participant_model->getUUID()); + LLSpeaker *agent_speaker = speaker_mgr->findSpeaker(gAgentID); + if (participant_speaker && agent_speaker) + { + participant_model->setDisplayModeratorRole(agent_speaker->mIsModerator && participant_speaker->mIsModerator); + } + } + current_participant_model++; + } + } + } + + mConversationViewModel.requestSortAll(); + if(mConversationsRoot != NULL) + { + mConversationsRoot->arrangeAll(); + mConversationsRoot->update(); + } + updateHeaderAndToolbar(); + refresh(); +} + +// Copied from LLFloaterIMContainer::createConversationViewParticipant(). Refactor opportunity! +LLConversationViewParticipant* LLFloaterIMSessionTab::createConversationViewParticipant(LLConversationItem* item) +{ + LLRect panel_rect = mParticipantListPanel->getRect(); + + LLConversationViewParticipant::Params params; + params.name = item->getDisplayName(); + params.root = mConversationsRoot; + params.listener = item; + params.rect = LLRect (0, 24, panel_rect.getWidth(), 0); // *TODO: use conversation_view_participant.xml itemHeight value in lieu of 24 + params.tool_tip = params.name; + params.participant_id = item->getUUID(); + + return LLUICtrlFactory::create(params); +} + +void LLFloaterIMSessionTab::setSortOrder(const LLConversationSort& order) +{ + mConversationViewModel.setSorter(order); + mConversationsRoot->arrangeAll(); + refreshConversation(); +} + +void LLFloaterIMSessionTab::onIMSessionMenuItemClicked(const LLSD& userdata) +{ + std::string item = userdata.asString(); + + if (item == "compact_view" || item == "expanded_view") + { + gSavedSettings.setBOOL("PlainTextChatHistory", item == "compact_view"); + } + else + { + bool prev_value = gSavedSettings.getBOOL(item); + gSavedSettings.setBOOL(item, !prev_value); + } + + LLFloaterIMSessionTab::processChatHistoryStyleUpdate(); +} + +bool LLFloaterIMSessionTab::onIMCompactExpandedMenuItemCheck(const LLSD& userdata) +{ + std::string item = userdata.asString(); + bool is_plain_text_mode = gSavedSettings.getBOOL("PlainTextChatHistory"); + + return is_plain_text_mode? item == "compact_view" : item == "expanded_view"; +} + + +bool LLFloaterIMSessionTab::onIMShowModesMenuItemCheck(const LLSD& userdata) +{ + return gSavedSettings.getBOOL(userdata.asString()); +} + +// enable/disable states for the "show time" and "show names" items of the show-modes menu +bool LLFloaterIMSessionTab::onIMShowModesMenuItemEnable(const LLSD& userdata) +{ + std::string item = userdata.asString(); + bool plain_text = gSavedSettings.getBOOL("PlainTextChatHistory"); + bool is_not_names = (item != "IMShowNamesForP2PConv"); + return (plain_text && (is_not_names || mIsP2PChat)); +} + +void LLFloaterIMSessionTab::hideOrShowTitle() +{ + const LLFloater::Params& default_params = LLFloater::getDefaultParams(); + S32 floater_header_size = default_params.header_height; + LLView* floater_contents = getChild("contents_view"); + + LLRect floater_rect = getLocalRect(); + S32 top_border_of_contents = floater_rect.mTop - (isTornOff()? floater_header_size : 0); + LLRect handle_rect (0, floater_rect.mTop, floater_rect.mRight, top_border_of_contents); + LLRect contents_rect (0, top_border_of_contents, floater_rect.mRight, floater_rect.mBottom); + mDragHandle->setShape(handle_rect); + mDragHandle->setVisible(isTornOff()); + floater_contents->setShape(contents_rect); +} + +void LLFloaterIMSessionTab::updateSessionName(const std::string& name) +{ + mInputEditor->setLabel(LLTrans::getString("IM_to_label") + " " + name); +} + +void LLFloaterIMSessionTab::updateChatIcon(const LLUUID& id) +{ + if (mSession) + { + if (mSession->isP2PSessionType()) + { + LLAvatarIconCtrl* icon = getChild(ICN_AVATAR); + icon->setVisible(true); + icon->setValue(id); + } + if (mSession->isAdHocSessionType()) + { + LLGroupIconCtrl* icon = getChild(ICN_GROUP); + icon->setVisible(true); + } + if (mSession->isGroupSessionType()) + { + LLGroupIconCtrl* icon = getChild(ICN_GROUP); + icon->setVisible(true); + icon->setValue(id); + } + } + else + { + if (mIsNearbyChat) + { + LLIconCtrl* icon = getChild(ICN_NEARBY); + icon->setVisible(true); + } + } + +} + +void LLFloaterIMSessionTab::hideAllStandardButtons() +{ + for (S32 i = 0; i < BUTTON_COUNT; i++) + { + if (mButtons[i]) + { + // Hide the standard header buttons in a docked IM floater. + mButtons[i]->setVisible(false); + } + } +} + +void LLFloaterIMSessionTab::updateHeaderAndToolbar() +{ + // prevent start conversation before its container + LLFloaterIMContainer::getInstance(); + + bool is_not_torn_off = !checkIfTornOff(); + if (is_not_torn_off) + { + hideAllStandardButtons(); + } + + hideOrShowTitle(); + + // Participant list should be visible only in torn off floaters. + bool is_participant_list_visible = + !is_not_torn_off + && mIsParticipantListExpanded + && !mIsP2PChat; + + mParticipantListAndHistoryStack->collapsePanel(mParticipantListPanel, !is_participant_list_visible); + mParticipantListPanel->setVisible(is_participant_list_visible); + + // Display collapse image (<<) if the floater is hosted + // or if it is torn off but has an open control panel. + bool is_expanded = is_not_torn_off || is_participant_list_visible; + + mExpandCollapseBtn->setImageOverlay(getString(is_expanded ? "collapse_icon" : "expand_icon")); + mExpandCollapseBtn->setToolTip( + is_not_torn_off? + getString("expcol_button_not_tearoff_tooltip") : + (is_expanded? + getString("expcol_button_tearoff_and_expanded_tooltip") : + getString("expcol_button_tearoff_and_collapsed_tooltip"))); + + // toggle floater's drag handle and title visibility + if (mDragHandle) + { + mDragHandle->setTitleVisible(!is_not_torn_off); + } + + // The button (>>) should be disabled for torn off P2P conversations. + mExpandCollapseBtn->setEnabled(is_not_torn_off || !mIsP2PChat); + + mTearOffBtn->setImageOverlay(getString(is_not_torn_off? "tear_off_icon" : "return_icon")); + mTearOffBtn->setToolTip(getString(is_not_torn_off? "tooltip_to_separate_window" : "tooltip_to_main_window")); + + + mCloseBtn->setVisible(is_not_torn_off && !mIsNearbyChat); + + enableDisableCallBtn(); +} + +void LLFloaterIMSessionTab::forceReshape() +{ + LLRect floater_rect = getRect(); + reshape(llmax(floater_rect.getWidth(), this->getMinWidth()), + llmax(floater_rect.getHeight(), this->getMinHeight()), + true); +} + + +void LLFloaterIMSessionTab::reshapeChatLayoutPanel() +{ + mChatLayoutPanel->reshape(mChatLayoutPanel->getRect().getWidth(), mInputEditor->getRect().getHeight() + mInputEditorPad, false); +} + +// static +void LLFloaterIMSessionTab::processChatHistoryStyleUpdate(bool clean_messages/* = false*/) +{ + LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("impanel"); + for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); + iter != inst_list.end(); ++iter) + { + LLFloaterIMSession* floater = dynamic_cast(*iter); + if (floater) + { + floater->reloadMessages(clean_messages); + } + } + + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); + if (nearby_chat) + { + nearby_chat->reloadMessages(clean_messages); + } +} + +// static +void LLFloaterIMSessionTab::reloadEmptyFloaters() +{ + LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("impanel"); + for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); + iter != inst_list.end(); ++iter) + { + LLFloaterIMSession* floater = dynamic_cast(*iter); + if (floater && floater->getLastChatMessageIndex() == -1) + { + floater->reloadMessages(true); + } + } + + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); + if (nearby_chat && nearby_chat->getMessageArchiveLength() == 0) + { + nearby_chat->reloadMessages(true); + } +} + +void LLFloaterIMSessionTab::updateCallBtnState(bool callIsActive) +{ + mVoiceButton->setImageOverlay(callIsActive? getString("call_btn_stop") : getString("call_btn_start")); + mVoiceButton->setToolTip(callIsActive? getString("end_call_button_tooltip") : getString("start_call_button_tooltip")); + + enableDisableCallBtn(); +} + +void LLFloaterIMSessionTab::onSlide(LLFloaterIMSessionTab* self) +{ + LLFloaterIMContainer* host_floater = dynamic_cast(self->getHost()); + bool should_be_expanded = false; + if (host_floater) + { + // Hide the messages pane if a floater is hosted in the Conversations + host_floater->collapseMessagesPane(true); + } + else ///< floater is torn off + { + if (!self->mIsP2PChat) + { + // The state must toggle the collapsed state of the panel + should_be_expanded = self->mParticipantListPanel->isCollapsed(); + + // Update the expand/collapse flag of the participant list panel and save it + gSavedSettings.setBOOL("IMShowControlPanel", should_be_expanded); + self->mIsParticipantListExpanded = should_be_expanded; + + // Refresh for immediate feedback + self->refreshConversation(); + } + } + + self->assignResizeLimits(); + if (should_be_expanded) + { + self->forceReshape(); + } +} + +void LLFloaterIMSessionTab::onCollapseToLine(LLFloaterIMSessionTab* self) +{ + LLFloaterIMContainer* host_floater = dynamic_cast(self->getHost()); + if (!host_floater) + { + bool expand = self->isMessagePaneExpanded(); + self->mExpandCollapseLineBtn->setImageOverlay(self->getString(expand ? "collapseline_icon" : "expandline_icon")); + self->mContentPanel->setVisible(!expand); + self->mToolbarPanel->setVisible(!expand); + self->mInputEditor->enableSingleLineMode(expand); + self->reshapeFloater(expand); + self->setMessagePaneExpanded(!expand); + } +} + +void LLFloaterIMSessionTab::reshapeFloater(bool collapse) +{ + LLRect floater_rect = getRect(); + + if(collapse) + { + mFloaterHeight = floater_rect.getHeight(); + S32 height = mContentPanel->getRect().getHeight() + mToolbarPanel->getRect().getHeight() + + mChatLayoutPanel->getRect().getHeight() - mChatLayoutPanelHeight + 2; + floater_rect.mTop -= height; + + setResizeLimits(getMinWidth(), floater_rect.getHeight()); + } + else + { + floater_rect.mTop = floater_rect.mBottom + mFloaterHeight; + setResizeLimits(getMinWidth(), mMinFloaterHeight); + } + + enableResizeCtrls(true, true, !collapse); + + saveCollapsedState(); + setShape(floater_rect, true); + mBodyStack->updateLayout(); +} + +void LLFloaterIMSessionTab::restoreFloater() +{ + if(!isMessagePaneExpanded()) + { + if(isMinimized()) + { + setMinimized(false); + } + mContentPanel->setVisible(true); + mToolbarPanel->setVisible(true); + LLRect floater_rect = getRect(); + floater_rect.mTop = floater_rect.mBottom + mFloaterHeight; + setShape(floater_rect, true); + mBodyStack->updateLayout(); + mExpandCollapseLineBtn->setImageOverlay(getString("expandline_icon")); + setResizeLimits(getMinWidth(), mMinFloaterHeight); + setMessagePaneExpanded(true); + saveCollapsedState(); + mInputEditor->enableSingleLineMode(false); + enableResizeCtrls(true, true, true); + } +} + +/*virtual*/ +void LLFloaterIMSessionTab::onOpen(const LLSD& key) +{ + if (!checkIfTornOff()) + { + LLFloaterIMContainer* host_floater = dynamic_cast(getHost()); + // Show the messages pane when opening a floater hosted in the Conversations + host_floater->collapseMessagesPane(false); + } + + mInputButtonPanel->setVisible(isTornOff()); + + setFocus(true); +} + + +void LLFloaterIMSessionTab::onTearOffClicked() +{ + restoreFloater(); + setFollows(isTornOff()? FOLLOWS_ALL : FOLLOWS_NONE); + mSaveRect = isTornOff(); + initRectControl(); + LLFloater::onClickTearOff(this); + LLFloaterIMContainer* container = LLFloaterReg::findTypedInstance("im_container"); + + if (isTornOff()) + { + container->selectAdjacentConversation(false); + forceReshape(); + } + //Upon re-docking the torn off floater, select the corresponding conversation line item + else + { + container->selectConversation(mSessionID); + + } + mInputButtonPanel->setVisible(isTornOff()); + + refreshConversation(); + updateGearBtn(); +} + +void LLFloaterIMSessionTab::updateGearBtn() +{ + bool prevVisibility = mGearBtn->getVisible(); + mGearBtn->setVisible(checkIfTornOff() && mIsP2PChat); + + + // Move buttons if Gear button changed visibility + if(prevVisibility != mGearBtn->getVisible()) + { + LLRect gear_btn_rect = mGearBtn->getRect(); + LLRect add_btn_rect = mAddBtn->getRect(); + LLRect call_btn_rect = mVoiceButton->getRect(); + S32 gap_width = call_btn_rect.mLeft - add_btn_rect.mRight; + S32 right_shift = gear_btn_rect.getWidth() + gap_width; + if(mGearBtn->getVisible()) + { + // Move buttons to the right to give space for Gear button + add_btn_rect.translate(right_shift,0); + call_btn_rect.translate(right_shift,0); + } + else + { + add_btn_rect.translate(-right_shift,0); + call_btn_rect.translate(-right_shift,0); + } + mAddBtn->setRect(add_btn_rect); + mVoiceButton->setRect(call_btn_rect); + } +} + +void LLFloaterIMSessionTab::initBtns() +{ + LLRect gear_btn_rect = mGearBtn->getRect(); + LLRect add_btn_rect = mAddBtn->getRect(); + LLRect call_btn_rect = mVoiceButton->getRect(); + S32 gap_width = call_btn_rect.mLeft - add_btn_rect.mRight; + S32 right_shift = gear_btn_rect.getWidth() + gap_width; + + add_btn_rect.translate(-right_shift,0); + call_btn_rect.translate(-right_shift,0); + + mAddBtn->setRect(add_btn_rect); + mVoiceButton->setRect(call_btn_rect); +} + +// static +bool LLFloaterIMSessionTab::isChatMultiTab() +{ + // Restart is required in order to change chat window type. + return true; +} + +bool LLFloaterIMSessionTab::checkIfTornOff() +{ + bool isTorn = !getHost(); + + if (isTorn != isTornOff()) + { + setTornOff(isTorn); + refreshConversation(); + } + + return isTorn; +} + +void LLFloaterIMSessionTab::doToSelected(const LLSD& userdata) +{ + // Get the list of selected items in the tab + std::string command = userdata.asString(); + uuid_vec_t selected_uuids; + getSelectedUUIDs(selected_uuids); + + // Perform the command (IM, profile, etc...) on the list using the general conversation container method + LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); + // Note: By construction, those can only be participants so we can call doToParticipants() directly + floater_container->doToParticipants(command, selected_uuids); +} + +bool LLFloaterIMSessionTab::enableContextMenuItem(const LLSD& userdata) +{ + // Get the list of selected items in the tab + std::string command = userdata.asString(); + uuid_vec_t selected_uuids; + getSelectedUUIDs(selected_uuids); + + // Perform the item enable test on the list using the general conversation container method + LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); + return floater_container->enableContextMenuItem(command, selected_uuids); +} + +bool LLFloaterIMSessionTab::checkContextMenuItem(const LLSD& userdata) +{ + // Get the list of selected items in the tab + std::string command = userdata.asString(); + uuid_vec_t selected_uuids; + getSelectedUUIDs(selected_uuids); + + // Perform the item check on the list using the general conversation container method + LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); + return floater_container->checkContextMenuItem(command, selected_uuids); +} + +void LLFloaterIMSessionTab::getSelectedUUIDs(uuid_vec_t& selected_uuids) +{ + const std::set selected_items = mConversationsRoot->getSelectionList(); + + std::set::const_iterator it = selected_items.begin(); + const std::set::const_iterator it_end = selected_items.end(); + + for (; it != it_end; ++it) + { + LLConversationItem* conversation_item = static_cast((*it)->getViewModelItem()); + if (conversation_item) + { + selected_uuids.push_back(conversation_item->getUUID()); + } + } +} + +LLConversationItem* LLFloaterIMSessionTab::getCurSelectedViewModelItem() +{ + LLConversationItem *conversationItem = NULL; + + if(mConversationsRoot && + mConversationsRoot->getCurSelectedItem() && + mConversationsRoot->getCurSelectedItem()->getViewModelItem()) + { + conversationItem = static_cast(mConversationsRoot->getCurSelectedItem()->getViewModelItem()) ; + } + + return conversationItem; +} + +void LLFloaterIMSessionTab::saveCollapsedState() +{ + LLFloaterIMSessionTab* conversp = LLFloaterIMSessionTab::getConversation(mSessionID); + if(conversp->isNearbyChat()) + { + gSavedPerAccountSettings.setBOOL("NearbyChatIsNotCollapsed", isMessagePaneExpanded()); + } +} + +LLView* LLFloaterIMSessionTab::getChatHistory() +{ + return mChatHistory; +} + +bool LLFloaterIMSessionTab::handleKeyHere(KEY key, MASK mask ) +{ + bool handled = false; + + if(mask == MASK_ALT) + { + LLFloaterIMContainer* floater_container = LLFloaterIMContainer::getInstance(); + if (KEY_RETURN == key && !isTornOff()) + { + floater_container->expandConversation(); + handled = true; + } + if ((KEY_UP == key) || (KEY_LEFT == key)) + { + floater_container->selectNextorPreviousConversation(false); + handled = true; + } + if ((KEY_DOWN == key ) || (KEY_RIGHT == key)) + { + floater_container->selectNextorPreviousConversation(true); + handled = true; + } + } + return handled; +} diff --git a/indra/newview/llfloaterimsessiontab.h b/indra/newview/llfloaterimsessiontab.h index 2c9606892a..f1773520af 100644 --- a/indra/newview/llfloaterimsessiontab.h +++ b/indra/newview/llfloaterimsessiontab.h @@ -1,238 +1,238 @@ -/** - * @file llfloaterimsessiontab.h - * @brief LLFloaterIMSessionTab class implements the common behavior of LNearbyChatBar - * @brief and LLFloaterIMSession for hosting both in LLIMContainer - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_FLOATERIMSESSIONTAB_H -#define LL_FLOATERIMSESSIONTAB_H - -#include "lllayoutstack.h" -#include "llparticipantlist.h" -#include "lltransientdockablefloater.h" -#include "llviewercontrol.h" -#include "lleventtimer.h" -#include "llimview.h" -#include "llconversationmodel.h" -#include "llconversationview.h" -#include "lltexteditor.h" - -class LLPanelChatControlPanel; -class LLChatEntry; -class LLChatHistory; -class LLPanelEmojiComplete; - -class LLFloaterIMSessionTab - : public LLTransientDockableFloater -{ - using super = LLTransientDockableFloater; - -public: - LOG_CLASS(LLFloaterIMSessionTab); - - LLFloaterIMSessionTab(const LLSD& session_id); - ~LLFloaterIMSessionTab(); - - // reload all message with new settings of visual modes - static void processChatHistoryStyleUpdate(bool clean_messages = false); - static void reloadEmptyFloaters(); - - /** - * Returns true if chat is displayed in multi tabbed floater - * false if chat is displayed in multiple windows - */ - static bool isChatMultiTab(); - - // add conversation to container - static void addToHost(const LLUUID& session_id); - - bool isHostAttached() {return mIsHostAttached;} - void setHostAttached(bool is_attached) {mIsHostAttached = is_attached;} - - static LLFloaterIMSessionTab* findConversation(const LLUUID& uuid); - static LLFloaterIMSessionTab* getConversation(const LLUUID& uuid); - - bool isNearbyChat() {return mIsNearbyChat;} - - // LLFloater overrides - /*virtual*/ void onOpen(const LLSD& key); - /*virtual*/ bool postBuild(); - /*virtual*/ void draw(); - /*virtual*/ void setVisible(bool visible); - /*virtual*/ void setFocus(bool focus); - /*virtual*/ void closeFloater(bool app_quitting = false); - - // Handle the left hand participant list widgets - void addConversationViewParticipant(LLConversationItem* item, bool update_view = true); - void removeConversationViewParticipant(const LLUUID& participant_id); - void updateConversationViewParticipant(const LLUUID& participant_id); - void refreshConversation(); - void buildConversationViewParticipant(); - - void setSortOrder(const LLConversationSort& order); - virtual void onTearOffClicked(); - void updateGearBtn(); - void initBtns(); - virtual void updateMessages() {} - LLConversationItem* getCurSelectedViewModelItem(); - void forceReshape(); - virtual bool handleKeyHere( KEY key, MASK mask ); - bool isMessagePaneExpanded(){return mMessagePaneExpanded;} - void setMessagePaneExpanded(bool expanded){mMessagePaneExpanded = expanded;} - void restoreFloater(); - void saveCollapsedState(); - - void updateChatIcon(const LLUUID& id); - - LLView* getChatHistory(); - -protected: - - // callback for click on any items of the visual states menu - void onIMSessionMenuItemClicked(const LLSD& userdata); - - // callback for check/uncheck of the expanded/collapse mode's switcher - bool onIMCompactExpandedMenuItemCheck(const LLSD& userdata); - - // - bool onIMShowModesMenuItemCheck(const LLSD& userdata); - bool onIMShowModesMenuItemEnable(const LLSD& userdata); - static void onSlide(LLFloaterIMSessionTab *self); - static void onCollapseToLine(LLFloaterIMSessionTab *self); - void reshapeFloater(bool collapse); - - // refresh a visual state of the Call button - void updateCallBtnState(bool callIsActive); - - void hideOrShowTitle(); // toggle the floater's drag handle - void hideAllStandardButtons(); - - /// Update floater header and toolbar buttons when hosted/torn off state is toggled. - void updateHeaderAndToolbar(); - - // Update the input field help text and other places that need the session name - virtual void updateSessionName(const std::string& name); - - // set the enable/disable state for the Call button - virtual void enableDisableCallBtn(); - - // process focus events to set a currently active session - /* virtual */ void onFocusReceived(); - /* virtual */ void onFocusLost(); - - // prepare chat's params and out one message to chatHistory - void appendMessage(const LLChat& chat, const LLSD& args = LLSD()); - - std::string appendTime(); - void assignResizeLimits(); - - void updateUsedEmojis(LLWString text); - - S32 mFloaterExtraWidth; - - bool mIsNearbyChat; - bool mIsP2PChat; - - bool mMessagePaneExpanded; - bool mIsParticipantListExpanded; - S32 mMinFloaterHeight; - - LLIMModel::LLIMSession* mSession; - - // Participants list: model and view - LLConversationViewParticipant* createConversationViewParticipant(LLConversationItem* item); - - LLUUID mSessionID; - LLLayoutStack* mBodyStack; - LLLayoutStack* mParticipantListAndHistoryStack; - LLLayoutPanel* mParticipantListPanel; // add the widgets to that see mConversationsListPanel - LLLayoutPanel* mRightPartPanel; - LLLayoutPanel* mContentPanel; - LLLayoutPanel* mToolbarPanel; - LLLayoutPanel* mInputButtonPanel; - LLLayoutPanel* mEmojiRecentPanel; - LLTextBox* mEmojiRecentEmptyText; - LLPanel* mEmojiRecentContainer; - LLPanelEmojiComplete* mEmojiRecentIconsCtrl; - LLParticipantList* getParticipantList(); - conversations_widgets_map mConversationsWidgets; - LLConversationViewModel mConversationViewModel; - LLFolderView* mConversationsRoot; - LLScrollContainer* mScroller; - - LLChatHistory* mChatHistory; - LLChatEntry* mInputEditor; - LLLayoutPanel* mChatLayoutPanel; - LLLayoutStack* mInputPanels; - - LLButton* mExpandCollapseLineBtn; - LLButton* mExpandCollapseBtn; - LLButton* mTearOffBtn; - LLButton* mEmojiRecentPanelToggleBtn; - LLButton* mEmojiPickerShowBtn; - LLButton* mCloseBtn; - LLButton* mGearBtn; - LLButton* mAddBtn; - LLButton* mVoiceButton; - -private: - // Handling selection and contextual menu - void doToSelected(const LLSD& userdata); - bool enableContextMenuItem(const LLSD& userdata); - bool checkContextMenuItem(const LLSD& userdata); - - void getSelectedUUIDs(uuid_vec_t& selected_uuids); - - /// Refreshes the floater at a constant rate. - virtual void refresh() = 0; - - /** - * Adjusts chat history height to fit vertically with input chat field - * and avoid overlapping, since input chat field can be vertically expanded. - * Implementation: chat history bottom "follows" top+top_pad of input chat field - */ - void reshapeChatLayoutPanel(); - - void onInputEditorClicked(); - - void onEmojiRecentPanelToggleBtnClicked(); - void onEmojiPickerShowBtnClicked(); - void initEmojiRecentPanel(); - void onEmojiRecentPanelFocusReceived(); - void onEmojiRecentPanelFocusLost(); - void onRecentEmojiPicked(const LLSD& value); - - bool checkIfTornOff(); - bool mIsHostAttached; - bool mHasVisibleBeenInitialized; - - LLTimer* mRefreshTimer; ///< Defines the rate at which refresh() is called. - - S32 mInputEditorPad; - S32 mChatLayoutPanelHeight; - S32 mFloaterHeight; -}; - - -#endif /* LL_FLOATERIMSESSIONTAB_H */ +/** + * @file llfloaterimsessiontab.h + * @brief LLFloaterIMSessionTab class implements the common behavior of LNearbyChatBar + * @brief and LLFloaterIMSession for hosting both in LLIMContainer + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_FLOATERIMSESSIONTAB_H +#define LL_FLOATERIMSESSIONTAB_H + +#include "lllayoutstack.h" +#include "llparticipantlist.h" +#include "lltransientdockablefloater.h" +#include "llviewercontrol.h" +#include "lleventtimer.h" +#include "llimview.h" +#include "llconversationmodel.h" +#include "llconversationview.h" +#include "lltexteditor.h" + +class LLPanelChatControlPanel; +class LLChatEntry; +class LLChatHistory; +class LLPanelEmojiComplete; + +class LLFloaterIMSessionTab + : public LLTransientDockableFloater +{ + using super = LLTransientDockableFloater; + +public: + LOG_CLASS(LLFloaterIMSessionTab); + + LLFloaterIMSessionTab(const LLSD& session_id); + ~LLFloaterIMSessionTab(); + + // reload all message with new settings of visual modes + static void processChatHistoryStyleUpdate(bool clean_messages = false); + static void reloadEmptyFloaters(); + + /** + * Returns true if chat is displayed in multi tabbed floater + * false if chat is displayed in multiple windows + */ + static bool isChatMultiTab(); + + // add conversation to container + static void addToHost(const LLUUID& session_id); + + bool isHostAttached() {return mIsHostAttached;} + void setHostAttached(bool is_attached) {mIsHostAttached = is_attached;} + + static LLFloaterIMSessionTab* findConversation(const LLUUID& uuid); + static LLFloaterIMSessionTab* getConversation(const LLUUID& uuid); + + bool isNearbyChat() {return mIsNearbyChat;} + + // LLFloater overrides + /*virtual*/ void onOpen(const LLSD& key); + /*virtual*/ bool postBuild(); + /*virtual*/ void draw(); + /*virtual*/ void setVisible(bool visible); + /*virtual*/ void setFocus(bool focus); + /*virtual*/ void closeFloater(bool app_quitting = false); + + // Handle the left hand participant list widgets + void addConversationViewParticipant(LLConversationItem* item, bool update_view = true); + void removeConversationViewParticipant(const LLUUID& participant_id); + void updateConversationViewParticipant(const LLUUID& participant_id); + void refreshConversation(); + void buildConversationViewParticipant(); + + void setSortOrder(const LLConversationSort& order); + virtual void onTearOffClicked(); + void updateGearBtn(); + void initBtns(); + virtual void updateMessages() {} + LLConversationItem* getCurSelectedViewModelItem(); + void forceReshape(); + virtual bool handleKeyHere( KEY key, MASK mask ); + bool isMessagePaneExpanded(){return mMessagePaneExpanded;} + void setMessagePaneExpanded(bool expanded){mMessagePaneExpanded = expanded;} + void restoreFloater(); + void saveCollapsedState(); + + void updateChatIcon(const LLUUID& id); + + LLView* getChatHistory(); + +protected: + + // callback for click on any items of the visual states menu + void onIMSessionMenuItemClicked(const LLSD& userdata); + + // callback for check/uncheck of the expanded/collapse mode's switcher + bool onIMCompactExpandedMenuItemCheck(const LLSD& userdata); + + // + bool onIMShowModesMenuItemCheck(const LLSD& userdata); + bool onIMShowModesMenuItemEnable(const LLSD& userdata); + static void onSlide(LLFloaterIMSessionTab *self); + static void onCollapseToLine(LLFloaterIMSessionTab *self); + void reshapeFloater(bool collapse); + + // refresh a visual state of the Call button + void updateCallBtnState(bool callIsActive); + + void hideOrShowTitle(); // toggle the floater's drag handle + void hideAllStandardButtons(); + + /// Update floater header and toolbar buttons when hosted/torn off state is toggled. + void updateHeaderAndToolbar(); + + // Update the input field help text and other places that need the session name + virtual void updateSessionName(const std::string& name); + + // set the enable/disable state for the Call button + virtual void enableDisableCallBtn(); + + // process focus events to set a currently active session + /* virtual */ void onFocusReceived(); + /* virtual */ void onFocusLost(); + + // prepare chat's params and out one message to chatHistory + void appendMessage(const LLChat& chat, const LLSD& args = LLSD()); + + std::string appendTime(); + void assignResizeLimits(); + + void updateUsedEmojis(LLWString text); + + S32 mFloaterExtraWidth; + + bool mIsNearbyChat; + bool mIsP2PChat; + + bool mMessagePaneExpanded; + bool mIsParticipantListExpanded; + S32 mMinFloaterHeight; + + LLIMModel::LLIMSession* mSession; + + // Participants list: model and view + LLConversationViewParticipant* createConversationViewParticipant(LLConversationItem* item); + + LLUUID mSessionID; + LLLayoutStack* mBodyStack; + LLLayoutStack* mParticipantListAndHistoryStack; + LLLayoutPanel* mParticipantListPanel; // add the widgets to that see mConversationsListPanel + LLLayoutPanel* mRightPartPanel; + LLLayoutPanel* mContentPanel; + LLLayoutPanel* mToolbarPanel; + LLLayoutPanel* mInputButtonPanel; + LLLayoutPanel* mEmojiRecentPanel; + LLTextBox* mEmojiRecentEmptyText; + LLPanel* mEmojiRecentContainer; + LLPanelEmojiComplete* mEmojiRecentIconsCtrl; + LLParticipantList* getParticipantList(); + conversations_widgets_map mConversationsWidgets; + LLConversationViewModel mConversationViewModel; + LLFolderView* mConversationsRoot; + LLScrollContainer* mScroller; + + LLChatHistory* mChatHistory; + LLChatEntry* mInputEditor; + LLLayoutPanel* mChatLayoutPanel; + LLLayoutStack* mInputPanels; + + LLButton* mExpandCollapseLineBtn; + LLButton* mExpandCollapseBtn; + LLButton* mTearOffBtn; + LLButton* mEmojiRecentPanelToggleBtn; + LLButton* mEmojiPickerShowBtn; + LLButton* mCloseBtn; + LLButton* mGearBtn; + LLButton* mAddBtn; + LLButton* mVoiceButton; + +private: + // Handling selection and contextual menu + void doToSelected(const LLSD& userdata); + bool enableContextMenuItem(const LLSD& userdata); + bool checkContextMenuItem(const LLSD& userdata); + + void getSelectedUUIDs(uuid_vec_t& selected_uuids); + + /// Refreshes the floater at a constant rate. + virtual void refresh() = 0; + + /** + * Adjusts chat history height to fit vertically with input chat field + * and avoid overlapping, since input chat field can be vertically expanded. + * Implementation: chat history bottom "follows" top+top_pad of input chat field + */ + void reshapeChatLayoutPanel(); + + void onInputEditorClicked(); + + void onEmojiRecentPanelToggleBtnClicked(); + void onEmojiPickerShowBtnClicked(); + void initEmojiRecentPanel(); + void onEmojiRecentPanelFocusReceived(); + void onEmojiRecentPanelFocusLost(); + void onRecentEmojiPicked(const LLSD& value); + + bool checkIfTornOff(); + bool mIsHostAttached; + bool mHasVisibleBeenInitialized; + + LLTimer* mRefreshTimer; ///< Defines the rate at which refresh() is called. + + S32 mInputEditorPad; + S32 mChatLayoutPanelHeight; + S32 mFloaterHeight; +}; + + +#endif /* LL_FLOATERIMSESSIONTAB_H */ diff --git a/indra/newview/llfloaterinspect.cpp b/indra/newview/llfloaterinspect.cpp index c38d54158e..4f993ca0e1 100644 --- a/indra/newview/llfloaterinspect.cpp +++ b/indra/newview/llfloaterinspect.cpp @@ -1,352 +1,352 @@ -/** - * @file llfloaterinspect.cpp - * @brief Floater for object inspection tool - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterinspect.h" - -#include "llfloaterreg.h" -#include "llfloatertools.h" -#include "llavataractions.h" -#include "llavatarnamecache.h" -#include "llgroupactions.h" -#include "llscrolllistctrl.h" -#include "llscrolllistitem.h" -#include "llselectmgr.h" -#include "lltoolcomp.h" -#include "lltoolmgr.h" -#include "lltrans.h" -#include "llviewercontrol.h" -#include "llviewerobject.h" -#include "lluictrlfactory.h" - -//LLFloaterInspect* LLFloaterInspect::sInstance = NULL; - -LLFloaterInspect::LLFloaterInspect(const LLSD& key) - : LLFloater(key), - mDirty(false), - mOwnerNameCacheConnection(), - mCreatorNameCacheConnection() -{ - mCommitCallbackRegistrar.add("Inspect.OwnerProfile", boost::bind(&LLFloaterInspect::onClickOwnerProfile, this)); - mCommitCallbackRegistrar.add("Inspect.CreatorProfile", boost::bind(&LLFloaterInspect::onClickCreatorProfile, this)); - mCommitCallbackRegistrar.add("Inspect.SelectObject", boost::bind(&LLFloaterInspect::onSelectObject, this)); -} - -bool LLFloaterInspect::postBuild() -{ - mObjectList = getChild("object_list"); -// childSetAction("button owner",onClickOwnerProfile, this); -// childSetAction("button creator",onClickCreatorProfile, this); -// childSetCommitCallback("object_list", onSelectObject, NULL); - - refresh(); - - return true; -} - -LLFloaterInspect::~LLFloaterInspect(void) -{ - if (mOwnerNameCacheConnection.connected()) - { - mOwnerNameCacheConnection.disconnect(); - } - if (mCreatorNameCacheConnection.connected()) - { - mCreatorNameCacheConnection.disconnect(); - } - if(!LLFloaterReg::instanceVisible("build")) - { - if(LLToolMgr::getInstance()->getBaseTool() == LLToolCompInspect::getInstance()) - { - LLToolMgr::getInstance()->clearTransientTool(); - } - // Switch back to basic toolset - LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); - } - else - { - LLFloaterReg::showInstance("build", LLSD(), true); - } -} - -void LLFloaterInspect::onOpen(const LLSD& key) -{ - bool forcesel = LLSelectMgr::getInstance()->setForceSelection(true); - LLToolMgr::getInstance()->setTransientTool(LLToolCompInspect::getInstance()); - LLSelectMgr::getInstance()->setForceSelection(forcesel); // restore previouis value - mObjectSelection = LLSelectMgr::getInstance()->getSelection(); - refresh(); -} -void LLFloaterInspect::onClickCreatorProfile() -{ - if(mObjectList->getAllSelected().size() == 0) - { - return; - } - LLScrollListItem* first_selected =mObjectList->getFirstSelected(); - - if (first_selected) - { - struct f : public LLSelectedNodeFunctor - { - LLUUID obj_id; - f(const LLUUID& id) : obj_id(id) {} - virtual bool apply(LLSelectNode* node) - { - return (obj_id == node->getObject()->getID()); - } - } func(first_selected->getUUID()); - LLSelectNode* node = mObjectSelection->getFirstNode(&func); - if(node) - { - LLAvatarActions::showProfile(node->mPermissions->getCreator()); - } - } -} - -void LLFloaterInspect::onClickOwnerProfile() -{ - if(mObjectList->getAllSelected().size() == 0) return; - LLScrollListItem* first_selected =mObjectList->getFirstSelected(); - - if (first_selected) - { - LLUUID selected_id = first_selected->getUUID(); - struct f : public LLSelectedNodeFunctor - { - LLUUID obj_id; - f(const LLUUID& id) : obj_id(id) {} - virtual bool apply(LLSelectNode* node) - { - return (obj_id == node->getObject()->getID()); - } - } func(selected_id); - LLSelectNode* node = mObjectSelection->getFirstNode(&func); - if(node) - { - if(node->mPermissions->isGroupOwned()) - { - const LLUUID& idGroup = node->mPermissions->getGroup(); - LLGroupActions::show(idGroup); - } - else - { - const LLUUID& owner_id = node->mPermissions->getOwner(); - LLAvatarActions::showProfile(owner_id); - } - - } - } -} - -void LLFloaterInspect::onSelectObject() -{ - if(LLFloaterInspect::getSelectedUUID() != LLUUID::null) - { - getChildView("button owner")->setEnabled(true); - getChildView("button creator")->setEnabled(true); - } -} - -LLUUID LLFloaterInspect::getSelectedUUID() -{ - if(mObjectList->getAllSelected().size() > 0) - { - LLScrollListItem* first_selected =mObjectList->getFirstSelected(); - if (first_selected) - { - return first_selected->getUUID(); - } - - } - return LLUUID::null; -} - -void LLFloaterInspect::refresh() -{ - LLUUID creator_id; - std::string creator_name; - S32 pos = mObjectList->getScrollPos(); - getChildView("button owner")->setEnabled(false); - getChildView("button creator")->setEnabled(false); - LLUUID selected_uuid; - S32 selected_index = mObjectList->getFirstSelectedIndex(); - if(selected_index > -1) - { - LLScrollListItem* first_selected = - mObjectList->getFirstSelected(); - if (first_selected) - { - selected_uuid = first_selected->getUUID(); - } - } - mObjectList->operateOnAll(LLScrollListCtrl::OP_DELETE); - //List all transient objects, then all linked objects - - for (LLObjectSelection::valid_iterator iter = mObjectSelection->valid_begin(); - iter != mObjectSelection->valid_end(); iter++) - { - LLSelectNode* obj = *iter; - LLSD row; - std::string owner_name, creator_name; - - if (obj->mCreationDate == 0) - { // Don't have valid information from the server, so skip this one - continue; - } - - time_t timestamp = (time_t) (obj->mCreationDate/1000000); - std::string timeStr = getString("timeStamp"); - LLSD substitution; - substitution["datetime"] = (S32) timestamp; - LLStringUtil::format (timeStr, substitution); - - const LLUUID& idOwner = obj->mPermissions->getOwner(); - const LLUUID& idCreator = obj->mPermissions->getCreator(); - LLAvatarName av_name; - - if(obj->mPermissions->isGroupOwned()) - { - std::string group_name; - const LLUUID& idGroup = obj->mPermissions->getGroup(); - if(gCacheName->getGroupName(idGroup, group_name)) - { - owner_name = "[" + group_name + "] (group)"; - } - else - { - owner_name = LLTrans::getString("RetrievingData"); - if (mOwnerNameCacheConnection.connected()) - { - mOwnerNameCacheConnection.disconnect(); - } - mOwnerNameCacheConnection = gCacheName->getGroup(idGroup, boost::bind(&LLFloaterInspect::onGetOwnerNameCallback, this)); - } - } - else - { - // Only work with the names if we actually get a result - // from the name cache. If not, defer setting the - // actual name and set a placeholder. - if (LLAvatarNameCache::get(idOwner, &av_name)) - { - owner_name = av_name.getCompleteName(); - } - else - { - owner_name = LLTrans::getString("RetrievingData"); - if (mOwnerNameCacheConnection.connected()) - { - mOwnerNameCacheConnection.disconnect(); - } - mOwnerNameCacheConnection = LLAvatarNameCache::get(idOwner, boost::bind(&LLFloaterInspect::onGetOwnerNameCallback, this)); - } - } - - if (LLAvatarNameCache::get(idCreator, &av_name)) - { - creator_name = av_name.getCompleteName(); - } - else - { - creator_name = LLTrans::getString("RetrievingData"); - if (mCreatorNameCacheConnection.connected()) - { - mCreatorNameCacheConnection.disconnect(); - } - mCreatorNameCacheConnection = LLAvatarNameCache::get(idCreator, boost::bind(&LLFloaterInspect::onGetCreatorNameCallback, this)); - } - - row["id"] = obj->getObject()->getID(); - row["columns"][0]["column"] = "object_name"; - row["columns"][0]["type"] = "text"; - // make sure we're either at the top of the link chain - // or top of the editable chain, for attachments - if(!(obj->getObject()->isRoot() || obj->getObject()->isRootEdit())) - { - row["columns"][0]["value"] = std::string(" ") + obj->mName; - } - else - { - row["columns"][0]["value"] = obj->mName; - } - row["columns"][1]["column"] = "owner_name"; - row["columns"][1]["type"] = "text"; - row["columns"][1]["value"] = owner_name; - row["columns"][2]["column"] = "creator_name"; - row["columns"][2]["type"] = "text"; - row["columns"][2]["value"] = creator_name; - row["columns"][3]["column"] = "creation_date"; - row["columns"][3]["type"] = "text"; - row["columns"][3]["value"] = timeStr; - mObjectList->addElement(row, ADD_TOP); - } - if(selected_index > -1 && mObjectList->getItemIndex(selected_uuid) == selected_index) - { - mObjectList->selectNthItem(selected_index); - } - else - { - mObjectList->selectNthItem(0); - } - onSelectObject(); - mObjectList->setScrollPos(pos); -} - -void LLFloaterInspect::onFocusReceived() -{ - LLToolMgr::getInstance()->setTransientTool(LLToolCompInspect::getInstance()); - LLFloater::onFocusReceived(); -} - -void LLFloaterInspect::dirty() -{ - setDirty(); -} - -void LLFloaterInspect::onGetOwnerNameCallback() -{ - mOwnerNameCacheConnection.disconnect(); - setDirty(); -} - -void LLFloaterInspect::onGetCreatorNameCallback() -{ - mCreatorNameCacheConnection.disconnect(); - setDirty(); -} - -void LLFloaterInspect::draw() -{ - if (mDirty) - { - refresh(); - mDirty = false; - } - - LLFloater::draw(); -} +/** + * @file llfloaterinspect.cpp + * @brief Floater for object inspection tool + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterinspect.h" + +#include "llfloaterreg.h" +#include "llfloatertools.h" +#include "llavataractions.h" +#include "llavatarnamecache.h" +#include "llgroupactions.h" +#include "llscrolllistctrl.h" +#include "llscrolllistitem.h" +#include "llselectmgr.h" +#include "lltoolcomp.h" +#include "lltoolmgr.h" +#include "lltrans.h" +#include "llviewercontrol.h" +#include "llviewerobject.h" +#include "lluictrlfactory.h" + +//LLFloaterInspect* LLFloaterInspect::sInstance = NULL; + +LLFloaterInspect::LLFloaterInspect(const LLSD& key) + : LLFloater(key), + mDirty(false), + mOwnerNameCacheConnection(), + mCreatorNameCacheConnection() +{ + mCommitCallbackRegistrar.add("Inspect.OwnerProfile", boost::bind(&LLFloaterInspect::onClickOwnerProfile, this)); + mCommitCallbackRegistrar.add("Inspect.CreatorProfile", boost::bind(&LLFloaterInspect::onClickCreatorProfile, this)); + mCommitCallbackRegistrar.add("Inspect.SelectObject", boost::bind(&LLFloaterInspect::onSelectObject, this)); +} + +bool LLFloaterInspect::postBuild() +{ + mObjectList = getChild("object_list"); +// childSetAction("button owner",onClickOwnerProfile, this); +// childSetAction("button creator",onClickCreatorProfile, this); +// childSetCommitCallback("object_list", onSelectObject, NULL); + + refresh(); + + return true; +} + +LLFloaterInspect::~LLFloaterInspect(void) +{ + if (mOwnerNameCacheConnection.connected()) + { + mOwnerNameCacheConnection.disconnect(); + } + if (mCreatorNameCacheConnection.connected()) + { + mCreatorNameCacheConnection.disconnect(); + } + if(!LLFloaterReg::instanceVisible("build")) + { + if(LLToolMgr::getInstance()->getBaseTool() == LLToolCompInspect::getInstance()) + { + LLToolMgr::getInstance()->clearTransientTool(); + } + // Switch back to basic toolset + LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); + } + else + { + LLFloaterReg::showInstance("build", LLSD(), true); + } +} + +void LLFloaterInspect::onOpen(const LLSD& key) +{ + bool forcesel = LLSelectMgr::getInstance()->setForceSelection(true); + LLToolMgr::getInstance()->setTransientTool(LLToolCompInspect::getInstance()); + LLSelectMgr::getInstance()->setForceSelection(forcesel); // restore previouis value + mObjectSelection = LLSelectMgr::getInstance()->getSelection(); + refresh(); +} +void LLFloaterInspect::onClickCreatorProfile() +{ + if(mObjectList->getAllSelected().size() == 0) + { + return; + } + LLScrollListItem* first_selected =mObjectList->getFirstSelected(); + + if (first_selected) + { + struct f : public LLSelectedNodeFunctor + { + LLUUID obj_id; + f(const LLUUID& id) : obj_id(id) {} + virtual bool apply(LLSelectNode* node) + { + return (obj_id == node->getObject()->getID()); + } + } func(first_selected->getUUID()); + LLSelectNode* node = mObjectSelection->getFirstNode(&func); + if(node) + { + LLAvatarActions::showProfile(node->mPermissions->getCreator()); + } + } +} + +void LLFloaterInspect::onClickOwnerProfile() +{ + if(mObjectList->getAllSelected().size() == 0) return; + LLScrollListItem* first_selected =mObjectList->getFirstSelected(); + + if (first_selected) + { + LLUUID selected_id = first_selected->getUUID(); + struct f : public LLSelectedNodeFunctor + { + LLUUID obj_id; + f(const LLUUID& id) : obj_id(id) {} + virtual bool apply(LLSelectNode* node) + { + return (obj_id == node->getObject()->getID()); + } + } func(selected_id); + LLSelectNode* node = mObjectSelection->getFirstNode(&func); + if(node) + { + if(node->mPermissions->isGroupOwned()) + { + const LLUUID& idGroup = node->mPermissions->getGroup(); + LLGroupActions::show(idGroup); + } + else + { + const LLUUID& owner_id = node->mPermissions->getOwner(); + LLAvatarActions::showProfile(owner_id); + } + + } + } +} + +void LLFloaterInspect::onSelectObject() +{ + if(LLFloaterInspect::getSelectedUUID() != LLUUID::null) + { + getChildView("button owner")->setEnabled(true); + getChildView("button creator")->setEnabled(true); + } +} + +LLUUID LLFloaterInspect::getSelectedUUID() +{ + if(mObjectList->getAllSelected().size() > 0) + { + LLScrollListItem* first_selected =mObjectList->getFirstSelected(); + if (first_selected) + { + return first_selected->getUUID(); + } + + } + return LLUUID::null; +} + +void LLFloaterInspect::refresh() +{ + LLUUID creator_id; + std::string creator_name; + S32 pos = mObjectList->getScrollPos(); + getChildView("button owner")->setEnabled(false); + getChildView("button creator")->setEnabled(false); + LLUUID selected_uuid; + S32 selected_index = mObjectList->getFirstSelectedIndex(); + if(selected_index > -1) + { + LLScrollListItem* first_selected = + mObjectList->getFirstSelected(); + if (first_selected) + { + selected_uuid = first_selected->getUUID(); + } + } + mObjectList->operateOnAll(LLScrollListCtrl::OP_DELETE); + //List all transient objects, then all linked objects + + for (LLObjectSelection::valid_iterator iter = mObjectSelection->valid_begin(); + iter != mObjectSelection->valid_end(); iter++) + { + LLSelectNode* obj = *iter; + LLSD row; + std::string owner_name, creator_name; + + if (obj->mCreationDate == 0) + { // Don't have valid information from the server, so skip this one + continue; + } + + time_t timestamp = (time_t) (obj->mCreationDate/1000000); + std::string timeStr = getString("timeStamp"); + LLSD substitution; + substitution["datetime"] = (S32) timestamp; + LLStringUtil::format (timeStr, substitution); + + const LLUUID& idOwner = obj->mPermissions->getOwner(); + const LLUUID& idCreator = obj->mPermissions->getCreator(); + LLAvatarName av_name; + + if(obj->mPermissions->isGroupOwned()) + { + std::string group_name; + const LLUUID& idGroup = obj->mPermissions->getGroup(); + if(gCacheName->getGroupName(idGroup, group_name)) + { + owner_name = "[" + group_name + "] (group)"; + } + else + { + owner_name = LLTrans::getString("RetrievingData"); + if (mOwnerNameCacheConnection.connected()) + { + mOwnerNameCacheConnection.disconnect(); + } + mOwnerNameCacheConnection = gCacheName->getGroup(idGroup, boost::bind(&LLFloaterInspect::onGetOwnerNameCallback, this)); + } + } + else + { + // Only work with the names if we actually get a result + // from the name cache. If not, defer setting the + // actual name and set a placeholder. + if (LLAvatarNameCache::get(idOwner, &av_name)) + { + owner_name = av_name.getCompleteName(); + } + else + { + owner_name = LLTrans::getString("RetrievingData"); + if (mOwnerNameCacheConnection.connected()) + { + mOwnerNameCacheConnection.disconnect(); + } + mOwnerNameCacheConnection = LLAvatarNameCache::get(idOwner, boost::bind(&LLFloaterInspect::onGetOwnerNameCallback, this)); + } + } + + if (LLAvatarNameCache::get(idCreator, &av_name)) + { + creator_name = av_name.getCompleteName(); + } + else + { + creator_name = LLTrans::getString("RetrievingData"); + if (mCreatorNameCacheConnection.connected()) + { + mCreatorNameCacheConnection.disconnect(); + } + mCreatorNameCacheConnection = LLAvatarNameCache::get(idCreator, boost::bind(&LLFloaterInspect::onGetCreatorNameCallback, this)); + } + + row["id"] = obj->getObject()->getID(); + row["columns"][0]["column"] = "object_name"; + row["columns"][0]["type"] = "text"; + // make sure we're either at the top of the link chain + // or top of the editable chain, for attachments + if(!(obj->getObject()->isRoot() || obj->getObject()->isRootEdit())) + { + row["columns"][0]["value"] = std::string(" ") + obj->mName; + } + else + { + row["columns"][0]["value"] = obj->mName; + } + row["columns"][1]["column"] = "owner_name"; + row["columns"][1]["type"] = "text"; + row["columns"][1]["value"] = owner_name; + row["columns"][2]["column"] = "creator_name"; + row["columns"][2]["type"] = "text"; + row["columns"][2]["value"] = creator_name; + row["columns"][3]["column"] = "creation_date"; + row["columns"][3]["type"] = "text"; + row["columns"][3]["value"] = timeStr; + mObjectList->addElement(row, ADD_TOP); + } + if(selected_index > -1 && mObjectList->getItemIndex(selected_uuid) == selected_index) + { + mObjectList->selectNthItem(selected_index); + } + else + { + mObjectList->selectNthItem(0); + } + onSelectObject(); + mObjectList->setScrollPos(pos); +} + +void LLFloaterInspect::onFocusReceived() +{ + LLToolMgr::getInstance()->setTransientTool(LLToolCompInspect::getInstance()); + LLFloater::onFocusReceived(); +} + +void LLFloaterInspect::dirty() +{ + setDirty(); +} + +void LLFloaterInspect::onGetOwnerNameCallback() +{ + mOwnerNameCacheConnection.disconnect(); + setDirty(); +} + +void LLFloaterInspect::onGetCreatorNameCallback() +{ + mCreatorNameCacheConnection.disconnect(); + setDirty(); +} + +void LLFloaterInspect::draw() +{ + if (mDirty) + { + refresh(); + mDirty = false; + } + + LLFloater::draw(); +} diff --git a/indra/newview/llfloaterinspect.h b/indra/newview/llfloaterinspect.h index 25e673f37e..961c6c8dc3 100644 --- a/indra/newview/llfloaterinspect.h +++ b/indra/newview/llfloaterinspect.h @@ -1,76 +1,76 @@ -/** -* @file llfloaterinspect.h -* @author Cube -* @date 2006-12-16 -* @brief Declaration of class for displaying object attributes -* -* $LicenseInfo:firstyear=2006&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2010, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#ifndef LL_LLFLOATERINSPECT_H -#define LL_LLFLOATERINSPECT_H - -#include "llavatarname.h" -#include "llfloater.h" - -//class LLTool; -class LLObjectSelection; -class LLScrollListCtrl; -class LLUICtrl; - -class LLFloaterInspect : public LLFloater -{ - friend class LLFloaterReg; -public: - -// static void show(void* ignored = NULL); - void onOpen(const LLSD& key); - virtual bool postBuild(); - void dirty(); - LLUUID getSelectedUUID(); - virtual void draw(); - virtual void refresh(); -// static bool isVisible(); - virtual void onFocusReceived(); - void onClickCreatorProfile(); - void onClickOwnerProfile(); - void onSelectObject(); - - LLScrollListCtrl* mObjectList; -protected: - // protected members - void setDirty() { mDirty = true; } - bool mDirty; - -private: - void onGetOwnerNameCallback(); - void onGetCreatorNameCallback(); - - LLFloaterInspect(const LLSD& key); - virtual ~LLFloaterInspect(void); - - LLSafeHandle mObjectSelection; - boost::signals2::connection mOwnerNameCacheConnection; - boost::signals2::connection mCreatorNameCacheConnection; -}; - -#endif //LL_LLFLOATERINSPECT_H +/** +* @file llfloaterinspect.h +* @author Cube +* @date 2006-12-16 +* @brief Declaration of class for displaying object attributes +* +* $LicenseInfo:firstyear=2006&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2010, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#ifndef LL_LLFLOATERINSPECT_H +#define LL_LLFLOATERINSPECT_H + +#include "llavatarname.h" +#include "llfloater.h" + +//class LLTool; +class LLObjectSelection; +class LLScrollListCtrl; +class LLUICtrl; + +class LLFloaterInspect : public LLFloater +{ + friend class LLFloaterReg; +public: + +// static void show(void* ignored = NULL); + void onOpen(const LLSD& key); + virtual bool postBuild(); + void dirty(); + LLUUID getSelectedUUID(); + virtual void draw(); + virtual void refresh(); +// static bool isVisible(); + virtual void onFocusReceived(); + void onClickCreatorProfile(); + void onClickOwnerProfile(); + void onSelectObject(); + + LLScrollListCtrl* mObjectList; +protected: + // protected members + void setDirty() { mDirty = true; } + bool mDirty; + +private: + void onGetOwnerNameCallback(); + void onGetCreatorNameCallback(); + + LLFloaterInspect(const LLSD& key); + virtual ~LLFloaterInspect(void); + + LLSafeHandle mObjectSelection; + boost::signals2::connection mOwnerNameCacheConnection; + boost::signals2::connection mCreatorNameCacheConnection; +}; + +#endif //LL_LLFLOATERINSPECT_H diff --git a/indra/newview/llfloaterjoystick.cpp b/indra/newview/llfloaterjoystick.cpp index 324c32c1a8..7db3621a4b 100644 --- a/indra/newview/llfloaterjoystick.cpp +++ b/indra/newview/llfloaterjoystick.cpp @@ -1,488 +1,488 @@ -/** - * @file llfloaterjoystick.cpp - * @brief Joystick preferences panel - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -// file include -#include "llfloaterjoystick.h" - -// linden library includes -#include "llerror.h" -#include "llrect.h" -#include "llstring.h" -#include "lltrace.h" - -// project includes -#include "lluictrlfactory.h" -#include "llviewercontrol.h" -#include "llappviewer.h" -#include "llviewerjoystick.h" -#include "llviewerwindow.h" -#include "llwindow.h" -#include "llcheckboxctrl.h" -#include "llcombobox.h" - -#if LL_WINDOWS && !LL_MESA_HEADLESS -// Require DirectInput version 8 -#define DIRECTINPUT_VERSION 0x0800 - -#include -#endif - -static LLTrace::SampleStatHandle<> sJoystickAxis0("Joystick axis 0"), - sJoystickAxis1("Joystick axis 1"), - sJoystickAxis2("Joystick axis 2"), - sJoystickAxis3("Joystick axis 3"), - sJoystickAxis4("Joystick axis 4"), - sJoystickAxis5("Joystick axis 5"); -static LLTrace::SampleStatHandle<>* sJoystickAxes[6] = -{ - &sJoystickAxis0, - &sJoystickAxis1, - &sJoystickAxis2, - &sJoystickAxis3, - &sJoystickAxis4, - &sJoystickAxis5 -}; - - -#if LL_WINDOWS && !LL_MESA_HEADLESS - -BOOL CALLBACK di8_list_devices_callback(LPCDIDEVICEINSTANCE device_instance_ptr, LPVOID pvRef) -{ - // Note: If a single device can function as more than one DirectInput - // device type, it is enumerated as each device type that it supports. - // Capable of detecting devices like Oculus Rift - if (device_instance_ptr && pvRef) - { - std::string product_name = utf16str_to_utf8str(llutf16string(device_instance_ptr->tszProductName)); - S32 size = sizeof(GUID); - LLSD::Binary data; //just an std::vector - data.resize(size); - memcpy(&data[0], &device_instance_ptr->guidInstance /*POD _GUID*/, size); - - LLFloaterJoystick * floater = (LLFloaterJoystick*)pvRef; - LLSD value = data; - floater->addDevice(product_name, value); - } - return DIENUM_CONTINUE; -} -#endif - -LLFloaterJoystick::LLFloaterJoystick(const LLSD& data) - : LLFloater(data), - mHasDeviceList(false) -{ - if (!LLViewerJoystick::getInstance()->isJoystickInitialized()) - { - LLViewerJoystick::getInstance()->init(false); - } - - initFromSettings(); -} - -void LLFloaterJoystick::draw() -{ - LLViewerJoystick* joystick(LLViewerJoystick::getInstance()); - bool joystick_inited = joystick->isJoystickInitialized(); - if (joystick_inited != mHasDeviceList) - { - refreshListOfDevices(); - } - - for (U32 i = 0; i < 6; i++) - { - F32 value = joystick->getJoystickAxis(i); - sample(*sJoystickAxes[i], value * gFrameIntervalSeconds.value()); - if (mAxisStatsBar[i]) - { - F32 minbar, maxbar; - mAxisStatsBar[i]->getRange(minbar, maxbar); - if (llabs(value) > maxbar) - { - F32 range = llabs(value); - mAxisStatsBar[i]->setRange(-range, range); - } - } - } - - LLFloater::draw(); -} - -bool LLFloaterJoystick::postBuild() -{ - center(); - F32 range = gSavedSettings.getBOOL("Cursor3D") ? 128.f : 2.f; - - for (U32 i = 0; i < 6; i++) - { - std::string stat_name(llformat("Joystick axis %d", i)); - std::string axisname = llformat("axis%d", i); - mAxisStatsBar[i] = getChild(axisname); - if (mAxisStatsBar[i]) - { - mAxisStatsBar[i]->setStat(stat_name); - mAxisStatsBar[i]->setRange(-range, range); - } - } - - mJoysticksCombo = getChild("joystick_combo"); - childSetCommitCallback("joystick_combo",onCommitJoystickEnabled,this); - mCheckFlycamEnabled = getChild("JoystickFlycamEnabled"); - childSetCommitCallback("JoystickFlycamEnabled",onCommitJoystickEnabled,this); - - childSetAction("SpaceNavigatorDefaults", onClickRestoreSNDefaults, this); - childSetAction("cancel_btn", onClickCancel, this); - childSetAction("ok_btn", onClickOK, this); - - refresh(); - refreshListOfDevices(); - return true; -} - -LLFloaterJoystick::~LLFloaterJoystick() -{ - // Children all cleaned up by default view destructor. -} - - -void LLFloaterJoystick::apply() -{ -} - -void LLFloaterJoystick::initFromSettings() -{ - mJoystickEnabled = gSavedSettings.getBOOL("JoystickEnabled"); - mJoystickId = gSavedSettings.getLLSD("JoystickDeviceUUID"); - - mJoystickAxis[0] = gSavedSettings.getS32("JoystickAxis0"); - mJoystickAxis[1] = gSavedSettings.getS32("JoystickAxis1"); - mJoystickAxis[2] = gSavedSettings.getS32("JoystickAxis2"); - mJoystickAxis[3] = gSavedSettings.getS32("JoystickAxis3"); - mJoystickAxis[4] = gSavedSettings.getS32("JoystickAxis4"); - mJoystickAxis[5] = gSavedSettings.getS32("JoystickAxis5"); - mJoystickAxis[6] = gSavedSettings.getS32("JoystickAxis6"); - - m3DCursor = gSavedSettings.getBOOL("Cursor3D"); - mAutoLeveling = gSavedSettings.getBOOL("AutoLeveling"); - mZoomDirect = gSavedSettings.getBOOL("ZoomDirect"); - - mAvatarEnabled = gSavedSettings.getBOOL("JoystickAvatarEnabled"); - mBuildEnabled = gSavedSettings.getBOOL("JoystickBuildEnabled"); - mFlycamEnabled = gSavedSettings.getBOOL("JoystickFlycamEnabled"); - - mAvatarAxisScale[0] = gSavedSettings.getF32("AvatarAxisScale0"); - mAvatarAxisScale[1] = gSavedSettings.getF32("AvatarAxisScale1"); - mAvatarAxisScale[2] = gSavedSettings.getF32("AvatarAxisScale2"); - mAvatarAxisScale[3] = gSavedSettings.getF32("AvatarAxisScale3"); - mAvatarAxisScale[4] = gSavedSettings.getF32("AvatarAxisScale4"); - mAvatarAxisScale[5] = gSavedSettings.getF32("AvatarAxisScale5"); - - mBuildAxisScale[0] = gSavedSettings.getF32("BuildAxisScale0"); - mBuildAxisScale[1] = gSavedSettings.getF32("BuildAxisScale1"); - mBuildAxisScale[2] = gSavedSettings.getF32("BuildAxisScale2"); - mBuildAxisScale[3] = gSavedSettings.getF32("BuildAxisScale3"); - mBuildAxisScale[4] = gSavedSettings.getF32("BuildAxisScale4"); - mBuildAxisScale[5] = gSavedSettings.getF32("BuildAxisScale5"); - - mFlycamAxisScale[0] = gSavedSettings.getF32("FlycamAxisScale0"); - mFlycamAxisScale[1] = gSavedSettings.getF32("FlycamAxisScale1"); - mFlycamAxisScale[2] = gSavedSettings.getF32("FlycamAxisScale2"); - mFlycamAxisScale[3] = gSavedSettings.getF32("FlycamAxisScale3"); - mFlycamAxisScale[4] = gSavedSettings.getF32("FlycamAxisScale4"); - mFlycamAxisScale[5] = gSavedSettings.getF32("FlycamAxisScale5"); - mFlycamAxisScale[6] = gSavedSettings.getF32("FlycamAxisScale6"); - - mAvatarAxisDeadZone[0] = gSavedSettings.getF32("AvatarAxisDeadZone0"); - mAvatarAxisDeadZone[1] = gSavedSettings.getF32("AvatarAxisDeadZone1"); - mAvatarAxisDeadZone[2] = gSavedSettings.getF32("AvatarAxisDeadZone2"); - mAvatarAxisDeadZone[3] = gSavedSettings.getF32("AvatarAxisDeadZone3"); - mAvatarAxisDeadZone[4] = gSavedSettings.getF32("AvatarAxisDeadZone4"); - mAvatarAxisDeadZone[5] = gSavedSettings.getF32("AvatarAxisDeadZone5"); - - mBuildAxisDeadZone[0] = gSavedSettings.getF32("BuildAxisDeadZone0"); - mBuildAxisDeadZone[1] = gSavedSettings.getF32("BuildAxisDeadZone1"); - mBuildAxisDeadZone[2] = gSavedSettings.getF32("BuildAxisDeadZone2"); - mBuildAxisDeadZone[3] = gSavedSettings.getF32("BuildAxisDeadZone3"); - mBuildAxisDeadZone[4] = gSavedSettings.getF32("BuildAxisDeadZone4"); - mBuildAxisDeadZone[5] = gSavedSettings.getF32("BuildAxisDeadZone5"); - - mFlycamAxisDeadZone[0] = gSavedSettings.getF32("FlycamAxisDeadZone0"); - mFlycamAxisDeadZone[1] = gSavedSettings.getF32("FlycamAxisDeadZone1"); - mFlycamAxisDeadZone[2] = gSavedSettings.getF32("FlycamAxisDeadZone2"); - mFlycamAxisDeadZone[3] = gSavedSettings.getF32("FlycamAxisDeadZone3"); - mFlycamAxisDeadZone[4] = gSavedSettings.getF32("FlycamAxisDeadZone4"); - mFlycamAxisDeadZone[5] = gSavedSettings.getF32("FlycamAxisDeadZone5"); - mFlycamAxisDeadZone[6] = gSavedSettings.getF32("FlycamAxisDeadZone6"); - - mAvatarFeathering = gSavedSettings.getF32("AvatarFeathering"); - mBuildFeathering = gSavedSettings.getF32("BuildFeathering"); - mFlycamFeathering = gSavedSettings.getF32("FlycamFeathering"); -} - -void LLFloaterJoystick::refresh() -{ - LLFloater::refresh(); - - initFromSettings(); -} - -bool LLFloaterJoystick::addDeviceCallback(std::string &name, LLSD& value, void* userdata) -{ - LLFloaterJoystick * floater = (LLFloaterJoystick*)userdata; - floater->mJoysticksCombo->add(name, value, ADD_BOTTOM, 1); - return false; // keep searching -} - -void LLFloaterJoystick::addDevice(std::string &name, LLSD& value) -{ - mJoysticksCombo->add(name, value, ADD_BOTTOM, 1); -} - -void LLFloaterJoystick::refreshListOfDevices() -{ - mJoysticksCombo->removeall(); - std::string no_device = getString("JoystickDisabled"); - LLSD value = LLSD::Integer(0); - addDevice(no_device, value); - - mHasDeviceList = false; - - void* win_calback = nullptr; - // di8_devices_callback callback is immediate and happens in scope of getInputDevices() -#if LL_WINDOWS && !LL_MESA_HEADLESS - // space navigator is marked as DI8DEVCLASS_GAMECTRL in ndof lib - U32 device_type = DI8DEVCLASS_GAMECTRL; - win_calback = di8_list_devices_callback; -#elif LL_DARWIN - U32 device_type = 0; -#else - // On MAC it is possible to specify product - // and manufacturer in NDOF_Device for - // ndof_init_first to pick specific device - U32 device_type = 0; -#endif - if (gViewerWindow->getWindow()->getInputDevices(device_type, addDeviceCallback, win_calback, this)) - { - mHasDeviceList = true; - } - - bool is_device_id_set = LLViewerJoystick::getInstance()->isDeviceUUIDSet(); - - if (LLViewerJoystick::getInstance()->isJoystickInitialized() && - (!mHasDeviceList || !is_device_id_set)) - { -#if LL_WINDOWS && !LL_MESA_HEADLESS - LL_WARNS() << "NDOF connected to device without using SL provided handle" << LL_ENDL; -#endif - std::string desc = LLViewerJoystick::getInstance()->getDescription(); - if (!desc.empty()) - { - LLSD value = LLSD::Integer(1); // value for selection - addDevice(desc, value); - mHasDeviceList = true; - } - } - - if (gSavedSettings.getBOOL("JoystickEnabled") && mHasDeviceList) - { - if (is_device_id_set) - { - LLSD guid = LLViewerJoystick::getInstance()->getDeviceUUID(); - mJoysticksCombo->selectByValue(guid); - } - else - { - mJoysticksCombo->selectByValue(LLSD::Integer(1)); - } - } - else - { - mJoysticksCombo->selectByValue(LLSD::Integer(0)); - } -} - -void LLFloaterJoystick::cancel() -{ - gSavedSettings.setBOOL("JoystickEnabled", mJoystickEnabled); - gSavedSettings.setLLSD("JoystickDeviceUUID", mJoystickId); - - gSavedSettings.setS32("JoystickAxis0", mJoystickAxis[0]); - gSavedSettings.setS32("JoystickAxis1", mJoystickAxis[1]); - gSavedSettings.setS32("JoystickAxis2", mJoystickAxis[2]); - gSavedSettings.setS32("JoystickAxis3", mJoystickAxis[3]); - gSavedSettings.setS32("JoystickAxis4", mJoystickAxis[4]); - gSavedSettings.setS32("JoystickAxis5", mJoystickAxis[5]); - gSavedSettings.setS32("JoystickAxis6", mJoystickAxis[6]); - - gSavedSettings.setBOOL("Cursor3D", m3DCursor); - gSavedSettings.setBOOL("AutoLeveling", mAutoLeveling); - gSavedSettings.setBOOL("ZoomDirect", mZoomDirect ); - - gSavedSettings.setBOOL("JoystickAvatarEnabled", mAvatarEnabled); - gSavedSettings.setBOOL("JoystickBuildEnabled", mBuildEnabled); - gSavedSettings.setBOOL("JoystickFlycamEnabled", mFlycamEnabled); - - gSavedSettings.setF32("AvatarAxisScale0", mAvatarAxisScale[0]); - gSavedSettings.setF32("AvatarAxisScale1", mAvatarAxisScale[1]); - gSavedSettings.setF32("AvatarAxisScale2", mAvatarAxisScale[2]); - gSavedSettings.setF32("AvatarAxisScale3", mAvatarAxisScale[3]); - gSavedSettings.setF32("AvatarAxisScale4", mAvatarAxisScale[4]); - gSavedSettings.setF32("AvatarAxisScale5", mAvatarAxisScale[5]); - - gSavedSettings.setF32("BuildAxisScale0", mBuildAxisScale[0]); - gSavedSettings.setF32("BuildAxisScale1", mBuildAxisScale[1]); - gSavedSettings.setF32("BuildAxisScale2", mBuildAxisScale[2]); - gSavedSettings.setF32("BuildAxisScale3", mBuildAxisScale[3]); - gSavedSettings.setF32("BuildAxisScale4", mBuildAxisScale[4]); - gSavedSettings.setF32("BuildAxisScale5", mBuildAxisScale[5]); - - gSavedSettings.setF32("FlycamAxisScale0", mFlycamAxisScale[0]); - gSavedSettings.setF32("FlycamAxisScale1", mFlycamAxisScale[1]); - gSavedSettings.setF32("FlycamAxisScale2", mFlycamAxisScale[2]); - gSavedSettings.setF32("FlycamAxisScale3", mFlycamAxisScale[3]); - gSavedSettings.setF32("FlycamAxisScale4", mFlycamAxisScale[4]); - gSavedSettings.setF32("FlycamAxisScale5", mFlycamAxisScale[5]); - gSavedSettings.setF32("FlycamAxisScale6", mFlycamAxisScale[6]); - - gSavedSettings.setF32("AvatarAxisDeadZone0", mAvatarAxisDeadZone[0]); - gSavedSettings.setF32("AvatarAxisDeadZone1", mAvatarAxisDeadZone[1]); - gSavedSettings.setF32("AvatarAxisDeadZone2", mAvatarAxisDeadZone[2]); - gSavedSettings.setF32("AvatarAxisDeadZone3", mAvatarAxisDeadZone[3]); - gSavedSettings.setF32("AvatarAxisDeadZone4", mAvatarAxisDeadZone[4]); - gSavedSettings.setF32("AvatarAxisDeadZone5", mAvatarAxisDeadZone[5]); - - gSavedSettings.setF32("BuildAxisDeadZone0", mBuildAxisDeadZone[0]); - gSavedSettings.setF32("BuildAxisDeadZone1", mBuildAxisDeadZone[1]); - gSavedSettings.setF32("BuildAxisDeadZone2", mBuildAxisDeadZone[2]); - gSavedSettings.setF32("BuildAxisDeadZone3", mBuildAxisDeadZone[3]); - gSavedSettings.setF32("BuildAxisDeadZone4", mBuildAxisDeadZone[4]); - gSavedSettings.setF32("BuildAxisDeadZone5", mBuildAxisDeadZone[5]); - - gSavedSettings.setF32("FlycamAxisDeadZone0", mFlycamAxisDeadZone[0]); - gSavedSettings.setF32("FlycamAxisDeadZone1", mFlycamAxisDeadZone[1]); - gSavedSettings.setF32("FlycamAxisDeadZone2", mFlycamAxisDeadZone[2]); - gSavedSettings.setF32("FlycamAxisDeadZone3", mFlycamAxisDeadZone[3]); - gSavedSettings.setF32("FlycamAxisDeadZone4", mFlycamAxisDeadZone[4]); - gSavedSettings.setF32("FlycamAxisDeadZone5", mFlycamAxisDeadZone[5]); - gSavedSettings.setF32("FlycamAxisDeadZone6", mFlycamAxisDeadZone[6]); - - gSavedSettings.setF32("AvatarFeathering", mAvatarFeathering); - gSavedSettings.setF32("BuildFeathering", mBuildFeathering); - gSavedSettings.setF32("FlycamFeathering", mFlycamFeathering); -} - -void LLFloaterJoystick::onCommitJoystickEnabled(LLUICtrl*, void *joy_panel) -{ - LLFloaterJoystick* self = (LLFloaterJoystick*)joy_panel; - - LLSD value = self->mJoysticksCombo->getValue(); - bool joystick_enabled = true; - // value is 0 for no device, - // 1 for a device on Mac (single device, no list support yet) - // binary packed guid for a device on windows (can have multiple devices) - if (value.isInteger()) - { - // ndof already has a device selected, we are just setting it enabled or disabled - joystick_enabled = value.asInteger(); - } - else - { - LLViewerJoystick::getInstance()->initDevice(value); - // else joystick is enabled, because combobox holds id of the device - joystick_enabled = true; - } - gSavedSettings.setBOOL("JoystickEnabled", joystick_enabled); - bool flycam_enabled = self->mCheckFlycamEnabled->get(); - - if (!joystick_enabled || !flycam_enabled) - { - // Turn off flycam - LLViewerJoystick* joystick(LLViewerJoystick::getInstance()); - if (joystick->getOverrideCamera()) - { - joystick->toggleFlycam(); - } - } - - LLViewerJoystick::getInstance()->saveDeviceIdToSettings(); - - std::string device_string = LLViewerJoystick::getInstance()->getDeviceUUIDString(); - LL_DEBUGS("Joystick") << "Selected " << device_string << " as joystick." << LL_ENDL; - - self->refreshListOfDevices(); -} - -void LLFloaterJoystick::onClickRestoreSNDefaults(void *joy_panel) -{ - setSNDefaults(); -} - -void LLFloaterJoystick::onClickCancel(void *joy_panel) -{ - if (joy_panel) - { - LLFloaterJoystick* self = (LLFloaterJoystick*)joy_panel; - - if (self) - { - self->cancel(); - self->closeFloater(); - } - } -} - -void LLFloaterJoystick::onClickOK(void *joy_panel) -{ - if (joy_panel) - { - LLFloaterJoystick* self = (LLFloaterJoystick*)joy_panel; - - if (self) - { - self->closeFloater(); - } - } -} - -void LLFloaterJoystick::onClickCloseBtn(bool app_quitting) -{ - cancel(); - closeFloater(app_quitting); -} - -void LLFloaterJoystick::setSNDefaults() -{ - LLViewerJoystick::getInstance()->setSNDefaults(); -} - -void LLFloaterJoystick::onClose(bool app_quitting) -{ - if (app_quitting) - { - cancel(); - } -} +/** + * @file llfloaterjoystick.cpp + * @brief Joystick preferences panel + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +// file include +#include "llfloaterjoystick.h" + +// linden library includes +#include "llerror.h" +#include "llrect.h" +#include "llstring.h" +#include "lltrace.h" + +// project includes +#include "lluictrlfactory.h" +#include "llviewercontrol.h" +#include "llappviewer.h" +#include "llviewerjoystick.h" +#include "llviewerwindow.h" +#include "llwindow.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" + +#if LL_WINDOWS && !LL_MESA_HEADLESS +// Require DirectInput version 8 +#define DIRECTINPUT_VERSION 0x0800 + +#include +#endif + +static LLTrace::SampleStatHandle<> sJoystickAxis0("Joystick axis 0"), + sJoystickAxis1("Joystick axis 1"), + sJoystickAxis2("Joystick axis 2"), + sJoystickAxis3("Joystick axis 3"), + sJoystickAxis4("Joystick axis 4"), + sJoystickAxis5("Joystick axis 5"); +static LLTrace::SampleStatHandle<>* sJoystickAxes[6] = +{ + &sJoystickAxis0, + &sJoystickAxis1, + &sJoystickAxis2, + &sJoystickAxis3, + &sJoystickAxis4, + &sJoystickAxis5 +}; + + +#if LL_WINDOWS && !LL_MESA_HEADLESS + +BOOL CALLBACK di8_list_devices_callback(LPCDIDEVICEINSTANCE device_instance_ptr, LPVOID pvRef) +{ + // Note: If a single device can function as more than one DirectInput + // device type, it is enumerated as each device type that it supports. + // Capable of detecting devices like Oculus Rift + if (device_instance_ptr && pvRef) + { + std::string product_name = utf16str_to_utf8str(llutf16string(device_instance_ptr->tszProductName)); + S32 size = sizeof(GUID); + LLSD::Binary data; //just an std::vector + data.resize(size); + memcpy(&data[0], &device_instance_ptr->guidInstance /*POD _GUID*/, size); + + LLFloaterJoystick * floater = (LLFloaterJoystick*)pvRef; + LLSD value = data; + floater->addDevice(product_name, value); + } + return DIENUM_CONTINUE; +} +#endif + +LLFloaterJoystick::LLFloaterJoystick(const LLSD& data) + : LLFloater(data), + mHasDeviceList(false) +{ + if (!LLViewerJoystick::getInstance()->isJoystickInitialized()) + { + LLViewerJoystick::getInstance()->init(false); + } + + initFromSettings(); +} + +void LLFloaterJoystick::draw() +{ + LLViewerJoystick* joystick(LLViewerJoystick::getInstance()); + bool joystick_inited = joystick->isJoystickInitialized(); + if (joystick_inited != mHasDeviceList) + { + refreshListOfDevices(); + } + + for (U32 i = 0; i < 6; i++) + { + F32 value = joystick->getJoystickAxis(i); + sample(*sJoystickAxes[i], value * gFrameIntervalSeconds.value()); + if (mAxisStatsBar[i]) + { + F32 minbar, maxbar; + mAxisStatsBar[i]->getRange(minbar, maxbar); + if (llabs(value) > maxbar) + { + F32 range = llabs(value); + mAxisStatsBar[i]->setRange(-range, range); + } + } + } + + LLFloater::draw(); +} + +bool LLFloaterJoystick::postBuild() +{ + center(); + F32 range = gSavedSettings.getBOOL("Cursor3D") ? 128.f : 2.f; + + for (U32 i = 0; i < 6; i++) + { + std::string stat_name(llformat("Joystick axis %d", i)); + std::string axisname = llformat("axis%d", i); + mAxisStatsBar[i] = getChild(axisname); + if (mAxisStatsBar[i]) + { + mAxisStatsBar[i]->setStat(stat_name); + mAxisStatsBar[i]->setRange(-range, range); + } + } + + mJoysticksCombo = getChild("joystick_combo"); + childSetCommitCallback("joystick_combo",onCommitJoystickEnabled,this); + mCheckFlycamEnabled = getChild("JoystickFlycamEnabled"); + childSetCommitCallback("JoystickFlycamEnabled",onCommitJoystickEnabled,this); + + childSetAction("SpaceNavigatorDefaults", onClickRestoreSNDefaults, this); + childSetAction("cancel_btn", onClickCancel, this); + childSetAction("ok_btn", onClickOK, this); + + refresh(); + refreshListOfDevices(); + return true; +} + +LLFloaterJoystick::~LLFloaterJoystick() +{ + // Children all cleaned up by default view destructor. +} + + +void LLFloaterJoystick::apply() +{ +} + +void LLFloaterJoystick::initFromSettings() +{ + mJoystickEnabled = gSavedSettings.getBOOL("JoystickEnabled"); + mJoystickId = gSavedSettings.getLLSD("JoystickDeviceUUID"); + + mJoystickAxis[0] = gSavedSettings.getS32("JoystickAxis0"); + mJoystickAxis[1] = gSavedSettings.getS32("JoystickAxis1"); + mJoystickAxis[2] = gSavedSettings.getS32("JoystickAxis2"); + mJoystickAxis[3] = gSavedSettings.getS32("JoystickAxis3"); + mJoystickAxis[4] = gSavedSettings.getS32("JoystickAxis4"); + mJoystickAxis[5] = gSavedSettings.getS32("JoystickAxis5"); + mJoystickAxis[6] = gSavedSettings.getS32("JoystickAxis6"); + + m3DCursor = gSavedSettings.getBOOL("Cursor3D"); + mAutoLeveling = gSavedSettings.getBOOL("AutoLeveling"); + mZoomDirect = gSavedSettings.getBOOL("ZoomDirect"); + + mAvatarEnabled = gSavedSettings.getBOOL("JoystickAvatarEnabled"); + mBuildEnabled = gSavedSettings.getBOOL("JoystickBuildEnabled"); + mFlycamEnabled = gSavedSettings.getBOOL("JoystickFlycamEnabled"); + + mAvatarAxisScale[0] = gSavedSettings.getF32("AvatarAxisScale0"); + mAvatarAxisScale[1] = gSavedSettings.getF32("AvatarAxisScale1"); + mAvatarAxisScale[2] = gSavedSettings.getF32("AvatarAxisScale2"); + mAvatarAxisScale[3] = gSavedSettings.getF32("AvatarAxisScale3"); + mAvatarAxisScale[4] = gSavedSettings.getF32("AvatarAxisScale4"); + mAvatarAxisScale[5] = gSavedSettings.getF32("AvatarAxisScale5"); + + mBuildAxisScale[0] = gSavedSettings.getF32("BuildAxisScale0"); + mBuildAxisScale[1] = gSavedSettings.getF32("BuildAxisScale1"); + mBuildAxisScale[2] = gSavedSettings.getF32("BuildAxisScale2"); + mBuildAxisScale[3] = gSavedSettings.getF32("BuildAxisScale3"); + mBuildAxisScale[4] = gSavedSettings.getF32("BuildAxisScale4"); + mBuildAxisScale[5] = gSavedSettings.getF32("BuildAxisScale5"); + + mFlycamAxisScale[0] = gSavedSettings.getF32("FlycamAxisScale0"); + mFlycamAxisScale[1] = gSavedSettings.getF32("FlycamAxisScale1"); + mFlycamAxisScale[2] = gSavedSettings.getF32("FlycamAxisScale2"); + mFlycamAxisScale[3] = gSavedSettings.getF32("FlycamAxisScale3"); + mFlycamAxisScale[4] = gSavedSettings.getF32("FlycamAxisScale4"); + mFlycamAxisScale[5] = gSavedSettings.getF32("FlycamAxisScale5"); + mFlycamAxisScale[6] = gSavedSettings.getF32("FlycamAxisScale6"); + + mAvatarAxisDeadZone[0] = gSavedSettings.getF32("AvatarAxisDeadZone0"); + mAvatarAxisDeadZone[1] = gSavedSettings.getF32("AvatarAxisDeadZone1"); + mAvatarAxisDeadZone[2] = gSavedSettings.getF32("AvatarAxisDeadZone2"); + mAvatarAxisDeadZone[3] = gSavedSettings.getF32("AvatarAxisDeadZone3"); + mAvatarAxisDeadZone[4] = gSavedSettings.getF32("AvatarAxisDeadZone4"); + mAvatarAxisDeadZone[5] = gSavedSettings.getF32("AvatarAxisDeadZone5"); + + mBuildAxisDeadZone[0] = gSavedSettings.getF32("BuildAxisDeadZone0"); + mBuildAxisDeadZone[1] = gSavedSettings.getF32("BuildAxisDeadZone1"); + mBuildAxisDeadZone[2] = gSavedSettings.getF32("BuildAxisDeadZone2"); + mBuildAxisDeadZone[3] = gSavedSettings.getF32("BuildAxisDeadZone3"); + mBuildAxisDeadZone[4] = gSavedSettings.getF32("BuildAxisDeadZone4"); + mBuildAxisDeadZone[5] = gSavedSettings.getF32("BuildAxisDeadZone5"); + + mFlycamAxisDeadZone[0] = gSavedSettings.getF32("FlycamAxisDeadZone0"); + mFlycamAxisDeadZone[1] = gSavedSettings.getF32("FlycamAxisDeadZone1"); + mFlycamAxisDeadZone[2] = gSavedSettings.getF32("FlycamAxisDeadZone2"); + mFlycamAxisDeadZone[3] = gSavedSettings.getF32("FlycamAxisDeadZone3"); + mFlycamAxisDeadZone[4] = gSavedSettings.getF32("FlycamAxisDeadZone4"); + mFlycamAxisDeadZone[5] = gSavedSettings.getF32("FlycamAxisDeadZone5"); + mFlycamAxisDeadZone[6] = gSavedSettings.getF32("FlycamAxisDeadZone6"); + + mAvatarFeathering = gSavedSettings.getF32("AvatarFeathering"); + mBuildFeathering = gSavedSettings.getF32("BuildFeathering"); + mFlycamFeathering = gSavedSettings.getF32("FlycamFeathering"); +} + +void LLFloaterJoystick::refresh() +{ + LLFloater::refresh(); + + initFromSettings(); +} + +bool LLFloaterJoystick::addDeviceCallback(std::string &name, LLSD& value, void* userdata) +{ + LLFloaterJoystick * floater = (LLFloaterJoystick*)userdata; + floater->mJoysticksCombo->add(name, value, ADD_BOTTOM, 1); + return false; // keep searching +} + +void LLFloaterJoystick::addDevice(std::string &name, LLSD& value) +{ + mJoysticksCombo->add(name, value, ADD_BOTTOM, 1); +} + +void LLFloaterJoystick::refreshListOfDevices() +{ + mJoysticksCombo->removeall(); + std::string no_device = getString("JoystickDisabled"); + LLSD value = LLSD::Integer(0); + addDevice(no_device, value); + + mHasDeviceList = false; + + void* win_calback = nullptr; + // di8_devices_callback callback is immediate and happens in scope of getInputDevices() +#if LL_WINDOWS && !LL_MESA_HEADLESS + // space navigator is marked as DI8DEVCLASS_GAMECTRL in ndof lib + U32 device_type = DI8DEVCLASS_GAMECTRL; + win_calback = di8_list_devices_callback; +#elif LL_DARWIN + U32 device_type = 0; +#else + // On MAC it is possible to specify product + // and manufacturer in NDOF_Device for + // ndof_init_first to pick specific device + U32 device_type = 0; +#endif + if (gViewerWindow->getWindow()->getInputDevices(device_type, addDeviceCallback, win_calback, this)) + { + mHasDeviceList = true; + } + + bool is_device_id_set = LLViewerJoystick::getInstance()->isDeviceUUIDSet(); + + if (LLViewerJoystick::getInstance()->isJoystickInitialized() && + (!mHasDeviceList || !is_device_id_set)) + { +#if LL_WINDOWS && !LL_MESA_HEADLESS + LL_WARNS() << "NDOF connected to device without using SL provided handle" << LL_ENDL; +#endif + std::string desc = LLViewerJoystick::getInstance()->getDescription(); + if (!desc.empty()) + { + LLSD value = LLSD::Integer(1); // value for selection + addDevice(desc, value); + mHasDeviceList = true; + } + } + + if (gSavedSettings.getBOOL("JoystickEnabled") && mHasDeviceList) + { + if (is_device_id_set) + { + LLSD guid = LLViewerJoystick::getInstance()->getDeviceUUID(); + mJoysticksCombo->selectByValue(guid); + } + else + { + mJoysticksCombo->selectByValue(LLSD::Integer(1)); + } + } + else + { + mJoysticksCombo->selectByValue(LLSD::Integer(0)); + } +} + +void LLFloaterJoystick::cancel() +{ + gSavedSettings.setBOOL("JoystickEnabled", mJoystickEnabled); + gSavedSettings.setLLSD("JoystickDeviceUUID", mJoystickId); + + gSavedSettings.setS32("JoystickAxis0", mJoystickAxis[0]); + gSavedSettings.setS32("JoystickAxis1", mJoystickAxis[1]); + gSavedSettings.setS32("JoystickAxis2", mJoystickAxis[2]); + gSavedSettings.setS32("JoystickAxis3", mJoystickAxis[3]); + gSavedSettings.setS32("JoystickAxis4", mJoystickAxis[4]); + gSavedSettings.setS32("JoystickAxis5", mJoystickAxis[5]); + gSavedSettings.setS32("JoystickAxis6", mJoystickAxis[6]); + + gSavedSettings.setBOOL("Cursor3D", m3DCursor); + gSavedSettings.setBOOL("AutoLeveling", mAutoLeveling); + gSavedSettings.setBOOL("ZoomDirect", mZoomDirect ); + + gSavedSettings.setBOOL("JoystickAvatarEnabled", mAvatarEnabled); + gSavedSettings.setBOOL("JoystickBuildEnabled", mBuildEnabled); + gSavedSettings.setBOOL("JoystickFlycamEnabled", mFlycamEnabled); + + gSavedSettings.setF32("AvatarAxisScale0", mAvatarAxisScale[0]); + gSavedSettings.setF32("AvatarAxisScale1", mAvatarAxisScale[1]); + gSavedSettings.setF32("AvatarAxisScale2", mAvatarAxisScale[2]); + gSavedSettings.setF32("AvatarAxisScale3", mAvatarAxisScale[3]); + gSavedSettings.setF32("AvatarAxisScale4", mAvatarAxisScale[4]); + gSavedSettings.setF32("AvatarAxisScale5", mAvatarAxisScale[5]); + + gSavedSettings.setF32("BuildAxisScale0", mBuildAxisScale[0]); + gSavedSettings.setF32("BuildAxisScale1", mBuildAxisScale[1]); + gSavedSettings.setF32("BuildAxisScale2", mBuildAxisScale[2]); + gSavedSettings.setF32("BuildAxisScale3", mBuildAxisScale[3]); + gSavedSettings.setF32("BuildAxisScale4", mBuildAxisScale[4]); + gSavedSettings.setF32("BuildAxisScale5", mBuildAxisScale[5]); + + gSavedSettings.setF32("FlycamAxisScale0", mFlycamAxisScale[0]); + gSavedSettings.setF32("FlycamAxisScale1", mFlycamAxisScale[1]); + gSavedSettings.setF32("FlycamAxisScale2", mFlycamAxisScale[2]); + gSavedSettings.setF32("FlycamAxisScale3", mFlycamAxisScale[3]); + gSavedSettings.setF32("FlycamAxisScale4", mFlycamAxisScale[4]); + gSavedSettings.setF32("FlycamAxisScale5", mFlycamAxisScale[5]); + gSavedSettings.setF32("FlycamAxisScale6", mFlycamAxisScale[6]); + + gSavedSettings.setF32("AvatarAxisDeadZone0", mAvatarAxisDeadZone[0]); + gSavedSettings.setF32("AvatarAxisDeadZone1", mAvatarAxisDeadZone[1]); + gSavedSettings.setF32("AvatarAxisDeadZone2", mAvatarAxisDeadZone[2]); + gSavedSettings.setF32("AvatarAxisDeadZone3", mAvatarAxisDeadZone[3]); + gSavedSettings.setF32("AvatarAxisDeadZone4", mAvatarAxisDeadZone[4]); + gSavedSettings.setF32("AvatarAxisDeadZone5", mAvatarAxisDeadZone[5]); + + gSavedSettings.setF32("BuildAxisDeadZone0", mBuildAxisDeadZone[0]); + gSavedSettings.setF32("BuildAxisDeadZone1", mBuildAxisDeadZone[1]); + gSavedSettings.setF32("BuildAxisDeadZone2", mBuildAxisDeadZone[2]); + gSavedSettings.setF32("BuildAxisDeadZone3", mBuildAxisDeadZone[3]); + gSavedSettings.setF32("BuildAxisDeadZone4", mBuildAxisDeadZone[4]); + gSavedSettings.setF32("BuildAxisDeadZone5", mBuildAxisDeadZone[5]); + + gSavedSettings.setF32("FlycamAxisDeadZone0", mFlycamAxisDeadZone[0]); + gSavedSettings.setF32("FlycamAxisDeadZone1", mFlycamAxisDeadZone[1]); + gSavedSettings.setF32("FlycamAxisDeadZone2", mFlycamAxisDeadZone[2]); + gSavedSettings.setF32("FlycamAxisDeadZone3", mFlycamAxisDeadZone[3]); + gSavedSettings.setF32("FlycamAxisDeadZone4", mFlycamAxisDeadZone[4]); + gSavedSettings.setF32("FlycamAxisDeadZone5", mFlycamAxisDeadZone[5]); + gSavedSettings.setF32("FlycamAxisDeadZone6", mFlycamAxisDeadZone[6]); + + gSavedSettings.setF32("AvatarFeathering", mAvatarFeathering); + gSavedSettings.setF32("BuildFeathering", mBuildFeathering); + gSavedSettings.setF32("FlycamFeathering", mFlycamFeathering); +} + +void LLFloaterJoystick::onCommitJoystickEnabled(LLUICtrl*, void *joy_panel) +{ + LLFloaterJoystick* self = (LLFloaterJoystick*)joy_panel; + + LLSD value = self->mJoysticksCombo->getValue(); + bool joystick_enabled = true; + // value is 0 for no device, + // 1 for a device on Mac (single device, no list support yet) + // binary packed guid for a device on windows (can have multiple devices) + if (value.isInteger()) + { + // ndof already has a device selected, we are just setting it enabled or disabled + joystick_enabled = value.asInteger(); + } + else + { + LLViewerJoystick::getInstance()->initDevice(value); + // else joystick is enabled, because combobox holds id of the device + joystick_enabled = true; + } + gSavedSettings.setBOOL("JoystickEnabled", joystick_enabled); + bool flycam_enabled = self->mCheckFlycamEnabled->get(); + + if (!joystick_enabled || !flycam_enabled) + { + // Turn off flycam + LLViewerJoystick* joystick(LLViewerJoystick::getInstance()); + if (joystick->getOverrideCamera()) + { + joystick->toggleFlycam(); + } + } + + LLViewerJoystick::getInstance()->saveDeviceIdToSettings(); + + std::string device_string = LLViewerJoystick::getInstance()->getDeviceUUIDString(); + LL_DEBUGS("Joystick") << "Selected " << device_string << " as joystick." << LL_ENDL; + + self->refreshListOfDevices(); +} + +void LLFloaterJoystick::onClickRestoreSNDefaults(void *joy_panel) +{ + setSNDefaults(); +} + +void LLFloaterJoystick::onClickCancel(void *joy_panel) +{ + if (joy_panel) + { + LLFloaterJoystick* self = (LLFloaterJoystick*)joy_panel; + + if (self) + { + self->cancel(); + self->closeFloater(); + } + } +} + +void LLFloaterJoystick::onClickOK(void *joy_panel) +{ + if (joy_panel) + { + LLFloaterJoystick* self = (LLFloaterJoystick*)joy_panel; + + if (self) + { + self->closeFloater(); + } + } +} + +void LLFloaterJoystick::onClickCloseBtn(bool app_quitting) +{ + cancel(); + closeFloater(app_quitting); +} + +void LLFloaterJoystick::setSNDefaults() +{ + LLViewerJoystick::getInstance()->setSNDefaults(); +} + +void LLFloaterJoystick::onClose(bool app_quitting) +{ + if (app_quitting) + { + cancel(); + } +} diff --git a/indra/newview/llfloaterjoystick.h b/indra/newview/llfloaterjoystick.h index 73bf8cd613..b94223a738 100644 --- a/indra/newview/llfloaterjoystick.h +++ b/indra/newview/llfloaterjoystick.h @@ -1,103 +1,103 @@ -/** - * @file llfloaterjoystick.h - * @brief Joystick preferences panel - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERJOYSTICK_H -#define LL_LLFLOATERJOYSTICK_H - -#include "llfloater.h" -#include "llstatview.h" - -class LLCheckBoxCtrl; -class LLComboBox; - -class LLFloaterJoystick : public LLFloater -{ - friend class LLFloaterReg; - -public: - - virtual bool postBuild(); - virtual void refresh(); - virtual void apply(); // Apply the changed values. - virtual void cancel(); // Cancel the changed values. - virtual void draw(); - static void setSNDefaults(); - - static bool addDeviceCallback(std::string &name, LLSD& value, void* userdata); - void addDevice(std::string &name, LLSD& value); - -protected: - - void refreshListOfDevices(); - void onClose(bool app_quitting); - void onClickCloseBtn(bool app_quitting); - -private: - - LLFloaterJoystick(const LLSD& data); - virtual ~LLFloaterJoystick(); - - void initFromSettings(); - - static void onCommitJoystickEnabled(LLUICtrl*, void*); - static void onClickRestoreSNDefaults(void*); - static void onClickCancel(void*); - static void onClickOK(void*); - -private: - // Device prefs - bool mJoystickEnabled; - LLSD mJoystickId; - S32 mJoystickAxis[7]; - bool m3DCursor; - bool mAutoLeveling; - bool mZoomDirect; - - // Modes prefs - bool mAvatarEnabled; - bool mBuildEnabled; - bool mFlycamEnabled; - F32 mAvatarAxisScale[6]; - F32 mBuildAxisScale[6]; - F32 mFlycamAxisScale[7]; - F32 mAvatarAxisDeadZone[6]; - F32 mBuildAxisDeadZone[6]; - F32 mFlycamAxisDeadZone[7]; - F32 mAvatarFeathering; - F32 mBuildFeathering; - F32 mFlycamFeathering; - - // Controls that can disable the flycam - LLCheckBoxCtrl *mCheckFlycamEnabled; - LLComboBox *mJoysticksCombo; - - bool mHasDeviceList; - - // stats view - LLStatBar* mAxisStatsBar[6]; -}; - -#endif +/** + * @file llfloaterjoystick.h + * @brief Joystick preferences panel + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERJOYSTICK_H +#define LL_LLFLOATERJOYSTICK_H + +#include "llfloater.h" +#include "llstatview.h" + +class LLCheckBoxCtrl; +class LLComboBox; + +class LLFloaterJoystick : public LLFloater +{ + friend class LLFloaterReg; + +public: + + virtual bool postBuild(); + virtual void refresh(); + virtual void apply(); // Apply the changed values. + virtual void cancel(); // Cancel the changed values. + virtual void draw(); + static void setSNDefaults(); + + static bool addDeviceCallback(std::string &name, LLSD& value, void* userdata); + void addDevice(std::string &name, LLSD& value); + +protected: + + void refreshListOfDevices(); + void onClose(bool app_quitting); + void onClickCloseBtn(bool app_quitting); + +private: + + LLFloaterJoystick(const LLSD& data); + virtual ~LLFloaterJoystick(); + + void initFromSettings(); + + static void onCommitJoystickEnabled(LLUICtrl*, void*); + static void onClickRestoreSNDefaults(void*); + static void onClickCancel(void*); + static void onClickOK(void*); + +private: + // Device prefs + bool mJoystickEnabled; + LLSD mJoystickId; + S32 mJoystickAxis[7]; + bool m3DCursor; + bool mAutoLeveling; + bool mZoomDirect; + + // Modes prefs + bool mAvatarEnabled; + bool mBuildEnabled; + bool mFlycamEnabled; + F32 mAvatarAxisScale[6]; + F32 mBuildAxisScale[6]; + F32 mFlycamAxisScale[7]; + F32 mAvatarAxisDeadZone[6]; + F32 mBuildAxisDeadZone[6]; + F32 mFlycamAxisDeadZone[7]; + F32 mAvatarFeathering; + F32 mBuildFeathering; + F32 mFlycamFeathering; + + // Controls that can disable the flycam + LLCheckBoxCtrl *mCheckFlycamEnabled; + LLComboBox *mJoysticksCombo; + + bool mHasDeviceList; + + // stats view + LLStatBar* mAxisStatsBar[6]; +}; + +#endif diff --git a/indra/newview/llfloaterlagmeter.cpp b/indra/newview/llfloaterlagmeter.cpp index 87ef70a532..28fa8dea9a 100644 --- a/indra/newview/llfloaterlagmeter.cpp +++ b/indra/newview/llfloaterlagmeter.cpp @@ -1,374 +1,374 @@ -/** - * @file llfloaterlagmeter.cpp - * @brief The "Lag-o-Meter" floater used to tell users what is causing lag. - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterlagmeter.h" - -#include "lluictrlfactory.h" -#include "llviewerstats.h" -#include "llviewertexture.h" -#include "llviewercontrol.h" -#include "llappviewer.h" - -#include "lltexturefetch.h" - -#include "llbutton.h" -#include "llfocusmgr.h" -#include "lltextbox.h" - -const std::string LAG_CRITICAL_IMAGE_NAME = "lag_status_critical.tga"; -const std::string LAG_WARNING_IMAGE_NAME = "lag_status_warning.tga"; -const std::string LAG_GOOD_IMAGE_NAME = "lag_status_good.tga"; - -LLFloaterLagMeter::LLFloaterLagMeter(const LLSD& key) - : LLFloater(key) -{ - mCommitCallbackRegistrar.add("LagMeter.ClickShrink", boost::bind(&LLFloaterLagMeter::onClickShrink, this)); -} - -bool LLFloaterLagMeter::postBuild() -{ - // Don't let this window take keyboard focus -- it's confusing to - // lose arrow-key driving when testing lag. - setIsChrome(true); - - // were we shrunk last time? - if (isShrunk()) - { - onClickShrink(); - } - - mClientButton = getChild("client_lagmeter"); - mClientText = getChild("client_text"); - mClientCause = getChild("client_lag_cause"); - - mNetworkButton = getChild("network_lagmeter"); - mNetworkText = getChild("network_text"); - mNetworkCause = getChild("network_lag_cause"); - - mServerButton = getChild("server_lagmeter"); - mServerText = getChild("server_text"); - mServerCause = getChild("server_lag_cause"); - - std::string config_string = getString("client_frame_rate_critical_fps", mStringArgs); - mClientFrameTimeCritical = F32Seconds(1.0f / (float)atof( config_string.c_str() )); - config_string = getString("client_frame_rate_warning_fps", mStringArgs); - mClientFrameTimeWarning = F32Seconds(1.0f / (float)atof( config_string.c_str() )); - - config_string = getString("network_packet_loss_critical_pct", mStringArgs); - mNetworkPacketLossCritical = F32Percent((float)atof( config_string.c_str() )); - config_string = getString("network_packet_loss_warning_pct", mStringArgs); - mNetworkPacketLossWarning = F32Percent((float)atof( config_string.c_str() )); - - config_string = getString("network_ping_critical_ms", mStringArgs); - mNetworkPingCritical = F32Milliseconds((float)atof( config_string.c_str() )); - config_string = getString("network_ping_warning_ms", mStringArgs); - mNetworkPingWarning = F32Milliseconds((float)atof( config_string.c_str() )); - config_string = getString("server_frame_rate_critical_fps", mStringArgs); - - mServerFrameTimeCritical = F32Seconds(1.0f / (float)atof( config_string.c_str() )); - config_string = getString("server_frame_rate_warning_fps", mStringArgs); - mServerFrameTimeWarning = F32Seconds(1.0f / (float)atof( config_string.c_str() )); - config_string = getString("server_single_process_max_time_ms", mStringArgs); - mServerSingleProcessMaxTime = F32Seconds((float)atof( config_string.c_str() )); - -// mShrunk = false; - config_string = getString("max_width_px", mStringArgs); - mMaxWidth = atoi( config_string.c_str() ); - config_string = getString("min_width_px", mStringArgs); - mMinWidth = atoi( config_string.c_str() ); - - mStringArgs["[CLIENT_FRAME_RATE_CRITICAL]"] = getString("client_frame_rate_critical_fps"); - mStringArgs["[CLIENT_FRAME_RATE_WARNING]"] = getString("client_frame_rate_warning_fps"); - - mStringArgs["[NETWORK_PACKET_LOSS_CRITICAL]"] = getString("network_packet_loss_critical_pct"); - mStringArgs["[NETWORK_PACKET_LOSS_WARNING]"] = getString("network_packet_loss_warning_pct"); - - mStringArgs["[NETWORK_PING_CRITICAL]"] = getString("network_ping_critical_ms"); - mStringArgs["[NETWORK_PING_WARNING]"] = getString("network_ping_warning_ms"); - - mStringArgs["[SERVER_FRAME_RATE_CRITICAL]"] = getString("server_frame_rate_critical_fps"); - mStringArgs["[SERVER_FRAME_RATE_WARNING]"] = getString("server_frame_rate_warning_fps"); - -// childSetAction("minimize", onClickShrink, this); - updateControls(isShrunk()); // if expanded append colon to the labels (EXT-4079) - - return true; -} -LLFloaterLagMeter::~LLFloaterLagMeter() -{ - // save shrunk status for next time -// gSavedSettings.setBOOL("LagMeterShrunk", mShrunk); - // expand so we save the large window rectangle - if (isShrunk()) - { - onClickShrink(); - } -} - -void LLFloaterLagMeter::draw() -{ - determineClient(); - determineNetwork(); - determineServer(); - - LLFloater::draw(); -} - -void LLFloaterLagMeter::determineClient() -{ - F32Milliseconds client_frame_time = LLTrace::get_frame_recording().getPeriodMean(LLStatViewer::FRAME_STACKTIME); - bool find_cause = false; - - if (!gFocusMgr.getAppHasFocus()) - { - mClientButton->setImageUnselected(LLUI::getUIImage(LAG_GOOD_IMAGE_NAME)); - mClientText->setText( getString("client_frame_time_window_bg_msg", mStringArgs) ); - mClientCause->setText( LLStringUtil::null ); - } - else if(client_frame_time >= mClientFrameTimeCritical) - { - mClientButton->setImageUnselected(LLUI::getUIImage(LAG_CRITICAL_IMAGE_NAME)); - mClientText->setText( getString("client_frame_time_critical_msg", mStringArgs) ); - find_cause = true; - } - else if(client_frame_time >= mClientFrameTimeWarning) - { - mClientButton->setImageUnselected(LLUI::getUIImage(LAG_WARNING_IMAGE_NAME)); - mClientText->setText( getString("client_frame_time_warning_msg", mStringArgs) ); - find_cause = true; - } - else - { - mClientButton->setImageUnselected(LLUI::getUIImage(LAG_GOOD_IMAGE_NAME)); - mClientText->setText( getString("client_frame_time_normal_msg", mStringArgs) ); - mClientCause->setText( LLStringUtil::null ); - } - - if(find_cause) - { - if(gSavedSettings.getF32("RenderFarClip") > 128) - { - mClientCause->setText( getString("client_draw_distance_cause_msg", mStringArgs) ); - } - else if(LLAppViewer::instance()->getTextureFetch()->getNumRequests() > 2) - { - mClientCause->setText( getString("client_texture_loading_cause_msg", mStringArgs) ); - } - else - { - mClientCause->setText( getString("client_complex_objects_cause_msg", mStringArgs) ); - } - } -} - -void LLFloaterLagMeter::determineNetwork() -{ - LLTrace::PeriodicRecording& frame_recording = LLTrace::get_frame_recording(); - F32Percent packet_loss = frame_recording.getPeriodMean(LLStatViewer::PACKETS_LOST_PERCENT); - F32Milliseconds ping_time = frame_recording.getPeriodMean(LLStatViewer::SIM_PING); - bool find_cause_loss = false; - bool find_cause_ping = false; - - // *FIXME: We can't blame a large ping time on anything in - // particular if the frame rate is low, because a low frame - // rate is a sure recipe for bad ping times right now until - // the network handlers are de-synched from the rendering. - F32Milliseconds client_frame_time = frame_recording.getPeriodMean(LLStatViewer::FRAME_STACKTIME); - - if(packet_loss >= mNetworkPacketLossCritical) - { - mNetworkButton->setImageUnselected(LLUI::getUIImage(LAG_CRITICAL_IMAGE_NAME)); - mNetworkText->setText( getString("network_packet_loss_critical_msg", mStringArgs) ); - find_cause_loss = true; - } - else if(ping_time >= mNetworkPingCritical) - { - mNetworkButton->setImageUnselected(LLUI::getUIImage(LAG_CRITICAL_IMAGE_NAME)); - if (client_frame_time < mNetworkPingCritical) - { - mNetworkText->setText( getString("network_ping_critical_msg", mStringArgs) ); - find_cause_ping = true; - } - } - else if(packet_loss >= mNetworkPacketLossWarning) - { - mNetworkButton->setImageUnselected(LLUI::getUIImage(LAG_WARNING_IMAGE_NAME)); - mNetworkText->setText( getString("network_packet_loss_warning_msg", mStringArgs) ); - find_cause_loss = true; - } - else if(ping_time >= mNetworkPingWarning) - { - mNetworkButton->setImageUnselected(LLUI::getUIImage(LAG_WARNING_IMAGE_NAME)); - if (client_frame_time < mNetworkPingWarning) - { - mNetworkText->setText( getString("network_ping_warning_msg", mStringArgs) ); - find_cause_ping = true; - } - } - else - { - mNetworkButton->setImageUnselected(LLUI::getUIImage(LAG_GOOD_IMAGE_NAME)); - mNetworkText->setText( getString("network_performance_normal_msg", mStringArgs) ); - } - - if(find_cause_loss) - { - mNetworkCause->setText( getString("network_packet_loss_cause_msg", mStringArgs) ); - } - else if(find_cause_ping) - { - mNetworkCause->setText( getString("network_ping_cause_msg", mStringArgs) ); - } - else - { - mNetworkCause->setText( LLStringUtil::null ); - } -} - -void LLFloaterLagMeter::determineServer() -{ - F32Milliseconds sim_frame_time = LLTrace::get_frame_recording().getLastRecording().getLastValue(LLStatViewer::SIM_FRAME_TIME); - bool find_cause = false; - - if(sim_frame_time >= mServerFrameTimeCritical) - { - mServerButton->setImageUnselected(LLUI::getUIImage(LAG_CRITICAL_IMAGE_NAME)); - mServerText->setText( getString("server_frame_time_critical_msg", mStringArgs) ); - find_cause = true; - } - else if(sim_frame_time >= mServerFrameTimeWarning) - { - mServerButton->setImageUnselected(LLUI::getUIImage(LAG_WARNING_IMAGE_NAME)); - mServerText->setText( getString("server_frame_time_warning_msg", mStringArgs) ); - find_cause = true; - } - else - { - mServerButton->setImageUnselected(LLUI::getUIImage(LAG_GOOD_IMAGE_NAME)); - mServerText->setText( getString("server_frame_time_normal_msg", mStringArgs) ); - mServerCause->setText( LLStringUtil::null ); - } - - if(find_cause) - { - LLTrace::Recording& last_recording = LLTrace::get_frame_recording().getLastRecording(); - - if(last_recording.getLastValue(LLStatViewer::SIM_PHYSICS_TIME) > mServerSingleProcessMaxTime) - { - mServerCause->setText( getString("server_physics_cause_msg", mStringArgs) ); - } - else if(last_recording.getLastValue(LLStatViewer::SIM_SCRIPTS_TIME) > mServerSingleProcessMaxTime) - { - mServerCause->setText( getString("server_scripts_cause_msg", mStringArgs) ); - } - else if(last_recording.getLastValue(LLStatViewer::SIM_NET_TIME) > mServerSingleProcessMaxTime) - { - mServerCause->setText( getString("server_net_cause_msg", mStringArgs) ); - } - else if(last_recording.getLastValue(LLStatViewer::SIM_AGENTS_TIME) > mServerSingleProcessMaxTime) - { - mServerCause->setText( getString("server_agent_cause_msg", mStringArgs) ); - } - else if(last_recording.getLastValue(LLStatViewer::SIM_IMAGES_TIME) > mServerSingleProcessMaxTime) - { - mServerCause->setText( getString("server_images_cause_msg", mStringArgs) ); - } - else - { - mServerCause->setText( getString("server_generic_cause_msg", mStringArgs) ); - } - } -} - -void LLFloaterLagMeter::updateControls(bool shrink) -{ -// LLFloaterLagMeter * self = (LLFloaterLagMeter*)data; - - LLButton * button = getChild("minimize"); - S32 delta_width = mMaxWidth -mMinWidth; - LLRect r = getRect(); - - if(!shrink) - { - setTitle(getString("max_title_msg", mStringArgs) ); - // make left edge appear to expand - r.translate(-delta_width, 0); - setRect(r); - reshape(mMaxWidth, getRect().getHeight()); - - getChild("client")->setValue(getString("client_text_msg", mStringArgs) + ":"); - getChild("network")->setValue(getString("network_text_msg",mStringArgs) + ":"); - getChild("server")->setValue(getString("server_text_msg", mStringArgs) + ":"); - - // usually "<<" - button->setLabel( getString("smaller_label", mStringArgs) ); - } - else - { - setTitle( getString("min_title_msg", mStringArgs) ); - // make left edge appear to collapse - r.translate(delta_width, 0); - setRect(r); - reshape(mMinWidth, getRect().getHeight()); - - getChild("client")->setValue(getString("client_text_msg", mStringArgs) ); - getChild("network")->setValue(getString("network_text_msg",mStringArgs) ); - getChild("server")->setValue(getString("server_text_msg", mStringArgs) ); - - // usually ">>" - button->setLabel( getString("bigger_label", mStringArgs) ); - } - // Don't put keyboard focus on the button - button->setFocus(false); - -// self->mClientText->setVisible(self->mShrunk); -// self->mClientCause->setVisible(self->mShrunk); -// self->getChildView("client_help")->setVisible( self->mShrunk); - -// self->mNetworkText->setVisible(self->mShrunk); -// self->mNetworkCause->setVisible(self->mShrunk); -// self->getChildView("network_help")->setVisible( self->mShrunk); - -// self->mServerText->setVisible(self->mShrunk); -// self->mServerCause->setVisible(self->mShrunk); -// self->getChildView("server_help")->setVisible( self->mShrunk); - -// self->mShrunk = !self->mShrunk; -} - -bool LLFloaterLagMeter::isShrunk() -{ - return gSavedSettings.getBOOL("LagMeterShrunk"); -} - -void LLFloaterLagMeter::onClickShrink() // toggle "LagMeterShrunk" -{ - bool shrunk = isShrunk(); - updateControls(!shrunk); - gSavedSettings.setBOOL("LagMeterShrunk", !shrunk); -} +/** + * @file llfloaterlagmeter.cpp + * @brief The "Lag-o-Meter" floater used to tell users what is causing lag. + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterlagmeter.h" + +#include "lluictrlfactory.h" +#include "llviewerstats.h" +#include "llviewertexture.h" +#include "llviewercontrol.h" +#include "llappviewer.h" + +#include "lltexturefetch.h" + +#include "llbutton.h" +#include "llfocusmgr.h" +#include "lltextbox.h" + +const std::string LAG_CRITICAL_IMAGE_NAME = "lag_status_critical.tga"; +const std::string LAG_WARNING_IMAGE_NAME = "lag_status_warning.tga"; +const std::string LAG_GOOD_IMAGE_NAME = "lag_status_good.tga"; + +LLFloaterLagMeter::LLFloaterLagMeter(const LLSD& key) + : LLFloater(key) +{ + mCommitCallbackRegistrar.add("LagMeter.ClickShrink", boost::bind(&LLFloaterLagMeter::onClickShrink, this)); +} + +bool LLFloaterLagMeter::postBuild() +{ + // Don't let this window take keyboard focus -- it's confusing to + // lose arrow-key driving when testing lag. + setIsChrome(true); + + // were we shrunk last time? + if (isShrunk()) + { + onClickShrink(); + } + + mClientButton = getChild("client_lagmeter"); + mClientText = getChild("client_text"); + mClientCause = getChild("client_lag_cause"); + + mNetworkButton = getChild("network_lagmeter"); + mNetworkText = getChild("network_text"); + mNetworkCause = getChild("network_lag_cause"); + + mServerButton = getChild("server_lagmeter"); + mServerText = getChild("server_text"); + mServerCause = getChild("server_lag_cause"); + + std::string config_string = getString("client_frame_rate_critical_fps", mStringArgs); + mClientFrameTimeCritical = F32Seconds(1.0f / (float)atof( config_string.c_str() )); + config_string = getString("client_frame_rate_warning_fps", mStringArgs); + mClientFrameTimeWarning = F32Seconds(1.0f / (float)atof( config_string.c_str() )); + + config_string = getString("network_packet_loss_critical_pct", mStringArgs); + mNetworkPacketLossCritical = F32Percent((float)atof( config_string.c_str() )); + config_string = getString("network_packet_loss_warning_pct", mStringArgs); + mNetworkPacketLossWarning = F32Percent((float)atof( config_string.c_str() )); + + config_string = getString("network_ping_critical_ms", mStringArgs); + mNetworkPingCritical = F32Milliseconds((float)atof( config_string.c_str() )); + config_string = getString("network_ping_warning_ms", mStringArgs); + mNetworkPingWarning = F32Milliseconds((float)atof( config_string.c_str() )); + config_string = getString("server_frame_rate_critical_fps", mStringArgs); + + mServerFrameTimeCritical = F32Seconds(1.0f / (float)atof( config_string.c_str() )); + config_string = getString("server_frame_rate_warning_fps", mStringArgs); + mServerFrameTimeWarning = F32Seconds(1.0f / (float)atof( config_string.c_str() )); + config_string = getString("server_single_process_max_time_ms", mStringArgs); + mServerSingleProcessMaxTime = F32Seconds((float)atof( config_string.c_str() )); + +// mShrunk = false; + config_string = getString("max_width_px", mStringArgs); + mMaxWidth = atoi( config_string.c_str() ); + config_string = getString("min_width_px", mStringArgs); + mMinWidth = atoi( config_string.c_str() ); + + mStringArgs["[CLIENT_FRAME_RATE_CRITICAL]"] = getString("client_frame_rate_critical_fps"); + mStringArgs["[CLIENT_FRAME_RATE_WARNING]"] = getString("client_frame_rate_warning_fps"); + + mStringArgs["[NETWORK_PACKET_LOSS_CRITICAL]"] = getString("network_packet_loss_critical_pct"); + mStringArgs["[NETWORK_PACKET_LOSS_WARNING]"] = getString("network_packet_loss_warning_pct"); + + mStringArgs["[NETWORK_PING_CRITICAL]"] = getString("network_ping_critical_ms"); + mStringArgs["[NETWORK_PING_WARNING]"] = getString("network_ping_warning_ms"); + + mStringArgs["[SERVER_FRAME_RATE_CRITICAL]"] = getString("server_frame_rate_critical_fps"); + mStringArgs["[SERVER_FRAME_RATE_WARNING]"] = getString("server_frame_rate_warning_fps"); + +// childSetAction("minimize", onClickShrink, this); + updateControls(isShrunk()); // if expanded append colon to the labels (EXT-4079) + + return true; +} +LLFloaterLagMeter::~LLFloaterLagMeter() +{ + // save shrunk status for next time +// gSavedSettings.setBOOL("LagMeterShrunk", mShrunk); + // expand so we save the large window rectangle + if (isShrunk()) + { + onClickShrink(); + } +} + +void LLFloaterLagMeter::draw() +{ + determineClient(); + determineNetwork(); + determineServer(); + + LLFloater::draw(); +} + +void LLFloaterLagMeter::determineClient() +{ + F32Milliseconds client_frame_time = LLTrace::get_frame_recording().getPeriodMean(LLStatViewer::FRAME_STACKTIME); + bool find_cause = false; + + if (!gFocusMgr.getAppHasFocus()) + { + mClientButton->setImageUnselected(LLUI::getUIImage(LAG_GOOD_IMAGE_NAME)); + mClientText->setText( getString("client_frame_time_window_bg_msg", mStringArgs) ); + mClientCause->setText( LLStringUtil::null ); + } + else if(client_frame_time >= mClientFrameTimeCritical) + { + mClientButton->setImageUnselected(LLUI::getUIImage(LAG_CRITICAL_IMAGE_NAME)); + mClientText->setText( getString("client_frame_time_critical_msg", mStringArgs) ); + find_cause = true; + } + else if(client_frame_time >= mClientFrameTimeWarning) + { + mClientButton->setImageUnselected(LLUI::getUIImage(LAG_WARNING_IMAGE_NAME)); + mClientText->setText( getString("client_frame_time_warning_msg", mStringArgs) ); + find_cause = true; + } + else + { + mClientButton->setImageUnselected(LLUI::getUIImage(LAG_GOOD_IMAGE_NAME)); + mClientText->setText( getString("client_frame_time_normal_msg", mStringArgs) ); + mClientCause->setText( LLStringUtil::null ); + } + + if(find_cause) + { + if(gSavedSettings.getF32("RenderFarClip") > 128) + { + mClientCause->setText( getString("client_draw_distance_cause_msg", mStringArgs) ); + } + else if(LLAppViewer::instance()->getTextureFetch()->getNumRequests() > 2) + { + mClientCause->setText( getString("client_texture_loading_cause_msg", mStringArgs) ); + } + else + { + mClientCause->setText( getString("client_complex_objects_cause_msg", mStringArgs) ); + } + } +} + +void LLFloaterLagMeter::determineNetwork() +{ + LLTrace::PeriodicRecording& frame_recording = LLTrace::get_frame_recording(); + F32Percent packet_loss = frame_recording.getPeriodMean(LLStatViewer::PACKETS_LOST_PERCENT); + F32Milliseconds ping_time = frame_recording.getPeriodMean(LLStatViewer::SIM_PING); + bool find_cause_loss = false; + bool find_cause_ping = false; + + // *FIXME: We can't blame a large ping time on anything in + // particular if the frame rate is low, because a low frame + // rate is a sure recipe for bad ping times right now until + // the network handlers are de-synched from the rendering. + F32Milliseconds client_frame_time = frame_recording.getPeriodMean(LLStatViewer::FRAME_STACKTIME); + + if(packet_loss >= mNetworkPacketLossCritical) + { + mNetworkButton->setImageUnselected(LLUI::getUIImage(LAG_CRITICAL_IMAGE_NAME)); + mNetworkText->setText( getString("network_packet_loss_critical_msg", mStringArgs) ); + find_cause_loss = true; + } + else if(ping_time >= mNetworkPingCritical) + { + mNetworkButton->setImageUnselected(LLUI::getUIImage(LAG_CRITICAL_IMAGE_NAME)); + if (client_frame_time < mNetworkPingCritical) + { + mNetworkText->setText( getString("network_ping_critical_msg", mStringArgs) ); + find_cause_ping = true; + } + } + else if(packet_loss >= mNetworkPacketLossWarning) + { + mNetworkButton->setImageUnselected(LLUI::getUIImage(LAG_WARNING_IMAGE_NAME)); + mNetworkText->setText( getString("network_packet_loss_warning_msg", mStringArgs) ); + find_cause_loss = true; + } + else if(ping_time >= mNetworkPingWarning) + { + mNetworkButton->setImageUnselected(LLUI::getUIImage(LAG_WARNING_IMAGE_NAME)); + if (client_frame_time < mNetworkPingWarning) + { + mNetworkText->setText( getString("network_ping_warning_msg", mStringArgs) ); + find_cause_ping = true; + } + } + else + { + mNetworkButton->setImageUnselected(LLUI::getUIImage(LAG_GOOD_IMAGE_NAME)); + mNetworkText->setText( getString("network_performance_normal_msg", mStringArgs) ); + } + + if(find_cause_loss) + { + mNetworkCause->setText( getString("network_packet_loss_cause_msg", mStringArgs) ); + } + else if(find_cause_ping) + { + mNetworkCause->setText( getString("network_ping_cause_msg", mStringArgs) ); + } + else + { + mNetworkCause->setText( LLStringUtil::null ); + } +} + +void LLFloaterLagMeter::determineServer() +{ + F32Milliseconds sim_frame_time = LLTrace::get_frame_recording().getLastRecording().getLastValue(LLStatViewer::SIM_FRAME_TIME); + bool find_cause = false; + + if(sim_frame_time >= mServerFrameTimeCritical) + { + mServerButton->setImageUnselected(LLUI::getUIImage(LAG_CRITICAL_IMAGE_NAME)); + mServerText->setText( getString("server_frame_time_critical_msg", mStringArgs) ); + find_cause = true; + } + else if(sim_frame_time >= mServerFrameTimeWarning) + { + mServerButton->setImageUnselected(LLUI::getUIImage(LAG_WARNING_IMAGE_NAME)); + mServerText->setText( getString("server_frame_time_warning_msg", mStringArgs) ); + find_cause = true; + } + else + { + mServerButton->setImageUnselected(LLUI::getUIImage(LAG_GOOD_IMAGE_NAME)); + mServerText->setText( getString("server_frame_time_normal_msg", mStringArgs) ); + mServerCause->setText( LLStringUtil::null ); + } + + if(find_cause) + { + LLTrace::Recording& last_recording = LLTrace::get_frame_recording().getLastRecording(); + + if(last_recording.getLastValue(LLStatViewer::SIM_PHYSICS_TIME) > mServerSingleProcessMaxTime) + { + mServerCause->setText( getString("server_physics_cause_msg", mStringArgs) ); + } + else if(last_recording.getLastValue(LLStatViewer::SIM_SCRIPTS_TIME) > mServerSingleProcessMaxTime) + { + mServerCause->setText( getString("server_scripts_cause_msg", mStringArgs) ); + } + else if(last_recording.getLastValue(LLStatViewer::SIM_NET_TIME) > mServerSingleProcessMaxTime) + { + mServerCause->setText( getString("server_net_cause_msg", mStringArgs) ); + } + else if(last_recording.getLastValue(LLStatViewer::SIM_AGENTS_TIME) > mServerSingleProcessMaxTime) + { + mServerCause->setText( getString("server_agent_cause_msg", mStringArgs) ); + } + else if(last_recording.getLastValue(LLStatViewer::SIM_IMAGES_TIME) > mServerSingleProcessMaxTime) + { + mServerCause->setText( getString("server_images_cause_msg", mStringArgs) ); + } + else + { + mServerCause->setText( getString("server_generic_cause_msg", mStringArgs) ); + } + } +} + +void LLFloaterLagMeter::updateControls(bool shrink) +{ +// LLFloaterLagMeter * self = (LLFloaterLagMeter*)data; + + LLButton * button = getChild("minimize"); + S32 delta_width = mMaxWidth -mMinWidth; + LLRect r = getRect(); + + if(!shrink) + { + setTitle(getString("max_title_msg", mStringArgs) ); + // make left edge appear to expand + r.translate(-delta_width, 0); + setRect(r); + reshape(mMaxWidth, getRect().getHeight()); + + getChild("client")->setValue(getString("client_text_msg", mStringArgs) + ":"); + getChild("network")->setValue(getString("network_text_msg",mStringArgs) + ":"); + getChild("server")->setValue(getString("server_text_msg", mStringArgs) + ":"); + + // usually "<<" + button->setLabel( getString("smaller_label", mStringArgs) ); + } + else + { + setTitle( getString("min_title_msg", mStringArgs) ); + // make left edge appear to collapse + r.translate(delta_width, 0); + setRect(r); + reshape(mMinWidth, getRect().getHeight()); + + getChild("client")->setValue(getString("client_text_msg", mStringArgs) ); + getChild("network")->setValue(getString("network_text_msg",mStringArgs) ); + getChild("server")->setValue(getString("server_text_msg", mStringArgs) ); + + // usually ">>" + button->setLabel( getString("bigger_label", mStringArgs) ); + } + // Don't put keyboard focus on the button + button->setFocus(false); + +// self->mClientText->setVisible(self->mShrunk); +// self->mClientCause->setVisible(self->mShrunk); +// self->getChildView("client_help")->setVisible( self->mShrunk); + +// self->mNetworkText->setVisible(self->mShrunk); +// self->mNetworkCause->setVisible(self->mShrunk); +// self->getChildView("network_help")->setVisible( self->mShrunk); + +// self->mServerText->setVisible(self->mShrunk); +// self->mServerCause->setVisible(self->mShrunk); +// self->getChildView("server_help")->setVisible( self->mShrunk); + +// self->mShrunk = !self->mShrunk; +} + +bool LLFloaterLagMeter::isShrunk() +{ + return gSavedSettings.getBOOL("LagMeterShrunk"); +} + +void LLFloaterLagMeter::onClickShrink() // toggle "LagMeterShrunk" +{ + bool shrunk = isShrunk(); + updateControls(!shrunk); + gSavedSettings.setBOOL("LagMeterShrunk", !shrunk); +} diff --git a/indra/newview/llfloaterlagmeter.h b/indra/newview/llfloaterlagmeter.h index 1ad8f97c52..1b5ff65781 100644 --- a/indra/newview/llfloaterlagmeter.h +++ b/indra/newview/llfloaterlagmeter.h @@ -1,80 +1,80 @@ -/** - * @file llfloaterlagmeter.h - * @brief The "Lag-o-Meter" floater used to tell users what is causing lag. - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLFLOATERLAGMETER_H -#define LLFLOATERLAGMETER_H - -#include "llfloater.h" - -class LLTextBox; - -class LLFloaterLagMeter : public LLFloater -{ - friend class LLFloaterReg; - -public: - /*virtual*/ void draw(); - /*virtual*/ bool postBuild(); -private: - - LLFloaterLagMeter(const LLSD& key); - /*virtual*/ ~LLFloaterLagMeter(); - void determineClient(); - void determineNetwork(); - void determineServer(); - void updateControls(bool shrink); - bool isShrunk(); - - void onClickShrink(); - - bool mShrunk; - S32 mMaxWidth, mMinWidth; - - F32Milliseconds mClientFrameTimeCritical; - F32Milliseconds mClientFrameTimeWarning; - LLButton* mClientButton; - LLTextBox* mClientText; - LLTextBox* mClientCause; - - F32Percent mNetworkPacketLossCritical; - F32Percent mNetworkPacketLossWarning; - F32Milliseconds mNetworkPingCritical; - F32Milliseconds mNetworkPingWarning; - LLButton* mNetworkButton; - LLTextBox* mNetworkText; - LLTextBox* mNetworkCause; - - F32Milliseconds mServerFrameTimeCritical; - F32Milliseconds mServerFrameTimeWarning; - F32Milliseconds mServerSingleProcessMaxTime; - LLButton* mServerButton; - LLTextBox* mServerText; - LLTextBox* mServerCause; - - LLStringUtil::format_map_t mStringArgs; -}; - -#endif +/** + * @file llfloaterlagmeter.h + * @brief The "Lag-o-Meter" floater used to tell users what is causing lag. + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLFLOATERLAGMETER_H +#define LLFLOATERLAGMETER_H + +#include "llfloater.h" + +class LLTextBox; + +class LLFloaterLagMeter : public LLFloater +{ + friend class LLFloaterReg; + +public: + /*virtual*/ void draw(); + /*virtual*/ bool postBuild(); +private: + + LLFloaterLagMeter(const LLSD& key); + /*virtual*/ ~LLFloaterLagMeter(); + void determineClient(); + void determineNetwork(); + void determineServer(); + void updateControls(bool shrink); + bool isShrunk(); + + void onClickShrink(); + + bool mShrunk; + S32 mMaxWidth, mMinWidth; + + F32Milliseconds mClientFrameTimeCritical; + F32Milliseconds mClientFrameTimeWarning; + LLButton* mClientButton; + LLTextBox* mClientText; + LLTextBox* mClientCause; + + F32Percent mNetworkPacketLossCritical; + F32Percent mNetworkPacketLossWarning; + F32Milliseconds mNetworkPingCritical; + F32Milliseconds mNetworkPingWarning; + LLButton* mNetworkButton; + LLTextBox* mNetworkText; + LLTextBox* mNetworkCause; + + F32Milliseconds mServerFrameTimeCritical; + F32Milliseconds mServerFrameTimeWarning; + F32Milliseconds mServerSingleProcessMaxTime; + LLButton* mServerButton; + LLTextBox* mServerText; + LLTextBox* mServerCause; + + LLStringUtil::format_map_t mStringArgs; +}; + +#endif diff --git a/indra/newview/llfloaterland.cpp b/indra/newview/llfloaterland.cpp index 9001acc1fc..a1951bc4a8 100644 --- a/indra/newview/llfloaterland.cpp +++ b/indra/newview/llfloaterland.cpp @@ -1,3459 +1,3459 @@ -/** - * @file llfloaterland.cpp - * @brief "About Land" floater, allowing display and editing of land parcel properties. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include -#include - -#include "llfloaterland.h" - -#include "llavatarnamecache.h" -#include "llfocusmgr.h" -#include "llnotificationsutil.h" -#include "llparcel.h" -#include "message.h" - -#include "llagent.h" -#include "llagentaccess.h" -#include "llappviewer.h" -#include "llbutton.h" -#include "llcheckboxctrl.h" -#include "llcombobox.h" -#include "llfloaterreg.h" -#include "llfloateravatarpicker.h" -#include "llfloaterauction.h" -#include "llfloaterbanduration.h" -#include "llfloatergroups.h" -#include "llfloaterscriptlimits.h" -#include "llavataractions.h" -#include "lllineeditor.h" -#include "llnamelistctrl.h" -#include "llpanellandaudio.h" -#include "llpanellandmedia.h" -#include "llradiogroup.h" -#include "llresmgr.h" // getMonetaryString -#include "llscrolllistctrl.h" -#include "llscrolllistitem.h" -#include "llscrolllistcell.h" -#include "llselectmgr.h" -#include "llslurl.h" -#include "llspinctrl.h" -#include "lltabcontainer.h" -#include "lltextbox.h" -#include "lltexturectrl.h" -#include "lluiconstants.h" -#include "lluictrlfactory.h" -#include "llviewertexturelist.h" // LLUIImageList -#include "llviewermessage.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" -#include "llviewerstats.h" -#include "llviewertexteditor.h" -#include "llviewerwindow.h" -#include "llviewercontrol.h" -#include "roles_constants.h" -#include "lltrans.h" -#include "llpanelexperiencelisteditor.h" -#include "llpanelexperiencepicker.h" -#include "llpanelenvironment.h" -#include "llexperiencecache.h" - -#include "llgroupactions.h" -#include "llenvironment.h" - -const F64 COVENANT_REFRESH_TIME_SEC = 60.0f; - -static std::string OWNER_ONLINE = "0"; -static std::string OWNER_OFFLINE = "1"; -static std::string OWNER_GROUP = "2"; -static std::string MATURITY = "[MATURITY]"; - -// constants used in callbacks below - syntactic sugar. -static const bool BUY_GROUP_LAND = true; -static const bool BUY_PERSONAL_LAND = false; -LLPointer LLPanelLandGeneral::sSelectionForBuyPass = NULL; - -// Statics -LLParcelSelectionObserver* LLFloaterLand::sObserver = NULL; -S32 LLFloaterLand::sLastTab = 0; - -// Local classes -class LLParcelSelectionObserver : public LLParcelObserver -{ -public: - virtual void changed() { LLFloaterLand::refreshAll(); } -}; - -// class needed to get full access to textbox inside checkbox, because LLCheckBoxCtrl::setLabel() has string as its argument. -// It was introduced while implementing EXT-4706 -class LLCheckBoxWithTBAcess : public LLCheckBoxCtrl -{ -public: - LLTextBox* getTextBox() - { - return mLabel; - } -}; - - -class LLPanelLandExperiences - : public LLPanel -{ -public: - LLPanelLandExperiences(LLSafeHandle& parcelp); - virtual bool postBuild(); - void refresh(); - - void experienceAdded(const LLUUID& id, U32 xp_type, U32 access_type); - void experienceRemoved(const LLUUID& id, U32 access_type); -protected: - LLPanelExperienceListEditor* setupList( const char* control_name, U32 xp_type, U32 access_type ); - void refreshPanel(LLPanelExperienceListEditor* panel, U32 xp_type); - - LLSafeHandle& mParcel; - - - LLPanelExperienceListEditor* mAllowed; - LLPanelExperienceListEditor* mBlocked; -}; - - -class LLPanelLandEnvironment - : public LLPanelEnvironmentInfo -{ -public: - LLPanelLandEnvironment(LLSafeHandle& parcelp); - - virtual bool isRegion() const override { return false; } - virtual bool isLargeEnough() override - { - LLParcel *parcelp = mParcel->getParcel(); - return ((parcelp) ? (parcelp->getArea() >= MINIMUM_PARCEL_SIZE) : false); - } - - virtual bool postBuild() override; - virtual void refresh() override; - - virtual LLParcel * getParcel() override; - - virtual bool canEdit() override; - virtual S32 getParcelId() override; - -protected: - virtual void refreshFromSource() override; - - bool isSameRegion(); - - LLSafeHandle & mParcel; - S32 mLastParcelId; -}; - - -// inserts maturity info(icon and text) into target textbox -// names_floater - pointer to floater which contains strings with maturity icons filenames -// str_to_parse is string in format "txt1[MATURITY]txt2" where maturity icon and text will be inserted instead of [MATURITY] -void insert_maturity_into_textbox(LLTextBox* target_textbox, LLFloater* names_floater, std::string str_to_parse); - -//--------------------------------------------------------------------------- -// LLFloaterLand -//--------------------------------------------------------------------------- - -void send_parcel_select_objects(S32 parcel_local_id, U32 return_type, - uuid_list_t* return_ids = NULL) -{ - LLMessageSystem *msg = gMessageSystem; - - LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); - if (!region) return; - - // Since new highlight will be coming in, drop any highlights - // that exist right now. - LLSelectMgr::getInstance()->unhighlightAll(); - - msg->newMessageFast(_PREHASH_ParcelSelectObjects); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ParcelData); - msg->addS32Fast(_PREHASH_LocalID, parcel_local_id); - msg->addU32Fast(_PREHASH_ReturnType, return_type); - - // Throw all return ids into the packet. - // TODO: Check for too many ids. - if (return_ids) - { - uuid_list_t::iterator end = return_ids->end(); - for (uuid_list_t::iterator it = return_ids->begin(); - it != end; - ++it) - { - msg->nextBlockFast(_PREHASH_ReturnIDs); - msg->addUUIDFast(_PREHASH_ReturnID, (*it)); - } - } - else - { - // Put in a null key so that the message is complete. - msg->nextBlockFast(_PREHASH_ReturnIDs); - msg->addUUIDFast(_PREHASH_ReturnID, LLUUID::null); - } - - msg->sendReliable(region->getHost()); -} - -LLParcel* LLFloaterLand::getCurrentSelectedParcel() -{ - return mParcel->getParcel(); -}; - -//static -LLPanelLandObjects* LLFloaterLand::getCurrentPanelLandObjects() -{ - LLFloaterLand* land_instance = LLFloaterReg::getTypedInstance("about_land"); - if(land_instance) - { - return land_instance->mPanelObjects; - } - else - { - return NULL; - } -} - -//static -LLPanelLandCovenant* LLFloaterLand::getCurrentPanelLandCovenant() -{ - LLFloaterLand* land_instance = LLFloaterReg::getTypedInstance("about_land"); - if(land_instance) - { - return land_instance->mPanelCovenant; - } - else - { - return NULL; - } -} - -// static -void LLFloaterLand::refreshAll() -{ - LLFloaterLand* land_instance = LLFloaterReg::findTypedInstance("about_land"); - if(land_instance) - { - land_instance->refresh(); - } -} - -void LLFloaterLand::onOpen(const LLSD& key) -{ - // moved from triggering show instance in llviwermenu.cpp - - if (LLViewerParcelMgr::getInstance()->selectionEmpty()) - { - LLViewerParcelMgr::getInstance()->selectParcelAt(gAgent.getPositionGlobal()); - } - - // Done automatically when the selected parcel's properties arrive - // (and hence we have the local id). - // LLViewerParcelMgr::getInstance()->sendParcelAccessListRequest(AL_ACCESS | AL_BAN | AL_RENTER); - - mParcel = LLViewerParcelMgr::getInstance()->getFloatingParcelSelection(); - - // Refresh even if not over a region so we don't get an - // uninitialized dialog. The dialog is 0-region aware. - refresh(); -} - -void LLFloaterLand::onVisibilityChanged(const LLSD& visible) -{ - if (!visible.asBoolean()) - { - // Might have been showing owned objects - LLSelectMgr::getInstance()->unhighlightAll(); - - // Save which panel we had open - sLastTab = mTabLand->getCurrentPanelIndex(); - } -} - - -LLFloaterLand::LLFloaterLand(const LLSD& seed) -: LLFloater(seed) -{ - mFactoryMap["land_general_panel"] = LLCallbackMap(createPanelLandGeneral, this); - mFactoryMap["land_covenant_panel"] = LLCallbackMap(createPanelLandCovenant, this); - mFactoryMap["land_objects_panel"] = LLCallbackMap(createPanelLandObjects, this); - mFactoryMap["land_options_panel"] = LLCallbackMap(createPanelLandOptions, this); - mFactoryMap["land_audio_panel"] = LLCallbackMap(createPanelLandAudio, this); - mFactoryMap["land_media_panel"] = LLCallbackMap(createPanelLandMedia, this); - mFactoryMap["land_access_panel"] = LLCallbackMap(createPanelLandAccess, this); - mFactoryMap["land_experiences_panel"] = LLCallbackMap(createPanelLandExperiences, this); - mFactoryMap["land_environment_panel"] = LLCallbackMap(createPanelLandEnvironment, this); - - sObserver = new LLParcelSelectionObserver(); - LLViewerParcelMgr::getInstance()->addObserver( sObserver ); -} - -bool LLFloaterLand::postBuild() -{ - setVisibleCallback(boost::bind(&LLFloaterLand::onVisibilityChanged, this, _2)); - - LLTabContainer* tab = getChild("landtab"); - - mTabLand = (LLTabContainer*) tab; - - if (tab) - { - tab->selectTab(sLastTab); - } - - return true; -} - - -// virtual -LLFloaterLand::~LLFloaterLand() -{ - LLViewerParcelMgr::getInstance()->removeObserver( sObserver ); - delete sObserver; - sObserver = NULL; -} - -// public -void LLFloaterLand::refresh() -{ - mPanelGeneral->refresh(); - mPanelObjects->refresh(); - mPanelOptions->refresh(); - mPanelAudio->refresh(); - mPanelMedia->refresh(); - mPanelAccess->refresh(); - mPanelCovenant->refresh(); - mPanelExperiences->refresh(); - mPanelEnvironment->refresh(); -} - - - -void* LLFloaterLand::createPanelLandGeneral(void* data) -{ - LLFloaterLand* self = (LLFloaterLand*)data; - self->mPanelGeneral = new LLPanelLandGeneral(self->mParcel); - return self->mPanelGeneral; -} - -// static -void* LLFloaterLand::createPanelLandCovenant(void* data) -{ - LLFloaterLand* self = (LLFloaterLand*)data; - self->mPanelCovenant = new LLPanelLandCovenant(self->mParcel); - return self->mPanelCovenant; -} - - -// static -void* LLFloaterLand::createPanelLandObjects(void* data) -{ - LLFloaterLand* self = (LLFloaterLand*)data; - self->mPanelObjects = new LLPanelLandObjects(self->mParcel); - return self->mPanelObjects; -} - -// static -void* LLFloaterLand::createPanelLandOptions(void* data) -{ - LLFloaterLand* self = (LLFloaterLand*)data; - self->mPanelOptions = new LLPanelLandOptions(self->mParcel); - return self->mPanelOptions; -} - -// static -void* LLFloaterLand::createPanelLandAudio(void* data) -{ - LLFloaterLand* self = (LLFloaterLand*)data; - self->mPanelAudio = new LLPanelLandAudio(self->mParcel); - return self->mPanelAudio; -} - -// static -void* LLFloaterLand::createPanelLandMedia(void* data) -{ - LLFloaterLand* self = (LLFloaterLand*)data; - self->mPanelMedia = new LLPanelLandMedia(self->mParcel); - return self->mPanelMedia; -} - -// static -void* LLFloaterLand::createPanelLandAccess(void* data) -{ - LLFloaterLand* self = (LLFloaterLand*)data; - self->mPanelAccess = new LLPanelLandAccess(self->mParcel); - return self->mPanelAccess; -} - -// static -void* LLFloaterLand::createPanelLandExperiences(void* data) -{ - LLFloaterLand* self = (LLFloaterLand*)data; - self->mPanelExperiences = new LLPanelLandExperiences(self->mParcel); - return self->mPanelExperiences; -} - -//static -void* LLFloaterLand::createPanelLandEnvironment(void* data) -{ - LLFloaterLand* self = (LLFloaterLand*)data; - self->mPanelEnvironment = new LLPanelLandEnvironment(self->mParcel); - return self->mPanelEnvironment; -} - - -//--------------------------------------------------------------------------- -// LLPanelLandGeneral -//--------------------------------------------------------------------------- - - -LLPanelLandGeneral::LLPanelLandGeneral(LLParcelSelectionHandle& parcel) -: LLPanel(), - mUncheckedSell(false), - mParcel(parcel) -{ -} - -bool LLPanelLandGeneral::postBuild() -{ - mEditName = getChild("Name"); - mEditName->setCommitCallback(onCommitAny, this); - getChild("Name")->setPrevalidate(LLTextValidate::validateASCIIPrintableNoPipe); - - mEditDesc = getChild("Description"); - mEditDesc->setCommitOnFocusLost(true); - mEditDesc->setCommitCallback(onCommitAny, this); - mEditDesc->setContentTrusted(false); - // No prevalidate function - historically the prevalidate function was broken, - // allowing residents to put in characters like U+2661 WHITE HEART SUIT, so - // preserve that ability. - - mTextSalePending = getChild("SalePending"); - mTextOwnerLabel = getChild("Owner:"); - mTextOwner = getChild("OwnerText"); - mTextOwner->setIsFriendCallback(LLAvatarActions::isFriend); - - mContentRating = getChild("ContentRatingText"); - mLandType = getChild("LandTypeText"); - - mBtnProfile = getChild("Profile..."); - mBtnProfile->setClickedCallback(boost::bind(&LLPanelLandGeneral::onClickProfile, this)); - - - mTextGroupLabel = getChild("Group:"); - mTextGroup = getChild("GroupText"); - - - mBtnSetGroup = getChild("Set..."); - mBtnSetGroup->setCommitCallback(boost::bind(&LLPanelLandGeneral::onClickSetGroup, this)); - - - mCheckDeedToGroup = getChild( "check deed"); - childSetCommitCallback("check deed", onCommitAny, this); - - - mBtnDeedToGroup = getChild("Deed..."); - mBtnDeedToGroup->setClickedCallback(onClickDeed, this); - - - mCheckContributeWithDeed = getChild( "check contrib"); - childSetCommitCallback("check contrib", onCommitAny, this); - - - - mSaleInfoNotForSale = getChild("Not for sale."); - - mSaleInfoForSale1 = getChild("For Sale: Price L$[PRICE]."); - - - mBtnSellLand = getChild("Sell Land..."); - mBtnSellLand->setClickedCallback(onClickSellLand, this); - - mSaleInfoForSale2 = getChild("For sale to"); - - mSaleInfoForSaleObjects = getChild("Sell with landowners objects in parcel."); - - mSaleInfoForSaleNoObjects = getChild("Selling with no objects in parcel."); - - - mBtnStopSellLand = getChild("Cancel Land Sale"); - mBtnStopSellLand->setClickedCallback(onClickStopSellLand, this); - - - mTextClaimDateLabel = getChild("Claimed:"); - mTextClaimDate = getChild("DateClaimText"); - - - mTextPriceLabel = getChild("PriceLabel"); - mTextPrice = getChild("PriceText"); - - - mTextDwell = getChild("DwellText"); - - mBtnBuyLand = getChild("Buy Land..."); - mBtnBuyLand->setClickedCallback(onClickBuyLand, (void*)&BUY_PERSONAL_LAND); - - - mBtnBuyGroupLand = getChild("Buy For Group..."); - mBtnBuyGroupLand->setClickedCallback(onClickBuyLand, (void*)&BUY_GROUP_LAND); - - - mBtnBuyPass = getChild("Buy Pass..."); - mBtnBuyPass->setClickedCallback(onClickBuyPass, this); - - mBtnReleaseLand = getChild("Abandon Land..."); - mBtnReleaseLand->setClickedCallback(onClickRelease, NULL); - - mBtnReclaimLand = getChild("Reclaim Land..."); - mBtnReclaimLand->setClickedCallback(onClickReclaim, NULL); - - mBtnStartAuction = getChild("Linden Sale..."); - mBtnStartAuction->setClickedCallback(onClickStartAuction, this); - - mBtnScriptLimits = getChild("Scripts..."); - - if(gDisconnected) - { - return true; - } - - // note: on region change this will not be re checked, should not matter on Agni as - // 99% of the time all regions will return the same caps. In case of an erroneous setting - // to enabled the floater will just throw an error when trying to get it's cap - std::string url = gAgent.getRegionCapability("LandResources"); - if (!url.empty()) - { - if(mBtnScriptLimits) - { - mBtnScriptLimits->setClickedCallback(onClickScriptLimits, this); - } - } - else - { - if(mBtnScriptLimits) - { - mBtnScriptLimits->setVisible(false); - } - } - - return true; -} - - -// virtual -LLPanelLandGeneral::~LLPanelLandGeneral() -{ } - - -// public -void LLPanelLandGeneral::refresh() -{ - mEditName->setEnabled(false); - mEditName->setText(LLStringUtil::null); - - mEditDesc->setEnabled(false); - mEditDesc->setText(getString("no_selection_text")); - - mTextSalePending->setText(LLStringUtil::null); - mTextSalePending->setEnabled(false); - - mBtnDeedToGroup->setEnabled(false); - mBtnSetGroup->setEnabled(false); - mBtnStartAuction->setEnabled(false); - - mCheckDeedToGroup ->set(false); - mCheckDeedToGroup ->setEnabled(false); - mCheckContributeWithDeed->set(false); - mCheckContributeWithDeed->setEnabled(false); - - mTextOwner->setText(LLStringUtil::null); - mContentRating->setText(LLStringUtil::null); - mLandType->setText(LLStringUtil::null); - mBtnProfile->setLabel(getString("profile_text")); - mBtnProfile->setEnabled(false); - - mTextClaimDate->setText(LLStringUtil::null); - mTextGroup->setText(LLStringUtil::null); - mTextPrice->setText(LLStringUtil::null); - - mSaleInfoForSale1->setVisible(false); - mSaleInfoForSale2->setVisible(false); - mSaleInfoForSaleObjects->setVisible(false); - mSaleInfoForSaleNoObjects->setVisible(false); - mSaleInfoNotForSale->setVisible(false); - mBtnSellLand->setVisible(false); - mBtnStopSellLand->setVisible(false); - - mTextPriceLabel->setText(LLStringUtil::null); - mTextDwell->setText(LLStringUtil::null); - - mBtnBuyLand->setEnabled(false); - mBtnScriptLimits->setEnabled(false); - mBtnBuyGroupLand->setEnabled(false); - mBtnReleaseLand->setEnabled(false); - mBtnReclaimLand->setEnabled(false); - mBtnBuyPass->setEnabled(false); - - if(gDisconnected) - { - return; - } - - mBtnStartAuction->setVisible(gAgent.isGodlike()); - bool region_owner = false; - LLViewerRegion* regionp = LLViewerParcelMgr::getInstance()->getSelectionRegion(); - if(regionp && (regionp->getOwner() == gAgent.getID())) - { - region_owner = true; - mBtnReleaseLand->setVisible(false); - mBtnReclaimLand->setVisible(true); - } - else - { - mBtnReleaseLand->setVisible(true); - mBtnReclaimLand->setVisible(false); - } - LLParcel *parcel = mParcel->getParcel(); - if (parcel) - { - // something selected, hooray! - bool is_leased = (LLParcel::OS_LEASED == parcel->getOwnershipStatus()); - bool region_xfer = false; - if(regionp - && !(regionp->getRegionFlag(REGION_FLAGS_BLOCK_LAND_RESELL))) - { - region_xfer = true; - } - - if (regionp) - { - insert_maturity_into_textbox(mContentRating, gFloaterView->getParentFloater(this), MATURITY); - mLandType->setText(regionp->getLocalizedSimProductName()); - } - - // estate owner/manager cannot edit other parts of the parcel - bool estate_manager_sellable = !parcel->getAuctionID() - && gAgent.canManageEstate() - // estate manager/owner can only sell parcels owned by estate owner - && regionp - && (parcel->getOwnerID() == regionp->getOwner()); - bool owner_sellable = region_xfer && !parcel->getAuctionID() - && LLViewerParcelMgr::isParcelModifiableByAgent( - parcel, GP_LAND_SET_SALE_INFO); - bool can_be_sold = owner_sellable || estate_manager_sellable; - - const LLUUID &owner_id = parcel->getOwnerID(); - bool is_public = parcel->isPublic(); - - // Is it owned? - if (is_public) - { - mTextSalePending->setText(LLStringUtil::null); - mTextSalePending->setEnabled(false); - mTextOwner->setText(getString("public_text")); - mTextOwner->setEnabled(false); - mBtnProfile->setEnabled(false); - mTextClaimDate->setText(LLStringUtil::null); - mTextClaimDate->setEnabled(false); - mTextGroup->setText(getString("none_text")); - mTextGroup->setEnabled(false); - mBtnStartAuction->setEnabled(false); - } - else - { - if(!is_leased && (owner_id == gAgent.getID())) - { - mTextSalePending->setText(getString("need_tier_to_modify")); - mTextSalePending->setEnabled(true); - } - else if(parcel->getAuctionID()) - { - mTextSalePending->setText(getString("auction_id_text")); - mTextSalePending->setTextArg("[ID]", llformat("%u", parcel->getAuctionID())); - mTextSalePending->setEnabled(true); - } - else - { - // not the owner, or it is leased - mTextSalePending->setText(LLStringUtil::null); - mTextSalePending->setEnabled(false); - } - //refreshNames(); - mTextOwner->setEnabled(true); - - // We support both group and personal profiles - mBtnProfile->setEnabled(true); - - if (parcel->getGroupID().isNull()) - { - // Not group owned, so "Profile" - mBtnProfile->setLabel(getString("profile_text")); - - mTextGroup->setText(getString("none_text")); - mTextGroup->setEnabled(false); - } - else - { - // Group owned, so "Info" - mBtnProfile->setLabel(getString("info_text")); - - //mTextGroup->setText("HIPPOS!");//parcel->getGroupName()); - mTextGroup->setEnabled(true); - } - - // Display claim date - time_t claim_date = parcel->getClaimDate(); - std::string claim_date_str = getString("time_stamp_template"); - LLSD substitution; - substitution["datetime"] = (S32) claim_date; - LLStringUtil::format (claim_date_str, substitution); - mTextClaimDate->setText(claim_date_str); - mTextClaimDate->setEnabled(is_leased); - - bool enable_auction = (gAgent.getGodLevel() >= GOD_LIAISON) - && (owner_id == GOVERNOR_LINDEN_ID) - && (parcel->getAuctionID() == 0); - mBtnStartAuction->setEnabled(enable_auction); - } - - // Display options - bool can_edit_identity = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_CHANGE_IDENTITY); - mEditName->setEnabled(can_edit_identity); - mEditDesc->setEnabled(can_edit_identity); - mEditDesc->setParseURLs(!can_edit_identity); - - bool can_edit_agent_only = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_NO_POWERS); - mBtnSetGroup->setEnabled(can_edit_agent_only && !parcel->getIsGroupOwned()); - - const LLUUID& group_id = parcel->getGroupID(); - - // Can only allow deeding if you own it and it's got a group. - bool enable_deed = (owner_id == gAgent.getID() - && group_id.notNull() - && gAgent.isInGroup(group_id)); - // You don't need special powers to allow your object to - // be deeded to the group. - mCheckDeedToGroup->setEnabled(enable_deed); - mCheckDeedToGroup->set( parcel->getAllowDeedToGroup() ); - mCheckContributeWithDeed->setEnabled(enable_deed && parcel->getAllowDeedToGroup()); - mCheckContributeWithDeed->set(parcel->getContributeWithDeed()); - - // Actually doing the deeding requires you to have GP_LAND_DEED - // powers in the group. - bool can_deed = gAgent.hasPowerInGroup(group_id, GP_LAND_DEED); - mBtnDeedToGroup->setEnabled( parcel->getAllowDeedToGroup() - && group_id.notNull() - && can_deed - && !parcel->getIsGroupOwned() - ); - - mEditName->setText( parcel->getName() ); - mEditDesc->setText( parcel->getDesc() ); - - bool for_sale = parcel->getForSale(); - - mBtnSellLand->setVisible(false); - mBtnStopSellLand->setVisible(false); - - // show pricing information - S32 area; - S32 claim_price; - S32 rent_price; - F32 dwell = DWELL_NAN; - LLViewerParcelMgr::getInstance()->getDisplayInfo(&area, - &claim_price, - &rent_price, - &for_sale, - &dwell); - // Area - LLUIString price = getString("area_size_text"); - price.setArg("[AREA]", llformat("%d",area)); - mTextPriceLabel->setText(getString("area_text")); - mTextPrice->setText(price.getString()); - - if (dwell == DWELL_NAN) - { - mTextDwell->setText(LLTrans::getString("LoadingData")); - } - else - { - mTextDwell->setText(llformat("%.0f", dwell)); - } - - if (for_sale) - { - mSaleInfoForSale1->setVisible(true); - mSaleInfoForSale2->setVisible(true); - if (parcel->getSellWithObjects()) - { - mSaleInfoForSaleObjects->setVisible(true); - mSaleInfoForSaleNoObjects->setVisible(false); - } - else - { - mSaleInfoForSaleObjects->setVisible(false); - mSaleInfoForSaleNoObjects->setVisible(true); - } - mSaleInfoNotForSale->setVisible(false); - - F32 cost_per_sqm = 0.0f; - if (area > 0) - { - cost_per_sqm = (F32)parcel->getSalePrice() / (F32)area; - } - - S32 price = parcel->getSalePrice(); - mSaleInfoForSale1->setTextArg("[PRICE]", LLResMgr::getInstance()->getMonetaryString(price)); - mSaleInfoForSale1->setTextArg("[PRICE_PER_SQM]", llformat("%.1f", cost_per_sqm)); - if (can_be_sold) - { - mBtnStopSellLand->setVisible(true); - } - } - else - { - mSaleInfoForSale1->setVisible(false); - mSaleInfoForSale2->setVisible(false); - mSaleInfoForSaleObjects->setVisible(false); - mSaleInfoForSaleNoObjects->setVisible(false); - mSaleInfoNotForSale->setVisible(true); - if (can_be_sold) - { - mBtnSellLand->setVisible(true); - } - } - - refreshNames(); - - mBtnBuyLand->setEnabled( - LLViewerParcelMgr::getInstance()->canAgentBuyParcel(parcel, false)); - mBtnScriptLimits->setEnabled(true); -// LLViewerParcelMgr::getInstance()->canAgentBuyParcel(parcel, false)); - mBtnBuyGroupLand->setEnabled( - LLViewerParcelMgr::getInstance()->canAgentBuyParcel(parcel, true)); - - if(region_owner) - { - mBtnReclaimLand->setEnabled( - !is_public && (parcel->getOwnerID() != gAgent.getID())); - } - else - { - bool is_owner_release = LLViewerParcelMgr::isParcelOwnedByAgent(parcel, GP_LAND_RELEASE); - bool is_manager_release = (gAgent.canManageEstate() && - regionp && - (parcel->getOwnerID() != regionp->getOwner())); - bool can_release = is_owner_release || is_manager_release; - mBtnReleaseLand->setEnabled( can_release ); - } - - bool use_pass = parcel->getOwnerID()!= gAgent.getID() && parcel->getParcelFlag(PF_USE_PASS_LIST) && !LLViewerParcelMgr::getInstance()->isCollisionBanned();; - mBtnBuyPass->setEnabled(use_pass); - - } -} - -// public -void LLPanelLandGeneral::refreshNames() -{ - LLParcel *parcel = mParcel->getParcel(); - if (!parcel) - { - mTextOwner->setText(LLStringUtil::null); - mTextGroup->setText(LLStringUtil::null); - return; - } - - std::string owner; - if (parcel->getIsGroupOwned()) - { - owner = getString("group_owned_text"); - } - else - { - // Figure out the owner's name - owner = LLSLURL("agent", parcel->getOwnerID(), "inspect").getSLURLString(); - } - - if(LLParcel::OS_LEASE_PENDING == parcel->getOwnershipStatus()) - { - owner += getString("sale_pending_text"); - } - mTextOwner->setText(owner); - - std::string group; - if (!parcel->getGroupID().isNull()) - { - group = LLSLURL("group", parcel->getGroupID(), "inspect").getSLURLString(); - } - mTextGroup->setText(group); - - if (parcel->getForSale()) - { - const LLUUID& auth_buyer_id = parcel->getAuthorizedBuyerID(); - if(auth_buyer_id.notNull()) - { - std::string name; - name = LLSLURL("agent", auth_buyer_id, "inspect").getSLURLString(); - mSaleInfoForSale2->setTextArg("[BUYER]", name); - } - else - { - mSaleInfoForSale2->setTextArg("[BUYER]", getString("anyone")); - } - } -} - - -// virtual -void LLPanelLandGeneral::draw() -{ - LLPanel::draw(); -} - -void LLPanelLandGeneral::onClickSetGroup() -{ - LLFloater* parent_floater = gFloaterView->getParentFloater(this); - - LLFloaterGroupPicker* fg = LLFloaterReg::showTypedInstance("group_picker", LLSD(gAgent.getID())); - if (fg) - { - fg->setSelectGroupCallback( boost::bind(&LLPanelLandGeneral::setGroup, this, _1 )); - if (parent_floater) - { - LLRect new_rect = gFloaterView->findNeighboringPosition(parent_floater, fg); - fg->setOrigin(new_rect.mLeft, new_rect.mBottom); - parent_floater->addDependentFloater(fg); - } - } -} - -void LLPanelLandGeneral::onClickProfile() -{ - LLParcel* parcel = mParcel->getParcel(); - if (!parcel) return; - - if (parcel->getIsGroupOwned()) - { - const LLUUID& group_id = parcel->getGroupID(); - LLGroupActions::show(group_id); - } - else - { - const LLUUID& avatar_id = parcel->getOwnerID(); - LLAvatarActions::showProfile(avatar_id); - } -} - -// public -void LLPanelLandGeneral::setGroup(const LLUUID& group_id) -{ - LLParcel* parcel = mParcel->getParcel(); - if (!parcel) return; - - // Set parcel properties and send message - parcel->setGroupID(group_id); - //parcel->setGroupName(group_name); - //mTextGroup->setText(group_name); - - // Send update - LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate(parcel); - - // Update UI - refresh(); -} - -// static -void LLPanelLandGeneral::onClickBuyLand(void* data) -{ - bool* for_group = (bool*)data; - LLViewerParcelMgr::getInstance()->startBuyLand(*for_group); -} - -// static -void LLPanelLandGeneral::onClickScriptLimits(void* data) -{ - LLPanelLandGeneral* panelp = (LLPanelLandGeneral*)data; - LLParcel* parcel = panelp->mParcel->getParcel(); - if(parcel != NULL) - { - LLFloaterReg::showInstance("script_limits"); - } -} - -// static -void LLPanelLandGeneral::onClickDeed(void*) -{ - //LLParcel* parcel = mParcel->getParcel(); - //if (parcel) - //{ - LLViewerParcelMgr::getInstance()->startDeedLandToGroup(); - //} -} - -// static -void LLPanelLandGeneral::onClickRelease(void*) -{ - LLViewerParcelMgr::getInstance()->startReleaseLand(); -} - -// static -void LLPanelLandGeneral::onClickReclaim(void*) -{ - LL_DEBUGS() << "LLPanelLandGeneral::onClickReclaim()" << LL_ENDL; - LLViewerParcelMgr::getInstance()->reclaimParcel(); -} - -// static -bool LLPanelLandGeneral::enableBuyPass(void* data) -{ - LLPanelLandGeneral* panelp = (LLPanelLandGeneral*)data; - LLParcel* parcel = panelp != NULL ? panelp->mParcel->getParcel() : LLViewerParcelMgr::getInstance()->getParcelSelection()->getParcel(); - return (parcel != NULL) && (parcel->getParcelFlag(PF_USE_PASS_LIST) && !LLViewerParcelMgr::getInstance()->isCollisionBanned()); -} - - -// static -void LLPanelLandGeneral::onClickBuyPass(void* data) -{ - LLPanelLandGeneral* panelp = (LLPanelLandGeneral*)data; - LLParcel* parcel = panelp != NULL ? panelp->mParcel->getParcel() : LLViewerParcelMgr::getInstance()->getParcelSelection()->getParcel(); - - if (!parcel) return; - - S32 pass_price = parcel->getPassPrice(); - std::string parcel_name = parcel->getName(); - F32 pass_hours = parcel->getPassHours(); - - std::string cost, time; - cost = llformat("%d", pass_price); - time = llformat("%.2f", pass_hours); - - LLSD args; - args["COST"] = cost; - args["PARCEL_NAME"] = parcel_name; - args["TIME"] = time; - - // creating pointer on selection to avoid deselection of parcel until we are done with buying pass (EXT-6464) - sSelectionForBuyPass = LLViewerParcelMgr::getInstance()->getParcelSelection(); - LLNotificationsUtil::add("LandBuyPass", args, LLSD(), cbBuyPass); -} - -// static -void LLPanelLandGeneral::onClickStartAuction(void* data) -{ - LLPanelLandGeneral* panelp = (LLPanelLandGeneral*)data; - LLParcel* parcelp = panelp->mParcel->getParcel(); - if(parcelp) - { - if(parcelp->getForSale()) - { - LLNotificationsUtil::add("CannotStartAuctionAlreadyForSale"); - } - else - { - //LLFloaterAuction::showInstance(); - LLFloaterReg::showInstance("auction"); - } - } -} - -// static -bool LLPanelLandGeneral::cbBuyPass(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (0 == option) - { - // User clicked OK - LLViewerParcelMgr::getInstance()->buyPass(); - } - // we are done with buying pass, additional selection is no longer needed - sSelectionForBuyPass = NULL; - return false; -} - -// static -void LLPanelLandGeneral::onCommitAny(LLUICtrl *ctrl, void *userdata) -{ - LLPanelLandGeneral *panelp = (LLPanelLandGeneral *)userdata; - - LLParcel* parcel = panelp->mParcel->getParcel(); - if (!parcel) - { - return; - } - - // Extract data from UI - std::string name = panelp->mEditName->getText(); - std::string desc = panelp->mEditDesc->getText(); - - // Valid data from UI - - // Stuff data into selected parcel - parcel->setName(name); - parcel->setDesc(desc); - - bool allow_deed_to_group= panelp->mCheckDeedToGroup->get(); - bool contribute_with_deed = panelp->mCheckContributeWithDeed->get(); - - parcel->setParcelFlag(PF_ALLOW_DEED_TO_GROUP, allow_deed_to_group); - parcel->setContributeWithDeed(contribute_with_deed); - - // Send update to server - LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate( parcel ); - - // Might have changed properties, so let's redraw! - panelp->refresh(); -} - -// static -void LLPanelLandGeneral::onClickSellLand(void* data) -{ - LLViewerParcelMgr::getInstance()->startSellLand(); - LLPanelLandGeneral *panelp = (LLPanelLandGeneral *)data; - panelp->refresh(); -} - -// static -void LLPanelLandGeneral::onClickStopSellLand(void* data) -{ - LLPanelLandGeneral* panelp = (LLPanelLandGeneral*)data; - LLParcel* parcel = panelp->mParcel->getParcel(); - - parcel->setParcelFlag(PF_FOR_SALE, false); - parcel->setSalePrice(0); - parcel->setAuthorizedBuyerID(LLUUID::null); - - LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate(parcel); -} - -//--------------------------------------------------------------------------- -// LLPanelLandObjects -//--------------------------------------------------------------------------- -LLPanelLandObjects::LLPanelLandObjects(LLParcelSelectionHandle& parcel) - : LLPanel(), - - mParcel(parcel), - mParcelObjectBonus(NULL), - mSWTotalObjects(NULL), - mObjectContribution(NULL), - mTotalObjects(NULL), - mOwnerObjects(NULL), - mBtnShowOwnerObjects(NULL), - mBtnReturnOwnerObjects(NULL), - mGroupObjects(NULL), - mBtnShowGroupObjects(NULL), - mBtnReturnGroupObjects(NULL), - mOtherObjects(NULL), - mBtnShowOtherObjects(NULL), - mBtnReturnOtherObjects(NULL), - mSelectedObjects(NULL), - mCleanOtherObjectsTime(NULL), - mOtherTime(0), - mBtnRefresh(NULL), - mBtnReturnOwnerList(NULL), - mOwnerList(NULL), - mFirstReply(true), - mSelectedCount(0), - mSelectedIsGroup(false) -{ -} - - - -bool LLPanelLandObjects::postBuild() -{ - - mFirstReply = true; - mParcelObjectBonus = getChild("parcel_object_bonus"); - mSWTotalObjects = getChild("objects_available"); - mObjectContribution = getChild("object_contrib_text"); - mTotalObjects = getChild("total_objects_text"); - mOwnerObjects = getChild("owner_objects_text"); - - mBtnShowOwnerObjects = getChild("ShowOwner"); - mBtnShowOwnerObjects->setClickedCallback(onClickShowOwnerObjects, this); - - mBtnReturnOwnerObjects = getChild("ReturnOwner..."); - mBtnReturnOwnerObjects->setClickedCallback(onClickReturnOwnerObjects, this); - - mGroupObjects = getChild("group_objects_text"); - mBtnShowGroupObjects = getChild("ShowGroup"); - mBtnShowGroupObjects->setClickedCallback(onClickShowGroupObjects, this); - - mBtnReturnGroupObjects = getChild("ReturnGroup..."); - mBtnReturnGroupObjects->setClickedCallback(onClickReturnGroupObjects, this); - - mOtherObjects = getChild("other_objects_text"); - mBtnShowOtherObjects = getChild("ShowOther"); - mBtnShowOtherObjects->setClickedCallback(onClickShowOtherObjects, this); - - mBtnReturnOtherObjects = getChild("ReturnOther..."); - mBtnReturnOtherObjects->setClickedCallback(onClickReturnOtherObjects, this); - - mSelectedObjects = getChild("selected_objects_text"); - mCleanOtherObjectsTime = getChild("clean other time"); - - mCleanOtherObjectsTime->setFocusLostCallback(boost::bind(onLostFocus, _1, this)); - mCleanOtherObjectsTime->setCommitCallback(onCommitClean, this); - getChild("clean other time")->setPrevalidate(LLTextValidate::validateNonNegativeS32); - - mBtnRefresh = getChild("Refresh List"); - mBtnRefresh->setClickedCallback(onClickRefresh, this); - - mBtnReturnOwnerList = getChild("Return objects..."); - mBtnReturnOwnerList->setClickedCallback(onClickReturnOwnerList, this); - - mIconAvatarOnline = LLUIImageList::getInstance()->getUIImage("icon_avatar_online.tga", 0); - mIconAvatarOffline = LLUIImageList::getInstance()->getUIImage("icon_avatar_offline.tga", 0); - mIconGroup = LLUIImageList::getInstance()->getUIImage("icon_group.tga", 0); - - mOwnerList = getChild("owner list"); - mOwnerList->setIsFriendCallback(LLAvatarActions::isFriend); - mOwnerList->sortByColumnIndex(3, false); - childSetCommitCallback("owner list", onCommitList, this); - mOwnerList->setDoubleClickCallback(onDoubleClickOwner, this); - mOwnerList->setContextMenu(LLScrollListCtrl::MENU_AVATAR); - - return true; -} - - - - -// virtual -LLPanelLandObjects::~LLPanelLandObjects() -{ } - -// static -void LLPanelLandObjects::onDoubleClickOwner(void *userdata) -{ - LLPanelLandObjects *self = (LLPanelLandObjects *)userdata; - - LLScrollListItem* item = self->mOwnerList->getFirstSelected(); - if (item) - { - LLUUID owner_id = item->getUUID(); - // Look up the selected name, for future dialog box use. - const LLScrollListCell* cell; - cell = item->getColumn(1); - if (!cell) - { - return; - } - // Is this a group? - bool is_group = cell->getValue().asString() == OWNER_GROUP; - if (is_group) - { - LLGroupActions::show(owner_id); - } - else - { - LLAvatarActions::showProfile(owner_id); - } - } -} - -// public -void LLPanelLandObjects::refresh() -{ - LLParcel *parcel = mParcel->getParcel(); - - mBtnShowOwnerObjects->setEnabled(false); - mBtnShowGroupObjects->setEnabled(false); - mBtnShowOtherObjects->setEnabled(false); - mBtnReturnOwnerObjects->setEnabled(false); - mBtnReturnGroupObjects->setEnabled(false); - mBtnReturnOtherObjects->setEnabled(false); - mCleanOtherObjectsTime->setEnabled(false); - mBtnRefresh-> setEnabled(false); - mBtnReturnOwnerList-> setEnabled(false); - - mSelectedOwners.clear(); - mOwnerList->deleteAllItems(); - mOwnerList->setEnabled(false); - - if (!parcel || gDisconnected) - { - mSWTotalObjects->setTextArg("[COUNT]", llformat("%d", 0)); - mSWTotalObjects->setTextArg("[TOTAL]", llformat("%d", 0)); - mSWTotalObjects->setTextArg("[AVAILABLE]", llformat("%d", 0)); - mObjectContribution->setTextArg("[COUNT]", llformat("%d", 0)); - mTotalObjects->setTextArg("[COUNT]", llformat("%d", 0)); - mOwnerObjects->setTextArg("[COUNT]", llformat("%d", 0)); - mGroupObjects->setTextArg("[COUNT]", llformat("%d", 0)); - mOtherObjects->setTextArg("[COUNT]", llformat("%d", 0)); - mSelectedObjects->setTextArg("[COUNT]", llformat("%d", 0)); - } - else - { - S32 sw_max = parcel->getSimWideMaxPrimCapacity(); - S32 sw_total = parcel->getSimWidePrimCount(); - S32 max = ll_round(parcel->getMaxPrimCapacity() * parcel->getParcelPrimBonus()); - S32 total = parcel->getPrimCount(); - S32 owned = parcel->getOwnerPrimCount(); - S32 group = parcel->getGroupPrimCount(); - S32 other = parcel->getOtherPrimCount(); - S32 selected = parcel->getSelectedPrimCount(); - F32 parcel_object_bonus = parcel->getParcelPrimBonus(); - mOtherTime = parcel->getCleanOtherTime(); - - // Can't have more than region max tasks, regardless of parcel - // object bonus factor. - LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); - if (region) - { - S32 max_tasks_per_region = (S32)region->getMaxTasks(); - sw_max = llmin(sw_max, max_tasks_per_region); - max = llmin(max, max_tasks_per_region); - } - - if (parcel_object_bonus != 1.0f) - { - mParcelObjectBonus->setVisible(true); - mParcelObjectBonus->setTextArg("[BONUS]", llformat("%.2f", parcel_object_bonus)); - } - else - { - mParcelObjectBonus->setVisible(false); - } - - if (sw_total > sw_max) - { - mSWTotalObjects->setText(getString("objects_deleted_text")); - mSWTotalObjects->setTextArg("[DELETED]", llformat("%d", sw_total - sw_max)); - } - else - { - mSWTotalObjects->setText(getString("objects_available_text")); - mSWTotalObjects->setTextArg("[AVAILABLE]", llformat("%d", sw_max - sw_total)); - } - mSWTotalObjects->setTextArg("[COUNT]", llformat("%d", sw_total)); - mSWTotalObjects->setTextArg("[MAX]", llformat("%d", sw_max)); - - mObjectContribution->setTextArg("[COUNT]", llformat("%d", max)); - mTotalObjects->setTextArg("[COUNT]", llformat("%d", total)); - mOwnerObjects->setTextArg("[COUNT]", llformat("%d", owned)); - mGroupObjects->setTextArg("[COUNT]", llformat("%d", group)); - mOtherObjects->setTextArg("[COUNT]", llformat("%d", other)); - mSelectedObjects->setTextArg("[COUNT]", llformat("%d", selected)); - mCleanOtherObjectsTime->setText(llformat("%d", mOtherTime)); - - bool can_return_owned = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_RETURN_GROUP_OWNED); - bool can_return_group_set = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_RETURN_GROUP_SET); - bool can_return_other = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_RETURN_NON_GROUP); - - if (can_return_owned || can_return_group_set || can_return_other) - { - if (owned && can_return_owned) - { - mBtnShowOwnerObjects->setEnabled(true); - mBtnReturnOwnerObjects->setEnabled(true); - } - if (group && can_return_group_set) - { - mBtnShowGroupObjects->setEnabled(true); - mBtnReturnGroupObjects->setEnabled(true); - } - if (other && can_return_other) - { - mBtnShowOtherObjects->setEnabled(true); - mBtnReturnOtherObjects->setEnabled(true); - } - - mCleanOtherObjectsTime->setEnabled(true); - mBtnRefresh->setEnabled(true); - } - } -} - -// virtual -void LLPanelLandObjects::draw() -{ - LLPanel::draw(); -} - -void send_other_clean_time_message(S32 parcel_local_id, S32 other_clean_time) -{ - LLMessageSystem *msg = gMessageSystem; - - LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); - if (!region) return; - - msg->newMessageFast(_PREHASH_ParcelSetOtherCleanTime); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ParcelData); - msg->addS32Fast(_PREHASH_LocalID, parcel_local_id); - msg->addS32Fast(_PREHASH_OtherCleanTime, other_clean_time); - - msg->sendReliable(region->getHost()); -} - -void send_return_objects_message(S32 parcel_local_id, S32 return_type, - uuid_list_t* owner_ids = NULL) -{ - LLMessageSystem *msg = gMessageSystem; - - LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); - if (!region) return; - - msg->newMessageFast(_PREHASH_ParcelReturnObjects); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ParcelData); - msg->addS32Fast(_PREHASH_LocalID, parcel_local_id); - msg->addU32Fast(_PREHASH_ReturnType, (U32) return_type); - - // Dummy task id, not used - msg->nextBlock("TaskIDs"); - msg->addUUID("TaskID", LLUUID::null); - - // Throw all return ids into the packet. - // TODO: Check for too many ids. - if (owner_ids) - { - uuid_list_t::iterator end = owner_ids->end(); - for (uuid_list_t::iterator it = owner_ids->begin(); - it != end; - ++it) - { - msg->nextBlockFast(_PREHASH_OwnerIDs); - msg->addUUIDFast(_PREHASH_OwnerID, (*it)); - } - } - else - { - msg->nextBlockFast(_PREHASH_OwnerIDs); - msg->addUUIDFast(_PREHASH_OwnerID, LLUUID::null); - } - - msg->sendReliable(region->getHost()); -} - -bool LLPanelLandObjects::callbackReturnOwnerObjects(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - LLParcel *parcel = mParcel->getParcel(); - if (0 == option) - { - if (parcel) - { - LLUUID owner_id = parcel->getOwnerID(); - LLSD args; - if (owner_id == gAgentID) - { - LLNotificationsUtil::add("OwnedObjectsReturned"); - } - else - { - args["NAME"] = LLSLURL("agent", owner_id, "completename").getSLURLString(); - LLNotificationsUtil::add("OtherObjectsReturned", args); - } - send_return_objects_message(parcel->getLocalID(), RT_OWNER); - } - } - - LLSelectMgr::getInstance()->unhighlightAll(); - LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate( parcel ); - refresh(); - return false; -} - -bool LLPanelLandObjects::callbackReturnGroupObjects(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - LLParcel *parcel = mParcel->getParcel(); - if (0 == option) - { - if (parcel) - { - std::string group_name; - gCacheName->getGroupName(parcel->getGroupID(), group_name); - LLSD args; - args["GROUPNAME"] = group_name; - LLNotificationsUtil::add("GroupObjectsReturned", args); - send_return_objects_message(parcel->getLocalID(), RT_GROUP); - } - } - LLSelectMgr::getInstance()->unhighlightAll(); - LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate( parcel ); - refresh(); - return false; -} - -bool LLPanelLandObjects::callbackReturnOtherObjects(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - LLParcel *parcel = mParcel->getParcel(); - if (0 == option) - { - if (parcel) - { - LLNotificationsUtil::add("UnOwnedObjectsReturned"); - send_return_objects_message(parcel->getLocalID(), RT_OTHER); - } - } - LLSelectMgr::getInstance()->unhighlightAll(); - LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate( parcel ); - refresh(); - return false; -} - -bool LLPanelLandObjects::callbackReturnOwnerList(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - LLParcel *parcel = mParcel->getParcel(); - if (0 == option) - { - if (parcel) - { - // Make sure we have something selected. - uuid_list_t::iterator selected = mSelectedOwners.begin(); - if (selected != mSelectedOwners.end()) - { - LLSD args; - if (mSelectedIsGroup) - { - args["GROUPNAME"] = mSelectedName; - LLNotificationsUtil::add("GroupObjectsReturned", args); - } - else - { - args["NAME"] = mSelectedName; - LLNotificationsUtil::add("OtherObjectsReturned2", args); - } - - send_return_objects_message(parcel->getLocalID(), RT_LIST, &(mSelectedOwners)); - } - } - } - LLSelectMgr::getInstance()->unhighlightAll(); - LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate( parcel ); - refresh(); - return false; -} - - -// static -void LLPanelLandObjects::onClickReturnOwnerList(void* userdata) -{ - LLPanelLandObjects *self = (LLPanelLandObjects *)userdata; - - LLParcel* parcelp = self->mParcel->getParcel(); - if (!parcelp) return; - - // Make sure we have something selected. - if (self->mSelectedOwners.empty()) - { - return; - } - //uuid_list_t::iterator selected_itr = self->mSelectedOwners.begin(); - //if (selected_itr == self->mSelectedOwners.end()) return; - - send_parcel_select_objects(parcelp->getLocalID(), RT_LIST, &(self->mSelectedOwners)); - - LLSD args; - args["NAME"] = self->mSelectedName; - args["N"] = llformat("%d",self->mSelectedCount); - if (self->mSelectedIsGroup) - { - LLNotificationsUtil::add("ReturnObjectsDeededToGroup", args, LLSD(), boost::bind(&LLPanelLandObjects::callbackReturnOwnerList, self, _1, _2)); - } - else - { - LLNotificationsUtil::add("ReturnObjectsOwnedByUser", args, LLSD(), boost::bind(&LLPanelLandObjects::callbackReturnOwnerList, self, _1, _2)); - } -} - - -// static -void LLPanelLandObjects::onClickRefresh(void* userdata) -{ - LLPanelLandObjects *self = (LLPanelLandObjects*)userdata; - - LLMessageSystem *msg = gMessageSystem; - - LLParcel* parcel = self->mParcel->getParcel(); - if (!parcel) return; - - LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); - if (!region) return; - - self->mBtnRefresh->setEnabled(false); - - // ready the list for results - self->mOwnerList->deleteAllItems(); - self->mOwnerList->setCommentText(LLTrans::getString("Searching")); - self->mOwnerList->setEnabled(false); - self->mFirstReply = true; - - // send the message - msg->newMessageFast(_PREHASH_ParcelObjectOwnersRequest); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ParcelData); - msg->addS32Fast(_PREHASH_LocalID, parcel->getLocalID()); - - msg->sendReliable(region->getHost()); -} - -// static -void LLPanelLandObjects::processParcelObjectOwnersReply(LLMessageSystem *msg, void **) -{ - LLPanelLandObjects* self = LLFloaterLand::getCurrentPanelLandObjects(); - - if (!self) - { - LL_WARNS() << "Received message for nonexistent LLPanelLandObject" - << LL_ENDL; - return; - } - - const LLFontGL* FONT = LLFontGL::getFontSansSerif(); - - // Extract all of the owners. - S32 rows = msg->getNumberOfBlocksFast(_PREHASH_Data); - //uuid_list_t return_ids; - LLUUID owner_id; - bool is_group_owned; - S32 object_count; - U32 most_recent_time = 0; - bool is_online; - std::string object_count_str; - //bool b_need_refresh = false; - - // If we were waiting for the first reply, clear the "Searching..." text. - if (self->mFirstReply) - { - self->mOwnerList->deleteAllItems(); - self->mFirstReply = false; - } - - for(S32 i = 0; i < rows; ++i) - { - msg->getUUIDFast(_PREHASH_Data, _PREHASH_OwnerID, owner_id, i); - msg->getBOOLFast(_PREHASH_Data, _PREHASH_IsGroupOwned, is_group_owned, i); - msg->getS32Fast (_PREHASH_Data, _PREHASH_Count, object_count, i); - msg->getBOOLFast(_PREHASH_Data, _PREHASH_OnlineStatus, is_online, i); - if(msg->has("DataExtended")) - { - msg->getU32("DataExtended", "TimeStamp", most_recent_time, i); - } - - if (owner_id.isNull()) - { - continue; - } - - LLNameListCtrl::NameItem item_params; - item_params.value = owner_id; - item_params.target = is_group_owned ? LLNameListCtrl::GROUP : LLNameListCtrl::INDIVIDUAL; - - if (is_group_owned) - { - item_params.columns.add().type("icon").value(self->mIconGroup->getName()).column("type"); - item_params.columns.add().value(OWNER_GROUP).font(FONT).column("online_status"); - } - else if (is_online) - { - item_params.columns.add().type("icon").value(self->mIconAvatarOnline->getName()).column("type"); - item_params.columns.add().value(OWNER_ONLINE).font(FONT).column("online_status"); - } - else // offline - { - item_params.columns.add().type("icon").value(self->mIconAvatarOffline->getName()).column("type"); - item_params.columns.add().value(OWNER_OFFLINE).font(FONT).column("online_status"); - } - - // Placeholder for name. - LLAvatarName av_name; - LLAvatarNameCache::get(owner_id, &av_name); - item_params.columns.add().value(av_name.getCompleteName()).font(FONT).column("name"); - - object_count_str = llformat("%d", object_count); - item_params.columns.add().value(object_count_str).font(FONT).column("count"); - item_params.columns.add().value(LLDate((time_t)most_recent_time)).font(FONT).column("mostrecent").type("date"); - - self->mOwnerList->addNameItemRow(item_params); - LL_DEBUGS() << "object owner " << owner_id << " (" << (is_group_owned ? "group" : "agent") - << ") owns " << object_count << " objects." << LL_ENDL; - } - - // check for no results - if (0 == self->mOwnerList->getItemCount()) - { - self->mOwnerList->setCommentText(LLTrans::getString("NoneFound")); - } - else - { - self->mOwnerList->setEnabled(true); - } - - self->mBtnRefresh->setEnabled(true); -} - -// static -void LLPanelLandObjects::onCommitList(LLUICtrl* ctrl, void* data) -{ - LLPanelLandObjects* self = (LLPanelLandObjects*)data; - - if (false == self->mOwnerList->getCanSelect()) - { - return; - } - LLScrollListItem *item = self->mOwnerList->getFirstSelected(); - if (item) - { - // Look up the selected name, for future dialog box use. - const LLScrollListCell* cell; - cell = item->getColumn(1); - if (!cell) - { - return; - } - // Is this a group? - self->mSelectedIsGroup = cell->getValue().asString() == OWNER_GROUP; - cell = item->getColumn(2); - self->mSelectedName = cell->getValue().asString(); - cell = item->getColumn(3); - self->mSelectedCount = atoi(cell->getValue().asString().c_str()); - - // Set the selection, and enable the return button. - self->mSelectedOwners.clear(); - self->mSelectedOwners.insert(item->getUUID()); - self->mBtnReturnOwnerList->setEnabled(true); - - // Highlight this user's objects - clickShowCore(self, RT_LIST, &(self->mSelectedOwners)); - } -} - -// static -void LLPanelLandObjects::clickShowCore(LLPanelLandObjects* self, S32 return_type, uuid_list_t* list) -{ - LLParcel* parcel = self->mParcel->getParcel(); - if (!parcel) return; - - send_parcel_select_objects(parcel->getLocalID(), return_type, list); -} - -// static -void LLPanelLandObjects::onClickShowOwnerObjects(void* userdata) -{ - clickShowCore((LLPanelLandObjects*)userdata, RT_OWNER); -} - -// static -void LLPanelLandObjects::onClickShowGroupObjects(void* userdata) -{ - clickShowCore((LLPanelLandObjects*)userdata, (RT_GROUP)); -} - -// static -void LLPanelLandObjects::onClickShowOtherObjects(void* userdata) -{ - clickShowCore((LLPanelLandObjects*)userdata, RT_OTHER); -} - -// static -void LLPanelLandObjects::onClickReturnOwnerObjects(void* userdata) -{ - S32 owned = 0; - - LLPanelLandObjects* panelp = (LLPanelLandObjects*)userdata; - LLParcel* parcel = panelp->mParcel->getParcel(); - if (!parcel) return; - - owned = parcel->getOwnerPrimCount(); - - send_parcel_select_objects(parcel->getLocalID(), RT_OWNER); - - LLUUID owner_id = parcel->getOwnerID(); - - LLSD args; - args["N"] = llformat("%d",owned); - - if (owner_id == gAgent.getID()) - { - LLNotificationsUtil::add("ReturnObjectsOwnedBySelf", args, LLSD(), boost::bind(&LLPanelLandObjects::callbackReturnOwnerObjects, panelp, _1, _2)); - } - else - { - args["NAME"] = LLSLURL("agent", owner_id, "completename").getSLURLString(); - LLNotificationsUtil::add("ReturnObjectsOwnedByUser", args, LLSD(), boost::bind(&LLPanelLandObjects::callbackReturnOwnerObjects, panelp, _1, _2)); - } -} - -// static -void LLPanelLandObjects::onClickReturnGroupObjects(void* userdata) -{ - LLPanelLandObjects* panelp = (LLPanelLandObjects*)userdata; - LLParcel* parcel = panelp->mParcel->getParcel(); - if (!parcel) return; - - send_parcel_select_objects(parcel->getLocalID(), RT_GROUP); - - std::string group_name; - gCacheName->getGroupName(parcel->getGroupID(), group_name); - - LLSD args; - args["NAME"] = group_name; - args["N"] = llformat("%d", parcel->getGroupPrimCount()); - - // create and show confirmation textbox - LLNotificationsUtil::add("ReturnObjectsDeededToGroup", args, LLSD(), boost::bind(&LLPanelLandObjects::callbackReturnGroupObjects, panelp, _1, _2)); -} - -// static -void LLPanelLandObjects::onClickReturnOtherObjects(void* userdata) -{ - S32 other = 0; - - LLPanelLandObjects* panelp = (LLPanelLandObjects*)userdata; - LLParcel* parcel = panelp->mParcel->getParcel(); - if (!parcel) return; - - other = parcel->getOtherPrimCount(); - - send_parcel_select_objects(parcel->getLocalID(), RT_OTHER); - - LLSD args; - args["N"] = llformat("%d", other); - - if (parcel->getIsGroupOwned()) - { - std::string group_name; - gCacheName->getGroupName(parcel->getGroupID(), group_name); - args["NAME"] = group_name; - - LLNotificationsUtil::add("ReturnObjectsNotOwnedByGroup", args, LLSD(), boost::bind(&LLPanelLandObjects::callbackReturnOtherObjects, panelp, _1, _2)); - } - else - { - LLUUID owner_id = parcel->getOwnerID(); - - if (owner_id == gAgent.getID()) - { - LLNotificationsUtil::add("ReturnObjectsNotOwnedBySelf", args, LLSD(), boost::bind(&LLPanelLandObjects::callbackReturnOtherObjects, panelp, _1, _2)); - } - else - { - args["NAME"] = LLSLURL("agent", owner_id, "completename").getSLURLString(); - LLNotificationsUtil::add("ReturnObjectsNotOwnedByUser", args, LLSD(), boost::bind(&LLPanelLandObjects::callbackReturnOtherObjects, panelp, _1, _2)); - } - } -} - -// static -void LLPanelLandObjects::onLostFocus(LLFocusableElement* caller, void* user_data) -{ - onCommitClean((LLUICtrl*)caller, user_data); -} - -// static -void LLPanelLandObjects::onCommitClean(LLUICtrl *caller, void* user_data) -{ - LLPanelLandObjects *lop = (LLPanelLandObjects *)user_data; - LLParcel* parcel = lop->mParcel->getParcel(); - if (parcel) - { - S32 return_time = atoi(lop->mCleanOtherObjectsTime->getText().c_str()); - // Only send return time if it has changed - if (return_time != lop->mOtherTime) - { - lop->mOtherTime = return_time; - - parcel->setCleanOtherTime(lop->mOtherTime); - send_other_clean_time_message(parcel->getLocalID(), lop->mOtherTime); - } -} -} - - -//--------------------------------------------------------------------------- -// LLPanelLandOptions -//--------------------------------------------------------------------------- - -LLPanelLandOptions::LLPanelLandOptions(LLParcelSelectionHandle& parcel) -: LLPanel(), - mCheckEditObjects(NULL), - mCheckEditGroupObjects(NULL), - mCheckAllObjectEntry(NULL), - mCheckGroupObjectEntry(NULL), - mCheckSafe(NULL), - mCheckFly(NULL), - mCheckGroupScripts(NULL), - mCheckOtherScripts(NULL), - mCheckShowDirectory(NULL), - mCategoryCombo(NULL), - mLandingTypeCombo(NULL), - mSnapshotCtrl(NULL), - mLocationText(NULL), - mSeeAvatarsText(NULL), - mSetBtn(NULL), - mClearBtn(NULL), - mMatureCtrl(NULL), - mPushRestrictionCtrl(NULL), - mSeeAvatarsCtrl(NULL), - mParcel(parcel) -{ -} - - -bool LLPanelLandOptions::postBuild() -{ - mCheckEditObjects = getChild( "edit objects check"); - childSetCommitCallback("edit objects check", onCommitAny, this); - - mCheckEditGroupObjects = getChild( "edit group objects check"); - childSetCommitCallback("edit group objects check", onCommitAny, this); - - mCheckAllObjectEntry = getChild( "all object entry check"); - childSetCommitCallback("all object entry check", onCommitAny, this); - - mCheckGroupObjectEntry = getChild( "group object entry check"); - childSetCommitCallback("group object entry check", onCommitAny, this); - - mCheckGroupScripts = getChild( "check group scripts"); - childSetCommitCallback("check group scripts", onCommitAny, this); - - - mCheckFly = getChild( "check fly"); - childSetCommitCallback("check fly", onCommitAny, this); - - - mCheckOtherScripts = getChild( "check other scripts"); - childSetCommitCallback("check other scripts", onCommitAny, this); - - - mCheckSafe = getChild( "check safe"); - childSetCommitCallback("check safe", onCommitAny, this); - - - mPushRestrictionCtrl = getChild( "PushRestrictCheck"); - childSetCommitCallback("PushRestrictCheck", onCommitAny, this); - - mSeeAvatarsCtrl = getChild( "SeeAvatarsCheck"); - childSetCommitCallback("SeeAvatarsCheck", onCommitAny, this); - - mSeeAvatarsText = getChild("allow_see_label"); - if (mSeeAvatarsText) - { - mSeeAvatarsText->setShowCursorHand(false); - mSeeAvatarsText->setSoundFlags(LLView::MOUSE_UP); - mSeeAvatarsText->setClickedCallback(boost::bind(&toggleSeeAvatars, this)); - } - - mCheckShowDirectory = getChild( "ShowDirectoryCheck"); - childSetCommitCallback("ShowDirectoryCheck", onCommitAny, this); - - - mCategoryCombo = getChild( "land category"); - childSetCommitCallback("land category", onCommitAny, this); - - - mMatureCtrl = getChild( "MatureCheck"); - childSetCommitCallback("MatureCheck", onCommitAny, this); - - if (gAgent.wantsPGOnly()) - { - // Disable these buttons if they are PG (Teen) users - mMatureCtrl->setVisible(false); - mMatureCtrl->setEnabled(false); - } - - - mSnapshotCtrl = getChild("snapshot_ctrl"); - if (mSnapshotCtrl) - { - mSnapshotCtrl->setCommitCallback( onCommitAny, this ); - mSnapshotCtrl->setAllowNoTexture ( true ); - mSnapshotCtrl->setImmediateFilterPermMask(PERM_COPY | PERM_TRANSFER); - mSnapshotCtrl->setDnDFilterPermMask(PERM_COPY | PERM_TRANSFER); - } - else - { - LL_WARNS() << "LLUICtrlFactory::getTexturePickerByName() returned NULL for 'snapshot_ctrl'" << LL_ENDL; - } - - - mLocationText = getChild("landing_point"); - - mSetBtn = getChild("Set"); - mSetBtn->setClickedCallback(onClickSet, this); - - - mClearBtn = getChild("Clear"); - mClearBtn->setClickedCallback(onClickClear, this); - - - mLandingTypeCombo = getChild( "landing type"); - childSetCommitCallback("landing type", onCommitAny, this); - - return true; -} - - -// virtual -LLPanelLandOptions::~LLPanelLandOptions() -{ } - - -// virtual -void LLPanelLandOptions::refresh() -{ - refreshSearch(); - - LLParcel *parcel = mParcel->getParcel(); - if (!parcel || gDisconnected) - { - mCheckEditObjects ->set(false); - mCheckEditObjects ->setEnabled(false); - - mCheckEditGroupObjects ->set(false); - mCheckEditGroupObjects ->setEnabled(false); - - mCheckAllObjectEntry ->set(false); - mCheckAllObjectEntry ->setEnabled(false); - - mCheckGroupObjectEntry ->set(false); - mCheckGroupObjectEntry ->setEnabled(false); - - mCheckSafe ->set(false); - mCheckSafe ->setEnabled(false); - - mCheckFly ->set(false); - mCheckFly ->setEnabled(false); - - mCheckGroupScripts ->set(false); - mCheckGroupScripts ->setEnabled(false); - - mCheckOtherScripts ->set(false); - mCheckOtherScripts ->setEnabled(false); - - mPushRestrictionCtrl->set(false); - mPushRestrictionCtrl->setEnabled(false); - - mSeeAvatarsCtrl->set(true); - mSeeAvatarsCtrl->setEnabled(false); - mSeeAvatarsText->setEnabled(false); - - mLandingTypeCombo->setCurrentByIndex(0); - mLandingTypeCombo->setEnabled(false); - - mSnapshotCtrl->setImageAssetID(LLUUID::null); - mSnapshotCtrl->setEnabled(false); - - mLocationText->setTextArg("[LANDING]", getString("landing_point_none")); - mSetBtn->setEnabled(false); - mClearBtn->setEnabled(false); - - mMatureCtrl->setEnabled(false); - } - else - { - // something selected, hooray! - - // Display options - bool can_change_options = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_OPTIONS); - mCheckEditObjects ->set( parcel->getAllowModify() ); - mCheckEditObjects ->setEnabled( can_change_options ); - - mCheckEditGroupObjects ->set( parcel->getAllowGroupModify() || parcel->getAllowModify()); - mCheckEditGroupObjects ->setEnabled( can_change_options && !parcel->getAllowModify() ); // If others edit is enabled, then this is explicitly enabled. - - mCheckAllObjectEntry ->set( parcel->getAllowAllObjectEntry() ); - mCheckAllObjectEntry ->setEnabled( can_change_options ); - - mCheckGroupObjectEntry ->set( parcel->getAllowGroupObjectEntry() || parcel->getAllowAllObjectEntry()); - mCheckGroupObjectEntry ->setEnabled( can_change_options && !parcel->getAllowAllObjectEntry() ); - - mCheckSafe ->set( !parcel->getAllowDamage() ); - mCheckSafe ->setEnabled( can_change_options ); - - mCheckFly ->set( parcel->getAllowFly() ); - mCheckFly ->setEnabled( can_change_options ); - - mCheckGroupScripts ->set( parcel->getAllowGroupScripts() || parcel->getAllowOtherScripts()); - mCheckGroupScripts ->setEnabled( can_change_options && !parcel->getAllowOtherScripts()); - - mCheckOtherScripts ->set( parcel->getAllowOtherScripts() ); - mCheckOtherScripts ->setEnabled( can_change_options ); - - mPushRestrictionCtrl->set( parcel->getRestrictPushObject() ); - if(parcel->getRegionPushOverride()) - { - mPushRestrictionCtrl->setLabel(getString("push_restrict_region_text")); - mPushRestrictionCtrl->setEnabled(false); - mPushRestrictionCtrl->set(true); - } - else - { - mPushRestrictionCtrl->setLabel(getString("push_restrict_text")); - mPushRestrictionCtrl->setEnabled(can_change_options); - } - - mSeeAvatarsCtrl->set(parcel->getSeeAVs()); - mSeeAvatarsCtrl->setEnabled(can_change_options && parcel->getHaveNewParcelLimitData()); - mSeeAvatarsText->setEnabled(can_change_options && parcel->getHaveNewParcelLimitData()); - - bool can_change_landing_point = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, - GP_LAND_SET_LANDING_POINT); - mLandingTypeCombo->setCurrentByIndex((S32)parcel->getLandingType()); - mLandingTypeCombo->setEnabled( can_change_landing_point ); - - bool can_change_identity = - LLViewerParcelMgr::isParcelModifiableByAgent( - parcel, GP_LAND_CHANGE_IDENTITY); - mSnapshotCtrl->setImageAssetID(parcel->getSnapshotID()); - mSnapshotCtrl->setEnabled( can_change_identity ); - - // find out where we're looking and convert that to an angle in degrees on a regular compass (not the internal representation) - LLVector3 user_look_at = parcel->getUserLookAt(); - U32 user_look_at_angle = ( (U32)( ( atan2(user_look_at[1], -user_look_at[0]) + F_PI * 2 ) * RAD_TO_DEG + 0.5) - 90) % 360; - - LLVector3 pos = parcel->getUserLocation(); - if (pos.isExactlyZero()) - { - mLocationText->setTextArg("[LANDING]", getString("landing_point_none")); - } - else - { - mLocationText->setTextArg("[LANDING]",llformat("%d, %d, %d (%d\xC2\xB0)", - ll_round(pos.mV[VX]), - ll_round(pos.mV[VY]), - ll_round(pos.mV[VZ]), - user_look_at_angle)); - } - - mSetBtn->setEnabled( can_change_landing_point ); - mClearBtn->setEnabled( can_change_landing_point ); - - if (gAgent.wantsPGOnly()) - { - // Disable these buttons if they are PG (Teen) users - mMatureCtrl->setVisible(false); - mMatureCtrl->setEnabled(false); - } - else - { - // not teen so fill in the data for the maturity control - mMatureCtrl->setVisible(true); - LLStyle::Params style; - style.image(LLUI::getUIImage(gFloaterView->getParentFloater(this)->getString("maturity_icon_moderate"))); - LLCheckBoxWithTBAcess* fullaccess_mature_ctrl = (LLCheckBoxWithTBAcess*)mMatureCtrl; - fullaccess_mature_ctrl->getTextBox()->setText(LLStringExplicit("")); - fullaccess_mature_ctrl->getTextBox()->appendImageSegment(style); - fullaccess_mature_ctrl->getTextBox()->appendText(getString("mature_check_mature"), false); - fullaccess_mature_ctrl->setToolTip(getString("mature_check_mature_tooltip")); - fullaccess_mature_ctrl->reshape(fullaccess_mature_ctrl->getRect().getWidth(), fullaccess_mature_ctrl->getRect().getHeight(), false); - - // they can see the checkbox, but its disposition depends on the - // state of the region - LLViewerRegion* regionp = LLViewerParcelMgr::getInstance()->getSelectionRegion(); - if (regionp) - { - if (regionp->getSimAccess() == SIM_ACCESS_PG) - { - mMatureCtrl->setEnabled(false); - mMatureCtrl->set(false); - } - else if (regionp->getSimAccess() == SIM_ACCESS_MATURE) - { - mMatureCtrl->setEnabled(can_change_identity); - mMatureCtrl->set(parcel->getMaturePublish()); - } - else if (regionp->getSimAccess() == SIM_ACCESS_ADULT) - { - mMatureCtrl->setEnabled(false); - mMatureCtrl->set(true); - mMatureCtrl->setLabel(getString("mature_check_adult")); - mMatureCtrl->setToolTip(getString("mature_check_adult_tooltip")); - } - } - } - } -} - -// virtual -void LLPanelLandOptions::draw() -{ - LLPanel::draw(); -} - - -// private -void LLPanelLandOptions::refreshSearch() -{ - LLParcel *parcel = mParcel->getParcel(); - if (!parcel || gDisconnected) - { - mCheckShowDirectory->set(false); - mCheckShowDirectory->setEnabled(false); - - const std::string& none_string = LLParcel::getCategoryString(LLParcel::C_NONE); - mCategoryCombo->setValue(none_string); - mCategoryCombo->setEnabled(false); - return; - } - - LLViewerRegion* region = - LLViewerParcelMgr::getInstance()->getSelectionRegion(); - - bool can_change = - LLViewerParcelMgr::isParcelModifiableByAgent( - parcel, GP_LAND_FIND_PLACES) - && region - && !(region->getRegionFlag(REGION_FLAGS_BLOCK_PARCEL_SEARCH)); - - bool show_directory = parcel->getParcelFlag(PF_SHOW_DIRECTORY); - mCheckShowDirectory->set(show_directory); - - // Set by string in case the order in UI doesn't match the order by index. - LLParcel::ECategory cat = parcel->getCategory(); - const std::string& category_string = LLParcel::getCategoryString(cat); - mCategoryCombo->setValue(category_string); - - std::string tooltip; - bool enable_show_directory = false; - // Parcels <= 128 square meters cannot be listed in search, in an - // effort to reduce search spam from small parcels. See also - // the search crawler "grid-crawl.py" in secondlife.com/doc/app/search/ JC - const S32 MIN_PARCEL_AREA_FOR_SEARCH = 128; - bool large_enough = parcel->getArea() >= MIN_PARCEL_AREA_FOR_SEARCH; - if (large_enough) - { - if (can_change) - { - tooltip = getString("search_enabled_tooltip"); - enable_show_directory = true; - } - else - { - tooltip = getString("search_disabled_permissions_tooltip"); - enable_show_directory = false; - } - } - else - { - // not large enough to include in search - if (can_change) - { - if (show_directory) - { - // parcels that are too small, but are still in search for - // legacy reasons, need to have the check box enabled so - // the owner can delist the parcel. JC - tooltip = getString("search_enabled_tooltip"); - enable_show_directory = true; - } - else - { - tooltip = getString("search_disabled_small_tooltip"); - enable_show_directory = false; - } - } - else - { - // both too small and don't have permission, so just - // show the permissions as the reason (which is probably - // the more common case) JC - tooltip = getString("search_disabled_permissions_tooltip"); - enable_show_directory = false; - } - } - mCheckShowDirectory->setToolTip(tooltip); - mCategoryCombo->setToolTip(tooltip); - mCheckShowDirectory->setEnabled(enable_show_directory); - mCategoryCombo->setEnabled(enable_show_directory); -} - - -// static -void LLPanelLandOptions::onCommitAny(LLUICtrl *ctrl, void *userdata) -{ - LLPanelLandOptions *self = (LLPanelLandOptions *)userdata; - - LLParcel* parcel = self->mParcel->getParcel(); - if (!parcel) - { - return; - } - - // Extract data from UI - bool create_objects = self->mCheckEditObjects->get(); - bool create_group_objects = self->mCheckEditGroupObjects->get() || self->mCheckEditObjects->get(); - bool all_object_entry = self->mCheckAllObjectEntry->get(); - bool group_object_entry = self->mCheckGroupObjectEntry->get() || self->mCheckAllObjectEntry->get(); - bool allow_terraform = false; // removed from UI so always off now - self->mCheckEditLand->get(); - bool allow_damage = !self->mCheckSafe->get(); - bool allow_fly = self->mCheckFly->get(); - bool allow_landmark = true; // cannot restrict landmark creation - bool allow_other_scripts = self->mCheckOtherScripts->get(); - bool allow_group_scripts = self->mCheckGroupScripts->get() || allow_other_scripts; - bool allow_publish = false; - bool mature_publish = self->mMatureCtrl->get(); - bool push_restriction = self->mPushRestrictionCtrl->get(); - bool see_avs = self->mSeeAvatarsCtrl->get(); - bool show_directory = self->mCheckShowDirectory->get(); - // we have to get the index from a lookup, not from the position in the dropdown! - S32 category_index = LLParcel::getCategoryFromString(self->mCategoryCombo->getSelectedValue()); - S32 landing_type_index = self->mLandingTypeCombo->getCurrentIndex(); - LLUUID snapshot_id = self->mSnapshotCtrl->getImageAssetID(); - LLViewerRegion* region; - region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); - - if (region && region->getAllowDamage()) - { // Damage is allowed on the region - server will always allow scripts - if ( (!allow_other_scripts && parcel->getParcelFlag(PF_ALLOW_OTHER_SCRIPTS)) || - (!allow_group_scripts && parcel->getParcelFlag(PF_ALLOW_GROUP_SCRIPTS)) ) - { // Don't allow turning off "Run Scripts" if damage is allowed in the region - self->mCheckOtherScripts->set(parcel->getParcelFlag(PF_ALLOW_OTHER_SCRIPTS)); // Restore UI to actual settings - self->mCheckGroupScripts->set(parcel->getParcelFlag(PF_ALLOW_GROUP_SCRIPTS)); - LLNotificationsUtil::add("UnableToDisableOutsideScripts"); - return; - } - } - - // Push data into current parcel - parcel->setParcelFlag(PF_CREATE_OBJECTS, create_objects); - parcel->setParcelFlag(PF_CREATE_GROUP_OBJECTS, create_group_objects); - parcel->setParcelFlag(PF_ALLOW_ALL_OBJECT_ENTRY, all_object_entry); - parcel->setParcelFlag(PF_ALLOW_GROUP_OBJECT_ENTRY, group_object_entry); - parcel->setParcelFlag(PF_ALLOW_TERRAFORM, allow_terraform); - parcel->setParcelFlag(PF_ALLOW_DAMAGE, allow_damage); - parcel->setParcelFlag(PF_ALLOW_FLY, allow_fly); - parcel->setParcelFlag(PF_ALLOW_LANDMARK, allow_landmark); - parcel->setParcelFlag(PF_ALLOW_GROUP_SCRIPTS, allow_group_scripts); - parcel->setParcelFlag(PF_ALLOW_OTHER_SCRIPTS, allow_other_scripts); - parcel->setParcelFlag(PF_SHOW_DIRECTORY, show_directory); - parcel->setParcelFlag(PF_ALLOW_PUBLISH, allow_publish); - parcel->setParcelFlag(PF_MATURE_PUBLISH, mature_publish); - parcel->setParcelFlag(PF_RESTRICT_PUSHOBJECT, push_restriction); - parcel->setCategory((LLParcel::ECategory)category_index); - parcel->setLandingType((LLParcel::ELandingType)landing_type_index); - parcel->setSnapshotID(snapshot_id); - parcel->setSeeAVs(see_avs); - - // Send current parcel data upstream to server - LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate( parcel ); - - // Might have changed properties, so let's redraw! - self->refresh(); -} - - -// static -void LLPanelLandOptions::onClickSet(void* userdata) -{ - LLPanelLandOptions* self = (LLPanelLandOptions*)userdata; - - LLParcel* selected_parcel = self->mParcel->getParcel(); - if (!selected_parcel) return; - - LLParcel* agent_parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - if (!agent_parcel) return; - - if (agent_parcel->getLocalID() != selected_parcel->getLocalID()) - { - LLNotificationsUtil::add("MustBeInParcel"); - return; - } - - LLVector3 pos_region = gAgent.getPositionAgent(); - selected_parcel->setUserLocation(pos_region); - selected_parcel->setUserLookAt(gAgent.getFrameAgent().getAtAxis()); - - LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate(selected_parcel); - - self->refresh(); -} - -void LLPanelLandOptions::onClickClear(void* userdata) -{ - LLPanelLandOptions* self = (LLPanelLandOptions*)userdata; - - LLParcel* selected_parcel = self->mParcel->getParcel(); - if (!selected_parcel) return; - - // yes, this magic number of 0,0,0 means that it is clear - LLVector3 zero_vec(0.f, 0.f, 0.f); - selected_parcel->setUserLocation(zero_vec); - selected_parcel->setUserLookAt(zero_vec); - - LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate(selected_parcel); - - self->refresh(); -} - -void LLPanelLandOptions::toggleSeeAvatars(void* userdata) -{ - LLPanelLandOptions* self = (LLPanelLandOptions*)userdata; - if (self) - { - self->getChild("SeeAvatarsCheck")->toggle(); - self->getChild("SeeAvatarsCheck")->setBtnFocus(); - self->onCommitAny(NULL, userdata); - } -} -//--------------------------------------------------------------------------- -// LLPanelLandAccess -//--------------------------------------------------------------------------- - -LLPanelLandAccess::LLPanelLandAccess(LLParcelSelectionHandle& parcel) - : LLPanel(), - mParcel(parcel) -{ -} - - -bool LLPanelLandAccess::postBuild() -{ - childSetCommitCallback("public_access", onCommitPublicAccess, this); - childSetCommitCallback("limit_payment", onCommitAny, this); - childSetCommitCallback("limit_age_verified", onCommitAny, this); - childSetCommitCallback("GroupCheck", onCommitGroupCheck, this); - childSetCommitCallback("PassCheck", onCommitAny, this); - childSetCommitCallback("pass_combo", onCommitAny, this); - childSetCommitCallback("PriceSpin", onCommitAny, this); - childSetCommitCallback("HoursSpin", onCommitAny, this); - - childSetAction("add_allowed", boost::bind(&LLPanelLandAccess::onClickAddAccess, this)); - childSetAction("remove_allowed", onClickRemoveAccess, this); - childSetAction("add_banned", boost::bind(&LLPanelLandAccess::onClickAddBanned, this)); - childSetAction("remove_banned", onClickRemoveBanned, this); - - mListAccess = getChild("AccessList"); - if (mListAccess) - { - mListAccess->sortByColumnIndex(0, true); // ascending - mListAccess->setContextMenu(LLScrollListCtrl::MENU_AVATAR); - } - - mListBanned = getChild("BannedList"); - if (mListBanned) - { - mListBanned->sortByColumnIndex(0, true); // ascending - mListBanned->setContextMenu(LLScrollListCtrl::MENU_AVATAR); - mListBanned->setAlternateSort(); - } - - return true; -} - - -LLPanelLandAccess::~LLPanelLandAccess() -{ -} - -void LLPanelLandAccess::refresh() -{ - LLFloater* parent_floater = gFloaterView->getParentFloater(this); - LLParcel *parcel = mParcel->getParcel(); - - // Display options - if (parcel && !gDisconnected) - { - bool use_access_list = parcel->getParcelFlag(PF_USE_ACCESS_LIST); - bool use_group = parcel->getParcelFlag(PF_USE_ACCESS_GROUP); - bool public_access = !use_access_list; - - if (parcel->getRegionAllowAccessOverride()) - { - getChild("public_access")->setValue(public_access); - getChild("GroupCheck")->setValue(use_group); - } - else - { - getChild("public_access")->setValue(true); - getChild("GroupCheck")->setValue(false); - } - std::string group_name; - gCacheName->getGroupName(parcel->getGroupID(), group_name); - getChild("GroupCheck")->setLabelArg("[GROUP]", group_name ); - - // Allow list - if (mListAccess) - { - // Clear the sort order so we don't re-sort on every add. - mListAccess->clearSortOrder(); - mListAccess->deleteAllItems(); - S32 count = parcel->mAccessList.size(); - getChild("AllowedText")->setTextArg("[COUNT]", llformat("%d",count)); - getChild("AllowedText")->setTextArg("[MAX]", llformat("%d",PARCEL_MAX_ACCESS_LIST)); - - getChild("AccessList")->setToolTipArg(LLStringExplicit("[LISTED]"), llformat("%d",count)); - getChild("AccessList")->setToolTipArg(LLStringExplicit("[MAX]"), llformat("%d",PARCEL_MAX_ACCESS_LIST)); - - for (LLAccessEntry::map::const_iterator cit = parcel->mAccessList.begin(); - cit != parcel->mAccessList.end(); ++cit) - { - const LLAccessEntry& entry = (*cit).second; - std::string prefix; - if (entry.mTime != 0) - { - LLStringUtil::format_map_t args; - S32 now = time(NULL); - S32 seconds = entry.mTime - now; - if (seconds < 0) seconds = 0; - prefix.assign(" ("); - if (seconds >= 120) - { - args["[MINUTES]"] = llformat("%d", (seconds/60)); - std::string buf = parent_floater->getString ("Minutes", args); - prefix.append(buf); - } - else if (seconds >= 60) - { - prefix.append("1 " + parent_floater->getString("Minute")); - } - else - { - args["[SECONDS]"] = llformat("%d", seconds); - std::string buf = parent_floater->getString ("Seconds", args); - prefix.append(buf); - } - prefix.append(" " + parent_floater->getString("Remaining") + ") "); - } - mListAccess->addNameItem(entry.mID, ADD_DEFAULT, true, "", prefix); - } - mListAccess->sortByName(true); - } - - // Ban List - if(mListBanned) - { - // Clear the sort order so we don't re-sort on every add. - mListBanned->clearSortOrder(); - mListBanned->deleteAllItems(); - S32 count = parcel->mBanList.size(); - getChild("BanCheck")->setTextArg("[COUNT]", llformat("%d",count)); - getChild("BanCheck")->setTextArg("[MAX]", llformat("%d",PARCEL_MAX_ACCESS_LIST)); - - getChild("BannedList")->setToolTipArg(LLStringExplicit("[LISTED]"), llformat("%d",count)); - getChild("BannedList")->setToolTipArg(LLStringExplicit("[MAX]"), llformat("%d",PARCEL_MAX_ACCESS_LIST)); - - for (LLAccessEntry::map::const_iterator cit = parcel->mBanList.begin(); - cit != parcel->mBanList.end(); ++cit) - { - const LLAccessEntry& entry = (*cit).second; - std::string duration; - S32 seconds = -1; - if (entry.mTime != 0) - { - LLStringUtil::format_map_t args; - S32 now = time(NULL); - seconds = entry.mTime - now; - if (seconds < 0) seconds = 0; - - if (seconds >= 7200) - { - args["[HOURS]"] = llformat("%d", (seconds / 3600)); - duration = parent_floater->getString("Hours", args); - } - else if (seconds >= 3600) - { - duration = "1 " + parent_floater->getString("Hour"); - } - else if (seconds >= 120) - { - args["[MINUTES]"] = llformat("%d", (seconds / 60)); - duration = parent_floater->getString("Minutes", args); - } - else if (seconds >= 60) - { - duration = "1 " + parent_floater->getString("Minute"); - } - else - { - args["[SECONDS]"] = llformat("%d", seconds); - duration = parent_floater->getString("Seconds", args); - } - } - else - { - duration = parent_floater->getString("Always"); - } - LLSD item; - item["id"] = entry.mID; - LLSD& columns = item["columns"]; - columns[0]["column"] = "name"; // to be populated later - columns[1]["column"] = "duration"; - columns[1]["value"] = duration; - columns[1]["alt_value"] = entry.mTime != 0 ? std::to_string(seconds) : "Always"; - mListBanned->addElement(item); - } - mListBanned->sortByName(true); - } - - if(parcel->getRegionDenyAnonymousOverride()) - { - getChild("limit_payment")->setValue(true); - getChild("limit_payment")->setLabelArg("[ESTATE_PAYMENT_LIMIT]", getString("access_estate_defined") ); - } - else - { - getChild("limit_payment")->setValue((parcel->getParcelFlag(PF_DENY_ANONYMOUS))); - getChild("limit_payment")->setLabelArg("[ESTATE_PAYMENT_LIMIT]", std::string() ); - } - if(parcel->getRegionDenyAgeUnverifiedOverride()) - { - getChild("limit_age_verified")->setValue(true); - getChild("limit_age_verified")->setLabelArg("[ESTATE_AGE_LIMIT]", getString("access_estate_defined") ); - } - else - { - getChild("limit_age_verified")->setValue((parcel->getParcelFlag(PF_DENY_AGEUNVERIFIED))); - getChild("limit_age_verified")->setLabelArg("[ESTATE_AGE_LIMIT]", std::string() ); - } - - bool use_pass = parcel->getParcelFlag(PF_USE_PASS_LIST); - getChild("PassCheck")->setValue(use_pass); - LLCtrlSelectionInterface* passcombo = childGetSelectionInterface("pass_combo"); - if (passcombo) - { - if (public_access || !use_pass) - { - passcombo->selectByValue("anyone"); - } - } - - S32 pass_price = parcel->getPassPrice(); - getChild("PriceSpin")->setValue((F32)pass_price ); - - F32 pass_hours = parcel->getPassHours(); - getChild("HoursSpin")->setValue(pass_hours ); - } - else - { - getChild("public_access")->setValue(false); - getChild("limit_payment")->setValue(false); - getChild("limit_age_verified")->setValue(false); - getChild("GroupCheck")->setValue(false); - getChild("GroupCheck")->setLabelArg("[GROUP]", LLStringUtil::null ); - getChild("PassCheck")->setValue(false); - getChild("PriceSpin")->setValue((F32)PARCEL_PASS_PRICE_DEFAULT); - getChild("HoursSpin")->setValue(PARCEL_PASS_HOURS_DEFAULT ); - getChild("AccessList")->setToolTipArg(LLStringExplicit("[LISTED]"), llformat("%d",0)); - getChild("AccessList")->setToolTipArg(LLStringExplicit("[MAX]"), llformat("%d",0)); - getChild("BannedList")->setToolTipArg(LLStringExplicit("[LISTED]"), llformat("%d",0)); - getChild("BannedList")->setToolTipArg(LLStringExplicit("[MAX]"), llformat("%d",0)); - } -} - -void LLPanelLandAccess::refresh_ui() -{ - getChildView("public_access")->setEnabled(false); - getChildView("limit_payment")->setEnabled(false); - getChildView("limit_age_verified")->setEnabled(false); - getChildView("GroupCheck")->setEnabled(false); - getChildView("PassCheck")->setEnabled(false); - getChildView("pass_combo")->setEnabled(false); - getChildView("PriceSpin")->setEnabled(false); - getChildView("HoursSpin")->setEnabled(false); - getChildView("AccessList")->setEnabled(false); - getChildView("BannedList")->setEnabled(false); - getChildView("add_allowed")->setEnabled(false); - getChildView("remove_allowed")->setEnabled(false); - getChildView("add_banned")->setEnabled(false); - getChildView("remove_banned")->setEnabled(false); - - LLParcel *parcel = mParcel->getParcel(); - if (parcel && !gDisconnected) - { - bool can_manage_allowed = false; - bool can_manage_banned = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_MANAGE_BANNED); - - if (parcel->getRegionAllowAccessOverride()) - { // Estate owner may have disabled allowing the parcel owner from managing access. - can_manage_allowed = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_MANAGE_ALLOWED); - } - - getChildView("public_access")->setEnabled(can_manage_allowed); - bool public_access = getChild("public_access")->getValue().asBoolean(); - if (public_access) - { - bool override = false; - if(parcel->getRegionDenyAnonymousOverride()) - { - override = true; - getChildView("limit_payment")->setEnabled(false); - } - else - { - getChildView("limit_payment")->setEnabled(can_manage_allowed); - } - if(parcel->getRegionDenyAgeUnverifiedOverride()) - { - override = true; - getChildView("limit_age_verified")->setEnabled(false); - } - else - { - getChildView("limit_age_verified")->setEnabled(can_manage_allowed); - } - if (override) - { - getChildView("Only Allow")->setToolTip(getString("estate_override")); - } - else - { - getChildView("Only Allow")->setToolTip(std::string()); - } - getChildView("PassCheck")->setEnabled(false); - getChildView("pass_combo")->setEnabled(false); - getChildView("AccessList")->setEnabled(false); - } - else - { - getChildView("limit_payment")->setEnabled(false); - getChildView("limit_age_verified")->setEnabled(false); - - - bool sell_passes = getChild("PassCheck")->getValue().asBoolean(); - getChildView("PassCheck")->setEnabled(can_manage_allowed); - if (sell_passes) - { - getChildView("pass_combo")->setEnabled(can_manage_allowed); - getChildView("PriceSpin")->setEnabled(can_manage_allowed); - getChildView("HoursSpin")->setEnabled(can_manage_allowed); - } - } - std::string group_name; - if (gCacheName->getGroupName(parcel->getGroupID(), group_name)) - { - bool can_allow_groups = !public_access || (public_access && (getChild("limit_payment")->getValue().asBoolean() ^ getChild("limit_age_verified")->getValue().asBoolean())); - getChildView("GroupCheck")->setEnabled(can_manage_allowed && can_allow_groups); - } - getChildView("AccessList")->setEnabled(can_manage_allowed); - S32 allowed_list_count = parcel->mAccessList.size(); - getChildView("add_allowed")->setEnabled(can_manage_allowed && allowed_list_count < PARCEL_MAX_ACCESS_LIST); - bool has_selected = (mListAccess && mListAccess->getSelectionInterface()->getFirstSelectedIndex() >= 0); - getChildView("remove_allowed")->setEnabled(can_manage_allowed && has_selected); - - getChildView("BannedList")->setEnabled(can_manage_banned); - S32 banned_list_count = parcel->mBanList.size(); - getChildView("add_banned")->setEnabled(can_manage_banned && banned_list_count < PARCEL_MAX_ACCESS_LIST); - has_selected = (mListBanned && mListBanned->getSelectionInterface()->getFirstSelectedIndex() >= 0); - getChildView("remove_banned")->setEnabled(can_manage_banned && has_selected); - } -} - - -// public -void LLPanelLandAccess::refreshNames() -{ - LLParcel* parcel = mParcel->getParcel(); - std::string group_name; - if(parcel) - { - gCacheName->getGroupName(parcel->getGroupID(), group_name); - } - getChild("GroupCheck")->setLabelArg("[GROUP]", group_name); -} - - -// virtual -void LLPanelLandAccess::draw() -{ - refresh_ui(); - refreshNames(); - LLPanel::draw(); -} - -// static -void LLPanelLandAccess::onCommitPublicAccess(LLUICtrl *ctrl, void *userdata) -{ - LLPanelLandAccess *self = (LLPanelLandAccess *)userdata; - LLParcel* parcel = self->mParcel->getParcel(); - if (!parcel) - { - return; - } - - onCommitAny(ctrl, userdata); -} - -void LLPanelLandAccess::onCommitGroupCheck(LLUICtrl *ctrl, void *userdata) -{ - LLPanelLandAccess *self = (LLPanelLandAccess *)userdata; - LLParcel* parcel = self->mParcel->getParcel(); - if (!parcel) - { - return; - } - - bool use_pass_list = !self->getChild("public_access")->getValue().asBoolean(); - bool use_access_group = self->getChild("GroupCheck")->getValue().asBoolean(); - LLCtrlSelectionInterface* passcombo = self->childGetSelectionInterface("pass_combo"); - if (passcombo) - { - if (use_access_group && use_pass_list) - { - if (passcombo->getSelectedValue().asString() == "group") - { - passcombo->selectByValue("anyone"); - } - } - } - - onCommitAny(ctrl, userdata); -} - -// static -void LLPanelLandAccess::onCommitAny(LLUICtrl *ctrl, void *userdata) -{ - LLPanelLandAccess *self = (LLPanelLandAccess *)userdata; - - LLParcel* parcel = self->mParcel->getParcel(); - if (!parcel) - { - return; - } - - // Extract data from UI - bool public_access = self->getChild("public_access")->getValue().asBoolean(); - bool use_access_group = self->getChild("GroupCheck")->getValue().asBoolean(); - if (use_access_group) - { - std::string group_name; - if (!gCacheName->getGroupName(parcel->getGroupID(), group_name)) - { - use_access_group = false; - } - } - - bool limit_payment = false, limit_age_verified = false; - bool use_access_list = false; - bool use_pass_list = false; - - if (public_access) - { - use_access_list = false; - limit_payment = self->getChild("limit_payment")->getValue().asBoolean(); - limit_age_verified = self->getChild("limit_age_verified")->getValue().asBoolean(); - } - else - { - use_access_list = true; - use_pass_list = self->getChild("PassCheck")->getValue().asBoolean(); - LLCtrlSelectionInterface* passcombo = self->childGetSelectionInterface("pass_combo"); - if (passcombo) - { - if (use_access_group && use_pass_list) - { - if (passcombo->getSelectedValue().asString() == "group") - { - use_access_group = false; - } - } - } - } - - S32 pass_price = llfloor((F32)self->getChild("PriceSpin")->getValue().asReal()); - F32 pass_hours = (F32)self->getChild("HoursSpin")->getValue().asReal(); - - // Push data into current parcel - parcel->setParcelFlag(PF_USE_ACCESS_GROUP, use_access_group); - parcel->setParcelFlag(PF_USE_ACCESS_LIST, use_access_list); - parcel->setParcelFlag(PF_USE_PASS_LIST, use_pass_list); - parcel->setParcelFlag(PF_USE_BAN_LIST, true); - parcel->setParcelFlag(PF_DENY_ANONYMOUS, limit_payment); - parcel->setParcelFlag(PF_DENY_AGEUNVERIFIED, limit_age_verified); - - parcel->setPassPrice( pass_price ); - parcel->setPassHours( pass_hours ); - - // Send current parcel data upstream to server - LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate( parcel ); - - // Might have changed properties, so let's redraw! - self->refresh(); -} - -void LLPanelLandAccess::onClickAddAccess() -{ - LLView * button = findChild("add_allowed"); - LLFloater * root_floater = gFloaterView->getParentFloater(this); - LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show( - boost::bind(&LLPanelLandAccess::callbackAvatarCBAccess, this, _1), false, false, false, root_floater->getName(), button); - if (picker) - { - root_floater->addDependentFloater(picker); - } -} - -void LLPanelLandAccess::callbackAvatarCBAccess(const uuid_vec_t& ids) -{ - if (!ids.empty()) - { - LLUUID id = ids[0]; - LLParcel* parcel = mParcel->getParcel(); - if (parcel && parcel->addToAccessList(id, 0)) - { - U32 lists_to_update = AL_ACCESS; - // agent was successfully added to access list - // but we also need to check ban list to ensure that agent will not be in two lists simultaneously - if(parcel->removeFromBanList(id)) - { - lists_to_update |= AL_BAN; - } - LLViewerParcelMgr::getInstance()->sendParcelAccessListUpdate(lists_to_update); - refresh(); - } - } -} - -// static -void LLPanelLandAccess::onClickRemoveAccess(void* data) -{ - LLPanelLandAccess* panelp = (LLPanelLandAccess*)data; - if (panelp && panelp->mListAccess) - { - LLParcel* parcel = panelp->mParcel->getParcel(); - if (parcel) - { - std::vector names = panelp->mListAccess->getAllSelected(); - for (std::vector::iterator iter = names.begin(); - iter != names.end(); ) - { - LLScrollListItem* item = *iter++; - const LLUUID& agent_id = item->getUUID(); - parcel->removeFromAccessList(agent_id); - } - LLViewerParcelMgr::getInstance()->sendParcelAccessListUpdate(AL_ACCESS); - panelp->refresh(); - } - } -} - -// static -void LLPanelLandAccess::onClickAddBanned() -{ - LLView * button = findChild("add_banned"); - LLFloater * root_floater = gFloaterView->getParentFloater(this); - LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show( - boost::bind(&LLPanelLandAccess::callbackAvatarCBBanned, this, _1), true, false, false, root_floater->getName(), button); - if (picker) - { - root_floater->addDependentFloater(picker); - } -} - -// static -void LLPanelLandAccess::callbackAvatarCBBanned(const uuid_vec_t& ids) -{ - LLFloater * root_floater = gFloaterView->getParentFloater(this); - LLFloaterBanDuration* duration_floater = LLFloaterBanDuration::show( - boost::bind(&LLPanelLandAccess::callbackAvatarCBBanned2, this, _1, _2), ids); - if (duration_floater) - { - root_floater->addDependentFloater(duration_floater); - } -} - -void LLPanelLandAccess::callbackAvatarCBBanned2(const uuid_vec_t& ids, S32 duration) -{ - LLParcel* parcel = mParcel->getParcel(); - if (!parcel) return; - - U32 lists_to_update = 0; - - for (uuid_vec_t::const_iterator it = ids.begin(); it < ids.end(); it++) - { - LLUUID id = *it; - if (parcel->addToBanList(id, duration)) - { - lists_to_update |= AL_BAN; - // agent was successfully added to ban list - // but we also need to check access list to ensure that agent will not be in two lists simultaneously - if (parcel->removeFromAccessList(id)) - { - lists_to_update |= AL_ACCESS; - } - } - } - if (lists_to_update > 0) - { - LLViewerParcelMgr::getInstance()->sendParcelAccessListUpdate(lists_to_update); - refresh(); - } -} - -// static -void LLPanelLandAccess::onClickRemoveBanned(void* data) -{ - LLPanelLandAccess* panelp = (LLPanelLandAccess*)data; - if (panelp && panelp->mListBanned) - { - LLParcel* parcel = panelp->mParcel->getParcel(); - if (parcel) - { - std::vector names = panelp->mListBanned->getAllSelected(); - for (std::vector::iterator iter = names.begin(); - iter != names.end(); ) - { - LLScrollListItem* item = *iter++; - const LLUUID& agent_id = item->getUUID(); - parcel->removeFromBanList(agent_id); - } - LLViewerParcelMgr::getInstance()->sendParcelAccessListUpdate(AL_BAN); - panelp->refresh(); - } - } -} - -//--------------------------------------------------------------------------- -// LLPanelLandCovenant -//--------------------------------------------------------------------------- -LLPanelLandCovenant::LLPanelLandCovenant(LLParcelSelectionHandle& parcel) - : LLPanel(), - mParcel(parcel), - mNextUpdateTime(0) -{ -} - -LLPanelLandCovenant::~LLPanelLandCovenant() -{ -} - -bool LLPanelLandCovenant::postBuild() -{ - mLastRegionID = LLUUID::null; - mNextUpdateTime = 0; - mTextEstateOwner = getChild("estate_owner_text"); - mTextEstateOwner->setIsFriendCallback(LLAvatarActions::isFriend); - return true; -} - -// virtual -void LLPanelLandCovenant::refresh() -{ - LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); - if(!region || gDisconnected) return; - - LLTextBox* region_name = getChild("region_name_text"); - if (region_name) - { - region_name->setText(region->getName()); - } - - LLTextBox* region_landtype = getChild("region_landtype_text"); - region_landtype->setText(region->getLocalizedSimProductName()); - - LLTextBox* region_maturity = getChild("region_maturity_text"); - if (region_maturity) - { - insert_maturity_into_textbox(region_maturity, gFloaterView->getParentFloater(this), MATURITY); - } - - LLTextBox* resellable_clause = getChild("resellable_clause"); - if (resellable_clause) - { - if (region->getRegionFlag(REGION_FLAGS_BLOCK_LAND_RESELL)) - { - resellable_clause->setText(getString("can_not_resell")); - } - else - { - resellable_clause->setText(getString("can_resell")); - } - } - - LLTextBox* changeable_clause = getChild("changeable_clause"); - if (changeable_clause) - { - if (region->getRegionFlag(REGION_FLAGS_ALLOW_PARCEL_CHANGES)) - { - changeable_clause->setText(getString("can_change")); - } - else - { - changeable_clause->setText(getString("can_not_change")); - } - } - - if (mLastRegionID != region->getRegionID() - || mNextUpdateTime < LLTimer::getElapsedSeconds()) - { - // Request Covenant Info - // Note: LLPanelLandCovenant doesn't change Covenant's content and any - // changes made by Estate floater should be requested by Estate floater - LLMessageSystem *msg = gMessageSystem; - msg->newMessage("EstateCovenantRequest"); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); - msg->sendReliable(region->getHost()); - - mLastRegionID = region->getRegionID(); - mNextUpdateTime = LLTimer::getElapsedSeconds() + COVENANT_REFRESH_TIME_SEC; - } -} - -// static -void LLPanelLandCovenant::updateCovenantText(const std::string &string) -{ - LLPanelLandCovenant* self = LLFloaterLand::getCurrentPanelLandCovenant(); - if (self) - { - LLViewerTextEditor* editor = self->getChild("covenant_editor"); - editor->setText(string); - } -} - -// static -void LLPanelLandCovenant::updateEstateName(const std::string& name) -{ - LLPanelLandCovenant* self = LLFloaterLand::getCurrentPanelLandCovenant(); - if (self) - { - LLTextBox* editor = self->getChild("estate_name_text"); - if (editor) editor->setText(name); - } -} - -// static -void LLPanelLandCovenant::updateLastModified(const std::string& text) -{ - LLPanelLandCovenant* self = LLFloaterLand::getCurrentPanelLandCovenant(); - if (self) - { - LLTextBox* editor = self->getChild("covenant_timestamp_text"); - if (editor) editor->setText(text); - } -} - -// static -void LLPanelLandCovenant::updateEstateOwnerName(const std::string& name) -{ - LLPanelLandCovenant* self = LLFloaterLand::getCurrentPanelLandCovenant(); - if (self) - { - self->mTextEstateOwner->setText(name); - } -} - -// inserts maturity info(icon and text) into target textbox -// names_floater - pointer to floater which contains strings with maturity icons filenames -// str_to_parse is string in format "txt1[MATURITY]txt2" where maturity icon and text will be inserted instead of [MATURITY] -void insert_maturity_into_textbox(LLTextBox* target_textbox, LLFloater* names_floater, std::string str_to_parse) -{ - LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); - if (!region) - return; - - LLStyle::Params style; - - U8 sim_access = region->getSimAccess(); - - switch(sim_access) - { - case SIM_ACCESS_PG: - style.image(LLUI::getUIImage(names_floater->getString("maturity_icon_general"))); - break; - - case SIM_ACCESS_ADULT: - style.image(LLUI::getUIImage(names_floater->getString("maturity_icon_adult"))); - break; - - case SIM_ACCESS_MATURE: - style.image(LLUI::getUIImage(names_floater->getString("maturity_icon_moderate"))); - break; - - default: - break; - } - - size_t maturity_pos = str_to_parse.find(MATURITY); - - if (maturity_pos == std::string::npos) - { - return; - } - - std::string text_before_rating = str_to_parse.substr(0, maturity_pos); - std::string text_after_rating = str_to_parse.substr(maturity_pos + MATURITY.length()); - - target_textbox->setText(text_before_rating); - - target_textbox->appendImageSegment(style); - - target_textbox->appendText(LLViewerParcelMgr::getInstance()->getSelectionRegion()->getSimAccessString(), false); - target_textbox->appendText(text_after_rating, false); -} - -LLPanelLandExperiences::LLPanelLandExperiences( LLSafeHandle& parcelp ) - : mParcel(parcelp) -{ - -} - - -bool LLPanelLandExperiences::postBuild() -{ - mAllowed = setupList("panel_allowed", EXPERIENCE_KEY_TYPE_ALLOWED, AL_ALLOW_EXPERIENCE); - mBlocked = setupList("panel_blocked", EXPERIENCE_KEY_TYPE_BLOCKED, AL_BLOCK_EXPERIENCE); - - // only non-grid-wide experiences - mAllowed->addFilter(boost::bind(LLPanelExperiencePicker::FilterWithProperty, _1, LLExperienceCache::PROPERTY_GRID)); - - // no privileged ones - mBlocked->addFilter(boost::bind(LLPanelExperiencePicker::FilterWithoutProperties, _1, LLExperienceCache::PROPERTY_PRIVILEGED|LLExperienceCache::PROPERTY_GRID)); - - getChild("trusted_layout_panel")->setVisible(false); - getChild("experiences_help_text")->setVisible(false); - getChild("allowed_text_help")->setText(getString("allowed_parcel_text")); - getChild("blocked_text_help")->setText(getString("blocked_parcel_text")); - - return LLPanel::postBuild(); -} - -LLPanelExperienceListEditor* LLPanelLandExperiences::setupList( const char* control_name, U32 xp_type, U32 access_type ) -{ - LLPanelExperienceListEditor* child = findChild(control_name); - if(child) - { - child->getChild("text_name")->setText(child->getString(control_name)); - child->setMaxExperienceIDs(PARCEL_MAX_EXPERIENCE_LIST); - child->setAddedCallback(boost::bind(&LLPanelLandExperiences::experienceAdded, this, _1, xp_type, access_type)); - child->setRemovedCallback(boost::bind(&LLPanelLandExperiences::experienceRemoved, this, _1, access_type)); - } - - return child; -} - -void LLPanelLandExperiences::experienceAdded( const LLUUID& id, U32 xp_type, U32 access_type ) -{ - LLParcel* parcel = mParcel->getParcel(); - if (parcel) - { - parcel->setExperienceKeyType(id, xp_type); - LLViewerParcelMgr::getInstance()->sendParcelAccessListUpdate(access_type); - refresh(); - } -} - -void LLPanelLandExperiences::experienceRemoved( const LLUUID& id, U32 access_type ) -{ - LLParcel* parcel = mParcel->getParcel(); - if (parcel) - { - parcel->setExperienceKeyType(id, EXPERIENCE_KEY_TYPE_NONE); - LLViewerParcelMgr::getInstance()->sendParcelAccessListUpdate(access_type); - refresh(); - } -} - -void LLPanelLandExperiences::refreshPanel(LLPanelExperienceListEditor* panel, U32 xp_type) -{ - LLParcel *parcel = mParcel->getParcel(); - - // Display options - if (panel == NULL) - { - return; - } - if (!parcel || gDisconnected) - { - // disable the panel - panel->setEnabled(false); - panel->setExperienceIds(LLSD::emptyArray()); - } - else - { - // enable the panel - panel->setEnabled(true); - LLAccessEntry::map entries = parcel->getExperienceKeysByType(xp_type); - LLAccessEntry::map::iterator it = entries.begin(); - LLSD ids = LLSD::emptyArray(); - for (/**/; it != entries.end(); ++it) - { - ids.append(it->second.mID); - } - panel->setExperienceIds(ids); - panel->setReadonly(!LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_OPTIONS)); - panel->refreshExperienceCounter(); - } -} - -void LLPanelLandExperiences::refresh() -{ - refreshPanel(mAllowed, EXPERIENCE_KEY_TYPE_ALLOWED); - refreshPanel(mBlocked, EXPERIENCE_KEY_TYPE_BLOCKED); -} - -//========================================================================= - -LLPanelLandEnvironment::LLPanelLandEnvironment(LLParcelSelectionHandle& parcel) : - LLPanelEnvironmentInfo(), - mParcel(parcel), - mLastParcelId(INVALID_PARCEL_ID) -{ -} - -bool LLPanelLandEnvironment::postBuild() -{ - if (!LLPanelEnvironmentInfo::postBuild()) - return false; - - getChild(BTN_USEDEFAULT)->setLabelArg("[USEDEFAULT]", getString(STR_LABEL_USEREGION)); - getChild(CHK_ALLOWOVERRIDE)->setVisible(false); - getChild(PNL_REGION_MSG)->setVisible(false); - getChild(PNL_ENVIRONMENT_ALTITUDES)->setVisible(true); - - return true; -} - -void LLPanelLandEnvironment::refresh() -{ - if (gDisconnected) - return; - - commitDayLenOffsetChanges(false); // commit unsaved changes if any - - if (!isSameRegion()) - { - setCrossRegion(true); - mCurrentEnvironment.reset(); - mLastParcelId = INVALID_PARCEL_ID; - mCurEnvVersion = INVALID_PARCEL_ENVIRONMENT_VERSION; - setControlsEnabled(false); - return; - } - - if (mLastParcelId != getParcelId()) - { - mCurEnvVersion = INVALID_PARCEL_ENVIRONMENT_VERSION; - mCurrentEnvironment.reset(); - } - - if (!mCurrentEnvironment && mCurEnvVersion <= INVALID_PARCEL_ENVIRONMENT_VERSION) - { - refreshFromSource(); - return; - } - - LLPanelEnvironmentInfo::refresh(); -} - -void LLPanelLandEnvironment::refreshFromSource() -{ - LLParcel *parcel = getParcel(); - - if (!LLEnvironment::instance().isExtendedEnvironmentEnabled()) - { - setNoEnvironmentSupport(true); - setControlsEnabled(false); - mCurEnvVersion = INVALID_PARCEL_ENVIRONMENT_VERSION; - return; - } - setNoEnvironmentSupport(false); - - if (!parcel) - { - setNoSelection(true); - setControlsEnabled(false); - mCurEnvVersion = INVALID_PARCEL_ENVIRONMENT_VERSION; - return; - } - - setNoSelection(false); - if (isSameRegion()) - { - LL_DEBUGS("ENVIRONMENT") << "Requesting environment for parcel " << parcel->getLocalID() << ", known version " << mCurEnvVersion << LL_ENDL; - setCrossRegion(false); - - LLHandle that_h = getHandle(); - - if (mCurEnvVersion < UNSET_PARCEL_ENVIRONMENT_VERSION) - { - // to mark as requesting - mCurEnvVersion = parcel->getParcelEnvironmentVersion(); - } - mLastParcelId = parcel->getLocalID(); - - LLEnvironment::instance().requestParcel(parcel->getLocalID(), - [that_h](S32 parcel_id, LLEnvironment::EnvironmentInfo::ptr_t envifo) - { - LLPanelLandEnvironment *that = (LLPanelLandEnvironment*)that_h.get(); - if (!that) return; - that->mLastParcelId = parcel_id; - that->onEnvironmentReceived(parcel_id, envifo); - }); - } - else - { - setCrossRegion(true); - mCurrentEnvironment.reset(); - mLastParcelId = INVALID_PARCEL_ID; - mCurEnvVersion = INVALID_PARCEL_ENVIRONMENT_VERSION; - } - setControlsEnabled(false); -} - - -bool LLPanelLandEnvironment::isSameRegion() -{ - LLViewerRegion* regionp = LLViewerParcelMgr::instance().getSelectionRegion(); - - return (!regionp || (regionp->getRegionID() == gAgent.getRegion()->getRegionID())); -} - -LLParcel *LLPanelLandEnvironment::getParcel() -{ - return mParcel->getParcel(); -} - - -bool LLPanelLandEnvironment::canEdit() -{ - LLParcel *parcel = getParcel(); - if (!parcel) - return false; - - return LLEnvironment::instance().canAgentUpdateParcelEnvironment(parcel) && mAllowOverride; -} - -S32 LLPanelLandEnvironment::getParcelId() -{ - LLParcel *parcel = getParcel(); - if (!parcel) - return INVALID_PARCEL_ID; - - return parcel->getLocalID(); -} +/** + * @file llfloaterland.cpp + * @brief "About Land" floater, allowing display and editing of land parcel properties. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include +#include + +#include "llfloaterland.h" + +#include "llavatarnamecache.h" +#include "llfocusmgr.h" +#include "llnotificationsutil.h" +#include "llparcel.h" +#include "message.h" + +#include "llagent.h" +#include "llagentaccess.h" +#include "llappviewer.h" +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "llfloaterreg.h" +#include "llfloateravatarpicker.h" +#include "llfloaterauction.h" +#include "llfloaterbanduration.h" +#include "llfloatergroups.h" +#include "llfloaterscriptlimits.h" +#include "llavataractions.h" +#include "lllineeditor.h" +#include "llnamelistctrl.h" +#include "llpanellandaudio.h" +#include "llpanellandmedia.h" +#include "llradiogroup.h" +#include "llresmgr.h" // getMonetaryString +#include "llscrolllistctrl.h" +#include "llscrolllistitem.h" +#include "llscrolllistcell.h" +#include "llselectmgr.h" +#include "llslurl.h" +#include "llspinctrl.h" +#include "lltabcontainer.h" +#include "lltextbox.h" +#include "lltexturectrl.h" +#include "lluiconstants.h" +#include "lluictrlfactory.h" +#include "llviewertexturelist.h" // LLUIImageList +#include "llviewermessage.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "llviewerstats.h" +#include "llviewertexteditor.h" +#include "llviewerwindow.h" +#include "llviewercontrol.h" +#include "roles_constants.h" +#include "lltrans.h" +#include "llpanelexperiencelisteditor.h" +#include "llpanelexperiencepicker.h" +#include "llpanelenvironment.h" +#include "llexperiencecache.h" + +#include "llgroupactions.h" +#include "llenvironment.h" + +const F64 COVENANT_REFRESH_TIME_SEC = 60.0f; + +static std::string OWNER_ONLINE = "0"; +static std::string OWNER_OFFLINE = "1"; +static std::string OWNER_GROUP = "2"; +static std::string MATURITY = "[MATURITY]"; + +// constants used in callbacks below - syntactic sugar. +static const bool BUY_GROUP_LAND = true; +static const bool BUY_PERSONAL_LAND = false; +LLPointer LLPanelLandGeneral::sSelectionForBuyPass = NULL; + +// Statics +LLParcelSelectionObserver* LLFloaterLand::sObserver = NULL; +S32 LLFloaterLand::sLastTab = 0; + +// Local classes +class LLParcelSelectionObserver : public LLParcelObserver +{ +public: + virtual void changed() { LLFloaterLand::refreshAll(); } +}; + +// class needed to get full access to textbox inside checkbox, because LLCheckBoxCtrl::setLabel() has string as its argument. +// It was introduced while implementing EXT-4706 +class LLCheckBoxWithTBAcess : public LLCheckBoxCtrl +{ +public: + LLTextBox* getTextBox() + { + return mLabel; + } +}; + + +class LLPanelLandExperiences + : public LLPanel +{ +public: + LLPanelLandExperiences(LLSafeHandle& parcelp); + virtual bool postBuild(); + void refresh(); + + void experienceAdded(const LLUUID& id, U32 xp_type, U32 access_type); + void experienceRemoved(const LLUUID& id, U32 access_type); +protected: + LLPanelExperienceListEditor* setupList( const char* control_name, U32 xp_type, U32 access_type ); + void refreshPanel(LLPanelExperienceListEditor* panel, U32 xp_type); + + LLSafeHandle& mParcel; + + + LLPanelExperienceListEditor* mAllowed; + LLPanelExperienceListEditor* mBlocked; +}; + + +class LLPanelLandEnvironment + : public LLPanelEnvironmentInfo +{ +public: + LLPanelLandEnvironment(LLSafeHandle& parcelp); + + virtual bool isRegion() const override { return false; } + virtual bool isLargeEnough() override + { + LLParcel *parcelp = mParcel->getParcel(); + return ((parcelp) ? (parcelp->getArea() >= MINIMUM_PARCEL_SIZE) : false); + } + + virtual bool postBuild() override; + virtual void refresh() override; + + virtual LLParcel * getParcel() override; + + virtual bool canEdit() override; + virtual S32 getParcelId() override; + +protected: + virtual void refreshFromSource() override; + + bool isSameRegion(); + + LLSafeHandle & mParcel; + S32 mLastParcelId; +}; + + +// inserts maturity info(icon and text) into target textbox +// names_floater - pointer to floater which contains strings with maturity icons filenames +// str_to_parse is string in format "txt1[MATURITY]txt2" where maturity icon and text will be inserted instead of [MATURITY] +void insert_maturity_into_textbox(LLTextBox* target_textbox, LLFloater* names_floater, std::string str_to_parse); + +//--------------------------------------------------------------------------- +// LLFloaterLand +//--------------------------------------------------------------------------- + +void send_parcel_select_objects(S32 parcel_local_id, U32 return_type, + uuid_list_t* return_ids = NULL) +{ + LLMessageSystem *msg = gMessageSystem; + + LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); + if (!region) return; + + // Since new highlight will be coming in, drop any highlights + // that exist right now. + LLSelectMgr::getInstance()->unhighlightAll(); + + msg->newMessageFast(_PREHASH_ParcelSelectObjects); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ParcelData); + msg->addS32Fast(_PREHASH_LocalID, parcel_local_id); + msg->addU32Fast(_PREHASH_ReturnType, return_type); + + // Throw all return ids into the packet. + // TODO: Check for too many ids. + if (return_ids) + { + uuid_list_t::iterator end = return_ids->end(); + for (uuid_list_t::iterator it = return_ids->begin(); + it != end; + ++it) + { + msg->nextBlockFast(_PREHASH_ReturnIDs); + msg->addUUIDFast(_PREHASH_ReturnID, (*it)); + } + } + else + { + // Put in a null key so that the message is complete. + msg->nextBlockFast(_PREHASH_ReturnIDs); + msg->addUUIDFast(_PREHASH_ReturnID, LLUUID::null); + } + + msg->sendReliable(region->getHost()); +} + +LLParcel* LLFloaterLand::getCurrentSelectedParcel() +{ + return mParcel->getParcel(); +}; + +//static +LLPanelLandObjects* LLFloaterLand::getCurrentPanelLandObjects() +{ + LLFloaterLand* land_instance = LLFloaterReg::getTypedInstance("about_land"); + if(land_instance) + { + return land_instance->mPanelObjects; + } + else + { + return NULL; + } +} + +//static +LLPanelLandCovenant* LLFloaterLand::getCurrentPanelLandCovenant() +{ + LLFloaterLand* land_instance = LLFloaterReg::getTypedInstance("about_land"); + if(land_instance) + { + return land_instance->mPanelCovenant; + } + else + { + return NULL; + } +} + +// static +void LLFloaterLand::refreshAll() +{ + LLFloaterLand* land_instance = LLFloaterReg::findTypedInstance("about_land"); + if(land_instance) + { + land_instance->refresh(); + } +} + +void LLFloaterLand::onOpen(const LLSD& key) +{ + // moved from triggering show instance in llviwermenu.cpp + + if (LLViewerParcelMgr::getInstance()->selectionEmpty()) + { + LLViewerParcelMgr::getInstance()->selectParcelAt(gAgent.getPositionGlobal()); + } + + // Done automatically when the selected parcel's properties arrive + // (and hence we have the local id). + // LLViewerParcelMgr::getInstance()->sendParcelAccessListRequest(AL_ACCESS | AL_BAN | AL_RENTER); + + mParcel = LLViewerParcelMgr::getInstance()->getFloatingParcelSelection(); + + // Refresh even if not over a region so we don't get an + // uninitialized dialog. The dialog is 0-region aware. + refresh(); +} + +void LLFloaterLand::onVisibilityChanged(const LLSD& visible) +{ + if (!visible.asBoolean()) + { + // Might have been showing owned objects + LLSelectMgr::getInstance()->unhighlightAll(); + + // Save which panel we had open + sLastTab = mTabLand->getCurrentPanelIndex(); + } +} + + +LLFloaterLand::LLFloaterLand(const LLSD& seed) +: LLFloater(seed) +{ + mFactoryMap["land_general_panel"] = LLCallbackMap(createPanelLandGeneral, this); + mFactoryMap["land_covenant_panel"] = LLCallbackMap(createPanelLandCovenant, this); + mFactoryMap["land_objects_panel"] = LLCallbackMap(createPanelLandObjects, this); + mFactoryMap["land_options_panel"] = LLCallbackMap(createPanelLandOptions, this); + mFactoryMap["land_audio_panel"] = LLCallbackMap(createPanelLandAudio, this); + mFactoryMap["land_media_panel"] = LLCallbackMap(createPanelLandMedia, this); + mFactoryMap["land_access_panel"] = LLCallbackMap(createPanelLandAccess, this); + mFactoryMap["land_experiences_panel"] = LLCallbackMap(createPanelLandExperiences, this); + mFactoryMap["land_environment_panel"] = LLCallbackMap(createPanelLandEnvironment, this); + + sObserver = new LLParcelSelectionObserver(); + LLViewerParcelMgr::getInstance()->addObserver( sObserver ); +} + +bool LLFloaterLand::postBuild() +{ + setVisibleCallback(boost::bind(&LLFloaterLand::onVisibilityChanged, this, _2)); + + LLTabContainer* tab = getChild("landtab"); + + mTabLand = (LLTabContainer*) tab; + + if (tab) + { + tab->selectTab(sLastTab); + } + + return true; +} + + +// virtual +LLFloaterLand::~LLFloaterLand() +{ + LLViewerParcelMgr::getInstance()->removeObserver( sObserver ); + delete sObserver; + sObserver = NULL; +} + +// public +void LLFloaterLand::refresh() +{ + mPanelGeneral->refresh(); + mPanelObjects->refresh(); + mPanelOptions->refresh(); + mPanelAudio->refresh(); + mPanelMedia->refresh(); + mPanelAccess->refresh(); + mPanelCovenant->refresh(); + mPanelExperiences->refresh(); + mPanelEnvironment->refresh(); +} + + + +void* LLFloaterLand::createPanelLandGeneral(void* data) +{ + LLFloaterLand* self = (LLFloaterLand*)data; + self->mPanelGeneral = new LLPanelLandGeneral(self->mParcel); + return self->mPanelGeneral; +} + +// static +void* LLFloaterLand::createPanelLandCovenant(void* data) +{ + LLFloaterLand* self = (LLFloaterLand*)data; + self->mPanelCovenant = new LLPanelLandCovenant(self->mParcel); + return self->mPanelCovenant; +} + + +// static +void* LLFloaterLand::createPanelLandObjects(void* data) +{ + LLFloaterLand* self = (LLFloaterLand*)data; + self->mPanelObjects = new LLPanelLandObjects(self->mParcel); + return self->mPanelObjects; +} + +// static +void* LLFloaterLand::createPanelLandOptions(void* data) +{ + LLFloaterLand* self = (LLFloaterLand*)data; + self->mPanelOptions = new LLPanelLandOptions(self->mParcel); + return self->mPanelOptions; +} + +// static +void* LLFloaterLand::createPanelLandAudio(void* data) +{ + LLFloaterLand* self = (LLFloaterLand*)data; + self->mPanelAudio = new LLPanelLandAudio(self->mParcel); + return self->mPanelAudio; +} + +// static +void* LLFloaterLand::createPanelLandMedia(void* data) +{ + LLFloaterLand* self = (LLFloaterLand*)data; + self->mPanelMedia = new LLPanelLandMedia(self->mParcel); + return self->mPanelMedia; +} + +// static +void* LLFloaterLand::createPanelLandAccess(void* data) +{ + LLFloaterLand* self = (LLFloaterLand*)data; + self->mPanelAccess = new LLPanelLandAccess(self->mParcel); + return self->mPanelAccess; +} + +// static +void* LLFloaterLand::createPanelLandExperiences(void* data) +{ + LLFloaterLand* self = (LLFloaterLand*)data; + self->mPanelExperiences = new LLPanelLandExperiences(self->mParcel); + return self->mPanelExperiences; +} + +//static +void* LLFloaterLand::createPanelLandEnvironment(void* data) +{ + LLFloaterLand* self = (LLFloaterLand*)data; + self->mPanelEnvironment = new LLPanelLandEnvironment(self->mParcel); + return self->mPanelEnvironment; +} + + +//--------------------------------------------------------------------------- +// LLPanelLandGeneral +//--------------------------------------------------------------------------- + + +LLPanelLandGeneral::LLPanelLandGeneral(LLParcelSelectionHandle& parcel) +: LLPanel(), + mUncheckedSell(false), + mParcel(parcel) +{ +} + +bool LLPanelLandGeneral::postBuild() +{ + mEditName = getChild("Name"); + mEditName->setCommitCallback(onCommitAny, this); + getChild("Name")->setPrevalidate(LLTextValidate::validateASCIIPrintableNoPipe); + + mEditDesc = getChild("Description"); + mEditDesc->setCommitOnFocusLost(true); + mEditDesc->setCommitCallback(onCommitAny, this); + mEditDesc->setContentTrusted(false); + // No prevalidate function - historically the prevalidate function was broken, + // allowing residents to put in characters like U+2661 WHITE HEART SUIT, so + // preserve that ability. + + mTextSalePending = getChild("SalePending"); + mTextOwnerLabel = getChild("Owner:"); + mTextOwner = getChild("OwnerText"); + mTextOwner->setIsFriendCallback(LLAvatarActions::isFriend); + + mContentRating = getChild("ContentRatingText"); + mLandType = getChild("LandTypeText"); + + mBtnProfile = getChild("Profile..."); + mBtnProfile->setClickedCallback(boost::bind(&LLPanelLandGeneral::onClickProfile, this)); + + + mTextGroupLabel = getChild("Group:"); + mTextGroup = getChild("GroupText"); + + + mBtnSetGroup = getChild("Set..."); + mBtnSetGroup->setCommitCallback(boost::bind(&LLPanelLandGeneral::onClickSetGroup, this)); + + + mCheckDeedToGroup = getChild( "check deed"); + childSetCommitCallback("check deed", onCommitAny, this); + + + mBtnDeedToGroup = getChild("Deed..."); + mBtnDeedToGroup->setClickedCallback(onClickDeed, this); + + + mCheckContributeWithDeed = getChild( "check contrib"); + childSetCommitCallback("check contrib", onCommitAny, this); + + + + mSaleInfoNotForSale = getChild("Not for sale."); + + mSaleInfoForSale1 = getChild("For Sale: Price L$[PRICE]."); + + + mBtnSellLand = getChild("Sell Land..."); + mBtnSellLand->setClickedCallback(onClickSellLand, this); + + mSaleInfoForSale2 = getChild("For sale to"); + + mSaleInfoForSaleObjects = getChild("Sell with landowners objects in parcel."); + + mSaleInfoForSaleNoObjects = getChild("Selling with no objects in parcel."); + + + mBtnStopSellLand = getChild("Cancel Land Sale"); + mBtnStopSellLand->setClickedCallback(onClickStopSellLand, this); + + + mTextClaimDateLabel = getChild("Claimed:"); + mTextClaimDate = getChild("DateClaimText"); + + + mTextPriceLabel = getChild("PriceLabel"); + mTextPrice = getChild("PriceText"); + + + mTextDwell = getChild("DwellText"); + + mBtnBuyLand = getChild("Buy Land..."); + mBtnBuyLand->setClickedCallback(onClickBuyLand, (void*)&BUY_PERSONAL_LAND); + + + mBtnBuyGroupLand = getChild("Buy For Group..."); + mBtnBuyGroupLand->setClickedCallback(onClickBuyLand, (void*)&BUY_GROUP_LAND); + + + mBtnBuyPass = getChild("Buy Pass..."); + mBtnBuyPass->setClickedCallback(onClickBuyPass, this); + + mBtnReleaseLand = getChild("Abandon Land..."); + mBtnReleaseLand->setClickedCallback(onClickRelease, NULL); + + mBtnReclaimLand = getChild("Reclaim Land..."); + mBtnReclaimLand->setClickedCallback(onClickReclaim, NULL); + + mBtnStartAuction = getChild("Linden Sale..."); + mBtnStartAuction->setClickedCallback(onClickStartAuction, this); + + mBtnScriptLimits = getChild("Scripts..."); + + if(gDisconnected) + { + return true; + } + + // note: on region change this will not be re checked, should not matter on Agni as + // 99% of the time all regions will return the same caps. In case of an erroneous setting + // to enabled the floater will just throw an error when trying to get it's cap + std::string url = gAgent.getRegionCapability("LandResources"); + if (!url.empty()) + { + if(mBtnScriptLimits) + { + mBtnScriptLimits->setClickedCallback(onClickScriptLimits, this); + } + } + else + { + if(mBtnScriptLimits) + { + mBtnScriptLimits->setVisible(false); + } + } + + return true; +} + + +// virtual +LLPanelLandGeneral::~LLPanelLandGeneral() +{ } + + +// public +void LLPanelLandGeneral::refresh() +{ + mEditName->setEnabled(false); + mEditName->setText(LLStringUtil::null); + + mEditDesc->setEnabled(false); + mEditDesc->setText(getString("no_selection_text")); + + mTextSalePending->setText(LLStringUtil::null); + mTextSalePending->setEnabled(false); + + mBtnDeedToGroup->setEnabled(false); + mBtnSetGroup->setEnabled(false); + mBtnStartAuction->setEnabled(false); + + mCheckDeedToGroup ->set(false); + mCheckDeedToGroup ->setEnabled(false); + mCheckContributeWithDeed->set(false); + mCheckContributeWithDeed->setEnabled(false); + + mTextOwner->setText(LLStringUtil::null); + mContentRating->setText(LLStringUtil::null); + mLandType->setText(LLStringUtil::null); + mBtnProfile->setLabel(getString("profile_text")); + mBtnProfile->setEnabled(false); + + mTextClaimDate->setText(LLStringUtil::null); + mTextGroup->setText(LLStringUtil::null); + mTextPrice->setText(LLStringUtil::null); + + mSaleInfoForSale1->setVisible(false); + mSaleInfoForSale2->setVisible(false); + mSaleInfoForSaleObjects->setVisible(false); + mSaleInfoForSaleNoObjects->setVisible(false); + mSaleInfoNotForSale->setVisible(false); + mBtnSellLand->setVisible(false); + mBtnStopSellLand->setVisible(false); + + mTextPriceLabel->setText(LLStringUtil::null); + mTextDwell->setText(LLStringUtil::null); + + mBtnBuyLand->setEnabled(false); + mBtnScriptLimits->setEnabled(false); + mBtnBuyGroupLand->setEnabled(false); + mBtnReleaseLand->setEnabled(false); + mBtnReclaimLand->setEnabled(false); + mBtnBuyPass->setEnabled(false); + + if(gDisconnected) + { + return; + } + + mBtnStartAuction->setVisible(gAgent.isGodlike()); + bool region_owner = false; + LLViewerRegion* regionp = LLViewerParcelMgr::getInstance()->getSelectionRegion(); + if(regionp && (regionp->getOwner() == gAgent.getID())) + { + region_owner = true; + mBtnReleaseLand->setVisible(false); + mBtnReclaimLand->setVisible(true); + } + else + { + mBtnReleaseLand->setVisible(true); + mBtnReclaimLand->setVisible(false); + } + LLParcel *parcel = mParcel->getParcel(); + if (parcel) + { + // something selected, hooray! + bool is_leased = (LLParcel::OS_LEASED == parcel->getOwnershipStatus()); + bool region_xfer = false; + if(regionp + && !(regionp->getRegionFlag(REGION_FLAGS_BLOCK_LAND_RESELL))) + { + region_xfer = true; + } + + if (regionp) + { + insert_maturity_into_textbox(mContentRating, gFloaterView->getParentFloater(this), MATURITY); + mLandType->setText(regionp->getLocalizedSimProductName()); + } + + // estate owner/manager cannot edit other parts of the parcel + bool estate_manager_sellable = !parcel->getAuctionID() + && gAgent.canManageEstate() + // estate manager/owner can only sell parcels owned by estate owner + && regionp + && (parcel->getOwnerID() == regionp->getOwner()); + bool owner_sellable = region_xfer && !parcel->getAuctionID() + && LLViewerParcelMgr::isParcelModifiableByAgent( + parcel, GP_LAND_SET_SALE_INFO); + bool can_be_sold = owner_sellable || estate_manager_sellable; + + const LLUUID &owner_id = parcel->getOwnerID(); + bool is_public = parcel->isPublic(); + + // Is it owned? + if (is_public) + { + mTextSalePending->setText(LLStringUtil::null); + mTextSalePending->setEnabled(false); + mTextOwner->setText(getString("public_text")); + mTextOwner->setEnabled(false); + mBtnProfile->setEnabled(false); + mTextClaimDate->setText(LLStringUtil::null); + mTextClaimDate->setEnabled(false); + mTextGroup->setText(getString("none_text")); + mTextGroup->setEnabled(false); + mBtnStartAuction->setEnabled(false); + } + else + { + if(!is_leased && (owner_id == gAgent.getID())) + { + mTextSalePending->setText(getString("need_tier_to_modify")); + mTextSalePending->setEnabled(true); + } + else if(parcel->getAuctionID()) + { + mTextSalePending->setText(getString("auction_id_text")); + mTextSalePending->setTextArg("[ID]", llformat("%u", parcel->getAuctionID())); + mTextSalePending->setEnabled(true); + } + else + { + // not the owner, or it is leased + mTextSalePending->setText(LLStringUtil::null); + mTextSalePending->setEnabled(false); + } + //refreshNames(); + mTextOwner->setEnabled(true); + + // We support both group and personal profiles + mBtnProfile->setEnabled(true); + + if (parcel->getGroupID().isNull()) + { + // Not group owned, so "Profile" + mBtnProfile->setLabel(getString("profile_text")); + + mTextGroup->setText(getString("none_text")); + mTextGroup->setEnabled(false); + } + else + { + // Group owned, so "Info" + mBtnProfile->setLabel(getString("info_text")); + + //mTextGroup->setText("HIPPOS!");//parcel->getGroupName()); + mTextGroup->setEnabled(true); + } + + // Display claim date + time_t claim_date = parcel->getClaimDate(); + std::string claim_date_str = getString("time_stamp_template"); + LLSD substitution; + substitution["datetime"] = (S32) claim_date; + LLStringUtil::format (claim_date_str, substitution); + mTextClaimDate->setText(claim_date_str); + mTextClaimDate->setEnabled(is_leased); + + bool enable_auction = (gAgent.getGodLevel() >= GOD_LIAISON) + && (owner_id == GOVERNOR_LINDEN_ID) + && (parcel->getAuctionID() == 0); + mBtnStartAuction->setEnabled(enable_auction); + } + + // Display options + bool can_edit_identity = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_CHANGE_IDENTITY); + mEditName->setEnabled(can_edit_identity); + mEditDesc->setEnabled(can_edit_identity); + mEditDesc->setParseURLs(!can_edit_identity); + + bool can_edit_agent_only = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_NO_POWERS); + mBtnSetGroup->setEnabled(can_edit_agent_only && !parcel->getIsGroupOwned()); + + const LLUUID& group_id = parcel->getGroupID(); + + // Can only allow deeding if you own it and it's got a group. + bool enable_deed = (owner_id == gAgent.getID() + && group_id.notNull() + && gAgent.isInGroup(group_id)); + // You don't need special powers to allow your object to + // be deeded to the group. + mCheckDeedToGroup->setEnabled(enable_deed); + mCheckDeedToGroup->set( parcel->getAllowDeedToGroup() ); + mCheckContributeWithDeed->setEnabled(enable_deed && parcel->getAllowDeedToGroup()); + mCheckContributeWithDeed->set(parcel->getContributeWithDeed()); + + // Actually doing the deeding requires you to have GP_LAND_DEED + // powers in the group. + bool can_deed = gAgent.hasPowerInGroup(group_id, GP_LAND_DEED); + mBtnDeedToGroup->setEnabled( parcel->getAllowDeedToGroup() + && group_id.notNull() + && can_deed + && !parcel->getIsGroupOwned() + ); + + mEditName->setText( parcel->getName() ); + mEditDesc->setText( parcel->getDesc() ); + + bool for_sale = parcel->getForSale(); + + mBtnSellLand->setVisible(false); + mBtnStopSellLand->setVisible(false); + + // show pricing information + S32 area; + S32 claim_price; + S32 rent_price; + F32 dwell = DWELL_NAN; + LLViewerParcelMgr::getInstance()->getDisplayInfo(&area, + &claim_price, + &rent_price, + &for_sale, + &dwell); + // Area + LLUIString price = getString("area_size_text"); + price.setArg("[AREA]", llformat("%d",area)); + mTextPriceLabel->setText(getString("area_text")); + mTextPrice->setText(price.getString()); + + if (dwell == DWELL_NAN) + { + mTextDwell->setText(LLTrans::getString("LoadingData")); + } + else + { + mTextDwell->setText(llformat("%.0f", dwell)); + } + + if (for_sale) + { + mSaleInfoForSale1->setVisible(true); + mSaleInfoForSale2->setVisible(true); + if (parcel->getSellWithObjects()) + { + mSaleInfoForSaleObjects->setVisible(true); + mSaleInfoForSaleNoObjects->setVisible(false); + } + else + { + mSaleInfoForSaleObjects->setVisible(false); + mSaleInfoForSaleNoObjects->setVisible(true); + } + mSaleInfoNotForSale->setVisible(false); + + F32 cost_per_sqm = 0.0f; + if (area > 0) + { + cost_per_sqm = (F32)parcel->getSalePrice() / (F32)area; + } + + S32 price = parcel->getSalePrice(); + mSaleInfoForSale1->setTextArg("[PRICE]", LLResMgr::getInstance()->getMonetaryString(price)); + mSaleInfoForSale1->setTextArg("[PRICE_PER_SQM]", llformat("%.1f", cost_per_sqm)); + if (can_be_sold) + { + mBtnStopSellLand->setVisible(true); + } + } + else + { + mSaleInfoForSale1->setVisible(false); + mSaleInfoForSale2->setVisible(false); + mSaleInfoForSaleObjects->setVisible(false); + mSaleInfoForSaleNoObjects->setVisible(false); + mSaleInfoNotForSale->setVisible(true); + if (can_be_sold) + { + mBtnSellLand->setVisible(true); + } + } + + refreshNames(); + + mBtnBuyLand->setEnabled( + LLViewerParcelMgr::getInstance()->canAgentBuyParcel(parcel, false)); + mBtnScriptLimits->setEnabled(true); +// LLViewerParcelMgr::getInstance()->canAgentBuyParcel(parcel, false)); + mBtnBuyGroupLand->setEnabled( + LLViewerParcelMgr::getInstance()->canAgentBuyParcel(parcel, true)); + + if(region_owner) + { + mBtnReclaimLand->setEnabled( + !is_public && (parcel->getOwnerID() != gAgent.getID())); + } + else + { + bool is_owner_release = LLViewerParcelMgr::isParcelOwnedByAgent(parcel, GP_LAND_RELEASE); + bool is_manager_release = (gAgent.canManageEstate() && + regionp && + (parcel->getOwnerID() != regionp->getOwner())); + bool can_release = is_owner_release || is_manager_release; + mBtnReleaseLand->setEnabled( can_release ); + } + + bool use_pass = parcel->getOwnerID()!= gAgent.getID() && parcel->getParcelFlag(PF_USE_PASS_LIST) && !LLViewerParcelMgr::getInstance()->isCollisionBanned();; + mBtnBuyPass->setEnabled(use_pass); + + } +} + +// public +void LLPanelLandGeneral::refreshNames() +{ + LLParcel *parcel = mParcel->getParcel(); + if (!parcel) + { + mTextOwner->setText(LLStringUtil::null); + mTextGroup->setText(LLStringUtil::null); + return; + } + + std::string owner; + if (parcel->getIsGroupOwned()) + { + owner = getString("group_owned_text"); + } + else + { + // Figure out the owner's name + owner = LLSLURL("agent", parcel->getOwnerID(), "inspect").getSLURLString(); + } + + if(LLParcel::OS_LEASE_PENDING == parcel->getOwnershipStatus()) + { + owner += getString("sale_pending_text"); + } + mTextOwner->setText(owner); + + std::string group; + if (!parcel->getGroupID().isNull()) + { + group = LLSLURL("group", parcel->getGroupID(), "inspect").getSLURLString(); + } + mTextGroup->setText(group); + + if (parcel->getForSale()) + { + const LLUUID& auth_buyer_id = parcel->getAuthorizedBuyerID(); + if(auth_buyer_id.notNull()) + { + std::string name; + name = LLSLURL("agent", auth_buyer_id, "inspect").getSLURLString(); + mSaleInfoForSale2->setTextArg("[BUYER]", name); + } + else + { + mSaleInfoForSale2->setTextArg("[BUYER]", getString("anyone")); + } + } +} + + +// virtual +void LLPanelLandGeneral::draw() +{ + LLPanel::draw(); +} + +void LLPanelLandGeneral::onClickSetGroup() +{ + LLFloater* parent_floater = gFloaterView->getParentFloater(this); + + LLFloaterGroupPicker* fg = LLFloaterReg::showTypedInstance("group_picker", LLSD(gAgent.getID())); + if (fg) + { + fg->setSelectGroupCallback( boost::bind(&LLPanelLandGeneral::setGroup, this, _1 )); + if (parent_floater) + { + LLRect new_rect = gFloaterView->findNeighboringPosition(parent_floater, fg); + fg->setOrigin(new_rect.mLeft, new_rect.mBottom); + parent_floater->addDependentFloater(fg); + } + } +} + +void LLPanelLandGeneral::onClickProfile() +{ + LLParcel* parcel = mParcel->getParcel(); + if (!parcel) return; + + if (parcel->getIsGroupOwned()) + { + const LLUUID& group_id = parcel->getGroupID(); + LLGroupActions::show(group_id); + } + else + { + const LLUUID& avatar_id = parcel->getOwnerID(); + LLAvatarActions::showProfile(avatar_id); + } +} + +// public +void LLPanelLandGeneral::setGroup(const LLUUID& group_id) +{ + LLParcel* parcel = mParcel->getParcel(); + if (!parcel) return; + + // Set parcel properties and send message + parcel->setGroupID(group_id); + //parcel->setGroupName(group_name); + //mTextGroup->setText(group_name); + + // Send update + LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate(parcel); + + // Update UI + refresh(); +} + +// static +void LLPanelLandGeneral::onClickBuyLand(void* data) +{ + bool* for_group = (bool*)data; + LLViewerParcelMgr::getInstance()->startBuyLand(*for_group); +} + +// static +void LLPanelLandGeneral::onClickScriptLimits(void* data) +{ + LLPanelLandGeneral* panelp = (LLPanelLandGeneral*)data; + LLParcel* parcel = panelp->mParcel->getParcel(); + if(parcel != NULL) + { + LLFloaterReg::showInstance("script_limits"); + } +} + +// static +void LLPanelLandGeneral::onClickDeed(void*) +{ + //LLParcel* parcel = mParcel->getParcel(); + //if (parcel) + //{ + LLViewerParcelMgr::getInstance()->startDeedLandToGroup(); + //} +} + +// static +void LLPanelLandGeneral::onClickRelease(void*) +{ + LLViewerParcelMgr::getInstance()->startReleaseLand(); +} + +// static +void LLPanelLandGeneral::onClickReclaim(void*) +{ + LL_DEBUGS() << "LLPanelLandGeneral::onClickReclaim()" << LL_ENDL; + LLViewerParcelMgr::getInstance()->reclaimParcel(); +} + +// static +bool LLPanelLandGeneral::enableBuyPass(void* data) +{ + LLPanelLandGeneral* panelp = (LLPanelLandGeneral*)data; + LLParcel* parcel = panelp != NULL ? panelp->mParcel->getParcel() : LLViewerParcelMgr::getInstance()->getParcelSelection()->getParcel(); + return (parcel != NULL) && (parcel->getParcelFlag(PF_USE_PASS_LIST) && !LLViewerParcelMgr::getInstance()->isCollisionBanned()); +} + + +// static +void LLPanelLandGeneral::onClickBuyPass(void* data) +{ + LLPanelLandGeneral* panelp = (LLPanelLandGeneral*)data; + LLParcel* parcel = panelp != NULL ? panelp->mParcel->getParcel() : LLViewerParcelMgr::getInstance()->getParcelSelection()->getParcel(); + + if (!parcel) return; + + S32 pass_price = parcel->getPassPrice(); + std::string parcel_name = parcel->getName(); + F32 pass_hours = parcel->getPassHours(); + + std::string cost, time; + cost = llformat("%d", pass_price); + time = llformat("%.2f", pass_hours); + + LLSD args; + args["COST"] = cost; + args["PARCEL_NAME"] = parcel_name; + args["TIME"] = time; + + // creating pointer on selection to avoid deselection of parcel until we are done with buying pass (EXT-6464) + sSelectionForBuyPass = LLViewerParcelMgr::getInstance()->getParcelSelection(); + LLNotificationsUtil::add("LandBuyPass", args, LLSD(), cbBuyPass); +} + +// static +void LLPanelLandGeneral::onClickStartAuction(void* data) +{ + LLPanelLandGeneral* panelp = (LLPanelLandGeneral*)data; + LLParcel* parcelp = panelp->mParcel->getParcel(); + if(parcelp) + { + if(parcelp->getForSale()) + { + LLNotificationsUtil::add("CannotStartAuctionAlreadyForSale"); + } + else + { + //LLFloaterAuction::showInstance(); + LLFloaterReg::showInstance("auction"); + } + } +} + +// static +bool LLPanelLandGeneral::cbBuyPass(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (0 == option) + { + // User clicked OK + LLViewerParcelMgr::getInstance()->buyPass(); + } + // we are done with buying pass, additional selection is no longer needed + sSelectionForBuyPass = NULL; + return false; +} + +// static +void LLPanelLandGeneral::onCommitAny(LLUICtrl *ctrl, void *userdata) +{ + LLPanelLandGeneral *panelp = (LLPanelLandGeneral *)userdata; + + LLParcel* parcel = panelp->mParcel->getParcel(); + if (!parcel) + { + return; + } + + // Extract data from UI + std::string name = panelp->mEditName->getText(); + std::string desc = panelp->mEditDesc->getText(); + + // Valid data from UI + + // Stuff data into selected parcel + parcel->setName(name); + parcel->setDesc(desc); + + bool allow_deed_to_group= panelp->mCheckDeedToGroup->get(); + bool contribute_with_deed = panelp->mCheckContributeWithDeed->get(); + + parcel->setParcelFlag(PF_ALLOW_DEED_TO_GROUP, allow_deed_to_group); + parcel->setContributeWithDeed(contribute_with_deed); + + // Send update to server + LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate( parcel ); + + // Might have changed properties, so let's redraw! + panelp->refresh(); +} + +// static +void LLPanelLandGeneral::onClickSellLand(void* data) +{ + LLViewerParcelMgr::getInstance()->startSellLand(); + LLPanelLandGeneral *panelp = (LLPanelLandGeneral *)data; + panelp->refresh(); +} + +// static +void LLPanelLandGeneral::onClickStopSellLand(void* data) +{ + LLPanelLandGeneral* panelp = (LLPanelLandGeneral*)data; + LLParcel* parcel = panelp->mParcel->getParcel(); + + parcel->setParcelFlag(PF_FOR_SALE, false); + parcel->setSalePrice(0); + parcel->setAuthorizedBuyerID(LLUUID::null); + + LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate(parcel); +} + +//--------------------------------------------------------------------------- +// LLPanelLandObjects +//--------------------------------------------------------------------------- +LLPanelLandObjects::LLPanelLandObjects(LLParcelSelectionHandle& parcel) + : LLPanel(), + + mParcel(parcel), + mParcelObjectBonus(NULL), + mSWTotalObjects(NULL), + mObjectContribution(NULL), + mTotalObjects(NULL), + mOwnerObjects(NULL), + mBtnShowOwnerObjects(NULL), + mBtnReturnOwnerObjects(NULL), + mGroupObjects(NULL), + mBtnShowGroupObjects(NULL), + mBtnReturnGroupObjects(NULL), + mOtherObjects(NULL), + mBtnShowOtherObjects(NULL), + mBtnReturnOtherObjects(NULL), + mSelectedObjects(NULL), + mCleanOtherObjectsTime(NULL), + mOtherTime(0), + mBtnRefresh(NULL), + mBtnReturnOwnerList(NULL), + mOwnerList(NULL), + mFirstReply(true), + mSelectedCount(0), + mSelectedIsGroup(false) +{ +} + + + +bool LLPanelLandObjects::postBuild() +{ + + mFirstReply = true; + mParcelObjectBonus = getChild("parcel_object_bonus"); + mSWTotalObjects = getChild("objects_available"); + mObjectContribution = getChild("object_contrib_text"); + mTotalObjects = getChild("total_objects_text"); + mOwnerObjects = getChild("owner_objects_text"); + + mBtnShowOwnerObjects = getChild("ShowOwner"); + mBtnShowOwnerObjects->setClickedCallback(onClickShowOwnerObjects, this); + + mBtnReturnOwnerObjects = getChild("ReturnOwner..."); + mBtnReturnOwnerObjects->setClickedCallback(onClickReturnOwnerObjects, this); + + mGroupObjects = getChild("group_objects_text"); + mBtnShowGroupObjects = getChild("ShowGroup"); + mBtnShowGroupObjects->setClickedCallback(onClickShowGroupObjects, this); + + mBtnReturnGroupObjects = getChild("ReturnGroup..."); + mBtnReturnGroupObjects->setClickedCallback(onClickReturnGroupObjects, this); + + mOtherObjects = getChild("other_objects_text"); + mBtnShowOtherObjects = getChild("ShowOther"); + mBtnShowOtherObjects->setClickedCallback(onClickShowOtherObjects, this); + + mBtnReturnOtherObjects = getChild("ReturnOther..."); + mBtnReturnOtherObjects->setClickedCallback(onClickReturnOtherObjects, this); + + mSelectedObjects = getChild("selected_objects_text"); + mCleanOtherObjectsTime = getChild("clean other time"); + + mCleanOtherObjectsTime->setFocusLostCallback(boost::bind(onLostFocus, _1, this)); + mCleanOtherObjectsTime->setCommitCallback(onCommitClean, this); + getChild("clean other time")->setPrevalidate(LLTextValidate::validateNonNegativeS32); + + mBtnRefresh = getChild("Refresh List"); + mBtnRefresh->setClickedCallback(onClickRefresh, this); + + mBtnReturnOwnerList = getChild("Return objects..."); + mBtnReturnOwnerList->setClickedCallback(onClickReturnOwnerList, this); + + mIconAvatarOnline = LLUIImageList::getInstance()->getUIImage("icon_avatar_online.tga", 0); + mIconAvatarOffline = LLUIImageList::getInstance()->getUIImage("icon_avatar_offline.tga", 0); + mIconGroup = LLUIImageList::getInstance()->getUIImage("icon_group.tga", 0); + + mOwnerList = getChild("owner list"); + mOwnerList->setIsFriendCallback(LLAvatarActions::isFriend); + mOwnerList->sortByColumnIndex(3, false); + childSetCommitCallback("owner list", onCommitList, this); + mOwnerList->setDoubleClickCallback(onDoubleClickOwner, this); + mOwnerList->setContextMenu(LLScrollListCtrl::MENU_AVATAR); + + return true; +} + + + + +// virtual +LLPanelLandObjects::~LLPanelLandObjects() +{ } + +// static +void LLPanelLandObjects::onDoubleClickOwner(void *userdata) +{ + LLPanelLandObjects *self = (LLPanelLandObjects *)userdata; + + LLScrollListItem* item = self->mOwnerList->getFirstSelected(); + if (item) + { + LLUUID owner_id = item->getUUID(); + // Look up the selected name, for future dialog box use. + const LLScrollListCell* cell; + cell = item->getColumn(1); + if (!cell) + { + return; + } + // Is this a group? + bool is_group = cell->getValue().asString() == OWNER_GROUP; + if (is_group) + { + LLGroupActions::show(owner_id); + } + else + { + LLAvatarActions::showProfile(owner_id); + } + } +} + +// public +void LLPanelLandObjects::refresh() +{ + LLParcel *parcel = mParcel->getParcel(); + + mBtnShowOwnerObjects->setEnabled(false); + mBtnShowGroupObjects->setEnabled(false); + mBtnShowOtherObjects->setEnabled(false); + mBtnReturnOwnerObjects->setEnabled(false); + mBtnReturnGroupObjects->setEnabled(false); + mBtnReturnOtherObjects->setEnabled(false); + mCleanOtherObjectsTime->setEnabled(false); + mBtnRefresh-> setEnabled(false); + mBtnReturnOwnerList-> setEnabled(false); + + mSelectedOwners.clear(); + mOwnerList->deleteAllItems(); + mOwnerList->setEnabled(false); + + if (!parcel || gDisconnected) + { + mSWTotalObjects->setTextArg("[COUNT]", llformat("%d", 0)); + mSWTotalObjects->setTextArg("[TOTAL]", llformat("%d", 0)); + mSWTotalObjects->setTextArg("[AVAILABLE]", llformat("%d", 0)); + mObjectContribution->setTextArg("[COUNT]", llformat("%d", 0)); + mTotalObjects->setTextArg("[COUNT]", llformat("%d", 0)); + mOwnerObjects->setTextArg("[COUNT]", llformat("%d", 0)); + mGroupObjects->setTextArg("[COUNT]", llformat("%d", 0)); + mOtherObjects->setTextArg("[COUNT]", llformat("%d", 0)); + mSelectedObjects->setTextArg("[COUNT]", llformat("%d", 0)); + } + else + { + S32 sw_max = parcel->getSimWideMaxPrimCapacity(); + S32 sw_total = parcel->getSimWidePrimCount(); + S32 max = ll_round(parcel->getMaxPrimCapacity() * parcel->getParcelPrimBonus()); + S32 total = parcel->getPrimCount(); + S32 owned = parcel->getOwnerPrimCount(); + S32 group = parcel->getGroupPrimCount(); + S32 other = parcel->getOtherPrimCount(); + S32 selected = parcel->getSelectedPrimCount(); + F32 parcel_object_bonus = parcel->getParcelPrimBonus(); + mOtherTime = parcel->getCleanOtherTime(); + + // Can't have more than region max tasks, regardless of parcel + // object bonus factor. + LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); + if (region) + { + S32 max_tasks_per_region = (S32)region->getMaxTasks(); + sw_max = llmin(sw_max, max_tasks_per_region); + max = llmin(max, max_tasks_per_region); + } + + if (parcel_object_bonus != 1.0f) + { + mParcelObjectBonus->setVisible(true); + mParcelObjectBonus->setTextArg("[BONUS]", llformat("%.2f", parcel_object_bonus)); + } + else + { + mParcelObjectBonus->setVisible(false); + } + + if (sw_total > sw_max) + { + mSWTotalObjects->setText(getString("objects_deleted_text")); + mSWTotalObjects->setTextArg("[DELETED]", llformat("%d", sw_total - sw_max)); + } + else + { + mSWTotalObjects->setText(getString("objects_available_text")); + mSWTotalObjects->setTextArg("[AVAILABLE]", llformat("%d", sw_max - sw_total)); + } + mSWTotalObjects->setTextArg("[COUNT]", llformat("%d", sw_total)); + mSWTotalObjects->setTextArg("[MAX]", llformat("%d", sw_max)); + + mObjectContribution->setTextArg("[COUNT]", llformat("%d", max)); + mTotalObjects->setTextArg("[COUNT]", llformat("%d", total)); + mOwnerObjects->setTextArg("[COUNT]", llformat("%d", owned)); + mGroupObjects->setTextArg("[COUNT]", llformat("%d", group)); + mOtherObjects->setTextArg("[COUNT]", llformat("%d", other)); + mSelectedObjects->setTextArg("[COUNT]", llformat("%d", selected)); + mCleanOtherObjectsTime->setText(llformat("%d", mOtherTime)); + + bool can_return_owned = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_RETURN_GROUP_OWNED); + bool can_return_group_set = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_RETURN_GROUP_SET); + bool can_return_other = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_RETURN_NON_GROUP); + + if (can_return_owned || can_return_group_set || can_return_other) + { + if (owned && can_return_owned) + { + mBtnShowOwnerObjects->setEnabled(true); + mBtnReturnOwnerObjects->setEnabled(true); + } + if (group && can_return_group_set) + { + mBtnShowGroupObjects->setEnabled(true); + mBtnReturnGroupObjects->setEnabled(true); + } + if (other && can_return_other) + { + mBtnShowOtherObjects->setEnabled(true); + mBtnReturnOtherObjects->setEnabled(true); + } + + mCleanOtherObjectsTime->setEnabled(true); + mBtnRefresh->setEnabled(true); + } + } +} + +// virtual +void LLPanelLandObjects::draw() +{ + LLPanel::draw(); +} + +void send_other_clean_time_message(S32 parcel_local_id, S32 other_clean_time) +{ + LLMessageSystem *msg = gMessageSystem; + + LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); + if (!region) return; + + msg->newMessageFast(_PREHASH_ParcelSetOtherCleanTime); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ParcelData); + msg->addS32Fast(_PREHASH_LocalID, parcel_local_id); + msg->addS32Fast(_PREHASH_OtherCleanTime, other_clean_time); + + msg->sendReliable(region->getHost()); +} + +void send_return_objects_message(S32 parcel_local_id, S32 return_type, + uuid_list_t* owner_ids = NULL) +{ + LLMessageSystem *msg = gMessageSystem; + + LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); + if (!region) return; + + msg->newMessageFast(_PREHASH_ParcelReturnObjects); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ParcelData); + msg->addS32Fast(_PREHASH_LocalID, parcel_local_id); + msg->addU32Fast(_PREHASH_ReturnType, (U32) return_type); + + // Dummy task id, not used + msg->nextBlock("TaskIDs"); + msg->addUUID("TaskID", LLUUID::null); + + // Throw all return ids into the packet. + // TODO: Check for too many ids. + if (owner_ids) + { + uuid_list_t::iterator end = owner_ids->end(); + for (uuid_list_t::iterator it = owner_ids->begin(); + it != end; + ++it) + { + msg->nextBlockFast(_PREHASH_OwnerIDs); + msg->addUUIDFast(_PREHASH_OwnerID, (*it)); + } + } + else + { + msg->nextBlockFast(_PREHASH_OwnerIDs); + msg->addUUIDFast(_PREHASH_OwnerID, LLUUID::null); + } + + msg->sendReliable(region->getHost()); +} + +bool LLPanelLandObjects::callbackReturnOwnerObjects(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + LLParcel *parcel = mParcel->getParcel(); + if (0 == option) + { + if (parcel) + { + LLUUID owner_id = parcel->getOwnerID(); + LLSD args; + if (owner_id == gAgentID) + { + LLNotificationsUtil::add("OwnedObjectsReturned"); + } + else + { + args["NAME"] = LLSLURL("agent", owner_id, "completename").getSLURLString(); + LLNotificationsUtil::add("OtherObjectsReturned", args); + } + send_return_objects_message(parcel->getLocalID(), RT_OWNER); + } + } + + LLSelectMgr::getInstance()->unhighlightAll(); + LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate( parcel ); + refresh(); + return false; +} + +bool LLPanelLandObjects::callbackReturnGroupObjects(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + LLParcel *parcel = mParcel->getParcel(); + if (0 == option) + { + if (parcel) + { + std::string group_name; + gCacheName->getGroupName(parcel->getGroupID(), group_name); + LLSD args; + args["GROUPNAME"] = group_name; + LLNotificationsUtil::add("GroupObjectsReturned", args); + send_return_objects_message(parcel->getLocalID(), RT_GROUP); + } + } + LLSelectMgr::getInstance()->unhighlightAll(); + LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate( parcel ); + refresh(); + return false; +} + +bool LLPanelLandObjects::callbackReturnOtherObjects(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + LLParcel *parcel = mParcel->getParcel(); + if (0 == option) + { + if (parcel) + { + LLNotificationsUtil::add("UnOwnedObjectsReturned"); + send_return_objects_message(parcel->getLocalID(), RT_OTHER); + } + } + LLSelectMgr::getInstance()->unhighlightAll(); + LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate( parcel ); + refresh(); + return false; +} + +bool LLPanelLandObjects::callbackReturnOwnerList(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + LLParcel *parcel = mParcel->getParcel(); + if (0 == option) + { + if (parcel) + { + // Make sure we have something selected. + uuid_list_t::iterator selected = mSelectedOwners.begin(); + if (selected != mSelectedOwners.end()) + { + LLSD args; + if (mSelectedIsGroup) + { + args["GROUPNAME"] = mSelectedName; + LLNotificationsUtil::add("GroupObjectsReturned", args); + } + else + { + args["NAME"] = mSelectedName; + LLNotificationsUtil::add("OtherObjectsReturned2", args); + } + + send_return_objects_message(parcel->getLocalID(), RT_LIST, &(mSelectedOwners)); + } + } + } + LLSelectMgr::getInstance()->unhighlightAll(); + LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate( parcel ); + refresh(); + return false; +} + + +// static +void LLPanelLandObjects::onClickReturnOwnerList(void* userdata) +{ + LLPanelLandObjects *self = (LLPanelLandObjects *)userdata; + + LLParcel* parcelp = self->mParcel->getParcel(); + if (!parcelp) return; + + // Make sure we have something selected. + if (self->mSelectedOwners.empty()) + { + return; + } + //uuid_list_t::iterator selected_itr = self->mSelectedOwners.begin(); + //if (selected_itr == self->mSelectedOwners.end()) return; + + send_parcel_select_objects(parcelp->getLocalID(), RT_LIST, &(self->mSelectedOwners)); + + LLSD args; + args["NAME"] = self->mSelectedName; + args["N"] = llformat("%d",self->mSelectedCount); + if (self->mSelectedIsGroup) + { + LLNotificationsUtil::add("ReturnObjectsDeededToGroup", args, LLSD(), boost::bind(&LLPanelLandObjects::callbackReturnOwnerList, self, _1, _2)); + } + else + { + LLNotificationsUtil::add("ReturnObjectsOwnedByUser", args, LLSD(), boost::bind(&LLPanelLandObjects::callbackReturnOwnerList, self, _1, _2)); + } +} + + +// static +void LLPanelLandObjects::onClickRefresh(void* userdata) +{ + LLPanelLandObjects *self = (LLPanelLandObjects*)userdata; + + LLMessageSystem *msg = gMessageSystem; + + LLParcel* parcel = self->mParcel->getParcel(); + if (!parcel) return; + + LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); + if (!region) return; + + self->mBtnRefresh->setEnabled(false); + + // ready the list for results + self->mOwnerList->deleteAllItems(); + self->mOwnerList->setCommentText(LLTrans::getString("Searching")); + self->mOwnerList->setEnabled(false); + self->mFirstReply = true; + + // send the message + msg->newMessageFast(_PREHASH_ParcelObjectOwnersRequest); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ParcelData); + msg->addS32Fast(_PREHASH_LocalID, parcel->getLocalID()); + + msg->sendReliable(region->getHost()); +} + +// static +void LLPanelLandObjects::processParcelObjectOwnersReply(LLMessageSystem *msg, void **) +{ + LLPanelLandObjects* self = LLFloaterLand::getCurrentPanelLandObjects(); + + if (!self) + { + LL_WARNS() << "Received message for nonexistent LLPanelLandObject" + << LL_ENDL; + return; + } + + const LLFontGL* FONT = LLFontGL::getFontSansSerif(); + + // Extract all of the owners. + S32 rows = msg->getNumberOfBlocksFast(_PREHASH_Data); + //uuid_list_t return_ids; + LLUUID owner_id; + bool is_group_owned; + S32 object_count; + U32 most_recent_time = 0; + bool is_online; + std::string object_count_str; + //bool b_need_refresh = false; + + // If we were waiting for the first reply, clear the "Searching..." text. + if (self->mFirstReply) + { + self->mOwnerList->deleteAllItems(); + self->mFirstReply = false; + } + + for(S32 i = 0; i < rows; ++i) + { + msg->getUUIDFast(_PREHASH_Data, _PREHASH_OwnerID, owner_id, i); + msg->getBOOLFast(_PREHASH_Data, _PREHASH_IsGroupOwned, is_group_owned, i); + msg->getS32Fast (_PREHASH_Data, _PREHASH_Count, object_count, i); + msg->getBOOLFast(_PREHASH_Data, _PREHASH_OnlineStatus, is_online, i); + if(msg->has("DataExtended")) + { + msg->getU32("DataExtended", "TimeStamp", most_recent_time, i); + } + + if (owner_id.isNull()) + { + continue; + } + + LLNameListCtrl::NameItem item_params; + item_params.value = owner_id; + item_params.target = is_group_owned ? LLNameListCtrl::GROUP : LLNameListCtrl::INDIVIDUAL; + + if (is_group_owned) + { + item_params.columns.add().type("icon").value(self->mIconGroup->getName()).column("type"); + item_params.columns.add().value(OWNER_GROUP).font(FONT).column("online_status"); + } + else if (is_online) + { + item_params.columns.add().type("icon").value(self->mIconAvatarOnline->getName()).column("type"); + item_params.columns.add().value(OWNER_ONLINE).font(FONT).column("online_status"); + } + else // offline + { + item_params.columns.add().type("icon").value(self->mIconAvatarOffline->getName()).column("type"); + item_params.columns.add().value(OWNER_OFFLINE).font(FONT).column("online_status"); + } + + // Placeholder for name. + LLAvatarName av_name; + LLAvatarNameCache::get(owner_id, &av_name); + item_params.columns.add().value(av_name.getCompleteName()).font(FONT).column("name"); + + object_count_str = llformat("%d", object_count); + item_params.columns.add().value(object_count_str).font(FONT).column("count"); + item_params.columns.add().value(LLDate((time_t)most_recent_time)).font(FONT).column("mostrecent").type("date"); + + self->mOwnerList->addNameItemRow(item_params); + LL_DEBUGS() << "object owner " << owner_id << " (" << (is_group_owned ? "group" : "agent") + << ") owns " << object_count << " objects." << LL_ENDL; + } + + // check for no results + if (0 == self->mOwnerList->getItemCount()) + { + self->mOwnerList->setCommentText(LLTrans::getString("NoneFound")); + } + else + { + self->mOwnerList->setEnabled(true); + } + + self->mBtnRefresh->setEnabled(true); +} + +// static +void LLPanelLandObjects::onCommitList(LLUICtrl* ctrl, void* data) +{ + LLPanelLandObjects* self = (LLPanelLandObjects*)data; + + if (false == self->mOwnerList->getCanSelect()) + { + return; + } + LLScrollListItem *item = self->mOwnerList->getFirstSelected(); + if (item) + { + // Look up the selected name, for future dialog box use. + const LLScrollListCell* cell; + cell = item->getColumn(1); + if (!cell) + { + return; + } + // Is this a group? + self->mSelectedIsGroup = cell->getValue().asString() == OWNER_GROUP; + cell = item->getColumn(2); + self->mSelectedName = cell->getValue().asString(); + cell = item->getColumn(3); + self->mSelectedCount = atoi(cell->getValue().asString().c_str()); + + // Set the selection, and enable the return button. + self->mSelectedOwners.clear(); + self->mSelectedOwners.insert(item->getUUID()); + self->mBtnReturnOwnerList->setEnabled(true); + + // Highlight this user's objects + clickShowCore(self, RT_LIST, &(self->mSelectedOwners)); + } +} + +// static +void LLPanelLandObjects::clickShowCore(LLPanelLandObjects* self, S32 return_type, uuid_list_t* list) +{ + LLParcel* parcel = self->mParcel->getParcel(); + if (!parcel) return; + + send_parcel_select_objects(parcel->getLocalID(), return_type, list); +} + +// static +void LLPanelLandObjects::onClickShowOwnerObjects(void* userdata) +{ + clickShowCore((LLPanelLandObjects*)userdata, RT_OWNER); +} + +// static +void LLPanelLandObjects::onClickShowGroupObjects(void* userdata) +{ + clickShowCore((LLPanelLandObjects*)userdata, (RT_GROUP)); +} + +// static +void LLPanelLandObjects::onClickShowOtherObjects(void* userdata) +{ + clickShowCore((LLPanelLandObjects*)userdata, RT_OTHER); +} + +// static +void LLPanelLandObjects::onClickReturnOwnerObjects(void* userdata) +{ + S32 owned = 0; + + LLPanelLandObjects* panelp = (LLPanelLandObjects*)userdata; + LLParcel* parcel = panelp->mParcel->getParcel(); + if (!parcel) return; + + owned = parcel->getOwnerPrimCount(); + + send_parcel_select_objects(parcel->getLocalID(), RT_OWNER); + + LLUUID owner_id = parcel->getOwnerID(); + + LLSD args; + args["N"] = llformat("%d",owned); + + if (owner_id == gAgent.getID()) + { + LLNotificationsUtil::add("ReturnObjectsOwnedBySelf", args, LLSD(), boost::bind(&LLPanelLandObjects::callbackReturnOwnerObjects, panelp, _1, _2)); + } + else + { + args["NAME"] = LLSLURL("agent", owner_id, "completename").getSLURLString(); + LLNotificationsUtil::add("ReturnObjectsOwnedByUser", args, LLSD(), boost::bind(&LLPanelLandObjects::callbackReturnOwnerObjects, panelp, _1, _2)); + } +} + +// static +void LLPanelLandObjects::onClickReturnGroupObjects(void* userdata) +{ + LLPanelLandObjects* panelp = (LLPanelLandObjects*)userdata; + LLParcel* parcel = panelp->mParcel->getParcel(); + if (!parcel) return; + + send_parcel_select_objects(parcel->getLocalID(), RT_GROUP); + + std::string group_name; + gCacheName->getGroupName(parcel->getGroupID(), group_name); + + LLSD args; + args["NAME"] = group_name; + args["N"] = llformat("%d", parcel->getGroupPrimCount()); + + // create and show confirmation textbox + LLNotificationsUtil::add("ReturnObjectsDeededToGroup", args, LLSD(), boost::bind(&LLPanelLandObjects::callbackReturnGroupObjects, panelp, _1, _2)); +} + +// static +void LLPanelLandObjects::onClickReturnOtherObjects(void* userdata) +{ + S32 other = 0; + + LLPanelLandObjects* panelp = (LLPanelLandObjects*)userdata; + LLParcel* parcel = panelp->mParcel->getParcel(); + if (!parcel) return; + + other = parcel->getOtherPrimCount(); + + send_parcel_select_objects(parcel->getLocalID(), RT_OTHER); + + LLSD args; + args["N"] = llformat("%d", other); + + if (parcel->getIsGroupOwned()) + { + std::string group_name; + gCacheName->getGroupName(parcel->getGroupID(), group_name); + args["NAME"] = group_name; + + LLNotificationsUtil::add("ReturnObjectsNotOwnedByGroup", args, LLSD(), boost::bind(&LLPanelLandObjects::callbackReturnOtherObjects, panelp, _1, _2)); + } + else + { + LLUUID owner_id = parcel->getOwnerID(); + + if (owner_id == gAgent.getID()) + { + LLNotificationsUtil::add("ReturnObjectsNotOwnedBySelf", args, LLSD(), boost::bind(&LLPanelLandObjects::callbackReturnOtherObjects, panelp, _1, _2)); + } + else + { + args["NAME"] = LLSLURL("agent", owner_id, "completename").getSLURLString(); + LLNotificationsUtil::add("ReturnObjectsNotOwnedByUser", args, LLSD(), boost::bind(&LLPanelLandObjects::callbackReturnOtherObjects, panelp, _1, _2)); + } + } +} + +// static +void LLPanelLandObjects::onLostFocus(LLFocusableElement* caller, void* user_data) +{ + onCommitClean((LLUICtrl*)caller, user_data); +} + +// static +void LLPanelLandObjects::onCommitClean(LLUICtrl *caller, void* user_data) +{ + LLPanelLandObjects *lop = (LLPanelLandObjects *)user_data; + LLParcel* parcel = lop->mParcel->getParcel(); + if (parcel) + { + S32 return_time = atoi(lop->mCleanOtherObjectsTime->getText().c_str()); + // Only send return time if it has changed + if (return_time != lop->mOtherTime) + { + lop->mOtherTime = return_time; + + parcel->setCleanOtherTime(lop->mOtherTime); + send_other_clean_time_message(parcel->getLocalID(), lop->mOtherTime); + } +} +} + + +//--------------------------------------------------------------------------- +// LLPanelLandOptions +//--------------------------------------------------------------------------- + +LLPanelLandOptions::LLPanelLandOptions(LLParcelSelectionHandle& parcel) +: LLPanel(), + mCheckEditObjects(NULL), + mCheckEditGroupObjects(NULL), + mCheckAllObjectEntry(NULL), + mCheckGroupObjectEntry(NULL), + mCheckSafe(NULL), + mCheckFly(NULL), + mCheckGroupScripts(NULL), + mCheckOtherScripts(NULL), + mCheckShowDirectory(NULL), + mCategoryCombo(NULL), + mLandingTypeCombo(NULL), + mSnapshotCtrl(NULL), + mLocationText(NULL), + mSeeAvatarsText(NULL), + mSetBtn(NULL), + mClearBtn(NULL), + mMatureCtrl(NULL), + mPushRestrictionCtrl(NULL), + mSeeAvatarsCtrl(NULL), + mParcel(parcel) +{ +} + + +bool LLPanelLandOptions::postBuild() +{ + mCheckEditObjects = getChild( "edit objects check"); + childSetCommitCallback("edit objects check", onCommitAny, this); + + mCheckEditGroupObjects = getChild( "edit group objects check"); + childSetCommitCallback("edit group objects check", onCommitAny, this); + + mCheckAllObjectEntry = getChild( "all object entry check"); + childSetCommitCallback("all object entry check", onCommitAny, this); + + mCheckGroupObjectEntry = getChild( "group object entry check"); + childSetCommitCallback("group object entry check", onCommitAny, this); + + mCheckGroupScripts = getChild( "check group scripts"); + childSetCommitCallback("check group scripts", onCommitAny, this); + + + mCheckFly = getChild( "check fly"); + childSetCommitCallback("check fly", onCommitAny, this); + + + mCheckOtherScripts = getChild( "check other scripts"); + childSetCommitCallback("check other scripts", onCommitAny, this); + + + mCheckSafe = getChild( "check safe"); + childSetCommitCallback("check safe", onCommitAny, this); + + + mPushRestrictionCtrl = getChild( "PushRestrictCheck"); + childSetCommitCallback("PushRestrictCheck", onCommitAny, this); + + mSeeAvatarsCtrl = getChild( "SeeAvatarsCheck"); + childSetCommitCallback("SeeAvatarsCheck", onCommitAny, this); + + mSeeAvatarsText = getChild("allow_see_label"); + if (mSeeAvatarsText) + { + mSeeAvatarsText->setShowCursorHand(false); + mSeeAvatarsText->setSoundFlags(LLView::MOUSE_UP); + mSeeAvatarsText->setClickedCallback(boost::bind(&toggleSeeAvatars, this)); + } + + mCheckShowDirectory = getChild( "ShowDirectoryCheck"); + childSetCommitCallback("ShowDirectoryCheck", onCommitAny, this); + + + mCategoryCombo = getChild( "land category"); + childSetCommitCallback("land category", onCommitAny, this); + + + mMatureCtrl = getChild( "MatureCheck"); + childSetCommitCallback("MatureCheck", onCommitAny, this); + + if (gAgent.wantsPGOnly()) + { + // Disable these buttons if they are PG (Teen) users + mMatureCtrl->setVisible(false); + mMatureCtrl->setEnabled(false); + } + + + mSnapshotCtrl = getChild("snapshot_ctrl"); + if (mSnapshotCtrl) + { + mSnapshotCtrl->setCommitCallback( onCommitAny, this ); + mSnapshotCtrl->setAllowNoTexture ( true ); + mSnapshotCtrl->setImmediateFilterPermMask(PERM_COPY | PERM_TRANSFER); + mSnapshotCtrl->setDnDFilterPermMask(PERM_COPY | PERM_TRANSFER); + } + else + { + LL_WARNS() << "LLUICtrlFactory::getTexturePickerByName() returned NULL for 'snapshot_ctrl'" << LL_ENDL; + } + + + mLocationText = getChild("landing_point"); + + mSetBtn = getChild("Set"); + mSetBtn->setClickedCallback(onClickSet, this); + + + mClearBtn = getChild("Clear"); + mClearBtn->setClickedCallback(onClickClear, this); + + + mLandingTypeCombo = getChild( "landing type"); + childSetCommitCallback("landing type", onCommitAny, this); + + return true; +} + + +// virtual +LLPanelLandOptions::~LLPanelLandOptions() +{ } + + +// virtual +void LLPanelLandOptions::refresh() +{ + refreshSearch(); + + LLParcel *parcel = mParcel->getParcel(); + if (!parcel || gDisconnected) + { + mCheckEditObjects ->set(false); + mCheckEditObjects ->setEnabled(false); + + mCheckEditGroupObjects ->set(false); + mCheckEditGroupObjects ->setEnabled(false); + + mCheckAllObjectEntry ->set(false); + mCheckAllObjectEntry ->setEnabled(false); + + mCheckGroupObjectEntry ->set(false); + mCheckGroupObjectEntry ->setEnabled(false); + + mCheckSafe ->set(false); + mCheckSafe ->setEnabled(false); + + mCheckFly ->set(false); + mCheckFly ->setEnabled(false); + + mCheckGroupScripts ->set(false); + mCheckGroupScripts ->setEnabled(false); + + mCheckOtherScripts ->set(false); + mCheckOtherScripts ->setEnabled(false); + + mPushRestrictionCtrl->set(false); + mPushRestrictionCtrl->setEnabled(false); + + mSeeAvatarsCtrl->set(true); + mSeeAvatarsCtrl->setEnabled(false); + mSeeAvatarsText->setEnabled(false); + + mLandingTypeCombo->setCurrentByIndex(0); + mLandingTypeCombo->setEnabled(false); + + mSnapshotCtrl->setImageAssetID(LLUUID::null); + mSnapshotCtrl->setEnabled(false); + + mLocationText->setTextArg("[LANDING]", getString("landing_point_none")); + mSetBtn->setEnabled(false); + mClearBtn->setEnabled(false); + + mMatureCtrl->setEnabled(false); + } + else + { + // something selected, hooray! + + // Display options + bool can_change_options = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_OPTIONS); + mCheckEditObjects ->set( parcel->getAllowModify() ); + mCheckEditObjects ->setEnabled( can_change_options ); + + mCheckEditGroupObjects ->set( parcel->getAllowGroupModify() || parcel->getAllowModify()); + mCheckEditGroupObjects ->setEnabled( can_change_options && !parcel->getAllowModify() ); // If others edit is enabled, then this is explicitly enabled. + + mCheckAllObjectEntry ->set( parcel->getAllowAllObjectEntry() ); + mCheckAllObjectEntry ->setEnabled( can_change_options ); + + mCheckGroupObjectEntry ->set( parcel->getAllowGroupObjectEntry() || parcel->getAllowAllObjectEntry()); + mCheckGroupObjectEntry ->setEnabled( can_change_options && !parcel->getAllowAllObjectEntry() ); + + mCheckSafe ->set( !parcel->getAllowDamage() ); + mCheckSafe ->setEnabled( can_change_options ); + + mCheckFly ->set( parcel->getAllowFly() ); + mCheckFly ->setEnabled( can_change_options ); + + mCheckGroupScripts ->set( parcel->getAllowGroupScripts() || parcel->getAllowOtherScripts()); + mCheckGroupScripts ->setEnabled( can_change_options && !parcel->getAllowOtherScripts()); + + mCheckOtherScripts ->set( parcel->getAllowOtherScripts() ); + mCheckOtherScripts ->setEnabled( can_change_options ); + + mPushRestrictionCtrl->set( parcel->getRestrictPushObject() ); + if(parcel->getRegionPushOverride()) + { + mPushRestrictionCtrl->setLabel(getString("push_restrict_region_text")); + mPushRestrictionCtrl->setEnabled(false); + mPushRestrictionCtrl->set(true); + } + else + { + mPushRestrictionCtrl->setLabel(getString("push_restrict_text")); + mPushRestrictionCtrl->setEnabled(can_change_options); + } + + mSeeAvatarsCtrl->set(parcel->getSeeAVs()); + mSeeAvatarsCtrl->setEnabled(can_change_options && parcel->getHaveNewParcelLimitData()); + mSeeAvatarsText->setEnabled(can_change_options && parcel->getHaveNewParcelLimitData()); + + bool can_change_landing_point = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, + GP_LAND_SET_LANDING_POINT); + mLandingTypeCombo->setCurrentByIndex((S32)parcel->getLandingType()); + mLandingTypeCombo->setEnabled( can_change_landing_point ); + + bool can_change_identity = + LLViewerParcelMgr::isParcelModifiableByAgent( + parcel, GP_LAND_CHANGE_IDENTITY); + mSnapshotCtrl->setImageAssetID(parcel->getSnapshotID()); + mSnapshotCtrl->setEnabled( can_change_identity ); + + // find out where we're looking and convert that to an angle in degrees on a regular compass (not the internal representation) + LLVector3 user_look_at = parcel->getUserLookAt(); + U32 user_look_at_angle = ( (U32)( ( atan2(user_look_at[1], -user_look_at[0]) + F_PI * 2 ) * RAD_TO_DEG + 0.5) - 90) % 360; + + LLVector3 pos = parcel->getUserLocation(); + if (pos.isExactlyZero()) + { + mLocationText->setTextArg("[LANDING]", getString("landing_point_none")); + } + else + { + mLocationText->setTextArg("[LANDING]",llformat("%d, %d, %d (%d\xC2\xB0)", + ll_round(pos.mV[VX]), + ll_round(pos.mV[VY]), + ll_round(pos.mV[VZ]), + user_look_at_angle)); + } + + mSetBtn->setEnabled( can_change_landing_point ); + mClearBtn->setEnabled( can_change_landing_point ); + + if (gAgent.wantsPGOnly()) + { + // Disable these buttons if they are PG (Teen) users + mMatureCtrl->setVisible(false); + mMatureCtrl->setEnabled(false); + } + else + { + // not teen so fill in the data for the maturity control + mMatureCtrl->setVisible(true); + LLStyle::Params style; + style.image(LLUI::getUIImage(gFloaterView->getParentFloater(this)->getString("maturity_icon_moderate"))); + LLCheckBoxWithTBAcess* fullaccess_mature_ctrl = (LLCheckBoxWithTBAcess*)mMatureCtrl; + fullaccess_mature_ctrl->getTextBox()->setText(LLStringExplicit("")); + fullaccess_mature_ctrl->getTextBox()->appendImageSegment(style); + fullaccess_mature_ctrl->getTextBox()->appendText(getString("mature_check_mature"), false); + fullaccess_mature_ctrl->setToolTip(getString("mature_check_mature_tooltip")); + fullaccess_mature_ctrl->reshape(fullaccess_mature_ctrl->getRect().getWidth(), fullaccess_mature_ctrl->getRect().getHeight(), false); + + // they can see the checkbox, but its disposition depends on the + // state of the region + LLViewerRegion* regionp = LLViewerParcelMgr::getInstance()->getSelectionRegion(); + if (regionp) + { + if (regionp->getSimAccess() == SIM_ACCESS_PG) + { + mMatureCtrl->setEnabled(false); + mMatureCtrl->set(false); + } + else if (regionp->getSimAccess() == SIM_ACCESS_MATURE) + { + mMatureCtrl->setEnabled(can_change_identity); + mMatureCtrl->set(parcel->getMaturePublish()); + } + else if (regionp->getSimAccess() == SIM_ACCESS_ADULT) + { + mMatureCtrl->setEnabled(false); + mMatureCtrl->set(true); + mMatureCtrl->setLabel(getString("mature_check_adult")); + mMatureCtrl->setToolTip(getString("mature_check_adult_tooltip")); + } + } + } + } +} + +// virtual +void LLPanelLandOptions::draw() +{ + LLPanel::draw(); +} + + +// private +void LLPanelLandOptions::refreshSearch() +{ + LLParcel *parcel = mParcel->getParcel(); + if (!parcel || gDisconnected) + { + mCheckShowDirectory->set(false); + mCheckShowDirectory->setEnabled(false); + + const std::string& none_string = LLParcel::getCategoryString(LLParcel::C_NONE); + mCategoryCombo->setValue(none_string); + mCategoryCombo->setEnabled(false); + return; + } + + LLViewerRegion* region = + LLViewerParcelMgr::getInstance()->getSelectionRegion(); + + bool can_change = + LLViewerParcelMgr::isParcelModifiableByAgent( + parcel, GP_LAND_FIND_PLACES) + && region + && !(region->getRegionFlag(REGION_FLAGS_BLOCK_PARCEL_SEARCH)); + + bool show_directory = parcel->getParcelFlag(PF_SHOW_DIRECTORY); + mCheckShowDirectory->set(show_directory); + + // Set by string in case the order in UI doesn't match the order by index. + LLParcel::ECategory cat = parcel->getCategory(); + const std::string& category_string = LLParcel::getCategoryString(cat); + mCategoryCombo->setValue(category_string); + + std::string tooltip; + bool enable_show_directory = false; + // Parcels <= 128 square meters cannot be listed in search, in an + // effort to reduce search spam from small parcels. See also + // the search crawler "grid-crawl.py" in secondlife.com/doc/app/search/ JC + const S32 MIN_PARCEL_AREA_FOR_SEARCH = 128; + bool large_enough = parcel->getArea() >= MIN_PARCEL_AREA_FOR_SEARCH; + if (large_enough) + { + if (can_change) + { + tooltip = getString("search_enabled_tooltip"); + enable_show_directory = true; + } + else + { + tooltip = getString("search_disabled_permissions_tooltip"); + enable_show_directory = false; + } + } + else + { + // not large enough to include in search + if (can_change) + { + if (show_directory) + { + // parcels that are too small, but are still in search for + // legacy reasons, need to have the check box enabled so + // the owner can delist the parcel. JC + tooltip = getString("search_enabled_tooltip"); + enable_show_directory = true; + } + else + { + tooltip = getString("search_disabled_small_tooltip"); + enable_show_directory = false; + } + } + else + { + // both too small and don't have permission, so just + // show the permissions as the reason (which is probably + // the more common case) JC + tooltip = getString("search_disabled_permissions_tooltip"); + enable_show_directory = false; + } + } + mCheckShowDirectory->setToolTip(tooltip); + mCategoryCombo->setToolTip(tooltip); + mCheckShowDirectory->setEnabled(enable_show_directory); + mCategoryCombo->setEnabled(enable_show_directory); +} + + +// static +void LLPanelLandOptions::onCommitAny(LLUICtrl *ctrl, void *userdata) +{ + LLPanelLandOptions *self = (LLPanelLandOptions *)userdata; + + LLParcel* parcel = self->mParcel->getParcel(); + if (!parcel) + { + return; + } + + // Extract data from UI + bool create_objects = self->mCheckEditObjects->get(); + bool create_group_objects = self->mCheckEditGroupObjects->get() || self->mCheckEditObjects->get(); + bool all_object_entry = self->mCheckAllObjectEntry->get(); + bool group_object_entry = self->mCheckGroupObjectEntry->get() || self->mCheckAllObjectEntry->get(); + bool allow_terraform = false; // removed from UI so always off now - self->mCheckEditLand->get(); + bool allow_damage = !self->mCheckSafe->get(); + bool allow_fly = self->mCheckFly->get(); + bool allow_landmark = true; // cannot restrict landmark creation + bool allow_other_scripts = self->mCheckOtherScripts->get(); + bool allow_group_scripts = self->mCheckGroupScripts->get() || allow_other_scripts; + bool allow_publish = false; + bool mature_publish = self->mMatureCtrl->get(); + bool push_restriction = self->mPushRestrictionCtrl->get(); + bool see_avs = self->mSeeAvatarsCtrl->get(); + bool show_directory = self->mCheckShowDirectory->get(); + // we have to get the index from a lookup, not from the position in the dropdown! + S32 category_index = LLParcel::getCategoryFromString(self->mCategoryCombo->getSelectedValue()); + S32 landing_type_index = self->mLandingTypeCombo->getCurrentIndex(); + LLUUID snapshot_id = self->mSnapshotCtrl->getImageAssetID(); + LLViewerRegion* region; + region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); + + if (region && region->getAllowDamage()) + { // Damage is allowed on the region - server will always allow scripts + if ( (!allow_other_scripts && parcel->getParcelFlag(PF_ALLOW_OTHER_SCRIPTS)) || + (!allow_group_scripts && parcel->getParcelFlag(PF_ALLOW_GROUP_SCRIPTS)) ) + { // Don't allow turning off "Run Scripts" if damage is allowed in the region + self->mCheckOtherScripts->set(parcel->getParcelFlag(PF_ALLOW_OTHER_SCRIPTS)); // Restore UI to actual settings + self->mCheckGroupScripts->set(parcel->getParcelFlag(PF_ALLOW_GROUP_SCRIPTS)); + LLNotificationsUtil::add("UnableToDisableOutsideScripts"); + return; + } + } + + // Push data into current parcel + parcel->setParcelFlag(PF_CREATE_OBJECTS, create_objects); + parcel->setParcelFlag(PF_CREATE_GROUP_OBJECTS, create_group_objects); + parcel->setParcelFlag(PF_ALLOW_ALL_OBJECT_ENTRY, all_object_entry); + parcel->setParcelFlag(PF_ALLOW_GROUP_OBJECT_ENTRY, group_object_entry); + parcel->setParcelFlag(PF_ALLOW_TERRAFORM, allow_terraform); + parcel->setParcelFlag(PF_ALLOW_DAMAGE, allow_damage); + parcel->setParcelFlag(PF_ALLOW_FLY, allow_fly); + parcel->setParcelFlag(PF_ALLOW_LANDMARK, allow_landmark); + parcel->setParcelFlag(PF_ALLOW_GROUP_SCRIPTS, allow_group_scripts); + parcel->setParcelFlag(PF_ALLOW_OTHER_SCRIPTS, allow_other_scripts); + parcel->setParcelFlag(PF_SHOW_DIRECTORY, show_directory); + parcel->setParcelFlag(PF_ALLOW_PUBLISH, allow_publish); + parcel->setParcelFlag(PF_MATURE_PUBLISH, mature_publish); + parcel->setParcelFlag(PF_RESTRICT_PUSHOBJECT, push_restriction); + parcel->setCategory((LLParcel::ECategory)category_index); + parcel->setLandingType((LLParcel::ELandingType)landing_type_index); + parcel->setSnapshotID(snapshot_id); + parcel->setSeeAVs(see_avs); + + // Send current parcel data upstream to server + LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate( parcel ); + + // Might have changed properties, so let's redraw! + self->refresh(); +} + + +// static +void LLPanelLandOptions::onClickSet(void* userdata) +{ + LLPanelLandOptions* self = (LLPanelLandOptions*)userdata; + + LLParcel* selected_parcel = self->mParcel->getParcel(); + if (!selected_parcel) return; + + LLParcel* agent_parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if (!agent_parcel) return; + + if (agent_parcel->getLocalID() != selected_parcel->getLocalID()) + { + LLNotificationsUtil::add("MustBeInParcel"); + return; + } + + LLVector3 pos_region = gAgent.getPositionAgent(); + selected_parcel->setUserLocation(pos_region); + selected_parcel->setUserLookAt(gAgent.getFrameAgent().getAtAxis()); + + LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate(selected_parcel); + + self->refresh(); +} + +void LLPanelLandOptions::onClickClear(void* userdata) +{ + LLPanelLandOptions* self = (LLPanelLandOptions*)userdata; + + LLParcel* selected_parcel = self->mParcel->getParcel(); + if (!selected_parcel) return; + + // yes, this magic number of 0,0,0 means that it is clear + LLVector3 zero_vec(0.f, 0.f, 0.f); + selected_parcel->setUserLocation(zero_vec); + selected_parcel->setUserLookAt(zero_vec); + + LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate(selected_parcel); + + self->refresh(); +} + +void LLPanelLandOptions::toggleSeeAvatars(void* userdata) +{ + LLPanelLandOptions* self = (LLPanelLandOptions*)userdata; + if (self) + { + self->getChild("SeeAvatarsCheck")->toggle(); + self->getChild("SeeAvatarsCheck")->setBtnFocus(); + self->onCommitAny(NULL, userdata); + } +} +//--------------------------------------------------------------------------- +// LLPanelLandAccess +//--------------------------------------------------------------------------- + +LLPanelLandAccess::LLPanelLandAccess(LLParcelSelectionHandle& parcel) + : LLPanel(), + mParcel(parcel) +{ +} + + +bool LLPanelLandAccess::postBuild() +{ + childSetCommitCallback("public_access", onCommitPublicAccess, this); + childSetCommitCallback("limit_payment", onCommitAny, this); + childSetCommitCallback("limit_age_verified", onCommitAny, this); + childSetCommitCallback("GroupCheck", onCommitGroupCheck, this); + childSetCommitCallback("PassCheck", onCommitAny, this); + childSetCommitCallback("pass_combo", onCommitAny, this); + childSetCommitCallback("PriceSpin", onCommitAny, this); + childSetCommitCallback("HoursSpin", onCommitAny, this); + + childSetAction("add_allowed", boost::bind(&LLPanelLandAccess::onClickAddAccess, this)); + childSetAction("remove_allowed", onClickRemoveAccess, this); + childSetAction("add_banned", boost::bind(&LLPanelLandAccess::onClickAddBanned, this)); + childSetAction("remove_banned", onClickRemoveBanned, this); + + mListAccess = getChild("AccessList"); + if (mListAccess) + { + mListAccess->sortByColumnIndex(0, true); // ascending + mListAccess->setContextMenu(LLScrollListCtrl::MENU_AVATAR); + } + + mListBanned = getChild("BannedList"); + if (mListBanned) + { + mListBanned->sortByColumnIndex(0, true); // ascending + mListBanned->setContextMenu(LLScrollListCtrl::MENU_AVATAR); + mListBanned->setAlternateSort(); + } + + return true; +} + + +LLPanelLandAccess::~LLPanelLandAccess() +{ +} + +void LLPanelLandAccess::refresh() +{ + LLFloater* parent_floater = gFloaterView->getParentFloater(this); + LLParcel *parcel = mParcel->getParcel(); + + // Display options + if (parcel && !gDisconnected) + { + bool use_access_list = parcel->getParcelFlag(PF_USE_ACCESS_LIST); + bool use_group = parcel->getParcelFlag(PF_USE_ACCESS_GROUP); + bool public_access = !use_access_list; + + if (parcel->getRegionAllowAccessOverride()) + { + getChild("public_access")->setValue(public_access); + getChild("GroupCheck")->setValue(use_group); + } + else + { + getChild("public_access")->setValue(true); + getChild("GroupCheck")->setValue(false); + } + std::string group_name; + gCacheName->getGroupName(parcel->getGroupID(), group_name); + getChild("GroupCheck")->setLabelArg("[GROUP]", group_name ); + + // Allow list + if (mListAccess) + { + // Clear the sort order so we don't re-sort on every add. + mListAccess->clearSortOrder(); + mListAccess->deleteAllItems(); + S32 count = parcel->mAccessList.size(); + getChild("AllowedText")->setTextArg("[COUNT]", llformat("%d",count)); + getChild("AllowedText")->setTextArg("[MAX]", llformat("%d",PARCEL_MAX_ACCESS_LIST)); + + getChild("AccessList")->setToolTipArg(LLStringExplicit("[LISTED]"), llformat("%d",count)); + getChild("AccessList")->setToolTipArg(LLStringExplicit("[MAX]"), llformat("%d",PARCEL_MAX_ACCESS_LIST)); + + for (LLAccessEntry::map::const_iterator cit = parcel->mAccessList.begin(); + cit != parcel->mAccessList.end(); ++cit) + { + const LLAccessEntry& entry = (*cit).second; + std::string prefix; + if (entry.mTime != 0) + { + LLStringUtil::format_map_t args; + S32 now = time(NULL); + S32 seconds = entry.mTime - now; + if (seconds < 0) seconds = 0; + prefix.assign(" ("); + if (seconds >= 120) + { + args["[MINUTES]"] = llformat("%d", (seconds/60)); + std::string buf = parent_floater->getString ("Minutes", args); + prefix.append(buf); + } + else if (seconds >= 60) + { + prefix.append("1 " + parent_floater->getString("Minute")); + } + else + { + args["[SECONDS]"] = llformat("%d", seconds); + std::string buf = parent_floater->getString ("Seconds", args); + prefix.append(buf); + } + prefix.append(" " + parent_floater->getString("Remaining") + ") "); + } + mListAccess->addNameItem(entry.mID, ADD_DEFAULT, true, "", prefix); + } + mListAccess->sortByName(true); + } + + // Ban List + if(mListBanned) + { + // Clear the sort order so we don't re-sort on every add. + mListBanned->clearSortOrder(); + mListBanned->deleteAllItems(); + S32 count = parcel->mBanList.size(); + getChild("BanCheck")->setTextArg("[COUNT]", llformat("%d",count)); + getChild("BanCheck")->setTextArg("[MAX]", llformat("%d",PARCEL_MAX_ACCESS_LIST)); + + getChild("BannedList")->setToolTipArg(LLStringExplicit("[LISTED]"), llformat("%d",count)); + getChild("BannedList")->setToolTipArg(LLStringExplicit("[MAX]"), llformat("%d",PARCEL_MAX_ACCESS_LIST)); + + for (LLAccessEntry::map::const_iterator cit = parcel->mBanList.begin(); + cit != parcel->mBanList.end(); ++cit) + { + const LLAccessEntry& entry = (*cit).second; + std::string duration; + S32 seconds = -1; + if (entry.mTime != 0) + { + LLStringUtil::format_map_t args; + S32 now = time(NULL); + seconds = entry.mTime - now; + if (seconds < 0) seconds = 0; + + if (seconds >= 7200) + { + args["[HOURS]"] = llformat("%d", (seconds / 3600)); + duration = parent_floater->getString("Hours", args); + } + else if (seconds >= 3600) + { + duration = "1 " + parent_floater->getString("Hour"); + } + else if (seconds >= 120) + { + args["[MINUTES]"] = llformat("%d", (seconds / 60)); + duration = parent_floater->getString("Minutes", args); + } + else if (seconds >= 60) + { + duration = "1 " + parent_floater->getString("Minute"); + } + else + { + args["[SECONDS]"] = llformat("%d", seconds); + duration = parent_floater->getString("Seconds", args); + } + } + else + { + duration = parent_floater->getString("Always"); + } + LLSD item; + item["id"] = entry.mID; + LLSD& columns = item["columns"]; + columns[0]["column"] = "name"; // to be populated later + columns[1]["column"] = "duration"; + columns[1]["value"] = duration; + columns[1]["alt_value"] = entry.mTime != 0 ? std::to_string(seconds) : "Always"; + mListBanned->addElement(item); + } + mListBanned->sortByName(true); + } + + if(parcel->getRegionDenyAnonymousOverride()) + { + getChild("limit_payment")->setValue(true); + getChild("limit_payment")->setLabelArg("[ESTATE_PAYMENT_LIMIT]", getString("access_estate_defined") ); + } + else + { + getChild("limit_payment")->setValue((parcel->getParcelFlag(PF_DENY_ANONYMOUS))); + getChild("limit_payment")->setLabelArg("[ESTATE_PAYMENT_LIMIT]", std::string() ); + } + if(parcel->getRegionDenyAgeUnverifiedOverride()) + { + getChild("limit_age_verified")->setValue(true); + getChild("limit_age_verified")->setLabelArg("[ESTATE_AGE_LIMIT]", getString("access_estate_defined") ); + } + else + { + getChild("limit_age_verified")->setValue((parcel->getParcelFlag(PF_DENY_AGEUNVERIFIED))); + getChild("limit_age_verified")->setLabelArg("[ESTATE_AGE_LIMIT]", std::string() ); + } + + bool use_pass = parcel->getParcelFlag(PF_USE_PASS_LIST); + getChild("PassCheck")->setValue(use_pass); + LLCtrlSelectionInterface* passcombo = childGetSelectionInterface("pass_combo"); + if (passcombo) + { + if (public_access || !use_pass) + { + passcombo->selectByValue("anyone"); + } + } + + S32 pass_price = parcel->getPassPrice(); + getChild("PriceSpin")->setValue((F32)pass_price ); + + F32 pass_hours = parcel->getPassHours(); + getChild("HoursSpin")->setValue(pass_hours ); + } + else + { + getChild("public_access")->setValue(false); + getChild("limit_payment")->setValue(false); + getChild("limit_age_verified")->setValue(false); + getChild("GroupCheck")->setValue(false); + getChild("GroupCheck")->setLabelArg("[GROUP]", LLStringUtil::null ); + getChild("PassCheck")->setValue(false); + getChild("PriceSpin")->setValue((F32)PARCEL_PASS_PRICE_DEFAULT); + getChild("HoursSpin")->setValue(PARCEL_PASS_HOURS_DEFAULT ); + getChild("AccessList")->setToolTipArg(LLStringExplicit("[LISTED]"), llformat("%d",0)); + getChild("AccessList")->setToolTipArg(LLStringExplicit("[MAX]"), llformat("%d",0)); + getChild("BannedList")->setToolTipArg(LLStringExplicit("[LISTED]"), llformat("%d",0)); + getChild("BannedList")->setToolTipArg(LLStringExplicit("[MAX]"), llformat("%d",0)); + } +} + +void LLPanelLandAccess::refresh_ui() +{ + getChildView("public_access")->setEnabled(false); + getChildView("limit_payment")->setEnabled(false); + getChildView("limit_age_verified")->setEnabled(false); + getChildView("GroupCheck")->setEnabled(false); + getChildView("PassCheck")->setEnabled(false); + getChildView("pass_combo")->setEnabled(false); + getChildView("PriceSpin")->setEnabled(false); + getChildView("HoursSpin")->setEnabled(false); + getChildView("AccessList")->setEnabled(false); + getChildView("BannedList")->setEnabled(false); + getChildView("add_allowed")->setEnabled(false); + getChildView("remove_allowed")->setEnabled(false); + getChildView("add_banned")->setEnabled(false); + getChildView("remove_banned")->setEnabled(false); + + LLParcel *parcel = mParcel->getParcel(); + if (parcel && !gDisconnected) + { + bool can_manage_allowed = false; + bool can_manage_banned = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_MANAGE_BANNED); + + if (parcel->getRegionAllowAccessOverride()) + { // Estate owner may have disabled allowing the parcel owner from managing access. + can_manage_allowed = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_MANAGE_ALLOWED); + } + + getChildView("public_access")->setEnabled(can_manage_allowed); + bool public_access = getChild("public_access")->getValue().asBoolean(); + if (public_access) + { + bool override = false; + if(parcel->getRegionDenyAnonymousOverride()) + { + override = true; + getChildView("limit_payment")->setEnabled(false); + } + else + { + getChildView("limit_payment")->setEnabled(can_manage_allowed); + } + if(parcel->getRegionDenyAgeUnverifiedOverride()) + { + override = true; + getChildView("limit_age_verified")->setEnabled(false); + } + else + { + getChildView("limit_age_verified")->setEnabled(can_manage_allowed); + } + if (override) + { + getChildView("Only Allow")->setToolTip(getString("estate_override")); + } + else + { + getChildView("Only Allow")->setToolTip(std::string()); + } + getChildView("PassCheck")->setEnabled(false); + getChildView("pass_combo")->setEnabled(false); + getChildView("AccessList")->setEnabled(false); + } + else + { + getChildView("limit_payment")->setEnabled(false); + getChildView("limit_age_verified")->setEnabled(false); + + + bool sell_passes = getChild("PassCheck")->getValue().asBoolean(); + getChildView("PassCheck")->setEnabled(can_manage_allowed); + if (sell_passes) + { + getChildView("pass_combo")->setEnabled(can_manage_allowed); + getChildView("PriceSpin")->setEnabled(can_manage_allowed); + getChildView("HoursSpin")->setEnabled(can_manage_allowed); + } + } + std::string group_name; + if (gCacheName->getGroupName(parcel->getGroupID(), group_name)) + { + bool can_allow_groups = !public_access || (public_access && (getChild("limit_payment")->getValue().asBoolean() ^ getChild("limit_age_verified")->getValue().asBoolean())); + getChildView("GroupCheck")->setEnabled(can_manage_allowed && can_allow_groups); + } + getChildView("AccessList")->setEnabled(can_manage_allowed); + S32 allowed_list_count = parcel->mAccessList.size(); + getChildView("add_allowed")->setEnabled(can_manage_allowed && allowed_list_count < PARCEL_MAX_ACCESS_LIST); + bool has_selected = (mListAccess && mListAccess->getSelectionInterface()->getFirstSelectedIndex() >= 0); + getChildView("remove_allowed")->setEnabled(can_manage_allowed && has_selected); + + getChildView("BannedList")->setEnabled(can_manage_banned); + S32 banned_list_count = parcel->mBanList.size(); + getChildView("add_banned")->setEnabled(can_manage_banned && banned_list_count < PARCEL_MAX_ACCESS_LIST); + has_selected = (mListBanned && mListBanned->getSelectionInterface()->getFirstSelectedIndex() >= 0); + getChildView("remove_banned")->setEnabled(can_manage_banned && has_selected); + } +} + + +// public +void LLPanelLandAccess::refreshNames() +{ + LLParcel* parcel = mParcel->getParcel(); + std::string group_name; + if(parcel) + { + gCacheName->getGroupName(parcel->getGroupID(), group_name); + } + getChild("GroupCheck")->setLabelArg("[GROUP]", group_name); +} + + +// virtual +void LLPanelLandAccess::draw() +{ + refresh_ui(); + refreshNames(); + LLPanel::draw(); +} + +// static +void LLPanelLandAccess::onCommitPublicAccess(LLUICtrl *ctrl, void *userdata) +{ + LLPanelLandAccess *self = (LLPanelLandAccess *)userdata; + LLParcel* parcel = self->mParcel->getParcel(); + if (!parcel) + { + return; + } + + onCommitAny(ctrl, userdata); +} + +void LLPanelLandAccess::onCommitGroupCheck(LLUICtrl *ctrl, void *userdata) +{ + LLPanelLandAccess *self = (LLPanelLandAccess *)userdata; + LLParcel* parcel = self->mParcel->getParcel(); + if (!parcel) + { + return; + } + + bool use_pass_list = !self->getChild("public_access")->getValue().asBoolean(); + bool use_access_group = self->getChild("GroupCheck")->getValue().asBoolean(); + LLCtrlSelectionInterface* passcombo = self->childGetSelectionInterface("pass_combo"); + if (passcombo) + { + if (use_access_group && use_pass_list) + { + if (passcombo->getSelectedValue().asString() == "group") + { + passcombo->selectByValue("anyone"); + } + } + } + + onCommitAny(ctrl, userdata); +} + +// static +void LLPanelLandAccess::onCommitAny(LLUICtrl *ctrl, void *userdata) +{ + LLPanelLandAccess *self = (LLPanelLandAccess *)userdata; + + LLParcel* parcel = self->mParcel->getParcel(); + if (!parcel) + { + return; + } + + // Extract data from UI + bool public_access = self->getChild("public_access")->getValue().asBoolean(); + bool use_access_group = self->getChild("GroupCheck")->getValue().asBoolean(); + if (use_access_group) + { + std::string group_name; + if (!gCacheName->getGroupName(parcel->getGroupID(), group_name)) + { + use_access_group = false; + } + } + + bool limit_payment = false, limit_age_verified = false; + bool use_access_list = false; + bool use_pass_list = false; + + if (public_access) + { + use_access_list = false; + limit_payment = self->getChild("limit_payment")->getValue().asBoolean(); + limit_age_verified = self->getChild("limit_age_verified")->getValue().asBoolean(); + } + else + { + use_access_list = true; + use_pass_list = self->getChild("PassCheck")->getValue().asBoolean(); + LLCtrlSelectionInterface* passcombo = self->childGetSelectionInterface("pass_combo"); + if (passcombo) + { + if (use_access_group && use_pass_list) + { + if (passcombo->getSelectedValue().asString() == "group") + { + use_access_group = false; + } + } + } + } + + S32 pass_price = llfloor((F32)self->getChild("PriceSpin")->getValue().asReal()); + F32 pass_hours = (F32)self->getChild("HoursSpin")->getValue().asReal(); + + // Push data into current parcel + parcel->setParcelFlag(PF_USE_ACCESS_GROUP, use_access_group); + parcel->setParcelFlag(PF_USE_ACCESS_LIST, use_access_list); + parcel->setParcelFlag(PF_USE_PASS_LIST, use_pass_list); + parcel->setParcelFlag(PF_USE_BAN_LIST, true); + parcel->setParcelFlag(PF_DENY_ANONYMOUS, limit_payment); + parcel->setParcelFlag(PF_DENY_AGEUNVERIFIED, limit_age_verified); + + parcel->setPassPrice( pass_price ); + parcel->setPassHours( pass_hours ); + + // Send current parcel data upstream to server + LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate( parcel ); + + // Might have changed properties, so let's redraw! + self->refresh(); +} + +void LLPanelLandAccess::onClickAddAccess() +{ + LLView * button = findChild("add_allowed"); + LLFloater * root_floater = gFloaterView->getParentFloater(this); + LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show( + boost::bind(&LLPanelLandAccess::callbackAvatarCBAccess, this, _1), false, false, false, root_floater->getName(), button); + if (picker) + { + root_floater->addDependentFloater(picker); + } +} + +void LLPanelLandAccess::callbackAvatarCBAccess(const uuid_vec_t& ids) +{ + if (!ids.empty()) + { + LLUUID id = ids[0]; + LLParcel* parcel = mParcel->getParcel(); + if (parcel && parcel->addToAccessList(id, 0)) + { + U32 lists_to_update = AL_ACCESS; + // agent was successfully added to access list + // but we also need to check ban list to ensure that agent will not be in two lists simultaneously + if(parcel->removeFromBanList(id)) + { + lists_to_update |= AL_BAN; + } + LLViewerParcelMgr::getInstance()->sendParcelAccessListUpdate(lists_to_update); + refresh(); + } + } +} + +// static +void LLPanelLandAccess::onClickRemoveAccess(void* data) +{ + LLPanelLandAccess* panelp = (LLPanelLandAccess*)data; + if (panelp && panelp->mListAccess) + { + LLParcel* parcel = panelp->mParcel->getParcel(); + if (parcel) + { + std::vector names = panelp->mListAccess->getAllSelected(); + for (std::vector::iterator iter = names.begin(); + iter != names.end(); ) + { + LLScrollListItem* item = *iter++; + const LLUUID& agent_id = item->getUUID(); + parcel->removeFromAccessList(agent_id); + } + LLViewerParcelMgr::getInstance()->sendParcelAccessListUpdate(AL_ACCESS); + panelp->refresh(); + } + } +} + +// static +void LLPanelLandAccess::onClickAddBanned() +{ + LLView * button = findChild("add_banned"); + LLFloater * root_floater = gFloaterView->getParentFloater(this); + LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show( + boost::bind(&LLPanelLandAccess::callbackAvatarCBBanned, this, _1), true, false, false, root_floater->getName(), button); + if (picker) + { + root_floater->addDependentFloater(picker); + } +} + +// static +void LLPanelLandAccess::callbackAvatarCBBanned(const uuid_vec_t& ids) +{ + LLFloater * root_floater = gFloaterView->getParentFloater(this); + LLFloaterBanDuration* duration_floater = LLFloaterBanDuration::show( + boost::bind(&LLPanelLandAccess::callbackAvatarCBBanned2, this, _1, _2), ids); + if (duration_floater) + { + root_floater->addDependentFloater(duration_floater); + } +} + +void LLPanelLandAccess::callbackAvatarCBBanned2(const uuid_vec_t& ids, S32 duration) +{ + LLParcel* parcel = mParcel->getParcel(); + if (!parcel) return; + + U32 lists_to_update = 0; + + for (uuid_vec_t::const_iterator it = ids.begin(); it < ids.end(); it++) + { + LLUUID id = *it; + if (parcel->addToBanList(id, duration)) + { + lists_to_update |= AL_BAN; + // agent was successfully added to ban list + // but we also need to check access list to ensure that agent will not be in two lists simultaneously + if (parcel->removeFromAccessList(id)) + { + lists_to_update |= AL_ACCESS; + } + } + } + if (lists_to_update > 0) + { + LLViewerParcelMgr::getInstance()->sendParcelAccessListUpdate(lists_to_update); + refresh(); + } +} + +// static +void LLPanelLandAccess::onClickRemoveBanned(void* data) +{ + LLPanelLandAccess* panelp = (LLPanelLandAccess*)data; + if (panelp && panelp->mListBanned) + { + LLParcel* parcel = panelp->mParcel->getParcel(); + if (parcel) + { + std::vector names = panelp->mListBanned->getAllSelected(); + for (std::vector::iterator iter = names.begin(); + iter != names.end(); ) + { + LLScrollListItem* item = *iter++; + const LLUUID& agent_id = item->getUUID(); + parcel->removeFromBanList(agent_id); + } + LLViewerParcelMgr::getInstance()->sendParcelAccessListUpdate(AL_BAN); + panelp->refresh(); + } + } +} + +//--------------------------------------------------------------------------- +// LLPanelLandCovenant +//--------------------------------------------------------------------------- +LLPanelLandCovenant::LLPanelLandCovenant(LLParcelSelectionHandle& parcel) + : LLPanel(), + mParcel(parcel), + mNextUpdateTime(0) +{ +} + +LLPanelLandCovenant::~LLPanelLandCovenant() +{ +} + +bool LLPanelLandCovenant::postBuild() +{ + mLastRegionID = LLUUID::null; + mNextUpdateTime = 0; + mTextEstateOwner = getChild("estate_owner_text"); + mTextEstateOwner->setIsFriendCallback(LLAvatarActions::isFriend); + return true; +} + +// virtual +void LLPanelLandCovenant::refresh() +{ + LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); + if(!region || gDisconnected) return; + + LLTextBox* region_name = getChild("region_name_text"); + if (region_name) + { + region_name->setText(region->getName()); + } + + LLTextBox* region_landtype = getChild("region_landtype_text"); + region_landtype->setText(region->getLocalizedSimProductName()); + + LLTextBox* region_maturity = getChild("region_maturity_text"); + if (region_maturity) + { + insert_maturity_into_textbox(region_maturity, gFloaterView->getParentFloater(this), MATURITY); + } + + LLTextBox* resellable_clause = getChild("resellable_clause"); + if (resellable_clause) + { + if (region->getRegionFlag(REGION_FLAGS_BLOCK_LAND_RESELL)) + { + resellable_clause->setText(getString("can_not_resell")); + } + else + { + resellable_clause->setText(getString("can_resell")); + } + } + + LLTextBox* changeable_clause = getChild("changeable_clause"); + if (changeable_clause) + { + if (region->getRegionFlag(REGION_FLAGS_ALLOW_PARCEL_CHANGES)) + { + changeable_clause->setText(getString("can_change")); + } + else + { + changeable_clause->setText(getString("can_not_change")); + } + } + + if (mLastRegionID != region->getRegionID() + || mNextUpdateTime < LLTimer::getElapsedSeconds()) + { + // Request Covenant Info + // Note: LLPanelLandCovenant doesn't change Covenant's content and any + // changes made by Estate floater should be requested by Estate floater + LLMessageSystem *msg = gMessageSystem; + msg->newMessage("EstateCovenantRequest"); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); + msg->sendReliable(region->getHost()); + + mLastRegionID = region->getRegionID(); + mNextUpdateTime = LLTimer::getElapsedSeconds() + COVENANT_REFRESH_TIME_SEC; + } +} + +// static +void LLPanelLandCovenant::updateCovenantText(const std::string &string) +{ + LLPanelLandCovenant* self = LLFloaterLand::getCurrentPanelLandCovenant(); + if (self) + { + LLViewerTextEditor* editor = self->getChild("covenant_editor"); + editor->setText(string); + } +} + +// static +void LLPanelLandCovenant::updateEstateName(const std::string& name) +{ + LLPanelLandCovenant* self = LLFloaterLand::getCurrentPanelLandCovenant(); + if (self) + { + LLTextBox* editor = self->getChild("estate_name_text"); + if (editor) editor->setText(name); + } +} + +// static +void LLPanelLandCovenant::updateLastModified(const std::string& text) +{ + LLPanelLandCovenant* self = LLFloaterLand::getCurrentPanelLandCovenant(); + if (self) + { + LLTextBox* editor = self->getChild("covenant_timestamp_text"); + if (editor) editor->setText(text); + } +} + +// static +void LLPanelLandCovenant::updateEstateOwnerName(const std::string& name) +{ + LLPanelLandCovenant* self = LLFloaterLand::getCurrentPanelLandCovenant(); + if (self) + { + self->mTextEstateOwner->setText(name); + } +} + +// inserts maturity info(icon and text) into target textbox +// names_floater - pointer to floater which contains strings with maturity icons filenames +// str_to_parse is string in format "txt1[MATURITY]txt2" where maturity icon and text will be inserted instead of [MATURITY] +void insert_maturity_into_textbox(LLTextBox* target_textbox, LLFloater* names_floater, std::string str_to_parse) +{ + LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); + if (!region) + return; + + LLStyle::Params style; + + U8 sim_access = region->getSimAccess(); + + switch(sim_access) + { + case SIM_ACCESS_PG: + style.image(LLUI::getUIImage(names_floater->getString("maturity_icon_general"))); + break; + + case SIM_ACCESS_ADULT: + style.image(LLUI::getUIImage(names_floater->getString("maturity_icon_adult"))); + break; + + case SIM_ACCESS_MATURE: + style.image(LLUI::getUIImage(names_floater->getString("maturity_icon_moderate"))); + break; + + default: + break; + } + + size_t maturity_pos = str_to_parse.find(MATURITY); + + if (maturity_pos == std::string::npos) + { + return; + } + + std::string text_before_rating = str_to_parse.substr(0, maturity_pos); + std::string text_after_rating = str_to_parse.substr(maturity_pos + MATURITY.length()); + + target_textbox->setText(text_before_rating); + + target_textbox->appendImageSegment(style); + + target_textbox->appendText(LLViewerParcelMgr::getInstance()->getSelectionRegion()->getSimAccessString(), false); + target_textbox->appendText(text_after_rating, false); +} + +LLPanelLandExperiences::LLPanelLandExperiences( LLSafeHandle& parcelp ) + : mParcel(parcelp) +{ + +} + + +bool LLPanelLandExperiences::postBuild() +{ + mAllowed = setupList("panel_allowed", EXPERIENCE_KEY_TYPE_ALLOWED, AL_ALLOW_EXPERIENCE); + mBlocked = setupList("panel_blocked", EXPERIENCE_KEY_TYPE_BLOCKED, AL_BLOCK_EXPERIENCE); + + // only non-grid-wide experiences + mAllowed->addFilter(boost::bind(LLPanelExperiencePicker::FilterWithProperty, _1, LLExperienceCache::PROPERTY_GRID)); + + // no privileged ones + mBlocked->addFilter(boost::bind(LLPanelExperiencePicker::FilterWithoutProperties, _1, LLExperienceCache::PROPERTY_PRIVILEGED|LLExperienceCache::PROPERTY_GRID)); + + getChild("trusted_layout_panel")->setVisible(false); + getChild("experiences_help_text")->setVisible(false); + getChild("allowed_text_help")->setText(getString("allowed_parcel_text")); + getChild("blocked_text_help")->setText(getString("blocked_parcel_text")); + + return LLPanel::postBuild(); +} + +LLPanelExperienceListEditor* LLPanelLandExperiences::setupList( const char* control_name, U32 xp_type, U32 access_type ) +{ + LLPanelExperienceListEditor* child = findChild(control_name); + if(child) + { + child->getChild("text_name")->setText(child->getString(control_name)); + child->setMaxExperienceIDs(PARCEL_MAX_EXPERIENCE_LIST); + child->setAddedCallback(boost::bind(&LLPanelLandExperiences::experienceAdded, this, _1, xp_type, access_type)); + child->setRemovedCallback(boost::bind(&LLPanelLandExperiences::experienceRemoved, this, _1, access_type)); + } + + return child; +} + +void LLPanelLandExperiences::experienceAdded( const LLUUID& id, U32 xp_type, U32 access_type ) +{ + LLParcel* parcel = mParcel->getParcel(); + if (parcel) + { + parcel->setExperienceKeyType(id, xp_type); + LLViewerParcelMgr::getInstance()->sendParcelAccessListUpdate(access_type); + refresh(); + } +} + +void LLPanelLandExperiences::experienceRemoved( const LLUUID& id, U32 access_type ) +{ + LLParcel* parcel = mParcel->getParcel(); + if (parcel) + { + parcel->setExperienceKeyType(id, EXPERIENCE_KEY_TYPE_NONE); + LLViewerParcelMgr::getInstance()->sendParcelAccessListUpdate(access_type); + refresh(); + } +} + +void LLPanelLandExperiences::refreshPanel(LLPanelExperienceListEditor* panel, U32 xp_type) +{ + LLParcel *parcel = mParcel->getParcel(); + + // Display options + if (panel == NULL) + { + return; + } + if (!parcel || gDisconnected) + { + // disable the panel + panel->setEnabled(false); + panel->setExperienceIds(LLSD::emptyArray()); + } + else + { + // enable the panel + panel->setEnabled(true); + LLAccessEntry::map entries = parcel->getExperienceKeysByType(xp_type); + LLAccessEntry::map::iterator it = entries.begin(); + LLSD ids = LLSD::emptyArray(); + for (/**/; it != entries.end(); ++it) + { + ids.append(it->second.mID); + } + panel->setExperienceIds(ids); + panel->setReadonly(!LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_OPTIONS)); + panel->refreshExperienceCounter(); + } +} + +void LLPanelLandExperiences::refresh() +{ + refreshPanel(mAllowed, EXPERIENCE_KEY_TYPE_ALLOWED); + refreshPanel(mBlocked, EXPERIENCE_KEY_TYPE_BLOCKED); +} + +//========================================================================= + +LLPanelLandEnvironment::LLPanelLandEnvironment(LLParcelSelectionHandle& parcel) : + LLPanelEnvironmentInfo(), + mParcel(parcel), + mLastParcelId(INVALID_PARCEL_ID) +{ +} + +bool LLPanelLandEnvironment::postBuild() +{ + if (!LLPanelEnvironmentInfo::postBuild()) + return false; + + getChild(BTN_USEDEFAULT)->setLabelArg("[USEDEFAULT]", getString(STR_LABEL_USEREGION)); + getChild(CHK_ALLOWOVERRIDE)->setVisible(false); + getChild(PNL_REGION_MSG)->setVisible(false); + getChild(PNL_ENVIRONMENT_ALTITUDES)->setVisible(true); + + return true; +} + +void LLPanelLandEnvironment::refresh() +{ + if (gDisconnected) + return; + + commitDayLenOffsetChanges(false); // commit unsaved changes if any + + if (!isSameRegion()) + { + setCrossRegion(true); + mCurrentEnvironment.reset(); + mLastParcelId = INVALID_PARCEL_ID; + mCurEnvVersion = INVALID_PARCEL_ENVIRONMENT_VERSION; + setControlsEnabled(false); + return; + } + + if (mLastParcelId != getParcelId()) + { + mCurEnvVersion = INVALID_PARCEL_ENVIRONMENT_VERSION; + mCurrentEnvironment.reset(); + } + + if (!mCurrentEnvironment && mCurEnvVersion <= INVALID_PARCEL_ENVIRONMENT_VERSION) + { + refreshFromSource(); + return; + } + + LLPanelEnvironmentInfo::refresh(); +} + +void LLPanelLandEnvironment::refreshFromSource() +{ + LLParcel *parcel = getParcel(); + + if (!LLEnvironment::instance().isExtendedEnvironmentEnabled()) + { + setNoEnvironmentSupport(true); + setControlsEnabled(false); + mCurEnvVersion = INVALID_PARCEL_ENVIRONMENT_VERSION; + return; + } + setNoEnvironmentSupport(false); + + if (!parcel) + { + setNoSelection(true); + setControlsEnabled(false); + mCurEnvVersion = INVALID_PARCEL_ENVIRONMENT_VERSION; + return; + } + + setNoSelection(false); + if (isSameRegion()) + { + LL_DEBUGS("ENVIRONMENT") << "Requesting environment for parcel " << parcel->getLocalID() << ", known version " << mCurEnvVersion << LL_ENDL; + setCrossRegion(false); + + LLHandle that_h = getHandle(); + + if (mCurEnvVersion < UNSET_PARCEL_ENVIRONMENT_VERSION) + { + // to mark as requesting + mCurEnvVersion = parcel->getParcelEnvironmentVersion(); + } + mLastParcelId = parcel->getLocalID(); + + LLEnvironment::instance().requestParcel(parcel->getLocalID(), + [that_h](S32 parcel_id, LLEnvironment::EnvironmentInfo::ptr_t envifo) + { + LLPanelLandEnvironment *that = (LLPanelLandEnvironment*)that_h.get(); + if (!that) return; + that->mLastParcelId = parcel_id; + that->onEnvironmentReceived(parcel_id, envifo); + }); + } + else + { + setCrossRegion(true); + mCurrentEnvironment.reset(); + mLastParcelId = INVALID_PARCEL_ID; + mCurEnvVersion = INVALID_PARCEL_ENVIRONMENT_VERSION; + } + setControlsEnabled(false); +} + + +bool LLPanelLandEnvironment::isSameRegion() +{ + LLViewerRegion* regionp = LLViewerParcelMgr::instance().getSelectionRegion(); + + return (!regionp || (regionp->getRegionID() == gAgent.getRegion()->getRegionID())); +} + +LLParcel *LLPanelLandEnvironment::getParcel() +{ + return mParcel->getParcel(); +} + + +bool LLPanelLandEnvironment::canEdit() +{ + LLParcel *parcel = getParcel(); + if (!parcel) + return false; + + return LLEnvironment::instance().canAgentUpdateParcelEnvironment(parcel) && mAllowOverride; +} + +S32 LLPanelLandEnvironment::getParcelId() +{ + LLParcel *parcel = getParcel(); + if (!parcel) + return INVALID_PARCEL_ID; + + return parcel->getLocalID(); +} diff --git a/indra/newview/llfloaterland.h b/indra/newview/llfloaterland.h index bd29233f9b..3560304566 100644 --- a/indra/newview/llfloaterland.h +++ b/indra/newview/llfloaterland.h @@ -1,419 +1,419 @@ -/** - * @file llfloaterland.h - * @author James Cook - * @brief "About Land" floater, allowing display and editing of land parcel properties. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERLAND_H -#define LL_LLFLOATERLAND_H - -#include -#include - -#include "llfloater.h" -#include "llpointer.h" // LLPointer<> -//#include "llviewertexturelist.h" -#include "llsafehandle.h" - -typedef std::set uuid_list_t; -const F32 CACHE_REFRESH_TIME = 2.5f; - -class LLButton; -class LLCheckBoxCtrl; -class LLRadioGroup; -class LLComboBox; -class LLLineEditor; -class LLMessageSystem; -class LLNameListCtrl; -class LLRadioGroup; -class LLParcelSelectionObserver; -class LLSpinCtrl; -class LLTabContainer; -class LLTextBox; -class LLTextEditor; -class LLTextureCtrl; -class LLUIImage; -class LLParcelSelection; - -class LLPanelLandGeneral; -class LLPanelLandObjects; -class LLPanelLandOptions; -class LLPanelLandAudio; -class LLPanelLandMedia; -class LLPanelLandAccess; -class LLPanelLandBan; -class LLPanelLandRenters; -class LLPanelLandCovenant; -class LLParcel; -class LLPanelLandExperiences; -class LLPanelLandEnvironment; - -class LLFloaterLand -: public LLFloater -{ - friend class LLFloaterReg; -public: - static void refreshAll(); - - static LLPanelLandObjects* getCurrentPanelLandObjects(); - static LLPanelLandCovenant* getCurrentPanelLandCovenant(); - - LLParcel* getCurrentSelectedParcel(); - - virtual void onOpen(const LLSD& key); - virtual bool postBuild(); - -private: - // Does its own instance management, so clients not allowed - // to allocate or destroy. - LLFloaterLand(const LLSD& seed); - virtual ~LLFloaterLand(); - - void onVisibilityChanged(const LLSD& visible); - -protected: - - /*virtual*/ void refresh(); - - static void* createPanelLandGeneral(void* data); - static void* createPanelLandCovenant(void* data); - static void* createPanelLandObjects(void* data); - static void* createPanelLandOptions(void* data); - static void* createPanelLandAudio(void* data); - static void* createPanelLandMedia(void* data); - static void* createPanelLandAccess(void* data); - static void* createPanelLandExperiences(void* data); - static void* createPanelLandEnvironment(void* data); - static void* createPanelLandBan(void* data); - - -protected: - static LLParcelSelectionObserver* sObserver; - static S32 sLastTab; - - LLTabContainer* mTabLand; - LLPanelLandGeneral* mPanelGeneral; - LLPanelLandObjects* mPanelObjects; - LLPanelLandOptions* mPanelOptions; - LLPanelLandAudio* mPanelAudio; - LLPanelLandMedia* mPanelMedia; - LLPanelLandAccess* mPanelAccess; - LLPanelLandCovenant* mPanelCovenant; - LLPanelLandExperiences* mPanelExperiences; - LLPanelLandEnvironment *mPanelEnvironment; - - LLSafeHandle mParcel; - -public: - // When closing the dialog, we want to deselect the land. But when - // we send an update to the simulator, it usually replies with the - // parcel information, causing the land to be reselected. This allows - // us to suppress that behavior. - static bool sRequestReplyOnUpdate; -}; - - -class LLPanelLandGeneral -: public LLPanel -{ -public: - LLPanelLandGeneral(LLSafeHandle& parcelp); - virtual ~LLPanelLandGeneral(); - /*virtual*/ void refresh(); - void refreshNames(); - virtual void draw(); - - void setGroup(const LLUUID& group_id); - void onClickProfile(); - void onClickSetGroup(); - static void onClickDeed(void*); - static void onClickBuyLand(void* data); - static void onClickScriptLimits(void* data); - static void onClickRelease(void*); - static void onClickReclaim(void*); - static void onClickBuyPass(void* deselect_when_done); - static bool enableBuyPass(void*); - static void onCommitAny(LLUICtrl* ctrl, void *userdata); - static void finalizeCommit(void * userdata); - static void onForSaleChange(LLUICtrl *ctrl, void * userdata); - static void finalizeSetSellChange(void * userdata); - static void onSalePriceChange(LLUICtrl *ctrl, void * userdata); - - static bool cbBuyPass(const LLSD& notification, const LLSD& response); - - static void onClickSellLand(void* data); - static void onClickStopSellLand(void* data); - static void onClickSet(void* data); - static void onClickClear(void* data); - static void onClickShow(void* data); - static void callbackAvatarPick(const std::vector& names, const uuid_vec_t& ids, void* data); - static void finalizeAvatarPick(void* data); - static void callbackHighlightTransferable(S32 option, void* userdata); - static void onClickStartAuction(void*); - // sale change confirmed when "is for sale", "sale price", "sell to whom" fields are changed - static void confirmSaleChange(S32 landSize, S32 salePrice, std::string authorizedName, void(*callback)(void*), void* userdata); - static void callbackConfirmSaleChange(S32 option, void* userdata); - - virtual bool postBuild(); - -protected: - bool mUncheckedSell; // True only when verifying land information when land is for sale on sale info change - - LLTextBox* mLabelName; - LLLineEditor* mEditName; - LLTextBox* mLabelDesc; - LLTextEditor* mEditDesc; - - LLTextBox* mTextSalePending; - - LLButton* mBtnDeedToGroup; - LLButton* mBtnSetGroup; - - LLTextBox* mTextOwnerLabel; - LLTextBox* mTextOwner; - LLButton* mBtnProfile; - - LLTextBox* mContentRating; - LLTextBox* mLandType; - - LLTextBox* mTextGroup; - LLTextBox* mTextGroupLabel; - LLTextBox* mTextClaimDateLabel; - LLTextBox* mTextClaimDate; - - LLTextBox* mTextPriceLabel; - LLTextBox* mTextPrice; - - LLCheckBoxCtrl* mCheckDeedToGroup; - LLCheckBoxCtrl* mCheckContributeWithDeed; - - LLTextBox* mSaleInfoForSale1; - LLTextBox* mSaleInfoForSale2; - LLTextBox* mSaleInfoForSaleObjects; - LLTextBox* mSaleInfoForSaleNoObjects; - LLTextBox* mSaleInfoNotForSale; - LLButton* mBtnSellLand; - LLButton* mBtnStopSellLand; - - LLTextBox* mTextDwell; - - LLButton* mBtnBuyLand; - LLButton* mBtnScriptLimits; - LLButton* mBtnBuyGroupLand; - - // these buttons share the same location, but - // reclaim is in exactly the same visual place, - // ond is only shown for estate owners on their - // estate since they cannot release land. - LLButton* mBtnReleaseLand; - LLButton* mBtnReclaimLand; - - LLButton* mBtnBuyPass; - LLButton* mBtnStartAuction; - - LLSafeHandle& mParcel; - - // This pointer is needed to avoid parcel deselection until buying pass is completed or canceled. - // Deselection happened because of zero references to parcel selection, which took place when - // "Buy Pass" was called from popup menu(EXT-6464) - static LLPointer sSelectionForBuyPass; - - static LLHandle sBuyPassDialogHandle; -}; - -class LLPanelLandObjects -: public LLPanel -{ -public: - LLPanelLandObjects(LLSafeHandle& parcelp); - virtual ~LLPanelLandObjects(); - /*virtual*/ void refresh(); - virtual void draw(); - - bool callbackReturnOwnerObjects(const LLSD& notification, const LLSD& response); - bool callbackReturnGroupObjects(const LLSD& notification, const LLSD& response); - bool callbackReturnOtherObjects(const LLSD& notification, const LLSD& response); - bool callbackReturnOwnerList(const LLSD& notification, const LLSD& response); - - static void clickShowCore(LLPanelLandObjects* panelp, S32 return_type, uuid_list_t* list = 0); - static void onClickShowOwnerObjects(void*); - static void onClickShowGroupObjects(void*); - static void onClickShowOtherObjects(void*); - - static void onClickReturnOwnerObjects(void*); - static void onClickReturnGroupObjects(void*); - static void onClickReturnOtherObjects(void*); - static void onClickReturnOwnerList(void*); - static void onClickRefresh(void*); - - static void onDoubleClickOwner(void*); - - static void onCommitList(LLUICtrl* ctrl, void* data); - static void onLostFocus(LLFocusableElement* caller, void* user_data); - static void onCommitClean(LLUICtrl* caller, void* user_data); - static void processParcelObjectOwnersReply(LLMessageSystem *msg, void **); - - virtual bool postBuild(); - -protected: - - LLTextBox *mParcelObjectBonus; - LLTextBox *mSWTotalObjects; - LLTextBox *mObjectContribution; - LLTextBox *mTotalObjects; - LLTextBox *mOwnerObjects; - LLButton *mBtnShowOwnerObjects; - LLButton *mBtnReturnOwnerObjects; - LLTextBox *mGroupObjects; - LLButton *mBtnShowGroupObjects; - LLButton *mBtnReturnGroupObjects; - LLTextBox *mOtherObjects; - LLButton *mBtnShowOtherObjects; - LLButton *mBtnReturnOtherObjects; - LLTextBox *mSelectedObjects; - LLLineEditor *mCleanOtherObjectsTime; - S32 mOtherTime; - LLButton *mBtnRefresh; - LLButton *mBtnReturnOwnerList; - LLNameListCtrl *mOwnerList; - - LLPointer mIconAvatarOnline; - LLPointer mIconAvatarOffline; - LLPointer mIconGroup; - - bool mFirstReply; - - uuid_list_t mSelectedOwners; - std::string mSelectedName; - S32 mSelectedCount; - bool mSelectedIsGroup; - - LLSafeHandle& mParcel; -}; - - -class LLPanelLandOptions -: public LLPanel -{ -public: - LLPanelLandOptions(LLSafeHandle& parcelp); - virtual ~LLPanelLandOptions(); - /*virtual*/ bool postBuild(); - /*virtual*/ void draw(); - /*virtual*/ void refresh(); - -private: - // Refresh the "show in search" checkbox and category selector. - void refreshSearch(); - - static void onCommitAny(LLUICtrl* ctrl, void *userdata); - static void onClickSet(void* userdata); - static void onClickClear(void* userdata); - static void toggleSeeAvatars(void* userdata); - -private: - LLCheckBoxCtrl* mCheckEditObjects; - LLCheckBoxCtrl* mCheckEditGroupObjects; - LLCheckBoxCtrl* mCheckAllObjectEntry; - LLCheckBoxCtrl* mCheckGroupObjectEntry; - LLCheckBoxCtrl* mCheckSafe; - LLCheckBoxCtrl* mCheckFly; - LLCheckBoxCtrl* mCheckGroupScripts; - LLCheckBoxCtrl* mCheckOtherScripts; - - LLCheckBoxCtrl* mCheckShowDirectory; - LLComboBox* mCategoryCombo; - LLComboBox* mLandingTypeCombo; - - LLTextureCtrl* mSnapshotCtrl; - - LLTextBox* mLocationText; - LLTextBox* mSeeAvatarsText; - LLButton* mSetBtn; - LLButton* mClearBtn; - - LLCheckBoxCtrl *mMatureCtrl; - LLCheckBoxCtrl *mPushRestrictionCtrl; - LLCheckBoxCtrl *mSeeAvatarsCtrl; - - LLSafeHandle& mParcel; -}; - - -class LLPanelLandAccess -: public LLPanel -{ -public: - LLPanelLandAccess(LLSafeHandle& parcelp); - virtual ~LLPanelLandAccess(); - void refresh(); - void refresh_ui(); - void refreshNames(); - virtual void draw(); - - static void onCommitPublicAccess(LLUICtrl* ctrl, void *userdata); - static void onCommitAny(LLUICtrl* ctrl, void *userdata); - static void onCommitGroupCheck(LLUICtrl* ctrl, void *userdata); - static void onClickRemoveAccess(void*); - static void onClickRemoveBanned(void*); - - virtual bool postBuild(); - - void onClickAddAccess(); - void onClickAddBanned(); - void callbackAvatarCBBanned(const uuid_vec_t& ids); - void callbackAvatarCBBanned2(const uuid_vec_t& ids, S32 duration); - void callbackAvatarCBAccess(const uuid_vec_t& ids); - -protected: - LLNameListCtrl* mListAccess; - LLNameListCtrl* mListBanned; - - LLSafeHandle& mParcel; -}; - - -class LLPanelLandCovenant -: public LLPanel -{ -public: - LLPanelLandCovenant(LLSafeHandle& parcelp); - virtual ~LLPanelLandCovenant(); - virtual bool postBuild(); - void refresh(); - static void updateCovenantText(const std::string& string); - static void updateEstateName(const std::string& name); - static void updateLastModified(const std::string& text); - static void updateEstateOwnerName(const std::string& name); - -protected: - LLSafeHandle& mParcel; - -private: - LLUUID mLastRegionID; - F64 mNextUpdateTime; //seconds since client start - LLTextBox* mTextEstateOwner; -}; - -#endif +/** + * @file llfloaterland.h + * @author James Cook + * @brief "About Land" floater, allowing display and editing of land parcel properties. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERLAND_H +#define LL_LLFLOATERLAND_H + +#include +#include + +#include "llfloater.h" +#include "llpointer.h" // LLPointer<> +//#include "llviewertexturelist.h" +#include "llsafehandle.h" + +typedef std::set uuid_list_t; +const F32 CACHE_REFRESH_TIME = 2.5f; + +class LLButton; +class LLCheckBoxCtrl; +class LLRadioGroup; +class LLComboBox; +class LLLineEditor; +class LLMessageSystem; +class LLNameListCtrl; +class LLRadioGroup; +class LLParcelSelectionObserver; +class LLSpinCtrl; +class LLTabContainer; +class LLTextBox; +class LLTextEditor; +class LLTextureCtrl; +class LLUIImage; +class LLParcelSelection; + +class LLPanelLandGeneral; +class LLPanelLandObjects; +class LLPanelLandOptions; +class LLPanelLandAudio; +class LLPanelLandMedia; +class LLPanelLandAccess; +class LLPanelLandBan; +class LLPanelLandRenters; +class LLPanelLandCovenant; +class LLParcel; +class LLPanelLandExperiences; +class LLPanelLandEnvironment; + +class LLFloaterLand +: public LLFloater +{ + friend class LLFloaterReg; +public: + static void refreshAll(); + + static LLPanelLandObjects* getCurrentPanelLandObjects(); + static LLPanelLandCovenant* getCurrentPanelLandCovenant(); + + LLParcel* getCurrentSelectedParcel(); + + virtual void onOpen(const LLSD& key); + virtual bool postBuild(); + +private: + // Does its own instance management, so clients not allowed + // to allocate or destroy. + LLFloaterLand(const LLSD& seed); + virtual ~LLFloaterLand(); + + void onVisibilityChanged(const LLSD& visible); + +protected: + + /*virtual*/ void refresh(); + + static void* createPanelLandGeneral(void* data); + static void* createPanelLandCovenant(void* data); + static void* createPanelLandObjects(void* data); + static void* createPanelLandOptions(void* data); + static void* createPanelLandAudio(void* data); + static void* createPanelLandMedia(void* data); + static void* createPanelLandAccess(void* data); + static void* createPanelLandExperiences(void* data); + static void* createPanelLandEnvironment(void* data); + static void* createPanelLandBan(void* data); + + +protected: + static LLParcelSelectionObserver* sObserver; + static S32 sLastTab; + + LLTabContainer* mTabLand; + LLPanelLandGeneral* mPanelGeneral; + LLPanelLandObjects* mPanelObjects; + LLPanelLandOptions* mPanelOptions; + LLPanelLandAudio* mPanelAudio; + LLPanelLandMedia* mPanelMedia; + LLPanelLandAccess* mPanelAccess; + LLPanelLandCovenant* mPanelCovenant; + LLPanelLandExperiences* mPanelExperiences; + LLPanelLandEnvironment *mPanelEnvironment; + + LLSafeHandle mParcel; + +public: + // When closing the dialog, we want to deselect the land. But when + // we send an update to the simulator, it usually replies with the + // parcel information, causing the land to be reselected. This allows + // us to suppress that behavior. + static bool sRequestReplyOnUpdate; +}; + + +class LLPanelLandGeneral +: public LLPanel +{ +public: + LLPanelLandGeneral(LLSafeHandle& parcelp); + virtual ~LLPanelLandGeneral(); + /*virtual*/ void refresh(); + void refreshNames(); + virtual void draw(); + + void setGroup(const LLUUID& group_id); + void onClickProfile(); + void onClickSetGroup(); + static void onClickDeed(void*); + static void onClickBuyLand(void* data); + static void onClickScriptLimits(void* data); + static void onClickRelease(void*); + static void onClickReclaim(void*); + static void onClickBuyPass(void* deselect_when_done); + static bool enableBuyPass(void*); + static void onCommitAny(LLUICtrl* ctrl, void *userdata); + static void finalizeCommit(void * userdata); + static void onForSaleChange(LLUICtrl *ctrl, void * userdata); + static void finalizeSetSellChange(void * userdata); + static void onSalePriceChange(LLUICtrl *ctrl, void * userdata); + + static bool cbBuyPass(const LLSD& notification, const LLSD& response); + + static void onClickSellLand(void* data); + static void onClickStopSellLand(void* data); + static void onClickSet(void* data); + static void onClickClear(void* data); + static void onClickShow(void* data); + static void callbackAvatarPick(const std::vector& names, const uuid_vec_t& ids, void* data); + static void finalizeAvatarPick(void* data); + static void callbackHighlightTransferable(S32 option, void* userdata); + static void onClickStartAuction(void*); + // sale change confirmed when "is for sale", "sale price", "sell to whom" fields are changed + static void confirmSaleChange(S32 landSize, S32 salePrice, std::string authorizedName, void(*callback)(void*), void* userdata); + static void callbackConfirmSaleChange(S32 option, void* userdata); + + virtual bool postBuild(); + +protected: + bool mUncheckedSell; // True only when verifying land information when land is for sale on sale info change + + LLTextBox* mLabelName; + LLLineEditor* mEditName; + LLTextBox* mLabelDesc; + LLTextEditor* mEditDesc; + + LLTextBox* mTextSalePending; + + LLButton* mBtnDeedToGroup; + LLButton* mBtnSetGroup; + + LLTextBox* mTextOwnerLabel; + LLTextBox* mTextOwner; + LLButton* mBtnProfile; + + LLTextBox* mContentRating; + LLTextBox* mLandType; + + LLTextBox* mTextGroup; + LLTextBox* mTextGroupLabel; + LLTextBox* mTextClaimDateLabel; + LLTextBox* mTextClaimDate; + + LLTextBox* mTextPriceLabel; + LLTextBox* mTextPrice; + + LLCheckBoxCtrl* mCheckDeedToGroup; + LLCheckBoxCtrl* mCheckContributeWithDeed; + + LLTextBox* mSaleInfoForSale1; + LLTextBox* mSaleInfoForSale2; + LLTextBox* mSaleInfoForSaleObjects; + LLTextBox* mSaleInfoForSaleNoObjects; + LLTextBox* mSaleInfoNotForSale; + LLButton* mBtnSellLand; + LLButton* mBtnStopSellLand; + + LLTextBox* mTextDwell; + + LLButton* mBtnBuyLand; + LLButton* mBtnScriptLimits; + LLButton* mBtnBuyGroupLand; + + // these buttons share the same location, but + // reclaim is in exactly the same visual place, + // ond is only shown for estate owners on their + // estate since they cannot release land. + LLButton* mBtnReleaseLand; + LLButton* mBtnReclaimLand; + + LLButton* mBtnBuyPass; + LLButton* mBtnStartAuction; + + LLSafeHandle& mParcel; + + // This pointer is needed to avoid parcel deselection until buying pass is completed or canceled. + // Deselection happened because of zero references to parcel selection, which took place when + // "Buy Pass" was called from popup menu(EXT-6464) + static LLPointer sSelectionForBuyPass; + + static LLHandle sBuyPassDialogHandle; +}; + +class LLPanelLandObjects +: public LLPanel +{ +public: + LLPanelLandObjects(LLSafeHandle& parcelp); + virtual ~LLPanelLandObjects(); + /*virtual*/ void refresh(); + virtual void draw(); + + bool callbackReturnOwnerObjects(const LLSD& notification, const LLSD& response); + bool callbackReturnGroupObjects(const LLSD& notification, const LLSD& response); + bool callbackReturnOtherObjects(const LLSD& notification, const LLSD& response); + bool callbackReturnOwnerList(const LLSD& notification, const LLSD& response); + + static void clickShowCore(LLPanelLandObjects* panelp, S32 return_type, uuid_list_t* list = 0); + static void onClickShowOwnerObjects(void*); + static void onClickShowGroupObjects(void*); + static void onClickShowOtherObjects(void*); + + static void onClickReturnOwnerObjects(void*); + static void onClickReturnGroupObjects(void*); + static void onClickReturnOtherObjects(void*); + static void onClickReturnOwnerList(void*); + static void onClickRefresh(void*); + + static void onDoubleClickOwner(void*); + + static void onCommitList(LLUICtrl* ctrl, void* data); + static void onLostFocus(LLFocusableElement* caller, void* user_data); + static void onCommitClean(LLUICtrl* caller, void* user_data); + static void processParcelObjectOwnersReply(LLMessageSystem *msg, void **); + + virtual bool postBuild(); + +protected: + + LLTextBox *mParcelObjectBonus; + LLTextBox *mSWTotalObjects; + LLTextBox *mObjectContribution; + LLTextBox *mTotalObjects; + LLTextBox *mOwnerObjects; + LLButton *mBtnShowOwnerObjects; + LLButton *mBtnReturnOwnerObjects; + LLTextBox *mGroupObjects; + LLButton *mBtnShowGroupObjects; + LLButton *mBtnReturnGroupObjects; + LLTextBox *mOtherObjects; + LLButton *mBtnShowOtherObjects; + LLButton *mBtnReturnOtherObjects; + LLTextBox *mSelectedObjects; + LLLineEditor *mCleanOtherObjectsTime; + S32 mOtherTime; + LLButton *mBtnRefresh; + LLButton *mBtnReturnOwnerList; + LLNameListCtrl *mOwnerList; + + LLPointer mIconAvatarOnline; + LLPointer mIconAvatarOffline; + LLPointer mIconGroup; + + bool mFirstReply; + + uuid_list_t mSelectedOwners; + std::string mSelectedName; + S32 mSelectedCount; + bool mSelectedIsGroup; + + LLSafeHandle& mParcel; +}; + + +class LLPanelLandOptions +: public LLPanel +{ +public: + LLPanelLandOptions(LLSafeHandle& parcelp); + virtual ~LLPanelLandOptions(); + /*virtual*/ bool postBuild(); + /*virtual*/ void draw(); + /*virtual*/ void refresh(); + +private: + // Refresh the "show in search" checkbox and category selector. + void refreshSearch(); + + static void onCommitAny(LLUICtrl* ctrl, void *userdata); + static void onClickSet(void* userdata); + static void onClickClear(void* userdata); + static void toggleSeeAvatars(void* userdata); + +private: + LLCheckBoxCtrl* mCheckEditObjects; + LLCheckBoxCtrl* mCheckEditGroupObjects; + LLCheckBoxCtrl* mCheckAllObjectEntry; + LLCheckBoxCtrl* mCheckGroupObjectEntry; + LLCheckBoxCtrl* mCheckSafe; + LLCheckBoxCtrl* mCheckFly; + LLCheckBoxCtrl* mCheckGroupScripts; + LLCheckBoxCtrl* mCheckOtherScripts; + + LLCheckBoxCtrl* mCheckShowDirectory; + LLComboBox* mCategoryCombo; + LLComboBox* mLandingTypeCombo; + + LLTextureCtrl* mSnapshotCtrl; + + LLTextBox* mLocationText; + LLTextBox* mSeeAvatarsText; + LLButton* mSetBtn; + LLButton* mClearBtn; + + LLCheckBoxCtrl *mMatureCtrl; + LLCheckBoxCtrl *mPushRestrictionCtrl; + LLCheckBoxCtrl *mSeeAvatarsCtrl; + + LLSafeHandle& mParcel; +}; + + +class LLPanelLandAccess +: public LLPanel +{ +public: + LLPanelLandAccess(LLSafeHandle& parcelp); + virtual ~LLPanelLandAccess(); + void refresh(); + void refresh_ui(); + void refreshNames(); + virtual void draw(); + + static void onCommitPublicAccess(LLUICtrl* ctrl, void *userdata); + static void onCommitAny(LLUICtrl* ctrl, void *userdata); + static void onCommitGroupCheck(LLUICtrl* ctrl, void *userdata); + static void onClickRemoveAccess(void*); + static void onClickRemoveBanned(void*); + + virtual bool postBuild(); + + void onClickAddAccess(); + void onClickAddBanned(); + void callbackAvatarCBBanned(const uuid_vec_t& ids); + void callbackAvatarCBBanned2(const uuid_vec_t& ids, S32 duration); + void callbackAvatarCBAccess(const uuid_vec_t& ids); + +protected: + LLNameListCtrl* mListAccess; + LLNameListCtrl* mListBanned; + + LLSafeHandle& mParcel; +}; + + +class LLPanelLandCovenant +: public LLPanel +{ +public: + LLPanelLandCovenant(LLSafeHandle& parcelp); + virtual ~LLPanelLandCovenant(); + virtual bool postBuild(); + void refresh(); + static void updateCovenantText(const std::string& string); + static void updateEstateName(const std::string& name); + static void updateLastModified(const std::string& text); + static void updateEstateOwnerName(const std::string& name); + +protected: + LLSafeHandle& mParcel; + +private: + LLUUID mLastRegionID; + F64 mNextUpdateTime; //seconds since client start + LLTextBox* mTextEstateOwner; +}; + +#endif diff --git a/indra/newview/llfloaterlandholdings.cpp b/indra/newview/llfloaterlandholdings.cpp index 5f864e54c1..df602387b3 100644 --- a/indra/newview/llfloaterlandholdings.cpp +++ b/indra/newview/llfloaterlandholdings.cpp @@ -1,355 +1,355 @@ -/** - * @file llfloaterlandholdings.cpp - * @brief "My Land" floater showing all your land parcels. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterlandholdings.h" - -#include "indra_constants.h" -#include "llfontgl.h" -#include "llqueryflags.h" -#include "llparcel.h" -#include "message.h" - -#include "llagent.h" -#include "llfloaterreg.h" -#include "llfloaterworldmap.h" -#include "llproductinforequest.h" -#include "llscrolllistctrl.h" -#include "llsdutil.h" -#include "llstatusbar.h" -#include "lltextbox.h" -#include "llscrolllistctrl.h" -#include "llscrolllistitem.h" -#include "llscrolllistcell.h" -#include "lltrans.h" -#include "lluiconstants.h" -#include "llviewermessage.h" -#include "lluictrlfactory.h" - -#include "llgroupactions.h" - -const std::string LINDEN_HOMES_SKU = "131"; -bool LLFloaterLandHoldings::sHasLindenHome = false; - -// protected -LLFloaterLandHoldings::LLFloaterLandHoldings(const LLSD& key) -: LLFloater(key), - mActualArea(0), - mBillableArea(0), - mFirstPacketReceived(false), - mSortColumn(""), - mSortAscending(true) -{ -} - -bool LLFloaterLandHoldings::postBuild() -{ - childSetAction("Teleport", onClickTeleport, this); - childSetAction("Show on Map", onClickMap, this); - - // Grant list - LLScrollListCtrl* grant_list = getChild("grant list"); - grant_list->sortByColumnIndex(0, true); - grant_list->setDoubleClickCallback(onGrantList, this); - - S32 count = gAgent.mGroups.size(); - for(S32 i = 0; i < count; ++i) - { - LLUUID id(gAgent.mGroups.at(i).mID); - LLUIString areastr = getString("area_string"); - areastr.setArg("[AREA]", llformat("%d", gAgent.mGroups.at(i).mContribution)); - - grant_list->addElement( - llsd::map( - "id", id, - "columns", llsd::array( - llsd::map( - "column", "group", - "value", gAgent.mGroups.at(i).mName, - "font", "SANSSERIF"), - llsd::map( - "column", "area", - "value", areastr, - "font", "SANSSERIF")))); - } - - center(); - - return true; -} - - -// protected -LLFloaterLandHoldings::~LLFloaterLandHoldings() -{ -} - -void LLFloaterLandHoldings::onOpen(const LLSD& key) -{ - LLScrollListCtrl *list = getChild("parcel list"); - list->clearRows(); - - // query_id null is known to be us - const LLUUID& query_id = LLUUID::null; - - // look only for parcels we own - U32 query_flags = DFQ_AGENT_OWNED; - - send_places_query(query_id, - LLUUID::null, - "", - query_flags, - LLParcel::C_ANY, - ""); -} - -void LLFloaterLandHoldings::draw() -{ - refresh(); - - LLFloater::draw(); -} - - -// public -void LLFloaterLandHoldings::refresh() -{ - LLCtrlSelectionInterface *list = childGetSelectionInterface("parcel list"); - bool enable_btns = false; - if (list && list->getFirstSelectedIndex()> -1) - { - enable_btns = true; - } - - getChildView("Teleport")->setEnabled(enable_btns); - getChildView("Show on Map")->setEnabled(enable_btns); - - refreshAggregates(); -} - - -// static -void LLFloaterLandHoldings::processPlacesReply(LLMessageSystem* msg, void**) -{ - LLFloaterLandHoldings* self = LLFloaterReg::findTypedInstance("land_holdings"); - S32 count = msg->getNumberOfBlocks("QueryData"); - std::string land_sku; - sHasLindenHome = false; - if (!self) - { - for (S32 i = 0; i < count; i++) - { - if ( msg->getSizeFast(_PREHASH_QueryData, i, _PREHASH_ProductSKU) > 0 ) - { - msg->getStringFast( _PREHASH_QueryData, _PREHASH_ProductSKU, land_sku, i); - - if (LINDEN_HOMES_SKU == land_sku) - { - sHasLindenHome = true; - return; - } - } - } - return; - } - - LLCtrlListInterface *list = self->childGetListInterface("parcel list"); - if (!list) return; - - // If this is the first packet, clear out the "loading..." indicator - if (!self->mFirstPacketReceived) - { - self->mFirstPacketReceived = true; - list->operateOnAll(LLCtrlSelectionInterface::OP_DELETE); - } - - LLUUID owner_id; - std::string name; - std::string desc; - S32 actual_area; - S32 billable_area; - U8 flags; - F32 global_x; - F32 global_y; - std::string sim_name; - std::string land_type; - - for (S32 i = 0; i < count; i++) - { - msg->getUUID("QueryData", "OwnerID", owner_id, i); - msg->getString("QueryData", "Name", name, i); - msg->getString("QueryData", "Desc", desc, i); - msg->getS32("QueryData", "ActualArea", actual_area, i); - msg->getS32("QueryData", "BillableArea", billable_area, i); - msg->getU8("QueryData", "Flags", flags, i); - msg->getF32("QueryData", "GlobalX", global_x, i); - msg->getF32("QueryData", "GlobalY", global_y, i); - msg->getString("QueryData", "SimName", sim_name, i); - - if ( msg->getSizeFast(_PREHASH_QueryData, i, _PREHASH_ProductSKU) > 0 ) - { - msg->getStringFast( _PREHASH_QueryData, _PREHASH_ProductSKU, land_sku, i); - LL_INFOS() << "Land sku: " << land_sku << LL_ENDL; - land_type = LLProductInfoRequestManager::instance().getDescriptionForSku(land_sku); - if (LINDEN_HOMES_SKU == land_sku) - { - sHasLindenHome = true; - } - } - else - { - land_sku.clear(); - land_type = LLTrans::getString("land_type_unknown"); - } - - if(owner_id.notNull()) - { - self->mActualArea += actual_area; - self->mBillableArea += billable_area; - - S32 region_x = ll_round(global_x) % REGION_WIDTH_UNITS; - S32 region_y = ll_round(global_y) % REGION_WIDTH_UNITS; - - std::string location; - location = llformat("%s (%d, %d)", sim_name.c_str(), region_x, region_y); - - std::string area; - if(billable_area == actual_area) - { - area = llformat("%d", billable_area); - } - else - { - area = llformat("%d / %d", billable_area, actual_area); - } - - std::string hidden; - hidden = llformat("%f %f", global_x, global_y); - - LLSD element; - element["columns"][0]["column"] = "name"; - element["columns"][0]["value"] = name; - element["columns"][0]["font"] = "SANSSERIF"; - - element["columns"][1]["column"] = "location"; - element["columns"][1]["value"] = location; - element["columns"][1]["font"] = "SANSSERIF"; - - element["columns"][2]["column"] = "area"; - element["columns"][2]["value"] = area; - element["columns"][2]["font"] = "SANSSERIF"; - - element["columns"][3]["column"] = "type"; - element["columns"][3]["value"] = land_type; - element["columns"][3]["font"] = "SANSSERIF"; - - // hidden is always last column - element["columns"][4]["column"] = "hidden"; - element["columns"][4]["value"] = hidden; - - list->addElement(element); - } - } - - self->refreshAggregates(); -} - -void LLFloaterLandHoldings::buttonCore(S32 which) -{ - LLScrollListCtrl *list = getChild("parcel list"); - if (!list) return; - - S32 index = list->getFirstSelectedIndex(); - if (index < 0) return; - - // hidden is always last column - std::string location = list->getSelectedItemLabel(list->getNumColumns()-1); - - F32 global_x = 0.f; - F32 global_y = 0.f; - sscanf(location.c_str(), "%f %f", &global_x, &global_y); - - // Hack: Use the agent's z-height - F64 global_z = gAgent.getPositionGlobal().mdV[VZ]; - - LLVector3d pos_global(global_x, global_y, global_z); - LLFloaterWorldMap* floater_world_map = LLFloaterWorldMap::getInstance(); - - switch(which) - { - case 0: - gAgent.teleportViaLocation(pos_global); - if(floater_world_map) floater_world_map->trackLocation(pos_global); - break; - case 1: - if(floater_world_map) floater_world_map->trackLocation(pos_global); - LLFloaterReg::showInstance("world_map", "center"); - break; - default: - break; - } -} - -// static -void LLFloaterLandHoldings::onClickTeleport(void* data) -{ - LLFloaterLandHoldings* self = (LLFloaterLandHoldings*)data; - self->buttonCore(0); - self->closeFloater(); -} - - -// static -void LLFloaterLandHoldings::onClickMap(void* data) -{ - LLFloaterLandHoldings* self = (LLFloaterLandHoldings*)data; - self->buttonCore(1); -} - -// static -void LLFloaterLandHoldings::onGrantList(void* data) -{ - LLFloaterLandHoldings* self = (LLFloaterLandHoldings*)data; - LLCtrlSelectionInterface *list = self->childGetSelectionInterface("grant list"); - if (!list) return; - LLUUID group_id = list->getCurrentID(); - if (group_id.notNull()) - { - LLGroupActions::show(group_id); - } -} - -void LLFloaterLandHoldings::refreshAggregates() -{ - S32 allowed_area = gStatusBar->getSquareMetersCredit(); - S32 current_area = gStatusBar->getSquareMetersCommitted(); - S32 available_area = gStatusBar->getSquareMetersLeft(); - - getChild("allowed_text")->setTextArg("[AREA]", llformat("%d",allowed_area)); - getChild("current_text")->setTextArg("[AREA]", llformat("%d",current_area)); - getChild("available_text")->setTextArg("[AREA]", llformat("%d",available_area)); -} +/** + * @file llfloaterlandholdings.cpp + * @brief "My Land" floater showing all your land parcels. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterlandholdings.h" + +#include "indra_constants.h" +#include "llfontgl.h" +#include "llqueryflags.h" +#include "llparcel.h" +#include "message.h" + +#include "llagent.h" +#include "llfloaterreg.h" +#include "llfloaterworldmap.h" +#include "llproductinforequest.h" +#include "llscrolllistctrl.h" +#include "llsdutil.h" +#include "llstatusbar.h" +#include "lltextbox.h" +#include "llscrolllistctrl.h" +#include "llscrolllistitem.h" +#include "llscrolllistcell.h" +#include "lltrans.h" +#include "lluiconstants.h" +#include "llviewermessage.h" +#include "lluictrlfactory.h" + +#include "llgroupactions.h" + +const std::string LINDEN_HOMES_SKU = "131"; +bool LLFloaterLandHoldings::sHasLindenHome = false; + +// protected +LLFloaterLandHoldings::LLFloaterLandHoldings(const LLSD& key) +: LLFloater(key), + mActualArea(0), + mBillableArea(0), + mFirstPacketReceived(false), + mSortColumn(""), + mSortAscending(true) +{ +} + +bool LLFloaterLandHoldings::postBuild() +{ + childSetAction("Teleport", onClickTeleport, this); + childSetAction("Show on Map", onClickMap, this); + + // Grant list + LLScrollListCtrl* grant_list = getChild("grant list"); + grant_list->sortByColumnIndex(0, true); + grant_list->setDoubleClickCallback(onGrantList, this); + + S32 count = gAgent.mGroups.size(); + for(S32 i = 0; i < count; ++i) + { + LLUUID id(gAgent.mGroups.at(i).mID); + LLUIString areastr = getString("area_string"); + areastr.setArg("[AREA]", llformat("%d", gAgent.mGroups.at(i).mContribution)); + + grant_list->addElement( + llsd::map( + "id", id, + "columns", llsd::array( + llsd::map( + "column", "group", + "value", gAgent.mGroups.at(i).mName, + "font", "SANSSERIF"), + llsd::map( + "column", "area", + "value", areastr, + "font", "SANSSERIF")))); + } + + center(); + + return true; +} + + +// protected +LLFloaterLandHoldings::~LLFloaterLandHoldings() +{ +} + +void LLFloaterLandHoldings::onOpen(const LLSD& key) +{ + LLScrollListCtrl *list = getChild("parcel list"); + list->clearRows(); + + // query_id null is known to be us + const LLUUID& query_id = LLUUID::null; + + // look only for parcels we own + U32 query_flags = DFQ_AGENT_OWNED; + + send_places_query(query_id, + LLUUID::null, + "", + query_flags, + LLParcel::C_ANY, + ""); +} + +void LLFloaterLandHoldings::draw() +{ + refresh(); + + LLFloater::draw(); +} + + +// public +void LLFloaterLandHoldings::refresh() +{ + LLCtrlSelectionInterface *list = childGetSelectionInterface("parcel list"); + bool enable_btns = false; + if (list && list->getFirstSelectedIndex()> -1) + { + enable_btns = true; + } + + getChildView("Teleport")->setEnabled(enable_btns); + getChildView("Show on Map")->setEnabled(enable_btns); + + refreshAggregates(); +} + + +// static +void LLFloaterLandHoldings::processPlacesReply(LLMessageSystem* msg, void**) +{ + LLFloaterLandHoldings* self = LLFloaterReg::findTypedInstance("land_holdings"); + S32 count = msg->getNumberOfBlocks("QueryData"); + std::string land_sku; + sHasLindenHome = false; + if (!self) + { + for (S32 i = 0; i < count; i++) + { + if ( msg->getSizeFast(_PREHASH_QueryData, i, _PREHASH_ProductSKU) > 0 ) + { + msg->getStringFast( _PREHASH_QueryData, _PREHASH_ProductSKU, land_sku, i); + + if (LINDEN_HOMES_SKU == land_sku) + { + sHasLindenHome = true; + return; + } + } + } + return; + } + + LLCtrlListInterface *list = self->childGetListInterface("parcel list"); + if (!list) return; + + // If this is the first packet, clear out the "loading..." indicator + if (!self->mFirstPacketReceived) + { + self->mFirstPacketReceived = true; + list->operateOnAll(LLCtrlSelectionInterface::OP_DELETE); + } + + LLUUID owner_id; + std::string name; + std::string desc; + S32 actual_area; + S32 billable_area; + U8 flags; + F32 global_x; + F32 global_y; + std::string sim_name; + std::string land_type; + + for (S32 i = 0; i < count; i++) + { + msg->getUUID("QueryData", "OwnerID", owner_id, i); + msg->getString("QueryData", "Name", name, i); + msg->getString("QueryData", "Desc", desc, i); + msg->getS32("QueryData", "ActualArea", actual_area, i); + msg->getS32("QueryData", "BillableArea", billable_area, i); + msg->getU8("QueryData", "Flags", flags, i); + msg->getF32("QueryData", "GlobalX", global_x, i); + msg->getF32("QueryData", "GlobalY", global_y, i); + msg->getString("QueryData", "SimName", sim_name, i); + + if ( msg->getSizeFast(_PREHASH_QueryData, i, _PREHASH_ProductSKU) > 0 ) + { + msg->getStringFast( _PREHASH_QueryData, _PREHASH_ProductSKU, land_sku, i); + LL_INFOS() << "Land sku: " << land_sku << LL_ENDL; + land_type = LLProductInfoRequestManager::instance().getDescriptionForSku(land_sku); + if (LINDEN_HOMES_SKU == land_sku) + { + sHasLindenHome = true; + } + } + else + { + land_sku.clear(); + land_type = LLTrans::getString("land_type_unknown"); + } + + if(owner_id.notNull()) + { + self->mActualArea += actual_area; + self->mBillableArea += billable_area; + + S32 region_x = ll_round(global_x) % REGION_WIDTH_UNITS; + S32 region_y = ll_round(global_y) % REGION_WIDTH_UNITS; + + std::string location; + location = llformat("%s (%d, %d)", sim_name.c_str(), region_x, region_y); + + std::string area; + if(billable_area == actual_area) + { + area = llformat("%d", billable_area); + } + else + { + area = llformat("%d / %d", billable_area, actual_area); + } + + std::string hidden; + hidden = llformat("%f %f", global_x, global_y); + + LLSD element; + element["columns"][0]["column"] = "name"; + element["columns"][0]["value"] = name; + element["columns"][0]["font"] = "SANSSERIF"; + + element["columns"][1]["column"] = "location"; + element["columns"][1]["value"] = location; + element["columns"][1]["font"] = "SANSSERIF"; + + element["columns"][2]["column"] = "area"; + element["columns"][2]["value"] = area; + element["columns"][2]["font"] = "SANSSERIF"; + + element["columns"][3]["column"] = "type"; + element["columns"][3]["value"] = land_type; + element["columns"][3]["font"] = "SANSSERIF"; + + // hidden is always last column + element["columns"][4]["column"] = "hidden"; + element["columns"][4]["value"] = hidden; + + list->addElement(element); + } + } + + self->refreshAggregates(); +} + +void LLFloaterLandHoldings::buttonCore(S32 which) +{ + LLScrollListCtrl *list = getChild("parcel list"); + if (!list) return; + + S32 index = list->getFirstSelectedIndex(); + if (index < 0) return; + + // hidden is always last column + std::string location = list->getSelectedItemLabel(list->getNumColumns()-1); + + F32 global_x = 0.f; + F32 global_y = 0.f; + sscanf(location.c_str(), "%f %f", &global_x, &global_y); + + // Hack: Use the agent's z-height + F64 global_z = gAgent.getPositionGlobal().mdV[VZ]; + + LLVector3d pos_global(global_x, global_y, global_z); + LLFloaterWorldMap* floater_world_map = LLFloaterWorldMap::getInstance(); + + switch(which) + { + case 0: + gAgent.teleportViaLocation(pos_global); + if(floater_world_map) floater_world_map->trackLocation(pos_global); + break; + case 1: + if(floater_world_map) floater_world_map->trackLocation(pos_global); + LLFloaterReg::showInstance("world_map", "center"); + break; + default: + break; + } +} + +// static +void LLFloaterLandHoldings::onClickTeleport(void* data) +{ + LLFloaterLandHoldings* self = (LLFloaterLandHoldings*)data; + self->buttonCore(0); + self->closeFloater(); +} + + +// static +void LLFloaterLandHoldings::onClickMap(void* data) +{ + LLFloaterLandHoldings* self = (LLFloaterLandHoldings*)data; + self->buttonCore(1); +} + +// static +void LLFloaterLandHoldings::onGrantList(void* data) +{ + LLFloaterLandHoldings* self = (LLFloaterLandHoldings*)data; + LLCtrlSelectionInterface *list = self->childGetSelectionInterface("grant list"); + if (!list) return; + LLUUID group_id = list->getCurrentID(); + if (group_id.notNull()) + { + LLGroupActions::show(group_id); + } +} + +void LLFloaterLandHoldings::refreshAggregates() +{ + S32 allowed_area = gStatusBar->getSquareMetersCredit(); + S32 current_area = gStatusBar->getSquareMetersCommitted(); + S32 available_area = gStatusBar->getSquareMetersLeft(); + + getChild("allowed_text")->setTextArg("[AREA]", llformat("%d",allowed_area)); + getChild("current_text")->setTextArg("[AREA]", llformat("%d",current_area)); + getChild("available_text")->setTextArg("[AREA]", llformat("%d",available_area)); +} diff --git a/indra/newview/llfloaterlandholdings.h b/indra/newview/llfloaterlandholdings.h index fdc4f90adc..d8a823e49c 100644 --- a/indra/newview/llfloaterlandholdings.h +++ b/indra/newview/llfloaterlandholdings.h @@ -1,78 +1,78 @@ -/** - * @file llfloaterlandholdings.h - * @brief "My Land" floater showing all your land parcels. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERLANDHOLDINGS_H -#define LL_LLFLOATERLANDHOLDINGS_H - -#include "llfloater.h" - -class LLMessageSystem; -class LLTextBox; -class LLScrollListCtrl; -class LLButton; - -class LLFloaterLandHoldings -: public LLFloater -{ -public: - LLFloaterLandHoldings(const LLSD& key); - virtual ~LLFloaterLandHoldings(); - - virtual bool postBuild(); - virtual void onOpen(const LLSD& key); - virtual void draw(); - - void refresh(); - - void buttonCore(S32 which); - - static void processPlacesReply(LLMessageSystem* msg, void**); - - static void onClickTeleport(void*); - static void onClickMap(void*); - static void onClickLandmark(void*); - - static void onGrantList(void* data); - - static bool sHasLindenHome; - -protected: - void refreshAggregates(); - -protected: - // Sum up as packets arrive the total holdings - S32 mActualArea; - S32 mBillableArea; - - // Has a packet of data been received? - // Used to clear out the mParcelList's "Loading..." indicator - bool mFirstPacketReceived; - - std::string mSortColumn; - bool mSortAscending; -}; - -#endif +/** + * @file llfloaterlandholdings.h + * @brief "My Land" floater showing all your land parcels. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERLANDHOLDINGS_H +#define LL_LLFLOATERLANDHOLDINGS_H + +#include "llfloater.h" + +class LLMessageSystem; +class LLTextBox; +class LLScrollListCtrl; +class LLButton; + +class LLFloaterLandHoldings +: public LLFloater +{ +public: + LLFloaterLandHoldings(const LLSD& key); + virtual ~LLFloaterLandHoldings(); + + virtual bool postBuild(); + virtual void onOpen(const LLSD& key); + virtual void draw(); + + void refresh(); + + void buttonCore(S32 which); + + static void processPlacesReply(LLMessageSystem* msg, void**); + + static void onClickTeleport(void*); + static void onClickMap(void*); + static void onClickLandmark(void*); + + static void onGrantList(void* data); + + static bool sHasLindenHome; + +protected: + void refreshAggregates(); + +protected: + // Sum up as packets arrive the total holdings + S32 mActualArea; + S32 mBillableArea; + + // Has a packet of data been received? + // Used to clear out the mParcelList's "Loading..." indicator + bool mFirstPacketReceived; + + std::string mSortColumn; + bool mSortAscending; +}; + +#endif diff --git a/indra/newview/llfloaterlinkreplace.cpp b/indra/newview/llfloaterlinkreplace.cpp index 155e8c66d5..c961070787 100644 --- a/indra/newview/llfloaterlinkreplace.cpp +++ b/indra/newview/llfloaterlinkreplace.cpp @@ -1,429 +1,429 @@ -/** - * @file llfloaterlinkreplace.cpp - * @brief Allows replacing link targets in inventory links - * @author Ansariel Hiller - * - * $LicenseInfo:firstyear=2017&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2017, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterlinkreplace.h" - -#include "llagent.h" -#include "llappearancemgr.h" -#include "lllineeditor.h" -#include "llnotificationsutil.h" -#include "llnotifications.h" -#include "lltextbox.h" -#include "llviewercontrol.h" - -LLFloaterLinkReplace::LLFloaterLinkReplace(const LLSD& key) - : LLFloater(key), - LLEventTimer(gSavedSettings.getF32("LinkReplaceBatchPauseTime")), - mRemainingItems(0), - mSourceUUID(LLUUID::null), - mTargetUUID(LLUUID::null), - mBatchSize(gSavedSettings.getU32("LinkReplaceBatchSize")) -{ - mEventTimer.stop(); -} - -LLFloaterLinkReplace::~LLFloaterLinkReplace() -{ -} - -bool LLFloaterLinkReplace::postBuild() -{ - mStartBtn = getChild("btn_start"); - mStartBtn->setCommitCallback(boost::bind(&LLFloaterLinkReplace::onStartClicked, this)); - - mRefreshBtn = getChild("btn_refresh"); - mRefreshBtn->setCommitCallback(boost::bind(&LLFloaterLinkReplace::checkEnableStart, this)); - - mSourceEditor = getChild("source_uuid_editor"); - mTargetEditor = getChild("target_uuid_editor"); - - mSourceEditor->setDADCallback(boost::bind(&LLFloaterLinkReplace::onSourceItemDrop, this, _1)); - mTargetEditor->setDADCallback(boost::bind(&LLFloaterLinkReplace::onTargetItemDrop, this, _1)); - - mStatusText = getChild("status_text"); - - return true; -} - -void LLFloaterLinkReplace::onOpen(const LLSD& key) -{ - if (key.asUUID().notNull()) - { - LLUUID item_id = key.asUUID(); - LLViewerInventoryItem* item = gInventory.getItem(item_id); - mSourceEditor->setItem(item); - onSourceItemDrop(item->getLinkedUUID()); - } - else - { - checkEnableStart(); - } -} - -void LLFloaterLinkReplace::onSourceItemDrop(const LLUUID& source_item_id) -{ - mSourceUUID = source_item_id; - checkEnableStart(); -} - -void LLFloaterLinkReplace::onTargetItemDrop(const LLUUID& target_item_id) -{ - mTargetUUID = target_item_id; - checkEnableStart(); -} - -void LLFloaterLinkReplace::updateFoundLinks() -{ - LLInventoryModel::item_array_t items; - LLInventoryModel::cat_array_t cat_array; - LLLinkedItemIDMatches is_linked_item_match(mSourceUUID); - gInventory.collectDescendentsIf(gInventory.getRootFolderID(), - cat_array, - items, - LLInventoryModel::INCLUDE_TRASH, - is_linked_item_match); - mRemainingItems = (U32)items.size(); - - LLStringUtil::format_map_t args; - args["NUM"] = llformat("%d", mRemainingItems); - mStatusText->setText(getString("ItemsFound", args)); -} - -void LLFloaterLinkReplace::checkEnableStart() -{ - if (mSourceUUID.notNull() && mTargetUUID.notNull() && mSourceUUID == mTargetUUID) - { - mStatusText->setText(getString("ItemsIdentical")); - } - else if (mSourceUUID.notNull()) - { - updateFoundLinks(); - } - - mStartBtn->setEnabled(mRemainingItems > 0 && mSourceUUID.notNull() && mTargetUUID.notNull() && mSourceUUID != mTargetUUID); -} - -void LLFloaterLinkReplace::onStartClicked() -{ - LL_INFOS() << "Starting inventory link replace" << LL_ENDL; - - if (mSourceUUID.isNull() || mTargetUUID.isNull()) - { - LL_WARNS() << "Cannot replace. Either source or target UUID is null." << LL_ENDL; - return; - } - - if (mSourceUUID == mTargetUUID) - { - LL_WARNS() << "Cannot replace. Source and target are identical." << LL_ENDL; - return; - } - - const LLUUID& source_item_id = gInventory.getLinkedItemID(mSourceUUID); - LLViewerInventoryItem *source_item = gInventory.getItem(source_item_id); - const LLUUID& target_item_id = gInventory.getLinkedItemID(mTargetUUID); - LLViewerInventoryItem *target_item = gInventory.getItem(target_item_id); - - - LLNotification::Params params("ConfirmReplaceLink"); - params.functor.function(boost::bind(&LLFloaterLinkReplace::onStartClickedResponse, this, _1, _2)); - if (source_item && source_item->isWearableType() && source_item->getWearableType() <= LLWearableType::WT_EYES) - { - if(target_item && target_item->isWearableType() && source_item->getWearableType() == target_item->getWearableType()) - { - LLNotifications::instance().forceResponse(params, 0); - } - else - { - LLSD args; - args["TYPE"] = LLWearableType::getInstance()->getTypeName(source_item->getWearableType()); - params.substitutions(args); - LLNotifications::instance().add(params); - } - } - else - { - LLNotifications::instance().forceResponse(params, 0); - } -} - -void LLFloaterLinkReplace::onStartClickedResponse(const LLSD& notification, const LLSD& response) -{ - - if (LLNotificationsUtil::getSelectedOption(notification, response) == 0) - { - - LLInventoryModel::cat_array_t cat_array; - LLLinkedItemIDMatches is_linked_item_match(mSourceUUID); - gInventory.collectDescendentsIf(gInventory.getRootFolderID(), - cat_array, - mRemainingInventoryItems, - LLInventoryModel::INCLUDE_TRASH, - is_linked_item_match); - LL_INFOS() << "Found " << mRemainingInventoryItems.size() << " inventory links that need to be replaced." << LL_ENDL; - - if (mRemainingInventoryItems.size() > 0) - { - LLViewerInventoryItem* target_item = gInventory.getItem(mTargetUUID); - if (target_item) - { - mRemainingItems = (U32)mRemainingInventoryItems.size(); - - LLStringUtil::format_map_t args; - args["NUM"] = llformat("%d", mRemainingItems); - mStatusText->setText(getString("ItemsRemaining", args)); - - mStartBtn->setEnabled(false); - mRefreshBtn->setEnabled(false); - - mEventTimer.start(); - tick(); - } - else - { - mStatusText->setText(getString("TargetNotFound")); - LL_WARNS() << "Link replace target not found." << LL_ENDL; - } - } - } -} - -// static -void LLFloaterLinkReplace::linkCreatedCallback(LLHandle floater_handle, const LLUUID& old_item_id, const LLUUID& target_item_id, - bool needs_wearable_ordering_update, bool needs_description_update, const LLUUID& outfit_folder_id) -{ - LL_DEBUGS() << "Inventory link replace:" << LL_NEWLINE - << " - old_item_id = " << old_item_id.asString() << LL_NEWLINE - << " - target_item_id = " << target_item_id.asString() << LL_NEWLINE - << " - order update = " << (needs_wearable_ordering_update ? "true" : "false") << LL_NEWLINE - << " - description update = " << (needs_description_update ? "true" : "false") << LL_NEWLINE - << " - outfit_folder_id = " << outfit_folder_id.asString() << LL_ENDL; - - // If we are replacing an object, bodypart or gesture link within an outfit folder, - // we need to change the actual description of the link itself. LLAppearanceMgr *should* - // have created COF links that will be used to save the outfit with an empty description. - // Since link_inventory_array() will set the description of the linked item for the link - // itself, this will lead to a dirty outfit state when the outfit with the replaced - // link is worn. So we have to correct this. - if (needs_description_update && outfit_folder_id.notNull()) - { - LLInventoryModel::item_array_t items; - LLInventoryModel::cat_array_t cats; - LLLinkedItemIDMatches is_target_link(target_item_id); - gInventory.collectDescendentsIf(outfit_folder_id, - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - is_target_link); - - for (LLInventoryModel::item_array_t::iterator it = items.begin(); it != items.end(); ++it) - { - LLPointer item = *it; - - if ((item->getType() == LLAssetType::AT_BODYPART || - item->getType() == LLAssetType::AT_OBJECT || - item->getType() == LLAssetType::AT_GESTURE) - && !item->getActualDescription().empty()) - { - LL_DEBUGS() << "Updating description for " << item->getName() << LL_ENDL; - - LLSD updates; - updates["desc"] = ""; - update_inventory_item(item->getUUID(), updates, LLPointer(NULL)); - } - } - } - - LLUUID outfit_update_folder = LLUUID::null; - if (needs_wearable_ordering_update && outfit_folder_id.notNull()) - { - // If a wearable item was involved in the link replace operation and replaced - // a link in an outfit folder, we need to update the clothing ordering information - // *after* the original link has been removed. LLAppearanceMgr abuses the actual link - // description to store the clothing ordering information it. We will have to update - // the clothing ordering information or the outfit will be in dirty state when worn. - outfit_update_folder = outfit_folder_id; - } - - LLPointer cb = new LLBoostFuncInventoryCallback(boost::bind(&LLFloaterLinkReplace::itemRemovedCallback, floater_handle, outfit_update_folder)); - remove_inventory_object(old_item_id, cb); -} - -// static -void LLFloaterLinkReplace::itemRemovedCallback(LLHandle floater_handle, const LLUUID& outfit_folder_id) -{ - if (outfit_folder_id.notNull()) - { - LLAppearanceMgr::getInstance()->updateClothingOrderingInfo(outfit_folder_id); - } - - if (!floater_handle.isDead()) - { - floater_handle.get()->decreaseOpenItemCount(); - } -} - -void LLFloaterLinkReplace::decreaseOpenItemCount() -{ - mRemainingItems--; - - if (mRemainingItems == 0) - { - mStatusText->setText(getString("ReplaceFinished")); - mStartBtn->setEnabled(true); - mRefreshBtn->setEnabled(true); - mEventTimer.stop(); - LL_INFOS() << "Inventory link replace finished." << LL_ENDL; - } - else - { - LLStringUtil::format_map_t args; - args["NUM"] = llformat("%d", mRemainingItems); - mStatusText->setText(getString("ItemsRemaining", args)); - LL_DEBUGS() << "Inventory link replace: " << mRemainingItems << " links remaining..." << LL_ENDL; - } -} - -bool LLFloaterLinkReplace::tick() -{ - LL_DEBUGS() << "Calling tick - remaining items = " << mRemainingInventoryItems.size() << LL_ENDL; - - LLInventoryModel::item_array_t current_batch; - - for (U32 i = 0; i < mBatchSize; ++i) - { - if (!mRemainingInventoryItems.size()) - { - mEventTimer.stop(); - break; - } - - current_batch.push_back(mRemainingInventoryItems.back()); - mRemainingInventoryItems.pop_back(); - } - processBatch(current_batch); - - return false; -} - -void LLFloaterLinkReplace::processBatch(LLInventoryModel::item_array_t items) -{ - const LLViewerInventoryItem* target_item = gInventory.getItem(mTargetUUID); - const LLUUID cof_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); - const LLUUID outfit_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); - - for (LLInventoryModel::item_array_t::iterator it = items.begin(); it != items.end(); ++it) - { - LLPointer source_item = *it; - - if (source_item->getParentUUID() != cof_folder_id) - { - bool is_outfit_folder = gInventory.isObjectDescendentOf(source_item->getParentUUID(), outfit_folder_id); - // If either the new or old item in the COF is a wearable, we need to update wearable ordering after the link has been replaced - bool needs_wearable_ordering_update = (is_outfit_folder && source_item->getType() == LLAssetType::AT_CLOTHING) || target_item->getType() == LLAssetType::AT_CLOTHING; - // Other items in the COF need a description update (description of the actual link item must be empty) - bool needs_description_update = is_outfit_folder && target_item->getType() != LLAssetType::AT_CLOTHING; - - LL_DEBUGS() << "is_outfit_folder = " << (is_outfit_folder ? "true" : "false") << LL_NEWLINE - << "needs_wearable_ordering_update = " << (needs_wearable_ordering_update ? "true" : "false") << LL_NEWLINE - << "needs_description_update = " << (needs_description_update ? "true" : "false") << LL_ENDL; - - LLInventoryObject::const_object_list_t obj_array; - obj_array.push_back(LLConstPointer(target_item)); - LLPointer cb = new LLBoostFuncInventoryCallback(boost::bind(&LLFloaterLinkReplace::linkCreatedCallback, - getDerivedHandle(), - source_item->getUUID(), - target_item->getUUID(), - needs_wearable_ordering_update, - needs_description_update, - (is_outfit_folder ? source_item->getParentUUID() : LLUUID::null) )); - link_inventory_array(source_item->getParentUUID(), obj_array, cb); - } - else - { - decreaseOpenItemCount(); - } - } -} - - -////////////////////////////////////////////////////////////////////////////// -// LLInventoryLinkReplaceDropTarget - -static LLDefaultChildRegistry::Register r("inventory_link_replace_drop_target"); - -bool LLInventoryLinkReplaceDropTarget::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - LLInventoryItem* item = (LLInventoryItem*)cargo_data; - - if (cargo_type >= DAD_TEXTURE && cargo_type <= DAD_LINK && - item && item->getActualType() != LLAssetType::AT_LINK_FOLDER && item->getType() != LLAssetType::AT_CATEGORY && - ( - LLAssetType::lookupCanLink(item->getType()) || - (item->getType() == LLAssetType::AT_LINK && !gInventory.getObject(item->getLinkedUUID())) // Broken Link! - )) - { - if (drop) - { - setItem(item); - if (!mDADSignal.empty()) - { - mDADSignal(mItemID); - } - } - else - { - *accept = ACCEPT_YES_SINGLE; - } - } - else - { - *accept = ACCEPT_NO; - } - - return true; -} - -void LLInventoryLinkReplaceDropTarget::setItem(LLInventoryItem* item) -{ - if (item) - { - mItemID = item->getLinkedUUID(); - setText(item->getName()); - } - else - { - mItemID.setNull(); - setText(LLStringExplicit("")); - } -} +/** + * @file llfloaterlinkreplace.cpp + * @brief Allows replacing link targets in inventory links + * @author Ansariel Hiller + * + * $LicenseInfo:firstyear=2017&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2017, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterlinkreplace.h" + +#include "llagent.h" +#include "llappearancemgr.h" +#include "lllineeditor.h" +#include "llnotificationsutil.h" +#include "llnotifications.h" +#include "lltextbox.h" +#include "llviewercontrol.h" + +LLFloaterLinkReplace::LLFloaterLinkReplace(const LLSD& key) + : LLFloater(key), + LLEventTimer(gSavedSettings.getF32("LinkReplaceBatchPauseTime")), + mRemainingItems(0), + mSourceUUID(LLUUID::null), + mTargetUUID(LLUUID::null), + mBatchSize(gSavedSettings.getU32("LinkReplaceBatchSize")) +{ + mEventTimer.stop(); +} + +LLFloaterLinkReplace::~LLFloaterLinkReplace() +{ +} + +bool LLFloaterLinkReplace::postBuild() +{ + mStartBtn = getChild("btn_start"); + mStartBtn->setCommitCallback(boost::bind(&LLFloaterLinkReplace::onStartClicked, this)); + + mRefreshBtn = getChild("btn_refresh"); + mRefreshBtn->setCommitCallback(boost::bind(&LLFloaterLinkReplace::checkEnableStart, this)); + + mSourceEditor = getChild("source_uuid_editor"); + mTargetEditor = getChild("target_uuid_editor"); + + mSourceEditor->setDADCallback(boost::bind(&LLFloaterLinkReplace::onSourceItemDrop, this, _1)); + mTargetEditor->setDADCallback(boost::bind(&LLFloaterLinkReplace::onTargetItemDrop, this, _1)); + + mStatusText = getChild("status_text"); + + return true; +} + +void LLFloaterLinkReplace::onOpen(const LLSD& key) +{ + if (key.asUUID().notNull()) + { + LLUUID item_id = key.asUUID(); + LLViewerInventoryItem* item = gInventory.getItem(item_id); + mSourceEditor->setItem(item); + onSourceItemDrop(item->getLinkedUUID()); + } + else + { + checkEnableStart(); + } +} + +void LLFloaterLinkReplace::onSourceItemDrop(const LLUUID& source_item_id) +{ + mSourceUUID = source_item_id; + checkEnableStart(); +} + +void LLFloaterLinkReplace::onTargetItemDrop(const LLUUID& target_item_id) +{ + mTargetUUID = target_item_id; + checkEnableStart(); +} + +void LLFloaterLinkReplace::updateFoundLinks() +{ + LLInventoryModel::item_array_t items; + LLInventoryModel::cat_array_t cat_array; + LLLinkedItemIDMatches is_linked_item_match(mSourceUUID); + gInventory.collectDescendentsIf(gInventory.getRootFolderID(), + cat_array, + items, + LLInventoryModel::INCLUDE_TRASH, + is_linked_item_match); + mRemainingItems = (U32)items.size(); + + LLStringUtil::format_map_t args; + args["NUM"] = llformat("%d", mRemainingItems); + mStatusText->setText(getString("ItemsFound", args)); +} + +void LLFloaterLinkReplace::checkEnableStart() +{ + if (mSourceUUID.notNull() && mTargetUUID.notNull() && mSourceUUID == mTargetUUID) + { + mStatusText->setText(getString("ItemsIdentical")); + } + else if (mSourceUUID.notNull()) + { + updateFoundLinks(); + } + + mStartBtn->setEnabled(mRemainingItems > 0 && mSourceUUID.notNull() && mTargetUUID.notNull() && mSourceUUID != mTargetUUID); +} + +void LLFloaterLinkReplace::onStartClicked() +{ + LL_INFOS() << "Starting inventory link replace" << LL_ENDL; + + if (mSourceUUID.isNull() || mTargetUUID.isNull()) + { + LL_WARNS() << "Cannot replace. Either source or target UUID is null." << LL_ENDL; + return; + } + + if (mSourceUUID == mTargetUUID) + { + LL_WARNS() << "Cannot replace. Source and target are identical." << LL_ENDL; + return; + } + + const LLUUID& source_item_id = gInventory.getLinkedItemID(mSourceUUID); + LLViewerInventoryItem *source_item = gInventory.getItem(source_item_id); + const LLUUID& target_item_id = gInventory.getLinkedItemID(mTargetUUID); + LLViewerInventoryItem *target_item = gInventory.getItem(target_item_id); + + + LLNotification::Params params("ConfirmReplaceLink"); + params.functor.function(boost::bind(&LLFloaterLinkReplace::onStartClickedResponse, this, _1, _2)); + if (source_item && source_item->isWearableType() && source_item->getWearableType() <= LLWearableType::WT_EYES) + { + if(target_item && target_item->isWearableType() && source_item->getWearableType() == target_item->getWearableType()) + { + LLNotifications::instance().forceResponse(params, 0); + } + else + { + LLSD args; + args["TYPE"] = LLWearableType::getInstance()->getTypeName(source_item->getWearableType()); + params.substitutions(args); + LLNotifications::instance().add(params); + } + } + else + { + LLNotifications::instance().forceResponse(params, 0); + } +} + +void LLFloaterLinkReplace::onStartClickedResponse(const LLSD& notification, const LLSD& response) +{ + + if (LLNotificationsUtil::getSelectedOption(notification, response) == 0) + { + + LLInventoryModel::cat_array_t cat_array; + LLLinkedItemIDMatches is_linked_item_match(mSourceUUID); + gInventory.collectDescendentsIf(gInventory.getRootFolderID(), + cat_array, + mRemainingInventoryItems, + LLInventoryModel::INCLUDE_TRASH, + is_linked_item_match); + LL_INFOS() << "Found " << mRemainingInventoryItems.size() << " inventory links that need to be replaced." << LL_ENDL; + + if (mRemainingInventoryItems.size() > 0) + { + LLViewerInventoryItem* target_item = gInventory.getItem(mTargetUUID); + if (target_item) + { + mRemainingItems = (U32)mRemainingInventoryItems.size(); + + LLStringUtil::format_map_t args; + args["NUM"] = llformat("%d", mRemainingItems); + mStatusText->setText(getString("ItemsRemaining", args)); + + mStartBtn->setEnabled(false); + mRefreshBtn->setEnabled(false); + + mEventTimer.start(); + tick(); + } + else + { + mStatusText->setText(getString("TargetNotFound")); + LL_WARNS() << "Link replace target not found." << LL_ENDL; + } + } + } +} + +// static +void LLFloaterLinkReplace::linkCreatedCallback(LLHandle floater_handle, const LLUUID& old_item_id, const LLUUID& target_item_id, + bool needs_wearable_ordering_update, bool needs_description_update, const LLUUID& outfit_folder_id) +{ + LL_DEBUGS() << "Inventory link replace:" << LL_NEWLINE + << " - old_item_id = " << old_item_id.asString() << LL_NEWLINE + << " - target_item_id = " << target_item_id.asString() << LL_NEWLINE + << " - order update = " << (needs_wearable_ordering_update ? "true" : "false") << LL_NEWLINE + << " - description update = " << (needs_description_update ? "true" : "false") << LL_NEWLINE + << " - outfit_folder_id = " << outfit_folder_id.asString() << LL_ENDL; + + // If we are replacing an object, bodypart or gesture link within an outfit folder, + // we need to change the actual description of the link itself. LLAppearanceMgr *should* + // have created COF links that will be used to save the outfit with an empty description. + // Since link_inventory_array() will set the description of the linked item for the link + // itself, this will lead to a dirty outfit state when the outfit with the replaced + // link is worn. So we have to correct this. + if (needs_description_update && outfit_folder_id.notNull()) + { + LLInventoryModel::item_array_t items; + LLInventoryModel::cat_array_t cats; + LLLinkedItemIDMatches is_target_link(target_item_id); + gInventory.collectDescendentsIf(outfit_folder_id, + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + is_target_link); + + for (LLInventoryModel::item_array_t::iterator it = items.begin(); it != items.end(); ++it) + { + LLPointer item = *it; + + if ((item->getType() == LLAssetType::AT_BODYPART || + item->getType() == LLAssetType::AT_OBJECT || + item->getType() == LLAssetType::AT_GESTURE) + && !item->getActualDescription().empty()) + { + LL_DEBUGS() << "Updating description for " << item->getName() << LL_ENDL; + + LLSD updates; + updates["desc"] = ""; + update_inventory_item(item->getUUID(), updates, LLPointer(NULL)); + } + } + } + + LLUUID outfit_update_folder = LLUUID::null; + if (needs_wearable_ordering_update && outfit_folder_id.notNull()) + { + // If a wearable item was involved in the link replace operation and replaced + // a link in an outfit folder, we need to update the clothing ordering information + // *after* the original link has been removed. LLAppearanceMgr abuses the actual link + // description to store the clothing ordering information it. We will have to update + // the clothing ordering information or the outfit will be in dirty state when worn. + outfit_update_folder = outfit_folder_id; + } + + LLPointer cb = new LLBoostFuncInventoryCallback(boost::bind(&LLFloaterLinkReplace::itemRemovedCallback, floater_handle, outfit_update_folder)); + remove_inventory_object(old_item_id, cb); +} + +// static +void LLFloaterLinkReplace::itemRemovedCallback(LLHandle floater_handle, const LLUUID& outfit_folder_id) +{ + if (outfit_folder_id.notNull()) + { + LLAppearanceMgr::getInstance()->updateClothingOrderingInfo(outfit_folder_id); + } + + if (!floater_handle.isDead()) + { + floater_handle.get()->decreaseOpenItemCount(); + } +} + +void LLFloaterLinkReplace::decreaseOpenItemCount() +{ + mRemainingItems--; + + if (mRemainingItems == 0) + { + mStatusText->setText(getString("ReplaceFinished")); + mStartBtn->setEnabled(true); + mRefreshBtn->setEnabled(true); + mEventTimer.stop(); + LL_INFOS() << "Inventory link replace finished." << LL_ENDL; + } + else + { + LLStringUtil::format_map_t args; + args["NUM"] = llformat("%d", mRemainingItems); + mStatusText->setText(getString("ItemsRemaining", args)); + LL_DEBUGS() << "Inventory link replace: " << mRemainingItems << " links remaining..." << LL_ENDL; + } +} + +bool LLFloaterLinkReplace::tick() +{ + LL_DEBUGS() << "Calling tick - remaining items = " << mRemainingInventoryItems.size() << LL_ENDL; + + LLInventoryModel::item_array_t current_batch; + + for (U32 i = 0; i < mBatchSize; ++i) + { + if (!mRemainingInventoryItems.size()) + { + mEventTimer.stop(); + break; + } + + current_batch.push_back(mRemainingInventoryItems.back()); + mRemainingInventoryItems.pop_back(); + } + processBatch(current_batch); + + return false; +} + +void LLFloaterLinkReplace::processBatch(LLInventoryModel::item_array_t items) +{ + const LLViewerInventoryItem* target_item = gInventory.getItem(mTargetUUID); + const LLUUID cof_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + const LLUUID outfit_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + + for (LLInventoryModel::item_array_t::iterator it = items.begin(); it != items.end(); ++it) + { + LLPointer source_item = *it; + + if (source_item->getParentUUID() != cof_folder_id) + { + bool is_outfit_folder = gInventory.isObjectDescendentOf(source_item->getParentUUID(), outfit_folder_id); + // If either the new or old item in the COF is a wearable, we need to update wearable ordering after the link has been replaced + bool needs_wearable_ordering_update = (is_outfit_folder && source_item->getType() == LLAssetType::AT_CLOTHING) || target_item->getType() == LLAssetType::AT_CLOTHING; + // Other items in the COF need a description update (description of the actual link item must be empty) + bool needs_description_update = is_outfit_folder && target_item->getType() != LLAssetType::AT_CLOTHING; + + LL_DEBUGS() << "is_outfit_folder = " << (is_outfit_folder ? "true" : "false") << LL_NEWLINE + << "needs_wearable_ordering_update = " << (needs_wearable_ordering_update ? "true" : "false") << LL_NEWLINE + << "needs_description_update = " << (needs_description_update ? "true" : "false") << LL_ENDL; + + LLInventoryObject::const_object_list_t obj_array; + obj_array.push_back(LLConstPointer(target_item)); + LLPointer cb = new LLBoostFuncInventoryCallback(boost::bind(&LLFloaterLinkReplace::linkCreatedCallback, + getDerivedHandle(), + source_item->getUUID(), + target_item->getUUID(), + needs_wearable_ordering_update, + needs_description_update, + (is_outfit_folder ? source_item->getParentUUID() : LLUUID::null) )); + link_inventory_array(source_item->getParentUUID(), obj_array, cb); + } + else + { + decreaseOpenItemCount(); + } + } +} + + +////////////////////////////////////////////////////////////////////////////// +// LLInventoryLinkReplaceDropTarget + +static LLDefaultChildRegistry::Register r("inventory_link_replace_drop_target"); + +bool LLInventoryLinkReplaceDropTarget::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + LLInventoryItem* item = (LLInventoryItem*)cargo_data; + + if (cargo_type >= DAD_TEXTURE && cargo_type <= DAD_LINK && + item && item->getActualType() != LLAssetType::AT_LINK_FOLDER && item->getType() != LLAssetType::AT_CATEGORY && + ( + LLAssetType::lookupCanLink(item->getType()) || + (item->getType() == LLAssetType::AT_LINK && !gInventory.getObject(item->getLinkedUUID())) // Broken Link! + )) + { + if (drop) + { + setItem(item); + if (!mDADSignal.empty()) + { + mDADSignal(mItemID); + } + } + else + { + *accept = ACCEPT_YES_SINGLE; + } + } + else + { + *accept = ACCEPT_NO; + } + + return true; +} + +void LLInventoryLinkReplaceDropTarget::setItem(LLInventoryItem* item) +{ + if (item) + { + mItemID = item->getLinkedUUID(); + setText(item->getName()); + } + else + { + mItemID.setNull(); + setText(LLStringExplicit("")); + } +} diff --git a/indra/newview/llfloaterlinkreplace.h b/indra/newview/llfloaterlinkreplace.h index b056b71aab..7f9f0b59e1 100644 --- a/indra/newview/llfloaterlinkreplace.h +++ b/indra/newview/llfloaterlinkreplace.h @@ -1,123 +1,123 @@ -/** - * @file llfloaterlinkreplace.h - * @brief Allows replacing link targets in inventory links - * @author Ansariel Hiller - * - * $LicenseInfo:firstyear=2017&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2017, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_FLOATERLINKREPLACE_H -#define LL_FLOATERLINKREPLACE_H - -#include "llfloater.h" -#include "lleventtimer.h" -#include "lllineeditor.h" -#include "llinventoryfunctions.h" -#include "llviewerinventory.h" - -class LLButton; -class LLTextBox; - -class LLInventoryLinkReplaceDropTarget : public LLLineEditor -{ -public: - struct Params : public LLInitParam::Block - { - Params() - {} - }; - - LLInventoryLinkReplaceDropTarget(const Params& p) - : LLLineEditor(p) {} - ~LLInventoryLinkReplaceDropTarget() {} - - typedef boost::signals2::signal item_dad_callback_t; - boost::signals2::connection setDADCallback(const item_dad_callback_t::slot_type& cb) - { - return mDADSignal.connect(cb); - } - - virtual bool postBuild() - { - setEnabled(false); - return LLLineEditor::postBuild(); - } - - virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg); - - LLUUID getItemID() const { return mItemID; } - void setItem(LLInventoryItem* item); - -private: - LLUUID mItemID; - - item_dad_callback_t mDADSignal; -}; - - -class LLFloaterLinkReplace : public LLFloater, LLEventTimer -{ - LOG_CLASS(LLFloaterLinkReplace); - -public: - LLFloaterLinkReplace(const LLSD& key); - virtual ~LLFloaterLinkReplace(); - - bool postBuild(); - virtual void onOpen(const LLSD& key); - - virtual bool tick(); - -private: - void checkEnableStart(); - void onStartClicked(); - void onStartClickedResponse(const LLSD& notification, const LLSD& response); - void decreaseOpenItemCount(); - void updateFoundLinks(); - void processBatch(LLInventoryModel::item_array_t items); - - static void linkCreatedCallback(LLHandle floater_handle, const LLUUID& old_item_id, const LLUUID& target_item_id, - bool needs_wearable_ordering_update, bool needs_description_update, const LLUUID& outfit_folder_id); - static void itemRemovedCallback(LLHandle floater_handle, const LLUUID& outfit_folder_id); - - void onSourceItemDrop(const LLUUID& source_item_id); - void onTargetItemDrop(const LLUUID& target_item_id); - - LLInventoryLinkReplaceDropTarget* mSourceEditor; - LLInventoryLinkReplaceDropTarget* mTargetEditor; - LLButton* mStartBtn; - LLButton* mRefreshBtn; - LLTextBox* mStatusText; - - LLUUID mSourceUUID; - LLUUID mTargetUUID; - U32 mRemainingItems; - U32 mBatchSize; - - LLInventoryModel::item_array_t mRemainingInventoryItems; -}; - -#endif // LL_FLOATERLINKREPLACE_H +/** + * @file llfloaterlinkreplace.h + * @brief Allows replacing link targets in inventory links + * @author Ansariel Hiller + * + * $LicenseInfo:firstyear=2017&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2017, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_FLOATERLINKREPLACE_H +#define LL_FLOATERLINKREPLACE_H + +#include "llfloater.h" +#include "lleventtimer.h" +#include "lllineeditor.h" +#include "llinventoryfunctions.h" +#include "llviewerinventory.h" + +class LLButton; +class LLTextBox; + +class LLInventoryLinkReplaceDropTarget : public LLLineEditor +{ +public: + struct Params : public LLInitParam::Block + { + Params() + {} + }; + + LLInventoryLinkReplaceDropTarget(const Params& p) + : LLLineEditor(p) {} + ~LLInventoryLinkReplaceDropTarget() {} + + typedef boost::signals2::signal item_dad_callback_t; + boost::signals2::connection setDADCallback(const item_dad_callback_t::slot_type& cb) + { + return mDADSignal.connect(cb); + } + + virtual bool postBuild() + { + setEnabled(false); + return LLLineEditor::postBuild(); + } + + virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + + LLUUID getItemID() const { return mItemID; } + void setItem(LLInventoryItem* item); + +private: + LLUUID mItemID; + + item_dad_callback_t mDADSignal; +}; + + +class LLFloaterLinkReplace : public LLFloater, LLEventTimer +{ + LOG_CLASS(LLFloaterLinkReplace); + +public: + LLFloaterLinkReplace(const LLSD& key); + virtual ~LLFloaterLinkReplace(); + + bool postBuild(); + virtual void onOpen(const LLSD& key); + + virtual bool tick(); + +private: + void checkEnableStart(); + void onStartClicked(); + void onStartClickedResponse(const LLSD& notification, const LLSD& response); + void decreaseOpenItemCount(); + void updateFoundLinks(); + void processBatch(LLInventoryModel::item_array_t items); + + static void linkCreatedCallback(LLHandle floater_handle, const LLUUID& old_item_id, const LLUUID& target_item_id, + bool needs_wearable_ordering_update, bool needs_description_update, const LLUUID& outfit_folder_id); + static void itemRemovedCallback(LLHandle floater_handle, const LLUUID& outfit_folder_id); + + void onSourceItemDrop(const LLUUID& source_item_id); + void onTargetItemDrop(const LLUUID& target_item_id); + + LLInventoryLinkReplaceDropTarget* mSourceEditor; + LLInventoryLinkReplaceDropTarget* mTargetEditor; + LLButton* mStartBtn; + LLButton* mRefreshBtn; + LLTextBox* mStatusText; + + LLUUID mSourceUUID; + LLUUID mTargetUUID; + U32 mRemainingItems; + U32 mBatchSize; + + LLInventoryModel::item_array_t mRemainingInventoryItems; +}; + +#endif // LL_FLOATERLINKREPLACE_H diff --git a/indra/newview/llfloaterloadprefpreset.cpp b/indra/newview/llfloaterloadprefpreset.cpp index a3bdbda408..2396e2659a 100644 --- a/indra/newview/llfloaterloadprefpreset.cpp +++ b/indra/newview/llfloaterloadprefpreset.cpp @@ -1,110 +1,110 @@ -/** - * @file llfloateloadprefpreset.cpp - * @brief Floater to load a graphics / camera preset - * - * $LicenseInfo:firstyear=2015&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2015, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterloadprefpreset.h" - -#include "llbutton.h" -#include "llcombobox.h" -#include "llfloaterpreference.h" -#include "llfloaterreg.h" -#include "llpresetsmanager.h" -#include "llviewercontrol.h" - -LLFloaterLoadPrefPreset::LLFloaterLoadPrefPreset(const LLSD &key) -: LLFloater(key) -{ -} - -// virtual -bool LLFloaterLoadPrefPreset::postBuild() -{ - LLFloaterPreference* preferences = LLFloaterReg::getTypedInstance("preferences"); - if (preferences) - { - preferences->addDependentFloater(this); - } - getChild("ok")->setCommitCallback(boost::bind(&LLFloaterLoadPrefPreset::onBtnOk, this)); - getChild("cancel")->setCommitCallback(boost::bind(&LLFloaterLoadPrefPreset::onBtnCancel, this)); - LLPresetsManager::instance().setPresetListChangeCallback(boost::bind(&LLFloaterLoadPrefPreset::onPresetsListChange, this)); - - return true; -} - -void LLFloaterLoadPrefPreset::onOpen(const LLSD& key) -{ - mSubdirectory = key.asString(); - std::string title_type = std::string("title_") + mSubdirectory; - if (hasString(title_type)) - { - std::string floater_title = getString(title_type); - setTitle(floater_title); - } - else - { - LL_WARNS() << title_type << " not found" << LL_ENDL; - setTitle(title_type); - } - - LLComboBox* combo = getChild("preset_combo"); - - EDefaultOptions option = DEFAULT_TOP; - LLPresetsManager::getInstance()->setPresetNamesInComboBox(mSubdirectory, combo, option); - std::string preset_graphic_active = gSavedSettings.getString("PresetGraphicActive"); - if (!preset_graphic_active.empty()) - { - combo->setSimple(preset_graphic_active); - } -} - -void LLFloaterLoadPrefPreset::onPresetsListChange() -{ - LLComboBox* combo = getChild("preset_combo"); - - EDefaultOptions option = DEFAULT_TOP; - LLPresetsManager::getInstance()->setPresetNamesInComboBox(mSubdirectory, combo, option); - std::string preset_graphic_active = gSavedSettings.getString("PresetGraphicActive"); - if (!preset_graphic_active.empty()) - { - combo->setSimple(preset_graphic_active); - } -} - -void LLFloaterLoadPrefPreset::onBtnCancel() -{ - closeFloater(); -} - -void LLFloaterLoadPrefPreset::onBtnOk() -{ - LLComboBox* combo = getChild("preset_combo"); - std::string name = combo->getSimple(); - - LLPresetsManager::getInstance()->loadPreset(mSubdirectory, name); - - closeFloater(); -} +/** + * @file llfloateloadprefpreset.cpp + * @brief Floater to load a graphics / camera preset + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2015, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterloadprefpreset.h" + +#include "llbutton.h" +#include "llcombobox.h" +#include "llfloaterpreference.h" +#include "llfloaterreg.h" +#include "llpresetsmanager.h" +#include "llviewercontrol.h" + +LLFloaterLoadPrefPreset::LLFloaterLoadPrefPreset(const LLSD &key) +: LLFloater(key) +{ +} + +// virtual +bool LLFloaterLoadPrefPreset::postBuild() +{ + LLFloaterPreference* preferences = LLFloaterReg::getTypedInstance("preferences"); + if (preferences) + { + preferences->addDependentFloater(this); + } + getChild("ok")->setCommitCallback(boost::bind(&LLFloaterLoadPrefPreset::onBtnOk, this)); + getChild("cancel")->setCommitCallback(boost::bind(&LLFloaterLoadPrefPreset::onBtnCancel, this)); + LLPresetsManager::instance().setPresetListChangeCallback(boost::bind(&LLFloaterLoadPrefPreset::onPresetsListChange, this)); + + return true; +} + +void LLFloaterLoadPrefPreset::onOpen(const LLSD& key) +{ + mSubdirectory = key.asString(); + std::string title_type = std::string("title_") + mSubdirectory; + if (hasString(title_type)) + { + std::string floater_title = getString(title_type); + setTitle(floater_title); + } + else + { + LL_WARNS() << title_type << " not found" << LL_ENDL; + setTitle(title_type); + } + + LLComboBox* combo = getChild("preset_combo"); + + EDefaultOptions option = DEFAULT_TOP; + LLPresetsManager::getInstance()->setPresetNamesInComboBox(mSubdirectory, combo, option); + std::string preset_graphic_active = gSavedSettings.getString("PresetGraphicActive"); + if (!preset_graphic_active.empty()) + { + combo->setSimple(preset_graphic_active); + } +} + +void LLFloaterLoadPrefPreset::onPresetsListChange() +{ + LLComboBox* combo = getChild("preset_combo"); + + EDefaultOptions option = DEFAULT_TOP; + LLPresetsManager::getInstance()->setPresetNamesInComboBox(mSubdirectory, combo, option); + std::string preset_graphic_active = gSavedSettings.getString("PresetGraphicActive"); + if (!preset_graphic_active.empty()) + { + combo->setSimple(preset_graphic_active); + } +} + +void LLFloaterLoadPrefPreset::onBtnCancel() +{ + closeFloater(); +} + +void LLFloaterLoadPrefPreset::onBtnOk() +{ + LLComboBox* combo = getChild("preset_combo"); + std::string name = combo->getSimple(); + + LLPresetsManager::getInstance()->loadPreset(mSubdirectory, name); + + closeFloater(); +} diff --git a/indra/newview/llfloaterloadprefpreset.h b/indra/newview/llfloaterloadprefpreset.h index 47d032b0e2..9e7d03f0c4 100644 --- a/indra/newview/llfloaterloadprefpreset.h +++ b/indra/newview/llfloaterloadprefpreset.h @@ -1,53 +1,53 @@ -/** - * @file llfloaterloadprefpreset.h - * @brief Floater to load a graphics / camera preset - - * - * $LicenseInfo:firstyear=2015&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2015, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERLOADPREFPRESET_H -#define LL_LLFLOATERLOADPREFPRESET_H - -#include "llfloater.h" - -class LLComboBox; - -class LLFloaterLoadPrefPreset : public LLFloater -{ - -public: - LLFloaterLoadPrefPreset(const LLSD &key); - - bool postBuild() override; - void onOpen(const LLSD& key) override; - - void onBtnOk(); - void onBtnCancel(); - -private: - void onPresetsListChange(); - - std::string mSubdirectory; -}; - -#endif // LL_LLFLOATERLOADPREFPRESET_H +/** + * @file llfloaterloadprefpreset.h + * @brief Floater to load a graphics / camera preset + + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2015, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERLOADPREFPRESET_H +#define LL_LLFLOATERLOADPREFPRESET_H + +#include "llfloater.h" + +class LLComboBox; + +class LLFloaterLoadPrefPreset : public LLFloater +{ + +public: + LLFloaterLoadPrefPreset(const LLSD &key); + + bool postBuild() override; + void onOpen(const LLSD& key) override; + + void onBtnOk(); + void onBtnCancel(); + +private: + void onPresetsListChange(); + + std::string mSubdirectory; +}; + +#endif // LL_LLFLOATERLOADPREFPRESET_H diff --git a/indra/newview/llfloatermap.cpp b/indra/newview/llfloatermap.cpp index 5dd4f29a8d..83abbb0357 100755 --- a/indra/newview/llfloatermap.cpp +++ b/indra/newview/llfloatermap.cpp @@ -1,254 +1,254 @@ -/** - * @file llfloatermap.cpp - * @brief The "mini-map" or radar in the upper right part of the screen. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -// self include -#include "llfloatermap.h" - -// Library includes -#include "llfloaterreg.h" -#include "llfontgl.h" -#include "llglheaders.h" - -// Viewer includes -#include "llagentcamera.h" -#include "llviewercontrol.h" -#include "llnetmap.h" -#include "lltracker.h" -#include "llviewercamera.h" -#include "lldraghandle.h" -#include "lltextbox.h" -#include "llfloaterworldmap.h" -#include "llagent.h" - -// -// Constants -// - -// The minor cardinal direction labels are hidden if their height is more -// than this proportion of the map. -const F32 MAP_MINOR_DIR_THRESHOLD = 0.035f; - -// -// Member functions -// - -LLFloaterMap::LLFloaterMap(const LLSD& key) - : LLFloater(key), - mTextBoxEast(NULL), - mTextBoxNorth(NULL), - mTextBoxWest(NULL), - mTextBoxSouth(NULL), - mTextBoxSouthEast(NULL), - mTextBoxNorthEast(NULL), - mTextBoxNorthWest(NULL), - mTextBoxSouthWest(NULL), - mMap(NULL) -{ -} - -LLFloaterMap::~LLFloaterMap() -{ -} - -bool LLFloaterMap::postBuild() -{ - mMap = getChild("Net Map"); - mMap->setToolTipMsg(getString("ToolTipMsg")); - mMap->setParcelNameMsg(getString("ParcelNameMsg")); - mMap->setParcelSalePriceMsg(getString("ParcelSalePriceMsg")); - mMap->setParcelSaleAreaMsg(getString("ParcelSaleAreaMsg")); - mMap->setParcelOwnerMsg(getString("ParcelOwnerMsg")); - mMap->setRegionNameMsg(getString("RegionNameMsg")); - mMap->setToolTipHintMsg(getString("ToolTipHintMsg")); - mMap->setAltToolTipHintMsg(getString("AltToolTipHintMsg")); - sendChildToBack(mMap); - - mTextBoxNorth = getChild("floater_map_north"); - mTextBoxEast = getChild("floater_map_east"); - mTextBoxWest = getChild("floater_map_west"); - mTextBoxSouth = getChild("floater_map_south"); - mTextBoxSouthEast = getChild("floater_map_southeast"); - mTextBoxNorthEast = getChild("floater_map_northeast"); - mTextBoxSouthWest = getChild("floater_map_southwest"); - mTextBoxNorthWest = getChild("floater_map_northwest"); - - mTextBoxNorth->reshapeToFitText(); - mTextBoxEast->reshapeToFitText(); - mTextBoxWest->reshapeToFitText(); - mTextBoxSouth->reshapeToFitText(); - mTextBoxSouthEast->reshapeToFitText(); - mTextBoxNorthEast->reshapeToFitText(); - mTextBoxSouthWest->reshapeToFitText(); - mTextBoxNorthWest->reshapeToFitText(); - - updateMinorDirections(); - - // Get the drag handle all the way in back - sendChildToBack(getDragHandle()); - - // keep onscreen - gFloaterView->adjustToFitScreen(this, false); - - return true; -} - -bool LLFloaterMap::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - // If floater is minimized, minimap should be shown on doubleclick (STORM-299) - if (isMinimized()) - { - setMinimized(false); - return true; - } - - LLVector3d pos_global = mMap->viewPosToGlobal(x, y); - - LLTracker::stopTracking(false); - LLFloaterWorldMap* world_map = LLFloaterWorldMap::getInstance(); - if (world_map) - { - world_map->trackLocation(pos_global); - } - - if (gSavedSettings.getBOOL("DoubleClickTeleport")) - { - // If DoubleClickTeleport is on, double clicking the minimap will teleport there - gAgent.teleportViaLocationLookAt(pos_global); - } - else if (gSavedSettings.getBOOL("DoubleClickShowWorldMap")) - { - LLFloaterReg::showInstance("world_map"); - } - return true; -} - -void LLFloaterMap::setDirectionPos(LLTextBox *text_box, F32 rotation) -{ - // Rotation is in radians. - // Rotation of 0 means x = 1, y = 0 on the unit circle. - - F32 map_half_height = (F32) (getRect().getHeight() / 2) - (getHeaderHeight() / 2); - F32 map_half_width = (F32) (getRect().getWidth() / 2); - F32 text_half_height = (F32) (text_box->getRect().getHeight() / 2); - F32 text_half_width = (F32) (text_box->getRect().getWidth() / 2); - F32 extra_padding = (F32) (mTextBoxNorth->getRect().getWidth() / 2); - F32 pos_half_height = map_half_height - text_half_height - extra_padding; - F32 pos_half_width = map_half_width - text_half_width - extra_padding; - - F32 corner_angle = atan2(pos_half_height, pos_half_width); - F32 rotation_mirrored_into_top = abs(fmodf(rotation, F_PI)); - if (rotation < 0) - { - rotation_mirrored_into_top = F_PI - rotation_mirrored_into_top; - } - F32 rotation_mirrored_into_top_right = (F_PI_BY_TWO - abs(rotation_mirrored_into_top - F_PI_BY_TWO)); - bool at_left_right_edge = rotation_mirrored_into_top_right < corner_angle; - - F32 part_x = cos(rotation); - F32 part_y = sin(rotation); - F32 y; - F32 x; - if (at_left_right_edge) - { - x = std::copysign(pos_half_width, part_x); - y = x * part_y / part_x; - } - else - { - y = std::copysign(pos_half_height, part_y); - x = y * part_x / part_y; - } - - text_box->setOrigin(ll_round(map_half_width + x - text_half_width), ll_round(map_half_height + y - text_half_height)); -} - -void LLFloaterMap::updateMinorDirections() -{ - if (mTextBoxNorthEast == NULL) - { - return; - } - - // Hide minor directions if they cover too much of the map - bool show_minors = mTextBoxNorthEast->getRect().getHeight() < MAP_MINOR_DIR_THRESHOLD * - llmin(getRect().getWidth(), getRect().getHeight()); - - mTextBoxNorthEast->setVisible(show_minors); - mTextBoxNorthWest->setVisible(show_minors); - mTextBoxSouthWest->setVisible(show_minors); - mTextBoxSouthEast->setVisible(show_minors); -} - -// virtual -void LLFloaterMap::draw() -{ - F32 rotation = 0; - - static LLUICachedControl rotate_map("MiniMapRotate", true); - if( rotate_map ) - { - // rotate subsequent draws to agent rotation - rotation = atan2( LLViewerCamera::getInstance()->getAtAxis().mV[VX], LLViewerCamera::getInstance()->getAtAxis().mV[VY] ); - } - - setDirectionPos( mTextBoxEast, rotation ); - setDirectionPos( mTextBoxNorth, rotation + F_PI_BY_TWO ); - setDirectionPos( mTextBoxWest, rotation + F_PI ); - setDirectionPos( mTextBoxSouth, rotation + F_PI + F_PI_BY_TWO ); - - setDirectionPos( mTextBoxNorthEast, rotation + F_PI_BY_TWO / 2); - setDirectionPos( mTextBoxNorthWest, rotation + F_PI_BY_TWO + F_PI_BY_TWO / 2); - setDirectionPos( mTextBoxSouthWest, rotation + F_PI + F_PI_BY_TWO / 2); - setDirectionPos( mTextBoxSouthEast, rotation + F_PI + F_PI_BY_TWO + F_PI_BY_TWO / 2); - - // Note: we can't just gAgent.check cameraMouselook() because the transition states are wrong. - if(gAgentCamera.cameraMouselook()) - { - setMouseOpaque(false); - getDragHandle()->setMouseOpaque(false); - } - else - { - setMouseOpaque(true); - getDragHandle()->setMouseOpaque(true); - } - - LLFloater::draw(); -} - -void LLFloaterMap::reshape(S32 width, S32 height, bool called_from_parent) -{ - LLFloater::reshape(width, height, called_from_parent); - - updateMinorDirections(); -} - -LLFloaterMap* LLFloaterMap::getInstance() -{ - return LLFloaterReg::getTypedInstance("mini_map"); -} +/** + * @file llfloatermap.cpp + * @brief The "mini-map" or radar in the upper right part of the screen. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +// self include +#include "llfloatermap.h" + +// Library includes +#include "llfloaterreg.h" +#include "llfontgl.h" +#include "llglheaders.h" + +// Viewer includes +#include "llagentcamera.h" +#include "llviewercontrol.h" +#include "llnetmap.h" +#include "lltracker.h" +#include "llviewercamera.h" +#include "lldraghandle.h" +#include "lltextbox.h" +#include "llfloaterworldmap.h" +#include "llagent.h" + +// +// Constants +// + +// The minor cardinal direction labels are hidden if their height is more +// than this proportion of the map. +const F32 MAP_MINOR_DIR_THRESHOLD = 0.035f; + +// +// Member functions +// + +LLFloaterMap::LLFloaterMap(const LLSD& key) + : LLFloater(key), + mTextBoxEast(NULL), + mTextBoxNorth(NULL), + mTextBoxWest(NULL), + mTextBoxSouth(NULL), + mTextBoxSouthEast(NULL), + mTextBoxNorthEast(NULL), + mTextBoxNorthWest(NULL), + mTextBoxSouthWest(NULL), + mMap(NULL) +{ +} + +LLFloaterMap::~LLFloaterMap() +{ +} + +bool LLFloaterMap::postBuild() +{ + mMap = getChild("Net Map"); + mMap->setToolTipMsg(getString("ToolTipMsg")); + mMap->setParcelNameMsg(getString("ParcelNameMsg")); + mMap->setParcelSalePriceMsg(getString("ParcelSalePriceMsg")); + mMap->setParcelSaleAreaMsg(getString("ParcelSaleAreaMsg")); + mMap->setParcelOwnerMsg(getString("ParcelOwnerMsg")); + mMap->setRegionNameMsg(getString("RegionNameMsg")); + mMap->setToolTipHintMsg(getString("ToolTipHintMsg")); + mMap->setAltToolTipHintMsg(getString("AltToolTipHintMsg")); + sendChildToBack(mMap); + + mTextBoxNorth = getChild("floater_map_north"); + mTextBoxEast = getChild("floater_map_east"); + mTextBoxWest = getChild("floater_map_west"); + mTextBoxSouth = getChild("floater_map_south"); + mTextBoxSouthEast = getChild("floater_map_southeast"); + mTextBoxNorthEast = getChild("floater_map_northeast"); + mTextBoxSouthWest = getChild("floater_map_southwest"); + mTextBoxNorthWest = getChild("floater_map_northwest"); + + mTextBoxNorth->reshapeToFitText(); + mTextBoxEast->reshapeToFitText(); + mTextBoxWest->reshapeToFitText(); + mTextBoxSouth->reshapeToFitText(); + mTextBoxSouthEast->reshapeToFitText(); + mTextBoxNorthEast->reshapeToFitText(); + mTextBoxSouthWest->reshapeToFitText(); + mTextBoxNorthWest->reshapeToFitText(); + + updateMinorDirections(); + + // Get the drag handle all the way in back + sendChildToBack(getDragHandle()); + + // keep onscreen + gFloaterView->adjustToFitScreen(this, false); + + return true; +} + +bool LLFloaterMap::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + // If floater is minimized, minimap should be shown on doubleclick (STORM-299) + if (isMinimized()) + { + setMinimized(false); + return true; + } + + LLVector3d pos_global = mMap->viewPosToGlobal(x, y); + + LLTracker::stopTracking(false); + LLFloaterWorldMap* world_map = LLFloaterWorldMap::getInstance(); + if (world_map) + { + world_map->trackLocation(pos_global); + } + + if (gSavedSettings.getBOOL("DoubleClickTeleport")) + { + // If DoubleClickTeleport is on, double clicking the minimap will teleport there + gAgent.teleportViaLocationLookAt(pos_global); + } + else if (gSavedSettings.getBOOL("DoubleClickShowWorldMap")) + { + LLFloaterReg::showInstance("world_map"); + } + return true; +} + +void LLFloaterMap::setDirectionPos(LLTextBox *text_box, F32 rotation) +{ + // Rotation is in radians. + // Rotation of 0 means x = 1, y = 0 on the unit circle. + + F32 map_half_height = (F32) (getRect().getHeight() / 2) - (getHeaderHeight() / 2); + F32 map_half_width = (F32) (getRect().getWidth() / 2); + F32 text_half_height = (F32) (text_box->getRect().getHeight() / 2); + F32 text_half_width = (F32) (text_box->getRect().getWidth() / 2); + F32 extra_padding = (F32) (mTextBoxNorth->getRect().getWidth() / 2); + F32 pos_half_height = map_half_height - text_half_height - extra_padding; + F32 pos_half_width = map_half_width - text_half_width - extra_padding; + + F32 corner_angle = atan2(pos_half_height, pos_half_width); + F32 rotation_mirrored_into_top = abs(fmodf(rotation, F_PI)); + if (rotation < 0) + { + rotation_mirrored_into_top = F_PI - rotation_mirrored_into_top; + } + F32 rotation_mirrored_into_top_right = (F_PI_BY_TWO - abs(rotation_mirrored_into_top - F_PI_BY_TWO)); + bool at_left_right_edge = rotation_mirrored_into_top_right < corner_angle; + + F32 part_x = cos(rotation); + F32 part_y = sin(rotation); + F32 y; + F32 x; + if (at_left_right_edge) + { + x = std::copysign(pos_half_width, part_x); + y = x * part_y / part_x; + } + else + { + y = std::copysign(pos_half_height, part_y); + x = y * part_x / part_y; + } + + text_box->setOrigin(ll_round(map_half_width + x - text_half_width), ll_round(map_half_height + y - text_half_height)); +} + +void LLFloaterMap::updateMinorDirections() +{ + if (mTextBoxNorthEast == NULL) + { + return; + } + + // Hide minor directions if they cover too much of the map + bool show_minors = mTextBoxNorthEast->getRect().getHeight() < MAP_MINOR_DIR_THRESHOLD * + llmin(getRect().getWidth(), getRect().getHeight()); + + mTextBoxNorthEast->setVisible(show_minors); + mTextBoxNorthWest->setVisible(show_minors); + mTextBoxSouthWest->setVisible(show_minors); + mTextBoxSouthEast->setVisible(show_minors); +} + +// virtual +void LLFloaterMap::draw() +{ + F32 rotation = 0; + + static LLUICachedControl rotate_map("MiniMapRotate", true); + if( rotate_map ) + { + // rotate subsequent draws to agent rotation + rotation = atan2( LLViewerCamera::getInstance()->getAtAxis().mV[VX], LLViewerCamera::getInstance()->getAtAxis().mV[VY] ); + } + + setDirectionPos( mTextBoxEast, rotation ); + setDirectionPos( mTextBoxNorth, rotation + F_PI_BY_TWO ); + setDirectionPos( mTextBoxWest, rotation + F_PI ); + setDirectionPos( mTextBoxSouth, rotation + F_PI + F_PI_BY_TWO ); + + setDirectionPos( mTextBoxNorthEast, rotation + F_PI_BY_TWO / 2); + setDirectionPos( mTextBoxNorthWest, rotation + F_PI_BY_TWO + F_PI_BY_TWO / 2); + setDirectionPos( mTextBoxSouthWest, rotation + F_PI + F_PI_BY_TWO / 2); + setDirectionPos( mTextBoxSouthEast, rotation + F_PI + F_PI_BY_TWO + F_PI_BY_TWO / 2); + + // Note: we can't just gAgent.check cameraMouselook() because the transition states are wrong. + if(gAgentCamera.cameraMouselook()) + { + setMouseOpaque(false); + getDragHandle()->setMouseOpaque(false); + } + else + { + setMouseOpaque(true); + getDragHandle()->setMouseOpaque(true); + } + + LLFloater::draw(); +} + +void LLFloaterMap::reshape(S32 width, S32 height, bool called_from_parent) +{ + LLFloater::reshape(width, height, called_from_parent); + + updateMinorDirections(); +} + +LLFloaterMap* LLFloaterMap::getInstance() +{ + return LLFloaterReg::getTypedInstance("mini_map"); +} diff --git a/indra/newview/llfloatermap.h b/indra/newview/llfloatermap.h index 12ddbd7f16..1955372567 100644 --- a/indra/newview/llfloatermap.h +++ b/indra/newview/llfloatermap.h @@ -1,67 +1,67 @@ -/** - * @file llfloatermap.h - * @brief The "mini-map" or radar in the upper right part of the screen. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERMAP_H -#define LL_LLFLOATERMAP_H - -#include "llfloater.h" - -class LLNetMap; -class LLTextBox; - -// -// Classes -// -class LLFloaterMap : public LLFloater -{ -public: - LLFloaterMap(const LLSD& key); - static LLFloaterMap* getInstance(); - virtual ~LLFloaterMap(); - - bool postBuild() override; - bool handleDoubleClick( S32 x, S32 y, MASK mask ) override; - void reshape(S32 width, S32 height, bool called_from_parent = true) override; - void draw() override; - -private: - void setDirectionPos( LLTextBox* text_box, F32 rotation ); - void updateMinorDirections(); - - LLTextBox* mTextBoxEast; - LLTextBox* mTextBoxNorth; - LLTextBox* mTextBoxWest; - LLTextBox* mTextBoxSouth; - - LLTextBox* mTextBoxSouthEast; - LLTextBox* mTextBoxNorthEast; - LLTextBox* mTextBoxNorthWest; - LLTextBox* mTextBoxSouthWest; - - LLNetMap* mMap; -}; - -#endif // LL_LLFLOATERMAP_H +/** + * @file llfloatermap.h + * @brief The "mini-map" or radar in the upper right part of the screen. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERMAP_H +#define LL_LLFLOATERMAP_H + +#include "llfloater.h" + +class LLNetMap; +class LLTextBox; + +// +// Classes +// +class LLFloaterMap : public LLFloater +{ +public: + LLFloaterMap(const LLSD& key); + static LLFloaterMap* getInstance(); + virtual ~LLFloaterMap(); + + bool postBuild() override; + bool handleDoubleClick( S32 x, S32 y, MASK mask ) override; + void reshape(S32 width, S32 height, bool called_from_parent = true) override; + void draw() override; + +private: + void setDirectionPos( LLTextBox* text_box, F32 rotation ); + void updateMinorDirections(); + + LLTextBox* mTextBoxEast; + LLTextBox* mTextBoxNorth; + LLTextBox* mTextBoxWest; + LLTextBox* mTextBoxSouth; + + LLTextBox* mTextBoxSouthEast; + LLTextBox* mTextBoxNorthEast; + LLTextBox* mTextBoxNorthWest; + LLTextBox* mTextBoxSouthWest; + + LLNetMap* mMap; +}; + +#endif // LL_LLFLOATERMAP_H diff --git a/indra/newview/llfloatermarketplacelistings.cpp b/indra/newview/llfloatermarketplacelistings.cpp index 4a83d839fe..b75012c44f 100644 --- a/indra/newview/llfloatermarketplacelistings.cpp +++ b/indra/newview/llfloatermarketplacelistings.cpp @@ -1,1066 +1,1066 @@ -/** - * @file llfloatermarketplacelistings.cpp - * @brief Implementation of the marketplace listings floater and panels - * @author merov@lindenlab.com - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloatermarketplacelistings.h" - -#include "llfloaterreg.h" -#include "llfiltereditor.h" -#include "llfolderview.h" -#include "llinventorybridge.h" -#include "llinventorymodelbackgroundfetch.h" -#include "llinventoryobserver.h" -#include "llinventoryfunctions.h" -#include "llmarketplacefunctions.h" -#include "llnotificationhandler.h" -#include "llnotificationmanager.h" -#include "llnotificationsutil.h" -#include "llsidepaneliteminfo.h" -#include "llsidepaneltaskinfo.h" -#include "lltabcontainer.h" -#include "lltextbox.h" -#include "lltrans.h" -#include "llviewerwindow.h" - -///---------------------------------------------------------------------------- -/// LLPanelMarketplaceListings -///---------------------------------------------------------------------------- - -static LLPanelInjector t_panel_status("llpanelmarketplacelistings"); - -LLPanelMarketplaceListings::LLPanelMarketplaceListings() -: mRootFolder(NULL) -, mSortOrder(LLInventoryFilter::SO_FOLDERS_BY_NAME) -, mFilterListingFoldersOnly(false) -{ - mCommitCallbackRegistrar.add("Marketplace.ViewSort.Action", boost::bind(&LLPanelMarketplaceListings::onViewSortMenuItemClicked, this, _2)); - mEnableCallbackRegistrar.add("Marketplace.ViewSort.CheckItem", boost::bind(&LLPanelMarketplaceListings::onViewSortMenuItemCheck, this, _2)); -} - -bool LLPanelMarketplaceListings::postBuild() -{ - childSetAction("add_btn", boost::bind(&LLPanelMarketplaceListings::onAddButtonClicked, this)); - childSetAction("audit_btn", boost::bind(&LLPanelMarketplaceListings::onAuditButtonClicked, this)); - - mFilterEditor = getChild("filter_editor"); - mFilterEditor->setCommitCallback(boost::bind(&LLPanelMarketplaceListings::onFilterEdit, this, _2)); - - mAuditBtn = getChild("audit_btn"); - mAuditBtn->setEnabled(false); - - return LLPanel::postBuild(); -} - -bool LLPanelMarketplaceListings::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - LLView * handled_view = childrenHandleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); - bool handled = (handled_view != NULL); - // Special case the drop zone - if (handled && (handled_view->getName() == "marketplace_drop_zone")) - { - LLFolderView* root_folder = getRootFolder(); - handled = root_folder->handleDragAndDropToThisFolder(mask, drop, cargo_type, cargo_data, accept, tooltip_msg); - } - return handled; -} - -void LLPanelMarketplaceListings::buildAllPanels() -{ - // Build the All panel first - LLInventoryPanel* panel_all_items; - panel_all_items = buildInventoryPanel("All Items", "panel_marketplace_listings_inventory.xml"); - panel_all_items->getFilter().setEmptyLookupMessage("MarketplaceNoMatchingItems"); - panel_all_items->getFilter().markDefault(); - - // Build the other panels - LLInventoryPanel* panel; - panel = buildInventoryPanel("Active Items", "panel_marketplace_listings_listed.xml"); - panel->getFilter().setFilterMarketplaceActiveFolders(); - panel->getFilter().setEmptyLookupMessage("MarketplaceNoMatchingItems"); - panel->getFilter().setDefaultEmptyLookupMessage("MarketplaceNoListing"); - panel->getFilter().markDefault(); - panel = buildInventoryPanel("Inactive Items", "panel_marketplace_listings_unlisted.xml"); - panel->getFilter().setFilterMarketplaceInactiveFolders(); - panel->getFilter().setEmptyLookupMessage("MarketplaceNoMatchingItems"); - panel->getFilter().setDefaultEmptyLookupMessage("MarketplaceNoListing"); - panel->getFilter().markDefault(); - panel = buildInventoryPanel("Unassociated Items", "panel_marketplace_listings_unassociated.xml"); - panel->getFilter().setFilterMarketplaceUnassociatedFolders(); - panel->getFilter().setEmptyLookupMessage("MarketplaceNoMatchingItems"); - panel->getFilter().setDefaultEmptyLookupMessage("MarketplaceNoListing"); - panel->getFilter().markDefault(); - - // Set the tab panel - LLTabContainer* tabs_panel = getChild("marketplace_filter_tabs"); - tabs_panel->setCommitCallback(boost::bind(&LLPanelMarketplaceListings::onTabChange, this)); - tabs_panel->selectTabPanel(panel_all_items); // All panel selected by default - mRootFolder = panel_all_items->getRootFolder(); // Keep the root of the all panel - - // Set the default sort order - setSortOrder(gSavedSettings.getU32("MarketplaceListingsSortOrder")); -} - -LLInventoryPanel* LLPanelMarketplaceListings::buildInventoryPanel(const std::string& childname, const std::string& filename) -{ - LLTabContainer* tabs_panel = getChild("marketplace_filter_tabs"); - LLInventoryPanel* panel = LLUICtrlFactory::createFromFile(filename, tabs_panel, LLInventoryPanel::child_registry_t::instance()); - llassert(panel != NULL); - - // Set sort order and callbacks - panel = getChild(childname); - panel->getFolderViewModel()->setSorter(LLInventoryFilter::SO_FOLDERS_BY_NAME); - panel->setSelectCallback(boost::bind(&LLPanelMarketplaceListings::onSelectionChange, this, panel, _1, _2)); - - return panel; -} - -void LLPanelMarketplaceListings::setSortOrder(U32 sort_order) -{ - mSortOrder = sort_order; - gSavedSettings.setU32("MarketplaceListingsSortOrder", sort_order); - - // Set each panel with that sort order - LLTabContainer* tabs_panel = getChild("marketplace_filter_tabs"); - LLInventoryPanel* panel = (LLInventoryPanel*)tabs_panel->getPanelByName("All Items"); - panel->setSortOrder(mSortOrder); - panel = (LLInventoryPanel*)tabs_panel->getPanelByName("Active Items"); - panel->setSortOrder(mSortOrder); - panel = (LLInventoryPanel*)tabs_panel->getPanelByName("Inactive Items"); - panel->setSortOrder(mSortOrder); - panel = (LLInventoryPanel*)tabs_panel->getPanelByName("Unassociated Items"); - panel->setSortOrder(mSortOrder); -} - -void LLPanelMarketplaceListings::onFilterEdit(const std::string& search_string) -{ - // Find active panel - LLInventoryPanel* panel = (LLInventoryPanel*)getChild("marketplace_filter_tabs")->getCurrentPanel(); - if (panel) - { - // Save filter string (needed when switching tabs) - mFilterSubString = search_string; - // Set filter string on active panel - panel->setFilterSubString(mFilterSubString); - } -} - -void LLPanelMarketplaceListings::draw() -{ - if (LLMarketplaceData::instance().checkDirtyCount()) - { - update_all_marketplace_count(); - } - - // Get the audit button enabled only after the whole inventory is fetched - if (!mAuditBtn->getEnabled()) - { - LLInventoryModelBackgroundFetch* inst = LLInventoryModelBackgroundFetch::getInstance(); - mAuditBtn->setEnabled(inst->isEverythingFetched() && !inst->folderFetchActive()); - } - - LLPanel::draw(); -} - -void LLPanelMarketplaceListings::onSelectionChange(LLInventoryPanel *panel, const std::deque& items, bool user_action) -{ - panel->onSelectionChange(items, user_action); -} - -bool LLPanelMarketplaceListings::allowDropOnRoot() -{ - LLInventoryPanel* panel = (LLInventoryPanel*)getChild("marketplace_filter_tabs")->getCurrentPanel(); - return (panel ? panel->getAllowDropOnRoot() : false); -} - -void LLPanelMarketplaceListings::onTabChange() -{ - // Find active panel - LLInventoryPanel* panel = (LLInventoryPanel*)getChild("marketplace_filter_tabs")->getCurrentPanel(); - if (panel) - { - // If the panel doesn't allow drop on root, it doesn't allow the creation of new folder on root either - LLButton* add_btn = getChild("add_btn"); - add_btn->setEnabled(panel->getAllowDropOnRoot()); - - // Set filter string on active panel - panel->setFilterSubString(mFilterSubString); - - // Show/hide the drop zone and resize the inventory tabs panel accordingly - LLPanel* drop_zone = (LLPanel*)getChild("marketplace_drop_zone"); - bool drop_zone_visible = drop_zone->getVisible(); - if (drop_zone_visible != panel->getAllowDropOnRoot()) - { - LLPanel* tabs = (LLPanel*)getChild("tab_container_panel"); - S32 delta_height = drop_zone->getRect().getHeight(); - delta_height = (drop_zone_visible ? delta_height : -delta_height); - tabs->reshape(tabs->getRect().getWidth(),tabs->getRect().getHeight() + delta_height); - tabs->translate(0,-delta_height); - } - drop_zone->setVisible(panel->getAllowDropOnRoot()); - } -} - -void LLPanelMarketplaceListings::onAddButtonClicked() -{ - LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - llassert(marketplacelistings_id.notNull()); - LLFolderType::EType preferred_type = LLFolderType::lookup("category"); - LLHandle handle = getHandle(); - gInventory.createNewCategory( - marketplacelistings_id, - preferred_type, - LLStringUtil::null, - [handle](const LLUUID &new_cat_id) - { - // Find active panel - LLPanel *marketplace_panel = handle.get(); - if (!marketplace_panel) - { - return; - } - LLInventoryPanel* panel = (LLInventoryPanel*)marketplace_panel->getChild("marketplace_filter_tabs")->getCurrentPanel(); - if (panel) - { - gInventory.notifyObservers(); - panel->setSelectionByID(new_cat_id, true); - panel->getRootFolder()->setNeedsAutoRename(true); - } - } - ); -} - -void LLPanelMarketplaceListings::onAuditButtonClicked() -{ - LLSD data(LLSD::emptyMap()); - LLFloaterReg::showInstance("marketplace_validation", data); -} - -void LLPanelMarketplaceListings::onViewSortMenuItemClicked(const LLSD& userdata) -{ - std::string chosen_item = userdata.asString(); - - // Sort options - if ((chosen_item == "sort_by_stock_amount") || (chosen_item == "sort_by_name") || (chosen_item == "sort_by_recent")) - { - // We're making sort options exclusive, default is SO_FOLDERS_BY_NAME - if (chosen_item == "sort_by_stock_amount") - { - setSortOrder(LLInventoryFilter::SO_FOLDERS_BY_WEIGHT); - } - else if (chosen_item == "sort_by_name") - { - setSortOrder(LLInventoryFilter::SO_FOLDERS_BY_NAME); - } - else if (chosen_item == "sort_by_recent") - { - setSortOrder(LLInventoryFilter::SO_DATE); - } - } - // Filter option - else if (chosen_item == "show_only_listing_folders") - { - mFilterListingFoldersOnly = !mFilterListingFoldersOnly; - // Set each panel with that filter flag - LLTabContainer* tabs_panel = getChild("marketplace_filter_tabs"); - LLInventoryPanel* panel = (LLInventoryPanel*)tabs_panel->getPanelByName("All Items"); - panel->getFilter().setFilterMarketplaceListingFolders(mFilterListingFoldersOnly); - panel = (LLInventoryPanel*)tabs_panel->getPanelByName("Active Items"); - panel->getFilter().setFilterMarketplaceListingFolders(mFilterListingFoldersOnly); - panel = (LLInventoryPanel*)tabs_panel->getPanelByName("Inactive Items"); - panel->getFilter().setFilterMarketplaceListingFolders(mFilterListingFoldersOnly); - panel = (LLInventoryPanel*)tabs_panel->getPanelByName("Unassociated Items"); - panel->getFilter().setFilterMarketplaceListingFolders(mFilterListingFoldersOnly); - } -} - -bool LLPanelMarketplaceListings::onViewSortMenuItemCheck(const LLSD& userdata) -{ - std::string chosen_item = userdata.asString(); - - if ((chosen_item == "sort_by_stock_amount") || (chosen_item == "sort_by_name") || (chosen_item == "sort_by_recent")) - { - if (chosen_item == "sort_by_stock_amount") - { - return (mSortOrder & LLInventoryFilter::SO_FOLDERS_BY_WEIGHT); - } - else if (chosen_item == "sort_by_name") - { - return (mSortOrder & LLInventoryFilter::SO_FOLDERS_BY_NAME); - } - else if (chosen_item == "sort_by_recent") - { - return (mSortOrder & LLInventoryFilter::SO_DATE); - } - } - else if (chosen_item == "show_only_listing_folders") - { - return mFilterListingFoldersOnly; - } - return false; -} - -///---------------------------------------------------------------------------- -/// LLMarketplaceListingsAddedObserver helper class -///---------------------------------------------------------------------------- - -class LLMarketplaceListingsAddedObserver : public LLInventoryCategoryAddedObserver -{ -public: - LLMarketplaceListingsAddedObserver(LLFloaterMarketplaceListings * marketplace_listings_floater) - : LLInventoryCategoryAddedObserver() - , mMarketplaceListingsFloater(marketplace_listings_floater) - { - } - - void done() - { - for (cat_vec_t::iterator it = mAddedCategories.begin(); it != mAddedCategories.end(); ++it) - { - LLViewerInventoryCategory* added_category = *it; - - LLFolderType::EType added_category_type = added_category->getPreferredType(); - - if (added_category_type == LLFolderType::FT_MARKETPLACE_LISTINGS) - { - mMarketplaceListingsFloater->initializeMarketPlace(); - } - } - } - -private: - LLFloaterMarketplaceListings * mMarketplaceListingsFloater; -}; - -///---------------------------------------------------------------------------- -/// LLFloaterMarketplaceListings -///---------------------------------------------------------------------------- - -LLFloaterMarketplaceListings::LLFloaterMarketplaceListings(const LLSD& key) -: LLFloater(key) -, mCategoriesObserver(NULL) -, mCategoryAddedObserver(NULL) -, mRootFolderId(LLUUID::null) -, mInventoryStatus(NULL) -, mInventoryInitializationInProgress(NULL) -, mInventoryPlaceholder(NULL) -, mInventoryText(NULL) -, mInventoryTitle(NULL) -, mPanelListings(NULL) -, mPanelListingsSet(false) -, mRootFolderCreating(false) -{ -} - -LLFloaterMarketplaceListings::~LLFloaterMarketplaceListings() -{ - if (mCategoriesObserver && gInventory.containsObserver(mCategoriesObserver)) - { - gInventory.removeObserver(mCategoriesObserver); - } - delete mCategoriesObserver; - - if (mCategoryAddedObserver && gInventory.containsObserver(mCategoryAddedObserver)) - { - gInventory.removeObserver(mCategoryAddedObserver); - } - delete mCategoryAddedObserver; -} - -bool LLFloaterMarketplaceListings::postBuild() -{ - mInventoryStatus = getChild("marketplace_status"); - mInventoryInitializationInProgress = getChild("initialization_progress_indicator"); - mInventoryPlaceholder = getChild("marketplace_listings_inventory_placeholder_panel"); - mInventoryText = mInventoryPlaceholder->getChild("marketplace_listings_inventory_placeholder_text"); - mInventoryTitle = mInventoryPlaceholder->getChild("marketplace_listings_inventory_placeholder_title"); - - mPanelListings = static_cast(getChild("panel_marketplace_listing")); - - LLFocusableElement::setFocusReceivedCallback(boost::bind(&LLFloaterMarketplaceListings::onFocusReceived, this)); - - // Observe category creation to catch marketplace listings creation (moot if already existing) - mCategoryAddedObserver = new LLMarketplaceListingsAddedObserver(this); - gInventory.addObserver(mCategoryAddedObserver); - - - // Fetch aggressively so we can interact with listings as soon as possible - if (!fetchContents()) - { - const LLUUID& marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - LLInventoryModelBackgroundFetch::instance().start(marketplacelistings_id, true); - } - - - return true; -} - -void LLFloaterMarketplaceListings::onClose(bool app_quitting) -{ -} - -void LLFloaterMarketplaceListings::onOpen(const LLSD& key) -{ - // - // Initialize the Market Place or go update the marketplace listings - // - if (LLMarketplaceData::instance().getSLMStatus() <= MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE) - { - initializeMarketPlace(); - } - else - { - updateView(); - } -} - -void LLFloaterMarketplaceListings::onFocusReceived() -{ - updateView(); -} - -bool LLFloaterMarketplaceListings::fetchContents() -{ - if (mRootFolderId.notNull() && - (LLMarketplaceData::instance().getSLMDataFetched() != MarketplaceFetchCodes::MARKET_FETCH_LOADING) && - (LLMarketplaceData::instance().getSLMDataFetched() != MarketplaceFetchCodes::MARKET_FETCH_DONE)) - { - LLMarketplaceData::instance().setDataFetchedSignal(boost::bind(&LLFloaterMarketplaceListings::updateView, this)); - LLMarketplaceData::instance().setSLMDataFetched(MarketplaceFetchCodes::MARKET_FETCH_LOADING); - LLInventoryModelBackgroundFetch::instance().start(mRootFolderId, true); - LLMarketplaceData::instance().getSLMListings(); - return true; - } - return false; -} - -void LLFloaterMarketplaceListings::setRootFolder() -{ - if ((LLMarketplaceData::instance().getSLMStatus() != MarketplaceStatusCodes::MARKET_PLACE_MERCHANT) && - (LLMarketplaceData::instance().getSLMStatus() != MarketplaceStatusCodes::MARKET_PLACE_MIGRATED_MERCHANT)) - { - // If we are *not* a merchant or we have no market place connection established yet, do nothing - return; - } - if (!gInventory.isInventoryUsable()) - { - return; - } - - LLFolderType::EType preferred_type = LLFolderType::FT_MARKETPLACE_LISTINGS; - // We are a merchant. Get the Marketplace listings folder, create it if needs be. - LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(preferred_type); - - if (marketplacelistings_id.isNull()) - { - if (!mRootFolderCreating) - { - mRootFolderCreating = true; - gInventory.createNewCategory( - gInventory.getRootFolderID(), - preferred_type, - LLStringUtil::null, - [](const LLUUID &new_cat_id) - { - LLFloaterMarketplaceListings* marketplace = LLFloaterReg::findTypedInstance("marketplace_listings"); - if (marketplace) - { - if (new_cat_id.notNull()) - { - // will call setRootFolder again - marketplace->updateView(); - } - // don't update in case of failure, createNewCategory can return - // immediately if cap is missing and will cause a loop - else - { - // unblock - marketplace->mRootFolderCreating = false; - LL_WARNS("SLM") << "Inventory warning: Failed to create marketplace listings folder for a merchant" << LL_ENDL; - } - } - } - ); - } - return; - } - - mRootFolderCreating = false; - - // No longer need to observe new category creation - if (mCategoryAddedObserver && gInventory.containsObserver(mCategoryAddedObserver)) - { - gInventory.removeObserver(mCategoryAddedObserver); - delete mCategoryAddedObserver; - mCategoryAddedObserver = NULL; - } - llassert(!mCategoryAddedObserver); - - if (marketplacelistings_id == mRootFolderId) - { - LL_WARNS("SLM") << "Inventory warning: Marketplace listings folder already set" << LL_ENDL; - return; - } - - mRootFolderId = marketplacelistings_id; -} - -void LLFloaterMarketplaceListings::setPanels() -{ - if (mRootFolderId.isNull()) - { - return; - } - - // Consolidate Marketplace listings - // We shouldn't have to do that but with a client/server system relying on a "well known folder" convention, - // things get messy and conventions get broken down eventually - gInventory.consolidateForType(mRootFolderId, LLFolderType::FT_MARKETPLACE_LISTINGS); - - // Now that we do have a non NULL root, we can build the inventory panels - mPanelListings->buildAllPanels(); - - // Create observer for marketplace listings modifications - if (!mCategoriesObserver) - { - mCategoriesObserver = new LLInventoryCategoriesObserver(); - llassert(mCategoriesObserver); - gInventory.addObserver(mCategoriesObserver); - mCategoriesObserver->addCategory(mRootFolderId, boost::bind(&LLFloaterMarketplaceListings::onChanged, this)); - } - - // Get the content of the marketplace listings folder - fetchContents(); - - // Flag that this is done - mPanelListingsSet = true; -} - -void LLFloaterMarketplaceListings::initializeMarketPlace() -{ - LLMarketplaceData::instance().initializeSLM(boost::bind(&LLFloaterMarketplaceListings::updateView, this)); -} - -S32 LLFloaterMarketplaceListings::getFolderCount() -{ - if (mPanelListings && mRootFolderId.notNull()) - { - LLInventoryModel::cat_array_t * cats; - LLInventoryModel::item_array_t * items; - gInventory.getDirectDescendentsOf(mRootFolderId, cats, items); - - return (cats->size() + items->size()); - } - else - { - return 0; - } -} - -void LLFloaterMarketplaceListings::setStatusString(const std::string& statusString) -{ - mInventoryStatus->setText(statusString); -} - -void LLFloaterMarketplaceListings::updateView() -{ - U32 mkt_status = LLMarketplaceData::instance().getSLMStatus(); - bool is_merchant = (mkt_status == MarketplaceStatusCodes::MARKET_PLACE_MERCHANT) || (mkt_status == MarketplaceStatusCodes::MARKET_PLACE_MIGRATED_MERCHANT); - U32 data_fetched = LLMarketplaceData::instance().getSLMDataFetched(); - - // Get or create the root folder if we are a merchant and it hasn't been done already - if (mRootFolderId.isNull() && is_merchant) - { - setRootFolder(); - } - if (mRootFolderCreating) - { - // waiting for callback - return; - } - - // Update the bottom initializing status and progress dial if we are initializing or if we're a merchant and still loading - if ((mkt_status <= MarketplaceStatusCodes::MARKET_PLACE_INITIALIZING) || (is_merchant && (data_fetched <= MarketplaceFetchCodes::MARKET_FETCH_LOADING)) ) - { - // Just show the loading indicator in that case and fetch the data (fetch will be skipped if it's already loading) - mInventoryInitializationInProgress->setVisible(true); - mPanelListings->setVisible(false); - fetchContents(); - return; - } - else - { - mInventoryInitializationInProgress->setVisible(false); - } - - // Update the middle portion : tabs or messages - if (getFolderCount() > 0) - { - if (!mPanelListingsSet) - { - // We need to rebuild the tabs cleanly the first time we make them visible - setPanels(); - } - mPanelListings->setVisible(true); - mInventoryPlaceholder->setVisible(false); - } - else - { - mPanelListings->setVisible(false); - mInventoryPlaceholder->setVisible(true); - - std::string text; - std::string title; - std::string tooltip; - - const LLSD& subs = LLMarketplaceData::getMarketplaceStringSubstitutions(); - - // Update the top message or flip to the tabs and folders view - // *TODO : check those messages and create better appropriate ones in strings.xml - if (mkt_status == MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE) - { - std::string reason = LLMarketplaceData::instance().getSLMConnectionfailureReason(); - if (reason.empty()) - { - text = LLTrans::getString("InventoryMarketplaceConnectionError"); - } - else - { - LLSD args; - args["[REASON]"] = reason; - text = LLTrans::getString("InventoryMarketplaceConnectionErrorReason", args); - } - - title = LLTrans::getString("InventoryOutboxErrorTitle"); - tooltip = LLTrans::getString("InventoryOutboxErrorTooltip"); - LL_WARNS() << "Marketplace status code: " << mkt_status << LL_ENDL; - } - else if (mRootFolderId.notNull()) - { - // "Marketplace listings is empty!" message strings - text = LLTrans::getString("InventoryMarketplaceListingsNoItems", subs); - title = LLTrans::getString("InventoryMarketplaceListingsNoItemsTitle"); - tooltip = LLTrans::getString("InventoryMarketplaceListingsNoItemsTooltip"); - } - else if (mkt_status <= MarketplaceStatusCodes::MARKET_PLACE_INITIALIZING) - { - // "Initializing!" message strings - text = LLTrans::getString("InventoryOutboxInitializing", subs); - title = LLTrans::getString("InventoryOutboxInitializingTitle"); - tooltip = LLTrans::getString("InventoryOutboxInitializingTooltip"); - } - else if (mkt_status == MarketplaceStatusCodes::MARKET_PLACE_NOT_MERCHANT) - { - // "Not a merchant!" message strings - text = LLTrans::getString("InventoryOutboxNotMerchant", subs); - title = LLTrans::getString("InventoryOutboxNotMerchantTitle"); - tooltip = LLTrans::getString("InventoryOutboxNotMerchantTooltip"); - } - else - { - // "Errors!" message strings - text = LLTrans::getString("InventoryMarketplaceError", subs); - title = LLTrans::getString("InventoryOutboxErrorTitle"); - tooltip = LLTrans::getString("InventoryOutboxErrorTooltip"); - LL_WARNS() << "Marketplace status code: " << mkt_status << LL_ENDL; - } - - mInventoryText->setValue(text); - mInventoryTitle->setValue(title); - mInventoryPlaceholder->getParent()->setToolTip(tooltip); - } -} - -bool LLFloaterMarketplaceListings::isAccepted(EAcceptance accept) -{ - return (accept >= ACCEPT_YES_COPY_SINGLE); -} - -bool LLFloaterMarketplaceListings::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - // If there's no panel to accept drops or no existing marketplace listings folder, we refuse all drop - if (!mPanelListings || mRootFolderId.isNull()) - { - return false; - } - - tooltip_msg = ""; - - // Pass to the children - LLView * handled_view = childrenHandleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); - bool handled = (handled_view != NULL); - - // If no one handled it or it was not accepted and we drop on an empty panel, we try to accept it at the floater level - // as if it was dropped on the marketplace listings root folder - if ((!handled || !isAccepted(*accept)) && !mPanelListings->getVisible() && mRootFolderId.notNull()) - { - if (!mPanelListingsSet) - { - setPanels(); - } - LLFolderView* root_folder = mPanelListings->getRootFolder(); - handled = root_folder->handleDragAndDropToThisFolder(mask, drop, cargo_type, cargo_data, accept, tooltip_msg); - } - - return handled; -} - -bool LLFloaterMarketplaceListings::handleHover(S32 x, S32 y, MASK mask) -{ - return LLFloater::handleHover(x, y, mask); -} - -void LLFloaterMarketplaceListings::onMouseLeave(S32 x, S32 y, MASK mask) -{ - LLFloater::onMouseLeave(x, y, mask); -} - -void LLFloaterMarketplaceListings::onChanged() -{ - LLViewerInventoryCategory* category = gInventory.getCategory(mRootFolderId); - if (mRootFolderId.notNull() && category) - { - updateView(); - } - else - { - // Invalidate the marketplace listings data - mRootFolderId.setNull(); - } -} - -//----------------------------------------------------------------------------- -// LLFloaterAssociateListing -//----------------------------------------------------------------------------- - -// Tell if a listing has one only version folder -bool hasUniqueVersionFolder(const LLUUID& folder_id) -{ - LLInventoryModel::cat_array_t* categories; - LLInventoryModel::item_array_t* items; - gInventory.getDirectDescendentsOf(folder_id, categories, items); - return (categories->size() == 1); -} - -LLFloaterAssociateListing::LLFloaterAssociateListing(const LLSD& key) -: LLFloater(key) -, mUUID() -{ -} - -LLFloaterAssociateListing::~LLFloaterAssociateListing() -{ - gFocusMgr.releaseFocusIfNeeded( this ); -} - -bool LLFloaterAssociateListing::postBuild() -{ - getChild("OK")->setCommitCallback(boost::bind(&LLFloaterAssociateListing::apply, this, true)); - getChild("Cancel")->setCommitCallback(boost::bind(&LLFloaterAssociateListing::cancel, this)); - getChild("listing_id")->setPrevalidate(&LLTextValidate::validateNonNegativeS32); - center(); - - return LLFloater::postBuild(); -} - -bool LLFloaterAssociateListing::handleKeyHere(KEY key, MASK mask) -{ - if (key == KEY_RETURN && mask == MASK_NONE) - { - apply(); - return true; - } - else if (key == KEY_ESCAPE && mask == MASK_NONE) - { - cancel(); - return true; - } - - return LLFloater::handleKeyHere(key, mask); -} - -// static -LLFloaterAssociateListing* LLFloaterAssociateListing::show(const LLUUID& folder_id) -{ - LLFloaterAssociateListing* floater = LLFloaterReg::showTypedInstance("associate_listing"); - - floater->mUUID = folder_id; - - return floater; -} - -// Callback for apply if DAMA required... -void LLFloaterAssociateListing::callback_apply(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) // YES - { - apply(false); - } -} - -void LLFloaterAssociateListing::apply(bool user_confirm) -{ - if (mUUID.notNull()) - { - S32 id = (S32)getChild("listing_id")->getValue().asInteger(); - if (id > 0) - { - // Check if the id exists in the merchant SLM DB: note that this record might exist in the LLMarketplaceData - // structure even if unseen in the UI, for instance, if its listing_uuid doesn't exist in the merchant inventory - LLUUID listing_uuid = LLMarketplaceData::instance().getListingFolder(id); - if (listing_uuid.notNull() && user_confirm && LLMarketplaceData::instance().getActivationState(listing_uuid) && !hasUniqueVersionFolder(mUUID)) - { - // Look for user confirmation before unlisting - LLNotificationsUtil::add("ConfirmMerchantUnlist", LLSD(), LLSD(), boost::bind(&LLFloaterAssociateListing::callback_apply, this, _1, _2)); - return; - } - // Associate the id with the user chosen folder - LLMarketplaceData::instance().associateListing(mUUID,listing_uuid,id); - } - else - { - LLNotificationsUtil::add("AlertMerchantListingInvalidID"); - } - } - closeFloater(); -} - -void LLFloaterAssociateListing::cancel() -{ - closeFloater(); -} - -//----------------------------------------------------------------------------- -// LLFloaterMarketplaceValidation -//----------------------------------------------------------------------------- - -// Note: The key is the UUID of the folder to validate. -// Validates the whole marketplace listings content if UUID is null. - -LLFloaterMarketplaceValidation::LLFloaterMarketplaceValidation(const LLSD& key) -: LLFloater(key), -mEditor(NULL) -{ -} - -bool LLFloaterMarketplaceValidation::postBuild() -{ - childSetAction("OK", onOK, this); - - // This widget displays the validation messages - mEditor = getChild("validation_text"); - mEditor->setEnabled(false); - mEditor->setFocus(true); - mEditor->setValue(LLSD()); - - return true; -} - -LLFloaterMarketplaceValidation::~LLFloaterMarketplaceValidation() -{ -} - -// virtual -void LLFloaterMarketplaceValidation::draw() -{ - // draw children - LLFloater::draw(); -} - -void LLFloaterMarketplaceValidation::onOpen(const LLSD& key) -{ - // Clear the messages - clearMessages(); - - // Get the folder UUID to validate. Use the whole marketplace listing if none provided. - LLUUID cat_id(key.asUUID()); - if (cat_id.isNull()) - { - cat_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - } - - // Validates the folder - if (cat_id.notNull()) - { - LLMarketplaceValidator::getInstance()->validateMarketplaceListings( - cat_id, - NULL, - boost::bind(&LLFloaterMarketplaceValidation::appendMessage, this, _1, _2, _3), - false); - } - - // Handle the listing folder being processed - handleCurrentListing(); - - // Dump result to the editor panel - if (mEditor) - { - mEditor->setValue(LLSD()); - if (mMessages.empty()) - { - // Display a no error message - mEditor->appendText(LLTrans::getString("Marketplace Validation No Error"), false); - } - else - { - // Print out all the messages to the panel - message_list_t::iterator mCurrentLine = mMessages.begin(); - bool new_line = false; - while (mCurrentLine != mMessages.end()) - { - // Errors are printed in bold, other messages in normal font - LLStyle::Params style; - LLFontDescriptor new_desc(mEditor->getFont()->getFontDesc()); - new_desc.setStyle(mCurrentLine->mErrorLevel == LLError::LEVEL_ERROR ? LLFontGL::BOLD : LLFontGL::NORMAL); - LLFontGL* new_font = LLFontGL::getFont(new_desc); - style.font = new_font; - mEditor->appendText(mCurrentLine->mMessage, new_line, style); - new_line = true; - mCurrentLine++; - } - } - } - // We don't need the messages anymore - clearMessages(); -} - -// static -void LLFloaterMarketplaceValidation::onOK( void* userdata ) -{ - // destroys this object - LLFloaterMarketplaceValidation* self = (LLFloaterMarketplaceValidation*) userdata; - self->clearMessages(); - self->closeFloater(); -} - -void LLFloaterMarketplaceValidation::appendMessage(std::string& message, S32 depth, LLError::ELevel log_level) -{ - // Dump previous listing messages if we're starting a new listing - if (depth == 1) - { - handleCurrentListing(); - } - - // Store the message in the current listing message list - Message current_message; - current_message.mErrorLevel = log_level; - current_message.mMessage = message; - mCurrentListingMessages.push_back(current_message); - mCurrentListingErrorLevel = (mCurrentListingErrorLevel < log_level ? log_level : mCurrentListingErrorLevel); -} - -// Move the current listing messages to the general list if needs be and reset the current listing data -void LLFloaterMarketplaceValidation::handleCurrentListing() -{ - // Dump the current folder messages to the general message list if level warrants it - if (mCurrentListingErrorLevel > LLError::LEVEL_INFO) - { - message_list_t::iterator mCurrentLine = mCurrentListingMessages.begin(); - while (mCurrentLine != mCurrentListingMessages.end()) - { - mMessages.push_back(*mCurrentLine); - mCurrentLine++; - } - } - - // Reset the current listing - mCurrentListingMessages.clear(); - mCurrentListingErrorLevel = LLError::LEVEL_INFO; -} - -void LLFloaterMarketplaceValidation::clearMessages() -{ - mMessages.clear(); - mCurrentListingMessages.clear(); - mCurrentListingErrorLevel = LLError::LEVEL_INFO; -} - -//----------------------------------------------------------------------------- -// LLFloaterItemProperties -//----------------------------------------------------------------------------- - -LLFloaterItemProperties::LLFloaterItemProperties(const LLSD& key) -: LLFloater(key) -{ -} - -LLFloaterItemProperties::~LLFloaterItemProperties() -{ -} - -bool LLFloaterItemProperties::postBuild() -{ - return LLFloater::postBuild(); -} - -void LLFloaterItemProperties::onOpen(const LLSD& key) -{ - // Tell the panel which item it needs to visualize - LLPanel* panel = findChild("sidepanel"); - - LLSidepanelItemInfo* item_panel = dynamic_cast(panel); - if (item_panel) - { - item_panel->setItemID(key["id"].asUUID()); - if (key.has("object")) - { - item_panel->setObjectID(key["object"].asUUID()); - } - item_panel->setParentFloater(this); - } - - LLSidepanelTaskInfo* task_panel = dynamic_cast(panel); - if (task_panel) - { - task_panel->setObjectSelection(LLSelectMgr::getInstance()->getSelection()); - } -} - -LLMultiItemProperties::LLMultiItemProperties(const LLSD& key) - : LLMultiFloater(LLSD()) -{ - // start with a small rect in the top-left corner ; will get resized - LLRect rect; - rect.setLeftTopAndSize(0, gViewerWindow->getWindowHeightScaled(), 350, 350); - setRect(rect); - LLFloater* last_floater = LLFloaterReg::getLastFloaterInGroup(key.asString()); - if (last_floater) - { - stackWith(*last_floater); - } - setTitle(LLTrans::getString("MultiPropertiesTitle")); - buildTabContainer(); -} +/** + * @file llfloatermarketplacelistings.cpp + * @brief Implementation of the marketplace listings floater and panels + * @author merov@lindenlab.com + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatermarketplacelistings.h" + +#include "llfloaterreg.h" +#include "llfiltereditor.h" +#include "llfolderview.h" +#include "llinventorybridge.h" +#include "llinventorymodelbackgroundfetch.h" +#include "llinventoryobserver.h" +#include "llinventoryfunctions.h" +#include "llmarketplacefunctions.h" +#include "llnotificationhandler.h" +#include "llnotificationmanager.h" +#include "llnotificationsutil.h" +#include "llsidepaneliteminfo.h" +#include "llsidepaneltaskinfo.h" +#include "lltabcontainer.h" +#include "lltextbox.h" +#include "lltrans.h" +#include "llviewerwindow.h" + +///---------------------------------------------------------------------------- +/// LLPanelMarketplaceListings +///---------------------------------------------------------------------------- + +static LLPanelInjector t_panel_status("llpanelmarketplacelistings"); + +LLPanelMarketplaceListings::LLPanelMarketplaceListings() +: mRootFolder(NULL) +, mSortOrder(LLInventoryFilter::SO_FOLDERS_BY_NAME) +, mFilterListingFoldersOnly(false) +{ + mCommitCallbackRegistrar.add("Marketplace.ViewSort.Action", boost::bind(&LLPanelMarketplaceListings::onViewSortMenuItemClicked, this, _2)); + mEnableCallbackRegistrar.add("Marketplace.ViewSort.CheckItem", boost::bind(&LLPanelMarketplaceListings::onViewSortMenuItemCheck, this, _2)); +} + +bool LLPanelMarketplaceListings::postBuild() +{ + childSetAction("add_btn", boost::bind(&LLPanelMarketplaceListings::onAddButtonClicked, this)); + childSetAction("audit_btn", boost::bind(&LLPanelMarketplaceListings::onAuditButtonClicked, this)); + + mFilterEditor = getChild("filter_editor"); + mFilterEditor->setCommitCallback(boost::bind(&LLPanelMarketplaceListings::onFilterEdit, this, _2)); + + mAuditBtn = getChild("audit_btn"); + mAuditBtn->setEnabled(false); + + return LLPanel::postBuild(); +} + +bool LLPanelMarketplaceListings::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + LLView * handled_view = childrenHandleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); + bool handled = (handled_view != NULL); + // Special case the drop zone + if (handled && (handled_view->getName() == "marketplace_drop_zone")) + { + LLFolderView* root_folder = getRootFolder(); + handled = root_folder->handleDragAndDropToThisFolder(mask, drop, cargo_type, cargo_data, accept, tooltip_msg); + } + return handled; +} + +void LLPanelMarketplaceListings::buildAllPanels() +{ + // Build the All panel first + LLInventoryPanel* panel_all_items; + panel_all_items = buildInventoryPanel("All Items", "panel_marketplace_listings_inventory.xml"); + panel_all_items->getFilter().setEmptyLookupMessage("MarketplaceNoMatchingItems"); + panel_all_items->getFilter().markDefault(); + + // Build the other panels + LLInventoryPanel* panel; + panel = buildInventoryPanel("Active Items", "panel_marketplace_listings_listed.xml"); + panel->getFilter().setFilterMarketplaceActiveFolders(); + panel->getFilter().setEmptyLookupMessage("MarketplaceNoMatchingItems"); + panel->getFilter().setDefaultEmptyLookupMessage("MarketplaceNoListing"); + panel->getFilter().markDefault(); + panel = buildInventoryPanel("Inactive Items", "panel_marketplace_listings_unlisted.xml"); + panel->getFilter().setFilterMarketplaceInactiveFolders(); + panel->getFilter().setEmptyLookupMessage("MarketplaceNoMatchingItems"); + panel->getFilter().setDefaultEmptyLookupMessage("MarketplaceNoListing"); + panel->getFilter().markDefault(); + panel = buildInventoryPanel("Unassociated Items", "panel_marketplace_listings_unassociated.xml"); + panel->getFilter().setFilterMarketplaceUnassociatedFolders(); + panel->getFilter().setEmptyLookupMessage("MarketplaceNoMatchingItems"); + panel->getFilter().setDefaultEmptyLookupMessage("MarketplaceNoListing"); + panel->getFilter().markDefault(); + + // Set the tab panel + LLTabContainer* tabs_panel = getChild("marketplace_filter_tabs"); + tabs_panel->setCommitCallback(boost::bind(&LLPanelMarketplaceListings::onTabChange, this)); + tabs_panel->selectTabPanel(panel_all_items); // All panel selected by default + mRootFolder = panel_all_items->getRootFolder(); // Keep the root of the all panel + + // Set the default sort order + setSortOrder(gSavedSettings.getU32("MarketplaceListingsSortOrder")); +} + +LLInventoryPanel* LLPanelMarketplaceListings::buildInventoryPanel(const std::string& childname, const std::string& filename) +{ + LLTabContainer* tabs_panel = getChild("marketplace_filter_tabs"); + LLInventoryPanel* panel = LLUICtrlFactory::createFromFile(filename, tabs_panel, LLInventoryPanel::child_registry_t::instance()); + llassert(panel != NULL); + + // Set sort order and callbacks + panel = getChild(childname); + panel->getFolderViewModel()->setSorter(LLInventoryFilter::SO_FOLDERS_BY_NAME); + panel->setSelectCallback(boost::bind(&LLPanelMarketplaceListings::onSelectionChange, this, panel, _1, _2)); + + return panel; +} + +void LLPanelMarketplaceListings::setSortOrder(U32 sort_order) +{ + mSortOrder = sort_order; + gSavedSettings.setU32("MarketplaceListingsSortOrder", sort_order); + + // Set each panel with that sort order + LLTabContainer* tabs_panel = getChild("marketplace_filter_tabs"); + LLInventoryPanel* panel = (LLInventoryPanel*)tabs_panel->getPanelByName("All Items"); + panel->setSortOrder(mSortOrder); + panel = (LLInventoryPanel*)tabs_panel->getPanelByName("Active Items"); + panel->setSortOrder(mSortOrder); + panel = (LLInventoryPanel*)tabs_panel->getPanelByName("Inactive Items"); + panel->setSortOrder(mSortOrder); + panel = (LLInventoryPanel*)tabs_panel->getPanelByName("Unassociated Items"); + panel->setSortOrder(mSortOrder); +} + +void LLPanelMarketplaceListings::onFilterEdit(const std::string& search_string) +{ + // Find active panel + LLInventoryPanel* panel = (LLInventoryPanel*)getChild("marketplace_filter_tabs")->getCurrentPanel(); + if (panel) + { + // Save filter string (needed when switching tabs) + mFilterSubString = search_string; + // Set filter string on active panel + panel->setFilterSubString(mFilterSubString); + } +} + +void LLPanelMarketplaceListings::draw() +{ + if (LLMarketplaceData::instance().checkDirtyCount()) + { + update_all_marketplace_count(); + } + + // Get the audit button enabled only after the whole inventory is fetched + if (!mAuditBtn->getEnabled()) + { + LLInventoryModelBackgroundFetch* inst = LLInventoryModelBackgroundFetch::getInstance(); + mAuditBtn->setEnabled(inst->isEverythingFetched() && !inst->folderFetchActive()); + } + + LLPanel::draw(); +} + +void LLPanelMarketplaceListings::onSelectionChange(LLInventoryPanel *panel, const std::deque& items, bool user_action) +{ + panel->onSelectionChange(items, user_action); +} + +bool LLPanelMarketplaceListings::allowDropOnRoot() +{ + LLInventoryPanel* panel = (LLInventoryPanel*)getChild("marketplace_filter_tabs")->getCurrentPanel(); + return (panel ? panel->getAllowDropOnRoot() : false); +} + +void LLPanelMarketplaceListings::onTabChange() +{ + // Find active panel + LLInventoryPanel* panel = (LLInventoryPanel*)getChild("marketplace_filter_tabs")->getCurrentPanel(); + if (panel) + { + // If the panel doesn't allow drop on root, it doesn't allow the creation of new folder on root either + LLButton* add_btn = getChild("add_btn"); + add_btn->setEnabled(panel->getAllowDropOnRoot()); + + // Set filter string on active panel + panel->setFilterSubString(mFilterSubString); + + // Show/hide the drop zone and resize the inventory tabs panel accordingly + LLPanel* drop_zone = (LLPanel*)getChild("marketplace_drop_zone"); + bool drop_zone_visible = drop_zone->getVisible(); + if (drop_zone_visible != panel->getAllowDropOnRoot()) + { + LLPanel* tabs = (LLPanel*)getChild("tab_container_panel"); + S32 delta_height = drop_zone->getRect().getHeight(); + delta_height = (drop_zone_visible ? delta_height : -delta_height); + tabs->reshape(tabs->getRect().getWidth(),tabs->getRect().getHeight() + delta_height); + tabs->translate(0,-delta_height); + } + drop_zone->setVisible(panel->getAllowDropOnRoot()); + } +} + +void LLPanelMarketplaceListings::onAddButtonClicked() +{ + LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + llassert(marketplacelistings_id.notNull()); + LLFolderType::EType preferred_type = LLFolderType::lookup("category"); + LLHandle handle = getHandle(); + gInventory.createNewCategory( + marketplacelistings_id, + preferred_type, + LLStringUtil::null, + [handle](const LLUUID &new_cat_id) + { + // Find active panel + LLPanel *marketplace_panel = handle.get(); + if (!marketplace_panel) + { + return; + } + LLInventoryPanel* panel = (LLInventoryPanel*)marketplace_panel->getChild("marketplace_filter_tabs")->getCurrentPanel(); + if (panel) + { + gInventory.notifyObservers(); + panel->setSelectionByID(new_cat_id, true); + panel->getRootFolder()->setNeedsAutoRename(true); + } + } + ); +} + +void LLPanelMarketplaceListings::onAuditButtonClicked() +{ + LLSD data(LLSD::emptyMap()); + LLFloaterReg::showInstance("marketplace_validation", data); +} + +void LLPanelMarketplaceListings::onViewSortMenuItemClicked(const LLSD& userdata) +{ + std::string chosen_item = userdata.asString(); + + // Sort options + if ((chosen_item == "sort_by_stock_amount") || (chosen_item == "sort_by_name") || (chosen_item == "sort_by_recent")) + { + // We're making sort options exclusive, default is SO_FOLDERS_BY_NAME + if (chosen_item == "sort_by_stock_amount") + { + setSortOrder(LLInventoryFilter::SO_FOLDERS_BY_WEIGHT); + } + else if (chosen_item == "sort_by_name") + { + setSortOrder(LLInventoryFilter::SO_FOLDERS_BY_NAME); + } + else if (chosen_item == "sort_by_recent") + { + setSortOrder(LLInventoryFilter::SO_DATE); + } + } + // Filter option + else if (chosen_item == "show_only_listing_folders") + { + mFilterListingFoldersOnly = !mFilterListingFoldersOnly; + // Set each panel with that filter flag + LLTabContainer* tabs_panel = getChild("marketplace_filter_tabs"); + LLInventoryPanel* panel = (LLInventoryPanel*)tabs_panel->getPanelByName("All Items"); + panel->getFilter().setFilterMarketplaceListingFolders(mFilterListingFoldersOnly); + panel = (LLInventoryPanel*)tabs_panel->getPanelByName("Active Items"); + panel->getFilter().setFilterMarketplaceListingFolders(mFilterListingFoldersOnly); + panel = (LLInventoryPanel*)tabs_panel->getPanelByName("Inactive Items"); + panel->getFilter().setFilterMarketplaceListingFolders(mFilterListingFoldersOnly); + panel = (LLInventoryPanel*)tabs_panel->getPanelByName("Unassociated Items"); + panel->getFilter().setFilterMarketplaceListingFolders(mFilterListingFoldersOnly); + } +} + +bool LLPanelMarketplaceListings::onViewSortMenuItemCheck(const LLSD& userdata) +{ + std::string chosen_item = userdata.asString(); + + if ((chosen_item == "sort_by_stock_amount") || (chosen_item == "sort_by_name") || (chosen_item == "sort_by_recent")) + { + if (chosen_item == "sort_by_stock_amount") + { + return (mSortOrder & LLInventoryFilter::SO_FOLDERS_BY_WEIGHT); + } + else if (chosen_item == "sort_by_name") + { + return (mSortOrder & LLInventoryFilter::SO_FOLDERS_BY_NAME); + } + else if (chosen_item == "sort_by_recent") + { + return (mSortOrder & LLInventoryFilter::SO_DATE); + } + } + else if (chosen_item == "show_only_listing_folders") + { + return mFilterListingFoldersOnly; + } + return false; +} + +///---------------------------------------------------------------------------- +/// LLMarketplaceListingsAddedObserver helper class +///---------------------------------------------------------------------------- + +class LLMarketplaceListingsAddedObserver : public LLInventoryCategoryAddedObserver +{ +public: + LLMarketplaceListingsAddedObserver(LLFloaterMarketplaceListings * marketplace_listings_floater) + : LLInventoryCategoryAddedObserver() + , mMarketplaceListingsFloater(marketplace_listings_floater) + { + } + + void done() + { + for (cat_vec_t::iterator it = mAddedCategories.begin(); it != mAddedCategories.end(); ++it) + { + LLViewerInventoryCategory* added_category = *it; + + LLFolderType::EType added_category_type = added_category->getPreferredType(); + + if (added_category_type == LLFolderType::FT_MARKETPLACE_LISTINGS) + { + mMarketplaceListingsFloater->initializeMarketPlace(); + } + } + } + +private: + LLFloaterMarketplaceListings * mMarketplaceListingsFloater; +}; + +///---------------------------------------------------------------------------- +/// LLFloaterMarketplaceListings +///---------------------------------------------------------------------------- + +LLFloaterMarketplaceListings::LLFloaterMarketplaceListings(const LLSD& key) +: LLFloater(key) +, mCategoriesObserver(NULL) +, mCategoryAddedObserver(NULL) +, mRootFolderId(LLUUID::null) +, mInventoryStatus(NULL) +, mInventoryInitializationInProgress(NULL) +, mInventoryPlaceholder(NULL) +, mInventoryText(NULL) +, mInventoryTitle(NULL) +, mPanelListings(NULL) +, mPanelListingsSet(false) +, mRootFolderCreating(false) +{ +} + +LLFloaterMarketplaceListings::~LLFloaterMarketplaceListings() +{ + if (mCategoriesObserver && gInventory.containsObserver(mCategoriesObserver)) + { + gInventory.removeObserver(mCategoriesObserver); + } + delete mCategoriesObserver; + + if (mCategoryAddedObserver && gInventory.containsObserver(mCategoryAddedObserver)) + { + gInventory.removeObserver(mCategoryAddedObserver); + } + delete mCategoryAddedObserver; +} + +bool LLFloaterMarketplaceListings::postBuild() +{ + mInventoryStatus = getChild("marketplace_status"); + mInventoryInitializationInProgress = getChild("initialization_progress_indicator"); + mInventoryPlaceholder = getChild("marketplace_listings_inventory_placeholder_panel"); + mInventoryText = mInventoryPlaceholder->getChild("marketplace_listings_inventory_placeholder_text"); + mInventoryTitle = mInventoryPlaceholder->getChild("marketplace_listings_inventory_placeholder_title"); + + mPanelListings = static_cast(getChild("panel_marketplace_listing")); + + LLFocusableElement::setFocusReceivedCallback(boost::bind(&LLFloaterMarketplaceListings::onFocusReceived, this)); + + // Observe category creation to catch marketplace listings creation (moot if already existing) + mCategoryAddedObserver = new LLMarketplaceListingsAddedObserver(this); + gInventory.addObserver(mCategoryAddedObserver); + + + // Fetch aggressively so we can interact with listings as soon as possible + if (!fetchContents()) + { + const LLUUID& marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + LLInventoryModelBackgroundFetch::instance().start(marketplacelistings_id, true); + } + + + return true; +} + +void LLFloaterMarketplaceListings::onClose(bool app_quitting) +{ +} + +void LLFloaterMarketplaceListings::onOpen(const LLSD& key) +{ + // + // Initialize the Market Place or go update the marketplace listings + // + if (LLMarketplaceData::instance().getSLMStatus() <= MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE) + { + initializeMarketPlace(); + } + else + { + updateView(); + } +} + +void LLFloaterMarketplaceListings::onFocusReceived() +{ + updateView(); +} + +bool LLFloaterMarketplaceListings::fetchContents() +{ + if (mRootFolderId.notNull() && + (LLMarketplaceData::instance().getSLMDataFetched() != MarketplaceFetchCodes::MARKET_FETCH_LOADING) && + (LLMarketplaceData::instance().getSLMDataFetched() != MarketplaceFetchCodes::MARKET_FETCH_DONE)) + { + LLMarketplaceData::instance().setDataFetchedSignal(boost::bind(&LLFloaterMarketplaceListings::updateView, this)); + LLMarketplaceData::instance().setSLMDataFetched(MarketplaceFetchCodes::MARKET_FETCH_LOADING); + LLInventoryModelBackgroundFetch::instance().start(mRootFolderId, true); + LLMarketplaceData::instance().getSLMListings(); + return true; + } + return false; +} + +void LLFloaterMarketplaceListings::setRootFolder() +{ + if ((LLMarketplaceData::instance().getSLMStatus() != MarketplaceStatusCodes::MARKET_PLACE_MERCHANT) && + (LLMarketplaceData::instance().getSLMStatus() != MarketplaceStatusCodes::MARKET_PLACE_MIGRATED_MERCHANT)) + { + // If we are *not* a merchant or we have no market place connection established yet, do nothing + return; + } + if (!gInventory.isInventoryUsable()) + { + return; + } + + LLFolderType::EType preferred_type = LLFolderType::FT_MARKETPLACE_LISTINGS; + // We are a merchant. Get the Marketplace listings folder, create it if needs be. + LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(preferred_type); + + if (marketplacelistings_id.isNull()) + { + if (!mRootFolderCreating) + { + mRootFolderCreating = true; + gInventory.createNewCategory( + gInventory.getRootFolderID(), + preferred_type, + LLStringUtil::null, + [](const LLUUID &new_cat_id) + { + LLFloaterMarketplaceListings* marketplace = LLFloaterReg::findTypedInstance("marketplace_listings"); + if (marketplace) + { + if (new_cat_id.notNull()) + { + // will call setRootFolder again + marketplace->updateView(); + } + // don't update in case of failure, createNewCategory can return + // immediately if cap is missing and will cause a loop + else + { + // unblock + marketplace->mRootFolderCreating = false; + LL_WARNS("SLM") << "Inventory warning: Failed to create marketplace listings folder for a merchant" << LL_ENDL; + } + } + } + ); + } + return; + } + + mRootFolderCreating = false; + + // No longer need to observe new category creation + if (mCategoryAddedObserver && gInventory.containsObserver(mCategoryAddedObserver)) + { + gInventory.removeObserver(mCategoryAddedObserver); + delete mCategoryAddedObserver; + mCategoryAddedObserver = NULL; + } + llassert(!mCategoryAddedObserver); + + if (marketplacelistings_id == mRootFolderId) + { + LL_WARNS("SLM") << "Inventory warning: Marketplace listings folder already set" << LL_ENDL; + return; + } + + mRootFolderId = marketplacelistings_id; +} + +void LLFloaterMarketplaceListings::setPanels() +{ + if (mRootFolderId.isNull()) + { + return; + } + + // Consolidate Marketplace listings + // We shouldn't have to do that but with a client/server system relying on a "well known folder" convention, + // things get messy and conventions get broken down eventually + gInventory.consolidateForType(mRootFolderId, LLFolderType::FT_MARKETPLACE_LISTINGS); + + // Now that we do have a non NULL root, we can build the inventory panels + mPanelListings->buildAllPanels(); + + // Create observer for marketplace listings modifications + if (!mCategoriesObserver) + { + mCategoriesObserver = new LLInventoryCategoriesObserver(); + llassert(mCategoriesObserver); + gInventory.addObserver(mCategoriesObserver); + mCategoriesObserver->addCategory(mRootFolderId, boost::bind(&LLFloaterMarketplaceListings::onChanged, this)); + } + + // Get the content of the marketplace listings folder + fetchContents(); + + // Flag that this is done + mPanelListingsSet = true; +} + +void LLFloaterMarketplaceListings::initializeMarketPlace() +{ + LLMarketplaceData::instance().initializeSLM(boost::bind(&LLFloaterMarketplaceListings::updateView, this)); +} + +S32 LLFloaterMarketplaceListings::getFolderCount() +{ + if (mPanelListings && mRootFolderId.notNull()) + { + LLInventoryModel::cat_array_t * cats; + LLInventoryModel::item_array_t * items; + gInventory.getDirectDescendentsOf(mRootFolderId, cats, items); + + return (cats->size() + items->size()); + } + else + { + return 0; + } +} + +void LLFloaterMarketplaceListings::setStatusString(const std::string& statusString) +{ + mInventoryStatus->setText(statusString); +} + +void LLFloaterMarketplaceListings::updateView() +{ + U32 mkt_status = LLMarketplaceData::instance().getSLMStatus(); + bool is_merchant = (mkt_status == MarketplaceStatusCodes::MARKET_PLACE_MERCHANT) || (mkt_status == MarketplaceStatusCodes::MARKET_PLACE_MIGRATED_MERCHANT); + U32 data_fetched = LLMarketplaceData::instance().getSLMDataFetched(); + + // Get or create the root folder if we are a merchant and it hasn't been done already + if (mRootFolderId.isNull() && is_merchant) + { + setRootFolder(); + } + if (mRootFolderCreating) + { + // waiting for callback + return; + } + + // Update the bottom initializing status and progress dial if we are initializing or if we're a merchant and still loading + if ((mkt_status <= MarketplaceStatusCodes::MARKET_PLACE_INITIALIZING) || (is_merchant && (data_fetched <= MarketplaceFetchCodes::MARKET_FETCH_LOADING)) ) + { + // Just show the loading indicator in that case and fetch the data (fetch will be skipped if it's already loading) + mInventoryInitializationInProgress->setVisible(true); + mPanelListings->setVisible(false); + fetchContents(); + return; + } + else + { + mInventoryInitializationInProgress->setVisible(false); + } + + // Update the middle portion : tabs or messages + if (getFolderCount() > 0) + { + if (!mPanelListingsSet) + { + // We need to rebuild the tabs cleanly the first time we make them visible + setPanels(); + } + mPanelListings->setVisible(true); + mInventoryPlaceholder->setVisible(false); + } + else + { + mPanelListings->setVisible(false); + mInventoryPlaceholder->setVisible(true); + + std::string text; + std::string title; + std::string tooltip; + + const LLSD& subs = LLMarketplaceData::getMarketplaceStringSubstitutions(); + + // Update the top message or flip to the tabs and folders view + // *TODO : check those messages and create better appropriate ones in strings.xml + if (mkt_status == MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE) + { + std::string reason = LLMarketplaceData::instance().getSLMConnectionfailureReason(); + if (reason.empty()) + { + text = LLTrans::getString("InventoryMarketplaceConnectionError"); + } + else + { + LLSD args; + args["[REASON]"] = reason; + text = LLTrans::getString("InventoryMarketplaceConnectionErrorReason", args); + } + + title = LLTrans::getString("InventoryOutboxErrorTitle"); + tooltip = LLTrans::getString("InventoryOutboxErrorTooltip"); + LL_WARNS() << "Marketplace status code: " << mkt_status << LL_ENDL; + } + else if (mRootFolderId.notNull()) + { + // "Marketplace listings is empty!" message strings + text = LLTrans::getString("InventoryMarketplaceListingsNoItems", subs); + title = LLTrans::getString("InventoryMarketplaceListingsNoItemsTitle"); + tooltip = LLTrans::getString("InventoryMarketplaceListingsNoItemsTooltip"); + } + else if (mkt_status <= MarketplaceStatusCodes::MARKET_PLACE_INITIALIZING) + { + // "Initializing!" message strings + text = LLTrans::getString("InventoryOutboxInitializing", subs); + title = LLTrans::getString("InventoryOutboxInitializingTitle"); + tooltip = LLTrans::getString("InventoryOutboxInitializingTooltip"); + } + else if (mkt_status == MarketplaceStatusCodes::MARKET_PLACE_NOT_MERCHANT) + { + // "Not a merchant!" message strings + text = LLTrans::getString("InventoryOutboxNotMerchant", subs); + title = LLTrans::getString("InventoryOutboxNotMerchantTitle"); + tooltip = LLTrans::getString("InventoryOutboxNotMerchantTooltip"); + } + else + { + // "Errors!" message strings + text = LLTrans::getString("InventoryMarketplaceError", subs); + title = LLTrans::getString("InventoryOutboxErrorTitle"); + tooltip = LLTrans::getString("InventoryOutboxErrorTooltip"); + LL_WARNS() << "Marketplace status code: " << mkt_status << LL_ENDL; + } + + mInventoryText->setValue(text); + mInventoryTitle->setValue(title); + mInventoryPlaceholder->getParent()->setToolTip(tooltip); + } +} + +bool LLFloaterMarketplaceListings::isAccepted(EAcceptance accept) +{ + return (accept >= ACCEPT_YES_COPY_SINGLE); +} + +bool LLFloaterMarketplaceListings::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + // If there's no panel to accept drops or no existing marketplace listings folder, we refuse all drop + if (!mPanelListings || mRootFolderId.isNull()) + { + return false; + } + + tooltip_msg = ""; + + // Pass to the children + LLView * handled_view = childrenHandleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); + bool handled = (handled_view != NULL); + + // If no one handled it or it was not accepted and we drop on an empty panel, we try to accept it at the floater level + // as if it was dropped on the marketplace listings root folder + if ((!handled || !isAccepted(*accept)) && !mPanelListings->getVisible() && mRootFolderId.notNull()) + { + if (!mPanelListingsSet) + { + setPanels(); + } + LLFolderView* root_folder = mPanelListings->getRootFolder(); + handled = root_folder->handleDragAndDropToThisFolder(mask, drop, cargo_type, cargo_data, accept, tooltip_msg); + } + + return handled; +} + +bool LLFloaterMarketplaceListings::handleHover(S32 x, S32 y, MASK mask) +{ + return LLFloater::handleHover(x, y, mask); +} + +void LLFloaterMarketplaceListings::onMouseLeave(S32 x, S32 y, MASK mask) +{ + LLFloater::onMouseLeave(x, y, mask); +} + +void LLFloaterMarketplaceListings::onChanged() +{ + LLViewerInventoryCategory* category = gInventory.getCategory(mRootFolderId); + if (mRootFolderId.notNull() && category) + { + updateView(); + } + else + { + // Invalidate the marketplace listings data + mRootFolderId.setNull(); + } +} + +//----------------------------------------------------------------------------- +// LLFloaterAssociateListing +//----------------------------------------------------------------------------- + +// Tell if a listing has one only version folder +bool hasUniqueVersionFolder(const LLUUID& folder_id) +{ + LLInventoryModel::cat_array_t* categories; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(folder_id, categories, items); + return (categories->size() == 1); +} + +LLFloaterAssociateListing::LLFloaterAssociateListing(const LLSD& key) +: LLFloater(key) +, mUUID() +{ +} + +LLFloaterAssociateListing::~LLFloaterAssociateListing() +{ + gFocusMgr.releaseFocusIfNeeded( this ); +} + +bool LLFloaterAssociateListing::postBuild() +{ + getChild("OK")->setCommitCallback(boost::bind(&LLFloaterAssociateListing::apply, this, true)); + getChild("Cancel")->setCommitCallback(boost::bind(&LLFloaterAssociateListing::cancel, this)); + getChild("listing_id")->setPrevalidate(&LLTextValidate::validateNonNegativeS32); + center(); + + return LLFloater::postBuild(); +} + +bool LLFloaterAssociateListing::handleKeyHere(KEY key, MASK mask) +{ + if (key == KEY_RETURN && mask == MASK_NONE) + { + apply(); + return true; + } + else if (key == KEY_ESCAPE && mask == MASK_NONE) + { + cancel(); + return true; + } + + return LLFloater::handleKeyHere(key, mask); +} + +// static +LLFloaterAssociateListing* LLFloaterAssociateListing::show(const LLUUID& folder_id) +{ + LLFloaterAssociateListing* floater = LLFloaterReg::showTypedInstance("associate_listing"); + + floater->mUUID = folder_id; + + return floater; +} + +// Callback for apply if DAMA required... +void LLFloaterAssociateListing::callback_apply(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // YES + { + apply(false); + } +} + +void LLFloaterAssociateListing::apply(bool user_confirm) +{ + if (mUUID.notNull()) + { + S32 id = (S32)getChild("listing_id")->getValue().asInteger(); + if (id > 0) + { + // Check if the id exists in the merchant SLM DB: note that this record might exist in the LLMarketplaceData + // structure even if unseen in the UI, for instance, if its listing_uuid doesn't exist in the merchant inventory + LLUUID listing_uuid = LLMarketplaceData::instance().getListingFolder(id); + if (listing_uuid.notNull() && user_confirm && LLMarketplaceData::instance().getActivationState(listing_uuid) && !hasUniqueVersionFolder(mUUID)) + { + // Look for user confirmation before unlisting + LLNotificationsUtil::add("ConfirmMerchantUnlist", LLSD(), LLSD(), boost::bind(&LLFloaterAssociateListing::callback_apply, this, _1, _2)); + return; + } + // Associate the id with the user chosen folder + LLMarketplaceData::instance().associateListing(mUUID,listing_uuid,id); + } + else + { + LLNotificationsUtil::add("AlertMerchantListingInvalidID"); + } + } + closeFloater(); +} + +void LLFloaterAssociateListing::cancel() +{ + closeFloater(); +} + +//----------------------------------------------------------------------------- +// LLFloaterMarketplaceValidation +//----------------------------------------------------------------------------- + +// Note: The key is the UUID of the folder to validate. +// Validates the whole marketplace listings content if UUID is null. + +LLFloaterMarketplaceValidation::LLFloaterMarketplaceValidation(const LLSD& key) +: LLFloater(key), +mEditor(NULL) +{ +} + +bool LLFloaterMarketplaceValidation::postBuild() +{ + childSetAction("OK", onOK, this); + + // This widget displays the validation messages + mEditor = getChild("validation_text"); + mEditor->setEnabled(false); + mEditor->setFocus(true); + mEditor->setValue(LLSD()); + + return true; +} + +LLFloaterMarketplaceValidation::~LLFloaterMarketplaceValidation() +{ +} + +// virtual +void LLFloaterMarketplaceValidation::draw() +{ + // draw children + LLFloater::draw(); +} + +void LLFloaterMarketplaceValidation::onOpen(const LLSD& key) +{ + // Clear the messages + clearMessages(); + + // Get the folder UUID to validate. Use the whole marketplace listing if none provided. + LLUUID cat_id(key.asUUID()); + if (cat_id.isNull()) + { + cat_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + } + + // Validates the folder + if (cat_id.notNull()) + { + LLMarketplaceValidator::getInstance()->validateMarketplaceListings( + cat_id, + NULL, + boost::bind(&LLFloaterMarketplaceValidation::appendMessage, this, _1, _2, _3), + false); + } + + // Handle the listing folder being processed + handleCurrentListing(); + + // Dump result to the editor panel + if (mEditor) + { + mEditor->setValue(LLSD()); + if (mMessages.empty()) + { + // Display a no error message + mEditor->appendText(LLTrans::getString("Marketplace Validation No Error"), false); + } + else + { + // Print out all the messages to the panel + message_list_t::iterator mCurrentLine = mMessages.begin(); + bool new_line = false; + while (mCurrentLine != mMessages.end()) + { + // Errors are printed in bold, other messages in normal font + LLStyle::Params style; + LLFontDescriptor new_desc(mEditor->getFont()->getFontDesc()); + new_desc.setStyle(mCurrentLine->mErrorLevel == LLError::LEVEL_ERROR ? LLFontGL::BOLD : LLFontGL::NORMAL); + LLFontGL* new_font = LLFontGL::getFont(new_desc); + style.font = new_font; + mEditor->appendText(mCurrentLine->mMessage, new_line, style); + new_line = true; + mCurrentLine++; + } + } + } + // We don't need the messages anymore + clearMessages(); +} + +// static +void LLFloaterMarketplaceValidation::onOK( void* userdata ) +{ + // destroys this object + LLFloaterMarketplaceValidation* self = (LLFloaterMarketplaceValidation*) userdata; + self->clearMessages(); + self->closeFloater(); +} + +void LLFloaterMarketplaceValidation::appendMessage(std::string& message, S32 depth, LLError::ELevel log_level) +{ + // Dump previous listing messages if we're starting a new listing + if (depth == 1) + { + handleCurrentListing(); + } + + // Store the message in the current listing message list + Message current_message; + current_message.mErrorLevel = log_level; + current_message.mMessage = message; + mCurrentListingMessages.push_back(current_message); + mCurrentListingErrorLevel = (mCurrentListingErrorLevel < log_level ? log_level : mCurrentListingErrorLevel); +} + +// Move the current listing messages to the general list if needs be and reset the current listing data +void LLFloaterMarketplaceValidation::handleCurrentListing() +{ + // Dump the current folder messages to the general message list if level warrants it + if (mCurrentListingErrorLevel > LLError::LEVEL_INFO) + { + message_list_t::iterator mCurrentLine = mCurrentListingMessages.begin(); + while (mCurrentLine != mCurrentListingMessages.end()) + { + mMessages.push_back(*mCurrentLine); + mCurrentLine++; + } + } + + // Reset the current listing + mCurrentListingMessages.clear(); + mCurrentListingErrorLevel = LLError::LEVEL_INFO; +} + +void LLFloaterMarketplaceValidation::clearMessages() +{ + mMessages.clear(); + mCurrentListingMessages.clear(); + mCurrentListingErrorLevel = LLError::LEVEL_INFO; +} + +//----------------------------------------------------------------------------- +// LLFloaterItemProperties +//----------------------------------------------------------------------------- + +LLFloaterItemProperties::LLFloaterItemProperties(const LLSD& key) +: LLFloater(key) +{ +} + +LLFloaterItemProperties::~LLFloaterItemProperties() +{ +} + +bool LLFloaterItemProperties::postBuild() +{ + return LLFloater::postBuild(); +} + +void LLFloaterItemProperties::onOpen(const LLSD& key) +{ + // Tell the panel which item it needs to visualize + LLPanel* panel = findChild("sidepanel"); + + LLSidepanelItemInfo* item_panel = dynamic_cast(panel); + if (item_panel) + { + item_panel->setItemID(key["id"].asUUID()); + if (key.has("object")) + { + item_panel->setObjectID(key["object"].asUUID()); + } + item_panel->setParentFloater(this); + } + + LLSidepanelTaskInfo* task_panel = dynamic_cast(panel); + if (task_panel) + { + task_panel->setObjectSelection(LLSelectMgr::getInstance()->getSelection()); + } +} + +LLMultiItemProperties::LLMultiItemProperties(const LLSD& key) + : LLMultiFloater(LLSD()) +{ + // start with a small rect in the top-left corner ; will get resized + LLRect rect; + rect.setLeftTopAndSize(0, gViewerWindow->getWindowHeightScaled(), 350, 350); + setRect(rect); + LLFloater* last_floater = LLFloaterReg::getLastFloaterInGroup(key.asString()); + if (last_floater) + { + stackWith(*last_floater); + } + setTitle(LLTrans::getString("MultiPropertiesTitle")); + buildTabContainer(); +} diff --git a/indra/newview/llfloatermarketplacelistings.h b/indra/newview/llfloatermarketplacelistings.h index 6f80c4a05b..09b3dc64b6 100644 --- a/indra/newview/llfloatermarketplacelistings.h +++ b/indra/newview/llfloatermarketplacelistings.h @@ -1,234 +1,234 @@ -/** - * @file llfloatermarketplacelistings.h - * @brief Implementation of the marketplace listings floater and panels - * @author merov@lindenlab.com - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * ABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERMARKETPLACELISTINGS_H -#define LL_LLFLOATERMARKETPLACELISTINGS_H - -#include "llfloater.h" -#include "llinventoryfilter.h" -#include "llinventorypanel.h" -#include "llnotificationptr.h" -#include "llmodaldialog.h" -#include "llmultifloater.h" -#include "lltexteditor.h" - -class LLInventoryCategoriesObserver; -class LLInventoryCategoryAddedObserver; -class LLTextBox; -class LLView; -class LLFilterEditor; - -class LLFloaterMarketplaceListings; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLPanelMarketplaceListings -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLPanelMarketplaceListings : public LLPanel -{ -public: - LLPanelMarketplaceListings(); - bool postBuild(); - bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg); - void draw(); - LLFolderView* getRootFolder() { return mRootFolder; } - bool allowDropOnRoot(); - - void buildAllPanels(); - -private: - LLInventoryPanel* buildInventoryPanel(const std::string& childname, const std::string& filename); - - // UI callbacks - void onViewSortMenuItemClicked(const LLSD& userdata); - bool onViewSortMenuItemCheck(const LLSD& userdata); - void onAddButtonClicked(); - void onAuditButtonClicked(); - void onSelectionChange(LLInventoryPanel *panel, const std::deque& items, bool user_action); - void onTabChange(); - void onFilterEdit(const std::string& search_string); - - void setSortOrder(U32 sort_order); - - LLFolderView* mRootFolder; - LLButton* mAuditBtn; - LLFilterEditor* mFilterEditor; - std::string mFilterSubString; - bool mFilterListingFoldersOnly; - U32 mSortOrder; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLFloaterMarketplaceListings -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLFloaterMarketplaceListings : public LLFloater -{ -public: - LLFloaterMarketplaceListings(const LLSD& key); - ~LLFloaterMarketplaceListings(); - - void initializeMarketPlace(); - - // virtuals - bool postBuild(); - bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg); - - void showNotification(const LLNotificationPtr& notification); - - bool handleHover(S32 x, S32 y, MASK mask); - void onMouseLeave(S32 x, S32 y, MASK mask); - -protected: - void setRootFolder(); - void setPanels(); - bool fetchContents(); - - void setStatusString(const std::string& statusString); - - void onClose(bool app_quitting); - void onOpen(const LLSD& key); - void onFocusReceived(); - void onChanged(); - - bool isAccepted(EAcceptance accept); - - void updateView(); - -private: - S32 getFolderCount(); - - LLInventoryCategoriesObserver * mCategoriesObserver; - LLInventoryCategoryAddedObserver * mCategoryAddedObserver; - - LLTextBox * mInventoryStatus; - LLView * mInventoryInitializationInProgress; - LLView * mInventoryPlaceholder; - LLTextBox * mInventoryText; - LLTextBox * mInventoryTitle; - - LLUUID mRootFolderId; - bool mRootFolderCreating; - LLPanelMarketplaceListings * mPanelListings; - bool mPanelListingsSet; -}; - -//----------------------------------------------------------------------------- -// LLFloaterAssociateListing -//----------------------------------------------------------------------------- -class LLFloaterAssociateListing : public LLFloater -{ - friend class LLFloaterReg; -public: - virtual bool postBuild(); - virtual bool handleKeyHere(KEY key, MASK mask); - - static LLFloaterAssociateListing* show(const LLUUID& folder_id); - -private: - LLFloaterAssociateListing(const LLSD& key); - virtual ~LLFloaterAssociateListing(); - - // UI Callbacks - void apply(bool user_confirm = true); - void cancel(); - void callback_apply(const LLSD& notification, const LLSD& response); - - LLUUID mUUID; -}; - -//----------------------------------------------------------------------------- -// LLFloaterMarketplaceValidation -//----------------------------------------------------------------------------- -// Note: The key is the UUID of the folder to validate. Validates the whole -// marketplace listings content if UUID is null. -// Note: For the moment, we just display the validation text. Eventually, we should -// get the validation triggered on the server and display the html report. -// *TODO : morph into an html/text window using the pattern in llfloatertos - -class LLFloaterMarketplaceValidation : public LLFloater -{ -public: - LLFloaterMarketplaceValidation(const LLSD& key); - virtual ~LLFloaterMarketplaceValidation(); - - virtual bool postBuild(); - virtual void draw(); - virtual void onOpen(const LLSD& key); - - void clearMessages(); - void appendMessage(std::string& message, S32 depth, LLError::ELevel log_level); - static void onOK( void* userdata ); - -private: - struct Message { - LLError::ELevel mErrorLevel; - std::string mMessage; - }; - typedef std::vector message_list_t; - - void handleCurrentListing(); - - message_list_t mCurrentListingMessages; - LLError::ELevel mCurrentListingErrorLevel; - - message_list_t mMessages; - - LLTextEditor* mEditor; -}; - -//----------------------------------------------------------------------------- -// LLFloaterItemProperties -//----------------------------------------------------------------------------- - -class LLFloaterItemProperties : public LLFloater -{ -public: - LLFloaterItemProperties(const LLSD& key); - virtual ~LLFloaterItemProperties(); - - bool postBuild(); - virtual void onOpen(const LLSD& key); - -private: -}; - -class LLMultiItemProperties : public LLMultiFloater -{ -public: - LLMultiItemProperties(const LLSD& key); -}; - -#endif // LL_LLFLOATERMARKETPLACELISTINGS_H +/** + * @file llfloatermarketplacelistings.h + * @brief Implementation of the marketplace listings floater and panels + * @author merov@lindenlab.com + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * ABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERMARKETPLACELISTINGS_H +#define LL_LLFLOATERMARKETPLACELISTINGS_H + +#include "llfloater.h" +#include "llinventoryfilter.h" +#include "llinventorypanel.h" +#include "llnotificationptr.h" +#include "llmodaldialog.h" +#include "llmultifloater.h" +#include "lltexteditor.h" + +class LLInventoryCategoriesObserver; +class LLInventoryCategoryAddedObserver; +class LLTextBox; +class LLView; +class LLFilterEditor; + +class LLFloaterMarketplaceListings; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLPanelMarketplaceListings +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLPanelMarketplaceListings : public LLPanel +{ +public: + LLPanelMarketplaceListings(); + bool postBuild(); + bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + void draw(); + LLFolderView* getRootFolder() { return mRootFolder; } + bool allowDropOnRoot(); + + void buildAllPanels(); + +private: + LLInventoryPanel* buildInventoryPanel(const std::string& childname, const std::string& filename); + + // UI callbacks + void onViewSortMenuItemClicked(const LLSD& userdata); + bool onViewSortMenuItemCheck(const LLSD& userdata); + void onAddButtonClicked(); + void onAuditButtonClicked(); + void onSelectionChange(LLInventoryPanel *panel, const std::deque& items, bool user_action); + void onTabChange(); + void onFilterEdit(const std::string& search_string); + + void setSortOrder(U32 sort_order); + + LLFolderView* mRootFolder; + LLButton* mAuditBtn; + LLFilterEditor* mFilterEditor; + std::string mFilterSubString; + bool mFilterListingFoldersOnly; + U32 mSortOrder; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLFloaterMarketplaceListings +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLFloaterMarketplaceListings : public LLFloater +{ +public: + LLFloaterMarketplaceListings(const LLSD& key); + ~LLFloaterMarketplaceListings(); + + void initializeMarketPlace(); + + // virtuals + bool postBuild(); + bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + + void showNotification(const LLNotificationPtr& notification); + + bool handleHover(S32 x, S32 y, MASK mask); + void onMouseLeave(S32 x, S32 y, MASK mask); + +protected: + void setRootFolder(); + void setPanels(); + bool fetchContents(); + + void setStatusString(const std::string& statusString); + + void onClose(bool app_quitting); + void onOpen(const LLSD& key); + void onFocusReceived(); + void onChanged(); + + bool isAccepted(EAcceptance accept); + + void updateView(); + +private: + S32 getFolderCount(); + + LLInventoryCategoriesObserver * mCategoriesObserver; + LLInventoryCategoryAddedObserver * mCategoryAddedObserver; + + LLTextBox * mInventoryStatus; + LLView * mInventoryInitializationInProgress; + LLView * mInventoryPlaceholder; + LLTextBox * mInventoryText; + LLTextBox * mInventoryTitle; + + LLUUID mRootFolderId; + bool mRootFolderCreating; + LLPanelMarketplaceListings * mPanelListings; + bool mPanelListingsSet; +}; + +//----------------------------------------------------------------------------- +// LLFloaterAssociateListing +//----------------------------------------------------------------------------- +class LLFloaterAssociateListing : public LLFloater +{ + friend class LLFloaterReg; +public: + virtual bool postBuild(); + virtual bool handleKeyHere(KEY key, MASK mask); + + static LLFloaterAssociateListing* show(const LLUUID& folder_id); + +private: + LLFloaterAssociateListing(const LLSD& key); + virtual ~LLFloaterAssociateListing(); + + // UI Callbacks + void apply(bool user_confirm = true); + void cancel(); + void callback_apply(const LLSD& notification, const LLSD& response); + + LLUUID mUUID; +}; + +//----------------------------------------------------------------------------- +// LLFloaterMarketplaceValidation +//----------------------------------------------------------------------------- +// Note: The key is the UUID of the folder to validate. Validates the whole +// marketplace listings content if UUID is null. +// Note: For the moment, we just display the validation text. Eventually, we should +// get the validation triggered on the server and display the html report. +// *TODO : morph into an html/text window using the pattern in llfloatertos + +class LLFloaterMarketplaceValidation : public LLFloater +{ +public: + LLFloaterMarketplaceValidation(const LLSD& key); + virtual ~LLFloaterMarketplaceValidation(); + + virtual bool postBuild(); + virtual void draw(); + virtual void onOpen(const LLSD& key); + + void clearMessages(); + void appendMessage(std::string& message, S32 depth, LLError::ELevel log_level); + static void onOK( void* userdata ); + +private: + struct Message { + LLError::ELevel mErrorLevel; + std::string mMessage; + }; + typedef std::vector message_list_t; + + void handleCurrentListing(); + + message_list_t mCurrentListingMessages; + LLError::ELevel mCurrentListingErrorLevel; + + message_list_t mMessages; + + LLTextEditor* mEditor; +}; + +//----------------------------------------------------------------------------- +// LLFloaterItemProperties +//----------------------------------------------------------------------------- + +class LLFloaterItemProperties : public LLFloater +{ +public: + LLFloaterItemProperties(const LLSD& key); + virtual ~LLFloaterItemProperties(); + + bool postBuild(); + virtual void onOpen(const LLSD& key); + +private: +}; + +class LLMultiItemProperties : public LLMultiFloater +{ +public: + LLMultiItemProperties(const LLSD& key); +}; + +#endif // LL_LLFLOATERMARKETPLACELISTINGS_H diff --git a/indra/newview/llfloatermediasettings.cpp b/indra/newview/llfloatermediasettings.cpp index a03e13d4e9..2496887c9d 100644 --- a/indra/newview/llfloatermediasettings.cpp +++ b/indra/newview/llfloatermediasettings.cpp @@ -1,335 +1,335 @@ -/** - * @file llfloatermediasettings.cpp - * @brief Tabbed dialog for media settings - class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterreg.h" -#include "llfloatermediasettings.h" -#include "llpanelmediasettingsgeneral.h" -#include "llpanelmediasettingssecurity.h" -#include "llpanelmediasettingspermissions.h" -#include "llviewercontrol.h" -#include "lluictrlfactory.h" -#include "llbutton.h" -#include "llselectmgr.h" -#include "llsdutil.h" - -LLFloaterMediaSettings* LLFloaterMediaSettings::sInstance = NULL; - -//////////////////////////////////////////////////////////////////////////////// -// -LLFloaterMediaSettings::LLFloaterMediaSettings(const LLSD& key) - : LLFloater(key), - mTabContainer(NULL), - mPanelMediaSettingsGeneral(NULL), - mPanelMediaSettingsSecurity(NULL), - mPanelMediaSettingsPermissions(NULL), - mIdenticalHasMediaInfo( true ), - mMultipleMedia(false), - mMultipleValidMedia(false) -{ -} - -//////////////////////////////////////////////////////////////////////////////// -// -LLFloaterMediaSettings::~LLFloaterMediaSettings() -{ - if ( mPanelMediaSettingsGeneral ) - { - delete mPanelMediaSettingsGeneral; - mPanelMediaSettingsGeneral = NULL; - } - - if ( mPanelMediaSettingsSecurity ) - { - delete mPanelMediaSettingsSecurity; - mPanelMediaSettingsSecurity = NULL; - } - - if ( mPanelMediaSettingsPermissions ) - { - delete mPanelMediaSettingsPermissions; - mPanelMediaSettingsPermissions = NULL; - } - - sInstance = NULL; -} - -//////////////////////////////////////////////////////////////////////////////// -// -bool LLFloaterMediaSettings::postBuild() -{ - mApplyBtn = getChild("Apply"); - mApplyBtn->setClickedCallback(onBtnApply, this); - - mCancelBtn = getChild("Cancel"); - mCancelBtn->setClickedCallback(onBtnCancel, this); - - mOKBtn = getChild("OK"); - mOKBtn->setClickedCallback(onBtnOK, this); - - mTabContainer = getChild( "tab_container" ); - - mPanelMediaSettingsGeneral = new LLPanelMediaSettingsGeneral(); - mTabContainer->addTabPanel( - LLTabContainer::TabPanelParams(). - panel(mPanelMediaSettingsGeneral)); - mPanelMediaSettingsGeneral->setParent( this ); - - // note that "permissions" tab is really "Controls" tab - refs to 'perms' and - // 'permissions' not changed to 'controls' since we don't want to change - // shared files in server code and keeping everything the same seemed best. - mPanelMediaSettingsPermissions = new LLPanelMediaSettingsPermissions(); - mTabContainer->addTabPanel( - LLTabContainer::TabPanelParams(). - panel(mPanelMediaSettingsPermissions)); - - mPanelMediaSettingsSecurity = new LLPanelMediaSettingsSecurity(); - mTabContainer->addTabPanel( - LLTabContainer::TabPanelParams(). - panel(mPanelMediaSettingsSecurity)); - mPanelMediaSettingsSecurity->setParent( this ); - - // restore the last tab viewed from persistance variable storage - if (!mTabContainer->selectTab(gSavedSettings.getS32("LastMediaSettingsTab"))) - { - mTabContainer->selectFirstTab(); - }; - - sInstance = this; - - return true; -} - -//static -LLFloaterMediaSettings* LLFloaterMediaSettings::getInstance() -{ - if ( !sInstance ) - { - sInstance = (LLFloaterReg::getTypedInstance("media_settings")); - } - - return sInstance; -} - -//static -void LLFloaterMediaSettings::apply() -{ - if (sInstance->haveValuesChanged()) - { - LLSD settings; - sInstance->mPanelMediaSettingsGeneral->preApply(); - sInstance->mPanelMediaSettingsGeneral->getValues( settings, false ); - sInstance->mPanelMediaSettingsSecurity->preApply(); - sInstance->mPanelMediaSettingsSecurity->getValues( settings, false ); - sInstance->mPanelMediaSettingsPermissions->preApply(); - sInstance->mPanelMediaSettingsPermissions->getValues( settings, false ); - - LLSelectMgr::getInstance()->selectionSetMedia( LLTextureEntry::MF_HAS_MEDIA, settings ); - - sInstance->mPanelMediaSettingsGeneral->postApply(); - sInstance->mPanelMediaSettingsSecurity->postApply(); - sInstance->mPanelMediaSettingsPermissions->postApply(); - } -} - -//////////////////////////////////////////////////////////////////////////////// -void LLFloaterMediaSettings::onOpen(const LLSD& key) -{ - if (mPanelMediaSettingsGeneral) - { - // media is expensive, so only load it when nessesary. - // If we need to preload it, set volume to 0 and any pause - // if applicable, then unpause here - mPanelMediaSettingsGeneral->updateMediaPreview(); - } -} - -//////////////////////////////////////////////////////////////////////////////// -void LLFloaterMediaSettings::onClose(bool app_quitting) -{ - if(mPanelMediaSettingsGeneral) - { - mPanelMediaSettingsGeneral->onClose(app_quitting); - } - LLFloaterReg::hideInstance("whitelist_entry"); -} - -//////////////////////////////////////////////////////////////////////////////// -//static -void LLFloaterMediaSettings::initValues( const LLSD& media_settings, bool editable ) -{ - if (sInstance->hasFocus()) return; - - // Clear values - sInstance->mPanelMediaSettingsGeneral->clearValues(sInstance->mPanelMediaSettingsGeneral, editable, false /*don't update preview*/); - sInstance->mPanelMediaSettingsSecurity->clearValues(sInstance->mPanelMediaSettingsSecurity, editable); - sInstance->mPanelMediaSettingsPermissions->clearValues(sInstance->mPanelMediaSettingsPermissions, editable); - - // update all panels with values from simulator - sInstance->mPanelMediaSettingsGeneral-> - initValues( sInstance->mPanelMediaSettingsGeneral, media_settings, editable ); - - sInstance->mPanelMediaSettingsSecurity-> - initValues( sInstance->mPanelMediaSettingsSecurity, media_settings, editable ); - - sInstance->mPanelMediaSettingsPermissions-> - initValues( sInstance->mPanelMediaSettingsPermissions, media_settings, editable ); - - // Squirrel away initial values - sInstance->mInitialValues.clear(); - sInstance->mPanelMediaSettingsGeneral->getValues( sInstance->mInitialValues ); - sInstance->mPanelMediaSettingsSecurity->getValues( sInstance->mInitialValues ); - sInstance->mPanelMediaSettingsPermissions->getValues( sInstance->mInitialValues ); - - sInstance->mApplyBtn->setEnabled(editable); - sInstance->mOKBtn->setEnabled(editable); -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLFloaterMediaSettings::commitFields() -{ - if (hasFocus()) - { - LLUICtrl* cur_focus = dynamic_cast(gFocusMgr.getKeyboardFocus()); - if (cur_focus && cur_focus->acceptsTextInput()) - { - cur_focus->onCommit(); - }; - }; -} - -//////////////////////////////////////////////////////////////////////////////// -//static -void LLFloaterMediaSettings::clearValues( bool editable) -{ - if (sInstance) - { - // clean up all panels before updating - sInstance->mPanelMediaSettingsGeneral ->clearValues(sInstance->mPanelMediaSettingsGeneral, editable); - sInstance->mPanelMediaSettingsSecurity ->clearValues(sInstance->mPanelMediaSettingsSecurity, editable); - sInstance->mPanelMediaSettingsPermissions->clearValues(sInstance->mPanelMediaSettingsPermissions, editable); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// static -void LLFloaterMediaSettings::onBtnOK( void* userdata ) -{ - sInstance->commitFields(); - - sInstance->apply(); - - sInstance->closeFloater(); -} - -//////////////////////////////////////////////////////////////////////////////// -// static -void LLFloaterMediaSettings::onBtnApply( void* userdata ) -{ - sInstance->commitFields(); - - sInstance->apply(); - - sInstance->mInitialValues.clear(); - sInstance->mPanelMediaSettingsGeneral->getValues( sInstance->mInitialValues ); - sInstance->mPanelMediaSettingsSecurity->getValues( sInstance->mInitialValues ); - sInstance->mPanelMediaSettingsPermissions->getValues( sInstance->mInitialValues ); - -} - -//////////////////////////////////////////////////////////////////////////////// -// static -void LLFloaterMediaSettings::onBtnCancel( void* userdata ) -{ - sInstance->closeFloater(); -} - -//////////////////////////////////////////////////////////////////////////////// -// static -void LLFloaterMediaSettings::onTabChanged(void* user_data, bool from_click) -{ - LLTabContainer* self = (LLTabContainer*)user_data; - gSavedSettings.setS32("LastMediaSettingsTab", self->getCurrentPanelIndex()); -} -//////////////////////////////////////////////////////////////////////////////// -// -const std::string LLFloaterMediaSettings::getHomeUrl() -{ - if ( mPanelMediaSettingsGeneral ) - return mPanelMediaSettingsGeneral->getHomeUrl(); - else - return std::string( "" ); -} - -//////////////////////////////////////////////////////////////////////////////// -// virtual -void LLFloaterMediaSettings::draw() -{ - if (NULL != mApplyBtn) - { - // Set the enabled state of the "Apply" button if values changed - mApplyBtn->setEnabled( haveValuesChanged() ); - } - - LLFloater::draw(); -} - - -//private -bool LLFloaterMediaSettings::haveValuesChanged() const -{ - bool values_changed = false; - // *NOTE: The code below is very inefficient. Better to do this - // only when data change. - // Every frame, check to see what the values are. If they are not - // the same as the initial media data, enable the OK/Apply buttons - LLSD settings; - sInstance->mPanelMediaSettingsGeneral->getValues( settings ); - sInstance->mPanelMediaSettingsSecurity->getValues( settings ); - sInstance->mPanelMediaSettingsPermissions->getValues( settings ); - LLSD::map_const_iterator iter = settings.beginMap(); - LLSD::map_const_iterator end = settings.endMap(); - for ( ; iter != end; ++iter ) - { - const std::string ¤t_key = iter->first; - const LLSD ¤t_value = iter->second; - if ( ! llsd_equals(current_value, mInitialValues[current_key])) - { - values_changed = true; - break; - } - } - return values_changed; -} - -bool LLFloaterMediaSettings::instanceExists() -{ - return LLFloaterReg::findTypedInstance("media_settings"); -} - - +/** + * @file llfloatermediasettings.cpp + * @brief Tabbed dialog for media settings - class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterreg.h" +#include "llfloatermediasettings.h" +#include "llpanelmediasettingsgeneral.h" +#include "llpanelmediasettingssecurity.h" +#include "llpanelmediasettingspermissions.h" +#include "llviewercontrol.h" +#include "lluictrlfactory.h" +#include "llbutton.h" +#include "llselectmgr.h" +#include "llsdutil.h" + +LLFloaterMediaSettings* LLFloaterMediaSettings::sInstance = NULL; + +//////////////////////////////////////////////////////////////////////////////// +// +LLFloaterMediaSettings::LLFloaterMediaSettings(const LLSD& key) + : LLFloater(key), + mTabContainer(NULL), + mPanelMediaSettingsGeneral(NULL), + mPanelMediaSettingsSecurity(NULL), + mPanelMediaSettingsPermissions(NULL), + mIdenticalHasMediaInfo( true ), + mMultipleMedia(false), + mMultipleValidMedia(false) +{ +} + +//////////////////////////////////////////////////////////////////////////////// +// +LLFloaterMediaSettings::~LLFloaterMediaSettings() +{ + if ( mPanelMediaSettingsGeneral ) + { + delete mPanelMediaSettingsGeneral; + mPanelMediaSettingsGeneral = NULL; + } + + if ( mPanelMediaSettingsSecurity ) + { + delete mPanelMediaSettingsSecurity; + mPanelMediaSettingsSecurity = NULL; + } + + if ( mPanelMediaSettingsPermissions ) + { + delete mPanelMediaSettingsPermissions; + mPanelMediaSettingsPermissions = NULL; + } + + sInstance = NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLFloaterMediaSettings::postBuild() +{ + mApplyBtn = getChild("Apply"); + mApplyBtn->setClickedCallback(onBtnApply, this); + + mCancelBtn = getChild("Cancel"); + mCancelBtn->setClickedCallback(onBtnCancel, this); + + mOKBtn = getChild("OK"); + mOKBtn->setClickedCallback(onBtnOK, this); + + mTabContainer = getChild( "tab_container" ); + + mPanelMediaSettingsGeneral = new LLPanelMediaSettingsGeneral(); + mTabContainer->addTabPanel( + LLTabContainer::TabPanelParams(). + panel(mPanelMediaSettingsGeneral)); + mPanelMediaSettingsGeneral->setParent( this ); + + // note that "permissions" tab is really "Controls" tab - refs to 'perms' and + // 'permissions' not changed to 'controls' since we don't want to change + // shared files in server code and keeping everything the same seemed best. + mPanelMediaSettingsPermissions = new LLPanelMediaSettingsPermissions(); + mTabContainer->addTabPanel( + LLTabContainer::TabPanelParams(). + panel(mPanelMediaSettingsPermissions)); + + mPanelMediaSettingsSecurity = new LLPanelMediaSettingsSecurity(); + mTabContainer->addTabPanel( + LLTabContainer::TabPanelParams(). + panel(mPanelMediaSettingsSecurity)); + mPanelMediaSettingsSecurity->setParent( this ); + + // restore the last tab viewed from persistance variable storage + if (!mTabContainer->selectTab(gSavedSettings.getS32("LastMediaSettingsTab"))) + { + mTabContainer->selectFirstTab(); + }; + + sInstance = this; + + return true; +} + +//static +LLFloaterMediaSettings* LLFloaterMediaSettings::getInstance() +{ + if ( !sInstance ) + { + sInstance = (LLFloaterReg::getTypedInstance("media_settings")); + } + + return sInstance; +} + +//static +void LLFloaterMediaSettings::apply() +{ + if (sInstance->haveValuesChanged()) + { + LLSD settings; + sInstance->mPanelMediaSettingsGeneral->preApply(); + sInstance->mPanelMediaSettingsGeneral->getValues( settings, false ); + sInstance->mPanelMediaSettingsSecurity->preApply(); + sInstance->mPanelMediaSettingsSecurity->getValues( settings, false ); + sInstance->mPanelMediaSettingsPermissions->preApply(); + sInstance->mPanelMediaSettingsPermissions->getValues( settings, false ); + + LLSelectMgr::getInstance()->selectionSetMedia( LLTextureEntry::MF_HAS_MEDIA, settings ); + + sInstance->mPanelMediaSettingsGeneral->postApply(); + sInstance->mPanelMediaSettingsSecurity->postApply(); + sInstance->mPanelMediaSettingsPermissions->postApply(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +void LLFloaterMediaSettings::onOpen(const LLSD& key) +{ + if (mPanelMediaSettingsGeneral) + { + // media is expensive, so only load it when nessesary. + // If we need to preload it, set volume to 0 and any pause + // if applicable, then unpause here + mPanelMediaSettingsGeneral->updateMediaPreview(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +void LLFloaterMediaSettings::onClose(bool app_quitting) +{ + if(mPanelMediaSettingsGeneral) + { + mPanelMediaSettingsGeneral->onClose(app_quitting); + } + LLFloaterReg::hideInstance("whitelist_entry"); +} + +//////////////////////////////////////////////////////////////////////////////// +//static +void LLFloaterMediaSettings::initValues( const LLSD& media_settings, bool editable ) +{ + if (sInstance->hasFocus()) return; + + // Clear values + sInstance->mPanelMediaSettingsGeneral->clearValues(sInstance->mPanelMediaSettingsGeneral, editable, false /*don't update preview*/); + sInstance->mPanelMediaSettingsSecurity->clearValues(sInstance->mPanelMediaSettingsSecurity, editable); + sInstance->mPanelMediaSettingsPermissions->clearValues(sInstance->mPanelMediaSettingsPermissions, editable); + + // update all panels with values from simulator + sInstance->mPanelMediaSettingsGeneral-> + initValues( sInstance->mPanelMediaSettingsGeneral, media_settings, editable ); + + sInstance->mPanelMediaSettingsSecurity-> + initValues( sInstance->mPanelMediaSettingsSecurity, media_settings, editable ); + + sInstance->mPanelMediaSettingsPermissions-> + initValues( sInstance->mPanelMediaSettingsPermissions, media_settings, editable ); + + // Squirrel away initial values + sInstance->mInitialValues.clear(); + sInstance->mPanelMediaSettingsGeneral->getValues( sInstance->mInitialValues ); + sInstance->mPanelMediaSettingsSecurity->getValues( sInstance->mInitialValues ); + sInstance->mPanelMediaSettingsPermissions->getValues( sInstance->mInitialValues ); + + sInstance->mApplyBtn->setEnabled(editable); + sInstance->mOKBtn->setEnabled(editable); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLFloaterMediaSettings::commitFields() +{ + if (hasFocus()) + { + LLUICtrl* cur_focus = dynamic_cast(gFocusMgr.getKeyboardFocus()); + if (cur_focus && cur_focus->acceptsTextInput()) + { + cur_focus->onCommit(); + }; + }; +} + +//////////////////////////////////////////////////////////////////////////////// +//static +void LLFloaterMediaSettings::clearValues( bool editable) +{ + if (sInstance) + { + // clean up all panels before updating + sInstance->mPanelMediaSettingsGeneral ->clearValues(sInstance->mPanelMediaSettingsGeneral, editable); + sInstance->mPanelMediaSettingsSecurity ->clearValues(sInstance->mPanelMediaSettingsSecurity, editable); + sInstance->mPanelMediaSettingsPermissions->clearValues(sInstance->mPanelMediaSettingsPermissions, editable); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// static +void LLFloaterMediaSettings::onBtnOK( void* userdata ) +{ + sInstance->commitFields(); + + sInstance->apply(); + + sInstance->closeFloater(); +} + +//////////////////////////////////////////////////////////////////////////////// +// static +void LLFloaterMediaSettings::onBtnApply( void* userdata ) +{ + sInstance->commitFields(); + + sInstance->apply(); + + sInstance->mInitialValues.clear(); + sInstance->mPanelMediaSettingsGeneral->getValues( sInstance->mInitialValues ); + sInstance->mPanelMediaSettingsSecurity->getValues( sInstance->mInitialValues ); + sInstance->mPanelMediaSettingsPermissions->getValues( sInstance->mInitialValues ); + +} + +//////////////////////////////////////////////////////////////////////////////// +// static +void LLFloaterMediaSettings::onBtnCancel( void* userdata ) +{ + sInstance->closeFloater(); +} + +//////////////////////////////////////////////////////////////////////////////// +// static +void LLFloaterMediaSettings::onTabChanged(void* user_data, bool from_click) +{ + LLTabContainer* self = (LLTabContainer*)user_data; + gSavedSettings.setS32("LastMediaSettingsTab", self->getCurrentPanelIndex()); +} +//////////////////////////////////////////////////////////////////////////////// +// +const std::string LLFloaterMediaSettings::getHomeUrl() +{ + if ( mPanelMediaSettingsGeneral ) + return mPanelMediaSettingsGeneral->getHomeUrl(); + else + return std::string( "" ); +} + +//////////////////////////////////////////////////////////////////////////////// +// virtual +void LLFloaterMediaSettings::draw() +{ + if (NULL != mApplyBtn) + { + // Set the enabled state of the "Apply" button if values changed + mApplyBtn->setEnabled( haveValuesChanged() ); + } + + LLFloater::draw(); +} + + +//private +bool LLFloaterMediaSettings::haveValuesChanged() const +{ + bool values_changed = false; + // *NOTE: The code below is very inefficient. Better to do this + // only when data change. + // Every frame, check to see what the values are. If they are not + // the same as the initial media data, enable the OK/Apply buttons + LLSD settings; + sInstance->mPanelMediaSettingsGeneral->getValues( settings ); + sInstance->mPanelMediaSettingsSecurity->getValues( settings ); + sInstance->mPanelMediaSettingsPermissions->getValues( settings ); + LLSD::map_const_iterator iter = settings.beginMap(); + LLSD::map_const_iterator end = settings.endMap(); + for ( ; iter != end; ++iter ) + { + const std::string ¤t_key = iter->first; + const LLSD ¤t_value = iter->second; + if ( ! llsd_equals(current_value, mInitialValues[current_key])) + { + values_changed = true; + break; + } + } + return values_changed; +} + +bool LLFloaterMediaSettings::instanceExists() +{ + return LLFloaterReg::findTypedInstance("media_settings"); +} + + diff --git a/indra/newview/llfloatermediasettings.h b/indra/newview/llfloatermediasettings.h index bee392f335..38730ddc98 100644 --- a/indra/newview/llfloatermediasettings.h +++ b/indra/newview/llfloatermediasettings.h @@ -1,89 +1,89 @@ -/** - * @file llfloatermediasettings.cpp - * @brief Tabbed dialog for media settings - class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERMEDIASETTINGS_H -#define LL_LLFLOATERMEDIASETTINGS_H - -#include "llfloater.h" -#include "lltabcontainer.h" - -class LLPanelMediaSettingsGeneral; -class LLPanelMediaSettingsSecurity; -class LLPanelMediaSettingsPermissions; - -class LLFloaterMediaSettings : - public LLFloater -{ -public: - LLFloaterMediaSettings(const LLSD& key); - ~LLFloaterMediaSettings(); - - bool postBuild() override; - void onOpen(const LLSD& key) override; - void onClose(bool app_quitting) override; - - static LLFloaterMediaSettings* getInstance(); - static bool instanceExists(); - static void apply(); - static void initValues( const LLSD& media_settings , bool editable); - static void clearValues( bool editable); - - LLPanelMediaSettingsSecurity* getPanelSecurity(){return mPanelMediaSettingsSecurity;}; - const std::string getHomeUrl(); - //bool passesWhiteList( const std::string& test_url ); - - virtual void draw() override; - - bool mIdenticalHasMediaInfo; - bool mMultipleMedia; - bool mMultipleValidMedia; - -protected: - LLButton *mOKBtn; - LLButton *mCancelBtn; - LLButton *mApplyBtn; - - LLTabContainer *mTabContainer; - LLPanelMediaSettingsGeneral* mPanelMediaSettingsGeneral; - LLPanelMediaSettingsSecurity* mPanelMediaSettingsSecurity; - LLPanelMediaSettingsPermissions* mPanelMediaSettingsPermissions; - - static void onBtnOK(void*); - static void onBtnCancel(void*); - static void onBtnApply(void*); - static void onTabChanged(void* user_data, bool from_click); - void commitFields(); - - static LLFloaterMediaSettings* sInstance; - -private: - - bool haveValuesChanged() const; - - LLSD mInitialValues; -}; - -#endif // LL_LLFLOATERMEDIASETTINGS_H +/** + * @file llfloatermediasettings.cpp + * @brief Tabbed dialog for media settings - class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERMEDIASETTINGS_H +#define LL_LLFLOATERMEDIASETTINGS_H + +#include "llfloater.h" +#include "lltabcontainer.h" + +class LLPanelMediaSettingsGeneral; +class LLPanelMediaSettingsSecurity; +class LLPanelMediaSettingsPermissions; + +class LLFloaterMediaSettings : + public LLFloater +{ +public: + LLFloaterMediaSettings(const LLSD& key); + ~LLFloaterMediaSettings(); + + bool postBuild() override; + void onOpen(const LLSD& key) override; + void onClose(bool app_quitting) override; + + static LLFloaterMediaSettings* getInstance(); + static bool instanceExists(); + static void apply(); + static void initValues( const LLSD& media_settings , bool editable); + static void clearValues( bool editable); + + LLPanelMediaSettingsSecurity* getPanelSecurity(){return mPanelMediaSettingsSecurity;}; + const std::string getHomeUrl(); + //bool passesWhiteList( const std::string& test_url ); + + virtual void draw() override; + + bool mIdenticalHasMediaInfo; + bool mMultipleMedia; + bool mMultipleValidMedia; + +protected: + LLButton *mOKBtn; + LLButton *mCancelBtn; + LLButton *mApplyBtn; + + LLTabContainer *mTabContainer; + LLPanelMediaSettingsGeneral* mPanelMediaSettingsGeneral; + LLPanelMediaSettingsSecurity* mPanelMediaSettingsSecurity; + LLPanelMediaSettingsPermissions* mPanelMediaSettingsPermissions; + + static void onBtnOK(void*); + static void onBtnCancel(void*); + static void onBtnApply(void*); + static void onTabChanged(void* user_data, bool from_click); + void commitFields(); + + static LLFloaterMediaSettings* sInstance; + +private: + + bool haveValuesChanged() const; + + LLSD mInitialValues; +}; + +#endif // LL_LLFLOATERMEDIASETTINGS_H diff --git a/indra/newview/llfloatermemleak.cpp b/indra/newview/llfloatermemleak.cpp index 68425116c6..cd5bea1be4 100644 --- a/indra/newview/llfloatermemleak.cpp +++ b/indra/newview/llfloatermemleak.cpp @@ -1,230 +1,230 @@ -/** - * @file llfloatermemleak.cpp - * @brief LLFloatermemleak class definition - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloatermemleak.h" - -#include "lluictrlfactory.h" -#include "llbutton.h" -#include "llspinctrl.h" -#include "llresmgr.h" - -#include "llmath.h" -#include "llviewerwindow.h" - -U32 LLFloaterMemLeak::sMemLeakingSpeed = 0 ; //bytes leaked per frame -U32 LLFloaterMemLeak::sMaxLeakedMem = 0 ; //maximum allowed leaked memory -U32 LLFloaterMemLeak::sTotalLeaked = 0 ; -S32 LLFloaterMemLeak::sStatus = LLFloaterMemLeak::STOP ; -bool LLFloaterMemLeak::sbAllocationFailed = false ; - -extern bool gSimulateMemLeak; - -LLFloaterMemLeak::LLFloaterMemLeak(const LLSD& key) - : LLFloater(key) -{ - setTitle("Memory Leaking Simulation Floater"); - mCommitCallbackRegistrar.add("MemLeak.ChangeLeakingSpeed", boost::bind(&LLFloaterMemLeak::onChangeLeakingSpeed, this)); - mCommitCallbackRegistrar.add("MemLeak.ChangeMaxMemLeaking", boost::bind(&LLFloaterMemLeak::onChangeMaxMemLeaking, this)); - mCommitCallbackRegistrar.add("MemLeak.Start", boost::bind(&LLFloaterMemLeak::onClickStart, this)); - mCommitCallbackRegistrar.add("MemLeak.Stop", boost::bind(&LLFloaterMemLeak::onClickStop, this)); - mCommitCallbackRegistrar.add("MemLeak.Release", boost::bind(&LLFloaterMemLeak::onClickRelease, this)); - mCommitCallbackRegistrar.add("MemLeak.Close", boost::bind(&LLFloaterMemLeak::onClickClose, this)); -} -//---------------------------------------------- - -bool LLFloaterMemLeak::postBuild(void) -{ - F32 a, b ; - a = getChild("leak_speed")->getValue().asReal(); - if(a > (F32)(0xFFFFFFFF)) - { - sMemLeakingSpeed = 0xFFFFFFFF ; - } - else - { - sMemLeakingSpeed = (U32)a ; - } - b = getChild("max_leak")->getValue().asReal(); - if(b > (F32)0xFFF) - { - sMaxLeakedMem = 0xFFFFFFFF ; - } - else - { - sMaxLeakedMem = ((U32)b) << 20 ; - } - - sbAllocationFailed = false ; - return true ; -} -LLFloaterMemLeak::~LLFloaterMemLeak() -{ - release() ; - - sMemLeakingSpeed = 0 ; //bytes leaked per frame - sMaxLeakedMem = 0 ; //maximum allowed leaked memory -} - -void LLFloaterMemLeak::release() -{ - if(mLeakedMem.empty()) - { - return ; - } - - for(S32 i = 0 ; i < (S32)mLeakedMem.size() ; i++) - { - delete[] mLeakedMem[i] ; - } - mLeakedMem.clear() ; - - sStatus = STOP ; - sTotalLeaked = 0 ; - sbAllocationFailed = false ; - gSimulateMemLeak = false; -} - -void LLFloaterMemLeak::stop() -{ - sStatus = STOP ; - sbAllocationFailed = true ; -} - -void LLFloaterMemLeak::idle() -{ - if(STOP == sStatus) - { - return ; - } - - sbAllocationFailed = false ; - - if(RELEASE == sStatus) - { - release() ; - return ; - } - - char* p = NULL ; - if(sMemLeakingSpeed > 0 && sTotalLeaked < sMaxLeakedMem) - { - p = new char[sMemLeakingSpeed] ; - - if(p) - { - mLeakedMem.push_back(p) ; - sTotalLeaked += sMemLeakingSpeed ; - } - } - if(!p) - { - stop(); - } -} - -//---------------------- -void LLFloaterMemLeak::onChangeLeakingSpeed() -{ - F32 tmp ; - tmp =getChild("leak_speed")->getValue().asReal(); - - if(tmp > (F32)0xFFFFFFFF) - { - sMemLeakingSpeed = 0xFFFFFFFF ; - } - else - { - sMemLeakingSpeed = (U32)tmp ; - } - -} - -void LLFloaterMemLeak::onChangeMaxMemLeaking() -{ - - F32 tmp ; - tmp =getChild("max_leak")->getValue().asReal(); - if(tmp > (F32)0xFFF) - { - sMaxLeakedMem = 0xFFFFFFFF ; - } - else - { - sMaxLeakedMem = ((U32)tmp) << 20 ; - } - -} - -void LLFloaterMemLeak::onClickStart() -{ - sStatus = START ; - gSimulateMemLeak = true; -} - -void LLFloaterMemLeak::onClickStop() -{ - sStatus = STOP ; -} - -void LLFloaterMemLeak::onClickRelease() -{ - sStatus = RELEASE ; -} - -void LLFloaterMemLeak::onClickClose() -{ - setVisible(false); -} - -void LLFloaterMemLeak::draw() -{ - //show total memory leaked - if(sTotalLeaked > 0) - { - std::string bytes_string; - LLResMgr::getInstance()->getIntegerString(bytes_string, sTotalLeaked >> 10 ); - getChild("total_leaked_label")->setTextArg("[SIZE]", bytes_string); - } - else - { - getChild("total_leaked_label")->setTextArg("[SIZE]", LLStringExplicit("0")); - } - - if(sbAllocationFailed) - { - getChild("note_label_1")->setTextArg("[NOTE1]", LLStringExplicit("Memory leaking simulation stops. Reduce leaking speed or")); - getChild("note_label_2")->setTextArg("[NOTE2]", LLStringExplicit("increase max leaked memory, then press Start to continue.")); - } - else - { - getChild("note_label_1")->setTextArg("[NOTE1]", LLStringExplicit("")); - getChild("note_label_2")->setTextArg("[NOTE2]", LLStringExplicit("")); - } - - LLFloater::draw(); -} +/** + * @file llfloatermemleak.cpp + * @brief LLFloatermemleak class definition + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatermemleak.h" + +#include "lluictrlfactory.h" +#include "llbutton.h" +#include "llspinctrl.h" +#include "llresmgr.h" + +#include "llmath.h" +#include "llviewerwindow.h" + +U32 LLFloaterMemLeak::sMemLeakingSpeed = 0 ; //bytes leaked per frame +U32 LLFloaterMemLeak::sMaxLeakedMem = 0 ; //maximum allowed leaked memory +U32 LLFloaterMemLeak::sTotalLeaked = 0 ; +S32 LLFloaterMemLeak::sStatus = LLFloaterMemLeak::STOP ; +bool LLFloaterMemLeak::sbAllocationFailed = false ; + +extern bool gSimulateMemLeak; + +LLFloaterMemLeak::LLFloaterMemLeak(const LLSD& key) + : LLFloater(key) +{ + setTitle("Memory Leaking Simulation Floater"); + mCommitCallbackRegistrar.add("MemLeak.ChangeLeakingSpeed", boost::bind(&LLFloaterMemLeak::onChangeLeakingSpeed, this)); + mCommitCallbackRegistrar.add("MemLeak.ChangeMaxMemLeaking", boost::bind(&LLFloaterMemLeak::onChangeMaxMemLeaking, this)); + mCommitCallbackRegistrar.add("MemLeak.Start", boost::bind(&LLFloaterMemLeak::onClickStart, this)); + mCommitCallbackRegistrar.add("MemLeak.Stop", boost::bind(&LLFloaterMemLeak::onClickStop, this)); + mCommitCallbackRegistrar.add("MemLeak.Release", boost::bind(&LLFloaterMemLeak::onClickRelease, this)); + mCommitCallbackRegistrar.add("MemLeak.Close", boost::bind(&LLFloaterMemLeak::onClickClose, this)); +} +//---------------------------------------------- + +bool LLFloaterMemLeak::postBuild(void) +{ + F32 a, b ; + a = getChild("leak_speed")->getValue().asReal(); + if(a > (F32)(0xFFFFFFFF)) + { + sMemLeakingSpeed = 0xFFFFFFFF ; + } + else + { + sMemLeakingSpeed = (U32)a ; + } + b = getChild("max_leak")->getValue().asReal(); + if(b > (F32)0xFFF) + { + sMaxLeakedMem = 0xFFFFFFFF ; + } + else + { + sMaxLeakedMem = ((U32)b) << 20 ; + } + + sbAllocationFailed = false ; + return true ; +} +LLFloaterMemLeak::~LLFloaterMemLeak() +{ + release() ; + + sMemLeakingSpeed = 0 ; //bytes leaked per frame + sMaxLeakedMem = 0 ; //maximum allowed leaked memory +} + +void LLFloaterMemLeak::release() +{ + if(mLeakedMem.empty()) + { + return ; + } + + for(S32 i = 0 ; i < (S32)mLeakedMem.size() ; i++) + { + delete[] mLeakedMem[i] ; + } + mLeakedMem.clear() ; + + sStatus = STOP ; + sTotalLeaked = 0 ; + sbAllocationFailed = false ; + gSimulateMemLeak = false; +} + +void LLFloaterMemLeak::stop() +{ + sStatus = STOP ; + sbAllocationFailed = true ; +} + +void LLFloaterMemLeak::idle() +{ + if(STOP == sStatus) + { + return ; + } + + sbAllocationFailed = false ; + + if(RELEASE == sStatus) + { + release() ; + return ; + } + + char* p = NULL ; + if(sMemLeakingSpeed > 0 && sTotalLeaked < sMaxLeakedMem) + { + p = new char[sMemLeakingSpeed] ; + + if(p) + { + mLeakedMem.push_back(p) ; + sTotalLeaked += sMemLeakingSpeed ; + } + } + if(!p) + { + stop(); + } +} + +//---------------------- +void LLFloaterMemLeak::onChangeLeakingSpeed() +{ + F32 tmp ; + tmp =getChild("leak_speed")->getValue().asReal(); + + if(tmp > (F32)0xFFFFFFFF) + { + sMemLeakingSpeed = 0xFFFFFFFF ; + } + else + { + sMemLeakingSpeed = (U32)tmp ; + } + +} + +void LLFloaterMemLeak::onChangeMaxMemLeaking() +{ + + F32 tmp ; + tmp =getChild("max_leak")->getValue().asReal(); + if(tmp > (F32)0xFFF) + { + sMaxLeakedMem = 0xFFFFFFFF ; + } + else + { + sMaxLeakedMem = ((U32)tmp) << 20 ; + } + +} + +void LLFloaterMemLeak::onClickStart() +{ + sStatus = START ; + gSimulateMemLeak = true; +} + +void LLFloaterMemLeak::onClickStop() +{ + sStatus = STOP ; +} + +void LLFloaterMemLeak::onClickRelease() +{ + sStatus = RELEASE ; +} + +void LLFloaterMemLeak::onClickClose() +{ + setVisible(false); +} + +void LLFloaterMemLeak::draw() +{ + //show total memory leaked + if(sTotalLeaked > 0) + { + std::string bytes_string; + LLResMgr::getInstance()->getIntegerString(bytes_string, sTotalLeaked >> 10 ); + getChild("total_leaked_label")->setTextArg("[SIZE]", bytes_string); + } + else + { + getChild("total_leaked_label")->setTextArg("[SIZE]", LLStringExplicit("0")); + } + + if(sbAllocationFailed) + { + getChild("note_label_1")->setTextArg("[NOTE1]", LLStringExplicit("Memory leaking simulation stops. Reduce leaking speed or")); + getChild("note_label_2")->setTextArg("[NOTE2]", LLStringExplicit("increase max leaked memory, then press Start to continue.")); + } + else + { + getChild("note_label_1")->setTextArg("[NOTE1]", LLStringExplicit("")); + getChild("note_label_2")->setTextArg("[NOTE2]", LLStringExplicit("")); + } + + LLFloater::draw(); +} diff --git a/indra/newview/llfloatermemleak.h b/indra/newview/llfloatermemleak.h index 99f7c0beab..810c946e61 100644 --- a/indra/newview/llfloatermemleak.h +++ b/indra/newview/llfloatermemleak.h @@ -1,75 +1,75 @@ -/** - * @file llfloatermemleak.h - * @brief memory leaking simulation window, debug use only - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERMEMLEAK_H -#define LL_LLFLOATERMEMLEAK_H - -#include "llfloater.h" - -class LLFloaterMemLeak : public LLFloater -{ - friend class LLFloaterReg; -public: - /// initialize all the callbacks for the menu - - bool postBuild() override; - void draw() override; - - void onChangeLeakingSpeed(); - void onChangeMaxMemLeaking(); - void onClickStart(); - void onClickStop(); - void onClickRelease(); - void onClickClose(); - -public: - void idle() ; - void stop() ; - -private: - - LLFloaterMemLeak(const LLSD& key); - virtual ~LLFloaterMemLeak(); - void release() ; - -private: - enum - { - RELEASE = -1 , - STOP, - START - } ; - - static U32 sMemLeakingSpeed ; //bytes leaked per frame - static U32 sMaxLeakedMem ; //maximum allowed leaked memory - static U32 sTotalLeaked ; - static S32 sStatus ; //0: stop ; >0: start ; <0: release - static bool sbAllocationFailed ; - - std::vector mLeakedMem ; -}; - -#endif // LL_LLFLOATERMEMLEAK_H +/** + * @file llfloatermemleak.h + * @brief memory leaking simulation window, debug use only + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERMEMLEAK_H +#define LL_LLFLOATERMEMLEAK_H + +#include "llfloater.h" + +class LLFloaterMemLeak : public LLFloater +{ + friend class LLFloaterReg; +public: + /// initialize all the callbacks for the menu + + bool postBuild() override; + void draw() override; + + void onChangeLeakingSpeed(); + void onChangeMaxMemLeaking(); + void onClickStart(); + void onClickStop(); + void onClickRelease(); + void onClickClose(); + +public: + void idle() ; + void stop() ; + +private: + + LLFloaterMemLeak(const LLSD& key); + virtual ~LLFloaterMemLeak(); + void release() ; + +private: + enum + { + RELEASE = -1 , + STOP, + START + } ; + + static U32 sMemLeakingSpeed ; //bytes leaked per frame + static U32 sMaxLeakedMem ; //maximum allowed leaked memory + static U32 sTotalLeaked ; + static S32 sStatus ; //0: stop ; >0: start ; <0: release + static bool sbAllocationFailed ; + + std::vector mLeakedMem ; +}; + +#endif // LL_LLFLOATERMEMLEAK_H diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index df9ddd738c..3a0e6dc05b 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -1,1985 +1,1985 @@ -/** - * @file llfloatermodelpreview.cpp - * @brief LLFloaterModelPreview class implementation - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llmodelloader.h" -#include "llmodelpreview.h" - -#include "llfloatermodelpreview.h" - -#include "llfilepicker.h" -#include "llimagebmp.h" -#include "llimagetga.h" -#include "llimagejpeg.h" -#include "llimagepng.h" - -#include "llagent.h" -#include "llbutton.h" -#include "llcombobox.h" -#include "llfloaterreg.h" -#include "llfocusmgr.h" -#include "llmeshrepository.h" -#include "llnotificationsutil.h" -#include "llsdutil_math.h" -#include "llskinningutil.h" -#include "lltextbox.h" -#include "lltoolmgr.h" -#include "llui.h" -#include "llviewerwindow.h" -#include "pipeline.h" -#include "llviewercontrol.h" -#include "llviewermenufile.h" //LLFilePickerThread -#include "llstring.h" -#include "llbutton.h" -#include "llcheckboxctrl.h" -#include "llsliderctrl.h" -#include "llspinctrl.h" -#include "lltabcontainer.h" -#include "lltrans.h" -#include "llfilesystem.h" -#include "llcallbacklist.h" -#include "llviewertexteditor.h" -#include "llviewernetwork.h" - - -//static -S32 LLFloaterModelPreview::sUploadAmount = 10; -LLFloaterModelPreview* LLFloaterModelPreview::sInstance = NULL; - -// "Retain%" decomp parameter has values from 0.0 to 1.0 by 0.01 -// But according to the UI spec for upload model floater, this parameter -// should be represented by Retain spinner with values from 1 to 100 by 1. -// To achieve this, RETAIN_COEFFICIENT is used while creating spinner -// and when value is requested from spinner. -constexpr double RETAIN_COEFFICIENT = 100; - -// "Cosine%" decomp parameter has values from 0.9 to 1 by 0.001 -// But according to the UI spec for upload model floater, this parameter -// should be represented by Smooth combobox with only 10 values. -// So this const is used as a size of Smooth combobox list. -constexpr S32 SMOOTH_VALUES_NUMBER = 10; -constexpr S32 PREVIEW_RENDER_SIZE = 1024; -constexpr F32 PREVIEW_CAMERA_DISTANCE = 16.f; - -class LLMeshFilePicker : public LLFilePickerThread -{ -public: - LLMeshFilePicker(LLModelPreview* mp, S32 lod); - virtual void notify(const std::vector& filenames); - -private: - LLModelPreview* mMP; - S32 mLOD; -}; - -LLMeshFilePicker::LLMeshFilePicker(LLModelPreview* mp, S32 lod) -: LLFilePickerThread(LLFilePicker::FFLOAD_MODEL) - { - mMP = mp; - mLOD = lod; - } - -void LLMeshFilePicker::notify(const std::vector& filenames) -{ - if(LLAppViewer::instance()->quitRequested()) - { - return; - } - - if (filenames.size() > 0) - { - mMP->loadModel(filenames[0], mLOD); - } - else - { - //closes floater - mMP->loadModel(std::string(), mLOD); - } -} - -//----------------------------------------------------------------------------- -// LLFloaterModelPreview() -//----------------------------------------------------------------------------- -LLFloaterModelPreview::LLFloaterModelPreview(const LLSD& key) : -LLFloaterModelUploadBase(key), -mUploadBtn(NULL), -mCalculateBtn(NULL), -mUploadLogText(NULL), -mTabContainer(NULL), -mAvatarTabIndex(0) -{ - sInstance = this; - mLastMouseX = 0; - mLastMouseY = 0; - mStatusLock = new LLMutex(); - mModelPreview = NULL; - - mLODMode[LLModel::LOD_HIGH] = LLModelPreview::LOD_FROM_FILE; - for (U32 i = 0; i < LLModel::LOD_HIGH; i++) - { - mLODMode[i] = LLModelPreview::MESH_OPTIMIZER_AUTO; - } -} - -//----------------------------------------------------------------------------- -// postBuild() -//----------------------------------------------------------------------------- -bool LLFloaterModelPreview::postBuild() -{ - if (!LLFloater::postBuild()) - { - return false; - } - - childSetCommitCallback("cancel_btn", onCancel, this); - childSetCommitCallback("crease_angle", onGenerateNormalsCommit, this); - getChild("gen_normals")->setCommitCallback(boost::bind(&LLFloaterModelPreview::toggleGenarateNormals, this)); - - childSetCommitCallback("lod_generate", onAutoFillCommit, this); - - for (S32 lod = 0; lod <= LLModel::LOD_HIGH; ++lod) - { - LLComboBox* lod_source_combo = getChild("lod_source_" + lod_name[lod]); - lod_source_combo->setCommitCallback(boost::bind(&LLFloaterModelPreview::onLoDSourceCommit, this, lod)); - lod_source_combo->setCurrentByIndex(mLODMode[lod]); - - getChild("lod_browse_" + lod_name[lod])->setCommitCallback(boost::bind(&LLFloaterModelPreview::onBrowseLOD, this, lod)); - getChild("lod_mode_" + lod_name[lod])->setCommitCallback(boost::bind(&LLFloaterModelPreview::onLODParamCommit, this, lod, false)); - getChild("lod_error_threshold_" + lod_name[lod])->setCommitCallback(boost::bind(&LLFloaterModelPreview::onLODParamCommit, this, lod, false)); - getChild("lod_triangle_limit_" + lod_name[lod])->setCommitCallback(boost::bind(&LLFloaterModelPreview::onLODParamCommit, this, lod, true)); - } - - // Upload/avatar options, they need to refresh errors/notifications - childSetCommitCallback("upload_skin", boost::bind(&LLFloaterModelPreview::onUploadOptionChecked, this, _1), NULL); - childSetCommitCallback("upload_joints", boost::bind(&LLFloaterModelPreview::onUploadOptionChecked, this, _1), NULL); - childSetCommitCallback("lock_scale_if_joint_position", boost::bind(&LLFloaterModelPreview::onUploadOptionChecked, this, _1), NULL); - childSetCommitCallback("upload_textures", boost::bind(&LLFloaterModelPreview::onUploadOptionChecked, this, _1), NULL); - - childSetTextArg("status", "[STATUS]", getString("status_idle")); - - childSetAction("ok_btn", onUpload, this); - childDisable("ok_btn"); - - childSetAction("reset_btn", onReset, this); - - childSetCommitCallback("preview_lod_combo", onPreviewLODCommit, this); - - childSetCommitCallback("import_scale", onImportScaleCommit, this); - childSetCommitCallback("pelvis_offset", onPelvisOffsetCommit, this); - - getChild("description_form")->setKeystrokeCallback(boost::bind(&LLFloaterModelPreview::onDescriptionKeystroke, this, _1), NULL); - - getChild("show_edges")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onViewOptionChecked, this, _1)); - getChild("show_physics")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onViewOptionChecked, this, _1)); - getChild("show_textures")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onViewOptionChecked, this, _1)); - getChild("show_skin_weight")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onShowSkinWeightChecked, this, _1)); - getChild("show_joint_overrides")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onViewOptionChecked, this, _1)); - getChild("show_joint_positions")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onViewOptionChecked, this, _1)); - - childDisable("upload_skin"); - childDisable("upload_joints"); - childDisable("lock_scale_if_joint_position"); - - childSetVisible("skin_too_many_joints", false); - childSetVisible("skin_unknown_joint", false); - - childSetVisible("warning_title", false); - childSetVisible("warning_message", false); - - initDecompControls(); - - LLView* preview_panel = getChild("preview_panel"); - - mPreviewRect = preview_panel->getRect(); - - initModelPreview(); - - //set callbacks for left click on line editor rows - for (U32 i = 0; i <= LLModel::LOD_HIGH; i++) - { - LLTextBox* text = getChild(lod_label_name[i]); - if (text) - { - text->setMouseDownCallback(boost::bind(&LLFloaterModelPreview::setPreviewLOD, this, i)); - } - - text = getChild(lod_triangles_name[i]); - if (text) - { - text->setMouseDownCallback(boost::bind(&LLFloaterModelPreview::setPreviewLOD, this, i)); - } - - text = getChild(lod_vertices_name[i]); - if (text) - { - text->setMouseDownCallback(boost::bind(&LLFloaterModelPreview::setPreviewLOD, this, i)); - } - - text = getChild(lod_status_name[i]); - if (text) - { - text->setMouseDownCallback(boost::bind(&LLFloaterModelPreview::setPreviewLOD, this, i)); - } - } - std::string current_grid = LLGridManager::getInstance()->getGridId(); - std::transform(current_grid.begin(),current_grid.end(),current_grid.begin(),::tolower); - std::string validate_url; - if (current_grid == "agni") - { - validate_url = "http://secondlife.com/my/account/mesh.php"; - } - else if (current_grid == "damballah") - { - // Staging grid has its own naming scheme. - validate_url = "http://secondlife-staging.com/my/account/mesh.php"; - } - else - { - validate_url = llformat("http://secondlife.%s.lindenlab.com/my/account/mesh.php",current_grid.c_str()); - } - getChild("warning_message")->setTextArg("[VURL]", validate_url); - - mUploadBtn = getChild("ok_btn"); - mCalculateBtn = getChild("calculate_btn"); - mUploadLogText = getChild("log_text"); - mTabContainer = getChild("import_tab"); - - LLPanel *panel = mTabContainer->getPanelByName("rigging_panel"); - mAvatarTabIndex = mTabContainer->getIndexForPanel(panel); - panel->getChild("joints_list")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onJointListSelection, this)); - - if (LLConvexDecomposition::getInstance() != NULL) - { - mCalculateBtn->setClickedCallback(boost::bind(&LLFloaterModelPreview::onClickCalculateBtn, this)); - - toggleCalculateButton(true); - } - else - { - mCalculateBtn->setEnabled(false); - } - - return true; -} - -//----------------------------------------------------------------------------- -// reshape() -//----------------------------------------------------------------------------- - -void LLFloaterModelPreview::reshape(S32 width, S32 height, bool called_from_parent) -{ - LLFloaterModelUploadBase::reshape(width, height, called_from_parent); - - LLView* preview_panel = getChild("preview_panel"); - LLRect rect = preview_panel->getRect(); - - if (rect != mPreviewRect) - { - mModelPreview->refresh(); - mPreviewRect = preview_panel->getRect(); - } -} - -//----------------------------------------------------------------------------- -// LLFloaterModelPreview() -//----------------------------------------------------------------------------- -LLFloaterModelPreview::~LLFloaterModelPreview() -{ - sInstance = NULL; - - if ( mModelPreview ) - { - delete mModelPreview; - } - - delete mStatusLock; - mStatusLock = NULL; -} - -void LLFloaterModelPreview::initModelPreview() -{ - if (mModelPreview) - { - delete mModelPreview; - } - - S32 tex_width = 512; - S32 tex_height = 512; - - S32 max_width = llmin(PREVIEW_RENDER_SIZE, (S32)gPipeline.mRT->width); - S32 max_height = llmin(PREVIEW_RENDER_SIZE, (S32)gPipeline.mRT->height); - - while ((tex_width << 1) < max_width) - { - tex_width <<= 1; - } - while ((tex_height << 1) < max_height) - { - tex_height <<= 1; - } - - mModelPreview = new LLModelPreview(tex_width, tex_height, this); - mModelPreview->setPreviewTarget(PREVIEW_CAMERA_DISTANCE); - mModelPreview->setDetailsCallback(boost::bind(&LLFloaterModelPreview::setDetails, this, _1, _2, _3)); - mModelPreview->setModelUpdatedCallback(boost::bind(&LLFloaterModelPreview::modelUpdated, this, _1)); -} - -//static -bool LLFloaterModelPreview::showModelPreview() -{ - LLFloaterModelPreview* fmp = (LLFloaterModelPreview*)LLFloaterReg::getInstance("upload_model"); - if (fmp && !fmp->isModelLoading()) - { - fmp->loadHighLodModel(); - } - return true; -} - -void LLFloaterModelPreview::onUploadOptionChecked(LLUICtrl* ctrl) -{ - if (mModelPreview) - { - auto name = ctrl->getName(); - bool value = ctrl->getValue().asBoolean(); - // update the option and notifications - // (this is a bit convoluted, because of the current structure of mModelPreview) - if (name == "upload_skin") - { - childSetValue("show_skin_weight", value); - mModelPreview->mViewOption["show_skin_weight"] = value; - if (!value) - { - mModelPreview->mViewOption["show_joint_overrides"] = false; - mModelPreview->mViewOption["show_joint_positions"] = false; - childSetValue("show_joint_overrides", false); - childSetValue("show_joint_positions", false); - } - } - else if (name == "upload_joints") - { - if (mModelPreview->mViewOption["show_skin_weight"]) - { - childSetValue("show_joint_overrides", value); - mModelPreview->mViewOption["show_joint_overrides"] = value; - } - } - else if (name == "upload_textures") - { - childSetValue("show_textures", value); - mModelPreview->mViewOption["show_textures"] = value; - } - else if (name == "lock_scale_if_joint_position") - { - mModelPreview->mViewOption["lock_scale_if_joint_position"] = value; - } - - mModelPreview->refresh(); // a 'dirty' flag for render - mModelPreview->resetPreviewTarget(); - mModelPreview->clearBuffers(); - mModelPreview->mDirty = true; - } - // set the button visible, it will be refreshed later - toggleCalculateButton(true); -} - -void LLFloaterModelPreview::onShowSkinWeightChecked(LLUICtrl* ctrl) -{ - if (mModelPreview) - { - mModelPreview->mCameraOffset.clearVec(); - onViewOptionChecked(ctrl); - } -} - -void LLFloaterModelPreview::onViewOptionChecked(LLUICtrl* ctrl) -{ - if (mModelPreview) - { - auto name = ctrl->getName(); - mModelPreview->mViewOption[name] = !mModelPreview->mViewOption[name]; - if (name == "show_physics") - { - auto enabled = mModelPreview->mViewOption[name]; - childSetEnabled("physics_explode", enabled); - childSetVisible("physics_explode", enabled); - } - mModelPreview->refresh(); - } -} - -bool LLFloaterModelPreview::isViewOptionChecked(const LLSD& userdata) -{ - if (mModelPreview) - { - return mModelPreview->mViewOption[userdata.asString()]; - } - - return false; -} - -bool LLFloaterModelPreview::isViewOptionEnabled(const LLSD& userdata) -{ - return getChildView(userdata.asString())->getEnabled(); -} - -void LLFloaterModelPreview::setViewOptionEnabled(const std::string& option, bool enabled) -{ - childSetEnabled(option, enabled); -} - -void LLFloaterModelPreview::enableViewOption(const std::string& option) -{ - setViewOptionEnabled(option, true); -} - -void LLFloaterModelPreview::disableViewOption(const std::string& option) -{ - setViewOptionEnabled(option, false); -} - -void LLFloaterModelPreview::loadHighLodModel() -{ - mModelPreview->mLookUpLodFiles = true; - loadModel(3); -} - -void LLFloaterModelPreview::prepareToLoadModel(S32 lod) -{ - mModelPreview->mLoading = true; - if (lod == LLModel::LOD_PHYSICS) - { - // loading physics from file - mModelPreview->mPhysicsSearchLOD = lod; - mModelPreview->mWarnOfUnmatchedPhyicsMeshes = false; - } -} -void LLFloaterModelPreview::loadModel(S32 lod) -{ - prepareToLoadModel(lod); - (new LLMeshFilePicker(mModelPreview, lod))->getFile(); -} - -void LLFloaterModelPreview::loadModel(S32 lod, const std::string& file_name, bool force_disable_slm) -{ - prepareToLoadModel(lod); - mModelPreview->loadModel(file_name, lod, force_disable_slm); -} - -void LLFloaterModelPreview::onClickCalculateBtn() -{ - clearLogTab(); - addStringToLog("Calculating model data.", false); - mModelPreview->rebuildUploadData(); - - bool upload_skinweights = childGetValue("upload_skin").asBoolean(); - bool upload_joint_positions = childGetValue("upload_joints").asBoolean(); - bool lock_scale_if_joint_position = childGetValue("lock_scale_if_joint_position").asBoolean(); - - mUploadModelUrl.clear(); - mModelPhysicsFee.clear(); - - gMeshRepo.uploadModel(mModelPreview->mUploadData, mModelPreview->mPreviewScale, - childGetValue("upload_textures").asBoolean(), - upload_skinweights, upload_joint_positions, lock_scale_if_joint_position, - mUploadModelUrl, false, - getWholeModelFeeObserverHandle()); - - toggleCalculateButton(false); - mUploadBtn->setEnabled(false); - - //disable "simplification" UI - LLPanel* simplification_panel = getChild("physics simplification"); - LLView* child = simplification_panel->getFirstChild(); - while (child) - { - child->setEnabled(false); - child = simplification_panel->findNextSibling(child); - } -} - -// Modified cell_params, make sure to clear values if you have to reuse cell_params outside of this function -void add_row_to_list(LLScrollListCtrl *listp, - LLScrollListCell::Params &cell_params, - const LLSD &item_value, - const std::string &name, - const LLSD &vx, - const LLSD &vy, - const LLSD &vz) -{ - LLScrollListItem::Params item_params; - item_params.value = item_value; - - cell_params.column = "model_name"; - cell_params.value = name; - - item_params.columns.add(cell_params); - - cell_params.column = "axis_x"; - cell_params.value = vx; - item_params.columns.add(cell_params); - - cell_params.column = "axis_y"; - cell_params.value = vy; - item_params.columns.add(cell_params); - - cell_params.column = "axis_z"; - cell_params.value = vz; - - item_params.columns.add(cell_params); - - listp->addRow(item_params); -} - -void populate_list_with_overrides(LLScrollListCtrl *listp, const LLJointOverrideData &data, bool include_overrides) -{ - if (data.mModelsNoOverrides.empty() && data.mPosOverrides.empty()) - { - return; - } - - static const std::string no_override_placeholder = "-"; - - S32 count = 0; - LLScrollListCell::Params cell_params; - cell_params.font = LLFontGL::getFontSansSerif(); - // Start out right justifying numeric displays - cell_params.font_halign = LLFontGL::HCENTER; - - std::map::const_iterator map_iter = data.mPosOverrides.begin(); - std::map::const_iterator map_end = data.mPosOverrides.end(); - while (map_iter != map_end) - { - if (include_overrides) - { - add_row_to_list(listp, - cell_params, - LLSD::Integer(count), - map_iter->first, - LLSD::Real(map_iter->second.mV[VX]), - LLSD::Real(map_iter->second.mV[VY]), - LLSD::Real(map_iter->second.mV[VZ])); - } - else - { - add_row_to_list(listp, - cell_params, - LLSD::Integer(count), - map_iter->first, - no_override_placeholder, - no_override_placeholder, - no_override_placeholder); - } - count++; - map_iter++; - } - - std::set::const_iterator set_iter = data.mModelsNoOverrides.begin(); - std::set::const_iterator set_end = data.mModelsNoOverrides.end(); - while (set_iter != set_end) - { - add_row_to_list(listp, - cell_params, - LLSD::Integer(count), - *set_iter, - no_override_placeholder, - no_override_placeholder, - no_override_placeholder); - count++; - set_iter++; - } -} - -void LLFloaterModelPreview::onJointListSelection() -{ - S32 display_lod = mModelPreview->mPreviewLOD; - LLPanel *panel = mTabContainer->getPanelByName("rigging_panel"); - LLScrollListCtrl *joints_list = panel->getChild("joints_list"); - LLScrollListCtrl *joints_pos = panel->getChild("pos_overrides_list"); - LLScrollListCtrl *joints_scale = panel->getChild("scale_overrides_list"); - LLTextBox *joint_pos_descr = panel->getChild("pos_overrides_descr"); - - joints_pos->deleteAllItems(); - joints_scale->deleteAllItems(); - - LLScrollListItem *selected = joints_list->getFirstSelected(); - if (selected) - { - std::string label = selected->getValue().asString(); - LLJointOverrideData &data = mJointOverrides[display_lod][label]; - bool upload_joint_positions = childGetValue("upload_joints").asBoolean(); - populate_list_with_overrides(joints_pos, data, upload_joint_positions); - - joint_pos_descr->setTextArg("[JOINT]", label); - mSelectedJointName = label; - } - else - { - // temporary value (shouldn't happen) - std::string label = "mPelvis"; - joint_pos_descr->setTextArg("[JOINT]", label); - mSelectedJointName.clear(); - } - - // Note: We can make a version of renderBones() to highlight selected joint -} - -void LLFloaterModelPreview::onDescriptionKeystroke(LLUICtrl* ctrl) -{ - // Workaround for SL-4186, server doesn't allow name changes after 'calculate' stage - LLLineEditor* input = static_cast(ctrl); - if (input->isDirty()) // dirty will be reset after commit - { - toggleCalculateButton(true); - } -} - -//static -void LLFloaterModelPreview::onImportScaleCommit(LLUICtrl*,void* userdata) -{ - LLFloaterModelPreview *fp =(LLFloaterModelPreview *)userdata; - - if (!fp->mModelPreview) - { - return; - } - - fp->mModelPreview->mDirty = true; - - fp->toggleCalculateButton(true); - - fp->mModelPreview->refresh(); -} -//static -void LLFloaterModelPreview::onPelvisOffsetCommit( LLUICtrl*, void* userdata ) -{ - LLFloaterModelPreview *fp =(LLFloaterModelPreview*)userdata; - - if (!fp->mModelPreview) - { - return; - } - - fp->mModelPreview->mDirty = true; - - fp->toggleCalculateButton(true); - - fp->mModelPreview->refresh(); -} - -//static -void LLFloaterModelPreview::onPreviewLODCommit(LLUICtrl* ctrl, void* userdata) -{ - LLFloaterModelPreview *fp =(LLFloaterModelPreview *)userdata; - - if (!fp->mModelPreview) - { - return; - } - - S32 which_mode = 0; - - LLComboBox* combo = (LLComboBox*) ctrl; - - which_mode = (NUM_LOD-1)-combo->getFirstSelectedIndex(); // combo box list of lods is in reverse order - - fp->mModelPreview->setPreviewLOD(which_mode); -} - -//static -void LLFloaterModelPreview::onGenerateNormalsCommit(LLUICtrl* ctrl, void* userdata) -{ - LLFloaterModelPreview* fp = (LLFloaterModelPreview*) userdata; - - fp->mModelPreview->generateNormals(); -} - -void LLFloaterModelPreview::toggleGenarateNormals() -{ - bool enabled = childGetValue("gen_normals").asBoolean(); - mModelPreview->mViewOption["gen_normals"] = enabled; - childSetEnabled("crease_angle", enabled); - if(enabled) { - mModelPreview->generateNormals(); - } else { - mModelPreview->restoreNormals(); - } -} - -//static -void LLFloaterModelPreview::onExplodeCommit(LLUICtrl* ctrl, void* userdata) -{ - LLFloaterModelPreview* fp = LLFloaterModelPreview::sInstance; - - fp->mModelPreview->refresh(); -} - -//static -void LLFloaterModelPreview::onAutoFillCommit(LLUICtrl* ctrl, void* userdata) -{ - LLFloaterModelPreview* fp = (LLFloaterModelPreview*) userdata; - - fp->mModelPreview->queryLODs(); -} - -void LLFloaterModelPreview::onLODParamCommit(S32 lod, bool enforce_tri_limit) -{ - LLComboBox* lod_source_combo = getChild("lod_source_" + lod_name[lod]); - S32 mode = lod_source_combo->getCurrentIndex(); - switch (mode) - { - case LLModelPreview::MESH_OPTIMIZER_AUTO: - case LLModelPreview::MESH_OPTIMIZER_SLOPPY: - case LLModelPreview::MESH_OPTIMIZER_PRECISE: - mModelPreview->onLODMeshOptimizerParamCommit(lod, enforce_tri_limit, mode); - break; - default: - LL_ERRS() << "Only supposed to be called to generate models" << LL_ENDL; - break; - } - - //refresh LoDs that reference this one - for (S32 i = lod - 1; i >= 0; --i) - { - LLComboBox* lod_source_combo = getChild("lod_source_" + lod_name[i]); - if (lod_source_combo->getCurrentIndex() == LLModelPreview::USE_LOD_ABOVE) - { - onLoDSourceCommit(i); - } - else - { - break; - } - } -} - -void LLFloaterModelPreview::draw3dPreview() -{ - gGL.color3f(1.f, 1.f, 1.f); - - gGL.getTexUnit(0)->bind(mModelPreview); - - gGL.begin( LLRender::QUADS ); - { - gGL.texCoord2f(0.f, 1.f); - gGL.vertex2i(mPreviewRect.mLeft+1, mPreviewRect.mTop-1); - gGL.texCoord2f(0.f, 0.f); - gGL.vertex2i(mPreviewRect.mLeft+1, mPreviewRect.mBottom+1); - gGL.texCoord2f(1.f, 0.f); - gGL.vertex2i(mPreviewRect.mRight-1, mPreviewRect.mBottom+1); - gGL.texCoord2f(1.f, 1.f); - gGL.vertex2i(mPreviewRect.mRight-1, mPreviewRect.mTop-1); - } - gGL.end(); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); -} - -//----------------------------------------------------------------------------- -// draw() -//----------------------------------------------------------------------------- -void LLFloaterModelPreview::draw() -{ - LLFloater::draw(); - - if (!mModelPreview) - { - return; - } - - mModelPreview->update(); - - if (!mModelPreview->mLoading) - { - if ( mModelPreview->getLoadState() == LLModelLoader::ERROR_MATERIALS ) - { - childSetTextArg("status", "[STATUS]", getString("status_material_mismatch")); - } - else - if ( mModelPreview->getLoadState() > LLModelLoader::ERROR_MODEL ) - { - childSetTextArg("status", "[STATUS]", getString(LLModel::getStatusString(mModelPreview->getLoadState() - LLModelLoader::ERROR_MODEL))); - } - else - if ( mModelPreview->getLoadState() == LLModelLoader::ERROR_PARSING ) - { - childSetTextArg("status", "[STATUS]", getString("status_parse_error")); - toggleCalculateButton(false); - } - else - if (mModelPreview->getLoadState() == LLModelLoader::WARNING_BIND_SHAPE_ORIENTATION) - { - childSetTextArg("status", "[STATUS]", getString("status_bind_shape_orientation")); - } - else - { - childSetTextArg("status", "[STATUS]", getString("status_idle")); - } - } - - if (!isMinimized() && mModelPreview->lodsReady()) - { - draw3dPreview(); - } -} - -//----------------------------------------------------------------------------- -// handleMouseDown() -//----------------------------------------------------------------------------- -bool LLFloaterModelPreview::handleMouseDown(S32 x, S32 y, MASK mask) -{ - if (mPreviewRect.pointInRect(x, y)) - { - bringToFront( x, y ); - gFocusMgr.setMouseCapture(this); - gViewerWindow->hideCursor(); - mLastMouseX = x; - mLastMouseY = y; - return true; - } - - return LLFloater::handleMouseDown(x, y, mask); -} - -//----------------------------------------------------------------------------- -// handleMouseUp() -//----------------------------------------------------------------------------- -bool LLFloaterModelPreview::handleMouseUp(S32 x, S32 y, MASK mask) -{ - gFocusMgr.setMouseCapture(nullptr); - gViewerWindow->showCursor(); - return LLFloater::handleMouseUp(x, y, mask); -} - -//----------------------------------------------------------------------------- -// handleHover() -//----------------------------------------------------------------------------- -bool LLFloaterModelPreview::handleHover (S32 x, S32 y, MASK mask) -{ - MASK local_mask = mask & ~MASK_ALT; - - if (mModelPreview && hasMouseCapture()) - { - if (local_mask == MASK_PAN) - { - // pan here - mModelPreview->pan((F32)(x - mLastMouseX) * -0.005f, (F32)(y - mLastMouseY) * -0.005f); - } - else if (local_mask == MASK_ORBIT) - { - F32 yaw_radians = (F32)(x - mLastMouseX) * -0.01f; - F32 pitch_radians = (F32)(y - mLastMouseY) * 0.02f; - - mModelPreview->rotate(yaw_radians, pitch_radians); - } - else - { - - F32 yaw_radians = (F32)(x - mLastMouseX) * -0.01f; - F32 zoom_amt = (F32)(y - mLastMouseY) * 0.02f; - - mModelPreview->rotate(yaw_radians, 0.f); - mModelPreview->zoom(zoom_amt); - } - - - mModelPreview->refresh(); - - LLUI::getInstance()->setMousePositionLocal(this, mLastMouseX, mLastMouseY); - } - - if (!mPreviewRect.pointInRect(x, y) || !mModelPreview) - { - return LLFloater::handleHover(x, y, mask); - } - else if (local_mask == MASK_ORBIT) - { - gViewerWindow->setCursor(UI_CURSOR_TOOLCAMERA); - } - else if (local_mask == MASK_PAN) - { - gViewerWindow->setCursor(UI_CURSOR_TOOLPAN); - } - else - { - gViewerWindow->setCursor(UI_CURSOR_TOOLZOOMIN); - } - - return true; -} - -//----------------------------------------------------------------------------- -// handleScrollWheel() -//----------------------------------------------------------------------------- -bool LLFloaterModelPreview::handleScrollWheel(S32 x, S32 y, S32 clicks) -{ - if (mPreviewRect.pointInRect(x, y) && mModelPreview) - { - mModelPreview->zoom((F32)clicks * -0.2f); - mModelPreview->refresh(); - } - else - { - LLFloaterModelUploadBase::handleScrollWheel(x, y, clicks); - } - return true; -} - -/*virtual*/ -void LLFloaterModelPreview::onOpen(const LLSD& key) -{ - LLModelPreview::sIgnoreLoadedCallback = false; - requestAgentUploadPermissions(); -} - -/*virtual*/ -void LLFloaterModelPreview::onClose(bool app_quitting) -{ - LLModelPreview::sIgnoreLoadedCallback = true; -} - -//static -void LLFloaterModelPreview::onPhysicsParamCommit(LLUICtrl* ctrl, void* data) -{ - if (LLConvexDecomposition::getInstance() == NULL) - { - LL_INFOS() << "convex decomposition tool is a stub on this platform. cannot get decomp." << LL_ENDL; - return; - } - - if (sInstance) - { - LLCDParam* param = (LLCDParam*) data; - std::string name(param->mName); - - LLSD value = ctrl->getValue(); - - if("Retain%" == name) - { - value = ctrl->getValue().asReal() / RETAIN_COEFFICIENT; - } - - sInstance->mDecompParams[name] = value; - - if (name == "Simplify Method") - { - bool show_retain = false; - bool show_detail = true; - - if (ctrl->getValue().asInteger() == 0) - { - show_retain = true; - show_detail = false; - } - - sInstance->childSetVisible("Retain%", show_retain); - sInstance->childSetVisible("Retain%_label", show_retain); - - sInstance->childSetVisible("Detail Scale", show_detail); - sInstance->childSetVisible("Detail Scale label", show_detail); - } - } -} - -//static -void LLFloaterModelPreview::onPhysicsStageExecute(LLUICtrl* ctrl, void* data) -{ - LLCDStageData* stage_data = (LLCDStageData*) data; - std::string stage = stage_data->mName; - - if (sInstance) - { - if (!sInstance->mCurRequest.empty()) - { - LL_INFOS() << "Decomposition request still pending." << LL_ENDL; - return; - } - - if (sInstance->mModelPreview) - { - for (S32 i = 0; i < sInstance->mModelPreview->mModel[LLModel::LOD_PHYSICS].size(); ++i) - { - LLModel* mdl = sInstance->mModelPreview->mModel[LLModel::LOD_PHYSICS][i]; - DecompRequest* request = new DecompRequest(stage, mdl); - sInstance->mCurRequest.insert(request); - gMeshRepo.mDecompThread->submitRequest(request); - } - } - - if (stage == "Decompose") - { - sInstance->setStatusMessage(sInstance->getString("decomposing")); - sInstance->childSetVisible("Decompose", false); - sInstance->childSetVisible("decompose_cancel", true); - sInstance->childDisable("Simplify"); - } - else if (stage == "Simplify") - { - sInstance->setStatusMessage(sInstance->getString("simplifying")); - sInstance->childSetVisible("Simplify", false); - sInstance->childSetVisible("simplify_cancel", true); - sInstance->childDisable("Decompose"); - } - } -} - -//static -void LLFloaterModelPreview::onPhysicsBrowse(LLUICtrl* ctrl, void* userdata) -{ - sInstance->loadModel(LLModel::LOD_PHYSICS); -} - -//static -void LLFloaterModelPreview::onPhysicsUseLOD(LLUICtrl* ctrl, void* userdata) -{ - S32 num_lods = 4; - S32 which_mode; - - LLCtrlSelectionInterface* iface = sInstance->childGetSelectionInterface("physics_lod_combo"); - if (iface) - { - which_mode = iface->getFirstSelectedIndex(); - } - else - { - LL_WARNS() << "no iface" << LL_ENDL; - return; - } - - if (which_mode <= 0) - { - LL_WARNS() << "which_mode out of range, " << which_mode << LL_ENDL; - } - - S32 file_mode = iface->getItemCount() - 1; - S32 cube_mode = file_mode - 1; - if (which_mode < cube_mode) - { - S32 which_lod = num_lods - which_mode; - sInstance->mModelPreview->setPhysicsFromLOD(which_lod); - } - else if (which_mode == cube_mode) - { - std::string path = gDirUtilp->getAppRODataDir(); - gDirUtilp->append(path, "cube.dae"); - sInstance->loadModel(LLModel::LOD_PHYSICS, path); - } - - LLModelPreview *model_preview = sInstance->mModelPreview; - if (model_preview) - { - model_preview->refresh(); - model_preview->updateStatusMessages(); - } -} - -//static -void LLFloaterModelPreview::onCancel(LLUICtrl* ctrl, void* data) -{ - if (sInstance) - { - sInstance->closeFloater(false); - } -} - -//static -void LLFloaterModelPreview::onPhysicsStageCancel(LLUICtrl* ctrl, void*data) -{ - if (sInstance) - { - for (std::set >::iterator iter = sInstance->mCurRequest.begin(); - iter != sInstance->mCurRequest.end(); ++iter) - { - DecompRequest* req = *iter; - req->mContinue = 0; - } - - sInstance->mCurRequest.clear(); - - if (sInstance->mModelPreview) - { - sInstance->mModelPreview->updateStatusMessages(); - } - } -} - -void LLFloaterModelPreview::initDecompControls() -{ - LLSD key; - - childSetCommitCallback("simplify_cancel", onPhysicsStageCancel, NULL); - childSetCommitCallback("decompose_cancel", onPhysicsStageCancel, NULL); - - childSetCommitCallback("physics_lod_combo", onPhysicsUseLOD, NULL); - childSetCommitCallback("physics_browse", onPhysicsBrowse, NULL); - - static const LLCDStageData* stage = NULL; - static S32 stage_count = 0; - - if (!stage && LLConvexDecomposition::getInstance() != NULL) - { - stage_count = LLConvexDecomposition::getInstance()->getStages(&stage); - } - - static const LLCDParam* param = NULL; - static S32 param_count = 0; - if (!param && LLConvexDecomposition::getInstance() != NULL) - { - param_count = LLConvexDecomposition::getInstance()->getParameters(¶m); - } - - for (S32 j = stage_count-1; j >= 0; --j) - { - LLButton* button = getChild(stage[j].mName); - if (button) - { - button->setCommitCallback(onPhysicsStageExecute, (void*) &stage[j]); - } - - gMeshRepo.mDecompThread->mStageID[stage[j].mName] = j; - // protected against stub by stage_count being 0 for stub above - LLConvexDecomposition::getInstance()->registerCallback(j, LLPhysicsDecomp::llcdCallback); - - //LL_INFOS() << "Physics decomp stage " << stage[j].mName << " (" << j << ") parameters:" << LL_ENDL; - //LL_INFOS() << "------------------------------------" << LL_ENDL; - - for (S32 i = 0; i < param_count; ++i) - { - if (param[i].mStage != j) - { - continue; - } - - std::string name(param[i].mName ? param[i].mName : ""); - std::string description(param[i].mDescription ? param[i].mDescription : ""); - - std::string type = "unknown"; - - LL_INFOS() << name << " - " << description << LL_ENDL; - - if (param[i].mType == LLCDParam::LLCD_FLOAT) - { - mDecompParams[param[i].mName] = LLSD(param[i].mDefault.mFloat); - //LL_INFOS() << "Type: float, Default: " << param[i].mDefault.mFloat << LL_ENDL; - - - LLUICtrl* ctrl = getChild(name); - if (LLSliderCtrl* slider = dynamic_cast(ctrl)) - { - slider->setMinValue(param[i].mDetails.mRange.mLow.mFloat); - slider->setMaxValue(param[i].mDetails.mRange.mHigh.mFloat); - slider->setIncrement(param[i].mDetails.mRange.mDelta.mFloat); - slider->setValue(param[i].mDefault.mFloat); - slider->setCommitCallback(onPhysicsParamCommit, (void*) ¶m[i]); - } - else if (LLSpinCtrl* spinner = dynamic_cast(ctrl)) - { - bool is_retain_ctrl = "Retain%" == name; - double coefficient = is_retain_ctrl ? RETAIN_COEFFICIENT : 1.f; - - spinner->setMinValue(param[i].mDetails.mRange.mLow.mFloat * coefficient); - spinner->setMaxValue(param[i].mDetails.mRange.mHigh.mFloat * coefficient); - spinner->setIncrement(param[i].mDetails.mRange.mDelta.mFloat * coefficient); - spinner->setValue(param[i].mDefault.mFloat * coefficient); - spinner->setCommitCallback(onPhysicsParamCommit, (void*) ¶m[i]); - } - else if (LLComboBox* combo_box = dynamic_cast(ctrl)) - { - float min = param[i].mDetails.mRange.mLow.mFloat; - float max = param[i].mDetails.mRange.mHigh.mFloat; - float delta = param[i].mDetails.mRange.mDelta.mFloat; - - bool is_smooth_cb = ("Cosine%" == name); - if (is_smooth_cb) - { - createSmoothComboBox(combo_box, min, max); - } - else - { - for(float value = min; value <= max; value += delta) - { - std::string label = llformat("%.1f", value); - combo_box->add(label, value, ADD_BOTTOM, true); - } - } - combo_box->setValue(is_smooth_cb ? 0: param[i].mDefault.mFloat); - combo_box->setCommitCallback(onPhysicsParamCommit, (void*) ¶m[i]); - } - } - else if (param[i].mType == LLCDParam::LLCD_INTEGER) - { - mDecompParams[param[i].mName] = LLSD(param[i].mDefault.mIntOrEnumValue); - //LL_INFOS() << "Type: integer, Default: " << param[i].mDefault.mIntOrEnumValue << LL_ENDL; - - - LLUICtrl* ctrl = getChild(name); - if (LLSliderCtrl* slider = dynamic_cast(ctrl)) - { - slider->setMinValue(param[i].mDetails.mRange.mLow.mIntOrEnumValue); - slider->setMaxValue(param[i].mDetails.mRange.mHigh.mIntOrEnumValue); - slider->setIncrement(param[i].mDetails.mRange.mDelta.mIntOrEnumValue); - slider->setValue(param[i].mDefault.mIntOrEnumValue); - slider->setCommitCallback(onPhysicsParamCommit, (void*) ¶m[i]); - } - else if (LLComboBox* combo_box = dynamic_cast(ctrl)) - { - for(int k = param[i].mDetails.mRange.mLow.mIntOrEnumValue; k<=param[i].mDetails.mRange.mHigh.mIntOrEnumValue; k+=param[i].mDetails.mRange.mDelta.mIntOrEnumValue) - { - std::string name = llformat("%.1d", k); - combo_box->add(name, k, ADD_BOTTOM, true); - } - combo_box->setValue(param[i].mDefault.mIntOrEnumValue); - combo_box->setCommitCallback(onPhysicsParamCommit, (void*) ¶m[i]); - } - } - else if (param[i].mType == LLCDParam::LLCD_BOOLEAN) - { - mDecompParams[param[i].mName] = LLSD(param[i].mDefault.mBool); - //LL_INFOS() << "Type: boolean, Default: " << (param[i].mDefault.mBool ? "True" : "False") << LL_ENDL; - - LLCheckBoxCtrl* check_box = getChild(name); - if (check_box) - { - check_box->setValue(param[i].mDefault.mBool); - check_box->setCommitCallback(onPhysicsParamCommit, (void*) ¶m[i]); - } - } - else if (param[i].mType == LLCDParam::LLCD_ENUM) - { - mDecompParams[param[i].mName] = LLSD(param[i].mDefault.mIntOrEnumValue); - //LL_INFOS() << "Type: enum, Default: " << param[i].mDefault.mIntOrEnumValue << LL_ENDL; - - { //plug into combo box - - //LL_INFOS() << "Accepted values: " << LL_ENDL; - LLComboBox* combo_box = getChild(name); - for (S32 k = 0; k < param[i].mDetails.mEnumValues.mNumEnums; ++k) - { - //LL_INFOS() << param[i].mDetails.mEnumValues.mEnumsArray[k].mValue - // << " - " << param[i].mDetails.mEnumValues.mEnumsArray[k].mName << LL_ENDL; - - std::string name(param[i].mDetails.mEnumValues.mEnumsArray[k].mName); - std::string localized_name; - bool is_localized = LLTrans::findString(localized_name, name); - - combo_box->add(is_localized ? localized_name : name, - LLSD::Integer(param[i].mDetails.mEnumValues.mEnumsArray[k].mValue)); - } - combo_box->setValue(param[i].mDefault.mIntOrEnumValue); - combo_box->setCommitCallback(onPhysicsParamCommit, (void*) ¶m[i]); - } - - //LL_INFOS() << "----" << LL_ENDL; - } - //LL_INFOS() << "-----------------------------" << LL_ENDL; - } - } - mDefaultDecompParams = mDecompParams; - childSetCommitCallback("physics_explode", LLFloaterModelPreview::onExplodeCommit, this); -} - -void LLFloaterModelPreview::createSmoothComboBox(LLComboBox* combo_box, float min, float max) -{ - float delta = (max - min) / SMOOTH_VALUES_NUMBER; - int ilabel = 0; - - combo_box->add("0 (none)", ADD_BOTTOM, true); - - for(float value = min + delta; value < max; value += delta) - { - std::string label = (++ilabel == SMOOTH_VALUES_NUMBER) ? "10 (max)" : llformat("%.1d", ilabel); - combo_box->add(label, value, ADD_BOTTOM, true); - } - - -} - -//----------------------------------------------------------------------------- -// onMouseCaptureLost() -//----------------------------------------------------------------------------- -// static -void LLFloaterModelPreview::onMouseCaptureLostModelPreview(LLMouseHandler* handler) -{ - gViewerWindow->showCursor(); -} - -//----------------------------------------------------------------------------- -// addStringToLog() -//----------------------------------------------------------------------------- -//static -void LLFloaterModelPreview::addStringToLog(const std::string& message, const LLSD& args, bool flash, S32 lod) -{ - if (sInstance && sInstance->hasString(message)) - { - std::string str; - switch (lod) -{ - case LLModel::LOD_IMPOSTOR: str = "LOD0 "; break; - case LLModel::LOD_LOW: str = "LOD1 "; break; - case LLModel::LOD_MEDIUM: str = "LOD2 "; break; - case LLModel::LOD_PHYSICS: str = "PHYS "; break; - case LLModel::LOD_HIGH: str = "LOD3 "; break; - default: break; -} - - LLStringUtil::format_map_t args_msg; - LLSD::map_const_iterator iter = args.beginMap(); - LLSD::map_const_iterator end = args.endMap(); - for (; iter != end; ++iter) -{ - args_msg[iter->first] = iter->second.asString(); - } - str += sInstance->getString(message, args_msg); - sInstance->addStringToLogTab(str, flash); - } - } - -// static -void LLFloaterModelPreview::addStringToLog(const std::string& str, bool flash) - { - if (sInstance) - { - sInstance->addStringToLogTab(str, flash); - } - } - -// static -void LLFloaterModelPreview::addStringToLog(const std::ostringstream& strm, bool flash) - { - if (sInstance) - { - sInstance->addStringToLogTab(strm.str(), flash); - } - } - -void LLFloaterModelPreview::clearAvatarTab() -{ - LLPanel *panel = mTabContainer->getPanelByName("rigging_panel"); - LLScrollListCtrl *joints_list = panel->getChild("joints_list"); - joints_list->deleteAllItems(); - LLScrollListCtrl *joints_pos = panel->getChild("pos_overrides_list"); - joints_pos->deleteAllItems(); mSelectedJointName.clear(); - - for (U32 i = 0; i < LLModel::NUM_LODS; ++i) -{ - mJointOverrides[i].clear(); - } - - LLTextBox *joint_total_descr = panel->getChild("conflicts_description"); - joint_total_descr->setTextArg("[CONFLICTS]", llformat("%d", 0)); - joint_total_descr->setTextArg("[JOINTS_COUNT]", llformat("%d", 0)); - - - LLTextBox *joint_pos_descr = panel->getChild("pos_overrides_descr"); - joint_pos_descr->setTextArg("[JOINT]", std::string("mPelvis")); // Might be better to hide it - } - -void LLFloaterModelPreview::updateAvatarTab(bool highlight_overrides) -{ - S32 display_lod = mModelPreview->mPreviewLOD; - if (mModelPreview->mModel[display_lod].empty()) - { - mSelectedJointName.clear(); - return; - } - - // Joints will be listed as long as they are listed in mAlternateBindMatrix - // even if they are for some reason identical to defaults. - // Todo: Are overrides always identical for all lods? They normally are, but there might be situations where they aren't. - if (mJointOverrides[display_lod].empty()) - { - // populate map - for (LLModelLoader::scene::iterator iter = mModelPreview->mScene[display_lod].begin(); iter != mModelPreview->mScene[display_lod].end(); ++iter) - { - for (LLModelLoader::model_instance_list::iterator model_iter = iter->second.begin(); model_iter != iter->second.end(); ++model_iter) - { - LLModelInstance& instance = *model_iter; - LLModel* model = instance.mModel; - const LLMeshSkinInfo *skin = &model->mSkinInfo; - U32 joint_count = LLSkinningUtil::getMeshJointCount(skin); - U32 bind_count = highlight_overrides ? skin->mAlternateBindMatrix.size() : 0; // simply do not include overrides if data is not needed - if (bind_count > 0 && bind_count != joint_count) - { - std::ostringstream out; - out << "Invalid joint overrides for model " << model->getName(); - out << ". Amount of joints " << joint_count; - out << ", is different from amount of overrides " << bind_count; - LL_INFOS() << out.str() << LL_ENDL; - addStringToLog(out.str(), true); - // Disable overrides for this model - bind_count = 0; - } - if (bind_count > 0) - { - for (U32 j = 0; j < joint_count; ++j) - { - const LLVector3& joint_pos = LLVector3(skin->mAlternateBindMatrix[j].getTranslation()); - LLJointOverrideData &data = mJointOverrides[display_lod][skin->mJointNames[j]]; - - LLJoint* pJoint = LLModelPreview::lookupJointByName(skin->mJointNames[j], mModelPreview); - if (pJoint) - { - // see how voavatar uses aboveJointPosThreshold - if (pJoint->aboveJointPosThreshold(joint_pos)) - { - // valid override - if (data.mPosOverrides.size() > 0 - && (data.mPosOverrides.begin()->second - joint_pos).lengthSquared() > (LL_JOINT_TRESHOLD_POS_OFFSET * LL_JOINT_TRESHOLD_POS_OFFSET)) - { - // File contains multiple meshes with conflicting joint offsets - // preview may be incorrect, upload result might wary (depends onto - // mesh_id that hasn't been generated yet). - data.mHasConflicts = true; - } - data.mPosOverrides[model->getName()] = joint_pos; - } - else - { - // default value, it won't be accounted for by avatar - data.mModelsNoOverrides.insert(model->getName()); - } - } - } - } - else - { - for (U32 j = 0; j < joint_count; ++j) - { - LLJointOverrideData &data = mJointOverrides[display_lod][skin->mJointNames[j]]; - data.mModelsNoOverrides.insert(model->getName()); - } - } - } - } - } - - LLPanel *panel = mTabContainer->getPanelByName("rigging_panel"); - LLScrollListCtrl *joints_list = panel->getChild("joints_list"); - - if (joints_list->isEmpty()) - { - // Populate table - - std::map joint_alias_map; - mModelPreview->getJointAliases(joint_alias_map); - - S32 conflicts = 0; - joint_override_data_map_t::iterator joint_iter = mJointOverrides[display_lod].begin(); - joint_override_data_map_t::iterator joint_end = mJointOverrides[display_lod].end(); - while (joint_iter != joint_end) - { - const std::string& listName = joint_iter->first; - - LLScrollListItem::Params item_params; - item_params.value(listName); - - LLScrollListCell::Params cell_params; - cell_params.font = LLFontGL::getFontSansSerif(); - cell_params.value = listName; - if (joint_alias_map.find(listName) == joint_alias_map.end()) - { - // Missing names - cell_params.color = LLColor4::red; - } - if (joint_iter->second.mHasConflicts) - { - // Conflicts - cell_params.color = LLColor4::orange; - conflicts++; - } - if (highlight_overrides && joint_iter->second.mPosOverrides.size() > 0) - { - cell_params.font.style = "BOLD"; - } - - item_params.columns.add(cell_params); - - joints_list->addRow(item_params, ADD_BOTTOM); - joint_iter++; - } - joints_list->selectFirstItem(); - LLScrollListItem *selected = joints_list->getFirstSelected(); - if (selected) - { - mSelectedJointName = selected->getValue().asString(); - } - - LLTextBox *joint_conf_descr = panel->getChild("conflicts_description"); - joint_conf_descr->setTextArg("[CONFLICTS]", llformat("%d", conflicts)); - joint_conf_descr->setTextArg("[JOINTS_COUNT]", llformat("%d", mJointOverrides[display_lod].size())); - } -} - -//----------------------------------------------------------------------------- -// addStringToLogTab() -//----------------------------------------------------------------------------- -void LLFloaterModelPreview::addStringToLogTab(const std::string& str, bool flash) -{ - if (str.empty()) - { - return; - } - - LLWString text = utf8str_to_wstring(str); - S32 add_text_len = text.length() + 1; // newline - S32 editor_max_len = mUploadLogText->getMaxTextLength(); - if (add_text_len > editor_max_len) - { - return; - } - - // Make sure we have space for new string - S32 editor_text_len = mUploadLogText->getLength(); - if (editor_max_len < (editor_text_len + add_text_len) - && mUploadLogText->getLineCount() <= 0) - { - mUploadLogText->getTextBoundingRect();// forces a reflow() to fix line count - } - while (editor_max_len < (editor_text_len + add_text_len)) - { - S32 shift = mUploadLogText->removeFirstLine(); - if (shift > 0) - { - // removed a line - editor_text_len -= shift; -} - else - { - //nothing to remove? - LL_WARNS() << "Failed to clear log lines" << LL_ENDL; - break; - } - } - - mUploadLogText->appendText(str, true); - - if (flash) - { - LLPanel* panel = mTabContainer->getPanelByName("logs_panel"); - if (mTabContainer->getCurrentPanel() != panel) - { - mTabContainer->setTabPanelFlashing(panel, true); - } - } - } - -void LLFloaterModelPreview::setDetails(F32 x, F32 y, F32 z) -{ - assert_main_thread(); - childSetTextArg("import_dimensions", "[X]", llformat("%.3f", x)); - childSetTextArg("import_dimensions", "[Y]", llformat("%.3f", y)); - childSetTextArg("import_dimensions", "[Z]", llformat("%.3f", z)); - } - -void LLFloaterModelPreview::setPreviewLOD(S32 lod) - { - if (mModelPreview) - { - mModelPreview->setPreviewLOD(lod); - } - } - -void LLFloaterModelPreview::onBrowseLOD(S32 lod) -{ - assert_main_thread(); - - loadModel(lod); -} - -//static -void LLFloaterModelPreview::onReset(void* user_data) -{ - assert_main_thread(); - - - LLFloaterModelPreview* fmp = (LLFloaterModelPreview*) user_data; - fmp->childDisable("reset_btn"); - fmp->clearLogTab(); - fmp->clearAvatarTab(); - LLModelPreview* mp = fmp->mModelPreview; - std::string filename = mp->mLODFile[LLModel::LOD_HIGH]; - - fmp->resetDisplayOptions(); - fmp->resetUploadOptions(); - //reset model preview - fmp->initModelPreview(); - - mp = fmp->mModelPreview; - mp->loadModel(filename,LLModel::LOD_HIGH,true); -} - -//static -void LLFloaterModelPreview::onUpload(void* user_data) -{ - assert_main_thread(); - - LLFloaterModelPreview* mp = (LLFloaterModelPreview*) user_data; - mp->clearLogTab(); - - mp->mUploadBtn->setEnabled(false); - - mp->mModelPreview->rebuildUploadData(); - - bool upload_skinweights = mp->childGetValue("upload_skin").asBoolean(); - bool upload_joint_positions = mp->childGetValue("upload_joints").asBoolean(); - bool lock_scale_if_joint_position = mp->childGetValue("lock_scale_if_joint_position").asBoolean(); - - if (gSavedSettings.getBOOL("MeshImportUseSLM")) - { - mp->mModelPreview->saveUploadData(upload_skinweights, upload_joint_positions, lock_scale_if_joint_position); - } - - gMeshRepo.uploadModel(mp->mModelPreview->mUploadData, mp->mModelPreview->mPreviewScale, - mp->childGetValue("upload_textures").asBoolean(), - upload_skinweights, upload_joint_positions, lock_scale_if_joint_position, - mp->mUploadModelUrl, - true, LLHandle(), mp->getWholeModelUploadObserverHandle()); -} - - -void LLFloaterModelPreview::refresh() -{ - sInstance->toggleCalculateButton(true); - sInstance->mModelPreview->mDirty = true; -} - -LLFloaterModelPreview::DecompRequest::DecompRequest(const std::string& stage, LLModel* mdl) -{ - mStage = stage; - mContinue = 1; - mModel = mdl; - mDecompID = &mdl->mDecompID; - mParams = sInstance->mDecompParams; - - //copy out positions and indices - assignData(mdl) ; -} - -void LLFloaterModelPreview::setCtrlLoadFromFile(S32 lod) -{ - if (lod == LLModel::LOD_PHYSICS) - { - LLComboBox* lod_combo = findChild("physics_lod_combo"); - if (lod_combo) - { - lod_combo->setCurrentByIndex(lod_combo->getItemCount() - 1); - } - } - else - { - LLComboBox* lod_combo = findChild("lod_source_" + lod_name[lod]); - if (lod_combo) - { - lod_combo->setCurrentByIndex(0); - } -} -} - -void LLFloaterModelPreview::setStatusMessage(const std::string& msg) -{ - LLMutexLock lock(mStatusLock); - mStatusMessage = msg; -} - -void LLFloaterModelPreview::toggleCalculateButton() -{ - toggleCalculateButton(true); -} - -void LLFloaterModelPreview::modelUpdated(bool calculate_visible) -{ - mModelPhysicsFee.clear(); - toggleCalculateButton(calculate_visible); -} - -void LLFloaterModelPreview::toggleCalculateButton(bool visible) -{ - mCalculateBtn->setVisible(visible); - - bool uploadingSkin = childGetValue("upload_skin").asBoolean(); - bool uploadingJointPositions = childGetValue("upload_joints").asBoolean(); - if ( uploadingSkin ) - { - //Disable the calculate button *if* the rig is invalid - which is determined during the critiquing process - if ( uploadingJointPositions && !mModelPreview->isRigValidForJointPositionUpload() ) - { - mCalculateBtn->setVisible( false ); - } - } - - mUploadBtn->setVisible(!visible); - mUploadBtn->setEnabled(isModelUploadAllowed()); - - if (visible) - { - std::string tbd = getString("tbd"); - childSetTextArg("prim_weight", "[EQ]", tbd); - childSetTextArg("download_weight", "[ST]", tbd); - childSetTextArg("server_weight", "[SIM]", tbd); - childSetTextArg("physics_weight", "[PH]", tbd); - if (!mModelPhysicsFee.isMap() || (mModelPhysicsFee.size() == 0)) - { - childSetTextArg("upload_fee", "[FEE]", tbd); - } - std::string dashes = hasString("--") ? getString("--") : "--"; - childSetTextArg("price_breakdown", "[STREAMING]", dashes); - childSetTextArg("price_breakdown", "[PHYSICS]", dashes); - childSetTextArg("price_breakdown", "[INSTANCES]", dashes); - childSetTextArg("price_breakdown", "[TEXTURES]", dashes); - childSetTextArg("price_breakdown", "[MODEL]", dashes); - childSetTextArg("physics_breakdown", "[PCH]", dashes); - childSetTextArg("physics_breakdown", "[PM]", dashes); - childSetTextArg("physics_breakdown", "[PHU]", dashes); - } -} - -void LLFloaterModelPreview::onLoDSourceCommit(S32 lod) -{ - mModelPreview->updateLodControls(lod); - - LLComboBox* lod_source_combo = getChild("lod_source_" + lod_name[lod]); - S32 index = lod_source_combo->getCurrentIndex(); - if (index == LLModelPreview::MESH_OPTIMIZER_AUTO - || index == LLModelPreview::MESH_OPTIMIZER_SLOPPY - || index == LLModelPreview::MESH_OPTIMIZER_PRECISE) - { //rebuild LoD to update triangle counts - onLODParamCommit(lod, true); - } -} - -void LLFloaterModelPreview::resetDisplayOptions() -{ - std::map::iterator option_it = mModelPreview->mViewOption.begin(); - - for(;option_it != mModelPreview->mViewOption.end(); ++option_it) - { - LLUICtrl* ctrl = getChild(option_it->first); - ctrl->setValue(false); - } -} - -void LLFloaterModelPreview::resetUploadOptions() -{ - childSetValue("import_scale", 1); - childSetValue("pelvis_offset", 0); - childSetValue("physics_explode", 0); - childSetValue("physics_file", ""); - childSetVisible("Retain%", false); - childSetVisible("Retain%_label", false); - childSetVisible("Detail Scale", true); - childSetVisible("Detail Scale label", true); - - getChild("lod_source_" + lod_name[NUM_LOD - 1])->setCurrentByIndex(LLModelPreview::LOD_FROM_FILE); - for (S32 lod = 0; lod < NUM_LOD - 1; ++lod) - { - getChild("lod_source_" + lod_name[lod])->setCurrentByIndex(LLModelPreview::MESH_OPTIMIZER_AUTO); - childSetValue("lod_file_" + lod_name[lod], ""); - } - - for(auto& p : mDefaultDecompParams) - { - std::string ctrl_name(p.first); - LLUICtrl* ctrl = getChild(ctrl_name); - if (ctrl) - { - ctrl->setValue(p.second); - } - } - getChild("physics_lod_combo")->setCurrentByIndex(0); - getChild("Cosine%")->setCurrentByIndex(0); -} - -void LLFloaterModelPreview::clearLogTab() -{ - mUploadLogText->clear(); - LLPanel* panel = mTabContainer->getPanelByName("logs_panel"); - mTabContainer->setTabPanelFlashing(panel, false); -} - -void LLFloaterModelPreview::onModelPhysicsFeeReceived(const LLSD& result, std::string upload_url) -{ - mModelPhysicsFee = result; - mModelPhysicsFee["url"] = upload_url; - - doOnIdleOneTime(boost::bind(&LLFloaterModelPreview::handleModelPhysicsFeeReceived,this)); -} - -void LLFloaterModelPreview::handleModelPhysicsFeeReceived() -{ - const LLSD& result = mModelPhysicsFee; - mUploadModelUrl = result["url"].asString(); - - childSetTextArg("prim_weight", "[EQ]", llformat("%0.3f", result["resource_cost"].asReal())); - childSetTextArg("download_weight", "[ST]", llformat("%0.3f", result["model_streaming_cost"].asReal())); - childSetTextArg("server_weight", "[SIM]", llformat("%0.3f", result["simulation_cost"].asReal())); - childSetTextArg("physics_weight", "[PH]", llformat("%0.3f", result["physics_cost"].asReal())); - childSetTextArg("upload_fee", "[FEE]", llformat("%d", result["upload_price"].asInteger())); - childSetTextArg("price_breakdown", "[STREAMING]", llformat("%d", result["upload_price_breakdown"]["mesh_streaming"].asInteger())); - childSetTextArg("price_breakdown", "[PHYSICS]", llformat("%d", result["upload_price_breakdown"]["mesh_physics"].asInteger())); - childSetTextArg("price_breakdown", "[INSTANCES]", llformat("%d", result["upload_price_breakdown"]["mesh_instance"].asInteger())); - childSetTextArg("price_breakdown", "[TEXTURES]", llformat("%d", result["upload_price_breakdown"]["texture"].asInteger())); - childSetTextArg("price_breakdown", "[MODEL]", llformat("%d", result["upload_price_breakdown"]["model"].asInteger())); - - childSetTextArg("physics_breakdown", "[PCH]", llformat("%0.3f", result["model_physics_cost"]["hull"].asReal())); - childSetTextArg("physics_breakdown", "[PM]", llformat("%0.3f", result["model_physics_cost"]["mesh"].asReal())); - childSetTextArg("physics_breakdown", "[PHU]", llformat("%0.3f", result["model_physics_cost"]["decomposition"].asReal())); - childSetTextArg("streaming_breakdown", "[STR_TOTAL]", llformat("%d", result["streaming_cost"].asInteger())); - childSetTextArg("streaming_breakdown", "[STR_HIGH]", llformat("%d", result["streaming_params"]["high_lod"].asInteger())); - childSetTextArg("streaming_breakdown", "[STR_MED]", llformat("%d", result["streaming_params"]["medium_lod"].asInteger())); - childSetTextArg("streaming_breakdown", "[STR_LOW]", llformat("%d", result["streaming_params"]["low_lod"].asInteger())); - childSetTextArg("streaming_breakdown", "[STR_LOWEST]", llformat("%d", result["streaming_params"]["lowest_lod"].asInteger())); - - childSetVisible("upload_fee", true); - childSetVisible("price_breakdown", true); - mUploadBtn->setEnabled(isModelUploadAllowed()); -} - -void LLFloaterModelPreview::setModelPhysicsFeeErrorStatus(S32 status, const std::string& reason, const LLSD& result) -{ - std::ostringstream out; - out << "LLFloaterModelPreview::setModelPhysicsFeeErrorStatus(" << status; - out << " : " << reason << ")"; - LL_WARNS() << out.str() << LL_ENDL; - LLFloaterModelPreview::addStringToLog(out, false); - doOnIdleOneTime(boost::bind(&LLFloaterModelPreview::toggleCalculateButton, this, true)); - - if (result.has("upload_price")) - { - mModelPhysicsFee = result; - childSetTextArg("upload_fee", "[FEE]", llformat("%d", result["upload_price"].asInteger())); - childSetVisible("upload_fee", true); - } - else - { - mModelPhysicsFee.clear(); - } -} - -/*virtual*/ -void LLFloaterModelPreview::onModelUploadSuccess() -{ - assert_main_thread(); - closeFloater(false); -} - -/*virtual*/ -void LLFloaterModelPreview::onModelUploadFailure() -{ - assert_main_thread(); - toggleCalculateButton(true); - mUploadBtn->setEnabled(true); -} - -bool LLFloaterModelPreview::isModelUploadAllowed() -{ - bool allow_upload = mHasUploadPerm && !mUploadModelUrl.empty(); - if (mModelPreview) - { - allow_upload &= mModelPreview->mModelNoErrors; - } - return allow_upload; -} - -S32 LLFloaterModelPreview::DecompRequest::statusCallback(const char* status, S32 p1, S32 p2) -{ - if (mContinue) - { - setStatusMessage(llformat("%s: %d/%d", status, p1, p2)); - if (LLFloaterModelPreview::sInstance) - { - LLFloaterModelPreview::sInstance->setStatusMessage(mStatusMessage); - } - } - - return mContinue; -} - -void LLFloaterModelPreview::DecompRequest::completed() -{ //called from the main thread - if (mContinue) - { - mModel->setConvexHullDecomposition(mHull); - - if (sInstance) - { - if (mContinue) - { - if (sInstance->mModelPreview) - { - sInstance->mModelPreview->mDirty = true; - LLFloaterModelPreview::sInstance->mModelPreview->refresh(); - } - } - - sInstance->mCurRequest.erase(this); - } - } - else if (sInstance) - { - llassert(sInstance->mCurRequest.find(this) == sInstance->mCurRequest.end()); - } -} - -void dump_llsd_to_file(const LLSD& content, std::string filename); - -void LLFloaterModelPreview::onPermissionsReceived(const LLSD& result) -{ - dump_llsd_to_file(result,"perm_received.xml"); - std::string upload_status = result["mesh_upload_status"].asString(); - // BAP HACK: handle "" for case that MeshUploadFlag cap is broken. - mHasUploadPerm = (("" == upload_status) || ("valid" == upload_status)); - - if (!mHasUploadPerm) - { - LL_WARNS() << "Upload permission set to false because upload_status=\"" << upload_status << "\"" << LL_ENDL; - } - else if (mHasUploadPerm && mUploadModelUrl.empty()) - { - LL_WARNS() << "Upload permission set to true but uploadModelUrl is empty!" << LL_ENDL; - } - - // isModelUploadAllowed() includes mHasUploadPerm - mUploadBtn->setEnabled(isModelUploadAllowed()); - getChild("warning_title")->setVisible(!mHasUploadPerm); - getChild("warning_message")->setVisible(!mHasUploadPerm); -} - -void LLFloaterModelPreview::setPermissonsErrorStatus(S32 status, const std::string& reason) -{ - LL_WARNS() << "LLFloaterModelPreview::setPermissonsErrorStatus(" << status << " : " << reason << ")" << LL_ENDL; - - LLNotificationsUtil::add("MeshUploadPermError"); -} - -bool LLFloaterModelPreview::isModelLoading() -{ - if(mModelPreview) - { - return mModelPreview->mLoading; - } - return false; -} - +/** + * @file llfloatermodelpreview.cpp + * @brief LLFloaterModelPreview class implementation + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llmodelloader.h" +#include "llmodelpreview.h" + +#include "llfloatermodelpreview.h" + +#include "llfilepicker.h" +#include "llimagebmp.h" +#include "llimagetga.h" +#include "llimagejpeg.h" +#include "llimagepng.h" + +#include "llagent.h" +#include "llbutton.h" +#include "llcombobox.h" +#include "llfloaterreg.h" +#include "llfocusmgr.h" +#include "llmeshrepository.h" +#include "llnotificationsutil.h" +#include "llsdutil_math.h" +#include "llskinningutil.h" +#include "lltextbox.h" +#include "lltoolmgr.h" +#include "llui.h" +#include "llviewerwindow.h" +#include "pipeline.h" +#include "llviewercontrol.h" +#include "llviewermenufile.h" //LLFilePickerThread +#include "llstring.h" +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "llsliderctrl.h" +#include "llspinctrl.h" +#include "lltabcontainer.h" +#include "lltrans.h" +#include "llfilesystem.h" +#include "llcallbacklist.h" +#include "llviewertexteditor.h" +#include "llviewernetwork.h" + + +//static +S32 LLFloaterModelPreview::sUploadAmount = 10; +LLFloaterModelPreview* LLFloaterModelPreview::sInstance = NULL; + +// "Retain%" decomp parameter has values from 0.0 to 1.0 by 0.01 +// But according to the UI spec for upload model floater, this parameter +// should be represented by Retain spinner with values from 1 to 100 by 1. +// To achieve this, RETAIN_COEFFICIENT is used while creating spinner +// and when value is requested from spinner. +constexpr double RETAIN_COEFFICIENT = 100; + +// "Cosine%" decomp parameter has values from 0.9 to 1 by 0.001 +// But according to the UI spec for upload model floater, this parameter +// should be represented by Smooth combobox with only 10 values. +// So this const is used as a size of Smooth combobox list. +constexpr S32 SMOOTH_VALUES_NUMBER = 10; +constexpr S32 PREVIEW_RENDER_SIZE = 1024; +constexpr F32 PREVIEW_CAMERA_DISTANCE = 16.f; + +class LLMeshFilePicker : public LLFilePickerThread +{ +public: + LLMeshFilePicker(LLModelPreview* mp, S32 lod); + virtual void notify(const std::vector& filenames); + +private: + LLModelPreview* mMP; + S32 mLOD; +}; + +LLMeshFilePicker::LLMeshFilePicker(LLModelPreview* mp, S32 lod) +: LLFilePickerThread(LLFilePicker::FFLOAD_MODEL) + { + mMP = mp; + mLOD = lod; + } + +void LLMeshFilePicker::notify(const std::vector& filenames) +{ + if(LLAppViewer::instance()->quitRequested()) + { + return; + } + + if (filenames.size() > 0) + { + mMP->loadModel(filenames[0], mLOD); + } + else + { + //closes floater + mMP->loadModel(std::string(), mLOD); + } +} + +//----------------------------------------------------------------------------- +// LLFloaterModelPreview() +//----------------------------------------------------------------------------- +LLFloaterModelPreview::LLFloaterModelPreview(const LLSD& key) : +LLFloaterModelUploadBase(key), +mUploadBtn(NULL), +mCalculateBtn(NULL), +mUploadLogText(NULL), +mTabContainer(NULL), +mAvatarTabIndex(0) +{ + sInstance = this; + mLastMouseX = 0; + mLastMouseY = 0; + mStatusLock = new LLMutex(); + mModelPreview = NULL; + + mLODMode[LLModel::LOD_HIGH] = LLModelPreview::LOD_FROM_FILE; + for (U32 i = 0; i < LLModel::LOD_HIGH; i++) + { + mLODMode[i] = LLModelPreview::MESH_OPTIMIZER_AUTO; + } +} + +//----------------------------------------------------------------------------- +// postBuild() +//----------------------------------------------------------------------------- +bool LLFloaterModelPreview::postBuild() +{ + if (!LLFloater::postBuild()) + { + return false; + } + + childSetCommitCallback("cancel_btn", onCancel, this); + childSetCommitCallback("crease_angle", onGenerateNormalsCommit, this); + getChild("gen_normals")->setCommitCallback(boost::bind(&LLFloaterModelPreview::toggleGenarateNormals, this)); + + childSetCommitCallback("lod_generate", onAutoFillCommit, this); + + for (S32 lod = 0; lod <= LLModel::LOD_HIGH; ++lod) + { + LLComboBox* lod_source_combo = getChild("lod_source_" + lod_name[lod]); + lod_source_combo->setCommitCallback(boost::bind(&LLFloaterModelPreview::onLoDSourceCommit, this, lod)); + lod_source_combo->setCurrentByIndex(mLODMode[lod]); + + getChild("lod_browse_" + lod_name[lod])->setCommitCallback(boost::bind(&LLFloaterModelPreview::onBrowseLOD, this, lod)); + getChild("lod_mode_" + lod_name[lod])->setCommitCallback(boost::bind(&LLFloaterModelPreview::onLODParamCommit, this, lod, false)); + getChild("lod_error_threshold_" + lod_name[lod])->setCommitCallback(boost::bind(&LLFloaterModelPreview::onLODParamCommit, this, lod, false)); + getChild("lod_triangle_limit_" + lod_name[lod])->setCommitCallback(boost::bind(&LLFloaterModelPreview::onLODParamCommit, this, lod, true)); + } + + // Upload/avatar options, they need to refresh errors/notifications + childSetCommitCallback("upload_skin", boost::bind(&LLFloaterModelPreview::onUploadOptionChecked, this, _1), NULL); + childSetCommitCallback("upload_joints", boost::bind(&LLFloaterModelPreview::onUploadOptionChecked, this, _1), NULL); + childSetCommitCallback("lock_scale_if_joint_position", boost::bind(&LLFloaterModelPreview::onUploadOptionChecked, this, _1), NULL); + childSetCommitCallback("upload_textures", boost::bind(&LLFloaterModelPreview::onUploadOptionChecked, this, _1), NULL); + + childSetTextArg("status", "[STATUS]", getString("status_idle")); + + childSetAction("ok_btn", onUpload, this); + childDisable("ok_btn"); + + childSetAction("reset_btn", onReset, this); + + childSetCommitCallback("preview_lod_combo", onPreviewLODCommit, this); + + childSetCommitCallback("import_scale", onImportScaleCommit, this); + childSetCommitCallback("pelvis_offset", onPelvisOffsetCommit, this); + + getChild("description_form")->setKeystrokeCallback(boost::bind(&LLFloaterModelPreview::onDescriptionKeystroke, this, _1), NULL); + + getChild("show_edges")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onViewOptionChecked, this, _1)); + getChild("show_physics")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onViewOptionChecked, this, _1)); + getChild("show_textures")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onViewOptionChecked, this, _1)); + getChild("show_skin_weight")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onShowSkinWeightChecked, this, _1)); + getChild("show_joint_overrides")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onViewOptionChecked, this, _1)); + getChild("show_joint_positions")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onViewOptionChecked, this, _1)); + + childDisable("upload_skin"); + childDisable("upload_joints"); + childDisable("lock_scale_if_joint_position"); + + childSetVisible("skin_too_many_joints", false); + childSetVisible("skin_unknown_joint", false); + + childSetVisible("warning_title", false); + childSetVisible("warning_message", false); + + initDecompControls(); + + LLView* preview_panel = getChild("preview_panel"); + + mPreviewRect = preview_panel->getRect(); + + initModelPreview(); + + //set callbacks for left click on line editor rows + for (U32 i = 0; i <= LLModel::LOD_HIGH; i++) + { + LLTextBox* text = getChild(lod_label_name[i]); + if (text) + { + text->setMouseDownCallback(boost::bind(&LLFloaterModelPreview::setPreviewLOD, this, i)); + } + + text = getChild(lod_triangles_name[i]); + if (text) + { + text->setMouseDownCallback(boost::bind(&LLFloaterModelPreview::setPreviewLOD, this, i)); + } + + text = getChild(lod_vertices_name[i]); + if (text) + { + text->setMouseDownCallback(boost::bind(&LLFloaterModelPreview::setPreviewLOD, this, i)); + } + + text = getChild(lod_status_name[i]); + if (text) + { + text->setMouseDownCallback(boost::bind(&LLFloaterModelPreview::setPreviewLOD, this, i)); + } + } + std::string current_grid = LLGridManager::getInstance()->getGridId(); + std::transform(current_grid.begin(),current_grid.end(),current_grid.begin(),::tolower); + std::string validate_url; + if (current_grid == "agni") + { + validate_url = "http://secondlife.com/my/account/mesh.php"; + } + else if (current_grid == "damballah") + { + // Staging grid has its own naming scheme. + validate_url = "http://secondlife-staging.com/my/account/mesh.php"; + } + else + { + validate_url = llformat("http://secondlife.%s.lindenlab.com/my/account/mesh.php",current_grid.c_str()); + } + getChild("warning_message")->setTextArg("[VURL]", validate_url); + + mUploadBtn = getChild("ok_btn"); + mCalculateBtn = getChild("calculate_btn"); + mUploadLogText = getChild("log_text"); + mTabContainer = getChild("import_tab"); + + LLPanel *panel = mTabContainer->getPanelByName("rigging_panel"); + mAvatarTabIndex = mTabContainer->getIndexForPanel(panel); + panel->getChild("joints_list")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onJointListSelection, this)); + + if (LLConvexDecomposition::getInstance() != NULL) + { + mCalculateBtn->setClickedCallback(boost::bind(&LLFloaterModelPreview::onClickCalculateBtn, this)); + + toggleCalculateButton(true); + } + else + { + mCalculateBtn->setEnabled(false); + } + + return true; +} + +//----------------------------------------------------------------------------- +// reshape() +//----------------------------------------------------------------------------- + +void LLFloaterModelPreview::reshape(S32 width, S32 height, bool called_from_parent) +{ + LLFloaterModelUploadBase::reshape(width, height, called_from_parent); + + LLView* preview_panel = getChild("preview_panel"); + LLRect rect = preview_panel->getRect(); + + if (rect != mPreviewRect) + { + mModelPreview->refresh(); + mPreviewRect = preview_panel->getRect(); + } +} + +//----------------------------------------------------------------------------- +// LLFloaterModelPreview() +//----------------------------------------------------------------------------- +LLFloaterModelPreview::~LLFloaterModelPreview() +{ + sInstance = NULL; + + if ( mModelPreview ) + { + delete mModelPreview; + } + + delete mStatusLock; + mStatusLock = NULL; +} + +void LLFloaterModelPreview::initModelPreview() +{ + if (mModelPreview) + { + delete mModelPreview; + } + + S32 tex_width = 512; + S32 tex_height = 512; + + S32 max_width = llmin(PREVIEW_RENDER_SIZE, (S32)gPipeline.mRT->width); + S32 max_height = llmin(PREVIEW_RENDER_SIZE, (S32)gPipeline.mRT->height); + + while ((tex_width << 1) < max_width) + { + tex_width <<= 1; + } + while ((tex_height << 1) < max_height) + { + tex_height <<= 1; + } + + mModelPreview = new LLModelPreview(tex_width, tex_height, this); + mModelPreview->setPreviewTarget(PREVIEW_CAMERA_DISTANCE); + mModelPreview->setDetailsCallback(boost::bind(&LLFloaterModelPreview::setDetails, this, _1, _2, _3)); + mModelPreview->setModelUpdatedCallback(boost::bind(&LLFloaterModelPreview::modelUpdated, this, _1)); +} + +//static +bool LLFloaterModelPreview::showModelPreview() +{ + LLFloaterModelPreview* fmp = (LLFloaterModelPreview*)LLFloaterReg::getInstance("upload_model"); + if (fmp && !fmp->isModelLoading()) + { + fmp->loadHighLodModel(); + } + return true; +} + +void LLFloaterModelPreview::onUploadOptionChecked(LLUICtrl* ctrl) +{ + if (mModelPreview) + { + auto name = ctrl->getName(); + bool value = ctrl->getValue().asBoolean(); + // update the option and notifications + // (this is a bit convoluted, because of the current structure of mModelPreview) + if (name == "upload_skin") + { + childSetValue("show_skin_weight", value); + mModelPreview->mViewOption["show_skin_weight"] = value; + if (!value) + { + mModelPreview->mViewOption["show_joint_overrides"] = false; + mModelPreview->mViewOption["show_joint_positions"] = false; + childSetValue("show_joint_overrides", false); + childSetValue("show_joint_positions", false); + } + } + else if (name == "upload_joints") + { + if (mModelPreview->mViewOption["show_skin_weight"]) + { + childSetValue("show_joint_overrides", value); + mModelPreview->mViewOption["show_joint_overrides"] = value; + } + } + else if (name == "upload_textures") + { + childSetValue("show_textures", value); + mModelPreview->mViewOption["show_textures"] = value; + } + else if (name == "lock_scale_if_joint_position") + { + mModelPreview->mViewOption["lock_scale_if_joint_position"] = value; + } + + mModelPreview->refresh(); // a 'dirty' flag for render + mModelPreview->resetPreviewTarget(); + mModelPreview->clearBuffers(); + mModelPreview->mDirty = true; + } + // set the button visible, it will be refreshed later + toggleCalculateButton(true); +} + +void LLFloaterModelPreview::onShowSkinWeightChecked(LLUICtrl* ctrl) +{ + if (mModelPreview) + { + mModelPreview->mCameraOffset.clearVec(); + onViewOptionChecked(ctrl); + } +} + +void LLFloaterModelPreview::onViewOptionChecked(LLUICtrl* ctrl) +{ + if (mModelPreview) + { + auto name = ctrl->getName(); + mModelPreview->mViewOption[name] = !mModelPreview->mViewOption[name]; + if (name == "show_physics") + { + auto enabled = mModelPreview->mViewOption[name]; + childSetEnabled("physics_explode", enabled); + childSetVisible("physics_explode", enabled); + } + mModelPreview->refresh(); + } +} + +bool LLFloaterModelPreview::isViewOptionChecked(const LLSD& userdata) +{ + if (mModelPreview) + { + return mModelPreview->mViewOption[userdata.asString()]; + } + + return false; +} + +bool LLFloaterModelPreview::isViewOptionEnabled(const LLSD& userdata) +{ + return getChildView(userdata.asString())->getEnabled(); +} + +void LLFloaterModelPreview::setViewOptionEnabled(const std::string& option, bool enabled) +{ + childSetEnabled(option, enabled); +} + +void LLFloaterModelPreview::enableViewOption(const std::string& option) +{ + setViewOptionEnabled(option, true); +} + +void LLFloaterModelPreview::disableViewOption(const std::string& option) +{ + setViewOptionEnabled(option, false); +} + +void LLFloaterModelPreview::loadHighLodModel() +{ + mModelPreview->mLookUpLodFiles = true; + loadModel(3); +} + +void LLFloaterModelPreview::prepareToLoadModel(S32 lod) +{ + mModelPreview->mLoading = true; + if (lod == LLModel::LOD_PHYSICS) + { + // loading physics from file + mModelPreview->mPhysicsSearchLOD = lod; + mModelPreview->mWarnOfUnmatchedPhyicsMeshes = false; + } +} +void LLFloaterModelPreview::loadModel(S32 lod) +{ + prepareToLoadModel(lod); + (new LLMeshFilePicker(mModelPreview, lod))->getFile(); +} + +void LLFloaterModelPreview::loadModel(S32 lod, const std::string& file_name, bool force_disable_slm) +{ + prepareToLoadModel(lod); + mModelPreview->loadModel(file_name, lod, force_disable_slm); +} + +void LLFloaterModelPreview::onClickCalculateBtn() +{ + clearLogTab(); + addStringToLog("Calculating model data.", false); + mModelPreview->rebuildUploadData(); + + bool upload_skinweights = childGetValue("upload_skin").asBoolean(); + bool upload_joint_positions = childGetValue("upload_joints").asBoolean(); + bool lock_scale_if_joint_position = childGetValue("lock_scale_if_joint_position").asBoolean(); + + mUploadModelUrl.clear(); + mModelPhysicsFee.clear(); + + gMeshRepo.uploadModel(mModelPreview->mUploadData, mModelPreview->mPreviewScale, + childGetValue("upload_textures").asBoolean(), + upload_skinweights, upload_joint_positions, lock_scale_if_joint_position, + mUploadModelUrl, false, + getWholeModelFeeObserverHandle()); + + toggleCalculateButton(false); + mUploadBtn->setEnabled(false); + + //disable "simplification" UI + LLPanel* simplification_panel = getChild("physics simplification"); + LLView* child = simplification_panel->getFirstChild(); + while (child) + { + child->setEnabled(false); + child = simplification_panel->findNextSibling(child); + } +} + +// Modified cell_params, make sure to clear values if you have to reuse cell_params outside of this function +void add_row_to_list(LLScrollListCtrl *listp, + LLScrollListCell::Params &cell_params, + const LLSD &item_value, + const std::string &name, + const LLSD &vx, + const LLSD &vy, + const LLSD &vz) +{ + LLScrollListItem::Params item_params; + item_params.value = item_value; + + cell_params.column = "model_name"; + cell_params.value = name; + + item_params.columns.add(cell_params); + + cell_params.column = "axis_x"; + cell_params.value = vx; + item_params.columns.add(cell_params); + + cell_params.column = "axis_y"; + cell_params.value = vy; + item_params.columns.add(cell_params); + + cell_params.column = "axis_z"; + cell_params.value = vz; + + item_params.columns.add(cell_params); + + listp->addRow(item_params); +} + +void populate_list_with_overrides(LLScrollListCtrl *listp, const LLJointOverrideData &data, bool include_overrides) +{ + if (data.mModelsNoOverrides.empty() && data.mPosOverrides.empty()) + { + return; + } + + static const std::string no_override_placeholder = "-"; + + S32 count = 0; + LLScrollListCell::Params cell_params; + cell_params.font = LLFontGL::getFontSansSerif(); + // Start out right justifying numeric displays + cell_params.font_halign = LLFontGL::HCENTER; + + std::map::const_iterator map_iter = data.mPosOverrides.begin(); + std::map::const_iterator map_end = data.mPosOverrides.end(); + while (map_iter != map_end) + { + if (include_overrides) + { + add_row_to_list(listp, + cell_params, + LLSD::Integer(count), + map_iter->first, + LLSD::Real(map_iter->second.mV[VX]), + LLSD::Real(map_iter->second.mV[VY]), + LLSD::Real(map_iter->second.mV[VZ])); + } + else + { + add_row_to_list(listp, + cell_params, + LLSD::Integer(count), + map_iter->first, + no_override_placeholder, + no_override_placeholder, + no_override_placeholder); + } + count++; + map_iter++; + } + + std::set::const_iterator set_iter = data.mModelsNoOverrides.begin(); + std::set::const_iterator set_end = data.mModelsNoOverrides.end(); + while (set_iter != set_end) + { + add_row_to_list(listp, + cell_params, + LLSD::Integer(count), + *set_iter, + no_override_placeholder, + no_override_placeholder, + no_override_placeholder); + count++; + set_iter++; + } +} + +void LLFloaterModelPreview::onJointListSelection() +{ + S32 display_lod = mModelPreview->mPreviewLOD; + LLPanel *panel = mTabContainer->getPanelByName("rigging_panel"); + LLScrollListCtrl *joints_list = panel->getChild("joints_list"); + LLScrollListCtrl *joints_pos = panel->getChild("pos_overrides_list"); + LLScrollListCtrl *joints_scale = panel->getChild("scale_overrides_list"); + LLTextBox *joint_pos_descr = panel->getChild("pos_overrides_descr"); + + joints_pos->deleteAllItems(); + joints_scale->deleteAllItems(); + + LLScrollListItem *selected = joints_list->getFirstSelected(); + if (selected) + { + std::string label = selected->getValue().asString(); + LLJointOverrideData &data = mJointOverrides[display_lod][label]; + bool upload_joint_positions = childGetValue("upload_joints").asBoolean(); + populate_list_with_overrides(joints_pos, data, upload_joint_positions); + + joint_pos_descr->setTextArg("[JOINT]", label); + mSelectedJointName = label; + } + else + { + // temporary value (shouldn't happen) + std::string label = "mPelvis"; + joint_pos_descr->setTextArg("[JOINT]", label); + mSelectedJointName.clear(); + } + + // Note: We can make a version of renderBones() to highlight selected joint +} + +void LLFloaterModelPreview::onDescriptionKeystroke(LLUICtrl* ctrl) +{ + // Workaround for SL-4186, server doesn't allow name changes after 'calculate' stage + LLLineEditor* input = static_cast(ctrl); + if (input->isDirty()) // dirty will be reset after commit + { + toggleCalculateButton(true); + } +} + +//static +void LLFloaterModelPreview::onImportScaleCommit(LLUICtrl*,void* userdata) +{ + LLFloaterModelPreview *fp =(LLFloaterModelPreview *)userdata; + + if (!fp->mModelPreview) + { + return; + } + + fp->mModelPreview->mDirty = true; + + fp->toggleCalculateButton(true); + + fp->mModelPreview->refresh(); +} +//static +void LLFloaterModelPreview::onPelvisOffsetCommit( LLUICtrl*, void* userdata ) +{ + LLFloaterModelPreview *fp =(LLFloaterModelPreview*)userdata; + + if (!fp->mModelPreview) + { + return; + } + + fp->mModelPreview->mDirty = true; + + fp->toggleCalculateButton(true); + + fp->mModelPreview->refresh(); +} + +//static +void LLFloaterModelPreview::onPreviewLODCommit(LLUICtrl* ctrl, void* userdata) +{ + LLFloaterModelPreview *fp =(LLFloaterModelPreview *)userdata; + + if (!fp->mModelPreview) + { + return; + } + + S32 which_mode = 0; + + LLComboBox* combo = (LLComboBox*) ctrl; + + which_mode = (NUM_LOD-1)-combo->getFirstSelectedIndex(); // combo box list of lods is in reverse order + + fp->mModelPreview->setPreviewLOD(which_mode); +} + +//static +void LLFloaterModelPreview::onGenerateNormalsCommit(LLUICtrl* ctrl, void* userdata) +{ + LLFloaterModelPreview* fp = (LLFloaterModelPreview*) userdata; + + fp->mModelPreview->generateNormals(); +} + +void LLFloaterModelPreview::toggleGenarateNormals() +{ + bool enabled = childGetValue("gen_normals").asBoolean(); + mModelPreview->mViewOption["gen_normals"] = enabled; + childSetEnabled("crease_angle", enabled); + if(enabled) { + mModelPreview->generateNormals(); + } else { + mModelPreview->restoreNormals(); + } +} + +//static +void LLFloaterModelPreview::onExplodeCommit(LLUICtrl* ctrl, void* userdata) +{ + LLFloaterModelPreview* fp = LLFloaterModelPreview::sInstance; + + fp->mModelPreview->refresh(); +} + +//static +void LLFloaterModelPreview::onAutoFillCommit(LLUICtrl* ctrl, void* userdata) +{ + LLFloaterModelPreview* fp = (LLFloaterModelPreview*) userdata; + + fp->mModelPreview->queryLODs(); +} + +void LLFloaterModelPreview::onLODParamCommit(S32 lod, bool enforce_tri_limit) +{ + LLComboBox* lod_source_combo = getChild("lod_source_" + lod_name[lod]); + S32 mode = lod_source_combo->getCurrentIndex(); + switch (mode) + { + case LLModelPreview::MESH_OPTIMIZER_AUTO: + case LLModelPreview::MESH_OPTIMIZER_SLOPPY: + case LLModelPreview::MESH_OPTIMIZER_PRECISE: + mModelPreview->onLODMeshOptimizerParamCommit(lod, enforce_tri_limit, mode); + break; + default: + LL_ERRS() << "Only supposed to be called to generate models" << LL_ENDL; + break; + } + + //refresh LoDs that reference this one + for (S32 i = lod - 1; i >= 0; --i) + { + LLComboBox* lod_source_combo = getChild("lod_source_" + lod_name[i]); + if (lod_source_combo->getCurrentIndex() == LLModelPreview::USE_LOD_ABOVE) + { + onLoDSourceCommit(i); + } + else + { + break; + } + } +} + +void LLFloaterModelPreview::draw3dPreview() +{ + gGL.color3f(1.f, 1.f, 1.f); + + gGL.getTexUnit(0)->bind(mModelPreview); + + gGL.begin( LLRender::QUADS ); + { + gGL.texCoord2f(0.f, 1.f); + gGL.vertex2i(mPreviewRect.mLeft+1, mPreviewRect.mTop-1); + gGL.texCoord2f(0.f, 0.f); + gGL.vertex2i(mPreviewRect.mLeft+1, mPreviewRect.mBottom+1); + gGL.texCoord2f(1.f, 0.f); + gGL.vertex2i(mPreviewRect.mRight-1, mPreviewRect.mBottom+1); + gGL.texCoord2f(1.f, 1.f); + gGL.vertex2i(mPreviewRect.mRight-1, mPreviewRect.mTop-1); + } + gGL.end(); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); +} + +//----------------------------------------------------------------------------- +// draw() +//----------------------------------------------------------------------------- +void LLFloaterModelPreview::draw() +{ + LLFloater::draw(); + + if (!mModelPreview) + { + return; + } + + mModelPreview->update(); + + if (!mModelPreview->mLoading) + { + if ( mModelPreview->getLoadState() == LLModelLoader::ERROR_MATERIALS ) + { + childSetTextArg("status", "[STATUS]", getString("status_material_mismatch")); + } + else + if ( mModelPreview->getLoadState() > LLModelLoader::ERROR_MODEL ) + { + childSetTextArg("status", "[STATUS]", getString(LLModel::getStatusString(mModelPreview->getLoadState() - LLModelLoader::ERROR_MODEL))); + } + else + if ( mModelPreview->getLoadState() == LLModelLoader::ERROR_PARSING ) + { + childSetTextArg("status", "[STATUS]", getString("status_parse_error")); + toggleCalculateButton(false); + } + else + if (mModelPreview->getLoadState() == LLModelLoader::WARNING_BIND_SHAPE_ORIENTATION) + { + childSetTextArg("status", "[STATUS]", getString("status_bind_shape_orientation")); + } + else + { + childSetTextArg("status", "[STATUS]", getString("status_idle")); + } + } + + if (!isMinimized() && mModelPreview->lodsReady()) + { + draw3dPreview(); + } +} + +//----------------------------------------------------------------------------- +// handleMouseDown() +//----------------------------------------------------------------------------- +bool LLFloaterModelPreview::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (mPreviewRect.pointInRect(x, y)) + { + bringToFront( x, y ); + gFocusMgr.setMouseCapture(this); + gViewerWindow->hideCursor(); + mLastMouseX = x; + mLastMouseY = y; + return true; + } + + return LLFloater::handleMouseDown(x, y, mask); +} + +//----------------------------------------------------------------------------- +// handleMouseUp() +//----------------------------------------------------------------------------- +bool LLFloaterModelPreview::handleMouseUp(S32 x, S32 y, MASK mask) +{ + gFocusMgr.setMouseCapture(nullptr); + gViewerWindow->showCursor(); + return LLFloater::handleMouseUp(x, y, mask); +} + +//----------------------------------------------------------------------------- +// handleHover() +//----------------------------------------------------------------------------- +bool LLFloaterModelPreview::handleHover (S32 x, S32 y, MASK mask) +{ + MASK local_mask = mask & ~MASK_ALT; + + if (mModelPreview && hasMouseCapture()) + { + if (local_mask == MASK_PAN) + { + // pan here + mModelPreview->pan((F32)(x - mLastMouseX) * -0.005f, (F32)(y - mLastMouseY) * -0.005f); + } + else if (local_mask == MASK_ORBIT) + { + F32 yaw_radians = (F32)(x - mLastMouseX) * -0.01f; + F32 pitch_radians = (F32)(y - mLastMouseY) * 0.02f; + + mModelPreview->rotate(yaw_radians, pitch_radians); + } + else + { + + F32 yaw_radians = (F32)(x - mLastMouseX) * -0.01f; + F32 zoom_amt = (F32)(y - mLastMouseY) * 0.02f; + + mModelPreview->rotate(yaw_radians, 0.f); + mModelPreview->zoom(zoom_amt); + } + + + mModelPreview->refresh(); + + LLUI::getInstance()->setMousePositionLocal(this, mLastMouseX, mLastMouseY); + } + + if (!mPreviewRect.pointInRect(x, y) || !mModelPreview) + { + return LLFloater::handleHover(x, y, mask); + } + else if (local_mask == MASK_ORBIT) + { + gViewerWindow->setCursor(UI_CURSOR_TOOLCAMERA); + } + else if (local_mask == MASK_PAN) + { + gViewerWindow->setCursor(UI_CURSOR_TOOLPAN); + } + else + { + gViewerWindow->setCursor(UI_CURSOR_TOOLZOOMIN); + } + + return true; +} + +//----------------------------------------------------------------------------- +// handleScrollWheel() +//----------------------------------------------------------------------------- +bool LLFloaterModelPreview::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + if (mPreviewRect.pointInRect(x, y) && mModelPreview) + { + mModelPreview->zoom((F32)clicks * -0.2f); + mModelPreview->refresh(); + } + else + { + LLFloaterModelUploadBase::handleScrollWheel(x, y, clicks); + } + return true; +} + +/*virtual*/ +void LLFloaterModelPreview::onOpen(const LLSD& key) +{ + LLModelPreview::sIgnoreLoadedCallback = false; + requestAgentUploadPermissions(); +} + +/*virtual*/ +void LLFloaterModelPreview::onClose(bool app_quitting) +{ + LLModelPreview::sIgnoreLoadedCallback = true; +} + +//static +void LLFloaterModelPreview::onPhysicsParamCommit(LLUICtrl* ctrl, void* data) +{ + if (LLConvexDecomposition::getInstance() == NULL) + { + LL_INFOS() << "convex decomposition tool is a stub on this platform. cannot get decomp." << LL_ENDL; + return; + } + + if (sInstance) + { + LLCDParam* param = (LLCDParam*) data; + std::string name(param->mName); + + LLSD value = ctrl->getValue(); + + if("Retain%" == name) + { + value = ctrl->getValue().asReal() / RETAIN_COEFFICIENT; + } + + sInstance->mDecompParams[name] = value; + + if (name == "Simplify Method") + { + bool show_retain = false; + bool show_detail = true; + + if (ctrl->getValue().asInteger() == 0) + { + show_retain = true; + show_detail = false; + } + + sInstance->childSetVisible("Retain%", show_retain); + sInstance->childSetVisible("Retain%_label", show_retain); + + sInstance->childSetVisible("Detail Scale", show_detail); + sInstance->childSetVisible("Detail Scale label", show_detail); + } + } +} + +//static +void LLFloaterModelPreview::onPhysicsStageExecute(LLUICtrl* ctrl, void* data) +{ + LLCDStageData* stage_data = (LLCDStageData*) data; + std::string stage = stage_data->mName; + + if (sInstance) + { + if (!sInstance->mCurRequest.empty()) + { + LL_INFOS() << "Decomposition request still pending." << LL_ENDL; + return; + } + + if (sInstance->mModelPreview) + { + for (S32 i = 0; i < sInstance->mModelPreview->mModel[LLModel::LOD_PHYSICS].size(); ++i) + { + LLModel* mdl = sInstance->mModelPreview->mModel[LLModel::LOD_PHYSICS][i]; + DecompRequest* request = new DecompRequest(stage, mdl); + sInstance->mCurRequest.insert(request); + gMeshRepo.mDecompThread->submitRequest(request); + } + } + + if (stage == "Decompose") + { + sInstance->setStatusMessage(sInstance->getString("decomposing")); + sInstance->childSetVisible("Decompose", false); + sInstance->childSetVisible("decompose_cancel", true); + sInstance->childDisable("Simplify"); + } + else if (stage == "Simplify") + { + sInstance->setStatusMessage(sInstance->getString("simplifying")); + sInstance->childSetVisible("Simplify", false); + sInstance->childSetVisible("simplify_cancel", true); + sInstance->childDisable("Decompose"); + } + } +} + +//static +void LLFloaterModelPreview::onPhysicsBrowse(LLUICtrl* ctrl, void* userdata) +{ + sInstance->loadModel(LLModel::LOD_PHYSICS); +} + +//static +void LLFloaterModelPreview::onPhysicsUseLOD(LLUICtrl* ctrl, void* userdata) +{ + S32 num_lods = 4; + S32 which_mode; + + LLCtrlSelectionInterface* iface = sInstance->childGetSelectionInterface("physics_lod_combo"); + if (iface) + { + which_mode = iface->getFirstSelectedIndex(); + } + else + { + LL_WARNS() << "no iface" << LL_ENDL; + return; + } + + if (which_mode <= 0) + { + LL_WARNS() << "which_mode out of range, " << which_mode << LL_ENDL; + } + + S32 file_mode = iface->getItemCount() - 1; + S32 cube_mode = file_mode - 1; + if (which_mode < cube_mode) + { + S32 which_lod = num_lods - which_mode; + sInstance->mModelPreview->setPhysicsFromLOD(which_lod); + } + else if (which_mode == cube_mode) + { + std::string path = gDirUtilp->getAppRODataDir(); + gDirUtilp->append(path, "cube.dae"); + sInstance->loadModel(LLModel::LOD_PHYSICS, path); + } + + LLModelPreview *model_preview = sInstance->mModelPreview; + if (model_preview) + { + model_preview->refresh(); + model_preview->updateStatusMessages(); + } +} + +//static +void LLFloaterModelPreview::onCancel(LLUICtrl* ctrl, void* data) +{ + if (sInstance) + { + sInstance->closeFloater(false); + } +} + +//static +void LLFloaterModelPreview::onPhysicsStageCancel(LLUICtrl* ctrl, void*data) +{ + if (sInstance) + { + for (std::set >::iterator iter = sInstance->mCurRequest.begin(); + iter != sInstance->mCurRequest.end(); ++iter) + { + DecompRequest* req = *iter; + req->mContinue = 0; + } + + sInstance->mCurRequest.clear(); + + if (sInstance->mModelPreview) + { + sInstance->mModelPreview->updateStatusMessages(); + } + } +} + +void LLFloaterModelPreview::initDecompControls() +{ + LLSD key; + + childSetCommitCallback("simplify_cancel", onPhysicsStageCancel, NULL); + childSetCommitCallback("decompose_cancel", onPhysicsStageCancel, NULL); + + childSetCommitCallback("physics_lod_combo", onPhysicsUseLOD, NULL); + childSetCommitCallback("physics_browse", onPhysicsBrowse, NULL); + + static const LLCDStageData* stage = NULL; + static S32 stage_count = 0; + + if (!stage && LLConvexDecomposition::getInstance() != NULL) + { + stage_count = LLConvexDecomposition::getInstance()->getStages(&stage); + } + + static const LLCDParam* param = NULL; + static S32 param_count = 0; + if (!param && LLConvexDecomposition::getInstance() != NULL) + { + param_count = LLConvexDecomposition::getInstance()->getParameters(¶m); + } + + for (S32 j = stage_count-1; j >= 0; --j) + { + LLButton* button = getChild(stage[j].mName); + if (button) + { + button->setCommitCallback(onPhysicsStageExecute, (void*) &stage[j]); + } + + gMeshRepo.mDecompThread->mStageID[stage[j].mName] = j; + // protected against stub by stage_count being 0 for stub above + LLConvexDecomposition::getInstance()->registerCallback(j, LLPhysicsDecomp::llcdCallback); + + //LL_INFOS() << "Physics decomp stage " << stage[j].mName << " (" << j << ") parameters:" << LL_ENDL; + //LL_INFOS() << "------------------------------------" << LL_ENDL; + + for (S32 i = 0; i < param_count; ++i) + { + if (param[i].mStage != j) + { + continue; + } + + std::string name(param[i].mName ? param[i].mName : ""); + std::string description(param[i].mDescription ? param[i].mDescription : ""); + + std::string type = "unknown"; + + LL_INFOS() << name << " - " << description << LL_ENDL; + + if (param[i].mType == LLCDParam::LLCD_FLOAT) + { + mDecompParams[param[i].mName] = LLSD(param[i].mDefault.mFloat); + //LL_INFOS() << "Type: float, Default: " << param[i].mDefault.mFloat << LL_ENDL; + + + LLUICtrl* ctrl = getChild(name); + if (LLSliderCtrl* slider = dynamic_cast(ctrl)) + { + slider->setMinValue(param[i].mDetails.mRange.mLow.mFloat); + slider->setMaxValue(param[i].mDetails.mRange.mHigh.mFloat); + slider->setIncrement(param[i].mDetails.mRange.mDelta.mFloat); + slider->setValue(param[i].mDefault.mFloat); + slider->setCommitCallback(onPhysicsParamCommit, (void*) ¶m[i]); + } + else if (LLSpinCtrl* spinner = dynamic_cast(ctrl)) + { + bool is_retain_ctrl = "Retain%" == name; + double coefficient = is_retain_ctrl ? RETAIN_COEFFICIENT : 1.f; + + spinner->setMinValue(param[i].mDetails.mRange.mLow.mFloat * coefficient); + spinner->setMaxValue(param[i].mDetails.mRange.mHigh.mFloat * coefficient); + spinner->setIncrement(param[i].mDetails.mRange.mDelta.mFloat * coefficient); + spinner->setValue(param[i].mDefault.mFloat * coefficient); + spinner->setCommitCallback(onPhysicsParamCommit, (void*) ¶m[i]); + } + else if (LLComboBox* combo_box = dynamic_cast(ctrl)) + { + float min = param[i].mDetails.mRange.mLow.mFloat; + float max = param[i].mDetails.mRange.mHigh.mFloat; + float delta = param[i].mDetails.mRange.mDelta.mFloat; + + bool is_smooth_cb = ("Cosine%" == name); + if (is_smooth_cb) + { + createSmoothComboBox(combo_box, min, max); + } + else + { + for(float value = min; value <= max; value += delta) + { + std::string label = llformat("%.1f", value); + combo_box->add(label, value, ADD_BOTTOM, true); + } + } + combo_box->setValue(is_smooth_cb ? 0: param[i].mDefault.mFloat); + combo_box->setCommitCallback(onPhysicsParamCommit, (void*) ¶m[i]); + } + } + else if (param[i].mType == LLCDParam::LLCD_INTEGER) + { + mDecompParams[param[i].mName] = LLSD(param[i].mDefault.mIntOrEnumValue); + //LL_INFOS() << "Type: integer, Default: " << param[i].mDefault.mIntOrEnumValue << LL_ENDL; + + + LLUICtrl* ctrl = getChild(name); + if (LLSliderCtrl* slider = dynamic_cast(ctrl)) + { + slider->setMinValue(param[i].mDetails.mRange.mLow.mIntOrEnumValue); + slider->setMaxValue(param[i].mDetails.mRange.mHigh.mIntOrEnumValue); + slider->setIncrement(param[i].mDetails.mRange.mDelta.mIntOrEnumValue); + slider->setValue(param[i].mDefault.mIntOrEnumValue); + slider->setCommitCallback(onPhysicsParamCommit, (void*) ¶m[i]); + } + else if (LLComboBox* combo_box = dynamic_cast(ctrl)) + { + for(int k = param[i].mDetails.mRange.mLow.mIntOrEnumValue; k<=param[i].mDetails.mRange.mHigh.mIntOrEnumValue; k+=param[i].mDetails.mRange.mDelta.mIntOrEnumValue) + { + std::string name = llformat("%.1d", k); + combo_box->add(name, k, ADD_BOTTOM, true); + } + combo_box->setValue(param[i].mDefault.mIntOrEnumValue); + combo_box->setCommitCallback(onPhysicsParamCommit, (void*) ¶m[i]); + } + } + else if (param[i].mType == LLCDParam::LLCD_BOOLEAN) + { + mDecompParams[param[i].mName] = LLSD(param[i].mDefault.mBool); + //LL_INFOS() << "Type: boolean, Default: " << (param[i].mDefault.mBool ? "True" : "False") << LL_ENDL; + + LLCheckBoxCtrl* check_box = getChild(name); + if (check_box) + { + check_box->setValue(param[i].mDefault.mBool); + check_box->setCommitCallback(onPhysicsParamCommit, (void*) ¶m[i]); + } + } + else if (param[i].mType == LLCDParam::LLCD_ENUM) + { + mDecompParams[param[i].mName] = LLSD(param[i].mDefault.mIntOrEnumValue); + //LL_INFOS() << "Type: enum, Default: " << param[i].mDefault.mIntOrEnumValue << LL_ENDL; + + { //plug into combo box + + //LL_INFOS() << "Accepted values: " << LL_ENDL; + LLComboBox* combo_box = getChild(name); + for (S32 k = 0; k < param[i].mDetails.mEnumValues.mNumEnums; ++k) + { + //LL_INFOS() << param[i].mDetails.mEnumValues.mEnumsArray[k].mValue + // << " - " << param[i].mDetails.mEnumValues.mEnumsArray[k].mName << LL_ENDL; + + std::string name(param[i].mDetails.mEnumValues.mEnumsArray[k].mName); + std::string localized_name; + bool is_localized = LLTrans::findString(localized_name, name); + + combo_box->add(is_localized ? localized_name : name, + LLSD::Integer(param[i].mDetails.mEnumValues.mEnumsArray[k].mValue)); + } + combo_box->setValue(param[i].mDefault.mIntOrEnumValue); + combo_box->setCommitCallback(onPhysicsParamCommit, (void*) ¶m[i]); + } + + //LL_INFOS() << "----" << LL_ENDL; + } + //LL_INFOS() << "-----------------------------" << LL_ENDL; + } + } + mDefaultDecompParams = mDecompParams; + childSetCommitCallback("physics_explode", LLFloaterModelPreview::onExplodeCommit, this); +} + +void LLFloaterModelPreview::createSmoothComboBox(LLComboBox* combo_box, float min, float max) +{ + float delta = (max - min) / SMOOTH_VALUES_NUMBER; + int ilabel = 0; + + combo_box->add("0 (none)", ADD_BOTTOM, true); + + for(float value = min + delta; value < max; value += delta) + { + std::string label = (++ilabel == SMOOTH_VALUES_NUMBER) ? "10 (max)" : llformat("%.1d", ilabel); + combo_box->add(label, value, ADD_BOTTOM, true); + } + + +} + +//----------------------------------------------------------------------------- +// onMouseCaptureLost() +//----------------------------------------------------------------------------- +// static +void LLFloaterModelPreview::onMouseCaptureLostModelPreview(LLMouseHandler* handler) +{ + gViewerWindow->showCursor(); +} + +//----------------------------------------------------------------------------- +// addStringToLog() +//----------------------------------------------------------------------------- +//static +void LLFloaterModelPreview::addStringToLog(const std::string& message, const LLSD& args, bool flash, S32 lod) +{ + if (sInstance && sInstance->hasString(message)) + { + std::string str; + switch (lod) +{ + case LLModel::LOD_IMPOSTOR: str = "LOD0 "; break; + case LLModel::LOD_LOW: str = "LOD1 "; break; + case LLModel::LOD_MEDIUM: str = "LOD2 "; break; + case LLModel::LOD_PHYSICS: str = "PHYS "; break; + case LLModel::LOD_HIGH: str = "LOD3 "; break; + default: break; +} + + LLStringUtil::format_map_t args_msg; + LLSD::map_const_iterator iter = args.beginMap(); + LLSD::map_const_iterator end = args.endMap(); + for (; iter != end; ++iter) +{ + args_msg[iter->first] = iter->second.asString(); + } + str += sInstance->getString(message, args_msg); + sInstance->addStringToLogTab(str, flash); + } + } + +// static +void LLFloaterModelPreview::addStringToLog(const std::string& str, bool flash) + { + if (sInstance) + { + sInstance->addStringToLogTab(str, flash); + } + } + +// static +void LLFloaterModelPreview::addStringToLog(const std::ostringstream& strm, bool flash) + { + if (sInstance) + { + sInstance->addStringToLogTab(strm.str(), flash); + } + } + +void LLFloaterModelPreview::clearAvatarTab() +{ + LLPanel *panel = mTabContainer->getPanelByName("rigging_panel"); + LLScrollListCtrl *joints_list = panel->getChild("joints_list"); + joints_list->deleteAllItems(); + LLScrollListCtrl *joints_pos = panel->getChild("pos_overrides_list"); + joints_pos->deleteAllItems(); mSelectedJointName.clear(); + + for (U32 i = 0; i < LLModel::NUM_LODS; ++i) +{ + mJointOverrides[i].clear(); + } + + LLTextBox *joint_total_descr = panel->getChild("conflicts_description"); + joint_total_descr->setTextArg("[CONFLICTS]", llformat("%d", 0)); + joint_total_descr->setTextArg("[JOINTS_COUNT]", llformat("%d", 0)); + + + LLTextBox *joint_pos_descr = panel->getChild("pos_overrides_descr"); + joint_pos_descr->setTextArg("[JOINT]", std::string("mPelvis")); // Might be better to hide it + } + +void LLFloaterModelPreview::updateAvatarTab(bool highlight_overrides) +{ + S32 display_lod = mModelPreview->mPreviewLOD; + if (mModelPreview->mModel[display_lod].empty()) + { + mSelectedJointName.clear(); + return; + } + + // Joints will be listed as long as they are listed in mAlternateBindMatrix + // even if they are for some reason identical to defaults. + // Todo: Are overrides always identical for all lods? They normally are, but there might be situations where they aren't. + if (mJointOverrides[display_lod].empty()) + { + // populate map + for (LLModelLoader::scene::iterator iter = mModelPreview->mScene[display_lod].begin(); iter != mModelPreview->mScene[display_lod].end(); ++iter) + { + for (LLModelLoader::model_instance_list::iterator model_iter = iter->second.begin(); model_iter != iter->second.end(); ++model_iter) + { + LLModelInstance& instance = *model_iter; + LLModel* model = instance.mModel; + const LLMeshSkinInfo *skin = &model->mSkinInfo; + U32 joint_count = LLSkinningUtil::getMeshJointCount(skin); + U32 bind_count = highlight_overrides ? skin->mAlternateBindMatrix.size() : 0; // simply do not include overrides if data is not needed + if (bind_count > 0 && bind_count != joint_count) + { + std::ostringstream out; + out << "Invalid joint overrides for model " << model->getName(); + out << ". Amount of joints " << joint_count; + out << ", is different from amount of overrides " << bind_count; + LL_INFOS() << out.str() << LL_ENDL; + addStringToLog(out.str(), true); + // Disable overrides for this model + bind_count = 0; + } + if (bind_count > 0) + { + for (U32 j = 0; j < joint_count; ++j) + { + const LLVector3& joint_pos = LLVector3(skin->mAlternateBindMatrix[j].getTranslation()); + LLJointOverrideData &data = mJointOverrides[display_lod][skin->mJointNames[j]]; + + LLJoint* pJoint = LLModelPreview::lookupJointByName(skin->mJointNames[j], mModelPreview); + if (pJoint) + { + // see how voavatar uses aboveJointPosThreshold + if (pJoint->aboveJointPosThreshold(joint_pos)) + { + // valid override + if (data.mPosOverrides.size() > 0 + && (data.mPosOverrides.begin()->second - joint_pos).lengthSquared() > (LL_JOINT_TRESHOLD_POS_OFFSET * LL_JOINT_TRESHOLD_POS_OFFSET)) + { + // File contains multiple meshes with conflicting joint offsets + // preview may be incorrect, upload result might wary (depends onto + // mesh_id that hasn't been generated yet). + data.mHasConflicts = true; + } + data.mPosOverrides[model->getName()] = joint_pos; + } + else + { + // default value, it won't be accounted for by avatar + data.mModelsNoOverrides.insert(model->getName()); + } + } + } + } + else + { + for (U32 j = 0; j < joint_count; ++j) + { + LLJointOverrideData &data = mJointOverrides[display_lod][skin->mJointNames[j]]; + data.mModelsNoOverrides.insert(model->getName()); + } + } + } + } + } + + LLPanel *panel = mTabContainer->getPanelByName("rigging_panel"); + LLScrollListCtrl *joints_list = panel->getChild("joints_list"); + + if (joints_list->isEmpty()) + { + // Populate table + + std::map joint_alias_map; + mModelPreview->getJointAliases(joint_alias_map); + + S32 conflicts = 0; + joint_override_data_map_t::iterator joint_iter = mJointOverrides[display_lod].begin(); + joint_override_data_map_t::iterator joint_end = mJointOverrides[display_lod].end(); + while (joint_iter != joint_end) + { + const std::string& listName = joint_iter->first; + + LLScrollListItem::Params item_params; + item_params.value(listName); + + LLScrollListCell::Params cell_params; + cell_params.font = LLFontGL::getFontSansSerif(); + cell_params.value = listName; + if (joint_alias_map.find(listName) == joint_alias_map.end()) + { + // Missing names + cell_params.color = LLColor4::red; + } + if (joint_iter->second.mHasConflicts) + { + // Conflicts + cell_params.color = LLColor4::orange; + conflicts++; + } + if (highlight_overrides && joint_iter->second.mPosOverrides.size() > 0) + { + cell_params.font.style = "BOLD"; + } + + item_params.columns.add(cell_params); + + joints_list->addRow(item_params, ADD_BOTTOM); + joint_iter++; + } + joints_list->selectFirstItem(); + LLScrollListItem *selected = joints_list->getFirstSelected(); + if (selected) + { + mSelectedJointName = selected->getValue().asString(); + } + + LLTextBox *joint_conf_descr = panel->getChild("conflicts_description"); + joint_conf_descr->setTextArg("[CONFLICTS]", llformat("%d", conflicts)); + joint_conf_descr->setTextArg("[JOINTS_COUNT]", llformat("%d", mJointOverrides[display_lod].size())); + } +} + +//----------------------------------------------------------------------------- +// addStringToLogTab() +//----------------------------------------------------------------------------- +void LLFloaterModelPreview::addStringToLogTab(const std::string& str, bool flash) +{ + if (str.empty()) + { + return; + } + + LLWString text = utf8str_to_wstring(str); + S32 add_text_len = text.length() + 1; // newline + S32 editor_max_len = mUploadLogText->getMaxTextLength(); + if (add_text_len > editor_max_len) + { + return; + } + + // Make sure we have space for new string + S32 editor_text_len = mUploadLogText->getLength(); + if (editor_max_len < (editor_text_len + add_text_len) + && mUploadLogText->getLineCount() <= 0) + { + mUploadLogText->getTextBoundingRect();// forces a reflow() to fix line count + } + while (editor_max_len < (editor_text_len + add_text_len)) + { + S32 shift = mUploadLogText->removeFirstLine(); + if (shift > 0) + { + // removed a line + editor_text_len -= shift; +} + else + { + //nothing to remove? + LL_WARNS() << "Failed to clear log lines" << LL_ENDL; + break; + } + } + + mUploadLogText->appendText(str, true); + + if (flash) + { + LLPanel* panel = mTabContainer->getPanelByName("logs_panel"); + if (mTabContainer->getCurrentPanel() != panel) + { + mTabContainer->setTabPanelFlashing(panel, true); + } + } + } + +void LLFloaterModelPreview::setDetails(F32 x, F32 y, F32 z) +{ + assert_main_thread(); + childSetTextArg("import_dimensions", "[X]", llformat("%.3f", x)); + childSetTextArg("import_dimensions", "[Y]", llformat("%.3f", y)); + childSetTextArg("import_dimensions", "[Z]", llformat("%.3f", z)); + } + +void LLFloaterModelPreview::setPreviewLOD(S32 lod) + { + if (mModelPreview) + { + mModelPreview->setPreviewLOD(lod); + } + } + +void LLFloaterModelPreview::onBrowseLOD(S32 lod) +{ + assert_main_thread(); + + loadModel(lod); +} + +//static +void LLFloaterModelPreview::onReset(void* user_data) +{ + assert_main_thread(); + + + LLFloaterModelPreview* fmp = (LLFloaterModelPreview*) user_data; + fmp->childDisable("reset_btn"); + fmp->clearLogTab(); + fmp->clearAvatarTab(); + LLModelPreview* mp = fmp->mModelPreview; + std::string filename = mp->mLODFile[LLModel::LOD_HIGH]; + + fmp->resetDisplayOptions(); + fmp->resetUploadOptions(); + //reset model preview + fmp->initModelPreview(); + + mp = fmp->mModelPreview; + mp->loadModel(filename,LLModel::LOD_HIGH,true); +} + +//static +void LLFloaterModelPreview::onUpload(void* user_data) +{ + assert_main_thread(); + + LLFloaterModelPreview* mp = (LLFloaterModelPreview*) user_data; + mp->clearLogTab(); + + mp->mUploadBtn->setEnabled(false); + + mp->mModelPreview->rebuildUploadData(); + + bool upload_skinweights = mp->childGetValue("upload_skin").asBoolean(); + bool upload_joint_positions = mp->childGetValue("upload_joints").asBoolean(); + bool lock_scale_if_joint_position = mp->childGetValue("lock_scale_if_joint_position").asBoolean(); + + if (gSavedSettings.getBOOL("MeshImportUseSLM")) + { + mp->mModelPreview->saveUploadData(upload_skinweights, upload_joint_positions, lock_scale_if_joint_position); + } + + gMeshRepo.uploadModel(mp->mModelPreview->mUploadData, mp->mModelPreview->mPreviewScale, + mp->childGetValue("upload_textures").asBoolean(), + upload_skinweights, upload_joint_positions, lock_scale_if_joint_position, + mp->mUploadModelUrl, + true, LLHandle(), mp->getWholeModelUploadObserverHandle()); +} + + +void LLFloaterModelPreview::refresh() +{ + sInstance->toggleCalculateButton(true); + sInstance->mModelPreview->mDirty = true; +} + +LLFloaterModelPreview::DecompRequest::DecompRequest(const std::string& stage, LLModel* mdl) +{ + mStage = stage; + mContinue = 1; + mModel = mdl; + mDecompID = &mdl->mDecompID; + mParams = sInstance->mDecompParams; + + //copy out positions and indices + assignData(mdl) ; +} + +void LLFloaterModelPreview::setCtrlLoadFromFile(S32 lod) +{ + if (lod == LLModel::LOD_PHYSICS) + { + LLComboBox* lod_combo = findChild("physics_lod_combo"); + if (lod_combo) + { + lod_combo->setCurrentByIndex(lod_combo->getItemCount() - 1); + } + } + else + { + LLComboBox* lod_combo = findChild("lod_source_" + lod_name[lod]); + if (lod_combo) + { + lod_combo->setCurrentByIndex(0); + } +} +} + +void LLFloaterModelPreview::setStatusMessage(const std::string& msg) +{ + LLMutexLock lock(mStatusLock); + mStatusMessage = msg; +} + +void LLFloaterModelPreview::toggleCalculateButton() +{ + toggleCalculateButton(true); +} + +void LLFloaterModelPreview::modelUpdated(bool calculate_visible) +{ + mModelPhysicsFee.clear(); + toggleCalculateButton(calculate_visible); +} + +void LLFloaterModelPreview::toggleCalculateButton(bool visible) +{ + mCalculateBtn->setVisible(visible); + + bool uploadingSkin = childGetValue("upload_skin").asBoolean(); + bool uploadingJointPositions = childGetValue("upload_joints").asBoolean(); + if ( uploadingSkin ) + { + //Disable the calculate button *if* the rig is invalid - which is determined during the critiquing process + if ( uploadingJointPositions && !mModelPreview->isRigValidForJointPositionUpload() ) + { + mCalculateBtn->setVisible( false ); + } + } + + mUploadBtn->setVisible(!visible); + mUploadBtn->setEnabled(isModelUploadAllowed()); + + if (visible) + { + std::string tbd = getString("tbd"); + childSetTextArg("prim_weight", "[EQ]", tbd); + childSetTextArg("download_weight", "[ST]", tbd); + childSetTextArg("server_weight", "[SIM]", tbd); + childSetTextArg("physics_weight", "[PH]", tbd); + if (!mModelPhysicsFee.isMap() || (mModelPhysicsFee.size() == 0)) + { + childSetTextArg("upload_fee", "[FEE]", tbd); + } + std::string dashes = hasString("--") ? getString("--") : "--"; + childSetTextArg("price_breakdown", "[STREAMING]", dashes); + childSetTextArg("price_breakdown", "[PHYSICS]", dashes); + childSetTextArg("price_breakdown", "[INSTANCES]", dashes); + childSetTextArg("price_breakdown", "[TEXTURES]", dashes); + childSetTextArg("price_breakdown", "[MODEL]", dashes); + childSetTextArg("physics_breakdown", "[PCH]", dashes); + childSetTextArg("physics_breakdown", "[PM]", dashes); + childSetTextArg("physics_breakdown", "[PHU]", dashes); + } +} + +void LLFloaterModelPreview::onLoDSourceCommit(S32 lod) +{ + mModelPreview->updateLodControls(lod); + + LLComboBox* lod_source_combo = getChild("lod_source_" + lod_name[lod]); + S32 index = lod_source_combo->getCurrentIndex(); + if (index == LLModelPreview::MESH_OPTIMIZER_AUTO + || index == LLModelPreview::MESH_OPTIMIZER_SLOPPY + || index == LLModelPreview::MESH_OPTIMIZER_PRECISE) + { //rebuild LoD to update triangle counts + onLODParamCommit(lod, true); + } +} + +void LLFloaterModelPreview::resetDisplayOptions() +{ + std::map::iterator option_it = mModelPreview->mViewOption.begin(); + + for(;option_it != mModelPreview->mViewOption.end(); ++option_it) + { + LLUICtrl* ctrl = getChild(option_it->first); + ctrl->setValue(false); + } +} + +void LLFloaterModelPreview::resetUploadOptions() +{ + childSetValue("import_scale", 1); + childSetValue("pelvis_offset", 0); + childSetValue("physics_explode", 0); + childSetValue("physics_file", ""); + childSetVisible("Retain%", false); + childSetVisible("Retain%_label", false); + childSetVisible("Detail Scale", true); + childSetVisible("Detail Scale label", true); + + getChild("lod_source_" + lod_name[NUM_LOD - 1])->setCurrentByIndex(LLModelPreview::LOD_FROM_FILE); + for (S32 lod = 0; lod < NUM_LOD - 1; ++lod) + { + getChild("lod_source_" + lod_name[lod])->setCurrentByIndex(LLModelPreview::MESH_OPTIMIZER_AUTO); + childSetValue("lod_file_" + lod_name[lod], ""); + } + + for(auto& p : mDefaultDecompParams) + { + std::string ctrl_name(p.first); + LLUICtrl* ctrl = getChild(ctrl_name); + if (ctrl) + { + ctrl->setValue(p.second); + } + } + getChild("physics_lod_combo")->setCurrentByIndex(0); + getChild("Cosine%")->setCurrentByIndex(0); +} + +void LLFloaterModelPreview::clearLogTab() +{ + mUploadLogText->clear(); + LLPanel* panel = mTabContainer->getPanelByName("logs_panel"); + mTabContainer->setTabPanelFlashing(panel, false); +} + +void LLFloaterModelPreview::onModelPhysicsFeeReceived(const LLSD& result, std::string upload_url) +{ + mModelPhysicsFee = result; + mModelPhysicsFee["url"] = upload_url; + + doOnIdleOneTime(boost::bind(&LLFloaterModelPreview::handleModelPhysicsFeeReceived,this)); +} + +void LLFloaterModelPreview::handleModelPhysicsFeeReceived() +{ + const LLSD& result = mModelPhysicsFee; + mUploadModelUrl = result["url"].asString(); + + childSetTextArg("prim_weight", "[EQ]", llformat("%0.3f", result["resource_cost"].asReal())); + childSetTextArg("download_weight", "[ST]", llformat("%0.3f", result["model_streaming_cost"].asReal())); + childSetTextArg("server_weight", "[SIM]", llformat("%0.3f", result["simulation_cost"].asReal())); + childSetTextArg("physics_weight", "[PH]", llformat("%0.3f", result["physics_cost"].asReal())); + childSetTextArg("upload_fee", "[FEE]", llformat("%d", result["upload_price"].asInteger())); + childSetTextArg("price_breakdown", "[STREAMING]", llformat("%d", result["upload_price_breakdown"]["mesh_streaming"].asInteger())); + childSetTextArg("price_breakdown", "[PHYSICS]", llformat("%d", result["upload_price_breakdown"]["mesh_physics"].asInteger())); + childSetTextArg("price_breakdown", "[INSTANCES]", llformat("%d", result["upload_price_breakdown"]["mesh_instance"].asInteger())); + childSetTextArg("price_breakdown", "[TEXTURES]", llformat("%d", result["upload_price_breakdown"]["texture"].asInteger())); + childSetTextArg("price_breakdown", "[MODEL]", llformat("%d", result["upload_price_breakdown"]["model"].asInteger())); + + childSetTextArg("physics_breakdown", "[PCH]", llformat("%0.3f", result["model_physics_cost"]["hull"].asReal())); + childSetTextArg("physics_breakdown", "[PM]", llformat("%0.3f", result["model_physics_cost"]["mesh"].asReal())); + childSetTextArg("physics_breakdown", "[PHU]", llformat("%0.3f", result["model_physics_cost"]["decomposition"].asReal())); + childSetTextArg("streaming_breakdown", "[STR_TOTAL]", llformat("%d", result["streaming_cost"].asInteger())); + childSetTextArg("streaming_breakdown", "[STR_HIGH]", llformat("%d", result["streaming_params"]["high_lod"].asInteger())); + childSetTextArg("streaming_breakdown", "[STR_MED]", llformat("%d", result["streaming_params"]["medium_lod"].asInteger())); + childSetTextArg("streaming_breakdown", "[STR_LOW]", llformat("%d", result["streaming_params"]["low_lod"].asInteger())); + childSetTextArg("streaming_breakdown", "[STR_LOWEST]", llformat("%d", result["streaming_params"]["lowest_lod"].asInteger())); + + childSetVisible("upload_fee", true); + childSetVisible("price_breakdown", true); + mUploadBtn->setEnabled(isModelUploadAllowed()); +} + +void LLFloaterModelPreview::setModelPhysicsFeeErrorStatus(S32 status, const std::string& reason, const LLSD& result) +{ + std::ostringstream out; + out << "LLFloaterModelPreview::setModelPhysicsFeeErrorStatus(" << status; + out << " : " << reason << ")"; + LL_WARNS() << out.str() << LL_ENDL; + LLFloaterModelPreview::addStringToLog(out, false); + doOnIdleOneTime(boost::bind(&LLFloaterModelPreview::toggleCalculateButton, this, true)); + + if (result.has("upload_price")) + { + mModelPhysicsFee = result; + childSetTextArg("upload_fee", "[FEE]", llformat("%d", result["upload_price"].asInteger())); + childSetVisible("upload_fee", true); + } + else + { + mModelPhysicsFee.clear(); + } +} + +/*virtual*/ +void LLFloaterModelPreview::onModelUploadSuccess() +{ + assert_main_thread(); + closeFloater(false); +} + +/*virtual*/ +void LLFloaterModelPreview::onModelUploadFailure() +{ + assert_main_thread(); + toggleCalculateButton(true); + mUploadBtn->setEnabled(true); +} + +bool LLFloaterModelPreview::isModelUploadAllowed() +{ + bool allow_upload = mHasUploadPerm && !mUploadModelUrl.empty(); + if (mModelPreview) + { + allow_upload &= mModelPreview->mModelNoErrors; + } + return allow_upload; +} + +S32 LLFloaterModelPreview::DecompRequest::statusCallback(const char* status, S32 p1, S32 p2) +{ + if (mContinue) + { + setStatusMessage(llformat("%s: %d/%d", status, p1, p2)); + if (LLFloaterModelPreview::sInstance) + { + LLFloaterModelPreview::sInstance->setStatusMessage(mStatusMessage); + } + } + + return mContinue; +} + +void LLFloaterModelPreview::DecompRequest::completed() +{ //called from the main thread + if (mContinue) + { + mModel->setConvexHullDecomposition(mHull); + + if (sInstance) + { + if (mContinue) + { + if (sInstance->mModelPreview) + { + sInstance->mModelPreview->mDirty = true; + LLFloaterModelPreview::sInstance->mModelPreview->refresh(); + } + } + + sInstance->mCurRequest.erase(this); + } + } + else if (sInstance) + { + llassert(sInstance->mCurRequest.find(this) == sInstance->mCurRequest.end()); + } +} + +void dump_llsd_to_file(const LLSD& content, std::string filename); + +void LLFloaterModelPreview::onPermissionsReceived(const LLSD& result) +{ + dump_llsd_to_file(result,"perm_received.xml"); + std::string upload_status = result["mesh_upload_status"].asString(); + // BAP HACK: handle "" for case that MeshUploadFlag cap is broken. + mHasUploadPerm = (("" == upload_status) || ("valid" == upload_status)); + + if (!mHasUploadPerm) + { + LL_WARNS() << "Upload permission set to false because upload_status=\"" << upload_status << "\"" << LL_ENDL; + } + else if (mHasUploadPerm && mUploadModelUrl.empty()) + { + LL_WARNS() << "Upload permission set to true but uploadModelUrl is empty!" << LL_ENDL; + } + + // isModelUploadAllowed() includes mHasUploadPerm + mUploadBtn->setEnabled(isModelUploadAllowed()); + getChild("warning_title")->setVisible(!mHasUploadPerm); + getChild("warning_message")->setVisible(!mHasUploadPerm); +} + +void LLFloaterModelPreview::setPermissonsErrorStatus(S32 status, const std::string& reason) +{ + LL_WARNS() << "LLFloaterModelPreview::setPermissonsErrorStatus(" << status << " : " << reason << ")" << LL_ENDL; + + LLNotificationsUtil::add("MeshUploadPermError"); +} + +bool LLFloaterModelPreview::isModelLoading() +{ + if(mModelPreview) + { + return mModelPreview->mLoading; + } + return false; +} + diff --git a/indra/newview/llfloatermodelpreview.h b/indra/newview/llfloatermodelpreview.h index 077da7e3f6..6adc084fe8 100644 --- a/indra/newview/llfloatermodelpreview.h +++ b/indra/newview/llfloatermodelpreview.h @@ -1,239 +1,239 @@ -/** - * @file llfloatermodelpreview.h - * @brief LLFloaterModelPreview class definition - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERMODELPREVIEW_H -#define LL_LLFLOATERMODELPREVIEW_H - -#include "llfloaternamedesc.h" -#include "llfloatermodeluploadbase.h" -#include "llmeshrepository.h" - -class LLComboBox; -class LLJoint; -class LLMeshFilePicker; -class LLModelPreview; -class LLTabContainer; -class LLViewerTextEditor; - - -class LLJointOverrideData -{ -public: - LLJointOverrideData() : mHasConflicts(false) {}; - std::map mPosOverrides; // models with overrides - std::set mModelsNoOverrides; // models without defined overrides - bool mHasConflicts; -}; -typedef std::map joint_override_data_map_t; - -class LLFloaterModelPreview : public LLFloaterModelUploadBase -{ -public: - - class DecompRequest : public LLPhysicsDecomp::Request - { - public: - S32 mContinue; - LLPointer mModel; - - DecompRequest(const std::string& stage, LLModel* mdl); - virtual S32 statusCallback(const char* status, S32 p1, S32 p2); - virtual void completed(); - - }; - static LLFloaterModelPreview* sInstance; - - LLFloaterModelPreview(const LLSD& key); - virtual ~LLFloaterModelPreview(); - - virtual bool postBuild(); - /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); - - void initModelPreview(); - static bool showModelPreview(); - - bool handleMouseDown(S32 x, S32 y, MASK mask); - bool handleMouseUp(S32 x, S32 y, MASK mask); - bool handleHover(S32 x, S32 y, MASK mask); - bool handleScrollWheel(S32 x, S32 y, S32 clicks); - - /*virtual*/ void onOpen(const LLSD& key); - /*virtual*/ void onClose(bool app_quitting); - - static void onMouseCaptureLostModelPreview(LLMouseHandler*); - static void setUploadAmount(S32 amount) { sUploadAmount = amount; } - static void addStringToLog(const std::string& message, const LLSD& args, bool flash, S32 lod = -1); - static void addStringToLog(const std::string& str, bool flash); - static void addStringToLog(const std::ostringstream& strm, bool flash); - void clearAvatarTab(); // clears table - void updateAvatarTab(bool highlight_overrides); // populates table and data as nessesary - - void setDetails(F32 x, F32 y, F32 z); - void setPreviewLOD(S32 lod); - - void onBrowseLOD(S32 lod); - - static void onReset(void* data); - - static void onUpload(void* data); - - void refresh(); - - void loadModel(S32 lod); - void loadModel(S32 lod, const std::string& file_name, bool force_disable_slm = false); - - void loadHighLodModel(); - - void onViewOptionChecked(LLUICtrl* ctrl); - void onUploadOptionChecked(LLUICtrl* ctrl); - bool isViewOptionChecked(const LLSD& userdata); - bool isViewOptionEnabled(const LLSD& userdata); - void setViewOptionEnabled(const std::string& option, bool enabled); - void enableViewOption(const std::string& option); - void disableViewOption(const std::string& option); - void onShowSkinWeightChecked(LLUICtrl* ctrl); - - bool isModelLoading(); - - // shows warning message if agent has no permissions to upload model - /*virtual*/ void onPermissionsReceived(const LLSD& result); - - // called when error occurs during permissions request - /*virtual*/ void setPermissonsErrorStatus(S32 status, const std::string& reason); - - /*virtual*/ void onModelPhysicsFeeReceived(const LLSD& result, std::string upload_url); - void handleModelPhysicsFeeReceived(); - /*virtual*/ void setModelPhysicsFeeErrorStatus(S32 status, const std::string& reason, const LLSD& result); - - /*virtual*/ void onModelUploadSuccess(); - - /*virtual*/ void onModelUploadFailure(); - - bool isModelUploadAllowed(); - -protected: - friend class LLModelPreview; - friend class LLMeshFilePicker; - friend class LLPhysicsDecomp; - - void onDescriptionKeystroke(LLUICtrl*); - - static void onImportScaleCommit(LLUICtrl*, void*); - static void onPelvisOffsetCommit(LLUICtrl*, void*); - - static void onPreviewLODCommit(LLUICtrl*,void*); - - static void onGenerateNormalsCommit(LLUICtrl*,void*); - - void toggleGenarateNormals(); - - static void onAutoFillCommit(LLUICtrl*,void*); - - void onLODParamCommit(S32 lod, bool enforce_tri_limit); - void draw3dPreview(); - - static void onExplodeCommit(LLUICtrl*, void*); - - static void onPhysicsParamCommit(LLUICtrl* ctrl, void* userdata); - static void onPhysicsStageExecute(LLUICtrl* ctrl, void* userdata); - static void onCancel(LLUICtrl* ctrl, void* userdata); - static void onPhysicsStageCancel(LLUICtrl* ctrl, void* userdata); - - static void onPhysicsBrowse(LLUICtrl* ctrl, void* userdata); - static void onPhysicsUseLOD(LLUICtrl* ctrl, void* userdata); - static void onPhysicsOptimize(LLUICtrl* ctrl, void* userdata); - static void onPhysicsDecomposeBack(LLUICtrl* ctrl, void* userdata); - static void onPhysicsSimplifyBack(LLUICtrl* ctrl, void* userdata); - - void draw(); - - void initDecompControls(); - - // FIXME - this function and mStatusMessage have no visible effect, and the - // actual status messages are managed by directly manipulation of - // the UI element. - void setStatusMessage(const std::string& msg); - void addStringToLogTab(const std::string& str, bool flash); - - void setCtrlLoadFromFile(S32 lod); - - LLModelPreview* mModelPreview; - - LLPhysicsDecomp::decomp_params mDecompParams; - LLPhysicsDecomp::decomp_params mDefaultDecompParams; - - S32 mLastMouseX; - S32 mLastMouseY; - LLRect mPreviewRect; - static S32 sUploadAmount; - - std::set > mCurRequest; - std::string mStatusMessage; - - //use "disabled" as false by default - std::map mViewOptionDisabled; - - //store which lod mode each LOD is using - // See eLoDMode - S32 mLODMode[4]; - - LLMutex* mStatusLock; - - LLSD mModelPhysicsFee; - -private: - void onClickCalculateBtn(); - void onJointListSelection(); - - void onLoDSourceCommit(S32 lod); - - void modelUpdated(bool calculate_visible); - - // Toggles between "Calculate weights & fee" and "Upload" buttons. - void toggleCalculateButton(); - void toggleCalculateButton(bool visible); - - // resets display options of model preview to their defaults. - void resetDisplayOptions(); - - void resetUploadOptions(); - void clearLogTab(); - void prepareToLoadModel(S32 lod); - - void createSmoothComboBox(LLComboBox* combo_box, float min, float max); - - LLButton* mUploadBtn; - LLButton* mCalculateBtn; - LLViewerTextEditor* mUploadLogText; - LLTabContainer* mTabContainer; - - S32 mAvatarTabIndex; // just to avoid any issues in case of xml changes - std::string mSelectedJointName; - - joint_override_data_map_t mJointOverrides[LLModel::NUM_LODS]; -}; - -#endif // LL_LLFLOATERMODELPREVIEW_H +/** + * @file llfloatermodelpreview.h + * @brief LLFloaterModelPreview class definition + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERMODELPREVIEW_H +#define LL_LLFLOATERMODELPREVIEW_H + +#include "llfloaternamedesc.h" +#include "llfloatermodeluploadbase.h" +#include "llmeshrepository.h" + +class LLComboBox; +class LLJoint; +class LLMeshFilePicker; +class LLModelPreview; +class LLTabContainer; +class LLViewerTextEditor; + + +class LLJointOverrideData +{ +public: + LLJointOverrideData() : mHasConflicts(false) {}; + std::map mPosOverrides; // models with overrides + std::set mModelsNoOverrides; // models without defined overrides + bool mHasConflicts; +}; +typedef std::map joint_override_data_map_t; + +class LLFloaterModelPreview : public LLFloaterModelUploadBase +{ +public: + + class DecompRequest : public LLPhysicsDecomp::Request + { + public: + S32 mContinue; + LLPointer mModel; + + DecompRequest(const std::string& stage, LLModel* mdl); + virtual S32 statusCallback(const char* status, S32 p1, S32 p2); + virtual void completed(); + + }; + static LLFloaterModelPreview* sInstance; + + LLFloaterModelPreview(const LLSD& key); + virtual ~LLFloaterModelPreview(); + + virtual bool postBuild(); + /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); + + void initModelPreview(); + static bool showModelPreview(); + + bool handleMouseDown(S32 x, S32 y, MASK mask); + bool handleMouseUp(S32 x, S32 y, MASK mask); + bool handleHover(S32 x, S32 y, MASK mask); + bool handleScrollWheel(S32 x, S32 y, S32 clicks); + + /*virtual*/ void onOpen(const LLSD& key); + /*virtual*/ void onClose(bool app_quitting); + + static void onMouseCaptureLostModelPreview(LLMouseHandler*); + static void setUploadAmount(S32 amount) { sUploadAmount = amount; } + static void addStringToLog(const std::string& message, const LLSD& args, bool flash, S32 lod = -1); + static void addStringToLog(const std::string& str, bool flash); + static void addStringToLog(const std::ostringstream& strm, bool flash); + void clearAvatarTab(); // clears table + void updateAvatarTab(bool highlight_overrides); // populates table and data as nessesary + + void setDetails(F32 x, F32 y, F32 z); + void setPreviewLOD(S32 lod); + + void onBrowseLOD(S32 lod); + + static void onReset(void* data); + + static void onUpload(void* data); + + void refresh(); + + void loadModel(S32 lod); + void loadModel(S32 lod, const std::string& file_name, bool force_disable_slm = false); + + void loadHighLodModel(); + + void onViewOptionChecked(LLUICtrl* ctrl); + void onUploadOptionChecked(LLUICtrl* ctrl); + bool isViewOptionChecked(const LLSD& userdata); + bool isViewOptionEnabled(const LLSD& userdata); + void setViewOptionEnabled(const std::string& option, bool enabled); + void enableViewOption(const std::string& option); + void disableViewOption(const std::string& option); + void onShowSkinWeightChecked(LLUICtrl* ctrl); + + bool isModelLoading(); + + // shows warning message if agent has no permissions to upload model + /*virtual*/ void onPermissionsReceived(const LLSD& result); + + // called when error occurs during permissions request + /*virtual*/ void setPermissonsErrorStatus(S32 status, const std::string& reason); + + /*virtual*/ void onModelPhysicsFeeReceived(const LLSD& result, std::string upload_url); + void handleModelPhysicsFeeReceived(); + /*virtual*/ void setModelPhysicsFeeErrorStatus(S32 status, const std::string& reason, const LLSD& result); + + /*virtual*/ void onModelUploadSuccess(); + + /*virtual*/ void onModelUploadFailure(); + + bool isModelUploadAllowed(); + +protected: + friend class LLModelPreview; + friend class LLMeshFilePicker; + friend class LLPhysicsDecomp; + + void onDescriptionKeystroke(LLUICtrl*); + + static void onImportScaleCommit(LLUICtrl*, void*); + static void onPelvisOffsetCommit(LLUICtrl*, void*); + + static void onPreviewLODCommit(LLUICtrl*,void*); + + static void onGenerateNormalsCommit(LLUICtrl*,void*); + + void toggleGenarateNormals(); + + static void onAutoFillCommit(LLUICtrl*,void*); + + void onLODParamCommit(S32 lod, bool enforce_tri_limit); + void draw3dPreview(); + + static void onExplodeCommit(LLUICtrl*, void*); + + static void onPhysicsParamCommit(LLUICtrl* ctrl, void* userdata); + static void onPhysicsStageExecute(LLUICtrl* ctrl, void* userdata); + static void onCancel(LLUICtrl* ctrl, void* userdata); + static void onPhysicsStageCancel(LLUICtrl* ctrl, void* userdata); + + static void onPhysicsBrowse(LLUICtrl* ctrl, void* userdata); + static void onPhysicsUseLOD(LLUICtrl* ctrl, void* userdata); + static void onPhysicsOptimize(LLUICtrl* ctrl, void* userdata); + static void onPhysicsDecomposeBack(LLUICtrl* ctrl, void* userdata); + static void onPhysicsSimplifyBack(LLUICtrl* ctrl, void* userdata); + + void draw(); + + void initDecompControls(); + + // FIXME - this function and mStatusMessage have no visible effect, and the + // actual status messages are managed by directly manipulation of + // the UI element. + void setStatusMessage(const std::string& msg); + void addStringToLogTab(const std::string& str, bool flash); + + void setCtrlLoadFromFile(S32 lod); + + LLModelPreview* mModelPreview; + + LLPhysicsDecomp::decomp_params mDecompParams; + LLPhysicsDecomp::decomp_params mDefaultDecompParams; + + S32 mLastMouseX; + S32 mLastMouseY; + LLRect mPreviewRect; + static S32 sUploadAmount; + + std::set > mCurRequest; + std::string mStatusMessage; + + //use "disabled" as false by default + std::map mViewOptionDisabled; + + //store which lod mode each LOD is using + // See eLoDMode + S32 mLODMode[4]; + + LLMutex* mStatusLock; + + LLSD mModelPhysicsFee; + +private: + void onClickCalculateBtn(); + void onJointListSelection(); + + void onLoDSourceCommit(S32 lod); + + void modelUpdated(bool calculate_visible); + + // Toggles between "Calculate weights & fee" and "Upload" buttons. + void toggleCalculateButton(); + void toggleCalculateButton(bool visible); + + // resets display options of model preview to their defaults. + void resetDisplayOptions(); + + void resetUploadOptions(); + void clearLogTab(); + void prepareToLoadModel(S32 lod); + + void createSmoothComboBox(LLComboBox* combo_box, float min, float max); + + LLButton* mUploadBtn; + LLButton* mCalculateBtn; + LLViewerTextEditor* mUploadLogText; + LLTabContainer* mTabContainer; + + S32 mAvatarTabIndex; // just to avoid any issues in case of xml changes + std::string mSelectedJointName; + + joint_override_data_map_t mJointOverrides[LLModel::NUM_LODS]; +}; + +#endif // LL_LLFLOATERMODELPREVIEW_H diff --git a/indra/newview/llfloatermyenvironment.h b/indra/newview/llfloatermyenvironment.h index 8cd0b57f23..8e81b8e5e2 100644 --- a/indra/newview/llfloatermyenvironment.h +++ b/indra/newview/llfloatermyenvironment.h @@ -1,78 +1,78 @@ -/** - * @file llfloatermyenvironment.h - * @brief LLFloaterMyEnvironment class header file - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2019, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERMYENVIRONMENT_H -#define LL_LLFLOATERMYENVIRONMENT_H -#include - -#include "llfloater.h" -#include "llinventoryobserver.h" -#include "llinventoryfilter.h" -#include "llfiltereditor.h" - -class LLInventoryPanel; - -class LLFloaterMyEnvironment -: public LLFloater, LLInventoryFetchDescendentsObserver -{ - LOG_CLASS(LLFloaterMyEnvironment); -public: - LLFloaterMyEnvironment(const LLSD& key); - virtual ~LLFloaterMyEnvironment(); - - virtual bool postBuild() override; - virtual void refresh() override; - - virtual void onOpen(const LLSD& key) override; - -private: - LLInventoryPanel * mInventoryList; - LLFilterEditor * mFilterEdit; - U64 mTypeFilter; - LLInventoryFilter::EFolderShow mShowFolders; - LLUUID mSelectedAsset; - LLSaveFolderState mSavedFolderState; - - void onShowFoldersChange(); - void onFilterCheckChange(); - void onFilterEdit(const std::string& search_string); - void onSelectionChange(); - void onDeleteSelected(); - void onDoCreate(const LLSD &data); - void onDoApply(const std::string &context); - bool canAction(const std::string &context); - bool canApply(const std::string &context); - - void getSelectedIds(uuid_vec_t& ids) const; - void refreshButtonStates(); - - bool isSettingSelected(LLUUID item_id); - - static LLUUID findItemByAssetId(LLUUID asset_id, bool copyable_only, bool ignore_library); -}; - - -#endif +/** + * @file llfloatermyenvironment.h + * @brief LLFloaterMyEnvironment class header file + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2019, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERMYENVIRONMENT_H +#define LL_LLFLOATERMYENVIRONMENT_H +#include + +#include "llfloater.h" +#include "llinventoryobserver.h" +#include "llinventoryfilter.h" +#include "llfiltereditor.h" + +class LLInventoryPanel; + +class LLFloaterMyEnvironment +: public LLFloater, LLInventoryFetchDescendentsObserver +{ + LOG_CLASS(LLFloaterMyEnvironment); +public: + LLFloaterMyEnvironment(const LLSD& key); + virtual ~LLFloaterMyEnvironment(); + + virtual bool postBuild() override; + virtual void refresh() override; + + virtual void onOpen(const LLSD& key) override; + +private: + LLInventoryPanel * mInventoryList; + LLFilterEditor * mFilterEdit; + U64 mTypeFilter; + LLInventoryFilter::EFolderShow mShowFolders; + LLUUID mSelectedAsset; + LLSaveFolderState mSavedFolderState; + + void onShowFoldersChange(); + void onFilterCheckChange(); + void onFilterEdit(const std::string& search_string); + void onSelectionChange(); + void onDeleteSelected(); + void onDoCreate(const LLSD &data); + void onDoApply(const std::string &context); + bool canAction(const std::string &context); + bool canApply(const std::string &context); + + void getSelectedIds(uuid_vec_t& ids) const; + void refreshButtonStates(); + + bool isSettingSelected(LLUUID item_id); + + static LLUUID findItemByAssetId(LLUUID asset_id, bool copyable_only, bool ignore_library); +}; + + +#endif diff --git a/indra/newview/llfloatermyscripts.cpp b/indra/newview/llfloatermyscripts.cpp index e4ecb934ee..a84f479d02 100644 --- a/indra/newview/llfloatermyscripts.cpp +++ b/indra/newview/llfloatermyscripts.cpp @@ -1,307 +1,307 @@ -/** - * @file llfloatermyscripts.cpp - * @brief LLFloaterMyScripts class implementation. - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2019, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llfloatermyscripts.h" - -#include "llagent.h" -#include "llcorehttputil.h" -#include "llcoros.h" -#include "lleventcoro.h" -#include "llfloaterreg.h" -#include "llscrolllistctrl.h" -#include "lltrans.h" -#include "llviewerregion.h" - -constexpr S32 SIZE_OF_ONE_KB = 1024; - -LLFloaterMyScripts::LLFloaterMyScripts(const LLSD& seed) - : LLFloater(seed), - mGotAttachmentMemoryUsed(false), - mAttachmentDetailsRequested(false), - mAttachmentMemoryMax(0), - mAttachmentMemoryUsed(0), - mGotAttachmentURLsUsed(false), - mAttachmentURLsMax(0), - mAttachmentURLsUsed(0) -{ -} - -bool LLFloaterMyScripts::postBuild() -{ - childSetAction("refresh_list_btn", onClickRefresh, this); - - std::string msg_waiting = LLTrans::getString("ScriptLimitsRequestWaiting"); - getChild("loading_text")->setValue(LLSD(msg_waiting)); - mAttachmentDetailsRequested = requestAttachmentDetails(); - return true; -} - -// virtual -void LLFloaterMyScripts::onOpen(const LLSD& key) -{ - if (!mAttachmentDetailsRequested) - { - mAttachmentDetailsRequested = requestAttachmentDetails(); - } - - LLFloater::onOpen(key); -} - -bool LLFloaterMyScripts::requestAttachmentDetails() -{ - if (!gAgent.getRegion()) return false; - - LLSD body; - std::string url = gAgent.getRegion()->getCapability("AttachmentResources"); - if (!url.empty()) - { - LLCoros::instance().launch("LLFloaterMyScripts::getAttachmentLimitsCoro", - boost::bind(&LLFloaterMyScripts::getAttachmentLimitsCoro, this, url)); - return true; - } - else - { - return false; - } -} - -void LLFloaterMyScripts::getAttachmentLimitsCoro(std::string url) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getAttachmentLimitsCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD result = httpAdapter->getAndSuspend(httpRequest, url); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS() << "Unable to retrieve attachment limits." << LL_ENDL; - return; - } - - LLFloaterMyScripts* instance = LLFloaterReg::getTypedInstance("my_scripts"); - - if (!instance) - { - LL_WARNS() << "Failed to get LLFloaterMyScripts instance" << LL_ENDL; - return; - } - - instance->getChild("loading_text")->setValue(LLSD(std::string(""))); - - LLButton* btn = instance->getChild("refresh_list_btn"); - if (btn) - { - btn->setEnabled(true); - } - - result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); - instance->setAttachmentDetails(result); -} - - -void LLFloaterMyScripts::setAttachmentDetails(LLSD content) -{ - LLScrollListCtrl *list = getChild("scripts_list"); - - if(!list) - { - return; - } - - S32 number_attachments = content["attachments"].size(); - - for(int i = 0; i < number_attachments; i++) - { - std::string humanReadableLocation = ""; - if(content["attachments"][i].has("location")) - { - std::string actualLocation = content["attachments"][i]["location"]; - humanReadableLocation = LLTrans::getString(actualLocation.c_str()); - } - - S32 number_objects = content["attachments"][i]["objects"].size(); - for(int j = 0; j < number_objects; j++) - { - LLUUID task_id = content["attachments"][i]["objects"][j]["id"].asUUID(); - S32 size = 0; - if(content["attachments"][i]["objects"][j]["resources"].has("memory")) - { - size = content["attachments"][i]["objects"][j]["resources"]["memory"].asInteger() / SIZE_OF_ONE_KB; - } - S32 urls = 0; - if(content["attachments"][i]["objects"][j]["resources"].has("urls")) - { - urls = content["attachments"][i]["objects"][j]["resources"]["urls"].asInteger(); - } - std::string name = content["attachments"][i]["objects"][j]["name"].asString(); - - LLSD element; - - element["id"] = task_id; - element["columns"][0]["column"] = "size"; - element["columns"][0]["value"] = llformat("%d", size); - element["columns"][0]["font"] = "SANSSERIF"; - element["columns"][0]["halign"] = LLFontGL::RIGHT; - - element["columns"][1]["column"] = "urls"; - element["columns"][1]["value"] = llformat("%d", urls); - element["columns"][1]["font"] = "SANSSERIF"; - element["columns"][1]["halign"] = LLFontGL::RIGHT; - - element["columns"][2]["column"] = "name"; - element["columns"][2]["value"] = name; - element["columns"][2]["font"] = "SANSSERIF"; - - element["columns"][3]["column"] = "location"; - element["columns"][3]["value"] = humanReadableLocation; - element["columns"][3]["font"] = "SANSSERIF"; - - list->addElement(element); - } - } - - setAttachmentSummary(content); - - getChild("loading_text")->setValue(LLSD(std::string(""))); - - LLButton* btn = getChild("refresh_list_btn"); - if(btn) - { - btn->setEnabled(true); - } -} - -void LLFloaterMyScripts::clearList() -{ - LLCtrlListInterface *list = childGetListInterface("scripts_list"); - - if (list) - { - list->operateOnAll(LLCtrlListInterface::OP_DELETE); - } - - std::string msg_waiting = LLTrans::getString("ScriptLimitsRequestWaiting"); - getChild("loading_text")->setValue(LLSD(msg_waiting)); -} - -void LLFloaterMyScripts::setAttachmentSummary(LLSD content) -{ - if(content["summary"]["used"][0]["type"].asString() == std::string("memory")) - { - mAttachmentMemoryUsed = content["summary"]["used"][0]["amount"].asInteger() / SIZE_OF_ONE_KB; - mAttachmentMemoryMax = content["summary"]["available"][0]["amount"].asInteger() / SIZE_OF_ONE_KB; - mGotAttachmentMemoryUsed = true; - } - else if(content["summary"]["used"][1]["type"].asString() == std::string("memory")) - { - mAttachmentMemoryUsed = content["summary"]["used"][1]["amount"].asInteger() / SIZE_OF_ONE_KB; - mAttachmentMemoryMax = content["summary"]["available"][1]["amount"].asInteger() / SIZE_OF_ONE_KB; - mGotAttachmentMemoryUsed = true; - } - else - { - LL_WARNS() << "attachment details don't contain memory summary info" << LL_ENDL; - return; - } - - if(content["summary"]["used"][0]["type"].asString() == std::string("urls")) - { - mAttachmentURLsUsed = content["summary"]["used"][0]["amount"].asInteger(); - mAttachmentURLsMax = content["summary"]["available"][0]["amount"].asInteger(); - mGotAttachmentURLsUsed = true; - } - else if(content["summary"]["used"][1]["type"].asString() == std::string("urls")) - { - mAttachmentURLsUsed = content["summary"]["used"][1]["amount"].asInteger(); - mAttachmentURLsMax = content["summary"]["available"][1]["amount"].asInteger(); - mGotAttachmentURLsUsed = true; - } - else - { - LL_WARNS() << "attachment details don't contain urls summary info" << LL_ENDL; - return; - } - - if((mAttachmentMemoryUsed >= 0) && (mAttachmentMemoryMax >= 0)) - { - LLStringUtil::format_map_t args_attachment_memory; - args_attachment_memory["[COUNT]"] = llformat ("%d", mAttachmentMemoryUsed); - std::string translate_message = "ScriptLimitsMemoryUsedSimple"; - - if (0 < mAttachmentMemoryMax) - { - S32 attachment_memory_available = mAttachmentMemoryMax - mAttachmentMemoryUsed; - - args_attachment_memory["[MAX]"] = llformat ("%d", mAttachmentMemoryMax); - args_attachment_memory["[AVAILABLE]"] = llformat ("%d", attachment_memory_available); - translate_message = "ScriptLimitsMemoryUsed"; - } - - getChild("memory_used")->setValue(LLTrans::getString(translate_message, args_attachment_memory)); - } - - if((mAttachmentURLsUsed >= 0) && (mAttachmentURLsMax >= 0)) - { - S32 attachment_urls_available = mAttachmentURLsMax - mAttachmentURLsUsed; - - LLStringUtil::format_map_t args_attachment_urls; - args_attachment_urls["[COUNT]"] = llformat ("%d", mAttachmentURLsUsed); - args_attachment_urls["[MAX]"] = llformat ("%d", mAttachmentURLsMax); - args_attachment_urls["[AVAILABLE]"] = llformat ("%d", attachment_urls_available); - std::string msg_attachment_urls = LLTrans::getString("ScriptLimitsURLsUsed", args_attachment_urls); - getChild("urls_used")->setValue(LLSD(msg_attachment_urls)); - } -} - -// static -void LLFloaterMyScripts::onClickRefresh(void* userdata) -{ - LLFloaterMyScripts* instance = LLFloaterReg::getTypedInstance("my_scripts"); - if(instance) - { - LLButton* btn = instance->getChild("refresh_list_btn"); - - //To stop people from hammering the refesh button and accidentally dosing themselves - enough requests can crash the viewer! - //turn the button off, then turn it on when we get a response - if(btn) - { - btn->setEnabled(false); - } - instance->clearList(); - instance->mAttachmentDetailsRequested = instance->requestAttachmentDetails(); - } - else - { - LL_WARNS() << "could not find LLFloaterMyScripts instance after refresh button clicked" << LL_ENDL; - } -} - +/** + * @file llfloatermyscripts.cpp + * @brief LLFloaterMyScripts class implementation. + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2019, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llfloatermyscripts.h" + +#include "llagent.h" +#include "llcorehttputil.h" +#include "llcoros.h" +#include "lleventcoro.h" +#include "llfloaterreg.h" +#include "llscrolllistctrl.h" +#include "lltrans.h" +#include "llviewerregion.h" + +constexpr S32 SIZE_OF_ONE_KB = 1024; + +LLFloaterMyScripts::LLFloaterMyScripts(const LLSD& seed) + : LLFloater(seed), + mGotAttachmentMemoryUsed(false), + mAttachmentDetailsRequested(false), + mAttachmentMemoryMax(0), + mAttachmentMemoryUsed(0), + mGotAttachmentURLsUsed(false), + mAttachmentURLsMax(0), + mAttachmentURLsUsed(0) +{ +} + +bool LLFloaterMyScripts::postBuild() +{ + childSetAction("refresh_list_btn", onClickRefresh, this); + + std::string msg_waiting = LLTrans::getString("ScriptLimitsRequestWaiting"); + getChild("loading_text")->setValue(LLSD(msg_waiting)); + mAttachmentDetailsRequested = requestAttachmentDetails(); + return true; +} + +// virtual +void LLFloaterMyScripts::onOpen(const LLSD& key) +{ + if (!mAttachmentDetailsRequested) + { + mAttachmentDetailsRequested = requestAttachmentDetails(); + } + + LLFloater::onOpen(key); +} + +bool LLFloaterMyScripts::requestAttachmentDetails() +{ + if (!gAgent.getRegion()) return false; + + LLSD body; + std::string url = gAgent.getRegion()->getCapability("AttachmentResources"); + if (!url.empty()) + { + LLCoros::instance().launch("LLFloaterMyScripts::getAttachmentLimitsCoro", + boost::bind(&LLFloaterMyScripts::getAttachmentLimitsCoro, this, url)); + return true; + } + else + { + return false; + } +} + +void LLFloaterMyScripts::getAttachmentLimitsCoro(std::string url) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getAttachmentLimitsCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD result = httpAdapter->getAndSuspend(httpRequest, url); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS() << "Unable to retrieve attachment limits." << LL_ENDL; + return; + } + + LLFloaterMyScripts* instance = LLFloaterReg::getTypedInstance("my_scripts"); + + if (!instance) + { + LL_WARNS() << "Failed to get LLFloaterMyScripts instance" << LL_ENDL; + return; + } + + instance->getChild("loading_text")->setValue(LLSD(std::string(""))); + + LLButton* btn = instance->getChild("refresh_list_btn"); + if (btn) + { + btn->setEnabled(true); + } + + result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); + instance->setAttachmentDetails(result); +} + + +void LLFloaterMyScripts::setAttachmentDetails(LLSD content) +{ + LLScrollListCtrl *list = getChild("scripts_list"); + + if(!list) + { + return; + } + + S32 number_attachments = content["attachments"].size(); + + for(int i = 0; i < number_attachments; i++) + { + std::string humanReadableLocation = ""; + if(content["attachments"][i].has("location")) + { + std::string actualLocation = content["attachments"][i]["location"]; + humanReadableLocation = LLTrans::getString(actualLocation.c_str()); + } + + S32 number_objects = content["attachments"][i]["objects"].size(); + for(int j = 0; j < number_objects; j++) + { + LLUUID task_id = content["attachments"][i]["objects"][j]["id"].asUUID(); + S32 size = 0; + if(content["attachments"][i]["objects"][j]["resources"].has("memory")) + { + size = content["attachments"][i]["objects"][j]["resources"]["memory"].asInteger() / SIZE_OF_ONE_KB; + } + S32 urls = 0; + if(content["attachments"][i]["objects"][j]["resources"].has("urls")) + { + urls = content["attachments"][i]["objects"][j]["resources"]["urls"].asInteger(); + } + std::string name = content["attachments"][i]["objects"][j]["name"].asString(); + + LLSD element; + + element["id"] = task_id; + element["columns"][0]["column"] = "size"; + element["columns"][0]["value"] = llformat("%d", size); + element["columns"][0]["font"] = "SANSSERIF"; + element["columns"][0]["halign"] = LLFontGL::RIGHT; + + element["columns"][1]["column"] = "urls"; + element["columns"][1]["value"] = llformat("%d", urls); + element["columns"][1]["font"] = "SANSSERIF"; + element["columns"][1]["halign"] = LLFontGL::RIGHT; + + element["columns"][2]["column"] = "name"; + element["columns"][2]["value"] = name; + element["columns"][2]["font"] = "SANSSERIF"; + + element["columns"][3]["column"] = "location"; + element["columns"][3]["value"] = humanReadableLocation; + element["columns"][3]["font"] = "SANSSERIF"; + + list->addElement(element); + } + } + + setAttachmentSummary(content); + + getChild("loading_text")->setValue(LLSD(std::string(""))); + + LLButton* btn = getChild("refresh_list_btn"); + if(btn) + { + btn->setEnabled(true); + } +} + +void LLFloaterMyScripts::clearList() +{ + LLCtrlListInterface *list = childGetListInterface("scripts_list"); + + if (list) + { + list->operateOnAll(LLCtrlListInterface::OP_DELETE); + } + + std::string msg_waiting = LLTrans::getString("ScriptLimitsRequestWaiting"); + getChild("loading_text")->setValue(LLSD(msg_waiting)); +} + +void LLFloaterMyScripts::setAttachmentSummary(LLSD content) +{ + if(content["summary"]["used"][0]["type"].asString() == std::string("memory")) + { + mAttachmentMemoryUsed = content["summary"]["used"][0]["amount"].asInteger() / SIZE_OF_ONE_KB; + mAttachmentMemoryMax = content["summary"]["available"][0]["amount"].asInteger() / SIZE_OF_ONE_KB; + mGotAttachmentMemoryUsed = true; + } + else if(content["summary"]["used"][1]["type"].asString() == std::string("memory")) + { + mAttachmentMemoryUsed = content["summary"]["used"][1]["amount"].asInteger() / SIZE_OF_ONE_KB; + mAttachmentMemoryMax = content["summary"]["available"][1]["amount"].asInteger() / SIZE_OF_ONE_KB; + mGotAttachmentMemoryUsed = true; + } + else + { + LL_WARNS() << "attachment details don't contain memory summary info" << LL_ENDL; + return; + } + + if(content["summary"]["used"][0]["type"].asString() == std::string("urls")) + { + mAttachmentURLsUsed = content["summary"]["used"][0]["amount"].asInteger(); + mAttachmentURLsMax = content["summary"]["available"][0]["amount"].asInteger(); + mGotAttachmentURLsUsed = true; + } + else if(content["summary"]["used"][1]["type"].asString() == std::string("urls")) + { + mAttachmentURLsUsed = content["summary"]["used"][1]["amount"].asInteger(); + mAttachmentURLsMax = content["summary"]["available"][1]["amount"].asInteger(); + mGotAttachmentURLsUsed = true; + } + else + { + LL_WARNS() << "attachment details don't contain urls summary info" << LL_ENDL; + return; + } + + if((mAttachmentMemoryUsed >= 0) && (mAttachmentMemoryMax >= 0)) + { + LLStringUtil::format_map_t args_attachment_memory; + args_attachment_memory["[COUNT]"] = llformat ("%d", mAttachmentMemoryUsed); + std::string translate_message = "ScriptLimitsMemoryUsedSimple"; + + if (0 < mAttachmentMemoryMax) + { + S32 attachment_memory_available = mAttachmentMemoryMax - mAttachmentMemoryUsed; + + args_attachment_memory["[MAX]"] = llformat ("%d", mAttachmentMemoryMax); + args_attachment_memory["[AVAILABLE]"] = llformat ("%d", attachment_memory_available); + translate_message = "ScriptLimitsMemoryUsed"; + } + + getChild("memory_used")->setValue(LLTrans::getString(translate_message, args_attachment_memory)); + } + + if((mAttachmentURLsUsed >= 0) && (mAttachmentURLsMax >= 0)) + { + S32 attachment_urls_available = mAttachmentURLsMax - mAttachmentURLsUsed; + + LLStringUtil::format_map_t args_attachment_urls; + args_attachment_urls["[COUNT]"] = llformat ("%d", mAttachmentURLsUsed); + args_attachment_urls["[MAX]"] = llformat ("%d", mAttachmentURLsMax); + args_attachment_urls["[AVAILABLE]"] = llformat ("%d", attachment_urls_available); + std::string msg_attachment_urls = LLTrans::getString("ScriptLimitsURLsUsed", args_attachment_urls); + getChild("urls_used")->setValue(LLSD(msg_attachment_urls)); + } +} + +// static +void LLFloaterMyScripts::onClickRefresh(void* userdata) +{ + LLFloaterMyScripts* instance = LLFloaterReg::getTypedInstance("my_scripts"); + if(instance) + { + LLButton* btn = instance->getChild("refresh_list_btn"); + + //To stop people from hammering the refesh button and accidentally dosing themselves - enough requests can crash the viewer! + //turn the button off, then turn it on when we get a response + if(btn) + { + btn->setEnabled(false); + } + instance->clearList(); + instance->mAttachmentDetailsRequested = instance->requestAttachmentDetails(); + } + else + { + LL_WARNS() << "could not find LLFloaterMyScripts instance after refresh button clicked" << LL_ENDL; + } +} + diff --git a/indra/newview/llfloatermyscripts.h b/indra/newview/llfloatermyscripts.h index cb02bcd09d..09affabd6a 100644 --- a/indra/newview/llfloatermyscripts.h +++ b/indra/newview/llfloatermyscripts.h @@ -1,62 +1,62 @@ -/** - * @file llfloatermyscripts.h - * @brief LLFloaterMyScripts class definition. - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2019, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERMYSCRIPTS_H -#define LL_LLFLOATERMYSCRIPTS_H - -#include "llfloater.h" -#include "llpanel.h" - -class LLFloaterMyScripts : public LLFloater -{ -public: - LLFloaterMyScripts(const LLSD& seed); - - bool postBuild() override; - void onOpen(const LLSD& key) override; - void setAttachmentDetails(LLSD content); - void setAttachmentSummary(LLSD content); - bool requestAttachmentDetails(); - void clearList(); - -private: - void getAttachmentLimitsCoro(std::string url); - - bool mGotAttachmentMemoryUsed; - bool mAttachmentDetailsRequested; - S32 mAttachmentMemoryMax; - S32 mAttachmentMemoryUsed; - - bool mGotAttachmentURLsUsed; - S32 mAttachmentURLsMax; - S32 mAttachmentURLsUsed; - -protected: - - static void onClickRefresh(void* userdata); -}; - -#endif +/** + * @file llfloatermyscripts.h + * @brief LLFloaterMyScripts class definition. + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2019, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERMYSCRIPTS_H +#define LL_LLFLOATERMYSCRIPTS_H + +#include "llfloater.h" +#include "llpanel.h" + +class LLFloaterMyScripts : public LLFloater +{ +public: + LLFloaterMyScripts(const LLSD& seed); + + bool postBuild() override; + void onOpen(const LLSD& key) override; + void setAttachmentDetails(LLSD content); + void setAttachmentSummary(LLSD content); + bool requestAttachmentDetails(); + void clearList(); + +private: + void getAttachmentLimitsCoro(std::string url); + + bool mGotAttachmentMemoryUsed; + bool mAttachmentDetailsRequested; + S32 mAttachmentMemoryMax; + S32 mAttachmentMemoryUsed; + + bool mGotAttachmentURLsUsed; + S32 mAttachmentURLsMax; + S32 mAttachmentURLsUsed; + +protected: + + static void onClickRefresh(void* userdata); +}; + +#endif diff --git a/indra/newview/llfloaternamedesc.cpp b/indra/newview/llfloaternamedesc.cpp index eba443ee85..01c50d89c5 100644 --- a/indra/newview/llfloaternamedesc.cpp +++ b/indra/newview/llfloaternamedesc.cpp @@ -1,287 +1,287 @@ -/** - * @file llfloaternamedesc.cpp - * @brief LLFloaterNameDesc class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaternamedesc.h" - -// project includes -#include "lllineeditor.h" -#include "llresmgr.h" -#include "lltextbox.h" -#include "llbutton.h" -#include "llviewerwindow.h" -#include "llfocusmgr.h" -#include "llrootview.h" -#include "llradiogroup.h" -#include "lldbstrings.h" -#include "lldir.h" -#include "llfloaterperms.h" -#include "llviewercontrol.h" -#include "llviewermenufile.h" // upload_new_resource() -#include "llstatusbar.h" // can_afford_transaction() -#include "llnotificationsutil.h" -#include "lluictrlfactory.h" -#include "llstring.h" -#include "llpermissions.h" -#include "lltrans.h" - -// linden includes -#include "llassetstorage.h" -#include "llinventorytype.h" -#include "llagentbenefits.h" - -const S32 PREVIEW_LINE_HEIGHT = 19; -const S32 PREVIEW_BORDER_WIDTH = 2; -const S32 PREVIEW_RESIZE_HANDLE_SIZE = S32(RESIZE_HANDLE_WIDTH * OO_SQRT2) + PREVIEW_BORDER_WIDTH; -const S32 PREVIEW_HPAD = PREVIEW_RESIZE_HANDLE_SIZE; - -//----------------------------------------------------------------------------- -// LLFloaterNameDesc() -//----------------------------------------------------------------------------- -LLFloaterNameDesc::LLFloaterNameDesc(const LLSD& filename ) - : LLFloater(filename), - mIsAudio(false) -{ - mFilenameAndPath = filename.asString(); - mFilename = gDirUtilp->getBaseFileName(mFilenameAndPath, false); -} - -//----------------------------------------------------------------------------- -// postBuild() -//----------------------------------------------------------------------------- -bool LLFloaterNameDesc::postBuild() -{ - LLRect r; - - std::string asset_name = mFilename; - LLStringUtil::replaceNonstandardASCII( asset_name, '?' ); - LLStringUtil::replaceChar(asset_name, '|', '?'); - LLStringUtil::stripNonprintable(asset_name); - LLStringUtil::trim(asset_name); - - asset_name = gDirUtilp->getBaseFileName(asset_name, true); // no extsntion - - setTitle(mFilename); - - centerWithin(gViewerWindow->getRootView()->getRect()); - - S32 line_width = getRect().getWidth() - 2 * PREVIEW_HPAD; - S32 y = getRect().getHeight() - PREVIEW_LINE_HEIGHT; - - r.setLeftTopAndSize( PREVIEW_HPAD, y, line_width, PREVIEW_LINE_HEIGHT ); - y -= PREVIEW_LINE_HEIGHT; - - r.setLeftTopAndSize( PREVIEW_HPAD, y, line_width, PREVIEW_LINE_HEIGHT ); - - getChild("name_form")->setCommitCallback(boost::bind(&LLFloaterNameDesc::doCommit, this)); - getChild("name_form")->setValue(LLSD(asset_name)); - - LLLineEditor *NameEditor = getChild("name_form"); - if (NameEditor) - { - NameEditor->setMaxTextLength(DB_INV_ITEM_NAME_STR_LEN); - NameEditor->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); - } - - y -= llfloor(PREVIEW_LINE_HEIGHT * 1.2f); - y -= PREVIEW_LINE_HEIGHT; - - r.setLeftTopAndSize( PREVIEW_HPAD, y, line_width, PREVIEW_LINE_HEIGHT ); - getChild("description_form")->setCommitCallback(boost::bind(&LLFloaterNameDesc::doCommit, this)); - LLLineEditor *DescEditor = getChild("description_form"); - if (DescEditor) - { - DescEditor->setMaxTextLength(DB_INV_ITEM_DESC_STR_LEN); - DescEditor->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); - } - - y -= llfloor(PREVIEW_LINE_HEIGHT * 1.2f); - - // Cancel button - getChild("cancel_btn")->setCommitCallback(boost::bind(&LLFloaterNameDesc::onBtnCancel, this)); - - S32 expected_upload_cost = getExpectedUploadCost(); - getChild("ok_btn")->setLabelArg("[AMOUNT]", llformat("%d", expected_upload_cost)); - - LLTextBox* info_text = getChild("info_text"); - if (info_text) - { - info_text->setValue(LLTrans::getString("UploadFeeInfo")); - } - - setDefaultBtn("ok_btn"); - - return true; -} - -S32 LLFloaterNameDesc::getExpectedUploadCost() const -{ - std::string exten = gDirUtilp->getExtension(mFilename); - LLAssetType::EType asset_type; - S32 upload_cost = -1; - if (LLResourceUploadInfo::findAssetTypeOfExtension(exten, asset_type)) - { - if (!LLAgentBenefitsMgr::current().findUploadCost(asset_type, upload_cost)) - { - LL_WARNS() << "Unable to find upload cost for asset type " << asset_type << LL_ENDL; - } - } - else - { - LL_WARNS() << "Unable to find upload cost for " << mFilename << LL_ENDL; - } - return upload_cost; -} - -//----------------------------------------------------------------------------- -// LLFloaterNameDesc() -//----------------------------------------------------------------------------- -LLFloaterNameDesc::~LLFloaterNameDesc() -{ - gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() -} - -// Sub-classes should override this function if they allow editing -//----------------------------------------------------------------------------- -// onCommit() -//----------------------------------------------------------------------------- -void LLFloaterNameDesc::onCommit() -{ -} - -//----------------------------------------------------------------------------- -// onCommit() -//----------------------------------------------------------------------------- -void LLFloaterNameDesc::doCommit() -{ - onCommit(); -} - -//----------------------------------------------------------------------------- -// onBtnOK() -//----------------------------------------------------------------------------- -void LLFloaterNameDesc::onBtnOK( ) -{ - getChildView("ok_btn")->setEnabled(false); // don't allow inadvertent extra uploads - - LLAssetStorage::LLStoreAssetCallback callback; - S32 expected_upload_cost = getExpectedUploadCost(); - if (can_afford_transaction(expected_upload_cost)) - { - void *nruserdata = NULL; - std::string display_name = LLStringUtil::null; - - LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared( - mFilenameAndPath, - getChild("name_form")->getValue().asString(), - getChild("description_form")->getValue().asString(), 0, - LLFolderType::FT_NONE, LLInventoryType::IT_NONE, - LLFloaterPerms::getNextOwnerPerms("Uploads"), - LLFloaterPerms::getGroupPerms("Uploads"), - LLFloaterPerms::getEveryonePerms("Uploads"), - expected_upload_cost)); - - upload_new_resource(uploadInfo, callback, nruserdata); - } - else - { - LLSD args; - args["COST"] = llformat("%d", expected_upload_cost); - LLNotificationsUtil::add("ErrorCannotAffordUpload", args); - } - - closeFloater(false); -} - -//----------------------------------------------------------------------------- -// onBtnCancel() -//----------------------------------------------------------------------------- -void LLFloaterNameDesc::onBtnCancel() -{ - closeFloater(false); -} - - -//----------------------------------------------------------------------------- -// LLFloaterSoundPreview() -//----------------------------------------------------------------------------- - -LLFloaterSoundPreview::LLFloaterSoundPreview(const LLSD& filename ) - : LLFloaterNameDesc(filename) -{ - mIsAudio = true; -} - -bool LLFloaterSoundPreview::postBuild() -{ - if (!LLFloaterNameDesc::postBuild()) - { - return false; - } - getChild("ok_btn")->setCommitCallback(boost::bind(&LLFloaterNameDesc::onBtnOK, this)); - return true; -} - - -//----------------------------------------------------------------------------- -// LLFloaterAnimPreview() -//----------------------------------------------------------------------------- - -LLFloaterAnimPreview::LLFloaterAnimPreview(const LLSD& filename ) - : LLFloaterNameDesc(filename) -{ -} - -bool LLFloaterAnimPreview::postBuild() -{ - if (!LLFloaterNameDesc::postBuild()) - { - return false; - } - getChild("ok_btn")->setCommitCallback(boost::bind(&LLFloaterNameDesc::onBtnOK, this)); - return true; -} - -//----------------------------------------------------------------------------- -// LLFloaterScriptPreview() -//----------------------------------------------------------------------------- - -LLFloaterScriptPreview::LLFloaterScriptPreview(const LLSD& filename ) - : LLFloaterNameDesc(filename) -{ - mIsText = true; -} - -bool LLFloaterScriptPreview::postBuild() -{ - if (!LLFloaterNameDesc::postBuild()) - { - return false; - } - getChild("ok_btn")->setCommitCallback(boost::bind(&LLFloaterNameDesc::onBtnOK, this)); - return true; -} +/** + * @file llfloaternamedesc.cpp + * @brief LLFloaterNameDesc class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaternamedesc.h" + +// project includes +#include "lllineeditor.h" +#include "llresmgr.h" +#include "lltextbox.h" +#include "llbutton.h" +#include "llviewerwindow.h" +#include "llfocusmgr.h" +#include "llrootview.h" +#include "llradiogroup.h" +#include "lldbstrings.h" +#include "lldir.h" +#include "llfloaterperms.h" +#include "llviewercontrol.h" +#include "llviewermenufile.h" // upload_new_resource() +#include "llstatusbar.h" // can_afford_transaction() +#include "llnotificationsutil.h" +#include "lluictrlfactory.h" +#include "llstring.h" +#include "llpermissions.h" +#include "lltrans.h" + +// linden includes +#include "llassetstorage.h" +#include "llinventorytype.h" +#include "llagentbenefits.h" + +const S32 PREVIEW_LINE_HEIGHT = 19; +const S32 PREVIEW_BORDER_WIDTH = 2; +const S32 PREVIEW_RESIZE_HANDLE_SIZE = S32(RESIZE_HANDLE_WIDTH * OO_SQRT2) + PREVIEW_BORDER_WIDTH; +const S32 PREVIEW_HPAD = PREVIEW_RESIZE_HANDLE_SIZE; + +//----------------------------------------------------------------------------- +// LLFloaterNameDesc() +//----------------------------------------------------------------------------- +LLFloaterNameDesc::LLFloaterNameDesc(const LLSD& filename ) + : LLFloater(filename), + mIsAudio(false) +{ + mFilenameAndPath = filename.asString(); + mFilename = gDirUtilp->getBaseFileName(mFilenameAndPath, false); +} + +//----------------------------------------------------------------------------- +// postBuild() +//----------------------------------------------------------------------------- +bool LLFloaterNameDesc::postBuild() +{ + LLRect r; + + std::string asset_name = mFilename; + LLStringUtil::replaceNonstandardASCII( asset_name, '?' ); + LLStringUtil::replaceChar(asset_name, '|', '?'); + LLStringUtil::stripNonprintable(asset_name); + LLStringUtil::trim(asset_name); + + asset_name = gDirUtilp->getBaseFileName(asset_name, true); // no extsntion + + setTitle(mFilename); + + centerWithin(gViewerWindow->getRootView()->getRect()); + + S32 line_width = getRect().getWidth() - 2 * PREVIEW_HPAD; + S32 y = getRect().getHeight() - PREVIEW_LINE_HEIGHT; + + r.setLeftTopAndSize( PREVIEW_HPAD, y, line_width, PREVIEW_LINE_HEIGHT ); + y -= PREVIEW_LINE_HEIGHT; + + r.setLeftTopAndSize( PREVIEW_HPAD, y, line_width, PREVIEW_LINE_HEIGHT ); + + getChild("name_form")->setCommitCallback(boost::bind(&LLFloaterNameDesc::doCommit, this)); + getChild("name_form")->setValue(LLSD(asset_name)); + + LLLineEditor *NameEditor = getChild("name_form"); + if (NameEditor) + { + NameEditor->setMaxTextLength(DB_INV_ITEM_NAME_STR_LEN); + NameEditor->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); + } + + y -= llfloor(PREVIEW_LINE_HEIGHT * 1.2f); + y -= PREVIEW_LINE_HEIGHT; + + r.setLeftTopAndSize( PREVIEW_HPAD, y, line_width, PREVIEW_LINE_HEIGHT ); + getChild("description_form")->setCommitCallback(boost::bind(&LLFloaterNameDesc::doCommit, this)); + LLLineEditor *DescEditor = getChild("description_form"); + if (DescEditor) + { + DescEditor->setMaxTextLength(DB_INV_ITEM_DESC_STR_LEN); + DescEditor->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); + } + + y -= llfloor(PREVIEW_LINE_HEIGHT * 1.2f); + + // Cancel button + getChild("cancel_btn")->setCommitCallback(boost::bind(&LLFloaterNameDesc::onBtnCancel, this)); + + S32 expected_upload_cost = getExpectedUploadCost(); + getChild("ok_btn")->setLabelArg("[AMOUNT]", llformat("%d", expected_upload_cost)); + + LLTextBox* info_text = getChild("info_text"); + if (info_text) + { + info_text->setValue(LLTrans::getString("UploadFeeInfo")); + } + + setDefaultBtn("ok_btn"); + + return true; +} + +S32 LLFloaterNameDesc::getExpectedUploadCost() const +{ + std::string exten = gDirUtilp->getExtension(mFilename); + LLAssetType::EType asset_type; + S32 upload_cost = -1; + if (LLResourceUploadInfo::findAssetTypeOfExtension(exten, asset_type)) + { + if (!LLAgentBenefitsMgr::current().findUploadCost(asset_type, upload_cost)) + { + LL_WARNS() << "Unable to find upload cost for asset type " << asset_type << LL_ENDL; + } + } + else + { + LL_WARNS() << "Unable to find upload cost for " << mFilename << LL_ENDL; + } + return upload_cost; +} + +//----------------------------------------------------------------------------- +// LLFloaterNameDesc() +//----------------------------------------------------------------------------- +LLFloaterNameDesc::~LLFloaterNameDesc() +{ + gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() +} + +// Sub-classes should override this function if they allow editing +//----------------------------------------------------------------------------- +// onCommit() +//----------------------------------------------------------------------------- +void LLFloaterNameDesc::onCommit() +{ +} + +//----------------------------------------------------------------------------- +// onCommit() +//----------------------------------------------------------------------------- +void LLFloaterNameDesc::doCommit() +{ + onCommit(); +} + +//----------------------------------------------------------------------------- +// onBtnOK() +//----------------------------------------------------------------------------- +void LLFloaterNameDesc::onBtnOK( ) +{ + getChildView("ok_btn")->setEnabled(false); // don't allow inadvertent extra uploads + + LLAssetStorage::LLStoreAssetCallback callback; + S32 expected_upload_cost = getExpectedUploadCost(); + if (can_afford_transaction(expected_upload_cost)) + { + void *nruserdata = NULL; + std::string display_name = LLStringUtil::null; + + LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared( + mFilenameAndPath, + getChild("name_form")->getValue().asString(), + getChild("description_form")->getValue().asString(), 0, + LLFolderType::FT_NONE, LLInventoryType::IT_NONE, + LLFloaterPerms::getNextOwnerPerms("Uploads"), + LLFloaterPerms::getGroupPerms("Uploads"), + LLFloaterPerms::getEveryonePerms("Uploads"), + expected_upload_cost)); + + upload_new_resource(uploadInfo, callback, nruserdata); + } + else + { + LLSD args; + args["COST"] = llformat("%d", expected_upload_cost); + LLNotificationsUtil::add("ErrorCannotAffordUpload", args); + } + + closeFloater(false); +} + +//----------------------------------------------------------------------------- +// onBtnCancel() +//----------------------------------------------------------------------------- +void LLFloaterNameDesc::onBtnCancel() +{ + closeFloater(false); +} + + +//----------------------------------------------------------------------------- +// LLFloaterSoundPreview() +//----------------------------------------------------------------------------- + +LLFloaterSoundPreview::LLFloaterSoundPreview(const LLSD& filename ) + : LLFloaterNameDesc(filename) +{ + mIsAudio = true; +} + +bool LLFloaterSoundPreview::postBuild() +{ + if (!LLFloaterNameDesc::postBuild()) + { + return false; + } + getChild("ok_btn")->setCommitCallback(boost::bind(&LLFloaterNameDesc::onBtnOK, this)); + return true; +} + + +//----------------------------------------------------------------------------- +// LLFloaterAnimPreview() +//----------------------------------------------------------------------------- + +LLFloaterAnimPreview::LLFloaterAnimPreview(const LLSD& filename ) + : LLFloaterNameDesc(filename) +{ +} + +bool LLFloaterAnimPreview::postBuild() +{ + if (!LLFloaterNameDesc::postBuild()) + { + return false; + } + getChild("ok_btn")->setCommitCallback(boost::bind(&LLFloaterNameDesc::onBtnOK, this)); + return true; +} + +//----------------------------------------------------------------------------- +// LLFloaterScriptPreview() +//----------------------------------------------------------------------------- + +LLFloaterScriptPreview::LLFloaterScriptPreview(const LLSD& filename ) + : LLFloaterNameDesc(filename) +{ + mIsText = true; +} + +bool LLFloaterScriptPreview::postBuild() +{ + if (!LLFloaterNameDesc::postBuild()) + { + return false; + } + getChild("ok_btn")->setCommitCallback(boost::bind(&LLFloaterNameDesc::onBtnOK, this)); + return true; +} diff --git a/indra/newview/llfloaternamedesc.h b/indra/newview/llfloaternamedesc.h index 798829d7a1..5996547b94 100644 --- a/indra/newview/llfloaternamedesc.h +++ b/indra/newview/llfloaternamedesc.h @@ -1,84 +1,84 @@ -/** - * @file llfloaternamedesc.h - * @brief LLFloaterNameDesc class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERNAMEDESC_H -#define LL_LLFLOATERNAMEDESC_H - -#include "llfloater.h" -#include "llresizehandle.h" -#include "llstring.h" -#include "llassettype.h" - -class LLLineEditor; -class LLButton; -class LLRadioGroup; - -class LLFloaterNameDesc : public LLFloater -{ -public: - LLFloaterNameDesc(const LLSD& filename); - virtual ~LLFloaterNameDesc(); - bool postBuild() override; - - void onBtnOK(); - void onBtnCancel(); - void doCommit(); - - S32 getExpectedUploadCost() const; - -protected: - virtual void onCommit() override; - -protected: - bool mIsAudio; - bool mIsText; - - std::string mFilenameAndPath; - std::string mFilename; -}; - -class LLFloaterSoundPreview : public LLFloaterNameDesc -{ -public: - LLFloaterSoundPreview(const LLSD& filename ); - bool postBuild() override; -}; - -class LLFloaterAnimPreview : public LLFloaterNameDesc -{ -public: - LLFloaterAnimPreview(const LLSD& filename ); - bool postBuild() override; -}; - -class LLFloaterScriptPreview : public LLFloaterNameDesc -{ -public: - LLFloaterScriptPreview(const LLSD& filename ); - bool postBuild() override; -}; - -#endif // LL_LLFLOATERNAMEDESC_H +/** + * @file llfloaternamedesc.h + * @brief LLFloaterNameDesc class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERNAMEDESC_H +#define LL_LLFLOATERNAMEDESC_H + +#include "llfloater.h" +#include "llresizehandle.h" +#include "llstring.h" +#include "llassettype.h" + +class LLLineEditor; +class LLButton; +class LLRadioGroup; + +class LLFloaterNameDesc : public LLFloater +{ +public: + LLFloaterNameDesc(const LLSD& filename); + virtual ~LLFloaterNameDesc(); + bool postBuild() override; + + void onBtnOK(); + void onBtnCancel(); + void doCommit(); + + S32 getExpectedUploadCost() const; + +protected: + virtual void onCommit() override; + +protected: + bool mIsAudio; + bool mIsText; + + std::string mFilenameAndPath; + std::string mFilename; +}; + +class LLFloaterSoundPreview : public LLFloaterNameDesc +{ +public: + LLFloaterSoundPreview(const LLSD& filename ); + bool postBuild() override; +}; + +class LLFloaterAnimPreview : public LLFloaterNameDesc +{ +public: + LLFloaterAnimPreview(const LLSD& filename ); + bool postBuild() override; +}; + +class LLFloaterScriptPreview : public LLFloaterNameDesc +{ +public: + LLFloaterScriptPreview(const LLSD& filename ); + bool postBuild() override; +}; + +#endif // LL_LLFLOATERNAMEDESC_H diff --git a/indra/newview/llfloaternewfeaturenotification.h b/indra/newview/llfloaternewfeaturenotification.h index 03382eef6f..5afb8e71e8 100644 --- a/indra/newview/llfloaternewfeaturenotification.h +++ b/indra/newview/llfloaternewfeaturenotification.h @@ -1,49 +1,49 @@ -/** - * @file llfloaternewfeaturenotification.h - * @brief LLFloaterNewFeatureNotification class definition - * - * $LicenseInfo:firstyear=2023&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2023, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_FLOATER_NEW_FEATURE_NOTOFICATION_H -#define LL_FLOATER_NEW_FEATURE_NOTOFICATION_H - -#include "llfloater.h" - -class LLFloaterNewFeatureNotification: - public LLFloater -{ - friend class LLFloaterReg; -public: - bool postBuild() override; - void onOpen(const LLSD& key) override; - -private: - LLFloaterNewFeatureNotification(const LLSD& key); - /*virtual*/ ~LLFloaterNewFeatureNotification(); - - void centerOnScreen(); - - void onCloseBtn(); -}; - -#endif +/** + * @file llfloaternewfeaturenotification.h + * @brief LLFloaterNewFeatureNotification class definition + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_FLOATER_NEW_FEATURE_NOTOFICATION_H +#define LL_FLOATER_NEW_FEATURE_NOTOFICATION_H + +#include "llfloater.h" + +class LLFloaterNewFeatureNotification: + public LLFloater +{ + friend class LLFloaterReg; +public: + bool postBuild() override; + void onOpen(const LLSD& key) override; + +private: + LLFloaterNewFeatureNotification(const LLSD& key); + /*virtual*/ ~LLFloaterNewFeatureNotification(); + + void centerOnScreen(); + + void onCloseBtn(); +}; + +#endif diff --git a/indra/newview/llfloaternotificationsconsole.cpp b/indra/newview/llfloaternotificationsconsole.cpp index ee3def7d80..a819b30e30 100644 --- a/indra/newview/llfloaternotificationsconsole.cpp +++ b/indra/newview/llfloaternotificationsconsole.cpp @@ -1,280 +1,280 @@ -/** - * @file llnotificationsconsole.cpp - * @brief Debugging console for unified notifications. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llfloaternotificationsconsole.h" -#include "llnotifications.h" -#include "lluictrlfactory.h" -#include "llbutton.h" -#include "llscrolllistctrl.h" -#include "llscrolllistitem.h" -#include "llpanel.h" -#include "llcombobox.h" - -const S32 NOTIFICATION_PANEL_HEADER_HEIGHT = 20; -const S32 HEADER_PADDING = 38; - -class LLNotificationChannelPanel : public LLLayoutPanel -{ -public: - LLNotificationChannelPanel(const Params& p); - ~LLNotificationChannelPanel(); - bool postBuild(); - -private: - bool update(const LLSD& payload); - static void toggleClick(void* user_data); - static void onClickNotification(void* user_data); - LLNotificationChannelPtr mChannelPtr; -}; - -LLNotificationChannelPanel::LLNotificationChannelPanel(const LLNotificationChannelPanel::Params& p) -: LLLayoutPanel(p) -{ - mChannelPtr = LLNotifications::instance().getChannel(p.name); - buildFromFile( "panel_notifications_channel.xml"); -} - -LLNotificationChannelPanel::~LLNotificationChannelPanel() -{ - // Userdata for all records is a LLNotification* we need to clean up - std::vector data_list = getChild("notifications_list")->getAllData(); - std::vector::iterator data_itor; - for (data_itor = data_list.begin(); data_itor != data_list.end(); ++data_itor) - { - LLScrollListItem* item = *data_itor; - LLNotification* notification = (LLNotification*)item->getUserdata(); - delete notification; - notification = NULL; - } -} - -bool LLNotificationChannelPanel::postBuild() -{ - LLButton* header_button = getChild("header"); - header_button->setLabel(mChannelPtr->getName()); - header_button->setClickedCallback(toggleClick, this); - - mChannelPtr->connectChanged(boost::bind(&LLNotificationChannelPanel::update, this, _1)); - - LLScrollListCtrl* scroll = getChild("notifications_list"); - scroll->setDoubleClickCallback(onClickNotification, this); - scroll->setRect(LLRect( getRect().mLeft, getRect().mTop, getRect().mRight, 0)); - return true; -} - -//static -void LLNotificationChannelPanel::toggleClick(void *user_data) -{ - LLNotificationChannelPanel* self = (LLNotificationChannelPanel*)user_data; - if (!self) return; - - LLButton* header_button = self->getChild("header"); - - LLLayoutStack* stack = dynamic_cast(self->getParent()); - if (stack) - { - stack->collapsePanel(self, header_button->getToggleState()); - } - - // turn off tab stop for collapsed panel - self->getChild("notifications_list")->setTabStop(!header_button->getToggleState()); - self->getChild("notifications_list")->setVisible(!header_button->getToggleState()); -} - -/*static*/ -void LLNotificationChannelPanel::onClickNotification(void* user_data) -{ - LLNotificationChannelPanel* self = (LLNotificationChannelPanel*)user_data; - if (!self) return; - LLScrollListItem* firstselected = self->getChild("notifications_list")->getFirstSelected(); - llassert(firstselected); - if (firstselected) - { - void* data = firstselected->getUserdata(); - if (data) - { - gFloaterView->getParentFloater(self)->addDependentFloater(new LLFloaterNotification((LLNotification*)data), true); - } - } -} - -bool LLNotificationChannelPanel::update(const LLSD& payload) -{ - LLNotificationPtr notification = LLNotifications::instance().find(payload["id"].asUUID()); - if (notification) - { - LLSD row; - row["columns"][0]["value"] = notification->getName(); - row["columns"][0]["column"] = "name"; - - row["columns"][1]["value"] = notification->getMessage(); - row["columns"][1]["column"] = "content"; - - row["columns"][2]["value"] = notification->getDate(); - row["columns"][2]["column"] = "date"; - row["columns"][2]["type"] = "date"; - - LLScrollListItem* sli = getChild("notifications_list")->addElement(row); - sli->setUserdata(new LLNotification(notification->asLLSD())); - } - - return false; -} - -// -// LLFloaterNotificationConsole -// -LLFloaterNotificationConsole::LLFloaterNotificationConsole(const LLSD& key) -: LLFloater(key) -{ - mCommitCallbackRegistrar.add("ClickAdd", boost::bind(&LLFloaterNotificationConsole::onClickAdd, this)); -} - -bool LLFloaterNotificationConsole::postBuild() -{ - // these are in the order of processing - addChannel("Unexpired"); - addChannel("Ignore"); - addChannel("VisibilityRules"); - addChannel("Visible", true); - // all the ones below attach to the Visible channel - addChannel("Persistent"); - addChannel("Alerts"); - addChannel("AlertModal"); - addChannel("Group Notifications"); - addChannel("Notifications"); - addChannel("NotificationTips"); - -// getChild("add_notification")->setClickedCallback(onClickAdd, this); - - LLComboBox* notifications = getChild("notification_types"); - LLNotifications::TemplateNames names = LLNotifications::instance().getTemplateNames(); - for (LLNotifications::TemplateNames::iterator template_it = names.begin(); - template_it != names.end(); - ++template_it) - { - notifications->add(*template_it); - } - notifications->sortByName(); - - return true; -} - -void LLFloaterNotificationConsole::addChannel(const std::string& name, bool open) -{ - LLLayoutStack& stack = getChildRef("notification_channels"); - LLNotificationChannelPanel::Params p; - p.min_dim = NOTIFICATION_PANEL_HEADER_HEIGHT; - p.auto_resize = true; - p.user_resize = true; - p.name = name; - LLNotificationChannelPanel* panelp = new LLNotificationChannelPanel(p); - stack.addPanel(panelp, LLLayoutStack::ANIMATE); - - LLButton& header_button = panelp->getChildRef("header"); - header_button.setToggleState(!open); - stack.collapsePanel(panelp, !open); - - updateResizeLimits(); -} - -void LLFloaterNotificationConsole::removeChannel(const std::string& name) -{ - LLPanel* panelp = getChild(name); - getChildRef("notification_channels").removeChild(panelp); - delete panelp; - - updateResizeLimits(); -} - -//static -void LLFloaterNotificationConsole::updateResizeLimits() -{ - const LLFloater::Params& floater_params = LLFloater::getDefaultParams(); - S32 floater_header_size = floater_params.header_height; - - LLLayoutStack& stack = getChildRef("notification_channels"); - setResizeLimits(getMinWidth(), floater_header_size + HEADER_PADDING + ((NOTIFICATION_PANEL_HEADER_HEIGHT + 3) * stack.getNumPanels())); -} - -void LLFloaterNotificationConsole::onClickAdd() -{ - std::string message_name = getChild("notification_types")->getValue().asString(); - if (!message_name.empty()) - { - LLNotifications::instance().add(message_name, LLSD(), LLSD()); - } -} - - -//=============== LLFloaterNotification ================ - -LLFloaterNotification::LLFloaterNotification(LLNotification* note) -: LLFloater(LLSD()), - mNote(note) -{ - buildFromFile("floater_notification.xml"); -} - -bool LLFloaterNotification::postBuild() -{ - setTitle(mNote->getName()); - getChild("payload")->setValue(mNote->getMessage()); - - LLComboBox* responses_combo = getChild("response"); - LLCtrlListInterface* response_list = responses_combo->getListInterface(); - LLNotificationFormPtr form(mNote->getForm()); - if(!form) - { - return true; - } - - responses_combo->setCommitCallback(onCommitResponse, this); - - LLSD form_sd = form->asLLSD(); - - for (LLSD::array_const_iterator form_item = form_sd.beginArray(); form_item != form_sd.endArray(); ++form_item) - { - if ( (*form_item)["type"].asString() != "button") continue; - std::string text = (*form_item)["text"].asString(); - response_list->addSimpleElement(text); - } - - return true; -} - -void LLFloaterNotification::respond() -{ - LLComboBox* responses_combo = getChild("response"); - LLCtrlListInterface* response_list = responses_combo->getListInterface(); - const std::string& trigger = response_list->getSelectedValue().asString(); - //LL_INFOS() << trigger << LL_ENDL; - - LLSD response = mNote->getResponseTemplate(); - response[trigger] = true; - mNote->respond(response); -} +/** + * @file llnotificationsconsole.cpp + * @brief Debugging console for unified notifications. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llfloaternotificationsconsole.h" +#include "llnotifications.h" +#include "lluictrlfactory.h" +#include "llbutton.h" +#include "llscrolllistctrl.h" +#include "llscrolllistitem.h" +#include "llpanel.h" +#include "llcombobox.h" + +const S32 NOTIFICATION_PANEL_HEADER_HEIGHT = 20; +const S32 HEADER_PADDING = 38; + +class LLNotificationChannelPanel : public LLLayoutPanel +{ +public: + LLNotificationChannelPanel(const Params& p); + ~LLNotificationChannelPanel(); + bool postBuild(); + +private: + bool update(const LLSD& payload); + static void toggleClick(void* user_data); + static void onClickNotification(void* user_data); + LLNotificationChannelPtr mChannelPtr; +}; + +LLNotificationChannelPanel::LLNotificationChannelPanel(const LLNotificationChannelPanel::Params& p) +: LLLayoutPanel(p) +{ + mChannelPtr = LLNotifications::instance().getChannel(p.name); + buildFromFile( "panel_notifications_channel.xml"); +} + +LLNotificationChannelPanel::~LLNotificationChannelPanel() +{ + // Userdata for all records is a LLNotification* we need to clean up + std::vector data_list = getChild("notifications_list")->getAllData(); + std::vector::iterator data_itor; + for (data_itor = data_list.begin(); data_itor != data_list.end(); ++data_itor) + { + LLScrollListItem* item = *data_itor; + LLNotification* notification = (LLNotification*)item->getUserdata(); + delete notification; + notification = NULL; + } +} + +bool LLNotificationChannelPanel::postBuild() +{ + LLButton* header_button = getChild("header"); + header_button->setLabel(mChannelPtr->getName()); + header_button->setClickedCallback(toggleClick, this); + + mChannelPtr->connectChanged(boost::bind(&LLNotificationChannelPanel::update, this, _1)); + + LLScrollListCtrl* scroll = getChild("notifications_list"); + scroll->setDoubleClickCallback(onClickNotification, this); + scroll->setRect(LLRect( getRect().mLeft, getRect().mTop, getRect().mRight, 0)); + return true; +} + +//static +void LLNotificationChannelPanel::toggleClick(void *user_data) +{ + LLNotificationChannelPanel* self = (LLNotificationChannelPanel*)user_data; + if (!self) return; + + LLButton* header_button = self->getChild("header"); + + LLLayoutStack* stack = dynamic_cast(self->getParent()); + if (stack) + { + stack->collapsePanel(self, header_button->getToggleState()); + } + + // turn off tab stop for collapsed panel + self->getChild("notifications_list")->setTabStop(!header_button->getToggleState()); + self->getChild("notifications_list")->setVisible(!header_button->getToggleState()); +} + +/*static*/ +void LLNotificationChannelPanel::onClickNotification(void* user_data) +{ + LLNotificationChannelPanel* self = (LLNotificationChannelPanel*)user_data; + if (!self) return; + LLScrollListItem* firstselected = self->getChild("notifications_list")->getFirstSelected(); + llassert(firstselected); + if (firstselected) + { + void* data = firstselected->getUserdata(); + if (data) + { + gFloaterView->getParentFloater(self)->addDependentFloater(new LLFloaterNotification((LLNotification*)data), true); + } + } +} + +bool LLNotificationChannelPanel::update(const LLSD& payload) +{ + LLNotificationPtr notification = LLNotifications::instance().find(payload["id"].asUUID()); + if (notification) + { + LLSD row; + row["columns"][0]["value"] = notification->getName(); + row["columns"][0]["column"] = "name"; + + row["columns"][1]["value"] = notification->getMessage(); + row["columns"][1]["column"] = "content"; + + row["columns"][2]["value"] = notification->getDate(); + row["columns"][2]["column"] = "date"; + row["columns"][2]["type"] = "date"; + + LLScrollListItem* sli = getChild("notifications_list")->addElement(row); + sli->setUserdata(new LLNotification(notification->asLLSD())); + } + + return false; +} + +// +// LLFloaterNotificationConsole +// +LLFloaterNotificationConsole::LLFloaterNotificationConsole(const LLSD& key) +: LLFloater(key) +{ + mCommitCallbackRegistrar.add("ClickAdd", boost::bind(&LLFloaterNotificationConsole::onClickAdd, this)); +} + +bool LLFloaterNotificationConsole::postBuild() +{ + // these are in the order of processing + addChannel("Unexpired"); + addChannel("Ignore"); + addChannel("VisibilityRules"); + addChannel("Visible", true); + // all the ones below attach to the Visible channel + addChannel("Persistent"); + addChannel("Alerts"); + addChannel("AlertModal"); + addChannel("Group Notifications"); + addChannel("Notifications"); + addChannel("NotificationTips"); + +// getChild("add_notification")->setClickedCallback(onClickAdd, this); + + LLComboBox* notifications = getChild("notification_types"); + LLNotifications::TemplateNames names = LLNotifications::instance().getTemplateNames(); + for (LLNotifications::TemplateNames::iterator template_it = names.begin(); + template_it != names.end(); + ++template_it) + { + notifications->add(*template_it); + } + notifications->sortByName(); + + return true; +} + +void LLFloaterNotificationConsole::addChannel(const std::string& name, bool open) +{ + LLLayoutStack& stack = getChildRef("notification_channels"); + LLNotificationChannelPanel::Params p; + p.min_dim = NOTIFICATION_PANEL_HEADER_HEIGHT; + p.auto_resize = true; + p.user_resize = true; + p.name = name; + LLNotificationChannelPanel* panelp = new LLNotificationChannelPanel(p); + stack.addPanel(panelp, LLLayoutStack::ANIMATE); + + LLButton& header_button = panelp->getChildRef("header"); + header_button.setToggleState(!open); + stack.collapsePanel(panelp, !open); + + updateResizeLimits(); +} + +void LLFloaterNotificationConsole::removeChannel(const std::string& name) +{ + LLPanel* panelp = getChild(name); + getChildRef("notification_channels").removeChild(panelp); + delete panelp; + + updateResizeLimits(); +} + +//static +void LLFloaterNotificationConsole::updateResizeLimits() +{ + const LLFloater::Params& floater_params = LLFloater::getDefaultParams(); + S32 floater_header_size = floater_params.header_height; + + LLLayoutStack& stack = getChildRef("notification_channels"); + setResizeLimits(getMinWidth(), floater_header_size + HEADER_PADDING + ((NOTIFICATION_PANEL_HEADER_HEIGHT + 3) * stack.getNumPanels())); +} + +void LLFloaterNotificationConsole::onClickAdd() +{ + std::string message_name = getChild("notification_types")->getValue().asString(); + if (!message_name.empty()) + { + LLNotifications::instance().add(message_name, LLSD(), LLSD()); + } +} + + +//=============== LLFloaterNotification ================ + +LLFloaterNotification::LLFloaterNotification(LLNotification* note) +: LLFloater(LLSD()), + mNote(note) +{ + buildFromFile("floater_notification.xml"); +} + +bool LLFloaterNotification::postBuild() +{ + setTitle(mNote->getName()); + getChild("payload")->setValue(mNote->getMessage()); + + LLComboBox* responses_combo = getChild("response"); + LLCtrlListInterface* response_list = responses_combo->getListInterface(); + LLNotificationFormPtr form(mNote->getForm()); + if(!form) + { + return true; + } + + responses_combo->setCommitCallback(onCommitResponse, this); + + LLSD form_sd = form->asLLSD(); + + for (LLSD::array_const_iterator form_item = form_sd.beginArray(); form_item != form_sd.endArray(); ++form_item) + { + if ( (*form_item)["type"].asString() != "button") continue; + std::string text = (*form_item)["text"].asString(); + response_list->addSimpleElement(text); + } + + return true; +} + +void LLFloaterNotification::respond() +{ + LLComboBox* responses_combo = getChild("response"); + LLCtrlListInterface* response_list = responses_combo->getListInterface(); + const std::string& trigger = response_list->getSelectedValue().asString(); + //LL_INFOS() << trigger << LL_ENDL; + + LLSD response = mNote->getResponseTemplate(); + response[trigger] = true; + mNote->respond(response); +} diff --git a/indra/newview/llfloaternotificationsconsole.h b/indra/newview/llfloaternotificationsconsole.h index b190d37f73..d644f74e13 100644 --- a/indra/newview/llfloaternotificationsconsole.h +++ b/indra/newview/llfloaternotificationsconsole.h @@ -1,75 +1,75 @@ -/** - * @file llfloaternotificationsconsole.h - * @brief Debugging console for unified notifications. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATER_NOTIFICATIONS_CONSOLE_H -#define LL_LLFLOATER_NOTIFICATIONS_CONSOLE_H - -#include "llfloater.h" -#include "lllayoutstack.h" -//#include "llnotificationsutil.h" - -class LLNotification; - -class LLFloaterNotificationConsole : - public LLFloater -{ - friend class LLFloaterReg; - -public: - - // LLPanel - bool postBuild(); - - void addChannel(const std::string& type, bool open = false); - void updateResizeLimits(LLLayoutStack &stack); - - void removeChannel(const std::string& type); - void updateResizeLimits(); - -private: - LLFloaterNotificationConsole(const LLSD& key); - void onClickAdd(); -}; - - -/* - * @brief Pop-up debugging view of a generic new notification. - */ -class LLFloaterNotification : public LLFloater -{ -public: - LLFloaterNotification(LLNotification* note); - - // LLPanel - bool postBuild(); - void respond(); - -private: - static void onCommitResponse(LLUICtrl* ctrl, void* data) { ((LLFloaterNotification*)data)->respond(); } - LLNotification* mNote; -}; -#endif - +/** + * @file llfloaternotificationsconsole.h + * @brief Debugging console for unified notifications. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATER_NOTIFICATIONS_CONSOLE_H +#define LL_LLFLOATER_NOTIFICATIONS_CONSOLE_H + +#include "llfloater.h" +#include "lllayoutstack.h" +//#include "llnotificationsutil.h" + +class LLNotification; + +class LLFloaterNotificationConsole : + public LLFloater +{ + friend class LLFloaterReg; + +public: + + // LLPanel + bool postBuild(); + + void addChannel(const std::string& type, bool open = false); + void updateResizeLimits(LLLayoutStack &stack); + + void removeChannel(const std::string& type); + void updateResizeLimits(); + +private: + LLFloaterNotificationConsole(const LLSD& key); + void onClickAdd(); +}; + + +/* + * @brief Pop-up debugging view of a generic new notification. + */ +class LLFloaterNotification : public LLFloater +{ +public: + LLFloaterNotification(LLNotification* note); + + // LLPanel + bool postBuild(); + void respond(); + +private: + static void onCommitResponse(LLUICtrl* ctrl, void* data) { ((LLFloaterNotification*)data)->respond(); } + LLNotification* mNote; +}; +#endif + diff --git a/indra/newview/llfloaternotificationstabbed.cpp b/indra/newview/llfloaternotificationstabbed.cpp index 6e793a9291..e571011acf 100644 --- a/indra/newview/llfloaternotificationstabbed.cpp +++ b/indra/newview/llfloaternotificationstabbed.cpp @@ -1,576 +1,576 @@ -/** - * @file llfloaternotificationstabbed.cpp - * @brief - * $LicenseInfo:firstyear=2015&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2015, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" // must be first include -#include "llfloaternotificationstabbed.h" - -#include "llchiclet.h" -#include "llchicletbar.h" -#include "llflatlistview.h" -#include "llfloaterreg.h" -#include "llnotificationmanager.h" -#include "llnotificationsutil.h" -#include "llscriptfloater.h" -#include "llspeakers.h" -#include "lltoastpanel.h" -#include "lltoastnotifypanel.h" - -//--------------------------------------------------------------------------------- -LLFloaterNotificationsTabbed::LLFloaterNotificationsTabbed(const LLSD& key) : LLTransientDockableFloater(NULL, true, key), - mChannel(NULL), - mSysWellChiclet(NULL), - mGroupInviteMessageList(NULL), - mGroupNoticeMessageList(NULL), - mTransactionMessageList(NULL), - mSystemMessageList(NULL), - mNotificationsSeparator(NULL), - mNotificationsTabContainer(NULL), - NOTIFICATION_TABBED_ANCHOR_NAME("notification_well_panel"), - IM_WELL_ANCHOR_NAME("im_well_panel"), - mIsReshapedByUser(false) - -{ - setOverlapsScreenChannel(true); - mNotificationUpdates.reset(new NotificationTabbedChannel(this)); - mNotificationsSeparator = new LLNotificationSeparator(); -} - -//--------------------------------------------------------------------------------- -bool LLFloaterNotificationsTabbed::postBuild() -{ - mGroupInviteMessageList = getChild("group_invite_notification_list"); - mGroupNoticeMessageList = getChild("group_notice_notification_list"); - mTransactionMessageList = getChild("transaction_notification_list"); - mSystemMessageList = getChild("system_notification_list"); - mNotificationsSeparator->initTaggedList(LLNotificationListItem::getGroupInviteTypes(), mGroupInviteMessageList); - mNotificationsSeparator->initTaggedList(LLNotificationListItem::getGroupNoticeTypes(), mGroupNoticeMessageList); - mNotificationsSeparator->initTaggedList(LLNotificationListItem::getTransactionTypes(), mTransactionMessageList); - mNotificationsSeparator->initUnTaggedList(mSystemMessageList); - mNotificationsTabContainer = getChild("notifications_tab_container"); - - mDeleteAllBtn = getChild("delete_all_button"); - mDeleteAllBtn->setClickedCallback(boost::bind(&LLFloaterNotificationsTabbed::onClickDeleteAllBtn,this)); - - mCollapseAllBtn = getChild("collapse_all_button"); - mCollapseAllBtn->setClickedCallback(boost::bind(&LLFloaterNotificationsTabbed::onClickCollapseAllBtn,this)); - - // get a corresponding channel - initChannel(); - bool rv = LLTransientDockableFloater::postBuild(); - - setTitle(getString("title_notification_tabbed_window")); - return rv; -} - -//--------------------------------------------------------------------------------- -void LLFloaterNotificationsTabbed::setMinimized(bool minimize) -{ - LLTransientDockableFloater::setMinimized(minimize); -} - -//--------------------------------------------------------------------------------- -void LLFloaterNotificationsTabbed::handleReshape(const LLRect& rect, bool by_user) -{ - mIsReshapedByUser |= by_user; // mark floater that it is reshaped by user - LLTransientDockableFloater::handleReshape(rect, by_user); -} - -//--------------------------------------------------------------------------------- -void LLFloaterNotificationsTabbed::onStartUpToastClick(S32 x, S32 y, MASK mask) -{ - // just set floater visible. Screen channels will be cleared. - setVisible(true); -} - -//--------------------------------------------------------------------------------- -void LLFloaterNotificationsTabbed::setSysWellChiclet(LLSysWellChiclet* chiclet) -{ - mSysWellChiclet = chiclet; - if(NULL != mSysWellChiclet) - { - mSysWellChiclet->updateWidget(isWindowEmpty()); - } -} - -//--------------------------------------------------------------------------------- -LLFloaterNotificationsTabbed::~LLFloaterNotificationsTabbed() -{ -} - -//--------------------------------------------------------------------------------- -void LLFloaterNotificationsTabbed::removeItemByID(const LLUUID& id, std::string type) -{ - if(mNotificationsSeparator->removeItemByID(type, id)) - { - if (NULL != mSysWellChiclet) - { - mSysWellChiclet->updateWidget(isWindowEmpty()); - } - reshapeWindow(); - updateNotificationCounters(); - } - else - { - LL_WARNS() << "Unable to remove notification from the list, ID: " << id - << LL_ENDL; - } - - // hide chiclet window if there are no items left - if(isWindowEmpty()) - { - setVisible(false); - } -} - -//--------------------------------------------------------------------------------- -LLPanel * LLFloaterNotificationsTabbed::findItemByID(const LLUUID& id, std::string type) -{ - return mNotificationsSeparator->findItemByID(type, id); -} - -//--------------------------------------------------------------------------------- -void LLFloaterNotificationsTabbed::initChannel() -{ - LLNotificationsUI::LLScreenChannelBase* channel = LLNotificationsUI::LLChannelManager::getInstance()->findChannelByID( - LLNotificationsUI::NOTIFICATION_CHANNEL_UUID); - mChannel = dynamic_cast(channel); - if(NULL == mChannel) - { - LL_WARNS() << "LLSysWellWindow::initChannel() - could not get a requested screen channel" << LL_ENDL; - } - - if(mChannel) - { - mChannel->addOnStoreToastCallback(boost::bind(&LLFloaterNotificationsTabbed::onStoreToast, this, _1, _2)); - } -} - -//--------------------------------------------------------------------------------- -void LLFloaterNotificationsTabbed::setVisible(bool visible) -{ - if (visible) - { - // when Notification channel is cleared, storable toasts will be added into the list. - clearScreenChannels(); - } - if (visible) - { - if (NULL == getDockControl() && getDockTongue().notNull()) - { - setDockControl(new LLDockControl( - LLChicletBar::getInstance()->getChild(getAnchorViewName()), this, - getDockTongue(), LLDockControl::BOTTOM)); - } - } - - // do not show empty window - if (NULL == mNotificationsSeparator || isWindowEmpty()) visible = false; - - LLTransientDockableFloater::setVisible(visible); - - // update notification channel state - initChannel(); // make sure the channel still exists - if(mChannel) - { - mChannel->updateShowToastsState(); - mChannel->redrawToasts(); - } -} - -//--------------------------------------------------------------------------------- -void LLFloaterNotificationsTabbed::setDocked(bool docked, bool pop_on_undock) -{ - LLTransientDockableFloater::setDocked(docked, pop_on_undock); - - // update notification channel state - if(mChannel) - { - mChannel->updateShowToastsState(); - mChannel->redrawToasts(); - } -} - -//--------------------------------------------------------------------------------- -void LLFloaterNotificationsTabbed::reshapeWindow() -{ - // update notification channel state - // update on a window reshape is important only when a window is visible and docked - if(mChannel && getVisible() && isDocked()) - { - mChannel->updateShowToastsState(); - } -} - -//--------------------------------------------------------------------------------- -bool LLFloaterNotificationsTabbed::isWindowEmpty() -{ - return mNotificationsSeparator->size() == 0; -} - -//--------------------------------------------------------------------------------- -LLFloaterNotificationsTabbed::NotificationTabbedChannel::NotificationTabbedChannel(LLFloaterNotificationsTabbed* notifications_tabbed_window) - : LLNotificationChannel(LLNotificationChannel::Params().name(notifications_tabbed_window->getPathname())), - mNotificationsTabbedWindow(notifications_tabbed_window) -{ - connectToChannel("Notifications"); - connectToChannel("Group Notifications"); - connectToChannel("Offer"); -} - -// static -//--------------------------------------------------------------------------------- -LLFloaterNotificationsTabbed* LLFloaterNotificationsTabbed::getInstance(const LLSD& key /*= LLSD()*/) -{ - return LLFloaterReg::getTypedInstance("notification_well_window", key); -} - -//--------------------------------------------------------------------------------- -void LLFloaterNotificationsTabbed::updateNotificationCounter(S32 panelIndex, S32 counterValue, std::string stringName) -{ - LLStringUtil::format_map_t string_args; - string_args["[COUNT]"] = llformat("%d", counterValue); - std::string label = getString(stringName, string_args); - mNotificationsTabContainer->setPanelTitle(panelIndex, label); -} - -//--------------------------------------------------------------------------------- -void LLFloaterNotificationsTabbed::updateNotificationCounters() -{ - updateNotificationCounter(0, mSystemMessageList->size(), "system_tab_title"); - updateNotificationCounter(1, mTransactionMessageList->size(), "transactions_tab_title"); - updateNotificationCounter(2, mGroupInviteMessageList->size(), "group_invitations_tab_title"); - updateNotificationCounter(3, mGroupNoticeMessageList->size(), "group_notices_tab_title"); -} - -//--------------------------------------------------------------------------------- -void LLFloaterNotificationsTabbed::addItem(LLNotificationListItem::Params p) -{ - // do not add clones - if (mNotificationsSeparator->findItemByID(p.notification_name, p.notification_id)) - return; - LLNotificationListItem* new_item = LLNotificationListItem::create(p); - if (new_item == NULL) - { - return; - } - if (mNotificationsSeparator->addItem(new_item->getNotificationName(), new_item)) - { - mSysWellChiclet->updateWidget(isWindowEmpty()); - reshapeWindow(); - updateNotificationCounters(); - new_item->setOnItemCloseCallback(boost::bind(&LLFloaterNotificationsTabbed::onItemClose, this, _1)); - new_item->setOnItemClickCallback(boost::bind(&LLFloaterNotificationsTabbed::onItemClick, this, _1)); - } - else - { - LL_WARNS() << "Unable to add Notification into the list, notification ID: " << p.notification_id - << ", title: " << new_item->getTitle() - << LL_ENDL; - - new_item->die(); - } -} - -//--------------------------------------------------------------------------------- -void LLFloaterNotificationsTabbed::closeAll() -{ - // Need to clear notification channel, to add storable toasts into the list. - clearScreenChannels(); - - std::vector items; - mNotificationsSeparator->getItems(items); - std::vector::iterator iter = items.begin(); - for (; iter != items.end(); ++iter) - { - onItemClose(*iter); - } -} - -//--------------------------------------------------------------------------------- -void LLFloaterNotificationsTabbed::getAllItemsOnCurrentTab(std::vector& items) const -{ - switch (mNotificationsTabContainer->getCurrentPanelIndex()) - { - case 0: - mSystemMessageList->getItems(items); - break; - case 1: - mTransactionMessageList->getItems(items); - break; - case 2: - mGroupInviteMessageList->getItems(items); - break; - case 3: - mGroupNoticeMessageList->getItems(items); - break; - default: - break; - } -} - -//--------------------------------------------------------------------------------- -void LLFloaterNotificationsTabbed::closeAllOnCurrentTab() -{ - // Need to clear notification channel, to add storable toasts into the list. - clearScreenChannels(); - std::vector items; - getAllItemsOnCurrentTab(items); - std::vector::iterator iter = items.begin(); - for (; iter != items.end(); ++iter) - { - LLNotificationListItem* notify_item = dynamic_cast(*iter); - if (notify_item) - onItemClose(notify_item); - } -} - -//--------------------------------------------------------------------------------- -void LLFloaterNotificationsTabbed::collapseAllOnCurrentTab() -{ - std::vector items; - getAllItemsOnCurrentTab(items); - std::vector::iterator iter = items.begin(); - for (; iter != items.end(); ++iter) - { - LLNotificationListItem* notify_item = dynamic_cast(*iter); - if (notify_item) - notify_item->setExpanded(false); - } -} - -//--------------------------------------------------------------------------------- -void LLFloaterNotificationsTabbed::clearScreenChannels() -{ - // 1 - remove StartUp toast and channel if present - if(!LLNotificationsUI::LLScreenChannel::getStartUpToastShown()) - { - LLNotificationsUI::LLChannelManager::getInstance()->onStartUpToastClose(); - } - - // 2 - remove toasts in Notification channel - if(mChannel) - { - mChannel->removeAndStoreAllStorableToasts(); - } -} - -//--------------------------------------------------------------------------------- -void LLFloaterNotificationsTabbed::onStoreToast(LLPanel* info_panel, LLUUID id) -{ - LLNotificationListItem::Params p; - p.notification_id = id; - p.title = static_cast(info_panel)->getTitle(); - LLNotificationPtr notify = mChannel->getToastByNotificationID(id)->getNotification(); - LLSD payload = notify->getPayload(); - p.notification_name = notify->getName(); - p.transaction_id = payload["transaction_id"]; - p.group_id = payload["group_id"]; - p.fee = payload["fee"]; - p.use_offline_cap = payload["use_offline_cap"].asInteger(); - p.subject = payload["subject"].asString(); - p.message = payload["message"].asString(); - p.sender = payload["sender_name"].asString(); - p.time_stamp = notify->getDate(); - p.received_time = payload["received_time"].asDate(); - p.paid_from_id = payload["from_id"]; - p.paid_to_id = payload["dest_id"]; - p.inventory_offer = payload["inventory_offer"]; - p.notification_priority = notify->getPriority(); - addItem(p); -} - -//--------------------------------------------------------------------------------- -void LLFloaterNotificationsTabbed::onItemClick(LLNotificationListItem* item) -{ - LLUUID id = item->getID(); - if (item->showPopup()) - { - LLFloaterReg::showInstance("inspect_toast", id); - } - else - { - item->setExpanded(true); - } -} - -//--------------------------------------------------------------------------------- -void LLFloaterNotificationsTabbed::onItemClose(LLNotificationListItem* item) -{ - LLUUID id = item->getID(); - - if(mChannel) - { - // removeItemByID() is invoked from killToastByNotificationID() and item will removed; - mChannel->killToastByNotificationID(id); - } - else - { - // removeItemByID() should be called one time for each item to remove it from notification well - removeItemByID(id, item->getNotificationName()); - } - -} - -//--------------------------------------------------------------------------------- -void LLFloaterNotificationsTabbed::onAdd( LLNotificationPtr notify ) -{ - removeItemByID(notify->getID(), notify->getName()); -} - -//--------------------------------------------------------------------------------- -void LLFloaterNotificationsTabbed::onClickDeleteAllBtn() -{ - closeAllOnCurrentTab(); -} - -//--------------------------------------------------------------------------------- -void LLFloaterNotificationsTabbed::onClickCollapseAllBtn() -{ - collapseAllOnCurrentTab(); -} - -//--------------------------------------------------------------------------------- -void LLNotificationSeparator::initTaggedList(const std::string& tag, LLNotificationListView* list) -{ - mNotificationListMap.insert(notification_list_map_t::value_type(tag, list)); - mNotificationLists.push_back(list); -} - -//--------------------------------------------------------------------------------- -void LLNotificationSeparator::initTaggedList(const std::set& tags, LLNotificationListView* list) -{ - std::set::const_iterator it = tags.begin(); - for(;it != tags.end();it++) - { - initTaggedList(*it, list); - } -} - -//--------------------------------------------------------------------------------- -void LLNotificationSeparator::initUnTaggedList(LLNotificationListView* list) -{ - mUnTaggedList = list; -} - -//--------------------------------------------------------------------------------- -bool LLNotificationSeparator::addItem(std::string& tag, LLNotificationListItem* item) -{ - notification_list_map_t::iterator it = mNotificationListMap.find(tag); - if (it != mNotificationListMap.end()) - { - return it->second->addNotification(item); - } - else if (mUnTaggedList != NULL) - { - return mUnTaggedList->addNotification(item); - } - return false; -} - -//--------------------------------------------------------------------------------- -bool LLNotificationSeparator::removeItemByID(std::string& tag, const LLUUID& id) -{ - notification_list_map_t::iterator it = mNotificationListMap.find(tag); - if (it != mNotificationListMap.end()) - { - return it->second->removeItemByValue(id); - } - else if (mUnTaggedList != NULL) - { - return mUnTaggedList->removeItemByValue(id); - } - return false; -} - -//--------------------------------------------------------------------------------- -U32 LLNotificationSeparator::size() const -{ - U32 size = 0; - notification_list_list_t::const_iterator it = mNotificationLists.begin(); - for (; it != mNotificationLists.end(); it++) - { - size = size + (*it)->size(); - } - if (mUnTaggedList != NULL) - { - size = size + mUnTaggedList->size(); - } - return size; -} - -//--------------------------------------------------------------------------------- -LLPanel* LLNotificationSeparator::findItemByID(std::string& tag, const LLUUID& id) -{ - notification_list_map_t::iterator it = mNotificationListMap.find(tag); - if (it != mNotificationListMap.end()) - { - return it->second->getItemByValue(id); - } - else if (mUnTaggedList != NULL) - { - return mUnTaggedList->getItemByValue(id); - } - - return NULL; -} - -//static -//--------------------------------------------------------------------------------- -void LLNotificationSeparator::getItemsFromList(std::vector& items, LLNotificationListView* list) -{ - std::vector list_items; - list->getItems(list_items); - std::vector::iterator it = list_items.begin(); - for (; it != list_items.end(); ++it) - { - LLNotificationListItem* notify_item = dynamic_cast(*it); - if (notify_item) - items.push_back(notify_item); - } -} - -//--------------------------------------------------------------------------------- -void LLNotificationSeparator::getItems(std::vector& items) const -{ - items.clear(); - notification_list_list_t::const_iterator lists_it = mNotificationLists.begin(); - for (; lists_it != mNotificationLists.end(); lists_it++) - { - getItemsFromList(items, *lists_it); - } - if (mUnTaggedList != NULL) - { - getItemsFromList(items, mUnTaggedList); - } -} - -//--------------------------------------------------------------------------------- -LLNotificationSeparator::LLNotificationSeparator() - : mUnTaggedList(NULL) -{} - -//--------------------------------------------------------------------------------- -LLNotificationSeparator::~LLNotificationSeparator() -{} +/** + * @file llfloaternotificationstabbed.cpp + * @brief + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2015, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" // must be first include +#include "llfloaternotificationstabbed.h" + +#include "llchiclet.h" +#include "llchicletbar.h" +#include "llflatlistview.h" +#include "llfloaterreg.h" +#include "llnotificationmanager.h" +#include "llnotificationsutil.h" +#include "llscriptfloater.h" +#include "llspeakers.h" +#include "lltoastpanel.h" +#include "lltoastnotifypanel.h" + +//--------------------------------------------------------------------------------- +LLFloaterNotificationsTabbed::LLFloaterNotificationsTabbed(const LLSD& key) : LLTransientDockableFloater(NULL, true, key), + mChannel(NULL), + mSysWellChiclet(NULL), + mGroupInviteMessageList(NULL), + mGroupNoticeMessageList(NULL), + mTransactionMessageList(NULL), + mSystemMessageList(NULL), + mNotificationsSeparator(NULL), + mNotificationsTabContainer(NULL), + NOTIFICATION_TABBED_ANCHOR_NAME("notification_well_panel"), + IM_WELL_ANCHOR_NAME("im_well_panel"), + mIsReshapedByUser(false) + +{ + setOverlapsScreenChannel(true); + mNotificationUpdates.reset(new NotificationTabbedChannel(this)); + mNotificationsSeparator = new LLNotificationSeparator(); +} + +//--------------------------------------------------------------------------------- +bool LLFloaterNotificationsTabbed::postBuild() +{ + mGroupInviteMessageList = getChild("group_invite_notification_list"); + mGroupNoticeMessageList = getChild("group_notice_notification_list"); + mTransactionMessageList = getChild("transaction_notification_list"); + mSystemMessageList = getChild("system_notification_list"); + mNotificationsSeparator->initTaggedList(LLNotificationListItem::getGroupInviteTypes(), mGroupInviteMessageList); + mNotificationsSeparator->initTaggedList(LLNotificationListItem::getGroupNoticeTypes(), mGroupNoticeMessageList); + mNotificationsSeparator->initTaggedList(LLNotificationListItem::getTransactionTypes(), mTransactionMessageList); + mNotificationsSeparator->initUnTaggedList(mSystemMessageList); + mNotificationsTabContainer = getChild("notifications_tab_container"); + + mDeleteAllBtn = getChild("delete_all_button"); + mDeleteAllBtn->setClickedCallback(boost::bind(&LLFloaterNotificationsTabbed::onClickDeleteAllBtn,this)); + + mCollapseAllBtn = getChild("collapse_all_button"); + mCollapseAllBtn->setClickedCallback(boost::bind(&LLFloaterNotificationsTabbed::onClickCollapseAllBtn,this)); + + // get a corresponding channel + initChannel(); + bool rv = LLTransientDockableFloater::postBuild(); + + setTitle(getString("title_notification_tabbed_window")); + return rv; +} + +//--------------------------------------------------------------------------------- +void LLFloaterNotificationsTabbed::setMinimized(bool minimize) +{ + LLTransientDockableFloater::setMinimized(minimize); +} + +//--------------------------------------------------------------------------------- +void LLFloaterNotificationsTabbed::handleReshape(const LLRect& rect, bool by_user) +{ + mIsReshapedByUser |= by_user; // mark floater that it is reshaped by user + LLTransientDockableFloater::handleReshape(rect, by_user); +} + +//--------------------------------------------------------------------------------- +void LLFloaterNotificationsTabbed::onStartUpToastClick(S32 x, S32 y, MASK mask) +{ + // just set floater visible. Screen channels will be cleared. + setVisible(true); +} + +//--------------------------------------------------------------------------------- +void LLFloaterNotificationsTabbed::setSysWellChiclet(LLSysWellChiclet* chiclet) +{ + mSysWellChiclet = chiclet; + if(NULL != mSysWellChiclet) + { + mSysWellChiclet->updateWidget(isWindowEmpty()); + } +} + +//--------------------------------------------------------------------------------- +LLFloaterNotificationsTabbed::~LLFloaterNotificationsTabbed() +{ +} + +//--------------------------------------------------------------------------------- +void LLFloaterNotificationsTabbed::removeItemByID(const LLUUID& id, std::string type) +{ + if(mNotificationsSeparator->removeItemByID(type, id)) + { + if (NULL != mSysWellChiclet) + { + mSysWellChiclet->updateWidget(isWindowEmpty()); + } + reshapeWindow(); + updateNotificationCounters(); + } + else + { + LL_WARNS() << "Unable to remove notification from the list, ID: " << id + << LL_ENDL; + } + + // hide chiclet window if there are no items left + if(isWindowEmpty()) + { + setVisible(false); + } +} + +//--------------------------------------------------------------------------------- +LLPanel * LLFloaterNotificationsTabbed::findItemByID(const LLUUID& id, std::string type) +{ + return mNotificationsSeparator->findItemByID(type, id); +} + +//--------------------------------------------------------------------------------- +void LLFloaterNotificationsTabbed::initChannel() +{ + LLNotificationsUI::LLScreenChannelBase* channel = LLNotificationsUI::LLChannelManager::getInstance()->findChannelByID( + LLNotificationsUI::NOTIFICATION_CHANNEL_UUID); + mChannel = dynamic_cast(channel); + if(NULL == mChannel) + { + LL_WARNS() << "LLSysWellWindow::initChannel() - could not get a requested screen channel" << LL_ENDL; + } + + if(mChannel) + { + mChannel->addOnStoreToastCallback(boost::bind(&LLFloaterNotificationsTabbed::onStoreToast, this, _1, _2)); + } +} + +//--------------------------------------------------------------------------------- +void LLFloaterNotificationsTabbed::setVisible(bool visible) +{ + if (visible) + { + // when Notification channel is cleared, storable toasts will be added into the list. + clearScreenChannels(); + } + if (visible) + { + if (NULL == getDockControl() && getDockTongue().notNull()) + { + setDockControl(new LLDockControl( + LLChicletBar::getInstance()->getChild(getAnchorViewName()), this, + getDockTongue(), LLDockControl::BOTTOM)); + } + } + + // do not show empty window + if (NULL == mNotificationsSeparator || isWindowEmpty()) visible = false; + + LLTransientDockableFloater::setVisible(visible); + + // update notification channel state + initChannel(); // make sure the channel still exists + if(mChannel) + { + mChannel->updateShowToastsState(); + mChannel->redrawToasts(); + } +} + +//--------------------------------------------------------------------------------- +void LLFloaterNotificationsTabbed::setDocked(bool docked, bool pop_on_undock) +{ + LLTransientDockableFloater::setDocked(docked, pop_on_undock); + + // update notification channel state + if(mChannel) + { + mChannel->updateShowToastsState(); + mChannel->redrawToasts(); + } +} + +//--------------------------------------------------------------------------------- +void LLFloaterNotificationsTabbed::reshapeWindow() +{ + // update notification channel state + // update on a window reshape is important only when a window is visible and docked + if(mChannel && getVisible() && isDocked()) + { + mChannel->updateShowToastsState(); + } +} + +//--------------------------------------------------------------------------------- +bool LLFloaterNotificationsTabbed::isWindowEmpty() +{ + return mNotificationsSeparator->size() == 0; +} + +//--------------------------------------------------------------------------------- +LLFloaterNotificationsTabbed::NotificationTabbedChannel::NotificationTabbedChannel(LLFloaterNotificationsTabbed* notifications_tabbed_window) + : LLNotificationChannel(LLNotificationChannel::Params().name(notifications_tabbed_window->getPathname())), + mNotificationsTabbedWindow(notifications_tabbed_window) +{ + connectToChannel("Notifications"); + connectToChannel("Group Notifications"); + connectToChannel("Offer"); +} + +// static +//--------------------------------------------------------------------------------- +LLFloaterNotificationsTabbed* LLFloaterNotificationsTabbed::getInstance(const LLSD& key /*= LLSD()*/) +{ + return LLFloaterReg::getTypedInstance("notification_well_window", key); +} + +//--------------------------------------------------------------------------------- +void LLFloaterNotificationsTabbed::updateNotificationCounter(S32 panelIndex, S32 counterValue, std::string stringName) +{ + LLStringUtil::format_map_t string_args; + string_args["[COUNT]"] = llformat("%d", counterValue); + std::string label = getString(stringName, string_args); + mNotificationsTabContainer->setPanelTitle(panelIndex, label); +} + +//--------------------------------------------------------------------------------- +void LLFloaterNotificationsTabbed::updateNotificationCounters() +{ + updateNotificationCounter(0, mSystemMessageList->size(), "system_tab_title"); + updateNotificationCounter(1, mTransactionMessageList->size(), "transactions_tab_title"); + updateNotificationCounter(2, mGroupInviteMessageList->size(), "group_invitations_tab_title"); + updateNotificationCounter(3, mGroupNoticeMessageList->size(), "group_notices_tab_title"); +} + +//--------------------------------------------------------------------------------- +void LLFloaterNotificationsTabbed::addItem(LLNotificationListItem::Params p) +{ + // do not add clones + if (mNotificationsSeparator->findItemByID(p.notification_name, p.notification_id)) + return; + LLNotificationListItem* new_item = LLNotificationListItem::create(p); + if (new_item == NULL) + { + return; + } + if (mNotificationsSeparator->addItem(new_item->getNotificationName(), new_item)) + { + mSysWellChiclet->updateWidget(isWindowEmpty()); + reshapeWindow(); + updateNotificationCounters(); + new_item->setOnItemCloseCallback(boost::bind(&LLFloaterNotificationsTabbed::onItemClose, this, _1)); + new_item->setOnItemClickCallback(boost::bind(&LLFloaterNotificationsTabbed::onItemClick, this, _1)); + } + else + { + LL_WARNS() << "Unable to add Notification into the list, notification ID: " << p.notification_id + << ", title: " << new_item->getTitle() + << LL_ENDL; + + new_item->die(); + } +} + +//--------------------------------------------------------------------------------- +void LLFloaterNotificationsTabbed::closeAll() +{ + // Need to clear notification channel, to add storable toasts into the list. + clearScreenChannels(); + + std::vector items; + mNotificationsSeparator->getItems(items); + std::vector::iterator iter = items.begin(); + for (; iter != items.end(); ++iter) + { + onItemClose(*iter); + } +} + +//--------------------------------------------------------------------------------- +void LLFloaterNotificationsTabbed::getAllItemsOnCurrentTab(std::vector& items) const +{ + switch (mNotificationsTabContainer->getCurrentPanelIndex()) + { + case 0: + mSystemMessageList->getItems(items); + break; + case 1: + mTransactionMessageList->getItems(items); + break; + case 2: + mGroupInviteMessageList->getItems(items); + break; + case 3: + mGroupNoticeMessageList->getItems(items); + break; + default: + break; + } +} + +//--------------------------------------------------------------------------------- +void LLFloaterNotificationsTabbed::closeAllOnCurrentTab() +{ + // Need to clear notification channel, to add storable toasts into the list. + clearScreenChannels(); + std::vector items; + getAllItemsOnCurrentTab(items); + std::vector::iterator iter = items.begin(); + for (; iter != items.end(); ++iter) + { + LLNotificationListItem* notify_item = dynamic_cast(*iter); + if (notify_item) + onItemClose(notify_item); + } +} + +//--------------------------------------------------------------------------------- +void LLFloaterNotificationsTabbed::collapseAllOnCurrentTab() +{ + std::vector items; + getAllItemsOnCurrentTab(items); + std::vector::iterator iter = items.begin(); + for (; iter != items.end(); ++iter) + { + LLNotificationListItem* notify_item = dynamic_cast(*iter); + if (notify_item) + notify_item->setExpanded(false); + } +} + +//--------------------------------------------------------------------------------- +void LLFloaterNotificationsTabbed::clearScreenChannels() +{ + // 1 - remove StartUp toast and channel if present + if(!LLNotificationsUI::LLScreenChannel::getStartUpToastShown()) + { + LLNotificationsUI::LLChannelManager::getInstance()->onStartUpToastClose(); + } + + // 2 - remove toasts in Notification channel + if(mChannel) + { + mChannel->removeAndStoreAllStorableToasts(); + } +} + +//--------------------------------------------------------------------------------- +void LLFloaterNotificationsTabbed::onStoreToast(LLPanel* info_panel, LLUUID id) +{ + LLNotificationListItem::Params p; + p.notification_id = id; + p.title = static_cast(info_panel)->getTitle(); + LLNotificationPtr notify = mChannel->getToastByNotificationID(id)->getNotification(); + LLSD payload = notify->getPayload(); + p.notification_name = notify->getName(); + p.transaction_id = payload["transaction_id"]; + p.group_id = payload["group_id"]; + p.fee = payload["fee"]; + p.use_offline_cap = payload["use_offline_cap"].asInteger(); + p.subject = payload["subject"].asString(); + p.message = payload["message"].asString(); + p.sender = payload["sender_name"].asString(); + p.time_stamp = notify->getDate(); + p.received_time = payload["received_time"].asDate(); + p.paid_from_id = payload["from_id"]; + p.paid_to_id = payload["dest_id"]; + p.inventory_offer = payload["inventory_offer"]; + p.notification_priority = notify->getPriority(); + addItem(p); +} + +//--------------------------------------------------------------------------------- +void LLFloaterNotificationsTabbed::onItemClick(LLNotificationListItem* item) +{ + LLUUID id = item->getID(); + if (item->showPopup()) + { + LLFloaterReg::showInstance("inspect_toast", id); + } + else + { + item->setExpanded(true); + } +} + +//--------------------------------------------------------------------------------- +void LLFloaterNotificationsTabbed::onItemClose(LLNotificationListItem* item) +{ + LLUUID id = item->getID(); + + if(mChannel) + { + // removeItemByID() is invoked from killToastByNotificationID() and item will removed; + mChannel->killToastByNotificationID(id); + } + else + { + // removeItemByID() should be called one time for each item to remove it from notification well + removeItemByID(id, item->getNotificationName()); + } + +} + +//--------------------------------------------------------------------------------- +void LLFloaterNotificationsTabbed::onAdd( LLNotificationPtr notify ) +{ + removeItemByID(notify->getID(), notify->getName()); +} + +//--------------------------------------------------------------------------------- +void LLFloaterNotificationsTabbed::onClickDeleteAllBtn() +{ + closeAllOnCurrentTab(); +} + +//--------------------------------------------------------------------------------- +void LLFloaterNotificationsTabbed::onClickCollapseAllBtn() +{ + collapseAllOnCurrentTab(); +} + +//--------------------------------------------------------------------------------- +void LLNotificationSeparator::initTaggedList(const std::string& tag, LLNotificationListView* list) +{ + mNotificationListMap.insert(notification_list_map_t::value_type(tag, list)); + mNotificationLists.push_back(list); +} + +//--------------------------------------------------------------------------------- +void LLNotificationSeparator::initTaggedList(const std::set& tags, LLNotificationListView* list) +{ + std::set::const_iterator it = tags.begin(); + for(;it != tags.end();it++) + { + initTaggedList(*it, list); + } +} + +//--------------------------------------------------------------------------------- +void LLNotificationSeparator::initUnTaggedList(LLNotificationListView* list) +{ + mUnTaggedList = list; +} + +//--------------------------------------------------------------------------------- +bool LLNotificationSeparator::addItem(std::string& tag, LLNotificationListItem* item) +{ + notification_list_map_t::iterator it = mNotificationListMap.find(tag); + if (it != mNotificationListMap.end()) + { + return it->second->addNotification(item); + } + else if (mUnTaggedList != NULL) + { + return mUnTaggedList->addNotification(item); + } + return false; +} + +//--------------------------------------------------------------------------------- +bool LLNotificationSeparator::removeItemByID(std::string& tag, const LLUUID& id) +{ + notification_list_map_t::iterator it = mNotificationListMap.find(tag); + if (it != mNotificationListMap.end()) + { + return it->second->removeItemByValue(id); + } + else if (mUnTaggedList != NULL) + { + return mUnTaggedList->removeItemByValue(id); + } + return false; +} + +//--------------------------------------------------------------------------------- +U32 LLNotificationSeparator::size() const +{ + U32 size = 0; + notification_list_list_t::const_iterator it = mNotificationLists.begin(); + for (; it != mNotificationLists.end(); it++) + { + size = size + (*it)->size(); + } + if (mUnTaggedList != NULL) + { + size = size + mUnTaggedList->size(); + } + return size; +} + +//--------------------------------------------------------------------------------- +LLPanel* LLNotificationSeparator::findItemByID(std::string& tag, const LLUUID& id) +{ + notification_list_map_t::iterator it = mNotificationListMap.find(tag); + if (it != mNotificationListMap.end()) + { + return it->second->getItemByValue(id); + } + else if (mUnTaggedList != NULL) + { + return mUnTaggedList->getItemByValue(id); + } + + return NULL; +} + +//static +//--------------------------------------------------------------------------------- +void LLNotificationSeparator::getItemsFromList(std::vector& items, LLNotificationListView* list) +{ + std::vector list_items; + list->getItems(list_items); + std::vector::iterator it = list_items.begin(); + for (; it != list_items.end(); ++it) + { + LLNotificationListItem* notify_item = dynamic_cast(*it); + if (notify_item) + items.push_back(notify_item); + } +} + +//--------------------------------------------------------------------------------- +void LLNotificationSeparator::getItems(std::vector& items) const +{ + items.clear(); + notification_list_list_t::const_iterator lists_it = mNotificationLists.begin(); + for (; lists_it != mNotificationLists.end(); lists_it++) + { + getItemsFromList(items, *lists_it); + } + if (mUnTaggedList != NULL) + { + getItemsFromList(items, mUnTaggedList); + } +} + +//--------------------------------------------------------------------------------- +LLNotificationSeparator::LLNotificationSeparator() + : mUnTaggedList(NULL) +{} + +//--------------------------------------------------------------------------------- +LLNotificationSeparator::~LLNotificationSeparator() +{} diff --git a/indra/newview/llfloaternotificationstabbed.h b/indra/newview/llfloaternotificationstabbed.h index 2f6a6bd5dc..87e880c8d2 100644 --- a/indra/newview/llfloaternotificationstabbed.h +++ b/indra/newview/llfloaternotificationstabbed.h @@ -1,174 +1,174 @@ -/** - * @file llfloaternotificationstabbed.h - * @brief - * - * $LicenseInfo:firstyear=2015&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2015, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_FLOATERNOTIFICATIONSTABBED_H -#define LL_FLOATERNOTIFICATIONSTABBED_H - -#include "llimview.h" -#include "llnotifications.h" -#include "llscreenchannel.h" -#include "llsyswellitem.h" -#include "lltransientdockablefloater.h" -#include "llnotificationlistview.h" -#include "lltabcontainer.h" - -class LLAvatarName; -class LLChiclet; -class LLFlatListView; -class LLIMChiclet; -class LLScriptChiclet; -class LLSysWellChiclet; - -class LLNotificationSeparator -{ -public: - LLNotificationSeparator(); - ~LLNotificationSeparator(); - void initTaggedList(const std::string& tag, LLNotificationListView* list); - void initTaggedList(const std::set& tags, LLNotificationListView* list); - void initUnTaggedList(LLNotificationListView* list); - bool addItem(std::string& tag, LLNotificationListItem* item); - LLPanel* findItemByID(std::string& tag, const LLUUID& id); - bool removeItemByID(std::string& tag, const LLUUID& id); - void getItems(std::vector& items) const; - U32 size() const; -private: - static void getItemsFromList(std::vector& items, LLNotificationListView* list); - - typedef std::map notification_list_map_t; - notification_list_map_t mNotificationListMap; - typedef std::list notification_list_list_t; - notification_list_list_t mNotificationLists; - LLNotificationListView* mUnTaggedList; -}; - -class LLFloaterNotificationsTabbed : public LLTransientDockableFloater -{ -public: - LOG_CLASS(LLFloaterNotificationsTabbed); - - LLFloaterNotificationsTabbed(const LLSD& key); - virtual ~LLFloaterNotificationsTabbed(); - bool postBuild(); - - // other interface functions - // check is window empty - bool isWindowEmpty(); - - // Operating with items - void removeItemByID(const LLUUID& id, std::string type); - LLPanel * findItemByID(const LLUUID& id, std::string type); - void updateNotificationCounters(); - void updateNotificationCounter(S32 panelIndex, S32 counterValue, std::string stringName); - - // Operating with outfit - virtual void setVisible(bool visible); - - /*virtual*/ void setDocked(bool docked, bool pop_on_undock = true); - // override LLFloater's minimization according to EXT-1216 - /*virtual*/ void setMinimized(bool minimize); - /*virtual*/ void handleReshape(const LLRect& rect, bool by_user); - - void onStartUpToastClick(S32 x, S32 y, MASK mask); - /*virtual*/ void onAdd(LLNotificationPtr notify); - - void setSysWellChiclet(LLSysWellChiclet* chiclet); - void closeAll(); - - static LLFloaterNotificationsTabbed* getInstance(const LLSD& key = LLSD()); - - // size constants for the window and for its elements - static constexpr S32 MAX_WINDOW_HEIGHT = 200; - static constexpr S32 MIN_WINDOW_WIDTH = 318; - -private: - // init Window's channel - virtual void initChannel(); - - const std::string NOTIFICATION_TABBED_ANCHOR_NAME; - const std::string IM_WELL_ANCHOR_NAME; - //virtual const std::string& getAnchorViewName() = 0; - - void reshapeWindow(); - - // pointer to a corresponding channel's instance - LLNotificationsUI::LLScreenChannel* mChannel; - - /** - * Reference to an appropriate Well chiclet to release "new message" state. EXT-3147 - */ - LLSysWellChiclet* mSysWellChiclet; - - bool mIsReshapedByUser; - - struct NotificationTabbedChannel : public LLNotificationChannel - { - NotificationTabbedChannel(LLFloaterNotificationsTabbed*); - void onDelete(LLNotificationPtr notify) - { - mNotificationsTabbedWindow->removeItemByID(notify->getID(), notify->getName()); - } - - LLFloaterNotificationsTabbed* mNotificationsTabbedWindow; - }; - - LLNotificationChannelPtr mNotificationUpdates; - virtual const std::string& getAnchorViewName() { return NOTIFICATION_TABBED_ANCHOR_NAME; } - - // init Window's channel - // void initChannel(); - void clearScreenChannels(); - // Operating with items - void addItem(LLNotificationListItem::Params p); - void getAllItemsOnCurrentTab(std::vector& items) const; - - // Closes all notifications and removes them from the Notification Well - void closeAllOnCurrentTab(); - void collapseAllOnCurrentTab(); - - void onStoreToast(LLPanel* info_panel, LLUUID id); - void onClickDeleteAllBtn(); - void onClickCollapseAllBtn(); - // Handlers - void onItemClick(LLNotificationListItem* item); - void onItemClose(LLNotificationListItem* item); - // ID of a toast loaded by user (by clicking notification well item) - LLUUID mLoadedToastId; - - LLNotificationListView* mGroupInviteMessageList; - LLNotificationListView* mGroupNoticeMessageList; - LLNotificationListView* mTransactionMessageList; - LLNotificationListView* mSystemMessageList; - LLNotificationSeparator* mNotificationsSeparator; - LLTabContainer* mNotificationsTabContainer; - LLButton* mDeleteAllBtn; - LLButton* mCollapseAllBtn; -}; - -#endif // LL_FLOATERNOTIFICATIONSTABBED_H - - - +/** + * @file llfloaternotificationstabbed.h + * @brief + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2015, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_FLOATERNOTIFICATIONSTABBED_H +#define LL_FLOATERNOTIFICATIONSTABBED_H + +#include "llimview.h" +#include "llnotifications.h" +#include "llscreenchannel.h" +#include "llsyswellitem.h" +#include "lltransientdockablefloater.h" +#include "llnotificationlistview.h" +#include "lltabcontainer.h" + +class LLAvatarName; +class LLChiclet; +class LLFlatListView; +class LLIMChiclet; +class LLScriptChiclet; +class LLSysWellChiclet; + +class LLNotificationSeparator +{ +public: + LLNotificationSeparator(); + ~LLNotificationSeparator(); + void initTaggedList(const std::string& tag, LLNotificationListView* list); + void initTaggedList(const std::set& tags, LLNotificationListView* list); + void initUnTaggedList(LLNotificationListView* list); + bool addItem(std::string& tag, LLNotificationListItem* item); + LLPanel* findItemByID(std::string& tag, const LLUUID& id); + bool removeItemByID(std::string& tag, const LLUUID& id); + void getItems(std::vector& items) const; + U32 size() const; +private: + static void getItemsFromList(std::vector& items, LLNotificationListView* list); + + typedef std::map notification_list_map_t; + notification_list_map_t mNotificationListMap; + typedef std::list notification_list_list_t; + notification_list_list_t mNotificationLists; + LLNotificationListView* mUnTaggedList; +}; + +class LLFloaterNotificationsTabbed : public LLTransientDockableFloater +{ +public: + LOG_CLASS(LLFloaterNotificationsTabbed); + + LLFloaterNotificationsTabbed(const LLSD& key); + virtual ~LLFloaterNotificationsTabbed(); + bool postBuild(); + + // other interface functions + // check is window empty + bool isWindowEmpty(); + + // Operating with items + void removeItemByID(const LLUUID& id, std::string type); + LLPanel * findItemByID(const LLUUID& id, std::string type); + void updateNotificationCounters(); + void updateNotificationCounter(S32 panelIndex, S32 counterValue, std::string stringName); + + // Operating with outfit + virtual void setVisible(bool visible); + + /*virtual*/ void setDocked(bool docked, bool pop_on_undock = true); + // override LLFloater's minimization according to EXT-1216 + /*virtual*/ void setMinimized(bool minimize); + /*virtual*/ void handleReshape(const LLRect& rect, bool by_user); + + void onStartUpToastClick(S32 x, S32 y, MASK mask); + /*virtual*/ void onAdd(LLNotificationPtr notify); + + void setSysWellChiclet(LLSysWellChiclet* chiclet); + void closeAll(); + + static LLFloaterNotificationsTabbed* getInstance(const LLSD& key = LLSD()); + + // size constants for the window and for its elements + static constexpr S32 MAX_WINDOW_HEIGHT = 200; + static constexpr S32 MIN_WINDOW_WIDTH = 318; + +private: + // init Window's channel + virtual void initChannel(); + + const std::string NOTIFICATION_TABBED_ANCHOR_NAME; + const std::string IM_WELL_ANCHOR_NAME; + //virtual const std::string& getAnchorViewName() = 0; + + void reshapeWindow(); + + // pointer to a corresponding channel's instance + LLNotificationsUI::LLScreenChannel* mChannel; + + /** + * Reference to an appropriate Well chiclet to release "new message" state. EXT-3147 + */ + LLSysWellChiclet* mSysWellChiclet; + + bool mIsReshapedByUser; + + struct NotificationTabbedChannel : public LLNotificationChannel + { + NotificationTabbedChannel(LLFloaterNotificationsTabbed*); + void onDelete(LLNotificationPtr notify) + { + mNotificationsTabbedWindow->removeItemByID(notify->getID(), notify->getName()); + } + + LLFloaterNotificationsTabbed* mNotificationsTabbedWindow; + }; + + LLNotificationChannelPtr mNotificationUpdates; + virtual const std::string& getAnchorViewName() { return NOTIFICATION_TABBED_ANCHOR_NAME; } + + // init Window's channel + // void initChannel(); + void clearScreenChannels(); + // Operating with items + void addItem(LLNotificationListItem::Params p); + void getAllItemsOnCurrentTab(std::vector& items) const; + + // Closes all notifications and removes them from the Notification Well + void closeAllOnCurrentTab(); + void collapseAllOnCurrentTab(); + + void onStoreToast(LLPanel* info_panel, LLUUID id); + void onClickDeleteAllBtn(); + void onClickCollapseAllBtn(); + // Handlers + void onItemClick(LLNotificationListItem* item); + void onItemClose(LLNotificationListItem* item); + // ID of a toast loaded by user (by clicking notification well item) + LLUUID mLoadedToastId; + + LLNotificationListView* mGroupInviteMessageList; + LLNotificationListView* mGroupNoticeMessageList; + LLNotificationListView* mTransactionMessageList; + LLNotificationListView* mSystemMessageList; + LLNotificationSeparator* mNotificationsSeparator; + LLTabContainer* mNotificationsTabContainer; + LLButton* mDeleteAllBtn; + LLButton* mCollapseAllBtn; +}; + +#endif // LL_FLOATERNOTIFICATIONSTABBED_H + + + diff --git a/indra/newview/llfloaterobjectweights.cpp b/indra/newview/llfloaterobjectweights.cpp index 7f9db8d850..26b7304b9a 100644 --- a/indra/newview/llfloaterobjectweights.cpp +++ b/indra/newview/llfloaterobjectweights.cpp @@ -1,274 +1,274 @@ -/** - * @file llfloaterobjectweights.cpp - * @brief Object weights advanced view floater - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ -#include "llviewerprecompiledheaders.h" - -#include "llfloaterobjectweights.h" - -#include "llparcel.h" - -#include "llfloaterreg.h" -#include "lltextbox.h" - -#include "llagent.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" - -// virtual -bool LLCrossParcelFunctor::apply(LLViewerObject* obj) -{ - // Add the root object box. - mBoundingBox.addBBoxAgent(LLBBox(obj->getPositionRegion(), obj->getRotationRegion(), obj->getScale() * -0.5f, obj->getScale() * 0.5f).getAxisAligned()); - - // Extend the bounding box across all the children. - LLViewerObject::const_child_list_t children = obj->getChildren(); - for (LLViewerObject::const_child_list_t::const_iterator iter = children.begin(); - iter != children.end(); iter++) - { - LLViewerObject* child = *iter; - mBoundingBox.addBBoxAgent(LLBBox(child->getPositionRegion(), child->getRotationRegion(), child->getScale() * -0.5f, child->getScale() * 0.5f).getAxisAligned()); - } - - bool result = false; - - LLViewerRegion* region = obj->getRegion(); - if (region) - { - std::vector boxes; - boxes.push_back(mBoundingBox); - result = region->objectsCrossParcel(boxes); - } - - return result; -} - -LLFloaterObjectWeights::LLFloaterObjectWeights(const LLSD& key) -: LLFloater(key), - mSelectedObjects(NULL), - mSelectedPrims(NULL), - mSelectedDownloadWeight(NULL), - mSelectedPhysicsWeight(NULL), - mSelectedServerWeight(NULL), - mSelectedDisplayWeight(NULL), - mSelectedOnLand(NULL), - mRezzedOnLand(NULL), - mRemainingCapacity(NULL), - mTotalCapacity(NULL) -{ -} - -LLFloaterObjectWeights::~LLFloaterObjectWeights() -{ -} - -// virtual -bool LLFloaterObjectWeights::postBuild() -{ - mSelectedObjects = getChild("objects"); - mSelectedPrims = getChild("prims"); - - mSelectedDownloadWeight = getChild("download"); - mSelectedPhysicsWeight = getChild("physics"); - mSelectedServerWeight = getChild("server"); - mSelectedDisplayWeight = getChild("display"); - - mSelectedOnLand = getChild("selected"); - mRezzedOnLand = getChild("rezzed_on_land"); - mRemainingCapacity = getChild("remaining_capacity"); - mTotalCapacity = getChild("total_capacity"); - - return true; -} - -// virtual -void LLFloaterObjectWeights::onOpen(const LLSD& key) -{ - refresh(); - updateLandImpacts(LLViewerParcelMgr::getInstance()->getFloatingParcelSelection()->getParcel()); -} - -// virtual -void LLFloaterObjectWeights::onWeightsUpdate(const SelectionCost& selection_cost) -{ - mSelectedDownloadWeight->setText(llformat("%.1f", selection_cost.mNetworkCost)); - mSelectedPhysicsWeight->setText(llformat("%.1f", selection_cost.mPhysicsCost)); - mSelectedServerWeight->setText(llformat("%.1f", selection_cost.mSimulationCost)); - - S32 render_cost = LLSelectMgr::getInstance()->getSelection()->getSelectedObjectRenderCost(); - mSelectedDisplayWeight->setText(llformat("%d", render_cost)); - - toggleWeightsLoadingIndicators(false); -} - -//virtual -void LLFloaterObjectWeights::setErrorStatus(S32 status, const std::string& reason) -{ - const std::string text = getString("nothing_selected"); - - mSelectedDownloadWeight->setText(text); - mSelectedPhysicsWeight->setText(text); - mSelectedServerWeight->setText(text); - mSelectedDisplayWeight->setText(text); - - toggleWeightsLoadingIndicators(false); -} - -void LLFloaterObjectWeights::updateLandImpacts(const LLParcel* parcel) -{ - if (!parcel || LLSelectMgr::getInstance()->getSelection()->isEmpty()) - { - updateIfNothingSelected(); - } - else - { - S32 rezzed_prims = parcel->getSimWidePrimCount(); - S32 total_capacity = parcel->getSimWideMaxPrimCapacity(); - // Can't have more than region max tasks, regardless of parcel - // object bonus factor. - LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); - if (region) - { - S32 max_tasks_per_region = (S32)region->getMaxTasks(); - total_capacity = llmin(total_capacity, max_tasks_per_region); - } - - mRezzedOnLand->setText(llformat("%d", rezzed_prims)); - mRemainingCapacity->setText(llformat("%d", total_capacity - rezzed_prims)); - mTotalCapacity->setText(llformat("%d", total_capacity)); - - toggleLandImpactsLoadingIndicators(false); - } -} - -void LLFloaterObjectWeights::refresh() -{ - LLSelectMgr* sel_mgr = LLSelectMgr::getInstance(); - - if (sel_mgr->getSelection()->isEmpty()) - { - updateIfNothingSelected(); - } - else - { - S32 prim_count = sel_mgr->getSelection()->getObjectCount(); - S32 link_count = sel_mgr->getSelection()->getRootObjectCount(); - F32 prim_equiv = sel_mgr->getSelection()->getSelectedLinksetCost(); - - mSelectedObjects->setText(llformat("%d", link_count)); - mSelectedPrims->setText(llformat("%d", prim_count)); - mSelectedOnLand->setText(llformat("%.1d", (S32)prim_equiv)); - - LLCrossParcelFunctor func; - if (sel_mgr->getSelection()->applyToRootObjects(&func, true)) - { - // Some of the selected objects cross parcel bounds. - // We don't display object weights and land impacts in this case. - const std::string text = getString("nothing_selected"); - - mRezzedOnLand->setText(text); - mRemainingCapacity->setText(text); - mTotalCapacity->setText(text); - - toggleLandImpactsLoadingIndicators(false); - } - - LLViewerRegion* region = gAgent.getRegion(); - if (region && region->capabilitiesReceived()) - { - for (LLObjectSelection::valid_root_iterator iter = sel_mgr->getSelection()->valid_root_begin(); - iter != sel_mgr->getSelection()->valid_root_end(); ++iter) - { - LLAccountingCostManager::getInstance()->addObject((*iter)->getObject()->getID()); - } - - std::string url = region->getCapability("ResourceCostSelected"); - if (!url.empty()) - { - // Update the transaction id before the new fetch request - generateTransactionID(); - - LLAccountingCostManager::getInstance()->fetchCosts(Roots, url, getObserverHandle()); - toggleWeightsLoadingIndicators(true); - } - } - else - { - LL_WARNS() << "Failed to get region capabilities" << LL_ENDL; - } - } -} - -// virtual -void LLFloaterObjectWeights::generateTransactionID() -{ - mTransactionID.generate(); -} - -void LLFloaterObjectWeights::toggleWeightsLoadingIndicators(bool visible) -{ - childSetVisible("download_loading_indicator", visible); - childSetVisible("physics_loading_indicator", visible); - childSetVisible("server_loading_indicator", visible); - childSetVisible("display_loading_indicator", visible); - - mSelectedDownloadWeight->setVisible(!visible); - mSelectedPhysicsWeight->setVisible(!visible); - mSelectedServerWeight->setVisible(!visible); - mSelectedDisplayWeight->setVisible(!visible); -} - -void LLFloaterObjectWeights::toggleLandImpactsLoadingIndicators(bool visible) -{ - childSetVisible("selected_loading_indicator", visible); - childSetVisible("rezzed_on_land_loading_indicator", visible); - childSetVisible("remaining_capacity_loading_indicator", visible); - childSetVisible("total_capacity_loading_indicator", visible); - - mSelectedOnLand->setVisible(!visible); - mRezzedOnLand->setVisible(!visible); - mRemainingCapacity->setVisible(!visible); - mTotalCapacity->setVisible(!visible); -} - -void LLFloaterObjectWeights::updateIfNothingSelected() -{ - const std::string text = getString("nothing_selected"); - - mSelectedObjects->setText(text); - mSelectedPrims->setText(text); - - mSelectedDownloadWeight->setText(text); - mSelectedPhysicsWeight->setText(text); - mSelectedServerWeight->setText(text); - mSelectedDisplayWeight->setText(text); - - mSelectedOnLand->setText(text); - mRezzedOnLand->setText(text); - mRemainingCapacity->setText(text); - mTotalCapacity->setText(text); - - toggleWeightsLoadingIndicators(false); - toggleLandImpactsLoadingIndicators(false); -} +/** + * @file llfloaterobjectweights.cpp + * @brief Object weights advanced view floater + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#include "llviewerprecompiledheaders.h" + +#include "llfloaterobjectweights.h" + +#include "llparcel.h" + +#include "llfloaterreg.h" +#include "lltextbox.h" + +#include "llagent.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" + +// virtual +bool LLCrossParcelFunctor::apply(LLViewerObject* obj) +{ + // Add the root object box. + mBoundingBox.addBBoxAgent(LLBBox(obj->getPositionRegion(), obj->getRotationRegion(), obj->getScale() * -0.5f, obj->getScale() * 0.5f).getAxisAligned()); + + // Extend the bounding box across all the children. + LLViewerObject::const_child_list_t children = obj->getChildren(); + for (LLViewerObject::const_child_list_t::const_iterator iter = children.begin(); + iter != children.end(); iter++) + { + LLViewerObject* child = *iter; + mBoundingBox.addBBoxAgent(LLBBox(child->getPositionRegion(), child->getRotationRegion(), child->getScale() * -0.5f, child->getScale() * 0.5f).getAxisAligned()); + } + + bool result = false; + + LLViewerRegion* region = obj->getRegion(); + if (region) + { + std::vector boxes; + boxes.push_back(mBoundingBox); + result = region->objectsCrossParcel(boxes); + } + + return result; +} + +LLFloaterObjectWeights::LLFloaterObjectWeights(const LLSD& key) +: LLFloater(key), + mSelectedObjects(NULL), + mSelectedPrims(NULL), + mSelectedDownloadWeight(NULL), + mSelectedPhysicsWeight(NULL), + mSelectedServerWeight(NULL), + mSelectedDisplayWeight(NULL), + mSelectedOnLand(NULL), + mRezzedOnLand(NULL), + mRemainingCapacity(NULL), + mTotalCapacity(NULL) +{ +} + +LLFloaterObjectWeights::~LLFloaterObjectWeights() +{ +} + +// virtual +bool LLFloaterObjectWeights::postBuild() +{ + mSelectedObjects = getChild("objects"); + mSelectedPrims = getChild("prims"); + + mSelectedDownloadWeight = getChild("download"); + mSelectedPhysicsWeight = getChild("physics"); + mSelectedServerWeight = getChild("server"); + mSelectedDisplayWeight = getChild("display"); + + mSelectedOnLand = getChild("selected"); + mRezzedOnLand = getChild("rezzed_on_land"); + mRemainingCapacity = getChild("remaining_capacity"); + mTotalCapacity = getChild("total_capacity"); + + return true; +} + +// virtual +void LLFloaterObjectWeights::onOpen(const LLSD& key) +{ + refresh(); + updateLandImpacts(LLViewerParcelMgr::getInstance()->getFloatingParcelSelection()->getParcel()); +} + +// virtual +void LLFloaterObjectWeights::onWeightsUpdate(const SelectionCost& selection_cost) +{ + mSelectedDownloadWeight->setText(llformat("%.1f", selection_cost.mNetworkCost)); + mSelectedPhysicsWeight->setText(llformat("%.1f", selection_cost.mPhysicsCost)); + mSelectedServerWeight->setText(llformat("%.1f", selection_cost.mSimulationCost)); + + S32 render_cost = LLSelectMgr::getInstance()->getSelection()->getSelectedObjectRenderCost(); + mSelectedDisplayWeight->setText(llformat("%d", render_cost)); + + toggleWeightsLoadingIndicators(false); +} + +//virtual +void LLFloaterObjectWeights::setErrorStatus(S32 status, const std::string& reason) +{ + const std::string text = getString("nothing_selected"); + + mSelectedDownloadWeight->setText(text); + mSelectedPhysicsWeight->setText(text); + mSelectedServerWeight->setText(text); + mSelectedDisplayWeight->setText(text); + + toggleWeightsLoadingIndicators(false); +} + +void LLFloaterObjectWeights::updateLandImpacts(const LLParcel* parcel) +{ + if (!parcel || LLSelectMgr::getInstance()->getSelection()->isEmpty()) + { + updateIfNothingSelected(); + } + else + { + S32 rezzed_prims = parcel->getSimWidePrimCount(); + S32 total_capacity = parcel->getSimWideMaxPrimCapacity(); + // Can't have more than region max tasks, regardless of parcel + // object bonus factor. + LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); + if (region) + { + S32 max_tasks_per_region = (S32)region->getMaxTasks(); + total_capacity = llmin(total_capacity, max_tasks_per_region); + } + + mRezzedOnLand->setText(llformat("%d", rezzed_prims)); + mRemainingCapacity->setText(llformat("%d", total_capacity - rezzed_prims)); + mTotalCapacity->setText(llformat("%d", total_capacity)); + + toggleLandImpactsLoadingIndicators(false); + } +} + +void LLFloaterObjectWeights::refresh() +{ + LLSelectMgr* sel_mgr = LLSelectMgr::getInstance(); + + if (sel_mgr->getSelection()->isEmpty()) + { + updateIfNothingSelected(); + } + else + { + S32 prim_count = sel_mgr->getSelection()->getObjectCount(); + S32 link_count = sel_mgr->getSelection()->getRootObjectCount(); + F32 prim_equiv = sel_mgr->getSelection()->getSelectedLinksetCost(); + + mSelectedObjects->setText(llformat("%d", link_count)); + mSelectedPrims->setText(llformat("%d", prim_count)); + mSelectedOnLand->setText(llformat("%.1d", (S32)prim_equiv)); + + LLCrossParcelFunctor func; + if (sel_mgr->getSelection()->applyToRootObjects(&func, true)) + { + // Some of the selected objects cross parcel bounds. + // We don't display object weights and land impacts in this case. + const std::string text = getString("nothing_selected"); + + mRezzedOnLand->setText(text); + mRemainingCapacity->setText(text); + mTotalCapacity->setText(text); + + toggleLandImpactsLoadingIndicators(false); + } + + LLViewerRegion* region = gAgent.getRegion(); + if (region && region->capabilitiesReceived()) + { + for (LLObjectSelection::valid_root_iterator iter = sel_mgr->getSelection()->valid_root_begin(); + iter != sel_mgr->getSelection()->valid_root_end(); ++iter) + { + LLAccountingCostManager::getInstance()->addObject((*iter)->getObject()->getID()); + } + + std::string url = region->getCapability("ResourceCostSelected"); + if (!url.empty()) + { + // Update the transaction id before the new fetch request + generateTransactionID(); + + LLAccountingCostManager::getInstance()->fetchCosts(Roots, url, getObserverHandle()); + toggleWeightsLoadingIndicators(true); + } + } + else + { + LL_WARNS() << "Failed to get region capabilities" << LL_ENDL; + } + } +} + +// virtual +void LLFloaterObjectWeights::generateTransactionID() +{ + mTransactionID.generate(); +} + +void LLFloaterObjectWeights::toggleWeightsLoadingIndicators(bool visible) +{ + childSetVisible("download_loading_indicator", visible); + childSetVisible("physics_loading_indicator", visible); + childSetVisible("server_loading_indicator", visible); + childSetVisible("display_loading_indicator", visible); + + mSelectedDownloadWeight->setVisible(!visible); + mSelectedPhysicsWeight->setVisible(!visible); + mSelectedServerWeight->setVisible(!visible); + mSelectedDisplayWeight->setVisible(!visible); +} + +void LLFloaterObjectWeights::toggleLandImpactsLoadingIndicators(bool visible) +{ + childSetVisible("selected_loading_indicator", visible); + childSetVisible("rezzed_on_land_loading_indicator", visible); + childSetVisible("remaining_capacity_loading_indicator", visible); + childSetVisible("total_capacity_loading_indicator", visible); + + mSelectedOnLand->setVisible(!visible); + mRezzedOnLand->setVisible(!visible); + mRemainingCapacity->setVisible(!visible); + mTotalCapacity->setVisible(!visible); +} + +void LLFloaterObjectWeights::updateIfNothingSelected() +{ + const std::string text = getString("nothing_selected"); + + mSelectedObjects->setText(text); + mSelectedPrims->setText(text); + + mSelectedDownloadWeight->setText(text); + mSelectedPhysicsWeight->setText(text); + mSelectedServerWeight->setText(text); + mSelectedDisplayWeight->setText(text); + + mSelectedOnLand->setText(text); + mRezzedOnLand->setText(text); + mRemainingCapacity->setText(text); + mTotalCapacity->setText(text); + + toggleWeightsLoadingIndicators(false); + toggleLandImpactsLoadingIndicators(false); +} diff --git a/indra/newview/llfloaterobjectweights.h b/indra/newview/llfloaterobjectweights.h index 7bc85b71a9..3b999f6b9b 100644 --- a/indra/newview/llfloaterobjectweights.h +++ b/indra/newview/llfloaterobjectweights.h @@ -1,93 +1,93 @@ -/** - * @file llfloaterobjectweights.h - * @brief Object weights advanced view floater - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATEROBJECTWEIGHTS_H -#define LL_LLFLOATEROBJECTWEIGHTS_H - -#include "llfloater.h" - -#include "llaccountingcostmanager.h" -#include "llselectmgr.h" - -class LLParcel; -class LLTextBox; - -/** - * struct LLCrossParcelFunctor - * - * A functor that checks whether a bounding box for all - * selected objects crosses a region or parcel bounds. - */ -struct LLCrossParcelFunctor : public LLSelectedObjectFunctor -{ - /*virtual*/ bool apply(LLViewerObject* obj); - -private: - LLBBox mBoundingBox; -}; - - -class LLFloaterObjectWeights : public LLFloater, LLAccountingCostObserver -{ -public: - LOG_CLASS(LLFloaterObjectWeights); - - LLFloaterObjectWeights(const LLSD& key); - ~LLFloaterObjectWeights(); - - /*virtual*/ bool postBuild(); - - /*virtual*/ void onOpen(const LLSD& key); - - /*virtual*/ void onWeightsUpdate(const SelectionCost& selection_cost); - /*virtual*/ void setErrorStatus(S32 status, const std::string& reason); - - void updateLandImpacts(const LLParcel* parcel); - void refresh(); - -private: - /*virtual*/ void generateTransactionID(); - - void toggleWeightsLoadingIndicators(bool visible); - void toggleLandImpactsLoadingIndicators(bool visible); - - void updateIfNothingSelected(); - - LLTextBox *mSelectedObjects; - LLTextBox *mSelectedPrims; - - LLTextBox *mSelectedDownloadWeight; - LLTextBox *mSelectedPhysicsWeight; - LLTextBox *mSelectedServerWeight; - LLTextBox *mSelectedDisplayWeight; - - LLTextBox *mSelectedOnLand; - LLTextBox *mRezzedOnLand; - LLTextBox *mRemainingCapacity; - LLTextBox *mTotalCapacity; -}; - -#endif //LL_LLFLOATEROBJECTWEIGHTS_H +/** + * @file llfloaterobjectweights.h + * @brief Object weights advanced view floater + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATEROBJECTWEIGHTS_H +#define LL_LLFLOATEROBJECTWEIGHTS_H + +#include "llfloater.h" + +#include "llaccountingcostmanager.h" +#include "llselectmgr.h" + +class LLParcel; +class LLTextBox; + +/** + * struct LLCrossParcelFunctor + * + * A functor that checks whether a bounding box for all + * selected objects crosses a region or parcel bounds. + */ +struct LLCrossParcelFunctor : public LLSelectedObjectFunctor +{ + /*virtual*/ bool apply(LLViewerObject* obj); + +private: + LLBBox mBoundingBox; +}; + + +class LLFloaterObjectWeights : public LLFloater, LLAccountingCostObserver +{ +public: + LOG_CLASS(LLFloaterObjectWeights); + + LLFloaterObjectWeights(const LLSD& key); + ~LLFloaterObjectWeights(); + + /*virtual*/ bool postBuild(); + + /*virtual*/ void onOpen(const LLSD& key); + + /*virtual*/ void onWeightsUpdate(const SelectionCost& selection_cost); + /*virtual*/ void setErrorStatus(S32 status, const std::string& reason); + + void updateLandImpacts(const LLParcel* parcel); + void refresh(); + +private: + /*virtual*/ void generateTransactionID(); + + void toggleWeightsLoadingIndicators(bool visible); + void toggleLandImpactsLoadingIndicators(bool visible); + + void updateIfNothingSelected(); + + LLTextBox *mSelectedObjects; + LLTextBox *mSelectedPrims; + + LLTextBox *mSelectedDownloadWeight; + LLTextBox *mSelectedPhysicsWeight; + LLTextBox *mSelectedServerWeight; + LLTextBox *mSelectedDisplayWeight; + + LLTextBox *mSelectedOnLand; + LLTextBox *mRezzedOnLand; + LLTextBox *mRemainingCapacity; + LLTextBox *mTotalCapacity; +}; + +#endif //LL_LLFLOATEROBJECTWEIGHTS_H diff --git a/indra/newview/llfloateropenobject.cpp b/indra/newview/llfloateropenobject.cpp index 91987f22f3..b06e35f65d 100644 --- a/indra/newview/llfloateropenobject.cpp +++ b/indra/newview/llfloateropenobject.cpp @@ -1,230 +1,230 @@ -/** - * @file llfloateropenobject.cpp - * @brief LLFloaterOpenObject class implementation - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/* - * Shows the contents of an object. - * A floater wrapper for LLPanelObjectInventory - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloateropenobject.h" - -#include "llcachename.h" -#include "llbutton.h" -#include "llnotificationsutil.h" -#include "lltextbox.h" - -#include "llinventorybridge.h" -#include "llinventorymodel.h" -#include "llinventorypanel.h" -#include "llpanelobjectinventory.h" -#include "llfloaterreg.h" -#include "llselectmgr.h" -#include "lluiconstants.h" -#include "llviewerobject.h" -#include "lluictrlfactory.h" -#include "llviewerwindow.h" - - -LLFloaterOpenObject::LLFloaterOpenObject(const LLSD& key) -: LLFloater(key), - mPanelInventoryObject(NULL), - mDirty(true) -{ - mCommitCallbackRegistrar.add("OpenObject.MoveToInventory", boost::bind(&LLFloaterOpenObject::onClickMoveToInventory, this)); - mCommitCallbackRegistrar.add("OpenObject.Cancel", boost::bind(&LLFloaterOpenObject::onClickCancel, this)); -} - -LLFloaterOpenObject::~LLFloaterOpenObject() -{ -// sInstance = NULL; -} - -// virtual -bool LLFloaterOpenObject::postBuild() -{ - getChild("object_name")->setTextArg("[DESC]", std::string("Object") ); // *Note: probably do not want to translate this - mPanelInventoryObject = getChild("object_contents"); - - refresh(); - return true; -} - -void LLFloaterOpenObject::onOpen(const LLSD& key) -{ - LLObjectSelectionHandle object_selection = LLSelectMgr::getInstance()->getSelection(); - if (object_selection->getRootObjectCount() != 1) - { - LLNotificationsUtil::add("UnableToViewContentsMoreThanOne"); - closeFloater(); - return; - } - if(!(object_selection->getPrimaryObject())) - { - closeFloater(); - return; - } - mObjectSelection = LLSelectMgr::getInstance()->getEditSelection(); - refresh(); -} - -void LLFloaterOpenObject::refresh() -{ - mPanelInventoryObject->refresh(); - - std::string name = ""; - bool enabled = false; - - LLSelectNode* node = mObjectSelection->getFirstRootNode(); - if (node) - { - name = node->mName; - enabled = true; - } - else - { - name = ""; - enabled = false; - } - - getChild("object_name")->setTextArg("[DESC]", name); - getChildView("copy_to_inventory_button")->setEnabled(enabled); - getChildView("copy_and_wear_button")->setEnabled(enabled); - getChildView("copy_and_replace_button")->setEnabled(enabled); - -} - -void LLFloaterOpenObject::draw() -{ - if (mDirty) - { - refresh(); - mDirty = false; - } - LLFloater::draw(); -} - -void LLFloaterOpenObject::dirty() -{ - mDirty = true; -} - - - -void LLFloaterOpenObject::moveToInventory(bool wear, bool replace) -{ - if (mObjectSelection->getRootObjectCount() != 1) - { - LLNotificationsUtil::add("OnlyCopyContentsOfSingleItem"); - return; - } - - LLSelectNode* node = mObjectSelection->getFirstRootNode(); - if (!node) return; - LLViewerObject* object = node->getObject(); - if (!object) return; - - LLUUID object_id = object->getID(); - std::string name = node->mName; - - // Either create a sub-folder of clothing, or of the root folder. - LLUUID parent_category_id; - if (wear) - { - parent_category_id = gInventory.findCategoryUUIDForType( - LLFolderType::FT_CLOTHING); - } - else - { - parent_category_id = gInventory.getRootFolderID(); - } - - inventory_func_type func = boost::bind(LLFloaterOpenObject::callbackCreateInventoryCategory,_1,object_id,wear,replace); - // D567 copy thumbnail info - gInventory.createNewCategory( - parent_category_id, - LLFolderType::FT_NONE, - name, - func); -} - -// static -void LLFloaterOpenObject::callbackCreateInventoryCategory(const LLUUID& category_id, LLUUID object_id, bool wear, bool replace) -{ - LLCatAndWear* wear_data = new LLCatAndWear; - - wear_data->mCatID = category_id; - wear_data->mWear = wear; - wear_data->mFolderResponded = true; - wear_data->mReplace = replace; - - // Copy and/or move the items into the newly created folder. - // Ignore any "you're going to break this item" messages. - bool success = move_inv_category_world_to_agent(object_id, - category_id, - true, - [](S32 result, void* data, const LLMoveInv*) - { - callbackMoveInventory(result, data); - }, - (void*)wear_data); - if (!success) - { - delete wear_data; - wear_data = NULL; - - LLNotificationsUtil::add("OpenObjectCannotCopy"); - } -} - -// static -void LLFloaterOpenObject::callbackMoveInventory(S32 result, void* data) -{ - LLCatAndWear* cat = (LLCatAndWear*)data; - - if (result == 0) - { - LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(); - if (active_panel) - { - active_panel->setSelection(cat->mCatID, TAKE_FOCUS_NO); - } - } - - delete cat; -} - -void LLFloaterOpenObject::onClickMoveToInventory() -{ - moveToInventory(false); - closeFloater(); -} - -void LLFloaterOpenObject::onClickCancel() -{ - closeFloater(); -} +/** + * @file llfloateropenobject.cpp + * @brief LLFloaterOpenObject class implementation + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/* + * Shows the contents of an object. + * A floater wrapper for LLPanelObjectInventory + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloateropenobject.h" + +#include "llcachename.h" +#include "llbutton.h" +#include "llnotificationsutil.h" +#include "lltextbox.h" + +#include "llinventorybridge.h" +#include "llinventorymodel.h" +#include "llinventorypanel.h" +#include "llpanelobjectinventory.h" +#include "llfloaterreg.h" +#include "llselectmgr.h" +#include "lluiconstants.h" +#include "llviewerobject.h" +#include "lluictrlfactory.h" +#include "llviewerwindow.h" + + +LLFloaterOpenObject::LLFloaterOpenObject(const LLSD& key) +: LLFloater(key), + mPanelInventoryObject(NULL), + mDirty(true) +{ + mCommitCallbackRegistrar.add("OpenObject.MoveToInventory", boost::bind(&LLFloaterOpenObject::onClickMoveToInventory, this)); + mCommitCallbackRegistrar.add("OpenObject.Cancel", boost::bind(&LLFloaterOpenObject::onClickCancel, this)); +} + +LLFloaterOpenObject::~LLFloaterOpenObject() +{ +// sInstance = NULL; +} + +// virtual +bool LLFloaterOpenObject::postBuild() +{ + getChild("object_name")->setTextArg("[DESC]", std::string("Object") ); // *Note: probably do not want to translate this + mPanelInventoryObject = getChild("object_contents"); + + refresh(); + return true; +} + +void LLFloaterOpenObject::onOpen(const LLSD& key) +{ + LLObjectSelectionHandle object_selection = LLSelectMgr::getInstance()->getSelection(); + if (object_selection->getRootObjectCount() != 1) + { + LLNotificationsUtil::add("UnableToViewContentsMoreThanOne"); + closeFloater(); + return; + } + if(!(object_selection->getPrimaryObject())) + { + closeFloater(); + return; + } + mObjectSelection = LLSelectMgr::getInstance()->getEditSelection(); + refresh(); +} + +void LLFloaterOpenObject::refresh() +{ + mPanelInventoryObject->refresh(); + + std::string name = ""; + bool enabled = false; + + LLSelectNode* node = mObjectSelection->getFirstRootNode(); + if (node) + { + name = node->mName; + enabled = true; + } + else + { + name = ""; + enabled = false; + } + + getChild("object_name")->setTextArg("[DESC]", name); + getChildView("copy_to_inventory_button")->setEnabled(enabled); + getChildView("copy_and_wear_button")->setEnabled(enabled); + getChildView("copy_and_replace_button")->setEnabled(enabled); + +} + +void LLFloaterOpenObject::draw() +{ + if (mDirty) + { + refresh(); + mDirty = false; + } + LLFloater::draw(); +} + +void LLFloaterOpenObject::dirty() +{ + mDirty = true; +} + + + +void LLFloaterOpenObject::moveToInventory(bool wear, bool replace) +{ + if (mObjectSelection->getRootObjectCount() != 1) + { + LLNotificationsUtil::add("OnlyCopyContentsOfSingleItem"); + return; + } + + LLSelectNode* node = mObjectSelection->getFirstRootNode(); + if (!node) return; + LLViewerObject* object = node->getObject(); + if (!object) return; + + LLUUID object_id = object->getID(); + std::string name = node->mName; + + // Either create a sub-folder of clothing, or of the root folder. + LLUUID parent_category_id; + if (wear) + { + parent_category_id = gInventory.findCategoryUUIDForType( + LLFolderType::FT_CLOTHING); + } + else + { + parent_category_id = gInventory.getRootFolderID(); + } + + inventory_func_type func = boost::bind(LLFloaterOpenObject::callbackCreateInventoryCategory,_1,object_id,wear,replace); + // D567 copy thumbnail info + gInventory.createNewCategory( + parent_category_id, + LLFolderType::FT_NONE, + name, + func); +} + +// static +void LLFloaterOpenObject::callbackCreateInventoryCategory(const LLUUID& category_id, LLUUID object_id, bool wear, bool replace) +{ + LLCatAndWear* wear_data = new LLCatAndWear; + + wear_data->mCatID = category_id; + wear_data->mWear = wear; + wear_data->mFolderResponded = true; + wear_data->mReplace = replace; + + // Copy and/or move the items into the newly created folder. + // Ignore any "you're going to break this item" messages. + bool success = move_inv_category_world_to_agent(object_id, + category_id, + true, + [](S32 result, void* data, const LLMoveInv*) + { + callbackMoveInventory(result, data); + }, + (void*)wear_data); + if (!success) + { + delete wear_data; + wear_data = NULL; + + LLNotificationsUtil::add("OpenObjectCannotCopy"); + } +} + +// static +void LLFloaterOpenObject::callbackMoveInventory(S32 result, void* data) +{ + LLCatAndWear* cat = (LLCatAndWear*)data; + + if (result == 0) + { + LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(); + if (active_panel) + { + active_panel->setSelection(cat->mCatID, TAKE_FOCUS_NO); + } + } + + delete cat; +} + +void LLFloaterOpenObject::onClickMoveToInventory() +{ + moveToInventory(false); + closeFloater(); +} + +void LLFloaterOpenObject::onClickCancel() +{ + closeFloater(); +} diff --git a/indra/newview/llfloateropenobject.h b/indra/newview/llfloateropenobject.h index 18b17b3ef0..3670dbc5e6 100644 --- a/indra/newview/llfloateropenobject.h +++ b/indra/newview/llfloateropenobject.h @@ -1,82 +1,82 @@ -/** - * @file llfloateropenobject.h - * @brief LLFloaterOpenObject class definition - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/* - * Shows the contents of an object and their permissions when you - * click "Buy..." on an object with "Sell Contents" checked. - */ - -#ifndef LL_LLFLOATEROPENOBJECT_H -#define LL_LLFLOATEROPENOBJECT_H - -#include "llfloater.h" - -class LLObjectSelection; -class LLPanelObjectInventory; - -class LLFloaterOpenObject -: public LLFloater -{ - friend class LLFloaterReg; -public: - - void dirty(); - - struct LLCatAndWear - { - LLUUID mCatID; - bool mWear; - bool mFolderResponded; - bool mReplace; - }; - -protected: - - /*virtual*/ bool postBuild(); - void refresh(); - void draw(); - virtual void onOpen(const LLSD& key); - - void moveToInventory(bool wear, bool replace = false); - - void onClickMoveToInventory(); - void onClickCancel(); - static void callbackCreateInventoryCategory(const LLUUID& category_id, LLUUID object_id, bool wear, bool replace = false); - static void callbackMoveInventory(S32 result, void* data); - -private: - - LLFloaterOpenObject(const LLSD& key); - ~LLFloaterOpenObject(); - -protected: - - LLPanelObjectInventory* mPanelInventoryObject; - LLSafeHandle mObjectSelection; - bool mDirty; -}; - -#endif +/** + * @file llfloateropenobject.h + * @brief LLFloaterOpenObject class definition + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/* + * Shows the contents of an object and their permissions when you + * click "Buy..." on an object with "Sell Contents" checked. + */ + +#ifndef LL_LLFLOATEROPENOBJECT_H +#define LL_LLFLOATEROPENOBJECT_H + +#include "llfloater.h" + +class LLObjectSelection; +class LLPanelObjectInventory; + +class LLFloaterOpenObject +: public LLFloater +{ + friend class LLFloaterReg; +public: + + void dirty(); + + struct LLCatAndWear + { + LLUUID mCatID; + bool mWear; + bool mFolderResponded; + bool mReplace; + }; + +protected: + + /*virtual*/ bool postBuild(); + void refresh(); + void draw(); + virtual void onOpen(const LLSD& key); + + void moveToInventory(bool wear, bool replace = false); + + void onClickMoveToInventory(); + void onClickCancel(); + static void callbackCreateInventoryCategory(const LLUUID& category_id, LLUUID object_id, bool wear, bool replace = false); + static void callbackMoveInventory(S32 result, void* data); + +private: + + LLFloaterOpenObject(const LLSD& key); + ~LLFloaterOpenObject(); + +protected: + + LLPanelObjectInventory* mPanelInventoryObject; + LLSafeHandle mObjectSelection; + bool mDirty; +}; + +#endif diff --git a/indra/newview/llfloaterpathfindingcharacters.cpp b/indra/newview/llfloaterpathfindingcharacters.cpp index 97b5197194..e0a234e3a2 100644 --- a/indra/newview/llfloaterpathfindingcharacters.cpp +++ b/indra/newview/llfloaterpathfindingcharacters.cpp @@ -1,326 +1,326 @@ -/** -* @file llfloaterpathfindingcharacters.cpp -* @brief "Pathfinding characters" floater, allowing for identification of pathfinding characters and their cpu usage. -* @author Stinson@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterpathfindingcharacters.h" - -#include - -#include "llcheckboxctrl.h" -#include "llfloaterreg.h" -#include "llfloaterpathfindingobjects.h" -#include "llhandle.h" -#include "llpathfindingcharacter.h" -#include "llpathfindingcharacterlist.h" -#include "llpathfindingmanager.h" -#include "llpathfindingobject.h" -#include "llpathfindingobjectlist.h" -#include "llpathinglib.h" -#include "llquaternion.h" -#include "llsd.h" -#include "lluicolortable.h" -#include "lluuid.h" -#include "llviewerobject.h" -#include "llviewerobjectlist.h" -#include "pipeline.h" -#include "v3math.h" -#include "v4color.h" - -LLHandle LLFloaterPathfindingCharacters::sInstanceHandle; - -//--------------------------------------------------------------------------- -// LLFloaterPathfindingCharacters -//--------------------------------------------------------------------------- - -void LLFloaterPathfindingCharacters::onClose(bool pIsAppQuitting) -{ - // Hide any capsule that might be showing on floater close - hideCapsule(); - LLFloaterPathfindingObjects::onClose( pIsAppQuitting ); -} - -bool LLFloaterPathfindingCharacters::isShowPhysicsCapsule() const -{ - return mShowPhysicsCapsuleCheckBox->get(); -} - -void LLFloaterPathfindingCharacters::setShowPhysicsCapsule(bool pIsShowPhysicsCapsule) -{ - mShowPhysicsCapsuleCheckBox->set(pIsShowPhysicsCapsule && (LLPathingLib::getInstance() != NULL)); -} - -bool LLFloaterPathfindingCharacters::isPhysicsCapsuleEnabled(LLUUID& id, LLVector3& pos, LLQuaternion& rot) const -{ - id = mSelectedCharacterId; - // Physics capsule is enable if the checkbox is enabled and if we can get the required render - // parameters for any selected object - return (isShowPhysicsCapsule() && getCapsuleRenderData(pos, rot )); -} - -void LLFloaterPathfindingCharacters::openCharactersWithSelectedObjects() -{ - LLFloaterPathfindingCharacters *charactersFloater = LLFloaterReg::getTypedInstance("pathfinding_characters"); - charactersFloater->showFloaterWithSelectionObjects(); -} - -LLHandle LLFloaterPathfindingCharacters::getInstanceHandle() -{ - if ( sInstanceHandle.isDead() ) - { - LLFloaterPathfindingCharacters *floaterInstance = LLFloaterReg::findTypedInstance("pathfinding_characters"); - if (floaterInstance != NULL) - { - sInstanceHandle = floaterInstance->mSelfHandle; - } - } - - return sInstanceHandle; -} - -LLFloaterPathfindingCharacters::LLFloaterPathfindingCharacters(const LLSD& pSeed) - : LLFloaterPathfindingObjects(pSeed), - mShowPhysicsCapsuleCheckBox(NULL), - mSelectedCharacterId(), - mBeaconColor(), - mSelfHandle() -{ - mSelfHandle.bind(this); -} - -LLFloaterPathfindingCharacters::~LLFloaterPathfindingCharacters() -{ -} - -bool LLFloaterPathfindingCharacters::postBuild() -{ - mBeaconColor = LLUIColorTable::getInstance()->getColor("PathfindingCharacterBeaconColor"); - - mShowPhysicsCapsuleCheckBox = findChild("show_physics_capsule"); - llassert(mShowPhysicsCapsuleCheckBox != NULL); - mShowPhysicsCapsuleCheckBox->setCommitCallback(boost::bind(&LLFloaterPathfindingCharacters::onShowPhysicsCapsuleClicked, this)); - mShowPhysicsCapsuleCheckBox->setEnabled(LLPathingLib::getInstance() != NULL); - - return LLFloaterPathfindingObjects::postBuild(); -} - -void LLFloaterPathfindingCharacters::requestGetObjects() -{ - LLPathfindingManager::getInstance()->requestGetCharacters(getNewRequestId(), boost::bind(&LLFloaterPathfindingCharacters::handleNewObjectList, this, _1, _2, _3)); -} - -void LLFloaterPathfindingCharacters::buildObjectsScrollList(const LLPathfindingObjectListPtr pObjectListPtr) -{ - llassert(pObjectListPtr != NULL); - llassert(!pObjectListPtr->isEmpty()); - - for (LLPathfindingObjectList::const_iterator objectIter = pObjectListPtr->begin(); objectIter != pObjectListPtr->end(); ++objectIter) - { - const LLPathfindingObjectPtr objectPtr = objectIter->second; - const LLPathfindingCharacter *characterPtr = dynamic_cast(objectPtr.get()); - llassert(characterPtr != NULL); - - LLSD scrollListItemData = buildCharacterScrollListItemData(characterPtr); - addObjectToScrollList(objectPtr, scrollListItemData); - } -} - -void LLFloaterPathfindingCharacters::updateControlsOnScrollListChange() -{ - LLFloaterPathfindingObjects::updateControlsOnScrollListChange(); - updateStateOnDisplayControls(); - showSelectedCharacterCapsules(); -} - -S32 LLFloaterPathfindingCharacters::getNameColumnIndex() const -{ - return 0; -} - -S32 LLFloaterPathfindingCharacters::getOwnerNameColumnIndex() const -{ - return 2; -} - -std::string LLFloaterPathfindingCharacters::getOwnerName(const LLPathfindingObject *pObject) const -{ - return (pObject->hasOwner() - ? (pObject->hasOwnerName() - ? (pObject->isGroupOwned() - ? (pObject->getOwnerName() + " " + getString("character_owner_group")) - : pObject->getOwnerName()) - : getString("character_owner_loading")) - : getString("character_owner_unknown")); -} - -const LLColor4 &LLFloaterPathfindingCharacters::getBeaconColor() const -{ - return mBeaconColor; -} - -LLPathfindingObjectListPtr LLFloaterPathfindingCharacters::getEmptyObjectList() const -{ - LLPathfindingObjectListPtr objectListPtr(new LLPathfindingCharacterList()); - return objectListPtr; -} - -void LLFloaterPathfindingCharacters::onShowPhysicsCapsuleClicked() -{ - if (LLPathingLib::getInstance() == NULL) - { - if (isShowPhysicsCapsule()) - { - setShowPhysicsCapsule(false); - } - } - else - { - if (mSelectedCharacterId.notNull() && isShowPhysicsCapsule()) - { - showCapsule(); - } - else - { - hideCapsule(); - } - } -} - -LLSD LLFloaterPathfindingCharacters::buildCharacterScrollListItemData(const LLPathfindingCharacter *pCharacterPtr) const -{ - LLSD columns = LLSD::emptyArray(); - - columns[0]["column"] = "name"; - columns[0]["value"] = pCharacterPtr->getName(); - - columns[1]["column"] = "description"; - columns[1]["value"] = pCharacterPtr->getDescription(); - - columns[2]["column"] = "owner"; - columns[2]["value"] = getOwnerName(pCharacterPtr); - - S32 cpuTime = ll_round(pCharacterPtr->getCPUTime()); - std::string cpuTimeString = llformat("%d", cpuTime); - LLStringUtil::format_map_t string_args; - string_args["[CPU_TIME]"] = cpuTimeString; - - columns[3]["column"] = "cpu_time"; - columns[3]["value"] = getString("character_cpu_time", string_args); - - columns[4]["column"] = "altitude"; - columns[4]["value"] = llformat("%1.0f m", pCharacterPtr->getLocation()[2]); - - return columns; -} - -void LLFloaterPathfindingCharacters::updateStateOnDisplayControls() -{ - int numSelectedItems = getNumSelectedObjects();; - bool isEditEnabled = ((numSelectedItems == 1) && (LLPathingLib::getInstance() != NULL)); - - mShowPhysicsCapsuleCheckBox->setEnabled(isEditEnabled); - if (!isEditEnabled) - { - setShowPhysicsCapsule(false); - } -} - -void LLFloaterPathfindingCharacters::showSelectedCharacterCapsules() -{ - // Hide any previous capsule - hideCapsule(); - - // Get the only selected object, or set the selected object to null if we do not have exactly - // one object selected - if (getNumSelectedObjects() == 1) - { - LLPathfindingObjectPtr selectedObjectPtr = getFirstSelectedObject(); - mSelectedCharacterId = selectedObjectPtr->getUUID(); - } - else - { - mSelectedCharacterId.setNull(); - } - - // Show any capsule if enabled - showCapsule(); -} - -void LLFloaterPathfindingCharacters::showCapsule() const -{ - if (mSelectedCharacterId.notNull() && isShowPhysicsCapsule()) - { - LLPathfindingObjectPtr objectPtr = getFirstSelectedObject(); - llassert(objectPtr != NULL); - if (objectPtr != NULL) - { - const LLPathfindingCharacter *character = dynamic_cast(objectPtr.get()); - llassert(mSelectedCharacterId == character->getUUID()); - if (LLPathingLib::getInstance() != NULL) - { - LLPathingLib::getInstance()->createPhysicsCapsuleRep(character->getLength(), character->getRadius(), - character->isHorizontal(), character->getUUID()); - } - } - - gPipeline.hideObject(mSelectedCharacterId); - } -} - -void LLFloaterPathfindingCharacters::hideCapsule() const -{ - if (mSelectedCharacterId.notNull()) - { - gPipeline.restoreHiddenObject(mSelectedCharacterId); - } - if (LLPathingLib::getInstance() != NULL) - { - LLPathingLib::getInstance()->cleanupPhysicsCapsuleRepResiduals(); - } -} - -bool LLFloaterPathfindingCharacters::getCapsuleRenderData(LLVector3& pPosition, LLQuaternion& rot) const -{ - bool result = false; - - // If we have a selected object, find the object on the viewer object list and return its - // position. Else, return false indicating that we either do not have a selected object - // or we cannot find the selected object on the viewer object list - if (mSelectedCharacterId.notNull()) - { - LLViewerObject *viewerObject = gObjectList.findObject(mSelectedCharacterId); - if ( viewerObject != NULL ) - { - rot = viewerObject->getRotation() ; - pPosition = viewerObject->getRenderPosition(); - result = true; - } - } - - return result; -} +/** +* @file llfloaterpathfindingcharacters.cpp +* @brief "Pathfinding characters" floater, allowing for identification of pathfinding characters and their cpu usage. +* @author Stinson@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterpathfindingcharacters.h" + +#include + +#include "llcheckboxctrl.h" +#include "llfloaterreg.h" +#include "llfloaterpathfindingobjects.h" +#include "llhandle.h" +#include "llpathfindingcharacter.h" +#include "llpathfindingcharacterlist.h" +#include "llpathfindingmanager.h" +#include "llpathfindingobject.h" +#include "llpathfindingobjectlist.h" +#include "llpathinglib.h" +#include "llquaternion.h" +#include "llsd.h" +#include "lluicolortable.h" +#include "lluuid.h" +#include "llviewerobject.h" +#include "llviewerobjectlist.h" +#include "pipeline.h" +#include "v3math.h" +#include "v4color.h" + +LLHandle LLFloaterPathfindingCharacters::sInstanceHandle; + +//--------------------------------------------------------------------------- +// LLFloaterPathfindingCharacters +//--------------------------------------------------------------------------- + +void LLFloaterPathfindingCharacters::onClose(bool pIsAppQuitting) +{ + // Hide any capsule that might be showing on floater close + hideCapsule(); + LLFloaterPathfindingObjects::onClose( pIsAppQuitting ); +} + +bool LLFloaterPathfindingCharacters::isShowPhysicsCapsule() const +{ + return mShowPhysicsCapsuleCheckBox->get(); +} + +void LLFloaterPathfindingCharacters::setShowPhysicsCapsule(bool pIsShowPhysicsCapsule) +{ + mShowPhysicsCapsuleCheckBox->set(pIsShowPhysicsCapsule && (LLPathingLib::getInstance() != NULL)); +} + +bool LLFloaterPathfindingCharacters::isPhysicsCapsuleEnabled(LLUUID& id, LLVector3& pos, LLQuaternion& rot) const +{ + id = mSelectedCharacterId; + // Physics capsule is enable if the checkbox is enabled and if we can get the required render + // parameters for any selected object + return (isShowPhysicsCapsule() && getCapsuleRenderData(pos, rot )); +} + +void LLFloaterPathfindingCharacters::openCharactersWithSelectedObjects() +{ + LLFloaterPathfindingCharacters *charactersFloater = LLFloaterReg::getTypedInstance("pathfinding_characters"); + charactersFloater->showFloaterWithSelectionObjects(); +} + +LLHandle LLFloaterPathfindingCharacters::getInstanceHandle() +{ + if ( sInstanceHandle.isDead() ) + { + LLFloaterPathfindingCharacters *floaterInstance = LLFloaterReg::findTypedInstance("pathfinding_characters"); + if (floaterInstance != NULL) + { + sInstanceHandle = floaterInstance->mSelfHandle; + } + } + + return sInstanceHandle; +} + +LLFloaterPathfindingCharacters::LLFloaterPathfindingCharacters(const LLSD& pSeed) + : LLFloaterPathfindingObjects(pSeed), + mShowPhysicsCapsuleCheckBox(NULL), + mSelectedCharacterId(), + mBeaconColor(), + mSelfHandle() +{ + mSelfHandle.bind(this); +} + +LLFloaterPathfindingCharacters::~LLFloaterPathfindingCharacters() +{ +} + +bool LLFloaterPathfindingCharacters::postBuild() +{ + mBeaconColor = LLUIColorTable::getInstance()->getColor("PathfindingCharacterBeaconColor"); + + mShowPhysicsCapsuleCheckBox = findChild("show_physics_capsule"); + llassert(mShowPhysicsCapsuleCheckBox != NULL); + mShowPhysicsCapsuleCheckBox->setCommitCallback(boost::bind(&LLFloaterPathfindingCharacters::onShowPhysicsCapsuleClicked, this)); + mShowPhysicsCapsuleCheckBox->setEnabled(LLPathingLib::getInstance() != NULL); + + return LLFloaterPathfindingObjects::postBuild(); +} + +void LLFloaterPathfindingCharacters::requestGetObjects() +{ + LLPathfindingManager::getInstance()->requestGetCharacters(getNewRequestId(), boost::bind(&LLFloaterPathfindingCharacters::handleNewObjectList, this, _1, _2, _3)); +} + +void LLFloaterPathfindingCharacters::buildObjectsScrollList(const LLPathfindingObjectListPtr pObjectListPtr) +{ + llassert(pObjectListPtr != NULL); + llassert(!pObjectListPtr->isEmpty()); + + for (LLPathfindingObjectList::const_iterator objectIter = pObjectListPtr->begin(); objectIter != pObjectListPtr->end(); ++objectIter) + { + const LLPathfindingObjectPtr objectPtr = objectIter->second; + const LLPathfindingCharacter *characterPtr = dynamic_cast(objectPtr.get()); + llassert(characterPtr != NULL); + + LLSD scrollListItemData = buildCharacterScrollListItemData(characterPtr); + addObjectToScrollList(objectPtr, scrollListItemData); + } +} + +void LLFloaterPathfindingCharacters::updateControlsOnScrollListChange() +{ + LLFloaterPathfindingObjects::updateControlsOnScrollListChange(); + updateStateOnDisplayControls(); + showSelectedCharacterCapsules(); +} + +S32 LLFloaterPathfindingCharacters::getNameColumnIndex() const +{ + return 0; +} + +S32 LLFloaterPathfindingCharacters::getOwnerNameColumnIndex() const +{ + return 2; +} + +std::string LLFloaterPathfindingCharacters::getOwnerName(const LLPathfindingObject *pObject) const +{ + return (pObject->hasOwner() + ? (pObject->hasOwnerName() + ? (pObject->isGroupOwned() + ? (pObject->getOwnerName() + " " + getString("character_owner_group")) + : pObject->getOwnerName()) + : getString("character_owner_loading")) + : getString("character_owner_unknown")); +} + +const LLColor4 &LLFloaterPathfindingCharacters::getBeaconColor() const +{ + return mBeaconColor; +} + +LLPathfindingObjectListPtr LLFloaterPathfindingCharacters::getEmptyObjectList() const +{ + LLPathfindingObjectListPtr objectListPtr(new LLPathfindingCharacterList()); + return objectListPtr; +} + +void LLFloaterPathfindingCharacters::onShowPhysicsCapsuleClicked() +{ + if (LLPathingLib::getInstance() == NULL) + { + if (isShowPhysicsCapsule()) + { + setShowPhysicsCapsule(false); + } + } + else + { + if (mSelectedCharacterId.notNull() && isShowPhysicsCapsule()) + { + showCapsule(); + } + else + { + hideCapsule(); + } + } +} + +LLSD LLFloaterPathfindingCharacters::buildCharacterScrollListItemData(const LLPathfindingCharacter *pCharacterPtr) const +{ + LLSD columns = LLSD::emptyArray(); + + columns[0]["column"] = "name"; + columns[0]["value"] = pCharacterPtr->getName(); + + columns[1]["column"] = "description"; + columns[1]["value"] = pCharacterPtr->getDescription(); + + columns[2]["column"] = "owner"; + columns[2]["value"] = getOwnerName(pCharacterPtr); + + S32 cpuTime = ll_round(pCharacterPtr->getCPUTime()); + std::string cpuTimeString = llformat("%d", cpuTime); + LLStringUtil::format_map_t string_args; + string_args["[CPU_TIME]"] = cpuTimeString; + + columns[3]["column"] = "cpu_time"; + columns[3]["value"] = getString("character_cpu_time", string_args); + + columns[4]["column"] = "altitude"; + columns[4]["value"] = llformat("%1.0f m", pCharacterPtr->getLocation()[2]); + + return columns; +} + +void LLFloaterPathfindingCharacters::updateStateOnDisplayControls() +{ + int numSelectedItems = getNumSelectedObjects();; + bool isEditEnabled = ((numSelectedItems == 1) && (LLPathingLib::getInstance() != NULL)); + + mShowPhysicsCapsuleCheckBox->setEnabled(isEditEnabled); + if (!isEditEnabled) + { + setShowPhysicsCapsule(false); + } +} + +void LLFloaterPathfindingCharacters::showSelectedCharacterCapsules() +{ + // Hide any previous capsule + hideCapsule(); + + // Get the only selected object, or set the selected object to null if we do not have exactly + // one object selected + if (getNumSelectedObjects() == 1) + { + LLPathfindingObjectPtr selectedObjectPtr = getFirstSelectedObject(); + mSelectedCharacterId = selectedObjectPtr->getUUID(); + } + else + { + mSelectedCharacterId.setNull(); + } + + // Show any capsule if enabled + showCapsule(); +} + +void LLFloaterPathfindingCharacters::showCapsule() const +{ + if (mSelectedCharacterId.notNull() && isShowPhysicsCapsule()) + { + LLPathfindingObjectPtr objectPtr = getFirstSelectedObject(); + llassert(objectPtr != NULL); + if (objectPtr != NULL) + { + const LLPathfindingCharacter *character = dynamic_cast(objectPtr.get()); + llassert(mSelectedCharacterId == character->getUUID()); + if (LLPathingLib::getInstance() != NULL) + { + LLPathingLib::getInstance()->createPhysicsCapsuleRep(character->getLength(), character->getRadius(), + character->isHorizontal(), character->getUUID()); + } + } + + gPipeline.hideObject(mSelectedCharacterId); + } +} + +void LLFloaterPathfindingCharacters::hideCapsule() const +{ + if (mSelectedCharacterId.notNull()) + { + gPipeline.restoreHiddenObject(mSelectedCharacterId); + } + if (LLPathingLib::getInstance() != NULL) + { + LLPathingLib::getInstance()->cleanupPhysicsCapsuleRepResiduals(); + } +} + +bool LLFloaterPathfindingCharacters::getCapsuleRenderData(LLVector3& pPosition, LLQuaternion& rot) const +{ + bool result = false; + + // If we have a selected object, find the object on the viewer object list and return its + // position. Else, return false indicating that we either do not have a selected object + // or we cannot find the selected object on the viewer object list + if (mSelectedCharacterId.notNull()) + { + LLViewerObject *viewerObject = gObjectList.findObject(mSelectedCharacterId); + if ( viewerObject != NULL ) + { + rot = viewerObject->getRotation() ; + pPosition = viewerObject->getRenderPosition(); + result = true; + } + } + + return result; +} diff --git a/indra/newview/llfloaterpathfindingcharacters.h b/indra/newview/llfloaterpathfindingcharacters.h index 2254bdc90c..1215bb72ec 100644 --- a/indra/newview/llfloaterpathfindingcharacters.h +++ b/indra/newview/llfloaterpathfindingcharacters.h @@ -1,99 +1,99 @@ -/** -* @file llfloaterpathfindingcharacters.h -* @brief "Pathfinding characters" floater, allowing for identification of pathfinding characters and their cpu usage. -* @author Stinson@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ -#ifndef LL_LLFLOATERPATHFINDINGCHARACTERS_H -#define LL_LLFLOATERPATHFINDINGCHARACTERS_H - -#include "llfloaterpathfindingobjects.h" -#include "llhandle.h" -#include "llpathfindingobjectlist.h" -#include "lluuid.h" -#include "v4color.h" - -class LLCheckBoxCtrl; -class LLPathfindingCharacter; -class LLQuaternion; -class LLSD; -class LLVector3; - -class LLFloaterPathfindingCharacters : public LLFloaterPathfindingObjects -{ -public: - virtual void onClose(bool pIsAppQuitting); - - bool isShowPhysicsCapsule() const; - void setShowPhysicsCapsule(bool pIsShowPhysicsCapsule); - - bool isPhysicsCapsuleEnabled(LLUUID& id, LLVector3& pos, LLQuaternion& rot) const; - - static void openCharactersWithSelectedObjects(); - static LLHandle getInstanceHandle(); - -protected: - friend class LLFloaterReg; - - LLFloaterPathfindingCharacters(const LLSD& pSeed); - virtual ~LLFloaterPathfindingCharacters(); - - virtual bool postBuild(); - - virtual void requestGetObjects(); - - virtual void buildObjectsScrollList(const LLPathfindingObjectListPtr pObjectListPtr); - - virtual void updateControlsOnScrollListChange(); - - virtual S32 getNameColumnIndex() const; - virtual S32 getOwnerNameColumnIndex() const; - virtual std::string getOwnerName(const LLPathfindingObject *pObject) const; - virtual const LLColor4 &getBeaconColor() const; - - virtual LLPathfindingObjectListPtr getEmptyObjectList() const; - -private: - void onShowPhysicsCapsuleClicked(); - - LLSD buildCharacterScrollListItemData(const LLPathfindingCharacter *pCharacterPtr) const; - - void updateStateOnDisplayControls(); - void showSelectedCharacterCapsules(); - - void showCapsule() const; - void hideCapsule() const; - - bool getCapsuleRenderData(LLVector3& pPosition, LLQuaternion& rot) const; - - LLCheckBoxCtrl *mShowPhysicsCapsuleCheckBox; - - LLUUID mSelectedCharacterId; - - LLColor4 mBeaconColor; - - LLRootHandle mSelfHandle; - static LLHandle sInstanceHandle; -}; - -#endif // LL_LLFLOATERPATHFINDINGCHARACTERS_H +/** +* @file llfloaterpathfindingcharacters.h +* @brief "Pathfinding characters" floater, allowing for identification of pathfinding characters and their cpu usage. +* @author Stinson@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LL_LLFLOATERPATHFINDINGCHARACTERS_H +#define LL_LLFLOATERPATHFINDINGCHARACTERS_H + +#include "llfloaterpathfindingobjects.h" +#include "llhandle.h" +#include "llpathfindingobjectlist.h" +#include "lluuid.h" +#include "v4color.h" + +class LLCheckBoxCtrl; +class LLPathfindingCharacter; +class LLQuaternion; +class LLSD; +class LLVector3; + +class LLFloaterPathfindingCharacters : public LLFloaterPathfindingObjects +{ +public: + virtual void onClose(bool pIsAppQuitting); + + bool isShowPhysicsCapsule() const; + void setShowPhysicsCapsule(bool pIsShowPhysicsCapsule); + + bool isPhysicsCapsuleEnabled(LLUUID& id, LLVector3& pos, LLQuaternion& rot) const; + + static void openCharactersWithSelectedObjects(); + static LLHandle getInstanceHandle(); + +protected: + friend class LLFloaterReg; + + LLFloaterPathfindingCharacters(const LLSD& pSeed); + virtual ~LLFloaterPathfindingCharacters(); + + virtual bool postBuild(); + + virtual void requestGetObjects(); + + virtual void buildObjectsScrollList(const LLPathfindingObjectListPtr pObjectListPtr); + + virtual void updateControlsOnScrollListChange(); + + virtual S32 getNameColumnIndex() const; + virtual S32 getOwnerNameColumnIndex() const; + virtual std::string getOwnerName(const LLPathfindingObject *pObject) const; + virtual const LLColor4 &getBeaconColor() const; + + virtual LLPathfindingObjectListPtr getEmptyObjectList() const; + +private: + void onShowPhysicsCapsuleClicked(); + + LLSD buildCharacterScrollListItemData(const LLPathfindingCharacter *pCharacterPtr) const; + + void updateStateOnDisplayControls(); + void showSelectedCharacterCapsules(); + + void showCapsule() const; + void hideCapsule() const; + + bool getCapsuleRenderData(LLVector3& pPosition, LLQuaternion& rot) const; + + LLCheckBoxCtrl *mShowPhysicsCapsuleCheckBox; + + LLUUID mSelectedCharacterId; + + LLColor4 mBeaconColor; + + LLRootHandle mSelfHandle; + static LLHandle sInstanceHandle; +}; + +#endif // LL_LLFLOATERPATHFINDINGCHARACTERS_H diff --git a/indra/newview/llfloaterpathfindingconsole.cpp b/indra/newview/llfloaterpathfindingconsole.cpp index 0b32b9839f..a097a3ee31 100644 --- a/indra/newview/llfloaterpathfindingconsole.cpp +++ b/indra/newview/llfloaterpathfindingconsole.cpp @@ -1,1273 +1,1273 @@ -/** -* @file llfloaterpathfindingconsole.cpp -* @brief "Pathfinding console" floater, allowing for viewing and testing of the pathfinding navmesh through Havok AI utilities. -* @author Stinson@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterpathfindingconsole.h" - -#include - -#include - -#include "llagent.h" -#include "llbutton.h" -#include "llcheckboxctrl.h" -#include "llcombobox.h" -#include "llcontrol.h" -#include "llfloaterpathfindingcharacters.h" -#include "llfloaterpathfindinglinksets.h" -#include "llfloaterreg.h" -#include "llhandle.h" -#include "llpanel.h" -#include "llpathfindingnavmeshzone.h" -#include "llpathfindingpathtool.h" -#include "llpathinglib.h" -#include "llsliderctrl.h" -#include "llsd.h" -#include "lltabcontainer.h" -#include "lltextbase.h" -#include "lltoolmgr.h" -#include "lltoolfocus.h" -#include "llviewercontrol.h" -#include "llviewerparcelmgr.h" -#include "pipeline.h" - -#define XUI_RENDER_HEATMAP_NONE 0 -#define XUI_RENDER_HEATMAP_A 1 -#define XUI_RENDER_HEATMAP_B 2 -#define XUI_RENDER_HEATMAP_C 3 -#define XUI_RENDER_HEATMAP_D 4 - -#define XUI_CHARACTER_TYPE_NONE 0 -#define XUI_CHARACTER_TYPE_A 1 -#define XUI_CHARACTER_TYPE_B 2 -#define XUI_CHARACTER_TYPE_C 3 -#define XUI_CHARACTER_TYPE_D 4 - -#define XUI_VIEW_TAB_INDEX 0 -#define XUI_TEST_TAB_INDEX 1 - -#define SET_SHAPE_RENDER_FLAG(_flag,_type) _flag |= (1U << _type) - -#define CONTROL_NAME_RETRIEVE_NEIGHBOR "PathfindingRetrieveNeighboringRegion" -#define CONTROL_NAME_WALKABLE_OBJECTS "PathfindingWalkable" -#define CONTROL_NAME_STATIC_OBSTACLE_OBJECTS "PathfindingObstacle" -#define CONTROL_NAME_MATERIAL_VOLUMES "PathfindingMaterial" -#define CONTROL_NAME_EXCLUSION_VOLUMES "PathfindingExclusion" -#define CONTROL_NAME_INTERIOR_EDGE "PathfindingConnectedEdge" -#define CONTROL_NAME_EXTERIOR_EDGE "PathfindingBoundaryEdge" -#define CONTROL_NAME_HEATMAP_MIN "PathfindingHeatColorBase" -#define CONTROL_NAME_HEATMAP_MAX "PathfindingHeatColorMax" -#define CONTROL_NAME_NAVMESH_FACE "PathfindingFaceColor" -#define CONTROL_NAME_TEST_PATH_VALID_END "PathfindingTestPathValidEndColor" -#define CONTROL_NAME_TEST_PATH_INVALID_END "PathfindingTestPathInvalidEndColor" -#define CONTROL_NAME_TEST_PATH "PathfindingTestPathColor" -#define CONTROL_NAME_WATER "PathfindingWaterColor" - -LLHandle LLFloaterPathfindingConsole::sInstanceHandle; - -//--------------------------------------------------------------------------- -// LLFloaterPathfindingConsole -//--------------------------------------------------------------------------- - -bool LLFloaterPathfindingConsole::postBuild() -{ - mViewTestTabContainer = findChild("view_test_tab_container"); - llassert(mViewTestTabContainer != NULL); - mViewTestTabContainer->setCommitCallback(boost::bind(&LLFloaterPathfindingConsole::onTabSwitch, this)); - - mViewTab = findChild("view_panel"); - llassert(mViewTab != NULL); - - mShowLabel = findChild("show_label"); - llassert(mShowLabel != NULL); - - mShowWorldCheckBox = findChild("show_world"); - llassert(mShowWorldCheckBox != NULL); - mShowWorldCheckBox->setCommitCallback(boost::bind(&LLFloaterPathfindingConsole::onShowWorldSet, this)); - - mShowWorldMovablesOnlyCheckBox = findChild("show_world_movables_only"); - llassert(mShowWorldMovablesOnlyCheckBox != NULL); - mShowWorldMovablesOnlyCheckBox->setCommitCallback(boost::bind(&LLFloaterPathfindingConsole::onShowWorldMovablesOnlySet, this)); - - mShowNavMeshCheckBox = findChild("show_navmesh"); - llassert(mShowNavMeshCheckBox != NULL); - mShowNavMeshCheckBox->setCommitCallback(boost::bind(&LLFloaterPathfindingConsole::onShowNavMeshSet, this)); - - mShowNavMeshWalkabilityLabel = findChild("show_walkability_label"); - llassert(mShowNavMeshWalkabilityLabel != NULL); - - mShowNavMeshWalkabilityComboBox = findChild("show_heatmap_mode"); - llassert(mShowNavMeshWalkabilityComboBox != NULL); - mShowNavMeshWalkabilityComboBox->setCommitCallback(boost::bind(&LLFloaterPathfindingConsole::onShowWalkabilitySet, this)); - - mShowWalkablesCheckBox = findChild("show_walkables"); - llassert(mShowWalkablesCheckBox != NULL); - - mShowStaticObstaclesCheckBox = findChild("show_static_obstacles"); - llassert(mShowStaticObstaclesCheckBox != NULL); - - mShowMaterialVolumesCheckBox = findChild("show_material_volumes"); - llassert(mShowMaterialVolumesCheckBox != NULL); - - mShowExclusionVolumesCheckBox = findChild("show_exclusion_volumes"); - llassert(mShowExclusionVolumesCheckBox != NULL); - - mShowRenderWaterPlaneCheckBox = findChild("show_water_plane"); - llassert(mShowRenderWaterPlaneCheckBox != NULL); - - mShowXRayCheckBox = findChild("show_xray"); - llassert(mShowXRayCheckBox != NULL); - - mTestTab = findChild("test_panel"); - llassert(mTestTab != NULL); - - mPathfindingViewerStatus = findChild("pathfinding_viewer_status"); - llassert(mPathfindingViewerStatus != NULL); - - mPathfindingSimulatorStatus = findChild("pathfinding_simulator_status"); - llassert(mPathfindingSimulatorStatus != NULL); - - mCtrlClickLabel = findChild("ctrl_click_label"); - llassert(mCtrlClickLabel != NULL); - - mShiftClickLabel = findChild("shift_click_label"); - llassert(mShiftClickLabel != NULL); - - mCharacterWidthLabel = findChild("character_width_label"); - llassert(mCharacterWidthLabel != NULL); - - mCharacterWidthSlider = findChild("character_width"); - llassert(mCharacterWidthSlider != NULL); - mCharacterWidthSlider->setCommitCallback(boost::bind(&LLFloaterPathfindingConsole::onCharacterWidthSet, this)); - - mCharacterWidthUnitLabel = findChild("character_width_unit_label"); - llassert(mCharacterWidthUnitLabel != NULL); - - mCharacterTypeLabel = findChild("character_type_label"); - llassert(mCharacterTypeLabel != NULL); - - mCharacterTypeComboBox = findChild("path_character_type"); - llassert(mCharacterTypeComboBox != NULL); - mCharacterTypeComboBox->setCommitCallback(boost::bind(&LLFloaterPathfindingConsole::onCharacterTypeSwitch, this)); - - mPathTestingStatus = findChild("path_test_status"); - llassert(mPathTestingStatus != NULL); - - mClearPathButton = findChild("clear_path"); - llassert(mClearPathButton != NULL); - mClearPathButton->setCommitCallback(boost::bind(&LLFloaterPathfindingConsole::onClearPathClicked, this)); - - mErrorColor = LLUIColorTable::instance().getColor("PathfindingErrorColor"); - mWarningColor = LLUIColorTable::instance().getColor("PathfindingWarningColor"); - - if (LLPathingLib::getInstance() != NULL) - { - mPathfindingToolset = new LLToolset(); - mPathfindingToolset->addTool(LLPathfindingPathTool::getInstance()); - mPathfindingToolset->addTool(LLToolCamera::getInstance()); - mPathfindingToolset->setShowFloaterTools(false); - } - - updateCharacterWidth(); - updateCharacterType(); - - return LLFloater::postBuild(); -} - -void LLFloaterPathfindingConsole::onOpen(const LLSD& pKey) -{ - LLFloater::onOpen(pKey); - //make sure we have a pathing system - if ( LLPathingLib::getInstance() == NULL ) - { - setConsoleState(kConsoleStateLibraryNotImplemented); - LL_WARNS() <<"Errror: cannot find pathing library implementation."<setTeleportFailedCallback(boost::bind(&LLFloaterPathfindingConsole::onRegionBoundaryCross, this)); - } - - if (!mPathEventSlot.connected()) - { - mPathEventSlot = LLPathfindingPathTool::getInstance()->registerPathEventListener(boost::bind(&LLFloaterPathfindingConsole::onPathEvent, this)); - } - - setDefaultInputs(); - updatePathTestStatus(); - - if (mViewTestTabContainer->getCurrentPanelIndex() == XUI_TEST_TAB_INDEX) - { - switchIntoTestPathMode(); - } -} - -void LLFloaterPathfindingConsole::onClose(bool pIsAppQuitting) -{ - switchOutOfTestPathMode(); - - if (mPathEventSlot.connected()) - { - mPathEventSlot.disconnect(); - } - - if (mTeleportFailedSlot.connected()) - { - mTeleportFailedSlot.disconnect(); - } - - if (mRegionBoundarySlot.connected()) - { - mRegionBoundarySlot.disconnect(); - } - - if (mNavMeshZoneSlot.connected()) - { - mNavMeshZoneSlot.disconnect(); - } - - if (LLPathingLib::getInstance() != NULL) - { - mNavMeshZone.disable(); - } - deregisterSavedSettingsListeners(); - - setDefaultInputs(); - setConsoleState(kConsoleStateUnknown); - cleanupRenderableRestoreItems(); - - LLFloater::onClose(pIsAppQuitting); -} - -LLHandle LLFloaterPathfindingConsole::getInstanceHandle() -{ - if (sInstanceHandle.isDead()) - { - LLFloaterPathfindingConsole *floaterInstance = LLFloaterReg::findTypedInstance("pathfinding_console"); - if (floaterInstance != NULL) - { - sInstanceHandle = floaterInstance->mSelfHandle; - } - } - - return sInstanceHandle; -} - -bool LLFloaterPathfindingConsole::isRenderNavMesh() const -{ - return mShowNavMeshCheckBox->get(); -} - -void LLFloaterPathfindingConsole::setRenderNavMesh(bool pIsRenderNavMesh) -{ - mShowNavMeshCheckBox->set(pIsRenderNavMesh); - setNavMeshRenderState(); -} - -bool LLFloaterPathfindingConsole::isRenderWalkables() const -{ - return mShowWalkablesCheckBox->get(); -} - -void LLFloaterPathfindingConsole::setRenderWalkables(bool pIsRenderWalkables) -{ - mShowWalkablesCheckBox->set(pIsRenderWalkables); -} - -bool LLFloaterPathfindingConsole::isRenderStaticObstacles() const -{ - return mShowStaticObstaclesCheckBox->get(); -} - -void LLFloaterPathfindingConsole::setRenderStaticObstacles(bool pIsRenderStaticObstacles) -{ - mShowStaticObstaclesCheckBox->set(pIsRenderStaticObstacles); -} - -bool LLFloaterPathfindingConsole::isRenderMaterialVolumes() const -{ - return mShowMaterialVolumesCheckBox->get(); -} - -void LLFloaterPathfindingConsole::setRenderMaterialVolumes(bool pIsRenderMaterialVolumes) -{ - mShowMaterialVolumesCheckBox->set(pIsRenderMaterialVolumes); -} - -bool LLFloaterPathfindingConsole::isRenderExclusionVolumes() const -{ - return mShowExclusionVolumesCheckBox->get(); -} - -void LLFloaterPathfindingConsole::setRenderExclusionVolumes(bool pIsRenderExclusionVolumes) -{ - mShowExclusionVolumesCheckBox->set(pIsRenderExclusionVolumes); -} - -bool LLFloaterPathfindingConsole::isRenderWorld() const -{ - return mShowWorldCheckBox->get(); -} - -void LLFloaterPathfindingConsole::setRenderWorld(bool pIsRenderWorld) -{ - mShowWorldCheckBox->set(pIsRenderWorld); - setWorldRenderState(); -} - -bool LLFloaterPathfindingConsole::isRenderWorldMovablesOnly() const -{ - return (mShowWorldCheckBox->get() && mShowWorldMovablesOnlyCheckBox->get()); -} - -void LLFloaterPathfindingConsole::setRenderWorldMovablesOnly(bool pIsRenderWorldMovablesOnly) -{ - mShowWorldMovablesOnlyCheckBox->set(pIsRenderWorldMovablesOnly); -} - -bool LLFloaterPathfindingConsole::isRenderWaterPlane() const -{ - return mShowRenderWaterPlaneCheckBox->get(); -} - -void LLFloaterPathfindingConsole::setRenderWaterPlane(bool pIsRenderWaterPlane) -{ - mShowRenderWaterPlaneCheckBox->set(pIsRenderWaterPlane); -} - -bool LLFloaterPathfindingConsole::isRenderXRay() const -{ - return mShowXRayCheckBox->get(); -} - -void LLFloaterPathfindingConsole::setRenderXRay(bool pIsRenderXRay) -{ - mShowXRayCheckBox->set(pIsRenderXRay); -} - -LLPathingLib::LLPLCharacterType LLFloaterPathfindingConsole::getRenderHeatmapType() const -{ - LLPathingLib::LLPLCharacterType renderHeatmapType; - - switch (mShowNavMeshWalkabilityComboBox->getValue().asInteger()) - { - case XUI_RENDER_HEATMAP_NONE : - renderHeatmapType = LLPathingLib::LLPL_CHARACTER_TYPE_NONE; - break; - case XUI_RENDER_HEATMAP_A : - renderHeatmapType = LLPathingLib::LLPL_CHARACTER_TYPE_A; - break; - case XUI_RENDER_HEATMAP_B : - renderHeatmapType = LLPathingLib::LLPL_CHARACTER_TYPE_B; - break; - case XUI_RENDER_HEATMAP_C : - renderHeatmapType = LLPathingLib::LLPL_CHARACTER_TYPE_C; - break; - case XUI_RENDER_HEATMAP_D : - renderHeatmapType = LLPathingLib::LLPL_CHARACTER_TYPE_D; - break; - default : - renderHeatmapType = LLPathingLib::LLPL_CHARACTER_TYPE_NONE; - llassert(0); - break; - } - - return renderHeatmapType; -} - -void LLFloaterPathfindingConsole::setRenderHeatmapType(LLPathingLib::LLPLCharacterType pRenderHeatmapType) -{ - LLSD comboBoxValue; - - switch (pRenderHeatmapType) - { - case LLPathingLib::LLPL_CHARACTER_TYPE_NONE : - comboBoxValue = XUI_RENDER_HEATMAP_NONE; - break; - case LLPathingLib::LLPL_CHARACTER_TYPE_A : - comboBoxValue = XUI_RENDER_HEATMAP_A; - break; - case LLPathingLib::LLPL_CHARACTER_TYPE_B : - comboBoxValue = XUI_RENDER_HEATMAP_B; - break; - case LLPathingLib::LLPL_CHARACTER_TYPE_C : - comboBoxValue = XUI_RENDER_HEATMAP_C; - break; - case LLPathingLib::LLPL_CHARACTER_TYPE_D : - comboBoxValue = XUI_RENDER_HEATMAP_D; - break; - default : - comboBoxValue = XUI_RENDER_HEATMAP_NONE; - llassert(0); - break; - } - - mShowNavMeshWalkabilityComboBox->setValue(comboBoxValue); -} - -LLFloaterPathfindingConsole::LLFloaterPathfindingConsole(const LLSD& pSeed) - : LLFloater(pSeed), - mSelfHandle(), - mViewTestTabContainer(NULL), - mViewTab(NULL), - mShowLabel(NULL), - mShowWorldCheckBox(NULL), - mShowWorldMovablesOnlyCheckBox(NULL), - mShowNavMeshCheckBox(NULL), - mShowNavMeshWalkabilityLabel(NULL), - mShowNavMeshWalkabilityComboBox(NULL), - mShowWalkablesCheckBox(NULL), - mShowStaticObstaclesCheckBox(NULL), - mShowMaterialVolumesCheckBox(NULL), - mShowExclusionVolumesCheckBox(NULL), - mShowRenderWaterPlaneCheckBox(NULL), - mShowXRayCheckBox(NULL), - mPathfindingViewerStatus(NULL), - mPathfindingSimulatorStatus(NULL), - mTestTab(NULL), - mCtrlClickLabel(), - mShiftClickLabel(), - mCharacterWidthLabel(), - mCharacterWidthUnitLabel(), - mCharacterWidthSlider(NULL), - mCharacterTypeLabel(), - mCharacterTypeComboBox(NULL), - mPathTestingStatus(NULL), - mClearPathButton(NULL), - mErrorColor(), - mWarningColor(), - mNavMeshZoneSlot(), - mNavMeshZone(), - mIsNavMeshUpdating(false), - mRegionBoundarySlot(), - mTeleportFailedSlot(), - mPathEventSlot(), - mPathfindingToolset(NULL), - mSavedToolset(NULL), - mSavedSettingRetrieveNeighborSlot(), - mSavedSettingWalkableSlot(), - mSavedSettingStaticObstacleSlot(), - mSavedSettingMaterialVolumeSlot(), - mSavedSettingExclusionVolumeSlot(), - mSavedSettingInteriorEdgeSlot(), - mSavedSettingExteriorEdgeSlot(), - mSavedSettingHeatmapMinSlot(), - mSavedSettingHeatmapMaxSlot(), - mSavedSettingNavMeshFaceSlot(), - mSavedSettingTestPathValidEndSlot(), - mSavedSettingTestPathInvalidEndSlot(), - mSavedSettingTestPathSlot(), - mSavedSettingWaterSlot(), - mConsoleState(kConsoleStateUnknown), - mRenderableRestoreList() -{ - mSelfHandle.bind(this); -} - -LLFloaterPathfindingConsole::~LLFloaterPathfindingConsole() -{ -} - -void LLFloaterPathfindingConsole::onTabSwitch() -{ - if (mViewTestTabContainer->getCurrentPanelIndex() == XUI_TEST_TAB_INDEX) - { - switchIntoTestPathMode(); - } - else - { - switchOutOfTestPathMode(); - } -} - -void LLFloaterPathfindingConsole::onShowWorldSet() -{ - setWorldRenderState(); - updateRenderablesObjects(); -} - -void LLFloaterPathfindingConsole::onShowWorldMovablesOnlySet() -{ - updateRenderablesObjects(); -} - -void LLFloaterPathfindingConsole::onShowNavMeshSet() -{ - setNavMeshRenderState(); -} - -void LLFloaterPathfindingConsole::onShowWalkabilitySet() -{ - if (LLPathingLib::getInstance() != NULL) - { - LLPathingLib::getInstance()->setNavMeshMaterialType(getRenderHeatmapType()); - } -} - -void LLFloaterPathfindingConsole::onCharacterWidthSet() -{ - updateCharacterWidth(); -} - -void LLFloaterPathfindingConsole::onCharacterTypeSwitch() -{ - updateCharacterType(); -} - -void LLFloaterPathfindingConsole::onClearPathClicked() -{ - clearPath(); -} - -void LLFloaterPathfindingConsole::handleNavMeshZoneStatus(LLPathfindingNavMeshZone::ENavMeshZoneRequestStatus pNavMeshZoneRequestStatus) -{ - switch (pNavMeshZoneRequestStatus) - { - case LLPathfindingNavMeshZone::kNavMeshZoneRequestUnknown : - setConsoleState(kConsoleStateUnknown); - break; - case LLPathfindingNavMeshZone::kNavMeshZoneRequestWaiting : - setConsoleState(kConsoleStateRegionLoading); - break; - case LLPathfindingNavMeshZone::kNavMeshZoneRequestChecking : - setConsoleState(kConsoleStateCheckingVersion); - break; - case LLPathfindingNavMeshZone::kNavMeshZoneRequestNeedsUpdate : - mIsNavMeshUpdating = true; - mNavMeshZone.refresh(); - break; - case LLPathfindingNavMeshZone::kNavMeshZoneRequestStarted : - setConsoleState(kConsoleStateDownloading); - break; - case LLPathfindingNavMeshZone::kNavMeshZoneRequestCompleted : - mIsNavMeshUpdating = false; - setConsoleState(kConsoleStateHasNavMesh); - break; - case LLPathfindingNavMeshZone::kNavMeshZoneRequestNotEnabled : - setConsoleState(kConsoleStateRegionNotEnabled); - break; - case LLPathfindingNavMeshZone::kNavMeshZoneRequestError : - setConsoleState(kConsoleStateError); - break; - default: - setConsoleState(kConsoleStateUnknown); - llassert(0); - break; - } -} - -void LLFloaterPathfindingConsole::onRegionBoundaryCross() -{ - initializeNavMeshZoneForCurrentRegion(); - setRenderWorld(true); - setRenderWorldMovablesOnly(false); -} - -void LLFloaterPathfindingConsole::onPathEvent() -{ - const LLPathfindingPathTool *pathToolInstance = LLPathfindingPathTool::getInstance(); - - mCharacterWidthSlider->setValue(LLSD(pathToolInstance->getCharacterWidth())); - - LLSD characterType; - switch (pathToolInstance->getCharacterType()) - { - case LLPathfindingPathTool::kCharacterTypeNone : - characterType = XUI_CHARACTER_TYPE_NONE; - break; - case LLPathfindingPathTool::kCharacterTypeA : - characterType = XUI_CHARACTER_TYPE_A; - break; - case LLPathfindingPathTool::kCharacterTypeB : - characterType = XUI_CHARACTER_TYPE_B; - break; - case LLPathfindingPathTool::kCharacterTypeC : - characterType = XUI_CHARACTER_TYPE_C; - break; - case LLPathfindingPathTool::kCharacterTypeD : - characterType = XUI_CHARACTER_TYPE_D; - break; - default : - characterType = XUI_CHARACTER_TYPE_NONE; - llassert(0); - break; - } - mCharacterTypeComboBox->setValue(characterType); - - updatePathTestStatus(); -} - -void LLFloaterPathfindingConsole::setDefaultInputs() -{ - mViewTestTabContainer->selectTab(XUI_VIEW_TAB_INDEX); - setRenderWorld(true); - setRenderWorldMovablesOnly(false); - setRenderNavMesh(false); - setRenderWalkables(false); - setRenderMaterialVolumes(false); - setRenderStaticObstacles(false); - setRenderExclusionVolumes(false); - setRenderWaterPlane(false); - setRenderXRay(false); -} - -void LLFloaterPathfindingConsole::setConsoleState(EConsoleState pConsoleState) -{ - mConsoleState = pConsoleState; - updateControlsOnConsoleState(); - updateViewerStatusOnConsoleState(); - updateSimulatorStatusOnConsoleState(); -} - -void LLFloaterPathfindingConsole::setWorldRenderState() -{ - bool renderWorld = isRenderWorld(); - - mShowWorldMovablesOnlyCheckBox->setEnabled(renderWorld && mShowWorldCheckBox->getEnabled()); - if (!renderWorld) - { - mShowWorldMovablesOnlyCheckBox->set(false); - } -} - -void LLFloaterPathfindingConsole::setNavMeshRenderState() -{ - bool renderNavMesh = isRenderNavMesh(); - - mShowNavMeshWalkabilityLabel->setEnabled(renderNavMesh); - mShowNavMeshWalkabilityComboBox->setEnabled(renderNavMesh); -} - -void LLFloaterPathfindingConsole::updateRenderablesObjects() -{ - if ( isRenderWorldMovablesOnly() ) - { - gPipeline.hidePermanentObjects( mRenderableRestoreList ); - } - else - { - cleanupRenderableRestoreItems(); - } -} - -void LLFloaterPathfindingConsole::updateControlsOnConsoleState() -{ - switch (mConsoleState) - { - case kConsoleStateUnknown : - case kConsoleStateRegionNotEnabled : - case kConsoleStateRegionLoading : - mViewTestTabContainer->selectTab(XUI_VIEW_TAB_INDEX); - mViewTab->setEnabled(false); - mShowLabel->setEnabled(false); - mShowWorldCheckBox->setEnabled(false); - mShowWorldMovablesOnlyCheckBox->setEnabled(false); - mShowNavMeshCheckBox->setEnabled(false); - mShowNavMeshWalkabilityLabel->setEnabled(false); - mShowNavMeshWalkabilityComboBox->setEnabled(false); - mShowWalkablesCheckBox->setEnabled(false); - mShowStaticObstaclesCheckBox->setEnabled(false); - mShowMaterialVolumesCheckBox->setEnabled(false); - mShowExclusionVolumesCheckBox->setEnabled(false); - mShowRenderWaterPlaneCheckBox->setEnabled(false); - mShowXRayCheckBox->setEnabled(false); - mTestTab->setEnabled(false); - mCtrlClickLabel->setEnabled(false); - mShiftClickLabel->setEnabled(false); - mCharacterWidthLabel->setEnabled(false); - mCharacterWidthUnitLabel->setEnabled(false); - mCharacterWidthSlider->setEnabled(false); - mCharacterTypeLabel->setEnabled(false); - mCharacterTypeComboBox->setEnabled(false); - mClearPathButton->setEnabled(false); - clearPath(); - break; - case kConsoleStateLibraryNotImplemented : - mViewTestTabContainer->selectTab(XUI_VIEW_TAB_INDEX); - mViewTab->setEnabled(false); - mShowLabel->setEnabled(false); - mShowWorldCheckBox->setEnabled(false); - mShowWorldMovablesOnlyCheckBox->setEnabled(false); - mShowNavMeshCheckBox->setEnabled(false); - mShowNavMeshWalkabilityLabel->setEnabled(false); - mShowNavMeshWalkabilityComboBox->setEnabled(false); - mShowWalkablesCheckBox->setEnabled(false); - mShowStaticObstaclesCheckBox->setEnabled(false); - mShowMaterialVolumesCheckBox->setEnabled(false); - mShowExclusionVolumesCheckBox->setEnabled(false); - mShowRenderWaterPlaneCheckBox->setEnabled(false); - mShowXRayCheckBox->setEnabled(false); - mTestTab->setEnabled(false); - mCtrlClickLabel->setEnabled(false); - mShiftClickLabel->setEnabled(false); - mCharacterWidthLabel->setEnabled(false); - mCharacterWidthUnitLabel->setEnabled(false); - mCharacterWidthSlider->setEnabled(false); - mCharacterTypeLabel->setEnabled(false); - mCharacterTypeComboBox->setEnabled(false); - mClearPathButton->setEnabled(false); - clearPath(); - break; - case kConsoleStateCheckingVersion : - case kConsoleStateDownloading : - case kConsoleStateError : - mViewTestTabContainer->selectTab(XUI_VIEW_TAB_INDEX); - mViewTab->setEnabled(false); - mShowLabel->setEnabled(false); - mShowWorldCheckBox->setEnabled(false); - mShowWorldMovablesOnlyCheckBox->setEnabled(false); - mShowNavMeshCheckBox->setEnabled(false); - mShowNavMeshWalkabilityLabel->setEnabled(false); - mShowNavMeshWalkabilityComboBox->setEnabled(false); - mShowWalkablesCheckBox->setEnabled(false); - mShowStaticObstaclesCheckBox->setEnabled(false); - mShowMaterialVolumesCheckBox->setEnabled(false); - mShowExclusionVolumesCheckBox->setEnabled(false); - mShowRenderWaterPlaneCheckBox->setEnabled(false); - mShowXRayCheckBox->setEnabled(false); - mTestTab->setEnabled(false); - mCtrlClickLabel->setEnabled(false); - mShiftClickLabel->setEnabled(false); - mCharacterWidthLabel->setEnabled(false); - mCharacterWidthUnitLabel->setEnabled(false); - mCharacterWidthSlider->setEnabled(false); - mCharacterTypeLabel->setEnabled(false); - mCharacterTypeComboBox->setEnabled(false); - mClearPathButton->setEnabled(false); - clearPath(); - break; - case kConsoleStateHasNavMesh : - mViewTab->setEnabled(true); - mShowLabel->setEnabled(true); - mShowWorldCheckBox->setEnabled(true); - setWorldRenderState(); - mShowNavMeshCheckBox->setEnabled(true); - setNavMeshRenderState(); - mShowWalkablesCheckBox->setEnabled(true); - mShowStaticObstaclesCheckBox->setEnabled(true); - mShowMaterialVolumesCheckBox->setEnabled(true); - mShowExclusionVolumesCheckBox->setEnabled(true); - mShowRenderWaterPlaneCheckBox->setEnabled(true); - mShowXRayCheckBox->setEnabled(true); - mTestTab->setEnabled(true); - mCtrlClickLabel->setEnabled(true); - mShiftClickLabel->setEnabled(true); - mCharacterWidthLabel->setEnabled(true); - mCharacterWidthUnitLabel->setEnabled(true); - mCharacterWidthSlider->setEnabled(true); - mCharacterTypeLabel->setEnabled(true); - mCharacterTypeComboBox->setEnabled(true); - mClearPathButton->setEnabled(true); - break; - default : - llassert(0); - break; - } -} - -void LLFloaterPathfindingConsole::updateViewerStatusOnConsoleState() -{ - std::string viewerStatusText(""); - LLStyle::Params viewerStyleParams; - - switch (mConsoleState) - { - case kConsoleStateUnknown : - viewerStatusText = getString("navmesh_viewer_status_unknown"); - viewerStyleParams.color = mErrorColor; - break; - case kConsoleStateLibraryNotImplemented : - viewerStatusText = getString("navmesh_viewer_status_library_not_implemented"); - viewerStyleParams.color = mErrorColor; - break; - case kConsoleStateRegionNotEnabled : - viewerStatusText = getString("navmesh_viewer_status_region_not_enabled"); - viewerStyleParams.color = mErrorColor; - break; - case kConsoleStateRegionLoading : - viewerStatusText = getString("navmesh_viewer_status_region_loading"); - viewerStyleParams.color = mWarningColor; - break; - case kConsoleStateCheckingVersion : - viewerStatusText = getString("navmesh_viewer_status_checking_version"); - viewerStyleParams.color = mWarningColor; - break; - case kConsoleStateDownloading : - if (mIsNavMeshUpdating) - { - viewerStatusText = getString("navmesh_viewer_status_updating"); - } - else - { - viewerStatusText = getString("navmesh_viewer_status_downloading"); - } - viewerStyleParams.color = mWarningColor; - break; - case kConsoleStateHasNavMesh : - viewerStatusText = getString("navmesh_viewer_status_has_navmesh"); - break; - case kConsoleStateError : - viewerStatusText = getString("navmesh_viewer_status_error"); - viewerStyleParams.color = mErrorColor; - break; - default : - viewerStatusText = getString("navmesh_viewer_status_unknown"); - viewerStyleParams.color = mErrorColor; - llassert(0); - break; - } - - mPathfindingViewerStatus->setText((LLStringExplicit)viewerStatusText, viewerStyleParams); -} - -void LLFloaterPathfindingConsole::updateSimulatorStatusOnConsoleState() -{ - std::string simulatorStatusText(""); - LLStyle::Params simulatorStyleParams; - - switch (mConsoleState) - { - case kConsoleStateUnknown : - case kConsoleStateLibraryNotImplemented : - case kConsoleStateRegionNotEnabled : - case kConsoleStateRegionLoading : - case kConsoleStateCheckingVersion : - case kConsoleStateError : - simulatorStatusText = getString("navmesh_simulator_status_unknown"); - simulatorStyleParams.color = mErrorColor; - break; - case kConsoleStateDownloading : - case kConsoleStateHasNavMesh : - switch (mNavMeshZone.getNavMeshZoneStatus()) - { - case LLPathfindingNavMeshZone::kNavMeshZonePending : - simulatorStatusText = getString("navmesh_simulator_status_pending"); - simulatorStyleParams.color = mWarningColor; - break; - case LLPathfindingNavMeshZone::kNavMeshZoneBuilding : - simulatorStatusText = getString("navmesh_simulator_status_building"); - simulatorStyleParams.color = mWarningColor; - break; - case LLPathfindingNavMeshZone::kNavMeshZoneSomePending : - simulatorStatusText = getString("navmesh_simulator_status_some_pending"); - simulatorStyleParams.color = mWarningColor; - break; - case LLPathfindingNavMeshZone::kNavMeshZoneSomeBuilding : - simulatorStatusText = getString("navmesh_simulator_status_some_building"); - simulatorStyleParams.color = mWarningColor; - break; - case LLPathfindingNavMeshZone::kNavMeshZonePendingAndBuilding : - simulatorStatusText = getString("navmesh_simulator_status_pending_and_building"); - simulatorStyleParams.color = mWarningColor; - break; - case LLPathfindingNavMeshZone::kNavMeshZoneComplete : - simulatorStatusText = getString("navmesh_simulator_status_complete"); - break; - default : - simulatorStatusText = getString("navmesh_simulator_status_unknown"); - simulatorStyleParams.color = mErrorColor; - break; - } - break; - default : - simulatorStatusText = getString("navmesh_simulator_status_unknown"); - simulatorStyleParams.color = mErrorColor; - llassert(0); - break; - } - - mPathfindingSimulatorStatus->setText((LLStringExplicit)simulatorStatusText, simulatorStyleParams); -} - -void LLFloaterPathfindingConsole::initializeNavMeshZoneForCurrentRegion() -{ - mNavMeshZone.disable(); - mNavMeshZone.initialize(); - mNavMeshZone.enable(); - mNavMeshZone.refresh(); - cleanupRenderableRestoreItems(); -} - -void LLFloaterPathfindingConsole::cleanupRenderableRestoreItems() -{ - if ( !mRenderableRestoreList.empty() ) - { - gPipeline.restorePermanentObjects( mRenderableRestoreList ); - mRenderableRestoreList.clear(); - } - else - { - gPipeline.skipRenderingOfTerrain( false ); - } -} - -void LLFloaterPathfindingConsole::switchIntoTestPathMode() -{ - if (LLPathingLib::getInstance() != NULL) - { - llassert(mPathfindingToolset != NULL); - LLToolMgr *toolMgrInstance = LLToolMgr::getInstance(); - if (toolMgrInstance->getCurrentToolset() != mPathfindingToolset) - { - mSavedToolset = toolMgrInstance->getCurrentToolset(); - toolMgrInstance->setCurrentToolset(mPathfindingToolset); - } - } -} - -void LLFloaterPathfindingConsole::switchOutOfTestPathMode() -{ - if (LLPathingLib::getInstance() != NULL) - { - llassert(mPathfindingToolset != NULL); - LLToolMgr *toolMgrInstance = LLToolMgr::getInstance(); - if (toolMgrInstance->getCurrentToolset() == mPathfindingToolset) - { - toolMgrInstance->setCurrentToolset(mSavedToolset); - mSavedToolset = NULL; - } - } -} - -void LLFloaterPathfindingConsole::updateCharacterWidth() -{ - LLPathfindingPathTool::getInstance()->setCharacterWidth(mCharacterWidthSlider->getValueF32()); -} - -void LLFloaterPathfindingConsole::updateCharacterType() -{ - LLPathfindingPathTool::ECharacterType characterType; - - switch (mCharacterTypeComboBox->getValue().asInteger()) - { - case XUI_CHARACTER_TYPE_NONE : - characterType = LLPathfindingPathTool::kCharacterTypeNone; - break; - case XUI_CHARACTER_TYPE_A : - characterType = LLPathfindingPathTool::kCharacterTypeA; - break; - case XUI_CHARACTER_TYPE_B : - characterType = LLPathfindingPathTool::kCharacterTypeB; - break; - case XUI_CHARACTER_TYPE_C : - characterType = LLPathfindingPathTool::kCharacterTypeC; - break; - case XUI_CHARACTER_TYPE_D : - characterType = LLPathfindingPathTool::kCharacterTypeD; - break; - default : - characterType = LLPathfindingPathTool::kCharacterTypeNone; - llassert(0); - break; - } - - LLPathfindingPathTool::getInstance()->setCharacterType(characterType); -} - -void LLFloaterPathfindingConsole::clearPath() -{ - LLPathfindingPathTool::getInstance()->clearPath(); -} - -void LLFloaterPathfindingConsole::updatePathTestStatus() -{ - std::string statusText(""); - LLStyle::Params styleParams; - - switch (LLPathfindingPathTool::getInstance()->getPathStatus()) - { - case LLPathfindingPathTool::kPathStatusUnknown : - statusText = getString("pathing_unknown"); - styleParams.color = mErrorColor; - break; - case LLPathfindingPathTool::kPathStatusChooseStartAndEndPoints : - statusText = getString("pathing_choose_start_and_end_points"); - styleParams.color = mWarningColor; - break; - case LLPathfindingPathTool::kPathStatusChooseStartPoint : - statusText = getString("pathing_choose_start_point"); - styleParams.color = mWarningColor; - break; - case LLPathfindingPathTool::kPathStatusChooseEndPoint : - statusText = getString("pathing_choose_end_point"); - styleParams.color = mWarningColor; - break; - case LLPathfindingPathTool::kPathStatusHasValidPath : - statusText = getString("pathing_path_valid"); - break; - case LLPathfindingPathTool::kPathStatusHasInvalidPath : - statusText = getString("pathing_path_invalid"); - styleParams.color = mErrorColor; - break; - case LLPathfindingPathTool::kPathStatusNotEnabled : - statusText = getString("pathing_region_not_enabled"); - styleParams.color = mErrorColor; - break; - case LLPathfindingPathTool::kPathStatusNotImplemented : - statusText = getString("pathing_library_not_implemented"); - styleParams.color = mErrorColor; - break; - case LLPathfindingPathTool::kPathStatusError : - statusText = getString("pathing_error"); - styleParams.color = mErrorColor; - break; - default : - statusText = getString("pathing_unknown"); - styleParams.color = mErrorColor; - break; - } - - mPathTestingStatus->setText((LLStringExplicit)statusText, styleParams); -} - - -bool LLFloaterPathfindingConsole::isRenderAnyShapes() const -{ - return (isRenderWalkables() || isRenderStaticObstacles() || - isRenderMaterialVolumes() || isRenderExclusionVolumes()); -} - -U32 LLFloaterPathfindingConsole::getRenderShapeFlags() -{ - U32 shapeRenderFlag = 0U; - - if (isRenderWalkables()) - { - SET_SHAPE_RENDER_FLAG(shapeRenderFlag, LLPathingLib::LLST_WalkableObjects); - } - if (isRenderStaticObstacles()) - { - SET_SHAPE_RENDER_FLAG(shapeRenderFlag, LLPathingLib::LLST_ObstacleObjects); - } - if (isRenderMaterialVolumes()) - { - SET_SHAPE_RENDER_FLAG(shapeRenderFlag, LLPathingLib::LLST_MaterialPhantoms); - } - if (isRenderExclusionVolumes()) - { - SET_SHAPE_RENDER_FLAG(shapeRenderFlag, LLPathingLib::LLST_ExclusionPhantoms); - } - - return shapeRenderFlag; -} - -void LLFloaterPathfindingConsole::registerSavedSettingsListeners() -{ - if (!mSavedSettingRetrieveNeighborSlot.connected()) - { - mSavedSettingRetrieveNeighborSlot = gSavedSettings.getControl(CONTROL_NAME_RETRIEVE_NEIGHBOR)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleRetrieveNeighborChange, this, _1, _2)); - } - if (!mSavedSettingWalkableSlot.connected()) - { - mSavedSettingWalkableSlot = gSavedSettings.getControl(CONTROL_NAME_WALKABLE_OBJECTS)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); - } - if (!mSavedSettingStaticObstacleSlot.connected()) - { - mSavedSettingStaticObstacleSlot = gSavedSettings.getControl(CONTROL_NAME_STATIC_OBSTACLE_OBJECTS)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); - } - if (!mSavedSettingMaterialVolumeSlot.connected()) - { - mSavedSettingMaterialVolumeSlot = gSavedSettings.getControl(CONTROL_NAME_MATERIAL_VOLUMES)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); - } - if (!mSavedSettingExclusionVolumeSlot.connected()) - { - mSavedSettingExclusionVolumeSlot = gSavedSettings.getControl(CONTROL_NAME_EXCLUSION_VOLUMES)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); - } - if (!mSavedSettingInteriorEdgeSlot.connected()) - { - mSavedSettingInteriorEdgeSlot = gSavedSettings.getControl(CONTROL_NAME_INTERIOR_EDGE)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); - } - if (!mSavedSettingExteriorEdgeSlot.connected()) - { - mSavedSettingExteriorEdgeSlot = gSavedSettings.getControl(CONTROL_NAME_EXTERIOR_EDGE)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); - } - if (!mSavedSettingHeatmapMinSlot.connected()) - { - mSavedSettingHeatmapMinSlot = gSavedSettings.getControl(CONTROL_NAME_HEATMAP_MIN)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); - } - if (!mSavedSettingHeatmapMaxSlot.connected()) - { - mSavedSettingHeatmapMaxSlot = gSavedSettings.getControl(CONTROL_NAME_HEATMAP_MAX)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); - } - if (!mSavedSettingNavMeshFaceSlot.connected()) - { - mSavedSettingNavMeshFaceSlot = gSavedSettings.getControl(CONTROL_NAME_NAVMESH_FACE)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); - } - if (!mSavedSettingTestPathValidEndSlot.connected()) - { - mSavedSettingTestPathValidEndSlot = gSavedSettings.getControl(CONTROL_NAME_TEST_PATH_VALID_END)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); - } - if (!mSavedSettingTestPathInvalidEndSlot.connected()) - { - mSavedSettingTestPathInvalidEndSlot = gSavedSettings.getControl(CONTROL_NAME_TEST_PATH_INVALID_END)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); - } - if (!mSavedSettingTestPathSlot.connected()) - { - mSavedSettingTestPathSlot = gSavedSettings.getControl(CONTROL_NAME_TEST_PATH)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); - } - if (!mSavedSettingWaterSlot.connected()) - { - mSavedSettingWaterSlot = gSavedSettings.getControl(CONTROL_NAME_WATER)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); - } -} - -void LLFloaterPathfindingConsole::deregisterSavedSettingsListeners() -{ - if (mSavedSettingRetrieveNeighborSlot.connected()) - { - mSavedSettingRetrieveNeighborSlot.disconnect(); - } - if (mSavedSettingWalkableSlot.connected()) - { - mSavedSettingWalkableSlot.disconnect(); - } - if (mSavedSettingStaticObstacleSlot.connected()) - { - mSavedSettingStaticObstacleSlot.disconnect(); - } - if (mSavedSettingMaterialVolumeSlot.connected()) - { - mSavedSettingMaterialVolumeSlot.disconnect(); - } - if (mSavedSettingExclusionVolumeSlot.connected()) - { - mSavedSettingExclusionVolumeSlot.disconnect(); - } - if (mSavedSettingInteriorEdgeSlot.connected()) - { - mSavedSettingInteriorEdgeSlot.disconnect(); - } - if (mSavedSettingExteriorEdgeSlot.connected()) - { - mSavedSettingExteriorEdgeSlot.disconnect(); - } - if (mSavedSettingHeatmapMinSlot.connected()) - { - mSavedSettingHeatmapMinSlot.disconnect(); - } - if (mSavedSettingHeatmapMaxSlot.connected()) - { - mSavedSettingHeatmapMaxSlot.disconnect(); - } - if (mSavedSettingNavMeshFaceSlot.connected()) - { - mSavedSettingNavMeshFaceSlot.disconnect(); - } - if (mSavedSettingTestPathValidEndSlot.connected()) - { - mSavedSettingTestPathValidEndSlot.disconnect(); - } - if (mSavedSettingTestPathInvalidEndSlot.connected()) - { - mSavedSettingTestPathInvalidEndSlot.disconnect(); - } - if (mSavedSettingTestPathSlot.connected()) - { - mSavedSettingTestPathSlot.disconnect(); - } - if (mSavedSettingWaterSlot.connected()) - { - mSavedSettingWaterSlot.disconnect(); - } -} - -void LLFloaterPathfindingConsole::handleRetrieveNeighborChange(LLControlVariable *pControl, const LLSD &pNewValue) -{ - initializeNavMeshZoneForCurrentRegion(); -} - -void LLFloaterPathfindingConsole::handleNavMeshColorChange(LLControlVariable *pControl, const LLSD &pNewValue) -{ - fillInColorsForNavMeshVisualization(); -} - -void LLFloaterPathfindingConsole::fillInColorsForNavMeshVisualization() -{ - if (LLPathingLib::getInstance() != NULL) - { - LLPathingLib::NavMeshColors navMeshColors; - - LLColor4 in = gSavedSettings.getColor4(CONTROL_NAME_WALKABLE_OBJECTS); - navMeshColors.mWalkable= LLColor4U(in); - - in = gSavedSettings.getColor4(CONTROL_NAME_STATIC_OBSTACLE_OBJECTS); - navMeshColors.mObstacle= LLColor4U(in); - - in = gSavedSettings.getColor4(CONTROL_NAME_MATERIAL_VOLUMES); - navMeshColors.mMaterial= LLColor4U(in); - - in = gSavedSettings.getColor4(CONTROL_NAME_EXCLUSION_VOLUMES); - navMeshColors.mExclusion= LLColor4U(in); - - in = gSavedSettings.getColor4(CONTROL_NAME_INTERIOR_EDGE); - navMeshColors.mConnectedEdge= LLColor4U(in); - - in = gSavedSettings.getColor4(CONTROL_NAME_EXTERIOR_EDGE); - navMeshColors.mBoundaryEdge= LLColor4U(in); - - navMeshColors.mHeatColorBase = gSavedSettings.getColor4(CONTROL_NAME_HEATMAP_MIN); - - navMeshColors.mHeatColorMax = gSavedSettings.getColor4(CONTROL_NAME_HEATMAP_MAX); - - in = gSavedSettings.getColor4(CONTROL_NAME_NAVMESH_FACE); - navMeshColors.mFaceColor= LLColor4U(in); - - in = gSavedSettings.getColor4(CONTROL_NAME_TEST_PATH_VALID_END); - navMeshColors.mStarValid= LLColor4U(in); - - in = gSavedSettings.getColor4(CONTROL_NAME_TEST_PATH_INVALID_END); - navMeshColors.mStarInvalid= LLColor4U(in); - - in = gSavedSettings.getColor4(CONTROL_NAME_TEST_PATH); - navMeshColors.mTestPath= LLColor4U(in); - - in = gSavedSettings.getColor4(CONTROL_NAME_WATER); - navMeshColors.mWaterColor= LLColor4U(in); - - LLPathingLib::getInstance()->setNavMeshColors(navMeshColors); - } -} +/** +* @file llfloaterpathfindingconsole.cpp +* @brief "Pathfinding console" floater, allowing for viewing and testing of the pathfinding navmesh through Havok AI utilities. +* @author Stinson@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterpathfindingconsole.h" + +#include + +#include + +#include "llagent.h" +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "llcontrol.h" +#include "llfloaterpathfindingcharacters.h" +#include "llfloaterpathfindinglinksets.h" +#include "llfloaterreg.h" +#include "llhandle.h" +#include "llpanel.h" +#include "llpathfindingnavmeshzone.h" +#include "llpathfindingpathtool.h" +#include "llpathinglib.h" +#include "llsliderctrl.h" +#include "llsd.h" +#include "lltabcontainer.h" +#include "lltextbase.h" +#include "lltoolmgr.h" +#include "lltoolfocus.h" +#include "llviewercontrol.h" +#include "llviewerparcelmgr.h" +#include "pipeline.h" + +#define XUI_RENDER_HEATMAP_NONE 0 +#define XUI_RENDER_HEATMAP_A 1 +#define XUI_RENDER_HEATMAP_B 2 +#define XUI_RENDER_HEATMAP_C 3 +#define XUI_RENDER_HEATMAP_D 4 + +#define XUI_CHARACTER_TYPE_NONE 0 +#define XUI_CHARACTER_TYPE_A 1 +#define XUI_CHARACTER_TYPE_B 2 +#define XUI_CHARACTER_TYPE_C 3 +#define XUI_CHARACTER_TYPE_D 4 + +#define XUI_VIEW_TAB_INDEX 0 +#define XUI_TEST_TAB_INDEX 1 + +#define SET_SHAPE_RENDER_FLAG(_flag,_type) _flag |= (1U << _type) + +#define CONTROL_NAME_RETRIEVE_NEIGHBOR "PathfindingRetrieveNeighboringRegion" +#define CONTROL_NAME_WALKABLE_OBJECTS "PathfindingWalkable" +#define CONTROL_NAME_STATIC_OBSTACLE_OBJECTS "PathfindingObstacle" +#define CONTROL_NAME_MATERIAL_VOLUMES "PathfindingMaterial" +#define CONTROL_NAME_EXCLUSION_VOLUMES "PathfindingExclusion" +#define CONTROL_NAME_INTERIOR_EDGE "PathfindingConnectedEdge" +#define CONTROL_NAME_EXTERIOR_EDGE "PathfindingBoundaryEdge" +#define CONTROL_NAME_HEATMAP_MIN "PathfindingHeatColorBase" +#define CONTROL_NAME_HEATMAP_MAX "PathfindingHeatColorMax" +#define CONTROL_NAME_NAVMESH_FACE "PathfindingFaceColor" +#define CONTROL_NAME_TEST_PATH_VALID_END "PathfindingTestPathValidEndColor" +#define CONTROL_NAME_TEST_PATH_INVALID_END "PathfindingTestPathInvalidEndColor" +#define CONTROL_NAME_TEST_PATH "PathfindingTestPathColor" +#define CONTROL_NAME_WATER "PathfindingWaterColor" + +LLHandle LLFloaterPathfindingConsole::sInstanceHandle; + +//--------------------------------------------------------------------------- +// LLFloaterPathfindingConsole +//--------------------------------------------------------------------------- + +bool LLFloaterPathfindingConsole::postBuild() +{ + mViewTestTabContainer = findChild("view_test_tab_container"); + llassert(mViewTestTabContainer != NULL); + mViewTestTabContainer->setCommitCallback(boost::bind(&LLFloaterPathfindingConsole::onTabSwitch, this)); + + mViewTab = findChild("view_panel"); + llassert(mViewTab != NULL); + + mShowLabel = findChild("show_label"); + llassert(mShowLabel != NULL); + + mShowWorldCheckBox = findChild("show_world"); + llassert(mShowWorldCheckBox != NULL); + mShowWorldCheckBox->setCommitCallback(boost::bind(&LLFloaterPathfindingConsole::onShowWorldSet, this)); + + mShowWorldMovablesOnlyCheckBox = findChild("show_world_movables_only"); + llassert(mShowWorldMovablesOnlyCheckBox != NULL); + mShowWorldMovablesOnlyCheckBox->setCommitCallback(boost::bind(&LLFloaterPathfindingConsole::onShowWorldMovablesOnlySet, this)); + + mShowNavMeshCheckBox = findChild("show_navmesh"); + llassert(mShowNavMeshCheckBox != NULL); + mShowNavMeshCheckBox->setCommitCallback(boost::bind(&LLFloaterPathfindingConsole::onShowNavMeshSet, this)); + + mShowNavMeshWalkabilityLabel = findChild("show_walkability_label"); + llassert(mShowNavMeshWalkabilityLabel != NULL); + + mShowNavMeshWalkabilityComboBox = findChild("show_heatmap_mode"); + llassert(mShowNavMeshWalkabilityComboBox != NULL); + mShowNavMeshWalkabilityComboBox->setCommitCallback(boost::bind(&LLFloaterPathfindingConsole::onShowWalkabilitySet, this)); + + mShowWalkablesCheckBox = findChild("show_walkables"); + llassert(mShowWalkablesCheckBox != NULL); + + mShowStaticObstaclesCheckBox = findChild("show_static_obstacles"); + llassert(mShowStaticObstaclesCheckBox != NULL); + + mShowMaterialVolumesCheckBox = findChild("show_material_volumes"); + llassert(mShowMaterialVolumesCheckBox != NULL); + + mShowExclusionVolumesCheckBox = findChild("show_exclusion_volumes"); + llassert(mShowExclusionVolumesCheckBox != NULL); + + mShowRenderWaterPlaneCheckBox = findChild("show_water_plane"); + llassert(mShowRenderWaterPlaneCheckBox != NULL); + + mShowXRayCheckBox = findChild("show_xray"); + llassert(mShowXRayCheckBox != NULL); + + mTestTab = findChild("test_panel"); + llassert(mTestTab != NULL); + + mPathfindingViewerStatus = findChild("pathfinding_viewer_status"); + llassert(mPathfindingViewerStatus != NULL); + + mPathfindingSimulatorStatus = findChild("pathfinding_simulator_status"); + llassert(mPathfindingSimulatorStatus != NULL); + + mCtrlClickLabel = findChild("ctrl_click_label"); + llassert(mCtrlClickLabel != NULL); + + mShiftClickLabel = findChild("shift_click_label"); + llassert(mShiftClickLabel != NULL); + + mCharacterWidthLabel = findChild("character_width_label"); + llassert(mCharacterWidthLabel != NULL); + + mCharacterWidthSlider = findChild("character_width"); + llassert(mCharacterWidthSlider != NULL); + mCharacterWidthSlider->setCommitCallback(boost::bind(&LLFloaterPathfindingConsole::onCharacterWidthSet, this)); + + mCharacterWidthUnitLabel = findChild("character_width_unit_label"); + llassert(mCharacterWidthUnitLabel != NULL); + + mCharacterTypeLabel = findChild("character_type_label"); + llassert(mCharacterTypeLabel != NULL); + + mCharacterTypeComboBox = findChild("path_character_type"); + llassert(mCharacterTypeComboBox != NULL); + mCharacterTypeComboBox->setCommitCallback(boost::bind(&LLFloaterPathfindingConsole::onCharacterTypeSwitch, this)); + + mPathTestingStatus = findChild("path_test_status"); + llassert(mPathTestingStatus != NULL); + + mClearPathButton = findChild("clear_path"); + llassert(mClearPathButton != NULL); + mClearPathButton->setCommitCallback(boost::bind(&LLFloaterPathfindingConsole::onClearPathClicked, this)); + + mErrorColor = LLUIColorTable::instance().getColor("PathfindingErrorColor"); + mWarningColor = LLUIColorTable::instance().getColor("PathfindingWarningColor"); + + if (LLPathingLib::getInstance() != NULL) + { + mPathfindingToolset = new LLToolset(); + mPathfindingToolset->addTool(LLPathfindingPathTool::getInstance()); + mPathfindingToolset->addTool(LLToolCamera::getInstance()); + mPathfindingToolset->setShowFloaterTools(false); + } + + updateCharacterWidth(); + updateCharacterType(); + + return LLFloater::postBuild(); +} + +void LLFloaterPathfindingConsole::onOpen(const LLSD& pKey) +{ + LLFloater::onOpen(pKey); + //make sure we have a pathing system + if ( LLPathingLib::getInstance() == NULL ) + { + setConsoleState(kConsoleStateLibraryNotImplemented); + LL_WARNS() <<"Errror: cannot find pathing library implementation."<setTeleportFailedCallback(boost::bind(&LLFloaterPathfindingConsole::onRegionBoundaryCross, this)); + } + + if (!mPathEventSlot.connected()) + { + mPathEventSlot = LLPathfindingPathTool::getInstance()->registerPathEventListener(boost::bind(&LLFloaterPathfindingConsole::onPathEvent, this)); + } + + setDefaultInputs(); + updatePathTestStatus(); + + if (mViewTestTabContainer->getCurrentPanelIndex() == XUI_TEST_TAB_INDEX) + { + switchIntoTestPathMode(); + } +} + +void LLFloaterPathfindingConsole::onClose(bool pIsAppQuitting) +{ + switchOutOfTestPathMode(); + + if (mPathEventSlot.connected()) + { + mPathEventSlot.disconnect(); + } + + if (mTeleportFailedSlot.connected()) + { + mTeleportFailedSlot.disconnect(); + } + + if (mRegionBoundarySlot.connected()) + { + mRegionBoundarySlot.disconnect(); + } + + if (mNavMeshZoneSlot.connected()) + { + mNavMeshZoneSlot.disconnect(); + } + + if (LLPathingLib::getInstance() != NULL) + { + mNavMeshZone.disable(); + } + deregisterSavedSettingsListeners(); + + setDefaultInputs(); + setConsoleState(kConsoleStateUnknown); + cleanupRenderableRestoreItems(); + + LLFloater::onClose(pIsAppQuitting); +} + +LLHandle LLFloaterPathfindingConsole::getInstanceHandle() +{ + if (sInstanceHandle.isDead()) + { + LLFloaterPathfindingConsole *floaterInstance = LLFloaterReg::findTypedInstance("pathfinding_console"); + if (floaterInstance != NULL) + { + sInstanceHandle = floaterInstance->mSelfHandle; + } + } + + return sInstanceHandle; +} + +bool LLFloaterPathfindingConsole::isRenderNavMesh() const +{ + return mShowNavMeshCheckBox->get(); +} + +void LLFloaterPathfindingConsole::setRenderNavMesh(bool pIsRenderNavMesh) +{ + mShowNavMeshCheckBox->set(pIsRenderNavMesh); + setNavMeshRenderState(); +} + +bool LLFloaterPathfindingConsole::isRenderWalkables() const +{ + return mShowWalkablesCheckBox->get(); +} + +void LLFloaterPathfindingConsole::setRenderWalkables(bool pIsRenderWalkables) +{ + mShowWalkablesCheckBox->set(pIsRenderWalkables); +} + +bool LLFloaterPathfindingConsole::isRenderStaticObstacles() const +{ + return mShowStaticObstaclesCheckBox->get(); +} + +void LLFloaterPathfindingConsole::setRenderStaticObstacles(bool pIsRenderStaticObstacles) +{ + mShowStaticObstaclesCheckBox->set(pIsRenderStaticObstacles); +} + +bool LLFloaterPathfindingConsole::isRenderMaterialVolumes() const +{ + return mShowMaterialVolumesCheckBox->get(); +} + +void LLFloaterPathfindingConsole::setRenderMaterialVolumes(bool pIsRenderMaterialVolumes) +{ + mShowMaterialVolumesCheckBox->set(pIsRenderMaterialVolumes); +} + +bool LLFloaterPathfindingConsole::isRenderExclusionVolumes() const +{ + return mShowExclusionVolumesCheckBox->get(); +} + +void LLFloaterPathfindingConsole::setRenderExclusionVolumes(bool pIsRenderExclusionVolumes) +{ + mShowExclusionVolumesCheckBox->set(pIsRenderExclusionVolumes); +} + +bool LLFloaterPathfindingConsole::isRenderWorld() const +{ + return mShowWorldCheckBox->get(); +} + +void LLFloaterPathfindingConsole::setRenderWorld(bool pIsRenderWorld) +{ + mShowWorldCheckBox->set(pIsRenderWorld); + setWorldRenderState(); +} + +bool LLFloaterPathfindingConsole::isRenderWorldMovablesOnly() const +{ + return (mShowWorldCheckBox->get() && mShowWorldMovablesOnlyCheckBox->get()); +} + +void LLFloaterPathfindingConsole::setRenderWorldMovablesOnly(bool pIsRenderWorldMovablesOnly) +{ + mShowWorldMovablesOnlyCheckBox->set(pIsRenderWorldMovablesOnly); +} + +bool LLFloaterPathfindingConsole::isRenderWaterPlane() const +{ + return mShowRenderWaterPlaneCheckBox->get(); +} + +void LLFloaterPathfindingConsole::setRenderWaterPlane(bool pIsRenderWaterPlane) +{ + mShowRenderWaterPlaneCheckBox->set(pIsRenderWaterPlane); +} + +bool LLFloaterPathfindingConsole::isRenderXRay() const +{ + return mShowXRayCheckBox->get(); +} + +void LLFloaterPathfindingConsole::setRenderXRay(bool pIsRenderXRay) +{ + mShowXRayCheckBox->set(pIsRenderXRay); +} + +LLPathingLib::LLPLCharacterType LLFloaterPathfindingConsole::getRenderHeatmapType() const +{ + LLPathingLib::LLPLCharacterType renderHeatmapType; + + switch (mShowNavMeshWalkabilityComboBox->getValue().asInteger()) + { + case XUI_RENDER_HEATMAP_NONE : + renderHeatmapType = LLPathingLib::LLPL_CHARACTER_TYPE_NONE; + break; + case XUI_RENDER_HEATMAP_A : + renderHeatmapType = LLPathingLib::LLPL_CHARACTER_TYPE_A; + break; + case XUI_RENDER_HEATMAP_B : + renderHeatmapType = LLPathingLib::LLPL_CHARACTER_TYPE_B; + break; + case XUI_RENDER_HEATMAP_C : + renderHeatmapType = LLPathingLib::LLPL_CHARACTER_TYPE_C; + break; + case XUI_RENDER_HEATMAP_D : + renderHeatmapType = LLPathingLib::LLPL_CHARACTER_TYPE_D; + break; + default : + renderHeatmapType = LLPathingLib::LLPL_CHARACTER_TYPE_NONE; + llassert(0); + break; + } + + return renderHeatmapType; +} + +void LLFloaterPathfindingConsole::setRenderHeatmapType(LLPathingLib::LLPLCharacterType pRenderHeatmapType) +{ + LLSD comboBoxValue; + + switch (pRenderHeatmapType) + { + case LLPathingLib::LLPL_CHARACTER_TYPE_NONE : + comboBoxValue = XUI_RENDER_HEATMAP_NONE; + break; + case LLPathingLib::LLPL_CHARACTER_TYPE_A : + comboBoxValue = XUI_RENDER_HEATMAP_A; + break; + case LLPathingLib::LLPL_CHARACTER_TYPE_B : + comboBoxValue = XUI_RENDER_HEATMAP_B; + break; + case LLPathingLib::LLPL_CHARACTER_TYPE_C : + comboBoxValue = XUI_RENDER_HEATMAP_C; + break; + case LLPathingLib::LLPL_CHARACTER_TYPE_D : + comboBoxValue = XUI_RENDER_HEATMAP_D; + break; + default : + comboBoxValue = XUI_RENDER_HEATMAP_NONE; + llassert(0); + break; + } + + mShowNavMeshWalkabilityComboBox->setValue(comboBoxValue); +} + +LLFloaterPathfindingConsole::LLFloaterPathfindingConsole(const LLSD& pSeed) + : LLFloater(pSeed), + mSelfHandle(), + mViewTestTabContainer(NULL), + mViewTab(NULL), + mShowLabel(NULL), + mShowWorldCheckBox(NULL), + mShowWorldMovablesOnlyCheckBox(NULL), + mShowNavMeshCheckBox(NULL), + mShowNavMeshWalkabilityLabel(NULL), + mShowNavMeshWalkabilityComboBox(NULL), + mShowWalkablesCheckBox(NULL), + mShowStaticObstaclesCheckBox(NULL), + mShowMaterialVolumesCheckBox(NULL), + mShowExclusionVolumesCheckBox(NULL), + mShowRenderWaterPlaneCheckBox(NULL), + mShowXRayCheckBox(NULL), + mPathfindingViewerStatus(NULL), + mPathfindingSimulatorStatus(NULL), + mTestTab(NULL), + mCtrlClickLabel(), + mShiftClickLabel(), + mCharacterWidthLabel(), + mCharacterWidthUnitLabel(), + mCharacterWidthSlider(NULL), + mCharacterTypeLabel(), + mCharacterTypeComboBox(NULL), + mPathTestingStatus(NULL), + mClearPathButton(NULL), + mErrorColor(), + mWarningColor(), + mNavMeshZoneSlot(), + mNavMeshZone(), + mIsNavMeshUpdating(false), + mRegionBoundarySlot(), + mTeleportFailedSlot(), + mPathEventSlot(), + mPathfindingToolset(NULL), + mSavedToolset(NULL), + mSavedSettingRetrieveNeighborSlot(), + mSavedSettingWalkableSlot(), + mSavedSettingStaticObstacleSlot(), + mSavedSettingMaterialVolumeSlot(), + mSavedSettingExclusionVolumeSlot(), + mSavedSettingInteriorEdgeSlot(), + mSavedSettingExteriorEdgeSlot(), + mSavedSettingHeatmapMinSlot(), + mSavedSettingHeatmapMaxSlot(), + mSavedSettingNavMeshFaceSlot(), + mSavedSettingTestPathValidEndSlot(), + mSavedSettingTestPathInvalidEndSlot(), + mSavedSettingTestPathSlot(), + mSavedSettingWaterSlot(), + mConsoleState(kConsoleStateUnknown), + mRenderableRestoreList() +{ + mSelfHandle.bind(this); +} + +LLFloaterPathfindingConsole::~LLFloaterPathfindingConsole() +{ +} + +void LLFloaterPathfindingConsole::onTabSwitch() +{ + if (mViewTestTabContainer->getCurrentPanelIndex() == XUI_TEST_TAB_INDEX) + { + switchIntoTestPathMode(); + } + else + { + switchOutOfTestPathMode(); + } +} + +void LLFloaterPathfindingConsole::onShowWorldSet() +{ + setWorldRenderState(); + updateRenderablesObjects(); +} + +void LLFloaterPathfindingConsole::onShowWorldMovablesOnlySet() +{ + updateRenderablesObjects(); +} + +void LLFloaterPathfindingConsole::onShowNavMeshSet() +{ + setNavMeshRenderState(); +} + +void LLFloaterPathfindingConsole::onShowWalkabilitySet() +{ + if (LLPathingLib::getInstance() != NULL) + { + LLPathingLib::getInstance()->setNavMeshMaterialType(getRenderHeatmapType()); + } +} + +void LLFloaterPathfindingConsole::onCharacterWidthSet() +{ + updateCharacterWidth(); +} + +void LLFloaterPathfindingConsole::onCharacterTypeSwitch() +{ + updateCharacterType(); +} + +void LLFloaterPathfindingConsole::onClearPathClicked() +{ + clearPath(); +} + +void LLFloaterPathfindingConsole::handleNavMeshZoneStatus(LLPathfindingNavMeshZone::ENavMeshZoneRequestStatus pNavMeshZoneRequestStatus) +{ + switch (pNavMeshZoneRequestStatus) + { + case LLPathfindingNavMeshZone::kNavMeshZoneRequestUnknown : + setConsoleState(kConsoleStateUnknown); + break; + case LLPathfindingNavMeshZone::kNavMeshZoneRequestWaiting : + setConsoleState(kConsoleStateRegionLoading); + break; + case LLPathfindingNavMeshZone::kNavMeshZoneRequestChecking : + setConsoleState(kConsoleStateCheckingVersion); + break; + case LLPathfindingNavMeshZone::kNavMeshZoneRequestNeedsUpdate : + mIsNavMeshUpdating = true; + mNavMeshZone.refresh(); + break; + case LLPathfindingNavMeshZone::kNavMeshZoneRequestStarted : + setConsoleState(kConsoleStateDownloading); + break; + case LLPathfindingNavMeshZone::kNavMeshZoneRequestCompleted : + mIsNavMeshUpdating = false; + setConsoleState(kConsoleStateHasNavMesh); + break; + case LLPathfindingNavMeshZone::kNavMeshZoneRequestNotEnabled : + setConsoleState(kConsoleStateRegionNotEnabled); + break; + case LLPathfindingNavMeshZone::kNavMeshZoneRequestError : + setConsoleState(kConsoleStateError); + break; + default: + setConsoleState(kConsoleStateUnknown); + llassert(0); + break; + } +} + +void LLFloaterPathfindingConsole::onRegionBoundaryCross() +{ + initializeNavMeshZoneForCurrentRegion(); + setRenderWorld(true); + setRenderWorldMovablesOnly(false); +} + +void LLFloaterPathfindingConsole::onPathEvent() +{ + const LLPathfindingPathTool *pathToolInstance = LLPathfindingPathTool::getInstance(); + + mCharacterWidthSlider->setValue(LLSD(pathToolInstance->getCharacterWidth())); + + LLSD characterType; + switch (pathToolInstance->getCharacterType()) + { + case LLPathfindingPathTool::kCharacterTypeNone : + characterType = XUI_CHARACTER_TYPE_NONE; + break; + case LLPathfindingPathTool::kCharacterTypeA : + characterType = XUI_CHARACTER_TYPE_A; + break; + case LLPathfindingPathTool::kCharacterTypeB : + characterType = XUI_CHARACTER_TYPE_B; + break; + case LLPathfindingPathTool::kCharacterTypeC : + characterType = XUI_CHARACTER_TYPE_C; + break; + case LLPathfindingPathTool::kCharacterTypeD : + characterType = XUI_CHARACTER_TYPE_D; + break; + default : + characterType = XUI_CHARACTER_TYPE_NONE; + llassert(0); + break; + } + mCharacterTypeComboBox->setValue(characterType); + + updatePathTestStatus(); +} + +void LLFloaterPathfindingConsole::setDefaultInputs() +{ + mViewTestTabContainer->selectTab(XUI_VIEW_TAB_INDEX); + setRenderWorld(true); + setRenderWorldMovablesOnly(false); + setRenderNavMesh(false); + setRenderWalkables(false); + setRenderMaterialVolumes(false); + setRenderStaticObstacles(false); + setRenderExclusionVolumes(false); + setRenderWaterPlane(false); + setRenderXRay(false); +} + +void LLFloaterPathfindingConsole::setConsoleState(EConsoleState pConsoleState) +{ + mConsoleState = pConsoleState; + updateControlsOnConsoleState(); + updateViewerStatusOnConsoleState(); + updateSimulatorStatusOnConsoleState(); +} + +void LLFloaterPathfindingConsole::setWorldRenderState() +{ + bool renderWorld = isRenderWorld(); + + mShowWorldMovablesOnlyCheckBox->setEnabled(renderWorld && mShowWorldCheckBox->getEnabled()); + if (!renderWorld) + { + mShowWorldMovablesOnlyCheckBox->set(false); + } +} + +void LLFloaterPathfindingConsole::setNavMeshRenderState() +{ + bool renderNavMesh = isRenderNavMesh(); + + mShowNavMeshWalkabilityLabel->setEnabled(renderNavMesh); + mShowNavMeshWalkabilityComboBox->setEnabled(renderNavMesh); +} + +void LLFloaterPathfindingConsole::updateRenderablesObjects() +{ + if ( isRenderWorldMovablesOnly() ) + { + gPipeline.hidePermanentObjects( mRenderableRestoreList ); + } + else + { + cleanupRenderableRestoreItems(); + } +} + +void LLFloaterPathfindingConsole::updateControlsOnConsoleState() +{ + switch (mConsoleState) + { + case kConsoleStateUnknown : + case kConsoleStateRegionNotEnabled : + case kConsoleStateRegionLoading : + mViewTestTabContainer->selectTab(XUI_VIEW_TAB_INDEX); + mViewTab->setEnabled(false); + mShowLabel->setEnabled(false); + mShowWorldCheckBox->setEnabled(false); + mShowWorldMovablesOnlyCheckBox->setEnabled(false); + mShowNavMeshCheckBox->setEnabled(false); + mShowNavMeshWalkabilityLabel->setEnabled(false); + mShowNavMeshWalkabilityComboBox->setEnabled(false); + mShowWalkablesCheckBox->setEnabled(false); + mShowStaticObstaclesCheckBox->setEnabled(false); + mShowMaterialVolumesCheckBox->setEnabled(false); + mShowExclusionVolumesCheckBox->setEnabled(false); + mShowRenderWaterPlaneCheckBox->setEnabled(false); + mShowXRayCheckBox->setEnabled(false); + mTestTab->setEnabled(false); + mCtrlClickLabel->setEnabled(false); + mShiftClickLabel->setEnabled(false); + mCharacterWidthLabel->setEnabled(false); + mCharacterWidthUnitLabel->setEnabled(false); + mCharacterWidthSlider->setEnabled(false); + mCharacterTypeLabel->setEnabled(false); + mCharacterTypeComboBox->setEnabled(false); + mClearPathButton->setEnabled(false); + clearPath(); + break; + case kConsoleStateLibraryNotImplemented : + mViewTestTabContainer->selectTab(XUI_VIEW_TAB_INDEX); + mViewTab->setEnabled(false); + mShowLabel->setEnabled(false); + mShowWorldCheckBox->setEnabled(false); + mShowWorldMovablesOnlyCheckBox->setEnabled(false); + mShowNavMeshCheckBox->setEnabled(false); + mShowNavMeshWalkabilityLabel->setEnabled(false); + mShowNavMeshWalkabilityComboBox->setEnabled(false); + mShowWalkablesCheckBox->setEnabled(false); + mShowStaticObstaclesCheckBox->setEnabled(false); + mShowMaterialVolumesCheckBox->setEnabled(false); + mShowExclusionVolumesCheckBox->setEnabled(false); + mShowRenderWaterPlaneCheckBox->setEnabled(false); + mShowXRayCheckBox->setEnabled(false); + mTestTab->setEnabled(false); + mCtrlClickLabel->setEnabled(false); + mShiftClickLabel->setEnabled(false); + mCharacterWidthLabel->setEnabled(false); + mCharacterWidthUnitLabel->setEnabled(false); + mCharacterWidthSlider->setEnabled(false); + mCharacterTypeLabel->setEnabled(false); + mCharacterTypeComboBox->setEnabled(false); + mClearPathButton->setEnabled(false); + clearPath(); + break; + case kConsoleStateCheckingVersion : + case kConsoleStateDownloading : + case kConsoleStateError : + mViewTestTabContainer->selectTab(XUI_VIEW_TAB_INDEX); + mViewTab->setEnabled(false); + mShowLabel->setEnabled(false); + mShowWorldCheckBox->setEnabled(false); + mShowWorldMovablesOnlyCheckBox->setEnabled(false); + mShowNavMeshCheckBox->setEnabled(false); + mShowNavMeshWalkabilityLabel->setEnabled(false); + mShowNavMeshWalkabilityComboBox->setEnabled(false); + mShowWalkablesCheckBox->setEnabled(false); + mShowStaticObstaclesCheckBox->setEnabled(false); + mShowMaterialVolumesCheckBox->setEnabled(false); + mShowExclusionVolumesCheckBox->setEnabled(false); + mShowRenderWaterPlaneCheckBox->setEnabled(false); + mShowXRayCheckBox->setEnabled(false); + mTestTab->setEnabled(false); + mCtrlClickLabel->setEnabled(false); + mShiftClickLabel->setEnabled(false); + mCharacterWidthLabel->setEnabled(false); + mCharacterWidthUnitLabel->setEnabled(false); + mCharacterWidthSlider->setEnabled(false); + mCharacterTypeLabel->setEnabled(false); + mCharacterTypeComboBox->setEnabled(false); + mClearPathButton->setEnabled(false); + clearPath(); + break; + case kConsoleStateHasNavMesh : + mViewTab->setEnabled(true); + mShowLabel->setEnabled(true); + mShowWorldCheckBox->setEnabled(true); + setWorldRenderState(); + mShowNavMeshCheckBox->setEnabled(true); + setNavMeshRenderState(); + mShowWalkablesCheckBox->setEnabled(true); + mShowStaticObstaclesCheckBox->setEnabled(true); + mShowMaterialVolumesCheckBox->setEnabled(true); + mShowExclusionVolumesCheckBox->setEnabled(true); + mShowRenderWaterPlaneCheckBox->setEnabled(true); + mShowXRayCheckBox->setEnabled(true); + mTestTab->setEnabled(true); + mCtrlClickLabel->setEnabled(true); + mShiftClickLabel->setEnabled(true); + mCharacterWidthLabel->setEnabled(true); + mCharacterWidthUnitLabel->setEnabled(true); + mCharacterWidthSlider->setEnabled(true); + mCharacterTypeLabel->setEnabled(true); + mCharacterTypeComboBox->setEnabled(true); + mClearPathButton->setEnabled(true); + break; + default : + llassert(0); + break; + } +} + +void LLFloaterPathfindingConsole::updateViewerStatusOnConsoleState() +{ + std::string viewerStatusText(""); + LLStyle::Params viewerStyleParams; + + switch (mConsoleState) + { + case kConsoleStateUnknown : + viewerStatusText = getString("navmesh_viewer_status_unknown"); + viewerStyleParams.color = mErrorColor; + break; + case kConsoleStateLibraryNotImplemented : + viewerStatusText = getString("navmesh_viewer_status_library_not_implemented"); + viewerStyleParams.color = mErrorColor; + break; + case kConsoleStateRegionNotEnabled : + viewerStatusText = getString("navmesh_viewer_status_region_not_enabled"); + viewerStyleParams.color = mErrorColor; + break; + case kConsoleStateRegionLoading : + viewerStatusText = getString("navmesh_viewer_status_region_loading"); + viewerStyleParams.color = mWarningColor; + break; + case kConsoleStateCheckingVersion : + viewerStatusText = getString("navmesh_viewer_status_checking_version"); + viewerStyleParams.color = mWarningColor; + break; + case kConsoleStateDownloading : + if (mIsNavMeshUpdating) + { + viewerStatusText = getString("navmesh_viewer_status_updating"); + } + else + { + viewerStatusText = getString("navmesh_viewer_status_downloading"); + } + viewerStyleParams.color = mWarningColor; + break; + case kConsoleStateHasNavMesh : + viewerStatusText = getString("navmesh_viewer_status_has_navmesh"); + break; + case kConsoleStateError : + viewerStatusText = getString("navmesh_viewer_status_error"); + viewerStyleParams.color = mErrorColor; + break; + default : + viewerStatusText = getString("navmesh_viewer_status_unknown"); + viewerStyleParams.color = mErrorColor; + llassert(0); + break; + } + + mPathfindingViewerStatus->setText((LLStringExplicit)viewerStatusText, viewerStyleParams); +} + +void LLFloaterPathfindingConsole::updateSimulatorStatusOnConsoleState() +{ + std::string simulatorStatusText(""); + LLStyle::Params simulatorStyleParams; + + switch (mConsoleState) + { + case kConsoleStateUnknown : + case kConsoleStateLibraryNotImplemented : + case kConsoleStateRegionNotEnabled : + case kConsoleStateRegionLoading : + case kConsoleStateCheckingVersion : + case kConsoleStateError : + simulatorStatusText = getString("navmesh_simulator_status_unknown"); + simulatorStyleParams.color = mErrorColor; + break; + case kConsoleStateDownloading : + case kConsoleStateHasNavMesh : + switch (mNavMeshZone.getNavMeshZoneStatus()) + { + case LLPathfindingNavMeshZone::kNavMeshZonePending : + simulatorStatusText = getString("navmesh_simulator_status_pending"); + simulatorStyleParams.color = mWarningColor; + break; + case LLPathfindingNavMeshZone::kNavMeshZoneBuilding : + simulatorStatusText = getString("navmesh_simulator_status_building"); + simulatorStyleParams.color = mWarningColor; + break; + case LLPathfindingNavMeshZone::kNavMeshZoneSomePending : + simulatorStatusText = getString("navmesh_simulator_status_some_pending"); + simulatorStyleParams.color = mWarningColor; + break; + case LLPathfindingNavMeshZone::kNavMeshZoneSomeBuilding : + simulatorStatusText = getString("navmesh_simulator_status_some_building"); + simulatorStyleParams.color = mWarningColor; + break; + case LLPathfindingNavMeshZone::kNavMeshZonePendingAndBuilding : + simulatorStatusText = getString("navmesh_simulator_status_pending_and_building"); + simulatorStyleParams.color = mWarningColor; + break; + case LLPathfindingNavMeshZone::kNavMeshZoneComplete : + simulatorStatusText = getString("navmesh_simulator_status_complete"); + break; + default : + simulatorStatusText = getString("navmesh_simulator_status_unknown"); + simulatorStyleParams.color = mErrorColor; + break; + } + break; + default : + simulatorStatusText = getString("navmesh_simulator_status_unknown"); + simulatorStyleParams.color = mErrorColor; + llassert(0); + break; + } + + mPathfindingSimulatorStatus->setText((LLStringExplicit)simulatorStatusText, simulatorStyleParams); +} + +void LLFloaterPathfindingConsole::initializeNavMeshZoneForCurrentRegion() +{ + mNavMeshZone.disable(); + mNavMeshZone.initialize(); + mNavMeshZone.enable(); + mNavMeshZone.refresh(); + cleanupRenderableRestoreItems(); +} + +void LLFloaterPathfindingConsole::cleanupRenderableRestoreItems() +{ + if ( !mRenderableRestoreList.empty() ) + { + gPipeline.restorePermanentObjects( mRenderableRestoreList ); + mRenderableRestoreList.clear(); + } + else + { + gPipeline.skipRenderingOfTerrain( false ); + } +} + +void LLFloaterPathfindingConsole::switchIntoTestPathMode() +{ + if (LLPathingLib::getInstance() != NULL) + { + llassert(mPathfindingToolset != NULL); + LLToolMgr *toolMgrInstance = LLToolMgr::getInstance(); + if (toolMgrInstance->getCurrentToolset() != mPathfindingToolset) + { + mSavedToolset = toolMgrInstance->getCurrentToolset(); + toolMgrInstance->setCurrentToolset(mPathfindingToolset); + } + } +} + +void LLFloaterPathfindingConsole::switchOutOfTestPathMode() +{ + if (LLPathingLib::getInstance() != NULL) + { + llassert(mPathfindingToolset != NULL); + LLToolMgr *toolMgrInstance = LLToolMgr::getInstance(); + if (toolMgrInstance->getCurrentToolset() == mPathfindingToolset) + { + toolMgrInstance->setCurrentToolset(mSavedToolset); + mSavedToolset = NULL; + } + } +} + +void LLFloaterPathfindingConsole::updateCharacterWidth() +{ + LLPathfindingPathTool::getInstance()->setCharacterWidth(mCharacterWidthSlider->getValueF32()); +} + +void LLFloaterPathfindingConsole::updateCharacterType() +{ + LLPathfindingPathTool::ECharacterType characterType; + + switch (mCharacterTypeComboBox->getValue().asInteger()) + { + case XUI_CHARACTER_TYPE_NONE : + characterType = LLPathfindingPathTool::kCharacterTypeNone; + break; + case XUI_CHARACTER_TYPE_A : + characterType = LLPathfindingPathTool::kCharacterTypeA; + break; + case XUI_CHARACTER_TYPE_B : + characterType = LLPathfindingPathTool::kCharacterTypeB; + break; + case XUI_CHARACTER_TYPE_C : + characterType = LLPathfindingPathTool::kCharacterTypeC; + break; + case XUI_CHARACTER_TYPE_D : + characterType = LLPathfindingPathTool::kCharacterTypeD; + break; + default : + characterType = LLPathfindingPathTool::kCharacterTypeNone; + llassert(0); + break; + } + + LLPathfindingPathTool::getInstance()->setCharacterType(characterType); +} + +void LLFloaterPathfindingConsole::clearPath() +{ + LLPathfindingPathTool::getInstance()->clearPath(); +} + +void LLFloaterPathfindingConsole::updatePathTestStatus() +{ + std::string statusText(""); + LLStyle::Params styleParams; + + switch (LLPathfindingPathTool::getInstance()->getPathStatus()) + { + case LLPathfindingPathTool::kPathStatusUnknown : + statusText = getString("pathing_unknown"); + styleParams.color = mErrorColor; + break; + case LLPathfindingPathTool::kPathStatusChooseStartAndEndPoints : + statusText = getString("pathing_choose_start_and_end_points"); + styleParams.color = mWarningColor; + break; + case LLPathfindingPathTool::kPathStatusChooseStartPoint : + statusText = getString("pathing_choose_start_point"); + styleParams.color = mWarningColor; + break; + case LLPathfindingPathTool::kPathStatusChooseEndPoint : + statusText = getString("pathing_choose_end_point"); + styleParams.color = mWarningColor; + break; + case LLPathfindingPathTool::kPathStatusHasValidPath : + statusText = getString("pathing_path_valid"); + break; + case LLPathfindingPathTool::kPathStatusHasInvalidPath : + statusText = getString("pathing_path_invalid"); + styleParams.color = mErrorColor; + break; + case LLPathfindingPathTool::kPathStatusNotEnabled : + statusText = getString("pathing_region_not_enabled"); + styleParams.color = mErrorColor; + break; + case LLPathfindingPathTool::kPathStatusNotImplemented : + statusText = getString("pathing_library_not_implemented"); + styleParams.color = mErrorColor; + break; + case LLPathfindingPathTool::kPathStatusError : + statusText = getString("pathing_error"); + styleParams.color = mErrorColor; + break; + default : + statusText = getString("pathing_unknown"); + styleParams.color = mErrorColor; + break; + } + + mPathTestingStatus->setText((LLStringExplicit)statusText, styleParams); +} + + +bool LLFloaterPathfindingConsole::isRenderAnyShapes() const +{ + return (isRenderWalkables() || isRenderStaticObstacles() || + isRenderMaterialVolumes() || isRenderExclusionVolumes()); +} + +U32 LLFloaterPathfindingConsole::getRenderShapeFlags() +{ + U32 shapeRenderFlag = 0U; + + if (isRenderWalkables()) + { + SET_SHAPE_RENDER_FLAG(shapeRenderFlag, LLPathingLib::LLST_WalkableObjects); + } + if (isRenderStaticObstacles()) + { + SET_SHAPE_RENDER_FLAG(shapeRenderFlag, LLPathingLib::LLST_ObstacleObjects); + } + if (isRenderMaterialVolumes()) + { + SET_SHAPE_RENDER_FLAG(shapeRenderFlag, LLPathingLib::LLST_MaterialPhantoms); + } + if (isRenderExclusionVolumes()) + { + SET_SHAPE_RENDER_FLAG(shapeRenderFlag, LLPathingLib::LLST_ExclusionPhantoms); + } + + return shapeRenderFlag; +} + +void LLFloaterPathfindingConsole::registerSavedSettingsListeners() +{ + if (!mSavedSettingRetrieveNeighborSlot.connected()) + { + mSavedSettingRetrieveNeighborSlot = gSavedSettings.getControl(CONTROL_NAME_RETRIEVE_NEIGHBOR)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleRetrieveNeighborChange, this, _1, _2)); + } + if (!mSavedSettingWalkableSlot.connected()) + { + mSavedSettingWalkableSlot = gSavedSettings.getControl(CONTROL_NAME_WALKABLE_OBJECTS)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); + } + if (!mSavedSettingStaticObstacleSlot.connected()) + { + mSavedSettingStaticObstacleSlot = gSavedSettings.getControl(CONTROL_NAME_STATIC_OBSTACLE_OBJECTS)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); + } + if (!mSavedSettingMaterialVolumeSlot.connected()) + { + mSavedSettingMaterialVolumeSlot = gSavedSettings.getControl(CONTROL_NAME_MATERIAL_VOLUMES)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); + } + if (!mSavedSettingExclusionVolumeSlot.connected()) + { + mSavedSettingExclusionVolumeSlot = gSavedSettings.getControl(CONTROL_NAME_EXCLUSION_VOLUMES)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); + } + if (!mSavedSettingInteriorEdgeSlot.connected()) + { + mSavedSettingInteriorEdgeSlot = gSavedSettings.getControl(CONTROL_NAME_INTERIOR_EDGE)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); + } + if (!mSavedSettingExteriorEdgeSlot.connected()) + { + mSavedSettingExteriorEdgeSlot = gSavedSettings.getControl(CONTROL_NAME_EXTERIOR_EDGE)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); + } + if (!mSavedSettingHeatmapMinSlot.connected()) + { + mSavedSettingHeatmapMinSlot = gSavedSettings.getControl(CONTROL_NAME_HEATMAP_MIN)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); + } + if (!mSavedSettingHeatmapMaxSlot.connected()) + { + mSavedSettingHeatmapMaxSlot = gSavedSettings.getControl(CONTROL_NAME_HEATMAP_MAX)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); + } + if (!mSavedSettingNavMeshFaceSlot.connected()) + { + mSavedSettingNavMeshFaceSlot = gSavedSettings.getControl(CONTROL_NAME_NAVMESH_FACE)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); + } + if (!mSavedSettingTestPathValidEndSlot.connected()) + { + mSavedSettingTestPathValidEndSlot = gSavedSettings.getControl(CONTROL_NAME_TEST_PATH_VALID_END)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); + } + if (!mSavedSettingTestPathInvalidEndSlot.connected()) + { + mSavedSettingTestPathInvalidEndSlot = gSavedSettings.getControl(CONTROL_NAME_TEST_PATH_INVALID_END)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); + } + if (!mSavedSettingTestPathSlot.connected()) + { + mSavedSettingTestPathSlot = gSavedSettings.getControl(CONTROL_NAME_TEST_PATH)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); + } + if (!mSavedSettingWaterSlot.connected()) + { + mSavedSettingWaterSlot = gSavedSettings.getControl(CONTROL_NAME_WATER)->getSignal()->connect(boost::bind(&LLFloaterPathfindingConsole::handleNavMeshColorChange, this, _1, _2)); + } +} + +void LLFloaterPathfindingConsole::deregisterSavedSettingsListeners() +{ + if (mSavedSettingRetrieveNeighborSlot.connected()) + { + mSavedSettingRetrieveNeighborSlot.disconnect(); + } + if (mSavedSettingWalkableSlot.connected()) + { + mSavedSettingWalkableSlot.disconnect(); + } + if (mSavedSettingStaticObstacleSlot.connected()) + { + mSavedSettingStaticObstacleSlot.disconnect(); + } + if (mSavedSettingMaterialVolumeSlot.connected()) + { + mSavedSettingMaterialVolumeSlot.disconnect(); + } + if (mSavedSettingExclusionVolumeSlot.connected()) + { + mSavedSettingExclusionVolumeSlot.disconnect(); + } + if (mSavedSettingInteriorEdgeSlot.connected()) + { + mSavedSettingInteriorEdgeSlot.disconnect(); + } + if (mSavedSettingExteriorEdgeSlot.connected()) + { + mSavedSettingExteriorEdgeSlot.disconnect(); + } + if (mSavedSettingHeatmapMinSlot.connected()) + { + mSavedSettingHeatmapMinSlot.disconnect(); + } + if (mSavedSettingHeatmapMaxSlot.connected()) + { + mSavedSettingHeatmapMaxSlot.disconnect(); + } + if (mSavedSettingNavMeshFaceSlot.connected()) + { + mSavedSettingNavMeshFaceSlot.disconnect(); + } + if (mSavedSettingTestPathValidEndSlot.connected()) + { + mSavedSettingTestPathValidEndSlot.disconnect(); + } + if (mSavedSettingTestPathInvalidEndSlot.connected()) + { + mSavedSettingTestPathInvalidEndSlot.disconnect(); + } + if (mSavedSettingTestPathSlot.connected()) + { + mSavedSettingTestPathSlot.disconnect(); + } + if (mSavedSettingWaterSlot.connected()) + { + mSavedSettingWaterSlot.disconnect(); + } +} + +void LLFloaterPathfindingConsole::handleRetrieveNeighborChange(LLControlVariable *pControl, const LLSD &pNewValue) +{ + initializeNavMeshZoneForCurrentRegion(); +} + +void LLFloaterPathfindingConsole::handleNavMeshColorChange(LLControlVariable *pControl, const LLSD &pNewValue) +{ + fillInColorsForNavMeshVisualization(); +} + +void LLFloaterPathfindingConsole::fillInColorsForNavMeshVisualization() +{ + if (LLPathingLib::getInstance() != NULL) + { + LLPathingLib::NavMeshColors navMeshColors; + + LLColor4 in = gSavedSettings.getColor4(CONTROL_NAME_WALKABLE_OBJECTS); + navMeshColors.mWalkable= LLColor4U(in); + + in = gSavedSettings.getColor4(CONTROL_NAME_STATIC_OBSTACLE_OBJECTS); + navMeshColors.mObstacle= LLColor4U(in); + + in = gSavedSettings.getColor4(CONTROL_NAME_MATERIAL_VOLUMES); + navMeshColors.mMaterial= LLColor4U(in); + + in = gSavedSettings.getColor4(CONTROL_NAME_EXCLUSION_VOLUMES); + navMeshColors.mExclusion= LLColor4U(in); + + in = gSavedSettings.getColor4(CONTROL_NAME_INTERIOR_EDGE); + navMeshColors.mConnectedEdge= LLColor4U(in); + + in = gSavedSettings.getColor4(CONTROL_NAME_EXTERIOR_EDGE); + navMeshColors.mBoundaryEdge= LLColor4U(in); + + navMeshColors.mHeatColorBase = gSavedSettings.getColor4(CONTROL_NAME_HEATMAP_MIN); + + navMeshColors.mHeatColorMax = gSavedSettings.getColor4(CONTROL_NAME_HEATMAP_MAX); + + in = gSavedSettings.getColor4(CONTROL_NAME_NAVMESH_FACE); + navMeshColors.mFaceColor= LLColor4U(in); + + in = gSavedSettings.getColor4(CONTROL_NAME_TEST_PATH_VALID_END); + navMeshColors.mStarValid= LLColor4U(in); + + in = gSavedSettings.getColor4(CONTROL_NAME_TEST_PATH_INVALID_END); + navMeshColors.mStarInvalid= LLColor4U(in); + + in = gSavedSettings.getColor4(CONTROL_NAME_TEST_PATH); + navMeshColors.mTestPath= LLColor4U(in); + + in = gSavedSettings.getColor4(CONTROL_NAME_WATER); + navMeshColors.mWaterColor= LLColor4U(in); + + LLPathingLib::getInstance()->setNavMeshColors(navMeshColors); + } +} diff --git a/indra/newview/llfloaterpathfindingconsole.h b/indra/newview/llfloaterpathfindingconsole.h index 7b8d9c820b..bcbc94e10e 100644 --- a/indra/newview/llfloaterpathfindingconsole.h +++ b/indra/newview/llfloaterpathfindingconsole.h @@ -1,220 +1,220 @@ -/** -* @file llfloaterpathfindingconsole.h -* @brief "Pathfinding console" floater, allowing for viewing and testing of the pathfinding navmesh through Havok AI utilities. -* @author Stinson@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ -#ifndef LL_LLFLOATERPATHFINDINGCONSOLE_H -#define LL_LLFLOATERPATHFINDINGCONSOLE_H - -#include - -#include - -#include "llfloater.h" -#include "llhandle.h" -#include "llpathfindingnavmeshzone.h" -#include "llpathfindingpathtool.h" -#include "llpathinglib.h" -#include "v4color.h" - -class LLButton; -class LLCheckBoxCtrl; -class LLComboBox; -class LLControlVariable; -class LLPanel; -class LLSD; -class LLSliderCtrl; -class LLTabContainer; -class LLTextBase; -class LLToolset; - -class LLFloaterPathfindingConsole -: public LLFloater -{ - friend class LLFloaterReg; - -public: - virtual bool postBuild(); - virtual void onOpen(const LLSD& pKey); - virtual void onClose(bool pIsAppQuitting); - - static LLHandle getInstanceHandle(); - - bool isRenderNavMesh() const; - void setRenderNavMesh(bool pIsRenderNavMesh); - - bool isRenderWalkables() const; - void setRenderWalkables(bool pIsRenderWalkables); - - bool isRenderStaticObstacles() const; - void setRenderStaticObstacles(bool pIsRenderStaticObstacles); - - bool isRenderMaterialVolumes() const; - void setRenderMaterialVolumes(bool pIsRenderMaterialVolumes); - - bool isRenderExclusionVolumes() const; - void setRenderExclusionVolumes(bool pIsRenderExclusionVolumes); - - bool isRenderWorld() const; - void setRenderWorld(bool pIsRenderWorld); - - bool isRenderWorldMovablesOnly() const; - void setRenderWorldMovablesOnly(bool pIsRenderWorldMovablesOnly); - - bool isRenderWaterPlane() const; - void setRenderWaterPlane(bool pIsRenderWaterPlane); - - bool isRenderXRay() const; - void setRenderXRay(bool pIsRenderXRay); - - bool isRenderAnyShapes() const; - U32 getRenderShapeFlags(); - - LLPathingLib::LLPLCharacterType getRenderHeatmapType() const; - void setRenderHeatmapType(LLPathingLib::LLPLCharacterType pRenderHeatmapType); - void onRegionBoundaryCross(); -protected: - -private: - typedef enum - { - kConsoleStateUnknown, - kConsoleStateLibraryNotImplemented, - kConsoleStateRegionNotEnabled, - kConsoleStateRegionLoading, - kConsoleStateCheckingVersion, - kConsoleStateDownloading, - kConsoleStateHasNavMesh, - kConsoleStateError - } EConsoleState; - - // Does its own instance management, so clients not allowed - // to allocate or destroy. - LLFloaterPathfindingConsole(const LLSD& pSeed); - virtual ~LLFloaterPathfindingConsole(); - - void onTabSwitch(); - void onShowWorldSet(); - void onShowWorldMovablesOnlySet(); - void onShowNavMeshSet(); - void onShowWalkabilitySet(); - void onCharacterWidthSet(); - void onCharacterTypeSwitch(); - void onClearPathClicked(); - - void handleNavMeshZoneStatus(LLPathfindingNavMeshZone::ENavMeshZoneRequestStatus pNavMeshZoneRequestStatus); - - void onPathEvent(); - - void setDefaultInputs(); - void setConsoleState(EConsoleState pConsoleState); - void setWorldRenderState(); - void setNavMeshRenderState(); - void updateRenderablesObjects(); - - void updateControlsOnConsoleState(); - void updateViewerStatusOnConsoleState(); - void updateSimulatorStatusOnConsoleState(); - - void initializeNavMeshZoneForCurrentRegion(); - - void switchIntoTestPathMode(); - void switchOutOfTestPathMode(); - void updateCharacterWidth(); - void updateCharacterType(); - void clearPath(); - void updatePathTestStatus(); - - void registerSavedSettingsListeners(); - void deregisterSavedSettingsListeners(); - void handleRetrieveNeighborChange(LLControlVariable *pControl, const LLSD &pNewValue); - void handleNavMeshColorChange(LLControlVariable *pControl, const LLSD &pNewValue); - void fillInColorsForNavMeshVisualization(); - void cleanupRenderableRestoreItems(); - - LLRootHandle mSelfHandle; - LLTabContainer *mViewTestTabContainer; - LLPanel *mViewTab; - LLTextBase *mShowLabel; - LLCheckBoxCtrl *mShowWorldCheckBox; - LLCheckBoxCtrl *mShowWorldMovablesOnlyCheckBox; - LLCheckBoxCtrl *mShowNavMeshCheckBox; - LLTextBase *mShowNavMeshWalkabilityLabel; - LLComboBox *mShowNavMeshWalkabilityComboBox; - LLCheckBoxCtrl *mShowWalkablesCheckBox; - LLCheckBoxCtrl *mShowStaticObstaclesCheckBox; - LLCheckBoxCtrl *mShowMaterialVolumesCheckBox; - LLCheckBoxCtrl *mShowExclusionVolumesCheckBox; - LLCheckBoxCtrl *mShowRenderWaterPlaneCheckBox; - LLCheckBoxCtrl *mShowXRayCheckBox; - LLTextBase *mPathfindingViewerStatus; - LLTextBase *mPathfindingSimulatorStatus; - LLPanel *mTestTab; - LLTextBase *mCtrlClickLabel; - LLTextBase *mShiftClickLabel; - LLTextBase *mCharacterWidthLabel; - LLTextBase *mCharacterWidthUnitLabel; - LLSliderCtrl *mCharacterWidthSlider; - LLTextBase *mCharacterTypeLabel; - LLComboBox *mCharacterTypeComboBox; - LLTextBase *mPathTestingStatus; - LLButton *mClearPathButton; - - LLColor4 mErrorColor; - LLColor4 mWarningColor; - - LLPathfindingNavMeshZone::navmesh_zone_slot_t mNavMeshZoneSlot; - LLPathfindingNavMeshZone mNavMeshZone; - bool mIsNavMeshUpdating; - - boost::signals2::connection mRegionBoundarySlot; - boost::signals2::connection mTeleportFailedSlot; - LLPathfindingPathTool::path_event_slot_t mPathEventSlot; - - LLToolset *mPathfindingToolset; - LLToolset *mSavedToolset; - - boost::signals2::connection mSavedSettingRetrieveNeighborSlot; - boost::signals2::connection mSavedSettingWalkableSlot; - boost::signals2::connection mSavedSettingStaticObstacleSlot; - boost::signals2::connection mSavedSettingMaterialVolumeSlot; - boost::signals2::connection mSavedSettingExclusionVolumeSlot; - boost::signals2::connection mSavedSettingInteriorEdgeSlot; - boost::signals2::connection mSavedSettingExteriorEdgeSlot; - boost::signals2::connection mSavedSettingHeatmapMinSlot; - boost::signals2::connection mSavedSettingHeatmapMaxSlot; - boost::signals2::connection mSavedSettingNavMeshFaceSlot; - boost::signals2::connection mSavedSettingTestPathValidEndSlot; - boost::signals2::connection mSavedSettingTestPathInvalidEndSlot; - boost::signals2::connection mSavedSettingTestPathSlot; - boost::signals2::connection mSavedSettingWaterSlot; - - EConsoleState mConsoleState; - - std::vector mRenderableRestoreList; - - static LLHandle sInstanceHandle; -}; - -#endif // LL_LLFLOATERPATHFINDINGCONSOLE_H +/** +* @file llfloaterpathfindingconsole.h +* @brief "Pathfinding console" floater, allowing for viewing and testing of the pathfinding navmesh through Havok AI utilities. +* @author Stinson@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LL_LLFLOATERPATHFINDINGCONSOLE_H +#define LL_LLFLOATERPATHFINDINGCONSOLE_H + +#include + +#include + +#include "llfloater.h" +#include "llhandle.h" +#include "llpathfindingnavmeshzone.h" +#include "llpathfindingpathtool.h" +#include "llpathinglib.h" +#include "v4color.h" + +class LLButton; +class LLCheckBoxCtrl; +class LLComboBox; +class LLControlVariable; +class LLPanel; +class LLSD; +class LLSliderCtrl; +class LLTabContainer; +class LLTextBase; +class LLToolset; + +class LLFloaterPathfindingConsole +: public LLFloater +{ + friend class LLFloaterReg; + +public: + virtual bool postBuild(); + virtual void onOpen(const LLSD& pKey); + virtual void onClose(bool pIsAppQuitting); + + static LLHandle getInstanceHandle(); + + bool isRenderNavMesh() const; + void setRenderNavMesh(bool pIsRenderNavMesh); + + bool isRenderWalkables() const; + void setRenderWalkables(bool pIsRenderWalkables); + + bool isRenderStaticObstacles() const; + void setRenderStaticObstacles(bool pIsRenderStaticObstacles); + + bool isRenderMaterialVolumes() const; + void setRenderMaterialVolumes(bool pIsRenderMaterialVolumes); + + bool isRenderExclusionVolumes() const; + void setRenderExclusionVolumes(bool pIsRenderExclusionVolumes); + + bool isRenderWorld() const; + void setRenderWorld(bool pIsRenderWorld); + + bool isRenderWorldMovablesOnly() const; + void setRenderWorldMovablesOnly(bool pIsRenderWorldMovablesOnly); + + bool isRenderWaterPlane() const; + void setRenderWaterPlane(bool pIsRenderWaterPlane); + + bool isRenderXRay() const; + void setRenderXRay(bool pIsRenderXRay); + + bool isRenderAnyShapes() const; + U32 getRenderShapeFlags(); + + LLPathingLib::LLPLCharacterType getRenderHeatmapType() const; + void setRenderHeatmapType(LLPathingLib::LLPLCharacterType pRenderHeatmapType); + void onRegionBoundaryCross(); +protected: + +private: + typedef enum + { + kConsoleStateUnknown, + kConsoleStateLibraryNotImplemented, + kConsoleStateRegionNotEnabled, + kConsoleStateRegionLoading, + kConsoleStateCheckingVersion, + kConsoleStateDownloading, + kConsoleStateHasNavMesh, + kConsoleStateError + } EConsoleState; + + // Does its own instance management, so clients not allowed + // to allocate or destroy. + LLFloaterPathfindingConsole(const LLSD& pSeed); + virtual ~LLFloaterPathfindingConsole(); + + void onTabSwitch(); + void onShowWorldSet(); + void onShowWorldMovablesOnlySet(); + void onShowNavMeshSet(); + void onShowWalkabilitySet(); + void onCharacterWidthSet(); + void onCharacterTypeSwitch(); + void onClearPathClicked(); + + void handleNavMeshZoneStatus(LLPathfindingNavMeshZone::ENavMeshZoneRequestStatus pNavMeshZoneRequestStatus); + + void onPathEvent(); + + void setDefaultInputs(); + void setConsoleState(EConsoleState pConsoleState); + void setWorldRenderState(); + void setNavMeshRenderState(); + void updateRenderablesObjects(); + + void updateControlsOnConsoleState(); + void updateViewerStatusOnConsoleState(); + void updateSimulatorStatusOnConsoleState(); + + void initializeNavMeshZoneForCurrentRegion(); + + void switchIntoTestPathMode(); + void switchOutOfTestPathMode(); + void updateCharacterWidth(); + void updateCharacterType(); + void clearPath(); + void updatePathTestStatus(); + + void registerSavedSettingsListeners(); + void deregisterSavedSettingsListeners(); + void handleRetrieveNeighborChange(LLControlVariable *pControl, const LLSD &pNewValue); + void handleNavMeshColorChange(LLControlVariable *pControl, const LLSD &pNewValue); + void fillInColorsForNavMeshVisualization(); + void cleanupRenderableRestoreItems(); + + LLRootHandle mSelfHandle; + LLTabContainer *mViewTestTabContainer; + LLPanel *mViewTab; + LLTextBase *mShowLabel; + LLCheckBoxCtrl *mShowWorldCheckBox; + LLCheckBoxCtrl *mShowWorldMovablesOnlyCheckBox; + LLCheckBoxCtrl *mShowNavMeshCheckBox; + LLTextBase *mShowNavMeshWalkabilityLabel; + LLComboBox *mShowNavMeshWalkabilityComboBox; + LLCheckBoxCtrl *mShowWalkablesCheckBox; + LLCheckBoxCtrl *mShowStaticObstaclesCheckBox; + LLCheckBoxCtrl *mShowMaterialVolumesCheckBox; + LLCheckBoxCtrl *mShowExclusionVolumesCheckBox; + LLCheckBoxCtrl *mShowRenderWaterPlaneCheckBox; + LLCheckBoxCtrl *mShowXRayCheckBox; + LLTextBase *mPathfindingViewerStatus; + LLTextBase *mPathfindingSimulatorStatus; + LLPanel *mTestTab; + LLTextBase *mCtrlClickLabel; + LLTextBase *mShiftClickLabel; + LLTextBase *mCharacterWidthLabel; + LLTextBase *mCharacterWidthUnitLabel; + LLSliderCtrl *mCharacterWidthSlider; + LLTextBase *mCharacterTypeLabel; + LLComboBox *mCharacterTypeComboBox; + LLTextBase *mPathTestingStatus; + LLButton *mClearPathButton; + + LLColor4 mErrorColor; + LLColor4 mWarningColor; + + LLPathfindingNavMeshZone::navmesh_zone_slot_t mNavMeshZoneSlot; + LLPathfindingNavMeshZone mNavMeshZone; + bool mIsNavMeshUpdating; + + boost::signals2::connection mRegionBoundarySlot; + boost::signals2::connection mTeleportFailedSlot; + LLPathfindingPathTool::path_event_slot_t mPathEventSlot; + + LLToolset *mPathfindingToolset; + LLToolset *mSavedToolset; + + boost::signals2::connection mSavedSettingRetrieveNeighborSlot; + boost::signals2::connection mSavedSettingWalkableSlot; + boost::signals2::connection mSavedSettingStaticObstacleSlot; + boost::signals2::connection mSavedSettingMaterialVolumeSlot; + boost::signals2::connection mSavedSettingExclusionVolumeSlot; + boost::signals2::connection mSavedSettingInteriorEdgeSlot; + boost::signals2::connection mSavedSettingExteriorEdgeSlot; + boost::signals2::connection mSavedSettingHeatmapMinSlot; + boost::signals2::connection mSavedSettingHeatmapMaxSlot; + boost::signals2::connection mSavedSettingNavMeshFaceSlot; + boost::signals2::connection mSavedSettingTestPathValidEndSlot; + boost::signals2::connection mSavedSettingTestPathInvalidEndSlot; + boost::signals2::connection mSavedSettingTestPathSlot; + boost::signals2::connection mSavedSettingWaterSlot; + + EConsoleState mConsoleState; + + std::vector mRenderableRestoreList; + + static LLHandle sInstanceHandle; +}; + +#endif // LL_LLFLOATERPATHFINDINGCONSOLE_H diff --git a/indra/newview/llfloaterpathfindinglinksets.cpp b/indra/newview/llfloaterpathfindinglinksets.cpp index 67082beb71..7ed64383f0 100644 --- a/indra/newview/llfloaterpathfindinglinksets.cpp +++ b/indra/newview/llfloaterpathfindinglinksets.cpp @@ -1,801 +1,801 @@ -/** -* @file llfloaterpathfindinglinksets.cpp -* @brief "Pathfinding linksets" floater, allowing manipulation of the linksets on the current region. -* @author Stinson@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterpathfindinglinksets.h" - -#include - -#include - -#include "llagent.h" -#include "llbutton.h" -#include "llcombobox.h" -#include "llfloaterpathfindingobjects.h" -#include "llfloaterreg.h" -#include "lllineeditor.h" -#include "llnotificationsutil.h" -#include "llpathfindinglinkset.h" -#include "llpathfindinglinksetlist.h" -#include "llpathfindingmanager.h" -#include "llsearcheditor.h" -#include "llscrolllistitem.h" -#include "llsd.h" -#include "lltextbase.h" -#include "lltextvalidate.h" -#include "lluicolortable.h" -#include "lluictrl.h" -#include "v3math.h" -#include "v4color.h" - -#define XUI_LINKSET_USE_NONE 0 -#define XUI_LINKSET_USE_WALKABLE 1 -#define XUI_LINKSET_USE_STATIC_OBSTACLE 2 -#define XUI_LINKSET_USE_DYNAMIC_OBSTACLE 3 -#define XUI_LINKSET_USE_MATERIAL_VOLUME 4 -#define XUI_LINKSET_USE_EXCLUSION_VOLUME 5 -#define XUI_LINKSET_USE_DYNAMIC_PHANTOM 6 - -//--------------------------------------------------------------------------- -// LLFloaterPathfindingLinksets -//--------------------------------------------------------------------------- - -void LLFloaterPathfindingLinksets::openLinksetsWithSelectedObjects() -{ - LLFloaterPathfindingLinksets *linksetsFloater = LLFloaterReg::getTypedInstance("pathfinding_linksets"); - linksetsFloater->clearFilters(); - linksetsFloater->showFloaterWithSelectionObjects(); -} - -LLFloaterPathfindingLinksets::LLFloaterPathfindingLinksets(const LLSD& pSeed) - : LLFloaterPathfindingObjects(pSeed), - mFilterByName(NULL), - mFilterByDescription(NULL), - mFilterByLinksetUse(NULL), - mEditLinksetUse(NULL), - mEditLinksetUseWalkable(NULL), - mEditLinksetUseStaticObstacle(NULL), - mEditLinksetUseDynamicObstacle(NULL), - mEditLinksetUseMaterialVolume(NULL), - mEditLinksetUseExclusionVolume(NULL), - mEditLinksetUseDynamicPhantom(NULL), - mLabelWalkabilityCoefficients(NULL), - mLabelEditA(NULL), - mLabelSuggestedUseA(NULL), - mEditA(NULL), - mLabelEditB(NULL), - mLabelSuggestedUseB(NULL), - mEditB(NULL), - mLabelEditC(NULL), - mLabelSuggestedUseC(NULL), - mEditC(NULL), - mLabelEditD(NULL), - mLabelSuggestedUseD(NULL), - mEditD(NULL), - mApplyEditsButton(NULL), - mBeaconColor(), - mPreviousValueA(LLPathfindingLinkset::MAX_WALKABILITY_VALUE), - mPreviousValueB(LLPathfindingLinkset::MAX_WALKABILITY_VALUE), - mPreviousValueC(LLPathfindingLinkset::MAX_WALKABILITY_VALUE), - mPreviousValueD(LLPathfindingLinkset::MAX_WALKABILITY_VALUE) -{ -} - -LLFloaterPathfindingLinksets::~LLFloaterPathfindingLinksets() -{ -} - -bool LLFloaterPathfindingLinksets::postBuild() -{ - mBeaconColor = LLUIColorTable::getInstance()->getColor("PathfindingLinksetBeaconColor"); - - mFilterByName = getChild("filter_by_name"); - mFilterByName->setCommitCallback(boost::bind(&LLFloaterPathfindingLinksets::onApplyAllFilters, this)); - mFilterByName->setCommitOnFocusLost(true); - - mFilterByDescription = getChild("filter_by_description"); - mFilterByDescription->setCommitCallback(boost::bind(&LLFloaterPathfindingLinksets::onApplyAllFilters, this)); - mFilterByDescription->setCommitOnFocusLost(true); - - mFilterByLinksetUse = findChild("filter_by_linkset_use"); - llassert(mFilterByLinksetUse != NULL); - mFilterByLinksetUse->setCommitCallback(boost::bind(&LLFloaterPathfindingLinksets::onApplyAllFilters, this)); - - childSetAction("apply_filters", boost::bind(&LLFloaterPathfindingLinksets::onApplyAllFilters, this)); - childSetAction("clear_filters", boost::bind(&LLFloaterPathfindingLinksets::onClearFiltersClicked, this)); - - mEditLinksetUse = findChild("edit_linkset_use"); - llassert(mEditLinksetUse != NULL); - mEditLinksetUse->clearRows(); - - mEditLinksetUseUnset = mEditLinksetUse->addElement(buildLinksetUseScrollListData(getString("linkset_choose_use"), XUI_LINKSET_USE_NONE)); - llassert(mEditLinksetUseUnset != NULL); - - mEditLinksetUseWalkable = mEditLinksetUse->addElement(buildLinksetUseScrollListData(getLinksetUseString(LLPathfindingLinkset::kWalkable), XUI_LINKSET_USE_WALKABLE)); - llassert(mEditLinksetUseWalkable != NULL); - - mEditLinksetUseStaticObstacle = mEditLinksetUse->addElement(buildLinksetUseScrollListData(getLinksetUseString(LLPathfindingLinkset::kStaticObstacle), XUI_LINKSET_USE_STATIC_OBSTACLE)); - llassert(mEditLinksetUseStaticObstacle != NULL); - - mEditLinksetUseDynamicObstacle = mEditLinksetUse->addElement(buildLinksetUseScrollListData(getLinksetUseString(LLPathfindingLinkset::kDynamicObstacle), XUI_LINKSET_USE_DYNAMIC_OBSTACLE)); - llassert(mEditLinksetUseDynamicObstacle != NULL); - - mEditLinksetUseMaterialVolume = mEditLinksetUse->addElement(buildLinksetUseScrollListData(getLinksetUseString(LLPathfindingLinkset::kMaterialVolume), XUI_LINKSET_USE_MATERIAL_VOLUME)); - llassert(mEditLinksetUseMaterialVolume != NULL); - - mEditLinksetUseExclusionVolume = mEditLinksetUse->addElement(buildLinksetUseScrollListData(getLinksetUseString(LLPathfindingLinkset::kExclusionVolume), XUI_LINKSET_USE_EXCLUSION_VOLUME)); - llassert(mEditLinksetUseExclusionVolume != NULL); - - mEditLinksetUseDynamicPhantom = mEditLinksetUse->addElement(buildLinksetUseScrollListData(getLinksetUseString(LLPathfindingLinkset::kDynamicPhantom), XUI_LINKSET_USE_DYNAMIC_PHANTOM)); - llassert(mEditLinksetUseDynamicPhantom != NULL); - - mEditLinksetUse->selectFirstItem(); - - mLabelWalkabilityCoefficients = findChild("walkability_coefficients_label"); - llassert(mLabelWalkabilityCoefficients != NULL); - - mLabelEditA = findChild("edit_a_label"); - llassert(mLabelEditA != NULL); - - mLabelSuggestedUseA = findChild("suggested_use_a_label"); - llassert(mLabelSuggestedUseA != NULL); - - mEditA = findChild("edit_a_value"); - llassert(mEditA != NULL); - mEditA->setPrevalidate(LLTextValidate::validateNonNegativeS32); - mEditA->setCommitCallback(boost::bind(&LLFloaterPathfindingLinksets::onWalkabilityCoefficientEntered, this, _1, mPreviousValueA)); - - mLabelEditB = findChild("edit_b_label"); - llassert(mLabelEditB != NULL); - - mLabelSuggestedUseB = findChild("suggested_use_b_label"); - llassert(mLabelSuggestedUseB != NULL); - - mEditB = findChild("edit_b_value"); - llassert(mEditB != NULL); - mEditB->setPrevalidate(LLTextValidate::validateNonNegativeS32); - mEditB->setCommitCallback(boost::bind(&LLFloaterPathfindingLinksets::onWalkabilityCoefficientEntered, this, _1, mPreviousValueB)); - - mLabelEditC = findChild("edit_c_label"); - llassert(mLabelEditC != NULL); - - mLabelSuggestedUseC = findChild("suggested_use_c_label"); - llassert(mLabelSuggestedUseC != NULL); - - mEditC = findChild("edit_c_value"); - llassert(mEditC != NULL); - mEditC->setPrevalidate(LLTextValidate::validateNonNegativeS32); - mEditC->setCommitCallback(boost::bind(&LLFloaterPathfindingLinksets::onWalkabilityCoefficientEntered, this, _1, mPreviousValueC)); - - mLabelEditD = findChild("edit_d_label"); - llassert(mLabelEditD != NULL); - - mLabelSuggestedUseD = findChild("suggested_use_d_label"); - llassert(mLabelSuggestedUseD != NULL); - - mEditD = findChild("edit_d_value"); - llassert(mEditD != NULL); - mEditD->setPrevalidate(LLTextValidate::validateNonNegativeS32); - mEditD->setCommitCallback(boost::bind(&LLFloaterPathfindingLinksets::onWalkabilityCoefficientEntered, this, _1, mPreviousValueD)); - - mApplyEditsButton = findChild("apply_edit_values"); - llassert(mApplyEditsButton != NULL); - mApplyEditsButton->setCommitCallback(boost::bind(&LLFloaterPathfindingLinksets::onApplyChangesClicked, this)); - - return LLFloaterPathfindingObjects::postBuild(); -} - -void LLFloaterPathfindingLinksets::requestGetObjects() -{ - LLPathfindingManager::getInstance()->requestGetLinksets(getNewRequestId(), boost::bind(&LLFloaterPathfindingLinksets::handleNewObjectList, this, _1, _2, _3)); -} - -void LLFloaterPathfindingLinksets::buildObjectsScrollList(const LLPathfindingObjectListPtr pObjectListPtr) -{ - llassert(pObjectListPtr != NULL); - llassert(!pObjectListPtr->isEmpty()); - - std::string nameFilter = mFilterByName->getText(); - std::string descriptionFilter = mFilterByDescription->getText(); - LLPathfindingLinkset::ELinksetUse linksetUseFilter = getFilterLinksetUse(); - bool isFilteringName = !nameFilter.empty(); - bool isFilteringDescription = !descriptionFilter.empty(); - bool isFilteringLinksetUse = (linksetUseFilter != LLPathfindingLinkset::kUnknown); - - const LLVector3& avatarPosition = gAgent.getPositionAgent(); - - if (isFilteringName || isFilteringDescription || isFilteringLinksetUse) - { - LLStringUtil::toUpper(nameFilter); - LLStringUtil::toUpper(descriptionFilter); - for (LLPathfindingObjectList::const_iterator objectIter = pObjectListPtr->begin(); objectIter != pObjectListPtr->end(); ++objectIter) - { - const LLPathfindingObjectPtr objectPtr = objectIter->second; - const LLPathfindingLinkset *linksetPtr = dynamic_cast(objectPtr.get()); - llassert(linksetPtr != NULL); - - std::string linksetName = (linksetPtr->isTerrain() ? getString("linkset_terrain_name") : linksetPtr->getName()); - std::string linksetDescription = linksetPtr->getDescription(); - LLStringUtil::toUpper(linksetName); - LLStringUtil::toUpper(linksetDescription); - - if ((!isFilteringName || (linksetName.find(nameFilter) != std::string::npos)) && - (!isFilteringDescription || (linksetDescription.find(descriptionFilter) != std::string::npos)) && - (!isFilteringLinksetUse || (linksetPtr->getLinksetUse() == linksetUseFilter))) - { - LLSD scrollListItemData = buildLinksetScrollListItemData(linksetPtr, avatarPosition); - addObjectToScrollList(objectPtr, scrollListItemData); - } - } - } - else - { - for (LLPathfindingObjectList::const_iterator objectIter = pObjectListPtr->begin(); objectIter != pObjectListPtr->end(); ++objectIter) - { - const LLPathfindingObjectPtr objectPtr = objectIter->second; - const LLPathfindingLinkset *linksetPtr = dynamic_cast(objectPtr.get()); - llassert(linksetPtr != NULL); - - LLSD scrollListItemData = buildLinksetScrollListItemData(linksetPtr, avatarPosition); - addObjectToScrollList(objectPtr, scrollListItemData); - } - } -} - -void LLFloaterPathfindingLinksets::updateControlsOnScrollListChange() -{ - LLFloaterPathfindingObjects::updateControlsOnScrollListChange(); - updateEditFieldValues(); - updateStateOnEditFields(); - updateStateOnEditLinksetUse(); -} - -S32 LLFloaterPathfindingLinksets::getNameColumnIndex() const -{ - return 0; -} - -S32 LLFloaterPathfindingLinksets::getOwnerNameColumnIndex() const -{ - return 2; -} - -std::string LLFloaterPathfindingLinksets::getOwnerName(const LLPathfindingObject *pObject) const -{ - return (pObject->hasOwner() - ? (pObject->hasOwnerName() - ? (pObject->isGroupOwned() - ? (pObject->getOwnerName() + " " + getString("linkset_owner_group")) - : pObject->getOwnerName()) - : getString("linkset_owner_loading")) - : getString("linkset_owner_unknown")); -} - -const LLColor4 &LLFloaterPathfindingLinksets::getBeaconColor() const -{ - return mBeaconColor; -} - -LLPathfindingObjectListPtr LLFloaterPathfindingLinksets::getEmptyObjectList() const -{ - LLPathfindingObjectListPtr objectListPtr(new LLPathfindingLinksetList()); - return objectListPtr; -} - -void LLFloaterPathfindingLinksets::requestSetLinksets(LLPathfindingObjectListPtr pLinksetList, LLPathfindingLinkset::ELinksetUse pLinksetUse, S32 pA, S32 pB, S32 pC, S32 pD) -{ - LLPathfindingManager::getInstance()->requestSetLinksets(getNewRequestId(), pLinksetList, pLinksetUse, pA, pB, pC, pD, boost::bind(&LLFloaterPathfindingLinksets::handleUpdateObjectList, this, _1, _2, _3)); -} - -void LLFloaterPathfindingLinksets::onApplyAllFilters() -{ - rebuildObjectsScrollList(); -} - -void LLFloaterPathfindingLinksets::onClearFiltersClicked() -{ - clearFilters(); - rebuildObjectsScrollList(); -} - -void LLFloaterPathfindingLinksets::onWalkabilityCoefficientEntered(LLUICtrl *pUICtrl, LLSD &pPreviousValue) -{ - LLLineEditor *pLineEditor = static_cast(pUICtrl); - llassert(pLineEditor != NULL); - - const std::string &valueString = pLineEditor->getText(); - - S32 intValue; - LLSD value; - bool doResetValue = false; - - if (valueString.empty()) - { - value = pPreviousValue; - doResetValue = true; - } - else if (LLStringUtil::convertToS32(valueString, intValue)) - { - doResetValue = ((intValue < LLPathfindingLinkset::MIN_WALKABILITY_VALUE) || (intValue > LLPathfindingLinkset::MAX_WALKABILITY_VALUE)); - value = LLSD(llclamp(intValue, LLPathfindingLinkset::MIN_WALKABILITY_VALUE, LLPathfindingLinkset::MAX_WALKABILITY_VALUE)); - } - else - { - value = LLSD(LLPathfindingLinkset::MAX_WALKABILITY_VALUE); - doResetValue = true; - } - - if (doResetValue) - { - pLineEditor->setValue(value); - } - pPreviousValue = value; -} - -void LLFloaterPathfindingLinksets::onApplyChangesClicked() -{ - applyEdit(); -} - -void LLFloaterPathfindingLinksets::clearFilters() -{ - mFilterByName->clear(); - mFilterByDescription->clear(); - setFilterLinksetUse(LLPathfindingLinkset::kUnknown); -} - -void LLFloaterPathfindingLinksets::updateEditFieldValues() -{ - int numSelectedObjects = getNumSelectedObjects(); - if (numSelectedObjects <= 0) - { - mEditLinksetUse->selectFirstItem(); - mEditA->clear(); - mEditB->clear(); - mEditC->clear(); - mEditD->clear(); - } - else - { - LLPathfindingObjectPtr firstSelectedObjectPtr = getFirstSelectedObject(); - llassert(firstSelectedObjectPtr != NULL); - - const LLPathfindingLinkset *linkset = dynamic_cast(firstSelectedObjectPtr.get()); - - setEditLinksetUse(linkset->getLinksetUse()); - mPreviousValueA = LLSD(linkset->getWalkabilityCoefficientA()); - mPreviousValueB = LLSD(linkset->getWalkabilityCoefficientB()); - mPreviousValueC = LLSD(linkset->getWalkabilityCoefficientC()); - mPreviousValueD = LLSD(linkset->getWalkabilityCoefficientD()); - mEditA->setValue(mPreviousValueA); - mEditB->setValue(mPreviousValueB); - mEditC->setValue(mPreviousValueC); - mEditD->setValue(mPreviousValueD); - } -} - -LLSD LLFloaterPathfindingLinksets::buildLinksetScrollListItemData(const LLPathfindingLinkset *pLinksetPtr, const LLVector3 &pAvatarPosition) const -{ - llassert(pLinksetPtr != NULL); - LLSD columns = LLSD::emptyArray(); - - if (pLinksetPtr->isTerrain()) - { - columns[0]["column"] = "name"; - columns[0]["value"] = getString("linkset_terrain_name"); - - columns[1]["column"] = "description"; - columns[1]["value"] = getString("linkset_terrain_description"); - - columns[2]["column"] = "owner"; - columns[2]["value"] = getString("linkset_terrain_owner"); - - columns[3]["column"] = "scripted"; - columns[3]["value"] = getString("linkset_terrain_scripted"); - - columns[4]["column"] = "land_impact"; - columns[4]["value"] = getString("linkset_terrain_land_impact"); - - columns[5]["column"] = "dist_from_you"; - columns[5]["value"] = getString("linkset_terrain_dist_from_you"); - } - else - { - columns[0]["column"] = "name"; - columns[0]["value"] = pLinksetPtr->getName(); - - columns[1]["column"] = "description"; - columns[1]["value"] = pLinksetPtr->getDescription(); - - columns[2]["column"] = "owner"; - columns[2]["value"] = getOwnerName(pLinksetPtr); - - columns[3]["column"] = "scripted"; - columns[3]["value"] = (pLinksetPtr->hasIsScripted() - ? (pLinksetPtr->isScripted() - ? getString("linkset_is_scripted") - : getString("linkset_is_not_scripted")) - : getString("linkset_is_unknown_scripted")); - - columns[4]["column"] = "land_impact"; - columns[4]["value"] = llformat("%1d", pLinksetPtr->getLandImpact()); - - columns[5]["column"] = "dist_from_you"; - columns[5]["value"] = llformat("%1.0f m", dist_vec(pAvatarPosition, pLinksetPtr->getLocation())); - } - - columns[6]["column"] = "linkset_use"; - std::string linksetUse = getLinksetUseString(pLinksetPtr->getLinksetUse()); - if (pLinksetPtr->isTerrain()) - { - linksetUse += (" " + getString("linkset_is_terrain")); - } - else if (!pLinksetPtr->isModifiable() && pLinksetPtr->canBeVolume()) - { - linksetUse += (" " + getString("linkset_is_restricted_state")); - } - else if (pLinksetPtr->isModifiable() && !pLinksetPtr->canBeVolume()) - { - linksetUse += (" " + getString("linkset_is_non_volume_state")); - } - else if (!pLinksetPtr->isModifiable() && !pLinksetPtr->canBeVolume()) - { - linksetUse += (" " + getString("linkset_is_restricted_non_volume_state")); - } - columns[6]["value"] = linksetUse; - - columns[7]["column"] = "a_percent"; - columns[7]["value"] = llformat("%3d", pLinksetPtr->getWalkabilityCoefficientA()); - - columns[8]["column"] = "b_percent"; - columns[8]["value"] = llformat("%3d", pLinksetPtr->getWalkabilityCoefficientB()); - - columns[9]["column"] = "c_percent"; - columns[9]["value"] = llformat("%3d", pLinksetPtr->getWalkabilityCoefficientC()); - - columns[10]["column"] = "d_percent"; - columns[10]["value"] = llformat("%3d", pLinksetPtr->getWalkabilityCoefficientD()); - - return columns; -} - -LLSD LLFloaterPathfindingLinksets::buildLinksetUseScrollListData(const std::string &pLabel, S32 pValue) const -{ - LLSD columns; - - columns[0]["column"] = "name"; - columns[0]["value"] = pLabel; - columns[0]["font"] = "SANSSERIF"; - - LLSD element; - element["value"] = pValue; - element["column"] = columns; - - return element; -} - -bool LLFloaterPathfindingLinksets::isShowUnmodifiablePhantomWarning(LLPathfindingLinkset::ELinksetUse pLinksetUse) const -{ - bool isShowWarning = false; - - if (pLinksetUse != LLPathfindingLinkset::kUnknown) - { - LLPathfindingObjectListPtr selectedObjects = getSelectedObjects(); - if ((selectedObjects != NULL) && !selectedObjects->isEmpty()) - { - const LLPathfindingLinksetList *linksetList = dynamic_cast(selectedObjects.get()); - isShowWarning = linksetList->isShowUnmodifiablePhantomWarning(pLinksetUse); - } - } - - return isShowWarning; -} - -bool LLFloaterPathfindingLinksets::isShowPhantomToggleWarning(LLPathfindingLinkset::ELinksetUse pLinksetUse) const -{ - bool isShowWarning = false; - - if (pLinksetUse != LLPathfindingLinkset::kUnknown) - { - LLPathfindingObjectListPtr selectedObjects = getSelectedObjects(); - if ((selectedObjects != NULL) && !selectedObjects->isEmpty()) - { - const LLPathfindingLinksetList *linksetList = dynamic_cast(selectedObjects.get()); - isShowWarning = linksetList->isShowPhantomToggleWarning(pLinksetUse); - } - } - - return isShowWarning; -} - -bool LLFloaterPathfindingLinksets::isShowCannotBeVolumeWarning(LLPathfindingLinkset::ELinksetUse pLinksetUse) const -{ - bool isShowWarning = false; - - if (pLinksetUse != LLPathfindingLinkset::kUnknown) - { - LLPathfindingObjectListPtr selectedObjects = getSelectedObjects(); - if ((selectedObjects != NULL) && !selectedObjects->isEmpty()) - { - const LLPathfindingLinksetList *linksetList = dynamic_cast(selectedObjects.get()); - isShowWarning = linksetList->isShowCannotBeVolumeWarning(pLinksetUse); - } - } - - return isShowWarning; -} - -void LLFloaterPathfindingLinksets::updateStateOnEditFields() -{ - int numSelectedItems = getNumSelectedObjects(); - bool isEditEnabled = (numSelectedItems > 0); - - mEditLinksetUse->setEnabled(isEditEnabled); - - mLabelWalkabilityCoefficients->setEnabled(isEditEnabled); - mLabelEditA->setEnabled(isEditEnabled); - mLabelEditB->setEnabled(isEditEnabled); - mLabelEditC->setEnabled(isEditEnabled); - mLabelEditD->setEnabled(isEditEnabled); - mLabelSuggestedUseA->setEnabled(isEditEnabled); - mLabelSuggestedUseB->setEnabled(isEditEnabled); - mLabelSuggestedUseC->setEnabled(isEditEnabled); - mLabelSuggestedUseD->setEnabled(isEditEnabled); - mEditA->setEnabled(isEditEnabled); - mEditB->setEnabled(isEditEnabled); - mEditC->setEnabled(isEditEnabled); - mEditD->setEnabled(isEditEnabled); - - mApplyEditsButton->setEnabled(isEditEnabled && (getMessagingState() == kMessagingComplete)); -} - -void LLFloaterPathfindingLinksets::updateStateOnEditLinksetUse() -{ - bool useWalkable = false; - bool useStaticObstacle = false; - bool useDynamicObstacle = false; - bool useMaterialVolume = false; - bool useExclusionVolume = false; - bool useDynamicPhantom = false; - - LLPathfindingObjectListPtr selectedObjects = getSelectedObjects(); - if ((selectedObjects != NULL) && !selectedObjects->isEmpty()) - { - const LLPathfindingLinksetList *linksetList = dynamic_cast(selectedObjects.get()); - linksetList->determinePossibleStates(useWalkable, useStaticObstacle, useDynamicObstacle, useMaterialVolume, useExclusionVolume, useDynamicPhantom); - } - - mEditLinksetUseWalkable->setEnabled(useWalkable); - mEditLinksetUseStaticObstacle->setEnabled(useStaticObstacle); - mEditLinksetUseDynamicObstacle->setEnabled(useDynamicObstacle); - mEditLinksetUseMaterialVolume->setEnabled(useMaterialVolume); - mEditLinksetUseExclusionVolume->setEnabled(useExclusionVolume); - mEditLinksetUseDynamicPhantom->setEnabled(useDynamicPhantom); -} - -void LLFloaterPathfindingLinksets::applyEdit() -{ - LLPathfindingLinkset::ELinksetUse linksetUse = getEditLinksetUse(); - - bool showPhantomToggleWarning = isShowPhantomToggleWarning(linksetUse); - bool showUnmodifiablePhantomWarning = isShowUnmodifiablePhantomWarning(linksetUse); - bool showCannotBeVolumeWarning = isShowCannotBeVolumeWarning(linksetUse); - - if (showPhantomToggleWarning || showUnmodifiablePhantomWarning || showCannotBeVolumeWarning) - { - LLPathfindingLinkset::ELinksetUse restrictedLinksetUse = LLPathfindingLinkset::getLinksetUseWithToggledPhantom(linksetUse); - LLSD substitutions; - substitutions["REQUESTED_TYPE"] = getLinksetUseString(linksetUse); - substitutions["RESTRICTED_TYPE"] = getLinksetUseString(restrictedLinksetUse); - - // Build one of the following notifications names - // - PathfindingLinksets_WarnOnPhantom - // - PathfindingLinksets_WarnOnPhantom_MismatchOnRestricted - // - PathfindingLinksets_WarnOnPhantom_MismatchOnVolume - // - PathfindingLinksets_WarnOnPhantom_MismatchOnRestricted_MismatchOnVolume - // - PathfindingLinksets_MismatchOnRestricted - // - PathfindingLinksets_MismatchOnVolume - // - PathfindingLinksets_MismatchOnRestricted_MismatchOnVolume - - std::string notificationName = "PathfindingLinksets"; - - if (showPhantomToggleWarning) - { - notificationName += "_WarnOnPhantom"; - } - if (showUnmodifiablePhantomWarning) - { - notificationName += "_MismatchOnRestricted"; - } - if (showCannotBeVolumeWarning) - { - notificationName += "_MismatchOnVolume"; - } - - LLNotificationsUtil::add(notificationName, substitutions, LLSD(), boost::bind(&LLFloaterPathfindingLinksets::handleApplyEdit, this, _1, _2)); - } - else - { - doApplyEdit(); - } -} - -void LLFloaterPathfindingLinksets::handleApplyEdit(const LLSD &pNotification, const LLSD &pResponse) -{ - if (LLNotificationsUtil::getSelectedOption(pNotification, pResponse) == 0) - { - doApplyEdit(); - } -} - -void LLFloaterPathfindingLinksets::doApplyEdit() -{ - LLPathfindingObjectListPtr selectedObjects = getSelectedObjects(); - if ((selectedObjects != NULL) && !selectedObjects->isEmpty()) - { - LLPathfindingLinkset::ELinksetUse linksetUse = getEditLinksetUse(); - const std::string &aString = mEditA->getText(); - const std::string &bString = mEditB->getText(); - const std::string &cString = mEditC->getText(); - const std::string &dString = mEditD->getText(); - S32 aValue = static_cast(atoi(aString.c_str())); - S32 bValue = static_cast(atoi(bString.c_str())); - S32 cValue = static_cast(atoi(cString.c_str())); - S32 dValue = static_cast(atoi(dString.c_str())); - - - requestSetLinksets(selectedObjects, linksetUse, aValue, bValue, cValue, dValue); - } -} - -std::string LLFloaterPathfindingLinksets::getLinksetUseString(LLPathfindingLinkset::ELinksetUse pLinksetUse) const -{ - std::string linksetUse; - - switch (pLinksetUse) - { - case LLPathfindingLinkset::kWalkable : - linksetUse = getString("linkset_use_walkable"); - break; - case LLPathfindingLinkset::kStaticObstacle : - linksetUse = getString("linkset_use_static_obstacle"); - break; - case LLPathfindingLinkset::kDynamicObstacle : - linksetUse = getString("linkset_use_dynamic_obstacle"); - break; - case LLPathfindingLinkset::kMaterialVolume : - linksetUse = getString("linkset_use_material_volume"); - break; - case LLPathfindingLinkset::kExclusionVolume : - linksetUse = getString("linkset_use_exclusion_volume"); - break; - case LLPathfindingLinkset::kDynamicPhantom : - linksetUse = getString("linkset_use_dynamic_phantom"); - break; - case LLPathfindingLinkset::kUnknown : - default : - linksetUse = getString("linkset_use_dynamic_obstacle"); - llassert(0); - break; - } - - return linksetUse; -} - -LLPathfindingLinkset::ELinksetUse LLFloaterPathfindingLinksets::getFilterLinksetUse() const -{ - return convertToLinksetUse(mFilterByLinksetUse->getValue()); -} - -void LLFloaterPathfindingLinksets::setFilterLinksetUse(LLPathfindingLinkset::ELinksetUse pLinksetUse) -{ - mFilterByLinksetUse->setValue(convertToXuiValue(pLinksetUse)); -} - -LLPathfindingLinkset::ELinksetUse LLFloaterPathfindingLinksets::getEditLinksetUse() const -{ - return convertToLinksetUse(mEditLinksetUse->getValue()); -} - -void LLFloaterPathfindingLinksets::setEditLinksetUse(LLPathfindingLinkset::ELinksetUse pLinksetUse) -{ - mEditLinksetUse->setValue(convertToXuiValue(pLinksetUse)); -} - -LLPathfindingLinkset::ELinksetUse LLFloaterPathfindingLinksets::convertToLinksetUse(LLSD pXuiValue) const -{ - LLPathfindingLinkset::ELinksetUse linkUse; - - switch (pXuiValue.asInteger()) - { - case XUI_LINKSET_USE_NONE : - linkUse = LLPathfindingLinkset::kUnknown; - break; - case XUI_LINKSET_USE_WALKABLE : - linkUse = LLPathfindingLinkset::kWalkable; - break; - case XUI_LINKSET_USE_STATIC_OBSTACLE : - linkUse = LLPathfindingLinkset::kStaticObstacle; - break; - case XUI_LINKSET_USE_DYNAMIC_OBSTACLE : - linkUse = LLPathfindingLinkset::kDynamicObstacle; - break; - case XUI_LINKSET_USE_MATERIAL_VOLUME : - linkUse = LLPathfindingLinkset::kMaterialVolume; - break; - case XUI_LINKSET_USE_EXCLUSION_VOLUME : - linkUse = LLPathfindingLinkset::kExclusionVolume; - break; - case XUI_LINKSET_USE_DYNAMIC_PHANTOM : - linkUse = LLPathfindingLinkset::kDynamicPhantom; - break; - default : - linkUse = LLPathfindingLinkset::kUnknown; - llassert(0); - break; - } - - return linkUse; -} - -LLSD LLFloaterPathfindingLinksets::convertToXuiValue(LLPathfindingLinkset::ELinksetUse pLinksetUse) const -{ - LLSD xuiValue; - - switch (pLinksetUse) - { - case LLPathfindingLinkset::kUnknown : - xuiValue = XUI_LINKSET_USE_NONE; - break; - case LLPathfindingLinkset::kWalkable : - xuiValue = XUI_LINKSET_USE_WALKABLE; - break; - case LLPathfindingLinkset::kStaticObstacle : - xuiValue = XUI_LINKSET_USE_STATIC_OBSTACLE; - break; - case LLPathfindingLinkset::kDynamicObstacle : - xuiValue = XUI_LINKSET_USE_DYNAMIC_OBSTACLE; - break; - case LLPathfindingLinkset::kMaterialVolume : - xuiValue = XUI_LINKSET_USE_MATERIAL_VOLUME; - break; - case LLPathfindingLinkset::kExclusionVolume : - xuiValue = XUI_LINKSET_USE_EXCLUSION_VOLUME; - break; - case LLPathfindingLinkset::kDynamicPhantom : - xuiValue = XUI_LINKSET_USE_DYNAMIC_PHANTOM; - break; - default : - xuiValue = XUI_LINKSET_USE_NONE; - llassert(0); - break; - } - - return xuiValue; -} +/** +* @file llfloaterpathfindinglinksets.cpp +* @brief "Pathfinding linksets" floater, allowing manipulation of the linksets on the current region. +* @author Stinson@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterpathfindinglinksets.h" + +#include + +#include + +#include "llagent.h" +#include "llbutton.h" +#include "llcombobox.h" +#include "llfloaterpathfindingobjects.h" +#include "llfloaterreg.h" +#include "lllineeditor.h" +#include "llnotificationsutil.h" +#include "llpathfindinglinkset.h" +#include "llpathfindinglinksetlist.h" +#include "llpathfindingmanager.h" +#include "llsearcheditor.h" +#include "llscrolllistitem.h" +#include "llsd.h" +#include "lltextbase.h" +#include "lltextvalidate.h" +#include "lluicolortable.h" +#include "lluictrl.h" +#include "v3math.h" +#include "v4color.h" + +#define XUI_LINKSET_USE_NONE 0 +#define XUI_LINKSET_USE_WALKABLE 1 +#define XUI_LINKSET_USE_STATIC_OBSTACLE 2 +#define XUI_LINKSET_USE_DYNAMIC_OBSTACLE 3 +#define XUI_LINKSET_USE_MATERIAL_VOLUME 4 +#define XUI_LINKSET_USE_EXCLUSION_VOLUME 5 +#define XUI_LINKSET_USE_DYNAMIC_PHANTOM 6 + +//--------------------------------------------------------------------------- +// LLFloaterPathfindingLinksets +//--------------------------------------------------------------------------- + +void LLFloaterPathfindingLinksets::openLinksetsWithSelectedObjects() +{ + LLFloaterPathfindingLinksets *linksetsFloater = LLFloaterReg::getTypedInstance("pathfinding_linksets"); + linksetsFloater->clearFilters(); + linksetsFloater->showFloaterWithSelectionObjects(); +} + +LLFloaterPathfindingLinksets::LLFloaterPathfindingLinksets(const LLSD& pSeed) + : LLFloaterPathfindingObjects(pSeed), + mFilterByName(NULL), + mFilterByDescription(NULL), + mFilterByLinksetUse(NULL), + mEditLinksetUse(NULL), + mEditLinksetUseWalkable(NULL), + mEditLinksetUseStaticObstacle(NULL), + mEditLinksetUseDynamicObstacle(NULL), + mEditLinksetUseMaterialVolume(NULL), + mEditLinksetUseExclusionVolume(NULL), + mEditLinksetUseDynamicPhantom(NULL), + mLabelWalkabilityCoefficients(NULL), + mLabelEditA(NULL), + mLabelSuggestedUseA(NULL), + mEditA(NULL), + mLabelEditB(NULL), + mLabelSuggestedUseB(NULL), + mEditB(NULL), + mLabelEditC(NULL), + mLabelSuggestedUseC(NULL), + mEditC(NULL), + mLabelEditD(NULL), + mLabelSuggestedUseD(NULL), + mEditD(NULL), + mApplyEditsButton(NULL), + mBeaconColor(), + mPreviousValueA(LLPathfindingLinkset::MAX_WALKABILITY_VALUE), + mPreviousValueB(LLPathfindingLinkset::MAX_WALKABILITY_VALUE), + mPreviousValueC(LLPathfindingLinkset::MAX_WALKABILITY_VALUE), + mPreviousValueD(LLPathfindingLinkset::MAX_WALKABILITY_VALUE) +{ +} + +LLFloaterPathfindingLinksets::~LLFloaterPathfindingLinksets() +{ +} + +bool LLFloaterPathfindingLinksets::postBuild() +{ + mBeaconColor = LLUIColorTable::getInstance()->getColor("PathfindingLinksetBeaconColor"); + + mFilterByName = getChild("filter_by_name"); + mFilterByName->setCommitCallback(boost::bind(&LLFloaterPathfindingLinksets::onApplyAllFilters, this)); + mFilterByName->setCommitOnFocusLost(true); + + mFilterByDescription = getChild("filter_by_description"); + mFilterByDescription->setCommitCallback(boost::bind(&LLFloaterPathfindingLinksets::onApplyAllFilters, this)); + mFilterByDescription->setCommitOnFocusLost(true); + + mFilterByLinksetUse = findChild("filter_by_linkset_use"); + llassert(mFilterByLinksetUse != NULL); + mFilterByLinksetUse->setCommitCallback(boost::bind(&LLFloaterPathfindingLinksets::onApplyAllFilters, this)); + + childSetAction("apply_filters", boost::bind(&LLFloaterPathfindingLinksets::onApplyAllFilters, this)); + childSetAction("clear_filters", boost::bind(&LLFloaterPathfindingLinksets::onClearFiltersClicked, this)); + + mEditLinksetUse = findChild("edit_linkset_use"); + llassert(mEditLinksetUse != NULL); + mEditLinksetUse->clearRows(); + + mEditLinksetUseUnset = mEditLinksetUse->addElement(buildLinksetUseScrollListData(getString("linkset_choose_use"), XUI_LINKSET_USE_NONE)); + llassert(mEditLinksetUseUnset != NULL); + + mEditLinksetUseWalkable = mEditLinksetUse->addElement(buildLinksetUseScrollListData(getLinksetUseString(LLPathfindingLinkset::kWalkable), XUI_LINKSET_USE_WALKABLE)); + llassert(mEditLinksetUseWalkable != NULL); + + mEditLinksetUseStaticObstacle = mEditLinksetUse->addElement(buildLinksetUseScrollListData(getLinksetUseString(LLPathfindingLinkset::kStaticObstacle), XUI_LINKSET_USE_STATIC_OBSTACLE)); + llassert(mEditLinksetUseStaticObstacle != NULL); + + mEditLinksetUseDynamicObstacle = mEditLinksetUse->addElement(buildLinksetUseScrollListData(getLinksetUseString(LLPathfindingLinkset::kDynamicObstacle), XUI_LINKSET_USE_DYNAMIC_OBSTACLE)); + llassert(mEditLinksetUseDynamicObstacle != NULL); + + mEditLinksetUseMaterialVolume = mEditLinksetUse->addElement(buildLinksetUseScrollListData(getLinksetUseString(LLPathfindingLinkset::kMaterialVolume), XUI_LINKSET_USE_MATERIAL_VOLUME)); + llassert(mEditLinksetUseMaterialVolume != NULL); + + mEditLinksetUseExclusionVolume = mEditLinksetUse->addElement(buildLinksetUseScrollListData(getLinksetUseString(LLPathfindingLinkset::kExclusionVolume), XUI_LINKSET_USE_EXCLUSION_VOLUME)); + llassert(mEditLinksetUseExclusionVolume != NULL); + + mEditLinksetUseDynamicPhantom = mEditLinksetUse->addElement(buildLinksetUseScrollListData(getLinksetUseString(LLPathfindingLinkset::kDynamicPhantom), XUI_LINKSET_USE_DYNAMIC_PHANTOM)); + llassert(mEditLinksetUseDynamicPhantom != NULL); + + mEditLinksetUse->selectFirstItem(); + + mLabelWalkabilityCoefficients = findChild("walkability_coefficients_label"); + llassert(mLabelWalkabilityCoefficients != NULL); + + mLabelEditA = findChild("edit_a_label"); + llassert(mLabelEditA != NULL); + + mLabelSuggestedUseA = findChild("suggested_use_a_label"); + llassert(mLabelSuggestedUseA != NULL); + + mEditA = findChild("edit_a_value"); + llassert(mEditA != NULL); + mEditA->setPrevalidate(LLTextValidate::validateNonNegativeS32); + mEditA->setCommitCallback(boost::bind(&LLFloaterPathfindingLinksets::onWalkabilityCoefficientEntered, this, _1, mPreviousValueA)); + + mLabelEditB = findChild("edit_b_label"); + llassert(mLabelEditB != NULL); + + mLabelSuggestedUseB = findChild("suggested_use_b_label"); + llassert(mLabelSuggestedUseB != NULL); + + mEditB = findChild("edit_b_value"); + llassert(mEditB != NULL); + mEditB->setPrevalidate(LLTextValidate::validateNonNegativeS32); + mEditB->setCommitCallback(boost::bind(&LLFloaterPathfindingLinksets::onWalkabilityCoefficientEntered, this, _1, mPreviousValueB)); + + mLabelEditC = findChild("edit_c_label"); + llassert(mLabelEditC != NULL); + + mLabelSuggestedUseC = findChild("suggested_use_c_label"); + llassert(mLabelSuggestedUseC != NULL); + + mEditC = findChild("edit_c_value"); + llassert(mEditC != NULL); + mEditC->setPrevalidate(LLTextValidate::validateNonNegativeS32); + mEditC->setCommitCallback(boost::bind(&LLFloaterPathfindingLinksets::onWalkabilityCoefficientEntered, this, _1, mPreviousValueC)); + + mLabelEditD = findChild("edit_d_label"); + llassert(mLabelEditD != NULL); + + mLabelSuggestedUseD = findChild("suggested_use_d_label"); + llassert(mLabelSuggestedUseD != NULL); + + mEditD = findChild("edit_d_value"); + llassert(mEditD != NULL); + mEditD->setPrevalidate(LLTextValidate::validateNonNegativeS32); + mEditD->setCommitCallback(boost::bind(&LLFloaterPathfindingLinksets::onWalkabilityCoefficientEntered, this, _1, mPreviousValueD)); + + mApplyEditsButton = findChild("apply_edit_values"); + llassert(mApplyEditsButton != NULL); + mApplyEditsButton->setCommitCallback(boost::bind(&LLFloaterPathfindingLinksets::onApplyChangesClicked, this)); + + return LLFloaterPathfindingObjects::postBuild(); +} + +void LLFloaterPathfindingLinksets::requestGetObjects() +{ + LLPathfindingManager::getInstance()->requestGetLinksets(getNewRequestId(), boost::bind(&LLFloaterPathfindingLinksets::handleNewObjectList, this, _1, _2, _3)); +} + +void LLFloaterPathfindingLinksets::buildObjectsScrollList(const LLPathfindingObjectListPtr pObjectListPtr) +{ + llassert(pObjectListPtr != NULL); + llassert(!pObjectListPtr->isEmpty()); + + std::string nameFilter = mFilterByName->getText(); + std::string descriptionFilter = mFilterByDescription->getText(); + LLPathfindingLinkset::ELinksetUse linksetUseFilter = getFilterLinksetUse(); + bool isFilteringName = !nameFilter.empty(); + bool isFilteringDescription = !descriptionFilter.empty(); + bool isFilteringLinksetUse = (linksetUseFilter != LLPathfindingLinkset::kUnknown); + + const LLVector3& avatarPosition = gAgent.getPositionAgent(); + + if (isFilteringName || isFilteringDescription || isFilteringLinksetUse) + { + LLStringUtil::toUpper(nameFilter); + LLStringUtil::toUpper(descriptionFilter); + for (LLPathfindingObjectList::const_iterator objectIter = pObjectListPtr->begin(); objectIter != pObjectListPtr->end(); ++objectIter) + { + const LLPathfindingObjectPtr objectPtr = objectIter->second; + const LLPathfindingLinkset *linksetPtr = dynamic_cast(objectPtr.get()); + llassert(linksetPtr != NULL); + + std::string linksetName = (linksetPtr->isTerrain() ? getString("linkset_terrain_name") : linksetPtr->getName()); + std::string linksetDescription = linksetPtr->getDescription(); + LLStringUtil::toUpper(linksetName); + LLStringUtil::toUpper(linksetDescription); + + if ((!isFilteringName || (linksetName.find(nameFilter) != std::string::npos)) && + (!isFilteringDescription || (linksetDescription.find(descriptionFilter) != std::string::npos)) && + (!isFilteringLinksetUse || (linksetPtr->getLinksetUse() == linksetUseFilter))) + { + LLSD scrollListItemData = buildLinksetScrollListItemData(linksetPtr, avatarPosition); + addObjectToScrollList(objectPtr, scrollListItemData); + } + } + } + else + { + for (LLPathfindingObjectList::const_iterator objectIter = pObjectListPtr->begin(); objectIter != pObjectListPtr->end(); ++objectIter) + { + const LLPathfindingObjectPtr objectPtr = objectIter->second; + const LLPathfindingLinkset *linksetPtr = dynamic_cast(objectPtr.get()); + llassert(linksetPtr != NULL); + + LLSD scrollListItemData = buildLinksetScrollListItemData(linksetPtr, avatarPosition); + addObjectToScrollList(objectPtr, scrollListItemData); + } + } +} + +void LLFloaterPathfindingLinksets::updateControlsOnScrollListChange() +{ + LLFloaterPathfindingObjects::updateControlsOnScrollListChange(); + updateEditFieldValues(); + updateStateOnEditFields(); + updateStateOnEditLinksetUse(); +} + +S32 LLFloaterPathfindingLinksets::getNameColumnIndex() const +{ + return 0; +} + +S32 LLFloaterPathfindingLinksets::getOwnerNameColumnIndex() const +{ + return 2; +} + +std::string LLFloaterPathfindingLinksets::getOwnerName(const LLPathfindingObject *pObject) const +{ + return (pObject->hasOwner() + ? (pObject->hasOwnerName() + ? (pObject->isGroupOwned() + ? (pObject->getOwnerName() + " " + getString("linkset_owner_group")) + : pObject->getOwnerName()) + : getString("linkset_owner_loading")) + : getString("linkset_owner_unknown")); +} + +const LLColor4 &LLFloaterPathfindingLinksets::getBeaconColor() const +{ + return mBeaconColor; +} + +LLPathfindingObjectListPtr LLFloaterPathfindingLinksets::getEmptyObjectList() const +{ + LLPathfindingObjectListPtr objectListPtr(new LLPathfindingLinksetList()); + return objectListPtr; +} + +void LLFloaterPathfindingLinksets::requestSetLinksets(LLPathfindingObjectListPtr pLinksetList, LLPathfindingLinkset::ELinksetUse pLinksetUse, S32 pA, S32 pB, S32 pC, S32 pD) +{ + LLPathfindingManager::getInstance()->requestSetLinksets(getNewRequestId(), pLinksetList, pLinksetUse, pA, pB, pC, pD, boost::bind(&LLFloaterPathfindingLinksets::handleUpdateObjectList, this, _1, _2, _3)); +} + +void LLFloaterPathfindingLinksets::onApplyAllFilters() +{ + rebuildObjectsScrollList(); +} + +void LLFloaterPathfindingLinksets::onClearFiltersClicked() +{ + clearFilters(); + rebuildObjectsScrollList(); +} + +void LLFloaterPathfindingLinksets::onWalkabilityCoefficientEntered(LLUICtrl *pUICtrl, LLSD &pPreviousValue) +{ + LLLineEditor *pLineEditor = static_cast(pUICtrl); + llassert(pLineEditor != NULL); + + const std::string &valueString = pLineEditor->getText(); + + S32 intValue; + LLSD value; + bool doResetValue = false; + + if (valueString.empty()) + { + value = pPreviousValue; + doResetValue = true; + } + else if (LLStringUtil::convertToS32(valueString, intValue)) + { + doResetValue = ((intValue < LLPathfindingLinkset::MIN_WALKABILITY_VALUE) || (intValue > LLPathfindingLinkset::MAX_WALKABILITY_VALUE)); + value = LLSD(llclamp(intValue, LLPathfindingLinkset::MIN_WALKABILITY_VALUE, LLPathfindingLinkset::MAX_WALKABILITY_VALUE)); + } + else + { + value = LLSD(LLPathfindingLinkset::MAX_WALKABILITY_VALUE); + doResetValue = true; + } + + if (doResetValue) + { + pLineEditor->setValue(value); + } + pPreviousValue = value; +} + +void LLFloaterPathfindingLinksets::onApplyChangesClicked() +{ + applyEdit(); +} + +void LLFloaterPathfindingLinksets::clearFilters() +{ + mFilterByName->clear(); + mFilterByDescription->clear(); + setFilterLinksetUse(LLPathfindingLinkset::kUnknown); +} + +void LLFloaterPathfindingLinksets::updateEditFieldValues() +{ + int numSelectedObjects = getNumSelectedObjects(); + if (numSelectedObjects <= 0) + { + mEditLinksetUse->selectFirstItem(); + mEditA->clear(); + mEditB->clear(); + mEditC->clear(); + mEditD->clear(); + } + else + { + LLPathfindingObjectPtr firstSelectedObjectPtr = getFirstSelectedObject(); + llassert(firstSelectedObjectPtr != NULL); + + const LLPathfindingLinkset *linkset = dynamic_cast(firstSelectedObjectPtr.get()); + + setEditLinksetUse(linkset->getLinksetUse()); + mPreviousValueA = LLSD(linkset->getWalkabilityCoefficientA()); + mPreviousValueB = LLSD(linkset->getWalkabilityCoefficientB()); + mPreviousValueC = LLSD(linkset->getWalkabilityCoefficientC()); + mPreviousValueD = LLSD(linkset->getWalkabilityCoefficientD()); + mEditA->setValue(mPreviousValueA); + mEditB->setValue(mPreviousValueB); + mEditC->setValue(mPreviousValueC); + mEditD->setValue(mPreviousValueD); + } +} + +LLSD LLFloaterPathfindingLinksets::buildLinksetScrollListItemData(const LLPathfindingLinkset *pLinksetPtr, const LLVector3 &pAvatarPosition) const +{ + llassert(pLinksetPtr != NULL); + LLSD columns = LLSD::emptyArray(); + + if (pLinksetPtr->isTerrain()) + { + columns[0]["column"] = "name"; + columns[0]["value"] = getString("linkset_terrain_name"); + + columns[1]["column"] = "description"; + columns[1]["value"] = getString("linkset_terrain_description"); + + columns[2]["column"] = "owner"; + columns[2]["value"] = getString("linkset_terrain_owner"); + + columns[3]["column"] = "scripted"; + columns[3]["value"] = getString("linkset_terrain_scripted"); + + columns[4]["column"] = "land_impact"; + columns[4]["value"] = getString("linkset_terrain_land_impact"); + + columns[5]["column"] = "dist_from_you"; + columns[5]["value"] = getString("linkset_terrain_dist_from_you"); + } + else + { + columns[0]["column"] = "name"; + columns[0]["value"] = pLinksetPtr->getName(); + + columns[1]["column"] = "description"; + columns[1]["value"] = pLinksetPtr->getDescription(); + + columns[2]["column"] = "owner"; + columns[2]["value"] = getOwnerName(pLinksetPtr); + + columns[3]["column"] = "scripted"; + columns[3]["value"] = (pLinksetPtr->hasIsScripted() + ? (pLinksetPtr->isScripted() + ? getString("linkset_is_scripted") + : getString("linkset_is_not_scripted")) + : getString("linkset_is_unknown_scripted")); + + columns[4]["column"] = "land_impact"; + columns[4]["value"] = llformat("%1d", pLinksetPtr->getLandImpact()); + + columns[5]["column"] = "dist_from_you"; + columns[5]["value"] = llformat("%1.0f m", dist_vec(pAvatarPosition, pLinksetPtr->getLocation())); + } + + columns[6]["column"] = "linkset_use"; + std::string linksetUse = getLinksetUseString(pLinksetPtr->getLinksetUse()); + if (pLinksetPtr->isTerrain()) + { + linksetUse += (" " + getString("linkset_is_terrain")); + } + else if (!pLinksetPtr->isModifiable() && pLinksetPtr->canBeVolume()) + { + linksetUse += (" " + getString("linkset_is_restricted_state")); + } + else if (pLinksetPtr->isModifiable() && !pLinksetPtr->canBeVolume()) + { + linksetUse += (" " + getString("linkset_is_non_volume_state")); + } + else if (!pLinksetPtr->isModifiable() && !pLinksetPtr->canBeVolume()) + { + linksetUse += (" " + getString("linkset_is_restricted_non_volume_state")); + } + columns[6]["value"] = linksetUse; + + columns[7]["column"] = "a_percent"; + columns[7]["value"] = llformat("%3d", pLinksetPtr->getWalkabilityCoefficientA()); + + columns[8]["column"] = "b_percent"; + columns[8]["value"] = llformat("%3d", pLinksetPtr->getWalkabilityCoefficientB()); + + columns[9]["column"] = "c_percent"; + columns[9]["value"] = llformat("%3d", pLinksetPtr->getWalkabilityCoefficientC()); + + columns[10]["column"] = "d_percent"; + columns[10]["value"] = llformat("%3d", pLinksetPtr->getWalkabilityCoefficientD()); + + return columns; +} + +LLSD LLFloaterPathfindingLinksets::buildLinksetUseScrollListData(const std::string &pLabel, S32 pValue) const +{ + LLSD columns; + + columns[0]["column"] = "name"; + columns[0]["value"] = pLabel; + columns[0]["font"] = "SANSSERIF"; + + LLSD element; + element["value"] = pValue; + element["column"] = columns; + + return element; +} + +bool LLFloaterPathfindingLinksets::isShowUnmodifiablePhantomWarning(LLPathfindingLinkset::ELinksetUse pLinksetUse) const +{ + bool isShowWarning = false; + + if (pLinksetUse != LLPathfindingLinkset::kUnknown) + { + LLPathfindingObjectListPtr selectedObjects = getSelectedObjects(); + if ((selectedObjects != NULL) && !selectedObjects->isEmpty()) + { + const LLPathfindingLinksetList *linksetList = dynamic_cast(selectedObjects.get()); + isShowWarning = linksetList->isShowUnmodifiablePhantomWarning(pLinksetUse); + } + } + + return isShowWarning; +} + +bool LLFloaterPathfindingLinksets::isShowPhantomToggleWarning(LLPathfindingLinkset::ELinksetUse pLinksetUse) const +{ + bool isShowWarning = false; + + if (pLinksetUse != LLPathfindingLinkset::kUnknown) + { + LLPathfindingObjectListPtr selectedObjects = getSelectedObjects(); + if ((selectedObjects != NULL) && !selectedObjects->isEmpty()) + { + const LLPathfindingLinksetList *linksetList = dynamic_cast(selectedObjects.get()); + isShowWarning = linksetList->isShowPhantomToggleWarning(pLinksetUse); + } + } + + return isShowWarning; +} + +bool LLFloaterPathfindingLinksets::isShowCannotBeVolumeWarning(LLPathfindingLinkset::ELinksetUse pLinksetUse) const +{ + bool isShowWarning = false; + + if (pLinksetUse != LLPathfindingLinkset::kUnknown) + { + LLPathfindingObjectListPtr selectedObjects = getSelectedObjects(); + if ((selectedObjects != NULL) && !selectedObjects->isEmpty()) + { + const LLPathfindingLinksetList *linksetList = dynamic_cast(selectedObjects.get()); + isShowWarning = linksetList->isShowCannotBeVolumeWarning(pLinksetUse); + } + } + + return isShowWarning; +} + +void LLFloaterPathfindingLinksets::updateStateOnEditFields() +{ + int numSelectedItems = getNumSelectedObjects(); + bool isEditEnabled = (numSelectedItems > 0); + + mEditLinksetUse->setEnabled(isEditEnabled); + + mLabelWalkabilityCoefficients->setEnabled(isEditEnabled); + mLabelEditA->setEnabled(isEditEnabled); + mLabelEditB->setEnabled(isEditEnabled); + mLabelEditC->setEnabled(isEditEnabled); + mLabelEditD->setEnabled(isEditEnabled); + mLabelSuggestedUseA->setEnabled(isEditEnabled); + mLabelSuggestedUseB->setEnabled(isEditEnabled); + mLabelSuggestedUseC->setEnabled(isEditEnabled); + mLabelSuggestedUseD->setEnabled(isEditEnabled); + mEditA->setEnabled(isEditEnabled); + mEditB->setEnabled(isEditEnabled); + mEditC->setEnabled(isEditEnabled); + mEditD->setEnabled(isEditEnabled); + + mApplyEditsButton->setEnabled(isEditEnabled && (getMessagingState() == kMessagingComplete)); +} + +void LLFloaterPathfindingLinksets::updateStateOnEditLinksetUse() +{ + bool useWalkable = false; + bool useStaticObstacle = false; + bool useDynamicObstacle = false; + bool useMaterialVolume = false; + bool useExclusionVolume = false; + bool useDynamicPhantom = false; + + LLPathfindingObjectListPtr selectedObjects = getSelectedObjects(); + if ((selectedObjects != NULL) && !selectedObjects->isEmpty()) + { + const LLPathfindingLinksetList *linksetList = dynamic_cast(selectedObjects.get()); + linksetList->determinePossibleStates(useWalkable, useStaticObstacle, useDynamicObstacle, useMaterialVolume, useExclusionVolume, useDynamicPhantom); + } + + mEditLinksetUseWalkable->setEnabled(useWalkable); + mEditLinksetUseStaticObstacle->setEnabled(useStaticObstacle); + mEditLinksetUseDynamicObstacle->setEnabled(useDynamicObstacle); + mEditLinksetUseMaterialVolume->setEnabled(useMaterialVolume); + mEditLinksetUseExclusionVolume->setEnabled(useExclusionVolume); + mEditLinksetUseDynamicPhantom->setEnabled(useDynamicPhantom); +} + +void LLFloaterPathfindingLinksets::applyEdit() +{ + LLPathfindingLinkset::ELinksetUse linksetUse = getEditLinksetUse(); + + bool showPhantomToggleWarning = isShowPhantomToggleWarning(linksetUse); + bool showUnmodifiablePhantomWarning = isShowUnmodifiablePhantomWarning(linksetUse); + bool showCannotBeVolumeWarning = isShowCannotBeVolumeWarning(linksetUse); + + if (showPhantomToggleWarning || showUnmodifiablePhantomWarning || showCannotBeVolumeWarning) + { + LLPathfindingLinkset::ELinksetUse restrictedLinksetUse = LLPathfindingLinkset::getLinksetUseWithToggledPhantom(linksetUse); + LLSD substitutions; + substitutions["REQUESTED_TYPE"] = getLinksetUseString(linksetUse); + substitutions["RESTRICTED_TYPE"] = getLinksetUseString(restrictedLinksetUse); + + // Build one of the following notifications names + // - PathfindingLinksets_WarnOnPhantom + // - PathfindingLinksets_WarnOnPhantom_MismatchOnRestricted + // - PathfindingLinksets_WarnOnPhantom_MismatchOnVolume + // - PathfindingLinksets_WarnOnPhantom_MismatchOnRestricted_MismatchOnVolume + // - PathfindingLinksets_MismatchOnRestricted + // - PathfindingLinksets_MismatchOnVolume + // - PathfindingLinksets_MismatchOnRestricted_MismatchOnVolume + + std::string notificationName = "PathfindingLinksets"; + + if (showPhantomToggleWarning) + { + notificationName += "_WarnOnPhantom"; + } + if (showUnmodifiablePhantomWarning) + { + notificationName += "_MismatchOnRestricted"; + } + if (showCannotBeVolumeWarning) + { + notificationName += "_MismatchOnVolume"; + } + + LLNotificationsUtil::add(notificationName, substitutions, LLSD(), boost::bind(&LLFloaterPathfindingLinksets::handleApplyEdit, this, _1, _2)); + } + else + { + doApplyEdit(); + } +} + +void LLFloaterPathfindingLinksets::handleApplyEdit(const LLSD &pNotification, const LLSD &pResponse) +{ + if (LLNotificationsUtil::getSelectedOption(pNotification, pResponse) == 0) + { + doApplyEdit(); + } +} + +void LLFloaterPathfindingLinksets::doApplyEdit() +{ + LLPathfindingObjectListPtr selectedObjects = getSelectedObjects(); + if ((selectedObjects != NULL) && !selectedObjects->isEmpty()) + { + LLPathfindingLinkset::ELinksetUse linksetUse = getEditLinksetUse(); + const std::string &aString = mEditA->getText(); + const std::string &bString = mEditB->getText(); + const std::string &cString = mEditC->getText(); + const std::string &dString = mEditD->getText(); + S32 aValue = static_cast(atoi(aString.c_str())); + S32 bValue = static_cast(atoi(bString.c_str())); + S32 cValue = static_cast(atoi(cString.c_str())); + S32 dValue = static_cast(atoi(dString.c_str())); + + + requestSetLinksets(selectedObjects, linksetUse, aValue, bValue, cValue, dValue); + } +} + +std::string LLFloaterPathfindingLinksets::getLinksetUseString(LLPathfindingLinkset::ELinksetUse pLinksetUse) const +{ + std::string linksetUse; + + switch (pLinksetUse) + { + case LLPathfindingLinkset::kWalkable : + linksetUse = getString("linkset_use_walkable"); + break; + case LLPathfindingLinkset::kStaticObstacle : + linksetUse = getString("linkset_use_static_obstacle"); + break; + case LLPathfindingLinkset::kDynamicObstacle : + linksetUse = getString("linkset_use_dynamic_obstacle"); + break; + case LLPathfindingLinkset::kMaterialVolume : + linksetUse = getString("linkset_use_material_volume"); + break; + case LLPathfindingLinkset::kExclusionVolume : + linksetUse = getString("linkset_use_exclusion_volume"); + break; + case LLPathfindingLinkset::kDynamicPhantom : + linksetUse = getString("linkset_use_dynamic_phantom"); + break; + case LLPathfindingLinkset::kUnknown : + default : + linksetUse = getString("linkset_use_dynamic_obstacle"); + llassert(0); + break; + } + + return linksetUse; +} + +LLPathfindingLinkset::ELinksetUse LLFloaterPathfindingLinksets::getFilterLinksetUse() const +{ + return convertToLinksetUse(mFilterByLinksetUse->getValue()); +} + +void LLFloaterPathfindingLinksets::setFilterLinksetUse(LLPathfindingLinkset::ELinksetUse pLinksetUse) +{ + mFilterByLinksetUse->setValue(convertToXuiValue(pLinksetUse)); +} + +LLPathfindingLinkset::ELinksetUse LLFloaterPathfindingLinksets::getEditLinksetUse() const +{ + return convertToLinksetUse(mEditLinksetUse->getValue()); +} + +void LLFloaterPathfindingLinksets::setEditLinksetUse(LLPathfindingLinkset::ELinksetUse pLinksetUse) +{ + mEditLinksetUse->setValue(convertToXuiValue(pLinksetUse)); +} + +LLPathfindingLinkset::ELinksetUse LLFloaterPathfindingLinksets::convertToLinksetUse(LLSD pXuiValue) const +{ + LLPathfindingLinkset::ELinksetUse linkUse; + + switch (pXuiValue.asInteger()) + { + case XUI_LINKSET_USE_NONE : + linkUse = LLPathfindingLinkset::kUnknown; + break; + case XUI_LINKSET_USE_WALKABLE : + linkUse = LLPathfindingLinkset::kWalkable; + break; + case XUI_LINKSET_USE_STATIC_OBSTACLE : + linkUse = LLPathfindingLinkset::kStaticObstacle; + break; + case XUI_LINKSET_USE_DYNAMIC_OBSTACLE : + linkUse = LLPathfindingLinkset::kDynamicObstacle; + break; + case XUI_LINKSET_USE_MATERIAL_VOLUME : + linkUse = LLPathfindingLinkset::kMaterialVolume; + break; + case XUI_LINKSET_USE_EXCLUSION_VOLUME : + linkUse = LLPathfindingLinkset::kExclusionVolume; + break; + case XUI_LINKSET_USE_DYNAMIC_PHANTOM : + linkUse = LLPathfindingLinkset::kDynamicPhantom; + break; + default : + linkUse = LLPathfindingLinkset::kUnknown; + llassert(0); + break; + } + + return linkUse; +} + +LLSD LLFloaterPathfindingLinksets::convertToXuiValue(LLPathfindingLinkset::ELinksetUse pLinksetUse) const +{ + LLSD xuiValue; + + switch (pLinksetUse) + { + case LLPathfindingLinkset::kUnknown : + xuiValue = XUI_LINKSET_USE_NONE; + break; + case LLPathfindingLinkset::kWalkable : + xuiValue = XUI_LINKSET_USE_WALKABLE; + break; + case LLPathfindingLinkset::kStaticObstacle : + xuiValue = XUI_LINKSET_USE_STATIC_OBSTACLE; + break; + case LLPathfindingLinkset::kDynamicObstacle : + xuiValue = XUI_LINKSET_USE_DYNAMIC_OBSTACLE; + break; + case LLPathfindingLinkset::kMaterialVolume : + xuiValue = XUI_LINKSET_USE_MATERIAL_VOLUME; + break; + case LLPathfindingLinkset::kExclusionVolume : + xuiValue = XUI_LINKSET_USE_EXCLUSION_VOLUME; + break; + case LLPathfindingLinkset::kDynamicPhantom : + xuiValue = XUI_LINKSET_USE_DYNAMIC_PHANTOM; + break; + default : + xuiValue = XUI_LINKSET_USE_NONE; + llassert(0); + break; + } + + return xuiValue; +} diff --git a/indra/newview/llfloaterpathfindinglinksets.h b/indra/newview/llfloaterpathfindinglinksets.h index 96ce45dc67..ecf705d2d4 100644 --- a/indra/newview/llfloaterpathfindinglinksets.h +++ b/indra/newview/llfloaterpathfindinglinksets.h @@ -1,143 +1,143 @@ -/** -* @file llfloaterpathfindinglinksets.h -* @brief "Pathfinding linksets" floater, allowing manipulation of the linksets on the current region. -* @author Stinson@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ -#ifndef LL_LLFLOATERPATHFINDINGLINKSETS_H -#define LL_LLFLOATERPATHFINDINGLINKSETS_H - -#include - -#include "llfloaterpathfindingobjects.h" -#include "llpathfindinglinkset.h" -#include "llpathfindingobjectlist.h" -#include "v4color.h" - -class LLButton; -class LLComboBox; -class LLLineEditor; -class LLScrollListItem; -class LLSD; -class LLTextBase; -class LLUICtrl; -class LLVector3; -class LLSearchEditor; - -class LLFloaterPathfindingLinksets : public LLFloaterPathfindingObjects -{ -public: - static void openLinksetsWithSelectedObjects(); - -protected: - friend class LLFloaterReg; - - LLFloaterPathfindingLinksets(const LLSD& pSeed); - virtual ~LLFloaterPathfindingLinksets(); - - virtual bool postBuild(); - - virtual void requestGetObjects(); - - virtual void buildObjectsScrollList(const LLPathfindingObjectListPtr pObjectListPtr); - - virtual void updateControlsOnScrollListChange(); - - virtual S32 getNameColumnIndex() const; - virtual S32 getOwnerNameColumnIndex() const; - virtual std::string getOwnerName(const LLPathfindingObject *pObject) const; - virtual const LLColor4 &getBeaconColor() const; - - virtual LLPathfindingObjectListPtr getEmptyObjectList() const; - -private: - void requestSetLinksets(LLPathfindingObjectListPtr pLinksetList, LLPathfindingLinkset::ELinksetUse pLinksetUse, S32 pA, S32 pB, S32 pC, S32 pD); - - void onApplyAllFilters(); - void onClearFiltersClicked(); - void onWalkabilityCoefficientEntered(LLUICtrl *pUICtrl, LLSD &pPreviousValue); - void onApplyChangesClicked(); - - void clearFilters(); - - void updateEditFieldValues(); - LLSD buildLinksetScrollListItemData(const LLPathfindingLinkset *pLinksetPtr, const LLVector3 &pAvatarPosition) const; - LLSD buildLinksetUseScrollListData(const std::string &pLabel, S32 pValue) const; - - bool isShowUnmodifiablePhantomWarning(LLPathfindingLinkset::ELinksetUse pLinksetUse) const; - bool isShowPhantomToggleWarning(LLPathfindingLinkset::ELinksetUse pLinksetUse) const; - bool isShowCannotBeVolumeWarning(LLPathfindingLinkset::ELinksetUse pLinksetUse) const; - - void updateStateOnEditFields(); - void updateStateOnEditLinksetUse(); - - void applyEdit(); - void handleApplyEdit(const LLSD &pNotification, const LLSD &pResponse); - void doApplyEdit(); - - std::string getLinksetUseString(LLPathfindingLinkset::ELinksetUse pLinksetUse) const; - - LLPathfindingLinkset::ELinksetUse getFilterLinksetUse() const; - void setFilterLinksetUse(LLPathfindingLinkset::ELinksetUse pLinksetUse); - - LLPathfindingLinkset::ELinksetUse getEditLinksetUse() const; - void setEditLinksetUse(LLPathfindingLinkset::ELinksetUse pLinksetUse); - - LLPathfindingLinkset::ELinksetUse convertToLinksetUse(LLSD pXuiValue) const; - LLSD convertToXuiValue(LLPathfindingLinkset::ELinksetUse pLinksetUse) const; - - LLSearchEditor *mFilterByName; - LLSearchEditor *mFilterByDescription; - LLComboBox *mFilterByLinksetUse; - LLComboBox *mEditLinksetUse; - LLScrollListItem *mEditLinksetUseUnset; - LLScrollListItem *mEditLinksetUseWalkable; - LLScrollListItem *mEditLinksetUseStaticObstacle; - LLScrollListItem *mEditLinksetUseDynamicObstacle; - LLScrollListItem *mEditLinksetUseMaterialVolume; - LLScrollListItem *mEditLinksetUseExclusionVolume; - LLScrollListItem *mEditLinksetUseDynamicPhantom; - LLTextBase *mLabelWalkabilityCoefficients; - LLTextBase *mLabelEditA; - LLTextBase *mLabelSuggestedUseA; - LLLineEditor *mEditA; - LLTextBase *mLabelEditB; - LLTextBase *mLabelSuggestedUseB; - LLLineEditor *mEditB; - LLTextBase *mLabelEditC; - LLTextBase *mLabelSuggestedUseC; - LLLineEditor *mEditC; - LLTextBase *mLabelEditD; - LLTextBase *mLabelSuggestedUseD; - LLLineEditor *mEditD; - LLButton *mApplyEditsButton; - - LLColor4 mBeaconColor; - - LLSD mPreviousValueA; - LLSD mPreviousValueB; - LLSD mPreviousValueC; - LLSD mPreviousValueD; -}; - -#endif // LL_LLFLOATERPATHFINDINGLINKSETS_H +/** +* @file llfloaterpathfindinglinksets.h +* @brief "Pathfinding linksets" floater, allowing manipulation of the linksets on the current region. +* @author Stinson@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LL_LLFLOATERPATHFINDINGLINKSETS_H +#define LL_LLFLOATERPATHFINDINGLINKSETS_H + +#include + +#include "llfloaterpathfindingobjects.h" +#include "llpathfindinglinkset.h" +#include "llpathfindingobjectlist.h" +#include "v4color.h" + +class LLButton; +class LLComboBox; +class LLLineEditor; +class LLScrollListItem; +class LLSD; +class LLTextBase; +class LLUICtrl; +class LLVector3; +class LLSearchEditor; + +class LLFloaterPathfindingLinksets : public LLFloaterPathfindingObjects +{ +public: + static void openLinksetsWithSelectedObjects(); + +protected: + friend class LLFloaterReg; + + LLFloaterPathfindingLinksets(const LLSD& pSeed); + virtual ~LLFloaterPathfindingLinksets(); + + virtual bool postBuild(); + + virtual void requestGetObjects(); + + virtual void buildObjectsScrollList(const LLPathfindingObjectListPtr pObjectListPtr); + + virtual void updateControlsOnScrollListChange(); + + virtual S32 getNameColumnIndex() const; + virtual S32 getOwnerNameColumnIndex() const; + virtual std::string getOwnerName(const LLPathfindingObject *pObject) const; + virtual const LLColor4 &getBeaconColor() const; + + virtual LLPathfindingObjectListPtr getEmptyObjectList() const; + +private: + void requestSetLinksets(LLPathfindingObjectListPtr pLinksetList, LLPathfindingLinkset::ELinksetUse pLinksetUse, S32 pA, S32 pB, S32 pC, S32 pD); + + void onApplyAllFilters(); + void onClearFiltersClicked(); + void onWalkabilityCoefficientEntered(LLUICtrl *pUICtrl, LLSD &pPreviousValue); + void onApplyChangesClicked(); + + void clearFilters(); + + void updateEditFieldValues(); + LLSD buildLinksetScrollListItemData(const LLPathfindingLinkset *pLinksetPtr, const LLVector3 &pAvatarPosition) const; + LLSD buildLinksetUseScrollListData(const std::string &pLabel, S32 pValue) const; + + bool isShowUnmodifiablePhantomWarning(LLPathfindingLinkset::ELinksetUse pLinksetUse) const; + bool isShowPhantomToggleWarning(LLPathfindingLinkset::ELinksetUse pLinksetUse) const; + bool isShowCannotBeVolumeWarning(LLPathfindingLinkset::ELinksetUse pLinksetUse) const; + + void updateStateOnEditFields(); + void updateStateOnEditLinksetUse(); + + void applyEdit(); + void handleApplyEdit(const LLSD &pNotification, const LLSD &pResponse); + void doApplyEdit(); + + std::string getLinksetUseString(LLPathfindingLinkset::ELinksetUse pLinksetUse) const; + + LLPathfindingLinkset::ELinksetUse getFilterLinksetUse() const; + void setFilterLinksetUse(LLPathfindingLinkset::ELinksetUse pLinksetUse); + + LLPathfindingLinkset::ELinksetUse getEditLinksetUse() const; + void setEditLinksetUse(LLPathfindingLinkset::ELinksetUse pLinksetUse); + + LLPathfindingLinkset::ELinksetUse convertToLinksetUse(LLSD pXuiValue) const; + LLSD convertToXuiValue(LLPathfindingLinkset::ELinksetUse pLinksetUse) const; + + LLSearchEditor *mFilterByName; + LLSearchEditor *mFilterByDescription; + LLComboBox *mFilterByLinksetUse; + LLComboBox *mEditLinksetUse; + LLScrollListItem *mEditLinksetUseUnset; + LLScrollListItem *mEditLinksetUseWalkable; + LLScrollListItem *mEditLinksetUseStaticObstacle; + LLScrollListItem *mEditLinksetUseDynamicObstacle; + LLScrollListItem *mEditLinksetUseMaterialVolume; + LLScrollListItem *mEditLinksetUseExclusionVolume; + LLScrollListItem *mEditLinksetUseDynamicPhantom; + LLTextBase *mLabelWalkabilityCoefficients; + LLTextBase *mLabelEditA; + LLTextBase *mLabelSuggestedUseA; + LLLineEditor *mEditA; + LLTextBase *mLabelEditB; + LLTextBase *mLabelSuggestedUseB; + LLLineEditor *mEditB; + LLTextBase *mLabelEditC; + LLTextBase *mLabelSuggestedUseC; + LLLineEditor *mEditC; + LLTextBase *mLabelEditD; + LLTextBase *mLabelSuggestedUseD; + LLLineEditor *mEditD; + LLButton *mApplyEditsButton; + + LLColor4 mBeaconColor; + + LLSD mPreviousValueA; + LLSD mPreviousValueB; + LLSD mPreviousValueC; + LLSD mPreviousValueD; +}; + +#endif // LL_LLFLOATERPATHFINDINGLINKSETS_H diff --git a/indra/newview/llfloaterpathfindingobjects.cpp b/indra/newview/llfloaterpathfindingobjects.cpp index 844e95d4e6..b4452d4c46 100644 --- a/indra/newview/llfloaterpathfindingobjects.cpp +++ b/indra/newview/llfloaterpathfindingobjects.cpp @@ -1,890 +1,890 @@ -/** -* @file llfloaterpathfindingobjects.cpp -* @brief Base class for both the pathfinding linksets and characters floater. -* @author Stinson@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterpathfindingobjects.h" - -#include -#include -#include - -#include -#include - -#include "llagent.h" -#include "llavatarname.h" -#include "llavatarnamecache.h" -#include "llbutton.h" -#include "llcheckboxctrl.h" -#include "llfloater.h" -#include "llfontgl.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "llpathfindingmanager.h" -#include "llresmgr.h" -#include "llscrolllistcell.h" -#include "llscrolllistctrl.h" -#include "llscrolllistitem.h" -#include "llselectmgr.h" -#include "llsd.h" -#include "llstring.h" -#include "llstyle.h" -#include "lltextbase.h" -#include "lluicolortable.h" -#include "llviewermenu.h" -#include "llviewerobject.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "v3dmath.h" -#include "v3math.h" -#include "v4color.h" - -#define DEFAULT_BEACON_WIDTH 6 - -//--------------------------------------------------------------------------- -// LLFloaterPathfindingObjects -//--------------------------------------------------------------------------- - -void LLFloaterPathfindingObjects::onOpen(const LLSD &pKey) -{ - LLFloater::onOpen(pKey); - - selectNoneObjects(); - mObjectsScrollList->setCommitOnSelectionChange(true); - - if (!mSelectionUpdateSlot.connected()) - { - mSelectionUpdateSlot = LLSelectMgr::getInstance()->mUpdateSignal.connect(boost::bind(&LLFloaterPathfindingObjects::onInWorldSelectionListChanged, this)); - } - - if (!mRegionBoundaryCrossingSlot.connected()) - { - mRegionBoundaryCrossingSlot = gAgent.addRegionChangedCallback(boost::bind(&LLFloaterPathfindingObjects::onRegionBoundaryCrossed, this)); - } - - if (!mGodLevelChangeSlot.connected()) - { - mGodLevelChangeSlot = gAgent.registerGodLevelChanageListener(boost::bind(&LLFloaterPathfindingObjects::onGodLevelChange, this, _1)); - } - - requestGetObjects(); -} - -void LLFloaterPathfindingObjects::onClose(bool pIsAppQuitting) -{ - if (mGodLevelChangeSlot.connected()) - { - mGodLevelChangeSlot.disconnect(); - } - - if (mRegionBoundaryCrossingSlot.connected()) - { - mRegionBoundaryCrossingSlot.disconnect(); - } - - if (mSelectionUpdateSlot.connected()) - { - mSelectionUpdateSlot.disconnect(); - } - - mObjectsScrollList->setCommitOnSelectionChange(false); - selectNoneObjects(); - - if (mObjectsSelection.notNull()) - { - mObjectsSelection.clear(); - } - - if (pIsAppQuitting) - { - clearAllObjects(); - } -} - -void LLFloaterPathfindingObjects::draw() -{ - LLFloater::draw(); - - if (isShowBeacons()) - { - std::vector selectedItems = mObjectsScrollList->getAllSelected(); - if (!selectedItems.empty()) - { - int numSelectedItems = selectedItems.size(); - S32 nameColumnIndex = getNameColumnIndex(); - const LLColor4 &beaconColor = getBeaconColor(); - const LLColor4 &beaconTextColor = getBeaconTextColor(); - S32 beaconWidth = getBeaconWidth(); - - std::vector viewerObjects; - viewerObjects.reserve(numSelectedItems); - - for (std::vector::const_iterator selectedItemIter = selectedItems.begin(); - selectedItemIter != selectedItems.end(); ++selectedItemIter) - { - const LLScrollListItem *selectedItem = *selectedItemIter; - - LLViewerObject *viewerObject = gObjectList.findObject(selectedItem->getUUID()); - if (viewerObject != NULL) - { - const std::string &objectName = selectedItem->getColumn(nameColumnIndex)->getValue().asString(); - gObjectList.addDebugBeacon(viewerObject->getPositionAgent(), objectName, beaconColor, beaconTextColor, beaconWidth); - } - } - } - } -} - -LLFloaterPathfindingObjects::LLFloaterPathfindingObjects(const LLSD &pSeed) - : LLFloater(pSeed), - mObjectsScrollList(NULL), - mMessagingStatus(NULL), - mRefreshListButton(NULL), - mSelectAllButton(NULL), - mSelectNoneButton(NULL), - mShowBeaconCheckBox(NULL), - mTakeButton(NULL), - mTakeCopyButton(NULL), - mReturnButton(NULL), - mDeleteButton(NULL), - mTeleportButton(NULL), - mDefaultBeaconColor(), - mDefaultBeaconTextColor(), - mErrorTextColor(), - mWarningTextColor(), - mMessagingState(kMessagingUnknown), - mMessagingRequestId(0U), - mMissingNameObjectsScrollListItems(), - mObjectList(), - mObjectsSelection(), - mHasObjectsToBeSelected(false), - mObjectsToBeSelected(), - mSelectionUpdateSlot(), - mRegionBoundaryCrossingSlot() -{ -} - -LLFloaterPathfindingObjects::~LLFloaterPathfindingObjects() -{ - clearAllObjects(); -} - -bool LLFloaterPathfindingObjects::postBuild() -{ - mDefaultBeaconColor = LLUIColorTable::getInstance()->getColor("PathfindingDefaultBeaconColor"); - mDefaultBeaconTextColor = LLUIColorTable::getInstance()->getColor("PathfindingDefaultBeaconTextColor"); - mErrorTextColor = LLUIColorTable::getInstance()->getColor("PathfindingErrorColor"); - mWarningTextColor = LLUIColorTable::getInstance()->getColor("PathfindingWarningColor"); - - mObjectsScrollList = findChild("objects_scroll_list"); - llassert(mObjectsScrollList != NULL); - mObjectsScrollList->setCommitCallback(boost::bind(&LLFloaterPathfindingObjects::onScrollListSelectionChanged, this)); - mObjectsScrollList->sortByColumnIndex(static_cast(getNameColumnIndex()), true); - - mMessagingStatus = findChild("messaging_status"); - llassert(mMessagingStatus != NULL); - - mRefreshListButton = findChild("refresh_objects_list"); - llassert(mRefreshListButton != NULL); - mRefreshListButton->setCommitCallback(boost::bind(&LLFloaterPathfindingObjects::onRefreshObjectsClicked, this)); - - mSelectAllButton = findChild("select_all_objects"); - llassert(mSelectAllButton != NULL); - mSelectAllButton->setCommitCallback(boost::bind(&LLFloaterPathfindingObjects::onSelectAllObjectsClicked, this)); - - mSelectNoneButton = findChild("select_none_objects"); - llassert(mSelectNoneButton != NULL); - mSelectNoneButton->setCommitCallback(boost::bind(&LLFloaterPathfindingObjects::onSelectNoneObjectsClicked, this)); - - mShowBeaconCheckBox = findChild("show_beacon"); - llassert(mShowBeaconCheckBox != NULL); - - mTakeButton = findChild("take_objects"); - llassert(mTakeButton != NULL); - mTakeButton->setCommitCallback(boost::bind(&LLFloaterPathfindingObjects::onTakeClicked, this)); - - mTakeCopyButton = findChild("take_copy_objects"); - llassert(mTakeCopyButton != NULL); - mTakeCopyButton->setCommitCallback(boost::bind(&LLFloaterPathfindingObjects::onTakeCopyClicked, this)); - - mReturnButton = findChild("return_objects"); - llassert(mReturnButton != NULL); - mReturnButton->setCommitCallback(boost::bind(&LLFloaterPathfindingObjects::onReturnClicked, this)); - - mDeleteButton = findChild("delete_objects"); - llassert(mDeleteButton != NULL); - mDeleteButton->setCommitCallback(boost::bind(&LLFloaterPathfindingObjects::onDeleteClicked, this)); - - mTeleportButton = findChild("teleport_me_to_object"); - llassert(mTeleportButton != NULL); - mTeleportButton->setCommitCallback(boost::bind(&LLFloaterPathfindingObjects::onTeleportClicked, this)); - - return LLFloater::postBuild(); -} - -void LLFloaterPathfindingObjects::requestGetObjects() -{ - llassert(0); -} - -LLPathfindingManager::request_id_t LLFloaterPathfindingObjects::getNewRequestId() -{ - return ++mMessagingRequestId; -} - -void LLFloaterPathfindingObjects::handleNewObjectList(LLPathfindingManager::request_id_t pRequestId, LLPathfindingManager::ERequestStatus pRequestStatus, LLPathfindingObjectListPtr pObjectList) -{ - llassert(pRequestId <= mMessagingRequestId); - if (pRequestId == mMessagingRequestId) - { - switch (pRequestStatus) - { - case LLPathfindingManager::kRequestStarted : - setMessagingState(kMessagingGetRequestSent); - break; - case LLPathfindingManager::kRequestCompleted : - mObjectList = pObjectList; - rebuildObjectsScrollList(); - setMessagingState(kMessagingComplete); - break; - case LLPathfindingManager::kRequestNotEnabled : - clearAllObjects(); - setMessagingState(kMessagingNotEnabled); - break; - case LLPathfindingManager::kRequestError : - clearAllObjects(); - setMessagingState(kMessagingGetError); - break; - default : - clearAllObjects(); - setMessagingState(kMessagingGetError); - llassert(0); - break; - } - } -} - -void LLFloaterPathfindingObjects::handleUpdateObjectList(LLPathfindingManager::request_id_t pRequestId, LLPathfindingManager::ERequestStatus pRequestStatus, LLPathfindingObjectListPtr pObjectList) -{ - // We current assume that handleUpdateObjectList is called only when objects are being SET - llassert(pRequestId <= mMessagingRequestId); - if (pRequestId == mMessagingRequestId) - { - switch (pRequestStatus) - { - case LLPathfindingManager::kRequestStarted : - setMessagingState(kMessagingSetRequestSent); - break; - case LLPathfindingManager::kRequestCompleted : - if (mObjectList == NULL) - { - mObjectList = pObjectList; - } - else - { - mObjectList->update(pObjectList); - } - rebuildObjectsScrollList(); - setMessagingState(kMessagingComplete); - break; - case LLPathfindingManager::kRequestNotEnabled : - clearAllObjects(); - setMessagingState(kMessagingNotEnabled); - break; - case LLPathfindingManager::kRequestError : - clearAllObjects(); - setMessagingState(kMessagingSetError); - break; - default : - clearAllObjects(); - setMessagingState(kMessagingSetError); - llassert(0); - break; - } - } -} - -void LLFloaterPathfindingObjects::rebuildObjectsScrollList(bool update_if_needed) -{ - if (!mHasObjectsToBeSelected) - { - std::vector selectedItems = mObjectsScrollList->getAllSelected(); - int numSelectedItems = selectedItems.size(); - if (numSelectedItems > 0) - { - mObjectsToBeSelected.reserve(selectedItems.size()); - for (std::vector::const_iterator itemIter = selectedItems.begin(); - itemIter != selectedItems.end(); ++itemIter) - { - const LLScrollListItem *listItem = *itemIter; - mObjectsToBeSelected.push_back(listItem->getUUID()); - } - } - } - - S32 origScrollPosition = mObjectsScrollList->getScrollPos(); - mObjectsScrollList->deleteAllItems(); - mMissingNameObjectsScrollListItems.clear(); - - if ((mObjectList != NULL) && !mObjectList->isEmpty()) - { - buildObjectsScrollList(mObjectList); - - if(mObjectsScrollList->selectMultiple(mObjectsToBeSelected) == 0) - { - if(update_if_needed && mRefreshListButton->getEnabled()) - { - requestGetObjects(); - return; - } - } - if (mHasObjectsToBeSelected) - { - mObjectsScrollList->scrollToShowSelected(); - } - else - { - mObjectsScrollList->setScrollPos(origScrollPosition); - } - } - - mObjectsToBeSelected.clear(); - mHasObjectsToBeSelected = false; - - updateControlsOnScrollListChange(); -} - -void LLFloaterPathfindingObjects::buildObjectsScrollList(const LLPathfindingObjectListPtr pObjectListPtr) -{ - llassert(0); -} - -void LLFloaterPathfindingObjects::addObjectToScrollList(const LLPathfindingObjectPtr pObjectPtr, const LLSD &pScrollListItemData) -{ - LLScrollListCell::Params cellParams; - cellParams.font = LLFontGL::getFontSansSerif(); - - LLScrollListItem::Params rowParams; - rowParams.value = pObjectPtr->getUUID().asString(); - - llassert(pScrollListItemData.isArray()); - for (LLSD::array_const_iterator cellIter = pScrollListItemData.beginArray(); - cellIter != pScrollListItemData.endArray(); ++cellIter) - { - const LLSD &cellElement = *cellIter; - - llassert(cellElement.has("column")); - llassert(cellElement.get("column").isString()); - cellParams.column = cellElement.get("column").asString(); - - llassert(cellElement.has("value")); - llassert(cellElement.get("value").isString()); - cellParams.value = cellElement.get("value").asString(); - - rowParams.columns.add(cellParams); - } - - LLScrollListItem *scrollListItem = mObjectsScrollList->addRow(rowParams); - - if (pObjectPtr->hasOwner() && !pObjectPtr->hasOwnerName()) - { - mMissingNameObjectsScrollListItems.insert(scroll_list_item_map::value_type(pObjectPtr->getUUID().asString(), scrollListItem)); - pObjectPtr->registerOwnerNameListener(boost::bind(&LLFloaterPathfindingObjects::handleObjectNameResponse, this, _1)); - } -} - -void LLFloaterPathfindingObjects::updateControlsOnScrollListChange() -{ - updateMessagingStatus(); - updateStateOnListControls(); - selectScrollListItemsInWorld(); - updateStateOnActionControls(); -} - -void LLFloaterPathfindingObjects::updateControlsOnInWorldSelectionChange() -{ - updateStateOnActionControls(); -} - -S32 LLFloaterPathfindingObjects::getNameColumnIndex() const -{ - return 0; -} - -S32 LLFloaterPathfindingObjects::getOwnerNameColumnIndex() const -{ - return 2; -} - -std::string LLFloaterPathfindingObjects::getOwnerName(const LLPathfindingObject *pObject) const -{ - llassert(0); - std::string returnVal; - return returnVal; -} - -const LLColor4 &LLFloaterPathfindingObjects::getBeaconColor() const -{ - return mDefaultBeaconColor; -} - -const LLColor4 &LLFloaterPathfindingObjects::getBeaconTextColor() const -{ - return mDefaultBeaconTextColor; -} - -S32 LLFloaterPathfindingObjects::getBeaconWidth() const -{ - return DEFAULT_BEACON_WIDTH; -} - -void LLFloaterPathfindingObjects::showFloaterWithSelectionObjects() -{ - mObjectsToBeSelected.clear(); - - LLObjectSelectionHandle selectedObjectsHandle = LLSelectMgr::getInstance()->getSelection(); - if (selectedObjectsHandle.notNull()) - { - LLObjectSelection *selectedObjects = selectedObjectsHandle.get(); - if (!selectedObjects->isEmpty()) - { - for (LLObjectSelection::valid_iterator objectIter = selectedObjects->valid_begin(); - objectIter != selectedObjects->valid_end(); ++objectIter) - { - LLSelectNode *object = *objectIter; - LLViewerObject *viewerObject = object->getObject(); - mObjectsToBeSelected.push_back(viewerObject->getID()); - } - } - } - mHasObjectsToBeSelected = true; - - if (!isShown()) - { - openFloater(); - setVisibleAndFrontmost(); - } - else - { - rebuildObjectsScrollList(true); - if (isMinimized()) - { - setMinimized(false); - } - setVisibleAndFrontmost(); - } - setFocus(true); -} - -bool LLFloaterPathfindingObjects::isShowBeacons() const -{ - return mShowBeaconCheckBox->get(); -} - -void LLFloaterPathfindingObjects::clearAllObjects() -{ - selectNoneObjects(); - mObjectsScrollList->deleteAllItems(); - mMissingNameObjectsScrollListItems.clear(); - mObjectList.reset(); -} - -void LLFloaterPathfindingObjects::selectAllObjects() -{ - mObjectsScrollList->selectAll(); -} - -void LLFloaterPathfindingObjects::selectNoneObjects() -{ - mObjectsScrollList->deselectAllItems(); -} - -void LLFloaterPathfindingObjects::teleportToSelectedObject() -{ - std::vector selectedItems = mObjectsScrollList->getAllSelected(); - llassert(selectedItems.size() == 1); - if (selectedItems.size() == 1) - { - std::vector::const_reference selectedItemRef = selectedItems.front(); - const LLScrollListItem *selectedItem = selectedItemRef; - llassert(mObjectList != NULL); - LLVector3d teleportLocation; - LLViewerObject *viewerObject = gObjectList.findObject(selectedItem->getUUID()); - if (viewerObject == NULL) - { - // If we cannot find the object in the viewer list, teleport to the last reported position - const LLPathfindingObjectPtr objectPtr = mObjectList->find(selectedItem->getUUID().asString()); - teleportLocation = gAgent.getPosGlobalFromAgent(objectPtr->getLocation()); - } - else - { - // If we can find the object in the viewer list, teleport to the known current position - teleportLocation = viewerObject->getPositionGlobal(); - } - gAgent.teleportViaLocationLookAt(teleportLocation); - } -} - -LLPathfindingObjectListPtr LLFloaterPathfindingObjects::getEmptyObjectList() const -{ - llassert(0); - LLPathfindingObjectListPtr objectListPtr(new LLPathfindingObjectList()); - return objectListPtr; -} - -int LLFloaterPathfindingObjects::getNumSelectedObjects() const -{ - return mObjectsScrollList->getNumSelected(); -} - -LLPathfindingObjectListPtr LLFloaterPathfindingObjects::getSelectedObjects() const -{ - LLPathfindingObjectListPtr selectedObjects = getEmptyObjectList(); - - std::vector selectedItems = mObjectsScrollList->getAllSelected(); - if (!selectedItems.empty()) - { - for (std::vector::const_iterator itemIter = selectedItems.begin(); - itemIter != selectedItems.end(); ++itemIter) - { - LLPathfindingObjectPtr objectPtr = findObject(*itemIter); - if (objectPtr != NULL) - { - selectedObjects->update(objectPtr); - } - } - } - - return selectedObjects; -} - -LLPathfindingObjectPtr LLFloaterPathfindingObjects::getFirstSelectedObject() const -{ - LLPathfindingObjectPtr objectPtr; - - std::vector selectedItems = mObjectsScrollList->getAllSelected(); - if (!selectedItems.empty()) - { - objectPtr = findObject(selectedItems.front()); - } - - return objectPtr; -} - -LLFloaterPathfindingObjects::EMessagingState LLFloaterPathfindingObjects::getMessagingState() const -{ - return mMessagingState; -} - -void LLFloaterPathfindingObjects::setMessagingState(EMessagingState pMessagingState) -{ - mMessagingState = pMessagingState; - updateControlsOnScrollListChange(); -} - -void LLFloaterPathfindingObjects::onRefreshObjectsClicked() -{ - requestGetObjects(); -} - -void LLFloaterPathfindingObjects::onSelectAllObjectsClicked() -{ - selectAllObjects(); -} - -void LLFloaterPathfindingObjects::onSelectNoneObjectsClicked() -{ - selectNoneObjects(); -} - -void LLFloaterPathfindingObjects::onTakeClicked() -{ - handle_take(); - requestGetObjects(); -} - -void LLFloaterPathfindingObjects::onTakeCopyClicked() -{ - handle_take_copy(); -} - -void LLFloaterPathfindingObjects::onReturnClicked() -{ - LLNotification::Params params("PathfindingReturnMultipleItems"); - params.functor.function(boost::bind(&LLFloaterPathfindingObjects::handleReturnItemsResponse, this, _1, _2)); - - LLSD substitutions; - int numItems = getNumSelectedObjects(); - substitutions["NUM_ITEMS"] = static_cast(numItems); - params.substitutions = substitutions; - - if (numItems == 1) - { - LLNotifications::getInstance()->forceResponse(params, 0); - } - else if (numItems > 1) - { - LLNotifications::getInstance()->add(params); - } -} - -void LLFloaterPathfindingObjects::onDeleteClicked() -{ - LLNotification::Params params("PathfindingDeleteMultipleItems"); - params.functor.function(boost::bind(&LLFloaterPathfindingObjects::handleDeleteItemsResponse, this, _1, _2)); - - LLSD substitutions; - int numItems = getNumSelectedObjects(); - substitutions["NUM_ITEMS"] = static_cast(numItems); - params.substitutions = substitutions; - - if (numItems == 1) - { - LLNotifications::getInstance()->forceResponse(params, 0); - } - else if (numItems > 1) - { - LLNotifications::getInstance()->add(params); - } -} - -void LLFloaterPathfindingObjects::onTeleportClicked() -{ - teleportToSelectedObject(); -} - -void LLFloaterPathfindingObjects::onScrollListSelectionChanged() -{ - updateControlsOnScrollListChange(); -} - -void LLFloaterPathfindingObjects::onInWorldSelectionListChanged() -{ - updateControlsOnInWorldSelectionChange(); -} - -void LLFloaterPathfindingObjects::onRegionBoundaryCrossed() -{ - requestGetObjects(); -} - -void LLFloaterPathfindingObjects::onGodLevelChange(U8 pGodLevel) -{ - requestGetObjects(); -} - -void LLFloaterPathfindingObjects::handleObjectNameResponse(const LLPathfindingObject *pObject) -{ - llassert(pObject != NULL); - const std::string uuid = pObject->getUUID().asString(); - scroll_list_item_map::iterator scrollListItemIter = mMissingNameObjectsScrollListItems.find(uuid); - if (scrollListItemIter != mMissingNameObjectsScrollListItems.end()) - { - LLScrollListItem *scrollListItem = scrollListItemIter->second; - llassert(scrollListItem != NULL); - - LLScrollListCell *scrollListCell = scrollListItem->getColumn(getOwnerNameColumnIndex()); - LLSD ownerName = getOwnerName(pObject); - - scrollListCell->setValue(ownerName); - - mMissingNameObjectsScrollListItems.erase(scrollListItemIter); - } -} - -void LLFloaterPathfindingObjects::updateMessagingStatus() -{ - std::string statusText(""); - LLStyle::Params styleParams; - - switch (getMessagingState()) - { - case kMessagingUnknown: - statusText = getString("messaging_initial"); - styleParams.color = mErrorTextColor; - break; - case kMessagingGetRequestSent : - statusText = getString("messaging_get_inprogress"); - styleParams.color = mWarningTextColor; - break; - case kMessagingGetError : - statusText = getString("messaging_get_error"); - styleParams.color = mErrorTextColor; - break; - case kMessagingSetRequestSent : - statusText = getString("messaging_set_inprogress"); - styleParams.color = mWarningTextColor; - break; - case kMessagingSetError : - statusText = getString("messaging_set_error"); - styleParams.color = mErrorTextColor; - break; - case kMessagingComplete : - if (mObjectsScrollList->isEmpty()) - { - statusText = getString("messaging_complete_none_found"); - } - else - { - S32 numItems = mObjectsScrollList->getItemCount(); - S32 numSelectedItems = mObjectsScrollList->getNumSelected(); - - LLLocale locale(LLStringUtil::getLocale()); - std::string numItemsString; - LLResMgr::getInstance()->getIntegerString(numItemsString, numItems); - - std::string numSelectedItemsString; - LLResMgr::getInstance()->getIntegerString(numSelectedItemsString, numSelectedItems); - - LLStringUtil::format_map_t string_args; - string_args["[NUM_SELECTED]"] = numSelectedItemsString; - string_args["[NUM_TOTAL]"] = numItemsString; - statusText = getString("messaging_complete_available", string_args); - } - break; - case kMessagingNotEnabled : - statusText = getString("messaging_not_enabled"); - styleParams.color = mErrorTextColor; - break; - default: - statusText = getString("messaging_initial"); - styleParams.color = mErrorTextColor; - llassert(0); - break; - } - - mMessagingStatus->setText((LLStringExplicit)statusText, styleParams); -} - -void LLFloaterPathfindingObjects::updateStateOnListControls() -{ - switch (getMessagingState()) - { - case kMessagingUnknown: - case kMessagingGetRequestSent : - case kMessagingSetRequestSent : - mRefreshListButton->setEnabled(false); - mSelectAllButton->setEnabled(false); - mSelectNoneButton->setEnabled(false); - break; - case kMessagingGetError : - case kMessagingSetError : - case kMessagingNotEnabled : - mRefreshListButton->setEnabled(true); - mSelectAllButton->setEnabled(false); - mSelectNoneButton->setEnabled(false); - break; - case kMessagingComplete : - { - int numItems = mObjectsScrollList->getItemCount(); - int numSelectedItems = mObjectsScrollList->getNumSelected(); - mRefreshListButton->setEnabled(true); - mSelectAllButton->setEnabled(numSelectedItems < numItems); - mSelectNoneButton->setEnabled(numSelectedItems > 0); - } - break; - default: - llassert(0); - break; - } -} - -void LLFloaterPathfindingObjects::updateStateOnActionControls() -{ - int numSelectedItems = mObjectsScrollList->getNumSelected(); - bool isEditEnabled = (numSelectedItems > 0); - - mShowBeaconCheckBox->setEnabled(isEditEnabled); - mTakeButton->setEnabled(isEditEnabled && visible_take_object()); - mTakeCopyButton->setEnabled(isEditEnabled && enable_object_take_copy()); - mReturnButton->setEnabled(isEditEnabled && enable_object_return()); - mDeleteButton->setEnabled(isEditEnabled && enable_object_delete()); - mTeleportButton->setEnabled(numSelectedItems == 1); -} - -void LLFloaterPathfindingObjects::selectScrollListItemsInWorld() -{ - mObjectsSelection.clear(); - LLSelectMgr::getInstance()->deselectAll(); - - std::vector selectedItems = mObjectsScrollList->getAllSelected(); - if (!selectedItems.empty()) - { - int numSelectedItems = selectedItems.size(); - - std::vectorviewerObjects; - viewerObjects.reserve(numSelectedItems); - - for (std::vector::const_iterator selectedItemIter = selectedItems.begin(); - selectedItemIter != selectedItems.end(); ++selectedItemIter) - { - const LLScrollListItem *selectedItem = *selectedItemIter; - - LLViewerObject *viewerObject = gObjectList.findObject(selectedItem->getUUID()); - if (viewerObject != NULL) - { - viewerObjects.push_back(viewerObject); - } - } - - if (!viewerObjects.empty()) - { - mObjectsSelection = LLSelectMgr::getInstance()->selectObjectAndFamily(viewerObjects); - } - } -} - -void LLFloaterPathfindingObjects::handleReturnItemsResponse(const LLSD &pNotification, const LLSD &pResponse) -{ - if (LLNotificationsUtil::getSelectedOption(pNotification, pResponse) == 0) - { - handle_object_return(); - requestGetObjects(); - } -} - -void LLFloaterPathfindingObjects::handleDeleteItemsResponse(const LLSD &pNotification, const LLSD &pResponse) -{ - if (LLNotificationsUtil::getSelectedOption(pNotification, pResponse) == 0) - { - handle_object_delete(); - requestGetObjects(); - } -} - -LLPathfindingObjectPtr LLFloaterPathfindingObjects::findObject(const LLScrollListItem *pListItem) const -{ - LLPathfindingObjectPtr objectPtr; - - LLUUID uuid = pListItem->getUUID(); - const std::string &uuidString = uuid.asString(); - llassert(mObjectList != NULL); - objectPtr = mObjectList->find(uuidString); - - return objectPtr; -} +/** +* @file llfloaterpathfindingobjects.cpp +* @brief Base class for both the pathfinding linksets and characters floater. +* @author Stinson@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterpathfindingobjects.h" + +#include +#include +#include + +#include +#include + +#include "llagent.h" +#include "llavatarname.h" +#include "llavatarnamecache.h" +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "llfloater.h" +#include "llfontgl.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "llpathfindingmanager.h" +#include "llresmgr.h" +#include "llscrolllistcell.h" +#include "llscrolllistctrl.h" +#include "llscrolllistitem.h" +#include "llselectmgr.h" +#include "llsd.h" +#include "llstring.h" +#include "llstyle.h" +#include "lltextbase.h" +#include "lluicolortable.h" +#include "llviewermenu.h" +#include "llviewerobject.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "v3dmath.h" +#include "v3math.h" +#include "v4color.h" + +#define DEFAULT_BEACON_WIDTH 6 + +//--------------------------------------------------------------------------- +// LLFloaterPathfindingObjects +//--------------------------------------------------------------------------- + +void LLFloaterPathfindingObjects::onOpen(const LLSD &pKey) +{ + LLFloater::onOpen(pKey); + + selectNoneObjects(); + mObjectsScrollList->setCommitOnSelectionChange(true); + + if (!mSelectionUpdateSlot.connected()) + { + mSelectionUpdateSlot = LLSelectMgr::getInstance()->mUpdateSignal.connect(boost::bind(&LLFloaterPathfindingObjects::onInWorldSelectionListChanged, this)); + } + + if (!mRegionBoundaryCrossingSlot.connected()) + { + mRegionBoundaryCrossingSlot = gAgent.addRegionChangedCallback(boost::bind(&LLFloaterPathfindingObjects::onRegionBoundaryCrossed, this)); + } + + if (!mGodLevelChangeSlot.connected()) + { + mGodLevelChangeSlot = gAgent.registerGodLevelChanageListener(boost::bind(&LLFloaterPathfindingObjects::onGodLevelChange, this, _1)); + } + + requestGetObjects(); +} + +void LLFloaterPathfindingObjects::onClose(bool pIsAppQuitting) +{ + if (mGodLevelChangeSlot.connected()) + { + mGodLevelChangeSlot.disconnect(); + } + + if (mRegionBoundaryCrossingSlot.connected()) + { + mRegionBoundaryCrossingSlot.disconnect(); + } + + if (mSelectionUpdateSlot.connected()) + { + mSelectionUpdateSlot.disconnect(); + } + + mObjectsScrollList->setCommitOnSelectionChange(false); + selectNoneObjects(); + + if (mObjectsSelection.notNull()) + { + mObjectsSelection.clear(); + } + + if (pIsAppQuitting) + { + clearAllObjects(); + } +} + +void LLFloaterPathfindingObjects::draw() +{ + LLFloater::draw(); + + if (isShowBeacons()) + { + std::vector selectedItems = mObjectsScrollList->getAllSelected(); + if (!selectedItems.empty()) + { + int numSelectedItems = selectedItems.size(); + S32 nameColumnIndex = getNameColumnIndex(); + const LLColor4 &beaconColor = getBeaconColor(); + const LLColor4 &beaconTextColor = getBeaconTextColor(); + S32 beaconWidth = getBeaconWidth(); + + std::vector viewerObjects; + viewerObjects.reserve(numSelectedItems); + + for (std::vector::const_iterator selectedItemIter = selectedItems.begin(); + selectedItemIter != selectedItems.end(); ++selectedItemIter) + { + const LLScrollListItem *selectedItem = *selectedItemIter; + + LLViewerObject *viewerObject = gObjectList.findObject(selectedItem->getUUID()); + if (viewerObject != NULL) + { + const std::string &objectName = selectedItem->getColumn(nameColumnIndex)->getValue().asString(); + gObjectList.addDebugBeacon(viewerObject->getPositionAgent(), objectName, beaconColor, beaconTextColor, beaconWidth); + } + } + } + } +} + +LLFloaterPathfindingObjects::LLFloaterPathfindingObjects(const LLSD &pSeed) + : LLFloater(pSeed), + mObjectsScrollList(NULL), + mMessagingStatus(NULL), + mRefreshListButton(NULL), + mSelectAllButton(NULL), + mSelectNoneButton(NULL), + mShowBeaconCheckBox(NULL), + mTakeButton(NULL), + mTakeCopyButton(NULL), + mReturnButton(NULL), + mDeleteButton(NULL), + mTeleportButton(NULL), + mDefaultBeaconColor(), + mDefaultBeaconTextColor(), + mErrorTextColor(), + mWarningTextColor(), + mMessagingState(kMessagingUnknown), + mMessagingRequestId(0U), + mMissingNameObjectsScrollListItems(), + mObjectList(), + mObjectsSelection(), + mHasObjectsToBeSelected(false), + mObjectsToBeSelected(), + mSelectionUpdateSlot(), + mRegionBoundaryCrossingSlot() +{ +} + +LLFloaterPathfindingObjects::~LLFloaterPathfindingObjects() +{ + clearAllObjects(); +} + +bool LLFloaterPathfindingObjects::postBuild() +{ + mDefaultBeaconColor = LLUIColorTable::getInstance()->getColor("PathfindingDefaultBeaconColor"); + mDefaultBeaconTextColor = LLUIColorTable::getInstance()->getColor("PathfindingDefaultBeaconTextColor"); + mErrorTextColor = LLUIColorTable::getInstance()->getColor("PathfindingErrorColor"); + mWarningTextColor = LLUIColorTable::getInstance()->getColor("PathfindingWarningColor"); + + mObjectsScrollList = findChild("objects_scroll_list"); + llassert(mObjectsScrollList != NULL); + mObjectsScrollList->setCommitCallback(boost::bind(&LLFloaterPathfindingObjects::onScrollListSelectionChanged, this)); + mObjectsScrollList->sortByColumnIndex(static_cast(getNameColumnIndex()), true); + + mMessagingStatus = findChild("messaging_status"); + llassert(mMessagingStatus != NULL); + + mRefreshListButton = findChild("refresh_objects_list"); + llassert(mRefreshListButton != NULL); + mRefreshListButton->setCommitCallback(boost::bind(&LLFloaterPathfindingObjects::onRefreshObjectsClicked, this)); + + mSelectAllButton = findChild("select_all_objects"); + llassert(mSelectAllButton != NULL); + mSelectAllButton->setCommitCallback(boost::bind(&LLFloaterPathfindingObjects::onSelectAllObjectsClicked, this)); + + mSelectNoneButton = findChild("select_none_objects"); + llassert(mSelectNoneButton != NULL); + mSelectNoneButton->setCommitCallback(boost::bind(&LLFloaterPathfindingObjects::onSelectNoneObjectsClicked, this)); + + mShowBeaconCheckBox = findChild("show_beacon"); + llassert(mShowBeaconCheckBox != NULL); + + mTakeButton = findChild("take_objects"); + llassert(mTakeButton != NULL); + mTakeButton->setCommitCallback(boost::bind(&LLFloaterPathfindingObjects::onTakeClicked, this)); + + mTakeCopyButton = findChild("take_copy_objects"); + llassert(mTakeCopyButton != NULL); + mTakeCopyButton->setCommitCallback(boost::bind(&LLFloaterPathfindingObjects::onTakeCopyClicked, this)); + + mReturnButton = findChild("return_objects"); + llassert(mReturnButton != NULL); + mReturnButton->setCommitCallback(boost::bind(&LLFloaterPathfindingObjects::onReturnClicked, this)); + + mDeleteButton = findChild("delete_objects"); + llassert(mDeleteButton != NULL); + mDeleteButton->setCommitCallback(boost::bind(&LLFloaterPathfindingObjects::onDeleteClicked, this)); + + mTeleportButton = findChild("teleport_me_to_object"); + llassert(mTeleportButton != NULL); + mTeleportButton->setCommitCallback(boost::bind(&LLFloaterPathfindingObjects::onTeleportClicked, this)); + + return LLFloater::postBuild(); +} + +void LLFloaterPathfindingObjects::requestGetObjects() +{ + llassert(0); +} + +LLPathfindingManager::request_id_t LLFloaterPathfindingObjects::getNewRequestId() +{ + return ++mMessagingRequestId; +} + +void LLFloaterPathfindingObjects::handleNewObjectList(LLPathfindingManager::request_id_t pRequestId, LLPathfindingManager::ERequestStatus pRequestStatus, LLPathfindingObjectListPtr pObjectList) +{ + llassert(pRequestId <= mMessagingRequestId); + if (pRequestId == mMessagingRequestId) + { + switch (pRequestStatus) + { + case LLPathfindingManager::kRequestStarted : + setMessagingState(kMessagingGetRequestSent); + break; + case LLPathfindingManager::kRequestCompleted : + mObjectList = pObjectList; + rebuildObjectsScrollList(); + setMessagingState(kMessagingComplete); + break; + case LLPathfindingManager::kRequestNotEnabled : + clearAllObjects(); + setMessagingState(kMessagingNotEnabled); + break; + case LLPathfindingManager::kRequestError : + clearAllObjects(); + setMessagingState(kMessagingGetError); + break; + default : + clearAllObjects(); + setMessagingState(kMessagingGetError); + llassert(0); + break; + } + } +} + +void LLFloaterPathfindingObjects::handleUpdateObjectList(LLPathfindingManager::request_id_t pRequestId, LLPathfindingManager::ERequestStatus pRequestStatus, LLPathfindingObjectListPtr pObjectList) +{ + // We current assume that handleUpdateObjectList is called only when objects are being SET + llassert(pRequestId <= mMessagingRequestId); + if (pRequestId == mMessagingRequestId) + { + switch (pRequestStatus) + { + case LLPathfindingManager::kRequestStarted : + setMessagingState(kMessagingSetRequestSent); + break; + case LLPathfindingManager::kRequestCompleted : + if (mObjectList == NULL) + { + mObjectList = pObjectList; + } + else + { + mObjectList->update(pObjectList); + } + rebuildObjectsScrollList(); + setMessagingState(kMessagingComplete); + break; + case LLPathfindingManager::kRequestNotEnabled : + clearAllObjects(); + setMessagingState(kMessagingNotEnabled); + break; + case LLPathfindingManager::kRequestError : + clearAllObjects(); + setMessagingState(kMessagingSetError); + break; + default : + clearAllObjects(); + setMessagingState(kMessagingSetError); + llassert(0); + break; + } + } +} + +void LLFloaterPathfindingObjects::rebuildObjectsScrollList(bool update_if_needed) +{ + if (!mHasObjectsToBeSelected) + { + std::vector selectedItems = mObjectsScrollList->getAllSelected(); + int numSelectedItems = selectedItems.size(); + if (numSelectedItems > 0) + { + mObjectsToBeSelected.reserve(selectedItems.size()); + for (std::vector::const_iterator itemIter = selectedItems.begin(); + itemIter != selectedItems.end(); ++itemIter) + { + const LLScrollListItem *listItem = *itemIter; + mObjectsToBeSelected.push_back(listItem->getUUID()); + } + } + } + + S32 origScrollPosition = mObjectsScrollList->getScrollPos(); + mObjectsScrollList->deleteAllItems(); + mMissingNameObjectsScrollListItems.clear(); + + if ((mObjectList != NULL) && !mObjectList->isEmpty()) + { + buildObjectsScrollList(mObjectList); + + if(mObjectsScrollList->selectMultiple(mObjectsToBeSelected) == 0) + { + if(update_if_needed && mRefreshListButton->getEnabled()) + { + requestGetObjects(); + return; + } + } + if (mHasObjectsToBeSelected) + { + mObjectsScrollList->scrollToShowSelected(); + } + else + { + mObjectsScrollList->setScrollPos(origScrollPosition); + } + } + + mObjectsToBeSelected.clear(); + mHasObjectsToBeSelected = false; + + updateControlsOnScrollListChange(); +} + +void LLFloaterPathfindingObjects::buildObjectsScrollList(const LLPathfindingObjectListPtr pObjectListPtr) +{ + llassert(0); +} + +void LLFloaterPathfindingObjects::addObjectToScrollList(const LLPathfindingObjectPtr pObjectPtr, const LLSD &pScrollListItemData) +{ + LLScrollListCell::Params cellParams; + cellParams.font = LLFontGL::getFontSansSerif(); + + LLScrollListItem::Params rowParams; + rowParams.value = pObjectPtr->getUUID().asString(); + + llassert(pScrollListItemData.isArray()); + for (LLSD::array_const_iterator cellIter = pScrollListItemData.beginArray(); + cellIter != pScrollListItemData.endArray(); ++cellIter) + { + const LLSD &cellElement = *cellIter; + + llassert(cellElement.has("column")); + llassert(cellElement.get("column").isString()); + cellParams.column = cellElement.get("column").asString(); + + llassert(cellElement.has("value")); + llassert(cellElement.get("value").isString()); + cellParams.value = cellElement.get("value").asString(); + + rowParams.columns.add(cellParams); + } + + LLScrollListItem *scrollListItem = mObjectsScrollList->addRow(rowParams); + + if (pObjectPtr->hasOwner() && !pObjectPtr->hasOwnerName()) + { + mMissingNameObjectsScrollListItems.insert(scroll_list_item_map::value_type(pObjectPtr->getUUID().asString(), scrollListItem)); + pObjectPtr->registerOwnerNameListener(boost::bind(&LLFloaterPathfindingObjects::handleObjectNameResponse, this, _1)); + } +} + +void LLFloaterPathfindingObjects::updateControlsOnScrollListChange() +{ + updateMessagingStatus(); + updateStateOnListControls(); + selectScrollListItemsInWorld(); + updateStateOnActionControls(); +} + +void LLFloaterPathfindingObjects::updateControlsOnInWorldSelectionChange() +{ + updateStateOnActionControls(); +} + +S32 LLFloaterPathfindingObjects::getNameColumnIndex() const +{ + return 0; +} + +S32 LLFloaterPathfindingObjects::getOwnerNameColumnIndex() const +{ + return 2; +} + +std::string LLFloaterPathfindingObjects::getOwnerName(const LLPathfindingObject *pObject) const +{ + llassert(0); + std::string returnVal; + return returnVal; +} + +const LLColor4 &LLFloaterPathfindingObjects::getBeaconColor() const +{ + return mDefaultBeaconColor; +} + +const LLColor4 &LLFloaterPathfindingObjects::getBeaconTextColor() const +{ + return mDefaultBeaconTextColor; +} + +S32 LLFloaterPathfindingObjects::getBeaconWidth() const +{ + return DEFAULT_BEACON_WIDTH; +} + +void LLFloaterPathfindingObjects::showFloaterWithSelectionObjects() +{ + mObjectsToBeSelected.clear(); + + LLObjectSelectionHandle selectedObjectsHandle = LLSelectMgr::getInstance()->getSelection(); + if (selectedObjectsHandle.notNull()) + { + LLObjectSelection *selectedObjects = selectedObjectsHandle.get(); + if (!selectedObjects->isEmpty()) + { + for (LLObjectSelection::valid_iterator objectIter = selectedObjects->valid_begin(); + objectIter != selectedObjects->valid_end(); ++objectIter) + { + LLSelectNode *object = *objectIter; + LLViewerObject *viewerObject = object->getObject(); + mObjectsToBeSelected.push_back(viewerObject->getID()); + } + } + } + mHasObjectsToBeSelected = true; + + if (!isShown()) + { + openFloater(); + setVisibleAndFrontmost(); + } + else + { + rebuildObjectsScrollList(true); + if (isMinimized()) + { + setMinimized(false); + } + setVisibleAndFrontmost(); + } + setFocus(true); +} + +bool LLFloaterPathfindingObjects::isShowBeacons() const +{ + return mShowBeaconCheckBox->get(); +} + +void LLFloaterPathfindingObjects::clearAllObjects() +{ + selectNoneObjects(); + mObjectsScrollList->deleteAllItems(); + mMissingNameObjectsScrollListItems.clear(); + mObjectList.reset(); +} + +void LLFloaterPathfindingObjects::selectAllObjects() +{ + mObjectsScrollList->selectAll(); +} + +void LLFloaterPathfindingObjects::selectNoneObjects() +{ + mObjectsScrollList->deselectAllItems(); +} + +void LLFloaterPathfindingObjects::teleportToSelectedObject() +{ + std::vector selectedItems = mObjectsScrollList->getAllSelected(); + llassert(selectedItems.size() == 1); + if (selectedItems.size() == 1) + { + std::vector::const_reference selectedItemRef = selectedItems.front(); + const LLScrollListItem *selectedItem = selectedItemRef; + llassert(mObjectList != NULL); + LLVector3d teleportLocation; + LLViewerObject *viewerObject = gObjectList.findObject(selectedItem->getUUID()); + if (viewerObject == NULL) + { + // If we cannot find the object in the viewer list, teleport to the last reported position + const LLPathfindingObjectPtr objectPtr = mObjectList->find(selectedItem->getUUID().asString()); + teleportLocation = gAgent.getPosGlobalFromAgent(objectPtr->getLocation()); + } + else + { + // If we can find the object in the viewer list, teleport to the known current position + teleportLocation = viewerObject->getPositionGlobal(); + } + gAgent.teleportViaLocationLookAt(teleportLocation); + } +} + +LLPathfindingObjectListPtr LLFloaterPathfindingObjects::getEmptyObjectList() const +{ + llassert(0); + LLPathfindingObjectListPtr objectListPtr(new LLPathfindingObjectList()); + return objectListPtr; +} + +int LLFloaterPathfindingObjects::getNumSelectedObjects() const +{ + return mObjectsScrollList->getNumSelected(); +} + +LLPathfindingObjectListPtr LLFloaterPathfindingObjects::getSelectedObjects() const +{ + LLPathfindingObjectListPtr selectedObjects = getEmptyObjectList(); + + std::vector selectedItems = mObjectsScrollList->getAllSelected(); + if (!selectedItems.empty()) + { + for (std::vector::const_iterator itemIter = selectedItems.begin(); + itemIter != selectedItems.end(); ++itemIter) + { + LLPathfindingObjectPtr objectPtr = findObject(*itemIter); + if (objectPtr != NULL) + { + selectedObjects->update(objectPtr); + } + } + } + + return selectedObjects; +} + +LLPathfindingObjectPtr LLFloaterPathfindingObjects::getFirstSelectedObject() const +{ + LLPathfindingObjectPtr objectPtr; + + std::vector selectedItems = mObjectsScrollList->getAllSelected(); + if (!selectedItems.empty()) + { + objectPtr = findObject(selectedItems.front()); + } + + return objectPtr; +} + +LLFloaterPathfindingObjects::EMessagingState LLFloaterPathfindingObjects::getMessagingState() const +{ + return mMessagingState; +} + +void LLFloaterPathfindingObjects::setMessagingState(EMessagingState pMessagingState) +{ + mMessagingState = pMessagingState; + updateControlsOnScrollListChange(); +} + +void LLFloaterPathfindingObjects::onRefreshObjectsClicked() +{ + requestGetObjects(); +} + +void LLFloaterPathfindingObjects::onSelectAllObjectsClicked() +{ + selectAllObjects(); +} + +void LLFloaterPathfindingObjects::onSelectNoneObjectsClicked() +{ + selectNoneObjects(); +} + +void LLFloaterPathfindingObjects::onTakeClicked() +{ + handle_take(); + requestGetObjects(); +} + +void LLFloaterPathfindingObjects::onTakeCopyClicked() +{ + handle_take_copy(); +} + +void LLFloaterPathfindingObjects::onReturnClicked() +{ + LLNotification::Params params("PathfindingReturnMultipleItems"); + params.functor.function(boost::bind(&LLFloaterPathfindingObjects::handleReturnItemsResponse, this, _1, _2)); + + LLSD substitutions; + int numItems = getNumSelectedObjects(); + substitutions["NUM_ITEMS"] = static_cast(numItems); + params.substitutions = substitutions; + + if (numItems == 1) + { + LLNotifications::getInstance()->forceResponse(params, 0); + } + else if (numItems > 1) + { + LLNotifications::getInstance()->add(params); + } +} + +void LLFloaterPathfindingObjects::onDeleteClicked() +{ + LLNotification::Params params("PathfindingDeleteMultipleItems"); + params.functor.function(boost::bind(&LLFloaterPathfindingObjects::handleDeleteItemsResponse, this, _1, _2)); + + LLSD substitutions; + int numItems = getNumSelectedObjects(); + substitutions["NUM_ITEMS"] = static_cast(numItems); + params.substitutions = substitutions; + + if (numItems == 1) + { + LLNotifications::getInstance()->forceResponse(params, 0); + } + else if (numItems > 1) + { + LLNotifications::getInstance()->add(params); + } +} + +void LLFloaterPathfindingObjects::onTeleportClicked() +{ + teleportToSelectedObject(); +} + +void LLFloaterPathfindingObjects::onScrollListSelectionChanged() +{ + updateControlsOnScrollListChange(); +} + +void LLFloaterPathfindingObjects::onInWorldSelectionListChanged() +{ + updateControlsOnInWorldSelectionChange(); +} + +void LLFloaterPathfindingObjects::onRegionBoundaryCrossed() +{ + requestGetObjects(); +} + +void LLFloaterPathfindingObjects::onGodLevelChange(U8 pGodLevel) +{ + requestGetObjects(); +} + +void LLFloaterPathfindingObjects::handleObjectNameResponse(const LLPathfindingObject *pObject) +{ + llassert(pObject != NULL); + const std::string uuid = pObject->getUUID().asString(); + scroll_list_item_map::iterator scrollListItemIter = mMissingNameObjectsScrollListItems.find(uuid); + if (scrollListItemIter != mMissingNameObjectsScrollListItems.end()) + { + LLScrollListItem *scrollListItem = scrollListItemIter->second; + llassert(scrollListItem != NULL); + + LLScrollListCell *scrollListCell = scrollListItem->getColumn(getOwnerNameColumnIndex()); + LLSD ownerName = getOwnerName(pObject); + + scrollListCell->setValue(ownerName); + + mMissingNameObjectsScrollListItems.erase(scrollListItemIter); + } +} + +void LLFloaterPathfindingObjects::updateMessagingStatus() +{ + std::string statusText(""); + LLStyle::Params styleParams; + + switch (getMessagingState()) + { + case kMessagingUnknown: + statusText = getString("messaging_initial"); + styleParams.color = mErrorTextColor; + break; + case kMessagingGetRequestSent : + statusText = getString("messaging_get_inprogress"); + styleParams.color = mWarningTextColor; + break; + case kMessagingGetError : + statusText = getString("messaging_get_error"); + styleParams.color = mErrorTextColor; + break; + case kMessagingSetRequestSent : + statusText = getString("messaging_set_inprogress"); + styleParams.color = mWarningTextColor; + break; + case kMessagingSetError : + statusText = getString("messaging_set_error"); + styleParams.color = mErrorTextColor; + break; + case kMessagingComplete : + if (mObjectsScrollList->isEmpty()) + { + statusText = getString("messaging_complete_none_found"); + } + else + { + S32 numItems = mObjectsScrollList->getItemCount(); + S32 numSelectedItems = mObjectsScrollList->getNumSelected(); + + LLLocale locale(LLStringUtil::getLocale()); + std::string numItemsString; + LLResMgr::getInstance()->getIntegerString(numItemsString, numItems); + + std::string numSelectedItemsString; + LLResMgr::getInstance()->getIntegerString(numSelectedItemsString, numSelectedItems); + + LLStringUtil::format_map_t string_args; + string_args["[NUM_SELECTED]"] = numSelectedItemsString; + string_args["[NUM_TOTAL]"] = numItemsString; + statusText = getString("messaging_complete_available", string_args); + } + break; + case kMessagingNotEnabled : + statusText = getString("messaging_not_enabled"); + styleParams.color = mErrorTextColor; + break; + default: + statusText = getString("messaging_initial"); + styleParams.color = mErrorTextColor; + llassert(0); + break; + } + + mMessagingStatus->setText((LLStringExplicit)statusText, styleParams); +} + +void LLFloaterPathfindingObjects::updateStateOnListControls() +{ + switch (getMessagingState()) + { + case kMessagingUnknown: + case kMessagingGetRequestSent : + case kMessagingSetRequestSent : + mRefreshListButton->setEnabled(false); + mSelectAllButton->setEnabled(false); + mSelectNoneButton->setEnabled(false); + break; + case kMessagingGetError : + case kMessagingSetError : + case kMessagingNotEnabled : + mRefreshListButton->setEnabled(true); + mSelectAllButton->setEnabled(false); + mSelectNoneButton->setEnabled(false); + break; + case kMessagingComplete : + { + int numItems = mObjectsScrollList->getItemCount(); + int numSelectedItems = mObjectsScrollList->getNumSelected(); + mRefreshListButton->setEnabled(true); + mSelectAllButton->setEnabled(numSelectedItems < numItems); + mSelectNoneButton->setEnabled(numSelectedItems > 0); + } + break; + default: + llassert(0); + break; + } +} + +void LLFloaterPathfindingObjects::updateStateOnActionControls() +{ + int numSelectedItems = mObjectsScrollList->getNumSelected(); + bool isEditEnabled = (numSelectedItems > 0); + + mShowBeaconCheckBox->setEnabled(isEditEnabled); + mTakeButton->setEnabled(isEditEnabled && visible_take_object()); + mTakeCopyButton->setEnabled(isEditEnabled && enable_object_take_copy()); + mReturnButton->setEnabled(isEditEnabled && enable_object_return()); + mDeleteButton->setEnabled(isEditEnabled && enable_object_delete()); + mTeleportButton->setEnabled(numSelectedItems == 1); +} + +void LLFloaterPathfindingObjects::selectScrollListItemsInWorld() +{ + mObjectsSelection.clear(); + LLSelectMgr::getInstance()->deselectAll(); + + std::vector selectedItems = mObjectsScrollList->getAllSelected(); + if (!selectedItems.empty()) + { + int numSelectedItems = selectedItems.size(); + + std::vectorviewerObjects; + viewerObjects.reserve(numSelectedItems); + + for (std::vector::const_iterator selectedItemIter = selectedItems.begin(); + selectedItemIter != selectedItems.end(); ++selectedItemIter) + { + const LLScrollListItem *selectedItem = *selectedItemIter; + + LLViewerObject *viewerObject = gObjectList.findObject(selectedItem->getUUID()); + if (viewerObject != NULL) + { + viewerObjects.push_back(viewerObject); + } + } + + if (!viewerObjects.empty()) + { + mObjectsSelection = LLSelectMgr::getInstance()->selectObjectAndFamily(viewerObjects); + } + } +} + +void LLFloaterPathfindingObjects::handleReturnItemsResponse(const LLSD &pNotification, const LLSD &pResponse) +{ + if (LLNotificationsUtil::getSelectedOption(pNotification, pResponse) == 0) + { + handle_object_return(); + requestGetObjects(); + } +} + +void LLFloaterPathfindingObjects::handleDeleteItemsResponse(const LLSD &pNotification, const LLSD &pResponse) +{ + if (LLNotificationsUtil::getSelectedOption(pNotification, pResponse) == 0) + { + handle_object_delete(); + requestGetObjects(); + } +} + +LLPathfindingObjectPtr LLFloaterPathfindingObjects::findObject(const LLScrollListItem *pListItem) const +{ + LLPathfindingObjectPtr objectPtr; + + LLUUID uuid = pListItem->getUUID(); + const std::string &uuidString = uuid.asString(); + llassert(mObjectList != NULL); + objectPtr = mObjectList->find(uuidString); + + return objectPtr; +} diff --git a/indra/newview/llfloaterpathfindingobjects.h b/indra/newview/llfloaterpathfindingobjects.h index 3e50d21f1d..44fb0fa92d 100644 --- a/indra/newview/llfloaterpathfindingobjects.h +++ b/indra/newview/llfloaterpathfindingobjects.h @@ -1,179 +1,179 @@ -/** -* @file llfloaterpathfindingobjects.h -* @brief Base class for both the pathfinding linksets and characters floater. -* @author Stinson@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ -#ifndef LL_LLFLOATERPATHFINDINGOBJECTS_H -#define LL_LLFLOATERPATHFINDINGOBJECTS_H - -#include -#include - -#include - -#include "llagent.h" -#include "llfloater.h" -#include "llpathfindingmanager.h" -#include "llpathfindingobject.h" -#include "llpathfindingobjectlist.h" -#include "llselectmgr.h" -#include "lluuid.h" -#include "v4color.h" - -class LLAvatarName; -class LLButton; -class LLCheckBoxCtrl; -class LLScrollListCtrl; -class LLScrollListItem; -class LLSD; -class LLTextBase; - -class LLFloaterPathfindingObjects : public LLFloater -{ -public: - virtual void onOpen(const LLSD &pKey); - virtual void onClose(bool pIsAppQuitting); - virtual void draw(); - -protected: - friend class LLFloaterReg; - - typedef enum - { - kMessagingUnknown, - kMessagingGetRequestSent, - kMessagingGetError, - kMessagingSetRequestSent, - kMessagingSetError, - kMessagingComplete, - kMessagingNotEnabled - } EMessagingState; - - LLFloaterPathfindingObjects(const LLSD &pSeed); - virtual ~LLFloaterPathfindingObjects(); - - virtual bool postBuild(); - - virtual void requestGetObjects(); - LLPathfindingManager::request_id_t getNewRequestId(); - void handleNewObjectList(LLPathfindingManager::request_id_t pRequestId, LLPathfindingManager::ERequestStatus pRequestStatus, LLPathfindingObjectListPtr pObjectList); - void handleUpdateObjectList(LLPathfindingManager::request_id_t pRequestId, LLPathfindingManager::ERequestStatus pRequestStatus, LLPathfindingObjectListPtr pObjectList); - - void rebuildObjectsScrollList(bool update_if_needed = false); - virtual void buildObjectsScrollList(const LLPathfindingObjectListPtr pObjectListPtr); - void addObjectToScrollList(const LLPathfindingObjectPtr pObjectPr, const LLSD &pScrollListItemData); - - virtual void updateControlsOnScrollListChange(); - virtual void updateControlsOnInWorldSelectionChange(); - - virtual S32 getNameColumnIndex() const; - virtual S32 getOwnerNameColumnIndex() const; - virtual std::string getOwnerName(const LLPathfindingObject *pObject) const; - virtual const LLColor4 &getBeaconColor() const; - virtual const LLColor4 &getBeaconTextColor() const; - virtual S32 getBeaconWidth() const; - - void showFloaterWithSelectionObjects(); - - bool isShowBeacons() const; - void clearAllObjects(); - void selectAllObjects(); - void selectNoneObjects(); - void teleportToSelectedObject(); - - virtual LLPathfindingObjectListPtr getEmptyObjectList() const; - int getNumSelectedObjects() const; - LLPathfindingObjectListPtr getSelectedObjects() const; - LLPathfindingObjectPtr getFirstSelectedObject() const; - - EMessagingState getMessagingState() const; - -private: - LLFloaterPathfindingObjects(const LLFloaterPathfindingObjects &pOther); - - void setMessagingState(EMessagingState pMessagingState); - - void onRefreshObjectsClicked(); - void onSelectAllObjectsClicked(); - void onSelectNoneObjectsClicked(); - void onTakeClicked(); - void onTakeCopyClicked(); - void onReturnClicked(); - void onDeleteClicked(); - void onTeleportClicked(); - - void onScrollListSelectionChanged(); - void onInWorldSelectionListChanged(); - void onRegionBoundaryCrossed(); - void onGodLevelChange(U8 pGodLevel); - - void handleObjectNameResponse(const LLPathfindingObject *pObject); - - void updateMessagingStatus(); - void updateStateOnListControls(); - void updateStateOnActionControls(); - void selectScrollListItemsInWorld(); - - void handleReturnItemsResponse(const LLSD &pNotification, const LLSD &pResponse); - void handleDeleteItemsResponse(const LLSD &pNotification, const LLSD &pResponse); - - LLPathfindingObjectPtr findObject(const LLScrollListItem *pListItem) const; - - LLScrollListCtrl *mObjectsScrollList; - LLTextBase *mMessagingStatus; - LLButton *mRefreshListButton; - LLButton *mSelectAllButton; - LLButton *mSelectNoneButton; - LLCheckBoxCtrl *mShowBeaconCheckBox; - - LLButton *mTakeButton; - LLButton *mTakeCopyButton; - LLButton *mReturnButton; - LLButton *mDeleteButton; - LLButton *mTeleportButton; - - LLColor4 mDefaultBeaconColor; - LLColor4 mDefaultBeaconTextColor; - LLColor4 mErrorTextColor; - LLColor4 mWarningTextColor; - - EMessagingState mMessagingState; - LLPathfindingManager::request_id_t mMessagingRequestId; - - typedef std::map scroll_list_item_map; - scroll_list_item_map mMissingNameObjectsScrollListItems; - - LLPathfindingObjectListPtr mObjectList; - - LLObjectSelectionHandle mObjectsSelection; - - bool mHasObjectsToBeSelected; - uuid_vec_t mObjectsToBeSelected; - - boost::signals2::connection mSelectionUpdateSlot; - boost::signals2::connection mRegionBoundaryCrossingSlot; - LLAgent::god_level_change_slot_t mGodLevelChangeSlot; -}; - -#endif // LL_LLFLOATERPATHFINDINGOBJECTS_H +/** +* @file llfloaterpathfindingobjects.h +* @brief Base class for both the pathfinding linksets and characters floater. +* @author Stinson@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LL_LLFLOATERPATHFINDINGOBJECTS_H +#define LL_LLFLOATERPATHFINDINGOBJECTS_H + +#include +#include + +#include + +#include "llagent.h" +#include "llfloater.h" +#include "llpathfindingmanager.h" +#include "llpathfindingobject.h" +#include "llpathfindingobjectlist.h" +#include "llselectmgr.h" +#include "lluuid.h" +#include "v4color.h" + +class LLAvatarName; +class LLButton; +class LLCheckBoxCtrl; +class LLScrollListCtrl; +class LLScrollListItem; +class LLSD; +class LLTextBase; + +class LLFloaterPathfindingObjects : public LLFloater +{ +public: + virtual void onOpen(const LLSD &pKey); + virtual void onClose(bool pIsAppQuitting); + virtual void draw(); + +protected: + friend class LLFloaterReg; + + typedef enum + { + kMessagingUnknown, + kMessagingGetRequestSent, + kMessagingGetError, + kMessagingSetRequestSent, + kMessagingSetError, + kMessagingComplete, + kMessagingNotEnabled + } EMessagingState; + + LLFloaterPathfindingObjects(const LLSD &pSeed); + virtual ~LLFloaterPathfindingObjects(); + + virtual bool postBuild(); + + virtual void requestGetObjects(); + LLPathfindingManager::request_id_t getNewRequestId(); + void handleNewObjectList(LLPathfindingManager::request_id_t pRequestId, LLPathfindingManager::ERequestStatus pRequestStatus, LLPathfindingObjectListPtr pObjectList); + void handleUpdateObjectList(LLPathfindingManager::request_id_t pRequestId, LLPathfindingManager::ERequestStatus pRequestStatus, LLPathfindingObjectListPtr pObjectList); + + void rebuildObjectsScrollList(bool update_if_needed = false); + virtual void buildObjectsScrollList(const LLPathfindingObjectListPtr pObjectListPtr); + void addObjectToScrollList(const LLPathfindingObjectPtr pObjectPr, const LLSD &pScrollListItemData); + + virtual void updateControlsOnScrollListChange(); + virtual void updateControlsOnInWorldSelectionChange(); + + virtual S32 getNameColumnIndex() const; + virtual S32 getOwnerNameColumnIndex() const; + virtual std::string getOwnerName(const LLPathfindingObject *pObject) const; + virtual const LLColor4 &getBeaconColor() const; + virtual const LLColor4 &getBeaconTextColor() const; + virtual S32 getBeaconWidth() const; + + void showFloaterWithSelectionObjects(); + + bool isShowBeacons() const; + void clearAllObjects(); + void selectAllObjects(); + void selectNoneObjects(); + void teleportToSelectedObject(); + + virtual LLPathfindingObjectListPtr getEmptyObjectList() const; + int getNumSelectedObjects() const; + LLPathfindingObjectListPtr getSelectedObjects() const; + LLPathfindingObjectPtr getFirstSelectedObject() const; + + EMessagingState getMessagingState() const; + +private: + LLFloaterPathfindingObjects(const LLFloaterPathfindingObjects &pOther); + + void setMessagingState(EMessagingState pMessagingState); + + void onRefreshObjectsClicked(); + void onSelectAllObjectsClicked(); + void onSelectNoneObjectsClicked(); + void onTakeClicked(); + void onTakeCopyClicked(); + void onReturnClicked(); + void onDeleteClicked(); + void onTeleportClicked(); + + void onScrollListSelectionChanged(); + void onInWorldSelectionListChanged(); + void onRegionBoundaryCrossed(); + void onGodLevelChange(U8 pGodLevel); + + void handleObjectNameResponse(const LLPathfindingObject *pObject); + + void updateMessagingStatus(); + void updateStateOnListControls(); + void updateStateOnActionControls(); + void selectScrollListItemsInWorld(); + + void handleReturnItemsResponse(const LLSD &pNotification, const LLSD &pResponse); + void handleDeleteItemsResponse(const LLSD &pNotification, const LLSD &pResponse); + + LLPathfindingObjectPtr findObject(const LLScrollListItem *pListItem) const; + + LLScrollListCtrl *mObjectsScrollList; + LLTextBase *mMessagingStatus; + LLButton *mRefreshListButton; + LLButton *mSelectAllButton; + LLButton *mSelectNoneButton; + LLCheckBoxCtrl *mShowBeaconCheckBox; + + LLButton *mTakeButton; + LLButton *mTakeCopyButton; + LLButton *mReturnButton; + LLButton *mDeleteButton; + LLButton *mTeleportButton; + + LLColor4 mDefaultBeaconColor; + LLColor4 mDefaultBeaconTextColor; + LLColor4 mErrorTextColor; + LLColor4 mWarningTextColor; + + EMessagingState mMessagingState; + LLPathfindingManager::request_id_t mMessagingRequestId; + + typedef std::map scroll_list_item_map; + scroll_list_item_map mMissingNameObjectsScrollListItems; + + LLPathfindingObjectListPtr mObjectList; + + LLObjectSelectionHandle mObjectsSelection; + + bool mHasObjectsToBeSelected; + uuid_vec_t mObjectsToBeSelected; + + boost::signals2::connection mSelectionUpdateSlot; + boost::signals2::connection mRegionBoundaryCrossingSlot; + LLAgent::god_level_change_slot_t mGodLevelChangeSlot; +}; + +#endif // LL_LLFLOATERPATHFINDINGOBJECTS_H diff --git a/indra/newview/llfloaterpay.cpp b/indra/newview/llfloaterpay.cpp index ddc5add2d7..e4e7c4ee39 100644 --- a/indra/newview/llfloaterpay.cpp +++ b/indra/newview/llfloaterpay.cpp @@ -1,627 +1,627 @@ -/** - * @file llfloaterpay.cpp - * @author Aaron Brashears, Kelly Washington, James Cook - * @brief Implementation of the LLFloaterPay class. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterpay.h" - -#include "message.h" -#include "llfloater.h" -#include "lllslconstants.h" // MAX_PAY_BUTTONS -#include "lluuid.h" - -#include "llagent.h" -#include "llfloaterreg.h" -#include "llresmgr.h" -#include "lltextbox.h" -#include "lllineeditor.h" -#include "llmutelist.h" -#include "llnotificationsutil.h" -#include "llfloaterreporter.h" -#include "llslurl.h" -#include "llstatusbar.h" -#include "llviewerobject.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llviewerwindow.h" -#include "llbutton.h" -#include "llselectmgr.h" -#include "lltransactiontypes.h" -#include "lluictrlfactory.h" - -///---------------------------------------------------------------------------- -/// Local function declarations, constants, enums, and typedefs -///---------------------------------------------------------------------------- - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLGiveMoneyInfo -// -// A small class used to track callback information -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLFloaterPay; - -struct LLGiveMoneyInfo -{ - LLFloaterPay* mFloater; - S32 mAmount; - LLGiveMoneyInfo(LLFloaterPay* floater, S32 amount) : - mFloater(floater), mAmount(amount){} -}; - -typedef std::shared_ptr give_money_ptr; - -///---------------------------------------------------------------------------- -/// Class LLFloaterPay -///---------------------------------------------------------------------------- - -class LLFloaterPay : public LLFloater -{ -public: - LLFloaterPay(const LLSD& key); - virtual ~LLFloaterPay(); - /*virtual*/ bool postBuild(); - /*virtual*/ void onClose(bool app_quitting); - - void setCallback(money_callback callback) { mCallback = callback; } - - - static void payViaObject(money_callback callback, LLSafeHandle selection); - - static void payDirectly(money_callback callback, - const LLUUID& target_id, - bool is_group); - static bool payConfirmationCallback(const LLSD& notification, - const LLSD& response, - give_money_ptr info); - -private: - static void onCancel(void* data); - static void onKeystroke(LLLineEditor* editor, void* data); - static void onGive(give_money_ptr info); - void give(S32 amount); - static void processPayPriceReply(LLMessageSystem* msg, void **userdata); - void finishPayUI(const LLUUID& target_id, bool is_group); - -protected: - std::vector mCallbackData; - money_callback mCallback; - LLTextBox* mObjectNameText; - LLUUID mTargetUUID; - bool mTargetIsGroup; - bool mHaveName; - - LLButton* mQuickPayButton[MAX_PAY_BUTTONS]; - give_money_ptr mQuickPayInfo[MAX_PAY_BUTTONS]; - - LLSafeHandle mObjectSelection; -}; - - -const S32 FASTPAY_BUTTON_WIDTH = 80; -const S32 PAY_AMOUNT_NOTIFICATION = 200; - -LLFloaterPay::LLFloaterPay(const LLSD& key) - : LLFloater(key), - mCallbackData(), - mCallback(NULL), - mObjectNameText(NULL), - mTargetUUID(key.asUUID()), - mTargetIsGroup(false), - mHaveName(false) -{ -} - -// Destroys the object -LLFloaterPay::~LLFloaterPay() -{ - std::vector::iterator iter; - for (iter = mCallbackData.begin(); iter != mCallbackData.end(); ++iter) - { - (*iter)->mFloater = NULL; - } - mCallbackData.clear(); - // Name callbacks will be automatically disconnected since LLFloater is trackable - - // In case this floater is currently waiting for a reply. - gMessageSystem->setHandlerFuncFast(_PREHASH_PayPriceReply, 0, 0); -} - -bool LLFloaterPay::postBuild() -{ - S32 i = 0; - - give_money_ptr info = give_money_ptr(new LLGiveMoneyInfo(this, PAY_BUTTON_DEFAULT_0)); - mCallbackData.push_back(info); - - childSetAction("fastpay 1", boost::bind(LLFloaterPay::onGive, info)); - getChildView("fastpay 1")->setVisible(false); - - mQuickPayButton[i] = getChild("fastpay 1"); - mQuickPayInfo[i] = info; - ++i; - - info = give_money_ptr(new LLGiveMoneyInfo(this, PAY_BUTTON_DEFAULT_1)); - mCallbackData.push_back(info); - - childSetAction("fastpay 5", boost::bind(LLFloaterPay::onGive, info)); - getChildView("fastpay 5")->setVisible(false); - - mQuickPayButton[i] = getChild("fastpay 5"); - mQuickPayInfo[i] = info; - ++i; - - info = give_money_ptr(new LLGiveMoneyInfo(this, PAY_BUTTON_DEFAULT_2)); - mCallbackData.push_back(info); - - childSetAction("fastpay 10", boost::bind(LLFloaterPay::onGive, info)); - getChildView("fastpay 10")->setVisible(false); - - mQuickPayButton[i] = getChild("fastpay 10"); - mQuickPayInfo[i] = info; - ++i; - - info = give_money_ptr(new LLGiveMoneyInfo(this, PAY_BUTTON_DEFAULT_3)); - mCallbackData.push_back(info); - - childSetAction("fastpay 20", boost::bind(LLFloaterPay::onGive, info)); - getChildView("fastpay 20")->setVisible(false); - - mQuickPayButton[i] = getChild("fastpay 20"); - mQuickPayInfo[i] = info; - ++i; - - - getChildView("amount text")->setVisible(false); - getChildView("amount")->setVisible(false); - - getChild("amount")->setKeystrokeCallback(&LLFloaterPay::onKeystroke, this); - getChild("amount")->setPrevalidate(LLTextValidate::validateNonNegativeS32); - - info = give_money_ptr(new LLGiveMoneyInfo(this, 0)); - mCallbackData.push_back(info); - - childSetAction("pay btn", boost::bind(LLFloaterPay::onGive, info)); - setDefaultBtn("pay btn"); - getChildView("pay btn")->setVisible(false); - getChildView("pay btn")->setEnabled(false); - - childSetAction("cancel btn",&LLFloaterPay::onCancel,this); - - return true; -} - -// virtual -void LLFloaterPay::onClose(bool app_quitting) -{ - // Deselect the objects - mObjectSelection = NULL; -} - -// static -void LLFloaterPay::processPayPriceReply(LLMessageSystem* msg, void **userdata) -{ - LLFloaterPay* self = (LLFloaterPay*)userdata; - if (self) - { - S32 price; - LLUUID target; - - msg->getUUIDFast(_PREHASH_ObjectData,_PREHASH_ObjectID,target); - if (target != self->mTargetUUID) - { - // This is a message for a different object's pay info - return; - } - - msg->getS32Fast(_PREHASH_ObjectData,_PREHASH_DefaultPayPrice,price); - - if (PAY_PRICE_HIDE == price) - { - self->getChildView("amount")->setVisible(false); - self->getChildView("pay btn")->setVisible(false); - self->getChildView("amount text")->setVisible(false); - } - else if (PAY_PRICE_DEFAULT == price) - { - self->getChildView("amount")->setVisible(true); - self->getChildView("pay btn")->setVisible(true); - self->getChildView("amount text")->setVisible(true); - } - else - { - // PAY_PRICE_HIDE and PAY_PRICE_DEFAULT are negative values - // So we take the absolute value here after we have checked for those cases - - self->getChildView("amount")->setVisible(true); - self->getChildView("pay btn")->setVisible(true); - self->getChildView("pay btn")->setEnabled(true); - self->getChildView("amount text")->setVisible(true); - - self->getChild("amount")->setValue(llformat("%d", llabs(price))); - } - - S32 num_blocks = msg->getNumberOfBlocksFast(_PREHASH_ButtonData); - S32 i = 0; - if (num_blocks > MAX_PAY_BUTTONS) num_blocks = MAX_PAY_BUTTONS; - - S32 max_pay_amount = 0; - S32 padding_required = 0; - - for (i=0;igetS32Fast(_PREHASH_ButtonData,_PREHASH_PayButton,pay_button,i); - if (pay_button > 0) - { - std::string button_str = "L$"; - button_str += LLResMgr::getInstance()->getMonetaryString( pay_button ); - - self->mQuickPayButton[i]->setLabelSelected(button_str); - self->mQuickPayButton[i]->setLabelUnselected(button_str); - self->mQuickPayButton[i]->setVisible(true); - self->mQuickPayInfo[i]->mAmount = pay_button; - - if ( pay_button > max_pay_amount ) - { - max_pay_amount = pay_button; - } - } - else - { - self->mQuickPayButton[i]->setVisible(false); - } - } - - // build a string containing the maximum value and calc nerw button width from it. - std::string balance_str = "L$"; - balance_str += LLResMgr::getInstance()->getMonetaryString( max_pay_amount ); - const LLFontGL* font = LLFontGL::getFontSansSerif(); - S32 new_button_width = font->getWidth( std::string(balance_str)); - new_button_width += ( 12 + 12 ); // padding - - // dialong is sized for 2 digit pay amounts - larger pay values need to be scaled - const S32 threshold = 100000; - if ( max_pay_amount >= threshold ) - { - S32 num_digits_threshold = (S32)log10((double)threshold) + 1; - S32 num_digits_max = (S32)log10((double)max_pay_amount) + 1; - - // calculate the extra width required by 2 buttons with max amount and some commas - padding_required = ( num_digits_max - num_digits_threshold + ( num_digits_max / 3 ) ) * font->getWidth( std::string("0") ); - }; - - // change in button width - S32 button_delta = new_button_width - FASTPAY_BUTTON_WIDTH; - if ( button_delta < 0 ) - button_delta = 0; - - // now we know the maximum amount, we can resize all the buttons to be - for (i=0;imQuickPayButton[i]->getRect(); - - // RHS button colum needs to move further because LHS changed too - if ( i % 2 ) - { - r.setCenterAndSize( r.getCenterX() + ( button_delta * 3 ) / 2 , - r.getCenterY(), - r.getWidth() + button_delta, - r.getHeight() ); - } - else - { - r.setCenterAndSize( r.getCenterX() + button_delta / 2, - r.getCenterY(), - r.getWidth() + button_delta, - r.getHeight() ); - } - self->mQuickPayButton[i]->setRect( r ); - } - - for (i=num_blocks;imQuickPayButton[i]->setVisible(false); - } - - self->reshape( self->getRect().getWidth() + padding_required, self->getRect().getHeight(), false ); - } - msg->setHandlerFunc("PayPriceReply",NULL,NULL); -} - -// static -void LLFloaterPay::payViaObject(money_callback callback, LLSafeHandle selection) -{ - // Object that lead to the selection, may be child - LLViewerObject* object = selection->getPrimaryObject(); - if (!object) - return; - - LLFloaterPay *floater = LLFloaterReg::showTypedInstance("pay_object", LLSD(object->getID())); - if (!floater) - return; - - floater->setCallback(callback); - // Hold onto the selection until we close - floater->mObjectSelection = selection; - - LLSelectNode* node = selection->getFirstRootNode(); - if (!node) - { - // object no longer exists - LLNotificationsUtil::add("PayObjectFailed"); - floater->closeFloater(); - return; - } - - LLHost target_region = object->getRegion()->getHost(); - - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_RequestPayPrice); - msg->nextBlockFast(_PREHASH_ObjectData); - msg->addUUIDFast(_PREHASH_ObjectID, object->getID()); - msg->sendReliable(target_region); - msg->setHandlerFuncFast(_PREHASH_PayPriceReply, processPayPriceReply,(void **)floater); - - LLUUID owner_id; - bool is_group = false; - node->mPermissions->getOwnership(owner_id, is_group); - - floater->getChild("object_name_text")->setValue(node->mName); - - floater->finishPayUI(owner_id, is_group); -} - -void LLFloaterPay::payDirectly(money_callback callback, - const LLUUID& target_id, - bool is_group) -{ - LLFloaterPay *floater = LLFloaterReg::showTypedInstance("pay_resident", LLSD(target_id)); - if (!floater) - return; - - floater->setCallback(callback); - floater->mObjectSelection = NULL; - - floater->getChildView("amount")->setVisible(true); - floater->getChildView("pay btn")->setVisible(true); - floater->getChildView("amount text")->setVisible(true); - - for(S32 i=0;imQuickPayButton[i]->setVisible(true); - } - - floater->finishPayUI(target_id, is_group); -} - -bool LLFloaterPay::payConfirmationCallback(const LLSD& notification, const LLSD& response, give_money_ptr info) -{ - if (!info.get() || !info->mFloater) - { - return false; - } - - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) - { - info->mFloater->give(info->mAmount); - info->mFloater->closeFloater(); - } - - return false; -} - -void LLFloaterPay::finishPayUI(const LLUUID& target_id, bool is_group) -{ - std::string slurl; - if (is_group) - { - setTitle(getString("payee_group")); - slurl = LLSLURL("group", target_id, "inspect").getSLURLString(); - } - else - { - setTitle(getString("payee_resident")); - slurl = LLSLURL("agent", target_id, "inspect").getSLURLString(); - } - getChild("payee_name")->setText(slurl); - - // Make sure the amount field has focus - - LLLineEditor* amount = getChild("amount"); - amount->setFocus(true); - amount->selectAll(); - - mTargetIsGroup = is_group; -} - -// static -void LLFloaterPay::onCancel(void* data) -{ - LLFloaterPay* self = reinterpret_cast(data); - if(self) - { - self->closeFloater(); - } -} - -// static -void LLFloaterPay::onKeystroke(LLLineEditor*, void* data) -{ - LLFloaterPay* self = reinterpret_cast(data); - if(self) - { - // enable the Pay button when amount is non-empty and positive, disable otherwise - std::string amtstr = self->getChild("amount")->getValue().asString(); - self->getChildView("pay btn")->setEnabled(!amtstr.empty() && atoi(amtstr.c_str()) > 0); - } -} - -// static -void LLFloaterPay::onGive(give_money_ptr info) -{ - if (!info.get() || !info->mFloater) - { - return; - } - - LLFloaterPay* floater = info->mFloater; - S32 amount = info->mAmount; - if (amount == 0) - { - LLUICtrl* text_field = floater->getChild("amount"); - if (!text_field) - { - return; - } - amount = atoi(text_field->getValue().asString().c_str()); - } - - if (amount > PAY_AMOUNT_NOTIFICATION && gStatusBar && gStatusBar->getBalance() > amount) - { - LLUUID payee_id = LLUUID::null; - bool is_group = false; - if (floater->mObjectSelection.notNull()) - { - LLSelectNode* node = floater->mObjectSelection->getFirstRootNode(); - if (node) - { - node->mPermissions->getOwnership(payee_id, is_group); - } - else - { - // object no longer exists - LLNotificationsUtil::add("PayObjectFailed"); - floater->closeFloater(); - return; - } - } - else - { - is_group = floater->mTargetIsGroup; - payee_id = floater->mTargetUUID; - } - - LLSD args; - args["TARGET"] = LLSLURL(is_group ? "group" : "agent", payee_id, "completename").getSLURLString(); - args["AMOUNT"] = amount; - - LLNotificationsUtil::add("PayConfirmation", args, LLSD(), boost::bind(&LLFloaterPay::payConfirmationCallback, _1, _2, info)); - } - else - { - floater->give(amount); - floater->closeFloater(); - } -} - -void LLFloaterPay::give(S32 amount) -{ - if(mCallback) - { - // if the amount is 0, that menas that we should use the - // text field. - if(amount == 0) - { - amount = atoi(getChild("amount")->getValue().asString().c_str()); - } - - // Try to pay an object. - if (mObjectSelection.notNull()) - { - LLViewerObject* dest_object = gObjectList.findObject(mTargetUUID); - if(dest_object) - { - LLViewerRegion* region = dest_object->getRegion(); - if (region) - { - // Find the name of the root object - LLSelectNode* node = mObjectSelection->getFirstRootNode(); - std::string object_name; - if (node) - { - object_name = node->mName; - } - S32 tx_type = TRANS_PAY_OBJECT; - if(dest_object->isAvatar()) tx_type = TRANS_GIFT; - mCallback(mTargetUUID, region, amount, false, tx_type, object_name); - mObjectSelection = NULL; - - // request the object owner in order to check if the owner needs to be unmuted - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_RequestObjectPropertiesFamily); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ObjectData); - msg->addU32Fast(_PREHASH_RequestFlags, OBJECT_PAY_REQUEST ); - msg->addUUIDFast(_PREHASH_ObjectID, mTargetUUID); - msg->sendReliable( region->getHost() ); - } - } - else - { - LLNotificationsUtil::add("PayObjectFailed"); - } - } - else - { - // just transfer the L$ - std::string paymentMessage(getChild("payment_message")->getValue().asString()); - mCallback(mTargetUUID, gAgent.getRegion(), amount, mTargetIsGroup, TRANS_GIFT, (paymentMessage.empty() ? LLStringUtil::null : paymentMessage)); - - // check if the payee needs to be unmuted - LLMuteList::getInstance()->autoRemove(mTargetUUID, LLMuteList::AR_MONEY); - } - } -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Namespace LLFloaterPayUtil -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -void LLFloaterPayUtil::registerFloater() -{ - // Sneaky, use same code but different XML for dialogs - LLFloaterReg::add("pay_resident", "floater_pay.xml", - &LLFloaterReg::build); - LLFloaterReg::add("pay_object", "floater_pay_object.xml", - &LLFloaterReg::build); -} - -void LLFloaterPayUtil::payViaObject(money_callback callback, - LLSafeHandle selection) -{ - LLFloaterPay::payViaObject(callback, selection); -} - -void LLFloaterPayUtil::payDirectly(money_callback callback, - const LLUUID& target_id, - bool is_group) -{ - LLFloaterPay::payDirectly(callback, target_id, is_group); -} +/** + * @file llfloaterpay.cpp + * @author Aaron Brashears, Kelly Washington, James Cook + * @brief Implementation of the LLFloaterPay class. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterpay.h" + +#include "message.h" +#include "llfloater.h" +#include "lllslconstants.h" // MAX_PAY_BUTTONS +#include "lluuid.h" + +#include "llagent.h" +#include "llfloaterreg.h" +#include "llresmgr.h" +#include "lltextbox.h" +#include "lllineeditor.h" +#include "llmutelist.h" +#include "llnotificationsutil.h" +#include "llfloaterreporter.h" +#include "llslurl.h" +#include "llstatusbar.h" +#include "llviewerobject.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" +#include "llbutton.h" +#include "llselectmgr.h" +#include "lltransactiontypes.h" +#include "lluictrlfactory.h" + +///---------------------------------------------------------------------------- +/// Local function declarations, constants, enums, and typedefs +///---------------------------------------------------------------------------- + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLGiveMoneyInfo +// +// A small class used to track callback information +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLFloaterPay; + +struct LLGiveMoneyInfo +{ + LLFloaterPay* mFloater; + S32 mAmount; + LLGiveMoneyInfo(LLFloaterPay* floater, S32 amount) : + mFloater(floater), mAmount(amount){} +}; + +typedef std::shared_ptr give_money_ptr; + +///---------------------------------------------------------------------------- +/// Class LLFloaterPay +///---------------------------------------------------------------------------- + +class LLFloaterPay : public LLFloater +{ +public: + LLFloaterPay(const LLSD& key); + virtual ~LLFloaterPay(); + /*virtual*/ bool postBuild(); + /*virtual*/ void onClose(bool app_quitting); + + void setCallback(money_callback callback) { mCallback = callback; } + + + static void payViaObject(money_callback callback, LLSafeHandle selection); + + static void payDirectly(money_callback callback, + const LLUUID& target_id, + bool is_group); + static bool payConfirmationCallback(const LLSD& notification, + const LLSD& response, + give_money_ptr info); + +private: + static void onCancel(void* data); + static void onKeystroke(LLLineEditor* editor, void* data); + static void onGive(give_money_ptr info); + void give(S32 amount); + static void processPayPriceReply(LLMessageSystem* msg, void **userdata); + void finishPayUI(const LLUUID& target_id, bool is_group); + +protected: + std::vector mCallbackData; + money_callback mCallback; + LLTextBox* mObjectNameText; + LLUUID mTargetUUID; + bool mTargetIsGroup; + bool mHaveName; + + LLButton* mQuickPayButton[MAX_PAY_BUTTONS]; + give_money_ptr mQuickPayInfo[MAX_PAY_BUTTONS]; + + LLSafeHandle mObjectSelection; +}; + + +const S32 FASTPAY_BUTTON_WIDTH = 80; +const S32 PAY_AMOUNT_NOTIFICATION = 200; + +LLFloaterPay::LLFloaterPay(const LLSD& key) + : LLFloater(key), + mCallbackData(), + mCallback(NULL), + mObjectNameText(NULL), + mTargetUUID(key.asUUID()), + mTargetIsGroup(false), + mHaveName(false) +{ +} + +// Destroys the object +LLFloaterPay::~LLFloaterPay() +{ + std::vector::iterator iter; + for (iter = mCallbackData.begin(); iter != mCallbackData.end(); ++iter) + { + (*iter)->mFloater = NULL; + } + mCallbackData.clear(); + // Name callbacks will be automatically disconnected since LLFloater is trackable + + // In case this floater is currently waiting for a reply. + gMessageSystem->setHandlerFuncFast(_PREHASH_PayPriceReply, 0, 0); +} + +bool LLFloaterPay::postBuild() +{ + S32 i = 0; + + give_money_ptr info = give_money_ptr(new LLGiveMoneyInfo(this, PAY_BUTTON_DEFAULT_0)); + mCallbackData.push_back(info); + + childSetAction("fastpay 1", boost::bind(LLFloaterPay::onGive, info)); + getChildView("fastpay 1")->setVisible(false); + + mQuickPayButton[i] = getChild("fastpay 1"); + mQuickPayInfo[i] = info; + ++i; + + info = give_money_ptr(new LLGiveMoneyInfo(this, PAY_BUTTON_DEFAULT_1)); + mCallbackData.push_back(info); + + childSetAction("fastpay 5", boost::bind(LLFloaterPay::onGive, info)); + getChildView("fastpay 5")->setVisible(false); + + mQuickPayButton[i] = getChild("fastpay 5"); + mQuickPayInfo[i] = info; + ++i; + + info = give_money_ptr(new LLGiveMoneyInfo(this, PAY_BUTTON_DEFAULT_2)); + mCallbackData.push_back(info); + + childSetAction("fastpay 10", boost::bind(LLFloaterPay::onGive, info)); + getChildView("fastpay 10")->setVisible(false); + + mQuickPayButton[i] = getChild("fastpay 10"); + mQuickPayInfo[i] = info; + ++i; + + info = give_money_ptr(new LLGiveMoneyInfo(this, PAY_BUTTON_DEFAULT_3)); + mCallbackData.push_back(info); + + childSetAction("fastpay 20", boost::bind(LLFloaterPay::onGive, info)); + getChildView("fastpay 20")->setVisible(false); + + mQuickPayButton[i] = getChild("fastpay 20"); + mQuickPayInfo[i] = info; + ++i; + + + getChildView("amount text")->setVisible(false); + getChildView("amount")->setVisible(false); + + getChild("amount")->setKeystrokeCallback(&LLFloaterPay::onKeystroke, this); + getChild("amount")->setPrevalidate(LLTextValidate::validateNonNegativeS32); + + info = give_money_ptr(new LLGiveMoneyInfo(this, 0)); + mCallbackData.push_back(info); + + childSetAction("pay btn", boost::bind(LLFloaterPay::onGive, info)); + setDefaultBtn("pay btn"); + getChildView("pay btn")->setVisible(false); + getChildView("pay btn")->setEnabled(false); + + childSetAction("cancel btn",&LLFloaterPay::onCancel,this); + + return true; +} + +// virtual +void LLFloaterPay::onClose(bool app_quitting) +{ + // Deselect the objects + mObjectSelection = NULL; +} + +// static +void LLFloaterPay::processPayPriceReply(LLMessageSystem* msg, void **userdata) +{ + LLFloaterPay* self = (LLFloaterPay*)userdata; + if (self) + { + S32 price; + LLUUID target; + + msg->getUUIDFast(_PREHASH_ObjectData,_PREHASH_ObjectID,target); + if (target != self->mTargetUUID) + { + // This is a message for a different object's pay info + return; + } + + msg->getS32Fast(_PREHASH_ObjectData,_PREHASH_DefaultPayPrice,price); + + if (PAY_PRICE_HIDE == price) + { + self->getChildView("amount")->setVisible(false); + self->getChildView("pay btn")->setVisible(false); + self->getChildView("amount text")->setVisible(false); + } + else if (PAY_PRICE_DEFAULT == price) + { + self->getChildView("amount")->setVisible(true); + self->getChildView("pay btn")->setVisible(true); + self->getChildView("amount text")->setVisible(true); + } + else + { + // PAY_PRICE_HIDE and PAY_PRICE_DEFAULT are negative values + // So we take the absolute value here after we have checked for those cases + + self->getChildView("amount")->setVisible(true); + self->getChildView("pay btn")->setVisible(true); + self->getChildView("pay btn")->setEnabled(true); + self->getChildView("amount text")->setVisible(true); + + self->getChild("amount")->setValue(llformat("%d", llabs(price))); + } + + S32 num_blocks = msg->getNumberOfBlocksFast(_PREHASH_ButtonData); + S32 i = 0; + if (num_blocks > MAX_PAY_BUTTONS) num_blocks = MAX_PAY_BUTTONS; + + S32 max_pay_amount = 0; + S32 padding_required = 0; + + for (i=0;igetS32Fast(_PREHASH_ButtonData,_PREHASH_PayButton,pay_button,i); + if (pay_button > 0) + { + std::string button_str = "L$"; + button_str += LLResMgr::getInstance()->getMonetaryString( pay_button ); + + self->mQuickPayButton[i]->setLabelSelected(button_str); + self->mQuickPayButton[i]->setLabelUnselected(button_str); + self->mQuickPayButton[i]->setVisible(true); + self->mQuickPayInfo[i]->mAmount = pay_button; + + if ( pay_button > max_pay_amount ) + { + max_pay_amount = pay_button; + } + } + else + { + self->mQuickPayButton[i]->setVisible(false); + } + } + + // build a string containing the maximum value and calc nerw button width from it. + std::string balance_str = "L$"; + balance_str += LLResMgr::getInstance()->getMonetaryString( max_pay_amount ); + const LLFontGL* font = LLFontGL::getFontSansSerif(); + S32 new_button_width = font->getWidth( std::string(balance_str)); + new_button_width += ( 12 + 12 ); // padding + + // dialong is sized for 2 digit pay amounts - larger pay values need to be scaled + const S32 threshold = 100000; + if ( max_pay_amount >= threshold ) + { + S32 num_digits_threshold = (S32)log10((double)threshold) + 1; + S32 num_digits_max = (S32)log10((double)max_pay_amount) + 1; + + // calculate the extra width required by 2 buttons with max amount and some commas + padding_required = ( num_digits_max - num_digits_threshold + ( num_digits_max / 3 ) ) * font->getWidth( std::string("0") ); + }; + + // change in button width + S32 button_delta = new_button_width - FASTPAY_BUTTON_WIDTH; + if ( button_delta < 0 ) + button_delta = 0; + + // now we know the maximum amount, we can resize all the buttons to be + for (i=0;imQuickPayButton[i]->getRect(); + + // RHS button colum needs to move further because LHS changed too + if ( i % 2 ) + { + r.setCenterAndSize( r.getCenterX() + ( button_delta * 3 ) / 2 , + r.getCenterY(), + r.getWidth() + button_delta, + r.getHeight() ); + } + else + { + r.setCenterAndSize( r.getCenterX() + button_delta / 2, + r.getCenterY(), + r.getWidth() + button_delta, + r.getHeight() ); + } + self->mQuickPayButton[i]->setRect( r ); + } + + for (i=num_blocks;imQuickPayButton[i]->setVisible(false); + } + + self->reshape( self->getRect().getWidth() + padding_required, self->getRect().getHeight(), false ); + } + msg->setHandlerFunc("PayPriceReply",NULL,NULL); +} + +// static +void LLFloaterPay::payViaObject(money_callback callback, LLSafeHandle selection) +{ + // Object that lead to the selection, may be child + LLViewerObject* object = selection->getPrimaryObject(); + if (!object) + return; + + LLFloaterPay *floater = LLFloaterReg::showTypedInstance("pay_object", LLSD(object->getID())); + if (!floater) + return; + + floater->setCallback(callback); + // Hold onto the selection until we close + floater->mObjectSelection = selection; + + LLSelectNode* node = selection->getFirstRootNode(); + if (!node) + { + // object no longer exists + LLNotificationsUtil::add("PayObjectFailed"); + floater->closeFloater(); + return; + } + + LLHost target_region = object->getRegion()->getHost(); + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_RequestPayPrice); + msg->nextBlockFast(_PREHASH_ObjectData); + msg->addUUIDFast(_PREHASH_ObjectID, object->getID()); + msg->sendReliable(target_region); + msg->setHandlerFuncFast(_PREHASH_PayPriceReply, processPayPriceReply,(void **)floater); + + LLUUID owner_id; + bool is_group = false; + node->mPermissions->getOwnership(owner_id, is_group); + + floater->getChild("object_name_text")->setValue(node->mName); + + floater->finishPayUI(owner_id, is_group); +} + +void LLFloaterPay::payDirectly(money_callback callback, + const LLUUID& target_id, + bool is_group) +{ + LLFloaterPay *floater = LLFloaterReg::showTypedInstance("pay_resident", LLSD(target_id)); + if (!floater) + return; + + floater->setCallback(callback); + floater->mObjectSelection = NULL; + + floater->getChildView("amount")->setVisible(true); + floater->getChildView("pay btn")->setVisible(true); + floater->getChildView("amount text")->setVisible(true); + + for(S32 i=0;imQuickPayButton[i]->setVisible(true); + } + + floater->finishPayUI(target_id, is_group); +} + +bool LLFloaterPay::payConfirmationCallback(const LLSD& notification, const LLSD& response, give_money_ptr info) +{ + if (!info.get() || !info->mFloater) + { + return false; + } + + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) + { + info->mFloater->give(info->mAmount); + info->mFloater->closeFloater(); + } + + return false; +} + +void LLFloaterPay::finishPayUI(const LLUUID& target_id, bool is_group) +{ + std::string slurl; + if (is_group) + { + setTitle(getString("payee_group")); + slurl = LLSLURL("group", target_id, "inspect").getSLURLString(); + } + else + { + setTitle(getString("payee_resident")); + slurl = LLSLURL("agent", target_id, "inspect").getSLURLString(); + } + getChild("payee_name")->setText(slurl); + + // Make sure the amount field has focus + + LLLineEditor* amount = getChild("amount"); + amount->setFocus(true); + amount->selectAll(); + + mTargetIsGroup = is_group; +} + +// static +void LLFloaterPay::onCancel(void* data) +{ + LLFloaterPay* self = reinterpret_cast(data); + if(self) + { + self->closeFloater(); + } +} + +// static +void LLFloaterPay::onKeystroke(LLLineEditor*, void* data) +{ + LLFloaterPay* self = reinterpret_cast(data); + if(self) + { + // enable the Pay button when amount is non-empty and positive, disable otherwise + std::string amtstr = self->getChild("amount")->getValue().asString(); + self->getChildView("pay btn")->setEnabled(!amtstr.empty() && atoi(amtstr.c_str()) > 0); + } +} + +// static +void LLFloaterPay::onGive(give_money_ptr info) +{ + if (!info.get() || !info->mFloater) + { + return; + } + + LLFloaterPay* floater = info->mFloater; + S32 amount = info->mAmount; + if (amount == 0) + { + LLUICtrl* text_field = floater->getChild("amount"); + if (!text_field) + { + return; + } + amount = atoi(text_field->getValue().asString().c_str()); + } + + if (amount > PAY_AMOUNT_NOTIFICATION && gStatusBar && gStatusBar->getBalance() > amount) + { + LLUUID payee_id = LLUUID::null; + bool is_group = false; + if (floater->mObjectSelection.notNull()) + { + LLSelectNode* node = floater->mObjectSelection->getFirstRootNode(); + if (node) + { + node->mPermissions->getOwnership(payee_id, is_group); + } + else + { + // object no longer exists + LLNotificationsUtil::add("PayObjectFailed"); + floater->closeFloater(); + return; + } + } + else + { + is_group = floater->mTargetIsGroup; + payee_id = floater->mTargetUUID; + } + + LLSD args; + args["TARGET"] = LLSLURL(is_group ? "group" : "agent", payee_id, "completename").getSLURLString(); + args["AMOUNT"] = amount; + + LLNotificationsUtil::add("PayConfirmation", args, LLSD(), boost::bind(&LLFloaterPay::payConfirmationCallback, _1, _2, info)); + } + else + { + floater->give(amount); + floater->closeFloater(); + } +} + +void LLFloaterPay::give(S32 amount) +{ + if(mCallback) + { + // if the amount is 0, that menas that we should use the + // text field. + if(amount == 0) + { + amount = atoi(getChild("amount")->getValue().asString().c_str()); + } + + // Try to pay an object. + if (mObjectSelection.notNull()) + { + LLViewerObject* dest_object = gObjectList.findObject(mTargetUUID); + if(dest_object) + { + LLViewerRegion* region = dest_object->getRegion(); + if (region) + { + // Find the name of the root object + LLSelectNode* node = mObjectSelection->getFirstRootNode(); + std::string object_name; + if (node) + { + object_name = node->mName; + } + S32 tx_type = TRANS_PAY_OBJECT; + if(dest_object->isAvatar()) tx_type = TRANS_GIFT; + mCallback(mTargetUUID, region, amount, false, tx_type, object_name); + mObjectSelection = NULL; + + // request the object owner in order to check if the owner needs to be unmuted + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_RequestObjectPropertiesFamily); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ObjectData); + msg->addU32Fast(_PREHASH_RequestFlags, OBJECT_PAY_REQUEST ); + msg->addUUIDFast(_PREHASH_ObjectID, mTargetUUID); + msg->sendReliable( region->getHost() ); + } + } + else + { + LLNotificationsUtil::add("PayObjectFailed"); + } + } + else + { + // just transfer the L$ + std::string paymentMessage(getChild("payment_message")->getValue().asString()); + mCallback(mTargetUUID, gAgent.getRegion(), amount, mTargetIsGroup, TRANS_GIFT, (paymentMessage.empty() ? LLStringUtil::null : paymentMessage)); + + // check if the payee needs to be unmuted + LLMuteList::getInstance()->autoRemove(mTargetUUID, LLMuteList::AR_MONEY); + } + } +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Namespace LLFloaterPayUtil +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +void LLFloaterPayUtil::registerFloater() +{ + // Sneaky, use same code but different XML for dialogs + LLFloaterReg::add("pay_resident", "floater_pay.xml", + &LLFloaterReg::build); + LLFloaterReg::add("pay_object", "floater_pay_object.xml", + &LLFloaterReg::build); +} + +void LLFloaterPayUtil::payViaObject(money_callback callback, + LLSafeHandle selection) +{ + LLFloaterPay::payViaObject(callback, selection); +} + +void LLFloaterPayUtil::payDirectly(money_callback callback, + const LLUUID& target_id, + bool is_group) +{ + LLFloaterPay::payDirectly(callback, target_id, is_group); +} diff --git a/indra/newview/llfloaterpay.h b/indra/newview/llfloaterpay.h index f6710dc620..190bf9f013 100644 --- a/indra/newview/llfloaterpay.h +++ b/indra/newview/llfloaterpay.h @@ -1,55 +1,55 @@ -/** - * @file llfloaterpay.h - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLFLOATERPAY_H -#define LLFLOATERPAY_H - -#include "llsafehandle.h" - -class LLObjectSelection; -class LLUUID; -class LLViewerRegion; - -typedef void (*money_callback)(const LLUUID&, LLViewerRegion*,S32,bool,S32,const std::string&); - -namespace LLFloaterPayUtil -{ - /// Register with LLFloaterReg - void registerFloater(); - - /// Pay into an in-world object, which will trigger scripts and eventually - /// transfer the L$ to the resident or group that owns the object. - /// Objects must be selected. Recipient (primary) object may be a child. - void payViaObject(money_callback callback, - LLSafeHandle selection); - - /// Pay an avatar or group directly, not via an object in the world. - /// Scripts are not notified, L$ can be direcly transferred. - void payDirectly(money_callback callback, - const LLUUID& target_id, - bool is_group); -} - -#endif // LLFLOATERPAY_H +/** + * @file llfloaterpay.h + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLFLOATERPAY_H +#define LLFLOATERPAY_H + +#include "llsafehandle.h" + +class LLObjectSelection; +class LLUUID; +class LLViewerRegion; + +typedef void (*money_callback)(const LLUUID&, LLViewerRegion*,S32,bool,S32,const std::string&); + +namespace LLFloaterPayUtil +{ + /// Register with LLFloaterReg + void registerFloater(); + + /// Pay into an in-world object, which will trigger scripts and eventually + /// transfer the L$ to the resident or group that owns the object. + /// Objects must be selected. Recipient (primary) object may be a child. + void payViaObject(money_callback callback, + LLSafeHandle selection); + + /// Pay an avatar or group directly, not via an object in the world. + /// Scripts are not notified, L$ can be direcly transferred. + void payDirectly(money_callback callback, + const LLUUID& target_id, + bool is_group); +} + +#endif // LLFLOATERPAY_H diff --git a/indra/newview/llfloaterperformance.h b/indra/newview/llfloaterperformance.h index ae653c25d9..089a508455 100644 --- a/indra/newview/llfloaterperformance.h +++ b/indra/newview/llfloaterperformance.h @@ -1,104 +1,104 @@ -/** - * @file llfloaterperformance.h - * - * $LicenseInfo:firstyear=2021&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2021, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERPERFORMANCE_H -#define LL_LLFLOATERPERFORMANCE_H - -#include "llfloater.h" -#include "lllistcontextmenu.h" - -class LLCharacter; -class LLNameListCtrl; - -class LLFloaterPerformance : public LLFloater -{ -public: - LLFloaterPerformance(const LLSD& key); - virtual ~LLFloaterPerformance(); - - /*virtual*/ bool postBuild(); - /*virtual*/ void draw(); - - void showSelectedPanel(LLPanel* selected_panel); - void showMainPanel(); - void hidePanels(); - void showAutoadjustmentsPanel(); - - void detachItem(const LLUUID& item_id); - - void onAvatarListRightClick(LLUICtrl* ctrl, S32 x, S32 y); - - void onCustomAction (const LLSD& userdata, const LLUUID& av_id); - bool isActionChecked(const LLSD& userdata, const LLUUID& av_id); - -private: - void initBackBtn(LLPanel* panel); - void populateHUDList(); - void populateObjectList(); - void populateNearbyList(); - void setFPSText(); - - void onClickAdvanced(); - void onClickDefaults(); - void onChangeQuality(const LLSD& data); - void onClickHideAvatars(); - void onClickExceptions(); - void onClickShadows(); - void onClickAdvancedLighting(); - - void startAutotune(); - void stopAutotune(); - void updateAutotuneCtrls(bool autotune_enabled); - void enableAutotuneWarning(); - - void updateMaxRenderTime(); - - static void changeQualityLevel(const std::string& notif); - - LLPanel* mMainPanel; - LLPanel* mNearbyPanel; - LLPanel* mComplexityPanel; - LLPanel* mHUDsPanel; - LLPanel* mSettingsPanel; - LLPanel* mAutoadjustmentsPanel; - LLNameListCtrl* mHUDList; - LLNameListCtrl* mObjectList; - LLNameListCtrl* mNearbyList; - - LLButton* mStartAutotuneBtn; - LLButton* mStopAutotuneBtn; - - LLListContextMenu* mContextMenu; - - LLTimer* mUpdateTimer; - - // maximum GPU time of nearby avatars in ms according to LLWorld::getNearbyAvatarsAndMaxGPUTime - // -1.f if no profile has happened yet - F32 mNearbyMaxGPUTime = -1.f; - - boost::signals2::connection mMaxARTChangedSignal; -}; - -#endif // LL_LLFLOATERPERFORMANCE_H +/** + * @file llfloaterperformance.h + * + * $LicenseInfo:firstyear=2021&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2021, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERPERFORMANCE_H +#define LL_LLFLOATERPERFORMANCE_H + +#include "llfloater.h" +#include "lllistcontextmenu.h" + +class LLCharacter; +class LLNameListCtrl; + +class LLFloaterPerformance : public LLFloater +{ +public: + LLFloaterPerformance(const LLSD& key); + virtual ~LLFloaterPerformance(); + + /*virtual*/ bool postBuild(); + /*virtual*/ void draw(); + + void showSelectedPanel(LLPanel* selected_panel); + void showMainPanel(); + void hidePanels(); + void showAutoadjustmentsPanel(); + + void detachItem(const LLUUID& item_id); + + void onAvatarListRightClick(LLUICtrl* ctrl, S32 x, S32 y); + + void onCustomAction (const LLSD& userdata, const LLUUID& av_id); + bool isActionChecked(const LLSD& userdata, const LLUUID& av_id); + +private: + void initBackBtn(LLPanel* panel); + void populateHUDList(); + void populateObjectList(); + void populateNearbyList(); + void setFPSText(); + + void onClickAdvanced(); + void onClickDefaults(); + void onChangeQuality(const LLSD& data); + void onClickHideAvatars(); + void onClickExceptions(); + void onClickShadows(); + void onClickAdvancedLighting(); + + void startAutotune(); + void stopAutotune(); + void updateAutotuneCtrls(bool autotune_enabled); + void enableAutotuneWarning(); + + void updateMaxRenderTime(); + + static void changeQualityLevel(const std::string& notif); + + LLPanel* mMainPanel; + LLPanel* mNearbyPanel; + LLPanel* mComplexityPanel; + LLPanel* mHUDsPanel; + LLPanel* mSettingsPanel; + LLPanel* mAutoadjustmentsPanel; + LLNameListCtrl* mHUDList; + LLNameListCtrl* mObjectList; + LLNameListCtrl* mNearbyList; + + LLButton* mStartAutotuneBtn; + LLButton* mStopAutotuneBtn; + + LLListContextMenu* mContextMenu; + + LLTimer* mUpdateTimer; + + // maximum GPU time of nearby avatars in ms according to LLWorld::getNearbyAvatarsAndMaxGPUTime + // -1.f if no profile has happened yet + F32 mNearbyMaxGPUTime = -1.f; + + boost::signals2::connection mMaxARTChangedSignal; +}; + +#endif // LL_LLFLOATERPERFORMANCE_H diff --git a/indra/newview/llfloaterperms.cpp b/indra/newview/llfloaterperms.cpp index bab780c1e7..7311f0deb6 100644 --- a/indra/newview/llfloaterperms.cpp +++ b/indra/newview/llfloaterperms.cpp @@ -1,306 +1,306 @@ -/** - * @file llfloaterperms.cpp - * @brief Asset creation permission preferences. - * @author Jonathan Yap - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llcheckboxctrl.h" -#include "llfloaterperms.h" -#include "llviewercontrol.h" -#include "llviewerwindow.h" -#include "lluictrlfactory.h" -#include "llpermissions.h" -#include "llagent.h" -#include "llviewerregion.h" -#include "llnotificationsutil.h" -#include "llsdserialize.h" -#include "llvoavatar.h" -#include "llcorehttputil.h" -#include "lleventfilter.h" -#include "lleventcoro.h" - -LLFloaterPerms::LLFloaterPerms(const LLSD& seed) -: LLFloater(seed) -{ -} - -bool LLFloaterPerms::postBuild() -{ - return true; -} - -//static -U32 LLFloaterPerms::getGroupPerms(std::string prefix) -{ - return gSavedSettings.getBOOL(prefix+"ShareWithGroup") ? PERM_COPY | PERM_MOVE | PERM_MODIFY : PERM_NONE; -} - -//static -U32 LLFloaterPerms::getEveryonePerms(std::string prefix) -{ - return gSavedSettings.getBOOL(prefix+"EveryoneCopy") ? PERM_COPY : PERM_NONE; -} - -//static -U32 LLFloaterPerms::getNextOwnerPerms(std::string prefix) -{ - U32 flags = PERM_MOVE; - if ( gSavedSettings.getBOOL(prefix+"NextOwnerCopy") ) - { - flags |= PERM_COPY; - } - if ( gSavedSettings.getBOOL(prefix+"NextOwnerModify") ) - { - flags |= PERM_MODIFY; - } - if ( gSavedSettings.getBOOL(prefix+"NextOwnerTransfer") ) - { - flags |= PERM_TRANSFER; - } - return flags; -} - -//static -U32 LLFloaterPerms::getNextOwnerPermsInverted(std::string prefix) -{ - // Sets bits for permissions that are off - U32 flags = PERM_MOVE; - if ( !gSavedSettings.getBOOL(prefix+"NextOwnerCopy") ) - { - flags |= PERM_COPY; - } - if ( !gSavedSettings.getBOOL(prefix+"NextOwnerModify") ) - { - flags |= PERM_MODIFY; - } - if ( !gSavedSettings.getBOOL(prefix+"NextOwnerTransfer") ) - { - flags |= PERM_TRANSFER; - } - return flags; -} - -static bool mCapSent = false; - -LLFloaterPermsDefault::LLFloaterPermsDefault(const LLSD& seed) - : LLFloater(seed) -{ - mCommitCallbackRegistrar.add("PermsDefault.Copy", boost::bind(&LLFloaterPermsDefault::onCommitCopy, this, _2)); - mCommitCallbackRegistrar.add("PermsDefault.OK", boost::bind(&LLFloaterPermsDefault::onClickOK, this)); - mCommitCallbackRegistrar.add("PermsDefault.Cancel", boost::bind(&LLFloaterPermsDefault::onClickCancel, this)); -} - - -// String equivalents of enum Categories - initialization order must match enum order! -const std::string LLFloaterPermsDefault::sCategoryNames[CAT_LAST] = -{ - "Objects", - "Uploads", - "Scripts", - "Notecards", - "Gestures", - "Wearables", - "Settings", - "Materials" -}; - -bool LLFloaterPermsDefault::postBuild() -{ - if(!gSavedSettings.getBOOL("DefaultUploadPermissionsConverted")) - { - gSavedSettings.setBOOL("UploadsEveryoneCopy", gSavedSettings.getBOOL("EveryoneCopy")); - gSavedSettings.setBOOL("UploadsNextOwnerCopy", gSavedSettings.getBOOL("NextOwnerCopy")); - gSavedSettings.setBOOL("UploadsNextOwnerModify", gSavedSettings.getBOOL("NextOwnerModify")); - gSavedSettings.setBOOL("UploadsNextOwnerTransfer", gSavedSettings.getBOOL("NextOwnerTransfer")); - gSavedSettings.setBOOL("UploadsShareWithGroup", gSavedSettings.getBOOL("ShareWithGroup")); - gSavedSettings.setBOOL("DefaultUploadPermissionsConverted", true); - } - - mCloseSignal.connect(boost::bind(&LLFloaterPermsDefault::cancel, this)); - - refresh(); - - return true; -} - -void LLFloaterPermsDefault::onClickOK() -{ - ok(); - closeFloater(); -} - -void LLFloaterPermsDefault::onClickCancel() -{ - cancel(); - closeFloater(); -} - -void LLFloaterPermsDefault::onCommitCopy(const LLSD& user_data) -{ - // Implements fair use - std::string prefix = user_data.asString(); - - bool copyable = gSavedSettings.getBOOL(prefix+"NextOwnerCopy"); - if(!copyable) - { - gSavedSettings.setBOOL(prefix+"NextOwnerTransfer", true); - } - LLCheckBoxCtrl* xfer = getChild(prefix+"_transfer"); - xfer->setEnabled(copyable); -} - -constexpr int MAX_HTTP_RETRIES = 5; -constexpr float RETRY_TIMEOUT = 5.0; - -void LLFloaterPermsDefault::sendInitialPerms() -{ - if(!mCapSent) - { - updateCap(); - } -} - -void LLFloaterPermsDefault::updateCap() -{ - if (!gAgent.getRegion()) - { - LL_WARNS("Avatar") << "Region not set, cannot request capability update" << LL_ENDL; - return; - } - std::string object_url = gAgent.getRegion()->getCapability("AgentPreferences"); - - if(!object_url.empty()) - { - LLCoros::instance().launch("LLFloaterPermsDefault::updateCapCoro", - boost::bind(&LLFloaterPermsDefault::updateCapCoro, object_url)); - } - else - { - LL_DEBUGS("ObjectPermissionsFloater") << "AgentPreferences cap not available." << LL_ENDL; - } -} - -/*static*/ -void LLFloaterPermsDefault::updateCapCoro(std::string url) -{ - int retryCount = 0; - std::string previousReason; - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("genericPostCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD postData = LLSD::emptyMap(); - postData["default_object_perm_masks"]["Group"] = - (LLSD::Integer)LLFloaterPerms::getGroupPerms(sCategoryNames[CAT_OBJECTS]); - postData["default_object_perm_masks"]["Everyone"] = - (LLSD::Integer)LLFloaterPerms::getEveryonePerms(sCategoryNames[CAT_OBJECTS]); - postData["default_object_perm_masks"]["NextOwner"] = - (LLSD::Integer)LLFloaterPerms::getNextOwnerPerms(sCategoryNames[CAT_OBJECTS]); - - { - LL_DEBUGS("ObjectPermissionsFloater") << "Sending default permissions to '" - << url << "'\n"; - std::ostringstream sent_perms_log; - LLSDSerialize::toPrettyXML(postData, sent_perms_log); - LL_CONT << sent_perms_log.str() << LL_ENDL; - } - - while (true) - { - ++retryCount; - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - const std::string& reason = status.toString(); - // Do not display the same error more than once in a row - if ((reason != previousReason) && mCapSent) - { - previousReason = reason; - LLSD args; - args["REASON"] = reason; - LLNotificationsUtil::add("DefaultObjectPermissions", args); - } - - llcoro::suspendUntilTimeout(RETRY_TIMEOUT); - if (retryCount < MAX_HTTP_RETRIES) - continue; - - LL_WARNS("ObjectPermissionsFloater") << "Unable to send default permissions. Giving up for now." << LL_ENDL; - return; - } - break; - } - - // Since we have had a successful POST call be sure to display the next error message - // even if it is the same as a previous one. - previousReason.clear(); - LLFloaterPermsDefault::setCapSent(true); - LL_INFOS("ObjectPermissionsFloater") << "Default permissions successfully sent to simulator" << LL_ENDL; -} - -void LLFloaterPermsDefault::setCapSent(bool cap_sent) -{ - mCapSent = cap_sent; -} - -void LLFloaterPermsDefault::ok() -{ -// Changes were already applied automatically to saved settings. -// Refreshing internal values makes it official. - refresh(); - -// We know some setting has changed but not which one. Just in case it was a setting for -// object permissions tell the server what the values are. - updateCap(); -} - -void LLFloaterPermsDefault::cancel() -{ - for (U32 iter = CAT_OBJECTS; iter < CAT_LAST; iter++) - { - gSavedSettings.setBOOL(sCategoryNames[iter]+"NextOwnerCopy", mNextOwnerCopy[iter]); - gSavedSettings.setBOOL(sCategoryNames[iter]+"NextOwnerModify", mNextOwnerModify[iter]); - gSavedSettings.setBOOL(sCategoryNames[iter]+"NextOwnerTransfer", mNextOwnerTransfer[iter]); - gSavedSettings.setBOOL(sCategoryNames[iter]+"ShareWithGroup", mShareWithGroup[iter]); - gSavedSettings.setBOOL(sCategoryNames[iter]+"EveryoneCopy", mEveryoneCopy[iter]); - } -} - -void LLFloaterPermsDefault::refresh() -{ - for (U32 iter = CAT_OBJECTS; iter < CAT_LAST; iter++) - { - mShareWithGroup[iter] = gSavedSettings.getBOOL(sCategoryNames[iter]+"ShareWithGroup"); - mEveryoneCopy[iter] = gSavedSettings.getBOOL(sCategoryNames[iter]+"EveryoneCopy"); - mNextOwnerCopy[iter] = gSavedSettings.getBOOL(sCategoryNames[iter]+"NextOwnerCopy"); - mNextOwnerModify[iter] = gSavedSettings.getBOOL(sCategoryNames[iter]+"NextOwnerModify"); - mNextOwnerTransfer[iter] = gSavedSettings.getBOOL(sCategoryNames[iter]+"NextOwnerTransfer"); - } -} +/** + * @file llfloaterperms.cpp + * @brief Asset creation permission preferences. + * @author Jonathan Yap + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llcheckboxctrl.h" +#include "llfloaterperms.h" +#include "llviewercontrol.h" +#include "llviewerwindow.h" +#include "lluictrlfactory.h" +#include "llpermissions.h" +#include "llagent.h" +#include "llviewerregion.h" +#include "llnotificationsutil.h" +#include "llsdserialize.h" +#include "llvoavatar.h" +#include "llcorehttputil.h" +#include "lleventfilter.h" +#include "lleventcoro.h" + +LLFloaterPerms::LLFloaterPerms(const LLSD& seed) +: LLFloater(seed) +{ +} + +bool LLFloaterPerms::postBuild() +{ + return true; +} + +//static +U32 LLFloaterPerms::getGroupPerms(std::string prefix) +{ + return gSavedSettings.getBOOL(prefix+"ShareWithGroup") ? PERM_COPY | PERM_MOVE | PERM_MODIFY : PERM_NONE; +} + +//static +U32 LLFloaterPerms::getEveryonePerms(std::string prefix) +{ + return gSavedSettings.getBOOL(prefix+"EveryoneCopy") ? PERM_COPY : PERM_NONE; +} + +//static +U32 LLFloaterPerms::getNextOwnerPerms(std::string prefix) +{ + U32 flags = PERM_MOVE; + if ( gSavedSettings.getBOOL(prefix+"NextOwnerCopy") ) + { + flags |= PERM_COPY; + } + if ( gSavedSettings.getBOOL(prefix+"NextOwnerModify") ) + { + flags |= PERM_MODIFY; + } + if ( gSavedSettings.getBOOL(prefix+"NextOwnerTransfer") ) + { + flags |= PERM_TRANSFER; + } + return flags; +} + +//static +U32 LLFloaterPerms::getNextOwnerPermsInverted(std::string prefix) +{ + // Sets bits for permissions that are off + U32 flags = PERM_MOVE; + if ( !gSavedSettings.getBOOL(prefix+"NextOwnerCopy") ) + { + flags |= PERM_COPY; + } + if ( !gSavedSettings.getBOOL(prefix+"NextOwnerModify") ) + { + flags |= PERM_MODIFY; + } + if ( !gSavedSettings.getBOOL(prefix+"NextOwnerTransfer") ) + { + flags |= PERM_TRANSFER; + } + return flags; +} + +static bool mCapSent = false; + +LLFloaterPermsDefault::LLFloaterPermsDefault(const LLSD& seed) + : LLFloater(seed) +{ + mCommitCallbackRegistrar.add("PermsDefault.Copy", boost::bind(&LLFloaterPermsDefault::onCommitCopy, this, _2)); + mCommitCallbackRegistrar.add("PermsDefault.OK", boost::bind(&LLFloaterPermsDefault::onClickOK, this)); + mCommitCallbackRegistrar.add("PermsDefault.Cancel", boost::bind(&LLFloaterPermsDefault::onClickCancel, this)); +} + + +// String equivalents of enum Categories - initialization order must match enum order! +const std::string LLFloaterPermsDefault::sCategoryNames[CAT_LAST] = +{ + "Objects", + "Uploads", + "Scripts", + "Notecards", + "Gestures", + "Wearables", + "Settings", + "Materials" +}; + +bool LLFloaterPermsDefault::postBuild() +{ + if(!gSavedSettings.getBOOL("DefaultUploadPermissionsConverted")) + { + gSavedSettings.setBOOL("UploadsEveryoneCopy", gSavedSettings.getBOOL("EveryoneCopy")); + gSavedSettings.setBOOL("UploadsNextOwnerCopy", gSavedSettings.getBOOL("NextOwnerCopy")); + gSavedSettings.setBOOL("UploadsNextOwnerModify", gSavedSettings.getBOOL("NextOwnerModify")); + gSavedSettings.setBOOL("UploadsNextOwnerTransfer", gSavedSettings.getBOOL("NextOwnerTransfer")); + gSavedSettings.setBOOL("UploadsShareWithGroup", gSavedSettings.getBOOL("ShareWithGroup")); + gSavedSettings.setBOOL("DefaultUploadPermissionsConverted", true); + } + + mCloseSignal.connect(boost::bind(&LLFloaterPermsDefault::cancel, this)); + + refresh(); + + return true; +} + +void LLFloaterPermsDefault::onClickOK() +{ + ok(); + closeFloater(); +} + +void LLFloaterPermsDefault::onClickCancel() +{ + cancel(); + closeFloater(); +} + +void LLFloaterPermsDefault::onCommitCopy(const LLSD& user_data) +{ + // Implements fair use + std::string prefix = user_data.asString(); + + bool copyable = gSavedSettings.getBOOL(prefix+"NextOwnerCopy"); + if(!copyable) + { + gSavedSettings.setBOOL(prefix+"NextOwnerTransfer", true); + } + LLCheckBoxCtrl* xfer = getChild(prefix+"_transfer"); + xfer->setEnabled(copyable); +} + +constexpr int MAX_HTTP_RETRIES = 5; +constexpr float RETRY_TIMEOUT = 5.0; + +void LLFloaterPermsDefault::sendInitialPerms() +{ + if(!mCapSent) + { + updateCap(); + } +} + +void LLFloaterPermsDefault::updateCap() +{ + if (!gAgent.getRegion()) + { + LL_WARNS("Avatar") << "Region not set, cannot request capability update" << LL_ENDL; + return; + } + std::string object_url = gAgent.getRegion()->getCapability("AgentPreferences"); + + if(!object_url.empty()) + { + LLCoros::instance().launch("LLFloaterPermsDefault::updateCapCoro", + boost::bind(&LLFloaterPermsDefault::updateCapCoro, object_url)); + } + else + { + LL_DEBUGS("ObjectPermissionsFloater") << "AgentPreferences cap not available." << LL_ENDL; + } +} + +/*static*/ +void LLFloaterPermsDefault::updateCapCoro(std::string url) +{ + int retryCount = 0; + std::string previousReason; + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("genericPostCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD postData = LLSD::emptyMap(); + postData["default_object_perm_masks"]["Group"] = + (LLSD::Integer)LLFloaterPerms::getGroupPerms(sCategoryNames[CAT_OBJECTS]); + postData["default_object_perm_masks"]["Everyone"] = + (LLSD::Integer)LLFloaterPerms::getEveryonePerms(sCategoryNames[CAT_OBJECTS]); + postData["default_object_perm_masks"]["NextOwner"] = + (LLSD::Integer)LLFloaterPerms::getNextOwnerPerms(sCategoryNames[CAT_OBJECTS]); + + { + LL_DEBUGS("ObjectPermissionsFloater") << "Sending default permissions to '" + << url << "'\n"; + std::ostringstream sent_perms_log; + LLSDSerialize::toPrettyXML(postData, sent_perms_log); + LL_CONT << sent_perms_log.str() << LL_ENDL; + } + + while (true) + { + ++retryCount; + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + const std::string& reason = status.toString(); + // Do not display the same error more than once in a row + if ((reason != previousReason) && mCapSent) + { + previousReason = reason; + LLSD args; + args["REASON"] = reason; + LLNotificationsUtil::add("DefaultObjectPermissions", args); + } + + llcoro::suspendUntilTimeout(RETRY_TIMEOUT); + if (retryCount < MAX_HTTP_RETRIES) + continue; + + LL_WARNS("ObjectPermissionsFloater") << "Unable to send default permissions. Giving up for now." << LL_ENDL; + return; + } + break; + } + + // Since we have had a successful POST call be sure to display the next error message + // even if it is the same as a previous one. + previousReason.clear(); + LLFloaterPermsDefault::setCapSent(true); + LL_INFOS("ObjectPermissionsFloater") << "Default permissions successfully sent to simulator" << LL_ENDL; +} + +void LLFloaterPermsDefault::setCapSent(bool cap_sent) +{ + mCapSent = cap_sent; +} + +void LLFloaterPermsDefault::ok() +{ +// Changes were already applied automatically to saved settings. +// Refreshing internal values makes it official. + refresh(); + +// We know some setting has changed but not which one. Just in case it was a setting for +// object permissions tell the server what the values are. + updateCap(); +} + +void LLFloaterPermsDefault::cancel() +{ + for (U32 iter = CAT_OBJECTS; iter < CAT_LAST; iter++) + { + gSavedSettings.setBOOL(sCategoryNames[iter]+"NextOwnerCopy", mNextOwnerCopy[iter]); + gSavedSettings.setBOOL(sCategoryNames[iter]+"NextOwnerModify", mNextOwnerModify[iter]); + gSavedSettings.setBOOL(sCategoryNames[iter]+"NextOwnerTransfer", mNextOwnerTransfer[iter]); + gSavedSettings.setBOOL(sCategoryNames[iter]+"ShareWithGroup", mShareWithGroup[iter]); + gSavedSettings.setBOOL(sCategoryNames[iter]+"EveryoneCopy", mEveryoneCopy[iter]); + } +} + +void LLFloaterPermsDefault::refresh() +{ + for (U32 iter = CAT_OBJECTS; iter < CAT_LAST; iter++) + { + mShareWithGroup[iter] = gSavedSettings.getBOOL(sCategoryNames[iter]+"ShareWithGroup"); + mEveryoneCopy[iter] = gSavedSettings.getBOOL(sCategoryNames[iter]+"EveryoneCopy"); + mNextOwnerCopy[iter] = gSavedSettings.getBOOL(sCategoryNames[iter]+"NextOwnerCopy"); + mNextOwnerModify[iter] = gSavedSettings.getBOOL(sCategoryNames[iter]+"NextOwnerModify"); + mNextOwnerTransfer[iter] = gSavedSettings.getBOOL(sCategoryNames[iter]+"NextOwnerTransfer"); + } +} diff --git a/indra/newview/llfloaterperms.h b/indra/newview/llfloaterperms.h index 8ad7984f05..ad3776a8c6 100644 --- a/indra/newview/llfloaterperms.h +++ b/indra/newview/llfloaterperms.h @@ -1,98 +1,98 @@ -/** - * @file llfloaterperms.h - * @brief Asset creation permission preferences. - * @author Jonathan Yap - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERPERMPREFS_H -#define LL_LLFLOATERPERMPREFS_H - -#include "llfloater.h" -#include "lleventcoro.h" -#include "llcoros.h" - -class LLFloaterPerms : public LLFloater -{ - friend class LLFloaterReg; - -public: - bool postBuild() override; - - // Convenience methods to get current permission preference bitfields from saved settings: - static U32 getEveryonePerms(std::string prefix=""); // prefix + "EveryoneCopy" - static U32 getGroupPerms(std::string prefix=""); // prefix + "ShareWithGroup" - static U32 getNextOwnerPerms(std::string prefix=""); // bitfield for prefix + "NextOwner" + "Copy", "Modify", and "Transfer" - static U32 getNextOwnerPermsInverted(std::string prefix=""); - -private: - LLFloaterPerms(const LLSD& seed); - -}; - -class LLFloaterPermsDefault : public LLFloater -{ - friend class LLFloaterReg; - -public: - bool postBuild() override; - void ok(); - void cancel(); - void onClickOK(); - void onClickCancel(); - void onCommitCopy(const LLSD& user_data); - static void sendInitialPerms(); - static void updateCap(); - static void setCapSent(bool cap_sent); - -// Update instantiation of sCategoryNames in the .cpp file to match if you change this! -enum Categories -{ - CAT_OBJECTS, - CAT_UPLOADS, - CAT_SCRIPTS, - CAT_NOTECARDS, - CAT_GESTURES, - CAT_WEARABLES, - CAT_SETTINGS, - CAT_MATERIALS, - CAT_LAST -}; - -private: - LLFloaterPermsDefault(const LLSD& seed); - void refresh() override; - - static const std::string sCategoryNames[CAT_LAST]; - static void updateCapCoro(std::string url); - - - // cached values only for implementing cancel. - bool mShareWithGroup[CAT_LAST]; - bool mEveryoneCopy[CAT_LAST]; - bool mNextOwnerCopy[CAT_LAST]; - bool mNextOwnerModify[CAT_LAST]; - bool mNextOwnerTransfer[CAT_LAST]; -}; - -#endif +/** + * @file llfloaterperms.h + * @brief Asset creation permission preferences. + * @author Jonathan Yap + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERPERMPREFS_H +#define LL_LLFLOATERPERMPREFS_H + +#include "llfloater.h" +#include "lleventcoro.h" +#include "llcoros.h" + +class LLFloaterPerms : public LLFloater +{ + friend class LLFloaterReg; + +public: + bool postBuild() override; + + // Convenience methods to get current permission preference bitfields from saved settings: + static U32 getEveryonePerms(std::string prefix=""); // prefix + "EveryoneCopy" + static U32 getGroupPerms(std::string prefix=""); // prefix + "ShareWithGroup" + static U32 getNextOwnerPerms(std::string prefix=""); // bitfield for prefix + "NextOwner" + "Copy", "Modify", and "Transfer" + static U32 getNextOwnerPermsInverted(std::string prefix=""); + +private: + LLFloaterPerms(const LLSD& seed); + +}; + +class LLFloaterPermsDefault : public LLFloater +{ + friend class LLFloaterReg; + +public: + bool postBuild() override; + void ok(); + void cancel(); + void onClickOK(); + void onClickCancel(); + void onCommitCopy(const LLSD& user_data); + static void sendInitialPerms(); + static void updateCap(); + static void setCapSent(bool cap_sent); + +// Update instantiation of sCategoryNames in the .cpp file to match if you change this! +enum Categories +{ + CAT_OBJECTS, + CAT_UPLOADS, + CAT_SCRIPTS, + CAT_NOTECARDS, + CAT_GESTURES, + CAT_WEARABLES, + CAT_SETTINGS, + CAT_MATERIALS, + CAT_LAST +}; + +private: + LLFloaterPermsDefault(const LLSD& seed); + void refresh() override; + + static const std::string sCategoryNames[CAT_LAST]; + static void updateCapCoro(std::string url); + + + // cached values only for implementing cancel. + bool mShareWithGroup[CAT_LAST]; + bool mEveryoneCopy[CAT_LAST]; + bool mNextOwnerCopy[CAT_LAST]; + bool mNextOwnerModify[CAT_LAST]; + bool mNextOwnerTransfer[CAT_LAST]; +}; + +#endif diff --git a/indra/newview/llfloaterpostprocess.cpp b/indra/newview/llfloaterpostprocess.cpp index 51baf9c1fc..616c13cdc7 100644 --- a/indra/newview/llfloaterpostprocess.cpp +++ b/indra/newview/llfloaterpostprocess.cpp @@ -1,229 +1,229 @@ -/** - * @file llfloaterpostprocess.cpp - * @brief LLFloaterPostProcess class definition - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterpostprocess.h" - -#include "llsliderctrl.h" -#include "llcheckboxctrl.h" -#include "llnotificationsutil.h" -#include "lluictrlfactory.h" -#include "llviewerdisplay.h" -#include "llpostprocess.h" -#include "llcombobox.h" -#include "lllineeditor.h" -#include "llviewerwindow.h" - - -LLFloaterPostProcess::LLFloaterPostProcess(const LLSD& key) - : LLFloater(key) -{ -} - -LLFloaterPostProcess::~LLFloaterPostProcess() -{ - - -} -bool LLFloaterPostProcess::postBuild() -{ - /// Color Filter Callbacks - childSetCommitCallback("ColorFilterToggle", &LLFloaterPostProcess::onBoolToggle, (char*)"enable_color_filter"); - //childSetCommitCallback("ColorFilterGamma", &LLFloaterPostProcess::onFloatControlMoved, &(gPostProcess->tweaks.gamma())); - childSetCommitCallback("ColorFilterBrightness", &LLFloaterPostProcess::onFloatControlMoved, (char*)"brightness"); - childSetCommitCallback("ColorFilterSaturation", &LLFloaterPostProcess::onFloatControlMoved, (char*)"saturation"); - childSetCommitCallback("ColorFilterContrast", &LLFloaterPostProcess::onFloatControlMoved, (char*)"contrast"); - - childSetCommitCallback("ColorFilterBaseR", &LLFloaterPostProcess::onColorControlRMoved, (char*)"contrast_base"); - childSetCommitCallback("ColorFilterBaseG", &LLFloaterPostProcess::onColorControlGMoved, (char*)"contrast_base"); - childSetCommitCallback("ColorFilterBaseB", &LLFloaterPostProcess::onColorControlBMoved, (char*)"contrast_base"); - childSetCommitCallback("ColorFilterBaseI", &LLFloaterPostProcess::onColorControlIMoved, (char*)"contrast_base"); - - /// Night Vision Callbacks - childSetCommitCallback("NightVisionToggle", &LLFloaterPostProcess::onBoolToggle, (char*)"enable_night_vision"); - childSetCommitCallback("NightVisionBrightMult", &LLFloaterPostProcess::onFloatControlMoved, (char*)"brightness_multiplier"); - childSetCommitCallback("NightVisionNoiseSize", &LLFloaterPostProcess::onFloatControlMoved, (char*)"noise_size"); - childSetCommitCallback("NightVisionNoiseStrength", &LLFloaterPostProcess::onFloatControlMoved, (char*)"noise_strength"); - - /// Bloom Callbacks - childSetCommitCallback("BloomToggle", &LLFloaterPostProcess::onBoolToggle, (char*)"enable_bloom"); - childSetCommitCallback("BloomExtract", &LLFloaterPostProcess::onFloatControlMoved, (char*)"extract_low"); - childSetCommitCallback("BloomSize", &LLFloaterPostProcess::onFloatControlMoved, (char*)"bloom_width"); - childSetCommitCallback("BloomStrength", &LLFloaterPostProcess::onFloatControlMoved, (char*)"bloom_strength"); - - // Effect loading and saving. - LLComboBox* comboBox = getChild("PPEffectsCombo"); - getChild("PPLoadEffect")->setCommitCallback(boost::bind(&LLFloaterPostProcess::onLoadEffect, this, comboBox)); - comboBox->setCommitCallback(boost::bind(&LLFloaterPostProcess::onChangeEffectName, this, _1)); - - LLLineEditor* editBox = getChild("PPEffectNameEditor"); - getChild("PPSaveEffect")->setCommitCallback(boost::bind(&LLFloaterPostProcess::onSaveEffect, this, editBox)); - - syncMenu(); - return true; -} - -// Bool Toggle -void LLFloaterPostProcess::onBoolToggle(LLUICtrl* ctrl, void* userData) -{ - char const * boolVariableName = (char const *)userData; - - // check the bool - LLCheckBoxCtrl* cbCtrl = static_cast(ctrl); - gPostProcess->tweaks[boolVariableName] = cbCtrl->getValue(); -} - -// Float Moved -void LLFloaterPostProcess::onFloatControlMoved(LLUICtrl* ctrl, void* userData) -{ - char const * floatVariableName = (char const *)userData; - LLSliderCtrl* sldrCtrl = static_cast(ctrl); - gPostProcess->tweaks[floatVariableName] = sldrCtrl->getValue(); -} - -// Color Moved -void LLFloaterPostProcess::onColorControlRMoved(LLUICtrl* ctrl, void* userData) -{ - char const * floatVariableName = (char const *)userData; - LLSliderCtrl* sldrCtrl = static_cast(ctrl); - gPostProcess->tweaks[floatVariableName][0] = sldrCtrl->getValue(); -} - -// Color Moved -void LLFloaterPostProcess::onColorControlGMoved(LLUICtrl* ctrl, void* userData) -{ - char const * floatVariableName = (char const *)userData; - LLSliderCtrl* sldrCtrl = static_cast(ctrl); - gPostProcess->tweaks[floatVariableName][1] = sldrCtrl->getValue(); -} - -// Color Moved -void LLFloaterPostProcess::onColorControlBMoved(LLUICtrl* ctrl, void* userData) -{ - char const * floatVariableName = (char const *)userData; - LLSliderCtrl* sldrCtrl = static_cast(ctrl); - gPostProcess->tweaks[floatVariableName][2] = sldrCtrl->getValue(); -} - -// Color Moved -void LLFloaterPostProcess::onColorControlIMoved(LLUICtrl* ctrl, void* userData) -{ - char const * floatVariableName = (char const *)userData; - LLSliderCtrl* sldrCtrl = static_cast(ctrl); - gPostProcess->tweaks[floatVariableName][3] = sldrCtrl->getValue(); -} - -void LLFloaterPostProcess::onLoadEffect(LLComboBox* comboBox) -{ - LLSD::String effectName(comboBox->getSelectedValue().asString()); - - gPostProcess->setSelectedEffect(effectName); - - syncMenu(); -} - -void LLFloaterPostProcess::onSaveEffect(LLLineEditor* editBox) -{ - std::string effectName(editBox->getValue().asString()); - - if (gPostProcess->mAllEffects.has(effectName)) - { - LLSD payload; - payload["effect_name"] = effectName; - LLNotificationsUtil::add("PPSaveEffectAlert", LLSD(), payload, boost::bind(&LLFloaterPostProcess::saveAlertCallback, this, _1, _2)); - } - else - { - gPostProcess->saveEffect(effectName); - syncMenu(); - } -} - -void LLFloaterPostProcess::onChangeEffectName(LLUICtrl* ctrl) -{ - // get the combo box and name - LLLineEditor* editBox = getChild("PPEffectNameEditor"); - - // set the parameter's new name - editBox->setValue(ctrl->getValue()); -} - -bool LLFloaterPostProcess::saveAlertCallback(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - - // if they choose save, do it. Otherwise, don't do anything - if (option == 0) - { - gPostProcess->saveEffect(notification["payload"]["effect_name"].asString()); - - syncMenu(); - } - return false; -} - -void LLFloaterPostProcess::syncMenu() -{ - // add the combo boxe contents - LLComboBox* comboBox = getChild("PPEffectsCombo"); - - comboBox->removeall(); - - LLSD::map_const_iterator currEffect; - for(currEffect = gPostProcess->mAllEffects.beginMap(); - currEffect != gPostProcess->mAllEffects.endMap(); - ++currEffect) - { - comboBox->add(currEffect->first); - } - - // set the current effect as selected. - comboBox->selectByValue(gPostProcess->getSelectedEffect()); - - /// Sync Color Filter Menu - getChild("ColorFilterToggle")->setValue(gPostProcess->tweaks.useColorFilter()); - //getChild("ColorFilterGamma")->setValue(gPostProcess->tweaks.gamma()); - getChild("ColorFilterBrightness")->setValue(gPostProcess->tweaks.brightness()); - getChild("ColorFilterSaturation")->setValue(gPostProcess->tweaks.saturation()); - getChild("ColorFilterContrast")->setValue(gPostProcess->tweaks.contrast()); - getChild("ColorFilterBaseR")->setValue(gPostProcess->tweaks.contrastBaseR()); - getChild("ColorFilterBaseG")->setValue(gPostProcess->tweaks.contrastBaseG()); - getChild("ColorFilterBaseB")->setValue(gPostProcess->tweaks.contrastBaseB()); - getChild("ColorFilterBaseI")->setValue(gPostProcess->tweaks.contrastBaseIntensity()); - - /// Sync Night Vision Menu - getChild("NightVisionToggle")->setValue(gPostProcess->tweaks.useNightVisionShader()); - getChild("NightVisionBrightMult")->setValue(gPostProcess->tweaks.brightMult()); - getChild("NightVisionNoiseSize")->setValue(gPostProcess->tweaks.noiseSize()); - getChild("NightVisionNoiseStrength")->setValue(gPostProcess->tweaks.noiseStrength()); - - /// Sync Bloom Menu - getChild("BloomToggle")->setValue(LLSD(gPostProcess->tweaks.useBloomShader())); - getChild("BloomExtract")->setValue(gPostProcess->tweaks.extractLow()); - getChild("BloomSize")->setValue(gPostProcess->tweaks.bloomWidth()); - getChild("BloomStrength")->setValue(gPostProcess->tweaks.bloomStrength()); -} +/** + * @file llfloaterpostprocess.cpp + * @brief LLFloaterPostProcess class definition + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterpostprocess.h" + +#include "llsliderctrl.h" +#include "llcheckboxctrl.h" +#include "llnotificationsutil.h" +#include "lluictrlfactory.h" +#include "llviewerdisplay.h" +#include "llpostprocess.h" +#include "llcombobox.h" +#include "lllineeditor.h" +#include "llviewerwindow.h" + + +LLFloaterPostProcess::LLFloaterPostProcess(const LLSD& key) + : LLFloater(key) +{ +} + +LLFloaterPostProcess::~LLFloaterPostProcess() +{ + + +} +bool LLFloaterPostProcess::postBuild() +{ + /// Color Filter Callbacks + childSetCommitCallback("ColorFilterToggle", &LLFloaterPostProcess::onBoolToggle, (char*)"enable_color_filter"); + //childSetCommitCallback("ColorFilterGamma", &LLFloaterPostProcess::onFloatControlMoved, &(gPostProcess->tweaks.gamma())); + childSetCommitCallback("ColorFilterBrightness", &LLFloaterPostProcess::onFloatControlMoved, (char*)"brightness"); + childSetCommitCallback("ColorFilterSaturation", &LLFloaterPostProcess::onFloatControlMoved, (char*)"saturation"); + childSetCommitCallback("ColorFilterContrast", &LLFloaterPostProcess::onFloatControlMoved, (char*)"contrast"); + + childSetCommitCallback("ColorFilterBaseR", &LLFloaterPostProcess::onColorControlRMoved, (char*)"contrast_base"); + childSetCommitCallback("ColorFilterBaseG", &LLFloaterPostProcess::onColorControlGMoved, (char*)"contrast_base"); + childSetCommitCallback("ColorFilterBaseB", &LLFloaterPostProcess::onColorControlBMoved, (char*)"contrast_base"); + childSetCommitCallback("ColorFilterBaseI", &LLFloaterPostProcess::onColorControlIMoved, (char*)"contrast_base"); + + /// Night Vision Callbacks + childSetCommitCallback("NightVisionToggle", &LLFloaterPostProcess::onBoolToggle, (char*)"enable_night_vision"); + childSetCommitCallback("NightVisionBrightMult", &LLFloaterPostProcess::onFloatControlMoved, (char*)"brightness_multiplier"); + childSetCommitCallback("NightVisionNoiseSize", &LLFloaterPostProcess::onFloatControlMoved, (char*)"noise_size"); + childSetCommitCallback("NightVisionNoiseStrength", &LLFloaterPostProcess::onFloatControlMoved, (char*)"noise_strength"); + + /// Bloom Callbacks + childSetCommitCallback("BloomToggle", &LLFloaterPostProcess::onBoolToggle, (char*)"enable_bloom"); + childSetCommitCallback("BloomExtract", &LLFloaterPostProcess::onFloatControlMoved, (char*)"extract_low"); + childSetCommitCallback("BloomSize", &LLFloaterPostProcess::onFloatControlMoved, (char*)"bloom_width"); + childSetCommitCallback("BloomStrength", &LLFloaterPostProcess::onFloatControlMoved, (char*)"bloom_strength"); + + // Effect loading and saving. + LLComboBox* comboBox = getChild("PPEffectsCombo"); + getChild("PPLoadEffect")->setCommitCallback(boost::bind(&LLFloaterPostProcess::onLoadEffect, this, comboBox)); + comboBox->setCommitCallback(boost::bind(&LLFloaterPostProcess::onChangeEffectName, this, _1)); + + LLLineEditor* editBox = getChild("PPEffectNameEditor"); + getChild("PPSaveEffect")->setCommitCallback(boost::bind(&LLFloaterPostProcess::onSaveEffect, this, editBox)); + + syncMenu(); + return true; +} + +// Bool Toggle +void LLFloaterPostProcess::onBoolToggle(LLUICtrl* ctrl, void* userData) +{ + char const * boolVariableName = (char const *)userData; + + // check the bool + LLCheckBoxCtrl* cbCtrl = static_cast(ctrl); + gPostProcess->tweaks[boolVariableName] = cbCtrl->getValue(); +} + +// Float Moved +void LLFloaterPostProcess::onFloatControlMoved(LLUICtrl* ctrl, void* userData) +{ + char const * floatVariableName = (char const *)userData; + LLSliderCtrl* sldrCtrl = static_cast(ctrl); + gPostProcess->tweaks[floatVariableName] = sldrCtrl->getValue(); +} + +// Color Moved +void LLFloaterPostProcess::onColorControlRMoved(LLUICtrl* ctrl, void* userData) +{ + char const * floatVariableName = (char const *)userData; + LLSliderCtrl* sldrCtrl = static_cast(ctrl); + gPostProcess->tweaks[floatVariableName][0] = sldrCtrl->getValue(); +} + +// Color Moved +void LLFloaterPostProcess::onColorControlGMoved(LLUICtrl* ctrl, void* userData) +{ + char const * floatVariableName = (char const *)userData; + LLSliderCtrl* sldrCtrl = static_cast(ctrl); + gPostProcess->tweaks[floatVariableName][1] = sldrCtrl->getValue(); +} + +// Color Moved +void LLFloaterPostProcess::onColorControlBMoved(LLUICtrl* ctrl, void* userData) +{ + char const * floatVariableName = (char const *)userData; + LLSliderCtrl* sldrCtrl = static_cast(ctrl); + gPostProcess->tweaks[floatVariableName][2] = sldrCtrl->getValue(); +} + +// Color Moved +void LLFloaterPostProcess::onColorControlIMoved(LLUICtrl* ctrl, void* userData) +{ + char const * floatVariableName = (char const *)userData; + LLSliderCtrl* sldrCtrl = static_cast(ctrl); + gPostProcess->tweaks[floatVariableName][3] = sldrCtrl->getValue(); +} + +void LLFloaterPostProcess::onLoadEffect(LLComboBox* comboBox) +{ + LLSD::String effectName(comboBox->getSelectedValue().asString()); + + gPostProcess->setSelectedEffect(effectName); + + syncMenu(); +} + +void LLFloaterPostProcess::onSaveEffect(LLLineEditor* editBox) +{ + std::string effectName(editBox->getValue().asString()); + + if (gPostProcess->mAllEffects.has(effectName)) + { + LLSD payload; + payload["effect_name"] = effectName; + LLNotificationsUtil::add("PPSaveEffectAlert", LLSD(), payload, boost::bind(&LLFloaterPostProcess::saveAlertCallback, this, _1, _2)); + } + else + { + gPostProcess->saveEffect(effectName); + syncMenu(); + } +} + +void LLFloaterPostProcess::onChangeEffectName(LLUICtrl* ctrl) +{ + // get the combo box and name + LLLineEditor* editBox = getChild("PPEffectNameEditor"); + + // set the parameter's new name + editBox->setValue(ctrl->getValue()); +} + +bool LLFloaterPostProcess::saveAlertCallback(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + + // if they choose save, do it. Otherwise, don't do anything + if (option == 0) + { + gPostProcess->saveEffect(notification["payload"]["effect_name"].asString()); + + syncMenu(); + } + return false; +} + +void LLFloaterPostProcess::syncMenu() +{ + // add the combo boxe contents + LLComboBox* comboBox = getChild("PPEffectsCombo"); + + comboBox->removeall(); + + LLSD::map_const_iterator currEffect; + for(currEffect = gPostProcess->mAllEffects.beginMap(); + currEffect != gPostProcess->mAllEffects.endMap(); + ++currEffect) + { + comboBox->add(currEffect->first); + } + + // set the current effect as selected. + comboBox->selectByValue(gPostProcess->getSelectedEffect()); + + /// Sync Color Filter Menu + getChild("ColorFilterToggle")->setValue(gPostProcess->tweaks.useColorFilter()); + //getChild("ColorFilterGamma")->setValue(gPostProcess->tweaks.gamma()); + getChild("ColorFilterBrightness")->setValue(gPostProcess->tweaks.brightness()); + getChild("ColorFilterSaturation")->setValue(gPostProcess->tweaks.saturation()); + getChild("ColorFilterContrast")->setValue(gPostProcess->tweaks.contrast()); + getChild("ColorFilterBaseR")->setValue(gPostProcess->tweaks.contrastBaseR()); + getChild("ColorFilterBaseG")->setValue(gPostProcess->tweaks.contrastBaseG()); + getChild("ColorFilterBaseB")->setValue(gPostProcess->tweaks.contrastBaseB()); + getChild("ColorFilterBaseI")->setValue(gPostProcess->tweaks.contrastBaseIntensity()); + + /// Sync Night Vision Menu + getChild("NightVisionToggle")->setValue(gPostProcess->tweaks.useNightVisionShader()); + getChild("NightVisionBrightMult")->setValue(gPostProcess->tweaks.brightMult()); + getChild("NightVisionNoiseSize")->setValue(gPostProcess->tweaks.noiseSize()); + getChild("NightVisionNoiseStrength")->setValue(gPostProcess->tweaks.noiseStrength()); + + /// Sync Bloom Menu + getChild("BloomToggle")->setValue(LLSD(gPostProcess->tweaks.useBloomShader())); + getChild("BloomExtract")->setValue(gPostProcess->tweaks.extractLow()); + getChild("BloomSize")->setValue(gPostProcess->tweaks.bloomWidth()); + getChild("BloomStrength")->setValue(gPostProcess->tweaks.bloomStrength()); +} diff --git a/indra/newview/llfloaterpostprocess.h b/indra/newview/llfloaterpostprocess.h index 1409faf75f..50b48d8410 100644 --- a/indra/newview/llfloaterpostprocess.h +++ b/indra/newview/llfloaterpostprocess.h @@ -1,72 +1,72 @@ -/** - * @file llfloaterpostprocess.h - * @brief LLFloaterPostProcess class definition - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERPOSTPROCESS_H -#define LL_LLFLOATERPOSTPROCESS_H - -#include "llfloater.h" - -class LLButton; -class LLComboBox; -class LLLineEditor; -class LLSliderCtrl; -class LLTabContainer; -class LLPanelPermissions; -class LLPanelObject; -class LLPanelVolume; -class LLPanelContents; -class LLPanelFace; - -/** - * Menu for adjusting the post process settings of the world - */ -class LLFloaterPostProcess : public LLFloater -{ -public: - - LLFloaterPostProcess(const LLSD& key); - virtual ~LLFloaterPostProcess(); - bool postBuild(); - - /// post process callbacks - static void onBoolToggle(LLUICtrl* ctrl, void* userData); - static void onFloatControlMoved(LLUICtrl* ctrl, void* userData); - static void onColorControlRMoved(LLUICtrl* ctrl, void* userData); - static void onColorControlGMoved(LLUICtrl* ctrl, void* userData); - static void onColorControlBMoved(LLUICtrl* ctrl, void* userData); - static void onColorControlIMoved(LLUICtrl* ctrl, void* userData); - void onLoadEffect(LLComboBox* comboBox); - void onSaveEffect(LLLineEditor* editBox); - void onChangeEffectName(LLUICtrl* ctrl); - - /// prompts a user when overwriting an effect - bool saveAlertCallback(const LLSD& notification, const LLSD& response); - - /// sync up sliders - void syncMenu(); -}; - -#endif +/** + * @file llfloaterpostprocess.h + * @brief LLFloaterPostProcess class definition + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERPOSTPROCESS_H +#define LL_LLFLOATERPOSTPROCESS_H + +#include "llfloater.h" + +class LLButton; +class LLComboBox; +class LLLineEditor; +class LLSliderCtrl; +class LLTabContainer; +class LLPanelPermissions; +class LLPanelObject; +class LLPanelVolume; +class LLPanelContents; +class LLPanelFace; + +/** + * Menu for adjusting the post process settings of the world + */ +class LLFloaterPostProcess : public LLFloater +{ +public: + + LLFloaterPostProcess(const LLSD& key); + virtual ~LLFloaterPostProcess(); + bool postBuild(); + + /// post process callbacks + static void onBoolToggle(LLUICtrl* ctrl, void* userData); + static void onFloatControlMoved(LLUICtrl* ctrl, void* userData); + static void onColorControlRMoved(LLUICtrl* ctrl, void* userData); + static void onColorControlGMoved(LLUICtrl* ctrl, void* userData); + static void onColorControlBMoved(LLUICtrl* ctrl, void* userData); + static void onColorControlIMoved(LLUICtrl* ctrl, void* userData); + void onLoadEffect(LLComboBox* comboBox); + void onSaveEffect(LLLineEditor* editBox); + void onChangeEffectName(LLUICtrl* ctrl); + + /// prompts a user when overwriting an effect + bool saveAlertCallback(const LLSD& notification, const LLSD& response); + + /// sync up sliders + void syncMenu(); +}; + +#endif diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index 3ed3abce58..85a07f23a4 100644 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -1,3436 +1,3436 @@ -/** - * @file llfloaterpreference.cpp - * @brief Global preferences with and without persistence. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/* - * App-wide preferences. Note that these are not per-user, - * because we need to load many preferences before we have - * a login name. - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterpreference.h" - -#include "message.h" -#include "llfloaterautoreplacesettings.h" -#include "llagent.h" -#include "llagentcamera.h" -#include "llcheckboxctrl.h" -#include "llcolorswatch.h" -#include "llcombobox.h" -#include "llcommandhandler.h" -#include "lldirpicker.h" -#include "lleventtimer.h" -#include "llfeaturemanager.h" -#include "llfocusmgr.h" -//#include "llfirstuse.h" -#include "llfloaterreg.h" -#include "llfloaterabout.h" -#include "llfavoritesbar.h" -#include "llfloaterpreferencesgraphicsadvanced.h" -#include "llfloaterperformance.h" -#include "llfloatersidepanelcontainer.h" -#include "llfloaterimsession.h" -#include "llkeyboard.h" -#include "llmodaldialog.h" -#include "llnavigationbar.h" -#include "llfloaterimnearbychat.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "llnotificationtemplate.h" -#include "llpanellogin.h" -#include "llpanelvoicedevicesettings.h" -#include "llradiogroup.h" -#include "llsearchcombobox.h" -#include "llsky.h" -#include "llscrolllistctrl.h" -#include "llscrolllistitem.h" -#include "llsliderctrl.h" -#include "lltabcontainer.h" -#include "lltrans.h" -#include "llviewercontrol.h" -#include "llviewercamera.h" -#include "llviewereventrecorder.h" -#include "llviewermessage.h" -#include "llviewerwindow.h" -#include "llviewerthrottle.h" -#include "llvoavatarself.h" -#include "llvotree.h" -#include "llfloaterpathfindingconsole.h" -// linden library includes -#include "llavatarnamecache.h" -#include "llerror.h" -#include "llfontgl.h" -#include "llrect.h" -#include "llstring.h" - -// project includes - -#include "llbutton.h" -#include "llflexibleobject.h" -#include "lllineeditor.h" -#include "llresmgr.h" -#include "llspinctrl.h" -#include "llstartup.h" -#include "lltextbox.h" -#include "llui.h" -#include "llviewerobjectlist.h" -#include "llvovolume.h" -#include "llwindow.h" -#include "llworld.h" -#include "lluictrlfactory.h" -#include "llviewermedia.h" -#include "llpluginclassmedia.h" -#include "llteleporthistorystorage.h" -#include "llproxy.h" -#include "llweb.h" - -#include "lllogininstance.h" // to check if logged in yet -#include "llsdserialize.h" -#include "llpresetsmanager.h" -#include "llviewercontrol.h" -#include "llpresetsmanager.h" -#include "llinventoryfunctions.h" - -#include "llsearchableui.h" -#include "llperfstats.h" - -const F32 BANDWIDTH_UPDATER_TIMEOUT = 0.5f; -char const* const VISIBILITY_DEFAULT = "default"; -char const* const VISIBILITY_HIDDEN = "hidden"; - -//control value for middle mouse as talk2push button -const static std::string MIDDLE_MOUSE_CV = "MiddleMouse"; // for voice client and redability -const static std::string MOUSE_BUTTON_4_CV = "MouseButton4"; -const static std::string MOUSE_BUTTON_5_CV = "MouseButton5"; - -/// This must equal the maximum value set for the IndirectMaxComplexity slider in panel_preferences_graphics1.xml -static const U32 INDIRECT_MAX_ARC_OFF = 101; // all the way to the right == disabled -static const U32 MIN_INDIRECT_ARC_LIMIT = 1; // must match minimum of IndirectMaxComplexity in panel_preferences_graphics1.xml -static const U32 MAX_INDIRECT_ARC_LIMIT = INDIRECT_MAX_ARC_OFF-1; // one short of all the way to the right... - -/// These are the effective range of values for RenderAvatarMaxComplexity -static const F32 MIN_ARC_LIMIT = 20000.0f; -static const F32 MAX_ARC_LIMIT = 350000.0f; -static const F32 MIN_ARC_LOG = log(MIN_ARC_LIMIT); -static const F32 MAX_ARC_LOG = log(MAX_ARC_LIMIT); -static const F32 ARC_LIMIT_MAP_SCALE = (MAX_ARC_LOG - MIN_ARC_LOG) / (MAX_INDIRECT_ARC_LIMIT - MIN_INDIRECT_ARC_LIMIT); - -struct LabelDef : public LLInitParam::Block -{ - Mandatory name; - Mandatory value; - - LabelDef() - : name("name"), - value("value") - {} -}; - -struct LabelTable : public LLInitParam::Block -{ - Multiple labels; - LabelTable() - : labels("label") - {} -}; - - -// global functions - -// helper functions for getting/freeing the web browser media -// if creating/destroying these is too slow, we'll need to create -// a static member and update all our static callbacks - -void handleNameTagOptionChanged(const LLSD& newvalue); -void handleDisplayNamesOptionChanged(const LLSD& newvalue); -bool callback_clear_browser_cache(const LLSD& notification, const LLSD& response); -bool callback_clear_cache(const LLSD& notification, const LLSD& response); - -//bool callback_skip_dialogs(const LLSD& notification, const LLSD& response, LLFloaterPreference* floater); -//bool callback_reset_dialogs(const LLSD& notification, const LLSD& response, LLFloaterPreference* floater); - -void fractionFromDecimal(F32 decimal_val, S32& numerator, S32& denominator); - -bool callback_clear_cache(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if ( option == 0 ) // YES - { - // flag client texture cache for clearing next time the client runs - gSavedSettings.setBOOL("PurgeCacheOnNextStartup", true); - LLNotificationsUtil::add("CacheWillClear"); - } - - return false; -} - -bool callback_clear_browser_cache(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if ( option == 0 ) // YES - { - // clean web - LLViewerMedia::getInstance()->clearAllCaches(); - LLViewerMedia::getInstance()->clearAllCookies(); - - // clean nav bar history - LLNavigationBar::getInstance()->clearHistoryCache(); - - // flag client texture cache for clearing next time the client runs - gSavedSettings.setBOOL("PurgeCacheOnNextStartup", true); - LLNotificationsUtil::add("CacheWillClear"); - - LLSearchHistory::getInstance()->clearHistory(); - LLSearchHistory::getInstance()->save(); - LLSearchComboBox* search_ctrl = LLNavigationBar::getInstance()->getChild("search_combo_box"); - search_ctrl->clearHistory(); - - LLTeleportHistoryStorage::getInstance()->purgeItems(); - LLTeleportHistoryStorage::getInstance()->save(); - } - - return false; -} - -void handleNameTagOptionChanged(const LLSD& newvalue) -{ - LLAvatarNameCache::getInstance()->setUseUsernames(gSavedSettings.getBOOL("NameTagShowUsernames")); - LLVOAvatar::invalidateNameTags(); -} - -void handleDisplayNamesOptionChanged(const LLSD& newvalue) -{ - LLAvatarNameCache::getInstance()->setUseDisplayNames(newvalue.asBoolean()); - LLVOAvatar::invalidateNameTags(); -} - -void handleAppearanceCameraMovementChanged(const LLSD& newvalue) -{ - if(!newvalue.asBoolean() && gAgentCamera.getCameraMode() == CAMERA_MODE_CUSTOMIZE_AVATAR) - { - gAgentCamera.changeCameraToDefault(); - gAgentCamera.resetView(); - } -} - -void fractionFromDecimal(F32 decimal_val, S32& numerator, S32& denominator) -{ - numerator = 0; - denominator = 0; - for (F32 test_denominator = 1.f; test_denominator < 30.f; test_denominator += 1.f) - { - if (fmodf((decimal_val * test_denominator) + 0.01f, 1.f) < 0.02f) - { - numerator = ll_round(decimal_val * test_denominator); - denominator = ll_round(test_denominator); - break; - } - } -} - -// handle secondlife:///app/worldmap/{NAME}/{COORDS} URLs -// Also see LLUrlEntryKeybinding, the value of this command type -// is ability to show up to date value in chat -class LLKeybindingHandler: public LLCommandHandler -{ -public: - // requires trusted browser to trigger - LLKeybindingHandler(): LLCommandHandler("keybinding", UNTRUSTED_CLICK_ONLY) - { - } - - bool handle(const LLSD& params, const LLSD& query_map, - const std::string& grid, LLMediaCtrl* web) - { - if (params.size() < 1) return false; - - LLFloaterPreference* prefsfloater = dynamic_cast - (LLFloaterReg::showInstance("preferences")); - - if (prefsfloater) - { - // find 'controls' panel and bring it the front - LLTabContainer* tabcontainer = prefsfloater->getChild("pref core"); - LLPanel* panel = prefsfloater->getChild("controls"); - if (tabcontainer && panel) - { - tabcontainer->selectTabPanel(panel); - } - } - - return true; - } -}; -LLKeybindingHandler gKeybindHandler; - - -////////////////////////////////////////////// -// LLFloaterPreference - -// static -std::string LLFloaterPreference::sSkin = ""; - -LLFloaterPreference::LLFloaterPreference(const LLSD& key) - : LLFloater(key), - mGotPersonalInfo(false), - mLanguageChanged(false), - mAvatarDataInitialized(false), - mSearchDataDirty(true) -{ - LLConversationLog::instance().addObserver(this); - - //Build Floater is now Called from LLFloaterReg::add("preferences", "floater_preferences.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); - - static bool registered_dialog = false; - if (!registered_dialog) - { - LLFloaterReg::add("keybind_dialog", "floater_select_key.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); - registered_dialog = true; - } - - mCommitCallbackRegistrar.add("Pref.Cancel", boost::bind(&LLFloaterPreference::onBtnCancel, this, _2)); - mCommitCallbackRegistrar.add("Pref.OK", boost::bind(&LLFloaterPreference::onBtnOK, this, _2)); - - mCommitCallbackRegistrar.add("Pref.ClearCache", boost::bind(&LLFloaterPreference::onClickClearCache, this)); - mCommitCallbackRegistrar.add("Pref.WebClearCache", boost::bind(&LLFloaterPreference::onClickBrowserClearCache, this)); - mCommitCallbackRegistrar.add("Pref.SetCache", boost::bind(&LLFloaterPreference::onClickSetCache, this)); - mCommitCallbackRegistrar.add("Pref.ResetCache", boost::bind(&LLFloaterPreference::onClickResetCache, this)); - mCommitCallbackRegistrar.add("Pref.ClickSkin", boost::bind(&LLFloaterPreference::onClickSkin, this,_1, _2)); - mCommitCallbackRegistrar.add("Pref.SelectSkin", boost::bind(&LLFloaterPreference::onSelectSkin, this)); - mCommitCallbackRegistrar.add("Pref.SetSounds", boost::bind(&LLFloaterPreference::onClickSetSounds, this)); - mCommitCallbackRegistrar.add("Pref.ClickEnablePopup", boost::bind(&LLFloaterPreference::onClickEnablePopup, this)); - mCommitCallbackRegistrar.add("Pref.ClickDisablePopup", boost::bind(&LLFloaterPreference::onClickDisablePopup, this)); - mCommitCallbackRegistrar.add("Pref.LogPath", boost::bind(&LLFloaterPreference::onClickLogPath, this)); - mCommitCallbackRegistrar.add("Pref.RenderExceptions", boost::bind(&LLFloaterPreference::onClickRenderExceptions, this)); - mCommitCallbackRegistrar.add("Pref.AutoAdjustments", boost::bind(&LLFloaterPreference::onClickAutoAdjustments, this)); - mCommitCallbackRegistrar.add("Pref.HardwareDefaults", boost::bind(&LLFloaterPreference::setHardwareDefaults, this)); - mCommitCallbackRegistrar.add("Pref.AvatarImpostorsEnable", boost::bind(&LLFloaterPreference::onAvatarImpostorsEnable, this)); - mCommitCallbackRegistrar.add("Pref.UpdateIndirectMaxComplexity", boost::bind(&LLFloaterPreference::updateMaxComplexity, this)); - mCommitCallbackRegistrar.add("Pref.RenderOptionUpdate", boost::bind(&LLFloaterPreference::onRenderOptionEnable, this)); - mCommitCallbackRegistrar.add("Pref.WindowedMod", boost::bind(&LLFloaterPreference::onCommitWindowedMode, this)); - mCommitCallbackRegistrar.add("Pref.UpdateSliderText", boost::bind(&LLFloaterPreference::refreshUI,this)); - mCommitCallbackRegistrar.add("Pref.QualityPerformance", boost::bind(&LLFloaterPreference::onChangeQuality, this, _2)); - mCommitCallbackRegistrar.add("Pref.applyUIColor", boost::bind(&LLFloaterPreference::applyUIColor, this ,_1, _2)); - mCommitCallbackRegistrar.add("Pref.getUIColor", boost::bind(&LLFloaterPreference::getUIColor, this ,_1, _2)); - mCommitCallbackRegistrar.add("Pref.MaturitySettings", boost::bind(&LLFloaterPreference::onChangeMaturity, this)); - mCommitCallbackRegistrar.add("Pref.BlockList", boost::bind(&LLFloaterPreference::onClickBlockList, this)); - mCommitCallbackRegistrar.add("Pref.Proxy", boost::bind(&LLFloaterPreference::onClickProxySettings, this)); - mCommitCallbackRegistrar.add("Pref.TranslationSettings", boost::bind(&LLFloaterPreference::onClickTranslationSettings, this)); - mCommitCallbackRegistrar.add("Pref.AutoReplace", boost::bind(&LLFloaterPreference::onClickAutoReplace, this)); - mCommitCallbackRegistrar.add("Pref.PermsDefault", boost::bind(&LLFloaterPreference::onClickPermsDefault, this)); - mCommitCallbackRegistrar.add("Pref.RememberedUsernames", boost::bind(&LLFloaterPreference::onClickRememberedUsernames, this)); - mCommitCallbackRegistrar.add("Pref.SpellChecker", boost::bind(&LLFloaterPreference::onClickSpellChecker, this)); - mCommitCallbackRegistrar.add("Pref.Advanced", boost::bind(&LLFloaterPreference::onClickAdvanced, this)); - - sSkin = gSavedSettings.getString("SkinCurrent"); - - mCommitCallbackRegistrar.add("Pref.ClickActionChange", boost::bind(&LLFloaterPreference::onClickActionChange, this)); - - gSavedSettings.getControl("NameTagShowUsernames")->getCommitSignal()->connect(boost::bind(&handleNameTagOptionChanged, _2)); - gSavedSettings.getControl("NameTagShowFriends")->getCommitSignal()->connect(boost::bind(&handleNameTagOptionChanged, _2)); - gSavedSettings.getControl("UseDisplayNames")->getCommitSignal()->connect(boost::bind(&handleDisplayNamesOptionChanged, _2)); - - gSavedSettings.getControl("AppearanceCameraMovement")->getCommitSignal()->connect(boost::bind(&handleAppearanceCameraMovementChanged, _2)); - gSavedSettings.getControl("WindLightUseAtmosShaders")->getCommitSignal()->connect(boost::bind(&LLFloaterPreference::onAtmosShaderChange, this)); - - LLAvatarPropertiesProcessor::getInstance()->addObserver( gAgent.getID(), this ); - - mComplexityChangedSignal = gSavedSettings.getControl("RenderAvatarMaxComplexity")->getCommitSignal()->connect(boost::bind(&LLFloaterPreference::updateComplexityText, this)); - - mCommitCallbackRegistrar.add("Pref.ClearLog", boost::bind(&LLConversationLog::onClearLog, &LLConversationLog::instance())); - mCommitCallbackRegistrar.add("Pref.DeleteTranscripts", boost::bind(&LLFloaterPreference::onDeleteTranscripts, this)); - mCommitCallbackRegistrar.add("UpdateFilter", boost::bind(&LLFloaterPreference::onUpdateFilterTerm, this, false)); // Hook up for filtering -} - -void LLFloaterPreference::processProperties( void* pData, EAvatarProcessorType type ) -{ - if ( APT_PROPERTIES_LEGACY == type ) - { - const LLAvatarLegacyData* pAvatarData = static_cast( pData ); - if (pAvatarData && (gAgent.getID() == pAvatarData->avatar_id) && (pAvatarData->avatar_id != LLUUID::null)) - { - mAllowPublish = (bool)(pAvatarData->flags & AVATAR_ALLOW_PUBLISH); - mAvatarDataInitialized = true; - getChild("online_searchresults")->setValue(mAllowPublish); - } - } -} - -void LLFloaterPreference::saveAvatarProperties( void ) -{ - const bool allowPublish = getChild("online_searchresults")->getValue(); - - if ((LLStartUp::getStartupState() == STATE_STARTED) - && mAvatarDataInitialized - && (allowPublish != mAllowPublish)) - { - std::string cap_url = gAgent.getRegionCapability("AgentProfile"); - if (!cap_url.empty()) - { - mAllowPublish = allowPublish; - - LLCoros::instance().launch("requestAgentUserInfoCoro", - boost::bind(saveAvatarPropertiesCoro, cap_url, allowPublish)); - } - } -} - -void LLFloaterPreference::saveAvatarPropertiesCoro(const std::string cap_url, bool allow_publish) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("put_avatar_properties_coro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpHeaders::ptr_t httpHeaders; - - LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); - httpOpts->setFollowRedirects(true); - - std::string finalUrl = cap_url + "/" + gAgentID.asString(); - LLSD data; - data["allow_publish"] = allow_publish; - - LLSD result = httpAdapter->putAndSuspend(httpRequest, finalUrl, data, httpOpts, httpHeaders); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS("Preferences") << "Failed to put agent information " << data << " for id " << gAgentID << LL_ENDL; - return; - } - - LL_DEBUGS("Preferences") << "Agent id: " << gAgentID << " Data: " << data << " Result: " << httpResults << LL_ENDL; -} - -bool LLFloaterPreference::postBuild() -{ - gSavedSettings.getControl("ChatFontSize")->getSignal()->connect(boost::bind(&LLFloaterIMSessionTab::processChatHistoryStyleUpdate, false)); - - gSavedSettings.getControl("ChatFontSize")->getSignal()->connect(boost::bind(&LLViewerChat::signalChatFontChanged)); - - gSavedSettings.getControl("ChatBubbleOpacity")->getSignal()->connect(boost::bind(&LLFloaterPreference::onNameTagOpacityChange, this, _2)); - - gSavedSettings.getControl("PreferredMaturity")->getSignal()->connect(boost::bind(&LLFloaterPreference::onChangeMaturity, this)); - - gSavedSettings.getControl("RenderAvatarComplexityMode")->getSignal()->connect( - [this](LLControlVariable* control, const LLSD& new_val, const LLSD& old_val) - { - onChangeComplexityMode(new_val); - }); - - gSavedPerAccountSettings.getControl("ModelUploadFolder")->getSignal()->connect(boost::bind(&LLFloaterPreference::onChangeModelFolder, this)); - gSavedPerAccountSettings.getControl("PBRUploadFolder")->getSignal()->connect(boost::bind(&LLFloaterPreference::onChangePBRFolder, this)); - gSavedPerAccountSettings.getControl("TextureUploadFolder")->getSignal()->connect(boost::bind(&LLFloaterPreference::onChangeTextureFolder, this)); - gSavedPerAccountSettings.getControl("SoundUploadFolder")->getSignal()->connect(boost::bind(&LLFloaterPreference::onChangeSoundFolder, this)); - gSavedPerAccountSettings.getControl("AnimationUploadFolder")->getSignal()->connect(boost::bind(&LLFloaterPreference::onChangeAnimationFolder, this)); - - LLTabContainer* tabcontainer = getChild("pref core"); - if (!tabcontainer->selectTab(gSavedSettings.getS32("LastPrefTab"))) - tabcontainer->selectFirstTab(); - - getChild("cache_location")->setEnabled(false); // make it read-only but selectable (STORM-227) - std::string cache_location = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ""); - setCacheLocation(cache_location); - - getChild("log_path_string")->setEnabled(false); // make it read-only but selectable - - getChild("language_combobox")->setCommitCallback(boost::bind(&LLFloaterPreference::onLanguageChange, this)); - - getChild("FriendIMOptions")->setCommitCallback(boost::bind(&LLFloaterPreference::onNotificationsChange, this,"FriendIMOptions")); - getChild("NonFriendIMOptions")->setCommitCallback(boost::bind(&LLFloaterPreference::onNotificationsChange, this,"NonFriendIMOptions")); - getChild("ConferenceIMOptions")->setCommitCallback(boost::bind(&LLFloaterPreference::onNotificationsChange, this,"ConferenceIMOptions")); - getChild("GroupChatOptions")->setCommitCallback(boost::bind(&LLFloaterPreference::onNotificationsChange, this,"GroupChatOptions")); - getChild("NearbyChatOptions")->setCommitCallback(boost::bind(&LLFloaterPreference::onNotificationsChange, this,"NearbyChatOptions")); - getChild("ObjectIMOptions")->setCommitCallback(boost::bind(&LLFloaterPreference::onNotificationsChange, this,"ObjectIMOptions")); - - // if floater is opened before login set default localized do not disturb message - if (LLStartUp::getStartupState() < STATE_STARTED) - { - gSavedPerAccountSettings.setString("DoNotDisturbModeResponse", LLTrans::getString("DoNotDisturbModeResponseDefault")); - } - - // set 'enable' property for 'Clear log...' button - changed(); - - LLLogChat::getInstance()->setSaveHistorySignal(boost::bind(&LLFloaterPreference::onLogChatHistorySaved, this)); - - LLSliderCtrl* fov_slider = getChild("camera_fov"); - fov_slider->setMinValue(LLViewerCamera::getInstance()->getMinView()); - fov_slider->setMaxValue(LLViewerCamera::getInstance()->getMaxView()); - - bool enable_complexity = gSavedSettings.getS32("RenderAvatarComplexityMode") != LLVOAvatar::AV_RENDER_ONLY_SHOW_FRIENDS; - getChild("IndirectMaxComplexity")->setEnabled(enable_complexity); - - // Hook up and init for filtering - mFilterEdit = getChild("search_prefs_edit"); - mFilterEdit->setKeystrokeCallback(boost::bind(&LLFloaterPreference::onUpdateFilterTerm, this, false)); - - // Load and assign label for 'default language' - std::string user_filename = gDirUtilp->getExpandedFilename(LL_PATH_DEFAULT_SKIN, "default_languages.xml"); - std::map labels; - if (loadFromFilename(user_filename, labels)) - { - std::string system_lang = gSavedSettings.getString("SystemLanguage"); - std::map::iterator iter = labels.find(system_lang); - if (iter != labels.end()) - { - getChild("language_combobox")->add(iter->second, LLSD("default"), ADD_TOP, true); - } - else - { - LL_WARNS() << "Language \"" << system_lang << "\" is not in default_languages.xml" << LL_ENDL; - getChild("language_combobox")->add("System default", LLSD("default"), ADD_TOP, true); - } - } - else - { - LL_WARNS() << "Failed to load labels from " << user_filename << ". Using default." << LL_ENDL; - getChild("language_combobox")->add("System default", LLSD("default"), ADD_TOP, true); - } - - return true; -} - -void LLFloaterPreference::updateDeleteTranscriptsButton() -{ - getChild("delete_transcripts")->setEnabled(LLLogChat::transcriptFilesExist()); -} - -void LLFloaterPreference::onDoNotDisturbResponseChanged() -{ - // set "DoNotDisturbResponseChanged" true if user edited message differs from default, false otherwise - bool response_changed_flag = - LLTrans::getString("DoNotDisturbModeResponseDefault") - != getChild("do_not_disturb_response")->getValue().asString(); - - gSavedPerAccountSettings.setBOOL("DoNotDisturbResponseChanged", response_changed_flag ); -} - -LLFloaterPreference::~LLFloaterPreference() -{ - LLConversationLog::instance().removeObserver(this); - mComplexityChangedSignal.disconnect(); -} - -void LLFloaterPreference::draw() -{ - bool has_first_selected = (getChildRef("disabled_popups").getFirstSelected()!=NULL); - gSavedSettings.setBOOL("FirstSelectedDisabledPopups", has_first_selected); - - has_first_selected = (getChildRef("enabled_popups").getFirstSelected()!=NULL); - gSavedSettings.setBOOL("FirstSelectedEnabledPopups", has_first_selected); - - LLFloater::draw(); -} - -void LLFloaterPreference::saveSettings() -{ - LLTabContainer* tabcontainer = getChild("pref core"); - child_list_t::const_iterator iter = tabcontainer->getChildList()->begin(); - child_list_t::const_iterator end = tabcontainer->getChildList()->end(); - for ( ; iter != end; ++iter) - { - LLView* view = *iter; - LLPanelPreference* panel = dynamic_cast(view); - if (panel) - panel->saveSettings(); - } - saveIgnoredNotifications(); -} - -void LLFloaterPreference::apply() -{ - LLAvatarPropertiesProcessor::getInstance()->addObserver( gAgent.getID(), this ); - - LLTabContainer* tabcontainer = getChild("pref core"); - if (sSkin != gSavedSettings.getString("SkinCurrent")) - { - LLNotificationsUtil::add("ChangeSkin"); - refreshSkin(this); - } - // Call apply() on all panels that derive from LLPanelPreference - for (child_list_t::const_iterator iter = tabcontainer->getChildList()->begin(); - iter != tabcontainer->getChildList()->end(); ++iter) - { - LLView* view = *iter; - LLPanelPreference* panel = dynamic_cast(view); - if (panel) - panel->apply(); - } - - gViewerWindow->requestResolutionUpdate(); // for UIScaleFactor - - LLSliderCtrl* fov_slider = getChild("camera_fov"); - fov_slider->setMinValue(LLViewerCamera::getInstance()->getMinView()); - fov_slider->setMaxValue(LLViewerCamera::getInstance()->getMaxView()); - - std::string cache_location = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ""); - setCacheLocation(cache_location); - - LLViewerMedia::getInstance()->setCookiesEnabled(getChild("cookies_enabled")->getValue()); - - if (hasChild("web_proxy_enabled", true) &&hasChild("web_proxy_editor", true) && hasChild("web_proxy_port", true)) - { - bool proxy_enable = getChild("web_proxy_enabled")->getValue(); - std::string proxy_address = getChild("web_proxy_editor")->getValue(); - int proxy_port = getChild("web_proxy_port")->getValue(); - LLViewerMedia::getInstance()->setProxyConfig(proxy_enable, proxy_address, proxy_port); - } - - if (mGotPersonalInfo) - { - bool new_hide_online = getChild("online_visibility")->getValue().asBoolean(); - - if (new_hide_online != mOriginalHideOnlineStatus) - { - // This hack is because we are representing several different - // possible strings with a single checkbox. Since most users - // can only select between 2 values, we represent it as a - // checkbox. This breaks down a little bit for liaisons, but - // works out in the end. - if (new_hide_online != mOriginalHideOnlineStatus) - { - if (new_hide_online) mDirectoryVisibility = VISIBILITY_HIDDEN; - else mDirectoryVisibility = VISIBILITY_DEFAULT; - //Update showonline value, otherwise multiple applys won't work - mOriginalHideOnlineStatus = new_hide_online; - } - gAgent.sendAgentUpdateUserInfo(mDirectoryVisibility); - } - } - - saveAvatarProperties(); -} - -void LLFloaterPreference::cancel(const std::vector settings_to_skip) -{ - LLTabContainer* tabcontainer = getChild("pref core"); - // Call cancel() on all panels that derive from LLPanelPreference - for (child_list_t::const_iterator iter = tabcontainer->getChildList()->begin(); - iter != tabcontainer->getChildList()->end(); ++iter) - { - LLView* view = *iter; - LLPanelPreference* panel = dynamic_cast(view); - if (panel) - panel->cancel(settings_to_skip); - } - // hide joystick pref floater - LLFloaterReg::hideInstance("pref_joystick"); - - // hide translation settings floater - LLFloaterReg::hideInstance("prefs_translation"); - - // hide autoreplace settings floater - LLFloaterReg::hideInstance("prefs_autoreplace"); - - // hide spellchecker settings folder - LLFloaterReg::hideInstance("prefs_spellchecker"); - - // hide advanced graphics floater - LLFloaterReg::hideInstance("prefs_graphics_advanced"); - - // reverts any changes to current skin - gSavedSettings.setString("SkinCurrent", sSkin); - - updateClickActionViews(); - - LLFloaterPreferenceProxy * advanced_proxy_settings = LLFloaterReg::findTypedInstance("prefs_proxy"); - if (advanced_proxy_settings) - { - advanced_proxy_settings->cancel(); - } - //Need to reload the navmesh if the pathing console is up - LLHandle pathfindingConsoleHandle = LLFloaterPathfindingConsole::getInstanceHandle(); - if ( !pathfindingConsoleHandle.isDead() ) - { - LLFloaterPathfindingConsole* pPathfindingConsole = pathfindingConsoleHandle.get(); - pPathfindingConsole->onRegionBoundaryCross(); - } - - if (!mSavedGraphicsPreset.empty()) - { - gSavedSettings.setString("PresetGraphicActive", mSavedGraphicsPreset); - LLPresetsManager::getInstance()->triggerChangeSignal(); - } - - restoreIgnoredNotifications(); -} - -void LLFloaterPreference::onOpen(const LLSD& key) -{ - // this variable and if that follows it are used to properly handle do not disturb mode response message - static bool initialized = false; - // if user is logged in and we haven't initialized do not disturb mode response yet, do it - if (!initialized && LLStartUp::getStartupState() == STATE_STARTED) - { - // Special approach is used for do not disturb response localization, because "DoNotDisturbModeResponse" is - // in non-localizable xml, and also because it may be changed by user and in this case it shouldn't be localized. - // To keep track of whether do not disturb response is default or changed by user additional setting DoNotDisturbResponseChanged - // was added into per account settings. - - // initialization should happen once,so setting variable to true - initialized = true; - // this connection is needed to properly set "DoNotDisturbResponseChanged" setting when user makes changes in - // do not disturb response message. - gSavedPerAccountSettings.getControl("DoNotDisturbModeResponse")->getSignal()->connect(boost::bind(&LLFloaterPreference::onDoNotDisturbResponseChanged, this)); - } - gAgent.sendAgentUserInfoRequest(); - - /////////////////////////// From LLPanelGeneral ////////////////////////// - // if we have no agent, we can't let them choose anything - // if we have an agent, then we only let them choose if they have a choice - bool can_choose_maturity = - gAgent.getID().notNull() && - (gAgent.isMature() || gAgent.isGodlike()); - - LLComboBox* maturity_combo = getChild("maturity_desired_combobox"); - LLAvatarPropertiesProcessor::getInstance()->sendAvatarLegacyPropertiesRequest( gAgent.getID() ); - if (can_choose_maturity) - { - // if they're not adult or a god, they shouldn't see the adult selection, so delete it - if (!gAgent.isAdult() && !gAgent.isGodlikeWithoutAdminMenuFakery()) - { - // we're going to remove the adult entry from the combo - LLScrollListCtrl* maturity_list = maturity_combo->findChild("ComboBox"); - if (maturity_list) - { - maturity_list->deleteItems(LLSD(SIM_ACCESS_ADULT)); - } - } - getChildView("maturity_desired_combobox")->setEnabled( true); - getChildView("maturity_desired_textbox")->setVisible( false); - } - else - { - getChild("maturity_desired_textbox")->setValue(maturity_combo->getSelectedItemLabel()); - getChildView("maturity_desired_combobox")->setEnabled( false); - } - - // Forget previous language changes. - mLanguageChanged = false; - - // Display selected maturity icons. - onChangeMaturity(); - - onChangeModelFolder(); - onChangePBRFolder(); - onChangeTextureFolder(); - onChangeSoundFolder(); - onChangeAnimationFolder(); - - // Load (double-)click to walk/teleport settings. - updateClickActionViews(); - - // Enabled/disabled popups, might have been changed by user actions - // while preferences floater was closed. - buildPopupLists(); - - - //get the options that were checked - onNotificationsChange("FriendIMOptions"); - onNotificationsChange("NonFriendIMOptions"); - onNotificationsChange("ConferenceIMOptions"); - onNotificationsChange("GroupChatOptions"); - onNotificationsChange("NearbyChatOptions"); - onNotificationsChange("ObjectIMOptions"); - - LLPanelLogin::setAlwaysRefresh(true); - refresh(); - - // Make sure the current state of prefs are saved away when - // when the floater is opened. That will make cancel do its - // job - saveSettings(); - - // Make sure there is a default preference file - LLPresetsManager::getInstance()->createMissingDefault(PRESETS_CAMERA); - LLPresetsManager::getInstance()->createMissingDefault(PRESETS_GRAPHIC); - - bool started = (LLStartUp::getStartupState() == STATE_STARTED); - - LLButton* load_btn = findChild("PrefLoadButton"); - LLButton* save_btn = findChild("PrefSaveButton"); - LLButton* delete_btn = findChild("PrefDeleteButton"); - LLButton* exceptions_btn = findChild("RenderExceptionsButton"); - LLButton* auto_adjustments_btn = findChild("AutoAdjustmentsButton"); - - if (load_btn && save_btn && delete_btn && exceptions_btn && auto_adjustments_btn) - { - load_btn->setEnabled(started); - save_btn->setEnabled(started); - delete_btn->setEnabled(started); - exceptions_btn->setEnabled(started); - auto_adjustments_btn->setEnabled(started); - } - - collectSearchableItems(); - if (!mFilterEdit->getText().empty()) - { - mFilterEdit->setText(LLStringExplicit("")); - onUpdateFilterTerm(true); - } -} - -void LLFloaterPreference::onRenderOptionEnable() -{ - refreshEnabledGraphics(); -} - -void LLFloaterPreference::onAvatarImpostorsEnable() -{ - refreshEnabledGraphics(); -} - -//static -void LLFloaterPreference::initDoNotDisturbResponse() - { - if (!gSavedPerAccountSettings.getBOOL("DoNotDisturbResponseChanged")) - { - //LLTrans::getString("DoNotDisturbModeResponseDefault") is used here for localization (EXT-5885) - gSavedPerAccountSettings.setString("DoNotDisturbModeResponse", LLTrans::getString("DoNotDisturbModeResponseDefault")); - } - } - -//static -void LLFloaterPreference::updateShowFavoritesCheckbox(bool val) -{ - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); - if (instance) - { - instance->getChild("favorites_on_login_check")->setValue(val); - } -} - -void LLFloaterPreference::setHardwareDefaults() -{ - std::string preset_graphic_active = gSavedSettings.getString("PresetGraphicActive"); - if (!preset_graphic_active.empty()) - { - saveGraphicsPreset(preset_graphic_active); - saveSettings(); // save here to be able to return to the previous preset by Cancel - } - setRecommendedSettings(); -} - -void LLFloaterPreference::setRecommendedSettings() -{ - resetAutotuneSettings(); - gSavedSettings.getControl("RenderVSyncEnable")->resetToDefault(true); - - LLFeatureManager::getInstance()->applyRecommendedSettings(); - - // reset indirects before refresh because we may have changed what they control - LLAvatarComplexityControls::setIndirectControls(); - - refreshEnabledGraphics(); - gSavedSettings.setString("PresetGraphicActive", ""); - LLPresetsManager::getInstance()->triggerChangeSignal(); - - LLTabContainer* tabcontainer = getChild("pref core"); - child_list_t::const_iterator iter = tabcontainer->getChildList()->begin(); - child_list_t::const_iterator end = tabcontainer->getChildList()->end(); - for ( ; iter != end; ++iter) - { - LLView* view = *iter; - LLPanelPreference* panel = dynamic_cast(view); - if (panel) - { - panel->setHardwareDefaults(); - } - } -} - -void LLFloaterPreference::resetAutotuneSettings() -{ - gSavedSettings.setBOOL("AutoTuneFPS", false); - - const std::string autotune_settings[] = { - "AutoTuneLock", - "KeepAutoTuneLock", - "TargetFPS", - "TuningFPSStrategy", - "AutoTuneImpostorByDistEnabled", - "AutoTuneImpostorFarAwayDistance" , - "AutoTuneRenderFarClipMin", - "AutoTuneRenderFarClipTarget", - "RenderAvatarMaxART" - }; - - for (auto it : autotune_settings) - { - gSavedSettings.getControl(it)->resetToDefault(true); - } -} - -void LLFloaterPreference::getControlNames(std::vector& names) -{ - LLView* view = findChild("display"); - LLFloater* advanced = LLFloaterReg::findTypedInstance("prefs_graphics_advanced"); - if (view && advanced) - { - std::list stack; - stack.push_back(view); - stack.push_back(advanced); - while(!stack.empty()) - { - // Process view on top of the stack - LLView* curview = stack.front(); - stack.pop_front(); - - LLUICtrl* ctrl = dynamic_cast(curview); - if (ctrl) - { - LLControlVariable* control = ctrl->getControlVariable(); - if (control) - { - std::string control_name = control->getName(); - if (std::find(names.begin(), names.end(), control_name) == names.end()) - { - names.push_back(control_name); - } - } - } - - for (child_list_t::const_iterator iter = curview->getChildList()->begin(); - iter != curview->getChildList()->end(); ++iter) - { - stack.push_back(*iter); - } - } - } -} - -//virtual -void LLFloaterPreference::onClose(bool app_quitting) -{ - gSavedSettings.setS32("LastPrefTab", getChild("pref core")->getCurrentPanelIndex()); - LLPanelLogin::setAlwaysRefresh(false); - if (!app_quitting) - { - cancel(); - } -} - -// static -void LLFloaterPreference::onBtnOK(const LLSD& userdata) -{ - // commit any outstanding text entry - if (hasFocus()) - { - LLUICtrl* cur_focus = dynamic_cast(gFocusMgr.getKeyboardFocus()); - if (cur_focus && cur_focus->acceptsTextInput()) - { - cur_focus->onCommit(); - } - } - - if (canClose()) - { - saveSettings(); - apply(); - - if (userdata.asString() == "closeadvanced") - { - LLFloaterReg::hideInstance("prefs_graphics_advanced"); - } - else - { - closeFloater(false); - } - - //Conversation transcript and log path changed so reload conversations based on new location - if(mPriorInstantMessageLogPath.length()) - { - if(moveTranscriptsAndLog()) - { - //When floaters are empty but have a chat history files, reload chat history into them - LLFloaterIMSessionTab::reloadEmptyFloaters(); - } - //Couldn't move files so restore the old path and show a notification - else - { - gSavedPerAccountSettings.setString("InstantMessageLogPath", mPriorInstantMessageLogPath); - LLNotificationsUtil::add("PreferenceChatPathChanged"); - } - mPriorInstantMessageLogPath.clear(); - } - - LLUIColorTable::instance().saveUserSettings(); - gSavedSettings.saveToFile(gSavedSettings.getString("ClientSettingsFile"), true); - - //Only save once logged in and loaded per account settings - if(mGotPersonalInfo) - { - gSavedPerAccountSettings.saveToFile(gSavedSettings.getString("PerAccountSettingsFile"), true); - } - } - else - { - // Show beep, pop up dialog, etc. - LL_INFOS("Preferences") << "Can't close preferences!" << LL_ENDL; - } - - LLPanelLogin::updateLocationSelectorsVisibility(); - //Need to reload the navmesh if the pathing console is up - LLHandle pathfindingConsoleHandle = LLFloaterPathfindingConsole::getInstanceHandle(); - if ( !pathfindingConsoleHandle.isDead() ) - { - LLFloaterPathfindingConsole* pPathfindingConsole = pathfindingConsoleHandle.get(); - pPathfindingConsole->onRegionBoundaryCross(); - } -} - -// static -void LLFloaterPreference::onBtnCancel(const LLSD& userdata) -{ - if (hasFocus()) - { - LLUICtrl* cur_focus = dynamic_cast(gFocusMgr.getKeyboardFocus()); - if (cur_focus && cur_focus->acceptsTextInput()) - { - cur_focus->onCommit(); - } - refresh(); - } - - if (userdata.asString() == "closeadvanced") - { - cancel({"RenderQualityPerformance"}); - LLFloaterReg::hideInstance("prefs_graphics_advanced"); - } - else - { - cancel(); - closeFloater(); - } -} - -// static -void LLFloaterPreference::updateUserInfo(const std::string& visibility) -{ - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); - if (instance) - { - instance->setPersonalInfo(visibility); - } -} - -void LLFloaterPreference::refreshEnabledGraphics() -{ - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); - if (instance) - { - instance->refresh(); - } - - LLFloater* advanced = LLFloaterReg::findTypedInstance("prefs_graphics_advanced"); - if (advanced) - { - advanced->refresh(); - } -} - -void LLFloaterPreference::onClickClearCache() -{ - LLNotificationsUtil::add("ConfirmClearCache", LLSD(), LLSD(), callback_clear_cache); -} - -void LLFloaterPreference::onClickBrowserClearCache() -{ - LLNotificationsUtil::add("ConfirmClearBrowserCache", LLSD(), LLSD(), callback_clear_browser_cache); -} - -// Called when user changes language via the combobox. -void LLFloaterPreference::onLanguageChange() -{ - // Let the user know that the change will only take effect after restart. - // Do it only once so that we're not too irritating. - if (!mLanguageChanged) - { - LLNotificationsUtil::add("ChangeLanguage"); - mLanguageChanged = true; - } -} - -void LLFloaterPreference::onNotificationsChange(const std::string& OptionName) -{ - mNotificationOptions[OptionName] = getChild(OptionName)->getSelectedItemLabel(); - - bool show_notifications_alert = true; - for (notifications_map::iterator it_notification = mNotificationOptions.begin(); it_notification != mNotificationOptions.end(); it_notification++) - { - if(it_notification->second != "No action") - { - show_notifications_alert = false; - break; - } - } - - getChild("notifications_alert")->setVisible(show_notifications_alert); -} - -void LLFloaterPreference::onNameTagOpacityChange(const LLSD& newvalue) -{ - LLColorSwatchCtrl* color_swatch = findChild("background"); - if (color_swatch) - { - LLColor4 new_color = color_swatch->get(); - color_swatch->set( new_color.setAlpha(newvalue.asReal()) ); - } -} - -void LLFloaterPreference::onClickSetCache() -{ - std::string cur_name(gSavedSettings.getString("CacheLocation")); -// std::string cur_top_folder(gDirUtilp->getBaseFileName(cur_name)); - - std::string proposed_name(cur_name); - - (new LLDirPickerThread(boost::bind(&LLFloaterPreference::changeCachePath, this, _1, _2), proposed_name))->getFile(); -} - -void LLFloaterPreference::changeCachePath(const std::vector& filenames, std::string proposed_name) -{ - std::string dir_name = filenames[0]; - if (!dir_name.empty() && dir_name != proposed_name) - { - std::string new_top_folder(gDirUtilp->getBaseFileName(dir_name)); - LLNotificationsUtil::add("CacheWillBeMoved"); - gSavedSettings.setString("NewCacheLocation", dir_name); - gSavedSettings.setString("NewCacheLocationTopFolder", new_top_folder); - } - else - { - std::string cache_location = gDirUtilp->getCacheDir(); - gSavedSettings.setString("CacheLocation", cache_location); - std::string top_folder(gDirUtilp->getBaseFileName(cache_location)); - gSavedSettings.setString("CacheLocationTopFolder", top_folder); - } -} - -void LLFloaterPreference::onClickResetCache() -{ - if (gDirUtilp->getCacheDir(false) == gDirUtilp->getCacheDir(true)) - { - // The cache location was already the default. - return; - } - gSavedSettings.setString("NewCacheLocation", ""); - gSavedSettings.setString("NewCacheLocationTopFolder", ""); - LLNotificationsUtil::add("CacheWillBeMoved"); - std::string cache_location = gDirUtilp->getCacheDir(false); - gSavedSettings.setString("CacheLocation", cache_location); - std::string top_folder(gDirUtilp->getBaseFileName(cache_location)); - gSavedSettings.setString("CacheLocationTopFolder", top_folder); -} - -void LLFloaterPreference::onClickSkin(LLUICtrl* ctrl, const LLSD& userdata) -{ - gSavedSettings.setString("SkinCurrent", userdata.asString()); - ctrl->setValue(userdata.asString()); -} - -void LLFloaterPreference::onSelectSkin() -{ - std::string skin_selection = getChild("skin_selection")->getValue().asString(); - gSavedSettings.setString("SkinCurrent", skin_selection); -} - -void LLFloaterPreference::refreshSkin(void* data) -{ - LLPanel*self = (LLPanel*)data; - sSkin = gSavedSettings.getString("SkinCurrent"); - self->getChild("skin_selection", true)->setValue(sSkin); -} - -void LLFloaterPreference::buildPopupLists() -{ - LLScrollListCtrl& disabled_popups = - getChildRef("disabled_popups"); - LLScrollListCtrl& enabled_popups = - getChildRef("enabled_popups"); - - disabled_popups.deleteAllItems(); - enabled_popups.deleteAllItems(); - - for (LLNotifications::TemplateMap::const_iterator iter = LLNotifications::instance().templatesBegin(); - iter != LLNotifications::instance().templatesEnd(); - ++iter) - { - LLNotificationTemplatePtr templatep = iter->second; - LLNotificationFormPtr formp = templatep->mForm; - - LLNotificationForm::EIgnoreType ignore = formp->getIgnoreType(); - if (ignore <= LLNotificationForm::IGNORE_NO) - continue; - - LLSD row; - row["columns"][0]["value"] = formp->getIgnoreMessage(); - row["columns"][0]["font"] = "SANSSERIF_SMALL"; - row["columns"][0]["width"] = 400; - - LLScrollListItem* item = NULL; - - bool show_popup = !formp->getIgnored(); - if (!show_popup) - { - if (ignore == LLNotificationForm::IGNORE_WITH_LAST_RESPONSE) - { - LLSD last_response = LLUI::getInstance()->mSettingGroups["config"]->getLLSD("Default" + templatep->mName); - if (!last_response.isUndefined()) - { - for (LLSD::map_const_iterator it = last_response.beginMap(); - it != last_response.endMap(); - ++it) - { - if (it->second.asBoolean()) - { - row["columns"][1]["value"] = formp->getElement(it->first)["ignore"].asString(); - row["columns"][1]["font"] = "SANSSERIF_SMALL"; - row["columns"][1]["width"] = 360; - break; - } - } - } - } - item = disabled_popups.addElement(row); - } - else - { - item = enabled_popups.addElement(row); - } - - if (item) - { - item->setUserdata((void*)&iter->first); - } - } -} - -void LLFloaterPreference::refreshEnabledState() -{ - LLCheckBoxCtrl* ctrl_pbr = getChild("UsePBRShaders"); - - //PBR - ctrl_pbr->setEnabled(true); - - // Cannot have floater active until caps have been received - getChild("default_creation_permissions")->setEnabled(LLStartUp::getStartupState() >= STATE_STARTED); - - getChildView("block_list")->setEnabled(LLLoginInstance::getInstance()->authSuccess()); -} - -void LLAvatarComplexityControls::setIndirectControls() -{ - /* - * We have controls that have an indirect relationship between the control - * values and adjacent text and the underlying setting they influence. - * In each case, the control and its associated setting are named Indirect - * This method interrogates the controlled setting and establishes the - * appropriate value for the indirect control. It must be called whenever the - * underlying setting may have changed other than through the indirect control, - * such as when the 'Reset all to recommended settings' button is used... - */ - setIndirectMaxNonImpostors(); - setIndirectMaxArc(); -} - -// static -void LLAvatarComplexityControls::setIndirectMaxNonImpostors() -{ - U32 max_non_impostors = gSavedSettings.getU32("RenderAvatarMaxNonImpostors"); - // for this one, we just need to make zero, which means off, the max value of the slider - U32 indirect_max_non_impostors = (0 == max_non_impostors) ? LLVOAvatar::NON_IMPOSTORS_MAX_SLIDER : max_non_impostors; - gSavedSettings.setU32("IndirectMaxNonImpostors", indirect_max_non_impostors); -} - -void LLAvatarComplexityControls::setIndirectMaxArc() -{ - U32 max_arc = gSavedSettings.getU32("RenderAvatarMaxComplexity"); - U32 indirect_max_arc; - if (0 == max_arc) - { - // the off position is all the way to the right, so set to control max - indirect_max_arc = INDIRECT_MAX_ARC_OFF; - } - else - { - // This is the inverse of the calculation in updateMaxComplexity - indirect_max_arc = (U32)ll_round(((log(F32(max_arc)) - MIN_ARC_LOG) / ARC_LIMIT_MAP_SCALE)) + MIN_INDIRECT_ARC_LIMIT; - } - gSavedSettings.setU32("IndirectMaxComplexity", indirect_max_arc); -} - -void LLFloaterPreference::refresh() -{ - LLPanel::refresh(); - LLAvatarComplexityControls::setText( - gSavedSettings.getU32("RenderAvatarMaxComplexity"), - getChild("IndirectMaxComplexityText", true)); - refreshEnabledState(); - LLFloater* advanced = LLFloaterReg::findTypedInstance("prefs_graphics_advanced"); - if (advanced) - { - advanced->refresh(); - } - updateClickActionViews(); -} - -void LLFloaterPreference::onCommitWindowedMode() -{ - refresh(); -} - -void LLFloaterPreference::onChangeQuality(const LLSD& data) -{ - U32 level = (U32)(data.asReal()); - LLFeatureManager::getInstance()->setGraphicsLevel(level, true); - refreshEnabledGraphics(); - refresh(); -} - -void LLFloaterPreference::onClickSetSounds() -{ - // Disable Enable gesture sounds checkbox if the master sound is disabled - // or if sound effects are disabled. - getChild("gesture_audio_play_btn")->setEnabled(!gSavedSettings.getBOOL("MuteSounds")); -} - -void LLFloaterPreference::onClickEnablePopup() -{ - LLScrollListCtrl& disabled_popups = getChildRef("disabled_popups"); - - std::vector items = disabled_popups.getAllSelected(); - std::vector::iterator itor; - for (itor = items.begin(); itor != items.end(); ++itor) - { - LLNotificationTemplatePtr templatep = LLNotifications::instance().getTemplate(*(std::string*)((*itor)->getUserdata())); - //gSavedSettings.setWarning(templatep->mName, true); - std::string notification_name = templatep->mName; - LLUI::getInstance()->mSettingGroups["ignores"]->setBOOL(notification_name, true); - } - - buildPopupLists(); - if (!mFilterEdit->getText().empty()) - { - filterIgnorableNotifications(); - } -} - -void LLFloaterPreference::onClickDisablePopup() -{ - LLScrollListCtrl& enabled_popups = getChildRef("enabled_popups"); - - std::vector items = enabled_popups.getAllSelected(); - std::vector::iterator itor; - for (itor = items.begin(); itor != items.end(); ++itor) - { - LLNotificationTemplatePtr templatep = LLNotifications::instance().getTemplate(*(std::string*)((*itor)->getUserdata())); - templatep->mForm->setIgnored(true); - } - - buildPopupLists(); - if (!mFilterEdit->getText().empty()) - { - filterIgnorableNotifications(); - } -} - -void LLFloaterPreference::resetAllIgnored() -{ - for (LLNotifications::TemplateMap::const_iterator iter = LLNotifications::instance().templatesBegin(); - iter != LLNotifications::instance().templatesEnd(); - ++iter) - { - if (iter->second->mForm->getIgnoreType() > LLNotificationForm::IGNORE_NO) - { - iter->second->mForm->setIgnored(false); - } - } -} - -void LLFloaterPreference::setAllIgnored() -{ - for (LLNotifications::TemplateMap::const_iterator iter = LLNotifications::instance().templatesBegin(); - iter != LLNotifications::instance().templatesEnd(); - ++iter) - { - if (iter->second->mForm->getIgnoreType() > LLNotificationForm::IGNORE_NO) - { - iter->second->mForm->setIgnored(true); - } - } -} - -void LLFloaterPreference::onClickLogPath() -{ - std::string proposed_name(gSavedPerAccountSettings.getString("InstantMessageLogPath")); - mPriorInstantMessageLogPath.clear(); - - - (new LLDirPickerThread(boost::bind(&LLFloaterPreference::changeLogPath, this, _1, _2), proposed_name))->getFile(); -} - -void LLFloaterPreference::changeLogPath(const std::vector& filenames, std::string proposed_name) -{ - //Path changed - if (proposed_name != filenames[0]) - { - gSavedPerAccountSettings.setString("InstantMessageLogPath", filenames[0]); - mPriorInstantMessageLogPath = proposed_name; - - // enable/disable 'Delete transcripts button - updateDeleteTranscriptsButton(); - } -} - -bool LLFloaterPreference::moveTranscriptsAndLog() -{ - std::string instantMessageLogPath(gSavedPerAccountSettings.getString("InstantMessageLogPath")); - std::string chatLogPath = gDirUtilp->add(instantMessageLogPath, gDirUtilp->getUserName()); - - bool madeDirectory = false; - - //Does the directory really exist, if not then make it - if(!LLFile::isdir(chatLogPath)) - { - //mkdir success is defined as zero - if(LLFile::mkdir(chatLogPath) != 0) - { - return false; - } - madeDirectory = true; - } - - std::string originalConversationLogDir = LLConversationLog::instance().getFileName(); - std::string targetConversationLogDir = gDirUtilp->add(chatLogPath, "conversation.log"); - //Try to move the conversation log - if(!LLConversationLog::instance().moveLog(originalConversationLogDir, targetConversationLogDir)) - { - //Couldn't move the log and created a new directory so remove the new directory - if(madeDirectory) - { - LLFile::rmdir(chatLogPath); - } - return false; - } - - //Attempt to move transcripts - std::vector listOfTranscripts; - std::vector listOfFilesMoved; - - LLLogChat::getListOfTranscriptFiles(listOfTranscripts); - - if(!LLLogChat::moveTranscripts(gDirUtilp->getChatLogsDir(), - instantMessageLogPath, - listOfTranscripts, - listOfFilesMoved)) - { - //Couldn't move all the transcripts so restore those that moved back to their old location - LLLogChat::moveTranscripts(instantMessageLogPath, - gDirUtilp->getChatLogsDir(), - listOfFilesMoved); - - //Move the conversation log back - LLConversationLog::instance().moveLog(targetConversationLogDir, originalConversationLogDir); - - if(madeDirectory) - { - LLFile::rmdir(chatLogPath); - } - - return false; - } - - gDirUtilp->setChatLogsDir(instantMessageLogPath); - gDirUtilp->updatePerAccountChatLogsDir(); - - return true; -} - -void LLFloaterPreference::setPersonalInfo(const std::string& visibility) -{ - mGotPersonalInfo = true; - mDirectoryVisibility = visibility; - - if (visibility == VISIBILITY_DEFAULT) - { - mOriginalHideOnlineStatus = false; - getChildView("online_visibility")->setEnabled(true); - } - else if (visibility == VISIBILITY_HIDDEN) - { - mOriginalHideOnlineStatus = true; - getChildView("online_visibility")->setEnabled(true); - } - else - { - mOriginalHideOnlineStatus = true; - } - - getChild("online_searchresults")->setEnabled(true); - getChildView("friends_online_notify_checkbox")->setEnabled(true); - getChild("online_visibility")->setValue(mOriginalHideOnlineStatus); - getChild("online_visibility")->setLabelArg("[DIR_VIS]", mDirectoryVisibility); - - getChildView("favorites_on_login_check")->setEnabled(true); - getChildView("log_path_button")->setEnabled(true); - getChildView("chat_font_size")->setEnabled(true); - getChildView("conversation_log_combo")->setEnabled(true); - getChild("voice_call_friends_only_check")->setEnabled(true); - getChild("voice_call_friends_only_check")->setValue(gSavedPerAccountSettings.getBOOL("VoiceCallsFriendsOnly")); -} - - -void LLFloaterPreference::refreshUI() -{ - refresh(); -} - -void LLAvatarComplexityControls::updateMax(LLSliderCtrl* slider, LLTextBox* value_label, bool short_val) -{ - // Called when the IndirectMaxComplexity control changes - // Responsible for fixing the slider label (IndirectMaxComplexityText) and setting RenderAvatarMaxComplexity - U32 indirect_value = slider->getValue().asInteger(); - U32 max_arc; - - if (INDIRECT_MAX_ARC_OFF == indirect_value) - { - // The 'off' position is when the slider is all the way to the right, - // which is a value of INDIRECT_MAX_ARC_OFF, - // so it is necessary to set max_arc to 0 disable muted avatars. - max_arc = 0; - } - else - { - // if this is changed, the inverse calculation in setIndirectMaxArc - // must be changed to match - max_arc = (U32)ll_round(exp(MIN_ARC_LOG + (ARC_LIMIT_MAP_SCALE * (indirect_value - MIN_INDIRECT_ARC_LIMIT)))); - } - - gSavedSettings.setU32("RenderAvatarMaxComplexity", (U32)max_arc); - setText(max_arc, value_label, short_val); -} - -void LLAvatarComplexityControls::setText(U32 value, LLTextBox* text_box, bool short_val) -{ - if (0 == value) - { - text_box->setText(LLTrans::getString("no_limit")); - } - else - { - std::string text_value = short_val ? llformat("%d", value / 1000) : llformat("%d", value); - text_box->setText(text_value); - } -} - -void LLAvatarComplexityControls::updateMaxRenderTime(LLSliderCtrl* slider, LLTextBox* value_label, bool short_val) -{ - setRenderTimeText((F32)(LLPerfStats::renderAvatarMaxART_ns/1000), value_label, short_val); -} - -void LLAvatarComplexityControls::setRenderTimeText(F32 value, LLTextBox* text_box, bool short_val) -{ - if (0 == value) - { - text_box->setText(LLTrans::getString("no_limit")); - } - else - { - text_box->setText(llformat("%.0f", value)); - } -} - -void LLFloaterPreference::updateMaxComplexity() -{ - // Called when the IndirectMaxComplexity control changes - LLAvatarComplexityControls::updateMax( - getChild("IndirectMaxComplexity"), - getChild("IndirectMaxComplexityText")); -} - -void LLFloaterPreference::updateComplexityText() -{ - LLAvatarComplexityControls::setText(gSavedSettings.getU32("RenderAvatarMaxComplexity"), - getChild("IndirectMaxComplexityText", true)); -} - -bool LLFloaterPreference::loadFromFilename(const std::string& filename, std::map &label_map) -{ - LLXMLNodePtr root; - - if (!LLXMLNode::parseFile(filename, root, NULL)) - { - LL_WARNS("Preferences") << "Unable to parse file " << filename << LL_ENDL; - return false; - } - - if (!root->hasName("labels")) - { - LL_WARNS("Preferences") << filename << " is not a valid definition file" << LL_ENDL; - return false; - } - - LabelTable params; - LLXUIParser parser; - parser.readXUI(root, params, filename); - - if (params.validateBlock()) - { - for (LLInitParam::ParamIterator::const_iterator it = params.labels.begin(); - it != params.labels.end(); - ++it) - { - LabelDef label_entry = *it; - label_map[label_entry.name] = label_entry.value; - } - } - else - { - LL_WARNS("Preferences") << filename << " failed to load" << LL_ENDL; - return false; - } - - return true; -} - -void LLFloaterPreference::onChangeMaturity() -{ - U8 sim_access = gSavedSettings.getU32("PreferredMaturity"); - - getChild("rating_icon_general")->setVisible(sim_access == SIM_ACCESS_PG - || sim_access == SIM_ACCESS_MATURE - || sim_access == SIM_ACCESS_ADULT); - - getChild("rating_icon_moderate")->setVisible(sim_access == SIM_ACCESS_MATURE - || sim_access == SIM_ACCESS_ADULT); - - getChild("rating_icon_adult")->setVisible(sim_access == SIM_ACCESS_ADULT); -} - -void LLFloaterPreference::onChangeComplexityMode(const LLSD& newvalue) -{ - bool enable_complexity = newvalue.asInteger() != LLVOAvatar::AV_RENDER_ONLY_SHOW_FRIENDS; - getChild("IndirectMaxComplexity")->setEnabled(enable_complexity); -} - -std::string get_category_path(LLFolderType::EType cat_type) -{ - LLUUID cat_id = gInventory.findUserDefinedCategoryUUIDForType(cat_type); - return get_category_path(cat_id); -} - -void LLFloaterPreference::onChangeModelFolder() -{ - if (gInventory.isInventoryUsable()) - { - getChild("upload_models")->setText(get_category_path(LLFolderType::FT_OBJECT)); - } -} - -void LLFloaterPreference::onChangePBRFolder() -{ - if (gInventory.isInventoryUsable()) - { - getChild("upload_pbr")->setText(get_category_path(LLFolderType::FT_MATERIAL)); - } -} - -void LLFloaterPreference::onChangeTextureFolder() -{ - if (gInventory.isInventoryUsable()) - { - getChild("upload_textures")->setText(get_category_path(LLFolderType::FT_TEXTURE)); - } -} - -void LLFloaterPreference::onChangeSoundFolder() -{ - if (gInventory.isInventoryUsable()) - { - getChild("upload_sounds")->setText(get_category_path(LLFolderType::FT_SOUND)); - } -} - -void LLFloaterPreference::onChangeAnimationFolder() -{ - if (gInventory.isInventoryUsable()) - { - getChild("upload_animation")->setText(get_category_path(LLFolderType::FT_ANIMATION)); - } -} - -// FIXME: this will stop you from spawning the sidetray from preferences dialog on login screen -// but the UI for this will still be enabled -void LLFloaterPreference::onClickBlockList() -{ - LLFloaterSidePanelContainer::showPanel("people", "panel_people", - LLSD().with("people_panel_tab_name", "blocked_panel")); -} - -void LLFloaterPreference::onClickProxySettings() -{ - LLFloaterReg::showInstance("prefs_proxy"); -} - -void LLFloaterPreference::onClickTranslationSettings() -{ - LLFloaterReg::showInstance("prefs_translation"); -} - -void LLFloaterPreference::onClickAutoReplace() -{ - LLFloaterReg::showInstance("prefs_autoreplace"); -} - -void LLFloaterPreference::onClickSpellChecker() -{ - LLFloaterReg::showInstance("prefs_spellchecker"); -} - -void LLFloaterPreference::onClickRenderExceptions() -{ - LLFloaterReg::showInstance("avatar_render_settings"); -} - -void LLFloaterPreference::onClickAutoAdjustments() -{ - LLFloaterPerformance* performance_floater = LLFloaterReg::showTypedInstance("performance"); - if (performance_floater) - { - performance_floater->showAutoadjustmentsPanel(); - } -} - -void LLFloaterPreference::onClickAdvanced() -{ - LLFloaterReg::showInstance("prefs_graphics_advanced"); - - LLTabContainer* tabcontainer = getChild("pref core"); - for (child_list_t::const_iterator iter = tabcontainer->getChildList()->begin(); - iter != tabcontainer->getChildList()->end(); ++iter) - { - LLView* view = *iter; - LLPanelPreferenceGraphics* panel = dynamic_cast(view); - if (panel) - { - panel->resetDirtyChilds(); - } - } -} - -void LLFloaterPreference::onClickActionChange() -{ - updateClickActionControls(); -} - -void LLFloaterPreference::onAtmosShaderChange() -{ - LLCheckBoxCtrl* ctrl_alm = getChild("UseLightShaders"); - if(ctrl_alm) - { - //Deferred/SSAO/Shadows - bool bumpshiny = LLCubeMap::sUseCubeMaps && LLFeatureManager::getInstance()->isFeatureAvailable("RenderObjectBump") && gSavedSettings.getBOOL("RenderObjectBump"); - bool shaders = gSavedSettings.getBOOL("WindLightUseAtmosShaders"); - bool enabled = LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferred") && - bumpshiny && - shaders; - - ctrl_alm->setEnabled(enabled); - } -} - -void LLFloaterPreference::onClickPermsDefault() -{ - LLFloaterReg::showInstance("perms_default"); -} - -void LLFloaterPreference::onClickRememberedUsernames() -{ - LLFloaterReg::showInstance("forget_username"); -} - -void LLFloaterPreference::onDeleteTranscripts() -{ - LLSD args; - args["FOLDER"] = gDirUtilp->getUserName(); - - LLNotificationsUtil::add("PreferenceChatDeleteTranscripts", args, LLSD(), boost::bind(&LLFloaterPreference::onDeleteTranscriptsResponse, this, _1, _2)); -} - -void LLFloaterPreference::onDeleteTranscriptsResponse(const LLSD& notification, const LLSD& response) -{ - if (0 == LLNotificationsUtil::getSelectedOption(notification, response)) - { - LLLogChat::deleteTranscripts(); - updateDeleteTranscriptsButton(); - } -} - -void LLFloaterPreference::onLogChatHistorySaved() -{ - LLButton * delete_transcripts_buttonp = getChild("delete_transcripts"); - - if (!delete_transcripts_buttonp->getEnabled()) - { - delete_transcripts_buttonp->setEnabled(true); - } -} - -void LLFloaterPreference::updateClickActionControls() -{ - const int single_clk_action = getChild("single_click_action_combo")->getValue().asInteger(); - const int double_clk_action = getChild("double_click_action_combo")->getValue().asInteger(); - - // Todo: This is a very ugly way to get access to keybindings. - // Reconsider possible options. - // Potential option: make constructor of LLKeyConflictHandler private - // but add a getter that will return shared pointer for specific - // mode, pointer should only exist so long as there are external users. - // In such case we won't need to do this 'dynamic_cast' nightmare. - // updateTable() can also be avoided - LLTabContainer* tabcontainer = getChild("pref core"); - for (child_list_t::const_iterator iter = tabcontainer->getChildList()->begin(); - iter != tabcontainer->getChildList()->end(); ++iter) - { - LLView* view = *iter; - LLPanelPreferenceControls* panel = dynamic_cast(view); - if (panel) - { - panel->setKeyBind("walk_to", - EMouseClickType::CLICK_LEFT, - KEY_NONE, - MASK_NONE, - single_clk_action == 1); - - panel->setKeyBind("walk_to", - EMouseClickType::CLICK_DOUBLELEFT, - KEY_NONE, - MASK_NONE, - double_clk_action == 1); - - panel->setKeyBind("teleport_to", - EMouseClickType::CLICK_DOUBLELEFT, - KEY_NONE, - MASK_NONE, - double_clk_action == 2); - - panel->updateAndApply(); - } - } -} - -void LLFloaterPreference::updateClickActionViews() -{ - bool click_to_walk = false; - bool dbl_click_to_walk = false; - bool dbl_click_to_teleport = false; - - // Todo: This is a very ugly way to get access to keybindings. - // Reconsider possible options. - LLTabContainer* tabcontainer = getChild("pref core"); - for (child_list_t::const_iterator iter = tabcontainer->getChildList()->begin(); - iter != tabcontainer->getChildList()->end(); ++iter) - { - LLView* view = *iter; - LLPanelPreferenceControls* panel = dynamic_cast(view); - if (panel) - { - click_to_walk = panel->canKeyBindHandle("walk_to", - EMouseClickType::CLICK_LEFT, - KEY_NONE, - MASK_NONE); - - dbl_click_to_walk = panel->canKeyBindHandle("walk_to", - EMouseClickType::CLICK_DOUBLELEFT, - KEY_NONE, - MASK_NONE); - - dbl_click_to_teleport = panel->canKeyBindHandle("teleport_to", - EMouseClickType::CLICK_DOUBLELEFT, - KEY_NONE, - MASK_NONE); - } - } - - getChild("single_click_action_combo")->setValue((int)click_to_walk); - getChild("double_click_action_combo")->setValue(dbl_click_to_teleport ? 2 : (int)dbl_click_to_walk); -} - -void LLFloaterPreference::updateSearchableItems() -{ - mSearchDataDirty = true; -} - -void LLFloaterPreference::applyUIColor(LLUICtrl* ctrl, const LLSD& param) -{ - LLUIColorTable::instance().setColor(param.asString(), LLColor4(ctrl->getValue())); -} - -void LLFloaterPreference::getUIColor(LLUICtrl* ctrl, const LLSD& param) -{ - LLColorSwatchCtrl* color_swatch = (LLColorSwatchCtrl*) ctrl; - color_swatch->setOriginal(LLUIColorTable::instance().getColor(param.asString())); -} - -void LLFloaterPreference::setCacheLocation(const LLStringExplicit& location) -{ - LLUICtrl* cache_location_editor = getChild("cache_location"); - cache_location_editor->setValue(location); - cache_location_editor->setToolTip(location); -} - -void LLFloaterPreference::selectPanel(const LLSD& name) -{ - LLTabContainer * tab_containerp = getChild("pref core"); - LLPanel * panel = tab_containerp->getPanelByName(name); - if (NULL != panel) - { - tab_containerp->selectTabPanel(panel); - } -} - -void LLFloaterPreference::selectPrivacyPanel() -{ - selectPanel("im"); -} - -void LLFloaterPreference::selectChatPanel() -{ - selectPanel("chat"); -} - -void LLFloaterPreference::changed() -{ - getChild("clear_log")->setEnabled(LLConversationLog::instance().getConversations().size() > 0); - - // set 'enable' property for 'Delete transcripts...' button - updateDeleteTranscriptsButton(); - -} - -void LLFloaterPreference::saveGraphicsPreset(std::string& preset) -{ - mSavedGraphicsPreset = preset; -} - -//------------------------------Updater--------------------------------------- - -static bool handleBandwidthChanged(const LLSD& newvalue) -{ - gViewerThrottle.setMaxBandwidth((F32) newvalue.asReal()); - return true; -} - -class LLPanelPreference::Updater : public LLEventTimer -{ - -public: - - typedef boost::function callback_t; - - Updater(callback_t cb, F32 period) - :LLEventTimer(period), - mCallback(cb) - { - mEventTimer.stop(); - } - - virtual ~Updater(){} - - void update(const LLSD& new_value) - { - mNewValue = new_value; - mEventTimer.start(); - } - -protected: - - bool tick() - { - mCallback(mNewValue); - mEventTimer.stop(); - - return false; - } - -private: - - LLSD mNewValue; - callback_t mCallback; -}; -//---------------------------------------------------------------------------- -static LLPanelInjector t_places("panel_preference"); -LLPanelPreference::LLPanelPreference() -: LLPanel(), - mBandWidthUpdater(NULL) -{ - mCommitCallbackRegistrar.add("Pref.setControlFalse", boost::bind(&LLPanelPreference::setControlFalse,this, _2)); - mCommitCallbackRegistrar.add("Pref.updateMediaAutoPlayCheckbox", boost::bind(&LLPanelPreference::updateMediaAutoPlayCheckbox, this, _1)); - mCommitCallbackRegistrar.add("Pref.PrefDelete", boost::bind(&LLPanelPreference::deletePreset, this, _2)); - mCommitCallbackRegistrar.add("Pref.PrefSave", boost::bind(&LLPanelPreference::savePreset, this, _2)); - mCommitCallbackRegistrar.add("Pref.PrefLoad", boost::bind(&LLPanelPreference::loadPreset, this, _2)); -} - -//virtual -bool LLPanelPreference::postBuild() -{ - ////////////////////// PanelGeneral /////////////////// - if (hasChild("display_names_check", true)) - { - bool use_people_api = gSavedSettings.getBOOL("UsePeopleAPI"); - LLCheckBoxCtrl* ctrl_display_name = getChild("display_names_check"); - ctrl_display_name->setEnabled(use_people_api); - if (!use_people_api) - { - ctrl_display_name->setValue(false); - } - } - - ////////////////////// PanelVoice /////////////////// - if (hasChild("voice_unavailable", true)) - { - bool voice_disabled = gSavedSettings.getBOOL("CmdLineDisableVoice"); - getChildView("voice_unavailable")->setVisible( voice_disabled); - getChildView("enable_voice_check")->setVisible( !voice_disabled); - } - - //////////////////////PanelSkins /////////////////// - - if (hasChild("skin_selection", true)) - { - LLFloaterPreference::refreshSkin(this); - - // if skin is set to a skin that no longer exists (silver) set back to default - if (getChild("skin_selection")->getSelectedIndex() < 0) - { - gSavedSettings.setString("SkinCurrent", "default"); - LLFloaterPreference::refreshSkin(this); - } - - } - - //////////////////////PanelPrivacy /////////////////// - if (hasChild("media_enabled", true)) - { - bool media_enabled = gSavedSettings.getBOOL("AudioStreamingMedia"); - - getChild("media_enabled")->set(media_enabled); - getChild("autoplay_enabled")->setEnabled(media_enabled); - } - if (hasChild("music_enabled", true)) - { - getChild("music_enabled")->set(gSavedSettings.getBOOL("AudioStreamingMusic")); - } - if (hasChild("voice_call_friends_only_check", true)) - { - getChild("voice_call_friends_only_check")->setCommitCallback(boost::bind(&showFriendsOnlyWarning, _1, _2)); - } - if (hasChild("allow_multiple_viewer_check", true)) - { - getChild("allow_multiple_viewer_check")->setCommitCallback(boost::bind(&showMultipleViewersWarning, _1, _2)); - } - if (hasChild("favorites_on_login_check", true)) - { - getChild("favorites_on_login_check")->setCommitCallback(boost::bind(&handleFavoritesOnLoginChanged, _1, _2)); - bool show_favorites_at_login = LLPanelLogin::getShowFavorites(); - getChild("favorites_on_login_check")->setValue(show_favorites_at_login); - } - if (hasChild("mute_chb_label", true)) - { - getChild("mute_chb_label")->setShowCursorHand(false); - getChild("mute_chb_label")->setSoundFlags(LLView::MOUSE_UP); - getChild("mute_chb_label")->setClickedCallback(boost::bind(&toggleMuteWhenMinimized)); - } - - //////////////////////PanelSetup /////////////////// - if (hasChild("max_bandwidth", true)) - { - mBandWidthUpdater = new LLPanelPreference::Updater(boost::bind(&handleBandwidthChanged, _1), BANDWIDTH_UPDATER_TIMEOUT); - gSavedSettings.getControl("ThrottleBandwidthKBPS")->getSignal()->connect(boost::bind(&LLPanelPreference::Updater::update, mBandWidthUpdater, _2)); - } - -#ifdef EXTERNAL_TOS - LLRadioGroup* ext_browser_settings = getChild("preferred_browser_behavior"); - if (ext_browser_settings) - { - // turn off ability to set external/internal browser - ext_browser_settings->setSelectedByValue(LLWeb::BROWSER_EXTERNAL_ONLY, true); - ext_browser_settings->setEnabled(false); - } -#endif - - apply(); - return true; -} - -LLPanelPreference::~LLPanelPreference() -{ - if (mBandWidthUpdater) - { - delete mBandWidthUpdater; - } -} -void LLPanelPreference::apply() -{ - // no-op -} - -void LLPanelPreference::saveSettings() -{ - LLFloater* advanced = LLFloaterReg::findTypedInstance("prefs_graphics_advanced"); - - // Save the value of all controls in the hierarchy - mSavedValues.clear(); - std::list view_stack; - view_stack.push_back(this); - if (advanced) - { - view_stack.push_back(advanced); - } - while(!view_stack.empty()) - { - // Process view on top of the stack - LLView* curview = view_stack.front(); - view_stack.pop_front(); - - LLColorSwatchCtrl* color_swatch = dynamic_cast(curview); - if (color_swatch) - { - mSavedColors[color_swatch->getName()] = color_swatch->get(); - } - else - { - LLUICtrl* ctrl = dynamic_cast(curview); - if (ctrl) - { - LLControlVariable* control = ctrl->getControlVariable(); - if (control) - { - mSavedValues[control] = control->getValue(); - } - } - } - - // Push children onto the end of the work stack - for (child_list_t::const_iterator iter = curview->getChildList()->begin(); - iter != curview->getChildList()->end(); ++iter) - { - view_stack.push_back(*iter); - } - } - - if (LLStartUp::getStartupState() == STATE_STARTED) - { - LLControlVariable* control = gSavedPerAccountSettings.getControl("VoiceCallsFriendsOnly"); - if (control) - { - mSavedValues[control] = control->getValue(); - } - } -} - -void LLPanelPreference::showMultipleViewersWarning(LLUICtrl* checkbox, const LLSD& value) -{ - if (checkbox && checkbox->getValue()) - { - LLNotificationsUtil::add("AllowMultipleViewers"); - } -} - -void LLPanelPreference::showFriendsOnlyWarning(LLUICtrl* checkbox, const LLSD& value) -{ - if (checkbox) - { - gSavedPerAccountSettings.setBOOL("VoiceCallsFriendsOnly", checkbox->getValue().asBoolean()); - if (checkbox->getValue()) - { - LLNotificationsUtil::add("FriendsAndGroupsOnly"); - } - } -} - -void LLPanelPreference::handleFavoritesOnLoginChanged(LLUICtrl* checkbox, const LLSD& value) -{ - if (checkbox) - { - LLFavoritesOrderStorage::instance().showFavoritesOnLoginChanged(checkbox->getValue().asBoolean()); - if(checkbox->getValue()) - { - LLNotificationsUtil::add("FavoritesOnLogin"); - } - } -} - -void LLPanelPreference::toggleMuteWhenMinimized() -{ - std::string mute("MuteWhenMinimized"); - gSavedSettings.setBOOL(mute, !gSavedSettings.getBOOL(mute)); - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); - if (instance) - { - instance->getChild("mute_when_minimized")->setBtnFocus(); - } -} - -void LLPanelPreference::cancel(const std::vector settings_to_skip) -{ - for (control_values_map_t::iterator iter = mSavedValues.begin(); - iter != mSavedValues.end(); ++iter) - { - LLControlVariable* control = iter->first; - LLSD ctrl_value = iter->second; - - if((control->getName() == "InstantMessageLogPath") && (ctrl_value.asString() == "")) - { - continue; - } - - auto found = std::find(settings_to_skip.begin(), settings_to_skip.end(), control->getName()); - if (found != settings_to_skip.end()) - { - continue; - } - - control->set(ctrl_value); - } - - for (string_color_map_t::iterator iter = mSavedColors.begin(); - iter != mSavedColors.end(); ++iter) - { - LLColorSwatchCtrl* color_swatch = findChild(iter->first); - if (color_swatch) - { - color_swatch->set(iter->second); - color_swatch->onCommit(); - } - } -} - -void LLPanelPreference::setControlFalse(const LLSD& user_data) -{ - std::string control_name = user_data.asString(); - LLControlVariable* control = findControl(control_name); - - if (control) - control->set(LLSD(false)); -} - -void LLPanelPreference::updateMediaAutoPlayCheckbox(LLUICtrl* ctrl) -{ - std::string name = ctrl->getName(); - - // Disable "Allow Media to auto play" only when both - // "Streaming Music" and "Media" are unchecked. STORM-513. - if ((name == "enable_music") || (name == "enable_media")) - { - bool music_enabled = getChild("enable_music")->get(); - bool media_enabled = getChild("enable_media")->get(); - - getChild("media_auto_play_combo")->setEnabled(music_enabled || media_enabled); - } -} - -void LLPanelPreference::deletePreset(const LLSD& user_data) -{ - LLFloaterReg::showInstance("delete_pref_preset", user_data.asString()); -} - -void LLPanelPreference::savePreset(const LLSD& user_data) -{ - LLFloaterReg::showInstance("save_pref_preset", user_data.asString()); -} - -void LLPanelPreference::loadPreset(const LLSD& user_data) -{ - LLFloaterReg::showInstance("load_pref_preset", user_data.asString()); -} - -void LLPanelPreference::setHardwareDefaults() -{ -} - -class LLPanelPreferencePrivacy : public LLPanelPreference -{ -public: - LLPanelPreferencePrivacy() - { - mAccountIndependentSettings.push_back("AutoDisengageMic"); - } - - /*virtual*/ void saveSettings() - { - LLPanelPreference::saveSettings(); - - // Don't save (=erase from the saved values map) per-account privacy settings - // if we're not logged in, otherwise they will be reset to defaults on log off. - if (LLStartUp::getStartupState() != STATE_STARTED) - { - // Erase only common settings, assuming there are no color settings on Privacy page. - for (control_values_map_t::iterator it = mSavedValues.begin(); it != mSavedValues.end(); ) - { - const std::string setting = it->first->getName(); - if (find(mAccountIndependentSettings.begin(), - mAccountIndependentSettings.end(), setting) == mAccountIndependentSettings.end()) - { - mSavedValues.erase(it++); - } - else - { - ++it; - } - } - } - } - -private: - std::list mAccountIndependentSettings; -}; - -static LLPanelInjector t_pref_graph("panel_preference_graphics"); -static LLPanelInjector t_pref_privacy("panel_preference_privacy"); - -bool LLPanelPreferenceGraphics::postBuild() -{ - LLFloaterReg::showInstance("prefs_graphics_advanced"); - LLFloaterReg::hideInstance("prefs_graphics_advanced"); - - resetDirtyChilds(); - setPresetText(); - - LLPresetsManager* presetsMgr = LLPresetsManager::getInstance(); - presetsMgr->setPresetListChangeCallback(boost::bind(&LLPanelPreferenceGraphics::onPresetsListChange, this)); - presetsMgr->createMissingDefault(PRESETS_GRAPHIC); // a no-op after the first time, but that's ok - - return LLPanelPreference::postBuild(); -} - -void LLPanelPreferenceGraphics::draw() -{ - setPresetText(); - LLPanelPreference::draw(); -} - -void LLPanelPreferenceGraphics::onPresetsListChange() -{ - resetDirtyChilds(); - setPresetText(); - - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); - if (instance && !gSavedSettings.getString("PresetGraphicActive").empty()) - { - instance->saveSettings(); //make cancel work correctly after changing the preset - } -} - -void LLPanelPreferenceGraphics::setPresetText() -{ - LLTextBox* preset_text = getChild("preset_text"); - - std::string preset_graphic_active = gSavedSettings.getString("PresetGraphicActive"); - - if (!preset_graphic_active.empty() && preset_graphic_active != preset_text->getText()) - { - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); - if (instance) - { - instance->saveGraphicsPreset(preset_graphic_active); - } - } - - if (hasDirtyChilds() && !preset_graphic_active.empty()) - { - gSavedSettings.setString("PresetGraphicActive", ""); - preset_graphic_active.clear(); - // This doesn't seem to cause an infinite recursion. This trigger is needed to cause the pulldown - // panel to update. - LLPresetsManager::getInstance()->triggerChangeSignal(); - } - - if (!preset_graphic_active.empty()) - { - if (preset_graphic_active == PRESETS_DEFAULT) - { - preset_graphic_active = LLTrans::getString(PRESETS_DEFAULT); - } - preset_text->setText(preset_graphic_active); - } - else - { - preset_text->setText(LLTrans::getString("none_paren_cap")); - } - - preset_text->resetDirty(); -} - -bool LLPanelPreferenceGraphics::hasDirtyChilds() -{ - LLFloater* advanced = LLFloaterReg::findTypedInstance("prefs_graphics_advanced"); - std::list view_stack; - view_stack.push_back(this); - if (advanced) - { - view_stack.push_back(advanced); - } - while(!view_stack.empty()) - { - // Process view on top of the stack - LLView* curview = view_stack.front(); - view_stack.pop_front(); - - LLUICtrl* ctrl = dynamic_cast(curview); - if (ctrl) - { - if (ctrl->isDirty()) - { - LLControlVariable* control = ctrl->getControlVariable(); - if (control) - { - std::string control_name = control->getName(); - if (!control_name.empty()) - { - return true; - } - } - } - } - // Push children onto the end of the work stack - for (child_list_t::const_iterator iter = curview->getChildList()->begin(); - iter != curview->getChildList()->end(); ++iter) - { - view_stack.push_back(*iter); - } - } - - return false; -} - -void LLPanelPreferenceGraphics::resetDirtyChilds() -{ - LLFloater* advanced = LLFloaterReg::findTypedInstance("prefs_graphics_advanced"); - std::list view_stack; - view_stack.push_back(this); - if (advanced) - { - view_stack.push_back(advanced); - } - while(!view_stack.empty()) - { - // Process view on top of the stack - LLView* curview = view_stack.front(); - view_stack.pop_front(); - - LLUICtrl* ctrl = dynamic_cast(curview); - if (ctrl) - { - ctrl->resetDirty(); - } - // Push children onto the end of the work stack - for (child_list_t::const_iterator iter = curview->getChildList()->begin(); - iter != curview->getChildList()->end(); ++iter) - { - view_stack.push_back(*iter); - } - } -} - -void LLPanelPreferenceGraphics::cancel(const std::vector settings_to_skip) -{ - LLPanelPreference::cancel(settings_to_skip); -} -void LLPanelPreferenceGraphics::saveSettings() -{ - resetDirtyChilds(); - std::string preset_graphic_active = gSavedSettings.getString("PresetGraphicActive"); - if (preset_graphic_active.empty()) - { - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); - if (instance) - { - //don't restore previous preset after closing Preferences - instance->saveGraphicsPreset(preset_graphic_active); - } - } - LLPanelPreference::saveSettings(); -} -void LLPanelPreferenceGraphics::setHardwareDefaults() -{ - resetDirtyChilds(); -} - -//------------------------LLPanelPreferenceControls-------------------------------- -static LLPanelInjector t_pref_contrls("panel_preference_controls"); - -LLPanelPreferenceControls::LLPanelPreferenceControls() - :LLPanelPreference(), - mEditingColumn(-1), - mEditingMode(0) -{ - // MODE_COUNT - 1 because there are currently no settings assigned to 'saved settings'. - for (U32 i = 0; i < LLKeyConflictHandler::MODE_COUNT - 1; ++i) - { - mConflictHandler[i].setLoadMode((LLKeyConflictHandler::ESourceMode)i); - } -} - -LLPanelPreferenceControls::~LLPanelPreferenceControls() -{ -} - -bool LLPanelPreferenceControls::postBuild() -{ - // populate list of controls - pControlsTable = getChild("controls_list"); - pKeyModeBox = getChild("key_mode"); - - pControlsTable->setCommitCallback(boost::bind(&LLPanelPreferenceControls::onListCommit, this)); - pKeyModeBox->setCommitCallback(boost::bind(&LLPanelPreferenceControls::onModeCommit, this)); - getChild("restore_defaults")->setCommitCallback(boost::bind(&LLPanelPreferenceControls::onRestoreDefaultsBtn, this)); - - return true; -} - -void LLPanelPreferenceControls::regenerateControls() -{ - mEditingMode = pKeyModeBox->getValue().asInteger(); - mConflictHandler[mEditingMode].loadFromSettings((LLKeyConflictHandler::ESourceMode)mEditingMode); - populateControlTable(); -} - -bool LLPanelPreferenceControls::addControlTableColumns(const std::string &filename) -{ - LLXMLNodePtr xmlNode; - LLScrollListCtrl::Contents contents; - if (!LLUICtrlFactory::getLayeredXMLNode(filename, xmlNode)) - { - LL_WARNS("Preferences") << "Failed to load " << filename << LL_ENDL; - return false; - } - LLXUIParser parser; - parser.readXUI(xmlNode, contents, filename); - - if (!contents.validateBlock()) - { - return false; - } - - for (LLInitParam::ParamIterator::const_iterator col_it = contents.columns.begin(); - col_it != contents.columns.end(); - ++col_it) - { - pControlsTable->addColumn(*col_it); - } - - return true; -} - -bool LLPanelPreferenceControls::addControlTableRows(const std::string &filename) -{ - LLXMLNodePtr xmlNode; - LLScrollListCtrl::Contents contents; - if (!LLUICtrlFactory::getLayeredXMLNode(filename, xmlNode)) - { - LL_WARNS("Preferences") << "Failed to load " << filename << LL_ENDL; - return false; - } - LLXUIParser parser; - parser.readXUI(xmlNode, contents, filename); - - if (!contents.validateBlock()) - { - return false; - } - - LLScrollListCell::Params cell_params; - // init basic cell params - cell_params.font = LLFontGL::getFontSansSerif(); - cell_params.font_halign = LLFontGL::LEFT; - cell_params.column = ""; - cell_params.value = ""; - - - for (LLInitParam::ParamIterator::const_iterator row_it = contents.rows.begin(); - row_it != contents.rows.end(); - ++row_it) - { - std::string control = row_it->value.getValue().asString(); - if (!control.empty() && control != "menu_separator") - { - bool show = true; - bool enabled = mConflictHandler[mEditingMode].canAssignControl(control); - if (!enabled) - { - // If empty: this is a placeholder to make sure user won't assign - // value by accident, don't show it - // If not empty: predefined control combination user should see - // to know that combination is reserved - show = !mConflictHandler[mEditingMode].isControlEmpty(control); - // example: teleport_to and walk_to in first person view, and - // sitting related functions, see generatePlaceholders() - } - - if (show) - { - // At the moment viewer is hardcoded to assume that columns are named as lst_ctrl%d - LLScrollListItem::Params item_params(*row_it); - item_params.enabled.setValue(enabled); - - S32 num_columns = pControlsTable->getNumColumns(); - for (S32 col = 1; col < num_columns; col++) - { - cell_params.column = llformat("lst_ctrl%d", col); - cell_params.value = mConflictHandler[mEditingMode].getControlString(control, col - 1); - item_params.columns.add(cell_params); - } - pControlsTable->addRow(item_params, EAddPosition::ADD_BOTTOM); - } - } - else - { - // Separator example: - // - // - // - pControlsTable->addRow(*row_it, EAddPosition::ADD_BOTTOM); - } - } - return true; -} - -void LLPanelPreferenceControls::addControlTableSeparator() -{ - LLScrollListItem::Params separator_params; - separator_params.enabled(false); - LLScrollListCell::Params column_params; - column_params.type = "icon"; - column_params.value = "menu_separator"; - column_params.column = "lst_action"; - column_params.color = LLColor4(0.f, 0.f, 0.f, 0.7f); - column_params.font_halign = LLFontGL::HCENTER; - separator_params.columns.add(column_params); - pControlsTable->addRow(separator_params, EAddPosition::ADD_BOTTOM); -} - -void LLPanelPreferenceControls::populateControlTable() -{ - pControlsTable->clearRows(); - pControlsTable->clearColumns(); - - // Add columns - std::string filename; - switch ((LLKeyConflictHandler::ESourceMode)mEditingMode) - { - case LLKeyConflictHandler::MODE_THIRD_PERSON: - case LLKeyConflictHandler::MODE_FIRST_PERSON: - case LLKeyConflictHandler::MODE_EDIT_AVATAR: - case LLKeyConflictHandler::MODE_SITTING: - filename = "control_table_contents_columns_basic.xml"; - break; - default: - { - // Either unknown mode or MODE_SAVED_SETTINGS - // It doesn't have UI or actual settings yet - LL_WARNS("Preferences") << "Unimplemented mode" << LL_ENDL; - - // Searchable columns were removed, mark searchables for an update - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); - if (instance) - { - instance->updateSearchableItems(); - } - return; - } - } - addControlTableColumns(filename); - - // Add rows. - // Each file represents individual visual group (movement/camera/media...) - if (mEditingMode == LLKeyConflictHandler::MODE_FIRST_PERSON) - { - // Don't display whole camera and editing groups - addControlTableRows("control_table_contents_movement.xml"); - addControlTableSeparator(); - addControlTableRows("control_table_contents_media.xml"); - } - // MODE_THIRD_PERSON; MODE_EDIT_AVATAR; MODE_SITTING - else if (mEditingMode < LLKeyConflictHandler::MODE_SAVED_SETTINGS) - { - // In case of 'sitting' mode, movements still apply due to vehicles - // but walk_to is not supported and will be hidden by addControlTableRows - addControlTableRows("control_table_contents_movement.xml"); - addControlTableSeparator(); - - addControlTableRows("control_table_contents_camera.xml"); - addControlTableSeparator(); - - addControlTableRows("control_table_contents_editing.xml"); - addControlTableSeparator(); - - addControlTableRows("control_table_contents_media.xml"); - } - else - { - LL_WARNS("Preferences") << "Unimplemented mode" << LL_ENDL; - } - - // explicit update to make sure table is ready for llsearchableui - pControlsTable->updateColumns(); - - // Searchable columns were removed and readded, mark searchables for an update - // Note: at the moment tables/lists lack proper llsearchableui support - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); - if (instance) - { - instance->updateSearchableItems(); - } -} - -void LLPanelPreferenceControls::updateTable() -{ - mEditingControl.clear(); - std::vector list = pControlsTable->getAllData(); - for (S32 i = 0; i < list.size(); ++i) - { - std::string control = list[i]->getValue(); - if (!control.empty()) - { - LLScrollListCell* cell = NULL; - - S32 num_columns = pControlsTable->getNumColumns(); - for (S32 col = 1; col < num_columns; col++) - { - cell = list[i]->getColumn(col); - cell->setValue(mConflictHandler[mEditingMode].getControlString(control, col - 1)); - } - } - } - pControlsTable->deselectAllItems(); -} - -void LLPanelPreferenceControls::apply() -{ - for (U32 i = 0; i < LLKeyConflictHandler::MODE_COUNT - 1; ++i) - { - if (mConflictHandler[i].hasUnsavedChanges()) - { - mConflictHandler[i].saveToSettings(); - } - } -} - -void LLPanelPreferenceControls::cancel(const std::vector settings_to_skip) -{ - for (U32 i = 0; i < LLKeyConflictHandler::MODE_COUNT - 1; ++i) - { - if (mConflictHandler[i].hasUnsavedChanges()) - { - mConflictHandler[i].clear(); - if (mEditingMode == i) - { - // cancel() can be called either when preferences floater closes - // or when child floater closes (like advanced graphical settings) - // in which case we need to clear and repopulate table - regenerateControls(); - } - } - } -} - -void LLPanelPreferenceControls::saveSettings() -{ - for (U32 i = 0; i < LLKeyConflictHandler::MODE_COUNT - 1; ++i) - { - if (mConflictHandler[i].hasUnsavedChanges()) - { - mConflictHandler[i].saveToSettings(); - mConflictHandler[i].clear(); - } - } - - S32 mode = pKeyModeBox->getValue().asInteger(); - if (mConflictHandler[mode].empty() || pControlsTable->isEmpty()) - { - regenerateControls(); - } -} - -void LLPanelPreferenceControls::resetDirtyChilds() -{ - regenerateControls(); -} - -void LLPanelPreferenceControls::onListCommit() -{ - LLScrollListItem* item = pControlsTable->getFirstSelected(); - if (item == NULL) - { - return; - } - - std::string control = item->getValue(); - - if (control.empty()) - { - pControlsTable->deselectAllItems(); - return; - } - - if (!mConflictHandler[mEditingMode].canAssignControl(control)) - { - pControlsTable->deselectAllItems(); - return; - } - - S32 cell_ind = item->getSelectedCell(); - if (cell_ind <= 0) - { - pControlsTable->deselectAllItems(); - return; - } - - // List does not tell us what cell was clicked, so we have to figure it out manually, but - // fresh mouse coordinates are not yet accessible during onCommit() and there are other issues, - // so we cheat: remember item user clicked at, trigger 'key dialog' on hover that comes next, - // use coordinates from hover to calculate cell - - LLScrollListCell* cell = item->getColumn(cell_ind); - if (cell) - { - LLSetKeyBindDialog* dialog = LLFloaterReg::getTypedInstance("keybind_dialog", LLSD()); - if (dialog) - { - mEditingControl = control; - mEditingColumn = cell_ind; - dialog->setParent(this, pControlsTable, DEFAULT_KEY_FILTER); - - LLFloater* root_floater = gFloaterView->getParentFloater(this); - if (root_floater) - root_floater->addDependentFloater(dialog); - dialog->openFloater(); - dialog->setFocus(true); - } - } - else - { - pControlsTable->deselectAllItems(); - } -} - -void LLPanelPreferenceControls::onModeCommit() -{ - mEditingMode = pKeyModeBox->getValue().asInteger(); - if (mConflictHandler[mEditingMode].empty()) - { - // opening for first time - mConflictHandler[mEditingMode].loadFromSettings((LLKeyConflictHandler::ESourceMode)mEditingMode); - } - populateControlTable(); -} - -void LLPanelPreferenceControls::onRestoreDefaultsBtn() -{ - LLNotificationsUtil::add("PreferenceControlsDefaults", LLSD(), LLSD(), boost::bind(&LLPanelPreferenceControls::onRestoreDefaultsResponse, this, _1, _2)); -} - -void LLPanelPreferenceControls::onRestoreDefaultsResponse(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - switch(option) - { - case 0: // All - for (U32 i = 0; i < LLKeyConflictHandler::MODE_COUNT - 1; ++i) - { - mConflictHandler[i].resetToDefaults(); - // Apply changes to viewer as 'temporary' - mConflictHandler[i].saveToSettings(true); - - // notify comboboxes in move&view about potential change - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); - if (instance) - { - instance->updateClickActionViews(); - } - } - - updateTable(); - break; - case 1: // Current - mConflictHandler[mEditingMode].resetToDefaults(); - // Apply changes to viewer as 'temporary' - mConflictHandler[mEditingMode].saveToSettings(true); - - if (mEditingMode == LLKeyConflictHandler::MODE_THIRD_PERSON) - { - // notify comboboxes in move&view about potential change - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); - if (instance) - { - instance->updateClickActionViews(); - } - } - - updateTable(); - break; - case 2: // Cancel - default: - //exit; - break; - } -} - -// Bypass to let Move & view read values without need to create own key binding handler -// Assumes third person view -// Might be better idea to just move whole mConflictHandler into LLFloaterPreference -bool LLPanelPreferenceControls::canKeyBindHandle(const std::string &control, EMouseClickType click, KEY key, MASK mask) -{ - S32 mode = LLKeyConflictHandler::MODE_THIRD_PERSON; - if (mConflictHandler[mode].empty()) - { - // opening for first time - mConflictHandler[mode].loadFromSettings(LLKeyConflictHandler::MODE_THIRD_PERSON); - } - - return mConflictHandler[mode].canHandleControl(control, click, key, mask); -} - -// Bypass to let Move & view modify values without need to create own key binding handler -// Assumes third person view -// Might be better idea to just move whole mConflictHandler into LLFloaterPreference -void LLPanelPreferenceControls::setKeyBind(const std::string &control, EMouseClickType click, KEY key, MASK mask, bool set) -{ - S32 mode = LLKeyConflictHandler::MODE_THIRD_PERSON; - if (mConflictHandler[mode].empty()) - { - // opening for first time - mConflictHandler[mode].loadFromSettings(LLKeyConflictHandler::MODE_THIRD_PERSON); - } - - if (!mConflictHandler[mode].canAssignControl(mEditingControl)) - { - return; - } - - bool already_recorded = mConflictHandler[mode].canHandleControl(control, click, key, mask); - if (set) - { - if (already_recorded) - { - // nothing to do - return; - } - - // find free spot to add data, if no free spot, assign to first - S32 index = 0; - for (S32 i = 0; i < 3; i++) - { - if (mConflictHandler[mode].getControl(control, i).isEmpty()) - { - index = i; - break; - } - } - // At the moment 'ignore_mask' mask is mostly ignored, a placeholder - // Todo: implement it since it's preferable for things like teleport to match - // mask exactly but for things like running to ignore additional masks - // Ideally this needs representation in keybindings UI - bool ignore_mask = true; - mConflictHandler[mode].registerControl(control, index, click, key, mask, ignore_mask); - } - else if (!set) - { - if (!already_recorded) - { - // nothing to do - return; - } - - // find specific control and reset it - for (S32 i = 0; i < 3; i++) - { - LLKeyData data = mConflictHandler[mode].getControl(control, i); - if (data.mMouse == click && data.mKey == key && data.mMask == mask) - { - mConflictHandler[mode].clearControl(control, i); - } - } - } -} - -void LLPanelPreferenceControls::updateAndApply() -{ - S32 mode = LLKeyConflictHandler::MODE_THIRD_PERSON; - mConflictHandler[mode].saveToSettings(true); - updateTable(); -} - -// from LLSetKeybindDialog's interface -bool LLPanelPreferenceControls::onSetKeyBind(EMouseClickType click, KEY key, MASK mask, bool all_modes) -{ - if (!mConflictHandler[mEditingMode].canAssignControl(mEditingControl)) - { - return true; - } - - if ( mEditingColumn > 0) - { - if (all_modes) - { - for (U32 i = 0; i < LLKeyConflictHandler::MODE_COUNT - 1; ++i) - { - if (mConflictHandler[i].empty()) - { - mConflictHandler[i].loadFromSettings((LLKeyConflictHandler::ESourceMode)i); - } - mConflictHandler[i].registerControl(mEditingControl, mEditingColumn - 1, click, key, mask, true); - // Apply changes to viewer as 'temporary' - mConflictHandler[i].saveToSettings(true); - } - } - else - { - mConflictHandler[mEditingMode].registerControl(mEditingControl, mEditingColumn - 1, click, key, mask, true); - // Apply changes to viewer as 'temporary' - mConflictHandler[mEditingMode].saveToSettings(true); - } - } - - updateTable(); - - if ((mEditingMode == LLKeyConflictHandler::MODE_THIRD_PERSON || all_modes) - && (mEditingControl == "walk_to" - || mEditingControl == "teleport_to" - || click == CLICK_LEFT - || click == CLICK_DOUBLELEFT)) - { - // notify comboboxes in move&view about potential change - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); - if (instance) - { - instance->updateClickActionViews(); - } - } - - return true; -} - -void LLPanelPreferenceControls::onDefaultKeyBind(bool all_modes) -{ - if (!mConflictHandler[mEditingMode].canAssignControl(mEditingControl)) - { - return; - } - - if (mEditingColumn > 0) - { - if (all_modes) - { - for (U32 i = 0; i < LLKeyConflictHandler::MODE_COUNT - 1; ++i) - { - if (mConflictHandler[i].empty()) - { - mConflictHandler[i].loadFromSettings((LLKeyConflictHandler::ESourceMode)i); - } - mConflictHandler[i].resetToDefault(mEditingControl, mEditingColumn - 1); - // Apply changes to viewer as 'temporary' - mConflictHandler[i].saveToSettings(true); - } - } - else - { - mConflictHandler[mEditingMode].resetToDefault(mEditingControl, mEditingColumn - 1); - // Apply changes to viewer as 'temporary' - mConflictHandler[mEditingMode].saveToSettings(true); - } - } - updateTable(); - - if (mEditingMode == LLKeyConflictHandler::MODE_THIRD_PERSON || all_modes) - { - // notify comboboxes in move&view about potential change - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); - if (instance) - { - instance->updateClickActionViews(); - } - } -} - -void LLPanelPreferenceControls::onCancelKeyBind() -{ - pControlsTable->deselectAllItems(); -} - -LLFloaterPreferenceProxy::LLFloaterPreferenceProxy(const LLSD& key) - : LLFloater(key), - mSocksSettingsDirty(false) -{ - mCommitCallbackRegistrar.add("Proxy.OK", boost::bind(&LLFloaterPreferenceProxy::onBtnOk, this)); - mCommitCallbackRegistrar.add("Proxy.Cancel", boost::bind(&LLFloaterPreferenceProxy::onBtnCancel, this)); - mCommitCallbackRegistrar.add("Proxy.Change", boost::bind(&LLFloaterPreferenceProxy::onChangeSocksSettings, this)); -} - -LLFloaterPreferenceProxy::~LLFloaterPreferenceProxy() -{ -} - -bool LLFloaterPreferenceProxy::postBuild() -{ - LLRadioGroup* socksAuth = getChild("socks5_auth_type"); - if (!socksAuth) - { - return false; - } - if (socksAuth->getSelectedValue().asString() == "None") - { - getChild("socks5_username")->setEnabled(false); - getChild("socks5_password")->setEnabled(false); - } - else - { - // Populate the SOCKS 5 credential fields with protected values. - LLPointer socks_cred = gSecAPIHandler->loadCredential("SOCKS5"); - getChild("socks5_username")->setValue(socks_cred->getIdentifier()["username"].asString()); - getChild("socks5_password")->setValue(socks_cred->getAuthenticator()["creds"].asString()); - } - - return true; -} - -void LLFloaterPreferenceProxy::onOpen(const LLSD& key) -{ - saveSettings(); -} - -void LLFloaterPreferenceProxy::onClose(bool app_quitting) -{ - if(app_quitting) - { - cancel(); - } - - if (mSocksSettingsDirty) - { - - // If the user plays with the Socks proxy settings after login, it's only fair we let them know - // it will not be updated until next restart. - if (LLStartUp::getStartupState()>STATE_LOGIN_WAIT) - { - LLNotifications::instance().add("ChangeProxySettings", LLSD(), LLSD()); - mSocksSettingsDirty = false; // we have notified the user now be quiet again - } - } -} - -void LLFloaterPreferenceProxy::saveSettings() -{ - // Save the value of all controls in the hierarchy - mSavedValues.clear(); - std::list view_stack; - view_stack.push_back(this); - while(!view_stack.empty()) - { - // Process view on top of the stack - LLView* curview = view_stack.front(); - view_stack.pop_front(); - - LLUICtrl* ctrl = dynamic_cast(curview); - if (ctrl) - { - LLControlVariable* control = ctrl->getControlVariable(); - if (control) - { - mSavedValues[control] = control->getValue(); - } - } - - // Push children onto the end of the work stack - for (child_list_t::const_iterator iter = curview->getChildList()->begin(); - iter != curview->getChildList()->end(); ++iter) - { - view_stack.push_back(*iter); - } - } -} - -void LLFloaterPreferenceProxy::onBtnOk() -{ - // commit any outstanding text entry - if (hasFocus()) - { - LLUICtrl* cur_focus = dynamic_cast(gFocusMgr.getKeyboardFocus()); - if (cur_focus && cur_focus->acceptsTextInput()) - { - cur_focus->onCommit(); - } - } - - // Save SOCKS proxy credentials securely if password auth is enabled - LLRadioGroup* socksAuth = getChild("socks5_auth_type"); - if (socksAuth->getSelectedValue().asString() == "UserPass") - { - LLSD socks_id = LLSD::emptyMap(); - socks_id["type"] = "SOCKS5"; - socks_id["username"] = getChild("socks5_username")->getValue().asString(); - - LLSD socks_authenticator = LLSD::emptyMap(); - socks_authenticator["type"] = "SOCKS5"; - socks_authenticator["creds"] = getChild("socks5_password")->getValue().asString(); - - // Using "SOCKS5" as the "grid" argument since the same proxy - // settings will be used for all grids and because there is no - // way to specify the type of credential. - LLPointer socks_cred = gSecAPIHandler->createCredential("SOCKS5", socks_id, socks_authenticator); - gSecAPIHandler->saveCredential(socks_cred, true); - } - else - { - // Clear SOCKS5 credentials since they are no longer needed. - LLPointer socks_cred = new LLCredential("SOCKS5"); - gSecAPIHandler->deleteCredential(socks_cred); - } - - closeFloater(false); -} - -void LLFloaterPreferenceProxy::onBtnCancel() -{ - if (hasFocus()) - { - LLUICtrl* cur_focus = dynamic_cast(gFocusMgr.getKeyboardFocus()); - if (cur_focus && cur_focus->acceptsTextInput()) - { - cur_focus->onCommit(); - } - refresh(); - } - - cancel(); -} - -void LLFloaterPreferenceProxy::onClickCloseBtn(bool app_quitting) -{ - cancel(); -} - -void LLFloaterPreferenceProxy::cancel() -{ - - for (control_values_map_t::iterator iter = mSavedValues.begin(); - iter != mSavedValues.end(); ++iter) - { - LLControlVariable* control = iter->first; - LLSD ctrl_value = iter->second; - control->set(ctrl_value); - } - mSocksSettingsDirty = false; - closeFloater(); -} - -void LLFloaterPreferenceProxy::onChangeSocksSettings() -{ - mSocksSettingsDirty = true; - - LLRadioGroup* socksAuth = getChild("socks5_auth_type"); - if (socksAuth->getSelectedValue().asString() == "None") - { - getChild("socks5_username")->setEnabled(false); - getChild("socks5_password")->setEnabled(false); - } - else - { - getChild("socks5_username")->setEnabled(true); - getChild("socks5_password")->setEnabled(true); - } - - // Check for invalid states for the other HTTP proxy radio - LLRadioGroup* otherHttpProxy = getChild("other_http_proxy_type"); - if ((otherHttpProxy->getSelectedValue().asString() == "Socks" && - !getChild("socks_proxy_enabled")->get())||( - otherHttpProxy->getSelectedValue().asString() == "Web" && - !getChild("web_proxy_enabled")->get())) - { - otherHttpProxy->selectFirstItem(); - } - -} - -void LLFloaterPreference::onUpdateFilterTerm(bool force) -{ - LLWString seachValue = utf8str_to_wstring(mFilterEdit->getValue()); - LLWStringUtil::toLower(seachValue); - - if (!mSearchData || (mSearchData->mLastFilter == seachValue && !force)) - return; - - if (mSearchDataDirty) - { - // Data exists, but is obsolete, regenerate - collectSearchableItems(); - } - - mSearchData->mLastFilter = seachValue; - - if (!mSearchData->mRootTab) - return; - - mSearchData->mRootTab->hightlightAndHide( seachValue ); - filterIgnorableNotifications(); - - if (LLTabContainer* pRoot = getChild("pref core")) - pRoot->selectFirstTab(); -} - -void LLFloaterPreference::filterIgnorableNotifications() -{ - bool visible = getChildRef("enabled_popups").highlightMatchingItems(mFilterEdit->getValue()); - visible |= getChildRef("disabled_popups").highlightMatchingItems(mFilterEdit->getValue()); - - if (visible) - { - getChildRef("pref core").setTabVisibility( getChild("msgs"), true ); - } -} - -void collectChildren( LLView const *aView, ll::prefs::PanelDataPtr aParentPanel, ll::prefs::TabContainerDataPtr aParentTabContainer ) -{ - if (!aView) - return; - - llassert_always(aParentPanel || aParentTabContainer); - - for (LLView* pView : *aView->getChildList()) - { - if (!pView) - continue; - - ll::prefs::PanelDataPtr pCurPanelData = aParentPanel; - ll::prefs::TabContainerDataPtr pCurTabContainer = aParentTabContainer; - - LLPanel const *pPanel = dynamic_cast(pView); - LLTabContainer const *pTabContainer = dynamic_cast(pView); - ll::ui::SearchableControl const *pSCtrl = dynamic_cast( pView ); - - if (pTabContainer) - { - pCurPanelData.reset(); - - pCurTabContainer = ll::prefs::TabContainerDataPtr(new ll::prefs::TabContainerData); - pCurTabContainer->mTabContainer = const_cast< LLTabContainer *>(pTabContainer); - pCurTabContainer->mLabel = pTabContainer->getLabel(); - pCurTabContainer->mPanel = 0; - - if (aParentPanel) - aParentPanel->mChildPanel.push_back(pCurTabContainer); - if (aParentTabContainer) - aParentTabContainer->mChildPanel.push_back(pCurTabContainer); - } - else if (pPanel) - { - pCurTabContainer.reset(); - - pCurPanelData = ll::prefs::PanelDataPtr(new ll::prefs::PanelData); - pCurPanelData->mPanel = pPanel; - pCurPanelData->mLabel = pPanel->getLabel(); - - llassert_always( aParentPanel || aParentTabContainer ); - - if (aParentTabContainer) - aParentTabContainer->mChildPanel.push_back(pCurPanelData); - else if (aParentPanel) - aParentPanel->mChildPanel.push_back(pCurPanelData); - } - else if (pSCtrl && pSCtrl->getSearchText().size()) - { - ll::prefs::SearchableItemPtr item = ll::prefs::SearchableItemPtr(new ll::prefs::SearchableItem()); - item->mView = pView; - item->mCtrl = pSCtrl; - - item->mLabel = utf8str_to_wstring(pSCtrl->getSearchText()); - LLWStringUtil::toLower(item->mLabel); - - llassert_always(aParentPanel || aParentTabContainer); - - if (aParentPanel) - aParentPanel->mChildren.push_back(item); - if (aParentTabContainer) - aParentTabContainer->mChildren.push_back(item); - } - collectChildren(pView, pCurPanelData, pCurTabContainer); - } -} - -void LLFloaterPreference::collectSearchableItems() -{ - mSearchData.reset( nullptr ); - LLTabContainer *pRoot = getChild< LLTabContainer >( "pref core" ); - if( mFilterEdit && pRoot ) - { - mSearchData.reset(new ll::prefs::SearchData() ); - - ll::prefs::TabContainerDataPtr pRootTabcontainer = ll::prefs::TabContainerDataPtr( new ll::prefs::TabContainerData ); - pRootTabcontainer->mTabContainer = pRoot; - pRootTabcontainer->mLabel = pRoot->getLabel(); - mSearchData->mRootTab = pRootTabcontainer; - - collectChildren( this, ll::prefs::PanelDataPtr(), pRootTabcontainer ); - } - mSearchDataDirty = false; -} - -void LLFloaterPreference::saveIgnoredNotifications() -{ - for (LLNotifications::TemplateMap::const_iterator iter = LLNotifications::instance().templatesBegin(); - iter != LLNotifications::instance().templatesEnd(); - ++iter) - { - LLNotificationTemplatePtr templatep = iter->second; - LLNotificationFormPtr formp = templatep->mForm; - - LLNotificationForm::EIgnoreType ignore = formp->getIgnoreType(); - if (ignore <= LLNotificationForm::IGNORE_NO) - continue; - - mIgnorableNotifs[templatep->mName] = !formp->getIgnored(); - } -} - -void LLFloaterPreference::restoreIgnoredNotifications() -{ - for (std::map::iterator it = mIgnorableNotifs.begin(); it != mIgnorableNotifs.end(); ++it) - { - LLUI::getInstance()->mSettingGroups["ignores"]->setBOOL(it->first, it->second); - } -} +/** + * @file llfloaterpreference.cpp + * @brief Global preferences with and without persistence. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/* + * App-wide preferences. Note that these are not per-user, + * because we need to load many preferences before we have + * a login name. + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterpreference.h" + +#include "message.h" +#include "llfloaterautoreplacesettings.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llcheckboxctrl.h" +#include "llcolorswatch.h" +#include "llcombobox.h" +#include "llcommandhandler.h" +#include "lldirpicker.h" +#include "lleventtimer.h" +#include "llfeaturemanager.h" +#include "llfocusmgr.h" +//#include "llfirstuse.h" +#include "llfloaterreg.h" +#include "llfloaterabout.h" +#include "llfavoritesbar.h" +#include "llfloaterpreferencesgraphicsadvanced.h" +#include "llfloaterperformance.h" +#include "llfloatersidepanelcontainer.h" +#include "llfloaterimsession.h" +#include "llkeyboard.h" +#include "llmodaldialog.h" +#include "llnavigationbar.h" +#include "llfloaterimnearbychat.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "llnotificationtemplate.h" +#include "llpanellogin.h" +#include "llpanelvoicedevicesettings.h" +#include "llradiogroup.h" +#include "llsearchcombobox.h" +#include "llsky.h" +#include "llscrolllistctrl.h" +#include "llscrolllistitem.h" +#include "llsliderctrl.h" +#include "lltabcontainer.h" +#include "lltrans.h" +#include "llviewercontrol.h" +#include "llviewercamera.h" +#include "llviewereventrecorder.h" +#include "llviewermessage.h" +#include "llviewerwindow.h" +#include "llviewerthrottle.h" +#include "llvoavatarself.h" +#include "llvotree.h" +#include "llfloaterpathfindingconsole.h" +// linden library includes +#include "llavatarnamecache.h" +#include "llerror.h" +#include "llfontgl.h" +#include "llrect.h" +#include "llstring.h" + +// project includes + +#include "llbutton.h" +#include "llflexibleobject.h" +#include "lllineeditor.h" +#include "llresmgr.h" +#include "llspinctrl.h" +#include "llstartup.h" +#include "lltextbox.h" +#include "llui.h" +#include "llviewerobjectlist.h" +#include "llvovolume.h" +#include "llwindow.h" +#include "llworld.h" +#include "lluictrlfactory.h" +#include "llviewermedia.h" +#include "llpluginclassmedia.h" +#include "llteleporthistorystorage.h" +#include "llproxy.h" +#include "llweb.h" + +#include "lllogininstance.h" // to check if logged in yet +#include "llsdserialize.h" +#include "llpresetsmanager.h" +#include "llviewercontrol.h" +#include "llpresetsmanager.h" +#include "llinventoryfunctions.h" + +#include "llsearchableui.h" +#include "llperfstats.h" + +const F32 BANDWIDTH_UPDATER_TIMEOUT = 0.5f; +char const* const VISIBILITY_DEFAULT = "default"; +char const* const VISIBILITY_HIDDEN = "hidden"; + +//control value for middle mouse as talk2push button +const static std::string MIDDLE_MOUSE_CV = "MiddleMouse"; // for voice client and redability +const static std::string MOUSE_BUTTON_4_CV = "MouseButton4"; +const static std::string MOUSE_BUTTON_5_CV = "MouseButton5"; + +/// This must equal the maximum value set for the IndirectMaxComplexity slider in panel_preferences_graphics1.xml +static const U32 INDIRECT_MAX_ARC_OFF = 101; // all the way to the right == disabled +static const U32 MIN_INDIRECT_ARC_LIMIT = 1; // must match minimum of IndirectMaxComplexity in panel_preferences_graphics1.xml +static const U32 MAX_INDIRECT_ARC_LIMIT = INDIRECT_MAX_ARC_OFF-1; // one short of all the way to the right... + +/// These are the effective range of values for RenderAvatarMaxComplexity +static const F32 MIN_ARC_LIMIT = 20000.0f; +static const F32 MAX_ARC_LIMIT = 350000.0f; +static const F32 MIN_ARC_LOG = log(MIN_ARC_LIMIT); +static const F32 MAX_ARC_LOG = log(MAX_ARC_LIMIT); +static const F32 ARC_LIMIT_MAP_SCALE = (MAX_ARC_LOG - MIN_ARC_LOG) / (MAX_INDIRECT_ARC_LIMIT - MIN_INDIRECT_ARC_LIMIT); + +struct LabelDef : public LLInitParam::Block +{ + Mandatory name; + Mandatory value; + + LabelDef() + : name("name"), + value("value") + {} +}; + +struct LabelTable : public LLInitParam::Block +{ + Multiple labels; + LabelTable() + : labels("label") + {} +}; + + +// global functions + +// helper functions for getting/freeing the web browser media +// if creating/destroying these is too slow, we'll need to create +// a static member and update all our static callbacks + +void handleNameTagOptionChanged(const LLSD& newvalue); +void handleDisplayNamesOptionChanged(const LLSD& newvalue); +bool callback_clear_browser_cache(const LLSD& notification, const LLSD& response); +bool callback_clear_cache(const LLSD& notification, const LLSD& response); + +//bool callback_skip_dialogs(const LLSD& notification, const LLSD& response, LLFloaterPreference* floater); +//bool callback_reset_dialogs(const LLSD& notification, const LLSD& response, LLFloaterPreference* floater); + +void fractionFromDecimal(F32 decimal_val, S32& numerator, S32& denominator); + +bool callback_clear_cache(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if ( option == 0 ) // YES + { + // flag client texture cache for clearing next time the client runs + gSavedSettings.setBOOL("PurgeCacheOnNextStartup", true); + LLNotificationsUtil::add("CacheWillClear"); + } + + return false; +} + +bool callback_clear_browser_cache(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if ( option == 0 ) // YES + { + // clean web + LLViewerMedia::getInstance()->clearAllCaches(); + LLViewerMedia::getInstance()->clearAllCookies(); + + // clean nav bar history + LLNavigationBar::getInstance()->clearHistoryCache(); + + // flag client texture cache for clearing next time the client runs + gSavedSettings.setBOOL("PurgeCacheOnNextStartup", true); + LLNotificationsUtil::add("CacheWillClear"); + + LLSearchHistory::getInstance()->clearHistory(); + LLSearchHistory::getInstance()->save(); + LLSearchComboBox* search_ctrl = LLNavigationBar::getInstance()->getChild("search_combo_box"); + search_ctrl->clearHistory(); + + LLTeleportHistoryStorage::getInstance()->purgeItems(); + LLTeleportHistoryStorage::getInstance()->save(); + } + + return false; +} + +void handleNameTagOptionChanged(const LLSD& newvalue) +{ + LLAvatarNameCache::getInstance()->setUseUsernames(gSavedSettings.getBOOL("NameTagShowUsernames")); + LLVOAvatar::invalidateNameTags(); +} + +void handleDisplayNamesOptionChanged(const LLSD& newvalue) +{ + LLAvatarNameCache::getInstance()->setUseDisplayNames(newvalue.asBoolean()); + LLVOAvatar::invalidateNameTags(); +} + +void handleAppearanceCameraMovementChanged(const LLSD& newvalue) +{ + if(!newvalue.asBoolean() && gAgentCamera.getCameraMode() == CAMERA_MODE_CUSTOMIZE_AVATAR) + { + gAgentCamera.changeCameraToDefault(); + gAgentCamera.resetView(); + } +} + +void fractionFromDecimal(F32 decimal_val, S32& numerator, S32& denominator) +{ + numerator = 0; + denominator = 0; + for (F32 test_denominator = 1.f; test_denominator < 30.f; test_denominator += 1.f) + { + if (fmodf((decimal_val * test_denominator) + 0.01f, 1.f) < 0.02f) + { + numerator = ll_round(decimal_val * test_denominator); + denominator = ll_round(test_denominator); + break; + } + } +} + +// handle secondlife:///app/worldmap/{NAME}/{COORDS} URLs +// Also see LLUrlEntryKeybinding, the value of this command type +// is ability to show up to date value in chat +class LLKeybindingHandler: public LLCommandHandler +{ +public: + // requires trusted browser to trigger + LLKeybindingHandler(): LLCommandHandler("keybinding", UNTRUSTED_CLICK_ONLY) + { + } + + bool handle(const LLSD& params, const LLSD& query_map, + const std::string& grid, LLMediaCtrl* web) + { + if (params.size() < 1) return false; + + LLFloaterPreference* prefsfloater = dynamic_cast + (LLFloaterReg::showInstance("preferences")); + + if (prefsfloater) + { + // find 'controls' panel and bring it the front + LLTabContainer* tabcontainer = prefsfloater->getChild("pref core"); + LLPanel* panel = prefsfloater->getChild("controls"); + if (tabcontainer && panel) + { + tabcontainer->selectTabPanel(panel); + } + } + + return true; + } +}; +LLKeybindingHandler gKeybindHandler; + + +////////////////////////////////////////////// +// LLFloaterPreference + +// static +std::string LLFloaterPreference::sSkin = ""; + +LLFloaterPreference::LLFloaterPreference(const LLSD& key) + : LLFloater(key), + mGotPersonalInfo(false), + mLanguageChanged(false), + mAvatarDataInitialized(false), + mSearchDataDirty(true) +{ + LLConversationLog::instance().addObserver(this); + + //Build Floater is now Called from LLFloaterReg::add("preferences", "floater_preferences.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + + static bool registered_dialog = false; + if (!registered_dialog) + { + LLFloaterReg::add("keybind_dialog", "floater_select_key.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + registered_dialog = true; + } + + mCommitCallbackRegistrar.add("Pref.Cancel", boost::bind(&LLFloaterPreference::onBtnCancel, this, _2)); + mCommitCallbackRegistrar.add("Pref.OK", boost::bind(&LLFloaterPreference::onBtnOK, this, _2)); + + mCommitCallbackRegistrar.add("Pref.ClearCache", boost::bind(&LLFloaterPreference::onClickClearCache, this)); + mCommitCallbackRegistrar.add("Pref.WebClearCache", boost::bind(&LLFloaterPreference::onClickBrowserClearCache, this)); + mCommitCallbackRegistrar.add("Pref.SetCache", boost::bind(&LLFloaterPreference::onClickSetCache, this)); + mCommitCallbackRegistrar.add("Pref.ResetCache", boost::bind(&LLFloaterPreference::onClickResetCache, this)); + mCommitCallbackRegistrar.add("Pref.ClickSkin", boost::bind(&LLFloaterPreference::onClickSkin, this,_1, _2)); + mCommitCallbackRegistrar.add("Pref.SelectSkin", boost::bind(&LLFloaterPreference::onSelectSkin, this)); + mCommitCallbackRegistrar.add("Pref.SetSounds", boost::bind(&LLFloaterPreference::onClickSetSounds, this)); + mCommitCallbackRegistrar.add("Pref.ClickEnablePopup", boost::bind(&LLFloaterPreference::onClickEnablePopup, this)); + mCommitCallbackRegistrar.add("Pref.ClickDisablePopup", boost::bind(&LLFloaterPreference::onClickDisablePopup, this)); + mCommitCallbackRegistrar.add("Pref.LogPath", boost::bind(&LLFloaterPreference::onClickLogPath, this)); + mCommitCallbackRegistrar.add("Pref.RenderExceptions", boost::bind(&LLFloaterPreference::onClickRenderExceptions, this)); + mCommitCallbackRegistrar.add("Pref.AutoAdjustments", boost::bind(&LLFloaterPreference::onClickAutoAdjustments, this)); + mCommitCallbackRegistrar.add("Pref.HardwareDefaults", boost::bind(&LLFloaterPreference::setHardwareDefaults, this)); + mCommitCallbackRegistrar.add("Pref.AvatarImpostorsEnable", boost::bind(&LLFloaterPreference::onAvatarImpostorsEnable, this)); + mCommitCallbackRegistrar.add("Pref.UpdateIndirectMaxComplexity", boost::bind(&LLFloaterPreference::updateMaxComplexity, this)); + mCommitCallbackRegistrar.add("Pref.RenderOptionUpdate", boost::bind(&LLFloaterPreference::onRenderOptionEnable, this)); + mCommitCallbackRegistrar.add("Pref.WindowedMod", boost::bind(&LLFloaterPreference::onCommitWindowedMode, this)); + mCommitCallbackRegistrar.add("Pref.UpdateSliderText", boost::bind(&LLFloaterPreference::refreshUI,this)); + mCommitCallbackRegistrar.add("Pref.QualityPerformance", boost::bind(&LLFloaterPreference::onChangeQuality, this, _2)); + mCommitCallbackRegistrar.add("Pref.applyUIColor", boost::bind(&LLFloaterPreference::applyUIColor, this ,_1, _2)); + mCommitCallbackRegistrar.add("Pref.getUIColor", boost::bind(&LLFloaterPreference::getUIColor, this ,_1, _2)); + mCommitCallbackRegistrar.add("Pref.MaturitySettings", boost::bind(&LLFloaterPreference::onChangeMaturity, this)); + mCommitCallbackRegistrar.add("Pref.BlockList", boost::bind(&LLFloaterPreference::onClickBlockList, this)); + mCommitCallbackRegistrar.add("Pref.Proxy", boost::bind(&LLFloaterPreference::onClickProxySettings, this)); + mCommitCallbackRegistrar.add("Pref.TranslationSettings", boost::bind(&LLFloaterPreference::onClickTranslationSettings, this)); + mCommitCallbackRegistrar.add("Pref.AutoReplace", boost::bind(&LLFloaterPreference::onClickAutoReplace, this)); + mCommitCallbackRegistrar.add("Pref.PermsDefault", boost::bind(&LLFloaterPreference::onClickPermsDefault, this)); + mCommitCallbackRegistrar.add("Pref.RememberedUsernames", boost::bind(&LLFloaterPreference::onClickRememberedUsernames, this)); + mCommitCallbackRegistrar.add("Pref.SpellChecker", boost::bind(&LLFloaterPreference::onClickSpellChecker, this)); + mCommitCallbackRegistrar.add("Pref.Advanced", boost::bind(&LLFloaterPreference::onClickAdvanced, this)); + + sSkin = gSavedSettings.getString("SkinCurrent"); + + mCommitCallbackRegistrar.add("Pref.ClickActionChange", boost::bind(&LLFloaterPreference::onClickActionChange, this)); + + gSavedSettings.getControl("NameTagShowUsernames")->getCommitSignal()->connect(boost::bind(&handleNameTagOptionChanged, _2)); + gSavedSettings.getControl("NameTagShowFriends")->getCommitSignal()->connect(boost::bind(&handleNameTagOptionChanged, _2)); + gSavedSettings.getControl("UseDisplayNames")->getCommitSignal()->connect(boost::bind(&handleDisplayNamesOptionChanged, _2)); + + gSavedSettings.getControl("AppearanceCameraMovement")->getCommitSignal()->connect(boost::bind(&handleAppearanceCameraMovementChanged, _2)); + gSavedSettings.getControl("WindLightUseAtmosShaders")->getCommitSignal()->connect(boost::bind(&LLFloaterPreference::onAtmosShaderChange, this)); + + LLAvatarPropertiesProcessor::getInstance()->addObserver( gAgent.getID(), this ); + + mComplexityChangedSignal = gSavedSettings.getControl("RenderAvatarMaxComplexity")->getCommitSignal()->connect(boost::bind(&LLFloaterPreference::updateComplexityText, this)); + + mCommitCallbackRegistrar.add("Pref.ClearLog", boost::bind(&LLConversationLog::onClearLog, &LLConversationLog::instance())); + mCommitCallbackRegistrar.add("Pref.DeleteTranscripts", boost::bind(&LLFloaterPreference::onDeleteTranscripts, this)); + mCommitCallbackRegistrar.add("UpdateFilter", boost::bind(&LLFloaterPreference::onUpdateFilterTerm, this, false)); // Hook up for filtering +} + +void LLFloaterPreference::processProperties( void* pData, EAvatarProcessorType type ) +{ + if ( APT_PROPERTIES_LEGACY == type ) + { + const LLAvatarLegacyData* pAvatarData = static_cast( pData ); + if (pAvatarData && (gAgent.getID() == pAvatarData->avatar_id) && (pAvatarData->avatar_id != LLUUID::null)) + { + mAllowPublish = (bool)(pAvatarData->flags & AVATAR_ALLOW_PUBLISH); + mAvatarDataInitialized = true; + getChild("online_searchresults")->setValue(mAllowPublish); + } + } +} + +void LLFloaterPreference::saveAvatarProperties( void ) +{ + const bool allowPublish = getChild("online_searchresults")->getValue(); + + if ((LLStartUp::getStartupState() == STATE_STARTED) + && mAvatarDataInitialized + && (allowPublish != mAllowPublish)) + { + std::string cap_url = gAgent.getRegionCapability("AgentProfile"); + if (!cap_url.empty()) + { + mAllowPublish = allowPublish; + + LLCoros::instance().launch("requestAgentUserInfoCoro", + boost::bind(saveAvatarPropertiesCoro, cap_url, allowPublish)); + } + } +} + +void LLFloaterPreference::saveAvatarPropertiesCoro(const std::string cap_url, bool allow_publish) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("put_avatar_properties_coro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpHeaders::ptr_t httpHeaders; + + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + httpOpts->setFollowRedirects(true); + + std::string finalUrl = cap_url + "/" + gAgentID.asString(); + LLSD data; + data["allow_publish"] = allow_publish; + + LLSD result = httpAdapter->putAndSuspend(httpRequest, finalUrl, data, httpOpts, httpHeaders); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS("Preferences") << "Failed to put agent information " << data << " for id " << gAgentID << LL_ENDL; + return; + } + + LL_DEBUGS("Preferences") << "Agent id: " << gAgentID << " Data: " << data << " Result: " << httpResults << LL_ENDL; +} + +bool LLFloaterPreference::postBuild() +{ + gSavedSettings.getControl("ChatFontSize")->getSignal()->connect(boost::bind(&LLFloaterIMSessionTab::processChatHistoryStyleUpdate, false)); + + gSavedSettings.getControl("ChatFontSize")->getSignal()->connect(boost::bind(&LLViewerChat::signalChatFontChanged)); + + gSavedSettings.getControl("ChatBubbleOpacity")->getSignal()->connect(boost::bind(&LLFloaterPreference::onNameTagOpacityChange, this, _2)); + + gSavedSettings.getControl("PreferredMaturity")->getSignal()->connect(boost::bind(&LLFloaterPreference::onChangeMaturity, this)); + + gSavedSettings.getControl("RenderAvatarComplexityMode")->getSignal()->connect( + [this](LLControlVariable* control, const LLSD& new_val, const LLSD& old_val) + { + onChangeComplexityMode(new_val); + }); + + gSavedPerAccountSettings.getControl("ModelUploadFolder")->getSignal()->connect(boost::bind(&LLFloaterPreference::onChangeModelFolder, this)); + gSavedPerAccountSettings.getControl("PBRUploadFolder")->getSignal()->connect(boost::bind(&LLFloaterPreference::onChangePBRFolder, this)); + gSavedPerAccountSettings.getControl("TextureUploadFolder")->getSignal()->connect(boost::bind(&LLFloaterPreference::onChangeTextureFolder, this)); + gSavedPerAccountSettings.getControl("SoundUploadFolder")->getSignal()->connect(boost::bind(&LLFloaterPreference::onChangeSoundFolder, this)); + gSavedPerAccountSettings.getControl("AnimationUploadFolder")->getSignal()->connect(boost::bind(&LLFloaterPreference::onChangeAnimationFolder, this)); + + LLTabContainer* tabcontainer = getChild("pref core"); + if (!tabcontainer->selectTab(gSavedSettings.getS32("LastPrefTab"))) + tabcontainer->selectFirstTab(); + + getChild("cache_location")->setEnabled(false); // make it read-only but selectable (STORM-227) + std::string cache_location = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ""); + setCacheLocation(cache_location); + + getChild("log_path_string")->setEnabled(false); // make it read-only but selectable + + getChild("language_combobox")->setCommitCallback(boost::bind(&LLFloaterPreference::onLanguageChange, this)); + + getChild("FriendIMOptions")->setCommitCallback(boost::bind(&LLFloaterPreference::onNotificationsChange, this,"FriendIMOptions")); + getChild("NonFriendIMOptions")->setCommitCallback(boost::bind(&LLFloaterPreference::onNotificationsChange, this,"NonFriendIMOptions")); + getChild("ConferenceIMOptions")->setCommitCallback(boost::bind(&LLFloaterPreference::onNotificationsChange, this,"ConferenceIMOptions")); + getChild("GroupChatOptions")->setCommitCallback(boost::bind(&LLFloaterPreference::onNotificationsChange, this,"GroupChatOptions")); + getChild("NearbyChatOptions")->setCommitCallback(boost::bind(&LLFloaterPreference::onNotificationsChange, this,"NearbyChatOptions")); + getChild("ObjectIMOptions")->setCommitCallback(boost::bind(&LLFloaterPreference::onNotificationsChange, this,"ObjectIMOptions")); + + // if floater is opened before login set default localized do not disturb message + if (LLStartUp::getStartupState() < STATE_STARTED) + { + gSavedPerAccountSettings.setString("DoNotDisturbModeResponse", LLTrans::getString("DoNotDisturbModeResponseDefault")); + } + + // set 'enable' property for 'Clear log...' button + changed(); + + LLLogChat::getInstance()->setSaveHistorySignal(boost::bind(&LLFloaterPreference::onLogChatHistorySaved, this)); + + LLSliderCtrl* fov_slider = getChild("camera_fov"); + fov_slider->setMinValue(LLViewerCamera::getInstance()->getMinView()); + fov_slider->setMaxValue(LLViewerCamera::getInstance()->getMaxView()); + + bool enable_complexity = gSavedSettings.getS32("RenderAvatarComplexityMode") != LLVOAvatar::AV_RENDER_ONLY_SHOW_FRIENDS; + getChild("IndirectMaxComplexity")->setEnabled(enable_complexity); + + // Hook up and init for filtering + mFilterEdit = getChild("search_prefs_edit"); + mFilterEdit->setKeystrokeCallback(boost::bind(&LLFloaterPreference::onUpdateFilterTerm, this, false)); + + // Load and assign label for 'default language' + std::string user_filename = gDirUtilp->getExpandedFilename(LL_PATH_DEFAULT_SKIN, "default_languages.xml"); + std::map labels; + if (loadFromFilename(user_filename, labels)) + { + std::string system_lang = gSavedSettings.getString("SystemLanguage"); + std::map::iterator iter = labels.find(system_lang); + if (iter != labels.end()) + { + getChild("language_combobox")->add(iter->second, LLSD("default"), ADD_TOP, true); + } + else + { + LL_WARNS() << "Language \"" << system_lang << "\" is not in default_languages.xml" << LL_ENDL; + getChild("language_combobox")->add("System default", LLSD("default"), ADD_TOP, true); + } + } + else + { + LL_WARNS() << "Failed to load labels from " << user_filename << ". Using default." << LL_ENDL; + getChild("language_combobox")->add("System default", LLSD("default"), ADD_TOP, true); + } + + return true; +} + +void LLFloaterPreference::updateDeleteTranscriptsButton() +{ + getChild("delete_transcripts")->setEnabled(LLLogChat::transcriptFilesExist()); +} + +void LLFloaterPreference::onDoNotDisturbResponseChanged() +{ + // set "DoNotDisturbResponseChanged" true if user edited message differs from default, false otherwise + bool response_changed_flag = + LLTrans::getString("DoNotDisturbModeResponseDefault") + != getChild("do_not_disturb_response")->getValue().asString(); + + gSavedPerAccountSettings.setBOOL("DoNotDisturbResponseChanged", response_changed_flag ); +} + +LLFloaterPreference::~LLFloaterPreference() +{ + LLConversationLog::instance().removeObserver(this); + mComplexityChangedSignal.disconnect(); +} + +void LLFloaterPreference::draw() +{ + bool has_first_selected = (getChildRef("disabled_popups").getFirstSelected()!=NULL); + gSavedSettings.setBOOL("FirstSelectedDisabledPopups", has_first_selected); + + has_first_selected = (getChildRef("enabled_popups").getFirstSelected()!=NULL); + gSavedSettings.setBOOL("FirstSelectedEnabledPopups", has_first_selected); + + LLFloater::draw(); +} + +void LLFloaterPreference::saveSettings() +{ + LLTabContainer* tabcontainer = getChild("pref core"); + child_list_t::const_iterator iter = tabcontainer->getChildList()->begin(); + child_list_t::const_iterator end = tabcontainer->getChildList()->end(); + for ( ; iter != end; ++iter) + { + LLView* view = *iter; + LLPanelPreference* panel = dynamic_cast(view); + if (panel) + panel->saveSettings(); + } + saveIgnoredNotifications(); +} + +void LLFloaterPreference::apply() +{ + LLAvatarPropertiesProcessor::getInstance()->addObserver( gAgent.getID(), this ); + + LLTabContainer* tabcontainer = getChild("pref core"); + if (sSkin != gSavedSettings.getString("SkinCurrent")) + { + LLNotificationsUtil::add("ChangeSkin"); + refreshSkin(this); + } + // Call apply() on all panels that derive from LLPanelPreference + for (child_list_t::const_iterator iter = tabcontainer->getChildList()->begin(); + iter != tabcontainer->getChildList()->end(); ++iter) + { + LLView* view = *iter; + LLPanelPreference* panel = dynamic_cast(view); + if (panel) + panel->apply(); + } + + gViewerWindow->requestResolutionUpdate(); // for UIScaleFactor + + LLSliderCtrl* fov_slider = getChild("camera_fov"); + fov_slider->setMinValue(LLViewerCamera::getInstance()->getMinView()); + fov_slider->setMaxValue(LLViewerCamera::getInstance()->getMaxView()); + + std::string cache_location = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ""); + setCacheLocation(cache_location); + + LLViewerMedia::getInstance()->setCookiesEnabled(getChild("cookies_enabled")->getValue()); + + if (hasChild("web_proxy_enabled", true) &&hasChild("web_proxy_editor", true) && hasChild("web_proxy_port", true)) + { + bool proxy_enable = getChild("web_proxy_enabled")->getValue(); + std::string proxy_address = getChild("web_proxy_editor")->getValue(); + int proxy_port = getChild("web_proxy_port")->getValue(); + LLViewerMedia::getInstance()->setProxyConfig(proxy_enable, proxy_address, proxy_port); + } + + if (mGotPersonalInfo) + { + bool new_hide_online = getChild("online_visibility")->getValue().asBoolean(); + + if (new_hide_online != mOriginalHideOnlineStatus) + { + // This hack is because we are representing several different + // possible strings with a single checkbox. Since most users + // can only select between 2 values, we represent it as a + // checkbox. This breaks down a little bit for liaisons, but + // works out in the end. + if (new_hide_online != mOriginalHideOnlineStatus) + { + if (new_hide_online) mDirectoryVisibility = VISIBILITY_HIDDEN; + else mDirectoryVisibility = VISIBILITY_DEFAULT; + //Update showonline value, otherwise multiple applys won't work + mOriginalHideOnlineStatus = new_hide_online; + } + gAgent.sendAgentUpdateUserInfo(mDirectoryVisibility); + } + } + + saveAvatarProperties(); +} + +void LLFloaterPreference::cancel(const std::vector settings_to_skip) +{ + LLTabContainer* tabcontainer = getChild("pref core"); + // Call cancel() on all panels that derive from LLPanelPreference + for (child_list_t::const_iterator iter = tabcontainer->getChildList()->begin(); + iter != tabcontainer->getChildList()->end(); ++iter) + { + LLView* view = *iter; + LLPanelPreference* panel = dynamic_cast(view); + if (panel) + panel->cancel(settings_to_skip); + } + // hide joystick pref floater + LLFloaterReg::hideInstance("pref_joystick"); + + // hide translation settings floater + LLFloaterReg::hideInstance("prefs_translation"); + + // hide autoreplace settings floater + LLFloaterReg::hideInstance("prefs_autoreplace"); + + // hide spellchecker settings folder + LLFloaterReg::hideInstance("prefs_spellchecker"); + + // hide advanced graphics floater + LLFloaterReg::hideInstance("prefs_graphics_advanced"); + + // reverts any changes to current skin + gSavedSettings.setString("SkinCurrent", sSkin); + + updateClickActionViews(); + + LLFloaterPreferenceProxy * advanced_proxy_settings = LLFloaterReg::findTypedInstance("prefs_proxy"); + if (advanced_proxy_settings) + { + advanced_proxy_settings->cancel(); + } + //Need to reload the navmesh if the pathing console is up + LLHandle pathfindingConsoleHandle = LLFloaterPathfindingConsole::getInstanceHandle(); + if ( !pathfindingConsoleHandle.isDead() ) + { + LLFloaterPathfindingConsole* pPathfindingConsole = pathfindingConsoleHandle.get(); + pPathfindingConsole->onRegionBoundaryCross(); + } + + if (!mSavedGraphicsPreset.empty()) + { + gSavedSettings.setString("PresetGraphicActive", mSavedGraphicsPreset); + LLPresetsManager::getInstance()->triggerChangeSignal(); + } + + restoreIgnoredNotifications(); +} + +void LLFloaterPreference::onOpen(const LLSD& key) +{ + // this variable and if that follows it are used to properly handle do not disturb mode response message + static bool initialized = false; + // if user is logged in and we haven't initialized do not disturb mode response yet, do it + if (!initialized && LLStartUp::getStartupState() == STATE_STARTED) + { + // Special approach is used for do not disturb response localization, because "DoNotDisturbModeResponse" is + // in non-localizable xml, and also because it may be changed by user and in this case it shouldn't be localized. + // To keep track of whether do not disturb response is default or changed by user additional setting DoNotDisturbResponseChanged + // was added into per account settings. + + // initialization should happen once,so setting variable to true + initialized = true; + // this connection is needed to properly set "DoNotDisturbResponseChanged" setting when user makes changes in + // do not disturb response message. + gSavedPerAccountSettings.getControl("DoNotDisturbModeResponse")->getSignal()->connect(boost::bind(&LLFloaterPreference::onDoNotDisturbResponseChanged, this)); + } + gAgent.sendAgentUserInfoRequest(); + + /////////////////////////// From LLPanelGeneral ////////////////////////// + // if we have no agent, we can't let them choose anything + // if we have an agent, then we only let them choose if they have a choice + bool can_choose_maturity = + gAgent.getID().notNull() && + (gAgent.isMature() || gAgent.isGodlike()); + + LLComboBox* maturity_combo = getChild("maturity_desired_combobox"); + LLAvatarPropertiesProcessor::getInstance()->sendAvatarLegacyPropertiesRequest( gAgent.getID() ); + if (can_choose_maturity) + { + // if they're not adult or a god, they shouldn't see the adult selection, so delete it + if (!gAgent.isAdult() && !gAgent.isGodlikeWithoutAdminMenuFakery()) + { + // we're going to remove the adult entry from the combo + LLScrollListCtrl* maturity_list = maturity_combo->findChild("ComboBox"); + if (maturity_list) + { + maturity_list->deleteItems(LLSD(SIM_ACCESS_ADULT)); + } + } + getChildView("maturity_desired_combobox")->setEnabled( true); + getChildView("maturity_desired_textbox")->setVisible( false); + } + else + { + getChild("maturity_desired_textbox")->setValue(maturity_combo->getSelectedItemLabel()); + getChildView("maturity_desired_combobox")->setEnabled( false); + } + + // Forget previous language changes. + mLanguageChanged = false; + + // Display selected maturity icons. + onChangeMaturity(); + + onChangeModelFolder(); + onChangePBRFolder(); + onChangeTextureFolder(); + onChangeSoundFolder(); + onChangeAnimationFolder(); + + // Load (double-)click to walk/teleport settings. + updateClickActionViews(); + + // Enabled/disabled popups, might have been changed by user actions + // while preferences floater was closed. + buildPopupLists(); + + + //get the options that were checked + onNotificationsChange("FriendIMOptions"); + onNotificationsChange("NonFriendIMOptions"); + onNotificationsChange("ConferenceIMOptions"); + onNotificationsChange("GroupChatOptions"); + onNotificationsChange("NearbyChatOptions"); + onNotificationsChange("ObjectIMOptions"); + + LLPanelLogin::setAlwaysRefresh(true); + refresh(); + + // Make sure the current state of prefs are saved away when + // when the floater is opened. That will make cancel do its + // job + saveSettings(); + + // Make sure there is a default preference file + LLPresetsManager::getInstance()->createMissingDefault(PRESETS_CAMERA); + LLPresetsManager::getInstance()->createMissingDefault(PRESETS_GRAPHIC); + + bool started = (LLStartUp::getStartupState() == STATE_STARTED); + + LLButton* load_btn = findChild("PrefLoadButton"); + LLButton* save_btn = findChild("PrefSaveButton"); + LLButton* delete_btn = findChild("PrefDeleteButton"); + LLButton* exceptions_btn = findChild("RenderExceptionsButton"); + LLButton* auto_adjustments_btn = findChild("AutoAdjustmentsButton"); + + if (load_btn && save_btn && delete_btn && exceptions_btn && auto_adjustments_btn) + { + load_btn->setEnabled(started); + save_btn->setEnabled(started); + delete_btn->setEnabled(started); + exceptions_btn->setEnabled(started); + auto_adjustments_btn->setEnabled(started); + } + + collectSearchableItems(); + if (!mFilterEdit->getText().empty()) + { + mFilterEdit->setText(LLStringExplicit("")); + onUpdateFilterTerm(true); + } +} + +void LLFloaterPreference::onRenderOptionEnable() +{ + refreshEnabledGraphics(); +} + +void LLFloaterPreference::onAvatarImpostorsEnable() +{ + refreshEnabledGraphics(); +} + +//static +void LLFloaterPreference::initDoNotDisturbResponse() + { + if (!gSavedPerAccountSettings.getBOOL("DoNotDisturbResponseChanged")) + { + //LLTrans::getString("DoNotDisturbModeResponseDefault") is used here for localization (EXT-5885) + gSavedPerAccountSettings.setString("DoNotDisturbModeResponse", LLTrans::getString("DoNotDisturbModeResponseDefault")); + } + } + +//static +void LLFloaterPreference::updateShowFavoritesCheckbox(bool val) +{ + LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); + if (instance) + { + instance->getChild("favorites_on_login_check")->setValue(val); + } +} + +void LLFloaterPreference::setHardwareDefaults() +{ + std::string preset_graphic_active = gSavedSettings.getString("PresetGraphicActive"); + if (!preset_graphic_active.empty()) + { + saveGraphicsPreset(preset_graphic_active); + saveSettings(); // save here to be able to return to the previous preset by Cancel + } + setRecommendedSettings(); +} + +void LLFloaterPreference::setRecommendedSettings() +{ + resetAutotuneSettings(); + gSavedSettings.getControl("RenderVSyncEnable")->resetToDefault(true); + + LLFeatureManager::getInstance()->applyRecommendedSettings(); + + // reset indirects before refresh because we may have changed what they control + LLAvatarComplexityControls::setIndirectControls(); + + refreshEnabledGraphics(); + gSavedSettings.setString("PresetGraphicActive", ""); + LLPresetsManager::getInstance()->triggerChangeSignal(); + + LLTabContainer* tabcontainer = getChild("pref core"); + child_list_t::const_iterator iter = tabcontainer->getChildList()->begin(); + child_list_t::const_iterator end = tabcontainer->getChildList()->end(); + for ( ; iter != end; ++iter) + { + LLView* view = *iter; + LLPanelPreference* panel = dynamic_cast(view); + if (panel) + { + panel->setHardwareDefaults(); + } + } +} + +void LLFloaterPreference::resetAutotuneSettings() +{ + gSavedSettings.setBOOL("AutoTuneFPS", false); + + const std::string autotune_settings[] = { + "AutoTuneLock", + "KeepAutoTuneLock", + "TargetFPS", + "TuningFPSStrategy", + "AutoTuneImpostorByDistEnabled", + "AutoTuneImpostorFarAwayDistance" , + "AutoTuneRenderFarClipMin", + "AutoTuneRenderFarClipTarget", + "RenderAvatarMaxART" + }; + + for (auto it : autotune_settings) + { + gSavedSettings.getControl(it)->resetToDefault(true); + } +} + +void LLFloaterPreference::getControlNames(std::vector& names) +{ + LLView* view = findChild("display"); + LLFloater* advanced = LLFloaterReg::findTypedInstance("prefs_graphics_advanced"); + if (view && advanced) + { + std::list stack; + stack.push_back(view); + stack.push_back(advanced); + while(!stack.empty()) + { + // Process view on top of the stack + LLView* curview = stack.front(); + stack.pop_front(); + + LLUICtrl* ctrl = dynamic_cast(curview); + if (ctrl) + { + LLControlVariable* control = ctrl->getControlVariable(); + if (control) + { + std::string control_name = control->getName(); + if (std::find(names.begin(), names.end(), control_name) == names.end()) + { + names.push_back(control_name); + } + } + } + + for (child_list_t::const_iterator iter = curview->getChildList()->begin(); + iter != curview->getChildList()->end(); ++iter) + { + stack.push_back(*iter); + } + } + } +} + +//virtual +void LLFloaterPreference::onClose(bool app_quitting) +{ + gSavedSettings.setS32("LastPrefTab", getChild("pref core")->getCurrentPanelIndex()); + LLPanelLogin::setAlwaysRefresh(false); + if (!app_quitting) + { + cancel(); + } +} + +// static +void LLFloaterPreference::onBtnOK(const LLSD& userdata) +{ + // commit any outstanding text entry + if (hasFocus()) + { + LLUICtrl* cur_focus = dynamic_cast(gFocusMgr.getKeyboardFocus()); + if (cur_focus && cur_focus->acceptsTextInput()) + { + cur_focus->onCommit(); + } + } + + if (canClose()) + { + saveSettings(); + apply(); + + if (userdata.asString() == "closeadvanced") + { + LLFloaterReg::hideInstance("prefs_graphics_advanced"); + } + else + { + closeFloater(false); + } + + //Conversation transcript and log path changed so reload conversations based on new location + if(mPriorInstantMessageLogPath.length()) + { + if(moveTranscriptsAndLog()) + { + //When floaters are empty but have a chat history files, reload chat history into them + LLFloaterIMSessionTab::reloadEmptyFloaters(); + } + //Couldn't move files so restore the old path and show a notification + else + { + gSavedPerAccountSettings.setString("InstantMessageLogPath", mPriorInstantMessageLogPath); + LLNotificationsUtil::add("PreferenceChatPathChanged"); + } + mPriorInstantMessageLogPath.clear(); + } + + LLUIColorTable::instance().saveUserSettings(); + gSavedSettings.saveToFile(gSavedSettings.getString("ClientSettingsFile"), true); + + //Only save once logged in and loaded per account settings + if(mGotPersonalInfo) + { + gSavedPerAccountSettings.saveToFile(gSavedSettings.getString("PerAccountSettingsFile"), true); + } + } + else + { + // Show beep, pop up dialog, etc. + LL_INFOS("Preferences") << "Can't close preferences!" << LL_ENDL; + } + + LLPanelLogin::updateLocationSelectorsVisibility(); + //Need to reload the navmesh if the pathing console is up + LLHandle pathfindingConsoleHandle = LLFloaterPathfindingConsole::getInstanceHandle(); + if ( !pathfindingConsoleHandle.isDead() ) + { + LLFloaterPathfindingConsole* pPathfindingConsole = pathfindingConsoleHandle.get(); + pPathfindingConsole->onRegionBoundaryCross(); + } +} + +// static +void LLFloaterPreference::onBtnCancel(const LLSD& userdata) +{ + if (hasFocus()) + { + LLUICtrl* cur_focus = dynamic_cast(gFocusMgr.getKeyboardFocus()); + if (cur_focus && cur_focus->acceptsTextInput()) + { + cur_focus->onCommit(); + } + refresh(); + } + + if (userdata.asString() == "closeadvanced") + { + cancel({"RenderQualityPerformance"}); + LLFloaterReg::hideInstance("prefs_graphics_advanced"); + } + else + { + cancel(); + closeFloater(); + } +} + +// static +void LLFloaterPreference::updateUserInfo(const std::string& visibility) +{ + LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); + if (instance) + { + instance->setPersonalInfo(visibility); + } +} + +void LLFloaterPreference::refreshEnabledGraphics() +{ + LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); + if (instance) + { + instance->refresh(); + } + + LLFloater* advanced = LLFloaterReg::findTypedInstance("prefs_graphics_advanced"); + if (advanced) + { + advanced->refresh(); + } +} + +void LLFloaterPreference::onClickClearCache() +{ + LLNotificationsUtil::add("ConfirmClearCache", LLSD(), LLSD(), callback_clear_cache); +} + +void LLFloaterPreference::onClickBrowserClearCache() +{ + LLNotificationsUtil::add("ConfirmClearBrowserCache", LLSD(), LLSD(), callback_clear_browser_cache); +} + +// Called when user changes language via the combobox. +void LLFloaterPreference::onLanguageChange() +{ + // Let the user know that the change will only take effect after restart. + // Do it only once so that we're not too irritating. + if (!mLanguageChanged) + { + LLNotificationsUtil::add("ChangeLanguage"); + mLanguageChanged = true; + } +} + +void LLFloaterPreference::onNotificationsChange(const std::string& OptionName) +{ + mNotificationOptions[OptionName] = getChild(OptionName)->getSelectedItemLabel(); + + bool show_notifications_alert = true; + for (notifications_map::iterator it_notification = mNotificationOptions.begin(); it_notification != mNotificationOptions.end(); it_notification++) + { + if(it_notification->second != "No action") + { + show_notifications_alert = false; + break; + } + } + + getChild("notifications_alert")->setVisible(show_notifications_alert); +} + +void LLFloaterPreference::onNameTagOpacityChange(const LLSD& newvalue) +{ + LLColorSwatchCtrl* color_swatch = findChild("background"); + if (color_swatch) + { + LLColor4 new_color = color_swatch->get(); + color_swatch->set( new_color.setAlpha(newvalue.asReal()) ); + } +} + +void LLFloaterPreference::onClickSetCache() +{ + std::string cur_name(gSavedSettings.getString("CacheLocation")); +// std::string cur_top_folder(gDirUtilp->getBaseFileName(cur_name)); + + std::string proposed_name(cur_name); + + (new LLDirPickerThread(boost::bind(&LLFloaterPreference::changeCachePath, this, _1, _2), proposed_name))->getFile(); +} + +void LLFloaterPreference::changeCachePath(const std::vector& filenames, std::string proposed_name) +{ + std::string dir_name = filenames[0]; + if (!dir_name.empty() && dir_name != proposed_name) + { + std::string new_top_folder(gDirUtilp->getBaseFileName(dir_name)); + LLNotificationsUtil::add("CacheWillBeMoved"); + gSavedSettings.setString("NewCacheLocation", dir_name); + gSavedSettings.setString("NewCacheLocationTopFolder", new_top_folder); + } + else + { + std::string cache_location = gDirUtilp->getCacheDir(); + gSavedSettings.setString("CacheLocation", cache_location); + std::string top_folder(gDirUtilp->getBaseFileName(cache_location)); + gSavedSettings.setString("CacheLocationTopFolder", top_folder); + } +} + +void LLFloaterPreference::onClickResetCache() +{ + if (gDirUtilp->getCacheDir(false) == gDirUtilp->getCacheDir(true)) + { + // The cache location was already the default. + return; + } + gSavedSettings.setString("NewCacheLocation", ""); + gSavedSettings.setString("NewCacheLocationTopFolder", ""); + LLNotificationsUtil::add("CacheWillBeMoved"); + std::string cache_location = gDirUtilp->getCacheDir(false); + gSavedSettings.setString("CacheLocation", cache_location); + std::string top_folder(gDirUtilp->getBaseFileName(cache_location)); + gSavedSettings.setString("CacheLocationTopFolder", top_folder); +} + +void LLFloaterPreference::onClickSkin(LLUICtrl* ctrl, const LLSD& userdata) +{ + gSavedSettings.setString("SkinCurrent", userdata.asString()); + ctrl->setValue(userdata.asString()); +} + +void LLFloaterPreference::onSelectSkin() +{ + std::string skin_selection = getChild("skin_selection")->getValue().asString(); + gSavedSettings.setString("SkinCurrent", skin_selection); +} + +void LLFloaterPreference::refreshSkin(void* data) +{ + LLPanel*self = (LLPanel*)data; + sSkin = gSavedSettings.getString("SkinCurrent"); + self->getChild("skin_selection", true)->setValue(sSkin); +} + +void LLFloaterPreference::buildPopupLists() +{ + LLScrollListCtrl& disabled_popups = + getChildRef("disabled_popups"); + LLScrollListCtrl& enabled_popups = + getChildRef("enabled_popups"); + + disabled_popups.deleteAllItems(); + enabled_popups.deleteAllItems(); + + for (LLNotifications::TemplateMap::const_iterator iter = LLNotifications::instance().templatesBegin(); + iter != LLNotifications::instance().templatesEnd(); + ++iter) + { + LLNotificationTemplatePtr templatep = iter->second; + LLNotificationFormPtr formp = templatep->mForm; + + LLNotificationForm::EIgnoreType ignore = formp->getIgnoreType(); + if (ignore <= LLNotificationForm::IGNORE_NO) + continue; + + LLSD row; + row["columns"][0]["value"] = formp->getIgnoreMessage(); + row["columns"][0]["font"] = "SANSSERIF_SMALL"; + row["columns"][0]["width"] = 400; + + LLScrollListItem* item = NULL; + + bool show_popup = !formp->getIgnored(); + if (!show_popup) + { + if (ignore == LLNotificationForm::IGNORE_WITH_LAST_RESPONSE) + { + LLSD last_response = LLUI::getInstance()->mSettingGroups["config"]->getLLSD("Default" + templatep->mName); + if (!last_response.isUndefined()) + { + for (LLSD::map_const_iterator it = last_response.beginMap(); + it != last_response.endMap(); + ++it) + { + if (it->second.asBoolean()) + { + row["columns"][1]["value"] = formp->getElement(it->first)["ignore"].asString(); + row["columns"][1]["font"] = "SANSSERIF_SMALL"; + row["columns"][1]["width"] = 360; + break; + } + } + } + } + item = disabled_popups.addElement(row); + } + else + { + item = enabled_popups.addElement(row); + } + + if (item) + { + item->setUserdata((void*)&iter->first); + } + } +} + +void LLFloaterPreference::refreshEnabledState() +{ + LLCheckBoxCtrl* ctrl_pbr = getChild("UsePBRShaders"); + + //PBR + ctrl_pbr->setEnabled(true); + + // Cannot have floater active until caps have been received + getChild("default_creation_permissions")->setEnabled(LLStartUp::getStartupState() >= STATE_STARTED); + + getChildView("block_list")->setEnabled(LLLoginInstance::getInstance()->authSuccess()); +} + +void LLAvatarComplexityControls::setIndirectControls() +{ + /* + * We have controls that have an indirect relationship between the control + * values and adjacent text and the underlying setting they influence. + * In each case, the control and its associated setting are named Indirect + * This method interrogates the controlled setting and establishes the + * appropriate value for the indirect control. It must be called whenever the + * underlying setting may have changed other than through the indirect control, + * such as when the 'Reset all to recommended settings' button is used... + */ + setIndirectMaxNonImpostors(); + setIndirectMaxArc(); +} + +// static +void LLAvatarComplexityControls::setIndirectMaxNonImpostors() +{ + U32 max_non_impostors = gSavedSettings.getU32("RenderAvatarMaxNonImpostors"); + // for this one, we just need to make zero, which means off, the max value of the slider + U32 indirect_max_non_impostors = (0 == max_non_impostors) ? LLVOAvatar::NON_IMPOSTORS_MAX_SLIDER : max_non_impostors; + gSavedSettings.setU32("IndirectMaxNonImpostors", indirect_max_non_impostors); +} + +void LLAvatarComplexityControls::setIndirectMaxArc() +{ + U32 max_arc = gSavedSettings.getU32("RenderAvatarMaxComplexity"); + U32 indirect_max_arc; + if (0 == max_arc) + { + // the off position is all the way to the right, so set to control max + indirect_max_arc = INDIRECT_MAX_ARC_OFF; + } + else + { + // This is the inverse of the calculation in updateMaxComplexity + indirect_max_arc = (U32)ll_round(((log(F32(max_arc)) - MIN_ARC_LOG) / ARC_LIMIT_MAP_SCALE)) + MIN_INDIRECT_ARC_LIMIT; + } + gSavedSettings.setU32("IndirectMaxComplexity", indirect_max_arc); +} + +void LLFloaterPreference::refresh() +{ + LLPanel::refresh(); + LLAvatarComplexityControls::setText( + gSavedSettings.getU32("RenderAvatarMaxComplexity"), + getChild("IndirectMaxComplexityText", true)); + refreshEnabledState(); + LLFloater* advanced = LLFloaterReg::findTypedInstance("prefs_graphics_advanced"); + if (advanced) + { + advanced->refresh(); + } + updateClickActionViews(); +} + +void LLFloaterPreference::onCommitWindowedMode() +{ + refresh(); +} + +void LLFloaterPreference::onChangeQuality(const LLSD& data) +{ + U32 level = (U32)(data.asReal()); + LLFeatureManager::getInstance()->setGraphicsLevel(level, true); + refreshEnabledGraphics(); + refresh(); +} + +void LLFloaterPreference::onClickSetSounds() +{ + // Disable Enable gesture sounds checkbox if the master sound is disabled + // or if sound effects are disabled. + getChild("gesture_audio_play_btn")->setEnabled(!gSavedSettings.getBOOL("MuteSounds")); +} + +void LLFloaterPreference::onClickEnablePopup() +{ + LLScrollListCtrl& disabled_popups = getChildRef("disabled_popups"); + + std::vector items = disabled_popups.getAllSelected(); + std::vector::iterator itor; + for (itor = items.begin(); itor != items.end(); ++itor) + { + LLNotificationTemplatePtr templatep = LLNotifications::instance().getTemplate(*(std::string*)((*itor)->getUserdata())); + //gSavedSettings.setWarning(templatep->mName, true); + std::string notification_name = templatep->mName; + LLUI::getInstance()->mSettingGroups["ignores"]->setBOOL(notification_name, true); + } + + buildPopupLists(); + if (!mFilterEdit->getText().empty()) + { + filterIgnorableNotifications(); + } +} + +void LLFloaterPreference::onClickDisablePopup() +{ + LLScrollListCtrl& enabled_popups = getChildRef("enabled_popups"); + + std::vector items = enabled_popups.getAllSelected(); + std::vector::iterator itor; + for (itor = items.begin(); itor != items.end(); ++itor) + { + LLNotificationTemplatePtr templatep = LLNotifications::instance().getTemplate(*(std::string*)((*itor)->getUserdata())); + templatep->mForm->setIgnored(true); + } + + buildPopupLists(); + if (!mFilterEdit->getText().empty()) + { + filterIgnorableNotifications(); + } +} + +void LLFloaterPreference::resetAllIgnored() +{ + for (LLNotifications::TemplateMap::const_iterator iter = LLNotifications::instance().templatesBegin(); + iter != LLNotifications::instance().templatesEnd(); + ++iter) + { + if (iter->second->mForm->getIgnoreType() > LLNotificationForm::IGNORE_NO) + { + iter->second->mForm->setIgnored(false); + } + } +} + +void LLFloaterPreference::setAllIgnored() +{ + for (LLNotifications::TemplateMap::const_iterator iter = LLNotifications::instance().templatesBegin(); + iter != LLNotifications::instance().templatesEnd(); + ++iter) + { + if (iter->second->mForm->getIgnoreType() > LLNotificationForm::IGNORE_NO) + { + iter->second->mForm->setIgnored(true); + } + } +} + +void LLFloaterPreference::onClickLogPath() +{ + std::string proposed_name(gSavedPerAccountSettings.getString("InstantMessageLogPath")); + mPriorInstantMessageLogPath.clear(); + + + (new LLDirPickerThread(boost::bind(&LLFloaterPreference::changeLogPath, this, _1, _2), proposed_name))->getFile(); +} + +void LLFloaterPreference::changeLogPath(const std::vector& filenames, std::string proposed_name) +{ + //Path changed + if (proposed_name != filenames[0]) + { + gSavedPerAccountSettings.setString("InstantMessageLogPath", filenames[0]); + mPriorInstantMessageLogPath = proposed_name; + + // enable/disable 'Delete transcripts button + updateDeleteTranscriptsButton(); + } +} + +bool LLFloaterPreference::moveTranscriptsAndLog() +{ + std::string instantMessageLogPath(gSavedPerAccountSettings.getString("InstantMessageLogPath")); + std::string chatLogPath = gDirUtilp->add(instantMessageLogPath, gDirUtilp->getUserName()); + + bool madeDirectory = false; + + //Does the directory really exist, if not then make it + if(!LLFile::isdir(chatLogPath)) + { + //mkdir success is defined as zero + if(LLFile::mkdir(chatLogPath) != 0) + { + return false; + } + madeDirectory = true; + } + + std::string originalConversationLogDir = LLConversationLog::instance().getFileName(); + std::string targetConversationLogDir = gDirUtilp->add(chatLogPath, "conversation.log"); + //Try to move the conversation log + if(!LLConversationLog::instance().moveLog(originalConversationLogDir, targetConversationLogDir)) + { + //Couldn't move the log and created a new directory so remove the new directory + if(madeDirectory) + { + LLFile::rmdir(chatLogPath); + } + return false; + } + + //Attempt to move transcripts + std::vector listOfTranscripts; + std::vector listOfFilesMoved; + + LLLogChat::getListOfTranscriptFiles(listOfTranscripts); + + if(!LLLogChat::moveTranscripts(gDirUtilp->getChatLogsDir(), + instantMessageLogPath, + listOfTranscripts, + listOfFilesMoved)) + { + //Couldn't move all the transcripts so restore those that moved back to their old location + LLLogChat::moveTranscripts(instantMessageLogPath, + gDirUtilp->getChatLogsDir(), + listOfFilesMoved); + + //Move the conversation log back + LLConversationLog::instance().moveLog(targetConversationLogDir, originalConversationLogDir); + + if(madeDirectory) + { + LLFile::rmdir(chatLogPath); + } + + return false; + } + + gDirUtilp->setChatLogsDir(instantMessageLogPath); + gDirUtilp->updatePerAccountChatLogsDir(); + + return true; +} + +void LLFloaterPreference::setPersonalInfo(const std::string& visibility) +{ + mGotPersonalInfo = true; + mDirectoryVisibility = visibility; + + if (visibility == VISIBILITY_DEFAULT) + { + mOriginalHideOnlineStatus = false; + getChildView("online_visibility")->setEnabled(true); + } + else if (visibility == VISIBILITY_HIDDEN) + { + mOriginalHideOnlineStatus = true; + getChildView("online_visibility")->setEnabled(true); + } + else + { + mOriginalHideOnlineStatus = true; + } + + getChild("online_searchresults")->setEnabled(true); + getChildView("friends_online_notify_checkbox")->setEnabled(true); + getChild("online_visibility")->setValue(mOriginalHideOnlineStatus); + getChild("online_visibility")->setLabelArg("[DIR_VIS]", mDirectoryVisibility); + + getChildView("favorites_on_login_check")->setEnabled(true); + getChildView("log_path_button")->setEnabled(true); + getChildView("chat_font_size")->setEnabled(true); + getChildView("conversation_log_combo")->setEnabled(true); + getChild("voice_call_friends_only_check")->setEnabled(true); + getChild("voice_call_friends_only_check")->setValue(gSavedPerAccountSettings.getBOOL("VoiceCallsFriendsOnly")); +} + + +void LLFloaterPreference::refreshUI() +{ + refresh(); +} + +void LLAvatarComplexityControls::updateMax(LLSliderCtrl* slider, LLTextBox* value_label, bool short_val) +{ + // Called when the IndirectMaxComplexity control changes + // Responsible for fixing the slider label (IndirectMaxComplexityText) and setting RenderAvatarMaxComplexity + U32 indirect_value = slider->getValue().asInteger(); + U32 max_arc; + + if (INDIRECT_MAX_ARC_OFF == indirect_value) + { + // The 'off' position is when the slider is all the way to the right, + // which is a value of INDIRECT_MAX_ARC_OFF, + // so it is necessary to set max_arc to 0 disable muted avatars. + max_arc = 0; + } + else + { + // if this is changed, the inverse calculation in setIndirectMaxArc + // must be changed to match + max_arc = (U32)ll_round(exp(MIN_ARC_LOG + (ARC_LIMIT_MAP_SCALE * (indirect_value - MIN_INDIRECT_ARC_LIMIT)))); + } + + gSavedSettings.setU32("RenderAvatarMaxComplexity", (U32)max_arc); + setText(max_arc, value_label, short_val); +} + +void LLAvatarComplexityControls::setText(U32 value, LLTextBox* text_box, bool short_val) +{ + if (0 == value) + { + text_box->setText(LLTrans::getString("no_limit")); + } + else + { + std::string text_value = short_val ? llformat("%d", value / 1000) : llformat("%d", value); + text_box->setText(text_value); + } +} + +void LLAvatarComplexityControls::updateMaxRenderTime(LLSliderCtrl* slider, LLTextBox* value_label, bool short_val) +{ + setRenderTimeText((F32)(LLPerfStats::renderAvatarMaxART_ns/1000), value_label, short_val); +} + +void LLAvatarComplexityControls::setRenderTimeText(F32 value, LLTextBox* text_box, bool short_val) +{ + if (0 == value) + { + text_box->setText(LLTrans::getString("no_limit")); + } + else + { + text_box->setText(llformat("%.0f", value)); + } +} + +void LLFloaterPreference::updateMaxComplexity() +{ + // Called when the IndirectMaxComplexity control changes + LLAvatarComplexityControls::updateMax( + getChild("IndirectMaxComplexity"), + getChild("IndirectMaxComplexityText")); +} + +void LLFloaterPreference::updateComplexityText() +{ + LLAvatarComplexityControls::setText(gSavedSettings.getU32("RenderAvatarMaxComplexity"), + getChild("IndirectMaxComplexityText", true)); +} + +bool LLFloaterPreference::loadFromFilename(const std::string& filename, std::map &label_map) +{ + LLXMLNodePtr root; + + if (!LLXMLNode::parseFile(filename, root, NULL)) + { + LL_WARNS("Preferences") << "Unable to parse file " << filename << LL_ENDL; + return false; + } + + if (!root->hasName("labels")) + { + LL_WARNS("Preferences") << filename << " is not a valid definition file" << LL_ENDL; + return false; + } + + LabelTable params; + LLXUIParser parser; + parser.readXUI(root, params, filename); + + if (params.validateBlock()) + { + for (LLInitParam::ParamIterator::const_iterator it = params.labels.begin(); + it != params.labels.end(); + ++it) + { + LabelDef label_entry = *it; + label_map[label_entry.name] = label_entry.value; + } + } + else + { + LL_WARNS("Preferences") << filename << " failed to load" << LL_ENDL; + return false; + } + + return true; +} + +void LLFloaterPreference::onChangeMaturity() +{ + U8 sim_access = gSavedSettings.getU32("PreferredMaturity"); + + getChild("rating_icon_general")->setVisible(sim_access == SIM_ACCESS_PG + || sim_access == SIM_ACCESS_MATURE + || sim_access == SIM_ACCESS_ADULT); + + getChild("rating_icon_moderate")->setVisible(sim_access == SIM_ACCESS_MATURE + || sim_access == SIM_ACCESS_ADULT); + + getChild("rating_icon_adult")->setVisible(sim_access == SIM_ACCESS_ADULT); +} + +void LLFloaterPreference::onChangeComplexityMode(const LLSD& newvalue) +{ + bool enable_complexity = newvalue.asInteger() != LLVOAvatar::AV_RENDER_ONLY_SHOW_FRIENDS; + getChild("IndirectMaxComplexity")->setEnabled(enable_complexity); +} + +std::string get_category_path(LLFolderType::EType cat_type) +{ + LLUUID cat_id = gInventory.findUserDefinedCategoryUUIDForType(cat_type); + return get_category_path(cat_id); +} + +void LLFloaterPreference::onChangeModelFolder() +{ + if (gInventory.isInventoryUsable()) + { + getChild("upload_models")->setText(get_category_path(LLFolderType::FT_OBJECT)); + } +} + +void LLFloaterPreference::onChangePBRFolder() +{ + if (gInventory.isInventoryUsable()) + { + getChild("upload_pbr")->setText(get_category_path(LLFolderType::FT_MATERIAL)); + } +} + +void LLFloaterPreference::onChangeTextureFolder() +{ + if (gInventory.isInventoryUsable()) + { + getChild("upload_textures")->setText(get_category_path(LLFolderType::FT_TEXTURE)); + } +} + +void LLFloaterPreference::onChangeSoundFolder() +{ + if (gInventory.isInventoryUsable()) + { + getChild("upload_sounds")->setText(get_category_path(LLFolderType::FT_SOUND)); + } +} + +void LLFloaterPreference::onChangeAnimationFolder() +{ + if (gInventory.isInventoryUsable()) + { + getChild("upload_animation")->setText(get_category_path(LLFolderType::FT_ANIMATION)); + } +} + +// FIXME: this will stop you from spawning the sidetray from preferences dialog on login screen +// but the UI for this will still be enabled +void LLFloaterPreference::onClickBlockList() +{ + LLFloaterSidePanelContainer::showPanel("people", "panel_people", + LLSD().with("people_panel_tab_name", "blocked_panel")); +} + +void LLFloaterPreference::onClickProxySettings() +{ + LLFloaterReg::showInstance("prefs_proxy"); +} + +void LLFloaterPreference::onClickTranslationSettings() +{ + LLFloaterReg::showInstance("prefs_translation"); +} + +void LLFloaterPreference::onClickAutoReplace() +{ + LLFloaterReg::showInstance("prefs_autoreplace"); +} + +void LLFloaterPreference::onClickSpellChecker() +{ + LLFloaterReg::showInstance("prefs_spellchecker"); +} + +void LLFloaterPreference::onClickRenderExceptions() +{ + LLFloaterReg::showInstance("avatar_render_settings"); +} + +void LLFloaterPreference::onClickAutoAdjustments() +{ + LLFloaterPerformance* performance_floater = LLFloaterReg::showTypedInstance("performance"); + if (performance_floater) + { + performance_floater->showAutoadjustmentsPanel(); + } +} + +void LLFloaterPreference::onClickAdvanced() +{ + LLFloaterReg::showInstance("prefs_graphics_advanced"); + + LLTabContainer* tabcontainer = getChild("pref core"); + for (child_list_t::const_iterator iter = tabcontainer->getChildList()->begin(); + iter != tabcontainer->getChildList()->end(); ++iter) + { + LLView* view = *iter; + LLPanelPreferenceGraphics* panel = dynamic_cast(view); + if (panel) + { + panel->resetDirtyChilds(); + } + } +} + +void LLFloaterPreference::onClickActionChange() +{ + updateClickActionControls(); +} + +void LLFloaterPreference::onAtmosShaderChange() +{ + LLCheckBoxCtrl* ctrl_alm = getChild("UseLightShaders"); + if(ctrl_alm) + { + //Deferred/SSAO/Shadows + bool bumpshiny = LLCubeMap::sUseCubeMaps && LLFeatureManager::getInstance()->isFeatureAvailable("RenderObjectBump") && gSavedSettings.getBOOL("RenderObjectBump"); + bool shaders = gSavedSettings.getBOOL("WindLightUseAtmosShaders"); + bool enabled = LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferred") && + bumpshiny && + shaders; + + ctrl_alm->setEnabled(enabled); + } +} + +void LLFloaterPreference::onClickPermsDefault() +{ + LLFloaterReg::showInstance("perms_default"); +} + +void LLFloaterPreference::onClickRememberedUsernames() +{ + LLFloaterReg::showInstance("forget_username"); +} + +void LLFloaterPreference::onDeleteTranscripts() +{ + LLSD args; + args["FOLDER"] = gDirUtilp->getUserName(); + + LLNotificationsUtil::add("PreferenceChatDeleteTranscripts", args, LLSD(), boost::bind(&LLFloaterPreference::onDeleteTranscriptsResponse, this, _1, _2)); +} + +void LLFloaterPreference::onDeleteTranscriptsResponse(const LLSD& notification, const LLSD& response) +{ + if (0 == LLNotificationsUtil::getSelectedOption(notification, response)) + { + LLLogChat::deleteTranscripts(); + updateDeleteTranscriptsButton(); + } +} + +void LLFloaterPreference::onLogChatHistorySaved() +{ + LLButton * delete_transcripts_buttonp = getChild("delete_transcripts"); + + if (!delete_transcripts_buttonp->getEnabled()) + { + delete_transcripts_buttonp->setEnabled(true); + } +} + +void LLFloaterPreference::updateClickActionControls() +{ + const int single_clk_action = getChild("single_click_action_combo")->getValue().asInteger(); + const int double_clk_action = getChild("double_click_action_combo")->getValue().asInteger(); + + // Todo: This is a very ugly way to get access to keybindings. + // Reconsider possible options. + // Potential option: make constructor of LLKeyConflictHandler private + // but add a getter that will return shared pointer for specific + // mode, pointer should only exist so long as there are external users. + // In such case we won't need to do this 'dynamic_cast' nightmare. + // updateTable() can also be avoided + LLTabContainer* tabcontainer = getChild("pref core"); + for (child_list_t::const_iterator iter = tabcontainer->getChildList()->begin(); + iter != tabcontainer->getChildList()->end(); ++iter) + { + LLView* view = *iter; + LLPanelPreferenceControls* panel = dynamic_cast(view); + if (panel) + { + panel->setKeyBind("walk_to", + EMouseClickType::CLICK_LEFT, + KEY_NONE, + MASK_NONE, + single_clk_action == 1); + + panel->setKeyBind("walk_to", + EMouseClickType::CLICK_DOUBLELEFT, + KEY_NONE, + MASK_NONE, + double_clk_action == 1); + + panel->setKeyBind("teleport_to", + EMouseClickType::CLICK_DOUBLELEFT, + KEY_NONE, + MASK_NONE, + double_clk_action == 2); + + panel->updateAndApply(); + } + } +} + +void LLFloaterPreference::updateClickActionViews() +{ + bool click_to_walk = false; + bool dbl_click_to_walk = false; + bool dbl_click_to_teleport = false; + + // Todo: This is a very ugly way to get access to keybindings. + // Reconsider possible options. + LLTabContainer* tabcontainer = getChild("pref core"); + for (child_list_t::const_iterator iter = tabcontainer->getChildList()->begin(); + iter != tabcontainer->getChildList()->end(); ++iter) + { + LLView* view = *iter; + LLPanelPreferenceControls* panel = dynamic_cast(view); + if (panel) + { + click_to_walk = panel->canKeyBindHandle("walk_to", + EMouseClickType::CLICK_LEFT, + KEY_NONE, + MASK_NONE); + + dbl_click_to_walk = panel->canKeyBindHandle("walk_to", + EMouseClickType::CLICK_DOUBLELEFT, + KEY_NONE, + MASK_NONE); + + dbl_click_to_teleport = panel->canKeyBindHandle("teleport_to", + EMouseClickType::CLICK_DOUBLELEFT, + KEY_NONE, + MASK_NONE); + } + } + + getChild("single_click_action_combo")->setValue((int)click_to_walk); + getChild("double_click_action_combo")->setValue(dbl_click_to_teleport ? 2 : (int)dbl_click_to_walk); +} + +void LLFloaterPreference::updateSearchableItems() +{ + mSearchDataDirty = true; +} + +void LLFloaterPreference::applyUIColor(LLUICtrl* ctrl, const LLSD& param) +{ + LLUIColorTable::instance().setColor(param.asString(), LLColor4(ctrl->getValue())); +} + +void LLFloaterPreference::getUIColor(LLUICtrl* ctrl, const LLSD& param) +{ + LLColorSwatchCtrl* color_swatch = (LLColorSwatchCtrl*) ctrl; + color_swatch->setOriginal(LLUIColorTable::instance().getColor(param.asString())); +} + +void LLFloaterPreference::setCacheLocation(const LLStringExplicit& location) +{ + LLUICtrl* cache_location_editor = getChild("cache_location"); + cache_location_editor->setValue(location); + cache_location_editor->setToolTip(location); +} + +void LLFloaterPreference::selectPanel(const LLSD& name) +{ + LLTabContainer * tab_containerp = getChild("pref core"); + LLPanel * panel = tab_containerp->getPanelByName(name); + if (NULL != panel) + { + tab_containerp->selectTabPanel(panel); + } +} + +void LLFloaterPreference::selectPrivacyPanel() +{ + selectPanel("im"); +} + +void LLFloaterPreference::selectChatPanel() +{ + selectPanel("chat"); +} + +void LLFloaterPreference::changed() +{ + getChild("clear_log")->setEnabled(LLConversationLog::instance().getConversations().size() > 0); + + // set 'enable' property for 'Delete transcripts...' button + updateDeleteTranscriptsButton(); + +} + +void LLFloaterPreference::saveGraphicsPreset(std::string& preset) +{ + mSavedGraphicsPreset = preset; +} + +//------------------------------Updater--------------------------------------- + +static bool handleBandwidthChanged(const LLSD& newvalue) +{ + gViewerThrottle.setMaxBandwidth((F32) newvalue.asReal()); + return true; +} + +class LLPanelPreference::Updater : public LLEventTimer +{ + +public: + + typedef boost::function callback_t; + + Updater(callback_t cb, F32 period) + :LLEventTimer(period), + mCallback(cb) + { + mEventTimer.stop(); + } + + virtual ~Updater(){} + + void update(const LLSD& new_value) + { + mNewValue = new_value; + mEventTimer.start(); + } + +protected: + + bool tick() + { + mCallback(mNewValue); + mEventTimer.stop(); + + return false; + } + +private: + + LLSD mNewValue; + callback_t mCallback; +}; +//---------------------------------------------------------------------------- +static LLPanelInjector t_places("panel_preference"); +LLPanelPreference::LLPanelPreference() +: LLPanel(), + mBandWidthUpdater(NULL) +{ + mCommitCallbackRegistrar.add("Pref.setControlFalse", boost::bind(&LLPanelPreference::setControlFalse,this, _2)); + mCommitCallbackRegistrar.add("Pref.updateMediaAutoPlayCheckbox", boost::bind(&LLPanelPreference::updateMediaAutoPlayCheckbox, this, _1)); + mCommitCallbackRegistrar.add("Pref.PrefDelete", boost::bind(&LLPanelPreference::deletePreset, this, _2)); + mCommitCallbackRegistrar.add("Pref.PrefSave", boost::bind(&LLPanelPreference::savePreset, this, _2)); + mCommitCallbackRegistrar.add("Pref.PrefLoad", boost::bind(&LLPanelPreference::loadPreset, this, _2)); +} + +//virtual +bool LLPanelPreference::postBuild() +{ + ////////////////////// PanelGeneral /////////////////// + if (hasChild("display_names_check", true)) + { + bool use_people_api = gSavedSettings.getBOOL("UsePeopleAPI"); + LLCheckBoxCtrl* ctrl_display_name = getChild("display_names_check"); + ctrl_display_name->setEnabled(use_people_api); + if (!use_people_api) + { + ctrl_display_name->setValue(false); + } + } + + ////////////////////// PanelVoice /////////////////// + if (hasChild("voice_unavailable", true)) + { + bool voice_disabled = gSavedSettings.getBOOL("CmdLineDisableVoice"); + getChildView("voice_unavailable")->setVisible( voice_disabled); + getChildView("enable_voice_check")->setVisible( !voice_disabled); + } + + //////////////////////PanelSkins /////////////////// + + if (hasChild("skin_selection", true)) + { + LLFloaterPreference::refreshSkin(this); + + // if skin is set to a skin that no longer exists (silver) set back to default + if (getChild("skin_selection")->getSelectedIndex() < 0) + { + gSavedSettings.setString("SkinCurrent", "default"); + LLFloaterPreference::refreshSkin(this); + } + + } + + //////////////////////PanelPrivacy /////////////////// + if (hasChild("media_enabled", true)) + { + bool media_enabled = gSavedSettings.getBOOL("AudioStreamingMedia"); + + getChild("media_enabled")->set(media_enabled); + getChild("autoplay_enabled")->setEnabled(media_enabled); + } + if (hasChild("music_enabled", true)) + { + getChild("music_enabled")->set(gSavedSettings.getBOOL("AudioStreamingMusic")); + } + if (hasChild("voice_call_friends_only_check", true)) + { + getChild("voice_call_friends_only_check")->setCommitCallback(boost::bind(&showFriendsOnlyWarning, _1, _2)); + } + if (hasChild("allow_multiple_viewer_check", true)) + { + getChild("allow_multiple_viewer_check")->setCommitCallback(boost::bind(&showMultipleViewersWarning, _1, _2)); + } + if (hasChild("favorites_on_login_check", true)) + { + getChild("favorites_on_login_check")->setCommitCallback(boost::bind(&handleFavoritesOnLoginChanged, _1, _2)); + bool show_favorites_at_login = LLPanelLogin::getShowFavorites(); + getChild("favorites_on_login_check")->setValue(show_favorites_at_login); + } + if (hasChild("mute_chb_label", true)) + { + getChild("mute_chb_label")->setShowCursorHand(false); + getChild("mute_chb_label")->setSoundFlags(LLView::MOUSE_UP); + getChild("mute_chb_label")->setClickedCallback(boost::bind(&toggleMuteWhenMinimized)); + } + + //////////////////////PanelSetup /////////////////// + if (hasChild("max_bandwidth", true)) + { + mBandWidthUpdater = new LLPanelPreference::Updater(boost::bind(&handleBandwidthChanged, _1), BANDWIDTH_UPDATER_TIMEOUT); + gSavedSettings.getControl("ThrottleBandwidthKBPS")->getSignal()->connect(boost::bind(&LLPanelPreference::Updater::update, mBandWidthUpdater, _2)); + } + +#ifdef EXTERNAL_TOS + LLRadioGroup* ext_browser_settings = getChild("preferred_browser_behavior"); + if (ext_browser_settings) + { + // turn off ability to set external/internal browser + ext_browser_settings->setSelectedByValue(LLWeb::BROWSER_EXTERNAL_ONLY, true); + ext_browser_settings->setEnabled(false); + } +#endif + + apply(); + return true; +} + +LLPanelPreference::~LLPanelPreference() +{ + if (mBandWidthUpdater) + { + delete mBandWidthUpdater; + } +} +void LLPanelPreference::apply() +{ + // no-op +} + +void LLPanelPreference::saveSettings() +{ + LLFloater* advanced = LLFloaterReg::findTypedInstance("prefs_graphics_advanced"); + + // Save the value of all controls in the hierarchy + mSavedValues.clear(); + std::list view_stack; + view_stack.push_back(this); + if (advanced) + { + view_stack.push_back(advanced); + } + while(!view_stack.empty()) + { + // Process view on top of the stack + LLView* curview = view_stack.front(); + view_stack.pop_front(); + + LLColorSwatchCtrl* color_swatch = dynamic_cast(curview); + if (color_swatch) + { + mSavedColors[color_swatch->getName()] = color_swatch->get(); + } + else + { + LLUICtrl* ctrl = dynamic_cast(curview); + if (ctrl) + { + LLControlVariable* control = ctrl->getControlVariable(); + if (control) + { + mSavedValues[control] = control->getValue(); + } + } + } + + // Push children onto the end of the work stack + for (child_list_t::const_iterator iter = curview->getChildList()->begin(); + iter != curview->getChildList()->end(); ++iter) + { + view_stack.push_back(*iter); + } + } + + if (LLStartUp::getStartupState() == STATE_STARTED) + { + LLControlVariable* control = gSavedPerAccountSettings.getControl("VoiceCallsFriendsOnly"); + if (control) + { + mSavedValues[control] = control->getValue(); + } + } +} + +void LLPanelPreference::showMultipleViewersWarning(LLUICtrl* checkbox, const LLSD& value) +{ + if (checkbox && checkbox->getValue()) + { + LLNotificationsUtil::add("AllowMultipleViewers"); + } +} + +void LLPanelPreference::showFriendsOnlyWarning(LLUICtrl* checkbox, const LLSD& value) +{ + if (checkbox) + { + gSavedPerAccountSettings.setBOOL("VoiceCallsFriendsOnly", checkbox->getValue().asBoolean()); + if (checkbox->getValue()) + { + LLNotificationsUtil::add("FriendsAndGroupsOnly"); + } + } +} + +void LLPanelPreference::handleFavoritesOnLoginChanged(LLUICtrl* checkbox, const LLSD& value) +{ + if (checkbox) + { + LLFavoritesOrderStorage::instance().showFavoritesOnLoginChanged(checkbox->getValue().asBoolean()); + if(checkbox->getValue()) + { + LLNotificationsUtil::add("FavoritesOnLogin"); + } + } +} + +void LLPanelPreference::toggleMuteWhenMinimized() +{ + std::string mute("MuteWhenMinimized"); + gSavedSettings.setBOOL(mute, !gSavedSettings.getBOOL(mute)); + LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); + if (instance) + { + instance->getChild("mute_when_minimized")->setBtnFocus(); + } +} + +void LLPanelPreference::cancel(const std::vector settings_to_skip) +{ + for (control_values_map_t::iterator iter = mSavedValues.begin(); + iter != mSavedValues.end(); ++iter) + { + LLControlVariable* control = iter->first; + LLSD ctrl_value = iter->second; + + if((control->getName() == "InstantMessageLogPath") && (ctrl_value.asString() == "")) + { + continue; + } + + auto found = std::find(settings_to_skip.begin(), settings_to_skip.end(), control->getName()); + if (found != settings_to_skip.end()) + { + continue; + } + + control->set(ctrl_value); + } + + for (string_color_map_t::iterator iter = mSavedColors.begin(); + iter != mSavedColors.end(); ++iter) + { + LLColorSwatchCtrl* color_swatch = findChild(iter->first); + if (color_swatch) + { + color_swatch->set(iter->second); + color_swatch->onCommit(); + } + } +} + +void LLPanelPreference::setControlFalse(const LLSD& user_data) +{ + std::string control_name = user_data.asString(); + LLControlVariable* control = findControl(control_name); + + if (control) + control->set(LLSD(false)); +} + +void LLPanelPreference::updateMediaAutoPlayCheckbox(LLUICtrl* ctrl) +{ + std::string name = ctrl->getName(); + + // Disable "Allow Media to auto play" only when both + // "Streaming Music" and "Media" are unchecked. STORM-513. + if ((name == "enable_music") || (name == "enable_media")) + { + bool music_enabled = getChild("enable_music")->get(); + bool media_enabled = getChild("enable_media")->get(); + + getChild("media_auto_play_combo")->setEnabled(music_enabled || media_enabled); + } +} + +void LLPanelPreference::deletePreset(const LLSD& user_data) +{ + LLFloaterReg::showInstance("delete_pref_preset", user_data.asString()); +} + +void LLPanelPreference::savePreset(const LLSD& user_data) +{ + LLFloaterReg::showInstance("save_pref_preset", user_data.asString()); +} + +void LLPanelPreference::loadPreset(const LLSD& user_data) +{ + LLFloaterReg::showInstance("load_pref_preset", user_data.asString()); +} + +void LLPanelPreference::setHardwareDefaults() +{ +} + +class LLPanelPreferencePrivacy : public LLPanelPreference +{ +public: + LLPanelPreferencePrivacy() + { + mAccountIndependentSettings.push_back("AutoDisengageMic"); + } + + /*virtual*/ void saveSettings() + { + LLPanelPreference::saveSettings(); + + // Don't save (=erase from the saved values map) per-account privacy settings + // if we're not logged in, otherwise they will be reset to defaults on log off. + if (LLStartUp::getStartupState() != STATE_STARTED) + { + // Erase only common settings, assuming there are no color settings on Privacy page. + for (control_values_map_t::iterator it = mSavedValues.begin(); it != mSavedValues.end(); ) + { + const std::string setting = it->first->getName(); + if (find(mAccountIndependentSettings.begin(), + mAccountIndependentSettings.end(), setting) == mAccountIndependentSettings.end()) + { + mSavedValues.erase(it++); + } + else + { + ++it; + } + } + } + } + +private: + std::list mAccountIndependentSettings; +}; + +static LLPanelInjector t_pref_graph("panel_preference_graphics"); +static LLPanelInjector t_pref_privacy("panel_preference_privacy"); + +bool LLPanelPreferenceGraphics::postBuild() +{ + LLFloaterReg::showInstance("prefs_graphics_advanced"); + LLFloaterReg::hideInstance("prefs_graphics_advanced"); + + resetDirtyChilds(); + setPresetText(); + + LLPresetsManager* presetsMgr = LLPresetsManager::getInstance(); + presetsMgr->setPresetListChangeCallback(boost::bind(&LLPanelPreferenceGraphics::onPresetsListChange, this)); + presetsMgr->createMissingDefault(PRESETS_GRAPHIC); // a no-op after the first time, but that's ok + + return LLPanelPreference::postBuild(); +} + +void LLPanelPreferenceGraphics::draw() +{ + setPresetText(); + LLPanelPreference::draw(); +} + +void LLPanelPreferenceGraphics::onPresetsListChange() +{ + resetDirtyChilds(); + setPresetText(); + + LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); + if (instance && !gSavedSettings.getString("PresetGraphicActive").empty()) + { + instance->saveSettings(); //make cancel work correctly after changing the preset + } +} + +void LLPanelPreferenceGraphics::setPresetText() +{ + LLTextBox* preset_text = getChild("preset_text"); + + std::string preset_graphic_active = gSavedSettings.getString("PresetGraphicActive"); + + if (!preset_graphic_active.empty() && preset_graphic_active != preset_text->getText()) + { + LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); + if (instance) + { + instance->saveGraphicsPreset(preset_graphic_active); + } + } + + if (hasDirtyChilds() && !preset_graphic_active.empty()) + { + gSavedSettings.setString("PresetGraphicActive", ""); + preset_graphic_active.clear(); + // This doesn't seem to cause an infinite recursion. This trigger is needed to cause the pulldown + // panel to update. + LLPresetsManager::getInstance()->triggerChangeSignal(); + } + + if (!preset_graphic_active.empty()) + { + if (preset_graphic_active == PRESETS_DEFAULT) + { + preset_graphic_active = LLTrans::getString(PRESETS_DEFAULT); + } + preset_text->setText(preset_graphic_active); + } + else + { + preset_text->setText(LLTrans::getString("none_paren_cap")); + } + + preset_text->resetDirty(); +} + +bool LLPanelPreferenceGraphics::hasDirtyChilds() +{ + LLFloater* advanced = LLFloaterReg::findTypedInstance("prefs_graphics_advanced"); + std::list view_stack; + view_stack.push_back(this); + if (advanced) + { + view_stack.push_back(advanced); + } + while(!view_stack.empty()) + { + // Process view on top of the stack + LLView* curview = view_stack.front(); + view_stack.pop_front(); + + LLUICtrl* ctrl = dynamic_cast(curview); + if (ctrl) + { + if (ctrl->isDirty()) + { + LLControlVariable* control = ctrl->getControlVariable(); + if (control) + { + std::string control_name = control->getName(); + if (!control_name.empty()) + { + return true; + } + } + } + } + // Push children onto the end of the work stack + for (child_list_t::const_iterator iter = curview->getChildList()->begin(); + iter != curview->getChildList()->end(); ++iter) + { + view_stack.push_back(*iter); + } + } + + return false; +} + +void LLPanelPreferenceGraphics::resetDirtyChilds() +{ + LLFloater* advanced = LLFloaterReg::findTypedInstance("prefs_graphics_advanced"); + std::list view_stack; + view_stack.push_back(this); + if (advanced) + { + view_stack.push_back(advanced); + } + while(!view_stack.empty()) + { + // Process view on top of the stack + LLView* curview = view_stack.front(); + view_stack.pop_front(); + + LLUICtrl* ctrl = dynamic_cast(curview); + if (ctrl) + { + ctrl->resetDirty(); + } + // Push children onto the end of the work stack + for (child_list_t::const_iterator iter = curview->getChildList()->begin(); + iter != curview->getChildList()->end(); ++iter) + { + view_stack.push_back(*iter); + } + } +} + +void LLPanelPreferenceGraphics::cancel(const std::vector settings_to_skip) +{ + LLPanelPreference::cancel(settings_to_skip); +} +void LLPanelPreferenceGraphics::saveSettings() +{ + resetDirtyChilds(); + std::string preset_graphic_active = gSavedSettings.getString("PresetGraphicActive"); + if (preset_graphic_active.empty()) + { + LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); + if (instance) + { + //don't restore previous preset after closing Preferences + instance->saveGraphicsPreset(preset_graphic_active); + } + } + LLPanelPreference::saveSettings(); +} +void LLPanelPreferenceGraphics::setHardwareDefaults() +{ + resetDirtyChilds(); +} + +//------------------------LLPanelPreferenceControls-------------------------------- +static LLPanelInjector t_pref_contrls("panel_preference_controls"); + +LLPanelPreferenceControls::LLPanelPreferenceControls() + :LLPanelPreference(), + mEditingColumn(-1), + mEditingMode(0) +{ + // MODE_COUNT - 1 because there are currently no settings assigned to 'saved settings'. + for (U32 i = 0; i < LLKeyConflictHandler::MODE_COUNT - 1; ++i) + { + mConflictHandler[i].setLoadMode((LLKeyConflictHandler::ESourceMode)i); + } +} + +LLPanelPreferenceControls::~LLPanelPreferenceControls() +{ +} + +bool LLPanelPreferenceControls::postBuild() +{ + // populate list of controls + pControlsTable = getChild("controls_list"); + pKeyModeBox = getChild("key_mode"); + + pControlsTable->setCommitCallback(boost::bind(&LLPanelPreferenceControls::onListCommit, this)); + pKeyModeBox->setCommitCallback(boost::bind(&LLPanelPreferenceControls::onModeCommit, this)); + getChild("restore_defaults")->setCommitCallback(boost::bind(&LLPanelPreferenceControls::onRestoreDefaultsBtn, this)); + + return true; +} + +void LLPanelPreferenceControls::regenerateControls() +{ + mEditingMode = pKeyModeBox->getValue().asInteger(); + mConflictHandler[mEditingMode].loadFromSettings((LLKeyConflictHandler::ESourceMode)mEditingMode); + populateControlTable(); +} + +bool LLPanelPreferenceControls::addControlTableColumns(const std::string &filename) +{ + LLXMLNodePtr xmlNode; + LLScrollListCtrl::Contents contents; + if (!LLUICtrlFactory::getLayeredXMLNode(filename, xmlNode)) + { + LL_WARNS("Preferences") << "Failed to load " << filename << LL_ENDL; + return false; + } + LLXUIParser parser; + parser.readXUI(xmlNode, contents, filename); + + if (!contents.validateBlock()) + { + return false; + } + + for (LLInitParam::ParamIterator::const_iterator col_it = contents.columns.begin(); + col_it != contents.columns.end(); + ++col_it) + { + pControlsTable->addColumn(*col_it); + } + + return true; +} + +bool LLPanelPreferenceControls::addControlTableRows(const std::string &filename) +{ + LLXMLNodePtr xmlNode; + LLScrollListCtrl::Contents contents; + if (!LLUICtrlFactory::getLayeredXMLNode(filename, xmlNode)) + { + LL_WARNS("Preferences") << "Failed to load " << filename << LL_ENDL; + return false; + } + LLXUIParser parser; + parser.readXUI(xmlNode, contents, filename); + + if (!contents.validateBlock()) + { + return false; + } + + LLScrollListCell::Params cell_params; + // init basic cell params + cell_params.font = LLFontGL::getFontSansSerif(); + cell_params.font_halign = LLFontGL::LEFT; + cell_params.column = ""; + cell_params.value = ""; + + + for (LLInitParam::ParamIterator::const_iterator row_it = contents.rows.begin(); + row_it != contents.rows.end(); + ++row_it) + { + std::string control = row_it->value.getValue().asString(); + if (!control.empty() && control != "menu_separator") + { + bool show = true; + bool enabled = mConflictHandler[mEditingMode].canAssignControl(control); + if (!enabled) + { + // If empty: this is a placeholder to make sure user won't assign + // value by accident, don't show it + // If not empty: predefined control combination user should see + // to know that combination is reserved + show = !mConflictHandler[mEditingMode].isControlEmpty(control); + // example: teleport_to and walk_to in first person view, and + // sitting related functions, see generatePlaceholders() + } + + if (show) + { + // At the moment viewer is hardcoded to assume that columns are named as lst_ctrl%d + LLScrollListItem::Params item_params(*row_it); + item_params.enabled.setValue(enabled); + + S32 num_columns = pControlsTable->getNumColumns(); + for (S32 col = 1; col < num_columns; col++) + { + cell_params.column = llformat("lst_ctrl%d", col); + cell_params.value = mConflictHandler[mEditingMode].getControlString(control, col - 1); + item_params.columns.add(cell_params); + } + pControlsTable->addRow(item_params, EAddPosition::ADD_BOTTOM); + } + } + else + { + // Separator example: + // + // + // + pControlsTable->addRow(*row_it, EAddPosition::ADD_BOTTOM); + } + } + return true; +} + +void LLPanelPreferenceControls::addControlTableSeparator() +{ + LLScrollListItem::Params separator_params; + separator_params.enabled(false); + LLScrollListCell::Params column_params; + column_params.type = "icon"; + column_params.value = "menu_separator"; + column_params.column = "lst_action"; + column_params.color = LLColor4(0.f, 0.f, 0.f, 0.7f); + column_params.font_halign = LLFontGL::HCENTER; + separator_params.columns.add(column_params); + pControlsTable->addRow(separator_params, EAddPosition::ADD_BOTTOM); +} + +void LLPanelPreferenceControls::populateControlTable() +{ + pControlsTable->clearRows(); + pControlsTable->clearColumns(); + + // Add columns + std::string filename; + switch ((LLKeyConflictHandler::ESourceMode)mEditingMode) + { + case LLKeyConflictHandler::MODE_THIRD_PERSON: + case LLKeyConflictHandler::MODE_FIRST_PERSON: + case LLKeyConflictHandler::MODE_EDIT_AVATAR: + case LLKeyConflictHandler::MODE_SITTING: + filename = "control_table_contents_columns_basic.xml"; + break; + default: + { + // Either unknown mode or MODE_SAVED_SETTINGS + // It doesn't have UI or actual settings yet + LL_WARNS("Preferences") << "Unimplemented mode" << LL_ENDL; + + // Searchable columns were removed, mark searchables for an update + LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); + if (instance) + { + instance->updateSearchableItems(); + } + return; + } + } + addControlTableColumns(filename); + + // Add rows. + // Each file represents individual visual group (movement/camera/media...) + if (mEditingMode == LLKeyConflictHandler::MODE_FIRST_PERSON) + { + // Don't display whole camera and editing groups + addControlTableRows("control_table_contents_movement.xml"); + addControlTableSeparator(); + addControlTableRows("control_table_contents_media.xml"); + } + // MODE_THIRD_PERSON; MODE_EDIT_AVATAR; MODE_SITTING + else if (mEditingMode < LLKeyConflictHandler::MODE_SAVED_SETTINGS) + { + // In case of 'sitting' mode, movements still apply due to vehicles + // but walk_to is not supported and will be hidden by addControlTableRows + addControlTableRows("control_table_contents_movement.xml"); + addControlTableSeparator(); + + addControlTableRows("control_table_contents_camera.xml"); + addControlTableSeparator(); + + addControlTableRows("control_table_contents_editing.xml"); + addControlTableSeparator(); + + addControlTableRows("control_table_contents_media.xml"); + } + else + { + LL_WARNS("Preferences") << "Unimplemented mode" << LL_ENDL; + } + + // explicit update to make sure table is ready for llsearchableui + pControlsTable->updateColumns(); + + // Searchable columns were removed and readded, mark searchables for an update + // Note: at the moment tables/lists lack proper llsearchableui support + LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); + if (instance) + { + instance->updateSearchableItems(); + } +} + +void LLPanelPreferenceControls::updateTable() +{ + mEditingControl.clear(); + std::vector list = pControlsTable->getAllData(); + for (S32 i = 0; i < list.size(); ++i) + { + std::string control = list[i]->getValue(); + if (!control.empty()) + { + LLScrollListCell* cell = NULL; + + S32 num_columns = pControlsTable->getNumColumns(); + for (S32 col = 1; col < num_columns; col++) + { + cell = list[i]->getColumn(col); + cell->setValue(mConflictHandler[mEditingMode].getControlString(control, col - 1)); + } + } + } + pControlsTable->deselectAllItems(); +} + +void LLPanelPreferenceControls::apply() +{ + for (U32 i = 0; i < LLKeyConflictHandler::MODE_COUNT - 1; ++i) + { + if (mConflictHandler[i].hasUnsavedChanges()) + { + mConflictHandler[i].saveToSettings(); + } + } +} + +void LLPanelPreferenceControls::cancel(const std::vector settings_to_skip) +{ + for (U32 i = 0; i < LLKeyConflictHandler::MODE_COUNT - 1; ++i) + { + if (mConflictHandler[i].hasUnsavedChanges()) + { + mConflictHandler[i].clear(); + if (mEditingMode == i) + { + // cancel() can be called either when preferences floater closes + // or when child floater closes (like advanced graphical settings) + // in which case we need to clear and repopulate table + regenerateControls(); + } + } + } +} + +void LLPanelPreferenceControls::saveSettings() +{ + for (U32 i = 0; i < LLKeyConflictHandler::MODE_COUNT - 1; ++i) + { + if (mConflictHandler[i].hasUnsavedChanges()) + { + mConflictHandler[i].saveToSettings(); + mConflictHandler[i].clear(); + } + } + + S32 mode = pKeyModeBox->getValue().asInteger(); + if (mConflictHandler[mode].empty() || pControlsTable->isEmpty()) + { + regenerateControls(); + } +} + +void LLPanelPreferenceControls::resetDirtyChilds() +{ + regenerateControls(); +} + +void LLPanelPreferenceControls::onListCommit() +{ + LLScrollListItem* item = pControlsTable->getFirstSelected(); + if (item == NULL) + { + return; + } + + std::string control = item->getValue(); + + if (control.empty()) + { + pControlsTable->deselectAllItems(); + return; + } + + if (!mConflictHandler[mEditingMode].canAssignControl(control)) + { + pControlsTable->deselectAllItems(); + return; + } + + S32 cell_ind = item->getSelectedCell(); + if (cell_ind <= 0) + { + pControlsTable->deselectAllItems(); + return; + } + + // List does not tell us what cell was clicked, so we have to figure it out manually, but + // fresh mouse coordinates are not yet accessible during onCommit() and there are other issues, + // so we cheat: remember item user clicked at, trigger 'key dialog' on hover that comes next, + // use coordinates from hover to calculate cell + + LLScrollListCell* cell = item->getColumn(cell_ind); + if (cell) + { + LLSetKeyBindDialog* dialog = LLFloaterReg::getTypedInstance("keybind_dialog", LLSD()); + if (dialog) + { + mEditingControl = control; + mEditingColumn = cell_ind; + dialog->setParent(this, pControlsTable, DEFAULT_KEY_FILTER); + + LLFloater* root_floater = gFloaterView->getParentFloater(this); + if (root_floater) + root_floater->addDependentFloater(dialog); + dialog->openFloater(); + dialog->setFocus(true); + } + } + else + { + pControlsTable->deselectAllItems(); + } +} + +void LLPanelPreferenceControls::onModeCommit() +{ + mEditingMode = pKeyModeBox->getValue().asInteger(); + if (mConflictHandler[mEditingMode].empty()) + { + // opening for first time + mConflictHandler[mEditingMode].loadFromSettings((LLKeyConflictHandler::ESourceMode)mEditingMode); + } + populateControlTable(); +} + +void LLPanelPreferenceControls::onRestoreDefaultsBtn() +{ + LLNotificationsUtil::add("PreferenceControlsDefaults", LLSD(), LLSD(), boost::bind(&LLPanelPreferenceControls::onRestoreDefaultsResponse, this, _1, _2)); +} + +void LLPanelPreferenceControls::onRestoreDefaultsResponse(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + switch(option) + { + case 0: // All + for (U32 i = 0; i < LLKeyConflictHandler::MODE_COUNT - 1; ++i) + { + mConflictHandler[i].resetToDefaults(); + // Apply changes to viewer as 'temporary' + mConflictHandler[i].saveToSettings(true); + + // notify comboboxes in move&view about potential change + LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); + if (instance) + { + instance->updateClickActionViews(); + } + } + + updateTable(); + break; + case 1: // Current + mConflictHandler[mEditingMode].resetToDefaults(); + // Apply changes to viewer as 'temporary' + mConflictHandler[mEditingMode].saveToSettings(true); + + if (mEditingMode == LLKeyConflictHandler::MODE_THIRD_PERSON) + { + // notify comboboxes in move&view about potential change + LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); + if (instance) + { + instance->updateClickActionViews(); + } + } + + updateTable(); + break; + case 2: // Cancel + default: + //exit; + break; + } +} + +// Bypass to let Move & view read values without need to create own key binding handler +// Assumes third person view +// Might be better idea to just move whole mConflictHandler into LLFloaterPreference +bool LLPanelPreferenceControls::canKeyBindHandle(const std::string &control, EMouseClickType click, KEY key, MASK mask) +{ + S32 mode = LLKeyConflictHandler::MODE_THIRD_PERSON; + if (mConflictHandler[mode].empty()) + { + // opening for first time + mConflictHandler[mode].loadFromSettings(LLKeyConflictHandler::MODE_THIRD_PERSON); + } + + return mConflictHandler[mode].canHandleControl(control, click, key, mask); +} + +// Bypass to let Move & view modify values without need to create own key binding handler +// Assumes third person view +// Might be better idea to just move whole mConflictHandler into LLFloaterPreference +void LLPanelPreferenceControls::setKeyBind(const std::string &control, EMouseClickType click, KEY key, MASK mask, bool set) +{ + S32 mode = LLKeyConflictHandler::MODE_THIRD_PERSON; + if (mConflictHandler[mode].empty()) + { + // opening for first time + mConflictHandler[mode].loadFromSettings(LLKeyConflictHandler::MODE_THIRD_PERSON); + } + + if (!mConflictHandler[mode].canAssignControl(mEditingControl)) + { + return; + } + + bool already_recorded = mConflictHandler[mode].canHandleControl(control, click, key, mask); + if (set) + { + if (already_recorded) + { + // nothing to do + return; + } + + // find free spot to add data, if no free spot, assign to first + S32 index = 0; + for (S32 i = 0; i < 3; i++) + { + if (mConflictHandler[mode].getControl(control, i).isEmpty()) + { + index = i; + break; + } + } + // At the moment 'ignore_mask' mask is mostly ignored, a placeholder + // Todo: implement it since it's preferable for things like teleport to match + // mask exactly but for things like running to ignore additional masks + // Ideally this needs representation in keybindings UI + bool ignore_mask = true; + mConflictHandler[mode].registerControl(control, index, click, key, mask, ignore_mask); + } + else if (!set) + { + if (!already_recorded) + { + // nothing to do + return; + } + + // find specific control and reset it + for (S32 i = 0; i < 3; i++) + { + LLKeyData data = mConflictHandler[mode].getControl(control, i); + if (data.mMouse == click && data.mKey == key && data.mMask == mask) + { + mConflictHandler[mode].clearControl(control, i); + } + } + } +} + +void LLPanelPreferenceControls::updateAndApply() +{ + S32 mode = LLKeyConflictHandler::MODE_THIRD_PERSON; + mConflictHandler[mode].saveToSettings(true); + updateTable(); +} + +// from LLSetKeybindDialog's interface +bool LLPanelPreferenceControls::onSetKeyBind(EMouseClickType click, KEY key, MASK mask, bool all_modes) +{ + if (!mConflictHandler[mEditingMode].canAssignControl(mEditingControl)) + { + return true; + } + + if ( mEditingColumn > 0) + { + if (all_modes) + { + for (U32 i = 0; i < LLKeyConflictHandler::MODE_COUNT - 1; ++i) + { + if (mConflictHandler[i].empty()) + { + mConflictHandler[i].loadFromSettings((LLKeyConflictHandler::ESourceMode)i); + } + mConflictHandler[i].registerControl(mEditingControl, mEditingColumn - 1, click, key, mask, true); + // Apply changes to viewer as 'temporary' + mConflictHandler[i].saveToSettings(true); + } + } + else + { + mConflictHandler[mEditingMode].registerControl(mEditingControl, mEditingColumn - 1, click, key, mask, true); + // Apply changes to viewer as 'temporary' + mConflictHandler[mEditingMode].saveToSettings(true); + } + } + + updateTable(); + + if ((mEditingMode == LLKeyConflictHandler::MODE_THIRD_PERSON || all_modes) + && (mEditingControl == "walk_to" + || mEditingControl == "teleport_to" + || click == CLICK_LEFT + || click == CLICK_DOUBLELEFT)) + { + // notify comboboxes in move&view about potential change + LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); + if (instance) + { + instance->updateClickActionViews(); + } + } + + return true; +} + +void LLPanelPreferenceControls::onDefaultKeyBind(bool all_modes) +{ + if (!mConflictHandler[mEditingMode].canAssignControl(mEditingControl)) + { + return; + } + + if (mEditingColumn > 0) + { + if (all_modes) + { + for (U32 i = 0; i < LLKeyConflictHandler::MODE_COUNT - 1; ++i) + { + if (mConflictHandler[i].empty()) + { + mConflictHandler[i].loadFromSettings((LLKeyConflictHandler::ESourceMode)i); + } + mConflictHandler[i].resetToDefault(mEditingControl, mEditingColumn - 1); + // Apply changes to viewer as 'temporary' + mConflictHandler[i].saveToSettings(true); + } + } + else + { + mConflictHandler[mEditingMode].resetToDefault(mEditingControl, mEditingColumn - 1); + // Apply changes to viewer as 'temporary' + mConflictHandler[mEditingMode].saveToSettings(true); + } + } + updateTable(); + + if (mEditingMode == LLKeyConflictHandler::MODE_THIRD_PERSON || all_modes) + { + // notify comboboxes in move&view about potential change + LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); + if (instance) + { + instance->updateClickActionViews(); + } + } +} + +void LLPanelPreferenceControls::onCancelKeyBind() +{ + pControlsTable->deselectAllItems(); +} + +LLFloaterPreferenceProxy::LLFloaterPreferenceProxy(const LLSD& key) + : LLFloater(key), + mSocksSettingsDirty(false) +{ + mCommitCallbackRegistrar.add("Proxy.OK", boost::bind(&LLFloaterPreferenceProxy::onBtnOk, this)); + mCommitCallbackRegistrar.add("Proxy.Cancel", boost::bind(&LLFloaterPreferenceProxy::onBtnCancel, this)); + mCommitCallbackRegistrar.add("Proxy.Change", boost::bind(&LLFloaterPreferenceProxy::onChangeSocksSettings, this)); +} + +LLFloaterPreferenceProxy::~LLFloaterPreferenceProxy() +{ +} + +bool LLFloaterPreferenceProxy::postBuild() +{ + LLRadioGroup* socksAuth = getChild("socks5_auth_type"); + if (!socksAuth) + { + return false; + } + if (socksAuth->getSelectedValue().asString() == "None") + { + getChild("socks5_username")->setEnabled(false); + getChild("socks5_password")->setEnabled(false); + } + else + { + // Populate the SOCKS 5 credential fields with protected values. + LLPointer socks_cred = gSecAPIHandler->loadCredential("SOCKS5"); + getChild("socks5_username")->setValue(socks_cred->getIdentifier()["username"].asString()); + getChild("socks5_password")->setValue(socks_cred->getAuthenticator()["creds"].asString()); + } + + return true; +} + +void LLFloaterPreferenceProxy::onOpen(const LLSD& key) +{ + saveSettings(); +} + +void LLFloaterPreferenceProxy::onClose(bool app_quitting) +{ + if(app_quitting) + { + cancel(); + } + + if (mSocksSettingsDirty) + { + + // If the user plays with the Socks proxy settings after login, it's only fair we let them know + // it will not be updated until next restart. + if (LLStartUp::getStartupState()>STATE_LOGIN_WAIT) + { + LLNotifications::instance().add("ChangeProxySettings", LLSD(), LLSD()); + mSocksSettingsDirty = false; // we have notified the user now be quiet again + } + } +} + +void LLFloaterPreferenceProxy::saveSettings() +{ + // Save the value of all controls in the hierarchy + mSavedValues.clear(); + std::list view_stack; + view_stack.push_back(this); + while(!view_stack.empty()) + { + // Process view on top of the stack + LLView* curview = view_stack.front(); + view_stack.pop_front(); + + LLUICtrl* ctrl = dynamic_cast(curview); + if (ctrl) + { + LLControlVariable* control = ctrl->getControlVariable(); + if (control) + { + mSavedValues[control] = control->getValue(); + } + } + + // Push children onto the end of the work stack + for (child_list_t::const_iterator iter = curview->getChildList()->begin(); + iter != curview->getChildList()->end(); ++iter) + { + view_stack.push_back(*iter); + } + } +} + +void LLFloaterPreferenceProxy::onBtnOk() +{ + // commit any outstanding text entry + if (hasFocus()) + { + LLUICtrl* cur_focus = dynamic_cast(gFocusMgr.getKeyboardFocus()); + if (cur_focus && cur_focus->acceptsTextInput()) + { + cur_focus->onCommit(); + } + } + + // Save SOCKS proxy credentials securely if password auth is enabled + LLRadioGroup* socksAuth = getChild("socks5_auth_type"); + if (socksAuth->getSelectedValue().asString() == "UserPass") + { + LLSD socks_id = LLSD::emptyMap(); + socks_id["type"] = "SOCKS5"; + socks_id["username"] = getChild("socks5_username")->getValue().asString(); + + LLSD socks_authenticator = LLSD::emptyMap(); + socks_authenticator["type"] = "SOCKS5"; + socks_authenticator["creds"] = getChild("socks5_password")->getValue().asString(); + + // Using "SOCKS5" as the "grid" argument since the same proxy + // settings will be used for all grids and because there is no + // way to specify the type of credential. + LLPointer socks_cred = gSecAPIHandler->createCredential("SOCKS5", socks_id, socks_authenticator); + gSecAPIHandler->saveCredential(socks_cred, true); + } + else + { + // Clear SOCKS5 credentials since they are no longer needed. + LLPointer socks_cred = new LLCredential("SOCKS5"); + gSecAPIHandler->deleteCredential(socks_cred); + } + + closeFloater(false); +} + +void LLFloaterPreferenceProxy::onBtnCancel() +{ + if (hasFocus()) + { + LLUICtrl* cur_focus = dynamic_cast(gFocusMgr.getKeyboardFocus()); + if (cur_focus && cur_focus->acceptsTextInput()) + { + cur_focus->onCommit(); + } + refresh(); + } + + cancel(); +} + +void LLFloaterPreferenceProxy::onClickCloseBtn(bool app_quitting) +{ + cancel(); +} + +void LLFloaterPreferenceProxy::cancel() +{ + + for (control_values_map_t::iterator iter = mSavedValues.begin(); + iter != mSavedValues.end(); ++iter) + { + LLControlVariable* control = iter->first; + LLSD ctrl_value = iter->second; + control->set(ctrl_value); + } + mSocksSettingsDirty = false; + closeFloater(); +} + +void LLFloaterPreferenceProxy::onChangeSocksSettings() +{ + mSocksSettingsDirty = true; + + LLRadioGroup* socksAuth = getChild("socks5_auth_type"); + if (socksAuth->getSelectedValue().asString() == "None") + { + getChild("socks5_username")->setEnabled(false); + getChild("socks5_password")->setEnabled(false); + } + else + { + getChild("socks5_username")->setEnabled(true); + getChild("socks5_password")->setEnabled(true); + } + + // Check for invalid states for the other HTTP proxy radio + LLRadioGroup* otherHttpProxy = getChild("other_http_proxy_type"); + if ((otherHttpProxy->getSelectedValue().asString() == "Socks" && + !getChild("socks_proxy_enabled")->get())||( + otherHttpProxy->getSelectedValue().asString() == "Web" && + !getChild("web_proxy_enabled")->get())) + { + otherHttpProxy->selectFirstItem(); + } + +} + +void LLFloaterPreference::onUpdateFilterTerm(bool force) +{ + LLWString seachValue = utf8str_to_wstring(mFilterEdit->getValue()); + LLWStringUtil::toLower(seachValue); + + if (!mSearchData || (mSearchData->mLastFilter == seachValue && !force)) + return; + + if (mSearchDataDirty) + { + // Data exists, but is obsolete, regenerate + collectSearchableItems(); + } + + mSearchData->mLastFilter = seachValue; + + if (!mSearchData->mRootTab) + return; + + mSearchData->mRootTab->hightlightAndHide( seachValue ); + filterIgnorableNotifications(); + + if (LLTabContainer* pRoot = getChild("pref core")) + pRoot->selectFirstTab(); +} + +void LLFloaterPreference::filterIgnorableNotifications() +{ + bool visible = getChildRef("enabled_popups").highlightMatchingItems(mFilterEdit->getValue()); + visible |= getChildRef("disabled_popups").highlightMatchingItems(mFilterEdit->getValue()); + + if (visible) + { + getChildRef("pref core").setTabVisibility( getChild("msgs"), true ); + } +} + +void collectChildren( LLView const *aView, ll::prefs::PanelDataPtr aParentPanel, ll::prefs::TabContainerDataPtr aParentTabContainer ) +{ + if (!aView) + return; + + llassert_always(aParentPanel || aParentTabContainer); + + for (LLView* pView : *aView->getChildList()) + { + if (!pView) + continue; + + ll::prefs::PanelDataPtr pCurPanelData = aParentPanel; + ll::prefs::TabContainerDataPtr pCurTabContainer = aParentTabContainer; + + LLPanel const *pPanel = dynamic_cast(pView); + LLTabContainer const *pTabContainer = dynamic_cast(pView); + ll::ui::SearchableControl const *pSCtrl = dynamic_cast( pView ); + + if (pTabContainer) + { + pCurPanelData.reset(); + + pCurTabContainer = ll::prefs::TabContainerDataPtr(new ll::prefs::TabContainerData); + pCurTabContainer->mTabContainer = const_cast< LLTabContainer *>(pTabContainer); + pCurTabContainer->mLabel = pTabContainer->getLabel(); + pCurTabContainer->mPanel = 0; + + if (aParentPanel) + aParentPanel->mChildPanel.push_back(pCurTabContainer); + if (aParentTabContainer) + aParentTabContainer->mChildPanel.push_back(pCurTabContainer); + } + else if (pPanel) + { + pCurTabContainer.reset(); + + pCurPanelData = ll::prefs::PanelDataPtr(new ll::prefs::PanelData); + pCurPanelData->mPanel = pPanel; + pCurPanelData->mLabel = pPanel->getLabel(); + + llassert_always( aParentPanel || aParentTabContainer ); + + if (aParentTabContainer) + aParentTabContainer->mChildPanel.push_back(pCurPanelData); + else if (aParentPanel) + aParentPanel->mChildPanel.push_back(pCurPanelData); + } + else if (pSCtrl && pSCtrl->getSearchText().size()) + { + ll::prefs::SearchableItemPtr item = ll::prefs::SearchableItemPtr(new ll::prefs::SearchableItem()); + item->mView = pView; + item->mCtrl = pSCtrl; + + item->mLabel = utf8str_to_wstring(pSCtrl->getSearchText()); + LLWStringUtil::toLower(item->mLabel); + + llassert_always(aParentPanel || aParentTabContainer); + + if (aParentPanel) + aParentPanel->mChildren.push_back(item); + if (aParentTabContainer) + aParentTabContainer->mChildren.push_back(item); + } + collectChildren(pView, pCurPanelData, pCurTabContainer); + } +} + +void LLFloaterPreference::collectSearchableItems() +{ + mSearchData.reset( nullptr ); + LLTabContainer *pRoot = getChild< LLTabContainer >( "pref core" ); + if( mFilterEdit && pRoot ) + { + mSearchData.reset(new ll::prefs::SearchData() ); + + ll::prefs::TabContainerDataPtr pRootTabcontainer = ll::prefs::TabContainerDataPtr( new ll::prefs::TabContainerData ); + pRootTabcontainer->mTabContainer = pRoot; + pRootTabcontainer->mLabel = pRoot->getLabel(); + mSearchData->mRootTab = pRootTabcontainer; + + collectChildren( this, ll::prefs::PanelDataPtr(), pRootTabcontainer ); + } + mSearchDataDirty = false; +} + +void LLFloaterPreference::saveIgnoredNotifications() +{ + for (LLNotifications::TemplateMap::const_iterator iter = LLNotifications::instance().templatesBegin(); + iter != LLNotifications::instance().templatesEnd(); + ++iter) + { + LLNotificationTemplatePtr templatep = iter->second; + LLNotificationFormPtr formp = templatep->mForm; + + LLNotificationForm::EIgnoreType ignore = formp->getIgnoreType(); + if (ignore <= LLNotificationForm::IGNORE_NO) + continue; + + mIgnorableNotifs[templatep->mName] = !formp->getIgnored(); + } +} + +void LLFloaterPreference::restoreIgnoredNotifications() +{ + for (std::map::iterator it = mIgnorableNotifs.begin(); it != mIgnorableNotifs.end(); ++it) + { + LLUI::getInstance()->mSettingGroups["ignores"]->setBOOL(it->first, it->second); + } +} diff --git a/indra/newview/llfloaterpreference.h b/indra/newview/llfloaterpreference.h index 1a4e156a87..c8b98d8e1b 100644 --- a/indra/newview/llfloaterpreference.h +++ b/indra/newview/llfloaterpreference.h @@ -1,404 +1,404 @@ -/** - * @file llfloaterpreference.h - * @brief LLPreferenceCore class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/* - * App-wide preferences. Note that these are not per-user, - * because we need to load many preferences before we have - * a login name. - */ - -#ifndef LL_LLFLOATERPREFERENCE_H -#define LL_LLFLOATERPREFERENCE_H - -#include "llfloater.h" -#include "llavatarpropertiesprocessor.h" -#include "llconversationlog.h" -#include "llsearcheditor.h" -#include "llsetkeybinddialog.h" -#include "llkeyconflict.h" - -class LLConversationLogObserver; -class LLPanelPreference; -class LLPanelLCD; -class LLPanelDebug; -class LLMessageSystem; -class LLComboBox; -class LLScrollListCtrl; -class LLScrollListCell; -class LLSliderCtrl; -class LLSD; -class LLTextBox; - -namespace ll -{ - namespace prefs - { - struct SearchData; - } -} - -typedef std::map notifications_map; - -typedef enum - { - GS_LOW_GRAPHICS, - GS_MID_GRAPHICS, - GS_HIGH_GRAPHICS, - GS_ULTRA_GRAPHICS - - } EGraphicsSettings; - -// Floater to control preferences (display, audio, bandwidth, general. -class LLFloaterPreference : public LLFloater, public LLAvatarPropertiesObserver, public LLConversationLogObserver -{ -public: - LLFloaterPreference(const LLSD& key); - ~LLFloaterPreference(); - - void apply(); - void cancel(const std::vector settings_to_skip = {}); - /*virtual*/ void draw(); - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); - /*virtual*/ void onClose(bool app_quitting); - /*virtual*/ void changed(); - /*virtual*/ void changed(const LLUUID& session_id, U32 mask) {}; - - // static data update, called from message handler - static void updateUserInfo(const std::string& visibility); - - // refresh all the graphics preferences menus - static void refreshEnabledGraphics(); - - // translate user's do not disturb response message according to current locale if message is default, otherwise do nothing - static void initDoNotDisturbResponse(); - - // update Show Favorites checkbox - static void updateShowFavoritesCheckbox(bool val); - - void processProperties( void* pData, EAvatarProcessorType type ); - void saveAvatarProperties( void ); - static void saveAvatarPropertiesCoro(const std::string url, bool allow_publish); - void selectPrivacyPanel(); - void selectChatPanel(); - void getControlNames(std::vector& names); - // updates click/double-click action controls depending on values from settings.xml - void updateClickActionViews(); - void updateSearchableItems(); - - void onBtnOK(const LLSD& userdata); - void onBtnCancel(const LLSD& userdata); - -protected: - - void onClickClearCache(); // Clear viewer texture cache, file cache on next startup - void onClickBrowserClearCache(); // Clear web history and caches as well as viewer caches above - void onLanguageChange(); - void onNotificationsChange(const std::string& OptionName); - void onNameTagOpacityChange(const LLSD& newvalue); - - // set value of "DoNotDisturbResponseChanged" in account settings depending on whether do not disturb response - // string differs from default after user changes. - void onDoNotDisturbResponseChanged(); - // if the custom settings box is clicked - void onChangeCustom(); - void updateMeterText(LLUICtrl* ctrl); - // callback for defaults - void setHardwareDefaults(); - void setRecommended(); - // callback for when client modifies a render option - void onRenderOptionEnable(); - // callback for when client turns on impostors - void onAvatarImpostorsEnable(); - - // callback for commit in the "Single click on land" and "Double click on land" comboboxes. - void onClickActionChange(); - // updates click/double-click action keybindngs depending on view values - void updateClickActionControls(); - - void onAtmosShaderChange(); - -public: - // This function squirrels away the current values of the controls so that - // cancel() can restore them. - void saveSettings(); - - void saveIgnoredNotifications(); - void restoreIgnoredNotifications(); - - void setCacheLocation(const LLStringExplicit& location); - - void onClickSetCache(); - void changeCachePath(const std::vector& filenames, std::string proposed_name); - void onClickResetCache(); - void onClickSkin(LLUICtrl* ctrl,const LLSD& userdata); - void onSelectSkin(); - void onClickSetSounds(); - void onClickEnablePopup(); - void onClickDisablePopup(); - void resetAllIgnored(); - void setAllIgnored(); - void onClickLogPath(); - void changeLogPath(const std::vector& filenames, std::string proposed_name); - bool moveTranscriptsAndLog(); - void setPersonalInfo(const std::string& visibility); - void refreshEnabledState(); - void onCommitWindowedMode(); - void refresh(); // Refresh enable/disable - // if the quality radio buttons are changed - void onChangeQuality(const LLSD& data); - - void refreshUI(); - - void onChangeMaturity(); - void onChangeComplexityMode(const LLSD& newvalue); - void onChangeModelFolder(); - void onChangePBRFolder(); - void onChangeTextureFolder(); - void onChangeSoundFolder(); - void onChangeAnimationFolder(); - void onClickBlockList(); - void onClickProxySettings(); - void onClickTranslationSettings(); - void onClickPermsDefault(); - void onClickRememberedUsernames(); - void onClickAutoReplace(); - void onClickSpellChecker(); - void onClickRenderExceptions(); - void onClickAutoAdjustments(); - void onClickAdvanced(); - void applyUIColor(LLUICtrl* ctrl, const LLSD& param); - void getUIColor(LLUICtrl* ctrl, const LLSD& param); - void onLogChatHistorySaved(); - void buildPopupLists(); - static void refreshSkin(void* data); - void selectPanel(const LLSD& name); - void saveGraphicsPreset(std::string& preset); - - void setRecommendedSettings(); - void resetAutotuneSettings(); - -private: - - void onDeleteTranscripts(); - void onDeleteTranscriptsResponse(const LLSD& notification, const LLSD& response); - void updateDeleteTranscriptsButton(); - void updateMaxComplexity(); - void updateComplexityText(); - static bool loadFromFilename(const std::string& filename, std::map &label_map); - - static std::string sSkin; - notifications_map mNotificationOptions; - bool mGotPersonalInfo; - bool mLanguageChanged; - bool mAvatarDataInitialized; - std::string mPriorInstantMessageLogPath; - - bool mOriginalHideOnlineStatus; - std::string mDirectoryVisibility; - - bool mAllowPublish; // Allow showing agent in search - std::string mSavedCameraPreset; - std::string mSavedGraphicsPreset; - LOG_CLASS(LLFloaterPreference); - - LLSearchEditor *mFilterEdit; - std::unique_ptr< ll::prefs::SearchData > mSearchData; - bool mSearchDataDirty; - - boost::signals2::connection mComplexityChangedSignal; - - void onUpdateFilterTerm( bool force = false ); - void collectSearchableItems(); - void filterIgnorableNotifications(); - - std::map mIgnorableNotifs; -}; - -class LLPanelPreference : public LLPanel -{ -public: - LLPanelPreference(); - /*virtual*/ bool postBuild(); - - virtual ~LLPanelPreference(); - - virtual void apply(); - virtual void cancel(const std::vector settings_to_skip = {}); - void setControlFalse(const LLSD& user_data); - virtual void setHardwareDefaults(); - - // Disables "Allow Media to auto play" check box only when both - // "Streaming Music" and "Media" are unchecked. Otherwise enables it. - void updateMediaAutoPlayCheckbox(LLUICtrl* ctrl); - - // This function squirrels away the current values of the controls so that - // cancel() can restore them. - virtual void saveSettings(); - - void deletePreset(const LLSD& user_data); - void savePreset(const LLSD& user_data); - void loadPreset(const LLSD& user_data); - - class Updater; - -protected: - typedef std::map control_values_map_t; - control_values_map_t mSavedValues; - -private: - //for "Only friends and groups can call or IM me" - static void showFriendsOnlyWarning(LLUICtrl*, const LLSD&); - //for "Allow Multiple Viewers" - static void showMultipleViewersWarning(LLUICtrl*, const LLSD&); - //for "Show my Favorite Landmarks at Login" - static void handleFavoritesOnLoginChanged(LLUICtrl* checkbox, const LLSD& value); - - static void toggleMuteWhenMinimized(); - typedef std::map string_color_map_t; - string_color_map_t mSavedColors; - - Updater* mBandWidthUpdater; - LOG_CLASS(LLPanelPreference); -}; - -class LLPanelPreferenceGraphics : public LLPanelPreference -{ -public: - bool postBuild(); - void draw(); - void cancel(const std::vector settings_to_skip = {}); - void saveSettings(); - void resetDirtyChilds(); - void setHardwareDefaults(); - void setPresetText(); - -protected: - bool hasDirtyChilds(); - -private: - void onPresetsListChange(); - LOG_CLASS(LLPanelPreferenceGraphics); -}; - -class LLPanelPreferenceControls : public LLPanelPreference, public LLKeyBindResponderInterface -{ - LOG_CLASS(LLPanelPreferenceControls); -public: - LLPanelPreferenceControls(); - virtual ~LLPanelPreferenceControls(); - - bool postBuild(); - - void apply(); - void cancel(const std::vector settings_to_skip = {}); - void saveSettings(); - void resetDirtyChilds(); - - void onListCommit(); - void onModeCommit(); - void onRestoreDefaultsBtn(); - void onRestoreDefaultsResponse(const LLSD& notification, const LLSD& response); - - // Bypass to let Move & view read values without need to create own key binding handler - // Todo: consider a better way to share access to keybindings - bool canKeyBindHandle(const std::string &control, EMouseClickType click, KEY key, MASK mask); - // Bypasses to let Move & view modify values without need to create own key binding handler - void setKeyBind(const std::string &control, EMouseClickType click, KEY key, MASK mask, bool set /*set or reset*/ ); - void updateAndApply(); - - // from interface - /*virtual*/ bool onSetKeyBind(EMouseClickType click, KEY key, MASK mask, bool all_modes); - /*virtual*/ void onDefaultKeyBind(bool all_modes); - /*virtual*/ void onCancelKeyBind(); - -private: - // reloads settings, discards current changes, updates table - void regenerateControls(); - - // These fuctions do not clean previous content - bool addControlTableColumns(const std::string &filename); - bool addControlTableRows(const std::string &filename); - void addControlTableSeparator(); - - // Cleans content and then adds content from xml files according to current mEditingMode - void populateControlTable(); - - // Updates keybindings from storage to table - void updateTable(); - - LLScrollListCtrl* pControlsTable; - LLComboBox *pKeyModeBox; - LLKeyConflictHandler mConflictHandler[LLKeyConflictHandler::MODE_COUNT]; - std::string mEditingControl; - S32 mEditingColumn; - S32 mEditingMode; -}; - -class LLAvatarComplexityControls -{ - public: - static void updateMax(LLSliderCtrl* slider, LLTextBox* value_label, bool short_val = false); - static void setText(U32 value, LLTextBox* text_box, bool short_val = false); - static void updateMaxRenderTime(LLSliderCtrl* slider, LLTextBox* value_label, bool short_val = false); - static void setRenderTimeText(F32 value, LLTextBox* text_box, bool short_val = false); - static void setIndirectControls(); - static void setIndirectMaxNonImpostors(); - static void setIndirectMaxArc(); - LOG_CLASS(LLAvatarComplexityControls); -}; - -class LLFloaterPreferenceProxy : public LLFloater -{ -public: - LLFloaterPreferenceProxy(const LLSD& key); - ~LLFloaterPreferenceProxy(); - - /// show off our menu - static void show(); - void cancel(); - -protected: - bool postBuild(); - void onOpen(const LLSD& key); - void onClose(bool app_quitting); - void saveSettings(); - void onBtnOk(); - void onBtnCancel(); - void onClickCloseBtn(bool app_quitting = false); - - void onChangeSocksSettings(); - -private: - - bool mSocksSettingsDirty; - typedef std::map control_values_map_t; - control_values_map_t mSavedValues; - LOG_CLASS(LLFloaterPreferenceProxy); -}; - - -#endif // LL_LLPREFERENCEFLOATER_H +/** + * @file llfloaterpreference.h + * @brief LLPreferenceCore class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/* + * App-wide preferences. Note that these are not per-user, + * because we need to load many preferences before we have + * a login name. + */ + +#ifndef LL_LLFLOATERPREFERENCE_H +#define LL_LLFLOATERPREFERENCE_H + +#include "llfloater.h" +#include "llavatarpropertiesprocessor.h" +#include "llconversationlog.h" +#include "llsearcheditor.h" +#include "llsetkeybinddialog.h" +#include "llkeyconflict.h" + +class LLConversationLogObserver; +class LLPanelPreference; +class LLPanelLCD; +class LLPanelDebug; +class LLMessageSystem; +class LLComboBox; +class LLScrollListCtrl; +class LLScrollListCell; +class LLSliderCtrl; +class LLSD; +class LLTextBox; + +namespace ll +{ + namespace prefs + { + struct SearchData; + } +} + +typedef std::map notifications_map; + +typedef enum + { + GS_LOW_GRAPHICS, + GS_MID_GRAPHICS, + GS_HIGH_GRAPHICS, + GS_ULTRA_GRAPHICS + + } EGraphicsSettings; + +// Floater to control preferences (display, audio, bandwidth, general. +class LLFloaterPreference : public LLFloater, public LLAvatarPropertiesObserver, public LLConversationLogObserver +{ +public: + LLFloaterPreference(const LLSD& key); + ~LLFloaterPreference(); + + void apply(); + void cancel(const std::vector settings_to_skip = {}); + /*virtual*/ void draw(); + /*virtual*/ bool postBuild(); + /*virtual*/ void onOpen(const LLSD& key); + /*virtual*/ void onClose(bool app_quitting); + /*virtual*/ void changed(); + /*virtual*/ void changed(const LLUUID& session_id, U32 mask) {}; + + // static data update, called from message handler + static void updateUserInfo(const std::string& visibility); + + // refresh all the graphics preferences menus + static void refreshEnabledGraphics(); + + // translate user's do not disturb response message according to current locale if message is default, otherwise do nothing + static void initDoNotDisturbResponse(); + + // update Show Favorites checkbox + static void updateShowFavoritesCheckbox(bool val); + + void processProperties( void* pData, EAvatarProcessorType type ); + void saveAvatarProperties( void ); + static void saveAvatarPropertiesCoro(const std::string url, bool allow_publish); + void selectPrivacyPanel(); + void selectChatPanel(); + void getControlNames(std::vector& names); + // updates click/double-click action controls depending on values from settings.xml + void updateClickActionViews(); + void updateSearchableItems(); + + void onBtnOK(const LLSD& userdata); + void onBtnCancel(const LLSD& userdata); + +protected: + + void onClickClearCache(); // Clear viewer texture cache, file cache on next startup + void onClickBrowserClearCache(); // Clear web history and caches as well as viewer caches above + void onLanguageChange(); + void onNotificationsChange(const std::string& OptionName); + void onNameTagOpacityChange(const LLSD& newvalue); + + // set value of "DoNotDisturbResponseChanged" in account settings depending on whether do not disturb response + // string differs from default after user changes. + void onDoNotDisturbResponseChanged(); + // if the custom settings box is clicked + void onChangeCustom(); + void updateMeterText(LLUICtrl* ctrl); + // callback for defaults + void setHardwareDefaults(); + void setRecommended(); + // callback for when client modifies a render option + void onRenderOptionEnable(); + // callback for when client turns on impostors + void onAvatarImpostorsEnable(); + + // callback for commit in the "Single click on land" and "Double click on land" comboboxes. + void onClickActionChange(); + // updates click/double-click action keybindngs depending on view values + void updateClickActionControls(); + + void onAtmosShaderChange(); + +public: + // This function squirrels away the current values of the controls so that + // cancel() can restore them. + void saveSettings(); + + void saveIgnoredNotifications(); + void restoreIgnoredNotifications(); + + void setCacheLocation(const LLStringExplicit& location); + + void onClickSetCache(); + void changeCachePath(const std::vector& filenames, std::string proposed_name); + void onClickResetCache(); + void onClickSkin(LLUICtrl* ctrl,const LLSD& userdata); + void onSelectSkin(); + void onClickSetSounds(); + void onClickEnablePopup(); + void onClickDisablePopup(); + void resetAllIgnored(); + void setAllIgnored(); + void onClickLogPath(); + void changeLogPath(const std::vector& filenames, std::string proposed_name); + bool moveTranscriptsAndLog(); + void setPersonalInfo(const std::string& visibility); + void refreshEnabledState(); + void onCommitWindowedMode(); + void refresh(); // Refresh enable/disable + // if the quality radio buttons are changed + void onChangeQuality(const LLSD& data); + + void refreshUI(); + + void onChangeMaturity(); + void onChangeComplexityMode(const LLSD& newvalue); + void onChangeModelFolder(); + void onChangePBRFolder(); + void onChangeTextureFolder(); + void onChangeSoundFolder(); + void onChangeAnimationFolder(); + void onClickBlockList(); + void onClickProxySettings(); + void onClickTranslationSettings(); + void onClickPermsDefault(); + void onClickRememberedUsernames(); + void onClickAutoReplace(); + void onClickSpellChecker(); + void onClickRenderExceptions(); + void onClickAutoAdjustments(); + void onClickAdvanced(); + void applyUIColor(LLUICtrl* ctrl, const LLSD& param); + void getUIColor(LLUICtrl* ctrl, const LLSD& param); + void onLogChatHistorySaved(); + void buildPopupLists(); + static void refreshSkin(void* data); + void selectPanel(const LLSD& name); + void saveGraphicsPreset(std::string& preset); + + void setRecommendedSettings(); + void resetAutotuneSettings(); + +private: + + void onDeleteTranscripts(); + void onDeleteTranscriptsResponse(const LLSD& notification, const LLSD& response); + void updateDeleteTranscriptsButton(); + void updateMaxComplexity(); + void updateComplexityText(); + static bool loadFromFilename(const std::string& filename, std::map &label_map); + + static std::string sSkin; + notifications_map mNotificationOptions; + bool mGotPersonalInfo; + bool mLanguageChanged; + bool mAvatarDataInitialized; + std::string mPriorInstantMessageLogPath; + + bool mOriginalHideOnlineStatus; + std::string mDirectoryVisibility; + + bool mAllowPublish; // Allow showing agent in search + std::string mSavedCameraPreset; + std::string mSavedGraphicsPreset; + LOG_CLASS(LLFloaterPreference); + + LLSearchEditor *mFilterEdit; + std::unique_ptr< ll::prefs::SearchData > mSearchData; + bool mSearchDataDirty; + + boost::signals2::connection mComplexityChangedSignal; + + void onUpdateFilterTerm( bool force = false ); + void collectSearchableItems(); + void filterIgnorableNotifications(); + + std::map mIgnorableNotifs; +}; + +class LLPanelPreference : public LLPanel +{ +public: + LLPanelPreference(); + /*virtual*/ bool postBuild(); + + virtual ~LLPanelPreference(); + + virtual void apply(); + virtual void cancel(const std::vector settings_to_skip = {}); + void setControlFalse(const LLSD& user_data); + virtual void setHardwareDefaults(); + + // Disables "Allow Media to auto play" check box only when both + // "Streaming Music" and "Media" are unchecked. Otherwise enables it. + void updateMediaAutoPlayCheckbox(LLUICtrl* ctrl); + + // This function squirrels away the current values of the controls so that + // cancel() can restore them. + virtual void saveSettings(); + + void deletePreset(const LLSD& user_data); + void savePreset(const LLSD& user_data); + void loadPreset(const LLSD& user_data); + + class Updater; + +protected: + typedef std::map control_values_map_t; + control_values_map_t mSavedValues; + +private: + //for "Only friends and groups can call or IM me" + static void showFriendsOnlyWarning(LLUICtrl*, const LLSD&); + //for "Allow Multiple Viewers" + static void showMultipleViewersWarning(LLUICtrl*, const LLSD&); + //for "Show my Favorite Landmarks at Login" + static void handleFavoritesOnLoginChanged(LLUICtrl* checkbox, const LLSD& value); + + static void toggleMuteWhenMinimized(); + typedef std::map string_color_map_t; + string_color_map_t mSavedColors; + + Updater* mBandWidthUpdater; + LOG_CLASS(LLPanelPreference); +}; + +class LLPanelPreferenceGraphics : public LLPanelPreference +{ +public: + bool postBuild(); + void draw(); + void cancel(const std::vector settings_to_skip = {}); + void saveSettings(); + void resetDirtyChilds(); + void setHardwareDefaults(); + void setPresetText(); + +protected: + bool hasDirtyChilds(); + +private: + void onPresetsListChange(); + LOG_CLASS(LLPanelPreferenceGraphics); +}; + +class LLPanelPreferenceControls : public LLPanelPreference, public LLKeyBindResponderInterface +{ + LOG_CLASS(LLPanelPreferenceControls); +public: + LLPanelPreferenceControls(); + virtual ~LLPanelPreferenceControls(); + + bool postBuild(); + + void apply(); + void cancel(const std::vector settings_to_skip = {}); + void saveSettings(); + void resetDirtyChilds(); + + void onListCommit(); + void onModeCommit(); + void onRestoreDefaultsBtn(); + void onRestoreDefaultsResponse(const LLSD& notification, const LLSD& response); + + // Bypass to let Move & view read values without need to create own key binding handler + // Todo: consider a better way to share access to keybindings + bool canKeyBindHandle(const std::string &control, EMouseClickType click, KEY key, MASK mask); + // Bypasses to let Move & view modify values without need to create own key binding handler + void setKeyBind(const std::string &control, EMouseClickType click, KEY key, MASK mask, bool set /*set or reset*/ ); + void updateAndApply(); + + // from interface + /*virtual*/ bool onSetKeyBind(EMouseClickType click, KEY key, MASK mask, bool all_modes); + /*virtual*/ void onDefaultKeyBind(bool all_modes); + /*virtual*/ void onCancelKeyBind(); + +private: + // reloads settings, discards current changes, updates table + void regenerateControls(); + + // These fuctions do not clean previous content + bool addControlTableColumns(const std::string &filename); + bool addControlTableRows(const std::string &filename); + void addControlTableSeparator(); + + // Cleans content and then adds content from xml files according to current mEditingMode + void populateControlTable(); + + // Updates keybindings from storage to table + void updateTable(); + + LLScrollListCtrl* pControlsTable; + LLComboBox *pKeyModeBox; + LLKeyConflictHandler mConflictHandler[LLKeyConflictHandler::MODE_COUNT]; + std::string mEditingControl; + S32 mEditingColumn; + S32 mEditingMode; +}; + +class LLAvatarComplexityControls +{ + public: + static void updateMax(LLSliderCtrl* slider, LLTextBox* value_label, bool short_val = false); + static void setText(U32 value, LLTextBox* text_box, bool short_val = false); + static void updateMaxRenderTime(LLSliderCtrl* slider, LLTextBox* value_label, bool short_val = false); + static void setRenderTimeText(F32 value, LLTextBox* text_box, bool short_val = false); + static void setIndirectControls(); + static void setIndirectMaxNonImpostors(); + static void setIndirectMaxArc(); + LOG_CLASS(LLAvatarComplexityControls); +}; + +class LLFloaterPreferenceProxy : public LLFloater +{ +public: + LLFloaterPreferenceProxy(const LLSD& key); + ~LLFloaterPreferenceProxy(); + + /// show off our menu + static void show(); + void cancel(); + +protected: + bool postBuild(); + void onOpen(const LLSD& key); + void onClose(bool app_quitting); + void saveSettings(); + void onBtnOk(); + void onBtnCancel(); + void onClickCloseBtn(bool app_quitting = false); + + void onChangeSocksSettings(); + +private: + + bool mSocksSettingsDirty; + typedef std::map control_values_map_t; + control_values_map_t mSavedValues; + LOG_CLASS(LLFloaterPreferenceProxy); +}; + + +#endif // LL_LLPREFERENCEFLOATER_H diff --git a/indra/newview/llfloaterpreferencesgraphicsadvanced.cpp b/indra/newview/llfloaterpreferencesgraphicsadvanced.cpp index b497e84f8f..082a77d741 100644 --- a/indra/newview/llfloaterpreferencesgraphicsadvanced.cpp +++ b/indra/newview/llfloaterpreferencesgraphicsadvanced.cpp @@ -1,502 +1,502 @@ -/** - * @file llfloaterpreferencesgraphicsadvanced.cpp - * @brief floater for adjusting camera position - * - * $LicenseInfo:firstyear=2021&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2021, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llfloaterpreferencesgraphicsadvanced.h" - -#include "llcheckboxctrl.h" -#include "llcombobox.h" -#include "llfeaturemanager.h" -#include "llfloaterpreference.h" -#include "llfloaterreg.h" -#include "llnotificationsutil.h" -#include "llsliderctrl.h" -#include "lltextbox.h" -#include "lltrans.h" -#include "llviewershadermgr.h" -#include "llviewertexturelist.h" -#include "llvoavatar.h" -#include "pipeline.h" - - -LLFloaterPreferenceGraphicsAdvanced::LLFloaterPreferenceGraphicsAdvanced(const LLSD& key) - : LLFloater(key) -{ - mCommitCallbackRegistrar.add("Pref.RenderOptionUpdate", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::onRenderOptionEnable, this)); - mCommitCallbackRegistrar.add("Pref.UpdateIndirectMaxNonImpostors", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::updateMaxNonImpostors,this)); - mCommitCallbackRegistrar.add("Pref.UpdateIndirectMaxComplexity", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::updateMaxComplexity,this)); - - mCommitCallbackRegistrar.add("Pref.Cancel", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::onBtnCancel, this, _2)); - mCommitCallbackRegistrar.add("Pref.OK", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::onBtnOK, this, _2)); -} - -LLFloaterPreferenceGraphicsAdvanced::~LLFloaterPreferenceGraphicsAdvanced() -{ - mComplexityChangedSignal.disconnect(); - mComplexityModeChangedSignal.disconnect(); - mLODFactorChangedSignal.disconnect(); - mNumImpostorsChangedSignal.disconnect(); -} - -bool LLFloaterPreferenceGraphicsAdvanced::postBuild() -{ - // Don't do this on Mac as their braindead GL versioning - // sets this when 8x and 16x are indeed available - // -#if !LL_DARWIN - if (gGLManager.mIsIntel || gGLManager.mGLVersion < 3.f) - { //remove FSAA settings above "4x" - LLComboBox* combo = getChild("fsaa"); - combo->remove("8x"); - combo->remove("16x"); - } - - LLCheckBoxCtrl *use_HiDPI = getChild("use HiDPI"); - use_HiDPI->setVisible(false); -#endif - - mComplexityChangedSignal = gSavedSettings.getControl("RenderAvatarMaxComplexity")->getCommitSignal()->connect( - [this](LLControlVariable* control, const LLSD& new_val, const LLSD& old_val) - { - updateComplexityText(); - }); - mComplexityModeChangedSignal = gSavedSettings.getControl("RenderAvatarComplexityMode")->getSignal()->connect( - [this](LLControlVariable* control, const LLSD& new_val, const LLSD& old_val) - { - updateComplexityMode(new_val); - }); - mLODFactorChangedSignal = gSavedSettings.getControl("RenderVolumeLODFactor")->getCommitSignal()->connect( - [this](LLControlVariable* control, const LLSD& new_val, const LLSD& old_val) - { - updateObjectMeshDetailText(); - }); - mNumImpostorsChangedSignal = gSavedSettings.getControl("RenderAvatarMaxNonImpostors")->getSignal()->connect( - [this](LLControlVariable* control, const LLSD& new_val, const LLSD& old_val) - { - updateIndirectMaxNonImpostors(new_val); - }); - return true; -} - -void LLFloaterPreferenceGraphicsAdvanced::onOpen(const LLSD& key) -{ - refresh(); -} - -void LLFloaterPreferenceGraphicsAdvanced::onClickCloseBtn(bool app_quitting) -{ - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); - if (instance) - { - instance->cancel({"RenderQualityPerformance"}); - } - updateMaxComplexity(); -} - -void LLFloaterPreferenceGraphicsAdvanced::onRenderOptionEnable() -{ - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); - if (instance) - { - instance->refresh(); - } - - refreshEnabledGraphics(); -} - -void LLFloaterPreferenceGraphicsAdvanced::onAdvancedAtmosphericsEnable() -{ - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); - if (instance) - { - instance->refresh(); - } - - refreshEnabledGraphics(); -} - -void LLFloaterPreferenceGraphicsAdvanced::refresh() -{ - getChild("fsaa")->setValue((LLSD::Integer) gSavedSettings.getU32("RenderFSAASamples")); - - // sliders and their text boxes - // mPostProcess = gSavedSettings.getS32("RenderGlowResolutionPow"); - // slider text boxes - updateSliderText(getChild("ObjectMeshDetail", true), getChild("ObjectMeshDetailText", true)); - updateSliderText(getChild("FlexibleMeshDetail", true), getChild("FlexibleMeshDetailText", true)); - updateSliderText(getChild("TreeMeshDetail", true), getChild("TreeMeshDetailText", true)); - updateSliderText(getChild("AvatarMeshDetail", true), getChild("AvatarMeshDetailText", true)); - updateSliderText(getChild("AvatarPhysicsDetail", true), getChild("AvatarPhysicsDetailText", true)); - updateSliderText(getChild("TerrainMeshDetail", true), getChild("TerrainMeshDetailText", true)); - updateSliderText(getChild("RenderPostProcess", true), getChild("PostProcessText", true)); - updateSliderText(getChild("SkyMeshDetail", true), getChild("SkyMeshDetailText", true)); - updateSliderText(getChild("TerrainDetail", true), getChild("TerrainDetailText", true)); - LLAvatarComplexityControls::setIndirectControls(); - setMaxNonImpostorsText( - gSavedSettings.getU32("RenderAvatarMaxNonImpostors"), - getChild("IndirectMaxNonImpostorsText", true)); - LLAvatarComplexityControls::setText( - gSavedSettings.getU32("RenderAvatarMaxComplexity"), - getChild("IndirectMaxComplexityText", true)); - refreshEnabledState(); - - bool enable_complexity = gSavedSettings.getS32("RenderAvatarComplexityMode") != LLVOAvatar::AV_RENDER_ONLY_SHOW_FRIENDS; - getChild("IndirectMaxComplexity")->setEnabled(enable_complexity); - getChild("IndirectMaxNonImpostors")->setEnabled(enable_complexity); -} - -void LLFloaterPreferenceGraphicsAdvanced::refreshEnabledGraphics() -{ - refreshEnabledState(); -} - -void LLFloaterPreferenceGraphicsAdvanced::updateMaxComplexity() -{ - // Called when the IndirectMaxComplexity control changes - LLAvatarComplexityControls::updateMax( - getChild("IndirectMaxComplexity"), - getChild("IndirectMaxComplexityText")); -} - -void LLFloaterPreferenceGraphicsAdvanced::updateComplexityMode(const LLSD& newvalue) -{ - bool enable_complexity = newvalue.asInteger() != LLVOAvatar::AV_RENDER_ONLY_SHOW_FRIENDS; - getChild("IndirectMaxComplexity")->setEnabled(enable_complexity); - getChild("IndirectMaxNonImpostors")->setEnabled(enable_complexity); -} - -void LLFloaterPreferenceGraphicsAdvanced::updateComplexityText() -{ - LLAvatarComplexityControls::setText(gSavedSettings.getU32("RenderAvatarMaxComplexity"), - getChild("IndirectMaxComplexityText", true)); -} - -void LLFloaterPreferenceGraphicsAdvanced::updateObjectMeshDetailText() -{ - updateSliderText(getChild("ObjectMeshDetail", true), getChild("ObjectMeshDetailText", true)); -} - -void LLFloaterPreferenceGraphicsAdvanced::updateSliderText(LLSliderCtrl* ctrl, LLTextBox* text_box) -{ - if (text_box == NULL || ctrl== NULL) - return; - - // get range and points when text should change - F32 value = (F32)ctrl->getValue().asReal(); - F32 min = ctrl->getMinValue(); - F32 max = ctrl->getMaxValue(); - F32 range = max - min; - llassert(range > 0); - F32 midPoint = min + range / 3.0f; - F32 highPoint = min + (2.0f * range / 3.0f); - - // choose the right text - if (value < midPoint) - { - text_box->setText(LLTrans::getString("GraphicsQualityLow")); - } - else if (value < highPoint) - { - text_box->setText(LLTrans::getString("GraphicsQualityMid")); - } - else - { - text_box->setText(LLTrans::getString("GraphicsQualityHigh")); - } -} - -void LLFloaterPreferenceGraphicsAdvanced::updateMaxNonImpostors() -{ - // Called when the IndirectMaxNonImpostors control changes - // Responsible for fixing the slider label (IndirectMaxNonImpostorsText) and setting RenderAvatarMaxNonImpostors - LLSliderCtrl* ctrl = getChild("IndirectMaxNonImpostors",true); - U32 value = ctrl->getValue().asInteger(); - - if (0 == value || LLVOAvatar::NON_IMPOSTORS_MAX_SLIDER <= value) - { - value=0; - } - gSavedSettings.setU32("RenderAvatarMaxNonImpostors", value); - LLVOAvatar::updateImpostorRendering(value); // make it effective immediately - setMaxNonImpostorsText(value, getChild("IndirectMaxNonImpostorsText")); -} - -void LLFloaterPreferenceGraphicsAdvanced::updateIndirectMaxNonImpostors(const LLSD& newvalue) -{ - U32 value = newvalue.asInteger(); - if ((value != 0) && (value != gSavedSettings.getU32("IndirectMaxNonImpostors"))) - { - gSavedSettings.setU32("IndirectMaxNonImpostors", value); - setMaxNonImpostorsText(value, getChild("IndirectMaxNonImpostorsText")); - } -} - -void LLFloaterPreferenceGraphicsAdvanced::setMaxNonImpostorsText(U32 value, LLTextBox* text_box) -{ - if (0 == value) - { - text_box->setText(LLTrans::getString("no_limit")); - } - else - { - text_box->setText(llformat("%d", value)); - } -} - -void LLFloaterPreferenceGraphicsAdvanced::disableUnavailableSettings() -{ - LLComboBox* ctrl_reflections = getChild("Reflections"); - LLTextBox* reflections_text = getChild("ReflectionsText"); - LLCheckBoxCtrl* ctrl_avatar_vp = getChild("AvatarVertexProgram"); - LLCheckBoxCtrl* ctrl_avatar_cloth = getChild("AvatarCloth"); - LLCheckBoxCtrl* ctrl_wind_light = getChild("WindLightUseAtmosShaders"); - LLCheckBoxCtrl* ctrl_deferred = getChild("UseLightShaders"); - LLComboBox* ctrl_shadows = getChild("ShadowDetail"); - LLTextBox* shadows_text = getChild("RenderShadowDetailText"); - LLCheckBoxCtrl* ctrl_ssao = getChild("UseSSAO"); - LLCheckBoxCtrl* ctrl_dof = getChild("UseDoF"); - LLSliderCtrl* sky = getChild("SkyMeshDetail"); - LLTextBox* sky_text = getChild("SkyMeshDetailText"); - - // disabled windlight - if (!LLFeatureManager::getInstance()->isFeatureAvailable("WindLightUseAtmosShaders")) - { - ctrl_wind_light->setEnabled(false); - ctrl_wind_light->setValue(false); - - sky->setEnabled(false); - sky_text->setEnabled(false); - - //deferred needs windlight, disable deferred - ctrl_shadows->setEnabled(false); - ctrl_shadows->setValue(0); - shadows_text->setEnabled(false); - - ctrl_ssao->setEnabled(false); - ctrl_ssao->setValue(false); - - ctrl_dof->setEnabled(false); - ctrl_dof->setValue(false); - - ctrl_deferred->setEnabled(false); - ctrl_deferred->setValue(false); - } - - // disabled deferred - if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferred")) - { - ctrl_shadows->setEnabled(false); - ctrl_shadows->setValue(0); - shadows_text->setEnabled(false); - - ctrl_ssao->setEnabled(false); - ctrl_ssao->setValue(false); - - ctrl_dof->setEnabled(false); - ctrl_dof->setValue(false); - - ctrl_deferred->setEnabled(false); - ctrl_deferred->setValue(false); - } - - // disabled deferred SSAO - if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferredSSAO")) - { - ctrl_ssao->setEnabled(false); - ctrl_ssao->setValue(false); - } - - // disabled deferred shadows - if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderShadowDetail")) - { - ctrl_shadows->setEnabled(false); - ctrl_shadows->setValue(0); - shadows_text->setEnabled(false); - } - - // disabled reflections - if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderReflectionDetail")) - { - ctrl_reflections->setEnabled(false); - ctrl_reflections->setValue(false); - reflections_text->setEnabled(false); - } - - // disabled av - if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderAvatarVP")) - { - ctrl_avatar_vp->setEnabled(false); - ctrl_avatar_vp->setValue(false); - - ctrl_avatar_cloth->setEnabled(false); - ctrl_avatar_cloth->setValue(false); - - //deferred needs AvatarVP, disable deferred - ctrl_shadows->setEnabled(false); - ctrl_shadows->setValue(0); - shadows_text->setEnabled(false); - - ctrl_ssao->setEnabled(false); - ctrl_ssao->setValue(false); - - ctrl_dof->setEnabled(false); - ctrl_dof->setValue(false); - - ctrl_deferred->setEnabled(false); - ctrl_deferred->setValue(false); - } - - // disabled cloth - if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderAvatarCloth")) - { - ctrl_avatar_cloth->setEnabled(false); - ctrl_avatar_cloth->setValue(false); - } -} - -void LLFloaterPreferenceGraphicsAdvanced::refreshEnabledState() -{ - LLComboBox* ctrl_reflections = getChild("Reflections"); - LLTextBox* reflections_text = getChild("ReflectionsText"); - - // Reflections - bool reflections = LLCubeMap::sUseCubeMaps; - ctrl_reflections->setEnabled(reflections); - reflections_text->setEnabled(reflections); - - // Bump & Shiny - LLCheckBoxCtrl* bumpshiny_ctrl = getChild("BumpShiny"); - bool bumpshiny = LLCubeMap::sUseCubeMaps && LLFeatureManager::getInstance()->isFeatureAvailable("RenderObjectBump"); - bumpshiny_ctrl->setEnabled(bumpshiny); - - // Avatar Mode - // Enable Avatar Shaders - LLCheckBoxCtrl* ctrl_avatar_vp = getChild("AvatarVertexProgram"); - // Avatar Render Mode - LLCheckBoxCtrl* ctrl_avatar_cloth = getChild("AvatarCloth"); - - bool avatar_vp_enabled = LLFeatureManager::getInstance()->isFeatureAvailable("RenderAvatarVP"); - if (LLViewerShaderMgr::sInitialized) - { - S32 max_avatar_shader = LLViewerShaderMgr::instance()->mMaxAvatarShaderLevel; - avatar_vp_enabled = max_avatar_shader > 0; - } - - ctrl_avatar_vp->setEnabled(avatar_vp_enabled); - - ctrl_avatar_cloth->setEnabled(gSavedSettings.getBOOL("RenderAvatarVP")); - - // Vertex Shaders, Global Shader Enable - // SL-12594 Basic shaders are always enabled. DJH TODO clean up now-orphaned state handling code - LLSliderCtrl* terrain_detail = getChild("TerrainDetail"); // can be linked with control var - LLTextBox* terrain_text = getChild("TerrainDetailText"); - - terrain_detail->setEnabled(false); - terrain_text->setEnabled(false); - - // WindLight - //LLCheckBoxCtrl* ctrl_wind_light = getChild("WindLightUseAtmosShaders"); - //ctrl_wind_light->setEnabled(true); - LLSliderCtrl* sky = getChild("SkyMeshDetail"); - LLTextBox* sky_text = getChild("SkyMeshDetailText"); - sky->setEnabled(true); - sky_text->setEnabled(true); - - bool enabled = true; -#if 0 // deferred always on now - //Deferred/SSAO/Shadows - LLCheckBoxCtrl* ctrl_deferred = getChild("UseLightShaders"); - - enabled = LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferred") && - bumpshiny_ctrl && bumpshiny_ctrl->get() && - ctrl_wind_light->get(); - - ctrl_deferred->setEnabled(enabled); -#endif - - LLCheckBoxCtrl* ctrl_pbr = getChild("UsePBRShaders"); - - //PBR - ctrl_pbr->setEnabled(true); - - LLCheckBoxCtrl* ctrl_ssao = getChild("UseSSAO"); - LLCheckBoxCtrl* ctrl_dof = getChild("UseDoF"); - LLComboBox* ctrl_shadow = getChild("ShadowDetail"); - LLTextBox* shadow_text = getChild("RenderShadowDetailText"); - - // note, okay here to get from ctrl_deferred as it's twin, ctrl_deferred2 will alway match it - enabled = enabled && LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferredSSAO");// && ctrl_deferred->get(); - - //ctrl_deferred->set(gSavedSettings.getBOOL("RenderDeferred")); - - ctrl_ssao->setEnabled(enabled); - ctrl_dof->setEnabled(enabled); - - enabled = enabled && LLFeatureManager::getInstance()->isFeatureAvailable("RenderShadowDetail"); - - ctrl_shadow->setEnabled(enabled); - shadow_text->setEnabled(enabled); - - // Hardware settings - - if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderVBOEnable")) - { - getChildView("vbo")->setEnabled(false); - } - - if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderCompressTextures")) - { - getChildView("texture compression")->setEnabled(false); - } - - // if no windlight shaders, turn off nighttime brightness, gamma, and fog distance - LLUICtrl* gamma_ctrl = getChild("gamma"); - gamma_ctrl->setEnabled(!gPipeline.canUseWindLightShaders()); - getChildView("(brightness, lower is brighter)")->setEnabled(!gPipeline.canUseWindLightShaders()); - getChildView("fog")->setEnabled(!gPipeline.canUseWindLightShaders()); - getChildView("antialiasing restart")->setVisible(!LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferred")); - - // now turn off any features that are unavailable - disableUnavailableSettings(); -} - -void LLFloaterPreferenceGraphicsAdvanced::onBtnOK(const LLSD& userdata) -{ - LLFloaterPreference* instance = LLFloaterReg::getTypedInstance("preferences"); - if (instance) - { - instance->onBtnOK(userdata); - } -} - -void LLFloaterPreferenceGraphicsAdvanced::onBtnCancel(const LLSD& userdata) -{ - LLFloaterPreference* instance = LLFloaterReg::getTypedInstance("preferences"); - if (instance) - { - instance->onBtnCancel(userdata); - } -} +/** + * @file llfloaterpreferencesgraphicsadvanced.cpp + * @brief floater for adjusting camera position + * + * $LicenseInfo:firstyear=2021&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2021, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llfloaterpreferencesgraphicsadvanced.h" + +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "llfeaturemanager.h" +#include "llfloaterpreference.h" +#include "llfloaterreg.h" +#include "llnotificationsutil.h" +#include "llsliderctrl.h" +#include "lltextbox.h" +#include "lltrans.h" +#include "llviewershadermgr.h" +#include "llviewertexturelist.h" +#include "llvoavatar.h" +#include "pipeline.h" + + +LLFloaterPreferenceGraphicsAdvanced::LLFloaterPreferenceGraphicsAdvanced(const LLSD& key) + : LLFloater(key) +{ + mCommitCallbackRegistrar.add("Pref.RenderOptionUpdate", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::onRenderOptionEnable, this)); + mCommitCallbackRegistrar.add("Pref.UpdateIndirectMaxNonImpostors", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::updateMaxNonImpostors,this)); + mCommitCallbackRegistrar.add("Pref.UpdateIndirectMaxComplexity", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::updateMaxComplexity,this)); + + mCommitCallbackRegistrar.add("Pref.Cancel", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::onBtnCancel, this, _2)); + mCommitCallbackRegistrar.add("Pref.OK", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::onBtnOK, this, _2)); +} + +LLFloaterPreferenceGraphicsAdvanced::~LLFloaterPreferenceGraphicsAdvanced() +{ + mComplexityChangedSignal.disconnect(); + mComplexityModeChangedSignal.disconnect(); + mLODFactorChangedSignal.disconnect(); + mNumImpostorsChangedSignal.disconnect(); +} + +bool LLFloaterPreferenceGraphicsAdvanced::postBuild() +{ + // Don't do this on Mac as their braindead GL versioning + // sets this when 8x and 16x are indeed available + // +#if !LL_DARWIN + if (gGLManager.mIsIntel || gGLManager.mGLVersion < 3.f) + { //remove FSAA settings above "4x" + LLComboBox* combo = getChild("fsaa"); + combo->remove("8x"); + combo->remove("16x"); + } + + LLCheckBoxCtrl *use_HiDPI = getChild("use HiDPI"); + use_HiDPI->setVisible(false); +#endif + + mComplexityChangedSignal = gSavedSettings.getControl("RenderAvatarMaxComplexity")->getCommitSignal()->connect( + [this](LLControlVariable* control, const LLSD& new_val, const LLSD& old_val) + { + updateComplexityText(); + }); + mComplexityModeChangedSignal = gSavedSettings.getControl("RenderAvatarComplexityMode")->getSignal()->connect( + [this](LLControlVariable* control, const LLSD& new_val, const LLSD& old_val) + { + updateComplexityMode(new_val); + }); + mLODFactorChangedSignal = gSavedSettings.getControl("RenderVolumeLODFactor")->getCommitSignal()->connect( + [this](LLControlVariable* control, const LLSD& new_val, const LLSD& old_val) + { + updateObjectMeshDetailText(); + }); + mNumImpostorsChangedSignal = gSavedSettings.getControl("RenderAvatarMaxNonImpostors")->getSignal()->connect( + [this](LLControlVariable* control, const LLSD& new_val, const LLSD& old_val) + { + updateIndirectMaxNonImpostors(new_val); + }); + return true; +} + +void LLFloaterPreferenceGraphicsAdvanced::onOpen(const LLSD& key) +{ + refresh(); +} + +void LLFloaterPreferenceGraphicsAdvanced::onClickCloseBtn(bool app_quitting) +{ + LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); + if (instance) + { + instance->cancel({"RenderQualityPerformance"}); + } + updateMaxComplexity(); +} + +void LLFloaterPreferenceGraphicsAdvanced::onRenderOptionEnable() +{ + LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); + if (instance) + { + instance->refresh(); + } + + refreshEnabledGraphics(); +} + +void LLFloaterPreferenceGraphicsAdvanced::onAdvancedAtmosphericsEnable() +{ + LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); + if (instance) + { + instance->refresh(); + } + + refreshEnabledGraphics(); +} + +void LLFloaterPreferenceGraphicsAdvanced::refresh() +{ + getChild("fsaa")->setValue((LLSD::Integer) gSavedSettings.getU32("RenderFSAASamples")); + + // sliders and their text boxes + // mPostProcess = gSavedSettings.getS32("RenderGlowResolutionPow"); + // slider text boxes + updateSliderText(getChild("ObjectMeshDetail", true), getChild("ObjectMeshDetailText", true)); + updateSliderText(getChild("FlexibleMeshDetail", true), getChild("FlexibleMeshDetailText", true)); + updateSliderText(getChild("TreeMeshDetail", true), getChild("TreeMeshDetailText", true)); + updateSliderText(getChild("AvatarMeshDetail", true), getChild("AvatarMeshDetailText", true)); + updateSliderText(getChild("AvatarPhysicsDetail", true), getChild("AvatarPhysicsDetailText", true)); + updateSliderText(getChild("TerrainMeshDetail", true), getChild("TerrainMeshDetailText", true)); + updateSliderText(getChild("RenderPostProcess", true), getChild("PostProcessText", true)); + updateSliderText(getChild("SkyMeshDetail", true), getChild("SkyMeshDetailText", true)); + updateSliderText(getChild("TerrainDetail", true), getChild("TerrainDetailText", true)); + LLAvatarComplexityControls::setIndirectControls(); + setMaxNonImpostorsText( + gSavedSettings.getU32("RenderAvatarMaxNonImpostors"), + getChild("IndirectMaxNonImpostorsText", true)); + LLAvatarComplexityControls::setText( + gSavedSettings.getU32("RenderAvatarMaxComplexity"), + getChild("IndirectMaxComplexityText", true)); + refreshEnabledState(); + + bool enable_complexity = gSavedSettings.getS32("RenderAvatarComplexityMode") != LLVOAvatar::AV_RENDER_ONLY_SHOW_FRIENDS; + getChild("IndirectMaxComplexity")->setEnabled(enable_complexity); + getChild("IndirectMaxNonImpostors")->setEnabled(enable_complexity); +} + +void LLFloaterPreferenceGraphicsAdvanced::refreshEnabledGraphics() +{ + refreshEnabledState(); +} + +void LLFloaterPreferenceGraphicsAdvanced::updateMaxComplexity() +{ + // Called when the IndirectMaxComplexity control changes + LLAvatarComplexityControls::updateMax( + getChild("IndirectMaxComplexity"), + getChild("IndirectMaxComplexityText")); +} + +void LLFloaterPreferenceGraphicsAdvanced::updateComplexityMode(const LLSD& newvalue) +{ + bool enable_complexity = newvalue.asInteger() != LLVOAvatar::AV_RENDER_ONLY_SHOW_FRIENDS; + getChild("IndirectMaxComplexity")->setEnabled(enable_complexity); + getChild("IndirectMaxNonImpostors")->setEnabled(enable_complexity); +} + +void LLFloaterPreferenceGraphicsAdvanced::updateComplexityText() +{ + LLAvatarComplexityControls::setText(gSavedSettings.getU32("RenderAvatarMaxComplexity"), + getChild("IndirectMaxComplexityText", true)); +} + +void LLFloaterPreferenceGraphicsAdvanced::updateObjectMeshDetailText() +{ + updateSliderText(getChild("ObjectMeshDetail", true), getChild("ObjectMeshDetailText", true)); +} + +void LLFloaterPreferenceGraphicsAdvanced::updateSliderText(LLSliderCtrl* ctrl, LLTextBox* text_box) +{ + if (text_box == NULL || ctrl== NULL) + return; + + // get range and points when text should change + F32 value = (F32)ctrl->getValue().asReal(); + F32 min = ctrl->getMinValue(); + F32 max = ctrl->getMaxValue(); + F32 range = max - min; + llassert(range > 0); + F32 midPoint = min + range / 3.0f; + F32 highPoint = min + (2.0f * range / 3.0f); + + // choose the right text + if (value < midPoint) + { + text_box->setText(LLTrans::getString("GraphicsQualityLow")); + } + else if (value < highPoint) + { + text_box->setText(LLTrans::getString("GraphicsQualityMid")); + } + else + { + text_box->setText(LLTrans::getString("GraphicsQualityHigh")); + } +} + +void LLFloaterPreferenceGraphicsAdvanced::updateMaxNonImpostors() +{ + // Called when the IndirectMaxNonImpostors control changes + // Responsible for fixing the slider label (IndirectMaxNonImpostorsText) and setting RenderAvatarMaxNonImpostors + LLSliderCtrl* ctrl = getChild("IndirectMaxNonImpostors",true); + U32 value = ctrl->getValue().asInteger(); + + if (0 == value || LLVOAvatar::NON_IMPOSTORS_MAX_SLIDER <= value) + { + value=0; + } + gSavedSettings.setU32("RenderAvatarMaxNonImpostors", value); + LLVOAvatar::updateImpostorRendering(value); // make it effective immediately + setMaxNonImpostorsText(value, getChild("IndirectMaxNonImpostorsText")); +} + +void LLFloaterPreferenceGraphicsAdvanced::updateIndirectMaxNonImpostors(const LLSD& newvalue) +{ + U32 value = newvalue.asInteger(); + if ((value != 0) && (value != gSavedSettings.getU32("IndirectMaxNonImpostors"))) + { + gSavedSettings.setU32("IndirectMaxNonImpostors", value); + setMaxNonImpostorsText(value, getChild("IndirectMaxNonImpostorsText")); + } +} + +void LLFloaterPreferenceGraphicsAdvanced::setMaxNonImpostorsText(U32 value, LLTextBox* text_box) +{ + if (0 == value) + { + text_box->setText(LLTrans::getString("no_limit")); + } + else + { + text_box->setText(llformat("%d", value)); + } +} + +void LLFloaterPreferenceGraphicsAdvanced::disableUnavailableSettings() +{ + LLComboBox* ctrl_reflections = getChild("Reflections"); + LLTextBox* reflections_text = getChild("ReflectionsText"); + LLCheckBoxCtrl* ctrl_avatar_vp = getChild("AvatarVertexProgram"); + LLCheckBoxCtrl* ctrl_avatar_cloth = getChild("AvatarCloth"); + LLCheckBoxCtrl* ctrl_wind_light = getChild("WindLightUseAtmosShaders"); + LLCheckBoxCtrl* ctrl_deferred = getChild("UseLightShaders"); + LLComboBox* ctrl_shadows = getChild("ShadowDetail"); + LLTextBox* shadows_text = getChild("RenderShadowDetailText"); + LLCheckBoxCtrl* ctrl_ssao = getChild("UseSSAO"); + LLCheckBoxCtrl* ctrl_dof = getChild("UseDoF"); + LLSliderCtrl* sky = getChild("SkyMeshDetail"); + LLTextBox* sky_text = getChild("SkyMeshDetailText"); + + // disabled windlight + if (!LLFeatureManager::getInstance()->isFeatureAvailable("WindLightUseAtmosShaders")) + { + ctrl_wind_light->setEnabled(false); + ctrl_wind_light->setValue(false); + + sky->setEnabled(false); + sky_text->setEnabled(false); + + //deferred needs windlight, disable deferred + ctrl_shadows->setEnabled(false); + ctrl_shadows->setValue(0); + shadows_text->setEnabled(false); + + ctrl_ssao->setEnabled(false); + ctrl_ssao->setValue(false); + + ctrl_dof->setEnabled(false); + ctrl_dof->setValue(false); + + ctrl_deferred->setEnabled(false); + ctrl_deferred->setValue(false); + } + + // disabled deferred + if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferred")) + { + ctrl_shadows->setEnabled(false); + ctrl_shadows->setValue(0); + shadows_text->setEnabled(false); + + ctrl_ssao->setEnabled(false); + ctrl_ssao->setValue(false); + + ctrl_dof->setEnabled(false); + ctrl_dof->setValue(false); + + ctrl_deferred->setEnabled(false); + ctrl_deferred->setValue(false); + } + + // disabled deferred SSAO + if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferredSSAO")) + { + ctrl_ssao->setEnabled(false); + ctrl_ssao->setValue(false); + } + + // disabled deferred shadows + if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderShadowDetail")) + { + ctrl_shadows->setEnabled(false); + ctrl_shadows->setValue(0); + shadows_text->setEnabled(false); + } + + // disabled reflections + if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderReflectionDetail")) + { + ctrl_reflections->setEnabled(false); + ctrl_reflections->setValue(false); + reflections_text->setEnabled(false); + } + + // disabled av + if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderAvatarVP")) + { + ctrl_avatar_vp->setEnabled(false); + ctrl_avatar_vp->setValue(false); + + ctrl_avatar_cloth->setEnabled(false); + ctrl_avatar_cloth->setValue(false); + + //deferred needs AvatarVP, disable deferred + ctrl_shadows->setEnabled(false); + ctrl_shadows->setValue(0); + shadows_text->setEnabled(false); + + ctrl_ssao->setEnabled(false); + ctrl_ssao->setValue(false); + + ctrl_dof->setEnabled(false); + ctrl_dof->setValue(false); + + ctrl_deferred->setEnabled(false); + ctrl_deferred->setValue(false); + } + + // disabled cloth + if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderAvatarCloth")) + { + ctrl_avatar_cloth->setEnabled(false); + ctrl_avatar_cloth->setValue(false); + } +} + +void LLFloaterPreferenceGraphicsAdvanced::refreshEnabledState() +{ + LLComboBox* ctrl_reflections = getChild("Reflections"); + LLTextBox* reflections_text = getChild("ReflectionsText"); + + // Reflections + bool reflections = LLCubeMap::sUseCubeMaps; + ctrl_reflections->setEnabled(reflections); + reflections_text->setEnabled(reflections); + + // Bump & Shiny + LLCheckBoxCtrl* bumpshiny_ctrl = getChild("BumpShiny"); + bool bumpshiny = LLCubeMap::sUseCubeMaps && LLFeatureManager::getInstance()->isFeatureAvailable("RenderObjectBump"); + bumpshiny_ctrl->setEnabled(bumpshiny); + + // Avatar Mode + // Enable Avatar Shaders + LLCheckBoxCtrl* ctrl_avatar_vp = getChild("AvatarVertexProgram"); + // Avatar Render Mode + LLCheckBoxCtrl* ctrl_avatar_cloth = getChild("AvatarCloth"); + + bool avatar_vp_enabled = LLFeatureManager::getInstance()->isFeatureAvailable("RenderAvatarVP"); + if (LLViewerShaderMgr::sInitialized) + { + S32 max_avatar_shader = LLViewerShaderMgr::instance()->mMaxAvatarShaderLevel; + avatar_vp_enabled = max_avatar_shader > 0; + } + + ctrl_avatar_vp->setEnabled(avatar_vp_enabled); + + ctrl_avatar_cloth->setEnabled(gSavedSettings.getBOOL("RenderAvatarVP")); + + // Vertex Shaders, Global Shader Enable + // SL-12594 Basic shaders are always enabled. DJH TODO clean up now-orphaned state handling code + LLSliderCtrl* terrain_detail = getChild("TerrainDetail"); // can be linked with control var + LLTextBox* terrain_text = getChild("TerrainDetailText"); + + terrain_detail->setEnabled(false); + terrain_text->setEnabled(false); + + // WindLight + //LLCheckBoxCtrl* ctrl_wind_light = getChild("WindLightUseAtmosShaders"); + //ctrl_wind_light->setEnabled(true); + LLSliderCtrl* sky = getChild("SkyMeshDetail"); + LLTextBox* sky_text = getChild("SkyMeshDetailText"); + sky->setEnabled(true); + sky_text->setEnabled(true); + + bool enabled = true; +#if 0 // deferred always on now + //Deferred/SSAO/Shadows + LLCheckBoxCtrl* ctrl_deferred = getChild("UseLightShaders"); + + enabled = LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferred") && + bumpshiny_ctrl && bumpshiny_ctrl->get() && + ctrl_wind_light->get(); + + ctrl_deferred->setEnabled(enabled); +#endif + + LLCheckBoxCtrl* ctrl_pbr = getChild("UsePBRShaders"); + + //PBR + ctrl_pbr->setEnabled(true); + + LLCheckBoxCtrl* ctrl_ssao = getChild("UseSSAO"); + LLCheckBoxCtrl* ctrl_dof = getChild("UseDoF"); + LLComboBox* ctrl_shadow = getChild("ShadowDetail"); + LLTextBox* shadow_text = getChild("RenderShadowDetailText"); + + // note, okay here to get from ctrl_deferred as it's twin, ctrl_deferred2 will alway match it + enabled = enabled && LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferredSSAO");// && ctrl_deferred->get(); + + //ctrl_deferred->set(gSavedSettings.getBOOL("RenderDeferred")); + + ctrl_ssao->setEnabled(enabled); + ctrl_dof->setEnabled(enabled); + + enabled = enabled && LLFeatureManager::getInstance()->isFeatureAvailable("RenderShadowDetail"); + + ctrl_shadow->setEnabled(enabled); + shadow_text->setEnabled(enabled); + + // Hardware settings + + if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderVBOEnable")) + { + getChildView("vbo")->setEnabled(false); + } + + if (!LLFeatureManager::getInstance()->isFeatureAvailable("RenderCompressTextures")) + { + getChildView("texture compression")->setEnabled(false); + } + + // if no windlight shaders, turn off nighttime brightness, gamma, and fog distance + LLUICtrl* gamma_ctrl = getChild("gamma"); + gamma_ctrl->setEnabled(!gPipeline.canUseWindLightShaders()); + getChildView("(brightness, lower is brighter)")->setEnabled(!gPipeline.canUseWindLightShaders()); + getChildView("fog")->setEnabled(!gPipeline.canUseWindLightShaders()); + getChildView("antialiasing restart")->setVisible(!LLFeatureManager::getInstance()->isFeatureAvailable("RenderDeferred")); + + // now turn off any features that are unavailable + disableUnavailableSettings(); +} + +void LLFloaterPreferenceGraphicsAdvanced::onBtnOK(const LLSD& userdata) +{ + LLFloaterPreference* instance = LLFloaterReg::getTypedInstance("preferences"); + if (instance) + { + instance->onBtnOK(userdata); + } +} + +void LLFloaterPreferenceGraphicsAdvanced::onBtnCancel(const LLSD& userdata) +{ + LLFloaterPreference* instance = LLFloaterReg::getTypedInstance("preferences"); + if (instance) + { + instance->onBtnCancel(userdata); + } +} diff --git a/indra/newview/llfloaterpreferencesgraphicsadvanced.h b/indra/newview/llfloaterpreferencesgraphicsadvanced.h index 3144b37751..61203be068 100644 --- a/indra/newview/llfloaterpreferencesgraphicsadvanced.h +++ b/indra/newview/llfloaterpreferencesgraphicsadvanced.h @@ -1,71 +1,71 @@ -/** - * @file llfloaterpreferencesgraphicsadvanced.h - * - * $LicenseInfo:firstyear=2021&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2021, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLFLOATERPREFERENCEGRAPHICSADVANCED_H -#define LLFLOATERPREFERENCEGRAPHICSADVANCED_H - -#include "llcontrol.h" -#include "llfloater.h" - -class LLSliderCtrl; -class LLTextBox; - -class LLFloaterPreferenceGraphicsAdvanced : public LLFloater -{ -public: - LLFloaterPreferenceGraphicsAdvanced(const LLSD& key); - ~LLFloaterPreferenceGraphicsAdvanced(); - /*virtual*/ bool postBuild(); - void onOpen(const LLSD& key); - void onClickCloseBtn(bool app_quitting); - void disableUnavailableSettings(); - void refreshEnabledGraphics(); - void refreshEnabledState(); - void updateSliderText(LLSliderCtrl* ctrl, LLTextBox* text_box); - void updateMaxNonImpostors(); - void updateIndirectMaxNonImpostors(const LLSD& newvalue); - void setMaxNonImpostorsText(U32 value, LLTextBox* text_box); - void updateMaxComplexity(); - void updateComplexityMode(const LLSD& newvalue); - void updateComplexityText(); - void updateObjectMeshDetailText(); - void refresh(); - // callback for when client modifies a render option - void onRenderOptionEnable(); - void onAdvancedAtmosphericsEnable(); - LOG_CLASS(LLFloaterPreferenceGraphicsAdvanced); - -protected: - void onBtnOK(const LLSD& userdata); - void onBtnCancel(const LLSD& userdata); - - boost::signals2::connection mComplexityChangedSignal; - boost::signals2::connection mComplexityModeChangedSignal; - boost::signals2::connection mLODFactorChangedSignal; - boost::signals2::connection mNumImpostorsChangedSignal; -}; - -#endif //LLFLOATERPREFERENCEGRAPHICSADVANCED_H - +/** + * @file llfloaterpreferencesgraphicsadvanced.h + * + * $LicenseInfo:firstyear=2021&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2021, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLFLOATERPREFERENCEGRAPHICSADVANCED_H +#define LLFLOATERPREFERENCEGRAPHICSADVANCED_H + +#include "llcontrol.h" +#include "llfloater.h" + +class LLSliderCtrl; +class LLTextBox; + +class LLFloaterPreferenceGraphicsAdvanced : public LLFloater +{ +public: + LLFloaterPreferenceGraphicsAdvanced(const LLSD& key); + ~LLFloaterPreferenceGraphicsAdvanced(); + /*virtual*/ bool postBuild(); + void onOpen(const LLSD& key); + void onClickCloseBtn(bool app_quitting); + void disableUnavailableSettings(); + void refreshEnabledGraphics(); + void refreshEnabledState(); + void updateSliderText(LLSliderCtrl* ctrl, LLTextBox* text_box); + void updateMaxNonImpostors(); + void updateIndirectMaxNonImpostors(const LLSD& newvalue); + void setMaxNonImpostorsText(U32 value, LLTextBox* text_box); + void updateMaxComplexity(); + void updateComplexityMode(const LLSD& newvalue); + void updateComplexityText(); + void updateObjectMeshDetailText(); + void refresh(); + // callback for when client modifies a render option + void onRenderOptionEnable(); + void onAdvancedAtmosphericsEnable(); + LOG_CLASS(LLFloaterPreferenceGraphicsAdvanced); + +protected: + void onBtnOK(const LLSD& userdata); + void onBtnCancel(const LLSD& userdata); + + boost::signals2::connection mComplexityChangedSignal; + boost::signals2::connection mComplexityModeChangedSignal; + boost::signals2::connection mLODFactorChangedSignal; + boost::signals2::connection mNumImpostorsChangedSignal; +}; + +#endif //LLFLOATERPREFERENCEGRAPHICSADVANCED_H + diff --git a/indra/newview/llfloaterpreviewtrash.cpp b/indra/newview/llfloaterpreviewtrash.cpp index 5ec9843c77..21e7585c40 100644 --- a/indra/newview/llfloaterpreviewtrash.cpp +++ b/indra/newview/llfloaterpreviewtrash.cpp @@ -1,82 +1,82 @@ -/** - * @file llfloaterpreviewtrash.cpp - * @author AndreyK Productengine - * @brief LLFloaterPreviewTrash class implementation - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterpreviewtrash.h" - -#include "llinventoryfunctions.h" -#include "llfloaterreg.h" - -LLFloaterPreviewTrash::LLFloaterPreviewTrash(const LLSD& key) -: LLFloater(key) -{ -} - -bool LLFloaterPreviewTrash::postBuild() -{ - getChild("empty_btn")->setCommitCallback( - boost::bind(&LLFloaterPreviewTrash::onClickEmpty, this)); - getChild("cancel_btn")->setCommitCallback( - boost::bind(&LLFloaterPreviewTrash::onClickCancel, this)); - // Always center the dialog. User can change the size, - // but purchases are important and should be center screen. - // This also avoids problems where the user resizes the application window - // mid-session and the saved rect is off-center. - center(); - - return true; -} - -LLFloaterPreviewTrash::~LLFloaterPreviewTrash() -{ -} - - -// static -void LLFloaterPreviewTrash::show() -{ - LLFloaterReg::showTypedInstance("preview_trash", LLSD(), true); -} - -// static -bool LLFloaterPreviewTrash::isVisible() -{ - return LLFloaterReg::instanceVisible("preview_trash"); -} - - -void LLFloaterPreviewTrash::onClickEmpty() -{ - gInventory.emptyFolderType("PurgeSelectedItems", LLFolderType::FT_TRASH); - closeFloater(); -} - -void LLFloaterPreviewTrash::onClickCancel() -{ - closeFloater(); -} +/** + * @file llfloaterpreviewtrash.cpp + * @author AndreyK Productengine + * @brief LLFloaterPreviewTrash class implementation + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterpreviewtrash.h" + +#include "llinventoryfunctions.h" +#include "llfloaterreg.h" + +LLFloaterPreviewTrash::LLFloaterPreviewTrash(const LLSD& key) +: LLFloater(key) +{ +} + +bool LLFloaterPreviewTrash::postBuild() +{ + getChild("empty_btn")->setCommitCallback( + boost::bind(&LLFloaterPreviewTrash::onClickEmpty, this)); + getChild("cancel_btn")->setCommitCallback( + boost::bind(&LLFloaterPreviewTrash::onClickCancel, this)); + // Always center the dialog. User can change the size, + // but purchases are important and should be center screen. + // This also avoids problems where the user resizes the application window + // mid-session and the saved rect is off-center. + center(); + + return true; +} + +LLFloaterPreviewTrash::~LLFloaterPreviewTrash() +{ +} + + +// static +void LLFloaterPreviewTrash::show() +{ + LLFloaterReg::showTypedInstance("preview_trash", LLSD(), true); +} + +// static +bool LLFloaterPreviewTrash::isVisible() +{ + return LLFloaterReg::instanceVisible("preview_trash"); +} + + +void LLFloaterPreviewTrash::onClickEmpty() +{ + gInventory.emptyFolderType("PurgeSelectedItems", LLFolderType::FT_TRASH); + closeFloater(); +} + +void LLFloaterPreviewTrash::onClickCancel() +{ + closeFloater(); +} diff --git a/indra/newview/llfloaterpreviewtrash.h b/indra/newview/llfloaterpreviewtrash.h index ca7a3aeccf..2be36cb188 100644 --- a/indra/newview/llfloaterpreviewtrash.h +++ b/indra/newview/llfloaterpreviewtrash.h @@ -1,49 +1,49 @@ -/** - * @file llfloaterpreviewtrash.h - * @author AndreyK Productengine - * @brief LLFloaterPreviewTrash class header file - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERPREVIEWTRASH_H -#define LL_LLFLOATERPREVIEWTRASH_H - -#include "llfloater.h" - -class LLFloaterPreviewTrash -: public LLFloater -{ -public: - static void show(); - static bool isVisible(); - - LLFloaterPreviewTrash(const LLSD& key); - ~LLFloaterPreviewTrash(); - bool postBuild() override; - -protected: - void onClickEmpty(); - void onClickCancel(); -}; - -#endif +/** + * @file llfloaterpreviewtrash.h + * @author AndreyK Productengine + * @brief LLFloaterPreviewTrash class header file + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERPREVIEWTRASH_H +#define LL_LLFLOATERPREVIEWTRASH_H + +#include "llfloater.h" + +class LLFloaterPreviewTrash +: public LLFloater +{ +public: + static void show(); + static bool isVisible(); + + LLFloaterPreviewTrash(const LLSD& key); + ~LLFloaterPreviewTrash(); + bool postBuild() override; + +protected: + void onClickEmpty(); + void onClickCancel(); +}; + +#endif diff --git a/indra/newview/llfloaterprofile.cpp b/indra/newview/llfloaterprofile.cpp index 53fa3f8e60..ee92785b6f 100644 --- a/indra/newview/llfloaterprofile.cpp +++ b/indra/newview/llfloaterprofile.cpp @@ -1,170 +1,170 @@ -/** - * @file llfloaterprofile.cpp - * @brief Avatar profile floater. - * - * $LicenseInfo:firstyear=2022&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2022, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterprofile.h" - -#include "llagent.h" //gAgent -#include "llnotificationsutil.h" -#include "llpanelavatar.h" -#include "llpanelprofile.h" - -static const std::string PANEL_PROFILE_VIEW = "panel_profile_view"; - -LLFloaterProfile::LLFloaterProfile(const LLSD& key) - : LLFloater(key), - mAvatarId(key["id"].asUUID()), - mNameCallbackConnection() -{ - mDefaultRectForGroup = false; -} - -LLFloaterProfile::~LLFloaterProfile() -{ - if (mNameCallbackConnection.connected()) - { - mNameCallbackConnection.disconnect(); - } -} - -void LLFloaterProfile::onOpen(const LLSD& key) -{ - mPanelProfile->onOpen(key); - - // Update the avatar name. - mNameCallbackConnection = LLAvatarNameCache::get(mAvatarId, boost::bind(&LLFloaterProfile::onAvatarNameCache, this, _1, _2)); -} - -bool LLFloaterProfile::postBuild() -{ - mPanelProfile = findChild(PANEL_PROFILE_VIEW); - - return true; -} - -void LLFloaterProfile::onClickCloseBtn(bool app_quitting) -{ - if (!app_quitting) - { - if (mPanelProfile->hasUnpublishedClassifieds()) - { - LLNotificationsUtil::add("ProfileUnpublishedClassified", LLSD(), LLSD(), - boost::bind(&LLFloaterProfile::onUnsavedChangesCallback, this, _1, _2, false)); - } - else if (mPanelProfile->hasUnsavedChanges()) - { - LLNotificationsUtil::add("ProfileUnsavedChanges", LLSD(), LLSD(), - boost::bind(&LLFloaterProfile::onUnsavedChangesCallback, this, _1, _2, true)); - } - else - { - closeFloater(); - } - } - else - { - closeFloater(); - } -} - -void LLFloaterProfile::onUnsavedChangesCallback(const LLSD& notification, const LLSD& response, bool can_save) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (can_save) - { - // savable content - - if (option == 0) // Save - { - mPanelProfile->commitUnsavedChanges(); - closeFloater(); - } - if (option == 1) // Discard - { - closeFloater(); - } - // else cancel - } - else - { - // classifieds - - if (option == 0) // Ok - { - closeFloater(); - } - // else cancel - } - -} - -void LLFloaterProfile::createPick(const LLPickData &data) -{ - mPanelProfile->createPick(data); -} - -void LLFloaterProfile::showPick(const LLUUID& pick_id) -{ - mPanelProfile->showPick(pick_id); -} - -bool LLFloaterProfile::isPickTabSelected() -{ - return mPanelProfile->isPickTabSelected(); -} - -void LLFloaterProfile::refreshName() -{ - if (!mNameCallbackConnection.connected()) - { - mNameCallbackConnection = LLAvatarNameCache::get(mAvatarId, boost::bind(&LLFloaterProfile::onAvatarNameCache, this, _1, _2)); - } - - LLPanelProfileSecondLife *panel = findChild("panel_profile_secondlife"); - if (panel) - { - panel->refreshName(); - } -} - -void LLFloaterProfile::showClassified(const LLUUID& classified_id, bool edit) -{ - mPanelProfile->showClassified(classified_id, edit); -} - -void LLFloaterProfile::createClassified() -{ - mPanelProfile->createClassified(); -} - -void LLFloaterProfile::onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name) -{ - mNameCallbackConnection.disconnect(); - setTitle(av_name.getCompleteName()); -} - -// eof +/** + * @file llfloaterprofile.cpp + * @brief Avatar profile floater. + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2022, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterprofile.h" + +#include "llagent.h" //gAgent +#include "llnotificationsutil.h" +#include "llpanelavatar.h" +#include "llpanelprofile.h" + +static const std::string PANEL_PROFILE_VIEW = "panel_profile_view"; + +LLFloaterProfile::LLFloaterProfile(const LLSD& key) + : LLFloater(key), + mAvatarId(key["id"].asUUID()), + mNameCallbackConnection() +{ + mDefaultRectForGroup = false; +} + +LLFloaterProfile::~LLFloaterProfile() +{ + if (mNameCallbackConnection.connected()) + { + mNameCallbackConnection.disconnect(); + } +} + +void LLFloaterProfile::onOpen(const LLSD& key) +{ + mPanelProfile->onOpen(key); + + // Update the avatar name. + mNameCallbackConnection = LLAvatarNameCache::get(mAvatarId, boost::bind(&LLFloaterProfile::onAvatarNameCache, this, _1, _2)); +} + +bool LLFloaterProfile::postBuild() +{ + mPanelProfile = findChild(PANEL_PROFILE_VIEW); + + return true; +} + +void LLFloaterProfile::onClickCloseBtn(bool app_quitting) +{ + if (!app_quitting) + { + if (mPanelProfile->hasUnpublishedClassifieds()) + { + LLNotificationsUtil::add("ProfileUnpublishedClassified", LLSD(), LLSD(), + boost::bind(&LLFloaterProfile::onUnsavedChangesCallback, this, _1, _2, false)); + } + else if (mPanelProfile->hasUnsavedChanges()) + { + LLNotificationsUtil::add("ProfileUnsavedChanges", LLSD(), LLSD(), + boost::bind(&LLFloaterProfile::onUnsavedChangesCallback, this, _1, _2, true)); + } + else + { + closeFloater(); + } + } + else + { + closeFloater(); + } +} + +void LLFloaterProfile::onUnsavedChangesCallback(const LLSD& notification, const LLSD& response, bool can_save) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (can_save) + { + // savable content + + if (option == 0) // Save + { + mPanelProfile->commitUnsavedChanges(); + closeFloater(); + } + if (option == 1) // Discard + { + closeFloater(); + } + // else cancel + } + else + { + // classifieds + + if (option == 0) // Ok + { + closeFloater(); + } + // else cancel + } + +} + +void LLFloaterProfile::createPick(const LLPickData &data) +{ + mPanelProfile->createPick(data); +} + +void LLFloaterProfile::showPick(const LLUUID& pick_id) +{ + mPanelProfile->showPick(pick_id); +} + +bool LLFloaterProfile::isPickTabSelected() +{ + return mPanelProfile->isPickTabSelected(); +} + +void LLFloaterProfile::refreshName() +{ + if (!mNameCallbackConnection.connected()) + { + mNameCallbackConnection = LLAvatarNameCache::get(mAvatarId, boost::bind(&LLFloaterProfile::onAvatarNameCache, this, _1, _2)); + } + + LLPanelProfileSecondLife *panel = findChild("panel_profile_secondlife"); + if (panel) + { + panel->refreshName(); + } +} + +void LLFloaterProfile::showClassified(const LLUUID& classified_id, bool edit) +{ + mPanelProfile->showClassified(classified_id, edit); +} + +void LLFloaterProfile::createClassified() +{ + mPanelProfile->createClassified(); +} + +void LLFloaterProfile::onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name) +{ + mNameCallbackConnection.disconnect(); + setTitle(av_name.getCompleteName()); +} + +// eof diff --git a/indra/newview/llfloaterregiondebugconsole.cpp b/indra/newview/llfloaterregiondebugconsole.cpp index 701b443b8d..26647333dc 100644 --- a/indra/newview/llfloaterregiondebugconsole.cpp +++ b/indra/newview/llfloaterregiondebugconsole.cpp @@ -1,211 +1,211 @@ -/** - * @file llfloaterregiondebugconsole.h - * @author Brad Kittenbrink - * @brief Quick and dirty console for region debug settings - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterregiondebugconsole.h" - -#include "llagent.h" -#include "llhttpnode.h" -#include "lllineeditor.h" -#include "lltexteditor.h" -#include "llviewerregion.h" -#include "llcorehttputil.h" - -// Two versions of the sim console API are supported. -// -// SimConsole capability (deprecated): -// This is the initial implementation that is supported by some versions of the -// simulator. It is simple and straight forward, just POST a command and the -// body of the response has the result. This API is deprecated because it -// doesn't allow the sim to use any asynchronous API. -// -// SimConsoleAsync capability: -// This capability replaces the original SimConsole capability. It is similar -// in that the command is POSTed to the SimConsoleAsync cap, but the response -// comes in through the event poll, which gives the simulator more flexibility -// and allows it to perform complex operations without blocking any frames. -// -// We will assume the SimConsoleAsync capability is available, and fall back to -// the SimConsole cap if it is not. The simulator will only support one or the -// other. - -namespace -{ - // Signal used to notify the floater of responses from the asynchronous - // API. - console_reply_signal_t sConsoleReplySignal; - - const std::string PROMPT("\n\n> "); - const std::string UNABLE_TO_SEND_COMMAND( - "ERROR: The last command was not received by the server."); - const std::string CONSOLE_UNAVAILABLE( - "ERROR: No console available for this region/simulator."); - const std::string CONSOLE_NOT_SUPPORTED( - "This region does not support the simulator console."); - - // This handles responses for console commands sent via the asynchronous - // API. - class ConsoleResponseNode : public LLHTTPNode - { - public: - /* virtual */ - void post( - LLHTTPNode::ResponsePtr reponse, - const LLSD& context, - const LLSD& input) const - { - LL_INFOS() << "Received response from the debug console: " - << input << LL_ENDL; - sConsoleReplySignal(input["body"].asString()); - } - }; -} - -boost::signals2::connection LLFloaterRegionDebugConsole::setConsoleReplyCallback(const console_reply_signal_t::slot_type& cb) -{ - return sConsoleReplySignal.connect(cb); -} - -LLFloaterRegionDebugConsole::LLFloaterRegionDebugConsole(LLSD const & key) -: LLFloater(key), mOutput(NULL) -{ - mReplySignalConnection = sConsoleReplySignal.connect( - boost::bind( - &LLFloaterRegionDebugConsole::onReplyReceived, - this, - _1)); -} - -LLFloaterRegionDebugConsole::~LLFloaterRegionDebugConsole() -{ - mReplySignalConnection.disconnect(); -} - -bool LLFloaterRegionDebugConsole::postBuild() -{ - LLLineEditor* input = getChild("region_debug_console_input"); - input->setEnableLineHistory(true); - input->setCommitCallback(boost::bind(&LLFloaterRegionDebugConsole::onInput, this, _1, _2)); - input->setFocus(true); - input->setCommitOnFocusLost(false); - - mOutput = getChild("region_debug_console_output"); - - std::string url = gAgent.getRegionCapability("SimConsoleAsync"); - if (url.empty()) - { - // Fall back to see if the old API is supported. - url = gAgent.getRegionCapability("SimConsole"); - if (url.empty()) - { - mOutput->appendText( - CONSOLE_NOT_SUPPORTED + PROMPT, - false); - return true; - } - } - - mOutput->appendText("> ", false); - return true; -} - -void LLFloaterRegionDebugConsole::onInput(LLUICtrl* ctrl, const LLSD& param) -{ - LLLineEditor* input = static_cast(ctrl); - std::string text = input->getText() + "\n"; - - std::string url = gAgent.getRegionCapability("SimConsoleAsync"); - if (url.empty()) - { - // Fall back to the old API - url = gAgent.getRegionCapability("SimConsole"); - if (url.empty()) - { - text += CONSOLE_UNAVAILABLE + PROMPT; - } - else - { - LLSD postData = LLSD(input->getText()); - LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost(url, postData, - boost::bind(&LLFloaterRegionDebugConsole::onConsoleSuccess, this, _1), - boost::bind(&LLFloaterRegionDebugConsole::onConsoleError, this, _1)); - } - } - else - { - LLSD postData = LLSD(input->getText()); - LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost(url, postData, - NULL, - boost::bind(&LLFloaterRegionDebugConsole::onAsyncConsoleError, this, _1)); - - } - - mOutput->appendText(text, false); - input->clear(); -} - -void LLFloaterRegionDebugConsole::onAsyncConsoleError(LLSD result) -{ - LL_WARNS("Console") << UNABLE_TO_SEND_COMMAND << LL_ENDL; - sConsoleReplySignal(UNABLE_TO_SEND_COMMAND); -} - -void LLFloaterRegionDebugConsole::onConsoleError(LLSD result) -{ - LL_WARNS("Console") << UNABLE_TO_SEND_COMMAND << LL_ENDL; - if (mOutput) - { - mOutput->appendText( - UNABLE_TO_SEND_COMMAND + PROMPT, - false); - } - -} - -void LLFloaterRegionDebugConsole::onConsoleSuccess(LLSD result) -{ - if (mOutput) - { - LLSD response = result; - if (response.isMap() && response.has(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT)) - { - response = response[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT]; - } - mOutput->appendText( - response.asString() + PROMPT, false); - } -} - -void LLFloaterRegionDebugConsole::onReplyReceived(const std::string& output) -{ - mOutput->appendText(output + PROMPT, false); -} - -LLHTTPRegistration - gHTTPRegistrationMessageDebugConsoleResponse( - "/message/SimConsoleResponse"); +/** + * @file llfloaterregiondebugconsole.h + * @author Brad Kittenbrink + * @brief Quick and dirty console for region debug settings + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterregiondebugconsole.h" + +#include "llagent.h" +#include "llhttpnode.h" +#include "lllineeditor.h" +#include "lltexteditor.h" +#include "llviewerregion.h" +#include "llcorehttputil.h" + +// Two versions of the sim console API are supported. +// +// SimConsole capability (deprecated): +// This is the initial implementation that is supported by some versions of the +// simulator. It is simple and straight forward, just POST a command and the +// body of the response has the result. This API is deprecated because it +// doesn't allow the sim to use any asynchronous API. +// +// SimConsoleAsync capability: +// This capability replaces the original SimConsole capability. It is similar +// in that the command is POSTed to the SimConsoleAsync cap, but the response +// comes in through the event poll, which gives the simulator more flexibility +// and allows it to perform complex operations without blocking any frames. +// +// We will assume the SimConsoleAsync capability is available, and fall back to +// the SimConsole cap if it is not. The simulator will only support one or the +// other. + +namespace +{ + // Signal used to notify the floater of responses from the asynchronous + // API. + console_reply_signal_t sConsoleReplySignal; + + const std::string PROMPT("\n\n> "); + const std::string UNABLE_TO_SEND_COMMAND( + "ERROR: The last command was not received by the server."); + const std::string CONSOLE_UNAVAILABLE( + "ERROR: No console available for this region/simulator."); + const std::string CONSOLE_NOT_SUPPORTED( + "This region does not support the simulator console."); + + // This handles responses for console commands sent via the asynchronous + // API. + class ConsoleResponseNode : public LLHTTPNode + { + public: + /* virtual */ + void post( + LLHTTPNode::ResponsePtr reponse, + const LLSD& context, + const LLSD& input) const + { + LL_INFOS() << "Received response from the debug console: " + << input << LL_ENDL; + sConsoleReplySignal(input["body"].asString()); + } + }; +} + +boost::signals2::connection LLFloaterRegionDebugConsole::setConsoleReplyCallback(const console_reply_signal_t::slot_type& cb) +{ + return sConsoleReplySignal.connect(cb); +} + +LLFloaterRegionDebugConsole::LLFloaterRegionDebugConsole(LLSD const & key) +: LLFloater(key), mOutput(NULL) +{ + mReplySignalConnection = sConsoleReplySignal.connect( + boost::bind( + &LLFloaterRegionDebugConsole::onReplyReceived, + this, + _1)); +} + +LLFloaterRegionDebugConsole::~LLFloaterRegionDebugConsole() +{ + mReplySignalConnection.disconnect(); +} + +bool LLFloaterRegionDebugConsole::postBuild() +{ + LLLineEditor* input = getChild("region_debug_console_input"); + input->setEnableLineHistory(true); + input->setCommitCallback(boost::bind(&LLFloaterRegionDebugConsole::onInput, this, _1, _2)); + input->setFocus(true); + input->setCommitOnFocusLost(false); + + mOutput = getChild("region_debug_console_output"); + + std::string url = gAgent.getRegionCapability("SimConsoleAsync"); + if (url.empty()) + { + // Fall back to see if the old API is supported. + url = gAgent.getRegionCapability("SimConsole"); + if (url.empty()) + { + mOutput->appendText( + CONSOLE_NOT_SUPPORTED + PROMPT, + false); + return true; + } + } + + mOutput->appendText("> ", false); + return true; +} + +void LLFloaterRegionDebugConsole::onInput(LLUICtrl* ctrl, const LLSD& param) +{ + LLLineEditor* input = static_cast(ctrl); + std::string text = input->getText() + "\n"; + + std::string url = gAgent.getRegionCapability("SimConsoleAsync"); + if (url.empty()) + { + // Fall back to the old API + url = gAgent.getRegionCapability("SimConsole"); + if (url.empty()) + { + text += CONSOLE_UNAVAILABLE + PROMPT; + } + else + { + LLSD postData = LLSD(input->getText()); + LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost(url, postData, + boost::bind(&LLFloaterRegionDebugConsole::onConsoleSuccess, this, _1), + boost::bind(&LLFloaterRegionDebugConsole::onConsoleError, this, _1)); + } + } + else + { + LLSD postData = LLSD(input->getText()); + LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost(url, postData, + NULL, + boost::bind(&LLFloaterRegionDebugConsole::onAsyncConsoleError, this, _1)); + + } + + mOutput->appendText(text, false); + input->clear(); +} + +void LLFloaterRegionDebugConsole::onAsyncConsoleError(LLSD result) +{ + LL_WARNS("Console") << UNABLE_TO_SEND_COMMAND << LL_ENDL; + sConsoleReplySignal(UNABLE_TO_SEND_COMMAND); +} + +void LLFloaterRegionDebugConsole::onConsoleError(LLSD result) +{ + LL_WARNS("Console") << UNABLE_TO_SEND_COMMAND << LL_ENDL; + if (mOutput) + { + mOutput->appendText( + UNABLE_TO_SEND_COMMAND + PROMPT, + false); + } + +} + +void LLFloaterRegionDebugConsole::onConsoleSuccess(LLSD result) +{ + if (mOutput) + { + LLSD response = result; + if (response.isMap() && response.has(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT)) + { + response = response[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT]; + } + mOutput->appendText( + response.asString() + PROMPT, false); + } +} + +void LLFloaterRegionDebugConsole::onReplyReceived(const std::string& output) +{ + mOutput->appendText(output + PROMPT, false); +} + +LLHTTPRegistration + gHTTPRegistrationMessageDebugConsoleResponse( + "/message/SimConsoleResponse"); diff --git a/indra/newview/llfloaterregiondebugconsole.h b/indra/newview/llfloaterregiondebugconsole.h index c5db806668..a26cf1a30f 100644 --- a/indra/newview/llfloaterregiondebugconsole.h +++ b/indra/newview/llfloaterregiondebugconsole.h @@ -1,64 +1,64 @@ -/** - * @file llfloaterregiondebugconsole.h - * @author Brad Kittenbrink - * @brief Quick and dirty console for region debug settings - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERREGIONDEBUGCONSOLE_H -#define LL_LLFLOATERREGIONDEBUGCONSOLE_H - -#include - -#include "llfloater.h" - -class LLTextEditor; - -typedef boost::signals2::signal< - void (const std::string& output)> console_reply_signal_t; - -class LLFloaterRegionDebugConsole : public LLFloater -{ -public: - LLFloaterRegionDebugConsole(LLSD const & key); - virtual ~LLFloaterRegionDebugConsole(); - - bool postBuild() override; - - void onInput(LLUICtrl* ctrl, const LLSD& param); - - LLTextEditor * mOutput; - - static boost::signals2::connection setConsoleReplyCallback(const console_reply_signal_t::slot_type& cb); - - private: - void onReplyReceived(const std::string& output); - - void onAsyncConsoleError(LLSD result); - void onConsoleError(LLSD result); - void onConsoleSuccess(LLSD result); - - boost::signals2::connection mReplySignalConnection; -}; - -#endif // LL_LLFLOATERREGIONDEBUGCONSOLE_H +/** + * @file llfloaterregiondebugconsole.h + * @author Brad Kittenbrink + * @brief Quick and dirty console for region debug settings + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERREGIONDEBUGCONSOLE_H +#define LL_LLFLOATERREGIONDEBUGCONSOLE_H + +#include + +#include "llfloater.h" + +class LLTextEditor; + +typedef boost::signals2::signal< + void (const std::string& output)> console_reply_signal_t; + +class LLFloaterRegionDebugConsole : public LLFloater +{ +public: + LLFloaterRegionDebugConsole(LLSD const & key); + virtual ~LLFloaterRegionDebugConsole(); + + bool postBuild() override; + + void onInput(LLUICtrl* ctrl, const LLSD& param); + + LLTextEditor * mOutput; + + static boost::signals2::connection setConsoleReplyCallback(const console_reply_signal_t::slot_type& cb); + + private: + void onReplyReceived(const std::string& output); + + void onAsyncConsoleError(LLSD result); + void onConsoleError(LLSD result); + void onConsoleSuccess(LLSD result); + + boost::signals2::connection mReplySignalConnection; +}; + +#endif // LL_LLFLOATERREGIONDEBUGCONSOLE_H diff --git a/indra/newview/llfloaterregioninfo.cpp b/indra/newview/llfloaterregioninfo.cpp index 218f8e428b..b5aa30e875 100644 --- a/indra/newview/llfloaterregioninfo.cpp +++ b/indra/newview/llfloaterregioninfo.cpp @@ -1,3886 +1,3886 @@ -/** - * @file llfloaterregioninfo.cpp - * @author Aaron Brashears - * @brief Implementation of the region info and controls floater and panels. - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llfloaterregioninfo.h" - -#include -#include - -#include "lldir.h" -#include "lldispatcher.h" -#include "llglheaders.h" -#include "llregionflags.h" -#include "llstl.h" -#include "llfilesystem.h" -#include "llxfermanager.h" -#include "indra_constants.h" -#include "message.h" -#include "llloadingindicator.h" -#include "llradiogroup.h" -#include "llsd.h" -#include "llsdserialize.h" - -#include "llagent.h" -#include "llappviewer.h" -#include "llavataractions.h" -#include "llavatarname.h" -#include "llfloateravatarpicker.h" -#include "llbutton.h" -#include "llcheckboxctrl.h" -#include "llclipboard.h" -#include "llcombobox.h" -#include "llestateinfomodel.h" -#include "llfilepicker.h" -#include "llfloatergodtools.h" // for send_sim_wide_deletes() -#include "llfloatertopobjects.h" // added to fix SL-32336 -#include "llfloatergroups.h" -#include "llfloaterreg.h" -#include "llfloaterregiondebugconsole.h" -#include "llfloatertelehub.h" -#include "llinventorymodel.h" -#include "lllineeditor.h" -#include "llnamelistctrl.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "llregioninfomodel.h" -#include "llscrolllistitem.h" -#include "llsliderctrl.h" -#include "llslurl.h" -#include "llspinctrl.h" -#include "lltabcontainer.h" -#include "lltextbox.h" -#include "llinventory.h" -#include "lltexturectrl.h" -#include "lltrans.h" -#include "llviewercontrol.h" -#include "lluictrlfactory.h" -#include "llviewerinventory.h" -#include "llviewertexture.h" -#include "llviewertexturelist.h" -#include "llviewerregion.h" -#include "llviewerstats.h" -#include "llviewertexteditor.h" -#include "llviewerwindow.h" -#include "llvlcomposition.h" -#include "lltrans.h" -#include "llagentui.h" -#include "llmeshrepository.h" -#include "llfloaterregionrestarting.h" -#include "llpanelexperiencelisteditor.h" -#include -#include "llpanelexperiencepicker.h" -#include "llexperiencecache.h" -#include "llpanelexperiences.h" -#include "llcorehttputil.h" -#include "llavatarnamecache.h" -#include "llenvironment.h" - -const S32 TERRAIN_TEXTURE_COUNT = 4; -const S32 CORNER_COUNT = 4; - -const U32 MAX_LISTED_NAMES = 100; - -#define TMP_DISABLE_WLES // STORM-1180 - -///---------------------------------------------------------------------------- -/// Local class declaration -///---------------------------------------------------------------------------- - -class LLDispatchEstateUpdateInfo : public LLDispatchHandler -{ -public: - LLDispatchEstateUpdateInfo() {} - virtual ~LLDispatchEstateUpdateInfo() {} - virtual bool operator()( - const LLDispatcher* dispatcher, - const std::string& key, - const LLUUID& invoice, - const sparam_t& strings); -}; - -class LLDispatchSetEstateAccess : public LLDispatchHandler -{ -public: - LLDispatchSetEstateAccess() {} - virtual ~LLDispatchSetEstateAccess() {} - virtual bool operator()( - const LLDispatcher* dispatcher, - const std::string& key, - const LLUUID& invoice, - const sparam_t& strings); -}; - -class LLDispatchSetEstateExperience : public LLDispatchHandler -{ -public: - virtual bool operator()( - const LLDispatcher* dispatcher, - const std::string& key, - const LLUUID& invoice, - const sparam_t& strings); - - static LLSD getIDs( sparam_t::const_iterator it, sparam_t::const_iterator end, S32 count ); -}; - - -/* -void unpack_request_params( - LLMessageSystem* msg, - LLDispatcher::sparam_t& strings, - LLDispatcher::iparam_t& integers) -{ - char str_buf[MAX_STRING]; - S32 str_count = msg->getNumberOfBlocksFast(_PREHASH_StringData); - S32 i; - for (i = 0; i < str_count; ++i) - { - // we treat the SParam as binary data (since it might be an - // LLUUID in compressed form which may have embedded \0's,) - str_buf[0] = '\0'; - S32 data_size = msg->getSizeFast(_PREHASH_StringData, i, _PREHASH_SParam); - if (data_size >= 0) - { - msg->getBinaryDataFast(_PREHASH_StringData, _PREHASH_SParam, - str_buf, data_size, i, MAX_STRING - 1); - strings.push_back(std::string(str_buf, data_size)); - } - } - - U32 int_buf; - S32 int_count = msg->getNumberOfBlocksFast(_PREHASH_IntegerData); - for (i = 0; i < int_count; ++i) - { - msg->getU32("IntegerData", "IParam", int_buf, i); - integers.push_back(int_buf); - } -} -*/ - -class LLPanelRegionEnvironment : public LLPanelEnvironmentInfo -{ -public: - LLPanelRegionEnvironment(); - virtual ~LLPanelRegionEnvironment(); - - virtual void refresh() override; - - virtual bool isRegion() const override { return true; } - virtual LLParcel * getParcel() override { return nullptr; } - virtual bool canEdit() override { return LLEnvironment::instance().canAgentUpdateRegionEnvironment(); } - virtual bool isLargeEnough() override { return true; } // regions are always large enough. - - bool refreshFromRegion(LLViewerRegion* region); - - virtual bool postBuild() override; - virtual void onOpen(const LLSD& key) override {}; - - virtual S32 getParcelId() override { return INVALID_PARCEL_ID; } - -protected: - static const U32 DIRTY_FLAG_OVERRIDE; - - virtual void refreshFromSource() override; - - bool confirmUpdateEstateEnvironment(const LLSD& notification, const LLSD& response); - - void onChkAllowOverride(bool value); - -private: - bool mAllowOverrideRestore; - connection_t mCommitConnect; -}; - - - -bool estate_dispatch_initialized = false; - - -///---------------------------------------------------------------------------- -/// LLFloaterRegionInfo -///---------------------------------------------------------------------------- - -//S32 LLFloaterRegionInfo::sRequestSerial = 0; -LLUUID LLFloaterRegionInfo::sRequestInvoice; - - -LLFloaterRegionInfo::LLFloaterRegionInfo(const LLSD& seed) - : LLFloater(seed), - mEnvironmentPanel(NULL), - mRegionChangedCallback() -{} - -bool LLFloaterRegionInfo::postBuild() -{ - mTab = getChild("region_panels"); - mTab->setCommitCallback(boost::bind(&LLFloaterRegionInfo::onTabSelected, this, _2)); - - // contruct the panels - LLPanelRegionInfo* panel; - panel = new LLPanelEstateInfo; - mInfoPanels.push_back(panel); - panel->buildFromFile("panel_region_estate.xml"); - mTab->addTabPanel(LLTabContainer::TabPanelParams().panel(panel).select_tab(true)); - - panel = new LLPanelEstateAccess; - mInfoPanels.push_back(panel); - panel->buildFromFile("panel_region_access.xml"); - mTab->addTabPanel(panel); - - panel = new LLPanelEstateCovenant; - mInfoPanels.push_back(panel); - panel->buildFromFile("panel_region_covenant.xml"); - mTab->addTabPanel(panel); - - panel = new LLPanelRegionGeneralInfo; - mInfoPanels.push_back(panel); - panel->getCommitCallbackRegistrar().add("RegionInfo.ManageTelehub", boost::bind(&LLPanelRegionInfo::onClickManageTelehub, panel)); - panel->buildFromFile("panel_region_general.xml"); - mTab->addTabPanel(panel); - - panel = new LLPanelRegionTerrainInfo; - mInfoPanels.push_back(panel); - panel->buildFromFile("panel_region_terrain.xml"); - mTab->addTabPanel(panel); - - mEnvironmentPanel = new LLPanelRegionEnvironment; - mEnvironmentPanel->buildFromFile("panel_region_environment.xml"); -// mEnvironmentPanel->configureForRegion(); - mTab->addTabPanel(mEnvironmentPanel); - - panel = new LLPanelRegionDebugInfo; - mInfoPanels.push_back(panel); - panel->buildFromFile("panel_region_debug.xml"); - mTab->addTabPanel(panel); - - if(gDisconnected) - { - return true; - } - - if(!gAgent.getRegionCapability("RegionExperiences").empty()) - { - panel = new LLPanelRegionExperiences; - mInfoPanels.push_back(panel); - panel->buildFromFile("panel_region_experiences.xml"); - mTab->addTabPanel(panel); - } - - gMessageSystem->setHandlerFunc( - "EstateOwnerMessage", - &processEstateOwnerRequest); - - // Request region info when agent region changes. - mRegionChangedCallback = gAgent.addRegionChangedCallback(boost::bind(&LLFloaterRegionInfo::onRegionChanged, this)); - - return true; -} - -LLFloaterRegionInfo::~LLFloaterRegionInfo() -{ - if (mRegionChangedCallback.connected()) - { - mRegionChangedCallback.disconnect(); - } -} - -void LLFloaterRegionInfo::onOpen(const LLSD& key) -{ - if(gDisconnected) - { - disableTabCtrls(); - return; - } - refreshFromRegion(gAgent.getRegion()); - requestRegionInfo(); - - if (!mGodLevelChangeSlot.connected()) - { - mGodLevelChangeSlot = gAgent.registerGodLevelChanageListener(boost::bind(&LLFloaterRegionInfo::onGodLevelChange, this, _1)); - } -} - -void LLFloaterRegionInfo::onClose(bool app_quitting) -{ - if (mGodLevelChangeSlot.connected()) - { - mGodLevelChangeSlot.disconnect(); - } -} - -void LLFloaterRegionInfo::onRegionChanged() -{ - if (getVisible()) //otherwise onOpen will do request - { - requestRegionInfo(); - } -} - -// static -void LLFloaterRegionInfo::requestRegionInfo() -{ - LLTabContainer* tab = findChild("region_panels"); - if (tab) - { - tab->getChild("General")->setCtrlsEnabled(false); - tab->getChild("Debug")->setCtrlsEnabled(false); - tab->getChild("Terrain")->setCtrlsEnabled(false); - tab->getChild("Estate")->setCtrlsEnabled(false); - tab->getChild("Access")->setCtrlsEnabled(false); - } - - // Must allow anyone to request the RegionInfo data - // so non-owners/non-gods can see the values. - // Therefore can't use an EstateOwnerMessage JC - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("RequestRegionInfo"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - gAgent.sendReliableMessage(); -} - -// static -void LLFloaterRegionInfo::processEstateOwnerRequest(LLMessageSystem* msg,void**) -{ - static LLDispatcher dispatch; - LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance("region_info"); - if(!floater) - { - return; - } - - if (!estate_dispatch_initialized) - { - LLPanelEstateInfo::initDispatch(dispatch); - } - - LLPanelEstateInfo* panel = LLFloaterRegionInfo::getPanelEstate(); - - // unpack the message - std::string request; - LLUUID invoice; - LLDispatcher::sparam_t strings; - LLDispatcher::unpackMessage(msg, request, invoice, strings); - if(invoice != getLastInvoice()) - { - LL_WARNS() << "Mismatched Estate message: " << request << LL_ENDL; - return; - } - - //dispatch the message - dispatch.dispatch(request, invoice, strings); - - if (panel) - { - panel->updateControls(gAgent.getRegion()); - } -} - - -// static -void LLFloaterRegionInfo::processRegionInfo(LLMessageSystem* msg) -{ - LLPanel* panel; - LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance("region_info"); - if(!floater) - { - return; - } -#if 0 - // We need to re-request environment setting here, - // otherwise after we apply (send) updated region settings we won't get them back, - // so our environment won't be updated. - // This is also the way to know about externally changed region environment. - LLEnvManagerNew::instance().requestRegionSettings(); -#endif - LLTabContainer* tab = floater->getChild("region_panels"); - - LLViewerRegion* region = gAgent.getRegion(); - bool allow_modify = gAgent.isGodlike() || (region && region->canManageEstate()); - - // *TODO: Replace parsing msg with accessing the region info model. - LLRegionInfoModel& region_info = LLRegionInfoModel::instance(); - - // extract message - std::string sim_name; - std::string sim_type = LLTrans::getString("land_type_unknown"); - U64 region_flags; - U8 agent_limit; - S32 hard_agent_limit; - F32 object_bonus_factor; - U8 sim_access; - F32 water_height; - F32 terrain_raise_limit; - F32 terrain_lower_limit; - bool use_estate_sun; - F32 sun_hour; - msg->getString("RegionInfo", "SimName", sim_name); - msg->getU8("RegionInfo", "MaxAgents", agent_limit); - msg->getS32("RegionInfo2", "HardMaxAgents", hard_agent_limit); - msg->getF32("RegionInfo", "ObjectBonusFactor", object_bonus_factor); - msg->getU8("RegionInfo", "SimAccess", sim_access); - msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_WaterHeight, water_height); - msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_TerrainRaiseLimit, terrain_raise_limit); - msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_TerrainLowerLimit, terrain_lower_limit); - msg->getBOOL("RegionInfo", "UseEstateSun", use_estate_sun); - // actually the "last set" sun hour, not the current sun hour. JC - msg->getF32("RegionInfo", "SunHour", sun_hour); - // the only reasonable way to decide if we actually have any data is to - // check to see if any of these fields have nonzero sizes - if (msg->getSize("RegionInfo2", "ProductSKU") > 0 || - msg->getSize("RegionInfo2", "ProductName") > 0) - { - msg->getString("RegionInfo2", "ProductName", sim_type); - LLTrans::findString(sim_type, sim_type); // try localizing sim product name - } - - if (msg->has(_PREHASH_RegionInfo3)) - { - msg->getU64("RegionInfo3", "RegionFlagsExtended", region_flags); - } - else - { - U32 flags = 0; - msg->getU32("RegionInfo", "RegionFlags", flags); - region_flags = flags; - } - - if (msg->has(_PREHASH_RegionInfo5)) - { - F32 chat_whisper_range; - F32 chat_normal_range; - F32 chat_shout_range; - F32 chat_whisper_offset; - F32 chat_normal_offset; - F32 chat_shout_offset; - U32 chat_flags; - - msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatWhisperRange, chat_whisper_range); - msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatNormalRange, chat_normal_range); - msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatShoutRange, chat_shout_range); - msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatWhisperOffset, chat_whisper_offset); - msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatNormalOffset, chat_normal_offset); - msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatShoutOffset, chat_shout_offset); - msg->getU32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatFlags, chat_flags); - - LL_INFOS() << "Whisper range: " << chat_whisper_range << " normal range: " << chat_normal_range << " shout range: " << chat_shout_range - << " whisper offset: " << chat_whisper_offset << " normal offset: " << chat_normal_offset << " shout offset: " << chat_shout_offset - << " chat flags: " << chat_flags << LL_ENDL; - } - - // GENERAL PANEL - panel = tab->getChild("General"); - panel->getChild("region_text")->setValue(LLSD(sim_name)); - panel->getChild("region_type")->setValue(LLSD(sim_type)); - panel->getChild("version_channel_text")->setValue(gLastVersionChannel); - - panel->getChild("block_terraform_check")->setValue(is_flag_set(region_flags, REGION_FLAGS_BLOCK_TERRAFORM)); - panel->getChild("block_fly_check")->setValue(is_flag_set(region_flags, REGION_FLAGS_BLOCK_FLY)); - panel->getChild("block_fly_over_check")->setValue(is_flag_set(region_flags, REGION_FLAGS_BLOCK_FLYOVER)); - panel->getChild("allow_damage_check")->setValue(is_flag_set(region_flags, REGION_FLAGS_ALLOW_DAMAGE)); - panel->getChild("restrict_pushobject")->setValue(is_flag_set(region_flags, REGION_FLAGS_RESTRICT_PUSHOBJECT)); - panel->getChild("allow_land_resell_check")->setValue(is_flag_set(region_flags, REGION_FLAGS_BLOCK_LAND_RESELL)); - panel->getChild("allow_parcel_changes_check")->setValue(is_flag_set(region_flags, REGION_FLAGS_ALLOW_PARCEL_CHANGES)); - panel->getChild("block_parcel_search_check")->setValue(is_flag_set(region_flags, REGION_FLAGS_BLOCK_PARCEL_SEARCH)); - panel->getChild("agent_limit_spin")->setValue(LLSD((F32)agent_limit)); - panel->getChild("object_bonus_spin")->setValue(LLSD(object_bonus_factor)); - panel->getChild("access_combo")->setValue(LLSD(sim_access)); - - panel->getChild("agent_limit_spin")->setMaxValue(hard_agent_limit); - - LLPanelRegionGeneralInfo* panel_general = LLFloaterRegionInfo::getPanelGeneral(); - if (panel) - { - panel_general->setObjBonusFactor(object_bonus_factor); - } - - // detect teen grid for maturity - - U32 parent_estate_id; - msg->getU32("RegionInfo", "ParentEstateID", parent_estate_id); - bool teen_grid = (parent_estate_id == 5); // *TODO add field to estate table and test that - panel->getChildView("access_combo")->setEnabled(gAgent.isGodlike() || (region && region->canManageEstate() && !teen_grid)); - panel->setCtrlsEnabled(allow_modify); - - - // DEBUG PANEL - panel = tab->getChild("Debug"); - - panel->getChild("region_text")->setValue(LLSD(sim_name) ); - panel->getChild("disable_scripts_check")->setValue(LLSD((bool)(region_flags & REGION_FLAGS_SKIP_SCRIPTS))); - panel->getChild("disable_collisions_check")->setValue(LLSD((bool)(region_flags & REGION_FLAGS_SKIP_COLLISIONS))); - panel->getChild("disable_physics_check")->setValue(LLSD((bool)(region_flags & REGION_FLAGS_SKIP_PHYSICS))); - panel->setCtrlsEnabled(allow_modify); - - // TERRAIN PANEL - panel = tab->getChild("Terrain"); - - panel->getChild("region_text")->setValue(LLSD(sim_name)); - panel->getChild("water_height_spin")->setValue(region_info.mWaterHeight); - panel->getChild("terrain_raise_spin")->setValue(region_info.mTerrainRaiseLimit); - panel->getChild("terrain_lower_spin")->setValue(region_info.mTerrainLowerLimit); - - panel->setCtrlsEnabled(allow_modify); - - if (floater->getVisible()) - { - // Note: region info also causes LLRegionInfoModel::instance().update(msg); -> requestRegion(); -> changed message - // we need to know env version here and in update(msg) to know when to request and when not to, when to filter 'changed' - floater->refreshFromRegion(gAgent.getRegion()); - } // else will rerequest on onOpen either way -} - -// static -LLPanelEstateInfo* LLFloaterRegionInfo::getPanelEstate() -{ - LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance("region_info"); - if (!floater) return NULL; - LLTabContainer* tab = floater->getChild("region_panels"); - LLPanelEstateInfo* panel = (LLPanelEstateInfo*)tab->getChild("Estate"); - return panel; -} - -// static -LLPanelEstateAccess* LLFloaterRegionInfo::getPanelAccess() -{ - LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance("region_info"); - if (!floater) return NULL; - LLTabContainer* tab = floater->getChild("region_panels"); - LLPanelEstateAccess* panel = (LLPanelEstateAccess*)tab->getChild("Access"); - return panel; -} - -// static -LLPanelEstateCovenant* LLFloaterRegionInfo::getPanelCovenant() -{ - LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance("region_info"); - if (!floater) return NULL; - LLTabContainer* tab = floater->getChild("region_panels"); - LLPanelEstateCovenant* panel = (LLPanelEstateCovenant*)tab->getChild("Covenant"); - return panel; -} - -// static -LLPanelRegionGeneralInfo* LLFloaterRegionInfo::getPanelGeneral() -{ - LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance("region_info"); - if (!floater) return NULL; - LLTabContainer* tab = floater->getChild("region_panels"); - LLPanelRegionGeneralInfo* panel = (LLPanelRegionGeneralInfo*)tab->getChild("General"); - return panel; -} - -// static -LLPanelRegionEnvironment* LLFloaterRegionInfo::getPanelEnvironment() -{ - LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance("region_info"); - if (!floater) return NULL; - LLTabContainer* tab = floater->getChild("region_panels"); - LLPanelRegionEnvironment* panel = (LLPanelRegionEnvironment*)tab->getChild("panel_env_info"); - return panel; -} - -// static -LLPanelRegionTerrainInfo* LLFloaterRegionInfo::getPanelRegionTerrain() -{ - LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance("region_info"); - if (!floater) - { - llassert(floater); - return NULL; - } - - LLTabContainer* tab_container = floater->getChild("region_panels"); - LLPanelRegionTerrainInfo* panel = - dynamic_cast(tab_container->getChild("Terrain")); - llassert(panel); - return panel; -} - -LLPanelRegionExperiences* LLFloaterRegionInfo::getPanelExperiences() -{ - LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance("region_info"); - if (!floater) return NULL; - LLTabContainer* tab = floater->getChild("region_panels"); - return (LLPanelRegionExperiences*)tab->getChild("Experiences"); -} - -void LLFloaterRegionInfo::disableTabCtrls() -{ - LLTabContainer* tab = getChild("region_panels"); - - tab->getChild("General")->setCtrlsEnabled(false); - tab->getChild("Debug")->setCtrlsEnabled(false); - tab->getChild("Terrain")->setCtrlsEnabled(false); - tab->getChild("panel_env_info")->setCtrlsEnabled(false); - tab->getChild("Estate")->setCtrlsEnabled(false); - tab->getChild("Access")->setCtrlsEnabled(false); -} - -void LLFloaterRegionInfo::onTabSelected(const LLSD& param) -{ - LLPanel* active_panel = getChild(param.asString()); - active_panel->onOpen(LLSD()); -} - -void LLFloaterRegionInfo::refreshFromRegion(LLViewerRegion* region) -{ - if (!region) - { - return; - } - - // call refresh from region on all panels - for (const auto& infoPanel : mInfoPanels) - { - infoPanel->refreshFromRegion(region); - } - mEnvironmentPanel->refreshFromRegion(region); -} - -// public -void LLFloaterRegionInfo::refresh() -{ - for(info_panels_t::iterator iter = mInfoPanels.begin(); - iter != mInfoPanels.end(); ++iter) - { - (*iter)->refresh(); - } - mEnvironmentPanel->refresh(); -} - -void LLFloaterRegionInfo::enableTopButtons() -{ - getChildView("top_colliders_btn")->setEnabled(true); - getChildView("top_scripts_btn")->setEnabled(true); -} - -void LLFloaterRegionInfo::disableTopButtons() -{ - getChildView("top_colliders_btn")->setEnabled(false); - getChildView("top_scripts_btn")->setEnabled(false); -} - -void LLFloaterRegionInfo::onGodLevelChange(U8 god_level) -{ - LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance("region_info"); - if (floater && floater->getVisible()) - { - refreshFromRegion(gAgent.getRegion()); - } -} - -///---------------------------------------------------------------------------- -/// Local class implementation -///---------------------------------------------------------------------------- - -// -// LLPanelRegionInfo -// - -LLPanelRegionInfo::LLPanelRegionInfo() - : LLPanel() -{ -} - -void LLPanelRegionInfo::onBtnSet() -{ - if (sendUpdate()) - { - disableButton("apply_btn"); - } -} - -void LLPanelRegionInfo::onChangeChildCtrl(LLUICtrl* ctrl) -{ - updateChild(ctrl); // virtual function -} - -// Enables the "set" button if it is not already enabled -void LLPanelRegionInfo::onChangeAnything() -{ - enableButton("apply_btn"); - refresh(); -} - -// static -// Enables set button on change to line editor -void LLPanelRegionInfo::onChangeText(LLLineEditor* caller, void* user_data) -{ - LLPanelRegionInfo* panel = dynamic_cast(caller->getParent()); - if(panel) - { - panel->enableButton("apply_btn"); - panel->refresh(); - } -} - - -// virtual -bool LLPanelRegionInfo::postBuild() -{ - // If the panel has an Apply button, set a callback for it. - LLUICtrl* apply_btn = findChild("apply_btn"); - if (apply_btn) - { - apply_btn->setCommitCallback(boost::bind(&LLPanelRegionInfo::onBtnSet, this)); - } - - refresh(); - return true; -} - -// virtual -void LLPanelRegionInfo::updateChild(LLUICtrl* child_ctr) -{ -} - -// virtual -bool LLPanelRegionInfo::refreshFromRegion(LLViewerRegion* region) -{ - if (region) mHost = region->getHost(); - return true; -} - -void LLPanelRegionInfo::sendEstateOwnerMessage( - LLMessageSystem* msg, - const std::string& request, - const LLUUID& invoice, - const strings_t& strings) -{ - LL_INFOS() << "Sending estate request '" << request << "'" << LL_ENDL; - msg->newMessage("EstateOwnerMessage"); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addUUIDFast(_PREHASH_TransactionID, LLUUID::null); //not used - msg->nextBlock("MethodData"); - msg->addString("Method", request); - msg->addUUID("Invoice", invoice); - if(strings.empty()) - { - msg->nextBlock("ParamList"); - msg->addString("Parameter", NULL); - } - else - { - strings_t::const_iterator it = strings.begin(); - strings_t::const_iterator end = strings.end(); - for(; it != end; ++it) - { - msg->nextBlock("ParamList"); - msg->addString("Parameter", *it); - } - } - msg->sendReliable(mHost); -} - -void LLPanelRegionInfo::enableButton(const std::string& btn_name, bool enable) -{ - LLView* button = findChildView(btn_name); - if (button) button->setEnabled(enable); -} - -void LLPanelRegionInfo::disableButton(const std::string& btn_name) -{ - LLView* button = findChildView(btn_name); - if (button) button->setEnabled(false); -} - -void LLPanelRegionInfo::initCtrl(const std::string& name) -{ - getChild(name)->setCommitCallback(boost::bind(&LLPanelRegionInfo::onChangeAnything, this)); -} - -void LLPanelRegionInfo::onClickManageTelehub() -{ - LLFloaterReg::hideInstance("region_info"); - LLFloaterReg::showInstance("telehubs"); -} - -///////////////////////////////////////////////////////////////////////////// -// LLPanelRegionGeneralInfo -// -bool LLPanelRegionGeneralInfo::refreshFromRegion(LLViewerRegion* region) -{ - bool allow_modify = gAgent.isGodlike() || (region && region->canManageEstate()); - setCtrlsEnabled(allow_modify); - getChildView("apply_btn")->setEnabled(false); - getChildView("access_text")->setEnabled(allow_modify); - // getChildView("access_combo")->setEnabled(allow_modify); - // now set in processRegionInfo for teen grid detection - getChildView("kick_btn")->setEnabled(allow_modify); - getChildView("kick_all_btn")->setEnabled(allow_modify); - getChildView("im_btn")->setEnabled(allow_modify); - getChildView("manage_telehub_btn")->setEnabled(allow_modify); - - // Data gets filled in by processRegionInfo - - return LLPanelRegionInfo::refreshFromRegion(region); -} - -bool LLPanelRegionGeneralInfo::postBuild() -{ - // Enable the "Apply" button if something is changed. JC - initCtrl("block_terraform_check"); - initCtrl("block_fly_check"); - initCtrl("block_fly_over_check"); - initCtrl("allow_damage_check"); - initCtrl("allow_land_resell_check"); - initCtrl("allow_parcel_changes_check"); - initCtrl("agent_limit_spin"); - initCtrl("object_bonus_spin"); - initCtrl("access_combo"); - initCtrl("restrict_pushobject"); - initCtrl("block_parcel_search_check"); - - childSetAction("kick_btn", boost::bind(&LLPanelRegionGeneralInfo::onClickKick, this)); - childSetAction("kick_all_btn", onClickKickAll, this); - childSetAction("im_btn", onClickMessage, this); -// childSetAction("manage_telehub_btn", onClickManageTelehub, this); - - LLUICtrl* apply_btn = findChild("apply_btn"); - if (apply_btn) - { - apply_btn->setCommitCallback(boost::bind(&LLPanelRegionGeneralInfo::onBtnSet, this)); - } - - refresh(); - return true; -} - -void LLPanelRegionGeneralInfo::onBtnSet() -{ - if(mObjBonusFactor == getChild("object_bonus_spin")->getValue().asReal()) - { - if (sendUpdate()) - { - disableButton("apply_btn"); - } - } - else - { - LLNotificationsUtil::add("ChangeObjectBonusFactor", LLSD(), LLSD(), boost::bind(&LLPanelRegionGeneralInfo::onChangeObjectBonus, this, _1, _2)); - } -} - -bool LLPanelRegionGeneralInfo::onChangeObjectBonus(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) - { - if (sendUpdate()) - { - disableButton("apply_btn"); - } - } - return false; -} - -void LLPanelRegionGeneralInfo::onClickKick() -{ - LL_INFOS() << "LLPanelRegionGeneralInfo::onClickKick" << LL_ENDL; - - // this depends on the grandparent view being a floater - // in order to set up floater dependency - LLView * button = findChild("kick_btn"); - LLFloater* parent_floater = gFloaterView->getParentFloater(this); - LLFloater* child_floater = LLFloaterAvatarPicker::show(boost::bind(&LLPanelRegionGeneralInfo::onKickCommit, this, _1), - false, true, false, parent_floater->getName(), button); - if (child_floater) - { - parent_floater->addDependentFloater(child_floater); - } -} - -void LLPanelRegionGeneralInfo::onKickCommit(const uuid_vec_t& ids) -{ - if (ids.empty()) return; - if(ids[0].notNull()) - { - strings_t strings; - // [0] = our agent id - // [1] = target agent id - std::string buffer; - gAgent.getID().toString(buffer); - strings.push_back(buffer); - - ids[0].toString(buffer); - strings.push_back(strings_t::value_type(buffer)); - - LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); - sendEstateOwnerMessage(gMessageSystem, "teleporthomeuser", invoice, strings); - } -} - -// static -void LLPanelRegionGeneralInfo::onClickKickAll(void* userdata) -{ - LL_INFOS() << "LLPanelRegionGeneralInfo::onClickKickAll" << LL_ENDL; - LLNotificationsUtil::add("KickUsersFromRegion", - LLSD(), - LLSD(), - boost::bind(&LLPanelRegionGeneralInfo::onKickAllCommit, (LLPanelRegionGeneralInfo*)userdata, _1, _2)); -} - -bool LLPanelRegionGeneralInfo::onKickAllCommit(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) - { - strings_t strings; - // [0] = our agent id - std::string buffer; - gAgent.getID().toString(buffer); - strings.push_back(buffer); - - LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); - // historical message name - sendEstateOwnerMessage(gMessageSystem, "teleporthomeallusers", invoice, strings); - } - return false; -} - -// static -void LLPanelRegionGeneralInfo::onClickMessage(void* userdata) -{ - LL_INFOS() << "LLPanelRegionGeneralInfo::onClickMessage" << LL_ENDL; - LLNotificationsUtil::add("MessageRegion", - LLSD(), - LLSD(), - boost::bind(&LLPanelRegionGeneralInfo::onMessageCommit, (LLPanelRegionGeneralInfo*)userdata, _1, _2)); -} - -// static -bool LLPanelRegionGeneralInfo::onMessageCommit(const LLSD& notification, const LLSD& response) -{ - if(LLNotificationsUtil::getSelectedOption(notification, response) != 0) return false; - - std::string text = response["message"].asString(); - if (text.empty()) return false; - - LL_INFOS() << "Message to everyone: " << text << LL_ENDL; - strings_t strings; - // [0] grid_x, unused here - // [1] grid_y, unused here - // [2] agent_id of sender - // [3] sender name - // [4] message - strings.push_back("-1"); - strings.push_back("-1"); - std::string buffer; - gAgent.getID().toString(buffer); - strings.push_back(buffer); - std::string name; - LLAgentUI::buildFullname(name); - strings.push_back(strings_t::value_type(name)); - strings.push_back(strings_t::value_type(text)); - LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); - sendEstateOwnerMessage(gMessageSystem, "simulatormessage", invoice, strings); - return false; -} - -// setregioninfo -// strings[0] = 'Y' - block terraform, 'N' - not -// strings[1] = 'Y' - block fly, 'N' - not -// strings[2] = 'Y' - allow damage, 'N' - not -// strings[3] = 'Y' - allow land sale, 'N' - not -// strings[4] = agent limit -// strings[5] = object bonus -// strings[6] = sim access (0 = unknown, 13 = PG, 21 = Mature, 42 = Adult) -// strings[7] = restrict pushobject -// strings[8] = 'Y' - allow parcel subdivide, 'N' - not -// strings[9] = 'Y' - block parcel search, 'N' - allow -bool LLPanelRegionGeneralInfo::sendUpdate() -{ - LL_INFOS() << "LLPanelRegionGeneralInfo::sendUpdate()" << LL_ENDL; - - // First try using a Cap. If that fails use the old method. - LLSD body; - std::string url = gAgent.getRegionCapability("DispatchRegionInfo"); - if (!url.empty()) - { - body["block_terraform"] = getChild("block_terraform_check")->getValue(); - body["block_fly"] = getChild("block_fly_check")->getValue(); - body["block_fly_over"] = getChild("block_fly_over_check")->getValue(); - body["allow_damage"] = getChild("allow_damage_check")->getValue(); - body["allow_land_resell"] = getChild("allow_land_resell_check")->getValue(); - body["agent_limit"] = getChild("agent_limit_spin")->getValue(); - body["prim_bonus"] = getChild("object_bonus_spin")->getValue(); - body["sim_access"] = getChild("access_combo")->getValue(); - body["restrict_pushobject"] = getChild("restrict_pushobject")->getValue(); - body["allow_parcel_changes"] = getChild("allow_parcel_changes_check")->getValue(); - body["block_parcel_search"] = getChild("block_parcel_search_check")->getValue(); - - LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, body, - "Region info update posted.", "Region info update not posted."); - } - else - { - strings_t strings; - std::string buffer; - - buffer = llformat("%s", (getChild("block_terraform_check")->getValue().asBoolean() ? "Y" : "N")); - strings.push_back(strings_t::value_type(buffer)); - - buffer = llformat("%s", (getChild("block_fly_check")->getValue().asBoolean() ? "Y" : "N")); - strings.push_back(strings_t::value_type(buffer)); - - buffer = llformat("%s", (getChild("allow_damage_check")->getValue().asBoolean() ? "Y" : "N")); - strings.push_back(strings_t::value_type(buffer)); - - buffer = llformat("%s", (getChild("allow_land_resell_check")->getValue().asBoolean() ? "Y" : "N")); - strings.push_back(strings_t::value_type(buffer)); - - F32 value = (F32)getChild("agent_limit_spin")->getValue().asReal(); - buffer = llformat("%f", value); - strings.push_back(strings_t::value_type(buffer)); - - value = (F32)getChild("object_bonus_spin")->getValue().asReal(); - buffer = llformat("%f", value); - strings.push_back(strings_t::value_type(buffer)); - - buffer = llformat("%d", getChild("access_combo")->getValue().asInteger()); - strings.push_back(strings_t::value_type(buffer)); - - buffer = llformat("%s", (getChild("restrict_pushobject")->getValue().asBoolean() ? "Y" : "N")); - strings.push_back(strings_t::value_type(buffer)); - - buffer = llformat("%s", (getChild("allow_parcel_changes_check")->getValue().asBoolean() ? "Y" : "N")); - strings.push_back(strings_t::value_type(buffer)); - - LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); - sendEstateOwnerMessage(gMessageSystem, "setregioninfo", invoice, strings); - } - - // if we changed access levels, tell user about it - LLViewerRegion* region = gAgent.getRegion(); - if (region && (getChild("access_combo")->getValue().asInteger() != region->getSimAccess()) ) - { - LLNotificationsUtil::add("RegionMaturityChange"); - } - - return true; -} - -///////////////////////////////////////////////////////////////////////////// -// LLPanelRegionDebugInfo -///////////////////////////////////////////////////////////////////////////// -bool LLPanelRegionDebugInfo::postBuild() -{ - LLPanelRegionInfo::postBuild(); - initCtrl("disable_scripts_check"); - initCtrl("disable_collisions_check"); - initCtrl("disable_physics_check"); - - childSetAction("choose_avatar_btn", boost::bind(&LLPanelRegionDebugInfo::onClickChooseAvatar, this)); - childSetAction("return_btn", onClickReturn, this); - childSetAction("top_colliders_btn", onClickTopColliders, this); - childSetAction("top_scripts_btn", onClickTopScripts, this); - childSetAction("restart_btn", onClickRestart, this); - childSetAction("cancel_restart_btn", onClickCancelRestart, this); - childSetAction("region_debug_console_btn", onClickDebugConsole, this); - - return true; -} - -// virtual -bool LLPanelRegionDebugInfo::refreshFromRegion(LLViewerRegion* region) -{ - bool allow_modify = gAgent.isGodlike() || (region && region->canManageEstate()); - setCtrlsEnabled(allow_modify); - getChildView("apply_btn")->setEnabled(false); - getChildView("target_avatar_name")->setEnabled(false); - - getChildView("choose_avatar_btn")->setEnabled(allow_modify); - getChildView("return_scripts")->setEnabled(allow_modify && !mTargetAvatar.isNull()); - getChildView("return_other_land")->setEnabled(allow_modify && !mTargetAvatar.isNull()); - getChildView("return_estate_wide")->setEnabled(allow_modify && !mTargetAvatar.isNull()); - getChildView("return_btn")->setEnabled(allow_modify && !mTargetAvatar.isNull()); - getChildView("top_colliders_btn")->setEnabled(allow_modify); - getChildView("top_scripts_btn")->setEnabled(allow_modify); - getChildView("restart_btn")->setEnabled(allow_modify); - getChildView("cancel_restart_btn")->setEnabled(allow_modify); - getChildView("region_debug_console_btn")->setEnabled(allow_modify); - - return LLPanelRegionInfo::refreshFromRegion(region); -} - -// virtual -bool LLPanelRegionDebugInfo::sendUpdate() -{ - LL_INFOS() << "LLPanelRegionDebugInfo::sendUpdate" << LL_ENDL; - strings_t strings; - std::string buffer; - - buffer = llformat("%s", (getChild("disable_scripts_check")->getValue().asBoolean() ? "Y" : "N")); - strings.push_back(buffer); - - buffer = llformat("%s", (getChild("disable_collisions_check")->getValue().asBoolean() ? "Y" : "N")); - strings.push_back(buffer); - - buffer = llformat("%s", (getChild("disable_physics_check")->getValue().asBoolean() ? "Y" : "N")); - strings.push_back(buffer); - - LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); - sendEstateOwnerMessage(gMessageSystem, "setregiondebug", invoice, strings); - return true; -} - -void LLPanelRegionDebugInfo::onClickChooseAvatar() -{ - LLView * button = findChild("choose_avatar_btn"); - LLFloater* parent_floater = gFloaterView->getParentFloater(this); - LLFloater * child_floater = LLFloaterAvatarPicker::show(boost::bind(&LLPanelRegionDebugInfo::callbackAvatarID, this, _1, _2), - false, true, false, parent_floater->getName(), button); - if (child_floater) - { - parent_floater->addDependentFloater(child_floater); - } -} - - -void LLPanelRegionDebugInfo::callbackAvatarID(const uuid_vec_t& ids, const std::vector names) -{ - if (ids.empty() || names.empty()) return; - mTargetAvatar = ids[0]; - getChild("target_avatar_name")->setValue(LLSD(names[0].getCompleteName())); - refreshFromRegion( gAgent.getRegion() ); -} - -// static -void LLPanelRegionDebugInfo::onClickReturn(void* data) -{ - LLPanelRegionDebugInfo* panelp = (LLPanelRegionDebugInfo*) data; - if (panelp->mTargetAvatar.isNull()) return; - - LLSD args; - args["USER_NAME"] = panelp->getChild("target_avatar_name")->getValue().asString(); - LLSD payload; - payload["avatar_id"] = panelp->mTargetAvatar; - - U32 flags = SWD_ALWAYS_RETURN_OBJECTS; - - if (panelp->getChild("return_scripts")->getValue().asBoolean()) - { - flags |= SWD_SCRIPTED_ONLY; - } - - if (panelp->getChild("return_other_land")->getValue().asBoolean()) - { - flags |= SWD_OTHERS_LAND_ONLY; - } - payload["flags"] = int(flags); - payload["return_estate_wide"] = panelp->getChild("return_estate_wide")->getValue().asBoolean(); - LLNotificationsUtil::add("EstateObjectReturn", args, payload, - boost::bind(&LLPanelRegionDebugInfo::callbackReturn, panelp, _1, _2)); -} - -bool LLPanelRegionDebugInfo::callbackReturn(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option != 0) return false; - - LLUUID target_avatar = notification["payload"]["avatar_id"].asUUID(); - if (!target_avatar.isNull()) - { - U32 flags = notification["payload"]["flags"].asInteger(); - bool return_estate_wide = notification["payload"]["return_estate_wide"]; - if (return_estate_wide) - { - // send as estate message - routed by spaceserver to all regions in estate - strings_t strings; - strings.push_back(llformat("%d", flags)); - strings.push_back(target_avatar.asString()); - - LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); - - sendEstateOwnerMessage(gMessageSystem, "estateobjectreturn", invoice, strings); - } - else - { - // send to this simulator only - send_sim_wide_deletes(target_avatar, flags); - } - } - return false; -} - - -// static -void LLPanelRegionDebugInfo::onClickTopColliders(void* data) -{ - LLPanelRegionDebugInfo* self = (LLPanelRegionDebugInfo*)data; - strings_t strings; - strings.push_back("1"); // one physics step - LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); - LLFloaterTopObjects* instance = LLFloaterReg::getTypedInstance("top_objects"); - if(!instance) return; - LLFloaterReg::showInstance("top_objects"); - instance->clearList(); - instance->disableRefreshBtn(); - - self->getChildView("top_colliders_btn")->setEnabled(false); - self->getChildView("top_scripts_btn")->setEnabled(false); - - self->sendEstateOwnerMessage(gMessageSystem, "colliders", invoice, strings); -} - -// static -void LLPanelRegionDebugInfo::onClickTopScripts(void* data) -{ - LLPanelRegionDebugInfo* self = (LLPanelRegionDebugInfo*)data; - strings_t strings; - strings.push_back("6"); // top 5 scripts - LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); - LLFloaterTopObjects* instance = LLFloaterReg::getTypedInstance("top_objects"); - if(!instance) return; - LLFloaterReg::showInstance("top_objects"); - instance->clearList(); - instance->disableRefreshBtn(); - - self->getChildView("top_colliders_btn")->setEnabled(false); - self->getChildView("top_scripts_btn")->setEnabled(false); - - self->sendEstateOwnerMessage(gMessageSystem, "scripts", invoice, strings); -} - -// static -void LLPanelRegionDebugInfo::onClickRestart(void* data) -{ - LLNotificationsUtil::add("ConfirmRestart", LLSD(), LLSD(), - boost::bind(&LLPanelRegionDebugInfo::callbackRestart, (LLPanelRegionDebugInfo*)data, _1, _2)); -} - -bool LLPanelRegionDebugInfo::callbackRestart(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option != 0) return false; - - strings_t strings; - strings.push_back("120"); - LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); - sendEstateOwnerMessage(gMessageSystem, "restart", invoice, strings); - return false; -} - -// static -void LLPanelRegionDebugInfo::onClickCancelRestart(void* data) -{ - LLPanelRegionDebugInfo* self = (LLPanelRegionDebugInfo*)data; - strings_t strings; - strings.push_back("-1"); - LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); - self->sendEstateOwnerMessage(gMessageSystem, "restart", invoice, strings); -} - -// static -void LLPanelRegionDebugInfo::onClickDebugConsole(void* data) -{ - LLFloaterReg::showInstance("region_debug_console"); -} - -bool LLPanelRegionTerrainInfo::validateTextureSizes() -{ - static const S32 MAX_TERRAIN_TEXTURE_SIZE = 1024; - for(S32 i = 0; i < TERRAIN_TEXTURE_COUNT; ++i) - { - std::string buffer; - buffer = llformat("texture_detail_%d", i); - LLTextureCtrl* texture_ctrl = getChild(buffer); - if (!texture_ctrl) continue; - - LLUUID image_asset_id = texture_ctrl->getImageAssetID(); - LLViewerTexture* img = LLViewerTextureManager::getFetchedTexture(image_asset_id); - S32 components = img->getComponents(); - // Must ask for highest resolution version's width. JC - S32 width = img->getFullWidth(); - S32 height = img->getFullHeight(); - - //LL_INFOS() << "texture detail " << i << " is " << width << "x" << height << "x" << components << LL_ENDL; - - if (components != 3) - { - LLSD args; - args["TEXTURE_NUM"] = i+1; - args["TEXTURE_BIT_DEPTH"] = llformat("%d",components * 8); - args["MAX_SIZE"] = MAX_TERRAIN_TEXTURE_SIZE; - LLNotificationsUtil::add("InvalidTerrainBitDepth", args); - return false; - } - - if (width > MAX_TERRAIN_TEXTURE_SIZE || height > MAX_TERRAIN_TEXTURE_SIZE) - { - - LLSD args; - args["TEXTURE_NUM"] = i+1; - args["TEXTURE_SIZE_X"] = width; - args["TEXTURE_SIZE_Y"] = height; - args["MAX_SIZE"] = MAX_TERRAIN_TEXTURE_SIZE; - LLNotificationsUtil::add("InvalidTerrainSize", args); - return false; - - } - } - - return true; -} - -bool LLPanelRegionTerrainInfo::validateTextureHeights() -{ - for (S32 i = 0; i < CORNER_COUNT; ++i) - { - std::string low = llformat("height_start_spin_%d", i); - std::string high = llformat("height_range_spin_%d", i); - - if (getChild(low)->getValue().asReal() > getChild(high)->getValue().asReal()) - { - return false; - } - } - - return true; -} - -///////////////////////////////////////////////////////////////////////////// -// LLPanelRegionTerrainInfo -///////////////////////////////////////////////////////////////////////////// -// Initialize statics - -bool LLPanelRegionTerrainInfo::postBuild() -{ - LLPanelRegionInfo::postBuild(); - - initCtrl("water_height_spin"); - initCtrl("terrain_raise_spin"); - initCtrl("terrain_lower_spin"); - - std::string buffer; - for(S32 i = 0; i < TERRAIN_TEXTURE_COUNT; ++i) - { - buffer = llformat("texture_detail_%d", i); - initCtrl(buffer); - } - - for(S32 i = 0; i < CORNER_COUNT; ++i) - { - buffer = llformat("height_start_spin_%d", i); - initCtrl(buffer); - buffer = llformat("height_range_spin_%d", i); - initCtrl(buffer); - } - - childSetAction("download_raw_btn", onClickDownloadRaw, this); - childSetAction("upload_raw_btn", onClickUploadRaw, this); - childSetAction("bake_terrain_btn", onClickBakeTerrain, this); - - mAskedTextureHeights = false; - mConfirmedTextureHeights = false; - - return LLPanelRegionInfo::postBuild(); -} - -// virtual -bool LLPanelRegionTerrainInfo::refreshFromRegion(LLViewerRegion* region) -{ - bool owner_or_god = gAgent.isGodlike() - || (region && (region->getOwner() == gAgent.getID())); - bool owner_or_god_or_manager = owner_or_god - || (region && region->isEstateManager()); - setCtrlsEnabled(owner_or_god_or_manager); - - getChildView("apply_btn")->setEnabled(false); - - if (region) - { - getChild("region_text")->setValue(LLSD(region->getName())); - - LLVLComposition* compp = region->getComposition(); - LLTextureCtrl* texture_ctrl; - std::string buffer; - for(S32 i = 0; i < TERRAIN_TEXTURE_COUNT; ++i) - { - buffer = llformat("texture_detail_%d", i); - texture_ctrl = getChild(buffer); - if(texture_ctrl) - { - LL_DEBUGS() << "Detail Texture " << i << ": " - << compp->getDetailTextureID(i) << LL_ENDL; - LLUUID tmp_id(compp->getDetailTextureID(i)); - texture_ctrl->setImageAssetID(tmp_id); - } - } - - for(S32 i = 0; i < CORNER_COUNT; ++i) - { - buffer = llformat("height_start_spin_%d", i); - getChild(buffer)->setValue(LLSD(compp->getStartHeight(i))); - buffer = llformat("height_range_spin_%d", i); - getChild(buffer)->setValue(LLSD(compp->getHeightRange(i))); - } - } - else - { - LL_DEBUGS() << "no region set" << LL_ENDL; - getChild("region_text")->setValue(LLSD("")); - } - - getChildView("download_raw_btn")->setEnabled(owner_or_god); - getChildView("upload_raw_btn")->setEnabled(owner_or_god); - getChildView("bake_terrain_btn")->setEnabled(owner_or_god); - - return LLPanelRegionInfo::refreshFromRegion(region); -} - - -// virtual -bool LLPanelRegionTerrainInfo::sendUpdate() -{ - LL_INFOS() << "LLPanelRegionTerrainInfo::sendUpdate" << LL_ENDL; - std::string buffer; - strings_t strings; - LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); - - // update the model - LLRegionInfoModel& region_info = LLRegionInfoModel::instance(); - region_info.mWaterHeight = (F32) getChild("water_height_spin")->getValue().asReal(); - region_info.mTerrainRaiseLimit = (F32) getChild("terrain_raise_spin")->getValue().asReal(); - region_info.mTerrainLowerLimit = (F32) getChild("terrain_lower_spin")->getValue().asReal(); - - // and sync the region with it - region_info.sendRegionTerrain(invoice); - - // ======================================= - // Assemble and send texturedetail message - - // Make sure user hasn't chosen wacky textures. - if (!validateTextureSizes()) - { - return false; - } - - // Check if terrain Elevation Ranges are correct - if (gSavedSettings.getBOOL("RegionCheckTextureHeights") && !validateTextureHeights()) - { - if (!mAskedTextureHeights) - { - LLNotificationsUtil::add("ConfirmTextureHeights", LLSD(), LLSD(), boost::bind(&LLPanelRegionTerrainInfo::callbackTextureHeights, this, _1, _2)); - mAskedTextureHeights = true; - return false; - } - else if (!mConfirmedTextureHeights) - { - return false; - } - } - - LLTextureCtrl* texture_ctrl; - std::string id_str; - LLMessageSystem* msg = gMessageSystem; - - for(S32 i = 0; i < TERRAIN_TEXTURE_COUNT; ++i) - { - buffer = llformat("texture_detail_%d", i); - texture_ctrl = getChild(buffer); - if(texture_ctrl) - { - LLUUID tmp_id(texture_ctrl->getImageAssetID()); - tmp_id.toString(id_str); - buffer = llformat("%d %s", i, id_str.c_str()); - strings.push_back(buffer); - } - } - sendEstateOwnerMessage(msg, "texturedetail", invoice, strings); - strings.clear(); - - // ======================================== - // Assemble and send textureheights message - - for(S32 i = 0; i < CORNER_COUNT; ++i) - { - buffer = llformat("height_start_spin_%d", i); - std::string buffer2 = llformat("height_range_spin_%d", i); - std::string buffer3 = llformat("%d %f %f", i, (F32)getChild(buffer)->getValue().asReal(), (F32)getChild(buffer2)->getValue().asReal()); - strings.push_back(buffer3); - } - sendEstateOwnerMessage(msg, "textureheights", invoice, strings); - strings.clear(); - - // ======================================== - // Send texturecommit message - - sendEstateOwnerMessage(msg, "texturecommit", invoice, strings); - - return true; -} - -bool LLPanelRegionTerrainInfo::callbackTextureHeights(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) // ok - { - mConfirmedTextureHeights = true; - } - else if (option == 1) // cancel - { - mConfirmedTextureHeights = false; - } - else if (option == 2) // don't ask - { - gSavedSettings.setBOOL("RegionCheckTextureHeights", false); - mConfirmedTextureHeights = true; - } - - onBtnSet(); - - mAskedTextureHeights = false; - return false; -} - -// static -void LLPanelRegionTerrainInfo::onClickDownloadRaw(void* data) -{ - LLFilePicker& picker = LLFilePicker::instance(); - if (!picker.getSaveFile(LLFilePicker::FFSAVE_RAW, "terrain.raw")) - { - LL_WARNS() << "No file" << LL_ENDL; - return; - } - std::string filepath = picker.getFirstFile(); - gXferManager->expectFileForRequest(filepath); - - LLPanelRegionTerrainInfo* self = (LLPanelRegionTerrainInfo*)data; - strings_t strings; - strings.push_back("download filename"); - strings.push_back(filepath); - LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); - self->sendEstateOwnerMessage(gMessageSystem, "terrain", invoice, strings); -} - -// static -void LLPanelRegionTerrainInfo::onClickUploadRaw(void* data) -{ - LLFilePicker& picker = LLFilePicker::instance(); - if (!picker.getOpenFile(LLFilePicker::FFLOAD_RAW)) - { - LL_WARNS() << "No file" << LL_ENDL; - return; - } - std::string filepath = picker.getFirstFile(); - gXferManager->expectFileForTransfer(filepath); - - LLPanelRegionTerrainInfo* self = (LLPanelRegionTerrainInfo*)data; - strings_t strings; - strings.push_back("upload filename"); - strings.push_back(filepath); - LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); - self->sendEstateOwnerMessage(gMessageSystem, "terrain", invoice, strings); - - LLNotificationsUtil::add("RawUploadStarted"); -} - -// static -void LLPanelRegionTerrainInfo::onClickBakeTerrain(void* data) -{ - LLNotificationsUtil::add("ConfirmBakeTerrain", LLSD(), LLSD(), boost::bind(&LLPanelRegionTerrainInfo::callbackBakeTerrain, (LLPanelRegionTerrainInfo*)data, _1, _2)); -} - -bool LLPanelRegionTerrainInfo::callbackBakeTerrain(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option != 0) return false; - - strings_t strings; - strings.push_back("bake"); - LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); - sendEstateOwnerMessage(gMessageSystem, "terrain", invoice, strings); - - return false; -} - -///////////////////////////////////////////////////////////////////////////// -// LLPanelEstateInfo -// - -LLPanelEstateInfo::LLPanelEstateInfo() -: LLPanelRegionInfo(), - mEstateID(0) // invalid -{ - LLEstateInfoModel& estate_info = LLEstateInfoModel::instance(); - estate_info.setCommitCallback(boost::bind(&LLPanelEstateInfo::refreshFromEstate, this)); - estate_info.setUpdateCallback(boost::bind(&LLPanelEstateInfo::refreshFromEstate, this)); -} - -// static -void LLPanelEstateInfo::initDispatch(LLDispatcher& dispatch) -{ - std::string name; - - name.assign("estateupdateinfo"); - static LLDispatchEstateUpdateInfo estate_update_info; - dispatch.addHandler(name, &estate_update_info); - - name.assign("setaccess"); - static LLDispatchSetEstateAccess set_access; - dispatch.addHandler(name, &set_access); - - name.assign("setexperience"); - static LLDispatchSetEstateExperience set_experience; - dispatch.addHandler(name, &set_experience); - - estate_dispatch_initialized = true; -} - -//--------------------------------------------------------------------------- -// Kick from estate methods -//--------------------------------------------------------------------------- - -void LLPanelEstateInfo::onClickKickUser() -{ - // this depends on the grandparent view being a floater - // in order to set up floater dependency - LLView * button = findChild("kick_user_from_estate_btn"); - LLFloater* parent_floater = gFloaterView->getParentFloater(this); - LLFloater* child_floater = LLFloaterAvatarPicker::show(boost::bind(&LLPanelEstateInfo::onKickUserCommit, this, _1), - false, true, false, parent_floater->getName(), button); - if (child_floater) - { - parent_floater->addDependentFloater(child_floater); - } -} - -void LLPanelEstateInfo::onKickUserCommit(const uuid_vec_t& ids) -{ - if (ids.empty()) return; - - //Bring up a confirmation dialog - LLSD args; - args["EVIL_USER"] = LLSLURL("agent", ids[0], "completename").getSLURLString(); - LLSD payload; - payload["agent_id"] = ids[0]; - LLNotificationsUtil::add("EstateKickUser", args, payload, boost::bind(&LLPanelEstateInfo::kickUserConfirm, this, _1, _2)); - -} - -bool LLPanelEstateInfo::kickUserConfirm(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - switch(option) - { - case 0: - { - //Kick User - strings_t strings; - strings.push_back(notification["payload"]["agent_id"].asString()); - - sendEstateOwnerMessage(gMessageSystem, "kickestate", LLFloaterRegionInfo::getLastInvoice(), strings); - break; - } - default: - break; - } - return false; -} - -//--------------------------------------------------------------------------- -// Core Add/Remove estate access methods -// TODO: INTERNATIONAL: don't build message text here; -// instead, create multiple translatable messages and choose -// one based on the status. -//--------------------------------------------------------------------------- -std::string all_estates_text() -{ - LLPanelEstateInfo* panel = LLFloaterRegionInfo::getPanelEstate(); - if (!panel) return "(" + LLTrans::getString("RegionInfoError") + ")"; - - LLStringUtil::format_map_t args; - std::string owner = panel->getOwnerName(); - - LLViewerRegion* region = gAgent.getRegion(); - if (gAgent.isGodlike()) - { - args["[OWNER]"] = owner.c_str(); - return LLTrans::getString("RegionInfoAllEstatesOwnedBy", args); - } - else if (region && region->getOwner() == gAgent.getID()) - { - return LLTrans::getString("RegionInfoAllEstatesYouOwn"); - } - else if (region && region->isEstateManager()) - { - args["[OWNER]"] = owner.c_str(); - return LLTrans::getString("RegionInfoAllEstatesYouManage", args); - } - else - { - return "(" + LLTrans::getString("RegionInfoError") + ")"; - } -} - -// static -bool LLPanelEstateInfo::isLindenEstate() -{ - U32 estate_id = LLEstateInfoModel::instance().getID(); - return (estate_id <= ESTATE_LAST_LINDEN); -} - -struct LLEstateAccessChangeInfo -{ - LLEstateAccessChangeInfo(const LLSD& sd) - { - mDialogName = sd["dialog_name"].asString(); - mOperationFlag = (U32)sd["operation"].asInteger(); - LLSD::array_const_iterator end_it = sd["allowed_ids"].endArray(); - for (LLSD::array_const_iterator id_it = sd["allowed_ids"].beginArray(); - id_it != end_it; - ++id_it) - { - mAgentOrGroupIDs.push_back(id_it->asUUID()); - } - } - - const LLSD asLLSD() const - { - LLSD sd; - sd["name"] = mDialogName; - sd["operation"] = (S32)mOperationFlag; - for (U32 i = 0; i < mAgentOrGroupIDs.size(); ++i) - { - sd["allowed_ids"].append(mAgentOrGroupIDs[i]); - if (mAgentNames.size() > i) - { - sd["allowed_names"].append(mAgentNames[i].asLLSD()); - } - } - return sd; - } - - U32 mOperationFlag; // ESTATE_ACCESS_BANNED_AGENT_ADD, _REMOVE, etc. - std::string mDialogName; - uuid_vec_t mAgentOrGroupIDs; // List of agent IDs to apply to this change - std::vector mAgentNames; // Optional list of the agent names for notifications -}; - -// static -void LLPanelEstateInfo::updateEstateOwnerName(const std::string& name) -{ - LLPanelEstateInfo* panelp = LLFloaterRegionInfo::getPanelEstate(); - if (panelp) - { - panelp->setOwnerName(name); - } -} - -// static -void LLPanelEstateInfo::updateEstateName(const std::string& name) -{ - LLPanelEstateInfo* panelp = LLFloaterRegionInfo::getPanelEstate(); - if (panelp) - { - panelp->getChildRef("estate_name").setText(name); - } -} - -void LLPanelEstateInfo::updateControls(LLViewerRegion* region) -{ - bool god = gAgent.isGodlike(); - bool owner = (region && (region->getOwner() == gAgent.getID())); - bool manager = (region && region->isEstateManager()); - setCtrlsEnabled(god || owner || manager); - - getChildView("apply_btn")->setEnabled(false); - getChildView("estate_owner")->setEnabled(true); - getChildView("message_estate_btn")->setEnabled(god || owner || manager); - getChildView("kick_user_from_estate_btn")->setEnabled(god || owner || manager); - - refresh(); -} - -bool LLPanelEstateInfo::refreshFromRegion(LLViewerRegion* region) -{ - updateControls(region); - - // let the parent class handle the general data collection. - bool rv = LLPanelRegionInfo::refreshFromRegion(region); - - // We want estate info. To make sure it works across region - // boundaries and multiple packets, we add a serial number to the - // integers and track against that on update. - strings_t strings; - //integers_t integers; - //LLFloaterRegionInfo::incrementSerial(); - LLFloaterRegionInfo::nextInvoice(); - LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); - //integers.push_back(LLFloaterRegionInfo::());::getPanelEstate(); - - - sendEstateOwnerMessage(gMessageSystem, "getinfo", invoice, strings); - - refresh(); - - return rv; -} - -void LLPanelEstateInfo::updateChild(LLUICtrl* child_ctrl) -{ - // Ensure appropriate state of the management ui. - updateControls(gAgent.getRegion()); -} - -bool LLPanelEstateInfo::estateUpdate(LLMessageSystem* msg) -{ - LL_INFOS() << "LLPanelEstateInfo::estateUpdate()" << LL_ENDL; - return false; -} - - -bool LLPanelEstateInfo::postBuild() -{ - // set up the callbacks for the generic controls - initCtrl("externally_visible_radio"); - initCtrl("allow_direct_teleport"); - initCtrl("limit_payment"); - initCtrl("limit_age_verified"); - initCtrl("limit_bots"); - initCtrl("voice_chat_check"); - initCtrl("parcel_access_override"); - - childSetAction("message_estate_btn", boost::bind(&LLPanelEstateInfo::onClickMessageEstate, this)); - childSetAction("kick_user_from_estate_btn", boost::bind(&LLPanelEstateInfo::onClickKickUser, this)); - - getChild("parcel_access_override")->setCommitCallback(boost::bind(&LLPanelEstateInfo::onChangeAccessOverride, this)); - - getChild("externally_visible_radio")->setFocus(true); - - getChild("estate_owner")->setIsFriendCallback(LLAvatarActions::isFriend); - - return LLPanelRegionInfo::postBuild(); -} - -void LLPanelEstateInfo::refresh() -{ - // Disable access restriction controls if they make no sense. - bool public_access = ("estate_public_access" == getChild("externally_visible_radio")->getValue().asString()); - - getChildView("Only Allow")->setEnabled(public_access); - getChildView("limit_payment")->setEnabled(public_access); - getChildView("limit_age_verified")->setEnabled(public_access); - getChildView("limit_bots")->setEnabled(public_access); - - // if this is set to false, then the limit fields are meaningless and should be turned off - if (!public_access) - { - getChild("limit_payment")->setValue(false); - getChild("limit_age_verified")->setValue(false); - getChild("limit_bots")->setValue(false); - } -} - -void LLPanelEstateInfo::refreshFromEstate() -{ - const LLEstateInfoModel& estate_info = LLEstateInfoModel::instance(); - - getChild("estate_name")->setValue(estate_info.getName()); - setOwnerName(LLSLURL("agent", estate_info.getOwnerID(), "inspect").getSLURLString()); - - getChild("externally_visible_radio")->setValue(estate_info.getIsExternallyVisible() ? "estate_public_access" : "estate_restricted_access"); - getChild("voice_chat_check")->setValue(estate_info.getAllowVoiceChat()); - getChild("allow_direct_teleport")->setValue(estate_info.getAllowDirectTeleport()); - getChild("limit_payment")->setValue(estate_info.getDenyAnonymous()); - getChild("limit_age_verified")->setValue(estate_info.getDenyAgeUnverified()); - getChild("parcel_access_override")->setValue(estate_info.getAllowAccessOverride()); - getChild("limit_bots")->setValue(estate_info.getDenyScriptedAgents()); - - // Ensure appriopriate state of the management UI - updateControls(gAgent.getRegion()); - refresh(); -} - -bool LLPanelEstateInfo::sendUpdate() -{ - LL_INFOS() << "LLPanelEsateInfo::sendUpdate()" << LL_ENDL; - - LLNotification::Params params("ChangeLindenEstate"); - params.functor.function(boost::bind(&LLPanelEstateInfo::callbackChangeLindenEstate, this, _1, _2)); - - if (isLindenEstate()) - { - // trying to change reserved estate, warn - LLNotifications::instance().add(params); - } - else - { - // for normal estates, just make the change - LLNotifications::instance().forceResponse(params, 0); - } - return true; -} - -bool LLPanelEstateInfo::callbackChangeLindenEstate(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - switch(option) - { - case 0: - { - LLEstateInfoModel& estate_info = LLEstateInfoModel::instance(); - - // update model - estate_info.setUseFixedSun(false); // we don't support fixed sun estates anymore - estate_info.setIsExternallyVisible("estate_public_access" == getChild("externally_visible_radio")->getValue().asString()); - estate_info.setAllowDirectTeleport(getChild("allow_direct_teleport")->getValue().asBoolean()); - estate_info.setDenyAnonymous(getChild("limit_payment")->getValue().asBoolean()); - estate_info.setDenyAgeUnverified(getChild("limit_age_verified")->getValue().asBoolean()); - estate_info.setAllowVoiceChat(getChild("voice_chat_check")->getValue().asBoolean()); - estate_info.setAllowAccessOverride(getChild("parcel_access_override")->getValue().asBoolean()); - estate_info.setDenyScriptedAgents(getChild("limit_bots")->getValue().asBoolean()); - // JIGGLYPUFF - //estate_info.setAllowAccessOverride(getChild("")->getValue().asBoolean()); - // send the update to sim - estate_info.sendEstateInfo(); - } - - // we don't want to do this because we'll get it automatically from the sim - // after the spaceserver processes it -// else -// { -// // caps method does not automatically send this info -// LLFloaterRegionInfo::requestRegionInfo(); -// } - break; - case 1: - default: - // do nothing - break; - } - return false; -} - - -/* -// Request = "getowner" -// SParam[0] = "" (empty string) -// IParam[0] = serial -void LLPanelEstateInfo::getEstateOwner() -{ - // TODO -- disable the panel - // and call this function whenever we cross a region boundary - // re-enable when owner matches, and get new estate info - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_EstateOwnerRequest); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - - msg->nextBlockFast(_PREHASH_RequestData); - msg->addStringFast(_PREHASH_Request, "getowner"); - - // we send an empty string so that the variable block is not empty - msg->nextBlockFast(_PREHASH_StringData); - msg->addStringFast(_PREHASH_SParam, ""); - - msg->nextBlockFast(_PREHASH_IntegerData); - msg->addS32Fast(_PREHASH_IParam, LLFloaterRegionInfo::getSerial()); - - gAgent.sendMessage(); -} -*/ - -const std::string LLPanelEstateInfo::getOwnerName() const -{ - return getChild("estate_owner")->getValue().asString(); -} - -void LLPanelEstateInfo::setOwnerName(const std::string& name) -{ - getChild("estate_owner")->setValue(LLSD(name)); -} - -// static -void LLPanelEstateInfo::onClickMessageEstate(void* userdata) -{ - LL_INFOS() << "LLPanelEstateInfo::onClickMessageEstate" << LL_ENDL; - LLNotificationsUtil::add("MessageEstate", LLSD(), LLSD(), boost::bind(&LLPanelEstateInfo::onMessageCommit, (LLPanelEstateInfo*)userdata, _1, _2)); -} - -bool LLPanelEstateInfo::onMessageCommit(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - std::string text = response["message"].asString(); - if(option != 0) return false; - if(text.empty()) return false; - LL_INFOS() << "Message to everyone: " << text << LL_ENDL; - strings_t strings; - //integers_t integers; - std::string name; - LLAgentUI::buildFullname(name); - strings.push_back(strings_t::value_type(name)); - strings.push_back(strings_t::value_type(text)); - LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); - sendEstateOwnerMessage(gMessageSystem, "instantmessage", invoice, strings); - return false; -} - -void LLPanelEstateInfo::onChangeAccessOverride() -{ - if (!getChild("parcel_access_override")->getValue().asBoolean()) - { - LLNotificationsUtil::add("EstateParcelAccessOverride"); - } -} - -LLPanelEstateCovenant::LLPanelEstateCovenant() - : - mCovenantID(LLUUID::null), - mAssetStatus(ASSET_ERROR) -{ -} - -// virtual -bool LLPanelEstateCovenant::refreshFromRegion(LLViewerRegion* region) -{ - LLTextBox* region_name = getChild("region_name_text"); - if (region_name) - { - region_name->setText(region->getName()); - } - - LLTextBox* resellable_clause = getChild("resellable_clause"); - if (resellable_clause) - { - if (region->getRegionFlag(REGION_FLAGS_BLOCK_LAND_RESELL)) - { - resellable_clause->setText(getString("can_not_resell")); - } - else - { - resellable_clause->setText(getString("can_resell")); - } - } - - LLTextBox* changeable_clause = getChild("changeable_clause"); - if (changeable_clause) - { - if (region->getRegionFlag(REGION_FLAGS_ALLOW_PARCEL_CHANGES)) - { - changeable_clause->setText(getString("can_change")); - } - else - { - changeable_clause->setText(getString("can_not_change")); - } - } - - LLTextBox* region_maturity = getChild("region_maturity_text"); - if (region_maturity) - { - region_maturity->setText(region->getSimAccessString()); - } - - LLTextBox* region_landtype = getChild("region_landtype_text"); - region_landtype->setText(region->getLocalizedSimProductName()); - - getChild("reset_covenant")->setEnabled(gAgent.isGodlike() || (region && region->canManageEstate())); - - // let the parent class handle the general data collection. - bool rv = LLPanelRegionInfo::refreshFromRegion(region); - LLMessageSystem *msg = gMessageSystem; - msg->newMessage("EstateCovenantRequest"); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); - msg->sendReliable(region->getHost()); - return rv; -} - -// virtual -bool LLPanelEstateCovenant::estateUpdate(LLMessageSystem* msg) -{ - LL_INFOS() << "LLPanelEstateCovenant::estateUpdate()" << LL_ENDL; - return true; -} - -// virtual -bool LLPanelEstateCovenant::postBuild() -{ - mEstateNameText = getChild("estate_name_text"); - mEstateOwnerText = getChild("estate_owner_text"); - mEstateOwnerText->setIsFriendCallback(LLAvatarActions::isFriend); - mLastModifiedText = getChild("covenant_timestamp_text"); - mEditor = getChild("covenant_editor"); - LLButton* reset_button = getChild("reset_covenant"); - reset_button->setEnabled(gAgent.canManageEstate()); - reset_button->setClickedCallback(LLPanelEstateCovenant::resetCovenantID, NULL); - - return LLPanelRegionInfo::postBuild(); -} - -// virtual -void LLPanelEstateCovenant::updateChild(LLUICtrl* child_ctrl) -{ -} - -// virtual -bool LLPanelEstateCovenant::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - LLInventoryItem* item = (LLInventoryItem*)cargo_data; - - if (!gAgent.canManageEstate()) - { - *accept = ACCEPT_NO; - return true; - } - - switch(cargo_type) - { - case DAD_NOTECARD: - *accept = ACCEPT_YES_COPY_SINGLE; - if (item && drop) - { - LLSD payload; - payload["item_id"] = item->getUUID(); - LLNotificationsUtil::add("EstateChangeCovenant", LLSD(), payload, - LLPanelEstateCovenant::confirmChangeCovenantCallback); - } - break; - default: - *accept = ACCEPT_NO; - break; - } - - return true; -} - -// static -bool LLPanelEstateCovenant::confirmChangeCovenantCallback(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - LLInventoryItem* item = gInventory.getItem(notification["payload"]["item_id"].asUUID()); - LLPanelEstateCovenant* self = LLFloaterRegionInfo::getPanelCovenant(); - - if (!item || !self) return false; - - switch(option) - { - case 0: - self->loadInvItem(item); - break; - default: - break; - } - return false; -} - -// static -void LLPanelEstateCovenant::resetCovenantID(void* userdata) -{ - LLNotificationsUtil::add("EstateChangeCovenant", LLSD(), LLSD(), confirmResetCovenantCallback); -} - -// static -bool LLPanelEstateCovenant::confirmResetCovenantCallback(const LLSD& notification, const LLSD& response) -{ - LLPanelEstateCovenant* self = LLFloaterRegionInfo::getPanelCovenant(); - if (!self) return false; - - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - switch(option) - { - case 0: - self->loadInvItem(NULL); - break; - default: - break; - } - return false; -} - -void LLPanelEstateCovenant::loadInvItem(LLInventoryItem *itemp) -{ - const bool high_priority = true; - if (itemp) - { - gAssetStorage->getInvItemAsset(gAgent.getRegionHost(), - gAgent.getID(), - gAgent.getSessionID(), - itemp->getPermissions().getOwner(), - LLUUID::null, - itemp->getUUID(), - itemp->getAssetUUID(), - itemp->getType(), - onLoadComplete, - (void*)this, - high_priority); - mAssetStatus = ASSET_LOADING; - } - else - { - mAssetStatus = ASSET_LOADED; - setCovenantTextEditor(LLTrans::getString("RegionNoCovenant")); - sendChangeCovenantID(LLUUID::null); - } -} - -// static -void LLPanelEstateCovenant::onLoadComplete(const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status) -{ - LL_INFOS() << "LLPanelEstateCovenant::onLoadComplete()" << LL_ENDL; - LLPanelEstateCovenant* panelp = (LLPanelEstateCovenant*)user_data; - if( panelp ) - { - if(0 == status) - { - LLFileSystem file(asset_uuid, type, LLFileSystem::READ); - - S32 file_length = file.getSize(); - - std::vector buffer(file_length+1); - file.read((U8*)&buffer[0], file_length); - // put a EOS at the end - buffer[file_length] = 0; - - if( (file_length > 19) && !strncmp( &buffer[0], "Linden text version", 19 ) ) - { - if( !panelp->mEditor->importBuffer( &buffer[0], file_length+1 ) ) - { - LL_WARNS() << "Problem importing estate covenant." << LL_ENDL; - LLNotificationsUtil::add("ProblemImportingEstateCovenant"); - } - else - { - panelp->sendChangeCovenantID(asset_uuid); - } - } - else - { - // Version 0 (just text, doesn't include version number) - panelp->sendChangeCovenantID(asset_uuid); - } - } - else - { - if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status || - LL_ERR_FILE_EMPTY == status) - { - LLNotificationsUtil::add("MissingNotecardAssetID"); - } - else if (LL_ERR_INSUFFICIENT_PERMISSIONS == status) - { - LLNotificationsUtil::add("NotAllowedToViewNotecard"); - } - else - { - LLNotificationsUtil::add("UnableToLoadNotecardAsset"); - } - - LL_WARNS() << "Problem loading notecard: " << status << LL_ENDL; - } - panelp->mAssetStatus = ASSET_LOADED; - panelp->setCovenantID(asset_uuid); - } -} - -// key = "estatechangecovenantid" -// strings[0] = str(estate_id) (added by simulator before relay - not here) -// strings[1] = str(covenant_id) -void LLPanelEstateCovenant::sendChangeCovenantID(const LLUUID &asset_id) -{ - if (asset_id != getCovenantID()) - { - setCovenantID(asset_id); - - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("EstateOwnerMessage"); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addUUIDFast(_PREHASH_TransactionID, LLUUID::null); //not used - - msg->nextBlock("MethodData"); - msg->addString("Method", "estatechangecovenantid"); - msg->addUUID("Invoice", LLFloaterRegionInfo::getLastInvoice()); - - msg->nextBlock("ParamList"); - msg->addString("Parameter", getCovenantID().asString()); - gAgent.sendReliableMessage(); - } -} - -// virtual -bool LLPanelEstateCovenant::sendUpdate() -{ - return true; -} - -std::string LLPanelEstateCovenant::getEstateName() const -{ - return mEstateNameText->getText(); -} - -void LLPanelEstateCovenant::setEstateName(const std::string& name) -{ - mEstateNameText->setText(name); -} - -// static -void LLPanelEstateCovenant::updateCovenantText(const std::string& string, const LLUUID& asset_id) -{ - LLPanelEstateCovenant* panelp = LLFloaterRegionInfo::getPanelCovenant(); - if( panelp ) - { - panelp->mEditor->setText(string); - panelp->setCovenantID(asset_id); - } -} - -// static -void LLPanelEstateCovenant::updateEstateName(const std::string& name) -{ - LLPanelEstateCovenant* panelp = LLFloaterRegionInfo::getPanelCovenant(); - if( panelp ) - { - panelp->mEstateNameText->setText(name); - } -} - -// static -void LLPanelEstateCovenant::updateLastModified(const std::string& text) -{ - LLPanelEstateCovenant* panelp = LLFloaterRegionInfo::getPanelCovenant(); - if( panelp ) - { - panelp->mLastModifiedText->setText(text); - } -} - -// static -void LLPanelEstateCovenant::updateEstateOwnerName(const std::string& name) -{ - LLPanelEstateCovenant* panelp = LLFloaterRegionInfo::getPanelCovenant(); - if( panelp ) - { - panelp->mEstateOwnerText->setText(name); - } -} - -std::string LLPanelEstateCovenant::getOwnerName() const -{ - return mEstateOwnerText->getText(); -} - -void LLPanelEstateCovenant::setOwnerName(const std::string& name) -{ - mEstateOwnerText->setText(name); -} - -void LLPanelEstateCovenant::setCovenantTextEditor(const std::string& text) -{ - mEditor->setText(text); -} - -// key = "estateupdateinfo" -// strings[0] = estate name -// strings[1] = str(owner_id) -// strings[2] = str(estate_id) -// strings[3] = str(estate_flags) -// strings[4] = str((S32)(sun_hour * 1024)) -// strings[5] = str(parent_estate_id) -// strings[6] = str(covenant_id) -// strings[7] = str(covenant_timestamp) -// strings[8] = str(send_to_agent_only) -// strings[9] = str(abuse_email_addr) -bool LLDispatchEstateUpdateInfo::operator()( - const LLDispatcher* dispatcher, - const std::string& key, - const LLUUID& invoice, - const sparam_t& strings) -{ - LL_DEBUGS() << "Received estate update" << LL_ENDL; - - // Update estate info model. - // This will call LLPanelEstateInfo::refreshFromEstate(). - // *TODO: Move estate message handling stuff to llestateinfomodel.cpp. - LLEstateInfoModel::instance().update(strings); - - return true; -} - -bool LLDispatchSetEstateAccess::operator()( - const LLDispatcher* dispatcher, - const std::string& key, - const LLUUID& invoice, - const sparam_t& strings) -{ - LLPanelEstateAccess* panel = LLFloaterRegionInfo::getPanelAccess(); - if (panel && panel->getPendingUpdate()) - { - panel->setPendingUpdate(false); - panel->updateLists(); - } - return true; -} - -// static -LLSD LLDispatchSetEstateExperience::getIDs( sparam_t::const_iterator it, sparam_t::const_iterator end, S32 count ) -{ - LLSD idList = LLSD::emptyArray(); - LLUUID id; - while (count-- > 0 && it < end) - { - memcpy(id.mData, (*(it++)).data(), UUID_BYTES); - idList.append(id); - } - return idList; -} - -// key = "setexperience" -// strings[0] = str(estate_id) -// strings[1] = str(send_to_agent_only) -// strings[2] = str(num blocked) -// strings[3] = str(num trusted) -// strings[4] = str(num allowed) -// strings[5] = bin(uuid) ... -// ... -bool LLDispatchSetEstateExperience::operator()( - const LLDispatcher* dispatcher, - const std::string& key, - const LLUUID& invoice, - const sparam_t& strings) -{ - LLPanelRegionExperiences* panel = LLFloaterRegionInfo::getPanelExperiences(); - if (!panel) - return true; - - const sparam_t::size_type MIN_SIZE = 5; - if (strings.size() < MIN_SIZE) - return true; - - // Skip 2 parameters - sparam_t::const_iterator it = strings.begin(); - ++it; // U32 estate_id = strtol((*it).c_str(), NULL, 10); - ++it; // U32 send_to_agent_only = strtoul((*(++it)).c_str(), NULL, 10); - - // Read 3 parameters - LLUUID id; - S32 num_blocked = strtol((*(it++)).c_str(), NULL, 10); - S32 num_trusted = strtol((*(it++)).c_str(), NULL, 10); - S32 num_allowed = strtol((*(it++)).c_str(), NULL, 10); - - LLSD ids = LLSD::emptyMap() - .with("blocked", getIDs(it, strings.end(), num_blocked)) - .with("trusted", getIDs(it + num_blocked, strings.end(), num_trusted)) - .with("allowed", getIDs(it + num_blocked + num_trusted, strings.end(), num_allowed)); - - panel->processResponse(ids); - - return true; -} - -bool LLPanelRegionExperiences::postBuild() -{ - mAllowed = setupList("panel_allowed", ESTATE_EXPERIENCE_ALLOWED_ADD, ESTATE_EXPERIENCE_ALLOWED_REMOVE); - mTrusted = setupList("panel_trusted", ESTATE_EXPERIENCE_TRUSTED_ADD, ESTATE_EXPERIENCE_TRUSTED_REMOVE); - mBlocked = setupList("panel_blocked", ESTATE_EXPERIENCE_BLOCKED_ADD, ESTATE_EXPERIENCE_BLOCKED_REMOVE); - - getChild("trusted_layout_panel")->setVisible(true); - getChild("experiences_help_text")->setText(getString("estate_caption")); - getChild("trusted_text_help")->setText(getString("trusted_estate_text")); - getChild("allowed_text_help")->setText(getString("allowed_estate_text")); - getChild("blocked_text_help")->setText(getString("blocked_estate_text")); - - return LLPanelRegionInfo::postBuild(); -} - -LLPanelExperienceListEditor* LLPanelRegionExperiences::setupList( const char* control_name, U32 add_id, U32 remove_id ) -{ - LLPanelExperienceListEditor* child = findChild(control_name); - if(child) - { - child->getChild("text_name")->setText(child->getString(control_name)); - child->setMaxExperienceIDs(ESTATE_MAX_EXPERIENCE_IDS); - child->setAddedCallback( boost::bind(&LLPanelRegionExperiences::itemChanged, this, add_id, _1)); - child->setRemovedCallback(boost::bind(&LLPanelRegionExperiences::itemChanged, this, remove_id, _1)); - } - - return child; -} - - -void LLPanelRegionExperiences::processResponse( const LLSD& content ) -{ - if(content.has("default")) - { - mDefaultExperience = content["default"].asUUID(); - } - - mAllowed->setExperienceIds(content["allowed"]); - mBlocked->setExperienceIds(content["blocked"]); - - LLSD trusted = content["trusted"]; - if(mDefaultExperience.notNull()) - { - mTrusted->setStickyFunction(boost::bind(LLPanelExperiencePicker::FilterMatching, _1, mDefaultExperience)); - trusted.append(mDefaultExperience); - } - - mTrusted->setExperienceIds(trusted); - - mAllowed->refreshExperienceCounter(); - mBlocked->refreshExperienceCounter(); - mTrusted->refreshExperienceCounter(); - -} - -// Used for both access add and remove operations, depending on the flag -// passed in (ESTATE_EXPERIENCE_ALLOWED_ADD, ESTATE_EXPERIENCE_ALLOWED_REMOVE, etc.) -// static -bool LLPanelRegionExperiences::experienceCoreConfirm(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - const U32 originalFlags = (U32)notification["payload"]["operation"].asInteger(); - - LLViewerRegion* region = gAgent.getRegion(); - - LLSD::array_const_iterator end_it = notification["payload"]["allowed_ids"].endArray(); - - for (LLSD::array_const_iterator iter = notification["payload"]["allowed_ids"].beginArray(); - iter != end_it; - iter++) - { - U32 flags = originalFlags; - if (iter + 1 != end_it) - flags |= ESTATE_ACCESS_NO_REPLY; - - const LLUUID id = iter->asUUID(); - switch(option) - { - case 0: - // This estate - sendEstateExperienceDelta(flags, id); - break; - case 1: - { - // All estates, either than I own or manage for this owner. - // This will be verified on simulator. JC - if (!region) break; - if (region->getOwner() == gAgent.getID() - || gAgent.isGodlike()) - { - flags |= ESTATE_ACCESS_APPLY_TO_ALL_ESTATES; - sendEstateExperienceDelta(flags, id); - } - else if (region->isEstateManager()) - { - flags |= ESTATE_ACCESS_APPLY_TO_MANAGED_ESTATES; - sendEstateExperienceDelta(flags, id); - } - break; - } - case 2: - default: - break; - } - } - return false; -} - - -// Send the actual "estateexperiencedelta" message -void LLPanelRegionExperiences::sendEstateExperienceDelta(U32 flags, const LLUUID& experience_id) -{ - strings_t str(3, std::string()); - gAgent.getID().toString(str[0]); - str[1] = llformat("%u", flags); - experience_id.toString(str[2]); - - LLPanelRegionExperiences* panel = LLFloaterRegionInfo::getPanelExperiences(); - if (panel) - { - panel->sendEstateOwnerMessage(gMessageSystem, "estateexperiencedelta", LLFloaterRegionInfo::getLastInvoice(), str); - } -} - - -void LLPanelRegionExperiences::infoCallback(LLHandle handle, const LLSD& content) -{ - if(handle.isDead()) - return; - - LLPanelRegionExperiences* floater = handle.get(); - if (floater) - { - floater->processResponse(content); - } -} - -/*static*/ -std::string LLPanelRegionExperiences::regionCapabilityQuery(LLViewerRegion* region, const std::string &cap) -{ - // region->getHandle() How to get a region * from a handle? - - return region->getCapability(cap); -} - -bool LLPanelRegionExperiences::refreshFromRegion(LLViewerRegion* region) -{ - bool allow_modify = gAgent.isGodlike() || (region && region->canManageEstate()); - - mAllowed->loading(); - mAllowed->setReadonly(!allow_modify); - // remove grid-wide experiences - mAllowed->addFilter(boost::bind(LLPanelExperiencePicker::FilterWithProperty, _1, LLExperienceCache::PROPERTY_GRID)); - // remove default experience - mAllowed->addFilter(boost::bind(LLPanelExperiencePicker::FilterMatching, _1, mDefaultExperience)); - - mBlocked->loading(); - mBlocked->setReadonly(!allow_modify); - // only grid-wide experiences - mBlocked->addFilter(boost::bind(LLPanelExperiencePicker::FilterWithoutProperty, _1, LLExperienceCache::PROPERTY_GRID)); - // but not privileged ones - mBlocked->addFilter(boost::bind(LLPanelExperiencePicker::FilterWithProperty, _1, LLExperienceCache::PROPERTY_PRIVILEGED)); - // remove default experience - mBlocked->addFilter(boost::bind(LLPanelExperiencePicker::FilterMatching, _1, mDefaultExperience)); - - mTrusted->loading(); - mTrusted->setReadonly(!allow_modify); - - LLExperienceCache::instance().getRegionExperiences(boost::bind(&LLPanelRegionExperiences::regionCapabilityQuery, region, _1), - boost::bind(&LLPanelRegionExperiences::infoCallback, getDerivedHandle(), _1)); - - return LLPanelRegionInfo::refreshFromRegion(region); -} - -LLSD LLPanelRegionExperiences::addIds(LLPanelExperienceListEditor* panel) -{ - LLSD ids; - const uuid_list_t& id_list = panel->getExperienceIds(); - for(uuid_list_t::const_iterator it = id_list.begin(); it != id_list.end(); ++it) - { - ids.append(*it); - } - return ids; -} - - -bool LLPanelRegionExperiences::sendUpdate() -{ - LLViewerRegion* region = gAgent.getRegion(); - - LLSD content; - - content["allowed"]=addIds(mAllowed); - content["blocked"]=addIds(mBlocked); - content["trusted"]=addIds(mTrusted); - - LLExperienceCache::instance().setRegionExperiences(boost::bind(&LLPanelRegionExperiences::regionCapabilityQuery, region, _1), - content, boost::bind(&LLPanelRegionExperiences::infoCallback, getDerivedHandle(), _1)); - - return true; -} - -void LLPanelRegionExperiences::itemChanged( U32 event_type, const LLUUID& id ) -{ - std::string dialog_name; - switch (event_type) - { - case ESTATE_EXPERIENCE_ALLOWED_ADD: - dialog_name = "EstateAllowedExperienceAdd"; - break; - - case ESTATE_EXPERIENCE_ALLOWED_REMOVE: - dialog_name = "EstateAllowedExperienceRemove"; - break; - - case ESTATE_EXPERIENCE_TRUSTED_ADD: - dialog_name = "EstateTrustedExperienceAdd"; - break; - - case ESTATE_EXPERIENCE_TRUSTED_REMOVE: - dialog_name = "EstateTrustedExperienceRemove"; - break; - - case ESTATE_EXPERIENCE_BLOCKED_ADD: - dialog_name = "EstateBlockedExperienceAdd"; - break; - - case ESTATE_EXPERIENCE_BLOCKED_REMOVE: - dialog_name = "EstateBlockedExperienceRemove"; - break; - - default: - return; - } - - LLSD payload; - payload["operation"] = (S32)event_type; - payload["dialog_name"] = dialog_name; - payload["allowed_ids"].append(id); - - LLSD args; - args["ALL_ESTATES"] = all_estates_text(); - - LLNotification::Params params(dialog_name); - params.payload(payload) - .substitutions(args) - .functor.function(LLPanelRegionExperiences::experienceCoreConfirm); - if (LLPanelEstateInfo::isLindenEstate()) - { - LLNotifications::instance().forceResponse(params, 0); - } - else - { - LLNotifications::instance().add(params); - } - - onChangeAnything(); -} - - -LLPanelEstateAccess::LLPanelEstateAccess() -: LLPanelRegionInfo(), mPendingUpdate(false) -{} - -bool LLPanelEstateAccess::postBuild() -{ - getChild("allowed_avatar_name_list")->setCommitCallback(boost::bind(&LLPanelEstateInfo::onChangeChildCtrl, this, _1)); - LLNameListCtrl *avatar_name_list = getChild("allowed_avatar_name_list"); - if (avatar_name_list) - { - avatar_name_list->setCommitOnSelectionChange(true); - avatar_name_list->setMaxItemCount(ESTATE_MAX_ACCESS_IDS); - } - - getChild("allowed_search_input")->setCommitCallback(boost::bind(&LLPanelEstateAccess::onAllowedSearchEdit, this, _2)); - childSetAction("add_allowed_avatar_btn", boost::bind(&LLPanelEstateAccess::onClickAddAllowedAgent, this)); - childSetAction("remove_allowed_avatar_btn", boost::bind(&LLPanelEstateAccess::onClickRemoveAllowedAgent, this)); - childSetAction("copy_allowed_list_btn", boost::bind(&LLPanelEstateAccess::onClickCopyAllowedList, this)); - - getChild("allowed_group_name_list")->setCommitCallback(boost::bind(&LLPanelEstateInfo::onChangeChildCtrl, this, _1)); - LLNameListCtrl* group_name_list = getChild("allowed_group_name_list"); - if (group_name_list) - { - group_name_list->setCommitOnSelectionChange(true); - group_name_list->setMaxItemCount(ESTATE_MAX_ACCESS_IDS); - } - - getChild("allowed_group_search_input")->setCommitCallback(boost::bind(&LLPanelEstateAccess::onAllowedGroupsSearchEdit, this, _2)); - getChild("add_allowed_group_btn")->setCommitCallback(boost::bind(&LLPanelEstateAccess::onClickAddAllowedGroup, this)); - childSetAction("remove_allowed_group_btn", boost::bind(&LLPanelEstateAccess::onClickRemoveAllowedGroup, this)); - childSetAction("copy_allowed_group_list_btn", boost::bind(&LLPanelEstateAccess::onClickCopyAllowedGroupList, this)); - - getChild("banned_avatar_name_list")->setCommitCallback(boost::bind(&LLPanelEstateInfo::onChangeChildCtrl, this, _1)); - LLNameListCtrl* banned_name_list = getChild("banned_avatar_name_list"); - if (banned_name_list) - { - banned_name_list->setCommitOnSelectionChange(true); - banned_name_list->setMaxItemCount(ESTATE_MAX_BANNED_IDS); - } - - getChild("banned_search_input")->setCommitCallback(boost::bind(&LLPanelEstateAccess::onBannedSearchEdit, this, _2)); - childSetAction("add_banned_avatar_btn", boost::bind(&LLPanelEstateAccess::onClickAddBannedAgent, this)); - childSetAction("remove_banned_avatar_btn", boost::bind(&LLPanelEstateAccess::onClickRemoveBannedAgent, this)); - childSetAction("copy_banned_list_btn", boost::bind(&LLPanelEstateAccess::onClickCopyBannedList, this)); - - getChild("estate_manager_name_list")->setCommitCallback(boost::bind(&LLPanelEstateInfo::onChangeChildCtrl, this, _1)); - LLNameListCtrl* manager_name_list = getChild("estate_manager_name_list"); - if (manager_name_list) - { - manager_name_list->setCommitOnSelectionChange(true); - manager_name_list->setMaxItemCount(ESTATE_MAX_MANAGERS * 4); // Allow extras for dupe issue - } - - childSetAction("add_estate_manager_btn", boost::bind(&LLPanelEstateAccess::onClickAddEstateManager, this)); - childSetAction("remove_estate_manager_btn", boost::bind(&LLPanelEstateAccess::onClickRemoveEstateManager, this)); - - return true; -} - -void LLPanelEstateAccess::updateControls(LLViewerRegion* region) -{ - bool god = gAgent.isGodlike(); - bool owner = (region && (region->getOwner() == gAgent.getID())); - bool manager = (region && region->isEstateManager()); - bool enable_cotrols = god || owner || manager; - setCtrlsEnabled(enable_cotrols); - - LLNameListCtrl* allowedAvatars = getChild("allowed_avatar_name_list"); - LLNameListCtrl* allowedGroups = getChild("allowed_group_name_list"); - LLNameListCtrl* bannedAvatars = getChild("banned_avatar_name_list"); - LLNameListCtrl* estateManagers = getChild("estate_manager_name_list"); - - bool has_allowed_avatar = allowedAvatars->getFirstSelected(); - bool has_allowed_group = allowedGroups->getFirstSelected(); - bool has_banned_agent = bannedAvatars->getFirstSelected(); - bool has_estate_manager = estateManagers->getFirstSelected(); - - getChildView("add_allowed_avatar_btn")->setEnabled(enable_cotrols); - getChildView("remove_allowed_avatar_btn")->setEnabled(has_allowed_avatar && enable_cotrols); - allowedAvatars->setEnabled(enable_cotrols); - - getChildView("add_allowed_group_btn")->setEnabled(enable_cotrols); - getChildView("remove_allowed_group_btn")->setEnabled(has_allowed_group && enable_cotrols); - allowedGroups->setEnabled(enable_cotrols); - - // Can't ban people from mainland, orientation islands, etc. because this - // creates much network traffic and server load. - // Disable their accounts in CSR tool instead. - bool linden_estate = LLPanelEstateInfo::isLindenEstate(); - bool enable_ban = enable_cotrols && !linden_estate; - getChildView("add_banned_avatar_btn")->setEnabled(enable_ban); - getChildView("remove_banned_avatar_btn")->setEnabled(has_banned_agent && enable_ban); - bannedAvatars->setEnabled(enable_cotrols); - - // estate managers can't add estate managers - getChildView("add_estate_manager_btn")->setEnabled(god || owner); - getChildView("remove_estate_manager_btn")->setEnabled(has_estate_manager && (god || owner)); - estateManagers->setEnabled(god || owner); - - if (enable_cotrols != mCtrlsEnabled) - { - mCtrlsEnabled = enable_cotrols; - updateLists(); // update the lists on the agent's access level change - } -} - -//--------------------------------------------------------------------------- -// Add/Remove estate access button callbacks -//--------------------------------------------------------------------------- -void LLPanelEstateAccess::onClickAddAllowedAgent() -{ - LLCtrlListInterface *list = childGetListInterface("allowed_avatar_name_list"); - if (!list) return; - if (list->getItemCount() >= ESTATE_MAX_ACCESS_IDS) - { - //args - - LLSD args; - args["MAX_AGENTS"] = llformat("%d", ESTATE_MAX_ACCESS_IDS); - LLNotificationsUtil::add("MaxAllowedAgentOnRegion", args); - return; - } - accessAddCore(ESTATE_ACCESS_ALLOWED_AGENT_ADD, "EstateAllowedAgentAdd"); -} - -void LLPanelEstateAccess::onClickRemoveAllowedAgent() -{ - accessRemoveCore(ESTATE_ACCESS_ALLOWED_AGENT_REMOVE, "EstateAllowedAgentRemove", "allowed_avatar_name_list"); -} - -void LLPanelEstateAccess::onClickAddAllowedGroup() -{ - LLCtrlListInterface *list = childGetListInterface("allowed_group_name_list"); - if (!list) return; - if (list->getItemCount() >= ESTATE_MAX_ACCESS_IDS) - { - LLSD args; - args["MAX_GROUPS"] = llformat("%d", ESTATE_MAX_ACCESS_IDS); - LLNotificationsUtil::add("MaxAllowedGroupsOnRegion", args); - return; - } - - LLNotification::Params params("ChangeLindenAccess"); - params.functor.function(boost::bind(&LLPanelEstateAccess::addAllowedGroup, this, _1, _2)); - if (LLPanelEstateInfo::isLindenEstate()) - { - LLNotifications::instance().add(params); - } - else - { - LLNotifications::instance().forceResponse(params, 0); - } -} - -bool LLPanelEstateAccess::addAllowedGroup(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option != 0) return false; - - LLFloater* parent_floater = gFloaterView->getParentFloater(this); - - LLFloaterGroupPicker* widget = LLFloaterReg::showTypedInstance("group_picker", LLSD(gAgent.getID())); - if (widget) - { - widget->removeNoneOption(); - widget->setSelectGroupCallback(boost::bind(&LLPanelEstateAccess::addAllowedGroup2, this, _1)); - if (parent_floater) - { - LLRect new_rect = gFloaterView->findNeighboringPosition(parent_floater, widget); - widget->setOrigin(new_rect.mLeft, new_rect.mBottom); - parent_floater->addDependentFloater(widget); - } - } - - return false; -} - -void LLPanelEstateAccess::onClickRemoveAllowedGroup() -{ - accessRemoveCore(ESTATE_ACCESS_ALLOWED_GROUP_REMOVE, "EstateAllowedGroupRemove", "allowed_group_name_list"); -} - -void LLPanelEstateAccess::onClickAddBannedAgent() -{ - LLCtrlListInterface *list = childGetListInterface("banned_avatar_name_list"); - if (!list) return; - if (list->getItemCount() >= ESTATE_MAX_BANNED_IDS) - { - LLSD args; - args["MAX_BANNED"] = llformat("%d", ESTATE_MAX_BANNED_IDS); - LLNotificationsUtil::add("MaxBannedAgentsOnRegion", args); - return; - } - accessAddCore(ESTATE_ACCESS_BANNED_AGENT_ADD, "EstateBannedAgentAdd"); -} - -void LLPanelEstateAccess::onClickRemoveBannedAgent() -{ - accessRemoveCore(ESTATE_ACCESS_BANNED_AGENT_REMOVE, "EstateBannedAgentRemove", "banned_avatar_name_list"); -} - -void LLPanelEstateAccess::onClickCopyAllowedList() -{ - copyListToClipboard("allowed_avatar_name_list"); -} - -void LLPanelEstateAccess::onClickCopyAllowedGroupList() -{ - copyListToClipboard("allowed_group_name_list"); -} - -void LLPanelEstateAccess::onClickCopyBannedList() -{ - copyListToClipboard("banned_avatar_name_list"); -} - -// static -void LLPanelEstateAccess::onClickAddEstateManager() -{ - LLCtrlListInterface *list = childGetListInterface("estate_manager_name_list"); - if (!list) return; - if (list->getItemCount() >= ESTATE_MAX_MANAGERS) - { // Tell user they can't add more managers - LLSD args; - args["MAX_MANAGER"] = llformat("%d", ESTATE_MAX_MANAGERS); - LLNotificationsUtil::add("MaxManagersOnRegion", args); - } - else - { // Go pick managers to add - accessAddCore(ESTATE_ACCESS_MANAGER_ADD, "EstateManagerAdd"); - } -} - -// static -void LLPanelEstateAccess::onClickRemoveEstateManager() -{ - accessRemoveCore(ESTATE_ACCESS_MANAGER_REMOVE, "EstateManagerRemove", "estate_manager_name_list"); -} - - -// Special case callback for groups, since it has different callback format than names -void LLPanelEstateAccess::addAllowedGroup2(LLUUID id) -{ - LLPanelEstateAccess* panel = LLFloaterRegionInfo::getPanelAccess(); - if (panel) - { - LLNameListCtrl* group_list = panel->getChild("allowed_group_name_list"); - LLScrollListItem* item = group_list->getNameItemByAgentId(id); - if (item) - { - LLSD args; - args["GROUP"] = item->getColumn(0)->getValue().asString(); - LLNotificationsUtil::add("GroupIsAlreadyInList", args); - return; - } - } - - LLSD payload; - payload["operation"] = (S32)ESTATE_ACCESS_ALLOWED_GROUP_ADD; - payload["dialog_name"] = "EstateAllowedGroupAdd"; - payload["allowed_ids"].append(id); - - LLSD args; - args["ALL_ESTATES"] = all_estates_text(); - - LLNotification::Params params("EstateAllowedGroupAdd"); - params.payload(payload) - .substitutions(args) - .functor.function(accessCoreConfirm); - if (LLPanelEstateInfo::isLindenEstate()) - { - LLNotifications::instance().forceResponse(params, 0); - } - else - { - LLNotifications::instance().add(params); - } -} - -// static -void LLPanelEstateAccess::accessAddCore(U32 operation_flag, const std::string& dialog_name) -{ - LLSD payload; - payload["operation"] = (S32)operation_flag; - payload["dialog_name"] = dialog_name; - // agent id filled in after avatar picker - - LLNotification::Params params("ChangeLindenAccess"); - params.payload(payload) - .functor.function(accessAddCore2); - - if (LLPanelEstateInfo::isLindenEstate()) - { - LLNotifications::instance().add(params); - } - else - { - // same as clicking "OK" - LLNotifications::instance().forceResponse(params, 0); - } -} - -// static -bool LLPanelEstateAccess::accessAddCore2(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option != 0) - { - // abort change - return false; - } - - LLEstateAccessChangeInfo* change_info = new LLEstateAccessChangeInfo(notification["payload"]); - //Get parent floater name - LLPanelEstateAccess* panel = LLFloaterRegionInfo::getPanelAccess(); - LLFloater* parent_floater = panel ? gFloaterView->getParentFloater(panel) : NULL; - const std::string& parent_floater_name = parent_floater ? parent_floater->getName() : ""; - - //Determine the button that triggered opening of the avatar picker - //(so that a shadow frustum from the button to the avatar picker can be created) - LLView * button = NULL; - switch (change_info->mOperationFlag) - { - case ESTATE_ACCESS_ALLOWED_AGENT_ADD: - button = panel->findChild("add_allowed_avatar_btn"); - break; - - case ESTATE_ACCESS_BANNED_AGENT_ADD: - button = panel->findChild("add_banned_avatar_btn"); - break; - - case ESTATE_ACCESS_MANAGER_ADD: - button = panel->findChild("add_estate_manager_btn"); - break; - } - - // avatar picker yes multi-select, yes close-on-select - LLFloater* child_floater = LLFloaterAvatarPicker::show(boost::bind(&LLPanelEstateAccess::accessAddCore3, _1, _2, (void*)change_info), - true, true, false, parent_floater_name, button); - - //Allows the closed parent floater to close the child floater (avatar picker) - if (child_floater) - { - parent_floater->addDependentFloater(child_floater); - } - - return false; -} - -// static -void LLPanelEstateAccess::accessAddCore3(const uuid_vec_t& ids, std::vector names, void* data) -{ - LLEstateAccessChangeInfo* change_info = (LLEstateAccessChangeInfo*)data; - if (!change_info) return; - if (ids.empty()) - { - // User didn't select a name. - delete change_info; - change_info = NULL; - return; - } - // User did select a name. - change_info->mAgentOrGroupIDs = ids; - // Can't put estate owner on ban list - LLPanelEstateAccess* panel = LLFloaterRegionInfo::getPanelAccess(); - if (!panel) return; - LLViewerRegion* region = gAgent.getRegion(); - if (!region) return; - - if (change_info->mOperationFlag & ESTATE_ACCESS_ALLOWED_AGENT_ADD) - { - LLNameListCtrl* name_list = panel->getChild("allowed_avatar_name_list"); - int currentCount = (name_list ? name_list->getItemCount() : 0); - if (ids.size() + currentCount > ESTATE_MAX_ACCESS_IDS) - { - LLSD args; - args["NUM_ADDED"] = llformat("%d", ids.size()); - args["MAX_AGENTS"] = llformat("%d", ESTATE_MAX_ACCESS_IDS); - args["LIST_TYPE"] = LLTrans::getString("RegionInfoListTypeAllowedAgents"); - args["NUM_EXCESS"] = llformat("%d", (ids.size() + currentCount) - ESTATE_MAX_ACCESS_IDS); - LLNotificationsUtil::add("MaxAgentOnRegionBatch", args); - delete change_info; - return; - } - - uuid_vec_t ids_allowed; - std::vector names_allowed; - std::string already_allowed; - bool single = true; - for (U32 i = 0; i < ids.size(); ++i) - { - LLScrollListItem* item = name_list->getNameItemByAgentId(ids[i]); - if (item) - { - if (!already_allowed.empty()) - { - already_allowed += ", "; - single = false; - } - already_allowed += item->getColumn(0)->getValue().asString(); - } - else - { - ids_allowed.push_back(ids[i]); - names_allowed.push_back(names[i]); - } - } - if (!already_allowed.empty()) - { - LLSD args; - args["AGENT"] = already_allowed; - args["LIST_TYPE"] = LLTrans::getString("RegionInfoListTypeAllowedAgents"); - LLNotificationsUtil::add(single ? "AgentIsAlreadyInList" : "AgentsAreAlreadyInList", args); - if (ids_allowed.empty()) - { - delete change_info; - return; - } - } - change_info->mAgentOrGroupIDs = ids_allowed; - change_info->mAgentNames = names_allowed; - } - if (change_info->mOperationFlag & ESTATE_ACCESS_BANNED_AGENT_ADD) - { - LLNameListCtrl* name_list = panel->getChild("banned_avatar_name_list"); - LLNameListCtrl* em_list = panel->getChild("estate_manager_name_list"); - int currentCount = (name_list ? name_list->getItemCount() : 0); - if (ids.size() + currentCount > ESTATE_MAX_BANNED_IDS) - { - LLSD args; - args["NUM_ADDED"] = llformat("%d", ids.size()); - args["MAX_AGENTS"] = llformat("%d", ESTATE_MAX_BANNED_IDS); - args["LIST_TYPE"] = LLTrans::getString("RegionInfoListTypeBannedAgents"); - args["NUM_EXCESS"] = llformat("%d", (ids.size() + currentCount) - ESTATE_MAX_BANNED_IDS); - LLNotificationsUtil::add("MaxAgentOnRegionBatch", args); - delete change_info; - return; - } - - uuid_vec_t ids_allowed; - std::vector names_allowed; - std::string already_banned; - std::string em_ban; - bool single = true; - for (U32 i = 0; i < ids.size(); ++i) - { - bool is_allowed = true; - LLScrollListItem* em_item = em_list->getNameItemByAgentId(ids[i]); - if (em_item) - { - if (!em_ban.empty()) - { - em_ban += ", "; - } - em_ban += em_item->getColumn(0)->getValue().asString(); - is_allowed = false; - } - - LLScrollListItem* item = name_list->getNameItemByAgentId(ids[i]); - if (item) - { - if (!already_banned.empty()) - { - already_banned += ", "; - single = false; - } - already_banned += item->getColumn(0)->getValue().asString(); - is_allowed = false; - } - - if (is_allowed) - { - ids_allowed.push_back(ids[i]); - names_allowed.push_back(names[i]); - } - } - if (!em_ban.empty()) - { - LLSD args; - args["AGENT"] = em_ban; - LLNotificationsUtil::add("ProblemBanningEstateManager", args); - if (ids_allowed.empty()) - { - delete change_info; - return; - } - } - if (!already_banned.empty()) - { - LLSD args; - args["AGENT"] = already_banned; - args["LIST_TYPE"] = LLTrans::getString("RegionInfoListTypeBannedAgents"); - LLNotificationsUtil::add(single ? "AgentIsAlreadyInList" : "AgentsAreAlreadyInList", args); - if (ids_allowed.empty()) - { - delete change_info; - return; - } - } - change_info->mAgentOrGroupIDs = ids_allowed; - change_info->mAgentNames = names_allowed; - } - - LLSD args; - args["ALL_ESTATES"] = all_estates_text(); - LLNotification::Params params(change_info->mDialogName); - params.substitutions(args) - .payload(change_info->asLLSD()) - .functor.function(accessCoreConfirm); - - if (LLPanelEstateInfo::isLindenEstate()) - { - // just apply to this estate - LLNotifications::instance().forceResponse(params, 0); - } - else - { - // ask if this estate or all estates with this owner - LLNotifications::instance().add(params); - } -} - -// static -void LLPanelEstateAccess::accessRemoveCore(U32 operation_flag, const std::string& dialog_name, const std::string& list_ctrl_name) -{ - LLPanelEstateAccess* panel = LLFloaterRegionInfo::getPanelAccess(); - if (!panel) return; - LLNameListCtrl* name_list = panel->getChild(list_ctrl_name); - if (!name_list) return; - - std::vector list_vector = name_list->getAllSelected(); - if (list_vector.size() == 0) - return; - - LLSD payload; - payload["operation"] = (S32)operation_flag; - payload["dialog_name"] = dialog_name; - - for (std::vector::const_iterator iter = list_vector.begin(); - iter != list_vector.end(); - iter++) - { - LLScrollListItem *item = (*iter); - payload["allowed_ids"].append(item->getUUID()); - } - - LLNotification::Params params("ChangeLindenAccess"); - params.payload(payload) - .functor.function(accessRemoveCore2); - - if (LLPanelEstateInfo::isLindenEstate()) - { - // warn on change linden estate - LLNotifications::instance().add(params); - } - else - { - // just proceed, as if clicking OK - LLNotifications::instance().forceResponse(params, 0); - } -} - -// static -bool LLPanelEstateAccess::accessRemoveCore2(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option != 0) - { - // abort - return false; - } - - // If Linden estate, can only apply to "this" estate, not all estates - // owned by NULL. - if (LLPanelEstateInfo::isLindenEstate()) - { - accessCoreConfirm(notification, response); - } - else - { - LLSD args; - args["ALL_ESTATES"] = all_estates_text(); - LLNotificationsUtil::add(notification["payload"]["dialog_name"], - args, - notification["payload"], - accessCoreConfirm); - } - return false; -} - -// Used for both access add and remove operations, depending on the mOperationFlag -// passed in (ESTATE_ACCESS_BANNED_AGENT_ADD, ESTATE_ACCESS_ALLOWED_AGENT_REMOVE, etc.) -// static -bool LLPanelEstateAccess::accessCoreConfirm(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - const U32 originalFlags = (U32)notification["payload"]["operation"].asInteger(); - U32 flags = originalFlags; - - LLViewerRegion* region = gAgent.getRegion(); - - if (option == 2) // cancel - { - return false; - } - else if (option == 1) - { - // All estates, either than I own or manage for this owner. - // This will be verified on simulator. JC - if (!region) return false; - if (region->getOwner() == gAgent.getID() - || gAgent.isGodlike()) - { - flags |= ESTATE_ACCESS_APPLY_TO_ALL_ESTATES; - } - else if (region->isEstateManager()) - { - flags |= ESTATE_ACCESS_APPLY_TO_MANAGED_ESTATES; - } - } - - std::string names; - U32 listed_names = 0; - for (U32 i = 0; i < notification["payload"]["allowed_ids"].size(); ++i) - { - if (i + 1 != notification["payload"]["allowed_ids"].size()) - { - flags |= ESTATE_ACCESS_NO_REPLY; - } - else - { - flags &= ~ESTATE_ACCESS_NO_REPLY; - } - - const LLUUID id = notification["payload"]["allowed_ids"][i].asUUID(); - if (((U32)notification["payload"]["operation"].asInteger() & ESTATE_ACCESS_BANNED_AGENT_ADD) - && region && (region->getOwner() == id)) - { - LLNotificationsUtil::add("OwnerCanNotBeDenied"); - break; - } - - sendEstateAccessDelta(flags, id); - - if ((flags & (ESTATE_ACCESS_ALLOWED_GROUP_ADD | ESTATE_ACCESS_ALLOWED_GROUP_REMOVE)) == 0) - { - // fill the name list for confirmation - if (listed_names < MAX_LISTED_NAMES) - { - if (!names.empty()) - { - names += ", "; - } - if (!notification["payload"]["allowed_names"][i]["display_name"].asString().empty()) - { - names += notification["payload"]["allowed_names"][i]["display_name"].asString(); - } - else - { //try to get an agent name from cache - LLAvatarName av_name; - if (LLAvatarNameCache::get(id, &av_name)) - { - names += av_name.getCompleteName(); - } - } - - } - listed_names++; - } - } - if (listed_names > MAX_LISTED_NAMES) - { - LLSD args; - args["EXTRA_COUNT"] = llformat("%d", listed_names - MAX_LISTED_NAMES); - names += " " + LLTrans::getString("AndNMore", args); - } - - if (!names.empty()) // show the conirmation - { - LLSD args; - args["AGENT"] = names; - - if (flags & (ESTATE_ACCESS_ALLOWED_AGENT_ADD | ESTATE_ACCESS_ALLOWED_AGENT_REMOVE)) - { - args["LIST_TYPE"] = LLTrans::getString("RegionInfoListTypeAllowedAgents"); - } - else if (flags & (ESTATE_ACCESS_BANNED_AGENT_ADD | ESTATE_ACCESS_BANNED_AGENT_REMOVE)) - { - args["LIST_TYPE"] = LLTrans::getString("RegionInfoListTypeBannedAgents"); - } - - if (flags & ESTATE_ACCESS_APPLY_TO_ALL_ESTATES) - { - args["ESTATE"] = LLTrans::getString("RegionInfoAllEstates"); - } - else if (flags & ESTATE_ACCESS_APPLY_TO_MANAGED_ESTATES) - { - args["ESTATE"] = LLTrans::getString("RegionInfoManagedEstates"); - } - else - { - args["ESTATE"] = LLTrans::getString("RegionInfoThisEstate"); - } - - bool single = (listed_names == 1); - if (flags & (ESTATE_ACCESS_ALLOWED_AGENT_ADD | ESTATE_ACCESS_BANNED_AGENT_ADD)) - { - LLNotificationsUtil::add(single ? "AgentWasAddedToList" : "AgentsWereAddedToList", args); - } - else if (flags & (ESTATE_ACCESS_ALLOWED_AGENT_REMOVE | ESTATE_ACCESS_BANNED_AGENT_REMOVE)) - { - LLNotificationsUtil::add(single ? "AgentWasRemovedFromList" : "AgentsWereRemovedFromList", args); - } - } - - LLPanelEstateAccess* panel = LLFloaterRegionInfo::getPanelAccess(); - if (panel) - { - panel->setPendingUpdate(true); - } - - return false; -} - -// key = "estateaccessdelta" -// str(estate_id) will be added to front of list by forward_EstateOwnerRequest_to_dataserver -// str[0] = str(agent_id) requesting the change -// str[1] = str(flags) (ESTATE_ACCESS_DELTA_*) -// str[2] = str(agent_id) to add or remove -// static -void LLPanelEstateAccess::sendEstateAccessDelta(U32 flags, const LLUUID& agent_or_group_id) -{ - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("EstateOwnerMessage"); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addUUIDFast(_PREHASH_TransactionID, LLUUID::null); //not used - - msg->nextBlock("MethodData"); - msg->addString("Method", "estateaccessdelta"); - msg->addUUID("Invoice", LLFloaterRegionInfo::getLastInvoice()); - - std::string buf; - gAgent.getID().toString(buf); - msg->nextBlock("ParamList"); - msg->addString("Parameter", buf); - - buf = llformat("%u", flags); - msg->nextBlock("ParamList"); - msg->addString("Parameter", buf); - - agent_or_group_id.toString(buf); - msg->nextBlock("ParamList"); - msg->addString("Parameter", buf); - - gAgent.sendReliableMessage(); -} - -void LLPanelEstateAccess::updateChild(LLUICtrl* child_ctrl) -{ - // Ensure appropriate state of the management ui. - updateControls(gAgent.getRegion()); -} - -void LLPanelEstateAccess::updateLists() -{ - std::string cap_url = gAgent.getRegionCapability("EstateAccess"); - if (!cap_url.empty()) - { - LLCoros::instance().launch("LLFloaterRegionInfo::requestEstateGetAccessCoro", boost::bind(LLPanelEstateAccess::requestEstateGetAccessCoro, cap_url)); - } -} - -void LLPanelEstateAccess::requestEstateGetAccessCoro(std::string url) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("requestEstateGetAccessoCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD result = httpAdapter->getAndSuspend(httpRequest, url); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - LLPanelEstateAccess* panel = LLFloaterRegionInfo::getPanelAccess(); - if (!panel) return; - - LLNameListCtrl* allowed_agent_name_list = panel->getChild("allowed_avatar_name_list"); - if (allowed_agent_name_list && result.has("AllowedAgents")) - { - LLStringUtil::format_map_t args; - args["[ALLOWEDAGENTS]"] = llformat("%d", result["AllowedAgents"].size()); - args["[MAXACCESS]"] = llformat("%d", ESTATE_MAX_ACCESS_IDS); - std::string msg = LLTrans::getString("RegionInfoAllowedResidents", args); - panel->getChild("allow_resident_label")->setValue(LLSD(msg)); - - allowed_agent_name_list->clearSortOrder(); - allowed_agent_name_list->deleteAllItems(); - for (LLSD::array_const_iterator it = result["AllowedAgents"].beginArray(); it != result["AllowedAgents"].endArray(); ++it) - { - LLUUID id = (*it)["id"].asUUID(); - allowed_agent_name_list->addNameItem(id); - } - allowed_agent_name_list->sortByName(true); - } - - LLNameListCtrl* banned_agent_name_list = panel->getChild("banned_avatar_name_list"); - if (banned_agent_name_list && result.has("BannedAgents")) - { - LLStringUtil::format_map_t args; - args["[BANNEDAGENTS]"] = llformat("%d", result["BannedAgents"].size()); - args["[MAXBANNED]"] = llformat("%d", ESTATE_MAX_BANNED_IDS); - std::string msg = LLTrans::getString("RegionInfoBannedResidents", args); - panel->getChild("ban_resident_label")->setValue(LLSD(msg)); - - banned_agent_name_list->clearSortOrder(); - banned_agent_name_list->deleteAllItems(); - for (LLSD::array_const_iterator it = result["BannedAgents"].beginArray(); it != result["BannedAgents"].endArray(); ++it) - { - LLSD item; - item["id"] = (*it)["id"].asUUID(); - LLSD& columns = item["columns"]; - - columns[0]["column"] = "name"; // to be populated later - - columns[1]["column"] = "last_login_date"; - columns[1]["value"] = (*it)["last_login_date"].asString().substr(0, 16); // cut the seconds - - std::string ban_date = (*it)["ban_date"].asString(); - columns[2]["column"] = "ban_date"; - columns[2]["value"] = ban_date[0] != '0' ? ban_date.substr(0, 16) : LLTrans::getString("na"); // server returns the "0000-00-00 00:00:00" date in case it doesn't know it - - columns[3]["column"] = "bannedby"; - LLUUID banning_id = (*it)["banning_id"].asUUID(); - LLAvatarName av_name; - if (banning_id.isNull()) - { - columns[3]["value"] = LLTrans::getString("na"); - } - else if (LLAvatarNameCache::get(banning_id, &av_name)) - { - columns[3]["value"] = av_name.getCompleteName(); //TODO: fetch the name if it wasn't cached - } - - banned_agent_name_list->addElement(item); - } - banned_agent_name_list->sortByName(true); - } - - LLNameListCtrl* allowed_group_name_list = panel->getChild("allowed_group_name_list"); - if (allowed_group_name_list && result.has("AllowedGroups")) - { - LLStringUtil::format_map_t args; - args["[ALLOWEDGROUPS]"] = llformat("%d", result["AllowedGroups"].size()); - args["[MAXACCESS]"] = llformat("%d", ESTATE_MAX_GROUP_IDS); - std::string msg = LLTrans::getString("RegionInfoAllowedGroups", args); - panel->getChild("allow_group_label")->setValue(LLSD(msg)); - - allowed_group_name_list->clearSortOrder(); - allowed_group_name_list->deleteAllItems(); - for (LLSD::array_const_iterator it = result["AllowedGroups"].beginArray(); it != result["AllowedGroups"].endArray(); ++it) - { - LLUUID id = (*it)["id"].asUUID(); - allowed_group_name_list->addGroupNameItem(id); - } - allowed_group_name_list->sortByName(true); - } - - LLNameListCtrl* estate_manager_name_list = panel->getChild("estate_manager_name_list"); - if (estate_manager_name_list && result.has("Managers")) - { - LLStringUtil::format_map_t args; - args["[ESTATEMANAGERS]"] = llformat("%d", result["Managers"].size()); - args["[MAXMANAGERS]"] = llformat("%d", ESTATE_MAX_MANAGERS); - std::string msg = LLTrans::getString("RegionInfoEstateManagers", args); - panel->getChild("estate_manager_label")->setValue(LLSD(msg)); - - estate_manager_name_list->clearSortOrder(); - estate_manager_name_list->deleteAllItems(); - for (LLSD::array_const_iterator it = result["Managers"].beginArray(); it != result["Managers"].endArray(); ++it) - { - LLUUID id = (*it)["agent_id"].asUUID(); - estate_manager_name_list->addNameItem(id); - } - estate_manager_name_list->sortByName(true); - } - - - panel->updateControls(gAgent.getRegion()); -} - -//--------------------------------------------------------------------------- -// Access lists search -//--------------------------------------------------------------------------- -void LLPanelEstateAccess::onAllowedSearchEdit(const std::string& search_string) -{ - LLPanelEstateAccess* panel = LLFloaterRegionInfo::getPanelAccess(); - if (!panel) return; - LLNameListCtrl* allowed_agent_name_list = panel->getChild("allowed_avatar_name_list"); - searchAgent(allowed_agent_name_list, search_string); -} - -void LLPanelEstateAccess::onAllowedGroupsSearchEdit(const std::string& search_string) -{ - LLPanelEstateAccess* panel = LLFloaterRegionInfo::getPanelAccess(); - if (!panel) return; - LLNameListCtrl* allowed_group_name_list = panel->getChild("allowed_group_name_list"); - searchAgent(allowed_group_name_list, search_string); -} - -void LLPanelEstateAccess::onBannedSearchEdit(const std::string& search_string) -{ - LLPanelEstateAccess* panel = LLFloaterRegionInfo::getPanelAccess(); - if (!panel) return; - LLNameListCtrl* banned_agent_name_list = panel->getChild("banned_avatar_name_list"); - searchAgent(banned_agent_name_list, search_string); -} - -void LLPanelEstateAccess::searchAgent(LLNameListCtrl* listCtrl, const std::string& search_string) -{ - if (!listCtrl) return; - - if (!search_string.empty()) - { - listCtrl->setSearchColumn(0); // name column - listCtrl->searchItems(search_string, false, true); - } - else - { - listCtrl->deselectAllItems(true); - } -} - -void LLPanelEstateAccess::copyListToClipboard(std::string list_name) -{ - LLPanelEstateAccess* panel = LLFloaterRegionInfo::getPanelAccess(); - if (!panel) return; - LLNameListCtrl* name_list = panel->getChild(list_name); - if (!name_list) return; - - std::vector list_vector = name_list->getAllData(); - if (list_vector.size() == 0) return; - - LLSD::String list_to_copy; - for (std::vector::const_iterator iter = list_vector.begin(); - iter != list_vector.end(); - iter++) - { - LLScrollListItem *item = (*iter); - if (item) - { - list_to_copy += item->getColumn(0)->getValue().asString(); - } - if (std::next(iter) != list_vector.end()) - { - list_to_copy += "\n"; - } - } - - LLClipboard::instance().copyToClipboard(utf8str_to_wstring(list_to_copy), 0, list_to_copy.length()); -} - -bool LLPanelEstateAccess::refreshFromRegion(LLViewerRegion* region) -{ - updateLists(); - return LLPanelRegionInfo::refreshFromRegion(region); -} - -//========================================================================= -const U32 LLPanelRegionEnvironment::DIRTY_FLAG_OVERRIDE(0x01 << 4); - -LLPanelRegionEnvironment::LLPanelRegionEnvironment(): - LLPanelEnvironmentInfo(), - mAllowOverrideRestore(false) -{ -} - -LLPanelRegionEnvironment::~LLPanelRegionEnvironment() -{ - if (mCommitConnect.connected()) - mCommitConnect.disconnect(); -} - -bool LLPanelRegionEnvironment::postBuild() -{ - LLEstateInfoModel& estate_info = LLEstateInfoModel::instance(); - - if (!LLPanelEnvironmentInfo::postBuild()) - return false; - - getChild(BTN_USEDEFAULT)->setLabelArg("[USEDEFAULT]", getString(STR_LABEL_USEDEFAULT)); - getChild(CHK_ALLOWOVERRIDE)->setVisible(true); - getChild(PNL_ENVIRONMENT_ALTITUDES)->setVisible(true); - - getChild(CHK_ALLOWOVERRIDE)->setCommitCallback([this](LLUICtrl *, const LLSD &value){ onChkAllowOverride(value.asBoolean()); }); - - mCommitConnect = estate_info.setCommitCallback(boost::bind(&LLPanelRegionEnvironment::refreshFromEstate, this)); - return true; -} - - -void LLPanelRegionEnvironment::refresh() -{ - commitDayLenOffsetChanges(false); // commit unsaved changes if any - - if (!mCurrentEnvironment) - { - if (mCurEnvVersion <= INVALID_PARCEL_ENVIRONMENT_VERSION) - { - refreshFromSource(); // will immediately set mCurEnvVersion - } // else - already requesting - return; - } - - LLPanelEnvironmentInfo::refresh(); - - getChild(CHK_ALLOWOVERRIDE)->setValue(mAllowOverride); -} - -bool LLPanelRegionEnvironment::refreshFromRegion(LLViewerRegion* region) -{ - if (!region) - { - setNoSelection(true); - setControlsEnabled(false); - mCurEnvVersion = INVALID_PARCEL_ENVIRONMENT_VERSION; - getChild("region_text")->setValue(LLSD("")); - } - else - { - getChild("region_text")->setValue(LLSD(region->getName())); - } - setNoSelection(false); - - if (gAgent.getRegion()->getRegionID() != region->getRegionID()) - { - setCrossRegion(true); - mCurEnvVersion = INVALID_PARCEL_ENVIRONMENT_VERSION; - } - setCrossRegion(false); - - refreshFromSource(); - return true; -} - -void LLPanelRegionEnvironment::refreshFromSource() -{ - LL_DEBUGS("ENVIRONMENT") << "Requesting environment for region, known version " << mCurEnvVersion << LL_ENDL; - LLHandle that_h = getHandle(); - - if (mCurEnvVersion < UNSET_PARCEL_ENVIRONMENT_VERSION) - { - // to mark as requesting - mCurEnvVersion = UNSET_PARCEL_ENVIRONMENT_VERSION; - } - - LLEnvironment::instance().requestRegion( - [that_h](S32 parcel_id, LLEnvironment::EnvironmentInfo::ptr_t envifo) { _onEnvironmentReceived(that_h, parcel_id, envifo); }); - - setControlsEnabled(false); -} - -bool LLPanelRegionEnvironment::confirmUpdateEstateEnvironment(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - - switch (option) - { - case 0: - { - LLEstateInfoModel& estate_info = LLEstateInfoModel::instance(); - - // update model - estate_info.setAllowEnvironmentOverride(mAllowOverride); - // send the update to sim - estate_info.sendEstateInfo(); - clearDirtyFlag(DIRTY_FLAG_OVERRIDE); - } - break; - - case 1: - mAllowOverride = mAllowOverrideRestore; - getChild(CHK_ALLOWOVERRIDE)->setValue(mAllowOverride); - break; - default: - break; - } - return false; -} - -void LLPanelRegionEnvironment::onChkAllowOverride(bool value) -{ - setDirtyFlag(DIRTY_FLAG_OVERRIDE); - mAllowOverrideRestore = mAllowOverride; - mAllowOverride = value; - - - std::string notification("EstateParcelEnvironmentOverride"); - if (LLPanelEstateInfo::isLindenEstate()) - notification = "ChangeLindenEstate"; - - LLSD args; - args["ESTATENAME"] = LLEstateInfoModel::instance().getName(); - LLNotification::Params params(notification); - params.substitutions(args); - params.functor.function([this](const LLSD& notification, const LLSD& response) { confirmUpdateEstateEnvironment(notification, response); }); - - if (!value || LLPanelEstateInfo::isLindenEstate()) - { // warn if turning off or a Linden Estate - LLNotifications::instance().add(params); - } - else - { - LLNotifications::instance().forceResponse(params, 0); - } - -} +/** + * @file llfloaterregioninfo.cpp + * @author Aaron Brashears + * @brief Implementation of the region info and controls floater and panels. + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llfloaterregioninfo.h" + +#include +#include + +#include "lldir.h" +#include "lldispatcher.h" +#include "llglheaders.h" +#include "llregionflags.h" +#include "llstl.h" +#include "llfilesystem.h" +#include "llxfermanager.h" +#include "indra_constants.h" +#include "message.h" +#include "llloadingindicator.h" +#include "llradiogroup.h" +#include "llsd.h" +#include "llsdserialize.h" + +#include "llagent.h" +#include "llappviewer.h" +#include "llavataractions.h" +#include "llavatarname.h" +#include "llfloateravatarpicker.h" +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "llclipboard.h" +#include "llcombobox.h" +#include "llestateinfomodel.h" +#include "llfilepicker.h" +#include "llfloatergodtools.h" // for send_sim_wide_deletes() +#include "llfloatertopobjects.h" // added to fix SL-32336 +#include "llfloatergroups.h" +#include "llfloaterreg.h" +#include "llfloaterregiondebugconsole.h" +#include "llfloatertelehub.h" +#include "llinventorymodel.h" +#include "lllineeditor.h" +#include "llnamelistctrl.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "llregioninfomodel.h" +#include "llscrolllistitem.h" +#include "llsliderctrl.h" +#include "llslurl.h" +#include "llspinctrl.h" +#include "lltabcontainer.h" +#include "lltextbox.h" +#include "llinventory.h" +#include "lltexturectrl.h" +#include "lltrans.h" +#include "llviewercontrol.h" +#include "lluictrlfactory.h" +#include "llviewerinventory.h" +#include "llviewertexture.h" +#include "llviewertexturelist.h" +#include "llviewerregion.h" +#include "llviewerstats.h" +#include "llviewertexteditor.h" +#include "llviewerwindow.h" +#include "llvlcomposition.h" +#include "lltrans.h" +#include "llagentui.h" +#include "llmeshrepository.h" +#include "llfloaterregionrestarting.h" +#include "llpanelexperiencelisteditor.h" +#include +#include "llpanelexperiencepicker.h" +#include "llexperiencecache.h" +#include "llpanelexperiences.h" +#include "llcorehttputil.h" +#include "llavatarnamecache.h" +#include "llenvironment.h" + +const S32 TERRAIN_TEXTURE_COUNT = 4; +const S32 CORNER_COUNT = 4; + +const U32 MAX_LISTED_NAMES = 100; + +#define TMP_DISABLE_WLES // STORM-1180 + +///---------------------------------------------------------------------------- +/// Local class declaration +///---------------------------------------------------------------------------- + +class LLDispatchEstateUpdateInfo : public LLDispatchHandler +{ +public: + LLDispatchEstateUpdateInfo() {} + virtual ~LLDispatchEstateUpdateInfo() {} + virtual bool operator()( + const LLDispatcher* dispatcher, + const std::string& key, + const LLUUID& invoice, + const sparam_t& strings); +}; + +class LLDispatchSetEstateAccess : public LLDispatchHandler +{ +public: + LLDispatchSetEstateAccess() {} + virtual ~LLDispatchSetEstateAccess() {} + virtual bool operator()( + const LLDispatcher* dispatcher, + const std::string& key, + const LLUUID& invoice, + const sparam_t& strings); +}; + +class LLDispatchSetEstateExperience : public LLDispatchHandler +{ +public: + virtual bool operator()( + const LLDispatcher* dispatcher, + const std::string& key, + const LLUUID& invoice, + const sparam_t& strings); + + static LLSD getIDs( sparam_t::const_iterator it, sparam_t::const_iterator end, S32 count ); +}; + + +/* +void unpack_request_params( + LLMessageSystem* msg, + LLDispatcher::sparam_t& strings, + LLDispatcher::iparam_t& integers) +{ + char str_buf[MAX_STRING]; + S32 str_count = msg->getNumberOfBlocksFast(_PREHASH_StringData); + S32 i; + for (i = 0; i < str_count; ++i) + { + // we treat the SParam as binary data (since it might be an + // LLUUID in compressed form which may have embedded \0's,) + str_buf[0] = '\0'; + S32 data_size = msg->getSizeFast(_PREHASH_StringData, i, _PREHASH_SParam); + if (data_size >= 0) + { + msg->getBinaryDataFast(_PREHASH_StringData, _PREHASH_SParam, + str_buf, data_size, i, MAX_STRING - 1); + strings.push_back(std::string(str_buf, data_size)); + } + } + + U32 int_buf; + S32 int_count = msg->getNumberOfBlocksFast(_PREHASH_IntegerData); + for (i = 0; i < int_count; ++i) + { + msg->getU32("IntegerData", "IParam", int_buf, i); + integers.push_back(int_buf); + } +} +*/ + +class LLPanelRegionEnvironment : public LLPanelEnvironmentInfo +{ +public: + LLPanelRegionEnvironment(); + virtual ~LLPanelRegionEnvironment(); + + virtual void refresh() override; + + virtual bool isRegion() const override { return true; } + virtual LLParcel * getParcel() override { return nullptr; } + virtual bool canEdit() override { return LLEnvironment::instance().canAgentUpdateRegionEnvironment(); } + virtual bool isLargeEnough() override { return true; } // regions are always large enough. + + bool refreshFromRegion(LLViewerRegion* region); + + virtual bool postBuild() override; + virtual void onOpen(const LLSD& key) override {}; + + virtual S32 getParcelId() override { return INVALID_PARCEL_ID; } + +protected: + static const U32 DIRTY_FLAG_OVERRIDE; + + virtual void refreshFromSource() override; + + bool confirmUpdateEstateEnvironment(const LLSD& notification, const LLSD& response); + + void onChkAllowOverride(bool value); + +private: + bool mAllowOverrideRestore; + connection_t mCommitConnect; +}; + + + +bool estate_dispatch_initialized = false; + + +///---------------------------------------------------------------------------- +/// LLFloaterRegionInfo +///---------------------------------------------------------------------------- + +//S32 LLFloaterRegionInfo::sRequestSerial = 0; +LLUUID LLFloaterRegionInfo::sRequestInvoice; + + +LLFloaterRegionInfo::LLFloaterRegionInfo(const LLSD& seed) + : LLFloater(seed), + mEnvironmentPanel(NULL), + mRegionChangedCallback() +{} + +bool LLFloaterRegionInfo::postBuild() +{ + mTab = getChild("region_panels"); + mTab->setCommitCallback(boost::bind(&LLFloaterRegionInfo::onTabSelected, this, _2)); + + // contruct the panels + LLPanelRegionInfo* panel; + panel = new LLPanelEstateInfo; + mInfoPanels.push_back(panel); + panel->buildFromFile("panel_region_estate.xml"); + mTab->addTabPanel(LLTabContainer::TabPanelParams().panel(panel).select_tab(true)); + + panel = new LLPanelEstateAccess; + mInfoPanels.push_back(panel); + panel->buildFromFile("panel_region_access.xml"); + mTab->addTabPanel(panel); + + panel = new LLPanelEstateCovenant; + mInfoPanels.push_back(panel); + panel->buildFromFile("panel_region_covenant.xml"); + mTab->addTabPanel(panel); + + panel = new LLPanelRegionGeneralInfo; + mInfoPanels.push_back(panel); + panel->getCommitCallbackRegistrar().add("RegionInfo.ManageTelehub", boost::bind(&LLPanelRegionInfo::onClickManageTelehub, panel)); + panel->buildFromFile("panel_region_general.xml"); + mTab->addTabPanel(panel); + + panel = new LLPanelRegionTerrainInfo; + mInfoPanels.push_back(panel); + panel->buildFromFile("panel_region_terrain.xml"); + mTab->addTabPanel(panel); + + mEnvironmentPanel = new LLPanelRegionEnvironment; + mEnvironmentPanel->buildFromFile("panel_region_environment.xml"); +// mEnvironmentPanel->configureForRegion(); + mTab->addTabPanel(mEnvironmentPanel); + + panel = new LLPanelRegionDebugInfo; + mInfoPanels.push_back(panel); + panel->buildFromFile("panel_region_debug.xml"); + mTab->addTabPanel(panel); + + if(gDisconnected) + { + return true; + } + + if(!gAgent.getRegionCapability("RegionExperiences").empty()) + { + panel = new LLPanelRegionExperiences; + mInfoPanels.push_back(panel); + panel->buildFromFile("panel_region_experiences.xml"); + mTab->addTabPanel(panel); + } + + gMessageSystem->setHandlerFunc( + "EstateOwnerMessage", + &processEstateOwnerRequest); + + // Request region info when agent region changes. + mRegionChangedCallback = gAgent.addRegionChangedCallback(boost::bind(&LLFloaterRegionInfo::onRegionChanged, this)); + + return true; +} + +LLFloaterRegionInfo::~LLFloaterRegionInfo() +{ + if (mRegionChangedCallback.connected()) + { + mRegionChangedCallback.disconnect(); + } +} + +void LLFloaterRegionInfo::onOpen(const LLSD& key) +{ + if(gDisconnected) + { + disableTabCtrls(); + return; + } + refreshFromRegion(gAgent.getRegion()); + requestRegionInfo(); + + if (!mGodLevelChangeSlot.connected()) + { + mGodLevelChangeSlot = gAgent.registerGodLevelChanageListener(boost::bind(&LLFloaterRegionInfo::onGodLevelChange, this, _1)); + } +} + +void LLFloaterRegionInfo::onClose(bool app_quitting) +{ + if (mGodLevelChangeSlot.connected()) + { + mGodLevelChangeSlot.disconnect(); + } +} + +void LLFloaterRegionInfo::onRegionChanged() +{ + if (getVisible()) //otherwise onOpen will do request + { + requestRegionInfo(); + } +} + +// static +void LLFloaterRegionInfo::requestRegionInfo() +{ + LLTabContainer* tab = findChild("region_panels"); + if (tab) + { + tab->getChild("General")->setCtrlsEnabled(false); + tab->getChild("Debug")->setCtrlsEnabled(false); + tab->getChild("Terrain")->setCtrlsEnabled(false); + tab->getChild("Estate")->setCtrlsEnabled(false); + tab->getChild("Access")->setCtrlsEnabled(false); + } + + // Must allow anyone to request the RegionInfo data + // so non-owners/non-gods can see the values. + // Therefore can't use an EstateOwnerMessage JC + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("RequestRegionInfo"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + gAgent.sendReliableMessage(); +} + +// static +void LLFloaterRegionInfo::processEstateOwnerRequest(LLMessageSystem* msg,void**) +{ + static LLDispatcher dispatch; + LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance("region_info"); + if(!floater) + { + return; + } + + if (!estate_dispatch_initialized) + { + LLPanelEstateInfo::initDispatch(dispatch); + } + + LLPanelEstateInfo* panel = LLFloaterRegionInfo::getPanelEstate(); + + // unpack the message + std::string request; + LLUUID invoice; + LLDispatcher::sparam_t strings; + LLDispatcher::unpackMessage(msg, request, invoice, strings); + if(invoice != getLastInvoice()) + { + LL_WARNS() << "Mismatched Estate message: " << request << LL_ENDL; + return; + } + + //dispatch the message + dispatch.dispatch(request, invoice, strings); + + if (panel) + { + panel->updateControls(gAgent.getRegion()); + } +} + + +// static +void LLFloaterRegionInfo::processRegionInfo(LLMessageSystem* msg) +{ + LLPanel* panel; + LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance("region_info"); + if(!floater) + { + return; + } +#if 0 + // We need to re-request environment setting here, + // otherwise after we apply (send) updated region settings we won't get them back, + // so our environment won't be updated. + // This is also the way to know about externally changed region environment. + LLEnvManagerNew::instance().requestRegionSettings(); +#endif + LLTabContainer* tab = floater->getChild("region_panels"); + + LLViewerRegion* region = gAgent.getRegion(); + bool allow_modify = gAgent.isGodlike() || (region && region->canManageEstate()); + + // *TODO: Replace parsing msg with accessing the region info model. + LLRegionInfoModel& region_info = LLRegionInfoModel::instance(); + + // extract message + std::string sim_name; + std::string sim_type = LLTrans::getString("land_type_unknown"); + U64 region_flags; + U8 agent_limit; + S32 hard_agent_limit; + F32 object_bonus_factor; + U8 sim_access; + F32 water_height; + F32 terrain_raise_limit; + F32 terrain_lower_limit; + bool use_estate_sun; + F32 sun_hour; + msg->getString("RegionInfo", "SimName", sim_name); + msg->getU8("RegionInfo", "MaxAgents", agent_limit); + msg->getS32("RegionInfo2", "HardMaxAgents", hard_agent_limit); + msg->getF32("RegionInfo", "ObjectBonusFactor", object_bonus_factor); + msg->getU8("RegionInfo", "SimAccess", sim_access); + msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_WaterHeight, water_height); + msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_TerrainRaiseLimit, terrain_raise_limit); + msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_TerrainLowerLimit, terrain_lower_limit); + msg->getBOOL("RegionInfo", "UseEstateSun", use_estate_sun); + // actually the "last set" sun hour, not the current sun hour. JC + msg->getF32("RegionInfo", "SunHour", sun_hour); + // the only reasonable way to decide if we actually have any data is to + // check to see if any of these fields have nonzero sizes + if (msg->getSize("RegionInfo2", "ProductSKU") > 0 || + msg->getSize("RegionInfo2", "ProductName") > 0) + { + msg->getString("RegionInfo2", "ProductName", sim_type); + LLTrans::findString(sim_type, sim_type); // try localizing sim product name + } + + if (msg->has(_PREHASH_RegionInfo3)) + { + msg->getU64("RegionInfo3", "RegionFlagsExtended", region_flags); + } + else + { + U32 flags = 0; + msg->getU32("RegionInfo", "RegionFlags", flags); + region_flags = flags; + } + + if (msg->has(_PREHASH_RegionInfo5)) + { + F32 chat_whisper_range; + F32 chat_normal_range; + F32 chat_shout_range; + F32 chat_whisper_offset; + F32 chat_normal_offset; + F32 chat_shout_offset; + U32 chat_flags; + + msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatWhisperRange, chat_whisper_range); + msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatNormalRange, chat_normal_range); + msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatShoutRange, chat_shout_range); + msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatWhisperOffset, chat_whisper_offset); + msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatNormalOffset, chat_normal_offset); + msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatShoutOffset, chat_shout_offset); + msg->getU32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatFlags, chat_flags); + + LL_INFOS() << "Whisper range: " << chat_whisper_range << " normal range: " << chat_normal_range << " shout range: " << chat_shout_range + << " whisper offset: " << chat_whisper_offset << " normal offset: " << chat_normal_offset << " shout offset: " << chat_shout_offset + << " chat flags: " << chat_flags << LL_ENDL; + } + + // GENERAL PANEL + panel = tab->getChild("General"); + panel->getChild("region_text")->setValue(LLSD(sim_name)); + panel->getChild("region_type")->setValue(LLSD(sim_type)); + panel->getChild("version_channel_text")->setValue(gLastVersionChannel); + + panel->getChild("block_terraform_check")->setValue(is_flag_set(region_flags, REGION_FLAGS_BLOCK_TERRAFORM)); + panel->getChild("block_fly_check")->setValue(is_flag_set(region_flags, REGION_FLAGS_BLOCK_FLY)); + panel->getChild("block_fly_over_check")->setValue(is_flag_set(region_flags, REGION_FLAGS_BLOCK_FLYOVER)); + panel->getChild("allow_damage_check")->setValue(is_flag_set(region_flags, REGION_FLAGS_ALLOW_DAMAGE)); + panel->getChild("restrict_pushobject")->setValue(is_flag_set(region_flags, REGION_FLAGS_RESTRICT_PUSHOBJECT)); + panel->getChild("allow_land_resell_check")->setValue(is_flag_set(region_flags, REGION_FLAGS_BLOCK_LAND_RESELL)); + panel->getChild("allow_parcel_changes_check")->setValue(is_flag_set(region_flags, REGION_FLAGS_ALLOW_PARCEL_CHANGES)); + panel->getChild("block_parcel_search_check")->setValue(is_flag_set(region_flags, REGION_FLAGS_BLOCK_PARCEL_SEARCH)); + panel->getChild("agent_limit_spin")->setValue(LLSD((F32)agent_limit)); + panel->getChild("object_bonus_spin")->setValue(LLSD(object_bonus_factor)); + panel->getChild("access_combo")->setValue(LLSD(sim_access)); + + panel->getChild("agent_limit_spin")->setMaxValue(hard_agent_limit); + + LLPanelRegionGeneralInfo* panel_general = LLFloaterRegionInfo::getPanelGeneral(); + if (panel) + { + panel_general->setObjBonusFactor(object_bonus_factor); + } + + // detect teen grid for maturity + + U32 parent_estate_id; + msg->getU32("RegionInfo", "ParentEstateID", parent_estate_id); + bool teen_grid = (parent_estate_id == 5); // *TODO add field to estate table and test that + panel->getChildView("access_combo")->setEnabled(gAgent.isGodlike() || (region && region->canManageEstate() && !teen_grid)); + panel->setCtrlsEnabled(allow_modify); + + + // DEBUG PANEL + panel = tab->getChild("Debug"); + + panel->getChild("region_text")->setValue(LLSD(sim_name) ); + panel->getChild("disable_scripts_check")->setValue(LLSD((bool)(region_flags & REGION_FLAGS_SKIP_SCRIPTS))); + panel->getChild("disable_collisions_check")->setValue(LLSD((bool)(region_flags & REGION_FLAGS_SKIP_COLLISIONS))); + panel->getChild("disable_physics_check")->setValue(LLSD((bool)(region_flags & REGION_FLAGS_SKIP_PHYSICS))); + panel->setCtrlsEnabled(allow_modify); + + // TERRAIN PANEL + panel = tab->getChild("Terrain"); + + panel->getChild("region_text")->setValue(LLSD(sim_name)); + panel->getChild("water_height_spin")->setValue(region_info.mWaterHeight); + panel->getChild("terrain_raise_spin")->setValue(region_info.mTerrainRaiseLimit); + panel->getChild("terrain_lower_spin")->setValue(region_info.mTerrainLowerLimit); + + panel->setCtrlsEnabled(allow_modify); + + if (floater->getVisible()) + { + // Note: region info also causes LLRegionInfoModel::instance().update(msg); -> requestRegion(); -> changed message + // we need to know env version here and in update(msg) to know when to request and when not to, when to filter 'changed' + floater->refreshFromRegion(gAgent.getRegion()); + } // else will rerequest on onOpen either way +} + +// static +LLPanelEstateInfo* LLFloaterRegionInfo::getPanelEstate() +{ + LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance("region_info"); + if (!floater) return NULL; + LLTabContainer* tab = floater->getChild("region_panels"); + LLPanelEstateInfo* panel = (LLPanelEstateInfo*)tab->getChild("Estate"); + return panel; +} + +// static +LLPanelEstateAccess* LLFloaterRegionInfo::getPanelAccess() +{ + LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance("region_info"); + if (!floater) return NULL; + LLTabContainer* tab = floater->getChild("region_panels"); + LLPanelEstateAccess* panel = (LLPanelEstateAccess*)tab->getChild("Access"); + return panel; +} + +// static +LLPanelEstateCovenant* LLFloaterRegionInfo::getPanelCovenant() +{ + LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance("region_info"); + if (!floater) return NULL; + LLTabContainer* tab = floater->getChild("region_panels"); + LLPanelEstateCovenant* panel = (LLPanelEstateCovenant*)tab->getChild("Covenant"); + return panel; +} + +// static +LLPanelRegionGeneralInfo* LLFloaterRegionInfo::getPanelGeneral() +{ + LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance("region_info"); + if (!floater) return NULL; + LLTabContainer* tab = floater->getChild("region_panels"); + LLPanelRegionGeneralInfo* panel = (LLPanelRegionGeneralInfo*)tab->getChild("General"); + return panel; +} + +// static +LLPanelRegionEnvironment* LLFloaterRegionInfo::getPanelEnvironment() +{ + LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance("region_info"); + if (!floater) return NULL; + LLTabContainer* tab = floater->getChild("region_panels"); + LLPanelRegionEnvironment* panel = (LLPanelRegionEnvironment*)tab->getChild("panel_env_info"); + return panel; +} + +// static +LLPanelRegionTerrainInfo* LLFloaterRegionInfo::getPanelRegionTerrain() +{ + LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance("region_info"); + if (!floater) + { + llassert(floater); + return NULL; + } + + LLTabContainer* tab_container = floater->getChild("region_panels"); + LLPanelRegionTerrainInfo* panel = + dynamic_cast(tab_container->getChild("Terrain")); + llassert(panel); + return panel; +} + +LLPanelRegionExperiences* LLFloaterRegionInfo::getPanelExperiences() +{ + LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance("region_info"); + if (!floater) return NULL; + LLTabContainer* tab = floater->getChild("region_panels"); + return (LLPanelRegionExperiences*)tab->getChild("Experiences"); +} + +void LLFloaterRegionInfo::disableTabCtrls() +{ + LLTabContainer* tab = getChild("region_panels"); + + tab->getChild("General")->setCtrlsEnabled(false); + tab->getChild("Debug")->setCtrlsEnabled(false); + tab->getChild("Terrain")->setCtrlsEnabled(false); + tab->getChild("panel_env_info")->setCtrlsEnabled(false); + tab->getChild("Estate")->setCtrlsEnabled(false); + tab->getChild("Access")->setCtrlsEnabled(false); +} + +void LLFloaterRegionInfo::onTabSelected(const LLSD& param) +{ + LLPanel* active_panel = getChild(param.asString()); + active_panel->onOpen(LLSD()); +} + +void LLFloaterRegionInfo::refreshFromRegion(LLViewerRegion* region) +{ + if (!region) + { + return; + } + + // call refresh from region on all panels + for (const auto& infoPanel : mInfoPanels) + { + infoPanel->refreshFromRegion(region); + } + mEnvironmentPanel->refreshFromRegion(region); +} + +// public +void LLFloaterRegionInfo::refresh() +{ + for(info_panels_t::iterator iter = mInfoPanels.begin(); + iter != mInfoPanels.end(); ++iter) + { + (*iter)->refresh(); + } + mEnvironmentPanel->refresh(); +} + +void LLFloaterRegionInfo::enableTopButtons() +{ + getChildView("top_colliders_btn")->setEnabled(true); + getChildView("top_scripts_btn")->setEnabled(true); +} + +void LLFloaterRegionInfo::disableTopButtons() +{ + getChildView("top_colliders_btn")->setEnabled(false); + getChildView("top_scripts_btn")->setEnabled(false); +} + +void LLFloaterRegionInfo::onGodLevelChange(U8 god_level) +{ + LLFloaterRegionInfo* floater = LLFloaterReg::getTypedInstance("region_info"); + if (floater && floater->getVisible()) + { + refreshFromRegion(gAgent.getRegion()); + } +} + +///---------------------------------------------------------------------------- +/// Local class implementation +///---------------------------------------------------------------------------- + +// +// LLPanelRegionInfo +// + +LLPanelRegionInfo::LLPanelRegionInfo() + : LLPanel() +{ +} + +void LLPanelRegionInfo::onBtnSet() +{ + if (sendUpdate()) + { + disableButton("apply_btn"); + } +} + +void LLPanelRegionInfo::onChangeChildCtrl(LLUICtrl* ctrl) +{ + updateChild(ctrl); // virtual function +} + +// Enables the "set" button if it is not already enabled +void LLPanelRegionInfo::onChangeAnything() +{ + enableButton("apply_btn"); + refresh(); +} + +// static +// Enables set button on change to line editor +void LLPanelRegionInfo::onChangeText(LLLineEditor* caller, void* user_data) +{ + LLPanelRegionInfo* panel = dynamic_cast(caller->getParent()); + if(panel) + { + panel->enableButton("apply_btn"); + panel->refresh(); + } +} + + +// virtual +bool LLPanelRegionInfo::postBuild() +{ + // If the panel has an Apply button, set a callback for it. + LLUICtrl* apply_btn = findChild("apply_btn"); + if (apply_btn) + { + apply_btn->setCommitCallback(boost::bind(&LLPanelRegionInfo::onBtnSet, this)); + } + + refresh(); + return true; +} + +// virtual +void LLPanelRegionInfo::updateChild(LLUICtrl* child_ctr) +{ +} + +// virtual +bool LLPanelRegionInfo::refreshFromRegion(LLViewerRegion* region) +{ + if (region) mHost = region->getHost(); + return true; +} + +void LLPanelRegionInfo::sendEstateOwnerMessage( + LLMessageSystem* msg, + const std::string& request, + const LLUUID& invoice, + const strings_t& strings) +{ + LL_INFOS() << "Sending estate request '" << request << "'" << LL_ENDL; + msg->newMessage("EstateOwnerMessage"); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addUUIDFast(_PREHASH_TransactionID, LLUUID::null); //not used + msg->nextBlock("MethodData"); + msg->addString("Method", request); + msg->addUUID("Invoice", invoice); + if(strings.empty()) + { + msg->nextBlock("ParamList"); + msg->addString("Parameter", NULL); + } + else + { + strings_t::const_iterator it = strings.begin(); + strings_t::const_iterator end = strings.end(); + for(; it != end; ++it) + { + msg->nextBlock("ParamList"); + msg->addString("Parameter", *it); + } + } + msg->sendReliable(mHost); +} + +void LLPanelRegionInfo::enableButton(const std::string& btn_name, bool enable) +{ + LLView* button = findChildView(btn_name); + if (button) button->setEnabled(enable); +} + +void LLPanelRegionInfo::disableButton(const std::string& btn_name) +{ + LLView* button = findChildView(btn_name); + if (button) button->setEnabled(false); +} + +void LLPanelRegionInfo::initCtrl(const std::string& name) +{ + getChild(name)->setCommitCallback(boost::bind(&LLPanelRegionInfo::onChangeAnything, this)); +} + +void LLPanelRegionInfo::onClickManageTelehub() +{ + LLFloaterReg::hideInstance("region_info"); + LLFloaterReg::showInstance("telehubs"); +} + +///////////////////////////////////////////////////////////////////////////// +// LLPanelRegionGeneralInfo +// +bool LLPanelRegionGeneralInfo::refreshFromRegion(LLViewerRegion* region) +{ + bool allow_modify = gAgent.isGodlike() || (region && region->canManageEstate()); + setCtrlsEnabled(allow_modify); + getChildView("apply_btn")->setEnabled(false); + getChildView("access_text")->setEnabled(allow_modify); + // getChildView("access_combo")->setEnabled(allow_modify); + // now set in processRegionInfo for teen grid detection + getChildView("kick_btn")->setEnabled(allow_modify); + getChildView("kick_all_btn")->setEnabled(allow_modify); + getChildView("im_btn")->setEnabled(allow_modify); + getChildView("manage_telehub_btn")->setEnabled(allow_modify); + + // Data gets filled in by processRegionInfo + + return LLPanelRegionInfo::refreshFromRegion(region); +} + +bool LLPanelRegionGeneralInfo::postBuild() +{ + // Enable the "Apply" button if something is changed. JC + initCtrl("block_terraform_check"); + initCtrl("block_fly_check"); + initCtrl("block_fly_over_check"); + initCtrl("allow_damage_check"); + initCtrl("allow_land_resell_check"); + initCtrl("allow_parcel_changes_check"); + initCtrl("agent_limit_spin"); + initCtrl("object_bonus_spin"); + initCtrl("access_combo"); + initCtrl("restrict_pushobject"); + initCtrl("block_parcel_search_check"); + + childSetAction("kick_btn", boost::bind(&LLPanelRegionGeneralInfo::onClickKick, this)); + childSetAction("kick_all_btn", onClickKickAll, this); + childSetAction("im_btn", onClickMessage, this); +// childSetAction("manage_telehub_btn", onClickManageTelehub, this); + + LLUICtrl* apply_btn = findChild("apply_btn"); + if (apply_btn) + { + apply_btn->setCommitCallback(boost::bind(&LLPanelRegionGeneralInfo::onBtnSet, this)); + } + + refresh(); + return true; +} + +void LLPanelRegionGeneralInfo::onBtnSet() +{ + if(mObjBonusFactor == getChild("object_bonus_spin")->getValue().asReal()) + { + if (sendUpdate()) + { + disableButton("apply_btn"); + } + } + else + { + LLNotificationsUtil::add("ChangeObjectBonusFactor", LLSD(), LLSD(), boost::bind(&LLPanelRegionGeneralInfo::onChangeObjectBonus, this, _1, _2)); + } +} + +bool LLPanelRegionGeneralInfo::onChangeObjectBonus(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) + { + if (sendUpdate()) + { + disableButton("apply_btn"); + } + } + return false; +} + +void LLPanelRegionGeneralInfo::onClickKick() +{ + LL_INFOS() << "LLPanelRegionGeneralInfo::onClickKick" << LL_ENDL; + + // this depends on the grandparent view being a floater + // in order to set up floater dependency + LLView * button = findChild("kick_btn"); + LLFloater* parent_floater = gFloaterView->getParentFloater(this); + LLFloater* child_floater = LLFloaterAvatarPicker::show(boost::bind(&LLPanelRegionGeneralInfo::onKickCommit, this, _1), + false, true, false, parent_floater->getName(), button); + if (child_floater) + { + parent_floater->addDependentFloater(child_floater); + } +} + +void LLPanelRegionGeneralInfo::onKickCommit(const uuid_vec_t& ids) +{ + if (ids.empty()) return; + if(ids[0].notNull()) + { + strings_t strings; + // [0] = our agent id + // [1] = target agent id + std::string buffer; + gAgent.getID().toString(buffer); + strings.push_back(buffer); + + ids[0].toString(buffer); + strings.push_back(strings_t::value_type(buffer)); + + LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); + sendEstateOwnerMessage(gMessageSystem, "teleporthomeuser", invoice, strings); + } +} + +// static +void LLPanelRegionGeneralInfo::onClickKickAll(void* userdata) +{ + LL_INFOS() << "LLPanelRegionGeneralInfo::onClickKickAll" << LL_ENDL; + LLNotificationsUtil::add("KickUsersFromRegion", + LLSD(), + LLSD(), + boost::bind(&LLPanelRegionGeneralInfo::onKickAllCommit, (LLPanelRegionGeneralInfo*)userdata, _1, _2)); +} + +bool LLPanelRegionGeneralInfo::onKickAllCommit(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) + { + strings_t strings; + // [0] = our agent id + std::string buffer; + gAgent.getID().toString(buffer); + strings.push_back(buffer); + + LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); + // historical message name + sendEstateOwnerMessage(gMessageSystem, "teleporthomeallusers", invoice, strings); + } + return false; +} + +// static +void LLPanelRegionGeneralInfo::onClickMessage(void* userdata) +{ + LL_INFOS() << "LLPanelRegionGeneralInfo::onClickMessage" << LL_ENDL; + LLNotificationsUtil::add("MessageRegion", + LLSD(), + LLSD(), + boost::bind(&LLPanelRegionGeneralInfo::onMessageCommit, (LLPanelRegionGeneralInfo*)userdata, _1, _2)); +} + +// static +bool LLPanelRegionGeneralInfo::onMessageCommit(const LLSD& notification, const LLSD& response) +{ + if(LLNotificationsUtil::getSelectedOption(notification, response) != 0) return false; + + std::string text = response["message"].asString(); + if (text.empty()) return false; + + LL_INFOS() << "Message to everyone: " << text << LL_ENDL; + strings_t strings; + // [0] grid_x, unused here + // [1] grid_y, unused here + // [2] agent_id of sender + // [3] sender name + // [4] message + strings.push_back("-1"); + strings.push_back("-1"); + std::string buffer; + gAgent.getID().toString(buffer); + strings.push_back(buffer); + std::string name; + LLAgentUI::buildFullname(name); + strings.push_back(strings_t::value_type(name)); + strings.push_back(strings_t::value_type(text)); + LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); + sendEstateOwnerMessage(gMessageSystem, "simulatormessage", invoice, strings); + return false; +} + +// setregioninfo +// strings[0] = 'Y' - block terraform, 'N' - not +// strings[1] = 'Y' - block fly, 'N' - not +// strings[2] = 'Y' - allow damage, 'N' - not +// strings[3] = 'Y' - allow land sale, 'N' - not +// strings[4] = agent limit +// strings[5] = object bonus +// strings[6] = sim access (0 = unknown, 13 = PG, 21 = Mature, 42 = Adult) +// strings[7] = restrict pushobject +// strings[8] = 'Y' - allow parcel subdivide, 'N' - not +// strings[9] = 'Y' - block parcel search, 'N' - allow +bool LLPanelRegionGeneralInfo::sendUpdate() +{ + LL_INFOS() << "LLPanelRegionGeneralInfo::sendUpdate()" << LL_ENDL; + + // First try using a Cap. If that fails use the old method. + LLSD body; + std::string url = gAgent.getRegionCapability("DispatchRegionInfo"); + if (!url.empty()) + { + body["block_terraform"] = getChild("block_terraform_check")->getValue(); + body["block_fly"] = getChild("block_fly_check")->getValue(); + body["block_fly_over"] = getChild("block_fly_over_check")->getValue(); + body["allow_damage"] = getChild("allow_damage_check")->getValue(); + body["allow_land_resell"] = getChild("allow_land_resell_check")->getValue(); + body["agent_limit"] = getChild("agent_limit_spin")->getValue(); + body["prim_bonus"] = getChild("object_bonus_spin")->getValue(); + body["sim_access"] = getChild("access_combo")->getValue(); + body["restrict_pushobject"] = getChild("restrict_pushobject")->getValue(); + body["allow_parcel_changes"] = getChild("allow_parcel_changes_check")->getValue(); + body["block_parcel_search"] = getChild("block_parcel_search_check")->getValue(); + + LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, body, + "Region info update posted.", "Region info update not posted."); + } + else + { + strings_t strings; + std::string buffer; + + buffer = llformat("%s", (getChild("block_terraform_check")->getValue().asBoolean() ? "Y" : "N")); + strings.push_back(strings_t::value_type(buffer)); + + buffer = llformat("%s", (getChild("block_fly_check")->getValue().asBoolean() ? "Y" : "N")); + strings.push_back(strings_t::value_type(buffer)); + + buffer = llformat("%s", (getChild("allow_damage_check")->getValue().asBoolean() ? "Y" : "N")); + strings.push_back(strings_t::value_type(buffer)); + + buffer = llformat("%s", (getChild("allow_land_resell_check")->getValue().asBoolean() ? "Y" : "N")); + strings.push_back(strings_t::value_type(buffer)); + + F32 value = (F32)getChild("agent_limit_spin")->getValue().asReal(); + buffer = llformat("%f", value); + strings.push_back(strings_t::value_type(buffer)); + + value = (F32)getChild("object_bonus_spin")->getValue().asReal(); + buffer = llformat("%f", value); + strings.push_back(strings_t::value_type(buffer)); + + buffer = llformat("%d", getChild("access_combo")->getValue().asInteger()); + strings.push_back(strings_t::value_type(buffer)); + + buffer = llformat("%s", (getChild("restrict_pushobject")->getValue().asBoolean() ? "Y" : "N")); + strings.push_back(strings_t::value_type(buffer)); + + buffer = llformat("%s", (getChild("allow_parcel_changes_check")->getValue().asBoolean() ? "Y" : "N")); + strings.push_back(strings_t::value_type(buffer)); + + LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); + sendEstateOwnerMessage(gMessageSystem, "setregioninfo", invoice, strings); + } + + // if we changed access levels, tell user about it + LLViewerRegion* region = gAgent.getRegion(); + if (region && (getChild("access_combo")->getValue().asInteger() != region->getSimAccess()) ) + { + LLNotificationsUtil::add("RegionMaturityChange"); + } + + return true; +} + +///////////////////////////////////////////////////////////////////////////// +// LLPanelRegionDebugInfo +///////////////////////////////////////////////////////////////////////////// +bool LLPanelRegionDebugInfo::postBuild() +{ + LLPanelRegionInfo::postBuild(); + initCtrl("disable_scripts_check"); + initCtrl("disable_collisions_check"); + initCtrl("disable_physics_check"); + + childSetAction("choose_avatar_btn", boost::bind(&LLPanelRegionDebugInfo::onClickChooseAvatar, this)); + childSetAction("return_btn", onClickReturn, this); + childSetAction("top_colliders_btn", onClickTopColliders, this); + childSetAction("top_scripts_btn", onClickTopScripts, this); + childSetAction("restart_btn", onClickRestart, this); + childSetAction("cancel_restart_btn", onClickCancelRestart, this); + childSetAction("region_debug_console_btn", onClickDebugConsole, this); + + return true; +} + +// virtual +bool LLPanelRegionDebugInfo::refreshFromRegion(LLViewerRegion* region) +{ + bool allow_modify = gAgent.isGodlike() || (region && region->canManageEstate()); + setCtrlsEnabled(allow_modify); + getChildView("apply_btn")->setEnabled(false); + getChildView("target_avatar_name")->setEnabled(false); + + getChildView("choose_avatar_btn")->setEnabled(allow_modify); + getChildView("return_scripts")->setEnabled(allow_modify && !mTargetAvatar.isNull()); + getChildView("return_other_land")->setEnabled(allow_modify && !mTargetAvatar.isNull()); + getChildView("return_estate_wide")->setEnabled(allow_modify && !mTargetAvatar.isNull()); + getChildView("return_btn")->setEnabled(allow_modify && !mTargetAvatar.isNull()); + getChildView("top_colliders_btn")->setEnabled(allow_modify); + getChildView("top_scripts_btn")->setEnabled(allow_modify); + getChildView("restart_btn")->setEnabled(allow_modify); + getChildView("cancel_restart_btn")->setEnabled(allow_modify); + getChildView("region_debug_console_btn")->setEnabled(allow_modify); + + return LLPanelRegionInfo::refreshFromRegion(region); +} + +// virtual +bool LLPanelRegionDebugInfo::sendUpdate() +{ + LL_INFOS() << "LLPanelRegionDebugInfo::sendUpdate" << LL_ENDL; + strings_t strings; + std::string buffer; + + buffer = llformat("%s", (getChild("disable_scripts_check")->getValue().asBoolean() ? "Y" : "N")); + strings.push_back(buffer); + + buffer = llformat("%s", (getChild("disable_collisions_check")->getValue().asBoolean() ? "Y" : "N")); + strings.push_back(buffer); + + buffer = llformat("%s", (getChild("disable_physics_check")->getValue().asBoolean() ? "Y" : "N")); + strings.push_back(buffer); + + LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); + sendEstateOwnerMessage(gMessageSystem, "setregiondebug", invoice, strings); + return true; +} + +void LLPanelRegionDebugInfo::onClickChooseAvatar() +{ + LLView * button = findChild("choose_avatar_btn"); + LLFloater* parent_floater = gFloaterView->getParentFloater(this); + LLFloater * child_floater = LLFloaterAvatarPicker::show(boost::bind(&LLPanelRegionDebugInfo::callbackAvatarID, this, _1, _2), + false, true, false, parent_floater->getName(), button); + if (child_floater) + { + parent_floater->addDependentFloater(child_floater); + } +} + + +void LLPanelRegionDebugInfo::callbackAvatarID(const uuid_vec_t& ids, const std::vector names) +{ + if (ids.empty() || names.empty()) return; + mTargetAvatar = ids[0]; + getChild("target_avatar_name")->setValue(LLSD(names[0].getCompleteName())); + refreshFromRegion( gAgent.getRegion() ); +} + +// static +void LLPanelRegionDebugInfo::onClickReturn(void* data) +{ + LLPanelRegionDebugInfo* panelp = (LLPanelRegionDebugInfo*) data; + if (panelp->mTargetAvatar.isNull()) return; + + LLSD args; + args["USER_NAME"] = panelp->getChild("target_avatar_name")->getValue().asString(); + LLSD payload; + payload["avatar_id"] = panelp->mTargetAvatar; + + U32 flags = SWD_ALWAYS_RETURN_OBJECTS; + + if (panelp->getChild("return_scripts")->getValue().asBoolean()) + { + flags |= SWD_SCRIPTED_ONLY; + } + + if (panelp->getChild("return_other_land")->getValue().asBoolean()) + { + flags |= SWD_OTHERS_LAND_ONLY; + } + payload["flags"] = int(flags); + payload["return_estate_wide"] = panelp->getChild("return_estate_wide")->getValue().asBoolean(); + LLNotificationsUtil::add("EstateObjectReturn", args, payload, + boost::bind(&LLPanelRegionDebugInfo::callbackReturn, panelp, _1, _2)); +} + +bool LLPanelRegionDebugInfo::callbackReturn(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) return false; + + LLUUID target_avatar = notification["payload"]["avatar_id"].asUUID(); + if (!target_avatar.isNull()) + { + U32 flags = notification["payload"]["flags"].asInteger(); + bool return_estate_wide = notification["payload"]["return_estate_wide"]; + if (return_estate_wide) + { + // send as estate message - routed by spaceserver to all regions in estate + strings_t strings; + strings.push_back(llformat("%d", flags)); + strings.push_back(target_avatar.asString()); + + LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); + + sendEstateOwnerMessage(gMessageSystem, "estateobjectreturn", invoice, strings); + } + else + { + // send to this simulator only + send_sim_wide_deletes(target_avatar, flags); + } + } + return false; +} + + +// static +void LLPanelRegionDebugInfo::onClickTopColliders(void* data) +{ + LLPanelRegionDebugInfo* self = (LLPanelRegionDebugInfo*)data; + strings_t strings; + strings.push_back("1"); // one physics step + LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); + LLFloaterTopObjects* instance = LLFloaterReg::getTypedInstance("top_objects"); + if(!instance) return; + LLFloaterReg::showInstance("top_objects"); + instance->clearList(); + instance->disableRefreshBtn(); + + self->getChildView("top_colliders_btn")->setEnabled(false); + self->getChildView("top_scripts_btn")->setEnabled(false); + + self->sendEstateOwnerMessage(gMessageSystem, "colliders", invoice, strings); +} + +// static +void LLPanelRegionDebugInfo::onClickTopScripts(void* data) +{ + LLPanelRegionDebugInfo* self = (LLPanelRegionDebugInfo*)data; + strings_t strings; + strings.push_back("6"); // top 5 scripts + LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); + LLFloaterTopObjects* instance = LLFloaterReg::getTypedInstance("top_objects"); + if(!instance) return; + LLFloaterReg::showInstance("top_objects"); + instance->clearList(); + instance->disableRefreshBtn(); + + self->getChildView("top_colliders_btn")->setEnabled(false); + self->getChildView("top_scripts_btn")->setEnabled(false); + + self->sendEstateOwnerMessage(gMessageSystem, "scripts", invoice, strings); +} + +// static +void LLPanelRegionDebugInfo::onClickRestart(void* data) +{ + LLNotificationsUtil::add("ConfirmRestart", LLSD(), LLSD(), + boost::bind(&LLPanelRegionDebugInfo::callbackRestart, (LLPanelRegionDebugInfo*)data, _1, _2)); +} + +bool LLPanelRegionDebugInfo::callbackRestart(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) return false; + + strings_t strings; + strings.push_back("120"); + LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); + sendEstateOwnerMessage(gMessageSystem, "restart", invoice, strings); + return false; +} + +// static +void LLPanelRegionDebugInfo::onClickCancelRestart(void* data) +{ + LLPanelRegionDebugInfo* self = (LLPanelRegionDebugInfo*)data; + strings_t strings; + strings.push_back("-1"); + LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); + self->sendEstateOwnerMessage(gMessageSystem, "restart", invoice, strings); +} + +// static +void LLPanelRegionDebugInfo::onClickDebugConsole(void* data) +{ + LLFloaterReg::showInstance("region_debug_console"); +} + +bool LLPanelRegionTerrainInfo::validateTextureSizes() +{ + static const S32 MAX_TERRAIN_TEXTURE_SIZE = 1024; + for(S32 i = 0; i < TERRAIN_TEXTURE_COUNT; ++i) + { + std::string buffer; + buffer = llformat("texture_detail_%d", i); + LLTextureCtrl* texture_ctrl = getChild(buffer); + if (!texture_ctrl) continue; + + LLUUID image_asset_id = texture_ctrl->getImageAssetID(); + LLViewerTexture* img = LLViewerTextureManager::getFetchedTexture(image_asset_id); + S32 components = img->getComponents(); + // Must ask for highest resolution version's width. JC + S32 width = img->getFullWidth(); + S32 height = img->getFullHeight(); + + //LL_INFOS() << "texture detail " << i << " is " << width << "x" << height << "x" << components << LL_ENDL; + + if (components != 3) + { + LLSD args; + args["TEXTURE_NUM"] = i+1; + args["TEXTURE_BIT_DEPTH"] = llformat("%d",components * 8); + args["MAX_SIZE"] = MAX_TERRAIN_TEXTURE_SIZE; + LLNotificationsUtil::add("InvalidTerrainBitDepth", args); + return false; + } + + if (width > MAX_TERRAIN_TEXTURE_SIZE || height > MAX_TERRAIN_TEXTURE_SIZE) + { + + LLSD args; + args["TEXTURE_NUM"] = i+1; + args["TEXTURE_SIZE_X"] = width; + args["TEXTURE_SIZE_Y"] = height; + args["MAX_SIZE"] = MAX_TERRAIN_TEXTURE_SIZE; + LLNotificationsUtil::add("InvalidTerrainSize", args); + return false; + + } + } + + return true; +} + +bool LLPanelRegionTerrainInfo::validateTextureHeights() +{ + for (S32 i = 0; i < CORNER_COUNT; ++i) + { + std::string low = llformat("height_start_spin_%d", i); + std::string high = llformat("height_range_spin_%d", i); + + if (getChild(low)->getValue().asReal() > getChild(high)->getValue().asReal()) + { + return false; + } + } + + return true; +} + +///////////////////////////////////////////////////////////////////////////// +// LLPanelRegionTerrainInfo +///////////////////////////////////////////////////////////////////////////// +// Initialize statics + +bool LLPanelRegionTerrainInfo::postBuild() +{ + LLPanelRegionInfo::postBuild(); + + initCtrl("water_height_spin"); + initCtrl("terrain_raise_spin"); + initCtrl("terrain_lower_spin"); + + std::string buffer; + for(S32 i = 0; i < TERRAIN_TEXTURE_COUNT; ++i) + { + buffer = llformat("texture_detail_%d", i); + initCtrl(buffer); + } + + for(S32 i = 0; i < CORNER_COUNT; ++i) + { + buffer = llformat("height_start_spin_%d", i); + initCtrl(buffer); + buffer = llformat("height_range_spin_%d", i); + initCtrl(buffer); + } + + childSetAction("download_raw_btn", onClickDownloadRaw, this); + childSetAction("upload_raw_btn", onClickUploadRaw, this); + childSetAction("bake_terrain_btn", onClickBakeTerrain, this); + + mAskedTextureHeights = false; + mConfirmedTextureHeights = false; + + return LLPanelRegionInfo::postBuild(); +} + +// virtual +bool LLPanelRegionTerrainInfo::refreshFromRegion(LLViewerRegion* region) +{ + bool owner_or_god = gAgent.isGodlike() + || (region && (region->getOwner() == gAgent.getID())); + bool owner_or_god_or_manager = owner_or_god + || (region && region->isEstateManager()); + setCtrlsEnabled(owner_or_god_or_manager); + + getChildView("apply_btn")->setEnabled(false); + + if (region) + { + getChild("region_text")->setValue(LLSD(region->getName())); + + LLVLComposition* compp = region->getComposition(); + LLTextureCtrl* texture_ctrl; + std::string buffer; + for(S32 i = 0; i < TERRAIN_TEXTURE_COUNT; ++i) + { + buffer = llformat("texture_detail_%d", i); + texture_ctrl = getChild(buffer); + if(texture_ctrl) + { + LL_DEBUGS() << "Detail Texture " << i << ": " + << compp->getDetailTextureID(i) << LL_ENDL; + LLUUID tmp_id(compp->getDetailTextureID(i)); + texture_ctrl->setImageAssetID(tmp_id); + } + } + + for(S32 i = 0; i < CORNER_COUNT; ++i) + { + buffer = llformat("height_start_spin_%d", i); + getChild(buffer)->setValue(LLSD(compp->getStartHeight(i))); + buffer = llformat("height_range_spin_%d", i); + getChild(buffer)->setValue(LLSD(compp->getHeightRange(i))); + } + } + else + { + LL_DEBUGS() << "no region set" << LL_ENDL; + getChild("region_text")->setValue(LLSD("")); + } + + getChildView("download_raw_btn")->setEnabled(owner_or_god); + getChildView("upload_raw_btn")->setEnabled(owner_or_god); + getChildView("bake_terrain_btn")->setEnabled(owner_or_god); + + return LLPanelRegionInfo::refreshFromRegion(region); +} + + +// virtual +bool LLPanelRegionTerrainInfo::sendUpdate() +{ + LL_INFOS() << "LLPanelRegionTerrainInfo::sendUpdate" << LL_ENDL; + std::string buffer; + strings_t strings; + LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); + + // update the model + LLRegionInfoModel& region_info = LLRegionInfoModel::instance(); + region_info.mWaterHeight = (F32) getChild("water_height_spin")->getValue().asReal(); + region_info.mTerrainRaiseLimit = (F32) getChild("terrain_raise_spin")->getValue().asReal(); + region_info.mTerrainLowerLimit = (F32) getChild("terrain_lower_spin")->getValue().asReal(); + + // and sync the region with it + region_info.sendRegionTerrain(invoice); + + // ======================================= + // Assemble and send texturedetail message + + // Make sure user hasn't chosen wacky textures. + if (!validateTextureSizes()) + { + return false; + } + + // Check if terrain Elevation Ranges are correct + if (gSavedSettings.getBOOL("RegionCheckTextureHeights") && !validateTextureHeights()) + { + if (!mAskedTextureHeights) + { + LLNotificationsUtil::add("ConfirmTextureHeights", LLSD(), LLSD(), boost::bind(&LLPanelRegionTerrainInfo::callbackTextureHeights, this, _1, _2)); + mAskedTextureHeights = true; + return false; + } + else if (!mConfirmedTextureHeights) + { + return false; + } + } + + LLTextureCtrl* texture_ctrl; + std::string id_str; + LLMessageSystem* msg = gMessageSystem; + + for(S32 i = 0; i < TERRAIN_TEXTURE_COUNT; ++i) + { + buffer = llformat("texture_detail_%d", i); + texture_ctrl = getChild(buffer); + if(texture_ctrl) + { + LLUUID tmp_id(texture_ctrl->getImageAssetID()); + tmp_id.toString(id_str); + buffer = llformat("%d %s", i, id_str.c_str()); + strings.push_back(buffer); + } + } + sendEstateOwnerMessage(msg, "texturedetail", invoice, strings); + strings.clear(); + + // ======================================== + // Assemble and send textureheights message + + for(S32 i = 0; i < CORNER_COUNT; ++i) + { + buffer = llformat("height_start_spin_%d", i); + std::string buffer2 = llformat("height_range_spin_%d", i); + std::string buffer3 = llformat("%d %f %f", i, (F32)getChild(buffer)->getValue().asReal(), (F32)getChild(buffer2)->getValue().asReal()); + strings.push_back(buffer3); + } + sendEstateOwnerMessage(msg, "textureheights", invoice, strings); + strings.clear(); + + // ======================================== + // Send texturecommit message + + sendEstateOwnerMessage(msg, "texturecommit", invoice, strings); + + return true; +} + +bool LLPanelRegionTerrainInfo::callbackTextureHeights(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // ok + { + mConfirmedTextureHeights = true; + } + else if (option == 1) // cancel + { + mConfirmedTextureHeights = false; + } + else if (option == 2) // don't ask + { + gSavedSettings.setBOOL("RegionCheckTextureHeights", false); + mConfirmedTextureHeights = true; + } + + onBtnSet(); + + mAskedTextureHeights = false; + return false; +} + +// static +void LLPanelRegionTerrainInfo::onClickDownloadRaw(void* data) +{ + LLFilePicker& picker = LLFilePicker::instance(); + if (!picker.getSaveFile(LLFilePicker::FFSAVE_RAW, "terrain.raw")) + { + LL_WARNS() << "No file" << LL_ENDL; + return; + } + std::string filepath = picker.getFirstFile(); + gXferManager->expectFileForRequest(filepath); + + LLPanelRegionTerrainInfo* self = (LLPanelRegionTerrainInfo*)data; + strings_t strings; + strings.push_back("download filename"); + strings.push_back(filepath); + LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); + self->sendEstateOwnerMessage(gMessageSystem, "terrain", invoice, strings); +} + +// static +void LLPanelRegionTerrainInfo::onClickUploadRaw(void* data) +{ + LLFilePicker& picker = LLFilePicker::instance(); + if (!picker.getOpenFile(LLFilePicker::FFLOAD_RAW)) + { + LL_WARNS() << "No file" << LL_ENDL; + return; + } + std::string filepath = picker.getFirstFile(); + gXferManager->expectFileForTransfer(filepath); + + LLPanelRegionTerrainInfo* self = (LLPanelRegionTerrainInfo*)data; + strings_t strings; + strings.push_back("upload filename"); + strings.push_back(filepath); + LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); + self->sendEstateOwnerMessage(gMessageSystem, "terrain", invoice, strings); + + LLNotificationsUtil::add("RawUploadStarted"); +} + +// static +void LLPanelRegionTerrainInfo::onClickBakeTerrain(void* data) +{ + LLNotificationsUtil::add("ConfirmBakeTerrain", LLSD(), LLSD(), boost::bind(&LLPanelRegionTerrainInfo::callbackBakeTerrain, (LLPanelRegionTerrainInfo*)data, _1, _2)); +} + +bool LLPanelRegionTerrainInfo::callbackBakeTerrain(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) return false; + + strings_t strings; + strings.push_back("bake"); + LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); + sendEstateOwnerMessage(gMessageSystem, "terrain", invoice, strings); + + return false; +} + +///////////////////////////////////////////////////////////////////////////// +// LLPanelEstateInfo +// + +LLPanelEstateInfo::LLPanelEstateInfo() +: LLPanelRegionInfo(), + mEstateID(0) // invalid +{ + LLEstateInfoModel& estate_info = LLEstateInfoModel::instance(); + estate_info.setCommitCallback(boost::bind(&LLPanelEstateInfo::refreshFromEstate, this)); + estate_info.setUpdateCallback(boost::bind(&LLPanelEstateInfo::refreshFromEstate, this)); +} + +// static +void LLPanelEstateInfo::initDispatch(LLDispatcher& dispatch) +{ + std::string name; + + name.assign("estateupdateinfo"); + static LLDispatchEstateUpdateInfo estate_update_info; + dispatch.addHandler(name, &estate_update_info); + + name.assign("setaccess"); + static LLDispatchSetEstateAccess set_access; + dispatch.addHandler(name, &set_access); + + name.assign("setexperience"); + static LLDispatchSetEstateExperience set_experience; + dispatch.addHandler(name, &set_experience); + + estate_dispatch_initialized = true; +} + +//--------------------------------------------------------------------------- +// Kick from estate methods +//--------------------------------------------------------------------------- + +void LLPanelEstateInfo::onClickKickUser() +{ + // this depends on the grandparent view being a floater + // in order to set up floater dependency + LLView * button = findChild("kick_user_from_estate_btn"); + LLFloater* parent_floater = gFloaterView->getParentFloater(this); + LLFloater* child_floater = LLFloaterAvatarPicker::show(boost::bind(&LLPanelEstateInfo::onKickUserCommit, this, _1), + false, true, false, parent_floater->getName(), button); + if (child_floater) + { + parent_floater->addDependentFloater(child_floater); + } +} + +void LLPanelEstateInfo::onKickUserCommit(const uuid_vec_t& ids) +{ + if (ids.empty()) return; + + //Bring up a confirmation dialog + LLSD args; + args["EVIL_USER"] = LLSLURL("agent", ids[0], "completename").getSLURLString(); + LLSD payload; + payload["agent_id"] = ids[0]; + LLNotificationsUtil::add("EstateKickUser", args, payload, boost::bind(&LLPanelEstateInfo::kickUserConfirm, this, _1, _2)); + +} + +bool LLPanelEstateInfo::kickUserConfirm(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + switch(option) + { + case 0: + { + //Kick User + strings_t strings; + strings.push_back(notification["payload"]["agent_id"].asString()); + + sendEstateOwnerMessage(gMessageSystem, "kickestate", LLFloaterRegionInfo::getLastInvoice(), strings); + break; + } + default: + break; + } + return false; +} + +//--------------------------------------------------------------------------- +// Core Add/Remove estate access methods +// TODO: INTERNATIONAL: don't build message text here; +// instead, create multiple translatable messages and choose +// one based on the status. +//--------------------------------------------------------------------------- +std::string all_estates_text() +{ + LLPanelEstateInfo* panel = LLFloaterRegionInfo::getPanelEstate(); + if (!panel) return "(" + LLTrans::getString("RegionInfoError") + ")"; + + LLStringUtil::format_map_t args; + std::string owner = panel->getOwnerName(); + + LLViewerRegion* region = gAgent.getRegion(); + if (gAgent.isGodlike()) + { + args["[OWNER]"] = owner.c_str(); + return LLTrans::getString("RegionInfoAllEstatesOwnedBy", args); + } + else if (region && region->getOwner() == gAgent.getID()) + { + return LLTrans::getString("RegionInfoAllEstatesYouOwn"); + } + else if (region && region->isEstateManager()) + { + args["[OWNER]"] = owner.c_str(); + return LLTrans::getString("RegionInfoAllEstatesYouManage", args); + } + else + { + return "(" + LLTrans::getString("RegionInfoError") + ")"; + } +} + +// static +bool LLPanelEstateInfo::isLindenEstate() +{ + U32 estate_id = LLEstateInfoModel::instance().getID(); + return (estate_id <= ESTATE_LAST_LINDEN); +} + +struct LLEstateAccessChangeInfo +{ + LLEstateAccessChangeInfo(const LLSD& sd) + { + mDialogName = sd["dialog_name"].asString(); + mOperationFlag = (U32)sd["operation"].asInteger(); + LLSD::array_const_iterator end_it = sd["allowed_ids"].endArray(); + for (LLSD::array_const_iterator id_it = sd["allowed_ids"].beginArray(); + id_it != end_it; + ++id_it) + { + mAgentOrGroupIDs.push_back(id_it->asUUID()); + } + } + + const LLSD asLLSD() const + { + LLSD sd; + sd["name"] = mDialogName; + sd["operation"] = (S32)mOperationFlag; + for (U32 i = 0; i < mAgentOrGroupIDs.size(); ++i) + { + sd["allowed_ids"].append(mAgentOrGroupIDs[i]); + if (mAgentNames.size() > i) + { + sd["allowed_names"].append(mAgentNames[i].asLLSD()); + } + } + return sd; + } + + U32 mOperationFlag; // ESTATE_ACCESS_BANNED_AGENT_ADD, _REMOVE, etc. + std::string mDialogName; + uuid_vec_t mAgentOrGroupIDs; // List of agent IDs to apply to this change + std::vector mAgentNames; // Optional list of the agent names for notifications +}; + +// static +void LLPanelEstateInfo::updateEstateOwnerName(const std::string& name) +{ + LLPanelEstateInfo* panelp = LLFloaterRegionInfo::getPanelEstate(); + if (panelp) + { + panelp->setOwnerName(name); + } +} + +// static +void LLPanelEstateInfo::updateEstateName(const std::string& name) +{ + LLPanelEstateInfo* panelp = LLFloaterRegionInfo::getPanelEstate(); + if (panelp) + { + panelp->getChildRef("estate_name").setText(name); + } +} + +void LLPanelEstateInfo::updateControls(LLViewerRegion* region) +{ + bool god = gAgent.isGodlike(); + bool owner = (region && (region->getOwner() == gAgent.getID())); + bool manager = (region && region->isEstateManager()); + setCtrlsEnabled(god || owner || manager); + + getChildView("apply_btn")->setEnabled(false); + getChildView("estate_owner")->setEnabled(true); + getChildView("message_estate_btn")->setEnabled(god || owner || manager); + getChildView("kick_user_from_estate_btn")->setEnabled(god || owner || manager); + + refresh(); +} + +bool LLPanelEstateInfo::refreshFromRegion(LLViewerRegion* region) +{ + updateControls(region); + + // let the parent class handle the general data collection. + bool rv = LLPanelRegionInfo::refreshFromRegion(region); + + // We want estate info. To make sure it works across region + // boundaries and multiple packets, we add a serial number to the + // integers and track against that on update. + strings_t strings; + //integers_t integers; + //LLFloaterRegionInfo::incrementSerial(); + LLFloaterRegionInfo::nextInvoice(); + LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); + //integers.push_back(LLFloaterRegionInfo::());::getPanelEstate(); + + + sendEstateOwnerMessage(gMessageSystem, "getinfo", invoice, strings); + + refresh(); + + return rv; +} + +void LLPanelEstateInfo::updateChild(LLUICtrl* child_ctrl) +{ + // Ensure appropriate state of the management ui. + updateControls(gAgent.getRegion()); +} + +bool LLPanelEstateInfo::estateUpdate(LLMessageSystem* msg) +{ + LL_INFOS() << "LLPanelEstateInfo::estateUpdate()" << LL_ENDL; + return false; +} + + +bool LLPanelEstateInfo::postBuild() +{ + // set up the callbacks for the generic controls + initCtrl("externally_visible_radio"); + initCtrl("allow_direct_teleport"); + initCtrl("limit_payment"); + initCtrl("limit_age_verified"); + initCtrl("limit_bots"); + initCtrl("voice_chat_check"); + initCtrl("parcel_access_override"); + + childSetAction("message_estate_btn", boost::bind(&LLPanelEstateInfo::onClickMessageEstate, this)); + childSetAction("kick_user_from_estate_btn", boost::bind(&LLPanelEstateInfo::onClickKickUser, this)); + + getChild("parcel_access_override")->setCommitCallback(boost::bind(&LLPanelEstateInfo::onChangeAccessOverride, this)); + + getChild("externally_visible_radio")->setFocus(true); + + getChild("estate_owner")->setIsFriendCallback(LLAvatarActions::isFriend); + + return LLPanelRegionInfo::postBuild(); +} + +void LLPanelEstateInfo::refresh() +{ + // Disable access restriction controls if they make no sense. + bool public_access = ("estate_public_access" == getChild("externally_visible_radio")->getValue().asString()); + + getChildView("Only Allow")->setEnabled(public_access); + getChildView("limit_payment")->setEnabled(public_access); + getChildView("limit_age_verified")->setEnabled(public_access); + getChildView("limit_bots")->setEnabled(public_access); + + // if this is set to false, then the limit fields are meaningless and should be turned off + if (!public_access) + { + getChild("limit_payment")->setValue(false); + getChild("limit_age_verified")->setValue(false); + getChild("limit_bots")->setValue(false); + } +} + +void LLPanelEstateInfo::refreshFromEstate() +{ + const LLEstateInfoModel& estate_info = LLEstateInfoModel::instance(); + + getChild("estate_name")->setValue(estate_info.getName()); + setOwnerName(LLSLURL("agent", estate_info.getOwnerID(), "inspect").getSLURLString()); + + getChild("externally_visible_radio")->setValue(estate_info.getIsExternallyVisible() ? "estate_public_access" : "estate_restricted_access"); + getChild("voice_chat_check")->setValue(estate_info.getAllowVoiceChat()); + getChild("allow_direct_teleport")->setValue(estate_info.getAllowDirectTeleport()); + getChild("limit_payment")->setValue(estate_info.getDenyAnonymous()); + getChild("limit_age_verified")->setValue(estate_info.getDenyAgeUnverified()); + getChild("parcel_access_override")->setValue(estate_info.getAllowAccessOverride()); + getChild("limit_bots")->setValue(estate_info.getDenyScriptedAgents()); + + // Ensure appriopriate state of the management UI + updateControls(gAgent.getRegion()); + refresh(); +} + +bool LLPanelEstateInfo::sendUpdate() +{ + LL_INFOS() << "LLPanelEsateInfo::sendUpdate()" << LL_ENDL; + + LLNotification::Params params("ChangeLindenEstate"); + params.functor.function(boost::bind(&LLPanelEstateInfo::callbackChangeLindenEstate, this, _1, _2)); + + if (isLindenEstate()) + { + // trying to change reserved estate, warn + LLNotifications::instance().add(params); + } + else + { + // for normal estates, just make the change + LLNotifications::instance().forceResponse(params, 0); + } + return true; +} + +bool LLPanelEstateInfo::callbackChangeLindenEstate(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + switch(option) + { + case 0: + { + LLEstateInfoModel& estate_info = LLEstateInfoModel::instance(); + + // update model + estate_info.setUseFixedSun(false); // we don't support fixed sun estates anymore + estate_info.setIsExternallyVisible("estate_public_access" == getChild("externally_visible_radio")->getValue().asString()); + estate_info.setAllowDirectTeleport(getChild("allow_direct_teleport")->getValue().asBoolean()); + estate_info.setDenyAnonymous(getChild("limit_payment")->getValue().asBoolean()); + estate_info.setDenyAgeUnverified(getChild("limit_age_verified")->getValue().asBoolean()); + estate_info.setAllowVoiceChat(getChild("voice_chat_check")->getValue().asBoolean()); + estate_info.setAllowAccessOverride(getChild("parcel_access_override")->getValue().asBoolean()); + estate_info.setDenyScriptedAgents(getChild("limit_bots")->getValue().asBoolean()); + // JIGGLYPUFF + //estate_info.setAllowAccessOverride(getChild("")->getValue().asBoolean()); + // send the update to sim + estate_info.sendEstateInfo(); + } + + // we don't want to do this because we'll get it automatically from the sim + // after the spaceserver processes it +// else +// { +// // caps method does not automatically send this info +// LLFloaterRegionInfo::requestRegionInfo(); +// } + break; + case 1: + default: + // do nothing + break; + } + return false; +} + + +/* +// Request = "getowner" +// SParam[0] = "" (empty string) +// IParam[0] = serial +void LLPanelEstateInfo::getEstateOwner() +{ + // TODO -- disable the panel + // and call this function whenever we cross a region boundary + // re-enable when owner matches, and get new estate info + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_EstateOwnerRequest); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + + msg->nextBlockFast(_PREHASH_RequestData); + msg->addStringFast(_PREHASH_Request, "getowner"); + + // we send an empty string so that the variable block is not empty + msg->nextBlockFast(_PREHASH_StringData); + msg->addStringFast(_PREHASH_SParam, ""); + + msg->nextBlockFast(_PREHASH_IntegerData); + msg->addS32Fast(_PREHASH_IParam, LLFloaterRegionInfo::getSerial()); + + gAgent.sendMessage(); +} +*/ + +const std::string LLPanelEstateInfo::getOwnerName() const +{ + return getChild("estate_owner")->getValue().asString(); +} + +void LLPanelEstateInfo::setOwnerName(const std::string& name) +{ + getChild("estate_owner")->setValue(LLSD(name)); +} + +// static +void LLPanelEstateInfo::onClickMessageEstate(void* userdata) +{ + LL_INFOS() << "LLPanelEstateInfo::onClickMessageEstate" << LL_ENDL; + LLNotificationsUtil::add("MessageEstate", LLSD(), LLSD(), boost::bind(&LLPanelEstateInfo::onMessageCommit, (LLPanelEstateInfo*)userdata, _1, _2)); +} + +bool LLPanelEstateInfo::onMessageCommit(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + std::string text = response["message"].asString(); + if(option != 0) return false; + if(text.empty()) return false; + LL_INFOS() << "Message to everyone: " << text << LL_ENDL; + strings_t strings; + //integers_t integers; + std::string name; + LLAgentUI::buildFullname(name); + strings.push_back(strings_t::value_type(name)); + strings.push_back(strings_t::value_type(text)); + LLUUID invoice(LLFloaterRegionInfo::getLastInvoice()); + sendEstateOwnerMessage(gMessageSystem, "instantmessage", invoice, strings); + return false; +} + +void LLPanelEstateInfo::onChangeAccessOverride() +{ + if (!getChild("parcel_access_override")->getValue().asBoolean()) + { + LLNotificationsUtil::add("EstateParcelAccessOverride"); + } +} + +LLPanelEstateCovenant::LLPanelEstateCovenant() + : + mCovenantID(LLUUID::null), + mAssetStatus(ASSET_ERROR) +{ +} + +// virtual +bool LLPanelEstateCovenant::refreshFromRegion(LLViewerRegion* region) +{ + LLTextBox* region_name = getChild("region_name_text"); + if (region_name) + { + region_name->setText(region->getName()); + } + + LLTextBox* resellable_clause = getChild("resellable_clause"); + if (resellable_clause) + { + if (region->getRegionFlag(REGION_FLAGS_BLOCK_LAND_RESELL)) + { + resellable_clause->setText(getString("can_not_resell")); + } + else + { + resellable_clause->setText(getString("can_resell")); + } + } + + LLTextBox* changeable_clause = getChild("changeable_clause"); + if (changeable_clause) + { + if (region->getRegionFlag(REGION_FLAGS_ALLOW_PARCEL_CHANGES)) + { + changeable_clause->setText(getString("can_change")); + } + else + { + changeable_clause->setText(getString("can_not_change")); + } + } + + LLTextBox* region_maturity = getChild("region_maturity_text"); + if (region_maturity) + { + region_maturity->setText(region->getSimAccessString()); + } + + LLTextBox* region_landtype = getChild("region_landtype_text"); + region_landtype->setText(region->getLocalizedSimProductName()); + + getChild("reset_covenant")->setEnabled(gAgent.isGodlike() || (region && region->canManageEstate())); + + // let the parent class handle the general data collection. + bool rv = LLPanelRegionInfo::refreshFromRegion(region); + LLMessageSystem *msg = gMessageSystem; + msg->newMessage("EstateCovenantRequest"); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); + msg->sendReliable(region->getHost()); + return rv; +} + +// virtual +bool LLPanelEstateCovenant::estateUpdate(LLMessageSystem* msg) +{ + LL_INFOS() << "LLPanelEstateCovenant::estateUpdate()" << LL_ENDL; + return true; +} + +// virtual +bool LLPanelEstateCovenant::postBuild() +{ + mEstateNameText = getChild("estate_name_text"); + mEstateOwnerText = getChild("estate_owner_text"); + mEstateOwnerText->setIsFriendCallback(LLAvatarActions::isFriend); + mLastModifiedText = getChild("covenant_timestamp_text"); + mEditor = getChild("covenant_editor"); + LLButton* reset_button = getChild("reset_covenant"); + reset_button->setEnabled(gAgent.canManageEstate()); + reset_button->setClickedCallback(LLPanelEstateCovenant::resetCovenantID, NULL); + + return LLPanelRegionInfo::postBuild(); +} + +// virtual +void LLPanelEstateCovenant::updateChild(LLUICtrl* child_ctrl) +{ +} + +// virtual +bool LLPanelEstateCovenant::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + LLInventoryItem* item = (LLInventoryItem*)cargo_data; + + if (!gAgent.canManageEstate()) + { + *accept = ACCEPT_NO; + return true; + } + + switch(cargo_type) + { + case DAD_NOTECARD: + *accept = ACCEPT_YES_COPY_SINGLE; + if (item && drop) + { + LLSD payload; + payload["item_id"] = item->getUUID(); + LLNotificationsUtil::add("EstateChangeCovenant", LLSD(), payload, + LLPanelEstateCovenant::confirmChangeCovenantCallback); + } + break; + default: + *accept = ACCEPT_NO; + break; + } + + return true; +} + +// static +bool LLPanelEstateCovenant::confirmChangeCovenantCallback(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + LLInventoryItem* item = gInventory.getItem(notification["payload"]["item_id"].asUUID()); + LLPanelEstateCovenant* self = LLFloaterRegionInfo::getPanelCovenant(); + + if (!item || !self) return false; + + switch(option) + { + case 0: + self->loadInvItem(item); + break; + default: + break; + } + return false; +} + +// static +void LLPanelEstateCovenant::resetCovenantID(void* userdata) +{ + LLNotificationsUtil::add("EstateChangeCovenant", LLSD(), LLSD(), confirmResetCovenantCallback); +} + +// static +bool LLPanelEstateCovenant::confirmResetCovenantCallback(const LLSD& notification, const LLSD& response) +{ + LLPanelEstateCovenant* self = LLFloaterRegionInfo::getPanelCovenant(); + if (!self) return false; + + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + switch(option) + { + case 0: + self->loadInvItem(NULL); + break; + default: + break; + } + return false; +} + +void LLPanelEstateCovenant::loadInvItem(LLInventoryItem *itemp) +{ + const bool high_priority = true; + if (itemp) + { + gAssetStorage->getInvItemAsset(gAgent.getRegionHost(), + gAgent.getID(), + gAgent.getSessionID(), + itemp->getPermissions().getOwner(), + LLUUID::null, + itemp->getUUID(), + itemp->getAssetUUID(), + itemp->getType(), + onLoadComplete, + (void*)this, + high_priority); + mAssetStatus = ASSET_LOADING; + } + else + { + mAssetStatus = ASSET_LOADED; + setCovenantTextEditor(LLTrans::getString("RegionNoCovenant")); + sendChangeCovenantID(LLUUID::null); + } +} + +// static +void LLPanelEstateCovenant::onLoadComplete(const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status) +{ + LL_INFOS() << "LLPanelEstateCovenant::onLoadComplete()" << LL_ENDL; + LLPanelEstateCovenant* panelp = (LLPanelEstateCovenant*)user_data; + if( panelp ) + { + if(0 == status) + { + LLFileSystem file(asset_uuid, type, LLFileSystem::READ); + + S32 file_length = file.getSize(); + + std::vector buffer(file_length+1); + file.read((U8*)&buffer[0], file_length); + // put a EOS at the end + buffer[file_length] = 0; + + if( (file_length > 19) && !strncmp( &buffer[0], "Linden text version", 19 ) ) + { + if( !panelp->mEditor->importBuffer( &buffer[0], file_length+1 ) ) + { + LL_WARNS() << "Problem importing estate covenant." << LL_ENDL; + LLNotificationsUtil::add("ProblemImportingEstateCovenant"); + } + else + { + panelp->sendChangeCovenantID(asset_uuid); + } + } + else + { + // Version 0 (just text, doesn't include version number) + panelp->sendChangeCovenantID(asset_uuid); + } + } + else + { + if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status || + LL_ERR_FILE_EMPTY == status) + { + LLNotificationsUtil::add("MissingNotecardAssetID"); + } + else if (LL_ERR_INSUFFICIENT_PERMISSIONS == status) + { + LLNotificationsUtil::add("NotAllowedToViewNotecard"); + } + else + { + LLNotificationsUtil::add("UnableToLoadNotecardAsset"); + } + + LL_WARNS() << "Problem loading notecard: " << status << LL_ENDL; + } + panelp->mAssetStatus = ASSET_LOADED; + panelp->setCovenantID(asset_uuid); + } +} + +// key = "estatechangecovenantid" +// strings[0] = str(estate_id) (added by simulator before relay - not here) +// strings[1] = str(covenant_id) +void LLPanelEstateCovenant::sendChangeCovenantID(const LLUUID &asset_id) +{ + if (asset_id != getCovenantID()) + { + setCovenantID(asset_id); + + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("EstateOwnerMessage"); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addUUIDFast(_PREHASH_TransactionID, LLUUID::null); //not used + + msg->nextBlock("MethodData"); + msg->addString("Method", "estatechangecovenantid"); + msg->addUUID("Invoice", LLFloaterRegionInfo::getLastInvoice()); + + msg->nextBlock("ParamList"); + msg->addString("Parameter", getCovenantID().asString()); + gAgent.sendReliableMessage(); + } +} + +// virtual +bool LLPanelEstateCovenant::sendUpdate() +{ + return true; +} + +std::string LLPanelEstateCovenant::getEstateName() const +{ + return mEstateNameText->getText(); +} + +void LLPanelEstateCovenant::setEstateName(const std::string& name) +{ + mEstateNameText->setText(name); +} + +// static +void LLPanelEstateCovenant::updateCovenantText(const std::string& string, const LLUUID& asset_id) +{ + LLPanelEstateCovenant* panelp = LLFloaterRegionInfo::getPanelCovenant(); + if( panelp ) + { + panelp->mEditor->setText(string); + panelp->setCovenantID(asset_id); + } +} + +// static +void LLPanelEstateCovenant::updateEstateName(const std::string& name) +{ + LLPanelEstateCovenant* panelp = LLFloaterRegionInfo::getPanelCovenant(); + if( panelp ) + { + panelp->mEstateNameText->setText(name); + } +} + +// static +void LLPanelEstateCovenant::updateLastModified(const std::string& text) +{ + LLPanelEstateCovenant* panelp = LLFloaterRegionInfo::getPanelCovenant(); + if( panelp ) + { + panelp->mLastModifiedText->setText(text); + } +} + +// static +void LLPanelEstateCovenant::updateEstateOwnerName(const std::string& name) +{ + LLPanelEstateCovenant* panelp = LLFloaterRegionInfo::getPanelCovenant(); + if( panelp ) + { + panelp->mEstateOwnerText->setText(name); + } +} + +std::string LLPanelEstateCovenant::getOwnerName() const +{ + return mEstateOwnerText->getText(); +} + +void LLPanelEstateCovenant::setOwnerName(const std::string& name) +{ + mEstateOwnerText->setText(name); +} + +void LLPanelEstateCovenant::setCovenantTextEditor(const std::string& text) +{ + mEditor->setText(text); +} + +// key = "estateupdateinfo" +// strings[0] = estate name +// strings[1] = str(owner_id) +// strings[2] = str(estate_id) +// strings[3] = str(estate_flags) +// strings[4] = str((S32)(sun_hour * 1024)) +// strings[5] = str(parent_estate_id) +// strings[6] = str(covenant_id) +// strings[7] = str(covenant_timestamp) +// strings[8] = str(send_to_agent_only) +// strings[9] = str(abuse_email_addr) +bool LLDispatchEstateUpdateInfo::operator()( + const LLDispatcher* dispatcher, + const std::string& key, + const LLUUID& invoice, + const sparam_t& strings) +{ + LL_DEBUGS() << "Received estate update" << LL_ENDL; + + // Update estate info model. + // This will call LLPanelEstateInfo::refreshFromEstate(). + // *TODO: Move estate message handling stuff to llestateinfomodel.cpp. + LLEstateInfoModel::instance().update(strings); + + return true; +} + +bool LLDispatchSetEstateAccess::operator()( + const LLDispatcher* dispatcher, + const std::string& key, + const LLUUID& invoice, + const sparam_t& strings) +{ + LLPanelEstateAccess* panel = LLFloaterRegionInfo::getPanelAccess(); + if (panel && panel->getPendingUpdate()) + { + panel->setPendingUpdate(false); + panel->updateLists(); + } + return true; +} + +// static +LLSD LLDispatchSetEstateExperience::getIDs( sparam_t::const_iterator it, sparam_t::const_iterator end, S32 count ) +{ + LLSD idList = LLSD::emptyArray(); + LLUUID id; + while (count-- > 0 && it < end) + { + memcpy(id.mData, (*(it++)).data(), UUID_BYTES); + idList.append(id); + } + return idList; +} + +// key = "setexperience" +// strings[0] = str(estate_id) +// strings[1] = str(send_to_agent_only) +// strings[2] = str(num blocked) +// strings[3] = str(num trusted) +// strings[4] = str(num allowed) +// strings[5] = bin(uuid) ... +// ... +bool LLDispatchSetEstateExperience::operator()( + const LLDispatcher* dispatcher, + const std::string& key, + const LLUUID& invoice, + const sparam_t& strings) +{ + LLPanelRegionExperiences* panel = LLFloaterRegionInfo::getPanelExperiences(); + if (!panel) + return true; + + const sparam_t::size_type MIN_SIZE = 5; + if (strings.size() < MIN_SIZE) + return true; + + // Skip 2 parameters + sparam_t::const_iterator it = strings.begin(); + ++it; // U32 estate_id = strtol((*it).c_str(), NULL, 10); + ++it; // U32 send_to_agent_only = strtoul((*(++it)).c_str(), NULL, 10); + + // Read 3 parameters + LLUUID id; + S32 num_blocked = strtol((*(it++)).c_str(), NULL, 10); + S32 num_trusted = strtol((*(it++)).c_str(), NULL, 10); + S32 num_allowed = strtol((*(it++)).c_str(), NULL, 10); + + LLSD ids = LLSD::emptyMap() + .with("blocked", getIDs(it, strings.end(), num_blocked)) + .with("trusted", getIDs(it + num_blocked, strings.end(), num_trusted)) + .with("allowed", getIDs(it + num_blocked + num_trusted, strings.end(), num_allowed)); + + panel->processResponse(ids); + + return true; +} + +bool LLPanelRegionExperiences::postBuild() +{ + mAllowed = setupList("panel_allowed", ESTATE_EXPERIENCE_ALLOWED_ADD, ESTATE_EXPERIENCE_ALLOWED_REMOVE); + mTrusted = setupList("panel_trusted", ESTATE_EXPERIENCE_TRUSTED_ADD, ESTATE_EXPERIENCE_TRUSTED_REMOVE); + mBlocked = setupList("panel_blocked", ESTATE_EXPERIENCE_BLOCKED_ADD, ESTATE_EXPERIENCE_BLOCKED_REMOVE); + + getChild("trusted_layout_panel")->setVisible(true); + getChild("experiences_help_text")->setText(getString("estate_caption")); + getChild("trusted_text_help")->setText(getString("trusted_estate_text")); + getChild("allowed_text_help")->setText(getString("allowed_estate_text")); + getChild("blocked_text_help")->setText(getString("blocked_estate_text")); + + return LLPanelRegionInfo::postBuild(); +} + +LLPanelExperienceListEditor* LLPanelRegionExperiences::setupList( const char* control_name, U32 add_id, U32 remove_id ) +{ + LLPanelExperienceListEditor* child = findChild(control_name); + if(child) + { + child->getChild("text_name")->setText(child->getString(control_name)); + child->setMaxExperienceIDs(ESTATE_MAX_EXPERIENCE_IDS); + child->setAddedCallback( boost::bind(&LLPanelRegionExperiences::itemChanged, this, add_id, _1)); + child->setRemovedCallback(boost::bind(&LLPanelRegionExperiences::itemChanged, this, remove_id, _1)); + } + + return child; +} + + +void LLPanelRegionExperiences::processResponse( const LLSD& content ) +{ + if(content.has("default")) + { + mDefaultExperience = content["default"].asUUID(); + } + + mAllowed->setExperienceIds(content["allowed"]); + mBlocked->setExperienceIds(content["blocked"]); + + LLSD trusted = content["trusted"]; + if(mDefaultExperience.notNull()) + { + mTrusted->setStickyFunction(boost::bind(LLPanelExperiencePicker::FilterMatching, _1, mDefaultExperience)); + trusted.append(mDefaultExperience); + } + + mTrusted->setExperienceIds(trusted); + + mAllowed->refreshExperienceCounter(); + mBlocked->refreshExperienceCounter(); + mTrusted->refreshExperienceCounter(); + +} + +// Used for both access add and remove operations, depending on the flag +// passed in (ESTATE_EXPERIENCE_ALLOWED_ADD, ESTATE_EXPERIENCE_ALLOWED_REMOVE, etc.) +// static +bool LLPanelRegionExperiences::experienceCoreConfirm(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + const U32 originalFlags = (U32)notification["payload"]["operation"].asInteger(); + + LLViewerRegion* region = gAgent.getRegion(); + + LLSD::array_const_iterator end_it = notification["payload"]["allowed_ids"].endArray(); + + for (LLSD::array_const_iterator iter = notification["payload"]["allowed_ids"].beginArray(); + iter != end_it; + iter++) + { + U32 flags = originalFlags; + if (iter + 1 != end_it) + flags |= ESTATE_ACCESS_NO_REPLY; + + const LLUUID id = iter->asUUID(); + switch(option) + { + case 0: + // This estate + sendEstateExperienceDelta(flags, id); + break; + case 1: + { + // All estates, either than I own or manage for this owner. + // This will be verified on simulator. JC + if (!region) break; + if (region->getOwner() == gAgent.getID() + || gAgent.isGodlike()) + { + flags |= ESTATE_ACCESS_APPLY_TO_ALL_ESTATES; + sendEstateExperienceDelta(flags, id); + } + else if (region->isEstateManager()) + { + flags |= ESTATE_ACCESS_APPLY_TO_MANAGED_ESTATES; + sendEstateExperienceDelta(flags, id); + } + break; + } + case 2: + default: + break; + } + } + return false; +} + + +// Send the actual "estateexperiencedelta" message +void LLPanelRegionExperiences::sendEstateExperienceDelta(U32 flags, const LLUUID& experience_id) +{ + strings_t str(3, std::string()); + gAgent.getID().toString(str[0]); + str[1] = llformat("%u", flags); + experience_id.toString(str[2]); + + LLPanelRegionExperiences* panel = LLFloaterRegionInfo::getPanelExperiences(); + if (panel) + { + panel->sendEstateOwnerMessage(gMessageSystem, "estateexperiencedelta", LLFloaterRegionInfo::getLastInvoice(), str); + } +} + + +void LLPanelRegionExperiences::infoCallback(LLHandle handle, const LLSD& content) +{ + if(handle.isDead()) + return; + + LLPanelRegionExperiences* floater = handle.get(); + if (floater) + { + floater->processResponse(content); + } +} + +/*static*/ +std::string LLPanelRegionExperiences::regionCapabilityQuery(LLViewerRegion* region, const std::string &cap) +{ + // region->getHandle() How to get a region * from a handle? + + return region->getCapability(cap); +} + +bool LLPanelRegionExperiences::refreshFromRegion(LLViewerRegion* region) +{ + bool allow_modify = gAgent.isGodlike() || (region && region->canManageEstate()); + + mAllowed->loading(); + mAllowed->setReadonly(!allow_modify); + // remove grid-wide experiences + mAllowed->addFilter(boost::bind(LLPanelExperiencePicker::FilterWithProperty, _1, LLExperienceCache::PROPERTY_GRID)); + // remove default experience + mAllowed->addFilter(boost::bind(LLPanelExperiencePicker::FilterMatching, _1, mDefaultExperience)); + + mBlocked->loading(); + mBlocked->setReadonly(!allow_modify); + // only grid-wide experiences + mBlocked->addFilter(boost::bind(LLPanelExperiencePicker::FilterWithoutProperty, _1, LLExperienceCache::PROPERTY_GRID)); + // but not privileged ones + mBlocked->addFilter(boost::bind(LLPanelExperiencePicker::FilterWithProperty, _1, LLExperienceCache::PROPERTY_PRIVILEGED)); + // remove default experience + mBlocked->addFilter(boost::bind(LLPanelExperiencePicker::FilterMatching, _1, mDefaultExperience)); + + mTrusted->loading(); + mTrusted->setReadonly(!allow_modify); + + LLExperienceCache::instance().getRegionExperiences(boost::bind(&LLPanelRegionExperiences::regionCapabilityQuery, region, _1), + boost::bind(&LLPanelRegionExperiences::infoCallback, getDerivedHandle(), _1)); + + return LLPanelRegionInfo::refreshFromRegion(region); +} + +LLSD LLPanelRegionExperiences::addIds(LLPanelExperienceListEditor* panel) +{ + LLSD ids; + const uuid_list_t& id_list = panel->getExperienceIds(); + for(uuid_list_t::const_iterator it = id_list.begin(); it != id_list.end(); ++it) + { + ids.append(*it); + } + return ids; +} + + +bool LLPanelRegionExperiences::sendUpdate() +{ + LLViewerRegion* region = gAgent.getRegion(); + + LLSD content; + + content["allowed"]=addIds(mAllowed); + content["blocked"]=addIds(mBlocked); + content["trusted"]=addIds(mTrusted); + + LLExperienceCache::instance().setRegionExperiences(boost::bind(&LLPanelRegionExperiences::regionCapabilityQuery, region, _1), + content, boost::bind(&LLPanelRegionExperiences::infoCallback, getDerivedHandle(), _1)); + + return true; +} + +void LLPanelRegionExperiences::itemChanged( U32 event_type, const LLUUID& id ) +{ + std::string dialog_name; + switch (event_type) + { + case ESTATE_EXPERIENCE_ALLOWED_ADD: + dialog_name = "EstateAllowedExperienceAdd"; + break; + + case ESTATE_EXPERIENCE_ALLOWED_REMOVE: + dialog_name = "EstateAllowedExperienceRemove"; + break; + + case ESTATE_EXPERIENCE_TRUSTED_ADD: + dialog_name = "EstateTrustedExperienceAdd"; + break; + + case ESTATE_EXPERIENCE_TRUSTED_REMOVE: + dialog_name = "EstateTrustedExperienceRemove"; + break; + + case ESTATE_EXPERIENCE_BLOCKED_ADD: + dialog_name = "EstateBlockedExperienceAdd"; + break; + + case ESTATE_EXPERIENCE_BLOCKED_REMOVE: + dialog_name = "EstateBlockedExperienceRemove"; + break; + + default: + return; + } + + LLSD payload; + payload["operation"] = (S32)event_type; + payload["dialog_name"] = dialog_name; + payload["allowed_ids"].append(id); + + LLSD args; + args["ALL_ESTATES"] = all_estates_text(); + + LLNotification::Params params(dialog_name); + params.payload(payload) + .substitutions(args) + .functor.function(LLPanelRegionExperiences::experienceCoreConfirm); + if (LLPanelEstateInfo::isLindenEstate()) + { + LLNotifications::instance().forceResponse(params, 0); + } + else + { + LLNotifications::instance().add(params); + } + + onChangeAnything(); +} + + +LLPanelEstateAccess::LLPanelEstateAccess() +: LLPanelRegionInfo(), mPendingUpdate(false) +{} + +bool LLPanelEstateAccess::postBuild() +{ + getChild("allowed_avatar_name_list")->setCommitCallback(boost::bind(&LLPanelEstateInfo::onChangeChildCtrl, this, _1)); + LLNameListCtrl *avatar_name_list = getChild("allowed_avatar_name_list"); + if (avatar_name_list) + { + avatar_name_list->setCommitOnSelectionChange(true); + avatar_name_list->setMaxItemCount(ESTATE_MAX_ACCESS_IDS); + } + + getChild("allowed_search_input")->setCommitCallback(boost::bind(&LLPanelEstateAccess::onAllowedSearchEdit, this, _2)); + childSetAction("add_allowed_avatar_btn", boost::bind(&LLPanelEstateAccess::onClickAddAllowedAgent, this)); + childSetAction("remove_allowed_avatar_btn", boost::bind(&LLPanelEstateAccess::onClickRemoveAllowedAgent, this)); + childSetAction("copy_allowed_list_btn", boost::bind(&LLPanelEstateAccess::onClickCopyAllowedList, this)); + + getChild("allowed_group_name_list")->setCommitCallback(boost::bind(&LLPanelEstateInfo::onChangeChildCtrl, this, _1)); + LLNameListCtrl* group_name_list = getChild("allowed_group_name_list"); + if (group_name_list) + { + group_name_list->setCommitOnSelectionChange(true); + group_name_list->setMaxItemCount(ESTATE_MAX_ACCESS_IDS); + } + + getChild("allowed_group_search_input")->setCommitCallback(boost::bind(&LLPanelEstateAccess::onAllowedGroupsSearchEdit, this, _2)); + getChild("add_allowed_group_btn")->setCommitCallback(boost::bind(&LLPanelEstateAccess::onClickAddAllowedGroup, this)); + childSetAction("remove_allowed_group_btn", boost::bind(&LLPanelEstateAccess::onClickRemoveAllowedGroup, this)); + childSetAction("copy_allowed_group_list_btn", boost::bind(&LLPanelEstateAccess::onClickCopyAllowedGroupList, this)); + + getChild("banned_avatar_name_list")->setCommitCallback(boost::bind(&LLPanelEstateInfo::onChangeChildCtrl, this, _1)); + LLNameListCtrl* banned_name_list = getChild("banned_avatar_name_list"); + if (banned_name_list) + { + banned_name_list->setCommitOnSelectionChange(true); + banned_name_list->setMaxItemCount(ESTATE_MAX_BANNED_IDS); + } + + getChild("banned_search_input")->setCommitCallback(boost::bind(&LLPanelEstateAccess::onBannedSearchEdit, this, _2)); + childSetAction("add_banned_avatar_btn", boost::bind(&LLPanelEstateAccess::onClickAddBannedAgent, this)); + childSetAction("remove_banned_avatar_btn", boost::bind(&LLPanelEstateAccess::onClickRemoveBannedAgent, this)); + childSetAction("copy_banned_list_btn", boost::bind(&LLPanelEstateAccess::onClickCopyBannedList, this)); + + getChild("estate_manager_name_list")->setCommitCallback(boost::bind(&LLPanelEstateInfo::onChangeChildCtrl, this, _1)); + LLNameListCtrl* manager_name_list = getChild("estate_manager_name_list"); + if (manager_name_list) + { + manager_name_list->setCommitOnSelectionChange(true); + manager_name_list->setMaxItemCount(ESTATE_MAX_MANAGERS * 4); // Allow extras for dupe issue + } + + childSetAction("add_estate_manager_btn", boost::bind(&LLPanelEstateAccess::onClickAddEstateManager, this)); + childSetAction("remove_estate_manager_btn", boost::bind(&LLPanelEstateAccess::onClickRemoveEstateManager, this)); + + return true; +} + +void LLPanelEstateAccess::updateControls(LLViewerRegion* region) +{ + bool god = gAgent.isGodlike(); + bool owner = (region && (region->getOwner() == gAgent.getID())); + bool manager = (region && region->isEstateManager()); + bool enable_cotrols = god || owner || manager; + setCtrlsEnabled(enable_cotrols); + + LLNameListCtrl* allowedAvatars = getChild("allowed_avatar_name_list"); + LLNameListCtrl* allowedGroups = getChild("allowed_group_name_list"); + LLNameListCtrl* bannedAvatars = getChild("banned_avatar_name_list"); + LLNameListCtrl* estateManagers = getChild("estate_manager_name_list"); + + bool has_allowed_avatar = allowedAvatars->getFirstSelected(); + bool has_allowed_group = allowedGroups->getFirstSelected(); + bool has_banned_agent = bannedAvatars->getFirstSelected(); + bool has_estate_manager = estateManagers->getFirstSelected(); + + getChildView("add_allowed_avatar_btn")->setEnabled(enable_cotrols); + getChildView("remove_allowed_avatar_btn")->setEnabled(has_allowed_avatar && enable_cotrols); + allowedAvatars->setEnabled(enable_cotrols); + + getChildView("add_allowed_group_btn")->setEnabled(enable_cotrols); + getChildView("remove_allowed_group_btn")->setEnabled(has_allowed_group && enable_cotrols); + allowedGroups->setEnabled(enable_cotrols); + + // Can't ban people from mainland, orientation islands, etc. because this + // creates much network traffic and server load. + // Disable their accounts in CSR tool instead. + bool linden_estate = LLPanelEstateInfo::isLindenEstate(); + bool enable_ban = enable_cotrols && !linden_estate; + getChildView("add_banned_avatar_btn")->setEnabled(enable_ban); + getChildView("remove_banned_avatar_btn")->setEnabled(has_banned_agent && enable_ban); + bannedAvatars->setEnabled(enable_cotrols); + + // estate managers can't add estate managers + getChildView("add_estate_manager_btn")->setEnabled(god || owner); + getChildView("remove_estate_manager_btn")->setEnabled(has_estate_manager && (god || owner)); + estateManagers->setEnabled(god || owner); + + if (enable_cotrols != mCtrlsEnabled) + { + mCtrlsEnabled = enable_cotrols; + updateLists(); // update the lists on the agent's access level change + } +} + +//--------------------------------------------------------------------------- +// Add/Remove estate access button callbacks +//--------------------------------------------------------------------------- +void LLPanelEstateAccess::onClickAddAllowedAgent() +{ + LLCtrlListInterface *list = childGetListInterface("allowed_avatar_name_list"); + if (!list) return; + if (list->getItemCount() >= ESTATE_MAX_ACCESS_IDS) + { + //args + + LLSD args; + args["MAX_AGENTS"] = llformat("%d", ESTATE_MAX_ACCESS_IDS); + LLNotificationsUtil::add("MaxAllowedAgentOnRegion", args); + return; + } + accessAddCore(ESTATE_ACCESS_ALLOWED_AGENT_ADD, "EstateAllowedAgentAdd"); +} + +void LLPanelEstateAccess::onClickRemoveAllowedAgent() +{ + accessRemoveCore(ESTATE_ACCESS_ALLOWED_AGENT_REMOVE, "EstateAllowedAgentRemove", "allowed_avatar_name_list"); +} + +void LLPanelEstateAccess::onClickAddAllowedGroup() +{ + LLCtrlListInterface *list = childGetListInterface("allowed_group_name_list"); + if (!list) return; + if (list->getItemCount() >= ESTATE_MAX_ACCESS_IDS) + { + LLSD args; + args["MAX_GROUPS"] = llformat("%d", ESTATE_MAX_ACCESS_IDS); + LLNotificationsUtil::add("MaxAllowedGroupsOnRegion", args); + return; + } + + LLNotification::Params params("ChangeLindenAccess"); + params.functor.function(boost::bind(&LLPanelEstateAccess::addAllowedGroup, this, _1, _2)); + if (LLPanelEstateInfo::isLindenEstate()) + { + LLNotifications::instance().add(params); + } + else + { + LLNotifications::instance().forceResponse(params, 0); + } +} + +bool LLPanelEstateAccess::addAllowedGroup(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) return false; + + LLFloater* parent_floater = gFloaterView->getParentFloater(this); + + LLFloaterGroupPicker* widget = LLFloaterReg::showTypedInstance("group_picker", LLSD(gAgent.getID())); + if (widget) + { + widget->removeNoneOption(); + widget->setSelectGroupCallback(boost::bind(&LLPanelEstateAccess::addAllowedGroup2, this, _1)); + if (parent_floater) + { + LLRect new_rect = gFloaterView->findNeighboringPosition(parent_floater, widget); + widget->setOrigin(new_rect.mLeft, new_rect.mBottom); + parent_floater->addDependentFloater(widget); + } + } + + return false; +} + +void LLPanelEstateAccess::onClickRemoveAllowedGroup() +{ + accessRemoveCore(ESTATE_ACCESS_ALLOWED_GROUP_REMOVE, "EstateAllowedGroupRemove", "allowed_group_name_list"); +} + +void LLPanelEstateAccess::onClickAddBannedAgent() +{ + LLCtrlListInterface *list = childGetListInterface("banned_avatar_name_list"); + if (!list) return; + if (list->getItemCount() >= ESTATE_MAX_BANNED_IDS) + { + LLSD args; + args["MAX_BANNED"] = llformat("%d", ESTATE_MAX_BANNED_IDS); + LLNotificationsUtil::add("MaxBannedAgentsOnRegion", args); + return; + } + accessAddCore(ESTATE_ACCESS_BANNED_AGENT_ADD, "EstateBannedAgentAdd"); +} + +void LLPanelEstateAccess::onClickRemoveBannedAgent() +{ + accessRemoveCore(ESTATE_ACCESS_BANNED_AGENT_REMOVE, "EstateBannedAgentRemove", "banned_avatar_name_list"); +} + +void LLPanelEstateAccess::onClickCopyAllowedList() +{ + copyListToClipboard("allowed_avatar_name_list"); +} + +void LLPanelEstateAccess::onClickCopyAllowedGroupList() +{ + copyListToClipboard("allowed_group_name_list"); +} + +void LLPanelEstateAccess::onClickCopyBannedList() +{ + copyListToClipboard("banned_avatar_name_list"); +} + +// static +void LLPanelEstateAccess::onClickAddEstateManager() +{ + LLCtrlListInterface *list = childGetListInterface("estate_manager_name_list"); + if (!list) return; + if (list->getItemCount() >= ESTATE_MAX_MANAGERS) + { // Tell user they can't add more managers + LLSD args; + args["MAX_MANAGER"] = llformat("%d", ESTATE_MAX_MANAGERS); + LLNotificationsUtil::add("MaxManagersOnRegion", args); + } + else + { // Go pick managers to add + accessAddCore(ESTATE_ACCESS_MANAGER_ADD, "EstateManagerAdd"); + } +} + +// static +void LLPanelEstateAccess::onClickRemoveEstateManager() +{ + accessRemoveCore(ESTATE_ACCESS_MANAGER_REMOVE, "EstateManagerRemove", "estate_manager_name_list"); +} + + +// Special case callback for groups, since it has different callback format than names +void LLPanelEstateAccess::addAllowedGroup2(LLUUID id) +{ + LLPanelEstateAccess* panel = LLFloaterRegionInfo::getPanelAccess(); + if (panel) + { + LLNameListCtrl* group_list = panel->getChild("allowed_group_name_list"); + LLScrollListItem* item = group_list->getNameItemByAgentId(id); + if (item) + { + LLSD args; + args["GROUP"] = item->getColumn(0)->getValue().asString(); + LLNotificationsUtil::add("GroupIsAlreadyInList", args); + return; + } + } + + LLSD payload; + payload["operation"] = (S32)ESTATE_ACCESS_ALLOWED_GROUP_ADD; + payload["dialog_name"] = "EstateAllowedGroupAdd"; + payload["allowed_ids"].append(id); + + LLSD args; + args["ALL_ESTATES"] = all_estates_text(); + + LLNotification::Params params("EstateAllowedGroupAdd"); + params.payload(payload) + .substitutions(args) + .functor.function(accessCoreConfirm); + if (LLPanelEstateInfo::isLindenEstate()) + { + LLNotifications::instance().forceResponse(params, 0); + } + else + { + LLNotifications::instance().add(params); + } +} + +// static +void LLPanelEstateAccess::accessAddCore(U32 operation_flag, const std::string& dialog_name) +{ + LLSD payload; + payload["operation"] = (S32)operation_flag; + payload["dialog_name"] = dialog_name; + // agent id filled in after avatar picker + + LLNotification::Params params("ChangeLindenAccess"); + params.payload(payload) + .functor.function(accessAddCore2); + + if (LLPanelEstateInfo::isLindenEstate()) + { + LLNotifications::instance().add(params); + } + else + { + // same as clicking "OK" + LLNotifications::instance().forceResponse(params, 0); + } +} + +// static +bool LLPanelEstateAccess::accessAddCore2(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) + { + // abort change + return false; + } + + LLEstateAccessChangeInfo* change_info = new LLEstateAccessChangeInfo(notification["payload"]); + //Get parent floater name + LLPanelEstateAccess* panel = LLFloaterRegionInfo::getPanelAccess(); + LLFloater* parent_floater = panel ? gFloaterView->getParentFloater(panel) : NULL; + const std::string& parent_floater_name = parent_floater ? parent_floater->getName() : ""; + + //Determine the button that triggered opening of the avatar picker + //(so that a shadow frustum from the button to the avatar picker can be created) + LLView * button = NULL; + switch (change_info->mOperationFlag) + { + case ESTATE_ACCESS_ALLOWED_AGENT_ADD: + button = panel->findChild("add_allowed_avatar_btn"); + break; + + case ESTATE_ACCESS_BANNED_AGENT_ADD: + button = panel->findChild("add_banned_avatar_btn"); + break; + + case ESTATE_ACCESS_MANAGER_ADD: + button = panel->findChild("add_estate_manager_btn"); + break; + } + + // avatar picker yes multi-select, yes close-on-select + LLFloater* child_floater = LLFloaterAvatarPicker::show(boost::bind(&LLPanelEstateAccess::accessAddCore3, _1, _2, (void*)change_info), + true, true, false, parent_floater_name, button); + + //Allows the closed parent floater to close the child floater (avatar picker) + if (child_floater) + { + parent_floater->addDependentFloater(child_floater); + } + + return false; +} + +// static +void LLPanelEstateAccess::accessAddCore3(const uuid_vec_t& ids, std::vector names, void* data) +{ + LLEstateAccessChangeInfo* change_info = (LLEstateAccessChangeInfo*)data; + if (!change_info) return; + if (ids.empty()) + { + // User didn't select a name. + delete change_info; + change_info = NULL; + return; + } + // User did select a name. + change_info->mAgentOrGroupIDs = ids; + // Can't put estate owner on ban list + LLPanelEstateAccess* panel = LLFloaterRegionInfo::getPanelAccess(); + if (!panel) return; + LLViewerRegion* region = gAgent.getRegion(); + if (!region) return; + + if (change_info->mOperationFlag & ESTATE_ACCESS_ALLOWED_AGENT_ADD) + { + LLNameListCtrl* name_list = panel->getChild("allowed_avatar_name_list"); + int currentCount = (name_list ? name_list->getItemCount() : 0); + if (ids.size() + currentCount > ESTATE_MAX_ACCESS_IDS) + { + LLSD args; + args["NUM_ADDED"] = llformat("%d", ids.size()); + args["MAX_AGENTS"] = llformat("%d", ESTATE_MAX_ACCESS_IDS); + args["LIST_TYPE"] = LLTrans::getString("RegionInfoListTypeAllowedAgents"); + args["NUM_EXCESS"] = llformat("%d", (ids.size() + currentCount) - ESTATE_MAX_ACCESS_IDS); + LLNotificationsUtil::add("MaxAgentOnRegionBatch", args); + delete change_info; + return; + } + + uuid_vec_t ids_allowed; + std::vector names_allowed; + std::string already_allowed; + bool single = true; + for (U32 i = 0; i < ids.size(); ++i) + { + LLScrollListItem* item = name_list->getNameItemByAgentId(ids[i]); + if (item) + { + if (!already_allowed.empty()) + { + already_allowed += ", "; + single = false; + } + already_allowed += item->getColumn(0)->getValue().asString(); + } + else + { + ids_allowed.push_back(ids[i]); + names_allowed.push_back(names[i]); + } + } + if (!already_allowed.empty()) + { + LLSD args; + args["AGENT"] = already_allowed; + args["LIST_TYPE"] = LLTrans::getString("RegionInfoListTypeAllowedAgents"); + LLNotificationsUtil::add(single ? "AgentIsAlreadyInList" : "AgentsAreAlreadyInList", args); + if (ids_allowed.empty()) + { + delete change_info; + return; + } + } + change_info->mAgentOrGroupIDs = ids_allowed; + change_info->mAgentNames = names_allowed; + } + if (change_info->mOperationFlag & ESTATE_ACCESS_BANNED_AGENT_ADD) + { + LLNameListCtrl* name_list = panel->getChild("banned_avatar_name_list"); + LLNameListCtrl* em_list = panel->getChild("estate_manager_name_list"); + int currentCount = (name_list ? name_list->getItemCount() : 0); + if (ids.size() + currentCount > ESTATE_MAX_BANNED_IDS) + { + LLSD args; + args["NUM_ADDED"] = llformat("%d", ids.size()); + args["MAX_AGENTS"] = llformat("%d", ESTATE_MAX_BANNED_IDS); + args["LIST_TYPE"] = LLTrans::getString("RegionInfoListTypeBannedAgents"); + args["NUM_EXCESS"] = llformat("%d", (ids.size() + currentCount) - ESTATE_MAX_BANNED_IDS); + LLNotificationsUtil::add("MaxAgentOnRegionBatch", args); + delete change_info; + return; + } + + uuid_vec_t ids_allowed; + std::vector names_allowed; + std::string already_banned; + std::string em_ban; + bool single = true; + for (U32 i = 0; i < ids.size(); ++i) + { + bool is_allowed = true; + LLScrollListItem* em_item = em_list->getNameItemByAgentId(ids[i]); + if (em_item) + { + if (!em_ban.empty()) + { + em_ban += ", "; + } + em_ban += em_item->getColumn(0)->getValue().asString(); + is_allowed = false; + } + + LLScrollListItem* item = name_list->getNameItemByAgentId(ids[i]); + if (item) + { + if (!already_banned.empty()) + { + already_banned += ", "; + single = false; + } + already_banned += item->getColumn(0)->getValue().asString(); + is_allowed = false; + } + + if (is_allowed) + { + ids_allowed.push_back(ids[i]); + names_allowed.push_back(names[i]); + } + } + if (!em_ban.empty()) + { + LLSD args; + args["AGENT"] = em_ban; + LLNotificationsUtil::add("ProblemBanningEstateManager", args); + if (ids_allowed.empty()) + { + delete change_info; + return; + } + } + if (!already_banned.empty()) + { + LLSD args; + args["AGENT"] = already_banned; + args["LIST_TYPE"] = LLTrans::getString("RegionInfoListTypeBannedAgents"); + LLNotificationsUtil::add(single ? "AgentIsAlreadyInList" : "AgentsAreAlreadyInList", args); + if (ids_allowed.empty()) + { + delete change_info; + return; + } + } + change_info->mAgentOrGroupIDs = ids_allowed; + change_info->mAgentNames = names_allowed; + } + + LLSD args; + args["ALL_ESTATES"] = all_estates_text(); + LLNotification::Params params(change_info->mDialogName); + params.substitutions(args) + .payload(change_info->asLLSD()) + .functor.function(accessCoreConfirm); + + if (LLPanelEstateInfo::isLindenEstate()) + { + // just apply to this estate + LLNotifications::instance().forceResponse(params, 0); + } + else + { + // ask if this estate or all estates with this owner + LLNotifications::instance().add(params); + } +} + +// static +void LLPanelEstateAccess::accessRemoveCore(U32 operation_flag, const std::string& dialog_name, const std::string& list_ctrl_name) +{ + LLPanelEstateAccess* panel = LLFloaterRegionInfo::getPanelAccess(); + if (!panel) return; + LLNameListCtrl* name_list = panel->getChild(list_ctrl_name); + if (!name_list) return; + + std::vector list_vector = name_list->getAllSelected(); + if (list_vector.size() == 0) + return; + + LLSD payload; + payload["operation"] = (S32)operation_flag; + payload["dialog_name"] = dialog_name; + + for (std::vector::const_iterator iter = list_vector.begin(); + iter != list_vector.end(); + iter++) + { + LLScrollListItem *item = (*iter); + payload["allowed_ids"].append(item->getUUID()); + } + + LLNotification::Params params("ChangeLindenAccess"); + params.payload(payload) + .functor.function(accessRemoveCore2); + + if (LLPanelEstateInfo::isLindenEstate()) + { + // warn on change linden estate + LLNotifications::instance().add(params); + } + else + { + // just proceed, as if clicking OK + LLNotifications::instance().forceResponse(params, 0); + } +} + +// static +bool LLPanelEstateAccess::accessRemoveCore2(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) + { + // abort + return false; + } + + // If Linden estate, can only apply to "this" estate, not all estates + // owned by NULL. + if (LLPanelEstateInfo::isLindenEstate()) + { + accessCoreConfirm(notification, response); + } + else + { + LLSD args; + args["ALL_ESTATES"] = all_estates_text(); + LLNotificationsUtil::add(notification["payload"]["dialog_name"], + args, + notification["payload"], + accessCoreConfirm); + } + return false; +} + +// Used for both access add and remove operations, depending on the mOperationFlag +// passed in (ESTATE_ACCESS_BANNED_AGENT_ADD, ESTATE_ACCESS_ALLOWED_AGENT_REMOVE, etc.) +// static +bool LLPanelEstateAccess::accessCoreConfirm(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + const U32 originalFlags = (U32)notification["payload"]["operation"].asInteger(); + U32 flags = originalFlags; + + LLViewerRegion* region = gAgent.getRegion(); + + if (option == 2) // cancel + { + return false; + } + else if (option == 1) + { + // All estates, either than I own or manage for this owner. + // This will be verified on simulator. JC + if (!region) return false; + if (region->getOwner() == gAgent.getID() + || gAgent.isGodlike()) + { + flags |= ESTATE_ACCESS_APPLY_TO_ALL_ESTATES; + } + else if (region->isEstateManager()) + { + flags |= ESTATE_ACCESS_APPLY_TO_MANAGED_ESTATES; + } + } + + std::string names; + U32 listed_names = 0; + for (U32 i = 0; i < notification["payload"]["allowed_ids"].size(); ++i) + { + if (i + 1 != notification["payload"]["allowed_ids"].size()) + { + flags |= ESTATE_ACCESS_NO_REPLY; + } + else + { + flags &= ~ESTATE_ACCESS_NO_REPLY; + } + + const LLUUID id = notification["payload"]["allowed_ids"][i].asUUID(); + if (((U32)notification["payload"]["operation"].asInteger() & ESTATE_ACCESS_BANNED_AGENT_ADD) + && region && (region->getOwner() == id)) + { + LLNotificationsUtil::add("OwnerCanNotBeDenied"); + break; + } + + sendEstateAccessDelta(flags, id); + + if ((flags & (ESTATE_ACCESS_ALLOWED_GROUP_ADD | ESTATE_ACCESS_ALLOWED_GROUP_REMOVE)) == 0) + { + // fill the name list for confirmation + if (listed_names < MAX_LISTED_NAMES) + { + if (!names.empty()) + { + names += ", "; + } + if (!notification["payload"]["allowed_names"][i]["display_name"].asString().empty()) + { + names += notification["payload"]["allowed_names"][i]["display_name"].asString(); + } + else + { //try to get an agent name from cache + LLAvatarName av_name; + if (LLAvatarNameCache::get(id, &av_name)) + { + names += av_name.getCompleteName(); + } + } + + } + listed_names++; + } + } + if (listed_names > MAX_LISTED_NAMES) + { + LLSD args; + args["EXTRA_COUNT"] = llformat("%d", listed_names - MAX_LISTED_NAMES); + names += " " + LLTrans::getString("AndNMore", args); + } + + if (!names.empty()) // show the conirmation + { + LLSD args; + args["AGENT"] = names; + + if (flags & (ESTATE_ACCESS_ALLOWED_AGENT_ADD | ESTATE_ACCESS_ALLOWED_AGENT_REMOVE)) + { + args["LIST_TYPE"] = LLTrans::getString("RegionInfoListTypeAllowedAgents"); + } + else if (flags & (ESTATE_ACCESS_BANNED_AGENT_ADD | ESTATE_ACCESS_BANNED_AGENT_REMOVE)) + { + args["LIST_TYPE"] = LLTrans::getString("RegionInfoListTypeBannedAgents"); + } + + if (flags & ESTATE_ACCESS_APPLY_TO_ALL_ESTATES) + { + args["ESTATE"] = LLTrans::getString("RegionInfoAllEstates"); + } + else if (flags & ESTATE_ACCESS_APPLY_TO_MANAGED_ESTATES) + { + args["ESTATE"] = LLTrans::getString("RegionInfoManagedEstates"); + } + else + { + args["ESTATE"] = LLTrans::getString("RegionInfoThisEstate"); + } + + bool single = (listed_names == 1); + if (flags & (ESTATE_ACCESS_ALLOWED_AGENT_ADD | ESTATE_ACCESS_BANNED_AGENT_ADD)) + { + LLNotificationsUtil::add(single ? "AgentWasAddedToList" : "AgentsWereAddedToList", args); + } + else if (flags & (ESTATE_ACCESS_ALLOWED_AGENT_REMOVE | ESTATE_ACCESS_BANNED_AGENT_REMOVE)) + { + LLNotificationsUtil::add(single ? "AgentWasRemovedFromList" : "AgentsWereRemovedFromList", args); + } + } + + LLPanelEstateAccess* panel = LLFloaterRegionInfo::getPanelAccess(); + if (panel) + { + panel->setPendingUpdate(true); + } + + return false; +} + +// key = "estateaccessdelta" +// str(estate_id) will be added to front of list by forward_EstateOwnerRequest_to_dataserver +// str[0] = str(agent_id) requesting the change +// str[1] = str(flags) (ESTATE_ACCESS_DELTA_*) +// str[2] = str(agent_id) to add or remove +// static +void LLPanelEstateAccess::sendEstateAccessDelta(U32 flags, const LLUUID& agent_or_group_id) +{ + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("EstateOwnerMessage"); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addUUIDFast(_PREHASH_TransactionID, LLUUID::null); //not used + + msg->nextBlock("MethodData"); + msg->addString("Method", "estateaccessdelta"); + msg->addUUID("Invoice", LLFloaterRegionInfo::getLastInvoice()); + + std::string buf; + gAgent.getID().toString(buf); + msg->nextBlock("ParamList"); + msg->addString("Parameter", buf); + + buf = llformat("%u", flags); + msg->nextBlock("ParamList"); + msg->addString("Parameter", buf); + + agent_or_group_id.toString(buf); + msg->nextBlock("ParamList"); + msg->addString("Parameter", buf); + + gAgent.sendReliableMessage(); +} + +void LLPanelEstateAccess::updateChild(LLUICtrl* child_ctrl) +{ + // Ensure appropriate state of the management ui. + updateControls(gAgent.getRegion()); +} + +void LLPanelEstateAccess::updateLists() +{ + std::string cap_url = gAgent.getRegionCapability("EstateAccess"); + if (!cap_url.empty()) + { + LLCoros::instance().launch("LLFloaterRegionInfo::requestEstateGetAccessCoro", boost::bind(LLPanelEstateAccess::requestEstateGetAccessCoro, cap_url)); + } +} + +void LLPanelEstateAccess::requestEstateGetAccessCoro(std::string url) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("requestEstateGetAccessoCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD result = httpAdapter->getAndSuspend(httpRequest, url); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + LLPanelEstateAccess* panel = LLFloaterRegionInfo::getPanelAccess(); + if (!panel) return; + + LLNameListCtrl* allowed_agent_name_list = panel->getChild("allowed_avatar_name_list"); + if (allowed_agent_name_list && result.has("AllowedAgents")) + { + LLStringUtil::format_map_t args; + args["[ALLOWEDAGENTS]"] = llformat("%d", result["AllowedAgents"].size()); + args["[MAXACCESS]"] = llformat("%d", ESTATE_MAX_ACCESS_IDS); + std::string msg = LLTrans::getString("RegionInfoAllowedResidents", args); + panel->getChild("allow_resident_label")->setValue(LLSD(msg)); + + allowed_agent_name_list->clearSortOrder(); + allowed_agent_name_list->deleteAllItems(); + for (LLSD::array_const_iterator it = result["AllowedAgents"].beginArray(); it != result["AllowedAgents"].endArray(); ++it) + { + LLUUID id = (*it)["id"].asUUID(); + allowed_agent_name_list->addNameItem(id); + } + allowed_agent_name_list->sortByName(true); + } + + LLNameListCtrl* banned_agent_name_list = panel->getChild("banned_avatar_name_list"); + if (banned_agent_name_list && result.has("BannedAgents")) + { + LLStringUtil::format_map_t args; + args["[BANNEDAGENTS]"] = llformat("%d", result["BannedAgents"].size()); + args["[MAXBANNED]"] = llformat("%d", ESTATE_MAX_BANNED_IDS); + std::string msg = LLTrans::getString("RegionInfoBannedResidents", args); + panel->getChild("ban_resident_label")->setValue(LLSD(msg)); + + banned_agent_name_list->clearSortOrder(); + banned_agent_name_list->deleteAllItems(); + for (LLSD::array_const_iterator it = result["BannedAgents"].beginArray(); it != result["BannedAgents"].endArray(); ++it) + { + LLSD item; + item["id"] = (*it)["id"].asUUID(); + LLSD& columns = item["columns"]; + + columns[0]["column"] = "name"; // to be populated later + + columns[1]["column"] = "last_login_date"; + columns[1]["value"] = (*it)["last_login_date"].asString().substr(0, 16); // cut the seconds + + std::string ban_date = (*it)["ban_date"].asString(); + columns[2]["column"] = "ban_date"; + columns[2]["value"] = ban_date[0] != '0' ? ban_date.substr(0, 16) : LLTrans::getString("na"); // server returns the "0000-00-00 00:00:00" date in case it doesn't know it + + columns[3]["column"] = "bannedby"; + LLUUID banning_id = (*it)["banning_id"].asUUID(); + LLAvatarName av_name; + if (banning_id.isNull()) + { + columns[3]["value"] = LLTrans::getString("na"); + } + else if (LLAvatarNameCache::get(banning_id, &av_name)) + { + columns[3]["value"] = av_name.getCompleteName(); //TODO: fetch the name if it wasn't cached + } + + banned_agent_name_list->addElement(item); + } + banned_agent_name_list->sortByName(true); + } + + LLNameListCtrl* allowed_group_name_list = panel->getChild("allowed_group_name_list"); + if (allowed_group_name_list && result.has("AllowedGroups")) + { + LLStringUtil::format_map_t args; + args["[ALLOWEDGROUPS]"] = llformat("%d", result["AllowedGroups"].size()); + args["[MAXACCESS]"] = llformat("%d", ESTATE_MAX_GROUP_IDS); + std::string msg = LLTrans::getString("RegionInfoAllowedGroups", args); + panel->getChild("allow_group_label")->setValue(LLSD(msg)); + + allowed_group_name_list->clearSortOrder(); + allowed_group_name_list->deleteAllItems(); + for (LLSD::array_const_iterator it = result["AllowedGroups"].beginArray(); it != result["AllowedGroups"].endArray(); ++it) + { + LLUUID id = (*it)["id"].asUUID(); + allowed_group_name_list->addGroupNameItem(id); + } + allowed_group_name_list->sortByName(true); + } + + LLNameListCtrl* estate_manager_name_list = panel->getChild("estate_manager_name_list"); + if (estate_manager_name_list && result.has("Managers")) + { + LLStringUtil::format_map_t args; + args["[ESTATEMANAGERS]"] = llformat("%d", result["Managers"].size()); + args["[MAXMANAGERS]"] = llformat("%d", ESTATE_MAX_MANAGERS); + std::string msg = LLTrans::getString("RegionInfoEstateManagers", args); + panel->getChild("estate_manager_label")->setValue(LLSD(msg)); + + estate_manager_name_list->clearSortOrder(); + estate_manager_name_list->deleteAllItems(); + for (LLSD::array_const_iterator it = result["Managers"].beginArray(); it != result["Managers"].endArray(); ++it) + { + LLUUID id = (*it)["agent_id"].asUUID(); + estate_manager_name_list->addNameItem(id); + } + estate_manager_name_list->sortByName(true); + } + + + panel->updateControls(gAgent.getRegion()); +} + +//--------------------------------------------------------------------------- +// Access lists search +//--------------------------------------------------------------------------- +void LLPanelEstateAccess::onAllowedSearchEdit(const std::string& search_string) +{ + LLPanelEstateAccess* panel = LLFloaterRegionInfo::getPanelAccess(); + if (!panel) return; + LLNameListCtrl* allowed_agent_name_list = panel->getChild("allowed_avatar_name_list"); + searchAgent(allowed_agent_name_list, search_string); +} + +void LLPanelEstateAccess::onAllowedGroupsSearchEdit(const std::string& search_string) +{ + LLPanelEstateAccess* panel = LLFloaterRegionInfo::getPanelAccess(); + if (!panel) return; + LLNameListCtrl* allowed_group_name_list = panel->getChild("allowed_group_name_list"); + searchAgent(allowed_group_name_list, search_string); +} + +void LLPanelEstateAccess::onBannedSearchEdit(const std::string& search_string) +{ + LLPanelEstateAccess* panel = LLFloaterRegionInfo::getPanelAccess(); + if (!panel) return; + LLNameListCtrl* banned_agent_name_list = panel->getChild("banned_avatar_name_list"); + searchAgent(banned_agent_name_list, search_string); +} + +void LLPanelEstateAccess::searchAgent(LLNameListCtrl* listCtrl, const std::string& search_string) +{ + if (!listCtrl) return; + + if (!search_string.empty()) + { + listCtrl->setSearchColumn(0); // name column + listCtrl->searchItems(search_string, false, true); + } + else + { + listCtrl->deselectAllItems(true); + } +} + +void LLPanelEstateAccess::copyListToClipboard(std::string list_name) +{ + LLPanelEstateAccess* panel = LLFloaterRegionInfo::getPanelAccess(); + if (!panel) return; + LLNameListCtrl* name_list = panel->getChild(list_name); + if (!name_list) return; + + std::vector list_vector = name_list->getAllData(); + if (list_vector.size() == 0) return; + + LLSD::String list_to_copy; + for (std::vector::const_iterator iter = list_vector.begin(); + iter != list_vector.end(); + iter++) + { + LLScrollListItem *item = (*iter); + if (item) + { + list_to_copy += item->getColumn(0)->getValue().asString(); + } + if (std::next(iter) != list_vector.end()) + { + list_to_copy += "\n"; + } + } + + LLClipboard::instance().copyToClipboard(utf8str_to_wstring(list_to_copy), 0, list_to_copy.length()); +} + +bool LLPanelEstateAccess::refreshFromRegion(LLViewerRegion* region) +{ + updateLists(); + return LLPanelRegionInfo::refreshFromRegion(region); +} + +//========================================================================= +const U32 LLPanelRegionEnvironment::DIRTY_FLAG_OVERRIDE(0x01 << 4); + +LLPanelRegionEnvironment::LLPanelRegionEnvironment(): + LLPanelEnvironmentInfo(), + mAllowOverrideRestore(false) +{ +} + +LLPanelRegionEnvironment::~LLPanelRegionEnvironment() +{ + if (mCommitConnect.connected()) + mCommitConnect.disconnect(); +} + +bool LLPanelRegionEnvironment::postBuild() +{ + LLEstateInfoModel& estate_info = LLEstateInfoModel::instance(); + + if (!LLPanelEnvironmentInfo::postBuild()) + return false; + + getChild(BTN_USEDEFAULT)->setLabelArg("[USEDEFAULT]", getString(STR_LABEL_USEDEFAULT)); + getChild(CHK_ALLOWOVERRIDE)->setVisible(true); + getChild(PNL_ENVIRONMENT_ALTITUDES)->setVisible(true); + + getChild(CHK_ALLOWOVERRIDE)->setCommitCallback([this](LLUICtrl *, const LLSD &value){ onChkAllowOverride(value.asBoolean()); }); + + mCommitConnect = estate_info.setCommitCallback(boost::bind(&LLPanelRegionEnvironment::refreshFromEstate, this)); + return true; +} + + +void LLPanelRegionEnvironment::refresh() +{ + commitDayLenOffsetChanges(false); // commit unsaved changes if any + + if (!mCurrentEnvironment) + { + if (mCurEnvVersion <= INVALID_PARCEL_ENVIRONMENT_VERSION) + { + refreshFromSource(); // will immediately set mCurEnvVersion + } // else - already requesting + return; + } + + LLPanelEnvironmentInfo::refresh(); + + getChild(CHK_ALLOWOVERRIDE)->setValue(mAllowOverride); +} + +bool LLPanelRegionEnvironment::refreshFromRegion(LLViewerRegion* region) +{ + if (!region) + { + setNoSelection(true); + setControlsEnabled(false); + mCurEnvVersion = INVALID_PARCEL_ENVIRONMENT_VERSION; + getChild("region_text")->setValue(LLSD("")); + } + else + { + getChild("region_text")->setValue(LLSD(region->getName())); + } + setNoSelection(false); + + if (gAgent.getRegion()->getRegionID() != region->getRegionID()) + { + setCrossRegion(true); + mCurEnvVersion = INVALID_PARCEL_ENVIRONMENT_VERSION; + } + setCrossRegion(false); + + refreshFromSource(); + return true; +} + +void LLPanelRegionEnvironment::refreshFromSource() +{ + LL_DEBUGS("ENVIRONMENT") << "Requesting environment for region, known version " << mCurEnvVersion << LL_ENDL; + LLHandle that_h = getHandle(); + + if (mCurEnvVersion < UNSET_PARCEL_ENVIRONMENT_VERSION) + { + // to mark as requesting + mCurEnvVersion = UNSET_PARCEL_ENVIRONMENT_VERSION; + } + + LLEnvironment::instance().requestRegion( + [that_h](S32 parcel_id, LLEnvironment::EnvironmentInfo::ptr_t envifo) { _onEnvironmentReceived(that_h, parcel_id, envifo); }); + + setControlsEnabled(false); +} + +bool LLPanelRegionEnvironment::confirmUpdateEstateEnvironment(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + + switch (option) + { + case 0: + { + LLEstateInfoModel& estate_info = LLEstateInfoModel::instance(); + + // update model + estate_info.setAllowEnvironmentOverride(mAllowOverride); + // send the update to sim + estate_info.sendEstateInfo(); + clearDirtyFlag(DIRTY_FLAG_OVERRIDE); + } + break; + + case 1: + mAllowOverride = mAllowOverrideRestore; + getChild(CHK_ALLOWOVERRIDE)->setValue(mAllowOverride); + break; + default: + break; + } + return false; +} + +void LLPanelRegionEnvironment::onChkAllowOverride(bool value) +{ + setDirtyFlag(DIRTY_FLAG_OVERRIDE); + mAllowOverrideRestore = mAllowOverride; + mAllowOverride = value; + + + std::string notification("EstateParcelEnvironmentOverride"); + if (LLPanelEstateInfo::isLindenEstate()) + notification = "ChangeLindenEstate"; + + LLSD args; + args["ESTATENAME"] = LLEstateInfoModel::instance().getName(); + LLNotification::Params params(notification); + params.substitutions(args); + params.functor.function([this](const LLSD& notification, const LLSD& response) { confirmUpdateEstateEnvironment(notification, response); }); + + if (!value || LLPanelEstateInfo::isLindenEstate()) + { // warn if turning off or a Linden Estate + LLNotifications::instance().add(params); + } + else + { + LLNotifications::instance().forceResponse(params, 0); + } + +} diff --git a/indra/newview/llfloaterregioninfo.h b/indra/newview/llfloaterregioninfo.h index 25de27b27e..5915c6c8b8 100644 --- a/indra/newview/llfloaterregioninfo.h +++ b/indra/newview/llfloaterregioninfo.h @@ -1,492 +1,492 @@ -/** - * @file llfloaterregioninfo.h - * @author Aaron Brashears - * @brief Declaration of the region info and controls floater and panels. - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERREGIONINFO_H -#define LL_LLFLOATERREGIONINFO_H - -#include -#include "llagent.h" -#include "llassettype.h" -#include "llfloater.h" -#include "llhost.h" -#include "llpanel.h" -#include "llextendedstatus.h" -#include "llpanelenvironment.h" - -#include "lleventcoro.h" - -class LLAvatarName; -class LLDispatcher; -class LLLineEditor; -class LLMessageSystem; -class LLPanelRegionInfo; -class LLTabContainer; -class LLViewerRegion; -class LLViewerTextEditor; -class LLInventoryItem; -class LLCheckBoxCtrl; -class LLComboBox; -class LLNameListCtrl; -class LLRadioGroup; -class LLSliderCtrl; -class LLSpinCtrl; -class LLTextBox; - -class LLPanelRegionGeneralInfo; -class LLPanelRegionDebugInfo; -class LLPanelRegionTerrainInfo; -class LLPanelEstateInfo; -class LLPanelEstateCovenant; -class LLPanelExperienceListEditor; -class LLPanelExperiences; -class LLPanelRegionExperiences; -class LLPanelEstateAccess; -class LLPanelRegionEnvironment; - -class LLEventTimer; - -class LLFloaterRegionInfo : public LLFloater -{ - friend class LLFloaterReg; -public: - - - /*virtual*/ void onOpen(const LLSD& key); - /*virtual*/ void onClose(bool app_quitting); - /*virtual*/ bool postBuild(); - - static void processEstateOwnerRequest(LLMessageSystem* msg, void**); - - // get and process region info if necessary. - static void processRegionInfo(LLMessageSystem* msg); - - static const LLUUID& getLastInvoice() { return sRequestInvoice; } - static void nextInvoice() { sRequestInvoice.generate(); } - //static S32 getSerial() { return sRequestSerial; } - //static void incrementSerial() { sRequestSerial++; } - - static LLPanelEstateInfo* getPanelEstate(); - static LLPanelEstateAccess* getPanelAccess(); - static LLPanelEstateCovenant* getPanelCovenant(); - static LLPanelRegionTerrainInfo* getPanelRegionTerrain(); - static LLPanelRegionExperiences* getPanelExperiences(); - static LLPanelRegionGeneralInfo* getPanelGeneral(); - static LLPanelRegionEnvironment* getPanelEnvironment(); - - // from LLPanel - virtual void refresh(); - - void onRegionChanged(); - void requestRegionInfo(); - void enableTopButtons(); - void disableTopButtons(); - -private: - - LLFloaterRegionInfo(const LLSD& seed); - ~LLFloaterRegionInfo(); - -protected: - void onTabSelected(const LLSD& param); - void disableTabCtrls(); - void refreshFromRegion(LLViewerRegion* region); - void onGodLevelChange(U8 god_level); - - // member data - LLTabContainer* mTab; - typedef std::vector info_panels_t; - info_panels_t mInfoPanels; - LLPanelRegionEnvironment *mEnvironmentPanel; - //static S32 sRequestSerial; // serial # of last EstateOwnerRequest - static LLUUID sRequestInvoice; - -private: - LLAgent::god_level_change_slot_t mGodLevelChangeSlot; - boost::signals2::connection mRegionChangedCallback; -}; - - -// Base class for all region information panels. -class LLPanelRegionInfo : public LLPanel -{ -public: - LLPanelRegionInfo(); - - void onBtnSet(); - void onChangeChildCtrl(LLUICtrl* ctrl); - void onChangeAnything(); - static void onChangeText(LLLineEditor* caller, void* user_data); - - virtual bool refreshFromRegion(LLViewerRegion* region); - virtual bool estateUpdate(LLMessageSystem* msg) { return true; } - - virtual bool postBuild(); - virtual void updateChild(LLUICtrl* child_ctrl); - - void enableButton(const std::string& btn_name, bool enable = true); - void disableButton(const std::string& btn_name); - - void onClickManageTelehub(); - -protected: - void initCtrl(const std::string& name); - - // Returns true if update sent and apply button should be - // disabled. - virtual bool sendUpdate() { return true; } - - typedef std::vector strings_t; - //typedef std::vector integers_t; - void sendEstateOwnerMessage( - LLMessageSystem* msg, - const std::string& request, - const LLUUID& invoice, - const strings_t& strings); - - - // member data - LLHost mHost; -}; - -///////////////////////////////////////////////////////////////////////////// -// Actual panels start here -///////////////////////////////////////////////////////////////////////////// - -class LLPanelRegionGeneralInfo : public LLPanelRegionInfo -{ - -public: - LLPanelRegionGeneralInfo() - : LLPanelRegionInfo() {} - ~LLPanelRegionGeneralInfo() {} - - virtual bool refreshFromRegion(LLViewerRegion* region); - - // LLPanel - virtual bool postBuild(); - - void onBtnSet(); - void setObjBonusFactor(F32 object_bonus_factor) {mObjBonusFactor = object_bonus_factor;} - -protected: - virtual bool sendUpdate(); - void onClickKick(); - void onKickCommit(const uuid_vec_t& ids); - static void onClickKickAll(void* userdata); - bool onKickAllCommit(const LLSD& notification, const LLSD& response); - static void onClickMessage(void* userdata); - bool onMessageCommit(const LLSD& notification, const LLSD& response); - bool onChangeObjectBonus(const LLSD& notification, const LLSD& response); - - F32 mObjBonusFactor; - -}; - -///////////////////////////////////////////////////////////////////////////// - -class LLPanelRegionDebugInfo : public LLPanelRegionInfo -{ -public: - LLPanelRegionDebugInfo() - : LLPanelRegionInfo(), mTargetAvatar() {} - ~LLPanelRegionDebugInfo() {} - // LLPanel - virtual bool postBuild(); - - virtual bool refreshFromRegion(LLViewerRegion* region); - -protected: - virtual bool sendUpdate(); - - void onClickChooseAvatar(); - void callbackAvatarID(const uuid_vec_t& ids, const std::vector names); - static void onClickReturn(void *); - bool callbackReturn(const LLSD& notification, const LLSD& response); - static void onClickTopColliders(void*); - static void onClickTopScripts(void*); - static void onClickRestart(void* data); - bool callbackRestart(const LLSD& notification, const LLSD& response); - static void onClickCancelRestart(void* data); - static void onClickDebugConsole(void* data); - -private: - LLUUID mTargetAvatar; -}; - -///////////////////////////////////////////////////////////////////////////// - -class LLPanelRegionTerrainInfo : public LLPanelRegionInfo -{ - LOG_CLASS(LLPanelRegionTerrainInfo); - -public: - LLPanelRegionTerrainInfo() : LLPanelRegionInfo() {} - ~LLPanelRegionTerrainInfo() {} - - virtual bool postBuild(); // LLPanel - - virtual bool refreshFromRegion(LLViewerRegion* region); // refresh local settings from region update from simulator - void setEnvControls(bool available); // Whether environment settings are available for this region - - bool validateTextureSizes(); - bool validateTextureHeights(); - - //static void onChangeAnything(LLUICtrl* ctrl, void* userData); // callback for any change, to enable commit button - - virtual bool sendUpdate(); - - static void onClickDownloadRaw(void*); - static void onClickUploadRaw(void*); - static void onClickBakeTerrain(void*); - bool callbackBakeTerrain(const LLSD& notification, const LLSD& response); - bool callbackTextureHeights(const LLSD& notification, const LLSD& response); - -private: - bool mConfirmedTextureHeights; - bool mAskedTextureHeights; -}; - -///////////////////////////////////////////////////////////////////////////// - -class LLPanelEstateInfo : public LLPanelRegionInfo -{ -public: - static void initDispatch(LLDispatcher& dispatch); - - void onChangeFixedSun(); - void onChangeUseGlobalTime(); - void onChangeAccessOverride(); - - void onClickEditSky(); - void onClickEditSkyHelp(); - void onClickEditDayCycle(); - void onClickEditDayCycleHelp(); - - void onClickKickUser(); - - - bool kickUserConfirm(const LLSD& notification, const LLSD& response); - - void onKickUserCommit(const uuid_vec_t& ids); - static void onClickMessageEstate(void* data); - bool onMessageCommit(const LLSD& notification, const LLSD& response); - - LLPanelEstateInfo(); - ~LLPanelEstateInfo() {} - - void updateControls(LLViewerRegion* region); - - static void updateEstateName(const std::string& name); - static void updateEstateOwnerName(const std::string& name); - - virtual bool refreshFromRegion(LLViewerRegion* region); - virtual bool estateUpdate(LLMessageSystem* msg); - - // LLPanel - virtual bool postBuild(); - virtual void updateChild(LLUICtrl* child_ctrl); - virtual void refresh(); - - void refreshFromEstate(); - - static bool isLindenEstate(); - - const std::string getOwnerName() const; - void setOwnerName(const std::string& name); - -protected: - virtual bool sendUpdate(); - // confirmation dialog callback - bool callbackChangeLindenEstate(const LLSD& notification, const LLSD& response); - - void commitEstateAccess(); - void commitEstateManagers(); - - bool checkSunHourSlider(LLUICtrl* child_ctrl); - - U32 mEstateID; -}; - -///////////////////////////////////////////////////////////////////////////// - -class LLPanelEstateCovenant : public LLPanelRegionInfo -{ -public: - LLPanelEstateCovenant(); - ~LLPanelEstateCovenant() {} - - // LLPanel - virtual bool postBuild(); - virtual void updateChild(LLUICtrl* child_ctrl); - virtual bool refreshFromRegion(LLViewerRegion* region); - virtual bool estateUpdate(LLMessageSystem* msg); - - // LLView overrides - bool handleDragAndDrop(S32 x, S32 y, MASK mask, - bool drop, EDragAndDropType cargo_type, - void *cargo_data, EAcceptance *accept, - std::string& tooltip_msg); - static bool confirmChangeCovenantCallback(const LLSD& notification, const LLSD& response); - static void resetCovenantID(void* userdata); - static bool confirmResetCovenantCallback(const LLSD& notification, const LLSD& response); - void sendChangeCovenantID(const LLUUID &asset_id); - void loadInvItem(LLInventoryItem *itemp); - static void onLoadComplete(const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status); - - // Accessor functions - static void updateCovenantText(const std::string& string, const LLUUID& asset_id); - static void updateEstateName(const std::string& name); - static void updateLastModified(const std::string& text); - static void updateEstateOwnerName(const std::string& name); - - const LLUUID& getCovenantID() const { return mCovenantID; } - void setCovenantID(const LLUUID& id) { mCovenantID = id; } - std::string getEstateName() const; - void setEstateName(const std::string& name); - std::string getOwnerName() const; - void setOwnerName(const std::string& name); - void setCovenantTextEditor(const std::string& text); - - typedef enum e_asset_status - { - ASSET_ERROR, - ASSET_UNLOADED, - ASSET_LOADING, - ASSET_LOADED - } EAssetStatus; - -protected: - virtual bool sendUpdate(); - LLTextBox* mEstateNameText; - LLTextBox* mEstateOwnerText; - LLTextBox* mLastModifiedText; - // CovenantID from sim - LLUUID mCovenantID; - LLViewerTextEditor* mEditor; - EAssetStatus mAssetStatus; -}; - -///////////////////////////////////////////////////////////////////////////// - - -class LLPanelRegionExperiences : public LLPanelRegionInfo -{ - LOG_CLASS(LLPanelRegionExperiences); - -public: - LLPanelRegionExperiences(){} - /*virtual*/ bool postBuild(); - virtual bool sendUpdate(); - - static bool experienceCoreConfirm(const LLSD& notification, const LLSD& response); - static void sendEstateExperienceDelta(U32 flags, const LLUUID& agent_id); - - static void infoCallback(LLHandle handle, const LLSD& content); - bool refreshFromRegion(LLViewerRegion* region); - void sendPurchaseRequest()const; - void processResponse( const LLSD& content ); -private: - void refreshRegionExperiences(); - - static std::string regionCapabilityQuery(LLViewerRegion* region, const std::string &cap); - - LLPanelExperienceListEditor* setupList(const char* control_name, U32 add_id, U32 remove_id); - static LLSD addIds( LLPanelExperienceListEditor* panel ); - - void itemChanged(U32 event_type, const LLUUID& id); - - LLPanelExperienceListEditor* mTrusted; - LLPanelExperienceListEditor* mAllowed; - LLPanelExperienceListEditor* mBlocked; - LLUUID mDefaultExperience; -}; - - -class LLPanelEstateAccess : public LLPanelRegionInfo -{ - LOG_CLASS(LLPanelEstateAccess); - -public: - LLPanelEstateAccess(); - - virtual bool postBuild(); - virtual void updateChild(LLUICtrl* child_ctrl); - - void updateControls(LLViewerRegion* region); - void updateLists(); - - void setPendingUpdate(bool pending) { mPendingUpdate = pending; } - bool getPendingUpdate() { return mPendingUpdate; } - - virtual bool refreshFromRegion(LLViewerRegion* region); - -private: - void onClickAddAllowedAgent(); - void onClickRemoveAllowedAgent(); - void onClickCopyAllowedList(); - void onClickAddAllowedGroup(); - void onClickRemoveAllowedGroup(); - void onClickCopyAllowedGroupList(); - void onClickAddBannedAgent(); - void onClickRemoveBannedAgent(); - void onClickCopyBannedList(); - void onClickAddEstateManager(); - void onClickRemoveEstateManager(); - void onAllowedSearchEdit(const std::string& search_string); - void onAllowedGroupsSearchEdit(const std::string& search_string); - void onBannedSearchEdit(const std::string& search_string); - - // Group picker callback is different, can't use core methods below - bool addAllowedGroup(const LLSD& notification, const LLSD& response); - void addAllowedGroup2(LLUUID id); - - // Core methods for all above add/remove button clicks - static void accessAddCore(U32 operation_flag, const std::string& dialog_name); - static bool accessAddCore2(const LLSD& notification, const LLSD& response); - static void accessAddCore3(const uuid_vec_t& ids, std::vector names, void* data); - - static void accessRemoveCore(U32 operation_flag, const std::string& dialog_name, const std::string& list_ctrl_name); - static bool accessRemoveCore2(const LLSD& notification, const LLSD& response); - - // used for both add and remove operations - static bool accessCoreConfirm(const LLSD& notification, const LLSD& response); - - // Send the actual EstateOwnerRequest "estateaccessdelta" message - static void sendEstateAccessDelta(U32 flags, const LLUUID& agent_id); - - static void requestEstateGetAccessCoro(std::string url); - - void searchAgent(LLNameListCtrl* listCtrl, const std::string& search_string); - void copyListToClipboard(std::string list_name); - - bool mPendingUpdate; - bool mCtrlsEnabled; -}; - -#endif +/** + * @file llfloaterregioninfo.h + * @author Aaron Brashears + * @brief Declaration of the region info and controls floater and panels. + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERREGIONINFO_H +#define LL_LLFLOATERREGIONINFO_H + +#include +#include "llagent.h" +#include "llassettype.h" +#include "llfloater.h" +#include "llhost.h" +#include "llpanel.h" +#include "llextendedstatus.h" +#include "llpanelenvironment.h" + +#include "lleventcoro.h" + +class LLAvatarName; +class LLDispatcher; +class LLLineEditor; +class LLMessageSystem; +class LLPanelRegionInfo; +class LLTabContainer; +class LLViewerRegion; +class LLViewerTextEditor; +class LLInventoryItem; +class LLCheckBoxCtrl; +class LLComboBox; +class LLNameListCtrl; +class LLRadioGroup; +class LLSliderCtrl; +class LLSpinCtrl; +class LLTextBox; + +class LLPanelRegionGeneralInfo; +class LLPanelRegionDebugInfo; +class LLPanelRegionTerrainInfo; +class LLPanelEstateInfo; +class LLPanelEstateCovenant; +class LLPanelExperienceListEditor; +class LLPanelExperiences; +class LLPanelRegionExperiences; +class LLPanelEstateAccess; +class LLPanelRegionEnvironment; + +class LLEventTimer; + +class LLFloaterRegionInfo : public LLFloater +{ + friend class LLFloaterReg; +public: + + + /*virtual*/ void onOpen(const LLSD& key); + /*virtual*/ void onClose(bool app_quitting); + /*virtual*/ bool postBuild(); + + static void processEstateOwnerRequest(LLMessageSystem* msg, void**); + + // get and process region info if necessary. + static void processRegionInfo(LLMessageSystem* msg); + + static const LLUUID& getLastInvoice() { return sRequestInvoice; } + static void nextInvoice() { sRequestInvoice.generate(); } + //static S32 getSerial() { return sRequestSerial; } + //static void incrementSerial() { sRequestSerial++; } + + static LLPanelEstateInfo* getPanelEstate(); + static LLPanelEstateAccess* getPanelAccess(); + static LLPanelEstateCovenant* getPanelCovenant(); + static LLPanelRegionTerrainInfo* getPanelRegionTerrain(); + static LLPanelRegionExperiences* getPanelExperiences(); + static LLPanelRegionGeneralInfo* getPanelGeneral(); + static LLPanelRegionEnvironment* getPanelEnvironment(); + + // from LLPanel + virtual void refresh(); + + void onRegionChanged(); + void requestRegionInfo(); + void enableTopButtons(); + void disableTopButtons(); + +private: + + LLFloaterRegionInfo(const LLSD& seed); + ~LLFloaterRegionInfo(); + +protected: + void onTabSelected(const LLSD& param); + void disableTabCtrls(); + void refreshFromRegion(LLViewerRegion* region); + void onGodLevelChange(U8 god_level); + + // member data + LLTabContainer* mTab; + typedef std::vector info_panels_t; + info_panels_t mInfoPanels; + LLPanelRegionEnvironment *mEnvironmentPanel; + //static S32 sRequestSerial; // serial # of last EstateOwnerRequest + static LLUUID sRequestInvoice; + +private: + LLAgent::god_level_change_slot_t mGodLevelChangeSlot; + boost::signals2::connection mRegionChangedCallback; +}; + + +// Base class for all region information panels. +class LLPanelRegionInfo : public LLPanel +{ +public: + LLPanelRegionInfo(); + + void onBtnSet(); + void onChangeChildCtrl(LLUICtrl* ctrl); + void onChangeAnything(); + static void onChangeText(LLLineEditor* caller, void* user_data); + + virtual bool refreshFromRegion(LLViewerRegion* region); + virtual bool estateUpdate(LLMessageSystem* msg) { return true; } + + virtual bool postBuild(); + virtual void updateChild(LLUICtrl* child_ctrl); + + void enableButton(const std::string& btn_name, bool enable = true); + void disableButton(const std::string& btn_name); + + void onClickManageTelehub(); + +protected: + void initCtrl(const std::string& name); + + // Returns true if update sent and apply button should be + // disabled. + virtual bool sendUpdate() { return true; } + + typedef std::vector strings_t; + //typedef std::vector integers_t; + void sendEstateOwnerMessage( + LLMessageSystem* msg, + const std::string& request, + const LLUUID& invoice, + const strings_t& strings); + + + // member data + LLHost mHost; +}; + +///////////////////////////////////////////////////////////////////////////// +// Actual panels start here +///////////////////////////////////////////////////////////////////////////// + +class LLPanelRegionGeneralInfo : public LLPanelRegionInfo +{ + +public: + LLPanelRegionGeneralInfo() + : LLPanelRegionInfo() {} + ~LLPanelRegionGeneralInfo() {} + + virtual bool refreshFromRegion(LLViewerRegion* region); + + // LLPanel + virtual bool postBuild(); + + void onBtnSet(); + void setObjBonusFactor(F32 object_bonus_factor) {mObjBonusFactor = object_bonus_factor;} + +protected: + virtual bool sendUpdate(); + void onClickKick(); + void onKickCommit(const uuid_vec_t& ids); + static void onClickKickAll(void* userdata); + bool onKickAllCommit(const LLSD& notification, const LLSD& response); + static void onClickMessage(void* userdata); + bool onMessageCommit(const LLSD& notification, const LLSD& response); + bool onChangeObjectBonus(const LLSD& notification, const LLSD& response); + + F32 mObjBonusFactor; + +}; + +///////////////////////////////////////////////////////////////////////////// + +class LLPanelRegionDebugInfo : public LLPanelRegionInfo +{ +public: + LLPanelRegionDebugInfo() + : LLPanelRegionInfo(), mTargetAvatar() {} + ~LLPanelRegionDebugInfo() {} + // LLPanel + virtual bool postBuild(); + + virtual bool refreshFromRegion(LLViewerRegion* region); + +protected: + virtual bool sendUpdate(); + + void onClickChooseAvatar(); + void callbackAvatarID(const uuid_vec_t& ids, const std::vector names); + static void onClickReturn(void *); + bool callbackReturn(const LLSD& notification, const LLSD& response); + static void onClickTopColliders(void*); + static void onClickTopScripts(void*); + static void onClickRestart(void* data); + bool callbackRestart(const LLSD& notification, const LLSD& response); + static void onClickCancelRestart(void* data); + static void onClickDebugConsole(void* data); + +private: + LLUUID mTargetAvatar; +}; + +///////////////////////////////////////////////////////////////////////////// + +class LLPanelRegionTerrainInfo : public LLPanelRegionInfo +{ + LOG_CLASS(LLPanelRegionTerrainInfo); + +public: + LLPanelRegionTerrainInfo() : LLPanelRegionInfo() {} + ~LLPanelRegionTerrainInfo() {} + + virtual bool postBuild(); // LLPanel + + virtual bool refreshFromRegion(LLViewerRegion* region); // refresh local settings from region update from simulator + void setEnvControls(bool available); // Whether environment settings are available for this region + + bool validateTextureSizes(); + bool validateTextureHeights(); + + //static void onChangeAnything(LLUICtrl* ctrl, void* userData); // callback for any change, to enable commit button + + virtual bool sendUpdate(); + + static void onClickDownloadRaw(void*); + static void onClickUploadRaw(void*); + static void onClickBakeTerrain(void*); + bool callbackBakeTerrain(const LLSD& notification, const LLSD& response); + bool callbackTextureHeights(const LLSD& notification, const LLSD& response); + +private: + bool mConfirmedTextureHeights; + bool mAskedTextureHeights; +}; + +///////////////////////////////////////////////////////////////////////////// + +class LLPanelEstateInfo : public LLPanelRegionInfo +{ +public: + static void initDispatch(LLDispatcher& dispatch); + + void onChangeFixedSun(); + void onChangeUseGlobalTime(); + void onChangeAccessOverride(); + + void onClickEditSky(); + void onClickEditSkyHelp(); + void onClickEditDayCycle(); + void onClickEditDayCycleHelp(); + + void onClickKickUser(); + + + bool kickUserConfirm(const LLSD& notification, const LLSD& response); + + void onKickUserCommit(const uuid_vec_t& ids); + static void onClickMessageEstate(void* data); + bool onMessageCommit(const LLSD& notification, const LLSD& response); + + LLPanelEstateInfo(); + ~LLPanelEstateInfo() {} + + void updateControls(LLViewerRegion* region); + + static void updateEstateName(const std::string& name); + static void updateEstateOwnerName(const std::string& name); + + virtual bool refreshFromRegion(LLViewerRegion* region); + virtual bool estateUpdate(LLMessageSystem* msg); + + // LLPanel + virtual bool postBuild(); + virtual void updateChild(LLUICtrl* child_ctrl); + virtual void refresh(); + + void refreshFromEstate(); + + static bool isLindenEstate(); + + const std::string getOwnerName() const; + void setOwnerName(const std::string& name); + +protected: + virtual bool sendUpdate(); + // confirmation dialog callback + bool callbackChangeLindenEstate(const LLSD& notification, const LLSD& response); + + void commitEstateAccess(); + void commitEstateManagers(); + + bool checkSunHourSlider(LLUICtrl* child_ctrl); + + U32 mEstateID; +}; + +///////////////////////////////////////////////////////////////////////////// + +class LLPanelEstateCovenant : public LLPanelRegionInfo +{ +public: + LLPanelEstateCovenant(); + ~LLPanelEstateCovenant() {} + + // LLPanel + virtual bool postBuild(); + virtual void updateChild(LLUICtrl* child_ctrl); + virtual bool refreshFromRegion(LLViewerRegion* region); + virtual bool estateUpdate(LLMessageSystem* msg); + + // LLView overrides + bool handleDragAndDrop(S32 x, S32 y, MASK mask, + bool drop, EDragAndDropType cargo_type, + void *cargo_data, EAcceptance *accept, + std::string& tooltip_msg); + static bool confirmChangeCovenantCallback(const LLSD& notification, const LLSD& response); + static void resetCovenantID(void* userdata); + static bool confirmResetCovenantCallback(const LLSD& notification, const LLSD& response); + void sendChangeCovenantID(const LLUUID &asset_id); + void loadInvItem(LLInventoryItem *itemp); + static void onLoadComplete(const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status); + + // Accessor functions + static void updateCovenantText(const std::string& string, const LLUUID& asset_id); + static void updateEstateName(const std::string& name); + static void updateLastModified(const std::string& text); + static void updateEstateOwnerName(const std::string& name); + + const LLUUID& getCovenantID() const { return mCovenantID; } + void setCovenantID(const LLUUID& id) { mCovenantID = id; } + std::string getEstateName() const; + void setEstateName(const std::string& name); + std::string getOwnerName() const; + void setOwnerName(const std::string& name); + void setCovenantTextEditor(const std::string& text); + + typedef enum e_asset_status + { + ASSET_ERROR, + ASSET_UNLOADED, + ASSET_LOADING, + ASSET_LOADED + } EAssetStatus; + +protected: + virtual bool sendUpdate(); + LLTextBox* mEstateNameText; + LLTextBox* mEstateOwnerText; + LLTextBox* mLastModifiedText; + // CovenantID from sim + LLUUID mCovenantID; + LLViewerTextEditor* mEditor; + EAssetStatus mAssetStatus; +}; + +///////////////////////////////////////////////////////////////////////////// + + +class LLPanelRegionExperiences : public LLPanelRegionInfo +{ + LOG_CLASS(LLPanelRegionExperiences); + +public: + LLPanelRegionExperiences(){} + /*virtual*/ bool postBuild(); + virtual bool sendUpdate(); + + static bool experienceCoreConfirm(const LLSD& notification, const LLSD& response); + static void sendEstateExperienceDelta(U32 flags, const LLUUID& agent_id); + + static void infoCallback(LLHandle handle, const LLSD& content); + bool refreshFromRegion(LLViewerRegion* region); + void sendPurchaseRequest()const; + void processResponse( const LLSD& content ); +private: + void refreshRegionExperiences(); + + static std::string regionCapabilityQuery(LLViewerRegion* region, const std::string &cap); + + LLPanelExperienceListEditor* setupList(const char* control_name, U32 add_id, U32 remove_id); + static LLSD addIds( LLPanelExperienceListEditor* panel ); + + void itemChanged(U32 event_type, const LLUUID& id); + + LLPanelExperienceListEditor* mTrusted; + LLPanelExperienceListEditor* mAllowed; + LLPanelExperienceListEditor* mBlocked; + LLUUID mDefaultExperience; +}; + + +class LLPanelEstateAccess : public LLPanelRegionInfo +{ + LOG_CLASS(LLPanelEstateAccess); + +public: + LLPanelEstateAccess(); + + virtual bool postBuild(); + virtual void updateChild(LLUICtrl* child_ctrl); + + void updateControls(LLViewerRegion* region); + void updateLists(); + + void setPendingUpdate(bool pending) { mPendingUpdate = pending; } + bool getPendingUpdate() { return mPendingUpdate; } + + virtual bool refreshFromRegion(LLViewerRegion* region); + +private: + void onClickAddAllowedAgent(); + void onClickRemoveAllowedAgent(); + void onClickCopyAllowedList(); + void onClickAddAllowedGroup(); + void onClickRemoveAllowedGroup(); + void onClickCopyAllowedGroupList(); + void onClickAddBannedAgent(); + void onClickRemoveBannedAgent(); + void onClickCopyBannedList(); + void onClickAddEstateManager(); + void onClickRemoveEstateManager(); + void onAllowedSearchEdit(const std::string& search_string); + void onAllowedGroupsSearchEdit(const std::string& search_string); + void onBannedSearchEdit(const std::string& search_string); + + // Group picker callback is different, can't use core methods below + bool addAllowedGroup(const LLSD& notification, const LLSD& response); + void addAllowedGroup2(LLUUID id); + + // Core methods for all above add/remove button clicks + static void accessAddCore(U32 operation_flag, const std::string& dialog_name); + static bool accessAddCore2(const LLSD& notification, const LLSD& response); + static void accessAddCore3(const uuid_vec_t& ids, std::vector names, void* data); + + static void accessRemoveCore(U32 operation_flag, const std::string& dialog_name, const std::string& list_ctrl_name); + static bool accessRemoveCore2(const LLSD& notification, const LLSD& response); + + // used for both add and remove operations + static bool accessCoreConfirm(const LLSD& notification, const LLSD& response); + + // Send the actual EstateOwnerRequest "estateaccessdelta" message + static void sendEstateAccessDelta(U32 flags, const LLUUID& agent_id); + + static void requestEstateGetAccessCoro(std::string url); + + void searchAgent(LLNameListCtrl* listCtrl, const std::string& search_string); + void copyListToClipboard(std::string list_name); + + bool mPendingUpdate; + bool mCtrlsEnabled; +}; + +#endif diff --git a/indra/newview/llfloaterregionrestarting.cpp b/indra/newview/llfloaterregionrestarting.cpp index d5c30f7521..6b795e5bc5 100644 --- a/indra/newview/llfloaterregionrestarting.cpp +++ b/indra/newview/llfloaterregionrestarting.cpp @@ -1,176 +1,176 @@ -/** - * @file llfloaterregionrestarting.cpp - * @brief Shows countdown timer during region restart - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterregionrestarting.h" - -#include "llfloaterreg.h" -#include "lluictrl.h" -#include "llagent.h" -#include "llagentcamera.h" -#include "llviewerwindow.h" - -static S32 sSeconds; -static U32 sShakeState; - -LLFloaterRegionRestarting::LLFloaterRegionRestarting(const LLSD& key) : - LLFloater(key), - LLEventTimer(1) -{ - mName = (std::string)key["NAME"]; - sSeconds = (LLSD::Integer)key["SECONDS"]; -} - -LLFloaterRegionRestarting::~LLFloaterRegionRestarting() -{ - mRegionChangedConnection.disconnect(); -} - -bool LLFloaterRegionRestarting::postBuild() -{ - mRegionChangedConnection = gAgent.addRegionChangedCallback(boost::bind(&LLFloaterRegionRestarting::regionChange, this)); - - LLStringUtil::format_map_t args; - std::string text; - - args["[NAME]"] = mName; - text = getString("RegionName", args); - LLTextBox* textbox = getChild("region_name"); - textbox->setValue(text); - - sShakeState = SHAKE_START; - - refresh(); - - return true; -} - -void LLFloaterRegionRestarting::regionChange() -{ - close(); -} - -bool LLFloaterRegionRestarting::tick() -{ - refresh(); - - return false; -} - -void LLFloaterRegionRestarting::refresh() -{ - LLStringUtil::format_map_t args; - std::string text; - - args["[SECONDS]"] = llformat("%d", sSeconds); - getChild("restart_seconds")->setValue(getString("RestartSeconds", args)); - - sSeconds = sSeconds - 1; - if(sSeconds < 0.0) - { - sSeconds = 0; - } -} - -void LLFloaterRegionRestarting::draw() -{ - LLFloater::draw(); - - const F32 SHAKE_INTERVAL = 0.025; - const F32 SHAKE_TOTAL_DURATION = 1.8; // the length of the default alert tone for this - const F32 SHAKE_INITIAL_MAGNITUDE = 1.5; - const F32 SHAKE_HORIZONTAL_BIAS = 0.25; - F32 time_shaking; - - if(SHAKE_START == sShakeState) - { - mShakeTimer.setTimerExpirySec(SHAKE_INTERVAL); - sShakeState = SHAKE_LEFT; - mShakeIterations = 0; - mShakeMagnitude = SHAKE_INITIAL_MAGNITUDE; - } - - if(SHAKE_DONE != sShakeState && mShakeTimer.hasExpired()) - { - gAgentCamera.unlockView(); - - switch(sShakeState) - { - case SHAKE_LEFT: - gAgentCamera.setPanLeftKey(mShakeMagnitude * SHAKE_HORIZONTAL_BIAS); - sShakeState = SHAKE_UP; - break; - - case SHAKE_UP: - gAgentCamera.setPanUpKey(mShakeMagnitude); - sShakeState = SHAKE_RIGHT; - break; - - case SHAKE_RIGHT: - gAgentCamera.setPanRightKey(mShakeMagnitude * SHAKE_HORIZONTAL_BIAS); - sShakeState = SHAKE_DOWN; - break; - - case SHAKE_DOWN: - gAgentCamera.setPanDownKey(mShakeMagnitude); - mShakeIterations++; - time_shaking = SHAKE_INTERVAL * (mShakeIterations * 4 /* left, up, right, down */); - if(SHAKE_TOTAL_DURATION <= time_shaking) - { - sShakeState = SHAKE_DONE; - mShakeMagnitude = 0.0; - } - else - { - sShakeState = SHAKE_LEFT; - F32 percent_done_shaking = (SHAKE_TOTAL_DURATION - time_shaking) / SHAKE_TOTAL_DURATION; - mShakeMagnitude = SHAKE_INITIAL_MAGNITUDE * (percent_done_shaking * percent_done_shaking); // exponential decay - } - break; - - default: - break; - } - mShakeTimer.setTimerExpirySec(SHAKE_INTERVAL); - } -} - -void LLFloaterRegionRestarting::close() -{ - LLFloaterRegionRestarting* floaterp = LLFloaterReg::findTypedInstance("region_restarting"); - - if (floaterp) - { - floaterp->closeFloater(); - } -} - -void LLFloaterRegionRestarting::updateTime(S32 time) -{ - sSeconds = time; - sShakeState = SHAKE_START; -} +/** + * @file llfloaterregionrestarting.cpp + * @brief Shows countdown timer during region restart + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterregionrestarting.h" + +#include "llfloaterreg.h" +#include "lluictrl.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llviewerwindow.h" + +static S32 sSeconds; +static U32 sShakeState; + +LLFloaterRegionRestarting::LLFloaterRegionRestarting(const LLSD& key) : + LLFloater(key), + LLEventTimer(1) +{ + mName = (std::string)key["NAME"]; + sSeconds = (LLSD::Integer)key["SECONDS"]; +} + +LLFloaterRegionRestarting::~LLFloaterRegionRestarting() +{ + mRegionChangedConnection.disconnect(); +} + +bool LLFloaterRegionRestarting::postBuild() +{ + mRegionChangedConnection = gAgent.addRegionChangedCallback(boost::bind(&LLFloaterRegionRestarting::regionChange, this)); + + LLStringUtil::format_map_t args; + std::string text; + + args["[NAME]"] = mName; + text = getString("RegionName", args); + LLTextBox* textbox = getChild("region_name"); + textbox->setValue(text); + + sShakeState = SHAKE_START; + + refresh(); + + return true; +} + +void LLFloaterRegionRestarting::regionChange() +{ + close(); +} + +bool LLFloaterRegionRestarting::tick() +{ + refresh(); + + return false; +} + +void LLFloaterRegionRestarting::refresh() +{ + LLStringUtil::format_map_t args; + std::string text; + + args["[SECONDS]"] = llformat("%d", sSeconds); + getChild("restart_seconds")->setValue(getString("RestartSeconds", args)); + + sSeconds = sSeconds - 1; + if(sSeconds < 0.0) + { + sSeconds = 0; + } +} + +void LLFloaterRegionRestarting::draw() +{ + LLFloater::draw(); + + const F32 SHAKE_INTERVAL = 0.025; + const F32 SHAKE_TOTAL_DURATION = 1.8; // the length of the default alert tone for this + const F32 SHAKE_INITIAL_MAGNITUDE = 1.5; + const F32 SHAKE_HORIZONTAL_BIAS = 0.25; + F32 time_shaking; + + if(SHAKE_START == sShakeState) + { + mShakeTimer.setTimerExpirySec(SHAKE_INTERVAL); + sShakeState = SHAKE_LEFT; + mShakeIterations = 0; + mShakeMagnitude = SHAKE_INITIAL_MAGNITUDE; + } + + if(SHAKE_DONE != sShakeState && mShakeTimer.hasExpired()) + { + gAgentCamera.unlockView(); + + switch(sShakeState) + { + case SHAKE_LEFT: + gAgentCamera.setPanLeftKey(mShakeMagnitude * SHAKE_HORIZONTAL_BIAS); + sShakeState = SHAKE_UP; + break; + + case SHAKE_UP: + gAgentCamera.setPanUpKey(mShakeMagnitude); + sShakeState = SHAKE_RIGHT; + break; + + case SHAKE_RIGHT: + gAgentCamera.setPanRightKey(mShakeMagnitude * SHAKE_HORIZONTAL_BIAS); + sShakeState = SHAKE_DOWN; + break; + + case SHAKE_DOWN: + gAgentCamera.setPanDownKey(mShakeMagnitude); + mShakeIterations++; + time_shaking = SHAKE_INTERVAL * (mShakeIterations * 4 /* left, up, right, down */); + if(SHAKE_TOTAL_DURATION <= time_shaking) + { + sShakeState = SHAKE_DONE; + mShakeMagnitude = 0.0; + } + else + { + sShakeState = SHAKE_LEFT; + F32 percent_done_shaking = (SHAKE_TOTAL_DURATION - time_shaking) / SHAKE_TOTAL_DURATION; + mShakeMagnitude = SHAKE_INITIAL_MAGNITUDE * (percent_done_shaking * percent_done_shaking); // exponential decay + } + break; + + default: + break; + } + mShakeTimer.setTimerExpirySec(SHAKE_INTERVAL); + } +} + +void LLFloaterRegionRestarting::close() +{ + LLFloaterRegionRestarting* floaterp = LLFloaterReg::findTypedInstance("region_restarting"); + + if (floaterp) + { + floaterp->closeFloater(); + } +} + +void LLFloaterRegionRestarting::updateTime(S32 time) +{ + sSeconds = time; + sShakeState = SHAKE_START; +} diff --git a/indra/newview/llfloaterregionrestarting.h b/indra/newview/llfloaterregionrestarting.h index edcfa4a050..ee836dd00d 100644 --- a/indra/newview/llfloaterregionrestarting.h +++ b/indra/newview/llfloaterregionrestarting.h @@ -1,69 +1,69 @@ -/** - * @file llfloaterregionrestarting.h - * @brief Shows countdown timer during region restart - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERREGIONRESTARTING_H -#define LL_LLFLOATERREGIONRESTARTING_H - -#include "llfloater.h" -#include "lltextbox.h" -#include "lleventtimer.h" - -class LLFloaterRegionRestarting : public LLFloater, public LLEventTimer -{ - friend class LLFloaterReg; - -public: - static void close(); - static void updateTime(S32 time); - -private: - LLFloaterRegionRestarting(const LLSD& key); - virtual ~LLFloaterRegionRestarting(); - virtual bool postBuild(); - virtual bool tick(); - virtual void refresh(); - virtual void draw(); - virtual void regionChange(); - - std::string mName; - U32 mShakeIterations; - F32 mShakeMagnitude; - LLTimer mShakeTimer; - - boost::signals2::connection mRegionChangedConnection; - - enum - { - SHAKE_START, - SHAKE_LEFT, - SHAKE_UP, - SHAKE_RIGHT, - SHAKE_DOWN, - SHAKE_DONE - }; -}; - -#endif // LL_LLFLOATERREGIONRESTARTING_H +/** + * @file llfloaterregionrestarting.h + * @brief Shows countdown timer during region restart + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERREGIONRESTARTING_H +#define LL_LLFLOATERREGIONRESTARTING_H + +#include "llfloater.h" +#include "lltextbox.h" +#include "lleventtimer.h" + +class LLFloaterRegionRestarting : public LLFloater, public LLEventTimer +{ + friend class LLFloaterReg; + +public: + static void close(); + static void updateTime(S32 time); + +private: + LLFloaterRegionRestarting(const LLSD& key); + virtual ~LLFloaterRegionRestarting(); + virtual bool postBuild(); + virtual bool tick(); + virtual void refresh(); + virtual void draw(); + virtual void regionChange(); + + std::string mName; + U32 mShakeIterations; + F32 mShakeMagnitude; + LLTimer mShakeTimer; + + boost::signals2::connection mRegionChangedConnection; + + enum + { + SHAKE_START, + SHAKE_LEFT, + SHAKE_UP, + SHAKE_RIGHT, + SHAKE_DOWN, + SHAKE_DONE + }; +}; + +#endif // LL_LLFLOATERREGIONRESTARTING_H diff --git a/indra/newview/llfloaterreporter.cpp b/indra/newview/llfloaterreporter.cpp index 8e58381751..e2c6305f4f 100644 --- a/indra/newview/llfloaterreporter.cpp +++ b/indra/newview/llfloaterreporter.cpp @@ -1,1045 +1,1045 @@ -/** - * @file llfloaterreporter.cpp - * @brief Abuse reports. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -// self include -#include "llfloaterreporter.h" - -#include - -// linden library includes -#include "llassetstorage.h" -#include "llavatarnamecache.h" -#include "llcachename.h" -#include "llcallbacklist.h" -#include "llcheckboxctrl.h" -#include "llfontgl.h" -#include "llimagepng.h" -#include "llimagej2c.h" -#include "llinventory.h" -#include "llnotificationsutil.h" -#include "llstring.h" -#include "llsys.h" -#include "llfilesystem.h" -#include "mean_collision_data.h" -#include "message.h" -#include "v3math.h" - -// viewer project includes -#include "llagent.h" -#include "llbutton.h" -#include "llfloaterreg.h" -#include "lltexturectrl.h" -#include "lltexteditor.h" -#include "llscrolllistctrl.h" -#include "lldispatcher.h" -#include "llviewerobject.h" -#include "llviewerregion.h" -#include "llcombobox.h" -#include "lltooldraganddrop.h" -#include "lluiconstants.h" -#include "lluploaddialog.h" -#include "llcallingcard.h" -#include "llviewerobjectlist.h" -#include "lltoolobjpicker.h" -#include "lltoolmgr.h" -#include "llresourcedata.h" // for LLResourceData -#include "llslurl.h" -#include "llviewerwindow.h" -#include "llviewertexturelist.h" -#include "llworldmap.h" -#include "llfilepicker.h" -#include "llfloateravatarpicker.h" -#include "lldir.h" -#include "llselectmgr.h" -#include "llversioninfo.h" -#include "lluictrlfactory.h" -#include "llviewercontrol.h" -#include "llviewernetwork.h" - -#include "llagentui.h" - -#include "lltrans.h" -#include "llexperiencecache.h" - -#include "llcorehttputil.h" -#include "llviewerassetupload.h" - -const std::string SCREEN_PREV_FILENAME = "screen_report_last.png"; - -//========================================================================= -//----------------------------------------------------------------------------- -// Support classes -//----------------------------------------------------------------------------- -class LLARScreenShotUploader : public LLResourceUploadInfo -{ -public: - LLARScreenShotUploader(LLSD report, LLUUID assetId, LLAssetType::EType assetType); - - virtual LLSD prepareUpload(); - virtual LLSD generatePostBody(); - virtual LLUUID finishUpload(LLSD &result); - - virtual bool showInventoryPanel() const { return false; } - virtual std::string getDisplayName() const { return "Abuse Report"; } - -private: - - LLSD mReport; -}; - -LLARScreenShotUploader::LLARScreenShotUploader(LLSD report, LLUUID assetId, LLAssetType::EType assetType) : - LLResourceUploadInfo(assetId, assetType, "Abuse Report"), - mReport(report) -{ -} - -LLSD LLARScreenShotUploader::prepareUpload() -{ - return LLSD().with("success", LLSD::Boolean(true)); -} - -LLSD LLARScreenShotUploader::generatePostBody() -{ // The report was pregenerated and passed in the constructor. - return mReport; -} - -LLUUID LLARScreenShotUploader::finishUpload(LLSD &result) -{ - /* *TODO$: Report success or failure. Carried over from previous todo on responder*/ - return LLUUID::null; -} - - -//========================================================================= -//----------------------------------------------------------------------------- -// Globals -//----------------------------------------------------------------------------- - -//----------------------------------------------------------------------------- -// Member functions -//----------------------------------------------------------------------------- - -LLFloaterReporter::LLFloaterReporter(const LLSD& key) -: LLFloater(key), - mReportType(COMPLAINT_REPORT), - mObjectID(), - mScreenID(), - mAbuserID(), - mOwnerName(), - mDeselectOnClose( false ), - mPicking( false), - mPosition(), - mCopyrightWarningSeen( false ), - mResourceDatap(new LLResourceData()), - mAvatarNameCacheConnection() -{ - gIdleCallbacks.addFunction(onIdle, this); -} - -// virtual -bool LLFloaterReporter::postBuild() -{ - LLSLURL slurl; - LLAgentUI::buildSLURL(slurl); - getChild("abuse_location_edit")->setValue(slurl.getSLURLString()); - - enableControls(true); - - // convert the position to a string - LLVector3d pos = gAgent.getPositionGlobal(); - LLViewerRegion *regionp = gAgent.getRegion(); - if (regionp) - { - getChild("sim_field")->setValue(regionp->getName()); - pos -= regionp->getOriginGlobal(); - } - setPosBox(pos); - - // Default text to be blank - getChild("object_name")->setValue(LLStringUtil::null); - getChild("owner_name")->setValue(LLStringUtil::null); - mOwnerName = LLStringUtil::null; - - getChild("summary_edit")->setFocus(true); - - mDefaultSummary = getChild("details_edit")->getValue().asString(); - - // abuser name is selected from a list - LLUICtrl* le = getChild("abuser_name_edit"); - le->setEnabled( false ); - - setPosBox((LLVector3d)mPosition.getValue()); - LLButton* pick_btn = getChild("pick_btn"); - pick_btn->setImages(std::string("tool_face.tga"), - std::string("tool_face_active.tga") ); - childSetAction("pick_btn", onClickObjPicker, this); - - childSetAction("select_abuser", boost::bind(&LLFloaterReporter::onClickSelectAbuser, this)); - - childSetAction("send_btn", onClickSend, this); - childSetAction("cancel_btn", onClickCancel, this); - - // grab the user's name - std::string reporter = LLSLURL("agent", gAgent.getID(), "inspect").getSLURLString(); - getChild("reporter_field")->setValue(reporter); - - // request categories - if (gAgent.getRegion() - && gAgent.getRegion()->capabilitiesReceived()) - { - std::string cap_url = gAgent.getRegionCapability("AbuseCategories"); - - if (!cap_url.empty()) - { - std::string lang = gSavedSettings.getString("Language"); - if (lang != "default" && !lang.empty()) - { - cap_url += "?lc="; - cap_url += lang; - } - LLCoros::instance().launch("LLFloaterReporter::requestAbuseCategoriesCoro", - boost::bind(LLFloaterReporter::requestAbuseCategoriesCoro, cap_url, this->getHandle())); - } - } - - center(); - - return true; -} - -// virtual -LLFloaterReporter::~LLFloaterReporter() -{ - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - gIdleCallbacks.deleteFunction(onIdle, this); - - // child views automatically deleted - mObjectID = LLUUID::null; - - if (mPicking) - { - closePickTool(this); - } - - mPosition.setVec(0.0f, 0.0f, 0.0f); - - delete mResourceDatap; -} - -void LLFloaterReporter::onIdle(void* user_data) -{ - LLFloaterReporter* floater_reporter = (LLFloaterReporter*)user_data; - if (floater_reporter) - { - static LLCachedControl screenshot_delay(gSavedSettings, "AbuseReportScreenshotDelay"); - if (floater_reporter->mSnapshotTimer.getStarted() && floater_reporter->mSnapshotTimer.getElapsedTimeF32() > screenshot_delay) - { - floater_reporter->mSnapshotTimer.stop(); - floater_reporter->takeNewSnapshot(); - } - } -} - -void LLFloaterReporter::enableControls(bool enable) -{ - getChildView("category_combo")->setEnabled(enable); - getChildView("chat_check")->setEnabled(enable); - getChildView("screenshot")->setEnabled(false); - getChildView("pick_btn")->setEnabled(enable); - getChildView("summary_edit")->setEnabled(enable); - getChildView("details_edit")->setEnabled(enable); - getChildView("send_btn")->setEnabled(enable); - getChildView("cancel_btn")->setEnabled(enable); -} - -void LLFloaterReporter::getExperienceInfo(const LLUUID& experience_id) -{ - mExperienceID = experience_id; - - if (LLUUID::null != mExperienceID) - { - const LLSD& experience = LLExperienceCache::instance().get(mExperienceID); - std::stringstream desc; - - if(experience.isDefined()) - { - setFromAvatarID(experience[LLExperienceCache::AGENT_ID]); - desc << "Experience id: " << mExperienceID; - } - else - { - desc << "Unable to retrieve details for id: "<< mExperienceID; - } - - LLUICtrl* details = getChild("details_edit"); - details->setValue(desc.str()); - } -} - -void LLFloaterReporter::getObjectInfo(const LLUUID& object_id) -{ - // TODO -- - // 1 need to send to correct simulator if object is not - // in same simulator as agent - // 2 display info in widget window that gives feedback that - // we have recorded the object info - // 3 can pick avatar ==> might want to indicate when a picked - // object is an avatar, attachment, or other category - - mObjectID = object_id; - - if (LLUUID::null != mObjectID) - { - // get object info for the user's benefit - LLViewerObject* objectp = NULL; - objectp = gObjectList.findObject( mObjectID ); - if (objectp) - { - if ( objectp->isAttachment() ) - { - objectp = (LLViewerObject*)objectp->getRoot(); - mObjectID = objectp->getID(); - } - - // correct the region and position information - LLViewerRegion *regionp = objectp->getRegion(); - if (regionp) - { - getChild("sim_field")->setValue(regionp->getName()); - LLVector3d global_pos; - global_pos.setVec(objectp->getPositionRegion()); - setPosBox(global_pos); - } - - if (objectp->isAvatar()) - { - setFromAvatarID(mObjectID); - } - else - { - // we have to query the simulator for information - // about this object - LLMessageSystem* msg = gMessageSystem; - U32 request_flags = COMPLAINT_REPORT_REQUEST; - msg->newMessageFast(_PREHASH_RequestObjectPropertiesFamily); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ObjectData); - msg->addU32Fast(_PREHASH_RequestFlags, request_flags ); - msg->addUUIDFast(_PREHASH_ObjectID, mObjectID); - LLViewerRegion* regionp = objectp->getRegion(); - msg->sendReliable( regionp->getHost() ); - } - } - } -} - -void LLFloaterReporter::onClickSelectAbuser() -{ - LLView * button = findChild("select_abuser", true); - - LLFloater * root_floater = gFloaterView->getParentFloater(this); - LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(boost::bind(&LLFloaterReporter::callbackAvatarID, this, _1, _2), false, true, false, root_floater->getName(), button); - if (picker) - { - root_floater->addDependentFloater(picker); - } -} - -void LLFloaterReporter::callbackAvatarID(const uuid_vec_t& ids, const std::vector names) -{ - if (ids.empty() || names.empty()) return; - - getChild("abuser_name_edit")->setValue(names[0].getCompleteName()); - - mAbuserID = ids[0]; - - refresh(); - -} - -void LLFloaterReporter::setFromAvatarID(const LLUUID& avatar_id) -{ - mAbuserID = mObjectID = avatar_id; - std::string avatar_link = LLSLURL("agent", mObjectID, "inspect").getSLURLString(); - getChild("owner_name")->setValue(avatar_link); - - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - mAvatarNameCacheConnection = LLAvatarNameCache::get(avatar_id, boost::bind(&LLFloaterReporter::onAvatarNameCache, this, _1, _2)); -} - -void LLFloaterReporter::onAvatarNameCache(const LLUUID& avatar_id, const LLAvatarName& av_name) -{ - mAvatarNameCacheConnection.disconnect(); - - if (mObjectID == avatar_id) - { - mOwnerName = av_name.getCompleteName(); - getChild("object_name")->setValue(av_name.getCompleteName()); - getChild("object_name")->setToolTip(av_name.getCompleteName()); - getChild("abuser_name_edit")->setValue(av_name.getCompleteName()); - } -} - -void LLFloaterReporter::requestAbuseCategoriesCoro(std::string url, LLHandle handle) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("requestAbuseCategoriesCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD result = httpAdapter->getAndSuspend(httpRequest, url); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status || !result.has("categories")) // success = httpResults["success"].asBoolean(); - { - LL_WARNS() << "Error requesting Abuse Categories from capability: " << url << LL_ENDL; - return; - } - - if (handle.isDead()) - { - // nothing to do - return; - } - - LLFloater* floater = handle.get(); - LLComboBox* combo = floater->getChild("category_combo"); - if (!combo) - { - LL_WARNS() << "categories category_combo not found!" << LL_ENDL; - return; - } - - //get selection (in case capability took a while) - S32 selection = combo->getCurrentIndex(); - - // Combobox should have a "Select category" element; - // This is a bit of workaround since there is no proper and simple way to save array of - // localizable strings in xml along with data (value). For now combobox is initialized along - // with placeholders, and first element is "Select category" which we want to keep, so remove - // everything but first element. - // Todo: once sim with capability fully releases, just remove this string and all unnecessary - // items from combobox since they will be obsolete (or depending on situation remake this to - // something better, for example move "Select category" to separate string) - while (combo->remove(1)); - - LLSD contents = result["categories"]; - - LLSD::array_iterator i = contents.beginArray(); - LLSD::array_iterator iEnd = contents.endArray(); - for (; i != iEnd; ++i) - { - const LLSD &message_data(*i); - std::string label = message_data["description_localized"]; - combo->add(label, message_data["category"]); - } - - //restore selection - combo->selectNthItem(selection); -} - -// static -void LLFloaterReporter::onClickSend(void *userdata) -{ - LLFloaterReporter *self = (LLFloaterReporter *)userdata; - - if (self->mPicking) - { - closePickTool(self); - } - - if(self->validateReport()) - { - - const int IP_CONTENT_REMOVAL = 66; - const int IP_PERMISSONS_EXPLOIT = 37; - LLComboBox* combo = self->getChild( "category_combo"); - int category_value = combo->getSelectedValue().asInteger(); - - if ( ! self->mCopyrightWarningSeen ) - { - - std::string details_lc = self->getChild("details_edit")->getValue().asString(); - LLStringUtil::toLower( details_lc ); - std::string summary_lc = self->getChild("summary_edit")->getValue().asString(); - LLStringUtil::toLower( summary_lc ); - if ( details_lc.find( "copyright" ) != std::string::npos || - summary_lc.find( "copyright" ) != std::string::npos || - category_value == IP_CONTENT_REMOVAL || - category_value == IP_PERMISSONS_EXPLOIT) - { - LLNotificationsUtil::add("HelpReportAbuseContainsCopyright"); - self->mCopyrightWarningSeen = true; - return; - } - } - else if (category_value == IP_CONTENT_REMOVAL) - { - // IP_CONTENT_REMOVAL *always* shows the dialog - - // ergo you can never send that abuse report type. - LLNotificationsUtil::add("HelpReportAbuseContainsCopyright"); - return; - } - - LLUploadDialog::modalUploadDialog(LLTrans::getString("uploading_abuse_report")); - // *TODO don't upload image if checkbox isn't checked - std::string url = gAgent.getRegionCapability("SendUserReport"); - std::string sshot_url = gAgent.getRegionCapability("SendUserReportWithScreenshot"); - if(!url.empty() || !sshot_url.empty()) - { - self->sendReportViaCaps(url, sshot_url, self->gatherReport()); - LLNotificationsUtil::add("HelpReportAbuseConfirm"); - self->closeFloater(); - } - else - { - self->getChildView("send_btn")->setEnabled(false); - self->getChildView("cancel_btn")->setEnabled(false); - // the callback from uploading the image calls sendReportViaLegacy() - self->uploadImage(); - } - } -} - - -// static -void LLFloaterReporter::onClickCancel(void *userdata) -{ - LLFloaterReporter *self = (LLFloaterReporter *)userdata; - - // reset flag in case the next report also contains this text - self->mCopyrightWarningSeen = false; - - if (self->mPicking) - { - closePickTool(self); - } - self->closeFloater(); -} - - -// static -void LLFloaterReporter::onClickObjPicker(void *userdata) -{ - LLFloaterReporter *self = (LLFloaterReporter *)userdata; - LLToolObjPicker::getInstance()->setExitCallback(LLFloaterReporter::closePickTool, self); - LLToolMgr::getInstance()->setTransientTool(LLToolObjPicker::getInstance()); - self->mPicking = true; - self->getChild("object_name")->setValue(LLStringUtil::null); - self->getChild("owner_name")->setValue(LLStringUtil::null); - self->mOwnerName = LLStringUtil::null; - LLButton* pick_btn = self->getChild("pick_btn"); - if (pick_btn) pick_btn->setToggleState(true); -} - - -// static -void LLFloaterReporter::closePickTool(void *userdata) -{ - LLFloaterReporter *self = (LLFloaterReporter *)userdata; - - LLUUID object_id = LLToolObjPicker::getInstance()->getObjectID(); - self->getObjectInfo(object_id); - - LLToolMgr::getInstance()->clearTransientTool(); - self->mPicking = false; - LLButton* pick_btn = self->getChild("pick_btn"); - if (pick_btn) pick_btn->setToggleState(false); -} - - -// static -void LLFloaterReporter::showFromMenu(EReportType report_type) -{ - if (COMPLAINT_REPORT != report_type) - { - LL_WARNS() << "Unknown LLViewerReporter type : " << report_type << LL_ENDL; - return; - } - LLFloaterReporter* reporter_floater = LLFloaterReg::findTypedInstance("reporter"); - if(reporter_floater && reporter_floater->isInVisibleChain()) - { - gSavedPerAccountSettings.setBOOL("PreviousScreenshotForReport", false); - } - reporter_floater = LLFloaterReg::showTypedInstance("reporter", LLSD()); - if (reporter_floater) - { - reporter_floater->setReportType(report_type); - } -} - -// static -void LLFloaterReporter::show(const LLUUID& object_id, const std::string& avatar_name, const LLUUID& experience_id) -{ - LLFloaterReporter* reporter_floater = LLFloaterReg::findTypedInstance("reporter"); - if(reporter_floater && reporter_floater->isInVisibleChain()) - { - gSavedPerAccountSettings.setBOOL("PreviousScreenshotForReport", false); - } - reporter_floater = LLFloaterReg::showTypedInstance("reporter"); - if (avatar_name.empty()) - { - // Request info for this object - reporter_floater->getObjectInfo(object_id); - } - else - { - reporter_floater->setFromAvatarID(object_id); - } - if(experience_id.notNull()) - { - reporter_floater->getExperienceInfo(experience_id); - } - - // Need to deselect on close - reporter_floater->mDeselectOnClose = true; -} - - - -void LLFloaterReporter::showFromExperience( const LLUUID& experience_id ) -{ - LLFloaterReporter* reporter_floater = LLFloaterReg::findTypedInstance("reporter"); - if(reporter_floater && reporter_floater->isInVisibleChain()) - { - gSavedPerAccountSettings.setBOOL("PreviousScreenshotForReport", false); - } - reporter_floater = LLFloaterReg::showTypedInstance("reporter"); - reporter_floater->getExperienceInfo(experience_id); - - // Need to deselect on close - reporter_floater->mDeselectOnClose = true; -} - - -// static -void LLFloaterReporter::showFromObject(const LLUUID& object_id, const LLUUID& experience_id) -{ - show(object_id, LLStringUtil::null, experience_id); -} - -// static -void LLFloaterReporter::showFromAvatar(const LLUUID& avatar_id, const std::string avatar_name) -{ - show(avatar_id, avatar_name); -} - -// static -void LLFloaterReporter::showFromChat(const LLUUID& avatar_id, const std::string& avatar_name, const std::string& time, const std::string& description) -{ - show(avatar_id, avatar_name); - - LLStringUtil::format_map_t args; - args["[MSG_TIME]"] = time; - args["[MSG_DESCRIPTION]"] = description; - - LLFloaterReporter *self = LLFloaterReg::findTypedInstance("reporter"); - if (self) - { - std::string description = self->getString("chat_report_format", args); - self->getChild("details_edit")->setValue(description); - } -} - -void LLFloaterReporter::setPickedObjectProperties(const std::string& object_name, const std::string& owner_name, const LLUUID owner_id) -{ - getChild("object_name")->setValue(object_name); - std::string owner_link = - LLSLURL("agent", owner_id, "inspect").getSLURLString(); - getChild("owner_name")->setValue(owner_link); - getChild("abuser_name_edit")->setValue(owner_name); - mAbuserID = owner_id; - mOwnerName = owner_name; -} - - -bool LLFloaterReporter::validateReport() -{ - // Ensure user selected a category from the list - LLSD category_sd = getChild("category_combo")->getValue(); - U8 category = (U8)category_sd.asInteger(); - if (category == 0) - { - LLNotificationsUtil::add("HelpReportAbuseSelectCategory"); - return false; - } - - - if ( getChild("abuser_name_edit")->getValue().asString().empty() ) - { - LLNotificationsUtil::add("HelpReportAbuseAbuserNameEmpty"); - return false; - }; - - if ( getChild("abuse_location_edit")->getValue().asString().empty() ) - { - LLNotificationsUtil::add("HelpReportAbuseAbuserLocationEmpty"); - return false; - }; - - if ( getChild("abuse_location_edit")->getValue().asString().empty() ) - { - LLNotificationsUtil::add("HelpReportAbuseAbuserLocationEmpty"); - return false; - }; - - - if ( getChild("summary_edit")->getValue().asString().empty() ) - { - LLNotificationsUtil::add("HelpReportAbuseSummaryEmpty"); - return false; - }; - - if ( getChild("details_edit")->getValue().asString() == mDefaultSummary ) - { - LLNotificationsUtil::add("HelpReportAbuseDetailsEmpty"); - return false; - }; - return true; -} - -LLSD LLFloaterReporter::gatherReport() -{ - LLViewerRegion *regionp = gAgent.getRegion(); - if (!regionp) return LLSD(); // *TODO handle this failure case more gracefully - - // reset flag in case the next report also contains this text - mCopyrightWarningSeen = false; - - std::ostringstream summary; - if (!LLGridManager::getInstance()->isInProductionGrid()) - { - summary << "Preview "; - } - - std::string category_name; - LLComboBox* combo = getChild( "category_combo"); - if (combo) - { - category_name = combo->getSelectedItemLabel(); // want label, not value - } - -#if LL_WINDOWS - const char* platform = "Win"; -#elif LL_DARWIN - const char* platform = "Mac"; -#elif LL_LINUX - const char* platform = "Lnx"; -#else - const char* platform = "???"; -#endif - - - - summary << "" - << " |" << regionp->getName() << "|" // region reporter is currently in. - << " (" << getChild("abuse_location_edit")->getValue().asString() << ")" // region abuse occured in (freeform text - no LLRegionPicker tool) - << " [" << category_name << "] " // updated category - << " {" << getChild("abuser_name_edit")->getValue().asString() << "} " // name of abuse entered in report (chosen using LLAvatarPicker) - << " \"" << getChild("summary_edit")->getValue().asString() << "\""; // summary as entered - - - std::ostringstream details; - - details << "V" << LLVersionInfo::instance().getVersion() << std::endl << std::endl; // client version moved to body of email for abuse reports - - std::string object_name = getChild("object_name")->getValue().asString(); - if (!object_name.empty() && !mOwnerName.empty()) - { - details << "Object: " << object_name << "\n"; - details << "Owner: " << mOwnerName << "\n"; - } - - - details << "Abuser name: " << getChild("abuser_name_edit")->getValue().asString() << " \n"; - details << "Abuser location: " << getChild("abuse_location_edit")->getValue().asString() << " \n"; - - details << getChild("details_edit")->getValue().asString(); - - std::string version_string; - version_string = llformat( - "%s %s %s %s %s", - LLVersionInfo::instance().getShortVersion().c_str(), - platform, - gSysCPU.getFamily().c_str(), - gGLManager.mGLRenderer.c_str(), - gGLManager.mDriverVersionVendorString.c_str()); - - // only send a screenshot ID if we're asked to and the email is - // going to LL - Estate Owners cannot see the screenshot asset - LLUUID screenshot_id = LLUUID::null; - screenshot_id = getChild("screenshot")->getValue(); - - LLSD report = LLSD::emptyMap(); - report["report-type"] = (U8) mReportType; - report["category"] = getChild("category_combo")->getValue(); - report["position"] = mPosition.getValue(); - report["check-flags"] = (U8)0; // this is not used - report["screenshot-id"] = screenshot_id; - report["object-id"] = mObjectID; - report["abuser-id"] = mAbuserID; - report["abuse-region-name"] = ""; - report["abuse-region-id"] = LLUUID::null; - report["summary"] = summary.str(); - report["version-string"] = version_string; - report["details"] = details.str(); - return report; -} - -void LLFloaterReporter::sendReportViaLegacy(const LLSD & report) -{ - LLViewerRegion *regionp = gAgent.getRegion(); - if (!regionp) return; - LLMessageSystem *msg = gMessageSystem; - msg->newMessageFast(_PREHASH_UserReport); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - - msg->nextBlockFast(_PREHASH_ReportData); - msg->addU8Fast(_PREHASH_ReportType, report["report-type"].asInteger()); - msg->addU8(_PREHASH_Category, report["category"].asInteger()); - msg->addVector3Fast(_PREHASH_Position, LLVector3(report["position"])); - msg->addU8Fast(_PREHASH_CheckFlags, report["check-flags"].asInteger()); - msg->addUUIDFast(_PREHASH_ScreenshotID, report["screenshot-id"].asUUID()); - msg->addUUIDFast(_PREHASH_ObjectID, report["object-id"].asUUID()); - msg->addUUID("AbuserID", report["abuser-id"].asUUID()); - msg->addString("AbuseRegionName", report["abuse-region-name"].asString()); - msg->addUUID("AbuseRegionID", report["abuse-region-id"].asUUID()); - - msg->addStringFast(_PREHASH_Summary, report["summary"].asString()); - msg->addString("VersionString", report["version-string"]); - msg->addStringFast(_PREHASH_Details, report["details"] ); - - msg->sendReliable(regionp->getHost()); -} - -void LLFloaterReporter::finishedARPost(const LLSD &) -{ - LLUploadDialog::modalUploadFinished(); - -} - -void LLFloaterReporter::sendReportViaCaps(std::string url, std::string sshot_url, const LLSD& report) -{ - if(!sshot_url.empty()) - { - // try to upload screenshot - LLResourceUploadInfo::ptr_t uploadInfo(new LLARScreenShotUploader(report, mResourceDatap->mAssetInfo.mUuid, mResourceDatap->mAssetInfo.mType)); - LLViewerAssetUpload::EnqueueInventoryUpload(sshot_url, uploadInfo); - } - else - { - LLCoreHttpUtil::HttpCoroutineAdapter::completionCallback_t proc = boost::bind(&LLFloaterReporter::finishedARPost, _1); - LLUploadDialog::modalUploadDialog("Abuse Report"); - LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost(url, report, proc, proc); - } -} - -void LLFloaterReporter::takeScreenshot(bool use_prev_screenshot) -{ - gSavedPerAccountSettings.setBOOL("PreviousScreenshotForReport", true); - if(!use_prev_screenshot) - { - std::string screenshot_filename(gDirUtilp->getLindenUserDir() + gDirUtilp->getDirDelimiter() + SCREEN_PREV_FILENAME); - LLPointer png_image = new LLImagePNG; - if(png_image->encode(mImageRaw, 0.0f)) - { - png_image->save(screenshot_filename); - } - } - else - { - mImageRaw = mPrevImageRaw; - } - - LLPointer upload_data = LLViewerTextureList::convertToUploadFile(mImageRaw); - - // create a resource data - mResourceDatap->mInventoryType = LLInventoryType::IT_NONE; - mResourceDatap->mNextOwnerPerm = 0; // not used - mResourceDatap->mExpectedUploadCost = 0; // we expect that abuse screenshots are free - mResourceDatap->mAssetInfo.mTransactionID.generate(); - mResourceDatap->mAssetInfo.mUuid = mResourceDatap->mAssetInfo.mTransactionID.makeAssetID(gAgent.getSecureSessionID()); - - if (COMPLAINT_REPORT == mReportType) - { - mResourceDatap->mAssetInfo.mType = LLAssetType::AT_TEXTURE; - mResourceDatap->mPreferredLocation = LLFolderType::EType(LLResourceData::INVALID_LOCATION); - } - else - { - LL_WARNS() << "Unknown LLFloaterReporter type" << LL_ENDL; - } - mResourceDatap->mAssetInfo.mCreatorID = gAgentID; - mResourceDatap->mAssetInfo.setName("screenshot_name"); - mResourceDatap->mAssetInfo.setDescription("screenshot_descr"); - - // store in cache - LLFileSystem j2c_file(mResourceDatap->mAssetInfo.mUuid, mResourceDatap->mAssetInfo.mType, LLFileSystem::WRITE); - j2c_file.write(upload_data->getData(), upload_data->getDataSize()); - - // store in the image list so it doesn't try to fetch from the server - LLPointer image_in_list = - LLViewerTextureManager::getFetchedTexture(mResourceDatap->mAssetInfo.mUuid); - image_in_list->createGLTexture(0, mImageRaw, 0, true, LLGLTexture::OTHER); - - // the texture picker then uses that texture - LLTextureCtrl* texture = getChild("screenshot"); - if (texture) - { - texture->setImageAssetID(mResourceDatap->mAssetInfo.mUuid); - texture->setDefaultImageAssetID(mResourceDatap->mAssetInfo.mUuid); - texture->setCaption(getString("Screenshot")); - } -} - -void LLFloaterReporter::takeNewSnapshot() -{ - childSetEnabled("send_btn", true); - mImageRaw = new LLImageRaw; - const S32 IMAGE_WIDTH = 1024; - const S32 IMAGE_HEIGHT = 768; - - // Take a screenshot, but don't draw this floater. - setVisible(false); - if (!gViewerWindow->rawSnapshot(mImageRaw,IMAGE_WIDTH, IMAGE_HEIGHT, true, false, true /*UI*/, true, false)) - { - LL_WARNS() << "Unable to take screenshot" << LL_ENDL; - setVisible(true); - return; - } - setVisible(true); - - if(gSavedPerAccountSettings.getBOOL("PreviousScreenshotForReport")) - { - std::string screenshot_filename(gDirUtilp->getLindenUserDir() + gDirUtilp->getDirDelimiter() + SCREEN_PREV_FILENAME); - mPrevImageRaw = new LLImageRaw; - LLPointer start_image_png = new LLImagePNG; - if(start_image_png->load(screenshot_filename)) - { - if (start_image_png->decode(mPrevImageRaw, 0.0f)) - { - LLNotificationsUtil::add("LoadPreviousReportScreenshot", LLSD(), LLSD(), boost::bind(&LLFloaterReporter::onLoadScreenshotDialog,this, _1, _2)); - return; - } - } - } - takeScreenshot(); -} - - -void LLFloaterReporter::onOpen(const LLSD& key) -{ - childSetEnabled("send_btn", false); - //Time delay to avoid UI artifacts. MAINT-7067 - mSnapshotTimer.start(); -} - -void LLFloaterReporter::onLoadScreenshotDialog(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - takeScreenshot(option == 0); -} - -void LLFloaterReporter::uploadImage() -{ - LL_INFOS() << "*** Uploading: " << LL_ENDL; - LL_INFOS() << "Type: " << LLAssetType::lookup(mResourceDatap->mAssetInfo.mType) << LL_ENDL; - LL_INFOS() << "UUID: " << mResourceDatap->mAssetInfo.mUuid << LL_ENDL; - LL_INFOS() << "Name: " << mResourceDatap->mAssetInfo.getName() << LL_ENDL; - LL_INFOS() << "Desc: " << mResourceDatap->mAssetInfo.getDescription() << LL_ENDL; - - gAssetStorage->storeAssetData(mResourceDatap->mAssetInfo.mTransactionID, - mResourceDatap->mAssetInfo.mType, - LLFloaterReporter::uploadDoneCallback, - (void*)mResourceDatap, true); -} - - -// static -void LLFloaterReporter::uploadDoneCallback(const LLUUID &uuid, void *user_data, S32 result, LLExtStat ext_status) // StoreAssetData callback (fixed) -{ - LLUploadDialog::modalUploadFinished(); - - LLResourceData* data = (LLResourceData*)user_data; - - if(result < 0) - { - LLSD args; - args["REASON"] = std::string(LLAssetStorage::getErrorString(result)); - LLNotificationsUtil::add("ErrorUploadingReportScreenshot", args); - - std::string err_msg("There was a problem uploading a report screenshot"); - err_msg += " due to the following reason: " + args["REASON"].asString(); - LL_WARNS() << err_msg << LL_ENDL; - return; - } - - if (data->mPreferredLocation != LLResourceData::INVALID_LOCATION) - { - LL_WARNS() << "Unknown report type : " << data->mPreferredLocation << LL_ENDL; - } - - LLFloaterReporter *self = LLFloaterReg::findTypedInstance("reporter"); - if (self) - { - self->mScreenID = uuid; - LL_INFOS() << "Got screen shot " << uuid << LL_ENDL; - self->sendReportViaLegacy(self->gatherReport()); - LLNotificationsUtil::add("HelpReportAbuseConfirm"); - self->closeFloater(); - } -} - - -void LLFloaterReporter::setPosBox(const LLVector3d &pos) -{ - mPosition.setVec(pos); - std::string pos_string = llformat("{%.1f, %.1f, %.1f}", - mPosition.mV[VX], - mPosition.mV[VY], - mPosition.mV[VZ]); - getChild("pos_field")->setValue(pos_string); -} - -void LLFloaterReporter::onClose(bool app_quitting) -{ - mSnapshotTimer.stop(); - gSavedPerAccountSettings.setBOOL("PreviousScreenshotForReport", app_quitting); -} +/** + * @file llfloaterreporter.cpp + * @brief Abuse reports. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +// self include +#include "llfloaterreporter.h" + +#include + +// linden library includes +#include "llassetstorage.h" +#include "llavatarnamecache.h" +#include "llcachename.h" +#include "llcallbacklist.h" +#include "llcheckboxctrl.h" +#include "llfontgl.h" +#include "llimagepng.h" +#include "llimagej2c.h" +#include "llinventory.h" +#include "llnotificationsutil.h" +#include "llstring.h" +#include "llsys.h" +#include "llfilesystem.h" +#include "mean_collision_data.h" +#include "message.h" +#include "v3math.h" + +// viewer project includes +#include "llagent.h" +#include "llbutton.h" +#include "llfloaterreg.h" +#include "lltexturectrl.h" +#include "lltexteditor.h" +#include "llscrolllistctrl.h" +#include "lldispatcher.h" +#include "llviewerobject.h" +#include "llviewerregion.h" +#include "llcombobox.h" +#include "lltooldraganddrop.h" +#include "lluiconstants.h" +#include "lluploaddialog.h" +#include "llcallingcard.h" +#include "llviewerobjectlist.h" +#include "lltoolobjpicker.h" +#include "lltoolmgr.h" +#include "llresourcedata.h" // for LLResourceData +#include "llslurl.h" +#include "llviewerwindow.h" +#include "llviewertexturelist.h" +#include "llworldmap.h" +#include "llfilepicker.h" +#include "llfloateravatarpicker.h" +#include "lldir.h" +#include "llselectmgr.h" +#include "llversioninfo.h" +#include "lluictrlfactory.h" +#include "llviewercontrol.h" +#include "llviewernetwork.h" + +#include "llagentui.h" + +#include "lltrans.h" +#include "llexperiencecache.h" + +#include "llcorehttputil.h" +#include "llviewerassetupload.h" + +const std::string SCREEN_PREV_FILENAME = "screen_report_last.png"; + +//========================================================================= +//----------------------------------------------------------------------------- +// Support classes +//----------------------------------------------------------------------------- +class LLARScreenShotUploader : public LLResourceUploadInfo +{ +public: + LLARScreenShotUploader(LLSD report, LLUUID assetId, LLAssetType::EType assetType); + + virtual LLSD prepareUpload(); + virtual LLSD generatePostBody(); + virtual LLUUID finishUpload(LLSD &result); + + virtual bool showInventoryPanel() const { return false; } + virtual std::string getDisplayName() const { return "Abuse Report"; } + +private: + + LLSD mReport; +}; + +LLARScreenShotUploader::LLARScreenShotUploader(LLSD report, LLUUID assetId, LLAssetType::EType assetType) : + LLResourceUploadInfo(assetId, assetType, "Abuse Report"), + mReport(report) +{ +} + +LLSD LLARScreenShotUploader::prepareUpload() +{ + return LLSD().with("success", LLSD::Boolean(true)); +} + +LLSD LLARScreenShotUploader::generatePostBody() +{ // The report was pregenerated and passed in the constructor. + return mReport; +} + +LLUUID LLARScreenShotUploader::finishUpload(LLSD &result) +{ + /* *TODO$: Report success or failure. Carried over from previous todo on responder*/ + return LLUUID::null; +} + + +//========================================================================= +//----------------------------------------------------------------------------- +// Globals +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Member functions +//----------------------------------------------------------------------------- + +LLFloaterReporter::LLFloaterReporter(const LLSD& key) +: LLFloater(key), + mReportType(COMPLAINT_REPORT), + mObjectID(), + mScreenID(), + mAbuserID(), + mOwnerName(), + mDeselectOnClose( false ), + mPicking( false), + mPosition(), + mCopyrightWarningSeen( false ), + mResourceDatap(new LLResourceData()), + mAvatarNameCacheConnection() +{ + gIdleCallbacks.addFunction(onIdle, this); +} + +// virtual +bool LLFloaterReporter::postBuild() +{ + LLSLURL slurl; + LLAgentUI::buildSLURL(slurl); + getChild("abuse_location_edit")->setValue(slurl.getSLURLString()); + + enableControls(true); + + // convert the position to a string + LLVector3d pos = gAgent.getPositionGlobal(); + LLViewerRegion *regionp = gAgent.getRegion(); + if (regionp) + { + getChild("sim_field")->setValue(regionp->getName()); + pos -= regionp->getOriginGlobal(); + } + setPosBox(pos); + + // Default text to be blank + getChild("object_name")->setValue(LLStringUtil::null); + getChild("owner_name")->setValue(LLStringUtil::null); + mOwnerName = LLStringUtil::null; + + getChild("summary_edit")->setFocus(true); + + mDefaultSummary = getChild("details_edit")->getValue().asString(); + + // abuser name is selected from a list + LLUICtrl* le = getChild("abuser_name_edit"); + le->setEnabled( false ); + + setPosBox((LLVector3d)mPosition.getValue()); + LLButton* pick_btn = getChild("pick_btn"); + pick_btn->setImages(std::string("tool_face.tga"), + std::string("tool_face_active.tga") ); + childSetAction("pick_btn", onClickObjPicker, this); + + childSetAction("select_abuser", boost::bind(&LLFloaterReporter::onClickSelectAbuser, this)); + + childSetAction("send_btn", onClickSend, this); + childSetAction("cancel_btn", onClickCancel, this); + + // grab the user's name + std::string reporter = LLSLURL("agent", gAgent.getID(), "inspect").getSLURLString(); + getChild("reporter_field")->setValue(reporter); + + // request categories + if (gAgent.getRegion() + && gAgent.getRegion()->capabilitiesReceived()) + { + std::string cap_url = gAgent.getRegionCapability("AbuseCategories"); + + if (!cap_url.empty()) + { + std::string lang = gSavedSettings.getString("Language"); + if (lang != "default" && !lang.empty()) + { + cap_url += "?lc="; + cap_url += lang; + } + LLCoros::instance().launch("LLFloaterReporter::requestAbuseCategoriesCoro", + boost::bind(LLFloaterReporter::requestAbuseCategoriesCoro, cap_url, this->getHandle())); + } + } + + center(); + + return true; +} + +// virtual +LLFloaterReporter::~LLFloaterReporter() +{ + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + gIdleCallbacks.deleteFunction(onIdle, this); + + // child views automatically deleted + mObjectID = LLUUID::null; + + if (mPicking) + { + closePickTool(this); + } + + mPosition.setVec(0.0f, 0.0f, 0.0f); + + delete mResourceDatap; +} + +void LLFloaterReporter::onIdle(void* user_data) +{ + LLFloaterReporter* floater_reporter = (LLFloaterReporter*)user_data; + if (floater_reporter) + { + static LLCachedControl screenshot_delay(gSavedSettings, "AbuseReportScreenshotDelay"); + if (floater_reporter->mSnapshotTimer.getStarted() && floater_reporter->mSnapshotTimer.getElapsedTimeF32() > screenshot_delay) + { + floater_reporter->mSnapshotTimer.stop(); + floater_reporter->takeNewSnapshot(); + } + } +} + +void LLFloaterReporter::enableControls(bool enable) +{ + getChildView("category_combo")->setEnabled(enable); + getChildView("chat_check")->setEnabled(enable); + getChildView("screenshot")->setEnabled(false); + getChildView("pick_btn")->setEnabled(enable); + getChildView("summary_edit")->setEnabled(enable); + getChildView("details_edit")->setEnabled(enable); + getChildView("send_btn")->setEnabled(enable); + getChildView("cancel_btn")->setEnabled(enable); +} + +void LLFloaterReporter::getExperienceInfo(const LLUUID& experience_id) +{ + mExperienceID = experience_id; + + if (LLUUID::null != mExperienceID) + { + const LLSD& experience = LLExperienceCache::instance().get(mExperienceID); + std::stringstream desc; + + if(experience.isDefined()) + { + setFromAvatarID(experience[LLExperienceCache::AGENT_ID]); + desc << "Experience id: " << mExperienceID; + } + else + { + desc << "Unable to retrieve details for id: "<< mExperienceID; + } + + LLUICtrl* details = getChild("details_edit"); + details->setValue(desc.str()); + } +} + +void LLFloaterReporter::getObjectInfo(const LLUUID& object_id) +{ + // TODO -- + // 1 need to send to correct simulator if object is not + // in same simulator as agent + // 2 display info in widget window that gives feedback that + // we have recorded the object info + // 3 can pick avatar ==> might want to indicate when a picked + // object is an avatar, attachment, or other category + + mObjectID = object_id; + + if (LLUUID::null != mObjectID) + { + // get object info for the user's benefit + LLViewerObject* objectp = NULL; + objectp = gObjectList.findObject( mObjectID ); + if (objectp) + { + if ( objectp->isAttachment() ) + { + objectp = (LLViewerObject*)objectp->getRoot(); + mObjectID = objectp->getID(); + } + + // correct the region and position information + LLViewerRegion *regionp = objectp->getRegion(); + if (regionp) + { + getChild("sim_field")->setValue(regionp->getName()); + LLVector3d global_pos; + global_pos.setVec(objectp->getPositionRegion()); + setPosBox(global_pos); + } + + if (objectp->isAvatar()) + { + setFromAvatarID(mObjectID); + } + else + { + // we have to query the simulator for information + // about this object + LLMessageSystem* msg = gMessageSystem; + U32 request_flags = COMPLAINT_REPORT_REQUEST; + msg->newMessageFast(_PREHASH_RequestObjectPropertiesFamily); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ObjectData); + msg->addU32Fast(_PREHASH_RequestFlags, request_flags ); + msg->addUUIDFast(_PREHASH_ObjectID, mObjectID); + LLViewerRegion* regionp = objectp->getRegion(); + msg->sendReliable( regionp->getHost() ); + } + } + } +} + +void LLFloaterReporter::onClickSelectAbuser() +{ + LLView * button = findChild("select_abuser", true); + + LLFloater * root_floater = gFloaterView->getParentFloater(this); + LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(boost::bind(&LLFloaterReporter::callbackAvatarID, this, _1, _2), false, true, false, root_floater->getName(), button); + if (picker) + { + root_floater->addDependentFloater(picker); + } +} + +void LLFloaterReporter::callbackAvatarID(const uuid_vec_t& ids, const std::vector names) +{ + if (ids.empty() || names.empty()) return; + + getChild("abuser_name_edit")->setValue(names[0].getCompleteName()); + + mAbuserID = ids[0]; + + refresh(); + +} + +void LLFloaterReporter::setFromAvatarID(const LLUUID& avatar_id) +{ + mAbuserID = mObjectID = avatar_id; + std::string avatar_link = LLSLURL("agent", mObjectID, "inspect").getSLURLString(); + getChild("owner_name")->setValue(avatar_link); + + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + mAvatarNameCacheConnection = LLAvatarNameCache::get(avatar_id, boost::bind(&LLFloaterReporter::onAvatarNameCache, this, _1, _2)); +} + +void LLFloaterReporter::onAvatarNameCache(const LLUUID& avatar_id, const LLAvatarName& av_name) +{ + mAvatarNameCacheConnection.disconnect(); + + if (mObjectID == avatar_id) + { + mOwnerName = av_name.getCompleteName(); + getChild("object_name")->setValue(av_name.getCompleteName()); + getChild("object_name")->setToolTip(av_name.getCompleteName()); + getChild("abuser_name_edit")->setValue(av_name.getCompleteName()); + } +} + +void LLFloaterReporter::requestAbuseCategoriesCoro(std::string url, LLHandle handle) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("requestAbuseCategoriesCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD result = httpAdapter->getAndSuspend(httpRequest, url); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status || !result.has("categories")) // success = httpResults["success"].asBoolean(); + { + LL_WARNS() << "Error requesting Abuse Categories from capability: " << url << LL_ENDL; + return; + } + + if (handle.isDead()) + { + // nothing to do + return; + } + + LLFloater* floater = handle.get(); + LLComboBox* combo = floater->getChild("category_combo"); + if (!combo) + { + LL_WARNS() << "categories category_combo not found!" << LL_ENDL; + return; + } + + //get selection (in case capability took a while) + S32 selection = combo->getCurrentIndex(); + + // Combobox should have a "Select category" element; + // This is a bit of workaround since there is no proper and simple way to save array of + // localizable strings in xml along with data (value). For now combobox is initialized along + // with placeholders, and first element is "Select category" which we want to keep, so remove + // everything but first element. + // Todo: once sim with capability fully releases, just remove this string and all unnecessary + // items from combobox since they will be obsolete (or depending on situation remake this to + // something better, for example move "Select category" to separate string) + while (combo->remove(1)); + + LLSD contents = result["categories"]; + + LLSD::array_iterator i = contents.beginArray(); + LLSD::array_iterator iEnd = contents.endArray(); + for (; i != iEnd; ++i) + { + const LLSD &message_data(*i); + std::string label = message_data["description_localized"]; + combo->add(label, message_data["category"]); + } + + //restore selection + combo->selectNthItem(selection); +} + +// static +void LLFloaterReporter::onClickSend(void *userdata) +{ + LLFloaterReporter *self = (LLFloaterReporter *)userdata; + + if (self->mPicking) + { + closePickTool(self); + } + + if(self->validateReport()) + { + + const int IP_CONTENT_REMOVAL = 66; + const int IP_PERMISSONS_EXPLOIT = 37; + LLComboBox* combo = self->getChild( "category_combo"); + int category_value = combo->getSelectedValue().asInteger(); + + if ( ! self->mCopyrightWarningSeen ) + { + + std::string details_lc = self->getChild("details_edit")->getValue().asString(); + LLStringUtil::toLower( details_lc ); + std::string summary_lc = self->getChild("summary_edit")->getValue().asString(); + LLStringUtil::toLower( summary_lc ); + if ( details_lc.find( "copyright" ) != std::string::npos || + summary_lc.find( "copyright" ) != std::string::npos || + category_value == IP_CONTENT_REMOVAL || + category_value == IP_PERMISSONS_EXPLOIT) + { + LLNotificationsUtil::add("HelpReportAbuseContainsCopyright"); + self->mCopyrightWarningSeen = true; + return; + } + } + else if (category_value == IP_CONTENT_REMOVAL) + { + // IP_CONTENT_REMOVAL *always* shows the dialog - + // ergo you can never send that abuse report type. + LLNotificationsUtil::add("HelpReportAbuseContainsCopyright"); + return; + } + + LLUploadDialog::modalUploadDialog(LLTrans::getString("uploading_abuse_report")); + // *TODO don't upload image if checkbox isn't checked + std::string url = gAgent.getRegionCapability("SendUserReport"); + std::string sshot_url = gAgent.getRegionCapability("SendUserReportWithScreenshot"); + if(!url.empty() || !sshot_url.empty()) + { + self->sendReportViaCaps(url, sshot_url, self->gatherReport()); + LLNotificationsUtil::add("HelpReportAbuseConfirm"); + self->closeFloater(); + } + else + { + self->getChildView("send_btn")->setEnabled(false); + self->getChildView("cancel_btn")->setEnabled(false); + // the callback from uploading the image calls sendReportViaLegacy() + self->uploadImage(); + } + } +} + + +// static +void LLFloaterReporter::onClickCancel(void *userdata) +{ + LLFloaterReporter *self = (LLFloaterReporter *)userdata; + + // reset flag in case the next report also contains this text + self->mCopyrightWarningSeen = false; + + if (self->mPicking) + { + closePickTool(self); + } + self->closeFloater(); +} + + +// static +void LLFloaterReporter::onClickObjPicker(void *userdata) +{ + LLFloaterReporter *self = (LLFloaterReporter *)userdata; + LLToolObjPicker::getInstance()->setExitCallback(LLFloaterReporter::closePickTool, self); + LLToolMgr::getInstance()->setTransientTool(LLToolObjPicker::getInstance()); + self->mPicking = true; + self->getChild("object_name")->setValue(LLStringUtil::null); + self->getChild("owner_name")->setValue(LLStringUtil::null); + self->mOwnerName = LLStringUtil::null; + LLButton* pick_btn = self->getChild("pick_btn"); + if (pick_btn) pick_btn->setToggleState(true); +} + + +// static +void LLFloaterReporter::closePickTool(void *userdata) +{ + LLFloaterReporter *self = (LLFloaterReporter *)userdata; + + LLUUID object_id = LLToolObjPicker::getInstance()->getObjectID(); + self->getObjectInfo(object_id); + + LLToolMgr::getInstance()->clearTransientTool(); + self->mPicking = false; + LLButton* pick_btn = self->getChild("pick_btn"); + if (pick_btn) pick_btn->setToggleState(false); +} + + +// static +void LLFloaterReporter::showFromMenu(EReportType report_type) +{ + if (COMPLAINT_REPORT != report_type) + { + LL_WARNS() << "Unknown LLViewerReporter type : " << report_type << LL_ENDL; + return; + } + LLFloaterReporter* reporter_floater = LLFloaterReg::findTypedInstance("reporter"); + if(reporter_floater && reporter_floater->isInVisibleChain()) + { + gSavedPerAccountSettings.setBOOL("PreviousScreenshotForReport", false); + } + reporter_floater = LLFloaterReg::showTypedInstance("reporter", LLSD()); + if (reporter_floater) + { + reporter_floater->setReportType(report_type); + } +} + +// static +void LLFloaterReporter::show(const LLUUID& object_id, const std::string& avatar_name, const LLUUID& experience_id) +{ + LLFloaterReporter* reporter_floater = LLFloaterReg::findTypedInstance("reporter"); + if(reporter_floater && reporter_floater->isInVisibleChain()) + { + gSavedPerAccountSettings.setBOOL("PreviousScreenshotForReport", false); + } + reporter_floater = LLFloaterReg::showTypedInstance("reporter"); + if (avatar_name.empty()) + { + // Request info for this object + reporter_floater->getObjectInfo(object_id); + } + else + { + reporter_floater->setFromAvatarID(object_id); + } + if(experience_id.notNull()) + { + reporter_floater->getExperienceInfo(experience_id); + } + + // Need to deselect on close + reporter_floater->mDeselectOnClose = true; +} + + + +void LLFloaterReporter::showFromExperience( const LLUUID& experience_id ) +{ + LLFloaterReporter* reporter_floater = LLFloaterReg::findTypedInstance("reporter"); + if(reporter_floater && reporter_floater->isInVisibleChain()) + { + gSavedPerAccountSettings.setBOOL("PreviousScreenshotForReport", false); + } + reporter_floater = LLFloaterReg::showTypedInstance("reporter"); + reporter_floater->getExperienceInfo(experience_id); + + // Need to deselect on close + reporter_floater->mDeselectOnClose = true; +} + + +// static +void LLFloaterReporter::showFromObject(const LLUUID& object_id, const LLUUID& experience_id) +{ + show(object_id, LLStringUtil::null, experience_id); +} + +// static +void LLFloaterReporter::showFromAvatar(const LLUUID& avatar_id, const std::string avatar_name) +{ + show(avatar_id, avatar_name); +} + +// static +void LLFloaterReporter::showFromChat(const LLUUID& avatar_id, const std::string& avatar_name, const std::string& time, const std::string& description) +{ + show(avatar_id, avatar_name); + + LLStringUtil::format_map_t args; + args["[MSG_TIME]"] = time; + args["[MSG_DESCRIPTION]"] = description; + + LLFloaterReporter *self = LLFloaterReg::findTypedInstance("reporter"); + if (self) + { + std::string description = self->getString("chat_report_format", args); + self->getChild("details_edit")->setValue(description); + } +} + +void LLFloaterReporter::setPickedObjectProperties(const std::string& object_name, const std::string& owner_name, const LLUUID owner_id) +{ + getChild("object_name")->setValue(object_name); + std::string owner_link = + LLSLURL("agent", owner_id, "inspect").getSLURLString(); + getChild("owner_name")->setValue(owner_link); + getChild("abuser_name_edit")->setValue(owner_name); + mAbuserID = owner_id; + mOwnerName = owner_name; +} + + +bool LLFloaterReporter::validateReport() +{ + // Ensure user selected a category from the list + LLSD category_sd = getChild("category_combo")->getValue(); + U8 category = (U8)category_sd.asInteger(); + if (category == 0) + { + LLNotificationsUtil::add("HelpReportAbuseSelectCategory"); + return false; + } + + + if ( getChild("abuser_name_edit")->getValue().asString().empty() ) + { + LLNotificationsUtil::add("HelpReportAbuseAbuserNameEmpty"); + return false; + }; + + if ( getChild("abuse_location_edit")->getValue().asString().empty() ) + { + LLNotificationsUtil::add("HelpReportAbuseAbuserLocationEmpty"); + return false; + }; + + if ( getChild("abuse_location_edit")->getValue().asString().empty() ) + { + LLNotificationsUtil::add("HelpReportAbuseAbuserLocationEmpty"); + return false; + }; + + + if ( getChild("summary_edit")->getValue().asString().empty() ) + { + LLNotificationsUtil::add("HelpReportAbuseSummaryEmpty"); + return false; + }; + + if ( getChild("details_edit")->getValue().asString() == mDefaultSummary ) + { + LLNotificationsUtil::add("HelpReportAbuseDetailsEmpty"); + return false; + }; + return true; +} + +LLSD LLFloaterReporter::gatherReport() +{ + LLViewerRegion *regionp = gAgent.getRegion(); + if (!regionp) return LLSD(); // *TODO handle this failure case more gracefully + + // reset flag in case the next report also contains this text + mCopyrightWarningSeen = false; + + std::ostringstream summary; + if (!LLGridManager::getInstance()->isInProductionGrid()) + { + summary << "Preview "; + } + + std::string category_name; + LLComboBox* combo = getChild( "category_combo"); + if (combo) + { + category_name = combo->getSelectedItemLabel(); // want label, not value + } + +#if LL_WINDOWS + const char* platform = "Win"; +#elif LL_DARWIN + const char* platform = "Mac"; +#elif LL_LINUX + const char* platform = "Lnx"; +#else + const char* platform = "???"; +#endif + + + + summary << "" + << " |" << regionp->getName() << "|" // region reporter is currently in. + << " (" << getChild("abuse_location_edit")->getValue().asString() << ")" // region abuse occured in (freeform text - no LLRegionPicker tool) + << " [" << category_name << "] " // updated category + << " {" << getChild("abuser_name_edit")->getValue().asString() << "} " // name of abuse entered in report (chosen using LLAvatarPicker) + << " \"" << getChild("summary_edit")->getValue().asString() << "\""; // summary as entered + + + std::ostringstream details; + + details << "V" << LLVersionInfo::instance().getVersion() << std::endl << std::endl; // client version moved to body of email for abuse reports + + std::string object_name = getChild("object_name")->getValue().asString(); + if (!object_name.empty() && !mOwnerName.empty()) + { + details << "Object: " << object_name << "\n"; + details << "Owner: " << mOwnerName << "\n"; + } + + + details << "Abuser name: " << getChild("abuser_name_edit")->getValue().asString() << " \n"; + details << "Abuser location: " << getChild("abuse_location_edit")->getValue().asString() << " \n"; + + details << getChild("details_edit")->getValue().asString(); + + std::string version_string; + version_string = llformat( + "%s %s %s %s %s", + LLVersionInfo::instance().getShortVersion().c_str(), + platform, + gSysCPU.getFamily().c_str(), + gGLManager.mGLRenderer.c_str(), + gGLManager.mDriverVersionVendorString.c_str()); + + // only send a screenshot ID if we're asked to and the email is + // going to LL - Estate Owners cannot see the screenshot asset + LLUUID screenshot_id = LLUUID::null; + screenshot_id = getChild("screenshot")->getValue(); + + LLSD report = LLSD::emptyMap(); + report["report-type"] = (U8) mReportType; + report["category"] = getChild("category_combo")->getValue(); + report["position"] = mPosition.getValue(); + report["check-flags"] = (U8)0; // this is not used + report["screenshot-id"] = screenshot_id; + report["object-id"] = mObjectID; + report["abuser-id"] = mAbuserID; + report["abuse-region-name"] = ""; + report["abuse-region-id"] = LLUUID::null; + report["summary"] = summary.str(); + report["version-string"] = version_string; + report["details"] = details.str(); + return report; +} + +void LLFloaterReporter::sendReportViaLegacy(const LLSD & report) +{ + LLViewerRegion *regionp = gAgent.getRegion(); + if (!regionp) return; + LLMessageSystem *msg = gMessageSystem; + msg->newMessageFast(_PREHASH_UserReport); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + + msg->nextBlockFast(_PREHASH_ReportData); + msg->addU8Fast(_PREHASH_ReportType, report["report-type"].asInteger()); + msg->addU8(_PREHASH_Category, report["category"].asInteger()); + msg->addVector3Fast(_PREHASH_Position, LLVector3(report["position"])); + msg->addU8Fast(_PREHASH_CheckFlags, report["check-flags"].asInteger()); + msg->addUUIDFast(_PREHASH_ScreenshotID, report["screenshot-id"].asUUID()); + msg->addUUIDFast(_PREHASH_ObjectID, report["object-id"].asUUID()); + msg->addUUID("AbuserID", report["abuser-id"].asUUID()); + msg->addString("AbuseRegionName", report["abuse-region-name"].asString()); + msg->addUUID("AbuseRegionID", report["abuse-region-id"].asUUID()); + + msg->addStringFast(_PREHASH_Summary, report["summary"].asString()); + msg->addString("VersionString", report["version-string"]); + msg->addStringFast(_PREHASH_Details, report["details"] ); + + msg->sendReliable(regionp->getHost()); +} + +void LLFloaterReporter::finishedARPost(const LLSD &) +{ + LLUploadDialog::modalUploadFinished(); + +} + +void LLFloaterReporter::sendReportViaCaps(std::string url, std::string sshot_url, const LLSD& report) +{ + if(!sshot_url.empty()) + { + // try to upload screenshot + LLResourceUploadInfo::ptr_t uploadInfo(new LLARScreenShotUploader(report, mResourceDatap->mAssetInfo.mUuid, mResourceDatap->mAssetInfo.mType)); + LLViewerAssetUpload::EnqueueInventoryUpload(sshot_url, uploadInfo); + } + else + { + LLCoreHttpUtil::HttpCoroutineAdapter::completionCallback_t proc = boost::bind(&LLFloaterReporter::finishedARPost, _1); + LLUploadDialog::modalUploadDialog("Abuse Report"); + LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost(url, report, proc, proc); + } +} + +void LLFloaterReporter::takeScreenshot(bool use_prev_screenshot) +{ + gSavedPerAccountSettings.setBOOL("PreviousScreenshotForReport", true); + if(!use_prev_screenshot) + { + std::string screenshot_filename(gDirUtilp->getLindenUserDir() + gDirUtilp->getDirDelimiter() + SCREEN_PREV_FILENAME); + LLPointer png_image = new LLImagePNG; + if(png_image->encode(mImageRaw, 0.0f)) + { + png_image->save(screenshot_filename); + } + } + else + { + mImageRaw = mPrevImageRaw; + } + + LLPointer upload_data = LLViewerTextureList::convertToUploadFile(mImageRaw); + + // create a resource data + mResourceDatap->mInventoryType = LLInventoryType::IT_NONE; + mResourceDatap->mNextOwnerPerm = 0; // not used + mResourceDatap->mExpectedUploadCost = 0; // we expect that abuse screenshots are free + mResourceDatap->mAssetInfo.mTransactionID.generate(); + mResourceDatap->mAssetInfo.mUuid = mResourceDatap->mAssetInfo.mTransactionID.makeAssetID(gAgent.getSecureSessionID()); + + if (COMPLAINT_REPORT == mReportType) + { + mResourceDatap->mAssetInfo.mType = LLAssetType::AT_TEXTURE; + mResourceDatap->mPreferredLocation = LLFolderType::EType(LLResourceData::INVALID_LOCATION); + } + else + { + LL_WARNS() << "Unknown LLFloaterReporter type" << LL_ENDL; + } + mResourceDatap->mAssetInfo.mCreatorID = gAgentID; + mResourceDatap->mAssetInfo.setName("screenshot_name"); + mResourceDatap->mAssetInfo.setDescription("screenshot_descr"); + + // store in cache + LLFileSystem j2c_file(mResourceDatap->mAssetInfo.mUuid, mResourceDatap->mAssetInfo.mType, LLFileSystem::WRITE); + j2c_file.write(upload_data->getData(), upload_data->getDataSize()); + + // store in the image list so it doesn't try to fetch from the server + LLPointer image_in_list = + LLViewerTextureManager::getFetchedTexture(mResourceDatap->mAssetInfo.mUuid); + image_in_list->createGLTexture(0, mImageRaw, 0, true, LLGLTexture::OTHER); + + // the texture picker then uses that texture + LLTextureCtrl* texture = getChild("screenshot"); + if (texture) + { + texture->setImageAssetID(mResourceDatap->mAssetInfo.mUuid); + texture->setDefaultImageAssetID(mResourceDatap->mAssetInfo.mUuid); + texture->setCaption(getString("Screenshot")); + } +} + +void LLFloaterReporter::takeNewSnapshot() +{ + childSetEnabled("send_btn", true); + mImageRaw = new LLImageRaw; + const S32 IMAGE_WIDTH = 1024; + const S32 IMAGE_HEIGHT = 768; + + // Take a screenshot, but don't draw this floater. + setVisible(false); + if (!gViewerWindow->rawSnapshot(mImageRaw,IMAGE_WIDTH, IMAGE_HEIGHT, true, false, true /*UI*/, true, false)) + { + LL_WARNS() << "Unable to take screenshot" << LL_ENDL; + setVisible(true); + return; + } + setVisible(true); + + if(gSavedPerAccountSettings.getBOOL("PreviousScreenshotForReport")) + { + std::string screenshot_filename(gDirUtilp->getLindenUserDir() + gDirUtilp->getDirDelimiter() + SCREEN_PREV_FILENAME); + mPrevImageRaw = new LLImageRaw; + LLPointer start_image_png = new LLImagePNG; + if(start_image_png->load(screenshot_filename)) + { + if (start_image_png->decode(mPrevImageRaw, 0.0f)) + { + LLNotificationsUtil::add("LoadPreviousReportScreenshot", LLSD(), LLSD(), boost::bind(&LLFloaterReporter::onLoadScreenshotDialog,this, _1, _2)); + return; + } + } + } + takeScreenshot(); +} + + +void LLFloaterReporter::onOpen(const LLSD& key) +{ + childSetEnabled("send_btn", false); + //Time delay to avoid UI artifacts. MAINT-7067 + mSnapshotTimer.start(); +} + +void LLFloaterReporter::onLoadScreenshotDialog(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + takeScreenshot(option == 0); +} + +void LLFloaterReporter::uploadImage() +{ + LL_INFOS() << "*** Uploading: " << LL_ENDL; + LL_INFOS() << "Type: " << LLAssetType::lookup(mResourceDatap->mAssetInfo.mType) << LL_ENDL; + LL_INFOS() << "UUID: " << mResourceDatap->mAssetInfo.mUuid << LL_ENDL; + LL_INFOS() << "Name: " << mResourceDatap->mAssetInfo.getName() << LL_ENDL; + LL_INFOS() << "Desc: " << mResourceDatap->mAssetInfo.getDescription() << LL_ENDL; + + gAssetStorage->storeAssetData(mResourceDatap->mAssetInfo.mTransactionID, + mResourceDatap->mAssetInfo.mType, + LLFloaterReporter::uploadDoneCallback, + (void*)mResourceDatap, true); +} + + +// static +void LLFloaterReporter::uploadDoneCallback(const LLUUID &uuid, void *user_data, S32 result, LLExtStat ext_status) // StoreAssetData callback (fixed) +{ + LLUploadDialog::modalUploadFinished(); + + LLResourceData* data = (LLResourceData*)user_data; + + if(result < 0) + { + LLSD args; + args["REASON"] = std::string(LLAssetStorage::getErrorString(result)); + LLNotificationsUtil::add("ErrorUploadingReportScreenshot", args); + + std::string err_msg("There was a problem uploading a report screenshot"); + err_msg += " due to the following reason: " + args["REASON"].asString(); + LL_WARNS() << err_msg << LL_ENDL; + return; + } + + if (data->mPreferredLocation != LLResourceData::INVALID_LOCATION) + { + LL_WARNS() << "Unknown report type : " << data->mPreferredLocation << LL_ENDL; + } + + LLFloaterReporter *self = LLFloaterReg::findTypedInstance("reporter"); + if (self) + { + self->mScreenID = uuid; + LL_INFOS() << "Got screen shot " << uuid << LL_ENDL; + self->sendReportViaLegacy(self->gatherReport()); + LLNotificationsUtil::add("HelpReportAbuseConfirm"); + self->closeFloater(); + } +} + + +void LLFloaterReporter::setPosBox(const LLVector3d &pos) +{ + mPosition.setVec(pos); + std::string pos_string = llformat("{%.1f, %.1f, %.1f}", + mPosition.mV[VX], + mPosition.mV[VY], + mPosition.mV[VZ]); + getChild("pos_field")->setValue(pos_string); +} + +void LLFloaterReporter::onClose(bool app_quitting) +{ + mSnapshotTimer.stop(); + gSavedPerAccountSettings.setBOOL("PreviousScreenshotForReport", app_quitting); +} diff --git a/indra/newview/llfloaterreporter.h b/indra/newview/llfloaterreporter.h index 66027656a8..31b05235a6 100644 --- a/indra/newview/llfloaterreporter.h +++ b/indra/newview/llfloaterreporter.h @@ -1,153 +1,153 @@ -/** - * @file llfloaterreporter.h - * @author Andrew Meadows - * @brief Abuse reports. - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERREPORTER_H -#define LL_LLFLOATERREPORTER_H - -#include "llfloater.h" -#include "lluuid.h" -#include "v3math.h" -#include "llextendedstatus.h" - -class LLAvatarName; -class LLMessageSystem; -class LLViewerTexture; -class LLInventoryItem; -class LLViewerObject; -class LLAgent; -class LLToolObjPicker; -class LLMeanCollisionData; -struct LLResourceData; - -// these flags are used to label info requests to the server -//const U32 BUG_REPORT_REQUEST = 0x01 << 0; // DEPRECATED -const U32 COMPLAINT_REPORT_REQUEST = 0x01 << 1; -const U32 OBJECT_PAY_REQUEST = 0x01 << 2; - - -// ************************************************************ -// THESE ENUMS ARE IN THE DATABASE!!! -// -// The process for adding a new report type is to: -// 1. Issue a command to the database to insert the new value: -// insert into user_report_type (description) -// values ('${new type name}'); -// 2. Record the integer value assigned: -// select type from user_report_type -// where description='${new type name}'; -// 3. Add it here. -// ${NEW TYPE NAME}_REPORT = ${type_number}; -// -// Failure to follow this process WILL result in incorrect -// queries on user reports. -// ************************************************************ -enum EReportType -{ - NULL_REPORT = 0, // don't use this value anywhere - UNKNOWN_REPORT = 1, - //BUG_REPORT = 2, // DEPRECATED - COMPLAINT_REPORT = 3, - CS_REQUEST_REPORT = 4 -}; - -class LLFloaterReporter -: public LLFloater -{ -public: - LLFloaterReporter(const LLSD& key); - /*virtual*/ ~LLFloaterReporter(); - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); - /*virtual*/ void onClose(bool app_quitting); - - static void onIdle(void* user_data); - - void setReportType(EReportType type) { mReportType = type; } - - // Enables all buttons - static void showFromMenu(EReportType report_type); - - static void showFromObject(const LLUUID& object_id, const LLUUID& experience_id = LLUUID::null); - static void showFromAvatar(const LLUUID& avatar_id, const std::string avatar_name); - static void showFromChat(const LLUUID& avatar_id, const std::string& avatar_name, const std::string& time, const std::string& description); - static void showFromExperience(const LLUUID& experience_id); - - static void onClickSend (void *userdata); - static void onClickCancel (void *userdata); - static void onClickObjPicker (void *userdata); - void onClickSelectAbuser (); - static void closePickTool (void *userdata); - static void uploadDoneCallback(const LLUUID &uuid, void* user_data, S32 result, LLExtStat ext_status); - - void setPickedObjectProperties(const std::string& object_name, const std::string& owner_name, const LLUUID owner_id); - - void onLoadScreenshotDialog(const LLSD& notification, const LLSD& response); - - void takeNewSnapshot(); - -private: - static void show(const LLUUID& object_id, const std::string& avatar_name = LLStringUtil::null, const LLUUID& experience_id = LLUUID::null); - - void takeScreenshot(bool use_prev_screenshot = false); - void uploadImage(); - bool validateReport(); - LLSD gatherReport(); - void sendReportViaLegacy(const LLSD & report); - void sendReportViaCaps(std::string url, std::string sshot_url, const LLSD & report); - void setPosBox(const LLVector3d &pos); - void enableControls(bool own_avatar); - void getExperienceInfo(const LLUUID& object_id); - void getObjectInfo(const LLUUID& object_id); - void callbackAvatarID(const uuid_vec_t& ids, const std::vector names); - void setFromAvatarID(const LLUUID& avatar_id); - void onAvatarNameCache(const LLUUID& avatar_id, const LLAvatarName& av_name); - - static void requestAbuseCategoriesCoro(std::string url, LLHandle handle); - static void finishedARPost(const LLSD &); - -private: - EReportType mReportType; - LLUUID mObjectID; - LLUUID mScreenID; - LLUUID mAbuserID; - LLUUID mExperienceID; - // Store the real name, not the link, for upstream reporting - std::string mOwnerName; - bool mDeselectOnClose; - bool mPicking; - LLVector3 mPosition; - bool mCopyrightWarningSeen; - std::string mDefaultSummary; - LLResourceData* mResourceDatap; - boost::signals2::connection mAvatarNameCacheConnection; - - LLPointer mImageRaw; - LLPointer mPrevImageRaw; - LLFrameTimer mSnapshotTimer; -}; - -#endif +/** + * @file llfloaterreporter.h + * @author Andrew Meadows + * @brief Abuse reports. + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERREPORTER_H +#define LL_LLFLOATERREPORTER_H + +#include "llfloater.h" +#include "lluuid.h" +#include "v3math.h" +#include "llextendedstatus.h" + +class LLAvatarName; +class LLMessageSystem; +class LLViewerTexture; +class LLInventoryItem; +class LLViewerObject; +class LLAgent; +class LLToolObjPicker; +class LLMeanCollisionData; +struct LLResourceData; + +// these flags are used to label info requests to the server +//const U32 BUG_REPORT_REQUEST = 0x01 << 0; // DEPRECATED +const U32 COMPLAINT_REPORT_REQUEST = 0x01 << 1; +const U32 OBJECT_PAY_REQUEST = 0x01 << 2; + + +// ************************************************************ +// THESE ENUMS ARE IN THE DATABASE!!! +// +// The process for adding a new report type is to: +// 1. Issue a command to the database to insert the new value: +// insert into user_report_type (description) +// values ('${new type name}'); +// 2. Record the integer value assigned: +// select type from user_report_type +// where description='${new type name}'; +// 3. Add it here. +// ${NEW TYPE NAME}_REPORT = ${type_number}; +// +// Failure to follow this process WILL result in incorrect +// queries on user reports. +// ************************************************************ +enum EReportType +{ + NULL_REPORT = 0, // don't use this value anywhere + UNKNOWN_REPORT = 1, + //BUG_REPORT = 2, // DEPRECATED + COMPLAINT_REPORT = 3, + CS_REQUEST_REPORT = 4 +}; + +class LLFloaterReporter +: public LLFloater +{ +public: + LLFloaterReporter(const LLSD& key); + /*virtual*/ ~LLFloaterReporter(); + /*virtual*/ bool postBuild(); + /*virtual*/ void onOpen(const LLSD& key); + /*virtual*/ void onClose(bool app_quitting); + + static void onIdle(void* user_data); + + void setReportType(EReportType type) { mReportType = type; } + + // Enables all buttons + static void showFromMenu(EReportType report_type); + + static void showFromObject(const LLUUID& object_id, const LLUUID& experience_id = LLUUID::null); + static void showFromAvatar(const LLUUID& avatar_id, const std::string avatar_name); + static void showFromChat(const LLUUID& avatar_id, const std::string& avatar_name, const std::string& time, const std::string& description); + static void showFromExperience(const LLUUID& experience_id); + + static void onClickSend (void *userdata); + static void onClickCancel (void *userdata); + static void onClickObjPicker (void *userdata); + void onClickSelectAbuser (); + static void closePickTool (void *userdata); + static void uploadDoneCallback(const LLUUID &uuid, void* user_data, S32 result, LLExtStat ext_status); + + void setPickedObjectProperties(const std::string& object_name, const std::string& owner_name, const LLUUID owner_id); + + void onLoadScreenshotDialog(const LLSD& notification, const LLSD& response); + + void takeNewSnapshot(); + +private: + static void show(const LLUUID& object_id, const std::string& avatar_name = LLStringUtil::null, const LLUUID& experience_id = LLUUID::null); + + void takeScreenshot(bool use_prev_screenshot = false); + void uploadImage(); + bool validateReport(); + LLSD gatherReport(); + void sendReportViaLegacy(const LLSD & report); + void sendReportViaCaps(std::string url, std::string sshot_url, const LLSD & report); + void setPosBox(const LLVector3d &pos); + void enableControls(bool own_avatar); + void getExperienceInfo(const LLUUID& object_id); + void getObjectInfo(const LLUUID& object_id); + void callbackAvatarID(const uuid_vec_t& ids, const std::vector names); + void setFromAvatarID(const LLUUID& avatar_id); + void onAvatarNameCache(const LLUUID& avatar_id, const LLAvatarName& av_name); + + static void requestAbuseCategoriesCoro(std::string url, LLHandle handle); + static void finishedARPost(const LLSD &); + +private: + EReportType mReportType; + LLUUID mObjectID; + LLUUID mScreenID; + LLUUID mAbuserID; + LLUUID mExperienceID; + // Store the real name, not the link, for upstream reporting + std::string mOwnerName; + bool mDeselectOnClose; + bool mPicking; + LLVector3 mPosition; + bool mCopyrightWarningSeen; + std::string mDefaultSummary; + LLResourceData* mResourceDatap; + boost::signals2::connection mAvatarNameCacheConnection; + + LLPointer mImageRaw; + LLPointer mPrevImageRaw; + LLFrameTimer mSnapshotTimer; +}; + +#endif diff --git a/indra/newview/llfloatersavecamerapreset.cpp b/indra/newview/llfloatersavecamerapreset.cpp index 78acd2d3f9..53c5e6ce31 100644 --- a/indra/newview/llfloatersavecamerapreset.cpp +++ b/indra/newview/llfloatersavecamerapreset.cpp @@ -1,172 +1,172 @@ -/** - * @file llfloatersavecamerapreset.cpp - * @brief Floater to save a camera preset - * - * $LicenseInfo:firstyear=2020&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2020, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloatersavecamerapreset.h" - -#include "llagent.h" -#include "llagentcamera.h" -#include "llbutton.h" -#include "llcombobox.h" -#include "llfloaterpreference.h" -#include "llfloaterreg.h" -#include "lllineeditor.h" -#include "llnotificationsutil.h" -#include "llpresetsmanager.h" -#include "llradiogroup.h" -#include "lltrans.h" -#include "llvoavatarself.h" - -LLFloaterSaveCameraPreset::LLFloaterSaveCameraPreset(const LLSD &key) - : LLModalDialog(key) -{ -} - -// virtual -bool LLFloaterSaveCameraPreset::postBuild() -{ - mPresetCombo = getChild("preset_combo"); - - mNameEditor = getChild("preset_txt_editor"); - mNameEditor->setKeystrokeCallback(boost::bind(&LLFloaterSaveCameraPreset::onPresetNameEdited, this), NULL); - - mSaveButton = getChild("save"); - mSaveButton->setCommitCallback(boost::bind(&LLFloaterSaveCameraPreset::onBtnSave, this)); - - mSaveRadioGroup = getChild("radio_save_preset"); - mSaveRadioGroup->setCommitCallback(boost::bind(&LLFloaterSaveCameraPreset::onSwitchSaveReplace, this)); - - getChild("cancel")->setCommitCallback(boost::bind(&LLFloaterSaveCameraPreset::onBtnCancel, this)); - - LLPresetsManager::instance().setPresetListChangeCallback(boost::bind(&LLFloaterSaveCameraPreset::onPresetsListChange, this)); - - return true; -} - -void LLFloaterSaveCameraPreset::onPresetNameEdited() -{ - if (mSaveRadioGroup->getSelectedIndex() == 0) - { - // Disable saving a preset having empty name. - std::string name = mNameEditor->getValue(); - mSaveButton->setEnabled(!name.empty()); - } -} - -void LLFloaterSaveCameraPreset::onOpen(const LLSD& key) -{ - LLModalDialog::onOpen(key); - S32 index = 0; - if (key.has("index")) - { - index = key["index"].asInteger(); - } - - LLPresetsManager::getInstance()->setPresetNamesInComboBox(PRESETS_CAMERA, mPresetCombo, DEFAULT_BOTTOM); - - mSaveRadioGroup->setSelectedIndex(index); - onPresetNameEdited(); - onSwitchSaveReplace(); -} - -void LLFloaterSaveCameraPreset::onBtnSave() -{ - bool is_saving_new = mSaveRadioGroup->getSelectedIndex() == 0; - std::string name = is_saving_new ? mNameEditor->getText() : mPresetCombo->getSimple(); - - if ((name == LLTrans::getString(PRESETS_DEFAULT)) || (name == PRESETS_DEFAULT)) - { - LLNotificationsUtil::add("DefaultPresetNotSaved"); - } - else - { - if (isAgentAvatarValid() && gAgentAvatarp->getParent()) - { - gSavedSettings.setLLSD("AvatarSitRotation", gAgent.getFrameAgent().getQuaternion().getValue()); - } - if (gAgentCamera.isJoystickCameraUsed()) - { - gSavedSettings.setVector3("CameraOffsetRearView", gAgentCamera.getCurrentCameraOffset()); - gSavedSettings.setVector3d("FocusOffsetRearView", gAgentCamera.getCurrentFocusOffset()); - gAgentCamera.resetCameraZoomFraction(); - gAgentCamera.setFocusOnAvatar(true, true, false); - } - else - { - LLVector3 camera_offset = gSavedSettings.getVector3("CameraOffsetRearView") * gAgentCamera.getCurrentCameraZoomFraction(); - gSavedSettings.setVector3("CameraOffsetRearView", camera_offset); - gAgentCamera.resetCameraZoomFraction(); - } - if (is_saving_new) - { - std::list preset_names; - LLPresetsManager::getInstance()->loadPresetNamesFromDir(PRESETS_CAMERA, preset_names, DEFAULT_HIDE); - if (std::find(preset_names.begin(), preset_names.end(), name) != preset_names.end()) - { - LLSD args; - args["NAME"] = name; - LLNotificationsUtil::add("PresetAlreadyExists", args); - return; - } - } - if (!LLPresetsManager::getInstance()->savePreset(PRESETS_CAMERA, name)) - { - LLSD args; - args["NAME"] = name; - LLNotificationsUtil::add("PresetNotSaved", args); - } - } - - closeFloater(); -} - -void LLFloaterSaveCameraPreset::onPresetsListChange() -{ - LLPresetsManager::getInstance()->setPresetNamesInComboBox(PRESETS_CAMERA, mPresetCombo, DEFAULT_BOTTOM); -} - -void LLFloaterSaveCameraPreset::onBtnCancel() -{ - closeFloater(); -} - -void LLFloaterSaveCameraPreset::onSwitchSaveReplace() -{ - bool is_saving_new = mSaveRadioGroup->getSelectedIndex() == 0; - std::string label = is_saving_new ? getString("btn_label_save") : getString("btn_label_replace"); - mSaveButton->setLabel(label); - mNameEditor->setEnabled(is_saving_new); - mPresetCombo->setEnabled(!is_saving_new); - if (is_saving_new) - { - onPresetNameEdited(); - } - else - { - mSaveButton->setEnabled(true); - } -} +/** + * @file llfloatersavecamerapreset.cpp + * @brief Floater to save a camera preset + * + * $LicenseInfo:firstyear=2020&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2020, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatersavecamerapreset.h" + +#include "llagent.h" +#include "llagentcamera.h" +#include "llbutton.h" +#include "llcombobox.h" +#include "llfloaterpreference.h" +#include "llfloaterreg.h" +#include "lllineeditor.h" +#include "llnotificationsutil.h" +#include "llpresetsmanager.h" +#include "llradiogroup.h" +#include "lltrans.h" +#include "llvoavatarself.h" + +LLFloaterSaveCameraPreset::LLFloaterSaveCameraPreset(const LLSD &key) + : LLModalDialog(key) +{ +} + +// virtual +bool LLFloaterSaveCameraPreset::postBuild() +{ + mPresetCombo = getChild("preset_combo"); + + mNameEditor = getChild("preset_txt_editor"); + mNameEditor->setKeystrokeCallback(boost::bind(&LLFloaterSaveCameraPreset::onPresetNameEdited, this), NULL); + + mSaveButton = getChild("save"); + mSaveButton->setCommitCallback(boost::bind(&LLFloaterSaveCameraPreset::onBtnSave, this)); + + mSaveRadioGroup = getChild("radio_save_preset"); + mSaveRadioGroup->setCommitCallback(boost::bind(&LLFloaterSaveCameraPreset::onSwitchSaveReplace, this)); + + getChild("cancel")->setCommitCallback(boost::bind(&LLFloaterSaveCameraPreset::onBtnCancel, this)); + + LLPresetsManager::instance().setPresetListChangeCallback(boost::bind(&LLFloaterSaveCameraPreset::onPresetsListChange, this)); + + return true; +} + +void LLFloaterSaveCameraPreset::onPresetNameEdited() +{ + if (mSaveRadioGroup->getSelectedIndex() == 0) + { + // Disable saving a preset having empty name. + std::string name = mNameEditor->getValue(); + mSaveButton->setEnabled(!name.empty()); + } +} + +void LLFloaterSaveCameraPreset::onOpen(const LLSD& key) +{ + LLModalDialog::onOpen(key); + S32 index = 0; + if (key.has("index")) + { + index = key["index"].asInteger(); + } + + LLPresetsManager::getInstance()->setPresetNamesInComboBox(PRESETS_CAMERA, mPresetCombo, DEFAULT_BOTTOM); + + mSaveRadioGroup->setSelectedIndex(index); + onPresetNameEdited(); + onSwitchSaveReplace(); +} + +void LLFloaterSaveCameraPreset::onBtnSave() +{ + bool is_saving_new = mSaveRadioGroup->getSelectedIndex() == 0; + std::string name = is_saving_new ? mNameEditor->getText() : mPresetCombo->getSimple(); + + if ((name == LLTrans::getString(PRESETS_DEFAULT)) || (name == PRESETS_DEFAULT)) + { + LLNotificationsUtil::add("DefaultPresetNotSaved"); + } + else + { + if (isAgentAvatarValid() && gAgentAvatarp->getParent()) + { + gSavedSettings.setLLSD("AvatarSitRotation", gAgent.getFrameAgent().getQuaternion().getValue()); + } + if (gAgentCamera.isJoystickCameraUsed()) + { + gSavedSettings.setVector3("CameraOffsetRearView", gAgentCamera.getCurrentCameraOffset()); + gSavedSettings.setVector3d("FocusOffsetRearView", gAgentCamera.getCurrentFocusOffset()); + gAgentCamera.resetCameraZoomFraction(); + gAgentCamera.setFocusOnAvatar(true, true, false); + } + else + { + LLVector3 camera_offset = gSavedSettings.getVector3("CameraOffsetRearView") * gAgentCamera.getCurrentCameraZoomFraction(); + gSavedSettings.setVector3("CameraOffsetRearView", camera_offset); + gAgentCamera.resetCameraZoomFraction(); + } + if (is_saving_new) + { + std::list preset_names; + LLPresetsManager::getInstance()->loadPresetNamesFromDir(PRESETS_CAMERA, preset_names, DEFAULT_HIDE); + if (std::find(preset_names.begin(), preset_names.end(), name) != preset_names.end()) + { + LLSD args; + args["NAME"] = name; + LLNotificationsUtil::add("PresetAlreadyExists", args); + return; + } + } + if (!LLPresetsManager::getInstance()->savePreset(PRESETS_CAMERA, name)) + { + LLSD args; + args["NAME"] = name; + LLNotificationsUtil::add("PresetNotSaved", args); + } + } + + closeFloater(); +} + +void LLFloaterSaveCameraPreset::onPresetsListChange() +{ + LLPresetsManager::getInstance()->setPresetNamesInComboBox(PRESETS_CAMERA, mPresetCombo, DEFAULT_BOTTOM); +} + +void LLFloaterSaveCameraPreset::onBtnCancel() +{ + closeFloater(); +} + +void LLFloaterSaveCameraPreset::onSwitchSaveReplace() +{ + bool is_saving_new = mSaveRadioGroup->getSelectedIndex() == 0; + std::string label = is_saving_new ? getString("btn_label_save") : getString("btn_label_replace"); + mSaveButton->setLabel(label); + mNameEditor->setEnabled(is_saving_new); + mPresetCombo->setEnabled(!is_saving_new); + if (is_saving_new) + { + onPresetNameEdited(); + } + else + { + mSaveButton->setEnabled(true); + } +} diff --git a/indra/newview/llfloatersavecamerapreset.h b/indra/newview/llfloatersavecamerapreset.h index 2b24d8170b..b37713a81a 100644 --- a/indra/newview/llfloatersavecamerapreset.h +++ b/indra/newview/llfloatersavecamerapreset.h @@ -1,60 +1,60 @@ -/** - * @file llfloatersavecamerapreset.h - * @brief Floater to save a camera preset - - * - * $LicenseInfo:firstyear=2020&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2020, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERSAVECAMERAPRESET_H -#define LL_LLFLOATERSAVECAMERAPRESET_H - -#include "llmodaldialog.h" - -class LLComboBox; -class LLRadioGroup; -class LLLineEditor; - -class LLFloaterSaveCameraPreset : public LLModalDialog -{ - -public: - LLFloaterSaveCameraPreset(const LLSD &key); - - bool postBuild() override; - void onOpen(const LLSD& key) override; - - void onBtnSave(); - void onBtnCancel(); - void onSwitchSaveReplace(); - -private: - LLRadioGroup* mSaveRadioGroup; - LLLineEditor* mNameEditor; - LLComboBox* mPresetCombo; - LLButton* mSaveButton; - - void onPresetsListChange(); - void onPresetNameEdited(); -}; - -#endif // LL_LLFLOATERSAVECAMERAPRESET_H +/** + * @file llfloatersavecamerapreset.h + * @brief Floater to save a camera preset + + * + * $LicenseInfo:firstyear=2020&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2020, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERSAVECAMERAPRESET_H +#define LL_LLFLOATERSAVECAMERAPRESET_H + +#include "llmodaldialog.h" + +class LLComboBox; +class LLRadioGroup; +class LLLineEditor; + +class LLFloaterSaveCameraPreset : public LLModalDialog +{ + +public: + LLFloaterSaveCameraPreset(const LLSD &key); + + bool postBuild() override; + void onOpen(const LLSD& key) override; + + void onBtnSave(); + void onBtnCancel(); + void onSwitchSaveReplace(); + +private: + LLRadioGroup* mSaveRadioGroup; + LLLineEditor* mNameEditor; + LLComboBox* mPresetCombo; + LLButton* mSaveButton; + + void onPresetsListChange(); + void onPresetNameEdited(); +}; + +#endif // LL_LLFLOATERSAVECAMERAPRESET_H diff --git a/indra/newview/llfloatersaveprefpreset.cpp b/indra/newview/llfloatersaveprefpreset.cpp index fa62df1ffc..170f74abd3 100644 --- a/indra/newview/llfloatersaveprefpreset.cpp +++ b/indra/newview/llfloatersaveprefpreset.cpp @@ -1,115 +1,115 @@ -/** - * @file llfloatersaveprefpreset.cpp - * @brief Floater to save a graphics preset - * - * $LicenseInfo:firstyear=2014&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2014, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloatersaveprefpreset.h" - -#include "llbutton.h" -#include "llcombobox.h" -#include "llfloaterpreference.h" -#include "llfloaterreg.h" -#include "llnotificationsutil.h" -#include "llpresetsmanager.h" -#include "lltrans.h" - -LLFloaterSavePrefPreset::LLFloaterSavePrefPreset(const LLSD &key) - : LLFloater(key) -{ -} - -// virtual -bool LLFloaterSavePrefPreset::postBuild() -{ - LLFloaterPreference* preferences = LLFloaterReg::getTypedInstance("preferences"); - if (preferences) - { - preferences->addDependentFloater(this); - } - - getChild("preset_combo")->setTextEntryCallback(boost::bind(&LLFloaterSavePrefPreset::onPresetNameEdited, this)); - getChild("preset_combo")->setCommitCallback(boost::bind(&LLFloaterSavePrefPreset::onPresetNameEdited, this)); - getChild("save")->setCommitCallback(boost::bind(&LLFloaterSavePrefPreset::onBtnSave, this)); - - getChild("cancel")->setCommitCallback(boost::bind(&LLFloaterSavePrefPreset::onBtnCancel, this)); - - LLPresetsManager::instance().setPresetListChangeCallback(boost::bind(&LLFloaterSavePrefPreset::onPresetsListChange, this)); - - mSaveButton = getChild("save"); - mPresetCombo = getChild("preset_combo"); - - return true; -} - -void LLFloaterSavePrefPreset::onPresetNameEdited() -{ - // Disable saving a preset having empty name. - std::string name = mPresetCombo->getSimple(); - - mSaveButton->setEnabled(!name.empty()); -} - -void LLFloaterSavePrefPreset::onOpen(const LLSD& key) -{ - mSubdirectory = key.asString(); - - EDefaultOptions option = DEFAULT_HIDE; - LLPresetsManager::getInstance()->setPresetNamesInComboBox(mSubdirectory, mPresetCombo, option); - - onPresetNameEdited(); -} - -void LLFloaterSavePrefPreset::onBtnSave() -{ - std::string name = mPresetCombo->getSimple(); - - std::string upper_name(name); - LLStringUtil::toUpper(upper_name); - - if ((name == LLTrans::getString(PRESETS_DEFAULT)) || (upper_name == PRESETS_DEFAULT_UPPER)) - { - LLNotificationsUtil::add("DefaultPresetNotSaved"); - } - else if (!LLPresetsManager::getInstance()->savePreset(mSubdirectory, name)) - { - LLSD args; - args["NAME"] = name; - LLNotificationsUtil::add("PresetNotSaved", args); - } - - closeFloater(); -} - -void LLFloaterSavePrefPreset::onPresetsListChange() -{ - EDefaultOptions option = DEFAULT_HIDE; - LLPresetsManager::getInstance()->setPresetNamesInComboBox(mSubdirectory, mPresetCombo, option); -} - -void LLFloaterSavePrefPreset::onBtnCancel() -{ - closeFloater(); -} +/** + * @file llfloatersaveprefpreset.cpp + * @brief Floater to save a graphics preset + * + * $LicenseInfo:firstyear=2014&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatersaveprefpreset.h" + +#include "llbutton.h" +#include "llcombobox.h" +#include "llfloaterpreference.h" +#include "llfloaterreg.h" +#include "llnotificationsutil.h" +#include "llpresetsmanager.h" +#include "lltrans.h" + +LLFloaterSavePrefPreset::LLFloaterSavePrefPreset(const LLSD &key) + : LLFloater(key) +{ +} + +// virtual +bool LLFloaterSavePrefPreset::postBuild() +{ + LLFloaterPreference* preferences = LLFloaterReg::getTypedInstance("preferences"); + if (preferences) + { + preferences->addDependentFloater(this); + } + + getChild("preset_combo")->setTextEntryCallback(boost::bind(&LLFloaterSavePrefPreset::onPresetNameEdited, this)); + getChild("preset_combo")->setCommitCallback(boost::bind(&LLFloaterSavePrefPreset::onPresetNameEdited, this)); + getChild("save")->setCommitCallback(boost::bind(&LLFloaterSavePrefPreset::onBtnSave, this)); + + getChild("cancel")->setCommitCallback(boost::bind(&LLFloaterSavePrefPreset::onBtnCancel, this)); + + LLPresetsManager::instance().setPresetListChangeCallback(boost::bind(&LLFloaterSavePrefPreset::onPresetsListChange, this)); + + mSaveButton = getChild("save"); + mPresetCombo = getChild("preset_combo"); + + return true; +} + +void LLFloaterSavePrefPreset::onPresetNameEdited() +{ + // Disable saving a preset having empty name. + std::string name = mPresetCombo->getSimple(); + + mSaveButton->setEnabled(!name.empty()); +} + +void LLFloaterSavePrefPreset::onOpen(const LLSD& key) +{ + mSubdirectory = key.asString(); + + EDefaultOptions option = DEFAULT_HIDE; + LLPresetsManager::getInstance()->setPresetNamesInComboBox(mSubdirectory, mPresetCombo, option); + + onPresetNameEdited(); +} + +void LLFloaterSavePrefPreset::onBtnSave() +{ + std::string name = mPresetCombo->getSimple(); + + std::string upper_name(name); + LLStringUtil::toUpper(upper_name); + + if ((name == LLTrans::getString(PRESETS_DEFAULT)) || (upper_name == PRESETS_DEFAULT_UPPER)) + { + LLNotificationsUtil::add("DefaultPresetNotSaved"); + } + else if (!LLPresetsManager::getInstance()->savePreset(mSubdirectory, name)) + { + LLSD args; + args["NAME"] = name; + LLNotificationsUtil::add("PresetNotSaved", args); + } + + closeFloater(); +} + +void LLFloaterSavePrefPreset::onPresetsListChange() +{ + EDefaultOptions option = DEFAULT_HIDE; + LLPresetsManager::getInstance()->setPresetNamesInComboBox(mSubdirectory, mPresetCombo, option); +} + +void LLFloaterSavePrefPreset::onBtnCancel() +{ + closeFloater(); +} diff --git a/indra/newview/llfloatersaveprefpreset.h b/indra/newview/llfloatersaveprefpreset.h index 3293e4d0e3..db48749e38 100644 --- a/indra/newview/llfloatersaveprefpreset.h +++ b/indra/newview/llfloatersaveprefpreset.h @@ -1,58 +1,58 @@ -/** - * @file llfloatersaveprefpreset.h - * @brief Floater to save a graphics preset - - * - * $LicenseInfo:firstyear=2014&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2014, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERSAVEPREFPRESET_H -#define LL_LLFLOATERSAVEPREFPRESET_H - -#include "llfloater.h" - -class LLComboBox; - -class LLFloaterSavePrefPreset : public LLFloater -{ - -public: - LLFloaterSavePrefPreset(const LLSD &key); - - bool postBuild() override; - void onOpen(const LLSD& key) override; - - void onBtnSave(); - void onBtnCancel(); - -private: - - LLComboBox* mPresetCombo; - LLButton* mSaveButton; - - void onPresetsListChange(); - void onPresetNameEdited(); - - std::string mSubdirectory; -}; - -#endif // LL_LLFLOATERSAVEPREFPRESET_H +/** + * @file llfloatersaveprefpreset.h + * @brief Floater to save a graphics preset + + * + * $LicenseInfo:firstyear=2014&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERSAVEPREFPRESET_H +#define LL_LLFLOATERSAVEPREFPRESET_H + +#include "llfloater.h" + +class LLComboBox; + +class LLFloaterSavePrefPreset : public LLFloater +{ + +public: + LLFloaterSavePrefPreset(const LLSD &key); + + bool postBuild() override; + void onOpen(const LLSD& key) override; + + void onBtnSave(); + void onBtnCancel(); + +private: + + LLComboBox* mPresetCombo; + LLButton* mSaveButton; + + void onPresetsListChange(); + void onPresetNameEdited(); + + std::string mSubdirectory; +}; + +#endif // LL_LLFLOATERSAVEPREFPRESET_H diff --git a/indra/newview/llfloatersceneloadstats.cpp b/indra/newview/llfloatersceneloadstats.cpp index ff396c4625..58b32bde9b 100644 --- a/indra/newview/llfloatersceneloadstats.cpp +++ b/indra/newview/llfloatersceneloadstats.cpp @@ -1,40 +1,40 @@ -/** - * @file llfloatersceneloadstats.cpp - * @author Richard Nelson - * @brief debug floater for measuring various scene load statistics - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2013, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloatersceneloadstats.h" - - -LLFloaterSceneLoadStats::LLFloaterSceneLoadStats( const LLSD& key ) -: LLFloater(key) -{} - -bool LLFloaterSceneLoadStats::postBuild() -{ - return true; -} +/** + * @file llfloatersceneloadstats.cpp + * @author Richard Nelson + * @brief debug floater for measuring various scene load statistics + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatersceneloadstats.h" + + +LLFloaterSceneLoadStats::LLFloaterSceneLoadStats( const LLSD& key ) +: LLFloater(key) +{} + +bool LLFloaterSceneLoadStats::postBuild() +{ + return true; +} diff --git a/indra/newview/llfloatersceneloadstats.h b/indra/newview/llfloatersceneloadstats.h index ab4e78182b..1522e54137 100644 --- a/indra/newview/llfloatersceneloadstats.h +++ b/indra/newview/llfloatersceneloadstats.h @@ -1,43 +1,43 @@ -/** - * @file llfloatersceneloadstats.h - * @brief debug floater for measuring various scene load statistics - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2013, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_FLOATERSCENELOADSTATS_H -#define LL_FLOATERSCENELOADSTATS_H - -#include "llfloater.h" - -class LLFloaterSceneLoadStats : public LLFloater -{ - friend class LLFloaterReg; -private: - LLFloaterSceneLoadStats(const LLSD& key); - -public: - bool postBuild() override; - -}; - -#endif // LL_FLOATERSCENELOADSTATS_H +/** + * @file llfloatersceneloadstats.h + * @brief debug floater for measuring various scene load statistics + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_FLOATERSCENELOADSTATS_H +#define LL_FLOATERSCENELOADSTATS_H + +#include "llfloater.h" + +class LLFloaterSceneLoadStats : public LLFloater +{ + friend class LLFloaterReg; +private: + LLFloaterSceneLoadStats(const LLSD& key); + +public: + bool postBuild() override; + +}; + +#endif // LL_FLOATERSCENELOADSTATS_H diff --git a/indra/newview/llfloaterscriptdebug.cpp b/indra/newview/llfloaterscriptdebug.cpp index 0449ea3500..49cb5d34ff 100644 --- a/indra/newview/llfloaterscriptdebug.cpp +++ b/indra/newview/llfloaterscriptdebug.cpp @@ -1,217 +1,217 @@ -/** - * @file llfloaterscriptdebug.cpp - * @brief Chat window for showing script errors and warnings - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterscriptdebug.h" - -#include "llfloaterreg.h" -#include "lluictrlfactory.h" -#include "llfontgl.h" -#include "llrect.h" -#include "llerror.h" -#include "llstring.h" -#include "llvoavatarself.h" -#include "message.h" - -// project include -#include "llviewertexteditor.h" -#include "llviewercontrol.h" -#include "llviewerobjectlist.h" -#include "llviewertexturelist.h" - -// -// Statics -// - -// -// Member Functions -// -LLFloaterScriptDebug::LLFloaterScriptDebug(const LLSD& key) - : LLMultiFloater(key) -{ - // avoid resizing of the window to match - // the initial size of the tabbed-childs, whenever a tab is opened or closed - mAutoResize = false; - // enabled autocous blocks controling focus via LLFloaterReg::showInstance - setAutoFocus(false); -} - -LLFloaterScriptDebug::~LLFloaterScriptDebug() -{ -} - -void LLFloaterScriptDebug::show(const LLUUID& object_id) -{ - addOutputWindow(object_id); -} - -bool LLFloaterScriptDebug::postBuild() -{ - LLMultiFloater::postBuild(); - - if (mTabContainer) - { - return true; - } - - return false; -} - -void LLFloaterScriptDebug::setVisible(bool visible) -{ - if(visible) - { - LLFloaterScriptDebugOutput* floater_output = LLFloaterReg::findTypedInstance("script_debug_output", LLUUID::null); - if (floater_output == NULL) - { - floater_output = dynamic_cast(LLFloaterReg::showInstance("script_debug_output", LLUUID::null, false)); - if (floater_output) - { - addFloater(floater_output, false); - } - } - - } - LLMultiFloater::setVisible(visible); -} - -void LLFloaterScriptDebug::closeFloater(bool app_quitting/* = false*/) -{ - if(app_quitting) - { - LLMultiFloater::closeFloater(app_quitting); - } - else - { - setVisible(false); - } -} - -LLFloater* LLFloaterScriptDebug::addOutputWindow(const LLUUID &object_id) -{ - LLMultiFloater* host = LLFloaterReg::showTypedInstance("script_debug", LLSD()); - if (!host) - return NULL; - - LLFloater::setFloaterHost(host); - // prevent stealing focus, see EXT-8040 - LLFloater* floaterp = LLFloaterReg::showInstance("script_debug_output", object_id, false); - LLFloater::setFloaterHost(NULL); - - return floaterp; -} - -void LLFloaterScriptDebug::addScriptLine(const std::string &utf8mesg, const std::string &user_name, const LLColor4& color, const LLUUID& source_id) -{ - LLViewerObject* objectp = gObjectList.findObject(source_id); - std::string floater_label; - - // Handle /me messages. - std::string prefix = utf8mesg.substr(0, 4); - std::string message = (prefix == "/me " || prefix == "/me'") ? user_name + utf8mesg.substr(3) : utf8mesg; - - if (objectp) - { - if(objectp->isHUDAttachment()) - { - if (isAgentAvatarValid()) - { - ((LLViewerObject*)gAgentAvatarp)->setIcon(LLViewerTextureManager::getFetchedTextureFromFile("script_error.j2c", FTT_LOCAL_FILE, true, LLGLTexture::BOOST_UI)); - } - } - else - { - objectp->setIcon(LLViewerTextureManager::getFetchedTextureFromFile("script_error.j2c", FTT_LOCAL_FILE, true, LLGLTexture::BOOST_UI)); - } - floater_label = llformat("%s(%.0f, %.0f, %.0f)", - user_name.c_str(), - objectp->getPositionRegion().mV[VX], - objectp->getPositionRegion().mV[VY], - objectp->getPositionRegion().mV[VZ]); - } - else - { - floater_label = user_name; - } - - addOutputWindow(source_id); - - // add to "All" floater - LLFloaterScriptDebugOutput* floaterp = LLFloaterReg::getTypedInstance("script_debug_output", LLUUID::null); - if (floaterp) - { - floaterp->addLine(message, user_name, color); - } - - // add to specific script instance floater - floaterp = LLFloaterReg::getTypedInstance("script_debug_output", source_id); - if (floaterp) - { - floaterp->addLine(message, floater_label, color); - } -} - -// -// LLFloaterScriptDebugOutput -// - -LLFloaterScriptDebugOutput::LLFloaterScriptDebugOutput(const LLSD& object_id) - : LLFloater(LLSD(object_id)), - mObjectID(object_id.asUUID()) -{ - // enabled autocous blocks controling focus via LLFloaterReg::showInstance - setAutoFocus(false); -} - -bool LLFloaterScriptDebugOutput::postBuild() -{ - LLFloater::postBuild(); - mHistoryEditor = getChild("Chat History Editor"); - return true; -} - -LLFloaterScriptDebugOutput::~LLFloaterScriptDebugOutput() -{ -} - -void LLFloaterScriptDebugOutput::addLine(const std::string &utf8mesg, const std::string &user_name, const LLColor4& color) -{ - if (mObjectID.isNull()) - { - setCanTearOff(false); - setCanClose(false); - } - else - { - setTitle(user_name); - setShortTitle(user_name); - } - - mHistoryEditor->appendText(utf8mesg, true, LLStyle::Params().color(color)); - mHistoryEditor->blockUndo(); -} - +/** + * @file llfloaterscriptdebug.cpp + * @brief Chat window for showing script errors and warnings + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterscriptdebug.h" + +#include "llfloaterreg.h" +#include "lluictrlfactory.h" +#include "llfontgl.h" +#include "llrect.h" +#include "llerror.h" +#include "llstring.h" +#include "llvoavatarself.h" +#include "message.h" + +// project include +#include "llviewertexteditor.h" +#include "llviewercontrol.h" +#include "llviewerobjectlist.h" +#include "llviewertexturelist.h" + +// +// Statics +// + +// +// Member Functions +// +LLFloaterScriptDebug::LLFloaterScriptDebug(const LLSD& key) + : LLMultiFloater(key) +{ + // avoid resizing of the window to match + // the initial size of the tabbed-childs, whenever a tab is opened or closed + mAutoResize = false; + // enabled autocous blocks controling focus via LLFloaterReg::showInstance + setAutoFocus(false); +} + +LLFloaterScriptDebug::~LLFloaterScriptDebug() +{ +} + +void LLFloaterScriptDebug::show(const LLUUID& object_id) +{ + addOutputWindow(object_id); +} + +bool LLFloaterScriptDebug::postBuild() +{ + LLMultiFloater::postBuild(); + + if (mTabContainer) + { + return true; + } + + return false; +} + +void LLFloaterScriptDebug::setVisible(bool visible) +{ + if(visible) + { + LLFloaterScriptDebugOutput* floater_output = LLFloaterReg::findTypedInstance("script_debug_output", LLUUID::null); + if (floater_output == NULL) + { + floater_output = dynamic_cast(LLFloaterReg::showInstance("script_debug_output", LLUUID::null, false)); + if (floater_output) + { + addFloater(floater_output, false); + } + } + + } + LLMultiFloater::setVisible(visible); +} + +void LLFloaterScriptDebug::closeFloater(bool app_quitting/* = false*/) +{ + if(app_quitting) + { + LLMultiFloater::closeFloater(app_quitting); + } + else + { + setVisible(false); + } +} + +LLFloater* LLFloaterScriptDebug::addOutputWindow(const LLUUID &object_id) +{ + LLMultiFloater* host = LLFloaterReg::showTypedInstance("script_debug", LLSD()); + if (!host) + return NULL; + + LLFloater::setFloaterHost(host); + // prevent stealing focus, see EXT-8040 + LLFloater* floaterp = LLFloaterReg::showInstance("script_debug_output", object_id, false); + LLFloater::setFloaterHost(NULL); + + return floaterp; +} + +void LLFloaterScriptDebug::addScriptLine(const std::string &utf8mesg, const std::string &user_name, const LLColor4& color, const LLUUID& source_id) +{ + LLViewerObject* objectp = gObjectList.findObject(source_id); + std::string floater_label; + + // Handle /me messages. + std::string prefix = utf8mesg.substr(0, 4); + std::string message = (prefix == "/me " || prefix == "/me'") ? user_name + utf8mesg.substr(3) : utf8mesg; + + if (objectp) + { + if(objectp->isHUDAttachment()) + { + if (isAgentAvatarValid()) + { + ((LLViewerObject*)gAgentAvatarp)->setIcon(LLViewerTextureManager::getFetchedTextureFromFile("script_error.j2c", FTT_LOCAL_FILE, true, LLGLTexture::BOOST_UI)); + } + } + else + { + objectp->setIcon(LLViewerTextureManager::getFetchedTextureFromFile("script_error.j2c", FTT_LOCAL_FILE, true, LLGLTexture::BOOST_UI)); + } + floater_label = llformat("%s(%.0f, %.0f, %.0f)", + user_name.c_str(), + objectp->getPositionRegion().mV[VX], + objectp->getPositionRegion().mV[VY], + objectp->getPositionRegion().mV[VZ]); + } + else + { + floater_label = user_name; + } + + addOutputWindow(source_id); + + // add to "All" floater + LLFloaterScriptDebugOutput* floaterp = LLFloaterReg::getTypedInstance("script_debug_output", LLUUID::null); + if (floaterp) + { + floaterp->addLine(message, user_name, color); + } + + // add to specific script instance floater + floaterp = LLFloaterReg::getTypedInstance("script_debug_output", source_id); + if (floaterp) + { + floaterp->addLine(message, floater_label, color); + } +} + +// +// LLFloaterScriptDebugOutput +// + +LLFloaterScriptDebugOutput::LLFloaterScriptDebugOutput(const LLSD& object_id) + : LLFloater(LLSD(object_id)), + mObjectID(object_id.asUUID()) +{ + // enabled autocous blocks controling focus via LLFloaterReg::showInstance + setAutoFocus(false); +} + +bool LLFloaterScriptDebugOutput::postBuild() +{ + LLFloater::postBuild(); + mHistoryEditor = getChild("Chat History Editor"); + return true; +} + +LLFloaterScriptDebugOutput::~LLFloaterScriptDebugOutput() +{ +} + +void LLFloaterScriptDebugOutput::addLine(const std::string &utf8mesg, const std::string &user_name, const LLColor4& color) +{ + if (mObjectID.isNull()) + { + setCanTearOff(false); + setCanClose(false); + } + else + { + setTitle(user_name); + setShortTitle(user_name); + } + + mHistoryEditor->appendText(utf8mesg, true, LLStyle::Params().color(color)); + mHistoryEditor->blockUndo(); +} + diff --git a/indra/newview/llfloaterscriptdebug.h b/indra/newview/llfloaterscriptdebug.h index 0f455b7db5..972202257b 100644 --- a/indra/newview/llfloaterscriptdebug.h +++ b/indra/newview/llfloaterscriptdebug.h @@ -1,70 +1,70 @@ -/** - * @file llfloaterscriptdebug.h - * @brief Shows error and warning output from scripts - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERSCRIPTDEBUG_H -#define LL_LLFLOATERSCRIPTDEBUG_H - -#include "llmultifloater.h" - -class LLTextEditor; -class LLUUID; - -class LLFloaterScriptDebug : public LLMultiFloater -{ -public: - LLFloaterScriptDebug(const LLSD& key); - virtual ~LLFloaterScriptDebug(); - virtual bool postBuild(); - virtual void setVisible(bool visible); - static void show(const LLUUID& object_id); - - /*virtual*/ void closeFloater(bool app_quitting = false); - static void addScriptLine(const std::string &utf8mesg, const std::string &user_name, const LLColor4& color, const LLUUID& source_id); - -protected: - static LLFloater* addOutputWindow(const LLUUID& object_id); - -protected: - static LLFloaterScriptDebug* sInstance; -}; - -class LLFloaterScriptDebugOutput : public LLFloater -{ -public: - LLFloaterScriptDebugOutput(const LLSD& object_id); - ~LLFloaterScriptDebugOutput(); - - void addLine(const std::string &utf8mesg, const std::string &user_name, const LLColor4& color); - - virtual bool postBuild(); - -protected: - LLTextEditor* mHistoryEditor; - - LLUUID mObjectID; -}; - -#endif // LL_LLFLOATERSCRIPTDEBUG_H +/** + * @file llfloaterscriptdebug.h + * @brief Shows error and warning output from scripts + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERSCRIPTDEBUG_H +#define LL_LLFLOATERSCRIPTDEBUG_H + +#include "llmultifloater.h" + +class LLTextEditor; +class LLUUID; + +class LLFloaterScriptDebug : public LLMultiFloater +{ +public: + LLFloaterScriptDebug(const LLSD& key); + virtual ~LLFloaterScriptDebug(); + virtual bool postBuild(); + virtual void setVisible(bool visible); + static void show(const LLUUID& object_id); + + /*virtual*/ void closeFloater(bool app_quitting = false); + static void addScriptLine(const std::string &utf8mesg, const std::string &user_name, const LLColor4& color, const LLUUID& source_id); + +protected: + static LLFloater* addOutputWindow(const LLUUID& object_id); + +protected: + static LLFloaterScriptDebug* sInstance; +}; + +class LLFloaterScriptDebugOutput : public LLFloater +{ +public: + LLFloaterScriptDebugOutput(const LLSD& object_id); + ~LLFloaterScriptDebugOutput(); + + void addLine(const std::string &utf8mesg, const std::string &user_name, const LLColor4& color); + + virtual bool postBuild(); + +protected: + LLTextEditor* mHistoryEditor; + + LLUUID mObjectID; +}; + +#endif // LL_LLFLOATERSCRIPTDEBUG_H diff --git a/indra/newview/llfloaterscriptedprefs.cpp b/indra/newview/llfloaterscriptedprefs.cpp index 110a437f1d..a38c4b51f2 100644 --- a/indra/newview/llfloaterscriptedprefs.cpp +++ b/indra/newview/llfloaterscriptedprefs.cpp @@ -1,65 +1,65 @@ -/** - * @file llfloaterscriptedprefs.cpp - * @brief Color controls for the script editor - * @author Cinder Roxley - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2014, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llfloaterscriptedprefs.h" - -#include "llcolorswatch.h" -#include "llscripteditor.h" - - -LLFloaterScriptEdPrefs::LLFloaterScriptEdPrefs(const LLSD& key) -: LLFloater(key) -, mEditor(NULL) -{ - mCommitCallbackRegistrar.add("ScriptPref.applyUIColor", boost::bind(&LLFloaterScriptEdPrefs::applyUIColor, this ,_1, _2)); - mCommitCallbackRegistrar.add("ScriptPref.getUIColor", boost::bind(&LLFloaterScriptEdPrefs::getUIColor, this ,_1, _2)); -} - -bool LLFloaterScriptEdPrefs::postBuild() -{ - mEditor = getChild("Script Preview"); - if (mEditor) - { - mEditor->initKeywords(); - mEditor->loadKeywords(); - } - return true; -} - -void LLFloaterScriptEdPrefs::applyUIColor(LLUICtrl* ctrl, const LLSD& param) -{ - LLUIColorTable::instance().setColor(param.asString(), LLColor4(ctrl->getValue())); - mEditor->initKeywords(); - mEditor->loadKeywords(); -} - -void LLFloaterScriptEdPrefs::getUIColor(LLUICtrl* ctrl, const LLSD& param) -{ - LLColorSwatchCtrl* color_swatch = dynamic_cast(ctrl); - color_swatch->setOriginal(LLUIColorTable::instance().getColor(param.asString())); -} +/** + * @file llfloaterscriptedprefs.cpp + * @brief Color controls for the script editor + * @author Cinder Roxley + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llfloaterscriptedprefs.h" + +#include "llcolorswatch.h" +#include "llscripteditor.h" + + +LLFloaterScriptEdPrefs::LLFloaterScriptEdPrefs(const LLSD& key) +: LLFloater(key) +, mEditor(NULL) +{ + mCommitCallbackRegistrar.add("ScriptPref.applyUIColor", boost::bind(&LLFloaterScriptEdPrefs::applyUIColor, this ,_1, _2)); + mCommitCallbackRegistrar.add("ScriptPref.getUIColor", boost::bind(&LLFloaterScriptEdPrefs::getUIColor, this ,_1, _2)); +} + +bool LLFloaterScriptEdPrefs::postBuild() +{ + mEditor = getChild("Script Preview"); + if (mEditor) + { + mEditor->initKeywords(); + mEditor->loadKeywords(); + } + return true; +} + +void LLFloaterScriptEdPrefs::applyUIColor(LLUICtrl* ctrl, const LLSD& param) +{ + LLUIColorTable::instance().setColor(param.asString(), LLColor4(ctrl->getValue())); + mEditor->initKeywords(); + mEditor->loadKeywords(); +} + +void LLFloaterScriptEdPrefs::getUIColor(LLUICtrl* ctrl, const LLSD& param) +{ + LLColorSwatchCtrl* color_swatch = dynamic_cast(ctrl); + color_swatch->setOriginal(LLUIColorTable::instance().getColor(param.asString())); +} diff --git a/indra/newview/llfloaterscriptedprefs.h b/indra/newview/llfloaterscriptedprefs.h index f3bac311bb..6654832a6d 100644 --- a/indra/newview/llfloaterscriptedprefs.h +++ b/indra/newview/llfloaterscriptedprefs.h @@ -1,51 +1,51 @@ -/** - * @file llfloaterscriptedprefs.h - * @brief Color controls for the script editor - * @author Cinder Roxley - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2014, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_FLOATERSCRIPTEDPREFS_H -#define LL_FLOATERSCRIPTEDPREFS_H - -#include "llfloater.h" - -class LLScriptEditor; -class LLUICtrl; - -class LLFloaterScriptEdPrefs : public LLFloater -{ -public: - LLFloaterScriptEdPrefs(const LLSD& key); - bool postBuild() override; - -private: - ~LLFloaterScriptEdPrefs() {}; - - void applyUIColor(LLUICtrl* ctrl, const LLSD& param); - void getUIColor(LLUICtrl* ctrl, const LLSD& param); - - LLScriptEditor* mEditor; -}; - -#endif // LL_FLOATERSCRIPTEDPREFS_H +/** + * @file llfloaterscriptedprefs.h + * @brief Color controls for the script editor + * @author Cinder Roxley + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_FLOATERSCRIPTEDPREFS_H +#define LL_FLOATERSCRIPTEDPREFS_H + +#include "llfloater.h" + +class LLScriptEditor; +class LLUICtrl; + +class LLFloaterScriptEdPrefs : public LLFloater +{ +public: + LLFloaterScriptEdPrefs(const LLSD& key); + bool postBuild() override; + +private: + ~LLFloaterScriptEdPrefs() {}; + + void applyUIColor(LLUICtrl* ctrl, const LLSD& param); + void getUIColor(LLUICtrl* ctrl, const LLSD& param); + + LLScriptEditor* mEditor; +}; + +#endif // LL_FLOATERSCRIPTEDPREFS_H diff --git a/indra/newview/llfloaterscriptlimits.cpp b/indra/newview/llfloaterscriptlimits.cpp index 71ee10bf4a..1f4e95c229 100644 --- a/indra/newview/llfloaterscriptlimits.cpp +++ b/indra/newview/llfloaterscriptlimits.cpp @@ -1,941 +1,941 @@ -/** - * @file llfloaterscriptlimits.cpp - * @author Gabriel Lee - * @brief Implementation of the region info and controls floater and panels. - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llfloaterscriptlimits.h" - -// library includes -#include "llavatarnamecache.h" -#include "llsdutil.h" -#include "llsdutil_math.h" -#include "message.h" - -#include "llagent.h" -#include "llfloateravatarpicker.h" -#include "llfloaterland.h" -#include "llfloaterreg.h" -#include "llregionhandle.h" -#include "llscrolllistctrl.h" -#include "llscrolllistitem.h" -#include "llparcel.h" -#include "lltabcontainer.h" -#include "lltracker.h" -#include "lltrans.h" -#include "llviewercontrol.h" -#include "lluictrlfactory.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" -#include "llviewerwindow.h" -#include "llcorehttputil.h" - -///---------------------------------------------------------------------------- -/// LLFloaterScriptLimits -///---------------------------------------------------------------------------- - -// debug switches, won't work in release -#ifndef LL_RELEASE_FOR_DOWNLOAD - -// dump responder replies to LL_INFOS() for debugging -//#define DUMP_REPLIES_TO_LLINFOS - -#ifdef DUMP_REPLIES_TO_LLINFOS -#include "llsdserialize.h" -#include "llwindow.h" -#endif - -// use fake LLSD responses to check the viewer side is working correctly -// I'm syncing this with the server side efforts so hopfully we can keep -// the to-ing and fro-ing between the two teams to a minimum -//#define USE_FAKE_RESPONSES - -#ifdef USE_FAKE_RESPONSES -const S32 FAKE_NUMBER_OF_URLS = 329; -const S32 FAKE_AVAILABLE_URLS = 731; -const S32 FAKE_AMOUNT_OF_MEMORY = 66741; -const S32 FAKE_AVAILABLE_MEMORY = 895577; -#endif - -#endif - -const S32 SIZE_OF_ONE_KB = 1024; - -LLFloaterScriptLimits::LLFloaterScriptLimits(const LLSD& seed) - : LLFloater(seed) -{ -} - -bool LLFloaterScriptLimits::postBuild() -{ - mTab = getChild("scriptlimits_panels"); - - if(!mTab) - { - LL_WARNS() << "Error! couldn't get scriptlimits_panels, aborting Script Information setup" << LL_ENDL; - return false; - } - - // contruct the panel - LLPanelScriptLimitsRegionMemory* panel_memory = new LLPanelScriptLimitsRegionMemory; - mInfoPanels.push_back(panel_memory); - panel_memory->buildFromFile( "panel_script_limits_region_memory.xml"); - mTab->addTabPanel(panel_memory); - mTab->selectTab(0); - return true; -} - -LLFloaterScriptLimits::~LLFloaterScriptLimits() -{ -} - -// public -void LLFloaterScriptLimits::refresh() -{ - for(info_panels_t::iterator iter = mInfoPanels.begin(); - iter != mInfoPanels.end(); ++iter) - { - (*iter)->refresh(); - } -} - -///---------------------------------------------------------------------------- -// Base class for panels -///---------------------------------------------------------------------------- - -LLPanelScriptLimitsInfo::LLPanelScriptLimitsInfo() - : LLPanel() -{ -} - - -// virtual -bool LLPanelScriptLimitsInfo::postBuild() -{ - refresh(); - return true; -} - -// virtual -void LLPanelScriptLimitsInfo::updateChild(LLUICtrl* child_ctr) -{ -} - -///---------------------------------------------------------------------------- -// Memory Panel -///---------------------------------------------------------------------------- - -LLPanelScriptLimitsRegionMemory::~LLPanelScriptLimitsRegionMemory() -{ - if(!mParcelId.isNull()) - { - LLRemoteParcelInfoProcessor::getInstance()->removeObserver(mParcelId, this); - mParcelId.setNull(); - } -}; - -bool LLPanelScriptLimitsRegionMemory::getLandScriptResources() -{ - if (!gAgent.getRegion()) return false; - - LLSD body; - std::string url = gAgent.getRegion()->getCapability("LandResources"); - if (!url.empty()) - { - LLCoros::instance().launch("LLPanelScriptLimitsRegionMemory::getLandScriptResourcesCoro", - boost::bind(&LLPanelScriptLimitsRegionMemory::getLandScriptResourcesCoro, this, url)); - return true; - } - else - { - return false; - } -} - -void LLPanelScriptLimitsRegionMemory::getLandScriptResourcesCoro(std::string url) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getLandScriptResourcesCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD postData; - - postData["parcel_id"] = mParcelId; - - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS() << "Failed to get script resource info" << LL_ENDL; - return; - } - - // We could retrieve these sequentially inline from this coroutine. But - // since the original code retrieved them in parallel I'll spawn two - // coroutines to do the retrieval. - - // The summary service: - if (result.has("ScriptResourceSummary")) - { - std::string urlResourceSummary = result["ScriptResourceSummary"].asString(); - LLCoros::instance().launch("LLPanelScriptLimitsRegionMemory::getLandScriptSummaryCoro", - boost::bind(&LLPanelScriptLimitsRegionMemory::getLandScriptSummaryCoro, this, urlResourceSummary)); - } - - if (result.has("ScriptResourceDetails")) - { - std::string urlResourceDetails = result["ScriptResourceDetails"].asString(); - LLCoros::instance().launch("LLPanelScriptLimitsRegionMemory::getLandScriptDetailsCoro", - boost::bind(&LLPanelScriptLimitsRegionMemory::getLandScriptDetailsCoro, this, urlResourceDetails)); - } - - -} - -void LLPanelScriptLimitsRegionMemory::getLandScriptSummaryCoro(std::string url) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getLandScriptSummaryCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD result = httpAdapter->getAndSuspend(httpRequest, url); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS() << "Unable to retrieve script summary." << LL_ENDL; - return; - } - - LLFloaterScriptLimits* instance = LLFloaterReg::getTypedInstance("script_limits"); - if (!instance) - { - LL_WARNS() << "Failed to get llfloaterscriptlimits instance" << LL_ENDL; - return; - } - - LLTabContainer* tab = instance->getChild("scriptlimits_panels"); - if (!tab) - { - LL_WARNS() << "Unable to access script limits tab" << LL_ENDL; - return; - } - - LLPanelScriptLimitsRegionMemory* panelMemory = (LLPanelScriptLimitsRegionMemory*)tab->getChild("script_limits_region_memory_panel"); - if (!panelMemory) - { - LL_WARNS() << "Unable to get memory panel." << LL_ENDL; - return; - } - - panelMemory->getChild("loading_text")->setValue(LLSD(std::string(""))); - - LLButton* btn = panelMemory->getChild("refresh_list_btn"); - if (btn) - { - btn->setEnabled(true); - } - - result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); - panelMemory->setRegionSummary(result); - -} - -void LLPanelScriptLimitsRegionMemory::getLandScriptDetailsCoro(std::string url) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getLandScriptDetailsCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD result = httpAdapter->getAndSuspend(httpRequest, url); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS() << "Unable to retrieve script details." << LL_ENDL; - return; - } - - LLFloaterScriptLimits* instance = LLFloaterReg::getTypedInstance("script_limits"); - - if (!instance) - { - LL_WARNS() << "Failed to get llfloaterscriptlimits instance" << LL_ENDL; - return; - } - - LLTabContainer* tab = instance->getChild("scriptlimits_panels"); - if (!tab) - { - LL_WARNS() << "Unable to access script limits tab" << LL_ENDL; - return; - } - - LLPanelScriptLimitsRegionMemory* panelMemory = (LLPanelScriptLimitsRegionMemory*)tab->getChild("script_limits_region_memory_panel"); - - if (!panelMemory) - { - LL_WARNS() << "Unable to get memory panel." << LL_ENDL; - return; - } - - result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); - panelMemory->setRegionDetails(result); -} - -void LLPanelScriptLimitsRegionMemory::processParcelInfo(const LLParcelData& parcel_data) -{ - if(!getLandScriptResources()) - { - std::string msg_error = LLTrans::getString("ScriptLimitsRequestError"); - getChild("loading_text")->setValue(LLSD(msg_error)); - } - else - { - std::string msg_waiting = LLTrans::getString("ScriptLimitsRequestWaiting"); - getChild("loading_text")->setValue(LLSD(msg_waiting)); - } -} - -void LLPanelScriptLimitsRegionMemory::setParcelID(const LLUUID& parcel_id) -{ - if (!parcel_id.isNull()) - { - if(!mParcelId.isNull()) - { - LLRemoteParcelInfoProcessor::getInstance()->removeObserver(mParcelId, this); - mParcelId.setNull(); - } - mParcelId = parcel_id; - LLRemoteParcelInfoProcessor::getInstance()->addObserver(parcel_id, this); - LLRemoteParcelInfoProcessor::getInstance()->sendParcelInfoRequest(parcel_id); - } - else - { - std::string msg_error = LLTrans::getString("ScriptLimitsRequestError"); - getChild("loading_text")->setValue(LLSD(msg_error)); - } -} - -// virtual -void LLPanelScriptLimitsRegionMemory::setErrorStatus(S32 status, const std::string& reason) -{ - LL_WARNS() << "Can't handle remote parcel request."<< " Http Status: "<< status << ". Reason : "<< reason<("scripts_list"); - if(!list) - { - return; - } - - std::string name = LLCacheName::buildUsername(full_name); - - std::vector::iterator id_itor; - for (id_itor = mObjectListItems.begin(); id_itor != mObjectListItems.end(); ++id_itor) - { - LLSD element = *id_itor; - if(element["owner_id"].asUUID() == id) - { - LLScrollListItem* item = list->getItem(element["id"].asUUID()); - - if(item) - { - item->getColumn(3)->setValue(LLSD(name)); - element["columns"][3]["value"] = name; - } - } - } -} - -void LLPanelScriptLimitsRegionMemory::setRegionDetails(LLSD content) -{ - LLScrollListCtrl *list = getChild("scripts_list"); - - if(!list) - { - LL_WARNS() << "Error getting the scripts_list control" << LL_ENDL; - return; - } - - S32 number_parcels = content["parcels"].size(); - - LLStringUtil::format_map_t args_parcels; - args_parcels["[PARCELS]"] = llformat ("%d", number_parcels); - std::string msg_parcels = LLTrans::getString("ScriptLimitsParcelsOwned", args_parcels); - getChild("parcels_listed")->setValue(LLSD(msg_parcels)); - - uuid_vec_t names_requested; - - // This makes the assumption that all objects will have the same set - // of attributes, ie they will all have, or none will have locations - // This is a pretty safe assumption as it's reliant on server version. - bool has_locations = false; - bool has_local_ids = false; - - for(S32 i = 0; i < number_parcels; i++) - { - std::string parcel_name = content["parcels"][i]["name"].asString(); - S32 number_objects = content["parcels"][i]["objects"].size(); - - S32 local_id = 0; - if(content["parcels"][i].has("local_id")) - { - // if any locations are found flag that we can use them and turn on the highlight button - has_local_ids = true; - local_id = content["parcels"][i]["local_id"].asInteger(); - } - - for(S32 j = 0; j < number_objects; j++) - { - S32 size = content["parcels"][i]["objects"][j]["resources"]["memory"].asInteger() / SIZE_OF_ONE_KB; - - S32 urls = content["parcels"][i]["objects"][j]["resources"]["urls"].asInteger(); - - std::string name_buf = content["parcels"][i]["objects"][j]["name"].asString(); - LLUUID task_id = content["parcels"][i]["objects"][j]["id"].asUUID(); - LLUUID owner_id = content["parcels"][i]["objects"][j]["owner_id"].asUUID(); - // This field may not be sent by all server versions, but it's OK if - // it uses the LLSD default of false - bool is_group_owned = content["parcels"][i]["objects"][j]["is_group_owned"].asBoolean(); - - F32 location_x = 0.0f; - F32 location_y = 0.0f; - F32 location_z = 0.0f; - - if(content["parcels"][i]["objects"][j].has("location")) - { - // if any locations are found flag that we can use them and turn on the highlight button - LLVector3 vec = ll_vector3_from_sd(content["parcels"][i]["objects"][j]["location"]); - has_locations = true; - location_x = vec.mV[0]; - location_y = vec.mV[1]; - location_z = vec.mV[2]; - } - - std::string owner_buf; - - // in the future the server will give us owner names, so see if we're there yet: - if(content["parcels"][i]["objects"][j].has("owner_name")) - { - owner_buf = content["parcels"][i]["objects"][j]["owner_name"].asString(); - } - // ...and if not use the slightly more painful method of disovery: - else - { - bool name_is_cached; - if (is_group_owned) - { - name_is_cached = gCacheName->getGroupName(owner_id, owner_buf); - } - else - { - LLAvatarName av_name; - name_is_cached = LLAvatarNameCache::get(owner_id, &av_name); - owner_buf = av_name.getUserName(); - owner_buf = LLCacheName::buildUsername(owner_buf); - } - if(!name_is_cached) - { - if(std::find(names_requested.begin(), names_requested.end(), owner_id) == names_requested.end()) - { - names_requested.push_back(owner_id); - if (is_group_owned) - { - gCacheName->getGroup(owner_id, - boost::bind(&LLPanelScriptLimitsRegionMemory::onNameCache, - this, _1, _2)); - } - else - { - LLAvatarNameCache::get(owner_id, - boost::bind(&LLPanelScriptLimitsRegionMemory::onAvatarNameCache, - this, _1, _2)); - } - } - } - } - - LLScrollListItem::Params item_params; - item_params.value = task_id; - - LLScrollListCell::Params cell_params; - cell_params.font = LLFontGL::getFontSansSerif(); - // Start out right justifying numeric displays - cell_params.font_halign = LLFontGL::RIGHT; - - cell_params.column = "size"; - cell_params.value = size; - item_params.columns.add(cell_params); - - cell_params.column = "urls"; - cell_params.value = urls; - item_params.columns.add(cell_params); - - cell_params.font_halign = LLFontGL::LEFT; - // The rest of the columns are text to left justify them - cell_params.column = "name"; - cell_params.value = name_buf; - item_params.columns.add(cell_params); - - cell_params.column = "owner"; - cell_params.value = owner_buf; - item_params.columns.add(cell_params); - - cell_params.column = "parcel"; - cell_params.value = parcel_name; - item_params.columns.add(cell_params); - - cell_params.column = "location"; - cell_params.value = has_locations - ? llformat("<%0.0f, %0.0f, %0.0f>", location_x, location_y, location_z) - : ""; - item_params.columns.add(cell_params); - - list->addRow(item_params); - - LLSD element; - element["owner_id"] = owner_id; - - element["id"] = task_id; - element["local_id"] = local_id; - mObjectListItems.push_back(element); - } - } - - if (has_locations) - { - LLButton* btn = getChild("highlight_btn"); - if(btn) - { - btn->setVisible(true); - } - } - - if (has_local_ids) - { - LLButton* btn = getChild("return_btn"); - if(btn) - { - btn->setVisible(true); - } - } - - // save the structure to make object return easier - mContent = content; -} - -void LLPanelScriptLimitsRegionMemory::setRegionSummary(LLSD content) -{ - if(content["summary"]["used"][0]["type"].asString() == std::string("memory")) - { - mParcelMemoryUsed = content["summary"]["used"][0]["amount"].asInteger() / SIZE_OF_ONE_KB; - mParcelMemoryMax = content["summary"]["available"][0]["amount"].asInteger() / SIZE_OF_ONE_KB; - mGotParcelMemoryUsed = true; - } - else if(content["summary"]["used"][1]["type"].asString() == std::string("memory")) - { - mParcelMemoryUsed = content["summary"]["used"][1]["amount"].asInteger() / SIZE_OF_ONE_KB; - mParcelMemoryMax = content["summary"]["available"][1]["amount"].asInteger() / SIZE_OF_ONE_KB; - mGotParcelMemoryUsed = true; - } - else - { - LL_WARNS() << "summary doesn't contain memory info" << LL_ENDL; - return; - } - - if(content["summary"]["used"][0]["type"].asString() == std::string("urls")) - { - mParcelURLsUsed = content["summary"]["used"][0]["amount"].asInteger(); - mParcelURLsMax = content["summary"]["available"][0]["amount"].asInteger(); - mGotParcelURLsUsed = true; - } - else if(content["summary"]["used"][1]["type"].asString() == std::string("urls")) - { - mParcelURLsUsed = content["summary"]["used"][1]["amount"].asInteger(); - mParcelURLsMax = content["summary"]["available"][1]["amount"].asInteger(); - mGotParcelURLsUsed = true; - } - else - { - LL_WARNS() << "summary doesn't contain urls info" << LL_ENDL; - return; - } - - if((mParcelMemoryUsed >= 0) && (mParcelMemoryMax >= 0)) - { - LLStringUtil::format_map_t args_parcel_memory; - args_parcel_memory["[COUNT]"] = llformat ("%d", mParcelMemoryUsed); - std::string translate_message = "ScriptLimitsMemoryUsedSimple"; - - if (0 < mParcelMemoryMax) - { - S32 parcel_memory_available = mParcelMemoryMax - mParcelMemoryUsed; - - args_parcel_memory["[MAX]"] = llformat ("%d", mParcelMemoryMax); - args_parcel_memory["[AVAILABLE]"] = llformat ("%d", parcel_memory_available); - translate_message = "ScriptLimitsMemoryUsed"; - } - - std::string msg_parcel_memory = LLTrans::getString(translate_message, args_parcel_memory); - getChild("memory_used")->setValue(LLSD(msg_parcel_memory)); - } - - if((mParcelURLsUsed >= 0) && (mParcelURLsMax >= 0)) - { - S32 parcel_urls_available = mParcelURLsMax - mParcelURLsUsed; - - LLStringUtil::format_map_t args_parcel_urls; - args_parcel_urls["[COUNT]"] = llformat ("%d", mParcelURLsUsed); - args_parcel_urls["[MAX]"] = llformat ("%d", mParcelURLsMax); - args_parcel_urls["[AVAILABLE]"] = llformat ("%d", parcel_urls_available); - std::string msg_parcel_urls = LLTrans::getString("ScriptLimitsURLsUsed", args_parcel_urls); - getChild("urls_used")->setValue(LLSD(msg_parcel_urls)); - } -} - -bool LLPanelScriptLimitsRegionMemory::postBuild() -{ - childSetAction("refresh_list_btn", onClickRefresh, this); - childSetAction("highlight_btn", onClickHighlight, this); - childSetAction("return_btn", onClickReturn, this); - - std::string msg_waiting = LLTrans::getString("ScriptLimitsRequestWaiting"); - getChild("loading_text")->setValue(LLSD(msg_waiting)); - - LLScrollListCtrl *list = getChild("scripts_list"); - if(!list) - { - return false; - } - list->setCommitCallback(boost::bind(&LLPanelScriptLimitsRegionMemory::checkButtonsEnabled, this)); - checkButtonsEnabled(); - - //set all columns to resizable mode even if some columns will be empty - for(S32 column = 0; column < list->getNumColumns(); column++) - { - LLScrollListColumn* columnp = list->getColumn(column); - columnp->mHeader->setHasResizableElement(true); - } - - return StartRequestChain(); -} - -bool LLPanelScriptLimitsRegionMemory::StartRequestChain() -{ - LLUUID region_id; - - LLFloaterLand* instance = LLFloaterReg::getTypedInstance("about_land"); - if(!instance) - { - getChild("loading_text")->setValue(LLSD(std::string(""))); - //might have to do parent post build here - //if not logic below could use early outs - return false; - } - LLParcel* parcel = instance->getCurrentSelectedParcel(); - LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); - - if ((region) && (parcel)) - { - LLUUID current_region_id = gAgent.getRegion()->getRegionID(); - LLVector3 parcel_center = parcel->getCenterpoint(); - - region_id = region->getRegionID(); - - if(region_id != current_region_id) - { - std::string msg_wrong_region = LLTrans::getString("ScriptLimitsRequestWrongRegion"); - getChild("loading_text")->setValue(LLSD(msg_wrong_region)); - return false; - } - - LLVector3d pos_global = region->getCenterGlobal(); - - LLSD body; - std::string url = region->getCapability("RemoteParcelRequest"); - if (!url.empty()) - { - LLRemoteParcelInfoProcessor::getInstance()->requestRegionParcelInfo(url, - region_id, parcel_center, pos_global, getObserverHandle()); - } - else - { - LL_WARNS() << "Can't get parcel info for script information request" << region_id - << ". Region: " << region->getName() - << " does not support RemoteParcelRequest" << LL_ENDL; - - std::string msg_waiting = LLTrans::getString("ScriptLimitsRequestError"); - getChild("loading_text")->setValue(LLSD(msg_waiting)); - } - } - else - { - std::string msg_waiting = LLTrans::getString("ScriptLimitsRequestNoParcelSelected"); - getChild("loading_text")->setValue(LLSD(msg_waiting)); - } - - return LLPanelScriptLimitsInfo::postBuild(); -} - -void LLPanelScriptLimitsRegionMemory::clearList() -{ - LLCtrlListInterface *list = childGetListInterface("scripts_list"); - - if (list) - { - list->operateOnAll(LLCtrlListInterface::OP_DELETE); - } - - mGotParcelMemoryUsed = false; - mGotParcelMemoryMax = false; - mGotParcelURLsUsed = false; - mGotParcelURLsMax = false; - - LLStringUtil::format_map_t args_parcel_memory; - std::string msg_empty_string(""); - getChild("memory_used")->setValue(LLSD(msg_empty_string)); - getChild("urls_used")->setValue(LLSD(msg_empty_string)); - getChild("parcels_listed")->setValue(LLSD(msg_empty_string)); - - mObjectListItems.clear(); - checkButtonsEnabled(); -} - -void LLPanelScriptLimitsRegionMemory::checkButtonsEnabled() -{ - LLScrollListCtrl* list = getChild("scripts_list"); - getChild("highlight_btn")->setEnabled(list->getNumSelected() > 0); - getChild("return_btn")->setEnabled(list->getNumSelected() > 0); -} - -// static -void LLPanelScriptLimitsRegionMemory::onClickRefresh(void* userdata) -{ - LLFloaterScriptLimits* instance = LLFloaterReg::getTypedInstance("script_limits"); - if(instance) - { - LLTabContainer* tab = instance->getChild("scriptlimits_panels"); - if(tab) - { - LLPanelScriptLimitsRegionMemory* panel_memory = (LLPanelScriptLimitsRegionMemory*)tab->getChild("script_limits_region_memory_panel"); - if(panel_memory) - { - //To stop people from hammering the refesh button and accidentally dosing themselves - enough requests can crash the viewer! - //turn the button off, then turn it on when we get a response - LLButton* btn = panel_memory->getChild("refresh_list_btn"); - if(btn) - { - btn->setEnabled(false); - } - panel_memory->clearList(); - - panel_memory->StartRequestChain(); - } - } - return; - } - else - { - LL_WARNS() << "could not find LLPanelScriptLimitsRegionMemory instance after refresh button clicked" << LL_ENDL; - return; - } -} - -void LLPanelScriptLimitsRegionMemory::showBeacon() -{ - LLScrollListCtrl* list = getChild("scripts_list"); - if (!list) return; - - LLScrollListItem* first_selected = list->getFirstSelected(); - if (!first_selected) return; - - std::string name = first_selected->getColumn(2)->getValue().asString(); - std::string pos_string = first_selected->getColumn(5)->getValue().asString(); - - F32 x, y, z; - S32 matched = sscanf(pos_string.c_str(), "<%g,%g,%g>", &x, &y, &z); - if (matched != 3) return; - - LLVector3 pos_agent(x, y, z); - LLVector3d pos_global = gAgent.getPosGlobalFromAgent(pos_agent); - - std::string tooltip(""); - LLTracker::trackLocation(pos_global, name, tooltip, LLTracker::LOCATION_ITEM); -} - -// static -void LLPanelScriptLimitsRegionMemory::onClickHighlight(void* userdata) -{ - LLFloaterScriptLimits* instance = LLFloaterReg::getTypedInstance("script_limits"); - if(instance) - { - LLTabContainer* tab = instance->getChild("scriptlimits_panels"); - if(tab) - { - LLPanelScriptLimitsRegionMemory* panel = (LLPanelScriptLimitsRegionMemory*)tab->getChild("script_limits_region_memory_panel"); - if(panel) - { - panel->showBeacon(); - } - } - return; - } - else - { - LL_WARNS() << "could not find LLPanelScriptLimitsRegionMemory instance after highlight button clicked" << LL_ENDL; - return; - } -} - -void LLPanelScriptLimitsRegionMemory::returnObjectsFromParcel(S32 local_id) -{ - LLMessageSystem *msg = gMessageSystem; - - LLViewerRegion* region = gAgent.getRegion(); - if (!region) return; - - LLCtrlListInterface *list = childGetListInterface("scripts_list"); - if (!list || list->getItemCount() == 0) return; - - std::vector::iterator id_itor; - - bool start_message = true; - - for (id_itor = mObjectListItems.begin(); id_itor != mObjectListItems.end(); ++id_itor) - { - LLSD element = *id_itor; - if (!list->isSelected(element["id"].asUUID())) - { - // Selected only - continue; - } - - if(element["local_id"].asInteger() != local_id) - { - // Not the parcel we are looking for - continue; - } - - if (start_message) - { - msg->newMessageFast(_PREHASH_ParcelReturnObjects); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ParcelData); - msg->addS32Fast(_PREHASH_LocalID, element["local_id"].asInteger()); - msg->addU32Fast(_PREHASH_ReturnType, RT_LIST); - start_message = false; - } - - msg->nextBlockFast(_PREHASH_TaskIDs); - msg->addUUIDFast(_PREHASH_TaskID, element["id"].asUUID()); - - if (msg->isSendFullFast(_PREHASH_TaskIDs)) - { - msg->sendReliable(region->getHost()); - start_message = true; - } - } - - if (!start_message) - { - msg->sendReliable(region->getHost()); - } -} - -void LLPanelScriptLimitsRegionMemory::returnObjects() -{ - if(!mContent.has("parcels")) - { - return; - } - - S32 number_parcels = mContent["parcels"].size(); - - // a message per parcel containing all objects to be returned from that parcel - for(S32 i = 0; i < number_parcels; i++) - { - S32 local_id = 0; - if(mContent["parcels"][i].has("local_id")) - { - local_id = mContent["parcels"][i]["local_id"].asInteger(); - returnObjectsFromParcel(local_id); - } - } - - onClickRefresh(NULL); -} - - -// static -void LLPanelScriptLimitsRegionMemory::onClickReturn(void* userdata) -{ - LLFloaterScriptLimits* instance = LLFloaterReg::getTypedInstance("script_limits"); - if(instance) - { - LLTabContainer* tab = instance->getChild("scriptlimits_panels"); - if(tab) - { - LLPanelScriptLimitsRegionMemory* panel = (LLPanelScriptLimitsRegionMemory*)tab->getChild("script_limits_region_memory_panel"); - if(panel) - { - panel->returnObjects(); - } - } - return; - } - else - { - LL_WARNS() << "could not find LLPanelScriptLimitsRegionMemory instance after highlight button clicked" << LL_ENDL; - return; - } -} - +/** + * @file llfloaterscriptlimits.cpp + * @author Gabriel Lee + * @brief Implementation of the region info and controls floater and panels. + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llfloaterscriptlimits.h" + +// library includes +#include "llavatarnamecache.h" +#include "llsdutil.h" +#include "llsdutil_math.h" +#include "message.h" + +#include "llagent.h" +#include "llfloateravatarpicker.h" +#include "llfloaterland.h" +#include "llfloaterreg.h" +#include "llregionhandle.h" +#include "llscrolllistctrl.h" +#include "llscrolllistitem.h" +#include "llparcel.h" +#include "lltabcontainer.h" +#include "lltracker.h" +#include "lltrans.h" +#include "llviewercontrol.h" +#include "lluictrlfactory.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" +#include "llcorehttputil.h" + +///---------------------------------------------------------------------------- +/// LLFloaterScriptLimits +///---------------------------------------------------------------------------- + +// debug switches, won't work in release +#ifndef LL_RELEASE_FOR_DOWNLOAD + +// dump responder replies to LL_INFOS() for debugging +//#define DUMP_REPLIES_TO_LLINFOS + +#ifdef DUMP_REPLIES_TO_LLINFOS +#include "llsdserialize.h" +#include "llwindow.h" +#endif + +// use fake LLSD responses to check the viewer side is working correctly +// I'm syncing this with the server side efforts so hopfully we can keep +// the to-ing and fro-ing between the two teams to a minimum +//#define USE_FAKE_RESPONSES + +#ifdef USE_FAKE_RESPONSES +const S32 FAKE_NUMBER_OF_URLS = 329; +const S32 FAKE_AVAILABLE_URLS = 731; +const S32 FAKE_AMOUNT_OF_MEMORY = 66741; +const S32 FAKE_AVAILABLE_MEMORY = 895577; +#endif + +#endif + +const S32 SIZE_OF_ONE_KB = 1024; + +LLFloaterScriptLimits::LLFloaterScriptLimits(const LLSD& seed) + : LLFloater(seed) +{ +} + +bool LLFloaterScriptLimits::postBuild() +{ + mTab = getChild("scriptlimits_panels"); + + if(!mTab) + { + LL_WARNS() << "Error! couldn't get scriptlimits_panels, aborting Script Information setup" << LL_ENDL; + return false; + } + + // contruct the panel + LLPanelScriptLimitsRegionMemory* panel_memory = new LLPanelScriptLimitsRegionMemory; + mInfoPanels.push_back(panel_memory); + panel_memory->buildFromFile( "panel_script_limits_region_memory.xml"); + mTab->addTabPanel(panel_memory); + mTab->selectTab(0); + return true; +} + +LLFloaterScriptLimits::~LLFloaterScriptLimits() +{ +} + +// public +void LLFloaterScriptLimits::refresh() +{ + for(info_panels_t::iterator iter = mInfoPanels.begin(); + iter != mInfoPanels.end(); ++iter) + { + (*iter)->refresh(); + } +} + +///---------------------------------------------------------------------------- +// Base class for panels +///---------------------------------------------------------------------------- + +LLPanelScriptLimitsInfo::LLPanelScriptLimitsInfo() + : LLPanel() +{ +} + + +// virtual +bool LLPanelScriptLimitsInfo::postBuild() +{ + refresh(); + return true; +} + +// virtual +void LLPanelScriptLimitsInfo::updateChild(LLUICtrl* child_ctr) +{ +} + +///---------------------------------------------------------------------------- +// Memory Panel +///---------------------------------------------------------------------------- + +LLPanelScriptLimitsRegionMemory::~LLPanelScriptLimitsRegionMemory() +{ + if(!mParcelId.isNull()) + { + LLRemoteParcelInfoProcessor::getInstance()->removeObserver(mParcelId, this); + mParcelId.setNull(); + } +}; + +bool LLPanelScriptLimitsRegionMemory::getLandScriptResources() +{ + if (!gAgent.getRegion()) return false; + + LLSD body; + std::string url = gAgent.getRegion()->getCapability("LandResources"); + if (!url.empty()) + { + LLCoros::instance().launch("LLPanelScriptLimitsRegionMemory::getLandScriptResourcesCoro", + boost::bind(&LLPanelScriptLimitsRegionMemory::getLandScriptResourcesCoro, this, url)); + return true; + } + else + { + return false; + } +} + +void LLPanelScriptLimitsRegionMemory::getLandScriptResourcesCoro(std::string url) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getLandScriptResourcesCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD postData; + + postData["parcel_id"] = mParcelId; + + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS() << "Failed to get script resource info" << LL_ENDL; + return; + } + + // We could retrieve these sequentially inline from this coroutine. But + // since the original code retrieved them in parallel I'll spawn two + // coroutines to do the retrieval. + + // The summary service: + if (result.has("ScriptResourceSummary")) + { + std::string urlResourceSummary = result["ScriptResourceSummary"].asString(); + LLCoros::instance().launch("LLPanelScriptLimitsRegionMemory::getLandScriptSummaryCoro", + boost::bind(&LLPanelScriptLimitsRegionMemory::getLandScriptSummaryCoro, this, urlResourceSummary)); + } + + if (result.has("ScriptResourceDetails")) + { + std::string urlResourceDetails = result["ScriptResourceDetails"].asString(); + LLCoros::instance().launch("LLPanelScriptLimitsRegionMemory::getLandScriptDetailsCoro", + boost::bind(&LLPanelScriptLimitsRegionMemory::getLandScriptDetailsCoro, this, urlResourceDetails)); + } + + +} + +void LLPanelScriptLimitsRegionMemory::getLandScriptSummaryCoro(std::string url) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getLandScriptSummaryCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD result = httpAdapter->getAndSuspend(httpRequest, url); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS() << "Unable to retrieve script summary." << LL_ENDL; + return; + } + + LLFloaterScriptLimits* instance = LLFloaterReg::getTypedInstance("script_limits"); + if (!instance) + { + LL_WARNS() << "Failed to get llfloaterscriptlimits instance" << LL_ENDL; + return; + } + + LLTabContainer* tab = instance->getChild("scriptlimits_panels"); + if (!tab) + { + LL_WARNS() << "Unable to access script limits tab" << LL_ENDL; + return; + } + + LLPanelScriptLimitsRegionMemory* panelMemory = (LLPanelScriptLimitsRegionMemory*)tab->getChild("script_limits_region_memory_panel"); + if (!panelMemory) + { + LL_WARNS() << "Unable to get memory panel." << LL_ENDL; + return; + } + + panelMemory->getChild("loading_text")->setValue(LLSD(std::string(""))); + + LLButton* btn = panelMemory->getChild("refresh_list_btn"); + if (btn) + { + btn->setEnabled(true); + } + + result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); + panelMemory->setRegionSummary(result); + +} + +void LLPanelScriptLimitsRegionMemory::getLandScriptDetailsCoro(std::string url) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getLandScriptDetailsCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD result = httpAdapter->getAndSuspend(httpRequest, url); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS() << "Unable to retrieve script details." << LL_ENDL; + return; + } + + LLFloaterScriptLimits* instance = LLFloaterReg::getTypedInstance("script_limits"); + + if (!instance) + { + LL_WARNS() << "Failed to get llfloaterscriptlimits instance" << LL_ENDL; + return; + } + + LLTabContainer* tab = instance->getChild("scriptlimits_panels"); + if (!tab) + { + LL_WARNS() << "Unable to access script limits tab" << LL_ENDL; + return; + } + + LLPanelScriptLimitsRegionMemory* panelMemory = (LLPanelScriptLimitsRegionMemory*)tab->getChild("script_limits_region_memory_panel"); + + if (!panelMemory) + { + LL_WARNS() << "Unable to get memory panel." << LL_ENDL; + return; + } + + result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); + panelMemory->setRegionDetails(result); +} + +void LLPanelScriptLimitsRegionMemory::processParcelInfo(const LLParcelData& parcel_data) +{ + if(!getLandScriptResources()) + { + std::string msg_error = LLTrans::getString("ScriptLimitsRequestError"); + getChild("loading_text")->setValue(LLSD(msg_error)); + } + else + { + std::string msg_waiting = LLTrans::getString("ScriptLimitsRequestWaiting"); + getChild("loading_text")->setValue(LLSD(msg_waiting)); + } +} + +void LLPanelScriptLimitsRegionMemory::setParcelID(const LLUUID& parcel_id) +{ + if (!parcel_id.isNull()) + { + if(!mParcelId.isNull()) + { + LLRemoteParcelInfoProcessor::getInstance()->removeObserver(mParcelId, this); + mParcelId.setNull(); + } + mParcelId = parcel_id; + LLRemoteParcelInfoProcessor::getInstance()->addObserver(parcel_id, this); + LLRemoteParcelInfoProcessor::getInstance()->sendParcelInfoRequest(parcel_id); + } + else + { + std::string msg_error = LLTrans::getString("ScriptLimitsRequestError"); + getChild("loading_text")->setValue(LLSD(msg_error)); + } +} + +// virtual +void LLPanelScriptLimitsRegionMemory::setErrorStatus(S32 status, const std::string& reason) +{ + LL_WARNS() << "Can't handle remote parcel request."<< " Http Status: "<< status << ". Reason : "<< reason<("scripts_list"); + if(!list) + { + return; + } + + std::string name = LLCacheName::buildUsername(full_name); + + std::vector::iterator id_itor; + for (id_itor = mObjectListItems.begin(); id_itor != mObjectListItems.end(); ++id_itor) + { + LLSD element = *id_itor; + if(element["owner_id"].asUUID() == id) + { + LLScrollListItem* item = list->getItem(element["id"].asUUID()); + + if(item) + { + item->getColumn(3)->setValue(LLSD(name)); + element["columns"][3]["value"] = name; + } + } + } +} + +void LLPanelScriptLimitsRegionMemory::setRegionDetails(LLSD content) +{ + LLScrollListCtrl *list = getChild("scripts_list"); + + if(!list) + { + LL_WARNS() << "Error getting the scripts_list control" << LL_ENDL; + return; + } + + S32 number_parcels = content["parcels"].size(); + + LLStringUtil::format_map_t args_parcels; + args_parcels["[PARCELS]"] = llformat ("%d", number_parcels); + std::string msg_parcels = LLTrans::getString("ScriptLimitsParcelsOwned", args_parcels); + getChild("parcels_listed")->setValue(LLSD(msg_parcels)); + + uuid_vec_t names_requested; + + // This makes the assumption that all objects will have the same set + // of attributes, ie they will all have, or none will have locations + // This is a pretty safe assumption as it's reliant on server version. + bool has_locations = false; + bool has_local_ids = false; + + for(S32 i = 0; i < number_parcels; i++) + { + std::string parcel_name = content["parcels"][i]["name"].asString(); + S32 number_objects = content["parcels"][i]["objects"].size(); + + S32 local_id = 0; + if(content["parcels"][i].has("local_id")) + { + // if any locations are found flag that we can use them and turn on the highlight button + has_local_ids = true; + local_id = content["parcels"][i]["local_id"].asInteger(); + } + + for(S32 j = 0; j < number_objects; j++) + { + S32 size = content["parcels"][i]["objects"][j]["resources"]["memory"].asInteger() / SIZE_OF_ONE_KB; + + S32 urls = content["parcels"][i]["objects"][j]["resources"]["urls"].asInteger(); + + std::string name_buf = content["parcels"][i]["objects"][j]["name"].asString(); + LLUUID task_id = content["parcels"][i]["objects"][j]["id"].asUUID(); + LLUUID owner_id = content["parcels"][i]["objects"][j]["owner_id"].asUUID(); + // This field may not be sent by all server versions, but it's OK if + // it uses the LLSD default of false + bool is_group_owned = content["parcels"][i]["objects"][j]["is_group_owned"].asBoolean(); + + F32 location_x = 0.0f; + F32 location_y = 0.0f; + F32 location_z = 0.0f; + + if(content["parcels"][i]["objects"][j].has("location")) + { + // if any locations are found flag that we can use them and turn on the highlight button + LLVector3 vec = ll_vector3_from_sd(content["parcels"][i]["objects"][j]["location"]); + has_locations = true; + location_x = vec.mV[0]; + location_y = vec.mV[1]; + location_z = vec.mV[2]; + } + + std::string owner_buf; + + // in the future the server will give us owner names, so see if we're there yet: + if(content["parcels"][i]["objects"][j].has("owner_name")) + { + owner_buf = content["parcels"][i]["objects"][j]["owner_name"].asString(); + } + // ...and if not use the slightly more painful method of disovery: + else + { + bool name_is_cached; + if (is_group_owned) + { + name_is_cached = gCacheName->getGroupName(owner_id, owner_buf); + } + else + { + LLAvatarName av_name; + name_is_cached = LLAvatarNameCache::get(owner_id, &av_name); + owner_buf = av_name.getUserName(); + owner_buf = LLCacheName::buildUsername(owner_buf); + } + if(!name_is_cached) + { + if(std::find(names_requested.begin(), names_requested.end(), owner_id) == names_requested.end()) + { + names_requested.push_back(owner_id); + if (is_group_owned) + { + gCacheName->getGroup(owner_id, + boost::bind(&LLPanelScriptLimitsRegionMemory::onNameCache, + this, _1, _2)); + } + else + { + LLAvatarNameCache::get(owner_id, + boost::bind(&LLPanelScriptLimitsRegionMemory::onAvatarNameCache, + this, _1, _2)); + } + } + } + } + + LLScrollListItem::Params item_params; + item_params.value = task_id; + + LLScrollListCell::Params cell_params; + cell_params.font = LLFontGL::getFontSansSerif(); + // Start out right justifying numeric displays + cell_params.font_halign = LLFontGL::RIGHT; + + cell_params.column = "size"; + cell_params.value = size; + item_params.columns.add(cell_params); + + cell_params.column = "urls"; + cell_params.value = urls; + item_params.columns.add(cell_params); + + cell_params.font_halign = LLFontGL::LEFT; + // The rest of the columns are text to left justify them + cell_params.column = "name"; + cell_params.value = name_buf; + item_params.columns.add(cell_params); + + cell_params.column = "owner"; + cell_params.value = owner_buf; + item_params.columns.add(cell_params); + + cell_params.column = "parcel"; + cell_params.value = parcel_name; + item_params.columns.add(cell_params); + + cell_params.column = "location"; + cell_params.value = has_locations + ? llformat("<%0.0f, %0.0f, %0.0f>", location_x, location_y, location_z) + : ""; + item_params.columns.add(cell_params); + + list->addRow(item_params); + + LLSD element; + element["owner_id"] = owner_id; + + element["id"] = task_id; + element["local_id"] = local_id; + mObjectListItems.push_back(element); + } + } + + if (has_locations) + { + LLButton* btn = getChild("highlight_btn"); + if(btn) + { + btn->setVisible(true); + } + } + + if (has_local_ids) + { + LLButton* btn = getChild("return_btn"); + if(btn) + { + btn->setVisible(true); + } + } + + // save the structure to make object return easier + mContent = content; +} + +void LLPanelScriptLimitsRegionMemory::setRegionSummary(LLSD content) +{ + if(content["summary"]["used"][0]["type"].asString() == std::string("memory")) + { + mParcelMemoryUsed = content["summary"]["used"][0]["amount"].asInteger() / SIZE_OF_ONE_KB; + mParcelMemoryMax = content["summary"]["available"][0]["amount"].asInteger() / SIZE_OF_ONE_KB; + mGotParcelMemoryUsed = true; + } + else if(content["summary"]["used"][1]["type"].asString() == std::string("memory")) + { + mParcelMemoryUsed = content["summary"]["used"][1]["amount"].asInteger() / SIZE_OF_ONE_KB; + mParcelMemoryMax = content["summary"]["available"][1]["amount"].asInteger() / SIZE_OF_ONE_KB; + mGotParcelMemoryUsed = true; + } + else + { + LL_WARNS() << "summary doesn't contain memory info" << LL_ENDL; + return; + } + + if(content["summary"]["used"][0]["type"].asString() == std::string("urls")) + { + mParcelURLsUsed = content["summary"]["used"][0]["amount"].asInteger(); + mParcelURLsMax = content["summary"]["available"][0]["amount"].asInteger(); + mGotParcelURLsUsed = true; + } + else if(content["summary"]["used"][1]["type"].asString() == std::string("urls")) + { + mParcelURLsUsed = content["summary"]["used"][1]["amount"].asInteger(); + mParcelURLsMax = content["summary"]["available"][1]["amount"].asInteger(); + mGotParcelURLsUsed = true; + } + else + { + LL_WARNS() << "summary doesn't contain urls info" << LL_ENDL; + return; + } + + if((mParcelMemoryUsed >= 0) && (mParcelMemoryMax >= 0)) + { + LLStringUtil::format_map_t args_parcel_memory; + args_parcel_memory["[COUNT]"] = llformat ("%d", mParcelMemoryUsed); + std::string translate_message = "ScriptLimitsMemoryUsedSimple"; + + if (0 < mParcelMemoryMax) + { + S32 parcel_memory_available = mParcelMemoryMax - mParcelMemoryUsed; + + args_parcel_memory["[MAX]"] = llformat ("%d", mParcelMemoryMax); + args_parcel_memory["[AVAILABLE]"] = llformat ("%d", parcel_memory_available); + translate_message = "ScriptLimitsMemoryUsed"; + } + + std::string msg_parcel_memory = LLTrans::getString(translate_message, args_parcel_memory); + getChild("memory_used")->setValue(LLSD(msg_parcel_memory)); + } + + if((mParcelURLsUsed >= 0) && (mParcelURLsMax >= 0)) + { + S32 parcel_urls_available = mParcelURLsMax - mParcelURLsUsed; + + LLStringUtil::format_map_t args_parcel_urls; + args_parcel_urls["[COUNT]"] = llformat ("%d", mParcelURLsUsed); + args_parcel_urls["[MAX]"] = llformat ("%d", mParcelURLsMax); + args_parcel_urls["[AVAILABLE]"] = llformat ("%d", parcel_urls_available); + std::string msg_parcel_urls = LLTrans::getString("ScriptLimitsURLsUsed", args_parcel_urls); + getChild("urls_used")->setValue(LLSD(msg_parcel_urls)); + } +} + +bool LLPanelScriptLimitsRegionMemory::postBuild() +{ + childSetAction("refresh_list_btn", onClickRefresh, this); + childSetAction("highlight_btn", onClickHighlight, this); + childSetAction("return_btn", onClickReturn, this); + + std::string msg_waiting = LLTrans::getString("ScriptLimitsRequestWaiting"); + getChild("loading_text")->setValue(LLSD(msg_waiting)); + + LLScrollListCtrl *list = getChild("scripts_list"); + if(!list) + { + return false; + } + list->setCommitCallback(boost::bind(&LLPanelScriptLimitsRegionMemory::checkButtonsEnabled, this)); + checkButtonsEnabled(); + + //set all columns to resizable mode even if some columns will be empty + for(S32 column = 0; column < list->getNumColumns(); column++) + { + LLScrollListColumn* columnp = list->getColumn(column); + columnp->mHeader->setHasResizableElement(true); + } + + return StartRequestChain(); +} + +bool LLPanelScriptLimitsRegionMemory::StartRequestChain() +{ + LLUUID region_id; + + LLFloaterLand* instance = LLFloaterReg::getTypedInstance("about_land"); + if(!instance) + { + getChild("loading_text")->setValue(LLSD(std::string(""))); + //might have to do parent post build here + //if not logic below could use early outs + return false; + } + LLParcel* parcel = instance->getCurrentSelectedParcel(); + LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); + + if ((region) && (parcel)) + { + LLUUID current_region_id = gAgent.getRegion()->getRegionID(); + LLVector3 parcel_center = parcel->getCenterpoint(); + + region_id = region->getRegionID(); + + if(region_id != current_region_id) + { + std::string msg_wrong_region = LLTrans::getString("ScriptLimitsRequestWrongRegion"); + getChild("loading_text")->setValue(LLSD(msg_wrong_region)); + return false; + } + + LLVector3d pos_global = region->getCenterGlobal(); + + LLSD body; + std::string url = region->getCapability("RemoteParcelRequest"); + if (!url.empty()) + { + LLRemoteParcelInfoProcessor::getInstance()->requestRegionParcelInfo(url, + region_id, parcel_center, pos_global, getObserverHandle()); + } + else + { + LL_WARNS() << "Can't get parcel info for script information request" << region_id + << ". Region: " << region->getName() + << " does not support RemoteParcelRequest" << LL_ENDL; + + std::string msg_waiting = LLTrans::getString("ScriptLimitsRequestError"); + getChild("loading_text")->setValue(LLSD(msg_waiting)); + } + } + else + { + std::string msg_waiting = LLTrans::getString("ScriptLimitsRequestNoParcelSelected"); + getChild("loading_text")->setValue(LLSD(msg_waiting)); + } + + return LLPanelScriptLimitsInfo::postBuild(); +} + +void LLPanelScriptLimitsRegionMemory::clearList() +{ + LLCtrlListInterface *list = childGetListInterface("scripts_list"); + + if (list) + { + list->operateOnAll(LLCtrlListInterface::OP_DELETE); + } + + mGotParcelMemoryUsed = false; + mGotParcelMemoryMax = false; + mGotParcelURLsUsed = false; + mGotParcelURLsMax = false; + + LLStringUtil::format_map_t args_parcel_memory; + std::string msg_empty_string(""); + getChild("memory_used")->setValue(LLSD(msg_empty_string)); + getChild("urls_used")->setValue(LLSD(msg_empty_string)); + getChild("parcels_listed")->setValue(LLSD(msg_empty_string)); + + mObjectListItems.clear(); + checkButtonsEnabled(); +} + +void LLPanelScriptLimitsRegionMemory::checkButtonsEnabled() +{ + LLScrollListCtrl* list = getChild("scripts_list"); + getChild("highlight_btn")->setEnabled(list->getNumSelected() > 0); + getChild("return_btn")->setEnabled(list->getNumSelected() > 0); +} + +// static +void LLPanelScriptLimitsRegionMemory::onClickRefresh(void* userdata) +{ + LLFloaterScriptLimits* instance = LLFloaterReg::getTypedInstance("script_limits"); + if(instance) + { + LLTabContainer* tab = instance->getChild("scriptlimits_panels"); + if(tab) + { + LLPanelScriptLimitsRegionMemory* panel_memory = (LLPanelScriptLimitsRegionMemory*)tab->getChild("script_limits_region_memory_panel"); + if(panel_memory) + { + //To stop people from hammering the refesh button and accidentally dosing themselves - enough requests can crash the viewer! + //turn the button off, then turn it on when we get a response + LLButton* btn = panel_memory->getChild("refresh_list_btn"); + if(btn) + { + btn->setEnabled(false); + } + panel_memory->clearList(); + + panel_memory->StartRequestChain(); + } + } + return; + } + else + { + LL_WARNS() << "could not find LLPanelScriptLimitsRegionMemory instance after refresh button clicked" << LL_ENDL; + return; + } +} + +void LLPanelScriptLimitsRegionMemory::showBeacon() +{ + LLScrollListCtrl* list = getChild("scripts_list"); + if (!list) return; + + LLScrollListItem* first_selected = list->getFirstSelected(); + if (!first_selected) return; + + std::string name = first_selected->getColumn(2)->getValue().asString(); + std::string pos_string = first_selected->getColumn(5)->getValue().asString(); + + F32 x, y, z; + S32 matched = sscanf(pos_string.c_str(), "<%g,%g,%g>", &x, &y, &z); + if (matched != 3) return; + + LLVector3 pos_agent(x, y, z); + LLVector3d pos_global = gAgent.getPosGlobalFromAgent(pos_agent); + + std::string tooltip(""); + LLTracker::trackLocation(pos_global, name, tooltip, LLTracker::LOCATION_ITEM); +} + +// static +void LLPanelScriptLimitsRegionMemory::onClickHighlight(void* userdata) +{ + LLFloaterScriptLimits* instance = LLFloaterReg::getTypedInstance("script_limits"); + if(instance) + { + LLTabContainer* tab = instance->getChild("scriptlimits_panels"); + if(tab) + { + LLPanelScriptLimitsRegionMemory* panel = (LLPanelScriptLimitsRegionMemory*)tab->getChild("script_limits_region_memory_panel"); + if(panel) + { + panel->showBeacon(); + } + } + return; + } + else + { + LL_WARNS() << "could not find LLPanelScriptLimitsRegionMemory instance after highlight button clicked" << LL_ENDL; + return; + } +} + +void LLPanelScriptLimitsRegionMemory::returnObjectsFromParcel(S32 local_id) +{ + LLMessageSystem *msg = gMessageSystem; + + LLViewerRegion* region = gAgent.getRegion(); + if (!region) return; + + LLCtrlListInterface *list = childGetListInterface("scripts_list"); + if (!list || list->getItemCount() == 0) return; + + std::vector::iterator id_itor; + + bool start_message = true; + + for (id_itor = mObjectListItems.begin(); id_itor != mObjectListItems.end(); ++id_itor) + { + LLSD element = *id_itor; + if (!list->isSelected(element["id"].asUUID())) + { + // Selected only + continue; + } + + if(element["local_id"].asInteger() != local_id) + { + // Not the parcel we are looking for + continue; + } + + if (start_message) + { + msg->newMessageFast(_PREHASH_ParcelReturnObjects); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ParcelData); + msg->addS32Fast(_PREHASH_LocalID, element["local_id"].asInteger()); + msg->addU32Fast(_PREHASH_ReturnType, RT_LIST); + start_message = false; + } + + msg->nextBlockFast(_PREHASH_TaskIDs); + msg->addUUIDFast(_PREHASH_TaskID, element["id"].asUUID()); + + if (msg->isSendFullFast(_PREHASH_TaskIDs)) + { + msg->sendReliable(region->getHost()); + start_message = true; + } + } + + if (!start_message) + { + msg->sendReliable(region->getHost()); + } +} + +void LLPanelScriptLimitsRegionMemory::returnObjects() +{ + if(!mContent.has("parcels")) + { + return; + } + + S32 number_parcels = mContent["parcels"].size(); + + // a message per parcel containing all objects to be returned from that parcel + for(S32 i = 0; i < number_parcels; i++) + { + S32 local_id = 0; + if(mContent["parcels"][i].has("local_id")) + { + local_id = mContent["parcels"][i]["local_id"].asInteger(); + returnObjectsFromParcel(local_id); + } + } + + onClickRefresh(NULL); +} + + +// static +void LLPanelScriptLimitsRegionMemory::onClickReturn(void* userdata) +{ + LLFloaterScriptLimits* instance = LLFloaterReg::getTypedInstance("script_limits"); + if(instance) + { + LLTabContainer* tab = instance->getChild("scriptlimits_panels"); + if(tab) + { + LLPanelScriptLimitsRegionMemory* panel = (LLPanelScriptLimitsRegionMemory*)tab->getChild("script_limits_region_memory_panel"); + if(panel) + { + panel->returnObjects(); + } + } + return; + } + else + { + LL_WARNS() << "could not find LLPanelScriptLimitsRegionMemory instance after highlight button clicked" << LL_ENDL; + return; + } +} + diff --git a/indra/newview/llfloaterscriptlimits.h b/indra/newview/llfloaterscriptlimits.h index 1b03dd9ccd..e1eb8cb079 100644 --- a/indra/newview/llfloaterscriptlimits.h +++ b/indra/newview/llfloaterscriptlimits.h @@ -1,155 +1,155 @@ -/** - * @file llfloaterscriptlimits.h - * @author Gabriel Lee - * @brief Declaration of the region info and controls floater and panels. - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERSCRIPTLIMITS_H -#define LL_LLFLOATERSCRIPTLIMITS_H - -#include -#include "llfloater.h" -#include "llhost.h" -#include "llpanel.h" -#include "llremoteparcelrequest.h" -#include "lleventcoro.h" -#include "llcoros.h" - -class LLPanelScriptLimitsInfo; -class LLTabContainer; -class LLAvatarName; - -class LLPanelScriptLimitsRegionMemory; - -class LLFloaterScriptLimits : public LLFloater -{ - friend class LLFloaterReg; -public: - - /*virtual*/ bool postBuild(); - - // from LLPanel - virtual void refresh(); - -private: - - LLFloaterScriptLimits(const LLSD& seed); - ~LLFloaterScriptLimits(); - -protected: - - LLTabContainer* mTab; - typedef std::vector info_panels_t; - info_panels_t mInfoPanels; -}; - - -// Base class for all script limits information panels. -class LLPanelScriptLimitsInfo : public LLPanel -{ -public: - LLPanelScriptLimitsInfo(); - - virtual bool postBuild(); - virtual void updateChild(LLUICtrl* child_ctrl); - -protected: - void initCtrl(const std::string& name); - - typedef std::vector strings_t; - - LLHost mHost; -}; - -///////////////////////////////////////////////////////////////////////////// -// Memory panel -///////////////////////////////////////////////////////////////////////////// - -class LLPanelScriptLimitsRegionMemory : public LLPanelScriptLimitsInfo, LLRemoteParcelInfoObserver -{ - -public: - LLPanelScriptLimitsRegionMemory() - : LLPanelScriptLimitsInfo(), LLRemoteParcelInfoObserver(), - - mParcelId(LLUUID()), - mGotParcelMemoryUsed(false), - mGotParcelMemoryMax(false), - mParcelMemoryMax(0), - mParcelMemoryUsed(0) {}; - - ~LLPanelScriptLimitsRegionMemory(); - - // LLPanel - virtual bool postBuild(); - - void setRegionDetails(LLSD content); - void setRegionSummary(LLSD content); - - bool StartRequestChain(); - - bool getLandScriptResources(); - void clearList(); - void showBeacon(); - void returnObjectsFromParcel(S32 local_id); - void returnObjects(); - void checkButtonsEnabled(); - -private: - void onAvatarNameCache(const LLUUID& id, - const LLAvatarName& av_name); - void onNameCache(const LLUUID& id, - const std::string& name); - - LLSD mContent; - LLUUID mParcelId; - bool mGotParcelMemoryUsed; - bool mGotParcelMemoryMax; - S32 mParcelMemoryMax; - S32 mParcelMemoryUsed; - - bool mGotParcelURLsUsed; - bool mGotParcelURLsMax; - S32 mParcelURLsMax; - S32 mParcelURLsUsed; - - std::vector mObjectListItems; - - void getLandScriptResourcesCoro(std::string url); - void getLandScriptSummaryCoro(std::string url); - void getLandScriptDetailsCoro(std::string url); - -protected: - -// LLRemoteParcelInfoObserver interface: -/*virtual*/ void processParcelInfo(const LLParcelData& parcel_data); -/*virtual*/ void setParcelID(const LLUUID& parcel_id); -/*virtual*/ void setErrorStatus(S32 status, const std::string& reason); - - static void onClickRefresh(void* userdata); - static void onClickHighlight(void* userdata); - static void onClickReturn(void* userdata); -}; - -#endif +/** + * @file llfloaterscriptlimits.h + * @author Gabriel Lee + * @brief Declaration of the region info and controls floater and panels. + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERSCRIPTLIMITS_H +#define LL_LLFLOATERSCRIPTLIMITS_H + +#include +#include "llfloater.h" +#include "llhost.h" +#include "llpanel.h" +#include "llremoteparcelrequest.h" +#include "lleventcoro.h" +#include "llcoros.h" + +class LLPanelScriptLimitsInfo; +class LLTabContainer; +class LLAvatarName; + +class LLPanelScriptLimitsRegionMemory; + +class LLFloaterScriptLimits : public LLFloater +{ + friend class LLFloaterReg; +public: + + /*virtual*/ bool postBuild(); + + // from LLPanel + virtual void refresh(); + +private: + + LLFloaterScriptLimits(const LLSD& seed); + ~LLFloaterScriptLimits(); + +protected: + + LLTabContainer* mTab; + typedef std::vector info_panels_t; + info_panels_t mInfoPanels; +}; + + +// Base class for all script limits information panels. +class LLPanelScriptLimitsInfo : public LLPanel +{ +public: + LLPanelScriptLimitsInfo(); + + virtual bool postBuild(); + virtual void updateChild(LLUICtrl* child_ctrl); + +protected: + void initCtrl(const std::string& name); + + typedef std::vector strings_t; + + LLHost mHost; +}; + +///////////////////////////////////////////////////////////////////////////// +// Memory panel +///////////////////////////////////////////////////////////////////////////// + +class LLPanelScriptLimitsRegionMemory : public LLPanelScriptLimitsInfo, LLRemoteParcelInfoObserver +{ + +public: + LLPanelScriptLimitsRegionMemory() + : LLPanelScriptLimitsInfo(), LLRemoteParcelInfoObserver(), + + mParcelId(LLUUID()), + mGotParcelMemoryUsed(false), + mGotParcelMemoryMax(false), + mParcelMemoryMax(0), + mParcelMemoryUsed(0) {}; + + ~LLPanelScriptLimitsRegionMemory(); + + // LLPanel + virtual bool postBuild(); + + void setRegionDetails(LLSD content); + void setRegionSummary(LLSD content); + + bool StartRequestChain(); + + bool getLandScriptResources(); + void clearList(); + void showBeacon(); + void returnObjectsFromParcel(S32 local_id); + void returnObjects(); + void checkButtonsEnabled(); + +private: + void onAvatarNameCache(const LLUUID& id, + const LLAvatarName& av_name); + void onNameCache(const LLUUID& id, + const std::string& name); + + LLSD mContent; + LLUUID mParcelId; + bool mGotParcelMemoryUsed; + bool mGotParcelMemoryMax; + S32 mParcelMemoryMax; + S32 mParcelMemoryUsed; + + bool mGotParcelURLsUsed; + bool mGotParcelURLsMax; + S32 mParcelURLsMax; + S32 mParcelURLsUsed; + + std::vector mObjectListItems; + + void getLandScriptResourcesCoro(std::string url); + void getLandScriptSummaryCoro(std::string url); + void getLandScriptDetailsCoro(std::string url); + +protected: + +// LLRemoteParcelInfoObserver interface: +/*virtual*/ void processParcelInfo(const LLParcelData& parcel_data); +/*virtual*/ void setParcelID(const LLUUID& parcel_id); +/*virtual*/ void setErrorStatus(S32 status, const std::string& reason); + + static void onClickRefresh(void* userdata); + static void onClickHighlight(void* userdata); + static void onClickReturn(void* userdata); +}; + +#endif diff --git a/indra/newview/llfloatersearch.cpp b/indra/newview/llfloatersearch.cpp index 3b16b725bf..d3c8bf3451 100644 --- a/indra/newview/llfloatersearch.cpp +++ b/indra/newview/llfloatersearch.cpp @@ -1,211 +1,211 @@ -/** - * @file llfloatersearch.cpp - * @author Martin Reddy - * @brief Search floater - uses an embedded web browser control - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llcommandhandler.h" -#include "llfloaterreg.h" -#include "llfloatersearch.h" -#include "llhttpconstants.h" -#include "llmediactrl.h" -#include "llnotificationsutil.h" -#include "lllogininstance.h" -#include "lluri.h" -#include "llagent.h" -#include "llui.h" -#include "llviewercontrol.h" -#include "llweb.h" - -// support secondlife:///app/search/{CATEGORY}/{QUERY} SLapps -class LLSearchHandler : public LLCommandHandler -{ -public: - // requires trusted browser to trigger - LLSearchHandler() : LLCommandHandler("search", UNTRUSTED_CLICK_ONLY) { } - bool handle(const LLSD& tokens, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) - { - const size_t parts = tokens.size(); - - // get the (optional) category for the search - std::string collection; - if (parts > 0) - { - collection = tokens[0].asString(); - } - - // get the (optional) search string - std::string search_text; - if (parts > 1) - { - search_text = tokens[1].asString(); - } - - // create the LLSD arguments for the search floater - LLFloaterSearch::Params p; - p.search.collection = collection; - p.search.query = LLURI::unescape(search_text); - - // open the search floater and perform the requested search - LLFloaterReg::showInstance("search", p); - return true; - } -}; -LLSearchHandler gSearchHandler; - -LLFloaterSearch::SearchQuery::SearchQuery() -: category("category", ""), - collection("collection", ""), - query("query") -{} - -LLFloaterSearch::LLFloaterSearch(const Params& key) : - LLFloaterWebContent(key), - mSearchGodLevel(0) -{ - // declare a map that transforms a category name into - // the URL suffix that is used to search that category - - mSearchType.insert("standard"); - mSearchType.insert("land"); - mSearchType.insert("classified"); - - mCollectionType.insert("events"); - mCollectionType.insert("destinations"); - mCollectionType.insert("places"); - mCollectionType.insert("groups"); - mCollectionType.insert("people"); -} - -bool LLFloaterSearch::postBuild() -{ - LLFloaterWebContent::postBuild(); - mWebBrowser->addObserver(this); - - return true; -} - -void LLFloaterSearch::onOpen(const LLSD& key) -{ - Params p(key); - p.trusted_content = true; - p.allow_address_entry = false; - - LLFloaterWebContent::onOpen(p); - mWebBrowser->setFocus(true); - search(p.search); -} - -void LLFloaterSearch::onClose(bool app_quitting) -{ - LLFloaterWebContent::onClose(app_quitting); - // tear down the web view so we don't show the previous search - // result when the floater is opened next time - destroy(); -} - -void LLFloaterSearch::godLevelChanged(U8 godlevel) -{ - // search results can change based upon god level - if the user - // changes god level, then give them a warning (we don't refresh - // the search as this might undo any page navigation or - // AJAX-driven changes since the last search). - - //FIXME: set status bar text - - //getChildView("refresh_search")->setVisible( (godlevel != mSearchGodLevel)); -} - -void LLFloaterSearch::search(const SearchQuery &p) -{ - if (! mWebBrowser || !p.validateBlock()) - { - return; - } - - // reset the god level warning as we're sending the latest state - getChildView("refresh_search")->setVisible(false); - mSearchGodLevel = gAgent.getGodLevel(); - - // work out the subdir to use based on the requested category - LLSD subs; - if (mSearchType.find(p.category) != mSearchType.end()) - { - subs["TYPE"] = p.category; - } - else - { - subs["TYPE"] = "standard"; - } - - // add the search query string - subs["QUERY"] = LLURI::escape(p.query); - - subs["COLLECTION"] = ""; - if (subs["TYPE"] == "standard") - { - if (mCollectionType.find(p.collection) != mCollectionType.end()) - { - subs["COLLECTION"] = "&collection_chosen=" + std::string(p.collection); - } - else - { - std::string collection_args(""); - for (std::set::iterator it = mCollectionType.begin(); it != mCollectionType.end(); ++it) - { - collection_args += "&collection_chosen=" + std::string(*it); - } - subs["COLLECTION"] = collection_args; - } - } - - // add the user's preferred maturity (can be changed via prefs) - std::string maturity; - if (gAgent.prefersAdult()) - { - maturity = "gma"; // PG,Mature,Adult - } - else if (gAgent.prefersMature()) - { - maturity = "gm"; // PG,Mature - } - else - { - maturity = "g"; // PG - } - subs["MATURITY"] = maturity; - - // add the user's god status - subs["GODLIKE"] = gAgent.isGodlike() ? "1" : "0"; - - // get the search URL and expand all of the substitutions - // (also adds things like [LANGUAGE], [VERSION], [OS], etc.) - std::string url = gSavedSettings.getString("SearchURL"); - url = LLWeb::expandURLSubstitutions(url, subs); - - // and load the URL in the web view - mWebBrowser->navigateTo(url, HTTP_CONTENT_TEXT_HTML); -} +/** + * @file llfloatersearch.cpp + * @author Martin Reddy + * @brief Search floater - uses an embedded web browser control + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llcommandhandler.h" +#include "llfloaterreg.h" +#include "llfloatersearch.h" +#include "llhttpconstants.h" +#include "llmediactrl.h" +#include "llnotificationsutil.h" +#include "lllogininstance.h" +#include "lluri.h" +#include "llagent.h" +#include "llui.h" +#include "llviewercontrol.h" +#include "llweb.h" + +// support secondlife:///app/search/{CATEGORY}/{QUERY} SLapps +class LLSearchHandler : public LLCommandHandler +{ +public: + // requires trusted browser to trigger + LLSearchHandler() : LLCommandHandler("search", UNTRUSTED_CLICK_ONLY) { } + bool handle(const LLSD& tokens, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) + { + const size_t parts = tokens.size(); + + // get the (optional) category for the search + std::string collection; + if (parts > 0) + { + collection = tokens[0].asString(); + } + + // get the (optional) search string + std::string search_text; + if (parts > 1) + { + search_text = tokens[1].asString(); + } + + // create the LLSD arguments for the search floater + LLFloaterSearch::Params p; + p.search.collection = collection; + p.search.query = LLURI::unescape(search_text); + + // open the search floater and perform the requested search + LLFloaterReg::showInstance("search", p); + return true; + } +}; +LLSearchHandler gSearchHandler; + +LLFloaterSearch::SearchQuery::SearchQuery() +: category("category", ""), + collection("collection", ""), + query("query") +{} + +LLFloaterSearch::LLFloaterSearch(const Params& key) : + LLFloaterWebContent(key), + mSearchGodLevel(0) +{ + // declare a map that transforms a category name into + // the URL suffix that is used to search that category + + mSearchType.insert("standard"); + mSearchType.insert("land"); + mSearchType.insert("classified"); + + mCollectionType.insert("events"); + mCollectionType.insert("destinations"); + mCollectionType.insert("places"); + mCollectionType.insert("groups"); + mCollectionType.insert("people"); +} + +bool LLFloaterSearch::postBuild() +{ + LLFloaterWebContent::postBuild(); + mWebBrowser->addObserver(this); + + return true; +} + +void LLFloaterSearch::onOpen(const LLSD& key) +{ + Params p(key); + p.trusted_content = true; + p.allow_address_entry = false; + + LLFloaterWebContent::onOpen(p); + mWebBrowser->setFocus(true); + search(p.search); +} + +void LLFloaterSearch::onClose(bool app_quitting) +{ + LLFloaterWebContent::onClose(app_quitting); + // tear down the web view so we don't show the previous search + // result when the floater is opened next time + destroy(); +} + +void LLFloaterSearch::godLevelChanged(U8 godlevel) +{ + // search results can change based upon god level - if the user + // changes god level, then give them a warning (we don't refresh + // the search as this might undo any page navigation or + // AJAX-driven changes since the last search). + + //FIXME: set status bar text + + //getChildView("refresh_search")->setVisible( (godlevel != mSearchGodLevel)); +} + +void LLFloaterSearch::search(const SearchQuery &p) +{ + if (! mWebBrowser || !p.validateBlock()) + { + return; + } + + // reset the god level warning as we're sending the latest state + getChildView("refresh_search")->setVisible(false); + mSearchGodLevel = gAgent.getGodLevel(); + + // work out the subdir to use based on the requested category + LLSD subs; + if (mSearchType.find(p.category) != mSearchType.end()) + { + subs["TYPE"] = p.category; + } + else + { + subs["TYPE"] = "standard"; + } + + // add the search query string + subs["QUERY"] = LLURI::escape(p.query); + + subs["COLLECTION"] = ""; + if (subs["TYPE"] == "standard") + { + if (mCollectionType.find(p.collection) != mCollectionType.end()) + { + subs["COLLECTION"] = "&collection_chosen=" + std::string(p.collection); + } + else + { + std::string collection_args(""); + for (std::set::iterator it = mCollectionType.begin(); it != mCollectionType.end(); ++it) + { + collection_args += "&collection_chosen=" + std::string(*it); + } + subs["COLLECTION"] = collection_args; + } + } + + // add the user's preferred maturity (can be changed via prefs) + std::string maturity; + if (gAgent.prefersAdult()) + { + maturity = "gma"; // PG,Mature,Adult + } + else if (gAgent.prefersMature()) + { + maturity = "gm"; // PG,Mature + } + else + { + maturity = "g"; // PG + } + subs["MATURITY"] = maturity; + + // add the user's god status + subs["GODLIKE"] = gAgent.isGodlike() ? "1" : "0"; + + // get the search URL and expand all of the substitutions + // (also adds things like [LANGUAGE], [VERSION], [OS], etc.) + std::string url = gSavedSettings.getString("SearchURL"); + url = LLWeb::expandURLSubstitutions(url, subs); + + // and load the URL in the web view + mWebBrowser->navigateTo(url, HTTP_CONTENT_TEXT_HTML); +} diff --git a/indra/newview/llfloatersearch.h b/indra/newview/llfloatersearch.h index b66658dd45..beaac2ad2f 100644 --- a/indra/newview/llfloatersearch.h +++ b/indra/newview/llfloatersearch.h @@ -1,94 +1,94 @@ -/** - * @file llfloatersearch.h - * @author Martin Reddy - * @brief Search floater - uses an embedded web browser control - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERSEARCH_H -#define LL_LLFLOATERSEARCH_H - -#include "llfloaterwebcontent.h" -#include "llviewermediaobserver.h" - -#include - -class LLMediaCtrl; - -/// -/// The search floater allows users to perform all search operations. -/// All search functionality is now implemented via web services and -/// so this floater simply embeds a web view and displays the search -/// web page. The browser control is explicitly marked as "trusted" -/// so that the user can click on teleport links in search results. -/// -class LLFloaterSearch : - public LLFloaterWebContent -{ -public: - struct SearchQuery : public LLInitParam::Block - { - Optional category; - Optional collection; - Optional query; - - SearchQuery(); - }; - - struct _Params : public LLInitParam::Block<_Params, LLFloaterWebContent::Params> - { - Optional search; - }; - - typedef LLSDParamAdapter<_Params> Params; - - LLFloaterSearch(const Params& key); - - /// show the search floater with a new search - /// see search() for details on the key parameter. - /*virtual*/ void onOpen(const LLSD& key); - - /*virtual*/ void onClose(bool app_quitting); - - /// perform a search with the specific search term. - /// The key should be a map that can contain the following keys: - /// - "id": specifies the text phrase to search for - /// - "category": one of "all" (default), "people", "places", - /// "events", "groups", "wiki", "destinations", "classifieds" - void search(const SearchQuery &query); - - /// changing godmode can affect the search results that are - /// returned by the search website - use this method to tell the - /// search floater that the user has changed god level. - void godLevelChanged(U8 godlevel); - -private: - /*virtual*/ bool postBuild(); - - std::set mSearchType; - std::set mCollectionType; - U8 mSearchGodLevel; -}; - -#endif // LL_LLFLOATERSEARCH_H - +/** + * @file llfloatersearch.h + * @author Martin Reddy + * @brief Search floater - uses an embedded web browser control + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERSEARCH_H +#define LL_LLFLOATERSEARCH_H + +#include "llfloaterwebcontent.h" +#include "llviewermediaobserver.h" + +#include + +class LLMediaCtrl; + +/// +/// The search floater allows users to perform all search operations. +/// All search functionality is now implemented via web services and +/// so this floater simply embeds a web view and displays the search +/// web page. The browser control is explicitly marked as "trusted" +/// so that the user can click on teleport links in search results. +/// +class LLFloaterSearch : + public LLFloaterWebContent +{ +public: + struct SearchQuery : public LLInitParam::Block + { + Optional category; + Optional collection; + Optional query; + + SearchQuery(); + }; + + struct _Params : public LLInitParam::Block<_Params, LLFloaterWebContent::Params> + { + Optional search; + }; + + typedef LLSDParamAdapter<_Params> Params; + + LLFloaterSearch(const Params& key); + + /// show the search floater with a new search + /// see search() for details on the key parameter. + /*virtual*/ void onOpen(const LLSD& key); + + /*virtual*/ void onClose(bool app_quitting); + + /// perform a search with the specific search term. + /// The key should be a map that can contain the following keys: + /// - "id": specifies the text phrase to search for + /// - "category": one of "all" (default), "people", "places", + /// "events", "groups", "wiki", "destinations", "classifieds" + void search(const SearchQuery &query); + + /// changing godmode can affect the search results that are + /// returned by the search website - use this method to tell the + /// search floater that the user has changed god level. + void godLevelChanged(U8 godlevel); + +private: + /*virtual*/ bool postBuild(); + + std::set mSearchType; + std::set mCollectionType; + U8 mSearchGodLevel; +}; + +#endif // LL_LLFLOATERSEARCH_H + diff --git a/indra/newview/llfloatersellland.cpp b/indra/newview/llfloatersellland.cpp index 03bc5717bc..691458d80a 100644 --- a/indra/newview/llfloatersellland.cpp +++ b/indra/newview/llfloatersellland.cpp @@ -1,557 +1,557 @@ -/** - * @file llfloatersellland.cpp - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloatersellland.h" - -#include "llavatarnamecache.h" -#include "llfloateravatarpicker.h" -#include "llfloaterreg.h" -#include "llfloaterland.h" -#include "lllineeditor.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "llparcel.h" -#include "llselectmgr.h" -#include "lltexturectrl.h" -#include "llviewercontrol.h" -#include "llviewerparcelmgr.h" -#include "lluictrlfactory.h" -#include "llviewerwindow.h" -#include "lltrans.h" - -class LLAvatarName; - -// defined in llfloaterland.cpp -void send_parcel_select_objects(S32 parcel_local_id, U32 return_type, - uuid_list_t* return_ids = NULL); - -enum Badge { BADGE_OK, BADGE_NOTE, BADGE_WARN, BADGE_ERROR }; - -class LLFloaterSellLandUI -: public LLFloater -{ -public: - LLFloaterSellLandUI(const LLSD& key); - virtual ~LLFloaterSellLandUI(); - /*virtual*/ void onClose(bool app_quitting); - -private: - class SelectionObserver : public LLParcelObserver - { - public: - SelectionObserver(LLFloaterSellLandUI* floater) : mFloater(floater) {} - virtual void changed(); - private: - LLFloaterSellLandUI* mFloater; - }; - -private: - LLViewerRegion* mRegion; - LLParcelSelectionHandle mParcelSelection; - bool mParcelIsForSale; - bool mSellToBuyer; - bool mChoseSellTo; - S32 mParcelPrice; - S32 mParcelActualArea; - LLUUID mParcelSnapshot; - LLUUID mAuthorizedBuyer; - bool mParcelSoldWithObjects; - SelectionObserver mParcelSelectionObserver; - boost::signals2::connection mAvatarNameCacheConnection; - - void updateParcelInfo(); - void refreshUI(); - void setBadge(const char* id, Badge badge); - - static void onChangeValue(LLUICtrl *ctrl, void *userdata); - void doSelectAgent(); - static void doCancel(void *userdata); - static void doSellLand(void *userdata); - bool onConfirmSale(const LLSD& notification, const LLSD& response); - static void doShowObjects(void *userdata); - - void callbackAvatarPick(const uuid_vec_t& ids, const std::vector names); - - void onBuyerNameCache(const LLAvatarName& av_name); - -public: - virtual bool postBuild(); - - bool setParcel(LLViewerRegion* region, LLParcelSelectionHandle parcel); - static bool callbackHighlightTransferable(const LLSD& notification, const LLSD& response); -}; - -// static -void LLFloaterSellLand::sellLand( - LLViewerRegion* region, LLParcelSelectionHandle parcel) -{ - LLFloaterSellLandUI* ui = LLFloaterReg::showTypedInstance("sell_land"); - ui->setParcel(region, parcel); -} - -// static -LLFloater* LLFloaterSellLand::buildFloater(const LLSD& key) -{ - LLFloaterSellLandUI* floater = new LLFloaterSellLandUI(key); - return floater; -} - -#if LL_WINDOWS -// passing 'this' during construction generates a warning. The callee -// only uses the pointer to hold a reference to 'this' which is -// already valid, so this call does the correct thing. Disable the -// warning so that we can compile without generating a warning. -#pragma warning(disable : 4355) -#endif -LLFloaterSellLandUI::LLFloaterSellLandUI(const LLSD& key) -: LLFloater(key), - mParcelSelectionObserver(this), - mRegion(0), - mAvatarNameCacheConnection() -{ - LLViewerParcelMgr::getInstance()->addObserver(&mParcelSelectionObserver); -} - -LLFloaterSellLandUI::~LLFloaterSellLandUI() -{ - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - LLViewerParcelMgr::getInstance()->removeObserver(&mParcelSelectionObserver); -} - -// Because we are single_instance, we are not destroyed on close. -void LLFloaterSellLandUI::onClose(bool app_quitting) -{ - // Must release parcel selection to allow land to deselect, see EXT-803 - mParcelSelection = NULL; -} - -void LLFloaterSellLandUI::SelectionObserver::changed() -{ - if (LLViewerParcelMgr::getInstance()->selectionEmpty()) - { - mFloater->closeFloater(); - } - else if (mFloater->getVisible()) // only update selection if sell land ui in use - { - mFloater->setParcel(LLViewerParcelMgr::getInstance()->getSelectionRegion(), - LLViewerParcelMgr::getInstance()->getParcelSelection()); - } -} - -bool LLFloaterSellLandUI::postBuild() -{ - childSetCommitCallback("sell_to", onChangeValue, this); - childSetCommitCallback("price", onChangeValue, this); - getChild("price")->setPrevalidate(LLTextValidate::validateNonNegativeS32); - childSetCommitCallback("sell_objects", onChangeValue, this); - childSetAction("sell_to_select_agent", boost::bind( &LLFloaterSellLandUI::doSelectAgent, this)); - childSetAction("cancel_btn", doCancel, this); - childSetAction("sell_btn", doSellLand, this); - childSetAction("show_objects", doShowObjects, this); - center(); - getChild("profile_scroll")->setTabStop(true); - return true; -} - -bool LLFloaterSellLandUI::setParcel(LLViewerRegion* region, LLParcelSelectionHandle parcel) -{ - if (!parcel->getParcel()) - { - return false; - } - - mRegion = region; - mParcelSelection = parcel; - mChoseSellTo = false; - - - updateParcelInfo(); - refreshUI(); - - return true; -} - -void LLFloaterSellLandUI::updateParcelInfo() -{ - LLParcel* parcelp = mParcelSelection->getParcel(); - if (!parcelp) return; - - mParcelActualArea = parcelp->getArea(); - mParcelIsForSale = parcelp->getForSale(); - if (mParcelIsForSale) - { - mChoseSellTo = true; - } - mParcelPrice = mParcelIsForSale ? parcelp->getSalePrice() : 0; - mParcelSoldWithObjects = parcelp->getSellWithObjects(); - if (mParcelIsForSale) - { - getChild("price")->setValue(mParcelPrice); - if (mParcelSoldWithObjects) - { - getChild("sell_objects")->setValue("yes"); - } - else - { - getChild("sell_objects")->setValue("no"); - } - } - else - { - getChild("price")->setValue(""); - getChild("sell_objects")->setValue("none"); - } - - mParcelSnapshot = parcelp->getSnapshotID(); - - mAuthorizedBuyer = parcelp->getAuthorizedBuyerID(); - mSellToBuyer = mAuthorizedBuyer.notNull(); - - if(mSellToBuyer) - { - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - mAvatarNameCacheConnection = LLAvatarNameCache::get(mAuthorizedBuyer, boost::bind(&LLFloaterSellLandUI::onBuyerNameCache, this, _2)); - } -} - -void LLFloaterSellLandUI::onBuyerNameCache(const LLAvatarName& av_name) -{ - mAvatarNameCacheConnection.disconnect(); - - getChild("sell_to_agent")->setValue(av_name.getCompleteName()); - getChild("sell_to_agent")->setToolTip(av_name.getUserName()); -} - -void LLFloaterSellLandUI::setBadge(const char* id, Badge badge) -{ - static std::string badgeOK("badge_ok.j2c"); - static std::string badgeNote("badge_note.j2c"); - static std::string badgeWarn("badge_warn.j2c"); - - std::string badgeName; - switch (badge) - { - default: - case BADGE_OK: badgeName = badgeOK; break; - case BADGE_NOTE: badgeName = badgeNote; break; - case BADGE_WARN: badgeName = badgeWarn; break; - case BADGE_ERROR: badgeName = badgeWarn; break; - } - - getChild(id)->setValue(badgeName); -} - -void LLFloaterSellLandUI::refreshUI() -{ - LLParcel* parcelp = mParcelSelection->getParcel(); - if (!parcelp) return; - - LLTextureCtrl* snapshot = getChild("info_image"); - snapshot->setImageAssetID(mParcelSnapshot); - - getChild("info_parcel")->setValue(parcelp->getName()); - getChild("info_size")->setTextArg("[AREA]", llformat("%d", mParcelActualArea)); - - std::string price_str = getChild("price")->getValue().asString(); - bool valid_price = !price_str.empty() && LLTextValidate::validateNonNegativeS32.validate(price_str); - if (valid_price && mParcelActualArea > 0) - { - F32 per_meter_price = 0; - per_meter_price = F32(mParcelPrice) / F32(mParcelActualArea); - getChild("price_per_m")->setTextArg("[PER_METER]", llformat("%0.2f", per_meter_price)); - getChildView("price_per_m")->setVisible(true); - - setBadge("step_price", BADGE_OK); - } - else - { - getChildView("price_per_m")->setVisible(false); - - if (price_str.empty()) - { - setBadge("step_price", BADGE_NOTE); - } - else - { - setBadge("step_price", BADGE_ERROR); - } - } - - if (mSellToBuyer) - { - getChild("sell_to")->setValue("user"); - getChildView("sell_to_agent")->setVisible(true); - getChildView("sell_to_select_agent")->setVisible(true); - } - else - { - if (mChoseSellTo) - { - getChild("sell_to")->setValue("anyone"); - } - else - { - getChild("sell_to")->setValue("select"); - } - getChildView("sell_to_agent")->setVisible(false); - getChildView("sell_to_select_agent")->setVisible(false); - } - - // Must select Sell To: Anybody, or User (with a specified username) - std::string sell_to = getChild("sell_to")->getValue().asString(); - bool valid_sell_to = "select" != sell_to && ("user" != sell_to || mAuthorizedBuyer.notNull()); - if (!valid_sell_to) - { - setBadge("step_sell_to", BADGE_NOTE); - } - else - { - setBadge("step_sell_to", BADGE_OK); - } - - bool valid_sell_objects = ("none" != getChild("sell_objects")->getValue().asString()); - if (!valid_sell_objects) - { - setBadge("step_sell_objects", BADGE_NOTE); - } - else - { - setBadge("step_sell_objects", BADGE_OK); - } - - if (valid_sell_to && valid_price && valid_sell_objects) - { - getChildView("sell_btn")->setEnabled(true); - } - else - { - getChildView("sell_btn")->setEnabled(false); - } -} - -// static -void LLFloaterSellLandUI::onChangeValue(LLUICtrl *ctrl, void *userdata) -{ - LLFloaterSellLandUI *self = (LLFloaterSellLandUI *)userdata; - - std::string sell_to = self->getChild("sell_to")->getValue().asString(); - - if (sell_to == "user") - { - self->mChoseSellTo = true; - self->mSellToBuyer = true; - if (self->mAuthorizedBuyer.isNull()) - { - self->doSelectAgent(); - } - } - else if (sell_to == "anyone") - { - self->mChoseSellTo = true; - self->mSellToBuyer = false; - } - - self->mParcelPrice = self->getChild("price")->getValue(); - - if ("yes" == self->getChild("sell_objects")->getValue().asString()) - { - self->mParcelSoldWithObjects = true; - } - else - { - self->mParcelSoldWithObjects = false; - } - - self->refreshUI(); -} - -void LLFloaterSellLandUI::doSelectAgent() -{ - LLView * button = findChild("sell_to_select_agent"); - LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(boost::bind(&LLFloaterSellLandUI::callbackAvatarPick, this, _1, _2), false, true, false, this->getName(), button); - // grandparent is a floater, in order to set up dependency - if (picker) - { - addDependentFloater(picker); - } -} - -void LLFloaterSellLandUI::callbackAvatarPick(const uuid_vec_t& ids, const std::vector names) -{ - LLParcel* parcel = mParcelSelection->getParcel(); - - if (names.empty() || ids.empty()) return; - - LLUUID id = ids[0]; - parcel->setAuthorizedBuyerID(id); - - mAuthorizedBuyer = ids[0]; - - getChild("sell_to_agent")->setValue(names[0].getCompleteName()); - - refreshUI(); -} - -// static -void LLFloaterSellLandUI::doCancel(void *userdata) -{ - LLFloaterSellLandUI* self = (LLFloaterSellLandUI*)userdata; - self->closeFloater(); -} - -// static -void LLFloaterSellLandUI::doShowObjects(void *userdata) -{ - LLFloaterSellLandUI* self = (LLFloaterSellLandUI*)userdata; - LLParcel* parcel = self->mParcelSelection->getParcel(); - if (!parcel) return; - - send_parcel_select_objects(parcel->getLocalID(), RT_SELL); - - // we shouldn't pass callback functor since it is registered in LLFunctorRegistration - LLNotificationsUtil::add("TransferObjectsHighlighted", - LLSD(), LLSD()); -} - -static LLNotificationFunctorRegistration tr("TransferObjectsHighlighted", &LLFloaterSellLandUI::callbackHighlightTransferable); - -// static -bool LLFloaterSellLandUI::callbackHighlightTransferable(const LLSD& notification, const LLSD& data) -{ - LLSelectMgr::getInstance()->unhighlightAll(); - return false; -} - -// static -void LLFloaterSellLandUI::doSellLand(void *userdata) -{ - LLFloaterSellLandUI* self = (LLFloaterSellLandUI*)userdata; - - LLParcel* parcel = self->mParcelSelection->getParcel(); - - // Do a confirmation - S32 sale_price = self->getChild("price")->getValue(); - S32 area = parcel->getArea(); - std::string authorizedBuyerName = LLTrans::getString("Anyone"); - bool sell_to_anyone = true; - if ("user" == self->getChild("sell_to")->getValue().asString()) - { - authorizedBuyerName = self->getChild("sell_to_agent")->getValue().asString(); - sell_to_anyone = false; - } - - // must sell to someone if indicating sale to anyone - if (!parcel->getForSale() - && (sale_price == 0) - && sell_to_anyone) - { - LLNotificationsUtil::add("SalePriceRestriction"); - return; - } - - LLSD args; - args["LAND_SIZE"] = llformat("%d",area); - args["SALE_PRICE"] = llformat("%d",sale_price); - args["NAME"] = authorizedBuyerName; - - LLNotification::Params params("ConfirmLandSaleChange"); - params.substitutions(args) - .functor.function(boost::bind(&LLFloaterSellLandUI::onConfirmSale, self, _1, _2)); - - if (sell_to_anyone) - { - params.name("ConfirmLandSaleToAnyoneChange"); - } - - if (parcel->getForSale()) - { - // parcel already for sale, so ignore this question - LLNotifications::instance().forceResponse(params, -1); - } - else - { - // ask away - LLNotifications::instance().add(params); - } - -} - -bool LLFloaterSellLandUI::onConfirmSale(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option != 0) - { - return false; - } - S32 sale_price = getChild("price")->getValue(); - - // Valid extracted data - if (sale_price < 0) - { - // TomY TODO: Throw an error - return false; - } - - LLParcel* parcel = mParcelSelection->getParcel(); - if (!parcel) return false; - - // can_agent_modify_parcel deprecated by GROUPS -// if (!can_agent_modify_parcel(parcel)) -// { -// close(); -// return; -// } - - parcel->setParcelFlag(PF_FOR_SALE, true); - parcel->setSalePrice(sale_price); - bool sell_with_objects = false; - if ("yes" == getChild("sell_objects")->getValue().asString()) - { - sell_with_objects = true; - } - parcel->setSellWithObjects(sell_with_objects); - if ("user" == getChild("sell_to")->getValue().asString()) - { - parcel->setAuthorizedBuyerID(mAuthorizedBuyer); - } - else - { - parcel->setAuthorizedBuyerID(LLUUID::null); - } - - // Send update to server - LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate( parcel ); - - closeFloater(); - return false; -} +/** + * @file llfloatersellland.cpp + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatersellland.h" + +#include "llavatarnamecache.h" +#include "llfloateravatarpicker.h" +#include "llfloaterreg.h" +#include "llfloaterland.h" +#include "lllineeditor.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "llparcel.h" +#include "llselectmgr.h" +#include "lltexturectrl.h" +#include "llviewercontrol.h" +#include "llviewerparcelmgr.h" +#include "lluictrlfactory.h" +#include "llviewerwindow.h" +#include "lltrans.h" + +class LLAvatarName; + +// defined in llfloaterland.cpp +void send_parcel_select_objects(S32 parcel_local_id, U32 return_type, + uuid_list_t* return_ids = NULL); + +enum Badge { BADGE_OK, BADGE_NOTE, BADGE_WARN, BADGE_ERROR }; + +class LLFloaterSellLandUI +: public LLFloater +{ +public: + LLFloaterSellLandUI(const LLSD& key); + virtual ~LLFloaterSellLandUI(); + /*virtual*/ void onClose(bool app_quitting); + +private: + class SelectionObserver : public LLParcelObserver + { + public: + SelectionObserver(LLFloaterSellLandUI* floater) : mFloater(floater) {} + virtual void changed(); + private: + LLFloaterSellLandUI* mFloater; + }; + +private: + LLViewerRegion* mRegion; + LLParcelSelectionHandle mParcelSelection; + bool mParcelIsForSale; + bool mSellToBuyer; + bool mChoseSellTo; + S32 mParcelPrice; + S32 mParcelActualArea; + LLUUID mParcelSnapshot; + LLUUID mAuthorizedBuyer; + bool mParcelSoldWithObjects; + SelectionObserver mParcelSelectionObserver; + boost::signals2::connection mAvatarNameCacheConnection; + + void updateParcelInfo(); + void refreshUI(); + void setBadge(const char* id, Badge badge); + + static void onChangeValue(LLUICtrl *ctrl, void *userdata); + void doSelectAgent(); + static void doCancel(void *userdata); + static void doSellLand(void *userdata); + bool onConfirmSale(const LLSD& notification, const LLSD& response); + static void doShowObjects(void *userdata); + + void callbackAvatarPick(const uuid_vec_t& ids, const std::vector names); + + void onBuyerNameCache(const LLAvatarName& av_name); + +public: + virtual bool postBuild(); + + bool setParcel(LLViewerRegion* region, LLParcelSelectionHandle parcel); + static bool callbackHighlightTransferable(const LLSD& notification, const LLSD& response); +}; + +// static +void LLFloaterSellLand::sellLand( + LLViewerRegion* region, LLParcelSelectionHandle parcel) +{ + LLFloaterSellLandUI* ui = LLFloaterReg::showTypedInstance("sell_land"); + ui->setParcel(region, parcel); +} + +// static +LLFloater* LLFloaterSellLand::buildFloater(const LLSD& key) +{ + LLFloaterSellLandUI* floater = new LLFloaterSellLandUI(key); + return floater; +} + +#if LL_WINDOWS +// passing 'this' during construction generates a warning. The callee +// only uses the pointer to hold a reference to 'this' which is +// already valid, so this call does the correct thing. Disable the +// warning so that we can compile without generating a warning. +#pragma warning(disable : 4355) +#endif +LLFloaterSellLandUI::LLFloaterSellLandUI(const LLSD& key) +: LLFloater(key), + mParcelSelectionObserver(this), + mRegion(0), + mAvatarNameCacheConnection() +{ + LLViewerParcelMgr::getInstance()->addObserver(&mParcelSelectionObserver); +} + +LLFloaterSellLandUI::~LLFloaterSellLandUI() +{ + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + LLViewerParcelMgr::getInstance()->removeObserver(&mParcelSelectionObserver); +} + +// Because we are single_instance, we are not destroyed on close. +void LLFloaterSellLandUI::onClose(bool app_quitting) +{ + // Must release parcel selection to allow land to deselect, see EXT-803 + mParcelSelection = NULL; +} + +void LLFloaterSellLandUI::SelectionObserver::changed() +{ + if (LLViewerParcelMgr::getInstance()->selectionEmpty()) + { + mFloater->closeFloater(); + } + else if (mFloater->getVisible()) // only update selection if sell land ui in use + { + mFloater->setParcel(LLViewerParcelMgr::getInstance()->getSelectionRegion(), + LLViewerParcelMgr::getInstance()->getParcelSelection()); + } +} + +bool LLFloaterSellLandUI::postBuild() +{ + childSetCommitCallback("sell_to", onChangeValue, this); + childSetCommitCallback("price", onChangeValue, this); + getChild("price")->setPrevalidate(LLTextValidate::validateNonNegativeS32); + childSetCommitCallback("sell_objects", onChangeValue, this); + childSetAction("sell_to_select_agent", boost::bind( &LLFloaterSellLandUI::doSelectAgent, this)); + childSetAction("cancel_btn", doCancel, this); + childSetAction("sell_btn", doSellLand, this); + childSetAction("show_objects", doShowObjects, this); + center(); + getChild("profile_scroll")->setTabStop(true); + return true; +} + +bool LLFloaterSellLandUI::setParcel(LLViewerRegion* region, LLParcelSelectionHandle parcel) +{ + if (!parcel->getParcel()) + { + return false; + } + + mRegion = region; + mParcelSelection = parcel; + mChoseSellTo = false; + + + updateParcelInfo(); + refreshUI(); + + return true; +} + +void LLFloaterSellLandUI::updateParcelInfo() +{ + LLParcel* parcelp = mParcelSelection->getParcel(); + if (!parcelp) return; + + mParcelActualArea = parcelp->getArea(); + mParcelIsForSale = parcelp->getForSale(); + if (mParcelIsForSale) + { + mChoseSellTo = true; + } + mParcelPrice = mParcelIsForSale ? parcelp->getSalePrice() : 0; + mParcelSoldWithObjects = parcelp->getSellWithObjects(); + if (mParcelIsForSale) + { + getChild("price")->setValue(mParcelPrice); + if (mParcelSoldWithObjects) + { + getChild("sell_objects")->setValue("yes"); + } + else + { + getChild("sell_objects")->setValue("no"); + } + } + else + { + getChild("price")->setValue(""); + getChild("sell_objects")->setValue("none"); + } + + mParcelSnapshot = parcelp->getSnapshotID(); + + mAuthorizedBuyer = parcelp->getAuthorizedBuyerID(); + mSellToBuyer = mAuthorizedBuyer.notNull(); + + if(mSellToBuyer) + { + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + mAvatarNameCacheConnection = LLAvatarNameCache::get(mAuthorizedBuyer, boost::bind(&LLFloaterSellLandUI::onBuyerNameCache, this, _2)); + } +} + +void LLFloaterSellLandUI::onBuyerNameCache(const LLAvatarName& av_name) +{ + mAvatarNameCacheConnection.disconnect(); + + getChild("sell_to_agent")->setValue(av_name.getCompleteName()); + getChild("sell_to_agent")->setToolTip(av_name.getUserName()); +} + +void LLFloaterSellLandUI::setBadge(const char* id, Badge badge) +{ + static std::string badgeOK("badge_ok.j2c"); + static std::string badgeNote("badge_note.j2c"); + static std::string badgeWarn("badge_warn.j2c"); + + std::string badgeName; + switch (badge) + { + default: + case BADGE_OK: badgeName = badgeOK; break; + case BADGE_NOTE: badgeName = badgeNote; break; + case BADGE_WARN: badgeName = badgeWarn; break; + case BADGE_ERROR: badgeName = badgeWarn; break; + } + + getChild(id)->setValue(badgeName); +} + +void LLFloaterSellLandUI::refreshUI() +{ + LLParcel* parcelp = mParcelSelection->getParcel(); + if (!parcelp) return; + + LLTextureCtrl* snapshot = getChild("info_image"); + snapshot->setImageAssetID(mParcelSnapshot); + + getChild("info_parcel")->setValue(parcelp->getName()); + getChild("info_size")->setTextArg("[AREA]", llformat("%d", mParcelActualArea)); + + std::string price_str = getChild("price")->getValue().asString(); + bool valid_price = !price_str.empty() && LLTextValidate::validateNonNegativeS32.validate(price_str); + if (valid_price && mParcelActualArea > 0) + { + F32 per_meter_price = 0; + per_meter_price = F32(mParcelPrice) / F32(mParcelActualArea); + getChild("price_per_m")->setTextArg("[PER_METER]", llformat("%0.2f", per_meter_price)); + getChildView("price_per_m")->setVisible(true); + + setBadge("step_price", BADGE_OK); + } + else + { + getChildView("price_per_m")->setVisible(false); + + if (price_str.empty()) + { + setBadge("step_price", BADGE_NOTE); + } + else + { + setBadge("step_price", BADGE_ERROR); + } + } + + if (mSellToBuyer) + { + getChild("sell_to")->setValue("user"); + getChildView("sell_to_agent")->setVisible(true); + getChildView("sell_to_select_agent")->setVisible(true); + } + else + { + if (mChoseSellTo) + { + getChild("sell_to")->setValue("anyone"); + } + else + { + getChild("sell_to")->setValue("select"); + } + getChildView("sell_to_agent")->setVisible(false); + getChildView("sell_to_select_agent")->setVisible(false); + } + + // Must select Sell To: Anybody, or User (with a specified username) + std::string sell_to = getChild("sell_to")->getValue().asString(); + bool valid_sell_to = "select" != sell_to && ("user" != sell_to || mAuthorizedBuyer.notNull()); + if (!valid_sell_to) + { + setBadge("step_sell_to", BADGE_NOTE); + } + else + { + setBadge("step_sell_to", BADGE_OK); + } + + bool valid_sell_objects = ("none" != getChild("sell_objects")->getValue().asString()); + if (!valid_sell_objects) + { + setBadge("step_sell_objects", BADGE_NOTE); + } + else + { + setBadge("step_sell_objects", BADGE_OK); + } + + if (valid_sell_to && valid_price && valid_sell_objects) + { + getChildView("sell_btn")->setEnabled(true); + } + else + { + getChildView("sell_btn")->setEnabled(false); + } +} + +// static +void LLFloaterSellLandUI::onChangeValue(LLUICtrl *ctrl, void *userdata) +{ + LLFloaterSellLandUI *self = (LLFloaterSellLandUI *)userdata; + + std::string sell_to = self->getChild("sell_to")->getValue().asString(); + + if (sell_to == "user") + { + self->mChoseSellTo = true; + self->mSellToBuyer = true; + if (self->mAuthorizedBuyer.isNull()) + { + self->doSelectAgent(); + } + } + else if (sell_to == "anyone") + { + self->mChoseSellTo = true; + self->mSellToBuyer = false; + } + + self->mParcelPrice = self->getChild("price")->getValue(); + + if ("yes" == self->getChild("sell_objects")->getValue().asString()) + { + self->mParcelSoldWithObjects = true; + } + else + { + self->mParcelSoldWithObjects = false; + } + + self->refreshUI(); +} + +void LLFloaterSellLandUI::doSelectAgent() +{ + LLView * button = findChild("sell_to_select_agent"); + LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(boost::bind(&LLFloaterSellLandUI::callbackAvatarPick, this, _1, _2), false, true, false, this->getName(), button); + // grandparent is a floater, in order to set up dependency + if (picker) + { + addDependentFloater(picker); + } +} + +void LLFloaterSellLandUI::callbackAvatarPick(const uuid_vec_t& ids, const std::vector names) +{ + LLParcel* parcel = mParcelSelection->getParcel(); + + if (names.empty() || ids.empty()) return; + + LLUUID id = ids[0]; + parcel->setAuthorizedBuyerID(id); + + mAuthorizedBuyer = ids[0]; + + getChild("sell_to_agent")->setValue(names[0].getCompleteName()); + + refreshUI(); +} + +// static +void LLFloaterSellLandUI::doCancel(void *userdata) +{ + LLFloaterSellLandUI* self = (LLFloaterSellLandUI*)userdata; + self->closeFloater(); +} + +// static +void LLFloaterSellLandUI::doShowObjects(void *userdata) +{ + LLFloaterSellLandUI* self = (LLFloaterSellLandUI*)userdata; + LLParcel* parcel = self->mParcelSelection->getParcel(); + if (!parcel) return; + + send_parcel_select_objects(parcel->getLocalID(), RT_SELL); + + // we shouldn't pass callback functor since it is registered in LLFunctorRegistration + LLNotificationsUtil::add("TransferObjectsHighlighted", + LLSD(), LLSD()); +} + +static LLNotificationFunctorRegistration tr("TransferObjectsHighlighted", &LLFloaterSellLandUI::callbackHighlightTransferable); + +// static +bool LLFloaterSellLandUI::callbackHighlightTransferable(const LLSD& notification, const LLSD& data) +{ + LLSelectMgr::getInstance()->unhighlightAll(); + return false; +} + +// static +void LLFloaterSellLandUI::doSellLand(void *userdata) +{ + LLFloaterSellLandUI* self = (LLFloaterSellLandUI*)userdata; + + LLParcel* parcel = self->mParcelSelection->getParcel(); + + // Do a confirmation + S32 sale_price = self->getChild("price")->getValue(); + S32 area = parcel->getArea(); + std::string authorizedBuyerName = LLTrans::getString("Anyone"); + bool sell_to_anyone = true; + if ("user" == self->getChild("sell_to")->getValue().asString()) + { + authorizedBuyerName = self->getChild("sell_to_agent")->getValue().asString(); + sell_to_anyone = false; + } + + // must sell to someone if indicating sale to anyone + if (!parcel->getForSale() + && (sale_price == 0) + && sell_to_anyone) + { + LLNotificationsUtil::add("SalePriceRestriction"); + return; + } + + LLSD args; + args["LAND_SIZE"] = llformat("%d",area); + args["SALE_PRICE"] = llformat("%d",sale_price); + args["NAME"] = authorizedBuyerName; + + LLNotification::Params params("ConfirmLandSaleChange"); + params.substitutions(args) + .functor.function(boost::bind(&LLFloaterSellLandUI::onConfirmSale, self, _1, _2)); + + if (sell_to_anyone) + { + params.name("ConfirmLandSaleToAnyoneChange"); + } + + if (parcel->getForSale()) + { + // parcel already for sale, so ignore this question + LLNotifications::instance().forceResponse(params, -1); + } + else + { + // ask away + LLNotifications::instance().add(params); + } + +} + +bool LLFloaterSellLandUI::onConfirmSale(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) + { + return false; + } + S32 sale_price = getChild("price")->getValue(); + + // Valid extracted data + if (sale_price < 0) + { + // TomY TODO: Throw an error + return false; + } + + LLParcel* parcel = mParcelSelection->getParcel(); + if (!parcel) return false; + + // can_agent_modify_parcel deprecated by GROUPS +// if (!can_agent_modify_parcel(parcel)) +// { +// close(); +// return; +// } + + parcel->setParcelFlag(PF_FOR_SALE, true); + parcel->setSalePrice(sale_price); + bool sell_with_objects = false; + if ("yes" == getChild("sell_objects")->getValue().asString()) + { + sell_with_objects = true; + } + parcel->setSellWithObjects(sell_with_objects); + if ("user" == getChild("sell_to")->getValue().asString()) + { + parcel->setAuthorizedBuyerID(mAuthorizedBuyer); + } + else + { + parcel->setAuthorizedBuyerID(LLUUID::null); + } + + // Send update to server + LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate( parcel ); + + closeFloater(); + return false; +} diff --git a/indra/newview/llfloatersettingsdebug.cpp b/indra/newview/llfloatersettingsdebug.cpp index 51569fb7d3..17707e808e 100644 --- a/indra/newview/llfloatersettingsdebug.cpp +++ b/indra/newview/llfloatersettingsdebug.cpp @@ -1,638 +1,638 @@ -/** - * @file llfloatersettingsdebug.cpp - * @brief floater for debugging internal viewer settings - * - * $LicenseInfo:firstyear=2022&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2022, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llfloatersettingsdebug.h" -#include "llfloater.h" -#include "llfiltereditor.h" -#include "lluictrlfactory.h" -#include "llcombobox.h" -#include "llspinctrl.h" -#include "llcolorswatch.h" -#include "llviewercontrol.h" -#include "lltexteditor.h" - - -LLFloaterSettingsDebug::LLFloaterSettingsDebug(const LLSD& key) -: LLFloater(key), - mSettingList(NULL) -{ - mCommitCallbackRegistrar.add("CommitSettings", boost::bind(&LLFloaterSettingsDebug::onCommitSettings, this)); - mCommitCallbackRegistrar.add("ClickDefault", boost::bind(&LLFloaterSettingsDebug::onClickDefault, this)); -} - -LLFloaterSettingsDebug::~LLFloaterSettingsDebug() -{} - -bool LLFloaterSettingsDebug::postBuild() -{ - enableResizeCtrls(true, false, true); - - mComment = getChild("comment_text"); - - getChild("filter_input")->setCommitCallback(boost::bind(&LLFloaterSettingsDebug::setSearchFilter, this, _2)); - - mSettingList = getChild("setting_list"); - mSettingList->setCommitOnSelectionChange(true); - mSettingList->setCommitCallback(boost::bind(&LLFloaterSettingsDebug::onSettingSelect, this)); - - updateList(); - - gSavedSettings.getControl("DebugSettingsHideDefault")->getCommitSignal()->connect(boost::bind(&LLFloaterSettingsDebug::updateList, this, false)); - - return true; -} - -void LLFloaterSettingsDebug::draw() -{ - LLScrollListItem* first_selected = mSettingList->getFirstSelected(); - if (first_selected) - { - LLControlVariable* controlp = (LLControlVariable*)first_selected->getUserdata(); - updateControl(controlp); - } - - LLFloater::draw(); -} - -void LLFloaterSettingsDebug::onCommitSettings() -{ - LLScrollListItem* first_selected = mSettingList->getFirstSelected(); - if (!first_selected) - { - return; - } - LLControlVariable* controlp = (LLControlVariable*)first_selected->getUserdata(); - - if (!controlp) - { - return; - } - - LLVector3 vector; - LLVector3d vectord; - LLQuaternion quat; - LLRect rect; - LLColor4 col4; - LLColor3 col3; - LLColor4U col4U; - LLColor4 color_with_alpha; - - switch(controlp->type()) - { - case TYPE_U32: - controlp->set(getChild("val_spinner_1")->getValue()); - break; - case TYPE_S32: - controlp->set(getChild("val_spinner_1")->getValue()); - break; - case TYPE_F32: - controlp->set(LLSD(getChild("val_spinner_1")->getValue().asReal())); - break; - case TYPE_BOOLEAN: - controlp->set(getChild("boolean_combo")->getValue()); - break; - case TYPE_STRING: - controlp->set(LLSD(getChild("val_text")->getValue().asString())); - break; - case TYPE_VEC3: - vector.mV[VX] = (F32)getChild("val_spinner_1")->getValue().asReal(); - vector.mV[VY] = (F32)getChild("val_spinner_2")->getValue().asReal(); - vector.mV[VZ] = (F32)getChild("val_spinner_3")->getValue().asReal(); - controlp->set(vector.getValue()); - break; - case TYPE_VEC3D: - vectord.mdV[VX] = getChild("val_spinner_1")->getValue().asReal(); - vectord.mdV[VY] = getChild("val_spinner_2")->getValue().asReal(); - vectord.mdV[VZ] = getChild("val_spinner_3")->getValue().asReal(); - controlp->set(vectord.getValue()); - break; - case TYPE_QUAT: - quat.mQ[VX] = getChild("val_spinner_1")->getValue().asReal(); - quat.mQ[VY] = getChild("val_spinner_2")->getValue().asReal(); - quat.mQ[VZ] = getChild("val_spinner_3")->getValue().asReal(); - quat.mQ[VS] = getChild("val_spinner_4")->getValue().asReal();; - controlp->set(quat.getValue()); - break; - case TYPE_RECT: - rect.mLeft = getChild("val_spinner_1")->getValue().asInteger(); - rect.mRight = getChild("val_spinner_2")->getValue().asInteger(); - rect.mBottom = getChild("val_spinner_3")->getValue().asInteger(); - rect.mTop = getChild("val_spinner_4")->getValue().asInteger(); - controlp->set(rect.getValue()); - break; - case TYPE_COL4: - col3.setValue(getChild("val_color_swatch")->getValue()); - col4 = LLColor4(col3, (F32)getChild("val_spinner_4")->getValue().asReal()); - controlp->set(col4.getValue()); - break; - case TYPE_COL3: - controlp->set(getChild("val_color_swatch")->getValue()); - //col3.mV[VRED] = (F32)floaterp->getChild("val_spinner_1")->getValue().asC(); - //col3.mV[VGREEN] = (F32)floaterp->getChild("val_spinner_2")->getValue().asReal(); - //col3.mV[VBLUE] = (F32)floaterp->getChild("val_spinner_3")->getValue().asReal(); - //controlp->set(col3.getValue()); - break; - default: - break; - } - updateDefaultColumn(controlp); -} - -// static -void LLFloaterSettingsDebug::onClickDefault() -{ - LLScrollListItem* first_selected = mSettingList->getFirstSelected(); - if (first_selected) - { - LLControlVariable* controlp = (LLControlVariable*)first_selected->getUserdata(); - if (controlp) - { - controlp->resetToDefault(true); - updateDefaultColumn(controlp); - updateControl(controlp); - } - } -} - -// we've switched controls, or doing per-frame update, so update spinners, etc. -void LLFloaterSettingsDebug::updateControl(LLControlVariable* controlp) -{ - LLSpinCtrl* spinner1 = getChild("val_spinner_1"); - LLSpinCtrl* spinner2 = getChild("val_spinner_2"); - LLSpinCtrl* spinner3 = getChild("val_spinner_3"); - LLSpinCtrl* spinner4 = getChild("val_spinner_4"); - LLColorSwatchCtrl* color_swatch = getChild("val_color_swatch"); - - if (!spinner1 || !spinner2 || !spinner3 || !spinner4 || !color_swatch) - { - LL_WARNS() << "Could not find all desired controls by name" - << LL_ENDL; - return; - } - - hideUIControls(); - - if (controlp && !isSettingHidden(controlp)) - { - eControlType type = controlp->type(); - - //hide combo box only for non booleans, otherwise this will result in the combo box closing every frame - getChildView("boolean_combo")->setVisible( type == TYPE_BOOLEAN); - getChildView("default_btn")->setVisible(true); - getChildView("setting_name_txt")->setVisible(true); - getChild("setting_name_txt")->setText(controlp->getName()); - getChild("setting_name_txt")->setToolTip(controlp->getName()); - mComment->setVisible(true); - - std::string old_text = mComment->getText(); - std::string new_text = controlp->getComment(); - // Don't setText if not nessesary, it will reset scroll - // This is a debug UI that reads from xml, there might - // be use cases where comment changes, but not the name - if (old_text != new_text) - { - mComment->setText(controlp->getComment()); - } - - spinner1->setMaxValue(F32_MAX); - spinner2->setMaxValue(F32_MAX); - spinner3->setMaxValue(F32_MAX); - spinner4->setMaxValue(F32_MAX); - spinner1->setMinValue(-F32_MAX); - spinner2->setMinValue(-F32_MAX); - spinner3->setMinValue(-F32_MAX); - spinner4->setMinValue(-F32_MAX); - if (!spinner1->hasFocus()) - { - spinner1->setIncrement(0.1f); - } - if (!spinner2->hasFocus()) - { - spinner2->setIncrement(0.1f); - } - if (!spinner3->hasFocus()) - { - spinner3->setIncrement(0.1f); - } - if (!spinner4->hasFocus()) - { - spinner4->setIncrement(0.1f); - } - - LLSD sd = controlp->get(); - switch(type) - { - case TYPE_U32: - spinner1->setVisible(true); - spinner1->setLabel(std::string("value")); // Debug, don't translate - if (!spinner1->hasFocus()) - { - spinner1->setValue(sd); - spinner1->setMinValue((F32)U32_MIN); - spinner1->setMaxValue((F32)U32_MAX); - spinner1->setIncrement(1.f); - spinner1->setPrecision(0); - } - break; - case TYPE_S32: - spinner1->setVisible(true); - spinner1->setLabel(std::string("value")); // Debug, don't translate - if (!spinner1->hasFocus()) - { - spinner1->setValue(sd); - spinner1->setMinValue((F32)S32_MIN); - spinner1->setMaxValue((F32)S32_MAX); - spinner1->setIncrement(1.f); - spinner1->setPrecision(0); - } - break; - case TYPE_F32: - spinner1->setVisible(true); - spinner1->setLabel(std::string("value")); // Debug, don't translate - if (!spinner1->hasFocus()) - { - spinner1->setPrecision(3); - spinner1->setValue(sd); - } - break; - case TYPE_BOOLEAN: - if (!getChild("boolean_combo")->hasFocus()) - { - if (sd.asBoolean()) - { - getChild("boolean_combo")->setValue(LLSD("true")); - } - else - { - getChild("boolean_combo")->setValue(LLSD("")); - } - } - break; - case TYPE_STRING: - getChildView("val_text")->setVisible( true); - if (!getChild("val_text")->hasFocus()) - { - getChild("val_text")->setValue(sd); - } - break; - case TYPE_VEC3: - { - LLVector3 v; - v.setValue(sd); - spinner1->setVisible(true); - spinner1->setLabel(std::string("X")); - spinner2->setVisible(true); - spinner2->setLabel(std::string("Y")); - spinner3->setVisible(true); - spinner3->setLabel(std::string("Z")); - if (!spinner1->hasFocus()) - { - spinner1->setPrecision(3); - spinner1->setValue(v[VX]); - } - if (!spinner2->hasFocus()) - { - spinner2->setPrecision(3); - spinner2->setValue(v[VY]); - } - if (!spinner3->hasFocus()) - { - spinner3->setPrecision(3); - spinner3->setValue(v[VZ]); - } - break; - } - case TYPE_VEC3D: - { - LLVector3d v; - v.setValue(sd); - spinner1->setVisible(true); - spinner1->setLabel(std::string("X")); - spinner2->setVisible(true); - spinner2->setLabel(std::string("Y")); - spinner3->setVisible(true); - spinner3->setLabel(std::string("Z")); - if (!spinner1->hasFocus()) - { - spinner1->setPrecision(3); - spinner1->setValue(v[VX]); - } - if (!spinner2->hasFocus()) - { - spinner2->setPrecision(3); - spinner2->setValue(v[VY]); - } - if (!spinner3->hasFocus()) - { - spinner3->setPrecision(3); - spinner3->setValue(v[VZ]); - } - break; - } - case TYPE_QUAT: - { - LLQuaternion q; - q.setValue(sd); - spinner1->setVisible(true); - spinner1->setLabel(std::string("X")); - spinner2->setVisible(true); - spinner2->setLabel(std::string("Y")); - spinner3->setVisible(true); - spinner3->setLabel(std::string("Z")); - spinner4->setVisible(true); - spinner4->setLabel(std::string("S")); - if (!spinner1->hasFocus()) - { - spinner1->setPrecision(4); - spinner1->setValue(q.mQ[VX]); - } - if (!spinner2->hasFocus()) - { - spinner2->setPrecision(4); - spinner2->setValue(q.mQ[VY]); - } - if (!spinner3->hasFocus()) - { - spinner3->setPrecision(4); - spinner3->setValue(q.mQ[VZ]); - } - if (!spinner4->hasFocus()) - { - spinner4->setPrecision(4); - spinner4->setValue(q.mQ[VS]); - } - break; - } - case TYPE_RECT: - { - LLRect r; - r.setValue(sd); - spinner1->setVisible(true); - spinner1->setLabel(std::string("Left")); - spinner2->setVisible(true); - spinner2->setLabel(std::string("Right")); - spinner3->setVisible(true); - spinner3->setLabel(std::string("Bottom")); - spinner4->setVisible(true); - spinner4->setLabel(std::string("Top")); - if (!spinner1->hasFocus()) - { - spinner1->setPrecision(0); - spinner1->setValue(r.mLeft); - } - if (!spinner2->hasFocus()) - { - spinner2->setPrecision(0); - spinner2->setValue(r.mRight); - } - if (!spinner3->hasFocus()) - { - spinner3->setPrecision(0); - spinner3->setValue(r.mBottom); - } - if (!spinner4->hasFocus()) - { - spinner4->setPrecision(0); - spinner4->setValue(r.mTop); - } - - spinner1->setMinValue((F32)S32_MIN); - spinner1->setMaxValue((F32)S32_MAX); - spinner1->setIncrement(1.f); - - spinner2->setMinValue((F32)S32_MIN); - spinner2->setMaxValue((F32)S32_MAX); - spinner2->setIncrement(1.f); - - spinner3->setMinValue((F32)S32_MIN); - spinner3->setMaxValue((F32)S32_MAX); - spinner3->setIncrement(1.f); - - spinner4->setMinValue((F32)S32_MIN); - spinner4->setMaxValue((F32)S32_MAX); - spinner4->setIncrement(1.f); - break; - } - case TYPE_COL4: - { - LLColor4 clr; - clr.setValue(sd); - color_swatch->setVisible(true); - // only set if changed so color picker doesn't update - if(clr != LLColor4(color_swatch->getValue())) - { - color_swatch->set(LLColor4(sd), true, false); - } - spinner4->setVisible(true); - spinner4->setLabel(std::string("Alpha")); - if (!spinner4->hasFocus()) - { - spinner4->setPrecision(3); - spinner4->setMinValue(0.0); - spinner4->setMaxValue(1.f); - spinner4->setValue(clr.mV[VALPHA]); - } - break; - } - case TYPE_COL3: - { - LLColor3 clr; - clr.setValue(sd); - color_swatch->setVisible(true); - color_swatch->setValue(sd); - break; - } - default: - mComment->setText(std::string("unknown")); - break; - } - } - -} - -void LLFloaterSettingsDebug::updateList(bool skip_selection) -{ - std::string last_selected; - LLScrollListItem* item = mSettingList->getFirstSelected(); - if (item) - { - LLScrollListCell* cell = item->getColumn(1); - if (cell) - { - last_selected = cell->getValue().asString(); - } - } - - mSettingList->deleteAllItems(); - struct f : public LLControlGroup::ApplyFunctor - { - LLScrollListCtrl* setting_list; - LLFloaterSettingsDebug* floater; - std::string selected_setting; - bool skip_selection; - f(LLScrollListCtrl* list, LLFloaterSettingsDebug* floater, std::string setting, bool skip_selection) - : setting_list(list), floater(floater), selected_setting(setting), skip_selection(skip_selection) {} - virtual void apply(const std::string& name, LLControlVariable* control) - { - if (!control->isHiddenFromSettingsEditor() && floater->matchesSearchFilter(name) && !floater->isSettingHidden(control)) - { - LLSD row; - - row["columns"][0]["column"] = "changed_setting"; - row["columns"][0]["value"] = control->isDefault() ? "" : "*"; - - row["columns"][1]["column"] = "setting"; - row["columns"][1]["value"] = name; - - LLScrollListItem* item = setting_list->addElement(row, ADD_BOTTOM, (void*)control); - if (!floater->mSearchFilter.empty() && (selected_setting == name) && !skip_selection) - { - std::string lower_name(name); - LLStringUtil::toLower(lower_name); - if (LLStringUtil::startsWith(lower_name, floater->mSearchFilter)) - { - item->setSelected(true); - } - } - } - } - } func(mSettingList, this, last_selected, skip_selection); - - std::string key = getKey().asString(); - if (key == "all" || key == "base") - { - gSavedSettings.applyToAll(&func); - } - if (key == "all" || key == "account") - { - gSavedPerAccountSettings.applyToAll(&func); - } - - - if (!mSettingList->isEmpty()) - { - if (mSettingList->hasSelectedItem()) - { - mSettingList->scrollToShowSelected(); - } - else if (!mSettingList->hasSelectedItem() && !mSearchFilter.empty() && !skip_selection) - { - if (!mSettingList->selectItemByPrefix(mSearchFilter, false, 1)) - { - mSettingList->selectFirstItem(); - } - mSettingList->scrollToShowSelected(); - } - } - else - { - LLSD row; - - row["columns"][0]["column"] = "changed_setting"; - row["columns"][0]["value"] = ""; - row["columns"][1]["column"] = "setting"; - row["columns"][1]["value"] = "No matching settings."; - - mSettingList->addElement(row); - hideUIControls(); - } -} - -void LLFloaterSettingsDebug::onSettingSelect() -{ - LLScrollListItem* first_selected = mSettingList->getFirstSelected(); - if (first_selected) - { - LLControlVariable* controlp = (LLControlVariable*)first_selected->getUserdata(); - if (controlp) - { - updateControl(controlp); - } - } -} - -void LLFloaterSettingsDebug::setSearchFilter(const std::string& filter) -{ - if(mSearchFilter == filter) - return; - mSearchFilter = filter; - LLStringUtil::toLower(mSearchFilter); - updateList(); -} - -bool LLFloaterSettingsDebug::matchesSearchFilter(std::string setting_name) -{ - // If the search filter is empty, everything passes. - if (mSearchFilter.empty()) return true; - - LLStringUtil::toLower(setting_name); - std::string::size_type match_name = setting_name.find(mSearchFilter); - - return (std::string::npos != match_name); -} - -bool LLFloaterSettingsDebug::isSettingHidden(LLControlVariable* control) -{ - static LLCachedControl hide_default(gSavedSettings, "DebugSettingsHideDefault", false); - return hide_default && control->isDefault(); -} - -void LLFloaterSettingsDebug::updateDefaultColumn(LLControlVariable* control) -{ - if (isSettingHidden(control)) - { - hideUIControls(); - updateList(true); - return; - } - - LLScrollListItem* item = mSettingList->getFirstSelected(); - if (item) - { - LLScrollListCell* cell = item->getColumn(0); - if (cell) - { - std::string is_default = control->isDefault() ? "" : "*"; - cell->setValue(is_default); - } - } -} - -void LLFloaterSettingsDebug::hideUIControls() -{ - getChildView("val_spinner_1")->setVisible(false); - getChildView("val_spinner_2")->setVisible(false); - getChildView("val_spinner_3")->setVisible(false); - getChildView("val_spinner_4")->setVisible(false); - getChildView("val_color_swatch")->setVisible(false); - getChildView("val_text")->setVisible(false); - getChildView("default_btn")->setVisible(false); - getChildView("boolean_combo")->setVisible(false); - getChildView("setting_name_txt")->setVisible(false); - mComment->setVisible(false); -} - +/** + * @file llfloatersettingsdebug.cpp + * @brief floater for debugging internal viewer settings + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2022, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llfloatersettingsdebug.h" +#include "llfloater.h" +#include "llfiltereditor.h" +#include "lluictrlfactory.h" +#include "llcombobox.h" +#include "llspinctrl.h" +#include "llcolorswatch.h" +#include "llviewercontrol.h" +#include "lltexteditor.h" + + +LLFloaterSettingsDebug::LLFloaterSettingsDebug(const LLSD& key) +: LLFloater(key), + mSettingList(NULL) +{ + mCommitCallbackRegistrar.add("CommitSettings", boost::bind(&LLFloaterSettingsDebug::onCommitSettings, this)); + mCommitCallbackRegistrar.add("ClickDefault", boost::bind(&LLFloaterSettingsDebug::onClickDefault, this)); +} + +LLFloaterSettingsDebug::~LLFloaterSettingsDebug() +{} + +bool LLFloaterSettingsDebug::postBuild() +{ + enableResizeCtrls(true, false, true); + + mComment = getChild("comment_text"); + + getChild("filter_input")->setCommitCallback(boost::bind(&LLFloaterSettingsDebug::setSearchFilter, this, _2)); + + mSettingList = getChild("setting_list"); + mSettingList->setCommitOnSelectionChange(true); + mSettingList->setCommitCallback(boost::bind(&LLFloaterSettingsDebug::onSettingSelect, this)); + + updateList(); + + gSavedSettings.getControl("DebugSettingsHideDefault")->getCommitSignal()->connect(boost::bind(&LLFloaterSettingsDebug::updateList, this, false)); + + return true; +} + +void LLFloaterSettingsDebug::draw() +{ + LLScrollListItem* first_selected = mSettingList->getFirstSelected(); + if (first_selected) + { + LLControlVariable* controlp = (LLControlVariable*)first_selected->getUserdata(); + updateControl(controlp); + } + + LLFloater::draw(); +} + +void LLFloaterSettingsDebug::onCommitSettings() +{ + LLScrollListItem* first_selected = mSettingList->getFirstSelected(); + if (!first_selected) + { + return; + } + LLControlVariable* controlp = (LLControlVariable*)first_selected->getUserdata(); + + if (!controlp) + { + return; + } + + LLVector3 vector; + LLVector3d vectord; + LLQuaternion quat; + LLRect rect; + LLColor4 col4; + LLColor3 col3; + LLColor4U col4U; + LLColor4 color_with_alpha; + + switch(controlp->type()) + { + case TYPE_U32: + controlp->set(getChild("val_spinner_1")->getValue()); + break; + case TYPE_S32: + controlp->set(getChild("val_spinner_1")->getValue()); + break; + case TYPE_F32: + controlp->set(LLSD(getChild("val_spinner_1")->getValue().asReal())); + break; + case TYPE_BOOLEAN: + controlp->set(getChild("boolean_combo")->getValue()); + break; + case TYPE_STRING: + controlp->set(LLSD(getChild("val_text")->getValue().asString())); + break; + case TYPE_VEC3: + vector.mV[VX] = (F32)getChild("val_spinner_1")->getValue().asReal(); + vector.mV[VY] = (F32)getChild("val_spinner_2")->getValue().asReal(); + vector.mV[VZ] = (F32)getChild("val_spinner_3")->getValue().asReal(); + controlp->set(vector.getValue()); + break; + case TYPE_VEC3D: + vectord.mdV[VX] = getChild("val_spinner_1")->getValue().asReal(); + vectord.mdV[VY] = getChild("val_spinner_2")->getValue().asReal(); + vectord.mdV[VZ] = getChild("val_spinner_3")->getValue().asReal(); + controlp->set(vectord.getValue()); + break; + case TYPE_QUAT: + quat.mQ[VX] = getChild("val_spinner_1")->getValue().asReal(); + quat.mQ[VY] = getChild("val_spinner_2")->getValue().asReal(); + quat.mQ[VZ] = getChild("val_spinner_3")->getValue().asReal(); + quat.mQ[VS] = getChild("val_spinner_4")->getValue().asReal();; + controlp->set(quat.getValue()); + break; + case TYPE_RECT: + rect.mLeft = getChild("val_spinner_1")->getValue().asInteger(); + rect.mRight = getChild("val_spinner_2")->getValue().asInteger(); + rect.mBottom = getChild("val_spinner_3")->getValue().asInteger(); + rect.mTop = getChild("val_spinner_4")->getValue().asInteger(); + controlp->set(rect.getValue()); + break; + case TYPE_COL4: + col3.setValue(getChild("val_color_swatch")->getValue()); + col4 = LLColor4(col3, (F32)getChild("val_spinner_4")->getValue().asReal()); + controlp->set(col4.getValue()); + break; + case TYPE_COL3: + controlp->set(getChild("val_color_swatch")->getValue()); + //col3.mV[VRED] = (F32)floaterp->getChild("val_spinner_1")->getValue().asC(); + //col3.mV[VGREEN] = (F32)floaterp->getChild("val_spinner_2")->getValue().asReal(); + //col3.mV[VBLUE] = (F32)floaterp->getChild("val_spinner_3")->getValue().asReal(); + //controlp->set(col3.getValue()); + break; + default: + break; + } + updateDefaultColumn(controlp); +} + +// static +void LLFloaterSettingsDebug::onClickDefault() +{ + LLScrollListItem* first_selected = mSettingList->getFirstSelected(); + if (first_selected) + { + LLControlVariable* controlp = (LLControlVariable*)first_selected->getUserdata(); + if (controlp) + { + controlp->resetToDefault(true); + updateDefaultColumn(controlp); + updateControl(controlp); + } + } +} + +// we've switched controls, or doing per-frame update, so update spinners, etc. +void LLFloaterSettingsDebug::updateControl(LLControlVariable* controlp) +{ + LLSpinCtrl* spinner1 = getChild("val_spinner_1"); + LLSpinCtrl* spinner2 = getChild("val_spinner_2"); + LLSpinCtrl* spinner3 = getChild("val_spinner_3"); + LLSpinCtrl* spinner4 = getChild("val_spinner_4"); + LLColorSwatchCtrl* color_swatch = getChild("val_color_swatch"); + + if (!spinner1 || !spinner2 || !spinner3 || !spinner4 || !color_swatch) + { + LL_WARNS() << "Could not find all desired controls by name" + << LL_ENDL; + return; + } + + hideUIControls(); + + if (controlp && !isSettingHidden(controlp)) + { + eControlType type = controlp->type(); + + //hide combo box only for non booleans, otherwise this will result in the combo box closing every frame + getChildView("boolean_combo")->setVisible( type == TYPE_BOOLEAN); + getChildView("default_btn")->setVisible(true); + getChildView("setting_name_txt")->setVisible(true); + getChild("setting_name_txt")->setText(controlp->getName()); + getChild("setting_name_txt")->setToolTip(controlp->getName()); + mComment->setVisible(true); + + std::string old_text = mComment->getText(); + std::string new_text = controlp->getComment(); + // Don't setText if not nessesary, it will reset scroll + // This is a debug UI that reads from xml, there might + // be use cases where comment changes, but not the name + if (old_text != new_text) + { + mComment->setText(controlp->getComment()); + } + + spinner1->setMaxValue(F32_MAX); + spinner2->setMaxValue(F32_MAX); + spinner3->setMaxValue(F32_MAX); + spinner4->setMaxValue(F32_MAX); + spinner1->setMinValue(-F32_MAX); + spinner2->setMinValue(-F32_MAX); + spinner3->setMinValue(-F32_MAX); + spinner4->setMinValue(-F32_MAX); + if (!spinner1->hasFocus()) + { + spinner1->setIncrement(0.1f); + } + if (!spinner2->hasFocus()) + { + spinner2->setIncrement(0.1f); + } + if (!spinner3->hasFocus()) + { + spinner3->setIncrement(0.1f); + } + if (!spinner4->hasFocus()) + { + spinner4->setIncrement(0.1f); + } + + LLSD sd = controlp->get(); + switch(type) + { + case TYPE_U32: + spinner1->setVisible(true); + spinner1->setLabel(std::string("value")); // Debug, don't translate + if (!spinner1->hasFocus()) + { + spinner1->setValue(sd); + spinner1->setMinValue((F32)U32_MIN); + spinner1->setMaxValue((F32)U32_MAX); + spinner1->setIncrement(1.f); + spinner1->setPrecision(0); + } + break; + case TYPE_S32: + spinner1->setVisible(true); + spinner1->setLabel(std::string("value")); // Debug, don't translate + if (!spinner1->hasFocus()) + { + spinner1->setValue(sd); + spinner1->setMinValue((F32)S32_MIN); + spinner1->setMaxValue((F32)S32_MAX); + spinner1->setIncrement(1.f); + spinner1->setPrecision(0); + } + break; + case TYPE_F32: + spinner1->setVisible(true); + spinner1->setLabel(std::string("value")); // Debug, don't translate + if (!spinner1->hasFocus()) + { + spinner1->setPrecision(3); + spinner1->setValue(sd); + } + break; + case TYPE_BOOLEAN: + if (!getChild("boolean_combo")->hasFocus()) + { + if (sd.asBoolean()) + { + getChild("boolean_combo")->setValue(LLSD("true")); + } + else + { + getChild("boolean_combo")->setValue(LLSD("")); + } + } + break; + case TYPE_STRING: + getChildView("val_text")->setVisible( true); + if (!getChild("val_text")->hasFocus()) + { + getChild("val_text")->setValue(sd); + } + break; + case TYPE_VEC3: + { + LLVector3 v; + v.setValue(sd); + spinner1->setVisible(true); + spinner1->setLabel(std::string("X")); + spinner2->setVisible(true); + spinner2->setLabel(std::string("Y")); + spinner3->setVisible(true); + spinner3->setLabel(std::string("Z")); + if (!spinner1->hasFocus()) + { + spinner1->setPrecision(3); + spinner1->setValue(v[VX]); + } + if (!spinner2->hasFocus()) + { + spinner2->setPrecision(3); + spinner2->setValue(v[VY]); + } + if (!spinner3->hasFocus()) + { + spinner3->setPrecision(3); + spinner3->setValue(v[VZ]); + } + break; + } + case TYPE_VEC3D: + { + LLVector3d v; + v.setValue(sd); + spinner1->setVisible(true); + spinner1->setLabel(std::string("X")); + spinner2->setVisible(true); + spinner2->setLabel(std::string("Y")); + spinner3->setVisible(true); + spinner3->setLabel(std::string("Z")); + if (!spinner1->hasFocus()) + { + spinner1->setPrecision(3); + spinner1->setValue(v[VX]); + } + if (!spinner2->hasFocus()) + { + spinner2->setPrecision(3); + spinner2->setValue(v[VY]); + } + if (!spinner3->hasFocus()) + { + spinner3->setPrecision(3); + spinner3->setValue(v[VZ]); + } + break; + } + case TYPE_QUAT: + { + LLQuaternion q; + q.setValue(sd); + spinner1->setVisible(true); + spinner1->setLabel(std::string("X")); + spinner2->setVisible(true); + spinner2->setLabel(std::string("Y")); + spinner3->setVisible(true); + spinner3->setLabel(std::string("Z")); + spinner4->setVisible(true); + spinner4->setLabel(std::string("S")); + if (!spinner1->hasFocus()) + { + spinner1->setPrecision(4); + spinner1->setValue(q.mQ[VX]); + } + if (!spinner2->hasFocus()) + { + spinner2->setPrecision(4); + spinner2->setValue(q.mQ[VY]); + } + if (!spinner3->hasFocus()) + { + spinner3->setPrecision(4); + spinner3->setValue(q.mQ[VZ]); + } + if (!spinner4->hasFocus()) + { + spinner4->setPrecision(4); + spinner4->setValue(q.mQ[VS]); + } + break; + } + case TYPE_RECT: + { + LLRect r; + r.setValue(sd); + spinner1->setVisible(true); + spinner1->setLabel(std::string("Left")); + spinner2->setVisible(true); + spinner2->setLabel(std::string("Right")); + spinner3->setVisible(true); + spinner3->setLabel(std::string("Bottom")); + spinner4->setVisible(true); + spinner4->setLabel(std::string("Top")); + if (!spinner1->hasFocus()) + { + spinner1->setPrecision(0); + spinner1->setValue(r.mLeft); + } + if (!spinner2->hasFocus()) + { + spinner2->setPrecision(0); + spinner2->setValue(r.mRight); + } + if (!spinner3->hasFocus()) + { + spinner3->setPrecision(0); + spinner3->setValue(r.mBottom); + } + if (!spinner4->hasFocus()) + { + spinner4->setPrecision(0); + spinner4->setValue(r.mTop); + } + + spinner1->setMinValue((F32)S32_MIN); + spinner1->setMaxValue((F32)S32_MAX); + spinner1->setIncrement(1.f); + + spinner2->setMinValue((F32)S32_MIN); + spinner2->setMaxValue((F32)S32_MAX); + spinner2->setIncrement(1.f); + + spinner3->setMinValue((F32)S32_MIN); + spinner3->setMaxValue((F32)S32_MAX); + spinner3->setIncrement(1.f); + + spinner4->setMinValue((F32)S32_MIN); + spinner4->setMaxValue((F32)S32_MAX); + spinner4->setIncrement(1.f); + break; + } + case TYPE_COL4: + { + LLColor4 clr; + clr.setValue(sd); + color_swatch->setVisible(true); + // only set if changed so color picker doesn't update + if(clr != LLColor4(color_swatch->getValue())) + { + color_swatch->set(LLColor4(sd), true, false); + } + spinner4->setVisible(true); + spinner4->setLabel(std::string("Alpha")); + if (!spinner4->hasFocus()) + { + spinner4->setPrecision(3); + spinner4->setMinValue(0.0); + spinner4->setMaxValue(1.f); + spinner4->setValue(clr.mV[VALPHA]); + } + break; + } + case TYPE_COL3: + { + LLColor3 clr; + clr.setValue(sd); + color_swatch->setVisible(true); + color_swatch->setValue(sd); + break; + } + default: + mComment->setText(std::string("unknown")); + break; + } + } + +} + +void LLFloaterSettingsDebug::updateList(bool skip_selection) +{ + std::string last_selected; + LLScrollListItem* item = mSettingList->getFirstSelected(); + if (item) + { + LLScrollListCell* cell = item->getColumn(1); + if (cell) + { + last_selected = cell->getValue().asString(); + } + } + + mSettingList->deleteAllItems(); + struct f : public LLControlGroup::ApplyFunctor + { + LLScrollListCtrl* setting_list; + LLFloaterSettingsDebug* floater; + std::string selected_setting; + bool skip_selection; + f(LLScrollListCtrl* list, LLFloaterSettingsDebug* floater, std::string setting, bool skip_selection) + : setting_list(list), floater(floater), selected_setting(setting), skip_selection(skip_selection) {} + virtual void apply(const std::string& name, LLControlVariable* control) + { + if (!control->isHiddenFromSettingsEditor() && floater->matchesSearchFilter(name) && !floater->isSettingHidden(control)) + { + LLSD row; + + row["columns"][0]["column"] = "changed_setting"; + row["columns"][0]["value"] = control->isDefault() ? "" : "*"; + + row["columns"][1]["column"] = "setting"; + row["columns"][1]["value"] = name; + + LLScrollListItem* item = setting_list->addElement(row, ADD_BOTTOM, (void*)control); + if (!floater->mSearchFilter.empty() && (selected_setting == name) && !skip_selection) + { + std::string lower_name(name); + LLStringUtil::toLower(lower_name); + if (LLStringUtil::startsWith(lower_name, floater->mSearchFilter)) + { + item->setSelected(true); + } + } + } + } + } func(mSettingList, this, last_selected, skip_selection); + + std::string key = getKey().asString(); + if (key == "all" || key == "base") + { + gSavedSettings.applyToAll(&func); + } + if (key == "all" || key == "account") + { + gSavedPerAccountSettings.applyToAll(&func); + } + + + if (!mSettingList->isEmpty()) + { + if (mSettingList->hasSelectedItem()) + { + mSettingList->scrollToShowSelected(); + } + else if (!mSettingList->hasSelectedItem() && !mSearchFilter.empty() && !skip_selection) + { + if (!mSettingList->selectItemByPrefix(mSearchFilter, false, 1)) + { + mSettingList->selectFirstItem(); + } + mSettingList->scrollToShowSelected(); + } + } + else + { + LLSD row; + + row["columns"][0]["column"] = "changed_setting"; + row["columns"][0]["value"] = ""; + row["columns"][1]["column"] = "setting"; + row["columns"][1]["value"] = "No matching settings."; + + mSettingList->addElement(row); + hideUIControls(); + } +} + +void LLFloaterSettingsDebug::onSettingSelect() +{ + LLScrollListItem* first_selected = mSettingList->getFirstSelected(); + if (first_selected) + { + LLControlVariable* controlp = (LLControlVariable*)first_selected->getUserdata(); + if (controlp) + { + updateControl(controlp); + } + } +} + +void LLFloaterSettingsDebug::setSearchFilter(const std::string& filter) +{ + if(mSearchFilter == filter) + return; + mSearchFilter = filter; + LLStringUtil::toLower(mSearchFilter); + updateList(); +} + +bool LLFloaterSettingsDebug::matchesSearchFilter(std::string setting_name) +{ + // If the search filter is empty, everything passes. + if (mSearchFilter.empty()) return true; + + LLStringUtil::toLower(setting_name); + std::string::size_type match_name = setting_name.find(mSearchFilter); + + return (std::string::npos != match_name); +} + +bool LLFloaterSettingsDebug::isSettingHidden(LLControlVariable* control) +{ + static LLCachedControl hide_default(gSavedSettings, "DebugSettingsHideDefault", false); + return hide_default && control->isDefault(); +} + +void LLFloaterSettingsDebug::updateDefaultColumn(LLControlVariable* control) +{ + if (isSettingHidden(control)) + { + hideUIControls(); + updateList(true); + return; + } + + LLScrollListItem* item = mSettingList->getFirstSelected(); + if (item) + { + LLScrollListCell* cell = item->getColumn(0); + if (cell) + { + std::string is_default = control->isDefault() ? "" : "*"; + cell->setValue(is_default); + } + } +} + +void LLFloaterSettingsDebug::hideUIControls() +{ + getChildView("val_spinner_1")->setVisible(false); + getChildView("val_spinner_2")->setVisible(false); + getChildView("val_spinner_3")->setVisible(false); + getChildView("val_spinner_4")->setVisible(false); + getChildView("val_color_swatch")->setVisible(false); + getChildView("val_text")->setVisible(false); + getChildView("default_btn")->setVisible(false); + getChildView("boolean_combo")->setVisible(false); + getChildView("setting_name_txt")->setVisible(false); + mComment->setVisible(false); +} + diff --git a/indra/newview/llfloatersettingsdebug.h b/indra/newview/llfloatersettingsdebug.h index 0ce1596c07..4df0dc8dd2 100644 --- a/indra/newview/llfloatersettingsdebug.h +++ b/indra/newview/llfloatersettingsdebug.h @@ -1,75 +1,75 @@ -/** - * @file llfloatersettingsdebug.h - * @brief floater for debugging internal viewer settings - * - * $LicenseInfo:firstyear=2022&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2022, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLFLOATERDEBUGSETTINGS_H -#define LLFLOATERDEBUGSETTINGS_H - -#include "llcontrol.h" -#include "llfloater.h" - -class LLScrollListCtrl; - -class LLFloaterSettingsDebug -: public LLFloater -{ - friend class LLFloaterReg; - -public: - - virtual bool postBuild(); - virtual void draw(); - - void updateControl(LLControlVariable* control); - - void onCommitSettings(); - void onClickDefault(); - - bool matchesSearchFilter(std::string setting_name); - bool isSettingHidden(LLControlVariable* control); - -private: - // key - selects which settings to show, one of: - // "all", "base", "account", "skin" - LLFloaterSettingsDebug(const LLSD& key); - virtual ~LLFloaterSettingsDebug(); - - void updateList(bool skip_selection = false); - void onSettingSelect(); - void setSearchFilter(const std::string& filter); - - void updateDefaultColumn(LLControlVariable* control); - void hideUIControls(); - - LLScrollListCtrl* mSettingList; - -protected: - class LLTextEditor* mComment; - - std::string mSearchFilter; -}; - -#endif //LLFLOATERDEBUGSETTINGS_H - +/** + * @file llfloatersettingsdebug.h + * @brief floater for debugging internal viewer settings + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2022, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLFLOATERDEBUGSETTINGS_H +#define LLFLOATERDEBUGSETTINGS_H + +#include "llcontrol.h" +#include "llfloater.h" + +class LLScrollListCtrl; + +class LLFloaterSettingsDebug +: public LLFloater +{ + friend class LLFloaterReg; + +public: + + virtual bool postBuild(); + virtual void draw(); + + void updateControl(LLControlVariable* control); + + void onCommitSettings(); + void onClickDefault(); + + bool matchesSearchFilter(std::string setting_name); + bool isSettingHidden(LLControlVariable* control); + +private: + // key - selects which settings to show, one of: + // "all", "base", "account", "skin" + LLFloaterSettingsDebug(const LLSD& key); + virtual ~LLFloaterSettingsDebug(); + + void updateList(bool skip_selection = false); + void onSettingSelect(); + void setSearchFilter(const std::string& filter); + + void updateDefaultColumn(LLControlVariable* control); + void hideUIControls(); + + LLScrollListCtrl* mSettingList; + +protected: + class LLTextEditor* mComment; + + std::string mSearchFilter; +}; + +#endif //LLFLOATERDEBUGSETTINGS_H + diff --git a/indra/newview/llfloatersidepanelcontainer.cpp b/indra/newview/llfloatersidepanelcontainer.cpp index 8e4c853ec3..2f6d14d6b5 100644 --- a/indra/newview/llfloatersidepanelcontainer.cpp +++ b/indra/newview/llfloatersidepanelcontainer.cpp @@ -1,187 +1,187 @@ -/** - * @file llfloatersidepanelcontainer.cpp - * @brief LLFloaterSidePanelContainer class definition - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterreg.h" -#include "llfloatersidepanelcontainer.h" -#include "llpaneleditwearable.h" - -// newview includes -#include "llsidetraypanelcontainer.h" -#include "lltransientfloatermgr.h" -#include "llpaneloutfitedit.h" -#include "llsidepanelappearance.h" - -//static -const std::string LLFloaterSidePanelContainer::sMainPanelName("main_panel"); - -LLFloaterSidePanelContainer::LLFloaterSidePanelContainer(const LLSD& key, const Params& params) -: LLFloater(key, params) -{ - // Prevent transient floaters (e.g. IM windows) from hiding - // when this floater is clicked. - LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::GLOBAL, this); -} - -LLFloaterSidePanelContainer::~LLFloaterSidePanelContainer() -{ - LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::GLOBAL, this); -} - -void LLFloaterSidePanelContainer::onOpen(const LLSD& key) -{ - getChild(sMainPanelName)->onOpen(key); -} - -void LLFloaterSidePanelContainer::closeFloater(bool app_quitting) -{ - LLPanelOutfitEdit* panel_outfit_edit = - dynamic_cast(LLFloaterSidePanelContainer::findPanel("appearance", "panel_outfit_edit")); - if (panel_outfit_edit) - { - LLFloater *parent = gFloaterView->getParentFloater(panel_outfit_edit); - if (parent == this ) - { - LLSidepanelAppearance* panel_appearance = dynamic_cast(getPanel("appearance")); - if (panel_appearance) - { - LLPanelEditWearable *edit_wearable_ptr = panel_appearance->getWearable(); - if (edit_wearable_ptr) - { - edit_wearable_ptr->onClose(); - } - if (!app_quitting) - { - panel_appearance->showOutfitsInventoryPanel(); - } - } - } - } - - LLFloater::closeFloater(app_quitting); - - if (getInstanceName() == "inventory" && !getKey().isUndefined()) - { - destroy(); - } -} - -LLFloater* LLFloaterSidePanelContainer::getTopmostInventoryFloater() -{ - LLFloater* topmost_floater = NULL; - S32 z_min = S32_MAX; - - LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("inventory"); - for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); iter != inst_list.end(); ++iter) - { - LLFloater* inventory_floater = (*iter); - - if (inventory_floater && inventory_floater->getVisible()) - { - S32 z_order = gFloaterView->getZOrder(inventory_floater); - if (z_order < z_min) - { - z_min = z_order; - topmost_floater = inventory_floater; - } - } - } - return topmost_floater; -} - -LLPanel* LLFloaterSidePanelContainer::openChildPanel(const std::string& panel_name, const LLSD& params) -{ - LLView* view = findChildView(panel_name, true); - if (!view) - return NULL; - - if (!getVisible()) - { - openFloater(); - } - else if (!hasFocus()) - { - setFocus(true); - } - - LLPanel* panel = NULL; - - LLSideTrayPanelContainer* container = dynamic_cast(view->getParent()); - if (container) - { - container->openPanel(panel_name, params); - panel = container->getCurrentPanel(); - } - else if ((panel = dynamic_cast(view)) != NULL) - { - panel->onOpen(params); - } - - return panel; -} - -void LLFloaterSidePanelContainer::showPanel(const std::string& floater_name, const LLSD& key) -{ - LLFloaterSidePanelContainer* floaterp = LLFloaterReg::getTypedInstance(floater_name); - if (floaterp) - { - floaterp->openChildPanel(sMainPanelName, key); - } -} - -void LLFloaterSidePanelContainer::showPanel(const std::string& floater_name, const std::string& panel_name, const LLSD& key) -{ - LLFloaterSidePanelContainer* floaterp = LLFloaterReg::getTypedInstance(floater_name); - if (floaterp) - { - floaterp->openChildPanel(panel_name, key); - } -} - -LLPanel* LLFloaterSidePanelContainer::getPanel(const std::string& floater_name, const std::string& panel_name) -{ - LLFloaterSidePanelContainer* floaterp = LLFloaterReg::getTypedInstance(floater_name); - - if (floaterp) - { - return floaterp->findChild(panel_name, true); - } - - return NULL; -} - -LLPanel* LLFloaterSidePanelContainer::findPanel(const std::string& floater_name, const std::string& panel_name) -{ - LLFloaterSidePanelContainer* floaterp = LLFloaterReg::findTypedInstance(floater_name); - - if (floaterp) - { - return floaterp->findChild(panel_name, true); - } - - return NULL; -} +/** + * @file llfloatersidepanelcontainer.cpp + * @brief LLFloaterSidePanelContainer class definition + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterreg.h" +#include "llfloatersidepanelcontainer.h" +#include "llpaneleditwearable.h" + +// newview includes +#include "llsidetraypanelcontainer.h" +#include "lltransientfloatermgr.h" +#include "llpaneloutfitedit.h" +#include "llsidepanelappearance.h" + +//static +const std::string LLFloaterSidePanelContainer::sMainPanelName("main_panel"); + +LLFloaterSidePanelContainer::LLFloaterSidePanelContainer(const LLSD& key, const Params& params) +: LLFloater(key, params) +{ + // Prevent transient floaters (e.g. IM windows) from hiding + // when this floater is clicked. + LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::GLOBAL, this); +} + +LLFloaterSidePanelContainer::~LLFloaterSidePanelContainer() +{ + LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::GLOBAL, this); +} + +void LLFloaterSidePanelContainer::onOpen(const LLSD& key) +{ + getChild(sMainPanelName)->onOpen(key); +} + +void LLFloaterSidePanelContainer::closeFloater(bool app_quitting) +{ + LLPanelOutfitEdit* panel_outfit_edit = + dynamic_cast(LLFloaterSidePanelContainer::findPanel("appearance", "panel_outfit_edit")); + if (panel_outfit_edit) + { + LLFloater *parent = gFloaterView->getParentFloater(panel_outfit_edit); + if (parent == this ) + { + LLSidepanelAppearance* panel_appearance = dynamic_cast(getPanel("appearance")); + if (panel_appearance) + { + LLPanelEditWearable *edit_wearable_ptr = panel_appearance->getWearable(); + if (edit_wearable_ptr) + { + edit_wearable_ptr->onClose(); + } + if (!app_quitting) + { + panel_appearance->showOutfitsInventoryPanel(); + } + } + } + } + + LLFloater::closeFloater(app_quitting); + + if (getInstanceName() == "inventory" && !getKey().isUndefined()) + { + destroy(); + } +} + +LLFloater* LLFloaterSidePanelContainer::getTopmostInventoryFloater() +{ + LLFloater* topmost_floater = NULL; + S32 z_min = S32_MAX; + + LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("inventory"); + for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); iter != inst_list.end(); ++iter) + { + LLFloater* inventory_floater = (*iter); + + if (inventory_floater && inventory_floater->getVisible()) + { + S32 z_order = gFloaterView->getZOrder(inventory_floater); + if (z_order < z_min) + { + z_min = z_order; + topmost_floater = inventory_floater; + } + } + } + return topmost_floater; +} + +LLPanel* LLFloaterSidePanelContainer::openChildPanel(const std::string& panel_name, const LLSD& params) +{ + LLView* view = findChildView(panel_name, true); + if (!view) + return NULL; + + if (!getVisible()) + { + openFloater(); + } + else if (!hasFocus()) + { + setFocus(true); + } + + LLPanel* panel = NULL; + + LLSideTrayPanelContainer* container = dynamic_cast(view->getParent()); + if (container) + { + container->openPanel(panel_name, params); + panel = container->getCurrentPanel(); + } + else if ((panel = dynamic_cast(view)) != NULL) + { + panel->onOpen(params); + } + + return panel; +} + +void LLFloaterSidePanelContainer::showPanel(const std::string& floater_name, const LLSD& key) +{ + LLFloaterSidePanelContainer* floaterp = LLFloaterReg::getTypedInstance(floater_name); + if (floaterp) + { + floaterp->openChildPanel(sMainPanelName, key); + } +} + +void LLFloaterSidePanelContainer::showPanel(const std::string& floater_name, const std::string& panel_name, const LLSD& key) +{ + LLFloaterSidePanelContainer* floaterp = LLFloaterReg::getTypedInstance(floater_name); + if (floaterp) + { + floaterp->openChildPanel(panel_name, key); + } +} + +LLPanel* LLFloaterSidePanelContainer::getPanel(const std::string& floater_name, const std::string& panel_name) +{ + LLFloaterSidePanelContainer* floaterp = LLFloaterReg::getTypedInstance(floater_name); + + if (floaterp) + { + return floaterp->findChild(panel_name, true); + } + + return NULL; +} + +LLPanel* LLFloaterSidePanelContainer::findPanel(const std::string& floater_name, const std::string& panel_name) +{ + LLFloaterSidePanelContainer* floaterp = LLFloaterReg::findTypedInstance(floater_name); + + if (floaterp) + { + return floaterp->findChild(panel_name, true); + } + + return NULL; +} diff --git a/indra/newview/llfloatersimplesnapshot.cpp b/indra/newview/llfloatersimplesnapshot.cpp index f9be57361f..c0de8ab811 100644 --- a/indra/newview/llfloatersimplesnapshot.cpp +++ b/indra/newview/llfloatersimplesnapshot.cpp @@ -1,522 +1,522 @@ -/** -* @file llfloatersimplesnapshot.cpp -* @brief Snapshot preview window for saving as a thumbnail -* -* $LicenseInfo:firstyear=2022&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2022, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#include "llviewerprecompiledheaders.h" - -#include "llfloatersimplesnapshot.h" - -#include "llfloaterreg.h" -#include "llimagefiltersmanager.h" -#include "llinventorymodel.h" -#include "llinventoryobserver.h" -#include "llstatusbar.h" // can_afford_transaction() -#include "llnotificationsutil.h" -#include "llagent.h" -#include "llagentbenefits.h" -#include "llviewercontrol.h" -#include "llviewertexturelist.h" - - - -LLSimpleSnapshotFloaterView* gSimpleSnapshotFloaterView = NULL; - -const S32 LLFloaterSimpleSnapshot::THUMBNAIL_SNAPSHOT_DIM_MAX = 256; -const S32 LLFloaterSimpleSnapshot::THUMBNAIL_SNAPSHOT_DIM_MIN = 64; - -// Thumbnail posting coro - -static const std::string THUMBNAIL_UPLOAD_CAP = "InventoryThumbnailUpload"; - -void post_thumbnail_image_coro(std::string cap_url, std::string path_to_image, LLSD first_data, LLFloaterSimpleSnapshot::completion_t callback) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("post_profile_image_coro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpHeaders::ptr_t httpHeaders; - - LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); - httpOpts->setFollowRedirects(true); - - LLSD result = httpAdapter->postAndSuspend(httpRequest, cap_url, first_data, httpOpts, httpHeaders); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - // todo: notification? - LL_WARNS("AvatarProperties") << "Failed to get uploader cap " << status.toString() << LL_ENDL; - return; - } - if (!result.has("uploader")) - { - // todo: notification? - LL_WARNS("AvatarProperties") << "Failed to get uploader cap, response contains no data." << LL_ENDL; - return; - } - std::string uploader_cap = result["uploader"].asString(); - if (uploader_cap.empty()) - { - LL_WARNS("AvatarProperties") << "Failed to get uploader cap, cap invalid." << LL_ENDL; - return; - } - - // Upload the image - - LLCore::HttpRequest::ptr_t uploaderhttpRequest(new LLCore::HttpRequest); - LLCore::HttpHeaders::ptr_t uploaderhttpHeaders(new LLCore::HttpHeaders); - LLCore::HttpOptions::ptr_t uploaderhttpOpts(new LLCore::HttpOptions); - S64 length; - - { - llifstream instream(path_to_image.c_str(), std::iostream::binary | std::iostream::ate); - if (!instream.is_open()) - { - LL_WARNS("AvatarProperties") << "Failed to open file " << path_to_image << LL_ENDL; - return; - } - length = instream.tellg(); - } - - uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/jp2"); // optional - uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_LENGTH, llformat("%d", length)); // required! - uploaderhttpOpts->setFollowRedirects(true); - - result = httpAdapter->postFileAndSuspend(uploaderhttpRequest, uploader_cap, path_to_image, uploaderhttpOpts, uploaderhttpHeaders); - - httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - LL_DEBUGS("Thumbnail") << result << LL_ENDL; - - if (!status) - { - LL_WARNS("Thumbnail") << "Failed to upload image " << status.toString() << LL_ENDL; - return; - } - - if (result["state"].asString() != "complete") - { - if (result.has("message")) - { - LL_WARNS("Thumbnail") << "Failed to upload image, state " << result["state"] << " message: " << result["message"] << LL_ENDL; - } - else - { - LL_WARNS("Thumbnail") << "Failed to upload image " << result << LL_ENDL; - } - - if (callback) - { - callback(LLUUID()); - } - return; - } - - if (first_data.has("category_id")) - { - LLUUID cat_id = first_data["category_id"].asUUID(); - LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); - if (cat) - { - cat->setThumbnailUUID(result["new_asset"].asUUID()); - } - gInventory.addChangedMask(LLInventoryObserver::INTERNAL, cat_id); - } - if (first_data.has("item_id")) - { - LLUUID item_id = first_data["item_id"].asUUID(); - LLViewerInventoryItem* item = gInventory.getItem(item_id); - if (item) - { - item->setThumbnailUUID(result["new_asset"].asUUID()); - } - // Are we supposed to get BulkUpdateInventory? - gInventory.addChangedMask(LLInventoryObserver::INTERNAL, item_id); - } - - if (callback) - { - callback(result["new_asset"].asUUID()); - } -} - -///---------------------------------------------------------------------------- -/// Class LLFloaterSimpleSnapshot::Impl -///---------------------------------------------------------------------------- - -LLSnapshotModel::ESnapshotFormat LLFloaterSimpleSnapshot::Impl::getImageFormat(LLFloaterSnapshotBase* floater) -{ - return LLSnapshotModel::SNAPSHOT_FORMAT_PNG; -} - -LLSnapshotModel::ESnapshotLayerType LLFloaterSimpleSnapshot::Impl::getLayerType(LLFloaterSnapshotBase* floater) -{ - return LLSnapshotModel::SNAPSHOT_TYPE_COLOR; -} - -void LLFloaterSimpleSnapshot::Impl::updateControls(LLFloaterSnapshotBase* floater) -{ - LLSnapshotLivePreview* previewp = getPreviewView(); - updateResolution(floater); - if (previewp) - { - previewp->setSnapshotType(LLSnapshotModel::ESnapshotType::SNAPSHOT_TEXTURE); - previewp->setSnapshotFormat(LLSnapshotModel::ESnapshotFormat::SNAPSHOT_FORMAT_PNG); - previewp->setSnapshotBufferType(LLSnapshotModel::ESnapshotLayerType::SNAPSHOT_TYPE_COLOR); - } -} - -std::string LLFloaterSimpleSnapshot::Impl::getSnapshotPanelPrefix() -{ - return "panel_outfit_snapshot_"; -} - -void LLFloaterSimpleSnapshot::Impl::updateResolution(void* data) -{ - LLFloaterSimpleSnapshot *view = (LLFloaterSimpleSnapshot *)data; - - if (!view) - { - llassert(view); - return; - } - - S32 width = THUMBNAIL_SNAPSHOT_DIM_MAX; - S32 height = THUMBNAIL_SNAPSHOT_DIM_MAX; - - LLSnapshotLivePreview* previewp = getPreviewView(); - if (previewp) - { - S32 original_width = 0, original_height = 0; - previewp->getSize(original_width, original_height); - - if (gSavedSettings.getBOOL("RenderHUDInSnapshot")) - { //clamp snapshot resolution to window size when showing UI HUD in snapshot - width = llmin(width, gViewerWindow->getWindowWidthRaw()); - height = llmin(height, gViewerWindow->getWindowHeightRaw()); - } - - llassert(width > 0 && height > 0); - - previewp->setSize(width, height); - - if (original_width != width || original_height != height) - { - // hide old preview as the aspect ratio could be wrong - checkAutoSnapshot(previewp, false); - previewp->updateSnapshot(true); - } - } -} - -void LLFloaterSimpleSnapshot::Impl::setStatus(EStatus status, bool ok, const std::string& msg) -{ - switch (status) - { - case STATUS_READY: - mFloater->setCtrlsEnabled(true); - break; - case STATUS_WORKING: - mFloater->setCtrlsEnabled(false); - break; - case STATUS_FINISHED: - mFloater->setCtrlsEnabled(true); - break; - } - - mStatus = status; -} - -///----------------------------------------------------------------re------------ -/// Class LLFloaterSimpleSnapshot -///---------------------------------------------------------------------------- - -LLFloaterSimpleSnapshot::LLFloaterSimpleSnapshot(const LLSD& key) - : LLFloaterSnapshotBase(key) - , mOwner(NULL) - , mContextConeOpacity(0.f) -{ - impl = new Impl(this); -} - -LLFloaterSimpleSnapshot::~LLFloaterSimpleSnapshot() -{ -} - -bool LLFloaterSimpleSnapshot::postBuild() -{ - childSetAction("new_snapshot_btn", ImplBase::onClickNewSnapshot, this); - childSetAction("save_btn", boost::bind(&LLFloaterSimpleSnapshot::onSend, this)); - childSetAction("cancel_btn", boost::bind(&LLFloaterSimpleSnapshot::onCancel, this)); - - mThumbnailPlaceholder = getChild("thumbnail_placeholder"); - - // create preview window - LLRect full_screen_rect = getRootView()->getRect(); - LLSnapshotLivePreview::Params p; - p.rect(full_screen_rect); - LLSnapshotLivePreview* previewp = new LLSnapshotLivePreview(p); - - // Do not move LLFloaterSimpleSnapshot floater into gSnapshotFloaterView - // since it can be a dependednt floater and does not draw UI - - impl->mPreviewHandle = previewp->getHandle(); - previewp->setContainer(this); - impl->updateControls(this); - impl->setAdvanced(true); - impl->setSkipReshaping(true); - - previewp->mKeepAspectRatio = false; - previewp->setThumbnailPlaceholderRect(getThumbnailPlaceholderRect()); - previewp->setAllowRenderUI(false); - previewp->setThumbnailSubsampled(true); - - return true; -} - -constexpr S32 PREVIEW_OFFSET_Y = 70; - -void LLFloaterSimpleSnapshot::draw() -{ - if (mOwner) - { - static LLCachedControl max_opacity(gSavedSettings, "PickerContextOpacity", 0.4f); - drawConeToOwner(mContextConeOpacity, max_opacity, mOwner); - } - - LLSnapshotLivePreview* previewp = getPreviewView(); - - if (previewp && (previewp->isSnapshotActive() || previewp->getThumbnailLock())) - { - // don't render snapshot window in snapshot, even if "show ui" is turned on - return; - } - - LLFloater::draw(); - - if (previewp && !isMinimized() && mThumbnailPlaceholder->getVisible()) - { - if(previewp->getThumbnailImage()) - { - bool working = impl->getStatus() == ImplBase::STATUS_WORKING; - const S32 thumbnail_w = previewp->getThumbnailWidth(); - const S32 thumbnail_h = previewp->getThumbnailHeight(); - - LLRect local_rect = getLocalRect(); - S32 offset_x = (local_rect.getWidth() - thumbnail_w) / 2; - S32 offset_y = PREVIEW_OFFSET_Y; - - gGL.matrixMode(LLRender::MM_MODELVIEW); - // Apply floater transparency to the texture unless the floater is focused. - F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); - LLColor4 color = working ? LLColor4::grey4 : LLColor4::white; - gl_draw_scaled_image(offset_x, offset_y, - thumbnail_w, thumbnail_h, - previewp->getThumbnailImage(), color % alpha); - } - } - impl->updateLayout(this); -} - -void LLFloaterSimpleSnapshot::onOpen(const LLSD& key) -{ - LLSnapshotLivePreview* preview = getPreviewView(); - if (preview) - { - preview->updateSnapshot(true); - } - focusFirstItem(false); - gSnapshotFloaterView->setEnabled(true); - gSnapshotFloaterView->setVisible(true); - gSnapshotFloaterView->adjustToFitScreen(this, false); - - impl->updateControls(this); - impl->setStatus(ImplBase::STATUS_READY); - - mInventoryId = key["item_id"].asUUID(); - mTaskId = key["task_id"].asUUID(); -} - -void LLFloaterSimpleSnapshot::onCancel() -{ - closeFloater(); -} - -void LLFloaterSimpleSnapshot::onSend() -{ - LLSnapshotLivePreview* previewp = getPreviewView(); - - std::string temp_file = gDirUtilp->getTempFilename(); - if (previewp->createUploadFile(temp_file, THUMBNAIL_SNAPSHOT_DIM_MAX, THUMBNAIL_SNAPSHOT_DIM_MIN)) - { - uploadImageUploadFile(temp_file, mInventoryId, mTaskId, mUploadCompletionCallback); - closeFloater(); - } - else - { - LLSD notif_args; - notif_args["REASON"] = LLImage::getLastThreadError().c_str(); - LLNotificationsUtil::add("CannotUploadTexture", notif_args); - if (mUploadCompletionCallback) - { - mUploadCompletionCallback(LLUUID::null); - } - } -} - -void LLFloaterSimpleSnapshot::postSave() -{ - impl->setStatus(ImplBase::STATUS_WORKING); -} - -// static -void LLFloaterSimpleSnapshot::uploadThumbnail(const std::string &file_path, - const LLUUID &inventory_id, - const LLUUID &task_id, - completion_t callback) -{ - // generate a temp texture file for coroutine - std::string temp_file = gDirUtilp->getTempFilename(); - U32 codec = LLImageBase::getCodecFromExtension(gDirUtilp->getExtension(file_path)); - if (!LLViewerTextureList::createUploadFile(file_path, temp_file, codec, THUMBNAIL_SNAPSHOT_DIM_MAX, THUMBNAIL_SNAPSHOT_DIM_MIN, true)) - { - LLSD notif_args; - notif_args["REASON"] = LLImage::getLastThreadError().c_str(); - LLNotificationsUtil::add("CannotUploadTexture", notif_args); - LL_WARNS("Thumbnail") << "Failed to upload thumbnail for " << inventory_id << " " << task_id << ", reason: " << notif_args["REASON"].asString() << LL_ENDL; - return; - } - uploadImageUploadFile(temp_file, inventory_id, task_id, callback); -} - -// static -void LLFloaterSimpleSnapshot::uploadThumbnail(LLPointer raw_image, - const LLUUID& inventory_id, - const LLUUID& task_id, - completion_t callback) -{ - std::string temp_file = gDirUtilp->getTempFilename(); - if (!LLViewerTextureList::createUploadFile(raw_image, temp_file, THUMBNAIL_SNAPSHOT_DIM_MAX, THUMBNAIL_SNAPSHOT_DIM_MIN)) - { - LLSD notif_args; - notif_args["REASON"] = LLImage::getLastThreadError().c_str(); - LLNotificationsUtil::add("CannotUploadTexture", notif_args); - LL_WARNS("Thumbnail") << "Failed to upload thumbnail for " << inventory_id << " " << task_id << ", reason: " << notif_args["REASON"].asString() << LL_ENDL; - return; - } - uploadImageUploadFile(temp_file, inventory_id, task_id, callback); -} - -// static -void LLFloaterSimpleSnapshot::uploadImageUploadFile(const std::string &temp_file, - const LLUUID &inventory_id, - const LLUUID &task_id, - completion_t callback) -{ - LLSD data; - - if (task_id.notNull()) - { - data["item_id"] = inventory_id; - data["task_id"] = task_id; - } - else if (gInventory.getCategory(inventory_id)) - { - data["category_id"] = inventory_id; - } - else - { - data["item_id"] = inventory_id; - } - - std::string cap_url = gAgent.getRegionCapability(THUMBNAIL_UPLOAD_CAP); - if (cap_url.empty()) - { - LLSD args; - args["CAPABILITY"] = THUMBNAIL_UPLOAD_CAP; - LLNotificationsUtil::add("RegionCapabilityRequestError", args); - LL_WARNS("Thumbnail") << "Failed to upload profile image for item " << inventory_id << " " << task_id << ", no cap found" << LL_ENDL; - return; - } - - LLCoros::instance().launch("postAgentUserImageCoro", - boost::bind(post_thumbnail_image_coro, cap_url, temp_file, data, callback)); -} - -void LLFloaterSimpleSnapshot::update() -{ - // initializes snapshots when needed - LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("simple_snapshot"); - for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); - iter != inst_list.end(); ++iter) - { - LLFloaterSimpleSnapshot* floater = dynamic_cast(*iter); - if (floater) - { - floater->impl->updateLivePreview(); - } - } -} - - -// static -LLFloaterSimpleSnapshot* LLFloaterSimpleSnapshot::findInstance(const LLSD &key) -{ - return LLFloaterReg::findTypedInstance("simple_snapshot", key); -} - -// static -LLFloaterSimpleSnapshot* LLFloaterSimpleSnapshot::getInstance(const LLSD &key) -{ - return LLFloaterReg::getTypedInstance("simple_snapshot", key); -} - -void LLFloaterSimpleSnapshot::saveTexture() -{ - LLSnapshotLivePreview* previewp = getPreviewView(); - if (!previewp) - { - llassert(previewp != NULL); - return; - } - - previewp->saveTexture(true, getInventoryId().asString()); - closeFloater(); -} - -///---------------------------------------------------------------------------- -/// Class LLSimpleOutfitSnapshotFloaterView -///---------------------------------------------------------------------------- - -LLSimpleSnapshotFloaterView::LLSimpleSnapshotFloaterView(const Params& p) : LLFloaterView(p) -{ -} - -LLSimpleSnapshotFloaterView::~LLSimpleSnapshotFloaterView() -{ -} +/** +* @file llfloatersimplesnapshot.cpp +* @brief Snapshot preview window for saving as a thumbnail +* +* $LicenseInfo:firstyear=2022&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2022, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatersimplesnapshot.h" + +#include "llfloaterreg.h" +#include "llimagefiltersmanager.h" +#include "llinventorymodel.h" +#include "llinventoryobserver.h" +#include "llstatusbar.h" // can_afford_transaction() +#include "llnotificationsutil.h" +#include "llagent.h" +#include "llagentbenefits.h" +#include "llviewercontrol.h" +#include "llviewertexturelist.h" + + + +LLSimpleSnapshotFloaterView* gSimpleSnapshotFloaterView = NULL; + +const S32 LLFloaterSimpleSnapshot::THUMBNAIL_SNAPSHOT_DIM_MAX = 256; +const S32 LLFloaterSimpleSnapshot::THUMBNAIL_SNAPSHOT_DIM_MIN = 64; + +// Thumbnail posting coro + +static const std::string THUMBNAIL_UPLOAD_CAP = "InventoryThumbnailUpload"; + +void post_thumbnail_image_coro(std::string cap_url, std::string path_to_image, LLSD first_data, LLFloaterSimpleSnapshot::completion_t callback) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("post_profile_image_coro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpHeaders::ptr_t httpHeaders; + + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + httpOpts->setFollowRedirects(true); + + LLSD result = httpAdapter->postAndSuspend(httpRequest, cap_url, first_data, httpOpts, httpHeaders); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + // todo: notification? + LL_WARNS("AvatarProperties") << "Failed to get uploader cap " << status.toString() << LL_ENDL; + return; + } + if (!result.has("uploader")) + { + // todo: notification? + LL_WARNS("AvatarProperties") << "Failed to get uploader cap, response contains no data." << LL_ENDL; + return; + } + std::string uploader_cap = result["uploader"].asString(); + if (uploader_cap.empty()) + { + LL_WARNS("AvatarProperties") << "Failed to get uploader cap, cap invalid." << LL_ENDL; + return; + } + + // Upload the image + + LLCore::HttpRequest::ptr_t uploaderhttpRequest(new LLCore::HttpRequest); + LLCore::HttpHeaders::ptr_t uploaderhttpHeaders(new LLCore::HttpHeaders); + LLCore::HttpOptions::ptr_t uploaderhttpOpts(new LLCore::HttpOptions); + S64 length; + + { + llifstream instream(path_to_image.c_str(), std::iostream::binary | std::iostream::ate); + if (!instream.is_open()) + { + LL_WARNS("AvatarProperties") << "Failed to open file " << path_to_image << LL_ENDL; + return; + } + length = instream.tellg(); + } + + uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/jp2"); // optional + uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_LENGTH, llformat("%d", length)); // required! + uploaderhttpOpts->setFollowRedirects(true); + + result = httpAdapter->postFileAndSuspend(uploaderhttpRequest, uploader_cap, path_to_image, uploaderhttpOpts, uploaderhttpHeaders); + + httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + LL_DEBUGS("Thumbnail") << result << LL_ENDL; + + if (!status) + { + LL_WARNS("Thumbnail") << "Failed to upload image " << status.toString() << LL_ENDL; + return; + } + + if (result["state"].asString() != "complete") + { + if (result.has("message")) + { + LL_WARNS("Thumbnail") << "Failed to upload image, state " << result["state"] << " message: " << result["message"] << LL_ENDL; + } + else + { + LL_WARNS("Thumbnail") << "Failed to upload image " << result << LL_ENDL; + } + + if (callback) + { + callback(LLUUID()); + } + return; + } + + if (first_data.has("category_id")) + { + LLUUID cat_id = first_data["category_id"].asUUID(); + LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); + if (cat) + { + cat->setThumbnailUUID(result["new_asset"].asUUID()); + } + gInventory.addChangedMask(LLInventoryObserver::INTERNAL, cat_id); + } + if (first_data.has("item_id")) + { + LLUUID item_id = first_data["item_id"].asUUID(); + LLViewerInventoryItem* item = gInventory.getItem(item_id); + if (item) + { + item->setThumbnailUUID(result["new_asset"].asUUID()); + } + // Are we supposed to get BulkUpdateInventory? + gInventory.addChangedMask(LLInventoryObserver::INTERNAL, item_id); + } + + if (callback) + { + callback(result["new_asset"].asUUID()); + } +} + +///---------------------------------------------------------------------------- +/// Class LLFloaterSimpleSnapshot::Impl +///---------------------------------------------------------------------------- + +LLSnapshotModel::ESnapshotFormat LLFloaterSimpleSnapshot::Impl::getImageFormat(LLFloaterSnapshotBase* floater) +{ + return LLSnapshotModel::SNAPSHOT_FORMAT_PNG; +} + +LLSnapshotModel::ESnapshotLayerType LLFloaterSimpleSnapshot::Impl::getLayerType(LLFloaterSnapshotBase* floater) +{ + return LLSnapshotModel::SNAPSHOT_TYPE_COLOR; +} + +void LLFloaterSimpleSnapshot::Impl::updateControls(LLFloaterSnapshotBase* floater) +{ + LLSnapshotLivePreview* previewp = getPreviewView(); + updateResolution(floater); + if (previewp) + { + previewp->setSnapshotType(LLSnapshotModel::ESnapshotType::SNAPSHOT_TEXTURE); + previewp->setSnapshotFormat(LLSnapshotModel::ESnapshotFormat::SNAPSHOT_FORMAT_PNG); + previewp->setSnapshotBufferType(LLSnapshotModel::ESnapshotLayerType::SNAPSHOT_TYPE_COLOR); + } +} + +std::string LLFloaterSimpleSnapshot::Impl::getSnapshotPanelPrefix() +{ + return "panel_outfit_snapshot_"; +} + +void LLFloaterSimpleSnapshot::Impl::updateResolution(void* data) +{ + LLFloaterSimpleSnapshot *view = (LLFloaterSimpleSnapshot *)data; + + if (!view) + { + llassert(view); + return; + } + + S32 width = THUMBNAIL_SNAPSHOT_DIM_MAX; + S32 height = THUMBNAIL_SNAPSHOT_DIM_MAX; + + LLSnapshotLivePreview* previewp = getPreviewView(); + if (previewp) + { + S32 original_width = 0, original_height = 0; + previewp->getSize(original_width, original_height); + + if (gSavedSettings.getBOOL("RenderHUDInSnapshot")) + { //clamp snapshot resolution to window size when showing UI HUD in snapshot + width = llmin(width, gViewerWindow->getWindowWidthRaw()); + height = llmin(height, gViewerWindow->getWindowHeightRaw()); + } + + llassert(width > 0 && height > 0); + + previewp->setSize(width, height); + + if (original_width != width || original_height != height) + { + // hide old preview as the aspect ratio could be wrong + checkAutoSnapshot(previewp, false); + previewp->updateSnapshot(true); + } + } +} + +void LLFloaterSimpleSnapshot::Impl::setStatus(EStatus status, bool ok, const std::string& msg) +{ + switch (status) + { + case STATUS_READY: + mFloater->setCtrlsEnabled(true); + break; + case STATUS_WORKING: + mFloater->setCtrlsEnabled(false); + break; + case STATUS_FINISHED: + mFloater->setCtrlsEnabled(true); + break; + } + + mStatus = status; +} + +///----------------------------------------------------------------re------------ +/// Class LLFloaterSimpleSnapshot +///---------------------------------------------------------------------------- + +LLFloaterSimpleSnapshot::LLFloaterSimpleSnapshot(const LLSD& key) + : LLFloaterSnapshotBase(key) + , mOwner(NULL) + , mContextConeOpacity(0.f) +{ + impl = new Impl(this); +} + +LLFloaterSimpleSnapshot::~LLFloaterSimpleSnapshot() +{ +} + +bool LLFloaterSimpleSnapshot::postBuild() +{ + childSetAction("new_snapshot_btn", ImplBase::onClickNewSnapshot, this); + childSetAction("save_btn", boost::bind(&LLFloaterSimpleSnapshot::onSend, this)); + childSetAction("cancel_btn", boost::bind(&LLFloaterSimpleSnapshot::onCancel, this)); + + mThumbnailPlaceholder = getChild("thumbnail_placeholder"); + + // create preview window + LLRect full_screen_rect = getRootView()->getRect(); + LLSnapshotLivePreview::Params p; + p.rect(full_screen_rect); + LLSnapshotLivePreview* previewp = new LLSnapshotLivePreview(p); + + // Do not move LLFloaterSimpleSnapshot floater into gSnapshotFloaterView + // since it can be a dependednt floater and does not draw UI + + impl->mPreviewHandle = previewp->getHandle(); + previewp->setContainer(this); + impl->updateControls(this); + impl->setAdvanced(true); + impl->setSkipReshaping(true); + + previewp->mKeepAspectRatio = false; + previewp->setThumbnailPlaceholderRect(getThumbnailPlaceholderRect()); + previewp->setAllowRenderUI(false); + previewp->setThumbnailSubsampled(true); + + return true; +} + +constexpr S32 PREVIEW_OFFSET_Y = 70; + +void LLFloaterSimpleSnapshot::draw() +{ + if (mOwner) + { + static LLCachedControl max_opacity(gSavedSettings, "PickerContextOpacity", 0.4f); + drawConeToOwner(mContextConeOpacity, max_opacity, mOwner); + } + + LLSnapshotLivePreview* previewp = getPreviewView(); + + if (previewp && (previewp->isSnapshotActive() || previewp->getThumbnailLock())) + { + // don't render snapshot window in snapshot, even if "show ui" is turned on + return; + } + + LLFloater::draw(); + + if (previewp && !isMinimized() && mThumbnailPlaceholder->getVisible()) + { + if(previewp->getThumbnailImage()) + { + bool working = impl->getStatus() == ImplBase::STATUS_WORKING; + const S32 thumbnail_w = previewp->getThumbnailWidth(); + const S32 thumbnail_h = previewp->getThumbnailHeight(); + + LLRect local_rect = getLocalRect(); + S32 offset_x = (local_rect.getWidth() - thumbnail_w) / 2; + S32 offset_y = PREVIEW_OFFSET_Y; + + gGL.matrixMode(LLRender::MM_MODELVIEW); + // Apply floater transparency to the texture unless the floater is focused. + F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); + LLColor4 color = working ? LLColor4::grey4 : LLColor4::white; + gl_draw_scaled_image(offset_x, offset_y, + thumbnail_w, thumbnail_h, + previewp->getThumbnailImage(), color % alpha); + } + } + impl->updateLayout(this); +} + +void LLFloaterSimpleSnapshot::onOpen(const LLSD& key) +{ + LLSnapshotLivePreview* preview = getPreviewView(); + if (preview) + { + preview->updateSnapshot(true); + } + focusFirstItem(false); + gSnapshotFloaterView->setEnabled(true); + gSnapshotFloaterView->setVisible(true); + gSnapshotFloaterView->adjustToFitScreen(this, false); + + impl->updateControls(this); + impl->setStatus(ImplBase::STATUS_READY); + + mInventoryId = key["item_id"].asUUID(); + mTaskId = key["task_id"].asUUID(); +} + +void LLFloaterSimpleSnapshot::onCancel() +{ + closeFloater(); +} + +void LLFloaterSimpleSnapshot::onSend() +{ + LLSnapshotLivePreview* previewp = getPreviewView(); + + std::string temp_file = gDirUtilp->getTempFilename(); + if (previewp->createUploadFile(temp_file, THUMBNAIL_SNAPSHOT_DIM_MAX, THUMBNAIL_SNAPSHOT_DIM_MIN)) + { + uploadImageUploadFile(temp_file, mInventoryId, mTaskId, mUploadCompletionCallback); + closeFloater(); + } + else + { + LLSD notif_args; + notif_args["REASON"] = LLImage::getLastThreadError().c_str(); + LLNotificationsUtil::add("CannotUploadTexture", notif_args); + if (mUploadCompletionCallback) + { + mUploadCompletionCallback(LLUUID::null); + } + } +} + +void LLFloaterSimpleSnapshot::postSave() +{ + impl->setStatus(ImplBase::STATUS_WORKING); +} + +// static +void LLFloaterSimpleSnapshot::uploadThumbnail(const std::string &file_path, + const LLUUID &inventory_id, + const LLUUID &task_id, + completion_t callback) +{ + // generate a temp texture file for coroutine + std::string temp_file = gDirUtilp->getTempFilename(); + U32 codec = LLImageBase::getCodecFromExtension(gDirUtilp->getExtension(file_path)); + if (!LLViewerTextureList::createUploadFile(file_path, temp_file, codec, THUMBNAIL_SNAPSHOT_DIM_MAX, THUMBNAIL_SNAPSHOT_DIM_MIN, true)) + { + LLSD notif_args; + notif_args["REASON"] = LLImage::getLastThreadError().c_str(); + LLNotificationsUtil::add("CannotUploadTexture", notif_args); + LL_WARNS("Thumbnail") << "Failed to upload thumbnail for " << inventory_id << " " << task_id << ", reason: " << notif_args["REASON"].asString() << LL_ENDL; + return; + } + uploadImageUploadFile(temp_file, inventory_id, task_id, callback); +} + +// static +void LLFloaterSimpleSnapshot::uploadThumbnail(LLPointer raw_image, + const LLUUID& inventory_id, + const LLUUID& task_id, + completion_t callback) +{ + std::string temp_file = gDirUtilp->getTempFilename(); + if (!LLViewerTextureList::createUploadFile(raw_image, temp_file, THUMBNAIL_SNAPSHOT_DIM_MAX, THUMBNAIL_SNAPSHOT_DIM_MIN)) + { + LLSD notif_args; + notif_args["REASON"] = LLImage::getLastThreadError().c_str(); + LLNotificationsUtil::add("CannotUploadTexture", notif_args); + LL_WARNS("Thumbnail") << "Failed to upload thumbnail for " << inventory_id << " " << task_id << ", reason: " << notif_args["REASON"].asString() << LL_ENDL; + return; + } + uploadImageUploadFile(temp_file, inventory_id, task_id, callback); +} + +// static +void LLFloaterSimpleSnapshot::uploadImageUploadFile(const std::string &temp_file, + const LLUUID &inventory_id, + const LLUUID &task_id, + completion_t callback) +{ + LLSD data; + + if (task_id.notNull()) + { + data["item_id"] = inventory_id; + data["task_id"] = task_id; + } + else if (gInventory.getCategory(inventory_id)) + { + data["category_id"] = inventory_id; + } + else + { + data["item_id"] = inventory_id; + } + + std::string cap_url = gAgent.getRegionCapability(THUMBNAIL_UPLOAD_CAP); + if (cap_url.empty()) + { + LLSD args; + args["CAPABILITY"] = THUMBNAIL_UPLOAD_CAP; + LLNotificationsUtil::add("RegionCapabilityRequestError", args); + LL_WARNS("Thumbnail") << "Failed to upload profile image for item " << inventory_id << " " << task_id << ", no cap found" << LL_ENDL; + return; + } + + LLCoros::instance().launch("postAgentUserImageCoro", + boost::bind(post_thumbnail_image_coro, cap_url, temp_file, data, callback)); +} + +void LLFloaterSimpleSnapshot::update() +{ + // initializes snapshots when needed + LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("simple_snapshot"); + for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); + iter != inst_list.end(); ++iter) + { + LLFloaterSimpleSnapshot* floater = dynamic_cast(*iter); + if (floater) + { + floater->impl->updateLivePreview(); + } + } +} + + +// static +LLFloaterSimpleSnapshot* LLFloaterSimpleSnapshot::findInstance(const LLSD &key) +{ + return LLFloaterReg::findTypedInstance("simple_snapshot", key); +} + +// static +LLFloaterSimpleSnapshot* LLFloaterSimpleSnapshot::getInstance(const LLSD &key) +{ + return LLFloaterReg::getTypedInstance("simple_snapshot", key); +} + +void LLFloaterSimpleSnapshot::saveTexture() +{ + LLSnapshotLivePreview* previewp = getPreviewView(); + if (!previewp) + { + llassert(previewp != NULL); + return; + } + + previewp->saveTexture(true, getInventoryId().asString()); + closeFloater(); +} + +///---------------------------------------------------------------------------- +/// Class LLSimpleOutfitSnapshotFloaterView +///---------------------------------------------------------------------------- + +LLSimpleSnapshotFloaterView::LLSimpleSnapshotFloaterView(const Params& p) : LLFloaterView(p) +{ +} + +LLSimpleSnapshotFloaterView::~LLSimpleSnapshotFloaterView() +{ +} diff --git a/indra/newview/llfloatersimplesnapshot.h b/indra/newview/llfloatersimplesnapshot.h index 9ab9c713bc..487e77469c 100644 --- a/indra/newview/llfloatersimplesnapshot.h +++ b/indra/newview/llfloatersimplesnapshot.h @@ -1,153 +1,153 @@ -/** -* @file llfloatersimplesnapshot.h -* @brief Snapshot preview window for saving as a thumbnail -* -* $LicenseInfo:firstyear=2022&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2022, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#ifndef LL_LLFLOATERSIMPLESNAPSHOT_H -#define LL_LLFLOATERSIMPLESNAPSHOT_H - -#include "llfloater.h" -#include "llfloatersnapshot.h" -#include "llsnapshotlivepreview.h" - -///---------------------------------------------------------------------------- -/// Class LLFloaterSimpleSnapshot -///---------------------------------------------------------------------------- - -class LLFloaterSimpleSnapshot : public LLFloaterSnapshotBase -{ - LOG_CLASS(LLFloaterSimpleSnapshot); - -public: - - LLFloaterSimpleSnapshot(const LLSD& key); - ~LLFloaterSimpleSnapshot(); - - bool postBuild(); - void onOpen(const LLSD& key); - void draw(); - - static void update(); - - static LLFloaterSimpleSnapshot* getInstance(const LLSD &key); - static LLFloaterSimpleSnapshot* findInstance(const LLSD &key); - void saveTexture(); - - const LLRect& getThumbnailPlaceholderRect() { return mThumbnailPlaceholder->getRect(); } - - void setInventoryId(const LLUUID &inventory_id) { mInventoryId = inventory_id; } - LLUUID getInventoryId() { return mInventoryId; } - void setTaskId(const LLUUID &task_id) { mTaskId = task_id; } - void setOwner(LLView *owner_view) { mOwner = owner_view; } - - void postSave(); - - typedef boost::function completion_t; - void setComplectionCallback(completion_t callback) { mUploadCompletionCallback = callback; } - static void uploadThumbnail(const std::string &file_path, - const LLUUID &inventory_id, - const LLUUID &task_id, - completion_t callback = completion_t()); - static void uploadThumbnail(LLPointer raw_image, - const LLUUID& inventory_id, - const LLUUID& task_id, - completion_t callback = completion_t()); - - class Impl; - friend class Impl; - - static const S32 THUMBNAIL_SNAPSHOT_DIM_MAX; - static const S32 THUMBNAIL_SNAPSHOT_DIM_MIN; - -private: - void onSend(); - void onCancel(); - - // uploads upload-ready file - static void uploadImageUploadFile(const std::string &temp_file, - const LLUUID &inventory_id, - const LLUUID &task_id, - completion_t callback); - - LLUUID mInventoryId; - LLUUID mTaskId; - - LLView* mOwner; - F32 mContextConeOpacity; - completion_t mUploadCompletionCallback; -}; - -///---------------------------------------------------------------------------- -/// Class LLFloaterSimpleSnapshot::Impl -///---------------------------------------------------------------------------- - -class LLFloaterSimpleSnapshot::Impl : public LLFloaterSnapshotBase::ImplBase -{ - LOG_CLASS(LLFloaterSimpleSnapshot::Impl); -public: - Impl(LLFloaterSnapshotBase* floater) - : LLFloaterSnapshotBase::ImplBase(floater) - {} - ~Impl() - {} - void updateResolution(void* data); - - static void onSnapshotUploadFinished(LLFloaterSnapshotBase* floater, bool status); - - LLPanelSnapshot* getActivePanel(LLFloaterSnapshotBase* floater, bool ok_if_not_found = true) { return NULL; } - LLSnapshotModel::ESnapshotFormat getImageFormat(LLFloaterSnapshotBase* floater); - std::string getSnapshotPanelPrefix(); - - void updateControls(LLFloaterSnapshotBase* floater); - - void setStatus(EStatus status, bool ok = true, const std::string& msg = LLStringUtil::null); - -private: - LLSnapshotModel::ESnapshotLayerType getLayerType(LLFloaterSnapshotBase* floater); - void setFinished(bool finished, bool ok = true, const std::string& msg = LLStringUtil::null) {}; -}; - -///---------------------------------------------------------------------------- -/// Class LLSimpleOutfitSnapshotFloaterView -///---------------------------------------------------------------------------- - -class LLSimpleSnapshotFloaterView : public LLFloaterView -{ -public: - struct Params - : public LLInitParam::Block - { - }; - -protected: - LLSimpleSnapshotFloaterView(const Params& p); - friend class LLUICtrlFactory; - -public: - virtual ~LLSimpleSnapshotFloaterView(); -}; - -extern LLSimpleSnapshotFloaterView* gSimpleOutfitSnapshotFloaterView; - -#endif // LL_LLFLOATERSIMPLESNAPSHOT_H +/** +* @file llfloatersimplesnapshot.h +* @brief Snapshot preview window for saving as a thumbnail +* +* $LicenseInfo:firstyear=2022&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2022, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#ifndef LL_LLFLOATERSIMPLESNAPSHOT_H +#define LL_LLFLOATERSIMPLESNAPSHOT_H + +#include "llfloater.h" +#include "llfloatersnapshot.h" +#include "llsnapshotlivepreview.h" + +///---------------------------------------------------------------------------- +/// Class LLFloaterSimpleSnapshot +///---------------------------------------------------------------------------- + +class LLFloaterSimpleSnapshot : public LLFloaterSnapshotBase +{ + LOG_CLASS(LLFloaterSimpleSnapshot); + +public: + + LLFloaterSimpleSnapshot(const LLSD& key); + ~LLFloaterSimpleSnapshot(); + + bool postBuild(); + void onOpen(const LLSD& key); + void draw(); + + static void update(); + + static LLFloaterSimpleSnapshot* getInstance(const LLSD &key); + static LLFloaterSimpleSnapshot* findInstance(const LLSD &key); + void saveTexture(); + + const LLRect& getThumbnailPlaceholderRect() { return mThumbnailPlaceholder->getRect(); } + + void setInventoryId(const LLUUID &inventory_id) { mInventoryId = inventory_id; } + LLUUID getInventoryId() { return mInventoryId; } + void setTaskId(const LLUUID &task_id) { mTaskId = task_id; } + void setOwner(LLView *owner_view) { mOwner = owner_view; } + + void postSave(); + + typedef boost::function completion_t; + void setComplectionCallback(completion_t callback) { mUploadCompletionCallback = callback; } + static void uploadThumbnail(const std::string &file_path, + const LLUUID &inventory_id, + const LLUUID &task_id, + completion_t callback = completion_t()); + static void uploadThumbnail(LLPointer raw_image, + const LLUUID& inventory_id, + const LLUUID& task_id, + completion_t callback = completion_t()); + + class Impl; + friend class Impl; + + static const S32 THUMBNAIL_SNAPSHOT_DIM_MAX; + static const S32 THUMBNAIL_SNAPSHOT_DIM_MIN; + +private: + void onSend(); + void onCancel(); + + // uploads upload-ready file + static void uploadImageUploadFile(const std::string &temp_file, + const LLUUID &inventory_id, + const LLUUID &task_id, + completion_t callback); + + LLUUID mInventoryId; + LLUUID mTaskId; + + LLView* mOwner; + F32 mContextConeOpacity; + completion_t mUploadCompletionCallback; +}; + +///---------------------------------------------------------------------------- +/// Class LLFloaterSimpleSnapshot::Impl +///---------------------------------------------------------------------------- + +class LLFloaterSimpleSnapshot::Impl : public LLFloaterSnapshotBase::ImplBase +{ + LOG_CLASS(LLFloaterSimpleSnapshot::Impl); +public: + Impl(LLFloaterSnapshotBase* floater) + : LLFloaterSnapshotBase::ImplBase(floater) + {} + ~Impl() + {} + void updateResolution(void* data); + + static void onSnapshotUploadFinished(LLFloaterSnapshotBase* floater, bool status); + + LLPanelSnapshot* getActivePanel(LLFloaterSnapshotBase* floater, bool ok_if_not_found = true) { return NULL; } + LLSnapshotModel::ESnapshotFormat getImageFormat(LLFloaterSnapshotBase* floater); + std::string getSnapshotPanelPrefix(); + + void updateControls(LLFloaterSnapshotBase* floater); + + void setStatus(EStatus status, bool ok = true, const std::string& msg = LLStringUtil::null); + +private: + LLSnapshotModel::ESnapshotLayerType getLayerType(LLFloaterSnapshotBase* floater); + void setFinished(bool finished, bool ok = true, const std::string& msg = LLStringUtil::null) {}; +}; + +///---------------------------------------------------------------------------- +/// Class LLSimpleOutfitSnapshotFloaterView +///---------------------------------------------------------------------------- + +class LLSimpleSnapshotFloaterView : public LLFloaterView +{ +public: + struct Params + : public LLInitParam::Block + { + }; + +protected: + LLSimpleSnapshotFloaterView(const Params& p); + friend class LLUICtrlFactory; + +public: + virtual ~LLSimpleSnapshotFloaterView(); +}; + +extern LLSimpleSnapshotFloaterView* gSimpleOutfitSnapshotFloaterView; + +#endif // LL_LLFLOATERSIMPLESNAPSHOT_H diff --git a/indra/newview/llfloatersnapshot.cpp b/indra/newview/llfloatersnapshot.cpp index 70773070ce..2bac7d6360 100644 --- a/indra/newview/llfloatersnapshot.cpp +++ b/indra/newview/llfloatersnapshot.cpp @@ -1,1494 +1,1494 @@ -/** - * @file llfloatersnapshot.cpp - * @brief Snapshot preview window, allowing saving, e-mailing, etc. - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2016, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloatersnapshot.h" - -#include "llfloaterreg.h" -#include "llimagefiltersmanager.h" -#include "llcheckboxctrl.h" -#include "llcombobox.h" -#include "llpostcard.h" -#include "llresmgr.h" // LLLocale -#include "llsdserialize.h" -#include "llsidetraypanelcontainer.h" -#include "llsnapshotlivepreview.h" -#include "llspinctrl.h" -#include "llviewercontrol.h" -#include "lltoolfocus.h" -#include "lltoolmgr.h" -#include "llwebprofile.h" - -///---------------------------------------------------------------------------- -/// Local function declarations, constants, enums, and typedefs -///---------------------------------------------------------------------------- -LLSnapshotFloaterView* gSnapshotFloaterView = NULL; - -const F32 AUTO_SNAPSHOT_TIME_DELAY = 1.f; - -const S32 MAX_POSTCARD_DATASIZE = 1572864; // 1.5 megabyte, similar to simulator limit -const S32 MAX_TEXTURE_SIZE = 512 ; //max upload texture size 512 * 512 - -static LLDefaultChildRegistry::Register r("snapshot_floater_view"); - -// virtual -LLPanelSnapshot* LLFloaterSnapshot::Impl::getActivePanel(LLFloaterSnapshotBase* floater, bool ok_if_not_found) -{ - LLSideTrayPanelContainer* panel_container = floater->getChild("panel_container"); - LLPanelSnapshot* active_panel = dynamic_cast(panel_container->getCurrentPanel()); - if (!ok_if_not_found) - { - llassert_always(active_panel != NULL); - } - return active_panel; -} - -// virtual -LLSnapshotModel::ESnapshotType LLFloaterSnapshotBase::ImplBase::getActiveSnapshotType(LLFloaterSnapshotBase* floater) -{ - LLPanelSnapshot* spanel = getActivePanel(floater); - - //return type; - if (spanel) - { - return spanel->getSnapshotType(); - } - return LLSnapshotModel::SNAPSHOT_WEB; -} - -// virtual -LLSnapshotModel::ESnapshotFormat LLFloaterSnapshot::Impl::getImageFormat(LLFloaterSnapshotBase* floater) -{ - LLPanelSnapshot* active_panel = getActivePanel(floater); - // FIXME: if the default is not PNG, profile uploads may fail. - return active_panel ? active_panel->getImageFormat() : LLSnapshotModel::SNAPSHOT_FORMAT_PNG; -} - -LLSpinCtrl* LLFloaterSnapshot::Impl::getWidthSpinner(LLFloaterSnapshotBase* floater) -{ - LLPanelSnapshot* active_panel = getActivePanel(floater); - return active_panel ? active_panel->getWidthSpinner() : floater->getChild("snapshot_width"); -} - -LLSpinCtrl* LLFloaterSnapshot::Impl::getHeightSpinner(LLFloaterSnapshotBase* floater) -{ - LLPanelSnapshot* active_panel = getActivePanel(floater); - return active_panel ? active_panel->getHeightSpinner() : floater->getChild("snapshot_height"); -} - -void LLFloaterSnapshot::Impl::enableAspectRatioCheckbox(LLFloaterSnapshotBase* floater, bool enable) -{ - LLPanelSnapshot* active_panel = getActivePanel(floater); - if (active_panel) - { - active_panel->enableAspectRatioCheckbox(enable); - } -} - -void LLFloaterSnapshot::Impl::setAspectRatioCheckboxValue(LLFloaterSnapshotBase* floater, bool checked) -{ - LLPanelSnapshot* active_panel = getActivePanel(floater); - if (active_panel) - { - active_panel->getChild(active_panel->getAspectRatioCBName())->setValue(checked); - } -} - -LLSnapshotLivePreview* LLFloaterSnapshotBase::getPreviewView() -{ - return impl->getPreviewView(); -} - -LLSnapshotLivePreview* LLFloaterSnapshotBase::ImplBase::getPreviewView() -{ - LLSnapshotLivePreview* previewp = (LLSnapshotLivePreview*)mPreviewHandle.get(); - return previewp; -} - -// virtual -LLSnapshotModel::ESnapshotLayerType LLFloaterSnapshot::Impl::getLayerType(LLFloaterSnapshotBase* floater) -{ - LLSnapshotModel::ESnapshotLayerType type = LLSnapshotModel::SNAPSHOT_TYPE_COLOR; - LLSD value = floater->getChild("layer_types")->getValue(); - const std::string id = value.asString(); - if (id == "colors") - type = LLSnapshotModel::SNAPSHOT_TYPE_COLOR; - else if (id == "depth") - type = LLSnapshotModel::SNAPSHOT_TYPE_DEPTH; - return type; -} - -void LLFloaterSnapshot::Impl::setResolution(LLFloaterSnapshotBase* floater, const std::string& comboname) -{ - LLComboBox* combo = floater->getChild(comboname); - combo->setVisible(true); - updateResolution(combo, floater, false); // to sync spinners with combo -} - -//virtual -void LLFloaterSnapshotBase::ImplBase::updateLayout(LLFloaterSnapshotBase* floaterp) -{ - LLSnapshotLivePreview* previewp = getPreviewView(); - - //BD - Automatically calculate the size of our snapshot window to enlarge - // the snapshot preview to its maximum size, this is especially helpfull - // for pretty much every aspect ratio other than 1:1. - F32 panel_width = 400.f * gViewerWindow->getWorldViewAspectRatio(); - - //BD - Make sure we clamp at 700 here because 700 would be for 16:9 which we - // consider the maximum. Everything bigger will be clamped and will have - // a slightly smaller preview window which most likely won't fill up the - // whole snapshot floater as it should. - if(panel_width > 700.f) - { - panel_width = 700.f; - } - - S32 floater_width = 224.f; - if(mAdvanced) - { - floater_width = floater_width + panel_width; - } - - LLUICtrl* thumbnail_placeholder = floaterp->getChild("thumbnail_placeholder"); - thumbnail_placeholder->setVisible(mAdvanced); - - floaterp->getChild("image_res_text")->setVisible(mAdvanced); - floaterp->getChild("file_size_label")->setVisible(mAdvanced); - if (floaterp->hasChild("360_label", true)) - { - floaterp->getChild("360_label")->setVisible(mAdvanced); - } - if (!mSkipReshaping) - { - thumbnail_placeholder->reshape(panel_width, thumbnail_placeholder->getRect().getHeight()); - if (!floaterp->isMinimized()) - { - floaterp->reshape(floater_width, floaterp->getRect().getHeight()); - } - } - - bool use_freeze_frame = floaterp->getChild("freeze_frame_check")->getValue().asBoolean(); - - if (use_freeze_frame) - { - // stop all mouse events at fullscreen preview layer - floaterp->getParent()->setMouseOpaque(true); - - // shrink to smaller layout - // *TODO: unneeded? - floaterp->reshape(floaterp->getRect().getWidth(), floaterp->getRect().getHeight()); - - // can see and interact with fullscreen preview now - if (previewp) - { - previewp->setVisible(true); - previewp->setEnabled(true); - } - - //RN: freeze all avatars - LLCharacter* avatarp; - for (std::vector::iterator iter = LLCharacter::sInstances.begin(); - iter != LLCharacter::sInstances.end(); ++iter) - { - avatarp = *iter; - floaterp->impl->mAvatarPauseHandles.push_back(avatarp->requestPause()); - } - - // freeze everything else - gSavedSettings.setBOOL("FreezeTime", true); - - if (LLToolMgr::getInstance()->getCurrentToolset() != gCameraToolset) - { - floaterp->impl->mLastToolset = LLToolMgr::getInstance()->getCurrentToolset(); - LLToolMgr::getInstance()->setCurrentToolset(gCameraToolset); - } - } - else // turning off freeze frame mode - { - floaterp->getParent()->setMouseOpaque(false); - // *TODO: unneeded? - floaterp->reshape(floaterp->getRect().getWidth(), floaterp->getRect().getHeight()); - if (previewp) - { - previewp->setVisible(false); - previewp->setEnabled(false); - } - - //RN: thaw all avatars - floaterp->impl->mAvatarPauseHandles.clear(); - - // thaw everything else - gSavedSettings.setBOOL("FreezeTime", false); - - // restore last tool (e.g. pie menu, etc) - if (floaterp->impl->mLastToolset) - { - LLToolMgr::getInstance()->setCurrentToolset(floaterp->impl->mLastToolset); - } - } -} - -// This is the main function that keeps all the GUI controls in sync with the saved settings. -// It should be called anytime a setting is changed that could affect the controls. -// No other methods should be changing any of the controls directly except for helpers called by this method. -// The basic pattern for programmatically changing the GUI settings is to first set the -// appropriate saved settings and then call this method to sync the GUI with them. -// FIXME: The above comment seems obsolete now. -// virtual -void LLFloaterSnapshot::Impl::updateControls(LLFloaterSnapshotBase* floater) -{ - LLSnapshotModel::ESnapshotType shot_type = getActiveSnapshotType(floater); - LLSnapshotModel::ESnapshotFormat shot_format = (LLSnapshotModel::ESnapshotFormat)gSavedSettings.getS32("SnapshotFormat"); - LLSnapshotModel::ESnapshotLayerType layer_type = getLayerType(floater); - - floater->getChild("local_format_combo")->selectNthItem(gSavedSettings.getS32("SnapshotFormat")); - floater->getChildView("layer_types")->setEnabled(shot_type == LLSnapshotModel::SNAPSHOT_LOCAL); - - LLPanelSnapshot* active_panel = getActivePanel(floater); - if (active_panel) - { - LLSpinCtrl* width_ctrl = getWidthSpinner(floater); - LLSpinCtrl* height_ctrl = getHeightSpinner(floater); - - // Initialize spinners. - if (width_ctrl->getValue().asInteger() == 0) - { - S32 w = gViewerWindow->getWindowWidthRaw(); - LL_DEBUGS() << "Initializing width spinner (" << width_ctrl->getName() << "): " << w << LL_ENDL; - width_ctrl->setValue(w); - if (getActiveSnapshotType(floater) == LLSnapshotModel::SNAPSHOT_TEXTURE) - { - width_ctrl->setIncrement(w >> 1); - } - } - if (height_ctrl->getValue().asInteger() == 0) - { - S32 h = gViewerWindow->getWindowHeightRaw(); - LL_DEBUGS() << "Initializing height spinner (" << height_ctrl->getName() << "): " << h << LL_ENDL; - height_ctrl->setValue(h); - if (getActiveSnapshotType(floater) == LLSnapshotModel::SNAPSHOT_TEXTURE) - { - height_ctrl->setIncrement(h >> 1); - } - } - - // Clamp snapshot resolution to window size when showing UI or HUD in snapshot. - if (gSavedSettings.getBOOL("RenderUIInSnapshot") || gSavedSettings.getBOOL("RenderHUDInSnapshot")) - { - S32 width = gViewerWindow->getWindowWidthRaw(); - S32 height = gViewerWindow->getWindowHeightRaw(); - - width_ctrl->setMaxValue(width); - - height_ctrl->setMaxValue(height); - - if (width_ctrl->getValue().asInteger() > width) - { - width_ctrl->forceSetValue(width); - } - if (height_ctrl->getValue().asInteger() > height) - { - height_ctrl->forceSetValue(height); - } - } - else - { - width_ctrl->setMaxValue(MAX_SNAPSHOT_IMAGE_SIZE); - height_ctrl->setMaxValue(MAX_SNAPSHOT_IMAGE_SIZE); - } - } - - LLSnapshotLivePreview* previewp = getPreviewView(); - bool got_bytes = previewp && previewp->getDataSize() > 0; - bool got_snap = previewp && previewp->getSnapshotUpToDate(); - - // *TODO: Separate maximum size for Web images from postcards - LL_DEBUGS() << "Is snapshot up-to-date? " << got_snap << LL_ENDL; - - LLLocale locale(LLLocale::USER_LOCALE); - std::string bytes_string; - if (got_snap) - { - LLResMgr::getInstance()->getIntegerString(bytes_string, (previewp->getDataSize()) >> 10 ); - } - - // Update displayed image resolution. - LLTextBox* image_res_tb = floater->getChild("image_res_text"); - image_res_tb->setVisible(got_snap); - if (got_snap) - { - image_res_tb->setTextArg("[WIDTH]", llformat("%d", previewp->getEncodedImageWidth())); - image_res_tb->setTextArg("[HEIGHT]", llformat("%d", previewp->getEncodedImageHeight())); - } - - floater->getChild("file_size_label")->setTextArg("[SIZE]", got_snap ? bytes_string : floater->getString("unknown")); - floater->getChild("file_size_label")->setColor( - shot_type == LLSnapshotModel::SNAPSHOT_POSTCARD - && got_bytes - && previewp->getDataSize() > MAX_POSTCARD_DATASIZE ? LLUIColor(LLColor4::red) : LLUIColorTable::instance().getColor( "LabelTextColor" )); - - // Update the width and height spinners based on the corresponding resolution combos. (?) - switch(shot_type) - { - case LLSnapshotModel::SNAPSHOT_WEB: - layer_type = LLSnapshotModel::SNAPSHOT_TYPE_COLOR; - floater->getChild("layer_types")->setValue("colors"); - setResolution(floater, "profile_size_combo"); - break; - case LLSnapshotModel::SNAPSHOT_POSTCARD: - layer_type = LLSnapshotModel::SNAPSHOT_TYPE_COLOR; - floater->getChild("layer_types")->setValue("colors"); - setResolution(floater, "postcard_size_combo"); - break; - case LLSnapshotModel::SNAPSHOT_TEXTURE: - layer_type = LLSnapshotModel::SNAPSHOT_TYPE_COLOR; - floater->getChild("layer_types")->setValue("colors"); - setResolution(floater, "texture_size_combo"); - break; - case LLSnapshotModel::SNAPSHOT_LOCAL: - setResolution(floater, "local_size_combo"); - break; - default: - break; - } - setAspectRatioCheckboxValue(floater, !floater->impl->mAspectRatioCheckOff && gSavedSettings.getBOOL("KeepAspectForSnapshot")); - - if (previewp) - { - previewp->setSnapshotType(shot_type); - previewp->setSnapshotFormat(shot_format); - previewp->setSnapshotBufferType(layer_type); - } - - LLPanelSnapshot* current_panel = Impl::getActivePanel(floater); - if (current_panel) - { - LLSD info; - info["have-snapshot"] = got_snap; - current_panel->updateControls(info); - } - LL_DEBUGS() << "finished updating controls" << LL_ENDL; -} - -//virtual -void LLFloaterSnapshotBase::ImplBase::setStatus(EStatus status, bool ok, const std::string& msg) -{ - switch (status) - { - case STATUS_READY: - setWorking(false); - setFinished(false); - break; - case STATUS_WORKING: - setWorking(true); - setFinished(false); - break; - case STATUS_FINISHED: - setWorking(false); - setFinished(true, ok, msg); - break; - } - - mStatus = status; -} - -// virtual -void LLFloaterSnapshotBase::ImplBase::setNeedRefresh(bool need) -{ - if (!mFloater) return; - - // Don't display the "Refresh to save" message if we're in auto-refresh mode. - if (gSavedSettings.getBOOL("AutoSnapshot")) - { - need = false; - } - - mFloater->setRefreshLabelVisible(need); - mNeedRefresh = need; -} - -// virtual -void LLFloaterSnapshotBase::ImplBase::checkAutoSnapshot(LLSnapshotLivePreview* previewp, bool update_thumbnail) -{ - if (previewp) - { - bool autosnap = gSavedSettings.getBOOL("AutoSnapshot"); - LL_DEBUGS() << "updating " << (autosnap ? "snapshot" : "thumbnail") << LL_ENDL; - previewp->updateSnapshot(autosnap, update_thumbnail, autosnap ? AUTO_SNAPSHOT_TIME_DELAY : 0.f); - } -} - -// static -void LLFloaterSnapshotBase::ImplBase::onClickNewSnapshot(void* data) -{ - LLFloaterSnapshotBase* floater = (LLFloaterSnapshotBase *)data; - LLSnapshotLivePreview* previewp = floater->getPreviewView(); - if (previewp) - { - floater->impl->setStatus(ImplBase::STATUS_READY); - LL_DEBUGS() << "updating snapshot" << LL_ENDL; - previewp->mForceUpdateSnapshot = true; - } -} - -// static -void LLFloaterSnapshotBase::ImplBase::onClickAutoSnap(LLUICtrl *ctrl, void* data) -{ - LLCheckBoxCtrl *check = (LLCheckBoxCtrl *)ctrl; - gSavedSettings.setBOOL( "AutoSnapshot", check->get() ); - - LLFloaterSnapshotBase *view = (LLFloaterSnapshotBase *)data; - if (view) - { - view->impl->checkAutoSnapshot(view->getPreviewView()); - view->impl->updateControls(view); - } -} - -// static -void LLFloaterSnapshotBase::ImplBase::onClickNoPost(LLUICtrl *ctrl, void* data) -{ - bool no_post = ((LLCheckBoxCtrl*)ctrl)->get(); - gSavedSettings.setBOOL("RenderSnapshotNoPost", no_post); - - LLFloaterSnapshotBase* view = (LLFloaterSnapshotBase*)data; - view->getPreviewView()->updateSnapshot(true, true); - view->impl->updateControls(view); -} - -// static -void LLFloaterSnapshotBase::ImplBase::onClickFilter(LLUICtrl *ctrl, void* data) -{ - LLFloaterSnapshotBase *view = (LLFloaterSnapshotBase *)data; - if (view) - { - view->impl->updateControls(view); - LLSnapshotLivePreview* previewp = view->getPreviewView(); - if (previewp) - { - view->impl->checkAutoSnapshot(previewp); - // Note : index 0 of the filter drop down is assumed to be "No filter" in whichever locale - LLComboBox* filterbox = static_cast(view->getChild("filters_combobox")); - std::string filter_name = (filterbox->getCurrentIndex() ? filterbox->getSimple() : ""); - previewp->setFilter(filter_name); - previewp->updateSnapshot(true); - } - } -} - -// static -void LLFloaterSnapshotBase::ImplBase::onClickUICheck(LLUICtrl *ctrl, void* data) -{ - LLCheckBoxCtrl *check = (LLCheckBoxCtrl *)ctrl; - gSavedSettings.setBOOL( "RenderUIInSnapshot", check->get() ); - - LLFloaterSnapshot *view = (LLFloaterSnapshot *)data; - if (view) - { - LLSnapshotLivePreview* previewp = view->getPreviewView(); - if(previewp) - { - previewp->updateSnapshot(true, true); - } - view->impl->updateControls(view); - } -} - -// static -void LLFloaterSnapshotBase::ImplBase::onClickHUDCheck(LLUICtrl *ctrl, void* data) -{ - LLCheckBoxCtrl *check = (LLCheckBoxCtrl *)ctrl; - gSavedSettings.setBOOL( "RenderHUDInSnapshot", check->get() ); - - LLFloaterSnapshot *view = (LLFloaterSnapshot *)data; - if (view) - { - LLSnapshotLivePreview* previewp = view->getPreviewView(); - if(previewp) - { - previewp->updateSnapshot(true, true); - } - view->impl->updateControls(view); - } -} - -void LLFloaterSnapshot::Impl::applyKeepAspectCheck(LLFloaterSnapshotBase* view, bool checked) -{ - gSavedSettings.setBOOL("KeepAspectForSnapshot", checked); - - if (view) - { - LLPanelSnapshot* active_panel = getActivePanel(view); - if (checked && active_panel) - { - LLComboBox* combo = view->getChild(active_panel->getImageSizeComboName()); - combo->setCurrentByIndex(combo->getItemCount() - 1); // "custom" is always the last index - } - - LLSnapshotLivePreview* previewp = getPreviewView() ; - if(previewp) - { - previewp->mKeepAspectRatio = gSavedSettings.getBOOL("KeepAspectForSnapshot") ; - - S32 w, h ; - previewp->getSize(w, h) ; - updateSpinners(view, previewp, w, h, true); // may change w and h - - LL_DEBUGS() << "updating thumbnail" << LL_ENDL; - previewp->setSize(w, h) ; - previewp->updateSnapshot(true); - checkAutoSnapshot(previewp, true); - } - } -} - -// static -void LLFloaterSnapshotBase::ImplBase::onCommitFreezeFrame(LLUICtrl* ctrl, void* data) -{ - LLCheckBoxCtrl* check_box = (LLCheckBoxCtrl*)ctrl; - LLFloaterSnapshotBase *view = (LLFloaterSnapshotBase *)data; - LLSnapshotLivePreview* previewp = view->getPreviewView(); - - if (!view || !check_box || !previewp) - { - return; - } - - gSavedSettings.setBOOL("UseFreezeFrame", check_box->get()); - - if (check_box->get()) - { - previewp->prepareFreezeFrame(); - } - - view->impl->updateLayout(view); -} - -void LLFloaterSnapshot::Impl::checkAspectRatio(LLFloaterSnapshotBase *view, S32 index) -{ - LLSnapshotLivePreview *previewp = getPreviewView() ; - - // Don't round texture sizes; textures are commonly stretched in world, profiles, etc and need to be "squashed" during upload, not cropped here - if (LLSnapshotModel::SNAPSHOT_TEXTURE == getActiveSnapshotType(view)) - { - previewp->mKeepAspectRatio = false ; - return ; - } - - bool keep_aspect = false, enable_cb = false; - - if (0 == index) // current window size - { - enable_cb = false; - keep_aspect = true; - } - else if (-1 == index) // custom - { - enable_cb = true; - keep_aspect = gSavedSettings.getBOOL("KeepAspectForSnapshot"); - } - else // predefined resolution - { - enable_cb = false; - keep_aspect = false; - } - - view->impl->mAspectRatioCheckOff = !enable_cb; - - if (previewp) - { - previewp->mKeepAspectRatio = keep_aspect; - } -} - -// Show/hide upload progress indicators. -void LLFloaterSnapshotBase::ImplBase::setWorking(bool working) -{ - LLUICtrl* working_lbl = mFloater->getChild("working_lbl"); - working_lbl->setVisible(working); - mFloater->getChild("working_indicator")->setVisible(working); - - if (working) - { - const std::string panel_name = getActivePanel(mFloater, false)->getName(); - const std::string prefix = panel_name.substr(getSnapshotPanelPrefix().size()); - std::string progress_text = mFloater->getString(prefix + "_" + "progress_str"); - working_lbl->setValue(progress_text); - } - - // All controls should be disabled while posting. - mFloater->setCtrlsEnabled(!working); - LLPanelSnapshot* active_panel = getActivePanel(mFloater); - if (active_panel) - { - active_panel->enableControls(!working); - } -} - -//virtual -std::string LLFloaterSnapshot::Impl::getSnapshotPanelPrefix() -{ - return "panel_snapshot_"; -} - -// Show/hide upload status message. -// virtual -void LLFloaterSnapshot::Impl::setFinished(bool finished, bool ok, const std::string& msg) -{ - mFloater->setSuccessLabelPanelVisible(finished && ok); - mFloater->setFailureLabelPanelVisible(finished && !ok); - - if (finished) - { - LLUICtrl* finished_lbl = mFloater->getChild(ok ? "succeeded_lbl" : "failed_lbl"); - std::string result_text = mFloater->getString(msg + "_" + (ok ? "succeeded_str" : "failed_str")); - finished_lbl->setValue(result_text); - } -} - -// Apply a new resolution selected from the given combobox. -void LLFloaterSnapshot::Impl::updateResolution(LLUICtrl* ctrl, void* data, bool do_update) -{ - LLComboBox* combobox = (LLComboBox*)ctrl; - LLFloaterSnapshot *view = (LLFloaterSnapshot *)data; - - if (!view || !combobox) - { - llassert(view && combobox); - return; - } - - std::string sdstring = combobox->getSelectedValue(); - LLSD sdres; - std::stringstream sstream(sdstring); - LLSDSerialize::fromNotation(sdres, sstream, sdstring.size()); - - S32 width = sdres[0]; - S32 height = sdres[1]; - - LLSnapshotLivePreview* previewp = getPreviewView(); - if (previewp && combobox->getCurrentIndex() >= 0) - { - S32 original_width = 0 , original_height = 0 ; - previewp->getSize(original_width, original_height) ; - - if (gSavedSettings.getBOOL("RenderUIInSnapshot") || gSavedSettings.getBOOL("RenderHUDInSnapshot")) - { //clamp snapshot resolution to window size when showing UI or HUD in snapshot - width = llmin(width, gViewerWindow->getWindowWidthRaw()); - height = llmin(height, gViewerWindow->getWindowHeightRaw()); - } - - if (width == 0 || height == 0) - { - // take resolution from current window size - LL_DEBUGS() << "Setting preview res from window: " << gViewerWindow->getWindowWidthRaw() << "x" << gViewerWindow->getWindowHeightRaw() << LL_ENDL; - previewp->setSize(gViewerWindow->getWindowWidthRaw(), gViewerWindow->getWindowHeightRaw()); - } - else if (width == -1 || height == -1) - { - // load last custom value - S32 new_width = 0, new_height = 0; - LLPanelSnapshot* spanel = getActivePanel(view); - if (spanel) - { - LL_DEBUGS() << "Loading typed res from panel " << spanel->getName() << LL_ENDL; - new_width = spanel->getTypedPreviewWidth(); - new_height = spanel->getTypedPreviewHeight(); - - // Limit custom size for inventory snapshots to 512x512 px. - if (getActiveSnapshotType(view) == LLSnapshotModel::SNAPSHOT_TEXTURE) - { - new_width = llmin(new_width, MAX_TEXTURE_SIZE); - new_height = llmin(new_height, MAX_TEXTURE_SIZE); - } - } - else - { - LL_DEBUGS() << "No custom res chosen, setting preview res from window: " - << gViewerWindow->getWindowWidthRaw() << "x" << gViewerWindow->getWindowHeightRaw() << LL_ENDL; - new_width = gViewerWindow->getWindowWidthRaw(); - new_height = gViewerWindow->getWindowHeightRaw(); - } - - llassert(new_width > 0 && new_height > 0); - previewp->setSize(new_width, new_height); - } - else - { - // use the resolution from the selected pre-canned drop-down choice - LL_DEBUGS() << "Setting preview res selected from combo: " << width << "x" << height << LL_ENDL; - previewp->setSize(width, height); - } - - checkAspectRatio(view, width) ; - - previewp->getSize(width, height); - - // We use the height spinner here because we come here via the aspect ratio - // checkbox as well and we want height always changing to width by default. - // If we use the width spinner we would change width according to height by - // default, that is not what we want. - updateSpinners(view, previewp, width, height, !getHeightSpinner(view)->isDirty()); // may change width and height - - if(getWidthSpinner(view)->getValue().asInteger() != width || getHeightSpinner(view)->getValue().asInteger() != height) - { - getWidthSpinner(view)->setValue(width); - getHeightSpinner(view)->setValue(height); - if (getActiveSnapshotType(view) == LLSnapshotModel::SNAPSHOT_TEXTURE) - { - getWidthSpinner(view)->setIncrement(width >> 1); - getHeightSpinner(view)->setIncrement(height >> 1); - } - } - - if(original_width != width || original_height != height) - { - previewp->setSize(width, height); - - // hide old preview as the aspect ratio could be wrong - checkAutoSnapshot(previewp, false); - LL_DEBUGS() << "updating thumbnail" << LL_ENDL; - // Don't update immediately, give window chance to redraw - getPreviewView()->updateSnapshot(true, false, 1.f); - if(do_update) - { - LL_DEBUGS() << "Will update controls" << LL_ENDL; - updateControls(view); - } - } - } -} - -// static -void LLFloaterSnapshot::Impl::onCommitLayerTypes(LLUICtrl* ctrl, void*data) -{ - LLComboBox* combobox = (LLComboBox*)ctrl; - - LLFloaterSnapshot *view = (LLFloaterSnapshot *)data; - - if (view) - { - LLSnapshotLivePreview* previewp = view->getPreviewView(); - if (previewp) - { - previewp->setSnapshotBufferType((LLSnapshotModel::ESnapshotLayerType)combobox->getCurrentIndex()); - } - view->impl->checkAutoSnapshot(previewp, true); - previewp->updateSnapshot(true, true); - } -} - -void LLFloaterSnapshot::Impl::onImageQualityChange(LLFloaterSnapshotBase* view, S32 quality_val) -{ - LLSnapshotLivePreview* previewp = getPreviewView(); - if (previewp) - { - previewp->setSnapshotQuality(quality_val); - } -} - -void LLFloaterSnapshot::Impl::onImageFormatChange(LLFloaterSnapshotBase* view) -{ - if (view) - { - gSavedSettings.setS32("SnapshotFormat", getImageFormat(view)); - LL_DEBUGS() << "image format changed, updating snapshot" << LL_ENDL; - getPreviewView()->updateSnapshot(true); - updateControls(view); - } -} - -// Sets the named size combo to "custom" mode. -void LLFloaterSnapshot::Impl::comboSetCustom(LLFloaterSnapshotBase* floater, const std::string& comboname) -{ - LLComboBox* combo = floater->getChild(comboname); - combo->setCurrentByIndex(combo->getItemCount() - 1); // "custom" is always the last index - checkAspectRatio(floater, -1); // -1 means custom -} - -// Update supplied width and height according to the constrain proportions flag; limit them by max_val. -bool LLFloaterSnapshot::Impl::checkImageSize(LLSnapshotLivePreview* previewp, S32& width, S32& height, bool isWidthChanged, S32 max_value) -{ - S32 w = width ; - S32 h = height ; - - if(previewp && previewp->mKeepAspectRatio) - { - if(gViewerWindow->getWindowWidthRaw() < 1 || gViewerWindow->getWindowHeightRaw() < 1) - { - return false ; - } - - //aspect ratio of the current window - F32 aspect_ratio = (F32)gViewerWindow->getWindowWidthRaw() / gViewerWindow->getWindowHeightRaw() ; - - //change another value proportionally - if(isWidthChanged) - { - height = ll_round(width / aspect_ratio) ; - } - else - { - width = ll_round(height * aspect_ratio) ; - } - - //bound w/h by the max_value - if(width > max_value || height > max_value) - { - if(width > height) - { - width = max_value ; - height = (S32)(width / aspect_ratio) ; - } - else - { - height = max_value ; - width = (S32)(height * aspect_ratio) ; - } - } - } - - return (w != width || h != height) ; -} - -void LLFloaterSnapshot::Impl::setImageSizeSpinnersValues(LLFloaterSnapshotBase* view, S32 width, S32 height) -{ - getWidthSpinner(view)->forceSetValue(width); - getHeightSpinner(view)->forceSetValue(height); - if (getActiveSnapshotType(view) == LLSnapshotModel::SNAPSHOT_TEXTURE) - { - getWidthSpinner(view)->setIncrement(width >> 1); - getHeightSpinner(view)->setIncrement(height >> 1); - } -} - -void LLFloaterSnapshot::Impl::updateSpinners(LLFloaterSnapshotBase* view, LLSnapshotLivePreview* previewp, S32& width, S32& height, bool is_width_changed) -{ - getWidthSpinner(view)->resetDirty(); - getHeightSpinner(view)->resetDirty(); - if (checkImageSize(previewp, width, height, is_width_changed, previewp->getMaxImageSize())) - { - setImageSizeSpinnersValues(view, width, height); - } -} - -void LLFloaterSnapshot::Impl::applyCustomResolution(LLFloaterSnapshotBase* view, S32 w, S32 h) -{ - LL_DEBUGS() << "applyCustomResolution(" << w << ", " << h << ")" << LL_ENDL; - if (!view) return; - - LLSnapshotLivePreview* previewp = getPreviewView(); - if (previewp) - { - S32 curw,curh; - previewp->getSize(curw, curh); - - if (w != curw || h != curh) - { - //if to upload a snapshot, process spinner input in a special way. - previewp->setMaxImageSize((S32) getWidthSpinner(view)->getMaxValue()) ; - - previewp->setSize(w,h); - checkAutoSnapshot(previewp, false); - comboSetCustom(view, "profile_size_combo"); - comboSetCustom(view, "postcard_size_combo"); - comboSetCustom(view, "texture_size_combo"); - comboSetCustom(view, "local_size_combo"); - LL_DEBUGS() << "applied custom resolution, updating thumbnail" << LL_ENDL; - previewp->updateSnapshot(true); - } - } -} - -// static -void LLFloaterSnapshot::Impl::onSnapshotUploadFinished(LLFloaterSnapshotBase* floater, bool status) -{ - floater->impl->setStatus(STATUS_FINISHED, status, "profile"); -} - -// static -void LLFloaterSnapshot::Impl::onSendingPostcardFinished(LLFloaterSnapshotBase* floater, bool status) -{ - floater->impl->setStatus(STATUS_FINISHED, status, "postcard"); -} - -///---------------------------------------------------------------------------- -/// Class LLFloaterSnapshotBase -///---------------------------------------------------------------------------- - -// Default constructor -LLFloaterSnapshotBase::LLFloaterSnapshotBase(const LLSD& key) - : LLFloater(key), - mRefreshBtn(NULL), - mRefreshLabel(NULL), - mSucceessLblPanel(NULL), - mFailureLblPanel(NULL) -{ -} - -LLFloaterSnapshotBase::~LLFloaterSnapshotBase() -{ - if (impl->mPreviewHandle.get()) impl->mPreviewHandle.get()->die(); - - //unfreeze everything else - gSavedSettings.setBOOL("FreezeTime", false); - - if (impl->mLastToolset) - { - LLToolMgr::getInstance()->setCurrentToolset(impl->mLastToolset); - } - - delete impl; -} - -///---------------------------------------------------------------------------- -/// Class LLFloaterSnapshot -///---------------------------------------------------------------------------- - -// Default constructor -LLFloaterSnapshot::LLFloaterSnapshot(const LLSD& key) - : LLFloaterSnapshotBase(key) -{ - impl = new Impl(this); -} - -LLFloaterSnapshot::~LLFloaterSnapshot() -{ -} - -// virtual -bool LLFloaterSnapshot::postBuild() -{ - mRefreshBtn = getChild("new_snapshot_btn"); - childSetAction("new_snapshot_btn", ImplBase::onClickNewSnapshot, this); - mRefreshLabel = getChild("refresh_lbl"); - mSucceessLblPanel = getChild("succeeded_panel"); - mFailureLblPanel = getChild("failed_panel"); - - childSetCommitCallback("ui_check", ImplBase::onClickUICheck, this); - getChild("ui_check")->setValue(gSavedSettings.getBOOL("RenderUIInSnapshot")); - - childSetCommitCallback("hud_check", ImplBase::onClickHUDCheck, this); - getChild("hud_check")->setValue(gSavedSettings.getBOOL("RenderHUDInSnapshot")); - - ((Impl*)impl)->setAspectRatioCheckboxValue(this, gSavedSettings.getBOOL("KeepAspectForSnapshot")); - - childSetCommitCallback("layer_types", Impl::onCommitLayerTypes, this); - getChild("layer_types")->setValue("colors"); - getChildView("layer_types")->setEnabled(false); - - getChild("freeze_frame_check")->setValue(gSavedSettings.getBOOL("UseFreezeFrame")); - childSetCommitCallback("freeze_frame_check", ImplBase::onCommitFreezeFrame, this); - - getChild("auto_snapshot_check")->setValue(gSavedSettings.getBOOL("AutoSnapshot")); - childSetCommitCallback("auto_snapshot_check", ImplBase::onClickAutoSnap, this); - - getChild("no_post_check")->setValue(gSavedSettings.getBOOL("RenderSnapshotNoPost")); - childSetCommitCallback("no_post_check", ImplBase::onClickNoPost, this); - - getChild("retract_btn")->setCommitCallback(boost::bind(&LLFloaterSnapshot::onExtendFloater, this)); - getChild("extend_btn")->setCommitCallback(boost::bind(&LLFloaterSnapshot::onExtendFloater, this)); - - getChild("360_label")->setSoundFlags(LLView::MOUSE_UP); - getChild("360_label")->setShowCursorHand(false); - getChild("360_label")->setClickedCallback(boost::bind(&LLFloaterSnapshot::on360Snapshot, this)); - - // Filters - LLComboBox* filterbox = getChild("filters_combobox"); - std::vector filter_list = LLImageFiltersManager::getInstance()->getFiltersList(); - for (U32 i = 0; i < filter_list.size(); i++) - { - filterbox->add(filter_list[i]); - } - childSetCommitCallback("filters_combobox", ImplBase::onClickFilter, this); - - LLWebProfile::setImageUploadResultCallback(boost::bind(&Impl::onSnapshotUploadFinished, this, _1)); - LLPostCard::setPostResultCallback(boost::bind(&Impl::onSendingPostcardFinished, this, _1)); - - mThumbnailPlaceholder = getChild("thumbnail_placeholder"); - - // create preview window - LLRect full_screen_rect = getRootView()->getRect(); - LLSnapshotLivePreview::Params p; - p.rect(full_screen_rect); - LLSnapshotLivePreview* previewp = new LLSnapshotLivePreview(p); - LLView* parent_view = gSnapshotFloaterView->getParent(); - - parent_view->removeChild(gSnapshotFloaterView); - // make sure preview is below snapshot floater - parent_view->addChild(previewp); - parent_view->addChild(gSnapshotFloaterView); - - //move snapshot floater to special purpose snapshotfloaterview - gFloaterView->removeChild(this); - gSnapshotFloaterView->addChild(this); - - // Pre-select "Current Window" resolution. - getChild("profile_size_combo")->selectNthItem(0); - getChild("postcard_size_combo")->selectNthItem(0); - getChild("texture_size_combo")->selectNthItem(0); - getChild("local_size_combo")->selectNthItem(8); - getChild("local_format_combo")->selectNthItem(0); - - impl->mPreviewHandle = previewp->getHandle(); - previewp->setContainer(this); - impl->updateControls(this); - impl->setAdvanced(gSavedSettings.getBOOL("AdvanceSnapshot")); - impl->updateLayout(this); - - - previewp->setThumbnailPlaceholderRect(getThumbnailPlaceholderRect()); - - return true; -} - -// virtual -void LLFloaterSnapshotBase::draw() -{ - LLSnapshotLivePreview* previewp = getPreviewView(); - - if (previewp && (previewp->isSnapshotActive() || previewp->getThumbnailLock())) - { - // don't render snapshot window in snapshot, even if "show ui" is turned on - return; - } - - LLFloater::draw(); - - if (previewp && !isMinimized() && mThumbnailPlaceholder->getVisible()) - { - if(previewp->getThumbnailImage()) - { - bool working = impl->getStatus() == ImplBase::STATUS_WORKING; - const LLRect& thumbnail_rect = getThumbnailPlaceholderRect(); - const S32 thumbnail_w = previewp->getThumbnailWidth(); - const S32 thumbnail_h = previewp->getThumbnailHeight(); - - // calc preview offset within the preview rect - const S32 local_offset_x = (thumbnail_rect.getWidth() - thumbnail_w) / 2 ; - const S32 local_offset_y = (thumbnail_rect.getHeight() - thumbnail_h) / 2 ; // preview y pos within the preview rect - - // calc preview offset within the floater rect - S32 offset_x = thumbnail_rect.mLeft + local_offset_x; - S32 offset_y = thumbnail_rect.mBottom + local_offset_y; - - gGL.matrixMode(LLRender::MM_MODELVIEW); - // Apply floater transparency to the texture unless the floater is focused. - F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); - LLColor4 color = working ? LLColor4::grey4 : LLColor4::white; - gl_draw_scaled_image(offset_x, offset_y, - thumbnail_w, thumbnail_h, - previewp->getThumbnailImage(), color % alpha); - - previewp->drawPreviewRect(offset_x, offset_y) ; - - gGL.pushUIMatrix(); - LLUI::translate((F32) thumbnail_rect.mLeft, (F32) thumbnail_rect.mBottom); - mThumbnailPlaceholder->draw(); - gGL.popUIMatrix(); - } - } - impl->updateLayout(this); -} - -//virtual -void LLFloaterSnapshot::onOpen(const LLSD& key) -{ - LLSnapshotLivePreview* preview = getPreviewView(); - if(preview) - { - LL_DEBUGS() << "opened, updating snapshot" << LL_ENDL; - preview->setAllowFullScreenPreview(true); - preview->updateSnapshot(true); - } - focusFirstItem(false); - gSnapshotFloaterView->setEnabled(true); - gSnapshotFloaterView->setVisible(true); - gSnapshotFloaterView->adjustToFitScreen(this, false); - - impl->updateControls(this); - impl->setAdvanced(gSavedSettings.getBOOL("AdvanceSnapshot")); - impl->updateLayout(this); - - // Initialize default tab. - getChild("panel_container")->getCurrentPanel()->onOpen(LLSD()); -} - -void LLFloaterSnapshot::onExtendFloater() -{ - impl->setAdvanced(gSavedSettings.getBOOL("AdvanceSnapshot")); -} - -void LLFloaterSnapshot::on360Snapshot() -{ - LLFloaterReg::showInstance("360capture"); - closeFloater(); -} - -//virtual -void LLFloaterSnapshotBase::onClose(bool app_quitting) -{ - getParent()->setMouseOpaque(false); - - //unfreeze everything, hide fullscreen preview - LLSnapshotLivePreview* previewp = getPreviewView(); - if (previewp) - { - previewp->setAllowFullScreenPreview(false); - previewp->setVisible(false); - previewp->setEnabled(false); - } - - gSavedSettings.setBOOL("FreezeTime", false); - impl->mAvatarPauseHandles.clear(); - - if (impl->mLastToolset) - { - LLToolMgr::getInstance()->setCurrentToolset(impl->mLastToolset); - } -} - -// virtual -S32 LLFloaterSnapshotBase::notify(const LLSD& info) -{ - if (info.has("set-ready")) - { - impl->setStatus(ImplBase::STATUS_READY); - return 1; - } - - if (info.has("set-working")) - { - impl->setStatus(ImplBase::STATUS_WORKING); - return 1; - } - - if (info.has("set-finished")) - { - LLSD data = info["set-finished"]; - impl->setStatus(ImplBase::STATUS_FINISHED, data["ok"].asBoolean(), data["msg"].asString()); - return 1; - } - - if (info.has("snapshot-updating")) - { - // Disable the send/post/save buttons until snapshot is ready. - impl->updateControls(this); - return 1; - } - - if (info.has("snapshot-updated")) - { - // Enable the send/post/save buttons. - impl->updateControls(this); - // We've just done refresh. - impl->setNeedRefresh(false); - - // The refresh button is initially hidden. We show it after the first update, - // i.e. when preview appears. - if (mRefreshBtn && !mRefreshBtn->getVisible()) - { - mRefreshBtn->setVisible(true); - } - return 1; - } - - return 0; -} - -// virtual -S32 LLFloaterSnapshot::notify(const LLSD& info) -{ - bool res = LLFloaterSnapshotBase::notify(info); - if (res) - return res; - // A child panel wants to change snapshot resolution. - if (info.has("combo-res-change")) - { - std::string combo_name = info["combo-res-change"]["control-name"].asString(); - ((Impl*)impl)->updateResolution(getChild(combo_name), this); - return 1; - } - - if (info.has("custom-res-change")) - { - LLSD res = info["custom-res-change"]; - ((Impl*)impl)->applyCustomResolution(this, res["w"].asInteger(), res["h"].asInteger()); - return 1; - } - - if (info.has("keep-aspect-change")) - { - ((Impl*)impl)->applyKeepAspectCheck(this, info["keep-aspect-change"].asBoolean()); - return 1; - } - - if (info.has("image-quality-change")) - { - ((Impl*)impl)->onImageQualityChange(this, info["image-quality-change"].asInteger()); - return 1; - } - - if (info.has("image-format-change")) - { - ((Impl*)impl)->onImageFormatChange(this); - return 1; - } - - return 0; -} - -bool LLFloaterSnapshot::isWaitingState() -{ - return (impl->getStatus() == ImplBase::STATUS_WORKING); -} - -bool LLFloaterSnapshotBase::ImplBase::updatePreviewList(bool initialized) -{ - if (!initialized) - return false; - - bool changed = false; - LL_DEBUGS() << "npreviews: " << LLSnapshotLivePreview::sList.size() << LL_ENDL; - for (std::set::iterator iter = LLSnapshotLivePreview::sList.begin(); - iter != LLSnapshotLivePreview::sList.end(); ++iter) - { - changed |= LLSnapshotLivePreview::onIdle(*iter); - } - return changed; -} - - -void LLFloaterSnapshotBase::ImplBase::updateLivePreview() -{ - if (ImplBase::updatePreviewList(true) && mFloater) - { - LL_DEBUGS() << "changed" << LL_ENDL; - updateControls(mFloater); - } -} - -//static -void LLFloaterSnapshot::update() -{ - LLFloaterSnapshot* inst = findInstance(); - if (inst != NULL) - { - inst->impl->updateLivePreview(); - } - else - { - ImplBase::updatePreviewList(false); - } -} - -// static -LLFloaterSnapshot* LLFloaterSnapshot::findInstance() -{ - return LLFloaterReg::findTypedInstance("snapshot"); -} - -// static -LLFloaterSnapshot* LLFloaterSnapshot::getInstance() -{ - return LLFloaterReg::getTypedInstance("snapshot"); -} - -// virtual -void LLFloaterSnapshot::saveTexture() -{ - LL_DEBUGS() << "saveTexture" << LL_ENDL; - - LLSnapshotLivePreview* previewp = getPreviewView(); - if (!previewp) - { - llassert(previewp != NULL); - return; - } - - previewp->saveTexture(); -} - -void LLFloaterSnapshot::saveLocal(const snapshot_saved_signal_t::slot_type& success_cb, const snapshot_saved_signal_t::slot_type& failure_cb) -{ - LL_DEBUGS() << "saveLocal" << LL_ENDL; - LLSnapshotLivePreview* previewp = getPreviewView(); - llassert(previewp != NULL); - if (previewp) - { - previewp->saveLocal(success_cb, failure_cb); - } -} - -void LLFloaterSnapshotBase::postSave() -{ - impl->updateControls(this); - impl->setStatus(ImplBase::STATUS_WORKING); -} - -// virtual -void LLFloaterSnapshotBase::postPanelSwitch() -{ - impl->updateControls(this); - - // Remove the success/failure indicator whenever user presses a snapshot option button. - impl->setStatus(ImplBase::STATUS_READY); -} - -void LLFloaterSnapshotBase::inventorySaveFailed() -{ - impl->updateControls(this); - impl->setStatus(ImplBase::STATUS_FINISHED, false, "inventory"); -} - -LLPointer LLFloaterSnapshotBase::getImageData() -{ - // FIXME: May not work for textures. - - LLSnapshotLivePreview* previewp = getPreviewView(); - if (!previewp) - { - llassert(previewp != NULL); - return NULL; - } - - LLPointer img = previewp->getFormattedImage(); - if (!img.get()) - { - LL_WARNS() << "Empty snapshot image data" << LL_ENDL; - llassert(img.get() != NULL); - } - - return img; -} - -const LLVector3d& LLFloaterSnapshotBase::getPosTakenGlobal() -{ - LLSnapshotLivePreview* previewp = getPreviewView(); - if (!previewp) - { - llassert(previewp != NULL); - return LLVector3d::zero; - } - - return previewp->getPosTakenGlobal(); -} - -// static -void LLFloaterSnapshot::setAgentEmail(const std::string& email) -{ - LLFloaterSnapshot* instance = findInstance(); - if (instance) - { - LLSideTrayPanelContainer* panel_container = instance->getChild("panel_container"); - LLPanel* postcard_panel = panel_container->getPanelByName("panel_snapshot_postcard"); - postcard_panel->notify(LLSD().with("agent-email", email)); - } -} - -///---------------------------------------------------------------------------- -/// Class LLSnapshotFloaterView -///---------------------------------------------------------------------------- - -LLSnapshotFloaterView::LLSnapshotFloaterView (const Params& p) : LLFloaterView (p) -{ -} - -LLSnapshotFloaterView::~LLSnapshotFloaterView() -{ -} - -// virtual -bool LLSnapshotFloaterView::handleKey(KEY key, MASK mask, bool called_from_parent) -{ - // use default handler when not in freeze-frame mode - if(!gSavedSettings.getBOOL("FreezeTime")) - { - return LLFloaterView::handleKey(key, mask, called_from_parent); - } - - if (called_from_parent) - { - // pass all keystrokes down - LLFloaterView::handleKey(key, mask, called_from_parent); - } - else - { - // bounce keystrokes back down - LLFloaterView::handleKey(key, mask, true); - } - return true; -} - -// virtual -bool LLSnapshotFloaterView::handleMouseDown(S32 x, S32 y, MASK mask) -{ - // use default handler when not in freeze-frame mode - if(!gSavedSettings.getBOOL("FreezeTime")) - { - return LLFloaterView::handleMouseDown(x, y, mask); - } - // give floater a change to handle mouse, else camera tool - if (childrenHandleMouseDown(x, y, mask) == NULL) - { - LLToolMgr::getInstance()->getCurrentTool()->handleMouseDown( x, y, mask ); - } - return true; -} - -// virtual -bool LLSnapshotFloaterView::handleMouseUp(S32 x, S32 y, MASK mask) -{ - // use default handler when not in freeze-frame mode - if(!gSavedSettings.getBOOL("FreezeTime")) - { - return LLFloaterView::handleMouseUp(x, y, mask); - } - // give floater a change to handle mouse, else camera tool - if (childrenHandleMouseUp(x, y, mask) == NULL) - { - LLToolMgr::getInstance()->getCurrentTool()->handleMouseUp( x, y, mask ); - } - return true; -} - -// virtual -bool LLSnapshotFloaterView::handleHover(S32 x, S32 y, MASK mask) -{ - // use default handler when not in freeze-frame mode - if(!gSavedSettings.getBOOL("FreezeTime")) - { - return LLFloaterView::handleHover(x, y, mask); - } - // give floater a change to handle mouse, else camera tool - if (childrenHandleHover(x, y, mask) == NULL) - { - LLToolMgr::getInstance()->getCurrentTool()->handleHover( x, y, mask ); - } - return true; -} +/** + * @file llfloatersnapshot.cpp + * @brief Snapshot preview window, allowing saving, e-mailing, etc. + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2016, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatersnapshot.h" + +#include "llfloaterreg.h" +#include "llimagefiltersmanager.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "llpostcard.h" +#include "llresmgr.h" // LLLocale +#include "llsdserialize.h" +#include "llsidetraypanelcontainer.h" +#include "llsnapshotlivepreview.h" +#include "llspinctrl.h" +#include "llviewercontrol.h" +#include "lltoolfocus.h" +#include "lltoolmgr.h" +#include "llwebprofile.h" + +///---------------------------------------------------------------------------- +/// Local function declarations, constants, enums, and typedefs +///---------------------------------------------------------------------------- +LLSnapshotFloaterView* gSnapshotFloaterView = NULL; + +const F32 AUTO_SNAPSHOT_TIME_DELAY = 1.f; + +const S32 MAX_POSTCARD_DATASIZE = 1572864; // 1.5 megabyte, similar to simulator limit +const S32 MAX_TEXTURE_SIZE = 512 ; //max upload texture size 512 * 512 + +static LLDefaultChildRegistry::Register r("snapshot_floater_view"); + +// virtual +LLPanelSnapshot* LLFloaterSnapshot::Impl::getActivePanel(LLFloaterSnapshotBase* floater, bool ok_if_not_found) +{ + LLSideTrayPanelContainer* panel_container = floater->getChild("panel_container"); + LLPanelSnapshot* active_panel = dynamic_cast(panel_container->getCurrentPanel()); + if (!ok_if_not_found) + { + llassert_always(active_panel != NULL); + } + return active_panel; +} + +// virtual +LLSnapshotModel::ESnapshotType LLFloaterSnapshotBase::ImplBase::getActiveSnapshotType(LLFloaterSnapshotBase* floater) +{ + LLPanelSnapshot* spanel = getActivePanel(floater); + + //return type; + if (spanel) + { + return spanel->getSnapshotType(); + } + return LLSnapshotModel::SNAPSHOT_WEB; +} + +// virtual +LLSnapshotModel::ESnapshotFormat LLFloaterSnapshot::Impl::getImageFormat(LLFloaterSnapshotBase* floater) +{ + LLPanelSnapshot* active_panel = getActivePanel(floater); + // FIXME: if the default is not PNG, profile uploads may fail. + return active_panel ? active_panel->getImageFormat() : LLSnapshotModel::SNAPSHOT_FORMAT_PNG; +} + +LLSpinCtrl* LLFloaterSnapshot::Impl::getWidthSpinner(LLFloaterSnapshotBase* floater) +{ + LLPanelSnapshot* active_panel = getActivePanel(floater); + return active_panel ? active_panel->getWidthSpinner() : floater->getChild("snapshot_width"); +} + +LLSpinCtrl* LLFloaterSnapshot::Impl::getHeightSpinner(LLFloaterSnapshotBase* floater) +{ + LLPanelSnapshot* active_panel = getActivePanel(floater); + return active_panel ? active_panel->getHeightSpinner() : floater->getChild("snapshot_height"); +} + +void LLFloaterSnapshot::Impl::enableAspectRatioCheckbox(LLFloaterSnapshotBase* floater, bool enable) +{ + LLPanelSnapshot* active_panel = getActivePanel(floater); + if (active_panel) + { + active_panel->enableAspectRatioCheckbox(enable); + } +} + +void LLFloaterSnapshot::Impl::setAspectRatioCheckboxValue(LLFloaterSnapshotBase* floater, bool checked) +{ + LLPanelSnapshot* active_panel = getActivePanel(floater); + if (active_panel) + { + active_panel->getChild(active_panel->getAspectRatioCBName())->setValue(checked); + } +} + +LLSnapshotLivePreview* LLFloaterSnapshotBase::getPreviewView() +{ + return impl->getPreviewView(); +} + +LLSnapshotLivePreview* LLFloaterSnapshotBase::ImplBase::getPreviewView() +{ + LLSnapshotLivePreview* previewp = (LLSnapshotLivePreview*)mPreviewHandle.get(); + return previewp; +} + +// virtual +LLSnapshotModel::ESnapshotLayerType LLFloaterSnapshot::Impl::getLayerType(LLFloaterSnapshotBase* floater) +{ + LLSnapshotModel::ESnapshotLayerType type = LLSnapshotModel::SNAPSHOT_TYPE_COLOR; + LLSD value = floater->getChild("layer_types")->getValue(); + const std::string id = value.asString(); + if (id == "colors") + type = LLSnapshotModel::SNAPSHOT_TYPE_COLOR; + else if (id == "depth") + type = LLSnapshotModel::SNAPSHOT_TYPE_DEPTH; + return type; +} + +void LLFloaterSnapshot::Impl::setResolution(LLFloaterSnapshotBase* floater, const std::string& comboname) +{ + LLComboBox* combo = floater->getChild(comboname); + combo->setVisible(true); + updateResolution(combo, floater, false); // to sync spinners with combo +} + +//virtual +void LLFloaterSnapshotBase::ImplBase::updateLayout(LLFloaterSnapshotBase* floaterp) +{ + LLSnapshotLivePreview* previewp = getPreviewView(); + + //BD - Automatically calculate the size of our snapshot window to enlarge + // the snapshot preview to its maximum size, this is especially helpfull + // for pretty much every aspect ratio other than 1:1. + F32 panel_width = 400.f * gViewerWindow->getWorldViewAspectRatio(); + + //BD - Make sure we clamp at 700 here because 700 would be for 16:9 which we + // consider the maximum. Everything bigger will be clamped and will have + // a slightly smaller preview window which most likely won't fill up the + // whole snapshot floater as it should. + if(panel_width > 700.f) + { + panel_width = 700.f; + } + + S32 floater_width = 224.f; + if(mAdvanced) + { + floater_width = floater_width + panel_width; + } + + LLUICtrl* thumbnail_placeholder = floaterp->getChild("thumbnail_placeholder"); + thumbnail_placeholder->setVisible(mAdvanced); + + floaterp->getChild("image_res_text")->setVisible(mAdvanced); + floaterp->getChild("file_size_label")->setVisible(mAdvanced); + if (floaterp->hasChild("360_label", true)) + { + floaterp->getChild("360_label")->setVisible(mAdvanced); + } + if (!mSkipReshaping) + { + thumbnail_placeholder->reshape(panel_width, thumbnail_placeholder->getRect().getHeight()); + if (!floaterp->isMinimized()) + { + floaterp->reshape(floater_width, floaterp->getRect().getHeight()); + } + } + + bool use_freeze_frame = floaterp->getChild("freeze_frame_check")->getValue().asBoolean(); + + if (use_freeze_frame) + { + // stop all mouse events at fullscreen preview layer + floaterp->getParent()->setMouseOpaque(true); + + // shrink to smaller layout + // *TODO: unneeded? + floaterp->reshape(floaterp->getRect().getWidth(), floaterp->getRect().getHeight()); + + // can see and interact with fullscreen preview now + if (previewp) + { + previewp->setVisible(true); + previewp->setEnabled(true); + } + + //RN: freeze all avatars + LLCharacter* avatarp; + for (std::vector::iterator iter = LLCharacter::sInstances.begin(); + iter != LLCharacter::sInstances.end(); ++iter) + { + avatarp = *iter; + floaterp->impl->mAvatarPauseHandles.push_back(avatarp->requestPause()); + } + + // freeze everything else + gSavedSettings.setBOOL("FreezeTime", true); + + if (LLToolMgr::getInstance()->getCurrentToolset() != gCameraToolset) + { + floaterp->impl->mLastToolset = LLToolMgr::getInstance()->getCurrentToolset(); + LLToolMgr::getInstance()->setCurrentToolset(gCameraToolset); + } + } + else // turning off freeze frame mode + { + floaterp->getParent()->setMouseOpaque(false); + // *TODO: unneeded? + floaterp->reshape(floaterp->getRect().getWidth(), floaterp->getRect().getHeight()); + if (previewp) + { + previewp->setVisible(false); + previewp->setEnabled(false); + } + + //RN: thaw all avatars + floaterp->impl->mAvatarPauseHandles.clear(); + + // thaw everything else + gSavedSettings.setBOOL("FreezeTime", false); + + // restore last tool (e.g. pie menu, etc) + if (floaterp->impl->mLastToolset) + { + LLToolMgr::getInstance()->setCurrentToolset(floaterp->impl->mLastToolset); + } + } +} + +// This is the main function that keeps all the GUI controls in sync with the saved settings. +// It should be called anytime a setting is changed that could affect the controls. +// No other methods should be changing any of the controls directly except for helpers called by this method. +// The basic pattern for programmatically changing the GUI settings is to first set the +// appropriate saved settings and then call this method to sync the GUI with them. +// FIXME: The above comment seems obsolete now. +// virtual +void LLFloaterSnapshot::Impl::updateControls(LLFloaterSnapshotBase* floater) +{ + LLSnapshotModel::ESnapshotType shot_type = getActiveSnapshotType(floater); + LLSnapshotModel::ESnapshotFormat shot_format = (LLSnapshotModel::ESnapshotFormat)gSavedSettings.getS32("SnapshotFormat"); + LLSnapshotModel::ESnapshotLayerType layer_type = getLayerType(floater); + + floater->getChild("local_format_combo")->selectNthItem(gSavedSettings.getS32("SnapshotFormat")); + floater->getChildView("layer_types")->setEnabled(shot_type == LLSnapshotModel::SNAPSHOT_LOCAL); + + LLPanelSnapshot* active_panel = getActivePanel(floater); + if (active_panel) + { + LLSpinCtrl* width_ctrl = getWidthSpinner(floater); + LLSpinCtrl* height_ctrl = getHeightSpinner(floater); + + // Initialize spinners. + if (width_ctrl->getValue().asInteger() == 0) + { + S32 w = gViewerWindow->getWindowWidthRaw(); + LL_DEBUGS() << "Initializing width spinner (" << width_ctrl->getName() << "): " << w << LL_ENDL; + width_ctrl->setValue(w); + if (getActiveSnapshotType(floater) == LLSnapshotModel::SNAPSHOT_TEXTURE) + { + width_ctrl->setIncrement(w >> 1); + } + } + if (height_ctrl->getValue().asInteger() == 0) + { + S32 h = gViewerWindow->getWindowHeightRaw(); + LL_DEBUGS() << "Initializing height spinner (" << height_ctrl->getName() << "): " << h << LL_ENDL; + height_ctrl->setValue(h); + if (getActiveSnapshotType(floater) == LLSnapshotModel::SNAPSHOT_TEXTURE) + { + height_ctrl->setIncrement(h >> 1); + } + } + + // Clamp snapshot resolution to window size when showing UI or HUD in snapshot. + if (gSavedSettings.getBOOL("RenderUIInSnapshot") || gSavedSettings.getBOOL("RenderHUDInSnapshot")) + { + S32 width = gViewerWindow->getWindowWidthRaw(); + S32 height = gViewerWindow->getWindowHeightRaw(); + + width_ctrl->setMaxValue(width); + + height_ctrl->setMaxValue(height); + + if (width_ctrl->getValue().asInteger() > width) + { + width_ctrl->forceSetValue(width); + } + if (height_ctrl->getValue().asInteger() > height) + { + height_ctrl->forceSetValue(height); + } + } + else + { + width_ctrl->setMaxValue(MAX_SNAPSHOT_IMAGE_SIZE); + height_ctrl->setMaxValue(MAX_SNAPSHOT_IMAGE_SIZE); + } + } + + LLSnapshotLivePreview* previewp = getPreviewView(); + bool got_bytes = previewp && previewp->getDataSize() > 0; + bool got_snap = previewp && previewp->getSnapshotUpToDate(); + + // *TODO: Separate maximum size for Web images from postcards + LL_DEBUGS() << "Is snapshot up-to-date? " << got_snap << LL_ENDL; + + LLLocale locale(LLLocale::USER_LOCALE); + std::string bytes_string; + if (got_snap) + { + LLResMgr::getInstance()->getIntegerString(bytes_string, (previewp->getDataSize()) >> 10 ); + } + + // Update displayed image resolution. + LLTextBox* image_res_tb = floater->getChild("image_res_text"); + image_res_tb->setVisible(got_snap); + if (got_snap) + { + image_res_tb->setTextArg("[WIDTH]", llformat("%d", previewp->getEncodedImageWidth())); + image_res_tb->setTextArg("[HEIGHT]", llformat("%d", previewp->getEncodedImageHeight())); + } + + floater->getChild("file_size_label")->setTextArg("[SIZE]", got_snap ? bytes_string : floater->getString("unknown")); + floater->getChild("file_size_label")->setColor( + shot_type == LLSnapshotModel::SNAPSHOT_POSTCARD + && got_bytes + && previewp->getDataSize() > MAX_POSTCARD_DATASIZE ? LLUIColor(LLColor4::red) : LLUIColorTable::instance().getColor( "LabelTextColor" )); + + // Update the width and height spinners based on the corresponding resolution combos. (?) + switch(shot_type) + { + case LLSnapshotModel::SNAPSHOT_WEB: + layer_type = LLSnapshotModel::SNAPSHOT_TYPE_COLOR; + floater->getChild("layer_types")->setValue("colors"); + setResolution(floater, "profile_size_combo"); + break; + case LLSnapshotModel::SNAPSHOT_POSTCARD: + layer_type = LLSnapshotModel::SNAPSHOT_TYPE_COLOR; + floater->getChild("layer_types")->setValue("colors"); + setResolution(floater, "postcard_size_combo"); + break; + case LLSnapshotModel::SNAPSHOT_TEXTURE: + layer_type = LLSnapshotModel::SNAPSHOT_TYPE_COLOR; + floater->getChild("layer_types")->setValue("colors"); + setResolution(floater, "texture_size_combo"); + break; + case LLSnapshotModel::SNAPSHOT_LOCAL: + setResolution(floater, "local_size_combo"); + break; + default: + break; + } + setAspectRatioCheckboxValue(floater, !floater->impl->mAspectRatioCheckOff && gSavedSettings.getBOOL("KeepAspectForSnapshot")); + + if (previewp) + { + previewp->setSnapshotType(shot_type); + previewp->setSnapshotFormat(shot_format); + previewp->setSnapshotBufferType(layer_type); + } + + LLPanelSnapshot* current_panel = Impl::getActivePanel(floater); + if (current_panel) + { + LLSD info; + info["have-snapshot"] = got_snap; + current_panel->updateControls(info); + } + LL_DEBUGS() << "finished updating controls" << LL_ENDL; +} + +//virtual +void LLFloaterSnapshotBase::ImplBase::setStatus(EStatus status, bool ok, const std::string& msg) +{ + switch (status) + { + case STATUS_READY: + setWorking(false); + setFinished(false); + break; + case STATUS_WORKING: + setWorking(true); + setFinished(false); + break; + case STATUS_FINISHED: + setWorking(false); + setFinished(true, ok, msg); + break; + } + + mStatus = status; +} + +// virtual +void LLFloaterSnapshotBase::ImplBase::setNeedRefresh(bool need) +{ + if (!mFloater) return; + + // Don't display the "Refresh to save" message if we're in auto-refresh mode. + if (gSavedSettings.getBOOL("AutoSnapshot")) + { + need = false; + } + + mFloater->setRefreshLabelVisible(need); + mNeedRefresh = need; +} + +// virtual +void LLFloaterSnapshotBase::ImplBase::checkAutoSnapshot(LLSnapshotLivePreview* previewp, bool update_thumbnail) +{ + if (previewp) + { + bool autosnap = gSavedSettings.getBOOL("AutoSnapshot"); + LL_DEBUGS() << "updating " << (autosnap ? "snapshot" : "thumbnail") << LL_ENDL; + previewp->updateSnapshot(autosnap, update_thumbnail, autosnap ? AUTO_SNAPSHOT_TIME_DELAY : 0.f); + } +} + +// static +void LLFloaterSnapshotBase::ImplBase::onClickNewSnapshot(void* data) +{ + LLFloaterSnapshotBase* floater = (LLFloaterSnapshotBase *)data; + LLSnapshotLivePreview* previewp = floater->getPreviewView(); + if (previewp) + { + floater->impl->setStatus(ImplBase::STATUS_READY); + LL_DEBUGS() << "updating snapshot" << LL_ENDL; + previewp->mForceUpdateSnapshot = true; + } +} + +// static +void LLFloaterSnapshotBase::ImplBase::onClickAutoSnap(LLUICtrl *ctrl, void* data) +{ + LLCheckBoxCtrl *check = (LLCheckBoxCtrl *)ctrl; + gSavedSettings.setBOOL( "AutoSnapshot", check->get() ); + + LLFloaterSnapshotBase *view = (LLFloaterSnapshotBase *)data; + if (view) + { + view->impl->checkAutoSnapshot(view->getPreviewView()); + view->impl->updateControls(view); + } +} + +// static +void LLFloaterSnapshotBase::ImplBase::onClickNoPost(LLUICtrl *ctrl, void* data) +{ + bool no_post = ((LLCheckBoxCtrl*)ctrl)->get(); + gSavedSettings.setBOOL("RenderSnapshotNoPost", no_post); + + LLFloaterSnapshotBase* view = (LLFloaterSnapshotBase*)data; + view->getPreviewView()->updateSnapshot(true, true); + view->impl->updateControls(view); +} + +// static +void LLFloaterSnapshotBase::ImplBase::onClickFilter(LLUICtrl *ctrl, void* data) +{ + LLFloaterSnapshotBase *view = (LLFloaterSnapshotBase *)data; + if (view) + { + view->impl->updateControls(view); + LLSnapshotLivePreview* previewp = view->getPreviewView(); + if (previewp) + { + view->impl->checkAutoSnapshot(previewp); + // Note : index 0 of the filter drop down is assumed to be "No filter" in whichever locale + LLComboBox* filterbox = static_cast(view->getChild("filters_combobox")); + std::string filter_name = (filterbox->getCurrentIndex() ? filterbox->getSimple() : ""); + previewp->setFilter(filter_name); + previewp->updateSnapshot(true); + } + } +} + +// static +void LLFloaterSnapshotBase::ImplBase::onClickUICheck(LLUICtrl *ctrl, void* data) +{ + LLCheckBoxCtrl *check = (LLCheckBoxCtrl *)ctrl; + gSavedSettings.setBOOL( "RenderUIInSnapshot", check->get() ); + + LLFloaterSnapshot *view = (LLFloaterSnapshot *)data; + if (view) + { + LLSnapshotLivePreview* previewp = view->getPreviewView(); + if(previewp) + { + previewp->updateSnapshot(true, true); + } + view->impl->updateControls(view); + } +} + +// static +void LLFloaterSnapshotBase::ImplBase::onClickHUDCheck(LLUICtrl *ctrl, void* data) +{ + LLCheckBoxCtrl *check = (LLCheckBoxCtrl *)ctrl; + gSavedSettings.setBOOL( "RenderHUDInSnapshot", check->get() ); + + LLFloaterSnapshot *view = (LLFloaterSnapshot *)data; + if (view) + { + LLSnapshotLivePreview* previewp = view->getPreviewView(); + if(previewp) + { + previewp->updateSnapshot(true, true); + } + view->impl->updateControls(view); + } +} + +void LLFloaterSnapshot::Impl::applyKeepAspectCheck(LLFloaterSnapshotBase* view, bool checked) +{ + gSavedSettings.setBOOL("KeepAspectForSnapshot", checked); + + if (view) + { + LLPanelSnapshot* active_panel = getActivePanel(view); + if (checked && active_panel) + { + LLComboBox* combo = view->getChild(active_panel->getImageSizeComboName()); + combo->setCurrentByIndex(combo->getItemCount() - 1); // "custom" is always the last index + } + + LLSnapshotLivePreview* previewp = getPreviewView() ; + if(previewp) + { + previewp->mKeepAspectRatio = gSavedSettings.getBOOL("KeepAspectForSnapshot") ; + + S32 w, h ; + previewp->getSize(w, h) ; + updateSpinners(view, previewp, w, h, true); // may change w and h + + LL_DEBUGS() << "updating thumbnail" << LL_ENDL; + previewp->setSize(w, h) ; + previewp->updateSnapshot(true); + checkAutoSnapshot(previewp, true); + } + } +} + +// static +void LLFloaterSnapshotBase::ImplBase::onCommitFreezeFrame(LLUICtrl* ctrl, void* data) +{ + LLCheckBoxCtrl* check_box = (LLCheckBoxCtrl*)ctrl; + LLFloaterSnapshotBase *view = (LLFloaterSnapshotBase *)data; + LLSnapshotLivePreview* previewp = view->getPreviewView(); + + if (!view || !check_box || !previewp) + { + return; + } + + gSavedSettings.setBOOL("UseFreezeFrame", check_box->get()); + + if (check_box->get()) + { + previewp->prepareFreezeFrame(); + } + + view->impl->updateLayout(view); +} + +void LLFloaterSnapshot::Impl::checkAspectRatio(LLFloaterSnapshotBase *view, S32 index) +{ + LLSnapshotLivePreview *previewp = getPreviewView() ; + + // Don't round texture sizes; textures are commonly stretched in world, profiles, etc and need to be "squashed" during upload, not cropped here + if (LLSnapshotModel::SNAPSHOT_TEXTURE == getActiveSnapshotType(view)) + { + previewp->mKeepAspectRatio = false ; + return ; + } + + bool keep_aspect = false, enable_cb = false; + + if (0 == index) // current window size + { + enable_cb = false; + keep_aspect = true; + } + else if (-1 == index) // custom + { + enable_cb = true; + keep_aspect = gSavedSettings.getBOOL("KeepAspectForSnapshot"); + } + else // predefined resolution + { + enable_cb = false; + keep_aspect = false; + } + + view->impl->mAspectRatioCheckOff = !enable_cb; + + if (previewp) + { + previewp->mKeepAspectRatio = keep_aspect; + } +} + +// Show/hide upload progress indicators. +void LLFloaterSnapshotBase::ImplBase::setWorking(bool working) +{ + LLUICtrl* working_lbl = mFloater->getChild("working_lbl"); + working_lbl->setVisible(working); + mFloater->getChild("working_indicator")->setVisible(working); + + if (working) + { + const std::string panel_name = getActivePanel(mFloater, false)->getName(); + const std::string prefix = panel_name.substr(getSnapshotPanelPrefix().size()); + std::string progress_text = mFloater->getString(prefix + "_" + "progress_str"); + working_lbl->setValue(progress_text); + } + + // All controls should be disabled while posting. + mFloater->setCtrlsEnabled(!working); + LLPanelSnapshot* active_panel = getActivePanel(mFloater); + if (active_panel) + { + active_panel->enableControls(!working); + } +} + +//virtual +std::string LLFloaterSnapshot::Impl::getSnapshotPanelPrefix() +{ + return "panel_snapshot_"; +} + +// Show/hide upload status message. +// virtual +void LLFloaterSnapshot::Impl::setFinished(bool finished, bool ok, const std::string& msg) +{ + mFloater->setSuccessLabelPanelVisible(finished && ok); + mFloater->setFailureLabelPanelVisible(finished && !ok); + + if (finished) + { + LLUICtrl* finished_lbl = mFloater->getChild(ok ? "succeeded_lbl" : "failed_lbl"); + std::string result_text = mFloater->getString(msg + "_" + (ok ? "succeeded_str" : "failed_str")); + finished_lbl->setValue(result_text); + } +} + +// Apply a new resolution selected from the given combobox. +void LLFloaterSnapshot::Impl::updateResolution(LLUICtrl* ctrl, void* data, bool do_update) +{ + LLComboBox* combobox = (LLComboBox*)ctrl; + LLFloaterSnapshot *view = (LLFloaterSnapshot *)data; + + if (!view || !combobox) + { + llassert(view && combobox); + return; + } + + std::string sdstring = combobox->getSelectedValue(); + LLSD sdres; + std::stringstream sstream(sdstring); + LLSDSerialize::fromNotation(sdres, sstream, sdstring.size()); + + S32 width = sdres[0]; + S32 height = sdres[1]; + + LLSnapshotLivePreview* previewp = getPreviewView(); + if (previewp && combobox->getCurrentIndex() >= 0) + { + S32 original_width = 0 , original_height = 0 ; + previewp->getSize(original_width, original_height) ; + + if (gSavedSettings.getBOOL("RenderUIInSnapshot") || gSavedSettings.getBOOL("RenderHUDInSnapshot")) + { //clamp snapshot resolution to window size when showing UI or HUD in snapshot + width = llmin(width, gViewerWindow->getWindowWidthRaw()); + height = llmin(height, gViewerWindow->getWindowHeightRaw()); + } + + if (width == 0 || height == 0) + { + // take resolution from current window size + LL_DEBUGS() << "Setting preview res from window: " << gViewerWindow->getWindowWidthRaw() << "x" << gViewerWindow->getWindowHeightRaw() << LL_ENDL; + previewp->setSize(gViewerWindow->getWindowWidthRaw(), gViewerWindow->getWindowHeightRaw()); + } + else if (width == -1 || height == -1) + { + // load last custom value + S32 new_width = 0, new_height = 0; + LLPanelSnapshot* spanel = getActivePanel(view); + if (spanel) + { + LL_DEBUGS() << "Loading typed res from panel " << spanel->getName() << LL_ENDL; + new_width = spanel->getTypedPreviewWidth(); + new_height = spanel->getTypedPreviewHeight(); + + // Limit custom size for inventory snapshots to 512x512 px. + if (getActiveSnapshotType(view) == LLSnapshotModel::SNAPSHOT_TEXTURE) + { + new_width = llmin(new_width, MAX_TEXTURE_SIZE); + new_height = llmin(new_height, MAX_TEXTURE_SIZE); + } + } + else + { + LL_DEBUGS() << "No custom res chosen, setting preview res from window: " + << gViewerWindow->getWindowWidthRaw() << "x" << gViewerWindow->getWindowHeightRaw() << LL_ENDL; + new_width = gViewerWindow->getWindowWidthRaw(); + new_height = gViewerWindow->getWindowHeightRaw(); + } + + llassert(new_width > 0 && new_height > 0); + previewp->setSize(new_width, new_height); + } + else + { + // use the resolution from the selected pre-canned drop-down choice + LL_DEBUGS() << "Setting preview res selected from combo: " << width << "x" << height << LL_ENDL; + previewp->setSize(width, height); + } + + checkAspectRatio(view, width) ; + + previewp->getSize(width, height); + + // We use the height spinner here because we come here via the aspect ratio + // checkbox as well and we want height always changing to width by default. + // If we use the width spinner we would change width according to height by + // default, that is not what we want. + updateSpinners(view, previewp, width, height, !getHeightSpinner(view)->isDirty()); // may change width and height + + if(getWidthSpinner(view)->getValue().asInteger() != width || getHeightSpinner(view)->getValue().asInteger() != height) + { + getWidthSpinner(view)->setValue(width); + getHeightSpinner(view)->setValue(height); + if (getActiveSnapshotType(view) == LLSnapshotModel::SNAPSHOT_TEXTURE) + { + getWidthSpinner(view)->setIncrement(width >> 1); + getHeightSpinner(view)->setIncrement(height >> 1); + } + } + + if(original_width != width || original_height != height) + { + previewp->setSize(width, height); + + // hide old preview as the aspect ratio could be wrong + checkAutoSnapshot(previewp, false); + LL_DEBUGS() << "updating thumbnail" << LL_ENDL; + // Don't update immediately, give window chance to redraw + getPreviewView()->updateSnapshot(true, false, 1.f); + if(do_update) + { + LL_DEBUGS() << "Will update controls" << LL_ENDL; + updateControls(view); + } + } + } +} + +// static +void LLFloaterSnapshot::Impl::onCommitLayerTypes(LLUICtrl* ctrl, void*data) +{ + LLComboBox* combobox = (LLComboBox*)ctrl; + + LLFloaterSnapshot *view = (LLFloaterSnapshot *)data; + + if (view) + { + LLSnapshotLivePreview* previewp = view->getPreviewView(); + if (previewp) + { + previewp->setSnapshotBufferType((LLSnapshotModel::ESnapshotLayerType)combobox->getCurrentIndex()); + } + view->impl->checkAutoSnapshot(previewp, true); + previewp->updateSnapshot(true, true); + } +} + +void LLFloaterSnapshot::Impl::onImageQualityChange(LLFloaterSnapshotBase* view, S32 quality_val) +{ + LLSnapshotLivePreview* previewp = getPreviewView(); + if (previewp) + { + previewp->setSnapshotQuality(quality_val); + } +} + +void LLFloaterSnapshot::Impl::onImageFormatChange(LLFloaterSnapshotBase* view) +{ + if (view) + { + gSavedSettings.setS32("SnapshotFormat", getImageFormat(view)); + LL_DEBUGS() << "image format changed, updating snapshot" << LL_ENDL; + getPreviewView()->updateSnapshot(true); + updateControls(view); + } +} + +// Sets the named size combo to "custom" mode. +void LLFloaterSnapshot::Impl::comboSetCustom(LLFloaterSnapshotBase* floater, const std::string& comboname) +{ + LLComboBox* combo = floater->getChild(comboname); + combo->setCurrentByIndex(combo->getItemCount() - 1); // "custom" is always the last index + checkAspectRatio(floater, -1); // -1 means custom +} + +// Update supplied width and height according to the constrain proportions flag; limit them by max_val. +bool LLFloaterSnapshot::Impl::checkImageSize(LLSnapshotLivePreview* previewp, S32& width, S32& height, bool isWidthChanged, S32 max_value) +{ + S32 w = width ; + S32 h = height ; + + if(previewp && previewp->mKeepAspectRatio) + { + if(gViewerWindow->getWindowWidthRaw() < 1 || gViewerWindow->getWindowHeightRaw() < 1) + { + return false ; + } + + //aspect ratio of the current window + F32 aspect_ratio = (F32)gViewerWindow->getWindowWidthRaw() / gViewerWindow->getWindowHeightRaw() ; + + //change another value proportionally + if(isWidthChanged) + { + height = ll_round(width / aspect_ratio) ; + } + else + { + width = ll_round(height * aspect_ratio) ; + } + + //bound w/h by the max_value + if(width > max_value || height > max_value) + { + if(width > height) + { + width = max_value ; + height = (S32)(width / aspect_ratio) ; + } + else + { + height = max_value ; + width = (S32)(height * aspect_ratio) ; + } + } + } + + return (w != width || h != height) ; +} + +void LLFloaterSnapshot::Impl::setImageSizeSpinnersValues(LLFloaterSnapshotBase* view, S32 width, S32 height) +{ + getWidthSpinner(view)->forceSetValue(width); + getHeightSpinner(view)->forceSetValue(height); + if (getActiveSnapshotType(view) == LLSnapshotModel::SNAPSHOT_TEXTURE) + { + getWidthSpinner(view)->setIncrement(width >> 1); + getHeightSpinner(view)->setIncrement(height >> 1); + } +} + +void LLFloaterSnapshot::Impl::updateSpinners(LLFloaterSnapshotBase* view, LLSnapshotLivePreview* previewp, S32& width, S32& height, bool is_width_changed) +{ + getWidthSpinner(view)->resetDirty(); + getHeightSpinner(view)->resetDirty(); + if (checkImageSize(previewp, width, height, is_width_changed, previewp->getMaxImageSize())) + { + setImageSizeSpinnersValues(view, width, height); + } +} + +void LLFloaterSnapshot::Impl::applyCustomResolution(LLFloaterSnapshotBase* view, S32 w, S32 h) +{ + LL_DEBUGS() << "applyCustomResolution(" << w << ", " << h << ")" << LL_ENDL; + if (!view) return; + + LLSnapshotLivePreview* previewp = getPreviewView(); + if (previewp) + { + S32 curw,curh; + previewp->getSize(curw, curh); + + if (w != curw || h != curh) + { + //if to upload a snapshot, process spinner input in a special way. + previewp->setMaxImageSize((S32) getWidthSpinner(view)->getMaxValue()) ; + + previewp->setSize(w,h); + checkAutoSnapshot(previewp, false); + comboSetCustom(view, "profile_size_combo"); + comboSetCustom(view, "postcard_size_combo"); + comboSetCustom(view, "texture_size_combo"); + comboSetCustom(view, "local_size_combo"); + LL_DEBUGS() << "applied custom resolution, updating thumbnail" << LL_ENDL; + previewp->updateSnapshot(true); + } + } +} + +// static +void LLFloaterSnapshot::Impl::onSnapshotUploadFinished(LLFloaterSnapshotBase* floater, bool status) +{ + floater->impl->setStatus(STATUS_FINISHED, status, "profile"); +} + +// static +void LLFloaterSnapshot::Impl::onSendingPostcardFinished(LLFloaterSnapshotBase* floater, bool status) +{ + floater->impl->setStatus(STATUS_FINISHED, status, "postcard"); +} + +///---------------------------------------------------------------------------- +/// Class LLFloaterSnapshotBase +///---------------------------------------------------------------------------- + +// Default constructor +LLFloaterSnapshotBase::LLFloaterSnapshotBase(const LLSD& key) + : LLFloater(key), + mRefreshBtn(NULL), + mRefreshLabel(NULL), + mSucceessLblPanel(NULL), + mFailureLblPanel(NULL) +{ +} + +LLFloaterSnapshotBase::~LLFloaterSnapshotBase() +{ + if (impl->mPreviewHandle.get()) impl->mPreviewHandle.get()->die(); + + //unfreeze everything else + gSavedSettings.setBOOL("FreezeTime", false); + + if (impl->mLastToolset) + { + LLToolMgr::getInstance()->setCurrentToolset(impl->mLastToolset); + } + + delete impl; +} + +///---------------------------------------------------------------------------- +/// Class LLFloaterSnapshot +///---------------------------------------------------------------------------- + +// Default constructor +LLFloaterSnapshot::LLFloaterSnapshot(const LLSD& key) + : LLFloaterSnapshotBase(key) +{ + impl = new Impl(this); +} + +LLFloaterSnapshot::~LLFloaterSnapshot() +{ +} + +// virtual +bool LLFloaterSnapshot::postBuild() +{ + mRefreshBtn = getChild("new_snapshot_btn"); + childSetAction("new_snapshot_btn", ImplBase::onClickNewSnapshot, this); + mRefreshLabel = getChild("refresh_lbl"); + mSucceessLblPanel = getChild("succeeded_panel"); + mFailureLblPanel = getChild("failed_panel"); + + childSetCommitCallback("ui_check", ImplBase::onClickUICheck, this); + getChild("ui_check")->setValue(gSavedSettings.getBOOL("RenderUIInSnapshot")); + + childSetCommitCallback("hud_check", ImplBase::onClickHUDCheck, this); + getChild("hud_check")->setValue(gSavedSettings.getBOOL("RenderHUDInSnapshot")); + + ((Impl*)impl)->setAspectRatioCheckboxValue(this, gSavedSettings.getBOOL("KeepAspectForSnapshot")); + + childSetCommitCallback("layer_types", Impl::onCommitLayerTypes, this); + getChild("layer_types")->setValue("colors"); + getChildView("layer_types")->setEnabled(false); + + getChild("freeze_frame_check")->setValue(gSavedSettings.getBOOL("UseFreezeFrame")); + childSetCommitCallback("freeze_frame_check", ImplBase::onCommitFreezeFrame, this); + + getChild("auto_snapshot_check")->setValue(gSavedSettings.getBOOL("AutoSnapshot")); + childSetCommitCallback("auto_snapshot_check", ImplBase::onClickAutoSnap, this); + + getChild("no_post_check")->setValue(gSavedSettings.getBOOL("RenderSnapshotNoPost")); + childSetCommitCallback("no_post_check", ImplBase::onClickNoPost, this); + + getChild("retract_btn")->setCommitCallback(boost::bind(&LLFloaterSnapshot::onExtendFloater, this)); + getChild("extend_btn")->setCommitCallback(boost::bind(&LLFloaterSnapshot::onExtendFloater, this)); + + getChild("360_label")->setSoundFlags(LLView::MOUSE_UP); + getChild("360_label")->setShowCursorHand(false); + getChild("360_label")->setClickedCallback(boost::bind(&LLFloaterSnapshot::on360Snapshot, this)); + + // Filters + LLComboBox* filterbox = getChild("filters_combobox"); + std::vector filter_list = LLImageFiltersManager::getInstance()->getFiltersList(); + for (U32 i = 0; i < filter_list.size(); i++) + { + filterbox->add(filter_list[i]); + } + childSetCommitCallback("filters_combobox", ImplBase::onClickFilter, this); + + LLWebProfile::setImageUploadResultCallback(boost::bind(&Impl::onSnapshotUploadFinished, this, _1)); + LLPostCard::setPostResultCallback(boost::bind(&Impl::onSendingPostcardFinished, this, _1)); + + mThumbnailPlaceholder = getChild("thumbnail_placeholder"); + + // create preview window + LLRect full_screen_rect = getRootView()->getRect(); + LLSnapshotLivePreview::Params p; + p.rect(full_screen_rect); + LLSnapshotLivePreview* previewp = new LLSnapshotLivePreview(p); + LLView* parent_view = gSnapshotFloaterView->getParent(); + + parent_view->removeChild(gSnapshotFloaterView); + // make sure preview is below snapshot floater + parent_view->addChild(previewp); + parent_view->addChild(gSnapshotFloaterView); + + //move snapshot floater to special purpose snapshotfloaterview + gFloaterView->removeChild(this); + gSnapshotFloaterView->addChild(this); + + // Pre-select "Current Window" resolution. + getChild("profile_size_combo")->selectNthItem(0); + getChild("postcard_size_combo")->selectNthItem(0); + getChild("texture_size_combo")->selectNthItem(0); + getChild("local_size_combo")->selectNthItem(8); + getChild("local_format_combo")->selectNthItem(0); + + impl->mPreviewHandle = previewp->getHandle(); + previewp->setContainer(this); + impl->updateControls(this); + impl->setAdvanced(gSavedSettings.getBOOL("AdvanceSnapshot")); + impl->updateLayout(this); + + + previewp->setThumbnailPlaceholderRect(getThumbnailPlaceholderRect()); + + return true; +} + +// virtual +void LLFloaterSnapshotBase::draw() +{ + LLSnapshotLivePreview* previewp = getPreviewView(); + + if (previewp && (previewp->isSnapshotActive() || previewp->getThumbnailLock())) + { + // don't render snapshot window in snapshot, even if "show ui" is turned on + return; + } + + LLFloater::draw(); + + if (previewp && !isMinimized() && mThumbnailPlaceholder->getVisible()) + { + if(previewp->getThumbnailImage()) + { + bool working = impl->getStatus() == ImplBase::STATUS_WORKING; + const LLRect& thumbnail_rect = getThumbnailPlaceholderRect(); + const S32 thumbnail_w = previewp->getThumbnailWidth(); + const S32 thumbnail_h = previewp->getThumbnailHeight(); + + // calc preview offset within the preview rect + const S32 local_offset_x = (thumbnail_rect.getWidth() - thumbnail_w) / 2 ; + const S32 local_offset_y = (thumbnail_rect.getHeight() - thumbnail_h) / 2 ; // preview y pos within the preview rect + + // calc preview offset within the floater rect + S32 offset_x = thumbnail_rect.mLeft + local_offset_x; + S32 offset_y = thumbnail_rect.mBottom + local_offset_y; + + gGL.matrixMode(LLRender::MM_MODELVIEW); + // Apply floater transparency to the texture unless the floater is focused. + F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); + LLColor4 color = working ? LLColor4::grey4 : LLColor4::white; + gl_draw_scaled_image(offset_x, offset_y, + thumbnail_w, thumbnail_h, + previewp->getThumbnailImage(), color % alpha); + + previewp->drawPreviewRect(offset_x, offset_y) ; + + gGL.pushUIMatrix(); + LLUI::translate((F32) thumbnail_rect.mLeft, (F32) thumbnail_rect.mBottom); + mThumbnailPlaceholder->draw(); + gGL.popUIMatrix(); + } + } + impl->updateLayout(this); +} + +//virtual +void LLFloaterSnapshot::onOpen(const LLSD& key) +{ + LLSnapshotLivePreview* preview = getPreviewView(); + if(preview) + { + LL_DEBUGS() << "opened, updating snapshot" << LL_ENDL; + preview->setAllowFullScreenPreview(true); + preview->updateSnapshot(true); + } + focusFirstItem(false); + gSnapshotFloaterView->setEnabled(true); + gSnapshotFloaterView->setVisible(true); + gSnapshotFloaterView->adjustToFitScreen(this, false); + + impl->updateControls(this); + impl->setAdvanced(gSavedSettings.getBOOL("AdvanceSnapshot")); + impl->updateLayout(this); + + // Initialize default tab. + getChild("panel_container")->getCurrentPanel()->onOpen(LLSD()); +} + +void LLFloaterSnapshot::onExtendFloater() +{ + impl->setAdvanced(gSavedSettings.getBOOL("AdvanceSnapshot")); +} + +void LLFloaterSnapshot::on360Snapshot() +{ + LLFloaterReg::showInstance("360capture"); + closeFloater(); +} + +//virtual +void LLFloaterSnapshotBase::onClose(bool app_quitting) +{ + getParent()->setMouseOpaque(false); + + //unfreeze everything, hide fullscreen preview + LLSnapshotLivePreview* previewp = getPreviewView(); + if (previewp) + { + previewp->setAllowFullScreenPreview(false); + previewp->setVisible(false); + previewp->setEnabled(false); + } + + gSavedSettings.setBOOL("FreezeTime", false); + impl->mAvatarPauseHandles.clear(); + + if (impl->mLastToolset) + { + LLToolMgr::getInstance()->setCurrentToolset(impl->mLastToolset); + } +} + +// virtual +S32 LLFloaterSnapshotBase::notify(const LLSD& info) +{ + if (info.has("set-ready")) + { + impl->setStatus(ImplBase::STATUS_READY); + return 1; + } + + if (info.has("set-working")) + { + impl->setStatus(ImplBase::STATUS_WORKING); + return 1; + } + + if (info.has("set-finished")) + { + LLSD data = info["set-finished"]; + impl->setStatus(ImplBase::STATUS_FINISHED, data["ok"].asBoolean(), data["msg"].asString()); + return 1; + } + + if (info.has("snapshot-updating")) + { + // Disable the send/post/save buttons until snapshot is ready. + impl->updateControls(this); + return 1; + } + + if (info.has("snapshot-updated")) + { + // Enable the send/post/save buttons. + impl->updateControls(this); + // We've just done refresh. + impl->setNeedRefresh(false); + + // The refresh button is initially hidden. We show it after the first update, + // i.e. when preview appears. + if (mRefreshBtn && !mRefreshBtn->getVisible()) + { + mRefreshBtn->setVisible(true); + } + return 1; + } + + return 0; +} + +// virtual +S32 LLFloaterSnapshot::notify(const LLSD& info) +{ + bool res = LLFloaterSnapshotBase::notify(info); + if (res) + return res; + // A child panel wants to change snapshot resolution. + if (info.has("combo-res-change")) + { + std::string combo_name = info["combo-res-change"]["control-name"].asString(); + ((Impl*)impl)->updateResolution(getChild(combo_name), this); + return 1; + } + + if (info.has("custom-res-change")) + { + LLSD res = info["custom-res-change"]; + ((Impl*)impl)->applyCustomResolution(this, res["w"].asInteger(), res["h"].asInteger()); + return 1; + } + + if (info.has("keep-aspect-change")) + { + ((Impl*)impl)->applyKeepAspectCheck(this, info["keep-aspect-change"].asBoolean()); + return 1; + } + + if (info.has("image-quality-change")) + { + ((Impl*)impl)->onImageQualityChange(this, info["image-quality-change"].asInteger()); + return 1; + } + + if (info.has("image-format-change")) + { + ((Impl*)impl)->onImageFormatChange(this); + return 1; + } + + return 0; +} + +bool LLFloaterSnapshot::isWaitingState() +{ + return (impl->getStatus() == ImplBase::STATUS_WORKING); +} + +bool LLFloaterSnapshotBase::ImplBase::updatePreviewList(bool initialized) +{ + if (!initialized) + return false; + + bool changed = false; + LL_DEBUGS() << "npreviews: " << LLSnapshotLivePreview::sList.size() << LL_ENDL; + for (std::set::iterator iter = LLSnapshotLivePreview::sList.begin(); + iter != LLSnapshotLivePreview::sList.end(); ++iter) + { + changed |= LLSnapshotLivePreview::onIdle(*iter); + } + return changed; +} + + +void LLFloaterSnapshotBase::ImplBase::updateLivePreview() +{ + if (ImplBase::updatePreviewList(true) && mFloater) + { + LL_DEBUGS() << "changed" << LL_ENDL; + updateControls(mFloater); + } +} + +//static +void LLFloaterSnapshot::update() +{ + LLFloaterSnapshot* inst = findInstance(); + if (inst != NULL) + { + inst->impl->updateLivePreview(); + } + else + { + ImplBase::updatePreviewList(false); + } +} + +// static +LLFloaterSnapshot* LLFloaterSnapshot::findInstance() +{ + return LLFloaterReg::findTypedInstance("snapshot"); +} + +// static +LLFloaterSnapshot* LLFloaterSnapshot::getInstance() +{ + return LLFloaterReg::getTypedInstance("snapshot"); +} + +// virtual +void LLFloaterSnapshot::saveTexture() +{ + LL_DEBUGS() << "saveTexture" << LL_ENDL; + + LLSnapshotLivePreview* previewp = getPreviewView(); + if (!previewp) + { + llassert(previewp != NULL); + return; + } + + previewp->saveTexture(); +} + +void LLFloaterSnapshot::saveLocal(const snapshot_saved_signal_t::slot_type& success_cb, const snapshot_saved_signal_t::slot_type& failure_cb) +{ + LL_DEBUGS() << "saveLocal" << LL_ENDL; + LLSnapshotLivePreview* previewp = getPreviewView(); + llassert(previewp != NULL); + if (previewp) + { + previewp->saveLocal(success_cb, failure_cb); + } +} + +void LLFloaterSnapshotBase::postSave() +{ + impl->updateControls(this); + impl->setStatus(ImplBase::STATUS_WORKING); +} + +// virtual +void LLFloaterSnapshotBase::postPanelSwitch() +{ + impl->updateControls(this); + + // Remove the success/failure indicator whenever user presses a snapshot option button. + impl->setStatus(ImplBase::STATUS_READY); +} + +void LLFloaterSnapshotBase::inventorySaveFailed() +{ + impl->updateControls(this); + impl->setStatus(ImplBase::STATUS_FINISHED, false, "inventory"); +} + +LLPointer LLFloaterSnapshotBase::getImageData() +{ + // FIXME: May not work for textures. + + LLSnapshotLivePreview* previewp = getPreviewView(); + if (!previewp) + { + llassert(previewp != NULL); + return NULL; + } + + LLPointer img = previewp->getFormattedImage(); + if (!img.get()) + { + LL_WARNS() << "Empty snapshot image data" << LL_ENDL; + llassert(img.get() != NULL); + } + + return img; +} + +const LLVector3d& LLFloaterSnapshotBase::getPosTakenGlobal() +{ + LLSnapshotLivePreview* previewp = getPreviewView(); + if (!previewp) + { + llassert(previewp != NULL); + return LLVector3d::zero; + } + + return previewp->getPosTakenGlobal(); +} + +// static +void LLFloaterSnapshot::setAgentEmail(const std::string& email) +{ + LLFloaterSnapshot* instance = findInstance(); + if (instance) + { + LLSideTrayPanelContainer* panel_container = instance->getChild("panel_container"); + LLPanel* postcard_panel = panel_container->getPanelByName("panel_snapshot_postcard"); + postcard_panel->notify(LLSD().with("agent-email", email)); + } +} + +///---------------------------------------------------------------------------- +/// Class LLSnapshotFloaterView +///---------------------------------------------------------------------------- + +LLSnapshotFloaterView::LLSnapshotFloaterView (const Params& p) : LLFloaterView (p) +{ +} + +LLSnapshotFloaterView::~LLSnapshotFloaterView() +{ +} + +// virtual +bool LLSnapshotFloaterView::handleKey(KEY key, MASK mask, bool called_from_parent) +{ + // use default handler when not in freeze-frame mode + if(!gSavedSettings.getBOOL("FreezeTime")) + { + return LLFloaterView::handleKey(key, mask, called_from_parent); + } + + if (called_from_parent) + { + // pass all keystrokes down + LLFloaterView::handleKey(key, mask, called_from_parent); + } + else + { + // bounce keystrokes back down + LLFloaterView::handleKey(key, mask, true); + } + return true; +} + +// virtual +bool LLSnapshotFloaterView::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // use default handler when not in freeze-frame mode + if(!gSavedSettings.getBOOL("FreezeTime")) + { + return LLFloaterView::handleMouseDown(x, y, mask); + } + // give floater a change to handle mouse, else camera tool + if (childrenHandleMouseDown(x, y, mask) == NULL) + { + LLToolMgr::getInstance()->getCurrentTool()->handleMouseDown( x, y, mask ); + } + return true; +} + +// virtual +bool LLSnapshotFloaterView::handleMouseUp(S32 x, S32 y, MASK mask) +{ + // use default handler when not in freeze-frame mode + if(!gSavedSettings.getBOOL("FreezeTime")) + { + return LLFloaterView::handleMouseUp(x, y, mask); + } + // give floater a change to handle mouse, else camera tool + if (childrenHandleMouseUp(x, y, mask) == NULL) + { + LLToolMgr::getInstance()->getCurrentTool()->handleMouseUp( x, y, mask ); + } + return true; +} + +// virtual +bool LLSnapshotFloaterView::handleHover(S32 x, S32 y, MASK mask) +{ + // use default handler when not in freeze-frame mode + if(!gSavedSettings.getBOOL("FreezeTime")) + { + return LLFloaterView::handleHover(x, y, mask); + } + // give floater a change to handle mouse, else camera tool + if (childrenHandleHover(x, y, mask) == NULL) + { + LLToolMgr::getInstance()->getCurrentTool()->handleHover( x, y, mask ); + } + return true; +} diff --git a/indra/newview/llfloatersnapshot.h b/indra/newview/llfloatersnapshot.h index adfec443d9..ac5a472b03 100644 --- a/indra/newview/llfloatersnapshot.h +++ b/indra/newview/llfloatersnapshot.h @@ -1,243 +1,243 @@ -/** - * @file llfloatersnapshot.h - * @brief Snapshot preview window, allowing saving, e-mailing, etc. - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2016, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERSNAPSHOT_H -#define LL_LLFLOATERSNAPSHOT_H - -#include "llagent.h" -#include "llfloater.h" -#include "llpanelsnapshot.h" -#include "llsnapshotmodel.h" - -class LLSpinCtrl; -class LLSnapshotLivePreview; -class LLToolset; - -class LLFloaterSnapshotBase : public LLFloater -{ - LOG_CLASS(LLFloaterSnapshotBase); - -public: - - LLFloaterSnapshotBase(const LLSD& key); - virtual ~LLFloaterSnapshotBase(); - - /*virtual*/ void draw(); - /*virtual*/ void onClose(bool app_quitting); - virtual S32 notify(const LLSD& info); - - // TODO: create a snapshot model instead - virtual void saveTexture() = 0; - void postSave(); - virtual void postPanelSwitch(); - LLPointer getImageData(); - LLSnapshotLivePreview* getPreviewView(); - const LLVector3d& getPosTakenGlobal(); - - const LLRect& getThumbnailPlaceholderRect() { return mThumbnailPlaceholder->getRect(); } - - void setRefreshLabelVisible(bool value) { if (mRefreshLabel) mRefreshLabel->setVisible(value); } - void setSuccessLabelPanelVisible(bool value) { if (mSucceessLblPanel) mSucceessLblPanel->setVisible(value); } - void setFailureLabelPanelVisible(bool value) { if (mFailureLblPanel) mFailureLblPanel->setVisible(value); } - void inventorySaveFailed(); - - class ImplBase; - friend class ImplBase; - ImplBase* impl; - -protected: - LLUICtrl* mThumbnailPlaceholder; - LLUICtrl *mRefreshBtn, *mRefreshLabel; - LLUICtrl *mSucceessLblPanel, *mFailureLblPanel; -}; - -class LLFloaterSnapshotBase::ImplBase -{ -public: - typedef enum e_status - { - STATUS_READY, - STATUS_WORKING, - STATUS_FINISHED - } EStatus; - - ImplBase(LLFloaterSnapshotBase* floater) : mAvatarPauseHandles(), - mLastToolset(NULL), - mAspectRatioCheckOff(false), - mNeedRefresh(false), - mSkipReshaping(false), - mStatus(STATUS_READY), - mFloater(floater) - {} - virtual ~ImplBase() - { - //unpause avatars - mAvatarPauseHandles.clear(); - } - - static void onClickNewSnapshot(void* data); - static void onClickAutoSnap(LLUICtrl *ctrl, void* data); - static void onClickNoPost(LLUICtrl *ctrl, void* data); - static void onClickFilter(LLUICtrl *ctrl, void* data); - static void onClickUICheck(LLUICtrl *ctrl, void* data); - static void onClickHUDCheck(LLUICtrl *ctrl, void* data); - static void onCommitFreezeFrame(LLUICtrl* ctrl, void* data); - - virtual LLPanelSnapshot* getActivePanel(LLFloaterSnapshotBase* floater, bool ok_if_not_found = true) = 0; - virtual LLSnapshotModel::ESnapshotType getActiveSnapshotType(LLFloaterSnapshotBase* floater); - virtual LLSnapshotModel::ESnapshotFormat getImageFormat(LLFloaterSnapshotBase* floater) = 0; - virtual std::string getSnapshotPanelPrefix() = 0; - - LLSnapshotLivePreview* getPreviewView(); - virtual void updateControls(LLFloaterSnapshotBase* floater) = 0; - virtual void updateLayout(LLFloaterSnapshotBase* floater); - virtual void updateLivePreview(); - virtual void setStatus(EStatus status, bool ok = true, const std::string& msg = LLStringUtil::null); - virtual EStatus getStatus() const { return mStatus; } - virtual void setNeedRefresh(bool need); - - static bool updatePreviewList(bool initialized); - - void setAdvanced(bool advanced) { mAdvanced = advanced; } - void setSkipReshaping(bool skip) { mSkipReshaping = skip; } - - virtual LLSnapshotModel::ESnapshotLayerType getLayerType(LLFloaterSnapshotBase* floater) = 0; - virtual void checkAutoSnapshot(LLSnapshotLivePreview* floater, bool update_thumbnail = false); - void setWorking(bool working); - virtual void setFinished(bool finished, bool ok = true, const std::string& msg = LLStringUtil::null) = 0; - -public: - LLFloaterSnapshotBase* mFloater; - std::vector mAvatarPauseHandles; - - LLToolset* mLastToolset; - LLHandle mPreviewHandle; - bool mAspectRatioCheckOff; - bool mNeedRefresh; - bool mAdvanced; - bool mSkipReshaping; - EStatus mStatus; -}; - -class LLFloaterSnapshot : public LLFloaterSnapshotBase -{ - LOG_CLASS(LLFloaterSnapshot); - -public: - LLFloaterSnapshot(const LLSD& key); - /*virtual*/ ~LLFloaterSnapshot(); - - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); - /*virtual*/ S32 notify(const LLSD& info); - - static void update(); - - void onExtendFloater(); - void on360Snapshot(); - - static LLFloaterSnapshot* getInstance(); - static LLFloaterSnapshot* findInstance(); - /*virtual*/ void saveTexture(); - - typedef boost::signals2::signal snapshot_saved_signal_t; - void saveLocal(const snapshot_saved_signal_t::slot_type& success_cb, const snapshot_saved_signal_t::slot_type& failure_cb); - static void setAgentEmail(const std::string& email); - - bool isWaitingState(); - - class Impl; - friend class Impl; -}; - -///---------------------------------------------------------------------------- -/// Class LLFloaterSnapshot::Impl -///---------------------------------------------------------------------------- - -class LLFloaterSnapshot::Impl : public LLFloaterSnapshotBase::ImplBase -{ - LOG_CLASS(LLFloaterSnapshot::Impl); -public: - Impl(LLFloaterSnapshotBase* floater) - : LLFloaterSnapshotBase::ImplBase(floater) - {} - ~Impl() - {} - - void applyKeepAspectCheck(LLFloaterSnapshotBase* view, bool checked); - void updateResolution(LLUICtrl* ctrl, void* data, bool do_update = true); - static void onCommitLayerTypes(LLUICtrl* ctrl, void*data); - void onImageQualityChange(LLFloaterSnapshotBase* view, S32 quality_val); - void onImageFormatChange(LLFloaterSnapshotBase* view); - void applyCustomResolution(LLFloaterSnapshotBase* view, S32 w, S32 h); - static void onSendingPostcardFinished(LLFloaterSnapshotBase* floater, bool status); - bool checkImageSize(LLSnapshotLivePreview* previewp, S32& width, S32& height, bool isWidthChanged, S32 max_value); - void setImageSizeSpinnersValues(LLFloaterSnapshotBase *view, S32 width, S32 height); - void updateSpinners(LLFloaterSnapshotBase* view, LLSnapshotLivePreview* previewp, S32& width, S32& height, bool is_width_changed); - static void onSnapshotUploadFinished(LLFloaterSnapshotBase* floater, bool status); - - /*virtual*/ LLPanelSnapshot* getActivePanel(LLFloaterSnapshotBase* floater, bool ok_if_not_found = true); - /*virtual*/ LLSnapshotModel::ESnapshotFormat getImageFormat(LLFloaterSnapshotBase* floater); - LLSpinCtrl* getWidthSpinner(LLFloaterSnapshotBase* floater); - LLSpinCtrl* getHeightSpinner(LLFloaterSnapshotBase* floater); - void enableAspectRatioCheckbox(LLFloaterSnapshotBase* floater, bool enable); - void setAspectRatioCheckboxValue(LLFloaterSnapshotBase* floater, bool checked); - /*virtual*/ std::string getSnapshotPanelPrefix(); - - void setResolution(LLFloaterSnapshotBase* floater, const std::string& comboname); - /*virtual*/ void updateControls(LLFloaterSnapshotBase* floater); - -private: - /*virtual*/ LLSnapshotModel::ESnapshotLayerType getLayerType(LLFloaterSnapshotBase* floater); - void comboSetCustom(LLFloaterSnapshotBase *floater, const std::string& comboname); - void checkAspectRatio(LLFloaterSnapshotBase *view, S32 index); - void setFinished(bool finished, bool ok = true, const std::string& msg = LLStringUtil::null); -}; - -class LLSnapshotFloaterView : public LLFloaterView -{ -public: - struct Params - : public LLInitParam::Block - { - }; - -protected: - LLSnapshotFloaterView (const Params& p); - friend class LLUICtrlFactory; - -public: - virtual ~LLSnapshotFloaterView(); - - /*virtual*/ bool handleKey(KEY key, MASK mask, bool called_from_parent); - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); -}; - -extern LLSnapshotFloaterView* gSnapshotFloaterView; - -#endif // LL_LLFLOATERSNAPSHOT_H +/** + * @file llfloatersnapshot.h + * @brief Snapshot preview window, allowing saving, e-mailing, etc. + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2016, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERSNAPSHOT_H +#define LL_LLFLOATERSNAPSHOT_H + +#include "llagent.h" +#include "llfloater.h" +#include "llpanelsnapshot.h" +#include "llsnapshotmodel.h" + +class LLSpinCtrl; +class LLSnapshotLivePreview; +class LLToolset; + +class LLFloaterSnapshotBase : public LLFloater +{ + LOG_CLASS(LLFloaterSnapshotBase); + +public: + + LLFloaterSnapshotBase(const LLSD& key); + virtual ~LLFloaterSnapshotBase(); + + /*virtual*/ void draw(); + /*virtual*/ void onClose(bool app_quitting); + virtual S32 notify(const LLSD& info); + + // TODO: create a snapshot model instead + virtual void saveTexture() = 0; + void postSave(); + virtual void postPanelSwitch(); + LLPointer getImageData(); + LLSnapshotLivePreview* getPreviewView(); + const LLVector3d& getPosTakenGlobal(); + + const LLRect& getThumbnailPlaceholderRect() { return mThumbnailPlaceholder->getRect(); } + + void setRefreshLabelVisible(bool value) { if (mRefreshLabel) mRefreshLabel->setVisible(value); } + void setSuccessLabelPanelVisible(bool value) { if (mSucceessLblPanel) mSucceessLblPanel->setVisible(value); } + void setFailureLabelPanelVisible(bool value) { if (mFailureLblPanel) mFailureLblPanel->setVisible(value); } + void inventorySaveFailed(); + + class ImplBase; + friend class ImplBase; + ImplBase* impl; + +protected: + LLUICtrl* mThumbnailPlaceholder; + LLUICtrl *mRefreshBtn, *mRefreshLabel; + LLUICtrl *mSucceessLblPanel, *mFailureLblPanel; +}; + +class LLFloaterSnapshotBase::ImplBase +{ +public: + typedef enum e_status + { + STATUS_READY, + STATUS_WORKING, + STATUS_FINISHED + } EStatus; + + ImplBase(LLFloaterSnapshotBase* floater) : mAvatarPauseHandles(), + mLastToolset(NULL), + mAspectRatioCheckOff(false), + mNeedRefresh(false), + mSkipReshaping(false), + mStatus(STATUS_READY), + mFloater(floater) + {} + virtual ~ImplBase() + { + //unpause avatars + mAvatarPauseHandles.clear(); + } + + static void onClickNewSnapshot(void* data); + static void onClickAutoSnap(LLUICtrl *ctrl, void* data); + static void onClickNoPost(LLUICtrl *ctrl, void* data); + static void onClickFilter(LLUICtrl *ctrl, void* data); + static void onClickUICheck(LLUICtrl *ctrl, void* data); + static void onClickHUDCheck(LLUICtrl *ctrl, void* data); + static void onCommitFreezeFrame(LLUICtrl* ctrl, void* data); + + virtual LLPanelSnapshot* getActivePanel(LLFloaterSnapshotBase* floater, bool ok_if_not_found = true) = 0; + virtual LLSnapshotModel::ESnapshotType getActiveSnapshotType(LLFloaterSnapshotBase* floater); + virtual LLSnapshotModel::ESnapshotFormat getImageFormat(LLFloaterSnapshotBase* floater) = 0; + virtual std::string getSnapshotPanelPrefix() = 0; + + LLSnapshotLivePreview* getPreviewView(); + virtual void updateControls(LLFloaterSnapshotBase* floater) = 0; + virtual void updateLayout(LLFloaterSnapshotBase* floater); + virtual void updateLivePreview(); + virtual void setStatus(EStatus status, bool ok = true, const std::string& msg = LLStringUtil::null); + virtual EStatus getStatus() const { return mStatus; } + virtual void setNeedRefresh(bool need); + + static bool updatePreviewList(bool initialized); + + void setAdvanced(bool advanced) { mAdvanced = advanced; } + void setSkipReshaping(bool skip) { mSkipReshaping = skip; } + + virtual LLSnapshotModel::ESnapshotLayerType getLayerType(LLFloaterSnapshotBase* floater) = 0; + virtual void checkAutoSnapshot(LLSnapshotLivePreview* floater, bool update_thumbnail = false); + void setWorking(bool working); + virtual void setFinished(bool finished, bool ok = true, const std::string& msg = LLStringUtil::null) = 0; + +public: + LLFloaterSnapshotBase* mFloater; + std::vector mAvatarPauseHandles; + + LLToolset* mLastToolset; + LLHandle mPreviewHandle; + bool mAspectRatioCheckOff; + bool mNeedRefresh; + bool mAdvanced; + bool mSkipReshaping; + EStatus mStatus; +}; + +class LLFloaterSnapshot : public LLFloaterSnapshotBase +{ + LOG_CLASS(LLFloaterSnapshot); + +public: + LLFloaterSnapshot(const LLSD& key); + /*virtual*/ ~LLFloaterSnapshot(); + + /*virtual*/ bool postBuild(); + /*virtual*/ void onOpen(const LLSD& key); + /*virtual*/ S32 notify(const LLSD& info); + + static void update(); + + void onExtendFloater(); + void on360Snapshot(); + + static LLFloaterSnapshot* getInstance(); + static LLFloaterSnapshot* findInstance(); + /*virtual*/ void saveTexture(); + + typedef boost::signals2::signal snapshot_saved_signal_t; + void saveLocal(const snapshot_saved_signal_t::slot_type& success_cb, const snapshot_saved_signal_t::slot_type& failure_cb); + static void setAgentEmail(const std::string& email); + + bool isWaitingState(); + + class Impl; + friend class Impl; +}; + +///---------------------------------------------------------------------------- +/// Class LLFloaterSnapshot::Impl +///---------------------------------------------------------------------------- + +class LLFloaterSnapshot::Impl : public LLFloaterSnapshotBase::ImplBase +{ + LOG_CLASS(LLFloaterSnapshot::Impl); +public: + Impl(LLFloaterSnapshotBase* floater) + : LLFloaterSnapshotBase::ImplBase(floater) + {} + ~Impl() + {} + + void applyKeepAspectCheck(LLFloaterSnapshotBase* view, bool checked); + void updateResolution(LLUICtrl* ctrl, void* data, bool do_update = true); + static void onCommitLayerTypes(LLUICtrl* ctrl, void*data); + void onImageQualityChange(LLFloaterSnapshotBase* view, S32 quality_val); + void onImageFormatChange(LLFloaterSnapshotBase* view); + void applyCustomResolution(LLFloaterSnapshotBase* view, S32 w, S32 h); + static void onSendingPostcardFinished(LLFloaterSnapshotBase* floater, bool status); + bool checkImageSize(LLSnapshotLivePreview* previewp, S32& width, S32& height, bool isWidthChanged, S32 max_value); + void setImageSizeSpinnersValues(LLFloaterSnapshotBase *view, S32 width, S32 height); + void updateSpinners(LLFloaterSnapshotBase* view, LLSnapshotLivePreview* previewp, S32& width, S32& height, bool is_width_changed); + static void onSnapshotUploadFinished(LLFloaterSnapshotBase* floater, bool status); + + /*virtual*/ LLPanelSnapshot* getActivePanel(LLFloaterSnapshotBase* floater, bool ok_if_not_found = true); + /*virtual*/ LLSnapshotModel::ESnapshotFormat getImageFormat(LLFloaterSnapshotBase* floater); + LLSpinCtrl* getWidthSpinner(LLFloaterSnapshotBase* floater); + LLSpinCtrl* getHeightSpinner(LLFloaterSnapshotBase* floater); + void enableAspectRatioCheckbox(LLFloaterSnapshotBase* floater, bool enable); + void setAspectRatioCheckboxValue(LLFloaterSnapshotBase* floater, bool checked); + /*virtual*/ std::string getSnapshotPanelPrefix(); + + void setResolution(LLFloaterSnapshotBase* floater, const std::string& comboname); + /*virtual*/ void updateControls(LLFloaterSnapshotBase* floater); + +private: + /*virtual*/ LLSnapshotModel::ESnapshotLayerType getLayerType(LLFloaterSnapshotBase* floater); + void comboSetCustom(LLFloaterSnapshotBase *floater, const std::string& comboname); + void checkAspectRatio(LLFloaterSnapshotBase *view, S32 index); + void setFinished(bool finished, bool ok = true, const std::string& msg = LLStringUtil::null); +}; + +class LLSnapshotFloaterView : public LLFloaterView +{ +public: + struct Params + : public LLInitParam::Block + { + }; + +protected: + LLSnapshotFloaterView (const Params& p); + friend class LLUICtrlFactory; + +public: + virtual ~LLSnapshotFloaterView(); + + /*virtual*/ bool handleKey(KEY key, MASK mask, bool called_from_parent); + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); +}; + +extern LLSnapshotFloaterView* gSnapshotFloaterView; + +#endif // LL_LLFLOATERSNAPSHOT_H diff --git a/indra/newview/llfloatersounddevices.cpp b/indra/newview/llfloatersounddevices.cpp index 682505c882..f11c5c0ad8 100644 --- a/indra/newview/llfloatersounddevices.cpp +++ b/indra/newview/llfloatersounddevices.cpp @@ -1,86 +1,86 @@ -/** - * @file llfloatersounddevices.cpp - * @author Leyla Farazha - * @brief Sound Preferences used for minimal skin - * -* $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ -#include "llviewerprecompiledheaders.h" - -#include "llfloatersounddevices.h" - -#include "lldraghandle.h" - -#include "llpanelvoicedevicesettings.h" - -// Library includes -#include "indra_constants.h" - -// protected -LLFloaterSoundDevices::LLFloaterSoundDevices(const LLSD& key) -: LLTransientDockableFloater(NULL, false, key) -{ - LLTransientFloaterMgr::getInstance()->addControlView(this); - - // force docked state since this floater doesn't save it between recreations - setDocked(true); -} - -LLFloaterSoundDevices::~LLFloaterSoundDevices() -{ - LLTransientFloaterMgr::getInstance()->removeControlView(this); -} - -// virtual -bool LLFloaterSoundDevices::postBuild() -{ - LLTransientDockableFloater::postBuild(); - - updateTransparency(TT_ACTIVE); // force using active floater transparency (STORM-730) - - LLPanelVoiceDeviceSettings* panel = findChild("device_settings_panel"); - if (panel) - { - panel->setUseTuningMode(false); - getChild("voice_input_device")->setCommitCallback(boost::bind(&LLPanelVoiceDeviceSettings::apply, panel)); - getChild("voice_output_device")->setCommitCallback(boost::bind(&LLPanelVoiceDeviceSettings::apply, panel)); - getChild("mic_volume_slider")->setCommitCallback(boost::bind(&LLPanelVoiceDeviceSettings::apply, panel)); - } - return true; -} - -//virtual -void LLFloaterSoundDevices::setDocked(bool docked, bool pop_on_undock/* = true*/) -{ - LLTransientDockableFloater::setDocked(docked, pop_on_undock); -} - -// virtual -void LLFloaterSoundDevices::setFocus(bool b) -{ - LLTransientDockableFloater::setFocus(b); - - // Force using active floater transparency - // We have to override setFocus() for because selecting an item of the - // combobox causes the floater to lose focus and thus become transparent. - updateTransparency(TT_ACTIVE); -} +/** + * @file llfloatersounddevices.cpp + * @author Leyla Farazha + * @brief Sound Preferences used for minimal skin + * +* $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#include "llviewerprecompiledheaders.h" + +#include "llfloatersounddevices.h" + +#include "lldraghandle.h" + +#include "llpanelvoicedevicesettings.h" + +// Library includes +#include "indra_constants.h" + +// protected +LLFloaterSoundDevices::LLFloaterSoundDevices(const LLSD& key) +: LLTransientDockableFloater(NULL, false, key) +{ + LLTransientFloaterMgr::getInstance()->addControlView(this); + + // force docked state since this floater doesn't save it between recreations + setDocked(true); +} + +LLFloaterSoundDevices::~LLFloaterSoundDevices() +{ + LLTransientFloaterMgr::getInstance()->removeControlView(this); +} + +// virtual +bool LLFloaterSoundDevices::postBuild() +{ + LLTransientDockableFloater::postBuild(); + + updateTransparency(TT_ACTIVE); // force using active floater transparency (STORM-730) + + LLPanelVoiceDeviceSettings* panel = findChild("device_settings_panel"); + if (panel) + { + panel->setUseTuningMode(false); + getChild("voice_input_device")->setCommitCallback(boost::bind(&LLPanelVoiceDeviceSettings::apply, panel)); + getChild("voice_output_device")->setCommitCallback(boost::bind(&LLPanelVoiceDeviceSettings::apply, panel)); + getChild("mic_volume_slider")->setCommitCallback(boost::bind(&LLPanelVoiceDeviceSettings::apply, panel)); + } + return true; +} + +//virtual +void LLFloaterSoundDevices::setDocked(bool docked, bool pop_on_undock/* = true*/) +{ + LLTransientDockableFloater::setDocked(docked, pop_on_undock); +} + +// virtual +void LLFloaterSoundDevices::setFocus(bool b) +{ + LLTransientDockableFloater::setFocus(b); + + // Force using active floater transparency + // We have to override setFocus() for because selecting an item of the + // combobox causes the floater to lose focus and thus become transparent. + updateTransparency(TT_ACTIVE); +} diff --git a/indra/newview/llfloatersounddevices.h b/indra/newview/llfloatersounddevices.h index 612f468f6d..9b21b62747 100644 --- a/indra/newview/llfloatersounddevices.h +++ b/indra/newview/llfloatersounddevices.h @@ -1,49 +1,49 @@ -/** - * @file llfloatersounddevices.h - * @author Leyla Farazha - * @brief Sound Preferences used for minimal skin - * -* $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERSOUNDDEVICES_H -#define LL_LLFLOATERSOUNDDEVICES_H - -#include "lltransientdockablefloater.h" - -class LLFloaterSoundDevices : public LLTransientDockableFloater -{ -public: - - LOG_CLASS(LLFloaterSoundDevices); - - LLFloaterSoundDevices(const LLSD& key); - ~LLFloaterSoundDevices(); - - bool postBuild() override; - void setDocked(bool docked, bool pop_on_undock = true) override; - void setFocus(bool b) override; -}; - - -#endif //LL_LLFLOATERSOUNDDEVICES_H - +/** + * @file llfloatersounddevices.h + * @author Leyla Farazha + * @brief Sound Preferences used for minimal skin + * +* $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERSOUNDDEVICES_H +#define LL_LLFLOATERSOUNDDEVICES_H + +#include "lltransientdockablefloater.h" + +class LLFloaterSoundDevices : public LLTransientDockableFloater +{ +public: + + LOG_CLASS(LLFloaterSoundDevices); + + LLFloaterSoundDevices(const LLSD& key); + ~LLFloaterSoundDevices(); + + bool postBuild() override; + void setDocked(bool docked, bool pop_on_undock = true) override; + void setFocus(bool b) override; +}; + + +#endif //LL_LLFLOATERSOUNDDEVICES_H + diff --git a/indra/newview/llfloaterspellchecksettings.cpp b/indra/newview/llfloaterspellchecksettings.cpp index 9186c7856a..735776f7e5 100644 --- a/indra/newview/llfloaterspellchecksettings.cpp +++ b/indra/newview/llfloaterspellchecksettings.cpp @@ -1,461 +1,461 @@ -/** - * @file llfloaterspellchecksettings.h - * @brief Spell checker settings floater - * -* $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llcombobox.h" -#include "llfilepicker.h" -#include "llfloaterreg.h" -#include "llfloaterspellchecksettings.h" -#include "llnotificationsutil.h" -#include "llscrolllistctrl.h" -#include "llsdserialize.h" -#include "llspellcheck.h" -#include "lltrans.h" -#include "llviewercontrol.h" -#include "llviewermenufile.h" // LLFilePickerReplyThread - -#include - -///---------------------------------------------------------------------------- -/// Class LLFloaterSpellCheckerSettings -///---------------------------------------------------------------------------- -LLFloaterSpellCheckerSettings::LLFloaterSpellCheckerSettings(const LLSD& key) - : LLFloater(key) -{ -} - -void LLFloaterSpellCheckerSettings::draw() -{ - LLFloater::draw(); - - std::vector sel_items = getChild("spellcheck_available_list")->getAllSelected(); - bool enable_remove = !sel_items.empty(); - for (std::vector::const_iterator sel_it = sel_items.begin(); sel_it != sel_items.end(); ++sel_it) - { - enable_remove &= LLSpellChecker::getInstance()->canRemoveDictionary((*sel_it)->getValue().asString()); - } - getChild("spellcheck_remove_btn")->setEnabled(enable_remove); -} - -bool LLFloaterSpellCheckerSettings::postBuild(void) -{ - gSavedSettings.getControl("SpellCheck")->getSignal()->connect(boost::bind(&LLFloaterSpellCheckerSettings::refreshDictionaries, this, false)); - LLSpellChecker::setSettingsChangeCallback(boost::bind(&LLFloaterSpellCheckerSettings::onSpellCheckSettingsChange, this)); - getChild("spellcheck_remove_btn")->setCommitCallback(boost::bind(&LLFloaterSpellCheckerSettings::onBtnRemove, this)); - getChild("spellcheck_import_btn")->setCommitCallback(boost::bind(&LLFloaterSpellCheckerSettings::onBtnImport, this)); - getChild("spellcheck_main_combo")->setCommitCallback(boost::bind(&LLFloaterSpellCheckerSettings::refreshDictionaries, this, false)); - getChild("spellcheck_moveleft_btn")->setCommitCallback(boost::bind(&LLFloaterSpellCheckerSettings::onBtnMove, this, "spellcheck_active_list", "spellcheck_available_list")); - getChild("spellcheck_moveright_btn")->setCommitCallback(boost::bind(&LLFloaterSpellCheckerSettings::onBtnMove, this, "spellcheck_available_list", "spellcheck_active_list")); - center(); - return true; -} - -void LLFloaterSpellCheckerSettings::onBtnImport() -{ - LLFloaterReg::showInstance("prefs_spellchecker_import"); -} - -void LLFloaterSpellCheckerSettings::onBtnMove(const std::string& from, const std::string& to) -{ - LLScrollListCtrl* from_ctrl = findChild(from); - LLScrollListCtrl* to_ctrl = findChild(to); - - LLSD row; - row["columns"][0]["column"] = "name"; - - std::vector sel_items = from_ctrl->getAllSelected(); - std::vector::const_iterator sel_it; - for ( sel_it = sel_items.begin(); sel_it != sel_items.end(); ++sel_it) - { - row["value"] = (*sel_it)->getValue(); - row["columns"][0]["value"] = (*sel_it)->getColumn(0)->getValue(); - to_ctrl->addElement(row); - to_ctrl->setSelectedByValue( (*sel_it)->getValue(), true ); - } - from_ctrl->deleteSelectedItems(); -} - -void LLFloaterSpellCheckerSettings::onClose(bool app_quitting) -{ - if (app_quitting) - { - // don't save anything - return; - } - LLFloaterReg::hideInstance("prefs_spellchecker_import"); - - std::list list_dict; - - LLComboBox* dict_combo = findChild("spellcheck_main_combo"); - const std::string dict_name = dict_combo->getSelectedItemLabel(); - if (!dict_name.empty()) - { - list_dict.push_back(dict_name); - - LLScrollListCtrl* list_ctrl = findChild("spellcheck_active_list"); - std::vector list_items = list_ctrl->getAllData(); - for (std::vector::const_iterator item_it = list_items.begin(); item_it != list_items.end(); ++item_it) - { - const std::string language = (*item_it)->getValue().asString(); - if (LLSpellChecker::getInstance()->hasDictionary(language, true)) - { - list_dict.push_back(language); - } - } - } - gSavedSettings.setString("SpellCheckDictionary", boost::join(list_dict, ",")); -} - -void LLFloaterSpellCheckerSettings::onOpen(const LLSD& key) -{ - refreshDictionaries(true); -} - -void LLFloaterSpellCheckerSettings::onBtnRemove() -{ - std::vector sel_items = getChild("spellcheck_available_list")->getAllSelected(); - for (std::vector::const_iterator sel_it = sel_items.begin(); sel_it != sel_items.end(); ++sel_it) - { - LLSpellChecker::instance().removeDictionary((*sel_it)->getValue().asString()); - } -} - -void LLFloaterSpellCheckerSettings::onSpellCheckSettingsChange() -{ - refreshDictionaries(true); -} - -void LLFloaterSpellCheckerSettings::refreshDictionaries(bool from_settings) -{ - bool enabled = gSavedSettings.getBOOL("SpellCheck"); - getChild("spellcheck_moveleft_btn")->setEnabled(enabled); - getChild("spellcheck_moveright_btn")->setEnabled(enabled); - - // Populate the dictionary combobox - LLComboBox* dict_combo = findChild("spellcheck_main_combo"); - std::string dict_cur = dict_combo->getSelectedItemLabel(); - if ((dict_cur.empty() || from_settings) && (LLSpellChecker::getUseSpellCheck())) - { - dict_cur = LLSpellChecker::instance().getPrimaryDictionary(); - } - dict_combo->clearRows(); - - const LLSD& dict_map = LLSpellChecker::getInstance()->getDictionaryMap(); - if (dict_map.size()) - { - for (LLSD::array_const_iterator dict_it = dict_map.beginArray(); dict_it != dict_map.endArray(); ++dict_it) - { - const LLSD& dict = *dict_it; - if ( (dict["installed"].asBoolean()) && (dict["is_primary"].asBoolean()) && (dict.has("language")) ) - { - dict_combo->add(dict["language"].asString()); - } - } - if (!dict_combo->selectByValue(dict_cur)) - { - dict_combo->clear(); - } - } - dict_combo->sortByName(); - dict_combo->setEnabled(enabled); - - // Populate the available and active dictionary list - LLScrollListCtrl* avail_ctrl = findChild("spellcheck_available_list"); - LLScrollListCtrl* active_ctrl = findChild("spellcheck_active_list"); - - LLSpellChecker::dict_list_t active_list; - if ( ((!avail_ctrl->getItemCount()) && (!active_ctrl->getItemCount())) || (from_settings) ) - { - if (LLSpellChecker::getUseSpellCheck()) - { - active_list = LLSpellChecker::instance().getSecondaryDictionaries(); - } - } - else - { - std::vector active_items = active_ctrl->getAllData(); - for (std::vector::const_iterator item_it = active_items.begin(); item_it != active_items.end(); ++item_it) - { - std::string dict = (*item_it)->getValue().asString(); - if (dict_cur != dict) - { - active_list.push_back(dict); - } - } - } - - LLSD row; - row["columns"][0]["column"] = "name"; - - active_ctrl->clearRows(); - active_ctrl->setEnabled(enabled); - for (LLSpellChecker::dict_list_t::const_iterator it = active_list.begin(); it != active_list.end(); ++it) - { - const std::string language = *it; - const LLSD dict = LLSpellChecker::getInstance()->getDictionaryData(language); - row["value"] = language; - row["columns"][0]["value"] = (!dict["user_installed"].asBoolean()) ? language : language + " " + LLTrans::getString("UserDictionary"); - active_ctrl->addElement(row); - } - active_ctrl->sortByColumnIndex(0, true); - active_list.push_back(dict_cur); - - avail_ctrl->clearRows(); - avail_ctrl->setEnabled(enabled); - for (LLSD::array_const_iterator dict_it = dict_map.beginArray(); dict_it != dict_map.endArray(); ++dict_it) - { - const LLSD& dict = *dict_it; - const std::string language = dict["language"].asString(); - if ( (dict["installed"].asBoolean()) && (active_list.end() == std::find(active_list.begin(), active_list.end(), language)) ) - { - row["value"] = language; - row["columns"][0]["value"] = (!dict["user_installed"].asBoolean()) ? language : language + " " + LLTrans::getString("UserDictionary"); - avail_ctrl->addElement(row); - } - } - avail_ctrl->sortByColumnIndex(0, true); -} - -///---------------------------------------------------------------------------- -/// Class LLFloaterSpellCheckerImport -///---------------------------------------------------------------------------- -LLFloaterSpellCheckerImport::LLFloaterSpellCheckerImport(const LLSD& key) - : LLFloater(key) -{ -} - -bool LLFloaterSpellCheckerImport::postBuild() -{ - getChild("dictionary_path_browse")->setCommitCallback(boost::bind(&LLFloaterSpellCheckerImport::onBtnBrowse, this)); - getChild("ok_btn")->setCommitCallback(boost::bind(&LLFloaterSpellCheckerImport::onBtnOK, this)); - getChild("cancel_btn")->setCommitCallback(boost::bind(&LLFloaterSpellCheckerImport::onBtnCancel, this)); - center(); - return true; -} - -void LLFloaterSpellCheckerImport::onBtnBrowse() -{ - LLFilePickerReplyThread::startPicker(boost::bind(&LLFloaterSpellCheckerImport::importSelectedDictionary, this, _1), LLFilePicker::FFLOAD_DICTIONARY, false); -} - -void LLFloaterSpellCheckerImport::importSelectedDictionary(const std::vector& filenames) -{ - std::string filepath = filenames[0]; - - const std::string extension = gDirUtilp->getExtension(filepath); - if ("xcu" == extension) - { - filepath = parseXcuFile(filepath); - if (filepath.empty()) - { - return; - } - } - - getChild("dictionary_path")->setValue(filepath); - - mDictionaryDir = gDirUtilp->getDirName(filepath); - mDictionaryBasename = gDirUtilp->getBaseFileName(filepath, true); - getChild("dictionary_name")->setValue(mDictionaryBasename); -} - -void LLFloaterSpellCheckerImport::onBtnCancel() -{ - closeFloater(false); -} - -void LLFloaterSpellCheckerImport::onBtnOK() -{ - const std::string dict_dic = mDictionaryDir + gDirUtilp->getDirDelimiter() + mDictionaryBasename + ".dic"; - const std::string dict_aff = mDictionaryDir + gDirUtilp->getDirDelimiter() + mDictionaryBasename + ".aff"; - std::string dict_language = getChild("dictionary_language")->getValue().asString(); - LLStringUtil::trim(dict_language); - - bool imported = false; - if ( dict_language.empty() - || mDictionaryDir.empty() - || mDictionaryBasename.empty() - || ! gDirUtilp->fileExists(dict_dic) - ) - { - LLNotificationsUtil::add("SpellingDictImportRequired"); - } - else - { - std::string settings_dic = LLSpellChecker::getDictionaryUserPath() + mDictionaryBasename + ".dic"; - if ( LLFile::copy( dict_dic, settings_dic ) ) - { - if (gDirUtilp->fileExists(dict_aff)) - { - std::string settings_aff = LLSpellChecker::getDictionaryUserPath() + mDictionaryBasename + ".aff"; - if ( LLFile::copy( dict_aff, settings_aff )) - { - imported = true; - } - else - { - LLSD args = LLSD::emptyMap(); - args["FROM_NAME"] = dict_aff; - args["TO_NAME"] = settings_aff; - LLNotificationsUtil::add("SpellingDictImportFailed", args); - } - } - else - { - LLSD args = LLSD::emptyMap(); - args["DIC_NAME"] = dict_dic; - LLNotificationsUtil::add("SpellingDictIsSecondary", args); - - imported = true; - } - } - else - { - LLSD args = LLSD::emptyMap(); - args["FROM_NAME"] = dict_dic; - args["TO_NAME"] = settings_dic; - LLNotificationsUtil::add("SpellingDictImportFailed", args); - } - } - - if ( imported ) - { - LLSD custom_dict_info; - custom_dict_info["is_primary"] = (bool)gDirUtilp->fileExists(dict_aff); - custom_dict_info["name"] = mDictionaryBasename; - custom_dict_info["language"] = dict_language; - - LLSD custom_dict_map; - std::string custom_filename(LLSpellChecker::getDictionaryUserPath() + "user_dictionaries.xml"); - llifstream custom_file_in(custom_filename.c_str()); - if (custom_file_in.is_open()) - { - LLSDSerialize::fromXMLDocument(custom_dict_map, custom_file_in); - custom_file_in.close(); - } - - LLSD::array_iterator it = custom_dict_map.beginArray(); - for (; it != custom_dict_map.endArray(); ++it) - { - LLSD& dict_info = *it; - if (dict_info["name"].asString() == mDictionaryBasename) - { - dict_info = custom_dict_info; - break; - } - } - if (custom_dict_map.endArray() == it) - { - custom_dict_map.append(custom_dict_info); - } - - llofstream custom_file_out(custom_filename.c_str(), std::ios::trunc); - if (custom_file_out.is_open()) - { - LLSDSerialize::toPrettyXML(custom_dict_map, custom_file_out); - custom_file_out.close(); - } - - LLSpellChecker::getInstance()->refreshDictionaryMap(); - } - - closeFloater(false); -} - -std::string LLFloaterSpellCheckerImport::parseXcuFile(const std::string& file_path) const -{ - LLXMLNodePtr xml_root; - if ( (!LLUICtrlFactory::getLayeredXMLNode(file_path, xml_root)) || (xml_root.isNull()) ) - { - return LLStringUtil::null; - } - - // Bury down to the "Dictionaries" parent node - LLXMLNode* dict_node = NULL; - for (LLXMLNode* outer_node = xml_root->getFirstChild(); outer_node && !dict_node; outer_node = outer_node->getNextSibling()) - { - std::string temp; - if ( (outer_node->getAttributeString("oor:name", temp)) && ("ServiceManager" == temp) ) - { - for (LLXMLNode* inner_node = outer_node->getFirstChild(); inner_node && !dict_node; inner_node = inner_node->getNextSibling()) - { - if ( (inner_node->getAttributeString("oor:name", temp)) && ("Dictionaries" == temp) ) - { - dict_node = inner_node; - break; - } - } - } - } - - if (dict_node) - { - // Iterate over all child nodes until we find one that has a DICT_SPELL node - for (LLXMLNode* outer_node = dict_node->getFirstChild(); outer_node; outer_node = outer_node->getNextSibling()) - { - std::string temp; - LLXMLNodePtr location_node, format_node; - for (LLXMLNode* inner_node = outer_node->getFirstChild(); inner_node; inner_node = inner_node->getNextSibling()) - { - if (inner_node->getAttributeString("oor:name", temp)) - { - if ("Locations" == temp) - { - inner_node->getChild("value", location_node, false); - } - else if ("Format" == temp) - { - inner_node->getChild("value", format_node, false); - } - } - } - if ( (format_node.isNull()) || ("DICT_SPELL" != format_node->getValue()) || (location_node.isNull()) ) - { - continue; - } - - // Found a list of file locations, return the .dic (if present) - std::list location_list; - boost::split(location_list, location_node->getValue(), boost::is_any_of(std::string(" "))); - for (std::list::iterator it = location_list.begin(); it != location_list.end(); ++it) - { - std::string& location = *it; - if ("\\" != gDirUtilp->getDirDelimiter()) - LLStringUtil::replaceString(location, "\\", gDirUtilp->getDirDelimiter()); - else - LLStringUtil::replaceString(location, "/", gDirUtilp->getDirDelimiter()); - LLStringUtil::replaceString(location, "%origin%", gDirUtilp->getDirName(file_path)); - if ("dic" == gDirUtilp->getExtension(location)) - { - return location; - } - } - } - } - - return LLStringUtil::null; -} +/** + * @file llfloaterspellchecksettings.h + * @brief Spell checker settings floater + * +* $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llcombobox.h" +#include "llfilepicker.h" +#include "llfloaterreg.h" +#include "llfloaterspellchecksettings.h" +#include "llnotificationsutil.h" +#include "llscrolllistctrl.h" +#include "llsdserialize.h" +#include "llspellcheck.h" +#include "lltrans.h" +#include "llviewercontrol.h" +#include "llviewermenufile.h" // LLFilePickerReplyThread + +#include + +///---------------------------------------------------------------------------- +/// Class LLFloaterSpellCheckerSettings +///---------------------------------------------------------------------------- +LLFloaterSpellCheckerSettings::LLFloaterSpellCheckerSettings(const LLSD& key) + : LLFloater(key) +{ +} + +void LLFloaterSpellCheckerSettings::draw() +{ + LLFloater::draw(); + + std::vector sel_items = getChild("spellcheck_available_list")->getAllSelected(); + bool enable_remove = !sel_items.empty(); + for (std::vector::const_iterator sel_it = sel_items.begin(); sel_it != sel_items.end(); ++sel_it) + { + enable_remove &= LLSpellChecker::getInstance()->canRemoveDictionary((*sel_it)->getValue().asString()); + } + getChild("spellcheck_remove_btn")->setEnabled(enable_remove); +} + +bool LLFloaterSpellCheckerSettings::postBuild(void) +{ + gSavedSettings.getControl("SpellCheck")->getSignal()->connect(boost::bind(&LLFloaterSpellCheckerSettings::refreshDictionaries, this, false)); + LLSpellChecker::setSettingsChangeCallback(boost::bind(&LLFloaterSpellCheckerSettings::onSpellCheckSettingsChange, this)); + getChild("spellcheck_remove_btn")->setCommitCallback(boost::bind(&LLFloaterSpellCheckerSettings::onBtnRemove, this)); + getChild("spellcheck_import_btn")->setCommitCallback(boost::bind(&LLFloaterSpellCheckerSettings::onBtnImport, this)); + getChild("spellcheck_main_combo")->setCommitCallback(boost::bind(&LLFloaterSpellCheckerSettings::refreshDictionaries, this, false)); + getChild("spellcheck_moveleft_btn")->setCommitCallback(boost::bind(&LLFloaterSpellCheckerSettings::onBtnMove, this, "spellcheck_active_list", "spellcheck_available_list")); + getChild("spellcheck_moveright_btn")->setCommitCallback(boost::bind(&LLFloaterSpellCheckerSettings::onBtnMove, this, "spellcheck_available_list", "spellcheck_active_list")); + center(); + return true; +} + +void LLFloaterSpellCheckerSettings::onBtnImport() +{ + LLFloaterReg::showInstance("prefs_spellchecker_import"); +} + +void LLFloaterSpellCheckerSettings::onBtnMove(const std::string& from, const std::string& to) +{ + LLScrollListCtrl* from_ctrl = findChild(from); + LLScrollListCtrl* to_ctrl = findChild(to); + + LLSD row; + row["columns"][0]["column"] = "name"; + + std::vector sel_items = from_ctrl->getAllSelected(); + std::vector::const_iterator sel_it; + for ( sel_it = sel_items.begin(); sel_it != sel_items.end(); ++sel_it) + { + row["value"] = (*sel_it)->getValue(); + row["columns"][0]["value"] = (*sel_it)->getColumn(0)->getValue(); + to_ctrl->addElement(row); + to_ctrl->setSelectedByValue( (*sel_it)->getValue(), true ); + } + from_ctrl->deleteSelectedItems(); +} + +void LLFloaterSpellCheckerSettings::onClose(bool app_quitting) +{ + if (app_quitting) + { + // don't save anything + return; + } + LLFloaterReg::hideInstance("prefs_spellchecker_import"); + + std::list list_dict; + + LLComboBox* dict_combo = findChild("spellcheck_main_combo"); + const std::string dict_name = dict_combo->getSelectedItemLabel(); + if (!dict_name.empty()) + { + list_dict.push_back(dict_name); + + LLScrollListCtrl* list_ctrl = findChild("spellcheck_active_list"); + std::vector list_items = list_ctrl->getAllData(); + for (std::vector::const_iterator item_it = list_items.begin(); item_it != list_items.end(); ++item_it) + { + const std::string language = (*item_it)->getValue().asString(); + if (LLSpellChecker::getInstance()->hasDictionary(language, true)) + { + list_dict.push_back(language); + } + } + } + gSavedSettings.setString("SpellCheckDictionary", boost::join(list_dict, ",")); +} + +void LLFloaterSpellCheckerSettings::onOpen(const LLSD& key) +{ + refreshDictionaries(true); +} + +void LLFloaterSpellCheckerSettings::onBtnRemove() +{ + std::vector sel_items = getChild("spellcheck_available_list")->getAllSelected(); + for (std::vector::const_iterator sel_it = sel_items.begin(); sel_it != sel_items.end(); ++sel_it) + { + LLSpellChecker::instance().removeDictionary((*sel_it)->getValue().asString()); + } +} + +void LLFloaterSpellCheckerSettings::onSpellCheckSettingsChange() +{ + refreshDictionaries(true); +} + +void LLFloaterSpellCheckerSettings::refreshDictionaries(bool from_settings) +{ + bool enabled = gSavedSettings.getBOOL("SpellCheck"); + getChild("spellcheck_moveleft_btn")->setEnabled(enabled); + getChild("spellcheck_moveright_btn")->setEnabled(enabled); + + // Populate the dictionary combobox + LLComboBox* dict_combo = findChild("spellcheck_main_combo"); + std::string dict_cur = dict_combo->getSelectedItemLabel(); + if ((dict_cur.empty() || from_settings) && (LLSpellChecker::getUseSpellCheck())) + { + dict_cur = LLSpellChecker::instance().getPrimaryDictionary(); + } + dict_combo->clearRows(); + + const LLSD& dict_map = LLSpellChecker::getInstance()->getDictionaryMap(); + if (dict_map.size()) + { + for (LLSD::array_const_iterator dict_it = dict_map.beginArray(); dict_it != dict_map.endArray(); ++dict_it) + { + const LLSD& dict = *dict_it; + if ( (dict["installed"].asBoolean()) && (dict["is_primary"].asBoolean()) && (dict.has("language")) ) + { + dict_combo->add(dict["language"].asString()); + } + } + if (!dict_combo->selectByValue(dict_cur)) + { + dict_combo->clear(); + } + } + dict_combo->sortByName(); + dict_combo->setEnabled(enabled); + + // Populate the available and active dictionary list + LLScrollListCtrl* avail_ctrl = findChild("spellcheck_available_list"); + LLScrollListCtrl* active_ctrl = findChild("spellcheck_active_list"); + + LLSpellChecker::dict_list_t active_list; + if ( ((!avail_ctrl->getItemCount()) && (!active_ctrl->getItemCount())) || (from_settings) ) + { + if (LLSpellChecker::getUseSpellCheck()) + { + active_list = LLSpellChecker::instance().getSecondaryDictionaries(); + } + } + else + { + std::vector active_items = active_ctrl->getAllData(); + for (std::vector::const_iterator item_it = active_items.begin(); item_it != active_items.end(); ++item_it) + { + std::string dict = (*item_it)->getValue().asString(); + if (dict_cur != dict) + { + active_list.push_back(dict); + } + } + } + + LLSD row; + row["columns"][0]["column"] = "name"; + + active_ctrl->clearRows(); + active_ctrl->setEnabled(enabled); + for (LLSpellChecker::dict_list_t::const_iterator it = active_list.begin(); it != active_list.end(); ++it) + { + const std::string language = *it; + const LLSD dict = LLSpellChecker::getInstance()->getDictionaryData(language); + row["value"] = language; + row["columns"][0]["value"] = (!dict["user_installed"].asBoolean()) ? language : language + " " + LLTrans::getString("UserDictionary"); + active_ctrl->addElement(row); + } + active_ctrl->sortByColumnIndex(0, true); + active_list.push_back(dict_cur); + + avail_ctrl->clearRows(); + avail_ctrl->setEnabled(enabled); + for (LLSD::array_const_iterator dict_it = dict_map.beginArray(); dict_it != dict_map.endArray(); ++dict_it) + { + const LLSD& dict = *dict_it; + const std::string language = dict["language"].asString(); + if ( (dict["installed"].asBoolean()) && (active_list.end() == std::find(active_list.begin(), active_list.end(), language)) ) + { + row["value"] = language; + row["columns"][0]["value"] = (!dict["user_installed"].asBoolean()) ? language : language + " " + LLTrans::getString("UserDictionary"); + avail_ctrl->addElement(row); + } + } + avail_ctrl->sortByColumnIndex(0, true); +} + +///---------------------------------------------------------------------------- +/// Class LLFloaterSpellCheckerImport +///---------------------------------------------------------------------------- +LLFloaterSpellCheckerImport::LLFloaterSpellCheckerImport(const LLSD& key) + : LLFloater(key) +{ +} + +bool LLFloaterSpellCheckerImport::postBuild() +{ + getChild("dictionary_path_browse")->setCommitCallback(boost::bind(&LLFloaterSpellCheckerImport::onBtnBrowse, this)); + getChild("ok_btn")->setCommitCallback(boost::bind(&LLFloaterSpellCheckerImport::onBtnOK, this)); + getChild("cancel_btn")->setCommitCallback(boost::bind(&LLFloaterSpellCheckerImport::onBtnCancel, this)); + center(); + return true; +} + +void LLFloaterSpellCheckerImport::onBtnBrowse() +{ + LLFilePickerReplyThread::startPicker(boost::bind(&LLFloaterSpellCheckerImport::importSelectedDictionary, this, _1), LLFilePicker::FFLOAD_DICTIONARY, false); +} + +void LLFloaterSpellCheckerImport::importSelectedDictionary(const std::vector& filenames) +{ + std::string filepath = filenames[0]; + + const std::string extension = gDirUtilp->getExtension(filepath); + if ("xcu" == extension) + { + filepath = parseXcuFile(filepath); + if (filepath.empty()) + { + return; + } + } + + getChild("dictionary_path")->setValue(filepath); + + mDictionaryDir = gDirUtilp->getDirName(filepath); + mDictionaryBasename = gDirUtilp->getBaseFileName(filepath, true); + getChild("dictionary_name")->setValue(mDictionaryBasename); +} + +void LLFloaterSpellCheckerImport::onBtnCancel() +{ + closeFloater(false); +} + +void LLFloaterSpellCheckerImport::onBtnOK() +{ + const std::string dict_dic = mDictionaryDir + gDirUtilp->getDirDelimiter() + mDictionaryBasename + ".dic"; + const std::string dict_aff = mDictionaryDir + gDirUtilp->getDirDelimiter() + mDictionaryBasename + ".aff"; + std::string dict_language = getChild("dictionary_language")->getValue().asString(); + LLStringUtil::trim(dict_language); + + bool imported = false; + if ( dict_language.empty() + || mDictionaryDir.empty() + || mDictionaryBasename.empty() + || ! gDirUtilp->fileExists(dict_dic) + ) + { + LLNotificationsUtil::add("SpellingDictImportRequired"); + } + else + { + std::string settings_dic = LLSpellChecker::getDictionaryUserPath() + mDictionaryBasename + ".dic"; + if ( LLFile::copy( dict_dic, settings_dic ) ) + { + if (gDirUtilp->fileExists(dict_aff)) + { + std::string settings_aff = LLSpellChecker::getDictionaryUserPath() + mDictionaryBasename + ".aff"; + if ( LLFile::copy( dict_aff, settings_aff )) + { + imported = true; + } + else + { + LLSD args = LLSD::emptyMap(); + args["FROM_NAME"] = dict_aff; + args["TO_NAME"] = settings_aff; + LLNotificationsUtil::add("SpellingDictImportFailed", args); + } + } + else + { + LLSD args = LLSD::emptyMap(); + args["DIC_NAME"] = dict_dic; + LLNotificationsUtil::add("SpellingDictIsSecondary", args); + + imported = true; + } + } + else + { + LLSD args = LLSD::emptyMap(); + args["FROM_NAME"] = dict_dic; + args["TO_NAME"] = settings_dic; + LLNotificationsUtil::add("SpellingDictImportFailed", args); + } + } + + if ( imported ) + { + LLSD custom_dict_info; + custom_dict_info["is_primary"] = (bool)gDirUtilp->fileExists(dict_aff); + custom_dict_info["name"] = mDictionaryBasename; + custom_dict_info["language"] = dict_language; + + LLSD custom_dict_map; + std::string custom_filename(LLSpellChecker::getDictionaryUserPath() + "user_dictionaries.xml"); + llifstream custom_file_in(custom_filename.c_str()); + if (custom_file_in.is_open()) + { + LLSDSerialize::fromXMLDocument(custom_dict_map, custom_file_in); + custom_file_in.close(); + } + + LLSD::array_iterator it = custom_dict_map.beginArray(); + for (; it != custom_dict_map.endArray(); ++it) + { + LLSD& dict_info = *it; + if (dict_info["name"].asString() == mDictionaryBasename) + { + dict_info = custom_dict_info; + break; + } + } + if (custom_dict_map.endArray() == it) + { + custom_dict_map.append(custom_dict_info); + } + + llofstream custom_file_out(custom_filename.c_str(), std::ios::trunc); + if (custom_file_out.is_open()) + { + LLSDSerialize::toPrettyXML(custom_dict_map, custom_file_out); + custom_file_out.close(); + } + + LLSpellChecker::getInstance()->refreshDictionaryMap(); + } + + closeFloater(false); +} + +std::string LLFloaterSpellCheckerImport::parseXcuFile(const std::string& file_path) const +{ + LLXMLNodePtr xml_root; + if ( (!LLUICtrlFactory::getLayeredXMLNode(file_path, xml_root)) || (xml_root.isNull()) ) + { + return LLStringUtil::null; + } + + // Bury down to the "Dictionaries" parent node + LLXMLNode* dict_node = NULL; + for (LLXMLNode* outer_node = xml_root->getFirstChild(); outer_node && !dict_node; outer_node = outer_node->getNextSibling()) + { + std::string temp; + if ( (outer_node->getAttributeString("oor:name", temp)) && ("ServiceManager" == temp) ) + { + for (LLXMLNode* inner_node = outer_node->getFirstChild(); inner_node && !dict_node; inner_node = inner_node->getNextSibling()) + { + if ( (inner_node->getAttributeString("oor:name", temp)) && ("Dictionaries" == temp) ) + { + dict_node = inner_node; + break; + } + } + } + } + + if (dict_node) + { + // Iterate over all child nodes until we find one that has a DICT_SPELL node + for (LLXMLNode* outer_node = dict_node->getFirstChild(); outer_node; outer_node = outer_node->getNextSibling()) + { + std::string temp; + LLXMLNodePtr location_node, format_node; + for (LLXMLNode* inner_node = outer_node->getFirstChild(); inner_node; inner_node = inner_node->getNextSibling()) + { + if (inner_node->getAttributeString("oor:name", temp)) + { + if ("Locations" == temp) + { + inner_node->getChild("value", location_node, false); + } + else if ("Format" == temp) + { + inner_node->getChild("value", format_node, false); + } + } + } + if ( (format_node.isNull()) || ("DICT_SPELL" != format_node->getValue()) || (location_node.isNull()) ) + { + continue; + } + + // Found a list of file locations, return the .dic (if present) + std::list location_list; + boost::split(location_list, location_node->getValue(), boost::is_any_of(std::string(" "))); + for (std::list::iterator it = location_list.begin(); it != location_list.end(); ++it) + { + std::string& location = *it; + if ("\\" != gDirUtilp->getDirDelimiter()) + LLStringUtil::replaceString(location, "\\", gDirUtilp->getDirDelimiter()); + else + LLStringUtil::replaceString(location, "/", gDirUtilp->getDirDelimiter()); + LLStringUtil::replaceString(location, "%origin%", gDirUtilp->getDirName(file_path)); + if ("dic" == gDirUtilp->getExtension(location)) + { + return location; + } + } + } + } + + return LLStringUtil::null; +} diff --git a/indra/newview/llfloaterspellchecksettings.h b/indra/newview/llfloaterspellchecksettings.h index e6375c393e..f05bf68040 100644 --- a/indra/newview/llfloaterspellchecksettings.h +++ b/indra/newview/llfloaterspellchecksettings.h @@ -1,68 +1,68 @@ -/** - * @file llfloaterspellchecksettings.h - * @brief Spell checker settings floater - * -* $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLFLOATERSPELLCHECKERSETTINGS_H -#define LLFLOATERSPELLCHECKERSETTINGS_H - -#include "llfloater.h" - -class LLFloaterSpellCheckerSettings : public LLFloater -{ -public: - LLFloaterSpellCheckerSettings(const LLSD& key); - - void draw() override; - bool postBuild() override; - void onOpen(const LLSD& key) override; - void onClose(bool app_quitting) override; - -protected: - void onBtnImport(); - void onBtnMove(const std::string& from, const std::string& to); - void onBtnRemove(); - void onSpellCheckSettingsChange(); - void refreshDictionaries(bool from_settings); -}; - -class LLFloaterSpellCheckerImport : public LLFloater -{ -public: - LLFloaterSpellCheckerImport(const LLSD& key); - - bool postBuild() override; - -protected: - void onBtnBrowse(); - void onBtnCancel(); - void onBtnOK(); - void importSelectedDictionary(const std::vector& filenames); - std::string parseXcuFile(const std::string& file_path) const; - - std::string mDictionaryDir; - std::string mDictionaryBasename; -}; - -#endif // LLFLOATERSPELLCHECKERSETTINGS_H +/** + * @file llfloaterspellchecksettings.h + * @brief Spell checker settings floater + * +* $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLFLOATERSPELLCHECKERSETTINGS_H +#define LLFLOATERSPELLCHECKERSETTINGS_H + +#include "llfloater.h" + +class LLFloaterSpellCheckerSettings : public LLFloater +{ +public: + LLFloaterSpellCheckerSettings(const LLSD& key); + + void draw() override; + bool postBuild() override; + void onOpen(const LLSD& key) override; + void onClose(bool app_quitting) override; + +protected: + void onBtnImport(); + void onBtnMove(const std::string& from, const std::string& to); + void onBtnRemove(); + void onSpellCheckSettingsChange(); + void refreshDictionaries(bool from_settings); +}; + +class LLFloaterSpellCheckerImport : public LLFloater +{ +public: + LLFloaterSpellCheckerImport(const LLSD& key); + + bool postBuild() override; + +protected: + void onBtnBrowse(); + void onBtnCancel(); + void onBtnOK(); + void importSelectedDictionary(const std::vector& filenames); + std::string parseXcuFile(const std::string& file_path) const; + + std::string mDictionaryDir; + std::string mDictionaryBasename; +}; + +#endif // LLFLOATERSPELLCHECKERSETTINGS_H diff --git a/indra/newview/llfloatertelehub.cpp b/indra/newview/llfloatertelehub.cpp index c56ad266cf..e49b5045e3 100644 --- a/indra/newview/llfloatertelehub.cpp +++ b/indra/newview/llfloatertelehub.cpp @@ -1,279 +1,279 @@ -/** - * @file llfloatertelehub.cpp - * @author James Cook - * @brief LLFloaterTelehub class implementation - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloatertelehub.h" - -#include "message.h" -#include "llfontgl.h" - -#include "llagent.h" -#include "llfloaterreg.h" -#include "llfloatertools.h" -#include "llscrolllistctrl.h" -#include "llselectmgr.h" -#include "lltoolcomp.h" -#include "lltoolmgr.h" -#include "llviewerobject.h" -#include "llviewerobjectlist.h" -#include "lluictrlfactory.h" - -LLFloaterTelehub::LLFloaterTelehub(const LLSD& key) -: LLFloater(key), - mTelehubObjectID(), - mTelehubObjectName(), - mTelehubPos(), - mTelehubRot(), - mNumSpawn(0) -{ -} - -bool LLFloaterTelehub::postBuild() -{ - gMessageSystem->setHandlerFunc("TelehubInfo", processTelehubInfo); - - getChild("connect_btn")->setCommitCallback(boost::bind(&LLFloaterTelehub::onClickConnect, this)); - getChild("disconnect_btn")->setCommitCallback(boost::bind(&LLFloaterTelehub::onClickDisconnect, this)); - getChild("add_spawn_point_btn")->setCommitCallback(boost::bind(&LLFloaterTelehub::onClickAddSpawnPoint, this)); - getChild("remove_spawn_point_btn")->setCommitCallback(boost::bind(&LLFloaterTelehub::onClickRemoveSpawnPoint, this)); - - LLScrollListCtrl* list = getChild("spawn_points_list"); - if (list) - { - // otherwise you can't walk with arrow keys while floater is up - list->setAllowKeyboardMovement(false); - } - - return true; -} -void LLFloaterTelehub::onOpen(const LLSD& key) -{ - // Show tools floater by selecting translate (select) tool - LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); - LLToolMgr::getInstance()->getCurrentToolset()->selectTool( LLToolCompTranslate::getInstance() ); - - sendTelehubInfoRequest(); - - mObjectSelection = LLSelectMgr::getInstance()->getEditSelection(); -} - -LLFloaterTelehub::~LLFloaterTelehub() -{ - // no longer interested in this message - gMessageSystem->setHandlerFunc("TelehubInfo", NULL); -} - -void LLFloaterTelehub::draw() -{ - if (!isMinimized()) - { - refresh(); - } - LLFloater::draw(); -} - -// Per-frame updates, because we don't have a selection manager observer. -void LLFloaterTelehub::refresh() -{ - constexpr bool children_ok = true; - LLViewerObject* object = mObjectSelection->getFirstRootObject(children_ok); - - bool have_selection = (object != NULL); - bool all_volume = LLSelectMgr::getInstance()->selectionAllPCode( LL_PCODE_VOLUME ); - getChildView("connect_btn")->setEnabled(have_selection && all_volume); - - bool have_telehub = mTelehubObjectID.notNull(); - getChildView("disconnect_btn")->setEnabled(have_telehub); - - bool space_avail = (mNumSpawn < MAX_SPAWNPOINTS_PER_TELEHUB); - getChildView("add_spawn_point_btn")->setEnabled(have_selection && all_volume && space_avail); - - LLScrollListCtrl* list = getChild("spawn_points_list"); - if (list) - { - bool enable_remove = (list->getFirstSelected() != NULL); - getChildView("remove_spawn_point_btn")->setEnabled(enable_remove); - } -} - -// static -bool LLFloaterTelehub::renderBeacons() -{ - // only render if we've got a telehub - LLFloaterTelehub* floater = LLFloaterReg::findTypedInstance("telehubs"); - return floater && floater->mTelehubObjectID.notNull(); -} - -// static -void LLFloaterTelehub::addBeacons() -{ - LLFloaterTelehub* floater = LLFloaterReg::findTypedInstance("telehubs"); - if (!floater) - return; - - // Find the telehub position, either our cached old position, or - // an updated one based on the actual object position. - LLVector3 hub_pos_region = floater->mTelehubPos; - LLQuaternion hub_rot = floater->mTelehubRot; - LLViewerObject* obj = gObjectList.findObject(floater->mTelehubObjectID); - if (obj) - { - hub_pos_region = obj->getPositionRegion(); - hub_rot = obj->getRotationRegion(); - } - // Draw nice thick 3-pixel lines. - gObjectList.addDebugBeacon(hub_pos_region, "", LLColor4::yellow, LLColor4::white, 4); - - LLScrollListCtrl* list = floater->getChild("spawn_points_list"); - if (list) - { - S32 spawn_index = list->getFirstSelectedIndex(); - if (spawn_index >= 0) - { - LLVector3 spawn_pos = hub_pos_region + (floater->mSpawnPointPos[spawn_index] * hub_rot); - gObjectList.addDebugBeacon(spawn_pos, "", LLColor4::orange, LLColor4::white, 4); - } - } -} - -void LLFloaterTelehub::sendTelehubInfoRequest() -{ - LLSelectMgr::getInstance()->sendGodlikeRequest("telehub", "info ui"); -} - -void LLFloaterTelehub::onClickConnect() -{ - LLSelectMgr::getInstance()->sendGodlikeRequest("telehub", "connect"); -} - -void LLFloaterTelehub::onClickDisconnect() -{ - LLSelectMgr::getInstance()->sendGodlikeRequest("telehub", "delete"); -} - -void LLFloaterTelehub::onClickAddSpawnPoint() -{ - LLSelectMgr::getInstance()->sendGodlikeRequest("telehub", "spawnpoint add"); - LLSelectMgr::getInstance()->deselectAll(); -} - -void LLFloaterTelehub::onClickRemoveSpawnPoint() -{ - LLScrollListCtrl* list = getChild("spawn_points_list"); - if (!list) - return; - - S32 spawn_index = list->getFirstSelectedIndex(); - if (spawn_index < 0) return; // nothing selected - - LLMessageSystem* msg = gMessageSystem; - - // Could be god or estate owner. If neither, server will reject message. - if (gAgent.isGodlike()) - { - msg->newMessage("GodlikeMessage"); - } - else - { - msg->newMessage("EstateOwnerMessage"); - } - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->addUUIDFast(_PREHASH_TransactionID, LLUUID::null); //not used - msg->nextBlock("MethodData"); - msg->addString("Method", "telehub"); - msg->addUUID("Invoice", LLUUID::null); - - msg->nextBlock("ParamList"); - msg->addString("Parameter", "spawnpoint remove"); - - std::string buffer; - buffer = llformat("%d", spawn_index); - msg->nextBlock("ParamList"); - msg->addString("Parameter", buffer); - - gAgent.sendReliableMessage(); -} - -// static -void LLFloaterTelehub::processTelehubInfo(LLMessageSystem* msg, void**) -{ - LLFloaterTelehub* floater = LLFloaterReg::findTypedInstance("telehubs"); - if (floater) - { - floater->unpackTelehubInfo(msg); - } -} - -void LLFloaterTelehub::unpackTelehubInfo(LLMessageSystem* msg) -{ - msg->getUUID("TelehubBlock", "ObjectID", mTelehubObjectID); - msg->getString("TelehubBlock", "ObjectName", mTelehubObjectName); - msg->getVector3("TelehubBlock", "TelehubPos", mTelehubPos); - msg->getQuat("TelehubBlock", "TelehubRot", mTelehubRot); - - mNumSpawn = msg->getNumberOfBlocks("SpawnPointBlock"); - for (S32 i = 0; i < mNumSpawn; i++) - { - msg->getVector3("SpawnPointBlock", "SpawnPointPos", mSpawnPointPos[i], i); - } - - // Update parts of the UI that change only when message received. - - if (mTelehubObjectID.isNull()) - { - getChildView("status_text_connected")->setVisible( false); - getChildView("status_text_not_connected")->setVisible( true); - getChildView("help_text_connected")->setVisible( false); - getChildView("help_text_not_connected")->setVisible( true); - } - else - { - getChild("status_text_connected")->setTextArg("[OBJECT]", mTelehubObjectName); - getChildView("status_text_connected")->setVisible( true); - getChildView("status_text_not_connected")->setVisible( false); - getChildView("help_text_connected")->setVisible( true); - getChildView("help_text_not_connected")->setVisible( false); - } - - LLScrollListCtrl* list = getChild("spawn_points_list"); - if (list) - { - list->deleteAllItems(); - for (S32 i = 0; i < mNumSpawn; i++) - { - std::string pos = llformat("%.1f, %.1f, %.1f", - mSpawnPointPos[i].mV[VX], - mSpawnPointPos[i].mV[VY], - mSpawnPointPos[i].mV[VZ]); - list->addSimpleElement(pos); - } - list->selectNthItem(mNumSpawn - 1); - } -} +/** + * @file llfloatertelehub.cpp + * @author James Cook + * @brief LLFloaterTelehub class implementation + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatertelehub.h" + +#include "message.h" +#include "llfontgl.h" + +#include "llagent.h" +#include "llfloaterreg.h" +#include "llfloatertools.h" +#include "llscrolllistctrl.h" +#include "llselectmgr.h" +#include "lltoolcomp.h" +#include "lltoolmgr.h" +#include "llviewerobject.h" +#include "llviewerobjectlist.h" +#include "lluictrlfactory.h" + +LLFloaterTelehub::LLFloaterTelehub(const LLSD& key) +: LLFloater(key), + mTelehubObjectID(), + mTelehubObjectName(), + mTelehubPos(), + mTelehubRot(), + mNumSpawn(0) +{ +} + +bool LLFloaterTelehub::postBuild() +{ + gMessageSystem->setHandlerFunc("TelehubInfo", processTelehubInfo); + + getChild("connect_btn")->setCommitCallback(boost::bind(&LLFloaterTelehub::onClickConnect, this)); + getChild("disconnect_btn")->setCommitCallback(boost::bind(&LLFloaterTelehub::onClickDisconnect, this)); + getChild("add_spawn_point_btn")->setCommitCallback(boost::bind(&LLFloaterTelehub::onClickAddSpawnPoint, this)); + getChild("remove_spawn_point_btn")->setCommitCallback(boost::bind(&LLFloaterTelehub::onClickRemoveSpawnPoint, this)); + + LLScrollListCtrl* list = getChild("spawn_points_list"); + if (list) + { + // otherwise you can't walk with arrow keys while floater is up + list->setAllowKeyboardMovement(false); + } + + return true; +} +void LLFloaterTelehub::onOpen(const LLSD& key) +{ + // Show tools floater by selecting translate (select) tool + LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); + LLToolMgr::getInstance()->getCurrentToolset()->selectTool( LLToolCompTranslate::getInstance() ); + + sendTelehubInfoRequest(); + + mObjectSelection = LLSelectMgr::getInstance()->getEditSelection(); +} + +LLFloaterTelehub::~LLFloaterTelehub() +{ + // no longer interested in this message + gMessageSystem->setHandlerFunc("TelehubInfo", NULL); +} + +void LLFloaterTelehub::draw() +{ + if (!isMinimized()) + { + refresh(); + } + LLFloater::draw(); +} + +// Per-frame updates, because we don't have a selection manager observer. +void LLFloaterTelehub::refresh() +{ + constexpr bool children_ok = true; + LLViewerObject* object = mObjectSelection->getFirstRootObject(children_ok); + + bool have_selection = (object != NULL); + bool all_volume = LLSelectMgr::getInstance()->selectionAllPCode( LL_PCODE_VOLUME ); + getChildView("connect_btn")->setEnabled(have_selection && all_volume); + + bool have_telehub = mTelehubObjectID.notNull(); + getChildView("disconnect_btn")->setEnabled(have_telehub); + + bool space_avail = (mNumSpawn < MAX_SPAWNPOINTS_PER_TELEHUB); + getChildView("add_spawn_point_btn")->setEnabled(have_selection && all_volume && space_avail); + + LLScrollListCtrl* list = getChild("spawn_points_list"); + if (list) + { + bool enable_remove = (list->getFirstSelected() != NULL); + getChildView("remove_spawn_point_btn")->setEnabled(enable_remove); + } +} + +// static +bool LLFloaterTelehub::renderBeacons() +{ + // only render if we've got a telehub + LLFloaterTelehub* floater = LLFloaterReg::findTypedInstance("telehubs"); + return floater && floater->mTelehubObjectID.notNull(); +} + +// static +void LLFloaterTelehub::addBeacons() +{ + LLFloaterTelehub* floater = LLFloaterReg::findTypedInstance("telehubs"); + if (!floater) + return; + + // Find the telehub position, either our cached old position, or + // an updated one based on the actual object position. + LLVector3 hub_pos_region = floater->mTelehubPos; + LLQuaternion hub_rot = floater->mTelehubRot; + LLViewerObject* obj = gObjectList.findObject(floater->mTelehubObjectID); + if (obj) + { + hub_pos_region = obj->getPositionRegion(); + hub_rot = obj->getRotationRegion(); + } + // Draw nice thick 3-pixel lines. + gObjectList.addDebugBeacon(hub_pos_region, "", LLColor4::yellow, LLColor4::white, 4); + + LLScrollListCtrl* list = floater->getChild("spawn_points_list"); + if (list) + { + S32 spawn_index = list->getFirstSelectedIndex(); + if (spawn_index >= 0) + { + LLVector3 spawn_pos = hub_pos_region + (floater->mSpawnPointPos[spawn_index] * hub_rot); + gObjectList.addDebugBeacon(spawn_pos, "", LLColor4::orange, LLColor4::white, 4); + } + } +} + +void LLFloaterTelehub::sendTelehubInfoRequest() +{ + LLSelectMgr::getInstance()->sendGodlikeRequest("telehub", "info ui"); +} + +void LLFloaterTelehub::onClickConnect() +{ + LLSelectMgr::getInstance()->sendGodlikeRequest("telehub", "connect"); +} + +void LLFloaterTelehub::onClickDisconnect() +{ + LLSelectMgr::getInstance()->sendGodlikeRequest("telehub", "delete"); +} + +void LLFloaterTelehub::onClickAddSpawnPoint() +{ + LLSelectMgr::getInstance()->sendGodlikeRequest("telehub", "spawnpoint add"); + LLSelectMgr::getInstance()->deselectAll(); +} + +void LLFloaterTelehub::onClickRemoveSpawnPoint() +{ + LLScrollListCtrl* list = getChild("spawn_points_list"); + if (!list) + return; + + S32 spawn_index = list->getFirstSelectedIndex(); + if (spawn_index < 0) return; // nothing selected + + LLMessageSystem* msg = gMessageSystem; + + // Could be god or estate owner. If neither, server will reject message. + if (gAgent.isGodlike()) + { + msg->newMessage("GodlikeMessage"); + } + else + { + msg->newMessage("EstateOwnerMessage"); + } + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->addUUIDFast(_PREHASH_TransactionID, LLUUID::null); //not used + msg->nextBlock("MethodData"); + msg->addString("Method", "telehub"); + msg->addUUID("Invoice", LLUUID::null); + + msg->nextBlock("ParamList"); + msg->addString("Parameter", "spawnpoint remove"); + + std::string buffer; + buffer = llformat("%d", spawn_index); + msg->nextBlock("ParamList"); + msg->addString("Parameter", buffer); + + gAgent.sendReliableMessage(); +} + +// static +void LLFloaterTelehub::processTelehubInfo(LLMessageSystem* msg, void**) +{ + LLFloaterTelehub* floater = LLFloaterReg::findTypedInstance("telehubs"); + if (floater) + { + floater->unpackTelehubInfo(msg); + } +} + +void LLFloaterTelehub::unpackTelehubInfo(LLMessageSystem* msg) +{ + msg->getUUID("TelehubBlock", "ObjectID", mTelehubObjectID); + msg->getString("TelehubBlock", "ObjectName", mTelehubObjectName); + msg->getVector3("TelehubBlock", "TelehubPos", mTelehubPos); + msg->getQuat("TelehubBlock", "TelehubRot", mTelehubRot); + + mNumSpawn = msg->getNumberOfBlocks("SpawnPointBlock"); + for (S32 i = 0; i < mNumSpawn; i++) + { + msg->getVector3("SpawnPointBlock", "SpawnPointPos", mSpawnPointPos[i], i); + } + + // Update parts of the UI that change only when message received. + + if (mTelehubObjectID.isNull()) + { + getChildView("status_text_connected")->setVisible( false); + getChildView("status_text_not_connected")->setVisible( true); + getChildView("help_text_connected")->setVisible( false); + getChildView("help_text_not_connected")->setVisible( true); + } + else + { + getChild("status_text_connected")->setTextArg("[OBJECT]", mTelehubObjectName); + getChildView("status_text_connected")->setVisible( true); + getChildView("status_text_not_connected")->setVisible( false); + getChildView("help_text_connected")->setVisible( true); + getChildView("help_text_not_connected")->setVisible( false); + } + + LLScrollListCtrl* list = getChild("spawn_points_list"); + if (list) + { + list->deleteAllItems(); + for (S32 i = 0; i < mNumSpawn; i++) + { + std::string pos = llformat("%.1f, %.1f, %.1f", + mSpawnPointPos[i].mV[VX], + mSpawnPointPos[i].mV[VY], + mSpawnPointPos[i].mV[VZ]); + list->addSimpleElement(pos); + } + list->selectNthItem(mNumSpawn - 1); + } +} diff --git a/indra/newview/llfloatertelehub.h b/indra/newview/llfloatertelehub.h index f35bd97a4e..2ae450ba32 100644 --- a/indra/newview/llfloatertelehub.h +++ b/indra/newview/llfloatertelehub.h @@ -1,77 +1,77 @@ -/** - * @file llfloatertelehub.h - * @author James Cook - * @brief LLFloaterTelehub class definition - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERTELEHUB_H -#define LL_LLFLOATERTELEHUB_H - -#include "llfloater.h" - -class LLMessageSystem; -class LLObjectSelection; - -const S32 MAX_SPAWNPOINTS_PER_TELEHUB = 16; - -class LLFloaterTelehub : public LLFloater -{ -public: - LLFloaterTelehub(const LLSD& key); - ~LLFloaterTelehub(); - - bool postBuild() override; - void onOpen(const LLSD& key) override; - - void draw() override; - - static bool renderBeacons(); - static void addBeacons(); - - void refresh() override; - void sendTelehubInfoRequest(); - - void onClickConnect(); - void onClickDisconnect(); - void onClickAddSpawnPoint(); - void onClickRemoveSpawnPoint(); - - static void processTelehubInfo(LLMessageSystem* msg, void**); - void unpackTelehubInfo(LLMessageSystem* msg); - -private: - LLUUID mTelehubObjectID; // null if no telehub - std::string mTelehubObjectName; - LLVector3 mTelehubPos; // region local, fallback if viewer can't see the object - LLQuaternion mTelehubRot; - - S32 mNumSpawn; - LLVector3 mSpawnPointPos[MAX_SPAWNPOINTS_PER_TELEHUB]; - - LLSafeHandle mObjectSelection; - - static LLFloaterTelehub* sInstance; -}; - -#endif +/** + * @file llfloatertelehub.h + * @author James Cook + * @brief LLFloaterTelehub class definition + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERTELEHUB_H +#define LL_LLFLOATERTELEHUB_H + +#include "llfloater.h" + +class LLMessageSystem; +class LLObjectSelection; + +const S32 MAX_SPAWNPOINTS_PER_TELEHUB = 16; + +class LLFloaterTelehub : public LLFloater +{ +public: + LLFloaterTelehub(const LLSD& key); + ~LLFloaterTelehub(); + + bool postBuild() override; + void onOpen(const LLSD& key) override; + + void draw() override; + + static bool renderBeacons(); + static void addBeacons(); + + void refresh() override; + void sendTelehubInfoRequest(); + + void onClickConnect(); + void onClickDisconnect(); + void onClickAddSpawnPoint(); + void onClickRemoveSpawnPoint(); + + static void processTelehubInfo(LLMessageSystem* msg, void**); + void unpackTelehubInfo(LLMessageSystem* msg); + +private: + LLUUID mTelehubObjectID; // null if no telehub + std::string mTelehubObjectName; + LLVector3 mTelehubPos; // region local, fallback if viewer can't see the object + LLQuaternion mTelehubRot; + + S32 mNumSpawn; + LLVector3 mSpawnPointPos[MAX_SPAWNPOINTS_PER_TELEHUB]; + + LLSafeHandle mObjectSelection; + + static LLFloaterTelehub* sInstance; +}; + +#endif diff --git a/indra/newview/llfloatertestinspectors.cpp b/indra/newview/llfloatertestinspectors.cpp index f3c60c927e..7f8d122435 100644 --- a/indra/newview/llfloatertestinspectors.cpp +++ b/indra/newview/llfloatertestinspectors.cpp @@ -1,113 +1,113 @@ -/** -* @file llfloatertestinspectors.cpp -* -* $LicenseInfo:firstyear=2009&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2010, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ -#include "llviewerprecompiledheaders.h" - -#include "llfloatertestinspectors.h" - -// Viewer includes -#include "llstartup.h" - -// Linden library includes -#include "llfloaterreg.h" -//#include "lluictrlfactory.h" - -LLFloaterTestInspectors::LLFloaterTestInspectors(const LLSD& seed) -: LLFloater(seed) -{ - mCommitCallbackRegistrar.add("ShowAvatarInspector", - boost::bind(&LLFloaterTestInspectors::showAvatarInspector, this, _1, _2)); - mCommitCallbackRegistrar.add("ShowObjectInspector", - boost::bind(&LLFloaterTestInspectors::showObjectInspector, this, _1, _2)); -} - -LLFloaterTestInspectors::~LLFloaterTestInspectors() -{} - -bool LLFloaterTestInspectors::postBuild() -{ - // Test the dummy widget construction code - getChild("intentionally-not-found")->setEnabled(true); - -// getChild("avatar_2d_btn")->setCommitCallback( -// boost::bind(&LLFloaterTestInspectors::onClickAvatar2D, this)); - getChild("avatar_3d_btn")->setCommitCallback( - boost::bind(&LLFloaterTestInspectors::onClickAvatar3D, this)); - getChild("object_2d_btn")->setCommitCallback( - boost::bind(&LLFloaterTestInspectors::onClickObject2D, this)); - getChild("object_3d_btn")->setCommitCallback( - boost::bind(&LLFloaterTestInspectors::onClickObject3D, this)); - getChild("group_btn")->setCommitCallback( - boost::bind(&LLFloaterTestInspectors::onClickGroup, this)); - getChild("place_btn")->setCommitCallback( - boost::bind(&LLFloaterTestInspectors::onClickPlace, this)); - getChild("event_btn")->setCommitCallback( - boost::bind(&LLFloaterTestInspectors::onClickEvent, this)); - - return LLFloater::postBuild(); -} - -void LLFloaterTestInspectors::showAvatarInspector(LLUICtrl*, const LLSD& avatar_id) -{ - LLUUID id; // defaults to null - if (LLStartUp::getStartupState() >= STATE_STARTED) - { - id = avatar_id.asUUID(); - } - // spawns off mouse position automatically - LLFloaterReg::showInstance("inspect_avatar", LLSD().with("avatar_id", id)); -} - -void LLFloaterTestInspectors::showObjectInspector(LLUICtrl*, const LLSD& object_id) -{ - LLFloaterReg::showInstance("inspect_object", LLSD().with("object_id", object_id)); -} - -void LLFloaterTestInspectors::onClickAvatar2D() -{ -} - -void LLFloaterTestInspectors::onClickAvatar3D() -{ -} - -void LLFloaterTestInspectors::onClickObject2D() -{ -} - -void LLFloaterTestInspectors::onClickObject3D() -{ -} - -void LLFloaterTestInspectors::onClickGroup() -{ -} - -void LLFloaterTestInspectors::onClickPlace() -{ -} - -void LLFloaterTestInspectors::onClickEvent() -{ -} +/** +* @file llfloatertestinspectors.cpp +* +* $LicenseInfo:firstyear=2009&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2010, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#include "llviewerprecompiledheaders.h" + +#include "llfloatertestinspectors.h" + +// Viewer includes +#include "llstartup.h" + +// Linden library includes +#include "llfloaterreg.h" +//#include "lluictrlfactory.h" + +LLFloaterTestInspectors::LLFloaterTestInspectors(const LLSD& seed) +: LLFloater(seed) +{ + mCommitCallbackRegistrar.add("ShowAvatarInspector", + boost::bind(&LLFloaterTestInspectors::showAvatarInspector, this, _1, _2)); + mCommitCallbackRegistrar.add("ShowObjectInspector", + boost::bind(&LLFloaterTestInspectors::showObjectInspector, this, _1, _2)); +} + +LLFloaterTestInspectors::~LLFloaterTestInspectors() +{} + +bool LLFloaterTestInspectors::postBuild() +{ + // Test the dummy widget construction code + getChild("intentionally-not-found")->setEnabled(true); + +// getChild("avatar_2d_btn")->setCommitCallback( +// boost::bind(&LLFloaterTestInspectors::onClickAvatar2D, this)); + getChild("avatar_3d_btn")->setCommitCallback( + boost::bind(&LLFloaterTestInspectors::onClickAvatar3D, this)); + getChild("object_2d_btn")->setCommitCallback( + boost::bind(&LLFloaterTestInspectors::onClickObject2D, this)); + getChild("object_3d_btn")->setCommitCallback( + boost::bind(&LLFloaterTestInspectors::onClickObject3D, this)); + getChild("group_btn")->setCommitCallback( + boost::bind(&LLFloaterTestInspectors::onClickGroup, this)); + getChild("place_btn")->setCommitCallback( + boost::bind(&LLFloaterTestInspectors::onClickPlace, this)); + getChild("event_btn")->setCommitCallback( + boost::bind(&LLFloaterTestInspectors::onClickEvent, this)); + + return LLFloater::postBuild(); +} + +void LLFloaterTestInspectors::showAvatarInspector(LLUICtrl*, const LLSD& avatar_id) +{ + LLUUID id; // defaults to null + if (LLStartUp::getStartupState() >= STATE_STARTED) + { + id = avatar_id.asUUID(); + } + // spawns off mouse position automatically + LLFloaterReg::showInstance("inspect_avatar", LLSD().with("avatar_id", id)); +} + +void LLFloaterTestInspectors::showObjectInspector(LLUICtrl*, const LLSD& object_id) +{ + LLFloaterReg::showInstance("inspect_object", LLSD().with("object_id", object_id)); +} + +void LLFloaterTestInspectors::onClickAvatar2D() +{ +} + +void LLFloaterTestInspectors::onClickAvatar3D() +{ +} + +void LLFloaterTestInspectors::onClickObject2D() +{ +} + +void LLFloaterTestInspectors::onClickObject3D() +{ +} + +void LLFloaterTestInspectors::onClickGroup() +{ +} + +void LLFloaterTestInspectors::onClickPlace() +{ +} + +void LLFloaterTestInspectors::onClickEvent() +{ +} diff --git a/indra/newview/llfloatertestinspectors.h b/indra/newview/llfloatertestinspectors.h index 98124bfe0e..ab634771d5 100644 --- a/indra/newview/llfloatertestinspectors.h +++ b/indra/newview/llfloatertestinspectors.h @@ -1,59 +1,59 @@ -/** -* @file llfloatertestinspectors.h -* -* $LicenseInfo:firstyear=2009&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2010, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ -#ifndef LLFLOATERTESTINSPECTORS_H -#define LLFLOATERTESTINSPECTORS_H - -#include "llfloater.h" - -class LLSD; - -class LLFloaterTestInspectors : public LLFloater -{ - friend class LLFloaterReg; -public: - // nothing yet - -private: - // Construction handled by LLFloaterReg - LLFloaterTestInspectors(const LLSD& seed); - ~LLFloaterTestInspectors(); - - bool postBuild() override; - - // Button callback to show - void showAvatarInspector(LLUICtrl*, const LLSD& avatar_id); - void showObjectInspector(LLUICtrl*, const LLSD& avatar_id); - - // Debug function hookups for buttons - void onClickAvatar2D(); - void onClickAvatar3D(); - void onClickObject2D(); - void onClickObject3D(); - void onClickGroup(); - void onClickPlace(); - void onClickEvent(); -}; - -#endif +/** +* @file llfloatertestinspectors.h +* +* $LicenseInfo:firstyear=2009&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2010, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LLFLOATERTESTINSPECTORS_H +#define LLFLOATERTESTINSPECTORS_H + +#include "llfloater.h" + +class LLSD; + +class LLFloaterTestInspectors : public LLFloater +{ + friend class LLFloaterReg; +public: + // nothing yet + +private: + // Construction handled by LLFloaterReg + LLFloaterTestInspectors(const LLSD& seed); + ~LLFloaterTestInspectors(); + + bool postBuild() override; + + // Button callback to show + void showAvatarInspector(LLUICtrl*, const LLSD& avatar_id); + void showObjectInspector(LLUICtrl*, const LLSD& avatar_id); + + // Debug function hookups for buttons + void onClickAvatar2D(); + void onClickAvatar3D(); + void onClickObject2D(); + void onClickObject3D(); + void onClickGroup(); + void onClickPlace(); + void onClickEvent(); +}; + +#endif diff --git a/indra/newview/llfloatertools.cpp b/indra/newview/llfloatertools.cpp index fd27f4e496..aadc5b9580 100644 --- a/indra/newview/llfloatertools.cpp +++ b/indra/newview/llfloatertools.cpp @@ -1,1170 +1,1170 @@ -/** - * @file llfloatertools.cpp - * @brief The edit tools, including move, position, land, etc. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloatertools.h" - -#include "llfontgl.h" -#include "llcoord.h" -//#include "llgl.h" - -#include "llagent.h" -#include "llagentcamera.h" -#include "llbutton.h" -#include "llcheckboxctrl.h" -#include "llcombobox.h" -#include "lldraghandle.h" -#include "llerror.h" -#include "llfloaterbuildoptions.h" -#include "llfloatermediasettings.h" -#include "llfloateropenobject.h" -#include "llfloaterobjectweights.h" -#include "llfloaterreg.h" -#include "llfocusmgr.h" -#include "llmediaentry.h" -#include "llmenugl.h" -#include "llnotificationsutil.h" -#include "llpanelcontents.h" -#include "llpanelface.h" -#include "llpanelland.h" -#include "llpanelobjectinventory.h" -#include "llpanelobject.h" -#include "llpanelvolume.h" -#include "llpanelpermissions.h" -#include "llparcel.h" -#include "llradiogroup.h" -#include "llresmgr.h" -#include "llselectmgr.h" -#include "llslider.h" -#include "llstatusbar.h" -#include "lltabcontainer.h" -#include "lltextbox.h" -#include "lltoolbrush.h" -#include "lltoolcomp.h" -#include "lltooldraganddrop.h" -#include "lltoolface.h" -#include "lltoolfocus.h" -#include "lltoolgrab.h" -#include "lltoolgrab.h" -#include "lltoolindividual.h" -#include "lltoolmgr.h" -#include "lltoolpie.h" -#include "lltoolpipette.h" -#include "lltoolplacer.h" -#include "lltoolselectland.h" -#include "lltrans.h" -#include "llui.h" -#include "llviewercontrol.h" -#include "llviewerjoystick.h" -#include "llviewerregion.h" -#include "llviewermenu.h" -#include "llviewerparcelmgr.h" -#include "llviewerwindow.h" -#include "llvovolume.h" -#include "lluictrlfactory.h" -#include "llmeshrepository.h" - -// Globals -LLFloaterTools *gFloaterTools = NULL; -bool LLFloaterTools::sShowObjectCost = true; -bool LLFloaterTools::sPreviousFocusOnAvatar = false; - -const std::string PANEL_NAMES[LLFloaterTools::PANEL_COUNT] = -{ - std::string("General"), // PANEL_GENERAL, - std::string("Object"), // PANEL_OBJECT, - std::string("Features"), // PANEL_FEATURES, - std::string("Texture"), // PANEL_FACE, - std::string("Content"), // PANEL_CONTENTS, -}; - - -// Local prototypes -void commit_grid_mode(LLUICtrl *ctrl); -void commit_select_component(void *data); -void click_show_more(void*); -void click_popup_info(void*); -void click_popup_done(void*); -void click_popup_minimize(void*); -void commit_slider_dozer_force(LLUICtrl *); -void click_apply_to_selection(void*); -void commit_radio_group_focus(LLUICtrl* ctrl); -void commit_radio_group_move(LLUICtrl* ctrl); -void commit_radio_group_edit(LLUICtrl* ctrl); -void commit_radio_group_land(LLUICtrl* ctrl); -void commit_slider_zoom(LLUICtrl *ctrl); - -/** - * Class LLLandImpactsObserver - * - * An observer class to monitor parcel selection and update - * the land impacts data from a parcel containing the selected object. - */ -class LLLandImpactsObserver : public LLParcelObserver -{ -public: - virtual void changed() - { - LLFloaterTools* tools_floater = LLFloaterReg::getTypedInstance("build"); - if(tools_floater) - { - tools_floater->updateLandImpacts(); - } - } -}; - -//static -void* LLFloaterTools::createPanelPermissions(void* data) -{ - LLFloaterTools* floater = (LLFloaterTools*)data; - floater->mPanelPermissions = new LLPanelPermissions(); - return floater->mPanelPermissions; -} -//static -void* LLFloaterTools::createPanelObject(void* data) -{ - LLFloaterTools* floater = (LLFloaterTools*)data; - floater->mPanelObject = new LLPanelObject(); - return floater->mPanelObject; -} - -//static -void* LLFloaterTools::createPanelVolume(void* data) -{ - LLFloaterTools* floater = (LLFloaterTools*)data; - floater->mPanelVolume = new LLPanelVolume(); - return floater->mPanelVolume; -} - -//static -void* LLFloaterTools::createPanelFace(void* data) -{ - LLFloaterTools* floater = (LLFloaterTools*)data; - floater->mPanelFace = new LLPanelFace(); - return floater->mPanelFace; -} - -//static -void* LLFloaterTools::createPanelContents(void* data) -{ - LLFloaterTools* floater = (LLFloaterTools*)data; - floater->mPanelContents = new LLPanelContents(); - return floater->mPanelContents; -} - -//static -void* LLFloaterTools::createPanelLandInfo(void* data) -{ - LLFloaterTools* floater = (LLFloaterTools*)data; - floater->mPanelLandInfo = new LLPanelLandInfo(); - return floater->mPanelLandInfo; -} - -static const std::string toolNames[]={ - "ToolCube", - "ToolPrism", - "ToolPyramid", - "ToolTetrahedron", - "ToolCylinder", - "ToolHemiCylinder", - "ToolCone", - "ToolHemiCone", - "ToolSphere", - "ToolHemiSphere", - "ToolTorus", - "ToolTube", - "ToolRing", - "ToolTree", - "ToolGrass"}; -LLPCode toolData[]={ - LL_PCODE_CUBE, - LL_PCODE_PRISM, - LL_PCODE_PYRAMID, - LL_PCODE_TETRAHEDRON, - LL_PCODE_CYLINDER, - LL_PCODE_CYLINDER_HEMI, - LL_PCODE_CONE, - LL_PCODE_CONE_HEMI, - LL_PCODE_SPHERE, - LL_PCODE_SPHERE_HEMI, - LL_PCODE_TORUS, - LLViewerObject::LL_VO_SQUARE_TORUS, - LLViewerObject::LL_VO_TRIANGLE_TORUS, - LL_PCODE_LEGACY_TREE, - LL_PCODE_LEGACY_GRASS}; - -bool LLFloaterTools::postBuild() -{ - // Hide until tool selected - setVisible(false); - - // Since we constantly show and hide this during drags, don't - // make sounds on visibility changes. - setSoundFlags(LLView::SILENT); - - getDragHandle()->setEnabled( !gSavedSettings.getBOOL("ToolboxAutoMove") ); - - LLRect rect; - mBtnFocus = getChild("button focus");//btn; - mBtnMove = getChild("button move"); - mBtnEdit = getChild("button edit"); - mBtnCreate = getChild("button create"); - mBtnLand = getChild("button land" ); - mTextStatus = getChild("text status"); - mRadioGroupFocus = getChild("focus_radio_group"); - mRadioGroupMove = getChild("move_radio_group"); - mRadioGroupEdit = getChild("edit_radio_group"); - mBtnGridOptions = getChild("Options..."); - mBtnLink = getChild("link_btn"); - mBtnUnlink = getChild("unlink_btn"); - - mCheckSelectIndividual = getChild("checkbox edit linked parts"); - getChild("checkbox edit linked parts")->setValue((bool)gSavedSettings.getBOOL("EditLinkedParts")); - mCheckSnapToGrid = getChild("checkbox snap to grid"); - getChild("checkbox snap to grid")->setValue((bool)gSavedSettings.getBOOL("SnapEnabled")); - mCheckStretchUniform = getChild("checkbox uniform"); - getChild("checkbox uniform")->setValue((bool)gSavedSettings.getBOOL("ScaleUniform")); - mCheckStretchTexture = getChild("checkbox stretch textures"); - getChild("checkbox stretch textures")->setValue((bool)gSavedSettings.getBOOL("ScaleStretchTextures")); - mComboGridMode = getChild("combobox grid mode"); - - // - // Create Buttons - // - - for(size_t t=0; t(toolNames[t]); - if(found) - { - found->setClickedCallback(boost::bind(&LLFloaterTools::setObjectType, toolData[t])); - mButtons.push_back( found ); - }else{ - LL_WARNS() << "Tool button not found! DOA Pending." << LL_ENDL; - } - } - mCheckCopySelection = getChild("checkbox copy selection"); - getChild("checkbox copy selection")->setValue((bool)gSavedSettings.getBOOL("CreateToolCopySelection")); - mCheckSticky = getChild("checkbox sticky"); - getChild("checkbox sticky")->setValue((bool)gSavedSettings.getBOOL("CreateToolKeepSelected")); - mCheckCopyCenters = getChild("checkbox copy centers"); - getChild("checkbox copy centers")->setValue((bool)gSavedSettings.getBOOL("CreateToolCopyCenters")); - mCheckCopyRotates = getChild("checkbox copy rotates"); - getChild("checkbox copy rotates")->setValue((bool)gSavedSettings.getBOOL("CreateToolCopyRotates")); - - mRadioGroupLand = getChild("land_radio_group"); - mBtnApplyToSelection = getChild("button apply to selection"); - mSliderDozerSize = getChild("slider brush size"); - getChild("slider brush size")->setValue(gSavedSettings.getF32("LandBrushSize")); - mSliderDozerForce = getChild("slider force"); - // the setting stores the actual force multiplier, but the slider is logarithmic, so we convert here - getChild("slider force")->setValue(log10(gSavedSettings.getF32("LandBrushForce"))); - - mCostTextBorder = getChild("cost_text_border"); - - mTab = getChild("Object Info Tabs"); - if(mTab) - { - mTab->setFollows(FOLLOWS_TOP | FOLLOWS_LEFT); - mTab->setBorderVisible(false); - mTab->selectFirstTab(); - } - - mStatusText["rotate"] = getString("status_rotate"); - mStatusText["scale"] = getString("status_scale"); - mStatusText["move"] = getString("status_move"); - mStatusText["modifyland"] = getString("status_modifyland"); - mStatusText["camera"] = getString("status_camera"); - mStatusText["grab"] = getString("status_grab"); - mStatusText["place"] = getString("status_place"); - mStatusText["selectland"] = getString("status_selectland"); - - sShowObjectCost = gSavedSettings.getBOOL("ShowObjectRenderingCost"); - - return true; -} - -// Create the popupview with a dummy center. It will be moved into place -// during LLViewerWindow's per-frame hover processing. -LLFloaterTools::LLFloaterTools(const LLSD& key) -: LLFloater(key), - mBtnFocus(NULL), - mBtnMove(NULL), - mBtnEdit(NULL), - mBtnCreate(NULL), - mBtnLand(NULL), - mTextStatus(NULL), - - mRadioGroupFocus(NULL), - mRadioGroupMove(NULL), - mRadioGroupEdit(NULL), - - mCheckSelectIndividual(NULL), - - mCheckSnapToGrid(NULL), - mBtnGridOptions(NULL), - mComboGridMode(NULL), - mCheckStretchUniform(NULL), - mCheckStretchTexture(NULL), - mCheckStretchUniformLabel(NULL), - - mBtnRotateLeft(NULL), - mBtnRotateReset(NULL), - mBtnRotateRight(NULL), - - mBtnLink(NULL), - mBtnUnlink(NULL), - - mBtnDelete(NULL), - mBtnDuplicate(NULL), - mBtnDuplicateInPlace(NULL), - - mCheckSticky(NULL), - mCheckCopySelection(NULL), - mCheckCopyCenters(NULL), - mCheckCopyRotates(NULL), - mRadioGroupLand(NULL), - mSliderDozerSize(NULL), - mSliderDozerForce(NULL), - mBtnApplyToSelection(NULL), - - mTab(NULL), - mPanelPermissions(NULL), - mPanelObject(NULL), - mPanelVolume(NULL), - mPanelContents(NULL), - mPanelFace(NULL), - mPanelLandInfo(NULL), - - mCostTextBorder(NULL), - mTabLand(NULL), - - mLandImpactsObserver(NULL), - - mDirty(true), - mHasSelection(true) -{ - gFloaterTools = this; - - setAutoFocus(false); - mFactoryMap["General"] = LLCallbackMap(createPanelPermissions, this);//LLPanelPermissions - mFactoryMap["Object"] = LLCallbackMap(createPanelObject, this);//LLPanelObject - mFactoryMap["Features"] = LLCallbackMap(createPanelVolume, this);//LLPanelVolume - mFactoryMap["Texture"] = LLCallbackMap(createPanelFace, this);//LLPanelFace - mFactoryMap["Contents"] = LLCallbackMap(createPanelContents, this);//LLPanelContents - mFactoryMap["land info panel"] = LLCallbackMap(createPanelLandInfo, this);//LLPanelLandInfo - - mCommitCallbackRegistrar.add("BuildTool.setTool", boost::bind(&LLFloaterTools::setTool,this, _2)); - mCommitCallbackRegistrar.add("BuildTool.commitZoom", boost::bind(&commit_slider_zoom, _1)); - mCommitCallbackRegistrar.add("BuildTool.commitRadioFocus", boost::bind(&commit_radio_group_focus, _1)); - mCommitCallbackRegistrar.add("BuildTool.commitRadioMove", boost::bind(&commit_radio_group_move,_1)); - mCommitCallbackRegistrar.add("BuildTool.commitRadioEdit", boost::bind(&commit_radio_group_edit,_1)); - - mCommitCallbackRegistrar.add("BuildTool.gridMode", boost::bind(&commit_grid_mode,_1)); - mCommitCallbackRegistrar.add("BuildTool.selectComponent", boost::bind(&commit_select_component, this)); - mCommitCallbackRegistrar.add("BuildTool.gridOptions", boost::bind(&LLFloaterTools::onClickGridOptions,this)); - mCommitCallbackRegistrar.add("BuildTool.applyToSelection", boost::bind(&click_apply_to_selection, this)); - mCommitCallbackRegistrar.add("BuildTool.commitRadioLand", boost::bind(&commit_radio_group_land,_1)); - mCommitCallbackRegistrar.add("BuildTool.LandBrushForce", boost::bind(&commit_slider_dozer_force,_1)); - - mCommitCallbackRegistrar.add("BuildTool.LinkObjects", boost::bind(&LLSelectMgr::linkObjects, LLSelectMgr::getInstance())); - mCommitCallbackRegistrar.add("BuildTool.UnlinkObjects", boost::bind(&LLSelectMgr::unlinkObjects, LLSelectMgr::getInstance())); - - mLandImpactsObserver = new LLLandImpactsObserver(); - LLViewerParcelMgr::getInstance()->addObserver(mLandImpactsObserver); -} - -LLFloaterTools::~LLFloaterTools() -{ - // children automatically deleted - gFloaterTools = NULL; - - LLViewerParcelMgr::getInstance()->removeObserver(mLandImpactsObserver); - delete mLandImpactsObserver; -} - -void LLFloaterTools::setStatusText(const std::string& text) -{ - std::map::iterator iter = mStatusText.find(text); - if (iter != mStatusText.end()) - { - mTextStatus->setText(iter->second); - } - else - { - mTextStatus->setText(text); - } -} - -void LLFloaterTools::refresh() -{ - const S32 INFO_WIDTH = getRect().getWidth(); - const S32 INFO_HEIGHT = 384; - LLRect object_info_rect(0, 0, INFO_WIDTH, -INFO_HEIGHT); - bool all_volume = LLSelectMgr::getInstance()->selectionAllPCode( LL_PCODE_VOLUME ); - - S32 idx_features = mTab->getPanelIndexByTitle(PANEL_NAMES[PANEL_FEATURES]); - S32 idx_face = mTab->getPanelIndexByTitle(PANEL_NAMES[PANEL_FACE]); - S32 idx_contents = mTab->getPanelIndexByTitle(PANEL_NAMES[PANEL_CONTENTS]); - - S32 selected_index = mTab->getCurrentPanelIndex(); - - if (!all_volume && (selected_index == idx_features || selected_index == idx_face || - selected_index == idx_contents)) - { - mTab->selectFirstTab(); - } - - mTab->enableTabButton(idx_features, all_volume); - mTab->enableTabButton(idx_face, all_volume); - mTab->enableTabButton(idx_contents, all_volume); - - // Refresh object and prim count labels - LLLocale locale(LLLocale::USER_LOCALE); -#if 0 - if (!gMeshRepo.meshRezEnabled()) - { - std::string obj_count_string; - LLResMgr::getInstance()->getIntegerString(obj_count_string, LLSelectMgr::getInstance()->getSelection()->getRootObjectCount()); - getChild("selection_count")->setTextArg("[OBJ_COUNT]", obj_count_string); - std::string prim_count_string; - LLResMgr::getInstance()->getIntegerString(prim_count_string, LLSelectMgr::getInstance()->getSelection()->getObjectCount()); - getChild("selection_count")->setTextArg("[PRIM_COUNT]", prim_count_string); - - // calculate selection rendering cost - if (sShowObjectCost) - { - std::string prim_cost_string; - S32 render_cost = LLSelectMgr::getInstance()->getSelection()->getSelectedObjectRenderCost(); - LLResMgr::getInstance()->getIntegerString(prim_cost_string, render_cost); - getChild("RenderingCost")->setTextArg("[COUNT]", prim_cost_string); - } - - // disable the object and prim counts if nothing selected - bool have_selection = ! LLSelectMgr::getInstance()->getSelection()->isEmpty(); - getChildView("obj_count")->setEnabled(have_selection); - getChildView("prim_count")->setEnabled(have_selection); - getChildView("RenderingCost")->setEnabled(have_selection && sShowObjectCost); - } - else -#endif - { - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - F32 link_cost = selection->getSelectedLinksetCost(); - S32 link_count = selection->getRootObjectCount(); - S32 object_count = selection->getObjectCount(); - - LLCrossParcelFunctor func; - if (!LLSelectMgr::getInstance()->getSelection()->applyToRootObjects(&func, true)) - { - // Unless multiple parcels selected, higlight parcel object is at. - LLViewerObject* selected_object = mObjectSelection->getFirstObject(); - if (selected_object) - { - // Select a parcel at the currently selected object's position. - LLViewerParcelMgr::getInstance()->selectParcelAt(selected_object->getPositionGlobal()); - } - else - { - LL_WARNS() << "Failed to get selected object" << LL_ENDL; - } - } - - if (object_count == 1) - { - // "selection_faces" shouldn't be visible if not LLToolFace::getInstance() - // But still need to be populated in case user switches - - std::string faces_str = ""; - - for (LLObjectSelection::iterator iter = selection->begin(); iter != selection->end();) - { - LLObjectSelection::iterator nextiter = iter++; // not strictly needed, we have only one object - LLSelectNode* node = *nextiter; - LLViewerObject* object = (*nextiter)->getObject(); - if (!object) - continue; - S32 num_tes = llmin((S32)object->getNumTEs(), (S32)object->getNumFaces()); - for (S32 te = 0; te < num_tes; ++te) - { - if (node->isTESelected(te)) - { - if (!faces_str.empty()) - { - faces_str += ", "; - } - faces_str += llformat("%d", te); - } - } - } - - childSetTextArg("selection_faces", "[FACES_STRING]", faces_str); - } - - bool show_faces = (object_count == 1) - && LLToolFace::getInstance() == LLToolMgr::getInstance()->getCurrentTool(); - getChildView("selection_faces")->setVisible(show_faces); - - LLStringUtil::format_map_t selection_args; - selection_args["OBJ_COUNT"] = llformat("%.1d", link_count); - selection_args["LAND_IMPACT"] = llformat("%.1d", (S32)link_cost); - - std::ostringstream selection_info; - - selection_info << getString("status_selectcount", selection_args); - - getChild("selection_count")->setText(selection_info.str()); - } - - - // Refresh child tabs - mPanelPermissions->refresh(); - mPanelObject->refresh(); - mPanelVolume->refresh(); - mPanelFace->refresh(); - mPanelFace->refreshMedia(); - mPanelContents->refresh(); - mPanelLandInfo->refresh(); - - // Refresh the advanced weights floater - LLFloaterObjectWeights* object_weights_floater = LLFloaterReg::findTypedInstance("object_weights"); - if(object_weights_floater && object_weights_floater->getVisible()) - { - object_weights_floater->refresh(); - } -} - -void LLFloaterTools::draw() -{ - bool has_selection = !LLSelectMgr::getInstance()->getSelection()->isEmpty(); - if(!has_selection && (mHasSelection != has_selection)) - { - mDirty = true; - } - mHasSelection = has_selection; - - if (mDirty) - { - refresh(); - mDirty = false; - } - - // mCheckSelectIndividual->set(gSavedSettings.getBOOL("EditLinkedParts")); - LLFloater::draw(); -} - -void LLFloaterTools::dirty() -{ - mDirty = true; - LLFloaterOpenObject* instance = LLFloaterReg::findTypedInstance("openobject"); - if (instance) instance->dirty(); -} - -// Clean up any tool state that should not persist when the -// floater is closed. -void LLFloaterTools::resetToolState() -{ - gCameraBtnZoom = true; - gCameraBtnOrbit = false; - gCameraBtnPan = false; - - gGrabBtnSpin = false; - gGrabBtnVertical = false; -} - -void LLFloaterTools::updatePopup(LLCoordGL center, MASK mask) -{ - LLTool *tool = LLToolMgr::getInstance()->getCurrentTool(); - - // HACK to allow seeing the buttons when you have the app in a window. - // Keep the visibility the same as it - if (tool == gToolNull) - { - return; - } - - if ( isMinimized() ) - { // SL looks odd if we draw the tools while the window is minimized - return; - } - - // Focus buttons - bool focus_visible = ( tool == LLToolCamera::getInstance() ); - - mBtnFocus ->setToggleState( focus_visible ); - - mRadioGroupFocus->setVisible( focus_visible ); - getChildView("slider zoom")->setVisible( focus_visible); - getChildView("slider zoom")->setEnabled(gCameraBtnZoom); - - if (!gCameraBtnOrbit && - !gCameraBtnPan && - !(mask == MASK_ORBIT) && - !(mask == (MASK_ORBIT | MASK_ALT)) && - !(mask == MASK_PAN) && - !(mask == (MASK_PAN | MASK_ALT)) ) - { - mRadioGroupFocus->setValue("radio zoom"); - } - else if ( gCameraBtnOrbit || - (mask == MASK_ORBIT) || - (mask == (MASK_ORBIT | MASK_ALT)) ) - { - mRadioGroupFocus->setValue("radio orbit"); - } - else if ( gCameraBtnPan || - (mask == MASK_PAN) || - (mask == (MASK_PAN | MASK_ALT)) ) - { - mRadioGroupFocus->setValue("radio pan"); - } - - // multiply by correction factor because volume sliders go [0, 0.5] - getChild("slider zoom")->setValue(gAgentCamera.getCameraZoomFraction() * 0.5f); - - // Move buttons - bool move_visible = (tool == LLToolGrab::getInstance()); - - if (mBtnMove) mBtnMove ->setToggleState( move_visible ); - - // HACK - highlight buttons for next click - mRadioGroupMove->setVisible(move_visible); - if (!(gGrabBtnSpin || - gGrabBtnVertical || - (mask == MASK_VERTICAL) || - (mask == MASK_SPIN))) - { - mRadioGroupMove->setValue("radio move"); - } - else if ((mask == MASK_VERTICAL) || - (gGrabBtnVertical && (mask != MASK_SPIN))) - { - mRadioGroupMove->setValue("radio lift"); - } - else if ((mask == MASK_SPIN) || - (gGrabBtnSpin && (mask != MASK_VERTICAL))) - { - mRadioGroupMove->setValue("radio spin"); - } - - // Edit buttons - bool edit_visible = tool == LLToolCompTranslate::getInstance() || - tool == LLToolCompRotate::getInstance() || - tool == LLToolCompScale::getInstance() || - tool == LLToolFace::getInstance() || - tool == LLToolIndividual::getInstance() || - tool == LLToolPipette::getInstance(); - - mBtnEdit ->setToggleState( edit_visible ); - mRadioGroupEdit->setVisible( edit_visible ); - //bool linked_parts = gSavedSettings.getBOOL("EditLinkedParts"); - //getChildView("RenderingCost")->setVisible( !linked_parts && (edit_visible || focus_visible || move_visible) && sShowObjectCost); - - mBtnLink->setVisible(edit_visible); - mBtnUnlink->setVisible(edit_visible); - - mBtnLink->setEnabled(LLSelectMgr::instance().enableLinkObjects()); - mBtnUnlink->setEnabled(LLSelectMgr::instance().enableUnlinkObjects()); - - if (mCheckSelectIndividual) - { - mCheckSelectIndividual->setVisible(edit_visible); - //mCheckSelectIndividual->set(gSavedSettings.getBOOL("EditLinkedParts")); - } - - if ( tool == LLToolCompTranslate::getInstance() ) - { - mRadioGroupEdit->setValue("radio position"); - } - else if ( tool == LLToolCompRotate::getInstance() ) - { - mRadioGroupEdit->setValue("radio rotate"); - } - else if ( tool == LLToolCompScale::getInstance() ) - { - mRadioGroupEdit->setValue("radio stretch"); - } - else if ( tool == LLToolFace::getInstance() ) - { - mRadioGroupEdit->setValue("radio select face"); - } - - if (mComboGridMode) - { - mComboGridMode->setVisible( edit_visible ); - S32 index = mComboGridMode->getCurrentIndex(); - mComboGridMode->removeall(); - - switch (mObjectSelection->getSelectType()) - { - case SELECT_TYPE_HUD: - mComboGridMode->add(getString("grid_screen_text")); - mComboGridMode->add(getString("grid_local_text")); - break; - case SELECT_TYPE_WORLD: - mComboGridMode->add(getString("grid_world_text")); - mComboGridMode->add(getString("grid_local_text")); - mComboGridMode->add(getString("grid_reference_text")); - break; - case SELECT_TYPE_ATTACHMENT: - mComboGridMode->add(getString("grid_attachment_text")); - mComboGridMode->add(getString("grid_local_text")); - mComboGridMode->add(getString("grid_reference_text")); - break; - } - - mComboGridMode->setCurrentByIndex(index); - } - - // Snap to grid disabled for grab tool - very confusing - if (mCheckSnapToGrid) mCheckSnapToGrid->setVisible( edit_visible /* || tool == LLToolGrab::getInstance() */ ); - if (mBtnGridOptions) mBtnGridOptions->setVisible( edit_visible /* || tool == LLToolGrab::getInstance() */ ); - - //mCheckSelectLinked ->setVisible( edit_visible ); - if (mCheckStretchUniform) mCheckStretchUniform->setVisible( edit_visible ); - if (mCheckStretchTexture) mCheckStretchTexture->setVisible( edit_visible ); - if (mCheckStretchUniformLabel) mCheckStretchUniformLabel->setVisible( edit_visible ); - - // Create buttons - bool create_visible = (tool == LLToolCompCreate::getInstance()); - - mBtnCreate ->setToggleState( tool == LLToolCompCreate::getInstance() ); - - if (mCheckCopySelection - && mCheckCopySelection->get()) - { - // don't highlight any placer button - for (std::vector::size_type i = 0; i < mButtons.size(); i++) - { - mButtons[i]->setToggleState(false); - mButtons[i]->setVisible( create_visible ); - } - } - else - { - // Highlight the correct placer button - for( S32 t = 0; t < (S32)mButtons.size(); t++ ) - { - LLPCode pcode = LLToolPlacer::getObjectType(); - LLPCode button_pcode = toolData[t]; - bool state = (pcode == button_pcode); - mButtons[t]->setToggleState( state ); - mButtons[t]->setVisible( create_visible ); - } - } - - if (mCheckSticky) mCheckSticky ->setVisible( create_visible ); - if (mCheckCopySelection) mCheckCopySelection ->setVisible( create_visible ); - if (mCheckCopyCenters) mCheckCopyCenters ->setVisible( create_visible ); - if (mCheckCopyRotates) mCheckCopyRotates ->setVisible( create_visible ); - - if (mCheckCopyCenters && mCheckCopySelection) mCheckCopyCenters->setEnabled( mCheckCopySelection->get() ); - if (mCheckCopyRotates && mCheckCopySelection) mCheckCopyRotates->setEnabled( mCheckCopySelection->get() ); - - // Land buttons - bool land_visible = (tool == LLToolBrushLand::getInstance() || tool == LLToolSelectLand::getInstance() ); - - mCostTextBorder->setVisible(!land_visible); - - if (mBtnLand) mBtnLand ->setToggleState( land_visible ); - - mRadioGroupLand->setVisible( land_visible ); - if ( tool == LLToolSelectLand::getInstance() ) - { - mRadioGroupLand->setValue("radio select land"); - } - else if ( tool == LLToolBrushLand::getInstance() ) - { - S32 dozer_mode = gSavedSettings.getS32("RadioLandBrushAction"); - switch(dozer_mode) - { - case 0: - mRadioGroupLand->setValue("radio flatten"); - break; - case 1: - mRadioGroupLand->setValue("radio raise"); - break; - case 2: - mRadioGroupLand->setValue("radio lower"); - break; - case 3: - mRadioGroupLand->setValue("radio smooth"); - break; - case 4: - mRadioGroupLand->setValue("radio noise"); - break; - case 5: - mRadioGroupLand->setValue("radio revert"); - break; - default: - break; - } - } - - if (mBtnApplyToSelection) - { - mBtnApplyToSelection->setVisible( land_visible ); - mBtnApplyToSelection->setEnabled( land_visible && !LLViewerParcelMgr::getInstance()->selectionEmpty() && tool != LLToolSelectLand::getInstance()); - } - if (mSliderDozerSize) - { - mSliderDozerSize ->setVisible( land_visible ); - getChildView("Bulldozer:")->setVisible( land_visible); - getChildView("Dozer Size:")->setVisible( land_visible); - } - if (mSliderDozerForce) - { - mSliderDozerForce ->setVisible( land_visible ); - getChildView("Strength:")->setVisible( land_visible); - } - - bool have_selection = !LLSelectMgr::getInstance()->getSelection()->isEmpty(); - - getChildView("selection_count")->setVisible(!land_visible && have_selection); - getChildView("selection_faces")->setVisible(LLToolFace::getInstance() == LLToolMgr::getInstance()->getCurrentTool() - && LLSelectMgr::getInstance()->getSelection()->getObjectCount() == 1); - getChildView("selection_empty")->setVisible(!land_visible && !have_selection); - - mTab->setVisible(!land_visible); - mPanelLandInfo->setVisible(land_visible); -} - - -// virtual -bool LLFloaterTools::canClose() -{ - // don't close when quitting, so camera will stay put - return !LLApp::isExiting(); -} - -// virtual -void LLFloaterTools::onOpen(const LLSD& key) -{ - mParcelSelection = LLViewerParcelMgr::getInstance()->getFloatingParcelSelection(); - mObjectSelection = LLSelectMgr::getInstance()->getEditSelection(); - - std::string panel = key.asString(); - if (!panel.empty()) - { - mTab->selectTabByName(panel); - } - - LLTool* tool = LLToolMgr::getInstance()->getCurrentTool(); - if (tool == LLToolCompInspect::getInstance() - || tool == LLToolDragAndDrop::getInstance()) - { - // Something called floater up while it was supressed (during drag n drop, inspect), - // so it won't be getting any layout or visibility updates, update once - // further updates will come from updateLayout() - LLCoordGL select_center_screen; - MASK mask = gKeyboard->currentMask(true); - updatePopup(select_center_screen, mask); - } - - //gMenuBarView->setItemVisible("BuildTools", true); -} - -// virtual -void LLFloaterTools::onClose(bool app_quitting) -{ - mTab->setVisible(false); - - LLViewerJoystick::getInstance()->moveAvatar(false); - - // destroy media source used to grab media title - mPanelFace->unloadMedia(); - - // Different from handle_reset_view in that it doesn't actually - // move the camera if EditCameraMovement is not set. - gAgentCamera.resetView(gSavedSettings.getBOOL("EditCameraMovement")); - - // exit component selection mode - LLSelectMgr::getInstance()->promoteSelectionToRoot(); - gSavedSettings.setBOOL("EditLinkedParts", false); - - gViewerWindow->showCursor(); - - resetToolState(); - - mParcelSelection = NULL; - mObjectSelection = NULL; - - // Switch back to basic toolset - LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); - // we were already in basic toolset, using build tools - // so manually reset tool to default (pie menu tool) - LLToolMgr::getInstance()->getCurrentToolset()->selectFirstTool(); - - //gMenuBarView->setItemVisible("BuildTools", false); - LLFloaterReg::hideInstance("media_settings"); - - // hide the advanced object weights floater - LLFloaterReg::hideInstance("object_weights"); - - // hide gltf material editor - LLFloaterReg::hideInstance("live_material_editor"); - - // prepare content for next call - mPanelContents->clearContents(); - - if(sPreviousFocusOnAvatar) - { - sPreviousFocusOnAvatar = false; - gAgentCamera.setAllowChangeToFollow(true); - } -} - -void click_popup_info(void*) -{ -} - -void click_popup_done(void*) -{ - handle_reset_view(); -} - -void commit_radio_group_move(LLUICtrl* ctrl) -{ - LLRadioGroup* group = (LLRadioGroup*)ctrl; - std::string selected = group->getValue().asString(); - if (selected == "radio move") - { - gGrabBtnVertical = false; - gGrabBtnSpin = false; - } - else if (selected == "radio lift") - { - gGrabBtnVertical = true; - gGrabBtnSpin = false; - } - else if (selected == "radio spin") - { - gGrabBtnVertical = false; - gGrabBtnSpin = true; - } -} - -void commit_radio_group_focus(LLUICtrl* ctrl) -{ - LLRadioGroup* group = (LLRadioGroup*)ctrl; - std::string selected = group->getValue().asString(); - if (selected == "radio zoom") - { - gCameraBtnZoom = true; - gCameraBtnOrbit = false; - gCameraBtnPan = false; - } - else if (selected == "radio orbit") - { - gCameraBtnZoom = false; - gCameraBtnOrbit = true; - gCameraBtnPan = false; - } - else if (selected == "radio pan") - { - gCameraBtnZoom = false; - gCameraBtnOrbit = false; - gCameraBtnPan = true; - } -} - -void commit_slider_zoom(LLUICtrl *ctrl) -{ - // renormalize value, since max "volume" level is 0.5 for some reason - F32 zoom_level = (F32)ctrl->getValue().asReal() * 2.f; // / 0.5f; - gAgentCamera.setCameraZoomFraction(zoom_level); -} - -void commit_slider_dozer_force(LLUICtrl *ctrl) -{ - // the slider is logarithmic, so we exponentiate to get the actual force multiplier - F32 dozer_force = pow(10.f, (F32)ctrl->getValue().asReal()); - gSavedSettings.setF32("LandBrushForce", dozer_force); -} - -void click_apply_to_selection(void*) -{ - LLToolBrushLand::getInstance()->modifyLandInSelectionGlobal(); -} - -void commit_radio_group_edit(LLUICtrl *ctrl) -{ - S32 show_owners = gSavedSettings.getBOOL("ShowParcelOwners"); - - LLRadioGroup* group = (LLRadioGroup*)ctrl; - std::string selected = group->getValue().asString(); - if (selected == "radio position") - { - LLFloaterTools::setEditTool( LLToolCompTranslate::getInstance() ); - } - else if (selected == "radio rotate") - { - LLFloaterTools::setEditTool( LLToolCompRotate::getInstance() ); - } - else if (selected == "radio stretch") - { - LLFloaterTools::setEditTool( LLToolCompScale::getInstance() ); - } - else if (selected == "radio select face") - { - LLFloaterTools::setEditTool( LLToolFace::getInstance() ); - } - gSavedSettings.setBOOL("ShowParcelOwners", show_owners); -} - -void commit_radio_group_land(LLUICtrl* ctrl) -{ - LLRadioGroup* group = (LLRadioGroup*)ctrl; - std::string selected = group->getValue().asString(); - if (selected == "radio select land") - { - LLFloaterTools::setEditTool( LLToolSelectLand::getInstance() ); - } - else - { - LLFloaterTools::setEditTool( LLToolBrushLand::getInstance() ); - S32 dozer_mode = gSavedSettings.getS32("RadioLandBrushAction"); - if (selected == "radio flatten") - dozer_mode = 0; - else if (selected == "radio raise") - dozer_mode = 1; - else if (selected == "radio lower") - dozer_mode = 2; - else if (selected == "radio smooth") - dozer_mode = 3; - else if (selected == "radio noise") - dozer_mode = 4; - else if (selected == "radio revert") - dozer_mode = 5; - gSavedSettings.setS32("RadioLandBrushAction", dozer_mode); - } -} - -void commit_select_component(void *data) -{ - LLFloaterTools* floaterp = (LLFloaterTools*)data; - - //forfeit focus - if (gFocusMgr.childHasKeyboardFocus(floaterp)) - { - gFocusMgr.setKeyboardFocus(NULL); - } - - bool select_individuals = floaterp->mCheckSelectIndividual->get(); - gSavedSettings.setBOOL("EditLinkedParts", select_individuals); - floaterp->dirty(); - - if (select_individuals) - { - LLSelectMgr::getInstance()->demoteSelectionToIndividuals(); - } - else - { - LLSelectMgr::getInstance()->promoteSelectionToRoot(); - } -} - -// static -void LLFloaterTools::setObjectType( LLPCode pcode ) -{ - LLToolPlacer::setObjectType( pcode ); - gSavedSettings.setBOOL("CreateToolCopySelection", false); - gFocusMgr.setMouseCapture(NULL); -} - -void commit_grid_mode(LLUICtrl *ctrl) -{ - LLComboBox* combo = (LLComboBox*)ctrl; - - LLSelectMgr::getInstance()->setGridMode((EGridMode)combo->getCurrentIndex()); -} - -// static -void LLFloaterTools::setGridMode(S32 mode) -{ - LLFloaterTools* tools_floater = LLFloaterReg::getTypedInstance("build"); - if (!tools_floater || !tools_floater->mComboGridMode) - { - return; - } - - tools_floater->mComboGridMode->setCurrentByIndex(mode); -} - -void LLFloaterTools::onClickGridOptions() -{ - LLFloater* floaterp = LLFloaterReg::showInstance("build_options"); - // position floater next to build tools, not over - floaterp->setShape(gFloaterView->findNeighboringPosition(this, floaterp), true); -} - -// static -void LLFloaterTools::setEditTool(void* tool_pointer) -{ - LLTool *tool = (LLTool *)tool_pointer; - LLToolMgr::getInstance()->getCurrentToolset()->selectTool( tool ); -} - -void LLFloaterTools::setTool(const LLSD& user_data) -{ - std::string control_name = user_data.asString(); - if(control_name == "Focus") - LLToolMgr::getInstance()->getCurrentToolset()->selectTool((LLTool *) LLToolCamera::getInstance() ); - else if (control_name == "Move" ) - LLToolMgr::getInstance()->getCurrentToolset()->selectTool( (LLTool *)LLToolGrab::getInstance() ); - else if (control_name == "Edit" ) - LLToolMgr::getInstance()->getCurrentToolset()->selectTool( (LLTool *) LLToolCompTranslate::getInstance()); - else if (control_name == "Create" ) - LLToolMgr::getInstance()->getCurrentToolset()->selectTool( (LLTool *) LLToolCompCreate::getInstance()); - else if (control_name == "Land" ) - LLToolMgr::getInstance()->getCurrentToolset()->selectTool( (LLTool *) LLToolSelectLand::getInstance()); - else - LL_WARNS()<<" no parameter name "<setCurrentToolset(gBasicToolset); - LLFloater::onFocusReceived(); -} - -void LLFloaterTools::updateLandImpacts() -{ - LLParcel *parcel = mParcelSelection->getParcel(); - if (!parcel) - { - return; - } - - // Update land impacts info in the weights floater - LLFloaterObjectWeights* object_weights_floater = LLFloaterReg::findTypedInstance("object_weights"); - if(object_weights_floater) - { - object_weights_floater->updateLandImpacts(parcel); - } -} - +/** + * @file llfloatertools.cpp + * @brief The edit tools, including move, position, land, etc. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatertools.h" + +#include "llfontgl.h" +#include "llcoord.h" +//#include "llgl.h" + +#include "llagent.h" +#include "llagentcamera.h" +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "lldraghandle.h" +#include "llerror.h" +#include "llfloaterbuildoptions.h" +#include "llfloatermediasettings.h" +#include "llfloateropenobject.h" +#include "llfloaterobjectweights.h" +#include "llfloaterreg.h" +#include "llfocusmgr.h" +#include "llmediaentry.h" +#include "llmenugl.h" +#include "llnotificationsutil.h" +#include "llpanelcontents.h" +#include "llpanelface.h" +#include "llpanelland.h" +#include "llpanelobjectinventory.h" +#include "llpanelobject.h" +#include "llpanelvolume.h" +#include "llpanelpermissions.h" +#include "llparcel.h" +#include "llradiogroup.h" +#include "llresmgr.h" +#include "llselectmgr.h" +#include "llslider.h" +#include "llstatusbar.h" +#include "lltabcontainer.h" +#include "lltextbox.h" +#include "lltoolbrush.h" +#include "lltoolcomp.h" +#include "lltooldraganddrop.h" +#include "lltoolface.h" +#include "lltoolfocus.h" +#include "lltoolgrab.h" +#include "lltoolgrab.h" +#include "lltoolindividual.h" +#include "lltoolmgr.h" +#include "lltoolpie.h" +#include "lltoolpipette.h" +#include "lltoolplacer.h" +#include "lltoolselectland.h" +#include "lltrans.h" +#include "llui.h" +#include "llviewercontrol.h" +#include "llviewerjoystick.h" +#include "llviewerregion.h" +#include "llviewermenu.h" +#include "llviewerparcelmgr.h" +#include "llviewerwindow.h" +#include "llvovolume.h" +#include "lluictrlfactory.h" +#include "llmeshrepository.h" + +// Globals +LLFloaterTools *gFloaterTools = NULL; +bool LLFloaterTools::sShowObjectCost = true; +bool LLFloaterTools::sPreviousFocusOnAvatar = false; + +const std::string PANEL_NAMES[LLFloaterTools::PANEL_COUNT] = +{ + std::string("General"), // PANEL_GENERAL, + std::string("Object"), // PANEL_OBJECT, + std::string("Features"), // PANEL_FEATURES, + std::string("Texture"), // PANEL_FACE, + std::string("Content"), // PANEL_CONTENTS, +}; + + +// Local prototypes +void commit_grid_mode(LLUICtrl *ctrl); +void commit_select_component(void *data); +void click_show_more(void*); +void click_popup_info(void*); +void click_popup_done(void*); +void click_popup_minimize(void*); +void commit_slider_dozer_force(LLUICtrl *); +void click_apply_to_selection(void*); +void commit_radio_group_focus(LLUICtrl* ctrl); +void commit_radio_group_move(LLUICtrl* ctrl); +void commit_radio_group_edit(LLUICtrl* ctrl); +void commit_radio_group_land(LLUICtrl* ctrl); +void commit_slider_zoom(LLUICtrl *ctrl); + +/** + * Class LLLandImpactsObserver + * + * An observer class to monitor parcel selection and update + * the land impacts data from a parcel containing the selected object. + */ +class LLLandImpactsObserver : public LLParcelObserver +{ +public: + virtual void changed() + { + LLFloaterTools* tools_floater = LLFloaterReg::getTypedInstance("build"); + if(tools_floater) + { + tools_floater->updateLandImpacts(); + } + } +}; + +//static +void* LLFloaterTools::createPanelPermissions(void* data) +{ + LLFloaterTools* floater = (LLFloaterTools*)data; + floater->mPanelPermissions = new LLPanelPermissions(); + return floater->mPanelPermissions; +} +//static +void* LLFloaterTools::createPanelObject(void* data) +{ + LLFloaterTools* floater = (LLFloaterTools*)data; + floater->mPanelObject = new LLPanelObject(); + return floater->mPanelObject; +} + +//static +void* LLFloaterTools::createPanelVolume(void* data) +{ + LLFloaterTools* floater = (LLFloaterTools*)data; + floater->mPanelVolume = new LLPanelVolume(); + return floater->mPanelVolume; +} + +//static +void* LLFloaterTools::createPanelFace(void* data) +{ + LLFloaterTools* floater = (LLFloaterTools*)data; + floater->mPanelFace = new LLPanelFace(); + return floater->mPanelFace; +} + +//static +void* LLFloaterTools::createPanelContents(void* data) +{ + LLFloaterTools* floater = (LLFloaterTools*)data; + floater->mPanelContents = new LLPanelContents(); + return floater->mPanelContents; +} + +//static +void* LLFloaterTools::createPanelLandInfo(void* data) +{ + LLFloaterTools* floater = (LLFloaterTools*)data; + floater->mPanelLandInfo = new LLPanelLandInfo(); + return floater->mPanelLandInfo; +} + +static const std::string toolNames[]={ + "ToolCube", + "ToolPrism", + "ToolPyramid", + "ToolTetrahedron", + "ToolCylinder", + "ToolHemiCylinder", + "ToolCone", + "ToolHemiCone", + "ToolSphere", + "ToolHemiSphere", + "ToolTorus", + "ToolTube", + "ToolRing", + "ToolTree", + "ToolGrass"}; +LLPCode toolData[]={ + LL_PCODE_CUBE, + LL_PCODE_PRISM, + LL_PCODE_PYRAMID, + LL_PCODE_TETRAHEDRON, + LL_PCODE_CYLINDER, + LL_PCODE_CYLINDER_HEMI, + LL_PCODE_CONE, + LL_PCODE_CONE_HEMI, + LL_PCODE_SPHERE, + LL_PCODE_SPHERE_HEMI, + LL_PCODE_TORUS, + LLViewerObject::LL_VO_SQUARE_TORUS, + LLViewerObject::LL_VO_TRIANGLE_TORUS, + LL_PCODE_LEGACY_TREE, + LL_PCODE_LEGACY_GRASS}; + +bool LLFloaterTools::postBuild() +{ + // Hide until tool selected + setVisible(false); + + // Since we constantly show and hide this during drags, don't + // make sounds on visibility changes. + setSoundFlags(LLView::SILENT); + + getDragHandle()->setEnabled( !gSavedSettings.getBOOL("ToolboxAutoMove") ); + + LLRect rect; + mBtnFocus = getChild("button focus");//btn; + mBtnMove = getChild("button move"); + mBtnEdit = getChild("button edit"); + mBtnCreate = getChild("button create"); + mBtnLand = getChild("button land" ); + mTextStatus = getChild("text status"); + mRadioGroupFocus = getChild("focus_radio_group"); + mRadioGroupMove = getChild("move_radio_group"); + mRadioGroupEdit = getChild("edit_radio_group"); + mBtnGridOptions = getChild("Options..."); + mBtnLink = getChild("link_btn"); + mBtnUnlink = getChild("unlink_btn"); + + mCheckSelectIndividual = getChild("checkbox edit linked parts"); + getChild("checkbox edit linked parts")->setValue((bool)gSavedSettings.getBOOL("EditLinkedParts")); + mCheckSnapToGrid = getChild("checkbox snap to grid"); + getChild("checkbox snap to grid")->setValue((bool)gSavedSettings.getBOOL("SnapEnabled")); + mCheckStretchUniform = getChild("checkbox uniform"); + getChild("checkbox uniform")->setValue((bool)gSavedSettings.getBOOL("ScaleUniform")); + mCheckStretchTexture = getChild("checkbox stretch textures"); + getChild("checkbox stretch textures")->setValue((bool)gSavedSettings.getBOOL("ScaleStretchTextures")); + mComboGridMode = getChild("combobox grid mode"); + + // + // Create Buttons + // + + for(size_t t=0; t(toolNames[t]); + if(found) + { + found->setClickedCallback(boost::bind(&LLFloaterTools::setObjectType, toolData[t])); + mButtons.push_back( found ); + }else{ + LL_WARNS() << "Tool button not found! DOA Pending." << LL_ENDL; + } + } + mCheckCopySelection = getChild("checkbox copy selection"); + getChild("checkbox copy selection")->setValue((bool)gSavedSettings.getBOOL("CreateToolCopySelection")); + mCheckSticky = getChild("checkbox sticky"); + getChild("checkbox sticky")->setValue((bool)gSavedSettings.getBOOL("CreateToolKeepSelected")); + mCheckCopyCenters = getChild("checkbox copy centers"); + getChild("checkbox copy centers")->setValue((bool)gSavedSettings.getBOOL("CreateToolCopyCenters")); + mCheckCopyRotates = getChild("checkbox copy rotates"); + getChild("checkbox copy rotates")->setValue((bool)gSavedSettings.getBOOL("CreateToolCopyRotates")); + + mRadioGroupLand = getChild("land_radio_group"); + mBtnApplyToSelection = getChild("button apply to selection"); + mSliderDozerSize = getChild("slider brush size"); + getChild("slider brush size")->setValue(gSavedSettings.getF32("LandBrushSize")); + mSliderDozerForce = getChild("slider force"); + // the setting stores the actual force multiplier, but the slider is logarithmic, so we convert here + getChild("slider force")->setValue(log10(gSavedSettings.getF32("LandBrushForce"))); + + mCostTextBorder = getChild("cost_text_border"); + + mTab = getChild("Object Info Tabs"); + if(mTab) + { + mTab->setFollows(FOLLOWS_TOP | FOLLOWS_LEFT); + mTab->setBorderVisible(false); + mTab->selectFirstTab(); + } + + mStatusText["rotate"] = getString("status_rotate"); + mStatusText["scale"] = getString("status_scale"); + mStatusText["move"] = getString("status_move"); + mStatusText["modifyland"] = getString("status_modifyland"); + mStatusText["camera"] = getString("status_camera"); + mStatusText["grab"] = getString("status_grab"); + mStatusText["place"] = getString("status_place"); + mStatusText["selectland"] = getString("status_selectland"); + + sShowObjectCost = gSavedSettings.getBOOL("ShowObjectRenderingCost"); + + return true; +} + +// Create the popupview with a dummy center. It will be moved into place +// during LLViewerWindow's per-frame hover processing. +LLFloaterTools::LLFloaterTools(const LLSD& key) +: LLFloater(key), + mBtnFocus(NULL), + mBtnMove(NULL), + mBtnEdit(NULL), + mBtnCreate(NULL), + mBtnLand(NULL), + mTextStatus(NULL), + + mRadioGroupFocus(NULL), + mRadioGroupMove(NULL), + mRadioGroupEdit(NULL), + + mCheckSelectIndividual(NULL), + + mCheckSnapToGrid(NULL), + mBtnGridOptions(NULL), + mComboGridMode(NULL), + mCheckStretchUniform(NULL), + mCheckStretchTexture(NULL), + mCheckStretchUniformLabel(NULL), + + mBtnRotateLeft(NULL), + mBtnRotateReset(NULL), + mBtnRotateRight(NULL), + + mBtnLink(NULL), + mBtnUnlink(NULL), + + mBtnDelete(NULL), + mBtnDuplicate(NULL), + mBtnDuplicateInPlace(NULL), + + mCheckSticky(NULL), + mCheckCopySelection(NULL), + mCheckCopyCenters(NULL), + mCheckCopyRotates(NULL), + mRadioGroupLand(NULL), + mSliderDozerSize(NULL), + mSliderDozerForce(NULL), + mBtnApplyToSelection(NULL), + + mTab(NULL), + mPanelPermissions(NULL), + mPanelObject(NULL), + mPanelVolume(NULL), + mPanelContents(NULL), + mPanelFace(NULL), + mPanelLandInfo(NULL), + + mCostTextBorder(NULL), + mTabLand(NULL), + + mLandImpactsObserver(NULL), + + mDirty(true), + mHasSelection(true) +{ + gFloaterTools = this; + + setAutoFocus(false); + mFactoryMap["General"] = LLCallbackMap(createPanelPermissions, this);//LLPanelPermissions + mFactoryMap["Object"] = LLCallbackMap(createPanelObject, this);//LLPanelObject + mFactoryMap["Features"] = LLCallbackMap(createPanelVolume, this);//LLPanelVolume + mFactoryMap["Texture"] = LLCallbackMap(createPanelFace, this);//LLPanelFace + mFactoryMap["Contents"] = LLCallbackMap(createPanelContents, this);//LLPanelContents + mFactoryMap["land info panel"] = LLCallbackMap(createPanelLandInfo, this);//LLPanelLandInfo + + mCommitCallbackRegistrar.add("BuildTool.setTool", boost::bind(&LLFloaterTools::setTool,this, _2)); + mCommitCallbackRegistrar.add("BuildTool.commitZoom", boost::bind(&commit_slider_zoom, _1)); + mCommitCallbackRegistrar.add("BuildTool.commitRadioFocus", boost::bind(&commit_radio_group_focus, _1)); + mCommitCallbackRegistrar.add("BuildTool.commitRadioMove", boost::bind(&commit_radio_group_move,_1)); + mCommitCallbackRegistrar.add("BuildTool.commitRadioEdit", boost::bind(&commit_radio_group_edit,_1)); + + mCommitCallbackRegistrar.add("BuildTool.gridMode", boost::bind(&commit_grid_mode,_1)); + mCommitCallbackRegistrar.add("BuildTool.selectComponent", boost::bind(&commit_select_component, this)); + mCommitCallbackRegistrar.add("BuildTool.gridOptions", boost::bind(&LLFloaterTools::onClickGridOptions,this)); + mCommitCallbackRegistrar.add("BuildTool.applyToSelection", boost::bind(&click_apply_to_selection, this)); + mCommitCallbackRegistrar.add("BuildTool.commitRadioLand", boost::bind(&commit_radio_group_land,_1)); + mCommitCallbackRegistrar.add("BuildTool.LandBrushForce", boost::bind(&commit_slider_dozer_force,_1)); + + mCommitCallbackRegistrar.add("BuildTool.LinkObjects", boost::bind(&LLSelectMgr::linkObjects, LLSelectMgr::getInstance())); + mCommitCallbackRegistrar.add("BuildTool.UnlinkObjects", boost::bind(&LLSelectMgr::unlinkObjects, LLSelectMgr::getInstance())); + + mLandImpactsObserver = new LLLandImpactsObserver(); + LLViewerParcelMgr::getInstance()->addObserver(mLandImpactsObserver); +} + +LLFloaterTools::~LLFloaterTools() +{ + // children automatically deleted + gFloaterTools = NULL; + + LLViewerParcelMgr::getInstance()->removeObserver(mLandImpactsObserver); + delete mLandImpactsObserver; +} + +void LLFloaterTools::setStatusText(const std::string& text) +{ + std::map::iterator iter = mStatusText.find(text); + if (iter != mStatusText.end()) + { + mTextStatus->setText(iter->second); + } + else + { + mTextStatus->setText(text); + } +} + +void LLFloaterTools::refresh() +{ + const S32 INFO_WIDTH = getRect().getWidth(); + const S32 INFO_HEIGHT = 384; + LLRect object_info_rect(0, 0, INFO_WIDTH, -INFO_HEIGHT); + bool all_volume = LLSelectMgr::getInstance()->selectionAllPCode( LL_PCODE_VOLUME ); + + S32 idx_features = mTab->getPanelIndexByTitle(PANEL_NAMES[PANEL_FEATURES]); + S32 idx_face = mTab->getPanelIndexByTitle(PANEL_NAMES[PANEL_FACE]); + S32 idx_contents = mTab->getPanelIndexByTitle(PANEL_NAMES[PANEL_CONTENTS]); + + S32 selected_index = mTab->getCurrentPanelIndex(); + + if (!all_volume && (selected_index == idx_features || selected_index == idx_face || + selected_index == idx_contents)) + { + mTab->selectFirstTab(); + } + + mTab->enableTabButton(idx_features, all_volume); + mTab->enableTabButton(idx_face, all_volume); + mTab->enableTabButton(idx_contents, all_volume); + + // Refresh object and prim count labels + LLLocale locale(LLLocale::USER_LOCALE); +#if 0 + if (!gMeshRepo.meshRezEnabled()) + { + std::string obj_count_string; + LLResMgr::getInstance()->getIntegerString(obj_count_string, LLSelectMgr::getInstance()->getSelection()->getRootObjectCount()); + getChild("selection_count")->setTextArg("[OBJ_COUNT]", obj_count_string); + std::string prim_count_string; + LLResMgr::getInstance()->getIntegerString(prim_count_string, LLSelectMgr::getInstance()->getSelection()->getObjectCount()); + getChild("selection_count")->setTextArg("[PRIM_COUNT]", prim_count_string); + + // calculate selection rendering cost + if (sShowObjectCost) + { + std::string prim_cost_string; + S32 render_cost = LLSelectMgr::getInstance()->getSelection()->getSelectedObjectRenderCost(); + LLResMgr::getInstance()->getIntegerString(prim_cost_string, render_cost); + getChild("RenderingCost")->setTextArg("[COUNT]", prim_cost_string); + } + + // disable the object and prim counts if nothing selected + bool have_selection = ! LLSelectMgr::getInstance()->getSelection()->isEmpty(); + getChildView("obj_count")->setEnabled(have_selection); + getChildView("prim_count")->setEnabled(have_selection); + getChildView("RenderingCost")->setEnabled(have_selection && sShowObjectCost); + } + else +#endif + { + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + F32 link_cost = selection->getSelectedLinksetCost(); + S32 link_count = selection->getRootObjectCount(); + S32 object_count = selection->getObjectCount(); + + LLCrossParcelFunctor func; + if (!LLSelectMgr::getInstance()->getSelection()->applyToRootObjects(&func, true)) + { + // Unless multiple parcels selected, higlight parcel object is at. + LLViewerObject* selected_object = mObjectSelection->getFirstObject(); + if (selected_object) + { + // Select a parcel at the currently selected object's position. + LLViewerParcelMgr::getInstance()->selectParcelAt(selected_object->getPositionGlobal()); + } + else + { + LL_WARNS() << "Failed to get selected object" << LL_ENDL; + } + } + + if (object_count == 1) + { + // "selection_faces" shouldn't be visible if not LLToolFace::getInstance() + // But still need to be populated in case user switches + + std::string faces_str = ""; + + for (LLObjectSelection::iterator iter = selection->begin(); iter != selection->end();) + { + LLObjectSelection::iterator nextiter = iter++; // not strictly needed, we have only one object + LLSelectNode* node = *nextiter; + LLViewerObject* object = (*nextiter)->getObject(); + if (!object) + continue; + S32 num_tes = llmin((S32)object->getNumTEs(), (S32)object->getNumFaces()); + for (S32 te = 0; te < num_tes; ++te) + { + if (node->isTESelected(te)) + { + if (!faces_str.empty()) + { + faces_str += ", "; + } + faces_str += llformat("%d", te); + } + } + } + + childSetTextArg("selection_faces", "[FACES_STRING]", faces_str); + } + + bool show_faces = (object_count == 1) + && LLToolFace::getInstance() == LLToolMgr::getInstance()->getCurrentTool(); + getChildView("selection_faces")->setVisible(show_faces); + + LLStringUtil::format_map_t selection_args; + selection_args["OBJ_COUNT"] = llformat("%.1d", link_count); + selection_args["LAND_IMPACT"] = llformat("%.1d", (S32)link_cost); + + std::ostringstream selection_info; + + selection_info << getString("status_selectcount", selection_args); + + getChild("selection_count")->setText(selection_info.str()); + } + + + // Refresh child tabs + mPanelPermissions->refresh(); + mPanelObject->refresh(); + mPanelVolume->refresh(); + mPanelFace->refresh(); + mPanelFace->refreshMedia(); + mPanelContents->refresh(); + mPanelLandInfo->refresh(); + + // Refresh the advanced weights floater + LLFloaterObjectWeights* object_weights_floater = LLFloaterReg::findTypedInstance("object_weights"); + if(object_weights_floater && object_weights_floater->getVisible()) + { + object_weights_floater->refresh(); + } +} + +void LLFloaterTools::draw() +{ + bool has_selection = !LLSelectMgr::getInstance()->getSelection()->isEmpty(); + if(!has_selection && (mHasSelection != has_selection)) + { + mDirty = true; + } + mHasSelection = has_selection; + + if (mDirty) + { + refresh(); + mDirty = false; + } + + // mCheckSelectIndividual->set(gSavedSettings.getBOOL("EditLinkedParts")); + LLFloater::draw(); +} + +void LLFloaterTools::dirty() +{ + mDirty = true; + LLFloaterOpenObject* instance = LLFloaterReg::findTypedInstance("openobject"); + if (instance) instance->dirty(); +} + +// Clean up any tool state that should not persist when the +// floater is closed. +void LLFloaterTools::resetToolState() +{ + gCameraBtnZoom = true; + gCameraBtnOrbit = false; + gCameraBtnPan = false; + + gGrabBtnSpin = false; + gGrabBtnVertical = false; +} + +void LLFloaterTools::updatePopup(LLCoordGL center, MASK mask) +{ + LLTool *tool = LLToolMgr::getInstance()->getCurrentTool(); + + // HACK to allow seeing the buttons when you have the app in a window. + // Keep the visibility the same as it + if (tool == gToolNull) + { + return; + } + + if ( isMinimized() ) + { // SL looks odd if we draw the tools while the window is minimized + return; + } + + // Focus buttons + bool focus_visible = ( tool == LLToolCamera::getInstance() ); + + mBtnFocus ->setToggleState( focus_visible ); + + mRadioGroupFocus->setVisible( focus_visible ); + getChildView("slider zoom")->setVisible( focus_visible); + getChildView("slider zoom")->setEnabled(gCameraBtnZoom); + + if (!gCameraBtnOrbit && + !gCameraBtnPan && + !(mask == MASK_ORBIT) && + !(mask == (MASK_ORBIT | MASK_ALT)) && + !(mask == MASK_PAN) && + !(mask == (MASK_PAN | MASK_ALT)) ) + { + mRadioGroupFocus->setValue("radio zoom"); + } + else if ( gCameraBtnOrbit || + (mask == MASK_ORBIT) || + (mask == (MASK_ORBIT | MASK_ALT)) ) + { + mRadioGroupFocus->setValue("radio orbit"); + } + else if ( gCameraBtnPan || + (mask == MASK_PAN) || + (mask == (MASK_PAN | MASK_ALT)) ) + { + mRadioGroupFocus->setValue("radio pan"); + } + + // multiply by correction factor because volume sliders go [0, 0.5] + getChild("slider zoom")->setValue(gAgentCamera.getCameraZoomFraction() * 0.5f); + + // Move buttons + bool move_visible = (tool == LLToolGrab::getInstance()); + + if (mBtnMove) mBtnMove ->setToggleState( move_visible ); + + // HACK - highlight buttons for next click + mRadioGroupMove->setVisible(move_visible); + if (!(gGrabBtnSpin || + gGrabBtnVertical || + (mask == MASK_VERTICAL) || + (mask == MASK_SPIN))) + { + mRadioGroupMove->setValue("radio move"); + } + else if ((mask == MASK_VERTICAL) || + (gGrabBtnVertical && (mask != MASK_SPIN))) + { + mRadioGroupMove->setValue("radio lift"); + } + else if ((mask == MASK_SPIN) || + (gGrabBtnSpin && (mask != MASK_VERTICAL))) + { + mRadioGroupMove->setValue("radio spin"); + } + + // Edit buttons + bool edit_visible = tool == LLToolCompTranslate::getInstance() || + tool == LLToolCompRotate::getInstance() || + tool == LLToolCompScale::getInstance() || + tool == LLToolFace::getInstance() || + tool == LLToolIndividual::getInstance() || + tool == LLToolPipette::getInstance(); + + mBtnEdit ->setToggleState( edit_visible ); + mRadioGroupEdit->setVisible( edit_visible ); + //bool linked_parts = gSavedSettings.getBOOL("EditLinkedParts"); + //getChildView("RenderingCost")->setVisible( !linked_parts && (edit_visible || focus_visible || move_visible) && sShowObjectCost); + + mBtnLink->setVisible(edit_visible); + mBtnUnlink->setVisible(edit_visible); + + mBtnLink->setEnabled(LLSelectMgr::instance().enableLinkObjects()); + mBtnUnlink->setEnabled(LLSelectMgr::instance().enableUnlinkObjects()); + + if (mCheckSelectIndividual) + { + mCheckSelectIndividual->setVisible(edit_visible); + //mCheckSelectIndividual->set(gSavedSettings.getBOOL("EditLinkedParts")); + } + + if ( tool == LLToolCompTranslate::getInstance() ) + { + mRadioGroupEdit->setValue("radio position"); + } + else if ( tool == LLToolCompRotate::getInstance() ) + { + mRadioGroupEdit->setValue("radio rotate"); + } + else if ( tool == LLToolCompScale::getInstance() ) + { + mRadioGroupEdit->setValue("radio stretch"); + } + else if ( tool == LLToolFace::getInstance() ) + { + mRadioGroupEdit->setValue("radio select face"); + } + + if (mComboGridMode) + { + mComboGridMode->setVisible( edit_visible ); + S32 index = mComboGridMode->getCurrentIndex(); + mComboGridMode->removeall(); + + switch (mObjectSelection->getSelectType()) + { + case SELECT_TYPE_HUD: + mComboGridMode->add(getString("grid_screen_text")); + mComboGridMode->add(getString("grid_local_text")); + break; + case SELECT_TYPE_WORLD: + mComboGridMode->add(getString("grid_world_text")); + mComboGridMode->add(getString("grid_local_text")); + mComboGridMode->add(getString("grid_reference_text")); + break; + case SELECT_TYPE_ATTACHMENT: + mComboGridMode->add(getString("grid_attachment_text")); + mComboGridMode->add(getString("grid_local_text")); + mComboGridMode->add(getString("grid_reference_text")); + break; + } + + mComboGridMode->setCurrentByIndex(index); + } + + // Snap to grid disabled for grab tool - very confusing + if (mCheckSnapToGrid) mCheckSnapToGrid->setVisible( edit_visible /* || tool == LLToolGrab::getInstance() */ ); + if (mBtnGridOptions) mBtnGridOptions->setVisible( edit_visible /* || tool == LLToolGrab::getInstance() */ ); + + //mCheckSelectLinked ->setVisible( edit_visible ); + if (mCheckStretchUniform) mCheckStretchUniform->setVisible( edit_visible ); + if (mCheckStretchTexture) mCheckStretchTexture->setVisible( edit_visible ); + if (mCheckStretchUniformLabel) mCheckStretchUniformLabel->setVisible( edit_visible ); + + // Create buttons + bool create_visible = (tool == LLToolCompCreate::getInstance()); + + mBtnCreate ->setToggleState( tool == LLToolCompCreate::getInstance() ); + + if (mCheckCopySelection + && mCheckCopySelection->get()) + { + // don't highlight any placer button + for (std::vector::size_type i = 0; i < mButtons.size(); i++) + { + mButtons[i]->setToggleState(false); + mButtons[i]->setVisible( create_visible ); + } + } + else + { + // Highlight the correct placer button + for( S32 t = 0; t < (S32)mButtons.size(); t++ ) + { + LLPCode pcode = LLToolPlacer::getObjectType(); + LLPCode button_pcode = toolData[t]; + bool state = (pcode == button_pcode); + mButtons[t]->setToggleState( state ); + mButtons[t]->setVisible( create_visible ); + } + } + + if (mCheckSticky) mCheckSticky ->setVisible( create_visible ); + if (mCheckCopySelection) mCheckCopySelection ->setVisible( create_visible ); + if (mCheckCopyCenters) mCheckCopyCenters ->setVisible( create_visible ); + if (mCheckCopyRotates) mCheckCopyRotates ->setVisible( create_visible ); + + if (mCheckCopyCenters && mCheckCopySelection) mCheckCopyCenters->setEnabled( mCheckCopySelection->get() ); + if (mCheckCopyRotates && mCheckCopySelection) mCheckCopyRotates->setEnabled( mCheckCopySelection->get() ); + + // Land buttons + bool land_visible = (tool == LLToolBrushLand::getInstance() || tool == LLToolSelectLand::getInstance() ); + + mCostTextBorder->setVisible(!land_visible); + + if (mBtnLand) mBtnLand ->setToggleState( land_visible ); + + mRadioGroupLand->setVisible( land_visible ); + if ( tool == LLToolSelectLand::getInstance() ) + { + mRadioGroupLand->setValue("radio select land"); + } + else if ( tool == LLToolBrushLand::getInstance() ) + { + S32 dozer_mode = gSavedSettings.getS32("RadioLandBrushAction"); + switch(dozer_mode) + { + case 0: + mRadioGroupLand->setValue("radio flatten"); + break; + case 1: + mRadioGroupLand->setValue("radio raise"); + break; + case 2: + mRadioGroupLand->setValue("radio lower"); + break; + case 3: + mRadioGroupLand->setValue("radio smooth"); + break; + case 4: + mRadioGroupLand->setValue("radio noise"); + break; + case 5: + mRadioGroupLand->setValue("radio revert"); + break; + default: + break; + } + } + + if (mBtnApplyToSelection) + { + mBtnApplyToSelection->setVisible( land_visible ); + mBtnApplyToSelection->setEnabled( land_visible && !LLViewerParcelMgr::getInstance()->selectionEmpty() && tool != LLToolSelectLand::getInstance()); + } + if (mSliderDozerSize) + { + mSliderDozerSize ->setVisible( land_visible ); + getChildView("Bulldozer:")->setVisible( land_visible); + getChildView("Dozer Size:")->setVisible( land_visible); + } + if (mSliderDozerForce) + { + mSliderDozerForce ->setVisible( land_visible ); + getChildView("Strength:")->setVisible( land_visible); + } + + bool have_selection = !LLSelectMgr::getInstance()->getSelection()->isEmpty(); + + getChildView("selection_count")->setVisible(!land_visible && have_selection); + getChildView("selection_faces")->setVisible(LLToolFace::getInstance() == LLToolMgr::getInstance()->getCurrentTool() + && LLSelectMgr::getInstance()->getSelection()->getObjectCount() == 1); + getChildView("selection_empty")->setVisible(!land_visible && !have_selection); + + mTab->setVisible(!land_visible); + mPanelLandInfo->setVisible(land_visible); +} + + +// virtual +bool LLFloaterTools::canClose() +{ + // don't close when quitting, so camera will stay put + return !LLApp::isExiting(); +} + +// virtual +void LLFloaterTools::onOpen(const LLSD& key) +{ + mParcelSelection = LLViewerParcelMgr::getInstance()->getFloatingParcelSelection(); + mObjectSelection = LLSelectMgr::getInstance()->getEditSelection(); + + std::string panel = key.asString(); + if (!panel.empty()) + { + mTab->selectTabByName(panel); + } + + LLTool* tool = LLToolMgr::getInstance()->getCurrentTool(); + if (tool == LLToolCompInspect::getInstance() + || tool == LLToolDragAndDrop::getInstance()) + { + // Something called floater up while it was supressed (during drag n drop, inspect), + // so it won't be getting any layout or visibility updates, update once + // further updates will come from updateLayout() + LLCoordGL select_center_screen; + MASK mask = gKeyboard->currentMask(true); + updatePopup(select_center_screen, mask); + } + + //gMenuBarView->setItemVisible("BuildTools", true); +} + +// virtual +void LLFloaterTools::onClose(bool app_quitting) +{ + mTab->setVisible(false); + + LLViewerJoystick::getInstance()->moveAvatar(false); + + // destroy media source used to grab media title + mPanelFace->unloadMedia(); + + // Different from handle_reset_view in that it doesn't actually + // move the camera if EditCameraMovement is not set. + gAgentCamera.resetView(gSavedSettings.getBOOL("EditCameraMovement")); + + // exit component selection mode + LLSelectMgr::getInstance()->promoteSelectionToRoot(); + gSavedSettings.setBOOL("EditLinkedParts", false); + + gViewerWindow->showCursor(); + + resetToolState(); + + mParcelSelection = NULL; + mObjectSelection = NULL; + + // Switch back to basic toolset + LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); + // we were already in basic toolset, using build tools + // so manually reset tool to default (pie menu tool) + LLToolMgr::getInstance()->getCurrentToolset()->selectFirstTool(); + + //gMenuBarView->setItemVisible("BuildTools", false); + LLFloaterReg::hideInstance("media_settings"); + + // hide the advanced object weights floater + LLFloaterReg::hideInstance("object_weights"); + + // hide gltf material editor + LLFloaterReg::hideInstance("live_material_editor"); + + // prepare content for next call + mPanelContents->clearContents(); + + if(sPreviousFocusOnAvatar) + { + sPreviousFocusOnAvatar = false; + gAgentCamera.setAllowChangeToFollow(true); + } +} + +void click_popup_info(void*) +{ +} + +void click_popup_done(void*) +{ + handle_reset_view(); +} + +void commit_radio_group_move(LLUICtrl* ctrl) +{ + LLRadioGroup* group = (LLRadioGroup*)ctrl; + std::string selected = group->getValue().asString(); + if (selected == "radio move") + { + gGrabBtnVertical = false; + gGrabBtnSpin = false; + } + else if (selected == "radio lift") + { + gGrabBtnVertical = true; + gGrabBtnSpin = false; + } + else if (selected == "radio spin") + { + gGrabBtnVertical = false; + gGrabBtnSpin = true; + } +} + +void commit_radio_group_focus(LLUICtrl* ctrl) +{ + LLRadioGroup* group = (LLRadioGroup*)ctrl; + std::string selected = group->getValue().asString(); + if (selected == "radio zoom") + { + gCameraBtnZoom = true; + gCameraBtnOrbit = false; + gCameraBtnPan = false; + } + else if (selected == "radio orbit") + { + gCameraBtnZoom = false; + gCameraBtnOrbit = true; + gCameraBtnPan = false; + } + else if (selected == "radio pan") + { + gCameraBtnZoom = false; + gCameraBtnOrbit = false; + gCameraBtnPan = true; + } +} + +void commit_slider_zoom(LLUICtrl *ctrl) +{ + // renormalize value, since max "volume" level is 0.5 for some reason + F32 zoom_level = (F32)ctrl->getValue().asReal() * 2.f; // / 0.5f; + gAgentCamera.setCameraZoomFraction(zoom_level); +} + +void commit_slider_dozer_force(LLUICtrl *ctrl) +{ + // the slider is logarithmic, so we exponentiate to get the actual force multiplier + F32 dozer_force = pow(10.f, (F32)ctrl->getValue().asReal()); + gSavedSettings.setF32("LandBrushForce", dozer_force); +} + +void click_apply_to_selection(void*) +{ + LLToolBrushLand::getInstance()->modifyLandInSelectionGlobal(); +} + +void commit_radio_group_edit(LLUICtrl *ctrl) +{ + S32 show_owners = gSavedSettings.getBOOL("ShowParcelOwners"); + + LLRadioGroup* group = (LLRadioGroup*)ctrl; + std::string selected = group->getValue().asString(); + if (selected == "radio position") + { + LLFloaterTools::setEditTool( LLToolCompTranslate::getInstance() ); + } + else if (selected == "radio rotate") + { + LLFloaterTools::setEditTool( LLToolCompRotate::getInstance() ); + } + else if (selected == "radio stretch") + { + LLFloaterTools::setEditTool( LLToolCompScale::getInstance() ); + } + else if (selected == "radio select face") + { + LLFloaterTools::setEditTool( LLToolFace::getInstance() ); + } + gSavedSettings.setBOOL("ShowParcelOwners", show_owners); +} + +void commit_radio_group_land(LLUICtrl* ctrl) +{ + LLRadioGroup* group = (LLRadioGroup*)ctrl; + std::string selected = group->getValue().asString(); + if (selected == "radio select land") + { + LLFloaterTools::setEditTool( LLToolSelectLand::getInstance() ); + } + else + { + LLFloaterTools::setEditTool( LLToolBrushLand::getInstance() ); + S32 dozer_mode = gSavedSettings.getS32("RadioLandBrushAction"); + if (selected == "radio flatten") + dozer_mode = 0; + else if (selected == "radio raise") + dozer_mode = 1; + else if (selected == "radio lower") + dozer_mode = 2; + else if (selected == "radio smooth") + dozer_mode = 3; + else if (selected == "radio noise") + dozer_mode = 4; + else if (selected == "radio revert") + dozer_mode = 5; + gSavedSettings.setS32("RadioLandBrushAction", dozer_mode); + } +} + +void commit_select_component(void *data) +{ + LLFloaterTools* floaterp = (LLFloaterTools*)data; + + //forfeit focus + if (gFocusMgr.childHasKeyboardFocus(floaterp)) + { + gFocusMgr.setKeyboardFocus(NULL); + } + + bool select_individuals = floaterp->mCheckSelectIndividual->get(); + gSavedSettings.setBOOL("EditLinkedParts", select_individuals); + floaterp->dirty(); + + if (select_individuals) + { + LLSelectMgr::getInstance()->demoteSelectionToIndividuals(); + } + else + { + LLSelectMgr::getInstance()->promoteSelectionToRoot(); + } +} + +// static +void LLFloaterTools::setObjectType( LLPCode pcode ) +{ + LLToolPlacer::setObjectType( pcode ); + gSavedSettings.setBOOL("CreateToolCopySelection", false); + gFocusMgr.setMouseCapture(NULL); +} + +void commit_grid_mode(LLUICtrl *ctrl) +{ + LLComboBox* combo = (LLComboBox*)ctrl; + + LLSelectMgr::getInstance()->setGridMode((EGridMode)combo->getCurrentIndex()); +} + +// static +void LLFloaterTools::setGridMode(S32 mode) +{ + LLFloaterTools* tools_floater = LLFloaterReg::getTypedInstance("build"); + if (!tools_floater || !tools_floater->mComboGridMode) + { + return; + } + + tools_floater->mComboGridMode->setCurrentByIndex(mode); +} + +void LLFloaterTools::onClickGridOptions() +{ + LLFloater* floaterp = LLFloaterReg::showInstance("build_options"); + // position floater next to build tools, not over + floaterp->setShape(gFloaterView->findNeighboringPosition(this, floaterp), true); +} + +// static +void LLFloaterTools::setEditTool(void* tool_pointer) +{ + LLTool *tool = (LLTool *)tool_pointer; + LLToolMgr::getInstance()->getCurrentToolset()->selectTool( tool ); +} + +void LLFloaterTools::setTool(const LLSD& user_data) +{ + std::string control_name = user_data.asString(); + if(control_name == "Focus") + LLToolMgr::getInstance()->getCurrentToolset()->selectTool((LLTool *) LLToolCamera::getInstance() ); + else if (control_name == "Move" ) + LLToolMgr::getInstance()->getCurrentToolset()->selectTool( (LLTool *)LLToolGrab::getInstance() ); + else if (control_name == "Edit" ) + LLToolMgr::getInstance()->getCurrentToolset()->selectTool( (LLTool *) LLToolCompTranslate::getInstance()); + else if (control_name == "Create" ) + LLToolMgr::getInstance()->getCurrentToolset()->selectTool( (LLTool *) LLToolCompCreate::getInstance()); + else if (control_name == "Land" ) + LLToolMgr::getInstance()->getCurrentToolset()->selectTool( (LLTool *) LLToolSelectLand::getInstance()); + else + LL_WARNS()<<" no parameter name "<setCurrentToolset(gBasicToolset); + LLFloater::onFocusReceived(); +} + +void LLFloaterTools::updateLandImpacts() +{ + LLParcel *parcel = mParcelSelection->getParcel(); + if (!parcel) + { + return; + } + + // Update land impacts info in the weights floater + LLFloaterObjectWeights* object_weights_floater = LLFloaterReg::findTypedInstance("object_weights"); + if(object_weights_floater) + { + object_weights_floater->updateLandImpacts(parcel); + } +} + diff --git a/indra/newview/llfloatertools.h b/indra/newview/llfloatertools.h index a3dcf244bc..f9c3b401bb 100644 --- a/indra/newview/llfloatertools.h +++ b/indra/newview/llfloatertools.h @@ -1,197 +1,197 @@ -/** - * @file llfloatertools.h - * @brief The edit tools, including move, position, land, etc. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERTOOLS_H -#define LL_LLFLOATERTOOLS_H - -#include "llfloater.h" -#include "llcoord.h" -#include "llparcelselection.h" - -class LLButton; -class LLCheckBoxCtrl; -class LLComboBox; -class LLPanelPermissions; -class LLPanelObject; -class LLPanelVolume; -class LLPanelContents; -class LLPanelFace; -class LLPanelLandInfo; -class LLRadioGroup; -class LLSlider; -class LLTabContainer; -class LLTextBox; -class LLTool; -class LLParcelSelection; -class LLObjectSelection; -class LLLandImpactsObserver; - -typedef LLSafeHandle LLObjectSelectionHandle; - -class LLFloaterTools -: public LLFloater -{ -public: - virtual bool postBuild(); - static void* createPanelPermissions(void* vdata); - static void* createPanelObject(void* vdata); - static void* createPanelVolume(void* vdata); - static void* createPanelFace(void* vdata); - static void* createPanelContents(void* vdata); - static void* createPanelLandInfo(void* vdata); - - LLFloaterTools(const LLSD& key); - virtual ~LLFloaterTools(); - - /*virtual*/ void onOpen(const LLSD& key); - /*virtual*/ bool canClose(); - /*virtual*/ void onClose(bool app_quitting); - /*virtual*/ void draw(); - /*virtual*/ void onFocusReceived(); - - // call this once per frame to handle visibility, rect location, - // button highlights, etc. - void updatePopup(LLCoordGL center, MASK mask); - - // When the floater is going away, reset any options that need to be - // cleared. - void resetToolState(); - - enum EInfoPanel - { - PANEL_GENERAL=0, - PANEL_OBJECT, - PANEL_FEATURES, - PANEL_FACE, - PANEL_CONTENTS, - PANEL_COUNT - }; - - void dirty(); - void showPanel(EInfoPanel panel); - - void setStatusText(const std::string& text); - static void setEditTool(void* data); - void setTool(const LLSD& user_data); - void saveLastTool(); - void updateLandImpacts(); - - static void setGridMode(S32 mode); - - LLPanelFace* getPanelFace() { return mPanelFace; } - -private: - void refresh(); - static void setObjectType( LLPCode pcode ); - void onClickGridOptions(); - -public: - LLButton *mBtnFocus; - LLButton *mBtnMove; - LLButton *mBtnEdit; - LLButton *mBtnCreate; - LLButton *mBtnLand; - - LLTextBox *mTextStatus; - - // Focus buttons - LLRadioGroup* mRadioGroupFocus; - - // Move buttons - LLRadioGroup* mRadioGroupMove; - - // Edit buttons - LLRadioGroup* mRadioGroupEdit; - - LLCheckBoxCtrl *mCheckSelectIndividual; - LLButton* mBtnLink; - LLButton* mBtnUnlink; - - LLCheckBoxCtrl* mCheckSnapToGrid; - LLButton* mBtnGridOptions; - LLComboBox* mComboGridMode; - LLCheckBoxCtrl* mCheckStretchUniform; - LLCheckBoxCtrl* mCheckStretchTexture; - - // !HACK! Replacement of mCheckStretchUniform label because LLCheckBoxCtrl - // doesn't support word_wrap of its label. Need to fix truncation bug EXT-6658 - LLTextBox* mCheckStretchUniformLabel; - - LLButton *mBtnRotateLeft; - LLButton *mBtnRotateReset; - LLButton *mBtnRotateRight; - - LLButton *mBtnDelete; - LLButton *mBtnDuplicate; - LLButton *mBtnDuplicateInPlace; - - // Create buttons - LLCheckBoxCtrl *mCheckSticky; - LLCheckBoxCtrl *mCheckCopySelection; - LLCheckBoxCtrl *mCheckCopyCenters; - LLCheckBoxCtrl *mCheckCopyRotates; - - // Land buttons - LLRadioGroup* mRadioGroupLand; - LLSlider *mSliderDozerSize; - LLSlider *mSliderDozerForce; - - LLButton *mBtnApplyToSelection; - - std::vector mButtons;//[ 15 ]; - - LLTabContainer *mTab; - LLPanelPermissions *mPanelPermissions; - LLPanelObject *mPanelObject; - LLPanelVolume *mPanelVolume; - LLPanelContents *mPanelContents; - LLPanelFace *mPanelFace; - LLPanelLandInfo *mPanelLandInfo; - - LLViewBorder* mCostTextBorder; - - LLTabContainer* mTabLand; - - LLLandImpactsObserver* mLandImpactsObserver; - - LLParcelSelectionHandle mParcelSelection; - LLObjectSelectionHandle mObjectSelection; - -private: - bool mDirty; - bool mHasSelection; - - std::map mStatusText; - -public: - static bool sShowObjectCost; - static bool sPreviousFocusOnAvatar; - -}; - -extern LLFloaterTools *gFloaterTools; - -#endif +/** + * @file llfloatertools.h + * @brief The edit tools, including move, position, land, etc. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERTOOLS_H +#define LL_LLFLOATERTOOLS_H + +#include "llfloater.h" +#include "llcoord.h" +#include "llparcelselection.h" + +class LLButton; +class LLCheckBoxCtrl; +class LLComboBox; +class LLPanelPermissions; +class LLPanelObject; +class LLPanelVolume; +class LLPanelContents; +class LLPanelFace; +class LLPanelLandInfo; +class LLRadioGroup; +class LLSlider; +class LLTabContainer; +class LLTextBox; +class LLTool; +class LLParcelSelection; +class LLObjectSelection; +class LLLandImpactsObserver; + +typedef LLSafeHandle LLObjectSelectionHandle; + +class LLFloaterTools +: public LLFloater +{ +public: + virtual bool postBuild(); + static void* createPanelPermissions(void* vdata); + static void* createPanelObject(void* vdata); + static void* createPanelVolume(void* vdata); + static void* createPanelFace(void* vdata); + static void* createPanelContents(void* vdata); + static void* createPanelLandInfo(void* vdata); + + LLFloaterTools(const LLSD& key); + virtual ~LLFloaterTools(); + + /*virtual*/ void onOpen(const LLSD& key); + /*virtual*/ bool canClose(); + /*virtual*/ void onClose(bool app_quitting); + /*virtual*/ void draw(); + /*virtual*/ void onFocusReceived(); + + // call this once per frame to handle visibility, rect location, + // button highlights, etc. + void updatePopup(LLCoordGL center, MASK mask); + + // When the floater is going away, reset any options that need to be + // cleared. + void resetToolState(); + + enum EInfoPanel + { + PANEL_GENERAL=0, + PANEL_OBJECT, + PANEL_FEATURES, + PANEL_FACE, + PANEL_CONTENTS, + PANEL_COUNT + }; + + void dirty(); + void showPanel(EInfoPanel panel); + + void setStatusText(const std::string& text); + static void setEditTool(void* data); + void setTool(const LLSD& user_data); + void saveLastTool(); + void updateLandImpacts(); + + static void setGridMode(S32 mode); + + LLPanelFace* getPanelFace() { return mPanelFace; } + +private: + void refresh(); + static void setObjectType( LLPCode pcode ); + void onClickGridOptions(); + +public: + LLButton *mBtnFocus; + LLButton *mBtnMove; + LLButton *mBtnEdit; + LLButton *mBtnCreate; + LLButton *mBtnLand; + + LLTextBox *mTextStatus; + + // Focus buttons + LLRadioGroup* mRadioGroupFocus; + + // Move buttons + LLRadioGroup* mRadioGroupMove; + + // Edit buttons + LLRadioGroup* mRadioGroupEdit; + + LLCheckBoxCtrl *mCheckSelectIndividual; + LLButton* mBtnLink; + LLButton* mBtnUnlink; + + LLCheckBoxCtrl* mCheckSnapToGrid; + LLButton* mBtnGridOptions; + LLComboBox* mComboGridMode; + LLCheckBoxCtrl* mCheckStretchUniform; + LLCheckBoxCtrl* mCheckStretchTexture; + + // !HACK! Replacement of mCheckStretchUniform label because LLCheckBoxCtrl + // doesn't support word_wrap of its label. Need to fix truncation bug EXT-6658 + LLTextBox* mCheckStretchUniformLabel; + + LLButton *mBtnRotateLeft; + LLButton *mBtnRotateReset; + LLButton *mBtnRotateRight; + + LLButton *mBtnDelete; + LLButton *mBtnDuplicate; + LLButton *mBtnDuplicateInPlace; + + // Create buttons + LLCheckBoxCtrl *mCheckSticky; + LLCheckBoxCtrl *mCheckCopySelection; + LLCheckBoxCtrl *mCheckCopyCenters; + LLCheckBoxCtrl *mCheckCopyRotates; + + // Land buttons + LLRadioGroup* mRadioGroupLand; + LLSlider *mSliderDozerSize; + LLSlider *mSliderDozerForce; + + LLButton *mBtnApplyToSelection; + + std::vector mButtons;//[ 15 ]; + + LLTabContainer *mTab; + LLPanelPermissions *mPanelPermissions; + LLPanelObject *mPanelObject; + LLPanelVolume *mPanelVolume; + LLPanelContents *mPanelContents; + LLPanelFace *mPanelFace; + LLPanelLandInfo *mPanelLandInfo; + + LLViewBorder* mCostTextBorder; + + LLTabContainer* mTabLand; + + LLLandImpactsObserver* mLandImpactsObserver; + + LLParcelSelectionHandle mParcelSelection; + LLObjectSelectionHandle mObjectSelection; + +private: + bool mDirty; + bool mHasSelection; + + std::map mStatusText; + +public: + static bool sShowObjectCost; + static bool sPreviousFocusOnAvatar; + +}; + +extern LLFloaterTools *gFloaterTools; + +#endif diff --git a/indra/newview/llfloatertopobjects.cpp b/indra/newview/llfloatertopobjects.cpp index 412d764af7..64b22c4bb1 100644 --- a/indra/newview/llfloatertopobjects.cpp +++ b/indra/newview/llfloatertopobjects.cpp @@ -1,550 +1,550 @@ -/** - * @file llfloatertopobjects.cpp - * @brief Shows top colliders, top scripts, etc. - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloatertopobjects.h" - -// library includes -#include "message.h" -#include "llavatarnamecache.h" -#include "llfontgl.h" - -#include "llagent.h" -#include "llbutton.h" -#include "llfloatergodtools.h" -#include "llfloaterreg.h" -#include "llnotificationsutil.h" -#include "llparcel.h" -#include "llscrolllistctrl.h" -#include "llscrolllistitem.h" -#include "llscrolllistcell.h" -#include "lllineeditor.h" -#include "lltextbox.h" -#include "lltracker.h" -#include "llviewermessage.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" -#include "lluictrlfactory.h" -#include "llviewerobjectlist.h" -#include "llviewerwindow.h" -#include "llfloaterregioninfo.h" - -//LLFloaterTopObjects* LLFloaterTopObjects::sInstance = NULL; - -// Globals -// const U32 TIME_STR_LENGTH = 30; -/* -// static -void LLFloaterTopObjects::show() -{ - if (sInstance) - { - sInstance->setVisibleAndFrontmost(); - return; - } - - sInstance = new LLFloaterTopObjects(); - sInstance->center(); -} -*/ -LLFloaterTopObjects::LLFloaterTopObjects(const LLSD& key) -: LLFloater(key), - mInitialized(false), - mtotalScore(0.f) -{ - mCommitCallbackRegistrar.add("TopObjects.ShowBeacon", boost::bind(&LLFloaterTopObjects::onClickShowBeacon, this)); - mCommitCallbackRegistrar.add("TopObjects.ReturnSelected", boost::bind(&LLFloaterTopObjects::onReturnSelected, this)); - mCommitCallbackRegistrar.add("TopObjects.ReturnAll", boost::bind(&LLFloaterTopObjects::onReturnAll, this)); - mCommitCallbackRegistrar.add("TopObjects.Refresh", boost::bind(&LLFloaterTopObjects::onRefresh, this)); - mCommitCallbackRegistrar.add("TopObjects.GetByObjectName", boost::bind(&LLFloaterTopObjects::onGetByObjectName, this)); - mCommitCallbackRegistrar.add("TopObjects.GetByOwnerName", boost::bind(&LLFloaterTopObjects::onGetByOwnerName, this)); - mCommitCallbackRegistrar.add("TopObjects.GetByParcelName", boost::bind(&LLFloaterTopObjects::onGetByParcelName, this)); - mCommitCallbackRegistrar.add("TopObjects.CommitObjectsList",boost::bind(&LLFloaterTopObjects::onCommitObjectsList, this)); - - mCommitCallbackRegistrar.add("TopObjects.TeleportToSelected", boost::bind(&LLFloaterTopObjects::teleportToSelectedObject, this)); -} - -LLFloaterTopObjects::~LLFloaterTopObjects() -{ -} - -// virtual -bool LLFloaterTopObjects::postBuild() -{ - mObjectsScrollList = getChild("objects_list"); - mObjectsScrollList->setFocus(true); - mObjectsScrollList->setDoubleClickCallback(onDoubleClickObjectsList, this); - mObjectsScrollList->setCommitOnSelectionChange(true); - mObjectsScrollList->setCommitCallback(boost::bind(&LLFloaterTopObjects::onSelectionChanged, this)); - - setDefaultBtn("show_beacon_btn"); - - mCurrentMode = STAT_REPORT_TOP_SCRIPTS; - mFlags = 0; - mFilter.clear(); - - return true; -} -// static -void LLFloaterTopObjects::setMode(U32 mode) -{ - LLFloaterTopObjects* instance = LLFloaterReg::getTypedInstance("top_objects"); - if(!instance) return; - instance->mCurrentMode = mode; -} - -// static -void LLFloaterTopObjects::handle_land_reply(LLMessageSystem* msg, void** data) -{ - LLFloaterTopObjects* instance = LLFloaterReg::getTypedInstance("top_objects"); - if(instance && instance->isInVisibleChain()) - { - instance->handleReply(msg, data); - //HACK: for some reason sometimes top scripts originally comes back - //with no results even though they're there - if (!instance->mObjectListIDs.size() && !instance->mInitialized) - { - instance->onRefresh(); - instance->mInitialized = true; - } - } - else - { - LLFloaterRegionInfo* region_info_floater = LLFloaterReg::getTypedInstance("region_info"); - if(region_info_floater) - { - region_info_floater->enableTopButtons(); - } - } - -} - -void LLFloaterTopObjects::handleReply(LLMessageSystem *msg, void** data) -{ - U32 request_flags; - U32 total_count; - U64 total_memory = 0; - - msg->getU32Fast(_PREHASH_RequestData, _PREHASH_RequestFlags, request_flags); - msg->getU32Fast(_PREHASH_RequestData, _PREHASH_TotalObjectCount, total_count); - msg->getU32Fast(_PREHASH_RequestData, _PREHASH_ReportType, mCurrentMode); - - LLScrollListCtrl *list = getChild("objects_list"); - - S32 block_count = msg->getNumberOfBlocks("ReportData"); - for (S32 block = 0; block < block_count; ++block) - { - U32 task_local_id; - U32 time_stamp = 0; - LLUUID task_id; - F32 location_x, location_y, location_z; - F32 score; - std::string name_buf; - std::string owner_buf; - std::string parcel_buf("unknown"); - F32 mono_score = 0.f; - bool have_extended_data = false; - S32 public_urls = 0; - F32 script_memory = 0.f; - - msg->getU32Fast(_PREHASH_ReportData, _PREHASH_TaskLocalID, task_local_id, block); - msg->getUUIDFast(_PREHASH_ReportData, _PREHASH_TaskID, task_id, block); - msg->getF32Fast(_PREHASH_ReportData, _PREHASH_LocationX, location_x, block); - msg->getF32Fast(_PREHASH_ReportData, _PREHASH_LocationY, location_y, block); - msg->getF32Fast(_PREHASH_ReportData, _PREHASH_LocationZ, location_z, block); - msg->getF32Fast(_PREHASH_ReportData, _PREHASH_Score, score, block); - msg->getStringFast(_PREHASH_ReportData, _PREHASH_TaskName, name_buf, block); - msg->getStringFast(_PREHASH_ReportData, _PREHASH_OwnerName, owner_buf, block); - - if(msg->has("DataExtended")) - { - have_extended_data = true; - msg->getU32("DataExtended", "TimeStamp", time_stamp, block); - msg->getF32("DataExtended", "MonoScore", mono_score, block); - msg->getS32("DataExtended", "PublicURLs", public_urls, block); - - std::string parcel_name; - F32 script_size = 0.f; - msg->getString("DataExtended", "ParcelName", parcel_name, block); - msg->getF32("DataExtended", "Size", script_size, block); - if (parcel_name.size() > 0 || script_size > 0) - { - parcel_buf = parcel_name; - script_memory = script_size; - total_memory += script_size; - } - } - - LLSD element; - - element["id"] = task_id; - - LLSD columns; - S32 column_num = 0; - columns[column_num]["column"] = "score"; - columns[column_num]["value"] = llformat("%0.3f", score); - columns[column_num++]["font"] = "SANSSERIF"; - - columns[column_num]["column"] = "name"; - columns[column_num]["value"] = name_buf; - columns[column_num++]["font"] = "SANSSERIF"; - - // Owner names can have trailing spaces sent from server - LLStringUtil::trim(owner_buf); - - // *TODO: Send owner_id from server and look up display name - owner_buf = LLCacheName::buildUsername(owner_buf); - - columns[column_num]["column"] = "owner"; - columns[column_num]["value"] = owner_buf; - columns[column_num++]["font"] = "SANSSERIF"; - - columns[column_num]["column"] = "location"; - columns[column_num]["value"] = llformat("<%0.f, %0.f, %0.f>", location_x, location_y, location_z); - columns[column_num++]["font"] = "SANSSERIF"; - - columns[column_num]["column"] = "parcel"; - columns[column_num]["value"] = parcel_buf; - columns[column_num++]["font"] = "SANSSERIF"; - - columns[column_num]["column"] = "time"; - columns[column_num]["type"] = "date"; - columns[column_num]["value"] = LLDate((time_t)time_stamp); - columns[column_num++]["font"] = "SANSSERIF"; - - if (mCurrentMode == STAT_REPORT_TOP_SCRIPTS - && have_extended_data) - { - columns[column_num]["column"] = "memory"; - columns[column_num]["value"] = llformat("%0.0f", (script_memory / 1024.f)); - columns[column_num++]["font"] = "SANSSERIF"; - - columns[column_num]["column"] = "URLs"; - columns[column_num]["value"] = llformat("%d", public_urls); - columns[column_num++]["font"] = "SANSSERIF"; - } - element["columns"] = columns; - list->addElement(element); - - mObjectListData.append(element); - mObjectListIDs.push_back(task_id); - - mtotalScore += score; - } - - if (total_count == 0 && list->getItemCount() == 0) - { - list->setCommentText(getString("none_descriptor")); - } - else - { - list->selectFirstItem(); - } - - if (mCurrentMode == STAT_REPORT_TOP_SCRIPTS) - { - setTitle(getString("top_scripts_title")); - list->setColumnLabel("score", getString("scripts_score_label")); - - LLUIString format = getString("top_scripts_text"); - total_memory /= 1024; - format.setArg("[MEMORY]", llformat("%ld", total_memory)); - format.setArg("[COUNT]", llformat("%d", total_count)); - format.setArg("[TIME]", llformat("%0.3f", mtotalScore)); - getChild("title_text")->setValue(LLSD(format)); - list->setColumnLabel("URLs", getString("URLs")); - list->setColumnLabel("memory", getString("memory")); - } - else - { - setTitle(getString("top_colliders_title")); - list->setColumnLabel("score", getString("colliders_score_label")); - list->setColumnLabel("URLs", ""); - list->setColumnLabel("memory", ""); - LLUIString format = getString("top_colliders_text"); - format.setArg("[COUNT]", llformat("%d", total_count)); - getChild("title_text")->setValue(LLSD(format)); - } - - LLFloaterRegionInfo* region_info_floater = LLFloaterReg::getTypedInstance("region_info"); - if(region_info_floater) - { - region_info_floater->enableTopButtons(); - } - getChildView("refresh_btn")->setEnabled(true); -} - -void LLFloaterTopObjects::onCommitObjectsList() -{ - updateSelectionInfo(); -} - -void LLFloaterTopObjects::updateSelectionInfo() -{ - LLScrollListCtrl* list = getChild("objects_list"); - - if (!list) return; - - LLUUID object_id = list->getCurrentID(); - if (object_id.isNull()) return; - - std::string object_id_string = object_id.asString(); - - getChild("id_editor")->setValue(LLSD(object_id_string)); - LLScrollListItem* sli = list->getFirstSelected(); - llassert(sli); - if (sli) - { - getChild("object_name_editor")->setValue(sli->getColumn(1)->getValue().asString()); - getChild("owner_name_editor")->setValue(sli->getColumn(2)->getValue().asString()); - getChild("parcel_name_editor")->setValue(sli->getColumn(4)->getValue().asString()); - } -} - -// static -void LLFloaterTopObjects::onDoubleClickObjectsList(void* data) -{ - LLFloaterTopObjects* self = (LLFloaterTopObjects*)data; - self->showBeacon(); -} - -// static -void LLFloaterTopObjects::onClickShowBeacon() -{ - showBeacon(); -} - -void LLFloaterTopObjects::returnObjects(bool all) -{ - LLMessageSystem *msg = gMessageSystem; - - LLViewerRegion* region = gAgent.getRegion(); - if (!region) return; - - LLCtrlListInterface *list = getChild("objects_list")->getListInterface(); - if (!list || list->getItemCount() == 0) return; - - uuid_vec_t::iterator id_itor; - - bool start_message = true; - - for (id_itor = mObjectListIDs.begin(); id_itor != mObjectListIDs.end(); ++id_itor) - { - LLUUID task_id = *id_itor; - if (!all && !list->isSelected(task_id)) - { - // Selected only - continue; - } - if (start_message) - { - msg->newMessageFast(_PREHASH_ParcelReturnObjects); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ParcelData); - msg->addS32Fast(_PREHASH_LocalID, -1); // Whole region - msg->addS32Fast(_PREHASH_ReturnType, RT_NONE); - start_message = false; - } - - msg->nextBlockFast(_PREHASH_TaskIDs); - msg->addUUIDFast(_PREHASH_TaskID, task_id); - - if (msg->isSendFullFast(_PREHASH_TaskIDs)) - { - msg->sendReliable(region->getHost()); - start_message = true; - } - } - - if (!start_message) - { - msg->sendReliable(region->getHost()); - } -} - -//static -bool LLFloaterTopObjects::callbackReturnAll(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - LLFloaterTopObjects* instance = LLFloaterReg::getTypedInstance("top_objects"); - if(!instance) return false; - if (option == 0) - { - instance->returnObjects(true); - } - return false; -} - -void LLFloaterTopObjects::onReturnAll() -{ - LLNotificationsUtil::add("ReturnAllTopObjects", LLSD(), LLSD(), &callbackReturnAll); -} - - -void LLFloaterTopObjects::onReturnSelected() -{ - returnObjects(false); -} - - -void LLFloaterTopObjects::clearList() -{ - LLCtrlListInterface *list = childGetListInterface("objects_list"); - - if (list) - { - list->operateOnAll(LLCtrlListInterface::OP_DELETE); - } - - mObjectListData.clear(); - mObjectListIDs.clear(); - mtotalScore = 0.f; - - onSelectionChanged(); -} - - -void LLFloaterTopObjects::onRefresh() -{ - U32 mode = STAT_REPORT_TOP_SCRIPTS; - U32 flags = 0; - std::string filter = ""; - - mode = mCurrentMode; - flags = mFlags; - filter = mFilter; - clearList(); - - LLMessageSystem *msg = gMessageSystem; - msg->newMessageFast(_PREHASH_LandStatRequest); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); - msg->nextBlockFast(_PREHASH_RequestData); - msg->addU32Fast(_PREHASH_ReportType, mode); - msg->addU32Fast(_PREHASH_RequestFlags, flags); - msg->addStringFast(_PREHASH_Filter, filter); - msg->addS32Fast(_PREHASH_ParcelLocalID, 0); - - LLFloaterRegionInfo* region_info_floater = LLFloaterReg::getTypedInstance("region_info"); - if(region_info_floater) - { - region_info_floater->disableTopButtons(); - } - disableRefreshBtn(); - - msg->sendReliable(gAgent.getRegionHost()); - - mFilter.clear(); - mFlags = 0; -} - -void LLFloaterTopObjects::disableRefreshBtn() -{ - getChildView("refresh_btn")->setEnabled(false); -} - -void LLFloaterTopObjects::onGetByObjectName() -{ - mFlags = STAT_FILTER_BY_OBJECT; - mFilter = getChild("object_name_editor")->getValue().asString(); - onRefresh(); -} - -void LLFloaterTopObjects::onGetByOwnerName() -{ - mFlags = STAT_FILTER_BY_OWNER; - mFilter = getChild("owner_name_editor")->getValue().asString(); - onRefresh(); -} - - -void LLFloaterTopObjects::onGetByParcelName() -{ - mFlags = STAT_FILTER_BY_PARCEL_NAME; - mFilter = getChild("parcel_name_editor")->getValue().asString(); - onRefresh(); -} - - -void LLFloaterTopObjects::showBeacon() -{ - LLScrollListCtrl* list = getChild("objects_list"); - if (!list) return; - - LLScrollListItem* first_selected = list->getFirstSelected(); - if (!first_selected) return; - - std::string name = first_selected->getColumn(1)->getValue().asString(); - std::string pos_string = first_selected->getColumn(3)->getValue().asString(); - - F32 x, y, z; - S32 matched = sscanf(pos_string.c_str(), "<%g,%g,%g>", &x, &y, &z); - if (matched != 3) return; - - LLVector3 pos_agent(x, y, z); - LLVector3d pos_global = gAgent.getPosGlobalFromAgent(pos_agent); - std::string tooltip(""); - LLTracker::trackLocation(pos_global, name, tooltip, LLTracker::LOCATION_ITEM); -} - -void LLFloaterTopObjects::teleportToSelectedObject() -{ - std::vector selected_items = mObjectsScrollList->getAllSelected(); - if (selected_items.size() == 1) - { - LLScrollListItem* first_selected = selected_items.front(); - - LLVector3d teleport_location; - LLViewerObject *viewer_object = gObjectList.findObject(first_selected->getUUID()); - if (viewer_object == NULL) - { - // If we cannot find the object in the viewer list, teleport to the last reported position - std::string pos_string = first_selected->getColumn(3)->getValue().asString(); - - F32 x, y, z; - S32 matched = sscanf(pos_string.c_str(), "<%g,%g,%g>", &x, &y, &z); - if (matched != 3) return; - - LLVector3 pos_agent(x, y, z); - teleport_location = gAgent.getPosGlobalFromAgent(pos_agent); - } - else - { - // If we can find the object in the viewer list, teleport to the known current position - teleport_location = viewer_object->getPositionGlobal(); - } - gAgent.teleportViaLocationLookAt(teleport_location); - } -} - -void LLFloaterTopObjects::onSelectionChanged() -{ - getChildView("teleport_btn")->setEnabled(mObjectsScrollList->getNumSelected() == 1); -} +/** + * @file llfloatertopobjects.cpp + * @brief Shows top colliders, top scripts, etc. + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatertopobjects.h" + +// library includes +#include "message.h" +#include "llavatarnamecache.h" +#include "llfontgl.h" + +#include "llagent.h" +#include "llbutton.h" +#include "llfloatergodtools.h" +#include "llfloaterreg.h" +#include "llnotificationsutil.h" +#include "llparcel.h" +#include "llscrolllistctrl.h" +#include "llscrolllistitem.h" +#include "llscrolllistcell.h" +#include "lllineeditor.h" +#include "lltextbox.h" +#include "lltracker.h" +#include "llviewermessage.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "lluictrlfactory.h" +#include "llviewerobjectlist.h" +#include "llviewerwindow.h" +#include "llfloaterregioninfo.h" + +//LLFloaterTopObjects* LLFloaterTopObjects::sInstance = NULL; + +// Globals +// const U32 TIME_STR_LENGTH = 30; +/* +// static +void LLFloaterTopObjects::show() +{ + if (sInstance) + { + sInstance->setVisibleAndFrontmost(); + return; + } + + sInstance = new LLFloaterTopObjects(); + sInstance->center(); +} +*/ +LLFloaterTopObjects::LLFloaterTopObjects(const LLSD& key) +: LLFloater(key), + mInitialized(false), + mtotalScore(0.f) +{ + mCommitCallbackRegistrar.add("TopObjects.ShowBeacon", boost::bind(&LLFloaterTopObjects::onClickShowBeacon, this)); + mCommitCallbackRegistrar.add("TopObjects.ReturnSelected", boost::bind(&LLFloaterTopObjects::onReturnSelected, this)); + mCommitCallbackRegistrar.add("TopObjects.ReturnAll", boost::bind(&LLFloaterTopObjects::onReturnAll, this)); + mCommitCallbackRegistrar.add("TopObjects.Refresh", boost::bind(&LLFloaterTopObjects::onRefresh, this)); + mCommitCallbackRegistrar.add("TopObjects.GetByObjectName", boost::bind(&LLFloaterTopObjects::onGetByObjectName, this)); + mCommitCallbackRegistrar.add("TopObjects.GetByOwnerName", boost::bind(&LLFloaterTopObjects::onGetByOwnerName, this)); + mCommitCallbackRegistrar.add("TopObjects.GetByParcelName", boost::bind(&LLFloaterTopObjects::onGetByParcelName, this)); + mCommitCallbackRegistrar.add("TopObjects.CommitObjectsList",boost::bind(&LLFloaterTopObjects::onCommitObjectsList, this)); + + mCommitCallbackRegistrar.add("TopObjects.TeleportToSelected", boost::bind(&LLFloaterTopObjects::teleportToSelectedObject, this)); +} + +LLFloaterTopObjects::~LLFloaterTopObjects() +{ +} + +// virtual +bool LLFloaterTopObjects::postBuild() +{ + mObjectsScrollList = getChild("objects_list"); + mObjectsScrollList->setFocus(true); + mObjectsScrollList->setDoubleClickCallback(onDoubleClickObjectsList, this); + mObjectsScrollList->setCommitOnSelectionChange(true); + mObjectsScrollList->setCommitCallback(boost::bind(&LLFloaterTopObjects::onSelectionChanged, this)); + + setDefaultBtn("show_beacon_btn"); + + mCurrentMode = STAT_REPORT_TOP_SCRIPTS; + mFlags = 0; + mFilter.clear(); + + return true; +} +// static +void LLFloaterTopObjects::setMode(U32 mode) +{ + LLFloaterTopObjects* instance = LLFloaterReg::getTypedInstance("top_objects"); + if(!instance) return; + instance->mCurrentMode = mode; +} + +// static +void LLFloaterTopObjects::handle_land_reply(LLMessageSystem* msg, void** data) +{ + LLFloaterTopObjects* instance = LLFloaterReg::getTypedInstance("top_objects"); + if(instance && instance->isInVisibleChain()) + { + instance->handleReply(msg, data); + //HACK: for some reason sometimes top scripts originally comes back + //with no results even though they're there + if (!instance->mObjectListIDs.size() && !instance->mInitialized) + { + instance->onRefresh(); + instance->mInitialized = true; + } + } + else + { + LLFloaterRegionInfo* region_info_floater = LLFloaterReg::getTypedInstance("region_info"); + if(region_info_floater) + { + region_info_floater->enableTopButtons(); + } + } + +} + +void LLFloaterTopObjects::handleReply(LLMessageSystem *msg, void** data) +{ + U32 request_flags; + U32 total_count; + U64 total_memory = 0; + + msg->getU32Fast(_PREHASH_RequestData, _PREHASH_RequestFlags, request_flags); + msg->getU32Fast(_PREHASH_RequestData, _PREHASH_TotalObjectCount, total_count); + msg->getU32Fast(_PREHASH_RequestData, _PREHASH_ReportType, mCurrentMode); + + LLScrollListCtrl *list = getChild("objects_list"); + + S32 block_count = msg->getNumberOfBlocks("ReportData"); + for (S32 block = 0; block < block_count; ++block) + { + U32 task_local_id; + U32 time_stamp = 0; + LLUUID task_id; + F32 location_x, location_y, location_z; + F32 score; + std::string name_buf; + std::string owner_buf; + std::string parcel_buf("unknown"); + F32 mono_score = 0.f; + bool have_extended_data = false; + S32 public_urls = 0; + F32 script_memory = 0.f; + + msg->getU32Fast(_PREHASH_ReportData, _PREHASH_TaskLocalID, task_local_id, block); + msg->getUUIDFast(_PREHASH_ReportData, _PREHASH_TaskID, task_id, block); + msg->getF32Fast(_PREHASH_ReportData, _PREHASH_LocationX, location_x, block); + msg->getF32Fast(_PREHASH_ReportData, _PREHASH_LocationY, location_y, block); + msg->getF32Fast(_PREHASH_ReportData, _PREHASH_LocationZ, location_z, block); + msg->getF32Fast(_PREHASH_ReportData, _PREHASH_Score, score, block); + msg->getStringFast(_PREHASH_ReportData, _PREHASH_TaskName, name_buf, block); + msg->getStringFast(_PREHASH_ReportData, _PREHASH_OwnerName, owner_buf, block); + + if(msg->has("DataExtended")) + { + have_extended_data = true; + msg->getU32("DataExtended", "TimeStamp", time_stamp, block); + msg->getF32("DataExtended", "MonoScore", mono_score, block); + msg->getS32("DataExtended", "PublicURLs", public_urls, block); + + std::string parcel_name; + F32 script_size = 0.f; + msg->getString("DataExtended", "ParcelName", parcel_name, block); + msg->getF32("DataExtended", "Size", script_size, block); + if (parcel_name.size() > 0 || script_size > 0) + { + parcel_buf = parcel_name; + script_memory = script_size; + total_memory += script_size; + } + } + + LLSD element; + + element["id"] = task_id; + + LLSD columns; + S32 column_num = 0; + columns[column_num]["column"] = "score"; + columns[column_num]["value"] = llformat("%0.3f", score); + columns[column_num++]["font"] = "SANSSERIF"; + + columns[column_num]["column"] = "name"; + columns[column_num]["value"] = name_buf; + columns[column_num++]["font"] = "SANSSERIF"; + + // Owner names can have trailing spaces sent from server + LLStringUtil::trim(owner_buf); + + // *TODO: Send owner_id from server and look up display name + owner_buf = LLCacheName::buildUsername(owner_buf); + + columns[column_num]["column"] = "owner"; + columns[column_num]["value"] = owner_buf; + columns[column_num++]["font"] = "SANSSERIF"; + + columns[column_num]["column"] = "location"; + columns[column_num]["value"] = llformat("<%0.f, %0.f, %0.f>", location_x, location_y, location_z); + columns[column_num++]["font"] = "SANSSERIF"; + + columns[column_num]["column"] = "parcel"; + columns[column_num]["value"] = parcel_buf; + columns[column_num++]["font"] = "SANSSERIF"; + + columns[column_num]["column"] = "time"; + columns[column_num]["type"] = "date"; + columns[column_num]["value"] = LLDate((time_t)time_stamp); + columns[column_num++]["font"] = "SANSSERIF"; + + if (mCurrentMode == STAT_REPORT_TOP_SCRIPTS + && have_extended_data) + { + columns[column_num]["column"] = "memory"; + columns[column_num]["value"] = llformat("%0.0f", (script_memory / 1024.f)); + columns[column_num++]["font"] = "SANSSERIF"; + + columns[column_num]["column"] = "URLs"; + columns[column_num]["value"] = llformat("%d", public_urls); + columns[column_num++]["font"] = "SANSSERIF"; + } + element["columns"] = columns; + list->addElement(element); + + mObjectListData.append(element); + mObjectListIDs.push_back(task_id); + + mtotalScore += score; + } + + if (total_count == 0 && list->getItemCount() == 0) + { + list->setCommentText(getString("none_descriptor")); + } + else + { + list->selectFirstItem(); + } + + if (mCurrentMode == STAT_REPORT_TOP_SCRIPTS) + { + setTitle(getString("top_scripts_title")); + list->setColumnLabel("score", getString("scripts_score_label")); + + LLUIString format = getString("top_scripts_text"); + total_memory /= 1024; + format.setArg("[MEMORY]", llformat("%ld", total_memory)); + format.setArg("[COUNT]", llformat("%d", total_count)); + format.setArg("[TIME]", llformat("%0.3f", mtotalScore)); + getChild("title_text")->setValue(LLSD(format)); + list->setColumnLabel("URLs", getString("URLs")); + list->setColumnLabel("memory", getString("memory")); + } + else + { + setTitle(getString("top_colliders_title")); + list->setColumnLabel("score", getString("colliders_score_label")); + list->setColumnLabel("URLs", ""); + list->setColumnLabel("memory", ""); + LLUIString format = getString("top_colliders_text"); + format.setArg("[COUNT]", llformat("%d", total_count)); + getChild("title_text")->setValue(LLSD(format)); + } + + LLFloaterRegionInfo* region_info_floater = LLFloaterReg::getTypedInstance("region_info"); + if(region_info_floater) + { + region_info_floater->enableTopButtons(); + } + getChildView("refresh_btn")->setEnabled(true); +} + +void LLFloaterTopObjects::onCommitObjectsList() +{ + updateSelectionInfo(); +} + +void LLFloaterTopObjects::updateSelectionInfo() +{ + LLScrollListCtrl* list = getChild("objects_list"); + + if (!list) return; + + LLUUID object_id = list->getCurrentID(); + if (object_id.isNull()) return; + + std::string object_id_string = object_id.asString(); + + getChild("id_editor")->setValue(LLSD(object_id_string)); + LLScrollListItem* sli = list->getFirstSelected(); + llassert(sli); + if (sli) + { + getChild("object_name_editor")->setValue(sli->getColumn(1)->getValue().asString()); + getChild("owner_name_editor")->setValue(sli->getColumn(2)->getValue().asString()); + getChild("parcel_name_editor")->setValue(sli->getColumn(4)->getValue().asString()); + } +} + +// static +void LLFloaterTopObjects::onDoubleClickObjectsList(void* data) +{ + LLFloaterTopObjects* self = (LLFloaterTopObjects*)data; + self->showBeacon(); +} + +// static +void LLFloaterTopObjects::onClickShowBeacon() +{ + showBeacon(); +} + +void LLFloaterTopObjects::returnObjects(bool all) +{ + LLMessageSystem *msg = gMessageSystem; + + LLViewerRegion* region = gAgent.getRegion(); + if (!region) return; + + LLCtrlListInterface *list = getChild("objects_list")->getListInterface(); + if (!list || list->getItemCount() == 0) return; + + uuid_vec_t::iterator id_itor; + + bool start_message = true; + + for (id_itor = mObjectListIDs.begin(); id_itor != mObjectListIDs.end(); ++id_itor) + { + LLUUID task_id = *id_itor; + if (!all && !list->isSelected(task_id)) + { + // Selected only + continue; + } + if (start_message) + { + msg->newMessageFast(_PREHASH_ParcelReturnObjects); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ParcelData); + msg->addS32Fast(_PREHASH_LocalID, -1); // Whole region + msg->addS32Fast(_PREHASH_ReturnType, RT_NONE); + start_message = false; + } + + msg->nextBlockFast(_PREHASH_TaskIDs); + msg->addUUIDFast(_PREHASH_TaskID, task_id); + + if (msg->isSendFullFast(_PREHASH_TaskIDs)) + { + msg->sendReliable(region->getHost()); + start_message = true; + } + } + + if (!start_message) + { + msg->sendReliable(region->getHost()); + } +} + +//static +bool LLFloaterTopObjects::callbackReturnAll(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + LLFloaterTopObjects* instance = LLFloaterReg::getTypedInstance("top_objects"); + if(!instance) return false; + if (option == 0) + { + instance->returnObjects(true); + } + return false; +} + +void LLFloaterTopObjects::onReturnAll() +{ + LLNotificationsUtil::add("ReturnAllTopObjects", LLSD(), LLSD(), &callbackReturnAll); +} + + +void LLFloaterTopObjects::onReturnSelected() +{ + returnObjects(false); +} + + +void LLFloaterTopObjects::clearList() +{ + LLCtrlListInterface *list = childGetListInterface("objects_list"); + + if (list) + { + list->operateOnAll(LLCtrlListInterface::OP_DELETE); + } + + mObjectListData.clear(); + mObjectListIDs.clear(); + mtotalScore = 0.f; + + onSelectionChanged(); +} + + +void LLFloaterTopObjects::onRefresh() +{ + U32 mode = STAT_REPORT_TOP_SCRIPTS; + U32 flags = 0; + std::string filter = ""; + + mode = mCurrentMode; + flags = mFlags; + filter = mFilter; + clearList(); + + LLMessageSystem *msg = gMessageSystem; + msg->newMessageFast(_PREHASH_LandStatRequest); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); + msg->nextBlockFast(_PREHASH_RequestData); + msg->addU32Fast(_PREHASH_ReportType, mode); + msg->addU32Fast(_PREHASH_RequestFlags, flags); + msg->addStringFast(_PREHASH_Filter, filter); + msg->addS32Fast(_PREHASH_ParcelLocalID, 0); + + LLFloaterRegionInfo* region_info_floater = LLFloaterReg::getTypedInstance("region_info"); + if(region_info_floater) + { + region_info_floater->disableTopButtons(); + } + disableRefreshBtn(); + + msg->sendReliable(gAgent.getRegionHost()); + + mFilter.clear(); + mFlags = 0; +} + +void LLFloaterTopObjects::disableRefreshBtn() +{ + getChildView("refresh_btn")->setEnabled(false); +} + +void LLFloaterTopObjects::onGetByObjectName() +{ + mFlags = STAT_FILTER_BY_OBJECT; + mFilter = getChild("object_name_editor")->getValue().asString(); + onRefresh(); +} + +void LLFloaterTopObjects::onGetByOwnerName() +{ + mFlags = STAT_FILTER_BY_OWNER; + mFilter = getChild("owner_name_editor")->getValue().asString(); + onRefresh(); +} + + +void LLFloaterTopObjects::onGetByParcelName() +{ + mFlags = STAT_FILTER_BY_PARCEL_NAME; + mFilter = getChild("parcel_name_editor")->getValue().asString(); + onRefresh(); +} + + +void LLFloaterTopObjects::showBeacon() +{ + LLScrollListCtrl* list = getChild("objects_list"); + if (!list) return; + + LLScrollListItem* first_selected = list->getFirstSelected(); + if (!first_selected) return; + + std::string name = first_selected->getColumn(1)->getValue().asString(); + std::string pos_string = first_selected->getColumn(3)->getValue().asString(); + + F32 x, y, z; + S32 matched = sscanf(pos_string.c_str(), "<%g,%g,%g>", &x, &y, &z); + if (matched != 3) return; + + LLVector3 pos_agent(x, y, z); + LLVector3d pos_global = gAgent.getPosGlobalFromAgent(pos_agent); + std::string tooltip(""); + LLTracker::trackLocation(pos_global, name, tooltip, LLTracker::LOCATION_ITEM); +} + +void LLFloaterTopObjects::teleportToSelectedObject() +{ + std::vector selected_items = mObjectsScrollList->getAllSelected(); + if (selected_items.size() == 1) + { + LLScrollListItem* first_selected = selected_items.front(); + + LLVector3d teleport_location; + LLViewerObject *viewer_object = gObjectList.findObject(first_selected->getUUID()); + if (viewer_object == NULL) + { + // If we cannot find the object in the viewer list, teleport to the last reported position + std::string pos_string = first_selected->getColumn(3)->getValue().asString(); + + F32 x, y, z; + S32 matched = sscanf(pos_string.c_str(), "<%g,%g,%g>", &x, &y, &z); + if (matched != 3) return; + + LLVector3 pos_agent(x, y, z); + teleport_location = gAgent.getPosGlobalFromAgent(pos_agent); + } + else + { + // If we can find the object in the viewer list, teleport to the known current position + teleport_location = viewer_object->getPositionGlobal(); + } + gAgent.teleportViaLocationLookAt(teleport_location); + } +} + +void LLFloaterTopObjects::onSelectionChanged() +{ + getChildView("teleport_btn")->setEnabled(mObjectsScrollList->getNumSelected() == 1); +} diff --git a/indra/newview/llfloatertopobjects.h b/indra/newview/llfloatertopobjects.h index ec73fa894d..5ceace6d01 100644 --- a/indra/newview/llfloatertopobjects.h +++ b/indra/newview/llfloatertopobjects.h @@ -1,115 +1,115 @@ -/** - * @file llfloatertopobjects.h - * @brief Shows top colliders or top scripts - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERTOPOBJECTS_H -#define LL_LLFLOATERTOPOBJECTS_H - -#include "llfloater.h" - -class LLUICtrl; -class LLScrollListCtrl; - -// Bits for simulator performance query flags -enum LAND_STAT_FLAGS -{ - STAT_FILTER_BY_PARCEL = 0x00000001, - STAT_FILTER_BY_OWNER = 0x00000002, - STAT_FILTER_BY_OBJECT = 0x00000004, - STAT_FILTER_BY_PARCEL_NAME = 0x00000008, - STAT_REQUEST_LAST_ENTRY = 0x80000000, -}; - -enum LAND_STAT_REPORT_TYPE -{ - STAT_REPORT_TOP_SCRIPTS = 0, - STAT_REPORT_TOP_COLLIDERS -}; - -class LLFloaterTopObjects : public LLFloater -{ - friend class LLFloaterReg; -public: - // Opens the floater on screen. -// static void show(); - - // Opens the floater if it's not on-screen. - // Juggles the UI based on method = "scripts" or "colliders" - static void handle_land_reply(LLMessageSystem* msg, void** data); - void handleReply(LLMessageSystem* msg, void** data); - - void clearList(); - void updateSelectionInfo(); - virtual bool postBuild(); - - void onRefresh(); - - static void setMode(U32 mode); - void disableRefreshBtn(); - -private: - LLFloaterTopObjects(const LLSD& key); - ~LLFloaterTopObjects(); - - void initColumns(LLCtrlListInterface *list); - - void onCommitObjectsList(); - void onSelectionChanged(); - static void onDoubleClickObjectsList(void* data); - void onClickShowBeacon(); - - void returnObjects(bool all); - - void onReturnAll(); - void onReturnSelected(); - - static bool callbackReturnAll(const LLSD& notification, const LLSD& response); - - void onGetByOwnerName(); - void onGetByObjectName(); - void onGetByParcelName(); - - void showBeacon(); - void teleportToSelectedObject(); - -private: - std::string mMethod; - - LLSD mObjectListData; - uuid_vec_t mObjectListIDs; - - U32 mCurrentMode; - U32 mFlags; - std::string mFilter; - - bool mInitialized; - - F32 mtotalScore; - - static LLFloaterTopObjects* sInstance; - LLScrollListCtrl* mObjectsScrollList; -}; - -#endif +/** + * @file llfloatertopobjects.h + * @brief Shows top colliders or top scripts + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERTOPOBJECTS_H +#define LL_LLFLOATERTOPOBJECTS_H + +#include "llfloater.h" + +class LLUICtrl; +class LLScrollListCtrl; + +// Bits for simulator performance query flags +enum LAND_STAT_FLAGS +{ + STAT_FILTER_BY_PARCEL = 0x00000001, + STAT_FILTER_BY_OWNER = 0x00000002, + STAT_FILTER_BY_OBJECT = 0x00000004, + STAT_FILTER_BY_PARCEL_NAME = 0x00000008, + STAT_REQUEST_LAST_ENTRY = 0x80000000, +}; + +enum LAND_STAT_REPORT_TYPE +{ + STAT_REPORT_TOP_SCRIPTS = 0, + STAT_REPORT_TOP_COLLIDERS +}; + +class LLFloaterTopObjects : public LLFloater +{ + friend class LLFloaterReg; +public: + // Opens the floater on screen. +// static void show(); + + // Opens the floater if it's not on-screen. + // Juggles the UI based on method = "scripts" or "colliders" + static void handle_land_reply(LLMessageSystem* msg, void** data); + void handleReply(LLMessageSystem* msg, void** data); + + void clearList(); + void updateSelectionInfo(); + virtual bool postBuild(); + + void onRefresh(); + + static void setMode(U32 mode); + void disableRefreshBtn(); + +private: + LLFloaterTopObjects(const LLSD& key); + ~LLFloaterTopObjects(); + + void initColumns(LLCtrlListInterface *list); + + void onCommitObjectsList(); + void onSelectionChanged(); + static void onDoubleClickObjectsList(void* data); + void onClickShowBeacon(); + + void returnObjects(bool all); + + void onReturnAll(); + void onReturnSelected(); + + static bool callbackReturnAll(const LLSD& notification, const LLSD& response); + + void onGetByOwnerName(); + void onGetByObjectName(); + void onGetByParcelName(); + + void showBeacon(); + void teleportToSelectedObject(); + +private: + std::string mMethod; + + LLSD mObjectListData; + uuid_vec_t mObjectListIDs; + + U32 mCurrentMode; + U32 mFlags; + std::string mFilter; + + bool mInitialized; + + F32 mtotalScore; + + static LLFloaterTopObjects* sInstance; + LLScrollListCtrl* mObjectsScrollList; +}; + +#endif diff --git a/indra/newview/llfloatertos.cpp b/indra/newview/llfloatertos.cpp index 3fa4c8aefd..8eec5b753a 100644 --- a/indra/newview/llfloatertos.cpp +++ b/indra/newview/llfloatertos.cpp @@ -1,281 +1,281 @@ -/** - * @file llfloatertos.cpp - * @brief Terms of Service Agreement dialog - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloatertos.h" - -// viewer includes -#include "llviewerstats.h" -#include "llviewerwindow.h" - -// linden library includes -#include "llbutton.h" -#include "llevents.h" -#include "llnotificationsutil.h" -#include "llradiogroup.h" -#include "lltextbox.h" -#include "llui.h" -#include "lluictrlfactory.h" -#include "llfilesystem.h" -#include "message.h" -#include "llstartup.h" // login_alert_done -#include "llcorehttputil.h" -#include "llfloaterreg.h" - -LLFloaterTOS::LLFloaterTOS(const LLSD& data) -: LLModalDialog( data["message"].asString() ), - mMessage(data["message"].asString()), - mLoadingScreenLoaded(false), - mSiteAlive(false), - mRealNavigateBegun(false), - mReplyPumpName(data["reply_pump"].asString()) -{ -} - -bool LLFloaterTOS::postBuild() -{ - childSetAction("Continue", onContinue, this); - childSetAction("Cancel", onCancel, this); - childSetCommitCallback("agree_chk", updateAgree, this); - - if (hasChild("tos_text")) - { - // this displays the critical message - LLUICtrl *tos_text = getChild("tos_text"); - tos_text->setEnabled(false); - tos_text->setFocus(true); - tos_text->setValue(LLSD(mMessage)); - - return true; - } - - // disable Agree to TOS radio button until the page has fully loaded - updateAgreeEnabled(false); - - // hide the SL text widget if we're displaying TOS with using a browser widget. - LLUICtrl *editor = getChild("tos_text"); - editor->setVisible(false); - - LLMediaCtrl* web_browser = getChild("tos_html"); - if ( web_browser ) - { -// if we are forced to send users to an external site in their system browser -// (e.g.) Linux users because of lack of media support for HTML ToS page -// remove exisiting UI and replace with a link to external page where users can accept ToS -#ifdef EXTERNAL_TOS - LLTextBox* header = getChild("tos_heading"); - if (header) - header->setVisible(false); - - LLTextBox* external_prompt = getChild("external_tos_required"); - if (external_prompt) - external_prompt->setVisible(true); - - web_browser->setVisible(false); -#else - web_browser->addObserver(this); - - // Don't use the start_url parameter for this browser instance -- it may finish loading before we get to add our observer. - // Store the URL separately and navigate here instead. - web_browser->navigateTo( getString( "loading_url" ) ); - LLPluginClassMedia* media_plugin = web_browser->getMediaPlugin(); - if (media_plugin) - { - // All links from tos_html should be opened in external browser - media_plugin->setOverrideClickTarget("_external"); - } -#endif - } - - return true; -} - -void LLFloaterTOS::setSiteIsAlive( bool alive ) -{ -// if we are forced to send users to an external site in their system browser -// (e.g.) Linux users because of lack of media support for HTML ToS page -// force the regular HTML UI to deactivate so alternative is rendered instead. -#ifdef EXTERNAL_TOS - mSiteAlive = false; -#else - - mSiteAlive = alive; - - // only do this for TOS pages - if (hasChild("tos_html")) - { - // if the contents of the site was retrieved - if ( alive ) - { - // navigate to the "real" page - if(!mRealNavigateBegun && mSiteAlive) - { - LLMediaCtrl* web_browser = getChild("tos_html"); - if(web_browser) - { - mRealNavigateBegun = true; - web_browser->navigateTo( getString( "real_url" ) ); - } - } - } - else - { - LL_INFOS("TOS") << "ToS page: ToS page unavailable!" << LL_ENDL; - // normally this is set when navigation to TOS page navigation completes (so you can't accept before TOS loads) - // but if the page is unavailable, we need to do this now - updateAgreeEnabled(true); - LLTextBox* tos_list = getChild("agree_list"); - tos_list->setEnabled(true); - } - } -#endif -} - -LLFloaterTOS::~LLFloaterTOS() -{ -} - -// virtual -void LLFloaterTOS::draw() -{ - // draw children - LLModalDialog::draw(); -} - - -// update status of "Agree" checkbox and text -void LLFloaterTOS::updateAgreeEnabled(bool enabled) -{ - LLCheckBoxCtrl* tos_agreement_agree_cb = getChild("agree_chk"); - tos_agreement_agree_cb->setEnabled(enabled); - - LLTextBox* tos_agreement_agree_text = getChild("agree_list"); - tos_agreement_agree_text->setEnabled(enabled); -} - -// static -void LLFloaterTOS::updateAgree(LLUICtrl*, void* userdata ) -{ - LLFloaterTOS* self = (LLFloaterTOS*) userdata; - bool agree = self->getChild("agree_chk")->getValue().asBoolean(); - self->getChildView("Continue")->setEnabled(agree); -} - -// static -void LLFloaterTOS::onContinue( void* userdata ) -{ - LLFloaterTOS* self = (LLFloaterTOS*) userdata; - LL_INFOS("TOS") << "User agrees with TOS." << LL_ENDL; - - if(self->mReplyPumpName != "") - { - LLEventPumps::instance().obtain(self->mReplyPumpName).post(LLSD(true)); - } - - self->closeFloater(); // destroys this object -} - -// static -void LLFloaterTOS::onCancel( void* userdata ) -{ - LLFloaterTOS* self = (LLFloaterTOS*) userdata; - LL_INFOS("TOS") << "User disagrees with TOS." << LL_ENDL; - LLNotificationsUtil::add("MustAgreeToLogIn", LLSD(), LLSD(), login_alert_done); - - if(self->mReplyPumpName != "") - { - LLEventPumps::instance().obtain(self->mReplyPumpName).post(LLSD(false)); - } - - // reset state for next time we come to TOS - self->mLoadingScreenLoaded = false; - self->mSiteAlive = false; - self->mRealNavigateBegun = false; - - // destroys this object - self->closeFloater(); -} - -//virtual -void LLFloaterTOS::handleMediaEvent(LLPluginClassMedia* /*self*/, EMediaEvent event) -{ - if(event == MEDIA_EVENT_NAVIGATE_COMPLETE) - { - if(!mLoadingScreenLoaded) - { - mLoadingScreenLoaded = true; - std::string url(getString("real_url")); - - LLHandle handle = getHandle(); - - LLCoros::instance().launch("LLFloaterTOS::testSiteIsAliveCoro", - boost::bind(&LLFloaterTOS::testSiteIsAliveCoro, handle, url)); - } - else if(mRealNavigateBegun) - { - LL_INFOS("TOS") << "TOS: NAVIGATE COMPLETE" << LL_ENDL; - // enable Agree to TOS check box now that page has loaded - updateAgreeEnabled(true); - } - } -} - -void LLFloaterTOS::testSiteIsAliveCoro(LLHandle handle, std::string url) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("genericPostCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); - - httpOpts->setWantHeaders(true); - httpOpts->setHeadersOnly(true); - - LL_INFOS("testSiteIsAliveCoro") << "Generic POST for " << url << LL_ENDL; - - LLSD result = httpAdapter->getAndSuspend(httpRequest, url); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (handle.isDead()) - { - LL_WARNS("testSiteIsAliveCoro") << "Dialog canceled before response." << LL_ENDL; - return; - } - - LLFloaterTOS *that = dynamic_cast(handle.get()); - - if (that) - that->setSiteIsAlive(static_cast(status)); - else - { - LL_WARNS("testSiteIsAliveCoro") << "Handle was not a TOS floater." << LL_ENDL; - } -} - - +/** + * @file llfloatertos.cpp + * @brief Terms of Service Agreement dialog + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatertos.h" + +// viewer includes +#include "llviewerstats.h" +#include "llviewerwindow.h" + +// linden library includes +#include "llbutton.h" +#include "llevents.h" +#include "llnotificationsutil.h" +#include "llradiogroup.h" +#include "lltextbox.h" +#include "llui.h" +#include "lluictrlfactory.h" +#include "llfilesystem.h" +#include "message.h" +#include "llstartup.h" // login_alert_done +#include "llcorehttputil.h" +#include "llfloaterreg.h" + +LLFloaterTOS::LLFloaterTOS(const LLSD& data) +: LLModalDialog( data["message"].asString() ), + mMessage(data["message"].asString()), + mLoadingScreenLoaded(false), + mSiteAlive(false), + mRealNavigateBegun(false), + mReplyPumpName(data["reply_pump"].asString()) +{ +} + +bool LLFloaterTOS::postBuild() +{ + childSetAction("Continue", onContinue, this); + childSetAction("Cancel", onCancel, this); + childSetCommitCallback("agree_chk", updateAgree, this); + + if (hasChild("tos_text")) + { + // this displays the critical message + LLUICtrl *tos_text = getChild("tos_text"); + tos_text->setEnabled(false); + tos_text->setFocus(true); + tos_text->setValue(LLSD(mMessage)); + + return true; + } + + // disable Agree to TOS radio button until the page has fully loaded + updateAgreeEnabled(false); + + // hide the SL text widget if we're displaying TOS with using a browser widget. + LLUICtrl *editor = getChild("tos_text"); + editor->setVisible(false); + + LLMediaCtrl* web_browser = getChild("tos_html"); + if ( web_browser ) + { +// if we are forced to send users to an external site in their system browser +// (e.g.) Linux users because of lack of media support for HTML ToS page +// remove exisiting UI and replace with a link to external page where users can accept ToS +#ifdef EXTERNAL_TOS + LLTextBox* header = getChild("tos_heading"); + if (header) + header->setVisible(false); + + LLTextBox* external_prompt = getChild("external_tos_required"); + if (external_prompt) + external_prompt->setVisible(true); + + web_browser->setVisible(false); +#else + web_browser->addObserver(this); + + // Don't use the start_url parameter for this browser instance -- it may finish loading before we get to add our observer. + // Store the URL separately and navigate here instead. + web_browser->navigateTo( getString( "loading_url" ) ); + LLPluginClassMedia* media_plugin = web_browser->getMediaPlugin(); + if (media_plugin) + { + // All links from tos_html should be opened in external browser + media_plugin->setOverrideClickTarget("_external"); + } +#endif + } + + return true; +} + +void LLFloaterTOS::setSiteIsAlive( bool alive ) +{ +// if we are forced to send users to an external site in their system browser +// (e.g.) Linux users because of lack of media support for HTML ToS page +// force the regular HTML UI to deactivate so alternative is rendered instead. +#ifdef EXTERNAL_TOS + mSiteAlive = false; +#else + + mSiteAlive = alive; + + // only do this for TOS pages + if (hasChild("tos_html")) + { + // if the contents of the site was retrieved + if ( alive ) + { + // navigate to the "real" page + if(!mRealNavigateBegun && mSiteAlive) + { + LLMediaCtrl* web_browser = getChild("tos_html"); + if(web_browser) + { + mRealNavigateBegun = true; + web_browser->navigateTo( getString( "real_url" ) ); + } + } + } + else + { + LL_INFOS("TOS") << "ToS page: ToS page unavailable!" << LL_ENDL; + // normally this is set when navigation to TOS page navigation completes (so you can't accept before TOS loads) + // but if the page is unavailable, we need to do this now + updateAgreeEnabled(true); + LLTextBox* tos_list = getChild("agree_list"); + tos_list->setEnabled(true); + } + } +#endif +} + +LLFloaterTOS::~LLFloaterTOS() +{ +} + +// virtual +void LLFloaterTOS::draw() +{ + // draw children + LLModalDialog::draw(); +} + + +// update status of "Agree" checkbox and text +void LLFloaterTOS::updateAgreeEnabled(bool enabled) +{ + LLCheckBoxCtrl* tos_agreement_agree_cb = getChild("agree_chk"); + tos_agreement_agree_cb->setEnabled(enabled); + + LLTextBox* tos_agreement_agree_text = getChild("agree_list"); + tos_agreement_agree_text->setEnabled(enabled); +} + +// static +void LLFloaterTOS::updateAgree(LLUICtrl*, void* userdata ) +{ + LLFloaterTOS* self = (LLFloaterTOS*) userdata; + bool agree = self->getChild("agree_chk")->getValue().asBoolean(); + self->getChildView("Continue")->setEnabled(agree); +} + +// static +void LLFloaterTOS::onContinue( void* userdata ) +{ + LLFloaterTOS* self = (LLFloaterTOS*) userdata; + LL_INFOS("TOS") << "User agrees with TOS." << LL_ENDL; + + if(self->mReplyPumpName != "") + { + LLEventPumps::instance().obtain(self->mReplyPumpName).post(LLSD(true)); + } + + self->closeFloater(); // destroys this object +} + +// static +void LLFloaterTOS::onCancel( void* userdata ) +{ + LLFloaterTOS* self = (LLFloaterTOS*) userdata; + LL_INFOS("TOS") << "User disagrees with TOS." << LL_ENDL; + LLNotificationsUtil::add("MustAgreeToLogIn", LLSD(), LLSD(), login_alert_done); + + if(self->mReplyPumpName != "") + { + LLEventPumps::instance().obtain(self->mReplyPumpName).post(LLSD(false)); + } + + // reset state for next time we come to TOS + self->mLoadingScreenLoaded = false; + self->mSiteAlive = false; + self->mRealNavigateBegun = false; + + // destroys this object + self->closeFloater(); +} + +//virtual +void LLFloaterTOS::handleMediaEvent(LLPluginClassMedia* /*self*/, EMediaEvent event) +{ + if(event == MEDIA_EVENT_NAVIGATE_COMPLETE) + { + if(!mLoadingScreenLoaded) + { + mLoadingScreenLoaded = true; + std::string url(getString("real_url")); + + LLHandle handle = getHandle(); + + LLCoros::instance().launch("LLFloaterTOS::testSiteIsAliveCoro", + boost::bind(&LLFloaterTOS::testSiteIsAliveCoro, handle, url)); + } + else if(mRealNavigateBegun) + { + LL_INFOS("TOS") << "TOS: NAVIGATE COMPLETE" << LL_ENDL; + // enable Agree to TOS check box now that page has loaded + updateAgreeEnabled(true); + } + } +} + +void LLFloaterTOS::testSiteIsAliveCoro(LLHandle handle, std::string url) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("genericPostCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); + + httpOpts->setWantHeaders(true); + httpOpts->setHeadersOnly(true); + + LL_INFOS("testSiteIsAliveCoro") << "Generic POST for " << url << LL_ENDL; + + LLSD result = httpAdapter->getAndSuspend(httpRequest, url); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (handle.isDead()) + { + LL_WARNS("testSiteIsAliveCoro") << "Dialog canceled before response." << LL_ENDL; + return; + } + + LLFloaterTOS *that = dynamic_cast(handle.get()); + + if (that) + that->setSiteIsAlive(static_cast(status)); + else + { + LL_WARNS("testSiteIsAliveCoro") << "Handle was not a TOS floater." << LL_ENDL; + } +} + + diff --git a/indra/newview/llfloatertos.h b/indra/newview/llfloatertos.h index f634a7c265..3bec4da58d 100644 --- a/indra/newview/llfloatertos.h +++ b/indra/newview/llfloatertos.h @@ -1,75 +1,75 @@ -/** - * @file llfloatertos.h - * @brief Terms of Service Agreement dialog - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERTOS_H -#define LL_LLFLOATERTOS_H - -#include "llmodaldialog.h" -#include "llassetstorage.h" -#include "llmediactrl.h" -#include -#include "lleventcoro.h" -#include "llcoros.h" - -class LLButton; -class LLRadioGroup; -class LLTextEditor; -class LLUUID; - -class LLFloaterTOS : - public LLModalDialog, - public LLViewerMediaObserver -{ -public: - LLFloaterTOS(const LLSD& data); - virtual ~LLFloaterTOS(); - - bool postBuild() override; - - void draw() override; - - static void updateAgree( LLUICtrl *, void* userdata ); - static void onContinue( void* userdata ); - static void onCancel( void* userdata ); - - void setSiteIsAlive( bool alive ); - - void updateAgreeEnabled(bool enabled); - - // inherited from LLViewerMediaObserver - void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) override; - -private: - static void testSiteIsAliveCoro(LLHandle handle, std::string url); - - std::string mMessage; - bool mLoadingScreenLoaded; - bool mSiteAlive; - bool mRealNavigateBegun; - std::string mReplyPumpName; -}; - -#endif // LL_LLFLOATERTOS_H +/** + * @file llfloatertos.h + * @brief Terms of Service Agreement dialog + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERTOS_H +#define LL_LLFLOATERTOS_H + +#include "llmodaldialog.h" +#include "llassetstorage.h" +#include "llmediactrl.h" +#include +#include "lleventcoro.h" +#include "llcoros.h" + +class LLButton; +class LLRadioGroup; +class LLTextEditor; +class LLUUID; + +class LLFloaterTOS : + public LLModalDialog, + public LLViewerMediaObserver +{ +public: + LLFloaterTOS(const LLSD& data); + virtual ~LLFloaterTOS(); + + bool postBuild() override; + + void draw() override; + + static void updateAgree( LLUICtrl *, void* userdata ); + static void onContinue( void* userdata ); + static void onCancel( void* userdata ); + + void setSiteIsAlive( bool alive ); + + void updateAgreeEnabled(bool enabled); + + // inherited from LLViewerMediaObserver + void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) override; + +private: + static void testSiteIsAliveCoro(LLHandle handle, std::string url); + + std::string mMessage; + bool mLoadingScreenLoaded; + bool mSiteAlive; + bool mRealNavigateBegun; + std::string mReplyPumpName; +}; + +#endif // LL_LLFLOATERTOS_H diff --git a/indra/newview/llfloatertoybox.cpp b/indra/newview/llfloatertoybox.cpp index 3537d97963..f6257dbd3d 100644 --- a/indra/newview/llfloatertoybox.cpp +++ b/indra/newview/llfloatertoybox.cpp @@ -1,191 +1,191 @@ -/** - * @file llfloatertoybox.cpp - * @brief The toybox for flexibilizing the UI. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloatertoybox.h" - -#include "llbutton.h" -#include "llcommandmanager.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "llpanel.h" -#include "lltoolbar.h" -#include "lltoolbarview.h" -#include "lltrans.h" - -LLFloaterToybox::LLFloaterToybox(const LLSD& key) - : LLFloater(key) - , mToolBar(NULL) -{ - mCommitCallbackRegistrar.add("Toybox.RestoreDefaults", boost::bind(&LLFloaterToybox::onBtnRestoreDefaults, this)); - mCommitCallbackRegistrar.add("Toybox.ClearAll", boost::bind(&LLFloaterToybox::onBtnClearAll, this)); -} - -LLFloaterToybox::~LLFloaterToybox() -{ -} - -bool compare_localized_command_labels(LLCommand * cmd1, LLCommand * cmd2) -{ - std::string lab1 = LLTrans::getString(cmd1->labelRef()); - std::string lab2 = LLTrans::getString(cmd2->labelRef()); - - return (lab1 < lab2); -} - -bool LLFloaterToybox::postBuild() -{ - mToolBar = getChild("toybox_toolbar"); - - mToolBar->setStartDragCallback(boost::bind(LLToolBarView::startDragTool,_1,_2,_3)); - mToolBar->setHandleDragCallback(boost::bind(LLToolBarView::handleDragTool,_1,_2,_3,_4)); - mToolBar->setHandleDropCallback(boost::bind(LLToolBarView::handleDropTool,_1,_2,_3,_4)); - mToolBar->setButtonEnterCallback(boost::bind(&LLFloaterToybox::onToolBarButtonEnter,this,_1)); - - // - // Sort commands by localized labels so they will appear alphabetized in all languages - // - - std::list alphabetized_commands; - - LLCommandManager& cmdMgr = LLCommandManager::instance(); - for (U32 i = 0; i < cmdMgr.commandCount(); i++) - { - LLCommand * command = cmdMgr.getCommand(i); - - if (command->availableInToybox()) - { - alphabetized_commands.push_back(command); - } - } - - alphabetized_commands.sort(compare_localized_command_labels); - - // - // Create Buttons - // - - for (std::list::iterator it = alphabetized_commands.begin(); it != alphabetized_commands.end(); ++it) - { - mToolBar->addCommand((*it)->id()); - } - - return true; -} - -void LLFloaterToybox::draw() -{ - llassert(gToolBarView != NULL); - - const command_id_list_t& command_list = mToolBar->getCommandsList(); - - for (command_id_list_t::const_iterator it = command_list.begin(); it != command_list.end(); ++it) - { - const LLCommandId& id = *it; - - const bool command_not_present = (gToolBarView->hasCommand(id) == LLToolBarEnums::TOOLBAR_NONE); - mToolBar->enableCommand(id, command_not_present); - } - - LLFloater::draw(); -} - -static bool finish_restore_toybox(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - - if (option == 0) - { - LLToolBarView::loadDefaultToolbars(); - } - - return false; -} - -static bool finish_clear_all_toybox(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - - if (option == 0) - { - LLToolBarView::clearAllToolbars(); - } - - return false; -} - -static LLNotificationFunctorRegistration finish_restore_toybox_reg("ConfirmRestoreToybox", finish_restore_toybox); -static LLNotificationFunctorRegistration finish_clear_all_toybox_reg("ConfirmClearAllToybox", finish_clear_all_toybox); - -void LLFloaterToybox::onBtnRestoreDefaults() -{ - LLNotificationsUtil::add("ConfirmRestoreToybox"); -} - -void LLFloaterToybox::onBtnClearAll() -{ - LLNotificationsUtil::add("ConfirmClearAllToybox"); -} - -bool LLFloaterToybox::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - S32 local_x = x - mToolBar->getRect().mLeft; - S32 local_y = y - mToolBar->getRect().mBottom; - return mToolBar->handleDragAndDrop(local_x, local_y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); -} - -void LLFloaterToybox::onToolBarButtonEnter(LLView* button) -{ - std::string suffix = ""; - - LLCommandId commandId(button->getName()); - LLCommand* command = LLCommandManager::instance().getCommand(commandId); - - if (command) - { - S32 command_loc = gToolBarView->hasCommand(commandId); - - switch(command_loc) - { - case LLToolBarEnums::TOOLBAR_BOTTOM: suffix = LLTrans::getString("Toolbar_Bottom_Tooltip"); break; - case LLToolBarEnums::TOOLBAR_LEFT: suffix = LLTrans::getString("Toolbar_Left_Tooltip"); break; - case LLToolBarEnums::TOOLBAR_RIGHT: suffix = LLTrans::getString("Toolbar_Right_Tooltip"); break; - - default: - break; - } - } - - mToolBar->setTooltipButtonSuffix(suffix); -} - - -// eof +/** + * @file llfloatertoybox.cpp + * @brief The toybox for flexibilizing the UI. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatertoybox.h" + +#include "llbutton.h" +#include "llcommandmanager.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "llpanel.h" +#include "lltoolbar.h" +#include "lltoolbarview.h" +#include "lltrans.h" + +LLFloaterToybox::LLFloaterToybox(const LLSD& key) + : LLFloater(key) + , mToolBar(NULL) +{ + mCommitCallbackRegistrar.add("Toybox.RestoreDefaults", boost::bind(&LLFloaterToybox::onBtnRestoreDefaults, this)); + mCommitCallbackRegistrar.add("Toybox.ClearAll", boost::bind(&LLFloaterToybox::onBtnClearAll, this)); +} + +LLFloaterToybox::~LLFloaterToybox() +{ +} + +bool compare_localized_command_labels(LLCommand * cmd1, LLCommand * cmd2) +{ + std::string lab1 = LLTrans::getString(cmd1->labelRef()); + std::string lab2 = LLTrans::getString(cmd2->labelRef()); + + return (lab1 < lab2); +} + +bool LLFloaterToybox::postBuild() +{ + mToolBar = getChild("toybox_toolbar"); + + mToolBar->setStartDragCallback(boost::bind(LLToolBarView::startDragTool,_1,_2,_3)); + mToolBar->setHandleDragCallback(boost::bind(LLToolBarView::handleDragTool,_1,_2,_3,_4)); + mToolBar->setHandleDropCallback(boost::bind(LLToolBarView::handleDropTool,_1,_2,_3,_4)); + mToolBar->setButtonEnterCallback(boost::bind(&LLFloaterToybox::onToolBarButtonEnter,this,_1)); + + // + // Sort commands by localized labels so they will appear alphabetized in all languages + // + + std::list alphabetized_commands; + + LLCommandManager& cmdMgr = LLCommandManager::instance(); + for (U32 i = 0; i < cmdMgr.commandCount(); i++) + { + LLCommand * command = cmdMgr.getCommand(i); + + if (command->availableInToybox()) + { + alphabetized_commands.push_back(command); + } + } + + alphabetized_commands.sort(compare_localized_command_labels); + + // + // Create Buttons + // + + for (std::list::iterator it = alphabetized_commands.begin(); it != alphabetized_commands.end(); ++it) + { + mToolBar->addCommand((*it)->id()); + } + + return true; +} + +void LLFloaterToybox::draw() +{ + llassert(gToolBarView != NULL); + + const command_id_list_t& command_list = mToolBar->getCommandsList(); + + for (command_id_list_t::const_iterator it = command_list.begin(); it != command_list.end(); ++it) + { + const LLCommandId& id = *it; + + const bool command_not_present = (gToolBarView->hasCommand(id) == LLToolBarEnums::TOOLBAR_NONE); + mToolBar->enableCommand(id, command_not_present); + } + + LLFloater::draw(); +} + +static bool finish_restore_toybox(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + + if (option == 0) + { + LLToolBarView::loadDefaultToolbars(); + } + + return false; +} + +static bool finish_clear_all_toybox(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + + if (option == 0) + { + LLToolBarView::clearAllToolbars(); + } + + return false; +} + +static LLNotificationFunctorRegistration finish_restore_toybox_reg("ConfirmRestoreToybox", finish_restore_toybox); +static LLNotificationFunctorRegistration finish_clear_all_toybox_reg("ConfirmClearAllToybox", finish_clear_all_toybox); + +void LLFloaterToybox::onBtnRestoreDefaults() +{ + LLNotificationsUtil::add("ConfirmRestoreToybox"); +} + +void LLFloaterToybox::onBtnClearAll() +{ + LLNotificationsUtil::add("ConfirmClearAllToybox"); +} + +bool LLFloaterToybox::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + S32 local_x = x - mToolBar->getRect().mLeft; + S32 local_y = y - mToolBar->getRect().mBottom; + return mToolBar->handleDragAndDrop(local_x, local_y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); +} + +void LLFloaterToybox::onToolBarButtonEnter(LLView* button) +{ + std::string suffix = ""; + + LLCommandId commandId(button->getName()); + LLCommand* command = LLCommandManager::instance().getCommand(commandId); + + if (command) + { + S32 command_loc = gToolBarView->hasCommand(commandId); + + switch(command_loc) + { + case LLToolBarEnums::TOOLBAR_BOTTOM: suffix = LLTrans::getString("Toolbar_Bottom_Tooltip"); break; + case LLToolBarEnums::TOOLBAR_LEFT: suffix = LLTrans::getString("Toolbar_Left_Tooltip"); break; + case LLToolBarEnums::TOOLBAR_RIGHT: suffix = LLTrans::getString("Toolbar_Right_Tooltip"); break; + + default: + break; + } + } + + mToolBar->setTooltipButtonSuffix(suffix); +} + + +// eof diff --git a/indra/newview/llfloatertoybox.h b/indra/newview/llfloatertoybox.h index 2648a34555..4fe723c8cb 100644 --- a/indra/newview/llfloatertoybox.h +++ b/indra/newview/llfloatertoybox.h @@ -1,62 +1,62 @@ -/** - * @file llfloatertoybox.h - * @brief The toybox for flexibilizing the UI. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERTOYBOX_H -#define LL_LLFLOATERTOYBOX_H - -#include "llfloater.h" - - -class LLButton; -class LLToolBar; - - -class LLFloaterToybox : public LLFloater -{ -public: - LLFloaterToybox(const LLSD& key); - virtual ~LLFloaterToybox(); - - // virtuals - bool postBuild() override; - void draw() override; - bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) override; - -protected: - void onBtnClearAll(); - void onBtnRestoreDefaults(); - - void onToolBarButtonEnter(LLView* button); - -public: - LLToolBar * mToolBar; -}; - -#endif // LL_LLFLOATERTOYBOX_H +/** + * @file llfloatertoybox.h + * @brief The toybox for flexibilizing the UI. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERTOYBOX_H +#define LL_LLFLOATERTOYBOX_H + +#include "llfloater.h" + + +class LLButton; +class LLToolBar; + + +class LLFloaterToybox : public LLFloater +{ +public: + LLFloaterToybox(const LLSD& key); + virtual ~LLFloaterToybox(); + + // virtuals + bool postBuild() override; + void draw() override; + bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) override; + +protected: + void onBtnClearAll(); + void onBtnRestoreDefaults(); + + void onToolBarButtonEnter(LLView* button); + +public: + LLToolBar * mToolBar; +}; + +#endif // LL_LLFLOATERTOYBOX_H diff --git a/indra/newview/llfloatertranslationsettings.cpp b/indra/newview/llfloatertranslationsettings.cpp index 7d8fb847dc..71fd7fbb41 100644 --- a/indra/newview/llfloatertranslationsettings.cpp +++ b/indra/newview/llfloatertranslationsettings.cpp @@ -1,424 +1,424 @@ -/** - * @file llfloatertranslationsettings.cpp - * @brief Machine translation settings for chat - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloatertranslationsettings.h" - -// Viewer includes -#include "llfloaterimnearbychat.h" -#include "lltranslate.h" -#include "llviewercontrol.h" // for gSavedSettings - -// Linden library includes -#include "llbutton.h" -#include "llcheckboxctrl.h" -#include "llcombobox.h" -#include "llfloaterreg.h" -#include "lllineeditor.h" -#include "llnotificationsutil.h" -#include "llradiogroup.h" - -LLFloaterTranslationSettings::LLFloaterTranslationSettings(const LLSD& key) -: LLFloater(key) -, mMachineTranslationCB(NULL) -, mAzureKeyVerified(false) -, mGoogleKeyVerified(false) -, mDeepLKeyVerified(false) -{ -} - -// virtual -bool LLFloaterTranslationSettings::postBuild() -{ - mMachineTranslationCB = getChild("translate_chat_checkbox"); - mLanguageCombo = getChild("translate_language_combo"); - mTranslationServiceRadioGroup = getChild("translation_service_rg"); - mAzureAPIEndpointEditor = getChild("azure_api_endpoint_combo"); - mAzureAPIKeyEditor = getChild("azure_api_key"); - mAzureAPIRegionEditor = getChild("azure_api_region"); - mGoogleAPIKeyEditor = getChild("google_api_key"); - mDeepLAPIDomainCombo = getChild("deepl_api_domain_combo"); - mDeepLAPIKeyEditor = getChild("deepl_api_key"); - mAzureVerifyBtn = getChild("verify_azure_api_key_btn"); - mGoogleVerifyBtn = getChild("verify_google_api_key_btn"); - mDeepLVerifyBtn = getChild("verify_deepl_api_key_btn"); - mOKBtn = getChild("ok_btn"); - - mMachineTranslationCB->setCommitCallback(boost::bind(&LLFloaterTranslationSettings::updateControlsEnabledState, this)); - mTranslationServiceRadioGroup->setCommitCallback(boost::bind(&LLFloaterTranslationSettings::updateControlsEnabledState, this)); - mOKBtn->setClickedCallback(boost::bind(&LLFloaterTranslationSettings::onBtnOK, this)); - getChild("cancel_btn")->setClickedCallback(boost::bind(&LLFloater::closeFloater, this, false)); - mAzureVerifyBtn->setClickedCallback(boost::bind(&LLFloaterTranslationSettings::onBtnAzureVerify, this)); - mGoogleVerifyBtn->setClickedCallback(boost::bind(&LLFloaterTranslationSettings::onBtnGoogleVerify, this)); - mDeepLVerifyBtn->setClickedCallback(boost::bind(&LLFloaterTranslationSettings::onBtnDeepLVerify, this)); - - mAzureAPIKeyEditor->setFocusReceivedCallback(boost::bind(&LLFloaterTranslationSettings::onEditorFocused, this, _1)); - mAzureAPIKeyEditor->setKeystrokeCallback(boost::bind(&LLFloaterTranslationSettings::onAzureKeyEdited, this), NULL); - mAzureAPIRegionEditor->setFocusReceivedCallback(boost::bind(&LLFloaterTranslationSettings::onEditorFocused, this, _1)); - mAzureAPIRegionEditor->setKeystrokeCallback(boost::bind(&LLFloaterTranslationSettings::onAzureKeyEdited, this), NULL); - - mAzureAPIEndpointEditor->setFocusLostCallback([this](LLFocusableElement*) - { - setAzureVerified(false, false, 0); - }); - mAzureAPIEndpointEditor->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) - { - setAzureVerified(false, false, 0); - }); - - mGoogleAPIKeyEditor->setFocusReceivedCallback(boost::bind(&LLFloaterTranslationSettings::onEditorFocused, this, _1)); - mGoogleAPIKeyEditor->setKeystrokeCallback(boost::bind(&LLFloaterTranslationSettings::onGoogleKeyEdited, this), NULL); - - mDeepLAPIKeyEditor->setFocusReceivedCallback(boost::bind(&LLFloaterTranslationSettings::onEditorFocused, this, _1)); - mDeepLAPIKeyEditor->setKeystrokeCallback(boost::bind(&LLFloaterTranslationSettings::onDeepLKeyEdited, this), NULL); - - mDeepLAPIDomainCombo->setFocusLostCallback([this](LLFocusableElement*) - { - setDeepLVerified(false, false, 0); - }); - mDeepLAPIDomainCombo->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) - { - setDeepLVerified(false, false, 0); - }); - - center(); - return true; -} - -// virtual -void LLFloaterTranslationSettings::onOpen(const LLSD& key) -{ - mMachineTranslationCB->setValue(gSavedSettings.getBOOL("TranslateChat")); - mLanguageCombo->setSelectedByValue(gSavedSettings.getString("TranslateLanguage"), true); - mTranslationServiceRadioGroup->setSelectedByValue(gSavedSettings.getString("TranslationService"), true); - - LLSD azure_key = gSavedSettings.getLLSD("AzureTranslateAPIKey"); - if (azure_key.isMap() && !azure_key["id"].asString().empty()) - { - mAzureAPIKeyEditor->setText(azure_key["id"].asString()); - mAzureAPIKeyEditor->setTentative(false); - if (azure_key.has("region") && !azure_key["region"].asString().empty()) - { - mAzureAPIRegionEditor->setText(azure_key["region"].asString()); - mAzureAPIRegionEditor->setTentative(false); - } - else - { - mAzureAPIRegionEditor->setTentative(true); - } - mAzureAPIEndpointEditor->setValue(azure_key["endpoint"]); - verifyKey(LLTranslate::SERVICE_AZURE, azure_key, false); - } - else - { - mAzureAPIKeyEditor->setTentative(true); - mAzureAPIRegionEditor->setTentative(true); - mAzureKeyVerified = false; - } - - std::string google_key = gSavedSettings.getString("GoogleTranslateAPIKey"); - if (!google_key.empty()) - { - mGoogleAPIKeyEditor->setText(google_key); - mGoogleAPIKeyEditor->setTentative(false); - verifyKey(LLTranslate::SERVICE_GOOGLE, google_key, false); - } - else - { - mGoogleAPIKeyEditor->setTentative(true); - mGoogleKeyVerified = false; - } - - LLSD deepl_key = gSavedSettings.getLLSD("DeepLTranslateAPIKey"); - if (deepl_key.isMap() && !deepl_key["id"].asString().empty()) - { - mDeepLAPIKeyEditor->setText(deepl_key["id"].asString()); - mDeepLAPIKeyEditor->setTentative(false); - mDeepLAPIDomainCombo->setValue(deepl_key["domain"]); - verifyKey(LLTranslate::SERVICE_DEEPL, deepl_key, false); - } - else - { - mDeepLAPIKeyEditor->setTentative(true); - mDeepLKeyVerified = false; - } - - updateControlsEnabledState(); -} - -void LLFloaterTranslationSettings::setAzureVerified(bool ok, bool alert, S32 status) -{ - if (alert) - { - showAlert(ok ? "azure_api_key_verified" : "azure_api_key_not_verified", status); - } - - mAzureKeyVerified = ok; - updateControlsEnabledState(); -} - -void LLFloaterTranslationSettings::setGoogleVerified(bool ok, bool alert, S32 status) -{ - if (alert) - { - showAlert(ok ? "google_api_key_verified" : "google_api_key_not_verified", status); - } - - mGoogleKeyVerified = ok; - updateControlsEnabledState(); -} - -void LLFloaterTranslationSettings::setDeepLVerified(bool ok, bool alert, S32 status) -{ - if (alert) - { - showAlert(ok ? "deepl_api_key_verified" : "deepl_api_key_not_verified", status); - } - - mDeepLKeyVerified = ok; - updateControlsEnabledState(); -} - -std::string LLFloaterTranslationSettings::getSelectedService() const -{ - return mTranslationServiceRadioGroup->getSelectedValue().asString(); -} - -LLSD LLFloaterTranslationSettings::getEnteredAzureKey() const -{ - LLSD key; - if (!mAzureAPIKeyEditor->getTentative()) - { - key["endpoint"] = mAzureAPIEndpointEditor->getValue(); - key["id"] = mAzureAPIKeyEditor->getText(); - if (!mAzureAPIRegionEditor->getTentative()) - { - key["region"] = mAzureAPIRegionEditor->getText(); - } - } - return key; -} - -std::string LLFloaterTranslationSettings::getEnteredGoogleKey() const -{ - return mGoogleAPIKeyEditor->getTentative() ? LLStringUtil::null : mGoogleAPIKeyEditor->getText(); -} - -LLSD LLFloaterTranslationSettings::getEnteredDeepLKey() const -{ - LLSD key; - if (!mDeepLAPIKeyEditor->getTentative()) - { - key["domain"] = mDeepLAPIDomainCombo->getValue(); - key["id"] = mDeepLAPIKeyEditor->getText(); - } - return key; -} - -void LLFloaterTranslationSettings::showAlert(const std::string& msg_name, S32 status) const -{ - LLStringUtil::format_map_t string_args; - // For now just show an http error code, whole 'reason' string might be added later - string_args["[STATUS]"] = llformat("%d", status); - std::string message = getString(msg_name, string_args); - - LLSD args; - args["MESSAGE"] = message; - LLNotificationsUtil::add("GenericAlert", args); -} - -void LLFloaterTranslationSettings::updateControlsEnabledState() -{ - // Enable/disable controls based on the checkbox value. - bool on = mMachineTranslationCB->getValue().asBoolean(); - std::string service = getSelectedService(); - bool azure_selected = service == "azure"; - bool google_selected = service == "google"; - bool deepl_selected = service == "deepl"; - - mTranslationServiceRadioGroup->setEnabled(on); - mLanguageCombo->setEnabled(on); - - // MS Azure - getChild("azure_api_endoint_label")->setEnabled(on); - mAzureAPIEndpointEditor->setEnabled(on && azure_selected); - getChild("azure_api_key_label")->setEnabled(on); - mAzureAPIKeyEditor->setEnabled(on && azure_selected); - getChild("azure_api_region_label")->setEnabled(on); - mAzureAPIRegionEditor->setEnabled(on && azure_selected); - - mAzureVerifyBtn->setEnabled(on && azure_selected && - !mAzureKeyVerified && getEnteredAzureKey().isMap()); - - // Google - getChild("google_api_key_label")->setEnabled(on); - mGoogleAPIKeyEditor->setEnabled(on && google_selected); - - mGoogleVerifyBtn->setEnabled(on && google_selected && - !mGoogleKeyVerified && !getEnteredGoogleKey().empty()); - - // DeepL - getChild("deepl_api_domain_label")->setEnabled(on); - mDeepLAPIDomainCombo->setEnabled(on && deepl_selected); - getChild("deepl_api_key_label")->setEnabled(on); - mDeepLAPIKeyEditor->setEnabled(on && deepl_selected); - - mDeepLVerifyBtn->setEnabled(on && deepl_selected && - !mDeepLKeyVerified && getEnteredDeepLKey().isMap()); - - bool service_verified = - (azure_selected && mAzureKeyVerified) - || (google_selected && mGoogleKeyVerified) - || (deepl_selected && mDeepLKeyVerified); - gSavedPerAccountSettings.setBOOL("TranslatingEnabled", service_verified); - - mOKBtn->setEnabled(!on || service_verified); -} - -/*static*/ -void LLFloaterTranslationSettings::setVerificationStatus(int service, bool ok, bool alert, S32 status) -{ - LLFloaterTranslationSettings* floater = - LLFloaterReg::getTypedInstance("prefs_translation"); - - if (!floater) - { - LL_WARNS() << "Cannot find translation settings floater" << LL_ENDL; - return; - } - - switch (service) - { - case LLTranslate::SERVICE_AZURE: - floater->setAzureVerified(ok, alert, status); - break; - case LLTranslate::SERVICE_GOOGLE: - floater->setGoogleVerified(ok, alert, status); - break; - case LLTranslate::SERVICE_DEEPL: - floater->setDeepLVerified(ok, alert, status); - break; - } -} - - -void LLFloaterTranslationSettings::verifyKey(int service, const LLSD& key, bool alert) -{ - LLTranslate::verifyKey(static_cast(service), key, - boost::bind(&LLFloaterTranslationSettings::setVerificationStatus, _1, _2, alert, _3)); -} - -void LLFloaterTranslationSettings::onEditorFocused(LLFocusableElement* control) -{ - LLLineEditor* editor = dynamic_cast(control); - if (editor && editor->hasTabStop()) // if enabled. getEnabled() doesn't work - { - if (editor->getTentative()) - { - editor->setText(LLStringUtil::null); - editor->setTentative(false); - } - } -} - -void LLFloaterTranslationSettings::onAzureKeyEdited() -{ - if (mAzureAPIKeyEditor->isDirty() - || mAzureAPIRegionEditor->isDirty()) - { - // todo: verify mAzureAPIEndpointEditor url - setAzureVerified(false, false, 0); - } -} - -void LLFloaterTranslationSettings::onGoogleKeyEdited() -{ - if (mGoogleAPIKeyEditor->isDirty()) - { - setGoogleVerified(false, false, 0); - } -} - -void LLFloaterTranslationSettings::onDeepLKeyEdited() -{ - if (mDeepLAPIKeyEditor->isDirty()) - { - setDeepLVerified(false, false, 0); - } -} - -void LLFloaterTranslationSettings::onBtnAzureVerify() -{ - LLSD key = getEnteredAzureKey(); - if (key.isMap()) - { - verifyKey(LLTranslate::SERVICE_AZURE, key); - } -} - -void LLFloaterTranslationSettings::onBtnGoogleVerify() -{ - std::string key = getEnteredGoogleKey(); - if (!key.empty()) - { - verifyKey(LLTranslate::SERVICE_GOOGLE, LLSD(key)); - } -} - -void LLFloaterTranslationSettings::onBtnDeepLVerify() -{ - LLSD key = getEnteredDeepLKey(); - if (key.isMap()) - { - verifyKey(LLTranslate::SERVICE_DEEPL, key); - } -} - -void LLFloaterTranslationSettings::onClose(bool app_quitting) -{ - std::string service = gSavedSettings.getString("TranslationService"); - bool azure_selected = service == "azure"; - bool google_selected = service == "google"; - bool deepl_selected = service == "deepl"; - - bool service_verified = - (azure_selected && mAzureKeyVerified) - || (google_selected && mGoogleKeyVerified) - || (deepl_selected && mDeepLKeyVerified); - gSavedPerAccountSettings.setBOOL("TranslatingEnabled", service_verified); -} -void LLFloaterTranslationSettings::onBtnOK() -{ - gSavedSettings.setBOOL("TranslateChat", mMachineTranslationCB->getValue().asBoolean()); - gSavedSettings.setString("TranslateLanguage", mLanguageCombo->getSelectedValue().asString()); - gSavedSettings.setString("TranslationService", getSelectedService()); - gSavedSettings.setLLSD("AzureTranslateAPIKey", getEnteredAzureKey()); - gSavedSettings.setString("GoogleTranslateAPIKey", getEnteredGoogleKey()); - gSavedSettings.setLLSD("DeepLTranslateAPIKey", getEnteredDeepLKey()); - - closeFloater(false); -} +/** + * @file llfloatertranslationsettings.cpp + * @brief Machine translation settings for chat + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatertranslationsettings.h" + +// Viewer includes +#include "llfloaterimnearbychat.h" +#include "lltranslate.h" +#include "llviewercontrol.h" // for gSavedSettings + +// Linden library includes +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "llfloaterreg.h" +#include "lllineeditor.h" +#include "llnotificationsutil.h" +#include "llradiogroup.h" + +LLFloaterTranslationSettings::LLFloaterTranslationSettings(const LLSD& key) +: LLFloater(key) +, mMachineTranslationCB(NULL) +, mAzureKeyVerified(false) +, mGoogleKeyVerified(false) +, mDeepLKeyVerified(false) +{ +} + +// virtual +bool LLFloaterTranslationSettings::postBuild() +{ + mMachineTranslationCB = getChild("translate_chat_checkbox"); + mLanguageCombo = getChild("translate_language_combo"); + mTranslationServiceRadioGroup = getChild("translation_service_rg"); + mAzureAPIEndpointEditor = getChild("azure_api_endpoint_combo"); + mAzureAPIKeyEditor = getChild("azure_api_key"); + mAzureAPIRegionEditor = getChild("azure_api_region"); + mGoogleAPIKeyEditor = getChild("google_api_key"); + mDeepLAPIDomainCombo = getChild("deepl_api_domain_combo"); + mDeepLAPIKeyEditor = getChild("deepl_api_key"); + mAzureVerifyBtn = getChild("verify_azure_api_key_btn"); + mGoogleVerifyBtn = getChild("verify_google_api_key_btn"); + mDeepLVerifyBtn = getChild("verify_deepl_api_key_btn"); + mOKBtn = getChild("ok_btn"); + + mMachineTranslationCB->setCommitCallback(boost::bind(&LLFloaterTranslationSettings::updateControlsEnabledState, this)); + mTranslationServiceRadioGroup->setCommitCallback(boost::bind(&LLFloaterTranslationSettings::updateControlsEnabledState, this)); + mOKBtn->setClickedCallback(boost::bind(&LLFloaterTranslationSettings::onBtnOK, this)); + getChild("cancel_btn")->setClickedCallback(boost::bind(&LLFloater::closeFloater, this, false)); + mAzureVerifyBtn->setClickedCallback(boost::bind(&LLFloaterTranslationSettings::onBtnAzureVerify, this)); + mGoogleVerifyBtn->setClickedCallback(boost::bind(&LLFloaterTranslationSettings::onBtnGoogleVerify, this)); + mDeepLVerifyBtn->setClickedCallback(boost::bind(&LLFloaterTranslationSettings::onBtnDeepLVerify, this)); + + mAzureAPIKeyEditor->setFocusReceivedCallback(boost::bind(&LLFloaterTranslationSettings::onEditorFocused, this, _1)); + mAzureAPIKeyEditor->setKeystrokeCallback(boost::bind(&LLFloaterTranslationSettings::onAzureKeyEdited, this), NULL); + mAzureAPIRegionEditor->setFocusReceivedCallback(boost::bind(&LLFloaterTranslationSettings::onEditorFocused, this, _1)); + mAzureAPIRegionEditor->setKeystrokeCallback(boost::bind(&LLFloaterTranslationSettings::onAzureKeyEdited, this), NULL); + + mAzureAPIEndpointEditor->setFocusLostCallback([this](LLFocusableElement*) + { + setAzureVerified(false, false, 0); + }); + mAzureAPIEndpointEditor->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) + { + setAzureVerified(false, false, 0); + }); + + mGoogleAPIKeyEditor->setFocusReceivedCallback(boost::bind(&LLFloaterTranslationSettings::onEditorFocused, this, _1)); + mGoogleAPIKeyEditor->setKeystrokeCallback(boost::bind(&LLFloaterTranslationSettings::onGoogleKeyEdited, this), NULL); + + mDeepLAPIKeyEditor->setFocusReceivedCallback(boost::bind(&LLFloaterTranslationSettings::onEditorFocused, this, _1)); + mDeepLAPIKeyEditor->setKeystrokeCallback(boost::bind(&LLFloaterTranslationSettings::onDeepLKeyEdited, this), NULL); + + mDeepLAPIDomainCombo->setFocusLostCallback([this](LLFocusableElement*) + { + setDeepLVerified(false, false, 0); + }); + mDeepLAPIDomainCombo->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) + { + setDeepLVerified(false, false, 0); + }); + + center(); + return true; +} + +// virtual +void LLFloaterTranslationSettings::onOpen(const LLSD& key) +{ + mMachineTranslationCB->setValue(gSavedSettings.getBOOL("TranslateChat")); + mLanguageCombo->setSelectedByValue(gSavedSettings.getString("TranslateLanguage"), true); + mTranslationServiceRadioGroup->setSelectedByValue(gSavedSettings.getString("TranslationService"), true); + + LLSD azure_key = gSavedSettings.getLLSD("AzureTranslateAPIKey"); + if (azure_key.isMap() && !azure_key["id"].asString().empty()) + { + mAzureAPIKeyEditor->setText(azure_key["id"].asString()); + mAzureAPIKeyEditor->setTentative(false); + if (azure_key.has("region") && !azure_key["region"].asString().empty()) + { + mAzureAPIRegionEditor->setText(azure_key["region"].asString()); + mAzureAPIRegionEditor->setTentative(false); + } + else + { + mAzureAPIRegionEditor->setTentative(true); + } + mAzureAPIEndpointEditor->setValue(azure_key["endpoint"]); + verifyKey(LLTranslate::SERVICE_AZURE, azure_key, false); + } + else + { + mAzureAPIKeyEditor->setTentative(true); + mAzureAPIRegionEditor->setTentative(true); + mAzureKeyVerified = false; + } + + std::string google_key = gSavedSettings.getString("GoogleTranslateAPIKey"); + if (!google_key.empty()) + { + mGoogleAPIKeyEditor->setText(google_key); + mGoogleAPIKeyEditor->setTentative(false); + verifyKey(LLTranslate::SERVICE_GOOGLE, google_key, false); + } + else + { + mGoogleAPIKeyEditor->setTentative(true); + mGoogleKeyVerified = false; + } + + LLSD deepl_key = gSavedSettings.getLLSD("DeepLTranslateAPIKey"); + if (deepl_key.isMap() && !deepl_key["id"].asString().empty()) + { + mDeepLAPIKeyEditor->setText(deepl_key["id"].asString()); + mDeepLAPIKeyEditor->setTentative(false); + mDeepLAPIDomainCombo->setValue(deepl_key["domain"]); + verifyKey(LLTranslate::SERVICE_DEEPL, deepl_key, false); + } + else + { + mDeepLAPIKeyEditor->setTentative(true); + mDeepLKeyVerified = false; + } + + updateControlsEnabledState(); +} + +void LLFloaterTranslationSettings::setAzureVerified(bool ok, bool alert, S32 status) +{ + if (alert) + { + showAlert(ok ? "azure_api_key_verified" : "azure_api_key_not_verified", status); + } + + mAzureKeyVerified = ok; + updateControlsEnabledState(); +} + +void LLFloaterTranslationSettings::setGoogleVerified(bool ok, bool alert, S32 status) +{ + if (alert) + { + showAlert(ok ? "google_api_key_verified" : "google_api_key_not_verified", status); + } + + mGoogleKeyVerified = ok; + updateControlsEnabledState(); +} + +void LLFloaterTranslationSettings::setDeepLVerified(bool ok, bool alert, S32 status) +{ + if (alert) + { + showAlert(ok ? "deepl_api_key_verified" : "deepl_api_key_not_verified", status); + } + + mDeepLKeyVerified = ok; + updateControlsEnabledState(); +} + +std::string LLFloaterTranslationSettings::getSelectedService() const +{ + return mTranslationServiceRadioGroup->getSelectedValue().asString(); +} + +LLSD LLFloaterTranslationSettings::getEnteredAzureKey() const +{ + LLSD key; + if (!mAzureAPIKeyEditor->getTentative()) + { + key["endpoint"] = mAzureAPIEndpointEditor->getValue(); + key["id"] = mAzureAPIKeyEditor->getText(); + if (!mAzureAPIRegionEditor->getTentative()) + { + key["region"] = mAzureAPIRegionEditor->getText(); + } + } + return key; +} + +std::string LLFloaterTranslationSettings::getEnteredGoogleKey() const +{ + return mGoogleAPIKeyEditor->getTentative() ? LLStringUtil::null : mGoogleAPIKeyEditor->getText(); +} + +LLSD LLFloaterTranslationSettings::getEnteredDeepLKey() const +{ + LLSD key; + if (!mDeepLAPIKeyEditor->getTentative()) + { + key["domain"] = mDeepLAPIDomainCombo->getValue(); + key["id"] = mDeepLAPIKeyEditor->getText(); + } + return key; +} + +void LLFloaterTranslationSettings::showAlert(const std::string& msg_name, S32 status) const +{ + LLStringUtil::format_map_t string_args; + // For now just show an http error code, whole 'reason' string might be added later + string_args["[STATUS]"] = llformat("%d", status); + std::string message = getString(msg_name, string_args); + + LLSD args; + args["MESSAGE"] = message; + LLNotificationsUtil::add("GenericAlert", args); +} + +void LLFloaterTranslationSettings::updateControlsEnabledState() +{ + // Enable/disable controls based on the checkbox value. + bool on = mMachineTranslationCB->getValue().asBoolean(); + std::string service = getSelectedService(); + bool azure_selected = service == "azure"; + bool google_selected = service == "google"; + bool deepl_selected = service == "deepl"; + + mTranslationServiceRadioGroup->setEnabled(on); + mLanguageCombo->setEnabled(on); + + // MS Azure + getChild("azure_api_endoint_label")->setEnabled(on); + mAzureAPIEndpointEditor->setEnabled(on && azure_selected); + getChild("azure_api_key_label")->setEnabled(on); + mAzureAPIKeyEditor->setEnabled(on && azure_selected); + getChild("azure_api_region_label")->setEnabled(on); + mAzureAPIRegionEditor->setEnabled(on && azure_selected); + + mAzureVerifyBtn->setEnabled(on && azure_selected && + !mAzureKeyVerified && getEnteredAzureKey().isMap()); + + // Google + getChild("google_api_key_label")->setEnabled(on); + mGoogleAPIKeyEditor->setEnabled(on && google_selected); + + mGoogleVerifyBtn->setEnabled(on && google_selected && + !mGoogleKeyVerified && !getEnteredGoogleKey().empty()); + + // DeepL + getChild("deepl_api_domain_label")->setEnabled(on); + mDeepLAPIDomainCombo->setEnabled(on && deepl_selected); + getChild("deepl_api_key_label")->setEnabled(on); + mDeepLAPIKeyEditor->setEnabled(on && deepl_selected); + + mDeepLVerifyBtn->setEnabled(on && deepl_selected && + !mDeepLKeyVerified && getEnteredDeepLKey().isMap()); + + bool service_verified = + (azure_selected && mAzureKeyVerified) + || (google_selected && mGoogleKeyVerified) + || (deepl_selected && mDeepLKeyVerified); + gSavedPerAccountSettings.setBOOL("TranslatingEnabled", service_verified); + + mOKBtn->setEnabled(!on || service_verified); +} + +/*static*/ +void LLFloaterTranslationSettings::setVerificationStatus(int service, bool ok, bool alert, S32 status) +{ + LLFloaterTranslationSettings* floater = + LLFloaterReg::getTypedInstance("prefs_translation"); + + if (!floater) + { + LL_WARNS() << "Cannot find translation settings floater" << LL_ENDL; + return; + } + + switch (service) + { + case LLTranslate::SERVICE_AZURE: + floater->setAzureVerified(ok, alert, status); + break; + case LLTranslate::SERVICE_GOOGLE: + floater->setGoogleVerified(ok, alert, status); + break; + case LLTranslate::SERVICE_DEEPL: + floater->setDeepLVerified(ok, alert, status); + break; + } +} + + +void LLFloaterTranslationSettings::verifyKey(int service, const LLSD& key, bool alert) +{ + LLTranslate::verifyKey(static_cast(service), key, + boost::bind(&LLFloaterTranslationSettings::setVerificationStatus, _1, _2, alert, _3)); +} + +void LLFloaterTranslationSettings::onEditorFocused(LLFocusableElement* control) +{ + LLLineEditor* editor = dynamic_cast(control); + if (editor && editor->hasTabStop()) // if enabled. getEnabled() doesn't work + { + if (editor->getTentative()) + { + editor->setText(LLStringUtil::null); + editor->setTentative(false); + } + } +} + +void LLFloaterTranslationSettings::onAzureKeyEdited() +{ + if (mAzureAPIKeyEditor->isDirty() + || mAzureAPIRegionEditor->isDirty()) + { + // todo: verify mAzureAPIEndpointEditor url + setAzureVerified(false, false, 0); + } +} + +void LLFloaterTranslationSettings::onGoogleKeyEdited() +{ + if (mGoogleAPIKeyEditor->isDirty()) + { + setGoogleVerified(false, false, 0); + } +} + +void LLFloaterTranslationSettings::onDeepLKeyEdited() +{ + if (mDeepLAPIKeyEditor->isDirty()) + { + setDeepLVerified(false, false, 0); + } +} + +void LLFloaterTranslationSettings::onBtnAzureVerify() +{ + LLSD key = getEnteredAzureKey(); + if (key.isMap()) + { + verifyKey(LLTranslate::SERVICE_AZURE, key); + } +} + +void LLFloaterTranslationSettings::onBtnGoogleVerify() +{ + std::string key = getEnteredGoogleKey(); + if (!key.empty()) + { + verifyKey(LLTranslate::SERVICE_GOOGLE, LLSD(key)); + } +} + +void LLFloaterTranslationSettings::onBtnDeepLVerify() +{ + LLSD key = getEnteredDeepLKey(); + if (key.isMap()) + { + verifyKey(LLTranslate::SERVICE_DEEPL, key); + } +} + +void LLFloaterTranslationSettings::onClose(bool app_quitting) +{ + std::string service = gSavedSettings.getString("TranslationService"); + bool azure_selected = service == "azure"; + bool google_selected = service == "google"; + bool deepl_selected = service == "deepl"; + + bool service_verified = + (azure_selected && mAzureKeyVerified) + || (google_selected && mGoogleKeyVerified) + || (deepl_selected && mDeepLKeyVerified); + gSavedPerAccountSettings.setBOOL("TranslatingEnabled", service_verified); +} +void LLFloaterTranslationSettings::onBtnOK() +{ + gSavedSettings.setBOOL("TranslateChat", mMachineTranslationCB->getValue().asBoolean()); + gSavedSettings.setString("TranslateLanguage", mLanguageCombo->getSelectedValue().asString()); + gSavedSettings.setString("TranslationService", getSelectedService()); + gSavedSettings.setLLSD("AzureTranslateAPIKey", getEnteredAzureKey()); + gSavedSettings.setString("GoogleTranslateAPIKey", getEnteredGoogleKey()); + gSavedSettings.setLLSD("DeepLTranslateAPIKey", getEnteredDeepLKey()); + + closeFloater(false); +} diff --git a/indra/newview/llfloatertranslationsettings.h b/indra/newview/llfloatertranslationsettings.h index 36ac333c53..3995059900 100644 --- a/indra/newview/llfloatertranslationsettings.h +++ b/indra/newview/llfloatertranslationsettings.h @@ -1,89 +1,89 @@ -/** - * @file llfloatertranslationsettings.h - * @brief Machine translation settings for chat - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERTRANSLATIONSETTINGS_H -#define LL_LLFLOATERTRANSLATIONSETTINGS_H - -#include "llfloater.h" - -class LLButton; -class LLCheckBoxCtrl; -class LLComboBox; -class LLLineEditor; -class LLRadioGroup; - -class LLFloaterTranslationSettings : public LLFloater -{ -public: - LLFloaterTranslationSettings(const LLSD& key); - bool postBuild() override; - void onOpen(const LLSD& key) override; - - void setAzureVerified(bool ok, bool alert, S32 status); - void setGoogleVerified(bool ok, bool alert, S32 status); - void setDeepLVerified(bool ok, bool alert, S32 status); - void onClose(bool app_quitting) override; - -private: - std::string getSelectedService() const; - LLSD getEnteredAzureKey() const; - std::string getEnteredGoogleKey() const; - LLSD getEnteredDeepLKey() const; - void showAlert(const std::string& msg_name, S32 status) const; - void updateControlsEnabledState(); - void verifyKey(int service, const LLSD& key, bool alert = true); - - void onEditorFocused(LLFocusableElement* control); - void onAzureKeyEdited(); - void onGoogleKeyEdited(); - void onDeepLKeyEdited(); - void onBtnAzureVerify(); - void onBtnGoogleVerify(); - void onBtnDeepLVerify(); - void onBtnOK(); - - static void setVerificationStatus(int service, bool alert, bool ok, S32 status); - - LLCheckBoxCtrl* mMachineTranslationCB; - LLComboBox* mLanguageCombo; - LLComboBox* mAzureAPIEndpointEditor; - LLLineEditor* mAzureAPIKeyEditor; - LLLineEditor* mAzureAPIRegionEditor; - LLLineEditor* mGoogleAPIKeyEditor; - LLComboBox* mDeepLAPIDomainCombo; - LLLineEditor* mDeepLAPIKeyEditor; - LLRadioGroup* mTranslationServiceRadioGroup; - LLButton* mAzureVerifyBtn; - LLButton* mGoogleVerifyBtn; - LLButton* mDeepLVerifyBtn; - LLButton* mOKBtn; - - bool mAzureKeyVerified; - bool mGoogleKeyVerified; - bool mDeepLKeyVerified; -}; - -#endif // LL_LLFLOATERTRANSLATIONSETTINGS_H +/** + * @file llfloatertranslationsettings.h + * @brief Machine translation settings for chat + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERTRANSLATIONSETTINGS_H +#define LL_LLFLOATERTRANSLATIONSETTINGS_H + +#include "llfloater.h" + +class LLButton; +class LLCheckBoxCtrl; +class LLComboBox; +class LLLineEditor; +class LLRadioGroup; + +class LLFloaterTranslationSettings : public LLFloater +{ +public: + LLFloaterTranslationSettings(const LLSD& key); + bool postBuild() override; + void onOpen(const LLSD& key) override; + + void setAzureVerified(bool ok, bool alert, S32 status); + void setGoogleVerified(bool ok, bool alert, S32 status); + void setDeepLVerified(bool ok, bool alert, S32 status); + void onClose(bool app_quitting) override; + +private: + std::string getSelectedService() const; + LLSD getEnteredAzureKey() const; + std::string getEnteredGoogleKey() const; + LLSD getEnteredDeepLKey() const; + void showAlert(const std::string& msg_name, S32 status) const; + void updateControlsEnabledState(); + void verifyKey(int service, const LLSD& key, bool alert = true); + + void onEditorFocused(LLFocusableElement* control); + void onAzureKeyEdited(); + void onGoogleKeyEdited(); + void onDeepLKeyEdited(); + void onBtnAzureVerify(); + void onBtnGoogleVerify(); + void onBtnDeepLVerify(); + void onBtnOK(); + + static void setVerificationStatus(int service, bool alert, bool ok, S32 status); + + LLCheckBoxCtrl* mMachineTranslationCB; + LLComboBox* mLanguageCombo; + LLComboBox* mAzureAPIEndpointEditor; + LLLineEditor* mAzureAPIKeyEditor; + LLLineEditor* mAzureAPIRegionEditor; + LLLineEditor* mGoogleAPIKeyEditor; + LLComboBox* mDeepLAPIDomainCombo; + LLLineEditor* mDeepLAPIKeyEditor; + LLRadioGroup* mTranslationServiceRadioGroup; + LLButton* mAzureVerifyBtn; + LLButton* mGoogleVerifyBtn; + LLButton* mDeepLVerifyBtn; + LLButton* mOKBtn; + + bool mAzureKeyVerified; + bool mGoogleKeyVerified; + bool mDeepLKeyVerified; +}; + +#endif // LL_LLFLOATERTRANSLATIONSETTINGS_H diff --git a/indra/newview/llfloateruipreview.cpp b/indra/newview/llfloateruipreview.cpp index ba0d5fc2c6..6f526e1905 100644 --- a/indra/newview/llfloateruipreview.cpp +++ b/indra/newview/llfloateruipreview.cpp @@ -1,1717 +1,1717 @@ -/** - * @file llfloateruipreview.cpp - * @brief Tool for previewing and editing floaters, plus localization tool integration - * - * $LicenseInfo:firstyear=2008&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -// Tool for previewing floaters and panels for localization and UI design purposes. -// See: https://wiki.lindenlab.com/wiki/GUI_Preview_And_Localization_Tools -// See: https://jira.lindenlab.com/browse/DEV-16869 - -// *TODO: Translate error messgaes using notifications/alerts.xml - -#include "llviewerprecompiledheaders.h" // Precompiled headers - -#include "llfloateruipreview.h" // Own header - -// Internal utility -#include "lldiriterator.h" -#include "lleventtimer.h" -#include "llexternaleditor.h" -#include "llrender.h" -#include "llsdutil.h" -#include "llxmltree.h" -#include "llviewerwindow.h" - -// XUI -#include "lluictrlfactory.h" -#include "llcombobox.h" -#include "llnotificationsutil.h" -#include "llresizebar.h" -#include "llscrolllistitem.h" -#include "llscrolllistctrl.h" -#include "llfilepicker.h" -#include "lldraghandle.h" -#include "lllayoutstack.h" -#include "lltooltip.h" -#include "llviewermenu.h" -#include "llrngwriter.h" -#include "llfloater.h" // superclass -#include "llfloaterreg.h" -#include "llscrollcontainer.h" // scroll container for overlapping elements -#include "lllivefile.h" // live file poll/stat/reload -#include "llviewermenufile.h" // LLFilePickerReplyThread - -// Boost (for linux/unix command-line execv) -#include -#include - -// External utility -#include -#include -#include - -#if LL_DARWIN -#include -#endif - -// Static initialization -static const S32 PRIMARY_FLOATER = 1; -static const S32 SECONDARY_FLOATER = 2; - -class LLOverlapPanel; -static LLDefaultChildRegistry::Register register_overlap_panel("overlap_panel"); - -static std::string get_xui_dir() -{ - std::string delim = gDirUtilp->getDirDelimiter(); - return gDirUtilp->getSkinBaseDir() + delim + "default" + delim + "xui" + delim; -} - -// Forward declarations to avoid header dependencies -class LLColor; -class LLScrollListCtrl; -class LLComboBox; -class LLButton; -class LLLineEditor; -class LLXmlTreeNode; -class LLFloaterUIPreview; -class LLFadeEventTimer; - -class LLLocalizationResetForcer; -class LLGUIPreviewLiveFile; -class LLFadeEventTimer; -class LLPreviewedFloater; - -// Implementation of custom overlapping element display panel -class LLOverlapPanel : public LLPanel -{ -public: - struct Params : public LLInitParam::Block - { - Params() {} - }; - LLOverlapPanel(Params p = Params()) : LLPanel(p), - mSpacing(10), - // mClickedElement(NULL), - mLastClickedElement(NULL) - { - mOriginalWidth = getRect().getWidth(); - mOriginalHeight = getRect().getHeight(); - } - virtual void draw(); - - typedef std::map > OverlapMap; - OverlapMap mOverlapMap; // map, of XUI element to a list of XUI elements it overlaps - - // LLView *mClickedElement; - LLView *mLastClickedElement; - int mOriginalWidth, mOriginalHeight, mSpacing; -}; - - -class LLFloaterUIPreview : public LLFloater -{ -public: - // Setup - LLFloaterUIPreview(const LLSD& key); - virtual ~LLFloaterUIPreview(); - - std::string getLocStr(S32 ID); // fetches the localization string based on what is selected in the drop-down menu - void displayFloater(bool click, S32 ID); // needs to be public so live file can call it when it finds an update - - /*virtual*/ bool postBuild(); - /*virtual*/ void onClose(bool app_quitting); - - void refreshList(); // refresh list (empty it out and fill it up from scratch) - void addFloaterEntry(const std::string& path); // add a single file's entry to the list of floaters - - static bool containerType(LLView* viewp); // check if the element is a container type and tree traverses need to look at its children - -public: - LLPreviewedFloater* mDisplayedFloater; // the floater which is currently being displayed - LLPreviewedFloater* mDisplayedFloater_2; // the floater which is currently being displayed - LLGUIPreviewLiveFile* mLiveFile; // live file for checking for updates to the currently-displayed XML file - LLOverlapPanel* mOverlapPanel; // custom overlapping elements panel - // bool mHighlightingDiffs; // bool for whether localization diffs are being highlighted or not - bool mHighlightingOverlaps; // bool for whether overlapping elements are being highlighted - - // typedef std::map,std::list > > DiffMap; // this version copies the lists etc., and thus is bad memory-wise - typedef std::list StringList; - typedef std::shared_ptr StringListPtr; - typedef std::map > DiffMap; - DiffMap mDiffsMap; // map, of filename to pair of list of changed element paths and list of errors - -private: - LLExternalEditor mExternalEditor; - - // XUI elements for this floater - LLScrollListCtrl* mFileList; // scroll list control for file list - LLLineEditor* mEditorPathTextBox; // text field for path to editor executable - LLLineEditor* mEditorArgsTextBox; // text field for arguments to editor executable - LLLineEditor* mDiffPathTextBox; // text field for path to diff file - LLButton* mDisplayFloaterBtn; // button to display primary floater - LLButton* mDisplayFloaterBtn_2; // button to display secondary floater - LLButton* mEditFloaterBtn; // button to edit floater - LLButton* mExecutableBrowseButton; // button to browse for executable - LLButton* mCloseOtherButton; // button to close primary displayed floater - LLButton* mCloseOtherButton_2; // button to close secondary displayed floater - LLButton* mDiffBrowseButton; // button to browse for diff file - LLButton* mToggleHighlightButton; // button to toggle highlight of files/elements with diffs - LLButton* mToggleOverlapButton; // button to togle overlap panel/highlighting - LLComboBox* mLanguageSelection; // combo box for primary language selection - LLComboBox* mLanguageSelection_2; // combo box for secondary language selection - S32 mLastDisplayedX, mLastDisplayedY; // stored position of last floater so the new one opens up in the same place - std::string mDelim; // the OS-specific delimiter character (/ or \) (*TODO: this shouldn't be needed, right?) - - std::string mSavedEditorPath; // stored editor path so closing this floater doesn't reset it - std::string mSavedEditorArgs; // stored editor args so closing this floater doesn't reset it - std::string mSavedDiffPath; // stored diff file path so closing this floater doesn't reset it - - // Internal functionality - static void popupAndPrintWarning(const std::string& warning); // pop up a warning - std::string getLocalizedDirectory(); // build and return the path to the XUI directory for the currently-selected localization - void scanDiffFile(LLXmlTreeNode* file_node); // scan a given XML node for diff entries and highlight them in its associated file - void highlightChangedElements(); // look up the list of elements to highlight and highlight them in the current floater - void highlightChangedFiles(); // look up the list of changed files to highlight and highlight them in the scroll list - void findOverlapsInChildren(LLView* parent); // fill the map below with element overlap information - static bool overlapIgnorable(LLView* viewp); // check it the element can be ignored for overlap/localization purposes - - // check if two elements overlap using their rectangles - // used instead of llrect functions because by adding a few pixels of leeway I can cut down drastically on the number of overlaps - bool elementOverlap(LLView* view1, LLView* view2); - - // Button/drop-down action listeners (self explanatory) - void onClickDisplayFloater(S32 id); - void onClickSaveFloater(S32 id); - void onClickSaveAll(S32 id); - void onClickEditFloater(); - void onClickBrowseForEditor(); - void getExecutablePath(const std::vector& filenames); - void onClickBrowseForDiffs(); - void getDiffsFilePath(const std::vector& filenames); - void onClickToggleDiffHighlighting(); - void onClickToggleOverlapping(); - void onClickCloseDisplayedFloater(S32 id); - void onLanguageComboSelect(LLUICtrl* ctrl); - void onClickExportSchema(); - void onClickShowRectangles(const LLSD& data); -}; - -//---------------------------------------------------------------------------- -// Local class declarations -// Reset object to ensure that when we change the current language setting for preview purposes, -// it automatically is reset. Constructed on the stack at the start of the method; the reset -// occurs as it falls out of scope at the end of the method. See llfloateruipreview.cpp for usage. -class LLLocalizationResetForcer -{ -public: - LLLocalizationResetForcer(LLFloaterUIPreview* floater, S32 ID); - virtual ~LLLocalizationResetForcer(); - -private: - std::string mSavedLocalization; // the localization before we change it -}; - -// Implementation of live file -// When a floater is being previewed, any saved changes to its corresponding -// file cause the previewed floater to be reloaded -class LLGUIPreviewLiveFile : public LLLiveFile -{ -public: - LLGUIPreviewLiveFile(std::string path, std::string name, LLFloaterUIPreview* parent); - virtual ~LLGUIPreviewLiveFile(); - LLFloaterUIPreview* mParent; - LLFadeEventTimer* mFadeTimer; // timer for fade-to-yellow-and-back effect to warn that file has been reloaded - bool mFirstFade; // setting this avoids showing the fade reload warning on first load - std::string mFileName; -protected: - bool loadFile(); -}; - -// Implementation of graphical fade in/out (on timer) for when XUI files are updated -class LLFadeEventTimer : public LLEventTimer -{ -public: - LLFadeEventTimer(F32 refresh, LLGUIPreviewLiveFile* parent); - bool tick(); - LLGUIPreviewLiveFile* mParent; -private: - bool mFadingOut; // fades in then out; this is toggled in between - LLColor4 mOriginalColor; // original color; color is reset to this after fade is coimplete -}; - -// Implementation of previewed floater -// Used to override draw and mouse handler -class LLPreviewedFloater : public LLFloater -{ -public: - LLPreviewedFloater(LLFloaterUIPreview* floater, const Params& params) - : LLFloater(LLSD(), params), - mFloaterUIPreview(floater) - { - } - - virtual void draw(); - bool handleRightMouseDown(S32 x, S32 y, MASK mask); - bool handleToolTip(S32 x, S32 y, MASK mask); - bool selectElement(LLView* parent, int x, int y, int depth); // select element to display its overlappers - - LLFloaterUIPreview* mFloaterUIPreview; - - // draw widget outlines - static bool sShowRectangles; -}; - -bool LLPreviewedFloater::sShowRectangles = false; - -//---------------------------------------------------------------------------- - -// Localization reset forcer -- ensures that when localization is temporarily changed for previewed floater, it is reset -// Changes are made here -LLLocalizationResetForcer::LLLocalizationResetForcer(LLFloaterUIPreview* floater, S32 ID) -{ - mSavedLocalization = LLUI::getInstance()->mSettingGroups["config"]->getString("Language"); // save current localization setting - LLUI::getInstance()->mSettingGroups["config"]->setString("Language", floater->getLocStr(ID));// hack language to be the one we want to preview floaters in - // forcibly reset XUI paths with this new language - gDirUtilp->setSkinFolder(gDirUtilp->getSkinFolder(), floater->getLocStr(ID)); -} - -// Actually reset in destructor -// Changes are reversed here -LLLocalizationResetForcer::~LLLocalizationResetForcer() -{ - LLUI::getInstance()->mSettingGroups["config"]->setString("Language", mSavedLocalization); // reset language to what it was before we changed it - // forcibly reset XUI paths with this new language - gDirUtilp->setSkinFolder(gDirUtilp->getSkinFolder(), mSavedLocalization); -} - -// Live file constructor -// Needs full path for LLLiveFile but needs just file name for this code, hence the reduntant arguments; easier than separating later -LLGUIPreviewLiveFile::LLGUIPreviewLiveFile(std::string path, std::string name, LLFloaterUIPreview* parent) - : mFileName(name), - mParent(parent), - mFirstFade(true), - mFadeTimer(NULL), - LLLiveFile(path, 1.0) -{} - -LLGUIPreviewLiveFile::~LLGUIPreviewLiveFile() -{ - mParent->mLiveFile = NULL; - if(mFadeTimer) - { - mFadeTimer->mParent = NULL; - // deletes itself; see lltimer.cpp - } -} - -// Live file load -bool LLGUIPreviewLiveFile::loadFile() -{ - mParent->displayFloater(false,1); // redisplay the floater - if(mFirstFade) // only fade if it wasn't just clicked on; can't use "clicked" bool below because of an oddity with setting LLLiveFile initial state - { - mFirstFade = false; - } - else - { - if(mFadeTimer) - { - mFadeTimer->mParent = NULL; - } - mFadeTimer = new LLFadeEventTimer(0.05f,this); - } - return true; -} - -// Initialize fade event timer -LLFadeEventTimer::LLFadeEventTimer(F32 refresh, LLGUIPreviewLiveFile* parent) - : mParent(parent), - mFadingOut(true), - LLEventTimer(refresh) -{ - mOriginalColor = mParent->mParent->mDisplayedFloater->getBackgroundColor(); -} - -// Single tick of fade event timer: increment the color -bool LLFadeEventTimer::tick() -{ - float diff = 0.04f; - if(true == mFadingOut) // set fade for in/out color direction - { - diff = -diff; - } - - if(NULL == mParent) // no more need to tick, so suicide - { - return true; - } - - // Set up colors - LLColor4 bg_color = mParent->mParent->mDisplayedFloater->getBackgroundColor(); - LLSD colors = bg_color.getValue(); - LLSD colors_old = colors; - - // Tick colors - colors[0] = colors[0].asReal() - diff; if(colors[0].asReal() < mOriginalColor.getValue()[0].asReal()) { colors[0] = colors_old[0]; } - colors[1] = colors[1].asReal() - diff; if(colors[1].asReal() < mOriginalColor.getValue()[1].asReal()) { colors[1] = colors_old[1]; } - colors[2] = colors[2].asReal() + diff; if(colors[2].asReal() > mOriginalColor.getValue()[2].asReal()) { colors[2] = colors_old[2]; } - - // Clamp and set colors - bg_color.setValue(colors); - bg_color.clamp(); // make sure we didn't exceed [0,1] - mParent->mParent->mDisplayedFloater->setBackgroundColor(bg_color); - - if(bg_color[2] <= 0.0f) // end of fade out, start fading in - { - mFadingOut = false; - } - - return false; -} - -// Constructor -LLFloaterUIPreview::LLFloaterUIPreview(const LLSD& key) - : LLFloater(key), - mDisplayedFloater(NULL), - mDisplayedFloater_2(NULL), - mLiveFile(NULL), - // sHighlightingDiffs(false), - mHighlightingOverlaps(false), - mLastDisplayedX(0), - mLastDisplayedY(0) -{ -} - -// Destructor -LLFloaterUIPreview::~LLFloaterUIPreview() -{ - // spawned floaters are deleted automatically, so we don't need to delete them here - - // save contents of textfields so it can be restored later if the floater is created again this session - mSavedEditorPath = mEditorPathTextBox->getText(); - mSavedEditorArgs = mEditorArgsTextBox->getText(); - mSavedDiffPath = mDiffPathTextBox->getText(); - - // delete live file if it exists - if(mLiveFile) - { - delete mLiveFile; - mLiveFile = NULL; - } -} - -// Perform post-build setup (defined in superclass) -bool LLFloaterUIPreview::postBuild() -{ - LLPanel* main_panel_tmp = getChild("main_panel"); // get a pointer to the main panel in order to... - mFileList = main_panel_tmp->getChild("name_list"); // save pointer to file list - // Double-click opens the floater, for convenience - mFileList->setDoubleClickCallback(boost::bind(&LLFloaterUIPreview::onClickDisplayFloater, this, PRIMARY_FLOATER)); - - setDefaultBtn("display_floater"); - // get pointers to buttons and link to callbacks - mLanguageSelection = main_panel_tmp->getChild("language_select_combo"); - mLanguageSelection->setCommitCallback(boost::bind(&LLFloaterUIPreview::onLanguageComboSelect, this, mLanguageSelection)); - mLanguageSelection_2 = main_panel_tmp->getChild("language_select_combo_2"); - mLanguageSelection_2->setCommitCallback(boost::bind(&LLFloaterUIPreview::onLanguageComboSelect, this, mLanguageSelection)); - LLPanel* editor_panel_tmp = main_panel_tmp->getChild("editor_panel"); - mDisplayFloaterBtn = main_panel_tmp->getChild("display_floater"); - mDisplayFloaterBtn->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickDisplayFloater, this, PRIMARY_FLOATER)); - mDisplayFloaterBtn_2 = main_panel_tmp->getChild("display_floater_2"); - mDisplayFloaterBtn_2->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickDisplayFloater, this, SECONDARY_FLOATER)); - mToggleOverlapButton = main_panel_tmp->getChild("toggle_overlap_panel"); - mToggleOverlapButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickToggleOverlapping, this)); - mCloseOtherButton = main_panel_tmp->getChild("close_displayed_floater"); - mCloseOtherButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickCloseDisplayedFloater, this, PRIMARY_FLOATER)); - mCloseOtherButton_2 = main_panel_tmp->getChild("close_displayed_floater_2"); - mCloseOtherButton_2->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickCloseDisplayedFloater, this, SECONDARY_FLOATER)); - mEditFloaterBtn = main_panel_tmp->getChild("edit_floater"); - mEditFloaterBtn->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickEditFloater, this)); - mExecutableBrowseButton = editor_panel_tmp->getChild("browse_for_executable"); - LLPanel* vlt_panel_tmp = main_panel_tmp->getChild("vlt_panel"); - mExecutableBrowseButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickBrowseForEditor, this)); - mDiffBrowseButton = vlt_panel_tmp->getChild("browse_for_vlt_diffs"); - mDiffBrowseButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickBrowseForDiffs, this)); - mToggleHighlightButton = vlt_panel_tmp->getChild("toggle_vlt_diff_highlight"); - mToggleHighlightButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickToggleDiffHighlighting, this)); - main_panel_tmp->getChild("save_floater")->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickSaveFloater, this, PRIMARY_FLOATER)); - main_panel_tmp->getChild("save_all_floaters")->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickSaveAll, this, PRIMARY_FLOATER)); - - getChild("export_schema")->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickExportSchema, this)); - getChild("show_rectangles")->setCommitCallback( - boost::bind(&LLFloaterUIPreview::onClickShowRectangles, this, _2)); - - // get pointers to text fields - mEditorPathTextBox = editor_panel_tmp->getChild("executable_path_field"); - mEditorArgsTextBox = editor_panel_tmp->getChild("executable_args_field"); - mDiffPathTextBox = vlt_panel_tmp->getChild("vlt_diff_path_field"); - - // *HACK: restored saved editor path and args to textfields - mEditorPathTextBox->setText(mSavedEditorPath); - mEditorArgsTextBox->setText(mSavedEditorArgs); - mDiffPathTextBox->setText(mSavedDiffPath); - - // Set up overlap panel - mOverlapPanel = getChild("overlap_panel"); - - getChildView("overlap_scroll")->setVisible( mHighlightingOverlaps); - - mDelim = gDirUtilp->getDirDelimiter(); // initialize delimiter to dir sep slash - - // refresh list of available languages (EN will still be default) - bool found = true; - bool found_en_us = false; - std::string language_directory; - std::string xui_dir = get_xui_dir(); // directory containing localizations -- don't forget trailing delim - mLanguageSelection->removeall(); // clear out anything temporarily in list from XML - - LLDirIterator iter(xui_dir, "*"); - while(found) // for every directory - { - if((found = iter.next(language_directory))) // get next directory - { - std::string full_path = gDirUtilp->add(xui_dir, language_directory); - if(LLFile::isfile(full_path.c_str())) // if it's not a directory, skip it - { - continue; - } - - if(strncmp("template",language_directory.c_str(),8) && -1 == language_directory.find(".")) // if it's not the template directory or a hidden directory - { - if(!strncmp("en",language_directory.c_str(),5)) // remember if we've seen en, so we can make it default - { - found_en_us = true; - } - else - { - mLanguageSelection->add(std::string(language_directory)); // add it to the language selection dropdown menu - mLanguageSelection_2->add(std::string(language_directory)); - } - } - } - } - if(found_en_us) - { - mLanguageSelection->add(std::string("en"),ADD_TOP); // make en first item if we found it - mLanguageSelection_2->add(std::string("en"),ADD_TOP); - } - else - { - std::string warning = std::string("No EN localization found; check your XUI directories!"); - popupAndPrintWarning(warning); - } - mLanguageSelection->selectFirstItem(); // select the first item - mLanguageSelection_2->selectFirstItem(); - - refreshList(); // refresh the list of available floaters - - return true; -} - -// Callback for language combo box selection: refresh current floater when you change languages -void LLFloaterUIPreview::onLanguageComboSelect(LLUICtrl* ctrl) -{ - LLComboBox* caller = dynamic_cast(ctrl); - if (!caller) - return; - if(caller->getName() == std::string("language_select_combo")) - { - if(mDisplayedFloater) - { - onClickCloseDisplayedFloater(PRIMARY_FLOATER); - displayFloater(true,1); - } - } - else - { - if(mDisplayedFloater_2) - { - onClickCloseDisplayedFloater(PRIMARY_FLOATER); - displayFloater(true,2); // *TODO: make take an arg - } - } - -} - -void LLFloaterUIPreview::onClickExportSchema() -{ - //NOTE: schema generation not complete - //gViewerWindow->setCursor(UI_CURSOR_WAIT); - //std::string template_path = gDirUtilp->getExpandedFilename(LL_PATH_DEFAULT_SKIN, "xui", "schema"); - - //typedef LLWidgetTypeRegistry::Registrar::registry_map_t::const_iterator registry_it; - //registry_it end_it = LLWidgetTypeRegistry::defaultRegistrar().endItems(); - //for(registry_it it = LLWidgetTypeRegistry::defaultRegistrar().beginItems(); - // it != end_it; - // ++it) - //{ - // std::string widget_name = it->first; - // const LLInitParam::BaseBlock& block = - // (*LLDefaultParamBlockRegistry::instance().getValue(*LLWidgetTypeRegistry::instance().getValue(widget_name)))(); - // LLXMLNodePtr root_nodep = new LLXMLNode(); - // LLRNGWriter().writeRNG(widget_name, root_nodep, block, "http://www.lindenlab.com/xui"); - - // std::string file_name(template_path + gDirUtilp->getDirDelimiter() + widget_name + ".rng"); - - // LLFILE* rng_file = LLFile::fopen(file_name.c_str(), "w"); - // { - // LLXMLNode::writeHeaderToFile(rng_file); - // const bool use_type_decorations = false; - // root_nodep->writeToFile(rng_file, std::string(), use_type_decorations); - // } - // fclose(rng_file); - //} - //gViewerWindow->setCursor(UI_CURSOR_ARROW); -} - -void LLFloaterUIPreview::onClickShowRectangles(const LLSD& data) -{ - LLPreviewedFloater::sShowRectangles = data.asBoolean(); -} - -// Close click handler -- delete my displayed floater if it exists -void LLFloaterUIPreview::onClose(bool app_quitting) -{ - if(!app_quitting && mDisplayedFloater) - { - onClickCloseDisplayedFloater(PRIMARY_FLOATER); - onClickCloseDisplayedFloater(SECONDARY_FLOATER); - delete mDisplayedFloater; - mDisplayedFloater = NULL; - delete mDisplayedFloater_2; - mDisplayedFloater_2 = NULL; - } -} - -// Error handling (to avoid code repetition) -// *TODO: this is currently unlocalized. Add to alerts/notifications.xml, someday, maybe. -void LLFloaterUIPreview::popupAndPrintWarning(const std::string& warning) -{ - LL_WARNS() << warning << LL_ENDL; - LLSD args; - args["MESSAGE"] = warning; - LLNotificationsUtil::add("GenericAlert", args); -} - -// Get localization string from drop-down menu -std::string LLFloaterUIPreview::getLocStr(S32 ID) -{ - if(ID == 1) - { - return mLanguageSelection->getSelectedItemLabel(0); - } - else - { - return mLanguageSelection_2->getSelectedItemLabel(0); - } -} - -// Get localized directory (build path from data directory to XUI files, substituting localization string in for language) -std::string LLFloaterUIPreview::getLocalizedDirectory() -{ - return get_xui_dir() + (getLocStr(1)) + mDelim; // e.g. "C:/Code/guipreview/indra/newview/skins/xui/en/"; -} - -// Refresh the list of floaters by doing a directory traverse for XML XUI floater files -// Could be used to grab any specific language's list of compatible floaters, but currently it's just used to get all of them -void LLFloaterUIPreview::refreshList() -{ - // Note: the mask doesn't seem to accept regular expressions, so there need to be two directory searches here - mFileList->clearRows(); // empty list - std::string name; - bool found = true; - - LLDirIterator floater_iter(getLocalizedDirectory(), "floater_*.xml"); - while(found) // for every floater file that matches the pattern - { - if((found = floater_iter.next(name))) // get next file matching pattern - { - addFloaterEntry(name.c_str()); // and add it to the list (file name only; localization code takes care of rest of path) - } - } - found = true; - - LLDirIterator inspect_iter(getLocalizedDirectory(), "inspect_*.xml"); - while(found) // for every inspector file that matches the pattern - { - if((found = inspect_iter.next(name))) // get next file matching pattern - { - addFloaterEntry(name.c_str()); // and add it to the list (file name only; localization code takes care of rest of path) - } - } - found = true; - - LLDirIterator menu_iter(getLocalizedDirectory(), "menu_*.xml"); - while(found) // for every menu file that matches the pattern - { - if((found = menu_iter.next(name))) // get next file matching pattern - { - addFloaterEntry(name.c_str()); // and add it to the list (file name only; localization code takes care of rest of path) - } - } - found = true; - - LLDirIterator panel_iter(getLocalizedDirectory(), "panel_*.xml"); - while(found) // for every panel file that matches the pattern - { - if((found = panel_iter.next(name))) // get next file matching pattern - { - addFloaterEntry(name.c_str()); // and add it to the list (file name only; localization code takes care of rest of path) - } - } - found = true; - - LLDirIterator sidepanel_iter(getLocalizedDirectory(), "sidepanel_*.xml"); - while(found) // for every sidepanel file that matches the pattern - { - if((found = sidepanel_iter.next(name))) // get next file matching pattern - { - addFloaterEntry(name.c_str()); // and add it to the list (file name only; localization code takes care of rest of path) - } - } - - if(!mFileList->isEmpty()) // if there were any matching files, just select the first one (so we don't have to worry about disabling buttons when no entry is selected) - { - mFileList->selectFirstItem(); - } -} - -// Add a single entry to the list of available floaters -// Note: no deduplification (shouldn't be necessary) -void LLFloaterUIPreview::addFloaterEntry(const std::string& path) -{ - LLUUID* entry_id = new LLUUID(); // create a new UUID - entry_id->generate(path); - const LLUUID& entry_id_ref = *entry_id; // get a reference to the UUID for the LLSD block - - // fill LLSD column entry: initialize row/col structure - LLSD row; - row["id"] = entry_id_ref; - LLSD& columns = row["columns"]; - - // Get name of floater: - LLXmlTree xml_tree; - std::string full_path = getLocalizedDirectory() + path; // get full path - bool success = xml_tree.parseFile(full_path.c_str(), true); // parse xml - std::string entry_name; - std::string entry_title; - if(success) - { - // get root (or error handle) - LLXmlTreeNode* root_floater = xml_tree.getRoot(); - if (!root_floater) - { - std::string warning = std::string("No root node found in XUI file: ") + path; - popupAndPrintWarning(warning); - return; - } - - // get name - root_floater->getAttributeString("name",entry_name); - if(std::string("") == entry_name) - { - entry_name = "Error: unable to load " + std::string(path); // set to error state if load fails - } - - // get title - root_floater->getAttributeString("title",entry_title); // some don't have a title, and some have title = "(unknown)", so just leave it blank if it fails - } - else - { - std::string warning = std::string("Unable to parse XUI file: ") + path; // error handling - popupAndPrintWarning(warning); - if(mLiveFile) - { - delete mLiveFile; - mLiveFile = NULL; - } - return; - } - - // Fill floater title column - columns[0]["column"] = "title_column"; - columns[0]["type"] = "text"; - columns[0]["value"] = entry_title; - - // Fill floater path column - columns[1]["column"] = "file_column"; - columns[1]["type"] = "text"; - columns[1]["value"] = std::string(path); - - // Fill floater name column - columns[2]["column"] = "top_level_node_column"; - columns[2]["type"] = "text"; - columns[2]["value"] = entry_name; - - mFileList->addElement(row); // actually add to list -} - -// Respond to button click to display/refresh currently-selected floater -void LLFloaterUIPreview::onClickDisplayFloater(S32 caller_id) -{ - displayFloater(true, caller_id); -} - -// Saves the current floater/panel -void LLFloaterUIPreview::onClickSaveFloater(S32 caller_id) -{ - displayFloater(true, caller_id); - popupAndPrintWarning("Save-floater functionality removed, use XML schema to clean up XUI files"); -} - -// Saves all floater/panels -void LLFloaterUIPreview::onClickSaveAll(S32 caller_id) -{ - int listSize = mFileList->getItemCount(); - - for (int index = 0; index < listSize; index++) - { - mFileList->selectNthItem(index); - displayFloater(true, caller_id); - } - popupAndPrintWarning("Save-floater functionality removed, use XML schema to clean up XUI files"); -} - -// Actually display the floater -// Only set up a new live file if this came from a click (at which point there should be no existing live file), rather than from the live file's update itself; -// otherwise, we get an infinite loop as the live file keeps recreating itself. That means this function is generally called twice. -void LLFloaterUIPreview::displayFloater(bool click, S32 ID) -{ - // Convince UI that we're in a different language (the one selected on the drop-down menu) - LLLocalizationResetForcer reset_forcer(this, ID); // save old language in reset forcer object (to be reset upon destruction when it falls out of scope) - - LLPreviewedFloater** floaterp = (ID == 1 ? &(mDisplayedFloater) : &(mDisplayedFloater_2)); - if(ID == 1) - { - bool floater_already_open = mDisplayedFloater != NULL; - if(floater_already_open) // if we are already displaying a floater - { - mLastDisplayedX = mDisplayedFloater->calcScreenRect().mLeft; // save floater's last known position to put the new one there - mLastDisplayedY = mDisplayedFloater->calcScreenRect().mBottom; - delete mDisplayedFloater; // delete it (this closes it too) - mDisplayedFloater = NULL; // and reset the pointer - } - } - else - { - if(mDisplayedFloater_2 != NULL) - { - delete mDisplayedFloater_2; - mDisplayedFloater_2 = NULL; - } - } - - std::string path = mFileList->getSelectedItemLabel(1); // get the path of the currently-selected floater - if(std::string("") == path) // if no item is selected - { - return; // ignore click (this can only happen with empty list; otherwise an item is always selected) - } - - LLFloater::Params p(LLFloater::getDefaultParams()); - p.min_height=p.header_height; - p.min_width=10; - - *floaterp = new LLPreviewedFloater(this, p); - - if(!strncmp(path.c_str(),"floater_",8) - || !strncmp(path.c_str(), "inspect_", 8)) // if it's a floater - { - (*floaterp)->buildFromFile(path); // just build it - (*floaterp)->openFloater((*floaterp)->getKey()); - (*floaterp)->setCanResize((*floaterp)->isResizable()); - } - else if (!strncmp(path.c_str(),"menu_",5)) // if it's a menu - { - // former 'save' processing excised - } - else // if it is a panel... - { - (*floaterp)->setCanResize(true); - - const LLFloater::Params& floater_params = LLFloater::getDefaultParams(); - S32 floater_header_size = floater_params.header_height; - - LLPanel::Params panel_params; - LLPanel* panel = LLUICtrlFactory::create(panel_params); // create a new panel - - panel->buildFromFile(path); // build it - panel->setOrigin(2,2); // reset its origin point so it's not offset by -left or other XUI attributes - (*floaterp)->setTitle(path); // use the file name as its title, since panels have no guaranteed meaningful name attribute - panel->setUseBoundingRect(true); // enable the use of its outer bounding rect (normally disabled because it's O(n) on the number of sub-elements) - panel->updateBoundingRect(); // update bounding rect - LLRect bounding_rect = panel->getBoundingRect(); // get the bounding rect - LLRect new_rect = panel->getRect(); // get the panel's rect - new_rect.unionWith(bounding_rect); // union them to make sure we get the biggest one possible - LLRect floater_rect = new_rect; - floater_rect.stretch(4, 4); - (*floaterp)->reshape(floater_rect.getWidth(), floater_rect.getHeight() + floater_header_size); // reshape floater to match the union rect's dimensions - panel->reshape(new_rect.getWidth(), new_rect.getHeight()); // reshape panel to match the union rect's dimensions as well (both are needed) - (*floaterp)->addChild(panel); // add panel as child - (*floaterp)->openFloater(); // open floater (needed?) - } - - if(ID == 1) - { - (*floaterp)->setOrigin(mLastDisplayedX, mLastDisplayedY); - } - - // *HACK: Remove ability to close it; if you close it, its destructor gets called, but we don't know it's null and try to delete it again, - // resulting in a double free - (*floaterp)->setCanClose(false); - - if(ID == 1) - { - mCloseOtherButton->setEnabled(true); // enable my floater's close button - } - else - { - mCloseOtherButton_2->setEnabled(true); - } - - // Add localization to title so user knows whether it's localized or defaulted to en - std::string full_path = getLocalizedDirectory() + path; - std::string floater_lang = "EN"; - llstat dummy; - if(!LLFile::stat(full_path.c_str(), &dummy)) // if the file does not exist - { - floater_lang = getLocStr(ID); - } - std::string new_title = (*floaterp)->getTitle() + std::string(" [") + floater_lang + - (ID == 1 ? " - Primary" : " - Secondary") + std::string("]"); - (*floaterp)->setTitle(new_title); - - (*floaterp)->center(); - addDependentFloater(*floaterp); - - if(click && ID == 1) - { - // set up live file to track it - if(mLiveFile) - { - delete mLiveFile; - mLiveFile = NULL; - } - mLiveFile = new LLGUIPreviewLiveFile(std::string(full_path.c_str()),std::string(path.c_str()),this); - mLiveFile->checkAndReload(); - mLiveFile->addToEventTimer(); - } - - if(ID == 1) - { - mToggleOverlapButton->setEnabled(true); - } - - if(LLView::sHighlightingDiffs && click && ID == 1) - { - highlightChangedElements(); - } - - if(ID == 1) - { - mOverlapPanel->mOverlapMap.clear(); - LLView::sPreviewClickedElement = NULL; // stop overlapping elements from drawing - mOverlapPanel->mLastClickedElement = NULL; - findOverlapsInChildren((LLView*)mDisplayedFloater); - - // highlight and enable them - if(mHighlightingOverlaps) - { - for(LLOverlapPanel::OverlapMap::iterator iter = mOverlapPanel->mOverlapMap.begin(); iter != mOverlapPanel->mOverlapMap.end(); ++iter) - { - LLView* viewp = iter->first; - LLView::sPreviewHighlightedElements.insert(viewp); - } - } - else if(LLView::sHighlightingDiffs) - { - highlightChangedElements(); - } - } - - // NOTE: language is reset here automatically when the reset forcer object falls out of scope (see header for details) -} - -// Respond to button click to edit currently-selected floater -void LLFloaterUIPreview::onClickEditFloater() -{ - // Determine file to edit. - std::string file_path; - { - std::string file_name = mFileList->getSelectedItemLabel(1); // get the file name of the currently-selected floater - if (file_name.empty()) // if no item is selected - { - LL_WARNS() << "No file selected" << LL_ENDL; - return; // ignore click - } - file_path = getLocalizedDirectory() + file_name; - - // stat file to see if it exists (some localized versions may not have it there are no diffs, and then we try to open an nonexistent file) - llstat dummy; - if(LLFile::stat(file_path.c_str(), &dummy)) // if the file does not exist - { - popupAndPrintWarning("No file for this floater exists in the selected localization. Opening the EN version instead."); - file_path = get_xui_dir() + mDelim + "en" + mDelim + file_name; // open the en version instead, by default - } - } - - // Set the editor command. - std::string cmd_override; - { - std::string bin = mEditorPathTextBox->getText(); - if (!bin.empty()) - { - // surround command with double quotes for the case if the path contains spaces - if (bin.find("\"") == std::string::npos) - { - bin = "\"" + bin + "\""; - } - - std::string args = mEditorArgsTextBox->getText(); - cmd_override = bin + " " + args; - } - } - - LLExternalEditor::EErrorCode status = mExternalEditor.setCommand("LL_XUI_EDITOR", cmd_override); - if (status != LLExternalEditor::EC_SUCCESS) - { - std::string warning; - - if (status == LLExternalEditor::EC_NOT_SPECIFIED) // Use custom message for this error. - { - warning = getString("ExternalEditorNotSet"); - } - else - { - warning = LLExternalEditor::getErrorMessage(status); - } - - popupAndPrintWarning(warning); - return; - } - - // Run the editor. - if (mExternalEditor.run(file_path) != LLExternalEditor::EC_SUCCESS) - { - popupAndPrintWarning(LLExternalEditor::getErrorMessage(status)); - return; - } -} - -// Respond to button click to browse for an executable with which to edit XML files -void LLFloaterUIPreview::onClickBrowseForEditor() -{ - // Let the user choose an executable through the file picker dialog box - LLFilePickerReplyThread::startPicker(boost::bind(&LLFloaterUIPreview::getExecutablePath, this, _1), LLFilePicker::FFLOAD_EXE, false); -} - -void LLFloaterUIPreview::getExecutablePath(const std::vector& filenames) -{ - // put the selected path into text field - const std::string chosen_path = filenames[0]; - std::string executable_path = chosen_path; -#if LL_DARWIN - // on Mac, if it's an application bundle, figure out the actual path from the Info.plist file - CFStringRef path_cfstr = CFStringCreateWithCString(kCFAllocatorDefault, chosen_path.c_str(), kCFStringEncodingMacRoman); // get path as a CFStringRef - CFURLRef path_url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path_cfstr, kCFURLPOSIXPathStyle, true); // turn it into a CFURLRef - CFBundleRef chosen_bundle = CFBundleCreate(kCFAllocatorDefault, path_url); // get a handle for the bundle - if(NULL != chosen_bundle) - { - CFDictionaryRef bundleInfoDict = CFBundleGetInfoDictionary(chosen_bundle); // get the bundle's dictionary - if(NULL != bundleInfoDict) - { - CFStringRef executable_cfstr = (CFStringRef)CFDictionaryGetValue(bundleInfoDict, CFSTR("CFBundleExecutable")); // get the name of the actual executable (e.g. TextEdit or firefox-bin) - int max_file_length = 256; // (max file name length is 255 in OSX) - char executable_buf[max_file_length]; - if(CFStringGetCString(executable_cfstr, executable_buf, max_file_length, kCFStringEncodingMacRoman)) // convert CFStringRef to char* - { - executable_path += std::string("/Contents/MacOS/") + std::string(executable_buf); // append path to executable directory and then executable name to exec path - } - else - { - std::string warning = "Unable to get CString from CFString for executable path"; - popupAndPrintWarning(warning); - } - } - else - { - std::string warning = "Unable to get bundle info dictionary from application bundle"; - popupAndPrintWarning(warning); - } - } - else - { - if(-1 != executable_path.find(".app")) // only warn if this path actually had ".app" in it, i.e. it probably just wasn'nt an app bundle and that's okay - { - std::string warning = std::string("Unable to get bundle from path \"") + chosen_path + std::string("\""); - popupAndPrintWarning(warning); - } - } - -#endif - mEditorPathTextBox->setText(std::string(executable_path)); // copy the path to the executable to the textfield for display and later fetching -} - -// Respond to button click to browse for a VLT-generated diffs file -void LLFloaterUIPreview::onClickBrowseForDiffs() -{ - // create load dialog box - LLFilePickerReplyThread::startPicker(boost::bind(&LLFloaterUIPreview::getDiffsFilePath, this, _1), LLFilePicker::FFLOAD_XML, false); -} - -void LLFloaterUIPreview::getDiffsFilePath(const std::vector& filenames) -{ - // put the selected path into text field - const std::string chosen_path = filenames[0]; - mDiffPathTextBox->setText(std::string(chosen_path)); // copy the path to the executable to the textfield for display and later fetching - if(LLView::sHighlightingDiffs) // if we're already highlighting, toggle off and then on so we get the data from the new file - { - onClickToggleDiffHighlighting(); - onClickToggleDiffHighlighting(); - } -} - -void LLFloaterUIPreview::onClickToggleDiffHighlighting() -{ - if(mHighlightingOverlaps) - { - onClickToggleOverlapping(); - mToggleOverlapButton->toggleState(); - } - - LLView::sPreviewHighlightedElements.clear(); // clear lists first - mDiffsMap.clear(); - mFileList->clearHighlightedItems(); - - if(LLView::sHighlightingDiffs) // Turning highlighting off - { - LLView::sHighlightingDiffs = !sHighlightingDiffs; - return; - } - else // Turning highlighting on - { - // Get the file and make sure it exists - std::string path_in_textfield = mDiffPathTextBox->getText(); // get file path - bool error = false; - - if(std::string("") == path_in_textfield) // check for blank file - { - std::string warning = "Unable to highlight differences because no file was provided; fill in the relevant text field"; - popupAndPrintWarning(warning); - error = true; - } - - llstat dummy; - if(LLFile::stat(path_in_textfield.c_str(), &dummy) && !error) // check if the file exists (empty check is reduntant but useful for the informative error message) - { - std::string warning = std::string("Unable to highlight differences because an invalid path to a difference file was provided:\"") + path_in_textfield + "\""; - popupAndPrintWarning(warning); - error = true; - } - - // Build a list of changed elements as given by the XML - std::list changed_element_names; - LLXmlTree xml_tree; - bool success = xml_tree.parseFile(path_in_textfield.c_str(), true); - - if(success && !error) - { - LLXmlTreeNode* root_floater = xml_tree.getRoot(); - if(!strncmp("XuiDelta",root_floater->getName().c_str(),9)) - { - for (LLXmlTreeNode* child = root_floater->getFirstChild(); // get the first child first, then below get the next one; otherwise the iterator is invalid (bug or feature in XML code?) - child != NULL; - child = root_floater->getNextChild()) // get child for next iteration - { - if(!strncmp("file",child->getName().c_str(),5)) - { - scanDiffFile(child); - } - else if(!strncmp("error",child->getName().c_str(),6)) - { - std::string error_file, error_message; - child->getAttributeString("filename",error_file); - child->getAttributeString("message",error_message); - if(mDiffsMap.find(error_file) != mDiffsMap.end()) - { - mDiffsMap.insert(std::make_pair(error_file,std::make_pair(StringListPtr(new StringList), StringListPtr(new StringList)))); - } - mDiffsMap[error_file].second->push_back(error_message); - } - else - { - std::string warning = std::string("Child was neither a file or an error, but rather the following:\"") + std::string(child->getName()) + "\""; - popupAndPrintWarning(warning); - error = true; - break; - } - } - } - else - { - std::string warning = std::string("Root node not named XuiDelta:\"") + path_in_textfield + "\""; - popupAndPrintWarning(warning); - error = true; - } - } - else if(!error) - { - std::string warning = std::string("Unable to create tree from XML:\"") + path_in_textfield + "\""; - popupAndPrintWarning(warning); - error = true; - } - - if(error) // if we encountered an error, reset the button to off - { - mToggleHighlightButton->setToggleState(false); - } - else // only toggle if we didn't encounter an error - { - LLView::sHighlightingDiffs = !sHighlightingDiffs; - highlightChangedElements(); // *TODO: this is extraneous, right? - highlightChangedFiles(); // *TODO: this is extraneous, right? - } - } -} - -void LLFloaterUIPreview::scanDiffFile(LLXmlTreeNode* file_node) -{ - // Get file name - std::string file_name; - file_node->getAttributeString("name",file_name); - if(std::string("") == file_name) - { - std::string warning = std::string("Empty file name encountered in differences:\"") + file_name + "\""; - popupAndPrintWarning(warning); - return; - } - - // Get a list of changed elements - // Get the first child first, then below get the next one; otherwise the iterator is invalid (bug or feature in XML code?) - for (LLXmlTreeNode* child = file_node->getFirstChild(); child != NULL; child = file_node->getNextChild()) - { - if(!strncmp("delta",child->getName().c_str(),6)) - { - std::string id; - child->getAttributeString("id",id); - if(mDiffsMap.find(file_name) == mDiffsMap.end()) - { - mDiffsMap.insert(std::make_pair(file_name,std::make_pair(StringListPtr(new StringList), StringListPtr(new StringList)))); - } - mDiffsMap[file_name].first->push_back(std::string(id.c_str())); - } - else - { - std::string warning = std::string("Child of file was not a delta, but rather the following:\"") + std::string(child->getName()) + "\""; - popupAndPrintWarning(warning); - return; - } - } -} - -void LLFloaterUIPreview::highlightChangedElements() -{ - if(NULL == mLiveFile) - { - return; - } - - // Process differences first (we want their warnings to be shown underneath other warnings) - StringListPtr changed_element_paths; - DiffMap::iterator iterExists = mDiffsMap.find(mLiveFile->mFileName); - if(iterExists != mDiffsMap.end()) - { - changed_element_paths = mDiffsMap[mLiveFile->mFileName].first; // retrieve list of changed element paths from map - } - - for(std::list::iterator iter = changed_element_paths->begin(); iter != changed_element_paths->end(); ++iter) // for every changed element path - { - LLView* element = mDisplayedFloater; - if(!strncmp(iter->c_str(),".",1)) // if it's the root floater itself - { - continue; - } - - // Split element hierarchy path on period (*HACK: it's possible that the element name will have a period in it, in which case this won't work. See https://wiki.lindenlab.com/wiki/Viewer_Localization_Tool_Documentation.) - typedef boost::tokenizer > tokenizer; - boost::char_separator sep("."); - tokenizer tokens(*iter, sep); - tokenizer::iterator token_iter; - bool failed = false; - for(token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) - { - element = element->findChild(*token_iter,false); // try to find element: don't recur, and don't create if missing - - // if we still didn't find it... - if(NULL == element) - { - LL_INFOS() << "Unable to find element in XuiDelta file named \"" << *iter << "\" in file \"" << mLiveFile->mFileName << - "\". The element may no longer exist, the path may be incorrect, or it may not be a non-displayable element (not an LLView) such as a \"string\" type." << LL_ENDL; - failed = true; - break; - } - } - - if(!failed) - { - // Now that we have a pointer to the actual element, add it to the list of elements to be highlighted - std::set::iterator iter2 = std::find(LLView::sPreviewHighlightedElements.begin(), LLView::sPreviewHighlightedElements.end(), element); - if(iter2 == LLView::sPreviewHighlightedElements.end()) - { - LLView::sPreviewHighlightedElements.insert(element); - } - } - } - - // Process errors second, so their warnings show up on top of other warnings - StringListPtr error_list; - if(iterExists != mDiffsMap.end()) - { - error_list = mDiffsMap[mLiveFile->mFileName].second; - } - for(std::list::iterator iter = error_list->begin(); iter != error_list->end(); ++iter) // for every changed element path - { - std::string warning = std::string("Error listed among differences. Filename: \"") + mLiveFile->mFileName + "\". Message: \"" + *iter + "\""; - popupAndPrintWarning(warning); - } -} - -void LLFloaterUIPreview::highlightChangedFiles() -{ - for(DiffMap::iterator iter = mDiffsMap.begin(); iter != mDiffsMap.end(); ++iter) // for every file listed in diffs - { - LLScrollListItem* item = mFileList->getItemByLabel(std::string(iter->first), false, 1); - if(item) - { - item->setHighlighted(true); - } - } -} - -// Respond to button click to browse for an executable with which to edit XML files -void LLFloaterUIPreview::onClickCloseDisplayedFloater(S32 caller_id) -{ - if(caller_id == PRIMARY_FLOATER) - { - mCloseOtherButton->setEnabled(false); - mToggleOverlapButton->setEnabled(false); - - if(mDisplayedFloater) - { - mLastDisplayedX = mDisplayedFloater->calcScreenRect().mLeft; - mLastDisplayedY = mDisplayedFloater->calcScreenRect().mBottom; - delete mDisplayedFloater; - mDisplayedFloater = NULL; - } - - if(mLiveFile) - { - delete mLiveFile; - mLiveFile = NULL; - } - - if(mToggleOverlapButton->getToggleState()) - { - mToggleOverlapButton->toggleState(); - onClickToggleOverlapping(); - } - - LLView::sPreviewClickedElement = NULL; // stop overlapping elements panel from drawing - mOverlapPanel->mLastClickedElement = NULL; - } - else - { - mCloseOtherButton_2->setEnabled(false); - delete mDisplayedFloater_2; - mDisplayedFloater_2 = NULL; - } - -} - -void append_view_tooltip(LLView* tooltip_view, std::string *tooltip_msg) -{ - LLRect rect = tooltip_view->getRect(); - LLRect parent_rect = tooltip_view->getParent()->getRect(); - S32 left = rect.mLeft; - // invert coordinate system for XUI top-left layout - S32 top = parent_rect.getHeight() - rect.mTop; - if (!tooltip_msg->empty()) - { - tooltip_msg->append("\n"); - } - std::string msg = llformat("%s %d, %d (%d x %d)", - tooltip_view->getName().c_str(), - left, - top, - rect.getWidth(), - rect.getHeight() ); - tooltip_msg->append( msg ); -} - -bool LLPreviewedFloater::handleToolTip(S32 x, S32 y, MASK mask) -{ - if (!sShowRectangles) - { - return LLFloater::handleToolTip(x, y, mask); - } - - S32 screen_x, screen_y; - localPointToScreen(x, y, &screen_x, &screen_y); - std::string tooltip_msg; - LLView* tooltip_view = this; - LLView::tree_iterator_t end_it = endTreeDFS(); - for (LLView::tree_iterator_t it = beginTreeDFS(); it != end_it; ++it) - { - LLView* viewp = *it; - LLRect screen_rect; - viewp->localRectToScreen(viewp->getLocalRect(), &screen_rect); - if (!(viewp->getVisible() - && screen_rect.pointInRect(screen_x, screen_y))) - { - it.skipDescendants(); - } - // only report xui names for LLUICtrls, not the various container LLViews - - else if (dynamic_cast(viewp)) - { - // if we are in a new part of the tree (not a descendent of current tooltip_view) - // then push the results for tooltip_view and start with a new potential view - // NOTE: this emulates visiting only the leaf nodes that meet our criteria - - if (tooltip_view != this - && !viewp->hasAncestor(tooltip_view)) - { - append_view_tooltip(tooltip_view, &tooltip_msg); - } - tooltip_view = viewp; - } - } - - append_view_tooltip(tooltip_view, &tooltip_msg); - - LLToolTipMgr::instance().show(LLToolTip::Params() - .message(tooltip_msg) - .max_width(400)); - return true; -} - -bool LLPreviewedFloater::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - selectElement(this,x,y,0); - return true; -} - -// *NOTE: In order to hide all of the overlapping elements of the selected element so as to see it in context, here is what you would need to do: -// -This selectElement call fills the overlap panel as normal. The element which is "selected" here is actually just an intermediate selection step; -// what you've really selected is a list of elements: the one you clicked on and everything that overlaps it. -// -The user then selects one of the elements from this list the overlap panel (click handling to the overlap panel would have to be added). -// This becomes the final selection (as opposed to the intermediate selection that was just made). -// -Everything else that is currently displayed on the overlap panel should be hidden from view in the previewed floater itself (setVisible(false)). -// -Subsequent clicks on other elements in the overlap panel (they should still be there) should make other elements the final selection. -// -On close or on the click of a new button, everything should be shown again and all selection state should be cleared. -// ~Jacob, 8/08 -bool LLPreviewedFloater::selectElement(LLView* parent, int x, int y, int depth) -{ - if(getVisible()) - { - bool handled = false; - if(LLFloaterUIPreview::containerType(parent)) - { - for(child_list_const_iter_t child_it = parent->getChildList()->begin(); child_it != parent->getChildList()->end(); ++child_it) - { - LLView* child = *child_it; - S32 local_x = x - child->getRect().mLeft; - S32 local_y = y - child->getRect().mBottom; - if (child->pointInView(local_x, local_y) && - child->getVisible() && - selectElement(child, x, y, ++depth)) - { - handled = true; - break; - } - } - } - - if(!handled) - { - LLView::sPreviewClickedElement = parent; - } - return true; - } - else - { - return false; - } -} - -void LLPreviewedFloater::draw() -{ - if(NULL != mFloaterUIPreview) - { - // Set and unset sDrawPreviewHighlights flag so as to avoid using two flags - if(mFloaterUIPreview->mHighlightingOverlaps) - { - LLView::sDrawPreviewHighlights = true; - } - - // If we're looking for truncations, draw debug rects for the displayed - // floater only. - bool old_debug_rects = LLView::sDebugRects; - bool old_show_names = LLView::sDebugRectsShowNames; - if (sShowRectangles) - { - LLView::sDebugRects = true; - LLView::sDebugRectsShowNames = false; - } - - LLFloater::draw(); - - LLView::sDebugRects = old_debug_rects; - LLView::sDebugRectsShowNames = old_show_names; - - if(mFloaterUIPreview->mHighlightingOverlaps) - { - LLView::sDrawPreviewHighlights = false; - } - } -} - -void LLFloaterUIPreview::onClickToggleOverlapping() -{ - if(LLView::sHighlightingDiffs) - { - onClickToggleDiffHighlighting(); - mToggleHighlightButton->toggleState(); - } - LLView::sPreviewHighlightedElements.clear(); // clear lists first - - S32 width, height; - getResizeLimits(&width, &height); // illegal call of non-static member function - if(mHighlightingOverlaps) - { - mHighlightingOverlaps = !mHighlightingOverlaps; - // reset list of preview highlighted elements - setRect(LLRect(getRect().mLeft,getRect().mTop,getRect().mRight - mOverlapPanel->getRect().getWidth(),getRect().mBottom)); - setResizeLimits(width - mOverlapPanel->getRect().getWidth(), height); - } - else - { - mHighlightingOverlaps = !mHighlightingOverlaps; - displayFloater(false,1); - setRect(LLRect(getRect().mLeft,getRect().mTop,getRect().mRight + mOverlapPanel->getRect().getWidth(),getRect().mBottom)); - setResizeLimits(width + mOverlapPanel->getRect().getWidth(), height); - } - getChildView("overlap_scroll")->setVisible( mHighlightingOverlaps); -} - -void LLFloaterUIPreview::findOverlapsInChildren(LLView* parent) -{ - if(parent->getChildCount() == 0 || !containerType(parent)) // if it has no children or isn't a container type, skip it - { - return; - } - - // for every child of the parent - for(child_list_const_iter_t child_it = parent->getChildList()->begin(); child_it != parent->getChildList()->end(); ++child_it) - { - LLView* child = *child_it; - if(overlapIgnorable(child)) - { - continue; - } - - // for every sibling - for(child_list_const_iter_t sibling_it = parent->getChildList()->begin(); sibling_it != parent->getChildList()->end(); ++sibling_it) // for each sibling - { - LLView* sibling = *sibling_it; - if(overlapIgnorable(sibling)) - { - continue; - } - - // if they overlap... (we don't care if they're visible or enabled -- we want to check those anyway, i.e. hidden tabs that can be later shown) - if(sibling != child && elementOverlap(child, sibling)) - { - mOverlapPanel->mOverlapMap[child].push_back(sibling); // add to the map - } - } - findOverlapsInChildren(child); // recur - } -} - -// *HACK: don't overlap with the drag handle and various other elements -// This is using dynamic casts because there is no object-oriented way to tell which elements contain localizable text. These are a few that are ignorable. -// *NOTE: If a list of elements which have localizable content were created, this function should return false if viewp's class is in that list. -bool LLFloaterUIPreview::overlapIgnorable(LLView* viewp) -{ - return NULL != dynamic_cast(viewp) || - NULL != dynamic_cast(viewp) || - NULL != dynamic_cast(viewp); -} - -// *HACK: these are the only two container types as of 8/08, per Richard -// This is using dynamic casts because there is no object-oriented way to tell which elements are containers. -bool LLFloaterUIPreview::containerType(LLView* viewp) -{ - return NULL != dynamic_cast(viewp) || NULL != dynamic_cast(viewp); -} - -// Check if two llview's rectangles overlap, with some tolerance -bool LLFloaterUIPreview::elementOverlap(LLView* view1, LLView* view2) -{ - LLSD rec1 = view1->getRect().getValue(); - LLSD rec2 = view2->getRect().getValue(); - int tolerance = 2; - return (int)rec1[0] <= (int)rec2[2] - tolerance && - (int)rec2[0] <= (int)rec1[2] - tolerance && - (int)rec1[3] <= (int)rec2[1] - tolerance && - (int)rec2[3] <= (int)rec1[1] - tolerance; -} - -void LLOverlapPanel::draw() -{ - static const std::string current_selection_text("Current selection: "); - static const std::string overlapper_text("Overlapper: "); - LLColor4 text_color = LLColor4::grey; - gGL.color4fv(text_color.mV); - - if(!LLView::sPreviewClickedElement) - { - LLUI::translate(5,getRect().getHeight()-20); // translate to top-5,left-5 - LLView::sDrawPreviewHighlights = false; - LLFontGL::getFontSansSerifSmall()->renderUTF8(current_selection_text, 0, 0, 0, text_color, - LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW); - } - else - { - OverlapMap::iterator iterExists = mOverlapMap.find(LLView::sPreviewClickedElement); - if(iterExists == mOverlapMap.end()) - { - return; - } - - std::list overlappers = mOverlapMap[LLView::sPreviewClickedElement]; - if(overlappers.size() == 0) - { - LLUI::translate(5,getRect().getHeight()-20); // translate to top-5,left-5 - LLView::sDrawPreviewHighlights = false; - std::string current_selection = std::string(current_selection_text + LLView::sPreviewClickedElement->getName() + " (no elements overlap)"); - S32 text_width = LLFontGL::getFontSansSerifSmall()->getWidth(current_selection) + 10; - LLFontGL::getFontSansSerifSmall()->renderUTF8(current_selection, 0, 0, 0, text_color, - LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW); - // widen panel enough to fit this text - LLRect rect = getRect(); - setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() < text_width ? rect.mLeft + text_width : rect.mRight,rect.mTop)); - return; - } - - // recalculate required with and height; otherwise use cached - bool need_to_recalculate_bounds = false; - if(mLastClickedElement == NULL) - { - need_to_recalculate_bounds = true; - } - - if(NULL == mLastClickedElement) - { - mLastClickedElement = LLView::sPreviewClickedElement; - } - - // recalculate bounds for scroll panel - if(need_to_recalculate_bounds || LLView::sPreviewClickedElement->getName() != mLastClickedElement->getName()) - { - // reset panel's rectangle to its default width and height (300x600) - LLRect panel_rect = getRect(); - setRect(LLRect(panel_rect.mLeft,panel_rect.mTop,panel_rect.mLeft+getRect().getWidth(),panel_rect.mTop-getRect().getHeight())); - - LLRect rect; - - // change bounds for selected element - int height_sum = mLastClickedElement->getRect().getHeight() + mSpacing + 80; - rect = getRect(); - setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() > mLastClickedElement->getRect().getWidth() + 5 ? rect.mRight : rect.mLeft + mLastClickedElement->getRect().getWidth() + 5, rect.mBottom)); - - // and widen to accomodate text if that's wider - std::string display_text = current_selection_text + LLView::sPreviewClickedElement->getName(); - S32 text_width = LLFontGL::getFontSansSerifSmall()->getWidth(display_text) + 10; - rect = getRect(); - setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() < text_width ? rect.mLeft + text_width : rect.mRight,rect.mTop)); - - std::list overlappers = mOverlapMap[LLView::sPreviewClickedElement]; - for(std::list::iterator overlap_it = overlappers.begin(); overlap_it != overlappers.end(); ++overlap_it) - { - LLView* viewp = *overlap_it; - height_sum += viewp->getRect().getHeight() + mSpacing*3; - - // widen panel's rectangle to accommodate widest overlapping element of this floater - rect = getRect(); - setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() > viewp->getRect().getWidth() + 5 ? rect.mRight : rect.mLeft + viewp->getRect().getWidth() + 5, rect.mBottom)); - - // and widen to accomodate text if that's wider - std::string display_text = overlapper_text + viewp->getName(); - S32 text_width = LLFontGL::getFontSansSerifSmall()->getWidth(display_text) + 10; - rect = getRect(); - setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() < text_width ? rect.mLeft + text_width : rect.mRight,rect.mTop)); - } - // change panel's height to accommodate all element heights plus spacing between them - rect = getRect(); - setRect(LLRect(rect.mLeft,rect.mTop,rect.mRight,rect.mTop-height_sum)); - } - - LLUI::translate(5,getRect().getHeight()-10); // translate to top left - LLView::sDrawPreviewHighlights = false; - - // draw currently-selected element at top of overlappers - LLUI::translate(0,-mSpacing); - LLFontGL::getFontSansSerifSmall()->renderUTF8(current_selection_text + LLView::sPreviewClickedElement->getName(), 0, 0, 0, text_color, - LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW); - LLUI::translate(0,-mSpacing-LLView::sPreviewClickedElement->getRect().getHeight()); // skip spacing distance + height - LLView::sPreviewClickedElement->draw(); - - for(std::list::iterator overlap_it = overlappers.begin(); overlap_it != overlappers.end(); ++overlap_it) - { - LLView* viewp = *overlap_it; - - // draw separating line - LLUI::translate(0,-mSpacing); - gl_line_2d(0,0,getRect().getWidth()-10,0,LLColor4(192.0f/255.0f,192.0f/255.0f,192.0f/255.0f)); - - // draw name - LLUI::translate(0,-mSpacing); - LLFontGL::getFontSansSerifSmall()->renderUTF8(overlapper_text + viewp->getName(), 0, 0, 0, text_color, - LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW); - - // draw element - LLUI::translate(0,-mSpacing-viewp->getRect().getHeight()); // skip spacing distance + height - viewp->draw(); - } - mLastClickedElement = LLView::sPreviewClickedElement; - } -} - -void LLFloaterUIPreviewUtil::registerFloater() -{ - LLFloaterReg::add("ui_preview", "floater_ui_preview.xml", - &LLFloaterReg::build); -} +/** + * @file llfloateruipreview.cpp + * @brief Tool for previewing and editing floaters, plus localization tool integration + * + * $LicenseInfo:firstyear=2008&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Tool for previewing floaters and panels for localization and UI design purposes. +// See: https://wiki.lindenlab.com/wiki/GUI_Preview_And_Localization_Tools +// See: https://jira.lindenlab.com/browse/DEV-16869 + +// *TODO: Translate error messgaes using notifications/alerts.xml + +#include "llviewerprecompiledheaders.h" // Precompiled headers + +#include "llfloateruipreview.h" // Own header + +// Internal utility +#include "lldiriterator.h" +#include "lleventtimer.h" +#include "llexternaleditor.h" +#include "llrender.h" +#include "llsdutil.h" +#include "llxmltree.h" +#include "llviewerwindow.h" + +// XUI +#include "lluictrlfactory.h" +#include "llcombobox.h" +#include "llnotificationsutil.h" +#include "llresizebar.h" +#include "llscrolllistitem.h" +#include "llscrolllistctrl.h" +#include "llfilepicker.h" +#include "lldraghandle.h" +#include "lllayoutstack.h" +#include "lltooltip.h" +#include "llviewermenu.h" +#include "llrngwriter.h" +#include "llfloater.h" // superclass +#include "llfloaterreg.h" +#include "llscrollcontainer.h" // scroll container for overlapping elements +#include "lllivefile.h" // live file poll/stat/reload +#include "llviewermenufile.h" // LLFilePickerReplyThread + +// Boost (for linux/unix command-line execv) +#include +#include + +// External utility +#include +#include +#include + +#if LL_DARWIN +#include +#endif + +// Static initialization +static const S32 PRIMARY_FLOATER = 1; +static const S32 SECONDARY_FLOATER = 2; + +class LLOverlapPanel; +static LLDefaultChildRegistry::Register register_overlap_panel("overlap_panel"); + +static std::string get_xui_dir() +{ + std::string delim = gDirUtilp->getDirDelimiter(); + return gDirUtilp->getSkinBaseDir() + delim + "default" + delim + "xui" + delim; +} + +// Forward declarations to avoid header dependencies +class LLColor; +class LLScrollListCtrl; +class LLComboBox; +class LLButton; +class LLLineEditor; +class LLXmlTreeNode; +class LLFloaterUIPreview; +class LLFadeEventTimer; + +class LLLocalizationResetForcer; +class LLGUIPreviewLiveFile; +class LLFadeEventTimer; +class LLPreviewedFloater; + +// Implementation of custom overlapping element display panel +class LLOverlapPanel : public LLPanel +{ +public: + struct Params : public LLInitParam::Block + { + Params() {} + }; + LLOverlapPanel(Params p = Params()) : LLPanel(p), + mSpacing(10), + // mClickedElement(NULL), + mLastClickedElement(NULL) + { + mOriginalWidth = getRect().getWidth(); + mOriginalHeight = getRect().getHeight(); + } + virtual void draw(); + + typedef std::map > OverlapMap; + OverlapMap mOverlapMap; // map, of XUI element to a list of XUI elements it overlaps + + // LLView *mClickedElement; + LLView *mLastClickedElement; + int mOriginalWidth, mOriginalHeight, mSpacing; +}; + + +class LLFloaterUIPreview : public LLFloater +{ +public: + // Setup + LLFloaterUIPreview(const LLSD& key); + virtual ~LLFloaterUIPreview(); + + std::string getLocStr(S32 ID); // fetches the localization string based on what is selected in the drop-down menu + void displayFloater(bool click, S32 ID); // needs to be public so live file can call it when it finds an update + + /*virtual*/ bool postBuild(); + /*virtual*/ void onClose(bool app_quitting); + + void refreshList(); // refresh list (empty it out and fill it up from scratch) + void addFloaterEntry(const std::string& path); // add a single file's entry to the list of floaters + + static bool containerType(LLView* viewp); // check if the element is a container type and tree traverses need to look at its children + +public: + LLPreviewedFloater* mDisplayedFloater; // the floater which is currently being displayed + LLPreviewedFloater* mDisplayedFloater_2; // the floater which is currently being displayed + LLGUIPreviewLiveFile* mLiveFile; // live file for checking for updates to the currently-displayed XML file + LLOverlapPanel* mOverlapPanel; // custom overlapping elements panel + // bool mHighlightingDiffs; // bool for whether localization diffs are being highlighted or not + bool mHighlightingOverlaps; // bool for whether overlapping elements are being highlighted + + // typedef std::map,std::list > > DiffMap; // this version copies the lists etc., and thus is bad memory-wise + typedef std::list StringList; + typedef std::shared_ptr StringListPtr; + typedef std::map > DiffMap; + DiffMap mDiffsMap; // map, of filename to pair of list of changed element paths and list of errors + +private: + LLExternalEditor mExternalEditor; + + // XUI elements for this floater + LLScrollListCtrl* mFileList; // scroll list control for file list + LLLineEditor* mEditorPathTextBox; // text field for path to editor executable + LLLineEditor* mEditorArgsTextBox; // text field for arguments to editor executable + LLLineEditor* mDiffPathTextBox; // text field for path to diff file + LLButton* mDisplayFloaterBtn; // button to display primary floater + LLButton* mDisplayFloaterBtn_2; // button to display secondary floater + LLButton* mEditFloaterBtn; // button to edit floater + LLButton* mExecutableBrowseButton; // button to browse for executable + LLButton* mCloseOtherButton; // button to close primary displayed floater + LLButton* mCloseOtherButton_2; // button to close secondary displayed floater + LLButton* mDiffBrowseButton; // button to browse for diff file + LLButton* mToggleHighlightButton; // button to toggle highlight of files/elements with diffs + LLButton* mToggleOverlapButton; // button to togle overlap panel/highlighting + LLComboBox* mLanguageSelection; // combo box for primary language selection + LLComboBox* mLanguageSelection_2; // combo box for secondary language selection + S32 mLastDisplayedX, mLastDisplayedY; // stored position of last floater so the new one opens up in the same place + std::string mDelim; // the OS-specific delimiter character (/ or \) (*TODO: this shouldn't be needed, right?) + + std::string mSavedEditorPath; // stored editor path so closing this floater doesn't reset it + std::string mSavedEditorArgs; // stored editor args so closing this floater doesn't reset it + std::string mSavedDiffPath; // stored diff file path so closing this floater doesn't reset it + + // Internal functionality + static void popupAndPrintWarning(const std::string& warning); // pop up a warning + std::string getLocalizedDirectory(); // build and return the path to the XUI directory for the currently-selected localization + void scanDiffFile(LLXmlTreeNode* file_node); // scan a given XML node for diff entries and highlight them in its associated file + void highlightChangedElements(); // look up the list of elements to highlight and highlight them in the current floater + void highlightChangedFiles(); // look up the list of changed files to highlight and highlight them in the scroll list + void findOverlapsInChildren(LLView* parent); // fill the map below with element overlap information + static bool overlapIgnorable(LLView* viewp); // check it the element can be ignored for overlap/localization purposes + + // check if two elements overlap using their rectangles + // used instead of llrect functions because by adding a few pixels of leeway I can cut down drastically on the number of overlaps + bool elementOverlap(LLView* view1, LLView* view2); + + // Button/drop-down action listeners (self explanatory) + void onClickDisplayFloater(S32 id); + void onClickSaveFloater(S32 id); + void onClickSaveAll(S32 id); + void onClickEditFloater(); + void onClickBrowseForEditor(); + void getExecutablePath(const std::vector& filenames); + void onClickBrowseForDiffs(); + void getDiffsFilePath(const std::vector& filenames); + void onClickToggleDiffHighlighting(); + void onClickToggleOverlapping(); + void onClickCloseDisplayedFloater(S32 id); + void onLanguageComboSelect(LLUICtrl* ctrl); + void onClickExportSchema(); + void onClickShowRectangles(const LLSD& data); +}; + +//---------------------------------------------------------------------------- +// Local class declarations +// Reset object to ensure that when we change the current language setting for preview purposes, +// it automatically is reset. Constructed on the stack at the start of the method; the reset +// occurs as it falls out of scope at the end of the method. See llfloateruipreview.cpp for usage. +class LLLocalizationResetForcer +{ +public: + LLLocalizationResetForcer(LLFloaterUIPreview* floater, S32 ID); + virtual ~LLLocalizationResetForcer(); + +private: + std::string mSavedLocalization; // the localization before we change it +}; + +// Implementation of live file +// When a floater is being previewed, any saved changes to its corresponding +// file cause the previewed floater to be reloaded +class LLGUIPreviewLiveFile : public LLLiveFile +{ +public: + LLGUIPreviewLiveFile(std::string path, std::string name, LLFloaterUIPreview* parent); + virtual ~LLGUIPreviewLiveFile(); + LLFloaterUIPreview* mParent; + LLFadeEventTimer* mFadeTimer; // timer for fade-to-yellow-and-back effect to warn that file has been reloaded + bool mFirstFade; // setting this avoids showing the fade reload warning on first load + std::string mFileName; +protected: + bool loadFile(); +}; + +// Implementation of graphical fade in/out (on timer) for when XUI files are updated +class LLFadeEventTimer : public LLEventTimer +{ +public: + LLFadeEventTimer(F32 refresh, LLGUIPreviewLiveFile* parent); + bool tick(); + LLGUIPreviewLiveFile* mParent; +private: + bool mFadingOut; // fades in then out; this is toggled in between + LLColor4 mOriginalColor; // original color; color is reset to this after fade is coimplete +}; + +// Implementation of previewed floater +// Used to override draw and mouse handler +class LLPreviewedFloater : public LLFloater +{ +public: + LLPreviewedFloater(LLFloaterUIPreview* floater, const Params& params) + : LLFloater(LLSD(), params), + mFloaterUIPreview(floater) + { + } + + virtual void draw(); + bool handleRightMouseDown(S32 x, S32 y, MASK mask); + bool handleToolTip(S32 x, S32 y, MASK mask); + bool selectElement(LLView* parent, int x, int y, int depth); // select element to display its overlappers + + LLFloaterUIPreview* mFloaterUIPreview; + + // draw widget outlines + static bool sShowRectangles; +}; + +bool LLPreviewedFloater::sShowRectangles = false; + +//---------------------------------------------------------------------------- + +// Localization reset forcer -- ensures that when localization is temporarily changed for previewed floater, it is reset +// Changes are made here +LLLocalizationResetForcer::LLLocalizationResetForcer(LLFloaterUIPreview* floater, S32 ID) +{ + mSavedLocalization = LLUI::getInstance()->mSettingGroups["config"]->getString("Language"); // save current localization setting + LLUI::getInstance()->mSettingGroups["config"]->setString("Language", floater->getLocStr(ID));// hack language to be the one we want to preview floaters in + // forcibly reset XUI paths with this new language + gDirUtilp->setSkinFolder(gDirUtilp->getSkinFolder(), floater->getLocStr(ID)); +} + +// Actually reset in destructor +// Changes are reversed here +LLLocalizationResetForcer::~LLLocalizationResetForcer() +{ + LLUI::getInstance()->mSettingGroups["config"]->setString("Language", mSavedLocalization); // reset language to what it was before we changed it + // forcibly reset XUI paths with this new language + gDirUtilp->setSkinFolder(gDirUtilp->getSkinFolder(), mSavedLocalization); +} + +// Live file constructor +// Needs full path for LLLiveFile but needs just file name for this code, hence the reduntant arguments; easier than separating later +LLGUIPreviewLiveFile::LLGUIPreviewLiveFile(std::string path, std::string name, LLFloaterUIPreview* parent) + : mFileName(name), + mParent(parent), + mFirstFade(true), + mFadeTimer(NULL), + LLLiveFile(path, 1.0) +{} + +LLGUIPreviewLiveFile::~LLGUIPreviewLiveFile() +{ + mParent->mLiveFile = NULL; + if(mFadeTimer) + { + mFadeTimer->mParent = NULL; + // deletes itself; see lltimer.cpp + } +} + +// Live file load +bool LLGUIPreviewLiveFile::loadFile() +{ + mParent->displayFloater(false,1); // redisplay the floater + if(mFirstFade) // only fade if it wasn't just clicked on; can't use "clicked" bool below because of an oddity with setting LLLiveFile initial state + { + mFirstFade = false; + } + else + { + if(mFadeTimer) + { + mFadeTimer->mParent = NULL; + } + mFadeTimer = new LLFadeEventTimer(0.05f,this); + } + return true; +} + +// Initialize fade event timer +LLFadeEventTimer::LLFadeEventTimer(F32 refresh, LLGUIPreviewLiveFile* parent) + : mParent(parent), + mFadingOut(true), + LLEventTimer(refresh) +{ + mOriginalColor = mParent->mParent->mDisplayedFloater->getBackgroundColor(); +} + +// Single tick of fade event timer: increment the color +bool LLFadeEventTimer::tick() +{ + float diff = 0.04f; + if(true == mFadingOut) // set fade for in/out color direction + { + diff = -diff; + } + + if(NULL == mParent) // no more need to tick, so suicide + { + return true; + } + + // Set up colors + LLColor4 bg_color = mParent->mParent->mDisplayedFloater->getBackgroundColor(); + LLSD colors = bg_color.getValue(); + LLSD colors_old = colors; + + // Tick colors + colors[0] = colors[0].asReal() - diff; if(colors[0].asReal() < mOriginalColor.getValue()[0].asReal()) { colors[0] = colors_old[0]; } + colors[1] = colors[1].asReal() - diff; if(colors[1].asReal() < mOriginalColor.getValue()[1].asReal()) { colors[1] = colors_old[1]; } + colors[2] = colors[2].asReal() + diff; if(colors[2].asReal() > mOriginalColor.getValue()[2].asReal()) { colors[2] = colors_old[2]; } + + // Clamp and set colors + bg_color.setValue(colors); + bg_color.clamp(); // make sure we didn't exceed [0,1] + mParent->mParent->mDisplayedFloater->setBackgroundColor(bg_color); + + if(bg_color[2] <= 0.0f) // end of fade out, start fading in + { + mFadingOut = false; + } + + return false; +} + +// Constructor +LLFloaterUIPreview::LLFloaterUIPreview(const LLSD& key) + : LLFloater(key), + mDisplayedFloater(NULL), + mDisplayedFloater_2(NULL), + mLiveFile(NULL), + // sHighlightingDiffs(false), + mHighlightingOverlaps(false), + mLastDisplayedX(0), + mLastDisplayedY(0) +{ +} + +// Destructor +LLFloaterUIPreview::~LLFloaterUIPreview() +{ + // spawned floaters are deleted automatically, so we don't need to delete them here + + // save contents of textfields so it can be restored later if the floater is created again this session + mSavedEditorPath = mEditorPathTextBox->getText(); + mSavedEditorArgs = mEditorArgsTextBox->getText(); + mSavedDiffPath = mDiffPathTextBox->getText(); + + // delete live file if it exists + if(mLiveFile) + { + delete mLiveFile; + mLiveFile = NULL; + } +} + +// Perform post-build setup (defined in superclass) +bool LLFloaterUIPreview::postBuild() +{ + LLPanel* main_panel_tmp = getChild("main_panel"); // get a pointer to the main panel in order to... + mFileList = main_panel_tmp->getChild("name_list"); // save pointer to file list + // Double-click opens the floater, for convenience + mFileList->setDoubleClickCallback(boost::bind(&LLFloaterUIPreview::onClickDisplayFloater, this, PRIMARY_FLOATER)); + + setDefaultBtn("display_floater"); + // get pointers to buttons and link to callbacks + mLanguageSelection = main_panel_tmp->getChild("language_select_combo"); + mLanguageSelection->setCommitCallback(boost::bind(&LLFloaterUIPreview::onLanguageComboSelect, this, mLanguageSelection)); + mLanguageSelection_2 = main_panel_tmp->getChild("language_select_combo_2"); + mLanguageSelection_2->setCommitCallback(boost::bind(&LLFloaterUIPreview::onLanguageComboSelect, this, mLanguageSelection)); + LLPanel* editor_panel_tmp = main_panel_tmp->getChild("editor_panel"); + mDisplayFloaterBtn = main_panel_tmp->getChild("display_floater"); + mDisplayFloaterBtn->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickDisplayFloater, this, PRIMARY_FLOATER)); + mDisplayFloaterBtn_2 = main_panel_tmp->getChild("display_floater_2"); + mDisplayFloaterBtn_2->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickDisplayFloater, this, SECONDARY_FLOATER)); + mToggleOverlapButton = main_panel_tmp->getChild("toggle_overlap_panel"); + mToggleOverlapButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickToggleOverlapping, this)); + mCloseOtherButton = main_panel_tmp->getChild("close_displayed_floater"); + mCloseOtherButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickCloseDisplayedFloater, this, PRIMARY_FLOATER)); + mCloseOtherButton_2 = main_panel_tmp->getChild("close_displayed_floater_2"); + mCloseOtherButton_2->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickCloseDisplayedFloater, this, SECONDARY_FLOATER)); + mEditFloaterBtn = main_panel_tmp->getChild("edit_floater"); + mEditFloaterBtn->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickEditFloater, this)); + mExecutableBrowseButton = editor_panel_tmp->getChild("browse_for_executable"); + LLPanel* vlt_panel_tmp = main_panel_tmp->getChild("vlt_panel"); + mExecutableBrowseButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickBrowseForEditor, this)); + mDiffBrowseButton = vlt_panel_tmp->getChild("browse_for_vlt_diffs"); + mDiffBrowseButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickBrowseForDiffs, this)); + mToggleHighlightButton = vlt_panel_tmp->getChild("toggle_vlt_diff_highlight"); + mToggleHighlightButton->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickToggleDiffHighlighting, this)); + main_panel_tmp->getChild("save_floater")->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickSaveFloater, this, PRIMARY_FLOATER)); + main_panel_tmp->getChild("save_all_floaters")->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickSaveAll, this, PRIMARY_FLOATER)); + + getChild("export_schema")->setClickedCallback(boost::bind(&LLFloaterUIPreview::onClickExportSchema, this)); + getChild("show_rectangles")->setCommitCallback( + boost::bind(&LLFloaterUIPreview::onClickShowRectangles, this, _2)); + + // get pointers to text fields + mEditorPathTextBox = editor_panel_tmp->getChild("executable_path_field"); + mEditorArgsTextBox = editor_panel_tmp->getChild("executable_args_field"); + mDiffPathTextBox = vlt_panel_tmp->getChild("vlt_diff_path_field"); + + // *HACK: restored saved editor path and args to textfields + mEditorPathTextBox->setText(mSavedEditorPath); + mEditorArgsTextBox->setText(mSavedEditorArgs); + mDiffPathTextBox->setText(mSavedDiffPath); + + // Set up overlap panel + mOverlapPanel = getChild("overlap_panel"); + + getChildView("overlap_scroll")->setVisible( mHighlightingOverlaps); + + mDelim = gDirUtilp->getDirDelimiter(); // initialize delimiter to dir sep slash + + // refresh list of available languages (EN will still be default) + bool found = true; + bool found_en_us = false; + std::string language_directory; + std::string xui_dir = get_xui_dir(); // directory containing localizations -- don't forget trailing delim + mLanguageSelection->removeall(); // clear out anything temporarily in list from XML + + LLDirIterator iter(xui_dir, "*"); + while(found) // for every directory + { + if((found = iter.next(language_directory))) // get next directory + { + std::string full_path = gDirUtilp->add(xui_dir, language_directory); + if(LLFile::isfile(full_path.c_str())) // if it's not a directory, skip it + { + continue; + } + + if(strncmp("template",language_directory.c_str(),8) && -1 == language_directory.find(".")) // if it's not the template directory or a hidden directory + { + if(!strncmp("en",language_directory.c_str(),5)) // remember if we've seen en, so we can make it default + { + found_en_us = true; + } + else + { + mLanguageSelection->add(std::string(language_directory)); // add it to the language selection dropdown menu + mLanguageSelection_2->add(std::string(language_directory)); + } + } + } + } + if(found_en_us) + { + mLanguageSelection->add(std::string("en"),ADD_TOP); // make en first item if we found it + mLanguageSelection_2->add(std::string("en"),ADD_TOP); + } + else + { + std::string warning = std::string("No EN localization found; check your XUI directories!"); + popupAndPrintWarning(warning); + } + mLanguageSelection->selectFirstItem(); // select the first item + mLanguageSelection_2->selectFirstItem(); + + refreshList(); // refresh the list of available floaters + + return true; +} + +// Callback for language combo box selection: refresh current floater when you change languages +void LLFloaterUIPreview::onLanguageComboSelect(LLUICtrl* ctrl) +{ + LLComboBox* caller = dynamic_cast(ctrl); + if (!caller) + return; + if(caller->getName() == std::string("language_select_combo")) + { + if(mDisplayedFloater) + { + onClickCloseDisplayedFloater(PRIMARY_FLOATER); + displayFloater(true,1); + } + } + else + { + if(mDisplayedFloater_2) + { + onClickCloseDisplayedFloater(PRIMARY_FLOATER); + displayFloater(true,2); // *TODO: make take an arg + } + } + +} + +void LLFloaterUIPreview::onClickExportSchema() +{ + //NOTE: schema generation not complete + //gViewerWindow->setCursor(UI_CURSOR_WAIT); + //std::string template_path = gDirUtilp->getExpandedFilename(LL_PATH_DEFAULT_SKIN, "xui", "schema"); + + //typedef LLWidgetTypeRegistry::Registrar::registry_map_t::const_iterator registry_it; + //registry_it end_it = LLWidgetTypeRegistry::defaultRegistrar().endItems(); + //for(registry_it it = LLWidgetTypeRegistry::defaultRegistrar().beginItems(); + // it != end_it; + // ++it) + //{ + // std::string widget_name = it->first; + // const LLInitParam::BaseBlock& block = + // (*LLDefaultParamBlockRegistry::instance().getValue(*LLWidgetTypeRegistry::instance().getValue(widget_name)))(); + // LLXMLNodePtr root_nodep = new LLXMLNode(); + // LLRNGWriter().writeRNG(widget_name, root_nodep, block, "http://www.lindenlab.com/xui"); + + // std::string file_name(template_path + gDirUtilp->getDirDelimiter() + widget_name + ".rng"); + + // LLFILE* rng_file = LLFile::fopen(file_name.c_str(), "w"); + // { + // LLXMLNode::writeHeaderToFile(rng_file); + // const bool use_type_decorations = false; + // root_nodep->writeToFile(rng_file, std::string(), use_type_decorations); + // } + // fclose(rng_file); + //} + //gViewerWindow->setCursor(UI_CURSOR_ARROW); +} + +void LLFloaterUIPreview::onClickShowRectangles(const LLSD& data) +{ + LLPreviewedFloater::sShowRectangles = data.asBoolean(); +} + +// Close click handler -- delete my displayed floater if it exists +void LLFloaterUIPreview::onClose(bool app_quitting) +{ + if(!app_quitting && mDisplayedFloater) + { + onClickCloseDisplayedFloater(PRIMARY_FLOATER); + onClickCloseDisplayedFloater(SECONDARY_FLOATER); + delete mDisplayedFloater; + mDisplayedFloater = NULL; + delete mDisplayedFloater_2; + mDisplayedFloater_2 = NULL; + } +} + +// Error handling (to avoid code repetition) +// *TODO: this is currently unlocalized. Add to alerts/notifications.xml, someday, maybe. +void LLFloaterUIPreview::popupAndPrintWarning(const std::string& warning) +{ + LL_WARNS() << warning << LL_ENDL; + LLSD args; + args["MESSAGE"] = warning; + LLNotificationsUtil::add("GenericAlert", args); +} + +// Get localization string from drop-down menu +std::string LLFloaterUIPreview::getLocStr(S32 ID) +{ + if(ID == 1) + { + return mLanguageSelection->getSelectedItemLabel(0); + } + else + { + return mLanguageSelection_2->getSelectedItemLabel(0); + } +} + +// Get localized directory (build path from data directory to XUI files, substituting localization string in for language) +std::string LLFloaterUIPreview::getLocalizedDirectory() +{ + return get_xui_dir() + (getLocStr(1)) + mDelim; // e.g. "C:/Code/guipreview/indra/newview/skins/xui/en/"; +} + +// Refresh the list of floaters by doing a directory traverse for XML XUI floater files +// Could be used to grab any specific language's list of compatible floaters, but currently it's just used to get all of them +void LLFloaterUIPreview::refreshList() +{ + // Note: the mask doesn't seem to accept regular expressions, so there need to be two directory searches here + mFileList->clearRows(); // empty list + std::string name; + bool found = true; + + LLDirIterator floater_iter(getLocalizedDirectory(), "floater_*.xml"); + while(found) // for every floater file that matches the pattern + { + if((found = floater_iter.next(name))) // get next file matching pattern + { + addFloaterEntry(name.c_str()); // and add it to the list (file name only; localization code takes care of rest of path) + } + } + found = true; + + LLDirIterator inspect_iter(getLocalizedDirectory(), "inspect_*.xml"); + while(found) // for every inspector file that matches the pattern + { + if((found = inspect_iter.next(name))) // get next file matching pattern + { + addFloaterEntry(name.c_str()); // and add it to the list (file name only; localization code takes care of rest of path) + } + } + found = true; + + LLDirIterator menu_iter(getLocalizedDirectory(), "menu_*.xml"); + while(found) // for every menu file that matches the pattern + { + if((found = menu_iter.next(name))) // get next file matching pattern + { + addFloaterEntry(name.c_str()); // and add it to the list (file name only; localization code takes care of rest of path) + } + } + found = true; + + LLDirIterator panel_iter(getLocalizedDirectory(), "panel_*.xml"); + while(found) // for every panel file that matches the pattern + { + if((found = panel_iter.next(name))) // get next file matching pattern + { + addFloaterEntry(name.c_str()); // and add it to the list (file name only; localization code takes care of rest of path) + } + } + found = true; + + LLDirIterator sidepanel_iter(getLocalizedDirectory(), "sidepanel_*.xml"); + while(found) // for every sidepanel file that matches the pattern + { + if((found = sidepanel_iter.next(name))) // get next file matching pattern + { + addFloaterEntry(name.c_str()); // and add it to the list (file name only; localization code takes care of rest of path) + } + } + + if(!mFileList->isEmpty()) // if there were any matching files, just select the first one (so we don't have to worry about disabling buttons when no entry is selected) + { + mFileList->selectFirstItem(); + } +} + +// Add a single entry to the list of available floaters +// Note: no deduplification (shouldn't be necessary) +void LLFloaterUIPreview::addFloaterEntry(const std::string& path) +{ + LLUUID* entry_id = new LLUUID(); // create a new UUID + entry_id->generate(path); + const LLUUID& entry_id_ref = *entry_id; // get a reference to the UUID for the LLSD block + + // fill LLSD column entry: initialize row/col structure + LLSD row; + row["id"] = entry_id_ref; + LLSD& columns = row["columns"]; + + // Get name of floater: + LLXmlTree xml_tree; + std::string full_path = getLocalizedDirectory() + path; // get full path + bool success = xml_tree.parseFile(full_path.c_str(), true); // parse xml + std::string entry_name; + std::string entry_title; + if(success) + { + // get root (or error handle) + LLXmlTreeNode* root_floater = xml_tree.getRoot(); + if (!root_floater) + { + std::string warning = std::string("No root node found in XUI file: ") + path; + popupAndPrintWarning(warning); + return; + } + + // get name + root_floater->getAttributeString("name",entry_name); + if(std::string("") == entry_name) + { + entry_name = "Error: unable to load " + std::string(path); // set to error state if load fails + } + + // get title + root_floater->getAttributeString("title",entry_title); // some don't have a title, and some have title = "(unknown)", so just leave it blank if it fails + } + else + { + std::string warning = std::string("Unable to parse XUI file: ") + path; // error handling + popupAndPrintWarning(warning); + if(mLiveFile) + { + delete mLiveFile; + mLiveFile = NULL; + } + return; + } + + // Fill floater title column + columns[0]["column"] = "title_column"; + columns[0]["type"] = "text"; + columns[0]["value"] = entry_title; + + // Fill floater path column + columns[1]["column"] = "file_column"; + columns[1]["type"] = "text"; + columns[1]["value"] = std::string(path); + + // Fill floater name column + columns[2]["column"] = "top_level_node_column"; + columns[2]["type"] = "text"; + columns[2]["value"] = entry_name; + + mFileList->addElement(row); // actually add to list +} + +// Respond to button click to display/refresh currently-selected floater +void LLFloaterUIPreview::onClickDisplayFloater(S32 caller_id) +{ + displayFloater(true, caller_id); +} + +// Saves the current floater/panel +void LLFloaterUIPreview::onClickSaveFloater(S32 caller_id) +{ + displayFloater(true, caller_id); + popupAndPrintWarning("Save-floater functionality removed, use XML schema to clean up XUI files"); +} + +// Saves all floater/panels +void LLFloaterUIPreview::onClickSaveAll(S32 caller_id) +{ + int listSize = mFileList->getItemCount(); + + for (int index = 0; index < listSize; index++) + { + mFileList->selectNthItem(index); + displayFloater(true, caller_id); + } + popupAndPrintWarning("Save-floater functionality removed, use XML schema to clean up XUI files"); +} + +// Actually display the floater +// Only set up a new live file if this came from a click (at which point there should be no existing live file), rather than from the live file's update itself; +// otherwise, we get an infinite loop as the live file keeps recreating itself. That means this function is generally called twice. +void LLFloaterUIPreview::displayFloater(bool click, S32 ID) +{ + // Convince UI that we're in a different language (the one selected on the drop-down menu) + LLLocalizationResetForcer reset_forcer(this, ID); // save old language in reset forcer object (to be reset upon destruction when it falls out of scope) + + LLPreviewedFloater** floaterp = (ID == 1 ? &(mDisplayedFloater) : &(mDisplayedFloater_2)); + if(ID == 1) + { + bool floater_already_open = mDisplayedFloater != NULL; + if(floater_already_open) // if we are already displaying a floater + { + mLastDisplayedX = mDisplayedFloater->calcScreenRect().mLeft; // save floater's last known position to put the new one there + mLastDisplayedY = mDisplayedFloater->calcScreenRect().mBottom; + delete mDisplayedFloater; // delete it (this closes it too) + mDisplayedFloater = NULL; // and reset the pointer + } + } + else + { + if(mDisplayedFloater_2 != NULL) + { + delete mDisplayedFloater_2; + mDisplayedFloater_2 = NULL; + } + } + + std::string path = mFileList->getSelectedItemLabel(1); // get the path of the currently-selected floater + if(std::string("") == path) // if no item is selected + { + return; // ignore click (this can only happen with empty list; otherwise an item is always selected) + } + + LLFloater::Params p(LLFloater::getDefaultParams()); + p.min_height=p.header_height; + p.min_width=10; + + *floaterp = new LLPreviewedFloater(this, p); + + if(!strncmp(path.c_str(),"floater_",8) + || !strncmp(path.c_str(), "inspect_", 8)) // if it's a floater + { + (*floaterp)->buildFromFile(path); // just build it + (*floaterp)->openFloater((*floaterp)->getKey()); + (*floaterp)->setCanResize((*floaterp)->isResizable()); + } + else if (!strncmp(path.c_str(),"menu_",5)) // if it's a menu + { + // former 'save' processing excised + } + else // if it is a panel... + { + (*floaterp)->setCanResize(true); + + const LLFloater::Params& floater_params = LLFloater::getDefaultParams(); + S32 floater_header_size = floater_params.header_height; + + LLPanel::Params panel_params; + LLPanel* panel = LLUICtrlFactory::create(panel_params); // create a new panel + + panel->buildFromFile(path); // build it + panel->setOrigin(2,2); // reset its origin point so it's not offset by -left or other XUI attributes + (*floaterp)->setTitle(path); // use the file name as its title, since panels have no guaranteed meaningful name attribute + panel->setUseBoundingRect(true); // enable the use of its outer bounding rect (normally disabled because it's O(n) on the number of sub-elements) + panel->updateBoundingRect(); // update bounding rect + LLRect bounding_rect = panel->getBoundingRect(); // get the bounding rect + LLRect new_rect = panel->getRect(); // get the panel's rect + new_rect.unionWith(bounding_rect); // union them to make sure we get the biggest one possible + LLRect floater_rect = new_rect; + floater_rect.stretch(4, 4); + (*floaterp)->reshape(floater_rect.getWidth(), floater_rect.getHeight() + floater_header_size); // reshape floater to match the union rect's dimensions + panel->reshape(new_rect.getWidth(), new_rect.getHeight()); // reshape panel to match the union rect's dimensions as well (both are needed) + (*floaterp)->addChild(panel); // add panel as child + (*floaterp)->openFloater(); // open floater (needed?) + } + + if(ID == 1) + { + (*floaterp)->setOrigin(mLastDisplayedX, mLastDisplayedY); + } + + // *HACK: Remove ability to close it; if you close it, its destructor gets called, but we don't know it's null and try to delete it again, + // resulting in a double free + (*floaterp)->setCanClose(false); + + if(ID == 1) + { + mCloseOtherButton->setEnabled(true); // enable my floater's close button + } + else + { + mCloseOtherButton_2->setEnabled(true); + } + + // Add localization to title so user knows whether it's localized or defaulted to en + std::string full_path = getLocalizedDirectory() + path; + std::string floater_lang = "EN"; + llstat dummy; + if(!LLFile::stat(full_path.c_str(), &dummy)) // if the file does not exist + { + floater_lang = getLocStr(ID); + } + std::string new_title = (*floaterp)->getTitle() + std::string(" [") + floater_lang + + (ID == 1 ? " - Primary" : " - Secondary") + std::string("]"); + (*floaterp)->setTitle(new_title); + + (*floaterp)->center(); + addDependentFloater(*floaterp); + + if(click && ID == 1) + { + // set up live file to track it + if(mLiveFile) + { + delete mLiveFile; + mLiveFile = NULL; + } + mLiveFile = new LLGUIPreviewLiveFile(std::string(full_path.c_str()),std::string(path.c_str()),this); + mLiveFile->checkAndReload(); + mLiveFile->addToEventTimer(); + } + + if(ID == 1) + { + mToggleOverlapButton->setEnabled(true); + } + + if(LLView::sHighlightingDiffs && click && ID == 1) + { + highlightChangedElements(); + } + + if(ID == 1) + { + mOverlapPanel->mOverlapMap.clear(); + LLView::sPreviewClickedElement = NULL; // stop overlapping elements from drawing + mOverlapPanel->mLastClickedElement = NULL; + findOverlapsInChildren((LLView*)mDisplayedFloater); + + // highlight and enable them + if(mHighlightingOverlaps) + { + for(LLOverlapPanel::OverlapMap::iterator iter = mOverlapPanel->mOverlapMap.begin(); iter != mOverlapPanel->mOverlapMap.end(); ++iter) + { + LLView* viewp = iter->first; + LLView::sPreviewHighlightedElements.insert(viewp); + } + } + else if(LLView::sHighlightingDiffs) + { + highlightChangedElements(); + } + } + + // NOTE: language is reset here automatically when the reset forcer object falls out of scope (see header for details) +} + +// Respond to button click to edit currently-selected floater +void LLFloaterUIPreview::onClickEditFloater() +{ + // Determine file to edit. + std::string file_path; + { + std::string file_name = mFileList->getSelectedItemLabel(1); // get the file name of the currently-selected floater + if (file_name.empty()) // if no item is selected + { + LL_WARNS() << "No file selected" << LL_ENDL; + return; // ignore click + } + file_path = getLocalizedDirectory() + file_name; + + // stat file to see if it exists (some localized versions may not have it there are no diffs, and then we try to open an nonexistent file) + llstat dummy; + if(LLFile::stat(file_path.c_str(), &dummy)) // if the file does not exist + { + popupAndPrintWarning("No file for this floater exists in the selected localization. Opening the EN version instead."); + file_path = get_xui_dir() + mDelim + "en" + mDelim + file_name; // open the en version instead, by default + } + } + + // Set the editor command. + std::string cmd_override; + { + std::string bin = mEditorPathTextBox->getText(); + if (!bin.empty()) + { + // surround command with double quotes for the case if the path contains spaces + if (bin.find("\"") == std::string::npos) + { + bin = "\"" + bin + "\""; + } + + std::string args = mEditorArgsTextBox->getText(); + cmd_override = bin + " " + args; + } + } + + LLExternalEditor::EErrorCode status = mExternalEditor.setCommand("LL_XUI_EDITOR", cmd_override); + if (status != LLExternalEditor::EC_SUCCESS) + { + std::string warning; + + if (status == LLExternalEditor::EC_NOT_SPECIFIED) // Use custom message for this error. + { + warning = getString("ExternalEditorNotSet"); + } + else + { + warning = LLExternalEditor::getErrorMessage(status); + } + + popupAndPrintWarning(warning); + return; + } + + // Run the editor. + if (mExternalEditor.run(file_path) != LLExternalEditor::EC_SUCCESS) + { + popupAndPrintWarning(LLExternalEditor::getErrorMessage(status)); + return; + } +} + +// Respond to button click to browse for an executable with which to edit XML files +void LLFloaterUIPreview::onClickBrowseForEditor() +{ + // Let the user choose an executable through the file picker dialog box + LLFilePickerReplyThread::startPicker(boost::bind(&LLFloaterUIPreview::getExecutablePath, this, _1), LLFilePicker::FFLOAD_EXE, false); +} + +void LLFloaterUIPreview::getExecutablePath(const std::vector& filenames) +{ + // put the selected path into text field + const std::string chosen_path = filenames[0]; + std::string executable_path = chosen_path; +#if LL_DARWIN + // on Mac, if it's an application bundle, figure out the actual path from the Info.plist file + CFStringRef path_cfstr = CFStringCreateWithCString(kCFAllocatorDefault, chosen_path.c_str(), kCFStringEncodingMacRoman); // get path as a CFStringRef + CFURLRef path_url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path_cfstr, kCFURLPOSIXPathStyle, true); // turn it into a CFURLRef + CFBundleRef chosen_bundle = CFBundleCreate(kCFAllocatorDefault, path_url); // get a handle for the bundle + if(NULL != chosen_bundle) + { + CFDictionaryRef bundleInfoDict = CFBundleGetInfoDictionary(chosen_bundle); // get the bundle's dictionary + if(NULL != bundleInfoDict) + { + CFStringRef executable_cfstr = (CFStringRef)CFDictionaryGetValue(bundleInfoDict, CFSTR("CFBundleExecutable")); // get the name of the actual executable (e.g. TextEdit or firefox-bin) + int max_file_length = 256; // (max file name length is 255 in OSX) + char executable_buf[max_file_length]; + if(CFStringGetCString(executable_cfstr, executable_buf, max_file_length, kCFStringEncodingMacRoman)) // convert CFStringRef to char* + { + executable_path += std::string("/Contents/MacOS/") + std::string(executable_buf); // append path to executable directory and then executable name to exec path + } + else + { + std::string warning = "Unable to get CString from CFString for executable path"; + popupAndPrintWarning(warning); + } + } + else + { + std::string warning = "Unable to get bundle info dictionary from application bundle"; + popupAndPrintWarning(warning); + } + } + else + { + if(-1 != executable_path.find(".app")) // only warn if this path actually had ".app" in it, i.e. it probably just wasn'nt an app bundle and that's okay + { + std::string warning = std::string("Unable to get bundle from path \"") + chosen_path + std::string("\""); + popupAndPrintWarning(warning); + } + } + +#endif + mEditorPathTextBox->setText(std::string(executable_path)); // copy the path to the executable to the textfield for display and later fetching +} + +// Respond to button click to browse for a VLT-generated diffs file +void LLFloaterUIPreview::onClickBrowseForDiffs() +{ + // create load dialog box + LLFilePickerReplyThread::startPicker(boost::bind(&LLFloaterUIPreview::getDiffsFilePath, this, _1), LLFilePicker::FFLOAD_XML, false); +} + +void LLFloaterUIPreview::getDiffsFilePath(const std::vector& filenames) +{ + // put the selected path into text field + const std::string chosen_path = filenames[0]; + mDiffPathTextBox->setText(std::string(chosen_path)); // copy the path to the executable to the textfield for display and later fetching + if(LLView::sHighlightingDiffs) // if we're already highlighting, toggle off and then on so we get the data from the new file + { + onClickToggleDiffHighlighting(); + onClickToggleDiffHighlighting(); + } +} + +void LLFloaterUIPreview::onClickToggleDiffHighlighting() +{ + if(mHighlightingOverlaps) + { + onClickToggleOverlapping(); + mToggleOverlapButton->toggleState(); + } + + LLView::sPreviewHighlightedElements.clear(); // clear lists first + mDiffsMap.clear(); + mFileList->clearHighlightedItems(); + + if(LLView::sHighlightingDiffs) // Turning highlighting off + { + LLView::sHighlightingDiffs = !sHighlightingDiffs; + return; + } + else // Turning highlighting on + { + // Get the file and make sure it exists + std::string path_in_textfield = mDiffPathTextBox->getText(); // get file path + bool error = false; + + if(std::string("") == path_in_textfield) // check for blank file + { + std::string warning = "Unable to highlight differences because no file was provided; fill in the relevant text field"; + popupAndPrintWarning(warning); + error = true; + } + + llstat dummy; + if(LLFile::stat(path_in_textfield.c_str(), &dummy) && !error) // check if the file exists (empty check is reduntant but useful for the informative error message) + { + std::string warning = std::string("Unable to highlight differences because an invalid path to a difference file was provided:\"") + path_in_textfield + "\""; + popupAndPrintWarning(warning); + error = true; + } + + // Build a list of changed elements as given by the XML + std::list changed_element_names; + LLXmlTree xml_tree; + bool success = xml_tree.parseFile(path_in_textfield.c_str(), true); + + if(success && !error) + { + LLXmlTreeNode* root_floater = xml_tree.getRoot(); + if(!strncmp("XuiDelta",root_floater->getName().c_str(),9)) + { + for (LLXmlTreeNode* child = root_floater->getFirstChild(); // get the first child first, then below get the next one; otherwise the iterator is invalid (bug or feature in XML code?) + child != NULL; + child = root_floater->getNextChild()) // get child for next iteration + { + if(!strncmp("file",child->getName().c_str(),5)) + { + scanDiffFile(child); + } + else if(!strncmp("error",child->getName().c_str(),6)) + { + std::string error_file, error_message; + child->getAttributeString("filename",error_file); + child->getAttributeString("message",error_message); + if(mDiffsMap.find(error_file) != mDiffsMap.end()) + { + mDiffsMap.insert(std::make_pair(error_file,std::make_pair(StringListPtr(new StringList), StringListPtr(new StringList)))); + } + mDiffsMap[error_file].second->push_back(error_message); + } + else + { + std::string warning = std::string("Child was neither a file or an error, but rather the following:\"") + std::string(child->getName()) + "\""; + popupAndPrintWarning(warning); + error = true; + break; + } + } + } + else + { + std::string warning = std::string("Root node not named XuiDelta:\"") + path_in_textfield + "\""; + popupAndPrintWarning(warning); + error = true; + } + } + else if(!error) + { + std::string warning = std::string("Unable to create tree from XML:\"") + path_in_textfield + "\""; + popupAndPrintWarning(warning); + error = true; + } + + if(error) // if we encountered an error, reset the button to off + { + mToggleHighlightButton->setToggleState(false); + } + else // only toggle if we didn't encounter an error + { + LLView::sHighlightingDiffs = !sHighlightingDiffs; + highlightChangedElements(); // *TODO: this is extraneous, right? + highlightChangedFiles(); // *TODO: this is extraneous, right? + } + } +} + +void LLFloaterUIPreview::scanDiffFile(LLXmlTreeNode* file_node) +{ + // Get file name + std::string file_name; + file_node->getAttributeString("name",file_name); + if(std::string("") == file_name) + { + std::string warning = std::string("Empty file name encountered in differences:\"") + file_name + "\""; + popupAndPrintWarning(warning); + return; + } + + // Get a list of changed elements + // Get the first child first, then below get the next one; otherwise the iterator is invalid (bug or feature in XML code?) + for (LLXmlTreeNode* child = file_node->getFirstChild(); child != NULL; child = file_node->getNextChild()) + { + if(!strncmp("delta",child->getName().c_str(),6)) + { + std::string id; + child->getAttributeString("id",id); + if(mDiffsMap.find(file_name) == mDiffsMap.end()) + { + mDiffsMap.insert(std::make_pair(file_name,std::make_pair(StringListPtr(new StringList), StringListPtr(new StringList)))); + } + mDiffsMap[file_name].first->push_back(std::string(id.c_str())); + } + else + { + std::string warning = std::string("Child of file was not a delta, but rather the following:\"") + std::string(child->getName()) + "\""; + popupAndPrintWarning(warning); + return; + } + } +} + +void LLFloaterUIPreview::highlightChangedElements() +{ + if(NULL == mLiveFile) + { + return; + } + + // Process differences first (we want their warnings to be shown underneath other warnings) + StringListPtr changed_element_paths; + DiffMap::iterator iterExists = mDiffsMap.find(mLiveFile->mFileName); + if(iterExists != mDiffsMap.end()) + { + changed_element_paths = mDiffsMap[mLiveFile->mFileName].first; // retrieve list of changed element paths from map + } + + for(std::list::iterator iter = changed_element_paths->begin(); iter != changed_element_paths->end(); ++iter) // for every changed element path + { + LLView* element = mDisplayedFloater; + if(!strncmp(iter->c_str(),".",1)) // if it's the root floater itself + { + continue; + } + + // Split element hierarchy path on period (*HACK: it's possible that the element name will have a period in it, in which case this won't work. See https://wiki.lindenlab.com/wiki/Viewer_Localization_Tool_Documentation.) + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("."); + tokenizer tokens(*iter, sep); + tokenizer::iterator token_iter; + bool failed = false; + for(token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) + { + element = element->findChild(*token_iter,false); // try to find element: don't recur, and don't create if missing + + // if we still didn't find it... + if(NULL == element) + { + LL_INFOS() << "Unable to find element in XuiDelta file named \"" << *iter << "\" in file \"" << mLiveFile->mFileName << + "\". The element may no longer exist, the path may be incorrect, or it may not be a non-displayable element (not an LLView) such as a \"string\" type." << LL_ENDL; + failed = true; + break; + } + } + + if(!failed) + { + // Now that we have a pointer to the actual element, add it to the list of elements to be highlighted + std::set::iterator iter2 = std::find(LLView::sPreviewHighlightedElements.begin(), LLView::sPreviewHighlightedElements.end(), element); + if(iter2 == LLView::sPreviewHighlightedElements.end()) + { + LLView::sPreviewHighlightedElements.insert(element); + } + } + } + + // Process errors second, so their warnings show up on top of other warnings + StringListPtr error_list; + if(iterExists != mDiffsMap.end()) + { + error_list = mDiffsMap[mLiveFile->mFileName].second; + } + for(std::list::iterator iter = error_list->begin(); iter != error_list->end(); ++iter) // for every changed element path + { + std::string warning = std::string("Error listed among differences. Filename: \"") + mLiveFile->mFileName + "\". Message: \"" + *iter + "\""; + popupAndPrintWarning(warning); + } +} + +void LLFloaterUIPreview::highlightChangedFiles() +{ + for(DiffMap::iterator iter = mDiffsMap.begin(); iter != mDiffsMap.end(); ++iter) // for every file listed in diffs + { + LLScrollListItem* item = mFileList->getItemByLabel(std::string(iter->first), false, 1); + if(item) + { + item->setHighlighted(true); + } + } +} + +// Respond to button click to browse for an executable with which to edit XML files +void LLFloaterUIPreview::onClickCloseDisplayedFloater(S32 caller_id) +{ + if(caller_id == PRIMARY_FLOATER) + { + mCloseOtherButton->setEnabled(false); + mToggleOverlapButton->setEnabled(false); + + if(mDisplayedFloater) + { + mLastDisplayedX = mDisplayedFloater->calcScreenRect().mLeft; + mLastDisplayedY = mDisplayedFloater->calcScreenRect().mBottom; + delete mDisplayedFloater; + mDisplayedFloater = NULL; + } + + if(mLiveFile) + { + delete mLiveFile; + mLiveFile = NULL; + } + + if(mToggleOverlapButton->getToggleState()) + { + mToggleOverlapButton->toggleState(); + onClickToggleOverlapping(); + } + + LLView::sPreviewClickedElement = NULL; // stop overlapping elements panel from drawing + mOverlapPanel->mLastClickedElement = NULL; + } + else + { + mCloseOtherButton_2->setEnabled(false); + delete mDisplayedFloater_2; + mDisplayedFloater_2 = NULL; + } + +} + +void append_view_tooltip(LLView* tooltip_view, std::string *tooltip_msg) +{ + LLRect rect = tooltip_view->getRect(); + LLRect parent_rect = tooltip_view->getParent()->getRect(); + S32 left = rect.mLeft; + // invert coordinate system for XUI top-left layout + S32 top = parent_rect.getHeight() - rect.mTop; + if (!tooltip_msg->empty()) + { + tooltip_msg->append("\n"); + } + std::string msg = llformat("%s %d, %d (%d x %d)", + tooltip_view->getName().c_str(), + left, + top, + rect.getWidth(), + rect.getHeight() ); + tooltip_msg->append( msg ); +} + +bool LLPreviewedFloater::handleToolTip(S32 x, S32 y, MASK mask) +{ + if (!sShowRectangles) + { + return LLFloater::handleToolTip(x, y, mask); + } + + S32 screen_x, screen_y; + localPointToScreen(x, y, &screen_x, &screen_y); + std::string tooltip_msg; + LLView* tooltip_view = this; + LLView::tree_iterator_t end_it = endTreeDFS(); + for (LLView::tree_iterator_t it = beginTreeDFS(); it != end_it; ++it) + { + LLView* viewp = *it; + LLRect screen_rect; + viewp->localRectToScreen(viewp->getLocalRect(), &screen_rect); + if (!(viewp->getVisible() + && screen_rect.pointInRect(screen_x, screen_y))) + { + it.skipDescendants(); + } + // only report xui names for LLUICtrls, not the various container LLViews + + else if (dynamic_cast(viewp)) + { + // if we are in a new part of the tree (not a descendent of current tooltip_view) + // then push the results for tooltip_view and start with a new potential view + // NOTE: this emulates visiting only the leaf nodes that meet our criteria + + if (tooltip_view != this + && !viewp->hasAncestor(tooltip_view)) + { + append_view_tooltip(tooltip_view, &tooltip_msg); + } + tooltip_view = viewp; + } + } + + append_view_tooltip(tooltip_view, &tooltip_msg); + + LLToolTipMgr::instance().show(LLToolTip::Params() + .message(tooltip_msg) + .max_width(400)); + return true; +} + +bool LLPreviewedFloater::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + selectElement(this,x,y,0); + return true; +} + +// *NOTE: In order to hide all of the overlapping elements of the selected element so as to see it in context, here is what you would need to do: +// -This selectElement call fills the overlap panel as normal. The element which is "selected" here is actually just an intermediate selection step; +// what you've really selected is a list of elements: the one you clicked on and everything that overlaps it. +// -The user then selects one of the elements from this list the overlap panel (click handling to the overlap panel would have to be added). +// This becomes the final selection (as opposed to the intermediate selection that was just made). +// -Everything else that is currently displayed on the overlap panel should be hidden from view in the previewed floater itself (setVisible(false)). +// -Subsequent clicks on other elements in the overlap panel (they should still be there) should make other elements the final selection. +// -On close or on the click of a new button, everything should be shown again and all selection state should be cleared. +// ~Jacob, 8/08 +bool LLPreviewedFloater::selectElement(LLView* parent, int x, int y, int depth) +{ + if(getVisible()) + { + bool handled = false; + if(LLFloaterUIPreview::containerType(parent)) + { + for(child_list_const_iter_t child_it = parent->getChildList()->begin(); child_it != parent->getChildList()->end(); ++child_it) + { + LLView* child = *child_it; + S32 local_x = x - child->getRect().mLeft; + S32 local_y = y - child->getRect().mBottom; + if (child->pointInView(local_x, local_y) && + child->getVisible() && + selectElement(child, x, y, ++depth)) + { + handled = true; + break; + } + } + } + + if(!handled) + { + LLView::sPreviewClickedElement = parent; + } + return true; + } + else + { + return false; + } +} + +void LLPreviewedFloater::draw() +{ + if(NULL != mFloaterUIPreview) + { + // Set and unset sDrawPreviewHighlights flag so as to avoid using two flags + if(mFloaterUIPreview->mHighlightingOverlaps) + { + LLView::sDrawPreviewHighlights = true; + } + + // If we're looking for truncations, draw debug rects for the displayed + // floater only. + bool old_debug_rects = LLView::sDebugRects; + bool old_show_names = LLView::sDebugRectsShowNames; + if (sShowRectangles) + { + LLView::sDebugRects = true; + LLView::sDebugRectsShowNames = false; + } + + LLFloater::draw(); + + LLView::sDebugRects = old_debug_rects; + LLView::sDebugRectsShowNames = old_show_names; + + if(mFloaterUIPreview->mHighlightingOverlaps) + { + LLView::sDrawPreviewHighlights = false; + } + } +} + +void LLFloaterUIPreview::onClickToggleOverlapping() +{ + if(LLView::sHighlightingDiffs) + { + onClickToggleDiffHighlighting(); + mToggleHighlightButton->toggleState(); + } + LLView::sPreviewHighlightedElements.clear(); // clear lists first + + S32 width, height; + getResizeLimits(&width, &height); // illegal call of non-static member function + if(mHighlightingOverlaps) + { + mHighlightingOverlaps = !mHighlightingOverlaps; + // reset list of preview highlighted elements + setRect(LLRect(getRect().mLeft,getRect().mTop,getRect().mRight - mOverlapPanel->getRect().getWidth(),getRect().mBottom)); + setResizeLimits(width - mOverlapPanel->getRect().getWidth(), height); + } + else + { + mHighlightingOverlaps = !mHighlightingOverlaps; + displayFloater(false,1); + setRect(LLRect(getRect().mLeft,getRect().mTop,getRect().mRight + mOverlapPanel->getRect().getWidth(),getRect().mBottom)); + setResizeLimits(width + mOverlapPanel->getRect().getWidth(), height); + } + getChildView("overlap_scroll")->setVisible( mHighlightingOverlaps); +} + +void LLFloaterUIPreview::findOverlapsInChildren(LLView* parent) +{ + if(parent->getChildCount() == 0 || !containerType(parent)) // if it has no children or isn't a container type, skip it + { + return; + } + + // for every child of the parent + for(child_list_const_iter_t child_it = parent->getChildList()->begin(); child_it != parent->getChildList()->end(); ++child_it) + { + LLView* child = *child_it; + if(overlapIgnorable(child)) + { + continue; + } + + // for every sibling + for(child_list_const_iter_t sibling_it = parent->getChildList()->begin(); sibling_it != parent->getChildList()->end(); ++sibling_it) // for each sibling + { + LLView* sibling = *sibling_it; + if(overlapIgnorable(sibling)) + { + continue; + } + + // if they overlap... (we don't care if they're visible or enabled -- we want to check those anyway, i.e. hidden tabs that can be later shown) + if(sibling != child && elementOverlap(child, sibling)) + { + mOverlapPanel->mOverlapMap[child].push_back(sibling); // add to the map + } + } + findOverlapsInChildren(child); // recur + } +} + +// *HACK: don't overlap with the drag handle and various other elements +// This is using dynamic casts because there is no object-oriented way to tell which elements contain localizable text. These are a few that are ignorable. +// *NOTE: If a list of elements which have localizable content were created, this function should return false if viewp's class is in that list. +bool LLFloaterUIPreview::overlapIgnorable(LLView* viewp) +{ + return NULL != dynamic_cast(viewp) || + NULL != dynamic_cast(viewp) || + NULL != dynamic_cast(viewp); +} + +// *HACK: these are the only two container types as of 8/08, per Richard +// This is using dynamic casts because there is no object-oriented way to tell which elements are containers. +bool LLFloaterUIPreview::containerType(LLView* viewp) +{ + return NULL != dynamic_cast(viewp) || NULL != dynamic_cast(viewp); +} + +// Check if two llview's rectangles overlap, with some tolerance +bool LLFloaterUIPreview::elementOverlap(LLView* view1, LLView* view2) +{ + LLSD rec1 = view1->getRect().getValue(); + LLSD rec2 = view2->getRect().getValue(); + int tolerance = 2; + return (int)rec1[0] <= (int)rec2[2] - tolerance && + (int)rec2[0] <= (int)rec1[2] - tolerance && + (int)rec1[3] <= (int)rec2[1] - tolerance && + (int)rec2[3] <= (int)rec1[1] - tolerance; +} + +void LLOverlapPanel::draw() +{ + static const std::string current_selection_text("Current selection: "); + static const std::string overlapper_text("Overlapper: "); + LLColor4 text_color = LLColor4::grey; + gGL.color4fv(text_color.mV); + + if(!LLView::sPreviewClickedElement) + { + LLUI::translate(5,getRect().getHeight()-20); // translate to top-5,left-5 + LLView::sDrawPreviewHighlights = false; + LLFontGL::getFontSansSerifSmall()->renderUTF8(current_selection_text, 0, 0, 0, text_color, + LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW); + } + else + { + OverlapMap::iterator iterExists = mOverlapMap.find(LLView::sPreviewClickedElement); + if(iterExists == mOverlapMap.end()) + { + return; + } + + std::list overlappers = mOverlapMap[LLView::sPreviewClickedElement]; + if(overlappers.size() == 0) + { + LLUI::translate(5,getRect().getHeight()-20); // translate to top-5,left-5 + LLView::sDrawPreviewHighlights = false; + std::string current_selection = std::string(current_selection_text + LLView::sPreviewClickedElement->getName() + " (no elements overlap)"); + S32 text_width = LLFontGL::getFontSansSerifSmall()->getWidth(current_selection) + 10; + LLFontGL::getFontSansSerifSmall()->renderUTF8(current_selection, 0, 0, 0, text_color, + LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW); + // widen panel enough to fit this text + LLRect rect = getRect(); + setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() < text_width ? rect.mLeft + text_width : rect.mRight,rect.mTop)); + return; + } + + // recalculate required with and height; otherwise use cached + bool need_to_recalculate_bounds = false; + if(mLastClickedElement == NULL) + { + need_to_recalculate_bounds = true; + } + + if(NULL == mLastClickedElement) + { + mLastClickedElement = LLView::sPreviewClickedElement; + } + + // recalculate bounds for scroll panel + if(need_to_recalculate_bounds || LLView::sPreviewClickedElement->getName() != mLastClickedElement->getName()) + { + // reset panel's rectangle to its default width and height (300x600) + LLRect panel_rect = getRect(); + setRect(LLRect(panel_rect.mLeft,panel_rect.mTop,panel_rect.mLeft+getRect().getWidth(),panel_rect.mTop-getRect().getHeight())); + + LLRect rect; + + // change bounds for selected element + int height_sum = mLastClickedElement->getRect().getHeight() + mSpacing + 80; + rect = getRect(); + setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() > mLastClickedElement->getRect().getWidth() + 5 ? rect.mRight : rect.mLeft + mLastClickedElement->getRect().getWidth() + 5, rect.mBottom)); + + // and widen to accomodate text if that's wider + std::string display_text = current_selection_text + LLView::sPreviewClickedElement->getName(); + S32 text_width = LLFontGL::getFontSansSerifSmall()->getWidth(display_text) + 10; + rect = getRect(); + setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() < text_width ? rect.mLeft + text_width : rect.mRight,rect.mTop)); + + std::list overlappers = mOverlapMap[LLView::sPreviewClickedElement]; + for(std::list::iterator overlap_it = overlappers.begin(); overlap_it != overlappers.end(); ++overlap_it) + { + LLView* viewp = *overlap_it; + height_sum += viewp->getRect().getHeight() + mSpacing*3; + + // widen panel's rectangle to accommodate widest overlapping element of this floater + rect = getRect(); + setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() > viewp->getRect().getWidth() + 5 ? rect.mRight : rect.mLeft + viewp->getRect().getWidth() + 5, rect.mBottom)); + + // and widen to accomodate text if that's wider + std::string display_text = overlapper_text + viewp->getName(); + S32 text_width = LLFontGL::getFontSansSerifSmall()->getWidth(display_text) + 10; + rect = getRect(); + setRect(LLRect(rect.mLeft,rect.mTop,rect.getWidth() < text_width ? rect.mLeft + text_width : rect.mRight,rect.mTop)); + } + // change panel's height to accommodate all element heights plus spacing between them + rect = getRect(); + setRect(LLRect(rect.mLeft,rect.mTop,rect.mRight,rect.mTop-height_sum)); + } + + LLUI::translate(5,getRect().getHeight()-10); // translate to top left + LLView::sDrawPreviewHighlights = false; + + // draw currently-selected element at top of overlappers + LLUI::translate(0,-mSpacing); + LLFontGL::getFontSansSerifSmall()->renderUTF8(current_selection_text + LLView::sPreviewClickedElement->getName(), 0, 0, 0, text_color, + LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW); + LLUI::translate(0,-mSpacing-LLView::sPreviewClickedElement->getRect().getHeight()); // skip spacing distance + height + LLView::sPreviewClickedElement->draw(); + + for(std::list::iterator overlap_it = overlappers.begin(); overlap_it != overlappers.end(); ++overlap_it) + { + LLView* viewp = *overlap_it; + + // draw separating line + LLUI::translate(0,-mSpacing); + gl_line_2d(0,0,getRect().getWidth()-10,0,LLColor4(192.0f/255.0f,192.0f/255.0f,192.0f/255.0f)); + + // draw name + LLUI::translate(0,-mSpacing); + LLFontGL::getFontSansSerifSmall()->renderUTF8(overlapper_text + viewp->getName(), 0, 0, 0, text_color, + LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW); + + // draw element + LLUI::translate(0,-mSpacing-viewp->getRect().getHeight()); // skip spacing distance + height + viewp->draw(); + } + mLastClickedElement = LLView::sPreviewClickedElement; + } +} + +void LLFloaterUIPreviewUtil::registerFloater() +{ + LLFloaterReg::add("ui_preview", "floater_ui_preview.xml", + &LLFloaterReg::build); +} diff --git a/indra/newview/llfloaterurlentry.cpp b/indra/newview/llfloaterurlentry.cpp index 2c21bdc371..7651b2528f 100644 --- a/indra/newview/llfloaterurlentry.cpp +++ b/indra/newview/llfloaterurlentry.cpp @@ -1,298 +1,298 @@ -/** - * @file llfloaterurlentry.cpp - * @brief LLFloaterURLEntry class implementation - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterurlentry.h" - -#include "llpanellandmedia.h" -#include "llpanelface.h" - -#include "llcombobox.h" -#include "llmimetypes.h" -#include "llnotificationsutil.h" -#include "llurlhistory.h" -#include "lluictrlfactory.h" -#include "llwindow.h" -#include "llviewerwindow.h" -#include "llcorehttputil.h" - -static LLFloaterURLEntry* sInstance = NULL; - -//----------------------------------------------------------------------------- -// LLFloaterURLEntry() -//----------------------------------------------------------------------------- -LLFloaterURLEntry::LLFloaterURLEntry(LLHandle parent) - : LLFloater(LLSD()), - mPanelLandMediaHandle(parent) -{ - buildFromFile("floater_url_entry.xml"); -} - -//----------------------------------------------------------------------------- -// ~LLFloaterURLEntry() -//----------------------------------------------------------------------------- -LLFloaterURLEntry::~LLFloaterURLEntry() -{ - sInstance = NULL; -} - -bool LLFloaterURLEntry::postBuild() -{ - mMediaURLEdit = getChild("media_entry"); - - // Cancel button - childSetAction("cancel_btn", onBtnCancel, this); - - // Cancel button - childSetAction("clear_btn", onBtnClear, this); - // clear media list button - LLSD parcel_history = LLURLHistory::getURLHistory("parcel"); - bool enable_clear_button = parcel_history.size() > 0; - getChildView("clear_btn")->setEnabled(enable_clear_button ); - - // OK button - childSetAction("ok_btn", onBtnOK, this); - - setDefaultBtn("ok_btn"); - buildURLHistory(); - - return true; -} -void LLFloaterURLEntry::buildURLHistory() -{ - LLCtrlListInterface* url_list = childGetListInterface("media_entry"); - if (url_list) - { - url_list->operateOnAll(LLCtrlListInterface::OP_DELETE); - } - - // Get all of the entries in the "parcel" collection - LLSD parcel_history = LLURLHistory::getURLHistory("parcel"); - - LLSD::array_iterator iter_history = - parcel_history.beginArray(); - LLSD::array_iterator end_history = - parcel_history.endArray(); - for(; iter_history != end_history; ++iter_history) - { - url_list->addSimpleElement((*iter_history).asString()); - } -} - -void LLFloaterURLEntry::headerFetchComplete(S32 status, const std::string& mime_type) -{ - LLPanelLandMedia* panel_media = dynamic_cast(mPanelLandMediaHandle.get()); - if (panel_media) - { - // status is ignored for now -- error = "none/none" - panel_media->setMediaType(mime_type); - panel_media->setMediaURL(mMediaURLEdit->getValue().asString()); - } - - getChildView("loading_label")->setVisible( false); - closeFloater(); -} - -// static -LLHandle LLFloaterURLEntry::show(LLHandle parent, const std::string media_url) -{ - if (!sInstance) - { - sInstance = new LLFloaterURLEntry(parent); - } - sInstance->openFloater(); - sInstance->addURLToCombobox(media_url); - return sInstance->getHandle(); -} - - - -bool LLFloaterURLEntry::addURLToCombobox(const std::string& media_url) -{ - if(! mMediaURLEdit->setSimple( media_url ) && ! media_url.empty()) - { - mMediaURLEdit->add( media_url ); - mMediaURLEdit->setSimple( media_url ); - return true; - } - - // URL was not added for whatever reason (either it was empty or already existed) - return false; -} - -// static -//----------------------------------------------------------------------------- -// onBtnOK() -//----------------------------------------------------------------------------- -void LLFloaterURLEntry::onBtnOK( void* userdata ) -{ - LLFloaterURLEntry *self =(LLFloaterURLEntry *)userdata; - - std::string media_url = self->mMediaURLEdit->getValue().asString(); - self->mMediaURLEdit->remove(media_url); - LLURLHistory::removeURL("parcel", media_url); - if(self->addURLToCombobox(media_url)) - { - // Add this url to the parcel collection - LLURLHistory::addURL("parcel", media_url); - } - - // show progress bar here? - getWindow()->incBusyCount(); - self->getChildView("loading_label")->setVisible( true); - - // leading whitespace causes problems with the MIME-type detection so strip it - LLStringUtil::trim( media_url ); - - // First check the URL scheme - LLURI url(media_url); - std::string scheme = url.scheme(); - - // We assume that an empty scheme is an http url, as this is how we will treat it. - if(scheme == "") - { - scheme = "https"; - } - - if(!media_url.empty() && - (scheme == "http" || scheme == "https")) - { - LLCoros::instance().launch("LLFloaterURLEntry::getMediaTypeCoro", - boost::bind(&LLFloaterURLEntry::getMediaTypeCoro, media_url, self->getHandle())); - } - else - { - self->headerFetchComplete(0, scheme); - } - - // Grey the buttons until we get the header response - self->getChildView("ok_btn")->setEnabled(false); - self->getChildView("cancel_btn")->setEnabled(false); - self->getChildView("media_entry")->setEnabled(false); - self->getChildView("clear_btn")->setEnabled(false); -} - -// static -void LLFloaterURLEntry::getMediaTypeCoro(std::string url, LLHandle parentHandle) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMediaTypeCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); - LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); - - httpOpts->setFollowRedirects(true); - httpOpts->setHeadersOnly(true); - - httpHeaders->append(HTTP_OUT_HEADER_ACCEPT, "*/*"); - httpHeaders->append(HTTP_OUT_HEADER_COOKIE, ""); - - LL_INFOS("HttpCoroutineAdapter", "genericPostCoro") << "Generic POST for " << url << LL_ENDL; - - LLSD result = httpAdapter->getRawAndSuspend(httpRequest, url, httpOpts, httpHeaders); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - LLFloaterURLEntry* floaterUrlEntry = (LLFloaterURLEntry*)parentHandle.get(); - if (!floaterUrlEntry) - { - LL_WARNS() << "Could not get URL entry floater." << LL_ENDL; - return; - } - - // Set empty type to none/none. Empty string is reserved for legacy parcels - // which have no mime type set. - std::string resolvedMimeType = LLMIMETypes::getDefaultMimeType(); - - LLSD resultHeaders = httpResults[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS]; - - if (resultHeaders.has(HTTP_IN_HEADER_CONTENT_TYPE)) - { - const std::string& mediaType = resultHeaders[HTTP_IN_HEADER_CONTENT_TYPE]; - std::string::size_type idx1 = mediaType.find_first_of(";"); - std::string mimeType = mediaType.substr(0, idx1); - if (!mimeType.empty()) - { - resolvedMimeType = mimeType; - } - } - - floaterUrlEntry->headerFetchComplete(status.getType(), resolvedMimeType); - -} - -// static -//----------------------------------------------------------------------------- -// onBtnCancel() -//----------------------------------------------------------------------------- -void LLFloaterURLEntry::onBtnCancel( void* userdata ) -{ - LLFloaterURLEntry *self =(LLFloaterURLEntry *)userdata; - self->closeFloater(); -} - -// static -//----------------------------------------------------------------------------- -// onBtnClear() -//----------------------------------------------------------------------------- -void LLFloaterURLEntry::onBtnClear( void* userdata ) -{ - LLNotificationsUtil::add( "ConfirmClearMediaUrlList", LLSD(), LLSD(), - boost::bind(&LLFloaterURLEntry::callback_clear_url_list, (LLFloaterURLEntry*)userdata, _1, _2) ); -} - -bool LLFloaterURLEntry::callback_clear_url_list(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if ( option == 0 ) // YES - { - // clear saved list - LLCtrlListInterface* url_list = childGetListInterface("media_entry"); - if ( url_list ) - { - url_list->operateOnAll( LLCtrlListInterface::OP_DELETE ); - } - - // clear current contents of combo box - mMediaURLEdit->clear(); - - // clear stored version of list - LLURLHistory::clear("parcel"); - - // cleared the list so disable Clear button - getChildView("clear_btn")->setEnabled(false ); - } - return false; -} - -void LLFloaterURLEntry::onClose( bool app_quitting ) -{ - // Decrement the cursor - getWindow()->decBusyCount(); -} +/** + * @file llfloaterurlentry.cpp + * @brief LLFloaterURLEntry class implementation + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterurlentry.h" + +#include "llpanellandmedia.h" +#include "llpanelface.h" + +#include "llcombobox.h" +#include "llmimetypes.h" +#include "llnotificationsutil.h" +#include "llurlhistory.h" +#include "lluictrlfactory.h" +#include "llwindow.h" +#include "llviewerwindow.h" +#include "llcorehttputil.h" + +static LLFloaterURLEntry* sInstance = NULL; + +//----------------------------------------------------------------------------- +// LLFloaterURLEntry() +//----------------------------------------------------------------------------- +LLFloaterURLEntry::LLFloaterURLEntry(LLHandle parent) + : LLFloater(LLSD()), + mPanelLandMediaHandle(parent) +{ + buildFromFile("floater_url_entry.xml"); +} + +//----------------------------------------------------------------------------- +// ~LLFloaterURLEntry() +//----------------------------------------------------------------------------- +LLFloaterURLEntry::~LLFloaterURLEntry() +{ + sInstance = NULL; +} + +bool LLFloaterURLEntry::postBuild() +{ + mMediaURLEdit = getChild("media_entry"); + + // Cancel button + childSetAction("cancel_btn", onBtnCancel, this); + + // Cancel button + childSetAction("clear_btn", onBtnClear, this); + // clear media list button + LLSD parcel_history = LLURLHistory::getURLHistory("parcel"); + bool enable_clear_button = parcel_history.size() > 0; + getChildView("clear_btn")->setEnabled(enable_clear_button ); + + // OK button + childSetAction("ok_btn", onBtnOK, this); + + setDefaultBtn("ok_btn"); + buildURLHistory(); + + return true; +} +void LLFloaterURLEntry::buildURLHistory() +{ + LLCtrlListInterface* url_list = childGetListInterface("media_entry"); + if (url_list) + { + url_list->operateOnAll(LLCtrlListInterface::OP_DELETE); + } + + // Get all of the entries in the "parcel" collection + LLSD parcel_history = LLURLHistory::getURLHistory("parcel"); + + LLSD::array_iterator iter_history = + parcel_history.beginArray(); + LLSD::array_iterator end_history = + parcel_history.endArray(); + for(; iter_history != end_history; ++iter_history) + { + url_list->addSimpleElement((*iter_history).asString()); + } +} + +void LLFloaterURLEntry::headerFetchComplete(S32 status, const std::string& mime_type) +{ + LLPanelLandMedia* panel_media = dynamic_cast(mPanelLandMediaHandle.get()); + if (panel_media) + { + // status is ignored for now -- error = "none/none" + panel_media->setMediaType(mime_type); + panel_media->setMediaURL(mMediaURLEdit->getValue().asString()); + } + + getChildView("loading_label")->setVisible( false); + closeFloater(); +} + +// static +LLHandle LLFloaterURLEntry::show(LLHandle parent, const std::string media_url) +{ + if (!sInstance) + { + sInstance = new LLFloaterURLEntry(parent); + } + sInstance->openFloater(); + sInstance->addURLToCombobox(media_url); + return sInstance->getHandle(); +} + + + +bool LLFloaterURLEntry::addURLToCombobox(const std::string& media_url) +{ + if(! mMediaURLEdit->setSimple( media_url ) && ! media_url.empty()) + { + mMediaURLEdit->add( media_url ); + mMediaURLEdit->setSimple( media_url ); + return true; + } + + // URL was not added for whatever reason (either it was empty or already existed) + return false; +} + +// static +//----------------------------------------------------------------------------- +// onBtnOK() +//----------------------------------------------------------------------------- +void LLFloaterURLEntry::onBtnOK( void* userdata ) +{ + LLFloaterURLEntry *self =(LLFloaterURLEntry *)userdata; + + std::string media_url = self->mMediaURLEdit->getValue().asString(); + self->mMediaURLEdit->remove(media_url); + LLURLHistory::removeURL("parcel", media_url); + if(self->addURLToCombobox(media_url)) + { + // Add this url to the parcel collection + LLURLHistory::addURL("parcel", media_url); + } + + // show progress bar here? + getWindow()->incBusyCount(); + self->getChildView("loading_label")->setVisible( true); + + // leading whitespace causes problems with the MIME-type detection so strip it + LLStringUtil::trim( media_url ); + + // First check the URL scheme + LLURI url(media_url); + std::string scheme = url.scheme(); + + // We assume that an empty scheme is an http url, as this is how we will treat it. + if(scheme == "") + { + scheme = "https"; + } + + if(!media_url.empty() && + (scheme == "http" || scheme == "https")) + { + LLCoros::instance().launch("LLFloaterURLEntry::getMediaTypeCoro", + boost::bind(&LLFloaterURLEntry::getMediaTypeCoro, media_url, self->getHandle())); + } + else + { + self->headerFetchComplete(0, scheme); + } + + // Grey the buttons until we get the header response + self->getChildView("ok_btn")->setEnabled(false); + self->getChildView("cancel_btn")->setEnabled(false); + self->getChildView("media_entry")->setEnabled(false); + self->getChildView("clear_btn")->setEnabled(false); +} + +// static +void LLFloaterURLEntry::getMediaTypeCoro(std::string url, LLHandle parentHandle) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMediaTypeCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); + LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); + + httpOpts->setFollowRedirects(true); + httpOpts->setHeadersOnly(true); + + httpHeaders->append(HTTP_OUT_HEADER_ACCEPT, "*/*"); + httpHeaders->append(HTTP_OUT_HEADER_COOKIE, ""); + + LL_INFOS("HttpCoroutineAdapter", "genericPostCoro") << "Generic POST for " << url << LL_ENDL; + + LLSD result = httpAdapter->getRawAndSuspend(httpRequest, url, httpOpts, httpHeaders); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + LLFloaterURLEntry* floaterUrlEntry = (LLFloaterURLEntry*)parentHandle.get(); + if (!floaterUrlEntry) + { + LL_WARNS() << "Could not get URL entry floater." << LL_ENDL; + return; + } + + // Set empty type to none/none. Empty string is reserved for legacy parcels + // which have no mime type set. + std::string resolvedMimeType = LLMIMETypes::getDefaultMimeType(); + + LLSD resultHeaders = httpResults[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS]; + + if (resultHeaders.has(HTTP_IN_HEADER_CONTENT_TYPE)) + { + const std::string& mediaType = resultHeaders[HTTP_IN_HEADER_CONTENT_TYPE]; + std::string::size_type idx1 = mediaType.find_first_of(";"); + std::string mimeType = mediaType.substr(0, idx1); + if (!mimeType.empty()) + { + resolvedMimeType = mimeType; + } + } + + floaterUrlEntry->headerFetchComplete(status.getType(), resolvedMimeType); + +} + +// static +//----------------------------------------------------------------------------- +// onBtnCancel() +//----------------------------------------------------------------------------- +void LLFloaterURLEntry::onBtnCancel( void* userdata ) +{ + LLFloaterURLEntry *self =(LLFloaterURLEntry *)userdata; + self->closeFloater(); +} + +// static +//----------------------------------------------------------------------------- +// onBtnClear() +//----------------------------------------------------------------------------- +void LLFloaterURLEntry::onBtnClear( void* userdata ) +{ + LLNotificationsUtil::add( "ConfirmClearMediaUrlList", LLSD(), LLSD(), + boost::bind(&LLFloaterURLEntry::callback_clear_url_list, (LLFloaterURLEntry*)userdata, _1, _2) ); +} + +bool LLFloaterURLEntry::callback_clear_url_list(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if ( option == 0 ) // YES + { + // clear saved list + LLCtrlListInterface* url_list = childGetListInterface("media_entry"); + if ( url_list ) + { + url_list->operateOnAll( LLCtrlListInterface::OP_DELETE ); + } + + // clear current contents of combo box + mMediaURLEdit->clear(); + + // clear stored version of list + LLURLHistory::clear("parcel"); + + // cleared the list so disable Clear button + getChildView("clear_btn")->setEnabled(false ); + } + return false; +} + +void LLFloaterURLEntry::onClose( bool app_quitting ) +{ + // Decrement the cursor + getWindow()->decBusyCount(); +} diff --git a/indra/newview/llfloaterurlentry.h b/indra/newview/llfloaterurlentry.h index 3bf0a33be4..d8cd946279 100644 --- a/indra/newview/llfloaterurlentry.h +++ b/indra/newview/llfloaterurlentry.h @@ -1,68 +1,68 @@ -/** - * @file llfloaterurlentry.h - * @brief LLFloaterURLEntry class definition - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERURLENTRY_H -#define LL_LLFLOATERURLENTRY_H - -#include "llfloater.h" -#include "llpanellandmedia.h" -#include "lleventcoro.h" -#include "llcoros.h" - -class LLLineEditor; -class LLComboBox; - -class LLFloaterURLEntry : public LLFloater -{ -public: - // Can only be shown by LLPanelLandMedia, and pushes data back into - // that panel via the handle. - static LLHandle show(LLHandle panel_land_media_handle, const std::string media_url); - bool postBuild() override; - void onClose( bool app_quitting ) override; - void headerFetchComplete(S32 status, const std::string& mime_type); - - bool addURLToCombobox(const std::string& media_url); - -private: - LLFloaterURLEntry(LLHandle parent); - /*virtual*/ ~LLFloaterURLEntry(); - void buildURLHistory(); - -private: - LLComboBox* mMediaURLEdit; - LLHandle mPanelLandMediaHandle; - - static void onBtnOK(void*); - static void onBtnCancel(void*); - static void onBtnClear(void*); - bool callback_clear_url_list(const LLSD& notification, const LLSD& response); - - static void getMediaTypeCoro(std::string url, LLHandle parentHandle); - -}; - -#endif // LL_LLFLOATERURLENTRY_H +/** + * @file llfloaterurlentry.h + * @brief LLFloaterURLEntry class definition + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERURLENTRY_H +#define LL_LLFLOATERURLENTRY_H + +#include "llfloater.h" +#include "llpanellandmedia.h" +#include "lleventcoro.h" +#include "llcoros.h" + +class LLLineEditor; +class LLComboBox; + +class LLFloaterURLEntry : public LLFloater +{ +public: + // Can only be shown by LLPanelLandMedia, and pushes data back into + // that panel via the handle. + static LLHandle show(LLHandle panel_land_media_handle, const std::string media_url); + bool postBuild() override; + void onClose( bool app_quitting ) override; + void headerFetchComplete(S32 status, const std::string& mime_type); + + bool addURLToCombobox(const std::string& media_url); + +private: + LLFloaterURLEntry(LLHandle parent); + /*virtual*/ ~LLFloaterURLEntry(); + void buildURLHistory(); + +private: + LLComboBox* mMediaURLEdit; + LLHandle mPanelLandMediaHandle; + + static void onBtnOK(void*); + static void onBtnCancel(void*); + static void onBtnClear(void*); + bool callback_clear_url_list(const LLSD& notification, const LLSD& response); + + static void getMediaTypeCoro(std::string url, LLHandle parentHandle); + +}; + +#endif // LL_LLFLOATERURLENTRY_H diff --git a/indra/newview/llfloatervoiceeffect.cpp b/indra/newview/llfloatervoiceeffect.cpp index ae4c1a7a91..9f7c9aba87 100644 --- a/indra/newview/llfloatervoiceeffect.cpp +++ b/indra/newview/llfloatervoiceeffect.cpp @@ -1,289 +1,289 @@ -/** - * @file llfloatervoiceeffect.cpp - * @author Aimee - * @brief Selection and preview of voice effect. - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloatervoiceeffect.h" - -#include "llscrolllistctrl.h" -#include "lltrans.h" -#include "llweb.h" - -LLFloaterVoiceEffect::LLFloaterVoiceEffect(const LLSD& key) - : LLFloater(key) -{ - mCommitCallbackRegistrar.add("VoiceEffect.Record", boost::bind(&LLFloaterVoiceEffect::onClickRecord, this)); - mCommitCallbackRegistrar.add("VoiceEffect.Play", boost::bind(&LLFloaterVoiceEffect::onClickPlay, this)); - mCommitCallbackRegistrar.add("VoiceEffect.Stop", boost::bind(&LLFloaterVoiceEffect::onClickStop, this)); -// mCommitCallbackRegistrar.add("VoiceEffect.Activate", boost::bind(&LLFloaterVoiceEffect::onClickActivate, this)); -} - -// virtual -LLFloaterVoiceEffect::~LLFloaterVoiceEffect() -{ - if(LLVoiceClient::instanceExists()) - { - LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); - if (effect_interface) - { - effect_interface->removeObserver(this); - } - } -} - -// virtual -bool LLFloaterVoiceEffect::postBuild() -{ - setDefaultBtn("record_btn"); - getChild("record_btn")->setFocus(true); - getChild("voice_morphing_link")->setTextArg("[URL]", LLTrans::getString("voice_morphing_url")); - - mVoiceEffectList = getChild("voice_effect_list"); - if (mVoiceEffectList) - { - mVoiceEffectList->setCommitCallback(boost::bind(&LLFloaterVoiceEffect::onClickPlay, this)); -// mVoiceEffectList->setDoubleClickCallback(boost::bind(&LLFloaterVoiceEffect::onClickActivate, this)); - } - - LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); - if (effect_interface) - { - effect_interface->addObserver(this); - - // Disconnect from the current voice channel ready to record a voice sample for previewing - effect_interface->enablePreviewBuffer(true); - } - - refreshEffectList(); - updateControls(); - - return true; -} - -// virtual -void LLFloaterVoiceEffect::onClose(bool app_quitting) -{ - LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); - if (effect_interface) - { - effect_interface->enablePreviewBuffer(false); - } -} - -void LLFloaterVoiceEffect::refreshEffectList() -{ - if (!mVoiceEffectList) - { - return; - } - - LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); - if (!effect_interface) - { - mVoiceEffectList->setEnabled(false); - return; - } - - LL_DEBUGS("Voice")<< "Rebuilding Voice Morph list."<< LL_ENDL; - - // Preserve selected items and scroll position - S32 scroll_pos = mVoiceEffectList->getScrollPos(); - uuid_vec_t selected_items; - std::vector items = mVoiceEffectList->getAllSelected(); - for(std::vector::const_iterator it = items.begin(); it != items.end(); it++) - { - selected_items.push_back((*it)->getUUID()); - } - - mVoiceEffectList->deleteAllItems(); - - { - // Add the "No Voice Morph" entry - LLSD element; - - element["id"] = LLUUID::null; - element["columns"][NAME_COLUMN]["column"] = "name"; - element["columns"][NAME_COLUMN]["value"] = getString("no_voice_effect"); - element["columns"][NAME_COLUMN]["font"]["style"] = "BOLD"; - - LLScrollListItem* sl_item = mVoiceEffectList->addElement(element, ADD_BOTTOM); - // *HACK: Copied from llfloatergesture.cpp : ["font"]["style"] does not affect font style :( - if(sl_item) - { - ((LLScrollListText*)sl_item->getColumn(0))->setFontStyle(LLFontGL::BOLD); - } - } - - // Add each Voice Morph template, if there are any (template list includes all usable effects) - const voice_effect_list_t& template_list = effect_interface->getVoiceEffectTemplateList(); - if (!template_list.empty()) - { - for (voice_effect_list_t::const_iterator it = template_list.begin(); it != template_list.end(); ++it) - { - const LLUUID& effect_id = it->second; - - std::string localized_effect = "effect_" + it->first; - std::string effect_name = hasString(localized_effect) ? getString(localized_effect) : it->first; // XML contains localized effects names - - LLSD effect_properties = effect_interface->getVoiceEffectProperties(effect_id); - - // Tag the active effect. - if (effect_id == LLVoiceClient::instance().getVoiceEffectDefault()) - { - effect_name += " " + getString("active_voice_effect"); - } - - // Tag available effects that are new this session - if (effect_properties["is_new"].asBoolean()) - { - effect_name += " " + getString("new_voice_effect"); - } - - LLDate expiry_date = effect_properties["expiry_date"].asDate(); - bool is_template_only = effect_properties["template_only"].asBoolean(); - - std::string font_style = "NORMAL"; - if (!is_template_only) - { - font_style = "BOLD"; - } - - LLSD element; - element["id"] = effect_id; - - element["columns"][NAME_COLUMN]["column"] = "name"; - element["columns"][NAME_COLUMN]["value"] = effect_name; - element["columns"][NAME_COLUMN]["font"]["style"] = font_style; - - element["columns"][1]["column"] = "expires"; - if (!is_template_only) - { - element["columns"][DATE_COLUMN]["value"] = expiry_date; - element["columns"][DATE_COLUMN]["type"] = "date"; - } - else { - element["columns"][DATE_COLUMN]["value"] = getString("unsubscribed_voice_effect"); - } -// element["columns"][DATE_COLUMN]["font"]["style"] = "NORMAL"; - - LLScrollListItem* sl_item = mVoiceEffectList->addElement(element, ADD_BOTTOM); - // *HACK: Copied from llfloatergesture.cpp : ["font"]["style"] does not affect font style :( - if(sl_item) - { - LLFontGL::StyleFlags style = is_template_only ? LLFontGL::NORMAL : LLFontGL::BOLD; - LLScrollListText* slt = dynamic_cast(sl_item->getColumn(0)); - llassert(slt); - if (slt) - { - slt->setFontStyle(style); - } - } - } - } - - // Re-select items that were selected before, and restore the scroll position - for(uuid_vec_t::iterator it = selected_items.begin(); it != selected_items.end(); it++) - { - mVoiceEffectList->selectByID(*it); - } - mVoiceEffectList->setScrollPos(scroll_pos); - mVoiceEffectList->setEnabled(true); -} - -void LLFloaterVoiceEffect::updateControls() -{ - bool recording = false; - - LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); - if (effect_interface) - { - recording = effect_interface->isPreviewRecording(); - } - - getChild("record_btn")->setVisible(!recording); - getChild("record_stop_btn")->setVisible(recording); -} - -// virtual -void LLFloaterVoiceEffect::onVoiceEffectChanged(bool effect_list_updated) -{ - if (effect_list_updated) - { - refreshEffectList(); - } - updateControls(); -} - -void LLFloaterVoiceEffect::onClickRecord() -{ - LL_DEBUGS("Voice") << "Record clicked" << LL_ENDL; - LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); - if (effect_interface) - { - effect_interface->recordPreviewBuffer(); - } - updateControls(); -} - -void LLFloaterVoiceEffect::onClickPlay() -{ - LL_DEBUGS("Voice") << "Play clicked" << LL_ENDL; - if (!mVoiceEffectList) - { - return; - } - - const LLUUID& effect_id = mVoiceEffectList->getCurrentID(); - - LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); - if (effect_interface) - { - effect_interface->playPreviewBuffer(effect_id); - } - updateControls(); -} - -void LLFloaterVoiceEffect::onClickStop() -{ - LL_DEBUGS("Voice") << "Stop clicked" << LL_ENDL; - LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); - if (effect_interface) - { - effect_interface->stopPreviewBuffer(); - } - updateControls(); -} - -//void LLFloaterVoiceEffect::onClickActivate() -//{ -// LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); -// if (effect_interface && mVoiceEffectList) -// { -// effect_interface->setVoiceEffect(mVoiceEffectList->getCurrentID()); -// } -//} - +/** + * @file llfloatervoiceeffect.cpp + * @author Aimee + * @brief Selection and preview of voice effect. + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatervoiceeffect.h" + +#include "llscrolllistctrl.h" +#include "lltrans.h" +#include "llweb.h" + +LLFloaterVoiceEffect::LLFloaterVoiceEffect(const LLSD& key) + : LLFloater(key) +{ + mCommitCallbackRegistrar.add("VoiceEffect.Record", boost::bind(&LLFloaterVoiceEffect::onClickRecord, this)); + mCommitCallbackRegistrar.add("VoiceEffect.Play", boost::bind(&LLFloaterVoiceEffect::onClickPlay, this)); + mCommitCallbackRegistrar.add("VoiceEffect.Stop", boost::bind(&LLFloaterVoiceEffect::onClickStop, this)); +// mCommitCallbackRegistrar.add("VoiceEffect.Activate", boost::bind(&LLFloaterVoiceEffect::onClickActivate, this)); +} + +// virtual +LLFloaterVoiceEffect::~LLFloaterVoiceEffect() +{ + if(LLVoiceClient::instanceExists()) + { + LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); + if (effect_interface) + { + effect_interface->removeObserver(this); + } + } +} + +// virtual +bool LLFloaterVoiceEffect::postBuild() +{ + setDefaultBtn("record_btn"); + getChild("record_btn")->setFocus(true); + getChild("voice_morphing_link")->setTextArg("[URL]", LLTrans::getString("voice_morphing_url")); + + mVoiceEffectList = getChild("voice_effect_list"); + if (mVoiceEffectList) + { + mVoiceEffectList->setCommitCallback(boost::bind(&LLFloaterVoiceEffect::onClickPlay, this)); +// mVoiceEffectList->setDoubleClickCallback(boost::bind(&LLFloaterVoiceEffect::onClickActivate, this)); + } + + LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); + if (effect_interface) + { + effect_interface->addObserver(this); + + // Disconnect from the current voice channel ready to record a voice sample for previewing + effect_interface->enablePreviewBuffer(true); + } + + refreshEffectList(); + updateControls(); + + return true; +} + +// virtual +void LLFloaterVoiceEffect::onClose(bool app_quitting) +{ + LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); + if (effect_interface) + { + effect_interface->enablePreviewBuffer(false); + } +} + +void LLFloaterVoiceEffect::refreshEffectList() +{ + if (!mVoiceEffectList) + { + return; + } + + LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); + if (!effect_interface) + { + mVoiceEffectList->setEnabled(false); + return; + } + + LL_DEBUGS("Voice")<< "Rebuilding Voice Morph list."<< LL_ENDL; + + // Preserve selected items and scroll position + S32 scroll_pos = mVoiceEffectList->getScrollPos(); + uuid_vec_t selected_items; + std::vector items = mVoiceEffectList->getAllSelected(); + for(std::vector::const_iterator it = items.begin(); it != items.end(); it++) + { + selected_items.push_back((*it)->getUUID()); + } + + mVoiceEffectList->deleteAllItems(); + + { + // Add the "No Voice Morph" entry + LLSD element; + + element["id"] = LLUUID::null; + element["columns"][NAME_COLUMN]["column"] = "name"; + element["columns"][NAME_COLUMN]["value"] = getString("no_voice_effect"); + element["columns"][NAME_COLUMN]["font"]["style"] = "BOLD"; + + LLScrollListItem* sl_item = mVoiceEffectList->addElement(element, ADD_BOTTOM); + // *HACK: Copied from llfloatergesture.cpp : ["font"]["style"] does not affect font style :( + if(sl_item) + { + ((LLScrollListText*)sl_item->getColumn(0))->setFontStyle(LLFontGL::BOLD); + } + } + + // Add each Voice Morph template, if there are any (template list includes all usable effects) + const voice_effect_list_t& template_list = effect_interface->getVoiceEffectTemplateList(); + if (!template_list.empty()) + { + for (voice_effect_list_t::const_iterator it = template_list.begin(); it != template_list.end(); ++it) + { + const LLUUID& effect_id = it->second; + + std::string localized_effect = "effect_" + it->first; + std::string effect_name = hasString(localized_effect) ? getString(localized_effect) : it->first; // XML contains localized effects names + + LLSD effect_properties = effect_interface->getVoiceEffectProperties(effect_id); + + // Tag the active effect. + if (effect_id == LLVoiceClient::instance().getVoiceEffectDefault()) + { + effect_name += " " + getString("active_voice_effect"); + } + + // Tag available effects that are new this session + if (effect_properties["is_new"].asBoolean()) + { + effect_name += " " + getString("new_voice_effect"); + } + + LLDate expiry_date = effect_properties["expiry_date"].asDate(); + bool is_template_only = effect_properties["template_only"].asBoolean(); + + std::string font_style = "NORMAL"; + if (!is_template_only) + { + font_style = "BOLD"; + } + + LLSD element; + element["id"] = effect_id; + + element["columns"][NAME_COLUMN]["column"] = "name"; + element["columns"][NAME_COLUMN]["value"] = effect_name; + element["columns"][NAME_COLUMN]["font"]["style"] = font_style; + + element["columns"][1]["column"] = "expires"; + if (!is_template_only) + { + element["columns"][DATE_COLUMN]["value"] = expiry_date; + element["columns"][DATE_COLUMN]["type"] = "date"; + } + else { + element["columns"][DATE_COLUMN]["value"] = getString("unsubscribed_voice_effect"); + } +// element["columns"][DATE_COLUMN]["font"]["style"] = "NORMAL"; + + LLScrollListItem* sl_item = mVoiceEffectList->addElement(element, ADD_BOTTOM); + // *HACK: Copied from llfloatergesture.cpp : ["font"]["style"] does not affect font style :( + if(sl_item) + { + LLFontGL::StyleFlags style = is_template_only ? LLFontGL::NORMAL : LLFontGL::BOLD; + LLScrollListText* slt = dynamic_cast(sl_item->getColumn(0)); + llassert(slt); + if (slt) + { + slt->setFontStyle(style); + } + } + } + } + + // Re-select items that were selected before, and restore the scroll position + for(uuid_vec_t::iterator it = selected_items.begin(); it != selected_items.end(); it++) + { + mVoiceEffectList->selectByID(*it); + } + mVoiceEffectList->setScrollPos(scroll_pos); + mVoiceEffectList->setEnabled(true); +} + +void LLFloaterVoiceEffect::updateControls() +{ + bool recording = false; + + LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); + if (effect_interface) + { + recording = effect_interface->isPreviewRecording(); + } + + getChild("record_btn")->setVisible(!recording); + getChild("record_stop_btn")->setVisible(recording); +} + +// virtual +void LLFloaterVoiceEffect::onVoiceEffectChanged(bool effect_list_updated) +{ + if (effect_list_updated) + { + refreshEffectList(); + } + updateControls(); +} + +void LLFloaterVoiceEffect::onClickRecord() +{ + LL_DEBUGS("Voice") << "Record clicked" << LL_ENDL; + LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); + if (effect_interface) + { + effect_interface->recordPreviewBuffer(); + } + updateControls(); +} + +void LLFloaterVoiceEffect::onClickPlay() +{ + LL_DEBUGS("Voice") << "Play clicked" << LL_ENDL; + if (!mVoiceEffectList) + { + return; + } + + const LLUUID& effect_id = mVoiceEffectList->getCurrentID(); + + LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); + if (effect_interface) + { + effect_interface->playPreviewBuffer(effect_id); + } + updateControls(); +} + +void LLFloaterVoiceEffect::onClickStop() +{ + LL_DEBUGS("Voice") << "Stop clicked" << LL_ENDL; + LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); + if (effect_interface) + { + effect_interface->stopPreviewBuffer(); + } + updateControls(); +} + +//void LLFloaterVoiceEffect::onClickActivate() +//{ +// LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); +// if (effect_interface && mVoiceEffectList) +// { +// effect_interface->setVoiceEffect(mVoiceEffectList->getCurrentID()); +// } +//} + diff --git a/indra/newview/llfloatervoiceeffect.h b/indra/newview/llfloatervoiceeffect.h index b2139e3143..323beb64ae 100644 --- a/indra/newview/llfloatervoiceeffect.h +++ b/indra/newview/llfloatervoiceeffect.h @@ -1,72 +1,72 @@ -/** - * @file llfloatervoiceeffect.h - * @author Aimee - * @brief Selection and preview of voice effects. - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERVOICEEFFECT_H -#define LL_LLFLOATERVOICEEFFECT_H - -#include "llfloater.h" -#include "llvoiceclient.h" - -class LLButton; -class LLScrollListCtrl; - -class LLFloaterVoiceEffect - : public LLFloater - , public LLVoiceEffectObserver -{ -public: - LOG_CLASS(LLFloaterVoiceEffect); - - LLFloaterVoiceEffect(const LLSD& key); - virtual ~LLFloaterVoiceEffect(); - - bool postBuild() override; - void onClose(bool app_quitting) override; - -private: - enum ColumnIndex - { - NAME_COLUMN = 0, - DATE_COLUMN = 1, - }; - - void refreshEffectList(); - void updateControls(); - - /// Called by voice effect provider when voice effect list is changed. - virtual void onVoiceEffectChanged(bool effect_list_updated) override; - - void onClickRecord(); - void onClickPlay(); - void onClickStop(); -// void onClickActivate(); - - LLUUID mSelectedID; - LLScrollListCtrl* mVoiceEffectList; -}; - -#endif +/** + * @file llfloatervoiceeffect.h + * @author Aimee + * @brief Selection and preview of voice effects. + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERVOICEEFFECT_H +#define LL_LLFLOATERVOICEEFFECT_H + +#include "llfloater.h" +#include "llvoiceclient.h" + +class LLButton; +class LLScrollListCtrl; + +class LLFloaterVoiceEffect + : public LLFloater + , public LLVoiceEffectObserver +{ +public: + LOG_CLASS(LLFloaterVoiceEffect); + + LLFloaterVoiceEffect(const LLSD& key); + virtual ~LLFloaterVoiceEffect(); + + bool postBuild() override; + void onClose(bool app_quitting) override; + +private: + enum ColumnIndex + { + NAME_COLUMN = 0, + DATE_COLUMN = 1, + }; + + void refreshEffectList(); + void updateControls(); + + /// Called by voice effect provider when voice effect list is changed. + virtual void onVoiceEffectChanged(bool effect_list_updated) override; + + void onClickRecord(); + void onClickPlay(); + void onClickStop(); +// void onClickActivate(); + + LLUUID mSelectedID; + LLScrollListCtrl* mVoiceEffectList; +}; + +#endif diff --git a/indra/newview/llfloatervoicevolume.cpp b/indra/newview/llfloatervoicevolume.cpp index cfb8ae8e68..17783ee810 100644 --- a/indra/newview/llfloatervoicevolume.cpp +++ b/indra/newview/llfloatervoicevolume.cpp @@ -1,220 +1,220 @@ -/** - * @file llfloatervoicevolume.cpp - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloatervoicevolume.h" - -// Linden libraries -#include "llavatarname.h" -#include "llavatarnamecache.h" -#include "llfloater.h" -#include "llfloaterreg.h" -#include "lltextbox.h" - -// viewer files -#include "llagent.h" -#include "llavataractions.h" -#include "llinspect.h" -#include "lltransientfloatermgr.h" -#include "llvoiceclient.h" - -class LLAvatarName; - -////////////////////////////////////////////////////////////////////////////// -// LLFloaterVoiceVolume -////////////////////////////////////////////////////////////////////////////// - -// Avatar Inspector, a small information window used when clicking -// on avatar names in the 2D UI and in the ambient inspector widget for -// the 3D world. -class LLFloaterVoiceVolume : public LLInspect, LLTransientFloater -{ - friend class LLFloaterReg; - -public: - // avatar_id - Avatar ID for which to show information - // Inspector will be positioned relative to current mouse position - LLFloaterVoiceVolume(const LLSD& avatar_id); - virtual ~LLFloaterVoiceVolume(); - - /*virtual*/ bool postBuild(void); - - // Because floater is single instance, need to re-parse data on each spawn - // (for example, inspector about same avatar but in different position) - /*virtual*/ void onOpen(const LLSD& avatar_id); - - /*virtual*/ LLTransientFloaterMgr::ETransientGroup getGroup() { return LLTransientFloaterMgr::GLOBAL; } - -private: - // Set the volume slider to this user's current client-side volume setting, - // hiding/disabling if the user is not nearby. - void updateVolumeControls(); - - void onClickMuteVolume(); - void onVolumeChange(const LLSD& data); - void onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name); - -private: - LLUUID mAvatarID; - // Need avatar name information to spawn friend add request - LLAvatarName mAvatarName; - boost::signals2::connection mAvatarNameCacheConnection; -}; - -LLFloaterVoiceVolume::LLFloaterVoiceVolume(const LLSD& sd) -: LLInspect(LLSD()) // single_instance, doesn't really need key -, mAvatarID() // set in onOpen() *Note: we used to show partner's name but we dont anymore --angela 3rd Dec* -, mAvatarName() -, mAvatarNameCacheConnection() -{ - LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::GLOBAL, this); - LLTransientFloater::init(this); -} - -LLFloaterVoiceVolume::~LLFloaterVoiceVolume() -{ - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - LLTransientFloaterMgr::getInstance()->removeControlView(this); -} - -/*virtual*/ -bool LLFloaterVoiceVolume::postBuild(void) -{ - getChild("mute_btn")->setCommitCallback( - boost::bind(&LLFloaterVoiceVolume::onClickMuteVolume, this) ); - - getChild("volume_slider")->setCommitCallback( - boost::bind(&LLFloaterVoiceVolume::onVolumeChange, this, _2)); - - return true; -} - - -// Multiple calls to showInstance("floater_voice_volume", foo) will provide different -// LLSD for foo, which we will catch here. -//virtual -void LLFloaterVoiceVolume::onOpen(const LLSD& data) -{ - // Start open animation - LLInspect::onOpen(data); - - // Extract appropriate avatar id - mAvatarID = data["avatar_id"]; - - LLInspect::repositionInspector(data); - - getChild("avatar_name")->setValue(""); - updateVolumeControls(); - - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - mAvatarNameCacheConnection = LLAvatarNameCache::get(mAvatarID, boost::bind(&LLFloaterVoiceVolume::onAvatarNameCache, this, _1, _2)); -} - -void LLFloaterVoiceVolume::updateVolumeControls() -{ - bool voice_enabled = LLVoiceClient::getInstance()->getVoiceEnabled(mAvatarID); - - LLUICtrl* mute_btn = getChild("mute_btn"); - LLUICtrl* volume_slider = getChild("volume_slider"); - - // Do not display volume slider and mute button if it - // is ourself or we are not in a voice channel together - if (!voice_enabled || (mAvatarID == gAgent.getID())) - { - mute_btn->setVisible(false); - volume_slider->setVisible(false); - } - else - { - mute_btn->setVisible(true); - volume_slider->setVisible(true); - - // By convention, we only display and toggle voice mutes, not all mutes - bool is_muted = LLAvatarActions::isVoiceMuted(mAvatarID); - bool is_linden = LLStringUtil::endsWith(mAvatarName.getUserName(), " Linden"); - - mute_btn->setEnabled(!is_linden); - mute_btn->setValue(is_muted); - - volume_slider->setEnabled(!is_muted); - - F32 volume; - if (is_muted) - { - // it's clearer to display their volume as zero - volume = 0.f; - } - else - { - // actual volume - volume = LLVoiceClient::getInstance()->getUserVolume(mAvatarID); - } - volume_slider->setValue((F64)volume); - } - -} - -void LLFloaterVoiceVolume::onClickMuteVolume() -{ - LLAvatarActions::toggleMuteVoice(mAvatarID); - updateVolumeControls(); -} - -void LLFloaterVoiceVolume::onVolumeChange(const LLSD& data) -{ - F32 volume = (F32)data.asReal(); - LLVoiceClient::getInstance()->setUserVolume(mAvatarID, volume); -} - -void LLFloaterVoiceVolume::onAvatarNameCache( - const LLUUID& agent_id, - const LLAvatarName& av_name) -{ - mAvatarNameCacheConnection.disconnect(); - - if (agent_id != mAvatarID) - { - return; - } - - getChild("avatar_name")->setValue(av_name.getCompleteName()); - mAvatarName = av_name; -} - -////////////////////////////////////////////////////////////////////////////// -// LLFloaterVoiceVolumeUtil -////////////////////////////////////////////////////////////////////////////// -void LLFloaterVoiceVolumeUtil::registerFloater() -{ - LLFloaterReg::add("floater_voice_volume", "floater_voice_volume.xml", - &LLFloaterReg::build); -} +/** + * @file llfloatervoicevolume.cpp + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatervoicevolume.h" + +// Linden libraries +#include "llavatarname.h" +#include "llavatarnamecache.h" +#include "llfloater.h" +#include "llfloaterreg.h" +#include "lltextbox.h" + +// viewer files +#include "llagent.h" +#include "llavataractions.h" +#include "llinspect.h" +#include "lltransientfloatermgr.h" +#include "llvoiceclient.h" + +class LLAvatarName; + +////////////////////////////////////////////////////////////////////////////// +// LLFloaterVoiceVolume +////////////////////////////////////////////////////////////////////////////// + +// Avatar Inspector, a small information window used when clicking +// on avatar names in the 2D UI and in the ambient inspector widget for +// the 3D world. +class LLFloaterVoiceVolume : public LLInspect, LLTransientFloater +{ + friend class LLFloaterReg; + +public: + // avatar_id - Avatar ID for which to show information + // Inspector will be positioned relative to current mouse position + LLFloaterVoiceVolume(const LLSD& avatar_id); + virtual ~LLFloaterVoiceVolume(); + + /*virtual*/ bool postBuild(void); + + // Because floater is single instance, need to re-parse data on each spawn + // (for example, inspector about same avatar but in different position) + /*virtual*/ void onOpen(const LLSD& avatar_id); + + /*virtual*/ LLTransientFloaterMgr::ETransientGroup getGroup() { return LLTransientFloaterMgr::GLOBAL; } + +private: + // Set the volume slider to this user's current client-side volume setting, + // hiding/disabling if the user is not nearby. + void updateVolumeControls(); + + void onClickMuteVolume(); + void onVolumeChange(const LLSD& data); + void onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name); + +private: + LLUUID mAvatarID; + // Need avatar name information to spawn friend add request + LLAvatarName mAvatarName; + boost::signals2::connection mAvatarNameCacheConnection; +}; + +LLFloaterVoiceVolume::LLFloaterVoiceVolume(const LLSD& sd) +: LLInspect(LLSD()) // single_instance, doesn't really need key +, mAvatarID() // set in onOpen() *Note: we used to show partner's name but we dont anymore --angela 3rd Dec* +, mAvatarName() +, mAvatarNameCacheConnection() +{ + LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::GLOBAL, this); + LLTransientFloater::init(this); +} + +LLFloaterVoiceVolume::~LLFloaterVoiceVolume() +{ + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + LLTransientFloaterMgr::getInstance()->removeControlView(this); +} + +/*virtual*/ +bool LLFloaterVoiceVolume::postBuild(void) +{ + getChild("mute_btn")->setCommitCallback( + boost::bind(&LLFloaterVoiceVolume::onClickMuteVolume, this) ); + + getChild("volume_slider")->setCommitCallback( + boost::bind(&LLFloaterVoiceVolume::onVolumeChange, this, _2)); + + return true; +} + + +// Multiple calls to showInstance("floater_voice_volume", foo) will provide different +// LLSD for foo, which we will catch here. +//virtual +void LLFloaterVoiceVolume::onOpen(const LLSD& data) +{ + // Start open animation + LLInspect::onOpen(data); + + // Extract appropriate avatar id + mAvatarID = data["avatar_id"]; + + LLInspect::repositionInspector(data); + + getChild("avatar_name")->setValue(""); + updateVolumeControls(); + + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + mAvatarNameCacheConnection = LLAvatarNameCache::get(mAvatarID, boost::bind(&LLFloaterVoiceVolume::onAvatarNameCache, this, _1, _2)); +} + +void LLFloaterVoiceVolume::updateVolumeControls() +{ + bool voice_enabled = LLVoiceClient::getInstance()->getVoiceEnabled(mAvatarID); + + LLUICtrl* mute_btn = getChild("mute_btn"); + LLUICtrl* volume_slider = getChild("volume_slider"); + + // Do not display volume slider and mute button if it + // is ourself or we are not in a voice channel together + if (!voice_enabled || (mAvatarID == gAgent.getID())) + { + mute_btn->setVisible(false); + volume_slider->setVisible(false); + } + else + { + mute_btn->setVisible(true); + volume_slider->setVisible(true); + + // By convention, we only display and toggle voice mutes, not all mutes + bool is_muted = LLAvatarActions::isVoiceMuted(mAvatarID); + bool is_linden = LLStringUtil::endsWith(mAvatarName.getUserName(), " Linden"); + + mute_btn->setEnabled(!is_linden); + mute_btn->setValue(is_muted); + + volume_slider->setEnabled(!is_muted); + + F32 volume; + if (is_muted) + { + // it's clearer to display their volume as zero + volume = 0.f; + } + else + { + // actual volume + volume = LLVoiceClient::getInstance()->getUserVolume(mAvatarID); + } + volume_slider->setValue((F64)volume); + } + +} + +void LLFloaterVoiceVolume::onClickMuteVolume() +{ + LLAvatarActions::toggleMuteVoice(mAvatarID); + updateVolumeControls(); +} + +void LLFloaterVoiceVolume::onVolumeChange(const LLSD& data) +{ + F32 volume = (F32)data.asReal(); + LLVoiceClient::getInstance()->setUserVolume(mAvatarID, volume); +} + +void LLFloaterVoiceVolume::onAvatarNameCache( + const LLUUID& agent_id, + const LLAvatarName& av_name) +{ + mAvatarNameCacheConnection.disconnect(); + + if (agent_id != mAvatarID) + { + return; + } + + getChild("avatar_name")->setValue(av_name.getCompleteName()); + mAvatarName = av_name; +} + +////////////////////////////////////////////////////////////////////////////// +// LLFloaterVoiceVolumeUtil +////////////////////////////////////////////////////////////////////////////// +void LLFloaterVoiceVolumeUtil::registerFloater() +{ + LLFloaterReg::add("floater_voice_volume", "floater_voice_volume.xml", + &LLFloaterReg::build); +} diff --git a/indra/newview/llfloaterwebcontent.cpp b/indra/newview/llfloaterwebcontent.cpp index 98a1a2245b..f5b5b8293f 100644 --- a/indra/newview/llfloaterwebcontent.cpp +++ b/indra/newview/llfloaterwebcontent.cpp @@ -1,485 +1,485 @@ -/** - * @file llfloaterwebcontent.cpp - * @brief floater for displaying web content - e.g. profiles and search (eventually) - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llcombobox.h" -#include "lliconctrl.h" -#include "llfloaterreg.h" -#include "llhttpconstants.h" -#include "lllayoutstack.h" -#include "llpluginclassmedia.h" -#include "llprogressbar.h" -#include "lltextbox.h" -#include "llurlhistory.h" -#include "llviewercontrol.h" -#include "llweb.h" -#include "llwindow.h" - -#include "llfloaterwebcontent.h" - -LLFloaterWebContent::_Params::_Params() -: url("url"), - target("target"), - id("id"), - window_class("window_class", "web_content"), - show_chrome("show_chrome", true), - allow_address_entry("allow_address_entry", true), - allow_back_forward_navigation("allow_back_forward_navigation", true), - preferred_media_size("preferred_media_size"), - trusted_content("trusted_content", false), - show_page_title("show_page_title", true), - clean_browser("clean_browser", false), - dev_mode("dev_mode", false) -{} - -LLFloaterWebContent::LLFloaterWebContent( const Params& params ) -: LLFloater( params ), - LLInstanceTracker(params.id()), - mWebBrowser(NULL), - mAddressCombo(NULL), - mSecureLockIcon(NULL), - mStatusBarText(NULL), - mStatusBarProgress(NULL), - mBtnBack(NULL), - mBtnForward(NULL), - mBtnReload(NULL), - mBtnStop(NULL), - mUUID(params.id()), - mShowPageTitle(params.show_page_title), - mAllowNavigation(true), - mCurrentURL(""), - mDisplayURL(""), - mDevelopMode(params.dev_mode) // if called from "Develop" Menu, set a flag and change things to be more useful for devs -{ - mCommitCallbackRegistrar.add( "WebContent.Back", boost::bind( &LLFloaterWebContent::onClickBack, this )); - mCommitCallbackRegistrar.add( "WebContent.Forward", boost::bind( &LLFloaterWebContent::onClickForward, this )); - mCommitCallbackRegistrar.add( "WebContent.Reload", boost::bind( &LLFloaterWebContent::onClickReload, this )); - mCommitCallbackRegistrar.add( "WebContent.Stop", boost::bind( &LLFloaterWebContent::onClickStop, this )); - mCommitCallbackRegistrar.add( "WebContent.EnterAddress", boost::bind( &LLFloaterWebContent::onEnterAddress, this )); - mCommitCallbackRegistrar.add( "WebContent.PopExternal", boost::bind(&LLFloaterWebContent::onPopExternal, this)); - mCommitCallbackRegistrar.add( "WebContent.TestURL", boost::bind(&LLFloaterWebContent::onTestURL, this, _2)); -} - -bool LLFloaterWebContent::postBuild() -{ - // these are used in a bunch of places so cache them - mWebBrowser = getChild< LLMediaCtrl >( "webbrowser" ); - mAddressCombo = getChild< LLComboBox >( "address" ); - mStatusBarText = getChild< LLTextBox >( "statusbartext" ); - mStatusBarProgress = getChild("statusbarprogress" ); - - mBtnBack = getChildView( "back" ); - mBtnForward = getChildView( "forward" ); - mBtnReload = getChildView( "reload" ); - mBtnStop = getChildView( "stop" ); - - // observe browser events - mWebBrowser->addObserver( this ); - - // these buttons are always enabled - mBtnReload->setEnabled( true ); - mBtnReload->setVisible( false ); - getChildView("popexternal")->setEnabled( true ); - - // cache image for secure browsing - mSecureLockIcon = getChild< LLIconCtrl >("media_secure_lock_flag"); - - // initialize the URL history using the system URL History manager - initializeURLHistory(); - - return true; -} - -void LLFloaterWebContent::initializeURLHistory() -{ - // start with an empty list - LLCtrlListInterface* url_list = childGetListInterface("address"); - if (url_list) - { - url_list->operateOnAll(LLCtrlListInterface::OP_DELETE); - } - - // Get all of the entries in the "browser" collection - LLSD browser_history = LLURLHistory::getURLHistory("browser"); - LLSD::array_iterator iter_history = browser_history.beginArray(); - LLSD::array_iterator end_history = browser_history.endArray(); - for(; iter_history != end_history; ++iter_history) - { - std::string url = (*iter_history).asString(); - if(! url.empty()) - url_list->addSimpleElement(url); - } -} - -bool LLFloaterWebContent::matchesKey(const LLSD& key) -{ - Params p(mKey); - Params other_p(key); - if (!other_p.target().empty() && other_p.target() != "_blank") - { - return other_p.target() == p.target(); - } - else - { - return other_p.id() == p.id(); - } -} - -//static -LLFloater* LLFloaterWebContent::create( Params p) -{ - preCreate(p); - return new LLFloaterWebContent(p); -} - -//static -void LLFloaterWebContent::closeRequest(const std::string &uuid) -{ - auto floaterp = instance_tracker_t::getInstance(uuid); - if (floaterp) - { - floaterp->closeFloater(false); - } -} - -//static -void LLFloaterWebContent::geometryChanged(const std::string &uuid, S32 x, S32 y, S32 width, S32 height) -{ - auto floaterp = instance_tracker_t::getInstance(uuid); - if (floaterp) - { - floaterp->geometryChanged(x, y, width, height); - } -} - -void LLFloaterWebContent::geometryChanged(S32 x, S32 y, S32 width, S32 height) -{ - // Make sure the layout of the browser control is updated, so this calculation is correct. - getChild("stack1")->updateLayout(); - - // TODO: need to adjust size and constrain position to make sure floaters aren't moved outside the window view, etc. - LLCoordWindow window_size; - getWindow()->getSize(&window_size); - - // Adjust width and height for the size of the chrome on the web Browser window. - LLRect browser_rect; - mWebBrowser->localRectToOtherView(mWebBrowser->getLocalRect(), &browser_rect, this); - - S32 requested_browser_bottom = window_size.mY - (y + height); - LLRect geom; - geom.setOriginAndSize(x - browser_rect.mLeft, - requested_browser_bottom - browser_rect.mBottom, - width + getRect().getWidth() - browser_rect.getWidth(), - height + getRect().getHeight() - browser_rect.getHeight()); - - LLRect new_rect; - getParent()->screenRectToLocal(geom, &new_rect); - setShape(new_rect); -} - -// static -void LLFloaterWebContent::preCreate(LLFloaterWebContent::Params& p) -{ - if (!p.id.isProvided()) - { - p.id = LLUUID::generateNewID().asString(); - } - - if(p.target().empty() || p.target() == "_blank") - { - p.target = p.id(); - } - - S32 browser_window_limit = gSavedSettings.getS32("WebContentWindowLimit"); - if(browser_window_limit != 0) - { - // showInstance will open a new window. Figure out how many web browsers are already open, - // and close the least recently opened one if this will put us over the limit. - - LLFloaterReg::const_instance_list_t &instances = LLFloaterReg::getFloaterList(p.window_class); - - if(instances.size() >= (size_t)browser_window_limit) - { - // Destroy the least recently opened instance - (*instances.begin())->closeFloater(); - } - } -} - -void LLFloaterWebContent::open_media(const Params& p) -{ - LLViewerMedia::getInstance()->proxyWindowOpened(p.target(), p.id()); - mWebBrowser->setHomePageUrl(p.url); - mWebBrowser->setTarget(p.target); - mWebBrowser->navigateTo(p.url); - - set_current_url(p.url); - - getChild("status_bar")->setVisible(p.show_chrome); - getChild("nav_controls")->setVisible(p.show_chrome); - - // turn additional debug controls on but only for Develop mode (Develop menu open) - getChild("debug_controls")->setVisible(mDevelopMode); - - bool address_entry_enabled = p.allow_address_entry && !p.trusted_content; - mAllowNavigation = p.allow_back_forward_navigation; - getChildView("address")->setEnabled(address_entry_enabled); - getChildView("popexternal")->setEnabled(address_entry_enabled); - - if (!p.show_chrome) - { - setResizeLimits(100, 100); - } - - if (!p.preferred_media_size().isEmpty()) - { - getChild("stack1")->updateLayout(); - LLRect browser_rect = mWebBrowser->calcScreenRect(); - LLCoordWindow window_size; - getWindow()->getSize(&window_size); - - geometryChanged(browser_rect.mLeft, window_size.mY - browser_rect.mTop, p.preferred_media_size().getWidth(), p.preferred_media_size().getHeight()); - } - -} - -void LLFloaterWebContent::onOpen(const LLSD& key) -{ - Params params(key); - - if (!params.validateBlock()) - { - closeFloater(); - return; - } - - mWebBrowser->setTrustedContent(params.trusted_content); - - // tell the browser instance to load the specified URL - open_media(params); -} - -//virtual -void LLFloaterWebContent::onClose(bool app_quitting) -{ - LLViewerMedia::getInstance()->proxyWindowClosed(mUUID); - destroy(); -} - -// virtual -void LLFloaterWebContent::draw() -{ - // this is asynchronous so we need to keep checking - mBtnBack->setEnabled( mWebBrowser->canNavigateBack() && mAllowNavigation); - mBtnForward->setEnabled( mWebBrowser->canNavigateForward() && mAllowNavigation); - - LLFloater::draw(); -} - -// virtual -void LLFloaterWebContent::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) -{ - if(event == MEDIA_EVENT_LOCATION_CHANGED) - { - const std::string url = self->getLocation(); - - if ( url.length() ) - mStatusBarText->setText( url ); - - set_current_url( url ); - } - else if(event == MEDIA_EVENT_NAVIGATE_BEGIN) - { - // flags are sent with this event - mBtnBack->setEnabled( self->getHistoryBackAvailable() ); - mBtnForward->setEnabled( self->getHistoryForwardAvailable() ); - - // toggle visibility of these buttons based on browser state - mBtnReload->setVisible( false ); - mBtnStop->setVisible( true ); - - // turn "on" progress bar now we're about to start loading - mStatusBarProgress->setVisible( true ); - } - else if(event == MEDIA_EVENT_NAVIGATE_COMPLETE) - { - // flags are sent with this event - mBtnBack->setEnabled( self->getHistoryBackAvailable() ); - mBtnForward->setEnabled( self->getHistoryForwardAvailable() ); - - // toggle visibility of these buttons based on browser state - mBtnReload->setVisible( true ); - mBtnStop->setVisible( false ); - - // turn "off" progress bar now we're loaded - mStatusBarProgress->setVisible( false ); - - // we populate the status bar with URLs as they change so clear it now we're done - const std::string end_str = ""; - mStatusBarText->setText( end_str ); - } - else if(event == MEDIA_EVENT_CLOSE_REQUEST) - { - // The browser instance wants its window closed. - closeFloater(); - } - else if(event == MEDIA_EVENT_STATUS_TEXT_CHANGED ) - { - const std::string text = self->getStatusText(); - if ( text.length() ) - mStatusBarText->setText( text ); - } - else if(event == MEDIA_EVENT_PROGRESS_UPDATED ) - { - int percent = (int)self->getProgressPercent(); - mStatusBarProgress->setValue( percent ); - } - else if(event == MEDIA_EVENT_NAME_CHANGED ) - { - // flags are sent with this event - mBtnBack->setEnabled(self->getHistoryBackAvailable()); - mBtnForward->setEnabled(self->getHistoryForwardAvailable()); - std::string page_title = self->getMediaName(); - // simulate browser behavior - title is empty, use the current URL - if (mShowPageTitle) - { - if ( page_title.length() > 0 ) - setTitle( page_title ); - else - setTitle( mCurrentURL ); - } - } - else if(event == MEDIA_EVENT_LINK_HOVERED ) - { - const std::string link = self->getHoverLink(); - mStatusBarText->setText( link ); - } -} - -void LLFloaterWebContent::set_current_url(const std::string& url) -{ - if (!url.empty()) - { - if (!mCurrentURL.empty()) - { - // Clean up the current browsing list to show true URL - mAddressCombo->remove(mDisplayURL); - mAddressCombo->add(mCurrentURL); - } - - // Update current URL - mCurrentURL = url; - LLStringUtil::trim(mCurrentURL); - - // Serialize url history into the system URL History manager - LLURLHistory::removeURL("browser", mCurrentURL); - LLURLHistory::addURL("browser", mCurrentURL); - - // Check if this is a secure URL - static const std::string secure_prefix = std::string("https://"); - std::string prefix = mCurrentURL.substr(0, secure_prefix.length()); - LLStringUtil::toLower(prefix); - bool secure_url = (prefix == secure_prefix); - mSecureLockIcon->setVisible(secure_url); - mAddressCombo->setLeftTextPadding(secure_url ? 22 : 2); - mDisplayURL = mCurrentURL; - - // Clean up browsing list (prevent dupes) and add/select the new URL to it - mAddressCombo->remove(mCurrentURL); - mAddressCombo->add(mDisplayURL); - mAddressCombo->selectByValue(mDisplayURL); - } -} - -void LLFloaterWebContent::onClickForward() -{ - mWebBrowser->navigateForward(); -} - -void LLFloaterWebContent::onClickBack() -{ - mWebBrowser->navigateBack(); -} - -void LLFloaterWebContent::onClickReload() -{ - - if( mWebBrowser->getMediaPlugin() ) - { - bool ignore_cache = true; - mWebBrowser->getMediaPlugin()->browse_reload( ignore_cache ); - } - else - { - mWebBrowser->navigateTo(mCurrentURL); - } -} - -void LLFloaterWebContent::onClickStop() -{ - if( mWebBrowser->getMediaPlugin() ) - mWebBrowser->getMediaPlugin()->browse_stop(); - - // still should happen when we catch the navigate complete event - // but sometimes (don't know why) that event isn't sent from Qt - // and we ghetto a point where the stop button stays active. - mBtnReload->setVisible( true ); - mBtnStop->setVisible( false ); -} - -void LLFloaterWebContent::onEnterAddress() -{ - // make sure there is at least something there. - // (perhaps this test should be for minimum length of a URL) - std::string url = mAddressCombo->getValue().asString(); - LLStringUtil::trim(url); - if ( url.length() > 0 ) - { - mWebBrowser->navigateTo(url); - }; -} - -void LLFloaterWebContent::onPopExternal() -{ - // make sure there is at least something there. - // (perhaps this test should be for minimum length of a URL) - std::string url = mAddressCombo->getValue().asString(); - LLStringUtil::trim(url); - if (url.length() > 0) - { - LLWeb::loadURLExternal(url); - }; -} - -void LLFloaterWebContent::onTestURL(std::string url) -{ - LLStringUtil::trim(url); - if (url.length() > 0) - { - mWebBrowser->navigateTo(url); - }; -} +/** + * @file llfloaterwebcontent.cpp + * @brief floater for displaying web content - e.g. profiles and search (eventually) + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llcombobox.h" +#include "lliconctrl.h" +#include "llfloaterreg.h" +#include "llhttpconstants.h" +#include "lllayoutstack.h" +#include "llpluginclassmedia.h" +#include "llprogressbar.h" +#include "lltextbox.h" +#include "llurlhistory.h" +#include "llviewercontrol.h" +#include "llweb.h" +#include "llwindow.h" + +#include "llfloaterwebcontent.h" + +LLFloaterWebContent::_Params::_Params() +: url("url"), + target("target"), + id("id"), + window_class("window_class", "web_content"), + show_chrome("show_chrome", true), + allow_address_entry("allow_address_entry", true), + allow_back_forward_navigation("allow_back_forward_navigation", true), + preferred_media_size("preferred_media_size"), + trusted_content("trusted_content", false), + show_page_title("show_page_title", true), + clean_browser("clean_browser", false), + dev_mode("dev_mode", false) +{} + +LLFloaterWebContent::LLFloaterWebContent( const Params& params ) +: LLFloater( params ), + LLInstanceTracker(params.id()), + mWebBrowser(NULL), + mAddressCombo(NULL), + mSecureLockIcon(NULL), + mStatusBarText(NULL), + mStatusBarProgress(NULL), + mBtnBack(NULL), + mBtnForward(NULL), + mBtnReload(NULL), + mBtnStop(NULL), + mUUID(params.id()), + mShowPageTitle(params.show_page_title), + mAllowNavigation(true), + mCurrentURL(""), + mDisplayURL(""), + mDevelopMode(params.dev_mode) // if called from "Develop" Menu, set a flag and change things to be more useful for devs +{ + mCommitCallbackRegistrar.add( "WebContent.Back", boost::bind( &LLFloaterWebContent::onClickBack, this )); + mCommitCallbackRegistrar.add( "WebContent.Forward", boost::bind( &LLFloaterWebContent::onClickForward, this )); + mCommitCallbackRegistrar.add( "WebContent.Reload", boost::bind( &LLFloaterWebContent::onClickReload, this )); + mCommitCallbackRegistrar.add( "WebContent.Stop", boost::bind( &LLFloaterWebContent::onClickStop, this )); + mCommitCallbackRegistrar.add( "WebContent.EnterAddress", boost::bind( &LLFloaterWebContent::onEnterAddress, this )); + mCommitCallbackRegistrar.add( "WebContent.PopExternal", boost::bind(&LLFloaterWebContent::onPopExternal, this)); + mCommitCallbackRegistrar.add( "WebContent.TestURL", boost::bind(&LLFloaterWebContent::onTestURL, this, _2)); +} + +bool LLFloaterWebContent::postBuild() +{ + // these are used in a bunch of places so cache them + mWebBrowser = getChild< LLMediaCtrl >( "webbrowser" ); + mAddressCombo = getChild< LLComboBox >( "address" ); + mStatusBarText = getChild< LLTextBox >( "statusbartext" ); + mStatusBarProgress = getChild("statusbarprogress" ); + + mBtnBack = getChildView( "back" ); + mBtnForward = getChildView( "forward" ); + mBtnReload = getChildView( "reload" ); + mBtnStop = getChildView( "stop" ); + + // observe browser events + mWebBrowser->addObserver( this ); + + // these buttons are always enabled + mBtnReload->setEnabled( true ); + mBtnReload->setVisible( false ); + getChildView("popexternal")->setEnabled( true ); + + // cache image for secure browsing + mSecureLockIcon = getChild< LLIconCtrl >("media_secure_lock_flag"); + + // initialize the URL history using the system URL History manager + initializeURLHistory(); + + return true; +} + +void LLFloaterWebContent::initializeURLHistory() +{ + // start with an empty list + LLCtrlListInterface* url_list = childGetListInterface("address"); + if (url_list) + { + url_list->operateOnAll(LLCtrlListInterface::OP_DELETE); + } + + // Get all of the entries in the "browser" collection + LLSD browser_history = LLURLHistory::getURLHistory("browser"); + LLSD::array_iterator iter_history = browser_history.beginArray(); + LLSD::array_iterator end_history = browser_history.endArray(); + for(; iter_history != end_history; ++iter_history) + { + std::string url = (*iter_history).asString(); + if(! url.empty()) + url_list->addSimpleElement(url); + } +} + +bool LLFloaterWebContent::matchesKey(const LLSD& key) +{ + Params p(mKey); + Params other_p(key); + if (!other_p.target().empty() && other_p.target() != "_blank") + { + return other_p.target() == p.target(); + } + else + { + return other_p.id() == p.id(); + } +} + +//static +LLFloater* LLFloaterWebContent::create( Params p) +{ + preCreate(p); + return new LLFloaterWebContent(p); +} + +//static +void LLFloaterWebContent::closeRequest(const std::string &uuid) +{ + auto floaterp = instance_tracker_t::getInstance(uuid); + if (floaterp) + { + floaterp->closeFloater(false); + } +} + +//static +void LLFloaterWebContent::geometryChanged(const std::string &uuid, S32 x, S32 y, S32 width, S32 height) +{ + auto floaterp = instance_tracker_t::getInstance(uuid); + if (floaterp) + { + floaterp->geometryChanged(x, y, width, height); + } +} + +void LLFloaterWebContent::geometryChanged(S32 x, S32 y, S32 width, S32 height) +{ + // Make sure the layout of the browser control is updated, so this calculation is correct. + getChild("stack1")->updateLayout(); + + // TODO: need to adjust size and constrain position to make sure floaters aren't moved outside the window view, etc. + LLCoordWindow window_size; + getWindow()->getSize(&window_size); + + // Adjust width and height for the size of the chrome on the web Browser window. + LLRect browser_rect; + mWebBrowser->localRectToOtherView(mWebBrowser->getLocalRect(), &browser_rect, this); + + S32 requested_browser_bottom = window_size.mY - (y + height); + LLRect geom; + geom.setOriginAndSize(x - browser_rect.mLeft, + requested_browser_bottom - browser_rect.mBottom, + width + getRect().getWidth() - browser_rect.getWidth(), + height + getRect().getHeight() - browser_rect.getHeight()); + + LLRect new_rect; + getParent()->screenRectToLocal(geom, &new_rect); + setShape(new_rect); +} + +// static +void LLFloaterWebContent::preCreate(LLFloaterWebContent::Params& p) +{ + if (!p.id.isProvided()) + { + p.id = LLUUID::generateNewID().asString(); + } + + if(p.target().empty() || p.target() == "_blank") + { + p.target = p.id(); + } + + S32 browser_window_limit = gSavedSettings.getS32("WebContentWindowLimit"); + if(browser_window_limit != 0) + { + // showInstance will open a new window. Figure out how many web browsers are already open, + // and close the least recently opened one if this will put us over the limit. + + LLFloaterReg::const_instance_list_t &instances = LLFloaterReg::getFloaterList(p.window_class); + + if(instances.size() >= (size_t)browser_window_limit) + { + // Destroy the least recently opened instance + (*instances.begin())->closeFloater(); + } + } +} + +void LLFloaterWebContent::open_media(const Params& p) +{ + LLViewerMedia::getInstance()->proxyWindowOpened(p.target(), p.id()); + mWebBrowser->setHomePageUrl(p.url); + mWebBrowser->setTarget(p.target); + mWebBrowser->navigateTo(p.url); + + set_current_url(p.url); + + getChild("status_bar")->setVisible(p.show_chrome); + getChild("nav_controls")->setVisible(p.show_chrome); + + // turn additional debug controls on but only for Develop mode (Develop menu open) + getChild("debug_controls")->setVisible(mDevelopMode); + + bool address_entry_enabled = p.allow_address_entry && !p.trusted_content; + mAllowNavigation = p.allow_back_forward_navigation; + getChildView("address")->setEnabled(address_entry_enabled); + getChildView("popexternal")->setEnabled(address_entry_enabled); + + if (!p.show_chrome) + { + setResizeLimits(100, 100); + } + + if (!p.preferred_media_size().isEmpty()) + { + getChild("stack1")->updateLayout(); + LLRect browser_rect = mWebBrowser->calcScreenRect(); + LLCoordWindow window_size; + getWindow()->getSize(&window_size); + + geometryChanged(browser_rect.mLeft, window_size.mY - browser_rect.mTop, p.preferred_media_size().getWidth(), p.preferred_media_size().getHeight()); + } + +} + +void LLFloaterWebContent::onOpen(const LLSD& key) +{ + Params params(key); + + if (!params.validateBlock()) + { + closeFloater(); + return; + } + + mWebBrowser->setTrustedContent(params.trusted_content); + + // tell the browser instance to load the specified URL + open_media(params); +} + +//virtual +void LLFloaterWebContent::onClose(bool app_quitting) +{ + LLViewerMedia::getInstance()->proxyWindowClosed(mUUID); + destroy(); +} + +// virtual +void LLFloaterWebContent::draw() +{ + // this is asynchronous so we need to keep checking + mBtnBack->setEnabled( mWebBrowser->canNavigateBack() && mAllowNavigation); + mBtnForward->setEnabled( mWebBrowser->canNavigateForward() && mAllowNavigation); + + LLFloater::draw(); +} + +// virtual +void LLFloaterWebContent::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) +{ + if(event == MEDIA_EVENT_LOCATION_CHANGED) + { + const std::string url = self->getLocation(); + + if ( url.length() ) + mStatusBarText->setText( url ); + + set_current_url( url ); + } + else if(event == MEDIA_EVENT_NAVIGATE_BEGIN) + { + // flags are sent with this event + mBtnBack->setEnabled( self->getHistoryBackAvailable() ); + mBtnForward->setEnabled( self->getHistoryForwardAvailable() ); + + // toggle visibility of these buttons based on browser state + mBtnReload->setVisible( false ); + mBtnStop->setVisible( true ); + + // turn "on" progress bar now we're about to start loading + mStatusBarProgress->setVisible( true ); + } + else if(event == MEDIA_EVENT_NAVIGATE_COMPLETE) + { + // flags are sent with this event + mBtnBack->setEnabled( self->getHistoryBackAvailable() ); + mBtnForward->setEnabled( self->getHistoryForwardAvailable() ); + + // toggle visibility of these buttons based on browser state + mBtnReload->setVisible( true ); + mBtnStop->setVisible( false ); + + // turn "off" progress bar now we're loaded + mStatusBarProgress->setVisible( false ); + + // we populate the status bar with URLs as they change so clear it now we're done + const std::string end_str = ""; + mStatusBarText->setText( end_str ); + } + else if(event == MEDIA_EVENT_CLOSE_REQUEST) + { + // The browser instance wants its window closed. + closeFloater(); + } + else if(event == MEDIA_EVENT_STATUS_TEXT_CHANGED ) + { + const std::string text = self->getStatusText(); + if ( text.length() ) + mStatusBarText->setText( text ); + } + else if(event == MEDIA_EVENT_PROGRESS_UPDATED ) + { + int percent = (int)self->getProgressPercent(); + mStatusBarProgress->setValue( percent ); + } + else if(event == MEDIA_EVENT_NAME_CHANGED ) + { + // flags are sent with this event + mBtnBack->setEnabled(self->getHistoryBackAvailable()); + mBtnForward->setEnabled(self->getHistoryForwardAvailable()); + std::string page_title = self->getMediaName(); + // simulate browser behavior - title is empty, use the current URL + if (mShowPageTitle) + { + if ( page_title.length() > 0 ) + setTitle( page_title ); + else + setTitle( mCurrentURL ); + } + } + else if(event == MEDIA_EVENT_LINK_HOVERED ) + { + const std::string link = self->getHoverLink(); + mStatusBarText->setText( link ); + } +} + +void LLFloaterWebContent::set_current_url(const std::string& url) +{ + if (!url.empty()) + { + if (!mCurrentURL.empty()) + { + // Clean up the current browsing list to show true URL + mAddressCombo->remove(mDisplayURL); + mAddressCombo->add(mCurrentURL); + } + + // Update current URL + mCurrentURL = url; + LLStringUtil::trim(mCurrentURL); + + // Serialize url history into the system URL History manager + LLURLHistory::removeURL("browser", mCurrentURL); + LLURLHistory::addURL("browser", mCurrentURL); + + // Check if this is a secure URL + static const std::string secure_prefix = std::string("https://"); + std::string prefix = mCurrentURL.substr(0, secure_prefix.length()); + LLStringUtil::toLower(prefix); + bool secure_url = (prefix == secure_prefix); + mSecureLockIcon->setVisible(secure_url); + mAddressCombo->setLeftTextPadding(secure_url ? 22 : 2); + mDisplayURL = mCurrentURL; + + // Clean up browsing list (prevent dupes) and add/select the new URL to it + mAddressCombo->remove(mCurrentURL); + mAddressCombo->add(mDisplayURL); + mAddressCombo->selectByValue(mDisplayURL); + } +} + +void LLFloaterWebContent::onClickForward() +{ + mWebBrowser->navigateForward(); +} + +void LLFloaterWebContent::onClickBack() +{ + mWebBrowser->navigateBack(); +} + +void LLFloaterWebContent::onClickReload() +{ + + if( mWebBrowser->getMediaPlugin() ) + { + bool ignore_cache = true; + mWebBrowser->getMediaPlugin()->browse_reload( ignore_cache ); + } + else + { + mWebBrowser->navigateTo(mCurrentURL); + } +} + +void LLFloaterWebContent::onClickStop() +{ + if( mWebBrowser->getMediaPlugin() ) + mWebBrowser->getMediaPlugin()->browse_stop(); + + // still should happen when we catch the navigate complete event + // but sometimes (don't know why) that event isn't sent from Qt + // and we ghetto a point where the stop button stays active. + mBtnReload->setVisible( true ); + mBtnStop->setVisible( false ); +} + +void LLFloaterWebContent::onEnterAddress() +{ + // make sure there is at least something there. + // (perhaps this test should be for minimum length of a URL) + std::string url = mAddressCombo->getValue().asString(); + LLStringUtil::trim(url); + if ( url.length() > 0 ) + { + mWebBrowser->navigateTo(url); + }; +} + +void LLFloaterWebContent::onPopExternal() +{ + // make sure there is at least something there. + // (perhaps this test should be for minimum length of a URL) + std::string url = mAddressCombo->getValue().asString(); + LLStringUtil::trim(url); + if (url.length() > 0) + { + LLWeb::loadURLExternal(url); + }; +} + +void LLFloaterWebContent::onTestURL(std::string url) +{ + LLStringUtil::trim(url); + if (url.length() > 0) + { + mWebBrowser->navigateTo(url); + }; +} diff --git a/indra/newview/llfloaterwebcontent.h b/indra/newview/llfloaterwebcontent.h index 4ce79e2dab..9bb6d73e5e 100644 --- a/indra/newview/llfloaterwebcontent.h +++ b/indra/newview/llfloaterwebcontent.h @@ -1,121 +1,121 @@ -/** - * @file llfloaterwebcontent.h - * @brief floater for displaying web content - e.g. profiles and search (eventually) - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERWEBCONTENT_H -#define LL_LLFLOATERWEBCONTENT_H - -#include "llfloater.h" -#include "llmediactrl.h" -#include "llsdparam.h" - -class LLMediaCtrl; -class LLComboBox; -class LLTextBox; -class LLProgressBar; -class LLIconCtrl; - -class LLFloaterWebContent : - public LLFloater, - public LLViewerMediaObserver, - public LLInstanceTracker -{ -public: - - typedef LLInstanceTracker instance_tracker_t; - LOG_CLASS(LLFloaterWebContent); - - struct _Params : public LLInitParam::Block<_Params> - { - Optional url, - target, - window_class, - id; - Optional show_chrome, - allow_address_entry, - allow_back_forward_navigation, - trusted_content, - show_page_title, - clean_browser, - dev_mode; - Optional preferred_media_size; - - _Params(); - }; - - typedef LLSDParamAdapter<_Params> Params; - - LLFloaterWebContent(const Params& params); - - void initializeURLHistory(); - - static LLFloater* create(Params); - - static void closeRequest(const std::string &uuid); - static void geometryChanged(const std::string &uuid, S32 x, S32 y, S32 width, S32 height); - void geometryChanged(S32 x, S32 y, S32 width, S32 height); - - /* virtual */ bool postBuild(); - /* virtual */ void onOpen(const LLSD& key); - /* virtual */ bool matchesKey(const LLSD& key); - /* virtual */ void onClose(bool app_quitting); - /* virtual */ void draw(); - -protected: - // inherited from LLViewerMediaObserver - /*virtual*/ void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event); - - void onClickBack(); - void onClickForward(); - void onClickReload(); - void onClickStop(); - void onEnterAddress(); - void onPopExternal(); - void onTestURL(std::string url); - - static void preCreate(Params& p); - void open_media(const Params& ); - void set_current_url(const std::string& url); - - LLMediaCtrl* mWebBrowser; - LLComboBox* mAddressCombo; - LLIconCtrl* mSecureLockIcon; - LLTextBox* mStatusBarText; - LLProgressBar* mStatusBarProgress; - - LLView* mBtnBack; - LLView* mBtnForward; - LLView* mBtnReload; - LLView* mBtnStop; - - std::string mCurrentURL; // Current URL - std::string mDisplayURL; // URL being displayed in the address bar (can differ by trailing / leading space) - std::string mUUID; - bool mShowPageTitle; - bool mAllowNavigation; - bool mDevelopMode; -}; - -#endif // LL_LLFLOATERWEBCONTENT_H +/** + * @file llfloaterwebcontent.h + * @brief floater for displaying web content - e.g. profiles and search (eventually) + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERWEBCONTENT_H +#define LL_LLFLOATERWEBCONTENT_H + +#include "llfloater.h" +#include "llmediactrl.h" +#include "llsdparam.h" + +class LLMediaCtrl; +class LLComboBox; +class LLTextBox; +class LLProgressBar; +class LLIconCtrl; + +class LLFloaterWebContent : + public LLFloater, + public LLViewerMediaObserver, + public LLInstanceTracker +{ +public: + + typedef LLInstanceTracker instance_tracker_t; + LOG_CLASS(LLFloaterWebContent); + + struct _Params : public LLInitParam::Block<_Params> + { + Optional url, + target, + window_class, + id; + Optional show_chrome, + allow_address_entry, + allow_back_forward_navigation, + trusted_content, + show_page_title, + clean_browser, + dev_mode; + Optional preferred_media_size; + + _Params(); + }; + + typedef LLSDParamAdapter<_Params> Params; + + LLFloaterWebContent(const Params& params); + + void initializeURLHistory(); + + static LLFloater* create(Params); + + static void closeRequest(const std::string &uuid); + static void geometryChanged(const std::string &uuid, S32 x, S32 y, S32 width, S32 height); + void geometryChanged(S32 x, S32 y, S32 width, S32 height); + + /* virtual */ bool postBuild(); + /* virtual */ void onOpen(const LLSD& key); + /* virtual */ bool matchesKey(const LLSD& key); + /* virtual */ void onClose(bool app_quitting); + /* virtual */ void draw(); + +protected: + // inherited from LLViewerMediaObserver + /*virtual*/ void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event); + + void onClickBack(); + void onClickForward(); + void onClickReload(); + void onClickStop(); + void onEnterAddress(); + void onPopExternal(); + void onTestURL(std::string url); + + static void preCreate(Params& p); + void open_media(const Params& ); + void set_current_url(const std::string& url); + + LLMediaCtrl* mWebBrowser; + LLComboBox* mAddressCombo; + LLIconCtrl* mSecureLockIcon; + LLTextBox* mStatusBarText; + LLProgressBar* mStatusBarProgress; + + LLView* mBtnBack; + LLView* mBtnForward; + LLView* mBtnReload; + LLView* mBtnStop; + + std::string mCurrentURL; // Current URL + std::string mDisplayURL; // URL being displayed in the address bar (can differ by trailing / leading space) + std::string mUUID; + bool mShowPageTitle; + bool mAllowNavigation; + bool mDevelopMode; +}; + +#endif // LL_LLFLOATERWEBCONTENT_H diff --git a/indra/newview/llfloaterwhitelistentry.cpp b/indra/newview/llfloaterwhitelistentry.cpp index 6f3cedbec5..ca6c34484a 100644 --- a/indra/newview/llfloaterwhitelistentry.cpp +++ b/indra/newview/llfloaterwhitelistentry.cpp @@ -1,91 +1,91 @@ -/** - * @file llfloaterwhitelistentry.cpp - * @brief LLFloaterWhistListEntry class implementation - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterreg.h" -#include "llfloatermediasettings.h" -#include "llfloaterwhitelistentry.h" -#include "llpanelmediasettingssecurity.h" -#include "lluictrlfactory.h" -#include "llwindow.h" -#include "llviewerwindow.h" -#include "lllineeditor.h" - - -/////////////////////////////////////////////////////////////////////////////// -// -LLFloaterWhiteListEntry::LLFloaterWhiteListEntry( const LLSD& key ) : - LLFloater(key) -{ -} - -/////////////////////////////////////////////////////////////////////////////// -// -LLFloaterWhiteListEntry::~LLFloaterWhiteListEntry() -{ -} - -/////////////////////////////////////////////////////////////////////////////// -// -bool LLFloaterWhiteListEntry::postBuild() -{ - mWhiteListEdit = getChild("whitelist_entry"); - - childSetAction("cancel_btn", onBtnCancel, this); - childSetAction("ok_btn", onBtnOK, this); - - setDefaultBtn("ok_btn"); - - return true; -} - -/////////////////////////////////////////////////////////////////////////////// -// static -void LLFloaterWhiteListEntry::onBtnOK( void* userdata ) -{ - LLFloaterWhiteListEntry *self =(LLFloaterWhiteListEntry *)userdata; - - LLPanelMediaSettingsSecurity* panel = LLFloaterReg::getTypedInstance("media_settings")->getPanelSecurity(); - if ( panel ) - { - std::string white_list_item = self->mWhiteListEdit->getText(); - - panel->addWhiteListEntry( white_list_item ); - panel->updateWhitelistEnableStatus(); - }; - - self->closeFloater(); -} - -/////////////////////////////////////////////////////////////////////////////// -// static -void LLFloaterWhiteListEntry::onBtnCancel( void* userdata ) -{ - LLFloaterWhiteListEntry *self =(LLFloaterWhiteListEntry *)userdata; - - self->closeFloater(); -} +/** + * @file llfloaterwhitelistentry.cpp + * @brief LLFloaterWhistListEntry class implementation + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterreg.h" +#include "llfloatermediasettings.h" +#include "llfloaterwhitelistentry.h" +#include "llpanelmediasettingssecurity.h" +#include "lluictrlfactory.h" +#include "llwindow.h" +#include "llviewerwindow.h" +#include "lllineeditor.h" + + +/////////////////////////////////////////////////////////////////////////////// +// +LLFloaterWhiteListEntry::LLFloaterWhiteListEntry( const LLSD& key ) : + LLFloater(key) +{ +} + +/////////////////////////////////////////////////////////////////////////////// +// +LLFloaterWhiteListEntry::~LLFloaterWhiteListEntry() +{ +} + +/////////////////////////////////////////////////////////////////////////////// +// +bool LLFloaterWhiteListEntry::postBuild() +{ + mWhiteListEdit = getChild("whitelist_entry"); + + childSetAction("cancel_btn", onBtnCancel, this); + childSetAction("ok_btn", onBtnOK, this); + + setDefaultBtn("ok_btn"); + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// static +void LLFloaterWhiteListEntry::onBtnOK( void* userdata ) +{ + LLFloaterWhiteListEntry *self =(LLFloaterWhiteListEntry *)userdata; + + LLPanelMediaSettingsSecurity* panel = LLFloaterReg::getTypedInstance("media_settings")->getPanelSecurity(); + if ( panel ) + { + std::string white_list_item = self->mWhiteListEdit->getText(); + + panel->addWhiteListEntry( white_list_item ); + panel->updateWhitelistEnableStatus(); + }; + + self->closeFloater(); +} + +/////////////////////////////////////////////////////////////////////////////// +// static +void LLFloaterWhiteListEntry::onBtnCancel( void* userdata ) +{ + LLFloaterWhiteListEntry *self =(LLFloaterWhiteListEntry *)userdata; + + self->closeFloater(); +} diff --git a/indra/newview/llfloaterwhitelistentry.h b/indra/newview/llfloaterwhitelistentry.h index b4ccdd994b..bb6944635b 100644 --- a/indra/newview/llfloaterwhitelistentry.h +++ b/indra/newview/llfloaterwhitelistentry.h @@ -1,50 +1,50 @@ -/** - * @file llfloaterwhitelistentry.h - * @brief LLFloaterWhiteListEntry class definition - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFLOATERWHITELISTENTRY_H -#define LL_LLFLOATERWHITELISTENTRY_H - -#include "llfloater.h" - -class LLLineEditor; - -class LLFloaterWhiteListEntry : - public LLFloater -{ - public: - LLFloaterWhiteListEntry(const LLSD& key); - ~LLFloaterWhiteListEntry(); - - bool postBuild() override; - - private: - LLLineEditor* mWhiteListEdit; - - static void onBtnOK(void*); - static void onBtnCancel(void*); -}; - -#endif // LL_LLFLOATERWHITELISTENTRY_H +/** + * @file llfloaterwhitelistentry.h + * @brief LLFloaterWhiteListEntry class definition + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERWHITELISTENTRY_H +#define LL_LLFLOATERWHITELISTENTRY_H + +#include "llfloater.h" + +class LLLineEditor; + +class LLFloaterWhiteListEntry : + public LLFloater +{ + public: + LLFloaterWhiteListEntry(const LLSD& key); + ~LLFloaterWhiteListEntry(); + + bool postBuild() override; + + private: + LLLineEditor* mWhiteListEdit; + + static void onBtnOK(void*); + static void onBtnCancel(void*); +}; + +#endif // LL_LLFLOATERWHITELISTENTRY_H diff --git a/indra/newview/llfloaterwindowsize.cpp b/indra/newview/llfloaterwindowsize.cpp index eed9f71f5b..442695c3bc 100644 --- a/indra/newview/llfloaterwindowsize.cpp +++ b/indra/newview/llfloaterwindowsize.cpp @@ -1,124 +1,124 @@ -/** - * @file llfloaterwindowsize.cpp - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterwindowsize.h" - -// Viewer includes -#include "llviewerwindow.h" - -// Linden library includes -#include "llcombobox.h" -#include "llfloater.h" -#include "llfloaterreg.h" -#include "llregex.h" -#include "lluictrl.h" - -// Extract from strings of the form " x ", e.g. "640 x 480". -bool extractWindowSizeFromString(const std::string& instr, U32 *width, U32 *height) -{ - boost::cmatch what; - // matches (any number)(any non-number)(any number) - const boost::regex expression("([0-9]+)[^0-9]+([0-9]+)"); - if (ll_regex_match(instr.c_str(), what, expression)) - { - *width = atoi(what[1].first); - *height = atoi(what[2].first); - return true; - } - - *width = 0; - *height = 0; - return false; -} - - -LLFloaterWindowSize::LLFloaterWindowSize(const LLSD& key) -: LLFloater(key) -{} - -LLFloaterWindowSize::~LLFloaterWindowSize() -{} - -bool LLFloaterWindowSize::postBuild() -{ - center(); - initWindowSizeControls(); - getChild("set_btn")->setCommitCallback( - boost::bind(&LLFloaterWindowSize::onClickSet, this)); - getChild("cancel_btn")->setCommitCallback( - boost::bind(&LLFloaterWindowSize::onClickCancel, this)); - setDefaultBtn("set_btn"); - return true; -} - -void LLFloaterWindowSize::initWindowSizeControls() -{ - LLComboBox* ctrl_window_size = getChild("window_size_combo"); - - // Look to see if current window size matches existing window sizes, if so then - // just set the selection value... - const U32 height = gViewerWindow->getWindowHeightRaw(); - const U32 width = gViewerWindow->getWindowWidthRaw(); - for (S32 i=0; i < ctrl_window_size->getItemCount(); i++) - { - U32 height_test = 0; - U32 width_test = 0; - ctrl_window_size->setCurrentByIndex(i); - std::string resolution = ctrl_window_size->getValue().asString(); - if (extractWindowSizeFromString(resolution, &width_test, &height_test)) - { - if ((height_test == height) && (width_test == width)) - { - return; - } - } - } - // ...otherwise, add a new entry with the current window height/width. - LLUIString resolution_label = getString("resolution_format"); - resolution_label.setArg("[RES_X]", llformat("%d", width)); - resolution_label.setArg("[RES_Y]", llformat("%d", height)); - ctrl_window_size->add(resolution_label, ADD_TOP); - ctrl_window_size->setCurrentByIndex(0); -} - -void LLFloaterWindowSize::onClickSet() -{ - LLComboBox* ctrl_window_size = getChild("window_size_combo"); - U32 width = 0; - U32 height = 0; - std::string resolution = ctrl_window_size->getValue().asString(); - if (extractWindowSizeFromString(resolution, &width, &height)) - { - LLViewerWindow::movieSize(width, height); - } - closeFloater(); -} - -void LLFloaterWindowSize::onClickCancel() -{ - closeFloater(); -} +/** + * @file llfloaterwindowsize.cpp + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterwindowsize.h" + +// Viewer includes +#include "llviewerwindow.h" + +// Linden library includes +#include "llcombobox.h" +#include "llfloater.h" +#include "llfloaterreg.h" +#include "llregex.h" +#include "lluictrl.h" + +// Extract from strings of the form " x ", e.g. "640 x 480". +bool extractWindowSizeFromString(const std::string& instr, U32 *width, U32 *height) +{ + boost::cmatch what; + // matches (any number)(any non-number)(any number) + const boost::regex expression("([0-9]+)[^0-9]+([0-9]+)"); + if (ll_regex_match(instr.c_str(), what, expression)) + { + *width = atoi(what[1].first); + *height = atoi(what[2].first); + return true; + } + + *width = 0; + *height = 0; + return false; +} + + +LLFloaterWindowSize::LLFloaterWindowSize(const LLSD& key) +: LLFloater(key) +{} + +LLFloaterWindowSize::~LLFloaterWindowSize() +{} + +bool LLFloaterWindowSize::postBuild() +{ + center(); + initWindowSizeControls(); + getChild("set_btn")->setCommitCallback( + boost::bind(&LLFloaterWindowSize::onClickSet, this)); + getChild("cancel_btn")->setCommitCallback( + boost::bind(&LLFloaterWindowSize::onClickCancel, this)); + setDefaultBtn("set_btn"); + return true; +} + +void LLFloaterWindowSize::initWindowSizeControls() +{ + LLComboBox* ctrl_window_size = getChild("window_size_combo"); + + // Look to see if current window size matches existing window sizes, if so then + // just set the selection value... + const U32 height = gViewerWindow->getWindowHeightRaw(); + const U32 width = gViewerWindow->getWindowWidthRaw(); + for (S32 i=0; i < ctrl_window_size->getItemCount(); i++) + { + U32 height_test = 0; + U32 width_test = 0; + ctrl_window_size->setCurrentByIndex(i); + std::string resolution = ctrl_window_size->getValue().asString(); + if (extractWindowSizeFromString(resolution, &width_test, &height_test)) + { + if ((height_test == height) && (width_test == width)) + { + return; + } + } + } + // ...otherwise, add a new entry with the current window height/width. + LLUIString resolution_label = getString("resolution_format"); + resolution_label.setArg("[RES_X]", llformat("%d", width)); + resolution_label.setArg("[RES_Y]", llformat("%d", height)); + ctrl_window_size->add(resolution_label, ADD_TOP); + ctrl_window_size->setCurrentByIndex(0); +} + +void LLFloaterWindowSize::onClickSet() +{ + LLComboBox* ctrl_window_size = getChild("window_size_combo"); + U32 width = 0; + U32 height = 0; + std::string resolution = ctrl_window_size->getValue().asString(); + if (extractWindowSizeFromString(resolution, &width, &height)) + { + LLViewerWindow::movieSize(width, height); + } + closeFloater(); +} + +void LLFloaterWindowSize::onClickCancel() +{ + closeFloater(); +} diff --git a/indra/newview/llfloaterwindowsize.h b/indra/newview/llfloaterwindowsize.h index e4a23f251b..49bd08e549 100644 --- a/indra/newview/llfloaterwindowsize.h +++ b/indra/newview/llfloaterwindowsize.h @@ -1,49 +1,49 @@ -/** - * @file llfloaterwindowsize.h - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLFLOATERWINDOWSIZE_H -#define LLFLOATERWINDOWSIZE_H - -#include "llfloater.h" - -///---------------------------------------------------------------------------- -/// Class LLFloaterWindowSize -///---------------------------------------------------------------------------- -class LLFloaterWindowSize - : public LLFloater -{ - friend class LLFloaterReg; -private: - LLFloaterWindowSize(const LLSD& key); - virtual ~LLFloaterWindowSize(); - -public: - bool postBuild() override; - void initWindowSizeControls(); - void onClickSet(); - void onClickCancel(); -}; - -#endif +/** + * @file llfloaterwindowsize.h + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLFLOATERWINDOWSIZE_H +#define LLFLOATERWINDOWSIZE_H + +#include "llfloater.h" + +///---------------------------------------------------------------------------- +/// Class LLFloaterWindowSize +///---------------------------------------------------------------------------- +class LLFloaterWindowSize + : public LLFloater +{ + friend class LLFloaterReg; +private: + LLFloaterWindowSize(const LLSD& key); + virtual ~LLFloaterWindowSize(); + +public: + bool postBuild() override; + void initWindowSizeControls(); + void onClickSet(); + void onClickCancel(); +}; + +#endif diff --git a/indra/newview/llfloaterworldmap.cpp b/indra/newview/llfloaterworldmap.cpp index 9cc0b873b2..2c28e80995 100755 --- a/indra/newview/llfloaterworldmap.cpp +++ b/indra/newview/llfloaterworldmap.cpp @@ -1,1795 +1,1795 @@ -/** - * @file llfloaterworldmap.cpp - * @author James Cook, Tom Yedwab - * @brief LLFloaterWorldMap class implementation - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/* - * Map of the entire world, with multiple background images, - * avatar tracking, teleportation by double-click, etc. - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterworldmap.h" - -#include "llagent.h" -#include "llagentcamera.h" -#include "llbutton.h" -#include "llcallingcard.h" -#include "llcombobox.h" -#include "llviewercontrol.h" -#include "llcommandhandler.h" -#include "lldraghandle.h" -//#include "llfirstuse.h" -#include "llfloaterreg.h" // getTypedInstance() -#include "llfocusmgr.h" -#include "lliconctrl.h" -#include "llinventoryfunctions.h" -#include "llinventorymodel.h" -#include "llinventorymodelbackgroundfetch.h" -#include "llinventoryobserver.h" -#include "lllandmarklist.h" -#include "llsearcheditor.h" -#include "llnotificationsutil.h" -#include "llregionhandle.h" -#include "llscrolllistctrl.h" -#include "llslurl.h" -#include "lltextbox.h" -#include "lltoolbarview.h" -#include "lltracker.h" -#include "lltrans.h" -#include "llviewerinventory.h" // LLViewerInventoryItem -#include "llviewermenu.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" -#include "llviewerstats.h" -#include "llviewertexture.h" -#include "llviewerwindow.h" -#include "llworldmap.h" -#include "llworldmapmessage.h" -#include "llworldmapview.h" -#include "lluictrlfactory.h" -#include "llappviewer.h" -#include "llmapimagetype.h" -#include "llweb.h" -#include "llsliderctrl.h" -#include "message.h" -#include "llwindow.h" // copyTextToClipboard() -#include - -//--------------------------------------------------------------------------- -// Constants -//--------------------------------------------------------------------------- - -// Merov: we switched from using the "world size" (which varies depending where the user went) to a fixed -// width of 512 regions max visible at a time. This makes the zoom slider works in a consistent way across -// sessions and doesn't prevent the user to pan the world if it was to grow a lot beyond that limit. -// Currently (01/26/09), this value allows the whole grid to be visible in a 1024x1024 window. -static const S32 MAX_VISIBLE_REGIONS = 512; - - -const S32 HIDE_BEACON_PAD = 133; - -// It would be more logical to have this inside the method where it is used but to compile under gcc this -// struct has to be here. -struct SortRegionNames -{ - inline bool operator ()(std::pair const& _left, std::pair const& _right) - { - return(LLStringUtil::compareInsensitive(_left.second->getName(), _right.second->getName()) < 0); - } -}; - -enum EPanDirection -{ - PAN_UP, - PAN_DOWN, - PAN_LEFT, - PAN_RIGHT -}; - -// Values in pixels per region -static const F32 ZOOM_MAX = 128.f; - -//--------------------------------------------------------------------------- -// Globals -//--------------------------------------------------------------------------- - -// handle secondlife:///app/worldmap/{NAME}/{COORDS} URLs -class LLWorldMapHandler : public LLCommandHandler -{ -public: - LLWorldMapHandler() : LLCommandHandler("worldmap", UNTRUSTED_THROTTLE) - { - } - - virtual bool canHandleUntrusted( - const LLSD& params, - const LLSD& query_map, - LLMediaCtrl* web, - const std::string& nav_type) - { - if (nav_type == NAV_TYPE_CLICKED - || nav_type == NAV_TYPE_EXTERNAL) - { - // NAV_TYPE_EXTERNAL will be throttled - return true; - } - - return false; - } - - bool handle(const LLSD& params, - const LLSD& query_map, - const std::string& grid, - LLMediaCtrl* web) - { - if (params.size() == 0) - { - // support the secondlife:///app/worldmap SLapp - LLFloaterReg::showInstance("world_map", "center"); - return true; - } - - // support the secondlife:///app/worldmap/{LOCATION}/{COORDS} SLapp - const std::string region_name = LLURI::unescape(params[0].asString()); - S32 x = (params.size() > 1) ? params[1].asInteger() : 128; - S32 y = (params.size() > 2) ? params[2].asInteger() : 128; - S32 z = (params.size() > 3) ? params[3].asInteger() : 0; - - LLFloaterWorldMap::getInstance()->trackURL(region_name, x, y, z); - LLFloaterReg::showInstance("world_map", "center"); - - return true; - } -}; -LLWorldMapHandler gWorldMapHandler; - -// SocialMap handler secondlife:///app/maptrackavatar/id -class LLMapTrackAvatarHandler : public LLCommandHandler -{ -public: - LLMapTrackAvatarHandler() : LLCommandHandler("maptrackavatar", UNTRUSTED_THROTTLE) - { - } - - virtual bool canHandleUntrusted( - const LLSD& params, - const LLSD& query_map, - LLMediaCtrl* web, - const std::string& nav_type) - { - if (params.size() < 1) - { - return true; // don't block, will fail later - } - - if (nav_type == NAV_TYPE_CLICKED - || nav_type == NAV_TYPE_EXTERNAL) - { - // NAV_TYPE_EXTERNAL will be throttled - return true; - } - - return false; - } - - bool handle(const LLSD& params, - const LLSD& query_map, - const std::string& grid, - LLMediaCtrl* web) - { - //Make sure we have some parameters - if (params.size() == 0) - { - return false; - } - - //Get the ID - LLUUID id; - if (!id.set( params[0], false )) - { - return false; - } - - LLFloaterWorldMap::getInstance()->avatarTrackFromSlapp( id ); - LLFloaterReg::showInstance( "world_map", "center" ); - - return true; - } -}; -LLMapTrackAvatarHandler gMapTrackAvatar; - -LLFloaterWorldMap* gFloaterWorldMap = NULL; - -class LLMapInventoryObserver : public LLInventoryObserver -{ -public: - LLMapInventoryObserver() {} - virtual ~LLMapInventoryObserver() {} - virtual void changed(U32 mask); -}; - -void LLMapInventoryObserver::changed(U32 mask) -{ - // if there's a change we're interested in. - if((mask & (LLInventoryObserver::CALLING_CARD | LLInventoryObserver::ADD | - LLInventoryObserver::REMOVE)) != 0) - { - gFloaterWorldMap->inventoryChanged(); - } -} - -class LLMapFriendObserver : public LLFriendObserver -{ -public: - LLMapFriendObserver() {} - virtual ~LLMapFriendObserver() {} - virtual void changed(U32 mask); -}; - -void LLMapFriendObserver::changed(U32 mask) -{ - // if there's a change we're interested in. - if((mask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE | LLFriendObserver::ONLINE | LLFriendObserver::POWERS)) != 0) - { - gFloaterWorldMap->friendsChanged(); - } -} - -//--------------------------------------------------------------------------- -// Statics -//--------------------------------------------------------------------------- - -// Used as a pretend asset and inventory id to mean "landmark at my home location." -const LLUUID LLFloaterWorldMap::sHomeID( "10000000-0000-0000-0000-000000000001" ); - -//--------------------------------------------------------------------------- -// Construction and destruction -//--------------------------------------------------------------------------- - - -LLFloaterWorldMap::LLFloaterWorldMap(const LLSD& key) -: LLFloater(key), - mInventory(NULL), - mInventoryObserver(NULL), - mFriendObserver(NULL), - mCompletingRegionName(), - mCompletingRegionPos(), - mWaitingForTracker(false), - mIsClosing(false), - mSetToUserPosition(true), - mTrackedLocation(0,0,0), - mTrackedStatus(LLTracker::TRACKING_NOTHING), - mListFriendCombo(NULL), - mListLandmarkCombo(NULL), - mListSearchResults(NULL) -{ - gFloaterWorldMap = this; - - mFactoryMap["objects_mapview"] = LLCallbackMap(createWorldMapView, NULL); - - mCommitCallbackRegistrar.add("WMap.Coordinates", boost::bind(&LLFloaterWorldMap::onCoordinatesCommit, this)); - mCommitCallbackRegistrar.add("WMap.Location", boost::bind(&LLFloaterWorldMap::onLocationCommit, this)); - mCommitCallbackRegistrar.add("WMap.AvatarCombo", boost::bind(&LLFloaterWorldMap::onAvatarComboCommit, this)); - mCommitCallbackRegistrar.add("WMap.Landmark", boost::bind(&LLFloaterWorldMap::onLandmarkComboCommit, this)); - mCommitCallbackRegistrar.add("WMap.SearchResult", boost::bind(&LLFloaterWorldMap::onCommitSearchResult, this)); - mCommitCallbackRegistrar.add("WMap.GoHome", boost::bind(&LLFloaterWorldMap::onGoHome, this)); - mCommitCallbackRegistrar.add("WMap.Teleport", boost::bind(&LLFloaterWorldMap::onClickTeleportBtn, this)); - mCommitCallbackRegistrar.add("WMap.ShowTarget", boost::bind(&LLFloaterWorldMap::onShowTargetBtn, this)); - mCommitCallbackRegistrar.add("WMap.ShowAgent", boost::bind(&LLFloaterWorldMap::onShowAgentBtn, this)); - mCommitCallbackRegistrar.add("WMap.Clear", boost::bind(&LLFloaterWorldMap::onClearBtn, this)); - mCommitCallbackRegistrar.add("WMap.CopySLURL", boost::bind(&LLFloaterWorldMap::onCopySLURL, this)); - - gSavedSettings.getControl("PreferredMaturity")->getSignal()->connect(boost::bind(&LLFloaterWorldMap::onChangeMaturity, this)); -} - -// static -void* LLFloaterWorldMap::createWorldMapView(void* data) -{ - return new LLWorldMapView(); -} - -bool LLFloaterWorldMap::postBuild() -{ - mMapView = dynamic_cast(getChild("objects_mapview")); - mMapView->setPan(0, 0, true); - - LLComboBox *avatar_combo = getChild("friend combo"); - avatar_combo->selectFirstItem(); - avatar_combo->setPrearrangeCallback( boost::bind(&LLFloaterWorldMap::onAvatarComboPrearrange, this) ); - avatar_combo->setTextChangedCallback( boost::bind(&LLFloaterWorldMap::onComboTextEntry, this) ); - mListFriendCombo = dynamic_cast(avatar_combo); - - LLSearchEditor *location_editor = getChild("location"); - location_editor->setFocusChangedCallback(boost::bind(&LLFloaterWorldMap::onLocationFocusChanged, this, _1)); - location_editor->setTextChangedCallback( boost::bind(&LLFloaterWorldMap::onSearchTextEntry, this)); - - getChild("search_results")->setDoubleClickCallback( boost::bind(&LLFloaterWorldMap::onClickTeleportBtn, this)); - mListSearchResults = childGetListInterface("search_results"); - - LLComboBox *landmark_combo = getChild( "landmark combo"); - landmark_combo->selectFirstItem(); - landmark_combo->setPrearrangeCallback( boost::bind(&LLFloaterWorldMap::onLandmarkComboPrearrange, this) ); - landmark_combo->setTextChangedCallback( boost::bind(&LLFloaterWorldMap::onComboTextEntry, this) ); - mListLandmarkCombo = dynamic_cast(landmark_combo); - - F32 slider_zoom = mMapView->getZoom(); - getChild("zoom slider")->setValue(slider_zoom); - - getChild("expand_btn_panel")->setMouseDownCallback(boost::bind(&LLFloaterWorldMap::onExpandCollapseBtn, this)); - - setDefaultBtn(NULL); - - onChangeMaturity(); - - return true; -} - -// virtual -LLFloaterWorldMap::~LLFloaterWorldMap() -{ - // All cleaned up by LLView destructor - mMapView = NULL; - - // Inventory deletes all observers on shutdown - mInventory = NULL; - mInventoryObserver = NULL; - - // avatar tracker will delete this for us. - mFriendObserver = NULL; - - gFloaterWorldMap = NULL; - - mTeleportFinishConnection.disconnect(); -} - -//static -LLFloaterWorldMap* LLFloaterWorldMap::getInstance() -{ - return LLFloaterReg::getTypedInstance("world_map"); -} - -// virtual -void LLFloaterWorldMap::onClose(bool app_quitting) -{ - // While we're not visible, discard the overlay images we're using - LLWorldMap::getInstance()->clearImageRefs(); - mTeleportFinishConnection.disconnect(); -} - -// virtual -void LLFloaterWorldMap::onOpen(const LLSD& key) -{ - mTeleportFinishConnection = LLViewerParcelMgr::getInstance()-> - setTeleportFinishedCallback(boost::bind(&LLFloaterWorldMap::onTeleportFinished, this)); - - bool center_on_target = (key.asString() == "center"); - - mIsClosing = false; - - mMapView->clearLastClick(); - - { - // reset pan on show, so it centers on you again - if (!center_on_target) - { - mMapView->setPan(0, 0, true); - } - mMapView->updateVisibleBlocks(); - - // Reload items as they may have changed - LLWorldMap::getInstance()->reloadItems(); - - // We may already have a bounding box for the regions of the world, - // so use that to adjust the view. - adjustZoomSliderBounds(); - - // Could be first show - //LLFirstUse::useMap(); - - // Start speculative download of landmarks - const LLUUID landmark_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK); - LLInventoryModelBackgroundFetch::instance().start(landmark_folder_id); - - getChild("location")->setFocus( true); - gFocusMgr.triggerFocusFlash(); - - buildAvatarIDList(); - buildLandmarkIDLists(); - - // If nothing is being tracked, set flag so the user position will be found - mSetToUserPosition = ( LLTracker::getTrackingStatus() == LLTracker::TRACKING_NOTHING ); - } - - if (center_on_target) - { - centerOnTarget(false); - } -} - -// static -void LLFloaterWorldMap::reloadIcons(void*) -{ - LLWorldMap::getInstance()->reloadItems(); -} - -// virtual -bool LLFloaterWorldMap::handleHover(S32 x, S32 y, MASK mask) -{ - bool handled; - handled = LLFloater::handleHover(x, y, mask); - return handled; -} - -bool LLFloaterWorldMap::handleScrollWheel(S32 x, S32 y, S32 clicks) -{ - if (!isMinimized() && isFrontmost()) - { - S32 map_x = x - mMapView->getRect().mLeft; - S32 map_y = y - mMapView->getRect().mBottom; - if (mMapView->pointInView(map_x, map_y)) - { - F32 old_slider_zoom = (F32) getChild("zoom slider")->getValue().asReal(); - F32 slider_zoom = old_slider_zoom + ((F32) clicks * -0.3333f); - getChild("zoom slider")->setValue(LLSD(slider_zoom)); - mMapView->zoomWithPivot(slider_zoom, map_x, map_y); - return true; - } - } - - return LLFloater::handleScrollWheel(x, y, clicks); -} - - -// virtual -void LLFloaterWorldMap::reshape( S32 width, S32 height, bool called_from_parent ) -{ - LLFloater::reshape( width, height, called_from_parent ); -} - - -// virtual -void LLFloaterWorldMap::draw() -{ - static LLUIColor map_track_color = LLUIColorTable::instance().getColor("MapTrackColor", LLColor4::white); - static LLUIColor map_track_disabled_color = LLUIColorTable::instance().getColor("MapTrackDisabledColor", LLColor4::white); - - // On orientation island, users don't have a home location yet, so don't - // let them teleport "home". It dumps them in an often-crowed welcome - // area (infohub) and they get confused. JC - LLViewerRegion* regionp = gAgent.getRegion(); - bool agent_on_prelude = (regionp && regionp->isPrelude()); - bool enable_go_home = gAgent.isGodlike() || !agent_on_prelude; - getChildView("Go Home")->setEnabled(enable_go_home); - - updateLocation(); - - LLTracker::ETrackingStatus tracking_status = LLTracker::getTrackingStatus(); - if (LLTracker::TRACKING_AVATAR == tracking_status) - { - getChild("avatar_icon")->setColor( map_track_color); - } - else - { - getChild("avatar_icon")->setColor( map_track_disabled_color); - } - - if (LLTracker::TRACKING_LANDMARK == tracking_status) - { - getChild("landmark_icon")->setColor( map_track_color); - } - else - { - getChild("landmark_icon")->setColor( map_track_disabled_color); - } - - if (LLTracker::TRACKING_LOCATION == tracking_status) - { - getChild("location_icon")->setColor( map_track_color); - } - else - { - if (mCompletingRegionName != "") - { - F64 seconds = LLTimer::getElapsedSeconds(); - double value = fmod(seconds, 2); - value = 0.5 + 0.5*cos(value * F_PI); - LLColor4 loading_color(0.0, F32(value/2), F32(value), 1.0); - getChild("location_icon")->setColor( loading_color); - } - else - { - getChild("location_icon")->setColor( map_track_disabled_color); - } - } - - // check for completion of tracking data - if (mWaitingForTracker) - { - centerOnTarget(true); - } - - getChildView("Teleport")->setEnabled((bool)tracking_status); - // getChildView("Clear")->setEnabled((bool)tracking_status); - getChildView("Show Destination")->setEnabled((bool)tracking_status || LLWorldMap::getInstance()->isTracking()); - getChildView("copy_slurl")->setEnabled((mSLURL.isValid()) ); - - setMouseOpaque(true); - getDragHandle()->setMouseOpaque(true); - - mMapView->zoom((F32)getChild("zoom slider")->getValue().asReal()); - - // Enable/disable checkboxes depending on the zoom level - // If above threshold level (i.e. low res) -> Disable all checkboxes - // If under threshold level (i.e. high res) -> Enable all checkboxes - bool enable = mMapView->showRegionInfo(); - getChildView("people_chk")->setEnabled(enable); - getChildView("infohub_chk")->setEnabled(enable); - getChildView("telehub_chk")->setEnabled(enable); - getChildView("land_for_sale_chk")->setEnabled(enable); - getChildView("event_chk")->setEnabled(enable); - getChildView("events_mature_chk")->setEnabled(enable); - getChildView("events_adult_chk")->setEnabled(enable); - - LLFloater::draw(); -} - - -//------------------------------------------------------------------------- -// Internal utility functions -//------------------------------------------------------------------------- - - -void LLFloaterWorldMap::trackAvatar( const LLUUID& avatar_id, const std::string& name ) -{ - LLCtrlSelectionInterface *iface = childGetSelectionInterface("friend combo"); - if (!iface) return; - - buildAvatarIDList(); - if(iface->setCurrentByID(avatar_id) || gAgent.isGodlike()) - { - // *HACK: Adjust Z values automatically for liaisons & gods so - // they swoop down when they click on the map. Requested - // convenience. - if(gAgent.isGodlike()) - { - getChild("teleport_coordinate_z")->setValue(LLSD(200.f)); - } - // Don't re-request info if we already have it or we won't have it in time to teleport - if (mTrackedStatus != LLTracker::TRACKING_AVATAR || avatar_id != mTrackedAvatarID) - { - mTrackedStatus = LLTracker::TRACKING_AVATAR; - mTrackedAvatarID = avatar_id; - LLTracker::trackAvatar(avatar_id, name); - } - } - else - { - LLTracker::stopTracking(false); - } - setDefaultBtn("Teleport"); -} - -void LLFloaterWorldMap::trackLandmark( const LLUUID& landmark_item_id ) -{ - LLCtrlSelectionInterface *iface = childGetSelectionInterface("landmark combo"); - if (!iface) return; - - buildLandmarkIDLists(); - bool found = false; - S32 idx; - for (idx = 0; idx < mLandmarkItemIDList.size(); idx++) - { - if ( mLandmarkItemIDList.at(idx) == landmark_item_id) - { - found = true; - break; - } - } - - if (found && iface->setCurrentByID( landmark_item_id ) ) - { - LLUUID asset_id = mLandmarkAssetIDList.at( idx ); - std::string name; - LLComboBox* combo = getChild( "landmark combo"); - if (combo) name = combo->getSimple(); - mTrackedStatus = LLTracker::TRACKING_LANDMARK; - LLTracker::trackLandmark(mLandmarkAssetIDList.at( idx ), // assetID - mLandmarkItemIDList.at( idx ), // itemID - name); // name - - if( asset_id != sHomeID ) - { - // start the download process - gLandmarkList.getAsset( asset_id); - } - - // We have to download both region info and landmark data, so set busy. JC - // getWindow()->incBusyCount(); - } - else - { - LLTracker::stopTracking(false); - } - setDefaultBtn("Teleport"); -} - - -void LLFloaterWorldMap::trackEvent(const LLItemInfo &event_info) -{ - mTrackedStatus = LLTracker::TRACKING_LOCATION; - LLTracker::trackLocation(event_info.getGlobalPosition(), event_info.getName(), event_info.getToolTip(), LLTracker::LOCATION_EVENT); - setDefaultBtn("Teleport"); -} - -void LLFloaterWorldMap::trackGenericItem(const LLItemInfo &item) -{ - mTrackedStatus = LLTracker::TRACKING_LOCATION; - LLTracker::trackLocation(item.getGlobalPosition(), item.getName(), item.getToolTip(), LLTracker::LOCATION_ITEM); - setDefaultBtn("Teleport"); -} - -void LLFloaterWorldMap::trackLocation(const LLVector3d& pos_global) -{ - LLSimInfo* sim_info = LLWorldMap::getInstance()->simInfoFromPosGlobal(pos_global); - if (!sim_info) - { - // We haven't found a region for that point yet, leave the tracking to the world map - LLTracker::stopTracking(false); - LLWorldMap::getInstance()->setTracking(pos_global); - S32 world_x = S32(pos_global.mdV[0] / 256); - S32 world_y = S32(pos_global.mdV[1] / 256); - LLWorldMapMessage::getInstance()->sendMapBlockRequest(world_x, world_y, world_x, world_y, true); - setDefaultBtn(""); - - // clicked on a non-region - turn off coord display - enableTeleportCoordsDisplay( false ); - - return; - } - if (sim_info->isDown()) - { - // Down region. Show the blue circle of death! - // i.e. let the world map that this and tell it it's invalid - LLTracker::stopTracking(false); - LLWorldMap::getInstance()->setTracking(pos_global); - LLWorldMap::getInstance()->setTrackingInvalid(); - setDefaultBtn(""); - - // clicked on a down region - turn off coord display - enableTeleportCoordsDisplay( false ); - - return; - } - - std::string sim_name = sim_info->getName(); - F32 region_x = (F32)fmod( pos_global.mdV[VX], (F64)REGION_WIDTH_METERS ); - F32 region_y = (F32)fmod( pos_global.mdV[VY], (F64)REGION_WIDTH_METERS ); - std::string full_name = llformat("%s (%d, %d, %d)", - sim_name.c_str(), - ll_round(region_x), - ll_round(region_y), - ll_round((F32)pos_global.mdV[VZ])); - - std::string tooltip(""); - mTrackedStatus = LLTracker::TRACKING_LOCATION; - LLWorldMap::getInstance()->cancelTracking(); // The floater is taking over the tracking - LLTracker::trackLocation(pos_global, full_name, tooltip); - - LLVector3d coord_pos = LLTracker::getTrackedPositionGlobal(); - updateTeleportCoordsDisplay( coord_pos ); - - // we have a valid region - turn on coord display - enableTeleportCoordsDisplay( true ); - - setDefaultBtn("Teleport"); -} - -// enable/disable teleport destination coordinates -void LLFloaterWorldMap::enableTeleportCoordsDisplay( bool enabled ) -{ - childSetEnabled("teleport_coordinate_x", enabled ); - childSetEnabled("teleport_coordinate_y", enabled ); - childSetEnabled("teleport_coordinate_z", enabled ); -} - -// update display of teleport destination coordinates - pos is in global coordinates -void LLFloaterWorldMap::updateTeleportCoordsDisplay( const LLVector3d& pos ) -{ - // if we're going to update their value, we should also enable them - enableTeleportCoordsDisplay( true ); - - // convert global specified position to a local one - F32 region_local_x = (F32)fmod( pos.mdV[VX], (F64)REGION_WIDTH_METERS ); - F32 region_local_y = (F32)fmod( pos.mdV[VY], (F64)REGION_WIDTH_METERS ); - F32 region_local_z = (F32)llclamp( pos.mdV[VZ], 0.0, (F64)REGION_HEIGHT_METERS ); - - // write in the values - childSetValue("teleport_coordinate_x", region_local_x ); - childSetValue("teleport_coordinate_y", region_local_y ); - childSetValue("teleport_coordinate_z", region_local_z ); -} - -void LLFloaterWorldMap::updateLocation() -{ - bool gotSimName; - - LLTracker::ETrackingStatus status = LLTracker::getTrackingStatus(); - - // These values may get updated by a message, so need to check them every frame - // The fields may be changed by the user, so only update them if the data changes - LLVector3d pos_global = LLTracker::getTrackedPositionGlobal(); - if (pos_global.isExactlyZero()) - { - LLVector3d agentPos = gAgent.getPositionGlobal(); - - // Set to avatar's current postion if nothing is selected - if ( status == LLTracker::TRACKING_NOTHING && mSetToUserPosition ) - { - // Make sure we know where we are before setting the current user position - std::string agent_sim_name; - gotSimName = LLWorldMap::getInstance()->simNameFromPosGlobal( agentPos, agent_sim_name ); - if ( gotSimName ) - { - mSetToUserPosition = false; - - // Fill out the location field - getChild("location")->setValue(agent_sim_name); - - // update the coordinate display with location of avatar in region - updateTeleportCoordsDisplay( agentPos ); - - // Figure out where user is - // Set the current SLURL - mSLURL = LLSLURL(agent_sim_name, gAgent.getPositionGlobal()); - } - } - - return; // invalid location - } - std::string sim_name; - gotSimName = LLWorldMap::getInstance()->simNameFromPosGlobal( pos_global, sim_name ); - if ((status != LLTracker::TRACKING_NOTHING) && - (status != mTrackedStatus || pos_global != mTrackedLocation || sim_name != mTrackedSimName)) - { - mTrackedStatus = status; - mTrackedLocation = pos_global; - mTrackedSimName = sim_name; - - if (status == LLTracker::TRACKING_AVATAR) - { - // *HACK: Adjust Z values automatically for liaisons & - // gods so they swoop down when they click on the - // map. Requested convenience. - if(gAgent.isGodlike()) - { - pos_global[2] = 200; - } - } - - getChild("location")->setValue(sim_name); - - // refresh coordinate display to reflect where user clicked. - LLVector3d coord_pos = LLTracker::getTrackedPositionGlobal(); - updateTeleportCoordsDisplay( coord_pos ); - - // simNameFromPosGlobal can fail, so don't give the user an invalid SLURL - if ( gotSimName ) - { - mSLURL = LLSLURL(sim_name, pos_global); - } - else - { // Empty SLURL will disable the "Copy SLURL to clipboard" button - mSLURL = LLSLURL(); - } - } -} - -void LLFloaterWorldMap::trackURL(const std::string& region_name, S32 x_coord, S32 y_coord, S32 z_coord) -{ - LLSimInfo* sim_info = LLWorldMap::getInstance()->simInfoFromName(region_name); - z_coord = llclamp(z_coord, 0, 4096); - if (sim_info) - { - LLVector3 local_pos; - local_pos.mV[VX] = (F32)x_coord; - local_pos.mV[VY] = (F32)y_coord; - local_pos.mV[VZ] = (F32)z_coord; - LLVector3d global_pos = sim_info->getGlobalPos(local_pos); - trackLocation(global_pos); - setDefaultBtn("Teleport"); - } - else - { - // fill in UI based on URL - gFloaterWorldMap->getChild("location")->setValue(region_name); - - // Save local coords to highlight position after region global - // position is returned. - gFloaterWorldMap->mCompletingRegionPos.set( - (F32)x_coord, (F32)y_coord, (F32)z_coord); - - // pass sim name to combo box - gFloaterWorldMap->mCompletingRegionName = region_name; - LLWorldMapMessage::getInstance()->sendNamedRegionRequest(region_name); - LLStringUtil::toLower(gFloaterWorldMap->mCompletingRegionName); - LLWorldMap::getInstance()->setTrackingCommit(); - } -} - -void LLFloaterWorldMap::observeInventory(LLInventoryModel* model) -{ - if(mInventory) - { - mInventory->removeObserver(mInventoryObserver); - delete mInventoryObserver; - mInventory = NULL; - mInventoryObserver = NULL; - } - if(model) - { - mInventory = model; - mInventoryObserver = new LLMapInventoryObserver; - // Inventory deletes all observers on shutdown - mInventory->addObserver(mInventoryObserver); - inventoryChanged(); - } -} - -void LLFloaterWorldMap::inventoryChanged() -{ - if(!LLTracker::getTrackedLandmarkItemID().isNull()) - { - LLUUID item_id = LLTracker::getTrackedLandmarkItemID(); - buildLandmarkIDLists(); - trackLandmark(item_id); - } -} - -void LLFloaterWorldMap::observeFriends() -{ - if(!mFriendObserver) - { - mFriendObserver = new LLMapFriendObserver; - LLAvatarTracker::instance().addObserver(mFriendObserver); - friendsChanged(); - } -} - -void LLFloaterWorldMap::friendsChanged() -{ - LLAvatarTracker& t = LLAvatarTracker::instance(); - const LLUUID& avatar_id = t.getAvatarID(); - buildAvatarIDList(); - if(avatar_id.notNull()) - { - LLCtrlSelectionInterface *iface = childGetSelectionInterface("friend combo"); - const LLRelationship* buddy_info = t.getBuddyInfo(avatar_id); - if(!iface || - !iface->setCurrentByID(avatar_id) || - (buddy_info && !buddy_info->isRightGrantedFrom(LLRelationship::GRANT_MAP_LOCATION)) || - gAgent.isGodlike()) - { - LLTracker::stopTracking(false); - } - } -} - -// No longer really builds a list. Instead, just updates mAvatarCombo. -void LLFloaterWorldMap::buildAvatarIDList() -{ - LLCtrlListInterface *list = mListFriendCombo; - if (!list) return; - - // Delete all but the "None" entry - S32 list_size = list->getItemCount(); - if (list_size > 1) - { - list->selectItemRange(1, -1); - list->operateOnSelection(LLCtrlListInterface::OP_DELETE); - } - - // Get all of the calling cards for avatar that are currently online - LLCollectMappableBuddies collector; - LLAvatarTracker::instance().applyFunctor(collector); - LLCollectMappableBuddies::buddy_map_t::iterator it; - LLCollectMappableBuddies::buddy_map_t::iterator end; - it = collector.mMappable.begin(); - end = collector.mMappable.end(); - for( ; it != end; ++it) - { - list->addSimpleElement((*it).second, ADD_BOTTOM, (*it).first); - } - - list->setCurrentByID( LLAvatarTracker::instance().getAvatarID() ); - list->selectFirstItem(); -} - - -void LLFloaterWorldMap::buildLandmarkIDLists() -{ - LLCtrlListInterface *list = mListLandmarkCombo; - if (!list) return; - - // Delete all but the "None" entry - S32 list_size = list->getItemCount(); - if (list_size > 1) - { - list->selectItemRange(1, -1); - list->operateOnSelection(LLCtrlListInterface::OP_DELETE); - } - - mLandmarkItemIDList.clear(); - mLandmarkAssetIDList.clear(); - - // Get all of the current landmarks - mLandmarkAssetIDList.push_back( LLUUID::null ); - mLandmarkItemIDList.push_back( LLUUID::null ); - - mLandmarkAssetIDList.push_back( sHomeID ); - mLandmarkItemIDList.push_back( sHomeID ); - - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - LLIsType is_landmark(LLAssetType::AT_LANDMARK); - gInventory.collectDescendentsIf(gInventory.getRootFolderID(), - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - is_landmark); - - std::sort(items.begin(), items.end(), LLViewerInventoryItem::comparePointers()); - - mLandmarkAssetIDList.reserve(mLandmarkAssetIDList.size() + items.size()); - mLandmarkItemIDList.reserve(mLandmarkItemIDList.size() + items.size()); - - S32 count = items.size(); - for(S32 i = 0; i < count; ++i) - { - LLInventoryItem* item = items.at(i); - - list->addSimpleElement(item->getName(), ADD_BOTTOM, item->getUUID()); - - mLandmarkAssetIDList.push_back( item->getAssetUUID() ); - mLandmarkItemIDList.push_back( item->getUUID() ); - } - - list->selectFirstItem(); -} - - -F32 LLFloaterWorldMap::getDistanceToDestination(const LLVector3d &destination, - F32 z_attenuation) const -{ - LLVector3d delta = destination - gAgent.getPositionGlobal(); - // by attenuating the z-component we effectively - // give more weight to the x-y plane - delta.mdV[VZ] *= z_attenuation; - F32 distance = (F32)delta.magVec(); - return distance; -} - - -void LLFloaterWorldMap::clearLocationSelection(bool clear_ui, bool dest_reached) -{ - LLCtrlListInterface *list = mListSearchResults; - if (list && (!dest_reached || (list->getItemCount() == 1))) - { - list->operateOnAll(LLCtrlListInterface::OP_DELETE); - } - LLWorldMap::getInstance()->cancelTracking(); - mCompletingRegionName = ""; -} - - -void LLFloaterWorldMap::clearLandmarkSelection(bool clear_ui) -{ - if (clear_ui || !childHasKeyboardFocus("landmark combo")) - { - LLCtrlListInterface *list = mListLandmarkCombo; - if (list) - { - list->selectByValue( "None" ); - } - } -} - - -void LLFloaterWorldMap::clearAvatarSelection(bool clear_ui) -{ - if (clear_ui || !childHasKeyboardFocus("friend combo")) - { - mTrackedStatus = LLTracker::TRACKING_NOTHING; - LLCtrlListInterface *list = mListFriendCombo; - if (list && list->getSelectedValue().asString() != "None") - { - list->selectByValue( "None" ); - } - } -} - - -// Adjust the maximally zoomed out limit of the zoom slider so you -// can see the whole world, plus a little. -void LLFloaterWorldMap::adjustZoomSliderBounds() -{ - // Merov: we switched from using the "world size" (which varies depending where the user went) to a fixed - // width of 512 regions max visible at a time. This makes the zoom slider works in a consistent way across - // sessions and doesn't prevent the user to pan the world if it was to grow a lot beyond that limit. - // Currently (01/26/09), this value allows the whole grid to be visible in a 1024x1024 window. - S32 world_width_regions = MAX_VISIBLE_REGIONS; - S32 world_height_regions = MAX_VISIBLE_REGIONS; - - // Find how much space we have to display the world - LLRect view_rect = mMapView->getRect(); - - // View size in pixels - S32 view_width = view_rect.getWidth(); - S32 view_height = view_rect.getHeight(); - - // Pixels per region to display entire width/height - F32 width_pixels_per_region = (F32) view_width / (F32) world_width_regions; - F32 height_pixels_per_region = (F32) view_height / (F32) world_height_regions; - - F32 pixels_per_region = llmin(width_pixels_per_region, - height_pixels_per_region); - - // Round pixels per region to an even number of slider increments - S32 slider_units = llfloor(pixels_per_region / 0.2f); - pixels_per_region = slider_units * 0.2f; - - // Make sure the zoom slider can be moved at least a little bit. - // Likewise, less than the increment pixels per region is just silly. - pixels_per_region = llclamp(pixels_per_region, 1.f, ZOOM_MAX); - - F32 min_power = log(pixels_per_region/256.f)/log(2.f); - - getChild("zoom slider")->setMinValue(min_power); -} - - -//------------------------------------------------------------------------- -// User interface widget callbacks -//------------------------------------------------------------------------- - -void LLFloaterWorldMap::onGoHome() -{ - gAgent.teleportHome(); - closeFloater(); -} - - -void LLFloaterWorldMap::onLandmarkComboPrearrange( ) -{ - if( mIsClosing ) - { - return; - } - - LLCtrlListInterface *list = mListLandmarkCombo; - if (!list) return; - - LLUUID current_choice = list->getCurrentID(); - - buildLandmarkIDLists(); - - if( current_choice.isNull() || !list->setCurrentByID( current_choice ) ) - { - LLTracker::stopTracking(false); - } - -} - -void LLFloaterWorldMap::onComboTextEntry() -{ - // Reset the tracking whenever we start typing into any of the search fields, - // so that hitting does an auto-complete versus teleporting us to the - // previously selected landmark/friend. - LLTracker::stopTracking(false); -} - -void LLFloaterWorldMap::onSearchTextEntry( ) -{ - onComboTextEntry(); - updateSearchEnabled(); -} - - -void LLFloaterWorldMap::onLandmarkComboCommit() -{ - if( mIsClosing ) - { - return; - } - - LLCtrlListInterface *list = mListLandmarkCombo; - if (!list) return; - - LLUUID asset_id; - LLUUID item_id = list->getCurrentID(); - - LLTracker::stopTracking(false); - - //RN: stopTracking() clears current combobox selection, need to reassert it here - list->setCurrentByID(item_id); - - if( item_id.isNull() ) - { - } - else if( item_id == sHomeID ) - { - asset_id = sHomeID; - } - else - { - LLInventoryItem* item = gInventory.getItem( item_id ); - if( item ) - { - asset_id = item->getAssetUUID(); - } - else - { - // Something went wrong, so revert to a safe value. - item_id.setNull(); - } - } - - trackLandmark( item_id); - onShowTargetBtn(); - - // Reset to user postion if nothing is tracked - mSetToUserPosition = ( LLTracker::getTrackingStatus() == LLTracker::TRACKING_NOTHING ); -} - -// static -void LLFloaterWorldMap::onAvatarComboPrearrange( ) -{ - if( mIsClosing ) - { - return; - } - - LLCtrlListInterface *list = mListFriendCombo; - if (!list) return; - - LLUUID current_choice; - - if( LLAvatarTracker::instance().haveTrackingInfo() ) - { - current_choice = LLAvatarTracker::instance().getAvatarID(); - } - - buildAvatarIDList(); - - if( !list->setCurrentByID( current_choice ) || current_choice.isNull() ) - { - LLTracker::stopTracking(false); - } -} - -void LLFloaterWorldMap::onAvatarComboCommit() -{ - if( mIsClosing ) - { - return; - } - - LLCtrlListInterface *list = mListFriendCombo; - if (!list) return; - - const LLUUID& new_avatar_id = list->getCurrentID(); - if (new_avatar_id.notNull()) - { - std::string name; - LLComboBox* combo = getChild("friend combo"); - if (combo) name = combo->getSimple(); - trackAvatar(new_avatar_id, name); - onShowTargetBtn(); - } - else - { // Reset to user postion if nothing is tracked - mSetToUserPosition = ( LLTracker::getTrackingStatus() == LLTracker::TRACKING_NOTHING ); - } -} - -void LLFloaterWorldMap::avatarTrackFromSlapp( const LLUUID& id ) -{ - trackAvatar( id, "av" ); - onShowTargetBtn(); -} - -void LLFloaterWorldMap::onLocationFocusChanged( LLFocusableElement* focus ) -{ - updateSearchEnabled(); -} - -void LLFloaterWorldMap::updateSearchEnabled() -{ - if (childHasKeyboardFocus("location") && - getChild("location")->getValue().asString().length() > 0) - { - setDefaultBtn("DoSearch"); - } - else - { - setDefaultBtn(NULL); - } -} - -void LLFloaterWorldMap::onLocationCommit() -{ - if( mIsClosing ) - { - return; - } - - clearLocationSelection(false); - mCompletingRegionName = ""; - mLastRegionName = ""; - - std::string str = getChild("location")->getValue().asString(); - - // Trim any leading and trailing spaces in the search target - std::string saved_str = str; - LLStringUtil::trim( str ); - if ( str != saved_str ) - { // Set the value in the UI if any spaces were removed - getChild("location")->setValue(str); - } - - // Don't try completing empty name (STORM-1427). - if (str.empty()) - { - return; - } - - LLStringUtil::toLower(str); - mCompletingRegionName = str; - LLWorldMap::getInstance()->setTrackingCommit(); - if (str.length() >= 3) - { - LLWorldMapMessage::getInstance()->sendNamedRegionRequest(str); - } - else - { - str += "#"; - LLWorldMapMessage::getInstance()->sendNamedRegionRequest(str); - } -} - -void LLFloaterWorldMap::onCoordinatesCommit() -{ - if( mIsClosing ) - { - return; - } - - S32 x_coord = (S32)childGetValue("teleport_coordinate_x").asReal(); - S32 y_coord = (S32)childGetValue("teleport_coordinate_y").asReal(); - S32 z_coord = (S32)childGetValue("teleport_coordinate_z").asReal(); - - const std::string region_name = childGetValue("location").asString(); - - trackURL( region_name, x_coord, y_coord, z_coord ); -} - -void LLFloaterWorldMap::onClearBtn() -{ - mTrackedStatus = LLTracker::TRACKING_NOTHING; - LLTracker::stopTracking(true); - LLWorldMap::getInstance()->cancelTracking(); - mSLURL = LLSLURL(); // Clear the SLURL since it's invalid - mSetToUserPosition = true; // Revert back to the current user position -} - -void LLFloaterWorldMap::onShowTargetBtn() -{ - centerOnTarget(true); -} - -void LLFloaterWorldMap::onShowAgentBtn() -{ - mMapView->setPanWithInterpTime(0, 0, false, 0.1f); // false == animate - // Set flag so user's location will be displayed if not tracking anything else - mSetToUserPosition = true; -} - -void LLFloaterWorldMap::onClickTeleportBtn() -{ - teleport(); -} - -void LLFloaterWorldMap::onCopySLURL() -{ - getWindow()->copyTextToClipboard(utf8str_to_wstring(mSLURL.getSLURLString())); - - LLSD args; - args["SLURL"] = mSLURL.getSLURLString(); - - LLNotificationsUtil::add("CopySLURL", args); -} - -void LLFloaterWorldMap::onExpandCollapseBtn() -{ - LLLayoutStack* floater_stack = getChild("floater_map_stack"); - LLLayoutPanel* controls_panel = getChild("controls_lp"); - - bool toggle_collapse = !controls_panel->isCollapsed(); - floater_stack->collapsePanel(controls_panel, toggle_collapse); - floater_stack->updateLayout(); - - std::string image_name = getString(toggle_collapse ? "expand_icon" : "collapse_icon"); - std::string tooltip = getString(toggle_collapse ? "expand_tooltip" : "collapse_tooltip"); - getChild("expand_collapse_icon")->setImage(LLUI::getUIImage(image_name)); - getChild("expand_collapse_icon")->setToolTip(tooltip); - getChild("expand_btn_panel")->setToolTip(tooltip); -} - -// protected -void LLFloaterWorldMap::centerOnTarget(bool animate) -{ - LLVector3d pos_global; - if(LLTracker::getTrackingStatus() != LLTracker::TRACKING_NOTHING) - { - LLVector3d tracked_position = LLTracker::getTrackedPositionGlobal(); - //RN: tracker doesn't allow us to query completion, so we check for a tracking position of - // absolute zero, and keep trying in the draw loop - if (tracked_position.isExactlyZero()) - { - mWaitingForTracker = true; - return; - } - else - { - // We've got the position finally, so we're no longer busy. JC - // getWindow()->decBusyCount(); - pos_global = LLTracker::getTrackedPositionGlobal() - gAgentCamera.getCameraPositionGlobal(); - } - } - else if(LLWorldMap::getInstance()->isTracking()) - { - pos_global = LLWorldMap::getInstance()->getTrackedPositionGlobal() - gAgentCamera.getCameraPositionGlobal();; - - - - } - else - { - // default behavior = center on agent - pos_global.clearVec(); - } - - F64 map_scale = (F64)mMapView->getScale(); - mMapView->setPanWithInterpTime(-llfloor((F32)(pos_global.mdV[VX] * map_scale / REGION_WIDTH_METERS)), - -llfloor((F32)(pos_global.mdV[VY] * map_scale / REGION_WIDTH_METERS)), - !animate, 0.1f); - mWaitingForTracker = false; -} - -// protected -void LLFloaterWorldMap::fly() -{ - LLVector3d pos_global = LLTracker::getTrackedPositionGlobal(); - - // Start the autopilot and close the floater, - // so we can see where we're flying - if (!pos_global.isExactlyZero()) - { - gAgent.startAutoPilotGlobal( pos_global ); - closeFloater(); - } - else - { - make_ui_sound("UISndInvalidOp"); - } -} - - -// protected -void LLFloaterWorldMap::teleport() -{ - bool teleport_home = false; - LLVector3d pos_global; - LLAvatarTracker& av_tracker = LLAvatarTracker::instance(); - - LLTracker::ETrackingStatus tracking_status = LLTracker::getTrackingStatus(); - if (LLTracker::TRACKING_AVATAR == tracking_status - && av_tracker.haveTrackingInfo() ) - { - pos_global = av_tracker.getGlobalPos(); - pos_global.mdV[VZ] = getChild("teleport_coordinate_z")->getValue(); - } - else if ( LLTracker::TRACKING_LANDMARK == tracking_status) - { - if( LLTracker::getTrackedLandmarkAssetID() == sHomeID ) - { - teleport_home = true; - } - else - { - LLLandmark* landmark = gLandmarkList.getAsset( LLTracker::getTrackedLandmarkAssetID() ); - LLUUID region_id; - if(landmark - && !landmark->getGlobalPos(pos_global) - && landmark->getRegionID(region_id)) - { - LLLandmark::requestRegionHandle( - gMessageSystem, - gAgent.getRegionHost(), - region_id, - NULL); - } - } - } - else if ( LLTracker::TRACKING_LOCATION == tracking_status) - { - pos_global = LLTracker::getTrackedPositionGlobal(); - } - else - { - make_ui_sound("UISndInvalidOp"); - } - - // Do the teleport, which will also close the floater - if (teleport_home) - { - gAgent.teleportHome(); - } - else if (!pos_global.isExactlyZero()) - { - if(LLTracker::TRACKING_LANDMARK == tracking_status) - { - gAgent.teleportViaLandmark(LLTracker::getTrackedLandmarkAssetID()); - } - else - { - gAgent.teleportViaLocation( pos_global ); - } - } -} - -void LLFloaterWorldMap::flyToLandmark() -{ - LLVector3d destination_pos_global; - if( !LLTracker::getTrackedLandmarkAssetID().isNull() ) - { - if (LLTracker::hasLandmarkPosition()) - { - gAgent.startAutoPilotGlobal( LLTracker::getTrackedPositionGlobal() ); - } - } -} - -void LLFloaterWorldMap::teleportToLandmark() -{ - bool has_destination = false; - LLUUID destination_id; // Null means "home" - - if( LLTracker::getTrackedLandmarkAssetID() == sHomeID ) - { - has_destination = true; - } - else - { - LLLandmark* landmark = gLandmarkList.getAsset( LLTracker::getTrackedLandmarkAssetID() ); - LLVector3d global_pos; - if(landmark && landmark->getGlobalPos(global_pos)) - { - destination_id = LLTracker::getTrackedLandmarkAssetID(); - has_destination = true; - } - else if(landmark) - { - // pop up an anonymous request request. - LLUUID region_id; - if(landmark->getRegionID(region_id)) - { - LLLandmark::requestRegionHandle( - gMessageSystem, - gAgent.getRegionHost(), - region_id, - NULL); - } - } - } - - if( has_destination ) - { - gAgent.teleportViaLandmark( destination_id ); - } -} - - -void LLFloaterWorldMap::teleportToAvatar() -{ - LLAvatarTracker& av_tracker = LLAvatarTracker::instance(); - if(av_tracker.haveTrackingInfo()) - { - LLVector3d pos_global = av_tracker.getGlobalPos(); - gAgent.teleportViaLocation( pos_global ); - } -} - - -void LLFloaterWorldMap::flyToAvatar() -{ - if( LLAvatarTracker::instance().haveTrackingInfo() ) - { - gAgent.startAutoPilotGlobal( LLAvatarTracker::instance().getGlobalPos() ); - } -} - -void LLFloaterWorldMap::updateSims(bool found_null_sim) -{ - if (mCompletingRegionName == "") - { - return; - } - - LLScrollListCtrl *list = getChild("search_results"); - list->operateOnAll(LLCtrlListInterface::OP_DELETE); - - S32 name_length = mCompletingRegionName.length(); - - LLSD match; - - S32 num_results = 0; - - std::vector > sim_info_vec(LLWorldMap::getInstance()->getRegionMap().begin(), LLWorldMap::getInstance()->getRegionMap().end()); - std::sort(sim_info_vec.begin(), sim_info_vec.end(), SortRegionNames()); - - for (std::vector >::const_iterator it = sim_info_vec.begin(); it != sim_info_vec.end(); ++it) - { - LLSimInfo* info = it->second; - std::string sim_name_lower = info->getName(); - LLStringUtil::toLower(sim_name_lower); - - if (sim_name_lower.substr(0, name_length) == mCompletingRegionName) - { - if (sim_name_lower == mCompletingRegionName) - { - match = info->getName(); - } - - LLSD value; - value["id"] = info->getName(); - value["columns"][0]["column"] = "sim_name"; - value["columns"][0]["value"] = info->getName(); - list->addElement(value); - num_results++; - } - } - - if (found_null_sim) - { - mCompletingRegionName = ""; - } - - if (num_results > 0) - { - // if match found, highlight it and go - if (!match.isUndefined()) - { - list->selectByValue(match); - } - // else select first found item - else - { - list->selectFirstItem(); - } - getChild("search_results")->setFocus(true); - onCommitSearchResult(); - } - else - { - // if we found nothing, say "none" - list->setCommentText(LLTrans::getString("worldmap_results_none_found")); - list->operateOnAll(LLCtrlListInterface::OP_DESELECT); - } -} - -void LLFloaterWorldMap::onTeleportFinished() -{ - if(isInVisibleChain()) - { - mMapView->setPan(0, 0, true); - } -} - -void LLFloaterWorldMap::onCommitSearchResult() -{ - LLCtrlListInterface *list = mListSearchResults; - if (!list) return; - - LLSD selected_value = list->getSelectedValue(); - std::string sim_name = selected_value.asString(); - if (sim_name.empty()) - { - return; - } - LLStringUtil::toLower(sim_name); - - std::map::const_iterator it; - for (it = LLWorldMap::getInstance()->getRegionMap().begin(); it != LLWorldMap::getInstance()->getRegionMap().end(); ++it) - { - LLSimInfo* info = it->second; - - if (info->isName(sim_name)) - { - LLVector3d pos_global = info->getGlobalOrigin(); - - const F64 SIM_COORD_DEFAULT = 128.0; - LLVector3 pos_local(SIM_COORD_DEFAULT, SIM_COORD_DEFAULT, 0.0f); - - // Did this value come from a trackURL() request? - if (!mCompletingRegionPos.isExactlyZero()) - { - pos_local = mCompletingRegionPos; - mCompletingRegionPos.clear(); - } - pos_global.mdV[VX] += (F64)pos_local.mV[VX]; - pos_global.mdV[VY] += (F64)pos_local.mV[VY]; - pos_global.mdV[VZ] = (F64)pos_local.mV[VZ]; - - getChild("location")->setValue(sim_name); - trackLocation(pos_global); - setDefaultBtn("Teleport"); - break; - } - } - - onShowTargetBtn(); -} - -void LLFloaterWorldMap::onChangeMaturity() -{ - bool can_access_mature = gAgent.canAccessMature(); - bool can_access_adult = gAgent.canAccessAdult(); - - getChildView("events_mature_icon")->setVisible( can_access_mature); - getChildView("events_mature_label")->setVisible( can_access_mature); - getChildView("events_mature_chk")->setVisible( can_access_mature); - - getChildView("events_adult_icon")->setVisible( can_access_adult); - getChildView("events_adult_label")->setVisible( can_access_adult); - getChildView("events_adult_chk")->setVisible( can_access_adult); - - // disable mature / adult events. - if (!can_access_mature) - { - gSavedSettings.setBOOL("ShowMatureEvents", false); - } - if (!can_access_adult) - { - gSavedSettings.setBOOL("ShowAdultEvents", false); - } -} - -void LLFloaterWorldMap::onFocusLost() -{ - gViewerWindow->showCursor(); - mMapView->mPanning = false; -} - -LLPanelHideBeacon::LLPanelHideBeacon() : - mHideButton(NULL) -{ -} - -// static -LLPanelHideBeacon* LLPanelHideBeacon::getInstance() -{ - static LLPanelHideBeacon* panel = getPanelHideBeacon(); - return panel; -} - - -bool LLPanelHideBeacon::postBuild() -{ - mHideButton = getChild("hide_beacon_btn"); - mHideButton->setCommitCallback(boost::bind(&LLPanelHideBeacon::onHideButtonClick, this)); - - gViewerWindow->setOnWorldViewRectUpdated(boost::bind(&LLPanelHideBeacon::updatePosition, this)); - - return true; -} - -//virtual -void LLPanelHideBeacon::draw() -{ - if (!LLTracker::isTracking(NULL)) - { - mHideButton->setVisible(false); - return; - } - mHideButton->setVisible(true); - updatePosition(); - LLPanel::draw(); -} - -//virtual -void LLPanelHideBeacon::setVisible(bool visible) -{ - if (gAgentCamera.getCameraMode() == CAMERA_MODE_MOUSELOOK) visible = false; - - if (visible) - { - updatePosition(); - } - - LLPanel::setVisible(visible); -} - - -//static -LLPanelHideBeacon* LLPanelHideBeacon::getPanelHideBeacon() -{ - LLPanelHideBeacon* panel = new LLPanelHideBeacon(); - panel->buildFromFile("panel_hide_beacon.xml"); - - LL_INFOS() << "Build LLPanelHideBeacon panel" << LL_ENDL; - - panel->updatePosition(); - return panel; -} - -void LLPanelHideBeacon::onHideButtonClick() -{ - LLFloaterWorldMap* instance = LLFloaterWorldMap::getInstance(); - if (instance) - { - instance->onClearBtn(); - } -} - -/** -* Updates position of the panel (similar to Stand & Stop Flying panel). -*/ -void LLPanelHideBeacon::updatePosition() -{ - S32 bottom_tb_center = 0; - if (LLToolBar* toolbar_bottom = gToolBarView->getToolbar(LLToolBarEnums::TOOLBAR_BOTTOM)) - { - bottom_tb_center = toolbar_bottom->getRect().getCenterX(); - } - - S32 left_tb_width = 0; - if (LLToolBar* toolbar_left = gToolBarView->getToolbar(LLToolBarEnums::TOOLBAR_LEFT)) - { - left_tb_width = toolbar_left->getRect().getWidth(); - } - - if (gToolBarView != NULL && gToolBarView->getToolbar(LLToolBarEnums::TOOLBAR_LEFT)->hasButtons()) - { - S32 x_pos = bottom_tb_center - getRect().getWidth() / 2 - left_tb_width; - setOrigin( x_pos + HIDE_BEACON_PAD, 0); - } - else - { - S32 x_pos = bottom_tb_center - getRect().getWidth() / 2; - setOrigin( x_pos + HIDE_BEACON_PAD, 0); - } -} +/** + * @file llfloaterworldmap.cpp + * @author James Cook, Tom Yedwab + * @brief LLFloaterWorldMap class implementation + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/* + * Map of the entire world, with multiple background images, + * avatar tracking, teleportation by double-click, etc. + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterworldmap.h" + +#include "llagent.h" +#include "llagentcamera.h" +#include "llbutton.h" +#include "llcallingcard.h" +#include "llcombobox.h" +#include "llviewercontrol.h" +#include "llcommandhandler.h" +#include "lldraghandle.h" +//#include "llfirstuse.h" +#include "llfloaterreg.h" // getTypedInstance() +#include "llfocusmgr.h" +#include "lliconctrl.h" +#include "llinventoryfunctions.h" +#include "llinventorymodel.h" +#include "llinventorymodelbackgroundfetch.h" +#include "llinventoryobserver.h" +#include "lllandmarklist.h" +#include "llsearcheditor.h" +#include "llnotificationsutil.h" +#include "llregionhandle.h" +#include "llscrolllistctrl.h" +#include "llslurl.h" +#include "lltextbox.h" +#include "lltoolbarview.h" +#include "lltracker.h" +#include "lltrans.h" +#include "llviewerinventory.h" // LLViewerInventoryItem +#include "llviewermenu.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "llviewerstats.h" +#include "llviewertexture.h" +#include "llviewerwindow.h" +#include "llworldmap.h" +#include "llworldmapmessage.h" +#include "llworldmapview.h" +#include "lluictrlfactory.h" +#include "llappviewer.h" +#include "llmapimagetype.h" +#include "llweb.h" +#include "llsliderctrl.h" +#include "message.h" +#include "llwindow.h" // copyTextToClipboard() +#include + +//--------------------------------------------------------------------------- +// Constants +//--------------------------------------------------------------------------- + +// Merov: we switched from using the "world size" (which varies depending where the user went) to a fixed +// width of 512 regions max visible at a time. This makes the zoom slider works in a consistent way across +// sessions and doesn't prevent the user to pan the world if it was to grow a lot beyond that limit. +// Currently (01/26/09), this value allows the whole grid to be visible in a 1024x1024 window. +static const S32 MAX_VISIBLE_REGIONS = 512; + + +const S32 HIDE_BEACON_PAD = 133; + +// It would be more logical to have this inside the method where it is used but to compile under gcc this +// struct has to be here. +struct SortRegionNames +{ + inline bool operator ()(std::pair const& _left, std::pair const& _right) + { + return(LLStringUtil::compareInsensitive(_left.second->getName(), _right.second->getName()) < 0); + } +}; + +enum EPanDirection +{ + PAN_UP, + PAN_DOWN, + PAN_LEFT, + PAN_RIGHT +}; + +// Values in pixels per region +static const F32 ZOOM_MAX = 128.f; + +//--------------------------------------------------------------------------- +// Globals +//--------------------------------------------------------------------------- + +// handle secondlife:///app/worldmap/{NAME}/{COORDS} URLs +class LLWorldMapHandler : public LLCommandHandler +{ +public: + LLWorldMapHandler() : LLCommandHandler("worldmap", UNTRUSTED_THROTTLE) + { + } + + virtual bool canHandleUntrusted( + const LLSD& params, + const LLSD& query_map, + LLMediaCtrl* web, + const std::string& nav_type) + { + if (nav_type == NAV_TYPE_CLICKED + || nav_type == NAV_TYPE_EXTERNAL) + { + // NAV_TYPE_EXTERNAL will be throttled + return true; + } + + return false; + } + + bool handle(const LLSD& params, + const LLSD& query_map, + const std::string& grid, + LLMediaCtrl* web) + { + if (params.size() == 0) + { + // support the secondlife:///app/worldmap SLapp + LLFloaterReg::showInstance("world_map", "center"); + return true; + } + + // support the secondlife:///app/worldmap/{LOCATION}/{COORDS} SLapp + const std::string region_name = LLURI::unescape(params[0].asString()); + S32 x = (params.size() > 1) ? params[1].asInteger() : 128; + S32 y = (params.size() > 2) ? params[2].asInteger() : 128; + S32 z = (params.size() > 3) ? params[3].asInteger() : 0; + + LLFloaterWorldMap::getInstance()->trackURL(region_name, x, y, z); + LLFloaterReg::showInstance("world_map", "center"); + + return true; + } +}; +LLWorldMapHandler gWorldMapHandler; + +// SocialMap handler secondlife:///app/maptrackavatar/id +class LLMapTrackAvatarHandler : public LLCommandHandler +{ +public: + LLMapTrackAvatarHandler() : LLCommandHandler("maptrackavatar", UNTRUSTED_THROTTLE) + { + } + + virtual bool canHandleUntrusted( + const LLSD& params, + const LLSD& query_map, + LLMediaCtrl* web, + const std::string& nav_type) + { + if (params.size() < 1) + { + return true; // don't block, will fail later + } + + if (nav_type == NAV_TYPE_CLICKED + || nav_type == NAV_TYPE_EXTERNAL) + { + // NAV_TYPE_EXTERNAL will be throttled + return true; + } + + return false; + } + + bool handle(const LLSD& params, + const LLSD& query_map, + const std::string& grid, + LLMediaCtrl* web) + { + //Make sure we have some parameters + if (params.size() == 0) + { + return false; + } + + //Get the ID + LLUUID id; + if (!id.set( params[0], false )) + { + return false; + } + + LLFloaterWorldMap::getInstance()->avatarTrackFromSlapp( id ); + LLFloaterReg::showInstance( "world_map", "center" ); + + return true; + } +}; +LLMapTrackAvatarHandler gMapTrackAvatar; + +LLFloaterWorldMap* gFloaterWorldMap = NULL; + +class LLMapInventoryObserver : public LLInventoryObserver +{ +public: + LLMapInventoryObserver() {} + virtual ~LLMapInventoryObserver() {} + virtual void changed(U32 mask); +}; + +void LLMapInventoryObserver::changed(U32 mask) +{ + // if there's a change we're interested in. + if((mask & (LLInventoryObserver::CALLING_CARD | LLInventoryObserver::ADD | + LLInventoryObserver::REMOVE)) != 0) + { + gFloaterWorldMap->inventoryChanged(); + } +} + +class LLMapFriendObserver : public LLFriendObserver +{ +public: + LLMapFriendObserver() {} + virtual ~LLMapFriendObserver() {} + virtual void changed(U32 mask); +}; + +void LLMapFriendObserver::changed(U32 mask) +{ + // if there's a change we're interested in. + if((mask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE | LLFriendObserver::ONLINE | LLFriendObserver::POWERS)) != 0) + { + gFloaterWorldMap->friendsChanged(); + } +} + +//--------------------------------------------------------------------------- +// Statics +//--------------------------------------------------------------------------- + +// Used as a pretend asset and inventory id to mean "landmark at my home location." +const LLUUID LLFloaterWorldMap::sHomeID( "10000000-0000-0000-0000-000000000001" ); + +//--------------------------------------------------------------------------- +// Construction and destruction +//--------------------------------------------------------------------------- + + +LLFloaterWorldMap::LLFloaterWorldMap(const LLSD& key) +: LLFloater(key), + mInventory(NULL), + mInventoryObserver(NULL), + mFriendObserver(NULL), + mCompletingRegionName(), + mCompletingRegionPos(), + mWaitingForTracker(false), + mIsClosing(false), + mSetToUserPosition(true), + mTrackedLocation(0,0,0), + mTrackedStatus(LLTracker::TRACKING_NOTHING), + mListFriendCombo(NULL), + mListLandmarkCombo(NULL), + mListSearchResults(NULL) +{ + gFloaterWorldMap = this; + + mFactoryMap["objects_mapview"] = LLCallbackMap(createWorldMapView, NULL); + + mCommitCallbackRegistrar.add("WMap.Coordinates", boost::bind(&LLFloaterWorldMap::onCoordinatesCommit, this)); + mCommitCallbackRegistrar.add("WMap.Location", boost::bind(&LLFloaterWorldMap::onLocationCommit, this)); + mCommitCallbackRegistrar.add("WMap.AvatarCombo", boost::bind(&LLFloaterWorldMap::onAvatarComboCommit, this)); + mCommitCallbackRegistrar.add("WMap.Landmark", boost::bind(&LLFloaterWorldMap::onLandmarkComboCommit, this)); + mCommitCallbackRegistrar.add("WMap.SearchResult", boost::bind(&LLFloaterWorldMap::onCommitSearchResult, this)); + mCommitCallbackRegistrar.add("WMap.GoHome", boost::bind(&LLFloaterWorldMap::onGoHome, this)); + mCommitCallbackRegistrar.add("WMap.Teleport", boost::bind(&LLFloaterWorldMap::onClickTeleportBtn, this)); + mCommitCallbackRegistrar.add("WMap.ShowTarget", boost::bind(&LLFloaterWorldMap::onShowTargetBtn, this)); + mCommitCallbackRegistrar.add("WMap.ShowAgent", boost::bind(&LLFloaterWorldMap::onShowAgentBtn, this)); + mCommitCallbackRegistrar.add("WMap.Clear", boost::bind(&LLFloaterWorldMap::onClearBtn, this)); + mCommitCallbackRegistrar.add("WMap.CopySLURL", boost::bind(&LLFloaterWorldMap::onCopySLURL, this)); + + gSavedSettings.getControl("PreferredMaturity")->getSignal()->connect(boost::bind(&LLFloaterWorldMap::onChangeMaturity, this)); +} + +// static +void* LLFloaterWorldMap::createWorldMapView(void* data) +{ + return new LLWorldMapView(); +} + +bool LLFloaterWorldMap::postBuild() +{ + mMapView = dynamic_cast(getChild("objects_mapview")); + mMapView->setPan(0, 0, true); + + LLComboBox *avatar_combo = getChild("friend combo"); + avatar_combo->selectFirstItem(); + avatar_combo->setPrearrangeCallback( boost::bind(&LLFloaterWorldMap::onAvatarComboPrearrange, this) ); + avatar_combo->setTextChangedCallback( boost::bind(&LLFloaterWorldMap::onComboTextEntry, this) ); + mListFriendCombo = dynamic_cast(avatar_combo); + + LLSearchEditor *location_editor = getChild("location"); + location_editor->setFocusChangedCallback(boost::bind(&LLFloaterWorldMap::onLocationFocusChanged, this, _1)); + location_editor->setTextChangedCallback( boost::bind(&LLFloaterWorldMap::onSearchTextEntry, this)); + + getChild("search_results")->setDoubleClickCallback( boost::bind(&LLFloaterWorldMap::onClickTeleportBtn, this)); + mListSearchResults = childGetListInterface("search_results"); + + LLComboBox *landmark_combo = getChild( "landmark combo"); + landmark_combo->selectFirstItem(); + landmark_combo->setPrearrangeCallback( boost::bind(&LLFloaterWorldMap::onLandmarkComboPrearrange, this) ); + landmark_combo->setTextChangedCallback( boost::bind(&LLFloaterWorldMap::onComboTextEntry, this) ); + mListLandmarkCombo = dynamic_cast(landmark_combo); + + F32 slider_zoom = mMapView->getZoom(); + getChild("zoom slider")->setValue(slider_zoom); + + getChild("expand_btn_panel")->setMouseDownCallback(boost::bind(&LLFloaterWorldMap::onExpandCollapseBtn, this)); + + setDefaultBtn(NULL); + + onChangeMaturity(); + + return true; +} + +// virtual +LLFloaterWorldMap::~LLFloaterWorldMap() +{ + // All cleaned up by LLView destructor + mMapView = NULL; + + // Inventory deletes all observers on shutdown + mInventory = NULL; + mInventoryObserver = NULL; + + // avatar tracker will delete this for us. + mFriendObserver = NULL; + + gFloaterWorldMap = NULL; + + mTeleportFinishConnection.disconnect(); +} + +//static +LLFloaterWorldMap* LLFloaterWorldMap::getInstance() +{ + return LLFloaterReg::getTypedInstance("world_map"); +} + +// virtual +void LLFloaterWorldMap::onClose(bool app_quitting) +{ + // While we're not visible, discard the overlay images we're using + LLWorldMap::getInstance()->clearImageRefs(); + mTeleportFinishConnection.disconnect(); +} + +// virtual +void LLFloaterWorldMap::onOpen(const LLSD& key) +{ + mTeleportFinishConnection = LLViewerParcelMgr::getInstance()-> + setTeleportFinishedCallback(boost::bind(&LLFloaterWorldMap::onTeleportFinished, this)); + + bool center_on_target = (key.asString() == "center"); + + mIsClosing = false; + + mMapView->clearLastClick(); + + { + // reset pan on show, so it centers on you again + if (!center_on_target) + { + mMapView->setPan(0, 0, true); + } + mMapView->updateVisibleBlocks(); + + // Reload items as they may have changed + LLWorldMap::getInstance()->reloadItems(); + + // We may already have a bounding box for the regions of the world, + // so use that to adjust the view. + adjustZoomSliderBounds(); + + // Could be first show + //LLFirstUse::useMap(); + + // Start speculative download of landmarks + const LLUUID landmark_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK); + LLInventoryModelBackgroundFetch::instance().start(landmark_folder_id); + + getChild("location")->setFocus( true); + gFocusMgr.triggerFocusFlash(); + + buildAvatarIDList(); + buildLandmarkIDLists(); + + // If nothing is being tracked, set flag so the user position will be found + mSetToUserPosition = ( LLTracker::getTrackingStatus() == LLTracker::TRACKING_NOTHING ); + } + + if (center_on_target) + { + centerOnTarget(false); + } +} + +// static +void LLFloaterWorldMap::reloadIcons(void*) +{ + LLWorldMap::getInstance()->reloadItems(); +} + +// virtual +bool LLFloaterWorldMap::handleHover(S32 x, S32 y, MASK mask) +{ + bool handled; + handled = LLFloater::handleHover(x, y, mask); + return handled; +} + +bool LLFloaterWorldMap::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + if (!isMinimized() && isFrontmost()) + { + S32 map_x = x - mMapView->getRect().mLeft; + S32 map_y = y - mMapView->getRect().mBottom; + if (mMapView->pointInView(map_x, map_y)) + { + F32 old_slider_zoom = (F32) getChild("zoom slider")->getValue().asReal(); + F32 slider_zoom = old_slider_zoom + ((F32) clicks * -0.3333f); + getChild("zoom slider")->setValue(LLSD(slider_zoom)); + mMapView->zoomWithPivot(slider_zoom, map_x, map_y); + return true; + } + } + + return LLFloater::handleScrollWheel(x, y, clicks); +} + + +// virtual +void LLFloaterWorldMap::reshape( S32 width, S32 height, bool called_from_parent ) +{ + LLFloater::reshape( width, height, called_from_parent ); +} + + +// virtual +void LLFloaterWorldMap::draw() +{ + static LLUIColor map_track_color = LLUIColorTable::instance().getColor("MapTrackColor", LLColor4::white); + static LLUIColor map_track_disabled_color = LLUIColorTable::instance().getColor("MapTrackDisabledColor", LLColor4::white); + + // On orientation island, users don't have a home location yet, so don't + // let them teleport "home". It dumps them in an often-crowed welcome + // area (infohub) and they get confused. JC + LLViewerRegion* regionp = gAgent.getRegion(); + bool agent_on_prelude = (regionp && regionp->isPrelude()); + bool enable_go_home = gAgent.isGodlike() || !agent_on_prelude; + getChildView("Go Home")->setEnabled(enable_go_home); + + updateLocation(); + + LLTracker::ETrackingStatus tracking_status = LLTracker::getTrackingStatus(); + if (LLTracker::TRACKING_AVATAR == tracking_status) + { + getChild("avatar_icon")->setColor( map_track_color); + } + else + { + getChild("avatar_icon")->setColor( map_track_disabled_color); + } + + if (LLTracker::TRACKING_LANDMARK == tracking_status) + { + getChild("landmark_icon")->setColor( map_track_color); + } + else + { + getChild("landmark_icon")->setColor( map_track_disabled_color); + } + + if (LLTracker::TRACKING_LOCATION == tracking_status) + { + getChild("location_icon")->setColor( map_track_color); + } + else + { + if (mCompletingRegionName != "") + { + F64 seconds = LLTimer::getElapsedSeconds(); + double value = fmod(seconds, 2); + value = 0.5 + 0.5*cos(value * F_PI); + LLColor4 loading_color(0.0, F32(value/2), F32(value), 1.0); + getChild("location_icon")->setColor( loading_color); + } + else + { + getChild("location_icon")->setColor( map_track_disabled_color); + } + } + + // check for completion of tracking data + if (mWaitingForTracker) + { + centerOnTarget(true); + } + + getChildView("Teleport")->setEnabled((bool)tracking_status); + // getChildView("Clear")->setEnabled((bool)tracking_status); + getChildView("Show Destination")->setEnabled((bool)tracking_status || LLWorldMap::getInstance()->isTracking()); + getChildView("copy_slurl")->setEnabled((mSLURL.isValid()) ); + + setMouseOpaque(true); + getDragHandle()->setMouseOpaque(true); + + mMapView->zoom((F32)getChild("zoom slider")->getValue().asReal()); + + // Enable/disable checkboxes depending on the zoom level + // If above threshold level (i.e. low res) -> Disable all checkboxes + // If under threshold level (i.e. high res) -> Enable all checkboxes + bool enable = mMapView->showRegionInfo(); + getChildView("people_chk")->setEnabled(enable); + getChildView("infohub_chk")->setEnabled(enable); + getChildView("telehub_chk")->setEnabled(enable); + getChildView("land_for_sale_chk")->setEnabled(enable); + getChildView("event_chk")->setEnabled(enable); + getChildView("events_mature_chk")->setEnabled(enable); + getChildView("events_adult_chk")->setEnabled(enable); + + LLFloater::draw(); +} + + +//------------------------------------------------------------------------- +// Internal utility functions +//------------------------------------------------------------------------- + + +void LLFloaterWorldMap::trackAvatar( const LLUUID& avatar_id, const std::string& name ) +{ + LLCtrlSelectionInterface *iface = childGetSelectionInterface("friend combo"); + if (!iface) return; + + buildAvatarIDList(); + if(iface->setCurrentByID(avatar_id) || gAgent.isGodlike()) + { + // *HACK: Adjust Z values automatically for liaisons & gods so + // they swoop down when they click on the map. Requested + // convenience. + if(gAgent.isGodlike()) + { + getChild("teleport_coordinate_z")->setValue(LLSD(200.f)); + } + // Don't re-request info if we already have it or we won't have it in time to teleport + if (mTrackedStatus != LLTracker::TRACKING_AVATAR || avatar_id != mTrackedAvatarID) + { + mTrackedStatus = LLTracker::TRACKING_AVATAR; + mTrackedAvatarID = avatar_id; + LLTracker::trackAvatar(avatar_id, name); + } + } + else + { + LLTracker::stopTracking(false); + } + setDefaultBtn("Teleport"); +} + +void LLFloaterWorldMap::trackLandmark( const LLUUID& landmark_item_id ) +{ + LLCtrlSelectionInterface *iface = childGetSelectionInterface("landmark combo"); + if (!iface) return; + + buildLandmarkIDLists(); + bool found = false; + S32 idx; + for (idx = 0; idx < mLandmarkItemIDList.size(); idx++) + { + if ( mLandmarkItemIDList.at(idx) == landmark_item_id) + { + found = true; + break; + } + } + + if (found && iface->setCurrentByID( landmark_item_id ) ) + { + LLUUID asset_id = mLandmarkAssetIDList.at( idx ); + std::string name; + LLComboBox* combo = getChild( "landmark combo"); + if (combo) name = combo->getSimple(); + mTrackedStatus = LLTracker::TRACKING_LANDMARK; + LLTracker::trackLandmark(mLandmarkAssetIDList.at( idx ), // assetID + mLandmarkItemIDList.at( idx ), // itemID + name); // name + + if( asset_id != sHomeID ) + { + // start the download process + gLandmarkList.getAsset( asset_id); + } + + // We have to download both region info and landmark data, so set busy. JC + // getWindow()->incBusyCount(); + } + else + { + LLTracker::stopTracking(false); + } + setDefaultBtn("Teleport"); +} + + +void LLFloaterWorldMap::trackEvent(const LLItemInfo &event_info) +{ + mTrackedStatus = LLTracker::TRACKING_LOCATION; + LLTracker::trackLocation(event_info.getGlobalPosition(), event_info.getName(), event_info.getToolTip(), LLTracker::LOCATION_EVENT); + setDefaultBtn("Teleport"); +} + +void LLFloaterWorldMap::trackGenericItem(const LLItemInfo &item) +{ + mTrackedStatus = LLTracker::TRACKING_LOCATION; + LLTracker::trackLocation(item.getGlobalPosition(), item.getName(), item.getToolTip(), LLTracker::LOCATION_ITEM); + setDefaultBtn("Teleport"); +} + +void LLFloaterWorldMap::trackLocation(const LLVector3d& pos_global) +{ + LLSimInfo* sim_info = LLWorldMap::getInstance()->simInfoFromPosGlobal(pos_global); + if (!sim_info) + { + // We haven't found a region for that point yet, leave the tracking to the world map + LLTracker::stopTracking(false); + LLWorldMap::getInstance()->setTracking(pos_global); + S32 world_x = S32(pos_global.mdV[0] / 256); + S32 world_y = S32(pos_global.mdV[1] / 256); + LLWorldMapMessage::getInstance()->sendMapBlockRequest(world_x, world_y, world_x, world_y, true); + setDefaultBtn(""); + + // clicked on a non-region - turn off coord display + enableTeleportCoordsDisplay( false ); + + return; + } + if (sim_info->isDown()) + { + // Down region. Show the blue circle of death! + // i.e. let the world map that this and tell it it's invalid + LLTracker::stopTracking(false); + LLWorldMap::getInstance()->setTracking(pos_global); + LLWorldMap::getInstance()->setTrackingInvalid(); + setDefaultBtn(""); + + // clicked on a down region - turn off coord display + enableTeleportCoordsDisplay( false ); + + return; + } + + std::string sim_name = sim_info->getName(); + F32 region_x = (F32)fmod( pos_global.mdV[VX], (F64)REGION_WIDTH_METERS ); + F32 region_y = (F32)fmod( pos_global.mdV[VY], (F64)REGION_WIDTH_METERS ); + std::string full_name = llformat("%s (%d, %d, %d)", + sim_name.c_str(), + ll_round(region_x), + ll_round(region_y), + ll_round((F32)pos_global.mdV[VZ])); + + std::string tooltip(""); + mTrackedStatus = LLTracker::TRACKING_LOCATION; + LLWorldMap::getInstance()->cancelTracking(); // The floater is taking over the tracking + LLTracker::trackLocation(pos_global, full_name, tooltip); + + LLVector3d coord_pos = LLTracker::getTrackedPositionGlobal(); + updateTeleportCoordsDisplay( coord_pos ); + + // we have a valid region - turn on coord display + enableTeleportCoordsDisplay( true ); + + setDefaultBtn("Teleport"); +} + +// enable/disable teleport destination coordinates +void LLFloaterWorldMap::enableTeleportCoordsDisplay( bool enabled ) +{ + childSetEnabled("teleport_coordinate_x", enabled ); + childSetEnabled("teleport_coordinate_y", enabled ); + childSetEnabled("teleport_coordinate_z", enabled ); +} + +// update display of teleport destination coordinates - pos is in global coordinates +void LLFloaterWorldMap::updateTeleportCoordsDisplay( const LLVector3d& pos ) +{ + // if we're going to update their value, we should also enable them + enableTeleportCoordsDisplay( true ); + + // convert global specified position to a local one + F32 region_local_x = (F32)fmod( pos.mdV[VX], (F64)REGION_WIDTH_METERS ); + F32 region_local_y = (F32)fmod( pos.mdV[VY], (F64)REGION_WIDTH_METERS ); + F32 region_local_z = (F32)llclamp( pos.mdV[VZ], 0.0, (F64)REGION_HEIGHT_METERS ); + + // write in the values + childSetValue("teleport_coordinate_x", region_local_x ); + childSetValue("teleport_coordinate_y", region_local_y ); + childSetValue("teleport_coordinate_z", region_local_z ); +} + +void LLFloaterWorldMap::updateLocation() +{ + bool gotSimName; + + LLTracker::ETrackingStatus status = LLTracker::getTrackingStatus(); + + // These values may get updated by a message, so need to check them every frame + // The fields may be changed by the user, so only update them if the data changes + LLVector3d pos_global = LLTracker::getTrackedPositionGlobal(); + if (pos_global.isExactlyZero()) + { + LLVector3d agentPos = gAgent.getPositionGlobal(); + + // Set to avatar's current postion if nothing is selected + if ( status == LLTracker::TRACKING_NOTHING && mSetToUserPosition ) + { + // Make sure we know where we are before setting the current user position + std::string agent_sim_name; + gotSimName = LLWorldMap::getInstance()->simNameFromPosGlobal( agentPos, agent_sim_name ); + if ( gotSimName ) + { + mSetToUserPosition = false; + + // Fill out the location field + getChild("location")->setValue(agent_sim_name); + + // update the coordinate display with location of avatar in region + updateTeleportCoordsDisplay( agentPos ); + + // Figure out where user is + // Set the current SLURL + mSLURL = LLSLURL(agent_sim_name, gAgent.getPositionGlobal()); + } + } + + return; // invalid location + } + std::string sim_name; + gotSimName = LLWorldMap::getInstance()->simNameFromPosGlobal( pos_global, sim_name ); + if ((status != LLTracker::TRACKING_NOTHING) && + (status != mTrackedStatus || pos_global != mTrackedLocation || sim_name != mTrackedSimName)) + { + mTrackedStatus = status; + mTrackedLocation = pos_global; + mTrackedSimName = sim_name; + + if (status == LLTracker::TRACKING_AVATAR) + { + // *HACK: Adjust Z values automatically for liaisons & + // gods so they swoop down when they click on the + // map. Requested convenience. + if(gAgent.isGodlike()) + { + pos_global[2] = 200; + } + } + + getChild("location")->setValue(sim_name); + + // refresh coordinate display to reflect where user clicked. + LLVector3d coord_pos = LLTracker::getTrackedPositionGlobal(); + updateTeleportCoordsDisplay( coord_pos ); + + // simNameFromPosGlobal can fail, so don't give the user an invalid SLURL + if ( gotSimName ) + { + mSLURL = LLSLURL(sim_name, pos_global); + } + else + { // Empty SLURL will disable the "Copy SLURL to clipboard" button + mSLURL = LLSLURL(); + } + } +} + +void LLFloaterWorldMap::trackURL(const std::string& region_name, S32 x_coord, S32 y_coord, S32 z_coord) +{ + LLSimInfo* sim_info = LLWorldMap::getInstance()->simInfoFromName(region_name); + z_coord = llclamp(z_coord, 0, 4096); + if (sim_info) + { + LLVector3 local_pos; + local_pos.mV[VX] = (F32)x_coord; + local_pos.mV[VY] = (F32)y_coord; + local_pos.mV[VZ] = (F32)z_coord; + LLVector3d global_pos = sim_info->getGlobalPos(local_pos); + trackLocation(global_pos); + setDefaultBtn("Teleport"); + } + else + { + // fill in UI based on URL + gFloaterWorldMap->getChild("location")->setValue(region_name); + + // Save local coords to highlight position after region global + // position is returned. + gFloaterWorldMap->mCompletingRegionPos.set( + (F32)x_coord, (F32)y_coord, (F32)z_coord); + + // pass sim name to combo box + gFloaterWorldMap->mCompletingRegionName = region_name; + LLWorldMapMessage::getInstance()->sendNamedRegionRequest(region_name); + LLStringUtil::toLower(gFloaterWorldMap->mCompletingRegionName); + LLWorldMap::getInstance()->setTrackingCommit(); + } +} + +void LLFloaterWorldMap::observeInventory(LLInventoryModel* model) +{ + if(mInventory) + { + mInventory->removeObserver(mInventoryObserver); + delete mInventoryObserver; + mInventory = NULL; + mInventoryObserver = NULL; + } + if(model) + { + mInventory = model; + mInventoryObserver = new LLMapInventoryObserver; + // Inventory deletes all observers on shutdown + mInventory->addObserver(mInventoryObserver); + inventoryChanged(); + } +} + +void LLFloaterWorldMap::inventoryChanged() +{ + if(!LLTracker::getTrackedLandmarkItemID().isNull()) + { + LLUUID item_id = LLTracker::getTrackedLandmarkItemID(); + buildLandmarkIDLists(); + trackLandmark(item_id); + } +} + +void LLFloaterWorldMap::observeFriends() +{ + if(!mFriendObserver) + { + mFriendObserver = new LLMapFriendObserver; + LLAvatarTracker::instance().addObserver(mFriendObserver); + friendsChanged(); + } +} + +void LLFloaterWorldMap::friendsChanged() +{ + LLAvatarTracker& t = LLAvatarTracker::instance(); + const LLUUID& avatar_id = t.getAvatarID(); + buildAvatarIDList(); + if(avatar_id.notNull()) + { + LLCtrlSelectionInterface *iface = childGetSelectionInterface("friend combo"); + const LLRelationship* buddy_info = t.getBuddyInfo(avatar_id); + if(!iface || + !iface->setCurrentByID(avatar_id) || + (buddy_info && !buddy_info->isRightGrantedFrom(LLRelationship::GRANT_MAP_LOCATION)) || + gAgent.isGodlike()) + { + LLTracker::stopTracking(false); + } + } +} + +// No longer really builds a list. Instead, just updates mAvatarCombo. +void LLFloaterWorldMap::buildAvatarIDList() +{ + LLCtrlListInterface *list = mListFriendCombo; + if (!list) return; + + // Delete all but the "None" entry + S32 list_size = list->getItemCount(); + if (list_size > 1) + { + list->selectItemRange(1, -1); + list->operateOnSelection(LLCtrlListInterface::OP_DELETE); + } + + // Get all of the calling cards for avatar that are currently online + LLCollectMappableBuddies collector; + LLAvatarTracker::instance().applyFunctor(collector); + LLCollectMappableBuddies::buddy_map_t::iterator it; + LLCollectMappableBuddies::buddy_map_t::iterator end; + it = collector.mMappable.begin(); + end = collector.mMappable.end(); + for( ; it != end; ++it) + { + list->addSimpleElement((*it).second, ADD_BOTTOM, (*it).first); + } + + list->setCurrentByID( LLAvatarTracker::instance().getAvatarID() ); + list->selectFirstItem(); +} + + +void LLFloaterWorldMap::buildLandmarkIDLists() +{ + LLCtrlListInterface *list = mListLandmarkCombo; + if (!list) return; + + // Delete all but the "None" entry + S32 list_size = list->getItemCount(); + if (list_size > 1) + { + list->selectItemRange(1, -1); + list->operateOnSelection(LLCtrlListInterface::OP_DELETE); + } + + mLandmarkItemIDList.clear(); + mLandmarkAssetIDList.clear(); + + // Get all of the current landmarks + mLandmarkAssetIDList.push_back( LLUUID::null ); + mLandmarkItemIDList.push_back( LLUUID::null ); + + mLandmarkAssetIDList.push_back( sHomeID ); + mLandmarkItemIDList.push_back( sHomeID ); + + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLIsType is_landmark(LLAssetType::AT_LANDMARK); + gInventory.collectDescendentsIf(gInventory.getRootFolderID(), + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + is_landmark); + + std::sort(items.begin(), items.end(), LLViewerInventoryItem::comparePointers()); + + mLandmarkAssetIDList.reserve(mLandmarkAssetIDList.size() + items.size()); + mLandmarkItemIDList.reserve(mLandmarkItemIDList.size() + items.size()); + + S32 count = items.size(); + for(S32 i = 0; i < count; ++i) + { + LLInventoryItem* item = items.at(i); + + list->addSimpleElement(item->getName(), ADD_BOTTOM, item->getUUID()); + + mLandmarkAssetIDList.push_back( item->getAssetUUID() ); + mLandmarkItemIDList.push_back( item->getUUID() ); + } + + list->selectFirstItem(); +} + + +F32 LLFloaterWorldMap::getDistanceToDestination(const LLVector3d &destination, + F32 z_attenuation) const +{ + LLVector3d delta = destination - gAgent.getPositionGlobal(); + // by attenuating the z-component we effectively + // give more weight to the x-y plane + delta.mdV[VZ] *= z_attenuation; + F32 distance = (F32)delta.magVec(); + return distance; +} + + +void LLFloaterWorldMap::clearLocationSelection(bool clear_ui, bool dest_reached) +{ + LLCtrlListInterface *list = mListSearchResults; + if (list && (!dest_reached || (list->getItemCount() == 1))) + { + list->operateOnAll(LLCtrlListInterface::OP_DELETE); + } + LLWorldMap::getInstance()->cancelTracking(); + mCompletingRegionName = ""; +} + + +void LLFloaterWorldMap::clearLandmarkSelection(bool clear_ui) +{ + if (clear_ui || !childHasKeyboardFocus("landmark combo")) + { + LLCtrlListInterface *list = mListLandmarkCombo; + if (list) + { + list->selectByValue( "None" ); + } + } +} + + +void LLFloaterWorldMap::clearAvatarSelection(bool clear_ui) +{ + if (clear_ui || !childHasKeyboardFocus("friend combo")) + { + mTrackedStatus = LLTracker::TRACKING_NOTHING; + LLCtrlListInterface *list = mListFriendCombo; + if (list && list->getSelectedValue().asString() != "None") + { + list->selectByValue( "None" ); + } + } +} + + +// Adjust the maximally zoomed out limit of the zoom slider so you +// can see the whole world, plus a little. +void LLFloaterWorldMap::adjustZoomSliderBounds() +{ + // Merov: we switched from using the "world size" (which varies depending where the user went) to a fixed + // width of 512 regions max visible at a time. This makes the zoom slider works in a consistent way across + // sessions and doesn't prevent the user to pan the world if it was to grow a lot beyond that limit. + // Currently (01/26/09), this value allows the whole grid to be visible in a 1024x1024 window. + S32 world_width_regions = MAX_VISIBLE_REGIONS; + S32 world_height_regions = MAX_VISIBLE_REGIONS; + + // Find how much space we have to display the world + LLRect view_rect = mMapView->getRect(); + + // View size in pixels + S32 view_width = view_rect.getWidth(); + S32 view_height = view_rect.getHeight(); + + // Pixels per region to display entire width/height + F32 width_pixels_per_region = (F32) view_width / (F32) world_width_regions; + F32 height_pixels_per_region = (F32) view_height / (F32) world_height_regions; + + F32 pixels_per_region = llmin(width_pixels_per_region, + height_pixels_per_region); + + // Round pixels per region to an even number of slider increments + S32 slider_units = llfloor(pixels_per_region / 0.2f); + pixels_per_region = slider_units * 0.2f; + + // Make sure the zoom slider can be moved at least a little bit. + // Likewise, less than the increment pixels per region is just silly. + pixels_per_region = llclamp(pixels_per_region, 1.f, ZOOM_MAX); + + F32 min_power = log(pixels_per_region/256.f)/log(2.f); + + getChild("zoom slider")->setMinValue(min_power); +} + + +//------------------------------------------------------------------------- +// User interface widget callbacks +//------------------------------------------------------------------------- + +void LLFloaterWorldMap::onGoHome() +{ + gAgent.teleportHome(); + closeFloater(); +} + + +void LLFloaterWorldMap::onLandmarkComboPrearrange( ) +{ + if( mIsClosing ) + { + return; + } + + LLCtrlListInterface *list = mListLandmarkCombo; + if (!list) return; + + LLUUID current_choice = list->getCurrentID(); + + buildLandmarkIDLists(); + + if( current_choice.isNull() || !list->setCurrentByID( current_choice ) ) + { + LLTracker::stopTracking(false); + } + +} + +void LLFloaterWorldMap::onComboTextEntry() +{ + // Reset the tracking whenever we start typing into any of the search fields, + // so that hitting does an auto-complete versus teleporting us to the + // previously selected landmark/friend. + LLTracker::stopTracking(false); +} + +void LLFloaterWorldMap::onSearchTextEntry( ) +{ + onComboTextEntry(); + updateSearchEnabled(); +} + + +void LLFloaterWorldMap::onLandmarkComboCommit() +{ + if( mIsClosing ) + { + return; + } + + LLCtrlListInterface *list = mListLandmarkCombo; + if (!list) return; + + LLUUID asset_id; + LLUUID item_id = list->getCurrentID(); + + LLTracker::stopTracking(false); + + //RN: stopTracking() clears current combobox selection, need to reassert it here + list->setCurrentByID(item_id); + + if( item_id.isNull() ) + { + } + else if( item_id == sHomeID ) + { + asset_id = sHomeID; + } + else + { + LLInventoryItem* item = gInventory.getItem( item_id ); + if( item ) + { + asset_id = item->getAssetUUID(); + } + else + { + // Something went wrong, so revert to a safe value. + item_id.setNull(); + } + } + + trackLandmark( item_id); + onShowTargetBtn(); + + // Reset to user postion if nothing is tracked + mSetToUserPosition = ( LLTracker::getTrackingStatus() == LLTracker::TRACKING_NOTHING ); +} + +// static +void LLFloaterWorldMap::onAvatarComboPrearrange( ) +{ + if( mIsClosing ) + { + return; + } + + LLCtrlListInterface *list = mListFriendCombo; + if (!list) return; + + LLUUID current_choice; + + if( LLAvatarTracker::instance().haveTrackingInfo() ) + { + current_choice = LLAvatarTracker::instance().getAvatarID(); + } + + buildAvatarIDList(); + + if( !list->setCurrentByID( current_choice ) || current_choice.isNull() ) + { + LLTracker::stopTracking(false); + } +} + +void LLFloaterWorldMap::onAvatarComboCommit() +{ + if( mIsClosing ) + { + return; + } + + LLCtrlListInterface *list = mListFriendCombo; + if (!list) return; + + const LLUUID& new_avatar_id = list->getCurrentID(); + if (new_avatar_id.notNull()) + { + std::string name; + LLComboBox* combo = getChild("friend combo"); + if (combo) name = combo->getSimple(); + trackAvatar(new_avatar_id, name); + onShowTargetBtn(); + } + else + { // Reset to user postion if nothing is tracked + mSetToUserPosition = ( LLTracker::getTrackingStatus() == LLTracker::TRACKING_NOTHING ); + } +} + +void LLFloaterWorldMap::avatarTrackFromSlapp( const LLUUID& id ) +{ + trackAvatar( id, "av" ); + onShowTargetBtn(); +} + +void LLFloaterWorldMap::onLocationFocusChanged( LLFocusableElement* focus ) +{ + updateSearchEnabled(); +} + +void LLFloaterWorldMap::updateSearchEnabled() +{ + if (childHasKeyboardFocus("location") && + getChild("location")->getValue().asString().length() > 0) + { + setDefaultBtn("DoSearch"); + } + else + { + setDefaultBtn(NULL); + } +} + +void LLFloaterWorldMap::onLocationCommit() +{ + if( mIsClosing ) + { + return; + } + + clearLocationSelection(false); + mCompletingRegionName = ""; + mLastRegionName = ""; + + std::string str = getChild("location")->getValue().asString(); + + // Trim any leading and trailing spaces in the search target + std::string saved_str = str; + LLStringUtil::trim( str ); + if ( str != saved_str ) + { // Set the value in the UI if any spaces were removed + getChild("location")->setValue(str); + } + + // Don't try completing empty name (STORM-1427). + if (str.empty()) + { + return; + } + + LLStringUtil::toLower(str); + mCompletingRegionName = str; + LLWorldMap::getInstance()->setTrackingCommit(); + if (str.length() >= 3) + { + LLWorldMapMessage::getInstance()->sendNamedRegionRequest(str); + } + else + { + str += "#"; + LLWorldMapMessage::getInstance()->sendNamedRegionRequest(str); + } +} + +void LLFloaterWorldMap::onCoordinatesCommit() +{ + if( mIsClosing ) + { + return; + } + + S32 x_coord = (S32)childGetValue("teleport_coordinate_x").asReal(); + S32 y_coord = (S32)childGetValue("teleport_coordinate_y").asReal(); + S32 z_coord = (S32)childGetValue("teleport_coordinate_z").asReal(); + + const std::string region_name = childGetValue("location").asString(); + + trackURL( region_name, x_coord, y_coord, z_coord ); +} + +void LLFloaterWorldMap::onClearBtn() +{ + mTrackedStatus = LLTracker::TRACKING_NOTHING; + LLTracker::stopTracking(true); + LLWorldMap::getInstance()->cancelTracking(); + mSLURL = LLSLURL(); // Clear the SLURL since it's invalid + mSetToUserPosition = true; // Revert back to the current user position +} + +void LLFloaterWorldMap::onShowTargetBtn() +{ + centerOnTarget(true); +} + +void LLFloaterWorldMap::onShowAgentBtn() +{ + mMapView->setPanWithInterpTime(0, 0, false, 0.1f); // false == animate + // Set flag so user's location will be displayed if not tracking anything else + mSetToUserPosition = true; +} + +void LLFloaterWorldMap::onClickTeleportBtn() +{ + teleport(); +} + +void LLFloaterWorldMap::onCopySLURL() +{ + getWindow()->copyTextToClipboard(utf8str_to_wstring(mSLURL.getSLURLString())); + + LLSD args; + args["SLURL"] = mSLURL.getSLURLString(); + + LLNotificationsUtil::add("CopySLURL", args); +} + +void LLFloaterWorldMap::onExpandCollapseBtn() +{ + LLLayoutStack* floater_stack = getChild("floater_map_stack"); + LLLayoutPanel* controls_panel = getChild("controls_lp"); + + bool toggle_collapse = !controls_panel->isCollapsed(); + floater_stack->collapsePanel(controls_panel, toggle_collapse); + floater_stack->updateLayout(); + + std::string image_name = getString(toggle_collapse ? "expand_icon" : "collapse_icon"); + std::string tooltip = getString(toggle_collapse ? "expand_tooltip" : "collapse_tooltip"); + getChild("expand_collapse_icon")->setImage(LLUI::getUIImage(image_name)); + getChild("expand_collapse_icon")->setToolTip(tooltip); + getChild("expand_btn_panel")->setToolTip(tooltip); +} + +// protected +void LLFloaterWorldMap::centerOnTarget(bool animate) +{ + LLVector3d pos_global; + if(LLTracker::getTrackingStatus() != LLTracker::TRACKING_NOTHING) + { + LLVector3d tracked_position = LLTracker::getTrackedPositionGlobal(); + //RN: tracker doesn't allow us to query completion, so we check for a tracking position of + // absolute zero, and keep trying in the draw loop + if (tracked_position.isExactlyZero()) + { + mWaitingForTracker = true; + return; + } + else + { + // We've got the position finally, so we're no longer busy. JC + // getWindow()->decBusyCount(); + pos_global = LLTracker::getTrackedPositionGlobal() - gAgentCamera.getCameraPositionGlobal(); + } + } + else if(LLWorldMap::getInstance()->isTracking()) + { + pos_global = LLWorldMap::getInstance()->getTrackedPositionGlobal() - gAgentCamera.getCameraPositionGlobal();; + + + + } + else + { + // default behavior = center on agent + pos_global.clearVec(); + } + + F64 map_scale = (F64)mMapView->getScale(); + mMapView->setPanWithInterpTime(-llfloor((F32)(pos_global.mdV[VX] * map_scale / REGION_WIDTH_METERS)), + -llfloor((F32)(pos_global.mdV[VY] * map_scale / REGION_WIDTH_METERS)), + !animate, 0.1f); + mWaitingForTracker = false; +} + +// protected +void LLFloaterWorldMap::fly() +{ + LLVector3d pos_global = LLTracker::getTrackedPositionGlobal(); + + // Start the autopilot and close the floater, + // so we can see where we're flying + if (!pos_global.isExactlyZero()) + { + gAgent.startAutoPilotGlobal( pos_global ); + closeFloater(); + } + else + { + make_ui_sound("UISndInvalidOp"); + } +} + + +// protected +void LLFloaterWorldMap::teleport() +{ + bool teleport_home = false; + LLVector3d pos_global; + LLAvatarTracker& av_tracker = LLAvatarTracker::instance(); + + LLTracker::ETrackingStatus tracking_status = LLTracker::getTrackingStatus(); + if (LLTracker::TRACKING_AVATAR == tracking_status + && av_tracker.haveTrackingInfo() ) + { + pos_global = av_tracker.getGlobalPos(); + pos_global.mdV[VZ] = getChild("teleport_coordinate_z")->getValue(); + } + else if ( LLTracker::TRACKING_LANDMARK == tracking_status) + { + if( LLTracker::getTrackedLandmarkAssetID() == sHomeID ) + { + teleport_home = true; + } + else + { + LLLandmark* landmark = gLandmarkList.getAsset( LLTracker::getTrackedLandmarkAssetID() ); + LLUUID region_id; + if(landmark + && !landmark->getGlobalPos(pos_global) + && landmark->getRegionID(region_id)) + { + LLLandmark::requestRegionHandle( + gMessageSystem, + gAgent.getRegionHost(), + region_id, + NULL); + } + } + } + else if ( LLTracker::TRACKING_LOCATION == tracking_status) + { + pos_global = LLTracker::getTrackedPositionGlobal(); + } + else + { + make_ui_sound("UISndInvalidOp"); + } + + // Do the teleport, which will also close the floater + if (teleport_home) + { + gAgent.teleportHome(); + } + else if (!pos_global.isExactlyZero()) + { + if(LLTracker::TRACKING_LANDMARK == tracking_status) + { + gAgent.teleportViaLandmark(LLTracker::getTrackedLandmarkAssetID()); + } + else + { + gAgent.teleportViaLocation( pos_global ); + } + } +} + +void LLFloaterWorldMap::flyToLandmark() +{ + LLVector3d destination_pos_global; + if( !LLTracker::getTrackedLandmarkAssetID().isNull() ) + { + if (LLTracker::hasLandmarkPosition()) + { + gAgent.startAutoPilotGlobal( LLTracker::getTrackedPositionGlobal() ); + } + } +} + +void LLFloaterWorldMap::teleportToLandmark() +{ + bool has_destination = false; + LLUUID destination_id; // Null means "home" + + if( LLTracker::getTrackedLandmarkAssetID() == sHomeID ) + { + has_destination = true; + } + else + { + LLLandmark* landmark = gLandmarkList.getAsset( LLTracker::getTrackedLandmarkAssetID() ); + LLVector3d global_pos; + if(landmark && landmark->getGlobalPos(global_pos)) + { + destination_id = LLTracker::getTrackedLandmarkAssetID(); + has_destination = true; + } + else if(landmark) + { + // pop up an anonymous request request. + LLUUID region_id; + if(landmark->getRegionID(region_id)) + { + LLLandmark::requestRegionHandle( + gMessageSystem, + gAgent.getRegionHost(), + region_id, + NULL); + } + } + } + + if( has_destination ) + { + gAgent.teleportViaLandmark( destination_id ); + } +} + + +void LLFloaterWorldMap::teleportToAvatar() +{ + LLAvatarTracker& av_tracker = LLAvatarTracker::instance(); + if(av_tracker.haveTrackingInfo()) + { + LLVector3d pos_global = av_tracker.getGlobalPos(); + gAgent.teleportViaLocation( pos_global ); + } +} + + +void LLFloaterWorldMap::flyToAvatar() +{ + if( LLAvatarTracker::instance().haveTrackingInfo() ) + { + gAgent.startAutoPilotGlobal( LLAvatarTracker::instance().getGlobalPos() ); + } +} + +void LLFloaterWorldMap::updateSims(bool found_null_sim) +{ + if (mCompletingRegionName == "") + { + return; + } + + LLScrollListCtrl *list = getChild("search_results"); + list->operateOnAll(LLCtrlListInterface::OP_DELETE); + + S32 name_length = mCompletingRegionName.length(); + + LLSD match; + + S32 num_results = 0; + + std::vector > sim_info_vec(LLWorldMap::getInstance()->getRegionMap().begin(), LLWorldMap::getInstance()->getRegionMap().end()); + std::sort(sim_info_vec.begin(), sim_info_vec.end(), SortRegionNames()); + + for (std::vector >::const_iterator it = sim_info_vec.begin(); it != sim_info_vec.end(); ++it) + { + LLSimInfo* info = it->second; + std::string sim_name_lower = info->getName(); + LLStringUtil::toLower(sim_name_lower); + + if (sim_name_lower.substr(0, name_length) == mCompletingRegionName) + { + if (sim_name_lower == mCompletingRegionName) + { + match = info->getName(); + } + + LLSD value; + value["id"] = info->getName(); + value["columns"][0]["column"] = "sim_name"; + value["columns"][0]["value"] = info->getName(); + list->addElement(value); + num_results++; + } + } + + if (found_null_sim) + { + mCompletingRegionName = ""; + } + + if (num_results > 0) + { + // if match found, highlight it and go + if (!match.isUndefined()) + { + list->selectByValue(match); + } + // else select first found item + else + { + list->selectFirstItem(); + } + getChild("search_results")->setFocus(true); + onCommitSearchResult(); + } + else + { + // if we found nothing, say "none" + list->setCommentText(LLTrans::getString("worldmap_results_none_found")); + list->operateOnAll(LLCtrlListInterface::OP_DESELECT); + } +} + +void LLFloaterWorldMap::onTeleportFinished() +{ + if(isInVisibleChain()) + { + mMapView->setPan(0, 0, true); + } +} + +void LLFloaterWorldMap::onCommitSearchResult() +{ + LLCtrlListInterface *list = mListSearchResults; + if (!list) return; + + LLSD selected_value = list->getSelectedValue(); + std::string sim_name = selected_value.asString(); + if (sim_name.empty()) + { + return; + } + LLStringUtil::toLower(sim_name); + + std::map::const_iterator it; + for (it = LLWorldMap::getInstance()->getRegionMap().begin(); it != LLWorldMap::getInstance()->getRegionMap().end(); ++it) + { + LLSimInfo* info = it->second; + + if (info->isName(sim_name)) + { + LLVector3d pos_global = info->getGlobalOrigin(); + + const F64 SIM_COORD_DEFAULT = 128.0; + LLVector3 pos_local(SIM_COORD_DEFAULT, SIM_COORD_DEFAULT, 0.0f); + + // Did this value come from a trackURL() request? + if (!mCompletingRegionPos.isExactlyZero()) + { + pos_local = mCompletingRegionPos; + mCompletingRegionPos.clear(); + } + pos_global.mdV[VX] += (F64)pos_local.mV[VX]; + pos_global.mdV[VY] += (F64)pos_local.mV[VY]; + pos_global.mdV[VZ] = (F64)pos_local.mV[VZ]; + + getChild("location")->setValue(sim_name); + trackLocation(pos_global); + setDefaultBtn("Teleport"); + break; + } + } + + onShowTargetBtn(); +} + +void LLFloaterWorldMap::onChangeMaturity() +{ + bool can_access_mature = gAgent.canAccessMature(); + bool can_access_adult = gAgent.canAccessAdult(); + + getChildView("events_mature_icon")->setVisible( can_access_mature); + getChildView("events_mature_label")->setVisible( can_access_mature); + getChildView("events_mature_chk")->setVisible( can_access_mature); + + getChildView("events_adult_icon")->setVisible( can_access_adult); + getChildView("events_adult_label")->setVisible( can_access_adult); + getChildView("events_adult_chk")->setVisible( can_access_adult); + + // disable mature / adult events. + if (!can_access_mature) + { + gSavedSettings.setBOOL("ShowMatureEvents", false); + } + if (!can_access_adult) + { + gSavedSettings.setBOOL("ShowAdultEvents", false); + } +} + +void LLFloaterWorldMap::onFocusLost() +{ + gViewerWindow->showCursor(); + mMapView->mPanning = false; +} + +LLPanelHideBeacon::LLPanelHideBeacon() : + mHideButton(NULL) +{ +} + +// static +LLPanelHideBeacon* LLPanelHideBeacon::getInstance() +{ + static LLPanelHideBeacon* panel = getPanelHideBeacon(); + return panel; +} + + +bool LLPanelHideBeacon::postBuild() +{ + mHideButton = getChild("hide_beacon_btn"); + mHideButton->setCommitCallback(boost::bind(&LLPanelHideBeacon::onHideButtonClick, this)); + + gViewerWindow->setOnWorldViewRectUpdated(boost::bind(&LLPanelHideBeacon::updatePosition, this)); + + return true; +} + +//virtual +void LLPanelHideBeacon::draw() +{ + if (!LLTracker::isTracking(NULL)) + { + mHideButton->setVisible(false); + return; + } + mHideButton->setVisible(true); + updatePosition(); + LLPanel::draw(); +} + +//virtual +void LLPanelHideBeacon::setVisible(bool visible) +{ + if (gAgentCamera.getCameraMode() == CAMERA_MODE_MOUSELOOK) visible = false; + + if (visible) + { + updatePosition(); + } + + LLPanel::setVisible(visible); +} + + +//static +LLPanelHideBeacon* LLPanelHideBeacon::getPanelHideBeacon() +{ + LLPanelHideBeacon* panel = new LLPanelHideBeacon(); + panel->buildFromFile("panel_hide_beacon.xml"); + + LL_INFOS() << "Build LLPanelHideBeacon panel" << LL_ENDL; + + panel->updatePosition(); + return panel; +} + +void LLPanelHideBeacon::onHideButtonClick() +{ + LLFloaterWorldMap* instance = LLFloaterWorldMap::getInstance(); + if (instance) + { + instance->onClearBtn(); + } +} + +/** +* Updates position of the panel (similar to Stand & Stop Flying panel). +*/ +void LLPanelHideBeacon::updatePosition() +{ + S32 bottom_tb_center = 0; + if (LLToolBar* toolbar_bottom = gToolBarView->getToolbar(LLToolBarEnums::TOOLBAR_BOTTOM)) + { + bottom_tb_center = toolbar_bottom->getRect().getCenterX(); + } + + S32 left_tb_width = 0; + if (LLToolBar* toolbar_left = gToolBarView->getToolbar(LLToolBarEnums::TOOLBAR_LEFT)) + { + left_tb_width = toolbar_left->getRect().getWidth(); + } + + if (gToolBarView != NULL && gToolBarView->getToolbar(LLToolBarEnums::TOOLBAR_LEFT)->hasButtons()) + { + S32 x_pos = bottom_tb_center - getRect().getWidth() / 2 - left_tb_width; + setOrigin( x_pos + HIDE_BEACON_PAD, 0); + } + else + { + S32 x_pos = bottom_tb_center - getRect().getWidth() / 2; + setOrigin( x_pos + HIDE_BEACON_PAD, 0); + } +} diff --git a/indra/newview/llfloaterworldmap.h b/indra/newview/llfloaterworldmap.h index b0e5c85fb1..6765157e55 100644 --- a/indra/newview/llfloaterworldmap.h +++ b/indra/newview/llfloaterworldmap.h @@ -1,224 +1,224 @@ -/** - * @file llfloaterworldmap.h - * @brief LLFloaterWorldMap class definition - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/* - * Map of the entire world, with multiple background images, - * avatar tracking, teleportation by double-click, etc. - */ - -#ifndef LL_LLFLOATERWORLDMAP_H -#define LL_LLFLOATERWORLDMAP_H - -#include "llfloater.h" -#include "llmapimagetype.h" -#include "lltracker.h" -#include "llslurl.h" - -class LLCtrlListInterface; -class LLFriendObserver; -class LLInventoryModel; -class LLInventoryObserver; -class LLItemInfo; -class LLLineEditor; -class LLTabContainer; -class LLWorldMapView; - -class LLFloaterWorldMap : public LLFloater -{ -public: - LLFloaterWorldMap(const LLSD& key); - virtual ~LLFloaterWorldMap(); - - // Prefer this to gFloaterWorldMap - static LLFloaterWorldMap* getInstance(); - - static void *createWorldMapView(void* data); - bool postBuild(); - - /*virtual*/ void onOpen(const LLSD& key); - /*virtual*/ void onClose(bool app_quitting); - - static void reloadIcons(void*); - - /*virtual*/ void reshape( S32 width, S32 height, bool called_from_parent = true ); - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleScrollWheel(S32 x, S32 y, S32 clicks); - /*virtual*/ void draw(); - - /*virtual*/ void onFocusLost(); - - // methods for dealing with inventory. The observe() method is - // called during program startup. inventoryUpdated() will be - // called by a helper object when an interesting change has - // occurred. - void observeInventory(LLInventoryModel* inventory); - void inventoryChanged(); - - // Calls for dealing with changes in friendship - void observeFriends(); - void friendsChanged(); - - // tracking methods - void trackAvatar( const LLUUID& avatar_id, const std::string& name ); - void trackLandmark( const LLUUID& landmark_item_id ); - void trackLocation(const LLVector3d& pos); - void trackEvent(const LLItemInfo &event_info); - void trackGenericItem(const LLItemInfo &item); - void trackURL(const std::string& region_name, S32 x_coord, S32 y_coord, S32 z_coord); - - static const LLUUID& getHomeID() { return sHomeID; } - - // A z_attenuation of 0.0f collapses the distance into the X-Y plane - F32 getDistanceToDestination(const LLVector3d& pos_global, F32 z_attenuation = 0.5f) const; - - void clearLocationSelection(bool clear_ui = false, bool dest_reached = false); - void clearAvatarSelection(bool clear_ui = false); - void clearLandmarkSelection(bool clear_ui = false); - - // Adjust the maximally zoomed out limit of the zoom slider so you can - // see the whole world, plus a little. - void adjustZoomSliderBounds(); - - // Catch changes in the sim list - void updateSims(bool found_null_sim); - - // teleport to the tracked item, if there is one - void teleport(); - void onChangeMaturity(); - - void onClearBtn(); - - //Slapp instigated avatar tracking - void avatarTrackFromSlapp( const LLUUID& id ); - -protected: - void onGoHome(); - - void onLandmarkComboPrearrange(); - void onLandmarkComboCommit(); - - void onAvatarComboPrearrange(); - void onAvatarComboCommit(); - - void onComboTextEntry( ); - void onSearchTextEntry( ); - - void onClickTeleportBtn(); - void onShowTargetBtn(); - void onShowAgentBtn(); - void onCopySLURL(); - - void onExpandCollapseBtn(); - - void centerOnTarget(bool animate); - void updateLocation(); - - // fly to the tracked item, if there is one - void fly(); - - void buildLandmarkIDLists(); - void flyToLandmark(); - void teleportToLandmark(); - void setLandmarkVisited(); - - void buildAvatarIDList(); - void flyToAvatar(); - void teleportToAvatar(); - - void updateSearchEnabled(); - void onLocationFocusChanged( LLFocusableElement* ctrl ); - void onLocationCommit(); - void onCoordinatesCommit(); - void onCommitSearchResult(); - - void onTeleportFinished(); - -private: - LLWorldMapView* mMapView; // Panel displaying the map - - // update display of teleport destination coordinates - pos is in global coordinates - void updateTeleportCoordsDisplay( const LLVector3d& pos ); - - // enable/disable teleport destination coordinates - void enableTeleportCoordsDisplay( bool enabled ); - - std::vector mLandmarkAssetIDList; - std::vector mLandmarkItemIDList; - - static const LLUUID sHomeID; - - LLInventoryModel* mInventory; - LLInventoryObserver* mInventoryObserver; - LLFriendObserver* mFriendObserver; - - std::string mCompletingRegionName; - // Local position from trackURL() request, used to select final - // position once region lookup complete. - LLVector3 mCompletingRegionPos; - - std::string mLastRegionName; - bool mWaitingForTracker; - - bool mIsClosing; - bool mSetToUserPosition; - - LLVector3d mTrackedLocation; - LLTracker::ETrackingStatus mTrackedStatus; - std::string mTrackedSimName; - LLUUID mTrackedAvatarID; - LLSLURL mSLURL; - - LLCtrlListInterface * mListFriendCombo; - LLCtrlListInterface * mListLandmarkCombo; - LLCtrlListInterface * mListSearchResults; - - boost::signals2::connection mTeleportFinishConnection; -}; - -extern LLFloaterWorldMap* gFloaterWorldMap; - - -class LLPanelHideBeacon : public LLPanel -{ -public: - static LLPanelHideBeacon* getInstance(); - - LLPanelHideBeacon(); - /*virtual*/ bool postBuild(); - /*virtual*/ void setVisible(bool visible); - /*virtual*/ void draw(); - -private: - static LLPanelHideBeacon* getPanelHideBeacon(); - void onHideButtonClick(); - void updatePosition(); - - LLButton* mHideButton; - -}; - -#endif - +/** + * @file llfloaterworldmap.h + * @brief LLFloaterWorldMap class definition + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/* + * Map of the entire world, with multiple background images, + * avatar tracking, teleportation by double-click, etc. + */ + +#ifndef LL_LLFLOATERWORLDMAP_H +#define LL_LLFLOATERWORLDMAP_H + +#include "llfloater.h" +#include "llmapimagetype.h" +#include "lltracker.h" +#include "llslurl.h" + +class LLCtrlListInterface; +class LLFriendObserver; +class LLInventoryModel; +class LLInventoryObserver; +class LLItemInfo; +class LLLineEditor; +class LLTabContainer; +class LLWorldMapView; + +class LLFloaterWorldMap : public LLFloater +{ +public: + LLFloaterWorldMap(const LLSD& key); + virtual ~LLFloaterWorldMap(); + + // Prefer this to gFloaterWorldMap + static LLFloaterWorldMap* getInstance(); + + static void *createWorldMapView(void* data); + bool postBuild(); + + /*virtual*/ void onOpen(const LLSD& key); + /*virtual*/ void onClose(bool app_quitting); + + static void reloadIcons(void*); + + /*virtual*/ void reshape( S32 width, S32 height, bool called_from_parent = true ); + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleScrollWheel(S32 x, S32 y, S32 clicks); + /*virtual*/ void draw(); + + /*virtual*/ void onFocusLost(); + + // methods for dealing with inventory. The observe() method is + // called during program startup. inventoryUpdated() will be + // called by a helper object when an interesting change has + // occurred. + void observeInventory(LLInventoryModel* inventory); + void inventoryChanged(); + + // Calls for dealing with changes in friendship + void observeFriends(); + void friendsChanged(); + + // tracking methods + void trackAvatar( const LLUUID& avatar_id, const std::string& name ); + void trackLandmark( const LLUUID& landmark_item_id ); + void trackLocation(const LLVector3d& pos); + void trackEvent(const LLItemInfo &event_info); + void trackGenericItem(const LLItemInfo &item); + void trackURL(const std::string& region_name, S32 x_coord, S32 y_coord, S32 z_coord); + + static const LLUUID& getHomeID() { return sHomeID; } + + // A z_attenuation of 0.0f collapses the distance into the X-Y plane + F32 getDistanceToDestination(const LLVector3d& pos_global, F32 z_attenuation = 0.5f) const; + + void clearLocationSelection(bool clear_ui = false, bool dest_reached = false); + void clearAvatarSelection(bool clear_ui = false); + void clearLandmarkSelection(bool clear_ui = false); + + // Adjust the maximally zoomed out limit of the zoom slider so you can + // see the whole world, plus a little. + void adjustZoomSliderBounds(); + + // Catch changes in the sim list + void updateSims(bool found_null_sim); + + // teleport to the tracked item, if there is one + void teleport(); + void onChangeMaturity(); + + void onClearBtn(); + + //Slapp instigated avatar tracking + void avatarTrackFromSlapp( const LLUUID& id ); + +protected: + void onGoHome(); + + void onLandmarkComboPrearrange(); + void onLandmarkComboCommit(); + + void onAvatarComboPrearrange(); + void onAvatarComboCommit(); + + void onComboTextEntry( ); + void onSearchTextEntry( ); + + void onClickTeleportBtn(); + void onShowTargetBtn(); + void onShowAgentBtn(); + void onCopySLURL(); + + void onExpandCollapseBtn(); + + void centerOnTarget(bool animate); + void updateLocation(); + + // fly to the tracked item, if there is one + void fly(); + + void buildLandmarkIDLists(); + void flyToLandmark(); + void teleportToLandmark(); + void setLandmarkVisited(); + + void buildAvatarIDList(); + void flyToAvatar(); + void teleportToAvatar(); + + void updateSearchEnabled(); + void onLocationFocusChanged( LLFocusableElement* ctrl ); + void onLocationCommit(); + void onCoordinatesCommit(); + void onCommitSearchResult(); + + void onTeleportFinished(); + +private: + LLWorldMapView* mMapView; // Panel displaying the map + + // update display of teleport destination coordinates - pos is in global coordinates + void updateTeleportCoordsDisplay( const LLVector3d& pos ); + + // enable/disable teleport destination coordinates + void enableTeleportCoordsDisplay( bool enabled ); + + std::vector mLandmarkAssetIDList; + std::vector mLandmarkItemIDList; + + static const LLUUID sHomeID; + + LLInventoryModel* mInventory; + LLInventoryObserver* mInventoryObserver; + LLFriendObserver* mFriendObserver; + + std::string mCompletingRegionName; + // Local position from trackURL() request, used to select final + // position once region lookup complete. + LLVector3 mCompletingRegionPos; + + std::string mLastRegionName; + bool mWaitingForTracker; + + bool mIsClosing; + bool mSetToUserPosition; + + LLVector3d mTrackedLocation; + LLTracker::ETrackingStatus mTrackedStatus; + std::string mTrackedSimName; + LLUUID mTrackedAvatarID; + LLSLURL mSLURL; + + LLCtrlListInterface * mListFriendCombo; + LLCtrlListInterface * mListLandmarkCombo; + LLCtrlListInterface * mListSearchResults; + + boost::signals2::connection mTeleportFinishConnection; +}; + +extern LLFloaterWorldMap* gFloaterWorldMap; + + +class LLPanelHideBeacon : public LLPanel +{ +public: + static LLPanelHideBeacon* getInstance(); + + LLPanelHideBeacon(); + /*virtual*/ bool postBuild(); + /*virtual*/ void setVisible(bool visible); + /*virtual*/ void draw(); + +private: + static LLPanelHideBeacon* getPanelHideBeacon(); + void onHideButtonClick(); + void updatePosition(); + + LLButton* mHideButton; + +}; + +#endif + diff --git a/indra/newview/llfolderviewmodelinventory.h b/indra/newview/llfolderviewmodelinventory.h index b3d032d31f..48b4ee5fd9 100644 --- a/indra/newview/llfolderviewmodelinventory.h +++ b/indra/newview/llfolderviewmodelinventory.h @@ -1,132 +1,132 @@ -/** - * @file llfolderviewmodelinventory.h - * @brief view model implementation specific to inventory - * class definition - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLFOLDERVIEWMODELINVENTORY_H -#define LL_LLFOLDERVIEWMODELINVENTORY_H - -#include "llinventoryfilter.h" -#include "llinventory.h" -#include "llwearabletype.h" -#include "lltooldraganddrop.h" - -class LLFolderViewModelItemInventory - : public LLFolderViewModelItemCommon -{ -public: - LLFolderViewModelItemInventory(class LLFolderViewModelInventory& root_view_model); - virtual const LLUUID& getUUID() const = 0; - virtual const LLUUID& getThumbnailUUID() const = 0; - virtual time_t getCreationDate() const = 0; // UTC seconds - virtual void setCreationDate(time_t creation_date_utc) = 0; - virtual PermissionMask getPermissionMask() const = 0; - virtual LLFolderType::EType getPreferredType() const = 0; - virtual void showProperties(void) = 0; - virtual bool isItemInTrash( void) const { return false; } // TODO: make into pure virtual. - virtual bool isItemInOutfits() const { return false; } - virtual bool isAgentInventory() const { return false; } - virtual bool isUpToDate() const = 0; - virtual void addChild(LLFolderViewModelItem* child); - virtual bool hasChildren() const = 0; - virtual LLInventoryType::EType getInventoryType() const = 0; - virtual void performAction(LLInventoryModel* model, std::string action) = 0; - virtual LLWearableType::EType getWearableType() const = 0; - virtual LLSettingsType::type_e getSettingsType() const = 0; - virtual EInventorySortGroup getSortGroup() const = 0; - virtual LLInventoryObject* getInventoryObject() const = 0; - virtual void requestSort(); - virtual void setPassedFilter(bool filtered, S32 filter_generation, std::string::size_type string_offset = std::string::npos, std::string::size_type string_size = 0); - virtual bool filter( LLFolderViewFilter& filter); - virtual bool filterChildItem( LLFolderViewModelItem* item, LLFolderViewFilter& filter); - - virtual bool startDrag(EDragAndDropType* type, LLUUID* id) const = 0; - virtual LLToolDragAndDrop::ESource getDragSource() const = 0; -protected: - bool mPrevPassedAllFilters; - time_t mLastAddedChildCreationDate; // -1 if nothing was added -}; - -class LLInventorySort -{ -public: - struct Params : public LLInitParam::Block - { - Optional order; - - Params() - : order("order", 0) - {} - }; - - LLInventorySort(S32 order = 0) - { - fromParams(Params().order(order)); - } - - bool isByDate() const { return mByDate; } - bool isFoldersByName() const { return (!mByDate || mFoldersByName) && !mFoldersByWeight; } - bool isFoldersByDate() const { return mByDate && !mFoldersByName && !mFoldersByWeight; } - U32 getSortOrder() const { return mSortOrder; } - void toParams(Params& p) { p.order(mSortOrder);} - void fromParams(Params& p) - { - mSortOrder = p.order; - mByDate = (mSortOrder & LLInventoryFilter::SO_DATE); - mSystemToTop = (mSortOrder & LLInventoryFilter::SO_SYSTEM_FOLDERS_TO_TOP); - mFoldersByName = (mSortOrder & LLInventoryFilter::SO_FOLDERS_BY_NAME); - mFoldersByWeight = (mSortOrder & LLInventoryFilter::SO_FOLDERS_BY_WEIGHT); - } - - bool operator()(const LLFolderViewModelItemInventory* const& a, const LLFolderViewModelItemInventory* const& b) const; -private: - U32 mSortOrder; - bool mByDate; - bool mSystemToTop; - bool mFoldersByName; - bool mFoldersByWeight; -}; - -class LLFolderViewModelInventory - : public LLFolderViewModel -{ -public: - typedef LLFolderViewModel base_t; - - LLFolderViewModelInventory(const std::string& name) - : base_t(new LLInventorySort(), new LLInventoryFilter(LLInventoryFilter::Params().name(name))) - {} - - void setTaskID(const LLUUID& id) {mTaskID = id;} - - void sort(LLFolderViewFolder* folder); - bool contentsReady(); - bool isFolderComplete(LLFolderViewFolder* folder); - bool startDrag(std::vector& items); - -private: - LLUUID mTaskID; -}; -#endif // LL_LLFOLDERVIEWMODELINVENTORY_H +/** + * @file llfolderviewmodelinventory.h + * @brief view model implementation specific to inventory + * class definition + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLFOLDERVIEWMODELINVENTORY_H +#define LL_LLFOLDERVIEWMODELINVENTORY_H + +#include "llinventoryfilter.h" +#include "llinventory.h" +#include "llwearabletype.h" +#include "lltooldraganddrop.h" + +class LLFolderViewModelItemInventory + : public LLFolderViewModelItemCommon +{ +public: + LLFolderViewModelItemInventory(class LLFolderViewModelInventory& root_view_model); + virtual const LLUUID& getUUID() const = 0; + virtual const LLUUID& getThumbnailUUID() const = 0; + virtual time_t getCreationDate() const = 0; // UTC seconds + virtual void setCreationDate(time_t creation_date_utc) = 0; + virtual PermissionMask getPermissionMask() const = 0; + virtual LLFolderType::EType getPreferredType() const = 0; + virtual void showProperties(void) = 0; + virtual bool isItemInTrash( void) const { return false; } // TODO: make into pure virtual. + virtual bool isItemInOutfits() const { return false; } + virtual bool isAgentInventory() const { return false; } + virtual bool isUpToDate() const = 0; + virtual void addChild(LLFolderViewModelItem* child); + virtual bool hasChildren() const = 0; + virtual LLInventoryType::EType getInventoryType() const = 0; + virtual void performAction(LLInventoryModel* model, std::string action) = 0; + virtual LLWearableType::EType getWearableType() const = 0; + virtual LLSettingsType::type_e getSettingsType() const = 0; + virtual EInventorySortGroup getSortGroup() const = 0; + virtual LLInventoryObject* getInventoryObject() const = 0; + virtual void requestSort(); + virtual void setPassedFilter(bool filtered, S32 filter_generation, std::string::size_type string_offset = std::string::npos, std::string::size_type string_size = 0); + virtual bool filter( LLFolderViewFilter& filter); + virtual bool filterChildItem( LLFolderViewModelItem* item, LLFolderViewFilter& filter); + + virtual bool startDrag(EDragAndDropType* type, LLUUID* id) const = 0; + virtual LLToolDragAndDrop::ESource getDragSource() const = 0; +protected: + bool mPrevPassedAllFilters; + time_t mLastAddedChildCreationDate; // -1 if nothing was added +}; + +class LLInventorySort +{ +public: + struct Params : public LLInitParam::Block + { + Optional order; + + Params() + : order("order", 0) + {} + }; + + LLInventorySort(S32 order = 0) + { + fromParams(Params().order(order)); + } + + bool isByDate() const { return mByDate; } + bool isFoldersByName() const { return (!mByDate || mFoldersByName) && !mFoldersByWeight; } + bool isFoldersByDate() const { return mByDate && !mFoldersByName && !mFoldersByWeight; } + U32 getSortOrder() const { return mSortOrder; } + void toParams(Params& p) { p.order(mSortOrder);} + void fromParams(Params& p) + { + mSortOrder = p.order; + mByDate = (mSortOrder & LLInventoryFilter::SO_DATE); + mSystemToTop = (mSortOrder & LLInventoryFilter::SO_SYSTEM_FOLDERS_TO_TOP); + mFoldersByName = (mSortOrder & LLInventoryFilter::SO_FOLDERS_BY_NAME); + mFoldersByWeight = (mSortOrder & LLInventoryFilter::SO_FOLDERS_BY_WEIGHT); + } + + bool operator()(const LLFolderViewModelItemInventory* const& a, const LLFolderViewModelItemInventory* const& b) const; +private: + U32 mSortOrder; + bool mByDate; + bool mSystemToTop; + bool mFoldersByName; + bool mFoldersByWeight; +}; + +class LLFolderViewModelInventory + : public LLFolderViewModel +{ +public: + typedef LLFolderViewModel base_t; + + LLFolderViewModelInventory(const std::string& name) + : base_t(new LLInventorySort(), new LLInventoryFilter(LLInventoryFilter::Params().name(name))) + {} + + void setTaskID(const LLUUID& id) {mTaskID = id;} + + void sort(LLFolderViewFolder* folder); + bool contentsReady(); + bool isFolderComplete(LLFolderViewFolder* folder); + bool startDrag(std::vector& items); + +private: + LLUUID mTaskID; +}; +#endif // LL_LLFOLDERVIEWMODELINVENTORY_H diff --git a/indra/newview/llfollowcam.cpp b/indra/newview/llfollowcam.cpp index 8ce6dc6ef4..89f77414a1 100644 --- a/indra/newview/llfollowcam.cpp +++ b/indra/newview/llfollowcam.cpp @@ -1,878 +1,878 @@ -/** - * @file llfollowcam.cpp - * @author Jeffrey Ventrella - * @brief LLFollowCam class implementation - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llfollowcam.h" -#include "llagent.h" - -//------------------------------------------------------- -// constants -//------------------------------------------------------- -const F32 FOLLOW_CAM_ZOOM_FACTOR = 0.1f; -const F32 FOLLOW_CAM_MIN_ZOOM_AMOUNT = 0.1f; -const F32 DISTANCE_EPSILON = 0.0001f; -const F32 DEFAULT_MAX_DISTANCE_FROM_SUBJECT = 1000.0; // this will be correctly set on me by my caller - -//---------------------------------------------------------------------------------------- -// This is how slowly the camera position moves to its ideal position -//---------------------------------------------------------------------------------------- -const F32 FOLLOW_CAM_MIN_POSITION_LAG = 0.0f; -const F32 FOLLOW_CAM_DEFAULT_POSITION_LAG = 0.1f; -const F32 FOLLOW_CAM_MAX_POSITION_LAG = 3.0f; - -//---------------------------------------------------------------------------------------- -// This is how slowly the camera focus moves to its subject -//---------------------------------------------------------------------------------------- -const F32 FOLLOW_CAM_MIN_FOCUS_LAG = 0.0f; -const F32 FOLLOW_CAM_DEFAULT_FOCUS_LAG = 0.1f; -const F32 FOLLOW_CAM_MAX_FOCUS_LAG = 3.0f; - -//---------------------------------------------------------------------------------------- -// This is far the position can get from its IDEAL POSITION before it starts getting pulled -//---------------------------------------------------------------------------------------- -const F32 FOLLOW_CAM_MIN_POSITION_THRESHOLD = 0.0f; -const F32 FOLLOW_CAM_DEFAULT_POSITION_THRESHOLD = 1.0f; -const F32 FOLLOW_CAM_MAX_POSITION_THRESHOLD = 4.0f; - -//---------------------------------------------------------------------------------------- -// This is far the focus can get from the subject before it starts getting pulled -//---------------------------------------------------------------------------------------- -const F32 FOLLOW_CAM_MIN_FOCUS_THRESHOLD = 0.0f; -const F32 FOLLOW_CAM_DEFAULT_FOCUS_THRESHOLD = 1.0f; -const F32 FOLLOW_CAM_MAX_FOCUS_THRESHOLD = 4.0f; - -//---------------------------------------------------------------------------------------- -// This is the distance the camera wants to be from the subject -//---------------------------------------------------------------------------------------- -const F32 FOLLOW_CAM_MIN_DISTANCE = 0.5f; -const F32 FOLLOW_CAM_DEFAULT_DISTANCE = 3.0f; -//const F32 FOLLOW_CAM_MAX_DISTANCE = 10.0f; // from now on I am using mMaxCameraDistantFromSubject - -//---------------------------------------------------------------------------------------- -// this is an angluar value -// It affects the angle that the camera rises (pitches) in relation -// to the horizontal plane -//---------------------------------------------------------------------------------------- -const F32 FOLLOW_CAM_MIN_PITCH = -45.0f; -const F32 FOLLOW_CAM_DEFAULT_PITCH = 0.0f; -const F32 FOLLOW_CAM_MAX_PITCH = 80.0f; // keep under 90 degrees - avoid gimble lock! - - -//---------------------------------------------------------------------------------------- -// how high or low the camera considers its ideal focus to be (relative to its subject) -//---------------------------------------------------------------------------------------- -const F32 FOLLOW_CAM_MIN_FOCUS_OFFSET = -10.0f; -const LLVector3 FOLLOW_CAM_DEFAULT_FOCUS_OFFSET = LLVector3(1.0f, 0.f, 0.f); -const F32 FOLLOW_CAM_MAX_FOCUS_OFFSET = 10.0f; - -//---------------------------------------------------------------------------------------- -// This affects the rate at which the camera adjusts to stay behind the subject -//---------------------------------------------------------------------------------------- -const F32 FOLLOW_CAM_MIN_BEHINDNESS_LAG = 0.0f; -const F32 FOLLOW_CAM_DEFAULT_BEHINDNESS_LAG = 0.f; -const F32 FOLLOW_CAM_MAX_BEHINDNESS_LAG = 3.0f; - -//--------------------------------------------------------------------------------------------------------------------- -// in degrees: This is the size of the pie slice behind the subject matter within which the camera is free to move -//--------------------------------------------------------------------------------------------------------------------- -const F32 FOLLOW_CAM_MIN_BEHINDNESS_ANGLE = 0.0f; -const F32 FOLLOW_CAM_DEFAULT_BEHINDNESS_ANGLE = 10.0f; -const F32 FOLLOW_CAM_MAX_BEHINDNESS_ANGLE = 180.0f; -const F32 FOLLOW_CAM_BEHINDNESS_EPSILON = 1.0f; - -//------------------------------------ -// Constructor -//------------------------------------ -LLFollowCamParams::LLFollowCamParams() -{ - mMaxCameraDistantFromSubject = DEFAULT_MAX_DISTANCE_FROM_SUBJECT; - mPositionLocked = false; - mFocusLocked = false; - mUsePosition = false; - mUseFocus = false; - - //------------------------------------------------------ - // setting the attributes to their defaults - //------------------------------------------------------ - setPositionLag ( FOLLOW_CAM_DEFAULT_POSITION_LAG ); - setFocusLag ( FOLLOW_CAM_DEFAULT_FOCUS_LAG ); - setPositionThreshold( FOLLOW_CAM_DEFAULT_POSITION_THRESHOLD ); - setFocusThreshold ( FOLLOW_CAM_DEFAULT_FOCUS_THRESHOLD ); - setBehindnessLag ( FOLLOW_CAM_DEFAULT_BEHINDNESS_LAG ); - setDistance ( FOLLOW_CAM_DEFAULT_DISTANCE ); - setPitch ( FOLLOW_CAM_DEFAULT_PITCH ); - setFocusOffset ( FOLLOW_CAM_DEFAULT_FOCUS_OFFSET ); - setBehindnessAngle ( FOLLOW_CAM_DEFAULT_BEHINDNESS_ANGLE ); - setPositionThreshold( FOLLOW_CAM_DEFAULT_POSITION_THRESHOLD ); - setFocusThreshold ( FOLLOW_CAM_DEFAULT_FOCUS_THRESHOLD ); - -} - -LLFollowCamParams::~LLFollowCamParams() { } - -//--------------------------------------------------------- -// buncho set methods -//--------------------------------------------------------- - -//--------------------------------------------------------- -void LLFollowCamParams::setPositionLag( F32 p ) -{ - mPositionLag = llclamp(p, FOLLOW_CAM_MIN_POSITION_LAG, FOLLOW_CAM_MAX_POSITION_LAG); -} - - -//--------------------------------------------------------- -void LLFollowCamParams::setFocusLag( F32 f ) -{ - mFocusLag = llclamp(f, FOLLOW_CAM_MIN_FOCUS_LAG, FOLLOW_CAM_MAX_FOCUS_LAG); -} - - -//--------------------------------------------------------- -void LLFollowCamParams::setPositionThreshold( F32 p ) -{ - mPositionThreshold = llclamp(p, FOLLOW_CAM_MIN_POSITION_THRESHOLD, FOLLOW_CAM_MAX_POSITION_THRESHOLD); -} - - -//--------------------------------------------------------- -void LLFollowCamParams::setFocusThreshold( F32 f ) -{ - mFocusThreshold = llclamp(f, FOLLOW_CAM_MIN_FOCUS_THRESHOLD, FOLLOW_CAM_MAX_FOCUS_THRESHOLD); -} - - -//--------------------------------------------------------- -void LLFollowCamParams::setPitch( F32 p ) -{ - mPitch = llclamp(p, FOLLOW_CAM_MIN_PITCH, FOLLOW_CAM_MAX_PITCH); -} - - -//--------------------------------------------------------- -void LLFollowCamParams::setBehindnessLag( F32 b ) -{ - mBehindnessLag = llclamp(b, FOLLOW_CAM_MIN_BEHINDNESS_LAG, FOLLOW_CAM_MAX_BEHINDNESS_LAG); -} - -//--------------------------------------------------------- -void LLFollowCamParams::setBehindnessAngle( F32 b ) -{ - mBehindnessMaxAngle = llclamp(b, FOLLOW_CAM_MIN_BEHINDNESS_ANGLE, FOLLOW_CAM_MAX_BEHINDNESS_ANGLE); -} - -//--------------------------------------------------------- -void LLFollowCamParams::setDistance( F32 d ) -{ - mDistance = llclamp(d, FOLLOW_CAM_MIN_DISTANCE, mMaxCameraDistantFromSubject); -} - -//--------------------------------------------------------- -void LLFollowCamParams::setPositionLocked( bool l ) -{ - mPositionLocked = l; -} - -//--------------------------------------------------------- -void LLFollowCamParams::setFocusLocked( bool l ) -{ - mFocusLocked = l; - -} - -//--------------------------------------------------------- -void LLFollowCamParams::setFocusOffset( const LLVector3& v ) -{ - mFocusOffset = v; - mFocusOffset.clamp(FOLLOW_CAM_MIN_FOCUS_OFFSET, FOLLOW_CAM_MAX_FOCUS_OFFSET); -} - -//--------------------------------------------------------- -void LLFollowCamParams::setPosition( const LLVector3& p ) -{ - mUsePosition = true; - mPosition = p; -} - -//--------------------------------------------------------- -void LLFollowCamParams::setFocus( const LLVector3& f ) -{ - mUseFocus = true; - mFocus = f; -} - -//--------------------------------------------------------- -// buncho get methods -//--------------------------------------------------------- -F32 LLFollowCamParams::getPositionLag () const { return mPositionLag; } -F32 LLFollowCamParams::getFocusLag () const { return mFocusLag; } -F32 LLFollowCamParams::getPositionThreshold () const { return mPositionThreshold; } -F32 LLFollowCamParams::getFocusThreshold () const { return mFocusThreshold; } -F32 LLFollowCamParams::getDistance () const { return mDistance; } -F32 LLFollowCamParams::getPitch () const { return mPitch; } -LLVector3 LLFollowCamParams::getFocusOffset () const { return mFocusOffset; } -F32 LLFollowCamParams::getBehindnessAngle () const { return mBehindnessMaxAngle; } -F32 LLFollowCamParams::getBehindnessLag () const { return mBehindnessLag; } -LLVector3 LLFollowCamParams::getPosition () const { return mPosition; } -LLVector3 LLFollowCamParams::getFocus () const { return mFocus; } -bool LLFollowCamParams::getPositionLocked () const { return mPositionLocked; } -bool LLFollowCamParams::getFocusLocked () const { return mFocusLocked; } - -//------------------------------------ -// Constructor -//------------------------------------ -LLFollowCam::LLFollowCam() : LLFollowCamParams() -{ - mUpVector = LLVector3::z_axis; - mSubjectPosition = LLVector3::zero; - mSubjectRotation = LLQuaternion::DEFAULT; - - mZoomedToMinimumDistance = false; - mPitchCos = mPitchSin = 0.f; - mPitchSineAndCosineNeedToBeUpdated = true; - - mSimulatedDistance = mDistance; -} - -void LLFollowCam::copyParams(LLFollowCamParams& params) -{ - setPositionLag(params.getPositionLag()); - setFocusLag(params.getFocusLag()); - setFocusThreshold( params.getFocusThreshold()); - setPositionThreshold(params.getPositionThreshold()); - setPitch(params.getPitch()); - setFocusOffset(params.getFocusOffset()); - setBehindnessAngle(params.getBehindnessAngle()); - setBehindnessLag(params.getBehindnessLag()); - - setPositionLocked(params.getPositionLocked()); - setFocusLocked(params.getFocusLocked()); - - setDistance(params.getDistance()); - if (params.getUsePosition()) - { - setPosition(params.getPosition()); - } - if (params.getUseFocus()) - { - setFocus(params.getFocus()); - } -} - -//--------------------------------------------------------------------------------------------------------- -void LLFollowCam::update() -{ - //#################################################################################### - // update Focus - //#################################################################################### - LLVector3 offsetSubjectPosition = mSubjectPosition + (mFocusOffset * mSubjectRotation); - - LLVector3 simulated_pos_agent = gAgent.getPosAgentFromGlobal(mSimulatedPositionGlobal); - LLVector3 vectorFromCameraToSubject = offsetSubjectPosition - simulated_pos_agent; - F32 distanceFromCameraToSubject = vectorFromCameraToSubject.magVec(); - - LLVector3 whereFocusWantsToBe = mFocus; - LLVector3 focus_pt_agent = gAgent.getPosAgentFromGlobal(mSimulatedFocusGlobal); - if ( mFocusLocked ) // if focus is locked, only relative focus needs to be updated - { - mRelativeFocus = (focus_pt_agent - mSubjectPosition) * ~mSubjectRotation; - } - else - { - LLVector3 focusOffset = offsetSubjectPosition - focus_pt_agent; - F32 focusOffsetDistance = focusOffset.magVec(); - - LLVector3 focusOffsetDirection = focusOffset / focusOffsetDistance; - whereFocusWantsToBe = focus_pt_agent + - (focusOffsetDirection * (focusOffsetDistance - mFocusThreshold)); - if ( focusOffsetDistance > mFocusThreshold ) - { - // this version normalizes focus threshold by distance - // so that the effect is not changed with distance - /* - F32 focusThresholdNormalizedByDistance = distanceFromCameraToSubject * mFocusThreshold; - if ( focusOffsetDistance > focusThresholdNormalizedByDistance ) - { - LLVector3 focusOffsetDirection = focusOffset / focusOffsetDistance; - F32 force = focusOffsetDistance - focusThresholdNormalizedByDistance; - */ - - F32 focusLagLerp = LLSmoothInterpolation::getInterpolant( mFocusLag ); - focus_pt_agent = lerp( focus_pt_agent, whereFocusWantsToBe, focusLagLerp ); - mSimulatedFocusGlobal = gAgent.getPosGlobalFromAgent(focus_pt_agent); - } - mRelativeFocus = lerp(mRelativeFocus, (focus_pt_agent - mSubjectPosition) * ~mSubjectRotation, LLSmoothInterpolation::getInterpolant(0.05f)); - }// if focus is not locked --------------------------------------------- - - - LLVector3 whereCameraPositionWantsToBe = gAgent.getPosAgentFromGlobal(mSimulatedPositionGlobal); - if ( mPositionLocked ) - { - mRelativePos = (whereCameraPositionWantsToBe - mSubjectPosition) * ~mSubjectRotation; - } - else - { - //#################################################################################### - // update Position - //#################################################################################### - //------------------------------------------------------------------------- - // I determine the horizontal vector from the camera to the subject - //------------------------------------------------------------------------- - LLVector3 horizontalVectorFromCameraToSubject = vectorFromCameraToSubject; - horizontalVectorFromCameraToSubject.mV[VZ] = 0.0f; - - //--------------------------------------------------------- - // Now I determine the horizontal distance - //--------------------------------------------------------- - F32 horizontalDistanceFromCameraToSubject = horizontalVectorFromCameraToSubject.magVec(); - - //--------------------------------------------------------- - // Then I get the (normalized) horizontal direction... - //--------------------------------------------------------- - LLVector3 horizontalDirectionFromCameraToSubject; - if ( horizontalDistanceFromCameraToSubject < DISTANCE_EPSILON ) - { - // make sure we still have a normalized vector if distance is really small - // (this case is rare and fleeting) - horizontalDirectionFromCameraToSubject = LLVector3::z_axis; - } - else - { - // I'm not using the "normalize" method, because I can just divide by horizontalDistanceFromCameraToSubject - horizontalDirectionFromCameraToSubject = horizontalVectorFromCameraToSubject / horizontalDistanceFromCameraToSubject; - } - - //------------------------------------------------------------------------------------------------------------ - // Here is where I determine an offset relative to subject position in oder to set the ideal position. - //------------------------------------------------------------------------------------------------------------ - if ( mPitchSineAndCosineNeedToBeUpdated ) - { - calculatePitchSineAndCosine(); - mPitchSineAndCosineNeedToBeUpdated = false; - } - - LLVector3 positionOffsetFromSubject; - positionOffsetFromSubject.setVec - ( - horizontalDirectionFromCameraToSubject.mV[ VX ] * mPitchCos, - horizontalDirectionFromCameraToSubject.mV[ VY ] * mPitchCos, - -mPitchSin - ); - - positionOffsetFromSubject *= mSimulatedDistance; - - //---------------------------------------------------------------------- - // Finally, ideal position is set by taking the subject position and - // extending the positionOffsetFromSubject from that - //---------------------------------------------------------------------- - LLVector3 idealCameraPosition = offsetSubjectPosition - positionOffsetFromSubject; - - //-------------------------------------------------------------------------------- - // Now I prepare to move the current camera position towards its ideal position... - //-------------------------------------------------------------------------------- - LLVector3 vectorFromPositionToIdealPosition = idealCameraPosition - simulated_pos_agent; - F32 distanceFromPositionToIdealPosition = vectorFromPositionToIdealPosition.magVec(); - - //put this inside of the block? - LLVector3 normalFromPositionToIdealPosition = vectorFromPositionToIdealPosition / distanceFromPositionToIdealPosition; - - whereCameraPositionWantsToBe = simulated_pos_agent + - (normalFromPositionToIdealPosition * (distanceFromPositionToIdealPosition - mPositionThreshold)); - //------------------------------------------------------------------------------------------------- - // The following method takes the target camera position and resets it so that it stays "behind" the subject, - // using behindness angle and behindness force as parameters affecting the exact behavior - //------------------------------------------------------------------------------------------------- - if ( distanceFromPositionToIdealPosition > mPositionThreshold ) - { - F32 positionPullLerp = LLSmoothInterpolation::getInterpolant( mPositionLag ); - simulated_pos_agent = lerp( simulated_pos_agent, whereCameraPositionWantsToBe, positionPullLerp ); - } - - //-------------------------------------------------------------------- - // don't let the camera get farther than its official max distance - //-------------------------------------------------------------------- - if ( distanceFromCameraToSubject > mMaxCameraDistantFromSubject ) - { - LLVector3 directionFromCameraToSubject = vectorFromCameraToSubject / distanceFromCameraToSubject; - simulated_pos_agent = offsetSubjectPosition - directionFromCameraToSubject * mMaxCameraDistantFromSubject; - } - - ////------------------------------------------------------------------------------------------------- - //// The following method takes mSimulatedPositionGlobal and resets it so that it stays "behind" the subject, - //// using behindness angle and behindness force as parameters affecting the exact behavior - ////------------------------------------------------------------------------------------------------- - updateBehindnessConstraint(gAgent.getPosAgentFromGlobal(mSimulatedFocusGlobal), simulated_pos_agent); - mSimulatedPositionGlobal = gAgent.getPosGlobalFromAgent(simulated_pos_agent); - - mRelativePos = lerp(mRelativePos, (simulated_pos_agent - mSubjectPosition) * ~mSubjectRotation, LLSmoothInterpolation::getInterpolant(0.05f)); - } // if position is not locked ----------------------------------------------------------- - - - //#################################################################################### - // update UpVector - //#################################################################################### - // this just points upward for now, but I anticipate future effects requiring - // some rolling ("banking" effects for fun, swoopy vehicles, etc.) - mUpVector = LLVector3::z_axis; -} - - - -//------------------------------------------------------------------------------------- -bool LLFollowCam::updateBehindnessConstraint(LLVector3 focus, LLVector3& cam_position) -{ - bool constraint_active = false; - // only apply this stuff if the behindness angle is something other than opened up all the way - if ( mBehindnessMaxAngle < FOLLOW_CAM_MAX_BEHINDNESS_ANGLE - FOLLOW_CAM_BEHINDNESS_EPSILON ) - { - //-------------------------------------------------------------- - // horizontalized vector from focus to camera - //-------------------------------------------------------------- - LLVector3 horizontalVectorFromFocusToCamera; - horizontalVectorFromFocusToCamera.setVec(cam_position - focus); - horizontalVectorFromFocusToCamera.mV[ VZ ] = 0.0f; - F32 cameraZ = cam_position.mV[ VZ ]; - - //-------------------------------------------------------------- - // distance of horizontalized vector - //-------------------------------------------------------------- - F32 horizontalDistance = horizontalVectorFromFocusToCamera.magVec(); - - //-------------------------------------------------------------------------------------------------- - // calculate horizontalized back vector of the subject and scale by horizontalDistance - //-------------------------------------------------------------------------------------------------- - LLVector3 horizontalSubjectBack( -1.0f, 0.0f, 0.0f ); - horizontalSubjectBack *= mSubjectRotation; - horizontalSubjectBack.mV[ VZ ] = 0.0f; - horizontalSubjectBack.normVec(); // because horizontalizing might make it shorter than 1 - horizontalSubjectBack *= horizontalDistance; - - //-------------------------------------------------------------------------------------------------- - // find the angle (in degrees) between these vectors - //-------------------------------------------------------------------------------------------------- - F32 cameraOffsetAngle = 0.f; - LLVector3 cameraOffsetRotationAxis; - LLQuaternion camera_offset_rotation; - camera_offset_rotation.shortestArc(horizontalSubjectBack, horizontalVectorFromFocusToCamera); - camera_offset_rotation.getAngleAxis(&cameraOffsetAngle, cameraOffsetRotationAxis); - cameraOffsetAngle *= RAD_TO_DEG; - - if ( cameraOffsetAngle > mBehindnessMaxAngle ) - { - F32 fraction = ((cameraOffsetAngle - mBehindnessMaxAngle) / cameraOffsetAngle) * LLSmoothInterpolation::getInterpolant(mBehindnessLag); - cam_position = focus + horizontalSubjectBack * (slerp(fraction, camera_offset_rotation, LLQuaternion::DEFAULT)); - cam_position.mV[VZ] = cameraZ; // clamp z value back to what it was before we started messing with it - constraint_active = true; - } - } - return constraint_active; -} - - -//--------------------------------------------------------- -void LLFollowCam::calculatePitchSineAndCosine() -{ - F32 radian = mPitch * DEG_TO_RAD; - mPitchCos = cos( radian ); - mPitchSin = sin( radian ); -} - -//--------------------------------------------------------- -void LLFollowCam::setSubjectPositionAndRotation( const LLVector3 p, const LLQuaternion r ) -{ - mSubjectPosition = p; - mSubjectRotation = r; -} - - -//--------------------------------------------------------- -void LLFollowCam::zoom( S32 z ) -{ - F32 zoomAmount = z * mSimulatedDistance * FOLLOW_CAM_ZOOM_FACTOR; - - if (( zoomAmount < FOLLOW_CAM_MIN_ZOOM_AMOUNT ) - && ( zoomAmount > -FOLLOW_CAM_MIN_ZOOM_AMOUNT )) - { - if ( zoomAmount < 0.0f ) - { - zoomAmount = -FOLLOW_CAM_MIN_ZOOM_AMOUNT; - } - else - { - zoomAmount = FOLLOW_CAM_MIN_ZOOM_AMOUNT; - } - } - - mSimulatedDistance += zoomAmount; - - mZoomedToMinimumDistance = false; - if ( mSimulatedDistance < FOLLOW_CAM_MIN_DISTANCE ) - { - mSimulatedDistance = FOLLOW_CAM_MIN_DISTANCE; - - // if zoomAmount is negative (i.e., getting closer), then - // this signifies having hit the minimum: - if ( zoomAmount < 0.0f ) - { - mZoomedToMinimumDistance = true; - } - } - else if ( mSimulatedDistance > mMaxCameraDistantFromSubject ) - { - mSimulatedDistance = mMaxCameraDistantFromSubject; - } -} - - -//--------------------------------------------------------- -bool LLFollowCam::isZoomedToMinimumDistance() -{ - return mZoomedToMinimumDistance; -} - - -//--------------------------------------------------------- -void LLFollowCam::reset( const LLVector3 p, const LLVector3 f , const LLVector3 u ) -{ - setPosition(p); - setFocus(f); - mUpVector = u; -} - -//--------------------------------------------------------- -void LLFollowCam::setMaxCameraDistantFromSubject( F32 m ) -{ - mMaxCameraDistantFromSubject = m; -} - - -void LLFollowCam::setPitch( F32 p ) -{ - LLFollowCamParams::setPitch(p); - mPitchSineAndCosineNeedToBeUpdated = true; // important -} - -void LLFollowCam::setDistance( F32 d ) -{ - if (d != mDistance) - { - LLFollowCamParams::setDistance(d); - mSimulatedDistance = d; - mZoomedToMinimumDistance = false; - } -} - -void LLFollowCam::setPosition( const LLVector3& p ) -{ - if (p != mPosition) - { - LLFollowCamParams::setPosition(p); - mSimulatedPositionGlobal = gAgent.getPosGlobalFromAgent(mPosition); - if (mPositionLocked) - { - mRelativePos = (mPosition - mSubjectPosition) * ~mSubjectRotation; - } - } -} - -void LLFollowCam::setFocus( const LLVector3& f ) -{ - if (f != mFocus) - { - LLFollowCamParams::setFocus(f); - mSimulatedFocusGlobal = gAgent.getPosGlobalFromAgent(f); - if (mFocusLocked) - { - mRelativeFocus = (mFocus - mSubjectPosition) * ~mSubjectRotation; - } - } -} - -void LLFollowCam::setPositionLocked( bool locked ) -{ - LLFollowCamParams::setPositionLocked(locked); - if (locked) - { - // propagate set position to relative position - mRelativePos = (gAgent.getPosAgentFromGlobal(mSimulatedPositionGlobal) - mSubjectPosition) * ~mSubjectRotation; - } -} - -void LLFollowCam::setFocusLocked( bool locked ) -{ - LLFollowCamParams::setFocusLocked(locked); - if (locked) - { - // propagate set position to relative position - mRelativeFocus = (gAgent.getPosAgentFromGlobal(mSimulatedFocusGlobal) - mSubjectPosition) * ~mSubjectRotation; - } -} - - -LLVector3 LLFollowCam::getSimulatedPosition() const -{ - // return simulated position - return mSubjectPosition + (mRelativePos * mSubjectRotation); -} - -LLVector3 LLFollowCam::getSimulatedFocus() const -{ - // return simulated focus point - return mSubjectPosition + (mRelativeFocus * mSubjectRotation); -} - -LLVector3 LLFollowCam::getUpVector() -{ - return mUpVector; -} - - -//------------------------------------ -// Destructor -//------------------------------------ -LLFollowCam::~LLFollowCam() -{ -} - - -//------------------------------------------------------- -// LLFollowCamMgr -//------------------------------------------------------- -LLFollowCamMgr::LLFollowCamMgr() -{ -} - -LLFollowCamMgr::~LLFollowCamMgr() -{ - for (param_map_t::iterator iter = mParamMap.begin(); iter != mParamMap.end(); ++iter) - { - LLFollowCamParams* params = iter->second; - delete params; - } - mParamMap.clear(); -} - -void LLFollowCamMgr::setPositionLag( const LLUUID& source, F32 lag) -{ - LLFollowCamParams* paramsp = getParamsForID(source); - if (paramsp) - { - paramsp->setPositionLag(lag); - } -} - -void LLFollowCamMgr::setFocusLag( const LLUUID& source, F32 lag) -{ - LLFollowCamParams* paramsp = getParamsForID(source); - if (paramsp) - { - paramsp->setFocusLag(lag); - } -} - -void LLFollowCamMgr::setFocusThreshold( const LLUUID& source, F32 threshold) -{ - LLFollowCamParams* paramsp = getParamsForID(source); - if (paramsp) - { - paramsp->setFocusThreshold(threshold); - } - -} - -void LLFollowCamMgr::setPositionThreshold( const LLUUID& source, F32 threshold) -{ - LLFollowCamParams* paramsp = getParamsForID(source); - if (paramsp) - { - paramsp->setPositionThreshold(threshold); - } -} - -void LLFollowCamMgr::setDistance( const LLUUID& source, F32 distance) -{ - LLFollowCamParams* paramsp = getParamsForID(source); - if (paramsp) - { - paramsp->setDistance(distance); - } -} - -void LLFollowCamMgr::setPitch( const LLUUID& source, F32 pitch) -{ - LLFollowCamParams* paramsp = getParamsForID(source); - if (paramsp) - { - paramsp->setPitch(pitch); - } -} - -void LLFollowCamMgr::setFocusOffset( const LLUUID& source, const LLVector3& offset) -{ - LLFollowCamParams* paramsp = getParamsForID(source); - if (paramsp) - { - paramsp->setFocusOffset(offset); - } -} - -void LLFollowCamMgr::setBehindnessAngle( const LLUUID& source, F32 angle) -{ - LLFollowCamParams* paramsp = getParamsForID(source); - if (paramsp) - { - paramsp->setBehindnessAngle(angle); - } -} - -void LLFollowCamMgr::setBehindnessLag( const LLUUID& source, F32 force) -{ - LLFollowCamParams* paramsp = getParamsForID(source); - if (paramsp) - { - paramsp->setBehindnessLag(force); - } -} - -void LLFollowCamMgr::setPosition( const LLUUID& source, const LLVector3 position) -{ - LLFollowCamParams* paramsp = getParamsForID(source); - if (paramsp) - { - paramsp->setPosition(position); - } -} - -void LLFollowCamMgr::setFocus( const LLUUID& source, const LLVector3 focus) -{ - LLFollowCamParams* paramsp = getParamsForID(source); - if (paramsp) - { - paramsp->setFocus(focus); - } -} - -void LLFollowCamMgr::setPositionLocked( const LLUUID& source, bool locked) -{ - LLFollowCamParams* paramsp = getParamsForID(source); - if (paramsp) - { - paramsp->setPositionLocked(locked); - } -} - -void LLFollowCamMgr::setFocusLocked( const LLUUID& source, bool locked ) -{ - LLFollowCamParams* paramsp = getParamsForID(source); - if (paramsp) - { - paramsp->setFocusLocked(locked); - } -} - -LLFollowCamParams* LLFollowCamMgr::getParamsForID(const LLUUID& source) -{ - LLFollowCamParams* params = NULL; - - param_map_t::iterator found_it = mParamMap.find(source); - if (found_it == mParamMap.end()) // didn't find it? - { - params = new LLFollowCamParams(); - mParamMap[source] = params; - } - else - { - params = found_it->second; - } - - return params; -} - -LLFollowCamParams* LLFollowCamMgr::getActiveFollowCamParams() -{ - if (mParamStack.empty()) - { - return NULL; - } - - return mParamStack.back(); -} - -void LLFollowCamMgr::setCameraActive( const LLUUID& source, bool active ) -{ - LLFollowCamParams* params = getParamsForID(source); - param_stack_t::iterator found_it = std::find(mParamStack.begin(), mParamStack.end(), params); - if (found_it != mParamStack.end()) - { - mParamStack.erase(found_it); - } - // put on top of stack - if(active) - { - mParamStack.push_back(params); - } -} - -void LLFollowCamMgr::removeFollowCamParams(const LLUUID& source) -{ - setCameraActive(source, false); - LLFollowCamParams* params = getParamsForID(source); - mParamMap.erase(source); - delete params; -} - -bool LLFollowCamMgr::isScriptedCameraSource(const LLUUID& source) -{ - param_map_t::iterator found_it = mParamMap.find(source); - return (found_it != mParamMap.end()); -} - -void LLFollowCamMgr::dump() -{ - S32 param_count = 0; - LL_INFOS() << "Scripted camera active stack" << LL_ENDL; - for (param_stack_t::iterator param_it = mParamStack.begin(); - param_it != mParamStack.end(); - ++param_it) - { - LL_INFOS() << param_count++ << - " rot_limit: " << (*param_it)->getBehindnessAngle() << - " rot_lag: " << (*param_it)->getBehindnessLag() << - " distance: " << (*param_it)->getDistance() << - " focus: " << (*param_it)->getFocus() << - " foc_lag: " << (*param_it)->getFocusLag() << - " foc_lock: " << ((*param_it)->getFocusLocked() ? "Y" : "N") << - " foc_offset: " << (*param_it)->getFocusOffset() << - " foc_thresh: " << (*param_it)->getFocusThreshold() << - " pitch: " << (*param_it)->getPitch() << - " pos: " << (*param_it)->getPosition() << - " pos_lag: " << (*param_it)->getPositionLag() << - " pos_lock: " << ((*param_it)->getPositionLocked() ? "Y" : "N") << - " pos_thresh: " << (*param_it)->getPositionThreshold() << LL_ENDL; - } -} - +/** + * @file llfollowcam.cpp + * @author Jeffrey Ventrella + * @brief LLFollowCam class implementation + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llfollowcam.h" +#include "llagent.h" + +//------------------------------------------------------- +// constants +//------------------------------------------------------- +const F32 FOLLOW_CAM_ZOOM_FACTOR = 0.1f; +const F32 FOLLOW_CAM_MIN_ZOOM_AMOUNT = 0.1f; +const F32 DISTANCE_EPSILON = 0.0001f; +const F32 DEFAULT_MAX_DISTANCE_FROM_SUBJECT = 1000.0; // this will be correctly set on me by my caller + +//---------------------------------------------------------------------------------------- +// This is how slowly the camera position moves to its ideal position +//---------------------------------------------------------------------------------------- +const F32 FOLLOW_CAM_MIN_POSITION_LAG = 0.0f; +const F32 FOLLOW_CAM_DEFAULT_POSITION_LAG = 0.1f; +const F32 FOLLOW_CAM_MAX_POSITION_LAG = 3.0f; + +//---------------------------------------------------------------------------------------- +// This is how slowly the camera focus moves to its subject +//---------------------------------------------------------------------------------------- +const F32 FOLLOW_CAM_MIN_FOCUS_LAG = 0.0f; +const F32 FOLLOW_CAM_DEFAULT_FOCUS_LAG = 0.1f; +const F32 FOLLOW_CAM_MAX_FOCUS_LAG = 3.0f; + +//---------------------------------------------------------------------------------------- +// This is far the position can get from its IDEAL POSITION before it starts getting pulled +//---------------------------------------------------------------------------------------- +const F32 FOLLOW_CAM_MIN_POSITION_THRESHOLD = 0.0f; +const F32 FOLLOW_CAM_DEFAULT_POSITION_THRESHOLD = 1.0f; +const F32 FOLLOW_CAM_MAX_POSITION_THRESHOLD = 4.0f; + +//---------------------------------------------------------------------------------------- +// This is far the focus can get from the subject before it starts getting pulled +//---------------------------------------------------------------------------------------- +const F32 FOLLOW_CAM_MIN_FOCUS_THRESHOLD = 0.0f; +const F32 FOLLOW_CAM_DEFAULT_FOCUS_THRESHOLD = 1.0f; +const F32 FOLLOW_CAM_MAX_FOCUS_THRESHOLD = 4.0f; + +//---------------------------------------------------------------------------------------- +// This is the distance the camera wants to be from the subject +//---------------------------------------------------------------------------------------- +const F32 FOLLOW_CAM_MIN_DISTANCE = 0.5f; +const F32 FOLLOW_CAM_DEFAULT_DISTANCE = 3.0f; +//const F32 FOLLOW_CAM_MAX_DISTANCE = 10.0f; // from now on I am using mMaxCameraDistantFromSubject + +//---------------------------------------------------------------------------------------- +// this is an angluar value +// It affects the angle that the camera rises (pitches) in relation +// to the horizontal plane +//---------------------------------------------------------------------------------------- +const F32 FOLLOW_CAM_MIN_PITCH = -45.0f; +const F32 FOLLOW_CAM_DEFAULT_PITCH = 0.0f; +const F32 FOLLOW_CAM_MAX_PITCH = 80.0f; // keep under 90 degrees - avoid gimble lock! + + +//---------------------------------------------------------------------------------------- +// how high or low the camera considers its ideal focus to be (relative to its subject) +//---------------------------------------------------------------------------------------- +const F32 FOLLOW_CAM_MIN_FOCUS_OFFSET = -10.0f; +const LLVector3 FOLLOW_CAM_DEFAULT_FOCUS_OFFSET = LLVector3(1.0f, 0.f, 0.f); +const F32 FOLLOW_CAM_MAX_FOCUS_OFFSET = 10.0f; + +//---------------------------------------------------------------------------------------- +// This affects the rate at which the camera adjusts to stay behind the subject +//---------------------------------------------------------------------------------------- +const F32 FOLLOW_CAM_MIN_BEHINDNESS_LAG = 0.0f; +const F32 FOLLOW_CAM_DEFAULT_BEHINDNESS_LAG = 0.f; +const F32 FOLLOW_CAM_MAX_BEHINDNESS_LAG = 3.0f; + +//--------------------------------------------------------------------------------------------------------------------- +// in degrees: This is the size of the pie slice behind the subject matter within which the camera is free to move +//--------------------------------------------------------------------------------------------------------------------- +const F32 FOLLOW_CAM_MIN_BEHINDNESS_ANGLE = 0.0f; +const F32 FOLLOW_CAM_DEFAULT_BEHINDNESS_ANGLE = 10.0f; +const F32 FOLLOW_CAM_MAX_BEHINDNESS_ANGLE = 180.0f; +const F32 FOLLOW_CAM_BEHINDNESS_EPSILON = 1.0f; + +//------------------------------------ +// Constructor +//------------------------------------ +LLFollowCamParams::LLFollowCamParams() +{ + mMaxCameraDistantFromSubject = DEFAULT_MAX_DISTANCE_FROM_SUBJECT; + mPositionLocked = false; + mFocusLocked = false; + mUsePosition = false; + mUseFocus = false; + + //------------------------------------------------------ + // setting the attributes to their defaults + //------------------------------------------------------ + setPositionLag ( FOLLOW_CAM_DEFAULT_POSITION_LAG ); + setFocusLag ( FOLLOW_CAM_DEFAULT_FOCUS_LAG ); + setPositionThreshold( FOLLOW_CAM_DEFAULT_POSITION_THRESHOLD ); + setFocusThreshold ( FOLLOW_CAM_DEFAULT_FOCUS_THRESHOLD ); + setBehindnessLag ( FOLLOW_CAM_DEFAULT_BEHINDNESS_LAG ); + setDistance ( FOLLOW_CAM_DEFAULT_DISTANCE ); + setPitch ( FOLLOW_CAM_DEFAULT_PITCH ); + setFocusOffset ( FOLLOW_CAM_DEFAULT_FOCUS_OFFSET ); + setBehindnessAngle ( FOLLOW_CAM_DEFAULT_BEHINDNESS_ANGLE ); + setPositionThreshold( FOLLOW_CAM_DEFAULT_POSITION_THRESHOLD ); + setFocusThreshold ( FOLLOW_CAM_DEFAULT_FOCUS_THRESHOLD ); + +} + +LLFollowCamParams::~LLFollowCamParams() { } + +//--------------------------------------------------------- +// buncho set methods +//--------------------------------------------------------- + +//--------------------------------------------------------- +void LLFollowCamParams::setPositionLag( F32 p ) +{ + mPositionLag = llclamp(p, FOLLOW_CAM_MIN_POSITION_LAG, FOLLOW_CAM_MAX_POSITION_LAG); +} + + +//--------------------------------------------------------- +void LLFollowCamParams::setFocusLag( F32 f ) +{ + mFocusLag = llclamp(f, FOLLOW_CAM_MIN_FOCUS_LAG, FOLLOW_CAM_MAX_FOCUS_LAG); +} + + +//--------------------------------------------------------- +void LLFollowCamParams::setPositionThreshold( F32 p ) +{ + mPositionThreshold = llclamp(p, FOLLOW_CAM_MIN_POSITION_THRESHOLD, FOLLOW_CAM_MAX_POSITION_THRESHOLD); +} + + +//--------------------------------------------------------- +void LLFollowCamParams::setFocusThreshold( F32 f ) +{ + mFocusThreshold = llclamp(f, FOLLOW_CAM_MIN_FOCUS_THRESHOLD, FOLLOW_CAM_MAX_FOCUS_THRESHOLD); +} + + +//--------------------------------------------------------- +void LLFollowCamParams::setPitch( F32 p ) +{ + mPitch = llclamp(p, FOLLOW_CAM_MIN_PITCH, FOLLOW_CAM_MAX_PITCH); +} + + +//--------------------------------------------------------- +void LLFollowCamParams::setBehindnessLag( F32 b ) +{ + mBehindnessLag = llclamp(b, FOLLOW_CAM_MIN_BEHINDNESS_LAG, FOLLOW_CAM_MAX_BEHINDNESS_LAG); +} + +//--------------------------------------------------------- +void LLFollowCamParams::setBehindnessAngle( F32 b ) +{ + mBehindnessMaxAngle = llclamp(b, FOLLOW_CAM_MIN_BEHINDNESS_ANGLE, FOLLOW_CAM_MAX_BEHINDNESS_ANGLE); +} + +//--------------------------------------------------------- +void LLFollowCamParams::setDistance( F32 d ) +{ + mDistance = llclamp(d, FOLLOW_CAM_MIN_DISTANCE, mMaxCameraDistantFromSubject); +} + +//--------------------------------------------------------- +void LLFollowCamParams::setPositionLocked( bool l ) +{ + mPositionLocked = l; +} + +//--------------------------------------------------------- +void LLFollowCamParams::setFocusLocked( bool l ) +{ + mFocusLocked = l; + +} + +//--------------------------------------------------------- +void LLFollowCamParams::setFocusOffset( const LLVector3& v ) +{ + mFocusOffset = v; + mFocusOffset.clamp(FOLLOW_CAM_MIN_FOCUS_OFFSET, FOLLOW_CAM_MAX_FOCUS_OFFSET); +} + +//--------------------------------------------------------- +void LLFollowCamParams::setPosition( const LLVector3& p ) +{ + mUsePosition = true; + mPosition = p; +} + +//--------------------------------------------------------- +void LLFollowCamParams::setFocus( const LLVector3& f ) +{ + mUseFocus = true; + mFocus = f; +} + +//--------------------------------------------------------- +// buncho get methods +//--------------------------------------------------------- +F32 LLFollowCamParams::getPositionLag () const { return mPositionLag; } +F32 LLFollowCamParams::getFocusLag () const { return mFocusLag; } +F32 LLFollowCamParams::getPositionThreshold () const { return mPositionThreshold; } +F32 LLFollowCamParams::getFocusThreshold () const { return mFocusThreshold; } +F32 LLFollowCamParams::getDistance () const { return mDistance; } +F32 LLFollowCamParams::getPitch () const { return mPitch; } +LLVector3 LLFollowCamParams::getFocusOffset () const { return mFocusOffset; } +F32 LLFollowCamParams::getBehindnessAngle () const { return mBehindnessMaxAngle; } +F32 LLFollowCamParams::getBehindnessLag () const { return mBehindnessLag; } +LLVector3 LLFollowCamParams::getPosition () const { return mPosition; } +LLVector3 LLFollowCamParams::getFocus () const { return mFocus; } +bool LLFollowCamParams::getPositionLocked () const { return mPositionLocked; } +bool LLFollowCamParams::getFocusLocked () const { return mFocusLocked; } + +//------------------------------------ +// Constructor +//------------------------------------ +LLFollowCam::LLFollowCam() : LLFollowCamParams() +{ + mUpVector = LLVector3::z_axis; + mSubjectPosition = LLVector3::zero; + mSubjectRotation = LLQuaternion::DEFAULT; + + mZoomedToMinimumDistance = false; + mPitchCos = mPitchSin = 0.f; + mPitchSineAndCosineNeedToBeUpdated = true; + + mSimulatedDistance = mDistance; +} + +void LLFollowCam::copyParams(LLFollowCamParams& params) +{ + setPositionLag(params.getPositionLag()); + setFocusLag(params.getFocusLag()); + setFocusThreshold( params.getFocusThreshold()); + setPositionThreshold(params.getPositionThreshold()); + setPitch(params.getPitch()); + setFocusOffset(params.getFocusOffset()); + setBehindnessAngle(params.getBehindnessAngle()); + setBehindnessLag(params.getBehindnessLag()); + + setPositionLocked(params.getPositionLocked()); + setFocusLocked(params.getFocusLocked()); + + setDistance(params.getDistance()); + if (params.getUsePosition()) + { + setPosition(params.getPosition()); + } + if (params.getUseFocus()) + { + setFocus(params.getFocus()); + } +} + +//--------------------------------------------------------------------------------------------------------- +void LLFollowCam::update() +{ + //#################################################################################### + // update Focus + //#################################################################################### + LLVector3 offsetSubjectPosition = mSubjectPosition + (mFocusOffset * mSubjectRotation); + + LLVector3 simulated_pos_agent = gAgent.getPosAgentFromGlobal(mSimulatedPositionGlobal); + LLVector3 vectorFromCameraToSubject = offsetSubjectPosition - simulated_pos_agent; + F32 distanceFromCameraToSubject = vectorFromCameraToSubject.magVec(); + + LLVector3 whereFocusWantsToBe = mFocus; + LLVector3 focus_pt_agent = gAgent.getPosAgentFromGlobal(mSimulatedFocusGlobal); + if ( mFocusLocked ) // if focus is locked, only relative focus needs to be updated + { + mRelativeFocus = (focus_pt_agent - mSubjectPosition) * ~mSubjectRotation; + } + else + { + LLVector3 focusOffset = offsetSubjectPosition - focus_pt_agent; + F32 focusOffsetDistance = focusOffset.magVec(); + + LLVector3 focusOffsetDirection = focusOffset / focusOffsetDistance; + whereFocusWantsToBe = focus_pt_agent + + (focusOffsetDirection * (focusOffsetDistance - mFocusThreshold)); + if ( focusOffsetDistance > mFocusThreshold ) + { + // this version normalizes focus threshold by distance + // so that the effect is not changed with distance + /* + F32 focusThresholdNormalizedByDistance = distanceFromCameraToSubject * mFocusThreshold; + if ( focusOffsetDistance > focusThresholdNormalizedByDistance ) + { + LLVector3 focusOffsetDirection = focusOffset / focusOffsetDistance; + F32 force = focusOffsetDistance - focusThresholdNormalizedByDistance; + */ + + F32 focusLagLerp = LLSmoothInterpolation::getInterpolant( mFocusLag ); + focus_pt_agent = lerp( focus_pt_agent, whereFocusWantsToBe, focusLagLerp ); + mSimulatedFocusGlobal = gAgent.getPosGlobalFromAgent(focus_pt_agent); + } + mRelativeFocus = lerp(mRelativeFocus, (focus_pt_agent - mSubjectPosition) * ~mSubjectRotation, LLSmoothInterpolation::getInterpolant(0.05f)); + }// if focus is not locked --------------------------------------------- + + + LLVector3 whereCameraPositionWantsToBe = gAgent.getPosAgentFromGlobal(mSimulatedPositionGlobal); + if ( mPositionLocked ) + { + mRelativePos = (whereCameraPositionWantsToBe - mSubjectPosition) * ~mSubjectRotation; + } + else + { + //#################################################################################### + // update Position + //#################################################################################### + //------------------------------------------------------------------------- + // I determine the horizontal vector from the camera to the subject + //------------------------------------------------------------------------- + LLVector3 horizontalVectorFromCameraToSubject = vectorFromCameraToSubject; + horizontalVectorFromCameraToSubject.mV[VZ] = 0.0f; + + //--------------------------------------------------------- + // Now I determine the horizontal distance + //--------------------------------------------------------- + F32 horizontalDistanceFromCameraToSubject = horizontalVectorFromCameraToSubject.magVec(); + + //--------------------------------------------------------- + // Then I get the (normalized) horizontal direction... + //--------------------------------------------------------- + LLVector3 horizontalDirectionFromCameraToSubject; + if ( horizontalDistanceFromCameraToSubject < DISTANCE_EPSILON ) + { + // make sure we still have a normalized vector if distance is really small + // (this case is rare and fleeting) + horizontalDirectionFromCameraToSubject = LLVector3::z_axis; + } + else + { + // I'm not using the "normalize" method, because I can just divide by horizontalDistanceFromCameraToSubject + horizontalDirectionFromCameraToSubject = horizontalVectorFromCameraToSubject / horizontalDistanceFromCameraToSubject; + } + + //------------------------------------------------------------------------------------------------------------ + // Here is where I determine an offset relative to subject position in oder to set the ideal position. + //------------------------------------------------------------------------------------------------------------ + if ( mPitchSineAndCosineNeedToBeUpdated ) + { + calculatePitchSineAndCosine(); + mPitchSineAndCosineNeedToBeUpdated = false; + } + + LLVector3 positionOffsetFromSubject; + positionOffsetFromSubject.setVec + ( + horizontalDirectionFromCameraToSubject.mV[ VX ] * mPitchCos, + horizontalDirectionFromCameraToSubject.mV[ VY ] * mPitchCos, + -mPitchSin + ); + + positionOffsetFromSubject *= mSimulatedDistance; + + //---------------------------------------------------------------------- + // Finally, ideal position is set by taking the subject position and + // extending the positionOffsetFromSubject from that + //---------------------------------------------------------------------- + LLVector3 idealCameraPosition = offsetSubjectPosition - positionOffsetFromSubject; + + //-------------------------------------------------------------------------------- + // Now I prepare to move the current camera position towards its ideal position... + //-------------------------------------------------------------------------------- + LLVector3 vectorFromPositionToIdealPosition = idealCameraPosition - simulated_pos_agent; + F32 distanceFromPositionToIdealPosition = vectorFromPositionToIdealPosition.magVec(); + + //put this inside of the block? + LLVector3 normalFromPositionToIdealPosition = vectorFromPositionToIdealPosition / distanceFromPositionToIdealPosition; + + whereCameraPositionWantsToBe = simulated_pos_agent + + (normalFromPositionToIdealPosition * (distanceFromPositionToIdealPosition - mPositionThreshold)); + //------------------------------------------------------------------------------------------------- + // The following method takes the target camera position and resets it so that it stays "behind" the subject, + // using behindness angle and behindness force as parameters affecting the exact behavior + //------------------------------------------------------------------------------------------------- + if ( distanceFromPositionToIdealPosition > mPositionThreshold ) + { + F32 positionPullLerp = LLSmoothInterpolation::getInterpolant( mPositionLag ); + simulated_pos_agent = lerp( simulated_pos_agent, whereCameraPositionWantsToBe, positionPullLerp ); + } + + //-------------------------------------------------------------------- + // don't let the camera get farther than its official max distance + //-------------------------------------------------------------------- + if ( distanceFromCameraToSubject > mMaxCameraDistantFromSubject ) + { + LLVector3 directionFromCameraToSubject = vectorFromCameraToSubject / distanceFromCameraToSubject; + simulated_pos_agent = offsetSubjectPosition - directionFromCameraToSubject * mMaxCameraDistantFromSubject; + } + + ////------------------------------------------------------------------------------------------------- + //// The following method takes mSimulatedPositionGlobal and resets it so that it stays "behind" the subject, + //// using behindness angle and behindness force as parameters affecting the exact behavior + ////------------------------------------------------------------------------------------------------- + updateBehindnessConstraint(gAgent.getPosAgentFromGlobal(mSimulatedFocusGlobal), simulated_pos_agent); + mSimulatedPositionGlobal = gAgent.getPosGlobalFromAgent(simulated_pos_agent); + + mRelativePos = lerp(mRelativePos, (simulated_pos_agent - mSubjectPosition) * ~mSubjectRotation, LLSmoothInterpolation::getInterpolant(0.05f)); + } // if position is not locked ----------------------------------------------------------- + + + //#################################################################################### + // update UpVector + //#################################################################################### + // this just points upward for now, but I anticipate future effects requiring + // some rolling ("banking" effects for fun, swoopy vehicles, etc.) + mUpVector = LLVector3::z_axis; +} + + + +//------------------------------------------------------------------------------------- +bool LLFollowCam::updateBehindnessConstraint(LLVector3 focus, LLVector3& cam_position) +{ + bool constraint_active = false; + // only apply this stuff if the behindness angle is something other than opened up all the way + if ( mBehindnessMaxAngle < FOLLOW_CAM_MAX_BEHINDNESS_ANGLE - FOLLOW_CAM_BEHINDNESS_EPSILON ) + { + //-------------------------------------------------------------- + // horizontalized vector from focus to camera + //-------------------------------------------------------------- + LLVector3 horizontalVectorFromFocusToCamera; + horizontalVectorFromFocusToCamera.setVec(cam_position - focus); + horizontalVectorFromFocusToCamera.mV[ VZ ] = 0.0f; + F32 cameraZ = cam_position.mV[ VZ ]; + + //-------------------------------------------------------------- + // distance of horizontalized vector + //-------------------------------------------------------------- + F32 horizontalDistance = horizontalVectorFromFocusToCamera.magVec(); + + //-------------------------------------------------------------------------------------------------- + // calculate horizontalized back vector of the subject and scale by horizontalDistance + //-------------------------------------------------------------------------------------------------- + LLVector3 horizontalSubjectBack( -1.0f, 0.0f, 0.0f ); + horizontalSubjectBack *= mSubjectRotation; + horizontalSubjectBack.mV[ VZ ] = 0.0f; + horizontalSubjectBack.normVec(); // because horizontalizing might make it shorter than 1 + horizontalSubjectBack *= horizontalDistance; + + //-------------------------------------------------------------------------------------------------- + // find the angle (in degrees) between these vectors + //-------------------------------------------------------------------------------------------------- + F32 cameraOffsetAngle = 0.f; + LLVector3 cameraOffsetRotationAxis; + LLQuaternion camera_offset_rotation; + camera_offset_rotation.shortestArc(horizontalSubjectBack, horizontalVectorFromFocusToCamera); + camera_offset_rotation.getAngleAxis(&cameraOffsetAngle, cameraOffsetRotationAxis); + cameraOffsetAngle *= RAD_TO_DEG; + + if ( cameraOffsetAngle > mBehindnessMaxAngle ) + { + F32 fraction = ((cameraOffsetAngle - mBehindnessMaxAngle) / cameraOffsetAngle) * LLSmoothInterpolation::getInterpolant(mBehindnessLag); + cam_position = focus + horizontalSubjectBack * (slerp(fraction, camera_offset_rotation, LLQuaternion::DEFAULT)); + cam_position.mV[VZ] = cameraZ; // clamp z value back to what it was before we started messing with it + constraint_active = true; + } + } + return constraint_active; +} + + +//--------------------------------------------------------- +void LLFollowCam::calculatePitchSineAndCosine() +{ + F32 radian = mPitch * DEG_TO_RAD; + mPitchCos = cos( radian ); + mPitchSin = sin( radian ); +} + +//--------------------------------------------------------- +void LLFollowCam::setSubjectPositionAndRotation( const LLVector3 p, const LLQuaternion r ) +{ + mSubjectPosition = p; + mSubjectRotation = r; +} + + +//--------------------------------------------------------- +void LLFollowCam::zoom( S32 z ) +{ + F32 zoomAmount = z * mSimulatedDistance * FOLLOW_CAM_ZOOM_FACTOR; + + if (( zoomAmount < FOLLOW_CAM_MIN_ZOOM_AMOUNT ) + && ( zoomAmount > -FOLLOW_CAM_MIN_ZOOM_AMOUNT )) + { + if ( zoomAmount < 0.0f ) + { + zoomAmount = -FOLLOW_CAM_MIN_ZOOM_AMOUNT; + } + else + { + zoomAmount = FOLLOW_CAM_MIN_ZOOM_AMOUNT; + } + } + + mSimulatedDistance += zoomAmount; + + mZoomedToMinimumDistance = false; + if ( mSimulatedDistance < FOLLOW_CAM_MIN_DISTANCE ) + { + mSimulatedDistance = FOLLOW_CAM_MIN_DISTANCE; + + // if zoomAmount is negative (i.e., getting closer), then + // this signifies having hit the minimum: + if ( zoomAmount < 0.0f ) + { + mZoomedToMinimumDistance = true; + } + } + else if ( mSimulatedDistance > mMaxCameraDistantFromSubject ) + { + mSimulatedDistance = mMaxCameraDistantFromSubject; + } +} + + +//--------------------------------------------------------- +bool LLFollowCam::isZoomedToMinimumDistance() +{ + return mZoomedToMinimumDistance; +} + + +//--------------------------------------------------------- +void LLFollowCam::reset( const LLVector3 p, const LLVector3 f , const LLVector3 u ) +{ + setPosition(p); + setFocus(f); + mUpVector = u; +} + +//--------------------------------------------------------- +void LLFollowCam::setMaxCameraDistantFromSubject( F32 m ) +{ + mMaxCameraDistantFromSubject = m; +} + + +void LLFollowCam::setPitch( F32 p ) +{ + LLFollowCamParams::setPitch(p); + mPitchSineAndCosineNeedToBeUpdated = true; // important +} + +void LLFollowCam::setDistance( F32 d ) +{ + if (d != mDistance) + { + LLFollowCamParams::setDistance(d); + mSimulatedDistance = d; + mZoomedToMinimumDistance = false; + } +} + +void LLFollowCam::setPosition( const LLVector3& p ) +{ + if (p != mPosition) + { + LLFollowCamParams::setPosition(p); + mSimulatedPositionGlobal = gAgent.getPosGlobalFromAgent(mPosition); + if (mPositionLocked) + { + mRelativePos = (mPosition - mSubjectPosition) * ~mSubjectRotation; + } + } +} + +void LLFollowCam::setFocus( const LLVector3& f ) +{ + if (f != mFocus) + { + LLFollowCamParams::setFocus(f); + mSimulatedFocusGlobal = gAgent.getPosGlobalFromAgent(f); + if (mFocusLocked) + { + mRelativeFocus = (mFocus - mSubjectPosition) * ~mSubjectRotation; + } + } +} + +void LLFollowCam::setPositionLocked( bool locked ) +{ + LLFollowCamParams::setPositionLocked(locked); + if (locked) + { + // propagate set position to relative position + mRelativePos = (gAgent.getPosAgentFromGlobal(mSimulatedPositionGlobal) - mSubjectPosition) * ~mSubjectRotation; + } +} + +void LLFollowCam::setFocusLocked( bool locked ) +{ + LLFollowCamParams::setFocusLocked(locked); + if (locked) + { + // propagate set position to relative position + mRelativeFocus = (gAgent.getPosAgentFromGlobal(mSimulatedFocusGlobal) - mSubjectPosition) * ~mSubjectRotation; + } +} + + +LLVector3 LLFollowCam::getSimulatedPosition() const +{ + // return simulated position + return mSubjectPosition + (mRelativePos * mSubjectRotation); +} + +LLVector3 LLFollowCam::getSimulatedFocus() const +{ + // return simulated focus point + return mSubjectPosition + (mRelativeFocus * mSubjectRotation); +} + +LLVector3 LLFollowCam::getUpVector() +{ + return mUpVector; +} + + +//------------------------------------ +// Destructor +//------------------------------------ +LLFollowCam::~LLFollowCam() +{ +} + + +//------------------------------------------------------- +// LLFollowCamMgr +//------------------------------------------------------- +LLFollowCamMgr::LLFollowCamMgr() +{ +} + +LLFollowCamMgr::~LLFollowCamMgr() +{ + for (param_map_t::iterator iter = mParamMap.begin(); iter != mParamMap.end(); ++iter) + { + LLFollowCamParams* params = iter->second; + delete params; + } + mParamMap.clear(); +} + +void LLFollowCamMgr::setPositionLag( const LLUUID& source, F32 lag) +{ + LLFollowCamParams* paramsp = getParamsForID(source); + if (paramsp) + { + paramsp->setPositionLag(lag); + } +} + +void LLFollowCamMgr::setFocusLag( const LLUUID& source, F32 lag) +{ + LLFollowCamParams* paramsp = getParamsForID(source); + if (paramsp) + { + paramsp->setFocusLag(lag); + } +} + +void LLFollowCamMgr::setFocusThreshold( const LLUUID& source, F32 threshold) +{ + LLFollowCamParams* paramsp = getParamsForID(source); + if (paramsp) + { + paramsp->setFocusThreshold(threshold); + } + +} + +void LLFollowCamMgr::setPositionThreshold( const LLUUID& source, F32 threshold) +{ + LLFollowCamParams* paramsp = getParamsForID(source); + if (paramsp) + { + paramsp->setPositionThreshold(threshold); + } +} + +void LLFollowCamMgr::setDistance( const LLUUID& source, F32 distance) +{ + LLFollowCamParams* paramsp = getParamsForID(source); + if (paramsp) + { + paramsp->setDistance(distance); + } +} + +void LLFollowCamMgr::setPitch( const LLUUID& source, F32 pitch) +{ + LLFollowCamParams* paramsp = getParamsForID(source); + if (paramsp) + { + paramsp->setPitch(pitch); + } +} + +void LLFollowCamMgr::setFocusOffset( const LLUUID& source, const LLVector3& offset) +{ + LLFollowCamParams* paramsp = getParamsForID(source); + if (paramsp) + { + paramsp->setFocusOffset(offset); + } +} + +void LLFollowCamMgr::setBehindnessAngle( const LLUUID& source, F32 angle) +{ + LLFollowCamParams* paramsp = getParamsForID(source); + if (paramsp) + { + paramsp->setBehindnessAngle(angle); + } +} + +void LLFollowCamMgr::setBehindnessLag( const LLUUID& source, F32 force) +{ + LLFollowCamParams* paramsp = getParamsForID(source); + if (paramsp) + { + paramsp->setBehindnessLag(force); + } +} + +void LLFollowCamMgr::setPosition( const LLUUID& source, const LLVector3 position) +{ + LLFollowCamParams* paramsp = getParamsForID(source); + if (paramsp) + { + paramsp->setPosition(position); + } +} + +void LLFollowCamMgr::setFocus( const LLUUID& source, const LLVector3 focus) +{ + LLFollowCamParams* paramsp = getParamsForID(source); + if (paramsp) + { + paramsp->setFocus(focus); + } +} + +void LLFollowCamMgr::setPositionLocked( const LLUUID& source, bool locked) +{ + LLFollowCamParams* paramsp = getParamsForID(source); + if (paramsp) + { + paramsp->setPositionLocked(locked); + } +} + +void LLFollowCamMgr::setFocusLocked( const LLUUID& source, bool locked ) +{ + LLFollowCamParams* paramsp = getParamsForID(source); + if (paramsp) + { + paramsp->setFocusLocked(locked); + } +} + +LLFollowCamParams* LLFollowCamMgr::getParamsForID(const LLUUID& source) +{ + LLFollowCamParams* params = NULL; + + param_map_t::iterator found_it = mParamMap.find(source); + if (found_it == mParamMap.end()) // didn't find it? + { + params = new LLFollowCamParams(); + mParamMap[source] = params; + } + else + { + params = found_it->second; + } + + return params; +} + +LLFollowCamParams* LLFollowCamMgr::getActiveFollowCamParams() +{ + if (mParamStack.empty()) + { + return NULL; + } + + return mParamStack.back(); +} + +void LLFollowCamMgr::setCameraActive( const LLUUID& source, bool active ) +{ + LLFollowCamParams* params = getParamsForID(source); + param_stack_t::iterator found_it = std::find(mParamStack.begin(), mParamStack.end(), params); + if (found_it != mParamStack.end()) + { + mParamStack.erase(found_it); + } + // put on top of stack + if(active) + { + mParamStack.push_back(params); + } +} + +void LLFollowCamMgr::removeFollowCamParams(const LLUUID& source) +{ + setCameraActive(source, false); + LLFollowCamParams* params = getParamsForID(source); + mParamMap.erase(source); + delete params; +} + +bool LLFollowCamMgr::isScriptedCameraSource(const LLUUID& source) +{ + param_map_t::iterator found_it = mParamMap.find(source); + return (found_it != mParamMap.end()); +} + +void LLFollowCamMgr::dump() +{ + S32 param_count = 0; + LL_INFOS() << "Scripted camera active stack" << LL_ENDL; + for (param_stack_t::iterator param_it = mParamStack.begin(); + param_it != mParamStack.end(); + ++param_it) + { + LL_INFOS() << param_count++ << + " rot_limit: " << (*param_it)->getBehindnessAngle() << + " rot_lag: " << (*param_it)->getBehindnessLag() << + " distance: " << (*param_it)->getDistance() << + " focus: " << (*param_it)->getFocus() << + " foc_lag: " << (*param_it)->getFocusLag() << + " foc_lock: " << ((*param_it)->getFocusLocked() ? "Y" : "N") << + " foc_offset: " << (*param_it)->getFocusOffset() << + " foc_thresh: " << (*param_it)->getFocusThreshold() << + " pitch: " << (*param_it)->getPitch() << + " pos: " << (*param_it)->getPosition() << + " pos_lag: " << (*param_it)->getPositionLag() << + " pos_lock: " << ((*param_it)->getPositionLocked() ? "Y" : "N") << + " pos_thresh: " << (*param_it)->getPositionThreshold() << LL_ENDL; + } +} + diff --git a/indra/newview/llfollowcam.h b/indra/newview/llfollowcam.h index 0a7a202305..20f3d45cbb 100644 --- a/indra/newview/llfollowcam.h +++ b/indra/newview/llfollowcam.h @@ -1,232 +1,232 @@ -/** - * @file llfollowcam.h - * @author Jeffrey Ventrella - * @brief LLFollowCam class definition - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -//-------------------------------------------------------------------- -// FollowCam -// -// The FollowCam controls three dynamic variables which determine -// a camera orientation and position for a "loose" third-person view -// (orientation being derived from a combination of focus and up -// vector). It is good for fast-moving vehicles that change -// acceleration a lot, but it can also be general-purpose, like for -// avatar navigation. It has a handful of parameters allowing it to -// be tweaked to assume different styles of tracking objects. -// -//-------------------------------------------------------------------- -#ifndef LL_FOLLOWCAM_H -#define LL_FOLLOWCAM_H - -#include "llcoordframe.h" -#include "indra_constants.h" -#include "llmath.h" -#include "lltimer.h" -#include "llquaternion.h" -#include "llcriticaldamp.h" -#include -#include - -class LLFollowCamParams -{ -public: - LLFollowCamParams(); - virtual ~LLFollowCamParams(); - - //-------------------------------------- - // setty setty set set - //-------------------------------------- - virtual void setPositionLag ( F32 ); - virtual void setFocusLag ( F32 ); - virtual void setFocusThreshold ( F32 ); - virtual void setPositionThreshold ( F32 ); - virtual void setDistance ( F32 ); - virtual void setPitch ( F32 ); - virtual void setFocusOffset ( const LLVector3& ); - virtual void setBehindnessAngle ( F32 ); - virtual void setBehindnessLag ( F32 ); - virtual void setPosition ( const LLVector3& ); - virtual void setFocus ( const LLVector3& ); - virtual void setPositionLocked ( bool ); - virtual void setFocusLocked ( bool ); - - - //-------------------------------------- - // getty getty get get - //-------------------------------------- - virtual F32 getPositionLag() const; - virtual F32 getFocusLag() const; - virtual F32 getPositionThreshold() const; - virtual F32 getFocusThreshold() const; - virtual F32 getDistance() const; - virtual F32 getPitch() const; - virtual LLVector3 getFocusOffset() const; - virtual F32 getBehindnessAngle() const; - virtual F32 getBehindnessLag() const; - virtual LLVector3 getPosition() const; - virtual LLVector3 getFocus() const; - virtual bool getFocusLocked() const; - virtual bool getPositionLocked() const; - virtual bool getUseFocus() const { return mUseFocus; } - virtual bool getUsePosition() const { return mUsePosition; } - -protected: - F32 mPositionLag; - F32 mFocusLag; - F32 mFocusThreshold; - F32 mPositionThreshold; - F32 mDistance; - F32 mPitch; - LLVector3 mFocusOffset; - F32 mBehindnessMaxAngle; - F32 mBehindnessLag; - F32 mMaxCameraDistantFromSubject; - - bool mPositionLocked; - bool mFocusLocked; - bool mUsePosition; // specific camera point specified by script - bool mUseFocus; // specific focus point specified by script - LLVector3 mPosition; // where the camera is (in world-space) - LLVector3 mFocus; // what the camera is aimed at (in world-space) -}; - -class LLFollowCam : public LLFollowCamParams -{ -public: - //-------------------- - // Contructor - //-------------------- - LLFollowCam(); - - //-------------------- - // Destructor - //-------------------- - virtual ~LLFollowCam(); - - //--------------------------------------------------------------------------------------- - // The following methods must be called every time step. However, if you know for - // sure that your subject matter (what the camera is looking at) is not moving, - // then you can get away with not calling "update" But keep in mind that "update" - // may still be needed after the subject matter has stopped moving because the - // camera may still need to animate itself catching up to its ideal resting place. - //--------------------------------------------------------------------------------------- - void setSubjectPositionAndRotation ( const LLVector3 p, const LLQuaternion r ); - void update(); - - // initialize from another instance of llfollowcamparams - void copyParams(LLFollowCamParams& params); - - //----------------------------------------------------------------------------------- - // this is how to bang the followCam into a specific configuration. Keep in mind - // that it will immediately try to adjust these values according to its attributes. - //----------------------------------------------------------------------------------- - void reset( const LLVector3 position, const LLVector3 focus, const LLVector3 upVector ); - - void setMaxCameraDistantFromSubject ( F32 m ); // this should be determined by llAgent - bool isZoomedToMinimumDistance(); - LLVector3 getUpVector(); - void zoom( S32 ); - - // overrides for setters and getters - virtual void setPitch( F32 ); - virtual void setDistance( F32 ); - virtual void setPosition(const LLVector3& pos); - virtual void setFocus(const LLVector3& focus); - virtual void setPositionLocked ( bool ); - virtual void setFocusLocked ( bool ); - - LLVector3 getSimulatedPosition() const; - LLVector3 getSimulatedFocus() const; - - //------------------------------------------ - // protected members of FollowCam - //------------------------------------------ -protected: - F32 mPitchCos; // derived from mPitch - F32 mPitchSin; // derived from mPitch - LLGlobalVec mSimulatedPositionGlobal; // where the camera is (global coordinates), simulated - LLGlobalVec mSimulatedFocusGlobal; // what the camera is aimed at (global coordinates), simulated - F32 mSimulatedDistance; - - //--------------------- - // dynamic variables - //--------------------- - bool mZoomedToMinimumDistance; - LLFrameTimer mTimer; - LLVector3 mSubjectPosition; // this is the position of what I'm looking at - LLQuaternion mSubjectRotation; // this is the rotation of what I'm looking at - LLVector3 mUpVector; // the camera's up vector in world-space (determines roll) - LLVector3 mRelativeFocus; - LLVector3 mRelativePos; - - bool mPitchSineAndCosineNeedToBeUpdated; - - //------------------------------------------ - // protected methods of FollowCam - //------------------------------------------ -protected: - void calculatePitchSineAndCosine(); - bool updateBehindnessConstraint(LLVector3 focus, LLVector3& cam_position); - -};// end of FollowCam class - - -class LLFollowCamMgr : public LLSingleton -{ - LLSINGLETON(LLFollowCamMgr); - ~LLFollowCamMgr(); -public: - void setPositionLag ( const LLUUID& source, F32 lag); - void setFocusLag ( const LLUUID& source, F32 lag); - void setFocusThreshold ( const LLUUID& source, F32 threshold); - void setPositionThreshold ( const LLUUID& source, F32 threshold); - void setDistance ( const LLUUID& source, F32 distance); - void setPitch ( const LLUUID& source, F32 pitch); - void setFocusOffset ( const LLUUID& source, const LLVector3& offset); - void setBehindnessAngle ( const LLUUID& source, F32 angle); - void setBehindnessLag ( const LLUUID& source, F32 lag); - void setPosition ( const LLUUID& source, const LLVector3 position); - void setFocus ( const LLUUID& source, const LLVector3 focus); - void setPositionLocked ( const LLUUID& source, bool locked); - void setFocusLocked ( const LLUUID& source, bool locked ); - - void setCameraActive ( const LLUUID& source, bool active ); - - LLFollowCamParams* getActiveFollowCamParams(); - LLFollowCamParams* getParamsForID(const LLUUID& source); - void removeFollowCamParams(const LLUUID& source); - bool isScriptedCameraSource(const LLUUID& source); - void dump(); - -protected: - - typedef std::map param_map_t; - param_map_t mParamMap; - - typedef std::vector param_stack_t; - param_stack_t mParamStack; -}; - -#endif //LL_FOLLOWCAM_H +/** + * @file llfollowcam.h + * @author Jeffrey Ventrella + * @brief LLFollowCam class definition + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +//-------------------------------------------------------------------- +// FollowCam +// +// The FollowCam controls three dynamic variables which determine +// a camera orientation and position for a "loose" third-person view +// (orientation being derived from a combination of focus and up +// vector). It is good for fast-moving vehicles that change +// acceleration a lot, but it can also be general-purpose, like for +// avatar navigation. It has a handful of parameters allowing it to +// be tweaked to assume different styles of tracking objects. +// +//-------------------------------------------------------------------- +#ifndef LL_FOLLOWCAM_H +#define LL_FOLLOWCAM_H + +#include "llcoordframe.h" +#include "indra_constants.h" +#include "llmath.h" +#include "lltimer.h" +#include "llquaternion.h" +#include "llcriticaldamp.h" +#include +#include + +class LLFollowCamParams +{ +public: + LLFollowCamParams(); + virtual ~LLFollowCamParams(); + + //-------------------------------------- + // setty setty set set + //-------------------------------------- + virtual void setPositionLag ( F32 ); + virtual void setFocusLag ( F32 ); + virtual void setFocusThreshold ( F32 ); + virtual void setPositionThreshold ( F32 ); + virtual void setDistance ( F32 ); + virtual void setPitch ( F32 ); + virtual void setFocusOffset ( const LLVector3& ); + virtual void setBehindnessAngle ( F32 ); + virtual void setBehindnessLag ( F32 ); + virtual void setPosition ( const LLVector3& ); + virtual void setFocus ( const LLVector3& ); + virtual void setPositionLocked ( bool ); + virtual void setFocusLocked ( bool ); + + + //-------------------------------------- + // getty getty get get + //-------------------------------------- + virtual F32 getPositionLag() const; + virtual F32 getFocusLag() const; + virtual F32 getPositionThreshold() const; + virtual F32 getFocusThreshold() const; + virtual F32 getDistance() const; + virtual F32 getPitch() const; + virtual LLVector3 getFocusOffset() const; + virtual F32 getBehindnessAngle() const; + virtual F32 getBehindnessLag() const; + virtual LLVector3 getPosition() const; + virtual LLVector3 getFocus() const; + virtual bool getFocusLocked() const; + virtual bool getPositionLocked() const; + virtual bool getUseFocus() const { return mUseFocus; } + virtual bool getUsePosition() const { return mUsePosition; } + +protected: + F32 mPositionLag; + F32 mFocusLag; + F32 mFocusThreshold; + F32 mPositionThreshold; + F32 mDistance; + F32 mPitch; + LLVector3 mFocusOffset; + F32 mBehindnessMaxAngle; + F32 mBehindnessLag; + F32 mMaxCameraDistantFromSubject; + + bool mPositionLocked; + bool mFocusLocked; + bool mUsePosition; // specific camera point specified by script + bool mUseFocus; // specific focus point specified by script + LLVector3 mPosition; // where the camera is (in world-space) + LLVector3 mFocus; // what the camera is aimed at (in world-space) +}; + +class LLFollowCam : public LLFollowCamParams +{ +public: + //-------------------- + // Contructor + //-------------------- + LLFollowCam(); + + //-------------------- + // Destructor + //-------------------- + virtual ~LLFollowCam(); + + //--------------------------------------------------------------------------------------- + // The following methods must be called every time step. However, if you know for + // sure that your subject matter (what the camera is looking at) is not moving, + // then you can get away with not calling "update" But keep in mind that "update" + // may still be needed after the subject matter has stopped moving because the + // camera may still need to animate itself catching up to its ideal resting place. + //--------------------------------------------------------------------------------------- + void setSubjectPositionAndRotation ( const LLVector3 p, const LLQuaternion r ); + void update(); + + // initialize from another instance of llfollowcamparams + void copyParams(LLFollowCamParams& params); + + //----------------------------------------------------------------------------------- + // this is how to bang the followCam into a specific configuration. Keep in mind + // that it will immediately try to adjust these values according to its attributes. + //----------------------------------------------------------------------------------- + void reset( const LLVector3 position, const LLVector3 focus, const LLVector3 upVector ); + + void setMaxCameraDistantFromSubject ( F32 m ); // this should be determined by llAgent + bool isZoomedToMinimumDistance(); + LLVector3 getUpVector(); + void zoom( S32 ); + + // overrides for setters and getters + virtual void setPitch( F32 ); + virtual void setDistance( F32 ); + virtual void setPosition(const LLVector3& pos); + virtual void setFocus(const LLVector3& focus); + virtual void setPositionLocked ( bool ); + virtual void setFocusLocked ( bool ); + + LLVector3 getSimulatedPosition() const; + LLVector3 getSimulatedFocus() const; + + //------------------------------------------ + // protected members of FollowCam + //------------------------------------------ +protected: + F32 mPitchCos; // derived from mPitch + F32 mPitchSin; // derived from mPitch + LLGlobalVec mSimulatedPositionGlobal; // where the camera is (global coordinates), simulated + LLGlobalVec mSimulatedFocusGlobal; // what the camera is aimed at (global coordinates), simulated + F32 mSimulatedDistance; + + //--------------------- + // dynamic variables + //--------------------- + bool mZoomedToMinimumDistance; + LLFrameTimer mTimer; + LLVector3 mSubjectPosition; // this is the position of what I'm looking at + LLQuaternion mSubjectRotation; // this is the rotation of what I'm looking at + LLVector3 mUpVector; // the camera's up vector in world-space (determines roll) + LLVector3 mRelativeFocus; + LLVector3 mRelativePos; + + bool mPitchSineAndCosineNeedToBeUpdated; + + //------------------------------------------ + // protected methods of FollowCam + //------------------------------------------ +protected: + void calculatePitchSineAndCosine(); + bool updateBehindnessConstraint(LLVector3 focus, LLVector3& cam_position); + +};// end of FollowCam class + + +class LLFollowCamMgr : public LLSingleton +{ + LLSINGLETON(LLFollowCamMgr); + ~LLFollowCamMgr(); +public: + void setPositionLag ( const LLUUID& source, F32 lag); + void setFocusLag ( const LLUUID& source, F32 lag); + void setFocusThreshold ( const LLUUID& source, F32 threshold); + void setPositionThreshold ( const LLUUID& source, F32 threshold); + void setDistance ( const LLUUID& source, F32 distance); + void setPitch ( const LLUUID& source, F32 pitch); + void setFocusOffset ( const LLUUID& source, const LLVector3& offset); + void setBehindnessAngle ( const LLUUID& source, F32 angle); + void setBehindnessLag ( const LLUUID& source, F32 lag); + void setPosition ( const LLUUID& source, const LLVector3 position); + void setFocus ( const LLUUID& source, const LLVector3 focus); + void setPositionLocked ( const LLUUID& source, bool locked); + void setFocusLocked ( const LLUUID& source, bool locked ); + + void setCameraActive ( const LLUUID& source, bool active ); + + LLFollowCamParams* getActiveFollowCamParams(); + LLFollowCamParams* getParamsForID(const LLUUID& source); + void removeFollowCamParams(const LLUUID& source); + bool isScriptedCameraSource(const LLUUID& source); + void dump(); + +protected: + + typedef std::map param_map_t; + param_map_t mParamMap; + + typedef std::vector param_stack_t; + param_stack_t mParamStack; +}; + +#endif //LL_FOLLOWCAM_H diff --git a/indra/newview/llfriendcard.cpp b/indra/newview/llfriendcard.cpp index 2ecf7c4eb1..b826b12bd3 100644 --- a/indra/newview/llfriendcard.cpp +++ b/indra/newview/llfriendcard.cpp @@ -1,682 +1,682 @@ -/** - * @file llfriendcard.cpp - * @brief Implementation of classes to process Friends Cards - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfriendcard.h" - -#include "llagent.h" -#include "llavatarnamecache.h" -#include "llinventory.h" -#include "llinventoryfunctions.h" -#include "llinventoryobserver.h" -#include "lltrans.h" - -#include "llcallingcard.h" // for LLAvatarTracker -#include "llviewerinventory.h" -#include "llinventorymodel.h" -#include "llcallbacklist.h" - -// Constants; - -static const std::string INVENTORY_STRING_FRIENDS_SUBFOLDER = "Friends"; -static const std::string INVENTORY_STRING_FRIENDS_ALL_SUBFOLDER = "All"; - -// helper functions - -// NOTE: For now Friends & All folders are created as protected folders of the LLFolderType::FT_CALLINGCARD type. -// So, their names will be processed in the LLFolderViewItem::refresh() to be localized -// using "InvFolder LABEL_NAME" as LLTrans::findString argument. - -// We must use in this file their hard-coded names to ensure found them on different locales. EXT-5829. -// These hard-coded names will be stored in InventoryItems but shown localized in FolderViewItems - -// If hack in the LLFolderViewItem::refresh() to localize protected folder is removed -// or these folders are not protected these names should be localized in another place/way. -inline const std::string get_friend_folder_name() -{ - return INVENTORY_STRING_FRIENDS_SUBFOLDER; -} - -inline const std::string get_friend_all_subfolder_name() -{ - return INVENTORY_STRING_FRIENDS_ALL_SUBFOLDER; -} - -void move_from_to_arrays(LLInventoryModel::cat_array_t& from, LLInventoryModel::cat_array_t& to) -{ - while (from.size() > 0) - { - to.push_back(from.at(0)); - from.erase(from.begin()); - } -} - -const LLUUID& get_folder_uuid(const LLUUID& parentFolderUUID, LLInventoryCollectFunctor& matchFunctor) -{ - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - - gInventory.collectDescendentsIf(parentFolderUUID, cats, items, - LLInventoryModel::EXCLUDE_TRASH, matchFunctor); - - S32 cats_count = cats.size(); - - if (cats_count > 1) - { - LL_WARNS_ONCE("LLFriendCardsManager") - << "There is more than one Friend card folder." - << "The first folder will be used." - << LL_ENDL; - } - - return (cats_count >= 1) ? cats.at(0)->getUUID() : LLUUID::null; -} - -/** - * Class LLFindAgentCallingCard - * - * An inventory collector functor for checking that agent's own calling card - * exists within the Calling Cards category and its sub-folders. - */ -class LLFindAgentCallingCard : public LLInventoryCollectFunctor -{ -public: - LLFindAgentCallingCard() : mIsAgentCallingCardFound(false) {} - virtual ~LLFindAgentCallingCard() {} - virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); - bool isAgentCallingCardFound() { return mIsAgentCallingCardFound; } - -private: - bool mIsAgentCallingCardFound; -}; - -bool LLFindAgentCallingCard::operator()(LLInventoryCategory* cat, LLInventoryItem* item) -{ - if (mIsAgentCallingCardFound) return true; - - if (item && item->getType() == LLAssetType::AT_CALLINGCARD && item->getCreatorUUID() == gAgentID) - { - mIsAgentCallingCardFound = true; - } - - return mIsAgentCallingCardFound; -} - -/** - * Class for fetching initial friend cards data - * - * Implemented to fix an issue when Inventory folders are in incomplete state. - * See EXT-2320, EXT-2061, EXT-1935, EXT-813. - * Uses a callback to sync Inventory Friends/All folder with agent's Friends List. - */ -class LLInitialFriendCardsFetch : public LLInventoryFetchDescendentsObserver -{ -public: - typedef boost::function callback_t; - - LLInitialFriendCardsFetch(const LLUUID& folder_id, - callback_t cb) : - LLInventoryFetchDescendentsObserver(folder_id), - mCheckFolderCallback(cb) - {} - - /* virtual */ void done(); - -private: - callback_t mCheckFolderCallback; -}; - -void LLInitialFriendCardsFetch::done() -{ - // This observer is no longer needed. - gInventory.removeObserver(this); - - doOnIdleOneTime(mCheckFolderCallback); - - delete this; -} - -// LLFriendCardsManager Constructor / Destructor -LLFriendCardsManager::LLFriendCardsManager() -: mState(INIT) -{ - LLAvatarTracker::instance().addObserver(this); -} - -LLFriendCardsManager::~LLFriendCardsManager() -{ - LLAvatarTracker::instance().removeObserver(this); -} - -void LLFriendCardsManager::putAvatarData(const LLUUID& avatarID) -{ - LL_INFOS() << "Store avatar data, avatarID: " << avatarID << LL_ENDL; - std::pair< avatar_uuid_set_t::iterator, bool > pr; - pr = mBuddyIDSet.insert(avatarID); - if (!pr.second) - { - LL_WARNS() << "Trying to add avatar UUID for the stored avatar: " - << avatarID - << LL_ENDL; - } -} - -const LLUUID LLFriendCardsManager::extractAvatarID(const LLUUID& avatarID) -{ - LLUUID rv; - avatar_uuid_set_t::iterator it = mBuddyIDSet.find(avatarID); - if (mBuddyIDSet.end() == it) - { - LL_WARNS() << "Call method for non-existent avatar name in the map: " << avatarID << LL_ENDL; - } - else - { - rv = (*it); - mBuddyIDSet.erase(it); - } - return rv; -} - -bool LLFriendCardsManager::isItemInAnyFriendsList(const LLViewerInventoryItem* item) -{ - if (item->getType() != LLAssetType::AT_CALLINGCARD) - return false; - - LLInventoryModel::item_array_t items; - findMatchedFriendCards(item->getCreatorUUID(), items); - - return items.size() > 0; -} - - -bool LLFriendCardsManager::isObjDirectDescendentOfCategory(const LLInventoryObject* obj, - const LLViewerInventoryCategory* cat) const -{ - // we need both params to proceed. - if ( !obj || !cat ) - return false; - - // Need to check that target category is in the Calling Card/Friends folder. - // In other case function returns unpredictable result. - if ( !isCategoryInFriendFolder(cat) ) - return false; - - bool result = false; - - LLInventoryModel::item_array_t* items; - LLInventoryModel::cat_array_t* cats; - - gInventory.lockDirectDescendentArrays(cat->getUUID(), cats, items); - if ( items ) - { - if ( obj->getType() == LLAssetType::AT_CALLINGCARD ) - { - // For CALLINGCARD compare items by creator's id, if they are equal assume - // that it is same card and return true. Note: UUID's of compared items - // may be not equal. Also, we already know that obj should be type of LLInventoryItem, - // but in case inventory database is broken check what dynamic_cast returns. - const LLInventoryItem* item = dynamic_cast < const LLInventoryItem* > (obj); - if ( item ) - { - LLUUID creator_id = item->getCreatorUUID(); - LLViewerInventoryItem* cur_item = NULL; - for ( S32 i = items->size() - 1; i >= 0; --i ) - { - cur_item = items->at(i); - if ( creator_id == cur_item->getCreatorUUID() ) - { - result = true; - break; - } - } - } - } - else - { - // Else check that items have same type and name. - // Note: UUID's of compared items also may be not equal. - std::string obj_name = obj->getName(); - LLViewerInventoryItem* cur_item = NULL; - for ( S32 i = items->size() - 1; i >= 0; --i ) - { - cur_item = items->at(i); - if ( obj->getType() != cur_item->getType() ) - continue; - if ( obj_name == cur_item->getName() ) - { - result = true; - break; - } - } - } - } - if ( !result && cats ) - { - // There is no direct descendent in items, so check categories. - // If target obj and descendent category have same type and name - // then return true. Note: UUID's of compared items also may be not equal. - std::string obj_name = obj->getName(); - LLViewerInventoryCategory* cur_cat = NULL; - for ( S32 i = cats->size() - 1; i >= 0; --i ) - { - cur_cat = cats->at(i); - if ( obj->getType() != cur_cat->getType() ) - continue; - if ( obj_name == cur_cat->getName() ) - { - result = true; - break; - } - } - } - gInventory.unlockDirectDescendentArrays(cat->getUUID()); - - return result; -} - - -bool LLFriendCardsManager::isCategoryInFriendFolder(const LLViewerInventoryCategory* cat) const -{ - if (NULL == cat) - return false; - return true == gInventory.isObjectDescendentOf(cat->getUUID(), findFriendFolderUUIDImpl()); -} - -bool LLFriendCardsManager::isAnyFriendCategory(const LLUUID& catID) const -{ - const LLUUID& friendFolderID = findFriendFolderUUIDImpl(); - if (catID == friendFolderID) - return true; - - return true == gInventory.isObjectDescendentOf(catID, friendFolderID); -} - -void LLFriendCardsManager::syncFriendCardsFolders() -{ - const LLUUID callingCardsFolderID = gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD); - - fetchAndCheckFolderDescendents(callingCardsFolderID, - boost::bind(&LLFriendCardsManager::ensureFriendsFolderExists, this)); -} - - -/************************************************************************/ -/* Private Methods */ -/************************************************************************/ -const LLUUID& LLFriendCardsManager::findFirstCallingCardSubfolder(const LLUUID &parent_id) const -{ - if (parent_id.isNull()) - { - return LLUUID::null; - } - - LLInventoryModel::cat_array_t* cats; - LLInventoryModel::item_array_t* items; - gInventory.getDirectDescendentsOf(parent_id, cats, items); - - if (!cats || !items || cats->size() == 0) - { - // call failed - return LLUUID::null; - } - - if (cats->size() > 1) - { - const LLViewerInventoryCategory* friendFolder = gInventory.getCategory(parent_id); - if (friendFolder) - { - LL_WARNS_ONCE() << friendFolder->getName() << " folder contains more than one folder" << LL_ENDL; - } - } - - for (LLInventoryModel::cat_array_t::const_iterator iter = cats->begin(); - iter != cats->end(); - ++iter) - { - const LLInventoryCategory* category = (*iter); - if (category->getPreferredType() == LLFolderType::FT_CALLINGCARD) - { - return category->getUUID(); - } - } - - return LLUUID::null; -} - -// Inventorry -> -// Calling Cards - > -// Friends - > (the only expected folder) -// All (the only expected folder) - -const LLUUID& LLFriendCardsManager::findFriendFolderUUIDImpl() const -{ - const LLUUID callingCardsFolderID = gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD); - - return findFirstCallingCardSubfolder(callingCardsFolderID); -} - -const LLUUID& LLFriendCardsManager::findFriendAllSubfolderUUIDImpl() const -{ - LLUUID friendFolderUUID = findFriendFolderUUIDImpl(); - - return findFirstCallingCardSubfolder(friendFolderUUID); -} - -const LLUUID& LLFriendCardsManager::findChildFolderUUID(const LLUUID& parentFolderUUID, const std::string& nonLocalizedName) const -{ - LLNameCategoryCollector matchFolderFunctor(nonLocalizedName); - - return get_folder_uuid(parentFolderUUID, matchFolderFunctor); -} -const LLUUID& LLFriendCardsManager::findFriendCardInventoryUUIDImpl(const LLUUID& avatarID) -{ - LLUUID friendAllSubfolderUUID = findFriendAllSubfolderUUIDImpl(); - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - LLInventoryModel::item_array_t::const_iterator it; - - // it is not necessary to check friendAllSubfolderUUID against NULL. It will be processed by collectDescendents - gInventory.collectDescendents(friendAllSubfolderUUID, cats, items, LLInventoryModel::EXCLUDE_TRASH); - for (it = items.begin(); it != items.end(); ++it) - { - if ((*it)->getCreatorUUID() == avatarID) - return (*it)->getUUID(); - } - - return LLUUID::null; -} - -void LLFriendCardsManager::findMatchedFriendCards(const LLUUID& avatarID, LLInventoryModel::item_array_t& items) const -{ - LLInventoryModel::cat_array_t cats; - LLUUID friendFolderUUID = findFriendFolderUUIDImpl(); - - - LLViewerInventoryCategory* friendFolder = gInventory.getCategory(friendFolderUUID); - if (NULL == friendFolder) - return; - - LLParticularBuddyCollector matchFunctor(avatarID); - LLInventoryModel::cat_array_t subFolders; - subFolders.push_back(friendFolder); - - while (subFolders.size() > 0) - { - LLViewerInventoryCategory* cat = subFolders.at(0); - subFolders.erase(subFolders.begin()); - - gInventory.collectDescendentsIf(cat->getUUID(), cats, items, - LLInventoryModel::EXCLUDE_TRASH, matchFunctor); - - move_from_to_arrays(cats, subFolders); - } -} - -void LLFriendCardsManager::fetchAndCheckFolderDescendents(const LLUUID& folder_id, callback_t cb) -{ - // This instance will be deleted in LLInitialFriendCardsFetch::done(). - LLInitialFriendCardsFetch* fetch = new LLInitialFriendCardsFetch(folder_id, cb); - fetch->startFetch(); - if(fetch->isFinished()) - { - // everything is already here - call done. - fetch->done(); - } - else - { - // it's all on it's way - add an observer, and the inventory - // will call done for us when everything is here. - gInventory.addObserver(fetch); - } -} - -// Make sure LLInventoryModel::buildParentChildMap() has been called before it. -// This method must be called before any actions with friends list. -void LLFriendCardsManager::ensureFriendsFolderExists() -{ - const LLUUID calling_cards_folder_ID = gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD); - - // If "Friends" folder exists in "Calling Cards" we should check if "All" sub-folder - // exists in "Friends", otherwise we create it. - LLUUID friends_folder_ID = findFriendFolderUUIDImpl(); - if (friends_folder_ID.notNull()) - { - mState = LOADING_FRIENDS_FOLDER; - fetchAndCheckFolderDescendents(friends_folder_ID, - boost::bind(&LLFriendCardsManager::ensureFriendsAllFolderExists, this)); - } - else - { - if (!gInventory.isCategoryComplete(calling_cards_folder_ID)) - { - LLViewerInventoryCategory* cat = gInventory.getCategory(calling_cards_folder_ID); - std::string cat_name = cat ? cat->getName() : "unknown"; - LL_WARNS() << "Failed to find \"" << cat_name << "\" category descendents in Category Tree." << LL_ENDL; - } - - gInventory.createNewCategory( - calling_cards_folder_ID, - LLFolderType::FT_CALLINGCARD, - get_friend_folder_name(), - [](const LLUUID &new_category_id) - { - gInventory.createNewCategory( - new_category_id, - LLFolderType::FT_CALLINGCARD, - get_friend_all_subfolder_name(), - [](const LLUUID &new_category_id) - { - // Now when we have all needed folders we can sync their contents with buddies list. - LLFriendCardsManager::getInstance()->syncFriendsFolder(); - } - ); - } - ); - } -} - -// Make sure LLFriendCardsManager::ensureFriendsFolderExists() has been called before it. -void LLFriendCardsManager::ensureFriendsAllFolderExists() -{ - LLUUID friends_all_folder_ID = findFriendAllSubfolderUUIDImpl(); - if (friends_all_folder_ID.notNull()) - { - mState = LOADING_ALL_FOLDER; - fetchAndCheckFolderDescendents(friends_all_folder_ID, - boost::bind(&LLFriendCardsManager::syncFriendsFolder, this)); - } - else - { - LLUUID friends_folder_ID = findFriendFolderUUIDImpl(); - - if (!gInventory.isCategoryComplete(friends_folder_ID)) - { - LLViewerInventoryCategory* cat = gInventory.getCategory(friends_folder_ID); - std::string cat_name = cat ? cat->getName() : "unknown"; - LL_WARNS() << "Failed to find \"" << cat_name << "\" category descendents in Category Tree." << LL_ENDL; - } - - gInventory.createNewCategory( - friends_folder_ID, - LLFolderType::FT_CALLINGCARD, - get_friend_all_subfolder_name(), - [](const LLUUID &new_cat_id) - { - // Now when we have all needed folders we can sync their contents with buddies list. - LLFriendCardsManager::getInstance()->syncFriendsFolder(); - } - ); - } -} - -void LLFriendCardsManager::syncFriendsFolder() -{ - LLAvatarTracker::buddy_map_t all_buddies; - LLAvatarTracker::instance().copyBuddyList(all_buddies); - - // 1. Check if own calling card exists - const LLUUID calling_cards_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD); - - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - LLFindAgentCallingCard collector; - gInventory.collectDescendentsIf(calling_cards_folder_id, cats, items, LLInventoryModel::EXCLUDE_TRASH, collector); - - // Create own calling card if it was not found in Friends/All folder - if (!collector.isAgentCallingCardFound()) - { - create_inventory_callingcard(gAgentID, calling_cards_folder_id); - } - - // All folders created and updated. - mState = MANAGER_READY; - - // 2. Add missing Friend Cards for friends - LLAvatarTracker::buddy_map_t::const_iterator buddy_it = all_buddies.begin(); - LL_INFOS() << "try to build friends, count: " << all_buddies.size() << LL_ENDL; - for(; buddy_it != all_buddies.end(); ++buddy_it) - { - const LLUUID& buddy_id = (*buddy_it).first; - addFriendCardToInventory(buddy_id); - } -} - -class CreateFriendCardCallback : public LLInventoryCallback -{ -public: - void fire(const LLUUID& inv_item_id) - { - LLViewerInventoryItem* item = gInventory.getItem(inv_item_id); - - if (item) - LLFriendCardsManager::instance().extractAvatarID(item->getCreatorUUID()); - } -}; - -void LLFriendCardsManager::addFriendCardToInventory(const LLUUID& avatarID) -{ - - bool shouldBeAdded = true; - LLAvatarName av_name; - LLAvatarNameCache::get(avatarID, &av_name); - const std::string& name = av_name.getAccountName(); - - LL_DEBUGS() << "Processing buddy name: " << name - << ", id: " << avatarID - << LL_ENDL; - - if (shouldBeAdded && !isManagerReady()) - { - shouldBeAdded = false; - LL_DEBUGS() << "Calling cards manager not ready, state: " << getManagerState() << LL_ENDL; - } - - if (shouldBeAdded && findFriendCardInventoryUUIDImpl(avatarID).notNull()) - { - shouldBeAdded = false; - LL_DEBUGS() << "is found in Inventory: " << name << LL_ENDL; - } - - if (shouldBeAdded && isAvatarDataStored(avatarID)) - { - shouldBeAdded = false; - LL_DEBUGS() << "is found in sentRequests: " << name << LL_ENDL; - } - - if (shouldBeAdded) - { - putAvatarData(avatarID); - LL_DEBUGS() << "Sent create_inventory_item for " << avatarID << ", " << name << LL_ENDL; - - // TODO: mantipov: Is CreateFriendCardCallback really needed? Probably not - LLPointer cb = new CreateFriendCardCallback; - - create_inventory_callingcard(avatarID, findFriendAllSubfolderUUIDImpl(), cb); - } -} - -void LLFriendCardsManager::removeFriendCardFromInventory(const LLUUID& avatarID) -{ - LLInventoryModel::item_array_t items; - findMatchedFriendCards(avatarID, items); - - LLInventoryModel::item_array_t::const_iterator it; - for (it = items.begin(); it != items.end(); ++ it) - { - gInventory.removeItem((*it)->getUUID()); - } -} - -void LLFriendCardsManager::onFriendListUpdate(U32 changed_mask) -{ - LLAvatarTracker& at = LLAvatarTracker::instance(); - - switch(changed_mask) { - case LLFriendObserver::ADD: - { - LLFriendCardsManager& cards_manager = LLFriendCardsManager::instance(); - if (cards_manager.isManagerReady()) - { - // Try to add cards into inventory. - // If cards already exist they won't be created. - const std::set& changed_items = at.getChangedIDs(); - std::set::const_iterator id_it = changed_items.begin(); - std::set::const_iterator id_end = changed_items.end(); - for (; id_it != id_end; ++id_it) - { - cards_manager.addFriendCardToInventory(*id_it); - } - } - else - { - // User either removed calling cards' folders and manager is loading them - // or update came too early, before viewer had chance to load all folders. - // Either way don't process 'add' operation - manager will recreate all - // cards after fetching folders. - LL_INFOS_ONCE() << "Calling cards manager not ready, state: " - << cards_manager.getManagerState() - << ", postponing update." - << LL_ENDL; - } - } - break; - case LLFriendObserver::REMOVE: - { - const std::set& changed_items = at.getChangedIDs(); - std::set::const_iterator id_it = changed_items.begin(); - std::set::const_iterator id_end = changed_items.end(); - for (;id_it != id_end; ++id_it) - { - LLFriendCardsManager::instance().removeFriendCardFromInventory(*id_it); - } - } - - default:; - } -} - -// EOF +/** + * @file llfriendcard.cpp + * @brief Implementation of classes to process Friends Cards + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfriendcard.h" + +#include "llagent.h" +#include "llavatarnamecache.h" +#include "llinventory.h" +#include "llinventoryfunctions.h" +#include "llinventoryobserver.h" +#include "lltrans.h" + +#include "llcallingcard.h" // for LLAvatarTracker +#include "llviewerinventory.h" +#include "llinventorymodel.h" +#include "llcallbacklist.h" + +// Constants; + +static const std::string INVENTORY_STRING_FRIENDS_SUBFOLDER = "Friends"; +static const std::string INVENTORY_STRING_FRIENDS_ALL_SUBFOLDER = "All"; + +// helper functions + +// NOTE: For now Friends & All folders are created as protected folders of the LLFolderType::FT_CALLINGCARD type. +// So, their names will be processed in the LLFolderViewItem::refresh() to be localized +// using "InvFolder LABEL_NAME" as LLTrans::findString argument. + +// We must use in this file their hard-coded names to ensure found them on different locales. EXT-5829. +// These hard-coded names will be stored in InventoryItems but shown localized in FolderViewItems + +// If hack in the LLFolderViewItem::refresh() to localize protected folder is removed +// or these folders are not protected these names should be localized in another place/way. +inline const std::string get_friend_folder_name() +{ + return INVENTORY_STRING_FRIENDS_SUBFOLDER; +} + +inline const std::string get_friend_all_subfolder_name() +{ + return INVENTORY_STRING_FRIENDS_ALL_SUBFOLDER; +} + +void move_from_to_arrays(LLInventoryModel::cat_array_t& from, LLInventoryModel::cat_array_t& to) +{ + while (from.size() > 0) + { + to.push_back(from.at(0)); + from.erase(from.begin()); + } +} + +const LLUUID& get_folder_uuid(const LLUUID& parentFolderUUID, LLInventoryCollectFunctor& matchFunctor) +{ + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + + gInventory.collectDescendentsIf(parentFolderUUID, cats, items, + LLInventoryModel::EXCLUDE_TRASH, matchFunctor); + + S32 cats_count = cats.size(); + + if (cats_count > 1) + { + LL_WARNS_ONCE("LLFriendCardsManager") + << "There is more than one Friend card folder." + << "The first folder will be used." + << LL_ENDL; + } + + return (cats_count >= 1) ? cats.at(0)->getUUID() : LLUUID::null; +} + +/** + * Class LLFindAgentCallingCard + * + * An inventory collector functor for checking that agent's own calling card + * exists within the Calling Cards category and its sub-folders. + */ +class LLFindAgentCallingCard : public LLInventoryCollectFunctor +{ +public: + LLFindAgentCallingCard() : mIsAgentCallingCardFound(false) {} + virtual ~LLFindAgentCallingCard() {} + virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); + bool isAgentCallingCardFound() { return mIsAgentCallingCardFound; } + +private: + bool mIsAgentCallingCardFound; +}; + +bool LLFindAgentCallingCard::operator()(LLInventoryCategory* cat, LLInventoryItem* item) +{ + if (mIsAgentCallingCardFound) return true; + + if (item && item->getType() == LLAssetType::AT_CALLINGCARD && item->getCreatorUUID() == gAgentID) + { + mIsAgentCallingCardFound = true; + } + + return mIsAgentCallingCardFound; +} + +/** + * Class for fetching initial friend cards data + * + * Implemented to fix an issue when Inventory folders are in incomplete state. + * See EXT-2320, EXT-2061, EXT-1935, EXT-813. + * Uses a callback to sync Inventory Friends/All folder with agent's Friends List. + */ +class LLInitialFriendCardsFetch : public LLInventoryFetchDescendentsObserver +{ +public: + typedef boost::function callback_t; + + LLInitialFriendCardsFetch(const LLUUID& folder_id, + callback_t cb) : + LLInventoryFetchDescendentsObserver(folder_id), + mCheckFolderCallback(cb) + {} + + /* virtual */ void done(); + +private: + callback_t mCheckFolderCallback; +}; + +void LLInitialFriendCardsFetch::done() +{ + // This observer is no longer needed. + gInventory.removeObserver(this); + + doOnIdleOneTime(mCheckFolderCallback); + + delete this; +} + +// LLFriendCardsManager Constructor / Destructor +LLFriendCardsManager::LLFriendCardsManager() +: mState(INIT) +{ + LLAvatarTracker::instance().addObserver(this); +} + +LLFriendCardsManager::~LLFriendCardsManager() +{ + LLAvatarTracker::instance().removeObserver(this); +} + +void LLFriendCardsManager::putAvatarData(const LLUUID& avatarID) +{ + LL_INFOS() << "Store avatar data, avatarID: " << avatarID << LL_ENDL; + std::pair< avatar_uuid_set_t::iterator, bool > pr; + pr = mBuddyIDSet.insert(avatarID); + if (!pr.second) + { + LL_WARNS() << "Trying to add avatar UUID for the stored avatar: " + << avatarID + << LL_ENDL; + } +} + +const LLUUID LLFriendCardsManager::extractAvatarID(const LLUUID& avatarID) +{ + LLUUID rv; + avatar_uuid_set_t::iterator it = mBuddyIDSet.find(avatarID); + if (mBuddyIDSet.end() == it) + { + LL_WARNS() << "Call method for non-existent avatar name in the map: " << avatarID << LL_ENDL; + } + else + { + rv = (*it); + mBuddyIDSet.erase(it); + } + return rv; +} + +bool LLFriendCardsManager::isItemInAnyFriendsList(const LLViewerInventoryItem* item) +{ + if (item->getType() != LLAssetType::AT_CALLINGCARD) + return false; + + LLInventoryModel::item_array_t items; + findMatchedFriendCards(item->getCreatorUUID(), items); + + return items.size() > 0; +} + + +bool LLFriendCardsManager::isObjDirectDescendentOfCategory(const LLInventoryObject* obj, + const LLViewerInventoryCategory* cat) const +{ + // we need both params to proceed. + if ( !obj || !cat ) + return false; + + // Need to check that target category is in the Calling Card/Friends folder. + // In other case function returns unpredictable result. + if ( !isCategoryInFriendFolder(cat) ) + return false; + + bool result = false; + + LLInventoryModel::item_array_t* items; + LLInventoryModel::cat_array_t* cats; + + gInventory.lockDirectDescendentArrays(cat->getUUID(), cats, items); + if ( items ) + { + if ( obj->getType() == LLAssetType::AT_CALLINGCARD ) + { + // For CALLINGCARD compare items by creator's id, if they are equal assume + // that it is same card and return true. Note: UUID's of compared items + // may be not equal. Also, we already know that obj should be type of LLInventoryItem, + // but in case inventory database is broken check what dynamic_cast returns. + const LLInventoryItem* item = dynamic_cast < const LLInventoryItem* > (obj); + if ( item ) + { + LLUUID creator_id = item->getCreatorUUID(); + LLViewerInventoryItem* cur_item = NULL; + for ( S32 i = items->size() - 1; i >= 0; --i ) + { + cur_item = items->at(i); + if ( creator_id == cur_item->getCreatorUUID() ) + { + result = true; + break; + } + } + } + } + else + { + // Else check that items have same type and name. + // Note: UUID's of compared items also may be not equal. + std::string obj_name = obj->getName(); + LLViewerInventoryItem* cur_item = NULL; + for ( S32 i = items->size() - 1; i >= 0; --i ) + { + cur_item = items->at(i); + if ( obj->getType() != cur_item->getType() ) + continue; + if ( obj_name == cur_item->getName() ) + { + result = true; + break; + } + } + } + } + if ( !result && cats ) + { + // There is no direct descendent in items, so check categories. + // If target obj and descendent category have same type and name + // then return true. Note: UUID's of compared items also may be not equal. + std::string obj_name = obj->getName(); + LLViewerInventoryCategory* cur_cat = NULL; + for ( S32 i = cats->size() - 1; i >= 0; --i ) + { + cur_cat = cats->at(i); + if ( obj->getType() != cur_cat->getType() ) + continue; + if ( obj_name == cur_cat->getName() ) + { + result = true; + break; + } + } + } + gInventory.unlockDirectDescendentArrays(cat->getUUID()); + + return result; +} + + +bool LLFriendCardsManager::isCategoryInFriendFolder(const LLViewerInventoryCategory* cat) const +{ + if (NULL == cat) + return false; + return true == gInventory.isObjectDescendentOf(cat->getUUID(), findFriendFolderUUIDImpl()); +} + +bool LLFriendCardsManager::isAnyFriendCategory(const LLUUID& catID) const +{ + const LLUUID& friendFolderID = findFriendFolderUUIDImpl(); + if (catID == friendFolderID) + return true; + + return true == gInventory.isObjectDescendentOf(catID, friendFolderID); +} + +void LLFriendCardsManager::syncFriendCardsFolders() +{ + const LLUUID callingCardsFolderID = gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD); + + fetchAndCheckFolderDescendents(callingCardsFolderID, + boost::bind(&LLFriendCardsManager::ensureFriendsFolderExists, this)); +} + + +/************************************************************************/ +/* Private Methods */ +/************************************************************************/ +const LLUUID& LLFriendCardsManager::findFirstCallingCardSubfolder(const LLUUID &parent_id) const +{ + if (parent_id.isNull()) + { + return LLUUID::null; + } + + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(parent_id, cats, items); + + if (!cats || !items || cats->size() == 0) + { + // call failed + return LLUUID::null; + } + + if (cats->size() > 1) + { + const LLViewerInventoryCategory* friendFolder = gInventory.getCategory(parent_id); + if (friendFolder) + { + LL_WARNS_ONCE() << friendFolder->getName() << " folder contains more than one folder" << LL_ENDL; + } + } + + for (LLInventoryModel::cat_array_t::const_iterator iter = cats->begin(); + iter != cats->end(); + ++iter) + { + const LLInventoryCategory* category = (*iter); + if (category->getPreferredType() == LLFolderType::FT_CALLINGCARD) + { + return category->getUUID(); + } + } + + return LLUUID::null; +} + +// Inventorry -> +// Calling Cards - > +// Friends - > (the only expected folder) +// All (the only expected folder) + +const LLUUID& LLFriendCardsManager::findFriendFolderUUIDImpl() const +{ + const LLUUID callingCardsFolderID = gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD); + + return findFirstCallingCardSubfolder(callingCardsFolderID); +} + +const LLUUID& LLFriendCardsManager::findFriendAllSubfolderUUIDImpl() const +{ + LLUUID friendFolderUUID = findFriendFolderUUIDImpl(); + + return findFirstCallingCardSubfolder(friendFolderUUID); +} + +const LLUUID& LLFriendCardsManager::findChildFolderUUID(const LLUUID& parentFolderUUID, const std::string& nonLocalizedName) const +{ + LLNameCategoryCollector matchFolderFunctor(nonLocalizedName); + + return get_folder_uuid(parentFolderUUID, matchFolderFunctor); +} +const LLUUID& LLFriendCardsManager::findFriendCardInventoryUUIDImpl(const LLUUID& avatarID) +{ + LLUUID friendAllSubfolderUUID = findFriendAllSubfolderUUIDImpl(); + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLInventoryModel::item_array_t::const_iterator it; + + // it is not necessary to check friendAllSubfolderUUID against NULL. It will be processed by collectDescendents + gInventory.collectDescendents(friendAllSubfolderUUID, cats, items, LLInventoryModel::EXCLUDE_TRASH); + for (it = items.begin(); it != items.end(); ++it) + { + if ((*it)->getCreatorUUID() == avatarID) + return (*it)->getUUID(); + } + + return LLUUID::null; +} + +void LLFriendCardsManager::findMatchedFriendCards(const LLUUID& avatarID, LLInventoryModel::item_array_t& items) const +{ + LLInventoryModel::cat_array_t cats; + LLUUID friendFolderUUID = findFriendFolderUUIDImpl(); + + + LLViewerInventoryCategory* friendFolder = gInventory.getCategory(friendFolderUUID); + if (NULL == friendFolder) + return; + + LLParticularBuddyCollector matchFunctor(avatarID); + LLInventoryModel::cat_array_t subFolders; + subFolders.push_back(friendFolder); + + while (subFolders.size() > 0) + { + LLViewerInventoryCategory* cat = subFolders.at(0); + subFolders.erase(subFolders.begin()); + + gInventory.collectDescendentsIf(cat->getUUID(), cats, items, + LLInventoryModel::EXCLUDE_TRASH, matchFunctor); + + move_from_to_arrays(cats, subFolders); + } +} + +void LLFriendCardsManager::fetchAndCheckFolderDescendents(const LLUUID& folder_id, callback_t cb) +{ + // This instance will be deleted in LLInitialFriendCardsFetch::done(). + LLInitialFriendCardsFetch* fetch = new LLInitialFriendCardsFetch(folder_id, cb); + fetch->startFetch(); + if(fetch->isFinished()) + { + // everything is already here - call done. + fetch->done(); + } + else + { + // it's all on it's way - add an observer, and the inventory + // will call done for us when everything is here. + gInventory.addObserver(fetch); + } +} + +// Make sure LLInventoryModel::buildParentChildMap() has been called before it. +// This method must be called before any actions with friends list. +void LLFriendCardsManager::ensureFriendsFolderExists() +{ + const LLUUID calling_cards_folder_ID = gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD); + + // If "Friends" folder exists in "Calling Cards" we should check if "All" sub-folder + // exists in "Friends", otherwise we create it. + LLUUID friends_folder_ID = findFriendFolderUUIDImpl(); + if (friends_folder_ID.notNull()) + { + mState = LOADING_FRIENDS_FOLDER; + fetchAndCheckFolderDescendents(friends_folder_ID, + boost::bind(&LLFriendCardsManager::ensureFriendsAllFolderExists, this)); + } + else + { + if (!gInventory.isCategoryComplete(calling_cards_folder_ID)) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(calling_cards_folder_ID); + std::string cat_name = cat ? cat->getName() : "unknown"; + LL_WARNS() << "Failed to find \"" << cat_name << "\" category descendents in Category Tree." << LL_ENDL; + } + + gInventory.createNewCategory( + calling_cards_folder_ID, + LLFolderType::FT_CALLINGCARD, + get_friend_folder_name(), + [](const LLUUID &new_category_id) + { + gInventory.createNewCategory( + new_category_id, + LLFolderType::FT_CALLINGCARD, + get_friend_all_subfolder_name(), + [](const LLUUID &new_category_id) + { + // Now when we have all needed folders we can sync their contents with buddies list. + LLFriendCardsManager::getInstance()->syncFriendsFolder(); + } + ); + } + ); + } +} + +// Make sure LLFriendCardsManager::ensureFriendsFolderExists() has been called before it. +void LLFriendCardsManager::ensureFriendsAllFolderExists() +{ + LLUUID friends_all_folder_ID = findFriendAllSubfolderUUIDImpl(); + if (friends_all_folder_ID.notNull()) + { + mState = LOADING_ALL_FOLDER; + fetchAndCheckFolderDescendents(friends_all_folder_ID, + boost::bind(&LLFriendCardsManager::syncFriendsFolder, this)); + } + else + { + LLUUID friends_folder_ID = findFriendFolderUUIDImpl(); + + if (!gInventory.isCategoryComplete(friends_folder_ID)) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(friends_folder_ID); + std::string cat_name = cat ? cat->getName() : "unknown"; + LL_WARNS() << "Failed to find \"" << cat_name << "\" category descendents in Category Tree." << LL_ENDL; + } + + gInventory.createNewCategory( + friends_folder_ID, + LLFolderType::FT_CALLINGCARD, + get_friend_all_subfolder_name(), + [](const LLUUID &new_cat_id) + { + // Now when we have all needed folders we can sync their contents with buddies list. + LLFriendCardsManager::getInstance()->syncFriendsFolder(); + } + ); + } +} + +void LLFriendCardsManager::syncFriendsFolder() +{ + LLAvatarTracker::buddy_map_t all_buddies; + LLAvatarTracker::instance().copyBuddyList(all_buddies); + + // 1. Check if own calling card exists + const LLUUID calling_cards_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD); + + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLFindAgentCallingCard collector; + gInventory.collectDescendentsIf(calling_cards_folder_id, cats, items, LLInventoryModel::EXCLUDE_TRASH, collector); + + // Create own calling card if it was not found in Friends/All folder + if (!collector.isAgentCallingCardFound()) + { + create_inventory_callingcard(gAgentID, calling_cards_folder_id); + } + + // All folders created and updated. + mState = MANAGER_READY; + + // 2. Add missing Friend Cards for friends + LLAvatarTracker::buddy_map_t::const_iterator buddy_it = all_buddies.begin(); + LL_INFOS() << "try to build friends, count: " << all_buddies.size() << LL_ENDL; + for(; buddy_it != all_buddies.end(); ++buddy_it) + { + const LLUUID& buddy_id = (*buddy_it).first; + addFriendCardToInventory(buddy_id); + } +} + +class CreateFriendCardCallback : public LLInventoryCallback +{ +public: + void fire(const LLUUID& inv_item_id) + { + LLViewerInventoryItem* item = gInventory.getItem(inv_item_id); + + if (item) + LLFriendCardsManager::instance().extractAvatarID(item->getCreatorUUID()); + } +}; + +void LLFriendCardsManager::addFriendCardToInventory(const LLUUID& avatarID) +{ + + bool shouldBeAdded = true; + LLAvatarName av_name; + LLAvatarNameCache::get(avatarID, &av_name); + const std::string& name = av_name.getAccountName(); + + LL_DEBUGS() << "Processing buddy name: " << name + << ", id: " << avatarID + << LL_ENDL; + + if (shouldBeAdded && !isManagerReady()) + { + shouldBeAdded = false; + LL_DEBUGS() << "Calling cards manager not ready, state: " << getManagerState() << LL_ENDL; + } + + if (shouldBeAdded && findFriendCardInventoryUUIDImpl(avatarID).notNull()) + { + shouldBeAdded = false; + LL_DEBUGS() << "is found in Inventory: " << name << LL_ENDL; + } + + if (shouldBeAdded && isAvatarDataStored(avatarID)) + { + shouldBeAdded = false; + LL_DEBUGS() << "is found in sentRequests: " << name << LL_ENDL; + } + + if (shouldBeAdded) + { + putAvatarData(avatarID); + LL_DEBUGS() << "Sent create_inventory_item for " << avatarID << ", " << name << LL_ENDL; + + // TODO: mantipov: Is CreateFriendCardCallback really needed? Probably not + LLPointer cb = new CreateFriendCardCallback; + + create_inventory_callingcard(avatarID, findFriendAllSubfolderUUIDImpl(), cb); + } +} + +void LLFriendCardsManager::removeFriendCardFromInventory(const LLUUID& avatarID) +{ + LLInventoryModel::item_array_t items; + findMatchedFriendCards(avatarID, items); + + LLInventoryModel::item_array_t::const_iterator it; + for (it = items.begin(); it != items.end(); ++ it) + { + gInventory.removeItem((*it)->getUUID()); + } +} + +void LLFriendCardsManager::onFriendListUpdate(U32 changed_mask) +{ + LLAvatarTracker& at = LLAvatarTracker::instance(); + + switch(changed_mask) { + case LLFriendObserver::ADD: + { + LLFriendCardsManager& cards_manager = LLFriendCardsManager::instance(); + if (cards_manager.isManagerReady()) + { + // Try to add cards into inventory. + // If cards already exist they won't be created. + const std::set& changed_items = at.getChangedIDs(); + std::set::const_iterator id_it = changed_items.begin(); + std::set::const_iterator id_end = changed_items.end(); + for (; id_it != id_end; ++id_it) + { + cards_manager.addFriendCardToInventory(*id_it); + } + } + else + { + // User either removed calling cards' folders and manager is loading them + // or update came too early, before viewer had chance to load all folders. + // Either way don't process 'add' operation - manager will recreate all + // cards after fetching folders. + LL_INFOS_ONCE() << "Calling cards manager not ready, state: " + << cards_manager.getManagerState() + << ", postponing update." + << LL_ENDL; + } + } + break; + case LLFriendObserver::REMOVE: + { + const std::set& changed_items = at.getChangedIDs(); + std::set::const_iterator id_it = changed_items.begin(); + std::set::const_iterator id_end = changed_items.end(); + for (;id_it != id_end; ++id_it) + { + LLFriendCardsManager::instance().removeFriendCardFromInventory(*id_it); + } + } + + default:; + } +} + +// EOF diff --git a/indra/newview/llgesturemgr.cpp b/indra/newview/llgesturemgr.cpp index fcafd949c0..2ac99a2f16 100644 --- a/indra/newview/llgesturemgr.cpp +++ b/indra/newview/llgesturemgr.cpp @@ -1,1580 +1,1580 @@ -/** - * @file llgesturemgr.cpp - * @brief Manager for playing gestures on the viewer - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llgesturemgr.h" - -// system -#include -#include - -// library -#include "llaudioengine.h" -#include "lldatapacker.h" -#include "llfloaterreg.h" -#include "llinventory.h" -#include "llkeyframemotion.h" -#include "llmultigesture.h" -#include "llnotificationsutil.h" -#include "llstl.h" -#include "llstring.h" // todo: remove -#include "llfilesystem.h" -#include "message.h" - -// newview -#include "llagent.h" -#include "lldelayedgestureerror.h" -#include "llinventorymodel.h" -#include "llviewermessage.h" -#include "llvoavatarself.h" -#include "llviewerstats.h" -#include "llfloaterimnearbychat.h" -#include "llappearancemgr.h" -#include "llgesturelistener.h" - -// Longest time, in seconds, to wait for all animations to stop playing -const F32 MAX_WAIT_ANIM_SECS = 30.f; -// Longest time, in seconds, to wait for a key release. -// This should be relatively long, but not too long. 10 minutes is enough -const F32 MAX_WAIT_KEY_SECS = 60.f * 10.f; - -// Lightweight constructor. -// init() does the heavy lifting. -LLGestureMgr::LLGestureMgr() -: mValid(false), - mPlaying(), - mActive(), - mLoadingCount(0) -{ - gInventory.addObserver(this); - mListener.reset(new LLGestureListener()); -} - - -// We own the data for gestures, so clean them up. -LLGestureMgr::~LLGestureMgr() -{ - item_map_t::iterator it; - for (it = mActive.begin(); it != mActive.end(); ++it) - { - LLMultiGesture* gesture = (*it).second; - - delete gesture; - gesture = NULL; - } - gInventory.removeObserver(this); -} - - -void LLGestureMgr::init() -{ - // TODO -} - -void LLGestureMgr::changed(U32 mask) -{ - LLInventoryFetchItemsObserver::changed(mask); - - if (mask & LLInventoryObserver::GESTURE) - { - // If there was a gesture label changed, update all the names in the - // active gestures and then notify observers - if (mask & LLInventoryObserver::LABEL) - { - for(item_map_t::iterator it = mActive.begin(); it != mActive.end(); ++it) - { - if(it->second) - { - LLViewerInventoryItem* item = gInventory.getItem(it->first); - if(item) - { - it->second->mName = item->getName(); - } - } - } - notifyObservers(); - } - // If there was a gesture added or removed notify observers - // STRUCTURE denotes that the inventory item has been moved - // In the case of deleting gesture, it is moved to the trash - else if(mask & LLInventoryObserver::ADD || - mask & LLInventoryObserver::REMOVE || - mask & LLInventoryObserver::STRUCTURE) - { - notifyObservers(); - } - } -} - - -// Use this version when you have the item_id but not the asset_id, -// and you KNOW the inventory is loaded. -void LLGestureMgr::activateGesture(const LLUUID& item_id) -{ - LLViewerInventoryItem* item = gInventory.getItem(item_id); - if (!item) return; - if (item->getType() != LLAssetType::AT_GESTURE) - return; - - LLUUID asset_id = item->getAssetUUID(); - - mLoadingCount = 1; - mDeactivateSimilarNames.clear(); - - const bool inform_server = true; - const bool deactivate_similar = false; - activateGestureWithAsset(item_id, asset_id, inform_server, deactivate_similar); -} - - -void LLGestureMgr::activateGestures(LLViewerInventoryItem::item_array_t& items) -{ - // Load up the assets - S32 count = 0; - LLViewerInventoryItem::item_array_t::const_iterator it; - for (it = items.begin(); it != items.end(); ++it) - { - LLViewerInventoryItem* item = *it; - - if (isGestureActive(item->getUUID())) - { - continue; - } - else - { // Make gesture active and persistent through login sessions. -Aura 07-12-06 - activateGesture(item->getUUID()); - } - - count++; - } - - mLoadingCount = count; - mDeactivateSimilarNames.clear(); - - for (it = items.begin(); it != items.end(); ++it) - { - LLViewerInventoryItem* item = *it; - - if (isGestureActive(item->getUUID())) - { - continue; - } - - // Don't inform server, we'll do that in bulk - const bool no_inform_server = false; - const bool deactivate_similar = true; - activateGestureWithAsset(item->getUUID(), item->getAssetUUID(), - no_inform_server, - deactivate_similar); - } - - // Inform the database of this change - LLMessageSystem* msg = gMessageSystem; - - bool start_message = true; - - for (it = items.begin(); it != items.end(); ++it) - { - LLViewerInventoryItem* item = *it; - - if (isGestureActive(item->getUUID())) - { - continue; - } - - if (start_message) - { - msg->newMessage("ActivateGestures"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->addU32("Flags", 0x0); - start_message = false; - } - - msg->nextBlock("Data"); - msg->addUUID("ItemID", item->getUUID()); - msg->addUUID("AssetID", item->getAssetUUID()); - msg->addU32("GestureFlags", 0x0); - - if (msg->getCurrentSendTotal() > MTUBYTES) - { - gAgent.sendReliableMessage(); - start_message = true; - } - } - - if (!start_message) - { - gAgent.sendReliableMessage(); - } -} - - -struct LLLoadInfo -{ - LLUUID mItemID; - bool mInformServer; - bool mDeactivateSimilar; -}; - -// If inform_server is true, will send a message upstream to update -// the user_gesture_active table. -/** - * It will load a gesture from remote storage - */ -void LLGestureMgr::activateGestureWithAsset(const LLUUID& item_id, - const LLUUID& asset_id, - bool inform_server, - bool deactivate_similar) -{ - const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id); - - if( !gAssetStorage ) - { - LL_WARNS() << "LLGestureMgr::activateGestureWithAsset without valid gAssetStorage" << LL_ENDL; - return; - } - // If gesture is already active, nothing to do. - if (isGestureActive(item_id)) - { - LL_WARNS() << "Tried to loadGesture twice " << item_id << LL_ENDL; - return; - } - -// if (asset_id.isNull()) -// { -// LL_WARNS() << "loadGesture() - gesture has no asset" << LL_ENDL; -// return; -// } - - // For now, put NULL into the item map. We'll build a gesture - // class object when the asset data arrives. - mActive[base_item_id] = NULL; - - // Copy the UUID - if (asset_id.notNull()) - { - LLLoadInfo* info = new LLLoadInfo; - info->mItemID = base_item_id; - info->mInformServer = inform_server; - info->mDeactivateSimilar = deactivate_similar; - - const bool high_priority = true; - gAssetStorage->getAssetData(asset_id, - LLAssetType::AT_GESTURE, - onLoadComplete, - (void*)info, - high_priority); - } - else - { - notifyObservers(); - } -} - - -void notify_update_label(const LLUUID& base_item_id) -{ - gInventory.addChangedMask(LLInventoryObserver::LABEL, base_item_id); - LLGestureMgr::instance().notifyObservers(); -} - -void LLGestureMgr::deactivateGesture(const LLUUID& item_id) -{ - const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id); - item_map_t::iterator it = mActive.find(base_item_id); - if (it == mActive.end()) - { - LL_WARNS() << "deactivateGesture for inactive gesture " << item_id << LL_ENDL; - return; - } - - // mActive owns this gesture pointer, so clean up memory. - LLMultiGesture* gesture = (*it).second; - - // Can be NULL gestures in the map - if (gesture) - { - stopGesture(gesture); - - delete gesture; - gesture = NULL; - } - - mActive.erase(it); - - // Inform the database of this change - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("DeactivateGestures"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->addU32("Flags", 0x0); - - msg->nextBlock("Data"); - msg->addUUID("ItemID", item_id); - msg->addU32("GestureFlags", 0x0); - - gAgent.sendReliableMessage(); - - LLPointer cb = - new LLBoostFuncInventoryCallback(no_op_inventory_func, - boost::bind(notify_update_label,base_item_id)); - - LLAppearanceMgr::instance().removeCOFItemLinks(base_item_id, cb); -} - - -void LLGestureMgr::deactivateSimilarGestures(LLMultiGesture* in, const LLUUID& in_item_id) -{ - const LLUUID& base_in_item_id = gInventory.getLinkedItemID(in_item_id); - uuid_vec_t gest_item_ids; - - // Deactivate all gestures that match - item_map_t::iterator it; - for (it = mActive.begin(); it != mActive.end(); ) - { - const LLUUID& item_id = (*it).first; - LLMultiGesture* gest = (*it).second; - - // Don't deactivate the gesture we are looking for duplicates of - // (for replaceGesture) - if (!gest || item_id == base_in_item_id) - { - // legal, can have null pointers in list - ++it; - } - else if ((!gest->mTrigger.empty() && gest->mTrigger == in->mTrigger) - || (gest->mKey != KEY_NONE && gest->mKey == in->mKey && gest->mMask == in->mMask)) - { - gest_item_ids.push_back(item_id); - - stopGesture(gest); - - delete gest; - gest = NULL; - - mActive.erase(it++); - gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id); - - } - else - { - ++it; - } - } - - // Inform database of the change - LLMessageSystem* msg = gMessageSystem; - bool start_message = true; - uuid_vec_t::const_iterator vit = gest_item_ids.begin(); - while (vit != gest_item_ids.end()) - { - if (start_message) - { - msg->newMessage("DeactivateGestures"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->addU32("Flags", 0x0); - start_message = false; - } - - msg->nextBlock("Data"); - msg->addUUID("ItemID", *vit); - msg->addU32("GestureFlags", 0x0); - - if (msg->getCurrentSendTotal() > MTUBYTES) - { - gAgent.sendReliableMessage(); - start_message = true; - } - - ++vit; - } - - if (!start_message) - { - gAgent.sendReliableMessage(); - } - - // Add to the list of names for the user. - for (vit = gest_item_ids.begin(); vit != gest_item_ids.end(); ++vit) - { - LLViewerInventoryItem* item = gInventory.getItem(*vit); - if (!item) continue; - - mDeactivateSimilarNames.append(item->getName()); - mDeactivateSimilarNames.append("\n"); - } - - notifyObservers(); -} - - -bool LLGestureMgr::isGestureActive(const LLUUID& item_id) -{ - const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id); - item_map_t::iterator it = mActive.find(base_item_id); - return (it != mActive.end()); -} - - -bool LLGestureMgr::isGesturePlaying(const LLUUID& item_id) -{ - const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id); - - item_map_t::iterator it = mActive.find(base_item_id); - if (it == mActive.end()) return false; - - LLMultiGesture* gesture = (*it).second; - if (!gesture) return false; - - return gesture->mPlaying; -} - -bool LLGestureMgr::isGesturePlaying(LLMultiGesture* gesture) -{ - if(!gesture) - { - return false; - } - - return gesture->mPlaying; -} - -void LLGestureMgr::replaceGesture(const LLUUID& item_id, LLMultiGesture* new_gesture, const LLUUID& asset_id) -{ - const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id); - - item_map_t::iterator it = mActive.find(base_item_id); - if (it == mActive.end()) - { - LL_WARNS() << "replaceGesture for inactive gesture " << base_item_id << LL_ENDL; - return; - } - - LLMultiGesture* old_gesture = (*it).second; - stopGesture(old_gesture); - - mActive.erase(base_item_id); - - mActive[base_item_id] = new_gesture; - - // replaceGesture(const LLUUID& item_id, const LLUUID& new_asset_id) - // replaces ids without repalcing gesture - if (old_gesture != new_gesture) - { - delete old_gesture; - old_gesture = NULL; - } - - if (asset_id.notNull()) - { - mLoadingCount = 1; - mDeactivateSimilarNames.clear(); - - LLLoadInfo* info = new LLLoadInfo; - info->mItemID = base_item_id; - info->mInformServer = true; - info->mDeactivateSimilar = false; - - const bool high_priority = true; - gAssetStorage->getAssetData(asset_id, - LLAssetType::AT_GESTURE, - onLoadComplete, - (void*)info, - high_priority); - } - - notifyObservers(); -} - -void LLGestureMgr::replaceGesture(const LLUUID& item_id, const LLUUID& new_asset_id) -{ - const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id); - - item_map_t::iterator it = LLGestureMgr::instance().mActive.find(base_item_id); - if (it == mActive.end()) - { - LL_WARNS() << "replaceGesture for inactive gesture " << base_item_id << LL_ENDL; - return; - } - - // mActive owns this gesture pointer, so clean up memory. - LLMultiGesture* gesture = (*it).second; - LLGestureMgr::instance().replaceGesture(base_item_id, gesture, new_asset_id); -} - -void LLGestureMgr::playGesture(LLMultiGesture* gesture, bool fromKeyPress) -{ - if (!gesture) return; - - // Reset gesture to first step - gesture->mCurrentStep = 0; - gesture->mTriggeredByKey = fromKeyPress; - - // Add to list of playing - gesture->mPlaying = true; - mPlaying.push_back(gesture); - - // Load all needed assets to minimize the delays - // when gesture is playing. - for (std::vector::iterator steps_it = gesture->mSteps.begin(); - steps_it != gesture->mSteps.end(); - ++steps_it) - { - LLGestureStep* step = *steps_it; - switch(step->getType()) - { - case STEP_ANIMATION: - { - LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step; - const LLUUID& anim_id = anim_step->mAnimAssetID; - - // Don't request the animation if this step stops it or if it is already in the cache - if (!(anim_id.isNull() - || anim_step->mFlags & ANIM_FLAG_STOP - || gAssetStorage->hasLocalAsset(anim_id, LLAssetType::AT_ANIMATION))) - { - mLoadingAssets.insert(anim_id); - - LLUUID* id = new LLUUID(gAgentID); - gAssetStorage->getAssetData(anim_id, - LLAssetType::AT_ANIMATION, - onAssetLoadComplete, - (void *)id, - true); - } - break; - } - case STEP_SOUND: - { - LLGestureStepSound* sound_step = (LLGestureStepSound*)step; - const LLUUID& sound_id = sound_step->mSoundAssetID; - if (!(sound_id.isNull() - || gAssetStorage->hasLocalAsset(sound_id, LLAssetType::AT_SOUND))) - { - mLoadingAssets.insert(sound_id); - - gAssetStorage->getAssetData(sound_id, - LLAssetType::AT_SOUND, - onAssetLoadComplete, - NULL, - true); - } - break; - } - case STEP_CHAT: - case STEP_WAIT: - case STEP_EOF: - { - break; - } - default: - { - LL_WARNS() << "Unknown gesture step type: " << step->getType() << LL_ENDL; - } - } - } - - // And get it going - stepGesture(gesture); - - notifyObservers(); -} - - -// Convenience function that looks up the item_id for you. -void LLGestureMgr::playGesture(const LLUUID& item_id) -{ - const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id); - - item_map_t::iterator it = mActive.find(base_item_id); - if (it == mActive.end()) return; - - LLMultiGesture* gesture = (*it).second; - if (!gesture) return; - - playGesture(gesture); -} - - -// Iterates through space delimited tokens in string, triggering any gestures found. -// Generates a revised string that has the found tokens replaced by their replacement strings -// and (as a minor side effect) has multiple spaces in a row replaced by single spaces. -bool LLGestureMgr::triggerAndReviseString(const std::string &utf8str, std::string* revised_string) -{ - std::string tokenized = utf8str; - - bool found_gestures = false; - bool first_token = true; - - typedef boost::tokenizer > tokenizer; - boost::char_separator sep(" "); - tokenizer tokens(tokenized, sep); - tokenizer::iterator token_iter; - - for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) - { - const char* cur_token = token_iter->c_str(); - LLMultiGesture* gesture = NULL; - - // Only pay attention to the first gesture in the string. - if( !found_gestures ) - { - // collect gestures that match - std::vector matching; - item_map_t::iterator it; - for (it = mActive.begin(); it != mActive.end(); ++it) - { - gesture = (*it).second; - - // Gesture asset data might not have arrived yet - if (!gesture) continue; - - if (LLStringUtil::compareInsensitive(gesture->mTrigger, cur_token) == 0) - { - matching.push_back(gesture); - } - - gesture = NULL; - } - - - if (matching.size() > 0) - { - // choose one at random - { - S32 random = ll_rand(matching.size()); - - gesture = matching[random]; - - playGesture(gesture); - - if (!gesture->mReplaceText.empty()) - { - if( !first_token ) - { - if (revised_string) - revised_string->append( " " ); - } - - // Don't muck with the user's capitalization if we don't have to. - if( LLStringUtil::compareInsensitive(cur_token, gesture->mReplaceText) == 0) - { - if (revised_string) - revised_string->append( cur_token ); - } - else - { - if (revised_string) - revised_string->append( gesture->mReplaceText ); - } - } - found_gestures = true; - } - } - } - - if(!gesture) - { - // This token doesn't match a gesture. Pass it through to the output. - if( !first_token ) - { - if (revised_string) - revised_string->append( " " ); - } - if (revised_string) - revised_string->append( cur_token ); - } - - first_token = false; - gesture = NULL; - } - return found_gestures; -} - - -bool LLGestureMgr::triggerGesture(KEY key, MASK mask) -{ - std::vector matching; - item_map_t::iterator it; - - // collect matching gestures - for (it = mActive.begin(); it != mActive.end(); ++it) - { - LLMultiGesture* gesture = (*it).second; - - // asset data might not have arrived yet - if (!gesture) continue; - - if (gesture->mKey == key - && gesture->mMask == mask - && gesture->mWaitingKeyRelease == false) - { - matching.push_back(gesture); - } - } - - // choose one and play it - if (matching.size() > 0) - { - U32 random = ll_rand(matching.size()); - - LLMultiGesture* gesture = matching[random]; - - playGesture(gesture, true); - return true; - } - return false; -} - - -bool LLGestureMgr::triggerGestureRelease(KEY key, MASK mask) -{ - std::vector matching; - item_map_t::iterator it; - - // collect matching gestures - for (it = mActive.begin(); it != mActive.end(); ++it) - { - LLMultiGesture* gesture = (*it).second; - - // asset data might not have arrived yet - if (!gesture) continue; - - if (gesture->mKey == key - && gesture->mMask == mask) - { - gesture->mKeyReleased = true; - } - } - - //If we found one, block. Otherwise tell them it's free to go. - return matching.size() > 0; -} - - -S32 LLGestureMgr::getPlayingCount() const -{ - return mPlaying.size(); -} - - -struct IsGesturePlaying -{ - bool operator()(const LLMultiGesture* gesture) const - { - return bool(gesture->mPlaying); - } -}; - -void LLGestureMgr::update() -{ - S32 i; - for (i = 0; i < (S32)mPlaying.size(); ++i) - { - stepGesture(mPlaying[i]); - } - - // Clear out gestures that are done, by moving all the - // ones that are still playing to the front. - std::vector::iterator new_end; - new_end = std::partition(mPlaying.begin(), - mPlaying.end(), - IsGesturePlaying()); - - // Something finished playing - if (new_end != mPlaying.end()) - { - // Delete the completed gestures that want deletion - std::vector::iterator it; - for (it = new_end; it != mPlaying.end(); ++it) - { - LLMultiGesture* gesture = *it; - - if (gesture->mDoneCallback) - { - gesture->mDoneCallback(gesture, gesture->mCallbackData); - - // callback might have deleted gesture, can't - // rely on this pointer any more - gesture = NULL; - } - } - - // And take done gestures out of the playing list - mPlaying.erase(new_end, mPlaying.end()); - - notifyObservers(); - } -} - - -// Run all steps until you're either done or hit a wait. -void LLGestureMgr::stepGesture(LLMultiGesture* gesture) -{ - if (!gesture) - { - return; - } - if (!isAgentAvatarValid() || hasLoadingAssets(gesture)) return; - - // Of the ones that started playing, have any stopped? - - std::set::iterator gest_it; - for (gest_it = gesture->mPlayingAnimIDs.begin(); - gest_it != gesture->mPlayingAnimIDs.end(); - ) - { - // look in signaled animations (simulator's view of what is - // currently playing. - LLVOAvatar::AnimIterator play_it = gAgentAvatarp->mSignaledAnimations.find(*gest_it); - if (play_it != gAgentAvatarp->mSignaledAnimations.end()) - { - ++gest_it; - } - else - { - // not found, so not currently playing or scheduled to play - // delete from the triggered set - gesture->mPlayingAnimIDs.erase(gest_it++); - } - } - - // Of all the animations that we asked the sim to start for us, - // pick up the ones that have actually started. - for (gest_it = gesture->mRequestedAnimIDs.begin(); - gest_it != gesture->mRequestedAnimIDs.end(); - ) - { - LLVOAvatar::AnimIterator play_it = gAgentAvatarp->mSignaledAnimations.find(*gest_it); - if (play_it != gAgentAvatarp->mSignaledAnimations.end()) - { - // Hooray, this animation has started playing! - // Copy into playing. - gesture->mPlayingAnimIDs.insert(*gest_it); - gesture->mRequestedAnimIDs.erase(gest_it++); - } - else - { - // nope, not playing yet - ++gest_it; - } - } - - // Run the current steps - bool waiting = false; - while (!waiting && gesture->mPlaying) - { - // Get the current step, if there is one. - // Otherwise enter the waiting at end state. - LLGestureStep* step = NULL; - if (gesture->mCurrentStep < (S32)gesture->mSteps.size()) - { - step = gesture->mSteps[gesture->mCurrentStep]; - llassert(step != NULL); - } - else - { - // step stays null, we're off the end - gesture->mWaitingAtEnd = true; - } - - - // If we're waiting at the end, wait for all gestures to stop - // playing. - // TODO: Wait for all sounds to complete as well. - if (gesture->mWaitingAtEnd) - { - // Neither do we have any pending requests, nor are they - // still playing. - if ((gesture->mRequestedAnimIDs.empty() - && gesture->mPlayingAnimIDs.empty())) - { - // all animations are done playing - gesture->mWaitingAtEnd = false; - gesture->mPlaying = false; - } - else - { - waiting = true; - } - continue; - } - - // If we're waiting a fixed amount of time, check for timer - // expiration. - if (gesture->mWaitingKeyRelease) - { - // We're waiting for a certain amount of time to pass - if (gesture->mKeyReleased) - { - // wait is done, continue execution - gesture->mWaitingKeyRelease = false; - gesture->mCurrentStep++; - } - else if (gesture->mWaitTimer.getElapsedTimeF32() > MAX_WAIT_KEY_SECS) - { - LL_INFOS("GestureMgr") << "Waited too long for key release, continuing gesture." - << LL_ENDL; - gesture->mWaitingKeyRelease = false; - gesture->mCurrentStep++; - } - else - { - // we're waiting, so execution is done for now - waiting = true; - } - continue; - } - - // If we're waiting on our animations to stop, poll for - // completion. - if (gesture->mWaitingAnimations) - { - // Neither do we have any pending requests, nor are they - // still playing. - if ((gesture->mRequestedAnimIDs.empty() - && gesture->mPlayingAnimIDs.empty())) - { - // all animations are done playing - gesture->mWaitingAnimations = false; - gesture->mCurrentStep++; - } - else if (gesture->mWaitTimer.getElapsedTimeF32() > MAX_WAIT_ANIM_SECS) - { - // we've waited too long for an animation - LL_INFOS("GestureMgr") << "Waited too long for animations to stop, continuing gesture." - << LL_ENDL; - gesture->mWaitingAnimations = false; - gesture->mCurrentStep++; - } - else - { - waiting = true; - } - continue; - } - - // If we're waiting a fixed amount of time, check for timer - // expiration. - if (gesture->mWaitingTimer) - { - // We're waiting for a certain amount of time to pass - LLGestureStepWait* wait_step = (LLGestureStepWait*)step; - - F32 elapsed = gesture->mWaitTimer.getElapsedTimeF32(); - if (elapsed > wait_step->mWaitSeconds) - { - // wait is done, continue execution - gesture->mWaitingTimer = false; - gesture->mCurrentStep++; - } - else - { - // we're waiting, so execution is done for now - waiting = true; - } - continue; - } - - // Not waiting, do normal execution - runStep(gesture, step); - } -} - - -void LLGestureMgr::runStep(LLMultiGesture* gesture, LLGestureStep* step) -{ - switch(step->getType()) - { - case STEP_ANIMATION: - { - LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step; - if (anim_step->mAnimAssetID.isNull()) - { - gesture->mCurrentStep++; - } - - if (anim_step->mFlags & ANIM_FLAG_STOP) - { - gAgent.sendAnimationRequest(anim_step->mAnimAssetID, ANIM_REQUEST_STOP); - // remove it from our request set in case we just requested it - std::set::iterator set_it = gesture->mRequestedAnimIDs.find(anim_step->mAnimAssetID); - if (set_it != gesture->mRequestedAnimIDs.end()) - { - gesture->mRequestedAnimIDs.erase(set_it); - } - } - else - { - gAgent.sendAnimationRequest(anim_step->mAnimAssetID, ANIM_REQUEST_START); - // Indicate that we've requested this animation to play as - // part of this gesture (but it won't start playing for at - // least one round-trip to simulator). - gesture->mRequestedAnimIDs.insert(anim_step->mAnimAssetID); - } - gesture->mCurrentStep++; - break; - } - case STEP_SOUND: - { - LLGestureStepSound* sound_step = (LLGestureStepSound*)step; - const LLUUID& sound_id = sound_step->mSoundAssetID; - const F32 volume = 1.f; - send_sound_trigger(sound_id, volume); - gesture->mCurrentStep++; - break; - } - case STEP_CHAT: - { - LLGestureStepChat* chat_step = (LLGestureStepChat*)step; - std::string chat_text = chat_step->mChatText; - // Don't animate the nodding, as this might not blend with - // other playing animations. - - const bool animate = false; - - (LLFloaterReg::getTypedInstance("nearby_chat"))-> - sendChatFromViewer(chat_text, CHAT_TYPE_NORMAL, animate); - - gesture->mCurrentStep++; - break; - } - case STEP_WAIT: - { - LLGestureStepWait* wait_step = (LLGestureStepWait*)step; - if (gesture->mTriggeredByKey // Only wait here IF we were triggered by a key! - && gesture->mWaitingKeyRelease == false // We can only do this once! Prevent gestures infinitely running - && wait_step->mFlags & WAIT_FLAG_KEY_RELEASE) - { - // Lets wait for the key release first so we don't hold up re-presses - gesture->mWaitingKeyRelease = true; - gesture->mKeyReleased = false; - // Use the wait timer as a deadlock breaker for key release waits. - gesture->mWaitTimer.reset(); - } - else if (wait_step->mFlags & WAIT_FLAG_TIME) - { - gesture->mWaitingTimer = true; - gesture->mWaitTimer.reset(); - } - else if (wait_step->mFlags & WAIT_FLAG_ALL_ANIM) - { - gesture->mWaitingAnimations = true; - // Use the wait timer as a deadlock breaker for animation waits. - gesture->mWaitTimer.reset(); - } - else - { - gesture->mCurrentStep++; - } - // Don't increment instruction pointer until wait is complete. - break; - } - default: - { - break; - } - } -} - - -// static -void LLGestureMgr::onLoadComplete(const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status) -{ - LLLoadInfo* info = (LLLoadInfo*)user_data; - - LLUUID item_id = info->mItemID; - bool inform_server = info->mInformServer; - bool deactivate_similar = info->mDeactivateSimilar; - - delete info; - info = NULL; - LLGestureMgr& self = LLGestureMgr::instance(); - self.mLoadingCount--; - - if (0 == status) - { - LLFileSystem file(asset_uuid, type, LLFileSystem::READ); - S32 size = file.getSize(); - - std::vector buffer(size+1); - - file.read((U8*)&buffer[0], size); - // ensure there's a trailing NULL so strlen will work. - buffer[size] = '\0'; - - LLMultiGesture* gesture = new LLMultiGesture(); - - LLDataPackerAsciiBuffer dp(&buffer[0], size+1); - bool ok = gesture->deserialize(dp); - - if (ok) - { - if (deactivate_similar) - { - self.deactivateSimilarGestures(gesture, item_id); - - // Display deactivation message if this was the last of the bunch. - if (self.mLoadingCount == 0 - && self.mDeactivateSimilarNames.length() > 0) - { - // we're done with this set of deactivations - LLSD args; - args["NAMES"] = self.mDeactivateSimilarNames; - LLNotificationsUtil::add("DeactivatedGesturesTrigger", args); - } - } - - LLViewerInventoryItem* item = gInventory.getItem(item_id); - if(item) - { - gesture->mName = item->getName(); - } - else - { - // Watch this item and set gesture name when item exists in inventory - self.setFetchID(item_id); - self.startFetch(); - } - - item_map_t::iterator it = self.mActive.find(item_id); - if (it == self.mActive.end()) - { - // Gesture is supposed to be present, active, but NULL - LL_DEBUGS("GestureMgr") << "Gesture " << item_id << " not found in active list" << LL_ENDL; - } - else - { - LLMultiGesture* old_gesture = (*it).second; - if (old_gesture && old_gesture != gesture) - { - LL_DEBUGS("GestureMgr") << "Received dupplicate " << item_id << " callback" << LL_ENDL; - // In case somebody managest to activate, deactivate and - // then activate gesture again, before asset finishes loading. - // LLLoadInfo will have a different pointer, asset storage will - // see it as a different request, resulting in two callbacks. - - // deactivateSimilarGestures() did not turn this one off - // because of matching item_id - self.stopGesture(old_gesture); - - self.mActive.erase(item_id); - delete old_gesture; - old_gesture = NULL; - } - } - - self.mActive[item_id] = gesture; - - // Everything has been successful. Add to the active list. - gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id); - - if (inform_server) - { - // Inform the database of this change - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("ActivateGestures"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->addU32("Flags", 0x0); - - msg->nextBlock("Data"); - msg->addUUID("ItemID", item_id); - msg->addUUID("AssetID", asset_uuid); - msg->addU32("GestureFlags", 0x0); - - gAgent.sendReliableMessage(); - } - callback_map_t::iterator i_cb = self.mCallbackMap.find(item_id); - - if(i_cb != self.mCallbackMap.end()) - { - i_cb->second(gesture); - self.mCallbackMap.erase(i_cb); - } - - self.notifyObservers(); - } - else - { - LL_WARNS("GestureMgr") << "Unable to load gesture" << LL_ENDL; - - item_map_t::iterator it = self.mActive.find(item_id); - if (it != self.mActive.end()) - { - LLMultiGesture* old_gesture = (*it).second; - if (old_gesture) - { - // Shouldn't happen, just in case - LL_WARNS("GestureMgr") << "Gesture " << item_id << " existed when it shouldn't" << LL_ENDL; - - self.stopGesture(old_gesture); - delete old_gesture; - old_gesture = NULL; - } - self.mActive.erase(item_id); - } - - delete gesture; - gesture = NULL; - } - } - else - { - if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status || - LL_ERR_FILE_EMPTY == status) - { - LLDelayedGestureError::gestureMissing( item_id ); - } - else - { - LLDelayedGestureError::gestureFailedToLoad( item_id ); - } - - LL_WARNS("GestureMgr") << "Problem loading gesture: " << status << LL_ENDL; - - item_map_t::iterator it = self.mActive.find(item_id); - if (it != self.mActive.end()) - { - LLMultiGesture* old_gesture = (*it).second; - if (old_gesture) - { - // Shouldn't happen, just in case - LL_WARNS("GestureMgr") << "Gesture " << item_id << " existed when it shouldn't" << LL_ENDL; - - self.stopGesture(old_gesture); - delete old_gesture; - old_gesture = NULL; - } - self.mActive.erase(item_id); - } - } -} - -// static -void LLGestureMgr::onAssetLoadComplete(const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status) -{ - LLGestureMgr& self = LLGestureMgr::instance(); - - // Complete the asset loading process depending on the type and - // remove the asset id from pending downloads list. - switch(type) - { - case LLAssetType::AT_ANIMATION: - { - LLKeyframeMotion::onLoadComplete(asset_uuid, type, user_data, status, ext_status); - - self.mLoadingAssets.erase(asset_uuid); - - break; - } - case LLAssetType::AT_SOUND: - { - LLAudioEngine::assetCallback(asset_uuid, type, user_data, status, ext_status); - - self.mLoadingAssets.erase(asset_uuid); - - break; - } - default: - { - LL_WARNS() << "Unexpected asset type: " << type << LL_ENDL; - - // We don't want to return from this callback without - // an animation or sound callback being fired - // and *user_data handled to avoid memory leaks. - llassert(type == LLAssetType::AT_ANIMATION || type == LLAssetType::AT_SOUND); - } - } -} - -// static -bool LLGestureMgr::hasLoadingAssets(LLMultiGesture* gesture) -{ - LLGestureMgr& self = LLGestureMgr::instance(); - - for (std::vector::iterator steps_it = gesture->mSteps.begin(); - steps_it != gesture->mSteps.end(); - ++steps_it) - { - LLGestureStep* step = *steps_it; - switch(step->getType()) - { - case STEP_ANIMATION: - { - LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step; - const LLUUID& anim_id = anim_step->mAnimAssetID; - - if (!(anim_id.isNull() - || anim_step->mFlags & ANIM_FLAG_STOP - || self.mLoadingAssets.find(anim_id) == self.mLoadingAssets.end())) - { - return true; - } - break; - } - case STEP_SOUND: - { - LLGestureStepSound* sound_step = (LLGestureStepSound*)step; - const LLUUID& sound_id = sound_step->mSoundAssetID; - - if (!(sound_id.isNull() - || self.mLoadingAssets.find(sound_id) == self.mLoadingAssets.end())) - { - return true; - } - break; - } - case STEP_CHAT: - case STEP_WAIT: - case STEP_EOF: - { - break; - } - default: - { - LL_WARNS() << "Unknown gesture step type: " << step->getType() << LL_ENDL; - } - } - } - - return false; -} - -void LLGestureMgr::stopGesture(LLMultiGesture* gesture) -{ - if (!gesture) return; - - // Stop any animations that this gesture is currently playing - std::set::const_iterator set_it; - for (set_it = gesture->mRequestedAnimIDs.begin(); set_it != gesture->mRequestedAnimIDs.end(); ++set_it) - { - const LLUUID& anim_id = *set_it; - gAgent.sendAnimationRequest(anim_id, ANIM_REQUEST_STOP); - } - for (set_it = gesture->mPlayingAnimIDs.begin(); set_it != gesture->mPlayingAnimIDs.end(); ++set_it) - { - const LLUUID& anim_id = *set_it; - gAgent.sendAnimationRequest(anim_id, ANIM_REQUEST_STOP); - } - - std::vector::iterator it; - it = std::find(mPlaying.begin(), mPlaying.end(), gesture); - while (it != mPlaying.end()) - { - mPlaying.erase(it); - it = std::find(mPlaying.begin(), mPlaying.end(), gesture); - } - - gesture->reset(); - - if (gesture->mDoneCallback) - { - gesture->mDoneCallback(gesture, gesture->mCallbackData); - - // callback might have deleted gesture, can't - // rely on this pointer any more - gesture = NULL; - } - - notifyObservers(); -} - - -void LLGestureMgr::stopGesture(const LLUUID& item_id) -{ - const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id); - - item_map_t::iterator it = mActive.find(base_item_id); - if (it == mActive.end()) return; - - LLMultiGesture* gesture = (*it).second; - if (!gesture) return; - - stopGesture(gesture); -} - - -void LLGestureMgr::addObserver(LLGestureManagerObserver* observer) -{ - mObservers.push_back(observer); -} - -void LLGestureMgr::removeObserver(LLGestureManagerObserver* observer) -{ - std::vector::iterator it; - it = std::find(mObservers.begin(), mObservers.end(), observer); - if (it != mObservers.end()) - { - mObservers.erase(it); - } -} - -// Call this method when it's time to update everyone on a new state. -// Copy the list because an observer could respond by removing itself -// from the list. -void LLGestureMgr::notifyObservers() -{ - LL_DEBUGS() << "LLGestureMgr::notifyObservers" << LL_ENDL; - - for(std::vector::iterator iter = mObservers.begin(); - iter != mObservers.end(); - ++iter) - { - LLGestureManagerObserver* observer = (*iter); - observer->changed(); - } -} - -bool LLGestureMgr::matchPrefix(const std::string& in_str, std::string* out_str) -{ - S32 in_len = in_str.length(); - - //return whole trigger, if received text equals to it - item_map_t::iterator it; - for (it = mActive.begin(); it != mActive.end(); ++it) - { - LLMultiGesture* gesture = (*it).second; - if (gesture) - { - const std::string& trigger = gesture->getTrigger(); - if (!LLStringUtil::compareInsensitive(in_str, trigger)) - { - *out_str = trigger; - return true; - } - } - } - - //return common chars, if more than one trigger matches the prefix - std::string rest_of_match = ""; - std::string buf = ""; - for (it = mActive.begin(); it != mActive.end(); ++it) - { - LLMultiGesture* gesture = (*it).second; - if (gesture) - { - const std::string& trigger = gesture->getTrigger(); - - if (in_len > (S32)trigger.length()) - { - // too short, bail out - continue; - } - - std::string trigger_trunc = trigger; - LLStringUtil::truncate(trigger_trunc, in_len); - if (!LLStringUtil::compareInsensitive(in_str, trigger_trunc)) - { - if (rest_of_match.compare("") == 0) - { - rest_of_match = trigger.substr(in_str.size()); - } - std::string cur_rest_of_match = trigger.substr(in_str.size()); - buf = ""; - S32 i=0; - - while (ipush_back(it->first); - } -} - -void LLGestureMgr::done() -{ - bool notify = false; - for(item_map_t::iterator it = mActive.begin(); it != mActive.end(); ++it) - { - if(it->second && it->second->mName.empty()) - { - LLViewerInventoryItem* item = gInventory.getItem(it->first); - if(item) - { - it->second->mName = item->getName(); - notify = true; - } - } - } - if(notify) - { - notifyObservers(); - } -} - - +/** + * @file llgesturemgr.cpp + * @brief Manager for playing gestures on the viewer + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llgesturemgr.h" + +// system +#include +#include + +// library +#include "llaudioengine.h" +#include "lldatapacker.h" +#include "llfloaterreg.h" +#include "llinventory.h" +#include "llkeyframemotion.h" +#include "llmultigesture.h" +#include "llnotificationsutil.h" +#include "llstl.h" +#include "llstring.h" // todo: remove +#include "llfilesystem.h" +#include "message.h" + +// newview +#include "llagent.h" +#include "lldelayedgestureerror.h" +#include "llinventorymodel.h" +#include "llviewermessage.h" +#include "llvoavatarself.h" +#include "llviewerstats.h" +#include "llfloaterimnearbychat.h" +#include "llappearancemgr.h" +#include "llgesturelistener.h" + +// Longest time, in seconds, to wait for all animations to stop playing +const F32 MAX_WAIT_ANIM_SECS = 30.f; +// Longest time, in seconds, to wait for a key release. +// This should be relatively long, but not too long. 10 minutes is enough +const F32 MAX_WAIT_KEY_SECS = 60.f * 10.f; + +// Lightweight constructor. +// init() does the heavy lifting. +LLGestureMgr::LLGestureMgr() +: mValid(false), + mPlaying(), + mActive(), + mLoadingCount(0) +{ + gInventory.addObserver(this); + mListener.reset(new LLGestureListener()); +} + + +// We own the data for gestures, so clean them up. +LLGestureMgr::~LLGestureMgr() +{ + item_map_t::iterator it; + for (it = mActive.begin(); it != mActive.end(); ++it) + { + LLMultiGesture* gesture = (*it).second; + + delete gesture; + gesture = NULL; + } + gInventory.removeObserver(this); +} + + +void LLGestureMgr::init() +{ + // TODO +} + +void LLGestureMgr::changed(U32 mask) +{ + LLInventoryFetchItemsObserver::changed(mask); + + if (mask & LLInventoryObserver::GESTURE) + { + // If there was a gesture label changed, update all the names in the + // active gestures and then notify observers + if (mask & LLInventoryObserver::LABEL) + { + for(item_map_t::iterator it = mActive.begin(); it != mActive.end(); ++it) + { + if(it->second) + { + LLViewerInventoryItem* item = gInventory.getItem(it->first); + if(item) + { + it->second->mName = item->getName(); + } + } + } + notifyObservers(); + } + // If there was a gesture added or removed notify observers + // STRUCTURE denotes that the inventory item has been moved + // In the case of deleting gesture, it is moved to the trash + else if(mask & LLInventoryObserver::ADD || + mask & LLInventoryObserver::REMOVE || + mask & LLInventoryObserver::STRUCTURE) + { + notifyObservers(); + } + } +} + + +// Use this version when you have the item_id but not the asset_id, +// and you KNOW the inventory is loaded. +void LLGestureMgr::activateGesture(const LLUUID& item_id) +{ + LLViewerInventoryItem* item = gInventory.getItem(item_id); + if (!item) return; + if (item->getType() != LLAssetType::AT_GESTURE) + return; + + LLUUID asset_id = item->getAssetUUID(); + + mLoadingCount = 1; + mDeactivateSimilarNames.clear(); + + const bool inform_server = true; + const bool deactivate_similar = false; + activateGestureWithAsset(item_id, asset_id, inform_server, deactivate_similar); +} + + +void LLGestureMgr::activateGestures(LLViewerInventoryItem::item_array_t& items) +{ + // Load up the assets + S32 count = 0; + LLViewerInventoryItem::item_array_t::const_iterator it; + for (it = items.begin(); it != items.end(); ++it) + { + LLViewerInventoryItem* item = *it; + + if (isGestureActive(item->getUUID())) + { + continue; + } + else + { // Make gesture active and persistent through login sessions. -Aura 07-12-06 + activateGesture(item->getUUID()); + } + + count++; + } + + mLoadingCount = count; + mDeactivateSimilarNames.clear(); + + for (it = items.begin(); it != items.end(); ++it) + { + LLViewerInventoryItem* item = *it; + + if (isGestureActive(item->getUUID())) + { + continue; + } + + // Don't inform server, we'll do that in bulk + const bool no_inform_server = false; + const bool deactivate_similar = true; + activateGestureWithAsset(item->getUUID(), item->getAssetUUID(), + no_inform_server, + deactivate_similar); + } + + // Inform the database of this change + LLMessageSystem* msg = gMessageSystem; + + bool start_message = true; + + for (it = items.begin(); it != items.end(); ++it) + { + LLViewerInventoryItem* item = *it; + + if (isGestureActive(item->getUUID())) + { + continue; + } + + if (start_message) + { + msg->newMessage("ActivateGestures"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->addU32("Flags", 0x0); + start_message = false; + } + + msg->nextBlock("Data"); + msg->addUUID("ItemID", item->getUUID()); + msg->addUUID("AssetID", item->getAssetUUID()); + msg->addU32("GestureFlags", 0x0); + + if (msg->getCurrentSendTotal() > MTUBYTES) + { + gAgent.sendReliableMessage(); + start_message = true; + } + } + + if (!start_message) + { + gAgent.sendReliableMessage(); + } +} + + +struct LLLoadInfo +{ + LLUUID mItemID; + bool mInformServer; + bool mDeactivateSimilar; +}; + +// If inform_server is true, will send a message upstream to update +// the user_gesture_active table. +/** + * It will load a gesture from remote storage + */ +void LLGestureMgr::activateGestureWithAsset(const LLUUID& item_id, + const LLUUID& asset_id, + bool inform_server, + bool deactivate_similar) +{ + const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id); + + if( !gAssetStorage ) + { + LL_WARNS() << "LLGestureMgr::activateGestureWithAsset without valid gAssetStorage" << LL_ENDL; + return; + } + // If gesture is already active, nothing to do. + if (isGestureActive(item_id)) + { + LL_WARNS() << "Tried to loadGesture twice " << item_id << LL_ENDL; + return; + } + +// if (asset_id.isNull()) +// { +// LL_WARNS() << "loadGesture() - gesture has no asset" << LL_ENDL; +// return; +// } + + // For now, put NULL into the item map. We'll build a gesture + // class object when the asset data arrives. + mActive[base_item_id] = NULL; + + // Copy the UUID + if (asset_id.notNull()) + { + LLLoadInfo* info = new LLLoadInfo; + info->mItemID = base_item_id; + info->mInformServer = inform_server; + info->mDeactivateSimilar = deactivate_similar; + + const bool high_priority = true; + gAssetStorage->getAssetData(asset_id, + LLAssetType::AT_GESTURE, + onLoadComplete, + (void*)info, + high_priority); + } + else + { + notifyObservers(); + } +} + + +void notify_update_label(const LLUUID& base_item_id) +{ + gInventory.addChangedMask(LLInventoryObserver::LABEL, base_item_id); + LLGestureMgr::instance().notifyObservers(); +} + +void LLGestureMgr::deactivateGesture(const LLUUID& item_id) +{ + const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id); + item_map_t::iterator it = mActive.find(base_item_id); + if (it == mActive.end()) + { + LL_WARNS() << "deactivateGesture for inactive gesture " << item_id << LL_ENDL; + return; + } + + // mActive owns this gesture pointer, so clean up memory. + LLMultiGesture* gesture = (*it).second; + + // Can be NULL gestures in the map + if (gesture) + { + stopGesture(gesture); + + delete gesture; + gesture = NULL; + } + + mActive.erase(it); + + // Inform the database of this change + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("DeactivateGestures"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->addU32("Flags", 0x0); + + msg->nextBlock("Data"); + msg->addUUID("ItemID", item_id); + msg->addU32("GestureFlags", 0x0); + + gAgent.sendReliableMessage(); + + LLPointer cb = + new LLBoostFuncInventoryCallback(no_op_inventory_func, + boost::bind(notify_update_label,base_item_id)); + + LLAppearanceMgr::instance().removeCOFItemLinks(base_item_id, cb); +} + + +void LLGestureMgr::deactivateSimilarGestures(LLMultiGesture* in, const LLUUID& in_item_id) +{ + const LLUUID& base_in_item_id = gInventory.getLinkedItemID(in_item_id); + uuid_vec_t gest_item_ids; + + // Deactivate all gestures that match + item_map_t::iterator it; + for (it = mActive.begin(); it != mActive.end(); ) + { + const LLUUID& item_id = (*it).first; + LLMultiGesture* gest = (*it).second; + + // Don't deactivate the gesture we are looking for duplicates of + // (for replaceGesture) + if (!gest || item_id == base_in_item_id) + { + // legal, can have null pointers in list + ++it; + } + else if ((!gest->mTrigger.empty() && gest->mTrigger == in->mTrigger) + || (gest->mKey != KEY_NONE && gest->mKey == in->mKey && gest->mMask == in->mMask)) + { + gest_item_ids.push_back(item_id); + + stopGesture(gest); + + delete gest; + gest = NULL; + + mActive.erase(it++); + gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id); + + } + else + { + ++it; + } + } + + // Inform database of the change + LLMessageSystem* msg = gMessageSystem; + bool start_message = true; + uuid_vec_t::const_iterator vit = gest_item_ids.begin(); + while (vit != gest_item_ids.end()) + { + if (start_message) + { + msg->newMessage("DeactivateGestures"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->addU32("Flags", 0x0); + start_message = false; + } + + msg->nextBlock("Data"); + msg->addUUID("ItemID", *vit); + msg->addU32("GestureFlags", 0x0); + + if (msg->getCurrentSendTotal() > MTUBYTES) + { + gAgent.sendReliableMessage(); + start_message = true; + } + + ++vit; + } + + if (!start_message) + { + gAgent.sendReliableMessage(); + } + + // Add to the list of names for the user. + for (vit = gest_item_ids.begin(); vit != gest_item_ids.end(); ++vit) + { + LLViewerInventoryItem* item = gInventory.getItem(*vit); + if (!item) continue; + + mDeactivateSimilarNames.append(item->getName()); + mDeactivateSimilarNames.append("\n"); + } + + notifyObservers(); +} + + +bool LLGestureMgr::isGestureActive(const LLUUID& item_id) +{ + const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id); + item_map_t::iterator it = mActive.find(base_item_id); + return (it != mActive.end()); +} + + +bool LLGestureMgr::isGesturePlaying(const LLUUID& item_id) +{ + const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id); + + item_map_t::iterator it = mActive.find(base_item_id); + if (it == mActive.end()) return false; + + LLMultiGesture* gesture = (*it).second; + if (!gesture) return false; + + return gesture->mPlaying; +} + +bool LLGestureMgr::isGesturePlaying(LLMultiGesture* gesture) +{ + if(!gesture) + { + return false; + } + + return gesture->mPlaying; +} + +void LLGestureMgr::replaceGesture(const LLUUID& item_id, LLMultiGesture* new_gesture, const LLUUID& asset_id) +{ + const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id); + + item_map_t::iterator it = mActive.find(base_item_id); + if (it == mActive.end()) + { + LL_WARNS() << "replaceGesture for inactive gesture " << base_item_id << LL_ENDL; + return; + } + + LLMultiGesture* old_gesture = (*it).second; + stopGesture(old_gesture); + + mActive.erase(base_item_id); + + mActive[base_item_id] = new_gesture; + + // replaceGesture(const LLUUID& item_id, const LLUUID& new_asset_id) + // replaces ids without repalcing gesture + if (old_gesture != new_gesture) + { + delete old_gesture; + old_gesture = NULL; + } + + if (asset_id.notNull()) + { + mLoadingCount = 1; + mDeactivateSimilarNames.clear(); + + LLLoadInfo* info = new LLLoadInfo; + info->mItemID = base_item_id; + info->mInformServer = true; + info->mDeactivateSimilar = false; + + const bool high_priority = true; + gAssetStorage->getAssetData(asset_id, + LLAssetType::AT_GESTURE, + onLoadComplete, + (void*)info, + high_priority); + } + + notifyObservers(); +} + +void LLGestureMgr::replaceGesture(const LLUUID& item_id, const LLUUID& new_asset_id) +{ + const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id); + + item_map_t::iterator it = LLGestureMgr::instance().mActive.find(base_item_id); + if (it == mActive.end()) + { + LL_WARNS() << "replaceGesture for inactive gesture " << base_item_id << LL_ENDL; + return; + } + + // mActive owns this gesture pointer, so clean up memory. + LLMultiGesture* gesture = (*it).second; + LLGestureMgr::instance().replaceGesture(base_item_id, gesture, new_asset_id); +} + +void LLGestureMgr::playGesture(LLMultiGesture* gesture, bool fromKeyPress) +{ + if (!gesture) return; + + // Reset gesture to first step + gesture->mCurrentStep = 0; + gesture->mTriggeredByKey = fromKeyPress; + + // Add to list of playing + gesture->mPlaying = true; + mPlaying.push_back(gesture); + + // Load all needed assets to minimize the delays + // when gesture is playing. + for (std::vector::iterator steps_it = gesture->mSteps.begin(); + steps_it != gesture->mSteps.end(); + ++steps_it) + { + LLGestureStep* step = *steps_it; + switch(step->getType()) + { + case STEP_ANIMATION: + { + LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step; + const LLUUID& anim_id = anim_step->mAnimAssetID; + + // Don't request the animation if this step stops it or if it is already in the cache + if (!(anim_id.isNull() + || anim_step->mFlags & ANIM_FLAG_STOP + || gAssetStorage->hasLocalAsset(anim_id, LLAssetType::AT_ANIMATION))) + { + mLoadingAssets.insert(anim_id); + + LLUUID* id = new LLUUID(gAgentID); + gAssetStorage->getAssetData(anim_id, + LLAssetType::AT_ANIMATION, + onAssetLoadComplete, + (void *)id, + true); + } + break; + } + case STEP_SOUND: + { + LLGestureStepSound* sound_step = (LLGestureStepSound*)step; + const LLUUID& sound_id = sound_step->mSoundAssetID; + if (!(sound_id.isNull() + || gAssetStorage->hasLocalAsset(sound_id, LLAssetType::AT_SOUND))) + { + mLoadingAssets.insert(sound_id); + + gAssetStorage->getAssetData(sound_id, + LLAssetType::AT_SOUND, + onAssetLoadComplete, + NULL, + true); + } + break; + } + case STEP_CHAT: + case STEP_WAIT: + case STEP_EOF: + { + break; + } + default: + { + LL_WARNS() << "Unknown gesture step type: " << step->getType() << LL_ENDL; + } + } + } + + // And get it going + stepGesture(gesture); + + notifyObservers(); +} + + +// Convenience function that looks up the item_id for you. +void LLGestureMgr::playGesture(const LLUUID& item_id) +{ + const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id); + + item_map_t::iterator it = mActive.find(base_item_id); + if (it == mActive.end()) return; + + LLMultiGesture* gesture = (*it).second; + if (!gesture) return; + + playGesture(gesture); +} + + +// Iterates through space delimited tokens in string, triggering any gestures found. +// Generates a revised string that has the found tokens replaced by their replacement strings +// and (as a minor side effect) has multiple spaces in a row replaced by single spaces. +bool LLGestureMgr::triggerAndReviseString(const std::string &utf8str, std::string* revised_string) +{ + std::string tokenized = utf8str; + + bool found_gestures = false; + bool first_token = true; + + typedef boost::tokenizer > tokenizer; + boost::char_separator sep(" "); + tokenizer tokens(tokenized, sep); + tokenizer::iterator token_iter; + + for( token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) + { + const char* cur_token = token_iter->c_str(); + LLMultiGesture* gesture = NULL; + + // Only pay attention to the first gesture in the string. + if( !found_gestures ) + { + // collect gestures that match + std::vector matching; + item_map_t::iterator it; + for (it = mActive.begin(); it != mActive.end(); ++it) + { + gesture = (*it).second; + + // Gesture asset data might not have arrived yet + if (!gesture) continue; + + if (LLStringUtil::compareInsensitive(gesture->mTrigger, cur_token) == 0) + { + matching.push_back(gesture); + } + + gesture = NULL; + } + + + if (matching.size() > 0) + { + // choose one at random + { + S32 random = ll_rand(matching.size()); + + gesture = matching[random]; + + playGesture(gesture); + + if (!gesture->mReplaceText.empty()) + { + if( !first_token ) + { + if (revised_string) + revised_string->append( " " ); + } + + // Don't muck with the user's capitalization if we don't have to. + if( LLStringUtil::compareInsensitive(cur_token, gesture->mReplaceText) == 0) + { + if (revised_string) + revised_string->append( cur_token ); + } + else + { + if (revised_string) + revised_string->append( gesture->mReplaceText ); + } + } + found_gestures = true; + } + } + } + + if(!gesture) + { + // This token doesn't match a gesture. Pass it through to the output. + if( !first_token ) + { + if (revised_string) + revised_string->append( " " ); + } + if (revised_string) + revised_string->append( cur_token ); + } + + first_token = false; + gesture = NULL; + } + return found_gestures; +} + + +bool LLGestureMgr::triggerGesture(KEY key, MASK mask) +{ + std::vector matching; + item_map_t::iterator it; + + // collect matching gestures + for (it = mActive.begin(); it != mActive.end(); ++it) + { + LLMultiGesture* gesture = (*it).second; + + // asset data might not have arrived yet + if (!gesture) continue; + + if (gesture->mKey == key + && gesture->mMask == mask + && gesture->mWaitingKeyRelease == false) + { + matching.push_back(gesture); + } + } + + // choose one and play it + if (matching.size() > 0) + { + U32 random = ll_rand(matching.size()); + + LLMultiGesture* gesture = matching[random]; + + playGesture(gesture, true); + return true; + } + return false; +} + + +bool LLGestureMgr::triggerGestureRelease(KEY key, MASK mask) +{ + std::vector matching; + item_map_t::iterator it; + + // collect matching gestures + for (it = mActive.begin(); it != mActive.end(); ++it) + { + LLMultiGesture* gesture = (*it).second; + + // asset data might not have arrived yet + if (!gesture) continue; + + if (gesture->mKey == key + && gesture->mMask == mask) + { + gesture->mKeyReleased = true; + } + } + + //If we found one, block. Otherwise tell them it's free to go. + return matching.size() > 0; +} + + +S32 LLGestureMgr::getPlayingCount() const +{ + return mPlaying.size(); +} + + +struct IsGesturePlaying +{ + bool operator()(const LLMultiGesture* gesture) const + { + return bool(gesture->mPlaying); + } +}; + +void LLGestureMgr::update() +{ + S32 i; + for (i = 0; i < (S32)mPlaying.size(); ++i) + { + stepGesture(mPlaying[i]); + } + + // Clear out gestures that are done, by moving all the + // ones that are still playing to the front. + std::vector::iterator new_end; + new_end = std::partition(mPlaying.begin(), + mPlaying.end(), + IsGesturePlaying()); + + // Something finished playing + if (new_end != mPlaying.end()) + { + // Delete the completed gestures that want deletion + std::vector::iterator it; + for (it = new_end; it != mPlaying.end(); ++it) + { + LLMultiGesture* gesture = *it; + + if (gesture->mDoneCallback) + { + gesture->mDoneCallback(gesture, gesture->mCallbackData); + + // callback might have deleted gesture, can't + // rely on this pointer any more + gesture = NULL; + } + } + + // And take done gestures out of the playing list + mPlaying.erase(new_end, mPlaying.end()); + + notifyObservers(); + } +} + + +// Run all steps until you're either done or hit a wait. +void LLGestureMgr::stepGesture(LLMultiGesture* gesture) +{ + if (!gesture) + { + return; + } + if (!isAgentAvatarValid() || hasLoadingAssets(gesture)) return; + + // Of the ones that started playing, have any stopped? + + std::set::iterator gest_it; + for (gest_it = gesture->mPlayingAnimIDs.begin(); + gest_it != gesture->mPlayingAnimIDs.end(); + ) + { + // look in signaled animations (simulator's view of what is + // currently playing. + LLVOAvatar::AnimIterator play_it = gAgentAvatarp->mSignaledAnimations.find(*gest_it); + if (play_it != gAgentAvatarp->mSignaledAnimations.end()) + { + ++gest_it; + } + else + { + // not found, so not currently playing or scheduled to play + // delete from the triggered set + gesture->mPlayingAnimIDs.erase(gest_it++); + } + } + + // Of all the animations that we asked the sim to start for us, + // pick up the ones that have actually started. + for (gest_it = gesture->mRequestedAnimIDs.begin(); + gest_it != gesture->mRequestedAnimIDs.end(); + ) + { + LLVOAvatar::AnimIterator play_it = gAgentAvatarp->mSignaledAnimations.find(*gest_it); + if (play_it != gAgentAvatarp->mSignaledAnimations.end()) + { + // Hooray, this animation has started playing! + // Copy into playing. + gesture->mPlayingAnimIDs.insert(*gest_it); + gesture->mRequestedAnimIDs.erase(gest_it++); + } + else + { + // nope, not playing yet + ++gest_it; + } + } + + // Run the current steps + bool waiting = false; + while (!waiting && gesture->mPlaying) + { + // Get the current step, if there is one. + // Otherwise enter the waiting at end state. + LLGestureStep* step = NULL; + if (gesture->mCurrentStep < (S32)gesture->mSteps.size()) + { + step = gesture->mSteps[gesture->mCurrentStep]; + llassert(step != NULL); + } + else + { + // step stays null, we're off the end + gesture->mWaitingAtEnd = true; + } + + + // If we're waiting at the end, wait for all gestures to stop + // playing. + // TODO: Wait for all sounds to complete as well. + if (gesture->mWaitingAtEnd) + { + // Neither do we have any pending requests, nor are they + // still playing. + if ((gesture->mRequestedAnimIDs.empty() + && gesture->mPlayingAnimIDs.empty())) + { + // all animations are done playing + gesture->mWaitingAtEnd = false; + gesture->mPlaying = false; + } + else + { + waiting = true; + } + continue; + } + + // If we're waiting a fixed amount of time, check for timer + // expiration. + if (gesture->mWaitingKeyRelease) + { + // We're waiting for a certain amount of time to pass + if (gesture->mKeyReleased) + { + // wait is done, continue execution + gesture->mWaitingKeyRelease = false; + gesture->mCurrentStep++; + } + else if (gesture->mWaitTimer.getElapsedTimeF32() > MAX_WAIT_KEY_SECS) + { + LL_INFOS("GestureMgr") << "Waited too long for key release, continuing gesture." + << LL_ENDL; + gesture->mWaitingKeyRelease = false; + gesture->mCurrentStep++; + } + else + { + // we're waiting, so execution is done for now + waiting = true; + } + continue; + } + + // If we're waiting on our animations to stop, poll for + // completion. + if (gesture->mWaitingAnimations) + { + // Neither do we have any pending requests, nor are they + // still playing. + if ((gesture->mRequestedAnimIDs.empty() + && gesture->mPlayingAnimIDs.empty())) + { + // all animations are done playing + gesture->mWaitingAnimations = false; + gesture->mCurrentStep++; + } + else if (gesture->mWaitTimer.getElapsedTimeF32() > MAX_WAIT_ANIM_SECS) + { + // we've waited too long for an animation + LL_INFOS("GestureMgr") << "Waited too long for animations to stop, continuing gesture." + << LL_ENDL; + gesture->mWaitingAnimations = false; + gesture->mCurrentStep++; + } + else + { + waiting = true; + } + continue; + } + + // If we're waiting a fixed amount of time, check for timer + // expiration. + if (gesture->mWaitingTimer) + { + // We're waiting for a certain amount of time to pass + LLGestureStepWait* wait_step = (LLGestureStepWait*)step; + + F32 elapsed = gesture->mWaitTimer.getElapsedTimeF32(); + if (elapsed > wait_step->mWaitSeconds) + { + // wait is done, continue execution + gesture->mWaitingTimer = false; + gesture->mCurrentStep++; + } + else + { + // we're waiting, so execution is done for now + waiting = true; + } + continue; + } + + // Not waiting, do normal execution + runStep(gesture, step); + } +} + + +void LLGestureMgr::runStep(LLMultiGesture* gesture, LLGestureStep* step) +{ + switch(step->getType()) + { + case STEP_ANIMATION: + { + LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step; + if (anim_step->mAnimAssetID.isNull()) + { + gesture->mCurrentStep++; + } + + if (anim_step->mFlags & ANIM_FLAG_STOP) + { + gAgent.sendAnimationRequest(anim_step->mAnimAssetID, ANIM_REQUEST_STOP); + // remove it from our request set in case we just requested it + std::set::iterator set_it = gesture->mRequestedAnimIDs.find(anim_step->mAnimAssetID); + if (set_it != gesture->mRequestedAnimIDs.end()) + { + gesture->mRequestedAnimIDs.erase(set_it); + } + } + else + { + gAgent.sendAnimationRequest(anim_step->mAnimAssetID, ANIM_REQUEST_START); + // Indicate that we've requested this animation to play as + // part of this gesture (but it won't start playing for at + // least one round-trip to simulator). + gesture->mRequestedAnimIDs.insert(anim_step->mAnimAssetID); + } + gesture->mCurrentStep++; + break; + } + case STEP_SOUND: + { + LLGestureStepSound* sound_step = (LLGestureStepSound*)step; + const LLUUID& sound_id = sound_step->mSoundAssetID; + const F32 volume = 1.f; + send_sound_trigger(sound_id, volume); + gesture->mCurrentStep++; + break; + } + case STEP_CHAT: + { + LLGestureStepChat* chat_step = (LLGestureStepChat*)step; + std::string chat_text = chat_step->mChatText; + // Don't animate the nodding, as this might not blend with + // other playing animations. + + const bool animate = false; + + (LLFloaterReg::getTypedInstance("nearby_chat"))-> + sendChatFromViewer(chat_text, CHAT_TYPE_NORMAL, animate); + + gesture->mCurrentStep++; + break; + } + case STEP_WAIT: + { + LLGestureStepWait* wait_step = (LLGestureStepWait*)step; + if (gesture->mTriggeredByKey // Only wait here IF we were triggered by a key! + && gesture->mWaitingKeyRelease == false // We can only do this once! Prevent gestures infinitely running + && wait_step->mFlags & WAIT_FLAG_KEY_RELEASE) + { + // Lets wait for the key release first so we don't hold up re-presses + gesture->mWaitingKeyRelease = true; + gesture->mKeyReleased = false; + // Use the wait timer as a deadlock breaker for key release waits. + gesture->mWaitTimer.reset(); + } + else if (wait_step->mFlags & WAIT_FLAG_TIME) + { + gesture->mWaitingTimer = true; + gesture->mWaitTimer.reset(); + } + else if (wait_step->mFlags & WAIT_FLAG_ALL_ANIM) + { + gesture->mWaitingAnimations = true; + // Use the wait timer as a deadlock breaker for animation waits. + gesture->mWaitTimer.reset(); + } + else + { + gesture->mCurrentStep++; + } + // Don't increment instruction pointer until wait is complete. + break; + } + default: + { + break; + } + } +} + + +// static +void LLGestureMgr::onLoadComplete(const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status) +{ + LLLoadInfo* info = (LLLoadInfo*)user_data; + + LLUUID item_id = info->mItemID; + bool inform_server = info->mInformServer; + bool deactivate_similar = info->mDeactivateSimilar; + + delete info; + info = NULL; + LLGestureMgr& self = LLGestureMgr::instance(); + self.mLoadingCount--; + + if (0 == status) + { + LLFileSystem file(asset_uuid, type, LLFileSystem::READ); + S32 size = file.getSize(); + + std::vector buffer(size+1); + + file.read((U8*)&buffer[0], size); + // ensure there's a trailing NULL so strlen will work. + buffer[size] = '\0'; + + LLMultiGesture* gesture = new LLMultiGesture(); + + LLDataPackerAsciiBuffer dp(&buffer[0], size+1); + bool ok = gesture->deserialize(dp); + + if (ok) + { + if (deactivate_similar) + { + self.deactivateSimilarGestures(gesture, item_id); + + // Display deactivation message if this was the last of the bunch. + if (self.mLoadingCount == 0 + && self.mDeactivateSimilarNames.length() > 0) + { + // we're done with this set of deactivations + LLSD args; + args["NAMES"] = self.mDeactivateSimilarNames; + LLNotificationsUtil::add("DeactivatedGesturesTrigger", args); + } + } + + LLViewerInventoryItem* item = gInventory.getItem(item_id); + if(item) + { + gesture->mName = item->getName(); + } + else + { + // Watch this item and set gesture name when item exists in inventory + self.setFetchID(item_id); + self.startFetch(); + } + + item_map_t::iterator it = self.mActive.find(item_id); + if (it == self.mActive.end()) + { + // Gesture is supposed to be present, active, but NULL + LL_DEBUGS("GestureMgr") << "Gesture " << item_id << " not found in active list" << LL_ENDL; + } + else + { + LLMultiGesture* old_gesture = (*it).second; + if (old_gesture && old_gesture != gesture) + { + LL_DEBUGS("GestureMgr") << "Received dupplicate " << item_id << " callback" << LL_ENDL; + // In case somebody managest to activate, deactivate and + // then activate gesture again, before asset finishes loading. + // LLLoadInfo will have a different pointer, asset storage will + // see it as a different request, resulting in two callbacks. + + // deactivateSimilarGestures() did not turn this one off + // because of matching item_id + self.stopGesture(old_gesture); + + self.mActive.erase(item_id); + delete old_gesture; + old_gesture = NULL; + } + } + + self.mActive[item_id] = gesture; + + // Everything has been successful. Add to the active list. + gInventory.addChangedMask(LLInventoryObserver::LABEL, item_id); + + if (inform_server) + { + // Inform the database of this change + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("ActivateGestures"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->addU32("Flags", 0x0); + + msg->nextBlock("Data"); + msg->addUUID("ItemID", item_id); + msg->addUUID("AssetID", asset_uuid); + msg->addU32("GestureFlags", 0x0); + + gAgent.sendReliableMessage(); + } + callback_map_t::iterator i_cb = self.mCallbackMap.find(item_id); + + if(i_cb != self.mCallbackMap.end()) + { + i_cb->second(gesture); + self.mCallbackMap.erase(i_cb); + } + + self.notifyObservers(); + } + else + { + LL_WARNS("GestureMgr") << "Unable to load gesture" << LL_ENDL; + + item_map_t::iterator it = self.mActive.find(item_id); + if (it != self.mActive.end()) + { + LLMultiGesture* old_gesture = (*it).second; + if (old_gesture) + { + // Shouldn't happen, just in case + LL_WARNS("GestureMgr") << "Gesture " << item_id << " existed when it shouldn't" << LL_ENDL; + + self.stopGesture(old_gesture); + delete old_gesture; + old_gesture = NULL; + } + self.mActive.erase(item_id); + } + + delete gesture; + gesture = NULL; + } + } + else + { + if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status || + LL_ERR_FILE_EMPTY == status) + { + LLDelayedGestureError::gestureMissing( item_id ); + } + else + { + LLDelayedGestureError::gestureFailedToLoad( item_id ); + } + + LL_WARNS("GestureMgr") << "Problem loading gesture: " << status << LL_ENDL; + + item_map_t::iterator it = self.mActive.find(item_id); + if (it != self.mActive.end()) + { + LLMultiGesture* old_gesture = (*it).second; + if (old_gesture) + { + // Shouldn't happen, just in case + LL_WARNS("GestureMgr") << "Gesture " << item_id << " existed when it shouldn't" << LL_ENDL; + + self.stopGesture(old_gesture); + delete old_gesture; + old_gesture = NULL; + } + self.mActive.erase(item_id); + } + } +} + +// static +void LLGestureMgr::onAssetLoadComplete(const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status) +{ + LLGestureMgr& self = LLGestureMgr::instance(); + + // Complete the asset loading process depending on the type and + // remove the asset id from pending downloads list. + switch(type) + { + case LLAssetType::AT_ANIMATION: + { + LLKeyframeMotion::onLoadComplete(asset_uuid, type, user_data, status, ext_status); + + self.mLoadingAssets.erase(asset_uuid); + + break; + } + case LLAssetType::AT_SOUND: + { + LLAudioEngine::assetCallback(asset_uuid, type, user_data, status, ext_status); + + self.mLoadingAssets.erase(asset_uuid); + + break; + } + default: + { + LL_WARNS() << "Unexpected asset type: " << type << LL_ENDL; + + // We don't want to return from this callback without + // an animation or sound callback being fired + // and *user_data handled to avoid memory leaks. + llassert(type == LLAssetType::AT_ANIMATION || type == LLAssetType::AT_SOUND); + } + } +} + +// static +bool LLGestureMgr::hasLoadingAssets(LLMultiGesture* gesture) +{ + LLGestureMgr& self = LLGestureMgr::instance(); + + for (std::vector::iterator steps_it = gesture->mSteps.begin(); + steps_it != gesture->mSteps.end(); + ++steps_it) + { + LLGestureStep* step = *steps_it; + switch(step->getType()) + { + case STEP_ANIMATION: + { + LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step; + const LLUUID& anim_id = anim_step->mAnimAssetID; + + if (!(anim_id.isNull() + || anim_step->mFlags & ANIM_FLAG_STOP + || self.mLoadingAssets.find(anim_id) == self.mLoadingAssets.end())) + { + return true; + } + break; + } + case STEP_SOUND: + { + LLGestureStepSound* sound_step = (LLGestureStepSound*)step; + const LLUUID& sound_id = sound_step->mSoundAssetID; + + if (!(sound_id.isNull() + || self.mLoadingAssets.find(sound_id) == self.mLoadingAssets.end())) + { + return true; + } + break; + } + case STEP_CHAT: + case STEP_WAIT: + case STEP_EOF: + { + break; + } + default: + { + LL_WARNS() << "Unknown gesture step type: " << step->getType() << LL_ENDL; + } + } + } + + return false; +} + +void LLGestureMgr::stopGesture(LLMultiGesture* gesture) +{ + if (!gesture) return; + + // Stop any animations that this gesture is currently playing + std::set::const_iterator set_it; + for (set_it = gesture->mRequestedAnimIDs.begin(); set_it != gesture->mRequestedAnimIDs.end(); ++set_it) + { + const LLUUID& anim_id = *set_it; + gAgent.sendAnimationRequest(anim_id, ANIM_REQUEST_STOP); + } + for (set_it = gesture->mPlayingAnimIDs.begin(); set_it != gesture->mPlayingAnimIDs.end(); ++set_it) + { + const LLUUID& anim_id = *set_it; + gAgent.sendAnimationRequest(anim_id, ANIM_REQUEST_STOP); + } + + std::vector::iterator it; + it = std::find(mPlaying.begin(), mPlaying.end(), gesture); + while (it != mPlaying.end()) + { + mPlaying.erase(it); + it = std::find(mPlaying.begin(), mPlaying.end(), gesture); + } + + gesture->reset(); + + if (gesture->mDoneCallback) + { + gesture->mDoneCallback(gesture, gesture->mCallbackData); + + // callback might have deleted gesture, can't + // rely on this pointer any more + gesture = NULL; + } + + notifyObservers(); +} + + +void LLGestureMgr::stopGesture(const LLUUID& item_id) +{ + const LLUUID& base_item_id = gInventory.getLinkedItemID(item_id); + + item_map_t::iterator it = mActive.find(base_item_id); + if (it == mActive.end()) return; + + LLMultiGesture* gesture = (*it).second; + if (!gesture) return; + + stopGesture(gesture); +} + + +void LLGestureMgr::addObserver(LLGestureManagerObserver* observer) +{ + mObservers.push_back(observer); +} + +void LLGestureMgr::removeObserver(LLGestureManagerObserver* observer) +{ + std::vector::iterator it; + it = std::find(mObservers.begin(), mObservers.end(), observer); + if (it != mObservers.end()) + { + mObservers.erase(it); + } +} + +// Call this method when it's time to update everyone on a new state. +// Copy the list because an observer could respond by removing itself +// from the list. +void LLGestureMgr::notifyObservers() +{ + LL_DEBUGS() << "LLGestureMgr::notifyObservers" << LL_ENDL; + + for(std::vector::iterator iter = mObservers.begin(); + iter != mObservers.end(); + ++iter) + { + LLGestureManagerObserver* observer = (*iter); + observer->changed(); + } +} + +bool LLGestureMgr::matchPrefix(const std::string& in_str, std::string* out_str) +{ + S32 in_len = in_str.length(); + + //return whole trigger, if received text equals to it + item_map_t::iterator it; + for (it = mActive.begin(); it != mActive.end(); ++it) + { + LLMultiGesture* gesture = (*it).second; + if (gesture) + { + const std::string& trigger = gesture->getTrigger(); + if (!LLStringUtil::compareInsensitive(in_str, trigger)) + { + *out_str = trigger; + return true; + } + } + } + + //return common chars, if more than one trigger matches the prefix + std::string rest_of_match = ""; + std::string buf = ""; + for (it = mActive.begin(); it != mActive.end(); ++it) + { + LLMultiGesture* gesture = (*it).second; + if (gesture) + { + const std::string& trigger = gesture->getTrigger(); + + if (in_len > (S32)trigger.length()) + { + // too short, bail out + continue; + } + + std::string trigger_trunc = trigger; + LLStringUtil::truncate(trigger_trunc, in_len); + if (!LLStringUtil::compareInsensitive(in_str, trigger_trunc)) + { + if (rest_of_match.compare("") == 0) + { + rest_of_match = trigger.substr(in_str.size()); + } + std::string cur_rest_of_match = trigger.substr(in_str.size()); + buf = ""; + S32 i=0; + + while (ipush_back(it->first); + } +} + +void LLGestureMgr::done() +{ + bool notify = false; + for(item_map_t::iterator it = mActive.begin(); it != mActive.end(); ++it) + { + if(it->second && it->second->mName.empty()) + { + LLViewerInventoryItem* item = gInventory.getItem(it->first); + if(item) + { + it->second->mName = item->getName(); + notify = true; + } + } + } + if(notify) + { + notifyObservers(); + } +} + + diff --git a/indra/newview/llgesturemgr.h b/indra/newview/llgesturemgr.h index c00b829e3d..8db36c6d89 100644 --- a/indra/newview/llgesturemgr.h +++ b/indra/newview/llgesturemgr.h @@ -1,198 +1,198 @@ -/** - * @file llgesturemgr.h - * @brief Manager for playing gestures on the viewer - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLGESTUREMGR_H -#define LL_LLGESTUREMGR_H - -#include -#include -#include - -#include "llassetstorage.h" // LLAssetType -#include "llinventoryobserver.h" -#include "llsingleton.h" -#include "llviewerinventory.h" - -class LLMultiGesture; -class LLGestureListener; -class LLGestureStep; -class LLUUID; - -class LLGestureManagerObserver -{ -public: - virtual ~LLGestureManagerObserver() { }; - virtual void changed() = 0; -}; - -class LLGestureMgr : public LLSingleton, public LLInventoryFetchItemsObserver -{ - LLSINGLETON(LLGestureMgr); - ~LLGestureMgr(); -public: - - typedef boost::function gesture_loaded_callback_t; - // Maps inventory item_id to gesture - typedef std::map item_map_t; - typedef std::map callback_map_t; - - - void init(); - - // Call once per frame to manage gestures - void update(); - - // Loads a gesture out of inventory into the in-memory active form - // Note that the inventory item must exist, so we can look up the - // asset id. - void activateGesture(const LLUUID& item_id); - - // Activate a list of gestures - void activateGestures(LLViewerInventoryItem::item_array_t& items); - - // If you change a gesture, you need to build a new multigesture - // and call this method. - void replaceGesture(const LLUUID& item_id, LLMultiGesture* new_gesture, const LLUUID& asset_id); - void replaceGesture(const LLUUID& item_id, const LLUUID& asset_id); - - // Load gesture into in-memory active form. - // Can be called even if the inventory item isn't loaded yet. - // inform_server true will send message upstream to update database - // user_gesture_active table, which isn't necessary on login. - // deactivate_similar will cause other gestures with the same trigger phrase - // or keybinding to be deactivated. - void activateGestureWithAsset(const LLUUID& item_id, const LLUUID& asset_id, bool inform_server, bool deactivate_similar); - - // Takes gesture out of active list and deletes it. - void deactivateGesture(const LLUUID& item_id); - - // Deactivates all gestures that match either this trigger phrase, - // or this hot key. - void deactivateSimilarGestures(LLMultiGesture* gesture, const LLUUID& in_item_id); - - bool isGestureActive(const LLUUID& item_id); - - bool isGesturePlaying(const LLUUID& item_id); - - bool isGesturePlaying(LLMultiGesture* gesture); - - const item_map_t& getActiveGestures() const { return mActive; } - // Force a gesture to be played, for example, if it is being - // previewed. - void playGesture(LLMultiGesture* gesture, bool fromKeyPress); - void playGesture(LLMultiGesture* gesture) { - playGesture(gesture, false); - } - void playGesture(const LLUUID& item_id); - - // Stop all requested or playing anims for this gesture - // Also remove from playing list - void stopGesture(LLMultiGesture* gesture); - void stopGesture(const LLUUID& item_id); - /** - * Add cb into callbackMap. - * Note: - * Manager will call cb after gesture will be loaded and will remove cb automatically. - */ - void setGestureLoadedCallback(LLUUID inv_item_id, gesture_loaded_callback_t cb) - { - mCallbackMap[inv_item_id] = cb; - } - // Trigger a random gesture that matches this key. - // Returns true if it finds a gesture bound to that key. - bool triggerGesture(KEY key, MASK mask); - - // Trigger release wait on all gestures that matches this key. - // Returns true if it finds a gesture bound to that key. - bool triggerGestureRelease(KEY key, MASK mask); - - // Trigger all gestures referenced as substrings in this string - bool triggerAndReviseString(const std::string &str, std::string *revised_string = NULL); - - // Does some gesture have this key bound? - bool isKeyBound(KEY key, MASK mask); - - S32 getPlayingCount() const; - - void addObserver(LLGestureManagerObserver* observer); - void removeObserver(LLGestureManagerObserver* observer); - void notifyObservers(); - - // Overriding so we can update active gesture names and notify observers - void changed(U32 mask) override; - - bool matchPrefix(const std::string& in_str, std::string* out_str); - - // Copy item ids into the vector - void getItemIDs(uuid_vec_t* ids); - -protected: - // Handle the processing of a single gesture - void stepGesture(LLMultiGesture* gesture); - - // Do a single step in a gesture - void runStep(LLMultiGesture* gesture, LLGestureStep* step); - - // LLInventoryCompletionObserver trigger - void done() override; - - // Used by loadGesture - static void onLoadComplete(const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status); - - // Used by playGesture to load an asset file - // required to play a gesture step - static void onAssetLoadComplete(const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status); - - // Checks whether all animation and sound assets - // needed to play a gesture are loaded. - static bool hasLoadingAssets(LLMultiGesture* gesture); - -private: - // Active gestures. - // NOTE: The gesture pointer CAN BE NULL. This means that - // there is a gesture with that item_id, but the asset data - // is still on its way down from the server. - item_map_t mActive; - - S32 mLoadingCount; - std::string mDeactivateSimilarNames; - - std::vector mObservers; - callback_map_t mCallbackMap; - std::vector mPlaying; - bool mValid; - - std::set mLoadingAssets; - - // LLEventHost interface - std::shared_ptr mListener; -}; - -#endif +/** + * @file llgesturemgr.h + * @brief Manager for playing gestures on the viewer + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLGESTUREMGR_H +#define LL_LLGESTUREMGR_H + +#include +#include +#include + +#include "llassetstorage.h" // LLAssetType +#include "llinventoryobserver.h" +#include "llsingleton.h" +#include "llviewerinventory.h" + +class LLMultiGesture; +class LLGestureListener; +class LLGestureStep; +class LLUUID; + +class LLGestureManagerObserver +{ +public: + virtual ~LLGestureManagerObserver() { }; + virtual void changed() = 0; +}; + +class LLGestureMgr : public LLSingleton, public LLInventoryFetchItemsObserver +{ + LLSINGLETON(LLGestureMgr); + ~LLGestureMgr(); +public: + + typedef boost::function gesture_loaded_callback_t; + // Maps inventory item_id to gesture + typedef std::map item_map_t; + typedef std::map callback_map_t; + + + void init(); + + // Call once per frame to manage gestures + void update(); + + // Loads a gesture out of inventory into the in-memory active form + // Note that the inventory item must exist, so we can look up the + // asset id. + void activateGesture(const LLUUID& item_id); + + // Activate a list of gestures + void activateGestures(LLViewerInventoryItem::item_array_t& items); + + // If you change a gesture, you need to build a new multigesture + // and call this method. + void replaceGesture(const LLUUID& item_id, LLMultiGesture* new_gesture, const LLUUID& asset_id); + void replaceGesture(const LLUUID& item_id, const LLUUID& asset_id); + + // Load gesture into in-memory active form. + // Can be called even if the inventory item isn't loaded yet. + // inform_server true will send message upstream to update database + // user_gesture_active table, which isn't necessary on login. + // deactivate_similar will cause other gestures with the same trigger phrase + // or keybinding to be deactivated. + void activateGestureWithAsset(const LLUUID& item_id, const LLUUID& asset_id, bool inform_server, bool deactivate_similar); + + // Takes gesture out of active list and deletes it. + void deactivateGesture(const LLUUID& item_id); + + // Deactivates all gestures that match either this trigger phrase, + // or this hot key. + void deactivateSimilarGestures(LLMultiGesture* gesture, const LLUUID& in_item_id); + + bool isGestureActive(const LLUUID& item_id); + + bool isGesturePlaying(const LLUUID& item_id); + + bool isGesturePlaying(LLMultiGesture* gesture); + + const item_map_t& getActiveGestures() const { return mActive; } + // Force a gesture to be played, for example, if it is being + // previewed. + void playGesture(LLMultiGesture* gesture, bool fromKeyPress); + void playGesture(LLMultiGesture* gesture) { + playGesture(gesture, false); + } + void playGesture(const LLUUID& item_id); + + // Stop all requested or playing anims for this gesture + // Also remove from playing list + void stopGesture(LLMultiGesture* gesture); + void stopGesture(const LLUUID& item_id); + /** + * Add cb into callbackMap. + * Note: + * Manager will call cb after gesture will be loaded and will remove cb automatically. + */ + void setGestureLoadedCallback(LLUUID inv_item_id, gesture_loaded_callback_t cb) + { + mCallbackMap[inv_item_id] = cb; + } + // Trigger a random gesture that matches this key. + // Returns true if it finds a gesture bound to that key. + bool triggerGesture(KEY key, MASK mask); + + // Trigger release wait on all gestures that matches this key. + // Returns true if it finds a gesture bound to that key. + bool triggerGestureRelease(KEY key, MASK mask); + + // Trigger all gestures referenced as substrings in this string + bool triggerAndReviseString(const std::string &str, std::string *revised_string = NULL); + + // Does some gesture have this key bound? + bool isKeyBound(KEY key, MASK mask); + + S32 getPlayingCount() const; + + void addObserver(LLGestureManagerObserver* observer); + void removeObserver(LLGestureManagerObserver* observer); + void notifyObservers(); + + // Overriding so we can update active gesture names and notify observers + void changed(U32 mask) override; + + bool matchPrefix(const std::string& in_str, std::string* out_str); + + // Copy item ids into the vector + void getItemIDs(uuid_vec_t* ids); + +protected: + // Handle the processing of a single gesture + void stepGesture(LLMultiGesture* gesture); + + // Do a single step in a gesture + void runStep(LLMultiGesture* gesture, LLGestureStep* step); + + // LLInventoryCompletionObserver trigger + void done() override; + + // Used by loadGesture + static void onLoadComplete(const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status); + + // Used by playGesture to load an asset file + // required to play a gesture step + static void onAssetLoadComplete(const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status); + + // Checks whether all animation and sound assets + // needed to play a gesture are loaded. + static bool hasLoadingAssets(LLMultiGesture* gesture); + +private: + // Active gestures. + // NOTE: The gesture pointer CAN BE NULL. This means that + // there is a gesture with that item_id, but the asset data + // is still on its way down from the server. + item_map_t mActive; + + S32 mLoadingCount; + std::string mDeactivateSimilarNames; + + std::vector mObservers; + callback_map_t mCallbackMap; + std::vector mPlaying; + bool mValid; + + std::set mLoadingAssets; + + // LLEventHost interface + std::shared_ptr mListener; +}; + +#endif diff --git a/indra/newview/llgiveinventory.cpp b/indra/newview/llgiveinventory.cpp index 8f91e8cc18..c4e0e53bb5 100644 --- a/indra/newview/llgiveinventory.cpp +++ b/indra/newview/llgiveinventory.cpp @@ -1,585 +1,585 @@ -/** - * @file llgiveinventory.cpp - * @brief LLGiveInventory class implementation - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llgiveinventory.h" - -// library includes -#include "llnotificationsutil.h" -#include "lltrans.h" - -// newview includes -#include "llagent.h" -#include "llagentdata.h" -#include "llagentui.h" -#include "llagentwearables.h" -#include "llavatarnamecache.h" -#include "llfloatertools.h" // for gFloaterTool -#include "llhudeffecttrail.h" -#include "llhudmanager.h" -#include "llimview.h" -#include "llinventory.h" -#include "llinventoryfunctions.h" -#include "llmutelist.h" -#include "llrecentpeople.h" -#include "llviewerobjectlist.h" -#include "llvoavatarself.h" - -// MAX ITEMS is based on (sizeof(uuid)+2) * count must be < MTUBYTES -// or 18 * count < 1200 => count < 1200/18 => 66. I've cut it down a -// bit from there to give some pad. -const S32 MAX_ITEMS = 42; - -class LLGiveable : public LLInventoryCollectFunctor -{ -public: - LLGiveable() : mCountLosing(0) {} - virtual ~LLGiveable() {} - virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); - - S32 countNoCopy() const { return mCountLosing; } -protected: - S32 mCountLosing; -}; - -bool LLGiveable::operator()(LLInventoryCategory* cat, LLInventoryItem* item) -{ - // All categories can be given. - if (cat) - return true; - - bool allowed = false; - if (item) - { - allowed = itemTransferCommonlyAllowed(item); - if (allowed && - !item->getPermissions().allowOperationBy(PERM_TRANSFER, - gAgent.getID())) - { - allowed = false; - } - if (allowed && - !item->getPermissions().allowCopyBy(gAgent.getID())) - { - ++mCountLosing; - } - } - return allowed; -} - -class LLUncopyableItems : public LLInventoryCollectFunctor -{ -public: - LLUncopyableItems() {} - virtual ~LLUncopyableItems() {} - virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); -}; - -bool LLUncopyableItems::operator()(LLInventoryCategory* cat, - LLInventoryItem* item) -{ - bool uncopyable = false; - if (item) - { - if (itemTransferCommonlyAllowed(item) && - !item->getPermissions().allowCopyBy(gAgent.getID())) - { - uncopyable = true; - } - } - return uncopyable; -} - -// static -bool LLGiveInventory::isInventoryGiveAcceptable(const LLInventoryItem* item) -{ - if (!item) return false; - - if (!isAgentAvatarValid()) return false; - - if (!item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgentID)) - { - return false; - } - - bool acceptable = true; - switch(item->getType()) - { - case LLAssetType::AT_OBJECT: - case LLAssetType::AT_BODYPART: - case LLAssetType::AT_CLOTHING: - { - if (get_is_item_worn(item->getUUID())) - { - acceptable = false; - } - break; - } - break; - default: - break; - } - return acceptable; -} - -// static -bool LLGiveInventory::isInventoryGroupGiveAcceptable(const LLInventoryItem* item) -{ - if (!item) return false; - - if (!isAgentAvatarValid()) return false; - - // These permissions are double checked in the simulator in - // LLGroupNoticeInventoryItemFetch::result(). - if (!item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgentID)) - { - return false; - } - if (!item->getPermissions().allowCopyBy(gAgent.getID())) - { - return false; - } - - - bool acceptable = true; - switch(item->getType()) - { - case LLAssetType::AT_OBJECT: - if (gAgentAvatarp->isWearingAttachment(item->getUUID())) - { - acceptable = false; - } - break; - default: - break; - } - return acceptable; -} - -// static -bool LLGiveInventory::doGiveInventoryItem(const LLUUID& to_agent, - const LLInventoryItem* item, - const LLUUID& im_session_id/* = LLUUID::null*/) - -{ - bool res = true; - LL_INFOS() << "LLGiveInventory::giveInventory()" << LL_ENDL; - if (!isInventoryGiveAcceptable(item)) - { - return false; - } - if (item->getPermissions().allowCopyBy(gAgentID)) - { - // just give it away. - LLGiveInventory::commitGiveInventoryItem(to_agent, item, im_session_id); - } - else - { - // ask if the agent is sure. - LLSD substitutions; - substitutions["ITEMS"] = item->getName(); - LLSD payload; - payload["agent_id"] = to_agent; - LLSD items = LLSD::emptyArray(); - items.append(item->getUUID()); - payload["items"] = items; - LLNotificationsUtil::add("CannotCopyWarning", substitutions, payload, - &LLGiveInventory::handleCopyProtectedItem); - res = false; - } - - return res; -} - -bool LLGiveInventory::doGiveInventoryCategory(const LLUUID& to_agent, - const LLInventoryCategory* cat, - const LLUUID& im_session_id, - const std::string& notification_name) - -{ - if (!cat) - { - return false; - } - LL_INFOS() << "LLGiveInventory::giveInventoryCategory() - " - << cat->getUUID() << LL_ENDL; - - if (!isAgentAvatarValid()) - { - return false; - } - - bool give_successful = true; - // Test out how many items are being given. - LLViewerInventoryCategory::cat_array_t cats; - LLViewerInventoryItem::item_array_t items; - LLGiveable giveable; - gInventory.collectDescendentsIf (cat->getUUID(), - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - giveable); - S32 count = cats.size(); - bool complete = true; - for(S32 i = 0; i < count; ++i) - { - if (!gInventory.isCategoryComplete(cats.at(i)->getUUID())) - { - complete = false; - break; - } - } - if (!complete) - { - LLNotificationsUtil::add("IncompleteInventory"); - give_successful = false; - } - count = items.size() + cats.size(); - if (count > MAX_ITEMS) - { - LLNotificationsUtil::add("TooManyItems"); - give_successful = false; - } - else if (count == 0) - { - LLNotificationsUtil::add("NoItems"); - give_successful = false; - } - else if (give_successful) - { - if (0 == giveable.countNoCopy()) - { - give_successful = LLGiveInventory::commitGiveInventoryCategory(to_agent, cat, im_session_id); - } - else - { - LLSD args; - args["COUNT"] = llformat("%d",giveable.countNoCopy()); - LLSD payload; - payload["agent_id"] = to_agent; - payload["folder_id"] = cat->getUUID(); - if (!notification_name.empty()) - { - payload["success_notification"] = notification_name; - } - LLNotificationsUtil::add("CannotCopyCountItems", args, payload, &LLGiveInventory::handleCopyProtectedCategory); - give_successful = false; - } - } - - return give_successful; -} - -////////////////////////////////////////////////////////////////////////// -// PRIVATE METHODS -////////////////////////////////////////////////////////////////////////// - -//static -void LLGiveInventory::logInventoryOffer(const LLUUID& to_agent, const LLUUID &im_session_id, const std::string& item_name, bool is_folder) -{ - // compute id of possible IM session with agent that has "to_agent" id - LLUUID session_id = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, to_agent); - // If this item was given by drag-and-drop into an IM panel, log this action in the IM panel chat. - LLSD args; - args["user_id"] = to_agent; - args["ITEM_NAME"] = item_name; - std::string message_name = is_folder ? "inventory_folder_offered" : "inventory_item_offered"; - if (im_session_id.notNull()) - { - gIMMgr->addSystemMessage(im_session_id, message_name, args); - } - // If this item was given by drag-and-drop on avatar while IM panel was open, log this action in the IM panel chat. - else if (LLIMModel::getInstance()->findIMSession(session_id)) - { - gIMMgr->addSystemMessage(session_id, message_name, args); - } - // If this item was given by drag-and-drop on avatar while IM panel wasn't open, log this action to IM history. - else - { - LLAvatarName av_name; - if (LLAvatarNameCache::get(to_agent, &av_name)) - { - // Build a new format username or firstname_lastname for legacy names - // to use it for a history log filename. - std::string full_name = LLCacheName::buildUsername(av_name.getUserName()); - LLUIString message = LLTrans::getString(message_name + "-im"); - message.setArgs(args); - LLIMModel::instance().logToFile(full_name, LLTrans::getString("SECOND_LIFE"), im_session_id, message.getString()); - } - } -} - -// static -bool LLGiveInventory::handleCopyProtectedItem(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - LLSD itmes = notification["payload"]["items"]; - LLInventoryItem* item = NULL; - bool give_successful = true; - switch(option) - { - case 0: // "Yes" - for (LLSD::array_iterator it = itmes.beginArray(); it != itmes.endArray(); it++) - { - item = gInventory.getItem((*it).asUUID()); - if (item) - { - LLGiveInventory::commitGiveInventoryItem(notification["payload"]["agent_id"].asUUID(), - item); - // delete it for now - it will be deleted on the server - // quickly enough. - gInventory.deleteObject(item->getUUID()); - gInventory.notifyObservers(); - } - else - { - LLNotificationsUtil::add("CannotGiveItem"); - give_successful = false; - } - } - if (give_successful && notification["payload"]["success_notification"].isDefined()) - { - LLNotificationsUtil::add(notification["payload"]["success_notification"].asString()); - } - break; - - default: // no, cancel, whatever, who cares, not yes. - LLNotificationsUtil::add("TransactionCancelled"); - give_successful = false; - break; - } - return give_successful; -} - -// static -void LLGiveInventory::commitGiveInventoryItem(const LLUUID& to_agent, - const LLInventoryItem* item, - const LLUUID& im_session_id) -{ - if (!item) return; - std::string name; - std::string item_name = item->getName(); - LLAgentUI::buildFullname(name); - LLUUID transaction_id; - transaction_id.generate(); - const S32 BUCKET_SIZE = sizeof(U8) + UUID_BYTES; - U8 bucket[BUCKET_SIZE]; - bucket[0] = (U8)item->getType(); - memcpy(&bucket[1], &(item->getUUID().mData), UUID_BYTES); /* Flawfinder: ignore */ - pack_instant_message( - gMessageSystem, - gAgentID, - false, - gAgentSessionID, - to_agent, - name, - item_name, - IM_ONLINE, - IM_INVENTORY_OFFERED, - transaction_id, - 0, - LLUUID::null, - gAgent.getPositionAgent(), - NO_TIMESTAMP, - bucket, - BUCKET_SIZE); - gAgent.sendReliableMessage(); - - // VEFFECT: giveInventory - LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM, true); - effectp->setSourceObject(gAgentAvatarp); - effectp->setTargetObject(gObjectList.findObject(to_agent)); - effectp->setDuration(LL_HUD_DUR_SHORT); - effectp->setColor(LLColor4U(gAgent.getEffectColor())); - gFloaterTools->dirty(); - - LLMuteList::getInstance()->autoRemove(to_agent, LLMuteList::AR_INVENTORY); - - logInventoryOffer(to_agent, im_session_id, item_name); - - // add buddy to recent people list - LLRecentPeople::instance().add(to_agent); -} - -// static -bool LLGiveInventory::handleCopyProtectedCategory(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - LLInventoryCategory* cat = NULL; - bool give_successful = true; - switch(option) - { - case 0: // "Yes" - cat = gInventory.getCategory(notification["payload"]["folder_id"].asUUID()); - if (cat) - { - give_successful = LLGiveInventory::commitGiveInventoryCategory(notification["payload"]["agent_id"].asUUID(), - cat); - LLViewerInventoryCategory::cat_array_t cats; - LLViewerInventoryItem::item_array_t items; - LLUncopyableItems remove; - gInventory.collectDescendentsIf (cat->getUUID(), - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - remove); - S32 count = items.size(); - for(S32 i = 0; i < count; ++i) - { - gInventory.deleteObject(items.at(i)->getUUID()); - } - gInventory.notifyObservers(); - - if (give_successful && notification["payload"]["success_notification"].isDefined()) - { - LLNotificationsUtil::add(notification["payload"]["success_notification"].asString()); - } - } - else - { - LLNotificationsUtil::add("CannotGiveCategory"); - give_successful = false; - } - break; - - default: // no, cancel, whatever, who cares, not yes. - LLNotificationsUtil::add("TransactionCancelled"); - give_successful = false; - break; - } - return give_successful; -} - -// static -bool LLGiveInventory::commitGiveInventoryCategory(const LLUUID& to_agent, - const LLInventoryCategory* cat, - const LLUUID& im_session_id) - -{ - if (!cat) - { - return false; - } - LL_INFOS() << "LLGiveInventory::commitGiveInventoryCategory() - " - << cat->getUUID() << LL_ENDL; - - // add buddy to recent people list - LLRecentPeople::instance().add(to_agent); - - // Test out how many items are being given. - LLViewerInventoryCategory::cat_array_t cats; - LLViewerInventoryItem::item_array_t items; - LLGiveable giveable; - gInventory.collectDescendentsIf (cat->getUUID(), - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - giveable); - std::string cat_name = cat->getName(); - bool give_successful = true; - // MAX ITEMS is based on (sizeof(uuid)+2) * count must be < - // MTUBYTES or 18 * count < 1200 => count < 1200/18 => - // 66. I've cut it down a bit from there to give some pad. - S32 count = items.size() + cats.size(); - if (count > MAX_ITEMS) - { - LLNotificationsUtil::add("TooManyItems"); - give_successful = false; - } - else if (count == 0) - { - LLNotificationsUtil::add("NoItems"); - give_successful = false; - } - else - { - std::string name; - LLAgentUI::buildFullname(name); - LLUUID transaction_id; - transaction_id.generate(); - S32 bucket_size = (sizeof(U8) + UUID_BYTES) * (count + 1); - U8* bucket = new U8[bucket_size]; - U8* pos = bucket; - U8 type = (U8)cat->getType(); - memcpy(pos, &type, sizeof(U8)); /* Flawfinder: ignore */ - pos += sizeof(U8); - memcpy(pos, &(cat->getUUID()), UUID_BYTES); /* Flawfinder: ignore */ - pos += UUID_BYTES; - S32 i; - count = cats.size(); - for(i = 0; i < count; ++i) - { - memcpy(pos, &type, sizeof(U8)); /* Flawfinder: ignore */ - pos += sizeof(U8); - memcpy(pos, &(cats.at(i)->getUUID()), UUID_BYTES); /* Flawfinder: ignore */ - pos += UUID_BYTES; - } - count = items.size(); - for(i = 0; i < count; ++i) - { - type = (U8)items.at(i)->getType(); - memcpy(pos, &type, sizeof(U8)); /* Flawfinder: ignore */ - pos += sizeof(U8); - memcpy(pos, &(items.at(i)->getUUID()), UUID_BYTES); /* Flawfinder: ignore */ - pos += UUID_BYTES; - } - pack_instant_message( - gMessageSystem, - gAgent.getID(), - false, - gAgent.getSessionID(), - to_agent, - name, - cat_name, - IM_ONLINE, - IM_INVENTORY_OFFERED, - transaction_id, - 0, - LLUUID::null, - gAgent.getPositionAgent(), - NO_TIMESTAMP, - bucket, - bucket_size); - gAgent.sendReliableMessage(); - delete[] bucket; - - // VEFFECT: giveInventoryCategory - LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM, true); - effectp->setSourceObject(gAgentAvatarp); - effectp->setTargetObject(gObjectList.findObject(to_agent)); - effectp->setDuration(LL_HUD_DUR_SHORT); - effectp->setColor(LLColor4U(gAgent.getEffectColor())); - gFloaterTools->dirty(); - - LLMuteList::getInstance()->autoRemove(to_agent, LLMuteList::AR_INVENTORY); - - logInventoryOffer(to_agent, im_session_id, cat_name, true); - } - - return give_successful; -} - -// EOF +/** + * @file llgiveinventory.cpp + * @brief LLGiveInventory class implementation + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llgiveinventory.h" + +// library includes +#include "llnotificationsutil.h" +#include "lltrans.h" + +// newview includes +#include "llagent.h" +#include "llagentdata.h" +#include "llagentui.h" +#include "llagentwearables.h" +#include "llavatarnamecache.h" +#include "llfloatertools.h" // for gFloaterTool +#include "llhudeffecttrail.h" +#include "llhudmanager.h" +#include "llimview.h" +#include "llinventory.h" +#include "llinventoryfunctions.h" +#include "llmutelist.h" +#include "llrecentpeople.h" +#include "llviewerobjectlist.h" +#include "llvoavatarself.h" + +// MAX ITEMS is based on (sizeof(uuid)+2) * count must be < MTUBYTES +// or 18 * count < 1200 => count < 1200/18 => 66. I've cut it down a +// bit from there to give some pad. +const S32 MAX_ITEMS = 42; + +class LLGiveable : public LLInventoryCollectFunctor +{ +public: + LLGiveable() : mCountLosing(0) {} + virtual ~LLGiveable() {} + virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); + + S32 countNoCopy() const { return mCountLosing; } +protected: + S32 mCountLosing; +}; + +bool LLGiveable::operator()(LLInventoryCategory* cat, LLInventoryItem* item) +{ + // All categories can be given. + if (cat) + return true; + + bool allowed = false; + if (item) + { + allowed = itemTransferCommonlyAllowed(item); + if (allowed && + !item->getPermissions().allowOperationBy(PERM_TRANSFER, + gAgent.getID())) + { + allowed = false; + } + if (allowed && + !item->getPermissions().allowCopyBy(gAgent.getID())) + { + ++mCountLosing; + } + } + return allowed; +} + +class LLUncopyableItems : public LLInventoryCollectFunctor +{ +public: + LLUncopyableItems() {} + virtual ~LLUncopyableItems() {} + virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); +}; + +bool LLUncopyableItems::operator()(LLInventoryCategory* cat, + LLInventoryItem* item) +{ + bool uncopyable = false; + if (item) + { + if (itemTransferCommonlyAllowed(item) && + !item->getPermissions().allowCopyBy(gAgent.getID())) + { + uncopyable = true; + } + } + return uncopyable; +} + +// static +bool LLGiveInventory::isInventoryGiveAcceptable(const LLInventoryItem* item) +{ + if (!item) return false; + + if (!isAgentAvatarValid()) return false; + + if (!item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgentID)) + { + return false; + } + + bool acceptable = true; + switch(item->getType()) + { + case LLAssetType::AT_OBJECT: + case LLAssetType::AT_BODYPART: + case LLAssetType::AT_CLOTHING: + { + if (get_is_item_worn(item->getUUID())) + { + acceptable = false; + } + break; + } + break; + default: + break; + } + return acceptable; +} + +// static +bool LLGiveInventory::isInventoryGroupGiveAcceptable(const LLInventoryItem* item) +{ + if (!item) return false; + + if (!isAgentAvatarValid()) return false; + + // These permissions are double checked in the simulator in + // LLGroupNoticeInventoryItemFetch::result(). + if (!item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgentID)) + { + return false; + } + if (!item->getPermissions().allowCopyBy(gAgent.getID())) + { + return false; + } + + + bool acceptable = true; + switch(item->getType()) + { + case LLAssetType::AT_OBJECT: + if (gAgentAvatarp->isWearingAttachment(item->getUUID())) + { + acceptable = false; + } + break; + default: + break; + } + return acceptable; +} + +// static +bool LLGiveInventory::doGiveInventoryItem(const LLUUID& to_agent, + const LLInventoryItem* item, + const LLUUID& im_session_id/* = LLUUID::null*/) + +{ + bool res = true; + LL_INFOS() << "LLGiveInventory::giveInventory()" << LL_ENDL; + if (!isInventoryGiveAcceptable(item)) + { + return false; + } + if (item->getPermissions().allowCopyBy(gAgentID)) + { + // just give it away. + LLGiveInventory::commitGiveInventoryItem(to_agent, item, im_session_id); + } + else + { + // ask if the agent is sure. + LLSD substitutions; + substitutions["ITEMS"] = item->getName(); + LLSD payload; + payload["agent_id"] = to_agent; + LLSD items = LLSD::emptyArray(); + items.append(item->getUUID()); + payload["items"] = items; + LLNotificationsUtil::add("CannotCopyWarning", substitutions, payload, + &LLGiveInventory::handleCopyProtectedItem); + res = false; + } + + return res; +} + +bool LLGiveInventory::doGiveInventoryCategory(const LLUUID& to_agent, + const LLInventoryCategory* cat, + const LLUUID& im_session_id, + const std::string& notification_name) + +{ + if (!cat) + { + return false; + } + LL_INFOS() << "LLGiveInventory::giveInventoryCategory() - " + << cat->getUUID() << LL_ENDL; + + if (!isAgentAvatarValid()) + { + return false; + } + + bool give_successful = true; + // Test out how many items are being given. + LLViewerInventoryCategory::cat_array_t cats; + LLViewerInventoryItem::item_array_t items; + LLGiveable giveable; + gInventory.collectDescendentsIf (cat->getUUID(), + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + giveable); + S32 count = cats.size(); + bool complete = true; + for(S32 i = 0; i < count; ++i) + { + if (!gInventory.isCategoryComplete(cats.at(i)->getUUID())) + { + complete = false; + break; + } + } + if (!complete) + { + LLNotificationsUtil::add("IncompleteInventory"); + give_successful = false; + } + count = items.size() + cats.size(); + if (count > MAX_ITEMS) + { + LLNotificationsUtil::add("TooManyItems"); + give_successful = false; + } + else if (count == 0) + { + LLNotificationsUtil::add("NoItems"); + give_successful = false; + } + else if (give_successful) + { + if (0 == giveable.countNoCopy()) + { + give_successful = LLGiveInventory::commitGiveInventoryCategory(to_agent, cat, im_session_id); + } + else + { + LLSD args; + args["COUNT"] = llformat("%d",giveable.countNoCopy()); + LLSD payload; + payload["agent_id"] = to_agent; + payload["folder_id"] = cat->getUUID(); + if (!notification_name.empty()) + { + payload["success_notification"] = notification_name; + } + LLNotificationsUtil::add("CannotCopyCountItems", args, payload, &LLGiveInventory::handleCopyProtectedCategory); + give_successful = false; + } + } + + return give_successful; +} + +////////////////////////////////////////////////////////////////////////// +// PRIVATE METHODS +////////////////////////////////////////////////////////////////////////// + +//static +void LLGiveInventory::logInventoryOffer(const LLUUID& to_agent, const LLUUID &im_session_id, const std::string& item_name, bool is_folder) +{ + // compute id of possible IM session with agent that has "to_agent" id + LLUUID session_id = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, to_agent); + // If this item was given by drag-and-drop into an IM panel, log this action in the IM panel chat. + LLSD args; + args["user_id"] = to_agent; + args["ITEM_NAME"] = item_name; + std::string message_name = is_folder ? "inventory_folder_offered" : "inventory_item_offered"; + if (im_session_id.notNull()) + { + gIMMgr->addSystemMessage(im_session_id, message_name, args); + } + // If this item was given by drag-and-drop on avatar while IM panel was open, log this action in the IM panel chat. + else if (LLIMModel::getInstance()->findIMSession(session_id)) + { + gIMMgr->addSystemMessage(session_id, message_name, args); + } + // If this item was given by drag-and-drop on avatar while IM panel wasn't open, log this action to IM history. + else + { + LLAvatarName av_name; + if (LLAvatarNameCache::get(to_agent, &av_name)) + { + // Build a new format username or firstname_lastname for legacy names + // to use it for a history log filename. + std::string full_name = LLCacheName::buildUsername(av_name.getUserName()); + LLUIString message = LLTrans::getString(message_name + "-im"); + message.setArgs(args); + LLIMModel::instance().logToFile(full_name, LLTrans::getString("SECOND_LIFE"), im_session_id, message.getString()); + } + } +} + +// static +bool LLGiveInventory::handleCopyProtectedItem(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + LLSD itmes = notification["payload"]["items"]; + LLInventoryItem* item = NULL; + bool give_successful = true; + switch(option) + { + case 0: // "Yes" + for (LLSD::array_iterator it = itmes.beginArray(); it != itmes.endArray(); it++) + { + item = gInventory.getItem((*it).asUUID()); + if (item) + { + LLGiveInventory::commitGiveInventoryItem(notification["payload"]["agent_id"].asUUID(), + item); + // delete it for now - it will be deleted on the server + // quickly enough. + gInventory.deleteObject(item->getUUID()); + gInventory.notifyObservers(); + } + else + { + LLNotificationsUtil::add("CannotGiveItem"); + give_successful = false; + } + } + if (give_successful && notification["payload"]["success_notification"].isDefined()) + { + LLNotificationsUtil::add(notification["payload"]["success_notification"].asString()); + } + break; + + default: // no, cancel, whatever, who cares, not yes. + LLNotificationsUtil::add("TransactionCancelled"); + give_successful = false; + break; + } + return give_successful; +} + +// static +void LLGiveInventory::commitGiveInventoryItem(const LLUUID& to_agent, + const LLInventoryItem* item, + const LLUUID& im_session_id) +{ + if (!item) return; + std::string name; + std::string item_name = item->getName(); + LLAgentUI::buildFullname(name); + LLUUID transaction_id; + transaction_id.generate(); + const S32 BUCKET_SIZE = sizeof(U8) + UUID_BYTES; + U8 bucket[BUCKET_SIZE]; + bucket[0] = (U8)item->getType(); + memcpy(&bucket[1], &(item->getUUID().mData), UUID_BYTES); /* Flawfinder: ignore */ + pack_instant_message( + gMessageSystem, + gAgentID, + false, + gAgentSessionID, + to_agent, + name, + item_name, + IM_ONLINE, + IM_INVENTORY_OFFERED, + transaction_id, + 0, + LLUUID::null, + gAgent.getPositionAgent(), + NO_TIMESTAMP, + bucket, + BUCKET_SIZE); + gAgent.sendReliableMessage(); + + // VEFFECT: giveInventory + LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM, true); + effectp->setSourceObject(gAgentAvatarp); + effectp->setTargetObject(gObjectList.findObject(to_agent)); + effectp->setDuration(LL_HUD_DUR_SHORT); + effectp->setColor(LLColor4U(gAgent.getEffectColor())); + gFloaterTools->dirty(); + + LLMuteList::getInstance()->autoRemove(to_agent, LLMuteList::AR_INVENTORY); + + logInventoryOffer(to_agent, im_session_id, item_name); + + // add buddy to recent people list + LLRecentPeople::instance().add(to_agent); +} + +// static +bool LLGiveInventory::handleCopyProtectedCategory(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + LLInventoryCategory* cat = NULL; + bool give_successful = true; + switch(option) + { + case 0: // "Yes" + cat = gInventory.getCategory(notification["payload"]["folder_id"].asUUID()); + if (cat) + { + give_successful = LLGiveInventory::commitGiveInventoryCategory(notification["payload"]["agent_id"].asUUID(), + cat); + LLViewerInventoryCategory::cat_array_t cats; + LLViewerInventoryItem::item_array_t items; + LLUncopyableItems remove; + gInventory.collectDescendentsIf (cat->getUUID(), + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + remove); + S32 count = items.size(); + for(S32 i = 0; i < count; ++i) + { + gInventory.deleteObject(items.at(i)->getUUID()); + } + gInventory.notifyObservers(); + + if (give_successful && notification["payload"]["success_notification"].isDefined()) + { + LLNotificationsUtil::add(notification["payload"]["success_notification"].asString()); + } + } + else + { + LLNotificationsUtil::add("CannotGiveCategory"); + give_successful = false; + } + break; + + default: // no, cancel, whatever, who cares, not yes. + LLNotificationsUtil::add("TransactionCancelled"); + give_successful = false; + break; + } + return give_successful; +} + +// static +bool LLGiveInventory::commitGiveInventoryCategory(const LLUUID& to_agent, + const LLInventoryCategory* cat, + const LLUUID& im_session_id) + +{ + if (!cat) + { + return false; + } + LL_INFOS() << "LLGiveInventory::commitGiveInventoryCategory() - " + << cat->getUUID() << LL_ENDL; + + // add buddy to recent people list + LLRecentPeople::instance().add(to_agent); + + // Test out how many items are being given. + LLViewerInventoryCategory::cat_array_t cats; + LLViewerInventoryItem::item_array_t items; + LLGiveable giveable; + gInventory.collectDescendentsIf (cat->getUUID(), + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + giveable); + std::string cat_name = cat->getName(); + bool give_successful = true; + // MAX ITEMS is based on (sizeof(uuid)+2) * count must be < + // MTUBYTES or 18 * count < 1200 => count < 1200/18 => + // 66. I've cut it down a bit from there to give some pad. + S32 count = items.size() + cats.size(); + if (count > MAX_ITEMS) + { + LLNotificationsUtil::add("TooManyItems"); + give_successful = false; + } + else if (count == 0) + { + LLNotificationsUtil::add("NoItems"); + give_successful = false; + } + else + { + std::string name; + LLAgentUI::buildFullname(name); + LLUUID transaction_id; + transaction_id.generate(); + S32 bucket_size = (sizeof(U8) + UUID_BYTES) * (count + 1); + U8* bucket = new U8[bucket_size]; + U8* pos = bucket; + U8 type = (U8)cat->getType(); + memcpy(pos, &type, sizeof(U8)); /* Flawfinder: ignore */ + pos += sizeof(U8); + memcpy(pos, &(cat->getUUID()), UUID_BYTES); /* Flawfinder: ignore */ + pos += UUID_BYTES; + S32 i; + count = cats.size(); + for(i = 0; i < count; ++i) + { + memcpy(pos, &type, sizeof(U8)); /* Flawfinder: ignore */ + pos += sizeof(U8); + memcpy(pos, &(cats.at(i)->getUUID()), UUID_BYTES); /* Flawfinder: ignore */ + pos += UUID_BYTES; + } + count = items.size(); + for(i = 0; i < count; ++i) + { + type = (U8)items.at(i)->getType(); + memcpy(pos, &type, sizeof(U8)); /* Flawfinder: ignore */ + pos += sizeof(U8); + memcpy(pos, &(items.at(i)->getUUID()), UUID_BYTES); /* Flawfinder: ignore */ + pos += UUID_BYTES; + } + pack_instant_message( + gMessageSystem, + gAgent.getID(), + false, + gAgent.getSessionID(), + to_agent, + name, + cat_name, + IM_ONLINE, + IM_INVENTORY_OFFERED, + transaction_id, + 0, + LLUUID::null, + gAgent.getPositionAgent(), + NO_TIMESTAMP, + bucket, + bucket_size); + gAgent.sendReliableMessage(); + delete[] bucket; + + // VEFFECT: giveInventoryCategory + LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM, true); + effectp->setSourceObject(gAgentAvatarp); + effectp->setTargetObject(gObjectList.findObject(to_agent)); + effectp->setDuration(LL_HUD_DUR_SHORT); + effectp->setColor(LLColor4U(gAgent.getEffectColor())); + gFloaterTools->dirty(); + + LLMuteList::getInstance()->autoRemove(to_agent, LLMuteList::AR_INVENTORY); + + logInventoryOffer(to_agent, im_session_id, cat_name, true); + } + + return give_successful; +} + +// EOF diff --git a/indra/newview/llglsandbox.cpp b/indra/newview/llglsandbox.cpp index 603d40423f..505d7b1dea 100644 --- a/indra/newview/llglsandbox.cpp +++ b/indra/newview/llglsandbox.cpp @@ -1,1200 +1,1200 @@ -/** - * @file llglsandbox.cpp - * @brief GL functionality access - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/** - * Contains ALL methods which directly access GL functionality - * except for core rendering engine functionality. - */ - -#include "llviewerprecompiledheaders.h" - -#include "llviewercontrol.h" - -#include "llgl.h" -#include "llrender.h" -#include "llglheaders.h" -#include "llparcel.h" -#include "llui.h" - -#include "lldrawable.h" -#include "lltextureentry.h" -#include "llviewercamera.h" - -#include "llvoavatarself.h" -#include "llsky.h" -#include "llagent.h" -#include "lltoolmgr.h" -#include "llselectmgr.h" -#include "llhudmanager.h" -#include "llhudtext.h" -#include "llrendersphere.h" -#include "llviewerobjectlist.h" -#include "lltoolselectrect.h" -#include "llviewerwindow.h" -#include "llsurface.h" -#include "llwind.h" -#include "llworld.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" -#include "llpreviewtexture.h" -#include "llresmgr.h" -#include "pipeline.h" -#include "llspatialpartition.h" -#include "llviewershadermgr.h" - -#include - -// Height of the yellow selection highlight posts for land -const F32 PARCEL_POST_HEIGHT = 0.666f; - -// Returns true if you got at least one object -void LLToolSelectRect::handleRectangleSelection(S32 x, S32 y, MASK mask) -{ - LLVector3 av_pos = gAgent.getPositionAgent(); - F32 select_dist_squared = gSavedSettings.getF32("MaxSelectDistance"); - select_dist_squared = select_dist_squared * select_dist_squared; - - bool deselect = (mask == MASK_CONTROL); - S32 left = llmin(x, mDragStartX); - S32 right = llmax(x, mDragStartX); - S32 top = llmax(y, mDragStartY); - S32 bottom =llmin(y, mDragStartY); - - left = ll_round((F32) left * LLUI::getScaleFactor().mV[VX]); - right = ll_round((F32) right * LLUI::getScaleFactor().mV[VX]); - top = ll_round((F32) top * LLUI::getScaleFactor().mV[VY]); - bottom = ll_round((F32) bottom * LLUI::getScaleFactor().mV[VY]); - - F32 old_far_plane = LLViewerCamera::getInstance()->getFar(); - F32 old_near_plane = LLViewerCamera::getInstance()->getNear(); - - S32 width = right - left + 1; - S32 height = top - bottom + 1; - - bool grow_selection = false; - bool shrink_selection = false; - - if (height > mDragLastHeight || width > mDragLastWidth) - { - grow_selection = true; - } - if (height < mDragLastHeight || width < mDragLastWidth) - { - shrink_selection = true; - } - - if (!grow_selection && !shrink_selection) - { - // nothing to do - return; - } - - mDragLastHeight = height; - mDragLastWidth = width; - - S32 center_x = (left + right) / 2; - S32 center_y = (top + bottom) / 2; - - // save drawing mode - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.pushMatrix(); - - bool limit_select_distance = gSavedSettings.getBOOL("LimitSelectDistance"); - if (limit_select_distance) - { - // ...select distance from control - LLVector3 relative_av_pos = av_pos; - relative_av_pos -= LLViewerCamera::getInstance()->getOrigin(); - - F32 new_far = relative_av_pos * LLViewerCamera::getInstance()->getAtAxis() + gSavedSettings.getF32("MaxSelectDistance"); - F32 new_near = relative_av_pos * LLViewerCamera::getInstance()->getAtAxis() - gSavedSettings.getF32("MaxSelectDistance"); - - new_near = llmax(new_near, 0.1f); - - LLViewerCamera::getInstance()->setFar(new_far); - LLViewerCamera::getInstance()->setNear(new_near); - } - LLViewerCamera::getInstance()->setPerspective(FOR_SELECTION, - center_x-width/2, center_y-height/2, width, height, - limit_select_distance); - - if (shrink_selection) - { - struct f : public LLSelectedObjectFunctor - { - virtual bool apply(LLViewerObject* vobjp) - { - LLDrawable* drawable = vobjp->mDrawable; - if (!drawable || vobjp->getPCode() != LL_PCODE_VOLUME || vobjp->isAttachment()) - { - return true; - } - S32 result = LLViewerCamera::getInstance()->sphereInFrustum(drawable->getPositionAgent(), drawable->getRadius()); - switch (result) - { - case 0: - LLSelectMgr::getInstance()->unhighlightObjectOnly(vobjp); - break; - case 1: - // check vertices - if (!LLViewerCamera::getInstance()->areVertsVisible(vobjp, LLSelectMgr::sRectSelectInclusive)) - { - LLSelectMgr::getInstance()->unhighlightObjectOnly(vobjp); - } - break; - default: - break; - } - return true; - } - } func; - LLSelectMgr::getInstance()->getHighlightedObjects()->applyToObjects(&func); - } - - if (grow_selection) - { - std::vector potentials; - - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) - { - LLViewerRegion* region = *iter; - for (U32 i = 0; i < LLViewerRegion::NUM_PARTITIONS; i++) - { - LLSpatialPartition* part = region->getSpatialPartition(i); - if (part) - { - part->cull(*LLViewerCamera::getInstance(), &potentials, true); - } - } - } - - for (std::vector::iterator iter = potentials.begin(); - iter != potentials.end(); iter++) - { - LLDrawable* drawable = *iter; - LLViewerObject* vobjp = drawable->getVObj(); - - if (!drawable || !vobjp || - vobjp->getPCode() != LL_PCODE_VOLUME || - vobjp->isAttachment() || - (deselect && !vobjp->isSelected())) - { - continue; - } - - if (limit_select_distance && dist_vec_squared(drawable->getWorldPosition(), av_pos) > select_dist_squared) - { - continue; - } - - S32 result = LLViewerCamera::getInstance()->sphereInFrustum(drawable->getPositionAgent(), drawable->getRadius()); - if (result) - { - switch (result) - { - case 1: - // check vertices - if (LLViewerCamera::getInstance()->areVertsVisible(vobjp, LLSelectMgr::sRectSelectInclusive)) - { - LLSelectMgr::getInstance()->highlightObjectOnly(vobjp); - } - break; - case 2: - LLSelectMgr::getInstance()->highlightObjectOnly(vobjp); - break; - default: - break; - } - } - } - } - - // restore drawing mode - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.popMatrix(); - gGL.matrixMode(LLRender::MM_MODELVIEW); - - // restore camera - LLViewerCamera::getInstance()->setFar(old_far_plane); - LLViewerCamera::getInstance()->setNear(old_near_plane); - gViewerWindow->setup3DRender(); -} - -const F32 WIND_RELATIVE_ALTITUDE = 25.f; - -void LLWind::renderVectors() -{ - // Renders the wind as vectors (used for debug) - S32 i,j; - F32 x,y; - - F32 region_width_meters = LLWorld::getInstance()->getRegionWidthInMeters(); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.pushMatrix(); - LLVector3 origin_agent; - origin_agent = gAgent.getPosAgentFromGlobal(mOriginGlobal); - gGL.translatef(origin_agent.mV[VX], origin_agent.mV[VY], gAgent.getPositionAgent().mV[VZ] + WIND_RELATIVE_ALTITUDE); - for (j = 0; j < mSize; j++) - { - for (i = 0; i < mSize; i++) - { - x = mVelX[i + j*mSize] * WIND_SCALE_HACK; - y = mVelY[i + j*mSize] * WIND_SCALE_HACK; - gGL.pushMatrix(); - gGL.translatef((F32)i * region_width_meters/mSize, (F32)j * region_width_meters/mSize, 0.0); - gGL.color3f(0,1,0); - gGL.begin(LLRender::POINTS); - gGL.vertex3f(0,0,0); - gGL.end(); - gGL.color3f(1,0,0); - gGL.begin(LLRender::LINES); - gGL.vertex3f(x * 0.1f, y * 0.1f ,0.f); - gGL.vertex3f(x, y, 0.f); - gGL.end(); - gGL.popMatrix(); - } - } - gGL.popMatrix(); -} - - - - -// Used by lltoolselectland -void LLViewerParcelMgr::renderRect(const LLVector3d &west_south_bottom_global, - const LLVector3d &east_north_top_global ) -{ - LLGLSUIDefault gls_ui; - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - LLGLDepthTest gls_depth(GL_TRUE); - - LLVector3 west_south_bottom_agent = gAgent.getPosAgentFromGlobal(west_south_bottom_global); - F32 west = west_south_bottom_agent.mV[VX]; - F32 south = west_south_bottom_agent.mV[VY]; -// F32 bottom = west_south_bottom_agent.mV[VZ] - 1.f; - - LLVector3 east_north_top_agent = gAgent.getPosAgentFromGlobal(east_north_top_global); - F32 east = east_north_top_agent.mV[VX]; - F32 north = east_north_top_agent.mV[VY]; -// F32 top = east_north_top_agent.mV[VZ] + 1.f; - - // HACK: At edge of last region of world, we need to make sure the region - // resolves correctly so we can get a height value. - const F32 FUDGE = 0.01f; - - F32 sw_bottom = LLWorld::getInstance()->resolveLandHeightAgent( LLVector3( west, south, 0.f ) ); - F32 se_bottom = LLWorld::getInstance()->resolveLandHeightAgent( LLVector3( east-FUDGE, south, 0.f ) ); - F32 ne_bottom = LLWorld::getInstance()->resolveLandHeightAgent( LLVector3( east-FUDGE, north-FUDGE, 0.f ) ); - F32 nw_bottom = LLWorld::getInstance()->resolveLandHeightAgent( LLVector3( west, north-FUDGE, 0.f ) ); - - F32 sw_top = sw_bottom + PARCEL_POST_HEIGHT; - F32 se_top = se_bottom + PARCEL_POST_HEIGHT; - F32 ne_top = ne_bottom + PARCEL_POST_HEIGHT; - F32 nw_top = nw_bottom + PARCEL_POST_HEIGHT; - - LLUI::setLineWidth(2.f); - gGL.color4f(1.f, 1.f, 0.f, 1.f); - - // Cheat and give this the same pick-name as land - gGL.begin(LLRender::LINES); - - gGL.vertex3f(west, north, nw_bottom); - gGL.vertex3f(west, north, nw_top); - - gGL.vertex3f(east, north, ne_bottom); - gGL.vertex3f(east, north, ne_top); - - gGL.vertex3f(east, south, se_bottom); - gGL.vertex3f(east, south, se_top); - - gGL.vertex3f(west, south, sw_bottom); - gGL.vertex3f(west, south, sw_top); - - gGL.end(); - - gGL.color4f(1.f, 1.f, 0.f, 0.2f); - gGL.begin(LLRender::QUADS); - - gGL.vertex3f(west, north, nw_bottom); - gGL.vertex3f(west, north, nw_top); - gGL.vertex3f(east, north, ne_top); - gGL.vertex3f(east, north, ne_bottom); - - gGL.vertex3f(east, north, ne_bottom); - gGL.vertex3f(east, north, ne_top); - gGL.vertex3f(east, south, se_top); - gGL.vertex3f(east, south, se_bottom); - - gGL.vertex3f(east, south, se_bottom); - gGL.vertex3f(east, south, se_top); - gGL.vertex3f(west, south, sw_top); - gGL.vertex3f(west, south, sw_bottom); - - gGL.vertex3f(west, south, sw_bottom); - gGL.vertex3f(west, south, sw_top); - gGL.vertex3f(west, north, nw_top); - gGL.vertex3f(west, north, nw_bottom); - - gGL.end(); - - LLUI::setLineWidth(1.f); -} - -/* -void LLViewerParcelMgr::renderParcel(LLParcel* parcel ) -{ - S32 i; - S32 count = parcel->getBoxCount(); - for (i = 0; i < count; i++) - { - const LLParcelBox& box = parcel->getBox(i); - - F32 west = box.mMin.mV[VX]; - F32 south = box.mMin.mV[VY]; - - F32 east = box.mMax.mV[VX]; - F32 north = box.mMax.mV[VY]; - - // HACK: At edge of last region of world, we need to make sure the region - // resolves correctly so we can get a height value. - const F32 FUDGE = 0.01f; - - F32 sw_bottom = LLWorld::getInstance()->resolveLandHeightAgent( LLVector3( west, south, 0.f ) ); - F32 se_bottom = LLWorld::getInstance()->resolveLandHeightAgent( LLVector3( east-FUDGE, south, 0.f ) ); - F32 ne_bottom = LLWorld::getInstance()->resolveLandHeightAgent( LLVector3( east-FUDGE, north-FUDGE, 0.f ) ); - F32 nw_bottom = LLWorld::getInstance()->resolveLandHeightAgent( LLVector3( west, north-FUDGE, 0.f ) ); - - // little hack to make nearby lines not Z-fight - east -= 0.1f; - north -= 0.1f; - - F32 sw_top = sw_bottom + POST_HEIGHT; - F32 se_top = se_bottom + POST_HEIGHT; - F32 ne_top = ne_bottom + POST_HEIGHT; - F32 nw_top = nw_bottom + POST_HEIGHT; - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - LLGLDepthTest gls_depth(GL_TRUE); - - LLUI::setLineWidth(2.f); - gGL.color4f(0.f, 1.f, 1.f, 1.f); - - // Cheat and give this the same pick-name as land - gGL.begin(LLRender::LINES); - - gGL.vertex3f(west, north, nw_bottom); - gGL.vertex3f(west, north, nw_top); - - gGL.vertex3f(east, north, ne_bottom); - gGL.vertex3f(east, north, ne_top); - - gGL.vertex3f(east, south, se_bottom); - gGL.vertex3f(east, south, se_top); - - gGL.vertex3f(west, south, sw_bottom); - gGL.vertex3f(west, south, sw_top); - - gGL.end(); - - gGL.color4f(0.f, 1.f, 1.f, 0.2f); - gGL.begin(LLRender::QUADS); - - gGL.vertex3f(west, north, nw_bottom); - gGL.vertex3f(west, north, nw_top); - gGL.vertex3f(east, north, ne_top); - gGL.vertex3f(east, north, ne_bottom); - - gGL.vertex3f(east, north, ne_bottom); - gGL.vertex3f(east, north, ne_top); - gGL.vertex3f(east, south, se_top); - gGL.vertex3f(east, south, se_bottom); - - gGL.vertex3f(east, south, se_bottom); - gGL.vertex3f(east, south, se_top); - gGL.vertex3f(west, south, sw_top); - gGL.vertex3f(west, south, sw_bottom); - - gGL.vertex3f(west, south, sw_bottom); - gGL.vertex3f(west, south, sw_top); - gGL.vertex3f(west, north, nw_top); - gGL.vertex3f(west, north, nw_bottom); - - gGL.end(); - - LLUI::setLineWidth(1.f); - } -} -*/ - - -// north = a wall going north/south. Need that info to set up texture -// coordinates correctly. -void LLViewerParcelMgr::renderOneSegment(F32 x1, F32 y1, F32 x2, F32 y2, F32 height, U8 direction, LLViewerRegion* regionp) -{ - // HACK: At edge of last region of world, we need to make sure the region - // resolves correctly so we can get a height value. - const F32 BORDER = REGION_WIDTH_METERS - 0.1f; - - F32 clamped_x1 = x1; - F32 clamped_y1 = y1; - F32 clamped_x2 = x2; - F32 clamped_y2 = y2; - - if (clamped_x1 > BORDER) clamped_x1 = BORDER; - if (clamped_y1 > BORDER) clamped_y1 = BORDER; - if (clamped_x2 > BORDER) clamped_x2 = BORDER; - if (clamped_y2 > BORDER) clamped_y2 = BORDER; - - F32 z; - F32 z1; - F32 z2; - - z1 = regionp->getLand().resolveHeightRegion( LLVector3( clamped_x1, clamped_y1, 0.f ) ); - z2 = regionp->getLand().resolveHeightRegion( LLVector3( clamped_x2, clamped_y2, 0.f ) ); - - // Convert x1 and x2 from region-local to agent coords. - LLVector3 origin = regionp->getOriginAgent(); - x1 += origin.mV[VX]; - x2 += origin.mV[VX]; - y1 += origin.mV[VY]; - y2 += origin.mV[VY]; - - if (height < 1.f) - { - z = z1+height; - gGL.vertex3f(x1, y1, z); - - gGL.vertex3f(x1, y1, z1); - - gGL.vertex3f(x2, y2, z2); - - z = z2+height; - gGL.vertex3f(x2, y2, z); - } - else - { - F32 tex_coord1; - F32 tex_coord2; - - if (WEST_MASK == direction) - { - tex_coord1 = y1; - tex_coord2 = y2; - } - else if (SOUTH_MASK == direction) - { - tex_coord1 = x1; - tex_coord2 = x2; - } - else if (EAST_MASK == direction) - { - tex_coord1 = y2; - tex_coord2 = y1; - } - else /* (NORTH_MASK == direction) */ - { - tex_coord1 = x2; - tex_coord2 = x1; - } - - - gGL.texCoord2f(tex_coord1*0.5f+0.5f, z1*0.5f); - gGL.vertex3f(x1, y1, z1); - - gGL.texCoord2f(tex_coord2*0.5f+0.5f, z2*0.5f); - gGL.vertex3f(x2, y2, z2); - - // top edge stairsteps - z = llmax(z2+height, z1+height); - gGL.texCoord2f(tex_coord2*0.5f+0.5f, z*0.5f); - gGL.vertex3f(x2, y2, z); - - gGL.texCoord2f(tex_coord1*0.5f+0.5f, z*0.5f); - gGL.vertex3f(x1, y1, z); - } -} - - -void LLViewerParcelMgr::renderHighlightSegments(const U8* segments, LLViewerRegion* regionp) -{ - S32 x, y; - F32 x1, y1; // start point - F32 x2, y2; // end point - bool has_segments = false; - - LLGLSUIDefault gls_ui; - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - LLGLDepthTest gls_depth(GL_TRUE); - - gGL.color4f(1.f, 1.f, 0.f, 0.2f); - - const S32 STRIDE = (mParcelsPerEdge+1); - - // Cheat and give this the same pick-name as land - - - for (y = 0; y < STRIDE; y++) - { - for (x = 0; x < STRIDE; x++) - { - U8 segment_mask = segments[x + y*STRIDE]; - - if (segment_mask & SOUTH_MASK) - { - x1 = x * PARCEL_GRID_STEP_METERS; - y1 = y * PARCEL_GRID_STEP_METERS; - - x2 = x1 + PARCEL_GRID_STEP_METERS; - y2 = y1; - - if (!has_segments) - { - has_segments = true; - gGL.begin(LLRender::QUADS); - } - renderOneSegment(x1, y1, x2, y2, PARCEL_POST_HEIGHT, SOUTH_MASK, regionp); - } - - if (segment_mask & WEST_MASK) - { - x1 = x * PARCEL_GRID_STEP_METERS; - y1 = y * PARCEL_GRID_STEP_METERS; - - x2 = x1; - y2 = y1 + PARCEL_GRID_STEP_METERS; - - if (!has_segments) - { - has_segments = true; - gGL.begin(LLRender::QUADS); - } - renderOneSegment(x1, y1, x2, y2, PARCEL_POST_HEIGHT, WEST_MASK, regionp); - } - } - } - - if (has_segments) - { - gGL.end(); - } -} - - -void LLViewerParcelMgr::renderCollisionSegments(U8* segments, bool use_pass, LLViewerRegion* regionp) -{ - - S32 x, y; - F32 x1, y1; // start point - F32 x2, y2; // end point - F32 alpha = 0; - F32 dist = 0; - F32 dx, dy; - F32 collision_height; - - const S32 STRIDE = (mParcelsPerEdge+1); - - LLVector3 pos = gAgent.getPositionAgent(); - - F32 pos_x = pos.mV[VX]; - F32 pos_y = pos.mV[VY]; - - LLGLSUIDefault gls_ui; - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); - LLGLDisable cull(GL_CULL_FACE); - - if (mCollisionBanned == BA_BANNED || - regionp->getRegionFlag(REGION_FLAGS_BLOCK_FLYOVER)) - { - collision_height = BAN_HEIGHT; - } - else - { - collision_height = PARCEL_HEIGHT; - } - - - if (use_pass && (mCollisionBanned == BA_NOT_ON_LIST)) - { - gGL.getTexUnit(0)->bind(mPassImage); - } - else - { - gGL.getTexUnit(0)->bind(mBlockedImage); - } - - gGL.begin(LLRender::QUADS); - - for (y = 0; y < STRIDE; y++) - { - for (x = 0; x < STRIDE; x++) - { - U8 segment_mask = segments[x + y*STRIDE]; - U8 direction; - const F32 MAX_ALPHA = 0.95f; - const S32 DIST_OFFSET = 5; - const S32 MIN_DIST_SQ = DIST_OFFSET*DIST_OFFSET; - const S32 MAX_DIST_SQ = 169; - - if (segment_mask & SOUTH_MASK) - { - x1 = x * PARCEL_GRID_STEP_METERS; - y1 = y * PARCEL_GRID_STEP_METERS; - - x2 = x1 + PARCEL_GRID_STEP_METERS; - y2 = y1; - - dy = (pos_y - y1) + DIST_OFFSET; - - if (pos_x < x1) - dx = pos_x - x1; - else if (pos_x > x2) - dx = pos_x - x2; - else - dx = 0; - - dist = dx*dx+dy*dy; - - if (dist < MIN_DIST_SQ) - alpha = MAX_ALPHA; - else if (dist > MAX_DIST_SQ) - alpha = 0.0f; - else - alpha = 30/dist; - - alpha = llclamp(alpha, 0.0f, MAX_ALPHA); - - gGL.color4f(1.f, 1.f, 1.f, alpha); - - if ((pos_y - y1) < 0) direction = SOUTH_MASK; - else direction = NORTH_MASK; - - // avoid Z fighting - renderOneSegment(x1+0.1f, y1+0.1f, x2+0.1f, y2+0.1f, collision_height, direction, regionp); - - } - - if (segment_mask & WEST_MASK) - { - x1 = x * PARCEL_GRID_STEP_METERS; - y1 = y * PARCEL_GRID_STEP_METERS; - - x2 = x1; - y2 = y1 + PARCEL_GRID_STEP_METERS; - - dx = (pos_x - x1) + DIST_OFFSET; - - if (pos_y < y1) - dy = pos_y - y1; - else if (pos_y > y2) - dy = pos_y - y2; - else - dy = 0; - - dist = dx*dx+dy*dy; - - if (dist < MIN_DIST_SQ) - alpha = MAX_ALPHA; - else if (dist > MAX_DIST_SQ) - alpha = 0.0f; - else - alpha = 30/dist; - - alpha = llclamp(alpha, 0.0f, MAX_ALPHA); - - gGL.color4f(1.f, 1.f, 1.f, alpha); - - if ((pos_x - x1) > 0) direction = WEST_MASK; - else direction = EAST_MASK; - - // avoid Z fighting - renderOneSegment(x1+0.1f, y1+0.1f, x2+0.1f, y2+0.1f, collision_height, direction, regionp); - - } - } - } - - gGL.end(); -} - -void LLViewerParcelMgr::resetCollisionTimer() -{ - mCollisionTimer.reset(); - mRenderCollision = true; -} - -void draw_line_cube(F32 width, const LLVector3& center) -{ - width = 0.5f * width; - gGL.vertex3f(center.mV[VX] + width ,center.mV[VY] + width,center.mV[VZ] + width); - gGL.vertex3f(center.mV[VX] - width ,center.mV[VY] + width,center.mV[VZ] + width); - gGL.vertex3f(center.mV[VX] - width ,center.mV[VY] + width,center.mV[VZ] + width); - gGL.vertex3f(center.mV[VX] - width ,center.mV[VY] - width,center.mV[VZ] + width); - gGL.vertex3f(center.mV[VX] - width ,center.mV[VY] - width,center.mV[VZ] + width); - gGL.vertex3f(center.mV[VX] + width ,center.mV[VY] - width,center.mV[VZ] + width); - gGL.vertex3f(center.mV[VX] + width ,center.mV[VY] - width,center.mV[VZ] + width); - gGL.vertex3f(center.mV[VX] + width ,center.mV[VY] + width,center.mV[VZ] + width); - - gGL.vertex3f(center.mV[VX] + width ,center.mV[VY] + width,center.mV[VZ] - width); - gGL.vertex3f(center.mV[VX] - width ,center.mV[VY] + width,center.mV[VZ] - width); - gGL.vertex3f(center.mV[VX] - width ,center.mV[VY] + width,center.mV[VZ] - width); - gGL.vertex3f(center.mV[VX] - width ,center.mV[VY] - width,center.mV[VZ] - width); - gGL.vertex3f(center.mV[VX] - width ,center.mV[VY] - width,center.mV[VZ] - width); - gGL.vertex3f(center.mV[VX] + width ,center.mV[VY] - width,center.mV[VZ] - width); - gGL.vertex3f(center.mV[VX] + width ,center.mV[VY] - width,center.mV[VZ] - width); - gGL.vertex3f(center.mV[VX] + width ,center.mV[VY] + width,center.mV[VZ] - width); - - gGL.vertex3f(center.mV[VX] + width ,center.mV[VY] + width,center.mV[VZ] + width); - gGL.vertex3f(center.mV[VX] + width ,center.mV[VY] + width,center.mV[VZ] - width); - gGL.vertex3f(center.mV[VX] - width ,center.mV[VY] + width,center.mV[VZ] + width); - gGL.vertex3f(center.mV[VX] - width ,center.mV[VY] + width,center.mV[VZ] - width); - gGL.vertex3f(center.mV[VX] - width ,center.mV[VY] - width,center.mV[VZ] + width); - gGL.vertex3f(center.mV[VX] - width ,center.mV[VY] - width,center.mV[VZ] - width); - gGL.vertex3f(center.mV[VX] + width ,center.mV[VY] - width,center.mV[VZ] + width); - gGL.vertex3f(center.mV[VX] + width ,center.mV[VY] - width,center.mV[VZ] - width); -} - -void draw_cross_lines(const LLVector3& center, F32 dx, F32 dy, F32 dz) -{ - gGL.vertex3f(center.mV[VX] - dx, center.mV[VY], center.mV[VZ]); - gGL.vertex3f(center.mV[VX] + dx, center.mV[VY], center.mV[VZ]); - gGL.vertex3f(center.mV[VX], center.mV[VY] - dy, center.mV[VZ]); - gGL.vertex3f(center.mV[VX], center.mV[VY] + dy, center.mV[VZ]); - gGL.vertex3f(center.mV[VX], center.mV[VY], center.mV[VZ] - dz); - gGL.vertex3f(center.mV[VX], center.mV[VY], center.mV[VZ] + dz); -} - -void LLViewerObjectList::renderObjectBeacons() -{ - if (mDebugBeacons.empty()) - { - return; - } - - LLGLSUIDefault gls_ui; - - gUIProgram.bind(); - - { - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - S32 last_line_width = -1; - // gGL.begin(LLRender::LINES); // Always happens in (line_width != last_line_width) - - for (std::vector::iterator iter = mDebugBeacons.begin(); iter != mDebugBeacons.end(); ++iter) - { - const LLDebugBeacon &debug_beacon = *iter; - LLColor4 color = debug_beacon.mColor; - color.mV[3] *= 0.25f; - S32 line_width = debug_beacon.mLineWidth; - if (line_width != last_line_width) - { - gGL.flush(); - glLineWidth( (F32)line_width ); - last_line_width = line_width; - } - - const LLVector3 &thisline = debug_beacon.mPositionAgent; - - gGL.begin(LLRender::LINES); - gGL.color4fv(linearColor4(color).mV); - draw_cross_lines(thisline, 2.0f, 2.0f, 50.f); - draw_line_cube(0.10f, thisline); - - gGL.end(); - } - } - - { - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - LLGLDepthTest gls_depth(GL_TRUE); - - S32 last_line_width = -1; - // gGL.begin(LLRender::LINES); // Always happens in (line_width != last_line_width) - - for (std::vector::iterator iter = mDebugBeacons.begin(); iter != mDebugBeacons.end(); ++iter) - { - const LLDebugBeacon &debug_beacon = *iter; - - S32 line_width = debug_beacon.mLineWidth; - if (line_width != last_line_width) - { - gGL.flush(); - glLineWidth( (F32)line_width ); - last_line_width = line_width; - } - - const LLVector3 &thisline = debug_beacon.mPositionAgent; - gGL.begin(LLRender::LINES); - gGL.color4fv(linearColor4(debug_beacon.mColor).mV); - draw_cross_lines(thisline, 0.5f, 0.5f, 0.5f); - draw_line_cube(0.10f, thisline); - - gGL.end(); - } - - gGL.flush(); - glLineWidth(1.f); - - for (std::vector::iterator iter = mDebugBeacons.begin(); iter != mDebugBeacons.end(); ++iter) - { - LLDebugBeacon &debug_beacon = *iter; - if (debug_beacon.mString == "") - { - continue; - } - LLHUDText *hud_textp = (LLHUDText *)LLHUDObject::addHUDObject(LLHUDObject::LL_HUD_TEXT); - - hud_textp->setZCompare(false); - LLColor4 color; - color = debug_beacon.mTextColor; - color.mV[3] *= 1.f; - - hud_textp->setString(debug_beacon.mString); - hud_textp->setColor(color); - hud_textp->setPositionAgent(debug_beacon.mPositionAgent); - debug_beacon.mHUDObject = hud_textp; - } - } -} - -void LLSky::renderSunMoonBeacons(const LLVector3& pos_agent, const LLVector3& direction, LLColor4 color) -{ - LLGLSUIDefault gls_ui; - gUIProgram.bind(); - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - LLVector3 pos_end; - for (S32 i = 0; i < 3; ++i) - { - pos_end.mV[i] = pos_agent.mV[i] + (50 * direction.mV[i]); - } - glLineWidth(LLPipeline::DebugBeaconLineWidth); - gGL.begin(LLRender::LINES); - color.mV[3] *= 0.5f; - gGL.color4fv(color.mV); - draw_cross_lines(pos_agent, 0.5f, 0.5f, 0.5f); - draw_cross_lines(pos_end, 2.f, 2.f, 2.f); - gGL.vertex3fv(pos_agent.mV); - gGL.vertex3fv(pos_end.mV); - gGL.end(); - - gGL.flush(); - glLineWidth(1.f); - -} - -//----------------------------------------------------------------------------- -// gpu_benchmark() helper classes -//----------------------------------------------------------------------------- - -// This struct is used to ensure that once we call initProfile(), it will -// definitely be matched by a corresponding call to finishProfile(). It's -// a struct rather than a class simply because every member is public. -struct ShaderProfileHelper -{ - ShaderProfileHelper() - { - LLGLSLShader::initProfile(); - } - ~ShaderProfileHelper() - { - LLGLSLShader::finishProfile(false); - } -}; - -// This helper class is used to ensure that each generateTextures() call -// is matched by a corresponding deleteTextures() call. It also handles -// the bindManual() calls using those textures. -class TextureHolder -{ -public: - TextureHolder(U32 unit, U32 size) : - texUnit(gGL.getTexUnit(unit)), - source(size) // preallocate vector - { - // takes (count, pointer) - // &vector[0] gets pointer to contiguous array - LLImageGL::generateTextures(source.size(), &source[0]); - } - - ~TextureHolder() - { - // unbind - if (texUnit) - { - texUnit->unbind(LLTexUnit::TT_TEXTURE); - } - // ensure that we delete these textures regardless of how we exit - LLImageGL::deleteTextures(source.size(), &source[0]); - } - - bool bind(U32 index) - { - if (texUnit) // should always be there with dummy (-1), but just in case - { - return texUnit->bindManual(LLTexUnit::TT_TEXTURE, source[index]); - } - return false; - } - -private: - // capture which LLTexUnit we're going to use - LLTexUnit* texUnit; - - // use std::vector for implicit resource management - std::vector source; -}; - -class ShaderBinder -{ -public: - ShaderBinder(LLGLSLShader& shader) : - mShader(shader) - { - mShader.bind(); - } - ~ShaderBinder() - { - mShader.unbind(); - } - -private: - LLGLSLShader& mShader; -}; - - -//----------------------------------------------------------------------------- -// gpu_benchmark() -// returns measured memory bandwidth of GPU in gigabytes per second -//----------------------------------------------------------------------------- -F32 gpu_benchmark() -{ - if (gGLManager.mGLVersion < 3.3f) - { // don't bother benchmarking venerable drivers which don't support accurate timing anyway - return -1.f; - } - - if (gBenchmarkProgram.mProgramObject == 0) - { - LLViewerShaderMgr::instance()->initAttribsAndUniforms(); - - gBenchmarkProgram.mName = "Benchmark Shader"; - gBenchmarkProgram.mFeatures.attachNothing = true; - gBenchmarkProgram.mShaderFiles.clear(); - gBenchmarkProgram.mShaderFiles.push_back(std::make_pair("interface/benchmarkV.glsl", GL_VERTEX_SHADER)); - gBenchmarkProgram.mShaderFiles.push_back(std::make_pair("interface/benchmarkF.glsl", GL_FRAGMENT_SHADER)); - gBenchmarkProgram.mShaderLevel = 1; - if (!gBenchmarkProgram.createShader(NULL, NULL)) - { - return -1.f; - } - } - - LLGLDisable blend(GL_BLEND); - - //measure memory bandwidth by: - // - allocating a batch of textures and render targets - // - rendering those textures to those render targets - // - recording time taken - // - taking the median time for a given number of samples - - //resolution of textures/render targets - const U32 res = 1024; - - //number of textures - const U32 count = 32; - - //number of samples to take - const S32 samples = 64; - - //time limit, allocation operations shouldn't take longer then 30 seconds, same for actual benchmark. - const F32 time_limit = 30; - - std::vector dest(count); - TextureHolder texHolder(0, count); - std::vector results; - - //build a random texture - U8* pixels = new U8[res*res*4]; - - for (U32 i = 0; i < res*res*4; ++i) - { - pixels[i] = (U8) ll_rand(255); - } - - gGL.setColorMask(true, true); - LLGLDepthTest depth(GL_FALSE); - - LLTimer alloc_timer; - alloc_timer.start(); - for (U32 i = 0; i < count; ++i) - { - //allocate render targets and textures - if (!dest[i].allocate(res, res, GL_RGBA)) - { - LL_WARNS("Benchmark") << "Failed to allocate render target." << LL_ENDL; - // abandon the benchmark test - delete[] pixels; - return -1.f; - } - dest[i].bindTarget(); - dest[i].clear(); - dest[i].flush(); - - if (!texHolder.bind(i)) - { - // can use a dummy value mDummyTexUnit = new LLTexUnit(-1); - LL_WARNS("Benchmark") << "Failed to bind tex unit." << LL_ENDL; - // abandon the benchmark test - delete[] pixels; - return -1.f; - } - LLImageGL::setManualImage(GL_TEXTURE_2D, 0, GL_RGBA, res,res,GL_RGBA, GL_UNSIGNED_BYTE, pixels); - - if (alloc_timer.getElapsedTimeF32() > time_limit) - { - // abandon the benchmark test - LL_WARNS("Benchmark") << "Allocation operation took longer then 30 seconds, stopping." << LL_ENDL; - delete[] pixels; - return -1.f; - } - } - - delete [] pixels; - - //make a dummy triangle to draw with - LLPointer buff = new LLVertexBuffer(LLVertexBuffer::MAP_VERTEX); - - if (!buff->allocateBuffer(3, 0)) - { - LL_WARNS("Benchmark") << "Failed to allocate buffer during benchmark." << LL_ENDL; - // abandon the benchmark test - return -1.f; - } - - LLStrider v; - - if (! buff->getVertexStrider(v)) - { - LL_WARNS("Benchmark") << "GL LLVertexBuffer::getVertexStrider() returned false, " - << "buff->getMappedData() is" - << (buff->getMappedData()? " not" : "") - << " NULL" << LL_ENDL; - // abandon the benchmark test - return -1.f; - } - - // generate dummy triangle - v[0].set(-1, 1, 0); - v[1].set(-1, -3, 0); - v[2].set(3, 1, 0); - - buff->unmapBuffer(); - - LLGLSLShader::unbind(); - - F32 time_passed = 0; // seconds - - { //run CPU timer benchmark - glFinish(); - gBenchmarkProgram.bind(); - for (S32 c = -1; c < samples && time_passed < time_limit; ++c) - { - LLTimer timer; - timer.start(); - - for (U32 i = 0; i < count; ++i) - { - dest[i].bindTarget(); - texHolder.bind(i); - buff->setBuffer(); - buff->drawArrays(LLRender::TRIANGLES, 0, 3); - dest[i].flush(); - } - - //wait for current batch of copies to finish - glFinish(); - - F32 time = timer.getElapsedTimeF32(); - time_passed += time; - - if (c >= 0) // <-- ignore the first sample as it tends to be artificially slow - { - //store result in gigabytes per second - F32 gb = (F32)((F64)(res * res * 8 * count)) / (1000000000); - F32 gbps = gb / time; - results.push_back(gbps); - } - } - gBenchmarkProgram.unbind(); - } - - std::sort(results.begin(), results.end()); - - F32 gbps = results[results.size()/2]; - - LL_INFOS("Benchmark") << "Memory bandwidth is " << llformat("%.3f", gbps) << " GB/sec according to CPU timers, " << (F32)results.size() << " tests took " << time_passed << " seconds" << LL_ENDL; - -#if LL_DARWIN - if (gbps > 512.f) - { - LL_WARNS("Benchmark") << "Memory bandwidth is improbably high and likely incorrect; discarding result." << LL_ENDL; - //OSX is probably lying, discard result - return -1.f; - } -#endif - - // run GPU timer benchmark - { - ShaderProfileHelper initProfile; - dest[0].bindTarget(); - gBenchmarkProgram.bind(); - for (S32 c = 0; c < samples; ++c) - { - for (U32 i = 0; i < count; ++i) - { - texHolder.bind(i); - buff->setBuffer(); - buff->drawArrays(LLRender::TRIANGLES, 0, 3); - } - } - gBenchmarkProgram.unbind(); - dest[0].flush(); - } - - F32 ms = gBenchmarkProgram.mTimeElapsed/1000000.f; - F32 seconds = ms/1000.f; - - F64 samples_drawn = gBenchmarkProgram.mSamplesDrawn; - F32 samples_sec = (samples_drawn/1000000000.0)/seconds; - gbps = samples_sec*4; // 4 bytes per sample - - LL_INFOS("Benchmark") << "Memory bandwidth is " << llformat("%.3f", gbps) << " GB/sec according to ARB_timer_query, total time " << seconds << " seconds" << LL_ENDL; - - return gbps; -} +/** + * @file llglsandbox.cpp + * @brief GL functionality access + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/** + * Contains ALL methods which directly access GL functionality + * except for core rendering engine functionality. + */ + +#include "llviewerprecompiledheaders.h" + +#include "llviewercontrol.h" + +#include "llgl.h" +#include "llrender.h" +#include "llglheaders.h" +#include "llparcel.h" +#include "llui.h" + +#include "lldrawable.h" +#include "lltextureentry.h" +#include "llviewercamera.h" + +#include "llvoavatarself.h" +#include "llsky.h" +#include "llagent.h" +#include "lltoolmgr.h" +#include "llselectmgr.h" +#include "llhudmanager.h" +#include "llhudtext.h" +#include "llrendersphere.h" +#include "llviewerobjectlist.h" +#include "lltoolselectrect.h" +#include "llviewerwindow.h" +#include "llsurface.h" +#include "llwind.h" +#include "llworld.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "llpreviewtexture.h" +#include "llresmgr.h" +#include "pipeline.h" +#include "llspatialpartition.h" +#include "llviewershadermgr.h" + +#include + +// Height of the yellow selection highlight posts for land +const F32 PARCEL_POST_HEIGHT = 0.666f; + +// Returns true if you got at least one object +void LLToolSelectRect::handleRectangleSelection(S32 x, S32 y, MASK mask) +{ + LLVector3 av_pos = gAgent.getPositionAgent(); + F32 select_dist_squared = gSavedSettings.getF32("MaxSelectDistance"); + select_dist_squared = select_dist_squared * select_dist_squared; + + bool deselect = (mask == MASK_CONTROL); + S32 left = llmin(x, mDragStartX); + S32 right = llmax(x, mDragStartX); + S32 top = llmax(y, mDragStartY); + S32 bottom =llmin(y, mDragStartY); + + left = ll_round((F32) left * LLUI::getScaleFactor().mV[VX]); + right = ll_round((F32) right * LLUI::getScaleFactor().mV[VX]); + top = ll_round((F32) top * LLUI::getScaleFactor().mV[VY]); + bottom = ll_round((F32) bottom * LLUI::getScaleFactor().mV[VY]); + + F32 old_far_plane = LLViewerCamera::getInstance()->getFar(); + F32 old_near_plane = LLViewerCamera::getInstance()->getNear(); + + S32 width = right - left + 1; + S32 height = top - bottom + 1; + + bool grow_selection = false; + bool shrink_selection = false; + + if (height > mDragLastHeight || width > mDragLastWidth) + { + grow_selection = true; + } + if (height < mDragLastHeight || width < mDragLastWidth) + { + shrink_selection = true; + } + + if (!grow_selection && !shrink_selection) + { + // nothing to do + return; + } + + mDragLastHeight = height; + mDragLastWidth = width; + + S32 center_x = (left + right) / 2; + S32 center_y = (top + bottom) / 2; + + // save drawing mode + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.pushMatrix(); + + bool limit_select_distance = gSavedSettings.getBOOL("LimitSelectDistance"); + if (limit_select_distance) + { + // ...select distance from control + LLVector3 relative_av_pos = av_pos; + relative_av_pos -= LLViewerCamera::getInstance()->getOrigin(); + + F32 new_far = relative_av_pos * LLViewerCamera::getInstance()->getAtAxis() + gSavedSettings.getF32("MaxSelectDistance"); + F32 new_near = relative_av_pos * LLViewerCamera::getInstance()->getAtAxis() - gSavedSettings.getF32("MaxSelectDistance"); + + new_near = llmax(new_near, 0.1f); + + LLViewerCamera::getInstance()->setFar(new_far); + LLViewerCamera::getInstance()->setNear(new_near); + } + LLViewerCamera::getInstance()->setPerspective(FOR_SELECTION, + center_x-width/2, center_y-height/2, width, height, + limit_select_distance); + + if (shrink_selection) + { + struct f : public LLSelectedObjectFunctor + { + virtual bool apply(LLViewerObject* vobjp) + { + LLDrawable* drawable = vobjp->mDrawable; + if (!drawable || vobjp->getPCode() != LL_PCODE_VOLUME || vobjp->isAttachment()) + { + return true; + } + S32 result = LLViewerCamera::getInstance()->sphereInFrustum(drawable->getPositionAgent(), drawable->getRadius()); + switch (result) + { + case 0: + LLSelectMgr::getInstance()->unhighlightObjectOnly(vobjp); + break; + case 1: + // check vertices + if (!LLViewerCamera::getInstance()->areVertsVisible(vobjp, LLSelectMgr::sRectSelectInclusive)) + { + LLSelectMgr::getInstance()->unhighlightObjectOnly(vobjp); + } + break; + default: + break; + } + return true; + } + } func; + LLSelectMgr::getInstance()->getHighlightedObjects()->applyToObjects(&func); + } + + if (grow_selection) + { + std::vector potentials; + + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* region = *iter; + for (U32 i = 0; i < LLViewerRegion::NUM_PARTITIONS; i++) + { + LLSpatialPartition* part = region->getSpatialPartition(i); + if (part) + { + part->cull(*LLViewerCamera::getInstance(), &potentials, true); + } + } + } + + for (std::vector::iterator iter = potentials.begin(); + iter != potentials.end(); iter++) + { + LLDrawable* drawable = *iter; + LLViewerObject* vobjp = drawable->getVObj(); + + if (!drawable || !vobjp || + vobjp->getPCode() != LL_PCODE_VOLUME || + vobjp->isAttachment() || + (deselect && !vobjp->isSelected())) + { + continue; + } + + if (limit_select_distance && dist_vec_squared(drawable->getWorldPosition(), av_pos) > select_dist_squared) + { + continue; + } + + S32 result = LLViewerCamera::getInstance()->sphereInFrustum(drawable->getPositionAgent(), drawable->getRadius()); + if (result) + { + switch (result) + { + case 1: + // check vertices + if (LLViewerCamera::getInstance()->areVertsVisible(vobjp, LLSelectMgr::sRectSelectInclusive)) + { + LLSelectMgr::getInstance()->highlightObjectOnly(vobjp); + } + break; + case 2: + LLSelectMgr::getInstance()->highlightObjectOnly(vobjp); + break; + default: + break; + } + } + } + } + + // restore drawing mode + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.popMatrix(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + + // restore camera + LLViewerCamera::getInstance()->setFar(old_far_plane); + LLViewerCamera::getInstance()->setNear(old_near_plane); + gViewerWindow->setup3DRender(); +} + +const F32 WIND_RELATIVE_ALTITUDE = 25.f; + +void LLWind::renderVectors() +{ + // Renders the wind as vectors (used for debug) + S32 i,j; + F32 x,y; + + F32 region_width_meters = LLWorld::getInstance()->getRegionWidthInMeters(); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gGL.pushMatrix(); + LLVector3 origin_agent; + origin_agent = gAgent.getPosAgentFromGlobal(mOriginGlobal); + gGL.translatef(origin_agent.mV[VX], origin_agent.mV[VY], gAgent.getPositionAgent().mV[VZ] + WIND_RELATIVE_ALTITUDE); + for (j = 0; j < mSize; j++) + { + for (i = 0; i < mSize; i++) + { + x = mVelX[i + j*mSize] * WIND_SCALE_HACK; + y = mVelY[i + j*mSize] * WIND_SCALE_HACK; + gGL.pushMatrix(); + gGL.translatef((F32)i * region_width_meters/mSize, (F32)j * region_width_meters/mSize, 0.0); + gGL.color3f(0,1,0); + gGL.begin(LLRender::POINTS); + gGL.vertex3f(0,0,0); + gGL.end(); + gGL.color3f(1,0,0); + gGL.begin(LLRender::LINES); + gGL.vertex3f(x * 0.1f, y * 0.1f ,0.f); + gGL.vertex3f(x, y, 0.f); + gGL.end(); + gGL.popMatrix(); + } + } + gGL.popMatrix(); +} + + + + +// Used by lltoolselectland +void LLViewerParcelMgr::renderRect(const LLVector3d &west_south_bottom_global, + const LLVector3d &east_north_top_global ) +{ + LLGLSUIDefault gls_ui; + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + LLGLDepthTest gls_depth(GL_TRUE); + + LLVector3 west_south_bottom_agent = gAgent.getPosAgentFromGlobal(west_south_bottom_global); + F32 west = west_south_bottom_agent.mV[VX]; + F32 south = west_south_bottom_agent.mV[VY]; +// F32 bottom = west_south_bottom_agent.mV[VZ] - 1.f; + + LLVector3 east_north_top_agent = gAgent.getPosAgentFromGlobal(east_north_top_global); + F32 east = east_north_top_agent.mV[VX]; + F32 north = east_north_top_agent.mV[VY]; +// F32 top = east_north_top_agent.mV[VZ] + 1.f; + + // HACK: At edge of last region of world, we need to make sure the region + // resolves correctly so we can get a height value. + const F32 FUDGE = 0.01f; + + F32 sw_bottom = LLWorld::getInstance()->resolveLandHeightAgent( LLVector3( west, south, 0.f ) ); + F32 se_bottom = LLWorld::getInstance()->resolveLandHeightAgent( LLVector3( east-FUDGE, south, 0.f ) ); + F32 ne_bottom = LLWorld::getInstance()->resolveLandHeightAgent( LLVector3( east-FUDGE, north-FUDGE, 0.f ) ); + F32 nw_bottom = LLWorld::getInstance()->resolveLandHeightAgent( LLVector3( west, north-FUDGE, 0.f ) ); + + F32 sw_top = sw_bottom + PARCEL_POST_HEIGHT; + F32 se_top = se_bottom + PARCEL_POST_HEIGHT; + F32 ne_top = ne_bottom + PARCEL_POST_HEIGHT; + F32 nw_top = nw_bottom + PARCEL_POST_HEIGHT; + + LLUI::setLineWidth(2.f); + gGL.color4f(1.f, 1.f, 0.f, 1.f); + + // Cheat and give this the same pick-name as land + gGL.begin(LLRender::LINES); + + gGL.vertex3f(west, north, nw_bottom); + gGL.vertex3f(west, north, nw_top); + + gGL.vertex3f(east, north, ne_bottom); + gGL.vertex3f(east, north, ne_top); + + gGL.vertex3f(east, south, se_bottom); + gGL.vertex3f(east, south, se_top); + + gGL.vertex3f(west, south, sw_bottom); + gGL.vertex3f(west, south, sw_top); + + gGL.end(); + + gGL.color4f(1.f, 1.f, 0.f, 0.2f); + gGL.begin(LLRender::QUADS); + + gGL.vertex3f(west, north, nw_bottom); + gGL.vertex3f(west, north, nw_top); + gGL.vertex3f(east, north, ne_top); + gGL.vertex3f(east, north, ne_bottom); + + gGL.vertex3f(east, north, ne_bottom); + gGL.vertex3f(east, north, ne_top); + gGL.vertex3f(east, south, se_top); + gGL.vertex3f(east, south, se_bottom); + + gGL.vertex3f(east, south, se_bottom); + gGL.vertex3f(east, south, se_top); + gGL.vertex3f(west, south, sw_top); + gGL.vertex3f(west, south, sw_bottom); + + gGL.vertex3f(west, south, sw_bottom); + gGL.vertex3f(west, south, sw_top); + gGL.vertex3f(west, north, nw_top); + gGL.vertex3f(west, north, nw_bottom); + + gGL.end(); + + LLUI::setLineWidth(1.f); +} + +/* +void LLViewerParcelMgr::renderParcel(LLParcel* parcel ) +{ + S32 i; + S32 count = parcel->getBoxCount(); + for (i = 0; i < count; i++) + { + const LLParcelBox& box = parcel->getBox(i); + + F32 west = box.mMin.mV[VX]; + F32 south = box.mMin.mV[VY]; + + F32 east = box.mMax.mV[VX]; + F32 north = box.mMax.mV[VY]; + + // HACK: At edge of last region of world, we need to make sure the region + // resolves correctly so we can get a height value. + const F32 FUDGE = 0.01f; + + F32 sw_bottom = LLWorld::getInstance()->resolveLandHeightAgent( LLVector3( west, south, 0.f ) ); + F32 se_bottom = LLWorld::getInstance()->resolveLandHeightAgent( LLVector3( east-FUDGE, south, 0.f ) ); + F32 ne_bottom = LLWorld::getInstance()->resolveLandHeightAgent( LLVector3( east-FUDGE, north-FUDGE, 0.f ) ); + F32 nw_bottom = LLWorld::getInstance()->resolveLandHeightAgent( LLVector3( west, north-FUDGE, 0.f ) ); + + // little hack to make nearby lines not Z-fight + east -= 0.1f; + north -= 0.1f; + + F32 sw_top = sw_bottom + POST_HEIGHT; + F32 se_top = se_bottom + POST_HEIGHT; + F32 ne_top = ne_bottom + POST_HEIGHT; + F32 nw_top = nw_bottom + POST_HEIGHT; + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + LLGLDepthTest gls_depth(GL_TRUE); + + LLUI::setLineWidth(2.f); + gGL.color4f(0.f, 1.f, 1.f, 1.f); + + // Cheat and give this the same pick-name as land + gGL.begin(LLRender::LINES); + + gGL.vertex3f(west, north, nw_bottom); + gGL.vertex3f(west, north, nw_top); + + gGL.vertex3f(east, north, ne_bottom); + gGL.vertex3f(east, north, ne_top); + + gGL.vertex3f(east, south, se_bottom); + gGL.vertex3f(east, south, se_top); + + gGL.vertex3f(west, south, sw_bottom); + gGL.vertex3f(west, south, sw_top); + + gGL.end(); + + gGL.color4f(0.f, 1.f, 1.f, 0.2f); + gGL.begin(LLRender::QUADS); + + gGL.vertex3f(west, north, nw_bottom); + gGL.vertex3f(west, north, nw_top); + gGL.vertex3f(east, north, ne_top); + gGL.vertex3f(east, north, ne_bottom); + + gGL.vertex3f(east, north, ne_bottom); + gGL.vertex3f(east, north, ne_top); + gGL.vertex3f(east, south, se_top); + gGL.vertex3f(east, south, se_bottom); + + gGL.vertex3f(east, south, se_bottom); + gGL.vertex3f(east, south, se_top); + gGL.vertex3f(west, south, sw_top); + gGL.vertex3f(west, south, sw_bottom); + + gGL.vertex3f(west, south, sw_bottom); + gGL.vertex3f(west, south, sw_top); + gGL.vertex3f(west, north, nw_top); + gGL.vertex3f(west, north, nw_bottom); + + gGL.end(); + + LLUI::setLineWidth(1.f); + } +} +*/ + + +// north = a wall going north/south. Need that info to set up texture +// coordinates correctly. +void LLViewerParcelMgr::renderOneSegment(F32 x1, F32 y1, F32 x2, F32 y2, F32 height, U8 direction, LLViewerRegion* regionp) +{ + // HACK: At edge of last region of world, we need to make sure the region + // resolves correctly so we can get a height value. + const F32 BORDER = REGION_WIDTH_METERS - 0.1f; + + F32 clamped_x1 = x1; + F32 clamped_y1 = y1; + F32 clamped_x2 = x2; + F32 clamped_y2 = y2; + + if (clamped_x1 > BORDER) clamped_x1 = BORDER; + if (clamped_y1 > BORDER) clamped_y1 = BORDER; + if (clamped_x2 > BORDER) clamped_x2 = BORDER; + if (clamped_y2 > BORDER) clamped_y2 = BORDER; + + F32 z; + F32 z1; + F32 z2; + + z1 = regionp->getLand().resolveHeightRegion( LLVector3( clamped_x1, clamped_y1, 0.f ) ); + z2 = regionp->getLand().resolveHeightRegion( LLVector3( clamped_x2, clamped_y2, 0.f ) ); + + // Convert x1 and x2 from region-local to agent coords. + LLVector3 origin = regionp->getOriginAgent(); + x1 += origin.mV[VX]; + x2 += origin.mV[VX]; + y1 += origin.mV[VY]; + y2 += origin.mV[VY]; + + if (height < 1.f) + { + z = z1+height; + gGL.vertex3f(x1, y1, z); + + gGL.vertex3f(x1, y1, z1); + + gGL.vertex3f(x2, y2, z2); + + z = z2+height; + gGL.vertex3f(x2, y2, z); + } + else + { + F32 tex_coord1; + F32 tex_coord2; + + if (WEST_MASK == direction) + { + tex_coord1 = y1; + tex_coord2 = y2; + } + else if (SOUTH_MASK == direction) + { + tex_coord1 = x1; + tex_coord2 = x2; + } + else if (EAST_MASK == direction) + { + tex_coord1 = y2; + tex_coord2 = y1; + } + else /* (NORTH_MASK == direction) */ + { + tex_coord1 = x2; + tex_coord2 = x1; + } + + + gGL.texCoord2f(tex_coord1*0.5f+0.5f, z1*0.5f); + gGL.vertex3f(x1, y1, z1); + + gGL.texCoord2f(tex_coord2*0.5f+0.5f, z2*0.5f); + gGL.vertex3f(x2, y2, z2); + + // top edge stairsteps + z = llmax(z2+height, z1+height); + gGL.texCoord2f(tex_coord2*0.5f+0.5f, z*0.5f); + gGL.vertex3f(x2, y2, z); + + gGL.texCoord2f(tex_coord1*0.5f+0.5f, z*0.5f); + gGL.vertex3f(x1, y1, z); + } +} + + +void LLViewerParcelMgr::renderHighlightSegments(const U8* segments, LLViewerRegion* regionp) +{ + S32 x, y; + F32 x1, y1; // start point + F32 x2, y2; // end point + bool has_segments = false; + + LLGLSUIDefault gls_ui; + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + LLGLDepthTest gls_depth(GL_TRUE); + + gGL.color4f(1.f, 1.f, 0.f, 0.2f); + + const S32 STRIDE = (mParcelsPerEdge+1); + + // Cheat and give this the same pick-name as land + + + for (y = 0; y < STRIDE; y++) + { + for (x = 0; x < STRIDE; x++) + { + U8 segment_mask = segments[x + y*STRIDE]; + + if (segment_mask & SOUTH_MASK) + { + x1 = x * PARCEL_GRID_STEP_METERS; + y1 = y * PARCEL_GRID_STEP_METERS; + + x2 = x1 + PARCEL_GRID_STEP_METERS; + y2 = y1; + + if (!has_segments) + { + has_segments = true; + gGL.begin(LLRender::QUADS); + } + renderOneSegment(x1, y1, x2, y2, PARCEL_POST_HEIGHT, SOUTH_MASK, regionp); + } + + if (segment_mask & WEST_MASK) + { + x1 = x * PARCEL_GRID_STEP_METERS; + y1 = y * PARCEL_GRID_STEP_METERS; + + x2 = x1; + y2 = y1 + PARCEL_GRID_STEP_METERS; + + if (!has_segments) + { + has_segments = true; + gGL.begin(LLRender::QUADS); + } + renderOneSegment(x1, y1, x2, y2, PARCEL_POST_HEIGHT, WEST_MASK, regionp); + } + } + } + + if (has_segments) + { + gGL.end(); + } +} + + +void LLViewerParcelMgr::renderCollisionSegments(U8* segments, bool use_pass, LLViewerRegion* regionp) +{ + + S32 x, y; + F32 x1, y1; // start point + F32 x2, y2; // end point + F32 alpha = 0; + F32 dist = 0; + F32 dx, dy; + F32 collision_height; + + const S32 STRIDE = (mParcelsPerEdge+1); + + LLVector3 pos = gAgent.getPositionAgent(); + + F32 pos_x = pos.mV[VX]; + F32 pos_y = pos.mV[VY]; + + LLGLSUIDefault gls_ui; + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); + LLGLDisable cull(GL_CULL_FACE); + + if (mCollisionBanned == BA_BANNED || + regionp->getRegionFlag(REGION_FLAGS_BLOCK_FLYOVER)) + { + collision_height = BAN_HEIGHT; + } + else + { + collision_height = PARCEL_HEIGHT; + } + + + if (use_pass && (mCollisionBanned == BA_NOT_ON_LIST)) + { + gGL.getTexUnit(0)->bind(mPassImage); + } + else + { + gGL.getTexUnit(0)->bind(mBlockedImage); + } + + gGL.begin(LLRender::QUADS); + + for (y = 0; y < STRIDE; y++) + { + for (x = 0; x < STRIDE; x++) + { + U8 segment_mask = segments[x + y*STRIDE]; + U8 direction; + const F32 MAX_ALPHA = 0.95f; + const S32 DIST_OFFSET = 5; + const S32 MIN_DIST_SQ = DIST_OFFSET*DIST_OFFSET; + const S32 MAX_DIST_SQ = 169; + + if (segment_mask & SOUTH_MASK) + { + x1 = x * PARCEL_GRID_STEP_METERS; + y1 = y * PARCEL_GRID_STEP_METERS; + + x2 = x1 + PARCEL_GRID_STEP_METERS; + y2 = y1; + + dy = (pos_y - y1) + DIST_OFFSET; + + if (pos_x < x1) + dx = pos_x - x1; + else if (pos_x > x2) + dx = pos_x - x2; + else + dx = 0; + + dist = dx*dx+dy*dy; + + if (dist < MIN_DIST_SQ) + alpha = MAX_ALPHA; + else if (dist > MAX_DIST_SQ) + alpha = 0.0f; + else + alpha = 30/dist; + + alpha = llclamp(alpha, 0.0f, MAX_ALPHA); + + gGL.color4f(1.f, 1.f, 1.f, alpha); + + if ((pos_y - y1) < 0) direction = SOUTH_MASK; + else direction = NORTH_MASK; + + // avoid Z fighting + renderOneSegment(x1+0.1f, y1+0.1f, x2+0.1f, y2+0.1f, collision_height, direction, regionp); + + } + + if (segment_mask & WEST_MASK) + { + x1 = x * PARCEL_GRID_STEP_METERS; + y1 = y * PARCEL_GRID_STEP_METERS; + + x2 = x1; + y2 = y1 + PARCEL_GRID_STEP_METERS; + + dx = (pos_x - x1) + DIST_OFFSET; + + if (pos_y < y1) + dy = pos_y - y1; + else if (pos_y > y2) + dy = pos_y - y2; + else + dy = 0; + + dist = dx*dx+dy*dy; + + if (dist < MIN_DIST_SQ) + alpha = MAX_ALPHA; + else if (dist > MAX_DIST_SQ) + alpha = 0.0f; + else + alpha = 30/dist; + + alpha = llclamp(alpha, 0.0f, MAX_ALPHA); + + gGL.color4f(1.f, 1.f, 1.f, alpha); + + if ((pos_x - x1) > 0) direction = WEST_MASK; + else direction = EAST_MASK; + + // avoid Z fighting + renderOneSegment(x1+0.1f, y1+0.1f, x2+0.1f, y2+0.1f, collision_height, direction, regionp); + + } + } + } + + gGL.end(); +} + +void LLViewerParcelMgr::resetCollisionTimer() +{ + mCollisionTimer.reset(); + mRenderCollision = true; +} + +void draw_line_cube(F32 width, const LLVector3& center) +{ + width = 0.5f * width; + gGL.vertex3f(center.mV[VX] + width ,center.mV[VY] + width,center.mV[VZ] + width); + gGL.vertex3f(center.mV[VX] - width ,center.mV[VY] + width,center.mV[VZ] + width); + gGL.vertex3f(center.mV[VX] - width ,center.mV[VY] + width,center.mV[VZ] + width); + gGL.vertex3f(center.mV[VX] - width ,center.mV[VY] - width,center.mV[VZ] + width); + gGL.vertex3f(center.mV[VX] - width ,center.mV[VY] - width,center.mV[VZ] + width); + gGL.vertex3f(center.mV[VX] + width ,center.mV[VY] - width,center.mV[VZ] + width); + gGL.vertex3f(center.mV[VX] + width ,center.mV[VY] - width,center.mV[VZ] + width); + gGL.vertex3f(center.mV[VX] + width ,center.mV[VY] + width,center.mV[VZ] + width); + + gGL.vertex3f(center.mV[VX] + width ,center.mV[VY] + width,center.mV[VZ] - width); + gGL.vertex3f(center.mV[VX] - width ,center.mV[VY] + width,center.mV[VZ] - width); + gGL.vertex3f(center.mV[VX] - width ,center.mV[VY] + width,center.mV[VZ] - width); + gGL.vertex3f(center.mV[VX] - width ,center.mV[VY] - width,center.mV[VZ] - width); + gGL.vertex3f(center.mV[VX] - width ,center.mV[VY] - width,center.mV[VZ] - width); + gGL.vertex3f(center.mV[VX] + width ,center.mV[VY] - width,center.mV[VZ] - width); + gGL.vertex3f(center.mV[VX] + width ,center.mV[VY] - width,center.mV[VZ] - width); + gGL.vertex3f(center.mV[VX] + width ,center.mV[VY] + width,center.mV[VZ] - width); + + gGL.vertex3f(center.mV[VX] + width ,center.mV[VY] + width,center.mV[VZ] + width); + gGL.vertex3f(center.mV[VX] + width ,center.mV[VY] + width,center.mV[VZ] - width); + gGL.vertex3f(center.mV[VX] - width ,center.mV[VY] + width,center.mV[VZ] + width); + gGL.vertex3f(center.mV[VX] - width ,center.mV[VY] + width,center.mV[VZ] - width); + gGL.vertex3f(center.mV[VX] - width ,center.mV[VY] - width,center.mV[VZ] + width); + gGL.vertex3f(center.mV[VX] - width ,center.mV[VY] - width,center.mV[VZ] - width); + gGL.vertex3f(center.mV[VX] + width ,center.mV[VY] - width,center.mV[VZ] + width); + gGL.vertex3f(center.mV[VX] + width ,center.mV[VY] - width,center.mV[VZ] - width); +} + +void draw_cross_lines(const LLVector3& center, F32 dx, F32 dy, F32 dz) +{ + gGL.vertex3f(center.mV[VX] - dx, center.mV[VY], center.mV[VZ]); + gGL.vertex3f(center.mV[VX] + dx, center.mV[VY], center.mV[VZ]); + gGL.vertex3f(center.mV[VX], center.mV[VY] - dy, center.mV[VZ]); + gGL.vertex3f(center.mV[VX], center.mV[VY] + dy, center.mV[VZ]); + gGL.vertex3f(center.mV[VX], center.mV[VY], center.mV[VZ] - dz); + gGL.vertex3f(center.mV[VX], center.mV[VY], center.mV[VZ] + dz); +} + +void LLViewerObjectList::renderObjectBeacons() +{ + if (mDebugBeacons.empty()) + { + return; + } + + LLGLSUIDefault gls_ui; + + gUIProgram.bind(); + + { + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + S32 last_line_width = -1; + // gGL.begin(LLRender::LINES); // Always happens in (line_width != last_line_width) + + for (std::vector::iterator iter = mDebugBeacons.begin(); iter != mDebugBeacons.end(); ++iter) + { + const LLDebugBeacon &debug_beacon = *iter; + LLColor4 color = debug_beacon.mColor; + color.mV[3] *= 0.25f; + S32 line_width = debug_beacon.mLineWidth; + if (line_width != last_line_width) + { + gGL.flush(); + glLineWidth( (F32)line_width ); + last_line_width = line_width; + } + + const LLVector3 &thisline = debug_beacon.mPositionAgent; + + gGL.begin(LLRender::LINES); + gGL.color4fv(linearColor4(color).mV); + draw_cross_lines(thisline, 2.0f, 2.0f, 50.f); + draw_line_cube(0.10f, thisline); + + gGL.end(); + } + } + + { + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + LLGLDepthTest gls_depth(GL_TRUE); + + S32 last_line_width = -1; + // gGL.begin(LLRender::LINES); // Always happens in (line_width != last_line_width) + + for (std::vector::iterator iter = mDebugBeacons.begin(); iter != mDebugBeacons.end(); ++iter) + { + const LLDebugBeacon &debug_beacon = *iter; + + S32 line_width = debug_beacon.mLineWidth; + if (line_width != last_line_width) + { + gGL.flush(); + glLineWidth( (F32)line_width ); + last_line_width = line_width; + } + + const LLVector3 &thisline = debug_beacon.mPositionAgent; + gGL.begin(LLRender::LINES); + gGL.color4fv(linearColor4(debug_beacon.mColor).mV); + draw_cross_lines(thisline, 0.5f, 0.5f, 0.5f); + draw_line_cube(0.10f, thisline); + + gGL.end(); + } + + gGL.flush(); + glLineWidth(1.f); + + for (std::vector::iterator iter = mDebugBeacons.begin(); iter != mDebugBeacons.end(); ++iter) + { + LLDebugBeacon &debug_beacon = *iter; + if (debug_beacon.mString == "") + { + continue; + } + LLHUDText *hud_textp = (LLHUDText *)LLHUDObject::addHUDObject(LLHUDObject::LL_HUD_TEXT); + + hud_textp->setZCompare(false); + LLColor4 color; + color = debug_beacon.mTextColor; + color.mV[3] *= 1.f; + + hud_textp->setString(debug_beacon.mString); + hud_textp->setColor(color); + hud_textp->setPositionAgent(debug_beacon.mPositionAgent); + debug_beacon.mHUDObject = hud_textp; + } + } +} + +void LLSky::renderSunMoonBeacons(const LLVector3& pos_agent, const LLVector3& direction, LLColor4 color) +{ + LLGLSUIDefault gls_ui; + gUIProgram.bind(); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + LLVector3 pos_end; + for (S32 i = 0; i < 3; ++i) + { + pos_end.mV[i] = pos_agent.mV[i] + (50 * direction.mV[i]); + } + glLineWidth(LLPipeline::DebugBeaconLineWidth); + gGL.begin(LLRender::LINES); + color.mV[3] *= 0.5f; + gGL.color4fv(color.mV); + draw_cross_lines(pos_agent, 0.5f, 0.5f, 0.5f); + draw_cross_lines(pos_end, 2.f, 2.f, 2.f); + gGL.vertex3fv(pos_agent.mV); + gGL.vertex3fv(pos_end.mV); + gGL.end(); + + gGL.flush(); + glLineWidth(1.f); + +} + +//----------------------------------------------------------------------------- +// gpu_benchmark() helper classes +//----------------------------------------------------------------------------- + +// This struct is used to ensure that once we call initProfile(), it will +// definitely be matched by a corresponding call to finishProfile(). It's +// a struct rather than a class simply because every member is public. +struct ShaderProfileHelper +{ + ShaderProfileHelper() + { + LLGLSLShader::initProfile(); + } + ~ShaderProfileHelper() + { + LLGLSLShader::finishProfile(false); + } +}; + +// This helper class is used to ensure that each generateTextures() call +// is matched by a corresponding deleteTextures() call. It also handles +// the bindManual() calls using those textures. +class TextureHolder +{ +public: + TextureHolder(U32 unit, U32 size) : + texUnit(gGL.getTexUnit(unit)), + source(size) // preallocate vector + { + // takes (count, pointer) + // &vector[0] gets pointer to contiguous array + LLImageGL::generateTextures(source.size(), &source[0]); + } + + ~TextureHolder() + { + // unbind + if (texUnit) + { + texUnit->unbind(LLTexUnit::TT_TEXTURE); + } + // ensure that we delete these textures regardless of how we exit + LLImageGL::deleteTextures(source.size(), &source[0]); + } + + bool bind(U32 index) + { + if (texUnit) // should always be there with dummy (-1), but just in case + { + return texUnit->bindManual(LLTexUnit::TT_TEXTURE, source[index]); + } + return false; + } + +private: + // capture which LLTexUnit we're going to use + LLTexUnit* texUnit; + + // use std::vector for implicit resource management + std::vector source; +}; + +class ShaderBinder +{ +public: + ShaderBinder(LLGLSLShader& shader) : + mShader(shader) + { + mShader.bind(); + } + ~ShaderBinder() + { + mShader.unbind(); + } + +private: + LLGLSLShader& mShader; +}; + + +//----------------------------------------------------------------------------- +// gpu_benchmark() +// returns measured memory bandwidth of GPU in gigabytes per second +//----------------------------------------------------------------------------- +F32 gpu_benchmark() +{ + if (gGLManager.mGLVersion < 3.3f) + { // don't bother benchmarking venerable drivers which don't support accurate timing anyway + return -1.f; + } + + if (gBenchmarkProgram.mProgramObject == 0) + { + LLViewerShaderMgr::instance()->initAttribsAndUniforms(); + + gBenchmarkProgram.mName = "Benchmark Shader"; + gBenchmarkProgram.mFeatures.attachNothing = true; + gBenchmarkProgram.mShaderFiles.clear(); + gBenchmarkProgram.mShaderFiles.push_back(std::make_pair("interface/benchmarkV.glsl", GL_VERTEX_SHADER)); + gBenchmarkProgram.mShaderFiles.push_back(std::make_pair("interface/benchmarkF.glsl", GL_FRAGMENT_SHADER)); + gBenchmarkProgram.mShaderLevel = 1; + if (!gBenchmarkProgram.createShader(NULL, NULL)) + { + return -1.f; + } + } + + LLGLDisable blend(GL_BLEND); + + //measure memory bandwidth by: + // - allocating a batch of textures and render targets + // - rendering those textures to those render targets + // - recording time taken + // - taking the median time for a given number of samples + + //resolution of textures/render targets + const U32 res = 1024; + + //number of textures + const U32 count = 32; + + //number of samples to take + const S32 samples = 64; + + //time limit, allocation operations shouldn't take longer then 30 seconds, same for actual benchmark. + const F32 time_limit = 30; + + std::vector dest(count); + TextureHolder texHolder(0, count); + std::vector results; + + //build a random texture + U8* pixels = new U8[res*res*4]; + + for (U32 i = 0; i < res*res*4; ++i) + { + pixels[i] = (U8) ll_rand(255); + } + + gGL.setColorMask(true, true); + LLGLDepthTest depth(GL_FALSE); + + LLTimer alloc_timer; + alloc_timer.start(); + for (U32 i = 0; i < count; ++i) + { + //allocate render targets and textures + if (!dest[i].allocate(res, res, GL_RGBA)) + { + LL_WARNS("Benchmark") << "Failed to allocate render target." << LL_ENDL; + // abandon the benchmark test + delete[] pixels; + return -1.f; + } + dest[i].bindTarget(); + dest[i].clear(); + dest[i].flush(); + + if (!texHolder.bind(i)) + { + // can use a dummy value mDummyTexUnit = new LLTexUnit(-1); + LL_WARNS("Benchmark") << "Failed to bind tex unit." << LL_ENDL; + // abandon the benchmark test + delete[] pixels; + return -1.f; + } + LLImageGL::setManualImage(GL_TEXTURE_2D, 0, GL_RGBA, res,res,GL_RGBA, GL_UNSIGNED_BYTE, pixels); + + if (alloc_timer.getElapsedTimeF32() > time_limit) + { + // abandon the benchmark test + LL_WARNS("Benchmark") << "Allocation operation took longer then 30 seconds, stopping." << LL_ENDL; + delete[] pixels; + return -1.f; + } + } + + delete [] pixels; + + //make a dummy triangle to draw with + LLPointer buff = new LLVertexBuffer(LLVertexBuffer::MAP_VERTEX); + + if (!buff->allocateBuffer(3, 0)) + { + LL_WARNS("Benchmark") << "Failed to allocate buffer during benchmark." << LL_ENDL; + // abandon the benchmark test + return -1.f; + } + + LLStrider v; + + if (! buff->getVertexStrider(v)) + { + LL_WARNS("Benchmark") << "GL LLVertexBuffer::getVertexStrider() returned false, " + << "buff->getMappedData() is" + << (buff->getMappedData()? " not" : "") + << " NULL" << LL_ENDL; + // abandon the benchmark test + return -1.f; + } + + // generate dummy triangle + v[0].set(-1, 1, 0); + v[1].set(-1, -3, 0); + v[2].set(3, 1, 0); + + buff->unmapBuffer(); + + LLGLSLShader::unbind(); + + F32 time_passed = 0; // seconds + + { //run CPU timer benchmark + glFinish(); + gBenchmarkProgram.bind(); + for (S32 c = -1; c < samples && time_passed < time_limit; ++c) + { + LLTimer timer; + timer.start(); + + for (U32 i = 0; i < count; ++i) + { + dest[i].bindTarget(); + texHolder.bind(i); + buff->setBuffer(); + buff->drawArrays(LLRender::TRIANGLES, 0, 3); + dest[i].flush(); + } + + //wait for current batch of copies to finish + glFinish(); + + F32 time = timer.getElapsedTimeF32(); + time_passed += time; + + if (c >= 0) // <-- ignore the first sample as it tends to be artificially slow + { + //store result in gigabytes per second + F32 gb = (F32)((F64)(res * res * 8 * count)) / (1000000000); + F32 gbps = gb / time; + results.push_back(gbps); + } + } + gBenchmarkProgram.unbind(); + } + + std::sort(results.begin(), results.end()); + + F32 gbps = results[results.size()/2]; + + LL_INFOS("Benchmark") << "Memory bandwidth is " << llformat("%.3f", gbps) << " GB/sec according to CPU timers, " << (F32)results.size() << " tests took " << time_passed << " seconds" << LL_ENDL; + +#if LL_DARWIN + if (gbps > 512.f) + { + LL_WARNS("Benchmark") << "Memory bandwidth is improbably high and likely incorrect; discarding result." << LL_ENDL; + //OSX is probably lying, discard result + return -1.f; + } +#endif + + // run GPU timer benchmark + { + ShaderProfileHelper initProfile; + dest[0].bindTarget(); + gBenchmarkProgram.bind(); + for (S32 c = 0; c < samples; ++c) + { + for (U32 i = 0; i < count; ++i) + { + texHolder.bind(i); + buff->setBuffer(); + buff->drawArrays(LLRender::TRIANGLES, 0, 3); + } + } + gBenchmarkProgram.unbind(); + dest[0].flush(); + } + + F32 ms = gBenchmarkProgram.mTimeElapsed/1000000.f; + F32 seconds = ms/1000.f; + + F64 samples_drawn = gBenchmarkProgram.mSamplesDrawn; + F32 samples_sec = (samples_drawn/1000000000.0)/seconds; + gbps = samples_sec*4; // 4 bytes per sample + + LL_INFOS("Benchmark") << "Memory bandwidth is " << llformat("%.3f", gbps) << " GB/sec according to ARB_timer_query, total time " << seconds << " seconds" << LL_ENDL; + + return gbps; +} diff --git a/indra/newview/llgroupactions.cpp b/indra/newview/llgroupactions.cpp index 8ca60b0455..b86d4214d6 100644 --- a/indra/newview/llgroupactions.cpp +++ b/indra/newview/llgroupactions.cpp @@ -1,575 +1,575 @@ -/** - * @file llgroupactions.cpp - * @brief Group-related actions (join, leave, new, delete, etc) - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#include "llviewerprecompiledheaders.h" - -#include "llgroupactions.h" - -#include "message.h" - -#include "llagent.h" -#include "llcommandhandler.h" -#include "llfloaterreg.h" -#include "llfloatersidepanelcontainer.h" -#include "llgroupmgr.h" -#include "llfloaterimcontainer.h" -#include "llimview.h" // for gIMMgr -#include "llnotificationsutil.h" -#include "llstartup.h" -#include "llstatusbar.h" // can_afford_transaction() -#include "groupchatlistener.h" - -// -// Globals -// -static GroupChatListener sGroupChatListener; - -class LLGroupHandler : public LLCommandHandler -{ -public: - // requires trusted browser to trigger - LLGroupHandler() : LLCommandHandler("group", UNTRUSTED_THROTTLE) { } - - virtual bool canHandleUntrusted( - const LLSD& params, - const LLSD& query_map, - LLMediaCtrl* web, - const std::string& nav_type) - { - if (params.size() < 1) - { - return true; // don't block, will fail later - } - - if (nav_type == NAV_TYPE_CLICKED - || nav_type == NAV_TYPE_EXTERNAL) - { - return true; - } - - const std::string verb = params[0].asString(); - if (verb == "create") - { - return false; - } - return true; - } - - bool handle(const LLSD& tokens, - const LLSD& query_map, - const std::string& grid, - LLMediaCtrl* web) - { - if (LLStartUp::getStartupState() < STATE_STARTED) - { - return true; - } - - if (tokens.size() < 1) - { - return false; - } - - if (tokens[0].asString() == "create") - { - LLGroupActions::createGroup(); - return true; - } - - if (tokens.size() < 2) - { - return false; - } - - if (tokens[0].asString() == "list") - { - if (tokens[1].asString() == "show") - { - LLSD params; - params["people_panel_tab_name"] = "groups_panel"; - LLFloaterSidePanelContainer::showPanel("people", "panel_people", params); - return true; - } - return false; - } - - LLUUID group_id; - if (!group_id.set(tokens[0], false)) - { - return false; - } - - if (tokens[1].asString() == "about") - { - if (group_id.isNull()) - return true; - - LLGroupActions::show(group_id); - - return true; - } - if (tokens[1].asString() == "inspect") - { - if (group_id.isNull()) - return true; - LLGroupActions::inspect(group_id); - return true; - } - return false; - } -}; -LLGroupHandler gGroupHandler; - -// This object represents a pending request for specified group member information -// which is needed to check whether avatar can leave group -class LLFetchGroupMemberData : public LLGroupMgrObserver -{ -public: - LLFetchGroupMemberData(const LLUUID& group_id) : - mGroupId(group_id), - mRequestProcessed(false), - LLGroupMgrObserver(group_id) - { - LL_INFOS() << "Sending new group member request for group_id: "<< group_id << LL_ENDL; - LLGroupMgr* mgr = LLGroupMgr::getInstance(); - // register ourselves as an observer - mgr->addObserver(this); - // send a request - mgr->sendGroupPropertiesRequest(group_id); - mgr->sendCapGroupMembersRequest(group_id); - } - - ~LLFetchGroupMemberData() - { - if (!mRequestProcessed) - { - // Request is pending - LL_WARNS() << "Destroying pending group member request for group_id: " - << mGroupId << LL_ENDL; - } - // Remove ourselves as an observer - LLGroupMgr::getInstance()->removeObserver(this); - } - - void changed(LLGroupChange gc) - { - if (gc == GC_PROPERTIES && !mRequestProcessed) - { - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupId); - if (!gdatap) - { - LL_WARNS() << "LLGroupMgr::getInstance()->getGroupData() was NULL" << LL_ENDL; - } - else if (!gdatap->isMemberDataComplete()) - { - LL_WARNS() << "LLGroupMgr::getInstance()->getGroupData()->isMemberDataComplete() was false" << LL_ENDL; - processGroupData(); - mRequestProcessed = true; - } - } - } - - LLUUID getGroupId() { return mGroupId; } - virtual void processGroupData() = 0; -protected: - LLUUID mGroupId; - bool mRequestProcessed; -}; - -class LLFetchLeaveGroupData: public LLFetchGroupMemberData -{ -public: - LLFetchLeaveGroupData(const LLUUID& group_id) - : LLFetchGroupMemberData(group_id) - {} - void processGroupData() - { - LLGroupActions::processLeaveGroupDataResponse(mGroupId); - } - void changed(LLGroupChange gc) - { - if (gc == GC_PROPERTIES && !mRequestProcessed) - { - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupId); - if (!gdatap) - { - LL_WARNS() << "GroupData was NULL" << LL_ENDL; - } - else - { - processGroupData(); - mRequestProcessed = true; - } - } - } -}; - -LLFetchLeaveGroupData* gFetchLeaveGroupData = NULL; - -// static -void LLGroupActions::search() -{ - LLFloaterReg::showInstance("search", LLSD().with("collection", "groups")); -} - -// static -void LLGroupActions::startCall(const LLUUID& group_id) -{ - // create a new group voice session - LLGroupData gdata; - - if (!gAgent.getGroupData(group_id, gdata)) - { - LL_WARNS() << "Error getting group data" << LL_ENDL; - return; - } - - LLUUID session_id = gIMMgr->addSession(gdata.mName, IM_SESSION_GROUP_START, group_id, true); - if (session_id == LLUUID::null) - { - LL_WARNS() << "Error adding session" << LL_ENDL; - return; - } - - // start the call - gIMMgr->autoStartCallOnStartup(session_id); - - make_ui_sound("UISndStartIM"); -} - -// static -void LLGroupActions::join(const LLUUID& group_id) -{ - if (!gAgent.canJoinGroups()) - { - LLNotificationsUtil::add("JoinedTooManyGroups"); - return; - } - - LLGroupMgrGroupData* gdatap = - LLGroupMgr::getInstance()->getGroupData(group_id); - - if (gdatap) - { - S32 cost = gdatap->mMembershipFee; - LLSD args; - args["COST"] = llformat("%d", cost); - args["NAME"] = gdatap->mName; - LLSD payload; - payload["group_id"] = group_id; - - if (can_afford_transaction(cost)) - { - if(cost > 0) - LLNotificationsUtil::add("JoinGroupCanAfford", args, payload, onJoinGroup); - else - LLNotificationsUtil::add("JoinGroupNoCost", args, payload, onJoinGroup); - - } - else - { - LLNotificationsUtil::add("JoinGroupCannotAfford", args, payload); - } - } - else - { - LL_WARNS() << "LLGroupMgr::getInstance()->getGroupData(" << group_id - << ") was NULL" << LL_ENDL; - } -} - -// static -bool LLGroupActions::onJoinGroup(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - - if (option == 1) - { - // user clicked cancel - return false; - } - - LLGroupMgr::getInstance()-> - sendGroupMemberJoin(notification["payload"]["group_id"].asUUID()); - return false; -} - -// static -void LLGroupActions::leave(const LLUUID& group_id) -{ - if (group_id.isNull()) - { - return; - } - - LLGroupData group_data; - if (gAgent.getGroupData(group_id, group_data)) - { - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_id); - if (!gdatap || !gdatap->isMemberDataComplete()) - { - if (gFetchLeaveGroupData != NULL) - { - delete gFetchLeaveGroupData; - gFetchLeaveGroupData = NULL; - } - gFetchLeaveGroupData = new LLFetchLeaveGroupData(group_id); - } - else - { - processLeaveGroupDataResponse(group_id); - } - } -} - -//static -void LLGroupActions::processLeaveGroupDataResponse(const LLUUID group_id) -{ - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_id); - LLUUID agent_id = gAgent.getID(); - LLGroupMgrGroupData::member_list_t::iterator mit = gdatap->mMembers.find(agent_id); - //get the member data for the group - if ( mit != gdatap->mMembers.end() ) - { - LLGroupMemberData* member_data = (*mit).second; - - if ( member_data && member_data->isOwner() && gdatap->mMemberCount == 1) - { - LLNotificationsUtil::add("OwnerCannotLeaveGroup"); - return; - } - } - LLSD args; - args["GROUP"] = gdatap->mName; - LLSD payload; - payload["group_id"] = group_id; - if (gdatap->mMembershipFee > 0) - { - args["COST"] = gdatap->mMembershipFee; - LLNotificationsUtil::add("GroupLeaveConfirmMember", args, payload, onLeaveGroup); -} - else - { - LLNotificationsUtil::add("GroupLeaveConfirmMemberNoFee", args, payload, onLeaveGroup); - } - -} - -// static -void LLGroupActions::activate(const LLUUID& group_id) -{ - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_ActivateGroup); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addUUIDFast(_PREHASH_GroupID, group_id); - gAgent.sendReliableMessage(); -} - -static bool isGroupUIVisible() -{ - static LLPanel* panel = 0; - if(!panel) - panel = LLFloaterSidePanelContainer::getPanel("people", "panel_group_info_sidetray"); - if(!panel) - return false; - return panel->isInVisibleChain(); -} - -// static -void LLGroupActions::inspect(const LLUUID& group_id) -{ - LLFloaterReg::showInstance("inspect_group", LLSD().with("group_id", group_id)); -} - -// static -void LLGroupActions::show(const LLUUID &group_id, bool expand_notices_tab) -{ - if (group_id.isNull()) - return; - - LLSD params; - params["group_id"] = group_id; - params["open_tab_name"] = "panel_group_info_sidetray"; - if (expand_notices_tab) - { - params["action"] = "show_notices"; - } - - LLFloaterSidePanelContainer::showPanel("people", "panel_group_info_sidetray", params); - LLFloater *floater = LLFloaterReg::getTypedInstance("people"); - if (!floater->isFrontmost()) - { - floater->setVisibleAndFrontmost(true, params); - } -} - -void LLGroupActions::refresh_notices() -{ - if(!isGroupUIVisible()) - return; - - LLSD params; - params["group_id"] = LLUUID::null; - params["open_tab_name"] = "panel_group_info_sidetray"; - params["action"] = "refresh_notices"; - - LLFloaterSidePanelContainer::showPanel("people", "panel_group_info_sidetray", params); -} - -//static -void LLGroupActions::refresh(const LLUUID& group_id) -{ - if(!isGroupUIVisible()) - return; - - LLSD params; - params["group_id"] = group_id; - params["open_tab_name"] = "panel_group_info_sidetray"; - params["action"] = "refresh"; - - LLFloaterSidePanelContainer::showPanel("people", "panel_group_info_sidetray", params); -} - -//static -void LLGroupActions::createGroup() -{ - LLSD params; - params["group_id"] = LLUUID::null; - params["open_tab_name"] = "panel_group_creation_sidetray"; - params["action"] = "create"; - - LLFloaterSidePanelContainer::showPanel("people", "panel_group_creation_sidetray", params); - -} -//static -void LLGroupActions::closeGroup(const LLUUID& group_id) -{ - if(!isGroupUIVisible()) - return; - - LLSD params; - params["group_id"] = group_id; - params["open_tab_name"] = "panel_group_info_sidetray"; - params["action"] = "close"; - - LLFloaterSidePanelContainer::showPanel("people", "panel_group_info_sidetray", params); -} - - -// static -LLUUID LLGroupActions::startIM(const LLUUID& group_id) -{ - if (group_id.isNull()) return LLUUID::null; - - LLGroupData group_data; - if (gAgent.getGroupData(group_id, group_data)) - { - LLUUID session_id = gIMMgr->addSession( - group_data.mName, - IM_SESSION_GROUP_START, - group_id); - if (session_id != LLUUID::null) - { - LLFloaterIMContainer::getInstance()->showConversation(session_id); - } - make_ui_sound("UISndStartIM"); - return session_id; - } - else - { - // this should never happen, as starting a group IM session - // relies on you belonging to the group and hence having the group data - make_ui_sound("UISndInvalidOp"); - return LLUUID::null; - } -} - -// static -void LLGroupActions::endIM(const LLUUID& group_id) -{ - if (group_id.isNull()) - return; - - LLUUID session_id = gIMMgr->computeSessionID(IM_SESSION_GROUP_START, group_id); - if (session_id != LLUUID::null) - { - gIMMgr->leaveSession(session_id); - } -} - -// static -bool LLGroupActions::isInGroup(const LLUUID& group_id) -{ - // *TODO: Move all the LLAgent group stuff into another class, such as - // this one. - return gAgent.isInGroup(group_id); -} - -// static -bool LLGroupActions::isAvatarMemberOfGroup(const LLUUID& group_id, const LLUUID& avatar_id) -{ - if(group_id.isNull() || avatar_id.isNull()) - { - return false; - } - - LLGroupMgrGroupData* group_data = LLGroupMgr::getInstance()->getGroupData(group_id); - if(!group_data) - { - return false; - } - - if(group_data->mMembers.end() == group_data->mMembers.find(avatar_id)) - { - return false; - } - - return true; -} - -//-- Private methods ---------------------------------------------------------- - -// static -bool LLGroupActions::onLeaveGroup(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - LLUUID group_id = notification["payload"]["group_id"].asUUID(); - if(option == 0) - { - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_LeaveGroupRequest); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_GroupData); - msg->addUUIDFast(_PREHASH_GroupID, group_id); - gAgent.sendReliableMessage(); - } - return false; -} +/** + * @file llgroupactions.cpp + * @brief Group-related actions (join, leave, new, delete, etc) + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#include "llviewerprecompiledheaders.h" + +#include "llgroupactions.h" + +#include "message.h" + +#include "llagent.h" +#include "llcommandhandler.h" +#include "llfloaterreg.h" +#include "llfloatersidepanelcontainer.h" +#include "llgroupmgr.h" +#include "llfloaterimcontainer.h" +#include "llimview.h" // for gIMMgr +#include "llnotificationsutil.h" +#include "llstartup.h" +#include "llstatusbar.h" // can_afford_transaction() +#include "groupchatlistener.h" + +// +// Globals +// +static GroupChatListener sGroupChatListener; + +class LLGroupHandler : public LLCommandHandler +{ +public: + // requires trusted browser to trigger + LLGroupHandler() : LLCommandHandler("group", UNTRUSTED_THROTTLE) { } + + virtual bool canHandleUntrusted( + const LLSD& params, + const LLSD& query_map, + LLMediaCtrl* web, + const std::string& nav_type) + { + if (params.size() < 1) + { + return true; // don't block, will fail later + } + + if (nav_type == NAV_TYPE_CLICKED + || nav_type == NAV_TYPE_EXTERNAL) + { + return true; + } + + const std::string verb = params[0].asString(); + if (verb == "create") + { + return false; + } + return true; + } + + bool handle(const LLSD& tokens, + const LLSD& query_map, + const std::string& grid, + LLMediaCtrl* web) + { + if (LLStartUp::getStartupState() < STATE_STARTED) + { + return true; + } + + if (tokens.size() < 1) + { + return false; + } + + if (tokens[0].asString() == "create") + { + LLGroupActions::createGroup(); + return true; + } + + if (tokens.size() < 2) + { + return false; + } + + if (tokens[0].asString() == "list") + { + if (tokens[1].asString() == "show") + { + LLSD params; + params["people_panel_tab_name"] = "groups_panel"; + LLFloaterSidePanelContainer::showPanel("people", "panel_people", params); + return true; + } + return false; + } + + LLUUID group_id; + if (!group_id.set(tokens[0], false)) + { + return false; + } + + if (tokens[1].asString() == "about") + { + if (group_id.isNull()) + return true; + + LLGroupActions::show(group_id); + + return true; + } + if (tokens[1].asString() == "inspect") + { + if (group_id.isNull()) + return true; + LLGroupActions::inspect(group_id); + return true; + } + return false; + } +}; +LLGroupHandler gGroupHandler; + +// This object represents a pending request for specified group member information +// which is needed to check whether avatar can leave group +class LLFetchGroupMemberData : public LLGroupMgrObserver +{ +public: + LLFetchGroupMemberData(const LLUUID& group_id) : + mGroupId(group_id), + mRequestProcessed(false), + LLGroupMgrObserver(group_id) + { + LL_INFOS() << "Sending new group member request for group_id: "<< group_id << LL_ENDL; + LLGroupMgr* mgr = LLGroupMgr::getInstance(); + // register ourselves as an observer + mgr->addObserver(this); + // send a request + mgr->sendGroupPropertiesRequest(group_id); + mgr->sendCapGroupMembersRequest(group_id); + } + + ~LLFetchGroupMemberData() + { + if (!mRequestProcessed) + { + // Request is pending + LL_WARNS() << "Destroying pending group member request for group_id: " + << mGroupId << LL_ENDL; + } + // Remove ourselves as an observer + LLGroupMgr::getInstance()->removeObserver(this); + } + + void changed(LLGroupChange gc) + { + if (gc == GC_PROPERTIES && !mRequestProcessed) + { + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupId); + if (!gdatap) + { + LL_WARNS() << "LLGroupMgr::getInstance()->getGroupData() was NULL" << LL_ENDL; + } + else if (!gdatap->isMemberDataComplete()) + { + LL_WARNS() << "LLGroupMgr::getInstance()->getGroupData()->isMemberDataComplete() was false" << LL_ENDL; + processGroupData(); + mRequestProcessed = true; + } + } + } + + LLUUID getGroupId() { return mGroupId; } + virtual void processGroupData() = 0; +protected: + LLUUID mGroupId; + bool mRequestProcessed; +}; + +class LLFetchLeaveGroupData: public LLFetchGroupMemberData +{ +public: + LLFetchLeaveGroupData(const LLUUID& group_id) + : LLFetchGroupMemberData(group_id) + {} + void processGroupData() + { + LLGroupActions::processLeaveGroupDataResponse(mGroupId); + } + void changed(LLGroupChange gc) + { + if (gc == GC_PROPERTIES && !mRequestProcessed) + { + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupId); + if (!gdatap) + { + LL_WARNS() << "GroupData was NULL" << LL_ENDL; + } + else + { + processGroupData(); + mRequestProcessed = true; + } + } + } +}; + +LLFetchLeaveGroupData* gFetchLeaveGroupData = NULL; + +// static +void LLGroupActions::search() +{ + LLFloaterReg::showInstance("search", LLSD().with("collection", "groups")); +} + +// static +void LLGroupActions::startCall(const LLUUID& group_id) +{ + // create a new group voice session + LLGroupData gdata; + + if (!gAgent.getGroupData(group_id, gdata)) + { + LL_WARNS() << "Error getting group data" << LL_ENDL; + return; + } + + LLUUID session_id = gIMMgr->addSession(gdata.mName, IM_SESSION_GROUP_START, group_id, true); + if (session_id == LLUUID::null) + { + LL_WARNS() << "Error adding session" << LL_ENDL; + return; + } + + // start the call + gIMMgr->autoStartCallOnStartup(session_id); + + make_ui_sound("UISndStartIM"); +} + +// static +void LLGroupActions::join(const LLUUID& group_id) +{ + if (!gAgent.canJoinGroups()) + { + LLNotificationsUtil::add("JoinedTooManyGroups"); + return; + } + + LLGroupMgrGroupData* gdatap = + LLGroupMgr::getInstance()->getGroupData(group_id); + + if (gdatap) + { + S32 cost = gdatap->mMembershipFee; + LLSD args; + args["COST"] = llformat("%d", cost); + args["NAME"] = gdatap->mName; + LLSD payload; + payload["group_id"] = group_id; + + if (can_afford_transaction(cost)) + { + if(cost > 0) + LLNotificationsUtil::add("JoinGroupCanAfford", args, payload, onJoinGroup); + else + LLNotificationsUtil::add("JoinGroupNoCost", args, payload, onJoinGroup); + + } + else + { + LLNotificationsUtil::add("JoinGroupCannotAfford", args, payload); + } + } + else + { + LL_WARNS() << "LLGroupMgr::getInstance()->getGroupData(" << group_id + << ") was NULL" << LL_ENDL; + } +} + +// static +bool LLGroupActions::onJoinGroup(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + + if (option == 1) + { + // user clicked cancel + return false; + } + + LLGroupMgr::getInstance()-> + sendGroupMemberJoin(notification["payload"]["group_id"].asUUID()); + return false; +} + +// static +void LLGroupActions::leave(const LLUUID& group_id) +{ + if (group_id.isNull()) + { + return; + } + + LLGroupData group_data; + if (gAgent.getGroupData(group_id, group_data)) + { + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_id); + if (!gdatap || !gdatap->isMemberDataComplete()) + { + if (gFetchLeaveGroupData != NULL) + { + delete gFetchLeaveGroupData; + gFetchLeaveGroupData = NULL; + } + gFetchLeaveGroupData = new LLFetchLeaveGroupData(group_id); + } + else + { + processLeaveGroupDataResponse(group_id); + } + } +} + +//static +void LLGroupActions::processLeaveGroupDataResponse(const LLUUID group_id) +{ + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_id); + LLUUID agent_id = gAgent.getID(); + LLGroupMgrGroupData::member_list_t::iterator mit = gdatap->mMembers.find(agent_id); + //get the member data for the group + if ( mit != gdatap->mMembers.end() ) + { + LLGroupMemberData* member_data = (*mit).second; + + if ( member_data && member_data->isOwner() && gdatap->mMemberCount == 1) + { + LLNotificationsUtil::add("OwnerCannotLeaveGroup"); + return; + } + } + LLSD args; + args["GROUP"] = gdatap->mName; + LLSD payload; + payload["group_id"] = group_id; + if (gdatap->mMembershipFee > 0) + { + args["COST"] = gdatap->mMembershipFee; + LLNotificationsUtil::add("GroupLeaveConfirmMember", args, payload, onLeaveGroup); +} + else + { + LLNotificationsUtil::add("GroupLeaveConfirmMemberNoFee", args, payload, onLeaveGroup); + } + +} + +// static +void LLGroupActions::activate(const LLUUID& group_id) +{ + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_ActivateGroup); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addUUIDFast(_PREHASH_GroupID, group_id); + gAgent.sendReliableMessage(); +} + +static bool isGroupUIVisible() +{ + static LLPanel* panel = 0; + if(!panel) + panel = LLFloaterSidePanelContainer::getPanel("people", "panel_group_info_sidetray"); + if(!panel) + return false; + return panel->isInVisibleChain(); +} + +// static +void LLGroupActions::inspect(const LLUUID& group_id) +{ + LLFloaterReg::showInstance("inspect_group", LLSD().with("group_id", group_id)); +} + +// static +void LLGroupActions::show(const LLUUID &group_id, bool expand_notices_tab) +{ + if (group_id.isNull()) + return; + + LLSD params; + params["group_id"] = group_id; + params["open_tab_name"] = "panel_group_info_sidetray"; + if (expand_notices_tab) + { + params["action"] = "show_notices"; + } + + LLFloaterSidePanelContainer::showPanel("people", "panel_group_info_sidetray", params); + LLFloater *floater = LLFloaterReg::getTypedInstance("people"); + if (!floater->isFrontmost()) + { + floater->setVisibleAndFrontmost(true, params); + } +} + +void LLGroupActions::refresh_notices() +{ + if(!isGroupUIVisible()) + return; + + LLSD params; + params["group_id"] = LLUUID::null; + params["open_tab_name"] = "panel_group_info_sidetray"; + params["action"] = "refresh_notices"; + + LLFloaterSidePanelContainer::showPanel("people", "panel_group_info_sidetray", params); +} + +//static +void LLGroupActions::refresh(const LLUUID& group_id) +{ + if(!isGroupUIVisible()) + return; + + LLSD params; + params["group_id"] = group_id; + params["open_tab_name"] = "panel_group_info_sidetray"; + params["action"] = "refresh"; + + LLFloaterSidePanelContainer::showPanel("people", "panel_group_info_sidetray", params); +} + +//static +void LLGroupActions::createGroup() +{ + LLSD params; + params["group_id"] = LLUUID::null; + params["open_tab_name"] = "panel_group_creation_sidetray"; + params["action"] = "create"; + + LLFloaterSidePanelContainer::showPanel("people", "panel_group_creation_sidetray", params); + +} +//static +void LLGroupActions::closeGroup(const LLUUID& group_id) +{ + if(!isGroupUIVisible()) + return; + + LLSD params; + params["group_id"] = group_id; + params["open_tab_name"] = "panel_group_info_sidetray"; + params["action"] = "close"; + + LLFloaterSidePanelContainer::showPanel("people", "panel_group_info_sidetray", params); +} + + +// static +LLUUID LLGroupActions::startIM(const LLUUID& group_id) +{ + if (group_id.isNull()) return LLUUID::null; + + LLGroupData group_data; + if (gAgent.getGroupData(group_id, group_data)) + { + LLUUID session_id = gIMMgr->addSession( + group_data.mName, + IM_SESSION_GROUP_START, + group_id); + if (session_id != LLUUID::null) + { + LLFloaterIMContainer::getInstance()->showConversation(session_id); + } + make_ui_sound("UISndStartIM"); + return session_id; + } + else + { + // this should never happen, as starting a group IM session + // relies on you belonging to the group and hence having the group data + make_ui_sound("UISndInvalidOp"); + return LLUUID::null; + } +} + +// static +void LLGroupActions::endIM(const LLUUID& group_id) +{ + if (group_id.isNull()) + return; + + LLUUID session_id = gIMMgr->computeSessionID(IM_SESSION_GROUP_START, group_id); + if (session_id != LLUUID::null) + { + gIMMgr->leaveSession(session_id); + } +} + +// static +bool LLGroupActions::isInGroup(const LLUUID& group_id) +{ + // *TODO: Move all the LLAgent group stuff into another class, such as + // this one. + return gAgent.isInGroup(group_id); +} + +// static +bool LLGroupActions::isAvatarMemberOfGroup(const LLUUID& group_id, const LLUUID& avatar_id) +{ + if(group_id.isNull() || avatar_id.isNull()) + { + return false; + } + + LLGroupMgrGroupData* group_data = LLGroupMgr::getInstance()->getGroupData(group_id); + if(!group_data) + { + return false; + } + + if(group_data->mMembers.end() == group_data->mMembers.find(avatar_id)) + { + return false; + } + + return true; +} + +//-- Private methods ---------------------------------------------------------- + +// static +bool LLGroupActions::onLeaveGroup(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + LLUUID group_id = notification["payload"]["group_id"].asUUID(); + if(option == 0) + { + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_LeaveGroupRequest); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_GroupData); + msg->addUUIDFast(_PREHASH_GroupID, group_id); + gAgent.sendReliableMessage(); + } + return false; +} diff --git a/indra/newview/llgrouplist.cpp b/indra/newview/llgrouplist.cpp index d092445604..1952b5b8a1 100644 --- a/indra/newview/llgrouplist.cpp +++ b/indra/newview/llgrouplist.cpp @@ -1,622 +1,622 @@ -/** - * @file llgrouplist.cpp - * @brief List of the groups the agent belongs to. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llgrouplist.h" - -// libs -#include "llbutton.h" -#include "llgroupiconctrl.h" -#include "llmenugl.h" -#include "lltextbox.h" -#include "lltextutil.h" -#include "lltrans.h" - -// newview -#include "llagent.h" -#include "llgroupactions.h" -#include "llfloaterreg.h" -#include "llviewercontrol.h" // for gSavedSettings -#include "llviewermenu.h" // for gMenuHolder -#include "llvoiceclient.h" - -static LLDefaultChildRegistry::Register r("group_list"); - -class LLGroupComparator : public LLFlatListView::ItemComparator -{ -public: - LLGroupComparator() {}; - - /** Returns true if item1 < item2, false otherwise */ - /*virtual*/ bool compare(const LLPanel* item1, const LLPanel* item2) const - { - std::string name1 = static_cast(item1)->getGroupName(); - std::string name2 = static_cast(item2)->getGroupName(); - - LLStringUtil::toUpper(name1); - LLStringUtil::toUpper(name2); - - return name1 < name2; - } -}; - -class LLSharedGroupComparator : public LLFlatListView::ItemComparator -{ -public: - LLSharedGroupComparator() {}; - - /*virtual*/ bool compare(const LLPanel* item1, const LLPanel* item2) const - { - const LLGroupListItem* group_item1 = static_cast(item1); - std::string name1 = group_item1->getGroupName(); - bool item1_shared = gAgent.isInGroup(group_item1->getGroupID(), true); - - const LLGroupListItem* group_item2 = static_cast(item2); - std::string name2 = group_item2->getGroupName(); - bool item2_shared = gAgent.isInGroup(group_item2->getGroupID(), true); - - if (item2_shared != item1_shared) - { - return item1_shared; - } - - LLStringUtil::toUpper(name1); - LLStringUtil::toUpper(name2); - - return name1 < name2; - } -}; - -static LLGroupComparator GROUP_COMPARATOR; -static LLSharedGroupComparator SHARED_GROUP_COMPARATOR; - -LLGroupList::Params::Params() -: for_agent("for_agent", true) -{ -} - -LLGroupList::LLGroupList(const Params& p) -: LLFlatListViewEx(p) - , mForAgent(p.for_agent) - , mDirty(true) // to force initial update - , mShowIcons(false) - , mShowNone(true) -{ - setCommitOnSelectionChange(true); - - // Set default sort order. - if (mForAgent) - { - setComparator(&GROUP_COMPARATOR); - } - else - { - // shared groups first - setComparator(&SHARED_GROUP_COMPARATOR); - } - - if (mForAgent) - { - enableForAgent(true); - } -} - -LLGroupList::~LLGroupList() -{ - if (mForAgent) gAgent.removeListener(this); - if (mContextMenuHandle.get()) mContextMenuHandle.get()->die(); -} - -void LLGroupList::enableForAgent(bool show_icons) -{ - mForAgent = true; - - mShowIcons = mForAgent && gSavedSettings.getBOOL("GroupListShowIcons") && show_icons; - - // Listen for agent group changes. - gAgent.addListener(this, "new group"); - - // Set up context menu. - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; - - registrar.add("People.Groups.Action", boost::bind(&LLGroupList::onContextMenuItemClick, this, _2)); - enable_registrar.add("People.Groups.Enable", boost::bind(&LLGroupList::onContextMenuItemEnable, this, _2)); - - LLToggleableMenu* context_menu = LLUICtrlFactory::getInstance()->createFromFile("menu_people_groups.xml", - gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - if(context_menu) - mContextMenuHandle = context_menu->getHandle(); -} - -// virtual -void LLGroupList::draw() -{ - if (mDirty) - refresh(); - - LLFlatListView::draw(); -} - -// virtual -bool LLGroupList::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = LLUICtrl::handleRightMouseDown(x, y, mask); - - if (mForAgent) - { - LLToggleableMenu* context_menu = mContextMenuHandle.get(); - if (context_menu && size() > 0) - { - context_menu->buildDrawLabels(); - context_menu->updateParent(LLMenuGL::sMenuContainer); - LLMenuGL::showPopup(this, context_menu, x, y); - } - } - - return handled; -} - -// virtual -bool LLGroupList::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - bool handled = LLView::handleDoubleClick(x, y, mask); - // Handle double click only for the selected item in the list, skip clicks on empty space. - if (handled) - { - if (mDoubleClickSignal && getItemsRect().pointInRect(x, y)) - { - (*mDoubleClickSignal)(this, x, y, mask); - } - } - - return handled; -} - -void LLGroupList::setNameFilter(const std::string& filter) -{ - std::string filter_upper = filter; - LLStringUtil::toUpper(filter_upper); - if (mNameFilter != filter_upper) - { - mNameFilter = filter_upper; - - // set no items message depend on filter state - updateNoItemsMessage(filter); - - setDirty(); - } -} - -static bool findInsensitive(std::string haystack, const std::string& needle_upper) -{ - LLStringUtil::toUpper(haystack); - return haystack.find(needle_upper) != std::string::npos; -} - -void LLGroupList::refresh() -{ - if (mForAgent) - { - const LLUUID& highlight_id = gAgent.getGroupID(); - S32 count = gAgent.mGroups.size(); - LLUUID id; - bool have_filter = !mNameFilter.empty(); - - clear(); - - for(S32 i = 0; i < count; ++i) - { - id = gAgent.mGroups.at(i).mID; - const LLGroupData& group_data = gAgent.mGroups.at(i); - if (have_filter && !findInsensitive(group_data.mName, mNameFilter)) - continue; - addNewItem(id, group_data.mName, group_data.mInsigniaID, ADD_BOTTOM, group_data.mListInProfile); - } - - // Sort the list. - sort(); - - // Add "none" to list at top if filter not set (what's the point of filtering "none"?). - // but only if some real groups exists. EXT-4838 - if (!have_filter && count > 0 && mShowNone) - { - std::string loc_none = LLTrans::getString("GroupsNone"); - addNewItem(LLUUID::null, loc_none, LLUUID::null, ADD_TOP); - } - - selectItemByUUID(highlight_id); - } - else - { - clear(); - - for (group_map_t::iterator it = mGroups.begin(); it != mGroups.end(); ++it) - { - addNewItem(it->second, it->first, LLUUID::null, ADD_BOTTOM); - } - - // Sort the list. - sort(); - } - - setDirty(false); - onCommit(); -} - -void LLGroupList::toggleIcons() -{ - // Save the new value for new items to use. - mShowIcons = !mShowIcons; - gSavedSettings.setBOOL("GroupListShowIcons", mShowIcons); - - // Show/hide icons for all existing items. - std::vector items; - getItems(items); - for( std::vector::const_iterator it = items.begin(); it != items.end(); it++) - { - static_cast(*it)->setGroupIconVisible(mShowIcons); - } -} - -void LLGroupList::setGroups(const std::map< std::string,LLUUID> group_list) -{ - mGroups = group_list; - setDirty(true); -} - -////////////////////////////////////////////////////////////////////////// -// PRIVATE Section -////////////////////////////////////////////////////////////////////////// - -void LLGroupList::addNewItem(const LLUUID& id, const std::string& name, const LLUUID& icon_id, EAddPosition pos, bool visible_in_profile) -{ - LLGroupListItem* item = new LLGroupListItem(mForAgent, mShowIcons); - - item->setGroupID(id); - item->setName(name, mNameFilter); - item->setGroupIconID(icon_id); - - item->getChildView("info_btn")->setVisible( false); - item->getChildView("profile_btn")->setVisible( false); - item->getChildView("notices_btn")->setVisible(false); - item->setGroupIconVisible(mShowIcons); - if (!mShowIcons) - { - item->setVisibleInProfile(visible_in_profile); - } - addItem(item, id, pos); - -// setCommentVisible(false); -} - -// virtual -bool LLGroupList::handleEvent(LLPointer event, const LLSD& userdata) -{ - // Why is "new group" sufficient? - if (event->desc() == "new group") - { - setDirty(); - return true; - } - - if (event->desc() == "value_changed") - { - LLSD data = event->getValue(); - if (data.has("group_id") && data.has("visible")) - { - LLUUID group_id = data["group_id"].asUUID(); - bool visible = data["visible"].asBoolean(); - - std::vector items; - getItems(items); - for (std::vector::iterator it = items.begin(); it != items.end(); ++it) - { - LLGroupListItem* item = dynamic_cast(*it); - if (item && item->getGroupID() == group_id) - { - item->setVisibleInProfile(visible); - break; - } - } - } - return true; - } - - return false; -} - -bool LLGroupList::onContextMenuItemClick(const LLSD& userdata) -{ - std::string action = userdata.asString(); - LLUUID selected_group = getSelectedUUID(); - - if (action == "view_info") - { - LLGroupActions::show(selected_group); - } - else if (action == "chat") - { - LLGroupActions::startIM(selected_group); - } - else if (action == "call") - { - LLGroupActions::startCall(selected_group); - } - else if (action == "activate") - { - LLGroupActions::activate(selected_group); - } - else if (action == "leave") - { - LLGroupActions::leave(selected_group); - } - - return true; -} - -bool LLGroupList::onContextMenuItemEnable(const LLSD& userdata) -{ - LLUUID selected_group_id = getSelectedUUID(); - bool real_group_selected = selected_group_id.notNull(); // a "real" (not "none") group is selected - - // each group including "none" can be activated - if (userdata.asString() == "activate") - return gAgent.getGroupID() != selected_group_id; - - if (userdata.asString() == "call") - return real_group_selected && LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking(); - - return real_group_selected; -} - -/************************************************************************/ -/* LLGroupListItem implementation */ -/************************************************************************/ - -LLGroupListItem::LLGroupListItem(bool for_agent, bool show_icons) -: LLPanel(), -mGroupIcon(NULL), -mGroupNameBox(NULL), -mInfoBtn(NULL), -mProfileBtn(NULL), -mNoticesBtn(NULL), -mVisibilityHideBtn(NULL), -mVisibilityShowBtn(NULL), -mGroupID(LLUUID::null), -mForAgent(for_agent) -{ - if (show_icons) - { - buildFromFile( "panel_group_list_item.xml"); - } - else - { - buildFromFile( "panel_group_list_item_short.xml"); - } -} - -LLGroupListItem::~LLGroupListItem() -{ - LLGroupMgr::getInstance()->removeObserver(this); -} - -//virtual -bool LLGroupListItem::postBuild() -{ - mGroupIcon = getChild("group_icon"); - mGroupNameBox = getChild("group_name"); - - mInfoBtn = getChild("info_btn"); - mInfoBtn->setClickedCallback(boost::bind(&LLGroupListItem::onInfoBtnClick, this)); - - mProfileBtn = getChild("profile_btn"); - mProfileBtn->setClickedCallback([this](LLUICtrl *, const LLSD &) { onProfileBtnClick(); }); - - mNoticesBtn = getChild("notices_btn"); - mNoticesBtn->setClickedCallback([this](LLUICtrl *, const LLSD &) { onNoticesBtnClick(); }); - - mVisibilityHideBtn = findChild("visibility_hide_btn"); - if (mVisibilityHideBtn) - { - mVisibilityHideBtn->setClickedCallback([this](LLUICtrl *, const LLSD &) { onVisibilityBtnClick(false); }); - } - mVisibilityShowBtn = findChild("visibility_show_btn"); - if (mVisibilityShowBtn) - { - mVisibilityShowBtn->setClickedCallback([this](LLUICtrl *, const LLSD &) { onVisibilityBtnClick(true); }); - } - - // Remember group icon width including its padding from the name text box, - // so that we can hide and show the icon again later. - // Also note that panel_group_list_item and panel_group_list_item_short - // have icons of different sizes so we need to figure it per file. - mIconWidth = mGroupNameBox->getRect().mLeft - mGroupIcon->getRect().mLeft; - - return true; -} - -//virtual -void LLGroupListItem::setValue( const LLSD& value ) -{ - if (!value.isMap()) return; - if (!value.has("selected")) return; - getChildView("selected_icon")->setVisible( value["selected"]); -} - -void LLGroupListItem::onMouseEnter(S32 x, S32 y, MASK mask) -{ - getChildView("hovered_icon")->setVisible( true); - if (mGroupID.notNull()) // don't show the info button for the "none" group - { - mInfoBtn->setVisible(true); - mProfileBtn->setVisible(true); - if (mForAgent) - { - LLGroupData agent_gdatap; - if (gAgent.getGroupData(mGroupID, agent_gdatap)) - { - if (mVisibilityHideBtn) - { - mVisibilityHideBtn->setVisible(agent_gdatap.mListInProfile); - mVisibilityShowBtn->setVisible(!agent_gdatap.mListInProfile); - } - mNoticesBtn->setVisible(true); - } - } - } - - LLPanel::onMouseEnter(x, y, mask); -} - -void LLGroupListItem::onMouseLeave(S32 x, S32 y, MASK mask) -{ - getChildView("hovered_icon")->setVisible( false); - mInfoBtn->setVisible(false); - mProfileBtn->setVisible(false); - mNoticesBtn->setVisible(false); - if (mVisibilityHideBtn) - { - mVisibilityHideBtn->setVisible(false); - mVisibilityShowBtn->setVisible(false); - } - - LLPanel::onMouseLeave(x, y, mask); -} - -void LLGroupListItem::setName(const std::string& name, const std::string& highlight) -{ - mGroupName = name; - LLTextUtil::textboxSetHighlightedVal(mGroupNameBox, mGroupNameStyle, name, highlight); - mGroupNameBox->setToolTip(name); -} - -void LLGroupListItem::setGroupID(const LLUUID& group_id) -{ - LLGroupMgr::getInstance()->removeObserver(this); - - mID = group_id; - mGroupID = group_id; - - if (mForAgent) - { - // Active group should be bold. - setBold(group_id == gAgent.getGroupID()); - } - else - { - // Groups shared with the agent should be bold - setBold(gAgent.isInGroup(group_id, true)); - } - - LLGroupMgr::getInstance()->addObserver(this); -} - -void LLGroupListItem::setGroupIconID(const LLUUID& group_icon_id) -{ - mGroupIcon->setIconId(group_icon_id); -} - -void LLGroupListItem::setGroupIconVisible(bool visible) -{ - // Already done? Then do nothing. - if (mGroupIcon->getVisible() == (bool)visible) - return; - - // Show/hide the group icon. - mGroupIcon->setVisible(visible); - - // Move the group name horizontally by icon size + its distance from the group name. - LLRect name_rect = mGroupNameBox->getRect(); - name_rect.mLeft += visible ? mIconWidth : -mIconWidth; - mGroupNameBox->setRect(name_rect); -} - -void LLGroupListItem::setVisibleInProfile(bool visible) -{ - mGroupNameBox->setColor(LLUIColorTable::instance().getColor((visible ? "GroupVisibleInProfile" : "GroupHiddenInProfile"), LLColor4::red).get()); -} - -////////////////////////////////////////////////////////////////////////// -// Private Section -////////////////////////////////////////////////////////////////////////// -void LLGroupListItem::setBold(bool bold) -{ - // *BUG: setName() overrides the style params. - - LLFontDescriptor new_desc(mGroupNameBox->getFont()->getFontDesc()); - - // *NOTE dzaporozhan - // On Windows LLFontGL::NORMAL will not remove LLFontGL::BOLD if font - // is predefined as bold (SansSerifSmallBold, for example) - new_desc.setStyle(bold ? LLFontGL::BOLD : LLFontGL::NORMAL); - LLFontGL* new_font = LLFontGL::getFont(new_desc); - mGroupNameStyle.font = new_font; - - // *NOTE: You cannot set the style on a text box anymore, you must - // rebuild the text. This will cause problems if the text contains - // hyperlinks, as their styles will be wrong. - mGroupNameBox->setText(mGroupName, mGroupNameStyle); -} - -void LLGroupListItem::onInfoBtnClick() -{ - LLFloaterReg::showInstance("inspect_group", LLSD().with("group_id", mGroupID)); -} - -void LLGroupListItem::onProfileBtnClick() -{ - LLGroupActions::show(mGroupID); -} - -void LLGroupListItem::onNoticesBtnClick() -{ - LLGroupActions::show(mGroupID, true); -} - -void LLGroupListItem::onVisibilityBtnClick(bool new_visibility) -{ - LLGroupData agent_gdatap; - if (gAgent.getGroupData(mGroupID, agent_gdatap)) - { - gAgent.setUserGroupFlags(mGroupID, agent_gdatap.mAcceptNotices, new_visibility); - setVisibleInProfile(new_visibility); - mVisibilityHideBtn->setVisible(new_visibility); - mVisibilityShowBtn->setVisible(!new_visibility); - } -} - -void LLGroupListItem::changed(LLGroupChange gc) -{ - LLGroupMgrGroupData* group_data = LLGroupMgr::getInstance()->getGroupData(mID); - if ((gc == GC_ALL || gc == GC_PROPERTIES) && group_data) - { - setGroupIconID(group_data->mInsigniaID); - } -} - -//EOF +/** + * @file llgrouplist.cpp + * @brief List of the groups the agent belongs to. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llgrouplist.h" + +// libs +#include "llbutton.h" +#include "llgroupiconctrl.h" +#include "llmenugl.h" +#include "lltextbox.h" +#include "lltextutil.h" +#include "lltrans.h" + +// newview +#include "llagent.h" +#include "llgroupactions.h" +#include "llfloaterreg.h" +#include "llviewercontrol.h" // for gSavedSettings +#include "llviewermenu.h" // for gMenuHolder +#include "llvoiceclient.h" + +static LLDefaultChildRegistry::Register r("group_list"); + +class LLGroupComparator : public LLFlatListView::ItemComparator +{ +public: + LLGroupComparator() {}; + + /** Returns true if item1 < item2, false otherwise */ + /*virtual*/ bool compare(const LLPanel* item1, const LLPanel* item2) const + { + std::string name1 = static_cast(item1)->getGroupName(); + std::string name2 = static_cast(item2)->getGroupName(); + + LLStringUtil::toUpper(name1); + LLStringUtil::toUpper(name2); + + return name1 < name2; + } +}; + +class LLSharedGroupComparator : public LLFlatListView::ItemComparator +{ +public: + LLSharedGroupComparator() {}; + + /*virtual*/ bool compare(const LLPanel* item1, const LLPanel* item2) const + { + const LLGroupListItem* group_item1 = static_cast(item1); + std::string name1 = group_item1->getGroupName(); + bool item1_shared = gAgent.isInGroup(group_item1->getGroupID(), true); + + const LLGroupListItem* group_item2 = static_cast(item2); + std::string name2 = group_item2->getGroupName(); + bool item2_shared = gAgent.isInGroup(group_item2->getGroupID(), true); + + if (item2_shared != item1_shared) + { + return item1_shared; + } + + LLStringUtil::toUpper(name1); + LLStringUtil::toUpper(name2); + + return name1 < name2; + } +}; + +static LLGroupComparator GROUP_COMPARATOR; +static LLSharedGroupComparator SHARED_GROUP_COMPARATOR; + +LLGroupList::Params::Params() +: for_agent("for_agent", true) +{ +} + +LLGroupList::LLGroupList(const Params& p) +: LLFlatListViewEx(p) + , mForAgent(p.for_agent) + , mDirty(true) // to force initial update + , mShowIcons(false) + , mShowNone(true) +{ + setCommitOnSelectionChange(true); + + // Set default sort order. + if (mForAgent) + { + setComparator(&GROUP_COMPARATOR); + } + else + { + // shared groups first + setComparator(&SHARED_GROUP_COMPARATOR); + } + + if (mForAgent) + { + enableForAgent(true); + } +} + +LLGroupList::~LLGroupList() +{ + if (mForAgent) gAgent.removeListener(this); + if (mContextMenuHandle.get()) mContextMenuHandle.get()->die(); +} + +void LLGroupList::enableForAgent(bool show_icons) +{ + mForAgent = true; + + mShowIcons = mForAgent && gSavedSettings.getBOOL("GroupListShowIcons") && show_icons; + + // Listen for agent group changes. + gAgent.addListener(this, "new group"); + + // Set up context menu. + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + + registrar.add("People.Groups.Action", boost::bind(&LLGroupList::onContextMenuItemClick, this, _2)); + enable_registrar.add("People.Groups.Enable", boost::bind(&LLGroupList::onContextMenuItemEnable, this, _2)); + + LLToggleableMenu* context_menu = LLUICtrlFactory::getInstance()->createFromFile("menu_people_groups.xml", + gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + if(context_menu) + mContextMenuHandle = context_menu->getHandle(); +} + +// virtual +void LLGroupList::draw() +{ + if (mDirty) + refresh(); + + LLFlatListView::draw(); +} + +// virtual +bool LLGroupList::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = LLUICtrl::handleRightMouseDown(x, y, mask); + + if (mForAgent) + { + LLToggleableMenu* context_menu = mContextMenuHandle.get(); + if (context_menu && size() > 0) + { + context_menu->buildDrawLabels(); + context_menu->updateParent(LLMenuGL::sMenuContainer); + LLMenuGL::showPopup(this, context_menu, x, y); + } + } + + return handled; +} + +// virtual +bool LLGroupList::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + bool handled = LLView::handleDoubleClick(x, y, mask); + // Handle double click only for the selected item in the list, skip clicks on empty space. + if (handled) + { + if (mDoubleClickSignal && getItemsRect().pointInRect(x, y)) + { + (*mDoubleClickSignal)(this, x, y, mask); + } + } + + return handled; +} + +void LLGroupList::setNameFilter(const std::string& filter) +{ + std::string filter_upper = filter; + LLStringUtil::toUpper(filter_upper); + if (mNameFilter != filter_upper) + { + mNameFilter = filter_upper; + + // set no items message depend on filter state + updateNoItemsMessage(filter); + + setDirty(); + } +} + +static bool findInsensitive(std::string haystack, const std::string& needle_upper) +{ + LLStringUtil::toUpper(haystack); + return haystack.find(needle_upper) != std::string::npos; +} + +void LLGroupList::refresh() +{ + if (mForAgent) + { + const LLUUID& highlight_id = gAgent.getGroupID(); + S32 count = gAgent.mGroups.size(); + LLUUID id; + bool have_filter = !mNameFilter.empty(); + + clear(); + + for(S32 i = 0; i < count; ++i) + { + id = gAgent.mGroups.at(i).mID; + const LLGroupData& group_data = gAgent.mGroups.at(i); + if (have_filter && !findInsensitive(group_data.mName, mNameFilter)) + continue; + addNewItem(id, group_data.mName, group_data.mInsigniaID, ADD_BOTTOM, group_data.mListInProfile); + } + + // Sort the list. + sort(); + + // Add "none" to list at top if filter not set (what's the point of filtering "none"?). + // but only if some real groups exists. EXT-4838 + if (!have_filter && count > 0 && mShowNone) + { + std::string loc_none = LLTrans::getString("GroupsNone"); + addNewItem(LLUUID::null, loc_none, LLUUID::null, ADD_TOP); + } + + selectItemByUUID(highlight_id); + } + else + { + clear(); + + for (group_map_t::iterator it = mGroups.begin(); it != mGroups.end(); ++it) + { + addNewItem(it->second, it->first, LLUUID::null, ADD_BOTTOM); + } + + // Sort the list. + sort(); + } + + setDirty(false); + onCommit(); +} + +void LLGroupList::toggleIcons() +{ + // Save the new value for new items to use. + mShowIcons = !mShowIcons; + gSavedSettings.setBOOL("GroupListShowIcons", mShowIcons); + + // Show/hide icons for all existing items. + std::vector items; + getItems(items); + for( std::vector::const_iterator it = items.begin(); it != items.end(); it++) + { + static_cast(*it)->setGroupIconVisible(mShowIcons); + } +} + +void LLGroupList::setGroups(const std::map< std::string,LLUUID> group_list) +{ + mGroups = group_list; + setDirty(true); +} + +////////////////////////////////////////////////////////////////////////// +// PRIVATE Section +////////////////////////////////////////////////////////////////////////// + +void LLGroupList::addNewItem(const LLUUID& id, const std::string& name, const LLUUID& icon_id, EAddPosition pos, bool visible_in_profile) +{ + LLGroupListItem* item = new LLGroupListItem(mForAgent, mShowIcons); + + item->setGroupID(id); + item->setName(name, mNameFilter); + item->setGroupIconID(icon_id); + + item->getChildView("info_btn")->setVisible( false); + item->getChildView("profile_btn")->setVisible( false); + item->getChildView("notices_btn")->setVisible(false); + item->setGroupIconVisible(mShowIcons); + if (!mShowIcons) + { + item->setVisibleInProfile(visible_in_profile); + } + addItem(item, id, pos); + +// setCommentVisible(false); +} + +// virtual +bool LLGroupList::handleEvent(LLPointer event, const LLSD& userdata) +{ + // Why is "new group" sufficient? + if (event->desc() == "new group") + { + setDirty(); + return true; + } + + if (event->desc() == "value_changed") + { + LLSD data = event->getValue(); + if (data.has("group_id") && data.has("visible")) + { + LLUUID group_id = data["group_id"].asUUID(); + bool visible = data["visible"].asBoolean(); + + std::vector items; + getItems(items); + for (std::vector::iterator it = items.begin(); it != items.end(); ++it) + { + LLGroupListItem* item = dynamic_cast(*it); + if (item && item->getGroupID() == group_id) + { + item->setVisibleInProfile(visible); + break; + } + } + } + return true; + } + + return false; +} + +bool LLGroupList::onContextMenuItemClick(const LLSD& userdata) +{ + std::string action = userdata.asString(); + LLUUID selected_group = getSelectedUUID(); + + if (action == "view_info") + { + LLGroupActions::show(selected_group); + } + else if (action == "chat") + { + LLGroupActions::startIM(selected_group); + } + else if (action == "call") + { + LLGroupActions::startCall(selected_group); + } + else if (action == "activate") + { + LLGroupActions::activate(selected_group); + } + else if (action == "leave") + { + LLGroupActions::leave(selected_group); + } + + return true; +} + +bool LLGroupList::onContextMenuItemEnable(const LLSD& userdata) +{ + LLUUID selected_group_id = getSelectedUUID(); + bool real_group_selected = selected_group_id.notNull(); // a "real" (not "none") group is selected + + // each group including "none" can be activated + if (userdata.asString() == "activate") + return gAgent.getGroupID() != selected_group_id; + + if (userdata.asString() == "call") + return real_group_selected && LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking(); + + return real_group_selected; +} + +/************************************************************************/ +/* LLGroupListItem implementation */ +/************************************************************************/ + +LLGroupListItem::LLGroupListItem(bool for_agent, bool show_icons) +: LLPanel(), +mGroupIcon(NULL), +mGroupNameBox(NULL), +mInfoBtn(NULL), +mProfileBtn(NULL), +mNoticesBtn(NULL), +mVisibilityHideBtn(NULL), +mVisibilityShowBtn(NULL), +mGroupID(LLUUID::null), +mForAgent(for_agent) +{ + if (show_icons) + { + buildFromFile( "panel_group_list_item.xml"); + } + else + { + buildFromFile( "panel_group_list_item_short.xml"); + } +} + +LLGroupListItem::~LLGroupListItem() +{ + LLGroupMgr::getInstance()->removeObserver(this); +} + +//virtual +bool LLGroupListItem::postBuild() +{ + mGroupIcon = getChild("group_icon"); + mGroupNameBox = getChild("group_name"); + + mInfoBtn = getChild("info_btn"); + mInfoBtn->setClickedCallback(boost::bind(&LLGroupListItem::onInfoBtnClick, this)); + + mProfileBtn = getChild("profile_btn"); + mProfileBtn->setClickedCallback([this](LLUICtrl *, const LLSD &) { onProfileBtnClick(); }); + + mNoticesBtn = getChild("notices_btn"); + mNoticesBtn->setClickedCallback([this](LLUICtrl *, const LLSD &) { onNoticesBtnClick(); }); + + mVisibilityHideBtn = findChild("visibility_hide_btn"); + if (mVisibilityHideBtn) + { + mVisibilityHideBtn->setClickedCallback([this](LLUICtrl *, const LLSD &) { onVisibilityBtnClick(false); }); + } + mVisibilityShowBtn = findChild("visibility_show_btn"); + if (mVisibilityShowBtn) + { + mVisibilityShowBtn->setClickedCallback([this](LLUICtrl *, const LLSD &) { onVisibilityBtnClick(true); }); + } + + // Remember group icon width including its padding from the name text box, + // so that we can hide and show the icon again later. + // Also note that panel_group_list_item and panel_group_list_item_short + // have icons of different sizes so we need to figure it per file. + mIconWidth = mGroupNameBox->getRect().mLeft - mGroupIcon->getRect().mLeft; + + return true; +} + +//virtual +void LLGroupListItem::setValue( const LLSD& value ) +{ + if (!value.isMap()) return; + if (!value.has("selected")) return; + getChildView("selected_icon")->setVisible( value["selected"]); +} + +void LLGroupListItem::onMouseEnter(S32 x, S32 y, MASK mask) +{ + getChildView("hovered_icon")->setVisible( true); + if (mGroupID.notNull()) // don't show the info button for the "none" group + { + mInfoBtn->setVisible(true); + mProfileBtn->setVisible(true); + if (mForAgent) + { + LLGroupData agent_gdatap; + if (gAgent.getGroupData(mGroupID, agent_gdatap)) + { + if (mVisibilityHideBtn) + { + mVisibilityHideBtn->setVisible(agent_gdatap.mListInProfile); + mVisibilityShowBtn->setVisible(!agent_gdatap.mListInProfile); + } + mNoticesBtn->setVisible(true); + } + } + } + + LLPanel::onMouseEnter(x, y, mask); +} + +void LLGroupListItem::onMouseLeave(S32 x, S32 y, MASK mask) +{ + getChildView("hovered_icon")->setVisible( false); + mInfoBtn->setVisible(false); + mProfileBtn->setVisible(false); + mNoticesBtn->setVisible(false); + if (mVisibilityHideBtn) + { + mVisibilityHideBtn->setVisible(false); + mVisibilityShowBtn->setVisible(false); + } + + LLPanel::onMouseLeave(x, y, mask); +} + +void LLGroupListItem::setName(const std::string& name, const std::string& highlight) +{ + mGroupName = name; + LLTextUtil::textboxSetHighlightedVal(mGroupNameBox, mGroupNameStyle, name, highlight); + mGroupNameBox->setToolTip(name); +} + +void LLGroupListItem::setGroupID(const LLUUID& group_id) +{ + LLGroupMgr::getInstance()->removeObserver(this); + + mID = group_id; + mGroupID = group_id; + + if (mForAgent) + { + // Active group should be bold. + setBold(group_id == gAgent.getGroupID()); + } + else + { + // Groups shared with the agent should be bold + setBold(gAgent.isInGroup(group_id, true)); + } + + LLGroupMgr::getInstance()->addObserver(this); +} + +void LLGroupListItem::setGroupIconID(const LLUUID& group_icon_id) +{ + mGroupIcon->setIconId(group_icon_id); +} + +void LLGroupListItem::setGroupIconVisible(bool visible) +{ + // Already done? Then do nothing. + if (mGroupIcon->getVisible() == (bool)visible) + return; + + // Show/hide the group icon. + mGroupIcon->setVisible(visible); + + // Move the group name horizontally by icon size + its distance from the group name. + LLRect name_rect = mGroupNameBox->getRect(); + name_rect.mLeft += visible ? mIconWidth : -mIconWidth; + mGroupNameBox->setRect(name_rect); +} + +void LLGroupListItem::setVisibleInProfile(bool visible) +{ + mGroupNameBox->setColor(LLUIColorTable::instance().getColor((visible ? "GroupVisibleInProfile" : "GroupHiddenInProfile"), LLColor4::red).get()); +} + +////////////////////////////////////////////////////////////////////////// +// Private Section +////////////////////////////////////////////////////////////////////////// +void LLGroupListItem::setBold(bool bold) +{ + // *BUG: setName() overrides the style params. + + LLFontDescriptor new_desc(mGroupNameBox->getFont()->getFontDesc()); + + // *NOTE dzaporozhan + // On Windows LLFontGL::NORMAL will not remove LLFontGL::BOLD if font + // is predefined as bold (SansSerifSmallBold, for example) + new_desc.setStyle(bold ? LLFontGL::BOLD : LLFontGL::NORMAL); + LLFontGL* new_font = LLFontGL::getFont(new_desc); + mGroupNameStyle.font = new_font; + + // *NOTE: You cannot set the style on a text box anymore, you must + // rebuild the text. This will cause problems if the text contains + // hyperlinks, as their styles will be wrong. + mGroupNameBox->setText(mGroupName, mGroupNameStyle); +} + +void LLGroupListItem::onInfoBtnClick() +{ + LLFloaterReg::showInstance("inspect_group", LLSD().with("group_id", mGroupID)); +} + +void LLGroupListItem::onProfileBtnClick() +{ + LLGroupActions::show(mGroupID); +} + +void LLGroupListItem::onNoticesBtnClick() +{ + LLGroupActions::show(mGroupID, true); +} + +void LLGroupListItem::onVisibilityBtnClick(bool new_visibility) +{ + LLGroupData agent_gdatap; + if (gAgent.getGroupData(mGroupID, agent_gdatap)) + { + gAgent.setUserGroupFlags(mGroupID, agent_gdatap.mAcceptNotices, new_visibility); + setVisibleInProfile(new_visibility); + mVisibilityHideBtn->setVisible(new_visibility); + mVisibilityShowBtn->setVisible(!new_visibility); + } +} + +void LLGroupListItem::changed(LLGroupChange gc) +{ + LLGroupMgrGroupData* group_data = LLGroupMgr::getInstance()->getGroupData(mID); + if ((gc == GC_ALL || gc == GC_PROPERTIES) && group_data) + { + setGroupIconID(group_data->mInsigniaID); + } +} + +//EOF diff --git a/indra/newview/llgrouplist.h b/indra/newview/llgrouplist.h index 530281cf18..19d4e82a93 100644 --- a/indra/newview/llgrouplist.h +++ b/indra/newview/llgrouplist.h @@ -1,144 +1,144 @@ -/** - * @file llgrouplist.h - * @brief List of the groups the agent belongs to. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLGROUPLIST_H -#define LL_LLGROUPLIST_H - -#include "llevent.h" -#include "llpointer.h" - -#include "llflatlistview.h" -#include "llpanel.h" -#include "llstyle.h" -#include "lltoggleablemenu.h" - -#include "llgroupmgr.h" - -/** - * Auto-updating list of agent groups. - * - * Can use optional group name filter. - * - * @see setNameFilter() - */ -class LLGroupList: public LLFlatListViewEx, public LLOldEvents::LLSimpleListener -{ - LOG_CLASS(LLGroupList); -public: - struct Params : public LLInitParam::Block - { - Optional for_agent; - Params(); - }; - - LLGroupList(const Params& p); - virtual ~LLGroupList(); - - void enableForAgent(bool show_icons); - - virtual void draw(); // from LLView - /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); // from LLView - /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask); // from LLView - - void setNameFilter(const std::string& filter); - void toggleIcons(); - bool getIconsVisible() const { return mShowIcons; } - void setIconsVisible(bool show_icons) { mShowIcons = show_icons; } - void setShowNone(bool show_none) { mShowNone = show_none; } - void setGroups(const std::map< std::string,LLUUID> group_list); - - LLToggleableMenu* getContextMenu() const { return mContextMenuHandle.get(); } - -private: - void setDirty(bool val = true) { mDirty = val; } - void refresh(); - void addNewItem(const LLUUID& id, const std::string& name, const LLUUID& icon_id, EAddPosition pos = ADD_BOTTOM, bool visible_in_profile = true); - bool handleEvent(LLPointer event, const LLSD& userdata); // called on agent group list changes - - bool onContextMenuItemClick(const LLSD& userdata); - bool onContextMenuItemEnable(const LLSD& userdata); - - LLHandle mContextMenuHandle; - - bool mShowIcons; - bool mDirty; - std::string mNameFilter; - - bool mForAgent; - bool mShowNone; - typedef std::map< std::string,LLUUID> group_map_t; - group_map_t mGroups; -}; - -class LLButton; -class LLGroupIconCtrl; -class LLTextBox; - -class LLGroupListItem : public LLPanel - , public LLGroupMgrObserver -{ -public: - LLGroupListItem(bool for_agent, bool show_icons); - ~LLGroupListItem(); - /*virtual*/ bool postBuild(); - /*virtual*/ void setValue(const LLSD& value); - void onMouseEnter(S32 x, S32 y, MASK mask); - void onMouseLeave(S32 x, S32 y, MASK mask); - - const LLUUID& getGroupID() const { return mGroupID; } - const std::string& getGroupName() const { return mGroupName; } - - void setName(const std::string& name, const std::string& highlight = LLStringUtil::null); - void setGroupID(const LLUUID& group_id); - void setGroupIconID(const LLUUID& group_icon_id); - void setGroupIconVisible(bool visible); - - virtual void changed(LLGroupChange gc); - - void setVisibleInProfile(bool visible); -private: - void setBold(bool bold); - void onInfoBtnClick(); - void onProfileBtnClick(); - void onNoticesBtnClick(); - void onVisibilityBtnClick(bool new_visibility); - - LLTextBox* mGroupNameBox; - LLUUID mGroupID; - LLGroupIconCtrl* mGroupIcon; - LLButton* mInfoBtn; - LLButton* mProfileBtn; - LLButton* mNoticesBtn; - LLButton* mVisibilityHideBtn; - LLButton* mVisibilityShowBtn; - - std::string mGroupName; - bool mForAgent; - LLStyle::Params mGroupNameStyle; - - S32 mIconWidth; -}; -#endif // LL_LLGROUPLIST_H +/** + * @file llgrouplist.h + * @brief List of the groups the agent belongs to. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLGROUPLIST_H +#define LL_LLGROUPLIST_H + +#include "llevent.h" +#include "llpointer.h" + +#include "llflatlistview.h" +#include "llpanel.h" +#include "llstyle.h" +#include "lltoggleablemenu.h" + +#include "llgroupmgr.h" + +/** + * Auto-updating list of agent groups. + * + * Can use optional group name filter. + * + * @see setNameFilter() + */ +class LLGroupList: public LLFlatListViewEx, public LLOldEvents::LLSimpleListener +{ + LOG_CLASS(LLGroupList); +public: + struct Params : public LLInitParam::Block + { + Optional for_agent; + Params(); + }; + + LLGroupList(const Params& p); + virtual ~LLGroupList(); + + void enableForAgent(bool show_icons); + + virtual void draw(); // from LLView + /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); // from LLView + /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask); // from LLView + + void setNameFilter(const std::string& filter); + void toggleIcons(); + bool getIconsVisible() const { return mShowIcons; } + void setIconsVisible(bool show_icons) { mShowIcons = show_icons; } + void setShowNone(bool show_none) { mShowNone = show_none; } + void setGroups(const std::map< std::string,LLUUID> group_list); + + LLToggleableMenu* getContextMenu() const { return mContextMenuHandle.get(); } + +private: + void setDirty(bool val = true) { mDirty = val; } + void refresh(); + void addNewItem(const LLUUID& id, const std::string& name, const LLUUID& icon_id, EAddPosition pos = ADD_BOTTOM, bool visible_in_profile = true); + bool handleEvent(LLPointer event, const LLSD& userdata); // called on agent group list changes + + bool onContextMenuItemClick(const LLSD& userdata); + bool onContextMenuItemEnable(const LLSD& userdata); + + LLHandle mContextMenuHandle; + + bool mShowIcons; + bool mDirty; + std::string mNameFilter; + + bool mForAgent; + bool mShowNone; + typedef std::map< std::string,LLUUID> group_map_t; + group_map_t mGroups; +}; + +class LLButton; +class LLGroupIconCtrl; +class LLTextBox; + +class LLGroupListItem : public LLPanel + , public LLGroupMgrObserver +{ +public: + LLGroupListItem(bool for_agent, bool show_icons); + ~LLGroupListItem(); + /*virtual*/ bool postBuild(); + /*virtual*/ void setValue(const LLSD& value); + void onMouseEnter(S32 x, S32 y, MASK mask); + void onMouseLeave(S32 x, S32 y, MASK mask); + + const LLUUID& getGroupID() const { return mGroupID; } + const std::string& getGroupName() const { return mGroupName; } + + void setName(const std::string& name, const std::string& highlight = LLStringUtil::null); + void setGroupID(const LLUUID& group_id); + void setGroupIconID(const LLUUID& group_icon_id); + void setGroupIconVisible(bool visible); + + virtual void changed(LLGroupChange gc); + + void setVisibleInProfile(bool visible); +private: + void setBold(bool bold); + void onInfoBtnClick(); + void onProfileBtnClick(); + void onNoticesBtnClick(); + void onVisibilityBtnClick(bool new_visibility); + + LLTextBox* mGroupNameBox; + LLUUID mGroupID; + LLGroupIconCtrl* mGroupIcon; + LLButton* mInfoBtn; + LLButton* mProfileBtn; + LLButton* mNoticesBtn; + LLButton* mVisibilityHideBtn; + LLButton* mVisibilityShowBtn; + + std::string mGroupName; + bool mForAgent; + LLStyle::Params mGroupNameStyle; + + S32 mIconWidth; +}; +#endif // LL_LLGROUPLIST_H diff --git a/indra/newview/llgroupmgr.cpp b/indra/newview/llgroupmgr.cpp index e0366f20c1..32943a9bb7 100644 --- a/indra/newview/llgroupmgr.cpp +++ b/indra/newview/llgroupmgr.cpp @@ -1,2507 +1,2507 @@ -/** - * @file llgroupmgr.cpp - * @brief LLGroupMgr class implementation - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/** - * Manager for aggregating all client knowledge for specific groups - * Keeps a cache of group information. - */ - -#include "llviewerprecompiledheaders.h" - -#include "llgroupmgr.h" - -#include -#include - -#include "llappviewer.h" -#include "llagent.h" -#include "llavatarnamecache.h" -#include "llui.h" -#include "message.h" -#include "roles_constants.h" -#include "lltransactiontypes.h" -#include "llstatusbar.h" -#include "llviewerwindow.h" -#include "llpanelgroupcreate.h" -#include "llgroupactions.h" -#include "llnotificationsutil.h" -#include "lluictrlfactory.h" -#include "lltrans.h" -#include "llviewerregion.h" -#include -#include "llcorehttputil.h" -#include "lluiusage.h" - - -#if LL_MSVC -#pragma warning(push) -// disable boost::lexical_cast warning -#pragma warning (disable:4702) -#endif - -#include - -#if LL_MSVC -#pragma warning(pop) // Restore all warnings to the previous state -#endif - -const U32 MAX_CACHED_GROUPS = 20; - -// -// LLRoleActionSet -// -LLRoleActionSet::LLRoleActionSet() -: mActionSetData(NULL) -{ } - -LLRoleActionSet::~LLRoleActionSet() -{ - delete mActionSetData; - std::for_each(mActions.begin(), mActions.end(), DeletePointer()); - mActions.clear(); -} - -// -// LLGroupMemberData -// - -LLGroupMemberData::LLGroupMemberData(const LLUUID& id, - S32 contribution, - U64 agent_powers, - const std::string& title, - const std::string& online_status, - bool is_owner) : - mID(id), - mContribution(contribution), - mAgentPowers(agent_powers), - mTitle(title), - mOnlineStatus(online_status), - mIsOwner(is_owner) -{ -} - -LLGroupMemberData::~LLGroupMemberData() -{ -} - -void LLGroupMemberData::addRole(const LLUUID& role, LLGroupRoleData* rd) -{ - mRolesList[role] = rd; -} - -bool LLGroupMemberData::removeRole(const LLUUID& role) -{ - role_list_t::iterator it = mRolesList.find(role); - - if (it != mRolesList.end()) - { - mRolesList.erase(it); - return true; - } - - return false; -} - -// -// LLGroupRoleData -// - -LLGroupRoleData::LLGroupRoleData(const LLUUID& role_id, - const std::string& role_name, - const std::string& role_title, - const std::string& role_desc, - const U64 role_powers, - const S32 member_count) : - mRoleID(role_id), - mMemberCount(member_count), - mMembersNeedsSort(false) -{ - mRoleData.mRoleName = role_name; - mRoleData.mRoleTitle = role_title; - mRoleData.mRoleDescription = role_desc; - mRoleData.mRolePowers = role_powers; - mRoleData.mChangeType = RC_UPDATE_NONE; -} - -LLGroupRoleData::LLGroupRoleData(const LLUUID& role_id, - LLRoleData role_data, - const S32 member_count) : - mRoleID(role_id), - mRoleData(role_data), - mMemberCount(member_count), - mMembersNeedsSort(false) -{ - -} - -LLGroupRoleData::~LLGroupRoleData() -{ -} - -S32 LLGroupRoleData::getMembersInRole(uuid_vec_t members, - bool needs_sort) -{ - if (mRoleID.isNull()) - { - // This is the everyone role, just return the size of members, - // because everyone is in the everyone role. - return members.size(); - } - - // Sort the members list, if needed. - if (mMembersNeedsSort) - { - std::sort(mMemberIDs.begin(), mMemberIDs.end()); - mMembersNeedsSort = false; - } - if (needs_sort) - { - // Sort the members parameter. - std::sort(members.begin(), members.end()); - } - - // Return the number of members in the intersection. - S32 max_size = llmin( members.size(), mMemberIDs.size() ); - uuid_vec_t in_role( max_size ); - uuid_vec_t::iterator in_role_end; - in_role_end = std::set_intersection(mMemberIDs.begin(), mMemberIDs.end(), - members.begin(), members.end(), - in_role.begin()); - return in_role_end - in_role.begin(); -} - -void LLGroupRoleData::addMember(const LLUUID& member) -{ - mMembersNeedsSort = true; - mMemberIDs.push_back(member); -} - -bool LLGroupRoleData::removeMember(const LLUUID& member) -{ - uuid_vec_t::iterator it = std::find(mMemberIDs.begin(),mMemberIDs.end(),member); - - if (it != mMemberIDs.end()) - { - mMembersNeedsSort = true; - mMemberIDs.erase(it); - return true; - } - - return false; -} - -void LLGroupRoleData::clearMembers() -{ - mMembersNeedsSort = false; - mMemberIDs.clear(); -} - - -// -// LLGroupMgrGroupData -// - -LLGroupMgrGroupData::LLGroupMgrGroupData(const LLUUID& id) : - mID(id), - mShowInList(true), - mOpenEnrollment(false), - mMembershipFee(0), - mAllowPublish(false), - mListInProfile(false), - mMaturePublish(false), - mChanged(false), - mMemberCount(0), - mRoleCount(0), - mReceivedRoleMemberPairs(0), - mMemberDataComplete(false), - mRoleDataComplete(false), - mRoleMemberDataComplete(false), - mGroupPropertiesDataComplete(false), - mPendingRoleMemberRequest(false), - mAccessTime(0.0f), - mPendingBanRequest(false) -{ - mMemberVersion.generate(); -} - -void LLGroupMgrGroupData::setAccessed() -{ - mAccessTime = (F32)LLFrameTimer::getTotalSeconds(); -} - -bool LLGroupMgrGroupData::getRoleData(const LLUUID& role_id, LLRoleData& role_data) -{ - role_data_map_t::const_iterator it; - - // Do we have changes for it? - it = mRoleChanges.find(role_id); - if (it != mRoleChanges.end()) - { - if ((*it).second.mChangeType == RC_DELETE) return false; - - role_data = (*it).second; - return true; - } - - // Ok, no changes, hasn't been deleted, isn't a new role, just find the role. - role_list_t::const_iterator rit = mRoles.find(role_id); - if (rit != mRoles.end()) - { - role_data = (*rit).second->getRoleData(); - return true; - } - - // This role must not exist. - return false; -} - - -void LLGroupMgrGroupData::setRoleData(const LLUUID& role_id, LLRoleData role_data) -{ - // If this is a newly created group, we need to change the data in the created list. - role_data_map_t::iterator it; - it = mRoleChanges.find(role_id); - if (it != mRoleChanges.end()) - { - if ((*it).second.mChangeType == RC_CREATE) - { - role_data.mChangeType = RC_CREATE; - mRoleChanges[role_id] = role_data; - return; - } - else if ((*it).second.mChangeType == RC_DELETE) - { - // Don't do anything for a role being deleted. - return; - } - } - - // Not a new role, so put it in the changes list. - LLRoleData old_role_data; - role_list_t::iterator rit = mRoles.find(role_id); - if (rit != mRoles.end()) - { - bool data_change = ( ((*rit).second->mRoleData.mRoleDescription != role_data.mRoleDescription) - || ((*rit).second->mRoleData.mRoleName != role_data.mRoleName) - || ((*rit).second->mRoleData.mRoleTitle != role_data.mRoleTitle) ); - bool powers_change = ((*rit).second->mRoleData.mRolePowers != role_data.mRolePowers); - - if (!data_change && !powers_change) - { - // We are back to the original state, the changes have been 'undone' so take out the change. - mRoleChanges.erase(role_id); - return; - } - - if (data_change && powers_change) - { - role_data.mChangeType = RC_UPDATE_ALL; - } - else if (data_change) - { - role_data.mChangeType = RC_UPDATE_DATA; - } - else - { - role_data.mChangeType = RC_UPDATE_POWERS; - } - - mRoleChanges[role_id] = role_data; - } - else - { - LL_WARNS() << "Change being made to non-existant role " << role_id << LL_ENDL; - } -} - -bool LLGroupMgrGroupData::pendingRoleChanges() -{ - return (!mRoleChanges.empty()); -} - -// This is a no-op if the role has already been created. -void LLGroupMgrGroupData::createRole(const LLUUID& role_id, LLRoleData role_data) -{ - if (mRoleChanges.find(role_id) != mRoleChanges.end()) - { - LL_WARNS() << "create role for existing role! " << role_id << LL_ENDL; - } - else - { - role_data.mChangeType = RC_CREATE; - mRoleChanges[role_id] = role_data; - } -} - -void LLGroupMgrGroupData::deleteRole(const LLUUID& role_id) -{ - role_data_map_t::iterator it; - - // If this was a new role, just discard it. - it = mRoleChanges.find(role_id); - if (it != mRoleChanges.end() - && (*it).second.mChangeType == RC_CREATE) - { - mRoleChanges.erase(it); - return; - } - - LLRoleData rd; - rd.mChangeType = RC_DELETE; - mRoleChanges[role_id] = rd; -} - -void LLGroupMgrGroupData::addRolePower(const LLUUID &role_id, U64 power) -{ - LLRoleData rd; - if (getRoleData(role_id,rd)) - { - rd.mRolePowers |= power; - setRoleData(role_id,rd); - } - else - { - LL_WARNS() << "addRolePower: no role data found for " << role_id << LL_ENDL; - } -} - -void LLGroupMgrGroupData::removeRolePower(const LLUUID &role_id, U64 power) -{ - LLRoleData rd; - if (getRoleData(role_id,rd)) - { - rd.mRolePowers &= ~power; - setRoleData(role_id,rd); - } - else - { - LL_WARNS() << "removeRolePower: no role data found for " << role_id << LL_ENDL; - } -} - -U64 LLGroupMgrGroupData::getRolePowers(const LLUUID& role_id) -{ - LLRoleData rd; - if (getRoleData(role_id,rd)) - { - return rd.mRolePowers; - } - else - { - LL_WARNS() << "getRolePowers: no role data found for " << role_id << LL_ENDL; - return GP_NO_POWERS; - } -} - -void LLGroupMgrGroupData::removeData() -{ - // Remove member data first, because removeRoleData will walk the member list - removeMemberData(); - removeRoleData(); -} - -void LLGroupMgrGroupData::removeMemberData() -{ - for (member_list_t::iterator mi = mMembers.begin(); mi != mMembers.end(); ++mi) - { - delete mi->second; - } - mMembers.clear(); - mMemberDataComplete = false; - mMemberVersion.generate(); -} - -void LLGroupMgrGroupData::removeRoleData() -{ - for (member_list_t::iterator mi = mMembers.begin(); mi != mMembers.end(); ++mi) - { - LLGroupMemberData* data = mi->second; - if (data) - { - data->clearRoles(); - } - } - - for (role_list_t::iterator ri = mRoles.begin(); ri != mRoles.end(); ++ri) - { - LLGroupRoleData* data = ri->second; - delete data; - } - mRoles.clear(); - mReceivedRoleMemberPairs = 0; - mRoleDataComplete = false; - mRoleMemberDataComplete= false; -} - -void LLGroupMgrGroupData::removeRoleMemberData() -{ - for (member_list_t::iterator mi = mMembers.begin(); mi != mMembers.end(); ++mi) - { - LLGroupMemberData* data = mi->second; - if (data) - { - data->clearRoles(); - } - } - - for (role_list_t::iterator ri = mRoles.begin(); ri != mRoles.end(); ++ri) - { - LLGroupRoleData* data = ri->second; - if (data) - { - data->clearMembers(); - } - } - - mReceivedRoleMemberPairs = 0; - mRoleMemberDataComplete= false; -} - -LLGroupMgrGroupData::~LLGroupMgrGroupData() -{ - removeData(); -} - -bool LLGroupMgrGroupData::changeRoleMember(const LLUUID& role_id, - const LLUUID& member_id, - LLRoleMemberChangeType rmc) -{ - role_list_t::iterator ri = mRoles.find(role_id); - member_list_t::iterator mi = mMembers.find(member_id); - - if (ri == mRoles.end() - || mi == mMembers.end() ) - { - if (ri == mRoles.end()) LL_WARNS() << "LLGroupMgrGroupData::changeRoleMember couldn't find role " << role_id << LL_ENDL; - if (mi == mMembers.end()) LL_WARNS() << "LLGroupMgrGroupData::changeRoleMember couldn't find member " << member_id << LL_ENDL; - return false; - } - - LLGroupRoleData* grd = ri->second; - LLGroupMemberData* gmd = mi->second; - - if (!grd || !gmd) - { - LL_WARNS() << "LLGroupMgrGroupData::changeRoleMember couldn't get member or role data." << LL_ENDL; - return false; - } - - if (RMC_ADD == rmc) - { - LL_INFOS() << " adding member to role." << LL_ENDL; - grd->addMember(member_id); - gmd->addRole(role_id,grd); - - //TODO move this into addrole function - //see if they added someone to the owner role and update isOwner - gmd->mIsOwner = (role_id == mOwnerRole) ? true : gmd->mIsOwner; - } - else if (RMC_REMOVE == rmc) - { - LL_INFOS() << " removing member from role." << LL_ENDL; - grd->removeMember(member_id); - gmd->removeRole(role_id); - - //see if they removed someone from the owner role and update isOwner - gmd->mIsOwner = (role_id == mOwnerRole) ? false : gmd->mIsOwner; - } - - lluuid_pair role_member; - role_member.first = role_id; - role_member.second = member_id; - - change_map_t::iterator it = mRoleMemberChanges.find(role_member); - if (it != mRoleMemberChanges.end()) - { - // There was already a role change for this role_member - if (it->second.mChange == rmc) - { - // Already recorded this change? Weird. - LL_INFOS() << "Received duplicate change for " - << " role: " << role_id << " member " << member_id - << " change " << (rmc == RMC_ADD ? "ADD" : "REMOVE") << LL_ENDL; - } - else - { - // The only two operations (add and remove) currently cancel each other out - // If that changes this will need more logic - if (rmc == RMC_NONE) - { - LL_WARNS() << "changeRoleMember: existing entry with 'RMC_NONE' change! This shouldn't happen." << LL_ENDL; - LLRoleMemberChange rc(role_id,member_id,rmc); - mRoleMemberChanges[role_member] = rc; - } - else - { - mRoleMemberChanges.erase(it); - } - } - } - else - { - LLRoleMemberChange rc(role_id,member_id,rmc); - mRoleMemberChanges[role_member] = rc; - } - - recalcAgentPowers(member_id); - - mChanged = true; - return true; -} - -void LLGroupMgrGroupData::recalcAllAgentPowers() -{ - LLGroupMemberData* gmd; - - for (member_list_t::iterator mit = mMembers.begin(); - mit != mMembers.end(); ++mit) - { - gmd = mit->second; - if (!gmd) continue; - - gmd->mAgentPowers = 0; - for (LLGroupMemberData::role_list_t::iterator it = gmd->mRolesList.begin(); - it != gmd->mRolesList.end(); ++it) - { - LLGroupRoleData* grd = (*it).second; - if (!grd) continue; - - gmd->mAgentPowers |= grd->mRoleData.mRolePowers; - } - } -} - -void LLGroupMgrGroupData::recalcAgentPowers(const LLUUID& agent_id) -{ - member_list_t::iterator mi = mMembers.find(agent_id); - if (mi == mMembers.end()) return; - - LLGroupMemberData* gmd = mi->second; - - if (!gmd) return; - - gmd->mAgentPowers = 0; - for (LLGroupMemberData::role_list_t::iterator it = gmd->mRolesList.begin(); - it != gmd->mRolesList.end(); ++it) - { - LLGroupRoleData* grd = (*it).second; - if (!grd) continue; - - gmd->mAgentPowers |= grd->mRoleData.mRolePowers; - } -} - -bool LLGroupMgrGroupData::isSingleMemberNotOwner() -{ - return mMembers.size() == 1 && !mMembers.begin()->second->isOwner(); -} - -bool packRoleUpdateMessageBlock(LLMessageSystem* msg, - const LLUUID& group_id, - const LLUUID& role_id, - const LLRoleData& role_data, - bool start_message) -{ - if (start_message) - { - msg->newMessage("GroupRoleUpdate"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID",gAgent.getID()); - msg->addUUID("SessionID",gAgent.getSessionID()); - msg->addUUID("GroupID",group_id); - start_message = false; - } - - msg->nextBlock("RoleData"); - msg->addUUID("RoleID",role_id); - msg->addString("Name", role_data.mRoleName); - msg->addString("Description", role_data.mRoleDescription); - msg->addString("Title", role_data.mRoleTitle); - msg->addU64("Powers", role_data.mRolePowers); - msg->addU8("UpdateType", (U8)role_data.mChangeType); - - if (msg->isSendFullFast()) - { - gAgent.sendReliableMessage(); - start_message = true; - } - - return start_message; -} - -void LLGroupMgrGroupData::sendRoleChanges() -{ - // Commit changes locally - LLGroupRoleData* grd; - role_list_t::iterator role_it; - LLMessageSystem* msg = gMessageSystem; - bool start_message = true; - - bool need_role_cleanup = false; - bool need_role_data = false; - bool need_power_recalc = false; - - // Apply all changes - for (role_data_map_t::iterator iter = mRoleChanges.begin(); - iter != mRoleChanges.end(); ) - { - role_data_map_t::iterator it = iter++; // safely incrament iter - const LLUUID& role_id = (*it).first; - const LLRoleData& role_data = (*it).second; - - // Commit to local data set - role_it = mRoles.find((*it).first); - if ( (mRoles.end() == role_it - && RC_CREATE != role_data.mChangeType) - || (mRoles.end() != role_it - && RC_CREATE == role_data.mChangeType)) - { - continue; - } - - // NOTE: role_it is valid EXCEPT for the RC_CREATE case - switch (role_data.mChangeType) - { - case RC_CREATE: - { - // NOTE: role_it is NOT valid in this case - grd = new LLGroupRoleData(role_id, role_data, 0); - mRoles[role_id] = grd; - need_role_data = true; - break; - } - case RC_DELETE: - { - LLGroupRoleData* group_role_data = (*role_it).second; - delete group_role_data; - mRoles.erase(role_it); - need_role_cleanup = true; - need_power_recalc = true; - break; - } - case RC_UPDATE_ALL: - // fall through - case RC_UPDATE_POWERS: - need_power_recalc = true; - // fall through - case RC_UPDATE_DATA: - // fall through - default: - { - LLGroupRoleData* group_role_data = (*role_it).second; - group_role_data->setRoleData(role_data); // NOTE! might modify mRoleChanges! - break; - } - } - - // Update dataserver - start_message = packRoleUpdateMessageBlock(msg,getID(),role_id,role_data,start_message); - } - - if (!start_message) - { - gAgent.sendReliableMessage(); - } - - // If we delete a role then all the role-member pairs are invalid! - if (need_role_cleanup) - { - removeRoleMemberData(); - } - - // If we create a new role, then we need to re-fetch all the role data. - if (need_role_data) - { - LLGroupMgr::getInstance()->sendGroupRoleDataRequest(getID()); - } - - // Clean up change lists - mRoleChanges.clear(); - - // Recalculate all the agent powers because role powers have now changed. - if (need_power_recalc) - { - recalcAllAgentPowers(); - } -} - -void LLGroupMgrGroupData::cancelRoleChanges() -{ - // Clear out all changes! - mRoleChanges.clear(); -} - -void LLGroupMgrGroupData::createBanEntry(const LLUUID& ban_id, const LLGroupBanData& ban_data) -{ - mBanList[ban_id] = ban_data; -} - -void LLGroupMgrGroupData::removeBanEntry(const LLUUID& ban_id) -{ - mBanList.erase(ban_id); -} - -void LLGroupMgrGroupData::banMemberById(const LLUUID& participant_uuid) -{ - if (!mMemberDataComplete || - !mRoleDataComplete || - !(mRoleMemberDataComplete && mMembers.size())) - { - LL_WARNS() << "No Role-Member data yet, setting ban request to pending." << LL_ENDL; - mPendingBanRequest = true; - mPendingBanMemberID = participant_uuid; - - if (!mMemberDataComplete || !mMembers.size()) - { - LLGroupMgr::getInstance()->sendCapGroupMembersRequest(mID); - } - - if (!mRoleDataComplete) - { - LLGroupMgr::getInstance()->sendGroupRoleDataRequest(mID); - } - - return; - } - - LLGroupMgrGroupData::member_list_t::iterator mi = mMembers.find((participant_uuid)); - if (mi == mMembers.end()) - { - if (!mPendingBanRequest) - { - mPendingBanRequest = true; - mPendingBanMemberID = participant_uuid; - LLGroupMgr::getInstance()->sendCapGroupMembersRequest(mID); // member isn't in members list, request reloading - } - else - { - mPendingBanRequest = false; - } - - return; - } - - mPendingBanRequest = false; - - LLGroupMemberData* member_data = (*mi).second; - if (member_data && member_data->isInRole(mOwnerRole)) - { - return; // can't ban group owner - } - - std::vector ids; - ids.push_back(participant_uuid); - - LLGroupBanData ban_data; - createBanEntry(participant_uuid, ban_data); - LLGroupMgr::getInstance()->sendGroupBanRequest(LLGroupMgr::REQUEST_POST, mID, LLGroupMgr::BAN_CREATE, ids); - LLGroupMgr::getInstance()->sendGroupMemberEjects(mID, ids); - LLGroupMgr::getInstance()->sendGroupMembersRequest(mID); - LLSD args; - LLAvatarName av_name; - LLAvatarNameCache::get(participant_uuid, &av_name); - args["AVATAR_NAME"] = av_name.getUserName(); - args["GROUP_NAME"] = mName; - LLNotifications::instance().add(LLNotification::Params("EjectAvatarFromGroup").substitutions(args)); -} - -// -// LLGroupMgr -// - -LLGroupMgr::LLGroupMgr(): - mMemberRequestInFlight(false) -{ -} - -LLGroupMgr::~LLGroupMgr() -{ - clearGroups(); -} - -void LLGroupMgr::clearGroups() -{ - std::for_each(mRoleActionSets.begin(), mRoleActionSets.end(), DeletePointer()); - mRoleActionSets.clear(); - std::for_each(mGroups.begin(), mGroups.end(), DeletePairedPointer()); - mGroups.clear(); - mObservers.clear(); -} - -void LLGroupMgr::clearGroupData(const LLUUID& group_id) -{ - group_map_t::iterator iter = mGroups.find(group_id); - if (iter != mGroups.end()) - { - delete (*iter).second; - mGroups.erase(iter); - } -} - -void LLGroupMgr::addObserver(LLGroupMgrObserver* observer) -{ - if( observer->getID() != LLUUID::null ) - mObservers.insert(std::pair(observer->getID(), observer)); -} - -void LLGroupMgr::addObserver(const LLUUID& group_id, LLParticularGroupObserver* observer) -{ - if(group_id.notNull() && observer) - { - mParticularObservers[group_id].insert(observer); - } -} - -void LLGroupMgr::removeObserver(LLGroupMgrObserver* observer) -{ - if (!observer) - { - return; - } - observer_multimap_t::iterator it; - it = mObservers.find(observer->getID()); - while (it != mObservers.end()) - { - if (it->second == observer) - { - mObservers.erase(it); - break; - } - else - { - ++it; - } - } -} - -void LLGroupMgr::removeObserver(const LLUUID& group_id, LLParticularGroupObserver* observer) -{ - if(group_id.isNull() || !observer) - { - return; - } - - observer_map_t::iterator obs_it = mParticularObservers.find(group_id); - if(obs_it == mParticularObservers.end()) - return; - - obs_it->second.erase(observer); - - if (obs_it->second.size() == 0) - mParticularObservers.erase(obs_it); -} - -LLGroupMgrGroupData* LLGroupMgr::getGroupData(const LLUUID& id) -{ - group_map_t::iterator gi = mGroups.find(id); - - if (gi != mGroups.end()) - { - return gi->second; - } - return NULL; -} - -// Helper function for LLGroupMgr::processGroupMembersReply -// This reformats date strings from MM/DD/YYYY to YYYY/MM/DD ( e.g. 1/27/2008 -> 2008/1/27 ) -// so that the sorter can sort by year before month before day. -static void formatDateString(std::string &date_string) -{ - using namespace boost; - cmatch result; - const regex expression("([0-9]{1,2})/([0-9]{1,2})/([0-9]{4})"); - if (regex_match(date_string.c_str(), result, expression)) - { - // convert matches to integers so that we can pad them with zeroes on Linux - S32 year = boost::lexical_cast(result[3]); - S32 month = boost::lexical_cast(result[1]); - S32 day = boost::lexical_cast(result[2]); - - // ISO 8601 date format - date_string = llformat("%04d/%02d/%02d", year, month, day); - } -} - -// static -void LLGroupMgr::processGroupMembersReply(LLMessageSystem* msg, void** data) -{ - LL_PROFILE_ZONE_SCOPED; - - LL_DEBUGS("GrpMgr") << "LLGroupMgr::processGroupMembersReply" << LL_ENDL; - LLUUID agent_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); - if (gAgent.getID() != agent_id) - { - LL_WARNS() << "Got group members reply for another agent!" << LL_ENDL; - return; - } - - LLUUID group_id; - msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupID, group_id ); - - LLUUID request_id; - msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_RequestID, request_id); - - LLGroupMgrGroupData* group_datap = LLGroupMgr::getInstance()->getGroupData(group_id); - if (!group_datap || (group_datap->mMemberRequestID != request_id)) - { - LL_WARNS() << "processGroupMembersReply: Received incorrect (stale?) group or request id" << LL_ENDL; - return; - } - - msg->getS32(_PREHASH_GroupData, "MemberCount", group_datap->mMemberCount ); - - if (group_datap->mMemberCount > 0) - { - S32 contribution = 0; - std::string online_status; - std::string title; - U64 agent_powers = 0; - bool is_owner = false; - - S32 num_members = msg->getNumberOfBlocksFast(_PREHASH_MemberData); - for (S32 i = 0; i < num_members; i++) - { - LLUUID member_id; - - msg->getUUIDFast(_PREHASH_MemberData, _PREHASH_AgentID, member_id, i ); - msg->getS32(_PREHASH_MemberData, _PREHASH_Contribution, contribution, i); - msg->getU64(_PREHASH_MemberData, "AgentPowers", agent_powers, i); - msg->getStringFast(_PREHASH_MemberData, _PREHASH_OnlineStatus, online_status, i); - msg->getString(_PREHASH_MemberData, "Title", title, i); - msg->getBOOL(_PREHASH_MemberData,"IsOwner",is_owner,i); - - if (member_id.notNull()) - { - if (online_status == "Online") - { - static std::string localized_online(LLTrans::getString("group_member_status_online")); - online_status = localized_online; - } - else - { - formatDateString(online_status); // reformat for sorting, e.g. 12/25/2008 -> 2008/12/25 - } - - //LL_INFOS() << "Member " << member_id << " has powers " << std::hex << agent_powers << std::dec << LL_ENDL; - LLGroupMemberData* newdata = new LLGroupMemberData(member_id, - contribution, - agent_powers, - title, - online_status, - is_owner); -#if LL_DEBUG - LLGroupMgrGroupData::member_list_t::iterator mit = group_datap->mMembers.find(member_id); - if (mit != group_datap->mMembers.end()) - { - LL_INFOS() << " *** Received duplicate member data for agent " << member_id << LL_ENDL; - } -#endif - group_datap->mMembers[member_id] = newdata; - } - else - { - LL_INFOS() << "Received null group member data." << LL_ENDL; - } - } - - //if group members are loaded while titles are missing, load the titles. - if(group_datap->mTitles.size() < 1) - { - LLGroupMgr::getInstance()->sendGroupTitlesRequest(group_id); - } - } - - group_datap->mMemberVersion.generate(); - - if (group_datap->mMembers.size() == (U32)group_datap->mMemberCount) - { - group_datap->mMemberDataComplete = true; - group_datap->mMemberRequestID.setNull(); - // We don't want to make role-member data requests until we have all the members - if (group_datap->mPendingRoleMemberRequest) - { - group_datap->mPendingRoleMemberRequest = false; - LLGroupMgr::getInstance()->sendGroupRoleMembersRequest(group_datap->mID); - } - } - - group_datap->mChanged = true; - LLGroupMgr::getInstance()->notifyObservers(GC_MEMBER_DATA); -} - -//static -void LLGroupMgr::processGroupPropertiesReply(LLMessageSystem* msg, void** data) -{ - LL_PROFILE_ZONE_SCOPED; - - LL_DEBUGS("GrpMgr") << "LLGroupMgr::processGroupPropertiesReply" << LL_ENDL; - if (!msg) - { - LL_ERRS() << "Can't access the messaging system" << LL_ENDL; - return; - } - LLUUID agent_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); - if (gAgent.getID() != agent_id) - { - LL_WARNS() << "Got group properties reply for another agent!" << LL_ENDL; - return; - } - - LLUUID group_id; - std::string name; - std::string charter; - bool show_in_list = false; - LLUUID founder_id; - U64 powers_mask = GP_NO_POWERS; - S32 money = 0; - std::string member_title; - LLUUID insignia_id; - LLUUID owner_role; - U32 membership_fee = 0; - bool open_enrollment = false; - S32 num_group_members = 0; - S32 num_group_roles = 0; - bool allow_publish = false; - bool mature = false; - - msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupID, group_id ); - msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_FounderID, founder_id); - msg->getStringFast(_PREHASH_GroupData, _PREHASH_Name, name ); - msg->getStringFast(_PREHASH_GroupData, _PREHASH_Charter, charter ); - msg->getBOOLFast(_PREHASH_GroupData, _PREHASH_ShowInList, show_in_list ); - msg->getStringFast(_PREHASH_GroupData, _PREHASH_MemberTitle, member_title ); - msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_InsigniaID, insignia_id ); - msg->getU64Fast(_PREHASH_GroupData, _PREHASH_PowersMask, powers_mask ); - msg->getU32Fast(_PREHASH_GroupData, _PREHASH_MembershipFee, membership_fee ); - msg->getBOOLFast(_PREHASH_GroupData, _PREHASH_OpenEnrollment, open_enrollment ); - msg->getS32Fast(_PREHASH_GroupData, _PREHASH_GroupMembershipCount, num_group_members); - msg->getS32(_PREHASH_GroupData, "GroupRolesCount", num_group_roles); - msg->getS32Fast(_PREHASH_GroupData, _PREHASH_Money, money); - msg->getBOOL("GroupData", "AllowPublish", allow_publish); - msg->getBOOL("GroupData", "MaturePublish", mature); - msg->getUUID(_PREHASH_GroupData, "OwnerRole", owner_role); - - LLGroupMgrGroupData* group_datap = LLGroupMgr::getInstance()->createGroupData(group_id); - - group_datap->mName = name; - group_datap->mCharter = charter; - group_datap->mShowInList = show_in_list; - group_datap->mInsigniaID = insignia_id; - group_datap->mFounderID = founder_id; - group_datap->mMembershipFee = membership_fee; - group_datap->mOpenEnrollment = open_enrollment; - group_datap->mAllowPublish = allow_publish; - group_datap->mMaturePublish = mature; - group_datap->mOwnerRole = owner_role; - group_datap->mMemberCount = num_group_members; - group_datap->mRoleCount = num_group_roles + 1; // Add the everyone role. - - group_datap->mGroupPropertiesDataComplete = true; - group_datap->mChanged = true; - - properties_request_map_t::iterator request = LLGroupMgr::getInstance()->mPropRequests.find(group_id); - if (request != LLGroupMgr::getInstance()->mPropRequests.end()) - { - LLGroupMgr::getInstance()->mPropRequests.erase(request); - } - else - { - LL_DEBUGS("GrpMgr") << "GroupPropertyResponse received with no pending request. Response was slow." << LL_ENDL; - } - LLGroupMgr::getInstance()->notifyObservers(GC_PROPERTIES); -} - -// static -void LLGroupMgr::processGroupRoleDataReply(LLMessageSystem* msg, void** data) -{ - LL_PROFILE_ZONE_SCOPED; - - LL_DEBUGS("GrpMgr") << "LLGroupMgr::processGroupRoleDataReply" << LL_ENDL; - LLUUID agent_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); - if (gAgent.getID() != agent_id) - { - LL_WARNS() << "Got group role data reply for another agent!" << LL_ENDL; - return; - } - - LLUUID group_id; - msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupID, group_id ); - - LLUUID request_id; - msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_RequestID, request_id); - - LLGroupMgrGroupData* group_datap = LLGroupMgr::getInstance()->getGroupData(group_id); - if (!group_datap || (group_datap->mRoleDataRequestID != request_id)) - { - LL_WARNS() << "processGroupPropertiesReply: Received incorrect (stale?) group or request id" << LL_ENDL; - return; - } - - msg->getS32(_PREHASH_GroupData, "RoleCount", group_datap->mRoleCount ); - - std::string name; - std::string title; - std::string desc; - U64 powers = 0; - U32 member_count = 0; - LLUUID role_id; - - U32 num_blocks = msg->getNumberOfBlocks("RoleData"); - U32 i = 0; - for (i=0; i< num_blocks; ++i) - { - msg->getUUID("RoleData", "RoleID", role_id, i ); - - msg->getString("RoleData","Name",name,i); - msg->getString("RoleData","Title",title,i); - msg->getString("RoleData","Description",desc,i); - msg->getU64("RoleData","Powers",powers,i); - msg->getU32("RoleData","Members",member_count,i); - - //there are 3 predifined roles - Owners, Officers, Everyone - //there names are defined in lldatagroups.cpp - //lets change names from server to localized strings - if(name == "Everyone") - { - name = LLTrans::getString("group_role_everyone"); - } - else if(name == "Officers") - { - name = LLTrans::getString("group_role_officers"); - } - else if(name == "Owners") - { - name = LLTrans::getString("group_role_owners"); - } - - - - LL_DEBUGS("GrpMgr") << "Adding role data: " << name << " {" << role_id << "}" << LL_ENDL; - LLGroupRoleData* rd = new LLGroupRoleData(role_id,name,title,desc,powers,member_count); - group_datap->mRoles[role_id] = rd; - } - - if (group_datap->mRoles.size() == (U32)group_datap->mRoleCount) - { - group_datap->mRoleDataComplete = true; - group_datap->mRoleDataRequestID.setNull(); - // We don't want to make role-member data requests until we have all the role data - if (group_datap->mPendingRoleMemberRequest) - { - group_datap->mPendingRoleMemberRequest = false; - LLGroupMgr::getInstance()->sendGroupRoleMembersRequest(group_datap->mID); - } - } - - group_datap->mChanged = true; - LLGroupMgr::getInstance()->notifyObservers(GC_ROLE_DATA); -} - -// static -void LLGroupMgr::processGroupRoleMembersReply(LLMessageSystem* msg, void** data) -{ - LL_PROFILE_ZONE_SCOPED; - - LL_DEBUGS("GrpMgr") << "LLGroupMgr::processGroupRoleMembersReply" << LL_ENDL; - LLUUID agent_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); - if (gAgent.getID() != agent_id) - { - LL_WARNS() << "Got group role members reply for another agent!" << LL_ENDL; - return; - } - - LLUUID request_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_RequestID, request_id); - - LLUUID group_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_GroupID, group_id ); - - U32 total_pairs; - msg->getU32(_PREHASH_AgentData, "TotalPairs", total_pairs); - - LLGroupMgrGroupData* group_datap = LLGroupMgr::getInstance()->getGroupData(group_id); - if (!group_datap || (group_datap->mRoleMembersRequestID != request_id)) - { - LL_WARNS() << "processGroupRoleMembersReply: Received incorrect (stale?) group or request id" << LL_ENDL; - return; - } - - U32 num_blocks = msg->getNumberOfBlocks("MemberData"); - U32 i; - LLUUID member_id; - LLUUID role_id; - LLGroupRoleData* rd = NULL; - LLGroupMemberData* md = NULL; - - LLGroupMgrGroupData::role_list_t::iterator ri; - LLGroupMgrGroupData::member_list_t::iterator mi; - - // If total_pairs == 0, there are no members in any custom roles. - if (total_pairs > 0) - { - for (i = 0;i < num_blocks; ++i) - { - msg->getUUID("MemberData","RoleID",role_id,i); - msg->getUUID("MemberData","MemberID",member_id,i); - - if (role_id.notNull() && member_id.notNull() ) - { - rd = NULL; - ri = group_datap->mRoles.find(role_id); - if (ri != group_datap->mRoles.end()) - { - rd = ri->second; - } - - md = NULL; - mi = group_datap->mMembers.find(member_id); - if (mi != group_datap->mMembers.end()) - { - md = mi->second; - } - - if (rd && md) - { - LL_DEBUGS("GrpMgr") << "Adding role-member pair: " << role_id << ", " << member_id << LL_ENDL; - rd->addMember(member_id); - md->addRole(role_id,rd); - } - else - { - if (!rd) LL_WARNS() << "Received role data for unknown role " << role_id << " in group " << group_id << LL_ENDL; - if (!md) LL_WARNS() << "Received role data for unknown member " << member_id << " in group " << group_id << LL_ENDL; - } - } - } - - group_datap->mReceivedRoleMemberPairs += num_blocks; - } - - if (group_datap->mReceivedRoleMemberPairs == total_pairs) - { - // Add role data for the 'everyone' role to all members - LLGroupRoleData* everyone = group_datap->mRoles[LLUUID::null]; - if (!everyone) - { - LL_WARNS() << "Everyone role not found!" << LL_ENDL; - } - else - { - for (LLGroupMgrGroupData::member_list_t::iterator mi = group_datap->mMembers.begin(); - mi != group_datap->mMembers.end(); ++mi) - { - LLGroupMemberData* data = mi->second; - if (data) - { - data->addRole(LLUUID::null,everyone); - } - } - } - - group_datap->mRoleMemberDataComplete= true; - group_datap->mRoleMembersRequestID.setNull(); - } - - group_datap->mChanged = true; - LLGroupMgr::getInstance()->notifyObservers(GC_ROLE_MEMBER_DATA); - - if (group_datap->mPendingBanRequest) - { - group_datap->banMemberById(group_datap->mPendingBanMemberID); - } -} - -// static -void LLGroupMgr::processGroupTitlesReply(LLMessageSystem* msg, void** data) -{ - LL_DEBUGS("GrpMgr") << "LLGroupMgr::processGroupTitlesReply" << LL_ENDL; - LLUUID agent_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); - if (gAgent.getID() != agent_id) - { - LL_WARNS() << "Got group properties reply for another agent!" << LL_ENDL; - return; - } - - LLUUID group_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_GroupID, group_id ); - LLUUID request_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_RequestID, request_id); - - LLGroupMgrGroupData* group_datap = LLGroupMgr::getInstance()->getGroupData(group_id); - if (!group_datap || (group_datap->mTitlesRequestID != request_id)) - { - LL_WARNS() << "processGroupTitlesReply: Received incorrect (stale?) group" << LL_ENDL; - return; - } - - LLGroupTitle title; - - S32 i = 0; - S32 blocks = msg->getNumberOfBlocksFast(_PREHASH_GroupData); - for (i=0; igetString("GroupData","Title",title.mTitle,i); - msg->getUUID("GroupData","RoleID",title.mRoleID,i); - msg->getBOOL("GroupData","Selected",title.mSelected,i); - - if (!title.mTitle.empty()) - { - LL_DEBUGS("GrpMgr") << "LLGroupMgr adding title: " << title.mTitle << ", " << title.mRoleID << ", " << (title.mSelected ? 'Y' : 'N') << LL_ENDL; - group_datap->mTitles.push_back(title); - } - } - - group_datap->mChanged = true; - LLGroupMgr::getInstance()->notifyObservers(GC_TITLES); -} - -// static -void LLGroupMgr::processEjectGroupMemberReply(LLMessageSystem* msg, void ** data) -{ - LL_DEBUGS("GrpMgr") << "processEjectGroupMemberReply" << LL_ENDL; - LLUUID group_id; - msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupID, group_id); - bool success; - msg->getBOOLFast(_PREHASH_EjectData, _PREHASH_Success, success); - - // If we had a failure, the group panel needs to be updated. - if (!success) - { - LLGroupActions::refresh(group_id); - } -} - -// static -void LLGroupMgr::processJoinGroupReply(LLMessageSystem* msg, void ** data) -{ - LL_DEBUGS("GrpMgr") << "processJoinGroupReply" << LL_ENDL; - LLUUID group_id; - bool success; - msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupID, group_id); - msg->getBOOLFast(_PREHASH_GroupData, _PREHASH_Success, success); - - if (success) - { - // refresh all group information - gAgent.sendAgentDataUpdateRequest(); - - LLGroupMgr::getInstance()->clearGroupData(group_id); - // refresh the floater for this group, if any. - LLGroupActions::refresh(group_id); - } -} - -// static -void LLGroupMgr::processLeaveGroupReply(LLMessageSystem* msg, void ** data) -{ - LL_DEBUGS("GrpMgr") << "processLeaveGroupReply" << LL_ENDL; - LLUUID group_id; - bool success; - msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupID, group_id); - msg->getBOOLFast(_PREHASH_GroupData, _PREHASH_Success, success); - - if (success) - { - // refresh all group information - gAgent.sendAgentDataUpdateRequest(); - - LLGroupMgr::getInstance()->clearGroupData(group_id); - // close the floater for this group, if any. - LLGroupActions::closeGroup(group_id); - } -} - -// static -void LLGroupMgr::processCreateGroupReply(LLMessageSystem* msg, void ** data) -{ - LLUUID group_id; - bool success; - std::string message; - - msg->getUUIDFast(_PREHASH_ReplyData, _PREHASH_GroupID, group_id ); - - msg->getBOOLFast(_PREHASH_ReplyData, _PREHASH_Success, success ); - msg->getStringFast(_PREHASH_ReplyData, _PREHASH_Message, message ); - - if (success) - { - // refresh all group information - gAgent.sendAgentDataUpdateRequest(); - - // HACK! We haven't gotten the agent group update yet, so ... um ... fake it. - // This is so when we go to modify the group we will be able to do so. - // This isn't actually too bad because real data will come down in 2 or 3 miliseconds and replace this. - LLGroupData gd; - gd.mAcceptNotices = true; - gd.mListInProfile = true; - gd.mContribution = 0; - gd.mID = group_id; - gd.mName = "new group"; - gd.mPowers = GP_ALL_POWERS; - - gAgent.mGroups.push_back(gd); - - LLPanelGroupCreate::refreshCreatedGroup(group_id); - //FIXME - //LLFloaterGroupInfo::closeCreateGroup(); - //LLFloaterGroupInfo::showFromUUID(group_id,"roles_tab"); - } - else - { - // *TODO: Translate - LLSD args; - args["MESSAGE"] = message; - LLNotificationsUtil::add("UnableToCreateGroup", args); - } -} - -LLGroupMgrGroupData* LLGroupMgr::createGroupData(const LLUUID& id) -{ - LLGroupMgrGroupData* group_datap = NULL; - - group_map_t::iterator existing_group = LLGroupMgr::getInstance()->mGroups.find(id); - if (existing_group == LLGroupMgr::getInstance()->mGroups.end()) - { - group_datap = new LLGroupMgrGroupData(id); - LLGroupMgr::getInstance()->addGroup(group_datap); - } - else - { - group_datap = existing_group->second; - } - - if (group_datap) - { - group_datap->setAccessed(); - } - - return group_datap; -} - -bool LLGroupMgr::hasPendingPropertyRequest(const LLUUID & id) -{ - properties_request_map_t::iterator existing_req = LLGroupMgr::getInstance()->mPropRequests.find(id); - if (existing_req != LLGroupMgr::getInstance()->mPropRequests.end()) - { - if (gFrameTime - existing_req->second < MIN_GROUP_PROPERTY_REQUEST_FREQ) - { - return true; - } - else - { - LLGroupMgr::getInstance()->mPropRequests.erase(existing_req); - } - } - return false; -} - -void LLGroupMgr::addPendingPropertyRequest(const LLUUID& id) -{ - LLGroupMgr::getInstance()->mPropRequests[id] = gFrameTime; -} - -void LLGroupMgr::notifyObservers(LLGroupChange gc) -{ - for (group_map_t::iterator gi = mGroups.begin(); gi != mGroups.end(); ++gi) - { - LLUUID group_id = gi->first; - if (gi->second->mChanged) - { - // notify LLGroupMgrObserver - // Copy the map because observers may remove themselves on update - observer_multimap_t observers = mObservers; - - // find all observers for this group id - observer_multimap_t::iterator oi = observers.lower_bound(group_id); - observer_multimap_t::iterator end = observers.upper_bound(group_id); - for (; oi != end; ++oi) - { - oi->second->changed(gc); - } - gi->second->mChanged = false; - - - // notify LLParticularGroupObserver - observer_map_t::iterator obs_it = mParticularObservers.find(group_id); - if(obs_it == mParticularObservers.end()) - return; - - observer_set_t& obs = obs_it->second; - for (observer_set_t::iterator ob_it = obs.begin(); ob_it != obs.end(); ++ob_it) - { - (*ob_it)->changed(group_id, gc); - } - } - } -} - -void LLGroupMgr::addGroup(LLGroupMgrGroupData* group_datap) -{ - while (mGroups.size() >= MAX_CACHED_GROUPS) - { - // LRU: Remove the oldest un-observed group from cache until group size is small enough - - F32 oldest_access = LLFrameTimer::getTotalSeconds(); - group_map_t::iterator oldest_gi = mGroups.end(); - - for (group_map_t::iterator gi = mGroups.begin(); gi != mGroups.end(); ++gi ) - { - observer_multimap_t::iterator oi = mObservers.find(gi->first); - if (oi == mObservers.end()) - { - if (gi->second - && (gi->second->getAccessTime() < oldest_access)) - { - oldest_access = gi->second->getAccessTime(); - oldest_gi = gi; - } - } - } - - if (oldest_gi != mGroups.end()) - { - delete oldest_gi->second; - mGroups.erase(oldest_gi); - } - else - { - // All groups must be currently open, none to remove. - // Just add the new group anyway, but get out of this loop as it - // will never drop below max_cached_groups. - break; - } - } - - mGroups[group_datap->getID()] = group_datap; -} - - -void LLGroupMgr::sendGroupPropertiesRequest(const LLUUID& group_id) -{ - LL_DEBUGS("GrpMgr") << "LLGroupMgr::sendGroupPropertiesRequest" << LL_ENDL; - // This will happen when we get the reply - //LLGroupMgrGroupData* group_datap = createGroupData(group_id); - - if (LLGroupMgr::getInstance()->hasPendingPropertyRequest(group_id)) - { - LL_DEBUGS("GrpMgr") << "LLGroupMgr::sendGroupPropertiesRequest suppressed repeat for " << group_id << LL_ENDL; - return; - } - LLGroupMgr::getInstance()->addPendingPropertyRequest(group_id); - - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("GroupProfileRequest"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID",gAgent.getID()); - msg->addUUID("SessionID",gAgent.getSessionID()); - msg->nextBlock("GroupData"); - msg->addUUID("GroupID",group_id); - gAgent.sendReliableMessage(); -} - -void LLGroupMgr::sendGroupMembersRequest(const LLUUID& group_id) -{ - LL_DEBUGS("GrpMgr") << "LLGroupMgr::sendGroupMembersRequest" << LL_ENDL; - LLGroupMgrGroupData* group_datap = createGroupData(group_id); - if (group_datap->mMemberRequestID.isNull()) - { - group_datap->removeMemberData(); - group_datap->mMemberRequestID.generate(); - - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("GroupMembersRequest"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID",gAgent.getID()); - msg->addUUID("SessionID",gAgent.getSessionID()); - msg->nextBlock("GroupData"); - msg->addUUID("GroupID",group_id); - msg->addUUID("RequestID",group_datap->mMemberRequestID); - gAgent.sendReliableMessage(); - } -} - - -void LLGroupMgr::sendGroupRoleDataRequest(const LLUUID& group_id) -{ - LL_DEBUGS("GrpMgr") << "LLGroupMgr::sendGroupRoleDataRequest" << LL_ENDL; - LLGroupMgrGroupData* group_datap = createGroupData(group_id); - if (group_datap->mRoleDataRequestID.isNull()) - { - group_datap->removeRoleData(); - group_datap->mRoleDataRequestID.generate(); - - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("GroupRoleDataRequest"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID",gAgent.getID()); - msg->addUUID("SessionID",gAgent.getSessionID()); - msg->nextBlock("GroupData"); - msg->addUUID("GroupID",group_id); - msg->addUUID("RequestID",group_datap->mRoleDataRequestID); - gAgent.sendReliableMessage(); - } -} - -void LLGroupMgr::sendGroupRoleMembersRequest(const LLUUID& group_id) -{ - LL_DEBUGS("GrpMgr") << "LLGroupMgr::sendGroupRoleMembersRequest" << LL_ENDL; - LLGroupMgrGroupData* group_datap = createGroupData(group_id); - - if (group_datap->mRoleMembersRequestID.isNull()) - { - // Don't send the request if we don't have all the member or role data - if (!group_datap->isMemberDataComplete() - || !group_datap->isRoleDataComplete()) - { - // *TODO: KLW FIXME: Should we start a member or role data request? - LL_INFOS("GrpMgr") << " Pending: " << (group_datap->mPendingRoleMemberRequest ? "Y" : "N") - << " MemberDataComplete: " << (group_datap->mMemberDataComplete ? "Y" : "N") - << " RoleDataComplete: " << (group_datap->mRoleDataComplete ? "Y" : "N") << LL_ENDL; - group_datap->mPendingRoleMemberRequest = true; - return; - } - - group_datap->removeRoleMemberData(); - group_datap->mRoleMembersRequestID.generate(); - - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("GroupRoleMembersRequest"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID",gAgent.getID()); - msg->addUUID("SessionID",gAgent.getSessionID()); - msg->nextBlock("GroupData"); - msg->addUUID("GroupID",group_id); - msg->addUUID("RequestID",group_datap->mRoleMembersRequestID); - gAgent.sendReliableMessage(); - } -} - -void LLGroupMgr::sendGroupTitlesRequest(const LLUUID& group_id) -{ - LL_DEBUGS("GrpMgr") << "LLGroupMgr::sendGroupTitlesRequest" << LL_ENDL; - LLGroupMgrGroupData* group_datap = createGroupData(group_id); - - group_datap->mTitles.clear(); - group_datap->mTitlesRequestID.generate(); - - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("GroupTitlesRequest"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID",gAgent.getID()); - msg->addUUID("SessionID",gAgent.getSessionID()); - msg->addUUID("GroupID",group_id); - msg->addUUID("RequestID",group_datap->mTitlesRequestID); - - gAgent.sendReliableMessage(); -} - -void LLGroupMgr::sendGroupTitleUpdate(const LLUUID& group_id, const LLUUID& title_role_id) -{ - LL_DEBUGS("GrpMgr") << "LLGroupMgr::sendGroupTitleUpdate" << LL_ENDL; - - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("GroupTitleUpdate"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID",gAgent.getID()); - msg->addUUID("SessionID",gAgent.getSessionID()); - msg->addUUID("GroupID",group_id); - msg->addUUID("TitleRoleID",title_role_id); - - gAgent.sendReliableMessage(); - - // Save the change locally - LLGroupMgrGroupData* group_datap = createGroupData(group_id); - for (std::vector::iterator iter = group_datap->mTitles.begin(); - iter != group_datap->mTitles.end(); ++iter) - { - if (iter->mRoleID == title_role_id) - { - iter->mSelected = true; - } - else if (iter->mSelected) - { - iter->mSelected = false; - } - } -} - -// static -void LLGroupMgr::sendCreateGroupRequest(const std::string& name, - const std::string& charter, - U8 show_in_list, - const LLUUID& insignia, - S32 membership_fee, - bool open_enrollment, - bool allow_publish, - bool mature_publish) -{ - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("CreateGroupRequest"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID",gAgent.getID()); - msg->addUUID("SessionID",gAgent.getSessionID()); - - msg->nextBlock("GroupData"); - msg->addString("Name",name); - msg->addString("Charter",charter); - msg->addBOOL("ShowInList",show_in_list); - msg->addUUID("InsigniaID",insignia); - msg->addS32("MembershipFee",membership_fee); - msg->addBOOL("OpenEnrollment",open_enrollment); - msg->addBOOL("AllowPublish",allow_publish); - msg->addBOOL("MaturePublish",mature_publish); - - gAgent.sendReliableMessage(); -} - -void LLGroupMgr::sendUpdateGroupInfo(const LLUUID& group_id) -{ - LL_DEBUGS("GrpMgr") << "LLGroupMgr::sendUpdateGroupInfo" << LL_ENDL; - LLGroupMgrGroupData* group_datap = createGroupData(group_id); - - LLMessageSystem* msg = gMessageSystem; - - msg->newMessageFast(_PREHASH_UpdateGroupInfo); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID,gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); - - msg->nextBlockFast(_PREHASH_GroupData); - msg->addUUIDFast(_PREHASH_GroupID,group_datap->getID()); - msg->addStringFast(_PREHASH_Charter,group_datap->mCharter); - msg->addBOOLFast(_PREHASH_ShowInList,group_datap->mShowInList); - msg->addUUIDFast(_PREHASH_InsigniaID,group_datap->mInsigniaID); - msg->addS32Fast(_PREHASH_MembershipFee,group_datap->mMembershipFee); - msg->addBOOLFast(_PREHASH_OpenEnrollment,group_datap->mOpenEnrollment); - msg->addBOOLFast(_PREHASH_AllowPublish,group_datap->mAllowPublish); - msg->addBOOLFast(_PREHASH_MaturePublish,group_datap->mMaturePublish); - - gAgent.sendReliableMessage(); - - // Not expecting a response, so let anyone else watching know the data has changed. - group_datap->mChanged = true; - notifyObservers(GC_PROPERTIES); -} - -void LLGroupMgr::sendGroupRoleMemberChanges(const LLUUID& group_id) -{ - LL_DEBUGS("GrpMgr") << "LLGroupMgr::sendGroupRoleMemberChanges" << LL_ENDL; - LLGroupMgrGroupData* group_datap = createGroupData(group_id); - - if (group_datap->mRoleMemberChanges.empty()) return; - - LLMessageSystem* msg = gMessageSystem; - - bool start_message = true; - for (LLGroupMgrGroupData::change_map_t::const_iterator citer = group_datap->mRoleMemberChanges.begin(); - citer != group_datap->mRoleMemberChanges.end(); ++citer) - { - if (start_message) - { - msg->newMessage("GroupRoleChanges"); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID,gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); - msg->addUUIDFast(_PREHASH_GroupID,group_id); - start_message = false; - } - msg->nextBlock("RoleChange"); - msg->addUUID("RoleID",citer->second.mRole); - msg->addUUID("MemberID",citer->second.mMember); - msg->addU32("Change",(U32)citer->second.mChange); - - if (msg->isSendFullFast()) - { - gAgent.sendReliableMessage(); - start_message = true; - } - } - - if (!start_message) - { - gAgent.sendReliableMessage(); - } - - group_datap->mRoleMemberChanges.clear(); - - // Not expecting a response, so let anyone else watching know the data has changed. - group_datap->mChanged = true; - notifyObservers(GC_ROLE_MEMBER_DATA); -} - -//static -void LLGroupMgr::sendGroupMemberJoin(const LLUUID& group_id) -{ - - LLUIUsage::instance().logCommand("Group.Join"); - - LLMessageSystem *msg = gMessageSystem; - - msg->newMessageFast(_PREHASH_JoinGroupRequest); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_GroupData); - msg->addUUIDFast(_PREHASH_GroupID, group_id); - - gAgent.sendReliableMessage(); -} - -// member_role_pairs is -// static -void LLGroupMgr::sendGroupMemberInvites(const LLUUID& group_id, std::map& member_role_pairs) -{ - bool start_message = true; - LLMessageSystem* msg = gMessageSystem; - - for (std::map::iterator it = member_role_pairs.begin(); - it != member_role_pairs.end(); ++it) - { - if (start_message) - { - msg->newMessage("InviteGroupRequest"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID",gAgent.getID()); - msg->addUUID("SessionID",gAgent.getSessionID()); - msg->nextBlock("GroupData"); - msg->addUUID("GroupID",group_id); - start_message = false; - } - - msg->nextBlock("InviteData"); - msg->addUUID("InviteeID",(*it).first); - msg->addUUID("RoleID",(*it).second); - - if (msg->isSendFull()) - { - gAgent.sendReliableMessage(); - start_message = true; - } - } - - if (!start_message) - { - gAgent.sendReliableMessage(); - } -} - -//static -void LLGroupMgr::sendGroupMemberEjects(const LLUUID& group_id, - uuid_vec_t& member_ids) -{ - bool start_message = true; - LLMessageSystem* msg = gMessageSystem; - - LLGroupMgrGroupData* group_datap = LLGroupMgr::getInstance()->getGroupData(group_id); - if (!group_datap) return; - - for (uuid_vec_t::iterator it = member_ids.begin(); - it != member_ids.end(); ++it) - { - LLUUID& ejected_member_id = (*it); - - // Can't use 'eject' to leave a group. - if (ejected_member_id == gAgent.getID()) continue; - - // Make sure they are in the group, and we need the member data - LLGroupMgrGroupData::member_list_t::iterator mit = group_datap->mMembers.find(ejected_member_id); - if (mit != group_datap->mMembers.end()) - { - // Add them to the message - if (start_message) - { - msg->newMessage("EjectGroupMemberRequest"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID",gAgent.getID()); - msg->addUUID("SessionID",gAgent.getSessionID()); - msg->nextBlock("GroupData"); - msg->addUUID("GroupID",group_id); - start_message = false; - } - - msg->nextBlock("EjectData"); - msg->addUUID("EjecteeID",ejected_member_id); - - if (msg->isSendFull()) - { - gAgent.sendReliableMessage(); - start_message = true; - } - - LLGroupMemberData* member_data = (*mit).second; - - // Clean up groupmgr - for (LLGroupMemberData::role_list_t::iterator rit = member_data->roleBegin(); - rit != member_data->roleEnd(); ++rit) - { - if ((*rit).first.notNull() && (*rit).second!=0) - { - (*rit).second->removeMember(ejected_member_id); - } - } - - group_datap->mMembers.erase(ejected_member_id); - - // member_data was introduced and is used here instead of (*mit).second to avoid crash because of invalid iterator - // It becomes invalid after line with erase above. EXT-4778 - delete member_data; - } - } - - if (!start_message) - { - gAgent.sendReliableMessage(); - } - - group_datap->mMemberVersion.generate(); -} - -void LLGroupMgr::getGroupBanRequestCoro(std::string url, LLUUID groupId) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("groupMembersRequest", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - std::string finalUrl = url + "?group_id=" + groupId.asString(); - - LLSD result = httpAdapter->getAndSuspend(httpRequest, finalUrl); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS("GrpMgr") << "Error receiving group member data " << LL_ENDL; - return; - } - - if (result.has("ban_list")) - { - result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); - // group ban data received - processGroupBanRequest(result); - } -} - -void LLGroupMgr::postGroupBanRequestCoro(std::string url, LLUUID groupId, - U32 action, uuid_vec_t banList, bool update) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("groupMembersRequest", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); - LLCore::HttpOptions::ptr_t httpOptions(new LLCore::HttpOptions); - - httpOptions->setFollowRedirects(false); - - httpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_LLSD_XML); - - - std::string finalUrl = url + "?group_id=" + groupId.asString(); - - LLSD postData = LLSD::emptyMap(); - postData["ban_action"] = (LLSD::Integer)action; - // Add our list of potential banned residents to the list - postData["ban_ids"] = LLSD::emptyArray(); - LLSD banEntry; - - uuid_vec_t::const_iterator it = banList.begin(); - for (; it != banList.end(); ++it) - { - banEntry = (*it); - postData["ban_ids"].append(banEntry); - } - - LL_WARNS() << "post: " << ll_pretty_print_sd(postData) << LL_ENDL; - - LLSD result = httpAdapter->postAndSuspend(httpRequest, finalUrl, postData, httpOptions, httpHeaders); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS("GrpMgr") << "Error posting group member data " << LL_ENDL; - return; - } - - if (result.has("ban_list")) - { - result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); - // group ban data received - processGroupBanRequest(result); - } - - if (update) - { - getGroupBanRequestCoro(url, groupId); - } -} - -void LLGroupMgr::sendGroupBanRequest( EBanRequestType request_type, - const LLUUID& group_id, - U32 ban_action, /* = BAN_NO_ACTION */ - const std::vector &ban_list) /* = std::vector() */ -{ - LLViewerRegion* currentRegion = gAgent.getRegion(); - if(!currentRegion) - { - LL_WARNS("GrpMgr") << "Agent does not have a current region. Uh-oh!" << LL_ENDL; - return; - } - - // Check to make sure we have our capabilities - if(!currentRegion->capabilitiesReceived()) - { - LL_WARNS("GrpMgr") << " Capabilities not received!" << LL_ENDL; - return; - } - - // Get our capability - std::string cap_url = currentRegion->getCapability("GroupAPIv1"); - if(cap_url.empty()) - { - return; - } - - U32 action = ban_action & ~BAN_UPDATE; - bool update = ((ban_action & BAN_UPDATE) == BAN_UPDATE); - - switch (request_type) - { - case REQUEST_GET: - LLCoros::instance().launch("LLGroupMgr::getGroupBanRequestCoro", - boost::bind(&LLGroupMgr::getGroupBanRequestCoro, this, cap_url, group_id)); - break; - case REQUEST_POST: - LLCoros::instance().launch("LLGroupMgr::postGroupBanRequestCoro", - boost::bind(&LLGroupMgr::postGroupBanRequestCoro, this, cap_url, group_id, - action, ban_list, update)); - break; - case REQUEST_PUT: - case REQUEST_DEL: - break; - } -} - -void LLGroupMgr::processGroupBanRequest(const LLSD& content) -{ - // Did we get anything in content? - if(!content.size()) - { - LL_WARNS("GrpMgr") << "No group member data received." << LL_ENDL; - return; - } - - LLUUID group_id = content["group_id"].asUUID(); - - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_id); - if (!gdatap) - return; - - gdatap->clearBanList(); - LLSD::map_const_iterator i = content["ban_list"].beginMap(); - LLSD::map_const_iterator iEnd = content["ban_list"].endMap(); - for(;i != iEnd; ++i) - { - const LLUUID ban_id(i->first); - LLSD ban_entry(i->second); - - LLGroupBanData ban_data; - if(ban_entry.has("ban_date")) - { - ban_data.mBanDate = ban_entry["ban_date"].asDate(); - // TODO: Ban Reason - } - - gdatap->createBanEntry(ban_id, ban_data); - } - - gdatap->mChanged = true; - LLGroupMgr::getInstance()->notifyObservers(GC_BANLIST); -} - -void LLGroupMgr::groupMembersRequestCoro(std::string url, LLUUID groupId) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("groupMembersRequest", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); - - mMemberRequestInFlight = true; - - LLSD postData = LLSD::emptyMap(); - postData["group_id"] = groupId; - - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData, httpOpts); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS("GrpMgr") << "Error receiving group member data " << LL_ENDL; - mMemberRequestInFlight = false; - return; - } - - result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); - LLGroupMgr::processCapGroupMembersRequest(result); - mMemberRequestInFlight = false; -} - -void LLGroupMgr::sendCapGroupMembersRequest(const LLUUID& group_id) -{ - static U32 lastGroupMemberRequestFrame = 0; - - // Have we requested the information already this frame? - // Todo: make this per group, we can invite to one group and simultaneously be checking another one - if ((lastGroupMemberRequestFrame == gFrameCount) || (mMemberRequestInFlight)) - return; - - LLViewerRegion* currentRegion = gAgent.getRegion(); - // Thank you FS:Ansariel! - if(!currentRegion) - { - LL_WARNS("GrpMgr") << "Agent does not have a current region. Uh-oh!" << LL_ENDL; - return; - } - - // Check to make sure we have our capabilities - if(!currentRegion->capabilitiesReceived()) - { - LL_WARNS("GrpMgr") << " Capabilities not received!" << LL_ENDL; - return; - } - - // Get our capability - std::string cap_url = currentRegion->getCapability("GroupMemberData"); - - // Thank you FS:Ansariel! - if(cap_url.empty()) - { - LL_INFOS("GrpMgr") << "Region has no GroupMemberData capability. Falling back to UDP fetch." << LL_ENDL; - sendGroupMembersRequest(group_id); - return; - } - - LLGroupMgrGroupData* group_datap = createGroupData(group_id); //make sure group exists - group_datap->mMemberRequestID.generate(); // mark as pending - - lastGroupMemberRequestFrame = gFrameCount; - - LLCoros::instance().launch("LLGroupMgr::groupMembersRequestCoro", - boost::bind(&LLGroupMgr::groupMembersRequestCoro, this, cap_url, group_id)); -} - - -void LLGroupMgr::processCapGroupMembersRequest(const LLSD& content) -{ - // Did we get anything in content? - if(!content.size()) - { - LL_DEBUGS("GrpMgr") << "No group member data received." << LL_ENDL; - return; - } - - LLUUID group_id = content["group_id"].asUUID(); - - LLGroupMgrGroupData* group_datap = getGroupData(group_id); - if(!group_datap) - { - LL_WARNS("GrpMgr") << "Received incorrect, possibly stale, group or request id" << LL_ENDL; - return; - } - - // If we have no members, there's no reason to do anything else - S32 num_members = content["member_count"]; - if (num_members < 1) - { - LL_INFOS("GrpMgr") << "Received empty group members list for group id: " << group_id.asString() << LL_ENDL; - // Set mMemberDataComplete for correct handling of empty responses. See MAINT-5237 - group_datap->mMemberDataComplete = true; - group_datap->mChanged = true; - LLGroupMgr::getInstance()->notifyObservers(GC_MEMBER_DATA); - return; - } - - group_datap->mMemberCount = num_members; - - LLSD member_list = content["members"]; - LLSD titles = content["titles"]; - LLSD defaults = content["defaults"]; - - std::string online_status; - std::string title; - S32 contribution; - U64 member_powers; - // If this is changed to a bool, make sure to change the LLGroupMemberData constructor - bool is_owner; - - // Compute this once, rather than every time. - U64 default_powers = llstrtou64(defaults["default_powers"].asString().c_str(), NULL, 16); - - LLSD::map_const_iterator member_iter_start = member_list.beginMap(); - LLSD::map_const_iterator member_iter_end = member_list.endMap(); - for( ; member_iter_start != member_iter_end; ++member_iter_start) - { - // Reset defaults - online_status = "unknown"; - title = titles[0].asString(); - contribution = 0; - member_powers = default_powers; - is_owner = false; - - const LLUUID member_id(member_iter_start->first); - LLSD member_info = member_iter_start->second; - - if(member_info.has("last_login")) - { - online_status = member_info["last_login"].asString(); - if(online_status == "Online") - online_status = LLTrans::getString("group_member_status_online"); - else - formatDateString(online_status); - } - - if(member_info.has("title")) - title = titles[member_info["title"].asInteger()].asString(); - - if(member_info.has("powers")) - member_powers = llstrtou64(member_info["powers"].asString().c_str(), NULL, 16); - - if(member_info.has("donated_square_meters")) - contribution = member_info["donated_square_meters"]; - - if(member_info.has("owner")) - is_owner = true; - - LLGroupMemberData* data = new LLGroupMemberData(member_id, - contribution, - member_powers, - title, - online_status, - is_owner); - - LLGroupMemberData* member_old = group_datap->mMembers[member_id]; - if (member_old && group_datap->mRoleMemberDataComplete) - { - LLGroupMemberData::role_list_t::iterator rit = member_old->roleBegin(); - LLGroupMemberData::role_list_t::iterator end = member_old->roleEnd(); - - for ( ; rit != end; ++rit) - { - data->addRole((*rit).first,(*rit).second); - } - } - else - { - group_datap->mRoleMemberDataComplete = false; - } - - group_datap->mMembers[member_id] = data; - } - - group_datap->mMemberVersion.generate(); - - // Technically, we have this data, but to prevent completely overhauling - // this entire system (it would be nice, but I don't have the time), - // I'm going to be dumb and just call services I most likely don't need - // with the thought being that the system might need it to be done. - // - // TODO: - // Refactor to reduce multiple calls for data we already have. - if(group_datap->mTitles.size() < 1) - sendGroupTitlesRequest(group_id); - - - group_datap->mMemberDataComplete = true; - group_datap->mMemberRequestID.setNull(); - // Make the role-member data request - if (group_datap->mPendingRoleMemberRequest || !group_datap->mRoleMemberDataComplete) - { - group_datap->mPendingRoleMemberRequest = false; - sendGroupRoleMembersRequest(group_id); - } - - group_datap->mChanged = true; - notifyObservers(GC_MEMBER_DATA); - -} - - -void LLGroupMgr::sendGroupRoleChanges(const LLUUID& group_id) -{ - LL_DEBUGS("GrpMgr") << "LLGroupMgr::sendGroupRoleChanges" << LL_ENDL; - LLGroupMgrGroupData* group_datap = getGroupData(group_id); - - if (group_datap && group_datap->pendingRoleChanges()) - { - group_datap->sendRoleChanges(); - - // Not expecting a response, so let anyone else watching know the data has changed. - group_datap->mChanged = true; - notifyObservers(GC_ROLE_DATA); - } -} - -void LLGroupMgr::cancelGroupRoleChanges(const LLUUID& group_id) -{ - LL_DEBUGS("GrpMgr") << "LLGroupMgr::cancelGroupRoleChanges" << LL_ENDL; - LLGroupMgrGroupData* group_datap = getGroupData(group_id); - - if (group_datap) group_datap->cancelRoleChanges(); -} - -//static -bool LLGroupMgr::parseRoleActions(const std::string& xml_filename) -{ - LLXMLNodePtr root; - - bool success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root); - - if (!success || !root || !root->hasName( "role_actions" )) - { - LL_ERRS() << "Problem reading UI role_actions file: " << xml_filename << LL_ENDL; - return false; - } - - LLXMLNodeList role_list; - - root->getChildren("action_set", role_list, false); - - for (LLXMLNodeList::iterator role_iter = role_list.begin(); role_iter != role_list.end(); ++role_iter) - { - LLXMLNodePtr action_set = role_iter->second; - - LLRoleActionSet* role_action_set = new LLRoleActionSet(); - LLRoleAction* role_action_data = new LLRoleAction(); - - // name= - std::string action_set_name; - if (action_set->getAttributeString("name", action_set_name)) - { - LL_DEBUGS("GrpMgr") << "Loading action set " << action_set_name << LL_ENDL; - role_action_data->mName = action_set_name; - } - else - { - LL_WARNS() << "Unable to parse action set with no name" << LL_ENDL; - delete role_action_set; - delete role_action_data; - continue; - } - // description= - std::string set_description; - if (action_set->getAttributeString("description", set_description)) - { - role_action_data->mDescription = set_description; - } - // long description= - std::string set_longdescription; - if (action_set->getAttributeString("longdescription", set_longdescription)) - { - role_action_data->mLongDescription = set_longdescription; - } - - // power mask= - U64 set_power_mask = 0; - - LLXMLNodeList action_list; - LLXMLNodeList::iterator action_iter; - - action_set->getChildren("action", action_list, false); - - for (action_iter = action_list.begin(); action_iter != action_list.end(); ++action_iter) - { - LLXMLNodePtr action = action_iter->second; - - LLRoleAction* role_action = new LLRoleAction(); - - // name= - std::string action_name; - if (action->getAttributeString("name", action_name)) - { - LL_DEBUGS("GrpMgr") << "Loading action " << action_name << LL_ENDL; - role_action->mName = action_name; - } - else - { - LL_WARNS() << "Unable to parse action with no name" << LL_ENDL; - delete role_action; - continue; - } - // description= - std::string description; - if (action->getAttributeString("description", description)) - { - role_action->mDescription = description; - } - // long description= - std::string longdescription; - if (action->getAttributeString("longdescription", longdescription)) - { - role_action->mLongDescription = longdescription; - } - // description= - S32 power_bit = 0; - if (action->getAttributeS32("value", power_bit)) - { - if (0 <= power_bit && power_bit < 64) - { - role_action->mPowerBit = 0x1LL << power_bit; - } - } - - set_power_mask |= role_action->mPowerBit; - - role_action_set->mActions.push_back(role_action); - } - - role_action_data->mPowerBit = set_power_mask; - role_action_set->mActionSetData = role_action_data; - - LLGroupMgr::getInstance()->mRoleActionSets.push_back(role_action_set); - } - return true; -} - -// static -void LLGroupMgr::debugClearAllGroups(void*) -{ - LLGroupMgr::getInstance()->clearGroups(); - LLGroupMgr::parseRoleActions("role_actions.xml"); -} - - +/** + * @file llgroupmgr.cpp + * @brief LLGroupMgr class implementation + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/** + * Manager for aggregating all client knowledge for specific groups + * Keeps a cache of group information. + */ + +#include "llviewerprecompiledheaders.h" + +#include "llgroupmgr.h" + +#include +#include + +#include "llappviewer.h" +#include "llagent.h" +#include "llavatarnamecache.h" +#include "llui.h" +#include "message.h" +#include "roles_constants.h" +#include "lltransactiontypes.h" +#include "llstatusbar.h" +#include "llviewerwindow.h" +#include "llpanelgroupcreate.h" +#include "llgroupactions.h" +#include "llnotificationsutil.h" +#include "lluictrlfactory.h" +#include "lltrans.h" +#include "llviewerregion.h" +#include +#include "llcorehttputil.h" +#include "lluiusage.h" + + +#if LL_MSVC +#pragma warning(push) +// disable boost::lexical_cast warning +#pragma warning (disable:4702) +#endif + +#include + +#if LL_MSVC +#pragma warning(pop) // Restore all warnings to the previous state +#endif + +const U32 MAX_CACHED_GROUPS = 20; + +// +// LLRoleActionSet +// +LLRoleActionSet::LLRoleActionSet() +: mActionSetData(NULL) +{ } + +LLRoleActionSet::~LLRoleActionSet() +{ + delete mActionSetData; + std::for_each(mActions.begin(), mActions.end(), DeletePointer()); + mActions.clear(); +} + +// +// LLGroupMemberData +// + +LLGroupMemberData::LLGroupMemberData(const LLUUID& id, + S32 contribution, + U64 agent_powers, + const std::string& title, + const std::string& online_status, + bool is_owner) : + mID(id), + mContribution(contribution), + mAgentPowers(agent_powers), + mTitle(title), + mOnlineStatus(online_status), + mIsOwner(is_owner) +{ +} + +LLGroupMemberData::~LLGroupMemberData() +{ +} + +void LLGroupMemberData::addRole(const LLUUID& role, LLGroupRoleData* rd) +{ + mRolesList[role] = rd; +} + +bool LLGroupMemberData::removeRole(const LLUUID& role) +{ + role_list_t::iterator it = mRolesList.find(role); + + if (it != mRolesList.end()) + { + mRolesList.erase(it); + return true; + } + + return false; +} + +// +// LLGroupRoleData +// + +LLGroupRoleData::LLGroupRoleData(const LLUUID& role_id, + const std::string& role_name, + const std::string& role_title, + const std::string& role_desc, + const U64 role_powers, + const S32 member_count) : + mRoleID(role_id), + mMemberCount(member_count), + mMembersNeedsSort(false) +{ + mRoleData.mRoleName = role_name; + mRoleData.mRoleTitle = role_title; + mRoleData.mRoleDescription = role_desc; + mRoleData.mRolePowers = role_powers; + mRoleData.mChangeType = RC_UPDATE_NONE; +} + +LLGroupRoleData::LLGroupRoleData(const LLUUID& role_id, + LLRoleData role_data, + const S32 member_count) : + mRoleID(role_id), + mRoleData(role_data), + mMemberCount(member_count), + mMembersNeedsSort(false) +{ + +} + +LLGroupRoleData::~LLGroupRoleData() +{ +} + +S32 LLGroupRoleData::getMembersInRole(uuid_vec_t members, + bool needs_sort) +{ + if (mRoleID.isNull()) + { + // This is the everyone role, just return the size of members, + // because everyone is in the everyone role. + return members.size(); + } + + // Sort the members list, if needed. + if (mMembersNeedsSort) + { + std::sort(mMemberIDs.begin(), mMemberIDs.end()); + mMembersNeedsSort = false; + } + if (needs_sort) + { + // Sort the members parameter. + std::sort(members.begin(), members.end()); + } + + // Return the number of members in the intersection. + S32 max_size = llmin( members.size(), mMemberIDs.size() ); + uuid_vec_t in_role( max_size ); + uuid_vec_t::iterator in_role_end; + in_role_end = std::set_intersection(mMemberIDs.begin(), mMemberIDs.end(), + members.begin(), members.end(), + in_role.begin()); + return in_role_end - in_role.begin(); +} + +void LLGroupRoleData::addMember(const LLUUID& member) +{ + mMembersNeedsSort = true; + mMemberIDs.push_back(member); +} + +bool LLGroupRoleData::removeMember(const LLUUID& member) +{ + uuid_vec_t::iterator it = std::find(mMemberIDs.begin(),mMemberIDs.end(),member); + + if (it != mMemberIDs.end()) + { + mMembersNeedsSort = true; + mMemberIDs.erase(it); + return true; + } + + return false; +} + +void LLGroupRoleData::clearMembers() +{ + mMembersNeedsSort = false; + mMemberIDs.clear(); +} + + +// +// LLGroupMgrGroupData +// + +LLGroupMgrGroupData::LLGroupMgrGroupData(const LLUUID& id) : + mID(id), + mShowInList(true), + mOpenEnrollment(false), + mMembershipFee(0), + mAllowPublish(false), + mListInProfile(false), + mMaturePublish(false), + mChanged(false), + mMemberCount(0), + mRoleCount(0), + mReceivedRoleMemberPairs(0), + mMemberDataComplete(false), + mRoleDataComplete(false), + mRoleMemberDataComplete(false), + mGroupPropertiesDataComplete(false), + mPendingRoleMemberRequest(false), + mAccessTime(0.0f), + mPendingBanRequest(false) +{ + mMemberVersion.generate(); +} + +void LLGroupMgrGroupData::setAccessed() +{ + mAccessTime = (F32)LLFrameTimer::getTotalSeconds(); +} + +bool LLGroupMgrGroupData::getRoleData(const LLUUID& role_id, LLRoleData& role_data) +{ + role_data_map_t::const_iterator it; + + // Do we have changes for it? + it = mRoleChanges.find(role_id); + if (it != mRoleChanges.end()) + { + if ((*it).second.mChangeType == RC_DELETE) return false; + + role_data = (*it).second; + return true; + } + + // Ok, no changes, hasn't been deleted, isn't a new role, just find the role. + role_list_t::const_iterator rit = mRoles.find(role_id); + if (rit != mRoles.end()) + { + role_data = (*rit).second->getRoleData(); + return true; + } + + // This role must not exist. + return false; +} + + +void LLGroupMgrGroupData::setRoleData(const LLUUID& role_id, LLRoleData role_data) +{ + // If this is a newly created group, we need to change the data in the created list. + role_data_map_t::iterator it; + it = mRoleChanges.find(role_id); + if (it != mRoleChanges.end()) + { + if ((*it).second.mChangeType == RC_CREATE) + { + role_data.mChangeType = RC_CREATE; + mRoleChanges[role_id] = role_data; + return; + } + else if ((*it).second.mChangeType == RC_DELETE) + { + // Don't do anything for a role being deleted. + return; + } + } + + // Not a new role, so put it in the changes list. + LLRoleData old_role_data; + role_list_t::iterator rit = mRoles.find(role_id); + if (rit != mRoles.end()) + { + bool data_change = ( ((*rit).second->mRoleData.mRoleDescription != role_data.mRoleDescription) + || ((*rit).second->mRoleData.mRoleName != role_data.mRoleName) + || ((*rit).second->mRoleData.mRoleTitle != role_data.mRoleTitle) ); + bool powers_change = ((*rit).second->mRoleData.mRolePowers != role_data.mRolePowers); + + if (!data_change && !powers_change) + { + // We are back to the original state, the changes have been 'undone' so take out the change. + mRoleChanges.erase(role_id); + return; + } + + if (data_change && powers_change) + { + role_data.mChangeType = RC_UPDATE_ALL; + } + else if (data_change) + { + role_data.mChangeType = RC_UPDATE_DATA; + } + else + { + role_data.mChangeType = RC_UPDATE_POWERS; + } + + mRoleChanges[role_id] = role_data; + } + else + { + LL_WARNS() << "Change being made to non-existant role " << role_id << LL_ENDL; + } +} + +bool LLGroupMgrGroupData::pendingRoleChanges() +{ + return (!mRoleChanges.empty()); +} + +// This is a no-op if the role has already been created. +void LLGroupMgrGroupData::createRole(const LLUUID& role_id, LLRoleData role_data) +{ + if (mRoleChanges.find(role_id) != mRoleChanges.end()) + { + LL_WARNS() << "create role for existing role! " << role_id << LL_ENDL; + } + else + { + role_data.mChangeType = RC_CREATE; + mRoleChanges[role_id] = role_data; + } +} + +void LLGroupMgrGroupData::deleteRole(const LLUUID& role_id) +{ + role_data_map_t::iterator it; + + // If this was a new role, just discard it. + it = mRoleChanges.find(role_id); + if (it != mRoleChanges.end() + && (*it).second.mChangeType == RC_CREATE) + { + mRoleChanges.erase(it); + return; + } + + LLRoleData rd; + rd.mChangeType = RC_DELETE; + mRoleChanges[role_id] = rd; +} + +void LLGroupMgrGroupData::addRolePower(const LLUUID &role_id, U64 power) +{ + LLRoleData rd; + if (getRoleData(role_id,rd)) + { + rd.mRolePowers |= power; + setRoleData(role_id,rd); + } + else + { + LL_WARNS() << "addRolePower: no role data found for " << role_id << LL_ENDL; + } +} + +void LLGroupMgrGroupData::removeRolePower(const LLUUID &role_id, U64 power) +{ + LLRoleData rd; + if (getRoleData(role_id,rd)) + { + rd.mRolePowers &= ~power; + setRoleData(role_id,rd); + } + else + { + LL_WARNS() << "removeRolePower: no role data found for " << role_id << LL_ENDL; + } +} + +U64 LLGroupMgrGroupData::getRolePowers(const LLUUID& role_id) +{ + LLRoleData rd; + if (getRoleData(role_id,rd)) + { + return rd.mRolePowers; + } + else + { + LL_WARNS() << "getRolePowers: no role data found for " << role_id << LL_ENDL; + return GP_NO_POWERS; + } +} + +void LLGroupMgrGroupData::removeData() +{ + // Remove member data first, because removeRoleData will walk the member list + removeMemberData(); + removeRoleData(); +} + +void LLGroupMgrGroupData::removeMemberData() +{ + for (member_list_t::iterator mi = mMembers.begin(); mi != mMembers.end(); ++mi) + { + delete mi->second; + } + mMembers.clear(); + mMemberDataComplete = false; + mMemberVersion.generate(); +} + +void LLGroupMgrGroupData::removeRoleData() +{ + for (member_list_t::iterator mi = mMembers.begin(); mi != mMembers.end(); ++mi) + { + LLGroupMemberData* data = mi->second; + if (data) + { + data->clearRoles(); + } + } + + for (role_list_t::iterator ri = mRoles.begin(); ri != mRoles.end(); ++ri) + { + LLGroupRoleData* data = ri->second; + delete data; + } + mRoles.clear(); + mReceivedRoleMemberPairs = 0; + mRoleDataComplete = false; + mRoleMemberDataComplete= false; +} + +void LLGroupMgrGroupData::removeRoleMemberData() +{ + for (member_list_t::iterator mi = mMembers.begin(); mi != mMembers.end(); ++mi) + { + LLGroupMemberData* data = mi->second; + if (data) + { + data->clearRoles(); + } + } + + for (role_list_t::iterator ri = mRoles.begin(); ri != mRoles.end(); ++ri) + { + LLGroupRoleData* data = ri->second; + if (data) + { + data->clearMembers(); + } + } + + mReceivedRoleMemberPairs = 0; + mRoleMemberDataComplete= false; +} + +LLGroupMgrGroupData::~LLGroupMgrGroupData() +{ + removeData(); +} + +bool LLGroupMgrGroupData::changeRoleMember(const LLUUID& role_id, + const LLUUID& member_id, + LLRoleMemberChangeType rmc) +{ + role_list_t::iterator ri = mRoles.find(role_id); + member_list_t::iterator mi = mMembers.find(member_id); + + if (ri == mRoles.end() + || mi == mMembers.end() ) + { + if (ri == mRoles.end()) LL_WARNS() << "LLGroupMgrGroupData::changeRoleMember couldn't find role " << role_id << LL_ENDL; + if (mi == mMembers.end()) LL_WARNS() << "LLGroupMgrGroupData::changeRoleMember couldn't find member " << member_id << LL_ENDL; + return false; + } + + LLGroupRoleData* grd = ri->second; + LLGroupMemberData* gmd = mi->second; + + if (!grd || !gmd) + { + LL_WARNS() << "LLGroupMgrGroupData::changeRoleMember couldn't get member or role data." << LL_ENDL; + return false; + } + + if (RMC_ADD == rmc) + { + LL_INFOS() << " adding member to role." << LL_ENDL; + grd->addMember(member_id); + gmd->addRole(role_id,grd); + + //TODO move this into addrole function + //see if they added someone to the owner role and update isOwner + gmd->mIsOwner = (role_id == mOwnerRole) ? true : gmd->mIsOwner; + } + else if (RMC_REMOVE == rmc) + { + LL_INFOS() << " removing member from role." << LL_ENDL; + grd->removeMember(member_id); + gmd->removeRole(role_id); + + //see if they removed someone from the owner role and update isOwner + gmd->mIsOwner = (role_id == mOwnerRole) ? false : gmd->mIsOwner; + } + + lluuid_pair role_member; + role_member.first = role_id; + role_member.second = member_id; + + change_map_t::iterator it = mRoleMemberChanges.find(role_member); + if (it != mRoleMemberChanges.end()) + { + // There was already a role change for this role_member + if (it->second.mChange == rmc) + { + // Already recorded this change? Weird. + LL_INFOS() << "Received duplicate change for " + << " role: " << role_id << " member " << member_id + << " change " << (rmc == RMC_ADD ? "ADD" : "REMOVE") << LL_ENDL; + } + else + { + // The only two operations (add and remove) currently cancel each other out + // If that changes this will need more logic + if (rmc == RMC_NONE) + { + LL_WARNS() << "changeRoleMember: existing entry with 'RMC_NONE' change! This shouldn't happen." << LL_ENDL; + LLRoleMemberChange rc(role_id,member_id,rmc); + mRoleMemberChanges[role_member] = rc; + } + else + { + mRoleMemberChanges.erase(it); + } + } + } + else + { + LLRoleMemberChange rc(role_id,member_id,rmc); + mRoleMemberChanges[role_member] = rc; + } + + recalcAgentPowers(member_id); + + mChanged = true; + return true; +} + +void LLGroupMgrGroupData::recalcAllAgentPowers() +{ + LLGroupMemberData* gmd; + + for (member_list_t::iterator mit = mMembers.begin(); + mit != mMembers.end(); ++mit) + { + gmd = mit->second; + if (!gmd) continue; + + gmd->mAgentPowers = 0; + for (LLGroupMemberData::role_list_t::iterator it = gmd->mRolesList.begin(); + it != gmd->mRolesList.end(); ++it) + { + LLGroupRoleData* grd = (*it).second; + if (!grd) continue; + + gmd->mAgentPowers |= grd->mRoleData.mRolePowers; + } + } +} + +void LLGroupMgrGroupData::recalcAgentPowers(const LLUUID& agent_id) +{ + member_list_t::iterator mi = mMembers.find(agent_id); + if (mi == mMembers.end()) return; + + LLGroupMemberData* gmd = mi->second; + + if (!gmd) return; + + gmd->mAgentPowers = 0; + for (LLGroupMemberData::role_list_t::iterator it = gmd->mRolesList.begin(); + it != gmd->mRolesList.end(); ++it) + { + LLGroupRoleData* grd = (*it).second; + if (!grd) continue; + + gmd->mAgentPowers |= grd->mRoleData.mRolePowers; + } +} + +bool LLGroupMgrGroupData::isSingleMemberNotOwner() +{ + return mMembers.size() == 1 && !mMembers.begin()->second->isOwner(); +} + +bool packRoleUpdateMessageBlock(LLMessageSystem* msg, + const LLUUID& group_id, + const LLUUID& role_id, + const LLRoleData& role_data, + bool start_message) +{ + if (start_message) + { + msg->newMessage("GroupRoleUpdate"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID",gAgent.getID()); + msg->addUUID("SessionID",gAgent.getSessionID()); + msg->addUUID("GroupID",group_id); + start_message = false; + } + + msg->nextBlock("RoleData"); + msg->addUUID("RoleID",role_id); + msg->addString("Name", role_data.mRoleName); + msg->addString("Description", role_data.mRoleDescription); + msg->addString("Title", role_data.mRoleTitle); + msg->addU64("Powers", role_data.mRolePowers); + msg->addU8("UpdateType", (U8)role_data.mChangeType); + + if (msg->isSendFullFast()) + { + gAgent.sendReliableMessage(); + start_message = true; + } + + return start_message; +} + +void LLGroupMgrGroupData::sendRoleChanges() +{ + // Commit changes locally + LLGroupRoleData* grd; + role_list_t::iterator role_it; + LLMessageSystem* msg = gMessageSystem; + bool start_message = true; + + bool need_role_cleanup = false; + bool need_role_data = false; + bool need_power_recalc = false; + + // Apply all changes + for (role_data_map_t::iterator iter = mRoleChanges.begin(); + iter != mRoleChanges.end(); ) + { + role_data_map_t::iterator it = iter++; // safely incrament iter + const LLUUID& role_id = (*it).first; + const LLRoleData& role_data = (*it).second; + + // Commit to local data set + role_it = mRoles.find((*it).first); + if ( (mRoles.end() == role_it + && RC_CREATE != role_data.mChangeType) + || (mRoles.end() != role_it + && RC_CREATE == role_data.mChangeType)) + { + continue; + } + + // NOTE: role_it is valid EXCEPT for the RC_CREATE case + switch (role_data.mChangeType) + { + case RC_CREATE: + { + // NOTE: role_it is NOT valid in this case + grd = new LLGroupRoleData(role_id, role_data, 0); + mRoles[role_id] = grd; + need_role_data = true; + break; + } + case RC_DELETE: + { + LLGroupRoleData* group_role_data = (*role_it).second; + delete group_role_data; + mRoles.erase(role_it); + need_role_cleanup = true; + need_power_recalc = true; + break; + } + case RC_UPDATE_ALL: + // fall through + case RC_UPDATE_POWERS: + need_power_recalc = true; + // fall through + case RC_UPDATE_DATA: + // fall through + default: + { + LLGroupRoleData* group_role_data = (*role_it).second; + group_role_data->setRoleData(role_data); // NOTE! might modify mRoleChanges! + break; + } + } + + // Update dataserver + start_message = packRoleUpdateMessageBlock(msg,getID(),role_id,role_data,start_message); + } + + if (!start_message) + { + gAgent.sendReliableMessage(); + } + + // If we delete a role then all the role-member pairs are invalid! + if (need_role_cleanup) + { + removeRoleMemberData(); + } + + // If we create a new role, then we need to re-fetch all the role data. + if (need_role_data) + { + LLGroupMgr::getInstance()->sendGroupRoleDataRequest(getID()); + } + + // Clean up change lists + mRoleChanges.clear(); + + // Recalculate all the agent powers because role powers have now changed. + if (need_power_recalc) + { + recalcAllAgentPowers(); + } +} + +void LLGroupMgrGroupData::cancelRoleChanges() +{ + // Clear out all changes! + mRoleChanges.clear(); +} + +void LLGroupMgrGroupData::createBanEntry(const LLUUID& ban_id, const LLGroupBanData& ban_data) +{ + mBanList[ban_id] = ban_data; +} + +void LLGroupMgrGroupData::removeBanEntry(const LLUUID& ban_id) +{ + mBanList.erase(ban_id); +} + +void LLGroupMgrGroupData::banMemberById(const LLUUID& participant_uuid) +{ + if (!mMemberDataComplete || + !mRoleDataComplete || + !(mRoleMemberDataComplete && mMembers.size())) + { + LL_WARNS() << "No Role-Member data yet, setting ban request to pending." << LL_ENDL; + mPendingBanRequest = true; + mPendingBanMemberID = participant_uuid; + + if (!mMemberDataComplete || !mMembers.size()) + { + LLGroupMgr::getInstance()->sendCapGroupMembersRequest(mID); + } + + if (!mRoleDataComplete) + { + LLGroupMgr::getInstance()->sendGroupRoleDataRequest(mID); + } + + return; + } + + LLGroupMgrGroupData::member_list_t::iterator mi = mMembers.find((participant_uuid)); + if (mi == mMembers.end()) + { + if (!mPendingBanRequest) + { + mPendingBanRequest = true; + mPendingBanMemberID = participant_uuid; + LLGroupMgr::getInstance()->sendCapGroupMembersRequest(mID); // member isn't in members list, request reloading + } + else + { + mPendingBanRequest = false; + } + + return; + } + + mPendingBanRequest = false; + + LLGroupMemberData* member_data = (*mi).second; + if (member_data && member_data->isInRole(mOwnerRole)) + { + return; // can't ban group owner + } + + std::vector ids; + ids.push_back(participant_uuid); + + LLGroupBanData ban_data; + createBanEntry(participant_uuid, ban_data); + LLGroupMgr::getInstance()->sendGroupBanRequest(LLGroupMgr::REQUEST_POST, mID, LLGroupMgr::BAN_CREATE, ids); + LLGroupMgr::getInstance()->sendGroupMemberEjects(mID, ids); + LLGroupMgr::getInstance()->sendGroupMembersRequest(mID); + LLSD args; + LLAvatarName av_name; + LLAvatarNameCache::get(participant_uuid, &av_name); + args["AVATAR_NAME"] = av_name.getUserName(); + args["GROUP_NAME"] = mName; + LLNotifications::instance().add(LLNotification::Params("EjectAvatarFromGroup").substitutions(args)); +} + +// +// LLGroupMgr +// + +LLGroupMgr::LLGroupMgr(): + mMemberRequestInFlight(false) +{ +} + +LLGroupMgr::~LLGroupMgr() +{ + clearGroups(); +} + +void LLGroupMgr::clearGroups() +{ + std::for_each(mRoleActionSets.begin(), mRoleActionSets.end(), DeletePointer()); + mRoleActionSets.clear(); + std::for_each(mGroups.begin(), mGroups.end(), DeletePairedPointer()); + mGroups.clear(); + mObservers.clear(); +} + +void LLGroupMgr::clearGroupData(const LLUUID& group_id) +{ + group_map_t::iterator iter = mGroups.find(group_id); + if (iter != mGroups.end()) + { + delete (*iter).second; + mGroups.erase(iter); + } +} + +void LLGroupMgr::addObserver(LLGroupMgrObserver* observer) +{ + if( observer->getID() != LLUUID::null ) + mObservers.insert(std::pair(observer->getID(), observer)); +} + +void LLGroupMgr::addObserver(const LLUUID& group_id, LLParticularGroupObserver* observer) +{ + if(group_id.notNull() && observer) + { + mParticularObservers[group_id].insert(observer); + } +} + +void LLGroupMgr::removeObserver(LLGroupMgrObserver* observer) +{ + if (!observer) + { + return; + } + observer_multimap_t::iterator it; + it = mObservers.find(observer->getID()); + while (it != mObservers.end()) + { + if (it->second == observer) + { + mObservers.erase(it); + break; + } + else + { + ++it; + } + } +} + +void LLGroupMgr::removeObserver(const LLUUID& group_id, LLParticularGroupObserver* observer) +{ + if(group_id.isNull() || !observer) + { + return; + } + + observer_map_t::iterator obs_it = mParticularObservers.find(group_id); + if(obs_it == mParticularObservers.end()) + return; + + obs_it->second.erase(observer); + + if (obs_it->second.size() == 0) + mParticularObservers.erase(obs_it); +} + +LLGroupMgrGroupData* LLGroupMgr::getGroupData(const LLUUID& id) +{ + group_map_t::iterator gi = mGroups.find(id); + + if (gi != mGroups.end()) + { + return gi->second; + } + return NULL; +} + +// Helper function for LLGroupMgr::processGroupMembersReply +// This reformats date strings from MM/DD/YYYY to YYYY/MM/DD ( e.g. 1/27/2008 -> 2008/1/27 ) +// so that the sorter can sort by year before month before day. +static void formatDateString(std::string &date_string) +{ + using namespace boost; + cmatch result; + const regex expression("([0-9]{1,2})/([0-9]{1,2})/([0-9]{4})"); + if (regex_match(date_string.c_str(), result, expression)) + { + // convert matches to integers so that we can pad them with zeroes on Linux + S32 year = boost::lexical_cast(result[3]); + S32 month = boost::lexical_cast(result[1]); + S32 day = boost::lexical_cast(result[2]); + + // ISO 8601 date format + date_string = llformat("%04d/%02d/%02d", year, month, day); + } +} + +// static +void LLGroupMgr::processGroupMembersReply(LLMessageSystem* msg, void** data) +{ + LL_PROFILE_ZONE_SCOPED; + + LL_DEBUGS("GrpMgr") << "LLGroupMgr::processGroupMembersReply" << LL_ENDL; + LLUUID agent_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); + if (gAgent.getID() != agent_id) + { + LL_WARNS() << "Got group members reply for another agent!" << LL_ENDL; + return; + } + + LLUUID group_id; + msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupID, group_id ); + + LLUUID request_id; + msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_RequestID, request_id); + + LLGroupMgrGroupData* group_datap = LLGroupMgr::getInstance()->getGroupData(group_id); + if (!group_datap || (group_datap->mMemberRequestID != request_id)) + { + LL_WARNS() << "processGroupMembersReply: Received incorrect (stale?) group or request id" << LL_ENDL; + return; + } + + msg->getS32(_PREHASH_GroupData, "MemberCount", group_datap->mMemberCount ); + + if (group_datap->mMemberCount > 0) + { + S32 contribution = 0; + std::string online_status; + std::string title; + U64 agent_powers = 0; + bool is_owner = false; + + S32 num_members = msg->getNumberOfBlocksFast(_PREHASH_MemberData); + for (S32 i = 0; i < num_members; i++) + { + LLUUID member_id; + + msg->getUUIDFast(_PREHASH_MemberData, _PREHASH_AgentID, member_id, i ); + msg->getS32(_PREHASH_MemberData, _PREHASH_Contribution, contribution, i); + msg->getU64(_PREHASH_MemberData, "AgentPowers", agent_powers, i); + msg->getStringFast(_PREHASH_MemberData, _PREHASH_OnlineStatus, online_status, i); + msg->getString(_PREHASH_MemberData, "Title", title, i); + msg->getBOOL(_PREHASH_MemberData,"IsOwner",is_owner,i); + + if (member_id.notNull()) + { + if (online_status == "Online") + { + static std::string localized_online(LLTrans::getString("group_member_status_online")); + online_status = localized_online; + } + else + { + formatDateString(online_status); // reformat for sorting, e.g. 12/25/2008 -> 2008/12/25 + } + + //LL_INFOS() << "Member " << member_id << " has powers " << std::hex << agent_powers << std::dec << LL_ENDL; + LLGroupMemberData* newdata = new LLGroupMemberData(member_id, + contribution, + agent_powers, + title, + online_status, + is_owner); +#if LL_DEBUG + LLGroupMgrGroupData::member_list_t::iterator mit = group_datap->mMembers.find(member_id); + if (mit != group_datap->mMembers.end()) + { + LL_INFOS() << " *** Received duplicate member data for agent " << member_id << LL_ENDL; + } +#endif + group_datap->mMembers[member_id] = newdata; + } + else + { + LL_INFOS() << "Received null group member data." << LL_ENDL; + } + } + + //if group members are loaded while titles are missing, load the titles. + if(group_datap->mTitles.size() < 1) + { + LLGroupMgr::getInstance()->sendGroupTitlesRequest(group_id); + } + } + + group_datap->mMemberVersion.generate(); + + if (group_datap->mMembers.size() == (U32)group_datap->mMemberCount) + { + group_datap->mMemberDataComplete = true; + group_datap->mMemberRequestID.setNull(); + // We don't want to make role-member data requests until we have all the members + if (group_datap->mPendingRoleMemberRequest) + { + group_datap->mPendingRoleMemberRequest = false; + LLGroupMgr::getInstance()->sendGroupRoleMembersRequest(group_datap->mID); + } + } + + group_datap->mChanged = true; + LLGroupMgr::getInstance()->notifyObservers(GC_MEMBER_DATA); +} + +//static +void LLGroupMgr::processGroupPropertiesReply(LLMessageSystem* msg, void** data) +{ + LL_PROFILE_ZONE_SCOPED; + + LL_DEBUGS("GrpMgr") << "LLGroupMgr::processGroupPropertiesReply" << LL_ENDL; + if (!msg) + { + LL_ERRS() << "Can't access the messaging system" << LL_ENDL; + return; + } + LLUUID agent_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); + if (gAgent.getID() != agent_id) + { + LL_WARNS() << "Got group properties reply for another agent!" << LL_ENDL; + return; + } + + LLUUID group_id; + std::string name; + std::string charter; + bool show_in_list = false; + LLUUID founder_id; + U64 powers_mask = GP_NO_POWERS; + S32 money = 0; + std::string member_title; + LLUUID insignia_id; + LLUUID owner_role; + U32 membership_fee = 0; + bool open_enrollment = false; + S32 num_group_members = 0; + S32 num_group_roles = 0; + bool allow_publish = false; + bool mature = false; + + msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupID, group_id ); + msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_FounderID, founder_id); + msg->getStringFast(_PREHASH_GroupData, _PREHASH_Name, name ); + msg->getStringFast(_PREHASH_GroupData, _PREHASH_Charter, charter ); + msg->getBOOLFast(_PREHASH_GroupData, _PREHASH_ShowInList, show_in_list ); + msg->getStringFast(_PREHASH_GroupData, _PREHASH_MemberTitle, member_title ); + msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_InsigniaID, insignia_id ); + msg->getU64Fast(_PREHASH_GroupData, _PREHASH_PowersMask, powers_mask ); + msg->getU32Fast(_PREHASH_GroupData, _PREHASH_MembershipFee, membership_fee ); + msg->getBOOLFast(_PREHASH_GroupData, _PREHASH_OpenEnrollment, open_enrollment ); + msg->getS32Fast(_PREHASH_GroupData, _PREHASH_GroupMembershipCount, num_group_members); + msg->getS32(_PREHASH_GroupData, "GroupRolesCount", num_group_roles); + msg->getS32Fast(_PREHASH_GroupData, _PREHASH_Money, money); + msg->getBOOL("GroupData", "AllowPublish", allow_publish); + msg->getBOOL("GroupData", "MaturePublish", mature); + msg->getUUID(_PREHASH_GroupData, "OwnerRole", owner_role); + + LLGroupMgrGroupData* group_datap = LLGroupMgr::getInstance()->createGroupData(group_id); + + group_datap->mName = name; + group_datap->mCharter = charter; + group_datap->mShowInList = show_in_list; + group_datap->mInsigniaID = insignia_id; + group_datap->mFounderID = founder_id; + group_datap->mMembershipFee = membership_fee; + group_datap->mOpenEnrollment = open_enrollment; + group_datap->mAllowPublish = allow_publish; + group_datap->mMaturePublish = mature; + group_datap->mOwnerRole = owner_role; + group_datap->mMemberCount = num_group_members; + group_datap->mRoleCount = num_group_roles + 1; // Add the everyone role. + + group_datap->mGroupPropertiesDataComplete = true; + group_datap->mChanged = true; + + properties_request_map_t::iterator request = LLGroupMgr::getInstance()->mPropRequests.find(group_id); + if (request != LLGroupMgr::getInstance()->mPropRequests.end()) + { + LLGroupMgr::getInstance()->mPropRequests.erase(request); + } + else + { + LL_DEBUGS("GrpMgr") << "GroupPropertyResponse received with no pending request. Response was slow." << LL_ENDL; + } + LLGroupMgr::getInstance()->notifyObservers(GC_PROPERTIES); +} + +// static +void LLGroupMgr::processGroupRoleDataReply(LLMessageSystem* msg, void** data) +{ + LL_PROFILE_ZONE_SCOPED; + + LL_DEBUGS("GrpMgr") << "LLGroupMgr::processGroupRoleDataReply" << LL_ENDL; + LLUUID agent_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); + if (gAgent.getID() != agent_id) + { + LL_WARNS() << "Got group role data reply for another agent!" << LL_ENDL; + return; + } + + LLUUID group_id; + msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupID, group_id ); + + LLUUID request_id; + msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_RequestID, request_id); + + LLGroupMgrGroupData* group_datap = LLGroupMgr::getInstance()->getGroupData(group_id); + if (!group_datap || (group_datap->mRoleDataRequestID != request_id)) + { + LL_WARNS() << "processGroupPropertiesReply: Received incorrect (stale?) group or request id" << LL_ENDL; + return; + } + + msg->getS32(_PREHASH_GroupData, "RoleCount", group_datap->mRoleCount ); + + std::string name; + std::string title; + std::string desc; + U64 powers = 0; + U32 member_count = 0; + LLUUID role_id; + + U32 num_blocks = msg->getNumberOfBlocks("RoleData"); + U32 i = 0; + for (i=0; i< num_blocks; ++i) + { + msg->getUUID("RoleData", "RoleID", role_id, i ); + + msg->getString("RoleData","Name",name,i); + msg->getString("RoleData","Title",title,i); + msg->getString("RoleData","Description",desc,i); + msg->getU64("RoleData","Powers",powers,i); + msg->getU32("RoleData","Members",member_count,i); + + //there are 3 predifined roles - Owners, Officers, Everyone + //there names are defined in lldatagroups.cpp + //lets change names from server to localized strings + if(name == "Everyone") + { + name = LLTrans::getString("group_role_everyone"); + } + else if(name == "Officers") + { + name = LLTrans::getString("group_role_officers"); + } + else if(name == "Owners") + { + name = LLTrans::getString("group_role_owners"); + } + + + + LL_DEBUGS("GrpMgr") << "Adding role data: " << name << " {" << role_id << "}" << LL_ENDL; + LLGroupRoleData* rd = new LLGroupRoleData(role_id,name,title,desc,powers,member_count); + group_datap->mRoles[role_id] = rd; + } + + if (group_datap->mRoles.size() == (U32)group_datap->mRoleCount) + { + group_datap->mRoleDataComplete = true; + group_datap->mRoleDataRequestID.setNull(); + // We don't want to make role-member data requests until we have all the role data + if (group_datap->mPendingRoleMemberRequest) + { + group_datap->mPendingRoleMemberRequest = false; + LLGroupMgr::getInstance()->sendGroupRoleMembersRequest(group_datap->mID); + } + } + + group_datap->mChanged = true; + LLGroupMgr::getInstance()->notifyObservers(GC_ROLE_DATA); +} + +// static +void LLGroupMgr::processGroupRoleMembersReply(LLMessageSystem* msg, void** data) +{ + LL_PROFILE_ZONE_SCOPED; + + LL_DEBUGS("GrpMgr") << "LLGroupMgr::processGroupRoleMembersReply" << LL_ENDL; + LLUUID agent_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); + if (gAgent.getID() != agent_id) + { + LL_WARNS() << "Got group role members reply for another agent!" << LL_ENDL; + return; + } + + LLUUID request_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_RequestID, request_id); + + LLUUID group_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_GroupID, group_id ); + + U32 total_pairs; + msg->getU32(_PREHASH_AgentData, "TotalPairs", total_pairs); + + LLGroupMgrGroupData* group_datap = LLGroupMgr::getInstance()->getGroupData(group_id); + if (!group_datap || (group_datap->mRoleMembersRequestID != request_id)) + { + LL_WARNS() << "processGroupRoleMembersReply: Received incorrect (stale?) group or request id" << LL_ENDL; + return; + } + + U32 num_blocks = msg->getNumberOfBlocks("MemberData"); + U32 i; + LLUUID member_id; + LLUUID role_id; + LLGroupRoleData* rd = NULL; + LLGroupMemberData* md = NULL; + + LLGroupMgrGroupData::role_list_t::iterator ri; + LLGroupMgrGroupData::member_list_t::iterator mi; + + // If total_pairs == 0, there are no members in any custom roles. + if (total_pairs > 0) + { + for (i = 0;i < num_blocks; ++i) + { + msg->getUUID("MemberData","RoleID",role_id,i); + msg->getUUID("MemberData","MemberID",member_id,i); + + if (role_id.notNull() && member_id.notNull() ) + { + rd = NULL; + ri = group_datap->mRoles.find(role_id); + if (ri != group_datap->mRoles.end()) + { + rd = ri->second; + } + + md = NULL; + mi = group_datap->mMembers.find(member_id); + if (mi != group_datap->mMembers.end()) + { + md = mi->second; + } + + if (rd && md) + { + LL_DEBUGS("GrpMgr") << "Adding role-member pair: " << role_id << ", " << member_id << LL_ENDL; + rd->addMember(member_id); + md->addRole(role_id,rd); + } + else + { + if (!rd) LL_WARNS() << "Received role data for unknown role " << role_id << " in group " << group_id << LL_ENDL; + if (!md) LL_WARNS() << "Received role data for unknown member " << member_id << " in group " << group_id << LL_ENDL; + } + } + } + + group_datap->mReceivedRoleMemberPairs += num_blocks; + } + + if (group_datap->mReceivedRoleMemberPairs == total_pairs) + { + // Add role data for the 'everyone' role to all members + LLGroupRoleData* everyone = group_datap->mRoles[LLUUID::null]; + if (!everyone) + { + LL_WARNS() << "Everyone role not found!" << LL_ENDL; + } + else + { + for (LLGroupMgrGroupData::member_list_t::iterator mi = group_datap->mMembers.begin(); + mi != group_datap->mMembers.end(); ++mi) + { + LLGroupMemberData* data = mi->second; + if (data) + { + data->addRole(LLUUID::null,everyone); + } + } + } + + group_datap->mRoleMemberDataComplete= true; + group_datap->mRoleMembersRequestID.setNull(); + } + + group_datap->mChanged = true; + LLGroupMgr::getInstance()->notifyObservers(GC_ROLE_MEMBER_DATA); + + if (group_datap->mPendingBanRequest) + { + group_datap->banMemberById(group_datap->mPendingBanMemberID); + } +} + +// static +void LLGroupMgr::processGroupTitlesReply(LLMessageSystem* msg, void** data) +{ + LL_DEBUGS("GrpMgr") << "LLGroupMgr::processGroupTitlesReply" << LL_ENDL; + LLUUID agent_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); + if (gAgent.getID() != agent_id) + { + LL_WARNS() << "Got group properties reply for another agent!" << LL_ENDL; + return; + } + + LLUUID group_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_GroupID, group_id ); + LLUUID request_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_RequestID, request_id); + + LLGroupMgrGroupData* group_datap = LLGroupMgr::getInstance()->getGroupData(group_id); + if (!group_datap || (group_datap->mTitlesRequestID != request_id)) + { + LL_WARNS() << "processGroupTitlesReply: Received incorrect (stale?) group" << LL_ENDL; + return; + } + + LLGroupTitle title; + + S32 i = 0; + S32 blocks = msg->getNumberOfBlocksFast(_PREHASH_GroupData); + for (i=0; igetString("GroupData","Title",title.mTitle,i); + msg->getUUID("GroupData","RoleID",title.mRoleID,i); + msg->getBOOL("GroupData","Selected",title.mSelected,i); + + if (!title.mTitle.empty()) + { + LL_DEBUGS("GrpMgr") << "LLGroupMgr adding title: " << title.mTitle << ", " << title.mRoleID << ", " << (title.mSelected ? 'Y' : 'N') << LL_ENDL; + group_datap->mTitles.push_back(title); + } + } + + group_datap->mChanged = true; + LLGroupMgr::getInstance()->notifyObservers(GC_TITLES); +} + +// static +void LLGroupMgr::processEjectGroupMemberReply(LLMessageSystem* msg, void ** data) +{ + LL_DEBUGS("GrpMgr") << "processEjectGroupMemberReply" << LL_ENDL; + LLUUID group_id; + msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupID, group_id); + bool success; + msg->getBOOLFast(_PREHASH_EjectData, _PREHASH_Success, success); + + // If we had a failure, the group panel needs to be updated. + if (!success) + { + LLGroupActions::refresh(group_id); + } +} + +// static +void LLGroupMgr::processJoinGroupReply(LLMessageSystem* msg, void ** data) +{ + LL_DEBUGS("GrpMgr") << "processJoinGroupReply" << LL_ENDL; + LLUUID group_id; + bool success; + msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupID, group_id); + msg->getBOOLFast(_PREHASH_GroupData, _PREHASH_Success, success); + + if (success) + { + // refresh all group information + gAgent.sendAgentDataUpdateRequest(); + + LLGroupMgr::getInstance()->clearGroupData(group_id); + // refresh the floater for this group, if any. + LLGroupActions::refresh(group_id); + } +} + +// static +void LLGroupMgr::processLeaveGroupReply(LLMessageSystem* msg, void ** data) +{ + LL_DEBUGS("GrpMgr") << "processLeaveGroupReply" << LL_ENDL; + LLUUID group_id; + bool success; + msg->getUUIDFast(_PREHASH_GroupData, _PREHASH_GroupID, group_id); + msg->getBOOLFast(_PREHASH_GroupData, _PREHASH_Success, success); + + if (success) + { + // refresh all group information + gAgent.sendAgentDataUpdateRequest(); + + LLGroupMgr::getInstance()->clearGroupData(group_id); + // close the floater for this group, if any. + LLGroupActions::closeGroup(group_id); + } +} + +// static +void LLGroupMgr::processCreateGroupReply(LLMessageSystem* msg, void ** data) +{ + LLUUID group_id; + bool success; + std::string message; + + msg->getUUIDFast(_PREHASH_ReplyData, _PREHASH_GroupID, group_id ); + + msg->getBOOLFast(_PREHASH_ReplyData, _PREHASH_Success, success ); + msg->getStringFast(_PREHASH_ReplyData, _PREHASH_Message, message ); + + if (success) + { + // refresh all group information + gAgent.sendAgentDataUpdateRequest(); + + // HACK! We haven't gotten the agent group update yet, so ... um ... fake it. + // This is so when we go to modify the group we will be able to do so. + // This isn't actually too bad because real data will come down in 2 or 3 miliseconds and replace this. + LLGroupData gd; + gd.mAcceptNotices = true; + gd.mListInProfile = true; + gd.mContribution = 0; + gd.mID = group_id; + gd.mName = "new group"; + gd.mPowers = GP_ALL_POWERS; + + gAgent.mGroups.push_back(gd); + + LLPanelGroupCreate::refreshCreatedGroup(group_id); + //FIXME + //LLFloaterGroupInfo::closeCreateGroup(); + //LLFloaterGroupInfo::showFromUUID(group_id,"roles_tab"); + } + else + { + // *TODO: Translate + LLSD args; + args["MESSAGE"] = message; + LLNotificationsUtil::add("UnableToCreateGroup", args); + } +} + +LLGroupMgrGroupData* LLGroupMgr::createGroupData(const LLUUID& id) +{ + LLGroupMgrGroupData* group_datap = NULL; + + group_map_t::iterator existing_group = LLGroupMgr::getInstance()->mGroups.find(id); + if (existing_group == LLGroupMgr::getInstance()->mGroups.end()) + { + group_datap = new LLGroupMgrGroupData(id); + LLGroupMgr::getInstance()->addGroup(group_datap); + } + else + { + group_datap = existing_group->second; + } + + if (group_datap) + { + group_datap->setAccessed(); + } + + return group_datap; +} + +bool LLGroupMgr::hasPendingPropertyRequest(const LLUUID & id) +{ + properties_request_map_t::iterator existing_req = LLGroupMgr::getInstance()->mPropRequests.find(id); + if (existing_req != LLGroupMgr::getInstance()->mPropRequests.end()) + { + if (gFrameTime - existing_req->second < MIN_GROUP_PROPERTY_REQUEST_FREQ) + { + return true; + } + else + { + LLGroupMgr::getInstance()->mPropRequests.erase(existing_req); + } + } + return false; +} + +void LLGroupMgr::addPendingPropertyRequest(const LLUUID& id) +{ + LLGroupMgr::getInstance()->mPropRequests[id] = gFrameTime; +} + +void LLGroupMgr::notifyObservers(LLGroupChange gc) +{ + for (group_map_t::iterator gi = mGroups.begin(); gi != mGroups.end(); ++gi) + { + LLUUID group_id = gi->first; + if (gi->second->mChanged) + { + // notify LLGroupMgrObserver + // Copy the map because observers may remove themselves on update + observer_multimap_t observers = mObservers; + + // find all observers for this group id + observer_multimap_t::iterator oi = observers.lower_bound(group_id); + observer_multimap_t::iterator end = observers.upper_bound(group_id); + for (; oi != end; ++oi) + { + oi->second->changed(gc); + } + gi->second->mChanged = false; + + + // notify LLParticularGroupObserver + observer_map_t::iterator obs_it = mParticularObservers.find(group_id); + if(obs_it == mParticularObservers.end()) + return; + + observer_set_t& obs = obs_it->second; + for (observer_set_t::iterator ob_it = obs.begin(); ob_it != obs.end(); ++ob_it) + { + (*ob_it)->changed(group_id, gc); + } + } + } +} + +void LLGroupMgr::addGroup(LLGroupMgrGroupData* group_datap) +{ + while (mGroups.size() >= MAX_CACHED_GROUPS) + { + // LRU: Remove the oldest un-observed group from cache until group size is small enough + + F32 oldest_access = LLFrameTimer::getTotalSeconds(); + group_map_t::iterator oldest_gi = mGroups.end(); + + for (group_map_t::iterator gi = mGroups.begin(); gi != mGroups.end(); ++gi ) + { + observer_multimap_t::iterator oi = mObservers.find(gi->first); + if (oi == mObservers.end()) + { + if (gi->second + && (gi->second->getAccessTime() < oldest_access)) + { + oldest_access = gi->second->getAccessTime(); + oldest_gi = gi; + } + } + } + + if (oldest_gi != mGroups.end()) + { + delete oldest_gi->second; + mGroups.erase(oldest_gi); + } + else + { + // All groups must be currently open, none to remove. + // Just add the new group anyway, but get out of this loop as it + // will never drop below max_cached_groups. + break; + } + } + + mGroups[group_datap->getID()] = group_datap; +} + + +void LLGroupMgr::sendGroupPropertiesRequest(const LLUUID& group_id) +{ + LL_DEBUGS("GrpMgr") << "LLGroupMgr::sendGroupPropertiesRequest" << LL_ENDL; + // This will happen when we get the reply + //LLGroupMgrGroupData* group_datap = createGroupData(group_id); + + if (LLGroupMgr::getInstance()->hasPendingPropertyRequest(group_id)) + { + LL_DEBUGS("GrpMgr") << "LLGroupMgr::sendGroupPropertiesRequest suppressed repeat for " << group_id << LL_ENDL; + return; + } + LLGroupMgr::getInstance()->addPendingPropertyRequest(group_id); + + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("GroupProfileRequest"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID",gAgent.getID()); + msg->addUUID("SessionID",gAgent.getSessionID()); + msg->nextBlock("GroupData"); + msg->addUUID("GroupID",group_id); + gAgent.sendReliableMessage(); +} + +void LLGroupMgr::sendGroupMembersRequest(const LLUUID& group_id) +{ + LL_DEBUGS("GrpMgr") << "LLGroupMgr::sendGroupMembersRequest" << LL_ENDL; + LLGroupMgrGroupData* group_datap = createGroupData(group_id); + if (group_datap->mMemberRequestID.isNull()) + { + group_datap->removeMemberData(); + group_datap->mMemberRequestID.generate(); + + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("GroupMembersRequest"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID",gAgent.getID()); + msg->addUUID("SessionID",gAgent.getSessionID()); + msg->nextBlock("GroupData"); + msg->addUUID("GroupID",group_id); + msg->addUUID("RequestID",group_datap->mMemberRequestID); + gAgent.sendReliableMessage(); + } +} + + +void LLGroupMgr::sendGroupRoleDataRequest(const LLUUID& group_id) +{ + LL_DEBUGS("GrpMgr") << "LLGroupMgr::sendGroupRoleDataRequest" << LL_ENDL; + LLGroupMgrGroupData* group_datap = createGroupData(group_id); + if (group_datap->mRoleDataRequestID.isNull()) + { + group_datap->removeRoleData(); + group_datap->mRoleDataRequestID.generate(); + + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("GroupRoleDataRequest"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID",gAgent.getID()); + msg->addUUID("SessionID",gAgent.getSessionID()); + msg->nextBlock("GroupData"); + msg->addUUID("GroupID",group_id); + msg->addUUID("RequestID",group_datap->mRoleDataRequestID); + gAgent.sendReliableMessage(); + } +} + +void LLGroupMgr::sendGroupRoleMembersRequest(const LLUUID& group_id) +{ + LL_DEBUGS("GrpMgr") << "LLGroupMgr::sendGroupRoleMembersRequest" << LL_ENDL; + LLGroupMgrGroupData* group_datap = createGroupData(group_id); + + if (group_datap->mRoleMembersRequestID.isNull()) + { + // Don't send the request if we don't have all the member or role data + if (!group_datap->isMemberDataComplete() + || !group_datap->isRoleDataComplete()) + { + // *TODO: KLW FIXME: Should we start a member or role data request? + LL_INFOS("GrpMgr") << " Pending: " << (group_datap->mPendingRoleMemberRequest ? "Y" : "N") + << " MemberDataComplete: " << (group_datap->mMemberDataComplete ? "Y" : "N") + << " RoleDataComplete: " << (group_datap->mRoleDataComplete ? "Y" : "N") << LL_ENDL; + group_datap->mPendingRoleMemberRequest = true; + return; + } + + group_datap->removeRoleMemberData(); + group_datap->mRoleMembersRequestID.generate(); + + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("GroupRoleMembersRequest"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID",gAgent.getID()); + msg->addUUID("SessionID",gAgent.getSessionID()); + msg->nextBlock("GroupData"); + msg->addUUID("GroupID",group_id); + msg->addUUID("RequestID",group_datap->mRoleMembersRequestID); + gAgent.sendReliableMessage(); + } +} + +void LLGroupMgr::sendGroupTitlesRequest(const LLUUID& group_id) +{ + LL_DEBUGS("GrpMgr") << "LLGroupMgr::sendGroupTitlesRequest" << LL_ENDL; + LLGroupMgrGroupData* group_datap = createGroupData(group_id); + + group_datap->mTitles.clear(); + group_datap->mTitlesRequestID.generate(); + + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("GroupTitlesRequest"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID",gAgent.getID()); + msg->addUUID("SessionID",gAgent.getSessionID()); + msg->addUUID("GroupID",group_id); + msg->addUUID("RequestID",group_datap->mTitlesRequestID); + + gAgent.sendReliableMessage(); +} + +void LLGroupMgr::sendGroupTitleUpdate(const LLUUID& group_id, const LLUUID& title_role_id) +{ + LL_DEBUGS("GrpMgr") << "LLGroupMgr::sendGroupTitleUpdate" << LL_ENDL; + + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("GroupTitleUpdate"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID",gAgent.getID()); + msg->addUUID("SessionID",gAgent.getSessionID()); + msg->addUUID("GroupID",group_id); + msg->addUUID("TitleRoleID",title_role_id); + + gAgent.sendReliableMessage(); + + // Save the change locally + LLGroupMgrGroupData* group_datap = createGroupData(group_id); + for (std::vector::iterator iter = group_datap->mTitles.begin(); + iter != group_datap->mTitles.end(); ++iter) + { + if (iter->mRoleID == title_role_id) + { + iter->mSelected = true; + } + else if (iter->mSelected) + { + iter->mSelected = false; + } + } +} + +// static +void LLGroupMgr::sendCreateGroupRequest(const std::string& name, + const std::string& charter, + U8 show_in_list, + const LLUUID& insignia, + S32 membership_fee, + bool open_enrollment, + bool allow_publish, + bool mature_publish) +{ + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("CreateGroupRequest"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID",gAgent.getID()); + msg->addUUID("SessionID",gAgent.getSessionID()); + + msg->nextBlock("GroupData"); + msg->addString("Name",name); + msg->addString("Charter",charter); + msg->addBOOL("ShowInList",show_in_list); + msg->addUUID("InsigniaID",insignia); + msg->addS32("MembershipFee",membership_fee); + msg->addBOOL("OpenEnrollment",open_enrollment); + msg->addBOOL("AllowPublish",allow_publish); + msg->addBOOL("MaturePublish",mature_publish); + + gAgent.sendReliableMessage(); +} + +void LLGroupMgr::sendUpdateGroupInfo(const LLUUID& group_id) +{ + LL_DEBUGS("GrpMgr") << "LLGroupMgr::sendUpdateGroupInfo" << LL_ENDL; + LLGroupMgrGroupData* group_datap = createGroupData(group_id); + + LLMessageSystem* msg = gMessageSystem; + + msg->newMessageFast(_PREHASH_UpdateGroupInfo); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID,gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); + + msg->nextBlockFast(_PREHASH_GroupData); + msg->addUUIDFast(_PREHASH_GroupID,group_datap->getID()); + msg->addStringFast(_PREHASH_Charter,group_datap->mCharter); + msg->addBOOLFast(_PREHASH_ShowInList,group_datap->mShowInList); + msg->addUUIDFast(_PREHASH_InsigniaID,group_datap->mInsigniaID); + msg->addS32Fast(_PREHASH_MembershipFee,group_datap->mMembershipFee); + msg->addBOOLFast(_PREHASH_OpenEnrollment,group_datap->mOpenEnrollment); + msg->addBOOLFast(_PREHASH_AllowPublish,group_datap->mAllowPublish); + msg->addBOOLFast(_PREHASH_MaturePublish,group_datap->mMaturePublish); + + gAgent.sendReliableMessage(); + + // Not expecting a response, so let anyone else watching know the data has changed. + group_datap->mChanged = true; + notifyObservers(GC_PROPERTIES); +} + +void LLGroupMgr::sendGroupRoleMemberChanges(const LLUUID& group_id) +{ + LL_DEBUGS("GrpMgr") << "LLGroupMgr::sendGroupRoleMemberChanges" << LL_ENDL; + LLGroupMgrGroupData* group_datap = createGroupData(group_id); + + if (group_datap->mRoleMemberChanges.empty()) return; + + LLMessageSystem* msg = gMessageSystem; + + bool start_message = true; + for (LLGroupMgrGroupData::change_map_t::const_iterator citer = group_datap->mRoleMemberChanges.begin(); + citer != group_datap->mRoleMemberChanges.end(); ++citer) + { + if (start_message) + { + msg->newMessage("GroupRoleChanges"); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID,gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); + msg->addUUIDFast(_PREHASH_GroupID,group_id); + start_message = false; + } + msg->nextBlock("RoleChange"); + msg->addUUID("RoleID",citer->second.mRole); + msg->addUUID("MemberID",citer->second.mMember); + msg->addU32("Change",(U32)citer->second.mChange); + + if (msg->isSendFullFast()) + { + gAgent.sendReliableMessage(); + start_message = true; + } + } + + if (!start_message) + { + gAgent.sendReliableMessage(); + } + + group_datap->mRoleMemberChanges.clear(); + + // Not expecting a response, so let anyone else watching know the data has changed. + group_datap->mChanged = true; + notifyObservers(GC_ROLE_MEMBER_DATA); +} + +//static +void LLGroupMgr::sendGroupMemberJoin(const LLUUID& group_id) +{ + + LLUIUsage::instance().logCommand("Group.Join"); + + LLMessageSystem *msg = gMessageSystem; + + msg->newMessageFast(_PREHASH_JoinGroupRequest); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_GroupData); + msg->addUUIDFast(_PREHASH_GroupID, group_id); + + gAgent.sendReliableMessage(); +} + +// member_role_pairs is +// static +void LLGroupMgr::sendGroupMemberInvites(const LLUUID& group_id, std::map& member_role_pairs) +{ + bool start_message = true; + LLMessageSystem* msg = gMessageSystem; + + for (std::map::iterator it = member_role_pairs.begin(); + it != member_role_pairs.end(); ++it) + { + if (start_message) + { + msg->newMessage("InviteGroupRequest"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID",gAgent.getID()); + msg->addUUID("SessionID",gAgent.getSessionID()); + msg->nextBlock("GroupData"); + msg->addUUID("GroupID",group_id); + start_message = false; + } + + msg->nextBlock("InviteData"); + msg->addUUID("InviteeID",(*it).first); + msg->addUUID("RoleID",(*it).second); + + if (msg->isSendFull()) + { + gAgent.sendReliableMessage(); + start_message = true; + } + } + + if (!start_message) + { + gAgent.sendReliableMessage(); + } +} + +//static +void LLGroupMgr::sendGroupMemberEjects(const LLUUID& group_id, + uuid_vec_t& member_ids) +{ + bool start_message = true; + LLMessageSystem* msg = gMessageSystem; + + LLGroupMgrGroupData* group_datap = LLGroupMgr::getInstance()->getGroupData(group_id); + if (!group_datap) return; + + for (uuid_vec_t::iterator it = member_ids.begin(); + it != member_ids.end(); ++it) + { + LLUUID& ejected_member_id = (*it); + + // Can't use 'eject' to leave a group. + if (ejected_member_id == gAgent.getID()) continue; + + // Make sure they are in the group, and we need the member data + LLGroupMgrGroupData::member_list_t::iterator mit = group_datap->mMembers.find(ejected_member_id); + if (mit != group_datap->mMembers.end()) + { + // Add them to the message + if (start_message) + { + msg->newMessage("EjectGroupMemberRequest"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID",gAgent.getID()); + msg->addUUID("SessionID",gAgent.getSessionID()); + msg->nextBlock("GroupData"); + msg->addUUID("GroupID",group_id); + start_message = false; + } + + msg->nextBlock("EjectData"); + msg->addUUID("EjecteeID",ejected_member_id); + + if (msg->isSendFull()) + { + gAgent.sendReliableMessage(); + start_message = true; + } + + LLGroupMemberData* member_data = (*mit).second; + + // Clean up groupmgr + for (LLGroupMemberData::role_list_t::iterator rit = member_data->roleBegin(); + rit != member_data->roleEnd(); ++rit) + { + if ((*rit).first.notNull() && (*rit).second!=0) + { + (*rit).second->removeMember(ejected_member_id); + } + } + + group_datap->mMembers.erase(ejected_member_id); + + // member_data was introduced and is used here instead of (*mit).second to avoid crash because of invalid iterator + // It becomes invalid after line with erase above. EXT-4778 + delete member_data; + } + } + + if (!start_message) + { + gAgent.sendReliableMessage(); + } + + group_datap->mMemberVersion.generate(); +} + +void LLGroupMgr::getGroupBanRequestCoro(std::string url, LLUUID groupId) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("groupMembersRequest", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + std::string finalUrl = url + "?group_id=" + groupId.asString(); + + LLSD result = httpAdapter->getAndSuspend(httpRequest, finalUrl); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS("GrpMgr") << "Error receiving group member data " << LL_ENDL; + return; + } + + if (result.has("ban_list")) + { + result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); + // group ban data received + processGroupBanRequest(result); + } +} + +void LLGroupMgr::postGroupBanRequestCoro(std::string url, LLUUID groupId, + U32 action, uuid_vec_t banList, bool update) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("groupMembersRequest", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); + LLCore::HttpOptions::ptr_t httpOptions(new LLCore::HttpOptions); + + httpOptions->setFollowRedirects(false); + + httpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_LLSD_XML); + + + std::string finalUrl = url + "?group_id=" + groupId.asString(); + + LLSD postData = LLSD::emptyMap(); + postData["ban_action"] = (LLSD::Integer)action; + // Add our list of potential banned residents to the list + postData["ban_ids"] = LLSD::emptyArray(); + LLSD banEntry; + + uuid_vec_t::const_iterator it = banList.begin(); + for (; it != banList.end(); ++it) + { + banEntry = (*it); + postData["ban_ids"].append(banEntry); + } + + LL_WARNS() << "post: " << ll_pretty_print_sd(postData) << LL_ENDL; + + LLSD result = httpAdapter->postAndSuspend(httpRequest, finalUrl, postData, httpOptions, httpHeaders); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS("GrpMgr") << "Error posting group member data " << LL_ENDL; + return; + } + + if (result.has("ban_list")) + { + result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); + // group ban data received + processGroupBanRequest(result); + } + + if (update) + { + getGroupBanRequestCoro(url, groupId); + } +} + +void LLGroupMgr::sendGroupBanRequest( EBanRequestType request_type, + const LLUUID& group_id, + U32 ban_action, /* = BAN_NO_ACTION */ + const std::vector &ban_list) /* = std::vector() */ +{ + LLViewerRegion* currentRegion = gAgent.getRegion(); + if(!currentRegion) + { + LL_WARNS("GrpMgr") << "Agent does not have a current region. Uh-oh!" << LL_ENDL; + return; + } + + // Check to make sure we have our capabilities + if(!currentRegion->capabilitiesReceived()) + { + LL_WARNS("GrpMgr") << " Capabilities not received!" << LL_ENDL; + return; + } + + // Get our capability + std::string cap_url = currentRegion->getCapability("GroupAPIv1"); + if(cap_url.empty()) + { + return; + } + + U32 action = ban_action & ~BAN_UPDATE; + bool update = ((ban_action & BAN_UPDATE) == BAN_UPDATE); + + switch (request_type) + { + case REQUEST_GET: + LLCoros::instance().launch("LLGroupMgr::getGroupBanRequestCoro", + boost::bind(&LLGroupMgr::getGroupBanRequestCoro, this, cap_url, group_id)); + break; + case REQUEST_POST: + LLCoros::instance().launch("LLGroupMgr::postGroupBanRequestCoro", + boost::bind(&LLGroupMgr::postGroupBanRequestCoro, this, cap_url, group_id, + action, ban_list, update)); + break; + case REQUEST_PUT: + case REQUEST_DEL: + break; + } +} + +void LLGroupMgr::processGroupBanRequest(const LLSD& content) +{ + // Did we get anything in content? + if(!content.size()) + { + LL_WARNS("GrpMgr") << "No group member data received." << LL_ENDL; + return; + } + + LLUUID group_id = content["group_id"].asUUID(); + + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_id); + if (!gdatap) + return; + + gdatap->clearBanList(); + LLSD::map_const_iterator i = content["ban_list"].beginMap(); + LLSD::map_const_iterator iEnd = content["ban_list"].endMap(); + for(;i != iEnd; ++i) + { + const LLUUID ban_id(i->first); + LLSD ban_entry(i->second); + + LLGroupBanData ban_data; + if(ban_entry.has("ban_date")) + { + ban_data.mBanDate = ban_entry["ban_date"].asDate(); + // TODO: Ban Reason + } + + gdatap->createBanEntry(ban_id, ban_data); + } + + gdatap->mChanged = true; + LLGroupMgr::getInstance()->notifyObservers(GC_BANLIST); +} + +void LLGroupMgr::groupMembersRequestCoro(std::string url, LLUUID groupId) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("groupMembersRequest", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); + + mMemberRequestInFlight = true; + + LLSD postData = LLSD::emptyMap(); + postData["group_id"] = groupId; + + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData, httpOpts); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS("GrpMgr") << "Error receiving group member data " << LL_ENDL; + mMemberRequestInFlight = false; + return; + } + + result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); + LLGroupMgr::processCapGroupMembersRequest(result); + mMemberRequestInFlight = false; +} + +void LLGroupMgr::sendCapGroupMembersRequest(const LLUUID& group_id) +{ + static U32 lastGroupMemberRequestFrame = 0; + + // Have we requested the information already this frame? + // Todo: make this per group, we can invite to one group and simultaneously be checking another one + if ((lastGroupMemberRequestFrame == gFrameCount) || (mMemberRequestInFlight)) + return; + + LLViewerRegion* currentRegion = gAgent.getRegion(); + // Thank you FS:Ansariel! + if(!currentRegion) + { + LL_WARNS("GrpMgr") << "Agent does not have a current region. Uh-oh!" << LL_ENDL; + return; + } + + // Check to make sure we have our capabilities + if(!currentRegion->capabilitiesReceived()) + { + LL_WARNS("GrpMgr") << " Capabilities not received!" << LL_ENDL; + return; + } + + // Get our capability + std::string cap_url = currentRegion->getCapability("GroupMemberData"); + + // Thank you FS:Ansariel! + if(cap_url.empty()) + { + LL_INFOS("GrpMgr") << "Region has no GroupMemberData capability. Falling back to UDP fetch." << LL_ENDL; + sendGroupMembersRequest(group_id); + return; + } + + LLGroupMgrGroupData* group_datap = createGroupData(group_id); //make sure group exists + group_datap->mMemberRequestID.generate(); // mark as pending + + lastGroupMemberRequestFrame = gFrameCount; + + LLCoros::instance().launch("LLGroupMgr::groupMembersRequestCoro", + boost::bind(&LLGroupMgr::groupMembersRequestCoro, this, cap_url, group_id)); +} + + +void LLGroupMgr::processCapGroupMembersRequest(const LLSD& content) +{ + // Did we get anything in content? + if(!content.size()) + { + LL_DEBUGS("GrpMgr") << "No group member data received." << LL_ENDL; + return; + } + + LLUUID group_id = content["group_id"].asUUID(); + + LLGroupMgrGroupData* group_datap = getGroupData(group_id); + if(!group_datap) + { + LL_WARNS("GrpMgr") << "Received incorrect, possibly stale, group or request id" << LL_ENDL; + return; + } + + // If we have no members, there's no reason to do anything else + S32 num_members = content["member_count"]; + if (num_members < 1) + { + LL_INFOS("GrpMgr") << "Received empty group members list for group id: " << group_id.asString() << LL_ENDL; + // Set mMemberDataComplete for correct handling of empty responses. See MAINT-5237 + group_datap->mMemberDataComplete = true; + group_datap->mChanged = true; + LLGroupMgr::getInstance()->notifyObservers(GC_MEMBER_DATA); + return; + } + + group_datap->mMemberCount = num_members; + + LLSD member_list = content["members"]; + LLSD titles = content["titles"]; + LLSD defaults = content["defaults"]; + + std::string online_status; + std::string title; + S32 contribution; + U64 member_powers; + // If this is changed to a bool, make sure to change the LLGroupMemberData constructor + bool is_owner; + + // Compute this once, rather than every time. + U64 default_powers = llstrtou64(defaults["default_powers"].asString().c_str(), NULL, 16); + + LLSD::map_const_iterator member_iter_start = member_list.beginMap(); + LLSD::map_const_iterator member_iter_end = member_list.endMap(); + for( ; member_iter_start != member_iter_end; ++member_iter_start) + { + // Reset defaults + online_status = "unknown"; + title = titles[0].asString(); + contribution = 0; + member_powers = default_powers; + is_owner = false; + + const LLUUID member_id(member_iter_start->first); + LLSD member_info = member_iter_start->second; + + if(member_info.has("last_login")) + { + online_status = member_info["last_login"].asString(); + if(online_status == "Online") + online_status = LLTrans::getString("group_member_status_online"); + else + formatDateString(online_status); + } + + if(member_info.has("title")) + title = titles[member_info["title"].asInteger()].asString(); + + if(member_info.has("powers")) + member_powers = llstrtou64(member_info["powers"].asString().c_str(), NULL, 16); + + if(member_info.has("donated_square_meters")) + contribution = member_info["donated_square_meters"]; + + if(member_info.has("owner")) + is_owner = true; + + LLGroupMemberData* data = new LLGroupMemberData(member_id, + contribution, + member_powers, + title, + online_status, + is_owner); + + LLGroupMemberData* member_old = group_datap->mMembers[member_id]; + if (member_old && group_datap->mRoleMemberDataComplete) + { + LLGroupMemberData::role_list_t::iterator rit = member_old->roleBegin(); + LLGroupMemberData::role_list_t::iterator end = member_old->roleEnd(); + + for ( ; rit != end; ++rit) + { + data->addRole((*rit).first,(*rit).second); + } + } + else + { + group_datap->mRoleMemberDataComplete = false; + } + + group_datap->mMembers[member_id] = data; + } + + group_datap->mMemberVersion.generate(); + + // Technically, we have this data, but to prevent completely overhauling + // this entire system (it would be nice, but I don't have the time), + // I'm going to be dumb and just call services I most likely don't need + // with the thought being that the system might need it to be done. + // + // TODO: + // Refactor to reduce multiple calls for data we already have. + if(group_datap->mTitles.size() < 1) + sendGroupTitlesRequest(group_id); + + + group_datap->mMemberDataComplete = true; + group_datap->mMemberRequestID.setNull(); + // Make the role-member data request + if (group_datap->mPendingRoleMemberRequest || !group_datap->mRoleMemberDataComplete) + { + group_datap->mPendingRoleMemberRequest = false; + sendGroupRoleMembersRequest(group_id); + } + + group_datap->mChanged = true; + notifyObservers(GC_MEMBER_DATA); + +} + + +void LLGroupMgr::sendGroupRoleChanges(const LLUUID& group_id) +{ + LL_DEBUGS("GrpMgr") << "LLGroupMgr::sendGroupRoleChanges" << LL_ENDL; + LLGroupMgrGroupData* group_datap = getGroupData(group_id); + + if (group_datap && group_datap->pendingRoleChanges()) + { + group_datap->sendRoleChanges(); + + // Not expecting a response, so let anyone else watching know the data has changed. + group_datap->mChanged = true; + notifyObservers(GC_ROLE_DATA); + } +} + +void LLGroupMgr::cancelGroupRoleChanges(const LLUUID& group_id) +{ + LL_DEBUGS("GrpMgr") << "LLGroupMgr::cancelGroupRoleChanges" << LL_ENDL; + LLGroupMgrGroupData* group_datap = getGroupData(group_id); + + if (group_datap) group_datap->cancelRoleChanges(); +} + +//static +bool LLGroupMgr::parseRoleActions(const std::string& xml_filename) +{ + LLXMLNodePtr root; + + bool success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root); + + if (!success || !root || !root->hasName( "role_actions" )) + { + LL_ERRS() << "Problem reading UI role_actions file: " << xml_filename << LL_ENDL; + return false; + } + + LLXMLNodeList role_list; + + root->getChildren("action_set", role_list, false); + + for (LLXMLNodeList::iterator role_iter = role_list.begin(); role_iter != role_list.end(); ++role_iter) + { + LLXMLNodePtr action_set = role_iter->second; + + LLRoleActionSet* role_action_set = new LLRoleActionSet(); + LLRoleAction* role_action_data = new LLRoleAction(); + + // name= + std::string action_set_name; + if (action_set->getAttributeString("name", action_set_name)) + { + LL_DEBUGS("GrpMgr") << "Loading action set " << action_set_name << LL_ENDL; + role_action_data->mName = action_set_name; + } + else + { + LL_WARNS() << "Unable to parse action set with no name" << LL_ENDL; + delete role_action_set; + delete role_action_data; + continue; + } + // description= + std::string set_description; + if (action_set->getAttributeString("description", set_description)) + { + role_action_data->mDescription = set_description; + } + // long description= + std::string set_longdescription; + if (action_set->getAttributeString("longdescription", set_longdescription)) + { + role_action_data->mLongDescription = set_longdescription; + } + + // power mask= + U64 set_power_mask = 0; + + LLXMLNodeList action_list; + LLXMLNodeList::iterator action_iter; + + action_set->getChildren("action", action_list, false); + + for (action_iter = action_list.begin(); action_iter != action_list.end(); ++action_iter) + { + LLXMLNodePtr action = action_iter->second; + + LLRoleAction* role_action = new LLRoleAction(); + + // name= + std::string action_name; + if (action->getAttributeString("name", action_name)) + { + LL_DEBUGS("GrpMgr") << "Loading action " << action_name << LL_ENDL; + role_action->mName = action_name; + } + else + { + LL_WARNS() << "Unable to parse action with no name" << LL_ENDL; + delete role_action; + continue; + } + // description= + std::string description; + if (action->getAttributeString("description", description)) + { + role_action->mDescription = description; + } + // long description= + std::string longdescription; + if (action->getAttributeString("longdescription", longdescription)) + { + role_action->mLongDescription = longdescription; + } + // description= + S32 power_bit = 0; + if (action->getAttributeS32("value", power_bit)) + { + if (0 <= power_bit && power_bit < 64) + { + role_action->mPowerBit = 0x1LL << power_bit; + } + } + + set_power_mask |= role_action->mPowerBit; + + role_action_set->mActions.push_back(role_action); + } + + role_action_data->mPowerBit = set_power_mask; + role_action_set->mActionSetData = role_action_data; + + LLGroupMgr::getInstance()->mRoleActionSets.push_back(role_action_set); + } + return true; +} + +// static +void LLGroupMgr::debugClearAllGroups(void*) +{ + LLGroupMgr::getInstance()->clearGroups(); + LLGroupMgr::parseRoleActions("role_actions.xml"); +} + + diff --git a/indra/newview/llgroupmgr.h b/indra/newview/llgroupmgr.h index 10fb285409..84adcd024f 100644 --- a/indra/newview/llgroupmgr.h +++ b/indra/newview/llgroupmgr.h @@ -1,472 +1,472 @@ -/** - * @file llgroupmgr.h - * @brief Manager for aggregating all client knowledge for specific groups - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLGROUPMGR_H -#define LL_LLGROUPMGR_H - -#include "lluuid.h" -#include "roles_constants.h" -#include -#include -#include -#include "lleventcoro.h" -#include "llcoros.h" - -// Forward Declarations -class LLMessageSystem; -class LLGroupRoleData; -class LLGroupMgr; - -enum LLGroupChange -{ - GC_PROPERTIES, - GC_MEMBER_DATA, - GC_ROLE_DATA, - GC_ROLE_MEMBER_DATA, - GC_TITLES, - GC_BANLIST, - GC_ALL -}; - -const U32 GB_MAX_BANNED_AGENTS = 500; - -class LLGroupMgrObserver -{ -public: - LLGroupMgrObserver(const LLUUID& id) : mID(id){}; - LLGroupMgrObserver() : mID(LLUUID::null){}; - virtual ~LLGroupMgrObserver(){}; - virtual void changed(LLGroupChange gc) = 0; - const LLUUID& getID() { return mID; } -protected: - LLUUID mID; -}; - -class LLParticularGroupObserver -{ -public: - virtual ~LLParticularGroupObserver(){} - virtual void changed(const LLUUID& group_id, LLGroupChange gc) = 0; -}; - -class LLGroupMemberData -{ -friend class LLGroupMgrGroupData; - -public: - typedef std::map role_list_t; - - LLGroupMemberData(const LLUUID& id, - S32 contribution, - U64 agent_powers, - const std::string& title, - const std::string& online_status, - bool is_owner); - - ~LLGroupMemberData(); - - const LLUUID& getID() const { return mID; } - S32 getContribution() const { return mContribution; } - U64 getAgentPowers() const { return mAgentPowers; } - bool isOwner() const { return mIsOwner; } - const std::string& getTitle() const { return mTitle; } - const std::string& getOnlineStatus() const { return mOnlineStatus; } - void addRole(const LLUUID& role, LLGroupRoleData* rd); - bool removeRole(const LLUUID& role); - void clearRoles() { mRolesList.clear(); }; - role_list_t::iterator roleBegin() { return mRolesList.begin(); } - role_list_t::iterator roleEnd() { return mRolesList.end(); } - - bool isInRole(const LLUUID& role_id) { return (mRolesList.find(role_id) != mRolesList.end()); } - -private: - LLUUID mID; - S32 mContribution; - U64 mAgentPowers; - std::string mTitle; - std::string mOnlineStatus; - bool mIsOwner; - role_list_t mRolesList; -}; - -struct LLRoleData -{ - LLRoleData() : mRolePowers(0), mChangeType(RC_UPDATE_NONE) { } - LLRoleData(const LLRoleData& rd) - : mRoleName(rd.mRoleName), - mRoleTitle(rd.mRoleTitle), - mRoleDescription(rd.mRoleDescription), - mRolePowers(rd.mRolePowers), - mChangeType(rd.mChangeType) { } - - std::string mRoleName; - std::string mRoleTitle; - std::string mRoleDescription; - U64 mRolePowers; - LLRoleChangeType mChangeType; -}; - -class LLGroupRoleData -{ -friend class LLGroupMgrGroupData; - -public: - LLGroupRoleData(const LLUUID& role_id, - const std::string& role_name, - const std::string& role_title, - const std::string& role_desc, - const U64 role_powers, - const S32 member_count); - - LLGroupRoleData(const LLUUID& role_id, - LLRoleData role_data, - const S32 member_count); - - ~LLGroupRoleData(); - - const LLUUID& getID() const { return mRoleID; } - - const uuid_vec_t& getRoleMembers() const { return mMemberIDs; } - S32 getMembersInRole(uuid_vec_t members, bool needs_sort = true); - S32 getTotalMembersInRole() { return mMemberCount ? mMemberCount : mMemberIDs.size(); } //FIXME: Returns 0 for Everyone role when Member list isn't yet loaded, see MAINT-5225 - - LLRoleData getRoleData() const { return mRoleData; } - void setRoleData(LLRoleData data) { mRoleData = data; } - - void addMember(const LLUUID& member); - bool removeMember(const LLUUID& member); - void clearMembers(); - - const uuid_vec_t::const_iterator getMembersBegin() const - { return mMemberIDs.begin(); } - - const uuid_vec_t::const_iterator getMembersEnd() const - { return mMemberIDs.end(); } - - -protected: - LLGroupRoleData() - : mMemberCount(0), mMembersNeedsSort(false) {} - - LLUUID mRoleID; - LLRoleData mRoleData; - - uuid_vec_t mMemberIDs; - S32 mMemberCount; - -private: - bool mMembersNeedsSort; -}; - -struct LLRoleMemberChange -{ - LLRoleMemberChange() : mChange(RMC_NONE) { } - LLRoleMemberChange(const LLUUID& role, const LLUUID& member, LLRoleMemberChangeType change) - : mRole(role), mMember(member), mChange(change) { } - LLRoleMemberChange(const LLRoleMemberChange& rc) - : mRole(rc.mRole), mMember(rc.mMember), mChange(rc.mChange) { } - LLUUID mRole; - LLUUID mMember; - LLRoleMemberChangeType mChange; -}; - -typedef std::pair lluuid_pair; - -struct lluuid_pair_less -{ - bool operator()(const lluuid_pair& lhs, const lluuid_pair& rhs) const - { - if (lhs.first == rhs.first) - return lhs.second < rhs.second; - else - return lhs.first < rhs.first; - } -}; - - -struct LLGroupBanData -{ - LLGroupBanData(): mBanDate() {} - ~LLGroupBanData() {} - - LLDate mBanDate; - // TODO: std:string ban_reason; -}; - - -struct LLGroupTitle -{ - std::string mTitle; - LLUUID mRoleID; - bool mSelected; -}; - -class LLGroupMgrGroupData -{ -friend class LLGroupMgr; - -public: - LLGroupMgrGroupData(const LLUUID& id); - ~LLGroupMgrGroupData(); - - const LLUUID& getID() { return mID; } - - bool getRoleData(const LLUUID& role_id, LLRoleData& role_data); - void setRoleData(const LLUUID& role_id, LLRoleData role_data); - void createRole(const LLUUID& role_id, LLRoleData role_data); - void deleteRole(const LLUUID& role_id); - bool pendingRoleChanges(); - - void addRolePower(const LLUUID& role_id, U64 power); - void removeRolePower(const LLUUID& role_id, U64 power); - U64 getRolePowers(const LLUUID& role_id); - - void removeData(); - void removeRoleData(); - void removeMemberData(); - void removeRoleMemberData(); - - bool changeRoleMember(const LLUUID& role_id, const LLUUID& member_id, LLRoleMemberChangeType rmc); - void recalcAllAgentPowers(); - void recalcAgentPowers(const LLUUID& agent_id); - - bool isMemberDataComplete() { return mMemberDataComplete; } - bool isRoleDataComplete() { return mRoleDataComplete; } - bool isRoleMemberDataComplete() { return mRoleMemberDataComplete; } - bool isGroupPropertiesDataComplete() { return mGroupPropertiesDataComplete; } - - bool isMemberDataPending() { return mMemberRequestID.notNull(); } - bool isRoleDataPending() { return mRoleDataRequestID.notNull(); } - bool isRoleMemberDataPending() { return (mRoleMembersRequestID.notNull() || mPendingRoleMemberRequest); } - bool isGroupTitlePending() { return mTitlesRequestID.notNull(); } - - bool isSingleMemberNotOwner(); - - F32 getAccessTime() const { return mAccessTime; } - void setAccessed(); - - const LLUUID& getMemberVersion() const { return mMemberVersion; } - - void clearBanList() { mBanList.clear(); } - void getBanList(const LLUUID& group_id, LLGroupBanData& ban_data); - const LLGroupBanData& getBanEntry(const LLUUID& ban_id) { return mBanList[ban_id]; } - - void createBanEntry(const LLUUID& ban_id, const LLGroupBanData& ban_data = LLGroupBanData()); - void removeBanEntry(const LLUUID& ban_id); - void banMemberById(const LLUUID& participant_uuid); - -public: - typedef std::map member_list_t; - typedef std::map role_list_t; - typedef std::map change_map_t; - typedef std::map role_data_map_t; - typedef std::map ban_list_t; - - member_list_t mMembers; - role_list_t mRoles; - change_map_t mRoleMemberChanges; - role_data_map_t mRoleChanges; - ban_list_t mBanList; - - std::vector mTitles; - - LLUUID mID; - LLUUID mOwnerRole; - std::string mName; - std::string mCharter; - bool mShowInList; - LLUUID mInsigniaID; - LLUUID mFounderID; - bool mOpenEnrollment; - S32 mMembershipFee; - bool mAllowPublish; - bool mListInProfile; - bool mMaturePublish; - bool mChanged; - S32 mMemberCount; - S32 mRoleCount; - - bool mPendingBanRequest; - LLUUID mPendingBanMemberID; - -protected: - void sendRoleChanges(); - void cancelRoleChanges(); - -private: - LLUUID mMemberRequestID; - LLUUID mRoleDataRequestID; - LLUUID mRoleMembersRequestID; - LLUUID mTitlesRequestID; - U32 mReceivedRoleMemberPairs; - - bool mMemberDataComplete; - bool mRoleDataComplete; - bool mRoleMemberDataComplete; - bool mGroupPropertiesDataComplete; - - bool mPendingRoleMemberRequest; - F32 mAccessTime; - - // Generate a new ID every time mMembers - LLUUID mMemberVersion; -}; - -struct LLRoleAction -{ - std::string mName; - std::string mDescription; - std::string mLongDescription; - U64 mPowerBit; -}; - -struct LLRoleActionSet -{ - LLRoleActionSet(); - ~LLRoleActionSet(); - LLRoleAction* mActionSetData; - std::vector mActions; -}; - -class LLGroupMgr : public LLSingleton -{ - LLSINGLETON(LLGroupMgr); - ~LLGroupMgr(); - LOG_CLASS(LLGroupMgr); - -public: - enum EBanRequestType - { - REQUEST_GET = 0, - REQUEST_POST, - REQUEST_PUT, - REQUEST_DEL - }; - - enum EBanRequestAction - { - BAN_NO_ACTION = 0, - BAN_CREATE = 1, - BAN_DELETE = 2, - BAN_UPDATE = 4 - }; - - -public: - - void addObserver(LLGroupMgrObserver* observer); - void addObserver(const LLUUID& group_id, LLParticularGroupObserver* observer); - void removeObserver(LLGroupMgrObserver* observer); - void removeObserver(const LLUUID& group_id, LLParticularGroupObserver* observer); - LLGroupMgrGroupData* getGroupData(const LLUUID& id); - - void sendGroupPropertiesRequest(const LLUUID& group_id); - void sendGroupRoleDataRequest(const LLUUID& group_id); - void sendGroupRoleMembersRequest(const LLUUID& group_id); - void sendGroupMembersRequest(const LLUUID& group_id); - void sendGroupTitlesRequest(const LLUUID& group_id); - void sendGroupTitleUpdate(const LLUUID& group_id, const LLUUID& title_role_id); - void sendUpdateGroupInfo(const LLUUID& group_id); - void sendGroupRoleMemberChanges(const LLUUID& group_id); - void sendGroupRoleChanges(const LLUUID& group_id); - - static void sendCreateGroupRequest(const std::string& name, - const std::string& charter, - U8 show_in_list, - const LLUUID& insignia, - S32 membership_fee, - bool open_enrollment, - bool allow_publish, - bool mature_publish); - - static void sendGroupMemberJoin(const LLUUID& group_id); - static void sendGroupMemberInvites(const LLUUID& group_id, std::map& role_member_pairs); - static void sendGroupMemberEjects(const LLUUID& group_id, - uuid_vec_t& member_ids); - - void sendGroupBanRequest(EBanRequestType request_type, - const LLUUID& group_id, - U32 ban_action = BAN_NO_ACTION, - const uuid_vec_t &ban_list = uuid_vec_t()); - - - void sendCapGroupMembersRequest(const LLUUID& group_id); - - void cancelGroupRoleChanges(const LLUUID& group_id); - - static void processGroupPropertiesReply(LLMessageSystem* msg, void** data); - static void processGroupMembersReply(LLMessageSystem* msg, void** data); - static void processGroupRoleDataReply(LLMessageSystem* msg, void** data); - static void processGroupRoleMembersReply(LLMessageSystem* msg, void** data); - static void processGroupTitlesReply(LLMessageSystem* msg, void** data); - static void processCreateGroupReply(LLMessageSystem* msg, void** data); - static void processJoinGroupReply(LLMessageSystem* msg, void ** data); - static void processEjectGroupMemberReply(LLMessageSystem* msg, void ** data); - static void processLeaveGroupReply(LLMessageSystem* msg, void ** data); - - static bool parseRoleActions(const std::string& xml_filename); - - std::vector mRoleActionSets; - - static void debugClearAllGroups(void*); - void clearGroups(); - void clearGroupData(const LLUUID& group_id); - -private: - void groupMembersRequestCoro(std::string url, LLUUID groupId); - void processCapGroupMembersRequest(const LLSD& content); - - void getGroupBanRequestCoro(std::string url, LLUUID groupId); - void postGroupBanRequestCoro(std::string url, LLUUID groupId, U32 action, uuid_vec_t banList, bool update); - - static void processGroupBanRequest(const LLSD& content); - - void notifyObservers(LLGroupChange gc); - void notifyObserver(const LLUUID& group_id, LLGroupChange gc); - void addGroup(LLGroupMgrGroupData* group_datap); - LLGroupMgrGroupData* createGroupData(const LLUUID &id); - bool hasPendingPropertyRequest(const LLUUID& id); - void addPendingPropertyRequest(const LLUUID& id); - - typedef std::multimap observer_multimap_t; - observer_multimap_t mObservers; - - typedef std::map group_map_t; - group_map_t mGroups; - - const U64MicrosecondsImplicit MIN_GROUP_PROPERTY_REQUEST_FREQ = 100000;//100ms between requests should be enough to avoid spamming. - typedef std::map properties_request_map_t; - properties_request_map_t mPropRequests; - - typedef std::set observer_set_t; - typedef std::map observer_map_t; - observer_map_t mParticularObservers; - - bool mMemberRequestInFlight; -}; - - -#endif +/** + * @file llgroupmgr.h + * @brief Manager for aggregating all client knowledge for specific groups + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLGROUPMGR_H +#define LL_LLGROUPMGR_H + +#include "lluuid.h" +#include "roles_constants.h" +#include +#include +#include +#include "lleventcoro.h" +#include "llcoros.h" + +// Forward Declarations +class LLMessageSystem; +class LLGroupRoleData; +class LLGroupMgr; + +enum LLGroupChange +{ + GC_PROPERTIES, + GC_MEMBER_DATA, + GC_ROLE_DATA, + GC_ROLE_MEMBER_DATA, + GC_TITLES, + GC_BANLIST, + GC_ALL +}; + +const U32 GB_MAX_BANNED_AGENTS = 500; + +class LLGroupMgrObserver +{ +public: + LLGroupMgrObserver(const LLUUID& id) : mID(id){}; + LLGroupMgrObserver() : mID(LLUUID::null){}; + virtual ~LLGroupMgrObserver(){}; + virtual void changed(LLGroupChange gc) = 0; + const LLUUID& getID() { return mID; } +protected: + LLUUID mID; +}; + +class LLParticularGroupObserver +{ +public: + virtual ~LLParticularGroupObserver(){} + virtual void changed(const LLUUID& group_id, LLGroupChange gc) = 0; +}; + +class LLGroupMemberData +{ +friend class LLGroupMgrGroupData; + +public: + typedef std::map role_list_t; + + LLGroupMemberData(const LLUUID& id, + S32 contribution, + U64 agent_powers, + const std::string& title, + const std::string& online_status, + bool is_owner); + + ~LLGroupMemberData(); + + const LLUUID& getID() const { return mID; } + S32 getContribution() const { return mContribution; } + U64 getAgentPowers() const { return mAgentPowers; } + bool isOwner() const { return mIsOwner; } + const std::string& getTitle() const { return mTitle; } + const std::string& getOnlineStatus() const { return mOnlineStatus; } + void addRole(const LLUUID& role, LLGroupRoleData* rd); + bool removeRole(const LLUUID& role); + void clearRoles() { mRolesList.clear(); }; + role_list_t::iterator roleBegin() { return mRolesList.begin(); } + role_list_t::iterator roleEnd() { return mRolesList.end(); } + + bool isInRole(const LLUUID& role_id) { return (mRolesList.find(role_id) != mRolesList.end()); } + +private: + LLUUID mID; + S32 mContribution; + U64 mAgentPowers; + std::string mTitle; + std::string mOnlineStatus; + bool mIsOwner; + role_list_t mRolesList; +}; + +struct LLRoleData +{ + LLRoleData() : mRolePowers(0), mChangeType(RC_UPDATE_NONE) { } + LLRoleData(const LLRoleData& rd) + : mRoleName(rd.mRoleName), + mRoleTitle(rd.mRoleTitle), + mRoleDescription(rd.mRoleDescription), + mRolePowers(rd.mRolePowers), + mChangeType(rd.mChangeType) { } + + std::string mRoleName; + std::string mRoleTitle; + std::string mRoleDescription; + U64 mRolePowers; + LLRoleChangeType mChangeType; +}; + +class LLGroupRoleData +{ +friend class LLGroupMgrGroupData; + +public: + LLGroupRoleData(const LLUUID& role_id, + const std::string& role_name, + const std::string& role_title, + const std::string& role_desc, + const U64 role_powers, + const S32 member_count); + + LLGroupRoleData(const LLUUID& role_id, + LLRoleData role_data, + const S32 member_count); + + ~LLGroupRoleData(); + + const LLUUID& getID() const { return mRoleID; } + + const uuid_vec_t& getRoleMembers() const { return mMemberIDs; } + S32 getMembersInRole(uuid_vec_t members, bool needs_sort = true); + S32 getTotalMembersInRole() { return mMemberCount ? mMemberCount : mMemberIDs.size(); } //FIXME: Returns 0 for Everyone role when Member list isn't yet loaded, see MAINT-5225 + + LLRoleData getRoleData() const { return mRoleData; } + void setRoleData(LLRoleData data) { mRoleData = data; } + + void addMember(const LLUUID& member); + bool removeMember(const LLUUID& member); + void clearMembers(); + + const uuid_vec_t::const_iterator getMembersBegin() const + { return mMemberIDs.begin(); } + + const uuid_vec_t::const_iterator getMembersEnd() const + { return mMemberIDs.end(); } + + +protected: + LLGroupRoleData() + : mMemberCount(0), mMembersNeedsSort(false) {} + + LLUUID mRoleID; + LLRoleData mRoleData; + + uuid_vec_t mMemberIDs; + S32 mMemberCount; + +private: + bool mMembersNeedsSort; +}; + +struct LLRoleMemberChange +{ + LLRoleMemberChange() : mChange(RMC_NONE) { } + LLRoleMemberChange(const LLUUID& role, const LLUUID& member, LLRoleMemberChangeType change) + : mRole(role), mMember(member), mChange(change) { } + LLRoleMemberChange(const LLRoleMemberChange& rc) + : mRole(rc.mRole), mMember(rc.mMember), mChange(rc.mChange) { } + LLUUID mRole; + LLUUID mMember; + LLRoleMemberChangeType mChange; +}; + +typedef std::pair lluuid_pair; + +struct lluuid_pair_less +{ + bool operator()(const lluuid_pair& lhs, const lluuid_pair& rhs) const + { + if (lhs.first == rhs.first) + return lhs.second < rhs.second; + else + return lhs.first < rhs.first; + } +}; + + +struct LLGroupBanData +{ + LLGroupBanData(): mBanDate() {} + ~LLGroupBanData() {} + + LLDate mBanDate; + // TODO: std:string ban_reason; +}; + + +struct LLGroupTitle +{ + std::string mTitle; + LLUUID mRoleID; + bool mSelected; +}; + +class LLGroupMgrGroupData +{ +friend class LLGroupMgr; + +public: + LLGroupMgrGroupData(const LLUUID& id); + ~LLGroupMgrGroupData(); + + const LLUUID& getID() { return mID; } + + bool getRoleData(const LLUUID& role_id, LLRoleData& role_data); + void setRoleData(const LLUUID& role_id, LLRoleData role_data); + void createRole(const LLUUID& role_id, LLRoleData role_data); + void deleteRole(const LLUUID& role_id); + bool pendingRoleChanges(); + + void addRolePower(const LLUUID& role_id, U64 power); + void removeRolePower(const LLUUID& role_id, U64 power); + U64 getRolePowers(const LLUUID& role_id); + + void removeData(); + void removeRoleData(); + void removeMemberData(); + void removeRoleMemberData(); + + bool changeRoleMember(const LLUUID& role_id, const LLUUID& member_id, LLRoleMemberChangeType rmc); + void recalcAllAgentPowers(); + void recalcAgentPowers(const LLUUID& agent_id); + + bool isMemberDataComplete() { return mMemberDataComplete; } + bool isRoleDataComplete() { return mRoleDataComplete; } + bool isRoleMemberDataComplete() { return mRoleMemberDataComplete; } + bool isGroupPropertiesDataComplete() { return mGroupPropertiesDataComplete; } + + bool isMemberDataPending() { return mMemberRequestID.notNull(); } + bool isRoleDataPending() { return mRoleDataRequestID.notNull(); } + bool isRoleMemberDataPending() { return (mRoleMembersRequestID.notNull() || mPendingRoleMemberRequest); } + bool isGroupTitlePending() { return mTitlesRequestID.notNull(); } + + bool isSingleMemberNotOwner(); + + F32 getAccessTime() const { return mAccessTime; } + void setAccessed(); + + const LLUUID& getMemberVersion() const { return mMemberVersion; } + + void clearBanList() { mBanList.clear(); } + void getBanList(const LLUUID& group_id, LLGroupBanData& ban_data); + const LLGroupBanData& getBanEntry(const LLUUID& ban_id) { return mBanList[ban_id]; } + + void createBanEntry(const LLUUID& ban_id, const LLGroupBanData& ban_data = LLGroupBanData()); + void removeBanEntry(const LLUUID& ban_id); + void banMemberById(const LLUUID& participant_uuid); + +public: + typedef std::map member_list_t; + typedef std::map role_list_t; + typedef std::map change_map_t; + typedef std::map role_data_map_t; + typedef std::map ban_list_t; + + member_list_t mMembers; + role_list_t mRoles; + change_map_t mRoleMemberChanges; + role_data_map_t mRoleChanges; + ban_list_t mBanList; + + std::vector mTitles; + + LLUUID mID; + LLUUID mOwnerRole; + std::string mName; + std::string mCharter; + bool mShowInList; + LLUUID mInsigniaID; + LLUUID mFounderID; + bool mOpenEnrollment; + S32 mMembershipFee; + bool mAllowPublish; + bool mListInProfile; + bool mMaturePublish; + bool mChanged; + S32 mMemberCount; + S32 mRoleCount; + + bool mPendingBanRequest; + LLUUID mPendingBanMemberID; + +protected: + void sendRoleChanges(); + void cancelRoleChanges(); + +private: + LLUUID mMemberRequestID; + LLUUID mRoleDataRequestID; + LLUUID mRoleMembersRequestID; + LLUUID mTitlesRequestID; + U32 mReceivedRoleMemberPairs; + + bool mMemberDataComplete; + bool mRoleDataComplete; + bool mRoleMemberDataComplete; + bool mGroupPropertiesDataComplete; + + bool mPendingRoleMemberRequest; + F32 mAccessTime; + + // Generate a new ID every time mMembers + LLUUID mMemberVersion; +}; + +struct LLRoleAction +{ + std::string mName; + std::string mDescription; + std::string mLongDescription; + U64 mPowerBit; +}; + +struct LLRoleActionSet +{ + LLRoleActionSet(); + ~LLRoleActionSet(); + LLRoleAction* mActionSetData; + std::vector mActions; +}; + +class LLGroupMgr : public LLSingleton +{ + LLSINGLETON(LLGroupMgr); + ~LLGroupMgr(); + LOG_CLASS(LLGroupMgr); + +public: + enum EBanRequestType + { + REQUEST_GET = 0, + REQUEST_POST, + REQUEST_PUT, + REQUEST_DEL + }; + + enum EBanRequestAction + { + BAN_NO_ACTION = 0, + BAN_CREATE = 1, + BAN_DELETE = 2, + BAN_UPDATE = 4 + }; + + +public: + + void addObserver(LLGroupMgrObserver* observer); + void addObserver(const LLUUID& group_id, LLParticularGroupObserver* observer); + void removeObserver(LLGroupMgrObserver* observer); + void removeObserver(const LLUUID& group_id, LLParticularGroupObserver* observer); + LLGroupMgrGroupData* getGroupData(const LLUUID& id); + + void sendGroupPropertiesRequest(const LLUUID& group_id); + void sendGroupRoleDataRequest(const LLUUID& group_id); + void sendGroupRoleMembersRequest(const LLUUID& group_id); + void sendGroupMembersRequest(const LLUUID& group_id); + void sendGroupTitlesRequest(const LLUUID& group_id); + void sendGroupTitleUpdate(const LLUUID& group_id, const LLUUID& title_role_id); + void sendUpdateGroupInfo(const LLUUID& group_id); + void sendGroupRoleMemberChanges(const LLUUID& group_id); + void sendGroupRoleChanges(const LLUUID& group_id); + + static void sendCreateGroupRequest(const std::string& name, + const std::string& charter, + U8 show_in_list, + const LLUUID& insignia, + S32 membership_fee, + bool open_enrollment, + bool allow_publish, + bool mature_publish); + + static void sendGroupMemberJoin(const LLUUID& group_id); + static void sendGroupMemberInvites(const LLUUID& group_id, std::map& role_member_pairs); + static void sendGroupMemberEjects(const LLUUID& group_id, + uuid_vec_t& member_ids); + + void sendGroupBanRequest(EBanRequestType request_type, + const LLUUID& group_id, + U32 ban_action = BAN_NO_ACTION, + const uuid_vec_t &ban_list = uuid_vec_t()); + + + void sendCapGroupMembersRequest(const LLUUID& group_id); + + void cancelGroupRoleChanges(const LLUUID& group_id); + + static void processGroupPropertiesReply(LLMessageSystem* msg, void** data); + static void processGroupMembersReply(LLMessageSystem* msg, void** data); + static void processGroupRoleDataReply(LLMessageSystem* msg, void** data); + static void processGroupRoleMembersReply(LLMessageSystem* msg, void** data); + static void processGroupTitlesReply(LLMessageSystem* msg, void** data); + static void processCreateGroupReply(LLMessageSystem* msg, void** data); + static void processJoinGroupReply(LLMessageSystem* msg, void ** data); + static void processEjectGroupMemberReply(LLMessageSystem* msg, void ** data); + static void processLeaveGroupReply(LLMessageSystem* msg, void ** data); + + static bool parseRoleActions(const std::string& xml_filename); + + std::vector mRoleActionSets; + + static void debugClearAllGroups(void*); + void clearGroups(); + void clearGroupData(const LLUUID& group_id); + +private: + void groupMembersRequestCoro(std::string url, LLUUID groupId); + void processCapGroupMembersRequest(const LLSD& content); + + void getGroupBanRequestCoro(std::string url, LLUUID groupId); + void postGroupBanRequestCoro(std::string url, LLUUID groupId, U32 action, uuid_vec_t banList, bool update); + + static void processGroupBanRequest(const LLSD& content); + + void notifyObservers(LLGroupChange gc); + void notifyObserver(const LLUUID& group_id, LLGroupChange gc); + void addGroup(LLGroupMgrGroupData* group_datap); + LLGroupMgrGroupData* createGroupData(const LLUUID &id); + bool hasPendingPropertyRequest(const LLUUID& id); + void addPendingPropertyRequest(const LLUUID& id); + + typedef std::multimap observer_multimap_t; + observer_multimap_t mObservers; + + typedef std::map group_map_t; + group_map_t mGroups; + + const U64MicrosecondsImplicit MIN_GROUP_PROPERTY_REQUEST_FREQ = 100000;//100ms between requests should be enough to avoid spamming. + typedef std::map properties_request_map_t; + properties_request_map_t mPropRequests; + + typedef std::set observer_set_t; + typedef std::map observer_map_t; + observer_map_t mParticularObservers; + + bool mMemberRequestInFlight; +}; + + +#endif diff --git a/indra/newview/llhints.cpp b/indra/newview/llhints.cpp index a5ed007d0b..d480274691 100644 --- a/indra/newview/llhints.cpp +++ b/indra/newview/llhints.cpp @@ -1,421 +1,421 @@ -/** - * @file llhints.cpp - * @brief Hint popups for displaying context sensitive help in a UI overlay - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#include "llviewerprecompiledheaders.h" // must be first include - -#include "llhints.h" - -#include "llbutton.h" -#include "lltextbox.h" -#include "llviewerwindow.h" -#include "llviewercontrol.h" -#include "lliconctrl.h" -#include "llsdparam.h" - -class LLHintPopup : public LLPanel -{ -public: - - typedef enum e_popup_direction - { - LEFT, - TOP, - RIGHT, - BOTTOM, - TOP_RIGHT - } EPopupDirection; - - struct PopupDirections : public LLInitParam::TypeValuesHelper - { - static void declareValues() - { - declare("left", LLHintPopup::LEFT); - declare("right", LLHintPopup::RIGHT); - declare("top", LLHintPopup::TOP); - declare("bottom", LLHintPopup::BOTTOM); - declare("top_right", LLHintPopup::TOP_RIGHT); - } - }; - - struct TargetParams : public LLInitParam::Block - { - Mandatory target; - Mandatory direction; - - TargetParams() - : target("target"), - direction("direction") - {} - }; - - struct Params : public LLInitParam::Block - { - Mandatory notification; - Optional target_params; - Optional distance; - Optional left_arrow, - up_arrow, - right_arrow, - down_arrow, - lower_left_arrow, - hint_image; - - Optional left_arrow_offset, - up_arrow_offset, - right_arrow_offset, - down_arrow_offset; - Optional fade_in_time, - fade_out_time; - - Params() - : distance("distance"), - left_arrow("left_arrow"), - up_arrow("up_arrow"), - right_arrow("right_arrow"), - down_arrow("down_arrow"), - lower_left_arrow("lower_left_arrow"), - hint_image("hint_image"), - left_arrow_offset("left_arrow_offset"), - up_arrow_offset("up_arrow_offset"), - right_arrow_offset("right_arrow_offset"), - down_arrow_offset("down_arrow_offset"), - fade_in_time("fade_in_time"), - fade_out_time("fade_out_time") - {} - }; - - LLHintPopup(const Params&); - - /*virtual*/ bool postBuild(); - - void onClickClose() - { - if (!mHidden) - { - hide(); - LLNotifications::instance().cancel(mNotification); - } - } - void draw(); - void hide() { if(!mHidden) {mHidden = true; mFadeTimer.reset();} } - -private: - LLNotificationPtr mNotification; - std::string mTarget; - EPopupDirection mDirection; - S32 mDistance; - LLUIImagePtr mArrowLeft, - mArrowUp, - mArrowRight, - mArrowDown, - mArrowDownAndLeft; - S32 mArrowLeftOffset, - mArrowUpOffset, - mArrowRightOffset, - mArrowDownOffset; - LLFrameTimer mFadeTimer; - F32 mFadeInTime, - mFadeOutTime; - bool mHidden; -}; - -static LLDefaultChildRegistry::Register r("hint_popup"); - - -LLHintPopup::LLHintPopup(const LLHintPopup::Params& p) -: mNotification(p.notification), - mDirection(TOP), - mDistance(p.distance), - mArrowLeft(p.left_arrow), - mArrowUp(p.up_arrow), - mArrowRight(p.right_arrow), - mArrowDown(p.down_arrow), - mArrowDownAndLeft(p.lower_left_arrow), - mArrowLeftOffset(p.left_arrow_offset), - mArrowUpOffset(p.up_arrow_offset), - mArrowRightOffset(p.right_arrow_offset), - mArrowDownOffset(p.down_arrow_offset), - mHidden(false), - mFadeInTime(p.fade_in_time), - mFadeOutTime(p.fade_out_time), - LLPanel(p) -{ - if (p.target_params.isProvided()) - { - mDirection = p.target_params.direction; - mTarget = p.target_params.target; - } - if (p.hint_image.isProvided()) - { - buildFromFile("panel_hint_image.xml", p); - getChild("hint_image")->setImage(p.hint_image()); - } - else - { - buildFromFile( "panel_hint.xml", p); - } -} - -bool LLHintPopup::postBuild() -{ - LLTextBox& hint_text = getChildRef("hint_text"); - hint_text.setText(mNotification->getMessage()); - - getChild("close")->setClickedCallback(boost::bind(&LLHintPopup::onClickClose, this)); - getChild("hint_title")->setText(mNotification->getLabel()); - - LLRect text_bounds = hint_text.getTextBoundingRect(); - S32 delta_height = text_bounds.getHeight() - hint_text.getRect().getHeight(); - reshape(getRect().getWidth(), getRect().getHeight() + delta_height); - hint_text.reshape(hint_text.getRect().getWidth(), hint_text.getRect().getHeight() + delta_height); -// hint_text.translate(0, -delta_height); - return true; -} - -void LLHintPopup::draw() -{ - F32 alpha = 1.f; - if (mHidden) - { - alpha = clamp_rescale(mFadeTimer.getElapsedTimeF32(), 0.f, mFadeOutTime, 1.f, 0.f); - if (alpha == 0.f) - { - die(); - return; - } - } - else - { - alpha = clamp_rescale(mFadeTimer.getElapsedTimeF32(), 0.f, mFadeInTime, 0.f, 1.f); - } - - LLIconCtrl* hint_icon = findChild("hint_image"); - - if (hint_icon) - { - LLUIImagePtr hint_image = hint_icon->getImage(); - S32 image_height = hint_image.isNull() ? 0 : hint_image->getHeight(); - S32 image_width = hint_image.isNull() ? 0 : hint_image->getWidth(); - - LLView* layout_stack = hint_icon->getParent()->getParent(); - S32 delta_height = image_height - layout_stack->getRect().getHeight(); - hint_icon->getParent()->reshape(image_width, hint_icon->getParent()->getRect().getHeight()); - layout_stack->reshape(layout_stack->getRect().getWidth(), image_height); - layout_stack->translate(0, -delta_height); - - LLRect hint_rect = getLocalRect(); - reshape(hint_rect.getWidth(), hint_rect.getHeight() + delta_height); - } - - { LLViewDrawContext context(alpha); - - if (mTarget.empty()) - { - // just draw contents, no arrow, in default position - LLPanel::draw(); - } - else - { - LLView* targetp = LLHints::getInstance()->getHintTarget(mTarget).get(); - if (!targetp) - { - // target widget is no longer valid, go away - die(); - } - else if (!targetp->isInVisibleChain()) - { - // if target is invisible, don't draw, but keep alive in case widget comes back - // but do make it so that it allows mouse events to pass through - setEnabled(false); - setMouseOpaque(false); - } - else - { - // revert back enabled and mouse opaque state in case we disabled it before - setEnabled(true); - setMouseOpaque(true); - - LLRect target_rect; - targetp->localRectToOtherView(targetp->getLocalRect(), &target_rect, getParent()); - - LLRect my_local_rect = getLocalRect(); - LLRect my_rect; - LLRect arrow_rect; - LLUIImagePtr arrow_imagep; - - switch(mDirection) - { - case LEFT: - my_rect.setCenterAndSize( target_rect.mLeft - (my_local_rect.getWidth() / 2 + mDistance), - target_rect.getCenterY(), - my_local_rect.getWidth(), - my_local_rect.getHeight()); - if (mArrowRight) - { - arrow_rect.setCenterAndSize(my_local_rect.mRight + mArrowRight->getWidth() / 2 + mArrowRightOffset, - my_local_rect.getCenterY(), - mArrowRight->getWidth(), - mArrowRight->getHeight()); - arrow_imagep = mArrowRight; - } - break; - case TOP: - my_rect.setCenterAndSize( target_rect.getCenterX(), - target_rect.mTop + (my_local_rect.getHeight() / 2 + mDistance), - my_local_rect.getWidth(), - my_local_rect.getHeight()); - if (mArrowDown) - { - arrow_rect.setCenterAndSize(my_local_rect.getCenterX(), - my_local_rect.mBottom - mArrowDown->getHeight() / 2 + mArrowDownOffset, - mArrowDown->getWidth(), - mArrowDown->getHeight()); - arrow_imagep = mArrowDown; - } - break; - case RIGHT: - my_rect.setCenterAndSize( target_rect.mRight + (my_local_rect.getWidth() / 2 + mDistance), - target_rect.getCenterY(), - my_local_rect.getWidth(), - my_local_rect.getHeight()); - if (mArrowLeft) - { - arrow_rect.setCenterAndSize(my_local_rect.mLeft - mArrowLeft->getWidth() / 2 + mArrowLeftOffset, - my_local_rect.getCenterY(), - mArrowLeft->getWidth(), - mArrowLeft->getHeight()); - arrow_imagep = mArrowLeft; - } - break; - case BOTTOM: - my_rect.setCenterAndSize( target_rect.getCenterX(), - target_rect.mBottom - (my_local_rect.getHeight() / 2 + mDistance), - my_local_rect.getWidth(), - my_local_rect.getHeight()); - if (mArrowUp) - { - arrow_rect.setCenterAndSize(my_local_rect.getCenterX(), - my_local_rect.mTop + mArrowUp->getHeight() / 2 + mArrowUpOffset, - mArrowUp->getWidth(), - mArrowUp->getHeight()); - arrow_imagep = mArrowUp; - } - break; - case TOP_RIGHT: - my_rect.setCenterAndSize( target_rect.mRight + (my_local_rect.getWidth() / 2), - target_rect.mTop + (my_local_rect.getHeight() / 2 + mDistance), - my_local_rect.getWidth(), - my_local_rect.getHeight()); - if (mArrowDownAndLeft) - { - arrow_rect.setCenterAndSize(my_local_rect.mLeft + mArrowDownAndLeft->getWidth() / 2 + mArrowLeftOffset, - my_local_rect.mBottom - mArrowDownAndLeft->getHeight() / 2 + mArrowDownOffset, - mArrowDownAndLeft->getWidth(), - mArrowDownAndLeft->getHeight()); - arrow_imagep = mArrowDownAndLeft; - } - } - setShape(my_rect); - LLPanel::draw(); - - if (arrow_imagep) arrow_imagep->draw(arrow_rect, LLColor4(1.f, 1.f, 1.f, alpha)); - } - } - } -} - - -/// LLHints - -LLHints::LLHints() -{ - LLControlVariablePtr control = gSavedSettings.getControl("EnableUIHints"); - mControlConnection = control->getSignal()->connect(boost::bind(&LLHints::showHints, this, _2)); - gViewerWindow->getHintHolder()->setVisible(control->getValue().asBoolean()); -} - -LLHints::~LLHints() -{ - mControlConnection.disconnect(); -} - -void LLHints::show(LLNotificationPtr hint) -{ - LLHintPopup::Params p(LLUICtrlFactory::getDefaultParams()); - - LLParamSDParser parser; - parser.readSD(hint->getPayload(), p, true); - p.notification = hint; - - if (p.validateBlock()) - { - LLHintPopup* popup = new LLHintPopup(p); - - mHints[hint] = popup; - - LLView* hint_holder = gViewerWindow->getHintHolder(); - if (hint_holder) - { - hint_holder->addChild(popup); - popup->centerWithin(hint_holder->getLocalRect()); - } - } -} - -void LLHints::hide(LLNotificationPtr hint) -{ - hint_map_t::iterator found_it = mHints.find(hint); - if (found_it != mHints.end()) - { - found_it->second->hide(); - mHints.erase(found_it); - } -} - -void LLHints::registerHintTarget(const std::string& name, LLHandle target) -{ - mTargetRegistry.defaultRegistrar().replace(name, target); -} - -LLHandle LLHints::getHintTarget(const std::string& name) -{ - LLHandle* handlep = mTargetRegistry.getValue(name); - if (handlep) - { - return *handlep; - } - else - { - return LLHandle(); - } -} - -void LLHints::showHints(const LLSD& show) -{ - bool visible = show.asBoolean(); - gViewerWindow->getHintHolder()->setVisible(visible); -} +/** + * @file llhints.cpp + * @brief Hint popups for displaying context sensitive help in a UI overlay + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#include "llviewerprecompiledheaders.h" // must be first include + +#include "llhints.h" + +#include "llbutton.h" +#include "lltextbox.h" +#include "llviewerwindow.h" +#include "llviewercontrol.h" +#include "lliconctrl.h" +#include "llsdparam.h" + +class LLHintPopup : public LLPanel +{ +public: + + typedef enum e_popup_direction + { + LEFT, + TOP, + RIGHT, + BOTTOM, + TOP_RIGHT + } EPopupDirection; + + struct PopupDirections : public LLInitParam::TypeValuesHelper + { + static void declareValues() + { + declare("left", LLHintPopup::LEFT); + declare("right", LLHintPopup::RIGHT); + declare("top", LLHintPopup::TOP); + declare("bottom", LLHintPopup::BOTTOM); + declare("top_right", LLHintPopup::TOP_RIGHT); + } + }; + + struct TargetParams : public LLInitParam::Block + { + Mandatory target; + Mandatory direction; + + TargetParams() + : target("target"), + direction("direction") + {} + }; + + struct Params : public LLInitParam::Block + { + Mandatory notification; + Optional target_params; + Optional distance; + Optional left_arrow, + up_arrow, + right_arrow, + down_arrow, + lower_left_arrow, + hint_image; + + Optional left_arrow_offset, + up_arrow_offset, + right_arrow_offset, + down_arrow_offset; + Optional fade_in_time, + fade_out_time; + + Params() + : distance("distance"), + left_arrow("left_arrow"), + up_arrow("up_arrow"), + right_arrow("right_arrow"), + down_arrow("down_arrow"), + lower_left_arrow("lower_left_arrow"), + hint_image("hint_image"), + left_arrow_offset("left_arrow_offset"), + up_arrow_offset("up_arrow_offset"), + right_arrow_offset("right_arrow_offset"), + down_arrow_offset("down_arrow_offset"), + fade_in_time("fade_in_time"), + fade_out_time("fade_out_time") + {} + }; + + LLHintPopup(const Params&); + + /*virtual*/ bool postBuild(); + + void onClickClose() + { + if (!mHidden) + { + hide(); + LLNotifications::instance().cancel(mNotification); + } + } + void draw(); + void hide() { if(!mHidden) {mHidden = true; mFadeTimer.reset();} } + +private: + LLNotificationPtr mNotification; + std::string mTarget; + EPopupDirection mDirection; + S32 mDistance; + LLUIImagePtr mArrowLeft, + mArrowUp, + mArrowRight, + mArrowDown, + mArrowDownAndLeft; + S32 mArrowLeftOffset, + mArrowUpOffset, + mArrowRightOffset, + mArrowDownOffset; + LLFrameTimer mFadeTimer; + F32 mFadeInTime, + mFadeOutTime; + bool mHidden; +}; + +static LLDefaultChildRegistry::Register r("hint_popup"); + + +LLHintPopup::LLHintPopup(const LLHintPopup::Params& p) +: mNotification(p.notification), + mDirection(TOP), + mDistance(p.distance), + mArrowLeft(p.left_arrow), + mArrowUp(p.up_arrow), + mArrowRight(p.right_arrow), + mArrowDown(p.down_arrow), + mArrowDownAndLeft(p.lower_left_arrow), + mArrowLeftOffset(p.left_arrow_offset), + mArrowUpOffset(p.up_arrow_offset), + mArrowRightOffset(p.right_arrow_offset), + mArrowDownOffset(p.down_arrow_offset), + mHidden(false), + mFadeInTime(p.fade_in_time), + mFadeOutTime(p.fade_out_time), + LLPanel(p) +{ + if (p.target_params.isProvided()) + { + mDirection = p.target_params.direction; + mTarget = p.target_params.target; + } + if (p.hint_image.isProvided()) + { + buildFromFile("panel_hint_image.xml", p); + getChild("hint_image")->setImage(p.hint_image()); + } + else + { + buildFromFile( "panel_hint.xml", p); + } +} + +bool LLHintPopup::postBuild() +{ + LLTextBox& hint_text = getChildRef("hint_text"); + hint_text.setText(mNotification->getMessage()); + + getChild("close")->setClickedCallback(boost::bind(&LLHintPopup::onClickClose, this)); + getChild("hint_title")->setText(mNotification->getLabel()); + + LLRect text_bounds = hint_text.getTextBoundingRect(); + S32 delta_height = text_bounds.getHeight() - hint_text.getRect().getHeight(); + reshape(getRect().getWidth(), getRect().getHeight() + delta_height); + hint_text.reshape(hint_text.getRect().getWidth(), hint_text.getRect().getHeight() + delta_height); +// hint_text.translate(0, -delta_height); + return true; +} + +void LLHintPopup::draw() +{ + F32 alpha = 1.f; + if (mHidden) + { + alpha = clamp_rescale(mFadeTimer.getElapsedTimeF32(), 0.f, mFadeOutTime, 1.f, 0.f); + if (alpha == 0.f) + { + die(); + return; + } + } + else + { + alpha = clamp_rescale(mFadeTimer.getElapsedTimeF32(), 0.f, mFadeInTime, 0.f, 1.f); + } + + LLIconCtrl* hint_icon = findChild("hint_image"); + + if (hint_icon) + { + LLUIImagePtr hint_image = hint_icon->getImage(); + S32 image_height = hint_image.isNull() ? 0 : hint_image->getHeight(); + S32 image_width = hint_image.isNull() ? 0 : hint_image->getWidth(); + + LLView* layout_stack = hint_icon->getParent()->getParent(); + S32 delta_height = image_height - layout_stack->getRect().getHeight(); + hint_icon->getParent()->reshape(image_width, hint_icon->getParent()->getRect().getHeight()); + layout_stack->reshape(layout_stack->getRect().getWidth(), image_height); + layout_stack->translate(0, -delta_height); + + LLRect hint_rect = getLocalRect(); + reshape(hint_rect.getWidth(), hint_rect.getHeight() + delta_height); + } + + { LLViewDrawContext context(alpha); + + if (mTarget.empty()) + { + // just draw contents, no arrow, in default position + LLPanel::draw(); + } + else + { + LLView* targetp = LLHints::getInstance()->getHintTarget(mTarget).get(); + if (!targetp) + { + // target widget is no longer valid, go away + die(); + } + else if (!targetp->isInVisibleChain()) + { + // if target is invisible, don't draw, but keep alive in case widget comes back + // but do make it so that it allows mouse events to pass through + setEnabled(false); + setMouseOpaque(false); + } + else + { + // revert back enabled and mouse opaque state in case we disabled it before + setEnabled(true); + setMouseOpaque(true); + + LLRect target_rect; + targetp->localRectToOtherView(targetp->getLocalRect(), &target_rect, getParent()); + + LLRect my_local_rect = getLocalRect(); + LLRect my_rect; + LLRect arrow_rect; + LLUIImagePtr arrow_imagep; + + switch(mDirection) + { + case LEFT: + my_rect.setCenterAndSize( target_rect.mLeft - (my_local_rect.getWidth() / 2 + mDistance), + target_rect.getCenterY(), + my_local_rect.getWidth(), + my_local_rect.getHeight()); + if (mArrowRight) + { + arrow_rect.setCenterAndSize(my_local_rect.mRight + mArrowRight->getWidth() / 2 + mArrowRightOffset, + my_local_rect.getCenterY(), + mArrowRight->getWidth(), + mArrowRight->getHeight()); + arrow_imagep = mArrowRight; + } + break; + case TOP: + my_rect.setCenterAndSize( target_rect.getCenterX(), + target_rect.mTop + (my_local_rect.getHeight() / 2 + mDistance), + my_local_rect.getWidth(), + my_local_rect.getHeight()); + if (mArrowDown) + { + arrow_rect.setCenterAndSize(my_local_rect.getCenterX(), + my_local_rect.mBottom - mArrowDown->getHeight() / 2 + mArrowDownOffset, + mArrowDown->getWidth(), + mArrowDown->getHeight()); + arrow_imagep = mArrowDown; + } + break; + case RIGHT: + my_rect.setCenterAndSize( target_rect.mRight + (my_local_rect.getWidth() / 2 + mDistance), + target_rect.getCenterY(), + my_local_rect.getWidth(), + my_local_rect.getHeight()); + if (mArrowLeft) + { + arrow_rect.setCenterAndSize(my_local_rect.mLeft - mArrowLeft->getWidth() / 2 + mArrowLeftOffset, + my_local_rect.getCenterY(), + mArrowLeft->getWidth(), + mArrowLeft->getHeight()); + arrow_imagep = mArrowLeft; + } + break; + case BOTTOM: + my_rect.setCenterAndSize( target_rect.getCenterX(), + target_rect.mBottom - (my_local_rect.getHeight() / 2 + mDistance), + my_local_rect.getWidth(), + my_local_rect.getHeight()); + if (mArrowUp) + { + arrow_rect.setCenterAndSize(my_local_rect.getCenterX(), + my_local_rect.mTop + mArrowUp->getHeight() / 2 + mArrowUpOffset, + mArrowUp->getWidth(), + mArrowUp->getHeight()); + arrow_imagep = mArrowUp; + } + break; + case TOP_RIGHT: + my_rect.setCenterAndSize( target_rect.mRight + (my_local_rect.getWidth() / 2), + target_rect.mTop + (my_local_rect.getHeight() / 2 + mDistance), + my_local_rect.getWidth(), + my_local_rect.getHeight()); + if (mArrowDownAndLeft) + { + arrow_rect.setCenterAndSize(my_local_rect.mLeft + mArrowDownAndLeft->getWidth() / 2 + mArrowLeftOffset, + my_local_rect.mBottom - mArrowDownAndLeft->getHeight() / 2 + mArrowDownOffset, + mArrowDownAndLeft->getWidth(), + mArrowDownAndLeft->getHeight()); + arrow_imagep = mArrowDownAndLeft; + } + } + setShape(my_rect); + LLPanel::draw(); + + if (arrow_imagep) arrow_imagep->draw(arrow_rect, LLColor4(1.f, 1.f, 1.f, alpha)); + } + } + } +} + + +/// LLHints + +LLHints::LLHints() +{ + LLControlVariablePtr control = gSavedSettings.getControl("EnableUIHints"); + mControlConnection = control->getSignal()->connect(boost::bind(&LLHints::showHints, this, _2)); + gViewerWindow->getHintHolder()->setVisible(control->getValue().asBoolean()); +} + +LLHints::~LLHints() +{ + mControlConnection.disconnect(); +} + +void LLHints::show(LLNotificationPtr hint) +{ + LLHintPopup::Params p(LLUICtrlFactory::getDefaultParams()); + + LLParamSDParser parser; + parser.readSD(hint->getPayload(), p, true); + p.notification = hint; + + if (p.validateBlock()) + { + LLHintPopup* popup = new LLHintPopup(p); + + mHints[hint] = popup; + + LLView* hint_holder = gViewerWindow->getHintHolder(); + if (hint_holder) + { + hint_holder->addChild(popup); + popup->centerWithin(hint_holder->getLocalRect()); + } + } +} + +void LLHints::hide(LLNotificationPtr hint) +{ + hint_map_t::iterator found_it = mHints.find(hint); + if (found_it != mHints.end()) + { + found_it->second->hide(); + mHints.erase(found_it); + } +} + +void LLHints::registerHintTarget(const std::string& name, LLHandle target) +{ + mTargetRegistry.defaultRegistrar().replace(name, target); +} + +LLHandle LLHints::getHintTarget(const std::string& name) +{ + LLHandle* handlep = mTargetRegistry.getValue(name); + if (handlep) + { + return *handlep; + } + else + { + return LLHandle(); + } +} + +void LLHints::showHints(const LLSD& show) +{ + bool visible = show.asBoolean(); + gViewerWindow->getHintHolder()->setVisible(visible); +} diff --git a/indra/newview/llhudeffect.cpp b/indra/newview/llhudeffect.cpp index 0187819a0b..4d6636aa4c 100644 --- a/indra/newview/llhudeffect.cpp +++ b/indra/newview/llhudeffect.cpp @@ -1,133 +1,133 @@ -/** - * @file llhudeffect.cpp - * @brief LLHUDEffect class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llhudeffect.h" - -#include "message.h" -#include "llgl.h" -#include "llagent.h" -#include "llrendersphere.h" - -#include "llviewerobjectlist.h" -#include "lldrawable.h" - -LLHUDEffect::LLHUDEffect(const U8 type) -: LLHUDObject(type), - mID(), - mDuration(1.f), - mColor() -{ - mNeedsSendToSim = false; - mOriginatedHere = false; - mDead = false; -} - -LLHUDEffect::~LLHUDEffect() -{ -} - - -void LLHUDEffect::packData(LLMessageSystem *mesgsys) -{ - mesgsys->addUUIDFast(_PREHASH_ID, mID); - mesgsys->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - mesgsys->addU8Fast(_PREHASH_Type, mType); - mesgsys->addF32Fast(_PREHASH_Duration, mDuration); - mesgsys->addBinaryData(_PREHASH_Color, mColor.mV, 4); -} - -void LLHUDEffect::unpackData(LLMessageSystem *mesgsys, S32 blocknum) -{ - mesgsys->getUUIDFast(_PREHASH_Effect, _PREHASH_ID, mID, blocknum); - mesgsys->getU8Fast(_PREHASH_Effect, _PREHASH_Type, mType, blocknum); - mesgsys->getF32Fast(_PREHASH_Effect, _PREHASH_Duration, mDuration, blocknum); - mesgsys->getBinaryDataFast(_PREHASH_Effect,_PREHASH_Color, mColor.mV, 4, blocknum); -} - -void LLHUDEffect::render() -{ - LL_ERRS() << "Never call this!" << LL_ENDL; -} - -void LLHUDEffect::setID(const LLUUID &id) -{ - mID = id; -} - -const LLUUID &LLHUDEffect::getID() const -{ - return mID; -} - -void LLHUDEffect::setColor(const LLColor4U &color) -{ - mColor = color; -} - -void LLHUDEffect::setDuration(const F32 duration) -{ - mDuration = duration; -} - -void LLHUDEffect::setNeedsSendToSim(const bool send_to_sim) -{ - mNeedsSendToSim = send_to_sim; -} - -bool LLHUDEffect::getNeedsSendToSim() const -{ - return mNeedsSendToSim; -} - - -void LLHUDEffect::setOriginatedHere(const bool orig_here) -{ - mOriginatedHere = orig_here; -} - -bool LLHUDEffect::getOriginatedHere() const -{ - return mOriginatedHere; -} - -//static -void LLHUDEffect::getIDType(LLMessageSystem *mesgsys, S32 blocknum, LLUUID &id, U8 &type) -{ - mesgsys->getUUIDFast(_PREHASH_Effect, _PREHASH_ID, id, blocknum); - mesgsys->getU8Fast(_PREHASH_Effect, _PREHASH_Type, type, blocknum); -} - -bool LLHUDEffect::isDead() const -{ - return mDead; -} - -void LLHUDEffect::update() -{ - // do nothing -} +/** + * @file llhudeffect.cpp + * @brief LLHUDEffect class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llhudeffect.h" + +#include "message.h" +#include "llgl.h" +#include "llagent.h" +#include "llrendersphere.h" + +#include "llviewerobjectlist.h" +#include "lldrawable.h" + +LLHUDEffect::LLHUDEffect(const U8 type) +: LLHUDObject(type), + mID(), + mDuration(1.f), + mColor() +{ + mNeedsSendToSim = false; + mOriginatedHere = false; + mDead = false; +} + +LLHUDEffect::~LLHUDEffect() +{ +} + + +void LLHUDEffect::packData(LLMessageSystem *mesgsys) +{ + mesgsys->addUUIDFast(_PREHASH_ID, mID); + mesgsys->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + mesgsys->addU8Fast(_PREHASH_Type, mType); + mesgsys->addF32Fast(_PREHASH_Duration, mDuration); + mesgsys->addBinaryData(_PREHASH_Color, mColor.mV, 4); +} + +void LLHUDEffect::unpackData(LLMessageSystem *mesgsys, S32 blocknum) +{ + mesgsys->getUUIDFast(_PREHASH_Effect, _PREHASH_ID, mID, blocknum); + mesgsys->getU8Fast(_PREHASH_Effect, _PREHASH_Type, mType, blocknum); + mesgsys->getF32Fast(_PREHASH_Effect, _PREHASH_Duration, mDuration, blocknum); + mesgsys->getBinaryDataFast(_PREHASH_Effect,_PREHASH_Color, mColor.mV, 4, blocknum); +} + +void LLHUDEffect::render() +{ + LL_ERRS() << "Never call this!" << LL_ENDL; +} + +void LLHUDEffect::setID(const LLUUID &id) +{ + mID = id; +} + +const LLUUID &LLHUDEffect::getID() const +{ + return mID; +} + +void LLHUDEffect::setColor(const LLColor4U &color) +{ + mColor = color; +} + +void LLHUDEffect::setDuration(const F32 duration) +{ + mDuration = duration; +} + +void LLHUDEffect::setNeedsSendToSim(const bool send_to_sim) +{ + mNeedsSendToSim = send_to_sim; +} + +bool LLHUDEffect::getNeedsSendToSim() const +{ + return mNeedsSendToSim; +} + + +void LLHUDEffect::setOriginatedHere(const bool orig_here) +{ + mOriginatedHere = orig_here; +} + +bool LLHUDEffect::getOriginatedHere() const +{ + return mOriginatedHere; +} + +//static +void LLHUDEffect::getIDType(LLMessageSystem *mesgsys, S32 blocknum, LLUUID &id, U8 &type) +{ + mesgsys->getUUIDFast(_PREHASH_Effect, _PREHASH_ID, id, blocknum); + mesgsys->getU8Fast(_PREHASH_Effect, _PREHASH_Type, type, blocknum); +} + +bool LLHUDEffect::isDead() const +{ + return mDead; +} + +void LLHUDEffect::update() +{ + // do nothing +} diff --git a/indra/newview/llhudeffect.h b/indra/newview/llhudeffect.h index 1c93ed0ee1..cace745de6 100644 --- a/indra/newview/llhudeffect.h +++ b/indra/newview/llhudeffect.h @@ -1,77 +1,77 @@ -/** - * @file llhudeffect.h - * @brief LLHUDEffect class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLHUDEFFECT_H -#define LL_LLHUDEFFECT_H - -#include "llhudobject.h" - -#include "lluuid.h" -#include "v4coloru.h" - -const F32 LL_HUD_DUR_SHORT = 1.f; - -class LLMessageSystem; - - -class LLHUDEffect : public LLHUDObject -{ -public: - void setNeedsSendToSim(const bool send_to_sim); - bool getNeedsSendToSim() const; - void setOriginatedHere(const bool orig_here); - bool getOriginatedHere() const; - - void setDuration(const F32 duration); - void setColor(const LLColor4U &color); - void setID(const LLUUID &id); - const LLUUID &getID() const; - - bool isDead() const; - - friend class LLHUDManager; -protected: - LLHUDEffect(const U8 type); - ~LLHUDEffect(); - - /*virtual*/ void render(); - - virtual void packData(LLMessageSystem *mesgsys); - virtual void unpackData(LLMessageSystem *mesgsys, S32 blocknum); - virtual void update(); - - static void getIDType(LLMessageSystem *mesgsys, S32 blocknum, LLUUID &uuid, U8 &type); - -protected: - LLUUID mID; - F32 mDuration; - LLColor4U mColor; - - bool mNeedsSendToSim; - bool mOriginatedHere; -}; - -#endif // LL_LLHUDEFFECT_H +/** + * @file llhudeffect.h + * @brief LLHUDEffect class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLHUDEFFECT_H +#define LL_LLHUDEFFECT_H + +#include "llhudobject.h" + +#include "lluuid.h" +#include "v4coloru.h" + +const F32 LL_HUD_DUR_SHORT = 1.f; + +class LLMessageSystem; + + +class LLHUDEffect : public LLHUDObject +{ +public: + void setNeedsSendToSim(const bool send_to_sim); + bool getNeedsSendToSim() const; + void setOriginatedHere(const bool orig_here); + bool getOriginatedHere() const; + + void setDuration(const F32 duration); + void setColor(const LLColor4U &color); + void setID(const LLUUID &id); + const LLUUID &getID() const; + + bool isDead() const; + + friend class LLHUDManager; +protected: + LLHUDEffect(const U8 type); + ~LLHUDEffect(); + + /*virtual*/ void render(); + + virtual void packData(LLMessageSystem *mesgsys); + virtual void unpackData(LLMessageSystem *mesgsys, S32 blocknum); + virtual void update(); + + static void getIDType(LLMessageSystem *mesgsys, S32 blocknum, LLUUID &uuid, U8 &type); + +protected: + LLUUID mID; + F32 mDuration; + LLColor4U mColor; + + bool mNeedsSendToSim; + bool mOriginatedHere; +}; + +#endif // LL_LLHUDEFFECT_H diff --git a/indra/newview/llhudeffectbeam.cpp b/indra/newview/llhudeffectbeam.cpp index 8987153a67..74d0e0af0b 100644 --- a/indra/newview/llhudeffectbeam.cpp +++ b/indra/newview/llhudeffectbeam.cpp @@ -1,348 +1,348 @@ -/** - * @file llhudeffectbeam.cpp - * @brief LLHUDEffectBeam class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llhudeffectbeam.h" -#include "message.h" - -#include "llviewerobjectlist.h" - -#include "llagent.h" -#include "lldrawable.h" -#include "llfontgl.h" -#include "llgl.h" -#include "llglheaders.h" -#include "llhudrender.h" -#include "llrendersphere.h" -#include "llviewercamera.h" -#include "llvoavatar.h" -#include "llviewercontrol.h" - -const F32 BEAM_SPACING = 0.075f; - -LLHUDEffectBeam::LLHUDEffectBeam(const U8 type) : LLHUDEffect(type) -{ - mKillTime = mDuration; - - // Initialize all of these to defaults - S32 i; - for (i = 0; i < NUM_POINTS; i++) - { - mInterp[i].setStartTime(BEAM_SPACING*i); - mInterp[i].setEndTime(BEAM_SPACING*NUM_POINTS + BEAM_SPACING*i); - mInterp[i].start(); - mInterpFade[i].setStartTime(BEAM_SPACING*NUM_POINTS + BEAM_SPACING*i - 0.5f*NUM_POINTS*BEAM_SPACING); - mInterpFade[i].setEndTime(BEAM_SPACING*NUM_POINTS + BEAM_SPACING*i); - mInterpFade[i].setStartVal(1.f); - mInterpFade[i].setEndVal(0.f); - } - - // Setup default timeouts and fade animations. - F32 fade_length; - fade_length = llmin(0.5f, mDuration); - mFadeInterp.setStartTime(mKillTime - fade_length); - mFadeInterp.setEndTime(mKillTime); - mFadeInterp.setStartVal(1.f); - mFadeInterp.setEndVal(0.f); -} - -LLHUDEffectBeam::~LLHUDEffectBeam() -{ -} - -void LLHUDEffectBeam::packData(LLMessageSystem *mesgsys) -{ - if (!mSourceObject) - { - LL_WARNS() << "Missing source object!" << LL_ENDL; - } - - // Pack the default data - LLHUDEffect::packData(mesgsys); - - // Pack the type-specific data. Uses a fun packed binary format. Whee! - // 16 + 24 + 1 = 41 - U8 packed_data[41]; - memset(packed_data, 0, 41); - if (mSourceObject) - { - htolememcpy(packed_data, mSourceObject->mID.mData, MVT_LLUUID, 16); - } - - if (mTargetObject) - { - packed_data[16] = 1; - } - else - { - packed_data[16] = 0; - } - - if (mTargetObject) - { - htolememcpy(&(packed_data[17]), mTargetObject->mID.mData, MVT_LLUUID, 16); - } - else - { - htolememcpy(&(packed_data[17]), mTargetPos.mdV, MVT_LLVector3d, 24); - } - mesgsys->addBinaryDataFast(_PREHASH_TypeData, packed_data, 41); -} - -void LLHUDEffectBeam::unpackData(LLMessageSystem *mesgsys, S32 blocknum) -{ - LL_ERRS() << "Got beam!" << LL_ENDL; - bool use_target_object; - LLVector3d new_target; - U8 packed_data[41]; - - LLHUDEffect::unpackData(mesgsys, blocknum); - LLUUID source_id; - LLUUID target_id; - S32 size = mesgsys->getSizeFast(_PREHASH_Effect, blocknum, _PREHASH_TypeData); - if (size != 41) - { - LL_WARNS() << "Beam effect with bad size " << size << LL_ENDL; - return; - } - mesgsys->getBinaryDataFast(_PREHASH_Effect, _PREHASH_TypeData, packed_data, 41, blocknum); - - htolememcpy(source_id.mData, packed_data, MVT_LLUUID, 16); - - LLViewerObject *objp = gObjectList.findObject(source_id); - if (objp) - { - setSourceObject(objp); - } - - use_target_object = packed_data[16]; - - if (use_target_object) - { - htolememcpy(target_id.mData, &packed_data[17], MVT_LLUUID, 16); - - LLViewerObject *objp = gObjectList.findObject(target_id); - if (objp) - { - setTargetObject(objp); - } - } - else - { - htolememcpy(new_target.mdV, &(packed_data[17]), MVT_LLVector3d, 24); - setTargetPos(new_target); - } - - // We've received an update for the effect, update the various timeouts - // and fade animations. - mKillTime = mTimer.getElapsedTimeF32() + mDuration; - F32 fade_length; - fade_length = llmin(0.5f, mDuration); - mFadeInterp.setStartTime(mKillTime - fade_length); - mFadeInterp.setEndTime(mKillTime); - mFadeInterp.setStartVal(1.f); - mFadeInterp.setEndVal(0.f); -} - -void LLHUDEffectBeam::setSourceObject(LLViewerObject *objp) -{ - if (objp->isDead()) - { - LL_WARNS() << "HUDEffectBeam: Source object is dead!" << LL_ENDL; - mSourceObject = NULL; - return; - } - - if (mSourceObject == objp) - { - return; - } - - mSourceObject = objp; - if (mSourceObject) - { - S32 i; - for (i = 0; i < NUM_POINTS; i++) - { - if (mSourceObject->isAvatar()) - { - LLViewerObject *objp = mSourceObject; - LLVOAvatar *avatarp = (LLVOAvatar *)objp; - LLVector3d hand_pos_global = gAgent.getPosGlobalFromAgent(avatarp->mWristLeftp->getWorldPosition()); - mInterp[i].setStartVal(hand_pos_global); - mInterp[i].start(); - } - else - { - mInterp[i].setStartVal(mSourceObject->getPositionGlobal()); - mInterp[i].start(); - } - } - } -} - - -void LLHUDEffectBeam::setTargetObject(LLViewerObject *objp) -{ - if (mTargetObject->isDead()) - { - LL_WARNS() << "HUDEffectBeam: Target object is dead!" << LL_ENDL; - } - - mTargetObject = objp; -} - -void LLHUDEffectBeam::setTargetPos(const LLVector3d &pos_global) -{ - mTargetPos = pos_global; - mTargetObject = NULL; -} - -void LLHUDEffectBeam::render() -{ - if (!mSourceObject) - { - markDead(); - return; - } - if (mSourceObject->isDead()) - { - markDead(); - return; - } - - F32 time = mTimer.getElapsedTimeF32(); - - // Kill us if our time is over... - if (mKillTime < time) - { - markDead(); - return; - } - - LLGLSPipelineAlpha gls_pipeline_alpha; - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - - // Interpolate the global fade alpha - mFadeInterp.update(time); - - if (mTargetObject.notNull() && mTargetObject->mDrawable.notNull()) - { - // use viewer object position on freshly created objects - if (mTargetObject->mDrawable->getGeneration() == -1) - { - mTargetPos = mTargetObject->getPositionGlobal(); - } - // otherwise use drawable - else - { - mTargetPos = gAgent.getPosGlobalFromAgent(mTargetObject->mDrawable->getPositionAgent()); - } - } - - - // Init the color of the particles - LLColor4U coloru = mColor; - - // Draw the particles - S32 i; - for (i = 0; i < NUM_POINTS; i++) - { - mInterp[i].update(time); - if (!mInterp[i].isActive()) - { - continue; - } - mInterpFade[i].update(time); - - if (mInterp[i].isDone()) - { - // Reinitialize the particle when the particle has finished its animation. - setupParticle(i); - } - - F32 frac = mInterp[i].getCurFrac(); - F32 scale = 0.025f + fabs(0.05f*sin(2.f*F_PI*(frac - time))); - scale *= mInterpFade[i].getCurVal(); - - LLVector3 pos_agent = gAgent.getPosAgentFromGlobal(mInterp[i].getCurVal()); - - F32 alpha = mFadeInterp.getCurVal()*mColor.mV[3]; - alpha *= mInterpFade[i].getCurVal(); - coloru.mV[3] = (U8)alpha; - gGL.color4ubv(coloru.mV); - - gGL.pushMatrix(); - gGL.translatef(pos_agent.mV[0], pos_agent.mV[1], pos_agent.mV[2]); - gGL.scalef(scale, scale, scale); - gSphere.render(); - gGL.popMatrix(); - } -} - -void LLHUDEffectBeam::renderForTimer() -{ - render(); -} - -void LLHUDEffectBeam::setupParticle(const S32 i) -{ - LLVector3d start_pos_global; - if (mSourceObject->getPCode() == LL_PCODE_LEGACY_AVATAR) - { - LLViewerObject *objp = mSourceObject; - LLVOAvatar *avatarp = (LLVOAvatar *)objp; - start_pos_global = gAgent.getPosGlobalFromAgent(avatarp->mWristLeftp->getWorldPosition()); - } - else - { - start_pos_global = mSourceObject->getPositionGlobal(); - } - - // Generate a random offset for the target point. - const F32 SCALE = 0.5f; - F32 x, y, z; - x = ll_frand(SCALE) - 0.5f*SCALE; - y = ll_frand(SCALE) - 0.5f*SCALE; - z = ll_frand(SCALE) - 0.5f*SCALE; - - LLVector3d target_pos_global(mTargetPos); - target_pos_global += LLVector3d(x, y, z); - - mInterp[i].setStartTime(mInterp[i].getEndTime()); - mInterp[i].setEndTime(mInterp[i].getStartTime() + BEAM_SPACING*NUM_POINTS); - mInterp[i].setStartVal(start_pos_global); - mInterp[i].setEndVal(target_pos_global); - mInterp[i].start(); - - - // Setup the interpolator that fades out the alpha. - mInterpFade[i].setStartTime(mInterp[i].getStartTime() + BEAM_SPACING*NUM_POINTS - 0.5f*NUM_POINTS*BEAM_SPACING); - mInterpFade[i].setEndTime(mInterp[i].getStartTime() + BEAM_SPACING*NUM_POINTS - 0.05f); - mInterpFade[i].start(); -} +/** + * @file llhudeffectbeam.cpp + * @brief LLHUDEffectBeam class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llhudeffectbeam.h" +#include "message.h" + +#include "llviewerobjectlist.h" + +#include "llagent.h" +#include "lldrawable.h" +#include "llfontgl.h" +#include "llgl.h" +#include "llglheaders.h" +#include "llhudrender.h" +#include "llrendersphere.h" +#include "llviewercamera.h" +#include "llvoavatar.h" +#include "llviewercontrol.h" + +const F32 BEAM_SPACING = 0.075f; + +LLHUDEffectBeam::LLHUDEffectBeam(const U8 type) : LLHUDEffect(type) +{ + mKillTime = mDuration; + + // Initialize all of these to defaults + S32 i; + for (i = 0; i < NUM_POINTS; i++) + { + mInterp[i].setStartTime(BEAM_SPACING*i); + mInterp[i].setEndTime(BEAM_SPACING*NUM_POINTS + BEAM_SPACING*i); + mInterp[i].start(); + mInterpFade[i].setStartTime(BEAM_SPACING*NUM_POINTS + BEAM_SPACING*i - 0.5f*NUM_POINTS*BEAM_SPACING); + mInterpFade[i].setEndTime(BEAM_SPACING*NUM_POINTS + BEAM_SPACING*i); + mInterpFade[i].setStartVal(1.f); + mInterpFade[i].setEndVal(0.f); + } + + // Setup default timeouts and fade animations. + F32 fade_length; + fade_length = llmin(0.5f, mDuration); + mFadeInterp.setStartTime(mKillTime - fade_length); + mFadeInterp.setEndTime(mKillTime); + mFadeInterp.setStartVal(1.f); + mFadeInterp.setEndVal(0.f); +} + +LLHUDEffectBeam::~LLHUDEffectBeam() +{ +} + +void LLHUDEffectBeam::packData(LLMessageSystem *mesgsys) +{ + if (!mSourceObject) + { + LL_WARNS() << "Missing source object!" << LL_ENDL; + } + + // Pack the default data + LLHUDEffect::packData(mesgsys); + + // Pack the type-specific data. Uses a fun packed binary format. Whee! + // 16 + 24 + 1 = 41 + U8 packed_data[41]; + memset(packed_data, 0, 41); + if (mSourceObject) + { + htolememcpy(packed_data, mSourceObject->mID.mData, MVT_LLUUID, 16); + } + + if (mTargetObject) + { + packed_data[16] = 1; + } + else + { + packed_data[16] = 0; + } + + if (mTargetObject) + { + htolememcpy(&(packed_data[17]), mTargetObject->mID.mData, MVT_LLUUID, 16); + } + else + { + htolememcpy(&(packed_data[17]), mTargetPos.mdV, MVT_LLVector3d, 24); + } + mesgsys->addBinaryDataFast(_PREHASH_TypeData, packed_data, 41); +} + +void LLHUDEffectBeam::unpackData(LLMessageSystem *mesgsys, S32 blocknum) +{ + LL_ERRS() << "Got beam!" << LL_ENDL; + bool use_target_object; + LLVector3d new_target; + U8 packed_data[41]; + + LLHUDEffect::unpackData(mesgsys, blocknum); + LLUUID source_id; + LLUUID target_id; + S32 size = mesgsys->getSizeFast(_PREHASH_Effect, blocknum, _PREHASH_TypeData); + if (size != 41) + { + LL_WARNS() << "Beam effect with bad size " << size << LL_ENDL; + return; + } + mesgsys->getBinaryDataFast(_PREHASH_Effect, _PREHASH_TypeData, packed_data, 41, blocknum); + + htolememcpy(source_id.mData, packed_data, MVT_LLUUID, 16); + + LLViewerObject *objp = gObjectList.findObject(source_id); + if (objp) + { + setSourceObject(objp); + } + + use_target_object = packed_data[16]; + + if (use_target_object) + { + htolememcpy(target_id.mData, &packed_data[17], MVT_LLUUID, 16); + + LLViewerObject *objp = gObjectList.findObject(target_id); + if (objp) + { + setTargetObject(objp); + } + } + else + { + htolememcpy(new_target.mdV, &(packed_data[17]), MVT_LLVector3d, 24); + setTargetPos(new_target); + } + + // We've received an update for the effect, update the various timeouts + // and fade animations. + mKillTime = mTimer.getElapsedTimeF32() + mDuration; + F32 fade_length; + fade_length = llmin(0.5f, mDuration); + mFadeInterp.setStartTime(mKillTime - fade_length); + mFadeInterp.setEndTime(mKillTime); + mFadeInterp.setStartVal(1.f); + mFadeInterp.setEndVal(0.f); +} + +void LLHUDEffectBeam::setSourceObject(LLViewerObject *objp) +{ + if (objp->isDead()) + { + LL_WARNS() << "HUDEffectBeam: Source object is dead!" << LL_ENDL; + mSourceObject = NULL; + return; + } + + if (mSourceObject == objp) + { + return; + } + + mSourceObject = objp; + if (mSourceObject) + { + S32 i; + for (i = 0; i < NUM_POINTS; i++) + { + if (mSourceObject->isAvatar()) + { + LLViewerObject *objp = mSourceObject; + LLVOAvatar *avatarp = (LLVOAvatar *)objp; + LLVector3d hand_pos_global = gAgent.getPosGlobalFromAgent(avatarp->mWristLeftp->getWorldPosition()); + mInterp[i].setStartVal(hand_pos_global); + mInterp[i].start(); + } + else + { + mInterp[i].setStartVal(mSourceObject->getPositionGlobal()); + mInterp[i].start(); + } + } + } +} + + +void LLHUDEffectBeam::setTargetObject(LLViewerObject *objp) +{ + if (mTargetObject->isDead()) + { + LL_WARNS() << "HUDEffectBeam: Target object is dead!" << LL_ENDL; + } + + mTargetObject = objp; +} + +void LLHUDEffectBeam::setTargetPos(const LLVector3d &pos_global) +{ + mTargetPos = pos_global; + mTargetObject = NULL; +} + +void LLHUDEffectBeam::render() +{ + if (!mSourceObject) + { + markDead(); + return; + } + if (mSourceObject->isDead()) + { + markDead(); + return; + } + + F32 time = mTimer.getElapsedTimeF32(); + + // Kill us if our time is over... + if (mKillTime < time) + { + markDead(); + return; + } + + LLGLSPipelineAlpha gls_pipeline_alpha; + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + + // Interpolate the global fade alpha + mFadeInterp.update(time); + + if (mTargetObject.notNull() && mTargetObject->mDrawable.notNull()) + { + // use viewer object position on freshly created objects + if (mTargetObject->mDrawable->getGeneration() == -1) + { + mTargetPos = mTargetObject->getPositionGlobal(); + } + // otherwise use drawable + else + { + mTargetPos = gAgent.getPosGlobalFromAgent(mTargetObject->mDrawable->getPositionAgent()); + } + } + + + // Init the color of the particles + LLColor4U coloru = mColor; + + // Draw the particles + S32 i; + for (i = 0; i < NUM_POINTS; i++) + { + mInterp[i].update(time); + if (!mInterp[i].isActive()) + { + continue; + } + mInterpFade[i].update(time); + + if (mInterp[i].isDone()) + { + // Reinitialize the particle when the particle has finished its animation. + setupParticle(i); + } + + F32 frac = mInterp[i].getCurFrac(); + F32 scale = 0.025f + fabs(0.05f*sin(2.f*F_PI*(frac - time))); + scale *= mInterpFade[i].getCurVal(); + + LLVector3 pos_agent = gAgent.getPosAgentFromGlobal(mInterp[i].getCurVal()); + + F32 alpha = mFadeInterp.getCurVal()*mColor.mV[3]; + alpha *= mInterpFade[i].getCurVal(); + coloru.mV[3] = (U8)alpha; + gGL.color4ubv(coloru.mV); + + gGL.pushMatrix(); + gGL.translatef(pos_agent.mV[0], pos_agent.mV[1], pos_agent.mV[2]); + gGL.scalef(scale, scale, scale); + gSphere.render(); + gGL.popMatrix(); + } +} + +void LLHUDEffectBeam::renderForTimer() +{ + render(); +} + +void LLHUDEffectBeam::setupParticle(const S32 i) +{ + LLVector3d start_pos_global; + if (mSourceObject->getPCode() == LL_PCODE_LEGACY_AVATAR) + { + LLViewerObject *objp = mSourceObject; + LLVOAvatar *avatarp = (LLVOAvatar *)objp; + start_pos_global = gAgent.getPosGlobalFromAgent(avatarp->mWristLeftp->getWorldPosition()); + } + else + { + start_pos_global = mSourceObject->getPositionGlobal(); + } + + // Generate a random offset for the target point. + const F32 SCALE = 0.5f; + F32 x, y, z; + x = ll_frand(SCALE) - 0.5f*SCALE; + y = ll_frand(SCALE) - 0.5f*SCALE; + z = ll_frand(SCALE) - 0.5f*SCALE; + + LLVector3d target_pos_global(mTargetPos); + target_pos_global += LLVector3d(x, y, z); + + mInterp[i].setStartTime(mInterp[i].getEndTime()); + mInterp[i].setEndTime(mInterp[i].getStartTime() + BEAM_SPACING*NUM_POINTS); + mInterp[i].setStartVal(start_pos_global); + mInterp[i].setEndVal(target_pos_global); + mInterp[i].start(); + + + // Setup the interpolator that fades out the alpha. + mInterpFade[i].setStartTime(mInterp[i].getStartTime() + BEAM_SPACING*NUM_POINTS - 0.5f*NUM_POINTS*BEAM_SPACING); + mInterpFade[i].setEndTime(mInterp[i].getStartTime() + BEAM_SPACING*NUM_POINTS - 0.05f); + mInterpFade[i].start(); +} diff --git a/indra/newview/llhudeffectlookat.cpp b/indra/newview/llhudeffectlookat.cpp index 9e538be2cb..c8b7e00776 100644 --- a/indra/newview/llhudeffectlookat.cpp +++ b/indra/newview/llhudeffectlookat.cpp @@ -1,671 +1,671 @@ -/** - * @file llhudeffectlookat.cpp - * @brief LLHUDEffectLookAt class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llhudeffectlookat.h" - -#include "llrender.h" - -#include "message.h" -#include "llagent.h" -#include "llagentcamera.h" -#include "llvoavatar.h" -#include "lldrawable.h" -#include "llviewerobjectlist.h" -#include "llrendersphere.h" -#include "llselectmgr.h" -#include "llglheaders.h" -#include "llxmltree.h" - - -bool LLHUDEffectLookAt::sDebugLookAt = false; - -// packet layout -const S32 SOURCE_AVATAR = 0; -const S32 TARGET_OBJECT = 16; -const S32 TARGET_POS = 32; -const S32 LOOKAT_TYPE = 56; -const S32 PKT_SIZE = 57; - -// throttle -const F32 MAX_SENDS_PER_SEC = 4.f; - -const F32 MIN_DELTAPOS_FOR_UPDATE_SQUARED = 0.05f * 0.05f; -const F32 MIN_TARGET_OFFSET_SQUARED = 0.0001f; - - -// can't use actual F32_MAX, because we add this to the current frametime -const F32 MAX_TIMEOUT = F32_MAX / 2.f; - -/** - * Simple data class holding values for a particular type of attention. - */ -class LLAttention -{ -public: - LLAttention() - : mTimeout(0.f), - mPriority(0.f) - {} - LLAttention(F32 timeout, F32 priority, const std::string& name, LLColor3 color) : - mTimeout(timeout), mPriority(priority), mName(name), mColor(color) - { - } - F32 mTimeout, mPriority; - std::string mName; - LLColor3 mColor; -}; - -/** - * Simple data class holding a list of attentions, one for every type. - */ -class LLAttentionSet -{ -public: - LLAttentionSet(const LLAttention attentions[]) - { - for(int i=0; igetAttributeString("name", str); - LLAttentionSet& attentions = (str.compare("Masculine") == 0) ? gBoyAttentions : gGirlAttentions; - for (LLXmlTreeNode* attention_node = gender->getChildByName( "param" ); - attention_node; - attention_node = gender->getNextNamedChild()) - { - attention_node->getAttributeString("attention", str); - LLAttention* attention; - if (str == "idle") attention = &attentions[LOOKAT_TARGET_IDLE]; - else if(str == "auto_listen") attention = &attentions[LOOKAT_TARGET_AUTO_LISTEN]; - else if(str == "freelook") attention = &attentions[LOOKAT_TARGET_FREELOOK]; - else if(str == "respond") attention = &attentions[LOOKAT_TARGET_RESPOND]; - else if(str == "hover") attention = &attentions[LOOKAT_TARGET_HOVER]; - else if(str == "conversation") attention = &attentions[LOOKAT_TARGET_CONVERSATION]; - else if(str == "select") attention = &attentions[LOOKAT_TARGET_SELECT]; - else if(str == "focus") attention = &attentions[LOOKAT_TARGET_FOCUS]; - else if(str == "mouselook") attention = &attentions[LOOKAT_TARGET_MOUSELOOK]; - else return false; - - F32 priority, timeout; - attention_node->getAttributeF32("priority", priority); - attention_node->getAttributeF32("timeout", timeout); - if(timeout < 0) timeout = MAX_TIMEOUT; - attention->mPriority = priority; - attention->mTimeout = timeout; - } - return true; -} - -static bool loadAttentions() -{ - static bool first_time = true; - if( ! first_time) - { - return true; // maybe not ideal but otherwise it can continue to fail forever. - } - first_time = false; - - std::string filename; - filename = gDirUtilp->getExpandedFilename(LL_PATH_CHARACTER,"attentions.xml"); - LLXmlTree xml_tree; - bool success = xml_tree.parseFile( filename, false ); - if( !success ) - { - return false; - } - LLXmlTreeNode* root = xml_tree.getRoot(); - if( !root ) - { - return false; - } - - //------------------------------------------------------------------------- - // (root) - //------------------------------------------------------------------------- - if( !root->hasName( "linden_attentions" ) ) - { - LL_WARNS() << "Invalid linden_attentions file header: " << filename << LL_ENDL; - return false; - } - - std::string version; - static LLStdStringHandle version_string = LLXmlTree::addAttributeString("version"); - if( !root->getFastAttributeString( version_string, version ) || (version != "1.0") ) - { - LL_WARNS() << "Invalid linden_attentions file version: " << version << LL_ENDL; - return false; - } - - //------------------------------------------------------------------------- - // - //------------------------------------------------------------------------- - for (LLXmlTreeNode* child = root->getChildByName( "gender" ); - child; - child = root->getNextNamedChild()) - { - if( !loadGender( child ) ) - { - return false; - } - } - - return true; -} - - - - -//----------------------------------------------------------------------------- -// LLHUDEffectLookAt() -//----------------------------------------------------------------------------- -LLHUDEffectLookAt::LLHUDEffectLookAt(const U8 type) : - LLHUDEffect(type), - mKillTime(0.f), - mLastSendTime(0.f) -{ - clearLookAtTarget(); - // parse the default sets - loadAttentions(); - // initialize current attention set. switches when avatar sex changes. - mAttentions = &gGirlAttentions; -} - -//----------------------------------------------------------------------------- -// ~LLHUDEffectLookAt() -//----------------------------------------------------------------------------- -LLHUDEffectLookAt::~LLHUDEffectLookAt() -{ -} - -//----------------------------------------------------------------------------- -// packData() -//----------------------------------------------------------------------------- -void LLHUDEffectLookAt::packData(LLMessageSystem *mesgsys) -{ - // Pack the default data - LLHUDEffect::packData(mesgsys); - - // Pack the type-specific data. Uses a fun packed binary format. Whee! - U8 packed_data[PKT_SIZE]; - memset(packed_data, 0, PKT_SIZE); - - if (mSourceObject) - { - htolememcpy(&(packed_data[SOURCE_AVATAR]), mSourceObject->mID.mData, MVT_LLUUID, 16); - } - else - { - htolememcpy(&(packed_data[SOURCE_AVATAR]), LLUUID::null.mData, MVT_LLUUID, 16); - } - - // pack both target object and position - // position interpreted as offset if target object is non-null - if (mTargetObject) - { - htolememcpy(&(packed_data[TARGET_OBJECT]), mTargetObject->mID.mData, MVT_LLUUID, 16); - } - else - { - htolememcpy(&(packed_data[TARGET_OBJECT]), LLUUID::null.mData, MVT_LLUUID, 16); - } - - htolememcpy(&(packed_data[TARGET_POS]), mTargetOffsetGlobal.mdV, MVT_LLVector3d, 24); - - U8 lookAtTypePacked = (U8)mTargetType; - - htolememcpy(&(packed_data[LOOKAT_TYPE]), &lookAtTypePacked, MVT_U8, 1); - - mesgsys->addBinaryDataFast(_PREHASH_TypeData, packed_data, PKT_SIZE); - - mLastSendTime = mTimer.getElapsedTimeF32(); -} - -//----------------------------------------------------------------------------- -// unpackData() -//----------------------------------------------------------------------------- -void LLHUDEffectLookAt::unpackData(LLMessageSystem *mesgsys, S32 blocknum) -{ - LLVector3d new_target; - U8 packed_data[PKT_SIZE]; - - LLUUID dataId; - mesgsys->getUUIDFast(_PREHASH_Effect, _PREHASH_ID, dataId, blocknum); - - if (!gAgentCamera.mLookAt.isNull() && dataId == gAgentCamera.mLookAt->getID()) - { - return; - } - - LLHUDEffect::unpackData(mesgsys, blocknum); - LLUUID source_id; - LLUUID target_id; - S32 size = mesgsys->getSizeFast(_PREHASH_Effect, blocknum, _PREHASH_TypeData); - if (size != PKT_SIZE) - { - LL_WARNS() << "LookAt effect with bad size " << size << LL_ENDL; - return; - } - mesgsys->getBinaryDataFast(_PREHASH_Effect, _PREHASH_TypeData, packed_data, PKT_SIZE, blocknum); - - htolememcpy(source_id.mData, &(packed_data[SOURCE_AVATAR]), MVT_LLUUID, 16); - - LLViewerObject *objp = gObjectList.findObject(source_id); - if (objp && objp->isAvatar()) - { - setSourceObject(objp); - } - else - { - //LL_WARNS() << "Could not find source avatar for lookat effect" << LL_ENDL; - return; - } - - htolememcpy(target_id.mData, &(packed_data[TARGET_OBJECT]), MVT_LLUUID, 16); - - objp = gObjectList.findObject(target_id); - - htolememcpy(new_target.mdV, &(packed_data[TARGET_POS]), MVT_LLVector3d, 24); - - if (objp) - { - setTargetObjectAndOffset(objp, new_target); - } - else if (target_id.isNull()) - { - setTargetPosGlobal(new_target); - } - else - { - //LL_WARNS() << "Could not find target object for lookat effect" << LL_ENDL; - } - - U8 lookAtTypeUnpacked = 0; - htolememcpy(&lookAtTypeUnpacked, &(packed_data[LOOKAT_TYPE]), MVT_U8, 1); - mTargetType = (ELookAtType)lookAtTypeUnpacked; - - if (mTargetType == LOOKAT_TARGET_NONE) - { - clearLookAtTarget(); - } -} - -//----------------------------------------------------------------------------- -// setTargetObjectAndOffset() -//----------------------------------------------------------------------------- -void LLHUDEffectLookAt::setTargetObjectAndOffset(LLViewerObject *objp, LLVector3d offset) -{ - mTargetObject = objp; - mTargetOffsetGlobal = offset; -} - -//----------------------------------------------------------------------------- -// setTargetPosGlobal() -//----------------------------------------------------------------------------- -void LLHUDEffectLookAt::setTargetPosGlobal(const LLVector3d &target_pos_global) -{ - mTargetObject = NULL; - mTargetOffsetGlobal = target_pos_global; -} - -//----------------------------------------------------------------------------- -// setLookAt() -// called by agent logic to set look at behavior locally, and propagate to sim -//----------------------------------------------------------------------------- -bool LLHUDEffectLookAt::setLookAt(ELookAtType target_type, LLViewerObject *object, LLVector3 position) -{ - if (!mSourceObject) - { - return false; - } - - if (target_type >= LOOKAT_NUM_TARGETS) - { - LL_WARNS() << "Bad target_type " << (int)target_type << " - ignoring." << LL_ENDL; - return false; - } - - // must be same or higher priority than existing effect - if ((*mAttentions)[target_type].mPriority < (*mAttentions)[mTargetType].mPriority) - { - return false; - } - - F32 current_time = mTimer.getElapsedTimeF32(); - - // type of lookat behavior or target object has changed - bool lookAtChanged = (target_type != mTargetType) || (object != mTargetObject); - - // lookat position has moved a certain amount and we haven't just sent an update - lookAtChanged = lookAtChanged || ((dist_vec_squared(position, mLastSentOffsetGlobal) > MIN_DELTAPOS_FOR_UPDATE_SQUARED) && - ((current_time - mLastSendTime) > (1.f / MAX_SENDS_PER_SEC))); - - if (lookAtChanged) - { - mLastSentOffsetGlobal = position; - F32 timeout = (*mAttentions)[target_type].mTimeout; - setDuration(timeout); - setNeedsSendToSim(true); - } - - if (target_type == LOOKAT_TARGET_CLEAR) - { - clearLookAtTarget(); - } - else - { - mTargetType = target_type; - mTargetObject = object; - if (object) - { - mTargetOffsetGlobal.setVec(position); - } - else - { - mTargetOffsetGlobal = gAgent.getPosGlobalFromAgent(position); - } - mKillTime = mTimer.getElapsedTimeF32() + mDuration; - - update(); - } - return true; -} - -//----------------------------------------------------------------------------- -// clearLookAtTarget() -//----------------------------------------------------------------------------- -void LLHUDEffectLookAt::clearLookAtTarget() -{ - mTargetObject = NULL; - mTargetOffsetGlobal.clearVec(); - mTargetType = LOOKAT_TARGET_NONE; - if (mSourceObject.notNull()) - { - ((LLVOAvatar*)(LLViewerObject*)mSourceObject)->stopMotion(ANIM_AGENT_HEAD_ROT); - } -} - -//----------------------------------------------------------------------------- -// markDead() -//----------------------------------------------------------------------------- -void LLHUDEffectLookAt::markDead() -{ - if (mSourceObject.notNull()) - { - ((LLVOAvatar*)(LLViewerObject*)mSourceObject)->removeAnimationData("LookAtPoint"); - } - - mSourceObject = NULL; - clearLookAtTarget(); - LLHUDEffect::markDead(); -} - -void LLHUDEffectLookAt::setSourceObject(LLViewerObject* objectp) -{ - // restrict source objects to avatars - if (objectp && objectp->isAvatar()) - { - LLHUDEffect::setSourceObject(objectp); - } -} - -//----------------------------------------------------------------------------- -// render() -//----------------------------------------------------------------------------- -void LLHUDEffectLookAt::render() -{ - if (sDebugLookAt && mSourceObject.notNull()) - { - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - //LLGLDisable gls_stencil(GL_STENCIL_TEST); - - LLVector3 target = mTargetPos + ((LLVOAvatar*)(LLViewerObject*)mSourceObject)->mHeadp->getWorldPosition(); - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - gGL.translatef(target.mV[VX], target.mV[VY], target.mV[VZ]); - gGL.scalef(0.3f, 0.3f, 0.3f); - gGL.begin(LLRender::LINES); - { - LLColor3 color = (*mAttentions)[mTargetType].mColor; - gGL.color3f(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE]); - gGL.vertex3f(-1.f, 0.f, 0.f); - gGL.vertex3f(1.f, 0.f, 0.f); - - gGL.vertex3f(0.f, -1.f, 0.f); - gGL.vertex3f(0.f, 1.f, 0.f); - - gGL.vertex3f(0.f, 0.f, -1.f); - gGL.vertex3f(0.f, 0.f, 1.f); - } gGL.end(); - gGL.popMatrix(); - } -} - -//----------------------------------------------------------------------------- -// update() -//----------------------------------------------------------------------------- -void LLHUDEffectLookAt::update() -{ - // If the target object is dead, set the target object to NULL - if (!mTargetObject.isNull() && mTargetObject->isDead()) - { - clearLookAtTarget(); - } - - // if source avatar is null or dead, mark self as dead and return - if (mSourceObject.isNull() || mSourceObject->isDead()) - { - markDead(); - return; - } - - // make sure the proper set of avatar attention are currently being used. - LLVOAvatar* source_avatar = (LLVOAvatar*)(LLViewerObject*)mSourceObject; - // for now the first cut will just switch on sex. future development could adjust - // timeouts according to avatar age and/or other features. - mAttentions = (source_avatar->getSex() == SEX_MALE) ? &gBoyAttentions : &gGirlAttentions; - //printf("updated to %s\n", (source_avatar->getSex() == SEX_MALE) ? "male" : "female"); - - F32 time = mTimer.getElapsedTimeF32(); - - // clear out the effect if time is up - if (mKillTime != 0.f && time > mKillTime) - { - if (mTargetType != LOOKAT_TARGET_NONE) - { - clearLookAtTarget(); - // look at timed out (only happens on own avatar), so tell everyone - setNeedsSendToSim(true); - } - } - - if (mTargetType != LOOKAT_TARGET_NONE) - { - if (calcTargetPosition()) - { - LLMotion* head_motion = ((LLVOAvatar*)(LLViewerObject*)mSourceObject)->findMotion(ANIM_AGENT_HEAD_ROT); - if (!head_motion || head_motion->isStopped()) - { - ((LLVOAvatar*)(LLViewerObject*)mSourceObject)->startMotion(ANIM_AGENT_HEAD_ROT); - } - } - } - - if (sDebugLookAt) - { - ((LLVOAvatar*)(LLViewerObject*)mSourceObject)->addDebugText((*mAttentions)[mTargetType].mName); - } -} - -/** - * Initializes the mTargetPos member from the current mSourceObjec and mTargetObject - * (and possibly mTargetOffsetGlobal). - * When mTargetObject is another avatar, it sets mTargetPos to be their eyes. - * - * Has the side-effect of also calling setAnimationData("LookAtPoint") with the new - * mTargetPos on the source object which is assumed to be an avatar. - * - * Returns whether we successfully calculated a finite target position. - */ -bool LLHUDEffectLookAt::calcTargetPosition() -{ - LLViewerObject *target_obj = (LLViewerObject *)mTargetObject; - LLVector3 local_offset; - - if (target_obj) - { - local_offset.setVec(mTargetOffsetGlobal); - } - else - { - local_offset = gAgent.getPosAgentFromGlobal(mTargetOffsetGlobal); - } - - LLVOAvatar* source_avatar = (LLVOAvatar*)(LLViewerObject*)mSourceObject; - if (!source_avatar->isBuilt()) - return false; - - if (target_obj && target_obj->mDrawable.notNull()) - { - LLQuaternion target_rot; - if (target_obj->isAvatar()) - { - LLVOAvatar *target_av = (LLVOAvatar *)target_obj; - - bool looking_at_self = source_avatar->isSelf() && target_av->isSelf(); - - // if selecting self, stare forward - if (looking_at_self && mTargetOffsetGlobal.magVecSquared() < MIN_TARGET_OFFSET_SQUARED) - { - //sets the lookat point in front of the avatar - mTargetOffsetGlobal.setVec(5.0, 0.0, 0.0); - local_offset.setVec(mTargetOffsetGlobal); - } - - // look the other avatar in the eye. note: what happens if target is self? -MG - mTargetPos = target_av->mHeadp->getWorldPosition(); - if (mTargetType == LOOKAT_TARGET_MOUSELOOK || mTargetType == LOOKAT_TARGET_FREELOOK) - { - // mouselook and freelook target offsets are absolute - target_rot = LLQuaternion::DEFAULT; - } - else if (looking_at_self && gAgentCamera.cameraCustomizeAvatar()) - { - // *NOTE: We have to do this because animation - // overrides do not set lookat behavior. - // *TODO: animation overrides for lookat behavior. - target_rot = target_av->mPelvisp->getWorldRotation(); - } - else - { - target_rot = target_av->mRoot->getWorldRotation(); - } - } - else // target obj is not an avatar - { - if (target_obj->mDrawable->getGeneration() == -1) - { - mTargetPos = target_obj->getPositionAgent(); - target_rot = target_obj->getWorldRotation(); - } - else - { - mTargetPos = target_obj->getRenderPosition(); - target_rot = target_obj->getRenderRotation(); - } - } - - mTargetPos += (local_offset * target_rot); - } - else // no target obj or it's not drawable - { - mTargetPos = local_offset; - } - - mTargetPos -= source_avatar->mHeadp->getWorldPosition(); - - if (!mTargetPos.isFinite()) - return false; - - source_avatar->setAnimationData("LookAtPoint", (void *)&mTargetPos); - - return true; -} +/** + * @file llhudeffectlookat.cpp + * @brief LLHUDEffectLookAt class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llhudeffectlookat.h" + +#include "llrender.h" + +#include "message.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llvoavatar.h" +#include "lldrawable.h" +#include "llviewerobjectlist.h" +#include "llrendersphere.h" +#include "llselectmgr.h" +#include "llglheaders.h" +#include "llxmltree.h" + + +bool LLHUDEffectLookAt::sDebugLookAt = false; + +// packet layout +const S32 SOURCE_AVATAR = 0; +const S32 TARGET_OBJECT = 16; +const S32 TARGET_POS = 32; +const S32 LOOKAT_TYPE = 56; +const S32 PKT_SIZE = 57; + +// throttle +const F32 MAX_SENDS_PER_SEC = 4.f; + +const F32 MIN_DELTAPOS_FOR_UPDATE_SQUARED = 0.05f * 0.05f; +const F32 MIN_TARGET_OFFSET_SQUARED = 0.0001f; + + +// can't use actual F32_MAX, because we add this to the current frametime +const F32 MAX_TIMEOUT = F32_MAX / 2.f; + +/** + * Simple data class holding values for a particular type of attention. + */ +class LLAttention +{ +public: + LLAttention() + : mTimeout(0.f), + mPriority(0.f) + {} + LLAttention(F32 timeout, F32 priority, const std::string& name, LLColor3 color) : + mTimeout(timeout), mPriority(priority), mName(name), mColor(color) + { + } + F32 mTimeout, mPriority; + std::string mName; + LLColor3 mColor; +}; + +/** + * Simple data class holding a list of attentions, one for every type. + */ +class LLAttentionSet +{ +public: + LLAttentionSet(const LLAttention attentions[]) + { + for(int i=0; igetAttributeString("name", str); + LLAttentionSet& attentions = (str.compare("Masculine") == 0) ? gBoyAttentions : gGirlAttentions; + for (LLXmlTreeNode* attention_node = gender->getChildByName( "param" ); + attention_node; + attention_node = gender->getNextNamedChild()) + { + attention_node->getAttributeString("attention", str); + LLAttention* attention; + if (str == "idle") attention = &attentions[LOOKAT_TARGET_IDLE]; + else if(str == "auto_listen") attention = &attentions[LOOKAT_TARGET_AUTO_LISTEN]; + else if(str == "freelook") attention = &attentions[LOOKAT_TARGET_FREELOOK]; + else if(str == "respond") attention = &attentions[LOOKAT_TARGET_RESPOND]; + else if(str == "hover") attention = &attentions[LOOKAT_TARGET_HOVER]; + else if(str == "conversation") attention = &attentions[LOOKAT_TARGET_CONVERSATION]; + else if(str == "select") attention = &attentions[LOOKAT_TARGET_SELECT]; + else if(str == "focus") attention = &attentions[LOOKAT_TARGET_FOCUS]; + else if(str == "mouselook") attention = &attentions[LOOKAT_TARGET_MOUSELOOK]; + else return false; + + F32 priority, timeout; + attention_node->getAttributeF32("priority", priority); + attention_node->getAttributeF32("timeout", timeout); + if(timeout < 0) timeout = MAX_TIMEOUT; + attention->mPriority = priority; + attention->mTimeout = timeout; + } + return true; +} + +static bool loadAttentions() +{ + static bool first_time = true; + if( ! first_time) + { + return true; // maybe not ideal but otherwise it can continue to fail forever. + } + first_time = false; + + std::string filename; + filename = gDirUtilp->getExpandedFilename(LL_PATH_CHARACTER,"attentions.xml"); + LLXmlTree xml_tree; + bool success = xml_tree.parseFile( filename, false ); + if( !success ) + { + return false; + } + LLXmlTreeNode* root = xml_tree.getRoot(); + if( !root ) + { + return false; + } + + //------------------------------------------------------------------------- + // (root) + //------------------------------------------------------------------------- + if( !root->hasName( "linden_attentions" ) ) + { + LL_WARNS() << "Invalid linden_attentions file header: " << filename << LL_ENDL; + return false; + } + + std::string version; + static LLStdStringHandle version_string = LLXmlTree::addAttributeString("version"); + if( !root->getFastAttributeString( version_string, version ) || (version != "1.0") ) + { + LL_WARNS() << "Invalid linden_attentions file version: " << version << LL_ENDL; + return false; + } + + //------------------------------------------------------------------------- + // + //------------------------------------------------------------------------- + for (LLXmlTreeNode* child = root->getChildByName( "gender" ); + child; + child = root->getNextNamedChild()) + { + if( !loadGender( child ) ) + { + return false; + } + } + + return true; +} + + + + +//----------------------------------------------------------------------------- +// LLHUDEffectLookAt() +//----------------------------------------------------------------------------- +LLHUDEffectLookAt::LLHUDEffectLookAt(const U8 type) : + LLHUDEffect(type), + mKillTime(0.f), + mLastSendTime(0.f) +{ + clearLookAtTarget(); + // parse the default sets + loadAttentions(); + // initialize current attention set. switches when avatar sex changes. + mAttentions = &gGirlAttentions; +} + +//----------------------------------------------------------------------------- +// ~LLHUDEffectLookAt() +//----------------------------------------------------------------------------- +LLHUDEffectLookAt::~LLHUDEffectLookAt() +{ +} + +//----------------------------------------------------------------------------- +// packData() +//----------------------------------------------------------------------------- +void LLHUDEffectLookAt::packData(LLMessageSystem *mesgsys) +{ + // Pack the default data + LLHUDEffect::packData(mesgsys); + + // Pack the type-specific data. Uses a fun packed binary format. Whee! + U8 packed_data[PKT_SIZE]; + memset(packed_data, 0, PKT_SIZE); + + if (mSourceObject) + { + htolememcpy(&(packed_data[SOURCE_AVATAR]), mSourceObject->mID.mData, MVT_LLUUID, 16); + } + else + { + htolememcpy(&(packed_data[SOURCE_AVATAR]), LLUUID::null.mData, MVT_LLUUID, 16); + } + + // pack both target object and position + // position interpreted as offset if target object is non-null + if (mTargetObject) + { + htolememcpy(&(packed_data[TARGET_OBJECT]), mTargetObject->mID.mData, MVT_LLUUID, 16); + } + else + { + htolememcpy(&(packed_data[TARGET_OBJECT]), LLUUID::null.mData, MVT_LLUUID, 16); + } + + htolememcpy(&(packed_data[TARGET_POS]), mTargetOffsetGlobal.mdV, MVT_LLVector3d, 24); + + U8 lookAtTypePacked = (U8)mTargetType; + + htolememcpy(&(packed_data[LOOKAT_TYPE]), &lookAtTypePacked, MVT_U8, 1); + + mesgsys->addBinaryDataFast(_PREHASH_TypeData, packed_data, PKT_SIZE); + + mLastSendTime = mTimer.getElapsedTimeF32(); +} + +//----------------------------------------------------------------------------- +// unpackData() +//----------------------------------------------------------------------------- +void LLHUDEffectLookAt::unpackData(LLMessageSystem *mesgsys, S32 blocknum) +{ + LLVector3d new_target; + U8 packed_data[PKT_SIZE]; + + LLUUID dataId; + mesgsys->getUUIDFast(_PREHASH_Effect, _PREHASH_ID, dataId, blocknum); + + if (!gAgentCamera.mLookAt.isNull() && dataId == gAgentCamera.mLookAt->getID()) + { + return; + } + + LLHUDEffect::unpackData(mesgsys, blocknum); + LLUUID source_id; + LLUUID target_id; + S32 size = mesgsys->getSizeFast(_PREHASH_Effect, blocknum, _PREHASH_TypeData); + if (size != PKT_SIZE) + { + LL_WARNS() << "LookAt effect with bad size " << size << LL_ENDL; + return; + } + mesgsys->getBinaryDataFast(_PREHASH_Effect, _PREHASH_TypeData, packed_data, PKT_SIZE, blocknum); + + htolememcpy(source_id.mData, &(packed_data[SOURCE_AVATAR]), MVT_LLUUID, 16); + + LLViewerObject *objp = gObjectList.findObject(source_id); + if (objp && objp->isAvatar()) + { + setSourceObject(objp); + } + else + { + //LL_WARNS() << "Could not find source avatar for lookat effect" << LL_ENDL; + return; + } + + htolememcpy(target_id.mData, &(packed_data[TARGET_OBJECT]), MVT_LLUUID, 16); + + objp = gObjectList.findObject(target_id); + + htolememcpy(new_target.mdV, &(packed_data[TARGET_POS]), MVT_LLVector3d, 24); + + if (objp) + { + setTargetObjectAndOffset(objp, new_target); + } + else if (target_id.isNull()) + { + setTargetPosGlobal(new_target); + } + else + { + //LL_WARNS() << "Could not find target object for lookat effect" << LL_ENDL; + } + + U8 lookAtTypeUnpacked = 0; + htolememcpy(&lookAtTypeUnpacked, &(packed_data[LOOKAT_TYPE]), MVT_U8, 1); + mTargetType = (ELookAtType)lookAtTypeUnpacked; + + if (mTargetType == LOOKAT_TARGET_NONE) + { + clearLookAtTarget(); + } +} + +//----------------------------------------------------------------------------- +// setTargetObjectAndOffset() +//----------------------------------------------------------------------------- +void LLHUDEffectLookAt::setTargetObjectAndOffset(LLViewerObject *objp, LLVector3d offset) +{ + mTargetObject = objp; + mTargetOffsetGlobal = offset; +} + +//----------------------------------------------------------------------------- +// setTargetPosGlobal() +//----------------------------------------------------------------------------- +void LLHUDEffectLookAt::setTargetPosGlobal(const LLVector3d &target_pos_global) +{ + mTargetObject = NULL; + mTargetOffsetGlobal = target_pos_global; +} + +//----------------------------------------------------------------------------- +// setLookAt() +// called by agent logic to set look at behavior locally, and propagate to sim +//----------------------------------------------------------------------------- +bool LLHUDEffectLookAt::setLookAt(ELookAtType target_type, LLViewerObject *object, LLVector3 position) +{ + if (!mSourceObject) + { + return false; + } + + if (target_type >= LOOKAT_NUM_TARGETS) + { + LL_WARNS() << "Bad target_type " << (int)target_type << " - ignoring." << LL_ENDL; + return false; + } + + // must be same or higher priority than existing effect + if ((*mAttentions)[target_type].mPriority < (*mAttentions)[mTargetType].mPriority) + { + return false; + } + + F32 current_time = mTimer.getElapsedTimeF32(); + + // type of lookat behavior or target object has changed + bool lookAtChanged = (target_type != mTargetType) || (object != mTargetObject); + + // lookat position has moved a certain amount and we haven't just sent an update + lookAtChanged = lookAtChanged || ((dist_vec_squared(position, mLastSentOffsetGlobal) > MIN_DELTAPOS_FOR_UPDATE_SQUARED) && + ((current_time - mLastSendTime) > (1.f / MAX_SENDS_PER_SEC))); + + if (lookAtChanged) + { + mLastSentOffsetGlobal = position; + F32 timeout = (*mAttentions)[target_type].mTimeout; + setDuration(timeout); + setNeedsSendToSim(true); + } + + if (target_type == LOOKAT_TARGET_CLEAR) + { + clearLookAtTarget(); + } + else + { + mTargetType = target_type; + mTargetObject = object; + if (object) + { + mTargetOffsetGlobal.setVec(position); + } + else + { + mTargetOffsetGlobal = gAgent.getPosGlobalFromAgent(position); + } + mKillTime = mTimer.getElapsedTimeF32() + mDuration; + + update(); + } + return true; +} + +//----------------------------------------------------------------------------- +// clearLookAtTarget() +//----------------------------------------------------------------------------- +void LLHUDEffectLookAt::clearLookAtTarget() +{ + mTargetObject = NULL; + mTargetOffsetGlobal.clearVec(); + mTargetType = LOOKAT_TARGET_NONE; + if (mSourceObject.notNull()) + { + ((LLVOAvatar*)(LLViewerObject*)mSourceObject)->stopMotion(ANIM_AGENT_HEAD_ROT); + } +} + +//----------------------------------------------------------------------------- +// markDead() +//----------------------------------------------------------------------------- +void LLHUDEffectLookAt::markDead() +{ + if (mSourceObject.notNull()) + { + ((LLVOAvatar*)(LLViewerObject*)mSourceObject)->removeAnimationData("LookAtPoint"); + } + + mSourceObject = NULL; + clearLookAtTarget(); + LLHUDEffect::markDead(); +} + +void LLHUDEffectLookAt::setSourceObject(LLViewerObject* objectp) +{ + // restrict source objects to avatars + if (objectp && objectp->isAvatar()) + { + LLHUDEffect::setSourceObject(objectp); + } +} + +//----------------------------------------------------------------------------- +// render() +//----------------------------------------------------------------------------- +void LLHUDEffectLookAt::render() +{ + if (sDebugLookAt && mSourceObject.notNull()) + { + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + //LLGLDisable gls_stencil(GL_STENCIL_TEST); + + LLVector3 target = mTargetPos + ((LLVOAvatar*)(LLViewerObject*)mSourceObject)->mHeadp->getWorldPosition(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + gGL.translatef(target.mV[VX], target.mV[VY], target.mV[VZ]); + gGL.scalef(0.3f, 0.3f, 0.3f); + gGL.begin(LLRender::LINES); + { + LLColor3 color = (*mAttentions)[mTargetType].mColor; + gGL.color3f(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE]); + gGL.vertex3f(-1.f, 0.f, 0.f); + gGL.vertex3f(1.f, 0.f, 0.f); + + gGL.vertex3f(0.f, -1.f, 0.f); + gGL.vertex3f(0.f, 1.f, 0.f); + + gGL.vertex3f(0.f, 0.f, -1.f); + gGL.vertex3f(0.f, 0.f, 1.f); + } gGL.end(); + gGL.popMatrix(); + } +} + +//----------------------------------------------------------------------------- +// update() +//----------------------------------------------------------------------------- +void LLHUDEffectLookAt::update() +{ + // If the target object is dead, set the target object to NULL + if (!mTargetObject.isNull() && mTargetObject->isDead()) + { + clearLookAtTarget(); + } + + // if source avatar is null or dead, mark self as dead and return + if (mSourceObject.isNull() || mSourceObject->isDead()) + { + markDead(); + return; + } + + // make sure the proper set of avatar attention are currently being used. + LLVOAvatar* source_avatar = (LLVOAvatar*)(LLViewerObject*)mSourceObject; + // for now the first cut will just switch on sex. future development could adjust + // timeouts according to avatar age and/or other features. + mAttentions = (source_avatar->getSex() == SEX_MALE) ? &gBoyAttentions : &gGirlAttentions; + //printf("updated to %s\n", (source_avatar->getSex() == SEX_MALE) ? "male" : "female"); + + F32 time = mTimer.getElapsedTimeF32(); + + // clear out the effect if time is up + if (mKillTime != 0.f && time > mKillTime) + { + if (mTargetType != LOOKAT_TARGET_NONE) + { + clearLookAtTarget(); + // look at timed out (only happens on own avatar), so tell everyone + setNeedsSendToSim(true); + } + } + + if (mTargetType != LOOKAT_TARGET_NONE) + { + if (calcTargetPosition()) + { + LLMotion* head_motion = ((LLVOAvatar*)(LLViewerObject*)mSourceObject)->findMotion(ANIM_AGENT_HEAD_ROT); + if (!head_motion || head_motion->isStopped()) + { + ((LLVOAvatar*)(LLViewerObject*)mSourceObject)->startMotion(ANIM_AGENT_HEAD_ROT); + } + } + } + + if (sDebugLookAt) + { + ((LLVOAvatar*)(LLViewerObject*)mSourceObject)->addDebugText((*mAttentions)[mTargetType].mName); + } +} + +/** + * Initializes the mTargetPos member from the current mSourceObjec and mTargetObject + * (and possibly mTargetOffsetGlobal). + * When mTargetObject is another avatar, it sets mTargetPos to be their eyes. + * + * Has the side-effect of also calling setAnimationData("LookAtPoint") with the new + * mTargetPos on the source object which is assumed to be an avatar. + * + * Returns whether we successfully calculated a finite target position. + */ +bool LLHUDEffectLookAt::calcTargetPosition() +{ + LLViewerObject *target_obj = (LLViewerObject *)mTargetObject; + LLVector3 local_offset; + + if (target_obj) + { + local_offset.setVec(mTargetOffsetGlobal); + } + else + { + local_offset = gAgent.getPosAgentFromGlobal(mTargetOffsetGlobal); + } + + LLVOAvatar* source_avatar = (LLVOAvatar*)(LLViewerObject*)mSourceObject; + if (!source_avatar->isBuilt()) + return false; + + if (target_obj && target_obj->mDrawable.notNull()) + { + LLQuaternion target_rot; + if (target_obj->isAvatar()) + { + LLVOAvatar *target_av = (LLVOAvatar *)target_obj; + + bool looking_at_self = source_avatar->isSelf() && target_av->isSelf(); + + // if selecting self, stare forward + if (looking_at_self && mTargetOffsetGlobal.magVecSquared() < MIN_TARGET_OFFSET_SQUARED) + { + //sets the lookat point in front of the avatar + mTargetOffsetGlobal.setVec(5.0, 0.0, 0.0); + local_offset.setVec(mTargetOffsetGlobal); + } + + // look the other avatar in the eye. note: what happens if target is self? -MG + mTargetPos = target_av->mHeadp->getWorldPosition(); + if (mTargetType == LOOKAT_TARGET_MOUSELOOK || mTargetType == LOOKAT_TARGET_FREELOOK) + { + // mouselook and freelook target offsets are absolute + target_rot = LLQuaternion::DEFAULT; + } + else if (looking_at_self && gAgentCamera.cameraCustomizeAvatar()) + { + // *NOTE: We have to do this because animation + // overrides do not set lookat behavior. + // *TODO: animation overrides for lookat behavior. + target_rot = target_av->mPelvisp->getWorldRotation(); + } + else + { + target_rot = target_av->mRoot->getWorldRotation(); + } + } + else // target obj is not an avatar + { + if (target_obj->mDrawable->getGeneration() == -1) + { + mTargetPos = target_obj->getPositionAgent(); + target_rot = target_obj->getWorldRotation(); + } + else + { + mTargetPos = target_obj->getRenderPosition(); + target_rot = target_obj->getRenderRotation(); + } + } + + mTargetPos += (local_offset * target_rot); + } + else // no target obj or it's not drawable + { + mTargetPos = local_offset; + } + + mTargetPos -= source_avatar->mHeadp->getWorldPosition(); + + if (!mTargetPos.isFinite()) + return false; + + source_avatar->setAnimationData("LookAtPoint", (void *)&mTargetPos); + + return true; +} diff --git a/indra/newview/llhudeffectlookat.h b/indra/newview/llhudeffectlookat.h index fd22774c8c..0e57422ee1 100644 --- a/indra/newview/llhudeffectlookat.h +++ b/indra/newview/llhudeffectlookat.h @@ -1,95 +1,95 @@ -/** - * @file llhudeffectlookat.h - * @brief LLHUDEffectLookAt class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLHUDEFFECTLOOKAT_H -#define LL_LLHUDEFFECTLOOKAT_H - -#include "llhudeffect.h" - -class LLViewerObject; -class LLVOAvatar; -class LLAttentionSet; - -typedef enum e_lookat_type -{ - LOOKAT_TARGET_NONE, - LOOKAT_TARGET_IDLE, - LOOKAT_TARGET_AUTO_LISTEN, - LOOKAT_TARGET_FREELOOK, - LOOKAT_TARGET_RESPOND, - LOOKAT_TARGET_HOVER, - LOOKAT_TARGET_CONVERSATION, - LOOKAT_TARGET_SELECT, - LOOKAT_TARGET_FOCUS, - LOOKAT_TARGET_MOUSELOOK, - LOOKAT_TARGET_CLEAR, - LOOKAT_NUM_TARGETS -} ELookAtType; - -class LLHUDEffectLookAt : public LLHUDEffect -{ -public: - friend class LLHUDObject; - - /*virtual*/ void markDead(); - /*virtual*/ void setSourceObject(LLViewerObject* objectp); - - bool setLookAt(ELookAtType target_type, LLViewerObject *object, LLVector3 position); - void clearLookAtTarget(); - - ELookAtType getLookAtType() { return mTargetType; } - const LLVector3& getTargetPos() { return mTargetPos; } - const LLVector3d& getTargetOffset() { return mTargetOffsetGlobal; } - bool calcTargetPosition(); - -protected: - LLHUDEffectLookAt(const U8 type); - ~LLHUDEffectLookAt(); - - /*virtual*/ void update(); - /*virtual*/ void render(); - /*virtual*/ void packData(LLMessageSystem *mesgsys); - /*virtual*/ void unpackData(LLMessageSystem *mesgsys, S32 blocknum); - - // lookat behavior has either target position or target object with offset - void setTargetObjectAndOffset(LLViewerObject *objp, LLVector3d offset); - void setTargetPosGlobal(const LLVector3d &target_pos_global); - -public: - static bool sDebugLookAt; - -private: - ELookAtType mTargetType; - LLVector3d mTargetOffsetGlobal; - LLVector3 mLastSentOffsetGlobal; - F32 mKillTime; - LLFrameTimer mTimer; - LLVector3 mTargetPos; - F32 mLastSendTime; - LLAttentionSet* mAttentions; -}; - -#endif // LL_LLHUDEFFECTLOOKAT_H +/** + * @file llhudeffectlookat.h + * @brief LLHUDEffectLookAt class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLHUDEFFECTLOOKAT_H +#define LL_LLHUDEFFECTLOOKAT_H + +#include "llhudeffect.h" + +class LLViewerObject; +class LLVOAvatar; +class LLAttentionSet; + +typedef enum e_lookat_type +{ + LOOKAT_TARGET_NONE, + LOOKAT_TARGET_IDLE, + LOOKAT_TARGET_AUTO_LISTEN, + LOOKAT_TARGET_FREELOOK, + LOOKAT_TARGET_RESPOND, + LOOKAT_TARGET_HOVER, + LOOKAT_TARGET_CONVERSATION, + LOOKAT_TARGET_SELECT, + LOOKAT_TARGET_FOCUS, + LOOKAT_TARGET_MOUSELOOK, + LOOKAT_TARGET_CLEAR, + LOOKAT_NUM_TARGETS +} ELookAtType; + +class LLHUDEffectLookAt : public LLHUDEffect +{ +public: + friend class LLHUDObject; + + /*virtual*/ void markDead(); + /*virtual*/ void setSourceObject(LLViewerObject* objectp); + + bool setLookAt(ELookAtType target_type, LLViewerObject *object, LLVector3 position); + void clearLookAtTarget(); + + ELookAtType getLookAtType() { return mTargetType; } + const LLVector3& getTargetPos() { return mTargetPos; } + const LLVector3d& getTargetOffset() { return mTargetOffsetGlobal; } + bool calcTargetPosition(); + +protected: + LLHUDEffectLookAt(const U8 type); + ~LLHUDEffectLookAt(); + + /*virtual*/ void update(); + /*virtual*/ void render(); + /*virtual*/ void packData(LLMessageSystem *mesgsys); + /*virtual*/ void unpackData(LLMessageSystem *mesgsys, S32 blocknum); + + // lookat behavior has either target position or target object with offset + void setTargetObjectAndOffset(LLViewerObject *objp, LLVector3d offset); + void setTargetPosGlobal(const LLVector3d &target_pos_global); + +public: + static bool sDebugLookAt; + +private: + ELookAtType mTargetType; + LLVector3d mTargetOffsetGlobal; + LLVector3 mLastSentOffsetGlobal; + F32 mKillTime; + LLFrameTimer mTimer; + LLVector3 mTargetPos; + F32 mLastSendTime; + LLAttentionSet* mAttentions; +}; + +#endif // LL_LLHUDEFFECTLOOKAT_H diff --git a/indra/newview/llhudeffectpointat.cpp b/indra/newview/llhudeffectpointat.cpp index 77f05955bd..eeb38cd6aa 100644 --- a/indra/newview/llhudeffectpointat.cpp +++ b/indra/newview/llhudeffectpointat.cpp @@ -1,462 +1,462 @@ -/** - * @file llhudeffectpointat.cpp - * @brief LLHUDEffectPointAt class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llhudeffectpointat.h" - -#include "llgl.h" -#include "llrender.h" - -#include "llagent.h" -#include "llagentcamera.h" -#include "lldrawable.h" -#include "llviewerobjectlist.h" -#include "llvoavatar.h" -#include "message.h" - -// packet layout -const S32 SOURCE_AVATAR = 0; -const S32 TARGET_OBJECT = 16; -const S32 TARGET_POS = 32; -const S32 POINTAT_TYPE = 56; -const S32 PKT_SIZE = 57; - -// throttle -const F32 MAX_SENDS_PER_SEC = 4.f; - -const F32 MIN_DELTAPOS_FOR_UPDATE_SQUARED = 0.05f * 0.05f; - -// timeouts -// can't use actual F32_MAX, because we add this to the current frametime -const F32 MAX_TIMEOUT = F32_MAX / 4.f; - -const F32 POINTAT_TIMEOUTS[POINTAT_NUM_TARGETS] = -{ - MAX_TIMEOUT, //POINTAT_TARGET_NONE - MAX_TIMEOUT, //POINTAT_TARGET_SELECT - MAX_TIMEOUT, //POINTAT_TARGET_GRAB - 0.f, //POINTAT_TARGET_CLEAR -}; - -const S32 POINTAT_PRIORITIES[POINTAT_NUM_TARGETS] = -{ - 0, //POINTAT_TARGET_NONE - 1, //POINTAT_TARGET_SELECT - 2, //POINTAT_TARGET_GRAB - 3, //POINTAT_TARGET_CLEAR -}; - -// statics - -bool LLHUDEffectPointAt::sDebugPointAt; - - -//----------------------------------------------------------------------------- -// LLHUDEffectPointAt() -//----------------------------------------------------------------------------- -LLHUDEffectPointAt::LLHUDEffectPointAt(const U8 type) : - LLHUDEffect(type), - mKillTime(0.f), - mLastSendTime(0.f) -{ - clearPointAtTarget(); -} - -//----------------------------------------------------------------------------- -// ~LLHUDEffectPointAt() -//----------------------------------------------------------------------------- -LLHUDEffectPointAt::~LLHUDEffectPointAt() -{ -} - -//----------------------------------------------------------------------------- -// packData() -//----------------------------------------------------------------------------- -void LLHUDEffectPointAt::packData(LLMessageSystem *mesgsys) -{ - // Pack the default data - LLHUDEffect::packData(mesgsys); - - // Pack the type-specific data. Uses a fun packed binary format. Whee! - U8 packed_data[PKT_SIZE]; - memset(packed_data, 0, PKT_SIZE); - - if (mSourceObject) - { - htolememcpy(&(packed_data[SOURCE_AVATAR]), mSourceObject->mID.mData, MVT_LLUUID, 16); - } - else - { - htolememcpy(&(packed_data[SOURCE_AVATAR]), LLUUID::null.mData, MVT_LLUUID, 16); - } - - // pack both target object and position - // position interpreted as offset if target object is non-null - if (mTargetObject) - { - htolememcpy(&(packed_data[TARGET_OBJECT]), mTargetObject->mID.mData, MVT_LLUUID, 16); - } - else - { - htolememcpy(&(packed_data[TARGET_OBJECT]), LLUUID::null.mData, MVT_LLUUID, 16); - } - - htolememcpy(&(packed_data[TARGET_POS]), mTargetOffsetGlobal.mdV, MVT_LLVector3d, 24); - - U8 pointAtTypePacked = (U8)mTargetType; - htolememcpy(&(packed_data[POINTAT_TYPE]), &pointAtTypePacked, MVT_U8, 1); - - mesgsys->addBinaryDataFast(_PREHASH_TypeData, packed_data, PKT_SIZE); - - mLastSendTime = mTimer.getElapsedTimeF32(); -} - -//----------------------------------------------------------------------------- -// unpackData() -//----------------------------------------------------------------------------- -void LLHUDEffectPointAt::unpackData(LLMessageSystem *mesgsys, S32 blocknum) -{ - LLVector3d new_target; - U8 packed_data[PKT_SIZE]; - - LLUUID dataId; - mesgsys->getUUIDFast(_PREHASH_Effect, _PREHASH_ID, dataId, blocknum); - - // ignore messages from ourselves - if (!gAgentCamera.mPointAt.isNull() && dataId == gAgentCamera.mPointAt->getID()) - { - return; - } - - LLHUDEffect::unpackData(mesgsys, blocknum); - LLUUID source_id; - LLUUID target_id; - U8 pointAtTypeUnpacked = 0; - S32 size = mesgsys->getSizeFast(_PREHASH_Effect, blocknum, _PREHASH_TypeData); - if (size != PKT_SIZE) - { - LL_WARNS() << "PointAt effect with bad size " << size << LL_ENDL; - return; - } - mesgsys->getBinaryDataFast(_PREHASH_Effect, _PREHASH_TypeData, packed_data, PKT_SIZE, blocknum); - - htolememcpy(source_id.mData, &(packed_data[SOURCE_AVATAR]), MVT_LLUUID, 16); - htolememcpy(target_id.mData, &(packed_data[TARGET_OBJECT]), MVT_LLUUID, 16); - htolememcpy(new_target.mdV, &(packed_data[TARGET_POS]), MVT_LLVector3d, 24); - htolememcpy(&pointAtTypeUnpacked, &(packed_data[POINTAT_TYPE]), MVT_U8, 1); - - LLViewerObject *objp = gObjectList.findObject(source_id); - if (objp && objp->isAvatar()) - { - setSourceObject(objp); - } - else - { - //LL_WARNS() << "Could not find source avatar for pointat effect" << LL_ENDL; - return; - } - - objp = gObjectList.findObject(target_id); - - if (objp) - { - setTargetObjectAndOffset(objp, new_target); - } - else if (target_id.isNull()) - { - setTargetPosGlobal(new_target); - } - - mTargetType = (EPointAtType)pointAtTypeUnpacked; - -// mKillTime = mTimer.getElapsedTimeF32() + mDuration; - update(); -} - -//----------------------------------------------------------------------------- -// setTargetObjectAndOffset() -//----------------------------------------------------------------------------- -void LLHUDEffectPointAt::setTargetObjectAndOffset(LLViewerObject *objp, LLVector3d offset) -{ - mTargetObject = objp; - mTargetOffsetGlobal = offset; -} - -//----------------------------------------------------------------------------- -// setTargetPosGlobal() -//----------------------------------------------------------------------------- -void LLHUDEffectPointAt::setTargetPosGlobal(const LLVector3d &target_pos_global) -{ - mTargetObject = NULL; - mTargetOffsetGlobal = target_pos_global; -} - -//----------------------------------------------------------------------------- -// setPointAt() -// called by agent logic to set look at behavior locally, and propagate to sim -//----------------------------------------------------------------------------- -bool LLHUDEffectPointAt::setPointAt(EPointAtType target_type, LLViewerObject *object, LLVector3 position) -{ - if (!mSourceObject) - { - return false; - } - - if (target_type >= POINTAT_NUM_TARGETS) - { - LL_WARNS() << "Bad target_type " << (int)target_type << " - ignoring." << LL_ENDL; - return false; - } - - // must be same or higher priority than existing effect - if (POINTAT_PRIORITIES[target_type] < POINTAT_PRIORITIES[mTargetType]) - { - return false; - } - - F32 current_time = mTimer.getElapsedTimeF32(); - - // type of pointat behavior or target object has changed - bool targetTypeChanged = (target_type != mTargetType) || - (object != mTargetObject); - - bool targetPosChanged = (dist_vec_squared(position, mLastSentOffsetGlobal) > MIN_DELTAPOS_FOR_UPDATE_SQUARED) && - ((current_time - mLastSendTime) > (1.f / MAX_SENDS_PER_SEC)); - - if (targetTypeChanged || targetPosChanged) - { - mLastSentOffsetGlobal = position; - setDuration(POINTAT_TIMEOUTS[target_type]); - setNeedsSendToSim(true); -// LL_INFOS() << "Sending pointat data" << LL_ENDL; - } - - if (target_type == POINTAT_TARGET_CLEAR) - { - clearPointAtTarget(); - } - else - { - mTargetType = target_type; - mTargetObject = object; - if (object) - { - mTargetOffsetGlobal.setVec(position); - } - else - { - mTargetOffsetGlobal = gAgent.getPosGlobalFromAgent(position); - } - - mKillTime = mTimer.getElapsedTimeF32() + mDuration; - - //set up requisite animation data - update(); - } - - return true; -} - -//----------------------------------------------------------------------------- -// clearPointAtTarget() -//----------------------------------------------------------------------------- -void LLHUDEffectPointAt::clearPointAtTarget() -{ - mTargetObject = NULL; - mTargetOffsetGlobal.clearVec(); - mTargetType = POINTAT_TARGET_NONE; -} - -//----------------------------------------------------------------------------- -// markDead() -//----------------------------------------------------------------------------- -void LLHUDEffectPointAt::markDead() -{ - if (!mSourceObject.isNull() && mSourceObject->isAvatar()) - { - ((LLVOAvatar*)(LLViewerObject*)mSourceObject)->removeAnimationData("PointAtPoint"); - } - - clearPointAtTarget(); - LLHUDEffect::markDead(); -} - -void LLHUDEffectPointAt::setSourceObject(LLViewerObject* objectp) -{ - // restrict source objects to avatars - if (objectp && objectp->isAvatar()) - { - LLHUDEffect::setSourceObject(objectp); - } -} - -//----------------------------------------------------------------------------- -// render() -//----------------------------------------------------------------------------- -void LLHUDEffectPointAt::render() -{ - update(); - if (sDebugPointAt && mTargetType != POINTAT_TARGET_NONE) - { - //LLGLDisable gls_stencil(GL_STENCIL_TEST); - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - LLVector3 target = mTargetPos + mSourceObject->getRenderPosition(); - gGL.pushMatrix(); - gGL.translatef(target.mV[VX], target.mV[VY], target.mV[VZ]); - gGL.scalef(0.3f, 0.3f, 0.3f); - gGL.begin(LLRender::LINES); - { - gGL.color3f(1.f, 0.f, 0.f); - gGL.vertex3f(-1.f, 0.f, 0.f); - gGL.vertex3f(1.f, 0.f, 0.f); - - gGL.vertex3f(0.f, -1.f, 0.f); - gGL.vertex3f(0.f, 1.f, 0.f); - - gGL.vertex3f(0.f, 0.f, -1.f); - gGL.vertex3f(0.f, 0.f, 1.f); - } gGL.end(); - gGL.popMatrix(); - } -} - -//----------------------------------------------------------------------------- -// update() -//----------------------------------------------------------------------------- -void LLHUDEffectPointAt::update() -{ - // If the target object is dead, set the target object to NULL - if (!mTargetObject.isNull() && mTargetObject->isDead()) - { - clearPointAtTarget(); - } - - if (mSourceObject.isNull() || mSourceObject->isDead()) - { - markDead(); - return; - } - - F32 time = mTimer.getElapsedTimeF32(); - - // clear out the effect if time is up - if (mKillTime != 0.f && time > mKillTime) - { - mTargetType = POINTAT_TARGET_NONE; - } - - if (mSourceObject->isAvatar()) - { - if (mTargetType == POINTAT_TARGET_NONE) - { - ((LLVOAvatar*)(LLViewerObject*)mSourceObject)->removeAnimationData("PointAtPoint"); - } - else - { - if (calcTargetPosition()) - { - ((LLVOAvatar*)(LLViewerObject*)mSourceObject)->startMotion(ANIM_AGENT_EDITING); - } - } - } -} - -//----------------------------------------------------------------------------- -// calcTargetPosition() -// returns whether we successfully calculated a finite target position. -//----------------------------------------------------------------------------- -bool LLHUDEffectPointAt::calcTargetPosition() -{ - LLViewerObject *targetObject = (LLViewerObject *)mTargetObject; - LLVector3 local_offset; - - if (targetObject) - { - local_offset.setVec(mTargetOffsetGlobal); - } - else - { - local_offset = gAgent.getPosAgentFromGlobal(mTargetOffsetGlobal); - } - - if (targetObject && targetObject->mDrawable.notNull()) - { - LLQuaternion objRot; - if (targetObject->isAvatar()) - { - LLVOAvatar *avatarp = (LLVOAvatar *)targetObject; - mTargetPos = avatarp->mHeadp->getWorldPosition(); - objRot = avatarp->mPelvisp->getWorldRotation(); - } - else - { - if (targetObject->mDrawable->getGeneration() == -1) - { - mTargetPos = targetObject->getPositionAgent(); - objRot = targetObject->getWorldRotation(); - } - else - { - mTargetPos = targetObject->getRenderPosition(); - objRot = targetObject->getRenderRotation(); - } - } - - mTargetPos += (local_offset * objRot); - } - else - { - mTargetPos = local_offset; - } - - mTargetPos -= mSourceObject->getRenderPosition(); - - if (!llfinite(mTargetPos.lengthSquared())) - { - return false; - } - - if (mSourceObject->isAvatar()) - { - ((LLVOAvatar*)(LLViewerObject*)mSourceObject)->setAnimationData("PointAtPoint", (void *)&mTargetPos); - } - - return true; -} - -const LLVector3d LLHUDEffectPointAt::getPointAtPosGlobal() -{ - LLVector3d global_pos; - global_pos.setVec(mTargetPos); - if (mSourceObject.notNull()) - { - global_pos += mSourceObject->getPositionGlobal(); - } - - return global_pos; -} +/** + * @file llhudeffectpointat.cpp + * @brief LLHUDEffectPointAt class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llhudeffectpointat.h" + +#include "llgl.h" +#include "llrender.h" + +#include "llagent.h" +#include "llagentcamera.h" +#include "lldrawable.h" +#include "llviewerobjectlist.h" +#include "llvoavatar.h" +#include "message.h" + +// packet layout +const S32 SOURCE_AVATAR = 0; +const S32 TARGET_OBJECT = 16; +const S32 TARGET_POS = 32; +const S32 POINTAT_TYPE = 56; +const S32 PKT_SIZE = 57; + +// throttle +const F32 MAX_SENDS_PER_SEC = 4.f; + +const F32 MIN_DELTAPOS_FOR_UPDATE_SQUARED = 0.05f * 0.05f; + +// timeouts +// can't use actual F32_MAX, because we add this to the current frametime +const F32 MAX_TIMEOUT = F32_MAX / 4.f; + +const F32 POINTAT_TIMEOUTS[POINTAT_NUM_TARGETS] = +{ + MAX_TIMEOUT, //POINTAT_TARGET_NONE + MAX_TIMEOUT, //POINTAT_TARGET_SELECT + MAX_TIMEOUT, //POINTAT_TARGET_GRAB + 0.f, //POINTAT_TARGET_CLEAR +}; + +const S32 POINTAT_PRIORITIES[POINTAT_NUM_TARGETS] = +{ + 0, //POINTAT_TARGET_NONE + 1, //POINTAT_TARGET_SELECT + 2, //POINTAT_TARGET_GRAB + 3, //POINTAT_TARGET_CLEAR +}; + +// statics + +bool LLHUDEffectPointAt::sDebugPointAt; + + +//----------------------------------------------------------------------------- +// LLHUDEffectPointAt() +//----------------------------------------------------------------------------- +LLHUDEffectPointAt::LLHUDEffectPointAt(const U8 type) : + LLHUDEffect(type), + mKillTime(0.f), + mLastSendTime(0.f) +{ + clearPointAtTarget(); +} + +//----------------------------------------------------------------------------- +// ~LLHUDEffectPointAt() +//----------------------------------------------------------------------------- +LLHUDEffectPointAt::~LLHUDEffectPointAt() +{ +} + +//----------------------------------------------------------------------------- +// packData() +//----------------------------------------------------------------------------- +void LLHUDEffectPointAt::packData(LLMessageSystem *mesgsys) +{ + // Pack the default data + LLHUDEffect::packData(mesgsys); + + // Pack the type-specific data. Uses a fun packed binary format. Whee! + U8 packed_data[PKT_SIZE]; + memset(packed_data, 0, PKT_SIZE); + + if (mSourceObject) + { + htolememcpy(&(packed_data[SOURCE_AVATAR]), mSourceObject->mID.mData, MVT_LLUUID, 16); + } + else + { + htolememcpy(&(packed_data[SOURCE_AVATAR]), LLUUID::null.mData, MVT_LLUUID, 16); + } + + // pack both target object and position + // position interpreted as offset if target object is non-null + if (mTargetObject) + { + htolememcpy(&(packed_data[TARGET_OBJECT]), mTargetObject->mID.mData, MVT_LLUUID, 16); + } + else + { + htolememcpy(&(packed_data[TARGET_OBJECT]), LLUUID::null.mData, MVT_LLUUID, 16); + } + + htolememcpy(&(packed_data[TARGET_POS]), mTargetOffsetGlobal.mdV, MVT_LLVector3d, 24); + + U8 pointAtTypePacked = (U8)mTargetType; + htolememcpy(&(packed_data[POINTAT_TYPE]), &pointAtTypePacked, MVT_U8, 1); + + mesgsys->addBinaryDataFast(_PREHASH_TypeData, packed_data, PKT_SIZE); + + mLastSendTime = mTimer.getElapsedTimeF32(); +} + +//----------------------------------------------------------------------------- +// unpackData() +//----------------------------------------------------------------------------- +void LLHUDEffectPointAt::unpackData(LLMessageSystem *mesgsys, S32 blocknum) +{ + LLVector3d new_target; + U8 packed_data[PKT_SIZE]; + + LLUUID dataId; + mesgsys->getUUIDFast(_PREHASH_Effect, _PREHASH_ID, dataId, blocknum); + + // ignore messages from ourselves + if (!gAgentCamera.mPointAt.isNull() && dataId == gAgentCamera.mPointAt->getID()) + { + return; + } + + LLHUDEffect::unpackData(mesgsys, blocknum); + LLUUID source_id; + LLUUID target_id; + U8 pointAtTypeUnpacked = 0; + S32 size = mesgsys->getSizeFast(_PREHASH_Effect, blocknum, _PREHASH_TypeData); + if (size != PKT_SIZE) + { + LL_WARNS() << "PointAt effect with bad size " << size << LL_ENDL; + return; + } + mesgsys->getBinaryDataFast(_PREHASH_Effect, _PREHASH_TypeData, packed_data, PKT_SIZE, blocknum); + + htolememcpy(source_id.mData, &(packed_data[SOURCE_AVATAR]), MVT_LLUUID, 16); + htolememcpy(target_id.mData, &(packed_data[TARGET_OBJECT]), MVT_LLUUID, 16); + htolememcpy(new_target.mdV, &(packed_data[TARGET_POS]), MVT_LLVector3d, 24); + htolememcpy(&pointAtTypeUnpacked, &(packed_data[POINTAT_TYPE]), MVT_U8, 1); + + LLViewerObject *objp = gObjectList.findObject(source_id); + if (objp && objp->isAvatar()) + { + setSourceObject(objp); + } + else + { + //LL_WARNS() << "Could not find source avatar for pointat effect" << LL_ENDL; + return; + } + + objp = gObjectList.findObject(target_id); + + if (objp) + { + setTargetObjectAndOffset(objp, new_target); + } + else if (target_id.isNull()) + { + setTargetPosGlobal(new_target); + } + + mTargetType = (EPointAtType)pointAtTypeUnpacked; + +// mKillTime = mTimer.getElapsedTimeF32() + mDuration; + update(); +} + +//----------------------------------------------------------------------------- +// setTargetObjectAndOffset() +//----------------------------------------------------------------------------- +void LLHUDEffectPointAt::setTargetObjectAndOffset(LLViewerObject *objp, LLVector3d offset) +{ + mTargetObject = objp; + mTargetOffsetGlobal = offset; +} + +//----------------------------------------------------------------------------- +// setTargetPosGlobal() +//----------------------------------------------------------------------------- +void LLHUDEffectPointAt::setTargetPosGlobal(const LLVector3d &target_pos_global) +{ + mTargetObject = NULL; + mTargetOffsetGlobal = target_pos_global; +} + +//----------------------------------------------------------------------------- +// setPointAt() +// called by agent logic to set look at behavior locally, and propagate to sim +//----------------------------------------------------------------------------- +bool LLHUDEffectPointAt::setPointAt(EPointAtType target_type, LLViewerObject *object, LLVector3 position) +{ + if (!mSourceObject) + { + return false; + } + + if (target_type >= POINTAT_NUM_TARGETS) + { + LL_WARNS() << "Bad target_type " << (int)target_type << " - ignoring." << LL_ENDL; + return false; + } + + // must be same or higher priority than existing effect + if (POINTAT_PRIORITIES[target_type] < POINTAT_PRIORITIES[mTargetType]) + { + return false; + } + + F32 current_time = mTimer.getElapsedTimeF32(); + + // type of pointat behavior or target object has changed + bool targetTypeChanged = (target_type != mTargetType) || + (object != mTargetObject); + + bool targetPosChanged = (dist_vec_squared(position, mLastSentOffsetGlobal) > MIN_DELTAPOS_FOR_UPDATE_SQUARED) && + ((current_time - mLastSendTime) > (1.f / MAX_SENDS_PER_SEC)); + + if (targetTypeChanged || targetPosChanged) + { + mLastSentOffsetGlobal = position; + setDuration(POINTAT_TIMEOUTS[target_type]); + setNeedsSendToSim(true); +// LL_INFOS() << "Sending pointat data" << LL_ENDL; + } + + if (target_type == POINTAT_TARGET_CLEAR) + { + clearPointAtTarget(); + } + else + { + mTargetType = target_type; + mTargetObject = object; + if (object) + { + mTargetOffsetGlobal.setVec(position); + } + else + { + mTargetOffsetGlobal = gAgent.getPosGlobalFromAgent(position); + } + + mKillTime = mTimer.getElapsedTimeF32() + mDuration; + + //set up requisite animation data + update(); + } + + return true; +} + +//----------------------------------------------------------------------------- +// clearPointAtTarget() +//----------------------------------------------------------------------------- +void LLHUDEffectPointAt::clearPointAtTarget() +{ + mTargetObject = NULL; + mTargetOffsetGlobal.clearVec(); + mTargetType = POINTAT_TARGET_NONE; +} + +//----------------------------------------------------------------------------- +// markDead() +//----------------------------------------------------------------------------- +void LLHUDEffectPointAt::markDead() +{ + if (!mSourceObject.isNull() && mSourceObject->isAvatar()) + { + ((LLVOAvatar*)(LLViewerObject*)mSourceObject)->removeAnimationData("PointAtPoint"); + } + + clearPointAtTarget(); + LLHUDEffect::markDead(); +} + +void LLHUDEffectPointAt::setSourceObject(LLViewerObject* objectp) +{ + // restrict source objects to avatars + if (objectp && objectp->isAvatar()) + { + LLHUDEffect::setSourceObject(objectp); + } +} + +//----------------------------------------------------------------------------- +// render() +//----------------------------------------------------------------------------- +void LLHUDEffectPointAt::render() +{ + update(); + if (sDebugPointAt && mTargetType != POINTAT_TARGET_NONE) + { + //LLGLDisable gls_stencil(GL_STENCIL_TEST); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + LLVector3 target = mTargetPos + mSourceObject->getRenderPosition(); + gGL.pushMatrix(); + gGL.translatef(target.mV[VX], target.mV[VY], target.mV[VZ]); + gGL.scalef(0.3f, 0.3f, 0.3f); + gGL.begin(LLRender::LINES); + { + gGL.color3f(1.f, 0.f, 0.f); + gGL.vertex3f(-1.f, 0.f, 0.f); + gGL.vertex3f(1.f, 0.f, 0.f); + + gGL.vertex3f(0.f, -1.f, 0.f); + gGL.vertex3f(0.f, 1.f, 0.f); + + gGL.vertex3f(0.f, 0.f, -1.f); + gGL.vertex3f(0.f, 0.f, 1.f); + } gGL.end(); + gGL.popMatrix(); + } +} + +//----------------------------------------------------------------------------- +// update() +//----------------------------------------------------------------------------- +void LLHUDEffectPointAt::update() +{ + // If the target object is dead, set the target object to NULL + if (!mTargetObject.isNull() && mTargetObject->isDead()) + { + clearPointAtTarget(); + } + + if (mSourceObject.isNull() || mSourceObject->isDead()) + { + markDead(); + return; + } + + F32 time = mTimer.getElapsedTimeF32(); + + // clear out the effect if time is up + if (mKillTime != 0.f && time > mKillTime) + { + mTargetType = POINTAT_TARGET_NONE; + } + + if (mSourceObject->isAvatar()) + { + if (mTargetType == POINTAT_TARGET_NONE) + { + ((LLVOAvatar*)(LLViewerObject*)mSourceObject)->removeAnimationData("PointAtPoint"); + } + else + { + if (calcTargetPosition()) + { + ((LLVOAvatar*)(LLViewerObject*)mSourceObject)->startMotion(ANIM_AGENT_EDITING); + } + } + } +} + +//----------------------------------------------------------------------------- +// calcTargetPosition() +// returns whether we successfully calculated a finite target position. +//----------------------------------------------------------------------------- +bool LLHUDEffectPointAt::calcTargetPosition() +{ + LLViewerObject *targetObject = (LLViewerObject *)mTargetObject; + LLVector3 local_offset; + + if (targetObject) + { + local_offset.setVec(mTargetOffsetGlobal); + } + else + { + local_offset = gAgent.getPosAgentFromGlobal(mTargetOffsetGlobal); + } + + if (targetObject && targetObject->mDrawable.notNull()) + { + LLQuaternion objRot; + if (targetObject->isAvatar()) + { + LLVOAvatar *avatarp = (LLVOAvatar *)targetObject; + mTargetPos = avatarp->mHeadp->getWorldPosition(); + objRot = avatarp->mPelvisp->getWorldRotation(); + } + else + { + if (targetObject->mDrawable->getGeneration() == -1) + { + mTargetPos = targetObject->getPositionAgent(); + objRot = targetObject->getWorldRotation(); + } + else + { + mTargetPos = targetObject->getRenderPosition(); + objRot = targetObject->getRenderRotation(); + } + } + + mTargetPos += (local_offset * objRot); + } + else + { + mTargetPos = local_offset; + } + + mTargetPos -= mSourceObject->getRenderPosition(); + + if (!llfinite(mTargetPos.lengthSquared())) + { + return false; + } + + if (mSourceObject->isAvatar()) + { + ((LLVOAvatar*)(LLViewerObject*)mSourceObject)->setAnimationData("PointAtPoint", (void *)&mTargetPos); + } + + return true; +} + +const LLVector3d LLHUDEffectPointAt::getPointAtPosGlobal() +{ + LLVector3d global_pos; + global_pos.setVec(mTargetPos); + if (mSourceObject.notNull()) + { + global_pos += mSourceObject->getPositionGlobal(); + } + + return global_pos; +} diff --git a/indra/newview/llhudeffectpointat.h b/indra/newview/llhudeffectpointat.h index bd59311d05..34257d8da9 100644 --- a/indra/newview/llhudeffectpointat.h +++ b/indra/newview/llhudeffectpointat.h @@ -1,84 +1,84 @@ -/** - * @file llhudeffectpointat.h - * @brief LLHUDEffectPointAt class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLHUDEFFECTPOINTAT_H -#define LL_LLHUDEFFECTPOINTAT_H - -#include "llframetimer.h" -#include "llhudeffect.h" - -class LLViewerObject; -class LLVOAvatar; - -typedef enum e_pointat_type -{ - POINTAT_TARGET_NONE, - POINTAT_TARGET_SELECT, - POINTAT_TARGET_GRAB, - POINTAT_TARGET_CLEAR, - POINTAT_NUM_TARGETS -} EPointAtType; - -class LLHUDEffectPointAt : public LLHUDEffect -{ -public: - friend class LLHUDObject; - - /*virtual*/ void markDead(); - /*virtual*/ void setSourceObject(LLViewerObject* objectp); - - bool setPointAt(EPointAtType target_type, LLViewerObject *object, LLVector3 position); - void clearPointAtTarget(); - - EPointAtType getPointAtType() { return mTargetType; } - const LLVector3& getPointAtPosAgent() { return mTargetPos; } - const LLVector3d getPointAtPosGlobal(); -protected: - LLHUDEffectPointAt(const U8 type); - ~LLHUDEffectPointAt(); - - /*virtual*/ void render(); - /*virtual*/ void packData(LLMessageSystem *mesgsys); - /*virtual*/ void unpackData(LLMessageSystem *mesgsys, S32 blocknum); - - // lookat behavior has either target position or target object with offset - void setTargetObjectAndOffset(LLViewerObject *objp, LLVector3d offset); - void setTargetPosGlobal(const LLVector3d &target_pos_global); - bool calcTargetPosition(); - void update(); -public: - static bool sDebugPointAt; -private: - EPointAtType mTargetType; - LLVector3d mTargetOffsetGlobal; - LLVector3 mLastSentOffsetGlobal; - F32 mKillTime; - LLFrameTimer mTimer; - LLVector3 mTargetPos; - F32 mLastSendTime; -}; - -#endif // LL_LLHUDEFFECTPOINTAT_H +/** + * @file llhudeffectpointat.h + * @brief LLHUDEffectPointAt class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLHUDEFFECTPOINTAT_H +#define LL_LLHUDEFFECTPOINTAT_H + +#include "llframetimer.h" +#include "llhudeffect.h" + +class LLViewerObject; +class LLVOAvatar; + +typedef enum e_pointat_type +{ + POINTAT_TARGET_NONE, + POINTAT_TARGET_SELECT, + POINTAT_TARGET_GRAB, + POINTAT_TARGET_CLEAR, + POINTAT_NUM_TARGETS +} EPointAtType; + +class LLHUDEffectPointAt : public LLHUDEffect +{ +public: + friend class LLHUDObject; + + /*virtual*/ void markDead(); + /*virtual*/ void setSourceObject(LLViewerObject* objectp); + + bool setPointAt(EPointAtType target_type, LLViewerObject *object, LLVector3 position); + void clearPointAtTarget(); + + EPointAtType getPointAtType() { return mTargetType; } + const LLVector3& getPointAtPosAgent() { return mTargetPos; } + const LLVector3d getPointAtPosGlobal(); +protected: + LLHUDEffectPointAt(const U8 type); + ~LLHUDEffectPointAt(); + + /*virtual*/ void render(); + /*virtual*/ void packData(LLMessageSystem *mesgsys); + /*virtual*/ void unpackData(LLMessageSystem *mesgsys, S32 blocknum); + + // lookat behavior has either target position or target object with offset + void setTargetObjectAndOffset(LLViewerObject *objp, LLVector3d offset); + void setTargetPosGlobal(const LLVector3d &target_pos_global); + bool calcTargetPosition(); + void update(); +public: + static bool sDebugPointAt; +private: + EPointAtType mTargetType; + LLVector3d mTargetOffsetGlobal; + LLVector3 mLastSentOffsetGlobal; + F32 mKillTime; + LLFrameTimer mTimer; + LLVector3 mTargetPos; + F32 mLastSendTime; +}; + +#endif // LL_LLHUDEFFECTPOINTAT_H diff --git a/indra/newview/llhudeffecttrail.cpp b/indra/newview/llhudeffecttrail.cpp index 3a5a4afb5b..48abd730c9 100644 --- a/indra/newview/llhudeffecttrail.cpp +++ b/indra/newview/llhudeffecttrail.cpp @@ -1,280 +1,280 @@ -/** - * @file llhudeffecttrail.cpp - * @brief LLHUDEffectSpiral class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llhudeffecttrail.h" - -#include "llviewercontrol.h" -#include "message.h" - -#include "llagent.h" -#include "llbox.h" -#include "lldrawable.h" -#include "llhudrender.h" -#include "llviewertexturelist.h" -#include "llviewerobjectlist.h" -#include "llviewerpartsim.h" -#include "llviewerpartsource.h" -#include "llvoavatar.h" -#include "llworld.h" - -LLHUDEffectSpiral::LLHUDEffectSpiral(const U8 type) : LLHUDEffect(type), mbInit(false) -{ - mKillTime = 10.f; - mVMag = 1.f; - mVOffset = 0.f; - mInitialRadius = 1.f; - mFinalRadius = 1.f; - mSpinRate = 10.f; - mFlickerRate = 50.f; - mScaleBase = 0.1f; - mScaleVar = 0.f; - - mFadeInterp.setStartTime(0.f); - mFadeInterp.setEndTime(mKillTime); - mFadeInterp.setStartVal(1.f); - mFadeInterp.setEndVal(1.f); -} - -LLHUDEffectSpiral::~LLHUDEffectSpiral() -{ -} - -void LLHUDEffectSpiral::markDead() -{ - if (mPartSourcep) - { - mPartSourcep->setDead(); - mPartSourcep = NULL; - } - LLHUDEffect::markDead(); -} - -void LLHUDEffectSpiral::packData(LLMessageSystem *mesgsys) -{ - if (!mSourceObject) - { - //LL_WARNS() << "Missing object in trail pack!" << LL_ENDL; - } - LLHUDEffect::packData(mesgsys); - - U8 packed_data[56]; - memset(packed_data, 0, 56); - - if (mSourceObject) - { - htolememcpy(packed_data, mSourceObject->mID.mData, MVT_LLUUID, 16); - } - if (mTargetObject) - { - htolememcpy(packed_data + 16, mTargetObject->mID.mData, MVT_LLUUID, 16); - } - if (!mPositionGlobal.isExactlyZero()) - { - htolememcpy(packed_data + 32, mPositionGlobal.mdV, MVT_LLVector3d, 24); - } - mesgsys->addBinaryDataFast(_PREHASH_TypeData, packed_data, 56); -} - -void LLHUDEffectSpiral::unpackData(LLMessageSystem *mesgsys, S32 blocknum) -{ - const size_t EFFECT_SIZE = 56; - U8 packed_data[EFFECT_SIZE]; - - LLHUDEffect::unpackData(mesgsys, blocknum); - LLUUID object_id, target_object_id; - size_t size = mesgsys->getSizeFast(_PREHASH_Effect, blocknum, _PREHASH_TypeData); - if (size != EFFECT_SIZE) - { - LL_WARNS() << "Spiral effect with bad size " << size << LL_ENDL; - return; - } - mesgsys->getBinaryDataFast(_PREHASH_Effect, _PREHASH_TypeData, - packed_data, EFFECT_SIZE, blocknum, EFFECT_SIZE); - - htolememcpy(object_id.mData, packed_data, MVT_LLUUID, 16); - htolememcpy(target_object_id.mData, packed_data + 16, MVT_LLUUID, 16); - htolememcpy(mPositionGlobal.mdV, packed_data + 32, MVT_LLVector3d, 24); - - LLViewerObject *objp = NULL; - - if (object_id.isNull()) - { - setSourceObject(NULL); - } - else - { - LLViewerObject *objp = gObjectList.findObject(object_id); - if (objp) - { - setSourceObject(objp); - } - else - { - // We don't have this object, kill this effect - markDead(); - return; - } - } - - if (target_object_id.isNull()) - { - setTargetObject(NULL); - } - else - { - objp = gObjectList.findObject(target_object_id); - if (objp) - { - setTargetObject(objp); - } - else - { - // We don't have this object, kill this effect - markDead(); - return; - } - } - - triggerLocal(); -} - -void LLHUDEffectSpiral::triggerLocal() -{ - mKillTime = mTimer.getElapsedTimeF32() + mDuration; - - bool show_beam = gSavedSettings.getBOOL("ShowSelectionBeam"); - - LLColor4 color; - color.setVec(mColor); - - if (!mPartSourcep) - { - if (!mTargetObject.isNull() && !mSourceObject.isNull()) - { - if (show_beam) - { - LLPointer psb = new LLViewerPartSourceBeam; - psb->setColor(color); - psb->setSourceObject(mSourceObject); - psb->setTargetObject(mTargetObject); - psb->setOwnerUUID(gAgent.getID()); - LLViewerPartSim::getInstance()->addPartSource(psb); - mPartSourcep = psb; - } - } - else - { - if (!mSourceObject.isNull() && !mPositionGlobal.isExactlyZero()) - { - if (show_beam) - { - LLPointer psb = new LLViewerPartSourceBeam; - psb->setSourceObject(mSourceObject); - psb->setTargetObject(NULL); - psb->setColor(color); - psb->mLKGTargetPosGlobal = mPositionGlobal; - psb->setOwnerUUID(gAgent.getID()); - LLViewerPartSim::getInstance()->addPartSource(psb); - mPartSourcep = psb; - } - } - else - { - LLVector3 pos; - if (mSourceObject) - { - pos = mSourceObject->getPositionAgent(); - } - else - { - pos = gAgent.getPosAgentFromGlobal(mPositionGlobal); - } - LLPointer pss = new LLViewerPartSourceSpiral(pos); - if (!mSourceObject.isNull()) - { - pss->setSourceObject(mSourceObject); - } - pss->setColor(color); - pss->setOwnerUUID(gAgent.getID()); - LLViewerPartSim::getInstance()->addPartSource(pss); - mPartSourcep = pss; - } - } - } - else - { - LLPointer& ps = mPartSourcep; - if (mPartSourcep->getType() == LLViewerPartSource::LL_PART_SOURCE_BEAM) - { - LLViewerPartSourceBeam *psb = (LLViewerPartSourceBeam *)ps.get(); - psb->setSourceObject(mSourceObject); - psb->setTargetObject(mTargetObject); - psb->setColor(color); - if (mTargetObject.isNull()) - { - psb->mLKGTargetPosGlobal = mPositionGlobal; - } - } - else - { - LLViewerPartSourceSpiral *pss = (LLViewerPartSourceSpiral *)ps.get(); - pss->setSourceObject(mSourceObject); - } - } - - mbInit = true; -} - -void LLHUDEffectSpiral::setTargetObject(LLViewerObject *objp) -{ - if (objp == mTargetObject) - { - return; - } - - mTargetObject = objp; -} - -void LLHUDEffectSpiral::render() -{ - F32 time = mTimer.getElapsedTimeF32(); - - if ((!mSourceObject.isNull() && mSourceObject->isDead()) || - (!mTargetObject.isNull() && mTargetObject->isDead()) || - mKillTime < time || - (!mPartSourcep.isNull() && !gSavedSettings.getBOOL("ShowSelectionBeam")) ) - { - markDead(); - return; - } -} - -void LLHUDEffectSpiral::renderForTimer() -{ - render(); -} +/** + * @file llhudeffecttrail.cpp + * @brief LLHUDEffectSpiral class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llhudeffecttrail.h" + +#include "llviewercontrol.h" +#include "message.h" + +#include "llagent.h" +#include "llbox.h" +#include "lldrawable.h" +#include "llhudrender.h" +#include "llviewertexturelist.h" +#include "llviewerobjectlist.h" +#include "llviewerpartsim.h" +#include "llviewerpartsource.h" +#include "llvoavatar.h" +#include "llworld.h" + +LLHUDEffectSpiral::LLHUDEffectSpiral(const U8 type) : LLHUDEffect(type), mbInit(false) +{ + mKillTime = 10.f; + mVMag = 1.f; + mVOffset = 0.f; + mInitialRadius = 1.f; + mFinalRadius = 1.f; + mSpinRate = 10.f; + mFlickerRate = 50.f; + mScaleBase = 0.1f; + mScaleVar = 0.f; + + mFadeInterp.setStartTime(0.f); + mFadeInterp.setEndTime(mKillTime); + mFadeInterp.setStartVal(1.f); + mFadeInterp.setEndVal(1.f); +} + +LLHUDEffectSpiral::~LLHUDEffectSpiral() +{ +} + +void LLHUDEffectSpiral::markDead() +{ + if (mPartSourcep) + { + mPartSourcep->setDead(); + mPartSourcep = NULL; + } + LLHUDEffect::markDead(); +} + +void LLHUDEffectSpiral::packData(LLMessageSystem *mesgsys) +{ + if (!mSourceObject) + { + //LL_WARNS() << "Missing object in trail pack!" << LL_ENDL; + } + LLHUDEffect::packData(mesgsys); + + U8 packed_data[56]; + memset(packed_data, 0, 56); + + if (mSourceObject) + { + htolememcpy(packed_data, mSourceObject->mID.mData, MVT_LLUUID, 16); + } + if (mTargetObject) + { + htolememcpy(packed_data + 16, mTargetObject->mID.mData, MVT_LLUUID, 16); + } + if (!mPositionGlobal.isExactlyZero()) + { + htolememcpy(packed_data + 32, mPositionGlobal.mdV, MVT_LLVector3d, 24); + } + mesgsys->addBinaryDataFast(_PREHASH_TypeData, packed_data, 56); +} + +void LLHUDEffectSpiral::unpackData(LLMessageSystem *mesgsys, S32 blocknum) +{ + const size_t EFFECT_SIZE = 56; + U8 packed_data[EFFECT_SIZE]; + + LLHUDEffect::unpackData(mesgsys, blocknum); + LLUUID object_id, target_object_id; + size_t size = mesgsys->getSizeFast(_PREHASH_Effect, blocknum, _PREHASH_TypeData); + if (size != EFFECT_SIZE) + { + LL_WARNS() << "Spiral effect with bad size " << size << LL_ENDL; + return; + } + mesgsys->getBinaryDataFast(_PREHASH_Effect, _PREHASH_TypeData, + packed_data, EFFECT_SIZE, blocknum, EFFECT_SIZE); + + htolememcpy(object_id.mData, packed_data, MVT_LLUUID, 16); + htolememcpy(target_object_id.mData, packed_data + 16, MVT_LLUUID, 16); + htolememcpy(mPositionGlobal.mdV, packed_data + 32, MVT_LLVector3d, 24); + + LLViewerObject *objp = NULL; + + if (object_id.isNull()) + { + setSourceObject(NULL); + } + else + { + LLViewerObject *objp = gObjectList.findObject(object_id); + if (objp) + { + setSourceObject(objp); + } + else + { + // We don't have this object, kill this effect + markDead(); + return; + } + } + + if (target_object_id.isNull()) + { + setTargetObject(NULL); + } + else + { + objp = gObjectList.findObject(target_object_id); + if (objp) + { + setTargetObject(objp); + } + else + { + // We don't have this object, kill this effect + markDead(); + return; + } + } + + triggerLocal(); +} + +void LLHUDEffectSpiral::triggerLocal() +{ + mKillTime = mTimer.getElapsedTimeF32() + mDuration; + + bool show_beam = gSavedSettings.getBOOL("ShowSelectionBeam"); + + LLColor4 color; + color.setVec(mColor); + + if (!mPartSourcep) + { + if (!mTargetObject.isNull() && !mSourceObject.isNull()) + { + if (show_beam) + { + LLPointer psb = new LLViewerPartSourceBeam; + psb->setColor(color); + psb->setSourceObject(mSourceObject); + psb->setTargetObject(mTargetObject); + psb->setOwnerUUID(gAgent.getID()); + LLViewerPartSim::getInstance()->addPartSource(psb); + mPartSourcep = psb; + } + } + else + { + if (!mSourceObject.isNull() && !mPositionGlobal.isExactlyZero()) + { + if (show_beam) + { + LLPointer psb = new LLViewerPartSourceBeam; + psb->setSourceObject(mSourceObject); + psb->setTargetObject(NULL); + psb->setColor(color); + psb->mLKGTargetPosGlobal = mPositionGlobal; + psb->setOwnerUUID(gAgent.getID()); + LLViewerPartSim::getInstance()->addPartSource(psb); + mPartSourcep = psb; + } + } + else + { + LLVector3 pos; + if (mSourceObject) + { + pos = mSourceObject->getPositionAgent(); + } + else + { + pos = gAgent.getPosAgentFromGlobal(mPositionGlobal); + } + LLPointer pss = new LLViewerPartSourceSpiral(pos); + if (!mSourceObject.isNull()) + { + pss->setSourceObject(mSourceObject); + } + pss->setColor(color); + pss->setOwnerUUID(gAgent.getID()); + LLViewerPartSim::getInstance()->addPartSource(pss); + mPartSourcep = pss; + } + } + } + else + { + LLPointer& ps = mPartSourcep; + if (mPartSourcep->getType() == LLViewerPartSource::LL_PART_SOURCE_BEAM) + { + LLViewerPartSourceBeam *psb = (LLViewerPartSourceBeam *)ps.get(); + psb->setSourceObject(mSourceObject); + psb->setTargetObject(mTargetObject); + psb->setColor(color); + if (mTargetObject.isNull()) + { + psb->mLKGTargetPosGlobal = mPositionGlobal; + } + } + else + { + LLViewerPartSourceSpiral *pss = (LLViewerPartSourceSpiral *)ps.get(); + pss->setSourceObject(mSourceObject); + } + } + + mbInit = true; +} + +void LLHUDEffectSpiral::setTargetObject(LLViewerObject *objp) +{ + if (objp == mTargetObject) + { + return; + } + + mTargetObject = objp; +} + +void LLHUDEffectSpiral::render() +{ + F32 time = mTimer.getElapsedTimeF32(); + + if ((!mSourceObject.isNull() && mSourceObject->isDead()) || + (!mTargetObject.isNull() && mTargetObject->isDead()) || + mKillTime < time || + (!mPartSourcep.isNull() && !gSavedSettings.getBOOL("ShowSelectionBeam")) ) + { + markDead(); + return; + } +} + +void LLHUDEffectSpiral::renderForTimer() +{ + render(); +} diff --git a/indra/newview/llhudeffecttrail.h b/indra/newview/llhudeffecttrail.h index 872f4b3565..042dfd8f20 100644 --- a/indra/newview/llhudeffecttrail.h +++ b/indra/newview/llhudeffecttrail.h @@ -1,95 +1,95 @@ -/** - * @file llhudeffecttrail.h - * @brief LLHUDEffectSpiral class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLHUDEFFECTTRAIL_H -#define LL_LLHUDEFFECTTRAIL_H - -#include "llhudeffect.h" - -#include "llframetimer.h" - -#include "llinterp.h" -#include "llviewerpartsim.h" - -class LLViewerObject; - -const U32 NUM_TRAIL_POINTS = 40; - - -class LLHUDEffectSpiral : public LLHUDEffect -{ -public: - /*virtual*/ void markDead(); - /*virtual*/ void setTargetObject(LLViewerObject* objectp); - void setVMag(F32 vmag) { mVMag = vmag; } - void setVOffset(F32 offset) { mVOffset = offset; } - void setInitialRadius(F32 radius) { mInitialRadius = radius; } - void setFinalRadius(F32 radius) { mFinalRadius = radius; } - void setScaleBase(F32 scale) { mScaleBase = scale; } - void setScaleVar(F32 scale) { mScaleVar = scale; } - void setSpinRate(F32 rate) { mSpinRate = rate; } - void setFlickerRate(F32 rate) { mFlickerRate = rate; } - - // Start the effect playing locally. - void triggerLocal(); - - friend class LLHUDObject; -protected: - LLHUDEffectSpiral(const U8 type); - ~LLHUDEffectSpiral(); - - /*virtual*/ void render(); - /*virtual*/ void renderForTimer(); - /*virtual*/ void packData(LLMessageSystem *mesgsys); - /*virtual*/ void unpackData(LLMessageSystem *mesgsys, S32 blocknum); -private: - /* - void setupParticle(const S32 i, const F32 start_time); - - - LLInterpExp mRadius[NUM_TRAIL_POINTS]; - LLInterpLinear mDistance[NUM_TRAIL_POINTS]; - LLInterpLinear mScale[NUM_TRAIL_POINTS]; - F32 mOffset[NUM_TRAIL_POINTS]; - */ - - bool mbInit; - LLPointer mPartSourcep; - - F32 mKillTime; - F32 mVMag; - F32 mVOffset; - F32 mInitialRadius; - F32 mFinalRadius; - F32 mSpinRate; - F32 mFlickerRate; - F32 mScaleBase; - F32 mScaleVar; - LLFrameTimer mTimer; - LLInterpLinear mFadeInterp; -}; - -#endif // LL_LLHUDEFFECTGLOW_H +/** + * @file llhudeffecttrail.h + * @brief LLHUDEffectSpiral class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLHUDEFFECTTRAIL_H +#define LL_LLHUDEFFECTTRAIL_H + +#include "llhudeffect.h" + +#include "llframetimer.h" + +#include "llinterp.h" +#include "llviewerpartsim.h" + +class LLViewerObject; + +const U32 NUM_TRAIL_POINTS = 40; + + +class LLHUDEffectSpiral : public LLHUDEffect +{ +public: + /*virtual*/ void markDead(); + /*virtual*/ void setTargetObject(LLViewerObject* objectp); + void setVMag(F32 vmag) { mVMag = vmag; } + void setVOffset(F32 offset) { mVOffset = offset; } + void setInitialRadius(F32 radius) { mInitialRadius = radius; } + void setFinalRadius(F32 radius) { mFinalRadius = radius; } + void setScaleBase(F32 scale) { mScaleBase = scale; } + void setScaleVar(F32 scale) { mScaleVar = scale; } + void setSpinRate(F32 rate) { mSpinRate = rate; } + void setFlickerRate(F32 rate) { mFlickerRate = rate; } + + // Start the effect playing locally. + void triggerLocal(); + + friend class LLHUDObject; +protected: + LLHUDEffectSpiral(const U8 type); + ~LLHUDEffectSpiral(); + + /*virtual*/ void render(); + /*virtual*/ void renderForTimer(); + /*virtual*/ void packData(LLMessageSystem *mesgsys); + /*virtual*/ void unpackData(LLMessageSystem *mesgsys, S32 blocknum); +private: + /* + void setupParticle(const S32 i, const F32 start_time); + + + LLInterpExp mRadius[NUM_TRAIL_POINTS]; + LLInterpLinear mDistance[NUM_TRAIL_POINTS]; + LLInterpLinear mScale[NUM_TRAIL_POINTS]; + F32 mOffset[NUM_TRAIL_POINTS]; + */ + + bool mbInit; + LLPointer mPartSourcep; + + F32 mKillTime; + F32 mVMag; + F32 mVOffset; + F32 mInitialRadius; + F32 mFinalRadius; + F32 mSpinRate; + F32 mFlickerRate; + F32 mScaleBase; + F32 mScaleVar; + LLFrameTimer mTimer; + LLInterpLinear mFadeInterp; +}; + +#endif // LL_LLHUDEFFECTGLOW_H diff --git a/indra/newview/llhudicon.cpp b/indra/newview/llhudicon.cpp index 79b920d70b..8fa4118a40 100644 --- a/indra/newview/llhudicon.cpp +++ b/indra/newview/llhudicon.cpp @@ -1,348 +1,348 @@ -/** - * @file llhudicon.cpp - * @brief LLHUDIcon class implementation - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llhudicon.h" - -#include "llgl.h" -#include "llrender.h" - -#include "llviewerobject.h" -#include "lldrawable.h" -#include "llvector4a.h" -#include "llviewercamera.h" -#include "llviewertexture.h" -#include "llviewerwindow.h" - -//----------------------------------------------------------------------------- -// Local consts -//----------------------------------------------------------------------------- -const F32 ANIM_TIME = 0.4f; -const F32 DIST_START_FADE = 15.f; -const F32 DIST_END_FADE = 30.f; -const F32 MAX_VISIBLE_TIME = 15.f; -const F32 FADE_OUT_TIME = 1.f; - -//----------------------------------------------------------------------------- -// Utility functions -//----------------------------------------------------------------------------- -static F32 calc_bouncy_animation(F32 x) -{ - return -(cosf(x * F_PI * 2.5f - F_PI_BY_TWO))*(0.4f + x * -0.1f) + x * 1.3f; -} - - -//----------------------------------------------------------------------------- -// static declarations -//----------------------------------------------------------------------------- -LLHUDIcon::icon_instance_t LLHUDIcon::sIconInstances; - -LLHUDIcon::LLHUDIcon(const U8 type) : - LLHUDObject(type), - mImagep(NULL), - mScale(0.1f), - mHidden(false) -{ - sIconInstances.push_back(this); -} - -LLHUDIcon::~LLHUDIcon() -{ - mImagep = NULL; -} - -void LLHUDIcon::render() -{ - LLGLSUIDefault texture_state; - LLGLDepthTest gls_depth(GL_TRUE); - //LLGLDisable gls_stencil(GL_STENCIL_TEST); - - if (mHidden) - return; - - if (mSourceObject.isNull() || mImagep.isNull()) - { - markDead(); - return; - } - - LLVector3 obj_position = mSourceObject->getRenderPosition(); - - // put icon above object, and in front - // RN: don't use drawable radius, it's fricking HUGE - LLViewerCamera* camera = LLViewerCamera::getInstance(); - LLVector3 icon_relative_pos = (camera->getUpAxis() * ~mSourceObject->getRenderRotation()); - icon_relative_pos.abs(); - - F32 distance_scale = llmin(mSourceObject->getScale().mV[VX] / icon_relative_pos.mV[VX], - mSourceObject->getScale().mV[VY] / icon_relative_pos.mV[VY], - mSourceObject->getScale().mV[VZ] / icon_relative_pos.mV[VZ]); - F32 up_distance = 0.5f * distance_scale; - LLVector3 icon_position = obj_position + (up_distance * camera->getUpAxis()) * 1.2f; - - LLVector3 icon_to_cam = LLViewerCamera::getInstance()->getOrigin() - icon_position; - icon_to_cam.normVec(); - - icon_position += icon_to_cam * mSourceObject->mDrawable->getRadius() * 1.1f; - - mDistance = dist_vec(icon_position, camera->getOrigin()); - - F32 alpha_factor = clamp_rescale(mDistance, DIST_START_FADE, DIST_END_FADE, 1.f, 0.f); - - LLVector3 x_pixel_vec; - LLVector3 y_pixel_vec; - - camera->getPixelVectors(icon_position, y_pixel_vec, x_pixel_vec); - - F32 scale_factor = 1.f; - if (mAnimTimer.getElapsedTimeF32() < ANIM_TIME) - { - scale_factor = llmax(0.f, calc_bouncy_animation(mAnimTimer.getElapsedTimeF32() / ANIM_TIME)); - } - - F32 time_elapsed = mLifeTimer.getElapsedTimeF32(); - if (time_elapsed > MAX_VISIBLE_TIME) - { - markDead(); - return; - } - - if (time_elapsed > MAX_VISIBLE_TIME - FADE_OUT_TIME) - { - alpha_factor *= clamp_rescale(time_elapsed, MAX_VISIBLE_TIME - FADE_OUT_TIME, MAX_VISIBLE_TIME, 1.f, 0.f); - } - - F32 image_aspect = (F32)mImagep->getFullWidth() / (F32)mImagep->getFullHeight() ; - LLVector3 x_scale = image_aspect * (F32)gViewerWindow->getWindowHeightScaled() * mScale * scale_factor * x_pixel_vec; - LLVector3 y_scale = (F32)gViewerWindow->getWindowHeightScaled() * mScale * scale_factor * y_pixel_vec; - - LLVector3 lower_left = icon_position - (x_scale * 0.5f); - LLVector3 lower_right = icon_position + (x_scale * 0.5f); - LLVector3 upper_left = icon_position - (x_scale * 0.5f) + y_scale; - LLVector3 upper_right = icon_position + (x_scale * 0.5f) + y_scale; - - { - LLColor4 icon_color = LLColor4::white; - icon_color.mV[VALPHA] = alpha_factor; - gGL.color4fv(icon_color.mV); - gGL.getTexUnit(0)->bind(mImagep); - } - - gGL.begin(LLRender::QUADS); - { - gGL.texCoord2f(0.f, 1.f); - gGL.vertex3fv(upper_left.mV); - gGL.texCoord2f(0.f, 0.f); - gGL.vertex3fv(lower_left.mV); - gGL.texCoord2f(1.f, 0.f); - gGL.vertex3fv(lower_right.mV); - gGL.texCoord2f(1.f, 1.f); - gGL.vertex3fv(upper_right.mV); - } - gGL.end(); -} - -void LLHUDIcon::setImage(LLViewerTexture* imagep) -{ - mImagep = imagep; - mImagep->setAddressMode(LLTexUnit::TAM_CLAMP); -} - -void LLHUDIcon::setScale(F32 fraction_of_fov) -{ - mScale = fraction_of_fov; -} - -void LLHUDIcon::markDead() -{ - if (mSourceObject) - { - mSourceObject->clearIcon(); - } - LLHUDObject::markDead(); -} - -bool LLHUDIcon::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, LLVector4a* intersection) -{ - if (mHidden) - return false; - - if (mSourceObject.isNull() || mImagep.isNull()) - { - markDead(); - return false; - } - - LLVector3 obj_position = mSourceObject->getRenderPosition(); - - // put icon above object, and in front - // RN: don't use drawable radius, it's fricking HUGE - LLViewerCamera* camera = LLViewerCamera::getInstance(); - LLVector3 icon_relative_pos = (camera->getUpAxis() * ~mSourceObject->getRenderRotation()); - icon_relative_pos.abs(); - - F32 distance_scale = llmin(mSourceObject->getScale().mV[VX] / icon_relative_pos.mV[VX], - mSourceObject->getScale().mV[VY] / icon_relative_pos.mV[VY], - mSourceObject->getScale().mV[VZ] / icon_relative_pos.mV[VZ]); - F32 up_distance = 0.5f * distance_scale; - LLVector3 icon_position = obj_position + (up_distance * camera->getUpAxis()) * 1.2f; - - LLVector3 icon_to_cam = LLViewerCamera::getInstance()->getOrigin() - icon_position; - icon_to_cam.normVec(); - - icon_position += icon_to_cam * mSourceObject->mDrawable->getRadius() * 1.1f; - - mDistance = dist_vec(icon_position, camera->getOrigin()); - - LLVector3 x_pixel_vec; - LLVector3 y_pixel_vec; - - camera->getPixelVectors(icon_position, y_pixel_vec, x_pixel_vec); - - F32 scale_factor = 1.f; - if (mAnimTimer.getElapsedTimeF32() < ANIM_TIME) - { - scale_factor = llmax(0.f, calc_bouncy_animation(mAnimTimer.getElapsedTimeF32() / ANIM_TIME)); - } - - F32 time_elapsed = mLifeTimer.getElapsedTimeF32(); - if (time_elapsed > MAX_VISIBLE_TIME) - { - markDead(); - return false; - } - - F32 image_aspect = (F32)mImagep->getFullWidth() / (F32)mImagep->getFullHeight() ; - LLVector3 x_scale = image_aspect * (F32)gViewerWindow->getWindowHeightScaled() * mScale * scale_factor * x_pixel_vec; - LLVector3 y_scale = (F32)gViewerWindow->getWindowHeightScaled() * mScale * scale_factor * y_pixel_vec; - - LLVector4a x_scalea; - LLVector4a icon_positiona; - LLVector4a y_scalea; - - x_scalea.load3(x_scale.mV); - x_scalea.mul(0.5f); - y_scalea.load3(y_scale.mV); - - icon_positiona.load3(icon_position.mV); - - LLVector4a lower_left; - lower_left.setSub(icon_positiona, x_scalea); - LLVector4a lower_right; - lower_right.setAdd(icon_positiona, x_scalea); - LLVector4a upper_left; - upper_left.setAdd(lower_left, y_scalea); - LLVector4a upper_right; - upper_right.setAdd(lower_right, y_scalea); - - LLVector4a dir; - dir.setSub(end, start); - - F32 a,b,t; - - if (LLTriangleRayIntersect(upper_right, upper_left, lower_right, start, dir, a,b,t) || - LLTriangleRayIntersect(upper_left, lower_left, lower_right, start, dir, a,b,t)) - { - if (intersection) - { - dir.mul(t); - intersection->setAdd(start, dir); - } - return true; - } - - return false; -} - -//static -LLHUDIcon* LLHUDIcon::lineSegmentIntersectAll(const LLVector4a& start, const LLVector4a& end, LLVector4a* intersection) -{ - icon_instance_t::iterator icon_it; - - LLVector4a local_end = end; - LLVector4a position; - - LLHUDIcon* ret = NULL; - for(icon_it = sIconInstances.begin(); icon_it != sIconInstances.end(); ++icon_it) - { - LLHUDIcon* icon = *icon_it; - if (icon->lineSegmentIntersect(start, local_end, &position)) - { - ret = icon; - if (intersection) - { - *intersection = position; - } - local_end = position; - } - } - - return ret; -} - - - //static -void LLHUDIcon::updateAll() -{ - cleanupDeadIcons(); -} - -//static -bool LLHUDIcon::iconsNearby() -{ - return !sIconInstances.empty(); -} - -//static -void LLHUDIcon::cleanupDeadIcons() -{ - icon_instance_t::iterator icon_it; - - icon_instance_t icons_to_erase; - for(icon_it = sIconInstances.begin(); icon_it != sIconInstances.end(); ++icon_it) - { - if ((*icon_it)->mDead) - { - icons_to_erase.push_back(*icon_it); - } - } - - for(icon_it = icons_to_erase.begin(); icon_it != icons_to_erase.end(); ++icon_it) - { - icon_instance_t::iterator found_it = std::find(sIconInstances.begin(), sIconInstances.end(), *icon_it); - if (found_it != sIconInstances.end()) - { - sIconInstances.erase(found_it); - } - } -} - -//static -S32 LLHUDIcon::getNumInstances() -{ - return (S32)sIconInstances.size(); -} +/** + * @file llhudicon.cpp + * @brief LLHUDIcon class implementation + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llhudicon.h" + +#include "llgl.h" +#include "llrender.h" + +#include "llviewerobject.h" +#include "lldrawable.h" +#include "llvector4a.h" +#include "llviewercamera.h" +#include "llviewertexture.h" +#include "llviewerwindow.h" + +//----------------------------------------------------------------------------- +// Local consts +//----------------------------------------------------------------------------- +const F32 ANIM_TIME = 0.4f; +const F32 DIST_START_FADE = 15.f; +const F32 DIST_END_FADE = 30.f; +const F32 MAX_VISIBLE_TIME = 15.f; +const F32 FADE_OUT_TIME = 1.f; + +//----------------------------------------------------------------------------- +// Utility functions +//----------------------------------------------------------------------------- +static F32 calc_bouncy_animation(F32 x) +{ + return -(cosf(x * F_PI * 2.5f - F_PI_BY_TWO))*(0.4f + x * -0.1f) + x * 1.3f; +} + + +//----------------------------------------------------------------------------- +// static declarations +//----------------------------------------------------------------------------- +LLHUDIcon::icon_instance_t LLHUDIcon::sIconInstances; + +LLHUDIcon::LLHUDIcon(const U8 type) : + LLHUDObject(type), + mImagep(NULL), + mScale(0.1f), + mHidden(false) +{ + sIconInstances.push_back(this); +} + +LLHUDIcon::~LLHUDIcon() +{ + mImagep = NULL; +} + +void LLHUDIcon::render() +{ + LLGLSUIDefault texture_state; + LLGLDepthTest gls_depth(GL_TRUE); + //LLGLDisable gls_stencil(GL_STENCIL_TEST); + + if (mHidden) + return; + + if (mSourceObject.isNull() || mImagep.isNull()) + { + markDead(); + return; + } + + LLVector3 obj_position = mSourceObject->getRenderPosition(); + + // put icon above object, and in front + // RN: don't use drawable radius, it's fricking HUGE + LLViewerCamera* camera = LLViewerCamera::getInstance(); + LLVector3 icon_relative_pos = (camera->getUpAxis() * ~mSourceObject->getRenderRotation()); + icon_relative_pos.abs(); + + F32 distance_scale = llmin(mSourceObject->getScale().mV[VX] / icon_relative_pos.mV[VX], + mSourceObject->getScale().mV[VY] / icon_relative_pos.mV[VY], + mSourceObject->getScale().mV[VZ] / icon_relative_pos.mV[VZ]); + F32 up_distance = 0.5f * distance_scale; + LLVector3 icon_position = obj_position + (up_distance * camera->getUpAxis()) * 1.2f; + + LLVector3 icon_to_cam = LLViewerCamera::getInstance()->getOrigin() - icon_position; + icon_to_cam.normVec(); + + icon_position += icon_to_cam * mSourceObject->mDrawable->getRadius() * 1.1f; + + mDistance = dist_vec(icon_position, camera->getOrigin()); + + F32 alpha_factor = clamp_rescale(mDistance, DIST_START_FADE, DIST_END_FADE, 1.f, 0.f); + + LLVector3 x_pixel_vec; + LLVector3 y_pixel_vec; + + camera->getPixelVectors(icon_position, y_pixel_vec, x_pixel_vec); + + F32 scale_factor = 1.f; + if (mAnimTimer.getElapsedTimeF32() < ANIM_TIME) + { + scale_factor = llmax(0.f, calc_bouncy_animation(mAnimTimer.getElapsedTimeF32() / ANIM_TIME)); + } + + F32 time_elapsed = mLifeTimer.getElapsedTimeF32(); + if (time_elapsed > MAX_VISIBLE_TIME) + { + markDead(); + return; + } + + if (time_elapsed > MAX_VISIBLE_TIME - FADE_OUT_TIME) + { + alpha_factor *= clamp_rescale(time_elapsed, MAX_VISIBLE_TIME - FADE_OUT_TIME, MAX_VISIBLE_TIME, 1.f, 0.f); + } + + F32 image_aspect = (F32)mImagep->getFullWidth() / (F32)mImagep->getFullHeight() ; + LLVector3 x_scale = image_aspect * (F32)gViewerWindow->getWindowHeightScaled() * mScale * scale_factor * x_pixel_vec; + LLVector3 y_scale = (F32)gViewerWindow->getWindowHeightScaled() * mScale * scale_factor * y_pixel_vec; + + LLVector3 lower_left = icon_position - (x_scale * 0.5f); + LLVector3 lower_right = icon_position + (x_scale * 0.5f); + LLVector3 upper_left = icon_position - (x_scale * 0.5f) + y_scale; + LLVector3 upper_right = icon_position + (x_scale * 0.5f) + y_scale; + + { + LLColor4 icon_color = LLColor4::white; + icon_color.mV[VALPHA] = alpha_factor; + gGL.color4fv(icon_color.mV); + gGL.getTexUnit(0)->bind(mImagep); + } + + gGL.begin(LLRender::QUADS); + { + gGL.texCoord2f(0.f, 1.f); + gGL.vertex3fv(upper_left.mV); + gGL.texCoord2f(0.f, 0.f); + gGL.vertex3fv(lower_left.mV); + gGL.texCoord2f(1.f, 0.f); + gGL.vertex3fv(lower_right.mV); + gGL.texCoord2f(1.f, 1.f); + gGL.vertex3fv(upper_right.mV); + } + gGL.end(); +} + +void LLHUDIcon::setImage(LLViewerTexture* imagep) +{ + mImagep = imagep; + mImagep->setAddressMode(LLTexUnit::TAM_CLAMP); +} + +void LLHUDIcon::setScale(F32 fraction_of_fov) +{ + mScale = fraction_of_fov; +} + +void LLHUDIcon::markDead() +{ + if (mSourceObject) + { + mSourceObject->clearIcon(); + } + LLHUDObject::markDead(); +} + +bool LLHUDIcon::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, LLVector4a* intersection) +{ + if (mHidden) + return false; + + if (mSourceObject.isNull() || mImagep.isNull()) + { + markDead(); + return false; + } + + LLVector3 obj_position = mSourceObject->getRenderPosition(); + + // put icon above object, and in front + // RN: don't use drawable radius, it's fricking HUGE + LLViewerCamera* camera = LLViewerCamera::getInstance(); + LLVector3 icon_relative_pos = (camera->getUpAxis() * ~mSourceObject->getRenderRotation()); + icon_relative_pos.abs(); + + F32 distance_scale = llmin(mSourceObject->getScale().mV[VX] / icon_relative_pos.mV[VX], + mSourceObject->getScale().mV[VY] / icon_relative_pos.mV[VY], + mSourceObject->getScale().mV[VZ] / icon_relative_pos.mV[VZ]); + F32 up_distance = 0.5f * distance_scale; + LLVector3 icon_position = obj_position + (up_distance * camera->getUpAxis()) * 1.2f; + + LLVector3 icon_to_cam = LLViewerCamera::getInstance()->getOrigin() - icon_position; + icon_to_cam.normVec(); + + icon_position += icon_to_cam * mSourceObject->mDrawable->getRadius() * 1.1f; + + mDistance = dist_vec(icon_position, camera->getOrigin()); + + LLVector3 x_pixel_vec; + LLVector3 y_pixel_vec; + + camera->getPixelVectors(icon_position, y_pixel_vec, x_pixel_vec); + + F32 scale_factor = 1.f; + if (mAnimTimer.getElapsedTimeF32() < ANIM_TIME) + { + scale_factor = llmax(0.f, calc_bouncy_animation(mAnimTimer.getElapsedTimeF32() / ANIM_TIME)); + } + + F32 time_elapsed = mLifeTimer.getElapsedTimeF32(); + if (time_elapsed > MAX_VISIBLE_TIME) + { + markDead(); + return false; + } + + F32 image_aspect = (F32)mImagep->getFullWidth() / (F32)mImagep->getFullHeight() ; + LLVector3 x_scale = image_aspect * (F32)gViewerWindow->getWindowHeightScaled() * mScale * scale_factor * x_pixel_vec; + LLVector3 y_scale = (F32)gViewerWindow->getWindowHeightScaled() * mScale * scale_factor * y_pixel_vec; + + LLVector4a x_scalea; + LLVector4a icon_positiona; + LLVector4a y_scalea; + + x_scalea.load3(x_scale.mV); + x_scalea.mul(0.5f); + y_scalea.load3(y_scale.mV); + + icon_positiona.load3(icon_position.mV); + + LLVector4a lower_left; + lower_left.setSub(icon_positiona, x_scalea); + LLVector4a lower_right; + lower_right.setAdd(icon_positiona, x_scalea); + LLVector4a upper_left; + upper_left.setAdd(lower_left, y_scalea); + LLVector4a upper_right; + upper_right.setAdd(lower_right, y_scalea); + + LLVector4a dir; + dir.setSub(end, start); + + F32 a,b,t; + + if (LLTriangleRayIntersect(upper_right, upper_left, lower_right, start, dir, a,b,t) || + LLTriangleRayIntersect(upper_left, lower_left, lower_right, start, dir, a,b,t)) + { + if (intersection) + { + dir.mul(t); + intersection->setAdd(start, dir); + } + return true; + } + + return false; +} + +//static +LLHUDIcon* LLHUDIcon::lineSegmentIntersectAll(const LLVector4a& start, const LLVector4a& end, LLVector4a* intersection) +{ + icon_instance_t::iterator icon_it; + + LLVector4a local_end = end; + LLVector4a position; + + LLHUDIcon* ret = NULL; + for(icon_it = sIconInstances.begin(); icon_it != sIconInstances.end(); ++icon_it) + { + LLHUDIcon* icon = *icon_it; + if (icon->lineSegmentIntersect(start, local_end, &position)) + { + ret = icon; + if (intersection) + { + *intersection = position; + } + local_end = position; + } + } + + return ret; +} + + + //static +void LLHUDIcon::updateAll() +{ + cleanupDeadIcons(); +} + +//static +bool LLHUDIcon::iconsNearby() +{ + return !sIconInstances.empty(); +} + +//static +void LLHUDIcon::cleanupDeadIcons() +{ + icon_instance_t::iterator icon_it; + + icon_instance_t icons_to_erase; + for(icon_it = sIconInstances.begin(); icon_it != sIconInstances.end(); ++icon_it) + { + if ((*icon_it)->mDead) + { + icons_to_erase.push_back(*icon_it); + } + } + + for(icon_it = icons_to_erase.begin(); icon_it != icons_to_erase.end(); ++icon_it) + { + icon_instance_t::iterator found_it = std::find(sIconInstances.begin(), sIconInstances.end(), *icon_it); + if (found_it != sIconInstances.end()) + { + sIconInstances.erase(found_it); + } + } +} + +//static +S32 LLHUDIcon::getNumInstances() +{ + return (S32)sIconInstances.size(); +} diff --git a/indra/newview/llhudicon.h b/indra/newview/llhudicon.h index 4084ee9e45..b18a4a3b76 100644 --- a/indra/newview/llhudicon.h +++ b/indra/newview/llhudicon.h @@ -1,88 +1,88 @@ -/** - * @file llhudicon.h - * @brief LLHUDIcon class definition - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLHUDICON_H -#define LL_LLHUDICON_H - -#include "llpointer.h" - -#include "llhudobject.h" -#include "v4color.h" -#include "v4coloru.h" -#include "v2math.h" -#include "llrect.h" -#include "llframetimer.h" -#include "llfontgl.h" -#include -#include - -// Renders a 2D icon billboard floating at the location specified. -class LLViewerTexture; - -class LLHUDIcon : public LLHUDObject -{ -friend class LLHUDObject; - -public: - /*virtual*/ void render(); - /*virtual*/ void markDead(); - /*virtual*/ F32 getDistance() const { return mDistance; } - - void setImage(LLViewerTexture* imagep); - void setScale(F32 fraction_of_fov); - - void restartLifeTimer() { mLifeTimer.reset(); } - - static LLHUDIcon* lineSegmentIntersectAll(const LLVector4a& start, const LLVector4a& end, LLVector4a* intersection); - - static void updateAll(); - static void cleanupDeadIcons(); - static S32 getNumInstances(); - - static bool iconsNearby(); - - bool getHidden() const { return mHidden; } - void setHidden( bool hide ) { mHidden = hide; } - - bool lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, LLVector4a* intersection); - -protected: - LLHUDIcon(const U8 type); - ~LLHUDIcon(); - -private: - LLPointer mImagep; - LLFrameTimer mAnimTimer; - LLFrameTimer mLifeTimer; - F32 mDistance; - F32 mScale; - bool mHidden; - - typedef std::vector > icon_instance_t; - static icon_instance_t sIconInstances; -}; - -#endif // LL_LLHUDICON_H +/** + * @file llhudicon.h + * @brief LLHUDIcon class definition + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLHUDICON_H +#define LL_LLHUDICON_H + +#include "llpointer.h" + +#include "llhudobject.h" +#include "v4color.h" +#include "v4coloru.h" +#include "v2math.h" +#include "llrect.h" +#include "llframetimer.h" +#include "llfontgl.h" +#include +#include + +// Renders a 2D icon billboard floating at the location specified. +class LLViewerTexture; + +class LLHUDIcon : public LLHUDObject +{ +friend class LLHUDObject; + +public: + /*virtual*/ void render(); + /*virtual*/ void markDead(); + /*virtual*/ F32 getDistance() const { return mDistance; } + + void setImage(LLViewerTexture* imagep); + void setScale(F32 fraction_of_fov); + + void restartLifeTimer() { mLifeTimer.reset(); } + + static LLHUDIcon* lineSegmentIntersectAll(const LLVector4a& start, const LLVector4a& end, LLVector4a* intersection); + + static void updateAll(); + static void cleanupDeadIcons(); + static S32 getNumInstances(); + + static bool iconsNearby(); + + bool getHidden() const { return mHidden; } + void setHidden( bool hide ) { mHidden = hide; } + + bool lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, LLVector4a* intersection); + +protected: + LLHUDIcon(const U8 type); + ~LLHUDIcon(); + +private: + LLPointer mImagep; + LLFrameTimer mAnimTimer; + LLFrameTimer mLifeTimer; + F32 mDistance; + F32 mScale; + bool mHidden; + + typedef std::vector > icon_instance_t; + static icon_instance_t sIconInstances; +}; + +#endif // LL_LLHUDICON_H diff --git a/indra/newview/llhudmanager.cpp b/indra/newview/llhudmanager.cpp index d5f5b795b0..fd8efe6816 100644 --- a/indra/newview/llhudmanager.cpp +++ b/indra/newview/llhudmanager.cpp @@ -1,210 +1,210 @@ -/** - * @file llhudmanager.cpp - * @brief LLHUDManager class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llhudmanager.h" - -#include "message.h" -#include "object_flags.h" - -#include "llagent.h" -#include "llhudeffect.h" -#include "pipeline.h" -#include "llui.h" -#include "llviewercontrol.h" -#include "llviewerobjectlist.h" - -// These are loaded from saved settings. -LLColor4 LLHUDManager::sParentColor; -LLColor4 LLHUDManager::sChildColor; - -LLHUDManager::LLHUDManager() -{ - - LLHUDManager::sParentColor = LLUIColorTable::instance().getColor("FocusColor"); - // rdw commented out since it's not used. Also removed from colors_base.xml - //LLHUDManager::sChildColor =LLUIColorTable::instance().getColor("FocusSecondaryColor"); -} - -LLHUDManager::~LLHUDManager() -{ -} - -static LLTrace::BlockTimerStatHandle FTM_UPDATE_HUD_EFFECTS("Update Hud Effects"); - -void LLHUDManager::updateEffects() -{ - LL_RECORD_BLOCK_TIME(FTM_UPDATE_HUD_EFFECTS); - S32 i; - for (i = 0; i < mHUDEffects.size(); i++) - { - LLHUDEffect *hep = mHUDEffects[i]; - if (hep->isDead()) - { - continue; - } - hep->update(); - } -} - -void LLHUDManager::sendEffects() -{ - S32 i; - for (i = 0; i < mHUDEffects.size(); i++) - { - LLHUDEffect *hep = mHUDEffects[i]; - if (hep->isDead()) - { - LL_WARNS() << "Trying to send dead effect!" << LL_ENDL; - continue; - } - if (hep->mType < LLHUDObject::LL_HUD_EFFECT_BEAM) - { - LL_WARNS() << "Trying to send effect of type " << hep->mType << " which isn't really an effect and shouldn't be in this list!" << LL_ENDL; - continue; - } - if (hep->getNeedsSendToSim() && hep->getOriginatedHere()) - { - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_ViewerEffect); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_Effect); - hep->packData(msg); - hep->setNeedsSendToSim(false); - gAgent.sendMessage(); - } - } -} - -//static -void LLHUDManager::shutdownClass() -{ - getInstance()->mHUDEffects.clear(); -} - -void LLHUDManager::cleanupEffects() -{ - S32 i = 0; - - while (i < mHUDEffects.size()) - { - if (mHUDEffects[i]->isDead()) - { - mHUDEffects.erase(mHUDEffects.begin() + i); - } - else - { - i++; - } - } -} - -LLHUDEffect *LLHUDManager::createViewerEffect(const U8 type, bool send_to_sim, bool originated_here) -{ - // SJB: DO NOT USE addHUDObject!!! Not all LLHUDObjects are LLHUDEffects! - LLHUDEffect *hep = LLHUDObject::addHUDEffect(type); - if (!hep) - { - return NULL; - } - - LLUUID tmp; - tmp.generate(); - hep->setID(tmp); - hep->setNeedsSendToSim(send_to_sim); - hep->setOriginatedHere(originated_here); - - mHUDEffects.push_back(hep); - return hep; -} - - -//static -void LLHUDManager::processViewerEffect(LLMessageSystem *mesgsys, void **user_data) -{ - LLHUDEffect *effectp = NULL; - LLUUID effect_id; - U8 effect_type = 0; - S32 number_blocks = mesgsys->getNumberOfBlocksFast(_PREHASH_Effect); - S32 k; - - for (k = 0; k < number_blocks; k++) - { - effectp = NULL; - LLHUDEffect::getIDType(mesgsys, k, effect_id, effect_type); - S32 i; - for (i = 0; i < LLHUDManager::getInstance()->mHUDEffects.size(); i++) - { - LLHUDEffect *cur_effectp = LLHUDManager::getInstance()->mHUDEffects[i]; - if (!cur_effectp) - { - LL_WARNS() << "Null effect in effect manager, skipping" << LL_ENDL; - LLHUDManager::getInstance()->mHUDEffects.erase(LLHUDManager::getInstance()->mHUDEffects.begin() + i); - i--; - continue; - } - if (cur_effectp->isDead()) - { - // LL_WARNS() << "Dead effect in effect manager, removing" << LL_ENDL; - LLHUDManager::getInstance()->mHUDEffects.erase(LLHUDManager::getInstance()->mHUDEffects.begin() + i); - i--; - continue; - } - if (cur_effectp->getID() == effect_id) - { - if (cur_effectp->getType() != effect_type) - { - LL_WARNS() << "Viewer effect update doesn't match old type!" << LL_ENDL; - } - effectp = cur_effectp; - break; - } - } - - if (effect_type) - { - if (!effectp) - { - effectp = LLHUDManager::getInstance()->createViewerEffect(effect_type, false, false); - } - - if (effectp) - { - effectp->unpackData(mesgsys, k); - } - } - else - { - LL_WARNS() << "Received viewer effect of type " << effect_type << " which isn't really an effect!" << LL_ENDL; - } - } - - //LL_INFOS() << "Got viewer effect: " << effect_id << LL_ENDL; -} - +/** + * @file llhudmanager.cpp + * @brief LLHUDManager class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llhudmanager.h" + +#include "message.h" +#include "object_flags.h" + +#include "llagent.h" +#include "llhudeffect.h" +#include "pipeline.h" +#include "llui.h" +#include "llviewercontrol.h" +#include "llviewerobjectlist.h" + +// These are loaded from saved settings. +LLColor4 LLHUDManager::sParentColor; +LLColor4 LLHUDManager::sChildColor; + +LLHUDManager::LLHUDManager() +{ + + LLHUDManager::sParentColor = LLUIColorTable::instance().getColor("FocusColor"); + // rdw commented out since it's not used. Also removed from colors_base.xml + //LLHUDManager::sChildColor =LLUIColorTable::instance().getColor("FocusSecondaryColor"); +} + +LLHUDManager::~LLHUDManager() +{ +} + +static LLTrace::BlockTimerStatHandle FTM_UPDATE_HUD_EFFECTS("Update Hud Effects"); + +void LLHUDManager::updateEffects() +{ + LL_RECORD_BLOCK_TIME(FTM_UPDATE_HUD_EFFECTS); + S32 i; + for (i = 0; i < mHUDEffects.size(); i++) + { + LLHUDEffect *hep = mHUDEffects[i]; + if (hep->isDead()) + { + continue; + } + hep->update(); + } +} + +void LLHUDManager::sendEffects() +{ + S32 i; + for (i = 0; i < mHUDEffects.size(); i++) + { + LLHUDEffect *hep = mHUDEffects[i]; + if (hep->isDead()) + { + LL_WARNS() << "Trying to send dead effect!" << LL_ENDL; + continue; + } + if (hep->mType < LLHUDObject::LL_HUD_EFFECT_BEAM) + { + LL_WARNS() << "Trying to send effect of type " << hep->mType << " which isn't really an effect and shouldn't be in this list!" << LL_ENDL; + continue; + } + if (hep->getNeedsSendToSim() && hep->getOriginatedHere()) + { + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_ViewerEffect); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_Effect); + hep->packData(msg); + hep->setNeedsSendToSim(false); + gAgent.sendMessage(); + } + } +} + +//static +void LLHUDManager::shutdownClass() +{ + getInstance()->mHUDEffects.clear(); +} + +void LLHUDManager::cleanupEffects() +{ + S32 i = 0; + + while (i < mHUDEffects.size()) + { + if (mHUDEffects[i]->isDead()) + { + mHUDEffects.erase(mHUDEffects.begin() + i); + } + else + { + i++; + } + } +} + +LLHUDEffect *LLHUDManager::createViewerEffect(const U8 type, bool send_to_sim, bool originated_here) +{ + // SJB: DO NOT USE addHUDObject!!! Not all LLHUDObjects are LLHUDEffects! + LLHUDEffect *hep = LLHUDObject::addHUDEffect(type); + if (!hep) + { + return NULL; + } + + LLUUID tmp; + tmp.generate(); + hep->setID(tmp); + hep->setNeedsSendToSim(send_to_sim); + hep->setOriginatedHere(originated_here); + + mHUDEffects.push_back(hep); + return hep; +} + + +//static +void LLHUDManager::processViewerEffect(LLMessageSystem *mesgsys, void **user_data) +{ + LLHUDEffect *effectp = NULL; + LLUUID effect_id; + U8 effect_type = 0; + S32 number_blocks = mesgsys->getNumberOfBlocksFast(_PREHASH_Effect); + S32 k; + + for (k = 0; k < number_blocks; k++) + { + effectp = NULL; + LLHUDEffect::getIDType(mesgsys, k, effect_id, effect_type); + S32 i; + for (i = 0; i < LLHUDManager::getInstance()->mHUDEffects.size(); i++) + { + LLHUDEffect *cur_effectp = LLHUDManager::getInstance()->mHUDEffects[i]; + if (!cur_effectp) + { + LL_WARNS() << "Null effect in effect manager, skipping" << LL_ENDL; + LLHUDManager::getInstance()->mHUDEffects.erase(LLHUDManager::getInstance()->mHUDEffects.begin() + i); + i--; + continue; + } + if (cur_effectp->isDead()) + { + // LL_WARNS() << "Dead effect in effect manager, removing" << LL_ENDL; + LLHUDManager::getInstance()->mHUDEffects.erase(LLHUDManager::getInstance()->mHUDEffects.begin() + i); + i--; + continue; + } + if (cur_effectp->getID() == effect_id) + { + if (cur_effectp->getType() != effect_type) + { + LL_WARNS() << "Viewer effect update doesn't match old type!" << LL_ENDL; + } + effectp = cur_effectp; + break; + } + } + + if (effect_type) + { + if (!effectp) + { + effectp = LLHUDManager::getInstance()->createViewerEffect(effect_type, false, false); + } + + if (effectp) + { + effectp->unpackData(mesgsys, k); + } + } + else + { + LL_WARNS() << "Received viewer effect of type " << effect_type << " which isn't really an effect!" << LL_ENDL; + } + } + + //LL_INFOS() << "Got viewer effect: " << effect_id << LL_ENDL; +} + diff --git a/indra/newview/llhudmanager.h b/indra/newview/llhudmanager.h index 2336fade56..58de31a061 100644 --- a/indra/newview/llhudmanager.h +++ b/indra/newview/llhudmanager.h @@ -1,60 +1,60 @@ -/** - * @file llhudmanager.h - * @brief LLHUDManager class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLHUDMANAGER_H -#define LL_LLHUDMANAGER_H - -// Responsible for managing all HUD elements. - -#include "llhudobject.h" - -class LLHUDEffect; -class LLMessageSystem; - -class LLHUDManager : public LLSingleton -{ - LLSINGLETON(LLHUDManager); - ~LLHUDManager(); - -public: - LLHUDEffect *createViewerEffect(const U8 type, bool send_to_sim = true, bool originated_here = true); - - void updateEffects(); - void sendEffects(); - void cleanupEffects(); - - static void shutdownClass(); - - static void processViewerEffect(LLMessageSystem *mesgsys, void **user_data); - - static LLColor4 sParentColor; - static LLColor4 sChildColor; - -protected: - std::vector > mHUDEffects; -}; - -#endif // LL_LLHUDMANAGER_H +/** + * @file llhudmanager.h + * @brief LLHUDManager class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLHUDMANAGER_H +#define LL_LLHUDMANAGER_H + +// Responsible for managing all HUD elements. + +#include "llhudobject.h" + +class LLHUDEffect; +class LLMessageSystem; + +class LLHUDManager : public LLSingleton +{ + LLSINGLETON(LLHUDManager); + ~LLHUDManager(); + +public: + LLHUDEffect *createViewerEffect(const U8 type, bool send_to_sim = true, bool originated_here = true); + + void updateEffects(); + void sendEffects(); + void cleanupEffects(); + + static void shutdownClass(); + + static void processViewerEffect(LLMessageSystem *mesgsys, void **user_data); + + static LLColor4 sParentColor; + static LLColor4 sChildColor; + +protected: + std::vector > mHUDEffects; +}; + +#endif // LL_LLHUDMANAGER_H diff --git a/indra/newview/llhudnametag.cpp b/indra/newview/llhudnametag.cpp index 61c62572b3..4224f7d134 100644 --- a/indra/newview/llhudnametag.cpp +++ b/indra/newview/llhudnametag.cpp @@ -1,958 +1,958 @@ -/** - * @file llhudnametag.cpp - * @brief Name tags for avatars - * @author James Cook - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llhudnametag.h" - -#include "llrender.h" -#include "lltracerecording.h" - -#include "llagent.h" -#include "llviewercontrol.h" -#include "llcriticaldamp.h" -#include "lldrawable.h" -#include "llfontgl.h" -#include "llglheaders.h" -#include "llhudrender.h" -#include "llui.h" -#include "llviewercamera.h" -#include "llviewertexturelist.h" -#include "llviewerobject.h" -#include "llvovolume.h" -#include "llviewerwindow.h" -#include "llstatusbar.h" -#include "llmenugl.h" -#include "pipeline.h" -#include - - -const F32 SPRING_STRENGTH = 0.7f; -const F32 HORIZONTAL_PADDING = 16.f; -const F32 VERTICAL_PADDING = 12.f; -const F32 LINE_PADDING = 3.f; // aka "leading" -const F32 BUFFER_SIZE = 2.f; -const S32 NUM_OVERLAP_ITERATIONS = 10; -const F32 POSITION_DAMPING_TC = 0.2f; -const F32 MAX_STABLE_CAMERA_VELOCITY = 0.1f; -const F32 LOD_0_SCREEN_COVERAGE = 0.15f; -const F32 LOD_1_SCREEN_COVERAGE = 0.30f; -const F32 LOD_2_SCREEN_COVERAGE = 0.40f; - -std::set > LLHUDNameTag::sTextObjects; -std::vector > LLHUDNameTag::sVisibleTextObjects; -bool LLHUDNameTag::sDisplayText = true ; -const F32 LLHUDNameTag::NAMETAG_MAX_WIDTH = 298.f; -const F32 LLHUDNameTag::HUD_TEXT_MAX_WIDTH = 190.f; - -bool llhudnametag_further_away::operator()(const LLPointer& lhs, const LLPointer& rhs) const -{ - return lhs->getDistance() > rhs->getDistance(); -} - - -LLHUDNameTag::LLHUDNameTag(const U8 type) -: LLHUDObject(type), - mDoFade(true), - mFadeDistance(8.f), - mFadeRange(4.f), - mLastDistance(0.f), - mZCompare(true), - mVisibleOffScreen(false), - mOffscreen(false), - mColor(1.f, 1.f, 1.f, 1.f), -// mScale(), - mWidth(0.f), - mHeight(0.f), - mFontp(LLFontGL::getFontSansSerifSmall()), - mBoldFontp(LLFontGL::getFontSansSerifBold()), - mSoftScreenRect(), - mPositionAgent(), - mPositionOffset(), - mMass(10.f), - mMaxLines(10), - mOffsetY(0), - mRadius(0.1f), - mTextSegments(), - mLabelSegments(), - mTextAlignment(ALIGN_TEXT_CENTER), - mVertAlignment(ALIGN_VERT_CENTER), - mLOD(0), - mHidden(false) -{ - LLPointer ptr(this); - sTextObjects.insert(ptr); - - mRoundedRectImgp = LLUI::getUIImage("Rounded_Rect"); - mRoundedRectTopImgp = LLUI::getUIImage("Rounded_Rect_Top"); -} - -LLHUDNameTag::~LLHUDNameTag() -{ -} - - -bool LLHUDNameTag::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, LLVector4a& intersection, bool debug_render) -{ - if (!mVisible || mHidden) - { - return false; - } - - // don't pick text that isn't bound to a viewerobject - if (!mSourceObject || mSourceObject->mDrawable.isNull()) - { - return false; - } - - F32 alpha_factor = 1.f; - LLColor4 text_color = mColor; - if (mDoFade) - { - if (mLastDistance > mFadeDistance) - { - alpha_factor = llmax(0.f, 1.f - (mLastDistance - mFadeDistance)/mFadeRange); - text_color.mV[3] = text_color.mV[3]*alpha_factor; - } - } - if (text_color.mV[3] < 0.01f) - { - return false; - } - - mOffsetY = lltrunc(mHeight * ((mVertAlignment == ALIGN_VERT_CENTER) ? 0.5f : 1.f)); - - LLVector3 position = mPositionAgent; - - if (mSourceObject) - { //get intersection of eye through mPositionAgent to plane of source object - //using this position keeps the camera from focusing on some seemingly random - //point several meters in front of the nametag - const LLVector3& p = mSourceObject->getPositionAgent(); - const LLVector3& n = LLViewerCamera::getInstance()->getAtAxis(); - const LLVector3& eye = LLViewerCamera::getInstance()->getOrigin(); - - LLVector3 ray = position-eye; - ray.normalize(); - - LLVector3 delta = p-position; - F32 dist = delta*n; - F32 dt = dist/(ray*n); - position += ray*dt; - } - - // scale screen size of borders down - - LLVector3 x_pixel_vec; - LLVector3 y_pixel_vec; - - LLViewerCamera::getInstance()->getPixelVectors(position, y_pixel_vec, x_pixel_vec); - - LLVector3 width_vec = mWidth * x_pixel_vec; - LLVector3 height_vec = mHeight * y_pixel_vec; - - LLCoordGL screen_pos; - LLViewerCamera::getInstance()->projectPosAgentToScreen(position, screen_pos, false); - - LLVector2 screen_offset; - screen_offset = updateScreenPos(mPositionOffset); - - LLVector3 render_position = position - + (x_pixel_vec * screen_offset.mV[VX]) - + (y_pixel_vec * screen_offset.mV[VY]); - - - LLVector3 bg_pos = render_position - + (F32)mOffsetY * y_pixel_vec - - (width_vec / 2.f) - - (height_vec); - - LLVector3 v[] = - { - bg_pos, - bg_pos + width_vec, - bg_pos + width_vec + height_vec, - bg_pos + height_vec, - }; - - LLVector4a dir; - dir.setSub(end,start); - F32 a, b, t; - - LLVector4a v0,v1,v2,v3; - v0.load3(v[0].mV); - v1.load3(v[1].mV); - v2.load3(v[2].mV); - v3.load3(v[3].mV); - - if (LLTriangleRayIntersect(v0, v1, v2, start, dir, a, b, t) || - LLTriangleRayIntersect(v2, v3, v0, start, dir, a, b, t) ) - { - if (t <= 1.f) - { - dir.mul(t); - intersection.setAdd(start, dir); - return true; - } - } - - return false; -} - -void LLHUDNameTag::render() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; - if (sDisplayText) - { - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); - //LLGLDisable gls_stencil(GL_STENCIL_TEST); - renderText(false); - } -} - -void LLHUDNameTag::renderText(bool for_select) -{ - if (!mVisible || mHidden) - { - return; - } - - // don't pick text that isn't bound to a viewerobject - if (for_select && - (!mSourceObject || mSourceObject->mDrawable.isNull())) - { - return; - } - - if (for_select) - { - gGL.getTexUnit(0)->disable(); - } - else - { - gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE); - } - - LLGLState gls_blend(GL_BLEND, !for_select); - - LLColor4 shadow_color(0.f, 0.f, 0.f, 1.f); - F32 alpha_factor = 1.f; - LLColor4 text_color = mColor; - if (mDoFade) - { - if (mLastDistance > mFadeDistance) - { - alpha_factor = llmax(0.f, 1.f - (mLastDistance - mFadeDistance)/mFadeRange); - text_color.mV[3] = text_color.mV[3]*alpha_factor; - } - } - if (text_color.mV[3] < 0.01f) - { - return; - } - shadow_color.mV[3] = text_color.mV[3]; - - mOffsetY = lltrunc(mHeight * ((mVertAlignment == ALIGN_VERT_CENTER) ? 0.5f : 1.f)); - - // *TODO: make this a per-text setting - LLColor4 bg_color = LLUIColorTable::instance().getColor("NameTagBackground"); - bg_color.setAlpha(gSavedSettings.getF32("ChatBubbleOpacity") * alpha_factor); - - // scale screen size of borders down - //RN: for now, text on hud objects is never occluded - - LLVector3 x_pixel_vec; - LLVector3 y_pixel_vec; - - LLViewerCamera::getInstance()->getPixelVectors(mPositionAgent, y_pixel_vec, x_pixel_vec); - - LLVector3 width_vec = mWidth * x_pixel_vec; - LLVector3 height_vec = mHeight * y_pixel_vec; - - mRadius = (width_vec + height_vec).magVec() * 0.5f; - - LLCoordGL screen_pos; - LLViewerCamera::getInstance()->projectPosAgentToScreen(mPositionAgent, screen_pos, false); - - LLVector2 screen_offset = updateScreenPos(mPositionOffset); - - LLVector3 render_position = mPositionAgent - + (x_pixel_vec * screen_offset.mV[VX]) - + (y_pixel_vec * screen_offset.mV[VY]); - - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); - LLRect screen_rect; - screen_rect.setCenterAndSize(0, static_cast(lltrunc(-mHeight / 2 + mOffsetY)), static_cast(lltrunc(mWidth)), static_cast(lltrunc(mHeight))); - mRoundedRectImgp->draw3D(render_position, x_pixel_vec, y_pixel_vec, screen_rect, bg_color); - if (mLabelSegments.size()) - { - LLRect label_top_rect = screen_rect; - const S32 label_height = ll_round((mFontp->getLineHeight() * (F32)mLabelSegments.size() + (VERTICAL_PADDING / 3.f))); - label_top_rect.mBottom = label_top_rect.mTop - label_height; - LLColor4 label_top_color = text_color; - label_top_color.mV[VALPHA] = gSavedSettings.getF32("ChatBubbleOpacity") * alpha_factor; - - mRoundedRectTopImgp->draw3D(render_position, x_pixel_vec, y_pixel_vec, label_top_rect, label_top_color); - } - - F32 y_offset = (F32)mOffsetY; - - // Render label - { - for(std::vector::iterator segment_iter = mLabelSegments.begin(); - segment_iter != mLabelSegments.end(); ++segment_iter ) - { - // Label segments use default font - const LLFontGL* fontp = (segment_iter->mStyle == LLFontGL::BOLD) ? mBoldFontp : mFontp; - y_offset -= fontp->getLineHeight(); - - F32 x_offset; - if (mTextAlignment == ALIGN_TEXT_CENTER) - { - x_offset = -0.5f*segment_iter->getWidth(fontp); - } - else // ALIGN_LEFT - { - x_offset = -0.5f * mWidth + (HORIZONTAL_PADDING / 2.f); - } - - LLColor4 label_color(0.f, 0.f, 0.f, 1.f); - label_color.mV[VALPHA] = alpha_factor; - hud_render_text(segment_iter->getText(), render_position, *fontp, segment_iter->mStyle, LLFontGL::NO_SHADOW, x_offset, y_offset, label_color, false); - } - } - - // Render text - { - // -1 mMaxLines means unlimited lines. - S32 start_segment; - S32 max_lines = getMaxLines(); - - if (max_lines < 0) - { - start_segment = 0; - } - else - { - start_segment = llmax((S32)0, (S32)mTextSegments.size() - max_lines); - } - - for (std::vector::iterator segment_iter = mTextSegments.begin() + start_segment; - segment_iter != mTextSegments.end(); ++segment_iter ) - { - const LLFontGL* fontp = segment_iter->mFont; - y_offset -= fontp->getLineHeight(); - y_offset -= LINE_PADDING; - - U8 style = segment_iter->mStyle; - LLFontGL::ShadowType shadow = LLFontGL::DROP_SHADOW; - - F32 x_offset; - if (mTextAlignment== ALIGN_TEXT_CENTER) - { - x_offset = -0.5f*segment_iter->getWidth(fontp); - } - else // ALIGN_LEFT - { - x_offset = -0.5f * mWidth + (HORIZONTAL_PADDING / 2.f); - - // *HACK - x_offset += 1; - } - - text_color = segment_iter->mColor; - text_color.mV[VALPHA] *= alpha_factor; - - hud_render_text(segment_iter->getText(), render_position, *fontp, style, shadow, x_offset, y_offset, text_color, false); - } - } - /// Reset the default color to white. The renderer expects this to be the default. - gGL.color4f(1.0f, 1.0f, 1.0f, 1.0f); - if (for_select) - { - gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE); - } -} - -void LLHUDNameTag::setString(const std::string &text_utf8) -{ - mTextSegments.clear(); - addLine(text_utf8, mColor); -} - -void LLHUDNameTag::clearString() -{ - mTextSegments.clear(); -} - - -void LLHUDNameTag::addLine(const std::string &text_utf8, - const LLColor4& color, - const LLFontGL::StyleFlags style, - const LLFontGL* font, - const bool use_ellipses, - F32 max_pixels) -{ - LLWString wline = utf8str_to_wstring(text_utf8); - if (!wline.empty()) - { - // use default font for segment if custom font not specified - if (!font) - { - font = mFontp; - } - typedef boost::tokenizer, LLWString::const_iterator, LLWString > tokenizer; - LLWString seps(utf8str_to_wstring("\r\n")); - boost::char_separator sep(seps.c_str()); - - tokenizer tokens(wline, sep); - tokenizer::iterator iter = tokens.begin(); - - max_pixels = llmin(max_pixels, NAMETAG_MAX_WIDTH); - while (iter != tokens.end()) - { - U32 line_length = 0; - if (use_ellipses) - { - // "QualityAssuranceAssuresQuality1" will end up like "QualityAssuranceAssuresQual..." - // "QualityAssuranceAssuresQuality QualityAssuranceAssuresQuality" will end up like "QualityAssuranceAssuresQual..." - // "QualityAssurance AssuresQuality1" will end up as "QualityAssurance AssuresQua..." because we are enforcing single line - do - { - S32 segment_length = font->maxDrawableChars(iter->substr(line_length).c_str(), max_pixels, wline.length(), LLFontGL::ANYWHERE); - if (segment_length + line_length < wline.length()) // since we only draw one string, line_length should be 0 - { - // token does does not fit into signle line, need to draw "...". - // Use four dots for ellipsis width to generate padding - const LLWString dots_pad(utf8str_to_wstring(std::string("...."))); - S32 elipses_width = font->getWidthF32(dots_pad.c_str()); - // truncated string length - segment_length = font->maxDrawableChars(iter->substr(line_length).c_str(), max_pixels - elipses_width, wline.length(), LLFontGL::ANYWHERE); - const LLWString dots(utf8str_to_wstring(std::string("..."))); - LLHUDTextSegment segment(iter->substr(line_length, segment_length) + dots, style, color, font); - mTextSegments.push_back(segment); - break; // consider it to be complete - } - else - { - // token fits fully into string - LLHUDTextSegment segment(iter->substr(line_length, segment_length), style, color, font); - mTextSegments.push_back(segment); - line_length += segment_length; - } - } while (line_length != iter->size()); - } - else - { - // "QualityAssuranceAssuresQuality 1" will be split into two lines "QualityAssuranceAssuresQualit" and "y 1" - // "QualityAssurance AssuresQuality 1" will be split into two lines "QualityAssurance" and "AssuresQuality" - do - { - S32 segment_length = font->maxDrawableChars(iter->substr(line_length).c_str(), max_pixels, wline.length(), LLFontGL::WORD_BOUNDARY_IF_POSSIBLE); - LLHUDTextSegment segment(iter->substr(line_length, segment_length), style, color, font); - mTextSegments.push_back(segment); - line_length += segment_length; - } while (line_length != iter->size()); - } - ++iter; - } - } -} - -void LLHUDNameTag::setLabel(const std::string &label_utf8) -{ - mLabelSegments.clear(); - addLabel(label_utf8); -} - -void LLHUDNameTag::addLabel(const std::string& label_utf8, F32 max_pixels) -{ - LLWString wstr = utf8string_to_wstring(label_utf8); - if (!wstr.empty()) - { - LLWString seps(utf8str_to_wstring("\r\n")); - LLWString empty; - - typedef boost::tokenizer, LLWString::const_iterator, LLWString > tokenizer; - boost::char_separator sep(seps.c_str(), empty.c_str(), boost::keep_empty_tokens); - - tokenizer tokens(wstr, sep); - tokenizer::iterator iter = tokens.begin(); - - max_pixels = llmin(max_pixels, NAMETAG_MAX_WIDTH); - - while (iter != tokens.end()) - { - U32 line_length = 0; - do - { - S32 segment_length = mFontp->maxDrawableChars(iter->substr(line_length).c_str(), - max_pixels, wstr.length(), LLFontGL::WORD_BOUNDARY_IF_POSSIBLE); - LLHUDTextSegment segment(iter->substr(line_length, segment_length), LLFontGL::NORMAL, mColor, mFontp); - mLabelSegments.push_back(segment); - line_length += segment_length; - } - while (line_length != iter->size()); - ++iter; - } - } -} - -void LLHUDNameTag::setZCompare(const bool zcompare) -{ - mZCompare = zcompare; -} - -void LLHUDNameTag::setFont(const LLFontGL* font) -{ - mFontp = font; -} - - -void LLHUDNameTag::setColor(const LLColor4 &color) -{ - mColor = color; - for (std::vector::iterator segment_iter = mTextSegments.begin(); - segment_iter != mTextSegments.end(); ++segment_iter ) - { - segment_iter->mColor = color; - } -} - -void LLHUDNameTag::setAlpha(F32 alpha) -{ - mColor.mV[VALPHA] = alpha; - for (std::vector::iterator segment_iter = mTextSegments.begin(); - segment_iter != mTextSegments.end(); ++segment_iter ) - { - segment_iter->mColor.mV[VALPHA] = alpha; - } -} - - -void LLHUDNameTag::setDoFade(const bool do_fade) -{ - mDoFade = do_fade; -} - -void LLHUDNameTag::updateVisibility() -{ - if (mSourceObject) - { - mSourceObject->updateText(); - } - - mPositionAgent = gAgent.getPosAgentFromGlobal(mPositionGlobal); - - if (!mSourceObject) - { - //LL_WARNS() << "LLHUDNameTag::updateScreenPos -- mSourceObject is NULL!" << LL_ENDL; - mVisible = true; - sVisibleTextObjects.push_back(LLPointer (this)); - return; - } - - // Not visible if parent object is dead - if (mSourceObject->isDead()) - { - mVisible = false; - return; - } - - // push text towards camera by radius of object, but not past camera - LLVector3 vec_from_camera = mPositionAgent - LLViewerCamera::getInstance()->getOrigin(); - LLVector3 dir_from_camera = vec_from_camera; - dir_from_camera.normVec(); - - if (dir_from_camera * LLViewerCamera::getInstance()->getAtAxis() <= 0.f) - { //text is behind camera, don't render - mVisible = false; - return; - } - - if (vec_from_camera * LLViewerCamera::getInstance()->getAtAxis() <= LLViewerCamera::getInstance()->getNear() + 0.1f + mSourceObject->getVObjRadius()) - { - mPositionAgent = LLViewerCamera::getInstance()->getOrigin() + vec_from_camera * ((LLViewerCamera::getInstance()->getNear() + 0.1f) / (vec_from_camera * LLViewerCamera::getInstance()->getAtAxis())); - } - else - { - mPositionAgent -= dir_from_camera * mSourceObject->getVObjRadius(); - } - - mLastDistance = (mPositionAgent - LLViewerCamera::getInstance()->getOrigin()).magVec(); - - if (mLOD >= 3 || !mTextSegments.size() || (mDoFade && (mLastDistance > mFadeDistance + mFadeRange))) - { - mVisible = false; - return; - } - - LLVector3 x_pixel_vec; - LLVector3 y_pixel_vec; - - LLViewerCamera::getInstance()->getPixelVectors(mPositionAgent, y_pixel_vec, x_pixel_vec); - - LLVector3 render_position = mPositionAgent + - (x_pixel_vec * mPositionOffset.mV[VX]) + - (y_pixel_vec * mPositionOffset.mV[VY]); - - mOffscreen = false; - if (!LLViewerCamera::getInstance()->sphereInFrustum(render_position, mRadius)) - { - if (!mVisibleOffScreen) - { - mVisible = false; - return; - } - else - { - mOffscreen = true; - } - } - - mVisible = true; - sVisibleTextObjects.push_back(LLPointer (this)); -} - -LLVector2 LLHUDNameTag::updateScreenPos(LLVector2 &offset) -{ - LLCoordGL screen_pos; - LLVector2 screen_pos_vec; - LLVector3 x_pixel_vec; - LLVector3 y_pixel_vec; - LLViewerCamera::getInstance()->getPixelVectors(mPositionAgent, y_pixel_vec, x_pixel_vec); - LLVector3 world_pos = mPositionAgent + (offset.mV[VX] * x_pixel_vec) + (offset.mV[VY] * y_pixel_vec); - if (!LLViewerCamera::getInstance()->projectPosAgentToScreen(world_pos, screen_pos, false) && mVisibleOffScreen) - { - // bubble off-screen, so find a spot for it along screen edge - LLViewerCamera::getInstance()->projectPosAgentToScreenEdge(world_pos, screen_pos); - } - - screen_pos_vec.setVec((F32)screen_pos.mX, (F32)screen_pos.mY); - - LLRect world_rect = gViewerWindow->getWorldViewRectScaled(); - S32 bottom = world_rect.mBottom + STATUS_BAR_HEIGHT; - - LLVector2 screen_center; - screen_center.mV[VX] = llclamp((F32)screen_pos_vec.mV[VX], (F32)world_rect.mLeft + mWidth * 0.5f, (F32)world_rect.mRight - mWidth * 0.5f); - - if(mVertAlignment == ALIGN_VERT_TOP) - { - screen_center.mV[VY] = llclamp((F32)screen_pos_vec.mV[VY], - (F32)bottom, - (F32)world_rect.mTop - mHeight - (F32)MENU_BAR_HEIGHT); - mSoftScreenRect.setLeftTopAndSize(screen_center.mV[VX] - (mWidth + BUFFER_SIZE) * 0.5f, - screen_center.mV[VY] + (mHeight + BUFFER_SIZE), mWidth + BUFFER_SIZE, mHeight + BUFFER_SIZE); - } - else - { - screen_center.mV[VY] = llclamp((F32)screen_pos_vec.mV[VY], - (F32)bottom + mHeight * 0.5f, - (F32)world_rect.mTop - mHeight * 0.5f - (F32)MENU_BAR_HEIGHT); - mSoftScreenRect.setCenterAndSize(screen_center.mV[VX], screen_center.mV[VY], mWidth + BUFFER_SIZE, mHeight + BUFFER_SIZE); - } - - return offset + (screen_center - LLVector2((F32)screen_pos.mX, (F32)screen_pos.mY)); -} - -void LLHUDNameTag::updateSize() -{ - F32 height = 0.f; - F32 width = 0.f; - - S32 max_lines = getMaxLines(); - //S32 lines = (max_lines < 0) ? (S32)mTextSegments.size() : llmin((S32)mTextSegments.size(), max_lines); - //F32 height = (F32)mFontp->getLineHeight() * (lines + mLabelSegments.size()); - - S32 start_segment; - if (max_lines < 0) start_segment = 0; - else start_segment = llmax((S32)0, (S32)mTextSegments.size() - max_lines); - - std::vector::iterator iter = mTextSegments.begin() + start_segment; - while (iter != mTextSegments.end()) - { - const LLFontGL* fontp = iter->mFont; - height += fontp->getLineHeight(); - height += LINE_PADDING; - width = llmax(width, llmin(iter->getWidth(fontp), NAMETAG_MAX_WIDTH)); - ++iter; - } - - // Don't want line spacing under the last line - if (height > 0.f) - { - height -= LINE_PADDING; - } - - iter = mLabelSegments.begin(); - while (iter != mLabelSegments.end()) - { - height += mFontp->getLineHeight(); - width = llmax(width, llmin(iter->getWidth(mFontp), NAMETAG_MAX_WIDTH)); - ++iter; - } - - if (width == 0.f) - { - return; - } - - width += HORIZONTAL_PADDING; - height += VERTICAL_PADDING; - - // *TODO: Could do a timer-based resize here - //mWidth = llmax(width, lerp(mWidth, (F32)width, u)); - //mHeight = llmax(height, lerp(mHeight, (F32)height, u)); - mWidth = width; - mHeight = height; -} - -void LLHUDNameTag::updateAll() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; - // iterate over all text objects, calculate their restoration forces, - // and add them to the visible set if they are on screen and close enough - sVisibleTextObjects.clear(); - - TextObjectIterator text_it; - for (text_it = sTextObjects.begin(); text_it != sTextObjects.end(); ++text_it) - { - LLHUDNameTag* textp = (*text_it); - textp->mTargetPositionOffset.clearVec(); - textp->updateSize(); - textp->updateVisibility(); - } - - // sort back to front for rendering purposes - std::sort(sVisibleTextObjects.begin(), sVisibleTextObjects.end(), llhudnametag_further_away()); - - // iterate from front to back, and set LOD based on current screen coverage - F32 screen_area = (F32)(gViewerWindow->getWindowWidthScaled() * gViewerWindow->getWindowHeightScaled()); - F32 current_screen_area = 0.f; - std::vector >::reverse_iterator r_it; - for (r_it = sVisibleTextObjects.rbegin(); r_it != sVisibleTextObjects.rend(); ++r_it) - { - LLHUDNameTag* textp = (*r_it); - if (current_screen_area / screen_area > LOD_2_SCREEN_COVERAGE) - { - textp->setLOD(3); - } - else if (current_screen_area / screen_area > LOD_1_SCREEN_COVERAGE) - { - textp->setLOD(2); - } - else if (current_screen_area / screen_area > LOD_0_SCREEN_COVERAGE) - { - textp->setLOD(1); - } - else - { - textp->setLOD(0); - } - textp->updateSize(); - // find on-screen position and initialize collision rectangle - textp->mTargetPositionOffset = textp->updateScreenPos(LLVector2::zero); - current_screen_area += (F32)(textp->mSoftScreenRect.getWidth() * textp->mSoftScreenRect.getHeight()); - } - - LLTrace::CountStatHandle<>* camera_vel_stat = LLViewerCamera::getVelocityStat(); - F32 camera_vel = LLTrace::get_frame_recording().getLastRecording().getPerSec(*camera_vel_stat); - if (camera_vel > MAX_STABLE_CAMERA_VELOCITY) - { - return; - } - - VisibleTextObjectIterator src_it; - - for (S32 i = 0; i < NUM_OVERLAP_ITERATIONS; i++) - { - for (src_it = sVisibleTextObjects.begin(); src_it != sVisibleTextObjects.end(); ++src_it) - { - LLHUDNameTag* src_textp = (*src_it); - - VisibleTextObjectIterator dst_it = src_it; - ++dst_it; - for (; dst_it != sVisibleTextObjects.end(); ++dst_it) - { - LLHUDNameTag* dst_textp = (*dst_it); - - if (src_textp->mSoftScreenRect.overlaps(dst_textp->mSoftScreenRect)) - { - LLRectf intersect_rect = src_textp->mSoftScreenRect; - intersect_rect.intersectWith(dst_textp->mSoftScreenRect); - intersect_rect.stretch(-BUFFER_SIZE * 0.5f); - - F32 src_center_x = src_textp->mSoftScreenRect.getCenterX(); - F32 src_center_y = src_textp->mSoftScreenRect.getCenterY(); - F32 dst_center_x = dst_textp->mSoftScreenRect.getCenterX(); - F32 dst_center_y = dst_textp->mSoftScreenRect.getCenterY(); - F32 intersect_center_x = intersect_rect.getCenterX(); - F32 intersect_center_y = intersect_rect.getCenterY(); - LLVector2 force = lerp(LLVector2(dst_center_x - intersect_center_x, dst_center_y - intersect_center_y), - LLVector2(intersect_center_x - src_center_x, intersect_center_y - src_center_y), - 0.5f); - force.setVec(dst_center_x - src_center_x, dst_center_y - src_center_y); - force.normVec(); - - LLVector2 src_force = -1.f * force; - LLVector2 dst_force = force; - - LLVector2 force_strength; - F32 src_mult = dst_textp->mMass / (dst_textp->mMass + src_textp->mMass); - F32 dst_mult = 1.f - src_mult; - F32 src_aspect_ratio = src_textp->mSoftScreenRect.getWidth() / src_textp->mSoftScreenRect.getHeight(); - F32 dst_aspect_ratio = dst_textp->mSoftScreenRect.getWidth() / dst_textp->mSoftScreenRect.getHeight(); - src_force.mV[VY] *= src_aspect_ratio; - src_force.normVec(); - dst_force.mV[VY] *= dst_aspect_ratio; - dst_force.normVec(); - - src_force.mV[VX] *= llmin(intersect_rect.getWidth() * src_mult, intersect_rect.getHeight() * SPRING_STRENGTH); - src_force.mV[VY] *= llmin(intersect_rect.getHeight() * src_mult, intersect_rect.getWidth() * SPRING_STRENGTH); - dst_force.mV[VX] *= llmin(intersect_rect.getWidth() * dst_mult, intersect_rect.getHeight() * SPRING_STRENGTH); - dst_force.mV[VY] *= llmin(intersect_rect.getHeight() * dst_mult, intersect_rect.getWidth() * SPRING_STRENGTH); - - src_textp->mTargetPositionOffset += src_force; - dst_textp->mTargetPositionOffset += dst_force; - src_textp->mTargetPositionOffset = src_textp->updateScreenPos(src_textp->mTargetPositionOffset); - dst_textp->mTargetPositionOffset = dst_textp->updateScreenPos(dst_textp->mTargetPositionOffset); - } - } - } - } - - VisibleTextObjectIterator this_object_it; - for (this_object_it = sVisibleTextObjects.begin(); this_object_it != sVisibleTextObjects.end(); ++this_object_it) - { - (*this_object_it)->mPositionOffset = lerp((*this_object_it)->mPositionOffset, (*this_object_it)->mTargetPositionOffset, LLSmoothInterpolation::getInterpolant(POSITION_DAMPING_TC)); - } -} - -void LLHUDNameTag::setLOD(S32 lod) -{ - mLOD = lod; - //RN: uncomment this to visualize LOD levels - //std::string label = llformat("%d", lod); - //setLabel(label); -} - -S32 LLHUDNameTag::getMaxLines() -{ - switch(mLOD) - { - case 0: - return mMaxLines; - case 1: - return mMaxLines > 0 ? mMaxLines / 2 : 5; - case 2: - return mMaxLines > 0 ? mMaxLines / 3 : 2; - default: - // label only - return 0; - } -} - -void LLHUDNameTag::markDead() -{ - sTextObjects.erase(LLPointer(this)); - LLHUDObject::markDead(); -} - -void LLHUDNameTag::shiftAll(const LLVector3& offset) -{ - TextObjectIterator text_it; - for (text_it = sTextObjects.begin(); text_it != sTextObjects.end(); ++text_it) - { - LLHUDNameTag *textp = text_it->get(); - textp->shift(offset); - } -} - -void LLHUDNameTag::shift(const LLVector3& offset) -{ - mPositionAgent += offset; -} - -F32 LLHUDNameTag::getWorldHeight() const -{ - const LLViewerCamera* camera = LLViewerCamera::getInstance(); - F32 height_meters = mLastDistance * (F32)tan(camera->getView() / 2.f); - F32 height_pixels = camera->getViewHeightInPixels() / 2.f; - F32 meters_per_pixel = height_meters / height_pixels; - return mHeight * meters_per_pixel * gViewerWindow->getDisplayScale().mV[VY]; -} - -//static -void LLHUDNameTag::addPickable(std::set &pick_list) -{ - //this might put an object on the pick list a second time, overriding it's mGLName, which is ok - // *FIX: we should probably cull against pick frustum - VisibleTextObjectIterator text_it; - for (text_it = sVisibleTextObjects.begin(); text_it != sVisibleTextObjects.end(); ++text_it) - { - pick_list.insert((*text_it)->mSourceObject); - } -} - -//static -// called when UI scale changes, to flush font width caches -void LLHUDNameTag::reshape() -{ - TextObjectIterator text_it; - for (text_it = sTextObjects.begin(); text_it != sTextObjects.end(); ++text_it) - { - LLHUDNameTag* textp = (*text_it); - std::vector::iterator segment_iter; - for (segment_iter = textp->mTextSegments.begin(); - segment_iter != textp->mTextSegments.end(); ++segment_iter ) - { - segment_iter->clearFontWidthMap(); - } - for(segment_iter = textp->mLabelSegments.begin(); - segment_iter != textp->mLabelSegments.end(); ++segment_iter ) - { - segment_iter->clearFontWidthMap(); - } - } -} - -//============================================================================ - -F32 LLHUDNameTag::LLHUDTextSegment::getWidth(const LLFontGL* font) -{ - std::map::iterator iter = mFontWidthMap.find(font); - if (iter != mFontWidthMap.end()) - { - return iter->second; - } - else - { - F32 width = font->getWidthF32(mText.c_str()); - mFontWidthMap[font] = width; - return width; - } -} +/** + * @file llhudnametag.cpp + * @brief Name tags for avatars + * @author James Cook + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llhudnametag.h" + +#include "llrender.h" +#include "lltracerecording.h" + +#include "llagent.h" +#include "llviewercontrol.h" +#include "llcriticaldamp.h" +#include "lldrawable.h" +#include "llfontgl.h" +#include "llglheaders.h" +#include "llhudrender.h" +#include "llui.h" +#include "llviewercamera.h" +#include "llviewertexturelist.h" +#include "llviewerobject.h" +#include "llvovolume.h" +#include "llviewerwindow.h" +#include "llstatusbar.h" +#include "llmenugl.h" +#include "pipeline.h" +#include + + +const F32 SPRING_STRENGTH = 0.7f; +const F32 HORIZONTAL_PADDING = 16.f; +const F32 VERTICAL_PADDING = 12.f; +const F32 LINE_PADDING = 3.f; // aka "leading" +const F32 BUFFER_SIZE = 2.f; +const S32 NUM_OVERLAP_ITERATIONS = 10; +const F32 POSITION_DAMPING_TC = 0.2f; +const F32 MAX_STABLE_CAMERA_VELOCITY = 0.1f; +const F32 LOD_0_SCREEN_COVERAGE = 0.15f; +const F32 LOD_1_SCREEN_COVERAGE = 0.30f; +const F32 LOD_2_SCREEN_COVERAGE = 0.40f; + +std::set > LLHUDNameTag::sTextObjects; +std::vector > LLHUDNameTag::sVisibleTextObjects; +bool LLHUDNameTag::sDisplayText = true ; +const F32 LLHUDNameTag::NAMETAG_MAX_WIDTH = 298.f; +const F32 LLHUDNameTag::HUD_TEXT_MAX_WIDTH = 190.f; + +bool llhudnametag_further_away::operator()(const LLPointer& lhs, const LLPointer& rhs) const +{ + return lhs->getDistance() > rhs->getDistance(); +} + + +LLHUDNameTag::LLHUDNameTag(const U8 type) +: LLHUDObject(type), + mDoFade(true), + mFadeDistance(8.f), + mFadeRange(4.f), + mLastDistance(0.f), + mZCompare(true), + mVisibleOffScreen(false), + mOffscreen(false), + mColor(1.f, 1.f, 1.f, 1.f), +// mScale(), + mWidth(0.f), + mHeight(0.f), + mFontp(LLFontGL::getFontSansSerifSmall()), + mBoldFontp(LLFontGL::getFontSansSerifBold()), + mSoftScreenRect(), + mPositionAgent(), + mPositionOffset(), + mMass(10.f), + mMaxLines(10), + mOffsetY(0), + mRadius(0.1f), + mTextSegments(), + mLabelSegments(), + mTextAlignment(ALIGN_TEXT_CENTER), + mVertAlignment(ALIGN_VERT_CENTER), + mLOD(0), + mHidden(false) +{ + LLPointer ptr(this); + sTextObjects.insert(ptr); + + mRoundedRectImgp = LLUI::getUIImage("Rounded_Rect"); + mRoundedRectTopImgp = LLUI::getUIImage("Rounded_Rect_Top"); +} + +LLHUDNameTag::~LLHUDNameTag() +{ +} + + +bool LLHUDNameTag::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, LLVector4a& intersection, bool debug_render) +{ + if (!mVisible || mHidden) + { + return false; + } + + // don't pick text that isn't bound to a viewerobject + if (!mSourceObject || mSourceObject->mDrawable.isNull()) + { + return false; + } + + F32 alpha_factor = 1.f; + LLColor4 text_color = mColor; + if (mDoFade) + { + if (mLastDistance > mFadeDistance) + { + alpha_factor = llmax(0.f, 1.f - (mLastDistance - mFadeDistance)/mFadeRange); + text_color.mV[3] = text_color.mV[3]*alpha_factor; + } + } + if (text_color.mV[3] < 0.01f) + { + return false; + } + + mOffsetY = lltrunc(mHeight * ((mVertAlignment == ALIGN_VERT_CENTER) ? 0.5f : 1.f)); + + LLVector3 position = mPositionAgent; + + if (mSourceObject) + { //get intersection of eye through mPositionAgent to plane of source object + //using this position keeps the camera from focusing on some seemingly random + //point several meters in front of the nametag + const LLVector3& p = mSourceObject->getPositionAgent(); + const LLVector3& n = LLViewerCamera::getInstance()->getAtAxis(); + const LLVector3& eye = LLViewerCamera::getInstance()->getOrigin(); + + LLVector3 ray = position-eye; + ray.normalize(); + + LLVector3 delta = p-position; + F32 dist = delta*n; + F32 dt = dist/(ray*n); + position += ray*dt; + } + + // scale screen size of borders down + + LLVector3 x_pixel_vec; + LLVector3 y_pixel_vec; + + LLViewerCamera::getInstance()->getPixelVectors(position, y_pixel_vec, x_pixel_vec); + + LLVector3 width_vec = mWidth * x_pixel_vec; + LLVector3 height_vec = mHeight * y_pixel_vec; + + LLCoordGL screen_pos; + LLViewerCamera::getInstance()->projectPosAgentToScreen(position, screen_pos, false); + + LLVector2 screen_offset; + screen_offset = updateScreenPos(mPositionOffset); + + LLVector3 render_position = position + + (x_pixel_vec * screen_offset.mV[VX]) + + (y_pixel_vec * screen_offset.mV[VY]); + + + LLVector3 bg_pos = render_position + + (F32)mOffsetY * y_pixel_vec + - (width_vec / 2.f) + - (height_vec); + + LLVector3 v[] = + { + bg_pos, + bg_pos + width_vec, + bg_pos + width_vec + height_vec, + bg_pos + height_vec, + }; + + LLVector4a dir; + dir.setSub(end,start); + F32 a, b, t; + + LLVector4a v0,v1,v2,v3; + v0.load3(v[0].mV); + v1.load3(v[1].mV); + v2.load3(v[2].mV); + v3.load3(v[3].mV); + + if (LLTriangleRayIntersect(v0, v1, v2, start, dir, a, b, t) || + LLTriangleRayIntersect(v2, v3, v0, start, dir, a, b, t) ) + { + if (t <= 1.f) + { + dir.mul(t); + intersection.setAdd(start, dir); + return true; + } + } + + return false; +} + +void LLHUDNameTag::render() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + if (sDisplayText) + { + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); + //LLGLDisable gls_stencil(GL_STENCIL_TEST); + renderText(false); + } +} + +void LLHUDNameTag::renderText(bool for_select) +{ + if (!mVisible || mHidden) + { + return; + } + + // don't pick text that isn't bound to a viewerobject + if (for_select && + (!mSourceObject || mSourceObject->mDrawable.isNull())) + { + return; + } + + if (for_select) + { + gGL.getTexUnit(0)->disable(); + } + else + { + gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE); + } + + LLGLState gls_blend(GL_BLEND, !for_select); + + LLColor4 shadow_color(0.f, 0.f, 0.f, 1.f); + F32 alpha_factor = 1.f; + LLColor4 text_color = mColor; + if (mDoFade) + { + if (mLastDistance > mFadeDistance) + { + alpha_factor = llmax(0.f, 1.f - (mLastDistance - mFadeDistance)/mFadeRange); + text_color.mV[3] = text_color.mV[3]*alpha_factor; + } + } + if (text_color.mV[3] < 0.01f) + { + return; + } + shadow_color.mV[3] = text_color.mV[3]; + + mOffsetY = lltrunc(mHeight * ((mVertAlignment == ALIGN_VERT_CENTER) ? 0.5f : 1.f)); + + // *TODO: make this a per-text setting + LLColor4 bg_color = LLUIColorTable::instance().getColor("NameTagBackground"); + bg_color.setAlpha(gSavedSettings.getF32("ChatBubbleOpacity") * alpha_factor); + + // scale screen size of borders down + //RN: for now, text on hud objects is never occluded + + LLVector3 x_pixel_vec; + LLVector3 y_pixel_vec; + + LLViewerCamera::getInstance()->getPixelVectors(mPositionAgent, y_pixel_vec, x_pixel_vec); + + LLVector3 width_vec = mWidth * x_pixel_vec; + LLVector3 height_vec = mHeight * y_pixel_vec; + + mRadius = (width_vec + height_vec).magVec() * 0.5f; + + LLCoordGL screen_pos; + LLViewerCamera::getInstance()->projectPosAgentToScreen(mPositionAgent, screen_pos, false); + + LLVector2 screen_offset = updateScreenPos(mPositionOffset); + + LLVector3 render_position = mPositionAgent + + (x_pixel_vec * screen_offset.mV[VX]) + + (y_pixel_vec * screen_offset.mV[VY]); + + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); + LLRect screen_rect; + screen_rect.setCenterAndSize(0, static_cast(lltrunc(-mHeight / 2 + mOffsetY)), static_cast(lltrunc(mWidth)), static_cast(lltrunc(mHeight))); + mRoundedRectImgp->draw3D(render_position, x_pixel_vec, y_pixel_vec, screen_rect, bg_color); + if (mLabelSegments.size()) + { + LLRect label_top_rect = screen_rect; + const S32 label_height = ll_round((mFontp->getLineHeight() * (F32)mLabelSegments.size() + (VERTICAL_PADDING / 3.f))); + label_top_rect.mBottom = label_top_rect.mTop - label_height; + LLColor4 label_top_color = text_color; + label_top_color.mV[VALPHA] = gSavedSettings.getF32("ChatBubbleOpacity") * alpha_factor; + + mRoundedRectTopImgp->draw3D(render_position, x_pixel_vec, y_pixel_vec, label_top_rect, label_top_color); + } + + F32 y_offset = (F32)mOffsetY; + + // Render label + { + for(std::vector::iterator segment_iter = mLabelSegments.begin(); + segment_iter != mLabelSegments.end(); ++segment_iter ) + { + // Label segments use default font + const LLFontGL* fontp = (segment_iter->mStyle == LLFontGL::BOLD) ? mBoldFontp : mFontp; + y_offset -= fontp->getLineHeight(); + + F32 x_offset; + if (mTextAlignment == ALIGN_TEXT_CENTER) + { + x_offset = -0.5f*segment_iter->getWidth(fontp); + } + else // ALIGN_LEFT + { + x_offset = -0.5f * mWidth + (HORIZONTAL_PADDING / 2.f); + } + + LLColor4 label_color(0.f, 0.f, 0.f, 1.f); + label_color.mV[VALPHA] = alpha_factor; + hud_render_text(segment_iter->getText(), render_position, *fontp, segment_iter->mStyle, LLFontGL::NO_SHADOW, x_offset, y_offset, label_color, false); + } + } + + // Render text + { + // -1 mMaxLines means unlimited lines. + S32 start_segment; + S32 max_lines = getMaxLines(); + + if (max_lines < 0) + { + start_segment = 0; + } + else + { + start_segment = llmax((S32)0, (S32)mTextSegments.size() - max_lines); + } + + for (std::vector::iterator segment_iter = mTextSegments.begin() + start_segment; + segment_iter != mTextSegments.end(); ++segment_iter ) + { + const LLFontGL* fontp = segment_iter->mFont; + y_offset -= fontp->getLineHeight(); + y_offset -= LINE_PADDING; + + U8 style = segment_iter->mStyle; + LLFontGL::ShadowType shadow = LLFontGL::DROP_SHADOW; + + F32 x_offset; + if (mTextAlignment== ALIGN_TEXT_CENTER) + { + x_offset = -0.5f*segment_iter->getWidth(fontp); + } + else // ALIGN_LEFT + { + x_offset = -0.5f * mWidth + (HORIZONTAL_PADDING / 2.f); + + // *HACK + x_offset += 1; + } + + text_color = segment_iter->mColor; + text_color.mV[VALPHA] *= alpha_factor; + + hud_render_text(segment_iter->getText(), render_position, *fontp, style, shadow, x_offset, y_offset, text_color, false); + } + } + /// Reset the default color to white. The renderer expects this to be the default. + gGL.color4f(1.0f, 1.0f, 1.0f, 1.0f); + if (for_select) + { + gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE); + } +} + +void LLHUDNameTag::setString(const std::string &text_utf8) +{ + mTextSegments.clear(); + addLine(text_utf8, mColor); +} + +void LLHUDNameTag::clearString() +{ + mTextSegments.clear(); +} + + +void LLHUDNameTag::addLine(const std::string &text_utf8, + const LLColor4& color, + const LLFontGL::StyleFlags style, + const LLFontGL* font, + const bool use_ellipses, + F32 max_pixels) +{ + LLWString wline = utf8str_to_wstring(text_utf8); + if (!wline.empty()) + { + // use default font for segment if custom font not specified + if (!font) + { + font = mFontp; + } + typedef boost::tokenizer, LLWString::const_iterator, LLWString > tokenizer; + LLWString seps(utf8str_to_wstring("\r\n")); + boost::char_separator sep(seps.c_str()); + + tokenizer tokens(wline, sep); + tokenizer::iterator iter = tokens.begin(); + + max_pixels = llmin(max_pixels, NAMETAG_MAX_WIDTH); + while (iter != tokens.end()) + { + U32 line_length = 0; + if (use_ellipses) + { + // "QualityAssuranceAssuresQuality1" will end up like "QualityAssuranceAssuresQual..." + // "QualityAssuranceAssuresQuality QualityAssuranceAssuresQuality" will end up like "QualityAssuranceAssuresQual..." + // "QualityAssurance AssuresQuality1" will end up as "QualityAssurance AssuresQua..." because we are enforcing single line + do + { + S32 segment_length = font->maxDrawableChars(iter->substr(line_length).c_str(), max_pixels, wline.length(), LLFontGL::ANYWHERE); + if (segment_length + line_length < wline.length()) // since we only draw one string, line_length should be 0 + { + // token does does not fit into signle line, need to draw "...". + // Use four dots for ellipsis width to generate padding + const LLWString dots_pad(utf8str_to_wstring(std::string("...."))); + S32 elipses_width = font->getWidthF32(dots_pad.c_str()); + // truncated string length + segment_length = font->maxDrawableChars(iter->substr(line_length).c_str(), max_pixels - elipses_width, wline.length(), LLFontGL::ANYWHERE); + const LLWString dots(utf8str_to_wstring(std::string("..."))); + LLHUDTextSegment segment(iter->substr(line_length, segment_length) + dots, style, color, font); + mTextSegments.push_back(segment); + break; // consider it to be complete + } + else + { + // token fits fully into string + LLHUDTextSegment segment(iter->substr(line_length, segment_length), style, color, font); + mTextSegments.push_back(segment); + line_length += segment_length; + } + } while (line_length != iter->size()); + } + else + { + // "QualityAssuranceAssuresQuality 1" will be split into two lines "QualityAssuranceAssuresQualit" and "y 1" + // "QualityAssurance AssuresQuality 1" will be split into two lines "QualityAssurance" and "AssuresQuality" + do + { + S32 segment_length = font->maxDrawableChars(iter->substr(line_length).c_str(), max_pixels, wline.length(), LLFontGL::WORD_BOUNDARY_IF_POSSIBLE); + LLHUDTextSegment segment(iter->substr(line_length, segment_length), style, color, font); + mTextSegments.push_back(segment); + line_length += segment_length; + } while (line_length != iter->size()); + } + ++iter; + } + } +} + +void LLHUDNameTag::setLabel(const std::string &label_utf8) +{ + mLabelSegments.clear(); + addLabel(label_utf8); +} + +void LLHUDNameTag::addLabel(const std::string& label_utf8, F32 max_pixels) +{ + LLWString wstr = utf8string_to_wstring(label_utf8); + if (!wstr.empty()) + { + LLWString seps(utf8str_to_wstring("\r\n")); + LLWString empty; + + typedef boost::tokenizer, LLWString::const_iterator, LLWString > tokenizer; + boost::char_separator sep(seps.c_str(), empty.c_str(), boost::keep_empty_tokens); + + tokenizer tokens(wstr, sep); + tokenizer::iterator iter = tokens.begin(); + + max_pixels = llmin(max_pixels, NAMETAG_MAX_WIDTH); + + while (iter != tokens.end()) + { + U32 line_length = 0; + do + { + S32 segment_length = mFontp->maxDrawableChars(iter->substr(line_length).c_str(), + max_pixels, wstr.length(), LLFontGL::WORD_BOUNDARY_IF_POSSIBLE); + LLHUDTextSegment segment(iter->substr(line_length, segment_length), LLFontGL::NORMAL, mColor, mFontp); + mLabelSegments.push_back(segment); + line_length += segment_length; + } + while (line_length != iter->size()); + ++iter; + } + } +} + +void LLHUDNameTag::setZCompare(const bool zcompare) +{ + mZCompare = zcompare; +} + +void LLHUDNameTag::setFont(const LLFontGL* font) +{ + mFontp = font; +} + + +void LLHUDNameTag::setColor(const LLColor4 &color) +{ + mColor = color; + for (std::vector::iterator segment_iter = mTextSegments.begin(); + segment_iter != mTextSegments.end(); ++segment_iter ) + { + segment_iter->mColor = color; + } +} + +void LLHUDNameTag::setAlpha(F32 alpha) +{ + mColor.mV[VALPHA] = alpha; + for (std::vector::iterator segment_iter = mTextSegments.begin(); + segment_iter != mTextSegments.end(); ++segment_iter ) + { + segment_iter->mColor.mV[VALPHA] = alpha; + } +} + + +void LLHUDNameTag::setDoFade(const bool do_fade) +{ + mDoFade = do_fade; +} + +void LLHUDNameTag::updateVisibility() +{ + if (mSourceObject) + { + mSourceObject->updateText(); + } + + mPositionAgent = gAgent.getPosAgentFromGlobal(mPositionGlobal); + + if (!mSourceObject) + { + //LL_WARNS() << "LLHUDNameTag::updateScreenPos -- mSourceObject is NULL!" << LL_ENDL; + mVisible = true; + sVisibleTextObjects.push_back(LLPointer (this)); + return; + } + + // Not visible if parent object is dead + if (mSourceObject->isDead()) + { + mVisible = false; + return; + } + + // push text towards camera by radius of object, but not past camera + LLVector3 vec_from_camera = mPositionAgent - LLViewerCamera::getInstance()->getOrigin(); + LLVector3 dir_from_camera = vec_from_camera; + dir_from_camera.normVec(); + + if (dir_from_camera * LLViewerCamera::getInstance()->getAtAxis() <= 0.f) + { //text is behind camera, don't render + mVisible = false; + return; + } + + if (vec_from_camera * LLViewerCamera::getInstance()->getAtAxis() <= LLViewerCamera::getInstance()->getNear() + 0.1f + mSourceObject->getVObjRadius()) + { + mPositionAgent = LLViewerCamera::getInstance()->getOrigin() + vec_from_camera * ((LLViewerCamera::getInstance()->getNear() + 0.1f) / (vec_from_camera * LLViewerCamera::getInstance()->getAtAxis())); + } + else + { + mPositionAgent -= dir_from_camera * mSourceObject->getVObjRadius(); + } + + mLastDistance = (mPositionAgent - LLViewerCamera::getInstance()->getOrigin()).magVec(); + + if (mLOD >= 3 || !mTextSegments.size() || (mDoFade && (mLastDistance > mFadeDistance + mFadeRange))) + { + mVisible = false; + return; + } + + LLVector3 x_pixel_vec; + LLVector3 y_pixel_vec; + + LLViewerCamera::getInstance()->getPixelVectors(mPositionAgent, y_pixel_vec, x_pixel_vec); + + LLVector3 render_position = mPositionAgent + + (x_pixel_vec * mPositionOffset.mV[VX]) + + (y_pixel_vec * mPositionOffset.mV[VY]); + + mOffscreen = false; + if (!LLViewerCamera::getInstance()->sphereInFrustum(render_position, mRadius)) + { + if (!mVisibleOffScreen) + { + mVisible = false; + return; + } + else + { + mOffscreen = true; + } + } + + mVisible = true; + sVisibleTextObjects.push_back(LLPointer (this)); +} + +LLVector2 LLHUDNameTag::updateScreenPos(LLVector2 &offset) +{ + LLCoordGL screen_pos; + LLVector2 screen_pos_vec; + LLVector3 x_pixel_vec; + LLVector3 y_pixel_vec; + LLViewerCamera::getInstance()->getPixelVectors(mPositionAgent, y_pixel_vec, x_pixel_vec); + LLVector3 world_pos = mPositionAgent + (offset.mV[VX] * x_pixel_vec) + (offset.mV[VY] * y_pixel_vec); + if (!LLViewerCamera::getInstance()->projectPosAgentToScreen(world_pos, screen_pos, false) && mVisibleOffScreen) + { + // bubble off-screen, so find a spot for it along screen edge + LLViewerCamera::getInstance()->projectPosAgentToScreenEdge(world_pos, screen_pos); + } + + screen_pos_vec.setVec((F32)screen_pos.mX, (F32)screen_pos.mY); + + LLRect world_rect = gViewerWindow->getWorldViewRectScaled(); + S32 bottom = world_rect.mBottom + STATUS_BAR_HEIGHT; + + LLVector2 screen_center; + screen_center.mV[VX] = llclamp((F32)screen_pos_vec.mV[VX], (F32)world_rect.mLeft + mWidth * 0.5f, (F32)world_rect.mRight - mWidth * 0.5f); + + if(mVertAlignment == ALIGN_VERT_TOP) + { + screen_center.mV[VY] = llclamp((F32)screen_pos_vec.mV[VY], + (F32)bottom, + (F32)world_rect.mTop - mHeight - (F32)MENU_BAR_HEIGHT); + mSoftScreenRect.setLeftTopAndSize(screen_center.mV[VX] - (mWidth + BUFFER_SIZE) * 0.5f, + screen_center.mV[VY] + (mHeight + BUFFER_SIZE), mWidth + BUFFER_SIZE, mHeight + BUFFER_SIZE); + } + else + { + screen_center.mV[VY] = llclamp((F32)screen_pos_vec.mV[VY], + (F32)bottom + mHeight * 0.5f, + (F32)world_rect.mTop - mHeight * 0.5f - (F32)MENU_BAR_HEIGHT); + mSoftScreenRect.setCenterAndSize(screen_center.mV[VX], screen_center.mV[VY], mWidth + BUFFER_SIZE, mHeight + BUFFER_SIZE); + } + + return offset + (screen_center - LLVector2((F32)screen_pos.mX, (F32)screen_pos.mY)); +} + +void LLHUDNameTag::updateSize() +{ + F32 height = 0.f; + F32 width = 0.f; + + S32 max_lines = getMaxLines(); + //S32 lines = (max_lines < 0) ? (S32)mTextSegments.size() : llmin((S32)mTextSegments.size(), max_lines); + //F32 height = (F32)mFontp->getLineHeight() * (lines + mLabelSegments.size()); + + S32 start_segment; + if (max_lines < 0) start_segment = 0; + else start_segment = llmax((S32)0, (S32)mTextSegments.size() - max_lines); + + std::vector::iterator iter = mTextSegments.begin() + start_segment; + while (iter != mTextSegments.end()) + { + const LLFontGL* fontp = iter->mFont; + height += fontp->getLineHeight(); + height += LINE_PADDING; + width = llmax(width, llmin(iter->getWidth(fontp), NAMETAG_MAX_WIDTH)); + ++iter; + } + + // Don't want line spacing under the last line + if (height > 0.f) + { + height -= LINE_PADDING; + } + + iter = mLabelSegments.begin(); + while (iter != mLabelSegments.end()) + { + height += mFontp->getLineHeight(); + width = llmax(width, llmin(iter->getWidth(mFontp), NAMETAG_MAX_WIDTH)); + ++iter; + } + + if (width == 0.f) + { + return; + } + + width += HORIZONTAL_PADDING; + height += VERTICAL_PADDING; + + // *TODO: Could do a timer-based resize here + //mWidth = llmax(width, lerp(mWidth, (F32)width, u)); + //mHeight = llmax(height, lerp(mHeight, (F32)height, u)); + mWidth = width; + mHeight = height; +} + +void LLHUDNameTag::updateAll() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + // iterate over all text objects, calculate their restoration forces, + // and add them to the visible set if they are on screen and close enough + sVisibleTextObjects.clear(); + + TextObjectIterator text_it; + for (text_it = sTextObjects.begin(); text_it != sTextObjects.end(); ++text_it) + { + LLHUDNameTag* textp = (*text_it); + textp->mTargetPositionOffset.clearVec(); + textp->updateSize(); + textp->updateVisibility(); + } + + // sort back to front for rendering purposes + std::sort(sVisibleTextObjects.begin(), sVisibleTextObjects.end(), llhudnametag_further_away()); + + // iterate from front to back, and set LOD based on current screen coverage + F32 screen_area = (F32)(gViewerWindow->getWindowWidthScaled() * gViewerWindow->getWindowHeightScaled()); + F32 current_screen_area = 0.f; + std::vector >::reverse_iterator r_it; + for (r_it = sVisibleTextObjects.rbegin(); r_it != sVisibleTextObjects.rend(); ++r_it) + { + LLHUDNameTag* textp = (*r_it); + if (current_screen_area / screen_area > LOD_2_SCREEN_COVERAGE) + { + textp->setLOD(3); + } + else if (current_screen_area / screen_area > LOD_1_SCREEN_COVERAGE) + { + textp->setLOD(2); + } + else if (current_screen_area / screen_area > LOD_0_SCREEN_COVERAGE) + { + textp->setLOD(1); + } + else + { + textp->setLOD(0); + } + textp->updateSize(); + // find on-screen position and initialize collision rectangle + textp->mTargetPositionOffset = textp->updateScreenPos(LLVector2::zero); + current_screen_area += (F32)(textp->mSoftScreenRect.getWidth() * textp->mSoftScreenRect.getHeight()); + } + + LLTrace::CountStatHandle<>* camera_vel_stat = LLViewerCamera::getVelocityStat(); + F32 camera_vel = LLTrace::get_frame_recording().getLastRecording().getPerSec(*camera_vel_stat); + if (camera_vel > MAX_STABLE_CAMERA_VELOCITY) + { + return; + } + + VisibleTextObjectIterator src_it; + + for (S32 i = 0; i < NUM_OVERLAP_ITERATIONS; i++) + { + for (src_it = sVisibleTextObjects.begin(); src_it != sVisibleTextObjects.end(); ++src_it) + { + LLHUDNameTag* src_textp = (*src_it); + + VisibleTextObjectIterator dst_it = src_it; + ++dst_it; + for (; dst_it != sVisibleTextObjects.end(); ++dst_it) + { + LLHUDNameTag* dst_textp = (*dst_it); + + if (src_textp->mSoftScreenRect.overlaps(dst_textp->mSoftScreenRect)) + { + LLRectf intersect_rect = src_textp->mSoftScreenRect; + intersect_rect.intersectWith(dst_textp->mSoftScreenRect); + intersect_rect.stretch(-BUFFER_SIZE * 0.5f); + + F32 src_center_x = src_textp->mSoftScreenRect.getCenterX(); + F32 src_center_y = src_textp->mSoftScreenRect.getCenterY(); + F32 dst_center_x = dst_textp->mSoftScreenRect.getCenterX(); + F32 dst_center_y = dst_textp->mSoftScreenRect.getCenterY(); + F32 intersect_center_x = intersect_rect.getCenterX(); + F32 intersect_center_y = intersect_rect.getCenterY(); + LLVector2 force = lerp(LLVector2(dst_center_x - intersect_center_x, dst_center_y - intersect_center_y), + LLVector2(intersect_center_x - src_center_x, intersect_center_y - src_center_y), + 0.5f); + force.setVec(dst_center_x - src_center_x, dst_center_y - src_center_y); + force.normVec(); + + LLVector2 src_force = -1.f * force; + LLVector2 dst_force = force; + + LLVector2 force_strength; + F32 src_mult = dst_textp->mMass / (dst_textp->mMass + src_textp->mMass); + F32 dst_mult = 1.f - src_mult; + F32 src_aspect_ratio = src_textp->mSoftScreenRect.getWidth() / src_textp->mSoftScreenRect.getHeight(); + F32 dst_aspect_ratio = dst_textp->mSoftScreenRect.getWidth() / dst_textp->mSoftScreenRect.getHeight(); + src_force.mV[VY] *= src_aspect_ratio; + src_force.normVec(); + dst_force.mV[VY] *= dst_aspect_ratio; + dst_force.normVec(); + + src_force.mV[VX] *= llmin(intersect_rect.getWidth() * src_mult, intersect_rect.getHeight() * SPRING_STRENGTH); + src_force.mV[VY] *= llmin(intersect_rect.getHeight() * src_mult, intersect_rect.getWidth() * SPRING_STRENGTH); + dst_force.mV[VX] *= llmin(intersect_rect.getWidth() * dst_mult, intersect_rect.getHeight() * SPRING_STRENGTH); + dst_force.mV[VY] *= llmin(intersect_rect.getHeight() * dst_mult, intersect_rect.getWidth() * SPRING_STRENGTH); + + src_textp->mTargetPositionOffset += src_force; + dst_textp->mTargetPositionOffset += dst_force; + src_textp->mTargetPositionOffset = src_textp->updateScreenPos(src_textp->mTargetPositionOffset); + dst_textp->mTargetPositionOffset = dst_textp->updateScreenPos(dst_textp->mTargetPositionOffset); + } + } + } + } + + VisibleTextObjectIterator this_object_it; + for (this_object_it = sVisibleTextObjects.begin(); this_object_it != sVisibleTextObjects.end(); ++this_object_it) + { + (*this_object_it)->mPositionOffset = lerp((*this_object_it)->mPositionOffset, (*this_object_it)->mTargetPositionOffset, LLSmoothInterpolation::getInterpolant(POSITION_DAMPING_TC)); + } +} + +void LLHUDNameTag::setLOD(S32 lod) +{ + mLOD = lod; + //RN: uncomment this to visualize LOD levels + //std::string label = llformat("%d", lod); + //setLabel(label); +} + +S32 LLHUDNameTag::getMaxLines() +{ + switch(mLOD) + { + case 0: + return mMaxLines; + case 1: + return mMaxLines > 0 ? mMaxLines / 2 : 5; + case 2: + return mMaxLines > 0 ? mMaxLines / 3 : 2; + default: + // label only + return 0; + } +} + +void LLHUDNameTag::markDead() +{ + sTextObjects.erase(LLPointer(this)); + LLHUDObject::markDead(); +} + +void LLHUDNameTag::shiftAll(const LLVector3& offset) +{ + TextObjectIterator text_it; + for (text_it = sTextObjects.begin(); text_it != sTextObjects.end(); ++text_it) + { + LLHUDNameTag *textp = text_it->get(); + textp->shift(offset); + } +} + +void LLHUDNameTag::shift(const LLVector3& offset) +{ + mPositionAgent += offset; +} + +F32 LLHUDNameTag::getWorldHeight() const +{ + const LLViewerCamera* camera = LLViewerCamera::getInstance(); + F32 height_meters = mLastDistance * (F32)tan(camera->getView() / 2.f); + F32 height_pixels = camera->getViewHeightInPixels() / 2.f; + F32 meters_per_pixel = height_meters / height_pixels; + return mHeight * meters_per_pixel * gViewerWindow->getDisplayScale().mV[VY]; +} + +//static +void LLHUDNameTag::addPickable(std::set &pick_list) +{ + //this might put an object on the pick list a second time, overriding it's mGLName, which is ok + // *FIX: we should probably cull against pick frustum + VisibleTextObjectIterator text_it; + for (text_it = sVisibleTextObjects.begin(); text_it != sVisibleTextObjects.end(); ++text_it) + { + pick_list.insert((*text_it)->mSourceObject); + } +} + +//static +// called when UI scale changes, to flush font width caches +void LLHUDNameTag::reshape() +{ + TextObjectIterator text_it; + for (text_it = sTextObjects.begin(); text_it != sTextObjects.end(); ++text_it) + { + LLHUDNameTag* textp = (*text_it); + std::vector::iterator segment_iter; + for (segment_iter = textp->mTextSegments.begin(); + segment_iter != textp->mTextSegments.end(); ++segment_iter ) + { + segment_iter->clearFontWidthMap(); + } + for(segment_iter = textp->mLabelSegments.begin(); + segment_iter != textp->mLabelSegments.end(); ++segment_iter ) + { + segment_iter->clearFontWidthMap(); + } + } +} + +//============================================================================ + +F32 LLHUDNameTag::LLHUDTextSegment::getWidth(const LLFontGL* font) +{ + std::map::iterator iter = mFontWidthMap.find(font); + if (iter != mFontWidthMap.end()) + { + return iter->second; + } + else + { + F32 width = font->getWidthF32(mText.c_str()); + mFontWidthMap[font] = width; + return width; + } +} diff --git a/indra/newview/llhudnametag.h b/indra/newview/llhudnametag.h index faba16198e..9abd8f33cc 100644 --- a/indra/newview/llhudnametag.h +++ b/indra/newview/llhudnametag.h @@ -1,195 +1,195 @@ -/** - * @file llhudnametag.h - * @brief Name tags for avatars - * @author James Cook - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLHUDNAMETAG_H -#define LLHUDNAMETAG_H - -#include "llpointer.h" - -#include "llhudobject.h" -#include "v4color.h" -//#include "v4coloru.h" -#include "v2math.h" -#include "llrect.h" -//#include "llframetimer.h" -#include "llfontgl.h" -#include -#include - -class LLHUDNameTag; -class LLUIImage; - -struct llhudnametag_further_away -{ - bool operator()(const LLPointer& lhs, const LLPointer& rhs) const; -}; - -class LLHUDNameTag : public LLHUDObject -{ -protected: - class LLHUDTextSegment - { - public: - LLHUDTextSegment(const LLWString& text, const LLFontGL::StyleFlags style, const LLColor4& color, const LLFontGL* font) - : mColor(color), - mStyle(style), - mText(text), - mFont(font) - {} - F32 getWidth(const LLFontGL* font); - const LLWString& getText() const { return mText; } - void clearFontWidthMap() { mFontWidthMap.clear(); } - - LLColor4 mColor; - LLFontGL::StyleFlags mStyle; - const LLFontGL* mFont; - private: - LLWString mText; - std::map mFontWidthMap; - }; - -public: - typedef enum e_text_alignment - { - ALIGN_TEXT_LEFT, - ALIGN_TEXT_CENTER - } ETextAlignment; - - typedef enum e_vert_alignment - { - ALIGN_VERT_TOP, - ALIGN_VERT_CENTER - } EVertAlignment; - - static const F32 NAMETAG_MAX_WIDTH; // 298px, made to fit 31 M's - static const F32 HUD_TEXT_MAX_WIDTH; // 190px - -public: - // Set entire string, eliminating existing lines - void setString(const std::string& text_utf8); - - void clearString(); - - // Add text a line at a time, allowing custom formatting - void addLine( - const std::string &text_utf8, - const LLColor4& color, - const LLFontGL::StyleFlags style = LLFontGL::NORMAL, - const LLFontGL* font = NULL, - const bool use_ellipses = false, - F32 max_pixels = HUD_TEXT_MAX_WIDTH); - - // For bubble chat, set the part above the chat text - void setLabel(const std::string& label_utf8); - void addLabel(const std::string& label_utf8, F32 max_pixels = HUD_TEXT_MAX_WIDTH); - - // Sets the default font for lines with no font specified - void setFont(const LLFontGL* font); - void setColor(const LLColor4 &color); - void setAlpha(F32 alpha); - void setZCompare(const bool zcompare); - void setDoFade(const bool do_fade); - void setVisibleOffScreen(bool visible) { mVisibleOffScreen = visible; } - - // mMaxLines of -1 means unlimited lines. - void setMaxLines(S32 max_lines) { mMaxLines = max_lines; } - void setFadeDistance(F32 fade_distance, F32 fade_range) { mFadeDistance = fade_distance; mFadeRange = fade_range; } - void updateVisibility(); - LLVector2 updateScreenPos(LLVector2 &offset_target); - void updateSize(); -// void setMass(F32 mass) { mMass = llmax(0.1f, mass); } - void setTextAlignment(ETextAlignment alignment) { mTextAlignment = alignment; } - void setVertAlignment(EVertAlignment alignment) { mVertAlignment = alignment; } - /*virtual*/ void markDead(); - friend class LLHUDObject; - /*virtual*/ F32 getDistance() const { return mLastDistance; } - S32 getLOD() const { return mLOD; } - bool getVisible() const { return mVisible; } - bool getHidden() const { return mHidden; } - void setHidden( bool hide ) { mHidden = hide; } - void shift(const LLVector3& offset); - F32 getWorldHeight() const; - - bool lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, LLVector4a& intersection, bool debug_render = false); - - static void shiftAll(const LLVector3& offset); - static void addPickable(std::set &pick_list); - static void reshape(); - static void setDisplayText(bool flag) { sDisplayText = flag ; } - -protected: - LLHUDNameTag(const U8 type); - - /*virtual*/ void render(); - void renderText(bool for_select); - static void updateAll(); - void setLOD(S32 lod); - S32 getMaxLines(); - -private: - ~LLHUDNameTag(); - bool mDoFade; - F32 mFadeRange; - F32 mFadeDistance; - F32 mLastDistance; - bool mZCompare; - bool mVisibleOffScreen; - bool mOffscreen; - LLColor4 mColor; -// LLVector3 mScale; - F32 mWidth; - F32 mHeight; -// LLColor4U mPickColor; - const LLFontGL* mFontp; - const LLFontGL* mBoldFontp; - LLRectf mSoftScreenRect; - LLVector3 mPositionAgent; - LLVector2 mPositionOffset; - LLVector2 mTargetPositionOffset; - F32 mMass; - S32 mMaxLines; - S32 mOffsetY; - F32 mRadius; - std::vector mTextSegments; - std::vector mLabelSegments; -// LLFrameTimer mResizeTimer; - ETextAlignment mTextAlignment; - EVertAlignment mVertAlignment; - S32 mLOD; - bool mHidden; - LLPointer mRoundedRectImgp; - LLPointer mRoundedRectTopImgp; - - static bool sDisplayText ; - static std::set > sTextObjects; - static std::vector > sVisibleTextObjects; -// static std::vector > sVisibleHUDTextObjects; - typedef std::set >::iterator TextObjectIterator; - typedef std::vector >::iterator VisibleTextObjectIterator; -}; - -#endif +/** + * @file llhudnametag.h + * @brief Name tags for avatars + * @author James Cook + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLHUDNAMETAG_H +#define LLHUDNAMETAG_H + +#include "llpointer.h" + +#include "llhudobject.h" +#include "v4color.h" +//#include "v4coloru.h" +#include "v2math.h" +#include "llrect.h" +//#include "llframetimer.h" +#include "llfontgl.h" +#include +#include + +class LLHUDNameTag; +class LLUIImage; + +struct llhudnametag_further_away +{ + bool operator()(const LLPointer& lhs, const LLPointer& rhs) const; +}; + +class LLHUDNameTag : public LLHUDObject +{ +protected: + class LLHUDTextSegment + { + public: + LLHUDTextSegment(const LLWString& text, const LLFontGL::StyleFlags style, const LLColor4& color, const LLFontGL* font) + : mColor(color), + mStyle(style), + mText(text), + mFont(font) + {} + F32 getWidth(const LLFontGL* font); + const LLWString& getText() const { return mText; } + void clearFontWidthMap() { mFontWidthMap.clear(); } + + LLColor4 mColor; + LLFontGL::StyleFlags mStyle; + const LLFontGL* mFont; + private: + LLWString mText; + std::map mFontWidthMap; + }; + +public: + typedef enum e_text_alignment + { + ALIGN_TEXT_LEFT, + ALIGN_TEXT_CENTER + } ETextAlignment; + + typedef enum e_vert_alignment + { + ALIGN_VERT_TOP, + ALIGN_VERT_CENTER + } EVertAlignment; + + static const F32 NAMETAG_MAX_WIDTH; // 298px, made to fit 31 M's + static const F32 HUD_TEXT_MAX_WIDTH; // 190px + +public: + // Set entire string, eliminating existing lines + void setString(const std::string& text_utf8); + + void clearString(); + + // Add text a line at a time, allowing custom formatting + void addLine( + const std::string &text_utf8, + const LLColor4& color, + const LLFontGL::StyleFlags style = LLFontGL::NORMAL, + const LLFontGL* font = NULL, + const bool use_ellipses = false, + F32 max_pixels = HUD_TEXT_MAX_WIDTH); + + // For bubble chat, set the part above the chat text + void setLabel(const std::string& label_utf8); + void addLabel(const std::string& label_utf8, F32 max_pixels = HUD_TEXT_MAX_WIDTH); + + // Sets the default font for lines with no font specified + void setFont(const LLFontGL* font); + void setColor(const LLColor4 &color); + void setAlpha(F32 alpha); + void setZCompare(const bool zcompare); + void setDoFade(const bool do_fade); + void setVisibleOffScreen(bool visible) { mVisibleOffScreen = visible; } + + // mMaxLines of -1 means unlimited lines. + void setMaxLines(S32 max_lines) { mMaxLines = max_lines; } + void setFadeDistance(F32 fade_distance, F32 fade_range) { mFadeDistance = fade_distance; mFadeRange = fade_range; } + void updateVisibility(); + LLVector2 updateScreenPos(LLVector2 &offset_target); + void updateSize(); +// void setMass(F32 mass) { mMass = llmax(0.1f, mass); } + void setTextAlignment(ETextAlignment alignment) { mTextAlignment = alignment; } + void setVertAlignment(EVertAlignment alignment) { mVertAlignment = alignment; } + /*virtual*/ void markDead(); + friend class LLHUDObject; + /*virtual*/ F32 getDistance() const { return mLastDistance; } + S32 getLOD() const { return mLOD; } + bool getVisible() const { return mVisible; } + bool getHidden() const { return mHidden; } + void setHidden( bool hide ) { mHidden = hide; } + void shift(const LLVector3& offset); + F32 getWorldHeight() const; + + bool lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, LLVector4a& intersection, bool debug_render = false); + + static void shiftAll(const LLVector3& offset); + static void addPickable(std::set &pick_list); + static void reshape(); + static void setDisplayText(bool flag) { sDisplayText = flag ; } + +protected: + LLHUDNameTag(const U8 type); + + /*virtual*/ void render(); + void renderText(bool for_select); + static void updateAll(); + void setLOD(S32 lod); + S32 getMaxLines(); + +private: + ~LLHUDNameTag(); + bool mDoFade; + F32 mFadeRange; + F32 mFadeDistance; + F32 mLastDistance; + bool mZCompare; + bool mVisibleOffScreen; + bool mOffscreen; + LLColor4 mColor; +// LLVector3 mScale; + F32 mWidth; + F32 mHeight; +// LLColor4U mPickColor; + const LLFontGL* mFontp; + const LLFontGL* mBoldFontp; + LLRectf mSoftScreenRect; + LLVector3 mPositionAgent; + LLVector2 mPositionOffset; + LLVector2 mTargetPositionOffset; + F32 mMass; + S32 mMaxLines; + S32 mOffsetY; + F32 mRadius; + std::vector mTextSegments; + std::vector mLabelSegments; +// LLFrameTimer mResizeTimer; + ETextAlignment mTextAlignment; + EVertAlignment mVertAlignment; + S32 mLOD; + bool mHidden; + LLPointer mRoundedRectImgp; + LLPointer mRoundedRectTopImgp; + + static bool sDisplayText ; + static std::set > sTextObjects; + static std::vector > sVisibleTextObjects; +// static std::vector > sVisibleHUDTextObjects; + typedef std::set >::iterator TextObjectIterator; + typedef std::vector >::iterator VisibleTextObjectIterator; +}; + +#endif diff --git a/indra/newview/llhudobject.cpp b/indra/newview/llhudobject.cpp index e914caa601..e6fbfbfb38 100644 --- a/indra/newview/llhudobject.cpp +++ b/indra/newview/llhudobject.cpp @@ -1,331 +1,331 @@ -/** - * @file llhudobject.cpp - * @brief LLHUDObject class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2002-2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llhudobject.h" -#include "llviewerobject.h" - -#include "llhudtext.h" -#include "llhudicon.h" -#include "llhudeffectbeam.h" -#include "llhudeffectblob.h" -#include "llhudeffecttrail.h" -#include "llhudeffectlookat.h" -#include "llhudeffectpointat.h" -#include "llhudnametag.h" -#include "llvoicevisualizer.h" - -#include "llagent.h" - -// statics -std::list > LLHUDObject::sHUDObjects; - -struct hud_object_further_away -{ - bool operator()(const LLPointer& lhs, const LLPointer& rhs) const; -}; - - -bool hud_object_further_away::operator()(const LLPointer& lhs, const LLPointer& rhs) const -{ - return lhs->getDistance() > rhs->getDistance(); -} - - -LLHUDObject::LLHUDObject(const U8 type) : - mPositionGlobal(), - mSourceObject(NULL), - mTargetObject(NULL) -{ - mVisible = true; - mType = type; - mDead = false; -} - -LLHUDObject::~LLHUDObject() -{ -} - -void LLHUDObject::markDead() -{ - mVisible = false; - mDead = true; - mSourceObject = NULL; - mTargetObject = NULL; -} - -F32 LLHUDObject::getDistance() const -{ - return 0.f; -} - -void LLHUDObject::setSourceObject(LLViewerObject* objectp) -{ - if (objectp == mSourceObject) - { - return; - } - - mSourceObject = objectp; -} - -void LLHUDObject::setTargetObject(LLViewerObject* objectp) -{ - if (objectp == mTargetObject) - { - return; - } - - mTargetObject = objectp; -} - -void LLHUDObject::setPositionGlobal(const LLVector3d &position_global) -{ - mPositionGlobal = position_global; -} - -void LLHUDObject::setPositionAgent(const LLVector3 &position_agent) -{ - mPositionGlobal = gAgent.getPosGlobalFromAgent(position_agent); -} - -// static -void LLHUDObject::cleanupHUDObjects() -{ - LLHUDIcon::cleanupDeadIcons(); - hud_object_list_t::iterator object_it; - for (object_it = sHUDObjects.begin(); object_it != sHUDObjects.end(); ++object_it) - { - (*object_it)->markDead(); - if ((*object_it)->getNumRefs() > 1) - { - LL_INFOS() << "LLHUDObject " << (LLHUDObject *)(*object_it) << " type " << (S32)(*object_it)->getType() << " has " << (*object_it)->getNumRefs() << " refs!" << LL_ENDL; - } - } - sHUDObjects.clear(); -} - -// static -LLHUDObject *LLHUDObject::addHUDObject(const U8 type) -{ - LLHUDObject *hud_objectp = NULL; - - switch (type) - { - case LL_HUD_TEXT: - hud_objectp = new LLHUDText(type); - break; - case LL_HUD_ICON: - hud_objectp = new LLHUDIcon(type); - break; - case LL_HUD_NAME_TAG: - hud_objectp = new LLHUDNameTag(type); - break; - default: - LL_WARNS() << "Unknown type of hud object:" << (U32) type << LL_ENDL; - } - if (hud_objectp) - { - sHUDObjects.push_back(hud_objectp); - } - return hud_objectp; -} - -LLHUDEffect *LLHUDObject::addHUDEffect(const U8 type) -{ - LLHUDEffect *hud_objectp = NULL; - - switch (type) - { - case LL_HUD_EFFECT_BEAM: - hud_objectp = new LLHUDEffectSpiral(type); - ((LLHUDEffectSpiral *)hud_objectp)->setDuration(0.7f); - ((LLHUDEffectSpiral *)hud_objectp)->setVMag(0.f); - ((LLHUDEffectSpiral *)hud_objectp)->setVOffset(0.f); - ((LLHUDEffectSpiral *)hud_objectp)->setInitialRadius(0.1f); - ((LLHUDEffectSpiral *)hud_objectp)->setFinalRadius(0.2f); - ((LLHUDEffectSpiral *)hud_objectp)->setSpinRate(10.f); - ((LLHUDEffectSpiral *)hud_objectp)->setFlickerRate(0.f); - ((LLHUDEffectSpiral *)hud_objectp)->setScaleBase(0.05f); - ((LLHUDEffectSpiral *)hud_objectp)->setScaleVar(0.02f); - ((LLHUDEffectSpiral *)hud_objectp)->setColor(LLColor4U(255, 255, 255, 255)); - break; - case LL_HUD_EFFECT_GLOW: - // deprecated - break; - case LL_HUD_EFFECT_POINT: - hud_objectp = new LLHUDEffectSpiral(type); - ((LLHUDEffectSpiral *)hud_objectp)->setDuration(0.5f); - ((LLHUDEffectSpiral *)hud_objectp)->setVMag(1.f); - ((LLHUDEffectSpiral *)hud_objectp)->setVOffset(0.f); - ((LLHUDEffectSpiral *)hud_objectp)->setInitialRadius(0.5f); - ((LLHUDEffectSpiral *)hud_objectp)->setFinalRadius(1.f); - ((LLHUDEffectSpiral *)hud_objectp)->setSpinRate(10.f); - ((LLHUDEffectSpiral *)hud_objectp)->setFlickerRate(0.f); - ((LLHUDEffectSpiral *)hud_objectp)->setScaleBase(0.1f); - ((LLHUDEffectSpiral *)hud_objectp)->setScaleVar(0.1f); - ((LLHUDEffectSpiral *)hud_objectp)->setColor(LLColor4U(255, 255, 255, 255)); - break; - case LL_HUD_EFFECT_SPHERE: - hud_objectp = new LLHUDEffectSpiral(type); - ((LLHUDEffectSpiral *)hud_objectp)->setDuration(0.5f); - ((LLHUDEffectSpiral *)hud_objectp)->setVMag(1.f); - ((LLHUDEffectSpiral *)hud_objectp)->setVOffset(0.f); - ((LLHUDEffectSpiral *)hud_objectp)->setInitialRadius(0.5f); - ((LLHUDEffectSpiral *)hud_objectp)->setFinalRadius(0.5f); - ((LLHUDEffectSpiral *)hud_objectp)->setSpinRate(20.f); - ((LLHUDEffectSpiral *)hud_objectp)->setFlickerRate(0.f); - ((LLHUDEffectSpiral *)hud_objectp)->setScaleBase(0.1f); - ((LLHUDEffectSpiral *)hud_objectp)->setScaleVar(0.1f); - ((LLHUDEffectSpiral *)hud_objectp)->setColor(LLColor4U(255, 255, 255, 255)); - break; - case LL_HUD_EFFECT_SPIRAL: - hud_objectp = new LLHUDEffectSpiral(type); - ((LLHUDEffectSpiral *)hud_objectp)->setDuration(2.f); - ((LLHUDEffectSpiral *)hud_objectp)->setVMag(-2.f); - ((LLHUDEffectSpiral *)hud_objectp)->setVOffset(0.5f); - ((LLHUDEffectSpiral *)hud_objectp)->setInitialRadius(1.f); - ((LLHUDEffectSpiral *)hud_objectp)->setFinalRadius(0.5f); - ((LLHUDEffectSpiral *)hud_objectp)->setSpinRate(10.f); - ((LLHUDEffectSpiral *)hud_objectp)->setFlickerRate(20.f); - ((LLHUDEffectSpiral *)hud_objectp)->setScaleBase(0.02f); - ((LLHUDEffectSpiral *)hud_objectp)->setScaleVar(0.02f); - ((LLHUDEffectSpiral *)hud_objectp)->setColor(LLColor4U(255, 255, 255, 255)); - break; - case LL_HUD_EFFECT_EDIT: - hud_objectp = new LLHUDEffectSpiral(type); - ((LLHUDEffectSpiral *)hud_objectp)->setDuration(2.f); - ((LLHUDEffectSpiral *)hud_objectp)->setVMag(2.f); - ((LLHUDEffectSpiral *)hud_objectp)->setVOffset(-1.f); - ((LLHUDEffectSpiral *)hud_objectp)->setInitialRadius(1.5f); - ((LLHUDEffectSpiral *)hud_objectp)->setFinalRadius(1.f); - ((LLHUDEffectSpiral *)hud_objectp)->setSpinRate(4.f); - ((LLHUDEffectSpiral *)hud_objectp)->setFlickerRate(200.f); - ((LLHUDEffectSpiral *)hud_objectp)->setScaleBase(0.1f); - ((LLHUDEffectSpiral *)hud_objectp)->setScaleVar(0.1f); - ((LLHUDEffectSpiral *)hud_objectp)->setColor(LLColor4U(255, 255, 255, 255)); - break; - case LL_HUD_EFFECT_LOOKAT: - hud_objectp = new LLHUDEffectLookAt(type); - break; - case LL_HUD_EFFECT_VOICE_VISUALIZER: - hud_objectp = new LLVoiceVisualizer(type); - break; - case LL_HUD_EFFECT_POINTAT: - hud_objectp = new LLHUDEffectPointAt(type); - break; - case LL_HUD_EFFECT_BLOB: - hud_objectp = new LLHUDEffectBlob(type); - break; - default: - LL_WARNS() << "Unknown type of hud effect:" << (U32) type << LL_ENDL; - } - - if (hud_objectp) - { - sHUDObjects.push_back(hud_objectp); - } - return hud_objectp; -} - -static LLTrace::BlockTimerStatHandle FTM_HUD_UPDATE("Update Hud"); - -// static -void LLHUDObject::updateAll() -{ - LL_RECORD_BLOCK_TIME(FTM_HUD_UPDATE); - LLHUDText::updateAll(); - LLHUDIcon::updateAll(); - LLHUDNameTag::updateAll(); - sortObjects(); -} - -// static -void LLHUDObject::renderAll() -{ - LLGLSUIDefault gls_ui; - - gUIProgram.bind(); - gGL.color4f(1, 1, 1, 1); - - LLGLDepthTest depth(GL_FALSE, GL_FALSE); - - LLHUDObject *hud_objp; - - hud_object_list_t::iterator object_it; - for (object_it = sHUDObjects.begin(); object_it != sHUDObjects.end(); ) - { - hud_object_list_t::iterator cur_it = object_it++; - hud_objp = (*cur_it); - if (hud_objp->getNumRefs() == 1) - { - sHUDObjects.erase(cur_it); - } - else if (hud_objp->isVisible()) - { - hud_objp->render(); - } - } - - LLVertexBuffer::unbind(); - gUIProgram.unbind(); -} - -// static -void LLHUDObject::renderAllForTimer() -{ - LLHUDObject *hud_objp; - - hud_object_list_t::iterator object_it; - for (object_it = sHUDObjects.begin(); object_it != sHUDObjects.end(); ) - { - hud_object_list_t::iterator cur_it = object_it++; - hud_objp = (*cur_it); - if (hud_objp->getNumRefs() == 1) - { - sHUDObjects.erase(cur_it); - } - else if (hud_objp->isVisible()) - { - hud_objp->renderForTimer(); - } - } -} - -// static -void LLHUDObject::reshapeAll() -{ - // only hud objects that use fonts care about window size/scale changes - LLHUDText::reshape(); - LLHUDNameTag::reshape(); -} - -// static -void LLHUDObject::sortObjects() -{ - sHUDObjects.sort(hud_object_further_away()); -} +/** + * @file llhudobject.cpp + * @brief LLHUDObject class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2002-2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llhudobject.h" +#include "llviewerobject.h" + +#include "llhudtext.h" +#include "llhudicon.h" +#include "llhudeffectbeam.h" +#include "llhudeffectblob.h" +#include "llhudeffecttrail.h" +#include "llhudeffectlookat.h" +#include "llhudeffectpointat.h" +#include "llhudnametag.h" +#include "llvoicevisualizer.h" + +#include "llagent.h" + +// statics +std::list > LLHUDObject::sHUDObjects; + +struct hud_object_further_away +{ + bool operator()(const LLPointer& lhs, const LLPointer& rhs) const; +}; + + +bool hud_object_further_away::operator()(const LLPointer& lhs, const LLPointer& rhs) const +{ + return lhs->getDistance() > rhs->getDistance(); +} + + +LLHUDObject::LLHUDObject(const U8 type) : + mPositionGlobal(), + mSourceObject(NULL), + mTargetObject(NULL) +{ + mVisible = true; + mType = type; + mDead = false; +} + +LLHUDObject::~LLHUDObject() +{ +} + +void LLHUDObject::markDead() +{ + mVisible = false; + mDead = true; + mSourceObject = NULL; + mTargetObject = NULL; +} + +F32 LLHUDObject::getDistance() const +{ + return 0.f; +} + +void LLHUDObject::setSourceObject(LLViewerObject* objectp) +{ + if (objectp == mSourceObject) + { + return; + } + + mSourceObject = objectp; +} + +void LLHUDObject::setTargetObject(LLViewerObject* objectp) +{ + if (objectp == mTargetObject) + { + return; + } + + mTargetObject = objectp; +} + +void LLHUDObject::setPositionGlobal(const LLVector3d &position_global) +{ + mPositionGlobal = position_global; +} + +void LLHUDObject::setPositionAgent(const LLVector3 &position_agent) +{ + mPositionGlobal = gAgent.getPosGlobalFromAgent(position_agent); +} + +// static +void LLHUDObject::cleanupHUDObjects() +{ + LLHUDIcon::cleanupDeadIcons(); + hud_object_list_t::iterator object_it; + for (object_it = sHUDObjects.begin(); object_it != sHUDObjects.end(); ++object_it) + { + (*object_it)->markDead(); + if ((*object_it)->getNumRefs() > 1) + { + LL_INFOS() << "LLHUDObject " << (LLHUDObject *)(*object_it) << " type " << (S32)(*object_it)->getType() << " has " << (*object_it)->getNumRefs() << " refs!" << LL_ENDL; + } + } + sHUDObjects.clear(); +} + +// static +LLHUDObject *LLHUDObject::addHUDObject(const U8 type) +{ + LLHUDObject *hud_objectp = NULL; + + switch (type) + { + case LL_HUD_TEXT: + hud_objectp = new LLHUDText(type); + break; + case LL_HUD_ICON: + hud_objectp = new LLHUDIcon(type); + break; + case LL_HUD_NAME_TAG: + hud_objectp = new LLHUDNameTag(type); + break; + default: + LL_WARNS() << "Unknown type of hud object:" << (U32) type << LL_ENDL; + } + if (hud_objectp) + { + sHUDObjects.push_back(hud_objectp); + } + return hud_objectp; +} + +LLHUDEffect *LLHUDObject::addHUDEffect(const U8 type) +{ + LLHUDEffect *hud_objectp = NULL; + + switch (type) + { + case LL_HUD_EFFECT_BEAM: + hud_objectp = new LLHUDEffectSpiral(type); + ((LLHUDEffectSpiral *)hud_objectp)->setDuration(0.7f); + ((LLHUDEffectSpiral *)hud_objectp)->setVMag(0.f); + ((LLHUDEffectSpiral *)hud_objectp)->setVOffset(0.f); + ((LLHUDEffectSpiral *)hud_objectp)->setInitialRadius(0.1f); + ((LLHUDEffectSpiral *)hud_objectp)->setFinalRadius(0.2f); + ((LLHUDEffectSpiral *)hud_objectp)->setSpinRate(10.f); + ((LLHUDEffectSpiral *)hud_objectp)->setFlickerRate(0.f); + ((LLHUDEffectSpiral *)hud_objectp)->setScaleBase(0.05f); + ((LLHUDEffectSpiral *)hud_objectp)->setScaleVar(0.02f); + ((LLHUDEffectSpiral *)hud_objectp)->setColor(LLColor4U(255, 255, 255, 255)); + break; + case LL_HUD_EFFECT_GLOW: + // deprecated + break; + case LL_HUD_EFFECT_POINT: + hud_objectp = new LLHUDEffectSpiral(type); + ((LLHUDEffectSpiral *)hud_objectp)->setDuration(0.5f); + ((LLHUDEffectSpiral *)hud_objectp)->setVMag(1.f); + ((LLHUDEffectSpiral *)hud_objectp)->setVOffset(0.f); + ((LLHUDEffectSpiral *)hud_objectp)->setInitialRadius(0.5f); + ((LLHUDEffectSpiral *)hud_objectp)->setFinalRadius(1.f); + ((LLHUDEffectSpiral *)hud_objectp)->setSpinRate(10.f); + ((LLHUDEffectSpiral *)hud_objectp)->setFlickerRate(0.f); + ((LLHUDEffectSpiral *)hud_objectp)->setScaleBase(0.1f); + ((LLHUDEffectSpiral *)hud_objectp)->setScaleVar(0.1f); + ((LLHUDEffectSpiral *)hud_objectp)->setColor(LLColor4U(255, 255, 255, 255)); + break; + case LL_HUD_EFFECT_SPHERE: + hud_objectp = new LLHUDEffectSpiral(type); + ((LLHUDEffectSpiral *)hud_objectp)->setDuration(0.5f); + ((LLHUDEffectSpiral *)hud_objectp)->setVMag(1.f); + ((LLHUDEffectSpiral *)hud_objectp)->setVOffset(0.f); + ((LLHUDEffectSpiral *)hud_objectp)->setInitialRadius(0.5f); + ((LLHUDEffectSpiral *)hud_objectp)->setFinalRadius(0.5f); + ((LLHUDEffectSpiral *)hud_objectp)->setSpinRate(20.f); + ((LLHUDEffectSpiral *)hud_objectp)->setFlickerRate(0.f); + ((LLHUDEffectSpiral *)hud_objectp)->setScaleBase(0.1f); + ((LLHUDEffectSpiral *)hud_objectp)->setScaleVar(0.1f); + ((LLHUDEffectSpiral *)hud_objectp)->setColor(LLColor4U(255, 255, 255, 255)); + break; + case LL_HUD_EFFECT_SPIRAL: + hud_objectp = new LLHUDEffectSpiral(type); + ((LLHUDEffectSpiral *)hud_objectp)->setDuration(2.f); + ((LLHUDEffectSpiral *)hud_objectp)->setVMag(-2.f); + ((LLHUDEffectSpiral *)hud_objectp)->setVOffset(0.5f); + ((LLHUDEffectSpiral *)hud_objectp)->setInitialRadius(1.f); + ((LLHUDEffectSpiral *)hud_objectp)->setFinalRadius(0.5f); + ((LLHUDEffectSpiral *)hud_objectp)->setSpinRate(10.f); + ((LLHUDEffectSpiral *)hud_objectp)->setFlickerRate(20.f); + ((LLHUDEffectSpiral *)hud_objectp)->setScaleBase(0.02f); + ((LLHUDEffectSpiral *)hud_objectp)->setScaleVar(0.02f); + ((LLHUDEffectSpiral *)hud_objectp)->setColor(LLColor4U(255, 255, 255, 255)); + break; + case LL_HUD_EFFECT_EDIT: + hud_objectp = new LLHUDEffectSpiral(type); + ((LLHUDEffectSpiral *)hud_objectp)->setDuration(2.f); + ((LLHUDEffectSpiral *)hud_objectp)->setVMag(2.f); + ((LLHUDEffectSpiral *)hud_objectp)->setVOffset(-1.f); + ((LLHUDEffectSpiral *)hud_objectp)->setInitialRadius(1.5f); + ((LLHUDEffectSpiral *)hud_objectp)->setFinalRadius(1.f); + ((LLHUDEffectSpiral *)hud_objectp)->setSpinRate(4.f); + ((LLHUDEffectSpiral *)hud_objectp)->setFlickerRate(200.f); + ((LLHUDEffectSpiral *)hud_objectp)->setScaleBase(0.1f); + ((LLHUDEffectSpiral *)hud_objectp)->setScaleVar(0.1f); + ((LLHUDEffectSpiral *)hud_objectp)->setColor(LLColor4U(255, 255, 255, 255)); + break; + case LL_HUD_EFFECT_LOOKAT: + hud_objectp = new LLHUDEffectLookAt(type); + break; + case LL_HUD_EFFECT_VOICE_VISUALIZER: + hud_objectp = new LLVoiceVisualizer(type); + break; + case LL_HUD_EFFECT_POINTAT: + hud_objectp = new LLHUDEffectPointAt(type); + break; + case LL_HUD_EFFECT_BLOB: + hud_objectp = new LLHUDEffectBlob(type); + break; + default: + LL_WARNS() << "Unknown type of hud effect:" << (U32) type << LL_ENDL; + } + + if (hud_objectp) + { + sHUDObjects.push_back(hud_objectp); + } + return hud_objectp; +} + +static LLTrace::BlockTimerStatHandle FTM_HUD_UPDATE("Update Hud"); + +// static +void LLHUDObject::updateAll() +{ + LL_RECORD_BLOCK_TIME(FTM_HUD_UPDATE); + LLHUDText::updateAll(); + LLHUDIcon::updateAll(); + LLHUDNameTag::updateAll(); + sortObjects(); +} + +// static +void LLHUDObject::renderAll() +{ + LLGLSUIDefault gls_ui; + + gUIProgram.bind(); + gGL.color4f(1, 1, 1, 1); + + LLGLDepthTest depth(GL_FALSE, GL_FALSE); + + LLHUDObject *hud_objp; + + hud_object_list_t::iterator object_it; + for (object_it = sHUDObjects.begin(); object_it != sHUDObjects.end(); ) + { + hud_object_list_t::iterator cur_it = object_it++; + hud_objp = (*cur_it); + if (hud_objp->getNumRefs() == 1) + { + sHUDObjects.erase(cur_it); + } + else if (hud_objp->isVisible()) + { + hud_objp->render(); + } + } + + LLVertexBuffer::unbind(); + gUIProgram.unbind(); +} + +// static +void LLHUDObject::renderAllForTimer() +{ + LLHUDObject *hud_objp; + + hud_object_list_t::iterator object_it; + for (object_it = sHUDObjects.begin(); object_it != sHUDObjects.end(); ) + { + hud_object_list_t::iterator cur_it = object_it++; + hud_objp = (*cur_it); + if (hud_objp->getNumRefs() == 1) + { + sHUDObjects.erase(cur_it); + } + else if (hud_objp->isVisible()) + { + hud_objp->renderForTimer(); + } + } +} + +// static +void LLHUDObject::reshapeAll() +{ + // only hud objects that use fonts care about window size/scale changes + LLHUDText::reshape(); + LLHUDNameTag::reshape(); +} + +// static +void LLHUDObject::sortObjects() +{ + sHUDObjects.sort(hud_object_further_away()); +} diff --git a/indra/newview/llhudobject.h b/indra/newview/llhudobject.h index 797dc63017..8c628e3f92 100644 --- a/indra/newview/llhudobject.h +++ b/indra/newview/llhudobject.h @@ -1,123 +1,123 @@ -/** - * @file llhudobject.h - * @brief LLHUDObject class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLHUDOBJECT_H -#define LL_LLHUDOBJECT_H - -/** - * Base class and manager for in-world 2.5D non-interactive objects - */ - -#include "llpointer.h" - -#include "v4color.h" -#include "v3math.h" -#include "v3dmath.h" -#include "lldrawpool.h" // TODO: eliminate, unused below -#include - -class LLViewerCamera; -class LLFontGL; -class LLFace; -class LLViewerObject; -class LLHUDEffect; - -class LLHUDObject : public LLRefCount -{ -public: - virtual void markDead(); - virtual F32 getDistance() const; - virtual void setSourceObject(LLViewerObject* objectp); - virtual void setTargetObject(LLViewerObject* objectp); - virtual LLViewerObject* getSourceObject() { return mSourceObject; } - virtual LLViewerObject* getTargetObject() { return mTargetObject; } - - void setPositionGlobal(const LLVector3d &position_global); - void setPositionAgent(const LLVector3 &position_agent); - - bool isVisible() const { return mVisible; } - - U8 getType() const { return mType; } - - LLVector3d getPositionGlobal() const { return mPositionGlobal; } - - static LLHUDObject *addHUDObject(const U8 type); - static LLHUDEffect *addHUDEffect(const U8 type); - static void updateAll(); - static void renderAll(); - static void renderAllForSelect(); - static void renderAllForTimer(); - - // Some objects may need to update when window shape changes - static void reshapeAll(); - - static void cleanupHUDObjects(); - - enum - { - LL_HUD_TEXT, - LL_HUD_ICON, - LL_HUD_CONNECTOR, - LL_HUD_FLEXIBLE_OBJECT, // Ventrella - LL_HUD_ANIMAL_CONTROLS, // Ventrella - LL_HUD_LOCAL_ANIMATION_OBJECT, // Ventrella - LL_HUD_CLOTH, // Ventrella - LL_HUD_EFFECT_BEAM, - LL_HUD_EFFECT_GLOW, - LL_HUD_EFFECT_POINT, - LL_HUD_EFFECT_TRAIL, - LL_HUD_EFFECT_SPHERE, - LL_HUD_EFFECT_SPIRAL, - LL_HUD_EFFECT_EDIT, - LL_HUD_EFFECT_LOOKAT, - LL_HUD_EFFECT_POINTAT, - LL_HUD_EFFECT_VOICE_VISUALIZER, // Ventrella - LL_HUD_NAME_TAG, - LL_HUD_EFFECT_BLOB - }; -protected: - static void sortObjects(); - - LLHUDObject(const U8 type); - virtual ~LLHUDObject(); - - virtual void render() = 0; - virtual void renderForTimer() {}; - -protected: - U8 mType; - bool mDead; - bool mVisible; - LLVector3d mPositionGlobal; - LLPointer mSourceObject; - LLPointer mTargetObject; - -private: - typedef std::list > hud_object_list_t; - static hud_object_list_t sHUDObjects; -}; - -#endif // LL_LLHUDOBJECT_H +/** + * @file llhudobject.h + * @brief LLHUDObject class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLHUDOBJECT_H +#define LL_LLHUDOBJECT_H + +/** + * Base class and manager for in-world 2.5D non-interactive objects + */ + +#include "llpointer.h" + +#include "v4color.h" +#include "v3math.h" +#include "v3dmath.h" +#include "lldrawpool.h" // TODO: eliminate, unused below +#include + +class LLViewerCamera; +class LLFontGL; +class LLFace; +class LLViewerObject; +class LLHUDEffect; + +class LLHUDObject : public LLRefCount +{ +public: + virtual void markDead(); + virtual F32 getDistance() const; + virtual void setSourceObject(LLViewerObject* objectp); + virtual void setTargetObject(LLViewerObject* objectp); + virtual LLViewerObject* getSourceObject() { return mSourceObject; } + virtual LLViewerObject* getTargetObject() { return mTargetObject; } + + void setPositionGlobal(const LLVector3d &position_global); + void setPositionAgent(const LLVector3 &position_agent); + + bool isVisible() const { return mVisible; } + + U8 getType() const { return mType; } + + LLVector3d getPositionGlobal() const { return mPositionGlobal; } + + static LLHUDObject *addHUDObject(const U8 type); + static LLHUDEffect *addHUDEffect(const U8 type); + static void updateAll(); + static void renderAll(); + static void renderAllForSelect(); + static void renderAllForTimer(); + + // Some objects may need to update when window shape changes + static void reshapeAll(); + + static void cleanupHUDObjects(); + + enum + { + LL_HUD_TEXT, + LL_HUD_ICON, + LL_HUD_CONNECTOR, + LL_HUD_FLEXIBLE_OBJECT, // Ventrella + LL_HUD_ANIMAL_CONTROLS, // Ventrella + LL_HUD_LOCAL_ANIMATION_OBJECT, // Ventrella + LL_HUD_CLOTH, // Ventrella + LL_HUD_EFFECT_BEAM, + LL_HUD_EFFECT_GLOW, + LL_HUD_EFFECT_POINT, + LL_HUD_EFFECT_TRAIL, + LL_HUD_EFFECT_SPHERE, + LL_HUD_EFFECT_SPIRAL, + LL_HUD_EFFECT_EDIT, + LL_HUD_EFFECT_LOOKAT, + LL_HUD_EFFECT_POINTAT, + LL_HUD_EFFECT_VOICE_VISUALIZER, // Ventrella + LL_HUD_NAME_TAG, + LL_HUD_EFFECT_BLOB + }; +protected: + static void sortObjects(); + + LLHUDObject(const U8 type); + virtual ~LLHUDObject(); + + virtual void render() = 0; + virtual void renderForTimer() {}; + +protected: + U8 mType; + bool mDead; + bool mVisible; + LLVector3d mPositionGlobal; + LLPointer mSourceObject; + LLPointer mTargetObject; + +private: + typedef std::list > hud_object_list_t; + static hud_object_list_t sHUDObjects; +}; + +#endif // LL_LLHUDOBJECT_H diff --git a/indra/newview/llhudrender.cpp b/indra/newview/llhudrender.cpp index b22595a432..80d88702da 100644 --- a/indra/newview/llhudrender.cpp +++ b/indra/newview/llhudrender.cpp @@ -1,149 +1,149 @@ -/** - * @file llhudrender.cpp - * @brief LLHUDRender class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llhudrender.h" - -#include "llrender.h" -#include "llgl.h" -#include "llviewercamera.h" -#include "v3math.h" -#include "llquaternion.h" -#include "llfontgl.h" -#include "llglheaders.h" -#include "llviewerwindow.h" -#include "llui.h" - -void hud_render_utf8text(const std::string &str, const LLVector3 &pos_agent, - const LLFontGL &font, - const U8 style, - const LLFontGL::ShadowType shadow, - const F32 x_offset, const F32 y_offset, - const LLColor4& color, - const bool orthographic) -{ - LLWString wstr(utf8str_to_wstring(str)); - hud_render_text(wstr, pos_agent, font, style, shadow, x_offset, y_offset, color, orthographic); -} - -void hud_render_text(const LLWString &wstr, const LLVector3 &pos_agent, - const LLFontGL &font, - const U8 style, - const LLFontGL::ShadowType shadow, - const F32 x_offset, const F32 y_offset, - const LLColor4& color, - const bool orthographic) -{ - LLViewerCamera* camera = LLViewerCamera::getInstance(); - // Do cheap plane culling - LLVector3 dir_vec = pos_agent - camera->getOrigin(); - dir_vec /= dir_vec.magVec(); - - if (wstr.empty() || (!orthographic && dir_vec * camera->getAtAxis() <= 0.f)) - { - return; - } - - LLVector3 right_axis; - LLVector3 up_axis; - if (orthographic) - { - right_axis.setVec(0.f, -1.f / gViewerWindow->getWorldViewHeightScaled(), 0.f); - up_axis.setVec(0.f, 0.f, 1.f / gViewerWindow->getWorldViewHeightScaled()); - } - else - { - camera->getPixelVectors(pos_agent, up_axis, right_axis); - } - LLCoordFrame render_frame = *camera; - LLQuaternion rot; - if (!orthographic) - { - rot = render_frame.getQuaternion(); - rot = rot * LLQuaternion(-F_PI_BY_TWO, camera->getYAxis()); - rot = rot * LLQuaternion(F_PI_BY_TWO, camera->getXAxis()); - } - else - { - rot = LLQuaternion(-F_PI_BY_TWO, LLVector3(0.f, 0.f, 1.f)); - rot = rot * LLQuaternion(-F_PI_BY_TWO, LLVector3(0.f, 1.f, 0.f)); - } - F32 angle; - LLVector3 axis; - rot.getAngleAxis(&angle, axis); - - LLVector3 render_pos = pos_agent + (floorf(x_offset) * right_axis) + (floorf(y_offset) * up_axis); - - //get the render_pos in screen space - - F64 winX, winY, winZ; - LLRect world_view_rect = gViewerWindow->getWorldViewRectRaw(); - S32 viewport[4]; - viewport[0] = world_view_rect.mLeft; - viewport[1] = world_view_rect.mBottom; - viewport[2] = world_view_rect.getWidth(); - viewport[3] = world_view_rect.getHeight(); - - F64 mdlv[16]; - F64 proj[16]; - - for (U32 i = 0; i < 16; i++) - { - mdlv[i] = (F64) gGLModelView[i]; - proj[i] = (F64) gGLProjection[i]; - } - - gluProject(render_pos.mV[0], render_pos.mV[1], render_pos.mV[2], - mdlv, proj, (GLint*) viewport, - &winX, &winY, &winZ); - - //fonts all render orthographically, set up projection`` - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.pushMatrix(); - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - LLUI::pushMatrix(); - - gl_state_for_2d(world_view_rect.getWidth(), world_view_rect.getHeight()); - gViewerWindow->setup3DViewport(); - - winX -= world_view_rect.mLeft; - winY -= world_view_rect.mBottom; - LLUI::loadIdentity(); - gGL.loadIdentity(); - LLUI::translate((F32) winX*1.0f/LLFontGL::sScaleX, (F32) winY*1.0f/(LLFontGL::sScaleY), -(((F32) winZ*2.f)-1.f)); - F32 right_x; - - font.render(wstr, 0, 0, 1, color, LLFontGL::LEFT, LLFontGL::BASELINE, style, shadow, wstr.length(), 1000, &right_x, /*use_ellipses*/false, /*use_color*/true); - - LLUI::popMatrix(); - gGL.popMatrix(); - - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.popMatrix(); - gGL.matrixMode(LLRender::MM_MODELVIEW); -} +/** + * @file llhudrender.cpp + * @brief LLHUDRender class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llhudrender.h" + +#include "llrender.h" +#include "llgl.h" +#include "llviewercamera.h" +#include "v3math.h" +#include "llquaternion.h" +#include "llfontgl.h" +#include "llglheaders.h" +#include "llviewerwindow.h" +#include "llui.h" + +void hud_render_utf8text(const std::string &str, const LLVector3 &pos_agent, + const LLFontGL &font, + const U8 style, + const LLFontGL::ShadowType shadow, + const F32 x_offset, const F32 y_offset, + const LLColor4& color, + const bool orthographic) +{ + LLWString wstr(utf8str_to_wstring(str)); + hud_render_text(wstr, pos_agent, font, style, shadow, x_offset, y_offset, color, orthographic); +} + +void hud_render_text(const LLWString &wstr, const LLVector3 &pos_agent, + const LLFontGL &font, + const U8 style, + const LLFontGL::ShadowType shadow, + const F32 x_offset, const F32 y_offset, + const LLColor4& color, + const bool orthographic) +{ + LLViewerCamera* camera = LLViewerCamera::getInstance(); + // Do cheap plane culling + LLVector3 dir_vec = pos_agent - camera->getOrigin(); + dir_vec /= dir_vec.magVec(); + + if (wstr.empty() || (!orthographic && dir_vec * camera->getAtAxis() <= 0.f)) + { + return; + } + + LLVector3 right_axis; + LLVector3 up_axis; + if (orthographic) + { + right_axis.setVec(0.f, -1.f / gViewerWindow->getWorldViewHeightScaled(), 0.f); + up_axis.setVec(0.f, 0.f, 1.f / gViewerWindow->getWorldViewHeightScaled()); + } + else + { + camera->getPixelVectors(pos_agent, up_axis, right_axis); + } + LLCoordFrame render_frame = *camera; + LLQuaternion rot; + if (!orthographic) + { + rot = render_frame.getQuaternion(); + rot = rot * LLQuaternion(-F_PI_BY_TWO, camera->getYAxis()); + rot = rot * LLQuaternion(F_PI_BY_TWO, camera->getXAxis()); + } + else + { + rot = LLQuaternion(-F_PI_BY_TWO, LLVector3(0.f, 0.f, 1.f)); + rot = rot * LLQuaternion(-F_PI_BY_TWO, LLVector3(0.f, 1.f, 0.f)); + } + F32 angle; + LLVector3 axis; + rot.getAngleAxis(&angle, axis); + + LLVector3 render_pos = pos_agent + (floorf(x_offset) * right_axis) + (floorf(y_offset) * up_axis); + + //get the render_pos in screen space + + F64 winX, winY, winZ; + LLRect world_view_rect = gViewerWindow->getWorldViewRectRaw(); + S32 viewport[4]; + viewport[0] = world_view_rect.mLeft; + viewport[1] = world_view_rect.mBottom; + viewport[2] = world_view_rect.getWidth(); + viewport[3] = world_view_rect.getHeight(); + + F64 mdlv[16]; + F64 proj[16]; + + for (U32 i = 0; i < 16; i++) + { + mdlv[i] = (F64) gGLModelView[i]; + proj[i] = (F64) gGLProjection[i]; + } + + gluProject(render_pos.mV[0], render_pos.mV[1], render_pos.mV[2], + mdlv, proj, (GLint*) viewport, + &winX, &winY, &winZ); + + //fonts all render orthographically, set up projection`` + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.pushMatrix(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + LLUI::pushMatrix(); + + gl_state_for_2d(world_view_rect.getWidth(), world_view_rect.getHeight()); + gViewerWindow->setup3DViewport(); + + winX -= world_view_rect.mLeft; + winY -= world_view_rect.mBottom; + LLUI::loadIdentity(); + gGL.loadIdentity(); + LLUI::translate((F32) winX*1.0f/LLFontGL::sScaleX, (F32) winY*1.0f/(LLFontGL::sScaleY), -(((F32) winZ*2.f)-1.f)); + F32 right_x; + + font.render(wstr, 0, 0, 1, color, LLFontGL::LEFT, LLFontGL::BASELINE, style, shadow, wstr.length(), 1000, &right_x, /*use_ellipses*/false, /*use_color*/true); + + LLUI::popMatrix(); + gGL.popMatrix(); + + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.popMatrix(); + gGL.matrixMode(LLRender::MM_MODELVIEW); +} diff --git a/indra/newview/llhudrender.h b/indra/newview/llhudrender.h index aa11467480..eb8f07e4d4 100644 --- a/indra/newview/llhudrender.h +++ b/indra/newview/llhudrender.h @@ -1,59 +1,59 @@ -/** - * @file llhudrender.h - * @brief LLHUDRender class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLHUDRENDER_H -#define LL_LLHUDRENDER_H - -#include "llfontgl.h" - -class LLVector3; -class LLFontGL; - -// Utility classes for rendering HUD elements -void hud_render_text(const LLWString &wstr, - const LLVector3 &pos_agent, - const LLFontGL &font, - const U8 style, - const LLFontGL::ShadowType, - const F32 x_offset, - const F32 y_offset, - const LLColor4& color, - const bool orthographic); - -// Legacy, slower -void hud_render_utf8text(const std::string &str, - const LLVector3 &pos_agent, - const LLFontGL &font, - const U8 style, - const LLFontGL::ShadowType, - const F32 x_offset, - const F32 y_offset, - const LLColor4& color, - const bool orthographic); - - -#endif //LL_LLHUDRENDER_H - +/** + * @file llhudrender.h + * @brief LLHUDRender class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLHUDRENDER_H +#define LL_LLHUDRENDER_H + +#include "llfontgl.h" + +class LLVector3; +class LLFontGL; + +// Utility classes for rendering HUD elements +void hud_render_text(const LLWString &wstr, + const LLVector3 &pos_agent, + const LLFontGL &font, + const U8 style, + const LLFontGL::ShadowType, + const F32 x_offset, + const F32 y_offset, + const LLColor4& color, + const bool orthographic); + +// Legacy, slower +void hud_render_utf8text(const std::string &str, + const LLVector3 &pos_agent, + const LLFontGL &font, + const U8 style, + const LLFontGL::ShadowType, + const F32 x_offset, + const F32 y_offset, + const LLColor4& color, + const bool orthographic); + + +#endif //LL_LLHUDRENDER_H + diff --git a/indra/newview/llhudtext.cpp b/indra/newview/llhudtext.cpp index 6e8b7875c8..d087d538af 100644 --- a/indra/newview/llhudtext.cpp +++ b/indra/newview/llhudtext.cpp @@ -1,639 +1,639 @@ -/** - * @file llhudtext.cpp - * @brief Floating text above objects, set via script with llSetText() - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llhudtext.h" - -#include "llrender.h" - -#include "llagent.h" -#include "llviewercontrol.h" -#include "llcriticaldamp.h" -#include "lldrawable.h" -#include "llfontgl.h" -#include "llglheaders.h" -#include "llhudrender.h" -#include "llui.h" -#include "llviewercamera.h" -#include "llviewertexturelist.h" -#include "llviewerobject.h" -#include "llvovolume.h" -#include "llviewerwindow.h" -#include "llstatusbar.h" -#include "llmenugl.h" -#include "pipeline.h" -#include - -const F32 HORIZONTAL_PADDING = 15.f; -const F32 VERTICAL_PADDING = 12.f; -const F32 BUFFER_SIZE = 2.f; -const F32 HUD_TEXT_MAX_WIDTH = 190.f; -const F32 HUD_TEXT_MAX_WIDTH_NO_BUBBLE = 1000.f; -const F32 MAX_DRAW_DISTANCE = 300.f; - -std::set > LLHUDText::sTextObjects; -std::vector > LLHUDText::sVisibleTextObjects; -std::vector > LLHUDText::sVisibleHUDTextObjects; -bool LLHUDText::sDisplayText = true ; - -bool lltextobject_further_away::operator()(const LLPointer& lhs, const LLPointer& rhs) const -{ - return lhs->getDistance() > rhs->getDistance(); -} - - -LLHUDText::LLHUDText(const U8 type) : - LLHUDObject(type), - mOnHUDAttachment(false), -// mVisibleOffScreen(false), - mWidth(0.f), - mHeight(0.f), - mFontp(LLFontGL::getFontSansSerifSmall()), - mBoldFontp(LLFontGL::getFontSansSerifBold()), - mMass(1.f), - mMaxLines(10), - mOffsetY(0), - mTextAlignment(ALIGN_TEXT_CENTER), - mVertAlignment(ALIGN_VERT_CENTER), -// mLOD(0), - mHidden(false) -{ - mColor = LLColor4(1.f, 1.f, 1.f, 1.f); - mDoFade = true; - mFadeDistance = 8.f; - mFadeRange = 4.f; - mZCompare = true; - mOffscreen = false; - mRadius = 0.1f; - LLPointer ptr(this); - sTextObjects.insert(ptr); -} - -LLHUDText::~LLHUDText() -{ -} - -void LLHUDText::render() -{ - if (!mOnHUDAttachment && sDisplayText) - { - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); - //LLGLDisable gls_stencil(GL_STENCIL_TEST); - renderText(); - } -} - -void LLHUDText::renderText() -{ - if (!mVisible || mHidden) - { - return; - } - - gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE); - - LLGLState gls_blend(GL_BLEND, true); - - LLColor4 shadow_color(0.f, 0.f, 0.f, 1.f); - F32 alpha_factor = 1.f; - LLColor4 text_color = mColor; - if (mDoFade) - { - if (mLastDistance > mFadeDistance) - { - alpha_factor = llmax(0.f, 1.f - (mLastDistance - mFadeDistance)/mFadeRange); - text_color.mV[3] = text_color.mV[3]*alpha_factor; - } - } - if (text_color.mV[3] < 0.01f) - { - return; - } - shadow_color.mV[3] = text_color.mV[3]; - - mOffsetY = lltrunc(mHeight * ((mVertAlignment == ALIGN_VERT_CENTER) ? 0.5f : 1.f)); - - // *TODO: make this a per-text setting - LLColor4 bg_color = LLUIColorTable::instance().getColor("ObjectBubbleColor"); - bg_color.setAlpha(gSavedSettings.getF32("ChatBubbleOpacity") * alpha_factor); - - const S32 border_height = 16; - const S32 border_width = 16; - - // *TODO move this into helper function - F32 border_scale = 1.f; - - if (border_height * 2 > mHeight) - { - border_scale = (F32)mHeight / ((F32)border_height * 2.f); - } - if (border_width * 2 > mWidth) - { - border_scale = llmin(border_scale, (F32)mWidth / ((F32)border_width * 2.f)); - } - - // scale screen size of borders down - //RN: for now, text on hud objects is never occluded - - LLVector3 x_pixel_vec; - LLVector3 y_pixel_vec; - - if (mOnHUDAttachment) - { - x_pixel_vec = LLVector3::y_axis / (F32)gViewerWindow->getWorldViewWidthRaw(); - y_pixel_vec = LLVector3::z_axis / (F32)gViewerWindow->getWorldViewHeightRaw(); - } - else - { - LLViewerCamera::getInstance()->getPixelVectors(mPositionAgent, y_pixel_vec, x_pixel_vec); - } - - LLVector3 width_vec = mWidth * x_pixel_vec; - LLVector3 height_vec = mHeight * y_pixel_vec; - - mRadius = (width_vec + height_vec).magVec() * 0.5f; - - LLVector2 screen_offset; - screen_offset = mPositionOffset; - - LLVector3 render_position = mPositionAgent - + (x_pixel_vec * screen_offset.mV[VX]) - + (y_pixel_vec * screen_offset.mV[VY]); - - F32 y_offset = (F32)mOffsetY; - - // Render label - - // Render text - { - // -1 mMaxLines means unlimited lines. - S32 start_segment; - S32 max_lines = getMaxLines(); - - if (max_lines < 0) - { - start_segment = 0; - } - else - { - start_segment = llmax((S32)0, (S32)mTextSegments.size() - max_lines); - } - - for (std::vector::iterator segment_iter = mTextSegments.begin() + start_segment; - segment_iter != mTextSegments.end(); ++segment_iter ) - { - const LLFontGL* fontp = segment_iter->mFont; - y_offset -= fontp->getLineHeight() - 1; // correction factor to match legacy font metrics - - U8 style = segment_iter->mStyle; - LLFontGL::ShadowType shadow = LLFontGL::DROP_SHADOW; - - F32 x_offset; - if (mTextAlignment== ALIGN_TEXT_CENTER) - { - x_offset = -0.5f*segment_iter->getWidth(fontp); - } - else // ALIGN_LEFT - { - x_offset = -0.5f * mWidth + (HORIZONTAL_PADDING / 2.f); - } - - text_color = segment_iter->mColor; - if (mOnHUDAttachment) - { - text_color = linearColor4(text_color); - } - text_color.mV[VALPHA] *= alpha_factor; - - hud_render_text(segment_iter->getText(), render_position, *fontp, style, shadow, x_offset, y_offset, text_color, mOnHUDAttachment); - } - } - /// Reset the default color to white. The renderer expects this to be the default. - gGL.color4f(1.0f, 1.0f, 1.0f, 1.0f); -} - -void LLHUDText::setString(const std::string &text_utf8) -{ - mTextSegments.clear(); - addLine(text_utf8, mColor); -} - -void LLHUDText::clearString() -{ - mTextSegments.clear(); -} - - -void LLHUDText::addLine(const std::string &text_utf8, - const LLColor4& color, - const LLFontGL::StyleFlags style, - const LLFontGL* font) -{ - LLWString wline = utf8str_to_wstring(text_utf8); - if (!wline.empty()) - { - // use default font for segment if custom font not specified - if (!font) - { - font = mFontp; - } - typedef boost::tokenizer, LLWString::const_iterator, LLWString > tokenizer; - LLWString seps(utf8str_to_wstring("\r\n")); - boost::char_separator sep(seps.c_str()); - - tokenizer tokens(wline, sep); - tokenizer::iterator iter = tokens.begin(); - - while (iter != tokens.end()) - { - U32 line_length = 0; - do - { - F32 max_pixels = HUD_TEXT_MAX_WIDTH_NO_BUBBLE; - S32 segment_length = font->maxDrawableChars(iter->substr(line_length).c_str(), max_pixels, wline.length(), LLFontGL::WORD_BOUNDARY_IF_POSSIBLE); - LLHUDTextSegment segment(iter->substr(line_length, segment_length), style, color, font); - mTextSegments.push_back(segment); - line_length += segment_length; - } - while (line_length != iter->size()); - ++iter; - } - } -} - -void LLHUDText::setZCompare(const bool zcompare) -{ - mZCompare = zcompare; -} - -void LLHUDText::setFont(const LLFontGL* font) -{ - mFontp = font; -} - - -void LLHUDText::setColor(const LLColor4 &color) -{ - mColor = color; - for (std::vector::iterator segment_iter = mTextSegments.begin(); - segment_iter != mTextSegments.end(); ++segment_iter ) - { - segment_iter->mColor = color; - } -} - -void LLHUDText::setAlpha(F32 alpha) -{ - mColor.mV[VALPHA] = alpha; - for (std::vector::iterator segment_iter = mTextSegments.begin(); - segment_iter != mTextSegments.end(); ++segment_iter ) - { - segment_iter->mColor.mV[VALPHA] = alpha; - } -} - - -void LLHUDText::setDoFade(const bool do_fade) -{ - mDoFade = do_fade; -} - -void LLHUDText::updateVisibility() -{ - if (mSourceObject) - { - mSourceObject->updateText(); - } - - mPositionAgent = gAgent.getPosAgentFromGlobal(mPositionGlobal); - - if (!mSourceObject) - { - // Beacons - mVisible = true; - if (mOnHUDAttachment) - { - sVisibleHUDTextObjects.push_back(LLPointer (this)); - } - else - { - sVisibleTextObjects.push_back(LLPointer (this)); - } - return; - } - - // Not visible if parent object is dead - if (mSourceObject->isDead()) - { - mVisible = false; - return; - } - - // for now, all text on hud objects is visible - if (mOnHUDAttachment) - { - mVisible = true; - sVisibleHUDTextObjects.push_back(LLPointer (this)); - mLastDistance = mPositionAgent.mV[VX]; - return; - } - - // push text towards camera by radius of object, but not past camera - LLVector3 vec_from_camera = mPositionAgent - LLViewerCamera::getInstance()->getOrigin(); - LLVector3 dir_from_camera = vec_from_camera; - dir_from_camera.normVec(); - - if (dir_from_camera * LLViewerCamera::getInstance()->getAtAxis() <= 0.f) - { //text is behind camera, don't render - mVisible = false; - return; - } - - if (vec_from_camera * LLViewerCamera::getInstance()->getAtAxis() <= LLViewerCamera::getInstance()->getNear() + 0.1f + mSourceObject->getVObjRadius()) - { - mPositionAgent = LLViewerCamera::getInstance()->getOrigin() + vec_from_camera * ((LLViewerCamera::getInstance()->getNear() + 0.1f) / (vec_from_camera * LLViewerCamera::getInstance()->getAtAxis())); - } - else - { - mPositionAgent -= dir_from_camera * mSourceObject->getVObjRadius(); - } - - mLastDistance = (mPositionAgent - LLViewerCamera::getInstance()->getOrigin()).magVec(); - - if (!mTextSegments.size() || (mDoFade && (mLastDistance > mFadeDistance + mFadeRange))) - { - mVisible = false; - return; - } - - LLVector3 pos_agent_center = gAgent.getPosAgentFromGlobal(mPositionGlobal) - dir_from_camera; - F32 last_distance_center = (pos_agent_center - LLViewerCamera::getInstance()->getOrigin()).magVec(); - F32 max_draw_distance = gSavedSettings.getF32("PrimTextMaxDrawDistance"); - - if(max_draw_distance < 0) - { - max_draw_distance = 0; - gSavedSettings.setF32("PrimTextMaxDrawDistance", max_draw_distance); - } - else if(max_draw_distance > MAX_DRAW_DISTANCE) - { - max_draw_distance = MAX_DRAW_DISTANCE; - gSavedSettings.setF32("PrimTextMaxDrawDistance", MAX_DRAW_DISTANCE); - } - - if(last_distance_center > max_draw_distance) - { - mVisible = false; - return; - } - - - LLVector3 x_pixel_vec; - LLVector3 y_pixel_vec; - - LLViewerCamera::getInstance()->getPixelVectors(mPositionAgent, y_pixel_vec, x_pixel_vec); - - LLVector3 render_position = mPositionAgent + - (x_pixel_vec * mPositionOffset.mV[VX]) + - (y_pixel_vec * mPositionOffset.mV[VY]); - - mOffscreen = false; - if (!LLViewerCamera::getInstance()->sphereInFrustum(render_position, mRadius)) - { -// if (!mVisibleOffScreen) -// { - mVisible = false; - return; -// } -// else -// { -// mOffscreen = true; -// } - } - - mVisible = true; - sVisibleTextObjects.push_back(LLPointer (this)); -} - -LLVector2 LLHUDText::updateScreenPos(LLVector2 &offset) -{ - LLCoordGL screen_pos; - LLVector2 screen_pos_vec; - LLVector3 x_pixel_vec; - LLVector3 y_pixel_vec; - LLViewerCamera::getInstance()->getPixelVectors(mPositionAgent, y_pixel_vec, x_pixel_vec); -// LLVector3 world_pos = mPositionAgent + (offset.mV[VX] * x_pixel_vec) + (offset.mV[VY] * y_pixel_vec); -// if (!LLViewerCamera::getInstance()->projectPosAgentToScreen(world_pos, screen_pos, false) && mVisibleOffScreen) -// { -// // bubble off-screen, so find a spot for it along screen edge -// LLViewerCamera::getInstance()->projectPosAgentToScreenEdge(world_pos, screen_pos); -// } - - screen_pos_vec.setVec((F32)screen_pos.mX, (F32)screen_pos.mY); - - LLRect world_rect = gViewerWindow->getWorldViewRectScaled(); - S32 bottom = world_rect.mBottom + STATUS_BAR_HEIGHT; - - LLVector2 screen_center; - screen_center.mV[VX] = llclamp((F32)screen_pos_vec.mV[VX], (F32)world_rect.mLeft + mWidth * 0.5f, (F32)world_rect.mRight - mWidth * 0.5f); - - if(mVertAlignment == ALIGN_VERT_TOP) - { - screen_center.mV[VY] = llclamp((F32)screen_pos_vec.mV[VY], - (F32)bottom, - (F32)world_rect.mTop - mHeight - (F32)MENU_BAR_HEIGHT); - mSoftScreenRect.setLeftTopAndSize(screen_center.mV[VX] - (mWidth + BUFFER_SIZE) * 0.5f, - screen_center.mV[VY] + (mHeight + BUFFER_SIZE), mWidth + BUFFER_SIZE, mHeight + BUFFER_SIZE); - } - else - { - screen_center.mV[VY] = llclamp((F32)screen_pos_vec.mV[VY], - (F32)bottom + mHeight * 0.5f, - (F32)world_rect.mTop - mHeight * 0.5f - (F32)MENU_BAR_HEIGHT); - mSoftScreenRect.setCenterAndSize(screen_center.mV[VX], screen_center.mV[VY], mWidth + BUFFER_SIZE, mHeight + BUFFER_SIZE); - } - - return offset + (screen_center - LLVector2((F32)screen_pos.mX, (F32)screen_pos.mY)); -} - -void LLHUDText::updateSize() -{ - F32 height = 0.f; - F32 width = 0.f; - - S32 max_lines = getMaxLines(); - - S32 start_segment; - if (max_lines < 0) start_segment = 0; - else start_segment = llmax((S32)0, (S32)mTextSegments.size() - max_lines); - - std::vector::iterator iter = mTextSegments.begin() + start_segment; - while (iter != mTextSegments.end()) - { - const LLFontGL* fontp = iter->mFont; - height += fontp->getLineHeight() - 1; // correction factor to match legacy font metrics - width = llmax(width, llmin(iter->getWidth(fontp), HUD_TEXT_MAX_WIDTH)); - ++iter; - } - - if (width == 0.f) - { - return; - } - - width += HORIZONTAL_PADDING; - height += VERTICAL_PADDING; - - // *TODO: Could do some sort of timer-based resize logic here - F32 u = 1.f; - mWidth = llmax(width, lerp(mWidth, (F32)width, u)); - mHeight = llmax(height, lerp(mHeight, (F32)height, u)); -} - -void LLHUDText::updateAll() -{ - // iterate over all text objects, calculate their restoration forces, - // and add them to the visible set if they are on screen and close enough - sVisibleTextObjects.clear(); - sVisibleHUDTextObjects.clear(); - - TextObjectIterator text_it; - for (text_it = sTextObjects.begin(); text_it != sTextObjects.end(); ++text_it) - { - LLHUDText* textp = (*text_it); - textp->mTargetPositionOffset.clearVec(); - textp->updateSize(); - textp->updateVisibility(); - } - - // sort back to front for rendering purposes - std::sort(sVisibleTextObjects.begin(), sVisibleTextObjects.end(), lltextobject_further_away()); - std::sort(sVisibleHUDTextObjects.begin(), sVisibleHUDTextObjects.end(), lltextobject_further_away()); -} - -//void LLHUDText::setLOD(S32 lod) -//{ -// mLOD = lod; -// //RN: uncomment this to visualize LOD levels -// //std::string label = llformat("%d", lod); -// //setLabel(label); -//} - -S32 LLHUDText::getMaxLines() -{ - return mMaxLines; - //switch(mLOD) - //{ - //case 0: - // return mMaxLines; - //case 1: - // return mMaxLines > 0 ? mMaxLines / 2 : 5; - //case 2: - // return mMaxLines > 0 ? mMaxLines / 3 : 2; - //default: - // // label only - // return 0; - //} -} - -void LLHUDText::markDead() -{ - // make sure we have at least one pointer - // till the end of the function - LLPointer ptr(this); - sTextObjects.erase(ptr); - LLHUDObject::markDead(); -} - -void LLHUDText::renderAllHUD() -{ - LLGLState::checkStates(); - - { - LLGLDepthTest depth(GL_FALSE, GL_FALSE); - - VisibleTextObjectIterator text_it; - - for (text_it = sVisibleHUDTextObjects.begin(); text_it != sVisibleHUDTextObjects.end(); ++text_it) - { - (*text_it)->renderText(); - } - } - - LLVertexBuffer::unbind(); - - LLGLState::checkStates(); -} - -void LLHUDText::shiftAll(const LLVector3& offset) -{ - TextObjectIterator text_it; - for (text_it = sTextObjects.begin(); text_it != sTextObjects.end(); ++text_it) - { - LLHUDText *textp = text_it->get(); - textp->shift(offset); - } -} - -void LLHUDText::shift(const LLVector3& offset) -{ - mPositionAgent += offset; -} - -//static -// called when UI scale changes, to flush font width caches -void LLHUDText::reshape() -{ - TextObjectIterator text_it; - for (text_it = sTextObjects.begin(); text_it != sTextObjects.end(); ++text_it) - { - LLHUDText* textp = (*text_it); - std::vector::iterator segment_iter; - for (segment_iter = textp->mTextSegments.begin(); - segment_iter != textp->mTextSegments.end(); ++segment_iter ) - { - segment_iter->clearFontWidthMap(); - } - } -} - -//============================================================================ - -F32 LLHUDText::LLHUDTextSegment::getWidth(const LLFontGL* font) -{ - std::map::iterator iter = mFontWidthMap.find(font); - if (iter != mFontWidthMap.end()) - { - return iter->second; - } - else - { - F32 width = font->getWidthF32(mText.c_str()); - mFontWidthMap[font] = width; - return width; - } -} +/** + * @file llhudtext.cpp + * @brief Floating text above objects, set via script with llSetText() + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llhudtext.h" + +#include "llrender.h" + +#include "llagent.h" +#include "llviewercontrol.h" +#include "llcriticaldamp.h" +#include "lldrawable.h" +#include "llfontgl.h" +#include "llglheaders.h" +#include "llhudrender.h" +#include "llui.h" +#include "llviewercamera.h" +#include "llviewertexturelist.h" +#include "llviewerobject.h" +#include "llvovolume.h" +#include "llviewerwindow.h" +#include "llstatusbar.h" +#include "llmenugl.h" +#include "pipeline.h" +#include + +const F32 HORIZONTAL_PADDING = 15.f; +const F32 VERTICAL_PADDING = 12.f; +const F32 BUFFER_SIZE = 2.f; +const F32 HUD_TEXT_MAX_WIDTH = 190.f; +const F32 HUD_TEXT_MAX_WIDTH_NO_BUBBLE = 1000.f; +const F32 MAX_DRAW_DISTANCE = 300.f; + +std::set > LLHUDText::sTextObjects; +std::vector > LLHUDText::sVisibleTextObjects; +std::vector > LLHUDText::sVisibleHUDTextObjects; +bool LLHUDText::sDisplayText = true ; + +bool lltextobject_further_away::operator()(const LLPointer& lhs, const LLPointer& rhs) const +{ + return lhs->getDistance() > rhs->getDistance(); +} + + +LLHUDText::LLHUDText(const U8 type) : + LLHUDObject(type), + mOnHUDAttachment(false), +// mVisibleOffScreen(false), + mWidth(0.f), + mHeight(0.f), + mFontp(LLFontGL::getFontSansSerifSmall()), + mBoldFontp(LLFontGL::getFontSansSerifBold()), + mMass(1.f), + mMaxLines(10), + mOffsetY(0), + mTextAlignment(ALIGN_TEXT_CENTER), + mVertAlignment(ALIGN_VERT_CENTER), +// mLOD(0), + mHidden(false) +{ + mColor = LLColor4(1.f, 1.f, 1.f, 1.f); + mDoFade = true; + mFadeDistance = 8.f; + mFadeRange = 4.f; + mZCompare = true; + mOffscreen = false; + mRadius = 0.1f; + LLPointer ptr(this); + sTextObjects.insert(ptr); +} + +LLHUDText::~LLHUDText() +{ +} + +void LLHUDText::render() +{ + if (!mOnHUDAttachment && sDisplayText) + { + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); + //LLGLDisable gls_stencil(GL_STENCIL_TEST); + renderText(); + } +} + +void LLHUDText::renderText() +{ + if (!mVisible || mHidden) + { + return; + } + + gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE); + + LLGLState gls_blend(GL_BLEND, true); + + LLColor4 shadow_color(0.f, 0.f, 0.f, 1.f); + F32 alpha_factor = 1.f; + LLColor4 text_color = mColor; + if (mDoFade) + { + if (mLastDistance > mFadeDistance) + { + alpha_factor = llmax(0.f, 1.f - (mLastDistance - mFadeDistance)/mFadeRange); + text_color.mV[3] = text_color.mV[3]*alpha_factor; + } + } + if (text_color.mV[3] < 0.01f) + { + return; + } + shadow_color.mV[3] = text_color.mV[3]; + + mOffsetY = lltrunc(mHeight * ((mVertAlignment == ALIGN_VERT_CENTER) ? 0.5f : 1.f)); + + // *TODO: make this a per-text setting + LLColor4 bg_color = LLUIColorTable::instance().getColor("ObjectBubbleColor"); + bg_color.setAlpha(gSavedSettings.getF32("ChatBubbleOpacity") * alpha_factor); + + const S32 border_height = 16; + const S32 border_width = 16; + + // *TODO move this into helper function + F32 border_scale = 1.f; + + if (border_height * 2 > mHeight) + { + border_scale = (F32)mHeight / ((F32)border_height * 2.f); + } + if (border_width * 2 > mWidth) + { + border_scale = llmin(border_scale, (F32)mWidth / ((F32)border_width * 2.f)); + } + + // scale screen size of borders down + //RN: for now, text on hud objects is never occluded + + LLVector3 x_pixel_vec; + LLVector3 y_pixel_vec; + + if (mOnHUDAttachment) + { + x_pixel_vec = LLVector3::y_axis / (F32)gViewerWindow->getWorldViewWidthRaw(); + y_pixel_vec = LLVector3::z_axis / (F32)gViewerWindow->getWorldViewHeightRaw(); + } + else + { + LLViewerCamera::getInstance()->getPixelVectors(mPositionAgent, y_pixel_vec, x_pixel_vec); + } + + LLVector3 width_vec = mWidth * x_pixel_vec; + LLVector3 height_vec = mHeight * y_pixel_vec; + + mRadius = (width_vec + height_vec).magVec() * 0.5f; + + LLVector2 screen_offset; + screen_offset = mPositionOffset; + + LLVector3 render_position = mPositionAgent + + (x_pixel_vec * screen_offset.mV[VX]) + + (y_pixel_vec * screen_offset.mV[VY]); + + F32 y_offset = (F32)mOffsetY; + + // Render label + + // Render text + { + // -1 mMaxLines means unlimited lines. + S32 start_segment; + S32 max_lines = getMaxLines(); + + if (max_lines < 0) + { + start_segment = 0; + } + else + { + start_segment = llmax((S32)0, (S32)mTextSegments.size() - max_lines); + } + + for (std::vector::iterator segment_iter = mTextSegments.begin() + start_segment; + segment_iter != mTextSegments.end(); ++segment_iter ) + { + const LLFontGL* fontp = segment_iter->mFont; + y_offset -= fontp->getLineHeight() - 1; // correction factor to match legacy font metrics + + U8 style = segment_iter->mStyle; + LLFontGL::ShadowType shadow = LLFontGL::DROP_SHADOW; + + F32 x_offset; + if (mTextAlignment== ALIGN_TEXT_CENTER) + { + x_offset = -0.5f*segment_iter->getWidth(fontp); + } + else // ALIGN_LEFT + { + x_offset = -0.5f * mWidth + (HORIZONTAL_PADDING / 2.f); + } + + text_color = segment_iter->mColor; + if (mOnHUDAttachment) + { + text_color = linearColor4(text_color); + } + text_color.mV[VALPHA] *= alpha_factor; + + hud_render_text(segment_iter->getText(), render_position, *fontp, style, shadow, x_offset, y_offset, text_color, mOnHUDAttachment); + } + } + /// Reset the default color to white. The renderer expects this to be the default. + gGL.color4f(1.0f, 1.0f, 1.0f, 1.0f); +} + +void LLHUDText::setString(const std::string &text_utf8) +{ + mTextSegments.clear(); + addLine(text_utf8, mColor); +} + +void LLHUDText::clearString() +{ + mTextSegments.clear(); +} + + +void LLHUDText::addLine(const std::string &text_utf8, + const LLColor4& color, + const LLFontGL::StyleFlags style, + const LLFontGL* font) +{ + LLWString wline = utf8str_to_wstring(text_utf8); + if (!wline.empty()) + { + // use default font for segment if custom font not specified + if (!font) + { + font = mFontp; + } + typedef boost::tokenizer, LLWString::const_iterator, LLWString > tokenizer; + LLWString seps(utf8str_to_wstring("\r\n")); + boost::char_separator sep(seps.c_str()); + + tokenizer tokens(wline, sep); + tokenizer::iterator iter = tokens.begin(); + + while (iter != tokens.end()) + { + U32 line_length = 0; + do + { + F32 max_pixels = HUD_TEXT_MAX_WIDTH_NO_BUBBLE; + S32 segment_length = font->maxDrawableChars(iter->substr(line_length).c_str(), max_pixels, wline.length(), LLFontGL::WORD_BOUNDARY_IF_POSSIBLE); + LLHUDTextSegment segment(iter->substr(line_length, segment_length), style, color, font); + mTextSegments.push_back(segment); + line_length += segment_length; + } + while (line_length != iter->size()); + ++iter; + } + } +} + +void LLHUDText::setZCompare(const bool zcompare) +{ + mZCompare = zcompare; +} + +void LLHUDText::setFont(const LLFontGL* font) +{ + mFontp = font; +} + + +void LLHUDText::setColor(const LLColor4 &color) +{ + mColor = color; + for (std::vector::iterator segment_iter = mTextSegments.begin(); + segment_iter != mTextSegments.end(); ++segment_iter ) + { + segment_iter->mColor = color; + } +} + +void LLHUDText::setAlpha(F32 alpha) +{ + mColor.mV[VALPHA] = alpha; + for (std::vector::iterator segment_iter = mTextSegments.begin(); + segment_iter != mTextSegments.end(); ++segment_iter ) + { + segment_iter->mColor.mV[VALPHA] = alpha; + } +} + + +void LLHUDText::setDoFade(const bool do_fade) +{ + mDoFade = do_fade; +} + +void LLHUDText::updateVisibility() +{ + if (mSourceObject) + { + mSourceObject->updateText(); + } + + mPositionAgent = gAgent.getPosAgentFromGlobal(mPositionGlobal); + + if (!mSourceObject) + { + // Beacons + mVisible = true; + if (mOnHUDAttachment) + { + sVisibleHUDTextObjects.push_back(LLPointer (this)); + } + else + { + sVisibleTextObjects.push_back(LLPointer (this)); + } + return; + } + + // Not visible if parent object is dead + if (mSourceObject->isDead()) + { + mVisible = false; + return; + } + + // for now, all text on hud objects is visible + if (mOnHUDAttachment) + { + mVisible = true; + sVisibleHUDTextObjects.push_back(LLPointer (this)); + mLastDistance = mPositionAgent.mV[VX]; + return; + } + + // push text towards camera by radius of object, but not past camera + LLVector3 vec_from_camera = mPositionAgent - LLViewerCamera::getInstance()->getOrigin(); + LLVector3 dir_from_camera = vec_from_camera; + dir_from_camera.normVec(); + + if (dir_from_camera * LLViewerCamera::getInstance()->getAtAxis() <= 0.f) + { //text is behind camera, don't render + mVisible = false; + return; + } + + if (vec_from_camera * LLViewerCamera::getInstance()->getAtAxis() <= LLViewerCamera::getInstance()->getNear() + 0.1f + mSourceObject->getVObjRadius()) + { + mPositionAgent = LLViewerCamera::getInstance()->getOrigin() + vec_from_camera * ((LLViewerCamera::getInstance()->getNear() + 0.1f) / (vec_from_camera * LLViewerCamera::getInstance()->getAtAxis())); + } + else + { + mPositionAgent -= dir_from_camera * mSourceObject->getVObjRadius(); + } + + mLastDistance = (mPositionAgent - LLViewerCamera::getInstance()->getOrigin()).magVec(); + + if (!mTextSegments.size() || (mDoFade && (mLastDistance > mFadeDistance + mFadeRange))) + { + mVisible = false; + return; + } + + LLVector3 pos_agent_center = gAgent.getPosAgentFromGlobal(mPositionGlobal) - dir_from_camera; + F32 last_distance_center = (pos_agent_center - LLViewerCamera::getInstance()->getOrigin()).magVec(); + F32 max_draw_distance = gSavedSettings.getF32("PrimTextMaxDrawDistance"); + + if(max_draw_distance < 0) + { + max_draw_distance = 0; + gSavedSettings.setF32("PrimTextMaxDrawDistance", max_draw_distance); + } + else if(max_draw_distance > MAX_DRAW_DISTANCE) + { + max_draw_distance = MAX_DRAW_DISTANCE; + gSavedSettings.setF32("PrimTextMaxDrawDistance", MAX_DRAW_DISTANCE); + } + + if(last_distance_center > max_draw_distance) + { + mVisible = false; + return; + } + + + LLVector3 x_pixel_vec; + LLVector3 y_pixel_vec; + + LLViewerCamera::getInstance()->getPixelVectors(mPositionAgent, y_pixel_vec, x_pixel_vec); + + LLVector3 render_position = mPositionAgent + + (x_pixel_vec * mPositionOffset.mV[VX]) + + (y_pixel_vec * mPositionOffset.mV[VY]); + + mOffscreen = false; + if (!LLViewerCamera::getInstance()->sphereInFrustum(render_position, mRadius)) + { +// if (!mVisibleOffScreen) +// { + mVisible = false; + return; +// } +// else +// { +// mOffscreen = true; +// } + } + + mVisible = true; + sVisibleTextObjects.push_back(LLPointer (this)); +} + +LLVector2 LLHUDText::updateScreenPos(LLVector2 &offset) +{ + LLCoordGL screen_pos; + LLVector2 screen_pos_vec; + LLVector3 x_pixel_vec; + LLVector3 y_pixel_vec; + LLViewerCamera::getInstance()->getPixelVectors(mPositionAgent, y_pixel_vec, x_pixel_vec); +// LLVector3 world_pos = mPositionAgent + (offset.mV[VX] * x_pixel_vec) + (offset.mV[VY] * y_pixel_vec); +// if (!LLViewerCamera::getInstance()->projectPosAgentToScreen(world_pos, screen_pos, false) && mVisibleOffScreen) +// { +// // bubble off-screen, so find a spot for it along screen edge +// LLViewerCamera::getInstance()->projectPosAgentToScreenEdge(world_pos, screen_pos); +// } + + screen_pos_vec.setVec((F32)screen_pos.mX, (F32)screen_pos.mY); + + LLRect world_rect = gViewerWindow->getWorldViewRectScaled(); + S32 bottom = world_rect.mBottom + STATUS_BAR_HEIGHT; + + LLVector2 screen_center; + screen_center.mV[VX] = llclamp((F32)screen_pos_vec.mV[VX], (F32)world_rect.mLeft + mWidth * 0.5f, (F32)world_rect.mRight - mWidth * 0.5f); + + if(mVertAlignment == ALIGN_VERT_TOP) + { + screen_center.mV[VY] = llclamp((F32)screen_pos_vec.mV[VY], + (F32)bottom, + (F32)world_rect.mTop - mHeight - (F32)MENU_BAR_HEIGHT); + mSoftScreenRect.setLeftTopAndSize(screen_center.mV[VX] - (mWidth + BUFFER_SIZE) * 0.5f, + screen_center.mV[VY] + (mHeight + BUFFER_SIZE), mWidth + BUFFER_SIZE, mHeight + BUFFER_SIZE); + } + else + { + screen_center.mV[VY] = llclamp((F32)screen_pos_vec.mV[VY], + (F32)bottom + mHeight * 0.5f, + (F32)world_rect.mTop - mHeight * 0.5f - (F32)MENU_BAR_HEIGHT); + mSoftScreenRect.setCenterAndSize(screen_center.mV[VX], screen_center.mV[VY], mWidth + BUFFER_SIZE, mHeight + BUFFER_SIZE); + } + + return offset + (screen_center - LLVector2((F32)screen_pos.mX, (F32)screen_pos.mY)); +} + +void LLHUDText::updateSize() +{ + F32 height = 0.f; + F32 width = 0.f; + + S32 max_lines = getMaxLines(); + + S32 start_segment; + if (max_lines < 0) start_segment = 0; + else start_segment = llmax((S32)0, (S32)mTextSegments.size() - max_lines); + + std::vector::iterator iter = mTextSegments.begin() + start_segment; + while (iter != mTextSegments.end()) + { + const LLFontGL* fontp = iter->mFont; + height += fontp->getLineHeight() - 1; // correction factor to match legacy font metrics + width = llmax(width, llmin(iter->getWidth(fontp), HUD_TEXT_MAX_WIDTH)); + ++iter; + } + + if (width == 0.f) + { + return; + } + + width += HORIZONTAL_PADDING; + height += VERTICAL_PADDING; + + // *TODO: Could do some sort of timer-based resize logic here + F32 u = 1.f; + mWidth = llmax(width, lerp(mWidth, (F32)width, u)); + mHeight = llmax(height, lerp(mHeight, (F32)height, u)); +} + +void LLHUDText::updateAll() +{ + // iterate over all text objects, calculate their restoration forces, + // and add them to the visible set if they are on screen and close enough + sVisibleTextObjects.clear(); + sVisibleHUDTextObjects.clear(); + + TextObjectIterator text_it; + for (text_it = sTextObjects.begin(); text_it != sTextObjects.end(); ++text_it) + { + LLHUDText* textp = (*text_it); + textp->mTargetPositionOffset.clearVec(); + textp->updateSize(); + textp->updateVisibility(); + } + + // sort back to front for rendering purposes + std::sort(sVisibleTextObjects.begin(), sVisibleTextObjects.end(), lltextobject_further_away()); + std::sort(sVisibleHUDTextObjects.begin(), sVisibleHUDTextObjects.end(), lltextobject_further_away()); +} + +//void LLHUDText::setLOD(S32 lod) +//{ +// mLOD = lod; +// //RN: uncomment this to visualize LOD levels +// //std::string label = llformat("%d", lod); +// //setLabel(label); +//} + +S32 LLHUDText::getMaxLines() +{ + return mMaxLines; + //switch(mLOD) + //{ + //case 0: + // return mMaxLines; + //case 1: + // return mMaxLines > 0 ? mMaxLines / 2 : 5; + //case 2: + // return mMaxLines > 0 ? mMaxLines / 3 : 2; + //default: + // // label only + // return 0; + //} +} + +void LLHUDText::markDead() +{ + // make sure we have at least one pointer + // till the end of the function + LLPointer ptr(this); + sTextObjects.erase(ptr); + LLHUDObject::markDead(); +} + +void LLHUDText::renderAllHUD() +{ + LLGLState::checkStates(); + + { + LLGLDepthTest depth(GL_FALSE, GL_FALSE); + + VisibleTextObjectIterator text_it; + + for (text_it = sVisibleHUDTextObjects.begin(); text_it != sVisibleHUDTextObjects.end(); ++text_it) + { + (*text_it)->renderText(); + } + } + + LLVertexBuffer::unbind(); + + LLGLState::checkStates(); +} + +void LLHUDText::shiftAll(const LLVector3& offset) +{ + TextObjectIterator text_it; + for (text_it = sTextObjects.begin(); text_it != sTextObjects.end(); ++text_it) + { + LLHUDText *textp = text_it->get(); + textp->shift(offset); + } +} + +void LLHUDText::shift(const LLVector3& offset) +{ + mPositionAgent += offset; +} + +//static +// called when UI scale changes, to flush font width caches +void LLHUDText::reshape() +{ + TextObjectIterator text_it; + for (text_it = sTextObjects.begin(); text_it != sTextObjects.end(); ++text_it) + { + LLHUDText* textp = (*text_it); + std::vector::iterator segment_iter; + for (segment_iter = textp->mTextSegments.begin(); + segment_iter != textp->mTextSegments.end(); ++segment_iter ) + { + segment_iter->clearFontWidthMap(); + } + } +} + +//============================================================================ + +F32 LLHUDText::LLHUDTextSegment::getWidth(const LLFontGL* font) +{ + std::map::iterator iter = mFontWidthMap.find(font); + if (iter != mFontWidthMap.end()) + { + return iter->second; + } + else + { + F32 width = font->getWidthF32(mText.c_str()); + mFontWidthMap[font] = width; + return width; + } +} diff --git a/indra/newview/llhudtext.h b/indra/newview/llhudtext.h index 8556b131b5..a81fdebb17 100644 --- a/indra/newview/llhudtext.h +++ b/indra/newview/llhudtext.h @@ -1,173 +1,173 @@ -/** - * @file llhudtext.h - * @brief LLHUDText class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLHUDTEXT_H -#define LL_LLHUDTEXT_H - -#include "llpointer.h" - -#include "llhudobject.h" -#include "v4color.h" -#include "v4coloru.h" -#include "v2math.h" -#include "llrect.h" -#include "llfontgl.h" -#include -#include - -// Renders a 2D text billboard floating at the location specified. -class LLDrawable; -class LLHUDText; - -struct lltextobject_further_away -{ - bool operator()(const LLPointer& lhs, const LLPointer& rhs) const; -}; - -class LLHUDText : public LLHUDObject -{ -protected: - class LLHUDTextSegment - { - public: - LLHUDTextSegment(const LLWString& text, const LLFontGL::StyleFlags style, const LLColor4& color, const LLFontGL* font) - : mColor(color), - mStyle(style), - mText(text), - mFont(font) - {} - F32 getWidth(const LLFontGL* font); - const LLWString& getText() const { return mText; } - void clearFontWidthMap() { mFontWidthMap.clear(); } - - LLColor4 mColor; - LLFontGL::StyleFlags mStyle; - const LLFontGL* mFont; - private: - LLWString mText; - std::map mFontWidthMap; - }; - -public: - typedef enum e_text_alignment - { - ALIGN_TEXT_LEFT, - ALIGN_TEXT_CENTER - } ETextAlignment; - - typedef enum e_vert_alignment - { - ALIGN_VERT_TOP, - ALIGN_VERT_CENTER - } EVertAlignment; - -public: - // Set entire string, eliminating existing lines - void setString(const std::string& text_utf8); - - void clearString(); - - // Add text a line at a time, allowing custom formatting - void addLine(const std::string &text_utf8, const LLColor4& color, const LLFontGL::StyleFlags style = LLFontGL::NORMAL, const LLFontGL* font = NULL); - - // Sets the default font for lines with no font specified - void setFont(const LLFontGL* font); - void setColor(const LLColor4 &color); - void setAlpha(F32 alpha); - void setZCompare(const bool zcompare); - void setDoFade(const bool do_fade); -// void setVisibleOffScreen(bool visible) { mVisibleOffScreen = visible; } - - // mMaxLines of -1 means unlimited lines. - void setMaxLines(S32 max_lines) { mMaxLines = max_lines; } - void setFadeDistance(F32 fade_distance, F32 fade_range) { mFadeDistance = fade_distance; mFadeRange = fade_range; } - void updateVisibility(); - LLVector2 updateScreenPos(LLVector2 &offset_target); - void updateSize(); - void setMass(F32 mass) { mMass = llmax(0.1f, mass); } - void setTextAlignment(ETextAlignment alignment) { mTextAlignment = alignment; } - void setVertAlignment(EVertAlignment alignment) { mVertAlignment = alignment; } - /*virtual*/ void markDead(); - friend class LLHUDObject; - /*virtual*/ F32 getDistance() const { return mLastDistance; } - bool getVisible() { return mVisible; } - bool getHidden() const { return mHidden; } - void setHidden( bool hide ) { mHidden = hide; } - void setOnHUDAttachment(bool on_hud) { mOnHUDAttachment = on_hud; } - void shift(const LLVector3& offset); - - static void shiftAll(const LLVector3& offset); - static void renderAllHUD(); - static void reshape(); - static void setDisplayText(bool flag) { sDisplayText = flag ; } - -protected: - LLHUDText(const U8 type); - - /*virtual*/ void render(); - void renderText(); - static void updateAll(); - S32 getMaxLines(); - -private: - ~LLHUDText(); - bool mOnHUDAttachment; - bool mDoFade; - F32 mFadeRange; - F32 mFadeDistance; - F32 mLastDistance; - bool mZCompare; -// bool mVisibleOffScreen; - bool mOffscreen; - LLColor4 mColor; - LLVector3 mScale; - F32 mWidth; - F32 mHeight; - LLColor4U mPickColor; - const LLFontGL* mFontp; - const LLFontGL* mBoldFontp; - LLRectf mSoftScreenRect; - LLVector3 mPositionAgent; - LLVector2 mPositionOffset; - LLVector2 mTargetPositionOffset; - F32 mMass; - S32 mMaxLines; - S32 mOffsetY; - F32 mRadius; - std::vector mTextSegments; - ETextAlignment mTextAlignment; - EVertAlignment mVertAlignment; - bool mHidden; - - static bool sDisplayText ; - static std::set > sTextObjects; - static std::vector > sVisibleTextObjects; - static std::vector > sVisibleHUDTextObjects; - typedef std::set >::iterator TextObjectIterator; - typedef std::vector >::iterator VisibleTextObjectIterator; -}; - -#endif // LL_LLHUDTEXT_H +/** + * @file llhudtext.h + * @brief LLHUDText class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLHUDTEXT_H +#define LL_LLHUDTEXT_H + +#include "llpointer.h" + +#include "llhudobject.h" +#include "v4color.h" +#include "v4coloru.h" +#include "v2math.h" +#include "llrect.h" +#include "llfontgl.h" +#include +#include + +// Renders a 2D text billboard floating at the location specified. +class LLDrawable; +class LLHUDText; + +struct lltextobject_further_away +{ + bool operator()(const LLPointer& lhs, const LLPointer& rhs) const; +}; + +class LLHUDText : public LLHUDObject +{ +protected: + class LLHUDTextSegment + { + public: + LLHUDTextSegment(const LLWString& text, const LLFontGL::StyleFlags style, const LLColor4& color, const LLFontGL* font) + : mColor(color), + mStyle(style), + mText(text), + mFont(font) + {} + F32 getWidth(const LLFontGL* font); + const LLWString& getText() const { return mText; } + void clearFontWidthMap() { mFontWidthMap.clear(); } + + LLColor4 mColor; + LLFontGL::StyleFlags mStyle; + const LLFontGL* mFont; + private: + LLWString mText; + std::map mFontWidthMap; + }; + +public: + typedef enum e_text_alignment + { + ALIGN_TEXT_LEFT, + ALIGN_TEXT_CENTER + } ETextAlignment; + + typedef enum e_vert_alignment + { + ALIGN_VERT_TOP, + ALIGN_VERT_CENTER + } EVertAlignment; + +public: + // Set entire string, eliminating existing lines + void setString(const std::string& text_utf8); + + void clearString(); + + // Add text a line at a time, allowing custom formatting + void addLine(const std::string &text_utf8, const LLColor4& color, const LLFontGL::StyleFlags style = LLFontGL::NORMAL, const LLFontGL* font = NULL); + + // Sets the default font for lines with no font specified + void setFont(const LLFontGL* font); + void setColor(const LLColor4 &color); + void setAlpha(F32 alpha); + void setZCompare(const bool zcompare); + void setDoFade(const bool do_fade); +// void setVisibleOffScreen(bool visible) { mVisibleOffScreen = visible; } + + // mMaxLines of -1 means unlimited lines. + void setMaxLines(S32 max_lines) { mMaxLines = max_lines; } + void setFadeDistance(F32 fade_distance, F32 fade_range) { mFadeDistance = fade_distance; mFadeRange = fade_range; } + void updateVisibility(); + LLVector2 updateScreenPos(LLVector2 &offset_target); + void updateSize(); + void setMass(F32 mass) { mMass = llmax(0.1f, mass); } + void setTextAlignment(ETextAlignment alignment) { mTextAlignment = alignment; } + void setVertAlignment(EVertAlignment alignment) { mVertAlignment = alignment; } + /*virtual*/ void markDead(); + friend class LLHUDObject; + /*virtual*/ F32 getDistance() const { return mLastDistance; } + bool getVisible() { return mVisible; } + bool getHidden() const { return mHidden; } + void setHidden( bool hide ) { mHidden = hide; } + void setOnHUDAttachment(bool on_hud) { mOnHUDAttachment = on_hud; } + void shift(const LLVector3& offset); + + static void shiftAll(const LLVector3& offset); + static void renderAllHUD(); + static void reshape(); + static void setDisplayText(bool flag) { sDisplayText = flag ; } + +protected: + LLHUDText(const U8 type); + + /*virtual*/ void render(); + void renderText(); + static void updateAll(); + S32 getMaxLines(); + +private: + ~LLHUDText(); + bool mOnHUDAttachment; + bool mDoFade; + F32 mFadeRange; + F32 mFadeDistance; + F32 mLastDistance; + bool mZCompare; +// bool mVisibleOffScreen; + bool mOffscreen; + LLColor4 mColor; + LLVector3 mScale; + F32 mWidth; + F32 mHeight; + LLColor4U mPickColor; + const LLFontGL* mFontp; + const LLFontGL* mBoldFontp; + LLRectf mSoftScreenRect; + LLVector3 mPositionAgent; + LLVector2 mPositionOffset; + LLVector2 mTargetPositionOffset; + F32 mMass; + S32 mMaxLines; + S32 mOffsetY; + F32 mRadius; + std::vector mTextSegments; + ETextAlignment mTextAlignment; + EVertAlignment mVertAlignment; + bool mHidden; + + static bool sDisplayText ; + static std::set > sTextObjects; + static std::vector > sVisibleTextObjects; + static std::vector > sVisibleHUDTextObjects; + typedef std::set >::iterator TextObjectIterator; + typedef std::vector >::iterator VisibleTextObjectIterator; +}; + +#endif // LL_LLHUDTEXT_H diff --git a/indra/newview/llhudview.cpp b/indra/newview/llhudview.cpp index c0a9670b3d..23fdf4b0fd 100644 --- a/indra/newview/llhudview.cpp +++ b/indra/newview/llhudview.cpp @@ -1,72 +1,72 @@ -/** - * @file llhudview.cpp - * @brief 2D HUD overlay - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llhudview.h" - -// library includes -#include "v4color.h" -#include "llcoord.h" - -// viewer includes -#include "llcallingcard.h" -#include "llviewercontrol.h" -#include "llfloaterworldmap.h" -#include "llworldmapview.h" -#include "lltracker.h" -#include "llviewercamera.h" -#include "llui.h" -#include "lluictrlfactory.h" - -LLHUDView *gHUDView = NULL; - -LLHUDView::LLHUDView(const LLRect& r) -{ - buildFromFile( "panel_hud.xml"); - setShape(r, true); -} - -LLHUDView::~LLHUDView() -{ -} - -// virtual -void LLHUDView::draw() -{ - LLTracker::drawHUDArrow(); - LLView::draw(); -} - -/*virtual*/ -bool LLHUDView::handleMouseDown(S32 x, S32 y, MASK mask) -{ - if (LLTracker::handleMouseDown(x, y)) - { - return true; - } - return LLView::handleMouseDown(x, y, mask); -} +/** + * @file llhudview.cpp + * @brief 2D HUD overlay + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llhudview.h" + +// library includes +#include "v4color.h" +#include "llcoord.h" + +// viewer includes +#include "llcallingcard.h" +#include "llviewercontrol.h" +#include "llfloaterworldmap.h" +#include "llworldmapview.h" +#include "lltracker.h" +#include "llviewercamera.h" +#include "llui.h" +#include "lluictrlfactory.h" + +LLHUDView *gHUDView = NULL; + +LLHUDView::LLHUDView(const LLRect& r) +{ + buildFromFile( "panel_hud.xml"); + setShape(r, true); +} + +LLHUDView::~LLHUDView() +{ +} + +// virtual +void LLHUDView::draw() +{ + LLTracker::drawHUDArrow(); + LLView::draw(); +} + +/*virtual*/ +bool LLHUDView::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (LLTracker::handleMouseDown(x, y)) + { + return true; + } + return LLView::handleMouseDown(x, y, mask); +} diff --git a/indra/newview/llhudview.h b/indra/newview/llhudview.h index ef00bbcd4b..1a98d635d0 100644 --- a/indra/newview/llhudview.h +++ b/indra/newview/llhudview.h @@ -1,50 +1,50 @@ -/** - * @file llhudview.h - * @brief 2D HUD overlay - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLHUDVIEW_H -#define LL_LLHUDVIEW_H - -#include "llpanel.h" -#include "v4color.h" - -class LLVector3d; - -class LLHUDView -: public LLPanel -{ -public: - LLHUDView(const LLRect& rect); - virtual ~LLHUDView(); - - virtual void draw(); - -protected: - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); -}; - -extern LLHUDView *gHUDView; - -#endif +/** + * @file llhudview.h + * @brief 2D HUD overlay + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLHUDVIEW_H +#define LL_LLHUDVIEW_H + +#include "llpanel.h" +#include "v4color.h" + +class LLVector3d; + +class LLHUDView +: public LLPanel +{ +public: + LLHUDView(const LLRect& rect); + virtual ~LLHUDView(); + + virtual void draw(); + +protected: + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); +}; + +extern LLHUDView *gHUDView; + +#endif diff --git a/indra/newview/llimprocessing.cpp b/indra/newview/llimprocessing.cpp index bc9a4a91c5..ca800be580 100644 --- a/indra/newview/llimprocessing.cpp +++ b/indra/newview/llimprocessing.cpp @@ -1,1672 +1,1672 @@ -/** -* @file LLIMProcessing.cpp -* @brief Container for Instant Messaging -* -* $LicenseInfo:firstyear=2001&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2018, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#include "llviewerprecompiledheaders.h" - -#include "llimprocessing.h" - -#include "llagent.h" -#include "llappviewer.h" -#include "llavatarnamecache.h" -#include "llfirstuse.h" -#include "llfloaterreg.h" -#include "llfloaterimnearbychat.h" -#include "llimview.h" -#include "llinventoryobserver.h" -#include "llinventorymodel.h" -#include "llmutelist.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "llnotificationmanager.h" -#include "llpanelgroup.h" -#include "llregex.h" -#include "llregionhandle.h" -#include "llsdserialize.h" -#include "llslurl.h" -#include "llstring.h" -#include "lltoastnotifypanel.h" -#include "lltrans.h" -#include "llviewergenericmessage.h" -#include "llviewerobjectlist.h" -#include "llviewermessage.h" -#include "llviewerwindow.h" -#include "llviewerregion.h" -#include "llvoavatarself.h" -#include "llworld.h" - -#include "boost/lexical_cast.hpp" -#if LL_MSVC -// disable boost::lexical_cast warning -#pragma warning (disable:4702) -#endif - -extern void on_new_message(const LLSD& msg); - -// Strip out "Resident" for display, but only if the message came from a user -// (rather than a script) -static std::string clean_name_from_im(const std::string& name, EInstantMessage type) -{ - switch (type) - { - case IM_NOTHING_SPECIAL: - case IM_MESSAGEBOX: - case IM_GROUP_INVITATION: - case IM_INVENTORY_OFFERED: - case IM_INVENTORY_ACCEPTED: - case IM_INVENTORY_DECLINED: - case IM_GROUP_VOTE: - case IM_GROUP_MESSAGE_DEPRECATED: - //IM_TASK_INVENTORY_OFFERED - //IM_TASK_INVENTORY_ACCEPTED - //IM_TASK_INVENTORY_DECLINED - case IM_NEW_USER_DEFAULT: - case IM_SESSION_INVITE: - case IM_SESSION_P2P_INVITE: - case IM_SESSION_GROUP_START: - case IM_SESSION_CONFERENCE_START: - case IM_SESSION_SEND: - case IM_SESSION_LEAVE: - //IM_FROM_TASK - case IM_DO_NOT_DISTURB_AUTO_RESPONSE: - case IM_CONSOLE_AND_CHAT_HISTORY: - case IM_LURE_USER: - case IM_LURE_ACCEPTED: - case IM_LURE_DECLINED: - case IM_GODLIKE_LURE_USER: - case IM_TELEPORT_REQUEST: - case IM_GROUP_ELECTION_DEPRECATED: - //IM_GOTO_URL - //IM_FROM_TASK_AS_ALERT - case IM_GROUP_NOTICE: - case IM_GROUP_NOTICE_INVENTORY_ACCEPTED: - case IM_GROUP_NOTICE_INVENTORY_DECLINED: - case IM_GROUP_INVITATION_ACCEPT: - case IM_GROUP_INVITATION_DECLINE: - case IM_GROUP_NOTICE_REQUESTED: - case IM_FRIENDSHIP_OFFERED: - case IM_FRIENDSHIP_ACCEPTED: - case IM_FRIENDSHIP_DECLINED_DEPRECATED: - //IM_TYPING_START - //IM_TYPING_STOP - return LLCacheName::cleanFullName(name); - default: - return name; - } -} - -static std::string clean_name_from_task_im(const std::string& msg, - bool from_group) -{ - boost::smatch match; - static const boost::regex returned_exp( - "(.*been returned to your inventory lost and found folder by )(.+)( (from|near).*)"); - if (ll_regex_match(msg, match, returned_exp)) - { - // match objects are 1-based for groups - std::string final = match[1].str(); - std::string name = match[2].str(); - // Don't try to clean up group names - if (!from_group) - { - final += LLCacheName::buildUsername(name); - } - final += match[3].str(); - return final; - } - return msg; -} - -const std::string NOT_ONLINE_MSG("User not online - message will be stored and delivered later."); -const std::string NOT_ONLINE_INVENTORY("User not online - inventory has been saved."); -void translate_if_needed(std::string& message) -{ - if (message == NOT_ONLINE_MSG) - { - message = LLTrans::getString("not_online_msg"); - } - else if (message == NOT_ONLINE_INVENTORY) - { - message = LLTrans::getString("not_online_inventory"); - } -} - -class LLPostponedIMSystemTipNotification : public LLPostponedNotification -{ -protected: - /* virtual */ - void modifyNotificationParams() - { - LLSD payload = mParams.payload; - payload["SESSION_NAME"] = mName; - mParams.payload = payload; - } -}; - -class LLPostponedOfferNotification : public LLPostponedNotification -{ -protected: - /* virtual */ - void modifyNotificationParams() - { - LLSD substitutions = mParams.substitutions; - substitutions["NAME"] = mName; - mParams.substitutions = substitutions; - } -}; - -void inventory_offer_handler(LLOfferInfo* info) -{ - // If muted, don't even go through the messaging stuff. Just curtail the offer here. - // Passing in a null UUID handles the case of where you have muted one of your own objects by_name. - // The solution for STORM-1297 seems to handle the cases where the object is owned by someone else. - if (LLMuteList::getInstance()->isMuted(info->mFromID, info->mFromName) || - LLMuteList::getInstance()->isMuted(LLUUID::null, info->mFromName)) - { - info->forceResponse(IOR_MUTE); - return; - } - - bool bAutoAccept(false); - // Avoid the Accept/Discard dialog if the user so desires. JC - if (gSavedSettings.getBOOL("AutoAcceptNewInventory") - && (info->mType == LLAssetType::AT_NOTECARD - || info->mType == LLAssetType::AT_LANDMARK - || info->mType == LLAssetType::AT_TEXTURE)) - { - // For certain types, just accept the items into the inventory, - // and possibly open them on receipt depending upon "ShowNewInventory". - bAutoAccept = true; - } - - // Strip any SLURL from the message display. (DEV-2754) - std::string msg = info->mDesc; - int indx = msg.find(" ( http://slurl.com/secondlife/"); - if (indx == std::string::npos) - { - // try to find new slurl host - indx = msg.find(" ( http://maps.secondlife.com/secondlife/"); - } - if (indx >= 0) - { - LLStringUtil::truncate(msg, indx); - } - - LLSD args; - args["[OBJECTNAME]"] = msg; - - LLSD payload; - - // must protect against a NULL return from lookupHumanReadable() - std::string typestr = ll_safe_string(LLAssetType::lookupHumanReadable(info->mType)); - if (!typestr.empty()) - { - // human readable matches string name from strings.xml - // lets get asset type localized name - args["OBJECTTYPE"] = LLTrans::getString(typestr); - } - else - { - LL_WARNS("Messaging") << "LLAssetType::lookupHumanReadable() returned NULL - probably bad asset type: " << info->mType << LL_ENDL; - args["OBJECTTYPE"] = ""; - - // This seems safest, rather than propagating bogosity - LL_WARNS("Messaging") << "Forcing an inventory-decline for probably-bad asset type." << LL_ENDL; - info->forceResponse(IOR_DECLINE); - return; - } - - // If mObjectID is null then generate the object_id based on msg to prevent - // multiple creation of chiclets for same object. - LLUUID object_id = info->mObjectID; - if (object_id.isNull()) - object_id.generate(msg); - - payload["from_id"] = info->mFromID; - // Needed by LLScriptFloaterManager to bind original notification with - // faked for toast one. - payload["object_id"] = object_id; - // Flag indicating that this notification is faked for toast. - payload["give_inventory_notification"] = false; - args["OBJECTFROMNAME"] = info->mFromName; - args["NAME"] = info->mFromName; - if (info->mFromGroup) - { - args["NAME_SLURL"] = LLSLURL("group", info->mFromID, "about").getSLURLString(); - } - else - { - args["NAME_SLURL"] = LLSLURL("agent", info->mFromID, "about").getSLURLString(); - } - std::string verb = "select?name=" + LLURI::escape(msg); - args["ITEM_SLURL"] = LLSLURL("inventory", info->mObjectID, verb.c_str()).getSLURLString(); - - LLNotification::Params p; - - // Object -> Agent Inventory Offer - if (info->mFromObject && !bAutoAccept) - { - // Inventory Slurls don't currently work for non agent transfers, so only display the object name. - args["ITEM_SLURL"] = msg; - // Note: sets inventory_task_offer_callback as the callback - p.substitutions(args).payload(payload).functor.responder(LLNotificationResponderPtr(info)); - info->mPersist = true; - - // Offers from your own objects need a special notification template. - p.name = info->mFromID == gAgentID ? "OwnObjectGiveItem" : "ObjectGiveItem"; - - // Pop up inv offer chiclet and let the user accept (keep), or reject (and silently delete) the inventory. - LLPostponedNotification::add(p, info->mFromID, info->mFromGroup); - } - else // Agent -> Agent Inventory Offer - { - p.responder = info; - // Note: sets inventory_offer_callback as the callback - // *TODO fix memory leak - // inventory_offer_callback() is not invoked if user received notification and - // closes viewer(without responding the notification) - p.substitutions(args).payload(payload).functor.responder(LLNotificationResponderPtr(info)); - info->mPersist = true; - p.name = "UserGiveItem"; - p.offer_from_agent = true; - - // Prefetch the item into your local inventory. - LLInventoryFetchItemsObserver* fetch_item = new LLInventoryFetchItemsObserver(info->mObjectID); - fetch_item->startFetch(); - if (fetch_item->isFinished()) - { - fetch_item->done(); - } - else - { - gInventory.addObserver(fetch_item); - } - - // In viewer 2 we're now auto receiving inventory offers and messaging as such (not sending reject messages). - info->send_auto_receive_response(); - - if (gAgent.isDoNotDisturb()) - { - send_do_not_disturb_message(gMessageSystem, info->mFromID); - } - - if (!bAutoAccept) // if we auto accept, do not pester the user - { - // Inform user that there is a script floater via toast system - payload["give_inventory_notification"] = true; - p.payload = payload; - LLPostponedNotification::add(p, info->mFromID, false); - } - } - - LLFirstUse::newInventory(); -} - -// Callback for name resolution of a god/estate message -static void god_message_name_cb(const LLAvatarName& av_name, LLChat chat, std::string message) -{ - LLSD args; - args["NAME"] = av_name.getCompleteName(); - args["MESSAGE"] = message; - LLNotificationsUtil::add("GodMessage", args); - - // Treat like a system message and put in chat history. - chat.mSourceType = CHAT_SOURCE_SYSTEM; - chat.mText = message; - - LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); - if (nearby_chat) - { - nearby_chat->addMessage(chat); - } -} - -static bool parse_lure_bucket(const std::string& bucket, - U64& region_handle, - LLVector3& pos, - LLVector3& look_at, - U8& region_access) -{ - // tokenize the bucket - typedef boost::tokenizer > tokenizer; - boost::char_separator sep("|", "", boost::keep_empty_tokens); - tokenizer tokens(bucket, sep); - tokenizer::iterator iter = tokens.begin(); - - S32 gx, gy, rx, ry, rz, lx, ly, lz; - try - { - gx = boost::lexical_cast((*(iter)).c_str()); - gy = boost::lexical_cast((*(++iter)).c_str()); - rx = boost::lexical_cast((*(++iter)).c_str()); - ry = boost::lexical_cast((*(++iter)).c_str()); - rz = boost::lexical_cast((*(++iter)).c_str()); - lx = boost::lexical_cast((*(++iter)).c_str()); - ly = boost::lexical_cast((*(++iter)).c_str()); - lz = boost::lexical_cast((*(++iter)).c_str()); - } - catch (boost::bad_lexical_cast&) - { - LL_WARNS("parse_lure_bucket") - << "Couldn't parse lure bucket." - << LL_ENDL; - return false; - } - // Grab region access - region_access = SIM_ACCESS_MIN; - if (++iter != tokens.end()) - { - std::string access_str((*iter).c_str()); - LLStringUtil::trim(access_str); - if (access_str == "A") - { - region_access = SIM_ACCESS_ADULT; - } - else if (access_str == "M") - { - region_access = SIM_ACCESS_MATURE; - } - else if (access_str == "PG") - { - region_access = SIM_ACCESS_PG; - } - } - - pos.setVec((F32)rx, (F32)ry, (F32)rz); - look_at.setVec((F32)lx, (F32)ly, (F32)lz); - - region_handle = to_region_handle(gx, gy); - return true; -} - -static void notification_display_name_callback(const LLUUID& id, - const LLAvatarName& av_name, - const std::string& name, - LLSD& substitutions, - const LLSD& payload) -{ - substitutions["NAME"] = av_name.getDisplayName(); - LLNotificationsUtil::add(name, substitutions, payload); -} - -void LLIMProcessing::processNewMessage(LLUUID from_id, - bool from_group, - LLUUID to_id, - U8 offline, - EInstantMessage dialog, // U8 - LLUUID session_id, - U32 timestamp, - std::string agentName, - std::string message, - U32 parent_estate_id, - LLUUID region_id, - LLVector3 position, - U8 *binary_bucket, - S32 binary_bucket_size, - LLHost &sender, - LLUUID aux_id) -{ - LLChat chat; - std::string buffer; - std::string name = agentName; - - // make sure that we don't have an empty or all-whitespace name - LLStringUtil::trim(name); - if (name.empty()) - { - name = LLTrans::getString("Unnamed"); - } - - // Preserve the unaltered name for use in group notice mute checking. - std::string original_name = name; - - // IDEVO convert new-style "Resident" names for display - name = clean_name_from_im(name, dialog); - - bool is_do_not_disturb = gAgent.isDoNotDisturb(); - bool is_muted = LLMuteList::getInstance()->isMuted(from_id, name, LLMute::flagTextChat) - // object IMs contain sender object id in session_id (STORM-1209) - || (dialog == IM_FROM_TASK && LLMuteList::getInstance()->isMuted(session_id)); - bool is_owned_by_me = false; - bool is_friend = LLAvatarTracker::instance().getBuddyInfo(from_id) != NULL; - bool accept_im_from_only_friend = gSavedPerAccountSettings.getBOOL("VoiceCallsFriendsOnly"); - bool is_linden = chat.mSourceType != CHAT_SOURCE_OBJECT && - LLMuteList::isLinden(name); - - chat.mMuted = is_muted; - chat.mFromID = from_id; - chat.mFromName = name; - chat.mSourceType = (from_id.isNull() || (name == std::string(SYSTEM_FROM))) ? CHAT_SOURCE_SYSTEM : CHAT_SOURCE_AGENT; - - if (chat.mSourceType == CHAT_SOURCE_SYSTEM) - { // Translate server message if required (MAINT-6109) - translate_if_needed(message); - } - - LLViewerObject *source = gObjectList.findObject(session_id); //Session ID is probably the wrong thing. - if (source) - { - is_owned_by_me = source->permYouOwner(); - } - - std::string separator_string(": "); - - LLSD args; - LLSD payload; - LLNotification::Params params; - - switch (dialog) - { - case IM_CONSOLE_AND_CHAT_HISTORY: - args["MESSAGE"] = message; - payload["from_id"] = from_id; - - params.name = "IMSystemMessageTip"; - params.substitutions = args; - params.payload = payload; - LLPostponedNotification::add(params, from_id, false); - break; - - case IM_NOTHING_SPECIAL: // p2p IM - // Don't show dialog, just do IM - if (!gAgent.isGodlike() - && gAgent.getRegion()->isPrelude() - && to_id.isNull()) - { - // do nothing -- don't distract newbies in - // Prelude with global IMs - } - else if (offline == IM_ONLINE - && is_do_not_disturb - && from_id.notNull() //not a system message - && to_id.notNull()) //not global message - { - - // now store incoming IM in chat history - - buffer = message; - - LL_DEBUGS("Messaging") << "session_id( " << session_id << " ), from_id( " << from_id << " )" << LL_ENDL; - - // add to IM panel, but do not bother the user - gIMMgr->addMessage( - session_id, - from_id, - name, - buffer, - IM_OFFLINE == offline, - LLStringUtil::null, - dialog, - parent_estate_id, - region_id, - position, - false, // is_region_msg - timestamp); - - if (!gIMMgr->isDNDMessageSend(session_id)) - { - // return a standard "do not disturb" message, but only do it to online IM - // (i.e. not other auto responses and not store-and-forward IM) - send_do_not_disturb_message(gMessageSystem, from_id, session_id); - gIMMgr->setDNDMessageSent(session_id, true); - } - - } - else if (from_id.isNull()) - { - LLSD args; - args["MESSAGE"] = message; - LLNotificationsUtil::add("SystemMessage", args); - } - else if (to_id.isNull()) - { - // Message to everyone from GOD, look up the fullname since - // server always slams name to legacy names - LLAvatarNameCache::get(from_id, boost::bind(god_message_name_cb, _2, chat, message)); - } - else - { - // standard message, not from system - std::string saved; - if (offline == IM_OFFLINE) - { - LLStringUtil::format_map_t args; - args["[LONG_TIMESTAMP]"] = formatted_time(timestamp); - saved = LLTrans::getString("Saved_message", args); - } - buffer = saved + message; - - LL_DEBUGS("Messaging") << "session_id( " << session_id << " ), from_id( " << from_id << " )" << LL_ENDL; - - bool mute_im = is_muted; - if (accept_im_from_only_friend && !is_friend && !is_linden) - { - if (!gIMMgr->isNonFriendSessionNotified(session_id)) - { - std::string message = LLTrans::getString("IM_unblock_only_groups_friends"); - gIMMgr->addMessage(session_id, from_id, name, message, IM_OFFLINE == offline); - gIMMgr->addNotifiedNonFriendSessionID(session_id); - } - - mute_im = true; - } - if (!mute_im) - { - bool region_message = false; - if (region_id.isNull()) - { - LLViewerRegion* regionp = LLWorld::instance().getRegionFromID(from_id); - if (regionp) - { - region_message = true; - } - } - gIMMgr->addMessage( - session_id, - from_id, - name, - buffer, - IM_OFFLINE == offline, - LLStringUtil::null, - dialog, - parent_estate_id, - region_id, - position, - region_message, - timestamp); - } - else - { - /* - EXT-5099 - */ - } - } - break; - - case IM_TYPING_START: - { - gIMMgr->processIMTypingStart(from_id, dialog); - } - break; - - case IM_TYPING_STOP: - { - gIMMgr->processIMTypingStop(from_id, dialog); - } - break; - - case IM_MESSAGEBOX: - { - // This is a block, modeless dialog. - args["MESSAGE"] = message; - LLNotificationsUtil::add("SystemMessageTip", args); - } - break; - case IM_GROUP_NOTICE: - case IM_GROUP_NOTICE_REQUESTED: - { - LL_INFOS("Messaging") << "Received IM_GROUP_NOTICE message." << LL_ENDL; - - LLUUID agent_id; - U8 has_inventory; - U8 asset_type = 0; - LLUUID group_id; - std::string item_name; - - if (aux_id.notNull()) - { - // aux_id contains group id, binary bucket contains name and asset type - group_id = aux_id; - has_inventory = binary_bucket_size > 1; - from_group = true; // inaccurate value correction - if (has_inventory) - { - std::string str_bucket = ll_safe_string((char*)binary_bucket, binary_bucket_size); - - typedef boost::tokenizer > tokenizer; - boost::char_separator sep("|", "", boost::keep_empty_tokens); - tokenizer tokens(str_bucket, sep); - tokenizer::iterator iter = tokens.begin(); - - asset_type = (LLAssetType::EType)(atoi((*(iter++)).c_str())); - iter++; // wearable type if applicable, otherwise asset type - item_name = std::string((*(iter++)).c_str()); - // Note There is more elements in 'tokens' ... - - - for (int i = 0; i < 6; i++) - { - LL_WARNS() << *(iter++) << LL_ENDL; - iter++; - } - } - } - else - { - // All info is in binary bucket, read it for more information. - struct notice_bucket_header_t - { - U8 has_inventory; - U8 asset_type; - LLUUID group_id; - }; - struct notice_bucket_full_t - { - struct notice_bucket_header_t header; - U8 item_name[DB_INV_ITEM_NAME_BUF_SIZE]; - }*notice_bin_bucket; - - // Make sure the binary bucket is big enough to hold the header - // and a null terminated item name. - if ((binary_bucket_size < (S32)((sizeof(notice_bucket_header_t) + sizeof(U8)))) - || (binary_bucket[binary_bucket_size - 1] != '\0')) - { - LL_WARNS("Messaging") << "Malformed group notice binary bucket" << LL_ENDL; - break; - } - - notice_bin_bucket = (struct notice_bucket_full_t*) &binary_bucket[0]; - has_inventory = notice_bin_bucket->header.has_inventory; - asset_type = notice_bin_bucket->header.asset_type; - group_id = notice_bin_bucket->header.group_id; - item_name = ll_safe_string((const char*)notice_bin_bucket->item_name); - } - - if (group_id != from_id) - { - agent_id = from_id; - } - else - { - S32 index = original_name.find(" Resident"); - if (index != std::string::npos) - { - original_name = original_name.substr(0, index); - } - - // The group notice packet does not have an AgentID. Obtain one from the name cache. - // If last name is "Resident" strip it out so the cache name lookup works. - std::string legacy_name = gCacheName->buildLegacyName(original_name); - agent_id = LLAvatarNameCache::getInstance()->findIdByName(legacy_name); - - if (agent_id.isNull()) - { - LL_WARNS("Messaging") << "buildLegacyName returned null while processing " << original_name << LL_ENDL; - } - } - - if (agent_id.notNull() && LLMuteList::getInstance()->isMuted(agent_id)) - { - break; - } - - // If there is inventory, give the user the inventory offer. - LLOfferInfo* info = NULL; - - if (has_inventory) - { - info = new LLOfferInfo(); - - info->mIM = dialog; - info->mFromID = from_id; - info->mFromGroup = from_group; - info->mTransactionID = session_id; - info->mType = (LLAssetType::EType) asset_type; - info->mFolderID = gInventory.findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(info->mType)); - std::string from_name; - - from_name += "A group member named "; - from_name += name; - - info->mFromName = from_name; - info->mDesc = item_name; - info->mHost = sender; - } - - std::string str(message); - - // Tokenize the string. - // TODO: Support escaped tokens ("||" -> "|") - typedef boost::tokenizer > tokenizer; - boost::char_separator sep("|", "", boost::keep_empty_tokens); - tokenizer tokens(str, sep); - tokenizer::iterator iter = tokens.begin(); - - std::string subj(*iter++); - std::string mes(*iter++); - - // Send the notification down the new path. - // For requested notices, we don't want to send the popups. - if (dialog != IM_GROUP_NOTICE_REQUESTED) - { - payload["subject"] = subj; - payload["message"] = mes; - payload["sender_name"] = name; - payload["sender_id"] = agent_id; - payload["group_id"] = group_id; - payload["inventory_name"] = item_name; - payload["received_time"] = LLDate::now(); - if (info && info->asLLSD()) - { - payload["inventory_offer"] = info->asLLSD(); - } - - LLSD args; - args["SUBJECT"] = subj; - args["MESSAGE"] = mes; - LLDate notice_date = LLDate(timestamp).notNull() ? LLDate(timestamp) : LLDate::now(); - LLNotifications::instance().add(LLNotification::Params("GroupNotice").substitutions(args).payload(payload).time_stamp(notice_date)); - } - - // Also send down the old path for now. - if (IM_GROUP_NOTICE_REQUESTED == dialog) - { - - LLPanelGroup::showNotice(subj, mes, group_id, has_inventory, item_name, info); - } - else - { - delete info; - } - } - break; - case IM_GROUP_INVITATION: - { - if (!is_muted) - { - // group is not blocked, but we still need to check agent that sent the invitation - // and we have no agent's id - // Note: server sends username "first.last". - is_muted |= LLMuteList::getInstance()->isMuted(name); - } - if (is_do_not_disturb || is_muted) - { - send_do_not_disturb_message(gMessageSystem, from_id); - } - - if (!is_muted) - { - LL_INFOS("Messaging") << "Received IM_GROUP_INVITATION message." << LL_ENDL; - // Read the binary bucket for more information. - struct invite_bucket_t - { - S32 membership_fee; - LLUUID role_id; - }*invite_bucket; - - // Make sure the binary bucket is the correct size. - if (binary_bucket_size != sizeof(invite_bucket_t)) - { - LL_WARNS("Messaging") << "Malformed group invite binary bucket" << LL_ENDL; - break; - } - - invite_bucket = (struct invite_bucket_t*) &binary_bucket[0]; - S32 membership_fee = ntohl(invite_bucket->membership_fee); - - LLSD payload; - payload["transaction_id"] = session_id; - payload["group_id"] = from_group ? from_id : aux_id; - payload["name"] = name; - payload["message"] = message; - payload["fee"] = membership_fee; - payload["use_offline_cap"] = session_id.isNull() && (offline == IM_OFFLINE); - - LLSD args; - args["MESSAGE"] = message; - // we shouldn't pass callback functor since it is registered in LLFunctorRegistration - LLNotificationsUtil::add("JoinGroup", args, payload); - } - } - break; - - case IM_INVENTORY_OFFERED: - case IM_TASK_INVENTORY_OFFERED: - // Someone has offered us some inventory. - { - LLOfferInfo* info = new LLOfferInfo; - if (IM_INVENTORY_OFFERED == dialog) - { - struct offer_agent_bucket_t - { - S8 asset_type; - LLUUID object_id; - }*bucketp; - - if (sizeof(offer_agent_bucket_t) != binary_bucket_size) - { - LL_WARNS("Messaging") << "Malformed inventory offer from agent" << LL_ENDL; - delete info; - break; - } - bucketp = (struct offer_agent_bucket_t*) &binary_bucket[0]; - info->mType = (LLAssetType::EType) bucketp->asset_type; - info->mObjectID = bucketp->object_id; - info->mFromObject = false; - } - else // IM_TASK_INVENTORY_OFFERED - { - if (sizeof(S8) == binary_bucket_size) - { - info->mType = (LLAssetType::EType) binary_bucket[0]; - } - else - { - /*RIDER*/ // The previous version of the protocol returned the wrong binary bucket... we - // still might be able to figure out the type... even though the offer is not retrievable. - - // Should be safe to remove once DRTSIM-451 fully deploys - std::string str_bucket(reinterpret_cast(binary_bucket)); - std::string str_type(str_bucket.substr(0, str_bucket.find('|'))); - - std::stringstream type_convert(str_type); - - S32 type; - type_convert >> type; - - // We could try AT_UNKNOWN which would be more accurate, but that causes an auto decline - info->mType = static_cast(type); - // Don't break in the case of a bad binary bucket. Go ahead and show the - // accept/decline popup even though it will not do anything. - LL_WARNS("Messaging") << "Malformed inventory offer from object, type might be " << info->mType << LL_ENDL; - } - info->mObjectID = LLUUID::null; - info->mFromObject = true; - } - - info->mIM = dialog; - info->mFromID = from_id; - info->mFromGroup = from_group; - info->mFolderID = gInventory.findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(info->mType)); - - info->mTransactionID = session_id.notNull() ? session_id : aux_id; - - info->mFromName = name; - info->mDesc = message; - info->mHost = sender; - //if (((is_do_not_disturb && !is_owned_by_me) || is_muted)) - if (is_muted) - { - // Prefetch the offered item so that it can be discarded by the appropriate observer. (EXT-4331) - if (IM_INVENTORY_OFFERED == dialog) - { - LLInventoryFetchItemsObserver* fetch_item = new LLInventoryFetchItemsObserver(info->mObjectID); - fetch_item->startFetch(); - delete fetch_item; - // Same as closing window - info->forceResponse(IOR_DECLINE); - } - else - { - info->forceResponse(IOR_MUTE); - } - } - // old logic: busy mode must not affect interaction with objects (STORM-565) - // new logic: inventory offers from in-world objects should be auto-declined (CHUI-519) - else if (is_do_not_disturb && dialog == IM_TASK_INVENTORY_OFFERED) - { - // Until throttling is implemented, do not disturb mode should reject inventory instead of silently - // accepting it. SEE SL-39554 - info->forceResponse(IOR_DECLINE); - } - else - { - inventory_offer_handler(info); - } - } - break; - - case IM_INVENTORY_ACCEPTED: - { - args["NAME"] = LLSLURL("agent", from_id, "completename").getSLURLString();; - args["ORIGINAL_NAME"] = original_name; - LLSD payload; - payload["from_id"] = from_id; - // Passing the "SESSION_NAME" to use it for IM notification logging - // in LLTipHandler::processNotification(). See STORM-941. - payload["SESSION_NAME"] = name; - LLNotificationsUtil::add("InventoryAccepted", args, payload); - break; - } - case IM_INVENTORY_DECLINED: - { - args["NAME"] = LLSLURL("agent", from_id, "completename").getSLURLString();; - LLSD payload; - payload["from_id"] = from_id; - LLNotificationsUtil::add("InventoryDeclined", args, payload); - break; - } - // TODO: _DEPRECATED suffix as part of vote removal - DEV-24856 - case IM_GROUP_VOTE: - { - LL_WARNS("Messaging") << "Received IM: IM_GROUP_VOTE_DEPRECATED" << LL_ENDL; - } - break; - - case IM_GROUP_ELECTION_DEPRECATED: - { - LL_WARNS("Messaging") << "Received IM: IM_GROUP_ELECTION_DEPRECATED" << LL_ENDL; - } - break; - - case IM_FROM_TASK: - { - - if (is_do_not_disturb && !is_owned_by_me) - { - return; - } - - // Build a link to open the object IM info window. - std::string location = ll_safe_string((char*)binary_bucket, binary_bucket_size - 1); - - if (session_id.notNull()) - { - chat.mFromID = session_id; - } - else - { - // This message originated on a region without the updated code for task id and slurl information. - // We just need a unique ID for this object that isn't the owner ID. - // If it is the owner ID it will overwrite the style that contains the link to that owner's profile. - // This isn't ideal - it will make 1 style for all objects owned by the the same person/group. - // This works because the only thing we can really do in this case is show the owner name and link to their profile. - chat.mFromID = from_id ^ gAgent.getSessionID(); - } - - chat.mSourceType = CHAT_SOURCE_OBJECT; - - // To conclude that the source type of message is CHAT_SOURCE_SYSTEM it's not - // enough to check only from name (i.e. fromName = "Second Life"). For example - // source type of messages from objects called "Second Life" should not be CHAT_SOURCE_SYSTEM. - bool chat_from_system = (SYSTEM_FROM == name) && region_id.isNull() && position.isNull(); - if (chat_from_system) - { - // System's UUID is NULL (fixes EXT-4766) - chat.mFromID = LLUUID::null; - chat.mSourceType = CHAT_SOURCE_SYSTEM; - } - - // IDEVO Some messages have embedded resident names - message = clean_name_from_task_im(message, from_group); - - LLSD query_string; - query_string["owner"] = from_id; - query_string["slurl"] = location; - query_string["name"] = name; - if (from_group) - { - query_string["groupowned"] = "true"; - } - - chat.mURL = LLSLURL("objectim", session_id, "").getSLURLString(); - chat.mText = message; - - // Note: lie to Nearby Chat, pretending that this is NOT an IM, because - // IMs from obejcts don't open IM sessions. - LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); - if (!chat_from_system && nearby_chat) - { - chat.mOwnerID = from_id; - LLSD args; - args["slurl"] = location; - - // Look for IRC-style emotes here so object name formatting is correct - std::string prefix = message.substr(0, 4); - if (prefix == "/me " || prefix == "/me'") - { - chat.mChatStyle = CHAT_STYLE_IRC; - } - - LLNotificationsUI::LLNotificationManager::instance().onChat(chat, args); - if (message != "") - { - LLSD msg_notify; - msg_notify["session_id"] = LLUUID(); - msg_notify["from_id"] = chat.mFromID; - msg_notify["source_type"] = chat.mSourceType; - on_new_message(msg_notify); - } - } - - - //Object IMs send with from name: 'Second Life' need to be displayed also in notification toasts (EXT-1590) - if (!chat_from_system) break; - - LLSD substitutions; - substitutions["NAME"] = name; - substitutions["MSG"] = message; - - LLSD payload; - payload["object_id"] = session_id; - payload["owner_id"] = from_id; - payload["from_id"] = from_id; - payload["slurl"] = location; - payload["name"] = name; - - if (from_group) - { - payload["group_owned"] = "true"; - } - - LLNotificationsUtil::add("ServerObjectMessage", substitutions, payload); - } - break; - - case IM_SESSION_SEND: // ad-hoc or group IMs - - // Only show messages if we have a session open (which - // should happen after you get an "invitation" - if (!gIMMgr->hasSession(session_id)) - { - return; - } - - else if (offline == IM_ONLINE && is_do_not_disturb) - { - - // return a standard "do not disturb" message, but only do it to online IM - // (i.e. not other auto responses and not store-and-forward IM) - if (!gIMMgr->hasSession(session_id)) - { - // if there is not a panel for this conversation (i.e. it is a new IM conversation - // initiated by the other party) then... - send_do_not_disturb_message(gMessageSystem, from_id, session_id); - } - - // now store incoming IM in chat history - - buffer = message; - - LL_DEBUGS("Messaging") << "message in dnd; session_id( " << session_id << " ), from_id( " << from_id << " )" << LL_ENDL; - - // add to IM panel, but do not bother the user - gIMMgr->addMessage( - session_id, - from_id, - name, - buffer, - IM_OFFLINE == offline, - ll_safe_string((char*)binary_bucket), - IM_SESSION_INVITE, - parent_estate_id, - region_id, - position, - false, // is_region_msg - timestamp); - } - else - { - // standard message, not from system - std::string saved; - if (offline == IM_OFFLINE) - { - saved = llformat("(Saved %s) ", formatted_time(timestamp).c_str()); - } - - buffer = saved + message; - - LL_DEBUGS("Messaging") << "standard message session_id( " << session_id << " ), from_id( " << from_id << " )" << LL_ENDL; - - gIMMgr->addMessage( - session_id, - from_id, - name, - buffer, - (IM_OFFLINE == offline), - ll_safe_string((char*)binary_bucket), // session name - IM_SESSION_INVITE, - parent_estate_id, - region_id, - position, - false, // is_region_msg - timestamp); - } - break; - - case IM_FROM_TASK_AS_ALERT: - if (is_do_not_disturb && !is_owned_by_me) - { - return; - } - { - // Construct a viewer alert for this message. - args["NAME"] = name; - args["MESSAGE"] = message; - LLNotificationsUtil::add("ObjectMessage", args); - } - break; - case IM_DO_NOT_DISTURB_AUTO_RESPONSE: - if (is_muted) - { - LL_DEBUGS("Messaging") << "Ignoring do-not-disturb response from " << from_id << LL_ENDL; - return; - } - else - { - gIMMgr->addMessage(session_id, from_id, name, message); - } - break; - - case IM_LURE_USER: - case IM_TELEPORT_REQUEST: - { - if (is_muted) - { - return; - } - else if (gSavedPerAccountSettings.getBOOL("VoiceCallsFriendsOnly") && (LLAvatarTracker::instance().getBuddyInfo(from_id) == NULL)) - { - return; - } - else - { - if (is_do_not_disturb) - { - send_do_not_disturb_message(gMessageSystem, from_id); - } - - LLVector3 pos, look_at; - U64 region_handle(0); - U8 region_access(SIM_ACCESS_MIN); - std::string region_info = ll_safe_string((char*)binary_bucket, binary_bucket_size); - std::string region_access_str = LLStringUtil::null; - std::string region_access_icn = LLStringUtil::null; - std::string region_access_lc = LLStringUtil::null; - - bool canUserAccessDstRegion = true; - bool doesUserRequireMaturityIncrease = false; - - // Do not parse the (empty) lure bucket for TELEPORT_REQUEST - if (IM_TELEPORT_REQUEST != dialog && parse_lure_bucket(region_info, region_handle, pos, look_at, region_access)) - { - region_access_str = LLViewerRegion::accessToString(region_access); - region_access_icn = LLViewerRegion::getAccessIcon(region_access); - region_access_lc = region_access_str; - LLStringUtil::toLower(region_access_lc); - - if (!gAgent.isGodlike()) - { - switch (region_access) - { - case SIM_ACCESS_MIN: - case SIM_ACCESS_PG: - break; - case SIM_ACCESS_MATURE: - if (gAgent.isTeen()) - { - canUserAccessDstRegion = false; - } - else if (gAgent.prefersPG()) - { - doesUserRequireMaturityIncrease = true; - } - break; - case SIM_ACCESS_ADULT: - if (!gAgent.isAdult()) - { - canUserAccessDstRegion = false; - } - else if (!gAgent.prefersAdult()) - { - doesUserRequireMaturityIncrease = true; - } - break; - default: - llassert(0); - break; - } - } - } - - LLSD args; - // *TODO: Translate -> [FIRST] [LAST] (maybe) - args["NAME_SLURL"] = LLSLURL("agent", from_id, "about").getSLURLString(); - args["MESSAGE"] = message; - args["MATURITY_STR"] = region_access_str; - args["MATURITY_ICON"] = region_access_icn; - args["REGION_CONTENT_MATURITY"] = region_access_lc; - LLSD payload; - payload["from_id"] = from_id; - payload["lure_id"] = session_id; - payload["godlike"] = false; - payload["region_maturity"] = region_access; - - if (!canUserAccessDstRegion) - { - LLNotification::Params params("TeleportOffered_MaturityBlocked"); - params.substitutions = args; - params.payload = payload; - LLPostponedNotification::add(params, from_id, false); - send_simple_im(from_id, LLTrans::getString("TeleportMaturityExceeded"), IM_NOTHING_SPECIAL, session_id); - send_simple_im(from_id, LLStringUtil::null, IM_LURE_DECLINED, session_id); - } - else if (doesUserRequireMaturityIncrease) - { - LLNotification::Params params("TeleportOffered_MaturityExceeded"); - params.substitutions = args; - params.payload = payload; - LLPostponedNotification::add(params, from_id, false); - } - else - { - LLNotification::Params params; - if (IM_LURE_USER == dialog) - { - params.name = "TeleportOffered"; - params.functor.name = "TeleportOffered"; - } - else if (IM_TELEPORT_REQUEST == dialog) - { - params.name = "TeleportRequest"; - params.functor.name = "TeleportRequest"; - } - - params.substitutions = args; - params.payload = payload; - LLPostponedNotification::add(params, from_id, false); - } - } - } - break; - - case IM_GODLIKE_LURE_USER: - { - LLVector3 pos, look_at; - U64 region_handle(0); - U8 region_access(SIM_ACCESS_MIN); - std::string region_info = ll_safe_string((char*)binary_bucket, binary_bucket_size); - std::string region_access_str = LLStringUtil::null; - std::string region_access_icn = LLStringUtil::null; - std::string region_access_lc = LLStringUtil::null; - - bool canUserAccessDstRegion = true; - bool doesUserRequireMaturityIncrease = false; - - if (parse_lure_bucket(region_info, region_handle, pos, look_at, region_access)) - { - region_access_str = LLViewerRegion::accessToString(region_access); - region_access_icn = LLViewerRegion::getAccessIcon(region_access); - region_access_lc = region_access_str; - LLStringUtil::toLower(region_access_lc); - - if (!gAgent.isGodlike()) - { - switch (region_access) - { - case SIM_ACCESS_MIN: - case SIM_ACCESS_PG: - break; - case SIM_ACCESS_MATURE: - if (gAgent.isTeen()) - { - canUserAccessDstRegion = false; - } - else if (gAgent.prefersPG()) - { - doesUserRequireMaturityIncrease = true; - } - break; - case SIM_ACCESS_ADULT: - if (!gAgent.isAdult()) - { - canUserAccessDstRegion = false; - } - else if (!gAgent.prefersAdult()) - { - doesUserRequireMaturityIncrease = true; - } - break; - default: - llassert(0); - break; - } - } - } - - LLSD args; - // *TODO: Translate -> [FIRST] [LAST] (maybe) - args["NAME_SLURL"] = LLSLURL("agent", from_id, "about").getSLURLString(); - args["MESSAGE"] = message; - args["MATURITY_STR"] = region_access_str; - args["MATURITY_ICON"] = region_access_icn; - args["REGION_CONTENT_MATURITY"] = region_access_lc; - LLSD payload; - payload["from_id"] = from_id; - payload["lure_id"] = session_id; - payload["godlike"] = true; - payload["region_maturity"] = region_access; - - if (!canUserAccessDstRegion) - { - LLNotification::Params params("TeleportOffered_MaturityBlocked"); - params.substitutions = args; - params.payload = payload; - LLPostponedNotification::add(params, from_id, false); - send_simple_im(from_id, LLTrans::getString("TeleportMaturityExceeded"), IM_NOTHING_SPECIAL, session_id); - send_simple_im(from_id, LLStringUtil::null, IM_LURE_DECLINED, session_id); - } - else if (doesUserRequireMaturityIncrease) - { - LLNotification::Params params("TeleportOffered_MaturityExceeded"); - params.substitutions = args; - params.payload = payload; - LLPostponedNotification::add(params, from_id, false); - } - else - { - // do not show a message box, because you're about to be - // teleported. - LLNotifications::instance().forceResponse(LLNotification::Params("TeleportOffered").payload(payload), 0); - } - } - break; - - case IM_GOTO_URL: - { - LLSD args; - // n.b. this is for URLs sent by the system, not for - // URLs sent by scripts (i.e. llLoadURL) - if (binary_bucket_size <= 0) - { - LL_WARNS("Messaging") << "bad binary_bucket_size: " - << binary_bucket_size - << " - aborting function." << LL_ENDL; - return; - } - - std::string url; - - url.assign((char*)binary_bucket, binary_bucket_size - 1); - args["MESSAGE"] = message; - args["URL"] = url; - LLSD payload; - payload["url"] = url; - LLNotificationsUtil::add("GotoURL", args, payload); - } - break; - - case IM_FRIENDSHIP_OFFERED: - { - LLSD payload; - payload["from_id"] = from_id; - payload["session_id"] = session_id;; - payload["online"] = (offline == IM_ONLINE); - payload["sender"] = sender.getIPandPort(); - - bool add_notification = true; - for (auto& panel : LLToastNotifyPanel::instance_snapshot()) - { - const std::string& notification_name = panel.getNotificationName(); - if (notification_name == "OfferFriendship" && panel.isControlPanelEnabled()) - { - add_notification = false; - break; - } - } - - if (is_muted && add_notification) - { - LLNotifications::instance().forceResponse(LLNotification::Params("OfferFriendship").payload(payload), 1); - } - else - { - if (is_do_not_disturb) - { - send_do_not_disturb_message(gMessageSystem, from_id); - } - args["NAME_SLURL"] = LLSLURL("agent", from_id, "about").getSLURLString(); - - if (add_notification) - { - if (message.empty()) - { - //support for frienship offers from clients before July 2008 - LLNotificationsUtil::add("OfferFriendshipNoMessage", args, payload); - } - else - { - args["[MESSAGE]"] = message; - LLNotification::Params params("OfferFriendship"); - params.substitutions = args; - params.payload = payload; - LLPostponedNotification::add(params, from_id, false); - } - } - } - } - break; - - case IM_FRIENDSHIP_ACCEPTED: - { - // In the case of an offline IM, the formFriendship() may be extraneous - // as the database should already include the relationship. But it - // doesn't hurt for dupes. - LLAvatarTracker::formFriendship(from_id); - - std::vector strings; - strings.push_back(from_id.asString()); - send_generic_message("requestonlinenotification", strings); - - args["NAME"] = name; - LLSD payload; - payload["from_id"] = from_id; - LLAvatarNameCache::get(from_id, boost::bind(¬ification_display_name_callback, _1, _2, "FriendshipAccepted", args, payload)); - } - break; - - case IM_FRIENDSHIP_DECLINED_DEPRECATED: - default: - LL_WARNS("Messaging") << "Instant message calling for unknown dialog " - << (S32)dialog << LL_ENDL; - break; - } - - LLWindow* viewer_window = gViewerWindow->getWindow(); - if (viewer_window && viewer_window->getMinimized()) - { - viewer_window->flashIcon(5.f); - } -} - -void LLIMProcessing::requestOfflineMessages() -{ - static bool requested = false; - if (!requested - && gMessageSystem - && !gDisconnected - && LLMuteList::getInstance()->isLoaded() - && isAgentAvatarValid() - && gAgent.getRegion() - && gAgent.getRegion()->capabilitiesReceived()) - { - std::string cap_url = gAgent.getRegionCapability("ReadOfflineMsgs"); - - // Auto-accepted inventory items may require the avatar object - // to build a correct name. Likewise, inventory offers from - // muted avatars require the mute list to properly mute. - if (cap_url.empty() - || gAgent.getRegionCapability("AcceptFriendship").empty() - || gAgent.getRegionCapability("AcceptGroupInvite").empty()) - { - // Offline messages capability provides no session/transaction ids for message AcceptFriendship and IM_GROUP_INVITATION to work - // So make sure we have the caps before using it. - requestOfflineMessagesLegacy(); - } - else - { - LLCoros::instance().launch("LLIMProcessing::requestOfflineMessagesCoro", - boost::bind(&LLIMProcessing::requestOfflineMessagesCoro, cap_url)); - } - requested = true; - } -} - -void LLIMProcessing::requestOfflineMessagesCoro(std::string url) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("requestOfflineMessagesCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD result = httpAdapter->getAndSuspend(httpRequest, url); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) // success = httpResults["success"].asBoolean(); - { - LL_WARNS("Messaging") << "Error requesting offline messages via capability " << url << ", Status: " << status.toString() << "\nFalling back to legacy method." << LL_ENDL; - - requestOfflineMessagesLegacy(); - return; - } - - LLSD contents = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT]; - - if (!contents.size()) - { - LL_WARNS("Messaging") << "No contents received for offline messages via capability " << url << LL_ENDL; - return; - } - - // Todo: once dirtsim-369 releases, remove one of the map/array options - LLSD messages; - if (contents.isArray()) - { - messages = *contents.beginArray(); - } - else if (contents.has("messages")) - { - messages = contents["messages"]; - } - else - { - LL_WARNS("Messaging") << "Invalid offline message content received via capability " << url << LL_ENDL; - return; - } - - if (!messages.isArray()) - { - LL_WARNS("Messaging") << "Invalid offline message content received via capability " << url << LL_ENDL; - return; - } - - if (messages.size() == 0) - { - // Nothing to process - return; - } - - if (!gAgent.getRegion()) - { - LL_WARNS("Messaging") << "Region null while attempting to load messages." << LL_ENDL; - return; - } - - LL_INFOS("Messaging") << "Processing offline messages." << LL_ENDL; - - LLHost sender = gAgent.getRegionHost(); - - LLSD::array_iterator i = messages.beginArray(); - LLSD::array_iterator iEnd = messages.endArray(); - for (; i != iEnd; ++i) - { - const LLSD &message_data(*i); - - /* RIDER: Many fields in this message are using a '_' rather than the standard '-'. This - * should be changed but would require tight coordination with the simulator. - */ - LLVector3 position; - if (message_data.has("position")) - { - position.setValue(message_data["position"]); - } - else - { - position.set(message_data["local_x"].asReal(), message_data["local_y"].asReal(), message_data["local_z"].asReal()); - } - - std::vector bin_bucket; - if (message_data.has("binary_bucket")) - { - bin_bucket = message_data["binary_bucket"].asBinary(); - } - else - { - bin_bucket.push_back(0); - } - - // Todo: once drtsim-451 releases, remove the string option - bool from_group; - if (message_data["from_group"].isInteger()) - { - from_group = message_data["from_group"].asInteger(); - } - else - { - from_group = message_data["from_group"].asString() == "Y"; - } - - EInstantMessage dialog = static_cast(message_data["dialog"].asInteger()); - LLUUID session_id = message_data["transaction-id"].asUUID(); - if (session_id.isNull() && dialog == IM_FROM_TASK) - { - session_id = message_data["asset_id"].asUUID(); - } - LLIMProcessing::processNewMessage( - message_data["from_agent_id"].asUUID(), - from_group, - message_data["to_agent_id"].asUUID(), - message_data.has("offline") ? static_cast(message_data["offline"].asInteger()) : IM_OFFLINE, - dialog, - session_id, - static_cast(message_data["timestamp"].asInteger()), - message_data["from_agent_name"].asString(), - message_data["message"].asString(), - static_cast((message_data.has("parent_estate_id")) ? message_data["parent_estate_id"].asInteger() : 1), // 1 - IMMainland - message_data["region_id"].asUUID(), - position, - bin_bucket.data(), - bin_bucket.size(), - sender, - message_data["asset_id"].asUUID()); - - } -} - -void LLIMProcessing::requestOfflineMessagesLegacy() -{ - LL_INFOS("Messaging") << "Requesting offline messages (Legacy)." << LL_ENDL; - - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_RetrieveInstantMessages); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - gAgent.sendReliableMessage(); -} - +/** +* @file LLIMProcessing.cpp +* @brief Container for Instant Messaging +* +* $LicenseInfo:firstyear=2001&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2018, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" + +#include "llimprocessing.h" + +#include "llagent.h" +#include "llappviewer.h" +#include "llavatarnamecache.h" +#include "llfirstuse.h" +#include "llfloaterreg.h" +#include "llfloaterimnearbychat.h" +#include "llimview.h" +#include "llinventoryobserver.h" +#include "llinventorymodel.h" +#include "llmutelist.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "llnotificationmanager.h" +#include "llpanelgroup.h" +#include "llregex.h" +#include "llregionhandle.h" +#include "llsdserialize.h" +#include "llslurl.h" +#include "llstring.h" +#include "lltoastnotifypanel.h" +#include "lltrans.h" +#include "llviewergenericmessage.h" +#include "llviewerobjectlist.h" +#include "llviewermessage.h" +#include "llviewerwindow.h" +#include "llviewerregion.h" +#include "llvoavatarself.h" +#include "llworld.h" + +#include "boost/lexical_cast.hpp" +#if LL_MSVC +// disable boost::lexical_cast warning +#pragma warning (disable:4702) +#endif + +extern void on_new_message(const LLSD& msg); + +// Strip out "Resident" for display, but only if the message came from a user +// (rather than a script) +static std::string clean_name_from_im(const std::string& name, EInstantMessage type) +{ + switch (type) + { + case IM_NOTHING_SPECIAL: + case IM_MESSAGEBOX: + case IM_GROUP_INVITATION: + case IM_INVENTORY_OFFERED: + case IM_INVENTORY_ACCEPTED: + case IM_INVENTORY_DECLINED: + case IM_GROUP_VOTE: + case IM_GROUP_MESSAGE_DEPRECATED: + //IM_TASK_INVENTORY_OFFERED + //IM_TASK_INVENTORY_ACCEPTED + //IM_TASK_INVENTORY_DECLINED + case IM_NEW_USER_DEFAULT: + case IM_SESSION_INVITE: + case IM_SESSION_P2P_INVITE: + case IM_SESSION_GROUP_START: + case IM_SESSION_CONFERENCE_START: + case IM_SESSION_SEND: + case IM_SESSION_LEAVE: + //IM_FROM_TASK + case IM_DO_NOT_DISTURB_AUTO_RESPONSE: + case IM_CONSOLE_AND_CHAT_HISTORY: + case IM_LURE_USER: + case IM_LURE_ACCEPTED: + case IM_LURE_DECLINED: + case IM_GODLIKE_LURE_USER: + case IM_TELEPORT_REQUEST: + case IM_GROUP_ELECTION_DEPRECATED: + //IM_GOTO_URL + //IM_FROM_TASK_AS_ALERT + case IM_GROUP_NOTICE: + case IM_GROUP_NOTICE_INVENTORY_ACCEPTED: + case IM_GROUP_NOTICE_INVENTORY_DECLINED: + case IM_GROUP_INVITATION_ACCEPT: + case IM_GROUP_INVITATION_DECLINE: + case IM_GROUP_NOTICE_REQUESTED: + case IM_FRIENDSHIP_OFFERED: + case IM_FRIENDSHIP_ACCEPTED: + case IM_FRIENDSHIP_DECLINED_DEPRECATED: + //IM_TYPING_START + //IM_TYPING_STOP + return LLCacheName::cleanFullName(name); + default: + return name; + } +} + +static std::string clean_name_from_task_im(const std::string& msg, + bool from_group) +{ + boost::smatch match; + static const boost::regex returned_exp( + "(.*been returned to your inventory lost and found folder by )(.+)( (from|near).*)"); + if (ll_regex_match(msg, match, returned_exp)) + { + // match objects are 1-based for groups + std::string final = match[1].str(); + std::string name = match[2].str(); + // Don't try to clean up group names + if (!from_group) + { + final += LLCacheName::buildUsername(name); + } + final += match[3].str(); + return final; + } + return msg; +} + +const std::string NOT_ONLINE_MSG("User not online - message will be stored and delivered later."); +const std::string NOT_ONLINE_INVENTORY("User not online - inventory has been saved."); +void translate_if_needed(std::string& message) +{ + if (message == NOT_ONLINE_MSG) + { + message = LLTrans::getString("not_online_msg"); + } + else if (message == NOT_ONLINE_INVENTORY) + { + message = LLTrans::getString("not_online_inventory"); + } +} + +class LLPostponedIMSystemTipNotification : public LLPostponedNotification +{ +protected: + /* virtual */ + void modifyNotificationParams() + { + LLSD payload = mParams.payload; + payload["SESSION_NAME"] = mName; + mParams.payload = payload; + } +}; + +class LLPostponedOfferNotification : public LLPostponedNotification +{ +protected: + /* virtual */ + void modifyNotificationParams() + { + LLSD substitutions = mParams.substitutions; + substitutions["NAME"] = mName; + mParams.substitutions = substitutions; + } +}; + +void inventory_offer_handler(LLOfferInfo* info) +{ + // If muted, don't even go through the messaging stuff. Just curtail the offer here. + // Passing in a null UUID handles the case of where you have muted one of your own objects by_name. + // The solution for STORM-1297 seems to handle the cases where the object is owned by someone else. + if (LLMuteList::getInstance()->isMuted(info->mFromID, info->mFromName) || + LLMuteList::getInstance()->isMuted(LLUUID::null, info->mFromName)) + { + info->forceResponse(IOR_MUTE); + return; + } + + bool bAutoAccept(false); + // Avoid the Accept/Discard dialog if the user so desires. JC + if (gSavedSettings.getBOOL("AutoAcceptNewInventory") + && (info->mType == LLAssetType::AT_NOTECARD + || info->mType == LLAssetType::AT_LANDMARK + || info->mType == LLAssetType::AT_TEXTURE)) + { + // For certain types, just accept the items into the inventory, + // and possibly open them on receipt depending upon "ShowNewInventory". + bAutoAccept = true; + } + + // Strip any SLURL from the message display. (DEV-2754) + std::string msg = info->mDesc; + int indx = msg.find(" ( http://slurl.com/secondlife/"); + if (indx == std::string::npos) + { + // try to find new slurl host + indx = msg.find(" ( http://maps.secondlife.com/secondlife/"); + } + if (indx >= 0) + { + LLStringUtil::truncate(msg, indx); + } + + LLSD args; + args["[OBJECTNAME]"] = msg; + + LLSD payload; + + // must protect against a NULL return from lookupHumanReadable() + std::string typestr = ll_safe_string(LLAssetType::lookupHumanReadable(info->mType)); + if (!typestr.empty()) + { + // human readable matches string name from strings.xml + // lets get asset type localized name + args["OBJECTTYPE"] = LLTrans::getString(typestr); + } + else + { + LL_WARNS("Messaging") << "LLAssetType::lookupHumanReadable() returned NULL - probably bad asset type: " << info->mType << LL_ENDL; + args["OBJECTTYPE"] = ""; + + // This seems safest, rather than propagating bogosity + LL_WARNS("Messaging") << "Forcing an inventory-decline for probably-bad asset type." << LL_ENDL; + info->forceResponse(IOR_DECLINE); + return; + } + + // If mObjectID is null then generate the object_id based on msg to prevent + // multiple creation of chiclets for same object. + LLUUID object_id = info->mObjectID; + if (object_id.isNull()) + object_id.generate(msg); + + payload["from_id"] = info->mFromID; + // Needed by LLScriptFloaterManager to bind original notification with + // faked for toast one. + payload["object_id"] = object_id; + // Flag indicating that this notification is faked for toast. + payload["give_inventory_notification"] = false; + args["OBJECTFROMNAME"] = info->mFromName; + args["NAME"] = info->mFromName; + if (info->mFromGroup) + { + args["NAME_SLURL"] = LLSLURL("group", info->mFromID, "about").getSLURLString(); + } + else + { + args["NAME_SLURL"] = LLSLURL("agent", info->mFromID, "about").getSLURLString(); + } + std::string verb = "select?name=" + LLURI::escape(msg); + args["ITEM_SLURL"] = LLSLURL("inventory", info->mObjectID, verb.c_str()).getSLURLString(); + + LLNotification::Params p; + + // Object -> Agent Inventory Offer + if (info->mFromObject && !bAutoAccept) + { + // Inventory Slurls don't currently work for non agent transfers, so only display the object name. + args["ITEM_SLURL"] = msg; + // Note: sets inventory_task_offer_callback as the callback + p.substitutions(args).payload(payload).functor.responder(LLNotificationResponderPtr(info)); + info->mPersist = true; + + // Offers from your own objects need a special notification template. + p.name = info->mFromID == gAgentID ? "OwnObjectGiveItem" : "ObjectGiveItem"; + + // Pop up inv offer chiclet and let the user accept (keep), or reject (and silently delete) the inventory. + LLPostponedNotification::add(p, info->mFromID, info->mFromGroup); + } + else // Agent -> Agent Inventory Offer + { + p.responder = info; + // Note: sets inventory_offer_callback as the callback + // *TODO fix memory leak + // inventory_offer_callback() is not invoked if user received notification and + // closes viewer(without responding the notification) + p.substitutions(args).payload(payload).functor.responder(LLNotificationResponderPtr(info)); + info->mPersist = true; + p.name = "UserGiveItem"; + p.offer_from_agent = true; + + // Prefetch the item into your local inventory. + LLInventoryFetchItemsObserver* fetch_item = new LLInventoryFetchItemsObserver(info->mObjectID); + fetch_item->startFetch(); + if (fetch_item->isFinished()) + { + fetch_item->done(); + } + else + { + gInventory.addObserver(fetch_item); + } + + // In viewer 2 we're now auto receiving inventory offers and messaging as such (not sending reject messages). + info->send_auto_receive_response(); + + if (gAgent.isDoNotDisturb()) + { + send_do_not_disturb_message(gMessageSystem, info->mFromID); + } + + if (!bAutoAccept) // if we auto accept, do not pester the user + { + // Inform user that there is a script floater via toast system + payload["give_inventory_notification"] = true; + p.payload = payload; + LLPostponedNotification::add(p, info->mFromID, false); + } + } + + LLFirstUse::newInventory(); +} + +// Callback for name resolution of a god/estate message +static void god_message_name_cb(const LLAvatarName& av_name, LLChat chat, std::string message) +{ + LLSD args; + args["NAME"] = av_name.getCompleteName(); + args["MESSAGE"] = message; + LLNotificationsUtil::add("GodMessage", args); + + // Treat like a system message and put in chat history. + chat.mSourceType = CHAT_SOURCE_SYSTEM; + chat.mText = message; + + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); + if (nearby_chat) + { + nearby_chat->addMessage(chat); + } +} + +static bool parse_lure_bucket(const std::string& bucket, + U64& region_handle, + LLVector3& pos, + LLVector3& look_at, + U8& region_access) +{ + // tokenize the bucket + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("|", "", boost::keep_empty_tokens); + tokenizer tokens(bucket, sep); + tokenizer::iterator iter = tokens.begin(); + + S32 gx, gy, rx, ry, rz, lx, ly, lz; + try + { + gx = boost::lexical_cast((*(iter)).c_str()); + gy = boost::lexical_cast((*(++iter)).c_str()); + rx = boost::lexical_cast((*(++iter)).c_str()); + ry = boost::lexical_cast((*(++iter)).c_str()); + rz = boost::lexical_cast((*(++iter)).c_str()); + lx = boost::lexical_cast((*(++iter)).c_str()); + ly = boost::lexical_cast((*(++iter)).c_str()); + lz = boost::lexical_cast((*(++iter)).c_str()); + } + catch (boost::bad_lexical_cast&) + { + LL_WARNS("parse_lure_bucket") + << "Couldn't parse lure bucket." + << LL_ENDL; + return false; + } + // Grab region access + region_access = SIM_ACCESS_MIN; + if (++iter != tokens.end()) + { + std::string access_str((*iter).c_str()); + LLStringUtil::trim(access_str); + if (access_str == "A") + { + region_access = SIM_ACCESS_ADULT; + } + else if (access_str == "M") + { + region_access = SIM_ACCESS_MATURE; + } + else if (access_str == "PG") + { + region_access = SIM_ACCESS_PG; + } + } + + pos.setVec((F32)rx, (F32)ry, (F32)rz); + look_at.setVec((F32)lx, (F32)ly, (F32)lz); + + region_handle = to_region_handle(gx, gy); + return true; +} + +static void notification_display_name_callback(const LLUUID& id, + const LLAvatarName& av_name, + const std::string& name, + LLSD& substitutions, + const LLSD& payload) +{ + substitutions["NAME"] = av_name.getDisplayName(); + LLNotificationsUtil::add(name, substitutions, payload); +} + +void LLIMProcessing::processNewMessage(LLUUID from_id, + bool from_group, + LLUUID to_id, + U8 offline, + EInstantMessage dialog, // U8 + LLUUID session_id, + U32 timestamp, + std::string agentName, + std::string message, + U32 parent_estate_id, + LLUUID region_id, + LLVector3 position, + U8 *binary_bucket, + S32 binary_bucket_size, + LLHost &sender, + LLUUID aux_id) +{ + LLChat chat; + std::string buffer; + std::string name = agentName; + + // make sure that we don't have an empty or all-whitespace name + LLStringUtil::trim(name); + if (name.empty()) + { + name = LLTrans::getString("Unnamed"); + } + + // Preserve the unaltered name for use in group notice mute checking. + std::string original_name = name; + + // IDEVO convert new-style "Resident" names for display + name = clean_name_from_im(name, dialog); + + bool is_do_not_disturb = gAgent.isDoNotDisturb(); + bool is_muted = LLMuteList::getInstance()->isMuted(from_id, name, LLMute::flagTextChat) + // object IMs contain sender object id in session_id (STORM-1209) + || (dialog == IM_FROM_TASK && LLMuteList::getInstance()->isMuted(session_id)); + bool is_owned_by_me = false; + bool is_friend = LLAvatarTracker::instance().getBuddyInfo(from_id) != NULL; + bool accept_im_from_only_friend = gSavedPerAccountSettings.getBOOL("VoiceCallsFriendsOnly"); + bool is_linden = chat.mSourceType != CHAT_SOURCE_OBJECT && + LLMuteList::isLinden(name); + + chat.mMuted = is_muted; + chat.mFromID = from_id; + chat.mFromName = name; + chat.mSourceType = (from_id.isNull() || (name == std::string(SYSTEM_FROM))) ? CHAT_SOURCE_SYSTEM : CHAT_SOURCE_AGENT; + + if (chat.mSourceType == CHAT_SOURCE_SYSTEM) + { // Translate server message if required (MAINT-6109) + translate_if_needed(message); + } + + LLViewerObject *source = gObjectList.findObject(session_id); //Session ID is probably the wrong thing. + if (source) + { + is_owned_by_me = source->permYouOwner(); + } + + std::string separator_string(": "); + + LLSD args; + LLSD payload; + LLNotification::Params params; + + switch (dialog) + { + case IM_CONSOLE_AND_CHAT_HISTORY: + args["MESSAGE"] = message; + payload["from_id"] = from_id; + + params.name = "IMSystemMessageTip"; + params.substitutions = args; + params.payload = payload; + LLPostponedNotification::add(params, from_id, false); + break; + + case IM_NOTHING_SPECIAL: // p2p IM + // Don't show dialog, just do IM + if (!gAgent.isGodlike() + && gAgent.getRegion()->isPrelude() + && to_id.isNull()) + { + // do nothing -- don't distract newbies in + // Prelude with global IMs + } + else if (offline == IM_ONLINE + && is_do_not_disturb + && from_id.notNull() //not a system message + && to_id.notNull()) //not global message + { + + // now store incoming IM in chat history + + buffer = message; + + LL_DEBUGS("Messaging") << "session_id( " << session_id << " ), from_id( " << from_id << " )" << LL_ENDL; + + // add to IM panel, but do not bother the user + gIMMgr->addMessage( + session_id, + from_id, + name, + buffer, + IM_OFFLINE == offline, + LLStringUtil::null, + dialog, + parent_estate_id, + region_id, + position, + false, // is_region_msg + timestamp); + + if (!gIMMgr->isDNDMessageSend(session_id)) + { + // return a standard "do not disturb" message, but only do it to online IM + // (i.e. not other auto responses and not store-and-forward IM) + send_do_not_disturb_message(gMessageSystem, from_id, session_id); + gIMMgr->setDNDMessageSent(session_id, true); + } + + } + else if (from_id.isNull()) + { + LLSD args; + args["MESSAGE"] = message; + LLNotificationsUtil::add("SystemMessage", args); + } + else if (to_id.isNull()) + { + // Message to everyone from GOD, look up the fullname since + // server always slams name to legacy names + LLAvatarNameCache::get(from_id, boost::bind(god_message_name_cb, _2, chat, message)); + } + else + { + // standard message, not from system + std::string saved; + if (offline == IM_OFFLINE) + { + LLStringUtil::format_map_t args; + args["[LONG_TIMESTAMP]"] = formatted_time(timestamp); + saved = LLTrans::getString("Saved_message", args); + } + buffer = saved + message; + + LL_DEBUGS("Messaging") << "session_id( " << session_id << " ), from_id( " << from_id << " )" << LL_ENDL; + + bool mute_im = is_muted; + if (accept_im_from_only_friend && !is_friend && !is_linden) + { + if (!gIMMgr->isNonFriendSessionNotified(session_id)) + { + std::string message = LLTrans::getString("IM_unblock_only_groups_friends"); + gIMMgr->addMessage(session_id, from_id, name, message, IM_OFFLINE == offline); + gIMMgr->addNotifiedNonFriendSessionID(session_id); + } + + mute_im = true; + } + if (!mute_im) + { + bool region_message = false; + if (region_id.isNull()) + { + LLViewerRegion* regionp = LLWorld::instance().getRegionFromID(from_id); + if (regionp) + { + region_message = true; + } + } + gIMMgr->addMessage( + session_id, + from_id, + name, + buffer, + IM_OFFLINE == offline, + LLStringUtil::null, + dialog, + parent_estate_id, + region_id, + position, + region_message, + timestamp); + } + else + { + /* + EXT-5099 + */ + } + } + break; + + case IM_TYPING_START: + { + gIMMgr->processIMTypingStart(from_id, dialog); + } + break; + + case IM_TYPING_STOP: + { + gIMMgr->processIMTypingStop(from_id, dialog); + } + break; + + case IM_MESSAGEBOX: + { + // This is a block, modeless dialog. + args["MESSAGE"] = message; + LLNotificationsUtil::add("SystemMessageTip", args); + } + break; + case IM_GROUP_NOTICE: + case IM_GROUP_NOTICE_REQUESTED: + { + LL_INFOS("Messaging") << "Received IM_GROUP_NOTICE message." << LL_ENDL; + + LLUUID agent_id; + U8 has_inventory; + U8 asset_type = 0; + LLUUID group_id; + std::string item_name; + + if (aux_id.notNull()) + { + // aux_id contains group id, binary bucket contains name and asset type + group_id = aux_id; + has_inventory = binary_bucket_size > 1; + from_group = true; // inaccurate value correction + if (has_inventory) + { + std::string str_bucket = ll_safe_string((char*)binary_bucket, binary_bucket_size); + + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("|", "", boost::keep_empty_tokens); + tokenizer tokens(str_bucket, sep); + tokenizer::iterator iter = tokens.begin(); + + asset_type = (LLAssetType::EType)(atoi((*(iter++)).c_str())); + iter++; // wearable type if applicable, otherwise asset type + item_name = std::string((*(iter++)).c_str()); + // Note There is more elements in 'tokens' ... + + + for (int i = 0; i < 6; i++) + { + LL_WARNS() << *(iter++) << LL_ENDL; + iter++; + } + } + } + else + { + // All info is in binary bucket, read it for more information. + struct notice_bucket_header_t + { + U8 has_inventory; + U8 asset_type; + LLUUID group_id; + }; + struct notice_bucket_full_t + { + struct notice_bucket_header_t header; + U8 item_name[DB_INV_ITEM_NAME_BUF_SIZE]; + }*notice_bin_bucket; + + // Make sure the binary bucket is big enough to hold the header + // and a null terminated item name. + if ((binary_bucket_size < (S32)((sizeof(notice_bucket_header_t) + sizeof(U8)))) + || (binary_bucket[binary_bucket_size - 1] != '\0')) + { + LL_WARNS("Messaging") << "Malformed group notice binary bucket" << LL_ENDL; + break; + } + + notice_bin_bucket = (struct notice_bucket_full_t*) &binary_bucket[0]; + has_inventory = notice_bin_bucket->header.has_inventory; + asset_type = notice_bin_bucket->header.asset_type; + group_id = notice_bin_bucket->header.group_id; + item_name = ll_safe_string((const char*)notice_bin_bucket->item_name); + } + + if (group_id != from_id) + { + agent_id = from_id; + } + else + { + S32 index = original_name.find(" Resident"); + if (index != std::string::npos) + { + original_name = original_name.substr(0, index); + } + + // The group notice packet does not have an AgentID. Obtain one from the name cache. + // If last name is "Resident" strip it out so the cache name lookup works. + std::string legacy_name = gCacheName->buildLegacyName(original_name); + agent_id = LLAvatarNameCache::getInstance()->findIdByName(legacy_name); + + if (agent_id.isNull()) + { + LL_WARNS("Messaging") << "buildLegacyName returned null while processing " << original_name << LL_ENDL; + } + } + + if (agent_id.notNull() && LLMuteList::getInstance()->isMuted(agent_id)) + { + break; + } + + // If there is inventory, give the user the inventory offer. + LLOfferInfo* info = NULL; + + if (has_inventory) + { + info = new LLOfferInfo(); + + info->mIM = dialog; + info->mFromID = from_id; + info->mFromGroup = from_group; + info->mTransactionID = session_id; + info->mType = (LLAssetType::EType) asset_type; + info->mFolderID = gInventory.findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(info->mType)); + std::string from_name; + + from_name += "A group member named "; + from_name += name; + + info->mFromName = from_name; + info->mDesc = item_name; + info->mHost = sender; + } + + std::string str(message); + + // Tokenize the string. + // TODO: Support escaped tokens ("||" -> "|") + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("|", "", boost::keep_empty_tokens); + tokenizer tokens(str, sep); + tokenizer::iterator iter = tokens.begin(); + + std::string subj(*iter++); + std::string mes(*iter++); + + // Send the notification down the new path. + // For requested notices, we don't want to send the popups. + if (dialog != IM_GROUP_NOTICE_REQUESTED) + { + payload["subject"] = subj; + payload["message"] = mes; + payload["sender_name"] = name; + payload["sender_id"] = agent_id; + payload["group_id"] = group_id; + payload["inventory_name"] = item_name; + payload["received_time"] = LLDate::now(); + if (info && info->asLLSD()) + { + payload["inventory_offer"] = info->asLLSD(); + } + + LLSD args; + args["SUBJECT"] = subj; + args["MESSAGE"] = mes; + LLDate notice_date = LLDate(timestamp).notNull() ? LLDate(timestamp) : LLDate::now(); + LLNotifications::instance().add(LLNotification::Params("GroupNotice").substitutions(args).payload(payload).time_stamp(notice_date)); + } + + // Also send down the old path for now. + if (IM_GROUP_NOTICE_REQUESTED == dialog) + { + + LLPanelGroup::showNotice(subj, mes, group_id, has_inventory, item_name, info); + } + else + { + delete info; + } + } + break; + case IM_GROUP_INVITATION: + { + if (!is_muted) + { + // group is not blocked, but we still need to check agent that sent the invitation + // and we have no agent's id + // Note: server sends username "first.last". + is_muted |= LLMuteList::getInstance()->isMuted(name); + } + if (is_do_not_disturb || is_muted) + { + send_do_not_disturb_message(gMessageSystem, from_id); + } + + if (!is_muted) + { + LL_INFOS("Messaging") << "Received IM_GROUP_INVITATION message." << LL_ENDL; + // Read the binary bucket for more information. + struct invite_bucket_t + { + S32 membership_fee; + LLUUID role_id; + }*invite_bucket; + + // Make sure the binary bucket is the correct size. + if (binary_bucket_size != sizeof(invite_bucket_t)) + { + LL_WARNS("Messaging") << "Malformed group invite binary bucket" << LL_ENDL; + break; + } + + invite_bucket = (struct invite_bucket_t*) &binary_bucket[0]; + S32 membership_fee = ntohl(invite_bucket->membership_fee); + + LLSD payload; + payload["transaction_id"] = session_id; + payload["group_id"] = from_group ? from_id : aux_id; + payload["name"] = name; + payload["message"] = message; + payload["fee"] = membership_fee; + payload["use_offline_cap"] = session_id.isNull() && (offline == IM_OFFLINE); + + LLSD args; + args["MESSAGE"] = message; + // we shouldn't pass callback functor since it is registered in LLFunctorRegistration + LLNotificationsUtil::add("JoinGroup", args, payload); + } + } + break; + + case IM_INVENTORY_OFFERED: + case IM_TASK_INVENTORY_OFFERED: + // Someone has offered us some inventory. + { + LLOfferInfo* info = new LLOfferInfo; + if (IM_INVENTORY_OFFERED == dialog) + { + struct offer_agent_bucket_t + { + S8 asset_type; + LLUUID object_id; + }*bucketp; + + if (sizeof(offer_agent_bucket_t) != binary_bucket_size) + { + LL_WARNS("Messaging") << "Malformed inventory offer from agent" << LL_ENDL; + delete info; + break; + } + bucketp = (struct offer_agent_bucket_t*) &binary_bucket[0]; + info->mType = (LLAssetType::EType) bucketp->asset_type; + info->mObjectID = bucketp->object_id; + info->mFromObject = false; + } + else // IM_TASK_INVENTORY_OFFERED + { + if (sizeof(S8) == binary_bucket_size) + { + info->mType = (LLAssetType::EType) binary_bucket[0]; + } + else + { + /*RIDER*/ // The previous version of the protocol returned the wrong binary bucket... we + // still might be able to figure out the type... even though the offer is not retrievable. + + // Should be safe to remove once DRTSIM-451 fully deploys + std::string str_bucket(reinterpret_cast(binary_bucket)); + std::string str_type(str_bucket.substr(0, str_bucket.find('|'))); + + std::stringstream type_convert(str_type); + + S32 type; + type_convert >> type; + + // We could try AT_UNKNOWN which would be more accurate, but that causes an auto decline + info->mType = static_cast(type); + // Don't break in the case of a bad binary bucket. Go ahead and show the + // accept/decline popup even though it will not do anything. + LL_WARNS("Messaging") << "Malformed inventory offer from object, type might be " << info->mType << LL_ENDL; + } + info->mObjectID = LLUUID::null; + info->mFromObject = true; + } + + info->mIM = dialog; + info->mFromID = from_id; + info->mFromGroup = from_group; + info->mFolderID = gInventory.findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(info->mType)); + + info->mTransactionID = session_id.notNull() ? session_id : aux_id; + + info->mFromName = name; + info->mDesc = message; + info->mHost = sender; + //if (((is_do_not_disturb && !is_owned_by_me) || is_muted)) + if (is_muted) + { + // Prefetch the offered item so that it can be discarded by the appropriate observer. (EXT-4331) + if (IM_INVENTORY_OFFERED == dialog) + { + LLInventoryFetchItemsObserver* fetch_item = new LLInventoryFetchItemsObserver(info->mObjectID); + fetch_item->startFetch(); + delete fetch_item; + // Same as closing window + info->forceResponse(IOR_DECLINE); + } + else + { + info->forceResponse(IOR_MUTE); + } + } + // old logic: busy mode must not affect interaction with objects (STORM-565) + // new logic: inventory offers from in-world objects should be auto-declined (CHUI-519) + else if (is_do_not_disturb && dialog == IM_TASK_INVENTORY_OFFERED) + { + // Until throttling is implemented, do not disturb mode should reject inventory instead of silently + // accepting it. SEE SL-39554 + info->forceResponse(IOR_DECLINE); + } + else + { + inventory_offer_handler(info); + } + } + break; + + case IM_INVENTORY_ACCEPTED: + { + args["NAME"] = LLSLURL("agent", from_id, "completename").getSLURLString();; + args["ORIGINAL_NAME"] = original_name; + LLSD payload; + payload["from_id"] = from_id; + // Passing the "SESSION_NAME" to use it for IM notification logging + // in LLTipHandler::processNotification(). See STORM-941. + payload["SESSION_NAME"] = name; + LLNotificationsUtil::add("InventoryAccepted", args, payload); + break; + } + case IM_INVENTORY_DECLINED: + { + args["NAME"] = LLSLURL("agent", from_id, "completename").getSLURLString();; + LLSD payload; + payload["from_id"] = from_id; + LLNotificationsUtil::add("InventoryDeclined", args, payload); + break; + } + // TODO: _DEPRECATED suffix as part of vote removal - DEV-24856 + case IM_GROUP_VOTE: + { + LL_WARNS("Messaging") << "Received IM: IM_GROUP_VOTE_DEPRECATED" << LL_ENDL; + } + break; + + case IM_GROUP_ELECTION_DEPRECATED: + { + LL_WARNS("Messaging") << "Received IM: IM_GROUP_ELECTION_DEPRECATED" << LL_ENDL; + } + break; + + case IM_FROM_TASK: + { + + if (is_do_not_disturb && !is_owned_by_me) + { + return; + } + + // Build a link to open the object IM info window. + std::string location = ll_safe_string((char*)binary_bucket, binary_bucket_size - 1); + + if (session_id.notNull()) + { + chat.mFromID = session_id; + } + else + { + // This message originated on a region without the updated code for task id and slurl information. + // We just need a unique ID for this object that isn't the owner ID. + // If it is the owner ID it will overwrite the style that contains the link to that owner's profile. + // This isn't ideal - it will make 1 style for all objects owned by the the same person/group. + // This works because the only thing we can really do in this case is show the owner name and link to their profile. + chat.mFromID = from_id ^ gAgent.getSessionID(); + } + + chat.mSourceType = CHAT_SOURCE_OBJECT; + + // To conclude that the source type of message is CHAT_SOURCE_SYSTEM it's not + // enough to check only from name (i.e. fromName = "Second Life"). For example + // source type of messages from objects called "Second Life" should not be CHAT_SOURCE_SYSTEM. + bool chat_from_system = (SYSTEM_FROM == name) && region_id.isNull() && position.isNull(); + if (chat_from_system) + { + // System's UUID is NULL (fixes EXT-4766) + chat.mFromID = LLUUID::null; + chat.mSourceType = CHAT_SOURCE_SYSTEM; + } + + // IDEVO Some messages have embedded resident names + message = clean_name_from_task_im(message, from_group); + + LLSD query_string; + query_string["owner"] = from_id; + query_string["slurl"] = location; + query_string["name"] = name; + if (from_group) + { + query_string["groupowned"] = "true"; + } + + chat.mURL = LLSLURL("objectim", session_id, "").getSLURLString(); + chat.mText = message; + + // Note: lie to Nearby Chat, pretending that this is NOT an IM, because + // IMs from obejcts don't open IM sessions. + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat"); + if (!chat_from_system && nearby_chat) + { + chat.mOwnerID = from_id; + LLSD args; + args["slurl"] = location; + + // Look for IRC-style emotes here so object name formatting is correct + std::string prefix = message.substr(0, 4); + if (prefix == "/me " || prefix == "/me'") + { + chat.mChatStyle = CHAT_STYLE_IRC; + } + + LLNotificationsUI::LLNotificationManager::instance().onChat(chat, args); + if (message != "") + { + LLSD msg_notify; + msg_notify["session_id"] = LLUUID(); + msg_notify["from_id"] = chat.mFromID; + msg_notify["source_type"] = chat.mSourceType; + on_new_message(msg_notify); + } + } + + + //Object IMs send with from name: 'Second Life' need to be displayed also in notification toasts (EXT-1590) + if (!chat_from_system) break; + + LLSD substitutions; + substitutions["NAME"] = name; + substitutions["MSG"] = message; + + LLSD payload; + payload["object_id"] = session_id; + payload["owner_id"] = from_id; + payload["from_id"] = from_id; + payload["slurl"] = location; + payload["name"] = name; + + if (from_group) + { + payload["group_owned"] = "true"; + } + + LLNotificationsUtil::add("ServerObjectMessage", substitutions, payload); + } + break; + + case IM_SESSION_SEND: // ad-hoc or group IMs + + // Only show messages if we have a session open (which + // should happen after you get an "invitation" + if (!gIMMgr->hasSession(session_id)) + { + return; + } + + else if (offline == IM_ONLINE && is_do_not_disturb) + { + + // return a standard "do not disturb" message, but only do it to online IM + // (i.e. not other auto responses and not store-and-forward IM) + if (!gIMMgr->hasSession(session_id)) + { + // if there is not a panel for this conversation (i.e. it is a new IM conversation + // initiated by the other party) then... + send_do_not_disturb_message(gMessageSystem, from_id, session_id); + } + + // now store incoming IM in chat history + + buffer = message; + + LL_DEBUGS("Messaging") << "message in dnd; session_id( " << session_id << " ), from_id( " << from_id << " )" << LL_ENDL; + + // add to IM panel, but do not bother the user + gIMMgr->addMessage( + session_id, + from_id, + name, + buffer, + IM_OFFLINE == offline, + ll_safe_string((char*)binary_bucket), + IM_SESSION_INVITE, + parent_estate_id, + region_id, + position, + false, // is_region_msg + timestamp); + } + else + { + // standard message, not from system + std::string saved; + if (offline == IM_OFFLINE) + { + saved = llformat("(Saved %s) ", formatted_time(timestamp).c_str()); + } + + buffer = saved + message; + + LL_DEBUGS("Messaging") << "standard message session_id( " << session_id << " ), from_id( " << from_id << " )" << LL_ENDL; + + gIMMgr->addMessage( + session_id, + from_id, + name, + buffer, + (IM_OFFLINE == offline), + ll_safe_string((char*)binary_bucket), // session name + IM_SESSION_INVITE, + parent_estate_id, + region_id, + position, + false, // is_region_msg + timestamp); + } + break; + + case IM_FROM_TASK_AS_ALERT: + if (is_do_not_disturb && !is_owned_by_me) + { + return; + } + { + // Construct a viewer alert for this message. + args["NAME"] = name; + args["MESSAGE"] = message; + LLNotificationsUtil::add("ObjectMessage", args); + } + break; + case IM_DO_NOT_DISTURB_AUTO_RESPONSE: + if (is_muted) + { + LL_DEBUGS("Messaging") << "Ignoring do-not-disturb response from " << from_id << LL_ENDL; + return; + } + else + { + gIMMgr->addMessage(session_id, from_id, name, message); + } + break; + + case IM_LURE_USER: + case IM_TELEPORT_REQUEST: + { + if (is_muted) + { + return; + } + else if (gSavedPerAccountSettings.getBOOL("VoiceCallsFriendsOnly") && (LLAvatarTracker::instance().getBuddyInfo(from_id) == NULL)) + { + return; + } + else + { + if (is_do_not_disturb) + { + send_do_not_disturb_message(gMessageSystem, from_id); + } + + LLVector3 pos, look_at; + U64 region_handle(0); + U8 region_access(SIM_ACCESS_MIN); + std::string region_info = ll_safe_string((char*)binary_bucket, binary_bucket_size); + std::string region_access_str = LLStringUtil::null; + std::string region_access_icn = LLStringUtil::null; + std::string region_access_lc = LLStringUtil::null; + + bool canUserAccessDstRegion = true; + bool doesUserRequireMaturityIncrease = false; + + // Do not parse the (empty) lure bucket for TELEPORT_REQUEST + if (IM_TELEPORT_REQUEST != dialog && parse_lure_bucket(region_info, region_handle, pos, look_at, region_access)) + { + region_access_str = LLViewerRegion::accessToString(region_access); + region_access_icn = LLViewerRegion::getAccessIcon(region_access); + region_access_lc = region_access_str; + LLStringUtil::toLower(region_access_lc); + + if (!gAgent.isGodlike()) + { + switch (region_access) + { + case SIM_ACCESS_MIN: + case SIM_ACCESS_PG: + break; + case SIM_ACCESS_MATURE: + if (gAgent.isTeen()) + { + canUserAccessDstRegion = false; + } + else if (gAgent.prefersPG()) + { + doesUserRequireMaturityIncrease = true; + } + break; + case SIM_ACCESS_ADULT: + if (!gAgent.isAdult()) + { + canUserAccessDstRegion = false; + } + else if (!gAgent.prefersAdult()) + { + doesUserRequireMaturityIncrease = true; + } + break; + default: + llassert(0); + break; + } + } + } + + LLSD args; + // *TODO: Translate -> [FIRST] [LAST] (maybe) + args["NAME_SLURL"] = LLSLURL("agent", from_id, "about").getSLURLString(); + args["MESSAGE"] = message; + args["MATURITY_STR"] = region_access_str; + args["MATURITY_ICON"] = region_access_icn; + args["REGION_CONTENT_MATURITY"] = region_access_lc; + LLSD payload; + payload["from_id"] = from_id; + payload["lure_id"] = session_id; + payload["godlike"] = false; + payload["region_maturity"] = region_access; + + if (!canUserAccessDstRegion) + { + LLNotification::Params params("TeleportOffered_MaturityBlocked"); + params.substitutions = args; + params.payload = payload; + LLPostponedNotification::add(params, from_id, false); + send_simple_im(from_id, LLTrans::getString("TeleportMaturityExceeded"), IM_NOTHING_SPECIAL, session_id); + send_simple_im(from_id, LLStringUtil::null, IM_LURE_DECLINED, session_id); + } + else if (doesUserRequireMaturityIncrease) + { + LLNotification::Params params("TeleportOffered_MaturityExceeded"); + params.substitutions = args; + params.payload = payload; + LLPostponedNotification::add(params, from_id, false); + } + else + { + LLNotification::Params params; + if (IM_LURE_USER == dialog) + { + params.name = "TeleportOffered"; + params.functor.name = "TeleportOffered"; + } + else if (IM_TELEPORT_REQUEST == dialog) + { + params.name = "TeleportRequest"; + params.functor.name = "TeleportRequest"; + } + + params.substitutions = args; + params.payload = payload; + LLPostponedNotification::add(params, from_id, false); + } + } + } + break; + + case IM_GODLIKE_LURE_USER: + { + LLVector3 pos, look_at; + U64 region_handle(0); + U8 region_access(SIM_ACCESS_MIN); + std::string region_info = ll_safe_string((char*)binary_bucket, binary_bucket_size); + std::string region_access_str = LLStringUtil::null; + std::string region_access_icn = LLStringUtil::null; + std::string region_access_lc = LLStringUtil::null; + + bool canUserAccessDstRegion = true; + bool doesUserRequireMaturityIncrease = false; + + if (parse_lure_bucket(region_info, region_handle, pos, look_at, region_access)) + { + region_access_str = LLViewerRegion::accessToString(region_access); + region_access_icn = LLViewerRegion::getAccessIcon(region_access); + region_access_lc = region_access_str; + LLStringUtil::toLower(region_access_lc); + + if (!gAgent.isGodlike()) + { + switch (region_access) + { + case SIM_ACCESS_MIN: + case SIM_ACCESS_PG: + break; + case SIM_ACCESS_MATURE: + if (gAgent.isTeen()) + { + canUserAccessDstRegion = false; + } + else if (gAgent.prefersPG()) + { + doesUserRequireMaturityIncrease = true; + } + break; + case SIM_ACCESS_ADULT: + if (!gAgent.isAdult()) + { + canUserAccessDstRegion = false; + } + else if (!gAgent.prefersAdult()) + { + doesUserRequireMaturityIncrease = true; + } + break; + default: + llassert(0); + break; + } + } + } + + LLSD args; + // *TODO: Translate -> [FIRST] [LAST] (maybe) + args["NAME_SLURL"] = LLSLURL("agent", from_id, "about").getSLURLString(); + args["MESSAGE"] = message; + args["MATURITY_STR"] = region_access_str; + args["MATURITY_ICON"] = region_access_icn; + args["REGION_CONTENT_MATURITY"] = region_access_lc; + LLSD payload; + payload["from_id"] = from_id; + payload["lure_id"] = session_id; + payload["godlike"] = true; + payload["region_maturity"] = region_access; + + if (!canUserAccessDstRegion) + { + LLNotification::Params params("TeleportOffered_MaturityBlocked"); + params.substitutions = args; + params.payload = payload; + LLPostponedNotification::add(params, from_id, false); + send_simple_im(from_id, LLTrans::getString("TeleportMaturityExceeded"), IM_NOTHING_SPECIAL, session_id); + send_simple_im(from_id, LLStringUtil::null, IM_LURE_DECLINED, session_id); + } + else if (doesUserRequireMaturityIncrease) + { + LLNotification::Params params("TeleportOffered_MaturityExceeded"); + params.substitutions = args; + params.payload = payload; + LLPostponedNotification::add(params, from_id, false); + } + else + { + // do not show a message box, because you're about to be + // teleported. + LLNotifications::instance().forceResponse(LLNotification::Params("TeleportOffered").payload(payload), 0); + } + } + break; + + case IM_GOTO_URL: + { + LLSD args; + // n.b. this is for URLs sent by the system, not for + // URLs sent by scripts (i.e. llLoadURL) + if (binary_bucket_size <= 0) + { + LL_WARNS("Messaging") << "bad binary_bucket_size: " + << binary_bucket_size + << " - aborting function." << LL_ENDL; + return; + } + + std::string url; + + url.assign((char*)binary_bucket, binary_bucket_size - 1); + args["MESSAGE"] = message; + args["URL"] = url; + LLSD payload; + payload["url"] = url; + LLNotificationsUtil::add("GotoURL", args, payload); + } + break; + + case IM_FRIENDSHIP_OFFERED: + { + LLSD payload; + payload["from_id"] = from_id; + payload["session_id"] = session_id;; + payload["online"] = (offline == IM_ONLINE); + payload["sender"] = sender.getIPandPort(); + + bool add_notification = true; + for (auto& panel : LLToastNotifyPanel::instance_snapshot()) + { + const std::string& notification_name = panel.getNotificationName(); + if (notification_name == "OfferFriendship" && panel.isControlPanelEnabled()) + { + add_notification = false; + break; + } + } + + if (is_muted && add_notification) + { + LLNotifications::instance().forceResponse(LLNotification::Params("OfferFriendship").payload(payload), 1); + } + else + { + if (is_do_not_disturb) + { + send_do_not_disturb_message(gMessageSystem, from_id); + } + args["NAME_SLURL"] = LLSLURL("agent", from_id, "about").getSLURLString(); + + if (add_notification) + { + if (message.empty()) + { + //support for frienship offers from clients before July 2008 + LLNotificationsUtil::add("OfferFriendshipNoMessage", args, payload); + } + else + { + args["[MESSAGE]"] = message; + LLNotification::Params params("OfferFriendship"); + params.substitutions = args; + params.payload = payload; + LLPostponedNotification::add(params, from_id, false); + } + } + } + } + break; + + case IM_FRIENDSHIP_ACCEPTED: + { + // In the case of an offline IM, the formFriendship() may be extraneous + // as the database should already include the relationship. But it + // doesn't hurt for dupes. + LLAvatarTracker::formFriendship(from_id); + + std::vector strings; + strings.push_back(from_id.asString()); + send_generic_message("requestonlinenotification", strings); + + args["NAME"] = name; + LLSD payload; + payload["from_id"] = from_id; + LLAvatarNameCache::get(from_id, boost::bind(¬ification_display_name_callback, _1, _2, "FriendshipAccepted", args, payload)); + } + break; + + case IM_FRIENDSHIP_DECLINED_DEPRECATED: + default: + LL_WARNS("Messaging") << "Instant message calling for unknown dialog " + << (S32)dialog << LL_ENDL; + break; + } + + LLWindow* viewer_window = gViewerWindow->getWindow(); + if (viewer_window && viewer_window->getMinimized()) + { + viewer_window->flashIcon(5.f); + } +} + +void LLIMProcessing::requestOfflineMessages() +{ + static bool requested = false; + if (!requested + && gMessageSystem + && !gDisconnected + && LLMuteList::getInstance()->isLoaded() + && isAgentAvatarValid() + && gAgent.getRegion() + && gAgent.getRegion()->capabilitiesReceived()) + { + std::string cap_url = gAgent.getRegionCapability("ReadOfflineMsgs"); + + // Auto-accepted inventory items may require the avatar object + // to build a correct name. Likewise, inventory offers from + // muted avatars require the mute list to properly mute. + if (cap_url.empty() + || gAgent.getRegionCapability("AcceptFriendship").empty() + || gAgent.getRegionCapability("AcceptGroupInvite").empty()) + { + // Offline messages capability provides no session/transaction ids for message AcceptFriendship and IM_GROUP_INVITATION to work + // So make sure we have the caps before using it. + requestOfflineMessagesLegacy(); + } + else + { + LLCoros::instance().launch("LLIMProcessing::requestOfflineMessagesCoro", + boost::bind(&LLIMProcessing::requestOfflineMessagesCoro, cap_url)); + } + requested = true; + } +} + +void LLIMProcessing::requestOfflineMessagesCoro(std::string url) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("requestOfflineMessagesCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD result = httpAdapter->getAndSuspend(httpRequest, url); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) // success = httpResults["success"].asBoolean(); + { + LL_WARNS("Messaging") << "Error requesting offline messages via capability " << url << ", Status: " << status.toString() << "\nFalling back to legacy method." << LL_ENDL; + + requestOfflineMessagesLegacy(); + return; + } + + LLSD contents = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT]; + + if (!contents.size()) + { + LL_WARNS("Messaging") << "No contents received for offline messages via capability " << url << LL_ENDL; + return; + } + + // Todo: once dirtsim-369 releases, remove one of the map/array options + LLSD messages; + if (contents.isArray()) + { + messages = *contents.beginArray(); + } + else if (contents.has("messages")) + { + messages = contents["messages"]; + } + else + { + LL_WARNS("Messaging") << "Invalid offline message content received via capability " << url << LL_ENDL; + return; + } + + if (!messages.isArray()) + { + LL_WARNS("Messaging") << "Invalid offline message content received via capability " << url << LL_ENDL; + return; + } + + if (messages.size() == 0) + { + // Nothing to process + return; + } + + if (!gAgent.getRegion()) + { + LL_WARNS("Messaging") << "Region null while attempting to load messages." << LL_ENDL; + return; + } + + LL_INFOS("Messaging") << "Processing offline messages." << LL_ENDL; + + LLHost sender = gAgent.getRegionHost(); + + LLSD::array_iterator i = messages.beginArray(); + LLSD::array_iterator iEnd = messages.endArray(); + for (; i != iEnd; ++i) + { + const LLSD &message_data(*i); + + /* RIDER: Many fields in this message are using a '_' rather than the standard '-'. This + * should be changed but would require tight coordination with the simulator. + */ + LLVector3 position; + if (message_data.has("position")) + { + position.setValue(message_data["position"]); + } + else + { + position.set(message_data["local_x"].asReal(), message_data["local_y"].asReal(), message_data["local_z"].asReal()); + } + + std::vector bin_bucket; + if (message_data.has("binary_bucket")) + { + bin_bucket = message_data["binary_bucket"].asBinary(); + } + else + { + bin_bucket.push_back(0); + } + + // Todo: once drtsim-451 releases, remove the string option + bool from_group; + if (message_data["from_group"].isInteger()) + { + from_group = message_data["from_group"].asInteger(); + } + else + { + from_group = message_data["from_group"].asString() == "Y"; + } + + EInstantMessage dialog = static_cast(message_data["dialog"].asInteger()); + LLUUID session_id = message_data["transaction-id"].asUUID(); + if (session_id.isNull() && dialog == IM_FROM_TASK) + { + session_id = message_data["asset_id"].asUUID(); + } + LLIMProcessing::processNewMessage( + message_data["from_agent_id"].asUUID(), + from_group, + message_data["to_agent_id"].asUUID(), + message_data.has("offline") ? static_cast(message_data["offline"].asInteger()) : IM_OFFLINE, + dialog, + session_id, + static_cast(message_data["timestamp"].asInteger()), + message_data["from_agent_name"].asString(), + message_data["message"].asString(), + static_cast((message_data.has("parent_estate_id")) ? message_data["parent_estate_id"].asInteger() : 1), // 1 - IMMainland + message_data["region_id"].asUUID(), + position, + bin_bucket.data(), + bin_bucket.size(), + sender, + message_data["asset_id"].asUUID()); + + } +} + +void LLIMProcessing::requestOfflineMessagesLegacy() +{ + LL_INFOS("Messaging") << "Requesting offline messages (Legacy)." << LL_ENDL; + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_RetrieveInstantMessages); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + gAgent.sendReliableMessage(); +} + diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 30ee4d7e67..62bc95db82 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -1,4204 +1,4204 @@ -/** - * @file LLIMMgr.cpp - * @brief Container for Instant Messaging - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llimview.h" - -#include "llavatarnamecache.h" // IDEVO -#include "llavataractions.h" -#include "llfloaterconversationlog.h" -#include "llfloaterreg.h" -#include "llfontgl.h" -#include "llgl.h" -#include "llrect.h" -#include "llerror.h" -#include "llbutton.h" -#include "llsdutil_math.h" -#include "llstring.h" -#include "lltextutil.h" -#include "lltrans.h" -#include "lltranslate.h" -#include "lluictrlfactory.h" -#include "llfloaterimsessiontab.h" -#include "llagent.h" -#include "llagentui.h" -#include "llappviewer.h" -#include "llavatariconctrl.h" -#include "llcallingcard.h" -#include "llchat.h" -#include "llfloaterimsession.h" -#include "llfloaterimcontainer.h" -#include "llgroupiconctrl.h" -#include "llmd5.h" -#include "llmutelist.h" -#include "llrecentpeople.h" -#include "llviewermessage.h" -#include "llviewerwindow.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "llfloaterimnearbychat.h" -#include "llspeakers.h" //for LLIMSpeakerMgr -#include "lltextbox.h" -#include "lltoolbarview.h" -#include "llviewercontrol.h" -#include "llviewerparcelmgr.h" -#include "llconversationlog.h" -#include "message.h" -#include "llviewerregion.h" -#include "llcorehttputil.h" -#include "lluiusage.h" - -#include - -const static std::string ADHOC_NAME_SUFFIX(" Conference"); - -const static std::string NEARBY_P2P_BY_OTHER("nearby_P2P_by_other"); -const static std::string NEARBY_P2P_BY_AGENT("nearby_P2P_by_agent"); - -// Markers inserted around translated part of chat text -const static std::string XL8_START_TAG(" ("); -const static std::string XL8_END_TAG(")"); -const S32 XL8_PADDING = 3; // XL8_START_TAG.size() + XL8_END_TAG.size() - -/** Timeout of outgoing session initialization (in seconds) */ -const static U32 SESSION_INITIALIZATION_TIMEOUT = 30; - -void startConfrenceCoro(std::string url, LLUUID tempSessionId, LLUUID creatorId, LLUUID otherParticipantId, LLSD agents); -void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvitationType invitationType); -void chatterBoxHistoryCoro(std::string url, LLUUID sessionId, std::string from, std::string message, U32 timestamp); -void start_deprecated_conference_chat(const LLUUID& temp_session_id, const LLUUID& creator_id, const LLUUID& other_participant_id, const LLSD& agents_to_invite); - -const LLUUID LLOutgoingCallDialog::OCD_KEY = LLUUID("7CF78E11-0CFE-498D-ADB9-1417BF03DDB4"); -// -// Globals -// -LLIMMgr* gIMMgr = NULL; - - -bool LLSessionTimeoutTimer::tick() -{ - if (mSessionId.isNull()) return true; - - LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(mSessionId); - if (session && !session->mSessionInitialized) - { - gIMMgr->showSessionStartError("session_initialization_timed_out_error", mSessionId); - } - return true; -} - - -void notify_of_message(const LLSD& msg, bool is_dnd_msg); - -void process_dnd_im(const LLSD& notification) -{ - LLSD data = notification["substitutions"]; - LLUUID sessionID = data["SESSION_ID"].asUUID(); - LLUUID fromID = data["FROM_ID"].asUUID(); - - //re-create the IM session if needed - //(when coming out of DND mode upon app restart) - if(!gIMMgr->hasSession(sessionID)) - { - //reconstruct session using data from the notification - std::string name = data["FROM"]; - LLAvatarName av_name; - if (LLAvatarNameCache::get(data["FROM_ID"], &av_name)) - { - name = av_name.getDisplayName(); - } - - - LLIMModel::getInstance()->newSession(sessionID, - name, - IM_NOTHING_SPECIAL, - fromID, - false, - false); //will need slight refactor to retrieve whether offline message or not (assume online for now) - } - - notify_of_message(data, true); -} - - -static void on_avatar_name_cache_toast(const LLUUID& agent_id, - const LLAvatarName& av_name, - LLSD msg) -{ - LLSD args; - args["MESSAGE"] = msg["message"]; - args["TIME"] = msg["time"]; - // *TODO: Can this ever be an object name or group name? - args["FROM"] = av_name.getCompleteName(); - args["FROM_ID"] = msg["from_id"]; - args["SESSION_ID"] = msg["session_id"]; - args["SESSION_TYPE"] = msg["session_type"]; - LLNotificationsUtil::add("IMToast", args, args, boost::bind(&LLFloaterIMContainer::showConversation, LLFloaterIMContainer::getInstance(), msg["session_id"].asUUID())); -} - -void notify_of_message(const LLSD& msg, bool is_dnd_msg) -{ - std::string user_preferences; - LLUUID participant_id = msg[is_dnd_msg ? "FROM_ID" : "from_id"].asUUID(); - LLUUID session_id = msg[is_dnd_msg ? "SESSION_ID" : "session_id"].asUUID(); - LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(session_id); - - // do not show notification which goes from agent - if (gAgent.getID() == participant_id) - { - return; - } - - // determine state of conversations floater - enum {CLOSED, NOT_ON_TOP, ON_TOP, ON_TOP_AND_ITEM_IS_SELECTED} conversations_floater_status; - - - LLFloaterIMContainer* im_box = LLFloaterReg::getTypedInstance("im_container"); - LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::getConversation(session_id); - bool store_dnd_message = false; // flag storage of a dnd message - bool is_session_focused = session_floater->isTornOff() && session_floater->hasFocus(); - if (!LLFloater::isVisible(im_box) || im_box->isMinimized()) - { - conversations_floater_status = CLOSED; - } - else if (!im_box->hasFocus() && - !(session_floater && LLFloater::isVisible(session_floater) - && !session_floater->isMinimized() && session_floater->hasFocus())) - { - conversations_floater_status = NOT_ON_TOP; - } - else if (im_box->getSelectedSession() != session_id) - { - conversations_floater_status = ON_TOP; - } - else - { - conversations_floater_status = ON_TOP_AND_ITEM_IS_SELECTED; - } - - // determine user prefs for this session - if (session_id.isNull()) - { - if (msg["source_type"].asInteger() == CHAT_SOURCE_OBJECT) - { - user_preferences = gSavedSettings.getString("NotificationObjectIMOptions"); - if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundObjectIM"))) - { - make_ui_sound("UISndNewIncomingIMSession"); - } - } - else - { - user_preferences = gSavedSettings.getString("NotificationNearbyChatOptions"); - if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNearbyChatIM"))) - { - make_ui_sound("UISndNewIncomingIMSession"); - } - } - } - else if (session->isP2PSessionType()) - { - if (LLAvatarTracker::instance().isBuddy(participant_id)) - { - user_preferences = gSavedSettings.getString("NotificationFriendIMOptions"); - if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundFriendIM"))) - { - make_ui_sound("UISndNewIncomingIMSession"); - } - } - else - { - user_preferences = gSavedSettings.getString("NotificationNonFriendIMOptions"); - if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNonFriendIM"))) - { - make_ui_sound("UISndNewIncomingIMSession"); - } - } - } - else if (session->isAdHocSessionType()) - { - user_preferences = gSavedSettings.getString("NotificationConferenceIMOptions"); - if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundConferenceIM"))) - { - make_ui_sound("UISndNewIncomingIMSession"); - } - } - else if(session->isGroupSessionType()) - { - user_preferences = gSavedSettings.getString("NotificationGroupChatOptions"); - if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundGroupChatIM"))) - { - make_ui_sound("UISndNewIncomingIMSession"); - } - } - - // actions: - - // 0. nothing - exit - if (("noaction" == user_preferences || - ON_TOP_AND_ITEM_IS_SELECTED == conversations_floater_status) - && session_floater->isMessagePaneExpanded()) - { - return; - } - - // 1. open floater and [optional] surface it - if ("openconversations" == user_preferences && - (CLOSED == conversations_floater_status - || NOT_ON_TOP == conversations_floater_status)) - { - if(!gAgent.isDoNotDisturb()) - { - if(!LLAppViewer::instance()->quitRequested() && !LLFloater::isVisible(im_box)) - { - // Open conversations floater - LLFloaterReg::showInstance("im_container"); - } - im_box->collapseMessagesPane(false); - if (session_floater) - { - if (session_floater->getHost()) - { - if (NULL != im_box && im_box->isMinimized()) - { - LLFloater::onClickMinimize(im_box); - } - } - else - { - if (session_floater->isMinimized()) - { - LLFloater::onClickMinimize(session_floater); - } - } - } - } - else - { - store_dnd_message = true; - } - } - - // 2. Flash line item - if ("openconversations" == user_preferences - || ON_TOP == conversations_floater_status - || ("toast" == user_preferences && ON_TOP != conversations_floater_status) - || ("flash" == user_preferences && (CLOSED == conversations_floater_status - || NOT_ON_TOP == conversations_floater_status)) - || is_dnd_msg) - { - if(!LLMuteList::getInstance()->isMuted(participant_id)) - { - if(gAgent.isDoNotDisturb()) - { - store_dnd_message = true; - } - else - { - if (is_dnd_msg && (ON_TOP == conversations_floater_status || - NOT_ON_TOP == conversations_floater_status || - CLOSED == conversations_floater_status)) - { - im_box->highlightConversationItemWidget(session_id, true); - } - else - { - im_box->flashConversationItemWidget(session_id, true); - } - } - } - } - - // 3. Flash FUI button - if (("toast" == user_preferences || "flash" == user_preferences) && - (CLOSED == conversations_floater_status - || NOT_ON_TOP == conversations_floater_status) - && !is_session_focused - && !is_dnd_msg) //prevent flashing FUI button because the conversation floater will have already opened - { - if(!LLMuteList::getInstance()->isMuted(participant_id)) - { - if(!gAgent.isDoNotDisturb()) - { - gToolBarView->flashCommand(LLCommandId("chat"), true, im_box->isMinimized()); - } - else - { - store_dnd_message = true; - } - } - } - - // 4. Toast - if ((("toast" == user_preferences) && - (ON_TOP_AND_ITEM_IS_SELECTED != conversations_floater_status) && - (!session_floater->isTornOff() || !LLFloater::isVisible(session_floater))) - || !session_floater->isMessagePaneExpanded()) - - { - //Show IM toasts (upper right toasts) - // Skip toasting for system messages and for nearby chat - if(session_id.notNull() && participant_id.notNull()) - { - if(!is_dnd_msg) - { - if(gAgent.isDoNotDisturb()) - { - store_dnd_message = true; - } - else - { - LLAvatarNameCache::get(participant_id, boost::bind(&on_avatar_name_cache_toast, _1, _2, msg)); - } - } -} - } - if (store_dnd_message) - { - // If in DND mode, allow notification to be stored so upon DND exit - // the user will be notified with some limitations (see 'is_dnd_msg' flag checks) - if(session_id.notNull() - && participant_id.notNull() - && !session_floater->isShown()) - { - LLAvatarNameCache::get(participant_id, boost::bind(&on_avatar_name_cache_toast, _1, _2, msg)); - } - } -} - -void on_new_message(const LLSD& msg) -{ - notify_of_message(msg, false); -} - -void startConfrenceCoro(std::string url, - LLUUID tempSessionId, LLUUID creatorId, LLUUID otherParticipantId, LLSD agents) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ConferenceChatStart", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD postData; - postData["method"] = "start conference"; - postData["session-id"] = tempSessionId; - postData["params"] = agents; - - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS("LLIMModel") << "Failed to start conference" << LL_ENDL; - //try an "old school" way. - // *TODO: What about other error status codes? 4xx 5xx? - if (status == LLCore::HttpStatus(HTTP_BAD_REQUEST)) - { - start_deprecated_conference_chat( - tempSessionId, - creatorId, - otherParticipantId, - agents); - } - - //else throw an error back to the client? - //in theory we should have just have these error strings - //etc. set up in this file as opposed to the IMMgr, - //but the error string were unneeded here previously - //and it is not worth the effort switching over all - //the possible different language translations - } -} - -void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvitationType invitationType) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ConferenceInviteStart", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD postData; - postData["method"] = "accept invitation"; - postData["session-id"] = sessionId; - - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!gIMMgr) - { - LL_WARNS("") << "Global IM Manager is NULL" << LL_ENDL; - return; - } - - if (!status) - { - LL_WARNS("LLIMModel") << "Bad HTTP response in chatterBoxInvitationCoro" << LL_ENDL; - //throw something back to the viewer here? - - gIMMgr->clearPendingAgentListUpdates(sessionId); - gIMMgr->clearPendingInvitation(sessionId); - - if (status == LLCore::HttpStatus(HTTP_NOT_FOUND)) - { - static const std::string error_string("session_does_not_exist_error"); - gIMMgr->showSessionStartError(error_string, sessionId); - } - return; - } - - result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); - - LLIMSpeakerMgr* speakerMgr = LLIMModel::getInstance()->getSpeakerManager(sessionId); - if (speakerMgr) - { - //we've accepted our invitation - //and received a list of agents that were - //currently in the session when the reply was sent - //to us. Now, it is possible that there were some agents - //to slip in/out between when that message was sent to us - //and now. - - //the agent list updates we've received have been - //accurate from the time we were added to the session - //but unfortunately, our base that we are receiving here - //may not be the most up to date. It was accurate at - //some point in time though. - speakerMgr->setSpeakers(result); - - //we now have our base of users in the session - //that was accurate at some point, but maybe not now - //so now we apply all of the updates we've received - //in case of race conditions - speakerMgr->updateSpeakers(gIMMgr->getPendingAgentListUpdates(sessionId)); - } - - if (LLIMMgr::INVITATION_TYPE_VOICE == invitationType) - { - gIMMgr->startCall(sessionId, LLVoiceChannel::INCOMING_CALL); - } - - if ((invitationType == LLIMMgr::INVITATION_TYPE_VOICE - || invitationType == LLIMMgr::INVITATION_TYPE_IMMEDIATE) - && LLIMModel::getInstance()->findIMSession(sessionId)) - { - // TODO remove in 2010, for voice calls we do not open an IM window - //LLFloaterIMSession::show(mSessionID); - } - - gIMMgr->clearPendingAgentListUpdates(sessionId); - gIMMgr->clearPendingInvitation(sessionId); - -} - -void translateSuccess(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text, - U64 time_n_flags, std::string originalMsg, std::string expectLang, std::string translation, const std::string detected_language) -{ - std::string message_txt(utf8_text); - // filter out non-interesting responses - if (!translation.empty() - && ((detected_language.empty()) || (expectLang != detected_language)) - && (LLStringUtil::compareInsensitive(translation, originalMsg) != 0)) - { // Note - if this format changes, also fix code in addMessagesFromServerHistory() - message_txt += XL8_START_TAG + LLTranslate::removeNoTranslateTags(translation) + XL8_END_TAG; - } - - // Extract info packed in time_n_flags - bool log2file = (bool)(time_n_flags & (1LL << 32)); - bool is_region_msg = (bool)(time_n_flags & (1LL << 33)); - U32 time_stamp = (U32)(time_n_flags & 0x00000000ffffffff); - - LLIMModel::getInstance()->processAddingMessage(session_id, from, from_id, message_txt, log2file, is_region_msg, time_stamp); -} - -void translateFailure(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text, - U64 time_n_flags, int status, const std::string err_msg) -{ - std::string message_txt(utf8_text); - std::string msg = LLTrans::getString("TranslationFailed", LLSD().with("[REASON]", err_msg)); - LLStringUtil::replaceString(msg, "\n", " "); // we want one-line error messages - message_txt += XL8_START_TAG + msg + XL8_END_TAG; - - // Extract info packed in time_n_flags - bool log2file = (bool)(time_n_flags & (1LL << 32)); - bool is_region_msg = (bool)(time_n_flags & (1LL << 33)); - U32 time_stamp = (U32)(time_n_flags & 0x00000000ffffffff); - - LLIMModel::getInstance()->processAddingMessage(session_id, from, from_id, message_txt, log2file, is_region_msg, time_stamp); -} - -void chatterBoxHistoryCoro(std::string url, LLUUID sessionId, std::string from, std::string message, U32 timestamp) -{ // if parameters from, message and timestamp have values, they are a message that opened chat - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ChatHistory", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD postData; - postData["method"] = "fetch history"; - postData["session-id"] = sessionId; - - LL_DEBUGS("ChatHistory") << sessionId << ": Chat history posting " << postData << " to " << url - << ", from " << from << ", message " << message << ", timestamp " << (S32)timestamp << LL_ENDL; - - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS("ChatHistory") << sessionId << ": Bad HTTP response in chatterBoxHistoryCoro" - << ", results: " << httpResults << LL_ENDL; - return; - } - - if (LLApp::isExiting() || gDisconnected) - { - LL_DEBUGS("ChatHistory") << "Ignoring chat history response, shutting down" << LL_ENDL; - return; - } - - // Add history to IM session - LLSD history = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT]; - - LL_DEBUGS("ChatHistory") << sessionId << ": Chat server history fetch returned " << history << LL_ENDL; - - try - { - LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(sessionId); - if (session && history.isArray()) - { // Result array is sorted oldest to newest - if (history.size() > 0) - { // History from the chat server has an integer 'time' value timestamp. Create 'datetime' string which will match - // what we have from the local history cache - for (LLSD::array_iterator cur_server_hist = history.beginArray(), endLists = history.endArray(); - cur_server_hist != endLists; - cur_server_hist++) - { - if ((*cur_server_hist).isMap()) - { // Take the 'time' value from the server and make the date-time string that will be in local cache log files - // {'from_id':u7aa8c222-8a81-450e-b3d1-9c28491ef717,'message':'Can you hear me now?','from':'Chat Tester','num':i86,'time':r1.66501e+09} - U32 timestamp = (U32)((*cur_server_hist)[LL_IM_TIME].asInteger()); - (*cur_server_hist)[LL_IM_DATE_TIME] = LLLogChat::timestamp2LogString(timestamp, true); - } - } - - session->addMessagesFromServerHistory(history, from, message, timestamp); - - // Display the newly added messages - LLFloaterIMSession* floater = LLFloaterReg::findTypedInstance("impanel", sessionId); - if (floater && floater->isInVisibleChain()) - { - floater->updateMessages(); - } - } - else - { - LL_DEBUGS("ChatHistory") << sessionId << ": Empty history from chat server, nothing to add" << LL_ENDL; - } - } - else if (session && !history.isArray()) - { - LL_WARNS("ChatHistory") << sessionId << ": Bad array data fetching chat history" << LL_ENDL; - } - else - { - LL_WARNS("ChatHistory") << sessionId << ": Unable to find session fetching chat history" << LL_ENDL; - } - } - catch (...) - { - LOG_UNHANDLED_EXCEPTION("chatterBoxHistoryCoro"); - LL_WARNS("ChatHistory") << "chatterBoxHistoryCoro unhandled exception while processing data for session " << sessionId << LL_ENDL; - } -} - -LLIMModel::LLIMModel() -{ - addNewMsgCallback(boost::bind(&LLFloaterIMSession::newIMCallback, _1)); - addNewMsgCallback(boost::bind(&on_new_message, _1)); - LLCallDialogManager::instance(); -} - -LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice, bool has_offline_msg) -: mSessionID(session_id), - mName(name), - mType(type), - mHasOfflineMessage(has_offline_msg), - mParticipantUnreadMessageCount(0), - mNumUnread(0), - mOtherParticipantID(other_participant_id), - mInitialTargetIDs(ids), - mVoiceChannel(NULL), - mSpeakers(NULL), - mSessionInitialized(false), - mCallBackEnabled(true), - mTextIMPossible(true), - mStartCallOnInitialize(false), - mStartedAsIMCall(voice), - mIsDNDsend(false), - mAvatarNameCacheConnection() -{ - // set P2P type by default - mSessionType = P2P_SESSION; - - if (IM_NOTHING_SPECIAL == mType || IM_SESSION_P2P_INVITE == mType) - { - mVoiceChannel = new LLVoiceChannelP2P(session_id, name, other_participant_id); - } - else - { - mVoiceChannel = new LLVoiceChannelGroup(session_id, name); - - // determine whether it is group or conference session - if (gAgent.isInGroup(mSessionID)) - { - mSessionType = GROUP_SESSION; - } - else - { - mSessionType = ADHOC_SESSION; - } - } - - if(mVoiceChannel) - { - mVoiceChannelStateChangeConnection = mVoiceChannel->setStateChangedCallback(boost::bind(&LLIMSession::onVoiceChannelStateChanged, this, _1, _2, _3)); - } - - mSpeakers = new LLIMSpeakerMgr(mVoiceChannel); - - // All participants will be added to the list of people we've recently interacted with. - - // we need to add only _active_ speakers...so comment this. - // may delete this later on cleanup - //mSpeakers->addListener(&LLRecentPeople::instance(), "add"); - - //we need to wait for session initialization for outgoing ad-hoc and group chat session - //correct session id for initiated ad-hoc chat will be received from the server - if (!LLIMModel::getInstance()->sendStartSession(mSessionID, mOtherParticipantID, - mInitialTargetIDs, mType)) - { - //we don't need to wait for any responses - //so we're already initialized - mSessionInitialized = true; - } - else - { - //tick returns true - timer will be deleted after the tick - new LLSessionTimeoutTimer(mSessionID, SESSION_INITIALIZATION_TIMEOUT); - } - - if (IM_NOTHING_SPECIAL == mType) - { - mCallBackEnabled = LLVoiceClient::getInstance()->isSessionCallBackPossible(mSessionID); - mTextIMPossible = LLVoiceClient::getInstance()->isSessionTextIMPossible(mSessionID); - } - - buildHistoryFileName(); - loadHistory(); - - // Localizing name of ad-hoc session. STORM-153 - // Changing name should happen here- after the history file was created, so that - // history files have consistent (English) names in different locales. - if (isAdHocSessionType() && IM_SESSION_INVITE == mType) - { - mAvatarNameCacheConnection = LLAvatarNameCache::get(mOtherParticipantID,boost::bind(&LLIMModel::LLIMSession::onAdHocNameCache,this, _2)); - } -} - -void LLIMModel::LLIMSession::onAdHocNameCache(const LLAvatarName& av_name) -{ - mAvatarNameCacheConnection.disconnect(); - - if (!av_name.isValidName()) - { - S32 separator_index = mName.rfind(" "); - std::string name = mName.substr(0, separator_index); - ++separator_index; - std::string conference_word = mName.substr(separator_index, mName.length()); - - // additional check that session name is what we expected - if ("Conference" == conference_word) - { - LLStringUtil::format_map_t args; - args["[AGENT_NAME]"] = name; - LLTrans::findString(mName, "conference-title-incoming", args); - } - } - else - { - LLStringUtil::format_map_t args; - args["[AGENT_NAME]"] = av_name.getCompleteName(); - LLTrans::findString(mName, "conference-title-incoming", args); - } -} - -void LLIMModel::LLIMSession::onVoiceChannelStateChanged(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state, const LLVoiceChannel::EDirection& direction) -{ - std::string you_joined_call = LLTrans::getString("you_joined_call"); - std::string you_started_call = LLTrans::getString("you_started_call"); - std::string other_avatar_name = ""; - LLAvatarName av_name; - - std::string message; - - switch(mSessionType) - { - case P2P_SESSION: - LLAvatarNameCache::get(mOtherParticipantID, &av_name); - other_avatar_name = av_name.getUserName(); - - if(direction == LLVoiceChannel::INCOMING_CALL) - { - switch(new_state) - { - case LLVoiceChannel::STATE_CALL_STARTED : - { - LLStringUtil::format_map_t string_args; - string_args["[NAME]"] = other_avatar_name; - message = LLTrans::getString("name_started_call", string_args); - LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, message); - break; - } - case LLVoiceChannel::STATE_CONNECTED : - LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, you_joined_call); - default: - break; - } - } - else // outgoing call - { - switch(new_state) - { - case LLVoiceChannel::STATE_CALL_STARTED : - LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, you_started_call); - break; - case LLVoiceChannel::STATE_CONNECTED : - message = LLTrans::getString("answered_call"); - LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, message); - default: - break; - } - } - break; - - case GROUP_SESSION: - case ADHOC_SESSION: - if(direction == LLVoiceChannel::INCOMING_CALL) - { - switch(new_state) - { - case LLVoiceChannel::STATE_CONNECTED : - LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, you_joined_call); - default: - break; - } - } - else // outgoing call - { - switch(new_state) - { - case LLVoiceChannel::STATE_CALL_STARTED : - LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, you_started_call); - break; - default: - break; - } - } - default: - break; - } - // Update speakers list when connected - if (LLVoiceChannel::STATE_CONNECTED == new_state) - { - mSpeakers->update(true); - } -} - -LLIMModel::LLIMSession::~LLIMSession() -{ - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - - delete mSpeakers; - mSpeakers = NULL; - - // End the text IM session if necessary - if(LLVoiceClient::getInstance() && mOtherParticipantID.notNull()) - { - switch(mType) - { - case IM_NOTHING_SPECIAL: - case IM_SESSION_P2P_INVITE: - LLVoiceClient::getInstance()->endUserIMSession(mOtherParticipantID); - break; - - default: - // Appease the linux compiler - break; - } - } - - mVoiceChannelStateChangeConnection.disconnect(); - - // HAVE to do this here -- if it happens in the LLVoiceChannel destructor it will call the wrong version (since the object's partially deconstructed at that point). - mVoiceChannel->deactivate(); - - delete mVoiceChannel; - mVoiceChannel = NULL; -} - -void LLIMModel::LLIMSession::sessionInitReplyReceived(const LLUUID& new_session_id) -{ - mSessionInitialized = true; - - if (new_session_id != mSessionID) - { - mSessionID = new_session_id; - mVoiceChannel->updateSessionID(new_session_id); - } -} - -void LLIMModel::LLIMSession::addMessage(const std::string& from, - const LLUUID& from_id, - const std::string& utf8_text, - const std::string& time, - const bool is_history, // comes from a history file or chat server - const bool is_region_msg, - const U32 timestamp) // may be zero -{ - LLSD message; - message["from"] = from; - message["from_id"] = from_id; - message["message"] = utf8_text; - message["time"] = time; // string used in display, may be full data YYYY/MM/DD HH:MM or just HH:MM - message["timestamp"] = (S32)timestamp; // use string? LLLogChat::timestamp2LogString(timestamp, true); - message["index"] = (LLSD::Integer)mMsgs.size(); - message["is_history"] = is_history; - message["is_region_msg"] = is_region_msg; - - LL_DEBUGS("UIUsage") << "addMessage " << " from " << from << " from_id " << from_id << " utf8_text " << utf8_text << " time " << time << " is_history " << is_history << " session mType " << mType << LL_ENDL; - if (from_id == gAgent.getID()) - { - if (mType == IM_SESSION_GROUP_START) - { - LLUIUsage::instance().logCommand("Chat.SendGroup"); - } - else if (mType == IM_NOTHING_SPECIAL) - { - LLUIUsage::instance().logCommand("Chat.SendIM"); - } - else - { - LLUIUsage::instance().logCommand("Chat.SendOther"); - } - } - - mMsgs.push_front(message); // Add most recent messages to the front of mMsgs - - if (mSpeakers && from_id.notNull()) - { - mSpeakers->speakerChatted(from_id); - mSpeakers->setSpeakerTyping(from_id, false); - } -} - -void LLIMModel::LLIMSession::addMessagesFromHistoryCache(const chat_message_list_t& history) -{ - // Add the messages from the local cached chat history to the session window - for (const auto& msg : history) - { - std::string from = msg[LL_IM_FROM]; - LLUUID from_id; - if (msg[LL_IM_FROM_ID].isDefined()) - { - from_id = msg[LL_IM_FROM_ID].asUUID(); - } - else - { // convert it to a legacy name if we have a complete name - std::string legacy_name = gCacheName->buildLegacyName(from); - from_id = LLAvatarNameCache::getInstance()->findIdByName(legacy_name); - } - - // Save the last minute of messages so we can merge with the chat server history. - // Really would be nice to have a numeric timestamp in the local cached chat file - const std::string & msg_time_str = msg[LL_IM_DATE_TIME].asString(); - if (mLastHistoryCacheDateTime != msg_time_str) - { - mLastHistoryCacheDateTime = msg_time_str; // Reset to the new time - mLastHistoryCacheMsgs.clear(); - } - mLastHistoryCacheMsgs.push_front(msg); - LL_DEBUGS("ChatHistory") << mSessionID << ": Adding history cache message: " << msg << LL_ENDL; - - // Add message from history cache to the display - addMessage(from, from_id, msg[LL_IM_TEXT], msg[LL_IM_TIME], true, false, 0); // from history data, not region message, no timestamp - } -} - -void LLIMModel::LLIMSession::addMessagesFromServerHistory(const LLSD& history, // Array of chat messages from chat server - const std::string& target_from, // Sender of message that opened chat - const std::string& target_message, // Message text that opened chat - U32 timestamp) // timestamp of message that opened chat -{ // Add messages from history returned by the chat server. - - // The session mMsgs may contain chat messages from the local history cache file, and possibly one or more newly - // arrived chat messages. If the chat window was manually opened, these will be empty and history can - // more easily merged. The history from the server, however, may overlap what is in the file and those must also be merged. - - // At this point, the session mMsgs can have - // no messages - // nothing from history file cache, but one or more very recently arrived messages, - // messages from history file cache, no recent chat - // messages from history file cache, one or more very recent messages - // - // The chat history from server can possibly contain: - // no messages - // messages that start back before anything in the local file (obscure case, but possible) - // messages that match messages from the history file cache - // messages from the last hour, new to the viewer - // one or more messages that match most recently received chat (the one that opened the window) - // In other words: - // messages from chat server may or may not match what we already have in mMsgs - // We can drop anything that is during the time span covered by the local cache file - // To keep things simple, drop any chat data older than the local cache file - - if (!history.isArray()) - { - LL_WARNS("ChatHistory") << mSessionID << ": Unexpected history data not array, type " << (S32)history.type() << LL_ENDL; - return; - } - - if (history.size() == 0) - { // If history is empty - LL_DEBUGS("ChatHistory") << mSessionID << ": addMessagesFromServerHistory() has empty history, nothing to merge" << LL_ENDL; - return; - } - - if (history.size() == 1 && // Server chat history has one entry, - target_from.length() > 0 && // and we have a chat message that just arrived - mMsgs.size() > 0) // and we have some data in the window - assume the history message is there. - { // This is the common case where a group chat is silent for a while, and then one message is sent. - LL_DEBUGS("ChatHistory") << mSessionID << ": addMessagesFromServerHistory() only has chat message just received." << LL_ENDL; - return; - } - - LL_DEBUGS("ChatHistory") << mSessionID << ": addMessagesFromServerHistory() starting with mMsg.size() " << mMsgs.size() - << " adding history with " << history.size() << " messages" - << ", target_from: " << target_from - << ", target_message: " << target_message - << ", timestamp: " << (S32)timestamp << LL_ENDL; - - // At start of merging, mMsgs is either empty, has some chat messages read from a local cache file, and may have - // one or more messages that just arrived from the server. - U32 match_timestamp = 0; - chat_message_list_t shift_msgs; - if (mMsgs.size() > 0 && - target_from.length() > 0 - && target_message.length() > 0) - { // Find where to insert the history messages by popping off a few in the session. - // The most common case is one duplciate message, the one that opens a chat session - while (mMsgs.size() > 0) - { - // The "time" value from mMsgs is a string, either just time HH:MM or a full date and time - LLSD cur_msg = mMsgs.front(); // Get most recent message from the chat display (front of mMsgs list) - - if (cur_msg.isMap()) - { - LL_DEBUGS("ChatHistoryCompare") << mSessionID << ": Finding insertion point, looking at cur_msg: " << cur_msg << LL_ENDL; - - match_timestamp = cur_msg["timestamp"].asInteger(); // get timestamp of message in the session, may be zero - if ((S32)timestamp > match_timestamp) - { - LL_DEBUGS("ChatHistory") << mSessionID << ": found older chat message: " << cur_msg - << ", timestamp " << (S32)timestamp - << " vs. match_timestamp " << match_timestamp - << ", shift_msgs size is " << shift_msgs.size() << LL_ENDL; - break; - } - // Have the matching message or one more recent: these need to be at the end - shift_msgs.push_front(cur_msg); // Move chat message to temp list. - mMsgs.pop_front(); // Normally this is just one message - LL_DEBUGS("ChatHistory") << mSessionID << ": shifting chat message " << cur_msg - << " to be inserted at end, shift_msgs size is " << shift_msgs.size() - << ", match_timestamp " << match_timestamp - << ", timestamp " << (S32)timestamp << LL_ENDL; - } - else - { - LL_DEBUGS("ChatHistory") << mSessionID << ": Unexpected non-map entry in session messages: " << cur_msg << LL_ENDL; - return; - } - } - } - - // Now merge messages from server history data into the session display. The history data - // from the local file may overlap with the chat messages from the server. - // Drop any messages from the chat server history that are before the latest one from the local history file. - // Unfortunately, messages from the local file don't have timestamps - just datetime strings - LLSD::array_const_iterator cur_history_iter = history.beginArray(); - while (cur_history_iter != history.endArray()) - { - const LLSD &cur_server_hist = *cur_history_iter; - cur_history_iter++; - - if (cur_server_hist.isMap()) - { // Each server history entry looks like - // { 'from':'Laggy Avatar', 'from_id' : u72345678 - 744f - 43b9 - 98af - b06f1c76ddda, 'index' : i24, 'is_history' : 1, 'message' : 'That was slow', 'time' : '02/13/2023 10:03', 'timestamp' : i1676311419 } - - // If we reach the message that opened our window, stop adding messages - U32 history_msg_timestamp = (U32)cur_server_hist[LL_IM_TIME].asInteger(); - if ((match_timestamp > 0 && match_timestamp <= history_msg_timestamp) || - (timestamp > 0 && timestamp <= history_msg_timestamp)) - { // we found the message we matched, so stop inserting from chat server history - LL_DEBUGS("ChatHistoryCompare") << "Found end of chat history insertion with match_timestamp " << (S32)match_timestamp - << " vs. history_msg_timestamp " << (S32)history_msg_timestamp - << " vs. timestamp " << (S32)timestamp - << LL_ENDL; - break; - } - LL_DEBUGS("ChatHistoryCompare") << "Compared match_timestamp " << (S32)match_timestamp - << " vs. history_msg_timestamp " << (S32)history_msg_timestamp << LL_ENDL; - - bool add_chat_to_conversation = true; - if (!mLastHistoryCacheDateTime.empty()) - { // Skip past the any from server that are older than what we already read from the history file. - std::string history_datetime = cur_server_hist[LL_IM_DATE_TIME].asString(); - if (history_datetime.empty()) - { - history_datetime = cur_server_hist[LL_IM_TIME].asString(); - } - - if (history_datetime < mLastHistoryCacheDateTime) - { - LL_DEBUGS("ChatHistoryCompare") << "Skipping message from chat server history since it's older than messages the session already has." - << history_datetime << " vs " << mLastHistoryCacheDateTime << LL_ENDL; - add_chat_to_conversation = false; - } - else if (history_datetime > mLastHistoryCacheDateTime) - { // The message from the chat server is more recent than the last one from the local cache file. Add it - LL_DEBUGS("ChatHistoryCompare") << "Found message dated " - << history_datetime << " vs " << mLastHistoryCacheDateTime - << ", adding new message from chat server history " << cur_server_hist << LL_ENDL; - } - else // (history_datetime == mLastHistoryCacheDateTime) - { // Messages are in the same minute as the last from the cache log file. - const std::string & history_msg_text = cur_server_hist[LL_IM_TEXT]; - - // Look in the saved messages from the history file that have the same time - for (const auto& scan_msg : mLastHistoryCacheMsgs) - { - LL_DEBUGS("ChatHistoryCompare") << "comparing messages " << scan_msg[LL_IM_TEXT] - << " with " << cur_server_hist << LL_ENDL; - if (scan_msg.size() > 0) - { // Extra work ... the history_msg_text value may have been translated, i.e. "I am confused (je suis confus)" - // while the server history will only have the first part "I am confused" - std::string target_compare(scan_msg[LL_IM_TEXT]); - if (target_compare.size() > history_msg_text.size() + XL8_PADDING && - target_compare.substr(history_msg_text.size(), XL8_START_TAG.size()) == XL8_START_TAG && - target_compare.substr(target_compare.size() - XL8_END_TAG.size()) == XL8_END_TAG) - { // This really looks like a "translated string (cadena traducida)" so just compare the source part - LL_DEBUGS("ChatHistory") << mSessionID << ": Found translated chat " << target_compare - << " when comparing to history " << history_msg_text - << ", will truncate" << LL_ENDL; - target_compare = target_compare.substr(0, history_msg_text.size()); - } - if (history_msg_text == target_compare) - { // Found a match, so don't add a duplicate chat message to the window - LL_DEBUGS("ChatHistory") << mSessionID << ": Found duplicate message text " << history_msg_text - << " : " << (S32)history_msg_timestamp << ", matching datetime " << history_datetime << LL_ENDL; - add_chat_to_conversation = false; - break; - } - } - } - } - } - - LLUUID sender_id = cur_server_hist[LL_IM_FROM_ID].asUUID(); - if (add_chat_to_conversation) - { // Check if they're muted - if (LLMuteList::getInstance()->isMuted(sender_id, LLMute::flagTextChat)) - { - add_chat_to_conversation = false; - LL_DEBUGS("ChatHistory") << mSessionID << ": Skipped adding chat from " << sender_id - << " as muted, message: " << cur_server_hist - << LL_ENDL; - } - } - - if (add_chat_to_conversation) - { // Finally add message to the chat session - std::string chat_time_str = LLConversation::createTimestamp((U64Seconds)history_msg_timestamp); - std::string sender_name = cur_server_hist[LL_IM_FROM].asString(); - - std::string history_msg_text = cur_server_hist[LL_IM_TEXT].asString(); - LLSD message; - message["from"] = sender_name; - message["from_id"] = sender_id; - message["message"] = history_msg_text; - message["time"] = chat_time_str; - message["timestamp"] = (S32)history_msg_timestamp; - message["index"] = (LLSD::Integer)mMsgs.size(); - message["is_history"] = true; - mMsgs.push_front(message); - - LL_DEBUGS("ChatHistory") << mSessionID << ": push_front() adding group chat history message " << message << LL_ENDL; - - // Add chat history messages to the local cache file, only in the case where we opened the chat window - // Need to solve the logic around messages that arrive and open chat - at this point, they've already been added to the - // local history cache file. If we append messages here, it will be out of order. - if (target_from.empty() && target_message.empty()) - { - LLIMModel::getInstance()->logToFile(LLIMModel::getInstance()->getHistoryFileName(mSessionID), - sender_name, sender_id, history_msg_text); - } - } - } - } - - S32 shifted_size = shift_msgs.size(); - while (shift_msgs.size() > 0) - { // Finally add back any new messages, and tweak the index value to be correct. - LLSD newer_message = shift_msgs.front(); - shift_msgs.pop_front(); - S32 old_index = newer_message["index"]; - newer_message["index"] = (LLSD::Integer)mMsgs.size(); // Update the index to match the new position in the conversation - LL_DEBUGS("ChatHistory") << mSessionID << ": Re-adding newest group chat history messages from " << newer_message["from"] - << ", text: " << newer_message["message"] - << " old index " << old_index << ", new index " << newer_message["index"] << LL_ENDL; - mMsgs.push_front(newer_message); - } - - LL_DEBUGS("ChatHistory") << mSessionID << ": addMessagesFromServerHistory() exiting with mMsg.size() " << mMsgs.size() - << ", shifted " << shifted_size << " messages" << LL_ENDL; - - mLastHistoryCacheDateTime.clear(); // Don't need this data - mLastHistoryCacheMsgs.clear(); -} - - -void LLIMModel::LLIMSession::chatFromLogFile(LLLogChat::ELogLineType type, const LLSD& msg, void* userdata) -{ - if (!userdata) return; - - LLIMSession* self = (LLIMSession*) userdata; - - if (type == LLLogChat::LOG_LINE) - { - LL_DEBUGS("ChatHistory") << "chatFromLogFile() adding LOG_LINE message from " << msg << LL_ENDL; - self->addMessage("", LLSD(), msg["message"].asString(), "", true, false, 0); // from history data, not region message, no timestamp - } - else if (type == LLLogChat::LOG_LLSD) - { - LL_DEBUGS("ChatHistory") << "chatFromLogFile() adding LOG_LLSD message from " << msg << LL_ENDL; - self->addMessage(msg["from"].asString(), msg["from_id"].asUUID(), msg["message"].asString(), msg["time"].asString(), true, false, 0); // from history data, not region message, no timestamp - } -} - -void LLIMModel::LLIMSession::loadHistory() -{ - mMsgs.clear(); - mLastHistoryCacheMsgs.clear(); - mLastHistoryCacheDateTime.clear(); - - if ( gSavedPerAccountSettings.getBOOL("LogShowHistory") ) - { - // read and parse chat history from local file - chat_message_list_t chat_history; - LLLogChat::loadChatHistory(mHistoryFileName, chat_history, LLSD(), isGroupChat()); - addMessagesFromHistoryCache(chat_history); - } -} - -LLIMModel::LLIMSession* LLIMModel::findIMSession(const LLUUID& session_id) const -{ - return get_if_there(mId2SessionMap, session_id, (LLIMModel::LLIMSession*) NULL); -} - -//*TODO consider switching to using std::set instead of std::list for holding LLUUIDs across the whole code -LLIMModel::LLIMSession* LLIMModel::findAdHocIMSession(const uuid_vec_t& ids) -{ - S32 num = ids.size(); - if (!num) return NULL; - - if (mId2SessionMap.empty()) return NULL; - - std::map::const_iterator it = mId2SessionMap.begin(); - for (; it != mId2SessionMap.end(); ++it) - { - LLIMSession* session = (*it).second; - - if (!session->isAdHoc()) continue; - if (session->mInitialTargetIDs.size() != num) continue; - - std::list tmp_list(session->mInitialTargetIDs.begin(), session->mInitialTargetIDs.end()); - - uuid_vec_t::const_iterator iter = ids.begin(); - while (iter != ids.end()) - { - tmp_list.remove(*iter); - ++iter; - - if (tmp_list.empty()) - { - break; - } - } - - if (tmp_list.empty() && iter == ids.end()) - { - return session; - } - } - - return NULL; -} - -bool LLIMModel::LLIMSession::isOutgoingAdHoc() const -{ - return IM_SESSION_CONFERENCE_START == mType; -} - -bool LLIMModel::LLIMSession::isAdHoc() -{ - return IM_SESSION_CONFERENCE_START == mType || (IM_SESSION_INVITE == mType && !gAgent.isInGroup(mSessionID, true)); -} - -bool LLIMModel::LLIMSession::isP2P() -{ - return IM_NOTHING_SPECIAL == mType; -} - -bool LLIMModel::LLIMSession::isGroupChat() -{ - return IM_SESSION_GROUP_START == mType || (IM_SESSION_INVITE == mType && gAgent.isInGroup(mSessionID, true)); -} - -LLUUID LLIMModel::LLIMSession::generateOutgoingAdHocHash() const -{ - LLUUID hash = LLUUID::null; - - if (mInitialTargetIDs.size()) - { - std::set sorted_uuids(mInitialTargetIDs.begin(), mInitialTargetIDs.end()); - hash = generateHash(sorted_uuids); - } - - return hash; -} - -void LLIMModel::LLIMSession::buildHistoryFileName() -{ - mHistoryFileName = mName; - - //ad-hoc requires sophisticated chat history saving schemes - if (isAdHoc()) - { - /* in case of outgoing ad-hoc sessions we need to make specilized names - * if this naming system is ever changed then the filtering definitions in - * lllogchat.cpp need to be change acordingly so that the filtering for the - * date stamp code introduced in STORM-102 will work properly and not add - * a date stamp to the Ad-hoc conferences. - */ - if (mInitialTargetIDs.size()) - { - std::set sorted_uuids(mInitialTargetIDs.begin(), mInitialTargetIDs.end()); - mHistoryFileName = mName + " hash" + generateHash(sorted_uuids).asString(); - } - else - { - //in case of incoming ad-hoc sessions - mHistoryFileName = mName + " " + LLLogChat::timestamp2LogString(0, true) + " " + mSessionID.asString().substr(0, 4); - } - } - else if (isP2P()) // look up username to use as the log name - { - LLAvatarName av_name; - // For outgoing sessions we already have a cached name - // so no need for a callback in LLAvatarNameCache::get() - if (LLAvatarNameCache::get(mOtherParticipantID, &av_name)) - { - mHistoryFileName = LLCacheName::buildUsername(av_name.getUserName()); - } - else - { - // Incoming P2P sessions include a name that we can use to build a history file name - mHistoryFileName = LLCacheName::buildUsername(mName); - } - - // user's account name can change, but filenames and session names are account name based - LLConversationLog::getInstance()->verifyFilename(mSessionID, mHistoryFileName, av_name.getCompleteName()); - } - else if (isGroupChat()) - { - mHistoryFileName = mName + GROUP_CHAT_SUFFIX; - } -} - -//static -LLUUID LLIMModel::LLIMSession::generateHash(const std::set& sorted_uuids) -{ - LLMD5 md5_uuid; - - std::set::const_iterator it = sorted_uuids.begin(); - while (it != sorted_uuids.end()) - { - md5_uuid.update((unsigned char*)(*it).mData, 16); - it++; - } - md5_uuid.finalize(); - - LLUUID participants_md5_hash; - md5_uuid.raw_digest((unsigned char*) participants_md5_hash.mData); - return participants_md5_hash; -} - -void LLIMModel::processSessionInitializedReply(const LLUUID& old_session_id, const LLUUID& new_session_id) -{ - LLIMSession* session = findIMSession(old_session_id); - if (session) - { - session->sessionInitReplyReceived(new_session_id); - - if (old_session_id != new_session_id) - { - mId2SessionMap.erase(old_session_id); - mId2SessionMap[new_session_id] = session; - } - - LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(old_session_id); - if (im_floater) - { - im_floater->sessionInitReplyReceived(new_session_id); - } - - if (old_session_id != new_session_id) - { - gIMMgr->notifyObserverSessionIDUpdated(old_session_id, new_session_id); - } - - // auto-start the call on session initialization? - if (session->mStartCallOnInitialize) - { - gIMMgr->startCall(new_session_id); - } - } -} - -void LLIMModel::testMessages() -{ - LLUUID bot1_id("d0426ec6-6535-4c11-a5d9-526bb0c654d9"); - LLUUID bot1_session_id; - std::string from = "IM Tester"; - - bot1_session_id = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, bot1_id); - newSession(bot1_session_id, from, IM_NOTHING_SPECIAL, bot1_id); - addMessage(bot1_session_id, from, bot1_id, "Test Message: Hi from testerbot land!"); - - LLUUID bot2_id; - std::string firstname[] = {"Roflcopter", "Joe"}; - std::string lastname[] = {"Linden", "Tester", "Resident", "Schmoe"}; - - S32 rand1 = ll_rand(sizeof firstname)/(sizeof firstname[0]); - S32 rand2 = ll_rand(sizeof lastname)/(sizeof lastname[0]); - - from = firstname[rand1] + " " + lastname[rand2]; - bot2_id.generate(from); - LLUUID bot2_session_id = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, bot2_id); - newSession(bot2_session_id, from, IM_NOTHING_SPECIAL, bot2_id); - addMessage(bot2_session_id, from, bot2_id, "Test Message: Hello there, I have a question. Can I bother you for a second? "); - addMessage(bot2_session_id, from, bot2_id, "Test Message: OMGWTFBBQ."); -} - -//session name should not be empty -bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, - const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice, bool has_offline_msg) -{ - if (name.empty()) - { - LL_WARNS() << "Attempt to create a new session with empty name; id = " << session_id << LL_ENDL; - return false; - } - - if (findIMSession(session_id)) - { - LL_WARNS() << "IM Session " << session_id << " already exists" << LL_ENDL; - return false; - } - - LLIMSession* session = new LLIMSession(session_id, name, type, other_participant_id, ids, voice, has_offline_msg); - mId2SessionMap[session_id] = session; - - // When notifying observer, name of session is used instead of "name", because they may not be the - // same if it is an adhoc session (in this case name is localized in LLIMSession constructor). - std::string session_name = LLIMModel::getInstance()->getName(session_id); - LLIMMgr::getInstance()->notifyObserverSessionAdded(session_id, session_name, other_participant_id,has_offline_msg); - - return true; - -} - -bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, bool voice, bool has_offline_msg) -{ - uuid_vec_t ids; - ids.push_back(other_participant_id); - return newSession(session_id, name, type, other_participant_id, ids, voice, has_offline_msg); -} - -bool LLIMModel::clearSession(const LLUUID& session_id) -{ - if (mId2SessionMap.find(session_id) == mId2SessionMap.end()) return false; - delete (mId2SessionMap[session_id]); - mId2SessionMap.erase(session_id); - return true; -} - -void LLIMModel::getMessages(const LLUUID& session_id, chat_message_list_t& messages, int start_index, const bool sendNoUnreadMsgs) -{ - getMessagesSilently(session_id, messages, start_index); - - if (sendNoUnreadMsgs) - { - sendNoUnreadMessages(session_id); - } -} - -void LLIMModel::getMessagesSilently(const LLUUID& session_id, chat_message_list_t& messages, int start_index) -{ - LLIMSession* session = findIMSession(session_id); - if (!session) - { - LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; - return; - } - - int i = session->mMsgs.size() - start_index; - - for (chat_message_list_t::iterator iter = session->mMsgs.begin(); - iter != session->mMsgs.end() && i > 0; - iter++) - { - LLSD msg; - msg = *iter; - messages.push_back(*iter); - i--; - } -} - -void LLIMModel::sendNoUnreadMessages(const LLUUID& session_id) -{ - LLIMSession* session = findIMSession(session_id); - if (!session) - { - LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; - return; - } - - session->mNumUnread = 0; - session->mParticipantUnreadMessageCount = 0; - - LLSD arg; - arg["session_id"] = session_id; - arg["num_unread"] = 0; - arg["participant_unread"] = session->mParticipantUnreadMessageCount; - mNoUnreadMsgsSignal(arg); -} - -bool LLIMModel::addToHistory(const LLUUID& session_id, - const std::string& from, - const LLUUID& from_id, - const std::string& utf8_text, - bool is_region_msg, - U32 timestamp) -{ - LLIMSession* session = findIMSession(session_id); - - if (!session) - { - LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; - return false; - } - - // This is where a normal arriving message is added to the session. Note that the time string created here is without the full date - session->addMessage(from, from_id, utf8_text, LLLogChat::timestamp2LogString(timestamp, false), false, is_region_msg, timestamp); - - return true; -} - -bool LLIMModel::logToFile(const std::string& file_name, const std::string& from, const LLUUID& from_id, const std::string& utf8_text) -{ - if (gSavedPerAccountSettings.getS32("KeepConversationLogTranscripts") > 1) - { - std::string from_name = from; - - LLAvatarName av_name; - if (!from_id.isNull() && - LLAvatarNameCache::get(from_id, &av_name) && - !av_name.isDisplayNameDefault()) - { - from_name = av_name.getCompleteName(); - } - - LLLogChat::saveHistory(file_name, from_name, from_id, utf8_text); - LLConversationLog::instance().cache(); // update the conversation log too - return true; - } - else - { - return false; - } -} - -void LLIMModel::proccessOnlineOfflineNotification( - const LLUUID& session_id, - const std::string& utf8_text) -{ - // Add system message to history - addMessage(session_id, SYSTEM_FROM, LLUUID::null, utf8_text); -} - -void LLIMModel::addMessage(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, - const std::string& utf8_text, bool log2file /* = true */, bool is_region_msg, /* = false */ U32 time_stamp /* = 0 */) -{ - if (gSavedSettings.getBOOL("TranslateChat") && (from != SYSTEM_FROM)) - { - const std::string from_lang = ""; // leave empty to trigger autodetect - const std::string to_lang = LLTranslate::getTranslateLanguage(); - U64 time_n_flags = ((U64) time_stamp) | (log2file ? (1LL << 32) : 0) | (is_region_msg ? (1LL << 33) : 0); // boost::bind has limited parameters - LLTranslate::translateMessage(from_lang, to_lang, utf8_text, - boost::bind(&translateSuccess, session_id, from, from_id, utf8_text, time_n_flags, utf8_text, from_lang, _1, _2), - boost::bind(&translateFailure, session_id, from, from_id, utf8_text, time_n_flags, _1, _2)); - } - else - { - processAddingMessage(session_id, from, from_id, utf8_text, log2file, is_region_msg, time_stamp); - } -} - -void LLIMModel::processAddingMessage(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, - const std::string& utf8_text, bool log2file, bool is_region_msg, U32 time_stamp) -{ - LLIMSession* session = addMessageSilently(session_id, from, from_id, utf8_text, log2file, is_region_msg, time_stamp); - if (!session) - return; - - //good place to add some1 to recent list - //other places may be called from message history. - if( !from_id.isNull() && - ( session->isP2PSessionType() || session->isAdHocSessionType() ) ) - LLRecentPeople::instance().add(from_id); - - // notify listeners - LLSD arg; - arg["session_id"] = session_id; - arg["num_unread"] = session->mNumUnread; - arg["participant_unread"] = session->mParticipantUnreadMessageCount; - arg["message"] = utf8_text; - arg["from"] = from; - arg["from_id"] = from_id; - arg["time"] = LLLogChat::timestamp2LogString(time_stamp, true); - arg["session_type"] = session->mSessionType; - arg["is_region_msg"] = is_region_msg; - - mNewMsgSignal(arg); -} - -LLIMModel::LLIMSession* LLIMModel::addMessageSilently(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, - const std::string& utf8_text, bool log2file /* = true */, bool is_region_msg, /* false */ - U32 timestamp /* = 0 */) -{ - LLIMSession* session = findIMSession(session_id); - - if (!session) - { - return NULL; - } - - // replace interactive system message marker with correct from string value - std::string from_name = from; - if (INTERACTIVE_SYSTEM_FROM == from) - { - from_name = SYSTEM_FROM; - } - - addToHistory(session_id, from_name, from_id, utf8_text, is_region_msg, timestamp); - if (log2file) - { - logToFile(getHistoryFileName(session_id), from_name, from_id, utf8_text); - } - - session->mNumUnread++; - - //update count of unread messages from real participant - if (!(from_id.isNull() || from_id == gAgentID || SYSTEM_FROM == from) - // we should increment counter for interactive system messages() - || INTERACTIVE_SYSTEM_FROM == from) - { - ++(session->mParticipantUnreadMessageCount); - } - - return session; -} - - -const std::string LLIMModel::getName(const LLUUID& session_id) const -{ - LLIMSession* session = findIMSession(session_id); - - if (!session) - { - LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; - return LLTrans::getString("no_session_message"); - } - - return session->mName; -} - -const S32 LLIMModel::getNumUnread(const LLUUID& session_id) const -{ - LLIMSession* session = findIMSession(session_id); - if (!session) - { - LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; - return -1; - } - - return session->mNumUnread; -} - -const LLUUID& LLIMModel::getOtherParticipantID(const LLUUID& session_id) const -{ - LLIMSession* session = findIMSession(session_id); - if (!session) - { - LL_WARNS() << "session " << session_id << " does not exist " << LL_ENDL; - return LLUUID::null; - } - - return session->mOtherParticipantID; -} - -EInstantMessage LLIMModel::getType(const LLUUID& session_id) const -{ - LLIMSession* session = findIMSession(session_id); - if (!session) - { - LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; - return IM_COUNT; - } - - return session->mType; -} - -LLVoiceChannel* LLIMModel::getVoiceChannel( const LLUUID& session_id ) const -{ - LLIMSession* session = findIMSession(session_id); - if (!session) - { - LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; - return NULL; - } - - return session->mVoiceChannel; -} - -LLIMSpeakerMgr* LLIMModel::getSpeakerManager( const LLUUID& session_id ) const -{ - LLIMSession* session = findIMSession(session_id); - if (!session) - { - LL_WARNS() << "session " << session_id << " does not exist " << LL_ENDL; - return NULL; - } - - return session->mSpeakers; -} - -const std::string& LLIMModel::getHistoryFileName(const LLUUID& session_id) const -{ - LLIMSession* session = findIMSession(session_id); - if (!session) - { - LL_WARNS() << "session " << session_id << " does not exist " << LL_ENDL; - return LLStringUtil::null; - } - - return session->mHistoryFileName; -} - - -// TODO get rid of other participant ID -void LLIMModel::sendTypingState(LLUUID session_id, LLUUID other_participant_id, bool typing) -{ - std::string name; - LLAgentUI::buildFullname(name); - - pack_instant_message( - gMessageSystem, - gAgent.getID(), - false, - gAgent.getSessionID(), - other_participant_id, - name, - std::string("typing"), - IM_ONLINE, - (typing ? IM_TYPING_START : IM_TYPING_STOP), - session_id); - gAgent.sendReliableMessage(); -} - -void LLIMModel::sendLeaveSession(const LLUUID& session_id, const LLUUID& other_participant_id) -{ - if(session_id.notNull()) - { - std::string name; - LLAgentUI::buildFullname(name); - pack_instant_message( - gMessageSystem, - gAgent.getID(), - false, - gAgent.getSessionID(), - other_participant_id, - name, - LLStringUtil::null, - IM_ONLINE, - IM_SESSION_LEAVE, - session_id); - gAgent.sendReliableMessage(); - } -} - -//*TODO this method is better be moved to the LLIMMgr -void LLIMModel::sendMessage(const std::string& utf8_text, - const LLUUID& im_session_id, - const LLUUID& other_participant_id, - EInstantMessage dialog) -{ - std::string name; - bool sent = false; - LLAgentUI::buildFullname(name); - - const LLRelationship* info = NULL; - info = LLAvatarTracker::instance().getBuddyInfo(other_participant_id); - - U8 offline = (!info || info->isOnline()) ? IM_ONLINE : IM_OFFLINE; - // Old call to send messages to SLim client, no longer supported. - //if((offline == IM_OFFLINE) && (LLVoiceClient::getInstance()->isOnlineSIP(other_participant_id))) - //{ - // // User is online through the OOW connector, but not with a regular viewer. Try to send the message via SLVoice. - // sent = LLVoiceClient::getInstance()->sendTextMessage(other_participant_id, utf8_text); - //} - - if(!sent) - { - // Send message normally. - - // default to IM_SESSION_SEND unless it's nothing special - in - // which case it's probably an IM to everyone. - U8 new_dialog = dialog; - - if ( dialog != IM_NOTHING_SPECIAL ) - { - new_dialog = IM_SESSION_SEND; - } - pack_instant_message( - gMessageSystem, - gAgent.getID(), - false, - gAgent.getSessionID(), - other_participant_id, - name.c_str(), - utf8_text.c_str(), - offline, - (EInstantMessage)new_dialog, - im_session_id); - gAgent.sendReliableMessage(); - } - - bool is_group_chat = false; - LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(im_session_id); - if(session) - { - is_group_chat = session->isGroupSessionType(); - } - - // If there is a mute list and this is not a group chat... - if ( LLMuteList::getInstance() && !is_group_chat) - { - // ... the target should not be in our mute list for some message types. - // Auto-remove them if present. - switch( dialog ) - { - case IM_NOTHING_SPECIAL: - case IM_GROUP_INVITATION: - case IM_INVENTORY_OFFERED: - case IM_SESSION_INVITE: - case IM_SESSION_P2P_INVITE: - case IM_SESSION_CONFERENCE_START: - case IM_SESSION_SEND: // This one is marginal - erring on the side of hearing. - case IM_LURE_USER: - case IM_GODLIKE_LURE_USER: - case IM_FRIENDSHIP_OFFERED: - LLMuteList::getInstance()->autoRemove(other_participant_id, LLMuteList::AR_IM); - break; - default: ; // do nothing - } - } - - if((dialog == IM_NOTHING_SPECIAL) && - (other_participant_id.notNull())) - { - // Do we have to replace the /me's here? - std::string from; - LLAgentUI::buildFullname(from); - LLIMModel::getInstance()->addMessage(im_session_id, from, gAgentID, utf8_text); - - //local echo for the legacy communicate panel - std::string history_echo; - LLAgentUI::buildFullname(history_echo); - - history_echo += ": " + utf8_text; - - LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(im_session_id); - if (speaker_mgr) - { - speaker_mgr->speakerChatted(gAgentID); - speaker_mgr->setSpeakerTyping(gAgentID, false); - } - } - - // Add the recipient to the recent people list. - bool is_not_group_id = LLGroupMgr::getInstance()->getGroupData(other_participant_id) == NULL; - - if (is_not_group_id) - { - if( session == 0)//??? shouldn't really happen - { - LLRecentPeople::instance().add(other_participant_id); - return; - } - // IM_SESSION_INVITE means that this is an Ad-hoc incoming chat - // (it can be also Group chat but it is checked above) - // In this case mInitialTargetIDs contains Ad-hoc session ID and it should not be added - // to Recent People to prevent showing of an item with (?? ?)(?? ?), sans the spaces. See EXT-8246. - // Concrete participants will be added into this list once they sent message in chat. - if (IM_SESSION_INVITE == dialog) return; - - if (IM_SESSION_CONFERENCE_START == dialog) // outgoing ad-hoc session - { - // Add only online members of conference to recent list (EXT-8658) - addSpeakersToRecent(im_session_id); - } - else // outgoing P2P session - { - // Add the recepient of the session. - if (!session->mInitialTargetIDs.empty()) - { - LLRecentPeople::instance().add(*(session->mInitialTargetIDs.begin())); - } - } - } -} - -void LLIMModel::addSpeakersToRecent(const LLUUID& im_session_id) -{ - LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(im_session_id); - LLSpeakerMgr::speaker_list_t speaker_list; - if(speaker_mgr != NULL) - { - speaker_mgr->getSpeakerList(&speaker_list, true); - } - for(LLSpeakerMgr::speaker_list_t::iterator it = speaker_list.begin(); it != speaker_list.end(); it++) - { - const LLPointer& speakerp = *it; - LLRecentPeople::instance().add(speakerp->mID); - } -} - -void session_starter_helper( - const LLUUID& temp_session_id, - const LLUUID& other_participant_id, - EInstantMessage im_type) -{ - LLMessageSystem *msg = gMessageSystem; - - msg->newMessageFast(_PREHASH_ImprovedInstantMessage); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - - msg->nextBlockFast(_PREHASH_MessageBlock); - msg->addBOOLFast(_PREHASH_FromGroup, false); - msg->addUUIDFast(_PREHASH_ToAgentID, other_participant_id); - msg->addU8Fast(_PREHASH_Offline, IM_ONLINE); - msg->addU8Fast(_PREHASH_Dialog, im_type); - msg->addUUIDFast(_PREHASH_ID, temp_session_id); - msg->addU32Fast(_PREHASH_Timestamp, NO_TIMESTAMP); // no timestamp necessary - - std::string name; - LLAgentUI::buildFullname(name); - - msg->addStringFast(_PREHASH_FromAgentName, name); - msg->addStringFast(_PREHASH_Message, LLStringUtil::null); - msg->addU32Fast(_PREHASH_ParentEstateID, 0); - msg->addUUIDFast(_PREHASH_RegionID, LLUUID::null); - msg->addVector3Fast(_PREHASH_Position, gAgent.getPositionAgent()); -} - -void start_deprecated_conference_chat( - const LLUUID& temp_session_id, - const LLUUID& creator_id, - const LLUUID& other_participant_id, - const LLSD& agents_to_invite) -{ - U8* bucket; - U8* pos; - S32 count; - S32 bucket_size; - - // *FIX: this could suffer from endian issues - count = agents_to_invite.size(); - bucket_size = UUID_BYTES * count; - bucket = new U8[bucket_size]; - pos = bucket; - - for(S32 i = 0; i < count; ++i) - { - LLUUID agent_id = agents_to_invite[i].asUUID(); - - memcpy(pos, &agent_id, UUID_BYTES); - pos += UUID_BYTES; - } - - session_starter_helper( - temp_session_id, - other_participant_id, - IM_SESSION_CONFERENCE_START); - - gMessageSystem->addBinaryDataFast( - _PREHASH_BinaryBucket, - bucket, - bucket_size); - - gAgent.sendReliableMessage(); - - delete[] bucket; -} - -// Returns true if any messages were sent, false otherwise. -// Is sort of equivalent to "does the server need to do anything?" -bool LLIMModel::sendStartSession( - const LLUUID& temp_session_id, - const LLUUID& other_participant_id, - const uuid_vec_t& ids, - EInstantMessage dialog) -{ - if ( dialog == IM_SESSION_GROUP_START ) - { - session_starter_helper( - temp_session_id, - other_participant_id, - dialog); - gMessageSystem->addBinaryDataFast( - _PREHASH_BinaryBucket, - EMPTY_BINARY_BUCKET, - EMPTY_BINARY_BUCKET_SIZE); - gAgent.sendReliableMessage(); - - return true; - } - else if ( dialog == IM_SESSION_CONFERENCE_START ) - { - LLSD agents; - for (int i = 0; i < (S32) ids.size(); i++) - { - agents.append(ids[i]); - } - - //we have a new way of starting conference calls now - LLViewerRegion* region = gAgent.getRegion(); - if (region) - { - std::string url = region->getCapability( - "ChatSessionRequest"); - - LLCoros::instance().launch("startConfrenceCoro", - boost::bind(&startConfrenceCoro, url, - temp_session_id, gAgent.getID(), other_participant_id, agents)); - } - else - { - start_deprecated_conference_chat( - temp_session_id, - gAgent.getID(), - other_participant_id, - agents); - } - - //we also need to wait for reply from the server in case of ad-hoc chat (we'll get new session id) - return true; - } - - return false; -} - - -// the other_participant_id is either an agent_id, a group_id, or an inventory -// folder item_id (collection of calling cards) - -// static -LLUUID LLIMMgr::computeSessionID( - EInstantMessage dialog, - const LLUUID& other_participant_id) -{ - LLUUID session_id; - if (IM_SESSION_GROUP_START == dialog) - { - // slam group session_id to the group_id (other_participant_id) - session_id = other_participant_id; - } - else if (IM_SESSION_CONFERENCE_START == dialog) - { - session_id.generate(); - } - else if (IM_SESSION_INVITE == dialog) - { - // use provided session id for invites - session_id = other_participant_id; - } - else - { - LLUUID agent_id = gAgent.getID(); - if (other_participant_id == agent_id) - { - // if we try to send an IM to ourselves then the XOR would be null - // so we just make the session_id the same as the agent_id - session_id = agent_id; - } - else - { - // peer-to-peer or peer-to-asset session_id is the XOR - session_id = other_participant_id ^ agent_id; - } - } - - if (gAgent.isInGroup(session_id, true) && (session_id != other_participant_id)) - { - LL_WARNS() << "Group session id different from group id: IM type = " << dialog << ", session id = " << session_id << ", group id = " << other_participant_id << LL_ENDL; - } - return session_id; -} - -void -LLIMMgr::showSessionStartError( - const std::string& error_string, - const LLUUID session_id) -{ - if (!hasSession(session_id)) return; - - LLSD args; - args["REASON"] = LLTrans::getString(error_string); - args["RECIPIENT"] = LLIMModel::getInstance()->getName(session_id); - - LLSD payload; - payload["session_id"] = session_id; - - LLNotificationsUtil::add( - "ChatterBoxSessionStartError", - args, - payload, - LLIMMgr::onConfirmForceCloseError); -} - -void -LLIMMgr::showSessionEventError( - const std::string& event_string, - const std::string& error_string, - const LLUUID session_id) -{ - LLSD args; - LLStringUtil::format_map_t event_args; - - event_args["RECIPIENT"] = LLIMModel::getInstance()->getName(session_id); - - args["REASON"] = - LLTrans::getString(error_string); - args["EVENT"] = - LLTrans::getString(event_string, event_args); - - LLNotificationsUtil::add( - "ChatterBoxSessionEventError", - args); -} - -void -LLIMMgr::showSessionForceClose( - const std::string& reason_string, - const LLUUID session_id) -{ - if (!hasSession(session_id)) return; - - LLSD args; - - args["NAME"] = LLIMModel::getInstance()->getName(session_id); - args["REASON"] = LLTrans::getString(reason_string); - - LLSD payload; - payload["session_id"] = session_id; - - LLNotificationsUtil::add( - "ForceCloseChatterBoxSession", - args, - payload, - LLIMMgr::onConfirmForceCloseError); -} - -//static -bool -LLIMMgr::onConfirmForceCloseError( - const LLSD& notification, - const LLSD& response) -{ - //only 1 option really - LLUUID session_id = notification["payload"]["session_id"]; - - LLFloater* floater = LLFloaterIMSession::findInstance(session_id); - if ( floater ) - { - floater->closeFloater(false); - } - return false; -} - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLCallDialogManager -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -LLCallDialogManager::LLCallDialogManager(): -mPreviousSessionlName(""), -mCurrentSessionlName(""), -mSession(NULL), -mOldState(LLVoiceChannel::STATE_READY) -{ -} - -LLCallDialogManager::~LLCallDialogManager() -{ -} - -void LLCallDialogManager::initSingleton() -{ - LLVoiceChannel::setCurrentVoiceChannelChangedCallback(LLCallDialogManager::onVoiceChannelChanged); -} - -// static -void LLCallDialogManager::onVoiceChannelChanged(const LLUUID &session_id) -{ - LLCallDialogManager::getInstance()->onVoiceChannelChangedInt(session_id); -} - -void LLCallDialogManager::onVoiceChannelChangedInt(const LLUUID &session_id) -{ - LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(session_id); - if(!session) - { - mPreviousSessionlName = mCurrentSessionlName; - mCurrentSessionlName = ""; // Empty string results in "Nearby Voice Chat" after substitution - return; - } - - mSession = session; - - static boost::signals2::connection prev_channel_state_changed_connection; - // disconnect previously connected callback to avoid have invalid sSession in onVoiceChannelStateChanged() - prev_channel_state_changed_connection.disconnect(); - prev_channel_state_changed_connection = - mSession->mVoiceChannel->setStateChangedCallback(boost::bind(LLCallDialogManager::onVoiceChannelStateChanged, _1, _2, _3, _4)); - - if(mCurrentSessionlName != session->mName) - { - mPreviousSessionlName = mCurrentSessionlName; - mCurrentSessionlName = session->mName; - } - - if (LLVoiceChannel::getCurrentVoiceChannel()->getState() == LLVoiceChannel::STATE_CALL_STARTED && - LLVoiceChannel::getCurrentVoiceChannel()->getCallDirection() == LLVoiceChannel::OUTGOING_CALL) - { - - //*TODO get rid of duplicated code - LLSD mCallDialogPayload; - mCallDialogPayload["session_id"] = mSession->mSessionID; - mCallDialogPayload["session_name"] = mSession->mName; - mCallDialogPayload["other_user_id"] = mSession->mOtherParticipantID; - mCallDialogPayload["old_channel_name"] = mPreviousSessionlName; - mCallDialogPayload["state"] = LLVoiceChannel::STATE_CALL_STARTED; - mCallDialogPayload["disconnected_channel_name"] = mSession->mName; - mCallDialogPayload["session_type"] = mSession->mSessionType; - - LLOutgoingCallDialog* ocd = LLFloaterReg::getTypedInstance("outgoing_call", LLOutgoingCallDialog::OCD_KEY); - if(ocd) - { - ocd->show(mCallDialogPayload); - } - } - -} - -// static -void LLCallDialogManager::onVoiceChannelStateChanged(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state, const LLVoiceChannel::EDirection& direction, bool ended_by_agent) -{ - LLCallDialogManager::getInstance()->onVoiceChannelStateChangedInt(old_state, new_state, direction, ended_by_agent); -} - -void LLCallDialogManager::onVoiceChannelStateChangedInt(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state, const LLVoiceChannel::EDirection& direction, bool ended_by_agent) -{ - LLSD mCallDialogPayload; - LLOutgoingCallDialog* ocd = NULL; - - if(mOldState == new_state) - { - return; - } - - mOldState = new_state; - - mCallDialogPayload["session_id"] = mSession->mSessionID; - mCallDialogPayload["session_name"] = mSession->mName; - mCallDialogPayload["other_user_id"] = mSession->mOtherParticipantID; - mCallDialogPayload["old_channel_name"] = mPreviousSessionlName; - mCallDialogPayload["state"] = new_state; - mCallDialogPayload["disconnected_channel_name"] = mSession->mName; - mCallDialogPayload["session_type"] = mSession->mSessionType; - mCallDialogPayload["ended_by_agent"] = ended_by_agent; - - switch(new_state) - { - case LLVoiceChannel::STATE_CALL_STARTED : - // do not show "Calling to..." if it is incoming call - if(direction == LLVoiceChannel::INCOMING_CALL) - { - return; - } - break; - - case LLVoiceChannel::STATE_HUNG_UP: - // this state is coming before session is changed - break; - - case LLVoiceChannel::STATE_CONNECTED : - ocd = LLFloaterReg::findTypedInstance("outgoing_call", LLOutgoingCallDialog::OCD_KEY); - if (ocd) - { - ocd->closeFloater(); - } - return; - - default: - break; - } - - ocd = LLFloaterReg::getTypedInstance("outgoing_call", LLOutgoingCallDialog::OCD_KEY); - if(ocd) - { - ocd->show(mCallDialogPayload); - } -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLCallDialog -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -LLCallDialog::LLCallDialog(const LLSD& payload) - : LLDockableFloater(NULL, false, payload), - - mPayload(payload), - mLifetime(DEFAULT_LIFETIME) -{ - setAutoFocus(false); - // force docked state since this floater doesn't save it between recreations - setDocked(true); -} - -LLCallDialog::~LLCallDialog() -{ - LLUI::getInstance()->removePopup(this); -} - -bool LLCallDialog::postBuild() -{ - if (!LLDockableFloater::postBuild() || !gToolBarView) - return false; - - dockToToolbarButton("speak"); - - return true; -} - -void LLCallDialog::dockToToolbarButton(const std::string& toolbarButtonName) -{ - LLDockControl::DocAt dock_pos = getDockControlPos(toolbarButtonName); - LLView *anchor_panel = gToolBarView->findChildView(toolbarButtonName); - - setUseTongue(anchor_panel); - - setDockControl(new LLDockControl(anchor_panel, this, getDockTongue(dock_pos), dock_pos)); -} - -LLDockControl::DocAt LLCallDialog::getDockControlPos(const std::string& toolbarButtonName) -{ - LLCommandId command_id(toolbarButtonName); - S32 toolbar_loc = gToolBarView->hasCommand(command_id); - - LLDockControl::DocAt doc_at = LLDockControl::TOP; - - switch (toolbar_loc) - { - case LLToolBarEnums::TOOLBAR_LEFT: - doc_at = LLDockControl::RIGHT; - break; - - case LLToolBarEnums::TOOLBAR_RIGHT: - doc_at = LLDockControl::LEFT; - break; - } - - return doc_at; -} - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLOutgoingCallDialog -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -LLOutgoingCallDialog::LLOutgoingCallDialog(const LLSD& payload) : -LLCallDialog(payload) -{ - LLOutgoingCallDialog* instance = LLFloaterReg::findTypedInstance("outgoing_call", LLOutgoingCallDialog::OCD_KEY); - if(instance && instance->getVisible()) - { - instance->onCancel(instance); - } -} - -void LLCallDialog::draw() -{ - if (lifetimeHasExpired()) - { - onLifetimeExpired(); - } - - if (getDockControl() != NULL) - { - LLDockableFloater::draw(); - } -} - -// virtual -void LLCallDialog::onOpen(const LLSD& key) -{ - LLDockableFloater::onOpen(key); - - // it should be over the all floaters. EXT-5116 - LLUI::getInstance()->addPopup(this); -} - -void LLCallDialog::setIcon(const LLSD& session_id, const LLSD& participant_id) -{ - bool participant_is_avatar = LLVoiceClient::getInstance()->isParticipantAvatar(session_id); - - bool is_group = participant_is_avatar && gAgent.isInGroup(session_id, true); - - LLAvatarIconCtrl* avatar_icon = getChild("avatar_icon"); - LLGroupIconCtrl* group_icon = getChild("group_icon"); - - avatar_icon->setVisible(!is_group); - group_icon->setVisible(is_group); - - if (is_group) - { - group_icon->setValue(session_id); - } - else if (participant_is_avatar) - { - avatar_icon->setValue(participant_id); - } - else - { - LL_WARNS() << "Participant neither avatar nor group" << LL_ENDL; - group_icon->setValue(session_id); - } -} - -bool LLCallDialog::lifetimeHasExpired() -{ - if (mLifetimeTimer.getStarted()) - { - F32 elapsed_time = mLifetimeTimer.getElapsedTimeF32(); - if (elapsed_time > mLifetime) - { - return true; - } - } - return false; -} - -void LLCallDialog::onLifetimeExpired() -{ - mLifetimeTimer.stop(); - closeFloater(); -} - -void LLOutgoingCallDialog::show(const LLSD& key) -{ - mPayload = key; - - //will be false only if voice in parcel is disabled and channel we leave is nearby(checked further) - bool show_oldchannel = LLViewerParcelMgr::getInstance()->allowAgentVoice(); - - // hide all text at first - hideAllText(); - - // init notification's lifetime - std::istringstream ss( getString("lifetime") ); - if (!(ss >> mLifetime)) - { - mLifetime = DEFAULT_LIFETIME; - } - - // customize text strings - // tell the user which voice channel they are leaving - if (!mPayload["old_channel_name"].asString().empty()) - { - std::string old_caller_name = mPayload["old_channel_name"].asString(); - - getChild("leaving")->setTextArg("[CURRENT_CHAT]", old_caller_name); - show_oldchannel = true; - } - else - { - getChild("leaving")->setTextArg("[CURRENT_CHAT]", getString("localchat")); - } - - if (!mPayload["disconnected_channel_name"].asString().empty()) - { - std::string channel_name = mPayload["disconnected_channel_name"].asString(); - getChild("nearby")->setTextArg("[VOICE_CHANNEL_NAME]", channel_name); - - // skipping "You will now be reconnected to nearby" in notification when call is ended by disabling voice, - // so no reconnection to nearby chat happens (EXT-4397) - bool voice_works = LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking(); - std::string reconnect_nearby = voice_works ? LLTrans::getString("reconnect_nearby") : std::string(); - getChild("nearby")->setTextArg("[RECONNECT_NEARBY]", reconnect_nearby); - - const std::string& nearby_str = mPayload["ended_by_agent"] ? NEARBY_P2P_BY_AGENT : NEARBY_P2P_BY_OTHER; - getChild(nearby_str)->setTextArg("[RECONNECT_NEARBY]", reconnect_nearby); - } - - std::string callee_name = mPayload["session_name"].asString(); - - if (callee_name == "anonymous") // obsolete? Likely was part of avaline support - { - callee_name = getString("anonymous"); - } - - LLSD callee_id = mPayload["other_user_id"]; - // Beautification: Since you know who you called, just show display name - std::string title = callee_name; - std::string final_callee_name = callee_name; - if (mPayload["session_type"].asInteger() == LLIMModel::LLIMSession::P2P_SESSION) - { - LLAvatarName av_name; - if (LLAvatarNameCache::get(callee_id, &av_name)) - { - final_callee_name = av_name.getDisplayName(); - title = av_name.getCompleteName(); - } - } - getChild("calling")->setTextArg("[CALLEE_NAME]", final_callee_name); - getChild("connecting")->setTextArg("[CALLEE_NAME]", final_callee_name); - - setTitle(title); - - // for outgoing group calls callee_id == group id == session id - setIcon(callee_id, callee_id); - - // stop timer by default - mLifetimeTimer.stop(); - - // show only necessary strings and controls - switch(mPayload["state"].asInteger()) - { - case LLVoiceChannel::STATE_CALL_STARTED : - getChild("calling")->setVisible(true); - getChild("Cancel")->setVisible(true); - if(show_oldchannel) - { - getChild("leaving")->setVisible(true); - } - break; - // STATE_READY is here to show appropriate text for ad-hoc and group calls when floater is shown(EXT-6893) - case LLVoiceChannel::STATE_READY : - case LLVoiceChannel::STATE_RINGING : - if(show_oldchannel) - { - getChild("leaving")->setVisible(true); - } - getChild("connecting")->setVisible(true); - break; - case LLVoiceChannel::STATE_ERROR : - getChild("noanswer")->setVisible(true); - getChild("Cancel")->setVisible(false); - setCanClose(true); - mLifetimeTimer.start(); - break; - case LLVoiceChannel::STATE_HUNG_UP : - if (mPayload["session_type"].asInteger() == LLIMModel::LLIMSession::P2P_SESSION) - { - const std::string& nearby_str = mPayload["ended_by_agent"] ? NEARBY_P2P_BY_AGENT : NEARBY_P2P_BY_OTHER; - getChild(nearby_str)->setVisible(true); - } - else - { - getChild("nearby")->setVisible(true); - } - getChild("Cancel")->setVisible(false); - setCanClose(true); - mLifetimeTimer.start(); - } - - openFloater(LLOutgoingCallDialog::OCD_KEY); -} - -void LLOutgoingCallDialog::hideAllText() -{ - getChild("calling")->setVisible(false); - getChild("leaving")->setVisible(false); - getChild("connecting")->setVisible(false); - getChild("nearby_P2P_by_other")->setVisible(false); - getChild("nearby_P2P_by_agent")->setVisible(false); - getChild("nearby")->setVisible(false); - getChild("noanswer")->setVisible(false); -} - -//static -void LLOutgoingCallDialog::onCancel(void* user_data) -{ - LLOutgoingCallDialog* self = (LLOutgoingCallDialog*)user_data; - - if (!gIMMgr) - return; - - LLUUID session_id = self->mPayload["session_id"].asUUID(); - gIMMgr->endCall(session_id); - - self->closeFloater(); -} - - -bool LLOutgoingCallDialog::postBuild() -{ - bool success = LLCallDialog::postBuild(); - - childSetAction("Cancel", onCancel, this); - - setCanDrag(false); - - return success; -} - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLIncomingCallDialog -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -const std::array voice_call_types = -{ - "VoiceInviteP2P", - "VoiceInviteGroup", - "VoiceInviteAdHoc", - "InviteAdHoc" -}; - -bool is_voice_call_type(const std::string &value) -{ - return std::find(voice_call_types.begin(), voice_call_types.end(), value) != voice_call_types.end(); -} - -LLIncomingCallDialog::LLIncomingCallDialog(const LLSD& payload) : -LLCallDialog(payload), -mAvatarNameCacheConnection() -{ -} - -void LLIncomingCallDialog::onLifetimeExpired() -{ - std::string session_handle = mPayload["session_handle"].asString(); - if (LLVoiceClient::getInstance()->isValidChannel(session_handle)) - { - // restart notification's timer if call is still valid - mLifetimeTimer.start(); - } - else - { - // close invitation if call is already not valid - mLifetimeTimer.stop(); - LLUUID session_id = mPayload["session_id"].asUUID(); - gIMMgr->clearPendingAgentListUpdates(session_id); - gIMMgr->clearPendingInvitation(session_id); - closeFloater(); - } -} - -bool LLIncomingCallDialog::postBuild() -{ - LLCallDialog::postBuild(); - - if (!mPayload.isMap() || mPayload.size() == 0) - { - LL_INFOS("IMVIEW") << "IncomingCall: invalid argument" << LL_ENDL; - return true; - } - - LLUUID session_id = mPayload["session_id"].asUUID(); - LLSD caller_id = mPayload["caller_id"]; - std::string caller_name = mPayload["caller_name"].asString(); - - if (session_id.isNull() && caller_id.asUUID().isNull()) - { - LL_INFOS("IMVIEW") << "IncomingCall: invalid ids" << LL_ENDL; - return true; - } - - std::string notify_box_type = mPayload["notify_box_type"].asString(); - if (!is_voice_call_type(notify_box_type)) - { - LL_INFOS("IMVIEW") << "IncomingCall: notify_box_type was not provided" << LL_ENDL; - return true; - } - - // init notification's lifetime - std::istringstream ss( getString("lifetime") ); - if (!(ss >> mLifetime)) - { - mLifetime = DEFAULT_LIFETIME; - } - - std::string call_type; - if (gAgent.isInGroup(session_id, true)) - { - LLStringUtil::format_map_t args; - LLGroupData data; - if (gAgent.getGroupData(session_id, data)) - { - args["[GROUP]"] = data.mName; - call_type = getString(notify_box_type, args); - } - } - else - { - call_type = getString(notify_box_type); - } - - if (caller_name == "anonymous") // obsolete? Likely was part of avaline support - { - caller_name = getString("anonymous"); - setCallerName(caller_name, caller_name, call_type); - } - else - { - // Get the full name information - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - mAvatarNameCacheConnection = LLAvatarNameCache::get(caller_id, boost::bind(&LLIncomingCallDialog::onAvatarNameCache, this, _1, _2, call_type)); - } - - setIcon(session_id, caller_id); - - childSetAction("Accept", onAccept, this); - childSetAction("Reject", onReject, this); - childSetAction("Start IM", onStartIM, this); - setDefaultBtn("Accept"); - - if(notify_box_type != "VoiceInviteGroup" && notify_box_type != "VoiceInviteAdHoc") - { - // starting notification's timer for P2P invitations - mLifetimeTimer.start(); - } - else - { - mLifetimeTimer.stop(); - } - - //it's not possible to connect to existing Ad-Hoc/Group chat through incoming ad-hoc call - bool is_avatar = LLVoiceClient::getInstance()->isParticipantAvatar(session_id); - getChildView("Start IM")->setVisible( is_avatar && notify_box_type != "VoiceInviteAdHoc" && notify_box_type != "VoiceInviteGroup"); - - setCanDrag(false); - return true; -} - -void LLIncomingCallDialog::setCallerName(const std::string& ui_title, - const std::string& ui_label, - const std::string& call_type) -{ - - // call_type may be a string like " is calling." - LLUICtrl* caller_name_widget = getChild("caller name"); - caller_name_widget->setValue(ui_label + " " + call_type); -} - -void LLIncomingCallDialog::onAvatarNameCache(const LLUUID& agent_id, - const LLAvatarName& av_name, - const std::string& call_type) -{ - mAvatarNameCacheConnection.disconnect(); - std::string title = av_name.getCompleteName(); - setCallerName(title, av_name.getCompleteName(), call_type); -} - -void LLIncomingCallDialog::onOpen(const LLSD& key) -{ - LLCallDialog::onOpen(key); - - if (gSavedSettings.getBOOL("PlaySoundIncomingVoiceCall")) - { - // play a sound for incoming voice call if respective property is set - make_ui_sound("UISndStartIM"); - } - - LLStringUtil::format_map_t args; - LLGroupData data; - // if it's a group call, retrieve group name to use it in question - if (gAgent.getGroupData(key["session_id"].asUUID(), data)) - { - args["[GROUP]"] = data.mName; - } -} - -//static -void LLIncomingCallDialog::onAccept(void* user_data) -{ - LLIncomingCallDialog* self = (LLIncomingCallDialog*)user_data; - processCallResponse(0, self->mPayload); - self->closeFloater(); -} - -//static -void LLIncomingCallDialog::onReject(void* user_data) -{ - LLIncomingCallDialog* self = (LLIncomingCallDialog*)user_data; - processCallResponse(1, self->mPayload); - self->closeFloater(); -} - -//static -void LLIncomingCallDialog::onStartIM(void* user_data) -{ - LLIncomingCallDialog* self = (LLIncomingCallDialog*)user_data; - processCallResponse(2, self->mPayload); - self->closeFloater(); -} - -// static -void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload) -{ - if (!gIMMgr || gDisconnected) - return; - - LLUUID session_id = payload["session_id"].asUUID(); - LLUUID caller_id = payload["caller_id"].asUUID(); - std::string session_name = payload["session_name"].asString(); - EInstantMessage type = (EInstantMessage)payload["type"].asInteger(); - LLIMMgr::EInvitationType inv_type = (LLIMMgr::EInvitationType)payload["inv_type"].asInteger(); - bool voice = true; - switch(response) - { - case 2: // start IM: just don't start the voice chat - { - voice = false; - /* FALLTHROUGH */ - } - case 0: // accept - { - if (type == IM_SESSION_P2P_INVITE) - { - // create a normal IM session - session_id = gIMMgr->addP2PSession( - session_name, - caller_id, - payload["session_handle"].asString(), - payload["session_uri"].asString()); - - if (voice) - { - gIMMgr->startCall(session_id, LLVoiceChannel::INCOMING_CALL); - } - else - { - LLAvatarActions::startIM(caller_id); - } - - gIMMgr->clearPendingAgentListUpdates(session_id); - gIMMgr->clearPendingInvitation(session_id); - } - else - { - //session name should not be empty, but it can contain spaces so we don't trim - std::string correct_session_name = session_name; - if (session_name.empty()) - { - LL_WARNS() << "Received an empty session name from a server" << LL_ENDL; - - switch(type){ - case IM_SESSION_CONFERENCE_START: - case IM_SESSION_GROUP_START: - case IM_SESSION_INVITE: - if (gAgent.isInGroup(session_id, true)) - { - LLGroupData data; - if (!gAgent.getGroupData(session_id, data)) break; - correct_session_name = data.mName; - } - else - { - // *NOTE: really should be using callbacks here - LLAvatarName av_name; - if (LLAvatarNameCache::get(caller_id, &av_name)) - { - correct_session_name = av_name.getCompleteName(); - correct_session_name.append(ADHOC_NAME_SUFFIX); - } - } - LL_INFOS("IMVIEW") << "Corrected session name is " << correct_session_name << LL_ENDL; - break; - default: - LL_WARNS("IMVIEW") << "Received an empty session name from a server and failed to generate a new proper session name" << LL_ENDL; - break; - } - } - - gIMMgr->addSession(correct_session_name, type, session_id, true); - - std::string url = gAgent.getRegion()->getCapability( - "ChatSessionRequest"); - - if (voice) - { - LLCoros::instance().launch("chatterBoxInvitationCoro", - boost::bind(&chatterBoxInvitationCoro, url, - session_id, inv_type)); - - // send notification message to the corresponding chat - if (payload["notify_box_type"].asString() == "VoiceInviteGroup" || payload["notify_box_type"].asString() == "VoiceInviteAdHoc") - { - LLStringUtil::format_map_t string_args; - string_args["[NAME]"] = payload["caller_name"].asString(); - std::string message = LLTrans::getString("name_started_call", string_args); - LLIMModel::getInstance()->addMessageSilently(session_id, SYSTEM_FROM, LLUUID::null, message); - } - } - } - if (voice) - { - break; - } - } - case 1: // decline - { - if (type == IM_SESSION_P2P_INVITE) - { - if(LLVoiceClient::getInstance()) - { - std::string s = payload["session_handle"].asString(); - LLVoiceClient::getInstance()->declineInvite(s); - } - } - else - { - std::string url = gAgent.getRegion()->getCapability( - "ChatSessionRequest"); - - LLSD data; - data["method"] = "decline invitation"; - data["session-id"] = session_id; - - LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data, - "Invitation declined", - "Invitation decline failed."); - } - } - - gIMMgr->clearPendingAgentListUpdates(session_id); - gIMMgr->clearPendingInvitation(session_id); - } -} - -bool inviteUserResponse(const LLSD& notification, const LLSD& response) -{ - if (!gIMMgr) - return false; - - const LLSD& payload = notification["payload"]; - LLUUID session_id = payload["session_id"].asUUID(); - EInstantMessage type = (EInstantMessage)payload["type"].asInteger(); - LLIMMgr::EInvitationType inv_type = (LLIMMgr::EInvitationType)payload["inv_type"].asInteger(); - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - switch(option) - { - case 0: // accept - { - if (type == IM_SESSION_P2P_INVITE) - { - // create a normal IM session - session_id = gIMMgr->addP2PSession( - payload["session_name"].asString(), - payload["caller_id"].asUUID(), - payload["session_handle"].asString(), - payload["session_uri"].asString()); - - gIMMgr->startCall(session_id); - - gIMMgr->clearPendingAgentListUpdates(session_id); - gIMMgr->clearPendingInvitation(session_id); - } - else - { - gIMMgr->addSession( - payload["session_name"].asString(), - type, - session_id, true); - - std::string url = gAgent.getRegion()->getCapability( - "ChatSessionRequest"); - - LLCoros::instance().launch("chatterBoxInvitationCoro", - boost::bind(&chatterBoxInvitationCoro, url, - session_id, inv_type)); - } - } - break; - case 2: // mute (also implies ignore, so this falls through to the "ignore" case below) - { - // mute the sender of this invite - if (!LLMuteList::getInstance()->isMuted(payload["caller_id"].asUUID())) - { - LLMute mute(payload["caller_id"].asUUID(), payload["caller_name"].asString(), LLMute::AGENT); - LLMuteList::getInstance()->add(mute); - } - } - /* FALLTHROUGH */ - - case 1: // decline - { - if (type == IM_SESSION_P2P_INVITE) - { - std::string s = payload["session_handle"].asString(); - LLVoiceClient::getInstance()->declineInvite(s); - } - else - { - std::string url = gAgent.getRegion()->getCapability( - "ChatSessionRequest"); - - LLSD data; - data["method"] = "decline invitation"; - data["session-id"] = session_id; - LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data, - "Invitation declined.", - "Invitation decline failed."); - } - } - - gIMMgr->clearPendingAgentListUpdates(session_id); - gIMMgr->clearPendingInvitation(session_id); - break; - } - - return false; -} - -// -// Member Functions -// - -LLIMMgr::LLIMMgr() -{ - mPendingInvitations = LLSD::emptyMap(); - mPendingAgentListUpdates = LLSD::emptyMap(); - - LLIMModel::getInstance()->addNewMsgCallback(boost::bind(&LLFloaterIMSession::sRemoveTypingIndicator, _1)); - - gSavedPerAccountSettings.declareBOOL("FetchGroupChatHistory", true, "Fetch recent messages from group chat servers when a group window opens", LLControlVariable::PERSIST_ALWAYS); -} - -// Add a message to a session. -void LLIMMgr::addMessage( - const LLUUID& session_id, - const LLUUID& target_id, - const std::string& from, - const std::string& msg, - bool is_offline_msg, - const std::string& session_name, - EInstantMessage dialog, - U32 parent_estate_id, - const LLUUID& region_id, - const LLVector3& position, - bool is_region_msg, - U32 timestamp) // May be zero -{ - LLUUID other_participant_id = target_id; - - LLUUID new_session_id = session_id; - if (new_session_id.isNull()) - { - //no session ID...compute new one - new_session_id = computeSessionID(dialog, other_participant_id); - } - - //*NOTE session_name is empty in case of incoming P2P sessions - std::string fixed_session_name = from; - bool name_is_setted = false; - if(!session_name.empty() && session_name.size()>1) - { - fixed_session_name = session_name; - name_is_setted = true; - } - bool skip_message = false; - bool from_linden = LLMuteList::isLinden(from); - if (gSavedPerAccountSettings.getBOOL("VoiceCallsFriendsOnly") && !from_linden) - { - // Evaluate if we need to skip this message when that setting is true (default is false) - skip_message = (LLAvatarTracker::instance().getBuddyInfo(other_participant_id) == NULL); // Skip non friends... - skip_message &= !(other_participant_id == gAgentID); // You are your best friend... Don't skip yourself - } - - bool new_session = !hasSession(new_session_id); - if (new_session) - { - // Group chat session was initiated by muted resident, do not start this session viewerside - // do not send leave msg either, so we are able to get group messages from other participants - if ((IM_SESSION_INVITE == dialog) && gAgent.isInGroup(new_session_id) && - LLMuteList::getInstance()->isMuted(other_participant_id, LLMute::flagTextChat) && !from_linden) - { - return; - } - - LLAvatarName av_name; - if (LLAvatarNameCache::get(other_participant_id, &av_name) && !name_is_setted) - { - fixed_session_name = av_name.getDisplayName(); - } - LLIMModel::getInstance()->newSession(new_session_id, fixed_session_name, dialog, other_participant_id, false, is_offline_msg); - - LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(new_session_id); - if (session) - { - skip_message &= !session->isGroupSessionType(); // Do not skip group chats... - if (skip_message) - { - gIMMgr->leaveSession(new_session_id); - } - // When we get a new IM, and if you are a god, display a bit - // of information about the source. This is to help liaisons - // when answering questions. - if (gAgent.isGodlike()) - { - // *TODO:translate (low priority, god ability) - std::ostringstream bonus_info; - bonus_info << LLTrans::getString("***") + " " + LLTrans::getString("IMParentEstate") + ":" + " " - << parent_estate_id - << ((parent_estate_id == 1) ? "," + LLTrans::getString("IMMainland") : "") - << ((parent_estate_id == 5) ? "," + LLTrans::getString("IMTeen") : ""); - - // once we have web-services (or something) which returns - // information about a region id, we can print this out - // and even have it link to map-teleport or something. - //<< "*** region_id: " << region_id << std::endl - //<< "*** position: " << position << std::endl; - - LLIMModel::instance().addMessage(new_session_id, from, other_participant_id, bonus_info.str(), true, is_region_msg); - } - - // Logically it would make more sense to reject the session sooner, in another area of the - // code, but the session has to be established inside the server before it can be left. - if (LLMuteList::getInstance()->isMuted(other_participant_id, LLMute::flagTextChat) && !from_linden) - { - LL_WARNS() << "Leaving IM session from initiating muted resident " << from << LL_ENDL; - if (!gIMMgr->leaveSession(new_session_id)) - { - LL_INFOS("IMVIEW") << "Session " << new_session_id << " does not exist." << LL_ENDL; - } - return; - } - - // Fetch group chat history, enabled by default. - if (gSavedPerAccountSettings.getBOOL("FetchGroupChatHistory")) - { - std::string chat_url = gAgent.getRegionCapability("ChatSessionRequest"); - if (!chat_url.empty()) - { - LLCoros::instance().launch("chatterBoxHistoryCoro", boost::bind(&chatterBoxHistoryCoro, chat_url, session_id, from, msg, timestamp)); - } - } - - //Play sound for new conversations - if (!skip_message & !gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNewConversation"))) - { - make_ui_sound("UISndNewIncomingIMSession"); - } - } - else - { - // Failed to create a session, most likely due to empty name (name cache failed?) - LL_WARNS() << "Failed to create IM session " << fixed_session_name << LL_ENDL; - } - } - - if (!LLMuteList::getInstance()->isMuted(other_participant_id, LLMute::flagTextChat) && !skip_message) - { - LLIMModel::instance().addMessage(new_session_id, from, other_participant_id, msg, true, is_region_msg, timestamp); - } - - // Open conversation floater if offline messages are present - if (is_offline_msg && !skip_message) - { - LLFloaterReg::showInstance("im_container"); - LLFloaterReg::getTypedInstance("im_container")-> - flashConversationItemWidget(new_session_id, true); - } -} - -void LLIMMgr::addSystemMessage(const LLUUID& session_id, const std::string& message_name, const LLSD& args) -{ - LLUIString message; - - // null session id means near me (chat history) - if (session_id.isNull()) - { - message = LLTrans::getString(message_name); - message.setArgs(args); - - LLChat chat(message); - chat.mSourceType = CHAT_SOURCE_SYSTEM; - - LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); - if (nearby_chat) - { - nearby_chat->addMessage(chat); - } - } - else // going to IM session - { - message = LLTrans::getString(message_name + "-im"); - message.setArgs(args); - if (hasSession(session_id)) - { - gIMMgr->addMessage(session_id, LLUUID::null, SYSTEM_FROM, message.getString()); - } - // log message to file - - else - { - LLAvatarName av_name; - // since we select user to share item with - his name is already in cache - LLAvatarNameCache::get(args["user_id"], &av_name); - std::string session_name = LLCacheName::buildUsername(av_name.getUserName()); - LLIMModel::instance().logToFile(session_name, SYSTEM_FROM, LLUUID::null, message.getString()); - } - } -} - -S32 LLIMMgr::getNumberOfUnreadIM() -{ - std::map::iterator it; - - S32 num = 0; - for(it = LLIMModel::getInstance()->mId2SessionMap.begin(); it != LLIMModel::getInstance()->mId2SessionMap.end(); ++it) - { - num += (*it).second->mNumUnread; - } - - return num; -} - -S32 LLIMMgr::getNumberOfUnreadParticipantMessages() -{ - std::map::iterator it; - - S32 num = 0; - for(it = LLIMModel::getInstance()->mId2SessionMap.begin(); it != LLIMModel::getInstance()->mId2SessionMap.end(); ++it) - { - num += (*it).second->mParticipantUnreadMessageCount; - } - - return num; -} - -void LLIMMgr::autoStartCallOnStartup(const LLUUID& session_id) -{ - LLIMModel::LLIMSession *session = LLIMModel::getInstance()->findIMSession(session_id); - if (!session) return; - - if (session->mSessionInitialized) - { - startCall(session_id); - } - else - { - session->mStartCallOnInitialize = true; - } -} - -LLUUID LLIMMgr::addP2PSession(const std::string& name, - const LLUUID& other_participant_id, - const std::string& voice_session_handle, - const std::string& caller_uri) -{ - LLUUID session_id = addSession(name, IM_NOTHING_SPECIAL, other_participant_id, true); - - LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(session_id); - if (speaker_mgr) - { - LLVoiceChannelP2P* voice_channel = dynamic_cast(speaker_mgr->getVoiceChannel()); - if (voice_channel) - { - voice_channel->setSessionHandle(voice_session_handle, caller_uri); - } - } - return session_id; -} - -// This adds a session to the talk view. The name is the local name of -// the session, dialog specifies the type of session. If the session -// exists, it is brought forward. Specifying id = NULL results in an -// im session to everyone. Returns the uuid of the session. -LLUUID LLIMMgr::addSession( - const std::string& name, - EInstantMessage dialog, - const LLUUID& other_participant_id, bool voice) -{ - std::vector ids; - ids.push_back(other_participant_id); - LLUUID session_id = addSession(name, dialog, other_participant_id, ids, voice); - return session_id; -} - -// Adds a session using the given session_id. If the session already exists -// the dialog type is assumed correct. Returns the uuid of the session. -LLUUID LLIMMgr::addSession( - const std::string& name, - EInstantMessage dialog, - const LLUUID& other_participant_id, - const std::vector& ids, bool voice, - const LLUUID& floater_id) -{ - if (ids.empty()) - { - return LLUUID::null; - } - - if (name.empty()) - { - LL_WARNS() << "Session name cannot be null!" << LL_ENDL; - return LLUUID::null; - } - - LLUUID session_id = computeSessionID(dialog,other_participant_id); - - if (floater_id.notNull()) - { - LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(floater_id); - - if (im_floater) - { - // The IM floater should be initialized with a new session_id - // so that it is found by that id when creating a chiclet in LLFloaterIMSession::onIMChicletCreated, - // and a new floater is not created. - im_floater->initIMSession(session_id); - im_floater->reloadMessages(); - } - } - - bool new_session = (LLIMModel::getInstance()->findIMSession(session_id) == NULL); - - //works only for outgoing ad-hoc sessions - if (new_session && IM_SESSION_CONFERENCE_START == dialog && ids.size()) - { - LLIMModel::LLIMSession* ad_hoc_found = LLIMModel::getInstance()->findAdHocIMSession(ids); - if (ad_hoc_found) - { - new_session = false; - session_id = ad_hoc_found->mSessionID; - } - } - - //Notify observers that a session was added - if (new_session) - { - LLIMModel::getInstance()->newSession(session_id, name, dialog, other_participant_id, ids, voice); - } - //Notifies observers that the session was already added - else - { - std::string session_name = LLIMModel::getInstance()->getName(session_id); - LLIMMgr::getInstance()->notifyObserverSessionActivated(session_id, session_name, other_participant_id); - } - - //we don't need to show notes about online/offline, mute/unmute users' statuses for existing sessions - if (!new_session) return session_id; - - LL_INFOS("IMVIEW") << "LLIMMgr::addSession, new session added, name = " << name << ", session id = " << session_id << LL_ENDL; - - //Per Plan's suggestion commented "explicit offline status warning" out to make Dessie happier (see EXT-3609) - //*TODO After February 2010 remove this commented out line if no one will be missing that warning - //noteOfflineUsers(session_id, floater, ids); - - // Only warn for regular IMs - not group IMs - if( dialog == IM_NOTHING_SPECIAL ) - { - noteMutedUsers(session_id, ids); - } - - notifyObserverSessionVoiceOrIMStarted(session_id); - - return session_id; -} - -bool LLIMMgr::leaveSession(const LLUUID& session_id) -{ - LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(session_id); - if (!im_session) return false; - - LLIMModel::getInstance()->sendLeaveSession(session_id, im_session->mOtherParticipantID); - gIMMgr->removeSession(session_id); - return true; -} - -// Removes data associated with a particular session specified by session_id -void LLIMMgr::removeSession(const LLUUID& session_id) -{ - llassert_always(hasSession(session_id)); - - clearPendingInvitation(session_id); - clearPendingAgentListUpdates(session_id); - - LLIMModel::getInstance()->clearSession(session_id); - - LL_INFOS("IMVIEW") << "LLIMMgr::removeSession, session removed, session id = " << session_id << LL_ENDL; - - notifyObserverSessionRemoved(session_id); -} - -void LLIMMgr::inviteToSession( - const LLUUID& session_id, - const std::string& session_name, - const LLUUID& caller_id, - const std::string& caller_name, - EInstantMessage type, - EInvitationType inv_type, - const std::string& session_handle, - const std::string& session_uri) -{ - std::string notify_box_type; - // voice invite question is different from default only for group call (EXT-7118) - std::string question_type = "VoiceInviteQuestionDefault"; - - bool voice_invite = false; - bool is_linden = LLMuteList::isLinden(caller_name); - - - if(type == IM_SESSION_P2P_INVITE) - { - //P2P is different...they only have voice invitations - notify_box_type = "VoiceInviteP2P"; - voice_invite = true; - } - else if ( gAgent.isInGroup(session_id, true) ) - { - //only really old school groups have voice invitations - notify_box_type = "VoiceInviteGroup"; - question_type = "VoiceInviteQuestionGroup"; - voice_invite = true; - } - else if ( inv_type == INVITATION_TYPE_VOICE ) - { - //else it's an ad-hoc - //and a voice ad-hoc - notify_box_type = "VoiceInviteAdHoc"; - voice_invite = true; - } - else if ( inv_type == INVITATION_TYPE_IMMEDIATE ) - { - notify_box_type = "InviteAdHoc"; - } - - LLSD payload; - payload["session_id"] = session_id; - payload["session_name"] = session_name; - payload["caller_id"] = caller_id; - payload["caller_name"] = caller_name; - payload["type"] = type; - payload["inv_type"] = inv_type; - payload["session_handle"] = session_handle; - payload["session_uri"] = session_uri; - payload["notify_box_type"] = notify_box_type; - payload["question_type"] = question_type; - - //ignore invites from muted residents - if (!is_linden) - { - if (LLMuteList::getInstance()->isMuted(caller_id, LLMute::flagVoiceChat) - && voice_invite && "VoiceInviteQuestionDefault" == question_type) - { - LL_INFOS("IMVIEW") << "Rejecting voice call from initiating muted resident " << caller_name << LL_ENDL; - LLIncomingCallDialog::processCallResponse(1, payload); - return; - } - else if (LLMuteList::getInstance()->isMuted(caller_id, LLMute::flagAll & ~LLMute::flagVoiceChat) && !voice_invite) - { - LL_INFOS("IMVIEW") << "Rejecting session invite from initiating muted resident " << caller_name << LL_ENDL; - return; - } - } - - LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(session_id); - if (channelp && channelp->callStarted()) - { - // you have already started a call to the other user, so just accept the invite - LLIncomingCallDialog::processCallResponse(0, payload); - return; - } - - if (voice_invite) - { - bool isRejectGroupCall = (gSavedSettings.getBOOL("VoiceCallsRejectGroup") && (notify_box_type == "VoiceInviteGroup")); - bool isRejectNonFriendCall = (gSavedPerAccountSettings.getBOOL("VoiceCallsFriendsOnly") && (LLAvatarTracker::instance().getBuddyInfo(caller_id) == NULL)); - if (isRejectGroupCall || isRejectNonFriendCall || gAgent.isDoNotDisturb()) - { - if (gAgent.isDoNotDisturb() && !isRejectGroupCall && !isRejectNonFriendCall) - { - if (!hasSession(session_id) && (type == IM_SESSION_P2P_INVITE)) - { - std::string fixed_session_name = caller_name; - if(!session_name.empty() && session_name.size()>1) - { - fixed_session_name = session_name; - } - else - { - LLAvatarName av_name; - if (LLAvatarNameCache::get(caller_id, &av_name)) - { - fixed_session_name = av_name.getDisplayName(); - } - } - LLIMModel::getInstance()->newSession(session_id, fixed_session_name, IM_NOTHING_SPECIAL, caller_id, false, false); - } - - LLSD args; - addSystemMessage(session_id, "you_auto_rejected_call", args); - send_do_not_disturb_message(gMessageSystem, caller_id, session_id); - } - // silently decline the call - LLIncomingCallDialog::processCallResponse(1, payload); - return; - } - } - - if ( !mPendingInvitations.has(session_id.asString()) ) - { - if (caller_name.empty()) - { - LLAvatarNameCache::get(caller_id, - boost::bind(&LLIMMgr::onInviteNameLookup, payload, _1, _2)); - } - else - { - LLFloaterReg::showInstance("incoming_call", payload, false); - } - - // Add the caller to the Recent List here (at this point - // "incoming_call" floater is shown and the recipient can - // reject the call), because even if a recipient will reject - // the call, the caller should be added to the recent list - // anyway. STORM-507. - if(type == IM_SESSION_P2P_INVITE) - LLRecentPeople::instance().add(caller_id); - - mPendingInvitations[session_id.asString()] = LLSD(); - } -} - -void LLIMMgr::onInviteNameLookup(LLSD payload, const LLUUID& id, const LLAvatarName& av_name) -{ - payload["caller_name"] = av_name.getUserName(); - payload["session_name"] = payload["caller_name"].asString(); - - std::string notify_box_type = payload["notify_box_type"].asString(); - - LLFloaterReg::showInstance("incoming_call", payload, false); -} - -//*TODO disconnects all sessions -void LLIMMgr::disconnectAllSessions() -{ - //*TODO disconnects all IM sessions -} - -bool LLIMMgr::hasSession(const LLUUID& session_id) -{ - return LLIMModel::getInstance()->findIMSession(session_id) != NULL; -} - -void LLIMMgr::clearPendingInvitation(const LLUUID& session_id) -{ - if ( mPendingInvitations.has(session_id.asString()) ) - { - mPendingInvitations.erase(session_id.asString()); - } -} - -void LLIMMgr::processAgentListUpdates(const LLUUID& session_id, const LLSD& body) -{ - LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); - if ( im_floater ) - { - im_floater->processAgentListUpdates(body); - } - LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(session_id); - if (speaker_mgr) - { - speaker_mgr->updateSpeakers(body); - - // also the same call is added into LLVoiceClient::participantUpdatedEvent because - // sometimes it is called AFTER LLViewerChatterBoxSessionAgentListUpdates::post() - // when moderation state changed too late. See EXT-3544. - speaker_mgr->update(true); - } - else - { - //we don't have a speaker manager yet..something went wrong - //we are probably receiving an update here before - //a start or an acceptance of an invitation. Race condition. - gIMMgr->addPendingAgentListUpdates( - session_id, - body); - } -} - -LLSD LLIMMgr::getPendingAgentListUpdates(const LLUUID& session_id) -{ - if ( mPendingAgentListUpdates.has(session_id.asString()) ) - { - return mPendingAgentListUpdates[session_id.asString()]; - } - else - { - return LLSD(); - } -} - -void LLIMMgr::addPendingAgentListUpdates( - const LLUUID& session_id, - const LLSD& updates) -{ - LLSD::map_const_iterator iter; - - if ( !mPendingAgentListUpdates.has(session_id.asString()) ) - { - //this is a new agent list update for this session - mPendingAgentListUpdates[session_id.asString()] = LLSD::emptyMap(); - } - - if ( - updates.has("agent_updates") && - updates["agent_updates"].isMap() && - updates.has("updates") && - updates["updates"].isMap() ) - { - //new school update - LLSD update_types = LLSD::emptyArray(); - LLSD::array_iterator array_iter; - - update_types.append("agent_updates"); - update_types.append("updates"); - - for ( - array_iter = update_types.beginArray(); - array_iter != update_types.endArray(); - ++array_iter) - { - //we only want to include the last update for a given agent - for ( - iter = updates[array_iter->asString()].beginMap(); - iter != updates[array_iter->asString()].endMap(); - ++iter) - { - mPendingAgentListUpdates[session_id.asString()][array_iter->asString()][iter->first] = - iter->second; - } - } - } - else if ( - updates.has("updates") && - updates["updates"].isMap() ) - { - //old school update where the SD contained just mappings - //of agent_id -> "LEAVE"/"ENTER" - - //only want to keep last update for each agent - for ( - iter = updates["updates"].beginMap(); - iter != updates["updates"].endMap(); - ++iter) - { - mPendingAgentListUpdates[session_id.asString()]["updates"][iter->first] = - iter->second; - } - } -} - -void LLIMMgr::clearPendingAgentListUpdates(const LLUUID& session_id) -{ - if ( mPendingAgentListUpdates.has(session_id.asString()) ) - { - mPendingAgentListUpdates.erase(session_id.asString()); - } -} - -void LLIMMgr::notifyObserverSessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id, bool has_offline_msg) -{ - for (session_observers_list_t::iterator it = mSessionObservers.begin(); it != mSessionObservers.end(); it++) - { - (*it)->sessionAdded(session_id, name, other_participant_id, has_offline_msg); - } -} - -void LLIMMgr::notifyObserverSessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id) -{ - for (session_observers_list_t::iterator it = mSessionObservers.begin(); it != mSessionObservers.end(); it++) - { - (*it)->sessionActivated(session_id, name, other_participant_id); - } -} - -void LLIMMgr::notifyObserverSessionVoiceOrIMStarted(const LLUUID& session_id) -{ - for (session_observers_list_t::iterator it = mSessionObservers.begin(); it != mSessionObservers.end(); it++) - { - (*it)->sessionVoiceOrIMStarted(session_id); - } -} - -void LLIMMgr::notifyObserverSessionRemoved(const LLUUID& session_id) -{ - for (session_observers_list_t::iterator it = mSessionObservers.begin(); it != mSessionObservers.end(); it++) - { - (*it)->sessionRemoved(session_id); - } -} - -void LLIMMgr::notifyObserverSessionIDUpdated( const LLUUID& old_session_id, const LLUUID& new_session_id ) -{ - for (session_observers_list_t::iterator it = mSessionObservers.begin(); it != mSessionObservers.end(); it++) - { - (*it)->sessionIDUpdated(old_session_id, new_session_id); - } - -} - -void LLIMMgr::addSessionObserver(LLIMSessionObserver *observer) -{ - mSessionObservers.push_back(observer); -} - -void LLIMMgr::removeSessionObserver(LLIMSessionObserver *observer) -{ - mSessionObservers.remove(observer); -} - -bool LLIMMgr::startCall(const LLUUID& session_id, LLVoiceChannel::EDirection direction) -{ - LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(session_id); - if (!voice_channel) return false; - - voice_channel->setCallDirection(direction); - voice_channel->activate(); - return true; -} - -bool LLIMMgr::endCall(const LLUUID& session_id) -{ - LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(session_id); - if (!voice_channel) return false; - - voice_channel->deactivate(); - LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(session_id); - if (im_session) - { - // need to update speakers' state - im_session->mSpeakers->update(false); - } - return true; -} - -bool LLIMMgr::isVoiceCall(const LLUUID& session_id) -{ - LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(session_id); - if (!im_session) return false; - - return im_session->mStartedAsIMCall; -} - -void LLIMMgr::updateDNDMessageStatus() -{ - if (LLIMModel::getInstance()->mId2SessionMap.empty()) return; - - std::map::const_iterator it = LLIMModel::getInstance()->mId2SessionMap.begin(); - for (; it != LLIMModel::getInstance()->mId2SessionMap.end(); ++it) - { - LLIMModel::LLIMSession* session = (*it).second; - - if (session->isP2P()) - { - setDNDMessageSent(session->mSessionID,false); - } - } -} - -bool LLIMMgr::isDNDMessageSend(const LLUUID& session_id) -{ - LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(session_id); - if (!im_session) return false; - - return im_session->mIsDNDsend; -} - -void LLIMMgr::setDNDMessageSent(const LLUUID& session_id, bool is_send) -{ - LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(session_id); - if (!im_session) return; - - im_session->mIsDNDsend = is_send; -} - -void LLIMMgr::addNotifiedNonFriendSessionID(const LLUUID& session_id) -{ - mNotifiedNonFriendSessions.insert(session_id); -} - -bool LLIMMgr::isNonFriendSessionNotified(const LLUUID& session_id) -{ - return mNotifiedNonFriendSessions.end() != mNotifiedNonFriendSessions.find(session_id); - -} - -void LLIMMgr::noteOfflineUsers( - const LLUUID& session_id, - const std::vector& ids) -{ - S32 count = ids.size(); - if(count == 0) - { - const std::string& only_user = LLTrans::getString("only_user_message"); - LLIMModel::getInstance()->addMessage(session_id, SYSTEM_FROM, LLUUID::null, only_user); - } - else - { - const LLRelationship* info = NULL; - LLAvatarTracker& at = LLAvatarTracker::instance(); - LLIMModel& im_model = LLIMModel::instance(); - for(S32 i = 0; i < count; ++i) - { - info = at.getBuddyInfo(ids.at(i)); - LLAvatarName av_name; - if (info - && !info->isOnline() - && LLAvatarNameCache::get(ids.at(i), &av_name)) - { - LLUIString offline = LLTrans::getString("offline_message"); - // Use display name only because this user is your friend - offline.setArg("[NAME]", av_name.getDisplayName()); - im_model.proccessOnlineOfflineNotification(session_id, offline); - } - } - } -} - -void LLIMMgr::noteMutedUsers(const LLUUID& session_id, - const std::vector& ids) -{ - // Don't do this if we don't have a mute list. - LLMuteList *ml = LLMuteList::getInstance(); - if( !ml ) - { - return; - } - - S32 count = ids.size(); - if(count > 0) - { - LLIMModel* im_model = LLIMModel::getInstance(); - - for(S32 i = 0; i < count; ++i) - { - if( ml->isMuted(ids.at(i)) ) - { - LLUIString muted = LLTrans::getString("muted_message"); - - im_model->addMessage(session_id, SYSTEM_FROM, LLUUID::null, muted); - break; - } - } - } -} - -void LLIMMgr::processIMTypingStart(const LLUUID& from_id, const EInstantMessage im_type) -{ - processIMTypingCore(from_id, im_type, true); -} - -void LLIMMgr::processIMTypingStop(const LLUUID& from_id, const EInstantMessage im_type) -{ - processIMTypingCore(from_id, im_type, false); -} - -void LLIMMgr::processIMTypingCore(const LLUUID& from_id, const EInstantMessage im_type, bool typing) -{ - LLUUID session_id = computeSessionID(im_type, from_id); - LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); - if ( im_floater ) - { - im_floater->processIMTyping(from_id, typing); - } -} - -class LLViewerChatterBoxSessionStartReply : public LLHTTPNode -{ -public: - virtual void describe(Description& desc) const - { - desc.shortInfo("Used for receiving a reply to a request to initialize an ChatterBox session"); - desc.postAPI(); - desc.input( - "{\"client_session_id\": UUID, \"session_id\": UUID, \"success\" boolean, \"reason\": string"); - desc.source(__FILE__, __LINE__); - } - - virtual void post(ResponsePtr response, - const LLSD& context, - const LLSD& input) const - { - if (LLApp::isExiting() || gDisconnected) - { - LL_DEBUGS("ChatHistory") << "Ignoring ChatterBox session, Shutting down" << LL_ENDL; - return; - } - - LLSD body; - LLUUID temp_session_id; - LLUUID session_id; - bool success; - - body = input["body"]; - success = body["success"].asBoolean(); - temp_session_id = body["temp_session_id"].asUUID(); - - if ( success ) - { - session_id = body["session_id"].asUUID(); - - LLIMModel::getInstance()->processSessionInitializedReply(temp_session_id, session_id); - - LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(session_id); - if (speaker_mgr) - { - speaker_mgr->setSpeakers(body); - speaker_mgr->updateSpeakers(gIMMgr->getPendingAgentListUpdates(session_id)); - } - - LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); - if ( im_floater ) - { - if ( body.has("session_info") ) - { - im_floater->processSessionUpdate(body["session_info"]); - - // Send request for chat history, if enabled. - if (gSavedPerAccountSettings.getBOOL("FetchGroupChatHistory")) - { - std::string url = gAgent.getRegion()->getCapability("ChatSessionRequest"); - LLCoros::instance().launch("chatterBoxHistoryCoro", - boost::bind(&chatterBoxHistoryCoro, url, session_id, "", "", 0)); - } - } - } - - gIMMgr->clearPendingAgentListUpdates(session_id); - } - else - { - //throw an error dialog and close the temp session's floater - gIMMgr->showSessionStartError(body["error"].asString(), temp_session_id); - } - - gIMMgr->clearPendingAgentListUpdates(session_id); - } -}; - -class LLViewerChatterBoxSessionEventReply : public LLHTTPNode -{ -public: - virtual void describe(Description& desc) const - { - desc.shortInfo("Used for receiving a reply to a ChatterBox session event"); - desc.postAPI(); - desc.input( - "{\"event\": string, \"reason\": string, \"success\": boolean, \"session_id\": UUID"); - desc.source(__FILE__, __LINE__); - } - - virtual void post(ResponsePtr response, - const LLSD& context, - const LLSD& input) const - { - LLUUID session_id; - bool success; - - LLSD body = input["body"]; - success = body["success"].asBoolean(); - session_id = body["session_id"].asUUID(); - - if ( !success ) - { - //throw an error dialog - gIMMgr->showSessionEventError( - body["event"].asString(), - body["error"].asString(), - session_id); - } - } -}; - -class LLViewerForceCloseChatterBoxSession: public LLHTTPNode -{ -public: - virtual void post(ResponsePtr response, - const LLSD& context, - const LLSD& input) const - { - LLUUID session_id; - std::string reason; - - session_id = input["body"]["session_id"].asUUID(); - reason = input["body"]["reason"].asString(); - - gIMMgr->showSessionForceClose(reason, session_id); - } -}; - -class LLViewerChatterBoxSessionAgentListUpdates : public LLHTTPNode -{ -public: - virtual void post( - ResponsePtr responder, - const LLSD& context, - const LLSD& input) const - { - const LLUUID& session_id = input["body"]["session_id"].asUUID(); - gIMMgr->processAgentListUpdates(session_id, input["body"]); - } -}; - -class LLViewerChatterBoxSessionUpdate : public LLHTTPNode -{ -public: - virtual void post( - ResponsePtr responder, - const LLSD& context, - const LLSD& input) const - { - LLUUID session_id = input["body"]["session_id"].asUUID(); - LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); - if ( im_floater ) - { - im_floater->processSessionUpdate(input["body"]["info"]); - } - LLIMSpeakerMgr* im_mgr = LLIMModel::getInstance()->getSpeakerManager(session_id); - if (im_mgr) - { - im_mgr->processSessionUpdate(input["body"]["info"]); - } - } -}; - - -class LLViewerChatterBoxInvitation : public LLHTTPNode -{ -public: - - virtual void post( - ResponsePtr response, - const LLSD& context, - const LLSD& input) const - { - //for backwards compatiblity reasons...we need to still - //check for 'text' or 'voice' invitations...bleh - if ( input["body"].has("instantmessage") ) - { - LLSD message_params = - input["body"]["instantmessage"]["message_params"]; - - //do something here to have the IM invite behave - //just like a normal IM - //this is just replicated code from process_improved_im - //and should really go in it's own function -jwolk - - std::string message = message_params["message"].asString(); - std::string name = message_params["from_name"].asString(); - LLUUID from_id = message_params["from_id"].asUUID(); - LLUUID session_id = message_params["id"].asUUID(); - std::vector bin_bucket = message_params["data"]["binary_bucket"].asBinary(); - U8 offline = (U8)message_params["offline"].asInteger(); - - time_t timestamp = - (time_t) message_params["timestamp"].asInteger(); - - bool is_do_not_disturb = gAgent.isDoNotDisturb(); - - //don't return if user is muted b/c proper way to ignore a muted user who - //initiated an adhoc/group conference is to create then leave the session (see STORM-1731) - if (is_do_not_disturb) - { - return; - } - - // standard message, not from system - std::string saved; - if(offline == IM_OFFLINE) - { - LLStringUtil::format_map_t args; - args["[LONG_TIMESTAMP]"] = formatted_time(timestamp); - saved = LLTrans::getString("Saved_message", args); - } - std::string buffer = saved + message; - - if(from_id == gAgentID) - { - return; - } - gIMMgr->addMessage( - session_id, - from_id, - name, - buffer, - IM_OFFLINE == offline, - std::string((char*)&bin_bucket[0]), - IM_SESSION_INVITE, - message_params["parent_estate_id"].asInteger(), - message_params["region_id"].asUUID(), - ll_vector3_from_sd(message_params["position"]), - false, // is_region_message - timestamp); - - if (LLMuteList::getInstance()->isMuted(from_id, name, LLMute::flagTextChat)) - { - return; - } - - //K now we want to accept the invitation - std::string url = gAgent.getRegionCapability("ChatSessionRequest"); - - if ( url != "" ) - { - LLCoros::instance().launch("chatterBoxInvitationCoro", - boost::bind(&chatterBoxInvitationCoro, url, - session_id, LLIMMgr::INVITATION_TYPE_INSTANT_MESSAGE)); - } - } //end if invitation has instant message - else if ( input["body"].has("voice") ) - { - if(!LLVoiceClient::getInstance()->voiceEnabled() || !LLVoiceClient::getInstance()->isVoiceWorking()) - { - // Don't display voice invites unless the user has voice enabled. - return; - } - - gIMMgr->inviteToSession( - input["body"]["session_id"].asUUID(), - input["body"]["session_name"].asString(), - input["body"]["from_id"].asUUID(), - input["body"]["from_name"].asString(), - IM_SESSION_INVITE, - LLIMMgr::INVITATION_TYPE_VOICE); - } - else if ( input["body"].has("immediate") ) - { - gIMMgr->inviteToSession( - input["body"]["session_id"].asUUID(), - input["body"]["session_name"].asString(), - input["body"]["from_id"].asUUID(), - input["body"]["from_name"].asString(), - IM_SESSION_INVITE, - LLIMMgr::INVITATION_TYPE_IMMEDIATE); - } - } -}; - -LLHTTPRegistration - gHTTPRegistrationMessageChatterboxsessionstartreply( - "/message/ChatterBoxSessionStartReply"); - -LLHTTPRegistration - gHTTPRegistrationMessageChatterboxsessioneventreply( - "/message/ChatterBoxSessionEventReply"); - -LLHTTPRegistration - gHTTPRegistrationMessageForceclosechatterboxsession( - "/message/ForceCloseChatterBoxSession"); - -LLHTTPRegistration - gHTTPRegistrationMessageChatterboxsessionagentlistupdates( - "/message/ChatterBoxSessionAgentListUpdates"); - -LLHTTPRegistration - gHTTPRegistrationMessageChatterBoxSessionUpdate( - "/message/ChatterBoxSessionUpdate"); - -LLHTTPRegistration - gHTTPRegistrationMessageChatterBoxInvitation( - "/message/ChatterBoxInvitation"); - +/** + * @file LLIMMgr.cpp + * @brief Container for Instant Messaging + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llimview.h" + +#include "llavatarnamecache.h" // IDEVO +#include "llavataractions.h" +#include "llfloaterconversationlog.h" +#include "llfloaterreg.h" +#include "llfontgl.h" +#include "llgl.h" +#include "llrect.h" +#include "llerror.h" +#include "llbutton.h" +#include "llsdutil_math.h" +#include "llstring.h" +#include "lltextutil.h" +#include "lltrans.h" +#include "lltranslate.h" +#include "lluictrlfactory.h" +#include "llfloaterimsessiontab.h" +#include "llagent.h" +#include "llagentui.h" +#include "llappviewer.h" +#include "llavatariconctrl.h" +#include "llcallingcard.h" +#include "llchat.h" +#include "llfloaterimsession.h" +#include "llfloaterimcontainer.h" +#include "llgroupiconctrl.h" +#include "llmd5.h" +#include "llmutelist.h" +#include "llrecentpeople.h" +#include "llviewermessage.h" +#include "llviewerwindow.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "llfloaterimnearbychat.h" +#include "llspeakers.h" //for LLIMSpeakerMgr +#include "lltextbox.h" +#include "lltoolbarview.h" +#include "llviewercontrol.h" +#include "llviewerparcelmgr.h" +#include "llconversationlog.h" +#include "message.h" +#include "llviewerregion.h" +#include "llcorehttputil.h" +#include "lluiusage.h" + +#include + +const static std::string ADHOC_NAME_SUFFIX(" Conference"); + +const static std::string NEARBY_P2P_BY_OTHER("nearby_P2P_by_other"); +const static std::string NEARBY_P2P_BY_AGENT("nearby_P2P_by_agent"); + +// Markers inserted around translated part of chat text +const static std::string XL8_START_TAG(" ("); +const static std::string XL8_END_TAG(")"); +const S32 XL8_PADDING = 3; // XL8_START_TAG.size() + XL8_END_TAG.size() + +/** Timeout of outgoing session initialization (in seconds) */ +const static U32 SESSION_INITIALIZATION_TIMEOUT = 30; + +void startConfrenceCoro(std::string url, LLUUID tempSessionId, LLUUID creatorId, LLUUID otherParticipantId, LLSD agents); +void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvitationType invitationType); +void chatterBoxHistoryCoro(std::string url, LLUUID sessionId, std::string from, std::string message, U32 timestamp); +void start_deprecated_conference_chat(const LLUUID& temp_session_id, const LLUUID& creator_id, const LLUUID& other_participant_id, const LLSD& agents_to_invite); + +const LLUUID LLOutgoingCallDialog::OCD_KEY = LLUUID("7CF78E11-0CFE-498D-ADB9-1417BF03DDB4"); +// +// Globals +// +LLIMMgr* gIMMgr = NULL; + + +bool LLSessionTimeoutTimer::tick() +{ + if (mSessionId.isNull()) return true; + + LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(mSessionId); + if (session && !session->mSessionInitialized) + { + gIMMgr->showSessionStartError("session_initialization_timed_out_error", mSessionId); + } + return true; +} + + +void notify_of_message(const LLSD& msg, bool is_dnd_msg); + +void process_dnd_im(const LLSD& notification) +{ + LLSD data = notification["substitutions"]; + LLUUID sessionID = data["SESSION_ID"].asUUID(); + LLUUID fromID = data["FROM_ID"].asUUID(); + + //re-create the IM session if needed + //(when coming out of DND mode upon app restart) + if(!gIMMgr->hasSession(sessionID)) + { + //reconstruct session using data from the notification + std::string name = data["FROM"]; + LLAvatarName av_name; + if (LLAvatarNameCache::get(data["FROM_ID"], &av_name)) + { + name = av_name.getDisplayName(); + } + + + LLIMModel::getInstance()->newSession(sessionID, + name, + IM_NOTHING_SPECIAL, + fromID, + false, + false); //will need slight refactor to retrieve whether offline message or not (assume online for now) + } + + notify_of_message(data, true); +} + + +static void on_avatar_name_cache_toast(const LLUUID& agent_id, + const LLAvatarName& av_name, + LLSD msg) +{ + LLSD args; + args["MESSAGE"] = msg["message"]; + args["TIME"] = msg["time"]; + // *TODO: Can this ever be an object name or group name? + args["FROM"] = av_name.getCompleteName(); + args["FROM_ID"] = msg["from_id"]; + args["SESSION_ID"] = msg["session_id"]; + args["SESSION_TYPE"] = msg["session_type"]; + LLNotificationsUtil::add("IMToast", args, args, boost::bind(&LLFloaterIMContainer::showConversation, LLFloaterIMContainer::getInstance(), msg["session_id"].asUUID())); +} + +void notify_of_message(const LLSD& msg, bool is_dnd_msg) +{ + std::string user_preferences; + LLUUID participant_id = msg[is_dnd_msg ? "FROM_ID" : "from_id"].asUUID(); + LLUUID session_id = msg[is_dnd_msg ? "SESSION_ID" : "session_id"].asUUID(); + LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(session_id); + + // do not show notification which goes from agent + if (gAgent.getID() == participant_id) + { + return; + } + + // determine state of conversations floater + enum {CLOSED, NOT_ON_TOP, ON_TOP, ON_TOP_AND_ITEM_IS_SELECTED} conversations_floater_status; + + + LLFloaterIMContainer* im_box = LLFloaterReg::getTypedInstance("im_container"); + LLFloaterIMSessionTab* session_floater = LLFloaterIMSessionTab::getConversation(session_id); + bool store_dnd_message = false; // flag storage of a dnd message + bool is_session_focused = session_floater->isTornOff() && session_floater->hasFocus(); + if (!LLFloater::isVisible(im_box) || im_box->isMinimized()) + { + conversations_floater_status = CLOSED; + } + else if (!im_box->hasFocus() && + !(session_floater && LLFloater::isVisible(session_floater) + && !session_floater->isMinimized() && session_floater->hasFocus())) + { + conversations_floater_status = NOT_ON_TOP; + } + else if (im_box->getSelectedSession() != session_id) + { + conversations_floater_status = ON_TOP; + } + else + { + conversations_floater_status = ON_TOP_AND_ITEM_IS_SELECTED; + } + + // determine user prefs for this session + if (session_id.isNull()) + { + if (msg["source_type"].asInteger() == CHAT_SOURCE_OBJECT) + { + user_preferences = gSavedSettings.getString("NotificationObjectIMOptions"); + if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundObjectIM"))) + { + make_ui_sound("UISndNewIncomingIMSession"); + } + } + else + { + user_preferences = gSavedSettings.getString("NotificationNearbyChatOptions"); + if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNearbyChatIM"))) + { + make_ui_sound("UISndNewIncomingIMSession"); + } + } + } + else if (session->isP2PSessionType()) + { + if (LLAvatarTracker::instance().isBuddy(participant_id)) + { + user_preferences = gSavedSettings.getString("NotificationFriendIMOptions"); + if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundFriendIM"))) + { + make_ui_sound("UISndNewIncomingIMSession"); + } + } + else + { + user_preferences = gSavedSettings.getString("NotificationNonFriendIMOptions"); + if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNonFriendIM"))) + { + make_ui_sound("UISndNewIncomingIMSession"); + } + } + } + else if (session->isAdHocSessionType()) + { + user_preferences = gSavedSettings.getString("NotificationConferenceIMOptions"); + if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundConferenceIM"))) + { + make_ui_sound("UISndNewIncomingIMSession"); + } + } + else if(session->isGroupSessionType()) + { + user_preferences = gSavedSettings.getString("NotificationGroupChatOptions"); + if (!gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundGroupChatIM"))) + { + make_ui_sound("UISndNewIncomingIMSession"); + } + } + + // actions: + + // 0. nothing - exit + if (("noaction" == user_preferences || + ON_TOP_AND_ITEM_IS_SELECTED == conversations_floater_status) + && session_floater->isMessagePaneExpanded()) + { + return; + } + + // 1. open floater and [optional] surface it + if ("openconversations" == user_preferences && + (CLOSED == conversations_floater_status + || NOT_ON_TOP == conversations_floater_status)) + { + if(!gAgent.isDoNotDisturb()) + { + if(!LLAppViewer::instance()->quitRequested() && !LLFloater::isVisible(im_box)) + { + // Open conversations floater + LLFloaterReg::showInstance("im_container"); + } + im_box->collapseMessagesPane(false); + if (session_floater) + { + if (session_floater->getHost()) + { + if (NULL != im_box && im_box->isMinimized()) + { + LLFloater::onClickMinimize(im_box); + } + } + else + { + if (session_floater->isMinimized()) + { + LLFloater::onClickMinimize(session_floater); + } + } + } + } + else + { + store_dnd_message = true; + } + } + + // 2. Flash line item + if ("openconversations" == user_preferences + || ON_TOP == conversations_floater_status + || ("toast" == user_preferences && ON_TOP != conversations_floater_status) + || ("flash" == user_preferences && (CLOSED == conversations_floater_status + || NOT_ON_TOP == conversations_floater_status)) + || is_dnd_msg) + { + if(!LLMuteList::getInstance()->isMuted(participant_id)) + { + if(gAgent.isDoNotDisturb()) + { + store_dnd_message = true; + } + else + { + if (is_dnd_msg && (ON_TOP == conversations_floater_status || + NOT_ON_TOP == conversations_floater_status || + CLOSED == conversations_floater_status)) + { + im_box->highlightConversationItemWidget(session_id, true); + } + else + { + im_box->flashConversationItemWidget(session_id, true); + } + } + } + } + + // 3. Flash FUI button + if (("toast" == user_preferences || "flash" == user_preferences) && + (CLOSED == conversations_floater_status + || NOT_ON_TOP == conversations_floater_status) + && !is_session_focused + && !is_dnd_msg) //prevent flashing FUI button because the conversation floater will have already opened + { + if(!LLMuteList::getInstance()->isMuted(participant_id)) + { + if(!gAgent.isDoNotDisturb()) + { + gToolBarView->flashCommand(LLCommandId("chat"), true, im_box->isMinimized()); + } + else + { + store_dnd_message = true; + } + } + } + + // 4. Toast + if ((("toast" == user_preferences) && + (ON_TOP_AND_ITEM_IS_SELECTED != conversations_floater_status) && + (!session_floater->isTornOff() || !LLFloater::isVisible(session_floater))) + || !session_floater->isMessagePaneExpanded()) + + { + //Show IM toasts (upper right toasts) + // Skip toasting for system messages and for nearby chat + if(session_id.notNull() && participant_id.notNull()) + { + if(!is_dnd_msg) + { + if(gAgent.isDoNotDisturb()) + { + store_dnd_message = true; + } + else + { + LLAvatarNameCache::get(participant_id, boost::bind(&on_avatar_name_cache_toast, _1, _2, msg)); + } + } +} + } + if (store_dnd_message) + { + // If in DND mode, allow notification to be stored so upon DND exit + // the user will be notified with some limitations (see 'is_dnd_msg' flag checks) + if(session_id.notNull() + && participant_id.notNull() + && !session_floater->isShown()) + { + LLAvatarNameCache::get(participant_id, boost::bind(&on_avatar_name_cache_toast, _1, _2, msg)); + } + } +} + +void on_new_message(const LLSD& msg) +{ + notify_of_message(msg, false); +} + +void startConfrenceCoro(std::string url, + LLUUID tempSessionId, LLUUID creatorId, LLUUID otherParticipantId, LLSD agents) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ConferenceChatStart", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD postData; + postData["method"] = "start conference"; + postData["session-id"] = tempSessionId; + postData["params"] = agents; + + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS("LLIMModel") << "Failed to start conference" << LL_ENDL; + //try an "old school" way. + // *TODO: What about other error status codes? 4xx 5xx? + if (status == LLCore::HttpStatus(HTTP_BAD_REQUEST)) + { + start_deprecated_conference_chat( + tempSessionId, + creatorId, + otherParticipantId, + agents); + } + + //else throw an error back to the client? + //in theory we should have just have these error strings + //etc. set up in this file as opposed to the IMMgr, + //but the error string were unneeded here previously + //and it is not worth the effort switching over all + //the possible different language translations + } +} + +void chatterBoxInvitationCoro(std::string url, LLUUID sessionId, LLIMMgr::EInvitationType invitationType) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ConferenceInviteStart", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD postData; + postData["method"] = "accept invitation"; + postData["session-id"] = sessionId; + + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!gIMMgr) + { + LL_WARNS("") << "Global IM Manager is NULL" << LL_ENDL; + return; + } + + if (!status) + { + LL_WARNS("LLIMModel") << "Bad HTTP response in chatterBoxInvitationCoro" << LL_ENDL; + //throw something back to the viewer here? + + gIMMgr->clearPendingAgentListUpdates(sessionId); + gIMMgr->clearPendingInvitation(sessionId); + + if (status == LLCore::HttpStatus(HTTP_NOT_FOUND)) + { + static const std::string error_string("session_does_not_exist_error"); + gIMMgr->showSessionStartError(error_string, sessionId); + } + return; + } + + result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); + + LLIMSpeakerMgr* speakerMgr = LLIMModel::getInstance()->getSpeakerManager(sessionId); + if (speakerMgr) + { + //we've accepted our invitation + //and received a list of agents that were + //currently in the session when the reply was sent + //to us. Now, it is possible that there were some agents + //to slip in/out between when that message was sent to us + //and now. + + //the agent list updates we've received have been + //accurate from the time we were added to the session + //but unfortunately, our base that we are receiving here + //may not be the most up to date. It was accurate at + //some point in time though. + speakerMgr->setSpeakers(result); + + //we now have our base of users in the session + //that was accurate at some point, but maybe not now + //so now we apply all of the updates we've received + //in case of race conditions + speakerMgr->updateSpeakers(gIMMgr->getPendingAgentListUpdates(sessionId)); + } + + if (LLIMMgr::INVITATION_TYPE_VOICE == invitationType) + { + gIMMgr->startCall(sessionId, LLVoiceChannel::INCOMING_CALL); + } + + if ((invitationType == LLIMMgr::INVITATION_TYPE_VOICE + || invitationType == LLIMMgr::INVITATION_TYPE_IMMEDIATE) + && LLIMModel::getInstance()->findIMSession(sessionId)) + { + // TODO remove in 2010, for voice calls we do not open an IM window + //LLFloaterIMSession::show(mSessionID); + } + + gIMMgr->clearPendingAgentListUpdates(sessionId); + gIMMgr->clearPendingInvitation(sessionId); + +} + +void translateSuccess(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text, + U64 time_n_flags, std::string originalMsg, std::string expectLang, std::string translation, const std::string detected_language) +{ + std::string message_txt(utf8_text); + // filter out non-interesting responses + if (!translation.empty() + && ((detected_language.empty()) || (expectLang != detected_language)) + && (LLStringUtil::compareInsensitive(translation, originalMsg) != 0)) + { // Note - if this format changes, also fix code in addMessagesFromServerHistory() + message_txt += XL8_START_TAG + LLTranslate::removeNoTranslateTags(translation) + XL8_END_TAG; + } + + // Extract info packed in time_n_flags + bool log2file = (bool)(time_n_flags & (1LL << 32)); + bool is_region_msg = (bool)(time_n_flags & (1LL << 33)); + U32 time_stamp = (U32)(time_n_flags & 0x00000000ffffffff); + + LLIMModel::getInstance()->processAddingMessage(session_id, from, from_id, message_txt, log2file, is_region_msg, time_stamp); +} + +void translateFailure(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text, + U64 time_n_flags, int status, const std::string err_msg) +{ + std::string message_txt(utf8_text); + std::string msg = LLTrans::getString("TranslationFailed", LLSD().with("[REASON]", err_msg)); + LLStringUtil::replaceString(msg, "\n", " "); // we want one-line error messages + message_txt += XL8_START_TAG + msg + XL8_END_TAG; + + // Extract info packed in time_n_flags + bool log2file = (bool)(time_n_flags & (1LL << 32)); + bool is_region_msg = (bool)(time_n_flags & (1LL << 33)); + U32 time_stamp = (U32)(time_n_flags & 0x00000000ffffffff); + + LLIMModel::getInstance()->processAddingMessage(session_id, from, from_id, message_txt, log2file, is_region_msg, time_stamp); +} + +void chatterBoxHistoryCoro(std::string url, LLUUID sessionId, std::string from, std::string message, U32 timestamp) +{ // if parameters from, message and timestamp have values, they are a message that opened chat + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("ChatHistory", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD postData; + postData["method"] = "fetch history"; + postData["session-id"] = sessionId; + + LL_DEBUGS("ChatHistory") << sessionId << ": Chat history posting " << postData << " to " << url + << ", from " << from << ", message " << message << ", timestamp " << (S32)timestamp << LL_ENDL; + + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS("ChatHistory") << sessionId << ": Bad HTTP response in chatterBoxHistoryCoro" + << ", results: " << httpResults << LL_ENDL; + return; + } + + if (LLApp::isExiting() || gDisconnected) + { + LL_DEBUGS("ChatHistory") << "Ignoring chat history response, shutting down" << LL_ENDL; + return; + } + + // Add history to IM session + LLSD history = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT]; + + LL_DEBUGS("ChatHistory") << sessionId << ": Chat server history fetch returned " << history << LL_ENDL; + + try + { + LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(sessionId); + if (session && history.isArray()) + { // Result array is sorted oldest to newest + if (history.size() > 0) + { // History from the chat server has an integer 'time' value timestamp. Create 'datetime' string which will match + // what we have from the local history cache + for (LLSD::array_iterator cur_server_hist = history.beginArray(), endLists = history.endArray(); + cur_server_hist != endLists; + cur_server_hist++) + { + if ((*cur_server_hist).isMap()) + { // Take the 'time' value from the server and make the date-time string that will be in local cache log files + // {'from_id':u7aa8c222-8a81-450e-b3d1-9c28491ef717,'message':'Can you hear me now?','from':'Chat Tester','num':i86,'time':r1.66501e+09} + U32 timestamp = (U32)((*cur_server_hist)[LL_IM_TIME].asInteger()); + (*cur_server_hist)[LL_IM_DATE_TIME] = LLLogChat::timestamp2LogString(timestamp, true); + } + } + + session->addMessagesFromServerHistory(history, from, message, timestamp); + + // Display the newly added messages + LLFloaterIMSession* floater = LLFloaterReg::findTypedInstance("impanel", sessionId); + if (floater && floater->isInVisibleChain()) + { + floater->updateMessages(); + } + } + else + { + LL_DEBUGS("ChatHistory") << sessionId << ": Empty history from chat server, nothing to add" << LL_ENDL; + } + } + else if (session && !history.isArray()) + { + LL_WARNS("ChatHistory") << sessionId << ": Bad array data fetching chat history" << LL_ENDL; + } + else + { + LL_WARNS("ChatHistory") << sessionId << ": Unable to find session fetching chat history" << LL_ENDL; + } + } + catch (...) + { + LOG_UNHANDLED_EXCEPTION("chatterBoxHistoryCoro"); + LL_WARNS("ChatHistory") << "chatterBoxHistoryCoro unhandled exception while processing data for session " << sessionId << LL_ENDL; + } +} + +LLIMModel::LLIMModel() +{ + addNewMsgCallback(boost::bind(&LLFloaterIMSession::newIMCallback, _1)); + addNewMsgCallback(boost::bind(&on_new_message, _1)); + LLCallDialogManager::instance(); +} + +LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice, bool has_offline_msg) +: mSessionID(session_id), + mName(name), + mType(type), + mHasOfflineMessage(has_offline_msg), + mParticipantUnreadMessageCount(0), + mNumUnread(0), + mOtherParticipantID(other_participant_id), + mInitialTargetIDs(ids), + mVoiceChannel(NULL), + mSpeakers(NULL), + mSessionInitialized(false), + mCallBackEnabled(true), + mTextIMPossible(true), + mStartCallOnInitialize(false), + mStartedAsIMCall(voice), + mIsDNDsend(false), + mAvatarNameCacheConnection() +{ + // set P2P type by default + mSessionType = P2P_SESSION; + + if (IM_NOTHING_SPECIAL == mType || IM_SESSION_P2P_INVITE == mType) + { + mVoiceChannel = new LLVoiceChannelP2P(session_id, name, other_participant_id); + } + else + { + mVoiceChannel = new LLVoiceChannelGroup(session_id, name); + + // determine whether it is group or conference session + if (gAgent.isInGroup(mSessionID)) + { + mSessionType = GROUP_SESSION; + } + else + { + mSessionType = ADHOC_SESSION; + } + } + + if(mVoiceChannel) + { + mVoiceChannelStateChangeConnection = mVoiceChannel->setStateChangedCallback(boost::bind(&LLIMSession::onVoiceChannelStateChanged, this, _1, _2, _3)); + } + + mSpeakers = new LLIMSpeakerMgr(mVoiceChannel); + + // All participants will be added to the list of people we've recently interacted with. + + // we need to add only _active_ speakers...so comment this. + // may delete this later on cleanup + //mSpeakers->addListener(&LLRecentPeople::instance(), "add"); + + //we need to wait for session initialization for outgoing ad-hoc and group chat session + //correct session id for initiated ad-hoc chat will be received from the server + if (!LLIMModel::getInstance()->sendStartSession(mSessionID, mOtherParticipantID, + mInitialTargetIDs, mType)) + { + //we don't need to wait for any responses + //so we're already initialized + mSessionInitialized = true; + } + else + { + //tick returns true - timer will be deleted after the tick + new LLSessionTimeoutTimer(mSessionID, SESSION_INITIALIZATION_TIMEOUT); + } + + if (IM_NOTHING_SPECIAL == mType) + { + mCallBackEnabled = LLVoiceClient::getInstance()->isSessionCallBackPossible(mSessionID); + mTextIMPossible = LLVoiceClient::getInstance()->isSessionTextIMPossible(mSessionID); + } + + buildHistoryFileName(); + loadHistory(); + + // Localizing name of ad-hoc session. STORM-153 + // Changing name should happen here- after the history file was created, so that + // history files have consistent (English) names in different locales. + if (isAdHocSessionType() && IM_SESSION_INVITE == mType) + { + mAvatarNameCacheConnection = LLAvatarNameCache::get(mOtherParticipantID,boost::bind(&LLIMModel::LLIMSession::onAdHocNameCache,this, _2)); + } +} + +void LLIMModel::LLIMSession::onAdHocNameCache(const LLAvatarName& av_name) +{ + mAvatarNameCacheConnection.disconnect(); + + if (!av_name.isValidName()) + { + S32 separator_index = mName.rfind(" "); + std::string name = mName.substr(0, separator_index); + ++separator_index; + std::string conference_word = mName.substr(separator_index, mName.length()); + + // additional check that session name is what we expected + if ("Conference" == conference_word) + { + LLStringUtil::format_map_t args; + args["[AGENT_NAME]"] = name; + LLTrans::findString(mName, "conference-title-incoming", args); + } + } + else + { + LLStringUtil::format_map_t args; + args["[AGENT_NAME]"] = av_name.getCompleteName(); + LLTrans::findString(mName, "conference-title-incoming", args); + } +} + +void LLIMModel::LLIMSession::onVoiceChannelStateChanged(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state, const LLVoiceChannel::EDirection& direction) +{ + std::string you_joined_call = LLTrans::getString("you_joined_call"); + std::string you_started_call = LLTrans::getString("you_started_call"); + std::string other_avatar_name = ""; + LLAvatarName av_name; + + std::string message; + + switch(mSessionType) + { + case P2P_SESSION: + LLAvatarNameCache::get(mOtherParticipantID, &av_name); + other_avatar_name = av_name.getUserName(); + + if(direction == LLVoiceChannel::INCOMING_CALL) + { + switch(new_state) + { + case LLVoiceChannel::STATE_CALL_STARTED : + { + LLStringUtil::format_map_t string_args; + string_args["[NAME]"] = other_avatar_name; + message = LLTrans::getString("name_started_call", string_args); + LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, message); + break; + } + case LLVoiceChannel::STATE_CONNECTED : + LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, you_joined_call); + default: + break; + } + } + else // outgoing call + { + switch(new_state) + { + case LLVoiceChannel::STATE_CALL_STARTED : + LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, you_started_call); + break; + case LLVoiceChannel::STATE_CONNECTED : + message = LLTrans::getString("answered_call"); + LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, message); + default: + break; + } + } + break; + + case GROUP_SESSION: + case ADHOC_SESSION: + if(direction == LLVoiceChannel::INCOMING_CALL) + { + switch(new_state) + { + case LLVoiceChannel::STATE_CONNECTED : + LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, you_joined_call); + default: + break; + } + } + else // outgoing call + { + switch(new_state) + { + case LLVoiceChannel::STATE_CALL_STARTED : + LLIMModel::getInstance()->addMessage(mSessionID, SYSTEM_FROM, LLUUID::null, you_started_call); + break; + default: + break; + } + } + default: + break; + } + // Update speakers list when connected + if (LLVoiceChannel::STATE_CONNECTED == new_state) + { + mSpeakers->update(true); + } +} + +LLIMModel::LLIMSession::~LLIMSession() +{ + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + + delete mSpeakers; + mSpeakers = NULL; + + // End the text IM session if necessary + if(LLVoiceClient::getInstance() && mOtherParticipantID.notNull()) + { + switch(mType) + { + case IM_NOTHING_SPECIAL: + case IM_SESSION_P2P_INVITE: + LLVoiceClient::getInstance()->endUserIMSession(mOtherParticipantID); + break; + + default: + // Appease the linux compiler + break; + } + } + + mVoiceChannelStateChangeConnection.disconnect(); + + // HAVE to do this here -- if it happens in the LLVoiceChannel destructor it will call the wrong version (since the object's partially deconstructed at that point). + mVoiceChannel->deactivate(); + + delete mVoiceChannel; + mVoiceChannel = NULL; +} + +void LLIMModel::LLIMSession::sessionInitReplyReceived(const LLUUID& new_session_id) +{ + mSessionInitialized = true; + + if (new_session_id != mSessionID) + { + mSessionID = new_session_id; + mVoiceChannel->updateSessionID(new_session_id); + } +} + +void LLIMModel::LLIMSession::addMessage(const std::string& from, + const LLUUID& from_id, + const std::string& utf8_text, + const std::string& time, + const bool is_history, // comes from a history file or chat server + const bool is_region_msg, + const U32 timestamp) // may be zero +{ + LLSD message; + message["from"] = from; + message["from_id"] = from_id; + message["message"] = utf8_text; + message["time"] = time; // string used in display, may be full data YYYY/MM/DD HH:MM or just HH:MM + message["timestamp"] = (S32)timestamp; // use string? LLLogChat::timestamp2LogString(timestamp, true); + message["index"] = (LLSD::Integer)mMsgs.size(); + message["is_history"] = is_history; + message["is_region_msg"] = is_region_msg; + + LL_DEBUGS("UIUsage") << "addMessage " << " from " << from << " from_id " << from_id << " utf8_text " << utf8_text << " time " << time << " is_history " << is_history << " session mType " << mType << LL_ENDL; + if (from_id == gAgent.getID()) + { + if (mType == IM_SESSION_GROUP_START) + { + LLUIUsage::instance().logCommand("Chat.SendGroup"); + } + else if (mType == IM_NOTHING_SPECIAL) + { + LLUIUsage::instance().logCommand("Chat.SendIM"); + } + else + { + LLUIUsage::instance().logCommand("Chat.SendOther"); + } + } + + mMsgs.push_front(message); // Add most recent messages to the front of mMsgs + + if (mSpeakers && from_id.notNull()) + { + mSpeakers->speakerChatted(from_id); + mSpeakers->setSpeakerTyping(from_id, false); + } +} + +void LLIMModel::LLIMSession::addMessagesFromHistoryCache(const chat_message_list_t& history) +{ + // Add the messages from the local cached chat history to the session window + for (const auto& msg : history) + { + std::string from = msg[LL_IM_FROM]; + LLUUID from_id; + if (msg[LL_IM_FROM_ID].isDefined()) + { + from_id = msg[LL_IM_FROM_ID].asUUID(); + } + else + { // convert it to a legacy name if we have a complete name + std::string legacy_name = gCacheName->buildLegacyName(from); + from_id = LLAvatarNameCache::getInstance()->findIdByName(legacy_name); + } + + // Save the last minute of messages so we can merge with the chat server history. + // Really would be nice to have a numeric timestamp in the local cached chat file + const std::string & msg_time_str = msg[LL_IM_DATE_TIME].asString(); + if (mLastHistoryCacheDateTime != msg_time_str) + { + mLastHistoryCacheDateTime = msg_time_str; // Reset to the new time + mLastHistoryCacheMsgs.clear(); + } + mLastHistoryCacheMsgs.push_front(msg); + LL_DEBUGS("ChatHistory") << mSessionID << ": Adding history cache message: " << msg << LL_ENDL; + + // Add message from history cache to the display + addMessage(from, from_id, msg[LL_IM_TEXT], msg[LL_IM_TIME], true, false, 0); // from history data, not region message, no timestamp + } +} + +void LLIMModel::LLIMSession::addMessagesFromServerHistory(const LLSD& history, // Array of chat messages from chat server + const std::string& target_from, // Sender of message that opened chat + const std::string& target_message, // Message text that opened chat + U32 timestamp) // timestamp of message that opened chat +{ // Add messages from history returned by the chat server. + + // The session mMsgs may contain chat messages from the local history cache file, and possibly one or more newly + // arrived chat messages. If the chat window was manually opened, these will be empty and history can + // more easily merged. The history from the server, however, may overlap what is in the file and those must also be merged. + + // At this point, the session mMsgs can have + // no messages + // nothing from history file cache, but one or more very recently arrived messages, + // messages from history file cache, no recent chat + // messages from history file cache, one or more very recent messages + // + // The chat history from server can possibly contain: + // no messages + // messages that start back before anything in the local file (obscure case, but possible) + // messages that match messages from the history file cache + // messages from the last hour, new to the viewer + // one or more messages that match most recently received chat (the one that opened the window) + // In other words: + // messages from chat server may or may not match what we already have in mMsgs + // We can drop anything that is during the time span covered by the local cache file + // To keep things simple, drop any chat data older than the local cache file + + if (!history.isArray()) + { + LL_WARNS("ChatHistory") << mSessionID << ": Unexpected history data not array, type " << (S32)history.type() << LL_ENDL; + return; + } + + if (history.size() == 0) + { // If history is empty + LL_DEBUGS("ChatHistory") << mSessionID << ": addMessagesFromServerHistory() has empty history, nothing to merge" << LL_ENDL; + return; + } + + if (history.size() == 1 && // Server chat history has one entry, + target_from.length() > 0 && // and we have a chat message that just arrived + mMsgs.size() > 0) // and we have some data in the window - assume the history message is there. + { // This is the common case where a group chat is silent for a while, and then one message is sent. + LL_DEBUGS("ChatHistory") << mSessionID << ": addMessagesFromServerHistory() only has chat message just received." << LL_ENDL; + return; + } + + LL_DEBUGS("ChatHistory") << mSessionID << ": addMessagesFromServerHistory() starting with mMsg.size() " << mMsgs.size() + << " adding history with " << history.size() << " messages" + << ", target_from: " << target_from + << ", target_message: " << target_message + << ", timestamp: " << (S32)timestamp << LL_ENDL; + + // At start of merging, mMsgs is either empty, has some chat messages read from a local cache file, and may have + // one or more messages that just arrived from the server. + U32 match_timestamp = 0; + chat_message_list_t shift_msgs; + if (mMsgs.size() > 0 && + target_from.length() > 0 + && target_message.length() > 0) + { // Find where to insert the history messages by popping off a few in the session. + // The most common case is one duplciate message, the one that opens a chat session + while (mMsgs.size() > 0) + { + // The "time" value from mMsgs is a string, either just time HH:MM or a full date and time + LLSD cur_msg = mMsgs.front(); // Get most recent message from the chat display (front of mMsgs list) + + if (cur_msg.isMap()) + { + LL_DEBUGS("ChatHistoryCompare") << mSessionID << ": Finding insertion point, looking at cur_msg: " << cur_msg << LL_ENDL; + + match_timestamp = cur_msg["timestamp"].asInteger(); // get timestamp of message in the session, may be zero + if ((S32)timestamp > match_timestamp) + { + LL_DEBUGS("ChatHistory") << mSessionID << ": found older chat message: " << cur_msg + << ", timestamp " << (S32)timestamp + << " vs. match_timestamp " << match_timestamp + << ", shift_msgs size is " << shift_msgs.size() << LL_ENDL; + break; + } + // Have the matching message or one more recent: these need to be at the end + shift_msgs.push_front(cur_msg); // Move chat message to temp list. + mMsgs.pop_front(); // Normally this is just one message + LL_DEBUGS("ChatHistory") << mSessionID << ": shifting chat message " << cur_msg + << " to be inserted at end, shift_msgs size is " << shift_msgs.size() + << ", match_timestamp " << match_timestamp + << ", timestamp " << (S32)timestamp << LL_ENDL; + } + else + { + LL_DEBUGS("ChatHistory") << mSessionID << ": Unexpected non-map entry in session messages: " << cur_msg << LL_ENDL; + return; + } + } + } + + // Now merge messages from server history data into the session display. The history data + // from the local file may overlap with the chat messages from the server. + // Drop any messages from the chat server history that are before the latest one from the local history file. + // Unfortunately, messages from the local file don't have timestamps - just datetime strings + LLSD::array_const_iterator cur_history_iter = history.beginArray(); + while (cur_history_iter != history.endArray()) + { + const LLSD &cur_server_hist = *cur_history_iter; + cur_history_iter++; + + if (cur_server_hist.isMap()) + { // Each server history entry looks like + // { 'from':'Laggy Avatar', 'from_id' : u72345678 - 744f - 43b9 - 98af - b06f1c76ddda, 'index' : i24, 'is_history' : 1, 'message' : 'That was slow', 'time' : '02/13/2023 10:03', 'timestamp' : i1676311419 } + + // If we reach the message that opened our window, stop adding messages + U32 history_msg_timestamp = (U32)cur_server_hist[LL_IM_TIME].asInteger(); + if ((match_timestamp > 0 && match_timestamp <= history_msg_timestamp) || + (timestamp > 0 && timestamp <= history_msg_timestamp)) + { // we found the message we matched, so stop inserting from chat server history + LL_DEBUGS("ChatHistoryCompare") << "Found end of chat history insertion with match_timestamp " << (S32)match_timestamp + << " vs. history_msg_timestamp " << (S32)history_msg_timestamp + << " vs. timestamp " << (S32)timestamp + << LL_ENDL; + break; + } + LL_DEBUGS("ChatHistoryCompare") << "Compared match_timestamp " << (S32)match_timestamp + << " vs. history_msg_timestamp " << (S32)history_msg_timestamp << LL_ENDL; + + bool add_chat_to_conversation = true; + if (!mLastHistoryCacheDateTime.empty()) + { // Skip past the any from server that are older than what we already read from the history file. + std::string history_datetime = cur_server_hist[LL_IM_DATE_TIME].asString(); + if (history_datetime.empty()) + { + history_datetime = cur_server_hist[LL_IM_TIME].asString(); + } + + if (history_datetime < mLastHistoryCacheDateTime) + { + LL_DEBUGS("ChatHistoryCompare") << "Skipping message from chat server history since it's older than messages the session already has." + << history_datetime << " vs " << mLastHistoryCacheDateTime << LL_ENDL; + add_chat_to_conversation = false; + } + else if (history_datetime > mLastHistoryCacheDateTime) + { // The message from the chat server is more recent than the last one from the local cache file. Add it + LL_DEBUGS("ChatHistoryCompare") << "Found message dated " + << history_datetime << " vs " << mLastHistoryCacheDateTime + << ", adding new message from chat server history " << cur_server_hist << LL_ENDL; + } + else // (history_datetime == mLastHistoryCacheDateTime) + { // Messages are in the same minute as the last from the cache log file. + const std::string & history_msg_text = cur_server_hist[LL_IM_TEXT]; + + // Look in the saved messages from the history file that have the same time + for (const auto& scan_msg : mLastHistoryCacheMsgs) + { + LL_DEBUGS("ChatHistoryCompare") << "comparing messages " << scan_msg[LL_IM_TEXT] + << " with " << cur_server_hist << LL_ENDL; + if (scan_msg.size() > 0) + { // Extra work ... the history_msg_text value may have been translated, i.e. "I am confused (je suis confus)" + // while the server history will only have the first part "I am confused" + std::string target_compare(scan_msg[LL_IM_TEXT]); + if (target_compare.size() > history_msg_text.size() + XL8_PADDING && + target_compare.substr(history_msg_text.size(), XL8_START_TAG.size()) == XL8_START_TAG && + target_compare.substr(target_compare.size() - XL8_END_TAG.size()) == XL8_END_TAG) + { // This really looks like a "translated string (cadena traducida)" so just compare the source part + LL_DEBUGS("ChatHistory") << mSessionID << ": Found translated chat " << target_compare + << " when comparing to history " << history_msg_text + << ", will truncate" << LL_ENDL; + target_compare = target_compare.substr(0, history_msg_text.size()); + } + if (history_msg_text == target_compare) + { // Found a match, so don't add a duplicate chat message to the window + LL_DEBUGS("ChatHistory") << mSessionID << ": Found duplicate message text " << history_msg_text + << " : " << (S32)history_msg_timestamp << ", matching datetime " << history_datetime << LL_ENDL; + add_chat_to_conversation = false; + break; + } + } + } + } + } + + LLUUID sender_id = cur_server_hist[LL_IM_FROM_ID].asUUID(); + if (add_chat_to_conversation) + { // Check if they're muted + if (LLMuteList::getInstance()->isMuted(sender_id, LLMute::flagTextChat)) + { + add_chat_to_conversation = false; + LL_DEBUGS("ChatHistory") << mSessionID << ": Skipped adding chat from " << sender_id + << " as muted, message: " << cur_server_hist + << LL_ENDL; + } + } + + if (add_chat_to_conversation) + { // Finally add message to the chat session + std::string chat_time_str = LLConversation::createTimestamp((U64Seconds)history_msg_timestamp); + std::string sender_name = cur_server_hist[LL_IM_FROM].asString(); + + std::string history_msg_text = cur_server_hist[LL_IM_TEXT].asString(); + LLSD message; + message["from"] = sender_name; + message["from_id"] = sender_id; + message["message"] = history_msg_text; + message["time"] = chat_time_str; + message["timestamp"] = (S32)history_msg_timestamp; + message["index"] = (LLSD::Integer)mMsgs.size(); + message["is_history"] = true; + mMsgs.push_front(message); + + LL_DEBUGS("ChatHistory") << mSessionID << ": push_front() adding group chat history message " << message << LL_ENDL; + + // Add chat history messages to the local cache file, only in the case where we opened the chat window + // Need to solve the logic around messages that arrive and open chat - at this point, they've already been added to the + // local history cache file. If we append messages here, it will be out of order. + if (target_from.empty() && target_message.empty()) + { + LLIMModel::getInstance()->logToFile(LLIMModel::getInstance()->getHistoryFileName(mSessionID), + sender_name, sender_id, history_msg_text); + } + } + } + } + + S32 shifted_size = shift_msgs.size(); + while (shift_msgs.size() > 0) + { // Finally add back any new messages, and tweak the index value to be correct. + LLSD newer_message = shift_msgs.front(); + shift_msgs.pop_front(); + S32 old_index = newer_message["index"]; + newer_message["index"] = (LLSD::Integer)mMsgs.size(); // Update the index to match the new position in the conversation + LL_DEBUGS("ChatHistory") << mSessionID << ": Re-adding newest group chat history messages from " << newer_message["from"] + << ", text: " << newer_message["message"] + << " old index " << old_index << ", new index " << newer_message["index"] << LL_ENDL; + mMsgs.push_front(newer_message); + } + + LL_DEBUGS("ChatHistory") << mSessionID << ": addMessagesFromServerHistory() exiting with mMsg.size() " << mMsgs.size() + << ", shifted " << shifted_size << " messages" << LL_ENDL; + + mLastHistoryCacheDateTime.clear(); // Don't need this data + mLastHistoryCacheMsgs.clear(); +} + + +void LLIMModel::LLIMSession::chatFromLogFile(LLLogChat::ELogLineType type, const LLSD& msg, void* userdata) +{ + if (!userdata) return; + + LLIMSession* self = (LLIMSession*) userdata; + + if (type == LLLogChat::LOG_LINE) + { + LL_DEBUGS("ChatHistory") << "chatFromLogFile() adding LOG_LINE message from " << msg << LL_ENDL; + self->addMessage("", LLSD(), msg["message"].asString(), "", true, false, 0); // from history data, not region message, no timestamp + } + else if (type == LLLogChat::LOG_LLSD) + { + LL_DEBUGS("ChatHistory") << "chatFromLogFile() adding LOG_LLSD message from " << msg << LL_ENDL; + self->addMessage(msg["from"].asString(), msg["from_id"].asUUID(), msg["message"].asString(), msg["time"].asString(), true, false, 0); // from history data, not region message, no timestamp + } +} + +void LLIMModel::LLIMSession::loadHistory() +{ + mMsgs.clear(); + mLastHistoryCacheMsgs.clear(); + mLastHistoryCacheDateTime.clear(); + + if ( gSavedPerAccountSettings.getBOOL("LogShowHistory") ) + { + // read and parse chat history from local file + chat_message_list_t chat_history; + LLLogChat::loadChatHistory(mHistoryFileName, chat_history, LLSD(), isGroupChat()); + addMessagesFromHistoryCache(chat_history); + } +} + +LLIMModel::LLIMSession* LLIMModel::findIMSession(const LLUUID& session_id) const +{ + return get_if_there(mId2SessionMap, session_id, (LLIMModel::LLIMSession*) NULL); +} + +//*TODO consider switching to using std::set instead of std::list for holding LLUUIDs across the whole code +LLIMModel::LLIMSession* LLIMModel::findAdHocIMSession(const uuid_vec_t& ids) +{ + S32 num = ids.size(); + if (!num) return NULL; + + if (mId2SessionMap.empty()) return NULL; + + std::map::const_iterator it = mId2SessionMap.begin(); + for (; it != mId2SessionMap.end(); ++it) + { + LLIMSession* session = (*it).second; + + if (!session->isAdHoc()) continue; + if (session->mInitialTargetIDs.size() != num) continue; + + std::list tmp_list(session->mInitialTargetIDs.begin(), session->mInitialTargetIDs.end()); + + uuid_vec_t::const_iterator iter = ids.begin(); + while (iter != ids.end()) + { + tmp_list.remove(*iter); + ++iter; + + if (tmp_list.empty()) + { + break; + } + } + + if (tmp_list.empty() && iter == ids.end()) + { + return session; + } + } + + return NULL; +} + +bool LLIMModel::LLIMSession::isOutgoingAdHoc() const +{ + return IM_SESSION_CONFERENCE_START == mType; +} + +bool LLIMModel::LLIMSession::isAdHoc() +{ + return IM_SESSION_CONFERENCE_START == mType || (IM_SESSION_INVITE == mType && !gAgent.isInGroup(mSessionID, true)); +} + +bool LLIMModel::LLIMSession::isP2P() +{ + return IM_NOTHING_SPECIAL == mType; +} + +bool LLIMModel::LLIMSession::isGroupChat() +{ + return IM_SESSION_GROUP_START == mType || (IM_SESSION_INVITE == mType && gAgent.isInGroup(mSessionID, true)); +} + +LLUUID LLIMModel::LLIMSession::generateOutgoingAdHocHash() const +{ + LLUUID hash = LLUUID::null; + + if (mInitialTargetIDs.size()) + { + std::set sorted_uuids(mInitialTargetIDs.begin(), mInitialTargetIDs.end()); + hash = generateHash(sorted_uuids); + } + + return hash; +} + +void LLIMModel::LLIMSession::buildHistoryFileName() +{ + mHistoryFileName = mName; + + //ad-hoc requires sophisticated chat history saving schemes + if (isAdHoc()) + { + /* in case of outgoing ad-hoc sessions we need to make specilized names + * if this naming system is ever changed then the filtering definitions in + * lllogchat.cpp need to be change acordingly so that the filtering for the + * date stamp code introduced in STORM-102 will work properly and not add + * a date stamp to the Ad-hoc conferences. + */ + if (mInitialTargetIDs.size()) + { + std::set sorted_uuids(mInitialTargetIDs.begin(), mInitialTargetIDs.end()); + mHistoryFileName = mName + " hash" + generateHash(sorted_uuids).asString(); + } + else + { + //in case of incoming ad-hoc sessions + mHistoryFileName = mName + " " + LLLogChat::timestamp2LogString(0, true) + " " + mSessionID.asString().substr(0, 4); + } + } + else if (isP2P()) // look up username to use as the log name + { + LLAvatarName av_name; + // For outgoing sessions we already have a cached name + // so no need for a callback in LLAvatarNameCache::get() + if (LLAvatarNameCache::get(mOtherParticipantID, &av_name)) + { + mHistoryFileName = LLCacheName::buildUsername(av_name.getUserName()); + } + else + { + // Incoming P2P sessions include a name that we can use to build a history file name + mHistoryFileName = LLCacheName::buildUsername(mName); + } + + // user's account name can change, but filenames and session names are account name based + LLConversationLog::getInstance()->verifyFilename(mSessionID, mHistoryFileName, av_name.getCompleteName()); + } + else if (isGroupChat()) + { + mHistoryFileName = mName + GROUP_CHAT_SUFFIX; + } +} + +//static +LLUUID LLIMModel::LLIMSession::generateHash(const std::set& sorted_uuids) +{ + LLMD5 md5_uuid; + + std::set::const_iterator it = sorted_uuids.begin(); + while (it != sorted_uuids.end()) + { + md5_uuid.update((unsigned char*)(*it).mData, 16); + it++; + } + md5_uuid.finalize(); + + LLUUID participants_md5_hash; + md5_uuid.raw_digest((unsigned char*) participants_md5_hash.mData); + return participants_md5_hash; +} + +void LLIMModel::processSessionInitializedReply(const LLUUID& old_session_id, const LLUUID& new_session_id) +{ + LLIMSession* session = findIMSession(old_session_id); + if (session) + { + session->sessionInitReplyReceived(new_session_id); + + if (old_session_id != new_session_id) + { + mId2SessionMap.erase(old_session_id); + mId2SessionMap[new_session_id] = session; + } + + LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(old_session_id); + if (im_floater) + { + im_floater->sessionInitReplyReceived(new_session_id); + } + + if (old_session_id != new_session_id) + { + gIMMgr->notifyObserverSessionIDUpdated(old_session_id, new_session_id); + } + + // auto-start the call on session initialization? + if (session->mStartCallOnInitialize) + { + gIMMgr->startCall(new_session_id); + } + } +} + +void LLIMModel::testMessages() +{ + LLUUID bot1_id("d0426ec6-6535-4c11-a5d9-526bb0c654d9"); + LLUUID bot1_session_id; + std::string from = "IM Tester"; + + bot1_session_id = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, bot1_id); + newSession(bot1_session_id, from, IM_NOTHING_SPECIAL, bot1_id); + addMessage(bot1_session_id, from, bot1_id, "Test Message: Hi from testerbot land!"); + + LLUUID bot2_id; + std::string firstname[] = {"Roflcopter", "Joe"}; + std::string lastname[] = {"Linden", "Tester", "Resident", "Schmoe"}; + + S32 rand1 = ll_rand(sizeof firstname)/(sizeof firstname[0]); + S32 rand2 = ll_rand(sizeof lastname)/(sizeof lastname[0]); + + from = firstname[rand1] + " " + lastname[rand2]; + bot2_id.generate(from); + LLUUID bot2_session_id = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, bot2_id); + newSession(bot2_session_id, from, IM_NOTHING_SPECIAL, bot2_id); + addMessage(bot2_session_id, from, bot2_id, "Test Message: Hello there, I have a question. Can I bother you for a second? "); + addMessage(bot2_session_id, from, bot2_id, "Test Message: OMGWTFBBQ."); +} + +//session name should not be empty +bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, + const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice, bool has_offline_msg) +{ + if (name.empty()) + { + LL_WARNS() << "Attempt to create a new session with empty name; id = " << session_id << LL_ENDL; + return false; + } + + if (findIMSession(session_id)) + { + LL_WARNS() << "IM Session " << session_id << " already exists" << LL_ENDL; + return false; + } + + LLIMSession* session = new LLIMSession(session_id, name, type, other_participant_id, ids, voice, has_offline_msg); + mId2SessionMap[session_id] = session; + + // When notifying observer, name of session is used instead of "name", because they may not be the + // same if it is an adhoc session (in this case name is localized in LLIMSession constructor). + std::string session_name = LLIMModel::getInstance()->getName(session_id); + LLIMMgr::getInstance()->notifyObserverSessionAdded(session_id, session_name, other_participant_id,has_offline_msg); + + return true; + +} + +bool LLIMModel::newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, bool voice, bool has_offline_msg) +{ + uuid_vec_t ids; + ids.push_back(other_participant_id); + return newSession(session_id, name, type, other_participant_id, ids, voice, has_offline_msg); +} + +bool LLIMModel::clearSession(const LLUUID& session_id) +{ + if (mId2SessionMap.find(session_id) == mId2SessionMap.end()) return false; + delete (mId2SessionMap[session_id]); + mId2SessionMap.erase(session_id); + return true; +} + +void LLIMModel::getMessages(const LLUUID& session_id, chat_message_list_t& messages, int start_index, const bool sendNoUnreadMsgs) +{ + getMessagesSilently(session_id, messages, start_index); + + if (sendNoUnreadMsgs) + { + sendNoUnreadMessages(session_id); + } +} + +void LLIMModel::getMessagesSilently(const LLUUID& session_id, chat_message_list_t& messages, int start_index) +{ + LLIMSession* session = findIMSession(session_id); + if (!session) + { + LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; + return; + } + + int i = session->mMsgs.size() - start_index; + + for (chat_message_list_t::iterator iter = session->mMsgs.begin(); + iter != session->mMsgs.end() && i > 0; + iter++) + { + LLSD msg; + msg = *iter; + messages.push_back(*iter); + i--; + } +} + +void LLIMModel::sendNoUnreadMessages(const LLUUID& session_id) +{ + LLIMSession* session = findIMSession(session_id); + if (!session) + { + LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; + return; + } + + session->mNumUnread = 0; + session->mParticipantUnreadMessageCount = 0; + + LLSD arg; + arg["session_id"] = session_id; + arg["num_unread"] = 0; + arg["participant_unread"] = session->mParticipantUnreadMessageCount; + mNoUnreadMsgsSignal(arg); +} + +bool LLIMModel::addToHistory(const LLUUID& session_id, + const std::string& from, + const LLUUID& from_id, + const std::string& utf8_text, + bool is_region_msg, + U32 timestamp) +{ + LLIMSession* session = findIMSession(session_id); + + if (!session) + { + LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; + return false; + } + + // This is where a normal arriving message is added to the session. Note that the time string created here is without the full date + session->addMessage(from, from_id, utf8_text, LLLogChat::timestamp2LogString(timestamp, false), false, is_region_msg, timestamp); + + return true; +} + +bool LLIMModel::logToFile(const std::string& file_name, const std::string& from, const LLUUID& from_id, const std::string& utf8_text) +{ + if (gSavedPerAccountSettings.getS32("KeepConversationLogTranscripts") > 1) + { + std::string from_name = from; + + LLAvatarName av_name; + if (!from_id.isNull() && + LLAvatarNameCache::get(from_id, &av_name) && + !av_name.isDisplayNameDefault()) + { + from_name = av_name.getCompleteName(); + } + + LLLogChat::saveHistory(file_name, from_name, from_id, utf8_text); + LLConversationLog::instance().cache(); // update the conversation log too + return true; + } + else + { + return false; + } +} + +void LLIMModel::proccessOnlineOfflineNotification( + const LLUUID& session_id, + const std::string& utf8_text) +{ + // Add system message to history + addMessage(session_id, SYSTEM_FROM, LLUUID::null, utf8_text); +} + +void LLIMModel::addMessage(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, + const std::string& utf8_text, bool log2file /* = true */, bool is_region_msg, /* = false */ U32 time_stamp /* = 0 */) +{ + if (gSavedSettings.getBOOL("TranslateChat") && (from != SYSTEM_FROM)) + { + const std::string from_lang = ""; // leave empty to trigger autodetect + const std::string to_lang = LLTranslate::getTranslateLanguage(); + U64 time_n_flags = ((U64) time_stamp) | (log2file ? (1LL << 32) : 0) | (is_region_msg ? (1LL << 33) : 0); // boost::bind has limited parameters + LLTranslate::translateMessage(from_lang, to_lang, utf8_text, + boost::bind(&translateSuccess, session_id, from, from_id, utf8_text, time_n_flags, utf8_text, from_lang, _1, _2), + boost::bind(&translateFailure, session_id, from, from_id, utf8_text, time_n_flags, _1, _2)); + } + else + { + processAddingMessage(session_id, from, from_id, utf8_text, log2file, is_region_msg, time_stamp); + } +} + +void LLIMModel::processAddingMessage(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, + const std::string& utf8_text, bool log2file, bool is_region_msg, U32 time_stamp) +{ + LLIMSession* session = addMessageSilently(session_id, from, from_id, utf8_text, log2file, is_region_msg, time_stamp); + if (!session) + return; + + //good place to add some1 to recent list + //other places may be called from message history. + if( !from_id.isNull() && + ( session->isP2PSessionType() || session->isAdHocSessionType() ) ) + LLRecentPeople::instance().add(from_id); + + // notify listeners + LLSD arg; + arg["session_id"] = session_id; + arg["num_unread"] = session->mNumUnread; + arg["participant_unread"] = session->mParticipantUnreadMessageCount; + arg["message"] = utf8_text; + arg["from"] = from; + arg["from_id"] = from_id; + arg["time"] = LLLogChat::timestamp2LogString(time_stamp, true); + arg["session_type"] = session->mSessionType; + arg["is_region_msg"] = is_region_msg; + + mNewMsgSignal(arg); +} + +LLIMModel::LLIMSession* LLIMModel::addMessageSilently(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, + const std::string& utf8_text, bool log2file /* = true */, bool is_region_msg, /* false */ + U32 timestamp /* = 0 */) +{ + LLIMSession* session = findIMSession(session_id); + + if (!session) + { + return NULL; + } + + // replace interactive system message marker with correct from string value + std::string from_name = from; + if (INTERACTIVE_SYSTEM_FROM == from) + { + from_name = SYSTEM_FROM; + } + + addToHistory(session_id, from_name, from_id, utf8_text, is_region_msg, timestamp); + if (log2file) + { + logToFile(getHistoryFileName(session_id), from_name, from_id, utf8_text); + } + + session->mNumUnread++; + + //update count of unread messages from real participant + if (!(from_id.isNull() || from_id == gAgentID || SYSTEM_FROM == from) + // we should increment counter for interactive system messages() + || INTERACTIVE_SYSTEM_FROM == from) + { + ++(session->mParticipantUnreadMessageCount); + } + + return session; +} + + +const std::string LLIMModel::getName(const LLUUID& session_id) const +{ + LLIMSession* session = findIMSession(session_id); + + if (!session) + { + LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; + return LLTrans::getString("no_session_message"); + } + + return session->mName; +} + +const S32 LLIMModel::getNumUnread(const LLUUID& session_id) const +{ + LLIMSession* session = findIMSession(session_id); + if (!session) + { + LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; + return -1; + } + + return session->mNumUnread; +} + +const LLUUID& LLIMModel::getOtherParticipantID(const LLUUID& session_id) const +{ + LLIMSession* session = findIMSession(session_id); + if (!session) + { + LL_WARNS() << "session " << session_id << " does not exist " << LL_ENDL; + return LLUUID::null; + } + + return session->mOtherParticipantID; +} + +EInstantMessage LLIMModel::getType(const LLUUID& session_id) const +{ + LLIMSession* session = findIMSession(session_id); + if (!session) + { + LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; + return IM_COUNT; + } + + return session->mType; +} + +LLVoiceChannel* LLIMModel::getVoiceChannel( const LLUUID& session_id ) const +{ + LLIMSession* session = findIMSession(session_id); + if (!session) + { + LL_WARNS() << "session " << session_id << "does not exist " << LL_ENDL; + return NULL; + } + + return session->mVoiceChannel; +} + +LLIMSpeakerMgr* LLIMModel::getSpeakerManager( const LLUUID& session_id ) const +{ + LLIMSession* session = findIMSession(session_id); + if (!session) + { + LL_WARNS() << "session " << session_id << " does not exist " << LL_ENDL; + return NULL; + } + + return session->mSpeakers; +} + +const std::string& LLIMModel::getHistoryFileName(const LLUUID& session_id) const +{ + LLIMSession* session = findIMSession(session_id); + if (!session) + { + LL_WARNS() << "session " << session_id << " does not exist " << LL_ENDL; + return LLStringUtil::null; + } + + return session->mHistoryFileName; +} + + +// TODO get rid of other participant ID +void LLIMModel::sendTypingState(LLUUID session_id, LLUUID other_participant_id, bool typing) +{ + std::string name; + LLAgentUI::buildFullname(name); + + pack_instant_message( + gMessageSystem, + gAgent.getID(), + false, + gAgent.getSessionID(), + other_participant_id, + name, + std::string("typing"), + IM_ONLINE, + (typing ? IM_TYPING_START : IM_TYPING_STOP), + session_id); + gAgent.sendReliableMessage(); +} + +void LLIMModel::sendLeaveSession(const LLUUID& session_id, const LLUUID& other_participant_id) +{ + if(session_id.notNull()) + { + std::string name; + LLAgentUI::buildFullname(name); + pack_instant_message( + gMessageSystem, + gAgent.getID(), + false, + gAgent.getSessionID(), + other_participant_id, + name, + LLStringUtil::null, + IM_ONLINE, + IM_SESSION_LEAVE, + session_id); + gAgent.sendReliableMessage(); + } +} + +//*TODO this method is better be moved to the LLIMMgr +void LLIMModel::sendMessage(const std::string& utf8_text, + const LLUUID& im_session_id, + const LLUUID& other_participant_id, + EInstantMessage dialog) +{ + std::string name; + bool sent = false; + LLAgentUI::buildFullname(name); + + const LLRelationship* info = NULL; + info = LLAvatarTracker::instance().getBuddyInfo(other_participant_id); + + U8 offline = (!info || info->isOnline()) ? IM_ONLINE : IM_OFFLINE; + // Old call to send messages to SLim client, no longer supported. + //if((offline == IM_OFFLINE) && (LLVoiceClient::getInstance()->isOnlineSIP(other_participant_id))) + //{ + // // User is online through the OOW connector, but not with a regular viewer. Try to send the message via SLVoice. + // sent = LLVoiceClient::getInstance()->sendTextMessage(other_participant_id, utf8_text); + //} + + if(!sent) + { + // Send message normally. + + // default to IM_SESSION_SEND unless it's nothing special - in + // which case it's probably an IM to everyone. + U8 new_dialog = dialog; + + if ( dialog != IM_NOTHING_SPECIAL ) + { + new_dialog = IM_SESSION_SEND; + } + pack_instant_message( + gMessageSystem, + gAgent.getID(), + false, + gAgent.getSessionID(), + other_participant_id, + name.c_str(), + utf8_text.c_str(), + offline, + (EInstantMessage)new_dialog, + im_session_id); + gAgent.sendReliableMessage(); + } + + bool is_group_chat = false; + LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(im_session_id); + if(session) + { + is_group_chat = session->isGroupSessionType(); + } + + // If there is a mute list and this is not a group chat... + if ( LLMuteList::getInstance() && !is_group_chat) + { + // ... the target should not be in our mute list for some message types. + // Auto-remove them if present. + switch( dialog ) + { + case IM_NOTHING_SPECIAL: + case IM_GROUP_INVITATION: + case IM_INVENTORY_OFFERED: + case IM_SESSION_INVITE: + case IM_SESSION_P2P_INVITE: + case IM_SESSION_CONFERENCE_START: + case IM_SESSION_SEND: // This one is marginal - erring on the side of hearing. + case IM_LURE_USER: + case IM_GODLIKE_LURE_USER: + case IM_FRIENDSHIP_OFFERED: + LLMuteList::getInstance()->autoRemove(other_participant_id, LLMuteList::AR_IM); + break; + default: ; // do nothing + } + } + + if((dialog == IM_NOTHING_SPECIAL) && + (other_participant_id.notNull())) + { + // Do we have to replace the /me's here? + std::string from; + LLAgentUI::buildFullname(from); + LLIMModel::getInstance()->addMessage(im_session_id, from, gAgentID, utf8_text); + + //local echo for the legacy communicate panel + std::string history_echo; + LLAgentUI::buildFullname(history_echo); + + history_echo += ": " + utf8_text; + + LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(im_session_id); + if (speaker_mgr) + { + speaker_mgr->speakerChatted(gAgentID); + speaker_mgr->setSpeakerTyping(gAgentID, false); + } + } + + // Add the recipient to the recent people list. + bool is_not_group_id = LLGroupMgr::getInstance()->getGroupData(other_participant_id) == NULL; + + if (is_not_group_id) + { + if( session == 0)//??? shouldn't really happen + { + LLRecentPeople::instance().add(other_participant_id); + return; + } + // IM_SESSION_INVITE means that this is an Ad-hoc incoming chat + // (it can be also Group chat but it is checked above) + // In this case mInitialTargetIDs contains Ad-hoc session ID and it should not be added + // to Recent People to prevent showing of an item with (?? ?)(?? ?), sans the spaces. See EXT-8246. + // Concrete participants will be added into this list once they sent message in chat. + if (IM_SESSION_INVITE == dialog) return; + + if (IM_SESSION_CONFERENCE_START == dialog) // outgoing ad-hoc session + { + // Add only online members of conference to recent list (EXT-8658) + addSpeakersToRecent(im_session_id); + } + else // outgoing P2P session + { + // Add the recepient of the session. + if (!session->mInitialTargetIDs.empty()) + { + LLRecentPeople::instance().add(*(session->mInitialTargetIDs.begin())); + } + } + } +} + +void LLIMModel::addSpeakersToRecent(const LLUUID& im_session_id) +{ + LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(im_session_id); + LLSpeakerMgr::speaker_list_t speaker_list; + if(speaker_mgr != NULL) + { + speaker_mgr->getSpeakerList(&speaker_list, true); + } + for(LLSpeakerMgr::speaker_list_t::iterator it = speaker_list.begin(); it != speaker_list.end(); it++) + { + const LLPointer& speakerp = *it; + LLRecentPeople::instance().add(speakerp->mID); + } +} + +void session_starter_helper( + const LLUUID& temp_session_id, + const LLUUID& other_participant_id, + EInstantMessage im_type) +{ + LLMessageSystem *msg = gMessageSystem; + + msg->newMessageFast(_PREHASH_ImprovedInstantMessage); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + + msg->nextBlockFast(_PREHASH_MessageBlock); + msg->addBOOLFast(_PREHASH_FromGroup, false); + msg->addUUIDFast(_PREHASH_ToAgentID, other_participant_id); + msg->addU8Fast(_PREHASH_Offline, IM_ONLINE); + msg->addU8Fast(_PREHASH_Dialog, im_type); + msg->addUUIDFast(_PREHASH_ID, temp_session_id); + msg->addU32Fast(_PREHASH_Timestamp, NO_TIMESTAMP); // no timestamp necessary + + std::string name; + LLAgentUI::buildFullname(name); + + msg->addStringFast(_PREHASH_FromAgentName, name); + msg->addStringFast(_PREHASH_Message, LLStringUtil::null); + msg->addU32Fast(_PREHASH_ParentEstateID, 0); + msg->addUUIDFast(_PREHASH_RegionID, LLUUID::null); + msg->addVector3Fast(_PREHASH_Position, gAgent.getPositionAgent()); +} + +void start_deprecated_conference_chat( + const LLUUID& temp_session_id, + const LLUUID& creator_id, + const LLUUID& other_participant_id, + const LLSD& agents_to_invite) +{ + U8* bucket; + U8* pos; + S32 count; + S32 bucket_size; + + // *FIX: this could suffer from endian issues + count = agents_to_invite.size(); + bucket_size = UUID_BYTES * count; + bucket = new U8[bucket_size]; + pos = bucket; + + for(S32 i = 0; i < count; ++i) + { + LLUUID agent_id = agents_to_invite[i].asUUID(); + + memcpy(pos, &agent_id, UUID_BYTES); + pos += UUID_BYTES; + } + + session_starter_helper( + temp_session_id, + other_participant_id, + IM_SESSION_CONFERENCE_START); + + gMessageSystem->addBinaryDataFast( + _PREHASH_BinaryBucket, + bucket, + bucket_size); + + gAgent.sendReliableMessage(); + + delete[] bucket; +} + +// Returns true if any messages were sent, false otherwise. +// Is sort of equivalent to "does the server need to do anything?" +bool LLIMModel::sendStartSession( + const LLUUID& temp_session_id, + const LLUUID& other_participant_id, + const uuid_vec_t& ids, + EInstantMessage dialog) +{ + if ( dialog == IM_SESSION_GROUP_START ) + { + session_starter_helper( + temp_session_id, + other_participant_id, + dialog); + gMessageSystem->addBinaryDataFast( + _PREHASH_BinaryBucket, + EMPTY_BINARY_BUCKET, + EMPTY_BINARY_BUCKET_SIZE); + gAgent.sendReliableMessage(); + + return true; + } + else if ( dialog == IM_SESSION_CONFERENCE_START ) + { + LLSD agents; + for (int i = 0; i < (S32) ids.size(); i++) + { + agents.append(ids[i]); + } + + //we have a new way of starting conference calls now + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + std::string url = region->getCapability( + "ChatSessionRequest"); + + LLCoros::instance().launch("startConfrenceCoro", + boost::bind(&startConfrenceCoro, url, + temp_session_id, gAgent.getID(), other_participant_id, agents)); + } + else + { + start_deprecated_conference_chat( + temp_session_id, + gAgent.getID(), + other_participant_id, + agents); + } + + //we also need to wait for reply from the server in case of ad-hoc chat (we'll get new session id) + return true; + } + + return false; +} + + +// the other_participant_id is either an agent_id, a group_id, or an inventory +// folder item_id (collection of calling cards) + +// static +LLUUID LLIMMgr::computeSessionID( + EInstantMessage dialog, + const LLUUID& other_participant_id) +{ + LLUUID session_id; + if (IM_SESSION_GROUP_START == dialog) + { + // slam group session_id to the group_id (other_participant_id) + session_id = other_participant_id; + } + else if (IM_SESSION_CONFERENCE_START == dialog) + { + session_id.generate(); + } + else if (IM_SESSION_INVITE == dialog) + { + // use provided session id for invites + session_id = other_participant_id; + } + else + { + LLUUID agent_id = gAgent.getID(); + if (other_participant_id == agent_id) + { + // if we try to send an IM to ourselves then the XOR would be null + // so we just make the session_id the same as the agent_id + session_id = agent_id; + } + else + { + // peer-to-peer or peer-to-asset session_id is the XOR + session_id = other_participant_id ^ agent_id; + } + } + + if (gAgent.isInGroup(session_id, true) && (session_id != other_participant_id)) + { + LL_WARNS() << "Group session id different from group id: IM type = " << dialog << ", session id = " << session_id << ", group id = " << other_participant_id << LL_ENDL; + } + return session_id; +} + +void +LLIMMgr::showSessionStartError( + const std::string& error_string, + const LLUUID session_id) +{ + if (!hasSession(session_id)) return; + + LLSD args; + args["REASON"] = LLTrans::getString(error_string); + args["RECIPIENT"] = LLIMModel::getInstance()->getName(session_id); + + LLSD payload; + payload["session_id"] = session_id; + + LLNotificationsUtil::add( + "ChatterBoxSessionStartError", + args, + payload, + LLIMMgr::onConfirmForceCloseError); +} + +void +LLIMMgr::showSessionEventError( + const std::string& event_string, + const std::string& error_string, + const LLUUID session_id) +{ + LLSD args; + LLStringUtil::format_map_t event_args; + + event_args["RECIPIENT"] = LLIMModel::getInstance()->getName(session_id); + + args["REASON"] = + LLTrans::getString(error_string); + args["EVENT"] = + LLTrans::getString(event_string, event_args); + + LLNotificationsUtil::add( + "ChatterBoxSessionEventError", + args); +} + +void +LLIMMgr::showSessionForceClose( + const std::string& reason_string, + const LLUUID session_id) +{ + if (!hasSession(session_id)) return; + + LLSD args; + + args["NAME"] = LLIMModel::getInstance()->getName(session_id); + args["REASON"] = LLTrans::getString(reason_string); + + LLSD payload; + payload["session_id"] = session_id; + + LLNotificationsUtil::add( + "ForceCloseChatterBoxSession", + args, + payload, + LLIMMgr::onConfirmForceCloseError); +} + +//static +bool +LLIMMgr::onConfirmForceCloseError( + const LLSD& notification, + const LLSD& response) +{ + //only 1 option really + LLUUID session_id = notification["payload"]["session_id"]; + + LLFloater* floater = LLFloaterIMSession::findInstance(session_id); + if ( floater ) + { + floater->closeFloater(false); + } + return false; +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLCallDialogManager +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +LLCallDialogManager::LLCallDialogManager(): +mPreviousSessionlName(""), +mCurrentSessionlName(""), +mSession(NULL), +mOldState(LLVoiceChannel::STATE_READY) +{ +} + +LLCallDialogManager::~LLCallDialogManager() +{ +} + +void LLCallDialogManager::initSingleton() +{ + LLVoiceChannel::setCurrentVoiceChannelChangedCallback(LLCallDialogManager::onVoiceChannelChanged); +} + +// static +void LLCallDialogManager::onVoiceChannelChanged(const LLUUID &session_id) +{ + LLCallDialogManager::getInstance()->onVoiceChannelChangedInt(session_id); +} + +void LLCallDialogManager::onVoiceChannelChangedInt(const LLUUID &session_id) +{ + LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(session_id); + if(!session) + { + mPreviousSessionlName = mCurrentSessionlName; + mCurrentSessionlName = ""; // Empty string results in "Nearby Voice Chat" after substitution + return; + } + + mSession = session; + + static boost::signals2::connection prev_channel_state_changed_connection; + // disconnect previously connected callback to avoid have invalid sSession in onVoiceChannelStateChanged() + prev_channel_state_changed_connection.disconnect(); + prev_channel_state_changed_connection = + mSession->mVoiceChannel->setStateChangedCallback(boost::bind(LLCallDialogManager::onVoiceChannelStateChanged, _1, _2, _3, _4)); + + if(mCurrentSessionlName != session->mName) + { + mPreviousSessionlName = mCurrentSessionlName; + mCurrentSessionlName = session->mName; + } + + if (LLVoiceChannel::getCurrentVoiceChannel()->getState() == LLVoiceChannel::STATE_CALL_STARTED && + LLVoiceChannel::getCurrentVoiceChannel()->getCallDirection() == LLVoiceChannel::OUTGOING_CALL) + { + + //*TODO get rid of duplicated code + LLSD mCallDialogPayload; + mCallDialogPayload["session_id"] = mSession->mSessionID; + mCallDialogPayload["session_name"] = mSession->mName; + mCallDialogPayload["other_user_id"] = mSession->mOtherParticipantID; + mCallDialogPayload["old_channel_name"] = mPreviousSessionlName; + mCallDialogPayload["state"] = LLVoiceChannel::STATE_CALL_STARTED; + mCallDialogPayload["disconnected_channel_name"] = mSession->mName; + mCallDialogPayload["session_type"] = mSession->mSessionType; + + LLOutgoingCallDialog* ocd = LLFloaterReg::getTypedInstance("outgoing_call", LLOutgoingCallDialog::OCD_KEY); + if(ocd) + { + ocd->show(mCallDialogPayload); + } + } + +} + +// static +void LLCallDialogManager::onVoiceChannelStateChanged(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state, const LLVoiceChannel::EDirection& direction, bool ended_by_agent) +{ + LLCallDialogManager::getInstance()->onVoiceChannelStateChangedInt(old_state, new_state, direction, ended_by_agent); +} + +void LLCallDialogManager::onVoiceChannelStateChangedInt(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state, const LLVoiceChannel::EDirection& direction, bool ended_by_agent) +{ + LLSD mCallDialogPayload; + LLOutgoingCallDialog* ocd = NULL; + + if(mOldState == new_state) + { + return; + } + + mOldState = new_state; + + mCallDialogPayload["session_id"] = mSession->mSessionID; + mCallDialogPayload["session_name"] = mSession->mName; + mCallDialogPayload["other_user_id"] = mSession->mOtherParticipantID; + mCallDialogPayload["old_channel_name"] = mPreviousSessionlName; + mCallDialogPayload["state"] = new_state; + mCallDialogPayload["disconnected_channel_name"] = mSession->mName; + mCallDialogPayload["session_type"] = mSession->mSessionType; + mCallDialogPayload["ended_by_agent"] = ended_by_agent; + + switch(new_state) + { + case LLVoiceChannel::STATE_CALL_STARTED : + // do not show "Calling to..." if it is incoming call + if(direction == LLVoiceChannel::INCOMING_CALL) + { + return; + } + break; + + case LLVoiceChannel::STATE_HUNG_UP: + // this state is coming before session is changed + break; + + case LLVoiceChannel::STATE_CONNECTED : + ocd = LLFloaterReg::findTypedInstance("outgoing_call", LLOutgoingCallDialog::OCD_KEY); + if (ocd) + { + ocd->closeFloater(); + } + return; + + default: + break; + } + + ocd = LLFloaterReg::getTypedInstance("outgoing_call", LLOutgoingCallDialog::OCD_KEY); + if(ocd) + { + ocd->show(mCallDialogPayload); + } +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLCallDialog +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +LLCallDialog::LLCallDialog(const LLSD& payload) + : LLDockableFloater(NULL, false, payload), + + mPayload(payload), + mLifetime(DEFAULT_LIFETIME) +{ + setAutoFocus(false); + // force docked state since this floater doesn't save it between recreations + setDocked(true); +} + +LLCallDialog::~LLCallDialog() +{ + LLUI::getInstance()->removePopup(this); +} + +bool LLCallDialog::postBuild() +{ + if (!LLDockableFloater::postBuild() || !gToolBarView) + return false; + + dockToToolbarButton("speak"); + + return true; +} + +void LLCallDialog::dockToToolbarButton(const std::string& toolbarButtonName) +{ + LLDockControl::DocAt dock_pos = getDockControlPos(toolbarButtonName); + LLView *anchor_panel = gToolBarView->findChildView(toolbarButtonName); + + setUseTongue(anchor_panel); + + setDockControl(new LLDockControl(anchor_panel, this, getDockTongue(dock_pos), dock_pos)); +} + +LLDockControl::DocAt LLCallDialog::getDockControlPos(const std::string& toolbarButtonName) +{ + LLCommandId command_id(toolbarButtonName); + S32 toolbar_loc = gToolBarView->hasCommand(command_id); + + LLDockControl::DocAt doc_at = LLDockControl::TOP; + + switch (toolbar_loc) + { + case LLToolBarEnums::TOOLBAR_LEFT: + doc_at = LLDockControl::RIGHT; + break; + + case LLToolBarEnums::TOOLBAR_RIGHT: + doc_at = LLDockControl::LEFT; + break; + } + + return doc_at; +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLOutgoingCallDialog +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +LLOutgoingCallDialog::LLOutgoingCallDialog(const LLSD& payload) : +LLCallDialog(payload) +{ + LLOutgoingCallDialog* instance = LLFloaterReg::findTypedInstance("outgoing_call", LLOutgoingCallDialog::OCD_KEY); + if(instance && instance->getVisible()) + { + instance->onCancel(instance); + } +} + +void LLCallDialog::draw() +{ + if (lifetimeHasExpired()) + { + onLifetimeExpired(); + } + + if (getDockControl() != NULL) + { + LLDockableFloater::draw(); + } +} + +// virtual +void LLCallDialog::onOpen(const LLSD& key) +{ + LLDockableFloater::onOpen(key); + + // it should be over the all floaters. EXT-5116 + LLUI::getInstance()->addPopup(this); +} + +void LLCallDialog::setIcon(const LLSD& session_id, const LLSD& participant_id) +{ + bool participant_is_avatar = LLVoiceClient::getInstance()->isParticipantAvatar(session_id); + + bool is_group = participant_is_avatar && gAgent.isInGroup(session_id, true); + + LLAvatarIconCtrl* avatar_icon = getChild("avatar_icon"); + LLGroupIconCtrl* group_icon = getChild("group_icon"); + + avatar_icon->setVisible(!is_group); + group_icon->setVisible(is_group); + + if (is_group) + { + group_icon->setValue(session_id); + } + else if (participant_is_avatar) + { + avatar_icon->setValue(participant_id); + } + else + { + LL_WARNS() << "Participant neither avatar nor group" << LL_ENDL; + group_icon->setValue(session_id); + } +} + +bool LLCallDialog::lifetimeHasExpired() +{ + if (mLifetimeTimer.getStarted()) + { + F32 elapsed_time = mLifetimeTimer.getElapsedTimeF32(); + if (elapsed_time > mLifetime) + { + return true; + } + } + return false; +} + +void LLCallDialog::onLifetimeExpired() +{ + mLifetimeTimer.stop(); + closeFloater(); +} + +void LLOutgoingCallDialog::show(const LLSD& key) +{ + mPayload = key; + + //will be false only if voice in parcel is disabled and channel we leave is nearby(checked further) + bool show_oldchannel = LLViewerParcelMgr::getInstance()->allowAgentVoice(); + + // hide all text at first + hideAllText(); + + // init notification's lifetime + std::istringstream ss( getString("lifetime") ); + if (!(ss >> mLifetime)) + { + mLifetime = DEFAULT_LIFETIME; + } + + // customize text strings + // tell the user which voice channel they are leaving + if (!mPayload["old_channel_name"].asString().empty()) + { + std::string old_caller_name = mPayload["old_channel_name"].asString(); + + getChild("leaving")->setTextArg("[CURRENT_CHAT]", old_caller_name); + show_oldchannel = true; + } + else + { + getChild("leaving")->setTextArg("[CURRENT_CHAT]", getString("localchat")); + } + + if (!mPayload["disconnected_channel_name"].asString().empty()) + { + std::string channel_name = mPayload["disconnected_channel_name"].asString(); + getChild("nearby")->setTextArg("[VOICE_CHANNEL_NAME]", channel_name); + + // skipping "You will now be reconnected to nearby" in notification when call is ended by disabling voice, + // so no reconnection to nearby chat happens (EXT-4397) + bool voice_works = LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking(); + std::string reconnect_nearby = voice_works ? LLTrans::getString("reconnect_nearby") : std::string(); + getChild("nearby")->setTextArg("[RECONNECT_NEARBY]", reconnect_nearby); + + const std::string& nearby_str = mPayload["ended_by_agent"] ? NEARBY_P2P_BY_AGENT : NEARBY_P2P_BY_OTHER; + getChild(nearby_str)->setTextArg("[RECONNECT_NEARBY]", reconnect_nearby); + } + + std::string callee_name = mPayload["session_name"].asString(); + + if (callee_name == "anonymous") // obsolete? Likely was part of avaline support + { + callee_name = getString("anonymous"); + } + + LLSD callee_id = mPayload["other_user_id"]; + // Beautification: Since you know who you called, just show display name + std::string title = callee_name; + std::string final_callee_name = callee_name; + if (mPayload["session_type"].asInteger() == LLIMModel::LLIMSession::P2P_SESSION) + { + LLAvatarName av_name; + if (LLAvatarNameCache::get(callee_id, &av_name)) + { + final_callee_name = av_name.getDisplayName(); + title = av_name.getCompleteName(); + } + } + getChild("calling")->setTextArg("[CALLEE_NAME]", final_callee_name); + getChild("connecting")->setTextArg("[CALLEE_NAME]", final_callee_name); + + setTitle(title); + + // for outgoing group calls callee_id == group id == session id + setIcon(callee_id, callee_id); + + // stop timer by default + mLifetimeTimer.stop(); + + // show only necessary strings and controls + switch(mPayload["state"].asInteger()) + { + case LLVoiceChannel::STATE_CALL_STARTED : + getChild("calling")->setVisible(true); + getChild("Cancel")->setVisible(true); + if(show_oldchannel) + { + getChild("leaving")->setVisible(true); + } + break; + // STATE_READY is here to show appropriate text for ad-hoc and group calls when floater is shown(EXT-6893) + case LLVoiceChannel::STATE_READY : + case LLVoiceChannel::STATE_RINGING : + if(show_oldchannel) + { + getChild("leaving")->setVisible(true); + } + getChild("connecting")->setVisible(true); + break; + case LLVoiceChannel::STATE_ERROR : + getChild("noanswer")->setVisible(true); + getChild("Cancel")->setVisible(false); + setCanClose(true); + mLifetimeTimer.start(); + break; + case LLVoiceChannel::STATE_HUNG_UP : + if (mPayload["session_type"].asInteger() == LLIMModel::LLIMSession::P2P_SESSION) + { + const std::string& nearby_str = mPayload["ended_by_agent"] ? NEARBY_P2P_BY_AGENT : NEARBY_P2P_BY_OTHER; + getChild(nearby_str)->setVisible(true); + } + else + { + getChild("nearby")->setVisible(true); + } + getChild("Cancel")->setVisible(false); + setCanClose(true); + mLifetimeTimer.start(); + } + + openFloater(LLOutgoingCallDialog::OCD_KEY); +} + +void LLOutgoingCallDialog::hideAllText() +{ + getChild("calling")->setVisible(false); + getChild("leaving")->setVisible(false); + getChild("connecting")->setVisible(false); + getChild("nearby_P2P_by_other")->setVisible(false); + getChild("nearby_P2P_by_agent")->setVisible(false); + getChild("nearby")->setVisible(false); + getChild("noanswer")->setVisible(false); +} + +//static +void LLOutgoingCallDialog::onCancel(void* user_data) +{ + LLOutgoingCallDialog* self = (LLOutgoingCallDialog*)user_data; + + if (!gIMMgr) + return; + + LLUUID session_id = self->mPayload["session_id"].asUUID(); + gIMMgr->endCall(session_id); + + self->closeFloater(); +} + + +bool LLOutgoingCallDialog::postBuild() +{ + bool success = LLCallDialog::postBuild(); + + childSetAction("Cancel", onCancel, this); + + setCanDrag(false); + + return success; +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLIncomingCallDialog +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +const std::array voice_call_types = +{ + "VoiceInviteP2P", + "VoiceInviteGroup", + "VoiceInviteAdHoc", + "InviteAdHoc" +}; + +bool is_voice_call_type(const std::string &value) +{ + return std::find(voice_call_types.begin(), voice_call_types.end(), value) != voice_call_types.end(); +} + +LLIncomingCallDialog::LLIncomingCallDialog(const LLSD& payload) : +LLCallDialog(payload), +mAvatarNameCacheConnection() +{ +} + +void LLIncomingCallDialog::onLifetimeExpired() +{ + std::string session_handle = mPayload["session_handle"].asString(); + if (LLVoiceClient::getInstance()->isValidChannel(session_handle)) + { + // restart notification's timer if call is still valid + mLifetimeTimer.start(); + } + else + { + // close invitation if call is already not valid + mLifetimeTimer.stop(); + LLUUID session_id = mPayload["session_id"].asUUID(); + gIMMgr->clearPendingAgentListUpdates(session_id); + gIMMgr->clearPendingInvitation(session_id); + closeFloater(); + } +} + +bool LLIncomingCallDialog::postBuild() +{ + LLCallDialog::postBuild(); + + if (!mPayload.isMap() || mPayload.size() == 0) + { + LL_INFOS("IMVIEW") << "IncomingCall: invalid argument" << LL_ENDL; + return true; + } + + LLUUID session_id = mPayload["session_id"].asUUID(); + LLSD caller_id = mPayload["caller_id"]; + std::string caller_name = mPayload["caller_name"].asString(); + + if (session_id.isNull() && caller_id.asUUID().isNull()) + { + LL_INFOS("IMVIEW") << "IncomingCall: invalid ids" << LL_ENDL; + return true; + } + + std::string notify_box_type = mPayload["notify_box_type"].asString(); + if (!is_voice_call_type(notify_box_type)) + { + LL_INFOS("IMVIEW") << "IncomingCall: notify_box_type was not provided" << LL_ENDL; + return true; + } + + // init notification's lifetime + std::istringstream ss( getString("lifetime") ); + if (!(ss >> mLifetime)) + { + mLifetime = DEFAULT_LIFETIME; + } + + std::string call_type; + if (gAgent.isInGroup(session_id, true)) + { + LLStringUtil::format_map_t args; + LLGroupData data; + if (gAgent.getGroupData(session_id, data)) + { + args["[GROUP]"] = data.mName; + call_type = getString(notify_box_type, args); + } + } + else + { + call_type = getString(notify_box_type); + } + + if (caller_name == "anonymous") // obsolete? Likely was part of avaline support + { + caller_name = getString("anonymous"); + setCallerName(caller_name, caller_name, call_type); + } + else + { + // Get the full name information + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + mAvatarNameCacheConnection = LLAvatarNameCache::get(caller_id, boost::bind(&LLIncomingCallDialog::onAvatarNameCache, this, _1, _2, call_type)); + } + + setIcon(session_id, caller_id); + + childSetAction("Accept", onAccept, this); + childSetAction("Reject", onReject, this); + childSetAction("Start IM", onStartIM, this); + setDefaultBtn("Accept"); + + if(notify_box_type != "VoiceInviteGroup" && notify_box_type != "VoiceInviteAdHoc") + { + // starting notification's timer for P2P invitations + mLifetimeTimer.start(); + } + else + { + mLifetimeTimer.stop(); + } + + //it's not possible to connect to existing Ad-Hoc/Group chat through incoming ad-hoc call + bool is_avatar = LLVoiceClient::getInstance()->isParticipantAvatar(session_id); + getChildView("Start IM")->setVisible( is_avatar && notify_box_type != "VoiceInviteAdHoc" && notify_box_type != "VoiceInviteGroup"); + + setCanDrag(false); + return true; +} + +void LLIncomingCallDialog::setCallerName(const std::string& ui_title, + const std::string& ui_label, + const std::string& call_type) +{ + + // call_type may be a string like " is calling." + LLUICtrl* caller_name_widget = getChild("caller name"); + caller_name_widget->setValue(ui_label + " " + call_type); +} + +void LLIncomingCallDialog::onAvatarNameCache(const LLUUID& agent_id, + const LLAvatarName& av_name, + const std::string& call_type) +{ + mAvatarNameCacheConnection.disconnect(); + std::string title = av_name.getCompleteName(); + setCallerName(title, av_name.getCompleteName(), call_type); +} + +void LLIncomingCallDialog::onOpen(const LLSD& key) +{ + LLCallDialog::onOpen(key); + + if (gSavedSettings.getBOOL("PlaySoundIncomingVoiceCall")) + { + // play a sound for incoming voice call if respective property is set + make_ui_sound("UISndStartIM"); + } + + LLStringUtil::format_map_t args; + LLGroupData data; + // if it's a group call, retrieve group name to use it in question + if (gAgent.getGroupData(key["session_id"].asUUID(), data)) + { + args["[GROUP]"] = data.mName; + } +} + +//static +void LLIncomingCallDialog::onAccept(void* user_data) +{ + LLIncomingCallDialog* self = (LLIncomingCallDialog*)user_data; + processCallResponse(0, self->mPayload); + self->closeFloater(); +} + +//static +void LLIncomingCallDialog::onReject(void* user_data) +{ + LLIncomingCallDialog* self = (LLIncomingCallDialog*)user_data; + processCallResponse(1, self->mPayload); + self->closeFloater(); +} + +//static +void LLIncomingCallDialog::onStartIM(void* user_data) +{ + LLIncomingCallDialog* self = (LLIncomingCallDialog*)user_data; + processCallResponse(2, self->mPayload); + self->closeFloater(); +} + +// static +void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload) +{ + if (!gIMMgr || gDisconnected) + return; + + LLUUID session_id = payload["session_id"].asUUID(); + LLUUID caller_id = payload["caller_id"].asUUID(); + std::string session_name = payload["session_name"].asString(); + EInstantMessage type = (EInstantMessage)payload["type"].asInteger(); + LLIMMgr::EInvitationType inv_type = (LLIMMgr::EInvitationType)payload["inv_type"].asInteger(); + bool voice = true; + switch(response) + { + case 2: // start IM: just don't start the voice chat + { + voice = false; + /* FALLTHROUGH */ + } + case 0: // accept + { + if (type == IM_SESSION_P2P_INVITE) + { + // create a normal IM session + session_id = gIMMgr->addP2PSession( + session_name, + caller_id, + payload["session_handle"].asString(), + payload["session_uri"].asString()); + + if (voice) + { + gIMMgr->startCall(session_id, LLVoiceChannel::INCOMING_CALL); + } + else + { + LLAvatarActions::startIM(caller_id); + } + + gIMMgr->clearPendingAgentListUpdates(session_id); + gIMMgr->clearPendingInvitation(session_id); + } + else + { + //session name should not be empty, but it can contain spaces so we don't trim + std::string correct_session_name = session_name; + if (session_name.empty()) + { + LL_WARNS() << "Received an empty session name from a server" << LL_ENDL; + + switch(type){ + case IM_SESSION_CONFERENCE_START: + case IM_SESSION_GROUP_START: + case IM_SESSION_INVITE: + if (gAgent.isInGroup(session_id, true)) + { + LLGroupData data; + if (!gAgent.getGroupData(session_id, data)) break; + correct_session_name = data.mName; + } + else + { + // *NOTE: really should be using callbacks here + LLAvatarName av_name; + if (LLAvatarNameCache::get(caller_id, &av_name)) + { + correct_session_name = av_name.getCompleteName(); + correct_session_name.append(ADHOC_NAME_SUFFIX); + } + } + LL_INFOS("IMVIEW") << "Corrected session name is " << correct_session_name << LL_ENDL; + break; + default: + LL_WARNS("IMVIEW") << "Received an empty session name from a server and failed to generate a new proper session name" << LL_ENDL; + break; + } + } + + gIMMgr->addSession(correct_session_name, type, session_id, true); + + std::string url = gAgent.getRegion()->getCapability( + "ChatSessionRequest"); + + if (voice) + { + LLCoros::instance().launch("chatterBoxInvitationCoro", + boost::bind(&chatterBoxInvitationCoro, url, + session_id, inv_type)); + + // send notification message to the corresponding chat + if (payload["notify_box_type"].asString() == "VoiceInviteGroup" || payload["notify_box_type"].asString() == "VoiceInviteAdHoc") + { + LLStringUtil::format_map_t string_args; + string_args["[NAME]"] = payload["caller_name"].asString(); + std::string message = LLTrans::getString("name_started_call", string_args); + LLIMModel::getInstance()->addMessageSilently(session_id, SYSTEM_FROM, LLUUID::null, message); + } + } + } + if (voice) + { + break; + } + } + case 1: // decline + { + if (type == IM_SESSION_P2P_INVITE) + { + if(LLVoiceClient::getInstance()) + { + std::string s = payload["session_handle"].asString(); + LLVoiceClient::getInstance()->declineInvite(s); + } + } + else + { + std::string url = gAgent.getRegion()->getCapability( + "ChatSessionRequest"); + + LLSD data; + data["method"] = "decline invitation"; + data["session-id"] = session_id; + + LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data, + "Invitation declined", + "Invitation decline failed."); + } + } + + gIMMgr->clearPendingAgentListUpdates(session_id); + gIMMgr->clearPendingInvitation(session_id); + } +} + +bool inviteUserResponse(const LLSD& notification, const LLSD& response) +{ + if (!gIMMgr) + return false; + + const LLSD& payload = notification["payload"]; + LLUUID session_id = payload["session_id"].asUUID(); + EInstantMessage type = (EInstantMessage)payload["type"].asInteger(); + LLIMMgr::EInvitationType inv_type = (LLIMMgr::EInvitationType)payload["inv_type"].asInteger(); + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + switch(option) + { + case 0: // accept + { + if (type == IM_SESSION_P2P_INVITE) + { + // create a normal IM session + session_id = gIMMgr->addP2PSession( + payload["session_name"].asString(), + payload["caller_id"].asUUID(), + payload["session_handle"].asString(), + payload["session_uri"].asString()); + + gIMMgr->startCall(session_id); + + gIMMgr->clearPendingAgentListUpdates(session_id); + gIMMgr->clearPendingInvitation(session_id); + } + else + { + gIMMgr->addSession( + payload["session_name"].asString(), + type, + session_id, true); + + std::string url = gAgent.getRegion()->getCapability( + "ChatSessionRequest"); + + LLCoros::instance().launch("chatterBoxInvitationCoro", + boost::bind(&chatterBoxInvitationCoro, url, + session_id, inv_type)); + } + } + break; + case 2: // mute (also implies ignore, so this falls through to the "ignore" case below) + { + // mute the sender of this invite + if (!LLMuteList::getInstance()->isMuted(payload["caller_id"].asUUID())) + { + LLMute mute(payload["caller_id"].asUUID(), payload["caller_name"].asString(), LLMute::AGENT); + LLMuteList::getInstance()->add(mute); + } + } + /* FALLTHROUGH */ + + case 1: // decline + { + if (type == IM_SESSION_P2P_INVITE) + { + std::string s = payload["session_handle"].asString(); + LLVoiceClient::getInstance()->declineInvite(s); + } + else + { + std::string url = gAgent.getRegion()->getCapability( + "ChatSessionRequest"); + + LLSD data; + data["method"] = "decline invitation"; + data["session-id"] = session_id; + LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, data, + "Invitation declined.", + "Invitation decline failed."); + } + } + + gIMMgr->clearPendingAgentListUpdates(session_id); + gIMMgr->clearPendingInvitation(session_id); + break; + } + + return false; +} + +// +// Member Functions +// + +LLIMMgr::LLIMMgr() +{ + mPendingInvitations = LLSD::emptyMap(); + mPendingAgentListUpdates = LLSD::emptyMap(); + + LLIMModel::getInstance()->addNewMsgCallback(boost::bind(&LLFloaterIMSession::sRemoveTypingIndicator, _1)); + + gSavedPerAccountSettings.declareBOOL("FetchGroupChatHistory", true, "Fetch recent messages from group chat servers when a group window opens", LLControlVariable::PERSIST_ALWAYS); +} + +// Add a message to a session. +void LLIMMgr::addMessage( + const LLUUID& session_id, + const LLUUID& target_id, + const std::string& from, + const std::string& msg, + bool is_offline_msg, + const std::string& session_name, + EInstantMessage dialog, + U32 parent_estate_id, + const LLUUID& region_id, + const LLVector3& position, + bool is_region_msg, + U32 timestamp) // May be zero +{ + LLUUID other_participant_id = target_id; + + LLUUID new_session_id = session_id; + if (new_session_id.isNull()) + { + //no session ID...compute new one + new_session_id = computeSessionID(dialog, other_participant_id); + } + + //*NOTE session_name is empty in case of incoming P2P sessions + std::string fixed_session_name = from; + bool name_is_setted = false; + if(!session_name.empty() && session_name.size()>1) + { + fixed_session_name = session_name; + name_is_setted = true; + } + bool skip_message = false; + bool from_linden = LLMuteList::isLinden(from); + if (gSavedPerAccountSettings.getBOOL("VoiceCallsFriendsOnly") && !from_linden) + { + // Evaluate if we need to skip this message when that setting is true (default is false) + skip_message = (LLAvatarTracker::instance().getBuddyInfo(other_participant_id) == NULL); // Skip non friends... + skip_message &= !(other_participant_id == gAgentID); // You are your best friend... Don't skip yourself + } + + bool new_session = !hasSession(new_session_id); + if (new_session) + { + // Group chat session was initiated by muted resident, do not start this session viewerside + // do not send leave msg either, so we are able to get group messages from other participants + if ((IM_SESSION_INVITE == dialog) && gAgent.isInGroup(new_session_id) && + LLMuteList::getInstance()->isMuted(other_participant_id, LLMute::flagTextChat) && !from_linden) + { + return; + } + + LLAvatarName av_name; + if (LLAvatarNameCache::get(other_participant_id, &av_name) && !name_is_setted) + { + fixed_session_name = av_name.getDisplayName(); + } + LLIMModel::getInstance()->newSession(new_session_id, fixed_session_name, dialog, other_participant_id, false, is_offline_msg); + + LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(new_session_id); + if (session) + { + skip_message &= !session->isGroupSessionType(); // Do not skip group chats... + if (skip_message) + { + gIMMgr->leaveSession(new_session_id); + } + // When we get a new IM, and if you are a god, display a bit + // of information about the source. This is to help liaisons + // when answering questions. + if (gAgent.isGodlike()) + { + // *TODO:translate (low priority, god ability) + std::ostringstream bonus_info; + bonus_info << LLTrans::getString("***") + " " + LLTrans::getString("IMParentEstate") + ":" + " " + << parent_estate_id + << ((parent_estate_id == 1) ? "," + LLTrans::getString("IMMainland") : "") + << ((parent_estate_id == 5) ? "," + LLTrans::getString("IMTeen") : ""); + + // once we have web-services (or something) which returns + // information about a region id, we can print this out + // and even have it link to map-teleport or something. + //<< "*** region_id: " << region_id << std::endl + //<< "*** position: " << position << std::endl; + + LLIMModel::instance().addMessage(new_session_id, from, other_participant_id, bonus_info.str(), true, is_region_msg); + } + + // Logically it would make more sense to reject the session sooner, in another area of the + // code, but the session has to be established inside the server before it can be left. + if (LLMuteList::getInstance()->isMuted(other_participant_id, LLMute::flagTextChat) && !from_linden) + { + LL_WARNS() << "Leaving IM session from initiating muted resident " << from << LL_ENDL; + if (!gIMMgr->leaveSession(new_session_id)) + { + LL_INFOS("IMVIEW") << "Session " << new_session_id << " does not exist." << LL_ENDL; + } + return; + } + + // Fetch group chat history, enabled by default. + if (gSavedPerAccountSettings.getBOOL("FetchGroupChatHistory")) + { + std::string chat_url = gAgent.getRegionCapability("ChatSessionRequest"); + if (!chat_url.empty()) + { + LLCoros::instance().launch("chatterBoxHistoryCoro", boost::bind(&chatterBoxHistoryCoro, chat_url, session_id, from, msg, timestamp)); + } + } + + //Play sound for new conversations + if (!skip_message & !gAgent.isDoNotDisturb() && (gSavedSettings.getBOOL("PlaySoundNewConversation"))) + { + make_ui_sound("UISndNewIncomingIMSession"); + } + } + else + { + // Failed to create a session, most likely due to empty name (name cache failed?) + LL_WARNS() << "Failed to create IM session " << fixed_session_name << LL_ENDL; + } + } + + if (!LLMuteList::getInstance()->isMuted(other_participant_id, LLMute::flagTextChat) && !skip_message) + { + LLIMModel::instance().addMessage(new_session_id, from, other_participant_id, msg, true, is_region_msg, timestamp); + } + + // Open conversation floater if offline messages are present + if (is_offline_msg && !skip_message) + { + LLFloaterReg::showInstance("im_container"); + LLFloaterReg::getTypedInstance("im_container")-> + flashConversationItemWidget(new_session_id, true); + } +} + +void LLIMMgr::addSystemMessage(const LLUUID& session_id, const std::string& message_name, const LLSD& args) +{ + LLUIString message; + + // null session id means near me (chat history) + if (session_id.isNull()) + { + message = LLTrans::getString(message_name); + message.setArgs(args); + + LLChat chat(message); + chat.mSourceType = CHAT_SOURCE_SYSTEM; + + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); + if (nearby_chat) + { + nearby_chat->addMessage(chat); + } + } + else // going to IM session + { + message = LLTrans::getString(message_name + "-im"); + message.setArgs(args); + if (hasSession(session_id)) + { + gIMMgr->addMessage(session_id, LLUUID::null, SYSTEM_FROM, message.getString()); + } + // log message to file + + else + { + LLAvatarName av_name; + // since we select user to share item with - his name is already in cache + LLAvatarNameCache::get(args["user_id"], &av_name); + std::string session_name = LLCacheName::buildUsername(av_name.getUserName()); + LLIMModel::instance().logToFile(session_name, SYSTEM_FROM, LLUUID::null, message.getString()); + } + } +} + +S32 LLIMMgr::getNumberOfUnreadIM() +{ + std::map::iterator it; + + S32 num = 0; + for(it = LLIMModel::getInstance()->mId2SessionMap.begin(); it != LLIMModel::getInstance()->mId2SessionMap.end(); ++it) + { + num += (*it).second->mNumUnread; + } + + return num; +} + +S32 LLIMMgr::getNumberOfUnreadParticipantMessages() +{ + std::map::iterator it; + + S32 num = 0; + for(it = LLIMModel::getInstance()->mId2SessionMap.begin(); it != LLIMModel::getInstance()->mId2SessionMap.end(); ++it) + { + num += (*it).second->mParticipantUnreadMessageCount; + } + + return num; +} + +void LLIMMgr::autoStartCallOnStartup(const LLUUID& session_id) +{ + LLIMModel::LLIMSession *session = LLIMModel::getInstance()->findIMSession(session_id); + if (!session) return; + + if (session->mSessionInitialized) + { + startCall(session_id); + } + else + { + session->mStartCallOnInitialize = true; + } +} + +LLUUID LLIMMgr::addP2PSession(const std::string& name, + const LLUUID& other_participant_id, + const std::string& voice_session_handle, + const std::string& caller_uri) +{ + LLUUID session_id = addSession(name, IM_NOTHING_SPECIAL, other_participant_id, true); + + LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(session_id); + if (speaker_mgr) + { + LLVoiceChannelP2P* voice_channel = dynamic_cast(speaker_mgr->getVoiceChannel()); + if (voice_channel) + { + voice_channel->setSessionHandle(voice_session_handle, caller_uri); + } + } + return session_id; +} + +// This adds a session to the talk view. The name is the local name of +// the session, dialog specifies the type of session. If the session +// exists, it is brought forward. Specifying id = NULL results in an +// im session to everyone. Returns the uuid of the session. +LLUUID LLIMMgr::addSession( + const std::string& name, + EInstantMessage dialog, + const LLUUID& other_participant_id, bool voice) +{ + std::vector ids; + ids.push_back(other_participant_id); + LLUUID session_id = addSession(name, dialog, other_participant_id, ids, voice); + return session_id; +} + +// Adds a session using the given session_id. If the session already exists +// the dialog type is assumed correct. Returns the uuid of the session. +LLUUID LLIMMgr::addSession( + const std::string& name, + EInstantMessage dialog, + const LLUUID& other_participant_id, + const std::vector& ids, bool voice, + const LLUUID& floater_id) +{ + if (ids.empty()) + { + return LLUUID::null; + } + + if (name.empty()) + { + LL_WARNS() << "Session name cannot be null!" << LL_ENDL; + return LLUUID::null; + } + + LLUUID session_id = computeSessionID(dialog,other_participant_id); + + if (floater_id.notNull()) + { + LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(floater_id); + + if (im_floater) + { + // The IM floater should be initialized with a new session_id + // so that it is found by that id when creating a chiclet in LLFloaterIMSession::onIMChicletCreated, + // and a new floater is not created. + im_floater->initIMSession(session_id); + im_floater->reloadMessages(); + } + } + + bool new_session = (LLIMModel::getInstance()->findIMSession(session_id) == NULL); + + //works only for outgoing ad-hoc sessions + if (new_session && IM_SESSION_CONFERENCE_START == dialog && ids.size()) + { + LLIMModel::LLIMSession* ad_hoc_found = LLIMModel::getInstance()->findAdHocIMSession(ids); + if (ad_hoc_found) + { + new_session = false; + session_id = ad_hoc_found->mSessionID; + } + } + + //Notify observers that a session was added + if (new_session) + { + LLIMModel::getInstance()->newSession(session_id, name, dialog, other_participant_id, ids, voice); + } + //Notifies observers that the session was already added + else + { + std::string session_name = LLIMModel::getInstance()->getName(session_id); + LLIMMgr::getInstance()->notifyObserverSessionActivated(session_id, session_name, other_participant_id); + } + + //we don't need to show notes about online/offline, mute/unmute users' statuses for existing sessions + if (!new_session) return session_id; + + LL_INFOS("IMVIEW") << "LLIMMgr::addSession, new session added, name = " << name << ", session id = " << session_id << LL_ENDL; + + //Per Plan's suggestion commented "explicit offline status warning" out to make Dessie happier (see EXT-3609) + //*TODO After February 2010 remove this commented out line if no one will be missing that warning + //noteOfflineUsers(session_id, floater, ids); + + // Only warn for regular IMs - not group IMs + if( dialog == IM_NOTHING_SPECIAL ) + { + noteMutedUsers(session_id, ids); + } + + notifyObserverSessionVoiceOrIMStarted(session_id); + + return session_id; +} + +bool LLIMMgr::leaveSession(const LLUUID& session_id) +{ + LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(session_id); + if (!im_session) return false; + + LLIMModel::getInstance()->sendLeaveSession(session_id, im_session->mOtherParticipantID); + gIMMgr->removeSession(session_id); + return true; +} + +// Removes data associated with a particular session specified by session_id +void LLIMMgr::removeSession(const LLUUID& session_id) +{ + llassert_always(hasSession(session_id)); + + clearPendingInvitation(session_id); + clearPendingAgentListUpdates(session_id); + + LLIMModel::getInstance()->clearSession(session_id); + + LL_INFOS("IMVIEW") << "LLIMMgr::removeSession, session removed, session id = " << session_id << LL_ENDL; + + notifyObserverSessionRemoved(session_id); +} + +void LLIMMgr::inviteToSession( + const LLUUID& session_id, + const std::string& session_name, + const LLUUID& caller_id, + const std::string& caller_name, + EInstantMessage type, + EInvitationType inv_type, + const std::string& session_handle, + const std::string& session_uri) +{ + std::string notify_box_type; + // voice invite question is different from default only for group call (EXT-7118) + std::string question_type = "VoiceInviteQuestionDefault"; + + bool voice_invite = false; + bool is_linden = LLMuteList::isLinden(caller_name); + + + if(type == IM_SESSION_P2P_INVITE) + { + //P2P is different...they only have voice invitations + notify_box_type = "VoiceInviteP2P"; + voice_invite = true; + } + else if ( gAgent.isInGroup(session_id, true) ) + { + //only really old school groups have voice invitations + notify_box_type = "VoiceInviteGroup"; + question_type = "VoiceInviteQuestionGroup"; + voice_invite = true; + } + else if ( inv_type == INVITATION_TYPE_VOICE ) + { + //else it's an ad-hoc + //and a voice ad-hoc + notify_box_type = "VoiceInviteAdHoc"; + voice_invite = true; + } + else if ( inv_type == INVITATION_TYPE_IMMEDIATE ) + { + notify_box_type = "InviteAdHoc"; + } + + LLSD payload; + payload["session_id"] = session_id; + payload["session_name"] = session_name; + payload["caller_id"] = caller_id; + payload["caller_name"] = caller_name; + payload["type"] = type; + payload["inv_type"] = inv_type; + payload["session_handle"] = session_handle; + payload["session_uri"] = session_uri; + payload["notify_box_type"] = notify_box_type; + payload["question_type"] = question_type; + + //ignore invites from muted residents + if (!is_linden) + { + if (LLMuteList::getInstance()->isMuted(caller_id, LLMute::flagVoiceChat) + && voice_invite && "VoiceInviteQuestionDefault" == question_type) + { + LL_INFOS("IMVIEW") << "Rejecting voice call from initiating muted resident " << caller_name << LL_ENDL; + LLIncomingCallDialog::processCallResponse(1, payload); + return; + } + else if (LLMuteList::getInstance()->isMuted(caller_id, LLMute::flagAll & ~LLMute::flagVoiceChat) && !voice_invite) + { + LL_INFOS("IMVIEW") << "Rejecting session invite from initiating muted resident " << caller_name << LL_ENDL; + return; + } + } + + LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(session_id); + if (channelp && channelp->callStarted()) + { + // you have already started a call to the other user, so just accept the invite + LLIncomingCallDialog::processCallResponse(0, payload); + return; + } + + if (voice_invite) + { + bool isRejectGroupCall = (gSavedSettings.getBOOL("VoiceCallsRejectGroup") && (notify_box_type == "VoiceInviteGroup")); + bool isRejectNonFriendCall = (gSavedPerAccountSettings.getBOOL("VoiceCallsFriendsOnly") && (LLAvatarTracker::instance().getBuddyInfo(caller_id) == NULL)); + if (isRejectGroupCall || isRejectNonFriendCall || gAgent.isDoNotDisturb()) + { + if (gAgent.isDoNotDisturb() && !isRejectGroupCall && !isRejectNonFriendCall) + { + if (!hasSession(session_id) && (type == IM_SESSION_P2P_INVITE)) + { + std::string fixed_session_name = caller_name; + if(!session_name.empty() && session_name.size()>1) + { + fixed_session_name = session_name; + } + else + { + LLAvatarName av_name; + if (LLAvatarNameCache::get(caller_id, &av_name)) + { + fixed_session_name = av_name.getDisplayName(); + } + } + LLIMModel::getInstance()->newSession(session_id, fixed_session_name, IM_NOTHING_SPECIAL, caller_id, false, false); + } + + LLSD args; + addSystemMessage(session_id, "you_auto_rejected_call", args); + send_do_not_disturb_message(gMessageSystem, caller_id, session_id); + } + // silently decline the call + LLIncomingCallDialog::processCallResponse(1, payload); + return; + } + } + + if ( !mPendingInvitations.has(session_id.asString()) ) + { + if (caller_name.empty()) + { + LLAvatarNameCache::get(caller_id, + boost::bind(&LLIMMgr::onInviteNameLookup, payload, _1, _2)); + } + else + { + LLFloaterReg::showInstance("incoming_call", payload, false); + } + + // Add the caller to the Recent List here (at this point + // "incoming_call" floater is shown and the recipient can + // reject the call), because even if a recipient will reject + // the call, the caller should be added to the recent list + // anyway. STORM-507. + if(type == IM_SESSION_P2P_INVITE) + LLRecentPeople::instance().add(caller_id); + + mPendingInvitations[session_id.asString()] = LLSD(); + } +} + +void LLIMMgr::onInviteNameLookup(LLSD payload, const LLUUID& id, const LLAvatarName& av_name) +{ + payload["caller_name"] = av_name.getUserName(); + payload["session_name"] = payload["caller_name"].asString(); + + std::string notify_box_type = payload["notify_box_type"].asString(); + + LLFloaterReg::showInstance("incoming_call", payload, false); +} + +//*TODO disconnects all sessions +void LLIMMgr::disconnectAllSessions() +{ + //*TODO disconnects all IM sessions +} + +bool LLIMMgr::hasSession(const LLUUID& session_id) +{ + return LLIMModel::getInstance()->findIMSession(session_id) != NULL; +} + +void LLIMMgr::clearPendingInvitation(const LLUUID& session_id) +{ + if ( mPendingInvitations.has(session_id.asString()) ) + { + mPendingInvitations.erase(session_id.asString()); + } +} + +void LLIMMgr::processAgentListUpdates(const LLUUID& session_id, const LLSD& body) +{ + LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); + if ( im_floater ) + { + im_floater->processAgentListUpdates(body); + } + LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(session_id); + if (speaker_mgr) + { + speaker_mgr->updateSpeakers(body); + + // also the same call is added into LLVoiceClient::participantUpdatedEvent because + // sometimes it is called AFTER LLViewerChatterBoxSessionAgentListUpdates::post() + // when moderation state changed too late. See EXT-3544. + speaker_mgr->update(true); + } + else + { + //we don't have a speaker manager yet..something went wrong + //we are probably receiving an update here before + //a start or an acceptance of an invitation. Race condition. + gIMMgr->addPendingAgentListUpdates( + session_id, + body); + } +} + +LLSD LLIMMgr::getPendingAgentListUpdates(const LLUUID& session_id) +{ + if ( mPendingAgentListUpdates.has(session_id.asString()) ) + { + return mPendingAgentListUpdates[session_id.asString()]; + } + else + { + return LLSD(); + } +} + +void LLIMMgr::addPendingAgentListUpdates( + const LLUUID& session_id, + const LLSD& updates) +{ + LLSD::map_const_iterator iter; + + if ( !mPendingAgentListUpdates.has(session_id.asString()) ) + { + //this is a new agent list update for this session + mPendingAgentListUpdates[session_id.asString()] = LLSD::emptyMap(); + } + + if ( + updates.has("agent_updates") && + updates["agent_updates"].isMap() && + updates.has("updates") && + updates["updates"].isMap() ) + { + //new school update + LLSD update_types = LLSD::emptyArray(); + LLSD::array_iterator array_iter; + + update_types.append("agent_updates"); + update_types.append("updates"); + + for ( + array_iter = update_types.beginArray(); + array_iter != update_types.endArray(); + ++array_iter) + { + //we only want to include the last update for a given agent + for ( + iter = updates[array_iter->asString()].beginMap(); + iter != updates[array_iter->asString()].endMap(); + ++iter) + { + mPendingAgentListUpdates[session_id.asString()][array_iter->asString()][iter->first] = + iter->second; + } + } + } + else if ( + updates.has("updates") && + updates["updates"].isMap() ) + { + //old school update where the SD contained just mappings + //of agent_id -> "LEAVE"/"ENTER" + + //only want to keep last update for each agent + for ( + iter = updates["updates"].beginMap(); + iter != updates["updates"].endMap(); + ++iter) + { + mPendingAgentListUpdates[session_id.asString()]["updates"][iter->first] = + iter->second; + } + } +} + +void LLIMMgr::clearPendingAgentListUpdates(const LLUUID& session_id) +{ + if ( mPendingAgentListUpdates.has(session_id.asString()) ) + { + mPendingAgentListUpdates.erase(session_id.asString()); + } +} + +void LLIMMgr::notifyObserverSessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id, bool has_offline_msg) +{ + for (session_observers_list_t::iterator it = mSessionObservers.begin(); it != mSessionObservers.end(); it++) + { + (*it)->sessionAdded(session_id, name, other_participant_id, has_offline_msg); + } +} + +void LLIMMgr::notifyObserverSessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id) +{ + for (session_observers_list_t::iterator it = mSessionObservers.begin(); it != mSessionObservers.end(); it++) + { + (*it)->sessionActivated(session_id, name, other_participant_id); + } +} + +void LLIMMgr::notifyObserverSessionVoiceOrIMStarted(const LLUUID& session_id) +{ + for (session_observers_list_t::iterator it = mSessionObservers.begin(); it != mSessionObservers.end(); it++) + { + (*it)->sessionVoiceOrIMStarted(session_id); + } +} + +void LLIMMgr::notifyObserverSessionRemoved(const LLUUID& session_id) +{ + for (session_observers_list_t::iterator it = mSessionObservers.begin(); it != mSessionObservers.end(); it++) + { + (*it)->sessionRemoved(session_id); + } +} + +void LLIMMgr::notifyObserverSessionIDUpdated( const LLUUID& old_session_id, const LLUUID& new_session_id ) +{ + for (session_observers_list_t::iterator it = mSessionObservers.begin(); it != mSessionObservers.end(); it++) + { + (*it)->sessionIDUpdated(old_session_id, new_session_id); + } + +} + +void LLIMMgr::addSessionObserver(LLIMSessionObserver *observer) +{ + mSessionObservers.push_back(observer); +} + +void LLIMMgr::removeSessionObserver(LLIMSessionObserver *observer) +{ + mSessionObservers.remove(observer); +} + +bool LLIMMgr::startCall(const LLUUID& session_id, LLVoiceChannel::EDirection direction) +{ + LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(session_id); + if (!voice_channel) return false; + + voice_channel->setCallDirection(direction); + voice_channel->activate(); + return true; +} + +bool LLIMMgr::endCall(const LLUUID& session_id) +{ + LLVoiceChannel* voice_channel = LLIMModel::getInstance()->getVoiceChannel(session_id); + if (!voice_channel) return false; + + voice_channel->deactivate(); + LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(session_id); + if (im_session) + { + // need to update speakers' state + im_session->mSpeakers->update(false); + } + return true; +} + +bool LLIMMgr::isVoiceCall(const LLUUID& session_id) +{ + LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(session_id); + if (!im_session) return false; + + return im_session->mStartedAsIMCall; +} + +void LLIMMgr::updateDNDMessageStatus() +{ + if (LLIMModel::getInstance()->mId2SessionMap.empty()) return; + + std::map::const_iterator it = LLIMModel::getInstance()->mId2SessionMap.begin(); + for (; it != LLIMModel::getInstance()->mId2SessionMap.end(); ++it) + { + LLIMModel::LLIMSession* session = (*it).second; + + if (session->isP2P()) + { + setDNDMessageSent(session->mSessionID,false); + } + } +} + +bool LLIMMgr::isDNDMessageSend(const LLUUID& session_id) +{ + LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(session_id); + if (!im_session) return false; + + return im_session->mIsDNDsend; +} + +void LLIMMgr::setDNDMessageSent(const LLUUID& session_id, bool is_send) +{ + LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(session_id); + if (!im_session) return; + + im_session->mIsDNDsend = is_send; +} + +void LLIMMgr::addNotifiedNonFriendSessionID(const LLUUID& session_id) +{ + mNotifiedNonFriendSessions.insert(session_id); +} + +bool LLIMMgr::isNonFriendSessionNotified(const LLUUID& session_id) +{ + return mNotifiedNonFriendSessions.end() != mNotifiedNonFriendSessions.find(session_id); + +} + +void LLIMMgr::noteOfflineUsers( + const LLUUID& session_id, + const std::vector& ids) +{ + S32 count = ids.size(); + if(count == 0) + { + const std::string& only_user = LLTrans::getString("only_user_message"); + LLIMModel::getInstance()->addMessage(session_id, SYSTEM_FROM, LLUUID::null, only_user); + } + else + { + const LLRelationship* info = NULL; + LLAvatarTracker& at = LLAvatarTracker::instance(); + LLIMModel& im_model = LLIMModel::instance(); + for(S32 i = 0; i < count; ++i) + { + info = at.getBuddyInfo(ids.at(i)); + LLAvatarName av_name; + if (info + && !info->isOnline() + && LLAvatarNameCache::get(ids.at(i), &av_name)) + { + LLUIString offline = LLTrans::getString("offline_message"); + // Use display name only because this user is your friend + offline.setArg("[NAME]", av_name.getDisplayName()); + im_model.proccessOnlineOfflineNotification(session_id, offline); + } + } + } +} + +void LLIMMgr::noteMutedUsers(const LLUUID& session_id, + const std::vector& ids) +{ + // Don't do this if we don't have a mute list. + LLMuteList *ml = LLMuteList::getInstance(); + if( !ml ) + { + return; + } + + S32 count = ids.size(); + if(count > 0) + { + LLIMModel* im_model = LLIMModel::getInstance(); + + for(S32 i = 0; i < count; ++i) + { + if( ml->isMuted(ids.at(i)) ) + { + LLUIString muted = LLTrans::getString("muted_message"); + + im_model->addMessage(session_id, SYSTEM_FROM, LLUUID::null, muted); + break; + } + } + } +} + +void LLIMMgr::processIMTypingStart(const LLUUID& from_id, const EInstantMessage im_type) +{ + processIMTypingCore(from_id, im_type, true); +} + +void LLIMMgr::processIMTypingStop(const LLUUID& from_id, const EInstantMessage im_type) +{ + processIMTypingCore(from_id, im_type, false); +} + +void LLIMMgr::processIMTypingCore(const LLUUID& from_id, const EInstantMessage im_type, bool typing) +{ + LLUUID session_id = computeSessionID(im_type, from_id); + LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); + if ( im_floater ) + { + im_floater->processIMTyping(from_id, typing); + } +} + +class LLViewerChatterBoxSessionStartReply : public LLHTTPNode +{ +public: + virtual void describe(Description& desc) const + { + desc.shortInfo("Used for receiving a reply to a request to initialize an ChatterBox session"); + desc.postAPI(); + desc.input( + "{\"client_session_id\": UUID, \"session_id\": UUID, \"success\" boolean, \"reason\": string"); + desc.source(__FILE__, __LINE__); + } + + virtual void post(ResponsePtr response, + const LLSD& context, + const LLSD& input) const + { + if (LLApp::isExiting() || gDisconnected) + { + LL_DEBUGS("ChatHistory") << "Ignoring ChatterBox session, Shutting down" << LL_ENDL; + return; + } + + LLSD body; + LLUUID temp_session_id; + LLUUID session_id; + bool success; + + body = input["body"]; + success = body["success"].asBoolean(); + temp_session_id = body["temp_session_id"].asUUID(); + + if ( success ) + { + session_id = body["session_id"].asUUID(); + + LLIMModel::getInstance()->processSessionInitializedReply(temp_session_id, session_id); + + LLIMSpeakerMgr* speaker_mgr = LLIMModel::getInstance()->getSpeakerManager(session_id); + if (speaker_mgr) + { + speaker_mgr->setSpeakers(body); + speaker_mgr->updateSpeakers(gIMMgr->getPendingAgentListUpdates(session_id)); + } + + LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); + if ( im_floater ) + { + if ( body.has("session_info") ) + { + im_floater->processSessionUpdate(body["session_info"]); + + // Send request for chat history, if enabled. + if (gSavedPerAccountSettings.getBOOL("FetchGroupChatHistory")) + { + std::string url = gAgent.getRegion()->getCapability("ChatSessionRequest"); + LLCoros::instance().launch("chatterBoxHistoryCoro", + boost::bind(&chatterBoxHistoryCoro, url, session_id, "", "", 0)); + } + } + } + + gIMMgr->clearPendingAgentListUpdates(session_id); + } + else + { + //throw an error dialog and close the temp session's floater + gIMMgr->showSessionStartError(body["error"].asString(), temp_session_id); + } + + gIMMgr->clearPendingAgentListUpdates(session_id); + } +}; + +class LLViewerChatterBoxSessionEventReply : public LLHTTPNode +{ +public: + virtual void describe(Description& desc) const + { + desc.shortInfo("Used for receiving a reply to a ChatterBox session event"); + desc.postAPI(); + desc.input( + "{\"event\": string, \"reason\": string, \"success\": boolean, \"session_id\": UUID"); + desc.source(__FILE__, __LINE__); + } + + virtual void post(ResponsePtr response, + const LLSD& context, + const LLSD& input) const + { + LLUUID session_id; + bool success; + + LLSD body = input["body"]; + success = body["success"].asBoolean(); + session_id = body["session_id"].asUUID(); + + if ( !success ) + { + //throw an error dialog + gIMMgr->showSessionEventError( + body["event"].asString(), + body["error"].asString(), + session_id); + } + } +}; + +class LLViewerForceCloseChatterBoxSession: public LLHTTPNode +{ +public: + virtual void post(ResponsePtr response, + const LLSD& context, + const LLSD& input) const + { + LLUUID session_id; + std::string reason; + + session_id = input["body"]["session_id"].asUUID(); + reason = input["body"]["reason"].asString(); + + gIMMgr->showSessionForceClose(reason, session_id); + } +}; + +class LLViewerChatterBoxSessionAgentListUpdates : public LLHTTPNode +{ +public: + virtual void post( + ResponsePtr responder, + const LLSD& context, + const LLSD& input) const + { + const LLUUID& session_id = input["body"]["session_id"].asUUID(); + gIMMgr->processAgentListUpdates(session_id, input["body"]); + } +}; + +class LLViewerChatterBoxSessionUpdate : public LLHTTPNode +{ +public: + virtual void post( + ResponsePtr responder, + const LLSD& context, + const LLSD& input) const + { + LLUUID session_id = input["body"]["session_id"].asUUID(); + LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); + if ( im_floater ) + { + im_floater->processSessionUpdate(input["body"]["info"]); + } + LLIMSpeakerMgr* im_mgr = LLIMModel::getInstance()->getSpeakerManager(session_id); + if (im_mgr) + { + im_mgr->processSessionUpdate(input["body"]["info"]); + } + } +}; + + +class LLViewerChatterBoxInvitation : public LLHTTPNode +{ +public: + + virtual void post( + ResponsePtr response, + const LLSD& context, + const LLSD& input) const + { + //for backwards compatiblity reasons...we need to still + //check for 'text' or 'voice' invitations...bleh + if ( input["body"].has("instantmessage") ) + { + LLSD message_params = + input["body"]["instantmessage"]["message_params"]; + + //do something here to have the IM invite behave + //just like a normal IM + //this is just replicated code from process_improved_im + //and should really go in it's own function -jwolk + + std::string message = message_params["message"].asString(); + std::string name = message_params["from_name"].asString(); + LLUUID from_id = message_params["from_id"].asUUID(); + LLUUID session_id = message_params["id"].asUUID(); + std::vector bin_bucket = message_params["data"]["binary_bucket"].asBinary(); + U8 offline = (U8)message_params["offline"].asInteger(); + + time_t timestamp = + (time_t) message_params["timestamp"].asInteger(); + + bool is_do_not_disturb = gAgent.isDoNotDisturb(); + + //don't return if user is muted b/c proper way to ignore a muted user who + //initiated an adhoc/group conference is to create then leave the session (see STORM-1731) + if (is_do_not_disturb) + { + return; + } + + // standard message, not from system + std::string saved; + if(offline == IM_OFFLINE) + { + LLStringUtil::format_map_t args; + args["[LONG_TIMESTAMP]"] = formatted_time(timestamp); + saved = LLTrans::getString("Saved_message", args); + } + std::string buffer = saved + message; + + if(from_id == gAgentID) + { + return; + } + gIMMgr->addMessage( + session_id, + from_id, + name, + buffer, + IM_OFFLINE == offline, + std::string((char*)&bin_bucket[0]), + IM_SESSION_INVITE, + message_params["parent_estate_id"].asInteger(), + message_params["region_id"].asUUID(), + ll_vector3_from_sd(message_params["position"]), + false, // is_region_message + timestamp); + + if (LLMuteList::getInstance()->isMuted(from_id, name, LLMute::flagTextChat)) + { + return; + } + + //K now we want to accept the invitation + std::string url = gAgent.getRegionCapability("ChatSessionRequest"); + + if ( url != "" ) + { + LLCoros::instance().launch("chatterBoxInvitationCoro", + boost::bind(&chatterBoxInvitationCoro, url, + session_id, LLIMMgr::INVITATION_TYPE_INSTANT_MESSAGE)); + } + } //end if invitation has instant message + else if ( input["body"].has("voice") ) + { + if(!LLVoiceClient::getInstance()->voiceEnabled() || !LLVoiceClient::getInstance()->isVoiceWorking()) + { + // Don't display voice invites unless the user has voice enabled. + return; + } + + gIMMgr->inviteToSession( + input["body"]["session_id"].asUUID(), + input["body"]["session_name"].asString(), + input["body"]["from_id"].asUUID(), + input["body"]["from_name"].asString(), + IM_SESSION_INVITE, + LLIMMgr::INVITATION_TYPE_VOICE); + } + else if ( input["body"].has("immediate") ) + { + gIMMgr->inviteToSession( + input["body"]["session_id"].asUUID(), + input["body"]["session_name"].asString(), + input["body"]["from_id"].asUUID(), + input["body"]["from_name"].asString(), + IM_SESSION_INVITE, + LLIMMgr::INVITATION_TYPE_IMMEDIATE); + } + } +}; + +LLHTTPRegistration + gHTTPRegistrationMessageChatterboxsessionstartreply( + "/message/ChatterBoxSessionStartReply"); + +LLHTTPRegistration + gHTTPRegistrationMessageChatterboxsessioneventreply( + "/message/ChatterBoxSessionEventReply"); + +LLHTTPRegistration + gHTTPRegistrationMessageForceclosechatterboxsession( + "/message/ForceCloseChatterBoxSession"); + +LLHTTPRegistration + gHTTPRegistrationMessageChatterboxsessionagentlistupdates( + "/message/ChatterBoxSessionAgentListUpdates"); + +LLHTTPRegistration + gHTTPRegistrationMessageChatterBoxSessionUpdate( + "/message/ChatterBoxSessionUpdate"); + +LLHTTPRegistration + gHTTPRegistrationMessageChatterBoxInvitation( + "/message/ChatterBoxInvitation"); + diff --git a/indra/newview/llimview.h b/indra/newview/llimview.h index ceb3df9b0d..7b76cc8c68 100644 --- a/indra/newview/llimview.h +++ b/indra/newview/llimview.h @@ -1,642 +1,642 @@ -/** - * @file LLIMMgr.h - * @brief Container for Instant Messaging - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLIMVIEW_H -#define LL_LLIMVIEW_H - -#include "../llui/lldockablefloater.h" -#include "lleventtimer.h" -#include "llinstantmessage.h" - -#include "lllogchat.h" -#include "llvoicechannel.h" -#include "llinitdestroyclass.h" - -#include "llcoros.h" -#include "lleventcoro.h" - -class LLAvatarName; -class LLFriendObserver; -class LLCallDialogManager; -class LLIMSpeakerMgr; - -/** - * Timeout Timer for outgoing Ad-Hoc/Group IM sessions which being initialized by the server - */ -class LLSessionTimeoutTimer : public LLEventTimer -{ -public: - LLSessionTimeoutTimer(const LLUUID& session_id, F32 period) : LLEventTimer(period), mSessionId(session_id) {} - virtual ~LLSessionTimeoutTimer() {}; - /* virtual */ bool tick(); - -private: - LLUUID mSessionId; -}; - - -/** - * Model (MVC) for IM Sessions - */ -class LLIMModel : public LLSingleton -{ - LLSINGLETON(LLIMModel); - -public: - - typedef std::list chat_message_list_t; - - struct LLIMSession : public boost::signals2::trackable - { - typedef enum e_session_type - { // for now we have 4 predefined types for a session - P2P_SESSION, - GROUP_SESSION, - ADHOC_SESSION, - NONE_SESSION, - } SType; - - LLIMSession(const LLUUID& session_id, const std::string& name, - const EInstantMessage& type, const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice, bool has_offline_msg); - virtual ~LLIMSession(); - - void sessionInitReplyReceived(const LLUUID& new_session_id); - void addMessagesFromHistoryCache(const std::list& history); // From local file - void addMessagesFromServerHistory(const LLSD& history, const std::string& target_from, const std::string& target_message, U32 timestamp); // From chat server - void addMessage(const std::string& from, - const LLUUID& from_id, - const std::string& utf8_text, - const std::string& time, - const bool is_history, - const bool is_region_msg, - U32 timestamp); - - void onVoiceChannelStateChanged(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state, const LLVoiceChannel::EDirection& direction); - - /** @deprecated */ - static void chatFromLogFile(LLLogChat::ELogLineType type, const LLSD& msg, void* userdata); - - bool isOutgoingAdHoc() const; - bool isAdHoc(); - bool isP2P(); - bool isGroupChat(); - - bool isP2PSessionType() const { return mSessionType == P2P_SESSION;} - bool isAdHocSessionType() const { return mSessionType == ADHOC_SESSION;} - bool isGroupSessionType() const { return mSessionType == GROUP_SESSION;} - - LLUUID generateOutgoingAdHocHash() const; - - //*TODO make private - /** ad-hoc sessions involve sophisticated chat history file naming schemes */ - void buildHistoryFileName(); - - void loadHistory(); - - LLUUID mSessionID; - std::string mName; - EInstantMessage mType; - SType mSessionType; - LLUUID mOtherParticipantID; - uuid_vec_t mInitialTargetIDs; - std::string mHistoryFileName; - - // Saved messages from the last minute of history read from the local group chat cache file - std::string mLastHistoryCacheDateTime; - chat_message_list_t mLastHistoryCacheMsgs; - - // connection to voice channel state change signal - boost::signals2::connection mVoiceChannelStateChangeConnection; - - //does NOT include system messages and agent's messages - S32 mParticipantUnreadMessageCount; - - // does include all incoming messages - S32 mNumUnread; - - chat_message_list_t mMsgs; - - LLVoiceChannel* mVoiceChannel; - LLIMSpeakerMgr* mSpeakers; - - bool mSessionInitialized; - - //true if calling back the session URI after the session has closed is possible. - //Currently this will be false only for PSTN P2P calls. - bool mCallBackEnabled; - - bool mTextIMPossible; - bool mStartCallOnInitialize; - - //if IM session is created for a voice call - bool mStartedAsIMCall; - - bool mHasOfflineMessage; - - bool mIsDNDsend; - - private: - void onAdHocNameCache(const LLAvatarName& av_name); - - static LLUUID generateHash(const std::set& sorted_uuids); - boost::signals2::connection mAvatarNameCacheConnection; - }; - - - - /** Session id to session object */ - std::map mId2SessionMap; - - typedef boost::signals2::signal session_signal_t; - session_signal_t mNewMsgSignal; - session_signal_t mNoUnreadMsgsSignal; - - /** - * Find an IM Session corresponding to session_id - * Returns NULL if the session does not exist - */ - LLIMSession* findIMSession(const LLUUID& session_id) const; - - /** - * Find an Ad-Hoc IM Session with specified participants - * @return first found Ad-Hoc session or NULL if the session does not exist - */ - LLIMSession* findAdHocIMSession(const uuid_vec_t& ids); - - /** - * Rebind session data to a new session id. - */ - void processSessionInitializedReply(const LLUUID& old_session_id, const LLUUID& new_session_id); - - boost::signals2::connection addNewMsgCallback(const session_signal_t::slot_type& cb ) { return mNewMsgSignal.connect(cb); } - boost::signals2::connection addNoUnreadMsgsCallback(const session_signal_t::slot_type& cb ) { return mNoUnreadMsgsSignal.connect(cb); } - - /** - * Create new session object in a model - * @param name session name should not be empty, will return false if empty - */ - bool newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, - const uuid_vec_t& ids, bool voice = false, bool has_offline_msg = false); - - bool newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, - const LLUUID& other_participant_id, bool voice = false, bool has_offline_msg = false); - - /** - * Remove all session data associated with a session specified by session_id - */ - bool clearSession(const LLUUID& session_id); - - /** - * Sends no unread messages signal. - */ - void sendNoUnreadMessages(const LLUUID& session_id); - - /** - * Populate supplied std::list with messages starting from index specified by start_index - */ - void getMessages(const LLUUID& session_id, std::list& messages, int start_index = 0, const bool sendNoUnreadMsgs = true); - - /** - * Add a message to an IM Model - the message is saved in a message store associated with a session specified by session_id - * and also saved into a file if log2file is specified. - * It sends new message signal for each added message. - */ - void addMessage(const LLUUID& session_id, - const std::string& from, - const LLUUID& other_participant_id, - const std::string& utf8_text, - bool log2file = true, - bool is_region_msg = false, - U32 time_stamp = 0); - - void processAddingMessage(const LLUUID& session_id, - const std::string& from, - const LLUUID& from_id, - const std::string& utf8_text, - bool log2file, - bool is_region_msg, - U32 time_stamp); - - /** - * Similar to addMessage(...) above but won't send a signal about a new message added - */ - LLIMModel::LLIMSession* addMessageSilently(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, - const std::string& utf8_text, bool log2file = true, bool is_region_msg = false, U32 timestamp = 0); - - /** - * Add a system message to an IM Model - */ - void proccessOnlineOfflineNotification(const LLUUID& session_id, const std::string& utf8_text); - - /** - * Get a session's name. - * For a P2P chat - it's an avatar's name, - * For a group chat - it's a group's name - * For an incoming ad-hoc chat - is received from the server and is in a from of " Conference" - * It is updated in LLIMModel::LLIMSession's constructor to localize the "Conference". - */ - const std::string getName(const LLUUID& session_id) const; - - /** - * Get number of unread messages in a session with session_id - * Returns -1 if the session with session_id doesn't exist - */ - const S32 getNumUnread(const LLUUID& session_id) const; - - /** - * Get uuid of other participant in a session with session_id - * Returns LLUUID::null if the session doesn't exist - * - * *TODO what to do with other participants in ad-hoc and group chats? - */ - const LLUUID& getOtherParticipantID(const LLUUID& session_id) const; - - /** - * Get type of a session specified by session_id - * Returns EInstantMessage::IM_COUNT if the session does not exist - */ - EInstantMessage getType(const LLUUID& session_id) const; - - /** - * Get voice channel for the session specified by session_id - * Returns NULL if the session does not exist - */ - LLVoiceChannel* getVoiceChannel(const LLUUID& session_id) const; - - /** - * Get im speaker manager for the session specified by session_id - * Returns NULL if the session does not exist - */ - LLIMSpeakerMgr* getSpeakerManager(const LLUUID& session_id) const; - - const std::string& getHistoryFileName(const LLUUID& session_id) const; - - static void sendLeaveSession(const LLUUID& session_id, const LLUUID& other_participant_id); - static bool sendStartSession(const LLUUID& temp_session_id, const LLUUID& other_participant_id, - const uuid_vec_t& ids, EInstantMessage dialog); - static void sendTypingState(LLUUID session_id, LLUUID other_participant_id, bool typing); - static void sendMessage(const std::string& utf8_text, const LLUUID& im_session_id, - const LLUUID& other_participant_id, EInstantMessage dialog); - - // Adds people from speakers list (people with whom you are currently speaking) to the Recent People List - static void addSpeakersToRecent(const LLUUID& im_session_id); - - void testMessages(); - - /** - * Saves an IM message into a file - */ - bool logToFile(const std::string& file_name, const std::string& from, const LLUUID& from_id, const std::string& utf8_text); - -private: - - /** - * Populate supplied std::list with messages starting from index specified by start_index without - * emitting no unread messages signal. - */ - void getMessagesSilently(const LLUUID& session_id, std::list& messages, int start_index = 0); - - /** - * Add message to a list of message associated with session specified by session_id - */ - bool addToHistory(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text, bool is_region_msg, U32 timestamp); - -}; - -class LLIMSessionObserver -{ -public: - virtual ~LLIMSessionObserver() {} - virtual void sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id, bool has_offline_msg) = 0; - virtual void sessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id) = 0; - virtual void sessionVoiceOrIMStarted(const LLUUID& session_id) = 0; - virtual void sessionRemoved(const LLUUID& session_id) = 0; - virtual void sessionIDUpdated(const LLUUID& old_session_id, const LLUUID& new_session_id) = 0; -}; - - -class LLIMMgr : public LLSingleton -{ - LLSINGLETON(LLIMMgr); - friend class LLIMModel; - -public: - enum EInvitationType - { - INVITATION_TYPE_INSTANT_MESSAGE = 0, - INVITATION_TYPE_VOICE = 1, - INVITATION_TYPE_IMMEDIATE = 2 - }; - - - // Add a message to a session. The session can keyed to sesion id - // or agent id. - void addMessage(const LLUUID& session_id, - const LLUUID& target_id, - const std::string& from, - const std::string& msg, - bool is_offline_msg = false, - const std::string& session_name = LLStringUtil::null, - EInstantMessage dialog = IM_NOTHING_SPECIAL, - U32 parent_estate_id = 0, - const LLUUID& region_id = LLUUID::null, - const LLVector3& position = LLVector3::zero, - bool is_region_msg = false, - U32 timestamp = 0); - - void addSystemMessage(const LLUUID& session_id, const std::string& message_name, const LLSD& args); - - // This adds a session to the talk view. The name is the local - // name of the session, dialog specifies the type of - // session. Since sessions can be keyed off of first recipient or - // initiator, the session can be matched against the id - // provided. If the session exists, it is brought forward. This - // method accepts a group id or an agent id. Specifying id = NULL - // results in an im session to everyone. Returns the uuid of the - // session. - LLUUID addSession(const std::string& name, - EInstantMessage dialog, - const LLUUID& other_participant_id, bool voice = false); - - // Adds a session using a specific group of starting agents - // the dialog type is assumed correct. Returns the uuid of the session. - // A session can be added to a floater specified by floater_id. - LLUUID addSession(const std::string& name, - EInstantMessage dialog, - const LLUUID& other_participant_id, - const std::vector& ids, bool voice = false, - const LLUUID& floater_id = LLUUID::null); - - /** - * Creates a P2P session with the requisite handle for responding to voice calls. - * - * @param name session name, cannot be null - * @param caller_uri - sip URI of caller. It should be always be passed into the method to avoid - * incorrect working of LLVoiceChannel instances. See EXT-2985. - */ - LLUUID addP2PSession(const std::string& name, - const LLUUID& other_participant_id, - const std::string& voice_session_handle, - const std::string& caller_uri); - - /** - * Leave the session with session id. Send leave session notification - * to the server and removes all associated session data - * @return false if the session with specified id was not exist - */ - bool leaveSession(const LLUUID& session_id); - - void inviteToSession( - const LLUUID& session_id, - const std::string& session_name, - const LLUUID& caller, - const std::string& caller_name, - EInstantMessage type, - EInvitationType inv_type, - const std::string& session_handle = LLStringUtil::null, - const std::string& session_uri = LLStringUtil::null); - - void processIMTypingStart(const LLUUID& from_id, const EInstantMessage im_type); - void processIMTypingStop(const LLUUID& from_id, const EInstantMessage im_type); - - // automatically start a call once the session has initialized - void autoStartCallOnStartup(const LLUUID& session_id); - - // Calc number of all unread IMs - S32 getNumberOfUnreadIM(); - - /** - * Calculates number of unread IMs from real participants in all stored sessions - */ - S32 getNumberOfUnreadParticipantMessages(); - - // This method is used to go through all active sessions and - // disable all of them. This method is usally called when you are - // forced to log out or similar situations where you do not have a - // good connection. - void disconnectAllSessions(); - - bool hasSession(const LLUUID& session_id); - - static LLUUID computeSessionID(EInstantMessage dialog, const LLUUID& other_participant_id); - - void clearPendingInvitation(const LLUUID& session_id); - - void processAgentListUpdates(const LLUUID& session_id, const LLSD& body); - LLSD getPendingAgentListUpdates(const LLUUID& session_id); - void addPendingAgentListUpdates( - const LLUUID& sessioN_id, - const LLSD& updates); - void clearPendingAgentListUpdates(const LLUUID& session_id); - - void addSessionObserver(LLIMSessionObserver *); - void removeSessionObserver(LLIMSessionObserver *); - - //show error statuses to the user - void showSessionStartError(const std::string& error_string, const LLUUID session_id); - void showSessionEventError(const std::string& event_string, const std::string& error_string, const LLUUID session_id); - void showSessionForceClose(const std::string& reason, const LLUUID session_id); - static bool onConfirmForceCloseError(const LLSD& notification, const LLSD& response); - - /** - * Start call in a session - * @return false if voice channel doesn't exist - **/ - bool startCall(const LLUUID& session_id, LLVoiceChannel::EDirection direction = LLVoiceChannel::OUTGOING_CALL); - - /** - * End call in a session - * @return false if voice channel doesn't exist - **/ - bool endCall(const LLUUID& session_id); - - bool isVoiceCall(const LLUUID& session_id); - - void updateDNDMessageStatus(); - - bool isDNDMessageSend(const LLUUID& session_id); - - void setDNDMessageSent(const LLUUID& session_id, bool is_send); - - void addNotifiedNonFriendSessionID(const LLUUID& session_id); - - bool isNonFriendSessionNotified(const LLUUID& session_id); - -private: - - /** - * Remove data associated with a particular session specified by session_id - */ - void removeSession(const LLUUID& session_id); - - // This simple method just iterates through all of the ids, and - // prints a simple message if they are not online. Used to help - // reduce 'hello' messages to the linden employees unlucky enough - // to have their calling card in the default inventory. - void noteOfflineUsers(const LLUUID& session_id, const std::vector& ids); - void noteMutedUsers(const LLUUID& session_id, const std::vector& ids); - - void processIMTypingCore(const LLUUID& from_id, const EInstantMessage im_type, bool typing); - - static void onInviteNameLookup(LLSD payload, const LLUUID& id, const LLAvatarName& name); - - void notifyObserverSessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id, bool has_offline_msg); - //Triggers when a session has already been added - void notifyObserverSessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id); - void notifyObserverSessionVoiceOrIMStarted(const LLUUID& session_id); - void notifyObserverSessionRemoved(const LLUUID& session_id); - void notifyObserverSessionIDUpdated(const LLUUID& old_session_id, const LLUUID& new_session_id); - -private: - - typedef std::list session_observers_list_t; - session_observers_list_t mSessionObservers; - - // EXP-901 - // If "Only friends and groups can IM me" option is ON but the user got message from non-friend, - // the user should be notified that to be able to see this message the option should be OFF. - // This set stores session IDs in which user was notified. Need to store this IDs so that the user - // be notified only one time per session with non-friend. - typedef std::set notified_non_friend_sessions_t; - notified_non_friend_sessions_t mNotifiedNonFriendSessions; - - LLSD mPendingInvitations; - LLSD mPendingAgentListUpdates; -}; - -class LLCallDialogManager : public LLSingleton -{ - LLSINGLETON(LLCallDialogManager); - ~LLCallDialogManager(); -public: - // static for convinience - static void onVoiceChannelChanged(const LLUUID &session_id); - static void onVoiceChannelStateChanged(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state, const LLVoiceChannel::EDirection& direction, bool ended_by_agent); - -private: - void initSingleton() override; - void onVoiceChannelChangedInt(const LLUUID &session_id); - void onVoiceChannelStateChangedInt(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state, const LLVoiceChannel::EDirection& direction, bool ended_by_agent); - -protected: - std::string mPreviousSessionlName; - std::string mCurrentSessionlName; - LLIMModel::LLIMSession* mSession; - LLVoiceChannel::EState mOldState; -}; - -class LLCallDialog : public LLDockableFloater -{ -public: - LLCallDialog(const LLSD& payload); - virtual ~LLCallDialog(); - - virtual bool postBuild(); - - void dockToToolbarButton(const std::string& toolbarButtonName); - - // check timer state - /*virtual*/ void draw(); - /*virtual*/ void onOpen(const LLSD& key); - -protected: - // lifetime timer for a notification - LLTimer mLifetimeTimer; - // notification's lifetime in seconds - S32 mLifetime; - static const S32 DEFAULT_LIFETIME = 5; - virtual bool lifetimeHasExpired(); - virtual void onLifetimeExpired(); - - /** - * Sets icon depend on session. - * - * If passed session_id is a group id group icon will be shown, otherwise avatar icon for participant_id - * - * @param session_id - UUID of session - * @param participant_id - UUID of other participant - */ - void setIcon(const LLSD& session_id, const LLSD& participant_id); - - LLSD mPayload; - -private: - LLDockControl::DocAt getDockControlPos(const std::string& toolbarButtonName); -}; - -class LLIncomingCallDialog : public LLCallDialog -{ -public: - LLIncomingCallDialog(const LLSD& payload); - ~LLIncomingCallDialog() - { - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - } - - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); - - static void onAccept(void* user_data); - static void onReject(void* user_data); - static void onStartIM(void* user_data); - - static void processCallResponse(S32 response, const LLSD& payload); -private: - void setCallerName(const std::string& ui_title, - const std::string& ui_label, - const std::string& call_type); - void onAvatarNameCache(const LLUUID& agent_id, - const LLAvatarName& av_name, - const std::string& call_type); - - boost::signals2::connection mAvatarNameCacheConnection; - - /*virtual*/ void onLifetimeExpired(); -}; - -class LLOutgoingCallDialog : public LLCallDialog -{ -public: - LLOutgoingCallDialog(const LLSD& payload); - - /*virtual*/ bool postBuild(); - void show(const LLSD& key); - - static void onCancel(void* user_data); - static const LLUUID OCD_KEY; - -private: - // hide all text boxes - void hideAllText(); -}; - -// Globals -extern LLIMMgr *gIMMgr; - -#endif // LL_LLIMView_H +/** + * @file LLIMMgr.h + * @brief Container for Instant Messaging + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLIMVIEW_H +#define LL_LLIMVIEW_H + +#include "../llui/lldockablefloater.h" +#include "lleventtimer.h" +#include "llinstantmessage.h" + +#include "lllogchat.h" +#include "llvoicechannel.h" +#include "llinitdestroyclass.h" + +#include "llcoros.h" +#include "lleventcoro.h" + +class LLAvatarName; +class LLFriendObserver; +class LLCallDialogManager; +class LLIMSpeakerMgr; + +/** + * Timeout Timer for outgoing Ad-Hoc/Group IM sessions which being initialized by the server + */ +class LLSessionTimeoutTimer : public LLEventTimer +{ +public: + LLSessionTimeoutTimer(const LLUUID& session_id, F32 period) : LLEventTimer(period), mSessionId(session_id) {} + virtual ~LLSessionTimeoutTimer() {}; + /* virtual */ bool tick(); + +private: + LLUUID mSessionId; +}; + + +/** + * Model (MVC) for IM Sessions + */ +class LLIMModel : public LLSingleton +{ + LLSINGLETON(LLIMModel); + +public: + + typedef std::list chat_message_list_t; + + struct LLIMSession : public boost::signals2::trackable + { + typedef enum e_session_type + { // for now we have 4 predefined types for a session + P2P_SESSION, + GROUP_SESSION, + ADHOC_SESSION, + NONE_SESSION, + } SType; + + LLIMSession(const LLUUID& session_id, const std::string& name, + const EInstantMessage& type, const LLUUID& other_participant_id, const uuid_vec_t& ids, bool voice, bool has_offline_msg); + virtual ~LLIMSession(); + + void sessionInitReplyReceived(const LLUUID& new_session_id); + void addMessagesFromHistoryCache(const std::list& history); // From local file + void addMessagesFromServerHistory(const LLSD& history, const std::string& target_from, const std::string& target_message, U32 timestamp); // From chat server + void addMessage(const std::string& from, + const LLUUID& from_id, + const std::string& utf8_text, + const std::string& time, + const bool is_history, + const bool is_region_msg, + U32 timestamp); + + void onVoiceChannelStateChanged(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state, const LLVoiceChannel::EDirection& direction); + + /** @deprecated */ + static void chatFromLogFile(LLLogChat::ELogLineType type, const LLSD& msg, void* userdata); + + bool isOutgoingAdHoc() const; + bool isAdHoc(); + bool isP2P(); + bool isGroupChat(); + + bool isP2PSessionType() const { return mSessionType == P2P_SESSION;} + bool isAdHocSessionType() const { return mSessionType == ADHOC_SESSION;} + bool isGroupSessionType() const { return mSessionType == GROUP_SESSION;} + + LLUUID generateOutgoingAdHocHash() const; + + //*TODO make private + /** ad-hoc sessions involve sophisticated chat history file naming schemes */ + void buildHistoryFileName(); + + void loadHistory(); + + LLUUID mSessionID; + std::string mName; + EInstantMessage mType; + SType mSessionType; + LLUUID mOtherParticipantID; + uuid_vec_t mInitialTargetIDs; + std::string mHistoryFileName; + + // Saved messages from the last minute of history read from the local group chat cache file + std::string mLastHistoryCacheDateTime; + chat_message_list_t mLastHistoryCacheMsgs; + + // connection to voice channel state change signal + boost::signals2::connection mVoiceChannelStateChangeConnection; + + //does NOT include system messages and agent's messages + S32 mParticipantUnreadMessageCount; + + // does include all incoming messages + S32 mNumUnread; + + chat_message_list_t mMsgs; + + LLVoiceChannel* mVoiceChannel; + LLIMSpeakerMgr* mSpeakers; + + bool mSessionInitialized; + + //true if calling back the session URI after the session has closed is possible. + //Currently this will be false only for PSTN P2P calls. + bool mCallBackEnabled; + + bool mTextIMPossible; + bool mStartCallOnInitialize; + + //if IM session is created for a voice call + bool mStartedAsIMCall; + + bool mHasOfflineMessage; + + bool mIsDNDsend; + + private: + void onAdHocNameCache(const LLAvatarName& av_name); + + static LLUUID generateHash(const std::set& sorted_uuids); + boost::signals2::connection mAvatarNameCacheConnection; + }; + + + + /** Session id to session object */ + std::map mId2SessionMap; + + typedef boost::signals2::signal session_signal_t; + session_signal_t mNewMsgSignal; + session_signal_t mNoUnreadMsgsSignal; + + /** + * Find an IM Session corresponding to session_id + * Returns NULL if the session does not exist + */ + LLIMSession* findIMSession(const LLUUID& session_id) const; + + /** + * Find an Ad-Hoc IM Session with specified participants + * @return first found Ad-Hoc session or NULL if the session does not exist + */ + LLIMSession* findAdHocIMSession(const uuid_vec_t& ids); + + /** + * Rebind session data to a new session id. + */ + void processSessionInitializedReply(const LLUUID& old_session_id, const LLUUID& new_session_id); + + boost::signals2::connection addNewMsgCallback(const session_signal_t::slot_type& cb ) { return mNewMsgSignal.connect(cb); } + boost::signals2::connection addNoUnreadMsgsCallback(const session_signal_t::slot_type& cb ) { return mNoUnreadMsgsSignal.connect(cb); } + + /** + * Create new session object in a model + * @param name session name should not be empty, will return false if empty + */ + bool newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, const LLUUID& other_participant_id, + const uuid_vec_t& ids, bool voice = false, bool has_offline_msg = false); + + bool newSession(const LLUUID& session_id, const std::string& name, const EInstantMessage& type, + const LLUUID& other_participant_id, bool voice = false, bool has_offline_msg = false); + + /** + * Remove all session data associated with a session specified by session_id + */ + bool clearSession(const LLUUID& session_id); + + /** + * Sends no unread messages signal. + */ + void sendNoUnreadMessages(const LLUUID& session_id); + + /** + * Populate supplied std::list with messages starting from index specified by start_index + */ + void getMessages(const LLUUID& session_id, std::list& messages, int start_index = 0, const bool sendNoUnreadMsgs = true); + + /** + * Add a message to an IM Model - the message is saved in a message store associated with a session specified by session_id + * and also saved into a file if log2file is specified. + * It sends new message signal for each added message. + */ + void addMessage(const LLUUID& session_id, + const std::string& from, + const LLUUID& other_participant_id, + const std::string& utf8_text, + bool log2file = true, + bool is_region_msg = false, + U32 time_stamp = 0); + + void processAddingMessage(const LLUUID& session_id, + const std::string& from, + const LLUUID& from_id, + const std::string& utf8_text, + bool log2file, + bool is_region_msg, + U32 time_stamp); + + /** + * Similar to addMessage(...) above but won't send a signal about a new message added + */ + LLIMModel::LLIMSession* addMessageSilently(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, + const std::string& utf8_text, bool log2file = true, bool is_region_msg = false, U32 timestamp = 0); + + /** + * Add a system message to an IM Model + */ + void proccessOnlineOfflineNotification(const LLUUID& session_id, const std::string& utf8_text); + + /** + * Get a session's name. + * For a P2P chat - it's an avatar's name, + * For a group chat - it's a group's name + * For an incoming ad-hoc chat - is received from the server and is in a from of " Conference" + * It is updated in LLIMModel::LLIMSession's constructor to localize the "Conference". + */ + const std::string getName(const LLUUID& session_id) const; + + /** + * Get number of unread messages in a session with session_id + * Returns -1 if the session with session_id doesn't exist + */ + const S32 getNumUnread(const LLUUID& session_id) const; + + /** + * Get uuid of other participant in a session with session_id + * Returns LLUUID::null if the session doesn't exist + * + * *TODO what to do with other participants in ad-hoc and group chats? + */ + const LLUUID& getOtherParticipantID(const LLUUID& session_id) const; + + /** + * Get type of a session specified by session_id + * Returns EInstantMessage::IM_COUNT if the session does not exist + */ + EInstantMessage getType(const LLUUID& session_id) const; + + /** + * Get voice channel for the session specified by session_id + * Returns NULL if the session does not exist + */ + LLVoiceChannel* getVoiceChannel(const LLUUID& session_id) const; + + /** + * Get im speaker manager for the session specified by session_id + * Returns NULL if the session does not exist + */ + LLIMSpeakerMgr* getSpeakerManager(const LLUUID& session_id) const; + + const std::string& getHistoryFileName(const LLUUID& session_id) const; + + static void sendLeaveSession(const LLUUID& session_id, const LLUUID& other_participant_id); + static bool sendStartSession(const LLUUID& temp_session_id, const LLUUID& other_participant_id, + const uuid_vec_t& ids, EInstantMessage dialog); + static void sendTypingState(LLUUID session_id, LLUUID other_participant_id, bool typing); + static void sendMessage(const std::string& utf8_text, const LLUUID& im_session_id, + const LLUUID& other_participant_id, EInstantMessage dialog); + + // Adds people from speakers list (people with whom you are currently speaking) to the Recent People List + static void addSpeakersToRecent(const LLUUID& im_session_id); + + void testMessages(); + + /** + * Saves an IM message into a file + */ + bool logToFile(const std::string& file_name, const std::string& from, const LLUUID& from_id, const std::string& utf8_text); + +private: + + /** + * Populate supplied std::list with messages starting from index specified by start_index without + * emitting no unread messages signal. + */ + void getMessagesSilently(const LLUUID& session_id, std::list& messages, int start_index = 0); + + /** + * Add message to a list of message associated with session specified by session_id + */ + bool addToHistory(const LLUUID& session_id, const std::string& from, const LLUUID& from_id, const std::string& utf8_text, bool is_region_msg, U32 timestamp); + +}; + +class LLIMSessionObserver +{ +public: + virtual ~LLIMSessionObserver() {} + virtual void sessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id, bool has_offline_msg) = 0; + virtual void sessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id) = 0; + virtual void sessionVoiceOrIMStarted(const LLUUID& session_id) = 0; + virtual void sessionRemoved(const LLUUID& session_id) = 0; + virtual void sessionIDUpdated(const LLUUID& old_session_id, const LLUUID& new_session_id) = 0; +}; + + +class LLIMMgr : public LLSingleton +{ + LLSINGLETON(LLIMMgr); + friend class LLIMModel; + +public: + enum EInvitationType + { + INVITATION_TYPE_INSTANT_MESSAGE = 0, + INVITATION_TYPE_VOICE = 1, + INVITATION_TYPE_IMMEDIATE = 2 + }; + + + // Add a message to a session. The session can keyed to sesion id + // or agent id. + void addMessage(const LLUUID& session_id, + const LLUUID& target_id, + const std::string& from, + const std::string& msg, + bool is_offline_msg = false, + const std::string& session_name = LLStringUtil::null, + EInstantMessage dialog = IM_NOTHING_SPECIAL, + U32 parent_estate_id = 0, + const LLUUID& region_id = LLUUID::null, + const LLVector3& position = LLVector3::zero, + bool is_region_msg = false, + U32 timestamp = 0); + + void addSystemMessage(const LLUUID& session_id, const std::string& message_name, const LLSD& args); + + // This adds a session to the talk view. The name is the local + // name of the session, dialog specifies the type of + // session. Since sessions can be keyed off of first recipient or + // initiator, the session can be matched against the id + // provided. If the session exists, it is brought forward. This + // method accepts a group id or an agent id. Specifying id = NULL + // results in an im session to everyone. Returns the uuid of the + // session. + LLUUID addSession(const std::string& name, + EInstantMessage dialog, + const LLUUID& other_participant_id, bool voice = false); + + // Adds a session using a specific group of starting agents + // the dialog type is assumed correct. Returns the uuid of the session. + // A session can be added to a floater specified by floater_id. + LLUUID addSession(const std::string& name, + EInstantMessage dialog, + const LLUUID& other_participant_id, + const std::vector& ids, bool voice = false, + const LLUUID& floater_id = LLUUID::null); + + /** + * Creates a P2P session with the requisite handle for responding to voice calls. + * + * @param name session name, cannot be null + * @param caller_uri - sip URI of caller. It should be always be passed into the method to avoid + * incorrect working of LLVoiceChannel instances. See EXT-2985. + */ + LLUUID addP2PSession(const std::string& name, + const LLUUID& other_participant_id, + const std::string& voice_session_handle, + const std::string& caller_uri); + + /** + * Leave the session with session id. Send leave session notification + * to the server and removes all associated session data + * @return false if the session with specified id was not exist + */ + bool leaveSession(const LLUUID& session_id); + + void inviteToSession( + const LLUUID& session_id, + const std::string& session_name, + const LLUUID& caller, + const std::string& caller_name, + EInstantMessage type, + EInvitationType inv_type, + const std::string& session_handle = LLStringUtil::null, + const std::string& session_uri = LLStringUtil::null); + + void processIMTypingStart(const LLUUID& from_id, const EInstantMessage im_type); + void processIMTypingStop(const LLUUID& from_id, const EInstantMessage im_type); + + // automatically start a call once the session has initialized + void autoStartCallOnStartup(const LLUUID& session_id); + + // Calc number of all unread IMs + S32 getNumberOfUnreadIM(); + + /** + * Calculates number of unread IMs from real participants in all stored sessions + */ + S32 getNumberOfUnreadParticipantMessages(); + + // This method is used to go through all active sessions and + // disable all of them. This method is usally called when you are + // forced to log out or similar situations where you do not have a + // good connection. + void disconnectAllSessions(); + + bool hasSession(const LLUUID& session_id); + + static LLUUID computeSessionID(EInstantMessage dialog, const LLUUID& other_participant_id); + + void clearPendingInvitation(const LLUUID& session_id); + + void processAgentListUpdates(const LLUUID& session_id, const LLSD& body); + LLSD getPendingAgentListUpdates(const LLUUID& session_id); + void addPendingAgentListUpdates( + const LLUUID& sessioN_id, + const LLSD& updates); + void clearPendingAgentListUpdates(const LLUUID& session_id); + + void addSessionObserver(LLIMSessionObserver *); + void removeSessionObserver(LLIMSessionObserver *); + + //show error statuses to the user + void showSessionStartError(const std::string& error_string, const LLUUID session_id); + void showSessionEventError(const std::string& event_string, const std::string& error_string, const LLUUID session_id); + void showSessionForceClose(const std::string& reason, const LLUUID session_id); + static bool onConfirmForceCloseError(const LLSD& notification, const LLSD& response); + + /** + * Start call in a session + * @return false if voice channel doesn't exist + **/ + bool startCall(const LLUUID& session_id, LLVoiceChannel::EDirection direction = LLVoiceChannel::OUTGOING_CALL); + + /** + * End call in a session + * @return false if voice channel doesn't exist + **/ + bool endCall(const LLUUID& session_id); + + bool isVoiceCall(const LLUUID& session_id); + + void updateDNDMessageStatus(); + + bool isDNDMessageSend(const LLUUID& session_id); + + void setDNDMessageSent(const LLUUID& session_id, bool is_send); + + void addNotifiedNonFriendSessionID(const LLUUID& session_id); + + bool isNonFriendSessionNotified(const LLUUID& session_id); + +private: + + /** + * Remove data associated with a particular session specified by session_id + */ + void removeSession(const LLUUID& session_id); + + // This simple method just iterates through all of the ids, and + // prints a simple message if they are not online. Used to help + // reduce 'hello' messages to the linden employees unlucky enough + // to have their calling card in the default inventory. + void noteOfflineUsers(const LLUUID& session_id, const std::vector& ids); + void noteMutedUsers(const LLUUID& session_id, const std::vector& ids); + + void processIMTypingCore(const LLUUID& from_id, const EInstantMessage im_type, bool typing); + + static void onInviteNameLookup(LLSD payload, const LLUUID& id, const LLAvatarName& name); + + void notifyObserverSessionAdded(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id, bool has_offline_msg); + //Triggers when a session has already been added + void notifyObserverSessionActivated(const LLUUID& session_id, const std::string& name, const LLUUID& other_participant_id); + void notifyObserverSessionVoiceOrIMStarted(const LLUUID& session_id); + void notifyObserverSessionRemoved(const LLUUID& session_id); + void notifyObserverSessionIDUpdated(const LLUUID& old_session_id, const LLUUID& new_session_id); + +private: + + typedef std::list session_observers_list_t; + session_observers_list_t mSessionObservers; + + // EXP-901 + // If "Only friends and groups can IM me" option is ON but the user got message from non-friend, + // the user should be notified that to be able to see this message the option should be OFF. + // This set stores session IDs in which user was notified. Need to store this IDs so that the user + // be notified only one time per session with non-friend. + typedef std::set notified_non_friend_sessions_t; + notified_non_friend_sessions_t mNotifiedNonFriendSessions; + + LLSD mPendingInvitations; + LLSD mPendingAgentListUpdates; +}; + +class LLCallDialogManager : public LLSingleton +{ + LLSINGLETON(LLCallDialogManager); + ~LLCallDialogManager(); +public: + // static for convinience + static void onVoiceChannelChanged(const LLUUID &session_id); + static void onVoiceChannelStateChanged(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state, const LLVoiceChannel::EDirection& direction, bool ended_by_agent); + +private: + void initSingleton() override; + void onVoiceChannelChangedInt(const LLUUID &session_id); + void onVoiceChannelStateChangedInt(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state, const LLVoiceChannel::EDirection& direction, bool ended_by_agent); + +protected: + std::string mPreviousSessionlName; + std::string mCurrentSessionlName; + LLIMModel::LLIMSession* mSession; + LLVoiceChannel::EState mOldState; +}; + +class LLCallDialog : public LLDockableFloater +{ +public: + LLCallDialog(const LLSD& payload); + virtual ~LLCallDialog(); + + virtual bool postBuild(); + + void dockToToolbarButton(const std::string& toolbarButtonName); + + // check timer state + /*virtual*/ void draw(); + /*virtual*/ void onOpen(const LLSD& key); + +protected: + // lifetime timer for a notification + LLTimer mLifetimeTimer; + // notification's lifetime in seconds + S32 mLifetime; + static const S32 DEFAULT_LIFETIME = 5; + virtual bool lifetimeHasExpired(); + virtual void onLifetimeExpired(); + + /** + * Sets icon depend on session. + * + * If passed session_id is a group id group icon will be shown, otherwise avatar icon for participant_id + * + * @param session_id - UUID of session + * @param participant_id - UUID of other participant + */ + void setIcon(const LLSD& session_id, const LLSD& participant_id); + + LLSD mPayload; + +private: + LLDockControl::DocAt getDockControlPos(const std::string& toolbarButtonName); +}; + +class LLIncomingCallDialog : public LLCallDialog +{ +public: + LLIncomingCallDialog(const LLSD& payload); + ~LLIncomingCallDialog() + { + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + } + + /*virtual*/ bool postBuild(); + /*virtual*/ void onOpen(const LLSD& key); + + static void onAccept(void* user_data); + static void onReject(void* user_data); + static void onStartIM(void* user_data); + + static void processCallResponse(S32 response, const LLSD& payload); +private: + void setCallerName(const std::string& ui_title, + const std::string& ui_label, + const std::string& call_type); + void onAvatarNameCache(const LLUUID& agent_id, + const LLAvatarName& av_name, + const std::string& call_type); + + boost::signals2::connection mAvatarNameCacheConnection; + + /*virtual*/ void onLifetimeExpired(); +}; + +class LLOutgoingCallDialog : public LLCallDialog +{ +public: + LLOutgoingCallDialog(const LLSD& payload); + + /*virtual*/ bool postBuild(); + void show(const LLSD& key); + + static void onCancel(void* user_data); + static const LLUUID OCD_KEY; + +private: + // hide all text boxes + void hideAllText(); +}; + +// Globals +extern LLIMMgr *gIMMgr; + +#endif // LL_LLIMView_H diff --git a/indra/newview/llinspect.cpp b/indra/newview/llinspect.cpp index 182b8e9c89..bfef4860c4 100644 --- a/indra/newview/llinspect.cpp +++ b/indra/newview/llinspect.cpp @@ -1,165 +1,165 @@ -/** - * @file llinspect.cpp - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ -#include "llviewerprecompiledheaders.h" - -#include "llinspect.h" - -#include "lltooltip.h" -#include "llcontrol.h" // LLCachedControl -#include "llui.h" // LLUI::getInstance()->mSettingsGroups -#include "llviewermenu.h" - -LLInspect::LLInspect(const LLSD& key) -: LLFloater(key), - mCloseTimer(), - mOpenTimer() -{ -} - -LLInspect::~LLInspect() -{ -} - -// virtual -void LLInspect::draw() -{ - const F32 FADE_TIME = 0.5f; - const F32 STAY_TIME = 3.f; - if (mOpenTimer.getStarted()) - { - LLFloater::draw(); - if (mOpenTimer.getElapsedTimeF32() > STAY_TIME) - { - mOpenTimer.stop(); - mCloseTimer.start(); - } - - } - else if (mCloseTimer.getStarted()) - { - F32 alpha = clamp_rescale(mCloseTimer.getElapsedTimeF32(), 0.f, FADE_TIME, 1.f, 0.f); - LLViewDrawContext context(alpha); - LLFloater::draw(); - if (mCloseTimer.getElapsedTimeF32() > FADE_TIME) - { - closeFloater(false); - } - } - else - { - LLFloater::draw(); - } -} - -// virtual -void LLInspect::onOpen(const LLSD& data) -{ - LLFloater::onOpen(data); - - mCloseTimer.stop(); - mOpenTimer.start(); -} - -// virtual -void LLInspect::onFocusLost() -{ - LLFloater::onFocusLost(); - - // Start closing when we lose focus - mCloseTimer.start(); - mOpenTimer.stop(); -} - -// virtual -bool LLInspect::handleHover(S32 x, S32 y, MASK mask) -{ - mOpenTimer.pause(); - return LLView::handleHover(x, y, mask); -} - -bool LLInspect::handleToolTip(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - - //delegate handling of tooltip to the hovered child - LLView* child_handler = childFromPoint(x,y); - if (child_handler && !child_handler->getToolTip().empty())// show tooltip if a view has non-empty tooltip message - { - //build LLInspector params to get correct tooltip setting, etc. background image - LLInspector::Params params; - params.fillFrom(LLUICtrlFactory::instance().getDefaultParams()); - params.message = child_handler->getToolTip(); - //set up delay if there is no visible tooltip at this moment - params.delay_time = LLToolTipMgr::instance().toolTipVisible() ? 0.f : LLUI::getInstance()->mSettingGroups["config"]->getF32( "ToolTipDelay" ); - LLToolTipMgr::instance().show(params); - handled = true; - } - return handled; -} -// virtual -void LLInspect::onMouseLeave(S32 x, S32 y, MASK mask) -{ - mOpenTimer.unpause(); -} - -bool LLInspect::childHasVisiblePopupMenu() -{ - // Child text-box may spawn a pop-up menu, if mouse is over the menu, Inspector - // will hide(which is not expected). - // This is an attempt to find out if child control has spawned a menu. - - LLView* child_menu = gMenuHolder->getVisibleMenu(); - if(child_menu) - { - LLRect floater_rc = calcScreenRect(); - LLRect menu_screen_rc = child_menu->calcScreenRect(); - S32 mx, my; - LLUI::getInstance()->getMousePositionScreen(&mx, &my); - - // This works wrong if we spawn a menu near Inspector and menu overlaps Inspector. - if(floater_rc.overlaps(menu_screen_rc) && menu_screen_rc.pointInRect(mx, my)) - { - return true; - } - } - return false; -} - -void LLInspect::repositionInspector(const LLSD& data) -{ - // Position the inspector relative to the mouse cursor - // Similar to how tooltips are positioned - // See LLToolTipMgr::createToolTip - if (data.has("pos")) - { - LLUI::getInstance()->positionViewNearMouse(this, data["pos"]["x"].asInteger(), data["pos"]["y"].asInteger()); - } - else - { - LLUI::getInstance()->positionViewNearMouse(this); - } - applyRectControl(); -} +/** + * @file llinspect.cpp + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#include "llviewerprecompiledheaders.h" + +#include "llinspect.h" + +#include "lltooltip.h" +#include "llcontrol.h" // LLCachedControl +#include "llui.h" // LLUI::getInstance()->mSettingsGroups +#include "llviewermenu.h" + +LLInspect::LLInspect(const LLSD& key) +: LLFloater(key), + mCloseTimer(), + mOpenTimer() +{ +} + +LLInspect::~LLInspect() +{ +} + +// virtual +void LLInspect::draw() +{ + const F32 FADE_TIME = 0.5f; + const F32 STAY_TIME = 3.f; + if (mOpenTimer.getStarted()) + { + LLFloater::draw(); + if (mOpenTimer.getElapsedTimeF32() > STAY_TIME) + { + mOpenTimer.stop(); + mCloseTimer.start(); + } + + } + else if (mCloseTimer.getStarted()) + { + F32 alpha = clamp_rescale(mCloseTimer.getElapsedTimeF32(), 0.f, FADE_TIME, 1.f, 0.f); + LLViewDrawContext context(alpha); + LLFloater::draw(); + if (mCloseTimer.getElapsedTimeF32() > FADE_TIME) + { + closeFloater(false); + } + } + else + { + LLFloater::draw(); + } +} + +// virtual +void LLInspect::onOpen(const LLSD& data) +{ + LLFloater::onOpen(data); + + mCloseTimer.stop(); + mOpenTimer.start(); +} + +// virtual +void LLInspect::onFocusLost() +{ + LLFloater::onFocusLost(); + + // Start closing when we lose focus + mCloseTimer.start(); + mOpenTimer.stop(); +} + +// virtual +bool LLInspect::handleHover(S32 x, S32 y, MASK mask) +{ + mOpenTimer.pause(); + return LLView::handleHover(x, y, mask); +} + +bool LLInspect::handleToolTip(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + + //delegate handling of tooltip to the hovered child + LLView* child_handler = childFromPoint(x,y); + if (child_handler && !child_handler->getToolTip().empty())// show tooltip if a view has non-empty tooltip message + { + //build LLInspector params to get correct tooltip setting, etc. background image + LLInspector::Params params; + params.fillFrom(LLUICtrlFactory::instance().getDefaultParams()); + params.message = child_handler->getToolTip(); + //set up delay if there is no visible tooltip at this moment + params.delay_time = LLToolTipMgr::instance().toolTipVisible() ? 0.f : LLUI::getInstance()->mSettingGroups["config"]->getF32( "ToolTipDelay" ); + LLToolTipMgr::instance().show(params); + handled = true; + } + return handled; +} +// virtual +void LLInspect::onMouseLeave(S32 x, S32 y, MASK mask) +{ + mOpenTimer.unpause(); +} + +bool LLInspect::childHasVisiblePopupMenu() +{ + // Child text-box may spawn a pop-up menu, if mouse is over the menu, Inspector + // will hide(which is not expected). + // This is an attempt to find out if child control has spawned a menu. + + LLView* child_menu = gMenuHolder->getVisibleMenu(); + if(child_menu) + { + LLRect floater_rc = calcScreenRect(); + LLRect menu_screen_rc = child_menu->calcScreenRect(); + S32 mx, my; + LLUI::getInstance()->getMousePositionScreen(&mx, &my); + + // This works wrong if we spawn a menu near Inspector and menu overlaps Inspector. + if(floater_rc.overlaps(menu_screen_rc) && menu_screen_rc.pointInRect(mx, my)) + { + return true; + } + } + return false; +} + +void LLInspect::repositionInspector(const LLSD& data) +{ + // Position the inspector relative to the mouse cursor + // Similar to how tooltips are positioned + // See LLToolTipMgr::createToolTip + if (data.has("pos")) + { + LLUI::getInstance()->positionViewNearMouse(this, data["pos"]["x"].asInteger(), data["pos"]["y"].asInteger()); + } + else + { + LLUI::getInstance()->positionViewNearMouse(this); + } + applyRectControl(); +} diff --git a/indra/newview/llinspect.h b/indra/newview/llinspect.h index fbf60ab77e..8113bf460b 100644 --- a/indra/newview/llinspect.h +++ b/indra/newview/llinspect.h @@ -1,64 +1,64 @@ -/** - * @file llinspect.h - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLINSPECT_H -#define LLINSPECT_H - -#include "llfloater.h" -#include "llframetimer.h" - -/// Base class for all inspectors (super-tooltips showing a miniature -/// properties view). -class LLInspect : public LLFloater -{ -public: - LLInspect(const LLSD& key); - virtual ~LLInspect(); - - /// Inspectors have a custom fade-in/fade-out animation - /*virtual*/ void draw(); - - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); - /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask); - - /// Start open animation - /*virtual*/ void onOpen(const LLSD& avatar_id); - - /// Inspectors close themselves when they lose focus - /*virtual*/ void onFocusLost(); - - void repositionInspector(const LLSD& data); - -protected: - - virtual bool childHasVisiblePopupMenu(); - - LLFrameTimer mCloseTimer; - LLFrameTimer mOpenTimer; -}; - -#endif - +/** + * @file llinspect.h + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLINSPECT_H +#define LLINSPECT_H + +#include "llfloater.h" +#include "llframetimer.h" + +/// Base class for all inspectors (super-tooltips showing a miniature +/// properties view). +class LLInspect : public LLFloater +{ +public: + LLInspect(const LLSD& key); + virtual ~LLInspect(); + + /// Inspectors have a custom fade-in/fade-out animation + /*virtual*/ void draw(); + + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); + /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask); + + /// Start open animation + /*virtual*/ void onOpen(const LLSD& avatar_id); + + /// Inspectors close themselves when they lose focus + /*virtual*/ void onFocusLost(); + + void repositionInspector(const LLSD& data); + +protected: + + virtual bool childHasVisiblePopupMenu(); + + LLFrameTimer mCloseTimer; + LLFrameTimer mOpenTimer; +}; + +#endif + diff --git a/indra/newview/llinspectavatar.cpp b/indra/newview/llinspectavatar.cpp index dbfa92ac81..b03b7beed6 100644 --- a/indra/newview/llinspectavatar.cpp +++ b/indra/newview/llinspectavatar.cpp @@ -1,398 +1,398 @@ -/** - * @file llinspectavatar.cpp - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llinspectavatar.h" - -// viewer files -#include "llagent.h" -#include "llavataractions.h" -#include "llavatariconctrl.h" -#include "llavatarnamecache.h" -#include "llavatarpropertiesprocessor.h" -#include "lldateutil.h" -#include "llinspect.h" -#include "llmutelist.h" -#include "llslurl.h" -#include "llstartup.h" -#include "llvoiceclient.h" -#include "lltransientfloatermgr.h" - -// Linden libraries -#include "llfloater.h" -#include "llfloaterreg.h" -#include "lltextbox.h" -#include "lltrans.h" - -class LLFetchAvatarData; - - -////////////////////////////////////////////////////////////////////////////// -// LLInspectAvatar -////////////////////////////////////////////////////////////////////////////// - -// Avatar Inspector, a small information window used when clicking -// on avatar names in the 2D UI and in the ambient inspector widget for -// the 3D world. -class LLInspectAvatar : public LLInspect, LLTransientFloater -{ - friend class LLFloaterReg; - -public: - // avatar_id - Avatar ID for which to show information - // Inspector will be positioned relative to current mouse position - LLInspectAvatar(const LLSD& avatar_id); - virtual ~LLInspectAvatar(); - - /*virtual*/ bool postBuild(void); - - // Because floater is single instance, need to re-parse data on each spawn - // (for example, inspector about same avatar but in different position) - /*virtual*/ void onOpen(const LLSD& avatar_id); - - // Update view based on information from avatar properties processor - void processAvatarData(LLAvatarData* data); - - virtual LLTransientFloaterMgr::ETransientGroup getGroup() { return LLTransientFloaterMgr::GLOBAL; } - -private: - // Make network requests for all the data to display in this view. - // Used on construction and if avatar id changes. - void requestUpdate(); - - // Set the volume slider to this user's current client-side volume setting, - // hiding/disabling if the user is not nearby. - void updateVolumeSlider(); - - // Button callbacks - void onClickMuteVolume(); - void onVolumeChange(const LLSD& data); - - void onAvatarNameCache(const LLUUID& agent_id, - const LLAvatarName& av_name); - -private: - LLUUID mAvatarID; - // Need avatar name information to spawn friend add request - LLAvatarName mAvatarName; - // an in-flight request for avatar properties from LLAvatarPropertiesProcessor - // is represented by this object - LLFetchAvatarData* mPropertiesRequest; - boost::signals2::connection mAvatarNameCacheConnection; -}; - -////////////////////////////////////////////////////////////////////////////// -// LLFetchAvatarData -////////////////////////////////////////////////////////////////////////////// - -// This object represents a pending request for avatar properties information -class LLFetchAvatarData : public LLAvatarPropertiesObserver -{ -public: - // If the inspector closes it will delete the pending request object, so the - // inspector pointer will be valid for the lifetime of this object - LLFetchAvatarData(const LLUUID& avatar_id, LLInspectAvatar* inspector) - : mAvatarID(avatar_id), - mInspector(inspector) - { - LLAvatarPropertiesProcessor* processor = - LLAvatarPropertiesProcessor::getInstance(); - // register ourselves as an observer - processor->addObserver(mAvatarID, this); - // send a request (duplicates will be suppressed inside the avatar - // properties processor) - processor->sendAvatarPropertiesRequest(mAvatarID); - } - - ~LLFetchAvatarData() - { - // remove ourselves as an observer - LLAvatarPropertiesProcessor::getInstance()-> - removeObserver(mAvatarID, this); - } - - void processProperties(void* data, EAvatarProcessorType type) - { - // route the data to the inspector - if (data - && type == APT_PROPERTIES) - { - LLAvatarData* avatar_data = static_cast(data); - mInspector->processAvatarData(avatar_data); - } - } - - // Store avatar ID so we can un-register the observer on destruction - LLUUID mAvatarID; - LLInspectAvatar* mInspector; -}; - -LLInspectAvatar::LLInspectAvatar(const LLSD& sd) -: LLInspect( LLSD() ), // single_instance, doesn't really need key - mAvatarID(), // set in onOpen() *Note: we used to show partner's name but we dont anymore --angela 3rd Dec* - mAvatarName(), - mPropertiesRequest(NULL), - mAvatarNameCacheConnection() -{ - // can't make the properties request until the widgets are constructed - // as it might return immediately, so do it in onOpen. - - LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::GLOBAL, this); - LLTransientFloater::init(this); -} - -LLInspectAvatar::~LLInspectAvatar() -{ - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - // clean up any pending requests so they don't call back into a deleted - // view - delete mPropertiesRequest; - mPropertiesRequest = NULL; - - LLTransientFloaterMgr::getInstance()->removeControlView(this); -} - -/*virtual*/ -bool LLInspectAvatar::postBuild(void) -{ - getChild("mute_btn")->setCommitCallback( - boost::bind(&LLInspectAvatar::onClickMuteVolume, this) ); - - getChild("volume_slider")->setCommitCallback( - boost::bind(&LLInspectAvatar::onVolumeChange, this, _2)); - - return true; -} - -// Multiple calls to showInstance("inspect_avatar", foo) will provide different -// LLSD for foo, which we will catch here. -//virtual -void LLInspectAvatar::onOpen(const LLSD& data) -{ - // Start open animation - LLInspect::onOpen(data); - - // Extract appropriate avatar id - mAvatarID = data["avatar_id"]; - - LLInspect::repositionInspector(data); - - // Generate link to avatar profile. - LLTextBase* avatar_profile_link = getChild("avatar_profile_link"); - avatar_profile_link->setTextArg("[LINK]", LLSLURL("agent", mAvatarID, "about").getSLURLString()); - avatar_profile_link->setIsFriendCallback(LLAvatarActions::isFriend); - - // can't call from constructor as widgets are not built yet - requestUpdate(); - - updateVolumeSlider(); -} - -void LLInspectAvatar::requestUpdate() -{ - // Don't make network requests when spawning from the debug menu at the - // login screen (which is useful to work on the layout). - if (mAvatarID.isNull()) - { - if (LLStartUp::getStartupState() >= STATE_STARTED) - { - // once we're running we don't want to show the test floater - // for bogus LLUUID::null links - closeFloater(); - } - return; - } - - // Clear out old data so it doesn't flash between old and new - getChild("user_name")->setValue(""); - getChild("user_name_small")->setValue(""); - getChild("user_slid")->setValue(""); - getChild("user_subtitle")->setValue(""); - getChild("user_details")->setValue(""); - - // Make a new request for properties - delete mPropertiesRequest; - mPropertiesRequest = new LLFetchAvatarData(mAvatarID, this); - - // Use an avatar_icon even though the image id will come down with the - // avatar properties because the avatar_icon code maintains a cache of icons - // and this may result in the image being visible sooner. - // *NOTE: This may generate a duplicate avatar properties request, but that - // will be suppressed internally in the avatar properties processor. - - //remove avatar id from cache to get fresh info - LLAvatarIconIDCache::getInstance()->remove(mAvatarID); - - getChild("avatar_icon")->setValue(LLSD(mAvatarID) ); - - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - mAvatarNameCacheConnection = LLAvatarNameCache::get(mAvatarID,boost::bind(&LLInspectAvatar::onAvatarNameCache,this, _1, _2)); -} - -void LLInspectAvatar::processAvatarData(LLAvatarData* data) -{ - LLStringUtil::format_map_t args; - - std::string birth_date = LLTrans::getString(data->hide_age ? - "AvatarBirthDateFormatShort" : - "AvatarBirthDateFormatFull"); - LLStringUtil::format(birth_date, LLSD().with("datetime", (S32) data->born_on.secondsSinceEpoch())); - args["[BORN_ON]"] = birth_date; - args["[AGE]"] = data->hide_age ? - LLStringUtilBase::null : - LLDateUtil::ageFromDate(data->born_on, LLDate::now()); - args["[SL_PROFILE]"] = data->about_text; - args["[RW_PROFILE"] = data->fl_about_text; - args["[ACCTTYPE]"] = LLAvatarPropertiesProcessor::accountType(data); - std::string payment_info = LLAvatarPropertiesProcessor::paymentInfo(data); - args["[PAYMENTINFO]"] = payment_info; - args["[COMMA]"] = (payment_info.empty() ? "" : ","); - - std::string subtitle = getString("Subtitle", args); - getChild("user_subtitle")->setValue( LLSD(subtitle) ); - std::string details = getString("Details", args); - getChild("user_details")->setValue( LLSD(details) ); - - // Delete the request object as it has been satisfied - delete mPropertiesRequest; - mPropertiesRequest = NULL; -} - -void LLInspectAvatar::updateVolumeSlider() -{ - bool voice_enabled = LLVoiceClient::getInstance()->getVoiceEnabled(mAvatarID); - - // Do not display volume slider and mute button if it - // is ourself or we are not in a voice channel together - if (!voice_enabled || (mAvatarID == gAgent.getID())) - { - getChild("mute_btn")->setVisible(false); - getChild("volume_slider")->setVisible(false); - } - - else - { - getChild("mute_btn")->setVisible(true); - getChild("volume_slider")->setVisible(true); - - // By convention, we only display and toggle voice mutes, not all mutes - bool is_muted = LLAvatarActions::isVoiceMuted(mAvatarID); - - LLUICtrl* mute_btn = getChild("mute_btn"); - - bool is_linden = LLStringUtil::endsWith(mAvatarName.getDisplayName(), " Linden"); - - mute_btn->setEnabled( !is_linden); - mute_btn->setValue( is_muted ); - - LLUICtrl* volume_slider = getChild("volume_slider"); - volume_slider->setEnabled( !is_muted ); - - F32 volume; - - if (is_muted) - { - // it's clearer to display their volume as zero - volume = 0.f; - } - else - { - // actual volume - volume = LLVoiceClient::getInstance()->getUserVolume(mAvatarID); - } - volume_slider->setValue( (F64)volume ); - } - -} - -void LLInspectAvatar::onClickMuteVolume() -{ - // By convention, we only display and toggle voice mutes, not all mutes - LLMuteList* mute_list = LLMuteList::getInstance(); - bool is_muted = mute_list->isMuted(mAvatarID, LLMute::flagVoiceChat); - - LLMute mute(mAvatarID, mAvatarName.getUserName(), LLMute::AGENT); - if (!is_muted) - { - mute_list->add(mute, LLMute::flagVoiceChat); - } - else - { - mute_list->remove(mute, LLMute::flagVoiceChat); - } - - updateVolumeSlider(); -} - -void LLInspectAvatar::onVolumeChange(const LLSD& data) -{ - F32 volume = (F32)data.asReal(); - LLVoiceClient::getInstance()->setUserVolume(mAvatarID, volume); -} - -void LLInspectAvatar::onAvatarNameCache( - const LLUUID& agent_id, - const LLAvatarName& av_name) -{ - mAvatarNameCacheConnection.disconnect(); - - if (agent_id == mAvatarID) - { - getChild("user_name")->setValue(av_name.getDisplayName()); - getChild("user_name_small")->setValue(av_name.getDisplayName()); - getChild("user_slid")->setValue(av_name.getUserName()); - mAvatarName = av_name; - - // show smaller display name if too long to display in regular size - if (getChild("user_name")->getTextPixelWidth() > getChild("user_name")->getRect().getWidth()) - { - getChild("user_name_small")->setVisible( true ); - getChild("user_name")->setVisible( false ); - } - else - { - getChild("user_name_small")->setVisible( false ); - getChild("user_name")->setVisible( true ); - - } - - } -} - -////////////////////////////////////////////////////////////////////////////// -// LLInspectAvatarUtil -////////////////////////////////////////////////////////////////////////////// -void LLInspectAvatarUtil::registerFloater() -{ - LLFloaterReg::add("inspect_avatar", "inspect_avatar.xml", - &LLFloaterReg::build); -} +/** + * @file llinspectavatar.cpp + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llinspectavatar.h" + +// viewer files +#include "llagent.h" +#include "llavataractions.h" +#include "llavatariconctrl.h" +#include "llavatarnamecache.h" +#include "llavatarpropertiesprocessor.h" +#include "lldateutil.h" +#include "llinspect.h" +#include "llmutelist.h" +#include "llslurl.h" +#include "llstartup.h" +#include "llvoiceclient.h" +#include "lltransientfloatermgr.h" + +// Linden libraries +#include "llfloater.h" +#include "llfloaterreg.h" +#include "lltextbox.h" +#include "lltrans.h" + +class LLFetchAvatarData; + + +////////////////////////////////////////////////////////////////////////////// +// LLInspectAvatar +////////////////////////////////////////////////////////////////////////////// + +// Avatar Inspector, a small information window used when clicking +// on avatar names in the 2D UI and in the ambient inspector widget for +// the 3D world. +class LLInspectAvatar : public LLInspect, LLTransientFloater +{ + friend class LLFloaterReg; + +public: + // avatar_id - Avatar ID for which to show information + // Inspector will be positioned relative to current mouse position + LLInspectAvatar(const LLSD& avatar_id); + virtual ~LLInspectAvatar(); + + /*virtual*/ bool postBuild(void); + + // Because floater is single instance, need to re-parse data on each spawn + // (for example, inspector about same avatar but in different position) + /*virtual*/ void onOpen(const LLSD& avatar_id); + + // Update view based on information from avatar properties processor + void processAvatarData(LLAvatarData* data); + + virtual LLTransientFloaterMgr::ETransientGroup getGroup() { return LLTransientFloaterMgr::GLOBAL; } + +private: + // Make network requests for all the data to display in this view. + // Used on construction and if avatar id changes. + void requestUpdate(); + + // Set the volume slider to this user's current client-side volume setting, + // hiding/disabling if the user is not nearby. + void updateVolumeSlider(); + + // Button callbacks + void onClickMuteVolume(); + void onVolumeChange(const LLSD& data); + + void onAvatarNameCache(const LLUUID& agent_id, + const LLAvatarName& av_name); + +private: + LLUUID mAvatarID; + // Need avatar name information to spawn friend add request + LLAvatarName mAvatarName; + // an in-flight request for avatar properties from LLAvatarPropertiesProcessor + // is represented by this object + LLFetchAvatarData* mPropertiesRequest; + boost::signals2::connection mAvatarNameCacheConnection; +}; + +////////////////////////////////////////////////////////////////////////////// +// LLFetchAvatarData +////////////////////////////////////////////////////////////////////////////// + +// This object represents a pending request for avatar properties information +class LLFetchAvatarData : public LLAvatarPropertiesObserver +{ +public: + // If the inspector closes it will delete the pending request object, so the + // inspector pointer will be valid for the lifetime of this object + LLFetchAvatarData(const LLUUID& avatar_id, LLInspectAvatar* inspector) + : mAvatarID(avatar_id), + mInspector(inspector) + { + LLAvatarPropertiesProcessor* processor = + LLAvatarPropertiesProcessor::getInstance(); + // register ourselves as an observer + processor->addObserver(mAvatarID, this); + // send a request (duplicates will be suppressed inside the avatar + // properties processor) + processor->sendAvatarPropertiesRequest(mAvatarID); + } + + ~LLFetchAvatarData() + { + // remove ourselves as an observer + LLAvatarPropertiesProcessor::getInstance()-> + removeObserver(mAvatarID, this); + } + + void processProperties(void* data, EAvatarProcessorType type) + { + // route the data to the inspector + if (data + && type == APT_PROPERTIES) + { + LLAvatarData* avatar_data = static_cast(data); + mInspector->processAvatarData(avatar_data); + } + } + + // Store avatar ID so we can un-register the observer on destruction + LLUUID mAvatarID; + LLInspectAvatar* mInspector; +}; + +LLInspectAvatar::LLInspectAvatar(const LLSD& sd) +: LLInspect( LLSD() ), // single_instance, doesn't really need key + mAvatarID(), // set in onOpen() *Note: we used to show partner's name but we dont anymore --angela 3rd Dec* + mAvatarName(), + mPropertiesRequest(NULL), + mAvatarNameCacheConnection() +{ + // can't make the properties request until the widgets are constructed + // as it might return immediately, so do it in onOpen. + + LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::GLOBAL, this); + LLTransientFloater::init(this); +} + +LLInspectAvatar::~LLInspectAvatar() +{ + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + // clean up any pending requests so they don't call back into a deleted + // view + delete mPropertiesRequest; + mPropertiesRequest = NULL; + + LLTransientFloaterMgr::getInstance()->removeControlView(this); +} + +/*virtual*/ +bool LLInspectAvatar::postBuild(void) +{ + getChild("mute_btn")->setCommitCallback( + boost::bind(&LLInspectAvatar::onClickMuteVolume, this) ); + + getChild("volume_slider")->setCommitCallback( + boost::bind(&LLInspectAvatar::onVolumeChange, this, _2)); + + return true; +} + +// Multiple calls to showInstance("inspect_avatar", foo) will provide different +// LLSD for foo, which we will catch here. +//virtual +void LLInspectAvatar::onOpen(const LLSD& data) +{ + // Start open animation + LLInspect::onOpen(data); + + // Extract appropriate avatar id + mAvatarID = data["avatar_id"]; + + LLInspect::repositionInspector(data); + + // Generate link to avatar profile. + LLTextBase* avatar_profile_link = getChild("avatar_profile_link"); + avatar_profile_link->setTextArg("[LINK]", LLSLURL("agent", mAvatarID, "about").getSLURLString()); + avatar_profile_link->setIsFriendCallback(LLAvatarActions::isFriend); + + // can't call from constructor as widgets are not built yet + requestUpdate(); + + updateVolumeSlider(); +} + +void LLInspectAvatar::requestUpdate() +{ + // Don't make network requests when spawning from the debug menu at the + // login screen (which is useful to work on the layout). + if (mAvatarID.isNull()) + { + if (LLStartUp::getStartupState() >= STATE_STARTED) + { + // once we're running we don't want to show the test floater + // for bogus LLUUID::null links + closeFloater(); + } + return; + } + + // Clear out old data so it doesn't flash between old and new + getChild("user_name")->setValue(""); + getChild("user_name_small")->setValue(""); + getChild("user_slid")->setValue(""); + getChild("user_subtitle")->setValue(""); + getChild("user_details")->setValue(""); + + // Make a new request for properties + delete mPropertiesRequest; + mPropertiesRequest = new LLFetchAvatarData(mAvatarID, this); + + // Use an avatar_icon even though the image id will come down with the + // avatar properties because the avatar_icon code maintains a cache of icons + // and this may result in the image being visible sooner. + // *NOTE: This may generate a duplicate avatar properties request, but that + // will be suppressed internally in the avatar properties processor. + + //remove avatar id from cache to get fresh info + LLAvatarIconIDCache::getInstance()->remove(mAvatarID); + + getChild("avatar_icon")->setValue(LLSD(mAvatarID) ); + + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + mAvatarNameCacheConnection = LLAvatarNameCache::get(mAvatarID,boost::bind(&LLInspectAvatar::onAvatarNameCache,this, _1, _2)); +} + +void LLInspectAvatar::processAvatarData(LLAvatarData* data) +{ + LLStringUtil::format_map_t args; + + std::string birth_date = LLTrans::getString(data->hide_age ? + "AvatarBirthDateFormatShort" : + "AvatarBirthDateFormatFull"); + LLStringUtil::format(birth_date, LLSD().with("datetime", (S32) data->born_on.secondsSinceEpoch())); + args["[BORN_ON]"] = birth_date; + args["[AGE]"] = data->hide_age ? + LLStringUtilBase::null : + LLDateUtil::ageFromDate(data->born_on, LLDate::now()); + args["[SL_PROFILE]"] = data->about_text; + args["[RW_PROFILE"] = data->fl_about_text; + args["[ACCTTYPE]"] = LLAvatarPropertiesProcessor::accountType(data); + std::string payment_info = LLAvatarPropertiesProcessor::paymentInfo(data); + args["[PAYMENTINFO]"] = payment_info; + args["[COMMA]"] = (payment_info.empty() ? "" : ","); + + std::string subtitle = getString("Subtitle", args); + getChild("user_subtitle")->setValue( LLSD(subtitle) ); + std::string details = getString("Details", args); + getChild("user_details")->setValue( LLSD(details) ); + + // Delete the request object as it has been satisfied + delete mPropertiesRequest; + mPropertiesRequest = NULL; +} + +void LLInspectAvatar::updateVolumeSlider() +{ + bool voice_enabled = LLVoiceClient::getInstance()->getVoiceEnabled(mAvatarID); + + // Do not display volume slider and mute button if it + // is ourself or we are not in a voice channel together + if (!voice_enabled || (mAvatarID == gAgent.getID())) + { + getChild("mute_btn")->setVisible(false); + getChild("volume_slider")->setVisible(false); + } + + else + { + getChild("mute_btn")->setVisible(true); + getChild("volume_slider")->setVisible(true); + + // By convention, we only display and toggle voice mutes, not all mutes + bool is_muted = LLAvatarActions::isVoiceMuted(mAvatarID); + + LLUICtrl* mute_btn = getChild("mute_btn"); + + bool is_linden = LLStringUtil::endsWith(mAvatarName.getDisplayName(), " Linden"); + + mute_btn->setEnabled( !is_linden); + mute_btn->setValue( is_muted ); + + LLUICtrl* volume_slider = getChild("volume_slider"); + volume_slider->setEnabled( !is_muted ); + + F32 volume; + + if (is_muted) + { + // it's clearer to display their volume as zero + volume = 0.f; + } + else + { + // actual volume + volume = LLVoiceClient::getInstance()->getUserVolume(mAvatarID); + } + volume_slider->setValue( (F64)volume ); + } + +} + +void LLInspectAvatar::onClickMuteVolume() +{ + // By convention, we only display and toggle voice mutes, not all mutes + LLMuteList* mute_list = LLMuteList::getInstance(); + bool is_muted = mute_list->isMuted(mAvatarID, LLMute::flagVoiceChat); + + LLMute mute(mAvatarID, mAvatarName.getUserName(), LLMute::AGENT); + if (!is_muted) + { + mute_list->add(mute, LLMute::flagVoiceChat); + } + else + { + mute_list->remove(mute, LLMute::flagVoiceChat); + } + + updateVolumeSlider(); +} + +void LLInspectAvatar::onVolumeChange(const LLSD& data) +{ + F32 volume = (F32)data.asReal(); + LLVoiceClient::getInstance()->setUserVolume(mAvatarID, volume); +} + +void LLInspectAvatar::onAvatarNameCache( + const LLUUID& agent_id, + const LLAvatarName& av_name) +{ + mAvatarNameCacheConnection.disconnect(); + + if (agent_id == mAvatarID) + { + getChild("user_name")->setValue(av_name.getDisplayName()); + getChild("user_name_small")->setValue(av_name.getDisplayName()); + getChild("user_slid")->setValue(av_name.getUserName()); + mAvatarName = av_name; + + // show smaller display name if too long to display in regular size + if (getChild("user_name")->getTextPixelWidth() > getChild("user_name")->getRect().getWidth()) + { + getChild("user_name_small")->setVisible( true ); + getChild("user_name")->setVisible( false ); + } + else + { + getChild("user_name_small")->setVisible( false ); + getChild("user_name")->setVisible( true ); + + } + + } +} + +////////////////////////////////////////////////////////////////////////////// +// LLInspectAvatarUtil +////////////////////////////////////////////////////////////////////////////// +void LLInspectAvatarUtil::registerFloater() +{ + LLFloaterReg::add("inspect_avatar", "inspect_avatar.xml", + &LLFloaterReg::build); +} diff --git a/indra/newview/llinspectobject.cpp b/indra/newview/llinspectobject.cpp index b6709a1e78..eb2cdb8632 100644 --- a/indra/newview/llinspectobject.cpp +++ b/indra/newview/llinspectobject.cpp @@ -1,691 +1,691 @@ -/** - * @file llinspectobject.cpp - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llinspectobject.h" - -// Viewer -#include "llagent.h" // To standup -#include "llfloatersidepanelcontainer.h" -#include "llinspect.h" -#include "llmediaentry.h" -#include "llselectmgr.h" -#include "llslurl.h" -#include "llviewermenu.h" // handle_object_touch(), handle_buy() -#include "llviewermedia.h" -#include "llviewermediafocus.h" -#include "llviewerobjectlist.h" // to select the requested object -#include "llvoavatarself.h" - -// Linden libraries -#include "llbutton.h" // setLabel(), not virtual! -#include "llclickaction.h" -#include "llfloaterreg.h" -#include "llmenubutton.h" -#include "llresmgr.h" // getMonetaryString -#include "llsafehandle.h" -#include "lltextbox.h" // for description truncation -#include "lltoggleablemenu.h" -#include "lltrans.h" -#include "lluictrl.h" - -class LLViewerObject; - -////////////////////////////////////////////////////////////////////////////// -// LLInspectObject -////////////////////////////////////////////////////////////////////////////// - -// Object Inspector, a small information window used when clicking -// in the ambient inspector widget for objects in the 3D world. -class LLInspectObject : public LLInspect -{ - friend class LLFloaterReg; - -public: - // object_id - Root object ID for which to show information - // Inspector will be positioned relative to current mouse position - LLInspectObject(const LLSD& object_id); - virtual ~LLInspectObject(); - - /*virtual*/ bool postBuild(void); - - // Because floater is single instance, need to re-parse data on each spawn - // (for example, inspector about same avatar but in different position) - /*virtual*/ void onOpen(const LLSD& avatar_id); - - // Release the selection and do other cleanup - /*virtual*/ void onClose(bool app_quitting); - - // override the inspector mouse leave so timer is only paused if - // gear menu is not open - /* virtual */ void onMouseLeave(S32 x, S32 y, MASK mask); - -private: - // Refresh displayed data with information from selection manager - void update(); - - void hideButtons(); - void updateButtons(LLSelectNode* nodep); - void updateSitLabel(LLSelectNode* nodep); - void updateTouchLabel(LLSelectNode* nodep); - - void updateName(LLSelectNode* nodep); - void updateDescription(LLSelectNode* nodep); - void updatePrice(LLSelectNode* nodep); - void updateCreator(LLSelectNode* nodep); - - void updateMediaCurrentURL(); - void updateSecureBrowsing(); - - void onClickBuy(); - void onClickPay(); - void onClickTakeFreeCopy(); - void onClickTouch(); - void onClickSit(); - void onClickOpen(); - void onClickMoreInfo(); - void onClickZoomIn(); - -private: - LLUUID mObjectID; - LLUUID mPreviousObjectID; - S32 mObjectFace; - viewer_media_t mMediaImpl; - LLMediaEntry* mMediaEntry; - LLSafeHandle mObjectSelection; - boost::signals2::connection mSelectionUpdateSlot; -}; - -LLInspectObject::LLInspectObject(const LLSD& sd) -: LLInspect( LLSD() ), // single_instance, doesn't really need key - mObjectID(NULL), // set in onOpen() - mObjectFace(0), - mObjectSelection(NULL), - mMediaImpl(NULL), - mMediaEntry(NULL) -{ - // can't make the properties request until the widgets are constructed - // as it might return immediately, so do it in postBuild. - mCommitCallbackRegistrar.add("InspectObject.Buy", boost::bind(&LLInspectObject::onClickBuy, this)); - mCommitCallbackRegistrar.add("InspectObject.Pay", boost::bind(&LLInspectObject::onClickPay, this)); - mCommitCallbackRegistrar.add("InspectObject.TakeFreeCopy", boost::bind(&LLInspectObject::onClickTakeFreeCopy, this)); - mCommitCallbackRegistrar.add("InspectObject.Touch", boost::bind(&LLInspectObject::onClickTouch, this)); - mCommitCallbackRegistrar.add("InspectObject.Sit", boost::bind(&LLInspectObject::onClickSit, this)); - mCommitCallbackRegistrar.add("InspectObject.Open", boost::bind(&LLInspectObject::onClickOpen, this)); - mCommitCallbackRegistrar.add("InspectObject.MoreInfo", boost::bind(&LLInspectObject::onClickMoreInfo, this)); - mCommitCallbackRegistrar.add("InspectObject.ZoomIn", boost::bind(&LLInspectObject::onClickZoomIn, this)); -} - - -LLInspectObject::~LLInspectObject() -{ - if (mSelectionUpdateSlot.connected()) - { - mSelectionUpdateSlot.disconnect(); - } -} - -/*virtual*/ -bool LLInspectObject::postBuild(void) -{ - // The XML file has sample data in it. Clear that out so we don't - // flicker when data arrives off network. - getChild("object_name")->setValue(""); - getChild("object_creator")->setValue(""); - getChild("object_description")->setValue(""); - getChild("object_media_url")->setValue(""); - // Set buttons invisible until we know what this object can do - hideButtons(); - - // Hide floater when name links clicked - LLTextBox* textbox = getChild("object_creator"); - textbox->setURLClickedCallback(boost::bind(&LLInspectObject::closeFloater, this, false) ); - - // Hook up functionality - getChild("buy_btn")->setCommitCallback( - boost::bind(&LLInspectObject::onClickBuy, this)); - getChild("pay_btn")->setCommitCallback( - boost::bind(&LLInspectObject::onClickPay, this)); - getChild("take_free_copy_btn")->setCommitCallback( - boost::bind(&LLInspectObject::onClickTakeFreeCopy, this)); - getChild("touch_btn")->setCommitCallback( - boost::bind(&LLInspectObject::onClickTouch, this)); - getChild("sit_btn")->setCommitCallback( - boost::bind(&LLInspectObject::onClickSit, this)); - getChild("open_btn")->setCommitCallback( - boost::bind(&LLInspectObject::onClickOpen, this)); - getChild("more_info_btn")->setCommitCallback( - boost::bind(&LLInspectObject::onClickMoreInfo, this)); - - if (!mSelectionUpdateSlot.connected()) - { - // Watch for updates to selection properties off the network - mSelectionUpdateSlot = LLSelectMgr::getInstance()->mUpdateSignal.connect( - boost::bind(&LLInspectObject::update, this)); - } - - return true; -} - -// Multiple calls to showInstance("inspect_avatar", foo) will provide different -// LLSD for foo, which we will catch here. -//virtual -void LLInspectObject::onOpen(const LLSD& data) -{ - // Start animation - LLInspect::onOpen(data); - - // Extract appropriate avatar id - mObjectID = data["object_id"]; - - if(data.has("object_face")) - { - mObjectFace = data["object_face"]; - } - - LLInspect::repositionInspector(data); - - // Promote hovered object to a complete selection, which will also force - // a request for selected object data off the network - LLViewerObject* obj = gObjectList.findObject( mObjectID ); - if (obj) - { - // Media focus and this code fight over the select manager. - // Make sure any media is unfocused before changing the selection here. - LLViewerMediaFocus::getInstance()->clearFocus(); - - LLSelectMgr::instance().deselectAll(); - mObjectSelection = LLSelectMgr::instance().selectObjectAndFamily(obj,false,true); - - // Mark this as a transient selection - struct SetTransient : public LLSelectedNodeFunctor - { - bool apply(LLSelectNode* node) - { - node->setTransient(true); - return true; - } - } functor; - mObjectSelection->applyToNodes(&functor); - - // Does this face have media? - const LLTextureEntry* tep = obj->getTE(mObjectFace); - if (!tep) - return; - - mMediaEntry = tep->hasMedia() ? tep->getMediaData() : NULL; - if(!mMediaEntry) - return; - - mMediaImpl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mMediaEntry->getMediaID()); - } -} - -// virtual -void LLInspectObject::onClose(bool app_quitting) -{ - // Release selection to deselect - mObjectSelection = NULL; - mPreviousObjectID = mObjectID; - - getChild("gear_btn")->hideMenu(); -} - - -void LLInspectObject::update() -{ - // Performance optimization, because we listen to updates from select mgr - // but we're never destroyed. - if (!getVisible()) return; - - LLObjectSelection* selection = LLSelectMgr::getInstance()->getSelection(); - if (!selection) return; - - LLSelectNode* nodep = selection->getFirstRootNode(); - if (!nodep) return; - - // If we don't have fresh object info yet and it's the object we inspected last time, - // keep showing the previously retrieved data until we get the update. - if (!nodep->mValid && nodep->getObject()->getID() == mPreviousObjectID) - { - return; - } - - updateButtons(nodep); - updateName(nodep); - updateDescription(nodep); - updateCreator(nodep); - updatePrice(nodep); - - LLViewerObject* obj = nodep->getObject(); - if(!obj) - return; - - if ( mObjectFace < 0 - || mObjectFace >= obj->getNumTEs() ) - { - return; - } - - // Does this face have media? - const LLTextureEntry* tep = obj->getTE(mObjectFace); - if (!tep) - return; - - mMediaEntry = tep->hasMedia() ? tep->getMediaData() : NULL; - if(!mMediaEntry) - return; - - mMediaImpl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mMediaEntry->getMediaID()); - - updateMediaCurrentURL(); - updateSecureBrowsing(); -} - -void LLInspectObject::hideButtons() -{ - getChild("buy_btn")->setVisible(false); - getChild("pay_btn")->setVisible(false); - getChild("take_free_copy_btn")->setVisible(false); - getChild("touch_btn")->setVisible(false); - getChild("sit_btn")->setVisible(false); - getChild("open_btn")->setVisible(false); -} - -// *TODO: Extract this method from lltoolpie.cpp and put somewhere shared -extern U8 final_click_action(LLViewerObject*); - -// Choose the "most relevant" operation for this object, and show a button for -// that operation as the left-most button in the inspector. -void LLInspectObject::updateButtons(LLSelectNode* nodep) -{ - // We'll start with everyone hidden and show the ones we need - hideButtons(); - - LLViewerObject* object = nodep->getObject(); - LLViewerObject *parent = (LLViewerObject*)object->getParent(); - bool for_copy = anyone_copy_selection(nodep); - bool for_sale = enable_buy_object(); - S32 price = nodep->mSaleInfo.getSalePrice(); - U8 click_action = final_click_action(object); - - if (for_copy - || (for_sale && price == 0)) - { - // Free copies have priority over other operations - getChild("take_free_copy_btn")->setVisible(true); - } - else if (for_sale) - { - getChild("buy_btn")->setVisible(true); - } - else if ( enable_pay_object() ) - { - getChild("pay_btn")->setVisible(true); - } - else if (click_action == CLICK_ACTION_SIT) - { - // Click-action sit must come before "open" because many objects on - // which you can sit have scripts, and hence can be opened - getChild("sit_btn")->setVisible(true); - updateSitLabel(nodep); - } - else if (object->flagHandleTouch() - || (parent && parent->flagHandleTouch())) - { - getChild("touch_btn")->setVisible(true); - updateTouchLabel(nodep); - } - else if ( enable_object_open() ) - { - // Open is last because anything with a script in it can be opened - getChild("open_btn")->setVisible(true); - } - else - { - // By default, we can sit on anything - getChild("sit_btn")->setVisible(true); - updateSitLabel(nodep); - } - - // No flash - focusFirstItem(false, false); -} - -void LLInspectObject::updateSitLabel(LLSelectNode* nodep) -{ - LLButton* sit_btn = getChild("sit_btn"); - if (!nodep->mSitName.empty()) - { - sit_btn->setLabel( nodep->mSitName ); - } - else - { - sit_btn->setLabel( getString("Sit") ); - } -} - -void LLInspectObject::updateTouchLabel(LLSelectNode* nodep) -{ - LLButton* sit_btn = getChild("touch_btn"); - if (!nodep->mTouchName.empty()) - { - sit_btn->setLabel( nodep->mTouchName ); - } - else - { - sit_btn->setLabel( getString("Touch") ); - } -} - -void LLInspectObject::updateName(LLSelectNode* nodep) -{ - std::string name; - if (!nodep->mName.empty()) - { - name = nodep->mName; - } - else - { - name = LLTrans::getString("TooltipNoName"); - } - getChild("object_name")->setValue(name); -} - -void LLInspectObject::updateDescription(LLSelectNode* nodep) -{ - const char* const DEFAULT_DESC = "(No Description)"; - std::string desc; - if (!nodep->mDescription.empty() - && nodep->mDescription != DEFAULT_DESC) - { - desc = nodep->mDescription; - } - - LLTextBox* textbox = getChild("object_description"); - textbox->setValue(desc); -} - -void LLInspectObject::updateMediaCurrentURL() -{ - if(!mMediaEntry) - return; - LLTextBox* textbox = getChild("object_media_url"); - std::string media_url = ""; - textbox->setValue(media_url); - textbox->setToolTip(media_url); - LLStringUtil::format_map_t args; - - if(mMediaImpl.notNull() && mMediaImpl->hasMedia()) - { - - LLPluginClassMedia* media_plugin = NULL; - media_plugin = mMediaImpl->getMediaPlugin(); - if(media_plugin) - { - if(media_plugin->pluginSupportsMediaTime()) - { - args["[CurrentURL]"] = mMediaImpl->getMediaURL(); - } - else - { - args["[CurrentURL]"] = media_plugin->getLocation(); - } - media_url = LLTrans::getString("CurrentURL", args); - - } - } - else if(mMediaEntry->getCurrentURL() != "") - { - args["[CurrentURL]"] = mMediaEntry->getCurrentURL(); - media_url = LLTrans::getString("CurrentURL", args); - } - - textbox->setText(media_url); - textbox->setToolTip(media_url); -} - -void LLInspectObject::updateCreator(LLSelectNode* nodep) -{ - // final information for display - LLStringUtil::format_map_t args; - std::string text; - - // Leave text blank until data loaded - if (nodep->mValid) - { - // Utilize automatic translation of SLURL into name to display - // a clickable link - // Objects cannot be created by a group, so use agent URL format - LLUUID creator_id = nodep->mPermissions->getCreator(); - std::string creator_url = - LLSLURL("agent", creator_id, "about").getSLURLString(); - args["[CREATOR]"] = creator_url; - - // created by one user but owned by another - std::string owner_url; - LLUUID owner_id; - bool group_owned = nodep->mPermissions->isGroupOwned(); - if (group_owned) - { - owner_id = nodep->mPermissions->getGroup(); - owner_url = LLSLURL("group", owner_id, "about").getSLURLString(); - } - else - { - owner_id = nodep->mPermissions->getOwner(); - owner_url = LLSLURL("agent", owner_id, "about").getSLURLString(); - } - args["[OWNER]"] = owner_url; - - if (creator_id == owner_id) - { - // common case, created and owned by one user - text = getString("Creator", args); - } - else - { - text = getString("CreatorAndOwner", args); - } - } - getChild("object_creator")->setValue(text); -} - -void LLInspectObject::updatePrice(LLSelectNode* nodep) -{ - // *TODO: Only look these up once and use for both updateButtons and here - bool for_copy = anyone_copy_selection(nodep); - bool for_sale = enable_buy_object(); - S32 price = nodep->mSaleInfo.getSalePrice(); - - bool show_price_icon = false; - std::string line; - if (for_copy - || (for_sale && price == 0)) - { - line = getString("PriceFree"); - show_price_icon = true; - } - else if (for_sale) - { - LLStringUtil::format_map_t args; - args["[AMOUNT]"] = LLResMgr::getInstance()->getMonetaryString(price); - line = getString("Price", args); - show_price_icon = true; - } - getChild("price_text")->setValue(line); - getChild("price_icon")->setVisible(show_price_icon); -} - -void LLInspectObject::updateSecureBrowsing() -{ - bool is_secure_browsing = false; - - if(mMediaImpl.notNull() - && mMediaImpl->hasMedia()) - { - LLPluginClassMedia* media_plugin = NULL; - std::string current_url = ""; - media_plugin = mMediaImpl->getMediaPlugin(); - if(media_plugin) - { - if(media_plugin->pluginSupportsMediaTime()) - { - current_url = mMediaImpl->getMediaURL(); - } - else - { - current_url = media_plugin->getLocation(); - } - } - - std::string prefix = std::string("https://"); - std::string test_prefix = current_url.substr(0, prefix.length()); - LLStringUtil::toLower(test_prefix); - if(test_prefix == prefix) - { - is_secure_browsing = true; - } - } - getChild("secure_browsing")->setVisible(is_secure_browsing); -} - -// For the object inspector, only unpause the fade timer -// if the gear menu is not open -void LLInspectObject::onMouseLeave(S32 x, S32 y, MASK mask) -{ - LLToggleableMenu* gear_menu = getChild("gear_btn")->getMenu(); - if ( gear_menu && gear_menu->getVisible() ) - { - return; - } - - if(childHasVisiblePopupMenu()) - { - return; - } - - mOpenTimer.unpause(); -} - -void LLInspectObject::onClickBuy() -{ - handle_buy(); - closeFloater(); -} - -void LLInspectObject::onClickPay() -{ - handle_give_money_dialog(); - closeFloater(); -} - -void LLInspectObject::onClickTakeFreeCopy() -{ - LLObjectSelection* selection = LLSelectMgr::getInstance()->getSelection(); - if (!selection) return; - - LLSelectNode* nodep = selection->getFirstRootNode(); - if (!nodep) return; - - // Figure out if this is a "free buy" or a "take copy" - bool for_copy = anyone_copy_selection(nodep); - // Prefer to just take a free copy - if (for_copy) - { - handle_take_copy(); - } - else - { - // Buy for free (confusing, but that's how it is) - handle_buy(); - } - closeFloater(); -} - -void LLInspectObject::onClickTouch() -{ - handle_object_touch(); - closeFloater(); -} - -void LLInspectObject::onClickSit() -{ - bool is_sitting = false; - if (mObjectSelection) - { - LLSelectNode* node = mObjectSelection->getFirstRootNode(); - if (node && node->mValid) - { - LLViewerObject* root_object = node->getObject(); - if (root_object - && isAgentAvatarValid() - && gAgentAvatarp->isSitting() - && gAgentAvatarp->getRoot() == root_object) - { - is_sitting = true; - } - } - } - - if (is_sitting) - { - gAgent.standUp(); - } - else - { - handle_object_sit(mObjectID); - } - closeFloater(); -} - -void LLInspectObject::onClickOpen() -{ - LLFloaterReg::showInstance("openobject"); - closeFloater(); -} - -void LLInspectObject::onClickMoreInfo() -{ - LLFloaterReg::showInstance("task_properties"); - closeFloater(); -} - -void LLInspectObject::onClickZoomIn() -{ - handle_look_at_selection("zoom"); - closeFloater(); -} - -////////////////////////////////////////////////////////////////////////////// -// LLInspectObjectUtil -////////////////////////////////////////////////////////////////////////////// -void LLInspectObjectUtil::registerFloater() -{ - LLFloaterReg::add("inspect_object", "inspect_object.xml", - &LLFloaterReg::build); -} - +/** + * @file llinspectobject.cpp + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llinspectobject.h" + +// Viewer +#include "llagent.h" // To standup +#include "llfloatersidepanelcontainer.h" +#include "llinspect.h" +#include "llmediaentry.h" +#include "llselectmgr.h" +#include "llslurl.h" +#include "llviewermenu.h" // handle_object_touch(), handle_buy() +#include "llviewermedia.h" +#include "llviewermediafocus.h" +#include "llviewerobjectlist.h" // to select the requested object +#include "llvoavatarself.h" + +// Linden libraries +#include "llbutton.h" // setLabel(), not virtual! +#include "llclickaction.h" +#include "llfloaterreg.h" +#include "llmenubutton.h" +#include "llresmgr.h" // getMonetaryString +#include "llsafehandle.h" +#include "lltextbox.h" // for description truncation +#include "lltoggleablemenu.h" +#include "lltrans.h" +#include "lluictrl.h" + +class LLViewerObject; + +////////////////////////////////////////////////////////////////////////////// +// LLInspectObject +////////////////////////////////////////////////////////////////////////////// + +// Object Inspector, a small information window used when clicking +// in the ambient inspector widget for objects in the 3D world. +class LLInspectObject : public LLInspect +{ + friend class LLFloaterReg; + +public: + // object_id - Root object ID for which to show information + // Inspector will be positioned relative to current mouse position + LLInspectObject(const LLSD& object_id); + virtual ~LLInspectObject(); + + /*virtual*/ bool postBuild(void); + + // Because floater is single instance, need to re-parse data on each spawn + // (for example, inspector about same avatar but in different position) + /*virtual*/ void onOpen(const LLSD& avatar_id); + + // Release the selection and do other cleanup + /*virtual*/ void onClose(bool app_quitting); + + // override the inspector mouse leave so timer is only paused if + // gear menu is not open + /* virtual */ void onMouseLeave(S32 x, S32 y, MASK mask); + +private: + // Refresh displayed data with information from selection manager + void update(); + + void hideButtons(); + void updateButtons(LLSelectNode* nodep); + void updateSitLabel(LLSelectNode* nodep); + void updateTouchLabel(LLSelectNode* nodep); + + void updateName(LLSelectNode* nodep); + void updateDescription(LLSelectNode* nodep); + void updatePrice(LLSelectNode* nodep); + void updateCreator(LLSelectNode* nodep); + + void updateMediaCurrentURL(); + void updateSecureBrowsing(); + + void onClickBuy(); + void onClickPay(); + void onClickTakeFreeCopy(); + void onClickTouch(); + void onClickSit(); + void onClickOpen(); + void onClickMoreInfo(); + void onClickZoomIn(); + +private: + LLUUID mObjectID; + LLUUID mPreviousObjectID; + S32 mObjectFace; + viewer_media_t mMediaImpl; + LLMediaEntry* mMediaEntry; + LLSafeHandle mObjectSelection; + boost::signals2::connection mSelectionUpdateSlot; +}; + +LLInspectObject::LLInspectObject(const LLSD& sd) +: LLInspect( LLSD() ), // single_instance, doesn't really need key + mObjectID(NULL), // set in onOpen() + mObjectFace(0), + mObjectSelection(NULL), + mMediaImpl(NULL), + mMediaEntry(NULL) +{ + // can't make the properties request until the widgets are constructed + // as it might return immediately, so do it in postBuild. + mCommitCallbackRegistrar.add("InspectObject.Buy", boost::bind(&LLInspectObject::onClickBuy, this)); + mCommitCallbackRegistrar.add("InspectObject.Pay", boost::bind(&LLInspectObject::onClickPay, this)); + mCommitCallbackRegistrar.add("InspectObject.TakeFreeCopy", boost::bind(&LLInspectObject::onClickTakeFreeCopy, this)); + mCommitCallbackRegistrar.add("InspectObject.Touch", boost::bind(&LLInspectObject::onClickTouch, this)); + mCommitCallbackRegistrar.add("InspectObject.Sit", boost::bind(&LLInspectObject::onClickSit, this)); + mCommitCallbackRegistrar.add("InspectObject.Open", boost::bind(&LLInspectObject::onClickOpen, this)); + mCommitCallbackRegistrar.add("InspectObject.MoreInfo", boost::bind(&LLInspectObject::onClickMoreInfo, this)); + mCommitCallbackRegistrar.add("InspectObject.ZoomIn", boost::bind(&LLInspectObject::onClickZoomIn, this)); +} + + +LLInspectObject::~LLInspectObject() +{ + if (mSelectionUpdateSlot.connected()) + { + mSelectionUpdateSlot.disconnect(); + } +} + +/*virtual*/ +bool LLInspectObject::postBuild(void) +{ + // The XML file has sample data in it. Clear that out so we don't + // flicker when data arrives off network. + getChild("object_name")->setValue(""); + getChild("object_creator")->setValue(""); + getChild("object_description")->setValue(""); + getChild("object_media_url")->setValue(""); + // Set buttons invisible until we know what this object can do + hideButtons(); + + // Hide floater when name links clicked + LLTextBox* textbox = getChild("object_creator"); + textbox->setURLClickedCallback(boost::bind(&LLInspectObject::closeFloater, this, false) ); + + // Hook up functionality + getChild("buy_btn")->setCommitCallback( + boost::bind(&LLInspectObject::onClickBuy, this)); + getChild("pay_btn")->setCommitCallback( + boost::bind(&LLInspectObject::onClickPay, this)); + getChild("take_free_copy_btn")->setCommitCallback( + boost::bind(&LLInspectObject::onClickTakeFreeCopy, this)); + getChild("touch_btn")->setCommitCallback( + boost::bind(&LLInspectObject::onClickTouch, this)); + getChild("sit_btn")->setCommitCallback( + boost::bind(&LLInspectObject::onClickSit, this)); + getChild("open_btn")->setCommitCallback( + boost::bind(&LLInspectObject::onClickOpen, this)); + getChild("more_info_btn")->setCommitCallback( + boost::bind(&LLInspectObject::onClickMoreInfo, this)); + + if (!mSelectionUpdateSlot.connected()) + { + // Watch for updates to selection properties off the network + mSelectionUpdateSlot = LLSelectMgr::getInstance()->mUpdateSignal.connect( + boost::bind(&LLInspectObject::update, this)); + } + + return true; +} + +// Multiple calls to showInstance("inspect_avatar", foo) will provide different +// LLSD for foo, which we will catch here. +//virtual +void LLInspectObject::onOpen(const LLSD& data) +{ + // Start animation + LLInspect::onOpen(data); + + // Extract appropriate avatar id + mObjectID = data["object_id"]; + + if(data.has("object_face")) + { + mObjectFace = data["object_face"]; + } + + LLInspect::repositionInspector(data); + + // Promote hovered object to a complete selection, which will also force + // a request for selected object data off the network + LLViewerObject* obj = gObjectList.findObject( mObjectID ); + if (obj) + { + // Media focus and this code fight over the select manager. + // Make sure any media is unfocused before changing the selection here. + LLViewerMediaFocus::getInstance()->clearFocus(); + + LLSelectMgr::instance().deselectAll(); + mObjectSelection = LLSelectMgr::instance().selectObjectAndFamily(obj,false,true); + + // Mark this as a transient selection + struct SetTransient : public LLSelectedNodeFunctor + { + bool apply(LLSelectNode* node) + { + node->setTransient(true); + return true; + } + } functor; + mObjectSelection->applyToNodes(&functor); + + // Does this face have media? + const LLTextureEntry* tep = obj->getTE(mObjectFace); + if (!tep) + return; + + mMediaEntry = tep->hasMedia() ? tep->getMediaData() : NULL; + if(!mMediaEntry) + return; + + mMediaImpl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mMediaEntry->getMediaID()); + } +} + +// virtual +void LLInspectObject::onClose(bool app_quitting) +{ + // Release selection to deselect + mObjectSelection = NULL; + mPreviousObjectID = mObjectID; + + getChild("gear_btn")->hideMenu(); +} + + +void LLInspectObject::update() +{ + // Performance optimization, because we listen to updates from select mgr + // but we're never destroyed. + if (!getVisible()) return; + + LLObjectSelection* selection = LLSelectMgr::getInstance()->getSelection(); + if (!selection) return; + + LLSelectNode* nodep = selection->getFirstRootNode(); + if (!nodep) return; + + // If we don't have fresh object info yet and it's the object we inspected last time, + // keep showing the previously retrieved data until we get the update. + if (!nodep->mValid && nodep->getObject()->getID() == mPreviousObjectID) + { + return; + } + + updateButtons(nodep); + updateName(nodep); + updateDescription(nodep); + updateCreator(nodep); + updatePrice(nodep); + + LLViewerObject* obj = nodep->getObject(); + if(!obj) + return; + + if ( mObjectFace < 0 + || mObjectFace >= obj->getNumTEs() ) + { + return; + } + + // Does this face have media? + const LLTextureEntry* tep = obj->getTE(mObjectFace); + if (!tep) + return; + + mMediaEntry = tep->hasMedia() ? tep->getMediaData() : NULL; + if(!mMediaEntry) + return; + + mMediaImpl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mMediaEntry->getMediaID()); + + updateMediaCurrentURL(); + updateSecureBrowsing(); +} + +void LLInspectObject::hideButtons() +{ + getChild("buy_btn")->setVisible(false); + getChild("pay_btn")->setVisible(false); + getChild("take_free_copy_btn")->setVisible(false); + getChild("touch_btn")->setVisible(false); + getChild("sit_btn")->setVisible(false); + getChild("open_btn")->setVisible(false); +} + +// *TODO: Extract this method from lltoolpie.cpp and put somewhere shared +extern U8 final_click_action(LLViewerObject*); + +// Choose the "most relevant" operation for this object, and show a button for +// that operation as the left-most button in the inspector. +void LLInspectObject::updateButtons(LLSelectNode* nodep) +{ + // We'll start with everyone hidden and show the ones we need + hideButtons(); + + LLViewerObject* object = nodep->getObject(); + LLViewerObject *parent = (LLViewerObject*)object->getParent(); + bool for_copy = anyone_copy_selection(nodep); + bool for_sale = enable_buy_object(); + S32 price = nodep->mSaleInfo.getSalePrice(); + U8 click_action = final_click_action(object); + + if (for_copy + || (for_sale && price == 0)) + { + // Free copies have priority over other operations + getChild("take_free_copy_btn")->setVisible(true); + } + else if (for_sale) + { + getChild("buy_btn")->setVisible(true); + } + else if ( enable_pay_object() ) + { + getChild("pay_btn")->setVisible(true); + } + else if (click_action == CLICK_ACTION_SIT) + { + // Click-action sit must come before "open" because many objects on + // which you can sit have scripts, and hence can be opened + getChild("sit_btn")->setVisible(true); + updateSitLabel(nodep); + } + else if (object->flagHandleTouch() + || (parent && parent->flagHandleTouch())) + { + getChild("touch_btn")->setVisible(true); + updateTouchLabel(nodep); + } + else if ( enable_object_open() ) + { + // Open is last because anything with a script in it can be opened + getChild("open_btn")->setVisible(true); + } + else + { + // By default, we can sit on anything + getChild("sit_btn")->setVisible(true); + updateSitLabel(nodep); + } + + // No flash + focusFirstItem(false, false); +} + +void LLInspectObject::updateSitLabel(LLSelectNode* nodep) +{ + LLButton* sit_btn = getChild("sit_btn"); + if (!nodep->mSitName.empty()) + { + sit_btn->setLabel( nodep->mSitName ); + } + else + { + sit_btn->setLabel( getString("Sit") ); + } +} + +void LLInspectObject::updateTouchLabel(LLSelectNode* nodep) +{ + LLButton* sit_btn = getChild("touch_btn"); + if (!nodep->mTouchName.empty()) + { + sit_btn->setLabel( nodep->mTouchName ); + } + else + { + sit_btn->setLabel( getString("Touch") ); + } +} + +void LLInspectObject::updateName(LLSelectNode* nodep) +{ + std::string name; + if (!nodep->mName.empty()) + { + name = nodep->mName; + } + else + { + name = LLTrans::getString("TooltipNoName"); + } + getChild("object_name")->setValue(name); +} + +void LLInspectObject::updateDescription(LLSelectNode* nodep) +{ + const char* const DEFAULT_DESC = "(No Description)"; + std::string desc; + if (!nodep->mDescription.empty() + && nodep->mDescription != DEFAULT_DESC) + { + desc = nodep->mDescription; + } + + LLTextBox* textbox = getChild("object_description"); + textbox->setValue(desc); +} + +void LLInspectObject::updateMediaCurrentURL() +{ + if(!mMediaEntry) + return; + LLTextBox* textbox = getChild("object_media_url"); + std::string media_url = ""; + textbox->setValue(media_url); + textbox->setToolTip(media_url); + LLStringUtil::format_map_t args; + + if(mMediaImpl.notNull() && mMediaImpl->hasMedia()) + { + + LLPluginClassMedia* media_plugin = NULL; + media_plugin = mMediaImpl->getMediaPlugin(); + if(media_plugin) + { + if(media_plugin->pluginSupportsMediaTime()) + { + args["[CurrentURL]"] = mMediaImpl->getMediaURL(); + } + else + { + args["[CurrentURL]"] = media_plugin->getLocation(); + } + media_url = LLTrans::getString("CurrentURL", args); + + } + } + else if(mMediaEntry->getCurrentURL() != "") + { + args["[CurrentURL]"] = mMediaEntry->getCurrentURL(); + media_url = LLTrans::getString("CurrentURL", args); + } + + textbox->setText(media_url); + textbox->setToolTip(media_url); +} + +void LLInspectObject::updateCreator(LLSelectNode* nodep) +{ + // final information for display + LLStringUtil::format_map_t args; + std::string text; + + // Leave text blank until data loaded + if (nodep->mValid) + { + // Utilize automatic translation of SLURL into name to display + // a clickable link + // Objects cannot be created by a group, so use agent URL format + LLUUID creator_id = nodep->mPermissions->getCreator(); + std::string creator_url = + LLSLURL("agent", creator_id, "about").getSLURLString(); + args["[CREATOR]"] = creator_url; + + // created by one user but owned by another + std::string owner_url; + LLUUID owner_id; + bool group_owned = nodep->mPermissions->isGroupOwned(); + if (group_owned) + { + owner_id = nodep->mPermissions->getGroup(); + owner_url = LLSLURL("group", owner_id, "about").getSLURLString(); + } + else + { + owner_id = nodep->mPermissions->getOwner(); + owner_url = LLSLURL("agent", owner_id, "about").getSLURLString(); + } + args["[OWNER]"] = owner_url; + + if (creator_id == owner_id) + { + // common case, created and owned by one user + text = getString("Creator", args); + } + else + { + text = getString("CreatorAndOwner", args); + } + } + getChild("object_creator")->setValue(text); +} + +void LLInspectObject::updatePrice(LLSelectNode* nodep) +{ + // *TODO: Only look these up once and use for both updateButtons and here + bool for_copy = anyone_copy_selection(nodep); + bool for_sale = enable_buy_object(); + S32 price = nodep->mSaleInfo.getSalePrice(); + + bool show_price_icon = false; + std::string line; + if (for_copy + || (for_sale && price == 0)) + { + line = getString("PriceFree"); + show_price_icon = true; + } + else if (for_sale) + { + LLStringUtil::format_map_t args; + args["[AMOUNT]"] = LLResMgr::getInstance()->getMonetaryString(price); + line = getString("Price", args); + show_price_icon = true; + } + getChild("price_text")->setValue(line); + getChild("price_icon")->setVisible(show_price_icon); +} + +void LLInspectObject::updateSecureBrowsing() +{ + bool is_secure_browsing = false; + + if(mMediaImpl.notNull() + && mMediaImpl->hasMedia()) + { + LLPluginClassMedia* media_plugin = NULL; + std::string current_url = ""; + media_plugin = mMediaImpl->getMediaPlugin(); + if(media_plugin) + { + if(media_plugin->pluginSupportsMediaTime()) + { + current_url = mMediaImpl->getMediaURL(); + } + else + { + current_url = media_plugin->getLocation(); + } + } + + std::string prefix = std::string("https://"); + std::string test_prefix = current_url.substr(0, prefix.length()); + LLStringUtil::toLower(test_prefix); + if(test_prefix == prefix) + { + is_secure_browsing = true; + } + } + getChild("secure_browsing")->setVisible(is_secure_browsing); +} + +// For the object inspector, only unpause the fade timer +// if the gear menu is not open +void LLInspectObject::onMouseLeave(S32 x, S32 y, MASK mask) +{ + LLToggleableMenu* gear_menu = getChild("gear_btn")->getMenu(); + if ( gear_menu && gear_menu->getVisible() ) + { + return; + } + + if(childHasVisiblePopupMenu()) + { + return; + } + + mOpenTimer.unpause(); +} + +void LLInspectObject::onClickBuy() +{ + handle_buy(); + closeFloater(); +} + +void LLInspectObject::onClickPay() +{ + handle_give_money_dialog(); + closeFloater(); +} + +void LLInspectObject::onClickTakeFreeCopy() +{ + LLObjectSelection* selection = LLSelectMgr::getInstance()->getSelection(); + if (!selection) return; + + LLSelectNode* nodep = selection->getFirstRootNode(); + if (!nodep) return; + + // Figure out if this is a "free buy" or a "take copy" + bool for_copy = anyone_copy_selection(nodep); + // Prefer to just take a free copy + if (for_copy) + { + handle_take_copy(); + } + else + { + // Buy for free (confusing, but that's how it is) + handle_buy(); + } + closeFloater(); +} + +void LLInspectObject::onClickTouch() +{ + handle_object_touch(); + closeFloater(); +} + +void LLInspectObject::onClickSit() +{ + bool is_sitting = false; + if (mObjectSelection) + { + LLSelectNode* node = mObjectSelection->getFirstRootNode(); + if (node && node->mValid) + { + LLViewerObject* root_object = node->getObject(); + if (root_object + && isAgentAvatarValid() + && gAgentAvatarp->isSitting() + && gAgentAvatarp->getRoot() == root_object) + { + is_sitting = true; + } + } + } + + if (is_sitting) + { + gAgent.standUp(); + } + else + { + handle_object_sit(mObjectID); + } + closeFloater(); +} + +void LLInspectObject::onClickOpen() +{ + LLFloaterReg::showInstance("openobject"); + closeFloater(); +} + +void LLInspectObject::onClickMoreInfo() +{ + LLFloaterReg::showInstance("task_properties"); + closeFloater(); +} + +void LLInspectObject::onClickZoomIn() +{ + handle_look_at_selection("zoom"); + closeFloater(); +} + +////////////////////////////////////////////////////////////////////////////// +// LLInspectObjectUtil +////////////////////////////////////////////////////////////////////////////// +void LLInspectObjectUtil::registerFloater() +{ + LLFloaterReg::add("inspect_object", "inspect_object.xml", + &LLFloaterReg::build); +} + diff --git a/indra/newview/llinspectremoteobject.cpp b/indra/newview/llinspectremoteobject.cpp index 1685d3e825..0060fe544d 100644 --- a/indra/newview/llinspectremoteobject.cpp +++ b/indra/newview/llinspectremoteobject.cpp @@ -1,184 +1,184 @@ -/** - * @file llinspectremoteobject.cpp - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterreg.h" -#include "llinspectremoteobject.h" -#include "llinspect.h" -#include "llmutelist.h" -#include "llpanelblockedlist.h" -#include "llslurl.h" -#include "lltrans.h" -#include "llui.h" -#include "lluictrl.h" -#include "llurlaction.h" - -////////////////////////////////////////////////////////////////////////////// -// LLInspectRemoteObject -////////////////////////////////////////////////////////////////////////////// - -// Remote Object Inspector, a small information window used to -// display information about potentially-remote objects. Used -// to display details about objects sending messages to the user. -class LLInspectRemoteObject : public LLInspect -{ - friend class LLFloaterReg; - -public: - LLInspectRemoteObject(const LLSD& object_id); - virtual ~LLInspectRemoteObject() {}; - - /*virtual*/ bool postBuild(void); - /*virtual*/ void onOpen(const LLSD& avatar_id); - - void onClickMap(); - void onClickBlock(); - void onClickClose(); - -private: - void update(); - -private: - LLUUID mObjectID; - LLUUID mOwnerID; - std::string mSLurl; - std::string mName; - bool mGroupOwned; -}; - -LLInspectRemoteObject::LLInspectRemoteObject(const LLSD& sd) : - LLInspect(LLSD()), - mObjectID(NULL), - mOwnerID(NULL), - mSLurl(""), - mName(""), - mGroupOwned(false) -{ -} - -/*virtual*/ -bool LLInspectRemoteObject::postBuild(void) -{ - // hook up the inspector's buttons - getChild("map_btn")->setCommitCallback( - boost::bind(&LLInspectRemoteObject::onClickMap, this)); - getChild("block_btn")->setCommitCallback( - boost::bind(&LLInspectRemoteObject::onClickBlock, this)); - getChild("close_btn")->setCommitCallback( - boost::bind(&LLInspectRemoteObject::onClickClose, this)); - - return true; -} - -/*virtual*/ -void LLInspectRemoteObject::onOpen(const LLSD& data) -{ - // Start animation - LLInspect::onOpen(data); - - // Extract appropriate object information from input LLSD - // (Eventually, it might be nice to query server for details - // rather than require caller to pass in the information.) - mObjectID = data["object_id"].asUUID(); - mName = data["name"].asString(); - mOwnerID = data["owner_id"].asUUID(); - mGroupOwned = data["group_owned"].asBoolean(); - mSLurl = data["slurl"].asString(); - - // update the inspector with the current object state - update(); - - LLInspect::repositionInspector(data); -} - -void LLInspectRemoteObject::onClickMap() -{ - std::string url = "secondlife://" + mSLurl; - LLUrlAction::showLocationOnMap(url); - closeFloater(); -} - -void LLInspectRemoteObject::onClickBlock() -{ - LLMute mute(mObjectID, mName, LLMute::OBJECT); - LLMuteList::getInstance()->add(mute); - LLPanelBlockedList::showPanelAndSelect(mute.mID); - closeFloater(); -} - -void LLInspectRemoteObject::onClickClose() -{ - closeFloater(); -} - -void LLInspectRemoteObject::update() -{ - // show the object name as the inspector's title - // (don't hyperlink URLs in object names) - getChild("object_name")->setValue("" + mName + ""); - - // show the object's owner - click it to show profile - std::string owner; - if (! mOwnerID.isNull()) - { - if (mGroupOwned) - { - owner = LLSLURL("group", mOwnerID, "about").getSLURLString(); - } - else - { - owner = LLSLURL("agent", mOwnerID, "about").getSLURLString(); - } - } - else - { - owner = LLTrans::getString("Unknown"); - } - getChild("object_owner")->setValue(owner); - - // display the object's SLurl - click it to teleport - std::string url; - if (! mSLurl.empty()) - { - url = "secondlife:///app/teleport/" + mSLurl; - } - getChild("object_slurl")->setValue(url); - - // disable the Map button if we don't have a SLurl - getChild("map_btn")->setEnabled(! mSLurl.empty()); - - // disable the Block button if we don't have the object ID (will this ever happen?) - getChild("block_btn")->setEnabled(!mObjectID.isNull() && !LLMuteList::getInstance()->isMuted(mObjectID)); -} - -////////////////////////////////////////////////////////////////////////////// -// LLInspectRemoteObjectUtil -////////////////////////////////////////////////////////////////////////////// -void LLInspectRemoteObjectUtil::registerFloater() -{ - LLFloaterReg::add("inspect_remote_object", "inspect_remote_object.xml", - &LLFloaterReg::build); -} +/** + * @file llinspectremoteobject.cpp + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterreg.h" +#include "llinspectremoteobject.h" +#include "llinspect.h" +#include "llmutelist.h" +#include "llpanelblockedlist.h" +#include "llslurl.h" +#include "lltrans.h" +#include "llui.h" +#include "lluictrl.h" +#include "llurlaction.h" + +////////////////////////////////////////////////////////////////////////////// +// LLInspectRemoteObject +////////////////////////////////////////////////////////////////////////////// + +// Remote Object Inspector, a small information window used to +// display information about potentially-remote objects. Used +// to display details about objects sending messages to the user. +class LLInspectRemoteObject : public LLInspect +{ + friend class LLFloaterReg; + +public: + LLInspectRemoteObject(const LLSD& object_id); + virtual ~LLInspectRemoteObject() {}; + + /*virtual*/ bool postBuild(void); + /*virtual*/ void onOpen(const LLSD& avatar_id); + + void onClickMap(); + void onClickBlock(); + void onClickClose(); + +private: + void update(); + +private: + LLUUID mObjectID; + LLUUID mOwnerID; + std::string mSLurl; + std::string mName; + bool mGroupOwned; +}; + +LLInspectRemoteObject::LLInspectRemoteObject(const LLSD& sd) : + LLInspect(LLSD()), + mObjectID(NULL), + mOwnerID(NULL), + mSLurl(""), + mName(""), + mGroupOwned(false) +{ +} + +/*virtual*/ +bool LLInspectRemoteObject::postBuild(void) +{ + // hook up the inspector's buttons + getChild("map_btn")->setCommitCallback( + boost::bind(&LLInspectRemoteObject::onClickMap, this)); + getChild("block_btn")->setCommitCallback( + boost::bind(&LLInspectRemoteObject::onClickBlock, this)); + getChild("close_btn")->setCommitCallback( + boost::bind(&LLInspectRemoteObject::onClickClose, this)); + + return true; +} + +/*virtual*/ +void LLInspectRemoteObject::onOpen(const LLSD& data) +{ + // Start animation + LLInspect::onOpen(data); + + // Extract appropriate object information from input LLSD + // (Eventually, it might be nice to query server for details + // rather than require caller to pass in the information.) + mObjectID = data["object_id"].asUUID(); + mName = data["name"].asString(); + mOwnerID = data["owner_id"].asUUID(); + mGroupOwned = data["group_owned"].asBoolean(); + mSLurl = data["slurl"].asString(); + + // update the inspector with the current object state + update(); + + LLInspect::repositionInspector(data); +} + +void LLInspectRemoteObject::onClickMap() +{ + std::string url = "secondlife://" + mSLurl; + LLUrlAction::showLocationOnMap(url); + closeFloater(); +} + +void LLInspectRemoteObject::onClickBlock() +{ + LLMute mute(mObjectID, mName, LLMute::OBJECT); + LLMuteList::getInstance()->add(mute); + LLPanelBlockedList::showPanelAndSelect(mute.mID); + closeFloater(); +} + +void LLInspectRemoteObject::onClickClose() +{ + closeFloater(); +} + +void LLInspectRemoteObject::update() +{ + // show the object name as the inspector's title + // (don't hyperlink URLs in object names) + getChild("object_name")->setValue("" + mName + ""); + + // show the object's owner - click it to show profile + std::string owner; + if (! mOwnerID.isNull()) + { + if (mGroupOwned) + { + owner = LLSLURL("group", mOwnerID, "about").getSLURLString(); + } + else + { + owner = LLSLURL("agent", mOwnerID, "about").getSLURLString(); + } + } + else + { + owner = LLTrans::getString("Unknown"); + } + getChild("object_owner")->setValue(owner); + + // display the object's SLurl - click it to teleport + std::string url; + if (! mSLurl.empty()) + { + url = "secondlife:///app/teleport/" + mSLurl; + } + getChild("object_slurl")->setValue(url); + + // disable the Map button if we don't have a SLurl + getChild("map_btn")->setEnabled(! mSLurl.empty()); + + // disable the Block button if we don't have the object ID (will this ever happen?) + getChild("block_btn")->setEnabled(!mObjectID.isNull() && !LLMuteList::getInstance()->isMuted(mObjectID)); +} + +////////////////////////////////////////////////////////////////////////////// +// LLInspectRemoteObjectUtil +////////////////////////////////////////////////////////////////////////////// +void LLInspectRemoteObjectUtil::registerFloater() +{ + LLFloaterReg::add("inspect_remote_object", "inspect_remote_object.xml", + &LLFloaterReg::build); +} diff --git a/indra/newview/llinspecttexture.cpp b/indra/newview/llinspecttexture.cpp index db433e6ca0..75366c4831 100644 --- a/indra/newview/llinspecttexture.cpp +++ b/indra/newview/llinspecttexture.cpp @@ -1,252 +1,252 @@ -/** - * @file llinspecttexture.cpp - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llinspect.h" -#include "llinspecttexture.h" -#include "llinventoryfunctions.h" -#include "llinventorymodel.h" -#include "lltexturectrl.h" -#include "lltrans.h" -#include "llviewertexturelist.h" - - -// ============================================================================ -// Helper functions -// - -LLToolTip* LLInspectTextureUtil::createInventoryToolTip(LLToolTip::Params p) -{ - const LLSD& sdTooltip = p.create_params; - - if (sdTooltip.has("thumbnail_id") && sdTooltip["thumbnail_id"].asUUID().notNull()) - { - // go straight for thumbnail regardless of type - // TODO: make a tooltip factory? - return LLUICtrlFactory::create(p); - } - - LLInventoryType::EType eInvType = (sdTooltip.has("inv_type")) ? (LLInventoryType::EType)sdTooltip["inv_type"].asInteger() : LLInventoryType::IT_NONE; - switch (eInvType) - { - case LLInventoryType::IT_CATEGORY: - { - if (sdTooltip.has("item_id")) - { - const LLUUID idCategory = sdTooltip["item_id"].asUUID(); - LLViewerInventoryCategory* cat = gInventory.getCategory(idCategory); - if (cat && cat->getPreferredType() == LLFolderType::FT_OUTFIT) - { - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - // Not LLIsOfAssetType, because we allow links - LLIsTextureType f; - gInventory.getDirectDescendentsOf(idCategory, cats, items, f); - - // Exactly one texture found => show the texture tooltip - if (1 == items.size()) - { - LLViewerInventoryItem* item = items.front(); - if (item && item->getIsLinkType()) - { - item = item->getLinkedItem(); - } - if (item) - { - // Note: LLFloaterChangeItemThumbnail will attempt to write this - // into folder's thumbnail id when opened - p.create_params.getValue()["thumbnail_id"] = item->getAssetUUID(); - return LLUICtrlFactory::create(p); - } - } - } - } - if ((!p.message.isProvided() || p.message().empty())) - { - return NULL; - } - // No or more than one texture found => show default tooltip - return LLUICtrlFactory::create(p); - } - default: - return LLUICtrlFactory::create(p); - } -} - -// ============================================================================ -// LLTexturePreviewView helper class -// - -class LLTexturePreviewView : public LLView -{ -public: - LLTexturePreviewView(const LLView::Params& p); - ~LLTexturePreviewView(); - -public: - void draw() override; - -public: - void setImageFromAssetId(const LLUUID& idAsset); - void setImageFromItemId(const LLUUID& idItem); - -protected: - LLPointer m_Image; - S32 mImageBoostLevel = LLGLTexture::BOOST_NONE; - std::string mLoadingText; -}; - - -LLTexturePreviewView::LLTexturePreviewView(const LLView::Params& p) - : LLView(p) -{ - mLoadingText = LLTrans::getString("texture_loading"); -} - -LLTexturePreviewView::~LLTexturePreviewView() -{ - if (m_Image) - { - m_Image->setBoostLevel(mImageBoostLevel); - m_Image = nullptr; - } -} - -void LLTexturePreviewView::draw() -{ - LLView::draw(); - - if (m_Image) - { - LLRect rctClient = getLocalRect(); - - if (4 == m_Image->getComponents()) - { - const LLColor4 color(.098f, .098f, .098f); - gl_rect_2d(rctClient, color, true); - } - gl_draw_scaled_image(rctClient.mLeft, rctClient.mBottom, rctClient.getWidth(), rctClient.getHeight(), m_Image); - - bool isLoading = (!m_Image->isFullyLoaded()) && (m_Image->getDiscardLevel() > 0); - if (isLoading) - LLFontGL::getFontSansSerif()->renderUTF8(mLoadingText, 0, llfloor(rctClient.mLeft + 3), llfloor(rctClient.mTop - 25), LLColor4::white, LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::DROP_SHADOW); - m_Image->addTextureStats((isLoading) ? MAX_IMAGE_AREA : (F32)(rctClient.getWidth() * rctClient.getHeight())); - } -} - -void LLTexturePreviewView::setImageFromAssetId(const LLUUID& idAsset) -{ - m_Image = LLViewerTextureManager::getFetchedTexture(idAsset, FTT_DEFAULT, MIPMAP_TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); - if (m_Image) - { - mImageBoostLevel = m_Image->getBoostLevel(); - m_Image->setBoostLevel(LLGLTexture::BOOST_PREVIEW); - m_Image->forceToSaveRawImage(0); - if ( (!m_Image->isFullyLoaded()) && (!m_Image->hasFetcher()) ) - { - if (m_Image->isInFastCacheList()) - { - m_Image->loadFromFastCache(); - } - gTextureList.forceImmediateUpdate(m_Image); - } - } -} - -void LLTexturePreviewView::setImageFromItemId(const LLUUID& idItem) -{ - const LLViewerInventoryItem* pItem = gInventory.getItem(idItem); - setImageFromAssetId( (pItem) ? pItem->getAssetUUID() : LLUUID::null ); -} - -// ============================================================================ -// LLTextureToolTip class -// - -LLTextureToolTip::LLTextureToolTip(const LLToolTip::Params& p) - : LLToolTip(p) - , mPreviewView(nullptr) - , mPreviewSize(256) -{ - mMaxWidth = llmax(mMaxWidth, mPreviewSize); - - // Currently has to share params with LLToolTip, override values - setBackgroundColor(LLColor4::black); - setTransparentColor(LLColor4::black); - setBorderVisible(true); -} - -LLTextureToolTip::~LLTextureToolTip() -{ -} - -void LLTextureToolTip::initFromParams(const LLToolTip::Params& p) -{ - LLToolTip::initFromParams(p); - - // Create and add the preview control - LLView::Params p_preview; - p_preview.name = "texture_preview"; - LLRect rctPreview; - rctPreview.setOriginAndSize(mPadding, mTextBox->getRect().mTop, mPreviewSize, mPreviewSize); - p_preview.rect = rctPreview; - mPreviewView = LLUICtrlFactory::create(p_preview); - addChild(mPreviewView); - - // Parse the control params - const LLSD& sdTextureParams = p.create_params; - if (sdTextureParams.has("thumbnail_id")) - { - mPreviewView->setImageFromAssetId(sdTextureParams["thumbnail_id"].asUUID()); - } - else if (sdTextureParams.has("item_id")) - { - mPreviewView->setImageFromItemId(sdTextureParams["item_id"].asUUID()); - } - - // Currently has to share params with LLToolTip, override values manually - // Todo: provide from own params instead, may be like object inspector does it - LLViewBorder::Params border_params; - border_params.border_thickness(LLPANEL_BORDER_WIDTH); - border_params.highlight_light_color(LLColor4::white); - border_params.highlight_dark_color(LLColor4::white); - border_params.shadow_light_color(LLColor4::white); - border_params.shadow_dark_color(LLColor4::white); - addBorder(border_params); - setBorderVisible(true); - - setBackgroundColor(LLColor4::black); - setBackgroundVisible(true); - setBackgroundOpaque(true); - setBackgroundImage(nullptr); - setTransparentImage(nullptr); - - mTextBox->setColor(LLColor4::white); - - snapToChildren(); -} - -// ============================================================================ +/** + * @file llinspecttexture.cpp + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llinspect.h" +#include "llinspecttexture.h" +#include "llinventoryfunctions.h" +#include "llinventorymodel.h" +#include "lltexturectrl.h" +#include "lltrans.h" +#include "llviewertexturelist.h" + + +// ============================================================================ +// Helper functions +// + +LLToolTip* LLInspectTextureUtil::createInventoryToolTip(LLToolTip::Params p) +{ + const LLSD& sdTooltip = p.create_params; + + if (sdTooltip.has("thumbnail_id") && sdTooltip["thumbnail_id"].asUUID().notNull()) + { + // go straight for thumbnail regardless of type + // TODO: make a tooltip factory? + return LLUICtrlFactory::create(p); + } + + LLInventoryType::EType eInvType = (sdTooltip.has("inv_type")) ? (LLInventoryType::EType)sdTooltip["inv_type"].asInteger() : LLInventoryType::IT_NONE; + switch (eInvType) + { + case LLInventoryType::IT_CATEGORY: + { + if (sdTooltip.has("item_id")) + { + const LLUUID idCategory = sdTooltip["item_id"].asUUID(); + LLViewerInventoryCategory* cat = gInventory.getCategory(idCategory); + if (cat && cat->getPreferredType() == LLFolderType::FT_OUTFIT) + { + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + // Not LLIsOfAssetType, because we allow links + LLIsTextureType f; + gInventory.getDirectDescendentsOf(idCategory, cats, items, f); + + // Exactly one texture found => show the texture tooltip + if (1 == items.size()) + { + LLViewerInventoryItem* item = items.front(); + if (item && item->getIsLinkType()) + { + item = item->getLinkedItem(); + } + if (item) + { + // Note: LLFloaterChangeItemThumbnail will attempt to write this + // into folder's thumbnail id when opened + p.create_params.getValue()["thumbnail_id"] = item->getAssetUUID(); + return LLUICtrlFactory::create(p); + } + } + } + } + if ((!p.message.isProvided() || p.message().empty())) + { + return NULL; + } + // No or more than one texture found => show default tooltip + return LLUICtrlFactory::create(p); + } + default: + return LLUICtrlFactory::create(p); + } +} + +// ============================================================================ +// LLTexturePreviewView helper class +// + +class LLTexturePreviewView : public LLView +{ +public: + LLTexturePreviewView(const LLView::Params& p); + ~LLTexturePreviewView(); + +public: + void draw() override; + +public: + void setImageFromAssetId(const LLUUID& idAsset); + void setImageFromItemId(const LLUUID& idItem); + +protected: + LLPointer m_Image; + S32 mImageBoostLevel = LLGLTexture::BOOST_NONE; + std::string mLoadingText; +}; + + +LLTexturePreviewView::LLTexturePreviewView(const LLView::Params& p) + : LLView(p) +{ + mLoadingText = LLTrans::getString("texture_loading"); +} + +LLTexturePreviewView::~LLTexturePreviewView() +{ + if (m_Image) + { + m_Image->setBoostLevel(mImageBoostLevel); + m_Image = nullptr; + } +} + +void LLTexturePreviewView::draw() +{ + LLView::draw(); + + if (m_Image) + { + LLRect rctClient = getLocalRect(); + + if (4 == m_Image->getComponents()) + { + const LLColor4 color(.098f, .098f, .098f); + gl_rect_2d(rctClient, color, true); + } + gl_draw_scaled_image(rctClient.mLeft, rctClient.mBottom, rctClient.getWidth(), rctClient.getHeight(), m_Image); + + bool isLoading = (!m_Image->isFullyLoaded()) && (m_Image->getDiscardLevel() > 0); + if (isLoading) + LLFontGL::getFontSansSerif()->renderUTF8(mLoadingText, 0, llfloor(rctClient.mLeft + 3), llfloor(rctClient.mTop - 25), LLColor4::white, LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::DROP_SHADOW); + m_Image->addTextureStats((isLoading) ? MAX_IMAGE_AREA : (F32)(rctClient.getWidth() * rctClient.getHeight())); + } +} + +void LLTexturePreviewView::setImageFromAssetId(const LLUUID& idAsset) +{ + m_Image = LLViewerTextureManager::getFetchedTexture(idAsset, FTT_DEFAULT, MIPMAP_TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + if (m_Image) + { + mImageBoostLevel = m_Image->getBoostLevel(); + m_Image->setBoostLevel(LLGLTexture::BOOST_PREVIEW); + m_Image->forceToSaveRawImage(0); + if ( (!m_Image->isFullyLoaded()) && (!m_Image->hasFetcher()) ) + { + if (m_Image->isInFastCacheList()) + { + m_Image->loadFromFastCache(); + } + gTextureList.forceImmediateUpdate(m_Image); + } + } +} + +void LLTexturePreviewView::setImageFromItemId(const LLUUID& idItem) +{ + const LLViewerInventoryItem* pItem = gInventory.getItem(idItem); + setImageFromAssetId( (pItem) ? pItem->getAssetUUID() : LLUUID::null ); +} + +// ============================================================================ +// LLTextureToolTip class +// + +LLTextureToolTip::LLTextureToolTip(const LLToolTip::Params& p) + : LLToolTip(p) + , mPreviewView(nullptr) + , mPreviewSize(256) +{ + mMaxWidth = llmax(mMaxWidth, mPreviewSize); + + // Currently has to share params with LLToolTip, override values + setBackgroundColor(LLColor4::black); + setTransparentColor(LLColor4::black); + setBorderVisible(true); +} + +LLTextureToolTip::~LLTextureToolTip() +{ +} + +void LLTextureToolTip::initFromParams(const LLToolTip::Params& p) +{ + LLToolTip::initFromParams(p); + + // Create and add the preview control + LLView::Params p_preview; + p_preview.name = "texture_preview"; + LLRect rctPreview; + rctPreview.setOriginAndSize(mPadding, mTextBox->getRect().mTop, mPreviewSize, mPreviewSize); + p_preview.rect = rctPreview; + mPreviewView = LLUICtrlFactory::create(p_preview); + addChild(mPreviewView); + + // Parse the control params + const LLSD& sdTextureParams = p.create_params; + if (sdTextureParams.has("thumbnail_id")) + { + mPreviewView->setImageFromAssetId(sdTextureParams["thumbnail_id"].asUUID()); + } + else if (sdTextureParams.has("item_id")) + { + mPreviewView->setImageFromItemId(sdTextureParams["item_id"].asUUID()); + } + + // Currently has to share params with LLToolTip, override values manually + // Todo: provide from own params instead, may be like object inspector does it + LLViewBorder::Params border_params; + border_params.border_thickness(LLPANEL_BORDER_WIDTH); + border_params.highlight_light_color(LLColor4::white); + border_params.highlight_dark_color(LLColor4::white); + border_params.shadow_light_color(LLColor4::white); + border_params.shadow_dark_color(LLColor4::white); + addBorder(border_params); + setBorderVisible(true); + + setBackgroundColor(LLColor4::black); + setBackgroundVisible(true); + setBackgroundOpaque(true); + setBackgroundImage(nullptr); + setTransparentImage(nullptr); + + mTextBox->setColor(LLColor4::white); + + snapToChildren(); +} + +// ============================================================================ diff --git a/indra/newview/llinspecttoast.cpp b/indra/newview/llinspecttoast.cpp index 14ea42e7f4..e3bf960c6d 100644 --- a/indra/newview/llinspecttoast.cpp +++ b/indra/newview/llinspecttoast.cpp @@ -1,152 +1,152 @@ -/** - * @file llinspecttoast.cpp - * @brief Toast inspector implementation. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" // must be first include - -#include "llinspecttoast.h" -#include "llinspect.h" -#include "llfloaterreg.h" -#include "llscreenchannel.h" -#include "llchannelmanager.h" -#include "lltransientfloatermgr.h" - -using namespace LLNotificationsUI; - -/** - * Represents inspectable toast . - */ -class LLInspectToast: public LLInspect -{ -public: - - LLInspectToast(const LLSD& notification_idl); - virtual ~LLInspectToast(); - - /*virtual*/ void onOpen(const LLSD& notification_id); - /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); - /*virtual*/ void deleteAllChildren(); - /*virtual*/ void removeChild(LLView* child); -private: - void onToastDestroy(LLToast * toast); - - boost::signals2::scoped_connection mConnection; - LLPanel* mPanel; - LLScreenChannel* mScreenChannel; -}; - -LLInspectToast::LLInspectToast(const LLSD& notification_id) : - LLInspect(LLSD()), mPanel(NULL) -{ - LLScreenChannelBase* channel = LLChannelManager::getInstance()->findChannelByID( - LLNotificationsUI::NOTIFICATION_CHANNEL_UUID); - mScreenChannel = dynamic_cast(channel); - if(NULL == mScreenChannel) - { - LL_WARNS() << "Could not get requested screen channel." << LL_ENDL; - return; - } - - LLTransientFloaterMgr::getInstance()->addControlView(this); -} -LLInspectToast::~LLInspectToast() -{ - LLTransientFloaterMgr::getInstance()->removeControlView(this); - - mConnection.disconnect(); -} - -// virtual -void LLInspectToast::onOpen(const LLSD& notification_id) -{ - LLInspect::onOpen(notification_id); - LLToast* toast = mScreenChannel->getToastByNotificationID(notification_id); - if (toast == NULL) - { - LL_WARNS() << "Could not get requested toast from screen channel." << LL_ENDL; - return; - } - mConnection = toast->setOnToastDestroyedCallback(boost::bind(&LLInspectToast::onToastDestroy, this, _1)); - - LLPanel * panel = toast->getPanel(); - if (panel == NULL) - { - LL_WARNS() << "Could not get toast's panel." << LL_ENDL; - return; - } - panel->setVisible(true); - panel->setMouseOpaque(false); - if(mPanel != NULL && mPanel->getParent() == this) - { - LLInspect::removeChild(mPanel); - } - addChild(panel); - panel->setFocus(true); - mPanel = panel; - - - LLRect panel_rect; - panel_rect = panel->getRect(); - reshape(panel_rect.getWidth(), panel_rect.getHeight()); - - LLInspect::repositionInspector(notification_id); -} - -// virtual -bool LLInspectToast::handleToolTip(S32 x, S32 y, MASK mask) -{ - // We don't like the way LLInspect handles tooltips - // (black tooltips look weird), - // so force using the default implementation (STORM-511). - return LLFloater::handleToolTip(x, y, mask); -} - -void LLInspectToast::deleteAllChildren() -{ - mPanel = NULL; - LLInspect::deleteAllChildren(); -} - -// virtual -void LLInspectToast::removeChild(LLView* child) -{ - if (mPanel == child) - { - mPanel = NULL; - } - LLInspect::removeChild(child); -} - -void LLInspectToast::onToastDestroy(LLToast * toast) -{ - closeFloater(false); -} - -void LLNotificationsUI::registerFloater() -{ - LLFloaterReg::add("inspect_toast", "inspect_toast.xml", - &LLFloaterReg::build); -} - +/** + * @file llinspecttoast.cpp + * @brief Toast inspector implementation. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" // must be first include + +#include "llinspecttoast.h" +#include "llinspect.h" +#include "llfloaterreg.h" +#include "llscreenchannel.h" +#include "llchannelmanager.h" +#include "lltransientfloatermgr.h" + +using namespace LLNotificationsUI; + +/** + * Represents inspectable toast . + */ +class LLInspectToast: public LLInspect +{ +public: + + LLInspectToast(const LLSD& notification_idl); + virtual ~LLInspectToast(); + + /*virtual*/ void onOpen(const LLSD& notification_id); + /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); + /*virtual*/ void deleteAllChildren(); + /*virtual*/ void removeChild(LLView* child); +private: + void onToastDestroy(LLToast * toast); + + boost::signals2::scoped_connection mConnection; + LLPanel* mPanel; + LLScreenChannel* mScreenChannel; +}; + +LLInspectToast::LLInspectToast(const LLSD& notification_id) : + LLInspect(LLSD()), mPanel(NULL) +{ + LLScreenChannelBase* channel = LLChannelManager::getInstance()->findChannelByID( + LLNotificationsUI::NOTIFICATION_CHANNEL_UUID); + mScreenChannel = dynamic_cast(channel); + if(NULL == mScreenChannel) + { + LL_WARNS() << "Could not get requested screen channel." << LL_ENDL; + return; + } + + LLTransientFloaterMgr::getInstance()->addControlView(this); +} +LLInspectToast::~LLInspectToast() +{ + LLTransientFloaterMgr::getInstance()->removeControlView(this); + + mConnection.disconnect(); +} + +// virtual +void LLInspectToast::onOpen(const LLSD& notification_id) +{ + LLInspect::onOpen(notification_id); + LLToast* toast = mScreenChannel->getToastByNotificationID(notification_id); + if (toast == NULL) + { + LL_WARNS() << "Could not get requested toast from screen channel." << LL_ENDL; + return; + } + mConnection = toast->setOnToastDestroyedCallback(boost::bind(&LLInspectToast::onToastDestroy, this, _1)); + + LLPanel * panel = toast->getPanel(); + if (panel == NULL) + { + LL_WARNS() << "Could not get toast's panel." << LL_ENDL; + return; + } + panel->setVisible(true); + panel->setMouseOpaque(false); + if(mPanel != NULL && mPanel->getParent() == this) + { + LLInspect::removeChild(mPanel); + } + addChild(panel); + panel->setFocus(true); + mPanel = panel; + + + LLRect panel_rect; + panel_rect = panel->getRect(); + reshape(panel_rect.getWidth(), panel_rect.getHeight()); + + LLInspect::repositionInspector(notification_id); +} + +// virtual +bool LLInspectToast::handleToolTip(S32 x, S32 y, MASK mask) +{ + // We don't like the way LLInspect handles tooltips + // (black tooltips look weird), + // so force using the default implementation (STORM-511). + return LLFloater::handleToolTip(x, y, mask); +} + +void LLInspectToast::deleteAllChildren() +{ + mPanel = NULL; + LLInspect::deleteAllChildren(); +} + +// virtual +void LLInspectToast::removeChild(LLView* child) +{ + if (mPanel == child) + { + mPanel = NULL; + } + LLInspect::removeChild(child); +} + +void LLInspectToast::onToastDestroy(LLToast * toast) +{ + closeFloater(false); +} + +void LLNotificationsUI::registerFloater() +{ + LLFloaterReg::add("inspect_toast", "inspect_toast.xml", + &LLFloaterReg::build); +} + diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index e202547a67..76323be3dc 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -1,8262 +1,8262 @@ -/** - * @file llinventorybridge.cpp - * @brief Implementation of the Inventory-Folder-View-Bridge classes. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llinventorybridge.h" - -// external projects -#include "lltransfersourceasset.h" -#include "llavatarnamecache.h" // IDEVO - -#include "llagent.h" -#include "llagentcamera.h" -#include "llagentwearables.h" -#include "llappearancemgr.h" -#include "llattachmentsmgr.h" -#include "llavataractions.h" -#include "llfavoritesbar.h" // management of favorites folder -#include "llfloateropenobject.h" -#include "llfloaterreg.h" -#include "llfloatermarketplacelistings.h" -#include "llfloatersidepanelcontainer.h" -#include "llsidepanelinventory.h" -#include "llfloaterworldmap.h" -#include "llfolderview.h" -#include "llfriendcard.h" -#include "llgesturemgr.h" -#include "llgiveinventory.h" -#include "llfloaterimcontainer.h" -#include "llimview.h" -#include "llclipboard.h" -#include "llinventorydefines.h" -#include "llinventoryfunctions.h" -#include "llinventoryicon.h" -#include "llinventorymodel.h" -#include "llinventorymodelbackgroundfetch.h" -#include "llinventorypanel.h" -#include "llmarketplacefunctions.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "llpreviewanim.h" -#include "llpreviewgesture.h" -#include "llpreviewtexture.h" -#include "llselectmgr.h" -#include "llsidepanelappearance.h" -#include "lltooldraganddrop.h" -#include "lltrans.h" -#include "llurlaction.h" -#include "llviewerassettype.h" -#include "llviewerfoldertype.h" -#include "llviewermenu.h" -#include "llviewermessage.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llviewerwindow.h" -#include "llvoavatarself.h" -#include "llwearablelist.h" -#include "llwearableitemslist.h" -#include "lllandmarkactions.h" -#include "llpanellandmarks.h" -#include "llviewerparcelmgr.h" -#include "llparcel.h" - -#include "llenvironment.h" - -#include - -void copy_slurl_to_clipboard_callback_inv(const std::string& slurl); - -const F32 SOUND_GAIN = 1.0f; -const F32 FOLDER_LOADING_MESSAGE_DELAY = 0.5f; // Seconds to wait before showing the LOADING... text in folder views - -using namespace LLOldEvents; - -// Function declarations -bool confirm_attachment_rez(const LLSD& notification, const LLSD& response); -void teleport_via_landmark(const LLUUID& asset_id); -static bool check_category(LLInventoryModel* model, - const LLUUID& cat_id, - LLInventoryPanel* active_panel, - LLInventoryFilter* filter); -static bool check_item(const LLUUID& item_id, - LLInventoryPanel* active_panel, - LLInventoryFilter* filter); - -// Helper functions - -bool isAddAction(const std::string& action) -{ - return ("wear" == action || "attach" == action || "activate" == action); -} - -bool isRemoveAction(const std::string& action) -{ - return ("take_off" == action || "detach" == action); -} - -bool isMarketplaceSendAction(const std::string& action) -{ - return ("send_to_marketplace" == action); -} - -bool isPanelActive(const std::string& panel_name) -{ - LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(false); - return (active_panel && (active_panel->getName() == panel_name)); -} - -// Used by LLFolderBridge as callback for directory fetching recursion -class LLRightClickInventoryFetchDescendentsObserver : public LLInventoryFetchDescendentsObserver -{ -public: - LLRightClickInventoryFetchDescendentsObserver(const uuid_vec_t& ids) : LLInventoryFetchDescendentsObserver(ids) {} - ~LLRightClickInventoryFetchDescendentsObserver() {} - virtual void execute(bool clear_observer = false); - virtual void done() - { - execute(true); - } -}; - -// Used by LLFolderBridge as callback for directory content items fetching -class LLRightClickInventoryFetchObserver : public LLInventoryFetchItemsObserver -{ -public: - LLRightClickInventoryFetchObserver(const uuid_vec_t& ids) : LLInventoryFetchItemsObserver(ids) { }; - ~LLRightClickInventoryFetchObserver() {} - void execute(bool clear_observer = false) - { - if (clear_observer) - { - gInventory.removeObserver(this); - delete this; - } - // we've downloaded all the items, so repaint the dialog - LLFolderBridge::staticFolderOptionsMenu(); - } - virtual void done() - { - execute(true); - } -}; - -class LLPasteIntoFolderCallback: public LLInventoryCallback -{ -public: - LLPasteIntoFolderCallback(LLHandle& handle) - : mInventoryPanel(handle) - { - } - ~LLPasteIntoFolderCallback() - { - processItems(); - } - - void fire(const LLUUID& inv_item) - { - mChangedIds.push_back(inv_item); - } - - void processItems() - { - LLInventoryPanel* panel = mInventoryPanel.get(); - bool has_elements = false; - for (LLUUID& inv_item : mChangedIds) - { - LLInventoryItem* item = gInventory.getItem(inv_item); - if (item && panel) - { - LLUUID root_id = panel->getRootFolderID(); - - if (inv_item == root_id) - { - return; - } - - LLFolderViewItem* item = panel->getItemByID(inv_item); - if (item) - { - if (!has_elements) - { - panel->clearSelection(); - panel->getRootFolder()->clearSelection(); - panel->getRootFolder()->requestArrange(); - panel->getRootFolder()->update(); - has_elements = true; - } - panel->getRootFolder()->changeSelection(item, true); - } - } - } - - if (has_elements) - { - panel->getRootFolder()->scrollToShowSelection(); - } - } -private: - LLHandle mInventoryPanel; - std::vector mChangedIds; -}; - -// +=================================================+ -// | LLInvFVBridge | -// +=================================================+ - -LLInvFVBridge::LLInvFVBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid) : - mUUID(uuid), - mRoot(root), - mInvType(LLInventoryType::IT_NONE), - mIsLink(false), - LLFolderViewModelItemInventory(inventory->getRootViewModel()) -{ - mInventoryPanel = inventory->getInventoryPanelHandle(); - const LLInventoryObject* obj = getInventoryObject(); - mIsLink = obj && obj->getIsLinkType(); -} - -const std::string& LLInvFVBridge::getName() const -{ - const LLInventoryObject* obj = getInventoryObject(); - if(obj) - { - return obj->getName(); - } - return LLStringUtil::null; -} - -const std::string& LLInvFVBridge::getDisplayName() const -{ - if(mDisplayName.empty()) - { - buildDisplayName(); - } - return mDisplayName; -} - -std::string LLInvFVBridge::getSearchableDescription() const -{ - return get_searchable_description(getInventoryModel(), mUUID); -} - -std::string LLInvFVBridge::getSearchableCreatorName() const -{ - return get_searchable_creator_name(getInventoryModel(), mUUID); -} - -std::string LLInvFVBridge::getSearchableUUIDString() const -{ - return get_searchable_UUID(getInventoryModel(), mUUID); -} - -// Folders have full perms -PermissionMask LLInvFVBridge::getPermissionMask() const -{ - return PERM_ALL; -} - -// virtual -LLFolderType::EType LLInvFVBridge::getPreferredType() const -{ - return LLFolderType::FT_NONE; -} - - -// Folders don't have creation dates. -time_t LLInvFVBridge::getCreationDate() const -{ - LLInventoryObject* objectp = getInventoryObject(); - if (objectp) - { - return objectp->getCreationDate(); - } - return (time_t)0; -} - -void LLInvFVBridge::setCreationDate(time_t creation_date_utc) -{ - LLInventoryObject* objectp = getInventoryObject(); - if (objectp) - { - objectp->setCreationDate(creation_date_utc); - } -} - - -// Can be destroyed (or moved to trash) -bool LLInvFVBridge::isItemRemovable(bool check_worn) const -{ - return get_is_item_removable(getInventoryModel(), mUUID, check_worn); -} - -// Can be moved to another folder -bool LLInvFVBridge::isItemMovable() const -{ - return true; -} - -bool LLInvFVBridge::isLink() const -{ - return mIsLink; -} - -bool LLInvFVBridge::isLibraryItem() const -{ - return gInventory.isObjectDescendentOf(getUUID(),gInventory.getLibraryRootFolderID()); -} - -/*virtual*/ -/** - * @brief Adds this item into clipboard storage - */ -bool LLInvFVBridge::cutToClipboard() -{ - const LLInventoryObject* obj = gInventory.getObject(mUUID); - if (obj && isItemMovable() && isItemRemovable()) - { - const LLUUID &marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - const bool cut_from_marketplacelistings = gInventory.isObjectDescendentOf(mUUID, marketplacelistings_id); - - if (cut_from_marketplacelistings && (LLMarketplaceData::instance().isInActiveFolder(mUUID) || - LLMarketplaceData::instance().isListedAndActive(mUUID))) - { - LLUUID parent_uuid = obj->getParentUUID(); - bool result = perform_cutToClipboard(); - gInventory.addChangedMask(LLInventoryObserver::STRUCTURE, parent_uuid); - return result; - } - else - { - // Otherwise just perform the cut - return perform_cutToClipboard(); - } - } - return false; -} - -// virtual -bool LLInvFVBridge::isCutToClipboard() -{ - if (LLClipboard::instance().isCutMode()) - { - return LLClipboard::instance().isOnClipboard(mUUID); - } - return false; -} - -// Callback for cutToClipboard if DAMA required... -bool LLInvFVBridge::callback_cutToClipboard(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) // YES - { - return perform_cutToClipboard(); - } - return false; -} - -bool LLInvFVBridge::perform_cutToClipboard() -{ - const LLInventoryObject* obj = gInventory.getObject(mUUID); - if (obj && isItemMovable() && isItemRemovable()) - { - LLClipboard::instance().setCutMode(true); - return LLClipboard::instance().addToClipboard(mUUID); - } - return false; -} - -bool LLInvFVBridge::copyToClipboard() const -{ - const LLInventoryObject* obj = gInventory.getObject(mUUID); - if (obj && isItemCopyable()) - { - return LLClipboard::instance().addToClipboard(mUUID); - } - return false; -} - -void LLInvFVBridge::showProperties() -{ - if (isMarketplaceListingsFolder()) - { - LLFloaterReg::showInstance("item_properties", LLSD().with("id",mUUID),true); - // Force it to show on top as this floater has a tendency to hide when confirmation dialog shows up - LLFloater* floater_properties = LLFloaterReg::findInstance("item_properties", LLSD().with("id",mUUID)); - if (floater_properties) - { - floater_properties->setVisibleAndFrontmost(); - } - } - else - { - show_item_profile(mUUID); - } -} - -void LLInvFVBridge::navigateToFolder(bool new_window, bool change_mode) -{ - if(new_window) - { - mInventoryPanel.get()->openSingleViewInventory(mUUID); - } - else - { - if(change_mode) - { - LLInventoryPanel::setSFViewAndOpenFolder(mInventoryPanel.get(), mUUID); - } - else - { - LLInventorySingleFolderPanel* panel = dynamic_cast(mInventoryPanel.get()); - if (!panel || !getInventoryModel() || mUUID.isNull()) - { - return; - } - - panel->changeFolderRoot(mUUID); - } - - } -} - -void LLInvFVBridge::removeBatch(std::vector& batch) -{ - // Deactivate gestures when moving them into Trash - LLInvFVBridge* bridge; - LLInventoryModel* model = getInventoryModel(); - LLViewerInventoryItem* item = NULL; - LLViewerInventoryCategory* cat = NULL; - LLInventoryModel::cat_array_t descendent_categories; - LLInventoryModel::item_array_t descendent_items; - S32 count = batch.size(); - S32 i,j; - for(i = 0; i < count; ++i) - { - bridge = (LLInvFVBridge*)(batch[i]); - if(!bridge || !bridge->isItemRemovable()) continue; - item = (LLViewerInventoryItem*)model->getItem(bridge->getUUID()); - if (item) - { - if(LLAssetType::AT_GESTURE == item->getType()) - { - LLGestureMgr::instance().deactivateGesture(item->getUUID()); - } - } - } - for(i = 0; i < count; ++i) - { - bridge = (LLInvFVBridge*)(batch[i]); - if(!bridge || !bridge->isItemRemovable()) continue; - cat = (LLViewerInventoryCategory*)model->getCategory(bridge->getUUID()); - if (cat) - { - gInventory.collectDescendents( cat->getUUID(), descendent_categories, descendent_items, false ); - for (j=0; jgetType()) - { - LLGestureMgr::instance().deactivateGesture(descendent_items[j]->getUUID()); - } - } - } - } - removeBatchNoCheck(batch); - model->checkTrashOverflow(); -} - -void LLInvFVBridge::removeBatchNoCheck(std::vector& batch) -{ - // this method moves a bunch of items and folders to the trash. As - // per design guidelines for the inventory model, the message is - // built and the accounting is performed first. After all of that, - // we call LLInventoryModel::moveObject() to move everything - // around. - LLInvFVBridge* bridge; - LLInventoryModel* model = getInventoryModel(); - if(!model) return; - LLMessageSystem* msg = gMessageSystem; - const LLUUID trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); - LLViewerInventoryItem* item = NULL; - uuid_vec_t move_ids; - LLInventoryModel::update_map_t update; - bool start_new_message = true; - S32 count = batch.size(); - S32 i; - - // first, hide any 'preview' floaters that correspond to the items - // being deleted. - for(i = 0; i < count; ++i) - { - bridge = (LLInvFVBridge*)(batch[i]); - if(!bridge || !bridge->isItemRemovable()) continue; - item = (LLViewerInventoryItem*)model->getItem(bridge->getUUID()); - if(item) - { - LLPreview::hide(item->getUUID()); - } - } - - // do the inventory move to trash - - for(i = 0; i < count; ++i) - { - bridge = (LLInvFVBridge*)(batch[i]); - if(!bridge || !bridge->isItemRemovable()) continue; - item = (LLViewerInventoryItem*)model->getItem(bridge->getUUID()); - if(item) - { - if(item->getParentUUID() == trash_id) continue; - move_ids.push_back(item->getUUID()); - --update[item->getParentUUID()]; - ++update[trash_id]; - if(start_new_message) - { - start_new_message = false; - msg->newMessageFast(_PREHASH_MoveInventoryItem); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addBOOLFast(_PREHASH_Stamp, true); - } - msg->nextBlockFast(_PREHASH_InventoryData); - msg->addUUIDFast(_PREHASH_ItemID, item->getUUID()); - msg->addUUIDFast(_PREHASH_FolderID, trash_id); - msg->addString("NewName", NULL); - if(msg->isSendFullFast(_PREHASH_InventoryData)) - { - start_new_message = true; - gAgent.sendReliableMessage(); - gInventory.accountForUpdate(update); - update.clear(); - } - } - } - if(!start_new_message) - { - start_new_message = true; - gAgent.sendReliableMessage(); - gInventory.accountForUpdate(update); - update.clear(); - } - - for(i = 0; i < count; ++i) - { - bridge = (LLInvFVBridge*)(batch[i]); - if(!bridge || !bridge->isItemRemovable()) continue; - LLViewerInventoryCategory* cat = (LLViewerInventoryCategory*)model->getCategory(bridge->getUUID()); - if(cat) - { - if(cat->getParentUUID() == trash_id) continue; - move_ids.push_back(cat->getUUID()); - --update[cat->getParentUUID()]; - ++update[trash_id]; - if(start_new_message) - { - start_new_message = false; - msg->newMessageFast(_PREHASH_MoveInventoryFolder); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addBOOL("Stamp", true); - } - msg->nextBlockFast(_PREHASH_InventoryData); - msg->addUUIDFast(_PREHASH_FolderID, cat->getUUID()); - msg->addUUIDFast(_PREHASH_ParentID, trash_id); - if(msg->isSendFullFast(_PREHASH_InventoryData)) - { - start_new_message = true; - gAgent.sendReliableMessage(); - gInventory.accountForUpdate(update); - update.clear(); - } - } - } - if(!start_new_message) - { - gAgent.sendReliableMessage(); - gInventory.accountForUpdate(update); - } - - // move everything. - uuid_vec_t::iterator it = move_ids.begin(); - uuid_vec_t::iterator end = move_ids.end(); - for(; it != end; ++it) - { - gInventory.moveObject((*it), trash_id); - LLViewerInventoryItem* item = gInventory.getItem(*it); - if (item) - { - model->updateItem(item); - } - } - - // notify inventory observers. - model->notifyObservers(); -} - -bool LLInvFVBridge::isClipboardPasteable() const -{ - // Return false on degenerated cases: empty clipboard, no inventory, no agent - if (!LLClipboard::instance().hasContents() || !isAgentInventory()) - { - return false; - } - LLInventoryModel* model = getInventoryModel(); - if (!model) - { - return false; - } - - // In cut mode, whatever is on the clipboard is always pastable - if (LLClipboard::instance().isCutMode()) - { - return true; - } - - // In normal mode, we need to check each element of the clipboard to know if we can paste or not - std::vector objects; - LLClipboard::instance().pasteFromClipboard(objects); - S32 count = objects.size(); - for(S32 i = 0; i < count; i++) - { - const LLUUID &item_id = objects.at(i); - - // Folders are pastable if all items in there are copyable - const LLInventoryCategory *cat = model->getCategory(item_id); - if (cat) - { - LLFolderBridge cat_br(mInventoryPanel.get(), mRoot, item_id); - if (!cat_br.isItemCopyable(false)) - return false; - // Skip to the next item in the clipboard - continue; - } - - // Each item must be copyable to be pastable - LLItemBridge item_br(mInventoryPanel.get(), mRoot, item_id); - if (!item_br.isItemCopyable(false)) - { - return false; - } - } - return true; -} - -bool LLInvFVBridge::isClipboardPasteableAsLink() const -{ - if (!LLClipboard::instance().hasContents() || !isAgentInventory()) - { - return false; - } - const LLInventoryModel* model = getInventoryModel(); - if (!model) - { - return false; - } - - std::vector objects; - LLClipboard::instance().pasteFromClipboard(objects); - S32 count = objects.size(); - for(S32 i = 0; i < count; i++) - { - const LLInventoryItem *item = model->getItem(objects.at(i)); - if (item) - { - if (!LLAssetType::lookupCanLink(item->getActualType())) - { - return false; - } - - if (gInventory.isObjectDescendentOf(item->getUUID(), gInventory.getLibraryRootFolderID())) - { - return false; - } - } - const LLViewerInventoryCategory *cat = model->getCategory(objects.at(i)); - if (cat && LLFolderType::lookupIsProtectedType(cat->getPreferredType())) - { - return false; - } - } - return true; -} - -void disable_context_entries_if_present(LLMenuGL& menu, - const menuentry_vec_t &disabled_entries) -{ - const LLView::child_list_t *list = menu.getChildList(); - for (LLView::child_list_t::const_iterator itor = list->begin(); - itor != list->end(); - ++itor) - { - LLView *menu_item = (*itor); - std::string name = menu_item->getName(); - - // descend into split menus: - LLMenuItemBranchGL* branchp = dynamic_cast(menu_item); - if ((name == "More") && branchp) - { - disable_context_entries_if_present(*branchp->getBranch(), disabled_entries); - } - - bool found = false; - menuentry_vec_t::const_iterator itor2; - for (itor2 = disabled_entries.begin(); itor2 != disabled_entries.end(); ++itor2) - { - if (*itor2 == name) - { - found = true; - break; - } - } - - if (found) - { - menu_item->setVisible(true); - // A bit of a hack so we can remember that some UI element explicitly set this to be visible - // so that some other UI element from multi-select doesn't later set this invisible. - menu_item->pushVisible(true); - - menu_item->setEnabled(false); - } - } -} -void hide_context_entries(LLMenuGL& menu, - const menuentry_vec_t &entries_to_show, - const menuentry_vec_t &disabled_entries) -{ - const LLView::child_list_t *list = menu.getChildList(); - - // For removing double separators or leading separator. Start at true so that - // if the first element is a separator, it will not be shown. - bool is_previous_entry_separator = true; - - for (LLView::child_list_t::const_iterator itor = list->begin(); - itor != list->end(); - ++itor) - { - LLView *menu_item = (*itor); - std::string name = menu_item->getName(); - - // descend into split menus: - LLMenuItemBranchGL* branchp = dynamic_cast(menu_item); - if (((name == "More") || (name == "create_new")) && branchp) - { - hide_context_entries(*branchp->getBranch(), entries_to_show, disabled_entries); - } - - bool found = false; - - menuentry_vec_t::const_iterator itor2 = std::find(entries_to_show.begin(), entries_to_show.end(), name); - if (itor2 != entries_to_show.end()) - { - found = true; - } - - // Don't allow multiple separators in a row (e.g. such as if there are no items - // between two separators). - if (found) - { - const bool is_entry_separator = (dynamic_cast(menu_item) != NULL); - found = !(is_entry_separator && is_previous_entry_separator); - is_previous_entry_separator = is_entry_separator; - } - - if (!found) - { - if (!menu_item->getLastVisible()) - { - menu_item->setVisible(false); - } - - if (menu_item->getEnabled()) - { - // These should stay enabled unless specifically disabled - const menuentry_vec_t exceptions = { - "Detach From Yourself", - "Wearable And Object Wear", - "Wearable Add", - }; - - menuentry_vec_t::const_iterator itor2 = std::find(exceptions.begin(), exceptions.end(), name); - if (itor2 == exceptions.end()) - { - menu_item->setEnabled(false); - } - } - } - else - { - menu_item->setVisible(true); - // A bit of a hack so we can remember that some UI element explicitly set this to be visible - // so that some other UI element from multi-select doesn't later set this invisible. - menu_item->pushVisible(true); - - bool enabled = true; - for (itor2 = disabled_entries.begin(); enabled && (itor2 != disabled_entries.end()); ++itor2) - { - enabled &= (*itor2 != name); - } - - menu_item->setEnabled(enabled); - } - } -} - -// Helper for commonly-used entries -void LLInvFVBridge::getClipboardEntries(bool show_asset_id, - menuentry_vec_t &items, - menuentry_vec_t &disabled_items, U32 flags) -{ - const LLInventoryObject *obj = getInventoryObject(); - bool single_folder_root = (mRoot == NULL); - - if (obj) - { - - if (obj->getType() != LLAssetType::AT_CATEGORY) - { - items.push_back(std::string("Copy Separator")); - } - items.push_back(std::string("Copy")); - if (!isItemCopyable()) - { - disabled_items.push_back(std::string("Copy")); - } - - if (isAgentInventory() && !single_folder_root) - { - items.push_back(std::string("New folder from selected")); - items.push_back(std::string("Subfolder Separator")); - std::set selected_uuid_set = LLAvatarActions::getInventorySelectedUUIDs(); - uuid_vec_t ids; - std::copy(selected_uuid_set.begin(), selected_uuid_set.end(), std::back_inserter(ids)); - if (!is_only_items_selected(ids) && !is_only_cats_selected(ids)) - { - disabled_items.push_back(std::string("New folder from selected")); - } - } - - if (obj->getIsLinkType()) - { - items.push_back(std::string("Find Original")); - if (isLinkedObjectMissing()) - { - disabled_items.push_back(std::string("Find Original")); - } - - items.push_back(std::string("Cut")); - if (!isItemMovable() || !canMenuCut()) - { - disabled_items.push_back(std::string("Cut")); - } - } - else - { - if (LLAssetType::lookupCanLink(obj->getType())) - { - items.push_back(std::string("Find Links")); - } - - if (!isInboxFolder() && !single_folder_root) - { - items.push_back(std::string("Rename")); - if (!isItemRenameable() || ((flags & FIRST_SELECTED_ITEM) == 0)) - { - disabled_items.push_back(std::string("Rename")); - } - } - - items.push_back(std::string("thumbnail")); - if (isLibraryItem()) - { - disabled_items.push_back(std::string("thumbnail")); - } - - LLViewerInventoryItem *inv_item = gInventory.getItem(mUUID); - if (show_asset_id) - { - items.push_back(std::string("Copy Asset UUID")); - - bool is_asset_knowable = false; - - if (inv_item) - { - is_asset_knowable = LLAssetType::lookupIsAssetIDKnowable(inv_item->getType()); - } - if ( !is_asset_knowable // disable menu item for Inventory items with unknown asset. EXT-5308 - || (! ( isItemPermissive() || gAgent.isGodlike() ) ) - || (flags & FIRST_SELECTED_ITEM) == 0) - { - disabled_items.push_back(std::string("Copy Asset UUID")); - } - } - - if(!single_folder_root) - { - items.push_back(std::string("Cut")); - if (!isItemMovable() || !canMenuCut()) - { - disabled_items.push_back(std::string("Cut")); - } - - if (canListOnMarketplace() && !isMarketplaceListingsFolder() && !isInboxFolder()) - { - items.push_back(std::string("Marketplace Separator")); - - if (gMenuHolder->getChild("MarketplaceListings")->getVisible()) - { - items.push_back(std::string("Marketplace Copy")); - items.push_back(std::string("Marketplace Move")); - if (!canListOnMarketplaceNow()) - { - disabled_items.push_back(std::string("Marketplace Copy")); - disabled_items.push_back(std::string("Marketplace Move")); - } - } - } - } - } - } - - // Don't allow items to be pasted directly into the COF or the inbox - if (!isCOFFolder() && !isInboxFolder()) - { - items.push_back(std::string("Paste")); - } - if (!isClipboardPasteable() || ((flags & FIRST_SELECTED_ITEM) == 0)) - { - disabled_items.push_back(std::string("Paste")); - } - - static LLCachedControl inventory_linking(gSavedSettings, "InventoryLinking", true); - if (inventory_linking) - { - items.push_back(std::string("Paste As Link")); - if (!isClipboardPasteableAsLink() || (flags & FIRST_SELECTED_ITEM) == 0) - { - disabled_items.push_back(std::string("Paste As Link")); - } - } - - if (obj->getType() != LLAssetType::AT_CATEGORY) - { - items.push_back(std::string("Paste Separator")); - } - - if(!single_folder_root) - { - addDeleteContextMenuOptions(items, disabled_items); - } - - if (!isPanelActive("All Items") && !isPanelActive("comb_single_folder_inv")) - { - items.push_back(std::string("Show in Main Panel")); - } -} - -void LLInvFVBridge::buildContextMenu(LLMenuGL& menu, U32 flags) -{ - LL_DEBUGS() << "LLInvFVBridge::buildContextMenu()" << LL_ENDL; - menuentry_vec_t items; - menuentry_vec_t disabled_items; - if(isItemInTrash()) - { - addTrashContextMenuOptions(items, disabled_items); - } - else - { - items.push_back(std::string("Share")); - if (!canShare()) - { - disabled_items.push_back(std::string("Share")); - } - - addOpenRightClickMenuOption(items); - items.push_back(std::string("Properties")); - - getClipboardEntries(true, items, disabled_items, flags); - } - addLinkReplaceMenuOption(items, disabled_items); - hide_context_entries(menu, items, disabled_items); -} - -bool get_selection_item_uuids(LLFolderView::selected_items_t& selected_items, uuid_vec_t& ids) -{ - uuid_vec_t results; - S32 non_item = 0; - for(LLFolderView::selected_items_t::iterator it = selected_items.begin(); it != selected_items.end(); ++it) - { - LLItemBridge *view_model = dynamic_cast((*it)->getViewModelItem()); - - if(view_model && view_model->getUUID().notNull()) - { - results.push_back(view_model->getUUID()); - } - else - { - non_item++; - } - } - if (non_item == 0) - { - ids = results; - return true; - } - return false; -} - -void LLInvFVBridge::addTrashContextMenuOptions(menuentry_vec_t &items, - menuentry_vec_t &disabled_items) -{ - const LLInventoryObject *obj = getInventoryObject(); - if (obj && obj->getIsLinkType()) - { - items.push_back(std::string("Find Original")); - if (isLinkedObjectMissing()) - { - disabled_items.push_back(std::string("Find Original")); - } - } - items.push_back(std::string("Purge Item")); - if (!isItemRemovable()) - { - disabled_items.push_back(std::string("Purge Item")); - } - items.push_back(std::string("Restore Item")); -} - -void LLInvFVBridge::addDeleteContextMenuOptions(menuentry_vec_t &items, - menuentry_vec_t &disabled_items) -{ - - const LLInventoryObject *obj = getInventoryObject(); - - // Don't allow delete as a direct option from COF folder. - if (obj && obj->getIsLinkType() && isCOFFolder() && get_is_item_worn(mUUID)) - { - return; - } - - items.push_back(std::string("Delete")); - - if (isPanelActive("Favorite Items") || !canMenuDelete()) - { - disabled_items.push_back(std::string("Delete")); - } -} - -void LLInvFVBridge::addOpenRightClickMenuOption(menuentry_vec_t &items) -{ - const LLInventoryObject *obj = getInventoryObject(); - const bool is_link = (obj && obj->getIsLinkType()); - - if (is_link) - items.push_back(std::string("Open Original")); - else - items.push_back(std::string("Open")); -} - -void LLInvFVBridge::addMarketplaceContextMenuOptions(U32 flags, - menuentry_vec_t &items, - menuentry_vec_t &disabled_items) -{ - S32 depth = depth_nesting_in_marketplace(mUUID); - if (depth == 1) - { - // Options available at the Listing Folder level - items.push_back(std::string("Marketplace Create Listing")); - items.push_back(std::string("Marketplace Associate Listing")); - items.push_back(std::string("Marketplace Check Listing")); - items.push_back(std::string("Marketplace List")); - items.push_back(std::string("Marketplace Unlist")); - if (LLMarketplaceData::instance().isUpdating(mUUID,depth) || ((flags & FIRST_SELECTED_ITEM) == 0)) - { - // During SLM update, disable all marketplace related options - // Also disable all if multiple selected items - disabled_items.push_back(std::string("Marketplace Create Listing")); - disabled_items.push_back(std::string("Marketplace Associate Listing")); - disabled_items.push_back(std::string("Marketplace Check Listing")); - disabled_items.push_back(std::string("Marketplace List")); - disabled_items.push_back(std::string("Marketplace Unlist")); - } - else - { - if (gSavedSettings.getBOOL("MarketplaceListingsLogging")) - { - items.push_back(std::string("Marketplace Get Listing")); - } - if (LLMarketplaceData::instance().isListed(mUUID)) - { - disabled_items.push_back(std::string("Marketplace Create Listing")); - disabled_items.push_back(std::string("Marketplace Associate Listing")); - if (LLMarketplaceData::instance().getVersionFolder(mUUID).isNull()) - { - disabled_items.push_back(std::string("Marketplace List")); - disabled_items.push_back(std::string("Marketplace Unlist")); - } - else - { - if (LLMarketplaceData::instance().getActivationState(mUUID)) - { - disabled_items.push_back(std::string("Marketplace List")); - } - else - { - disabled_items.push_back(std::string("Marketplace Unlist")); - } - } - } - else - { - disabled_items.push_back(std::string("Marketplace List")); - disabled_items.push_back(std::string("Marketplace Unlist")); - if (gSavedSettings.getBOOL("MarketplaceListingsLogging")) - { - disabled_items.push_back(std::string("Marketplace Get Listing")); - } - } - } - } - if (depth == 2) - { - // Options available at the Version Folder levels and only for folders - LLInventoryCategory* cat = gInventory.getCategory(mUUID); - if (cat && LLMarketplaceData::instance().isListed(cat->getParentUUID())) - { - items.push_back(std::string("Marketplace Activate")); - items.push_back(std::string("Marketplace Deactivate")); - if (LLMarketplaceData::instance().isUpdating(mUUID,depth) || ((flags & FIRST_SELECTED_ITEM) == 0)) - { - // During SLM update, disable all marketplace related options - // Also disable all if multiple selected items - disabled_items.push_back(std::string("Marketplace Activate")); - disabled_items.push_back(std::string("Marketplace Deactivate")); - } - else - { - if (LLMarketplaceData::instance().isVersionFolder(mUUID)) - { - disabled_items.push_back(std::string("Marketplace Activate")); - if (LLMarketplaceData::instance().getActivationState(mUUID)) - { - disabled_items.push_back(std::string("Marketplace Deactivate")); - } - } - else - { - disabled_items.push_back(std::string("Marketplace Deactivate")); - } - } - } - } - - items.push_back(std::string("Marketplace Edit Listing")); - LLUUID listing_folder_id = nested_parent_id(mUUID,depth); - LLUUID version_folder_id = LLMarketplaceData::instance().getVersionFolder(listing_folder_id); - - if (depth >= 2) - { - // Prevent creation of new folders if the max count has been reached on this version folder (active or not) - LLUUID local_version_folder_id = nested_parent_id(mUUID,depth-1); - LLInventoryModel::cat_array_t categories; - LLInventoryModel::item_array_t items; - gInventory.collectDescendents(local_version_folder_id, categories, items, false); - static LLCachedControl max_depth(gSavedSettings, "InventoryOutboxMaxFolderDepth", 4); - static LLCachedControl max_count(gSavedSettings, "InventoryOutboxMaxFolderCount", 20); - if (categories.size() >= max_count - || depth > (max_depth + 1)) - { - disabled_items.push_back(std::string("New Folder")); - } - } - - // Options available at all levels on items and categories - if (!LLMarketplaceData::instance().isListed(listing_folder_id) || version_folder_id.isNull()) - { - disabled_items.push_back(std::string("Marketplace Edit Listing")); - } - - // Separator - items.push_back(std::string("Marketplace Listings Separator")); -} - -void LLInvFVBridge::addLinkReplaceMenuOption(menuentry_vec_t& items, menuentry_vec_t& disabled_items) -{ - const LLInventoryObject* obj = getInventoryObject(); - - if (isAgentInventory() && obj && obj->getType() != LLAssetType::AT_CATEGORY && obj->getType() != LLAssetType::AT_LINK_FOLDER) - { - items.push_back(std::string("Replace Links")); - - if (mRoot->getSelectedCount() != 1) - { - disabled_items.push_back(std::string("Replace Links")); - } - } -} - -bool LLInvFVBridge::canMenuDelete() -{ - return isItemRemovable(false); -} - -bool LLInvFVBridge::canMenuCut() -{ - return isItemRemovable(true); -} - -// *TODO: remove this -bool LLInvFVBridge::startDrag(EDragAndDropType* type, LLUUID* id) const -{ - bool rv = false; - - const LLInventoryObject* obj = getInventoryObject(); - - if(obj) - { - *type = LLViewerAssetType::lookupDragAndDropType(obj->getActualType()); - if(*type == DAD_NONE) - { - return false; - } - - *id = obj->getUUID(); - //object_ids.push_back(obj->getUUID()); - - if (*type == DAD_CATEGORY) - { - LLInventoryModelBackgroundFetch::instance().start(obj->getUUID()); - } - - rv = true; - } - - return rv; -} - -LLInventoryObject* LLInvFVBridge::getInventoryObject() const -{ - LLInventoryObject* obj = NULL; - LLInventoryModel* model = getInventoryModel(); - if(model) - { - obj = (LLInventoryObject*)model->getObject(mUUID); - } - return obj; -} - -LLInventoryModel* LLInvFVBridge::getInventoryModel() const -{ - LLInventoryPanel* panel = mInventoryPanel.get(); - return panel ? panel->getModel() : NULL; -} - -LLInventoryFilter* LLInvFVBridge::getInventoryFilter() const -{ - LLInventoryPanel* panel = mInventoryPanel.get(); - return panel ? &(panel->getFilter()) : NULL; -} - -bool LLInvFVBridge::isItemInTrash() const -{ - LLInventoryModel* model = getInventoryModel(); - if(!model) return false; - const LLUUID trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); - return model->isObjectDescendentOf(mUUID, trash_id); -} - -bool LLInvFVBridge::isLinkedObjectInTrash() const -{ - if (isItemInTrash()) return true; - - const LLInventoryObject *obj = getInventoryObject(); - if (obj && obj->getIsLinkType()) - { - LLInventoryModel* model = getInventoryModel(); - if(!model) return false; - const LLUUID trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); - return model->isObjectDescendentOf(obj->getLinkedUUID(), trash_id); - } - return false; -} - -bool LLInvFVBridge::isItemInOutfits() const -{ - const LLInventoryModel* model = getInventoryModel(); - if(!model) return false; - - const LLUUID my_outfits_cat = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); - - return isCOFFolder() || (my_outfits_cat == mUUID) || model->isObjectDescendentOf(mUUID, my_outfits_cat); -} - -bool LLInvFVBridge::isLinkedObjectMissing() const -{ - const LLInventoryObject *obj = getInventoryObject(); - if (!obj) - { - return true; - } - if (obj->getIsLinkType() && LLAssetType::lookupIsLinkType(obj->getType())) - { - return true; - } - return false; -} - -bool LLInvFVBridge::isAgentInventory() const -{ - const LLInventoryModel* model = getInventoryModel(); - if(!model) return false; - if(gInventory.getRootFolderID() == mUUID) return true; - return model->isObjectDescendentOf(mUUID, gInventory.getRootFolderID()); -} - -bool LLInvFVBridge::isCOFFolder() const -{ - return LLAppearanceMgr::instance().getIsInCOF(mUUID); -} - -// *TODO : Suppress isInboxFolder() once Merchant Outbox is fully deprecated -bool LLInvFVBridge::isInboxFolder() const -{ - const LLUUID inbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX); - - if (inbox_id.isNull()) - { - return false; - } - - return gInventory.isObjectDescendentOf(mUUID, inbox_id); -} - -bool LLInvFVBridge::isMarketplaceListingsFolder() const -{ - const LLUUID folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - - if (folder_id.isNull()) - { - return false; - } - - return gInventory.isObjectDescendentOf(mUUID, folder_id); -} - -bool LLInvFVBridge::isItemPermissive() const -{ - return false; -} - -// static -void LLInvFVBridge::changeItemParent(LLInventoryModel* model, - LLViewerInventoryItem* item, - const LLUUID& new_parent_id, - bool restamp) -{ - model->changeItemParent(item, new_parent_id, restamp); -} - -// static -void LLInvFVBridge::changeCategoryParent(LLInventoryModel* model, - LLViewerInventoryCategory* cat, - const LLUUID& new_parent_id, - bool restamp) -{ - model->changeCategoryParent(cat, new_parent_id, restamp); -} - -LLInvFVBridge* LLInvFVBridge::createBridge(LLAssetType::EType asset_type, - LLAssetType::EType actual_asset_type, - LLInventoryType::EType inv_type, - LLInventoryPanel* inventory, - LLFolderViewModelInventory* view_model, - LLFolderView* root, - const LLUUID& uuid, - U32 flags) -{ - LLInvFVBridge* new_listener = NULL; - switch(asset_type) - { - case LLAssetType::AT_TEXTURE: - if(!(inv_type == LLInventoryType::IT_TEXTURE || inv_type == LLInventoryType::IT_SNAPSHOT)) - { - LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; - } - new_listener = new LLTextureBridge(inventory, root, uuid, inv_type); - break; - - case LLAssetType::AT_SOUND: - if(!(inv_type == LLInventoryType::IT_SOUND)) - { - LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; - } - new_listener = new LLSoundBridge(inventory, root, uuid); - break; - - case LLAssetType::AT_LANDMARK: - if(!(inv_type == LLInventoryType::IT_LANDMARK)) - { - LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; - } - new_listener = new LLLandmarkBridge(inventory, root, uuid, flags); - break; - - case LLAssetType::AT_CALLINGCARD: - if(!(inv_type == LLInventoryType::IT_CALLINGCARD)) - { - LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; - } - new_listener = new LLCallingCardBridge(inventory, root, uuid); - break; - - case LLAssetType::AT_SCRIPT: - if(!(inv_type == LLInventoryType::IT_LSL)) - { - LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; - } - new_listener = new LLItemBridge(inventory, root, uuid); - break; - - case LLAssetType::AT_OBJECT: - if(!(inv_type == LLInventoryType::IT_OBJECT || inv_type == LLInventoryType::IT_ATTACHMENT)) - { - LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; - } - new_listener = new LLObjectBridge(inventory, root, uuid, inv_type, flags); - break; - - case LLAssetType::AT_NOTECARD: - if(!(inv_type == LLInventoryType::IT_NOTECARD)) - { - LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; - } - new_listener = new LLNotecardBridge(inventory, root, uuid); - break; - - case LLAssetType::AT_ANIMATION: - if(!(inv_type == LLInventoryType::IT_ANIMATION)) - { - LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; - } - new_listener = new LLAnimationBridge(inventory, root, uuid); - break; - - case LLAssetType::AT_GESTURE: - if(!(inv_type == LLInventoryType::IT_GESTURE)) - { - LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; - } - new_listener = new LLGestureBridge(inventory, root, uuid); - break; - - case LLAssetType::AT_LSL_TEXT: - if(!(inv_type == LLInventoryType::IT_LSL)) - { - LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; - } - new_listener = new LLLSLTextBridge(inventory, root, uuid); - break; - - case LLAssetType::AT_CLOTHING: - case LLAssetType::AT_BODYPART: - if(!(inv_type == LLInventoryType::IT_WEARABLE)) - { - LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; - } - new_listener = new LLWearableBridge(inventory, root, uuid, asset_type, inv_type, LLWearableType::inventoryFlagsToWearableType(flags)); - break; - case LLAssetType::AT_CATEGORY: - if (actual_asset_type == LLAssetType::AT_LINK_FOLDER) - { - // Create a link folder handler instead - new_listener = new LLLinkFolderBridge(inventory, root, uuid); - } - else if (actual_asset_type == LLAssetType::AT_MARKETPLACE_FOLDER) - { - // Create a marketplace folder handler - new_listener = new LLMarketplaceFolderBridge(inventory, root, uuid); - } - else - { - new_listener = new LLFolderBridge(inventory, root, uuid); - } - break; - case LLAssetType::AT_LINK: - case LLAssetType::AT_LINK_FOLDER: - // Only should happen for broken links. - new_listener = new LLLinkItemBridge(inventory, root, uuid); - break; - case LLAssetType::AT_UNKNOWN: - new_listener = new LLUnknownItemBridge(inventory, root, uuid); - break; - case LLAssetType::AT_IMAGE_TGA: - case LLAssetType::AT_IMAGE_JPEG: - //LL_WARNS() << LLAssetType::lookup(asset_type) << " asset type is unhandled for uuid " << uuid << LL_ENDL; - break; - - case LLAssetType::AT_SETTINGS: - if (inv_type != LLInventoryType::IT_SETTINGS) - { - LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; - } - new_listener = new LLSettingsBridge(inventory, root, uuid, LLSettingsType::fromInventoryFlags(flags)); - break; - - case LLAssetType::AT_MATERIAL: - if (inv_type != LLInventoryType::IT_MATERIAL) - { - LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; - } - new_listener = new LLMaterialBridge(inventory, root, uuid); - break; - - default: - LL_INFOS_ONCE() << "Unhandled asset type (llassetstorage.h): " - << (S32)asset_type << " (" << LLAssetType::lookup(asset_type) << ")" << LL_ENDL; - break; - } - - if (new_listener) - { - new_listener->mInvType = inv_type; - } - - return new_listener; -} - -void LLInvFVBridge::purgeItem(LLInventoryModel *model, const LLUUID &uuid) -{ - LLInventoryObject* obj = model->getObject(uuid); - if (obj) - { - remove_inventory_object(uuid, NULL); - } -} - -void LLInvFVBridge::removeObject(LLInventoryModel *model, const LLUUID &uuid) -{ - // Keep track of the parent - LLInventoryItem* itemp = model->getItem(uuid); - LLUUID parent_id = (itemp ? itemp->getParentUUID() : LLUUID::null); - // Remove the object - model->removeObject(uuid); - // Get the parent updated - if (parent_id.notNull()) - { - LLViewerInventoryCategory* parent_cat = model->getCategory(parent_id); - model->updateCategory(parent_cat); - model->notifyObservers(); - } -} - -bool LLInvFVBridge::canShare() const -{ - bool can_share = false; - - if (isAgentInventory()) - { - const LLInventoryModel* model = getInventoryModel(); - if (model) - { - const LLViewerInventoryItem *item = model->getItem(mUUID); - if (item) - { - if (LLInventoryCollectFunctor::itemTransferCommonlyAllowed(item)) - { - can_share = LLGiveInventory::isInventoryGiveAcceptable(item); - } - } - else - { - // Categories can be given. - can_share = (model->getCategory(mUUID) != NULL); - } - - const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - if ((mUUID == trash_id) || gInventory.isObjectDescendentOf(mUUID, trash_id)) - { - can_share = false; - } - } - } - - return can_share; -} - -bool LLInvFVBridge::canListOnMarketplace() const -{ - LLInventoryModel * model = getInventoryModel(); - - LLViewerInventoryCategory * cat = model->getCategory(mUUID); - if (cat && LLFolderType::lookupIsProtectedType(cat->getPreferredType())) - { - return false; - } - - if (!isAgentInventory()) - { - return false; - } - - LLViewerInventoryItem * item = model->getItem(mUUID); - if (item) - { - if (!item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID())) - { - return false; - } - - if (LLAssetType::AT_CALLINGCARD == item->getType()) - { - return false; - } - } - - return true; -} - -bool LLInvFVBridge::canListOnMarketplaceNow() const -{ - bool can_list = true; - - const LLInventoryObject* obj = getInventoryObject(); - can_list &= (obj != NULL); - - if (can_list) - { - const LLUUID& object_id = obj->getLinkedUUID(); - can_list = object_id.notNull(); - - if (can_list) - { - LLFolderViewFolder * object_folderp = mInventoryPanel.get() ? mInventoryPanel.get()->getFolderByID(object_id) : NULL; - if (object_folderp) - { - can_list = !static_cast(object_folderp->getViewModelItem())->isLoading(); - } - } - - if (can_list) - { - std::string error_msg; - LLInventoryModel* model = getInventoryModel(); - const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - if (marketplacelistings_id.notNull()) - { - LLViewerInventoryCategory * master_folder = model->getCategory(marketplacelistings_id); - LLInventoryCategory *cat = model->getCategory(mUUID); - if (cat) - { - can_list = can_move_folder_to_marketplace(master_folder, master_folder, cat, error_msg); - } - else - { - LLInventoryItem *item = model->getItem(mUUID); - can_list = (item ? can_move_item_to_marketplace(master_folder, master_folder, item, error_msg) : false); - } - } - else - { - can_list = false; - } - } - } - - return can_list; -} - -LLToolDragAndDrop::ESource LLInvFVBridge::getDragSource() const -{ - if (gInventory.isObjectDescendentOf(getUUID(), gInventory.getRootFolderID())) - { - return LLToolDragAndDrop::SOURCE_AGENT; - } - else if (gInventory.isObjectDescendentOf(getUUID(), gInventory.getLibraryRootFolderID())) - { - return LLToolDragAndDrop::SOURCE_LIBRARY; - } - - return LLToolDragAndDrop::SOURCE_VIEWER; -} - - - -// +=================================================+ -// | InventoryFVBridgeBuilder | -// +=================================================+ -LLInvFVBridge* LLInventoryFolderViewModelBuilder::createBridge(LLAssetType::EType asset_type, - LLAssetType::EType actual_asset_type, - LLInventoryType::EType inv_type, - LLInventoryPanel* inventory, - LLFolderViewModelInventory* view_model, - LLFolderView* root, - const LLUUID& uuid, - U32 flags /* = 0x00 */) const -{ - return LLInvFVBridge::createBridge(asset_type, - actual_asset_type, - inv_type, - inventory, - view_model, - root, - uuid, - flags); -} - -// +=================================================+ -// | LLItemBridge | -// +=================================================+ - -void LLItemBridge::performAction(LLInventoryModel* model, std::string action) -{ - if ("goto" == action) - { - gotoItem(); - } - - if ("open" == action || "open_original" == action) - { - openItem(); - return; - } - else if ("properties" == action) - { - showProperties(); - return; - } - else if ("purge" == action) - { - purgeItem(model, mUUID); - return; - } - else if ("restoreToWorld" == action) - { - restoreToWorld(); - return; - } - else if ("restore" == action) - { - restoreItem(); - return; - } - else if ("thumbnail" == action) - { - LLSD data(mUUID); - LLFloaterReg::showInstance("change_item_thumbnail", data); - return; - } - else if ("copy_uuid" == action) - { - // Single item only - LLViewerInventoryItem* item = static_cast(getItem()); - if(!item) return; - LLUUID asset_id = item->getProtectedAssetUUID(); - std::string buffer; - asset_id.toString(buffer); - - gViewerWindow->getWindow()->copyTextToClipboard(utf8str_to_wstring(buffer)); - return; - } - else if ("show_in_main_panel" == action) - { - LLInventoryPanel::openInventoryPanelAndSetSelection(true, mUUID, true); - return; - } - else if ("cut" == action) - { - cutToClipboard(); - return; - } - else if ("copy" == action) - { - copyToClipboard(); - return; - } - else if ("paste" == action) - { - LLInventoryItem* itemp = model->getItem(mUUID); - if (!itemp) return; - - LLFolderViewItem* folder_view_itemp = mInventoryPanel.get()->getItemByID(itemp->getParentUUID()); - if (!folder_view_itemp) return; - - folder_view_itemp->getViewModelItem()->pasteFromClipboard(); - return; - } - else if ("paste_link" == action) - { - // Single item only - LLInventoryItem* itemp = model->getItem(mUUID); - if (!itemp) return; - - LLFolderViewItem* folder_view_itemp = mInventoryPanel.get()->getItemByID(itemp->getParentUUID()); - if (!folder_view_itemp) return; - - folder_view_itemp->getViewModelItem()->pasteLinkFromClipboard(); - return; - } - else if (("move_to_marketplace_listings" == action) || ("copy_to_marketplace_listings" == action) || ("copy_or_move_to_marketplace_listings" == action)) - { - LLInventoryItem* itemp = model->getItem(mUUID); - if (!itemp) return; - const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - // Note: For a single item, if it's not a copy, then it's a move - move_item_to_marketplacelistings(itemp, marketplacelistings_id, ("copy_to_marketplace_listings" == action)); - } - else if ("copy_slurl" == action) - { - LLViewerInventoryItem* item = static_cast(getItem()); - if(item) - { - LLUUID asset_id = item->getAssetUUID(); - LLLandmark* landmark = gLandmarkList.getAsset(asset_id); - if (landmark) - { - LLVector3d global_pos; - landmark->getGlobalPos(global_pos); - LLLandmarkActions::getSLURLfromPosGlobal(global_pos, ©_slurl_to_clipboard_callback_inv, true); - } - } - } - else if ("show_on_map" == action) - { - doActionOnCurSelectedLandmark(boost::bind(&LLItemBridge::doShowOnMap, this, _1)); - } - else if ("marketplace_edit_listing" == action) - { - std::string url = LLMarketplaceData::instance().getListingURL(mUUID); - LLUrlAction::openURL(url); - } -} - -void LLItemBridge::doActionOnCurSelectedLandmark(LLLandmarkList::loaded_callback_t cb) -{ - LLViewerInventoryItem* cur_item = getItem(); - if(cur_item && cur_item->getInventoryType() == LLInventoryType::IT_LANDMARK) - { - LLLandmark* landmark = LLLandmarkActions::getLandmark(cur_item->getUUID(), cb); - if (landmark) - { - cb(landmark); - } - } -} - -void LLItemBridge::doShowOnMap(LLLandmark* landmark) -{ - LLVector3d landmark_global_pos; - // landmark has already been tested for NULL by calling routine - if (landmark->getGlobalPos(landmark_global_pos)) - { - LLFloaterWorldMap* worldmap_instance = LLFloaterWorldMap::getInstance(); - if (!landmark_global_pos.isExactlyZero() && worldmap_instance) - { - worldmap_instance->trackLocation(landmark_global_pos); - LLFloaterReg::showInstance("world_map", "center"); - } - } -} - -void copy_slurl_to_clipboard_callback_inv(const std::string& slurl) -{ - gViewerWindow->getWindow()->copyTextToClipboard(utf8str_to_wstring(slurl)); - LLSD args; - args["SLURL"] = slurl; - LLNotificationsUtil::add("CopySLURL", args); -} - -void LLItemBridge::selectItem() -{ - LLViewerInventoryItem* item = static_cast(getItem()); - if(item && !item->isFinished()) - { - //item->fetchFromServer(); - LLInventoryModelBackgroundFetch::instance().start(item->getUUID(), false); - } -} - -void LLItemBridge::restoreItem() -{ - LLViewerInventoryItem* item = static_cast(getItem()); - if(item) - { - LLInventoryModel* model = getInventoryModel(); - bool is_snapshot = (item->getInventoryType() == LLInventoryType::IT_SNAPSHOT); - - const LLUUID new_parent = model->findCategoryUUIDForType(is_snapshot? LLFolderType::FT_SNAPSHOT_CATEGORY : LLFolderType::assetTypeToFolderType(item->getType())); - // do not restamp on restore. - LLInvFVBridge::changeItemParent(model, item, new_parent, false); - } -} - -void LLItemBridge::restoreToWorld() -{ - //Similar functionality to the drag and drop rez logic - bool remove_from_inventory = false; - - LLViewerInventoryItem* itemp = static_cast(getItem()); - if (itemp) - { - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("RezRestoreToWorld"); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - - msg->nextBlockFast(_PREHASH_InventoryData); - itemp->packMessage(msg); - msg->sendReliable(gAgent.getRegionHost()); - - //remove local inventory copy, sim will deal with permissions and removing the item - //from the actual inventory if its a no-copy etc - if(!itemp->getPermissions().allowCopyBy(gAgent.getID())) - { - remove_from_inventory = true; - } - - // Check if it's in the trash. (again similar to the normal rez logic) - const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - if(gInventory.isObjectDescendentOf(itemp->getUUID(), trash_id)) - { - remove_from_inventory = true; - } - } - - if(remove_from_inventory) - { - gInventory.deleteObject(itemp->getUUID()); - gInventory.notifyObservers(); - } -} - -void LLItemBridge::gotoItem() -{ - LLInventoryObject *obj = getInventoryObject(); - if (obj && obj->getIsLinkType()) - { - show_item_original(obj->getUUID()); - } -} - -LLUIImagePtr LLItemBridge::getIcon() const -{ - LLInventoryObject *obj = getInventoryObject(); - if (obj) - { - return LLInventoryIcon::getIcon(obj->getType(), - LLInventoryType::IT_NONE, - mIsLink); - } - - return LLInventoryIcon::getIcon(LLInventoryType::ICONNAME_OBJECT); -} - -LLUIImagePtr LLItemBridge::getIconOverlay() const -{ - if (getItem() && getItem()->getIsLinkType()) - { - return LLUI::getUIImage("Inv_Link"); - } - return NULL; -} - -PermissionMask LLItemBridge::getPermissionMask() const -{ - LLViewerInventoryItem* item = getItem(); - PermissionMask perm_mask = 0; - if (item) perm_mask = item->getPermissionMask(); - return perm_mask; -} - -void LLItemBridge::buildDisplayName() const -{ - if(getItem()) - { - mDisplayName.assign(getItem()->getName()); - } - else - { - mDisplayName.assign(LLStringUtil::null); - } - - mSearchableName.assign(mDisplayName); - mSearchableName.append(getLabelSuffix()); - LLStringUtil::toUpper(mSearchableName); - - //Name set, so trigger a sort - LLInventorySort sorter = static_cast(mRootViewModel).getSorter(); - if(mParent && !sorter.isByDate()) - { - mParent->requestSort(); - } -} - -LLFontGL::StyleFlags LLItemBridge::getLabelStyle() const -{ - U8 font = LLFontGL::NORMAL; - const LLViewerInventoryItem* item = getItem(); - - if (get_is_item_worn(mUUID)) - { - // LL_INFOS() << "BOLD" << LL_ENDL; - font |= LLFontGL::BOLD; - } - else if(item && item->getIsLinkType()) - { - font |= LLFontGL::ITALIC; - } - - return (LLFontGL::StyleFlags)font; -} - -std::string LLItemBridge::getLabelSuffix() const -{ - // String table is loaded before login screen and inventory items are - // loaded after login, so LLTrans should be ready. - static std::string NO_COPY = LLTrans::getString("no_copy_lbl"); - static std::string NO_MOD = LLTrans::getString("no_modify_lbl"); - static std::string NO_XFER = LLTrans::getString("no_transfer_lbl"); - static std::string LINK = LLTrans::getString("link"); - static std::string BROKEN_LINK = LLTrans::getString("broken_link"); - std::string suffix; - LLInventoryItem* item = getItem(); - if(item) - { - // Any type can have the link suffix... - bool broken_link = LLAssetType::lookupIsLinkType(item->getType()); - if (broken_link) return BROKEN_LINK; - - bool link = item->getIsLinkType(); - if (link) return LINK; - - // ...but it's a bit confusing to put nocopy/nomod/etc suffixes on calling cards. - if(LLAssetType::AT_CALLINGCARD != item->getType() - && item->getPermissions().getOwner() == gAgent.getID()) - { - bool copy = item->getPermissions().allowCopyBy(gAgent.getID()); - if (!copy) - { - suffix += " "; - suffix += NO_COPY; - } - bool mod = item->getPermissions().allowModifyBy(gAgent.getID()); - if (!mod) - { - suffix += suffix.empty() ? " " : ","; - suffix += NO_MOD; - } - bool xfer = item->getPermissions().allowOperationBy(PERM_TRANSFER, - gAgent.getID()); - if (!xfer) - { - suffix += suffix.empty() ? " " : ","; - suffix += NO_XFER; - } - } - } - return suffix; -} - -time_t LLItemBridge::getCreationDate() const -{ - LLViewerInventoryItem* item = getItem(); - if (item) - { - return item->getCreationDate(); - } - return 0; -} - - -bool LLItemBridge::isItemRenameable() const -{ - LLViewerInventoryItem* item = getItem(); - if(item) - { - // (For now) Don't allow calling card rename since that may confuse users as to - // what the calling card points to. - if (item->getInventoryType() == LLInventoryType::IT_CALLINGCARD) - { - return false; - } - - if (!item->isFinished()) // EXT-8662 - { - return false; - } - - if (isInboxFolder()) - { - return false; - } - - return (item->getPermissions().allowModifyBy(gAgent.getID())); - } - return false; -} - -bool LLItemBridge::renameItem(const std::string& new_name) -{ - if(!isItemRenameable()) - return false; - LLPreview::dirty(mUUID); - LLInventoryModel* model = getInventoryModel(); - if(!model) - return false; - LLViewerInventoryItem* item = getItem(); - if(item && (item->getName() != new_name)) - { - LLSD updates; - updates["name"] = new_name; - update_inventory_item(item->getUUID(),updates, NULL); - } - // return false because we either notified observers (& therefore - // rebuilt) or we didn't update. - return false; -} - -bool LLItemBridge::removeItem() -{ - if(!isItemRemovable()) - { - return false; - } - - // move it to the trash - LLInventoryModel* model = getInventoryModel(); - if(!model) return false; - const LLUUID& trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); - LLViewerInventoryItem* item = getItem(); - if (!item) return false; - if (item->getType() != LLAssetType::AT_LSL_TEXT) - { - LLPreview::hide(mUUID, true); - } - // Already in trash - if (model->isObjectDescendentOf(mUUID, trash_id)) return false; - - LLNotification::Params params("ConfirmItemDeleteHasLinks"); - params.functor.function(boost::bind(&LLItemBridge::confirmRemoveItem, this, _1, _2)); - - // Check if this item has any links. If generic inventory linking is enabled, - // we can't do this check because we may have items in a folder somewhere that is - // not yet in memory, so we don't want false negatives. (If disabled, then we - // know we only have links in the Outfits folder which we explicitly fetch.) - static LLCachedControl inventory_linking(gSavedSettings, "InventoryLinking", true); - if (!inventory_linking) - { - if (!item->getIsLinkType()) - { - LLInventoryModel::item_array_t item_array = gInventory.collectLinksTo(mUUID); - const U32 num_links = item_array.size(); - if (num_links > 0) - { - // Warn if the user is will break any links when deleting this item. - LLNotifications::instance().add(params); - return false; - } - } - } - - LLNotifications::instance().forceResponse(params, 0); - model->checkTrashOverflow(); - return true; -} - -bool LLItemBridge::confirmRemoveItem(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option != 0) return false; - - LLInventoryModel* model = getInventoryModel(); - if (!model) return false; - - LLViewerInventoryItem* item = getItem(); - if (!item) return false; - - const LLUUID& trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); - // if item is not already in trash - if(item && !model->isObjectDescendentOf(mUUID, trash_id)) - { - // move to trash, and restamp - LLInvFVBridge::changeItemParent(model, item, trash_id, true); - // delete was successful - return true; - } - return false; -} - -bool LLItemBridge::isItemCopyable(bool can_copy_as_link) const -{ - LLViewerInventoryItem* item = getItem(); - if (!item) - { - return false; - } - // Can't copy worn objects. - // Worn objects are tied to their inworld conterparts - // Copy of modified worn object will return object with obsolete asset and inventory - if (get_is_item_worn(mUUID)) - { - return false; - } - - static LLCachedControl inventory_linking(gSavedSettings, "InventoryLinking", true); - return (can_copy_as_link && inventory_linking) - || (mIsLink && inventory_linking) - || item->getPermissions().allowCopyBy(gAgent.getID()); -} - -LLViewerInventoryItem* LLItemBridge::getItem() const -{ - LLViewerInventoryItem* item = NULL; - LLInventoryModel* model = getInventoryModel(); - if(model) - { - item = (LLViewerInventoryItem*)model->getItem(mUUID); - } - return item; -} - -const LLUUID& LLItemBridge::getThumbnailUUID() const -{ - LLViewerInventoryItem* item = NULL; - LLInventoryModel* model = getInventoryModel(); - if(model) - { - item = (LLViewerInventoryItem*)model->getItem(mUUID); - } - if (item) - { - return item->getThumbnailUUID(); - } - return LLUUID::null; -} - -bool LLItemBridge::isItemPermissive() const -{ - LLViewerInventoryItem* item = getItem(); - if(item) - { - return item->getIsFullPerm(); - } - return false; -} - -// +=================================================+ -// | LLFolderBridge | -// +=================================================+ - -LLHandle LLFolderBridge::sSelf; - -// Can be moved to another folder -bool LLFolderBridge::isItemMovable() const -{ - LLInventoryObject* obj = getInventoryObject(); - if(obj) - { - // If it's a protected type folder, we can't move it - if (LLFolderType::lookupIsProtectedType(((LLInventoryCategory*)obj)->getPreferredType())) - return false; - return true; - } - return false; -} - -void LLFolderBridge::selectItem() -{ - LLViewerInventoryCategory* cat = gInventory.getCategory(getUUID()); - if (cat) - { - cat->fetch(); - } -} - -void LLFolderBridge::buildDisplayName() const -{ - LLFolderType::EType preferred_type = getPreferredType(); - - // *TODO: to be removed when database supports multi language. This is a - // temporary attempt to display the inventory folder in the user locale. - // mantipov: *NOTE: be sure this code is synchronized with LLFriendCardsManager::findChildFolderUUID - // it uses the same way to find localized string - - // HACK: EXT - 6028 ([HARD CODED]? Inventory > Library > "Accessories" folder) - // Translation of Accessories folder in Library inventory folder - bool accessories = false; - if(getName() == "Accessories") - { - //To ensure that Accessories folder is in Library we have to check its parent folder. - //Due to parent LLFolderViewFloder is not set to this item yet we have to check its parent via Inventory Model - LLInventoryCategory* cat = gInventory.getCategory(getUUID()); - if(cat) - { - const LLUUID& parent_folder_id = cat->getParentUUID(); - accessories = (parent_folder_id == gInventory.getLibraryRootFolderID()); - } - } - - //"Accessories" inventory category has folder type FT_NONE. So, this folder - //can not be detected as protected with LLFolderType::lookupIsProtectedType - mDisplayName.assign(getName()); - if (accessories || LLFolderType::lookupIsProtectedType(preferred_type)) - { - LLTrans::findString(mDisplayName, std::string("InvFolder ") + getName(), LLSD()); - } - - mSearchableName.assign(mDisplayName); - mSearchableName.append(getLabelSuffix()); - LLStringUtil::toUpper(mSearchableName); - - //Name set, so trigger a sort - LLInventorySort sorter = static_cast(mRootViewModel).getSorter(); - if(mParent && sorter.isFoldersByName()) - { - mParent->requestSort(); - } -} - -std::string LLFolderBridge::getLabelSuffix() const -{ - static LLCachedControl xui_debug(gSavedSettings, "DebugShowXUINames", 0); - - if (mIsLoading && mTimeSinceRequestStart.getElapsedTimeF32() >= FOLDER_LOADING_MESSAGE_DELAY) - { - return llformat(" ( %s ) ", LLTrans::getString("LoadingData").c_str()); - } - std::string suffix = ""; - if (xui_debug) - { - LLInventoryModel::cat_array_t* cats; - LLInventoryModel::item_array_t* items; - gInventory.getDirectDescendentsOf(getUUID(), cats, items); - - LLViewerInventoryCategory* cat = gInventory.getCategory(getUUID()); - if (cat) - { - LLStringUtil::format_map_t args; - args["[FOLDER_COUNT]"] = llformat("%d", cats->size()); - args["[ITEMS_COUNT]"] = llformat("%d", items->size()); - args["[VERSION]"] = llformat("%d", cat->getVersion()); - args["[VIEWER_DESCENDANT_COUNT]"] = llformat("%d", cats->size() + items->size()); - args["[SERVER_DESCENDANT_COUNT]"] = llformat("%d", cat->getDescendentCount()); - suffix = " " + LLTrans::getString("InventoryFolderDebug", args); - } - } - else if(mShowDescendantsCount) - { - LLInventoryModel::cat_array_t cat_array; - LLInventoryModel::item_array_t item_array; - gInventory.collectDescendents(getUUID(), cat_array, item_array, true); - S32 count = item_array.size(); - if(count > 0) - { - std::ostringstream oss; - oss << count; - LLStringUtil::format_map_t args; - args["[ITEMS_COUNT]"] = oss.str(); - suffix = " " + LLTrans::getString("InventoryItemsCount", args); - } - } - - return LLInvFVBridge::getLabelSuffix() + suffix; -} - -LLFontGL::StyleFlags LLFolderBridge::getLabelStyle() const -{ - return LLFontGL::NORMAL; -} - -const LLUUID& LLFolderBridge::getThumbnailUUID() const -{ - LLViewerInventoryCategory* cat = getCategory(); - if (cat) - { - return cat->getThumbnailUUID(); - } - return LLUUID::null; -} - -void LLFolderBridge::update() -{ - // we know we have children but haven't fetched them (doesn't obey filter) - bool loading = !isUpToDate() && hasChildren() && mFolderViewItem->isOpen(); - - if (loading != mIsLoading) - { - if ( loading ) - { - // Measure how long we've been in the loading state - mTimeSinceRequestStart.reset(); - } - mIsLoading = loading; - - mFolderViewItem->refresh(); - } -} - -// Can be destroyed (or moved to trash) -bool LLFolderBridge::isItemRemovable(bool check_worn) const -{ - if (!get_is_category_and_children_removable(getInventoryModel(), mUUID, check_worn)) - { - return false; - } - - if (isMarketplaceListingsFolder() - && (!LLMarketplaceData::instance().isSLMDataFetched() || LLMarketplaceData::instance().getActivationState(mUUID))) - { - return false; - } - - return true; -} - -bool LLFolderBridge::isUpToDate() const -{ - LLInventoryModel* model = getInventoryModel(); - if(!model) return false; - LLViewerInventoryCategory* category = (LLViewerInventoryCategory*)model->getCategory(mUUID); - if( !category ) - { - return false; - } - - return category->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN; -} - -bool LLFolderBridge::isItemCopyable(bool can_copy_as_link) const -{ - if (can_copy_as_link && !LLFolderType::lookupIsProtectedType(getPreferredType())) - { - // Can copy and paste unprotected folders as links - return true; - } - - // Folders are copyable if items in them are, recursively, copyable. - - // Get the content of the folder - LLInventoryModel::cat_array_t* cat_array; - LLInventoryModel::item_array_t* item_array; - gInventory.getDirectDescendentsOf(mUUID,cat_array,item_array); - - // Check the items - LLInventoryModel::item_array_t item_array_copy = *item_array; - for (LLInventoryModel::item_array_t::iterator iter = item_array_copy.begin(); iter != item_array_copy.end(); iter++) - { - LLInventoryItem* item = *iter; - LLItemBridge item_br(mInventoryPanel.get(), mRoot, item->getUUID()); - if (!item_br.isItemCopyable(false)) - { - return false; - } - } - - // Check the folders - LLInventoryModel::cat_array_t cat_array_copy = *cat_array; - for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) - { - LLViewerInventoryCategory* category = *iter; - LLFolderBridge cat_br(mInventoryPanel.get(), mRoot, category->getUUID()); - if (!cat_br.isItemCopyable(false)) - { - return false; - } - } - - return true; -} - -bool LLFolderBridge::isClipboardPasteable() const -{ - if ( ! LLInvFVBridge::isClipboardPasteable() ) - return false; - - // Don't allow pasting duplicates to the Calling Card/Friends subfolders, see bug EXT-1599 - if ( LLFriendCardsManager::instance().isCategoryInFriendFolder( getCategory() ) ) - { - LLInventoryModel* model = getInventoryModel(); - if ( !model ) - { - return false; - } - - std::vector objects; - LLClipboard::instance().pasteFromClipboard(objects); - const LLViewerInventoryCategory *current_cat = getCategory(); - - // Search for the direct descendent of current Friends subfolder among all pasted items, - // and return false if is found. - for(S32 i = objects.size() - 1; i >= 0; --i) - { - const LLUUID &obj_id = objects.at(i); - if ( LLFriendCardsManager::instance().isObjDirectDescendentOfCategory(model->getObject(obj_id), current_cat) ) - { - return false; - } - } - - } - return true; -} - -bool LLFolderBridge::isClipboardPasteableAsLink() const -{ - // Check normal paste-as-link permissions - if (!LLInvFVBridge::isClipboardPasteableAsLink()) - { - return false; - } - - const LLInventoryModel* model = getInventoryModel(); - if (!model) - { - return false; - } - - const LLViewerInventoryCategory *current_cat = getCategory(); - if (current_cat) - { - const bool is_in_friend_folder = LLFriendCardsManager::instance().isCategoryInFriendFolder( current_cat ); - const LLUUID ¤t_cat_id = current_cat->getUUID(); - std::vector objects; - LLClipboard::instance().pasteFromClipboard(objects); - S32 count = objects.size(); - for(S32 i = 0; i < count; i++) - { - const LLUUID &obj_id = objects.at(i); - const LLInventoryCategory *cat = model->getCategory(obj_id); - if (cat) - { - const LLUUID &cat_id = cat->getUUID(); - // Don't allow recursive pasting - if ((cat_id == current_cat_id) || - model->isObjectDescendentOf(current_cat_id, cat_id)) - { - return false; - } - } - // Don't allow pasting duplicates to the Calling Card/Friends subfolders, see bug EXT-1599 - if ( is_in_friend_folder ) - { - // If object is direct descendent of current Friends subfolder than return false. - // Note: We can't use 'const LLInventoryCategory *cat', because it may be null - // in case type of obj_id is LLInventoryItem. - if ( LLFriendCardsManager::instance().isObjDirectDescendentOfCategory(model->getObject(obj_id), current_cat) ) - { - return false; - } - } - } - } - return true; - -} - - -bool LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat, - bool drop, - std::string& tooltip_msg, - bool is_link, - bool user_confirm, - LLPointer cb) -{ - - LLInventoryModel* model = getInventoryModel(); - - if (!inv_cat) return false; // shouldn't happen, but in case item is incorrectly parented in which case inv_cat will be NULL - if (!model) return false; - if (!isAgentAvatarValid()) return false; - if (!isAgentInventory()) return false; // cannot drag categories into library - - LLInventoryPanel* destination_panel = mInventoryPanel.get(); - if (!destination_panel) return false; - - LLInventoryFilter* filter = getInventoryFilter(); - if (!filter) return false; - - const LLUUID &cat_id = inv_cat->getUUID(); - const LLUUID ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); - const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - const LLUUID from_folder_uuid = inv_cat->getParentUUID(); - - const bool move_is_into_current_outfit = (mUUID == current_outfit_id); - const bool move_is_into_marketplacelistings = model->isObjectDescendentOf(mUUID, marketplacelistings_id); - const bool move_is_from_marketplacelistings = model->isObjectDescendentOf(cat_id, marketplacelistings_id); - - // check to make sure source is agent inventory, and is represented there. - LLToolDragAndDrop::ESource source = LLToolDragAndDrop::getInstance()->getSource(); - const bool is_agent_inventory = (model->getCategory(cat_id) != NULL) - && (LLToolDragAndDrop::SOURCE_AGENT == source); - - bool accept = false; - U64 filter_types = filter->getFilterTypes(); - bool use_filter = filter_types && (filter_types&LLInventoryFilter::FILTERTYPE_DATE || (filter_types&LLInventoryFilter::FILTERTYPE_OBJECT)==0); - - if (is_agent_inventory) - { - const LLUUID &trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); - const LLUUID &landmarks_id = model->findCategoryUUIDForType(LLFolderType::FT_LANDMARK); - const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); - const LLUUID &lost_and_found_id = model->findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); - - const bool move_is_into_trash = (mUUID == trash_id) || model->isObjectDescendentOf(mUUID, trash_id); - const bool move_is_into_my_outfits = (mUUID == my_outifts_id) || model->isObjectDescendentOf(mUUID, my_outifts_id); - const bool move_is_into_outfit = move_is_into_my_outfits || (getCategory() && getCategory()->getPreferredType()==LLFolderType::FT_OUTFIT); - const bool move_is_into_current_outfit = (getCategory() && getCategory()->getPreferredType()==LLFolderType::FT_CURRENT_OUTFIT); - const bool move_is_into_landmarks = (mUUID == landmarks_id) || model->isObjectDescendentOf(mUUID, landmarks_id); - const bool move_is_into_lost_and_found = model->isObjectDescendentOf(mUUID, lost_and_found_id); - - //-------------------------------------------------------------------------------- - // Determine if folder can be moved. - // - - bool is_movable = true; - - if (is_movable && (marketplacelistings_id == cat_id)) - { - is_movable = false; - tooltip_msg = LLTrans::getString("TooltipOutboxCannotMoveRoot"); - } - if (is_movable && move_is_from_marketplacelistings && LLMarketplaceData::instance().getActivationState(cat_id)) - { - // If the incoming folder is listed and active (and is therefore either the listing or the version folder), - // then moving is *not* allowed - is_movable = false; - tooltip_msg = LLTrans::getString("TooltipOutboxDragActive"); - } - if (is_movable && (mUUID == cat_id)) - { - is_movable = false; - tooltip_msg = LLTrans::getString("TooltipDragOntoSelf"); - } - if (is_movable && (model->isObjectDescendentOf(mUUID, cat_id))) - { - is_movable = false; - tooltip_msg = LLTrans::getString("TooltipDragOntoOwnChild"); - } - if (is_movable && LLFolderType::lookupIsProtectedType(inv_cat->getPreferredType())) - { - is_movable = false; - // tooltip? - } - - U32 max_items_to_wear = gSavedSettings.getU32("WearFolderLimit"); - if (is_movable && move_is_into_outfit) - { - if (mUUID == my_outifts_id) - { - if (source != LLToolDragAndDrop::SOURCE_AGENT || move_is_from_marketplacelistings) - { - tooltip_msg = LLTrans::getString("TooltipOutfitNotInInventory"); - is_movable = false; - } - else if (can_move_to_my_outfits(model, inv_cat, max_items_to_wear)) - { - is_movable = true; - } - else - { - tooltip_msg = LLTrans::getString("TooltipCantCreateOutfit"); - is_movable = false; - } - } - else if(getCategory() && getCategory()->getPreferredType() == LLFolderType::FT_NONE) - { - is_movable = ((inv_cat->getPreferredType() == LLFolderType::FT_NONE) || (inv_cat->getPreferredType() == LLFolderType::FT_OUTFIT)); - } - else - { - is_movable = false; - } - } - if(is_movable && move_is_into_current_outfit && is_link) - { - is_movable = false; - } - if (is_movable && move_is_into_lost_and_found) - { - is_movable = false; - } - if (is_movable && (mUUID == model->findCategoryUUIDForType(LLFolderType::FT_FAVORITE))) - { - is_movable = false; - // tooltip? - } - if (is_movable && (getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK)) - { - // One cannot move a folder into a stock folder - is_movable = false; - // tooltip? - } - - LLInventoryModel::cat_array_t descendent_categories; - LLInventoryModel::item_array_t descendent_items; - if (is_movable) - { - model->collectDescendents(cat_id, descendent_categories, descendent_items, false); - for (S32 i=0; i < descendent_categories.size(); ++i) - { - LLInventoryCategory* category = descendent_categories[i]; - if(LLFolderType::lookupIsProtectedType(category->getPreferredType())) - { - // Can't move "special folders" (e.g. Textures Folder). - is_movable = false; - break; - } - } - } - if (is_movable - && move_is_into_current_outfit - && descendent_items.size() > max_items_to_wear) - { - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - LLFindWearablesEx not_worn(/*is_worn=*/ false, /*include_body_parts=*/ false); - gInventory.collectDescendentsIf(cat_id, - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - not_worn); - - if (items.size() > max_items_to_wear) - { - // Can't move 'large' folders into current outfit: MAINT-4086 - is_movable = false; - LLStringUtil::format_map_t args; - args["AMOUNT"] = llformat("%d", max_items_to_wear); - tooltip_msg = LLTrans::getString("TooltipTooManyWearables",args); - } - } - if (is_movable && move_is_into_trash) - { - for (S32 i=0; i < descendent_items.size(); ++i) - { - LLInventoryItem* item = descendent_items[i]; - if (get_is_item_worn(item->getUUID())) - { - is_movable = false; - break; // It's generally movable, but not into the trash. - } - } - } - if (is_movable && move_is_into_landmarks) - { - for (S32 i=0; i < descendent_items.size(); ++i) - { - LLViewerInventoryItem* item = descendent_items[i]; - - // Don't move anything except landmarks and categories into Landmarks folder. - // We use getType() instead of getActua;Type() to allow links to landmarks and folders. - if (LLAssetType::AT_LANDMARK != item->getType() && LLAssetType::AT_CATEGORY != item->getType()) - { - is_movable = false; - break; // It's generally movable, but not into Landmarks. - } - } - } - - if (is_movable && move_is_into_marketplacelistings) - { - const LLViewerInventoryCategory * master_folder = model->getFirstDescendantOf(marketplacelistings_id, mUUID); - LLViewerInventoryCategory * dest_folder = getCategory(); - S32 bundle_size = (drop ? 1 : LLToolDragAndDrop::instance().getCargoCount()); - is_movable = can_move_folder_to_marketplace(master_folder, dest_folder, inv_cat, tooltip_msg, bundle_size); - } - - if (is_movable && !move_is_into_landmarks) - { - LLInventoryPanel* active_panel = LLInventoryPanel::getActiveInventoryPanel(false); - is_movable = active_panel != NULL; - - // For a folder to pass the filter all its descendants are required to pass. - // We make this exception to allow reordering folders within an inventory panel, - // which has a filter applied, like Recent tab for example. - // There may be folders which are displayed because some of their descendants pass - // the filter, but other don't, and thus remain hidden. Without this check, - // such folders would not be allowed to be moved within a panel. - if (destination_panel == active_panel) - { - is_movable = true; - } - else - { - LLFolderView* active_folder_view = NULL; - - if (is_movable) - { - active_folder_view = active_panel->getRootFolder(); - is_movable = active_folder_view != NULL; - } - - if (is_movable && use_filter) - { - // Check whether the folder being dragged from active inventory panel - // passes the filter of the destination panel. - is_movable = check_category(model, cat_id, active_panel, filter); - } - } - } - // - //-------------------------------------------------------------------------------- - - accept = is_movable; - - if (accept && drop) - { - // Dropping in or out of marketplace needs (sometimes) confirmation - if (user_confirm && (move_is_from_marketplacelistings || move_is_into_marketplacelistings)) - { - if (move_is_from_marketplacelistings && (LLMarketplaceData::instance().isInActiveFolder(cat_id) || - LLMarketplaceData::instance().isListedAndActive(cat_id))) - { - if (LLMarketplaceData::instance().isListed(cat_id) || LLMarketplaceData::instance().isVersionFolder(cat_id)) - { - // Move the active version folder or listing folder itself outside marketplace listings will unlist the listing so ask that question specifically - LLNotificationsUtil::add("ConfirmMerchantUnlist", LLSD(), LLSD(), boost::bind(&LLFolderBridge::callback_dropCategoryIntoFolder, this, _1, _2, inv_cat)); - } - else - { - // Any other case will simply modify but not unlist an active listed listing - LLNotificationsUtil::add("ConfirmMerchantActiveChange", LLSD(), LLSD(), boost::bind(&LLFolderBridge::callback_dropCategoryIntoFolder, this, _1, _2, inv_cat)); - } - return true; - } - if (move_is_from_marketplacelistings && LLMarketplaceData::instance().isVersionFolder(cat_id)) - { - // Moving the version folder from its location will deactivate it. Ask confirmation. - LLNotificationsUtil::add("ConfirmMerchantClearVersion", LLSD(), LLSD(), boost::bind(&LLFolderBridge::callback_dropCategoryIntoFolder, this, _1, _2, inv_cat)); - return true; - } - if (move_is_into_marketplacelistings && LLMarketplaceData::instance().isInActiveFolder(mUUID)) - { - // Moving something in an active listed listing will modify it. Ask confirmation. - LLNotificationsUtil::add("ConfirmMerchantActiveChange", LLSD(), LLSD(), boost::bind(&LLFolderBridge::callback_dropCategoryIntoFolder, this, _1, _2, inv_cat)); - return true; - } - if (move_is_from_marketplacelistings && LLMarketplaceData::instance().isListed(cat_id)) - { - // Moving a whole listing folder will result in archival of SLM data. Ask confirmation. - LLNotificationsUtil::add("ConfirmListingCutOrDelete", LLSD(), LLSD(), boost::bind(&LLFolderBridge::callback_dropCategoryIntoFolder, this, _1, _2, inv_cat)); - return true; - } - if (move_is_into_marketplacelistings && !move_is_from_marketplacelistings) - { - LLNotificationsUtil::add("ConfirmMerchantMoveInventory", LLSD(), LLSD(), boost::bind(&LLFolderBridge::callback_dropCategoryIntoFolder, this, _1, _2, inv_cat)); - return true; - } - } - // Look for any gestures and deactivate them - if (move_is_into_trash) - { - for (S32 i=0; i < descendent_items.size(); i++) - { - LLInventoryItem* item = descendent_items[i]; - if (item->getType() == LLAssetType::AT_GESTURE - && LLGestureMgr::instance().isGestureActive(item->getUUID())) - { - LLGestureMgr::instance().deactivateGesture(item->getUUID()); - } - } - } - - if (mUUID == my_outifts_id) - { - // Category can contains objects, - // create a new folder and populate it with links to original objects - dropToMyOutfits(inv_cat, cb); - } - // if target is current outfit folder we use link - else if (move_is_into_current_outfit && - (inv_cat->getPreferredType() == LLFolderType::FT_NONE || - inv_cat->getPreferredType() == LLFolderType::FT_OUTFIT)) - { - // traverse category and add all contents to currently worn. - bool append = true; - LLAppearanceMgr::instance().wearInventoryCategory(inv_cat, false, append); - if (cb) cb->fire(inv_cat->getUUID()); - } - else if (move_is_into_marketplacelistings) - { - move_folder_to_marketplacelistings(inv_cat, mUUID); - if (cb) cb->fire(inv_cat->getUUID()); - } - else - { - if (model->isObjectDescendentOf(cat_id, model->findCategoryUUIDForType(LLFolderType::FT_INBOX))) - { - set_dad_inbox_object(cat_id); - } - - // Reparent the folder and restamp children if it's moving - // into trash. - LLInvFVBridge::changeCategoryParent( - model, - (LLViewerInventoryCategory*)inv_cat, - mUUID, - move_is_into_trash); - if (cb) cb->fire(inv_cat->getUUID()); - } - if (move_is_from_marketplacelistings) - { - // If we are moving a folder at the listing folder level (i.e. its parent is the marketplace listings folder) - if (from_folder_uuid == marketplacelistings_id) - { - // Clear the folder from the marketplace in case it is a listing folder - if (LLMarketplaceData::instance().isListed(cat_id)) - { - LLMarketplaceData::instance().clearListing(cat_id); - } - } - else - { - // If we move from within an active (listed) listing, checks that it's still valid, if not, unlist - LLUUID version_folder_id = LLMarketplaceData::instance().getActiveFolder(from_folder_uuid); - if (version_folder_id.notNull()) - { - LLMarketplaceValidator::getInstance()->validateMarketplaceListings( - version_folder_id, - [version_folder_id](bool result) - { - if (!result) - { - LLMarketplaceData::instance().activateListing(version_folder_id, false); - } - } - ); - } - // In all cases, update the listing we moved from so suffix are updated - update_marketplace_category(from_folder_uuid); - if (cb) cb->fire(inv_cat->getUUID()); - } - } - } - } - else if (LLToolDragAndDrop::SOURCE_WORLD == source) - { - if (move_is_into_marketplacelistings) - { - tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); - accept = false; - } - else - { - // Todo: fix me. moving from task inventory doesn't have a completion callback, - // yet making a copy creates new item id so this doesn't work right - std::function callback = [cb](S32, void*, const LLMoveInv* move_inv) mutable - { - two_uuids_list_t::const_iterator move_it; - for (move_it = move_inv->mMoveList.begin(); - move_it != move_inv->mMoveList.end(); - ++move_it) - { - if (cb) - { - cb->fire(move_it->second); - } - } - }; - accept = move_inv_category_world_to_agent(cat_id, mUUID, drop, callback, NULL, filter); - } - } - else if (LLToolDragAndDrop::SOURCE_LIBRARY == source) - { - if (move_is_into_marketplacelistings) - { - tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); - accept = false; - } - else - { - // Accept folders that contain complete outfits. - accept = move_is_into_current_outfit && LLAppearanceMgr::instance().getCanMakeFolderIntoOutfit(cat_id); - } - - if (accept && drop) - { - LLAppearanceMgr::instance().wearInventoryCategory(inv_cat, true, false); - } - } - - return accept; -} - -void warn_move_inventory(LLViewerObject* object, std::shared_ptr move_inv) -{ - const char* dialog = NULL; - if (object->flagScripted()) - { - dialog = "MoveInventoryFromScriptedObject"; - } - else - { - dialog = "MoveInventoryFromObject"; - } - - static LLNotificationPtr notification_ptr; - static std::shared_ptr inv_ptr; - - // Notification blocks user from interacting with inventories so everything that comes after first message - // is part of this message - don'r show it again - // Note: workaround for MAINT-5495 untill proper refactoring and warning system for Drag&Drop can be made. - if (notification_ptr == NULL - || !notification_ptr->isActive() - || LLNotificationsUtil::find(notification_ptr->getID()) == NULL - || inv_ptr->mCategoryID != move_inv->mCategoryID - || inv_ptr->mObjectID != move_inv->mObjectID) - { - notification_ptr = LLNotificationsUtil::add(dialog, LLSD(), LLSD(), boost::bind(move_task_inventory_callback, _1, _2, move_inv)); - inv_ptr = move_inv; - } - else - { - // Notification is alive and not responded, operating inv_ptr should be safe so attach new data - two_uuids_list_t::iterator move_it; - for (move_it = move_inv->mMoveList.begin(); - move_it != move_inv->mMoveList.end(); - ++move_it) - { - inv_ptr->mMoveList.push_back(*move_it); - } - move_inv.reset(); - } -} - -// Move/copy all inventory items from the Contents folder of an in-world -// object to the agent's inventory, inside a given category. -bool move_inv_category_world_to_agent(const LLUUID& object_id, - const LLUUID& category_id, - bool drop, - std::function callback, - void* user_data, - LLInventoryFilter* filter) -{ - // Make sure the object exists. If we allowed dragging from - // anonymous objects, it would be possible to bypass - // permissions. - // content category has same ID as object itself - LLViewerObject* object = gObjectList.findObject(object_id); - if(!object) - { - LL_INFOS() << "Object not found for drop." << LL_ENDL; - return false; - } - - // this folder is coming from an object, as there is only one folder in an object, the root, - // we need to collect the entire contents and handle them as a group - LLInventoryObject::object_list_t inventory_objects; - object->getInventoryContents(inventory_objects); - - if (inventory_objects.empty()) - { - LL_INFOS() << "Object contents not found for drop." << LL_ENDL; - return false; - } - - bool accept = false; - bool is_move = false; - bool use_filter = false; - if (filter) - { - U64 filter_types = filter->getFilterTypes(); - use_filter = filter_types && (filter_types&LLInventoryFilter::FILTERTYPE_DATE || (filter_types&LLInventoryFilter::FILTERTYPE_OBJECT)==0); - } - - // coming from a task. Need to figure out if the person can - // move/copy this item. - LLInventoryObject::object_list_t::iterator it = inventory_objects.begin(); - LLInventoryObject::object_list_t::iterator end = inventory_objects.end(); - for ( ; it != end; ++it) - { - LLInventoryItem* item = dynamic_cast(it->get()); - if (!item) - { - LL_WARNS() << "Invalid inventory item for drop" << LL_ENDL; - continue; - } - - // coming from a task. Need to figure out if the person can - // move/copy this item. - LLPermissions perm(item->getPermissions()); - if((perm.allowCopyBy(gAgent.getID(), gAgent.getGroupID()) - && perm.allowTransferTo(gAgent.getID()))) -// || gAgent.isGodlike()) - { - accept = true; - } - else if(object->permYouOwner()) - { - // If the object cannot be copied, but the object the - // inventory is owned by the agent, then the item can be - // moved from the task to agent inventory. - is_move = true; - accept = true; - } - - if (accept && use_filter) - { - accept = filter->check(item); - } - - if (!accept) - { - break; - } - } - - if(drop && accept) - { - it = inventory_objects.begin(); - std::shared_ptr move_inv(new LLMoveInv); - move_inv->mObjectID = object_id; - move_inv->mCategoryID = category_id; - move_inv->mCallback = callback; - move_inv->mUserData = user_data; - - for ( ; it != end; ++it) - { - two_uuids_t two(category_id, (*it)->getUUID()); - move_inv->mMoveList.push_back(two); - } - - if(is_move) - { - // Callback called from within here. - warn_move_inventory(object, move_inv); - } - else - { - LLNotification::Params params("MoveInventoryFromObject"); - params.functor.function(boost::bind(move_task_inventory_callback, _1, _2, move_inv)); - LLNotifications::instance().forceResponse(params, 0); - } - } - return accept; -} - -void LLRightClickInventoryFetchDescendentsObserver::execute(bool clear_observer) -{ - // Bail out immediately if no descendents - if( mComplete.empty() ) - { - LL_WARNS() << "LLRightClickInventoryFetchDescendentsObserver::done with empty mCompleteFolders" << LL_ENDL; - if (clear_observer) - { - gInventory.removeObserver(this); - delete this; - } - return; - } - - // Copy the list of complete fetched folders while "this" is still valid - uuid_vec_t completed_folder = mComplete; - - // Clean up, and remove this as an observer now since recursive calls - // could notify observers and throw us into an infinite loop. - if (clear_observer) - { - gInventory.removeObserver(this); - delete this; - } - - for (uuid_vec_t::iterator current_folder = completed_folder.begin(); current_folder != completed_folder.end(); ++current_folder) - { - // Get the information on the fetched folder items and subfolders and fetch those - LLInventoryModel::cat_array_t* cat_array; - LLInventoryModel::item_array_t* item_array; - gInventory.getDirectDescendentsOf(*current_folder, cat_array, item_array); - - S32 item_count(0); - if( item_array ) - { - item_count = item_array->size(); - } - - S32 cat_count(0); - if( cat_array ) - { - cat_count = cat_array->size(); - } - - // Move to next if current folder empty - if ((item_count == 0) && (cat_count == 0)) - { - continue; - } - - uuid_vec_t ids; - LLRightClickInventoryFetchObserver* outfit = NULL; - LLRightClickInventoryFetchDescendentsObserver* categories = NULL; - - // Fetch the items - if (item_count) - { - for (S32 i = 0; i < item_count; ++i) - { - ids.push_back(item_array->at(i)->getUUID()); - } - outfit = new LLRightClickInventoryFetchObserver(ids); - } - // Fetch the subfolders - if (cat_count) - { - for (S32 i = 0; i < cat_count; ++i) - { - ids.push_back(cat_array->at(i)->getUUID()); - } - categories = new LLRightClickInventoryFetchDescendentsObserver(ids); - } - - // Perform the item fetch - if (outfit) - { - outfit->startFetch(); - outfit->execute(); // Not interested in waiting and this will be right 99% of the time. - delete outfit; -//Uncomment the following code for laggy Inventory UI. - /* - if (outfit->isFinished()) - { - // everything is already here - call done. - outfit->execute(); - delete outfit; - } - else - { - // it's all on its way - add an observer, and the inventory - // will call done for us when everything is here. - gInventory.addObserver(outfit); - } - */ - } - // Perform the subfolders fetch : this is where we truly recurse down the folder hierarchy - if (categories) - { - categories->startFetch(); - if (categories->isFinished()) - { - // everything is already here - call done. - categories->execute(); - delete categories; - } - else - { - // it's all on its way - add an observer, and the inventory - // will call done for us when everything is here. - gInventory.addObserver(categories); - } - } - } -} - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLInventoryWearObserver -// -// Observer for "copy and wear" operation to support knowing -// when the all of the contents have been added to inventory. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLInventoryCopyAndWearObserver : public LLInventoryObserver -{ -public: - LLInventoryCopyAndWearObserver(const LLUUID& cat_id, int count, bool folder_added=false, bool replace=false) : - mCatID(cat_id), mContentsCount(count), mFolderAdded(folder_added), mReplace(replace){} - virtual ~LLInventoryCopyAndWearObserver() {} - virtual void changed(U32 mask); - -protected: - LLUUID mCatID; - int mContentsCount; - bool mFolderAdded; - bool mReplace; -}; - - - -void LLInventoryCopyAndWearObserver::changed(U32 mask) -{ - if((mask & (LLInventoryObserver::ADD)) != 0) - { - if (!mFolderAdded) - { - const std::set& changed_items = gInventory.getChangedIDs(); - - std::set::const_iterator id_it = changed_items.begin(); - std::set::const_iterator id_end = changed_items.end(); - for (;id_it != id_end; ++id_it) - { - if ((*id_it) == mCatID) - { - mFolderAdded = true; - break; - } - } - } - - if (mFolderAdded) - { - LLViewerInventoryCategory* category = gInventory.getCategory(mCatID); - if (NULL == category) - { - LL_WARNS() << "gInventory.getCategory(" << mCatID - << ") was NULL" << LL_ENDL; - } - else - { - if (category->getDescendentCount() == - mContentsCount) - { - gInventory.removeObserver(this); - LLAppearanceMgr::instance().wearInventoryCategory(category, false, !mReplace); - delete this; - } - } - } - - } -} - - - -void LLFolderBridge::performAction(LLInventoryModel* model, std::string action) -{ - if ("open" == action) - { - LLFolderViewFolder *f = dynamic_cast(mInventoryPanel.get()->getItemByID(mUUID)); - if (f) - { - f->toggleOpen(); - } - - return; - } - else if ("thumbnail" == action) - { - LLSD data(mUUID); - LLFloaterReg::showInstance("change_item_thumbnail", data); - return; - } - else if ("paste" == action) - { - pasteFromClipboard(); - return; - } - else if ("paste_link" == action) - { - pasteLinkFromClipboard(); - return; - } - else if ("properties" == action) - { - showProperties(); - return; - } - else if ("replaceoutfit" == action) - { - modifyOutfit(false); - return; - } - else if ("addtooutfit" == action) - { - modifyOutfit(true); - return; - } - else if ("show_in_main_panel" == action) - { - LLInventoryPanel::openInventoryPanelAndSetSelection(true, mUUID, true); - return; - } - else if ("cut" == action) - { - cutToClipboard(); - return; - } - else if ("copy" == action) - { - copyToClipboard(); - return; - } - else if ("removefromoutfit" == action) - { - LLInventoryModel* model = getInventoryModel(); - if(!model) return; - LLViewerInventoryCategory* cat = getCategory(); - if(!cat) return; - - LLAppearanceMgr::instance().takeOffOutfit( cat->getLinkedUUID() ); - return; - } - else if ("copyoutfittoclipboard" == action) - { - copyOutfitToClipboard(); - } - else if ("purge" == action) - { - purgeItem(model, mUUID); - return; - } - else if ("restore" == action) - { - restoreItem(); - return; - } - else if ("marketplace_list" == action) - { - if (depth_nesting_in_marketplace(mUUID) == 1) - { - LLUUID version_folder_id = LLMarketplaceData::instance().getVersionFolder(mUUID); - mMessage = ""; - - LLMarketplaceValidator::getInstance()->validateMarketplaceListings( - version_folder_id, - [this](bool result) - { - // todo: might need to ensure bridge/mUUID exists or this will cause crashes - if (!result) - { - LLSD subs; - subs["[ERROR_CODE]"] = mMessage; - LLNotificationsUtil::add("MerchantListingFailed", subs); - } - else - { - LLMarketplaceData::instance().activateListing(mUUID, true); - } - }, - boost::bind(&LLFolderBridge::gatherMessage, this, _1, _2, _3) - ); - } - return; - } - else if ("marketplace_activate" == action) - { - if (depth_nesting_in_marketplace(mUUID) == 2) - { - mMessage = ""; - - LLMarketplaceValidator::getInstance()->validateMarketplaceListings( - mUUID, - [this](bool result) - { - if (!result) - { - LLSD subs; - subs["[ERROR_CODE]"] = mMessage; - LLNotificationsUtil::add("MerchantFolderActivationFailed", subs); - } - else - { - LLInventoryCategory* category = gInventory.getCategory(mUUID); - LLMarketplaceData::instance().setVersionFolder(category->getParentUUID(), mUUID); - } - }, - boost::bind(&LLFolderBridge::gatherMessage, this, _1, _2, _3), - false, - 2); - } - return; - } - else if ("marketplace_unlist" == action) - { - if (depth_nesting_in_marketplace(mUUID) == 1) - { - LLMarketplaceData::instance().activateListing(mUUID,false,1); - } - return; - } - else if ("marketplace_deactivate" == action) - { - if (depth_nesting_in_marketplace(mUUID) == 2) - { - LLInventoryCategory* category = gInventory.getCategory(mUUID); - LLMarketplaceData::instance().setVersionFolder(category->getParentUUID(), LLUUID::null, 1); - } - return; - } - else if ("marketplace_create_listing" == action) - { - mMessage = ""; - - // first run vithout fix_hierarchy, second run with fix_hierarchy - LLMarketplaceValidator::getInstance()->validateMarketplaceListings( - mUUID, - [this](bool result) - { - if (!result) - { - mMessage = ""; - - LLMarketplaceValidator::getInstance()->validateMarketplaceListings( - mUUID, - [this](bool result) - { - if (result) - { - LLNotificationsUtil::add("MerchantForceValidateListing"); - LLMarketplaceData::instance().createListing(mUUID); - } - else - { - LLSD subs; - subs["[ERROR_CODE]"] = mMessage; - LLNotificationsUtil::add("MerchantListingFailed", subs); - } - }, - boost::bind(&LLFolderBridge::gatherMessage, this, _1, _2, _3), - true); - } - else - { - LLMarketplaceData::instance().createListing(mUUID); - } - }, - boost::bind(&LLFolderBridge::gatherMessage, this, _1, _2, _3), - false); - - return; - } - else if ("marketplace_disassociate_listing" == action) - { - LLMarketplaceData::instance().clearListing(mUUID); - return; - } - else if ("marketplace_get_listing" == action) - { - // This is used only to exercise the SLM API but won't be shown to end users - LLMarketplaceData::instance().getListing(mUUID); - return; - } - else if ("marketplace_associate_listing" == action) - { - LLFloaterAssociateListing::show(mUUID); - return; - } - else if ("marketplace_check_listing" == action) - { - LLSD data(mUUID); - LLFloaterReg::showInstance("marketplace_validation", data); - return; - } - else if ("marketplace_edit_listing" == action) - { - std::string url = LLMarketplaceData::instance().getListingURL(mUUID); - if (!url.empty()) - { - LLUrlAction::openURL(url); - } - return; - } -#ifndef LL_RELEASE_FOR_DOWNLOAD - else if ("delete_system_folder" == action) - { - removeSystemFolder(); - } -#endif - else if (("move_to_marketplace_listings" == action) || ("copy_to_marketplace_listings" == action) || ("copy_or_move_to_marketplace_listings" == action)) - { - LLInventoryCategory * cat = gInventory.getCategory(mUUID); - if (!cat) return; - const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - move_folder_to_marketplacelistings(cat, marketplacelistings_id, ("move_to_marketplace_listings" != action), (("copy_or_move_to_marketplace_listings" == action))); - } -} - -void LLFolderBridge::gatherMessage(std::string& message, S32 depth, LLError::ELevel log_level) -{ - if (log_level >= LLError::LEVEL_ERROR) - { - if (!mMessage.empty()) - { - // Currently, we do not gather all messages as it creates very long alerts - // Users can get to the whole list of errors on a listing using the "Check for Errors" audit button or "Check listing" right click menu - //mMessage += "\n"; - return; - } - // Take the leading spaces out... - std::string::size_type start = message.find_first_not_of(" "); - // Append the message - mMessage += message.substr(start, message.length() - start); - } -} - -void LLFolderBridge::copyOutfitToClipboard() -{ - std::string text; - - LLInventoryModel::cat_array_t* cat_array; - LLInventoryModel::item_array_t* item_array; - gInventory.getDirectDescendentsOf(mUUID, cat_array, item_array); - - S32 item_count(0); - if( item_array ) - { - item_count = item_array->size(); - } - - if (item_count) - { - for (S32 i = 0; i < item_count;) - { - LLSD uuid =item_array->at(i)->getUUID(); - LLViewerInventoryItem* item = gInventory.getItem(uuid); - - i++; - if (item != NULL) - { - // Append a newline to all but the last line - text += i != item_count ? item->getName() + "\n" : item->getName(); - } - } - } - - LLClipboard::instance().copyToClipboard(utf8str_to_wstring(text),0,text.size()); -} - -void LLFolderBridge::openItem() -{ - LL_DEBUGS() << "LLFolderBridge::openItem()" << LL_ENDL; - - LLInventoryPanel* panel = mInventoryPanel.get(); - if (!panel) - { - return; - } - LLInventoryModel* model = getInventoryModel(); - if (!model) - { - return; - } - if (mUUID.isNull()) - { - return; - } - panel->onFolderOpening(mUUID); - bool fetching_inventory = model->fetchDescendentsOf(mUUID); - // Only change folder type if we have the folder contents. - if (!fetching_inventory) - { - // Disabling this for now, it's causing crash when new items are added to folders - // since folder type may change before new item item has finished processing. - // determineFolderType(); - } -} - -void LLFolderBridge::closeItem() -{ - determineFolderType(); -} - -void LLFolderBridge::determineFolderType() -{ - if (isUpToDate()) - { - LLInventoryModel* model = getInventoryModel(); - LLViewerInventoryCategory* category = model->getCategory(mUUID); - if (category) - { - category->determineFolderType(); - } - } -} - -bool LLFolderBridge::isItemRenameable() const -{ - return get_is_category_renameable(getInventoryModel(), mUUID); -} - -void LLFolderBridge::restoreItem() -{ - LLViewerInventoryCategory* cat; - cat = (LLViewerInventoryCategory*)getCategory(); - if(cat) - { - LLInventoryModel* model = getInventoryModel(); - const LLUUID new_parent = model->findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(cat->getType())); - // do not restamp children on restore - LLInvFVBridge::changeCategoryParent(model, cat, new_parent, false); - } -} - -LLFolderType::EType LLFolderBridge::getPreferredType() const -{ - LLFolderType::EType preferred_type = LLFolderType::FT_NONE; - LLViewerInventoryCategory* cat = getCategory(); - if(cat) - { - preferred_type = cat->getPreferredType(); - } - - return preferred_type; -} - -// Icons for folders are based on the preferred type -LLUIImagePtr LLFolderBridge::getIcon() const -{ - return getFolderIcon(false); -} - -LLUIImagePtr LLFolderBridge::getIconOpen() const -{ - return getFolderIcon(true); -} - -LLUIImagePtr LLFolderBridge::getFolderIcon(bool is_open) const -{ - LLFolderType::EType preferred_type = getPreferredType(); - return LLUI::getUIImage(LLViewerFolderType::lookupIconName(preferred_type, is_open)); -} - -// static : use by LLLinkFolderBridge to get the closed type icons -LLUIImagePtr LLFolderBridge::getIcon(LLFolderType::EType preferred_type) -{ - return LLUI::getUIImage(LLViewerFolderType::lookupIconName(preferred_type, false)); -} - -LLUIImagePtr LLFolderBridge::getIconOverlay() const -{ - if (getInventoryObject() && getInventoryObject()->getIsLinkType()) - { - return LLUI::getUIImage("Inv_Link"); - } - return NULL; -} - -bool LLFolderBridge::renameItem(const std::string& new_name) -{ - - LLScrollOnRenameObserver *observer = new LLScrollOnRenameObserver(mUUID, mRoot); - gInventory.addObserver(observer); - - rename_category(getInventoryModel(), mUUID, new_name); - - // return false because we either notified observers (& therefore - // rebuilt) or we didn't update. - return false; -} - -bool LLFolderBridge::removeItem() -{ - if(!isItemRemovable()) - { - return false; - } - const LLViewerInventoryCategory *cat = getCategory(); - - LLSD payload; - LLSD args; - args["FOLDERNAME"] = cat->getName(); - - LLNotification::Params params("ConfirmDeleteProtectedCategory"); - params.payload(payload).substitutions(args).functor.function(boost::bind(&LLFolderBridge::removeItemResponse, this, _1, _2)); - LLNotifications::instance().forceResponse(params, 0); - return true; -} - - -bool LLFolderBridge::removeSystemFolder() -{ - const LLViewerInventoryCategory *cat = getCategory(); - if (!LLFolderType::lookupIsProtectedType(cat->getPreferredType())) - { - return false; - } - - LLSD payload; - LLSD args; - args["FOLDERNAME"] = cat->getName(); - - LLNotification::Params params("ConfirmDeleteProtectedCategory"); - params.payload(payload).substitutions(args).functor.function(boost::bind(&LLFolderBridge::removeItemResponse, this, _1, _2)); - { - LLNotifications::instance().add(params); - } - return true; -} - -bool LLFolderBridge::removeItemResponse(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotification::getSelectedOption(notification, response); - - // if they choose delete, do it. Otherwise, don't do anything - if(option == 0) - { - // move it to the trash - LLPreview::hide(mUUID); - getInventoryModel()->removeCategory(mUUID); - return true; - } - return false; -} - -//Recursively update the folder's creation date -void LLFolderBridge::updateHierarchyCreationDate(time_t date) -{ - if(getCreationDate() < date) - { - setCreationDate(date); - if(mParent) - { - static_cast(mParent)->updateHierarchyCreationDate(date); - } - } -} - -void LLFolderBridge::pasteFromClipboard() -{ - LLInventoryModel* model = getInventoryModel(); - if (model && isClipboardPasteable()) - { - const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - const bool paste_into_marketplacelistings = model->isObjectDescendentOf(mUUID, marketplacelistings_id); - - bool cut_from_marketplacelistings = false; - if (LLClipboard::instance().isCutMode()) - { - //Items are not removed from folder on "cut", so we need update listing folder on "paste" operation - std::vector objects; - LLClipboard::instance().pasteFromClipboard(objects); - for (std::vector::const_iterator iter = objects.begin(); iter != objects.end(); ++iter) - { - const LLUUID& item_id = (*iter); - if(gInventory.isObjectDescendentOf(item_id, marketplacelistings_id) && (LLMarketplaceData::instance().isInActiveFolder(item_id) || - LLMarketplaceData::instance().isListedAndActive(item_id))) - { - cut_from_marketplacelistings = true; - break; - } - } - } - if (cut_from_marketplacelistings || (paste_into_marketplacelistings && !LLMarketplaceData::instance().isListed(mUUID) && LLMarketplaceData::instance().isInActiveFolder(mUUID))) - { - // Prompt the user if pasting in a marketplace active version listing (note that pasting right under the listing folder root doesn't need a prompt) - LLNotificationsUtil::add("ConfirmMerchantActiveChange", LLSD(), LLSD(), boost::bind(&LLFolderBridge::callback_pasteFromClipboard, this, _1, _2)); - } - else - { - // Otherwise just do the paste - perform_pasteFromClipboard(); - } - } -} - -// Callback for pasteFromClipboard if DAMA required... -void LLFolderBridge::callback_pasteFromClipboard(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) // YES - { - std::vector objects; - std::set parent_folders; - LLClipboard::instance().pasteFromClipboard(objects); - for (std::vector::const_iterator iter = objects.begin(); iter != objects.end(); ++iter) - { - const LLInventoryObject* obj = gInventory.getObject(*iter); - parent_folders.insert(obj->getParentUUID()); - } - perform_pasteFromClipboard(); - for (std::set::const_iterator iter = parent_folders.begin(); iter != parent_folders.end(); ++iter) - { - gInventory.addChangedMask(LLInventoryObserver::STRUCTURE, *iter); - } - - } -} - -void LLFolderBridge::perform_pasteFromClipboard() -{ - LLInventoryModel* model = getInventoryModel(); - if (model && isClipboardPasteable()) - { - const LLUUID ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); - const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - const LLUUID &favorites_id = model->findCategoryUUIDForType(LLFolderType::FT_FAVORITE); - const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); - const LLUUID &lost_and_found_id = model->findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); - - const bool move_is_into_current_outfit = (mUUID == current_outfit_id); - const bool move_is_into_my_outfits = (mUUID == my_outifts_id) || model->isObjectDescendentOf(mUUID, my_outifts_id); - const bool move_is_into_outfit = move_is_into_my_outfits || (getCategory() && getCategory()->getPreferredType()==LLFolderType::FT_OUTFIT); - const bool move_is_into_marketplacelistings = model->isObjectDescendentOf(mUUID, marketplacelistings_id); - const bool move_is_into_favorites = (mUUID == favorites_id); - const bool move_is_into_lost_and_found = model->isObjectDescendentOf(mUUID, lost_and_found_id); - - std::vector objects; - LLClipboard::instance().pasteFromClipboard(objects); - - LLPointer cb = NULL; - LLInventoryPanel* panel = mInventoryPanel.get(); - if (panel->getRootFolder()->isSingleFolderMode() && panel->getRootFolderID() == mUUID) - { - cb = new LLPasteIntoFolderCallback(mInventoryPanel); - } - - LLViewerInventoryCategory * dest_folder = getCategory(); - if (move_is_into_marketplacelistings) - { - std::string error_msg; - const LLViewerInventoryCategory * master_folder = model->getFirstDescendantOf(marketplacelistings_id, mUUID); - int index = 0; - for (std::vector::const_iterator iter = objects.begin(); iter != objects.end(); ++iter) - { - const LLUUID& item_id = (*iter); - LLInventoryItem *item = model->getItem(item_id); - LLInventoryCategory *cat = model->getCategory(item_id); - - if (item && !can_move_item_to_marketplace(master_folder, dest_folder, item, error_msg, objects.size() - index, true)) - { - break; - } - if (cat && !can_move_folder_to_marketplace(master_folder, dest_folder, cat, error_msg, objects.size() - index, true, true)) - { - break; - } - ++index; - } - if (!error_msg.empty()) - { - LLSD subs; - subs["[ERROR_CODE]"] = error_msg; - LLNotificationsUtil::add("MerchantPasteFailed", subs); - return; - } - } - else - { - // Check that all items can be moved into that folder : for the moment, only stock folder mismatch is checked - for (std::vector::const_iterator iter = objects.begin(); iter != objects.end(); ++iter) - { - const LLUUID& item_id = (*iter); - LLInventoryItem *item = model->getItem(item_id); - LLInventoryCategory *cat = model->getCategory(item_id); - - if ((item && !dest_folder->acceptItem(item)) || (cat && (dest_folder->getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK))) - { - std::string error_msg = LLTrans::getString("TooltipOutboxMixedStock"); - LLSD subs; - subs["[ERROR_CODE]"] = error_msg; - LLNotificationsUtil::add("StockPasteFailed", subs); - return; - } - } - } - - const LLUUID parent_id(mUUID); - - for (std::vector::const_iterator iter = objects.begin(); - iter != objects.end(); - ++iter) - { - const LLUUID& item_id = (*iter); - - LLInventoryItem *item = model->getItem(item_id); - LLInventoryObject *obj = model->getObject(item_id); - if (obj) - { - - if (move_is_into_lost_and_found) - { - if (LLAssetType::AT_CATEGORY == obj->getType()) - { - return; - } - } - if (move_is_into_outfit) - { - if (!move_is_into_my_outfits && item && can_move_to_outfit(item, move_is_into_current_outfit)) - { - dropToOutfit(item, move_is_into_current_outfit, cb); - } - else if (move_is_into_my_outfits && LLAssetType::AT_CATEGORY == obj->getType()) - { - LLInventoryCategory* cat = model->getCategory(item_id); - U32 max_items_to_wear = gSavedSettings.getU32("WearFolderLimit"); - if (cat && can_move_to_my_outfits(model, cat, max_items_to_wear)) - { - dropToMyOutfits(cat, cb); - } - else - { - LLNotificationsUtil::add("MyOutfitsPasteFailed"); - } - } - else - { - LLNotificationsUtil::add("MyOutfitsPasteFailed"); - } - } - else if (move_is_into_current_outfit) - { - if (item && can_move_to_outfit(item, move_is_into_current_outfit)) - { - dropToOutfit(item, move_is_into_current_outfit, cb); - } - else - { - LLNotificationsUtil::add("MyOutfitsPasteFailed"); - } - } - else if (move_is_into_favorites) - { - if (item && can_move_to_landmarks(item)) - { - if (LLClipboard::instance().isCutMode()) - { - LLViewerInventoryItem* viitem = dynamic_cast(item); - llassert(viitem); - if (viitem) - { - //changeItemParent() implicity calls dirtyFilter - changeItemParent(model, viitem, parent_id, false); - if (cb) cb->fire(item_id); - } - } - else - { - dropToFavorites(item, cb); - } - } - } - else if (LLClipboard::instance().isCutMode()) - { - // Do a move to "paste" a "cut" - // move_inventory_item() is not enough, as we have to update inventory locally too - if (LLAssetType::AT_CATEGORY == obj->getType()) - { - LLViewerInventoryCategory* vicat = (LLViewerInventoryCategory *) model->getCategory(item_id); - llassert(vicat); - if (vicat) - { - // Clear the cut folder from the marketplace if it is a listing folder - if (LLMarketplaceData::instance().isListed(item_id)) - { - LLMarketplaceData::instance().clearListing(item_id); - } - if (move_is_into_marketplacelistings) - { - move_folder_to_marketplacelistings(vicat, parent_id); - } - else - { - //changeCategoryParent() implicity calls dirtyFilter - changeCategoryParent(model, vicat, parent_id, false); - } - if (cb) cb->fire(item_id); - } - } - else - { - LLViewerInventoryItem* viitem = dynamic_cast(item); - llassert(viitem); - if (viitem) - { - if (move_is_into_marketplacelistings) - { - if (!move_item_to_marketplacelistings(viitem, parent_id)) - { - // Stop pasting into the marketplace as soon as we get an error - break; - } - } - else - { - //changeItemParent() implicity calls dirtyFilter - changeItemParent(model, viitem, parent_id, false); - } - if (cb) cb->fire(item_id); - } - } - } - else - { - // Do a "copy" to "paste" a regular copy clipboard - if (LLAssetType::AT_CATEGORY == obj->getType()) - { - LLViewerInventoryCategory* vicat = (LLViewerInventoryCategory *) model->getCategory(item_id); - llassert(vicat); - if (vicat) - { - if (move_is_into_marketplacelistings) - { - move_folder_to_marketplacelistings(vicat, parent_id, true); - } - else - { - copy_inventory_category(model, vicat, parent_id); - } - if (cb) cb->fire(item_id); - } - } - else - { - LLViewerInventoryItem* viitem = dynamic_cast(item); - llassert(viitem); - if (viitem) - { - if (move_is_into_marketplacelistings) - { - if (!move_item_to_marketplacelistings(viitem, parent_id, true)) - { - // Stop pasting into the marketplace as soon as we get an error - break; - } - if (cb) cb->fire(item_id); - } - else if (item->getIsLinkType()) - { - link_inventory_object(parent_id, - item_id, - cb); - } - else - { - copy_inventory_item( - gAgent.getID(), - item->getPermissions().getOwner(), - item->getUUID(), - parent_id, - std::string(), - cb); - } - } - } - } - } - } - // Change mode to paste for next paste - LLClipboard::instance().setCutMode(false); - } -} - -void LLFolderBridge::pasteLinkFromClipboard() -{ - LLInventoryModel* model = getInventoryModel(); - if(model) - { - const LLUUID ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); - const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); - - const bool move_is_into_current_outfit = (mUUID == current_outfit_id); - const bool move_is_into_my_outfits = (mUUID == my_outifts_id) || model->isObjectDescendentOf(mUUID, my_outifts_id); - const bool move_is_into_outfit = move_is_into_my_outfits || (getCategory() && getCategory()->getPreferredType()==LLFolderType::FT_OUTFIT); - const bool move_is_into_marketplacelistings = model->isObjectDescendentOf(mUUID, marketplacelistings_id); - - if (move_is_into_marketplacelistings) - { - // Notify user of failure somehow -- play error sound? modal dialog? - return; - } - - const LLUUID parent_id(mUUID); - - std::vector objects; - LLClipboard::instance().pasteFromClipboard(objects); - - LLPointer cb = NULL; - LLInventoryPanel* panel = mInventoryPanel.get(); - if (panel->getRootFolder()->isSingleFolderMode()) - { - cb = new LLPasteIntoFolderCallback(mInventoryPanel); - } - - for (std::vector::const_iterator iter = objects.begin(); - iter != objects.end(); - ++iter) - { - const LLUUID &object_id = (*iter); - if (move_is_into_current_outfit || move_is_into_outfit) - { - LLInventoryItem *item = model->getItem(object_id); - if (item && can_move_to_outfit(item, move_is_into_current_outfit)) - { - dropToOutfit(item, move_is_into_current_outfit, cb); - } - } - else if (LLConstPointer obj = model->getObject(object_id)) - { - link_inventory_object(parent_id, obj, cb); - } - } - // Change mode to paste for next paste - LLClipboard::instance().setCutMode(false); - } -} - -void LLFolderBridge::staticFolderOptionsMenu() -{ - LLFolderBridge* selfp = sSelf.get(); - - if (selfp && selfp->mRoot) - { - selfp->mRoot->updateMenu(); - } -} - -bool LLFolderBridge::checkFolderForContentsOfType(LLInventoryModel* model, LLInventoryCollectFunctor& is_type) -{ - LLInventoryModel::cat_array_t cat_array; - LLInventoryModel::item_array_t item_array; - model->collectDescendentsIf(mUUID, - cat_array, - item_array, - LLInventoryModel::EXCLUDE_TRASH, - is_type); - return !item_array.empty(); -} - -void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items, menuentry_vec_t& disabled_items) -{ - LLInventoryModel* model = getInventoryModel(); - llassert(model != NULL); - - const LLUUID &trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); - const LLUUID &lost_and_found_id = model->findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); - const LLUUID &favorites = model->findCategoryUUIDForType(LLFolderType::FT_FAVORITE); - const LLUUID &marketplace_listings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - const LLUUID &outfits_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); - - if (outfits_id == mUUID) - { - items.push_back(std::string("New Outfit")); - } - - if (lost_and_found_id == mUUID) - { - // This is the lost+found folder. - items.push_back(std::string("Empty Lost And Found")); - - LLInventoryModel::cat_array_t* cat_array; - LLInventoryModel::item_array_t* item_array; - gInventory.getDirectDescendentsOf(mUUID, cat_array, item_array); - // Enable Empty menu item only when there is something to act upon. - if (0 == cat_array->size() && 0 == item_array->size()) - { - disabled_items.push_back(std::string("Empty Lost And Found")); - } - - disabled_items.push_back(std::string("New Folder")); - disabled_items.push_back(std::string("upload_def")); - disabled_items.push_back(std::string("create_new")); - } - if (favorites == mUUID) - { - disabled_items.push_back(std::string("New Folder")); - } - if (isMarketplaceListingsFolder()) - { - addMarketplaceContextMenuOptions(flags, items, disabled_items); - if (LLMarketplaceData::instance().isUpdating(mUUID)) - { - disabled_items.push_back(std::string("New Folder")); - disabled_items.push_back(std::string("Rename")); - disabled_items.push_back(std::string("Cut")); - disabled_items.push_back(std::string("Copy")); - disabled_items.push_back(std::string("Paste")); - disabled_items.push_back(std::string("Delete")); - } - } - if (getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK) - { - disabled_items.push_back(std::string("New Folder")); - disabled_items.push_back(std::string("upload_def")); - disabled_items.push_back(std::string("create_new")); - } - if (marketplace_listings_id == mUUID) - { - disabled_items.push_back(std::string("New Folder")); - disabled_items.push_back(std::string("Rename")); - disabled_items.push_back(std::string("Cut")); - disabled_items.push_back(std::string("Delete")); - } - - if (isPanelActive("Favorite Items")) - { - disabled_items.push_back(std::string("Delete")); - } - if(trash_id == mUUID) - { - bool is_recent_panel = isPanelActive("Recent Items"); - - // This is the trash. - items.push_back(std::string("Empty Trash")); - - LLInventoryModel::cat_array_t* cat_array; - LLInventoryModel::item_array_t* item_array; - gInventory.getDirectDescendentsOf(mUUID, cat_array, item_array); - LLViewerInventoryCategory *trash = getCategory(); - // Enable Empty menu item only when there is something to act upon. - // Also don't enable menu if folder isn't fully fetched - if ((0 == cat_array->size() && 0 == item_array->size()) - || is_recent_panel - || !trash - || trash->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN - || trash->getDescendentCount() == LLViewerInventoryCategory::VERSION_UNKNOWN - || gAgentAvatarp->hasAttachmentsInTrash()) - { - disabled_items.push_back(std::string("Empty Trash")); - } - - items.push_back(std::string("thumbnail")); - } - else if(isItemInTrash()) - { - // This is a folder in the trash. - items.clear(); // clear any items that used to exist - addTrashContextMenuOptions(items, disabled_items); - } - else if(isAgentInventory()) // do not allow creating in library - { - LLViewerInventoryCategory *cat = getCategory(); - // BAP removed protected check to re-enable standard ops in untyped folders. - // Not sure what the right thing is to do here. - if (!isCOFFolder() && cat && (cat->getPreferredType() != LLFolderType::FT_OUTFIT)) - { - if (!isInboxFolder() // don't allow creation in inbox - && outfits_id != mUUID) - { - bool menu_items_added = false; - // Do not allow to create 2-level subfolder in the Calling Card/Friends folder. EXT-694. - if (!LLFriendCardsManager::instance().isCategoryInFriendFolder(cat)) - { - items.push_back(std::string("New Folder")); - menu_items_added = true; - } - if (!isMarketplaceListingsFolder()) - { - items.push_back(std::string("upload_def")); - items.push_back(std::string("create_new")); - items.push_back(std::string("New Script")); - items.push_back(std::string("New Note")); - items.push_back(std::string("New Gesture")); - items.push_back(std::string("New Material")); - items.push_back(std::string("New Clothes")); - items.push_back(std::string("New Body Parts")); - items.push_back(std::string("New Settings")); - if (!LLEnvironment::instance().isInventoryEnabled()) - { - disabled_items.push_back("New Settings"); - } - } - else - { - items.push_back(std::string("New Listing Folder")); - } - if (menu_items_added) - { - items.push_back(std::string("Create Separator")); - } - } - getClipboardEntries(false, items, disabled_items, flags); - } - else - { - // Want some but not all of the items from getClipboardEntries for outfits. - if (cat && (cat->getPreferredType() == LLFolderType::FT_OUTFIT)) - { - items.push_back(std::string("Rename")); - items.push_back(std::string("thumbnail")); - - addDeleteContextMenuOptions(items, disabled_items); - // EXT-4030: disallow deletion of currently worn outfit - const LLViewerInventoryItem *base_outfit_link = LLAppearanceMgr::instance().getBaseOutfitLink(); - if (base_outfit_link && (cat == base_outfit_link->getLinkedCategory())) - { - disabled_items.push_back(std::string("Delete")); - } - } - } - - if (model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT) == mUUID) - { - items.push_back(std::string("Copy outfit list to clipboard")); - addOpenFolderMenuOptions(flags, items); - } - - //Added by aura to force inventory pull on right-click to display folder options correctly. 07-17-06 - mCallingCards = mWearables = false; - - LLIsType is_callingcard(LLAssetType::AT_CALLINGCARD); - if (checkFolderForContentsOfType(model, is_callingcard)) - { - mCallingCards=true; - } - - LLFindWearables is_wearable; - LLIsType is_object( LLAssetType::AT_OBJECT ); - LLIsType is_gesture( LLAssetType::AT_GESTURE ); - - if (checkFolderForContentsOfType(model, is_wearable) || - checkFolderForContentsOfType(model, is_object) || - checkFolderForContentsOfType(model, is_gesture) ) - { - mWearables=true; - } - } - else - { - // Mark wearables and allow copy from library - LLInventoryModel* model = getInventoryModel(); - if(!model) return; - const LLInventoryCategory* category = model->getCategory(mUUID); - if (!category) return; - LLFolderType::EType type = category->getPreferredType(); - const bool is_system_folder = LLFolderType::lookupIsProtectedType(type); - - LLFindWearables is_wearable; - LLIsType is_object(LLAssetType::AT_OBJECT); - LLIsType is_gesture(LLAssetType::AT_GESTURE); - - if (checkFolderForContentsOfType(model, is_wearable) || - checkFolderForContentsOfType(model, is_object) || - checkFolderForContentsOfType(model, is_gesture)) - { - mWearables = true; - } - - if (!is_system_folder) - { - items.push_back(std::string("Copy")); - if (!isItemCopyable()) - { - // For some reason there are items in library that can't be copied directly - disabled_items.push_back(std::string("Copy")); - } - } - } - - // Preemptively disable system folder removal if more than one item selected. - if ((flags & FIRST_SELECTED_ITEM) == 0) - { - disabled_items.push_back(std::string("Delete System Folder")); - } - - if (isAgentInventory() && !isMarketplaceListingsFolder()) - { - items.push_back(std::string("Share")); - if (!canShare()) - { - disabled_items.push_back(std::string("Share")); - } - } - - - - // Add menu items that are dependent on the contents of the folder. - LLViewerInventoryCategory* category = (LLViewerInventoryCategory *) model->getCategory(mUUID); - if (category && (marketplace_listings_id != mUUID)) - { - uuid_vec_t folders; - folders.push_back(category->getUUID()); - - sSelf = getHandle(); - LLRightClickInventoryFetchDescendentsObserver* fetch = new LLRightClickInventoryFetchDescendentsObserver(folders); - fetch->startFetch(); - if (fetch->isFinished()) - { - // Do not call execute() or done() here as if the folder is here, there's likely no point drilling down - // This saves lots of time as buildContextMenu() is called a lot - delete fetch; - buildContextMenuFolderOptions(flags, items, disabled_items); - } - else - { - // it's all on its way - add an observer, and the inventory will call done for us when everything is here. - gInventory.addObserver(fetch); - } - } -} - -void LLFolderBridge::buildContextMenuFolderOptions(U32 flags, menuentry_vec_t& items, menuentry_vec_t& disabled_items) -{ - // Build folder specific options back up - LLInventoryModel* model = getInventoryModel(); - if(!model) return; - - const LLInventoryCategory* category = model->getCategory(mUUID); - if(!category) return; - - const LLUUID trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); - if ((trash_id == mUUID) || isItemInTrash()) - { - addOpenFolderMenuOptions(flags, items); - return; - } - - if (!canMenuDelete()) - { - disabled_items.push_back(std::string("Delete")); - } - if (isMarketplaceListingsFolder()) return; - - LLFolderType::EType type = category->getPreferredType(); - const bool is_system_folder = LLFolderType::lookupIsProtectedType(type); - // BAP change once we're no longer treating regular categories as ensembles. - const bool is_agent_inventory = isAgentInventory(); - - // Only enable calling-card related options for non-system folders. - if (!is_system_folder && is_agent_inventory && (mRoot != NULL)) - { - LLIsType is_callingcard(LLAssetType::AT_CALLINGCARD); - if (mCallingCards || checkFolderForContentsOfType(model, is_callingcard)) - { - items.push_back(std::string("Calling Card Separator")); - items.push_back(std::string("Conference Chat Folder")); - items.push_back(std::string("IM All Contacts In Folder")); - } - - if (((flags & ITEM_IN_MULTI_SELECTION) == 0) && hasChildren() && (type != LLFolderType::FT_OUTFIT)) - { - items.push_back(std::string("Ungroup folder items")); - } - } - else - { - disabled_items.push_back(std::string("New folder from selected")); - } - - //skip the rest options in single-folder mode - if (mRoot == NULL) - { - return; - } - - addOpenFolderMenuOptions(flags, items); - -#ifndef LL_RELEASE_FOR_DOWNLOAD - if (LLFolderType::lookupIsProtectedType(type) && is_agent_inventory) - { - items.push_back(std::string("Delete System Folder")); - } -#endif - - // wearables related functionality for folders. - //is_wearable - LLFindWearables is_wearable; - LLIsType is_object( LLAssetType::AT_OBJECT ); - LLIsType is_gesture( LLAssetType::AT_GESTURE ); - - if (mWearables || - checkFolderForContentsOfType(model, is_wearable) || - checkFolderForContentsOfType(model, is_object) || - checkFolderForContentsOfType(model, is_gesture) ) - { - // Only enable add/replace outfit for non-system folders. - if (!is_system_folder) - { - // Adding an outfit onto another (versus replacing) doesn't make sense. - if (type != LLFolderType::FT_OUTFIT) - { - items.push_back(std::string("Add To Outfit")); - if (!LLAppearanceMgr::instance().getCanAddToCOF(mUUID)) - { - disabled_items.push_back(std::string("Add To Outfit")); - } - } - - items.push_back(std::string("Replace Outfit")); - if (!LLAppearanceMgr::instance().getCanReplaceCOF(mUUID)) - { - disabled_items.push_back(std::string("Replace Outfit")); - } - } - if (is_agent_inventory) - { - items.push_back(std::string("Folder Wearables Separator")); - // Note: If user tries to unwear "My Inventory", it's going to deactivate everything including gestures - // Might be safer to disable this for "My Inventory" - items.push_back(std::string("Remove From Outfit")); - if (type != LLFolderType::FT_ROOT_INVENTORY // Unless COF is empty, whih shouldn't be, warrantied to have worn items - && !LLAppearanceMgr::getCanRemoveFromCOF(mUUID)) // expensive from root! - { - disabled_items.push_back(std::string("Remove From Outfit")); - } - } - items.push_back(std::string("Outfit Separator")); - - } -} - -// Flags unused -void LLFolderBridge::buildContextMenu(LLMenuGL& menu, U32 flags) -{ - sSelf.markDead(); - - // fetch contents of this folder, as context menu can depend on contents - // still, user would have to open context menu again to see the changes - gInventory.fetchDescendentsOf(getUUID()); - - - menuentry_vec_t items; - menuentry_vec_t disabled_items; - - LL_DEBUGS() << "LLFolderBridge::buildContextMenu()" << LL_ENDL; - - LLInventoryModel* model = getInventoryModel(); - if(!model) return; - - buildContextMenuOptions(flags, items, disabled_items); - hide_context_entries(menu, items, disabled_items); - - // Reposition the menu, in case we're adding items to an existing menu. - menu.needsArrange(); - menu.arrangeAndClear(); -} - -void LLFolderBridge::addOpenFolderMenuOptions(U32 flags, menuentry_vec_t& items) -{ - if ((flags & ITEM_IN_MULTI_SELECTION) == 0) - { - items.push_back(std::string("open_in_new_window")); - items.push_back(std::string("Open Folder Separator")); - items.push_back(std::string("Copy Separator")); - if(isPanelActive("comb_single_folder_inv")) - { - items.push_back(std::string("open_in_current_window")); - } - } -} - -bool LLFolderBridge::hasChildren() const -{ - LLInventoryModel* model = getInventoryModel(); - if(!model) return false; - LLInventoryModel::EHasChildren has_children; - has_children = gInventory.categoryHasChildren(mUUID); - return has_children != LLInventoryModel::CHILDREN_NO; -} - -bool LLFolderBridge::dragOrDrop(MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - std::string& tooltip_msg) -{ - LLInventoryItem* inv_item = (LLInventoryItem*)cargo_data; - - static LLPointer drop_cb = NULL; - LLInventoryPanel* panel = mInventoryPanel.get(); - LLToolDragAndDrop* drop_tool = LLToolDragAndDrop::getInstance(); - if (drop - && panel->getRootFolder()->isSingleFolderMode() - && panel->getRootFolderID() == mUUID - && drop_tool->getCargoIndex() == 0) - { - drop_cb = new LLPasteIntoFolderCallback(mInventoryPanel); - } - - - //LL_INFOS() << "LLFolderBridge::dragOrDrop()" << LL_ENDL; - bool accept = false; - switch(cargo_type) - { - case DAD_TEXTURE: - case DAD_SOUND: - case DAD_CALLINGCARD: - case DAD_LANDMARK: - case DAD_SCRIPT: - case DAD_CLOTHING: - case DAD_OBJECT: - case DAD_NOTECARD: - case DAD_BODYPART: - case DAD_ANIMATION: - case DAD_GESTURE: - case DAD_MESH: - case DAD_SETTINGS: - case DAD_MATERIAL: - accept = dragItemIntoFolder(inv_item, drop, tooltip_msg, true, drop_cb); - break; - case DAD_LINK: - // DAD_LINK type might mean one of two asset types: AT_LINK or AT_LINK_FOLDER. - // If we have an item of AT_LINK_FOLDER type we should process the linked - // category being dragged or dropped into folder. - if (inv_item && LLAssetType::AT_LINK_FOLDER == inv_item->getActualType()) - { - LLInventoryCategory* linked_category = gInventory.getCategory(inv_item->getLinkedUUID()); - if (linked_category) - { - accept = dragCategoryIntoFolder((LLInventoryCategory*)linked_category, drop, tooltip_msg, true, true, drop_cb); - } - } - else - { - accept = dragItemIntoFolder(inv_item, drop, tooltip_msg, true, drop_cb); - } - break; - case DAD_CATEGORY: - if (LLFriendCardsManager::instance().isAnyFriendCategory(mUUID)) - { - accept = false; - } - else - { - accept = dragCategoryIntoFolder((LLInventoryCategory*)cargo_data, drop, tooltip_msg, false, true, drop_cb); - } - break; - case DAD_ROOT_CATEGORY: - case DAD_NONE: - break; - default: - LL_WARNS() << "Unhandled cargo type for drag&drop " << cargo_type << LL_ENDL; - break; - } - - if (!drop || drop_tool->getCargoIndex() + 1 == drop_tool->getCargoCount()) - { - drop_cb = NULL; - } - return accept; -} - -LLViewerInventoryCategory* LLFolderBridge::getCategory() const -{ - LLViewerInventoryCategory* cat = NULL; - LLInventoryModel* model = getInventoryModel(); - if(model) - { - cat = (LLViewerInventoryCategory*)model->getCategory(mUUID); - } - return cat; -} - - -// static -void LLFolderBridge::pasteClipboard(void* user_data) -{ - LLFolderBridge* self = (LLFolderBridge*)user_data; - if(self) self->pasteFromClipboard(); -} - -void LLFolderBridge::createNewShirt(void* user_data) -{ - LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_SHIRT); -} - -void LLFolderBridge::createNewPants(void* user_data) -{ - LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_PANTS); -} - -void LLFolderBridge::createNewShoes(void* user_data) -{ - LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_SHOES); -} - -void LLFolderBridge::createNewSocks(void* user_data) -{ - LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_SOCKS); -} - -void LLFolderBridge::createNewJacket(void* user_data) -{ - LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_JACKET); -} - -void LLFolderBridge::createNewSkirt(void* user_data) -{ - LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_SKIRT); -} - -void LLFolderBridge::createNewGloves(void* user_data) -{ - LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_GLOVES); -} - -void LLFolderBridge::createNewUndershirt(void* user_data) -{ - LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_UNDERSHIRT); -} - -void LLFolderBridge::createNewUnderpants(void* user_data) -{ - LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_UNDERPANTS); -} - -void LLFolderBridge::createNewShape(void* user_data) -{ - LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_SHAPE); -} - -void LLFolderBridge::createNewSkin(void* user_data) -{ - LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_SKIN); -} - -void LLFolderBridge::createNewHair(void* user_data) -{ - LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_HAIR); -} - -void LLFolderBridge::createNewEyes(void* user_data) -{ - LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_EYES); -} - -EInventorySortGroup LLFolderBridge::getSortGroup() const -{ - LLFolderType::EType preferred_type = getPreferredType(); - - if (preferred_type == LLFolderType::FT_TRASH) - { - return SG_TRASH_FOLDER; - } - - if(LLFolderType::lookupIsProtectedType(preferred_type)) - { - return SG_SYSTEM_FOLDER; - } - - return SG_NORMAL_FOLDER; -} - - -// static -void LLFolderBridge::createWearable(LLFolderBridge* bridge, LLWearableType::EType type) -{ - if(!bridge) return; - LLUUID parent_id = bridge->getUUID(); - LLAgentWearables::createWearable(type, false, parent_id); -} - -void LLFolderBridge::modifyOutfit(bool append) -{ - LLInventoryModel* model = getInventoryModel(); - if(!model) return; - LLViewerInventoryCategory* cat = getCategory(); - if(!cat) return; - - // checking amount of items to wear - U32 max_items = gSavedSettings.getU32("WearFolderLimit"); - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - LLFindWearablesEx not_worn(/*is_worn=*/ false, /*include_body_parts=*/ false); - gInventory.collectDescendentsIf(cat->getUUID(), - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - not_worn); - - if (items.size() > max_items) - { - LLSD args; - args["AMOUNT"] = llformat("%d", max_items); - LLNotificationsUtil::add("TooManyWearables", args); - return; - } - - if (isAgentInventory()) - { - LLAppearanceMgr::instance().wearInventoryCategory(cat, false, append); - } - else - { - // Library, we need to copy content first - LLAppearanceMgr::instance().wearInventoryCategory(cat, true, append); - } -} - -//static -void LLFolderBridge::onCanDeleteIdle(void* user_data) -{ - LLFolderBridge* self = (LLFolderBridge*)user_data; - - // we really need proper onidle mechanics that returns available time - const F32 EXPIRY_SECONDS = 0.008f; - LLTimer timer; - timer.setTimerExpirySec(EXPIRY_SECONDS); - - LLInventoryModel* model = self->getInventoryModel(); - if (model) - { - switch (self->mCanDeleteFolderState) - { - case CDS_INIT_FOLDER_CHECK: - // Can still be expensive, split it further? - model->collectDescendents( - self->mUUID, - self->mFoldersToCheck, - self->mItemsToCheck, - LLInventoryModel::EXCLUDE_TRASH); - self->mCanDeleteFolderState = CDS_PROCESSING_ITEMS; - break; - - case CDS_PROCESSING_ITEMS: - while (!timer.hasExpired() && !self->mItemsToCheck.empty()) - { - LLViewerInventoryItem* item = self->mItemsToCheck.back().get(); - if (item) - { - if (LLAppearanceMgr::instance().getIsProtectedCOFItem(item)) - { - if (get_is_item_worn(item)) - { - // At the moment we disable 'cut' if category has worn items (do we need to?) - // but allow 'delete' to happen since it will prompt user to detach - self->mCanCut = false; - } - } - - if (!item->getIsLinkType() && get_is_item_worn(item)) - { - self->mCanCut = false; - } - } - self->mItemsToCheck.pop_back(); - } - self->mCanDeleteFolderState = CDS_PROCESSING_FOLDERS; - break; - case CDS_PROCESSING_FOLDERS: - { - const LLViewerInventoryItem* base_outfit_link = LLAppearanceMgr::instance().getBaseOutfitLink(); - LLViewerInventoryCategory* outfit_linked_category = base_outfit_link ? base_outfit_link->getLinkedCategory() : nullptr; - - while (!timer.hasExpired() && !self->mFoldersToCheck.empty()) - { - LLViewerInventoryCategory* cat = self->mFoldersToCheck.back().get(); - if (cat) - { - const LLFolderType::EType folder_type = cat->getPreferredType(); - if (LLFolderType::lookupIsProtectedType(folder_type)) - { - self->mCanCut = false; - self->mCanDelete = false; - self->completeDeleteProcessing(); - break; - } - - // Can't delete the outfit that is currently being worn. - if (folder_type == LLFolderType::FT_OUTFIT) - { - if (cat == outfit_linked_category) - { - self->mCanCut = false; - self->mCanDelete = false; - self->completeDeleteProcessing(); - break; - } - } - } - self->mFoldersToCheck.pop_back(); - } - } - self->mCanDeleteFolderState = CDS_DONE; - break; - case CDS_DONE: - self->completeDeleteProcessing(); - break; - } - } -} - -bool LLFolderBridge::canMenuDelete() -{ - LLInventoryModel* model = getInventoryModel(); - if (!model) return false; - LLViewerInventoryCategory* category = (LLViewerInventoryCategory*)model->getCategory(mUUID); - if (!category) - { - return false; - } - - S32 version = category->getVersion(); - if (mLastCheckedVersion == version) - { - return mCanDelete; - } - - initCanDeleteProcessing(model, version); - return false; -} - -bool LLFolderBridge::canMenuCut() -{ - LLInventoryModel* model = getInventoryModel(); - if (!model) return false; - LLViewerInventoryCategory* category = (LLViewerInventoryCategory*)model->getCategory(mUUID); - if (!category) - { - return false; - } - - S32 version = category->getVersion(); - if (mLastCheckedVersion == version) - { - return mCanCut; - } - - initCanDeleteProcessing(model, version); - return false; -} - -void LLFolderBridge::initCanDeleteProcessing(LLInventoryModel* model, S32 version) -{ - if (mCanDeleteFolderState == CDS_DONE - || mInProgressVersion != version) - { - if (get_is_category_removable(model, mUUID)) - { - // init recursive check of content - mInProgressVersion = version; - mCanCut = true; - mCanDelete = true; - mCanDeleteFolderState = CDS_INIT_FOLDER_CHECK; - mFoldersToCheck.clear(); - mItemsToCheck.clear(); - gIdleCallbacks.addFunction(onCanDeleteIdle, this); - } - else - { - // no check needed - mCanDelete = false; - mCanCut = false; - mLastCheckedVersion = version; - mCanDeleteFolderState = CDS_DONE; - mFoldersToCheck.clear(); - mItemsToCheck.clear(); - } - } -} - -void LLFolderBridge::completeDeleteProcessing() -{ - LLInventoryModel* model = getInventoryModel(); - LLViewerInventoryCategory* category = model ? (LLViewerInventoryCategory*)model->getCategory(mUUID) : nullptr; - if (model && category && category->getVersion() == mInProgressVersion) - { - mLastCheckedVersion = mInProgressVersion; - mCanDeleteFolderState = CDS_DONE; - gIdleCallbacks.deleteFunction(onCanDeleteIdle, this); - } - else - { - mCanDelete = false; - mCanCut = false; - mLastCheckedVersion = LLViewerInventoryCategory::VERSION_UNKNOWN; - mCanDeleteFolderState = CDS_DONE; - } - - if (mRoot) - { - mRoot->updateMenu(); - } -} - - -// +=================================================+ -// | LLMarketplaceFolderBridge | -// +=================================================+ - -// LLMarketplaceFolderBridge is a specialized LLFolderBridge for use in Marketplace Inventory panels -LLMarketplaceFolderBridge::LLMarketplaceFolderBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid) : -LLFolderBridge(inventory, root, uuid) -{ - m_depth = depth_nesting_in_marketplace(mUUID); - m_stockCountCache = COMPUTE_STOCK_NOT_EVALUATED; -} - -LLUIImagePtr LLMarketplaceFolderBridge::getIcon() const -{ - return getMarketplaceFolderIcon(false); -} - -LLUIImagePtr LLMarketplaceFolderBridge::getIconOpen() const -{ - return getMarketplaceFolderIcon(true); -} - -LLUIImagePtr LLMarketplaceFolderBridge::getMarketplaceFolderIcon(bool is_open) const -{ - LLFolderType::EType preferred_type = getPreferredType(); - if (!LLMarketplaceData::instance().isUpdating(getUUID())) - { - // Skip computation (expensive) if we're waiting for updates. Use the old value in that case. - m_depth = depth_nesting_in_marketplace(mUUID); - } - if ((preferred_type == LLFolderType::FT_NONE) && (m_depth == 2)) - { - // We override the type when in the marketplace listings folder and only for version folder - preferred_type = LLFolderType::FT_MARKETPLACE_VERSION; - } - return LLUI::getUIImage(LLViewerFolderType::lookupIconName(preferred_type, is_open)); -} - -std::string LLMarketplaceFolderBridge::getLabelSuffix() const -{ - if (mIsLoading && mTimeSinceRequestStart.getElapsedTimeF32() >= FOLDER_LOADING_MESSAGE_DELAY) - { - return llformat(" ( %s ) ", LLTrans::getString("LoadingData").c_str()); - } - - std::string suffix = ""; - // Listing folder case - if (LLMarketplaceData::instance().isListed(getUUID())) - { - suffix = llformat("%d",LLMarketplaceData::instance().getListingID(getUUID())); - if (suffix.empty()) - { - suffix = LLTrans::getString("MarketplaceNoID"); - } - suffix = " (" + suffix + ")"; - if (LLMarketplaceData::instance().getActivationState(getUUID())) - { - suffix += " (" + LLTrans::getString("MarketplaceLive") + ")"; - } - } - // Version folder case - else if (LLMarketplaceData::instance().isVersionFolder(getUUID())) - { - suffix += " (" + LLTrans::getString("MarketplaceActive") + ")"; - } - // Add stock amount - bool updating = LLMarketplaceData::instance().isUpdating(getUUID()); - if (!updating) - { - // Skip computation (expensive) if we're waiting for update anyway. Use the old value in that case. - m_stockCountCache = compute_stock_count(getUUID()); - } - if (m_stockCountCache == 0) - { - suffix += " (" + LLTrans::getString("MarketplaceNoStock") + ")"; - } - else if (m_stockCountCache != COMPUTE_STOCK_INFINITE) - { - if (getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK) - { - suffix += " (" + LLTrans::getString("MarketplaceStock"); - } - else - { - suffix += " (" + LLTrans::getString("MarketplaceMax"); - } - if (m_stockCountCache == COMPUTE_STOCK_NOT_EVALUATED) - { - suffix += "=" + LLTrans::getString("MarketplaceUpdating") + ")"; - } - else - { - suffix += "=" + llformat("%d", m_stockCountCache) + ")"; - } - } - // Add updating suffix - if (updating) - { - suffix += " (" + LLTrans::getString("MarketplaceUpdating") + ")"; - } - return LLInvFVBridge::getLabelSuffix() + suffix; -} - -LLFontGL::StyleFlags LLMarketplaceFolderBridge::getLabelStyle() const -{ - return (LLMarketplaceData::instance().getActivationState(getUUID()) ? LLFontGL::BOLD : LLFontGL::NORMAL); -} - - - - -// helper stuff -bool move_task_inventory_callback(const LLSD& notification, const LLSD& response, std::shared_ptr move_inv) -{ - LLFloaterOpenObject::LLCatAndWear* cat_and_wear = (LLFloaterOpenObject::LLCatAndWear* )move_inv->mUserData; - LLViewerObject* object = gObjectList.findObject(move_inv->mObjectID); - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - - if(option == 0 && object) - { - if (cat_and_wear && cat_and_wear->mWear) // && !cat_and_wear->mFolderResponded) - { - LLInventoryObject::object_list_t inventory_objects; - object->getInventoryContents(inventory_objects); - int contents_count = inventory_objects.size(); - LLInventoryCopyAndWearObserver* inventoryObserver = new LLInventoryCopyAndWearObserver(cat_and_wear->mCatID, contents_count, cat_and_wear->mFolderResponded, - cat_and_wear->mReplace); - - gInventory.addObserver(inventoryObserver); - } - - two_uuids_list_t::iterator move_it; - for (move_it = move_inv->mMoveList.begin(); - move_it != move_inv->mMoveList.end(); - ++move_it) - { - object->moveInventory(move_it->first, move_it->second); - } - - // update the UI. - dialog_refresh_all(); - } - - if (move_inv->mCallback) - { - move_inv->mCallback(option, move_inv->mUserData, move_inv.get()); - } - - move_inv.reset(); //since notification will persist - return false; -} - -void drop_to_favorites_cb(const LLUUID& id, LLPointer cb1, LLPointer cb2) -{ - cb1->fire(id); - cb2->fire(id); -} - -LLFolderBridge::LLFolderBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid) - : LLInvFVBridge(inventory, root, uuid) - , mCallingCards(false) - , mWearables(false) - , mIsLoading(false) - , mShowDescendantsCount(false) - , mCanDeleteFolderState(CDS_DONE) - , mLastCheckedVersion(S32_MIN) - , mInProgressVersion(S32_MIN) - , mCanDelete(false) - , mCanCut(false) -{ -} - -LLFolderBridge::~LLFolderBridge() -{ - gIdleCallbacks.deleteFunction(onCanDeleteIdle, this); -} - -void LLFolderBridge::dropToFavorites(LLInventoryItem* inv_item, LLPointer cb) -{ - // use callback to rearrange favorite landmarks after adding - // to have new one placed before target (on which it was dropped). See EXT-4312. - LLPointer cb_fav = new AddFavoriteLandmarkCallback(); - LLInventoryPanel* panel = mInventoryPanel.get(); - LLFolderViewItem* drag_over_item = panel ? panel->getRootFolder()->getDraggingOverItem() : NULL; - LLFolderViewModelItemInventory* view_model = drag_over_item ? static_cast(drag_over_item->getViewModelItem()) : NULL; - if (view_model) - { - cb_fav.get()->setTargetLandmarkId(view_model->getUUID()); - } - - LLPointer callback = cb_fav; - if (cb) - { - callback = new LLBoostFuncInventoryCallback(boost::bind(drop_to_favorites_cb, _1, cb, cb_fav)); - } - - copy_inventory_item( - gAgent.getID(), - inv_item->getPermissions().getOwner(), - inv_item->getUUID(), - mUUID, - std::string(), - callback); -} - -void LLFolderBridge::dropToOutfit(LLInventoryItem* inv_item, bool move_is_into_current_outfit, LLPointer cb) -{ - if((inv_item->getInventoryType() == LLInventoryType::IT_TEXTURE) || (inv_item->getInventoryType() == LLInventoryType::IT_SNAPSHOT)) - { - const LLUUID &my_outifts_id = getInventoryModel()->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); - if(mUUID != my_outifts_id) - { - // Legacy: prior to thumbnails images in outfits were used for outfit gallery. - LLNotificationsUtil::add("ThumbnailOutfitPhoto"); - } - return; - } - - // BAP - should skip if dup. - if (move_is_into_current_outfit) - { - LLAppearanceMgr::instance().wearItemOnAvatar(inv_item->getUUID(), true, true); - } - else - { - LLPointer cb = NULL; - link_inventory_object(mUUID, LLConstPointer(inv_item), cb); - } -} - -void LLFolderBridge::dropToMyOutfits(LLInventoryCategory* inv_cat, LLPointer cb) -{ - // make a folder in the My Outfits directory. - const LLUUID dest_id = getInventoryModel()->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); - - // Note: creation will take time, so passing folder id to callback is slightly unreliable, - // but so is collecting and passing descendants' ids - inventory_func_type func = boost::bind(&LLFolderBridge::outfitFolderCreatedCallback, this, inv_cat->getUUID(), _1, cb); - gInventory.createNewCategory(dest_id, - LLFolderType::FT_OUTFIT, - inv_cat->getName(), - func, - inv_cat->getThumbnailUUID()); -} - -void LLFolderBridge::outfitFolderCreatedCallback(LLUUID cat_source_id, LLUUID cat_dest_id, LLPointer cb) -{ - LLInventoryModel::cat_array_t* categories; - LLInventoryModel::item_array_t* items; - getInventoryModel()->getDirectDescendentsOf(cat_source_id, categories, items); - - LLInventoryObject::const_object_list_t link_array; - - - LLInventoryModel::item_array_t::iterator iter = items->begin(); - LLInventoryModel::item_array_t::iterator end = items->end(); - while (iter!=end) - { - const LLViewerInventoryItem* item = (*iter); - // By this point everything is supposed to be filtered, - // but there was a delay to create folder so something could have changed - LLInventoryType::EType inv_type = item->getInventoryType(); - if ((inv_type == LLInventoryType::IT_WEARABLE) || - (inv_type == LLInventoryType::IT_GESTURE) || - (inv_type == LLInventoryType::IT_ATTACHMENT) || - (inv_type == LLInventoryType::IT_OBJECT) || - (inv_type == LLInventoryType::IT_SNAPSHOT) || - (inv_type == LLInventoryType::IT_TEXTURE)) - { - link_array.push_back(LLConstPointer(item)); - } - iter++; - } - - if (!link_array.empty()) - { - link_inventory_array(cat_dest_id, link_array, cb); - } -} - -// Callback for drop item if DAMA required... -void LLFolderBridge::callback_dropItemIntoFolder(const LLSD& notification, const LLSD& response, LLInventoryItem* inv_item) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) // YES - { - std::string tooltip_msg; - dragItemIntoFolder(inv_item, true, tooltip_msg, false); - } -} - -// Callback for drop category if DAMA required... -void LLFolderBridge::callback_dropCategoryIntoFolder(const LLSD& notification, const LLSD& response, LLInventoryCategory* inv_category) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) // YES - { - std::string tooltip_msg; - dragCategoryIntoFolder(inv_category, true, tooltip_msg, false, false); - } -} - -// This is used both for testing whether an item can be dropped -// into the folder, as well as performing the actual drop, depending -// if drop == true. -bool LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, - bool drop, - std::string& tooltip_msg, - bool user_confirm, - LLPointer cb) -{ - LLInventoryModel* model = getInventoryModel(); - - if (!model || !inv_item) return false; - if (!isAgentInventory()) return false; // cannot drag into library - if (!isAgentAvatarValid()) return false; - - LLInventoryPanel* destination_panel = mInventoryPanel.get(); - if (!destination_panel) return false; - - LLInventoryFilter* filter = getInventoryFilter(); - if (!filter) return false; - - const LLUUID ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); - const LLUUID &favorites_id = model->findCategoryUUIDForType(LLFolderType::FT_FAVORITE); - const LLUUID &landmarks_id = model->findCategoryUUIDForType(LLFolderType::FT_LANDMARK); - const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); - const LLUUID from_folder_uuid = inv_item->getParentUUID(); - - const bool move_is_into_current_outfit = (mUUID == current_outfit_id); - const bool move_is_into_favorites = (mUUID == favorites_id); - const bool move_is_into_my_outfits = (mUUID == my_outifts_id) || model->isObjectDescendentOf(mUUID, my_outifts_id); - const bool move_is_into_outfit = move_is_into_my_outfits || (getCategory() && getCategory()->getPreferredType()==LLFolderType::FT_OUTFIT); - const bool move_is_into_landmarks = (mUUID == landmarks_id) || model->isObjectDescendentOf(mUUID, landmarks_id); - const bool move_is_into_marketplacelistings = model->isObjectDescendentOf(mUUID, marketplacelistings_id); - const bool move_is_from_marketplacelistings = model->isObjectDescendentOf(inv_item->getUUID(), marketplacelistings_id); - - LLToolDragAndDrop::ESource source = LLToolDragAndDrop::getInstance()->getSource(); - bool accept = false; - U64 filter_types = filter->getFilterTypes(); - // We shouldn't allow to drop non recent items into recent tab (or some similar transactions) - // while we are allowing to interact with regular filtered inventory - bool use_filter = filter_types && (filter_types&LLInventoryFilter::FILTERTYPE_DATE || (filter_types&LLInventoryFilter::FILTERTYPE_OBJECT)==0); - LLViewerObject* object = NULL; - if(LLToolDragAndDrop::SOURCE_AGENT == source) - { - const LLUUID &trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); - - const bool move_is_into_trash = (mUUID == trash_id) || model->isObjectDescendentOf(mUUID, trash_id); - const bool move_is_outof_current_outfit = LLAppearanceMgr::instance().getIsInCOF(inv_item->getUUID()); - - //-------------------------------------------------------------------------------- - // Determine if item can be moved. - // - - bool is_movable = true; - - switch (inv_item->getActualType()) - { - case LLAssetType::AT_CATEGORY: - is_movable = !LLFolderType::lookupIsProtectedType(((LLInventoryCategory*)inv_item)->getPreferredType()); - break; - default: - break; - } - // Can't explicitly drag things out of the COF. - if (move_is_outof_current_outfit) - { - is_movable = false; - } - if (move_is_into_trash) - { - is_movable &= inv_item->getIsLinkType() || !get_is_item_worn(inv_item->getUUID()); - } - if (is_movable) - { - // Don't allow creating duplicates in the Calling Card/Friends - // subfolders, see bug EXT-1599. Check is item direct descendent - // of target folder and forbid item's movement if it so. - // Note: isItemDirectDescendentOfCategory checks if - // passed category is in the Calling Card/Friends folder - is_movable &= !LLFriendCardsManager::instance().isObjDirectDescendentOfCategory(inv_item, getCategory()); - } - - // - //-------------------------------------------------------------------------------- - - //-------------------------------------------------------------------------------- - // Determine if item can be moved & dropped - // Note: if user_confirm is false, we already went through those accept logic test and can skip them - - accept = true; - - if (user_confirm && !is_movable) - { - accept = false; - } - else if (user_confirm && (mUUID == inv_item->getParentUUID()) && !move_is_into_favorites) - { - accept = false; - } - else if (user_confirm && (move_is_into_current_outfit || move_is_into_outfit)) - { - accept = can_move_to_outfit(inv_item, move_is_into_current_outfit); - } - else if (user_confirm && (move_is_into_favorites || move_is_into_landmarks)) - { - accept = can_move_to_landmarks(inv_item); - } - else if (user_confirm && move_is_into_marketplacelistings) - { - const LLViewerInventoryCategory * master_folder = model->getFirstDescendantOf(marketplacelistings_id, mUUID); - LLViewerInventoryCategory * dest_folder = getCategory(); - accept = can_move_item_to_marketplace(master_folder, dest_folder, inv_item, tooltip_msg, LLToolDragAndDrop::instance().getCargoCount() - LLToolDragAndDrop::instance().getCargoIndex()); - } - - // Check that the folder can accept this item based on folder/item type compatibility (e.g. stock folder compatibility) - if (user_confirm && accept) - { - LLViewerInventoryCategory * dest_folder = getCategory(); - accept = dest_folder->acceptItem(inv_item); - } - - LLInventoryPanel* active_panel = LLInventoryPanel::getActiveInventoryPanel(false); - - // Check whether the item being dragged from active inventory panel - // passes the filter of the destination panel. - if (user_confirm && accept && active_panel && use_filter) - { - LLFolderViewItem* fv_item = active_panel->getItemByID(inv_item->getUUID()); - if (!fv_item) return false; - - accept = filter->check(fv_item->getViewModelItem()); - } - - if (accept && drop) - { - if (inv_item->getType() == LLAssetType::AT_GESTURE - && LLGestureMgr::instance().isGestureActive(inv_item->getUUID()) && move_is_into_trash) - { - LLGestureMgr::instance().deactivateGesture(inv_item->getUUID()); - } - // If an item is being dragged between windows, unselect everything in the active window - // so that we don't follow the selection to its new location (which is very annoying). - // RN: a better solution would be to deselect automatically when an item is moved - // and then select any item that is dropped only in the panel that it is dropped in - if (active_panel && (destination_panel != active_panel)) - { - active_panel->unSelectAll(); - } - // Dropping in or out of marketplace needs (sometimes) confirmation - if (user_confirm && (move_is_from_marketplacelistings || move_is_into_marketplacelistings)) - { - if ((move_is_from_marketplacelistings && (LLMarketplaceData::instance().isInActiveFolder(inv_item->getUUID()) - || LLMarketplaceData::instance().isListedAndActive(inv_item->getUUID()))) || - (move_is_into_marketplacelistings && LLMarketplaceData::instance().isInActiveFolder(mUUID))) - { - LLNotificationsUtil::add("ConfirmMerchantActiveChange", LLSD(), LLSD(), boost::bind(&LLFolderBridge::callback_dropItemIntoFolder, this, _1, _2, inv_item)); - return true; - } - if (move_is_into_marketplacelistings && !move_is_from_marketplacelistings) - { - LLNotificationsUtil::add("ConfirmMerchantMoveInventory", LLSD(), LLSD(), boost::bind(&LLFolderBridge::callback_dropItemIntoFolder, this, _1, _2, inv_item)); - return true; - } - } - - //-------------------------------------------------------------------------------- - // Destination folder logic - // - - // REORDER - // (only reorder the item in Favorites folder) - if ((mUUID == inv_item->getParentUUID()) && move_is_into_favorites) - { - LLFolderViewItem* itemp = destination_panel->getRootFolder()->getDraggingOverItem(); - if (itemp) - { - LLUUID srcItemId = inv_item->getUUID(); - LLUUID destItemId = static_cast(itemp->getViewModelItem())->getUUID(); - LLFavoritesOrderStorage::instance().rearrangeFavoriteLandmarks(srcItemId, destItemId); - } - } - - // FAVORITES folder - // (copy the item) - else if (move_is_into_favorites) - { - dropToFavorites(inv_item, cb); - } - // CURRENT OUTFIT or OUTFIT folder - // (link the item) - else if (move_is_into_current_outfit || move_is_into_outfit) - { - dropToOutfit(inv_item, move_is_into_current_outfit, cb); - } - // MARKETPLACE LISTINGS folder - // Move the item - else if (move_is_into_marketplacelistings) - { - move_item_to_marketplacelistings(inv_item, mUUID); - if (cb) cb->fire(inv_item->getUUID()); - } - // NORMAL or TRASH folder - // (move the item, restamp if into trash) - else - { - // set up observer to select item once drag and drop from inbox is complete - if (gInventory.isObjectDescendentOf(inv_item->getUUID(), gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX))) - { - set_dad_inbox_object(inv_item->getUUID()); - } - - LLInvFVBridge::changeItemParent( - model, - (LLViewerInventoryItem*)inv_item, - mUUID, - move_is_into_trash); - if (cb) cb->fire(inv_item->getUUID()); - } - - if (move_is_from_marketplacelistings) - { - // If we move from an active (listed) listing, checks that it's still valid, if not, unlist - LLUUID version_folder_id = LLMarketplaceData::instance().getActiveFolder(from_folder_uuid); - if (version_folder_id.notNull()) - { - LLMarketplaceValidator::getInstance()->validateMarketplaceListings( - version_folder_id, - [version_folder_id](bool result) - { - if (!result) - { - LLMarketplaceData::instance().activateListing(version_folder_id, false); - } - }); - } - } - - // - //-------------------------------------------------------------------------------- - } - } - else if (LLToolDragAndDrop::SOURCE_WORLD == source) - { - // Make sure the object exists. If we allowed dragging from - // anonymous objects, it would be possible to bypass - // permissions. - object = gObjectList.findObject(inv_item->getParentUUID()); - if (!object) - { - LL_INFOS() << "Object not found for drop." << LL_ENDL; - return false; - } - - // coming from a task. Need to figure out if the person can - // move/copy this item. - LLPermissions perm(inv_item->getPermissions()); - bool is_move = false; - if ((perm.allowCopyBy(gAgent.getID(), gAgent.getGroupID()) - && perm.allowTransferTo(gAgent.getID()))) - // || gAgent.isGodlike()) - { - accept = true; - } - else if(object->permYouOwner()) - { - // If the object cannot be copied, but the object the - // inventory is owned by the agent, then the item can be - // moved from the task to agent inventory. - is_move = true; - accept = true; - } - - // Don't allow placing an original item into Current Outfit or an outfit folder - // because they must contain only links to wearable items. - // *TODO: Probably we should create a link to an item if it was dragged to outfit or COF. - if (move_is_into_current_outfit || move_is_into_outfit) - { - accept = false; - } - // Don't allow to move a single item to Favorites or Landmarks - // if it is not a landmark or a link to a landmark. - else if ((move_is_into_favorites || move_is_into_landmarks) - && !can_move_to_landmarks(inv_item)) - { - accept = false; - } - else if (move_is_into_marketplacelistings) - { - tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); - accept = false; - } - - // Check whether the item being dragged from in world - // passes the filter of the destination panel. - if (accept && use_filter) - { - accept = filter->check(inv_item); - } - - if (accept && drop) - { - LLUUID item_id = inv_item->getUUID(); - std::shared_ptr move_inv (new LLMoveInv()); - move_inv->mObjectID = inv_item->getParentUUID(); - two_uuids_t item_pair(mUUID, item_id); - move_inv->mMoveList.push_back(item_pair); - if (cb) - { - move_inv->mCallback = [item_id, cb](S32, void*, const LLMoveInv* move_inv) mutable - { cb->fire(item_id); }; - } - move_inv->mUserData = NULL; - if(is_move) - { - warn_move_inventory(object, move_inv); - } - else - { - // store dad inventory item to select added one later. See EXT-4347 - set_dad_inventory_item(inv_item, mUUID); - - LLNotification::Params params("MoveInventoryFromObject"); - params.functor.function(boost::bind(move_task_inventory_callback, _1, _2, move_inv)); - LLNotifications::instance().forceResponse(params, 0); - } - } - } - else if(LLToolDragAndDrop::SOURCE_NOTECARD == source) - { - if (move_is_into_marketplacelistings) - { - tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); - accept = false; - } - else if ((inv_item->getActualType() == LLAssetType::AT_SETTINGS) && !LLEnvironment::instance().isInventoryEnabled()) - { - tooltip_msg = LLTrans::getString("NoEnvironmentSettings"); - accept = false; - } - else - { - // Don't allow placing an original item from a notecard to Current Outfit or an outfit folder - // because they must contain only links to wearable items. - accept = !(move_is_into_current_outfit || move_is_into_outfit); - } - - // Check whether the item being dragged from notecard - // passes the filter of the destination panel. - if (accept && use_filter) - { - accept = filter->check(inv_item); - } - - if (accept && drop) - { - copy_inventory_from_notecard(mUUID, // Drop to the chosen destination folder - LLToolDragAndDrop::getInstance()->getObjectID(), - LLToolDragAndDrop::getInstance()->getSourceID(), - inv_item); - } - } - else if(LLToolDragAndDrop::SOURCE_LIBRARY == source) - { - LLViewerInventoryItem* item = (LLViewerInventoryItem*)inv_item; - if(item && item->isFinished()) - { - accept = true; - - if (move_is_into_marketplacelistings) - { - tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); - accept = false; - } - else if (move_is_into_current_outfit || move_is_into_outfit) - { - accept = can_move_to_outfit(inv_item, move_is_into_current_outfit); - } - // Don't allow to move a single item to Favorites or Landmarks - // if it is not a landmark or a link to a landmark. - else if (move_is_into_favorites || move_is_into_landmarks) - { - accept = can_move_to_landmarks(inv_item); - } - - LLInventoryPanel* active_panel = LLInventoryPanel::getActiveInventoryPanel(false); - - // Check whether the item being dragged from the library - // passes the filter of the destination panel. - if (accept && active_panel && use_filter) - { - LLFolderViewItem* fv_item = active_panel->getItemByID(inv_item->getUUID()); - if (!fv_item) return false; - - accept = filter->check(fv_item->getViewModelItem()); - } - - if (accept && drop) - { - // FAVORITES folder - // (copy the item) - if (move_is_into_favorites) - { - dropToFavorites(inv_item, cb); - } - // CURRENT OUTFIT or OUTFIT folder - // (link the item) - else if (move_is_into_current_outfit || move_is_into_outfit) - { - dropToOutfit(inv_item, move_is_into_current_outfit, cb); - } - else - { - copy_inventory_item( - gAgent.getID(), - inv_item->getPermissions().getOwner(), - inv_item->getUUID(), - mUUID, - std::string(), - cb); - } - } - } - } - else - { - LL_WARNS() << "unhandled drag source" << LL_ENDL; - } - return accept; -} - -// static -bool check_category(LLInventoryModel* model, - const LLUUID& cat_id, - LLInventoryPanel* active_panel, - LLInventoryFilter* filter) -{ - if (!model || !active_panel || !filter) - return false; - - if (!filter->checkFolder(cat_id)) - { - return false; - } - - LLInventoryModel::cat_array_t descendent_categories; - LLInventoryModel::item_array_t descendent_items; - model->collectDescendents(cat_id, descendent_categories, descendent_items, true); - - S32 num_descendent_categories = descendent_categories.size(); - S32 num_descendent_items = descendent_items.size(); - - if (num_descendent_categories + num_descendent_items == 0) - { - // Empty folder should be checked as any other folder view item. - // If we are filtering by date the folder should not pass because - // it doesn't have its own creation date. See LLInvFVBridge::getCreationDate(). - return check_item(cat_id, active_panel, filter); - } - - for (S32 i = 0; i < num_descendent_categories; ++i) - { - LLInventoryCategory* category = descendent_categories[i]; - if(!check_category(model, category->getUUID(), active_panel, filter)) - { - return false; - } - } - - for (S32 i = 0; i < num_descendent_items; ++i) - { - LLViewerInventoryItem* item = descendent_items[i]; - if(!check_item(item->getUUID(), active_panel, filter)) - { - return false; - } - } - - return true; -} - -// static -bool check_item(const LLUUID& item_id, - LLInventoryPanel* active_panel, - LLInventoryFilter* filter) -{ - if (!active_panel || !filter) return false; - - LLFolderViewItem* fv_item = active_panel->getItemByID(item_id); - if (!fv_item) return false; - - return filter->check(fv_item->getViewModelItem()); -} - -// +=================================================+ -// | LLTextureBridge | -// +=================================================+ - -LLUIImagePtr LLTextureBridge::getIcon() const -{ - return LLInventoryIcon::getIcon(LLAssetType::AT_TEXTURE, mInvType); -} - -void LLTextureBridge::openItem() -{ - LLViewerInventoryItem* item = getItem(); - - if (item) - { - LLInvFVBridgeAction::doAction(item->getType(),mUUID,getInventoryModel()); - } -} - -bool LLTextureBridge::canSaveTexture(void) -{ - const LLInventoryModel* model = getInventoryModel(); - if(!model) - { - return false; - } - - const LLViewerInventoryItem *item = model->getItem(mUUID); - if (item) - { - return item->checkPermissionsSet(PERM_ITEM_UNRESTRICTED); - } - return false; -} - -void LLTextureBridge::buildContextMenu(LLMenuGL& menu, U32 flags) -{ - LL_DEBUGS() << "LLTextureBridge::buildContextMenu()" << LL_ENDL; - menuentry_vec_t items; - menuentry_vec_t disabled_items; - if(isItemInTrash()) - { - addTrashContextMenuOptions(items, disabled_items); - } - else if (isMarketplaceListingsFolder()) - { - addMarketplaceContextMenuOptions(flags, items, disabled_items); - items.push_back(std::string("Properties")); - getClipboardEntries(false, items, disabled_items, flags); - } - else - { - items.push_back(std::string("Share")); - if (!canShare()) - { - disabled_items.push_back(std::string("Share")); - } - - addOpenRightClickMenuOption(items); - items.push_back(std::string("Properties")); - - getClipboardEntries(true, items, disabled_items, flags); - - items.push_back(std::string("Texture Separator")); - - if ((flags & ITEM_IN_MULTI_SELECTION) != 0) - { - items.push_back(std::string("Save Selected As")); - } - else - { - items.push_back(std::string("Save As")); - if (!canSaveTexture()) - { - disabled_items.push_back(std::string("Save As")); - } - } - - } - addLinkReplaceMenuOption(items, disabled_items); - hide_context_entries(menu, items, disabled_items); -} - -// virtual -void LLTextureBridge::performAction(LLInventoryModel* model, std::string action) -{ - if ("save_as" == action) - { - LLPreviewTexture* preview_texture = LLFloaterReg::getTypedInstance("preview_texture", mUUID); - if (preview_texture) - { - preview_texture->openToSave(); - preview_texture->saveAs(); - } - } - else if ("save_selected_as" == action) - { - openItem(); - if (canSaveTexture()) - { - LLPreviewTexture* preview_texture = LLFloaterReg::getTypedInstance("preview_texture", mUUID); - if (preview_texture) - { - preview_texture->saveMultipleToFile(mFileName); - } - } - else - { - LL_WARNS() << "You don't have permission to save " << getName() << " to disk." << LL_ENDL; - } - - } - else LLItemBridge::performAction(model, action); -} - -// +=================================================+ -// | LLSoundBridge | -// +=================================================+ - -void LLSoundBridge::openItem() -{ - const LLViewerInventoryItem* item = getItem(); - if (item) - { - LLInvFVBridgeAction::doAction(item->getType(),mUUID,getInventoryModel()); - } -} - -void LLSoundBridge::openSoundPreview(void* which) -{ - LLSoundBridge *me = (LLSoundBridge *)which; - LLFloaterReg::showInstance("preview_sound", LLSD(me->mUUID), TAKE_FOCUS_YES); -} - -void LLSoundBridge::buildContextMenu(LLMenuGL& menu, U32 flags) -{ - LL_DEBUGS() << "LLSoundBridge::buildContextMenu()" << LL_ENDL; - menuentry_vec_t items; - menuentry_vec_t disabled_items; - - if (isMarketplaceListingsFolder()) - { - addMarketplaceContextMenuOptions(flags, items, disabled_items); - items.push_back(std::string("Properties")); - getClipboardEntries(false, items, disabled_items, flags); - } - else - { - if (isItemInTrash()) - { - addTrashContextMenuOptions(items, disabled_items); - } - else - { - items.push_back(std::string("Share")); - if (!canShare()) - { - disabled_items.push_back(std::string("Share")); - } - items.push_back(std::string("Sound Open")); - items.push_back(std::string("Properties")); - - getClipboardEntries(true, items, disabled_items, flags); - } - - items.push_back(std::string("Sound Separator")); - items.push_back(std::string("Sound Play")); - } - - addLinkReplaceMenuOption(items, disabled_items); - hide_context_entries(menu, items, disabled_items); -} - -void LLSoundBridge::performAction(LLInventoryModel* model, std::string action) -{ - if ("sound_play" == action) - { - LLViewerInventoryItem* item = getItem(); - if(item) - { - send_sound_trigger(item->getAssetUUID(), SOUND_GAIN); - } - } - else if ("open" == action) - { - openSoundPreview((void*)this); - } - else LLItemBridge::performAction(model, action); -} - -// +=================================================+ -// | LLLandmarkBridge | -// +=================================================+ - -LLLandmarkBridge::LLLandmarkBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid, - U32 flags/* = 0x00*/) : - LLItemBridge(inventory, root, uuid) -{ - mVisited = false; - if (flags & LLInventoryItemFlags::II_FLAGS_LANDMARK_VISITED) - { - mVisited = true; - } -} - -LLUIImagePtr LLLandmarkBridge::getIcon() const -{ - return LLInventoryIcon::getIcon(LLAssetType::AT_LANDMARK, LLInventoryType::IT_LANDMARK, mVisited, false); -} - -void LLLandmarkBridge::buildContextMenu(LLMenuGL& menu, U32 flags) -{ - menuentry_vec_t items; - menuentry_vec_t disabled_items; - - LL_DEBUGS() << "LLLandmarkBridge::buildContextMenu()" << LL_ENDL; - if (isMarketplaceListingsFolder()) - { - addMarketplaceContextMenuOptions(flags, items, disabled_items); - items.push_back(std::string("Properties")); - getClipboardEntries(false, items, disabled_items, flags); - } - else - { - if(isItemInTrash()) - { - addTrashContextMenuOptions(items, disabled_items); - } - else - { - items.push_back(std::string("Share")); - if (!canShare()) - { - disabled_items.push_back(std::string("Share")); - } - items.push_back(std::string("Landmark Open")); - items.push_back(std::string("Properties")); - - getClipboardEntries(true, items, disabled_items, flags); - } - - items.push_back(std::string("Landmark Separator")); - items.push_back(std::string("url_copy")); - items.push_back(std::string("About Landmark")); - items.push_back(std::string("show_on_map")); - } - - // Disable "About Landmark" menu item for - // multiple landmarks selected. Only one landmark - // info panel can be shown at a time. - if ((flags & FIRST_SELECTED_ITEM) == 0) - { - disabled_items.push_back(std::string("url_copy")); - disabled_items.push_back(std::string("About Landmark")); - } - - addLinkReplaceMenuOption(items, disabled_items); - hide_context_entries(menu, items, disabled_items); -} - -// Convenience function for the two functions below. -void teleport_via_landmark(const LLUUID& asset_id) -{ - gAgent.teleportViaLandmark( asset_id ); - - // we now automatically track the landmark you're teleporting to - // because you'll probably arrive at a telehub instead - LLFloaterWorldMap* floater_world_map = LLFloaterWorldMap::getInstance(); - if( floater_world_map ) - { - floater_world_map->trackLandmark( asset_id ); - } -} - -// virtual -void LLLandmarkBridge::performAction(LLInventoryModel* model, std::string action) -{ - if ("teleport" == action) - { - LLViewerInventoryItem* item = getItem(); - if(item) - { - teleport_via_landmark(item->getAssetUUID()); - } - } - else if ("about" == action) - { - LLViewerInventoryItem* item = getItem(); - if(item) - { - LLSD key; - key["type"] = "landmark"; - key["id"] = item->getUUID(); - - LLFloaterSidePanelContainer::showPanel("places", key); - } - } - else - { - LLItemBridge::performAction(model, action); - } -} - -static bool open_landmark_callback(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - - LLUUID asset_id = notification["payload"]["asset_id"].asUUID(); - if (option == 0) - { - teleport_via_landmark(asset_id); - } - - return false; -} -static LLNotificationFunctorRegistration open_landmark_callback_reg("TeleportFromLandmark", open_landmark_callback); - - -void LLLandmarkBridge::openItem() -{ - LLViewerInventoryItem* item = getItem(); - - if (item) - { - LLInvFVBridgeAction::doAction(item->getType(),mUUID,getInventoryModel()); - } -} - - -// +=================================================+ -// | LLCallingCardObserver | -// +=================================================+ -class LLCallingCardObserver : public LLFriendObserver -{ -public: - LLCallingCardObserver(LLCallingCardBridge* bridge) : mBridgep(bridge) {} - virtual ~LLCallingCardObserver() { mBridgep = NULL; } - virtual void changed(U32 mask) - { - if (mask & LLFriendObserver::ONLINE) - { - mBridgep->refreshFolderViewItem(); - mBridgep->checkSearchBySuffixChanges(); - } - } -protected: - LLCallingCardBridge* mBridgep; -}; - -// +=================================================+ -// | LLCallingCardBridge | -// +=================================================+ - -LLCallingCardBridge::LLCallingCardBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid ) : - LLItemBridge(inventory, root, uuid) -{ - mObserver = new LLCallingCardObserver(this); - mCreatorUUID = getItem()->getCreatorUUID(); - LLAvatarTracker::instance().addParticularFriendObserver(mCreatorUUID, mObserver); -} - -LLCallingCardBridge::~LLCallingCardBridge() -{ - LLAvatarTracker::instance().removeParticularFriendObserver(mCreatorUUID, mObserver); - - delete mObserver; -} - -void LLCallingCardBridge::refreshFolderViewItem() -{ - LLInventoryPanel* panel = mInventoryPanel.get(); - LLFolderViewItem* itemp = panel ? panel->getItemByID(mUUID) : NULL; - if (itemp) - { - itemp->refresh(); - } -} - -void LLCallingCardBridge::checkSearchBySuffixChanges() -{ - if (!mDisplayName.empty()) - { - // changes in mDisplayName are processed by rename function and here it will be always same - // suffixes are also of fixed length, and we are processing change of one at a time, - // so it should be safe to use length (note: mSearchableName is capitalized) - S32 old_length = mSearchableName.length(); - S32 new_length = mDisplayName.length() + getLabelSuffix().length(); - if (old_length == new_length) - { - return; - } - mSearchableName.assign(mDisplayName); - mSearchableName.append(getLabelSuffix()); - LLStringUtil::toUpper(mSearchableName); - if (new_lengthgetFilterSubString()) == std::string::npos) - { - // string no longer contains substring - // we either have to update all parents manually or restart filter. - // dirtyFilter will not work here due to obsolete descendants' generations - getInventoryFilter()->setModified(LLFolderViewFilter::FILTER_MORE_RESTRICTIVE); - } - } - else - { - if (getInventoryFilter()) - { - // mSearchableName became longer, we gained additional suffix and need to repeat filter check. - dirtyFilter(); - } - } - } -} - -// virtual -void LLCallingCardBridge::performAction(LLInventoryModel* model, std::string action) -{ - if ("begin_im" == action) - { - LLViewerInventoryItem *item = getItem(); - if (item && (item->getCreatorUUID() != gAgent.getID()) && - (!item->getCreatorUUID().isNull())) - { - std::string callingcard_name = LLCacheName::getDefaultName(); - LLAvatarName av_name; - if (LLAvatarNameCache::get(item->getCreatorUUID(), &av_name)) - { - callingcard_name = av_name.getCompleteName(); - } - LLUUID session_id = gIMMgr->addSession(callingcard_name, IM_NOTHING_SPECIAL, item->getCreatorUUID()); - if (session_id != LLUUID::null) - { - LLFloaterIMContainer::getInstance()->showConversation(session_id); - } - } - } - else if ("lure" == action) - { - LLViewerInventoryItem *item = getItem(); - if (item && (item->getCreatorUUID() != gAgent.getID()) && - (!item->getCreatorUUID().isNull())) - { - LLAvatarActions::offerTeleport(item->getCreatorUUID()); - } - } - else if ("request_lure" == action) - { - LLViewerInventoryItem *item = getItem(); - if (item && (item->getCreatorUUID() != gAgent.getID()) && - (!item->getCreatorUUID().isNull())) - { - LLAvatarActions::teleportRequest(item->getCreatorUUID()); - } - } - - else LLItemBridge::performAction(model, action); -} - -LLUIImagePtr LLCallingCardBridge::getIcon() const -{ - bool online = false; - LLViewerInventoryItem* item = getItem(); - if(item) - { - online = LLAvatarTracker::instance().isBuddyOnline(item->getCreatorUUID()); - } - return LLInventoryIcon::getIcon(LLAssetType::AT_CALLINGCARD, LLInventoryType::IT_CALLINGCARD, online, false); -} - -std::string LLCallingCardBridge::getLabelSuffix() const -{ - LLViewerInventoryItem* item = getItem(); - if( item && LLAvatarTracker::instance().isBuddyOnline(item->getCreatorUUID()) ) - { - return LLItemBridge::getLabelSuffix() + " online"; - } - else - { - return LLItemBridge::getLabelSuffix(); - } -} - -void LLCallingCardBridge::openItem() -{ - LLViewerInventoryItem* item = getItem(); - - if (item) - { - LLInvFVBridgeAction::doAction(item->getType(),mUUID,getInventoryModel()); - } -/* - LLViewerInventoryItem* item = getItem(); - if(item && !item->getCreatorUUID().isNull()) - { - LLAvatarActions::showProfile(item->getCreatorUUID()); - } -*/ -} - -void LLCallingCardBridge::buildContextMenu(LLMenuGL& menu, U32 flags) -{ - LL_DEBUGS() << "LLCallingCardBridge::buildContextMenu()" << LL_ENDL; - menuentry_vec_t items; - menuentry_vec_t disabled_items; - - if(isItemInTrash()) - { - addTrashContextMenuOptions(items, disabled_items); - } - else if (isMarketplaceListingsFolder()) - { - addMarketplaceContextMenuOptions(flags, items, disabled_items); - items.push_back(std::string("Properties")); - getClipboardEntries(false, items, disabled_items, flags); - } - else - { - items.push_back(std::string("Share")); - if (!canShare()) - { - disabled_items.push_back(std::string("Share")); - } - if ((flags & FIRST_SELECTED_ITEM) == 0) - { - disabled_items.push_back(std::string("Open")); - } - addOpenRightClickMenuOption(items); - items.push_back(std::string("Properties")); - - getClipboardEntries(true, items, disabled_items, flags); - - LLInventoryItem* item = getItem(); - bool good_card = (item - && (LLUUID::null != item->getCreatorUUID()) - && (item->getCreatorUUID() != gAgent.getID())); - bool user_online = false; - if (item) - { - user_online = (LLAvatarTracker::instance().isBuddyOnline(item->getCreatorUUID())); - } - items.push_back(std::string("Send Instant Message Separator")); - items.push_back(std::string("Send Instant Message")); - items.push_back(std::string("Offer Teleport...")); - items.push_back(std::string("Request Teleport...")); - items.push_back(std::string("Conference Chat")); - - if (!good_card) - { - disabled_items.push_back(std::string("Send Instant Message")); - } - if (!good_card || !user_online) - { - disabled_items.push_back(std::string("Offer Teleport...")); - disabled_items.push_back(std::string("Request Teleport...")); - disabled_items.push_back(std::string("Conference Chat")); - } - } - addLinkReplaceMenuOption(items, disabled_items); - hide_context_entries(menu, items, disabled_items); -} - -bool LLCallingCardBridge::dragOrDrop(MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - std::string& tooltip_msg) -{ - LLViewerInventoryItem* item = getItem(); - bool rv = false; - if(item) - { - // check the type - switch(cargo_type) - { - case DAD_TEXTURE: - case DAD_SOUND: - case DAD_LANDMARK: - case DAD_SCRIPT: - case DAD_CLOTHING: - case DAD_OBJECT: - case DAD_NOTECARD: - case DAD_BODYPART: - case DAD_ANIMATION: - case DAD_GESTURE: - case DAD_MESH: - case DAD_SETTINGS: - case DAD_MATERIAL: - { - LLInventoryItem* inv_item = (LLInventoryItem*)cargo_data; - const LLPermissions& perm = inv_item->getPermissions(); - if(gInventory.getItem(inv_item->getUUID()) - && perm.allowOperationBy(PERM_TRANSFER, gAgent.getID())) - { - rv = true; - if(drop) - { - LLGiveInventory::doGiveInventoryItem(item->getCreatorUUID(), - (LLInventoryItem*)cargo_data); - } - } - else - { - // It's not in the user's inventory (it's probably in - // an object's contents), so disallow dragging it here. - // You can't give something you don't yet have. - rv = false; - } - break; - } - case DAD_CATEGORY: - { - LLInventoryCategory* inv_cat = (LLInventoryCategory*)cargo_data; - if( gInventory.getCategory( inv_cat->getUUID() ) ) - { - rv = true; - if(drop) - { - LLGiveInventory::doGiveInventoryCategory( - item->getCreatorUUID(), - inv_cat); - } - } - else - { - // It's not in the user's inventory (it's probably in - // an object's contents), so disallow dragging it here. - // You can't give something you don't yet have. - rv = false; - } - break; - } - default: - break; - } - } - return rv; -} - -// +=================================================+ -// | LLNotecardBridge | -// +=================================================+ - -void LLNotecardBridge::openItem() -{ - LLViewerInventoryItem* item = getItem(); - if (item) - { - LLInvFVBridgeAction::doAction(item->getType(),mUUID,getInventoryModel()); - } -} - -void LLNotecardBridge::buildContextMenu(LLMenuGL& menu, U32 flags) -{ - LL_DEBUGS() << "LLNotecardBridge::buildContextMenu()" << LL_ENDL; - - if (isMarketplaceListingsFolder()) - { - menuentry_vec_t items; - menuentry_vec_t disabled_items; - addMarketplaceContextMenuOptions(flags, items, disabled_items); - items.push_back(std::string("Properties")); - getClipboardEntries(false, items, disabled_items, flags); - hide_context_entries(menu, items, disabled_items); - } - else - { - LLItemBridge::buildContextMenu(menu, flags); - } -} - -// +=================================================+ -// | LLGestureBridge | -// +=================================================+ - -LLFontGL::StyleFlags LLGestureBridge::getLabelStyle() const -{ - if( LLGestureMgr::instance().isGestureActive(mUUID) ) - { - return LLFontGL::BOLD; - } - else - { - return LLFontGL::NORMAL; - } -} - -std::string LLGestureBridge::getLabelSuffix() const -{ - if( LLGestureMgr::instance().isGestureActive(mUUID) ) - { - LLStringUtil::format_map_t args; - args["[GESLABEL]"] = LLItemBridge::getLabelSuffix(); - return LLTrans::getString("ActiveGesture", args); - } - else - { - return LLItemBridge::getLabelSuffix(); - } -} - -// virtual -void LLGestureBridge::performAction(LLInventoryModel* model, std::string action) -{ - if (isAddAction(action)) - { - LLGestureMgr::instance().activateGesture(mUUID); - - LLViewerInventoryItem* item = gInventory.getItem(mUUID); - if (!item) return; - - // Since we just changed the suffix to indicate (active) - // the server doesn't need to know, just the viewer. - gInventory.updateItem(item); - gInventory.notifyObservers(); - } - else if ("deactivate" == action || isRemoveAction(action)) - { - LLGestureMgr::instance().deactivateGesture(mUUID); - - LLViewerInventoryItem* item = gInventory.getItem(mUUID); - if (!item) return; - - // Since we just changed the suffix to indicate (active) - // the server doesn't need to know, just the viewer. - gInventory.updateItem(item); - gInventory.notifyObservers(); - } - else if("play" == action) - { - if(!LLGestureMgr::instance().isGestureActive(mUUID)) - { - // we need to inform server about gesture activating to be consistent with LLPreviewGesture and LLGestureComboList. - bool inform_server = true; - bool deactivate_similar = false; - LLGestureMgr::instance().setGestureLoadedCallback(mUUID, boost::bind(&LLGestureBridge::playGesture, mUUID)); - LLViewerInventoryItem* item = gInventory.getItem(mUUID); - llassert(item); - if (item) - { - LLGestureMgr::instance().activateGestureWithAsset(mUUID, item->getAssetUUID(), inform_server, deactivate_similar); - } - } - else - { - playGesture(mUUID); - } - } - else LLItemBridge::performAction(model, action); -} - -void LLGestureBridge::openItem() -{ - LLViewerInventoryItem* item = getItem(); - - if (item) - { - LLInvFVBridgeAction::doAction(item->getType(),mUUID,getInventoryModel()); - } -/* - LLViewerInventoryItem* item = getItem(); - if (item) - { - LLPreviewGesture* preview = LLPreviewGesture::show(mUUID, LLUUID::null); - preview->setFocus(true); - } -*/ -} - -bool LLGestureBridge::removeItem() -{ - // Grab class information locally since *this may be deleted - // within this function. Not a great pattern... - const LLInventoryModel* model = getInventoryModel(); - if(!model) - { - return false; - } - const LLUUID item_id = mUUID; - - // This will also force close the preview window, if it exists. - // This may actually delete *this, if mUUID is in the COF. - LLGestureMgr::instance().deactivateGesture(item_id); - - // If deactivateGesture deleted *this, then return out immediately. - if (!model->getObject(item_id)) - { - return true; - } - - return LLItemBridge::removeItem(); -} - -void LLGestureBridge::buildContextMenu(LLMenuGL& menu, U32 flags) -{ - LL_DEBUGS() << "LLGestureBridge::buildContextMenu()" << LL_ENDL; - menuentry_vec_t items; - menuentry_vec_t disabled_items; - if(isItemInTrash()) - { - addTrashContextMenuOptions(items, disabled_items); - } - else if (isMarketplaceListingsFolder()) - { - addMarketplaceContextMenuOptions(flags, items, disabled_items); - items.push_back(std::string("Properties")); - getClipboardEntries(false, items, disabled_items, flags); - } - else - { - items.push_back(std::string("Share")); - if (!canShare()) - { - disabled_items.push_back(std::string("Share")); - } - - addOpenRightClickMenuOption(items); - items.push_back(std::string("Properties")); - - getClipboardEntries(true, items, disabled_items, flags); - - items.push_back(std::string("Gesture Separator")); - if (LLGestureMgr::instance().isGestureActive(getUUID())) - { - items.push_back(std::string("Deactivate")); - } - else - { - items.push_back(std::string("Activate")); - } - items.push_back(std::string("PlayGesture")); - } - addLinkReplaceMenuOption(items, disabled_items); - hide_context_entries(menu, items, disabled_items); -} - -// static -void LLGestureBridge::playGesture(const LLUUID& item_id) -{ - if (LLGestureMgr::instance().isGesturePlaying(item_id)) - { - LLGestureMgr::instance().stopGesture(item_id); - } - else - { - LLGestureMgr::instance().playGesture(item_id); - } -} - - -// +=================================================+ -// | LLAnimationBridge | -// +=================================================+ - -void LLAnimationBridge::buildContextMenu(LLMenuGL& menu, U32 flags) -{ - menuentry_vec_t items; - menuentry_vec_t disabled_items; - - LL_DEBUGS() << "LLAnimationBridge::buildContextMenu()" << LL_ENDL; - if (isMarketplaceListingsFolder()) - { - addMarketplaceContextMenuOptions(flags, items, disabled_items); - items.push_back(std::string("Properties")); - getClipboardEntries(false, items, disabled_items, flags); - } - else - { - if(isItemInTrash()) - { - addTrashContextMenuOptions(items, disabled_items); - } - else - { - items.push_back(std::string("Share")); - if (!canShare()) - { - disabled_items.push_back(std::string("Share")); - } - items.push_back(std::string("Animation Open")); - items.push_back(std::string("Properties")); - - getClipboardEntries(true, items, disabled_items, flags); - } - - items.push_back(std::string("Animation Separator")); - items.push_back(std::string("Animation Play")); - items.push_back(std::string("Animation Audition")); - } - - addLinkReplaceMenuOption(items, disabled_items); - hide_context_entries(menu, items, disabled_items); -} - -// virtual -void LLAnimationBridge::performAction(LLInventoryModel* model, std::string action) -{ - if ((action == "playworld") || (action == "playlocal")) - { - if (getItem()) - { - LLSD::String activate = "NONE"; - if ("playworld" == action) activate = "Inworld"; - if ("playlocal" == action) activate = "Locally"; - - LLPreviewAnim* preview = LLFloaterReg::showTypedInstance("preview_anim", LLSD(mUUID)); - if (preview) - { - preview->play(activate); - } - } - } - else - { - LLItemBridge::performAction(model, action); - } -} - -void LLAnimationBridge::openItem() -{ - LLViewerInventoryItem* item = getItem(); - - if (item) - { - LLInvFVBridgeAction::doAction(item->getType(),mUUID,getInventoryModel()); - } -/* - LLViewerInventoryItem* item = getItem(); - if (item) - { - LLFloaterReg::showInstance("preview_anim", LLSD(mUUID), TAKE_FOCUS_YES); - } -*/ -} - -// +=================================================+ -// | LLObjectBridge | -// +=================================================+ - -// static -LLUUID LLObjectBridge::sContextMenuItemID; - -LLObjectBridge::LLObjectBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid, - LLInventoryType::EType type, - U32 flags) : - LLItemBridge(inventory, root, uuid) -{ - mAttachPt = (flags & 0xff); // low bye of inventory flags - mIsMultiObject = is_flag_set(flags, LLInventoryItemFlags::II_FLAGS_OBJECT_HAS_MULTIPLE_ITEMS); - mInvType = type; -} - -LLUIImagePtr LLObjectBridge::getIcon() const -{ - return LLInventoryIcon::getIcon(LLAssetType::AT_OBJECT, mInvType, mAttachPt, mIsMultiObject); -} - -LLInventoryObject* LLObjectBridge::getObject() const -{ - LLInventoryObject* object = NULL; - LLInventoryModel* model = getInventoryModel(); - if(model) - { - object = (LLInventoryObject*)model->getObject(mUUID); - } - return object; -} - -LLViewerInventoryItem* LLObjectBridge::getItem() const -{ - LLInventoryModel* model = getInventoryModel(); - if (model) - { - return model->getItem(mUUID); - } - return NULL; -} - -LLViewerInventoryCategory* LLObjectBridge::getCategory() const -{ - LLInventoryModel* model = getInventoryModel(); - if (model) - { - return model->getCategory(mUUID); - } - return NULL; -} - -// virtual -void LLObjectBridge::performAction(LLInventoryModel* model, std::string action) -{ - if (isAddAction(action)) - { - LLUUID object_id = mUUID; - LLViewerInventoryItem* item; - item = (LLViewerInventoryItem*)gInventory.getItem(object_id); - if(item && gInventory.isObjectDescendentOf(object_id, gInventory.getRootFolderID())) - { - rez_attachment(item, NULL, true); // Replace if "Wear"ing. - } - else if(item && item->isFinished()) - { - // must be in library. copy it to our inventory and put it on. - LLPointer cb = new LLBoostFuncInventoryCallback(boost::bind(rez_attachment_cb, _1, (LLViewerJointAttachment*)0)); - copy_inventory_item( - gAgent.getID(), - item->getPermissions().getOwner(), - item->getUUID(), - LLUUID::null, - std::string(), - cb); - } - gFocusMgr.setKeyboardFocus(NULL); - } - else if ("wear_add" == action) - { - LLAppearanceMgr::instance().wearItemOnAvatar(mUUID, true, false); // Don't replace if adding. - } - else if ("touch" == action) - { - handle_attachment_touch(mUUID); - } - else if ("edit" == action) - { - handle_attachment_edit(mUUID); - } - else if (isRemoveAction(action)) - { - LLAppearanceMgr::instance().removeItemFromAvatar(mUUID); - } - else LLItemBridge::performAction(model, action); -} - -void LLObjectBridge::openItem() -{ - // object double-click action is to wear/unwear object - performAction(getInventoryModel(), - get_is_item_worn(mUUID) ? "detach" : "attach"); -} - -std::string LLObjectBridge::getLabelSuffix() const -{ - if (get_is_item_worn(mUUID)) - { - if (!isAgentAvatarValid()) // Error condition, can't figure out attach point - { - return LLItemBridge::getLabelSuffix() + LLTrans::getString("worn"); - } - std::string attachment_point_name; - if (gAgentAvatarp->getAttachedPointName(mUUID, attachment_point_name)) - { - LLStringUtil::format_map_t args; - args["[ATTACHMENT_POINT]"] = LLTrans::getString(attachment_point_name); - - return LLItemBridge::getLabelSuffix() + LLTrans::getString("WornOnAttachmentPoint", args); - } - else - { - LLStringUtil::format_map_t args; - args["[ATTACHMENT_ERROR]"] = LLTrans::getString(attachment_point_name); - return LLItemBridge::getLabelSuffix() + LLTrans::getString("AttachmentErrorMessage", args); - } - } - return LLItemBridge::getLabelSuffix(); -} - -void rez_attachment(LLViewerInventoryItem* item, LLViewerJointAttachment* attachment, bool replace) -{ - const LLUUID& item_id = item->getLinkedUUID(); - - // Check for duplicate request. - if (isAgentAvatarValid() && - gAgentAvatarp->isWearingAttachment(item_id)) - { - LL_WARNS() << "ATT duplicate attachment request, ignoring" << LL_ENDL; - return; - } - - S32 attach_pt = 0; - if (isAgentAvatarValid() && attachment) - { - for (LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); - iter != gAgentAvatarp->mAttachmentPoints.end(); ++iter) - { - if (iter->second == attachment) - { - attach_pt = iter->first; - break; - } - } - } - - LLSD payload; - payload["item_id"] = item_id; // Wear the base object in case this is a link. - payload["attachment_point"] = attach_pt; - payload["is_add"] = !replace; - - if (replace && - (attachment && attachment->getNumObjects() > 0)) - { - LLNotificationsUtil::add("ReplaceAttachment", LLSD(), payload, confirm_attachment_rez); - } - else - { - LLNotifications::instance().forceResponse(LLNotification::Params("ReplaceAttachment").payload(payload), 0/*YES*/); - } -} - -bool confirm_attachment_rez(const LLSD& notification, const LLSD& response) -{ - if (!gAgentAvatarp->canAttachMoreObjects()) - { - LLSD args; - args["MAX_ATTACHMENTS"] = llformat("%d", gAgentAvatarp->getMaxAttachments()); - LLNotificationsUtil::add("MaxAttachmentsOnOutfit", args); - return false; - } - - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0/*YES*/) - { - LLUUID item_id = notification["payload"]["item_id"].asUUID(); - LLViewerInventoryItem* itemp = gInventory.getItem(item_id); - - if (itemp) - { - // Queue up attachments to be sent in next idle tick, this way the - // attachments are batched up all into one message versus each attachment - // being sent in its own separate attachments message. - U8 attachment_pt = notification["payload"]["attachment_point"].asInteger(); - bool is_add = notification["payload"]["is_add"].asBoolean(); - - LL_DEBUGS("Avatar") << "ATT calling addAttachmentRequest " << (itemp ? itemp->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL; - LLAttachmentsMgr::instance().addAttachmentRequest(item_id, attachment_pt, is_add); - } - } - return false; -} -static LLNotificationFunctorRegistration confirm_replace_attachment_rez_reg("ReplaceAttachment", confirm_attachment_rez); - -void LLObjectBridge::buildContextMenu(LLMenuGL& menu, U32 flags) -{ - menuentry_vec_t items; - menuentry_vec_t disabled_items; - if(isItemInTrash()) - { - addTrashContextMenuOptions(items, disabled_items); - } - else if (isMarketplaceListingsFolder()) - { - addMarketplaceContextMenuOptions(flags, items, disabled_items); - items.push_back(std::string("Properties")); - getClipboardEntries(false, items, disabled_items, flags); - } - else - { - items.push_back(std::string("Share")); - if (!canShare()) - { - disabled_items.push_back(std::string("Share")); - } - - items.push_back(std::string("Properties")); - - getClipboardEntries(true, items, disabled_items, flags); - - LLObjectBridge::sContextMenuItemID = mUUID; - - LLInventoryItem *item = getItem(); - if(item) - { - if (!isAgentAvatarValid()) return; - - if( get_is_item_worn( mUUID ) ) - { - items.push_back(std::string("Wearable And Object Separator")); - - items.push_back(std::string("Attachment Touch")); - if ( ((flags & FIRST_SELECTED_ITEM) == 0) || !enable_attachment_touch(mUUID) ) - { - disabled_items.push_back(std::string("Attachment Touch")); - } - - items.push_back(std::string("Wearable Edit")); - if ( ((flags & FIRST_SELECTED_ITEM) == 0) || !get_is_item_editable(mUUID) ) - { - disabled_items.push_back(std::string("Wearable Edit")); - } - - items.push_back(std::string("Detach From Yourself")); - } - else if (!isItemInTrash() && !isLinkedObjectInTrash() && !isLinkedObjectMissing() && !isCOFFolder()) - { - items.push_back(std::string("Wearable And Object Separator")); - items.push_back(std::string("Wearable And Object Wear")); - items.push_back(std::string("Wearable Add")); - items.push_back(std::string("Attach To")); - items.push_back(std::string("Attach To HUD")); - // commented out for DEV-32347 - //items.push_back(std::string("Restore to Last Position")); - - if (!gAgentAvatarp->canAttachMoreObjects()) - { - disabled_items.push_back(std::string("Wearable And Object Wear")); - disabled_items.push_back(std::string("Wearable Add")); - disabled_items.push_back(std::string("Attach To")); - disabled_items.push_back(std::string("Attach To HUD")); - } - LLMenuGL* attach_menu = menu.findChildMenuByName("Attach To", true); - LLMenuGL* attach_hud_menu = menu.findChildMenuByName("Attach To HUD", true); - if (attach_menu - && (attach_menu->getChildCount() == 0) - && attach_hud_menu - && (attach_hud_menu->getChildCount() == 0) - && isAgentAvatarValid()) - { - for (LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); - iter != gAgentAvatarp->mAttachmentPoints.end(); ) - { - LLVOAvatar::attachment_map_t::iterator curiter = iter++; - LLViewerJointAttachment* attachment = curiter->second; - LLMenuItemCallGL::Params p; - std::string submenu_name = attachment->getName(); - if (LLTrans::getString(submenu_name) != "") - { - p.name = (" ")+LLTrans::getString(submenu_name)+" "; - } - else - { - p.name = submenu_name; - } - LLSD cbparams; - cbparams["index"] = curiter->first; - cbparams["label"] = p.name; - p.on_click.function_name = "Inventory.AttachObject"; - p.on_click.parameter = LLSD(attachment->getName()); - p.on_enable.function_name = "Attachment.Label"; - p.on_enable.parameter = cbparams; - LLView* parent = attachment->getIsHUDAttachment() ? attach_hud_menu : attach_menu; - LLUICtrlFactory::create(p, parent); - items.push_back(p.name); - } - } - } - } - } - addLinkReplaceMenuOption(items, disabled_items); - hide_context_entries(menu, items, disabled_items); -} - -bool LLObjectBridge::renameItem(const std::string& new_name) -{ - if(!isItemRenameable()) - return false; - LLPreview::dirty(mUUID); - LLInventoryModel* model = getInventoryModel(); - if(!model) - return false; - LLViewerInventoryItem* item = getItem(); - if(item && (item->getName() != new_name)) - { - LLPointer new_item = new LLViewerInventoryItem(item); - new_item->rename(new_name); - new_item->updateServer(false); - model->updateItem(new_item); - model->notifyObservers(); - buildDisplayName(); - - if (isAgentAvatarValid()) - { - LLViewerObject* obj = gAgentAvatarp->getWornAttachment( item->getUUID() ); - if(obj) - { - LLSelectMgr::getInstance()->deselectAll(); - LLSelectMgr::getInstance()->addAsIndividual( obj, SELECT_ALL_TES, false ); - LLSelectMgr::getInstance()->selectionSetObjectName( new_name ); - LLSelectMgr::getInstance()->deselectAll(); - } - } - } - // return false because we either notified observers (& therefore - // rebuilt) or we didn't update. - return false; -} - -// +=================================================+ -// | LLLSLTextBridge | -// +=================================================+ - -void LLLSLTextBridge::openItem() -{ - LLViewerInventoryItem* item = getItem(); - - if (item) - { - LLInvFVBridgeAction::doAction(item->getType(),mUUID,getInventoryModel()); - } -} - -// +=================================================+ -// | LLWearableBridge | -// +=================================================+ - -LLWearableBridge::LLWearableBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid, - LLAssetType::EType asset_type, - LLInventoryType::EType inv_type, - LLWearableType::EType wearable_type) : - LLItemBridge(inventory, root, uuid), - mAssetType( asset_type ), - mWearableType(wearable_type) -{ - mInvType = inv_type; -} - -bool LLWearableBridge::renameItem(const std::string& new_name) -{ - if (get_is_item_worn(mUUID)) - { - gAgentWearables.setWearableName( mUUID, new_name ); - } - return LLItemBridge::renameItem(new_name); -} - -std::string LLWearableBridge::getLabelSuffix() const -{ - if (get_is_item_worn(mUUID)) - { - // e.g. "(worn)" - return LLItemBridge::getLabelSuffix() + LLTrans::getString("worn"); - } - else - { - return LLItemBridge::getLabelSuffix(); - } -} - -LLUIImagePtr LLWearableBridge::getIcon() const -{ - return LLInventoryIcon::getIcon(mAssetType, mInvType, mWearableType, false); -} - -// virtual -void LLWearableBridge::performAction(LLInventoryModel* model, std::string action) -{ - if (isAddAction(action)) - { - wearOnAvatar(); - } - else if ("wear_add" == action) - { - wearAddOnAvatar(); - } - else if ("edit" == action) - { - editOnAvatar(); - return; - } - else if (isRemoveAction(action)) - { - removeFromAvatar(); - return; - } - else LLItemBridge::performAction(model, action); -} - -void LLWearableBridge::openItem() -{ - performAction(getInventoryModel(), - get_is_item_worn(mUUID) ? "take_off" : "wear"); -} - -void LLWearableBridge::buildContextMenu(LLMenuGL& menu, U32 flags) -{ - LL_DEBUGS() << "LLWearableBridge::buildContextMenu()" << LL_ENDL; - menuentry_vec_t items; - menuentry_vec_t disabled_items; - if(isItemInTrash()) - { - addTrashContextMenuOptions(items, disabled_items); - } - else if (isMarketplaceListingsFolder()) - { - addMarketplaceContextMenuOptions(flags, items, disabled_items); - items.push_back(std::string("Properties")); - getClipboardEntries(false, items, disabled_items, flags); - } - else - { // FWIW, it looks like SUPPRESS_OPEN_ITEM is not set anywhere - bool can_open = ((flags & SUPPRESS_OPEN_ITEM) != SUPPRESS_OPEN_ITEM); - - // If we have clothing, don't add "Open" as it's the same action as "Wear" SL-18976 - LLViewerInventoryItem* item = getItem(); - if (can_open && item) - { - can_open = (item->getType() != LLAssetType::AT_CLOTHING) && - (item->getType() != LLAssetType::AT_BODYPART); - } - if (isLinkedObjectMissing()) - { - can_open = false; - } - items.push_back(std::string("Share")); - if (!canShare()) - { - disabled_items.push_back(std::string("Share")); - } - - if (can_open) - { - addOpenRightClickMenuOption(items); - } - else - { - disabled_items.push_back(std::string("Open")); - disabled_items.push_back(std::string("Open Original")); - } - - items.push_back(std::string("Properties")); - - getClipboardEntries(true, items, disabled_items, flags); - - items.push_back(std::string("Wearable And Object Separator")); - items.push_back(std::string("Wearable Edit")); - - if (((flags & FIRST_SELECTED_ITEM) == 0) || (item && !gAgentWearables.isWearableModifiable(item->getUUID()))) - { - disabled_items.push_back(std::string("Wearable Edit")); - } - // Don't allow items to be worn if their baseobj is in the trash. - if (isLinkedObjectInTrash() || isLinkedObjectMissing() || isCOFFolder()) - { - disabled_items.push_back(std::string("Wearable And Object Wear")); - disabled_items.push_back(std::string("Wearable Add")); - disabled_items.push_back(std::string("Wearable Edit")); - } - - // Disable wear and take off based on whether the item is worn. - if(item) - { - switch (item->getType()) - { - case LLAssetType::AT_CLOTHING: - items.push_back(std::string("Take Off")); - // Fallthrough since clothing and bodypart share wear options - case LLAssetType::AT_BODYPART: - if (get_is_item_worn(item->getUUID())) - { - disabled_items.push_back(std::string("Wearable And Object Wear")); - disabled_items.push_back(std::string("Wearable Add")); - } - else - { - items.push_back(std::string("Wearable And Object Wear")); - disabled_items.push_back(std::string("Take Off")); - disabled_items.push_back(std::string("Wearable Edit")); - } - - if (LLWearableType::getInstance()->getAllowMultiwear(mWearableType)) - { - items.push_back(std::string("Wearable Add")); - if (!gAgentWearables.canAddWearable(mWearableType)) - { - disabled_items.push_back(std::string("Wearable Add")); - } - } - break; - default: - break; - } - } - } - addLinkReplaceMenuOption(items, disabled_items); - hide_context_entries(menu, items, disabled_items); -} - -// Called from menus -// static -bool LLWearableBridge::canWearOnAvatar(void* user_data) -{ - LLWearableBridge* self = (LLWearableBridge*)user_data; - if(!self) return false; - if(!self->isAgentInventory()) - { - LLViewerInventoryItem* item = (LLViewerInventoryItem*)self->getItem(); - if(!item || !item->isFinished()) return false; - } - return (!get_is_item_worn(self->mUUID)); -} - -// Called from menus -// static -void LLWearableBridge::onWearOnAvatar(void* user_data) -{ - LLWearableBridge* self = (LLWearableBridge*)user_data; - if(!self) return; - self->wearOnAvatar(); -} - -void LLWearableBridge::wearOnAvatar() -{ - // TODO: investigate wearables may not be loaded at this point EXT-8231 - - LLViewerInventoryItem* item = getItem(); - if(item) - { - LLAppearanceMgr::instance().wearItemOnAvatar(item->getUUID(), true, true); - } -} - -void LLWearableBridge::wearAddOnAvatar() -{ - // TODO: investigate wearables may not be loaded at this point EXT-8231 - - LLViewerInventoryItem* item = getItem(); - if(item) - { - LLAppearanceMgr::instance().wearItemOnAvatar(item->getUUID(), true, false); - } -} - -// static -void LLWearableBridge::onWearOnAvatarArrived( LLViewerWearable* wearable, void* userdata ) -{ - LLUUID* item_id = (LLUUID*) userdata; - if(wearable) - { - LLViewerInventoryItem* item = NULL; - item = (LLViewerInventoryItem*)gInventory.getItem(*item_id); - if(item) - { - if(item->getAssetUUID() == wearable->getAssetID()) - { - gAgentWearables.setWearableItem(item, wearable); - gInventory.notifyObservers(); - //self->getFolderItem()->refreshFromRoot(); - } - else - { - LL_INFOS() << "By the time wearable asset arrived, its inv item already pointed to a different asset." << LL_ENDL; - } - } - } - delete item_id; -} - -// static -// BAP remove the "add" code path once everything is fully COF-ified. -void LLWearableBridge::onWearAddOnAvatarArrived( LLViewerWearable* wearable, void* userdata ) -{ - LLUUID* item_id = (LLUUID*) userdata; - if(wearable) - { - LLViewerInventoryItem* item = NULL; - item = (LLViewerInventoryItem*)gInventory.getItem(*item_id); - if(item) - { - if(item->getAssetUUID() == wearable->getAssetID()) - { - bool do_append = true; - gAgentWearables.setWearableItem(item, wearable, do_append); - gInventory.notifyObservers(); - //self->getFolderItem()->refreshFromRoot(); - } - else - { - LL_INFOS() << "By the time wearable asset arrived, its inv item already pointed to a different asset." << LL_ENDL; - } - } - } - delete item_id; -} - -// static -bool LLWearableBridge::canEditOnAvatar(void* user_data) -{ - LLWearableBridge* self = (LLWearableBridge*)user_data; - if(!self) return false; - - return (get_is_item_worn(self->mUUID)); -} - -// static -void LLWearableBridge::onEditOnAvatar(void* user_data) -{ - LLWearableBridge* self = (LLWearableBridge*)user_data; - if(self) - { - self->editOnAvatar(); - } -} - -void LLWearableBridge::editOnAvatar() -{ - LLAgentWearables::editWearable(mUUID); -} - -// static -bool LLWearableBridge::canRemoveFromAvatar(void* user_data) -{ - LLWearableBridge* self = (LLWearableBridge*)user_data; - if( self && (LLAssetType::AT_BODYPART != self->mAssetType) ) - { - return get_is_item_worn( self->mUUID ); - } - return false; -} - -void LLWearableBridge::removeFromAvatar() -{ - LL_WARNS() << "safe to remove?" << LL_ENDL; - if (get_is_item_worn(mUUID)) - { - LLAppearanceMgr::instance().removeItemFromAvatar(mUUID); - } -} - - -// +=================================================+ -// | LLLinkItemBridge | -// +=================================================+ -// For broken item links - -std::string LLLinkItemBridge::sPrefix("Link: "); - -void LLLinkItemBridge::buildContextMenu(LLMenuGL& menu, U32 flags) -{ - // *TODO: Translate - LL_DEBUGS() << "LLLink::buildContextMenu()" << LL_ENDL; - menuentry_vec_t items; - menuentry_vec_t disabled_items; - - items.push_back(std::string("Find Original")); - disabled_items.push_back(std::string("Find Original")); - - if(isItemInTrash()) - { - addTrashContextMenuOptions(items, disabled_items); - } - else - { - items.push_back(std::string("Properties")); - addDeleteContextMenuOptions(items, disabled_items); - } - addLinkReplaceMenuOption(items, disabled_items); - hide_context_entries(menu, items, disabled_items); -} - -// +=================================================+ -// | LLSettingsBridge | -// +=================================================+ - -LLSettingsBridge::LLSettingsBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid, - LLSettingsType::type_e settings_type): - LLItemBridge(inventory, root, uuid), - mSettingsType(settings_type) -{ -} - -LLUIImagePtr LLSettingsBridge::getIcon() const -{ - return LLInventoryIcon::getIcon(LLAssetType::AT_SETTINGS, LLInventoryType::IT_SETTINGS, mSettingsType, false); -} - -void LLSettingsBridge::performAction(LLInventoryModel* model, std::string action) -{ - if ("apply_settings_local" == action) - { - // Single item only - LLViewerInventoryItem* item = static_cast(getItem()); - if (!item) - return; - LLUUID asset_id = item->getAssetUUID(); - LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, asset_id, LLEnvironment::TRANSITION_INSTANT); - LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); - } - else if ("apply_settings_parcel" == action) - { - // Single item only - LLViewerInventoryItem* item = static_cast(getItem()); - if (!item) - return; - LLUUID asset_id = item->getAssetUUID(); - std::string name = item->getName(); - - U32 flags(0); - - if (!item->getPermissions().allowOperationBy(PERM_MODIFY, gAgent.getID())) - flags |= LLSettingsBase::FLAG_NOMOD; - if (!item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID())) - flags |= LLSettingsBase::FLAG_NOTRANS; - - LLParcel *parcel = LLViewerParcelMgr::instance().getAgentOrSelectedParcel(); - if (!parcel) - { - LL_WARNS("INVENTORY") << "could not identify parcel." << LL_ENDL; - return; - } - S32 parcel_id = parcel->getLocalID(); - - LL_DEBUGS("ENVIRONMENT") << "Applying asset ID " << asset_id << " to parcel " << parcel_id << LL_ENDL; - LLEnvironment::instance().updateParcel(parcel_id, asset_id, name, LLEnvironment::NO_TRACK, -1, -1, flags); - LLEnvironment::instance().setSharedEnvironment(); - } - else - LLItemBridge::performAction(model, action); -} - -void LLSettingsBridge::openItem() -{ - LLViewerInventoryItem* item = getItem(); - if (item) - { - if (item->getPermissions().getOwner() != gAgent.getID()) - LLNotificationsUtil::add("NoEditFromLibrary"); - else - LLInvFVBridgeAction::doAction(item->getType(), mUUID, getInventoryModel()); - } -} - -void LLSettingsBridge::buildContextMenu(LLMenuGL& menu, U32 flags) -{ - LL_DEBUGS() << "LLSettingsBridge::buildContextMenu()" << LL_ENDL; - menuentry_vec_t items; - menuentry_vec_t disabled_items; - - if (isMarketplaceListingsFolder()) - { - menuentry_vec_t items; - menuentry_vec_t disabled_items; - addMarketplaceContextMenuOptions(flags, items, disabled_items); - items.push_back(std::string("Properties")); - getClipboardEntries(false, items, disabled_items, flags); - hide_context_entries(menu, items, disabled_items); - } - else if (isItemInTrash()) - { - addTrashContextMenuOptions(items, disabled_items); - } - else - { - items.push_back(std::string("Share")); - if (!canShare()) - { - disabled_items.push_back(std::string("Share")); - } - - addOpenRightClickMenuOption(items); - items.push_back(std::string("Properties")); - - getClipboardEntries(true, items, disabled_items, flags); - - items.push_back("Settings Separator"); - items.push_back("Settings Apply Local"); - - items.push_back("Settings Apply Parcel"); - if (!canUpdateParcel()) - disabled_items.push_back("Settings Apply Parcel"); - - items.push_back("Settings Apply Region"); - if (!canUpdateRegion()) - disabled_items.push_back("Settings Apply Region"); - } - addLinkReplaceMenuOption(items, disabled_items); - hide_context_entries(menu, items, disabled_items); -} - -bool LLSettingsBridge::renameItem(const std::string& new_name) -{ - /*TODO: change internal settings name? */ - return LLItemBridge::renameItem(new_name); -} - -bool LLSettingsBridge::isItemRenameable() const -{ - LLViewerInventoryItem* item = getItem(); - if (item) - { - return (item->getPermissions().allowModifyBy(gAgent.getID())); - } - return false; -} - -bool LLSettingsBridge::canUpdateParcel() const -{ - return LLEnvironment::instance().canAgentUpdateParcelEnvironment(); -} - -bool LLSettingsBridge::canUpdateRegion() const -{ - return LLEnvironment::instance().canAgentUpdateRegionEnvironment(); -} - - -// +=================================================+ -// | LLMaterialBridge | -// +=================================================+ - -void LLMaterialBridge::openItem() -{ - LLViewerInventoryItem* item = getItem(); - if (item) - { - LLInvFVBridgeAction::doAction(item->getType(),mUUID,getInventoryModel()); - } -} - -void LLMaterialBridge::buildContextMenu(LLMenuGL& menu, U32 flags) -{ - LL_DEBUGS() << "LLMaterialBridge::buildContextMenu()" << LL_ENDL; - - if (isMarketplaceListingsFolder()) - { - menuentry_vec_t items; - menuentry_vec_t disabled_items; - addMarketplaceContextMenuOptions(flags, items, disabled_items); - items.push_back(std::string("Properties")); - getClipboardEntries(false, items, disabled_items, flags); - hide_context_entries(menu, items, disabled_items); - } - else - { - LLItemBridge::buildContextMenu(menu, flags); - } -} - - -// +=================================================+ -// | LLLinkBridge | -// +=================================================+ -// For broken folder links. -std::string LLLinkFolderBridge::sPrefix("Link: "); -LLUIImagePtr LLLinkFolderBridge::getIcon() const -{ - LLFolderType::EType folder_type = LLFolderType::FT_NONE; - const LLInventoryObject *obj = getInventoryObject(); - if (obj) - { - LLViewerInventoryCategory* cat = NULL; - LLInventoryModel* model = getInventoryModel(); - if(model) - { - cat = (LLViewerInventoryCategory*)model->getCategory(obj->getLinkedUUID()); - if (cat) - { - folder_type = cat->getPreferredType(); - } - } - } - return LLFolderBridge::getIcon(folder_type); -} - -void LLLinkFolderBridge::buildContextMenu(LLMenuGL& menu, U32 flags) -{ - // *TODO: Translate - LL_DEBUGS() << "LLLink::buildContextMenu()" << LL_ENDL; - menuentry_vec_t items; - menuentry_vec_t disabled_items; - - if (isItemInTrash()) - { - addTrashContextMenuOptions(items, disabled_items); - } - else - { - items.push_back(std::string("Find Original")); - addDeleteContextMenuOptions(items, disabled_items); - } - hide_context_entries(menu, items, disabled_items); -} -void LLLinkFolderBridge::performAction(LLInventoryModel* model, std::string action) -{ - if ("goto" == action) - { - gotoItem(); - return; - } - LLItemBridge::performAction(model,action); -} -void LLLinkFolderBridge::gotoItem() -{ - LLItemBridge::gotoItem(); - - const LLUUID &cat_uuid = getFolderID(); - if (!cat_uuid.isNull()) - { - LLFolderViewItem *base_folder = LLInventoryPanel::getActiveInventoryPanel()->getItemByID(cat_uuid); - if (base_folder) - { - base_folder->setOpen(true); - } - } -} -const LLUUID &LLLinkFolderBridge::getFolderID() const -{ - if (LLViewerInventoryItem *link_item = getItem()) - { - if (const LLViewerInventoryCategory *cat = link_item->getLinkedCategory()) - { - const LLUUID& cat_uuid = cat->getUUID(); - return cat_uuid; - } - } - return LLUUID::null; -} - -void LLUnknownItemBridge::buildContextMenu(LLMenuGL& menu, U32 flags) -{ - menuentry_vec_t items; - menuentry_vec_t disabled_items; - items.push_back(std::string("Properties")); - items.push_back(std::string("Rename")); - hide_context_entries(menu, items, disabled_items); -} - -LLUIImagePtr LLUnknownItemBridge::getIcon() const -{ - return LLInventoryIcon::getIcon(LLAssetType::AT_UNKNOWN, mInvType); -} - - -/******************************************************************************** - ** - ** BRIDGE ACTIONS - **/ - -// static -void LLInvFVBridgeAction::doAction(LLAssetType::EType asset_type, - const LLUUID& uuid, - LLInventoryModel* model) -{ - // Perform indirection in case of link. - const LLUUID& linked_uuid = gInventory.getLinkedItemID(uuid); - - LLInvFVBridgeAction* action = createAction(asset_type,linked_uuid,model); - if(action) - { - action->doIt(); - delete action; - } -} - -// static -void LLInvFVBridgeAction::doAction(const LLUUID& uuid, LLInventoryModel* model) -{ - llassert(model); - LLViewerInventoryItem* item = model->getItem(uuid); - llassert(item); - if (item) - { - LLAssetType::EType asset_type = item->getType(); - LLInvFVBridgeAction* action = createAction(asset_type,uuid,model); - if(action) - { - action->doIt(); - delete action; - } - } -} - -LLViewerInventoryItem* LLInvFVBridgeAction::getItem() const -{ - if (mModel) - return (LLViewerInventoryItem*)mModel->getItem(mUUID); - return NULL; -} - -class LLTextureBridgeAction: public LLInvFVBridgeAction -{ - friend class LLInvFVBridgeAction; -public: - virtual void doIt() - { - if (getItem()) - { - LLFloaterReg::showInstance("preview_texture", LLSD(mUUID), TAKE_FOCUS_YES); - } - LLInvFVBridgeAction::doIt(); - } - virtual ~LLTextureBridgeAction(){} -protected: - LLTextureBridgeAction(const LLUUID& id,LLInventoryModel* model) : LLInvFVBridgeAction(id,model) {} -}; - -class LLSoundBridgeAction: public LLInvFVBridgeAction -{ - friend class LLInvFVBridgeAction; -public: - virtual void doIt() - { - LLViewerInventoryItem* item = getItem(); - if (item) - { - send_sound_trigger(item->getAssetUUID(), SOUND_GAIN); - } - LLInvFVBridgeAction::doIt(); - } - virtual ~LLSoundBridgeAction(){} -protected: - LLSoundBridgeAction(const LLUUID& id,LLInventoryModel* model) : LLInvFVBridgeAction(id,model) {} -}; - -class LLLandmarkBridgeAction: public LLInvFVBridgeAction -{ - friend class LLInvFVBridgeAction; -public: - virtual void doIt() - { - LLViewerInventoryItem* item = getItem(); - if (item) - { - // Opening (double-clicking) a landmark immediately teleports, - // but warns you the first time. - LLSD payload; - payload["asset_id"] = item->getAssetUUID(); - - LLSD args; - args["LOCATION"] = item->getName(); - - LLNotificationsUtil::add("TeleportFromLandmark", args, payload); - } - LLInvFVBridgeAction::doIt(); - } - virtual ~LLLandmarkBridgeAction(){} -protected: - LLLandmarkBridgeAction(const LLUUID& id,LLInventoryModel* model) : LLInvFVBridgeAction(id,model) {} -}; - -class LLCallingCardBridgeAction: public LLInvFVBridgeAction -{ - friend class LLInvFVBridgeAction; -public: - virtual void doIt() - { - LLViewerInventoryItem* item = getItem(); - if (item && item->getCreatorUUID().notNull()) - { - LLAvatarActions::showProfile(item->getCreatorUUID()); - } - LLInvFVBridgeAction::doIt(); - } - virtual ~LLCallingCardBridgeAction(){} -protected: - LLCallingCardBridgeAction(const LLUUID& id,LLInventoryModel* model) : LLInvFVBridgeAction(id,model) {} - -}; - -class LLNotecardBridgeAction -: public LLInvFVBridgeAction -{ - friend class LLInvFVBridgeAction; -public: - virtual void doIt() - { - LLViewerInventoryItem* item = getItem(); - if (item) - { - LLFloaterReg::showInstance("preview_notecard", LLSD(item->getUUID()), TAKE_FOCUS_YES); - } - LLInvFVBridgeAction::doIt(); - } - virtual ~LLNotecardBridgeAction(){} -protected: - LLNotecardBridgeAction(const LLUUID& id,LLInventoryModel* model) : LLInvFVBridgeAction(id,model) {} -}; - -class LLGestureBridgeAction: public LLInvFVBridgeAction -{ - friend class LLInvFVBridgeAction; -public: - virtual void doIt() - { - LLViewerInventoryItem* item = getItem(); - if (item) - { - LLPreviewGesture* preview = LLPreviewGesture::show(mUUID, LLUUID::null); - preview->setFocus(true); - } - LLInvFVBridgeAction::doIt(); - } - virtual ~LLGestureBridgeAction(){} -protected: - LLGestureBridgeAction(const LLUUID& id,LLInventoryModel* model) : LLInvFVBridgeAction(id,model) {} -}; - -class LLAnimationBridgeAction: public LLInvFVBridgeAction -{ - friend class LLInvFVBridgeAction; -public: - virtual void doIt() - { - LLViewerInventoryItem* item = getItem(); - if (item) - { - LLFloaterReg::showInstance("preview_anim", LLSD(mUUID), TAKE_FOCUS_YES); - } - LLInvFVBridgeAction::doIt(); - } - virtual ~LLAnimationBridgeAction(){} -protected: - LLAnimationBridgeAction(const LLUUID& id,LLInventoryModel* model) : LLInvFVBridgeAction(id,model) {} -}; - -class LLObjectBridgeAction: public LLInvFVBridgeAction -{ - friend class LLInvFVBridgeAction; -public: - virtual void doIt() - { - attachOrDetach(); - } - virtual ~LLObjectBridgeAction(){} -protected: - LLObjectBridgeAction(const LLUUID& id,LLInventoryModel* model) : LLInvFVBridgeAction(id,model) {} - void attachOrDetach(); -}; - -void LLObjectBridgeAction::attachOrDetach() -{ - if (get_is_item_worn(mUUID)) - { - LLAppearanceMgr::instance().removeItemFromAvatar(mUUID); - } - else - { - LLAppearanceMgr::instance().wearItemOnAvatar(mUUID, true, false); // Don't replace if adding. - } -} - -class LLLSLTextBridgeAction: public LLInvFVBridgeAction -{ - friend class LLInvFVBridgeAction; -public: - virtual void doIt() - { - LLViewerInventoryItem* item = getItem(); - if (item) - { - LLFloaterReg::showInstance("preview_script", LLSD(mUUID), TAKE_FOCUS_YES); - } - LLInvFVBridgeAction::doIt(); - } - virtual ~LLLSLTextBridgeAction(){} -protected: - LLLSLTextBridgeAction(const LLUUID& id,LLInventoryModel* model) : LLInvFVBridgeAction(id,model) {} -}; - -class LLWearableBridgeAction: public LLInvFVBridgeAction -{ - friend class LLInvFVBridgeAction; -public: - virtual void doIt() - { - wearOnAvatar(); - } - - virtual ~LLWearableBridgeAction(){} -protected: - LLWearableBridgeAction(const LLUUID& id,LLInventoryModel* model) : LLInvFVBridgeAction(id,model) {} - bool isItemInTrash() const; - // return true if the item is in agent inventory. if false, it - // must be lost or in the inventory library. - bool isAgentInventory() const; - void wearOnAvatar(); -}; - -bool LLWearableBridgeAction::isItemInTrash() const -{ - if(!mModel) return false; - const LLUUID trash_id = mModel->findCategoryUUIDForType(LLFolderType::FT_TRASH); - return mModel->isObjectDescendentOf(mUUID, trash_id); -} - -bool LLWearableBridgeAction::isAgentInventory() const -{ - if(!mModel) return false; - if(gInventory.getRootFolderID() == mUUID) return true; - return mModel->isObjectDescendentOf(mUUID, gInventory.getRootFolderID()); -} - -void LLWearableBridgeAction::wearOnAvatar() -{ - // TODO: investigate wearables may not be loaded at this point EXT-8231 - - LLViewerInventoryItem* item = getItem(); - if(item) - { - if (get_is_item_worn(mUUID)) - { - if(item->getType() != LLAssetType::AT_BODYPART) - { - LLAppearanceMgr::instance().removeItemFromAvatar(item->getUUID()); - } - } - else - { - LLAppearanceMgr::instance().wearItemOnAvatar(item->getUUID(), true, true); - } - } -} - -class LLSettingsBridgeAction - : public LLInvFVBridgeAction -{ - friend class LLInvFVBridgeAction; -public: - virtual void doIt() - { - LLViewerInventoryItem* item = getItem(); - if (item) - { - LLSettingsType::type_e type = item->getSettingsType(); - switch (type) - { - case LLSettingsType::ST_SKY: - LLFloaterReg::showInstance("env_fixed_environmentent_sky", LLSDMap("inventory_id", item->getUUID()), TAKE_FOCUS_YES); - break; - case LLSettingsType::ST_WATER: - LLFloaterReg::showInstance("env_fixed_environmentent_water", LLSDMap("inventory_id", item->getUUID()), TAKE_FOCUS_YES); - break; - case LLSettingsType::ST_DAYCYCLE: - LLFloaterReg::showInstance("env_edit_extdaycycle", LLSDMap("inventory_id", item->getUUID())("edit_context", "inventory"), TAKE_FOCUS_YES); - break; - default: - break; - } - } - LLInvFVBridgeAction::doIt(); - } - virtual ~LLSettingsBridgeAction(){} -protected: - LLSettingsBridgeAction(const LLUUID& id, LLInventoryModel* model) : LLInvFVBridgeAction(id, model) {} -}; - -class LLMaterialBridgeAction : public LLInvFVBridgeAction -{ - friend class LLInvFVBridgeAction; -public: - void doIt() override - { - LLViewerInventoryItem* item = getItem(); - if (item) - { - LLFloaterReg::showInstance("material_editor", LLSD(item->getUUID()), TAKE_FOCUS_YES); - } - LLInvFVBridgeAction::doIt(); - } - ~LLMaterialBridgeAction() = default; -private: - LLMaterialBridgeAction(const LLUUID& id,LLInventoryModel* model) : LLInvFVBridgeAction(id,model) {} -}; - - -LLInvFVBridgeAction* LLInvFVBridgeAction::createAction(LLAssetType::EType asset_type, - const LLUUID& uuid, - LLInventoryModel* model) -{ - LLInvFVBridgeAction* action = NULL; - switch(asset_type) - { - case LLAssetType::AT_TEXTURE: - action = new LLTextureBridgeAction(uuid,model); - break; - case LLAssetType::AT_SOUND: - action = new LLSoundBridgeAction(uuid,model); - break; - case LLAssetType::AT_LANDMARK: - action = new LLLandmarkBridgeAction(uuid,model); - break; - case LLAssetType::AT_CALLINGCARD: - action = new LLCallingCardBridgeAction(uuid,model); - break; - case LLAssetType::AT_OBJECT: - action = new LLObjectBridgeAction(uuid,model); - break; - case LLAssetType::AT_NOTECARD: - action = new LLNotecardBridgeAction(uuid,model); - break; - case LLAssetType::AT_ANIMATION: - action = new LLAnimationBridgeAction(uuid,model); - break; - case LLAssetType::AT_GESTURE: - action = new LLGestureBridgeAction(uuid,model); - break; - case LLAssetType::AT_LSL_TEXT: - action = new LLLSLTextBridgeAction(uuid,model); - break; - case LLAssetType::AT_CLOTHING: - case LLAssetType::AT_BODYPART: - action = new LLWearableBridgeAction(uuid,model); - break; - case LLAssetType::AT_SETTINGS: - action = new LLSettingsBridgeAction(uuid, model); - break; - case LLAssetType::AT_MATERIAL: - action = new LLMaterialBridgeAction(uuid, model); - break; - default: - break; - } - return action; -} - -/** Bridge Actions - ** - ********************************************************************************/ - -/************************************************************************/ -/* Recent Inventory Panel related classes */ -/************************************************************************/ -void LLRecentItemsFolderBridge::buildContextMenu(LLMenuGL& menu, U32 flags) -{ - menuentry_vec_t disabled_items, items; - buildContextMenuOptions(flags, items, disabled_items); - - items.erase(std::remove(items.begin(), items.end(), std::string("New Folder")), items.end()); - - hide_context_entries(menu, items, disabled_items); -} - -LLInvFVBridge* LLRecentInventoryBridgeBuilder::createBridge( - LLAssetType::EType asset_type, - LLAssetType::EType actual_asset_type, - LLInventoryType::EType inv_type, - LLInventoryPanel* inventory, - LLFolderViewModelInventory* view_model, - LLFolderView* root, - const LLUUID& uuid, - U32 flags /*= 0x00*/ ) const -{ - LLInvFVBridge* new_listener = NULL; - if (asset_type == LLAssetType::AT_CATEGORY - && actual_asset_type != LLAssetType::AT_LINK_FOLDER) - { - new_listener = new LLRecentItemsFolderBridge(inv_type, inventory, root, uuid); - } - else - { - new_listener = LLInventoryFolderViewModelBuilder::createBridge(asset_type, - actual_asset_type, - inv_type, - inventory, - view_model, - root, - uuid, - flags); - } - return new_listener; -} - -LLFolderViewGroupedItemBridge::LLFolderViewGroupedItemBridge() -{ -} - -void LLFolderViewGroupedItemBridge::groupFilterContextMenu(folder_view_item_deque& selected_items, LLMenuGL& menu) -{ - uuid_vec_t ids; - menuentry_vec_t disabled_items; - if (get_selection_item_uuids(selected_items, ids)) - { - if (!LLAppearanceMgr::instance().canAddWearables(ids) && canWearSelected(ids)) - { - disabled_items.push_back(std::string("Wearable And Object Wear")); - disabled_items.push_back(std::string("Wearable Add")); - disabled_items.push_back(std::string("Attach To")); - disabled_items.push_back(std::string("Attach To HUD")); - } - } - disable_context_entries_if_present(menu, disabled_items); -} - -bool LLFolderViewGroupedItemBridge::canWearSelected(const uuid_vec_t& item_ids) const -{ - for (uuid_vec_t::const_iterator it = item_ids.begin(); it != item_ids.end(); ++it) - { - const LLViewerInventoryItem* item = gInventory.getItem(*it); - if (!item || (item->getType() >= LLAssetType::AT_COUNT) || (item->getType() <= LLAssetType::AT_NONE)) - { - return false; - } - } - return true; -} -// EOF +/** + * @file llinventorybridge.cpp + * @brief Implementation of the Inventory-Folder-View-Bridge classes. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llinventorybridge.h" + +// external projects +#include "lltransfersourceasset.h" +#include "llavatarnamecache.h" // IDEVO + +#include "llagent.h" +#include "llagentcamera.h" +#include "llagentwearables.h" +#include "llappearancemgr.h" +#include "llattachmentsmgr.h" +#include "llavataractions.h" +#include "llfavoritesbar.h" // management of favorites folder +#include "llfloateropenobject.h" +#include "llfloaterreg.h" +#include "llfloatermarketplacelistings.h" +#include "llfloatersidepanelcontainer.h" +#include "llsidepanelinventory.h" +#include "llfloaterworldmap.h" +#include "llfolderview.h" +#include "llfriendcard.h" +#include "llgesturemgr.h" +#include "llgiveinventory.h" +#include "llfloaterimcontainer.h" +#include "llimview.h" +#include "llclipboard.h" +#include "llinventorydefines.h" +#include "llinventoryfunctions.h" +#include "llinventoryicon.h" +#include "llinventorymodel.h" +#include "llinventorymodelbackgroundfetch.h" +#include "llinventorypanel.h" +#include "llmarketplacefunctions.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "llpreviewanim.h" +#include "llpreviewgesture.h" +#include "llpreviewtexture.h" +#include "llselectmgr.h" +#include "llsidepanelappearance.h" +#include "lltooldraganddrop.h" +#include "lltrans.h" +#include "llurlaction.h" +#include "llviewerassettype.h" +#include "llviewerfoldertype.h" +#include "llviewermenu.h" +#include "llviewermessage.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" +#include "llvoavatarself.h" +#include "llwearablelist.h" +#include "llwearableitemslist.h" +#include "lllandmarkactions.h" +#include "llpanellandmarks.h" +#include "llviewerparcelmgr.h" +#include "llparcel.h" + +#include "llenvironment.h" + +#include + +void copy_slurl_to_clipboard_callback_inv(const std::string& slurl); + +const F32 SOUND_GAIN = 1.0f; +const F32 FOLDER_LOADING_MESSAGE_DELAY = 0.5f; // Seconds to wait before showing the LOADING... text in folder views + +using namespace LLOldEvents; + +// Function declarations +bool confirm_attachment_rez(const LLSD& notification, const LLSD& response); +void teleport_via_landmark(const LLUUID& asset_id); +static bool check_category(LLInventoryModel* model, + const LLUUID& cat_id, + LLInventoryPanel* active_panel, + LLInventoryFilter* filter); +static bool check_item(const LLUUID& item_id, + LLInventoryPanel* active_panel, + LLInventoryFilter* filter); + +// Helper functions + +bool isAddAction(const std::string& action) +{ + return ("wear" == action || "attach" == action || "activate" == action); +} + +bool isRemoveAction(const std::string& action) +{ + return ("take_off" == action || "detach" == action); +} + +bool isMarketplaceSendAction(const std::string& action) +{ + return ("send_to_marketplace" == action); +} + +bool isPanelActive(const std::string& panel_name) +{ + LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(false); + return (active_panel && (active_panel->getName() == panel_name)); +} + +// Used by LLFolderBridge as callback for directory fetching recursion +class LLRightClickInventoryFetchDescendentsObserver : public LLInventoryFetchDescendentsObserver +{ +public: + LLRightClickInventoryFetchDescendentsObserver(const uuid_vec_t& ids) : LLInventoryFetchDescendentsObserver(ids) {} + ~LLRightClickInventoryFetchDescendentsObserver() {} + virtual void execute(bool clear_observer = false); + virtual void done() + { + execute(true); + } +}; + +// Used by LLFolderBridge as callback for directory content items fetching +class LLRightClickInventoryFetchObserver : public LLInventoryFetchItemsObserver +{ +public: + LLRightClickInventoryFetchObserver(const uuid_vec_t& ids) : LLInventoryFetchItemsObserver(ids) { }; + ~LLRightClickInventoryFetchObserver() {} + void execute(bool clear_observer = false) + { + if (clear_observer) + { + gInventory.removeObserver(this); + delete this; + } + // we've downloaded all the items, so repaint the dialog + LLFolderBridge::staticFolderOptionsMenu(); + } + virtual void done() + { + execute(true); + } +}; + +class LLPasteIntoFolderCallback: public LLInventoryCallback +{ +public: + LLPasteIntoFolderCallback(LLHandle& handle) + : mInventoryPanel(handle) + { + } + ~LLPasteIntoFolderCallback() + { + processItems(); + } + + void fire(const LLUUID& inv_item) + { + mChangedIds.push_back(inv_item); + } + + void processItems() + { + LLInventoryPanel* panel = mInventoryPanel.get(); + bool has_elements = false; + for (LLUUID& inv_item : mChangedIds) + { + LLInventoryItem* item = gInventory.getItem(inv_item); + if (item && panel) + { + LLUUID root_id = panel->getRootFolderID(); + + if (inv_item == root_id) + { + return; + } + + LLFolderViewItem* item = panel->getItemByID(inv_item); + if (item) + { + if (!has_elements) + { + panel->clearSelection(); + panel->getRootFolder()->clearSelection(); + panel->getRootFolder()->requestArrange(); + panel->getRootFolder()->update(); + has_elements = true; + } + panel->getRootFolder()->changeSelection(item, true); + } + } + } + + if (has_elements) + { + panel->getRootFolder()->scrollToShowSelection(); + } + } +private: + LLHandle mInventoryPanel; + std::vector mChangedIds; +}; + +// +=================================================+ +// | LLInvFVBridge | +// +=================================================+ + +LLInvFVBridge::LLInvFVBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid) : + mUUID(uuid), + mRoot(root), + mInvType(LLInventoryType::IT_NONE), + mIsLink(false), + LLFolderViewModelItemInventory(inventory->getRootViewModel()) +{ + mInventoryPanel = inventory->getInventoryPanelHandle(); + const LLInventoryObject* obj = getInventoryObject(); + mIsLink = obj && obj->getIsLinkType(); +} + +const std::string& LLInvFVBridge::getName() const +{ + const LLInventoryObject* obj = getInventoryObject(); + if(obj) + { + return obj->getName(); + } + return LLStringUtil::null; +} + +const std::string& LLInvFVBridge::getDisplayName() const +{ + if(mDisplayName.empty()) + { + buildDisplayName(); + } + return mDisplayName; +} + +std::string LLInvFVBridge::getSearchableDescription() const +{ + return get_searchable_description(getInventoryModel(), mUUID); +} + +std::string LLInvFVBridge::getSearchableCreatorName() const +{ + return get_searchable_creator_name(getInventoryModel(), mUUID); +} + +std::string LLInvFVBridge::getSearchableUUIDString() const +{ + return get_searchable_UUID(getInventoryModel(), mUUID); +} + +// Folders have full perms +PermissionMask LLInvFVBridge::getPermissionMask() const +{ + return PERM_ALL; +} + +// virtual +LLFolderType::EType LLInvFVBridge::getPreferredType() const +{ + return LLFolderType::FT_NONE; +} + + +// Folders don't have creation dates. +time_t LLInvFVBridge::getCreationDate() const +{ + LLInventoryObject* objectp = getInventoryObject(); + if (objectp) + { + return objectp->getCreationDate(); + } + return (time_t)0; +} + +void LLInvFVBridge::setCreationDate(time_t creation_date_utc) +{ + LLInventoryObject* objectp = getInventoryObject(); + if (objectp) + { + objectp->setCreationDate(creation_date_utc); + } +} + + +// Can be destroyed (or moved to trash) +bool LLInvFVBridge::isItemRemovable(bool check_worn) const +{ + return get_is_item_removable(getInventoryModel(), mUUID, check_worn); +} + +// Can be moved to another folder +bool LLInvFVBridge::isItemMovable() const +{ + return true; +} + +bool LLInvFVBridge::isLink() const +{ + return mIsLink; +} + +bool LLInvFVBridge::isLibraryItem() const +{ + return gInventory.isObjectDescendentOf(getUUID(),gInventory.getLibraryRootFolderID()); +} + +/*virtual*/ +/** + * @brief Adds this item into clipboard storage + */ +bool LLInvFVBridge::cutToClipboard() +{ + const LLInventoryObject* obj = gInventory.getObject(mUUID); + if (obj && isItemMovable() && isItemRemovable()) + { + const LLUUID &marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + const bool cut_from_marketplacelistings = gInventory.isObjectDescendentOf(mUUID, marketplacelistings_id); + + if (cut_from_marketplacelistings && (LLMarketplaceData::instance().isInActiveFolder(mUUID) || + LLMarketplaceData::instance().isListedAndActive(mUUID))) + { + LLUUID parent_uuid = obj->getParentUUID(); + bool result = perform_cutToClipboard(); + gInventory.addChangedMask(LLInventoryObserver::STRUCTURE, parent_uuid); + return result; + } + else + { + // Otherwise just perform the cut + return perform_cutToClipboard(); + } + } + return false; +} + +// virtual +bool LLInvFVBridge::isCutToClipboard() +{ + if (LLClipboard::instance().isCutMode()) + { + return LLClipboard::instance().isOnClipboard(mUUID); + } + return false; +} + +// Callback for cutToClipboard if DAMA required... +bool LLInvFVBridge::callback_cutToClipboard(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // YES + { + return perform_cutToClipboard(); + } + return false; +} + +bool LLInvFVBridge::perform_cutToClipboard() +{ + const LLInventoryObject* obj = gInventory.getObject(mUUID); + if (obj && isItemMovable() && isItemRemovable()) + { + LLClipboard::instance().setCutMode(true); + return LLClipboard::instance().addToClipboard(mUUID); + } + return false; +} + +bool LLInvFVBridge::copyToClipboard() const +{ + const LLInventoryObject* obj = gInventory.getObject(mUUID); + if (obj && isItemCopyable()) + { + return LLClipboard::instance().addToClipboard(mUUID); + } + return false; +} + +void LLInvFVBridge::showProperties() +{ + if (isMarketplaceListingsFolder()) + { + LLFloaterReg::showInstance("item_properties", LLSD().with("id",mUUID),true); + // Force it to show on top as this floater has a tendency to hide when confirmation dialog shows up + LLFloater* floater_properties = LLFloaterReg::findInstance("item_properties", LLSD().with("id",mUUID)); + if (floater_properties) + { + floater_properties->setVisibleAndFrontmost(); + } + } + else + { + show_item_profile(mUUID); + } +} + +void LLInvFVBridge::navigateToFolder(bool new_window, bool change_mode) +{ + if(new_window) + { + mInventoryPanel.get()->openSingleViewInventory(mUUID); + } + else + { + if(change_mode) + { + LLInventoryPanel::setSFViewAndOpenFolder(mInventoryPanel.get(), mUUID); + } + else + { + LLInventorySingleFolderPanel* panel = dynamic_cast(mInventoryPanel.get()); + if (!panel || !getInventoryModel() || mUUID.isNull()) + { + return; + } + + panel->changeFolderRoot(mUUID); + } + + } +} + +void LLInvFVBridge::removeBatch(std::vector& batch) +{ + // Deactivate gestures when moving them into Trash + LLInvFVBridge* bridge; + LLInventoryModel* model = getInventoryModel(); + LLViewerInventoryItem* item = NULL; + LLViewerInventoryCategory* cat = NULL; + LLInventoryModel::cat_array_t descendent_categories; + LLInventoryModel::item_array_t descendent_items; + S32 count = batch.size(); + S32 i,j; + for(i = 0; i < count; ++i) + { + bridge = (LLInvFVBridge*)(batch[i]); + if(!bridge || !bridge->isItemRemovable()) continue; + item = (LLViewerInventoryItem*)model->getItem(bridge->getUUID()); + if (item) + { + if(LLAssetType::AT_GESTURE == item->getType()) + { + LLGestureMgr::instance().deactivateGesture(item->getUUID()); + } + } + } + for(i = 0; i < count; ++i) + { + bridge = (LLInvFVBridge*)(batch[i]); + if(!bridge || !bridge->isItemRemovable()) continue; + cat = (LLViewerInventoryCategory*)model->getCategory(bridge->getUUID()); + if (cat) + { + gInventory.collectDescendents( cat->getUUID(), descendent_categories, descendent_items, false ); + for (j=0; jgetType()) + { + LLGestureMgr::instance().deactivateGesture(descendent_items[j]->getUUID()); + } + } + } + } + removeBatchNoCheck(batch); + model->checkTrashOverflow(); +} + +void LLInvFVBridge::removeBatchNoCheck(std::vector& batch) +{ + // this method moves a bunch of items and folders to the trash. As + // per design guidelines for the inventory model, the message is + // built and the accounting is performed first. After all of that, + // we call LLInventoryModel::moveObject() to move everything + // around. + LLInvFVBridge* bridge; + LLInventoryModel* model = getInventoryModel(); + if(!model) return; + LLMessageSystem* msg = gMessageSystem; + const LLUUID trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); + LLViewerInventoryItem* item = NULL; + uuid_vec_t move_ids; + LLInventoryModel::update_map_t update; + bool start_new_message = true; + S32 count = batch.size(); + S32 i; + + // first, hide any 'preview' floaters that correspond to the items + // being deleted. + for(i = 0; i < count; ++i) + { + bridge = (LLInvFVBridge*)(batch[i]); + if(!bridge || !bridge->isItemRemovable()) continue; + item = (LLViewerInventoryItem*)model->getItem(bridge->getUUID()); + if(item) + { + LLPreview::hide(item->getUUID()); + } + } + + // do the inventory move to trash + + for(i = 0; i < count; ++i) + { + bridge = (LLInvFVBridge*)(batch[i]); + if(!bridge || !bridge->isItemRemovable()) continue; + item = (LLViewerInventoryItem*)model->getItem(bridge->getUUID()); + if(item) + { + if(item->getParentUUID() == trash_id) continue; + move_ids.push_back(item->getUUID()); + --update[item->getParentUUID()]; + ++update[trash_id]; + if(start_new_message) + { + start_new_message = false; + msg->newMessageFast(_PREHASH_MoveInventoryItem); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addBOOLFast(_PREHASH_Stamp, true); + } + msg->nextBlockFast(_PREHASH_InventoryData); + msg->addUUIDFast(_PREHASH_ItemID, item->getUUID()); + msg->addUUIDFast(_PREHASH_FolderID, trash_id); + msg->addString("NewName", NULL); + if(msg->isSendFullFast(_PREHASH_InventoryData)) + { + start_new_message = true; + gAgent.sendReliableMessage(); + gInventory.accountForUpdate(update); + update.clear(); + } + } + } + if(!start_new_message) + { + start_new_message = true; + gAgent.sendReliableMessage(); + gInventory.accountForUpdate(update); + update.clear(); + } + + for(i = 0; i < count; ++i) + { + bridge = (LLInvFVBridge*)(batch[i]); + if(!bridge || !bridge->isItemRemovable()) continue; + LLViewerInventoryCategory* cat = (LLViewerInventoryCategory*)model->getCategory(bridge->getUUID()); + if(cat) + { + if(cat->getParentUUID() == trash_id) continue; + move_ids.push_back(cat->getUUID()); + --update[cat->getParentUUID()]; + ++update[trash_id]; + if(start_new_message) + { + start_new_message = false; + msg->newMessageFast(_PREHASH_MoveInventoryFolder); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addBOOL("Stamp", true); + } + msg->nextBlockFast(_PREHASH_InventoryData); + msg->addUUIDFast(_PREHASH_FolderID, cat->getUUID()); + msg->addUUIDFast(_PREHASH_ParentID, trash_id); + if(msg->isSendFullFast(_PREHASH_InventoryData)) + { + start_new_message = true; + gAgent.sendReliableMessage(); + gInventory.accountForUpdate(update); + update.clear(); + } + } + } + if(!start_new_message) + { + gAgent.sendReliableMessage(); + gInventory.accountForUpdate(update); + } + + // move everything. + uuid_vec_t::iterator it = move_ids.begin(); + uuid_vec_t::iterator end = move_ids.end(); + for(; it != end; ++it) + { + gInventory.moveObject((*it), trash_id); + LLViewerInventoryItem* item = gInventory.getItem(*it); + if (item) + { + model->updateItem(item); + } + } + + // notify inventory observers. + model->notifyObservers(); +} + +bool LLInvFVBridge::isClipboardPasteable() const +{ + // Return false on degenerated cases: empty clipboard, no inventory, no agent + if (!LLClipboard::instance().hasContents() || !isAgentInventory()) + { + return false; + } + LLInventoryModel* model = getInventoryModel(); + if (!model) + { + return false; + } + + // In cut mode, whatever is on the clipboard is always pastable + if (LLClipboard::instance().isCutMode()) + { + return true; + } + + // In normal mode, we need to check each element of the clipboard to know if we can paste or not + std::vector objects; + LLClipboard::instance().pasteFromClipboard(objects); + S32 count = objects.size(); + for(S32 i = 0; i < count; i++) + { + const LLUUID &item_id = objects.at(i); + + // Folders are pastable if all items in there are copyable + const LLInventoryCategory *cat = model->getCategory(item_id); + if (cat) + { + LLFolderBridge cat_br(mInventoryPanel.get(), mRoot, item_id); + if (!cat_br.isItemCopyable(false)) + return false; + // Skip to the next item in the clipboard + continue; + } + + // Each item must be copyable to be pastable + LLItemBridge item_br(mInventoryPanel.get(), mRoot, item_id); + if (!item_br.isItemCopyable(false)) + { + return false; + } + } + return true; +} + +bool LLInvFVBridge::isClipboardPasteableAsLink() const +{ + if (!LLClipboard::instance().hasContents() || !isAgentInventory()) + { + return false; + } + const LLInventoryModel* model = getInventoryModel(); + if (!model) + { + return false; + } + + std::vector objects; + LLClipboard::instance().pasteFromClipboard(objects); + S32 count = objects.size(); + for(S32 i = 0; i < count; i++) + { + const LLInventoryItem *item = model->getItem(objects.at(i)); + if (item) + { + if (!LLAssetType::lookupCanLink(item->getActualType())) + { + return false; + } + + if (gInventory.isObjectDescendentOf(item->getUUID(), gInventory.getLibraryRootFolderID())) + { + return false; + } + } + const LLViewerInventoryCategory *cat = model->getCategory(objects.at(i)); + if (cat && LLFolderType::lookupIsProtectedType(cat->getPreferredType())) + { + return false; + } + } + return true; +} + +void disable_context_entries_if_present(LLMenuGL& menu, + const menuentry_vec_t &disabled_entries) +{ + const LLView::child_list_t *list = menu.getChildList(); + for (LLView::child_list_t::const_iterator itor = list->begin(); + itor != list->end(); + ++itor) + { + LLView *menu_item = (*itor); + std::string name = menu_item->getName(); + + // descend into split menus: + LLMenuItemBranchGL* branchp = dynamic_cast(menu_item); + if ((name == "More") && branchp) + { + disable_context_entries_if_present(*branchp->getBranch(), disabled_entries); + } + + bool found = false; + menuentry_vec_t::const_iterator itor2; + for (itor2 = disabled_entries.begin(); itor2 != disabled_entries.end(); ++itor2) + { + if (*itor2 == name) + { + found = true; + break; + } + } + + if (found) + { + menu_item->setVisible(true); + // A bit of a hack so we can remember that some UI element explicitly set this to be visible + // so that some other UI element from multi-select doesn't later set this invisible. + menu_item->pushVisible(true); + + menu_item->setEnabled(false); + } + } +} +void hide_context_entries(LLMenuGL& menu, + const menuentry_vec_t &entries_to_show, + const menuentry_vec_t &disabled_entries) +{ + const LLView::child_list_t *list = menu.getChildList(); + + // For removing double separators or leading separator. Start at true so that + // if the first element is a separator, it will not be shown. + bool is_previous_entry_separator = true; + + for (LLView::child_list_t::const_iterator itor = list->begin(); + itor != list->end(); + ++itor) + { + LLView *menu_item = (*itor); + std::string name = menu_item->getName(); + + // descend into split menus: + LLMenuItemBranchGL* branchp = dynamic_cast(menu_item); + if (((name == "More") || (name == "create_new")) && branchp) + { + hide_context_entries(*branchp->getBranch(), entries_to_show, disabled_entries); + } + + bool found = false; + + menuentry_vec_t::const_iterator itor2 = std::find(entries_to_show.begin(), entries_to_show.end(), name); + if (itor2 != entries_to_show.end()) + { + found = true; + } + + // Don't allow multiple separators in a row (e.g. such as if there are no items + // between two separators). + if (found) + { + const bool is_entry_separator = (dynamic_cast(menu_item) != NULL); + found = !(is_entry_separator && is_previous_entry_separator); + is_previous_entry_separator = is_entry_separator; + } + + if (!found) + { + if (!menu_item->getLastVisible()) + { + menu_item->setVisible(false); + } + + if (menu_item->getEnabled()) + { + // These should stay enabled unless specifically disabled + const menuentry_vec_t exceptions = { + "Detach From Yourself", + "Wearable And Object Wear", + "Wearable Add", + }; + + menuentry_vec_t::const_iterator itor2 = std::find(exceptions.begin(), exceptions.end(), name); + if (itor2 == exceptions.end()) + { + menu_item->setEnabled(false); + } + } + } + else + { + menu_item->setVisible(true); + // A bit of a hack so we can remember that some UI element explicitly set this to be visible + // so that some other UI element from multi-select doesn't later set this invisible. + menu_item->pushVisible(true); + + bool enabled = true; + for (itor2 = disabled_entries.begin(); enabled && (itor2 != disabled_entries.end()); ++itor2) + { + enabled &= (*itor2 != name); + } + + menu_item->setEnabled(enabled); + } + } +} + +// Helper for commonly-used entries +void LLInvFVBridge::getClipboardEntries(bool show_asset_id, + menuentry_vec_t &items, + menuentry_vec_t &disabled_items, U32 flags) +{ + const LLInventoryObject *obj = getInventoryObject(); + bool single_folder_root = (mRoot == NULL); + + if (obj) + { + + if (obj->getType() != LLAssetType::AT_CATEGORY) + { + items.push_back(std::string("Copy Separator")); + } + items.push_back(std::string("Copy")); + if (!isItemCopyable()) + { + disabled_items.push_back(std::string("Copy")); + } + + if (isAgentInventory() && !single_folder_root) + { + items.push_back(std::string("New folder from selected")); + items.push_back(std::string("Subfolder Separator")); + std::set selected_uuid_set = LLAvatarActions::getInventorySelectedUUIDs(); + uuid_vec_t ids; + std::copy(selected_uuid_set.begin(), selected_uuid_set.end(), std::back_inserter(ids)); + if (!is_only_items_selected(ids) && !is_only_cats_selected(ids)) + { + disabled_items.push_back(std::string("New folder from selected")); + } + } + + if (obj->getIsLinkType()) + { + items.push_back(std::string("Find Original")); + if (isLinkedObjectMissing()) + { + disabled_items.push_back(std::string("Find Original")); + } + + items.push_back(std::string("Cut")); + if (!isItemMovable() || !canMenuCut()) + { + disabled_items.push_back(std::string("Cut")); + } + } + else + { + if (LLAssetType::lookupCanLink(obj->getType())) + { + items.push_back(std::string("Find Links")); + } + + if (!isInboxFolder() && !single_folder_root) + { + items.push_back(std::string("Rename")); + if (!isItemRenameable() || ((flags & FIRST_SELECTED_ITEM) == 0)) + { + disabled_items.push_back(std::string("Rename")); + } + } + + items.push_back(std::string("thumbnail")); + if (isLibraryItem()) + { + disabled_items.push_back(std::string("thumbnail")); + } + + LLViewerInventoryItem *inv_item = gInventory.getItem(mUUID); + if (show_asset_id) + { + items.push_back(std::string("Copy Asset UUID")); + + bool is_asset_knowable = false; + + if (inv_item) + { + is_asset_knowable = LLAssetType::lookupIsAssetIDKnowable(inv_item->getType()); + } + if ( !is_asset_knowable // disable menu item for Inventory items with unknown asset. EXT-5308 + || (! ( isItemPermissive() || gAgent.isGodlike() ) ) + || (flags & FIRST_SELECTED_ITEM) == 0) + { + disabled_items.push_back(std::string("Copy Asset UUID")); + } + } + + if(!single_folder_root) + { + items.push_back(std::string("Cut")); + if (!isItemMovable() || !canMenuCut()) + { + disabled_items.push_back(std::string("Cut")); + } + + if (canListOnMarketplace() && !isMarketplaceListingsFolder() && !isInboxFolder()) + { + items.push_back(std::string("Marketplace Separator")); + + if (gMenuHolder->getChild("MarketplaceListings")->getVisible()) + { + items.push_back(std::string("Marketplace Copy")); + items.push_back(std::string("Marketplace Move")); + if (!canListOnMarketplaceNow()) + { + disabled_items.push_back(std::string("Marketplace Copy")); + disabled_items.push_back(std::string("Marketplace Move")); + } + } + } + } + } + } + + // Don't allow items to be pasted directly into the COF or the inbox + if (!isCOFFolder() && !isInboxFolder()) + { + items.push_back(std::string("Paste")); + } + if (!isClipboardPasteable() || ((flags & FIRST_SELECTED_ITEM) == 0)) + { + disabled_items.push_back(std::string("Paste")); + } + + static LLCachedControl inventory_linking(gSavedSettings, "InventoryLinking", true); + if (inventory_linking) + { + items.push_back(std::string("Paste As Link")); + if (!isClipboardPasteableAsLink() || (flags & FIRST_SELECTED_ITEM) == 0) + { + disabled_items.push_back(std::string("Paste As Link")); + } + } + + if (obj->getType() != LLAssetType::AT_CATEGORY) + { + items.push_back(std::string("Paste Separator")); + } + + if(!single_folder_root) + { + addDeleteContextMenuOptions(items, disabled_items); + } + + if (!isPanelActive("All Items") && !isPanelActive("comb_single_folder_inv")) + { + items.push_back(std::string("Show in Main Panel")); + } +} + +void LLInvFVBridge::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + LL_DEBUGS() << "LLInvFVBridge::buildContextMenu()" << LL_ENDL; + menuentry_vec_t items; + menuentry_vec_t disabled_items; + if(isItemInTrash()) + { + addTrashContextMenuOptions(items, disabled_items); + } + else + { + items.push_back(std::string("Share")); + if (!canShare()) + { + disabled_items.push_back(std::string("Share")); + } + + addOpenRightClickMenuOption(items); + items.push_back(std::string("Properties")); + + getClipboardEntries(true, items, disabled_items, flags); + } + addLinkReplaceMenuOption(items, disabled_items); + hide_context_entries(menu, items, disabled_items); +} + +bool get_selection_item_uuids(LLFolderView::selected_items_t& selected_items, uuid_vec_t& ids) +{ + uuid_vec_t results; + S32 non_item = 0; + for(LLFolderView::selected_items_t::iterator it = selected_items.begin(); it != selected_items.end(); ++it) + { + LLItemBridge *view_model = dynamic_cast((*it)->getViewModelItem()); + + if(view_model && view_model->getUUID().notNull()) + { + results.push_back(view_model->getUUID()); + } + else + { + non_item++; + } + } + if (non_item == 0) + { + ids = results; + return true; + } + return false; +} + +void LLInvFVBridge::addTrashContextMenuOptions(menuentry_vec_t &items, + menuentry_vec_t &disabled_items) +{ + const LLInventoryObject *obj = getInventoryObject(); + if (obj && obj->getIsLinkType()) + { + items.push_back(std::string("Find Original")); + if (isLinkedObjectMissing()) + { + disabled_items.push_back(std::string("Find Original")); + } + } + items.push_back(std::string("Purge Item")); + if (!isItemRemovable()) + { + disabled_items.push_back(std::string("Purge Item")); + } + items.push_back(std::string("Restore Item")); +} + +void LLInvFVBridge::addDeleteContextMenuOptions(menuentry_vec_t &items, + menuentry_vec_t &disabled_items) +{ + + const LLInventoryObject *obj = getInventoryObject(); + + // Don't allow delete as a direct option from COF folder. + if (obj && obj->getIsLinkType() && isCOFFolder() && get_is_item_worn(mUUID)) + { + return; + } + + items.push_back(std::string("Delete")); + + if (isPanelActive("Favorite Items") || !canMenuDelete()) + { + disabled_items.push_back(std::string("Delete")); + } +} + +void LLInvFVBridge::addOpenRightClickMenuOption(menuentry_vec_t &items) +{ + const LLInventoryObject *obj = getInventoryObject(); + const bool is_link = (obj && obj->getIsLinkType()); + + if (is_link) + items.push_back(std::string("Open Original")); + else + items.push_back(std::string("Open")); +} + +void LLInvFVBridge::addMarketplaceContextMenuOptions(U32 flags, + menuentry_vec_t &items, + menuentry_vec_t &disabled_items) +{ + S32 depth = depth_nesting_in_marketplace(mUUID); + if (depth == 1) + { + // Options available at the Listing Folder level + items.push_back(std::string("Marketplace Create Listing")); + items.push_back(std::string("Marketplace Associate Listing")); + items.push_back(std::string("Marketplace Check Listing")); + items.push_back(std::string("Marketplace List")); + items.push_back(std::string("Marketplace Unlist")); + if (LLMarketplaceData::instance().isUpdating(mUUID,depth) || ((flags & FIRST_SELECTED_ITEM) == 0)) + { + // During SLM update, disable all marketplace related options + // Also disable all if multiple selected items + disabled_items.push_back(std::string("Marketplace Create Listing")); + disabled_items.push_back(std::string("Marketplace Associate Listing")); + disabled_items.push_back(std::string("Marketplace Check Listing")); + disabled_items.push_back(std::string("Marketplace List")); + disabled_items.push_back(std::string("Marketplace Unlist")); + } + else + { + if (gSavedSettings.getBOOL("MarketplaceListingsLogging")) + { + items.push_back(std::string("Marketplace Get Listing")); + } + if (LLMarketplaceData::instance().isListed(mUUID)) + { + disabled_items.push_back(std::string("Marketplace Create Listing")); + disabled_items.push_back(std::string("Marketplace Associate Listing")); + if (LLMarketplaceData::instance().getVersionFolder(mUUID).isNull()) + { + disabled_items.push_back(std::string("Marketplace List")); + disabled_items.push_back(std::string("Marketplace Unlist")); + } + else + { + if (LLMarketplaceData::instance().getActivationState(mUUID)) + { + disabled_items.push_back(std::string("Marketplace List")); + } + else + { + disabled_items.push_back(std::string("Marketplace Unlist")); + } + } + } + else + { + disabled_items.push_back(std::string("Marketplace List")); + disabled_items.push_back(std::string("Marketplace Unlist")); + if (gSavedSettings.getBOOL("MarketplaceListingsLogging")) + { + disabled_items.push_back(std::string("Marketplace Get Listing")); + } + } + } + } + if (depth == 2) + { + // Options available at the Version Folder levels and only for folders + LLInventoryCategory* cat = gInventory.getCategory(mUUID); + if (cat && LLMarketplaceData::instance().isListed(cat->getParentUUID())) + { + items.push_back(std::string("Marketplace Activate")); + items.push_back(std::string("Marketplace Deactivate")); + if (LLMarketplaceData::instance().isUpdating(mUUID,depth) || ((flags & FIRST_SELECTED_ITEM) == 0)) + { + // During SLM update, disable all marketplace related options + // Also disable all if multiple selected items + disabled_items.push_back(std::string("Marketplace Activate")); + disabled_items.push_back(std::string("Marketplace Deactivate")); + } + else + { + if (LLMarketplaceData::instance().isVersionFolder(mUUID)) + { + disabled_items.push_back(std::string("Marketplace Activate")); + if (LLMarketplaceData::instance().getActivationState(mUUID)) + { + disabled_items.push_back(std::string("Marketplace Deactivate")); + } + } + else + { + disabled_items.push_back(std::string("Marketplace Deactivate")); + } + } + } + } + + items.push_back(std::string("Marketplace Edit Listing")); + LLUUID listing_folder_id = nested_parent_id(mUUID,depth); + LLUUID version_folder_id = LLMarketplaceData::instance().getVersionFolder(listing_folder_id); + + if (depth >= 2) + { + // Prevent creation of new folders if the max count has been reached on this version folder (active or not) + LLUUID local_version_folder_id = nested_parent_id(mUUID,depth-1); + LLInventoryModel::cat_array_t categories; + LLInventoryModel::item_array_t items; + gInventory.collectDescendents(local_version_folder_id, categories, items, false); + static LLCachedControl max_depth(gSavedSettings, "InventoryOutboxMaxFolderDepth", 4); + static LLCachedControl max_count(gSavedSettings, "InventoryOutboxMaxFolderCount", 20); + if (categories.size() >= max_count + || depth > (max_depth + 1)) + { + disabled_items.push_back(std::string("New Folder")); + } + } + + // Options available at all levels on items and categories + if (!LLMarketplaceData::instance().isListed(listing_folder_id) || version_folder_id.isNull()) + { + disabled_items.push_back(std::string("Marketplace Edit Listing")); + } + + // Separator + items.push_back(std::string("Marketplace Listings Separator")); +} + +void LLInvFVBridge::addLinkReplaceMenuOption(menuentry_vec_t& items, menuentry_vec_t& disabled_items) +{ + const LLInventoryObject* obj = getInventoryObject(); + + if (isAgentInventory() && obj && obj->getType() != LLAssetType::AT_CATEGORY && obj->getType() != LLAssetType::AT_LINK_FOLDER) + { + items.push_back(std::string("Replace Links")); + + if (mRoot->getSelectedCount() != 1) + { + disabled_items.push_back(std::string("Replace Links")); + } + } +} + +bool LLInvFVBridge::canMenuDelete() +{ + return isItemRemovable(false); +} + +bool LLInvFVBridge::canMenuCut() +{ + return isItemRemovable(true); +} + +// *TODO: remove this +bool LLInvFVBridge::startDrag(EDragAndDropType* type, LLUUID* id) const +{ + bool rv = false; + + const LLInventoryObject* obj = getInventoryObject(); + + if(obj) + { + *type = LLViewerAssetType::lookupDragAndDropType(obj->getActualType()); + if(*type == DAD_NONE) + { + return false; + } + + *id = obj->getUUID(); + //object_ids.push_back(obj->getUUID()); + + if (*type == DAD_CATEGORY) + { + LLInventoryModelBackgroundFetch::instance().start(obj->getUUID()); + } + + rv = true; + } + + return rv; +} + +LLInventoryObject* LLInvFVBridge::getInventoryObject() const +{ + LLInventoryObject* obj = NULL; + LLInventoryModel* model = getInventoryModel(); + if(model) + { + obj = (LLInventoryObject*)model->getObject(mUUID); + } + return obj; +} + +LLInventoryModel* LLInvFVBridge::getInventoryModel() const +{ + LLInventoryPanel* panel = mInventoryPanel.get(); + return panel ? panel->getModel() : NULL; +} + +LLInventoryFilter* LLInvFVBridge::getInventoryFilter() const +{ + LLInventoryPanel* panel = mInventoryPanel.get(); + return panel ? &(panel->getFilter()) : NULL; +} + +bool LLInvFVBridge::isItemInTrash() const +{ + LLInventoryModel* model = getInventoryModel(); + if(!model) return false; + const LLUUID trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); + return model->isObjectDescendentOf(mUUID, trash_id); +} + +bool LLInvFVBridge::isLinkedObjectInTrash() const +{ + if (isItemInTrash()) return true; + + const LLInventoryObject *obj = getInventoryObject(); + if (obj && obj->getIsLinkType()) + { + LLInventoryModel* model = getInventoryModel(); + if(!model) return false; + const LLUUID trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); + return model->isObjectDescendentOf(obj->getLinkedUUID(), trash_id); + } + return false; +} + +bool LLInvFVBridge::isItemInOutfits() const +{ + const LLInventoryModel* model = getInventoryModel(); + if(!model) return false; + + const LLUUID my_outfits_cat = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + + return isCOFFolder() || (my_outfits_cat == mUUID) || model->isObjectDescendentOf(mUUID, my_outfits_cat); +} + +bool LLInvFVBridge::isLinkedObjectMissing() const +{ + const LLInventoryObject *obj = getInventoryObject(); + if (!obj) + { + return true; + } + if (obj->getIsLinkType() && LLAssetType::lookupIsLinkType(obj->getType())) + { + return true; + } + return false; +} + +bool LLInvFVBridge::isAgentInventory() const +{ + const LLInventoryModel* model = getInventoryModel(); + if(!model) return false; + if(gInventory.getRootFolderID() == mUUID) return true; + return model->isObjectDescendentOf(mUUID, gInventory.getRootFolderID()); +} + +bool LLInvFVBridge::isCOFFolder() const +{ + return LLAppearanceMgr::instance().getIsInCOF(mUUID); +} + +// *TODO : Suppress isInboxFolder() once Merchant Outbox is fully deprecated +bool LLInvFVBridge::isInboxFolder() const +{ + const LLUUID inbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX); + + if (inbox_id.isNull()) + { + return false; + } + + return gInventory.isObjectDescendentOf(mUUID, inbox_id); +} + +bool LLInvFVBridge::isMarketplaceListingsFolder() const +{ + const LLUUID folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + + if (folder_id.isNull()) + { + return false; + } + + return gInventory.isObjectDescendentOf(mUUID, folder_id); +} + +bool LLInvFVBridge::isItemPermissive() const +{ + return false; +} + +// static +void LLInvFVBridge::changeItemParent(LLInventoryModel* model, + LLViewerInventoryItem* item, + const LLUUID& new_parent_id, + bool restamp) +{ + model->changeItemParent(item, new_parent_id, restamp); +} + +// static +void LLInvFVBridge::changeCategoryParent(LLInventoryModel* model, + LLViewerInventoryCategory* cat, + const LLUUID& new_parent_id, + bool restamp) +{ + model->changeCategoryParent(cat, new_parent_id, restamp); +} + +LLInvFVBridge* LLInvFVBridge::createBridge(LLAssetType::EType asset_type, + LLAssetType::EType actual_asset_type, + LLInventoryType::EType inv_type, + LLInventoryPanel* inventory, + LLFolderViewModelInventory* view_model, + LLFolderView* root, + const LLUUID& uuid, + U32 flags) +{ + LLInvFVBridge* new_listener = NULL; + switch(asset_type) + { + case LLAssetType::AT_TEXTURE: + if(!(inv_type == LLInventoryType::IT_TEXTURE || inv_type == LLInventoryType::IT_SNAPSHOT)) + { + LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; + } + new_listener = new LLTextureBridge(inventory, root, uuid, inv_type); + break; + + case LLAssetType::AT_SOUND: + if(!(inv_type == LLInventoryType::IT_SOUND)) + { + LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; + } + new_listener = new LLSoundBridge(inventory, root, uuid); + break; + + case LLAssetType::AT_LANDMARK: + if(!(inv_type == LLInventoryType::IT_LANDMARK)) + { + LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; + } + new_listener = new LLLandmarkBridge(inventory, root, uuid, flags); + break; + + case LLAssetType::AT_CALLINGCARD: + if(!(inv_type == LLInventoryType::IT_CALLINGCARD)) + { + LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; + } + new_listener = new LLCallingCardBridge(inventory, root, uuid); + break; + + case LLAssetType::AT_SCRIPT: + if(!(inv_type == LLInventoryType::IT_LSL)) + { + LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; + } + new_listener = new LLItemBridge(inventory, root, uuid); + break; + + case LLAssetType::AT_OBJECT: + if(!(inv_type == LLInventoryType::IT_OBJECT || inv_type == LLInventoryType::IT_ATTACHMENT)) + { + LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; + } + new_listener = new LLObjectBridge(inventory, root, uuid, inv_type, flags); + break; + + case LLAssetType::AT_NOTECARD: + if(!(inv_type == LLInventoryType::IT_NOTECARD)) + { + LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; + } + new_listener = new LLNotecardBridge(inventory, root, uuid); + break; + + case LLAssetType::AT_ANIMATION: + if(!(inv_type == LLInventoryType::IT_ANIMATION)) + { + LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; + } + new_listener = new LLAnimationBridge(inventory, root, uuid); + break; + + case LLAssetType::AT_GESTURE: + if(!(inv_type == LLInventoryType::IT_GESTURE)) + { + LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; + } + new_listener = new LLGestureBridge(inventory, root, uuid); + break; + + case LLAssetType::AT_LSL_TEXT: + if(!(inv_type == LLInventoryType::IT_LSL)) + { + LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; + } + new_listener = new LLLSLTextBridge(inventory, root, uuid); + break; + + case LLAssetType::AT_CLOTHING: + case LLAssetType::AT_BODYPART: + if(!(inv_type == LLInventoryType::IT_WEARABLE)) + { + LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; + } + new_listener = new LLWearableBridge(inventory, root, uuid, asset_type, inv_type, LLWearableType::inventoryFlagsToWearableType(flags)); + break; + case LLAssetType::AT_CATEGORY: + if (actual_asset_type == LLAssetType::AT_LINK_FOLDER) + { + // Create a link folder handler instead + new_listener = new LLLinkFolderBridge(inventory, root, uuid); + } + else if (actual_asset_type == LLAssetType::AT_MARKETPLACE_FOLDER) + { + // Create a marketplace folder handler + new_listener = new LLMarketplaceFolderBridge(inventory, root, uuid); + } + else + { + new_listener = new LLFolderBridge(inventory, root, uuid); + } + break; + case LLAssetType::AT_LINK: + case LLAssetType::AT_LINK_FOLDER: + // Only should happen for broken links. + new_listener = new LLLinkItemBridge(inventory, root, uuid); + break; + case LLAssetType::AT_UNKNOWN: + new_listener = new LLUnknownItemBridge(inventory, root, uuid); + break; + case LLAssetType::AT_IMAGE_TGA: + case LLAssetType::AT_IMAGE_JPEG: + //LL_WARNS() << LLAssetType::lookup(asset_type) << " asset type is unhandled for uuid " << uuid << LL_ENDL; + break; + + case LLAssetType::AT_SETTINGS: + if (inv_type != LLInventoryType::IT_SETTINGS) + { + LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; + } + new_listener = new LLSettingsBridge(inventory, root, uuid, LLSettingsType::fromInventoryFlags(flags)); + break; + + case LLAssetType::AT_MATERIAL: + if (inv_type != LLInventoryType::IT_MATERIAL) + { + LL_WARNS() << LLAssetType::lookup(asset_type) << " asset has inventory type " << LLInventoryType::lookupHumanReadable(inv_type) << " on uuid " << uuid << LL_ENDL; + } + new_listener = new LLMaterialBridge(inventory, root, uuid); + break; + + default: + LL_INFOS_ONCE() << "Unhandled asset type (llassetstorage.h): " + << (S32)asset_type << " (" << LLAssetType::lookup(asset_type) << ")" << LL_ENDL; + break; + } + + if (new_listener) + { + new_listener->mInvType = inv_type; + } + + return new_listener; +} + +void LLInvFVBridge::purgeItem(LLInventoryModel *model, const LLUUID &uuid) +{ + LLInventoryObject* obj = model->getObject(uuid); + if (obj) + { + remove_inventory_object(uuid, NULL); + } +} + +void LLInvFVBridge::removeObject(LLInventoryModel *model, const LLUUID &uuid) +{ + // Keep track of the parent + LLInventoryItem* itemp = model->getItem(uuid); + LLUUID parent_id = (itemp ? itemp->getParentUUID() : LLUUID::null); + // Remove the object + model->removeObject(uuid); + // Get the parent updated + if (parent_id.notNull()) + { + LLViewerInventoryCategory* parent_cat = model->getCategory(parent_id); + model->updateCategory(parent_cat); + model->notifyObservers(); + } +} + +bool LLInvFVBridge::canShare() const +{ + bool can_share = false; + + if (isAgentInventory()) + { + const LLInventoryModel* model = getInventoryModel(); + if (model) + { + const LLViewerInventoryItem *item = model->getItem(mUUID); + if (item) + { + if (LLInventoryCollectFunctor::itemTransferCommonlyAllowed(item)) + { + can_share = LLGiveInventory::isInventoryGiveAcceptable(item); + } + } + else + { + // Categories can be given. + can_share = (model->getCategory(mUUID) != NULL); + } + + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + if ((mUUID == trash_id) || gInventory.isObjectDescendentOf(mUUID, trash_id)) + { + can_share = false; + } + } + } + + return can_share; +} + +bool LLInvFVBridge::canListOnMarketplace() const +{ + LLInventoryModel * model = getInventoryModel(); + + LLViewerInventoryCategory * cat = model->getCategory(mUUID); + if (cat && LLFolderType::lookupIsProtectedType(cat->getPreferredType())) + { + return false; + } + + if (!isAgentInventory()) + { + return false; + } + + LLViewerInventoryItem * item = model->getItem(mUUID); + if (item) + { + if (!item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID())) + { + return false; + } + + if (LLAssetType::AT_CALLINGCARD == item->getType()) + { + return false; + } + } + + return true; +} + +bool LLInvFVBridge::canListOnMarketplaceNow() const +{ + bool can_list = true; + + const LLInventoryObject* obj = getInventoryObject(); + can_list &= (obj != NULL); + + if (can_list) + { + const LLUUID& object_id = obj->getLinkedUUID(); + can_list = object_id.notNull(); + + if (can_list) + { + LLFolderViewFolder * object_folderp = mInventoryPanel.get() ? mInventoryPanel.get()->getFolderByID(object_id) : NULL; + if (object_folderp) + { + can_list = !static_cast(object_folderp->getViewModelItem())->isLoading(); + } + } + + if (can_list) + { + std::string error_msg; + LLInventoryModel* model = getInventoryModel(); + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + if (marketplacelistings_id.notNull()) + { + LLViewerInventoryCategory * master_folder = model->getCategory(marketplacelistings_id); + LLInventoryCategory *cat = model->getCategory(mUUID); + if (cat) + { + can_list = can_move_folder_to_marketplace(master_folder, master_folder, cat, error_msg); + } + else + { + LLInventoryItem *item = model->getItem(mUUID); + can_list = (item ? can_move_item_to_marketplace(master_folder, master_folder, item, error_msg) : false); + } + } + else + { + can_list = false; + } + } + } + + return can_list; +} + +LLToolDragAndDrop::ESource LLInvFVBridge::getDragSource() const +{ + if (gInventory.isObjectDescendentOf(getUUID(), gInventory.getRootFolderID())) + { + return LLToolDragAndDrop::SOURCE_AGENT; + } + else if (gInventory.isObjectDescendentOf(getUUID(), gInventory.getLibraryRootFolderID())) + { + return LLToolDragAndDrop::SOURCE_LIBRARY; + } + + return LLToolDragAndDrop::SOURCE_VIEWER; +} + + + +// +=================================================+ +// | InventoryFVBridgeBuilder | +// +=================================================+ +LLInvFVBridge* LLInventoryFolderViewModelBuilder::createBridge(LLAssetType::EType asset_type, + LLAssetType::EType actual_asset_type, + LLInventoryType::EType inv_type, + LLInventoryPanel* inventory, + LLFolderViewModelInventory* view_model, + LLFolderView* root, + const LLUUID& uuid, + U32 flags /* = 0x00 */) const +{ + return LLInvFVBridge::createBridge(asset_type, + actual_asset_type, + inv_type, + inventory, + view_model, + root, + uuid, + flags); +} + +// +=================================================+ +// | LLItemBridge | +// +=================================================+ + +void LLItemBridge::performAction(LLInventoryModel* model, std::string action) +{ + if ("goto" == action) + { + gotoItem(); + } + + if ("open" == action || "open_original" == action) + { + openItem(); + return; + } + else if ("properties" == action) + { + showProperties(); + return; + } + else if ("purge" == action) + { + purgeItem(model, mUUID); + return; + } + else if ("restoreToWorld" == action) + { + restoreToWorld(); + return; + } + else if ("restore" == action) + { + restoreItem(); + return; + } + else if ("thumbnail" == action) + { + LLSD data(mUUID); + LLFloaterReg::showInstance("change_item_thumbnail", data); + return; + } + else if ("copy_uuid" == action) + { + // Single item only + LLViewerInventoryItem* item = static_cast(getItem()); + if(!item) return; + LLUUID asset_id = item->getProtectedAssetUUID(); + std::string buffer; + asset_id.toString(buffer); + + gViewerWindow->getWindow()->copyTextToClipboard(utf8str_to_wstring(buffer)); + return; + } + else if ("show_in_main_panel" == action) + { + LLInventoryPanel::openInventoryPanelAndSetSelection(true, mUUID, true); + return; + } + else if ("cut" == action) + { + cutToClipboard(); + return; + } + else if ("copy" == action) + { + copyToClipboard(); + return; + } + else if ("paste" == action) + { + LLInventoryItem* itemp = model->getItem(mUUID); + if (!itemp) return; + + LLFolderViewItem* folder_view_itemp = mInventoryPanel.get()->getItemByID(itemp->getParentUUID()); + if (!folder_view_itemp) return; + + folder_view_itemp->getViewModelItem()->pasteFromClipboard(); + return; + } + else if ("paste_link" == action) + { + // Single item only + LLInventoryItem* itemp = model->getItem(mUUID); + if (!itemp) return; + + LLFolderViewItem* folder_view_itemp = mInventoryPanel.get()->getItemByID(itemp->getParentUUID()); + if (!folder_view_itemp) return; + + folder_view_itemp->getViewModelItem()->pasteLinkFromClipboard(); + return; + } + else if (("move_to_marketplace_listings" == action) || ("copy_to_marketplace_listings" == action) || ("copy_or_move_to_marketplace_listings" == action)) + { + LLInventoryItem* itemp = model->getItem(mUUID); + if (!itemp) return; + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + // Note: For a single item, if it's not a copy, then it's a move + move_item_to_marketplacelistings(itemp, marketplacelistings_id, ("copy_to_marketplace_listings" == action)); + } + else if ("copy_slurl" == action) + { + LLViewerInventoryItem* item = static_cast(getItem()); + if(item) + { + LLUUID asset_id = item->getAssetUUID(); + LLLandmark* landmark = gLandmarkList.getAsset(asset_id); + if (landmark) + { + LLVector3d global_pos; + landmark->getGlobalPos(global_pos); + LLLandmarkActions::getSLURLfromPosGlobal(global_pos, ©_slurl_to_clipboard_callback_inv, true); + } + } + } + else if ("show_on_map" == action) + { + doActionOnCurSelectedLandmark(boost::bind(&LLItemBridge::doShowOnMap, this, _1)); + } + else if ("marketplace_edit_listing" == action) + { + std::string url = LLMarketplaceData::instance().getListingURL(mUUID); + LLUrlAction::openURL(url); + } +} + +void LLItemBridge::doActionOnCurSelectedLandmark(LLLandmarkList::loaded_callback_t cb) +{ + LLViewerInventoryItem* cur_item = getItem(); + if(cur_item && cur_item->getInventoryType() == LLInventoryType::IT_LANDMARK) + { + LLLandmark* landmark = LLLandmarkActions::getLandmark(cur_item->getUUID(), cb); + if (landmark) + { + cb(landmark); + } + } +} + +void LLItemBridge::doShowOnMap(LLLandmark* landmark) +{ + LLVector3d landmark_global_pos; + // landmark has already been tested for NULL by calling routine + if (landmark->getGlobalPos(landmark_global_pos)) + { + LLFloaterWorldMap* worldmap_instance = LLFloaterWorldMap::getInstance(); + if (!landmark_global_pos.isExactlyZero() && worldmap_instance) + { + worldmap_instance->trackLocation(landmark_global_pos); + LLFloaterReg::showInstance("world_map", "center"); + } + } +} + +void copy_slurl_to_clipboard_callback_inv(const std::string& slurl) +{ + gViewerWindow->getWindow()->copyTextToClipboard(utf8str_to_wstring(slurl)); + LLSD args; + args["SLURL"] = slurl; + LLNotificationsUtil::add("CopySLURL", args); +} + +void LLItemBridge::selectItem() +{ + LLViewerInventoryItem* item = static_cast(getItem()); + if(item && !item->isFinished()) + { + //item->fetchFromServer(); + LLInventoryModelBackgroundFetch::instance().start(item->getUUID(), false); + } +} + +void LLItemBridge::restoreItem() +{ + LLViewerInventoryItem* item = static_cast(getItem()); + if(item) + { + LLInventoryModel* model = getInventoryModel(); + bool is_snapshot = (item->getInventoryType() == LLInventoryType::IT_SNAPSHOT); + + const LLUUID new_parent = model->findCategoryUUIDForType(is_snapshot? LLFolderType::FT_SNAPSHOT_CATEGORY : LLFolderType::assetTypeToFolderType(item->getType())); + // do not restamp on restore. + LLInvFVBridge::changeItemParent(model, item, new_parent, false); + } +} + +void LLItemBridge::restoreToWorld() +{ + //Similar functionality to the drag and drop rez logic + bool remove_from_inventory = false; + + LLViewerInventoryItem* itemp = static_cast(getItem()); + if (itemp) + { + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("RezRestoreToWorld"); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + + msg->nextBlockFast(_PREHASH_InventoryData); + itemp->packMessage(msg); + msg->sendReliable(gAgent.getRegionHost()); + + //remove local inventory copy, sim will deal with permissions and removing the item + //from the actual inventory if its a no-copy etc + if(!itemp->getPermissions().allowCopyBy(gAgent.getID())) + { + remove_from_inventory = true; + } + + // Check if it's in the trash. (again similar to the normal rez logic) + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + if(gInventory.isObjectDescendentOf(itemp->getUUID(), trash_id)) + { + remove_from_inventory = true; + } + } + + if(remove_from_inventory) + { + gInventory.deleteObject(itemp->getUUID()); + gInventory.notifyObservers(); + } +} + +void LLItemBridge::gotoItem() +{ + LLInventoryObject *obj = getInventoryObject(); + if (obj && obj->getIsLinkType()) + { + show_item_original(obj->getUUID()); + } +} + +LLUIImagePtr LLItemBridge::getIcon() const +{ + LLInventoryObject *obj = getInventoryObject(); + if (obj) + { + return LLInventoryIcon::getIcon(obj->getType(), + LLInventoryType::IT_NONE, + mIsLink); + } + + return LLInventoryIcon::getIcon(LLInventoryType::ICONNAME_OBJECT); +} + +LLUIImagePtr LLItemBridge::getIconOverlay() const +{ + if (getItem() && getItem()->getIsLinkType()) + { + return LLUI::getUIImage("Inv_Link"); + } + return NULL; +} + +PermissionMask LLItemBridge::getPermissionMask() const +{ + LLViewerInventoryItem* item = getItem(); + PermissionMask perm_mask = 0; + if (item) perm_mask = item->getPermissionMask(); + return perm_mask; +} + +void LLItemBridge::buildDisplayName() const +{ + if(getItem()) + { + mDisplayName.assign(getItem()->getName()); + } + else + { + mDisplayName.assign(LLStringUtil::null); + } + + mSearchableName.assign(mDisplayName); + mSearchableName.append(getLabelSuffix()); + LLStringUtil::toUpper(mSearchableName); + + //Name set, so trigger a sort + LLInventorySort sorter = static_cast(mRootViewModel).getSorter(); + if(mParent && !sorter.isByDate()) + { + mParent->requestSort(); + } +} + +LLFontGL::StyleFlags LLItemBridge::getLabelStyle() const +{ + U8 font = LLFontGL::NORMAL; + const LLViewerInventoryItem* item = getItem(); + + if (get_is_item_worn(mUUID)) + { + // LL_INFOS() << "BOLD" << LL_ENDL; + font |= LLFontGL::BOLD; + } + else if(item && item->getIsLinkType()) + { + font |= LLFontGL::ITALIC; + } + + return (LLFontGL::StyleFlags)font; +} + +std::string LLItemBridge::getLabelSuffix() const +{ + // String table is loaded before login screen and inventory items are + // loaded after login, so LLTrans should be ready. + static std::string NO_COPY = LLTrans::getString("no_copy_lbl"); + static std::string NO_MOD = LLTrans::getString("no_modify_lbl"); + static std::string NO_XFER = LLTrans::getString("no_transfer_lbl"); + static std::string LINK = LLTrans::getString("link"); + static std::string BROKEN_LINK = LLTrans::getString("broken_link"); + std::string suffix; + LLInventoryItem* item = getItem(); + if(item) + { + // Any type can have the link suffix... + bool broken_link = LLAssetType::lookupIsLinkType(item->getType()); + if (broken_link) return BROKEN_LINK; + + bool link = item->getIsLinkType(); + if (link) return LINK; + + // ...but it's a bit confusing to put nocopy/nomod/etc suffixes on calling cards. + if(LLAssetType::AT_CALLINGCARD != item->getType() + && item->getPermissions().getOwner() == gAgent.getID()) + { + bool copy = item->getPermissions().allowCopyBy(gAgent.getID()); + if (!copy) + { + suffix += " "; + suffix += NO_COPY; + } + bool mod = item->getPermissions().allowModifyBy(gAgent.getID()); + if (!mod) + { + suffix += suffix.empty() ? " " : ","; + suffix += NO_MOD; + } + bool xfer = item->getPermissions().allowOperationBy(PERM_TRANSFER, + gAgent.getID()); + if (!xfer) + { + suffix += suffix.empty() ? " " : ","; + suffix += NO_XFER; + } + } + } + return suffix; +} + +time_t LLItemBridge::getCreationDate() const +{ + LLViewerInventoryItem* item = getItem(); + if (item) + { + return item->getCreationDate(); + } + return 0; +} + + +bool LLItemBridge::isItemRenameable() const +{ + LLViewerInventoryItem* item = getItem(); + if(item) + { + // (For now) Don't allow calling card rename since that may confuse users as to + // what the calling card points to. + if (item->getInventoryType() == LLInventoryType::IT_CALLINGCARD) + { + return false; + } + + if (!item->isFinished()) // EXT-8662 + { + return false; + } + + if (isInboxFolder()) + { + return false; + } + + return (item->getPermissions().allowModifyBy(gAgent.getID())); + } + return false; +} + +bool LLItemBridge::renameItem(const std::string& new_name) +{ + if(!isItemRenameable()) + return false; + LLPreview::dirty(mUUID); + LLInventoryModel* model = getInventoryModel(); + if(!model) + return false; + LLViewerInventoryItem* item = getItem(); + if(item && (item->getName() != new_name)) + { + LLSD updates; + updates["name"] = new_name; + update_inventory_item(item->getUUID(),updates, NULL); + } + // return false because we either notified observers (& therefore + // rebuilt) or we didn't update. + return false; +} + +bool LLItemBridge::removeItem() +{ + if(!isItemRemovable()) + { + return false; + } + + // move it to the trash + LLInventoryModel* model = getInventoryModel(); + if(!model) return false; + const LLUUID& trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); + LLViewerInventoryItem* item = getItem(); + if (!item) return false; + if (item->getType() != LLAssetType::AT_LSL_TEXT) + { + LLPreview::hide(mUUID, true); + } + // Already in trash + if (model->isObjectDescendentOf(mUUID, trash_id)) return false; + + LLNotification::Params params("ConfirmItemDeleteHasLinks"); + params.functor.function(boost::bind(&LLItemBridge::confirmRemoveItem, this, _1, _2)); + + // Check if this item has any links. If generic inventory linking is enabled, + // we can't do this check because we may have items in a folder somewhere that is + // not yet in memory, so we don't want false negatives. (If disabled, then we + // know we only have links in the Outfits folder which we explicitly fetch.) + static LLCachedControl inventory_linking(gSavedSettings, "InventoryLinking", true); + if (!inventory_linking) + { + if (!item->getIsLinkType()) + { + LLInventoryModel::item_array_t item_array = gInventory.collectLinksTo(mUUID); + const U32 num_links = item_array.size(); + if (num_links > 0) + { + // Warn if the user is will break any links when deleting this item. + LLNotifications::instance().add(params); + return false; + } + } + } + + LLNotifications::instance().forceResponse(params, 0); + model->checkTrashOverflow(); + return true; +} + +bool LLItemBridge::confirmRemoveItem(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) return false; + + LLInventoryModel* model = getInventoryModel(); + if (!model) return false; + + LLViewerInventoryItem* item = getItem(); + if (!item) return false; + + const LLUUID& trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); + // if item is not already in trash + if(item && !model->isObjectDescendentOf(mUUID, trash_id)) + { + // move to trash, and restamp + LLInvFVBridge::changeItemParent(model, item, trash_id, true); + // delete was successful + return true; + } + return false; +} + +bool LLItemBridge::isItemCopyable(bool can_copy_as_link) const +{ + LLViewerInventoryItem* item = getItem(); + if (!item) + { + return false; + } + // Can't copy worn objects. + // Worn objects are tied to their inworld conterparts + // Copy of modified worn object will return object with obsolete asset and inventory + if (get_is_item_worn(mUUID)) + { + return false; + } + + static LLCachedControl inventory_linking(gSavedSettings, "InventoryLinking", true); + return (can_copy_as_link && inventory_linking) + || (mIsLink && inventory_linking) + || item->getPermissions().allowCopyBy(gAgent.getID()); +} + +LLViewerInventoryItem* LLItemBridge::getItem() const +{ + LLViewerInventoryItem* item = NULL; + LLInventoryModel* model = getInventoryModel(); + if(model) + { + item = (LLViewerInventoryItem*)model->getItem(mUUID); + } + return item; +} + +const LLUUID& LLItemBridge::getThumbnailUUID() const +{ + LLViewerInventoryItem* item = NULL; + LLInventoryModel* model = getInventoryModel(); + if(model) + { + item = (LLViewerInventoryItem*)model->getItem(mUUID); + } + if (item) + { + return item->getThumbnailUUID(); + } + return LLUUID::null; +} + +bool LLItemBridge::isItemPermissive() const +{ + LLViewerInventoryItem* item = getItem(); + if(item) + { + return item->getIsFullPerm(); + } + return false; +} + +// +=================================================+ +// | LLFolderBridge | +// +=================================================+ + +LLHandle LLFolderBridge::sSelf; + +// Can be moved to another folder +bool LLFolderBridge::isItemMovable() const +{ + LLInventoryObject* obj = getInventoryObject(); + if(obj) + { + // If it's a protected type folder, we can't move it + if (LLFolderType::lookupIsProtectedType(((LLInventoryCategory*)obj)->getPreferredType())) + return false; + return true; + } + return false; +} + +void LLFolderBridge::selectItem() +{ + LLViewerInventoryCategory* cat = gInventory.getCategory(getUUID()); + if (cat) + { + cat->fetch(); + } +} + +void LLFolderBridge::buildDisplayName() const +{ + LLFolderType::EType preferred_type = getPreferredType(); + + // *TODO: to be removed when database supports multi language. This is a + // temporary attempt to display the inventory folder in the user locale. + // mantipov: *NOTE: be sure this code is synchronized with LLFriendCardsManager::findChildFolderUUID + // it uses the same way to find localized string + + // HACK: EXT - 6028 ([HARD CODED]? Inventory > Library > "Accessories" folder) + // Translation of Accessories folder in Library inventory folder + bool accessories = false; + if(getName() == "Accessories") + { + //To ensure that Accessories folder is in Library we have to check its parent folder. + //Due to parent LLFolderViewFloder is not set to this item yet we have to check its parent via Inventory Model + LLInventoryCategory* cat = gInventory.getCategory(getUUID()); + if(cat) + { + const LLUUID& parent_folder_id = cat->getParentUUID(); + accessories = (parent_folder_id == gInventory.getLibraryRootFolderID()); + } + } + + //"Accessories" inventory category has folder type FT_NONE. So, this folder + //can not be detected as protected with LLFolderType::lookupIsProtectedType + mDisplayName.assign(getName()); + if (accessories || LLFolderType::lookupIsProtectedType(preferred_type)) + { + LLTrans::findString(mDisplayName, std::string("InvFolder ") + getName(), LLSD()); + } + + mSearchableName.assign(mDisplayName); + mSearchableName.append(getLabelSuffix()); + LLStringUtil::toUpper(mSearchableName); + + //Name set, so trigger a sort + LLInventorySort sorter = static_cast(mRootViewModel).getSorter(); + if(mParent && sorter.isFoldersByName()) + { + mParent->requestSort(); + } +} + +std::string LLFolderBridge::getLabelSuffix() const +{ + static LLCachedControl xui_debug(gSavedSettings, "DebugShowXUINames", 0); + + if (mIsLoading && mTimeSinceRequestStart.getElapsedTimeF32() >= FOLDER_LOADING_MESSAGE_DELAY) + { + return llformat(" ( %s ) ", LLTrans::getString("LoadingData").c_str()); + } + std::string suffix = ""; + if (xui_debug) + { + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(getUUID(), cats, items); + + LLViewerInventoryCategory* cat = gInventory.getCategory(getUUID()); + if (cat) + { + LLStringUtil::format_map_t args; + args["[FOLDER_COUNT]"] = llformat("%d", cats->size()); + args["[ITEMS_COUNT]"] = llformat("%d", items->size()); + args["[VERSION]"] = llformat("%d", cat->getVersion()); + args["[VIEWER_DESCENDANT_COUNT]"] = llformat("%d", cats->size() + items->size()); + args["[SERVER_DESCENDANT_COUNT]"] = llformat("%d", cat->getDescendentCount()); + suffix = " " + LLTrans::getString("InventoryFolderDebug", args); + } + } + else if(mShowDescendantsCount) + { + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + gInventory.collectDescendents(getUUID(), cat_array, item_array, true); + S32 count = item_array.size(); + if(count > 0) + { + std::ostringstream oss; + oss << count; + LLStringUtil::format_map_t args; + args["[ITEMS_COUNT]"] = oss.str(); + suffix = " " + LLTrans::getString("InventoryItemsCount", args); + } + } + + return LLInvFVBridge::getLabelSuffix() + suffix; +} + +LLFontGL::StyleFlags LLFolderBridge::getLabelStyle() const +{ + return LLFontGL::NORMAL; +} + +const LLUUID& LLFolderBridge::getThumbnailUUID() const +{ + LLViewerInventoryCategory* cat = getCategory(); + if (cat) + { + return cat->getThumbnailUUID(); + } + return LLUUID::null; +} + +void LLFolderBridge::update() +{ + // we know we have children but haven't fetched them (doesn't obey filter) + bool loading = !isUpToDate() && hasChildren() && mFolderViewItem->isOpen(); + + if (loading != mIsLoading) + { + if ( loading ) + { + // Measure how long we've been in the loading state + mTimeSinceRequestStart.reset(); + } + mIsLoading = loading; + + mFolderViewItem->refresh(); + } +} + +// Can be destroyed (or moved to trash) +bool LLFolderBridge::isItemRemovable(bool check_worn) const +{ + if (!get_is_category_and_children_removable(getInventoryModel(), mUUID, check_worn)) + { + return false; + } + + if (isMarketplaceListingsFolder() + && (!LLMarketplaceData::instance().isSLMDataFetched() || LLMarketplaceData::instance().getActivationState(mUUID))) + { + return false; + } + + return true; +} + +bool LLFolderBridge::isUpToDate() const +{ + LLInventoryModel* model = getInventoryModel(); + if(!model) return false; + LLViewerInventoryCategory* category = (LLViewerInventoryCategory*)model->getCategory(mUUID); + if( !category ) + { + return false; + } + + return category->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN; +} + +bool LLFolderBridge::isItemCopyable(bool can_copy_as_link) const +{ + if (can_copy_as_link && !LLFolderType::lookupIsProtectedType(getPreferredType())) + { + // Can copy and paste unprotected folders as links + return true; + } + + // Folders are copyable if items in them are, recursively, copyable. + + // Get the content of the folder + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(mUUID,cat_array,item_array); + + // Check the items + LLInventoryModel::item_array_t item_array_copy = *item_array; + for (LLInventoryModel::item_array_t::iterator iter = item_array_copy.begin(); iter != item_array_copy.end(); iter++) + { + LLInventoryItem* item = *iter; + LLItemBridge item_br(mInventoryPanel.get(), mRoot, item->getUUID()); + if (!item_br.isItemCopyable(false)) + { + return false; + } + } + + // Check the folders + LLInventoryModel::cat_array_t cat_array_copy = *cat_array; + for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) + { + LLViewerInventoryCategory* category = *iter; + LLFolderBridge cat_br(mInventoryPanel.get(), mRoot, category->getUUID()); + if (!cat_br.isItemCopyable(false)) + { + return false; + } + } + + return true; +} + +bool LLFolderBridge::isClipboardPasteable() const +{ + if ( ! LLInvFVBridge::isClipboardPasteable() ) + return false; + + // Don't allow pasting duplicates to the Calling Card/Friends subfolders, see bug EXT-1599 + if ( LLFriendCardsManager::instance().isCategoryInFriendFolder( getCategory() ) ) + { + LLInventoryModel* model = getInventoryModel(); + if ( !model ) + { + return false; + } + + std::vector objects; + LLClipboard::instance().pasteFromClipboard(objects); + const LLViewerInventoryCategory *current_cat = getCategory(); + + // Search for the direct descendent of current Friends subfolder among all pasted items, + // and return false if is found. + for(S32 i = objects.size() - 1; i >= 0; --i) + { + const LLUUID &obj_id = objects.at(i); + if ( LLFriendCardsManager::instance().isObjDirectDescendentOfCategory(model->getObject(obj_id), current_cat) ) + { + return false; + } + } + + } + return true; +} + +bool LLFolderBridge::isClipboardPasteableAsLink() const +{ + // Check normal paste-as-link permissions + if (!LLInvFVBridge::isClipboardPasteableAsLink()) + { + return false; + } + + const LLInventoryModel* model = getInventoryModel(); + if (!model) + { + return false; + } + + const LLViewerInventoryCategory *current_cat = getCategory(); + if (current_cat) + { + const bool is_in_friend_folder = LLFriendCardsManager::instance().isCategoryInFriendFolder( current_cat ); + const LLUUID ¤t_cat_id = current_cat->getUUID(); + std::vector objects; + LLClipboard::instance().pasteFromClipboard(objects); + S32 count = objects.size(); + for(S32 i = 0; i < count; i++) + { + const LLUUID &obj_id = objects.at(i); + const LLInventoryCategory *cat = model->getCategory(obj_id); + if (cat) + { + const LLUUID &cat_id = cat->getUUID(); + // Don't allow recursive pasting + if ((cat_id == current_cat_id) || + model->isObjectDescendentOf(current_cat_id, cat_id)) + { + return false; + } + } + // Don't allow pasting duplicates to the Calling Card/Friends subfolders, see bug EXT-1599 + if ( is_in_friend_folder ) + { + // If object is direct descendent of current Friends subfolder than return false. + // Note: We can't use 'const LLInventoryCategory *cat', because it may be null + // in case type of obj_id is LLInventoryItem. + if ( LLFriendCardsManager::instance().isObjDirectDescendentOfCategory(model->getObject(obj_id), current_cat) ) + { + return false; + } + } + } + } + return true; + +} + + +bool LLFolderBridge::dragCategoryIntoFolder(LLInventoryCategory* inv_cat, + bool drop, + std::string& tooltip_msg, + bool is_link, + bool user_confirm, + LLPointer cb) +{ + + LLInventoryModel* model = getInventoryModel(); + + if (!inv_cat) return false; // shouldn't happen, but in case item is incorrectly parented in which case inv_cat will be NULL + if (!model) return false; + if (!isAgentAvatarValid()) return false; + if (!isAgentInventory()) return false; // cannot drag categories into library + + LLInventoryPanel* destination_panel = mInventoryPanel.get(); + if (!destination_panel) return false; + + LLInventoryFilter* filter = getInventoryFilter(); + if (!filter) return false; + + const LLUUID &cat_id = inv_cat->getUUID(); + const LLUUID ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + const LLUUID from_folder_uuid = inv_cat->getParentUUID(); + + const bool move_is_into_current_outfit = (mUUID == current_outfit_id); + const bool move_is_into_marketplacelistings = model->isObjectDescendentOf(mUUID, marketplacelistings_id); + const bool move_is_from_marketplacelistings = model->isObjectDescendentOf(cat_id, marketplacelistings_id); + + // check to make sure source is agent inventory, and is represented there. + LLToolDragAndDrop::ESource source = LLToolDragAndDrop::getInstance()->getSource(); + const bool is_agent_inventory = (model->getCategory(cat_id) != NULL) + && (LLToolDragAndDrop::SOURCE_AGENT == source); + + bool accept = false; + U64 filter_types = filter->getFilterTypes(); + bool use_filter = filter_types && (filter_types&LLInventoryFilter::FILTERTYPE_DATE || (filter_types&LLInventoryFilter::FILTERTYPE_OBJECT)==0); + + if (is_agent_inventory) + { + const LLUUID &trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); + const LLUUID &landmarks_id = model->findCategoryUUIDForType(LLFolderType::FT_LANDMARK); + const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + const LLUUID &lost_and_found_id = model->findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); + + const bool move_is_into_trash = (mUUID == trash_id) || model->isObjectDescendentOf(mUUID, trash_id); + const bool move_is_into_my_outfits = (mUUID == my_outifts_id) || model->isObjectDescendentOf(mUUID, my_outifts_id); + const bool move_is_into_outfit = move_is_into_my_outfits || (getCategory() && getCategory()->getPreferredType()==LLFolderType::FT_OUTFIT); + const bool move_is_into_current_outfit = (getCategory() && getCategory()->getPreferredType()==LLFolderType::FT_CURRENT_OUTFIT); + const bool move_is_into_landmarks = (mUUID == landmarks_id) || model->isObjectDescendentOf(mUUID, landmarks_id); + const bool move_is_into_lost_and_found = model->isObjectDescendentOf(mUUID, lost_and_found_id); + + //-------------------------------------------------------------------------------- + // Determine if folder can be moved. + // + + bool is_movable = true; + + if (is_movable && (marketplacelistings_id == cat_id)) + { + is_movable = false; + tooltip_msg = LLTrans::getString("TooltipOutboxCannotMoveRoot"); + } + if (is_movable && move_is_from_marketplacelistings && LLMarketplaceData::instance().getActivationState(cat_id)) + { + // If the incoming folder is listed and active (and is therefore either the listing or the version folder), + // then moving is *not* allowed + is_movable = false; + tooltip_msg = LLTrans::getString("TooltipOutboxDragActive"); + } + if (is_movable && (mUUID == cat_id)) + { + is_movable = false; + tooltip_msg = LLTrans::getString("TooltipDragOntoSelf"); + } + if (is_movable && (model->isObjectDescendentOf(mUUID, cat_id))) + { + is_movable = false; + tooltip_msg = LLTrans::getString("TooltipDragOntoOwnChild"); + } + if (is_movable && LLFolderType::lookupIsProtectedType(inv_cat->getPreferredType())) + { + is_movable = false; + // tooltip? + } + + U32 max_items_to_wear = gSavedSettings.getU32("WearFolderLimit"); + if (is_movable && move_is_into_outfit) + { + if (mUUID == my_outifts_id) + { + if (source != LLToolDragAndDrop::SOURCE_AGENT || move_is_from_marketplacelistings) + { + tooltip_msg = LLTrans::getString("TooltipOutfitNotInInventory"); + is_movable = false; + } + else if (can_move_to_my_outfits(model, inv_cat, max_items_to_wear)) + { + is_movable = true; + } + else + { + tooltip_msg = LLTrans::getString("TooltipCantCreateOutfit"); + is_movable = false; + } + } + else if(getCategory() && getCategory()->getPreferredType() == LLFolderType::FT_NONE) + { + is_movable = ((inv_cat->getPreferredType() == LLFolderType::FT_NONE) || (inv_cat->getPreferredType() == LLFolderType::FT_OUTFIT)); + } + else + { + is_movable = false; + } + } + if(is_movable && move_is_into_current_outfit && is_link) + { + is_movable = false; + } + if (is_movable && move_is_into_lost_and_found) + { + is_movable = false; + } + if (is_movable && (mUUID == model->findCategoryUUIDForType(LLFolderType::FT_FAVORITE))) + { + is_movable = false; + // tooltip? + } + if (is_movable && (getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK)) + { + // One cannot move a folder into a stock folder + is_movable = false; + // tooltip? + } + + LLInventoryModel::cat_array_t descendent_categories; + LLInventoryModel::item_array_t descendent_items; + if (is_movable) + { + model->collectDescendents(cat_id, descendent_categories, descendent_items, false); + for (S32 i=0; i < descendent_categories.size(); ++i) + { + LLInventoryCategory* category = descendent_categories[i]; + if(LLFolderType::lookupIsProtectedType(category->getPreferredType())) + { + // Can't move "special folders" (e.g. Textures Folder). + is_movable = false; + break; + } + } + } + if (is_movable + && move_is_into_current_outfit + && descendent_items.size() > max_items_to_wear) + { + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLFindWearablesEx not_worn(/*is_worn=*/ false, /*include_body_parts=*/ false); + gInventory.collectDescendentsIf(cat_id, + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + not_worn); + + if (items.size() > max_items_to_wear) + { + // Can't move 'large' folders into current outfit: MAINT-4086 + is_movable = false; + LLStringUtil::format_map_t args; + args["AMOUNT"] = llformat("%d", max_items_to_wear); + tooltip_msg = LLTrans::getString("TooltipTooManyWearables",args); + } + } + if (is_movable && move_is_into_trash) + { + for (S32 i=0; i < descendent_items.size(); ++i) + { + LLInventoryItem* item = descendent_items[i]; + if (get_is_item_worn(item->getUUID())) + { + is_movable = false; + break; // It's generally movable, but not into the trash. + } + } + } + if (is_movable && move_is_into_landmarks) + { + for (S32 i=0; i < descendent_items.size(); ++i) + { + LLViewerInventoryItem* item = descendent_items[i]; + + // Don't move anything except landmarks and categories into Landmarks folder. + // We use getType() instead of getActua;Type() to allow links to landmarks and folders. + if (LLAssetType::AT_LANDMARK != item->getType() && LLAssetType::AT_CATEGORY != item->getType()) + { + is_movable = false; + break; // It's generally movable, but not into Landmarks. + } + } + } + + if (is_movable && move_is_into_marketplacelistings) + { + const LLViewerInventoryCategory * master_folder = model->getFirstDescendantOf(marketplacelistings_id, mUUID); + LLViewerInventoryCategory * dest_folder = getCategory(); + S32 bundle_size = (drop ? 1 : LLToolDragAndDrop::instance().getCargoCount()); + is_movable = can_move_folder_to_marketplace(master_folder, dest_folder, inv_cat, tooltip_msg, bundle_size); + } + + if (is_movable && !move_is_into_landmarks) + { + LLInventoryPanel* active_panel = LLInventoryPanel::getActiveInventoryPanel(false); + is_movable = active_panel != NULL; + + // For a folder to pass the filter all its descendants are required to pass. + // We make this exception to allow reordering folders within an inventory panel, + // which has a filter applied, like Recent tab for example. + // There may be folders which are displayed because some of their descendants pass + // the filter, but other don't, and thus remain hidden. Without this check, + // such folders would not be allowed to be moved within a panel. + if (destination_panel == active_panel) + { + is_movable = true; + } + else + { + LLFolderView* active_folder_view = NULL; + + if (is_movable) + { + active_folder_view = active_panel->getRootFolder(); + is_movable = active_folder_view != NULL; + } + + if (is_movable && use_filter) + { + // Check whether the folder being dragged from active inventory panel + // passes the filter of the destination panel. + is_movable = check_category(model, cat_id, active_panel, filter); + } + } + } + // + //-------------------------------------------------------------------------------- + + accept = is_movable; + + if (accept && drop) + { + // Dropping in or out of marketplace needs (sometimes) confirmation + if (user_confirm && (move_is_from_marketplacelistings || move_is_into_marketplacelistings)) + { + if (move_is_from_marketplacelistings && (LLMarketplaceData::instance().isInActiveFolder(cat_id) || + LLMarketplaceData::instance().isListedAndActive(cat_id))) + { + if (LLMarketplaceData::instance().isListed(cat_id) || LLMarketplaceData::instance().isVersionFolder(cat_id)) + { + // Move the active version folder or listing folder itself outside marketplace listings will unlist the listing so ask that question specifically + LLNotificationsUtil::add("ConfirmMerchantUnlist", LLSD(), LLSD(), boost::bind(&LLFolderBridge::callback_dropCategoryIntoFolder, this, _1, _2, inv_cat)); + } + else + { + // Any other case will simply modify but not unlist an active listed listing + LLNotificationsUtil::add("ConfirmMerchantActiveChange", LLSD(), LLSD(), boost::bind(&LLFolderBridge::callback_dropCategoryIntoFolder, this, _1, _2, inv_cat)); + } + return true; + } + if (move_is_from_marketplacelistings && LLMarketplaceData::instance().isVersionFolder(cat_id)) + { + // Moving the version folder from its location will deactivate it. Ask confirmation. + LLNotificationsUtil::add("ConfirmMerchantClearVersion", LLSD(), LLSD(), boost::bind(&LLFolderBridge::callback_dropCategoryIntoFolder, this, _1, _2, inv_cat)); + return true; + } + if (move_is_into_marketplacelistings && LLMarketplaceData::instance().isInActiveFolder(mUUID)) + { + // Moving something in an active listed listing will modify it. Ask confirmation. + LLNotificationsUtil::add("ConfirmMerchantActiveChange", LLSD(), LLSD(), boost::bind(&LLFolderBridge::callback_dropCategoryIntoFolder, this, _1, _2, inv_cat)); + return true; + } + if (move_is_from_marketplacelistings && LLMarketplaceData::instance().isListed(cat_id)) + { + // Moving a whole listing folder will result in archival of SLM data. Ask confirmation. + LLNotificationsUtil::add("ConfirmListingCutOrDelete", LLSD(), LLSD(), boost::bind(&LLFolderBridge::callback_dropCategoryIntoFolder, this, _1, _2, inv_cat)); + return true; + } + if (move_is_into_marketplacelistings && !move_is_from_marketplacelistings) + { + LLNotificationsUtil::add("ConfirmMerchantMoveInventory", LLSD(), LLSD(), boost::bind(&LLFolderBridge::callback_dropCategoryIntoFolder, this, _1, _2, inv_cat)); + return true; + } + } + // Look for any gestures and deactivate them + if (move_is_into_trash) + { + for (S32 i=0; i < descendent_items.size(); i++) + { + LLInventoryItem* item = descendent_items[i]; + if (item->getType() == LLAssetType::AT_GESTURE + && LLGestureMgr::instance().isGestureActive(item->getUUID())) + { + LLGestureMgr::instance().deactivateGesture(item->getUUID()); + } + } + } + + if (mUUID == my_outifts_id) + { + // Category can contains objects, + // create a new folder and populate it with links to original objects + dropToMyOutfits(inv_cat, cb); + } + // if target is current outfit folder we use link + else if (move_is_into_current_outfit && + (inv_cat->getPreferredType() == LLFolderType::FT_NONE || + inv_cat->getPreferredType() == LLFolderType::FT_OUTFIT)) + { + // traverse category and add all contents to currently worn. + bool append = true; + LLAppearanceMgr::instance().wearInventoryCategory(inv_cat, false, append); + if (cb) cb->fire(inv_cat->getUUID()); + } + else if (move_is_into_marketplacelistings) + { + move_folder_to_marketplacelistings(inv_cat, mUUID); + if (cb) cb->fire(inv_cat->getUUID()); + } + else + { + if (model->isObjectDescendentOf(cat_id, model->findCategoryUUIDForType(LLFolderType::FT_INBOX))) + { + set_dad_inbox_object(cat_id); + } + + // Reparent the folder and restamp children if it's moving + // into trash. + LLInvFVBridge::changeCategoryParent( + model, + (LLViewerInventoryCategory*)inv_cat, + mUUID, + move_is_into_trash); + if (cb) cb->fire(inv_cat->getUUID()); + } + if (move_is_from_marketplacelistings) + { + // If we are moving a folder at the listing folder level (i.e. its parent is the marketplace listings folder) + if (from_folder_uuid == marketplacelistings_id) + { + // Clear the folder from the marketplace in case it is a listing folder + if (LLMarketplaceData::instance().isListed(cat_id)) + { + LLMarketplaceData::instance().clearListing(cat_id); + } + } + else + { + // If we move from within an active (listed) listing, checks that it's still valid, if not, unlist + LLUUID version_folder_id = LLMarketplaceData::instance().getActiveFolder(from_folder_uuid); + if (version_folder_id.notNull()) + { + LLMarketplaceValidator::getInstance()->validateMarketplaceListings( + version_folder_id, + [version_folder_id](bool result) + { + if (!result) + { + LLMarketplaceData::instance().activateListing(version_folder_id, false); + } + } + ); + } + // In all cases, update the listing we moved from so suffix are updated + update_marketplace_category(from_folder_uuid); + if (cb) cb->fire(inv_cat->getUUID()); + } + } + } + } + else if (LLToolDragAndDrop::SOURCE_WORLD == source) + { + if (move_is_into_marketplacelistings) + { + tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); + accept = false; + } + else + { + // Todo: fix me. moving from task inventory doesn't have a completion callback, + // yet making a copy creates new item id so this doesn't work right + std::function callback = [cb](S32, void*, const LLMoveInv* move_inv) mutable + { + two_uuids_list_t::const_iterator move_it; + for (move_it = move_inv->mMoveList.begin(); + move_it != move_inv->mMoveList.end(); + ++move_it) + { + if (cb) + { + cb->fire(move_it->second); + } + } + }; + accept = move_inv_category_world_to_agent(cat_id, mUUID, drop, callback, NULL, filter); + } + } + else if (LLToolDragAndDrop::SOURCE_LIBRARY == source) + { + if (move_is_into_marketplacelistings) + { + tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); + accept = false; + } + else + { + // Accept folders that contain complete outfits. + accept = move_is_into_current_outfit && LLAppearanceMgr::instance().getCanMakeFolderIntoOutfit(cat_id); + } + + if (accept && drop) + { + LLAppearanceMgr::instance().wearInventoryCategory(inv_cat, true, false); + } + } + + return accept; +} + +void warn_move_inventory(LLViewerObject* object, std::shared_ptr move_inv) +{ + const char* dialog = NULL; + if (object->flagScripted()) + { + dialog = "MoveInventoryFromScriptedObject"; + } + else + { + dialog = "MoveInventoryFromObject"; + } + + static LLNotificationPtr notification_ptr; + static std::shared_ptr inv_ptr; + + // Notification blocks user from interacting with inventories so everything that comes after first message + // is part of this message - don'r show it again + // Note: workaround for MAINT-5495 untill proper refactoring and warning system for Drag&Drop can be made. + if (notification_ptr == NULL + || !notification_ptr->isActive() + || LLNotificationsUtil::find(notification_ptr->getID()) == NULL + || inv_ptr->mCategoryID != move_inv->mCategoryID + || inv_ptr->mObjectID != move_inv->mObjectID) + { + notification_ptr = LLNotificationsUtil::add(dialog, LLSD(), LLSD(), boost::bind(move_task_inventory_callback, _1, _2, move_inv)); + inv_ptr = move_inv; + } + else + { + // Notification is alive and not responded, operating inv_ptr should be safe so attach new data + two_uuids_list_t::iterator move_it; + for (move_it = move_inv->mMoveList.begin(); + move_it != move_inv->mMoveList.end(); + ++move_it) + { + inv_ptr->mMoveList.push_back(*move_it); + } + move_inv.reset(); + } +} + +// Move/copy all inventory items from the Contents folder of an in-world +// object to the agent's inventory, inside a given category. +bool move_inv_category_world_to_agent(const LLUUID& object_id, + const LLUUID& category_id, + bool drop, + std::function callback, + void* user_data, + LLInventoryFilter* filter) +{ + // Make sure the object exists. If we allowed dragging from + // anonymous objects, it would be possible to bypass + // permissions. + // content category has same ID as object itself + LLViewerObject* object = gObjectList.findObject(object_id); + if(!object) + { + LL_INFOS() << "Object not found for drop." << LL_ENDL; + return false; + } + + // this folder is coming from an object, as there is only one folder in an object, the root, + // we need to collect the entire contents and handle them as a group + LLInventoryObject::object_list_t inventory_objects; + object->getInventoryContents(inventory_objects); + + if (inventory_objects.empty()) + { + LL_INFOS() << "Object contents not found for drop." << LL_ENDL; + return false; + } + + bool accept = false; + bool is_move = false; + bool use_filter = false; + if (filter) + { + U64 filter_types = filter->getFilterTypes(); + use_filter = filter_types && (filter_types&LLInventoryFilter::FILTERTYPE_DATE || (filter_types&LLInventoryFilter::FILTERTYPE_OBJECT)==0); + } + + // coming from a task. Need to figure out if the person can + // move/copy this item. + LLInventoryObject::object_list_t::iterator it = inventory_objects.begin(); + LLInventoryObject::object_list_t::iterator end = inventory_objects.end(); + for ( ; it != end; ++it) + { + LLInventoryItem* item = dynamic_cast(it->get()); + if (!item) + { + LL_WARNS() << "Invalid inventory item for drop" << LL_ENDL; + continue; + } + + // coming from a task. Need to figure out if the person can + // move/copy this item. + LLPermissions perm(item->getPermissions()); + if((perm.allowCopyBy(gAgent.getID(), gAgent.getGroupID()) + && perm.allowTransferTo(gAgent.getID()))) +// || gAgent.isGodlike()) + { + accept = true; + } + else if(object->permYouOwner()) + { + // If the object cannot be copied, but the object the + // inventory is owned by the agent, then the item can be + // moved from the task to agent inventory. + is_move = true; + accept = true; + } + + if (accept && use_filter) + { + accept = filter->check(item); + } + + if (!accept) + { + break; + } + } + + if(drop && accept) + { + it = inventory_objects.begin(); + std::shared_ptr move_inv(new LLMoveInv); + move_inv->mObjectID = object_id; + move_inv->mCategoryID = category_id; + move_inv->mCallback = callback; + move_inv->mUserData = user_data; + + for ( ; it != end; ++it) + { + two_uuids_t two(category_id, (*it)->getUUID()); + move_inv->mMoveList.push_back(two); + } + + if(is_move) + { + // Callback called from within here. + warn_move_inventory(object, move_inv); + } + else + { + LLNotification::Params params("MoveInventoryFromObject"); + params.functor.function(boost::bind(move_task_inventory_callback, _1, _2, move_inv)); + LLNotifications::instance().forceResponse(params, 0); + } + } + return accept; +} + +void LLRightClickInventoryFetchDescendentsObserver::execute(bool clear_observer) +{ + // Bail out immediately if no descendents + if( mComplete.empty() ) + { + LL_WARNS() << "LLRightClickInventoryFetchDescendentsObserver::done with empty mCompleteFolders" << LL_ENDL; + if (clear_observer) + { + gInventory.removeObserver(this); + delete this; + } + return; + } + + // Copy the list of complete fetched folders while "this" is still valid + uuid_vec_t completed_folder = mComplete; + + // Clean up, and remove this as an observer now since recursive calls + // could notify observers and throw us into an infinite loop. + if (clear_observer) + { + gInventory.removeObserver(this); + delete this; + } + + for (uuid_vec_t::iterator current_folder = completed_folder.begin(); current_folder != completed_folder.end(); ++current_folder) + { + // Get the information on the fetched folder items and subfolders and fetch those + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(*current_folder, cat_array, item_array); + + S32 item_count(0); + if( item_array ) + { + item_count = item_array->size(); + } + + S32 cat_count(0); + if( cat_array ) + { + cat_count = cat_array->size(); + } + + // Move to next if current folder empty + if ((item_count == 0) && (cat_count == 0)) + { + continue; + } + + uuid_vec_t ids; + LLRightClickInventoryFetchObserver* outfit = NULL; + LLRightClickInventoryFetchDescendentsObserver* categories = NULL; + + // Fetch the items + if (item_count) + { + for (S32 i = 0; i < item_count; ++i) + { + ids.push_back(item_array->at(i)->getUUID()); + } + outfit = new LLRightClickInventoryFetchObserver(ids); + } + // Fetch the subfolders + if (cat_count) + { + for (S32 i = 0; i < cat_count; ++i) + { + ids.push_back(cat_array->at(i)->getUUID()); + } + categories = new LLRightClickInventoryFetchDescendentsObserver(ids); + } + + // Perform the item fetch + if (outfit) + { + outfit->startFetch(); + outfit->execute(); // Not interested in waiting and this will be right 99% of the time. + delete outfit; +//Uncomment the following code for laggy Inventory UI. + /* + if (outfit->isFinished()) + { + // everything is already here - call done. + outfit->execute(); + delete outfit; + } + else + { + // it's all on its way - add an observer, and the inventory + // will call done for us when everything is here. + gInventory.addObserver(outfit); + } + */ + } + // Perform the subfolders fetch : this is where we truly recurse down the folder hierarchy + if (categories) + { + categories->startFetch(); + if (categories->isFinished()) + { + // everything is already here - call done. + categories->execute(); + delete categories; + } + else + { + // it's all on its way - add an observer, and the inventory + // will call done for us when everything is here. + gInventory.addObserver(categories); + } + } + } +} + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLInventoryWearObserver +// +// Observer for "copy and wear" operation to support knowing +// when the all of the contents have been added to inventory. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLInventoryCopyAndWearObserver : public LLInventoryObserver +{ +public: + LLInventoryCopyAndWearObserver(const LLUUID& cat_id, int count, bool folder_added=false, bool replace=false) : + mCatID(cat_id), mContentsCount(count), mFolderAdded(folder_added), mReplace(replace){} + virtual ~LLInventoryCopyAndWearObserver() {} + virtual void changed(U32 mask); + +protected: + LLUUID mCatID; + int mContentsCount; + bool mFolderAdded; + bool mReplace; +}; + + + +void LLInventoryCopyAndWearObserver::changed(U32 mask) +{ + if((mask & (LLInventoryObserver::ADD)) != 0) + { + if (!mFolderAdded) + { + const std::set& changed_items = gInventory.getChangedIDs(); + + std::set::const_iterator id_it = changed_items.begin(); + std::set::const_iterator id_end = changed_items.end(); + for (;id_it != id_end; ++id_it) + { + if ((*id_it) == mCatID) + { + mFolderAdded = true; + break; + } + } + } + + if (mFolderAdded) + { + LLViewerInventoryCategory* category = gInventory.getCategory(mCatID); + if (NULL == category) + { + LL_WARNS() << "gInventory.getCategory(" << mCatID + << ") was NULL" << LL_ENDL; + } + else + { + if (category->getDescendentCount() == + mContentsCount) + { + gInventory.removeObserver(this); + LLAppearanceMgr::instance().wearInventoryCategory(category, false, !mReplace); + delete this; + } + } + } + + } +} + + + +void LLFolderBridge::performAction(LLInventoryModel* model, std::string action) +{ + if ("open" == action) + { + LLFolderViewFolder *f = dynamic_cast(mInventoryPanel.get()->getItemByID(mUUID)); + if (f) + { + f->toggleOpen(); + } + + return; + } + else if ("thumbnail" == action) + { + LLSD data(mUUID); + LLFloaterReg::showInstance("change_item_thumbnail", data); + return; + } + else if ("paste" == action) + { + pasteFromClipboard(); + return; + } + else if ("paste_link" == action) + { + pasteLinkFromClipboard(); + return; + } + else if ("properties" == action) + { + showProperties(); + return; + } + else if ("replaceoutfit" == action) + { + modifyOutfit(false); + return; + } + else if ("addtooutfit" == action) + { + modifyOutfit(true); + return; + } + else if ("show_in_main_panel" == action) + { + LLInventoryPanel::openInventoryPanelAndSetSelection(true, mUUID, true); + return; + } + else if ("cut" == action) + { + cutToClipboard(); + return; + } + else if ("copy" == action) + { + copyToClipboard(); + return; + } + else if ("removefromoutfit" == action) + { + LLInventoryModel* model = getInventoryModel(); + if(!model) return; + LLViewerInventoryCategory* cat = getCategory(); + if(!cat) return; + + LLAppearanceMgr::instance().takeOffOutfit( cat->getLinkedUUID() ); + return; + } + else if ("copyoutfittoclipboard" == action) + { + copyOutfitToClipboard(); + } + else if ("purge" == action) + { + purgeItem(model, mUUID); + return; + } + else if ("restore" == action) + { + restoreItem(); + return; + } + else if ("marketplace_list" == action) + { + if (depth_nesting_in_marketplace(mUUID) == 1) + { + LLUUID version_folder_id = LLMarketplaceData::instance().getVersionFolder(mUUID); + mMessage = ""; + + LLMarketplaceValidator::getInstance()->validateMarketplaceListings( + version_folder_id, + [this](bool result) + { + // todo: might need to ensure bridge/mUUID exists or this will cause crashes + if (!result) + { + LLSD subs; + subs["[ERROR_CODE]"] = mMessage; + LLNotificationsUtil::add("MerchantListingFailed", subs); + } + else + { + LLMarketplaceData::instance().activateListing(mUUID, true); + } + }, + boost::bind(&LLFolderBridge::gatherMessage, this, _1, _2, _3) + ); + } + return; + } + else if ("marketplace_activate" == action) + { + if (depth_nesting_in_marketplace(mUUID) == 2) + { + mMessage = ""; + + LLMarketplaceValidator::getInstance()->validateMarketplaceListings( + mUUID, + [this](bool result) + { + if (!result) + { + LLSD subs; + subs["[ERROR_CODE]"] = mMessage; + LLNotificationsUtil::add("MerchantFolderActivationFailed", subs); + } + else + { + LLInventoryCategory* category = gInventory.getCategory(mUUID); + LLMarketplaceData::instance().setVersionFolder(category->getParentUUID(), mUUID); + } + }, + boost::bind(&LLFolderBridge::gatherMessage, this, _1, _2, _3), + false, + 2); + } + return; + } + else if ("marketplace_unlist" == action) + { + if (depth_nesting_in_marketplace(mUUID) == 1) + { + LLMarketplaceData::instance().activateListing(mUUID,false,1); + } + return; + } + else if ("marketplace_deactivate" == action) + { + if (depth_nesting_in_marketplace(mUUID) == 2) + { + LLInventoryCategory* category = gInventory.getCategory(mUUID); + LLMarketplaceData::instance().setVersionFolder(category->getParentUUID(), LLUUID::null, 1); + } + return; + } + else if ("marketplace_create_listing" == action) + { + mMessage = ""; + + // first run vithout fix_hierarchy, second run with fix_hierarchy + LLMarketplaceValidator::getInstance()->validateMarketplaceListings( + mUUID, + [this](bool result) + { + if (!result) + { + mMessage = ""; + + LLMarketplaceValidator::getInstance()->validateMarketplaceListings( + mUUID, + [this](bool result) + { + if (result) + { + LLNotificationsUtil::add("MerchantForceValidateListing"); + LLMarketplaceData::instance().createListing(mUUID); + } + else + { + LLSD subs; + subs["[ERROR_CODE]"] = mMessage; + LLNotificationsUtil::add("MerchantListingFailed", subs); + } + }, + boost::bind(&LLFolderBridge::gatherMessage, this, _1, _2, _3), + true); + } + else + { + LLMarketplaceData::instance().createListing(mUUID); + } + }, + boost::bind(&LLFolderBridge::gatherMessage, this, _1, _2, _3), + false); + + return; + } + else if ("marketplace_disassociate_listing" == action) + { + LLMarketplaceData::instance().clearListing(mUUID); + return; + } + else if ("marketplace_get_listing" == action) + { + // This is used only to exercise the SLM API but won't be shown to end users + LLMarketplaceData::instance().getListing(mUUID); + return; + } + else if ("marketplace_associate_listing" == action) + { + LLFloaterAssociateListing::show(mUUID); + return; + } + else if ("marketplace_check_listing" == action) + { + LLSD data(mUUID); + LLFloaterReg::showInstance("marketplace_validation", data); + return; + } + else if ("marketplace_edit_listing" == action) + { + std::string url = LLMarketplaceData::instance().getListingURL(mUUID); + if (!url.empty()) + { + LLUrlAction::openURL(url); + } + return; + } +#ifndef LL_RELEASE_FOR_DOWNLOAD + else if ("delete_system_folder" == action) + { + removeSystemFolder(); + } +#endif + else if (("move_to_marketplace_listings" == action) || ("copy_to_marketplace_listings" == action) || ("copy_or_move_to_marketplace_listings" == action)) + { + LLInventoryCategory * cat = gInventory.getCategory(mUUID); + if (!cat) return; + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + move_folder_to_marketplacelistings(cat, marketplacelistings_id, ("move_to_marketplace_listings" != action), (("copy_or_move_to_marketplace_listings" == action))); + } +} + +void LLFolderBridge::gatherMessage(std::string& message, S32 depth, LLError::ELevel log_level) +{ + if (log_level >= LLError::LEVEL_ERROR) + { + if (!mMessage.empty()) + { + // Currently, we do not gather all messages as it creates very long alerts + // Users can get to the whole list of errors on a listing using the "Check for Errors" audit button or "Check listing" right click menu + //mMessage += "\n"; + return; + } + // Take the leading spaces out... + std::string::size_type start = message.find_first_not_of(" "); + // Append the message + mMessage += message.substr(start, message.length() - start); + } +} + +void LLFolderBridge::copyOutfitToClipboard() +{ + std::string text; + + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(mUUID, cat_array, item_array); + + S32 item_count(0); + if( item_array ) + { + item_count = item_array->size(); + } + + if (item_count) + { + for (S32 i = 0; i < item_count;) + { + LLSD uuid =item_array->at(i)->getUUID(); + LLViewerInventoryItem* item = gInventory.getItem(uuid); + + i++; + if (item != NULL) + { + // Append a newline to all but the last line + text += i != item_count ? item->getName() + "\n" : item->getName(); + } + } + } + + LLClipboard::instance().copyToClipboard(utf8str_to_wstring(text),0,text.size()); +} + +void LLFolderBridge::openItem() +{ + LL_DEBUGS() << "LLFolderBridge::openItem()" << LL_ENDL; + + LLInventoryPanel* panel = mInventoryPanel.get(); + if (!panel) + { + return; + } + LLInventoryModel* model = getInventoryModel(); + if (!model) + { + return; + } + if (mUUID.isNull()) + { + return; + } + panel->onFolderOpening(mUUID); + bool fetching_inventory = model->fetchDescendentsOf(mUUID); + // Only change folder type if we have the folder contents. + if (!fetching_inventory) + { + // Disabling this for now, it's causing crash when new items are added to folders + // since folder type may change before new item item has finished processing. + // determineFolderType(); + } +} + +void LLFolderBridge::closeItem() +{ + determineFolderType(); +} + +void LLFolderBridge::determineFolderType() +{ + if (isUpToDate()) + { + LLInventoryModel* model = getInventoryModel(); + LLViewerInventoryCategory* category = model->getCategory(mUUID); + if (category) + { + category->determineFolderType(); + } + } +} + +bool LLFolderBridge::isItemRenameable() const +{ + return get_is_category_renameable(getInventoryModel(), mUUID); +} + +void LLFolderBridge::restoreItem() +{ + LLViewerInventoryCategory* cat; + cat = (LLViewerInventoryCategory*)getCategory(); + if(cat) + { + LLInventoryModel* model = getInventoryModel(); + const LLUUID new_parent = model->findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(cat->getType())); + // do not restamp children on restore + LLInvFVBridge::changeCategoryParent(model, cat, new_parent, false); + } +} + +LLFolderType::EType LLFolderBridge::getPreferredType() const +{ + LLFolderType::EType preferred_type = LLFolderType::FT_NONE; + LLViewerInventoryCategory* cat = getCategory(); + if(cat) + { + preferred_type = cat->getPreferredType(); + } + + return preferred_type; +} + +// Icons for folders are based on the preferred type +LLUIImagePtr LLFolderBridge::getIcon() const +{ + return getFolderIcon(false); +} + +LLUIImagePtr LLFolderBridge::getIconOpen() const +{ + return getFolderIcon(true); +} + +LLUIImagePtr LLFolderBridge::getFolderIcon(bool is_open) const +{ + LLFolderType::EType preferred_type = getPreferredType(); + return LLUI::getUIImage(LLViewerFolderType::lookupIconName(preferred_type, is_open)); +} + +// static : use by LLLinkFolderBridge to get the closed type icons +LLUIImagePtr LLFolderBridge::getIcon(LLFolderType::EType preferred_type) +{ + return LLUI::getUIImage(LLViewerFolderType::lookupIconName(preferred_type, false)); +} + +LLUIImagePtr LLFolderBridge::getIconOverlay() const +{ + if (getInventoryObject() && getInventoryObject()->getIsLinkType()) + { + return LLUI::getUIImage("Inv_Link"); + } + return NULL; +} + +bool LLFolderBridge::renameItem(const std::string& new_name) +{ + + LLScrollOnRenameObserver *observer = new LLScrollOnRenameObserver(mUUID, mRoot); + gInventory.addObserver(observer); + + rename_category(getInventoryModel(), mUUID, new_name); + + // return false because we either notified observers (& therefore + // rebuilt) or we didn't update. + return false; +} + +bool LLFolderBridge::removeItem() +{ + if(!isItemRemovable()) + { + return false; + } + const LLViewerInventoryCategory *cat = getCategory(); + + LLSD payload; + LLSD args; + args["FOLDERNAME"] = cat->getName(); + + LLNotification::Params params("ConfirmDeleteProtectedCategory"); + params.payload(payload).substitutions(args).functor.function(boost::bind(&LLFolderBridge::removeItemResponse, this, _1, _2)); + LLNotifications::instance().forceResponse(params, 0); + return true; +} + + +bool LLFolderBridge::removeSystemFolder() +{ + const LLViewerInventoryCategory *cat = getCategory(); + if (!LLFolderType::lookupIsProtectedType(cat->getPreferredType())) + { + return false; + } + + LLSD payload; + LLSD args; + args["FOLDERNAME"] = cat->getName(); + + LLNotification::Params params("ConfirmDeleteProtectedCategory"); + params.payload(payload).substitutions(args).functor.function(boost::bind(&LLFolderBridge::removeItemResponse, this, _1, _2)); + { + LLNotifications::instance().add(params); + } + return true; +} + +bool LLFolderBridge::removeItemResponse(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotification::getSelectedOption(notification, response); + + // if they choose delete, do it. Otherwise, don't do anything + if(option == 0) + { + // move it to the trash + LLPreview::hide(mUUID); + getInventoryModel()->removeCategory(mUUID); + return true; + } + return false; +} + +//Recursively update the folder's creation date +void LLFolderBridge::updateHierarchyCreationDate(time_t date) +{ + if(getCreationDate() < date) + { + setCreationDate(date); + if(mParent) + { + static_cast(mParent)->updateHierarchyCreationDate(date); + } + } +} + +void LLFolderBridge::pasteFromClipboard() +{ + LLInventoryModel* model = getInventoryModel(); + if (model && isClipboardPasteable()) + { + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + const bool paste_into_marketplacelistings = model->isObjectDescendentOf(mUUID, marketplacelistings_id); + + bool cut_from_marketplacelistings = false; + if (LLClipboard::instance().isCutMode()) + { + //Items are not removed from folder on "cut", so we need update listing folder on "paste" operation + std::vector objects; + LLClipboard::instance().pasteFromClipboard(objects); + for (std::vector::const_iterator iter = objects.begin(); iter != objects.end(); ++iter) + { + const LLUUID& item_id = (*iter); + if(gInventory.isObjectDescendentOf(item_id, marketplacelistings_id) && (LLMarketplaceData::instance().isInActiveFolder(item_id) || + LLMarketplaceData::instance().isListedAndActive(item_id))) + { + cut_from_marketplacelistings = true; + break; + } + } + } + if (cut_from_marketplacelistings || (paste_into_marketplacelistings && !LLMarketplaceData::instance().isListed(mUUID) && LLMarketplaceData::instance().isInActiveFolder(mUUID))) + { + // Prompt the user if pasting in a marketplace active version listing (note that pasting right under the listing folder root doesn't need a prompt) + LLNotificationsUtil::add("ConfirmMerchantActiveChange", LLSD(), LLSD(), boost::bind(&LLFolderBridge::callback_pasteFromClipboard, this, _1, _2)); + } + else + { + // Otherwise just do the paste + perform_pasteFromClipboard(); + } + } +} + +// Callback for pasteFromClipboard if DAMA required... +void LLFolderBridge::callback_pasteFromClipboard(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // YES + { + std::vector objects; + std::set parent_folders; + LLClipboard::instance().pasteFromClipboard(objects); + for (std::vector::const_iterator iter = objects.begin(); iter != objects.end(); ++iter) + { + const LLInventoryObject* obj = gInventory.getObject(*iter); + parent_folders.insert(obj->getParentUUID()); + } + perform_pasteFromClipboard(); + for (std::set::const_iterator iter = parent_folders.begin(); iter != parent_folders.end(); ++iter) + { + gInventory.addChangedMask(LLInventoryObserver::STRUCTURE, *iter); + } + + } +} + +void LLFolderBridge::perform_pasteFromClipboard() +{ + LLInventoryModel* model = getInventoryModel(); + if (model && isClipboardPasteable()) + { + const LLUUID ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + const LLUUID &favorites_id = model->findCategoryUUIDForType(LLFolderType::FT_FAVORITE); + const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + const LLUUID &lost_and_found_id = model->findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); + + const bool move_is_into_current_outfit = (mUUID == current_outfit_id); + const bool move_is_into_my_outfits = (mUUID == my_outifts_id) || model->isObjectDescendentOf(mUUID, my_outifts_id); + const bool move_is_into_outfit = move_is_into_my_outfits || (getCategory() && getCategory()->getPreferredType()==LLFolderType::FT_OUTFIT); + const bool move_is_into_marketplacelistings = model->isObjectDescendentOf(mUUID, marketplacelistings_id); + const bool move_is_into_favorites = (mUUID == favorites_id); + const bool move_is_into_lost_and_found = model->isObjectDescendentOf(mUUID, lost_and_found_id); + + std::vector objects; + LLClipboard::instance().pasteFromClipboard(objects); + + LLPointer cb = NULL; + LLInventoryPanel* panel = mInventoryPanel.get(); + if (panel->getRootFolder()->isSingleFolderMode() && panel->getRootFolderID() == mUUID) + { + cb = new LLPasteIntoFolderCallback(mInventoryPanel); + } + + LLViewerInventoryCategory * dest_folder = getCategory(); + if (move_is_into_marketplacelistings) + { + std::string error_msg; + const LLViewerInventoryCategory * master_folder = model->getFirstDescendantOf(marketplacelistings_id, mUUID); + int index = 0; + for (std::vector::const_iterator iter = objects.begin(); iter != objects.end(); ++iter) + { + const LLUUID& item_id = (*iter); + LLInventoryItem *item = model->getItem(item_id); + LLInventoryCategory *cat = model->getCategory(item_id); + + if (item && !can_move_item_to_marketplace(master_folder, dest_folder, item, error_msg, objects.size() - index, true)) + { + break; + } + if (cat && !can_move_folder_to_marketplace(master_folder, dest_folder, cat, error_msg, objects.size() - index, true, true)) + { + break; + } + ++index; + } + if (!error_msg.empty()) + { + LLSD subs; + subs["[ERROR_CODE]"] = error_msg; + LLNotificationsUtil::add("MerchantPasteFailed", subs); + return; + } + } + else + { + // Check that all items can be moved into that folder : for the moment, only stock folder mismatch is checked + for (std::vector::const_iterator iter = objects.begin(); iter != objects.end(); ++iter) + { + const LLUUID& item_id = (*iter); + LLInventoryItem *item = model->getItem(item_id); + LLInventoryCategory *cat = model->getCategory(item_id); + + if ((item && !dest_folder->acceptItem(item)) || (cat && (dest_folder->getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK))) + { + std::string error_msg = LLTrans::getString("TooltipOutboxMixedStock"); + LLSD subs; + subs["[ERROR_CODE]"] = error_msg; + LLNotificationsUtil::add("StockPasteFailed", subs); + return; + } + } + } + + const LLUUID parent_id(mUUID); + + for (std::vector::const_iterator iter = objects.begin(); + iter != objects.end(); + ++iter) + { + const LLUUID& item_id = (*iter); + + LLInventoryItem *item = model->getItem(item_id); + LLInventoryObject *obj = model->getObject(item_id); + if (obj) + { + + if (move_is_into_lost_and_found) + { + if (LLAssetType::AT_CATEGORY == obj->getType()) + { + return; + } + } + if (move_is_into_outfit) + { + if (!move_is_into_my_outfits && item && can_move_to_outfit(item, move_is_into_current_outfit)) + { + dropToOutfit(item, move_is_into_current_outfit, cb); + } + else if (move_is_into_my_outfits && LLAssetType::AT_CATEGORY == obj->getType()) + { + LLInventoryCategory* cat = model->getCategory(item_id); + U32 max_items_to_wear = gSavedSettings.getU32("WearFolderLimit"); + if (cat && can_move_to_my_outfits(model, cat, max_items_to_wear)) + { + dropToMyOutfits(cat, cb); + } + else + { + LLNotificationsUtil::add("MyOutfitsPasteFailed"); + } + } + else + { + LLNotificationsUtil::add("MyOutfitsPasteFailed"); + } + } + else if (move_is_into_current_outfit) + { + if (item && can_move_to_outfit(item, move_is_into_current_outfit)) + { + dropToOutfit(item, move_is_into_current_outfit, cb); + } + else + { + LLNotificationsUtil::add("MyOutfitsPasteFailed"); + } + } + else if (move_is_into_favorites) + { + if (item && can_move_to_landmarks(item)) + { + if (LLClipboard::instance().isCutMode()) + { + LLViewerInventoryItem* viitem = dynamic_cast(item); + llassert(viitem); + if (viitem) + { + //changeItemParent() implicity calls dirtyFilter + changeItemParent(model, viitem, parent_id, false); + if (cb) cb->fire(item_id); + } + } + else + { + dropToFavorites(item, cb); + } + } + } + else if (LLClipboard::instance().isCutMode()) + { + // Do a move to "paste" a "cut" + // move_inventory_item() is not enough, as we have to update inventory locally too + if (LLAssetType::AT_CATEGORY == obj->getType()) + { + LLViewerInventoryCategory* vicat = (LLViewerInventoryCategory *) model->getCategory(item_id); + llassert(vicat); + if (vicat) + { + // Clear the cut folder from the marketplace if it is a listing folder + if (LLMarketplaceData::instance().isListed(item_id)) + { + LLMarketplaceData::instance().clearListing(item_id); + } + if (move_is_into_marketplacelistings) + { + move_folder_to_marketplacelistings(vicat, parent_id); + } + else + { + //changeCategoryParent() implicity calls dirtyFilter + changeCategoryParent(model, vicat, parent_id, false); + } + if (cb) cb->fire(item_id); + } + } + else + { + LLViewerInventoryItem* viitem = dynamic_cast(item); + llassert(viitem); + if (viitem) + { + if (move_is_into_marketplacelistings) + { + if (!move_item_to_marketplacelistings(viitem, parent_id)) + { + // Stop pasting into the marketplace as soon as we get an error + break; + } + } + else + { + //changeItemParent() implicity calls dirtyFilter + changeItemParent(model, viitem, parent_id, false); + } + if (cb) cb->fire(item_id); + } + } + } + else + { + // Do a "copy" to "paste" a regular copy clipboard + if (LLAssetType::AT_CATEGORY == obj->getType()) + { + LLViewerInventoryCategory* vicat = (LLViewerInventoryCategory *) model->getCategory(item_id); + llassert(vicat); + if (vicat) + { + if (move_is_into_marketplacelistings) + { + move_folder_to_marketplacelistings(vicat, parent_id, true); + } + else + { + copy_inventory_category(model, vicat, parent_id); + } + if (cb) cb->fire(item_id); + } + } + else + { + LLViewerInventoryItem* viitem = dynamic_cast(item); + llassert(viitem); + if (viitem) + { + if (move_is_into_marketplacelistings) + { + if (!move_item_to_marketplacelistings(viitem, parent_id, true)) + { + // Stop pasting into the marketplace as soon as we get an error + break; + } + if (cb) cb->fire(item_id); + } + else if (item->getIsLinkType()) + { + link_inventory_object(parent_id, + item_id, + cb); + } + else + { + copy_inventory_item( + gAgent.getID(), + item->getPermissions().getOwner(), + item->getUUID(), + parent_id, + std::string(), + cb); + } + } + } + } + } + } + // Change mode to paste for next paste + LLClipboard::instance().setCutMode(false); + } +} + +void LLFolderBridge::pasteLinkFromClipboard() +{ + LLInventoryModel* model = getInventoryModel(); + if(model) + { + const LLUUID ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + + const bool move_is_into_current_outfit = (mUUID == current_outfit_id); + const bool move_is_into_my_outfits = (mUUID == my_outifts_id) || model->isObjectDescendentOf(mUUID, my_outifts_id); + const bool move_is_into_outfit = move_is_into_my_outfits || (getCategory() && getCategory()->getPreferredType()==LLFolderType::FT_OUTFIT); + const bool move_is_into_marketplacelistings = model->isObjectDescendentOf(mUUID, marketplacelistings_id); + + if (move_is_into_marketplacelistings) + { + // Notify user of failure somehow -- play error sound? modal dialog? + return; + } + + const LLUUID parent_id(mUUID); + + std::vector objects; + LLClipboard::instance().pasteFromClipboard(objects); + + LLPointer cb = NULL; + LLInventoryPanel* panel = mInventoryPanel.get(); + if (panel->getRootFolder()->isSingleFolderMode()) + { + cb = new LLPasteIntoFolderCallback(mInventoryPanel); + } + + for (std::vector::const_iterator iter = objects.begin(); + iter != objects.end(); + ++iter) + { + const LLUUID &object_id = (*iter); + if (move_is_into_current_outfit || move_is_into_outfit) + { + LLInventoryItem *item = model->getItem(object_id); + if (item && can_move_to_outfit(item, move_is_into_current_outfit)) + { + dropToOutfit(item, move_is_into_current_outfit, cb); + } + } + else if (LLConstPointer obj = model->getObject(object_id)) + { + link_inventory_object(parent_id, obj, cb); + } + } + // Change mode to paste for next paste + LLClipboard::instance().setCutMode(false); + } +} + +void LLFolderBridge::staticFolderOptionsMenu() +{ + LLFolderBridge* selfp = sSelf.get(); + + if (selfp && selfp->mRoot) + { + selfp->mRoot->updateMenu(); + } +} + +bool LLFolderBridge::checkFolderForContentsOfType(LLInventoryModel* model, LLInventoryCollectFunctor& is_type) +{ + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + model->collectDescendentsIf(mUUID, + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH, + is_type); + return !item_array.empty(); +} + +void LLFolderBridge::buildContextMenuOptions(U32 flags, menuentry_vec_t& items, menuentry_vec_t& disabled_items) +{ + LLInventoryModel* model = getInventoryModel(); + llassert(model != NULL); + + const LLUUID &trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); + const LLUUID &lost_and_found_id = model->findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); + const LLUUID &favorites = model->findCategoryUUIDForType(LLFolderType::FT_FAVORITE); + const LLUUID &marketplace_listings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + const LLUUID &outfits_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + + if (outfits_id == mUUID) + { + items.push_back(std::string("New Outfit")); + } + + if (lost_and_found_id == mUUID) + { + // This is the lost+found folder. + items.push_back(std::string("Empty Lost And Found")); + + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(mUUID, cat_array, item_array); + // Enable Empty menu item only when there is something to act upon. + if (0 == cat_array->size() && 0 == item_array->size()) + { + disabled_items.push_back(std::string("Empty Lost And Found")); + } + + disabled_items.push_back(std::string("New Folder")); + disabled_items.push_back(std::string("upload_def")); + disabled_items.push_back(std::string("create_new")); + } + if (favorites == mUUID) + { + disabled_items.push_back(std::string("New Folder")); + } + if (isMarketplaceListingsFolder()) + { + addMarketplaceContextMenuOptions(flags, items, disabled_items); + if (LLMarketplaceData::instance().isUpdating(mUUID)) + { + disabled_items.push_back(std::string("New Folder")); + disabled_items.push_back(std::string("Rename")); + disabled_items.push_back(std::string("Cut")); + disabled_items.push_back(std::string("Copy")); + disabled_items.push_back(std::string("Paste")); + disabled_items.push_back(std::string("Delete")); + } + } + if (getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK) + { + disabled_items.push_back(std::string("New Folder")); + disabled_items.push_back(std::string("upload_def")); + disabled_items.push_back(std::string("create_new")); + } + if (marketplace_listings_id == mUUID) + { + disabled_items.push_back(std::string("New Folder")); + disabled_items.push_back(std::string("Rename")); + disabled_items.push_back(std::string("Cut")); + disabled_items.push_back(std::string("Delete")); + } + + if (isPanelActive("Favorite Items")) + { + disabled_items.push_back(std::string("Delete")); + } + if(trash_id == mUUID) + { + bool is_recent_panel = isPanelActive("Recent Items"); + + // This is the trash. + items.push_back(std::string("Empty Trash")); + + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(mUUID, cat_array, item_array); + LLViewerInventoryCategory *trash = getCategory(); + // Enable Empty menu item only when there is something to act upon. + // Also don't enable menu if folder isn't fully fetched + if ((0 == cat_array->size() && 0 == item_array->size()) + || is_recent_panel + || !trash + || trash->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN + || trash->getDescendentCount() == LLViewerInventoryCategory::VERSION_UNKNOWN + || gAgentAvatarp->hasAttachmentsInTrash()) + { + disabled_items.push_back(std::string("Empty Trash")); + } + + items.push_back(std::string("thumbnail")); + } + else if(isItemInTrash()) + { + // This is a folder in the trash. + items.clear(); // clear any items that used to exist + addTrashContextMenuOptions(items, disabled_items); + } + else if(isAgentInventory()) // do not allow creating in library + { + LLViewerInventoryCategory *cat = getCategory(); + // BAP removed protected check to re-enable standard ops in untyped folders. + // Not sure what the right thing is to do here. + if (!isCOFFolder() && cat && (cat->getPreferredType() != LLFolderType::FT_OUTFIT)) + { + if (!isInboxFolder() // don't allow creation in inbox + && outfits_id != mUUID) + { + bool menu_items_added = false; + // Do not allow to create 2-level subfolder in the Calling Card/Friends folder. EXT-694. + if (!LLFriendCardsManager::instance().isCategoryInFriendFolder(cat)) + { + items.push_back(std::string("New Folder")); + menu_items_added = true; + } + if (!isMarketplaceListingsFolder()) + { + items.push_back(std::string("upload_def")); + items.push_back(std::string("create_new")); + items.push_back(std::string("New Script")); + items.push_back(std::string("New Note")); + items.push_back(std::string("New Gesture")); + items.push_back(std::string("New Material")); + items.push_back(std::string("New Clothes")); + items.push_back(std::string("New Body Parts")); + items.push_back(std::string("New Settings")); + if (!LLEnvironment::instance().isInventoryEnabled()) + { + disabled_items.push_back("New Settings"); + } + } + else + { + items.push_back(std::string("New Listing Folder")); + } + if (menu_items_added) + { + items.push_back(std::string("Create Separator")); + } + } + getClipboardEntries(false, items, disabled_items, flags); + } + else + { + // Want some but not all of the items from getClipboardEntries for outfits. + if (cat && (cat->getPreferredType() == LLFolderType::FT_OUTFIT)) + { + items.push_back(std::string("Rename")); + items.push_back(std::string("thumbnail")); + + addDeleteContextMenuOptions(items, disabled_items); + // EXT-4030: disallow deletion of currently worn outfit + const LLViewerInventoryItem *base_outfit_link = LLAppearanceMgr::instance().getBaseOutfitLink(); + if (base_outfit_link && (cat == base_outfit_link->getLinkedCategory())) + { + disabled_items.push_back(std::string("Delete")); + } + } + } + + if (model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT) == mUUID) + { + items.push_back(std::string("Copy outfit list to clipboard")); + addOpenFolderMenuOptions(flags, items); + } + + //Added by aura to force inventory pull on right-click to display folder options correctly. 07-17-06 + mCallingCards = mWearables = false; + + LLIsType is_callingcard(LLAssetType::AT_CALLINGCARD); + if (checkFolderForContentsOfType(model, is_callingcard)) + { + mCallingCards=true; + } + + LLFindWearables is_wearable; + LLIsType is_object( LLAssetType::AT_OBJECT ); + LLIsType is_gesture( LLAssetType::AT_GESTURE ); + + if (checkFolderForContentsOfType(model, is_wearable) || + checkFolderForContentsOfType(model, is_object) || + checkFolderForContentsOfType(model, is_gesture) ) + { + mWearables=true; + } + } + else + { + // Mark wearables and allow copy from library + LLInventoryModel* model = getInventoryModel(); + if(!model) return; + const LLInventoryCategory* category = model->getCategory(mUUID); + if (!category) return; + LLFolderType::EType type = category->getPreferredType(); + const bool is_system_folder = LLFolderType::lookupIsProtectedType(type); + + LLFindWearables is_wearable; + LLIsType is_object(LLAssetType::AT_OBJECT); + LLIsType is_gesture(LLAssetType::AT_GESTURE); + + if (checkFolderForContentsOfType(model, is_wearable) || + checkFolderForContentsOfType(model, is_object) || + checkFolderForContentsOfType(model, is_gesture)) + { + mWearables = true; + } + + if (!is_system_folder) + { + items.push_back(std::string("Copy")); + if (!isItemCopyable()) + { + // For some reason there are items in library that can't be copied directly + disabled_items.push_back(std::string("Copy")); + } + } + } + + // Preemptively disable system folder removal if more than one item selected. + if ((flags & FIRST_SELECTED_ITEM) == 0) + { + disabled_items.push_back(std::string("Delete System Folder")); + } + + if (isAgentInventory() && !isMarketplaceListingsFolder()) + { + items.push_back(std::string("Share")); + if (!canShare()) + { + disabled_items.push_back(std::string("Share")); + } + } + + + + // Add menu items that are dependent on the contents of the folder. + LLViewerInventoryCategory* category = (LLViewerInventoryCategory *) model->getCategory(mUUID); + if (category && (marketplace_listings_id != mUUID)) + { + uuid_vec_t folders; + folders.push_back(category->getUUID()); + + sSelf = getHandle(); + LLRightClickInventoryFetchDescendentsObserver* fetch = new LLRightClickInventoryFetchDescendentsObserver(folders); + fetch->startFetch(); + if (fetch->isFinished()) + { + // Do not call execute() or done() here as if the folder is here, there's likely no point drilling down + // This saves lots of time as buildContextMenu() is called a lot + delete fetch; + buildContextMenuFolderOptions(flags, items, disabled_items); + } + else + { + // it's all on its way - add an observer, and the inventory will call done for us when everything is here. + gInventory.addObserver(fetch); + } + } +} + +void LLFolderBridge::buildContextMenuFolderOptions(U32 flags, menuentry_vec_t& items, menuentry_vec_t& disabled_items) +{ + // Build folder specific options back up + LLInventoryModel* model = getInventoryModel(); + if(!model) return; + + const LLInventoryCategory* category = model->getCategory(mUUID); + if(!category) return; + + const LLUUID trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); + if ((trash_id == mUUID) || isItemInTrash()) + { + addOpenFolderMenuOptions(flags, items); + return; + } + + if (!canMenuDelete()) + { + disabled_items.push_back(std::string("Delete")); + } + if (isMarketplaceListingsFolder()) return; + + LLFolderType::EType type = category->getPreferredType(); + const bool is_system_folder = LLFolderType::lookupIsProtectedType(type); + // BAP change once we're no longer treating regular categories as ensembles. + const bool is_agent_inventory = isAgentInventory(); + + // Only enable calling-card related options for non-system folders. + if (!is_system_folder && is_agent_inventory && (mRoot != NULL)) + { + LLIsType is_callingcard(LLAssetType::AT_CALLINGCARD); + if (mCallingCards || checkFolderForContentsOfType(model, is_callingcard)) + { + items.push_back(std::string("Calling Card Separator")); + items.push_back(std::string("Conference Chat Folder")); + items.push_back(std::string("IM All Contacts In Folder")); + } + + if (((flags & ITEM_IN_MULTI_SELECTION) == 0) && hasChildren() && (type != LLFolderType::FT_OUTFIT)) + { + items.push_back(std::string("Ungroup folder items")); + } + } + else + { + disabled_items.push_back(std::string("New folder from selected")); + } + + //skip the rest options in single-folder mode + if (mRoot == NULL) + { + return; + } + + addOpenFolderMenuOptions(flags, items); + +#ifndef LL_RELEASE_FOR_DOWNLOAD + if (LLFolderType::lookupIsProtectedType(type) && is_agent_inventory) + { + items.push_back(std::string("Delete System Folder")); + } +#endif + + // wearables related functionality for folders. + //is_wearable + LLFindWearables is_wearable; + LLIsType is_object( LLAssetType::AT_OBJECT ); + LLIsType is_gesture( LLAssetType::AT_GESTURE ); + + if (mWearables || + checkFolderForContentsOfType(model, is_wearable) || + checkFolderForContentsOfType(model, is_object) || + checkFolderForContentsOfType(model, is_gesture) ) + { + // Only enable add/replace outfit for non-system folders. + if (!is_system_folder) + { + // Adding an outfit onto another (versus replacing) doesn't make sense. + if (type != LLFolderType::FT_OUTFIT) + { + items.push_back(std::string("Add To Outfit")); + if (!LLAppearanceMgr::instance().getCanAddToCOF(mUUID)) + { + disabled_items.push_back(std::string("Add To Outfit")); + } + } + + items.push_back(std::string("Replace Outfit")); + if (!LLAppearanceMgr::instance().getCanReplaceCOF(mUUID)) + { + disabled_items.push_back(std::string("Replace Outfit")); + } + } + if (is_agent_inventory) + { + items.push_back(std::string("Folder Wearables Separator")); + // Note: If user tries to unwear "My Inventory", it's going to deactivate everything including gestures + // Might be safer to disable this for "My Inventory" + items.push_back(std::string("Remove From Outfit")); + if (type != LLFolderType::FT_ROOT_INVENTORY // Unless COF is empty, whih shouldn't be, warrantied to have worn items + && !LLAppearanceMgr::getCanRemoveFromCOF(mUUID)) // expensive from root! + { + disabled_items.push_back(std::string("Remove From Outfit")); + } + } + items.push_back(std::string("Outfit Separator")); + + } +} + +// Flags unused +void LLFolderBridge::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + sSelf.markDead(); + + // fetch contents of this folder, as context menu can depend on contents + // still, user would have to open context menu again to see the changes + gInventory.fetchDescendentsOf(getUUID()); + + + menuentry_vec_t items; + menuentry_vec_t disabled_items; + + LL_DEBUGS() << "LLFolderBridge::buildContextMenu()" << LL_ENDL; + + LLInventoryModel* model = getInventoryModel(); + if(!model) return; + + buildContextMenuOptions(flags, items, disabled_items); + hide_context_entries(menu, items, disabled_items); + + // Reposition the menu, in case we're adding items to an existing menu. + menu.needsArrange(); + menu.arrangeAndClear(); +} + +void LLFolderBridge::addOpenFolderMenuOptions(U32 flags, menuentry_vec_t& items) +{ + if ((flags & ITEM_IN_MULTI_SELECTION) == 0) + { + items.push_back(std::string("open_in_new_window")); + items.push_back(std::string("Open Folder Separator")); + items.push_back(std::string("Copy Separator")); + if(isPanelActive("comb_single_folder_inv")) + { + items.push_back(std::string("open_in_current_window")); + } + } +} + +bool LLFolderBridge::hasChildren() const +{ + LLInventoryModel* model = getInventoryModel(); + if(!model) return false; + LLInventoryModel::EHasChildren has_children; + has_children = gInventory.categoryHasChildren(mUUID); + return has_children != LLInventoryModel::CHILDREN_NO; +} + +bool LLFolderBridge::dragOrDrop(MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + std::string& tooltip_msg) +{ + LLInventoryItem* inv_item = (LLInventoryItem*)cargo_data; + + static LLPointer drop_cb = NULL; + LLInventoryPanel* panel = mInventoryPanel.get(); + LLToolDragAndDrop* drop_tool = LLToolDragAndDrop::getInstance(); + if (drop + && panel->getRootFolder()->isSingleFolderMode() + && panel->getRootFolderID() == mUUID + && drop_tool->getCargoIndex() == 0) + { + drop_cb = new LLPasteIntoFolderCallback(mInventoryPanel); + } + + + //LL_INFOS() << "LLFolderBridge::dragOrDrop()" << LL_ENDL; + bool accept = false; + switch(cargo_type) + { + case DAD_TEXTURE: + case DAD_SOUND: + case DAD_CALLINGCARD: + case DAD_LANDMARK: + case DAD_SCRIPT: + case DAD_CLOTHING: + case DAD_OBJECT: + case DAD_NOTECARD: + case DAD_BODYPART: + case DAD_ANIMATION: + case DAD_GESTURE: + case DAD_MESH: + case DAD_SETTINGS: + case DAD_MATERIAL: + accept = dragItemIntoFolder(inv_item, drop, tooltip_msg, true, drop_cb); + break; + case DAD_LINK: + // DAD_LINK type might mean one of two asset types: AT_LINK or AT_LINK_FOLDER. + // If we have an item of AT_LINK_FOLDER type we should process the linked + // category being dragged or dropped into folder. + if (inv_item && LLAssetType::AT_LINK_FOLDER == inv_item->getActualType()) + { + LLInventoryCategory* linked_category = gInventory.getCategory(inv_item->getLinkedUUID()); + if (linked_category) + { + accept = dragCategoryIntoFolder((LLInventoryCategory*)linked_category, drop, tooltip_msg, true, true, drop_cb); + } + } + else + { + accept = dragItemIntoFolder(inv_item, drop, tooltip_msg, true, drop_cb); + } + break; + case DAD_CATEGORY: + if (LLFriendCardsManager::instance().isAnyFriendCategory(mUUID)) + { + accept = false; + } + else + { + accept = dragCategoryIntoFolder((LLInventoryCategory*)cargo_data, drop, tooltip_msg, false, true, drop_cb); + } + break; + case DAD_ROOT_CATEGORY: + case DAD_NONE: + break; + default: + LL_WARNS() << "Unhandled cargo type for drag&drop " << cargo_type << LL_ENDL; + break; + } + + if (!drop || drop_tool->getCargoIndex() + 1 == drop_tool->getCargoCount()) + { + drop_cb = NULL; + } + return accept; +} + +LLViewerInventoryCategory* LLFolderBridge::getCategory() const +{ + LLViewerInventoryCategory* cat = NULL; + LLInventoryModel* model = getInventoryModel(); + if(model) + { + cat = (LLViewerInventoryCategory*)model->getCategory(mUUID); + } + return cat; +} + + +// static +void LLFolderBridge::pasteClipboard(void* user_data) +{ + LLFolderBridge* self = (LLFolderBridge*)user_data; + if(self) self->pasteFromClipboard(); +} + +void LLFolderBridge::createNewShirt(void* user_data) +{ + LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_SHIRT); +} + +void LLFolderBridge::createNewPants(void* user_data) +{ + LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_PANTS); +} + +void LLFolderBridge::createNewShoes(void* user_data) +{ + LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_SHOES); +} + +void LLFolderBridge::createNewSocks(void* user_data) +{ + LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_SOCKS); +} + +void LLFolderBridge::createNewJacket(void* user_data) +{ + LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_JACKET); +} + +void LLFolderBridge::createNewSkirt(void* user_data) +{ + LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_SKIRT); +} + +void LLFolderBridge::createNewGloves(void* user_data) +{ + LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_GLOVES); +} + +void LLFolderBridge::createNewUndershirt(void* user_data) +{ + LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_UNDERSHIRT); +} + +void LLFolderBridge::createNewUnderpants(void* user_data) +{ + LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_UNDERPANTS); +} + +void LLFolderBridge::createNewShape(void* user_data) +{ + LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_SHAPE); +} + +void LLFolderBridge::createNewSkin(void* user_data) +{ + LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_SKIN); +} + +void LLFolderBridge::createNewHair(void* user_data) +{ + LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_HAIR); +} + +void LLFolderBridge::createNewEyes(void* user_data) +{ + LLFolderBridge::createWearable((LLFolderBridge*)user_data, LLWearableType::WT_EYES); +} + +EInventorySortGroup LLFolderBridge::getSortGroup() const +{ + LLFolderType::EType preferred_type = getPreferredType(); + + if (preferred_type == LLFolderType::FT_TRASH) + { + return SG_TRASH_FOLDER; + } + + if(LLFolderType::lookupIsProtectedType(preferred_type)) + { + return SG_SYSTEM_FOLDER; + } + + return SG_NORMAL_FOLDER; +} + + +// static +void LLFolderBridge::createWearable(LLFolderBridge* bridge, LLWearableType::EType type) +{ + if(!bridge) return; + LLUUID parent_id = bridge->getUUID(); + LLAgentWearables::createWearable(type, false, parent_id); +} + +void LLFolderBridge::modifyOutfit(bool append) +{ + LLInventoryModel* model = getInventoryModel(); + if(!model) return; + LLViewerInventoryCategory* cat = getCategory(); + if(!cat) return; + + // checking amount of items to wear + U32 max_items = gSavedSettings.getU32("WearFolderLimit"); + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLFindWearablesEx not_worn(/*is_worn=*/ false, /*include_body_parts=*/ false); + gInventory.collectDescendentsIf(cat->getUUID(), + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + not_worn); + + if (items.size() > max_items) + { + LLSD args; + args["AMOUNT"] = llformat("%d", max_items); + LLNotificationsUtil::add("TooManyWearables", args); + return; + } + + if (isAgentInventory()) + { + LLAppearanceMgr::instance().wearInventoryCategory(cat, false, append); + } + else + { + // Library, we need to copy content first + LLAppearanceMgr::instance().wearInventoryCategory(cat, true, append); + } +} + +//static +void LLFolderBridge::onCanDeleteIdle(void* user_data) +{ + LLFolderBridge* self = (LLFolderBridge*)user_data; + + // we really need proper onidle mechanics that returns available time + const F32 EXPIRY_SECONDS = 0.008f; + LLTimer timer; + timer.setTimerExpirySec(EXPIRY_SECONDS); + + LLInventoryModel* model = self->getInventoryModel(); + if (model) + { + switch (self->mCanDeleteFolderState) + { + case CDS_INIT_FOLDER_CHECK: + // Can still be expensive, split it further? + model->collectDescendents( + self->mUUID, + self->mFoldersToCheck, + self->mItemsToCheck, + LLInventoryModel::EXCLUDE_TRASH); + self->mCanDeleteFolderState = CDS_PROCESSING_ITEMS; + break; + + case CDS_PROCESSING_ITEMS: + while (!timer.hasExpired() && !self->mItemsToCheck.empty()) + { + LLViewerInventoryItem* item = self->mItemsToCheck.back().get(); + if (item) + { + if (LLAppearanceMgr::instance().getIsProtectedCOFItem(item)) + { + if (get_is_item_worn(item)) + { + // At the moment we disable 'cut' if category has worn items (do we need to?) + // but allow 'delete' to happen since it will prompt user to detach + self->mCanCut = false; + } + } + + if (!item->getIsLinkType() && get_is_item_worn(item)) + { + self->mCanCut = false; + } + } + self->mItemsToCheck.pop_back(); + } + self->mCanDeleteFolderState = CDS_PROCESSING_FOLDERS; + break; + case CDS_PROCESSING_FOLDERS: + { + const LLViewerInventoryItem* base_outfit_link = LLAppearanceMgr::instance().getBaseOutfitLink(); + LLViewerInventoryCategory* outfit_linked_category = base_outfit_link ? base_outfit_link->getLinkedCategory() : nullptr; + + while (!timer.hasExpired() && !self->mFoldersToCheck.empty()) + { + LLViewerInventoryCategory* cat = self->mFoldersToCheck.back().get(); + if (cat) + { + const LLFolderType::EType folder_type = cat->getPreferredType(); + if (LLFolderType::lookupIsProtectedType(folder_type)) + { + self->mCanCut = false; + self->mCanDelete = false; + self->completeDeleteProcessing(); + break; + } + + // Can't delete the outfit that is currently being worn. + if (folder_type == LLFolderType::FT_OUTFIT) + { + if (cat == outfit_linked_category) + { + self->mCanCut = false; + self->mCanDelete = false; + self->completeDeleteProcessing(); + break; + } + } + } + self->mFoldersToCheck.pop_back(); + } + } + self->mCanDeleteFolderState = CDS_DONE; + break; + case CDS_DONE: + self->completeDeleteProcessing(); + break; + } + } +} + +bool LLFolderBridge::canMenuDelete() +{ + LLInventoryModel* model = getInventoryModel(); + if (!model) return false; + LLViewerInventoryCategory* category = (LLViewerInventoryCategory*)model->getCategory(mUUID); + if (!category) + { + return false; + } + + S32 version = category->getVersion(); + if (mLastCheckedVersion == version) + { + return mCanDelete; + } + + initCanDeleteProcessing(model, version); + return false; +} + +bool LLFolderBridge::canMenuCut() +{ + LLInventoryModel* model = getInventoryModel(); + if (!model) return false; + LLViewerInventoryCategory* category = (LLViewerInventoryCategory*)model->getCategory(mUUID); + if (!category) + { + return false; + } + + S32 version = category->getVersion(); + if (mLastCheckedVersion == version) + { + return mCanCut; + } + + initCanDeleteProcessing(model, version); + return false; +} + +void LLFolderBridge::initCanDeleteProcessing(LLInventoryModel* model, S32 version) +{ + if (mCanDeleteFolderState == CDS_DONE + || mInProgressVersion != version) + { + if (get_is_category_removable(model, mUUID)) + { + // init recursive check of content + mInProgressVersion = version; + mCanCut = true; + mCanDelete = true; + mCanDeleteFolderState = CDS_INIT_FOLDER_CHECK; + mFoldersToCheck.clear(); + mItemsToCheck.clear(); + gIdleCallbacks.addFunction(onCanDeleteIdle, this); + } + else + { + // no check needed + mCanDelete = false; + mCanCut = false; + mLastCheckedVersion = version; + mCanDeleteFolderState = CDS_DONE; + mFoldersToCheck.clear(); + mItemsToCheck.clear(); + } + } +} + +void LLFolderBridge::completeDeleteProcessing() +{ + LLInventoryModel* model = getInventoryModel(); + LLViewerInventoryCategory* category = model ? (LLViewerInventoryCategory*)model->getCategory(mUUID) : nullptr; + if (model && category && category->getVersion() == mInProgressVersion) + { + mLastCheckedVersion = mInProgressVersion; + mCanDeleteFolderState = CDS_DONE; + gIdleCallbacks.deleteFunction(onCanDeleteIdle, this); + } + else + { + mCanDelete = false; + mCanCut = false; + mLastCheckedVersion = LLViewerInventoryCategory::VERSION_UNKNOWN; + mCanDeleteFolderState = CDS_DONE; + } + + if (mRoot) + { + mRoot->updateMenu(); + } +} + + +// +=================================================+ +// | LLMarketplaceFolderBridge | +// +=================================================+ + +// LLMarketplaceFolderBridge is a specialized LLFolderBridge for use in Marketplace Inventory panels +LLMarketplaceFolderBridge::LLMarketplaceFolderBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid) : +LLFolderBridge(inventory, root, uuid) +{ + m_depth = depth_nesting_in_marketplace(mUUID); + m_stockCountCache = COMPUTE_STOCK_NOT_EVALUATED; +} + +LLUIImagePtr LLMarketplaceFolderBridge::getIcon() const +{ + return getMarketplaceFolderIcon(false); +} + +LLUIImagePtr LLMarketplaceFolderBridge::getIconOpen() const +{ + return getMarketplaceFolderIcon(true); +} + +LLUIImagePtr LLMarketplaceFolderBridge::getMarketplaceFolderIcon(bool is_open) const +{ + LLFolderType::EType preferred_type = getPreferredType(); + if (!LLMarketplaceData::instance().isUpdating(getUUID())) + { + // Skip computation (expensive) if we're waiting for updates. Use the old value in that case. + m_depth = depth_nesting_in_marketplace(mUUID); + } + if ((preferred_type == LLFolderType::FT_NONE) && (m_depth == 2)) + { + // We override the type when in the marketplace listings folder and only for version folder + preferred_type = LLFolderType::FT_MARKETPLACE_VERSION; + } + return LLUI::getUIImage(LLViewerFolderType::lookupIconName(preferred_type, is_open)); +} + +std::string LLMarketplaceFolderBridge::getLabelSuffix() const +{ + if (mIsLoading && mTimeSinceRequestStart.getElapsedTimeF32() >= FOLDER_LOADING_MESSAGE_DELAY) + { + return llformat(" ( %s ) ", LLTrans::getString("LoadingData").c_str()); + } + + std::string suffix = ""; + // Listing folder case + if (LLMarketplaceData::instance().isListed(getUUID())) + { + suffix = llformat("%d",LLMarketplaceData::instance().getListingID(getUUID())); + if (suffix.empty()) + { + suffix = LLTrans::getString("MarketplaceNoID"); + } + suffix = " (" + suffix + ")"; + if (LLMarketplaceData::instance().getActivationState(getUUID())) + { + suffix += " (" + LLTrans::getString("MarketplaceLive") + ")"; + } + } + // Version folder case + else if (LLMarketplaceData::instance().isVersionFolder(getUUID())) + { + suffix += " (" + LLTrans::getString("MarketplaceActive") + ")"; + } + // Add stock amount + bool updating = LLMarketplaceData::instance().isUpdating(getUUID()); + if (!updating) + { + // Skip computation (expensive) if we're waiting for update anyway. Use the old value in that case. + m_stockCountCache = compute_stock_count(getUUID()); + } + if (m_stockCountCache == 0) + { + suffix += " (" + LLTrans::getString("MarketplaceNoStock") + ")"; + } + else if (m_stockCountCache != COMPUTE_STOCK_INFINITE) + { + if (getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK) + { + suffix += " (" + LLTrans::getString("MarketplaceStock"); + } + else + { + suffix += " (" + LLTrans::getString("MarketplaceMax"); + } + if (m_stockCountCache == COMPUTE_STOCK_NOT_EVALUATED) + { + suffix += "=" + LLTrans::getString("MarketplaceUpdating") + ")"; + } + else + { + suffix += "=" + llformat("%d", m_stockCountCache) + ")"; + } + } + // Add updating suffix + if (updating) + { + suffix += " (" + LLTrans::getString("MarketplaceUpdating") + ")"; + } + return LLInvFVBridge::getLabelSuffix() + suffix; +} + +LLFontGL::StyleFlags LLMarketplaceFolderBridge::getLabelStyle() const +{ + return (LLMarketplaceData::instance().getActivationState(getUUID()) ? LLFontGL::BOLD : LLFontGL::NORMAL); +} + + + + +// helper stuff +bool move_task_inventory_callback(const LLSD& notification, const LLSD& response, std::shared_ptr move_inv) +{ + LLFloaterOpenObject::LLCatAndWear* cat_and_wear = (LLFloaterOpenObject::LLCatAndWear* )move_inv->mUserData; + LLViewerObject* object = gObjectList.findObject(move_inv->mObjectID); + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + + if(option == 0 && object) + { + if (cat_and_wear && cat_and_wear->mWear) // && !cat_and_wear->mFolderResponded) + { + LLInventoryObject::object_list_t inventory_objects; + object->getInventoryContents(inventory_objects); + int contents_count = inventory_objects.size(); + LLInventoryCopyAndWearObserver* inventoryObserver = new LLInventoryCopyAndWearObserver(cat_and_wear->mCatID, contents_count, cat_and_wear->mFolderResponded, + cat_and_wear->mReplace); + + gInventory.addObserver(inventoryObserver); + } + + two_uuids_list_t::iterator move_it; + for (move_it = move_inv->mMoveList.begin(); + move_it != move_inv->mMoveList.end(); + ++move_it) + { + object->moveInventory(move_it->first, move_it->second); + } + + // update the UI. + dialog_refresh_all(); + } + + if (move_inv->mCallback) + { + move_inv->mCallback(option, move_inv->mUserData, move_inv.get()); + } + + move_inv.reset(); //since notification will persist + return false; +} + +void drop_to_favorites_cb(const LLUUID& id, LLPointer cb1, LLPointer cb2) +{ + cb1->fire(id); + cb2->fire(id); +} + +LLFolderBridge::LLFolderBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid) + : LLInvFVBridge(inventory, root, uuid) + , mCallingCards(false) + , mWearables(false) + , mIsLoading(false) + , mShowDescendantsCount(false) + , mCanDeleteFolderState(CDS_DONE) + , mLastCheckedVersion(S32_MIN) + , mInProgressVersion(S32_MIN) + , mCanDelete(false) + , mCanCut(false) +{ +} + +LLFolderBridge::~LLFolderBridge() +{ + gIdleCallbacks.deleteFunction(onCanDeleteIdle, this); +} + +void LLFolderBridge::dropToFavorites(LLInventoryItem* inv_item, LLPointer cb) +{ + // use callback to rearrange favorite landmarks after adding + // to have new one placed before target (on which it was dropped). See EXT-4312. + LLPointer cb_fav = new AddFavoriteLandmarkCallback(); + LLInventoryPanel* panel = mInventoryPanel.get(); + LLFolderViewItem* drag_over_item = panel ? panel->getRootFolder()->getDraggingOverItem() : NULL; + LLFolderViewModelItemInventory* view_model = drag_over_item ? static_cast(drag_over_item->getViewModelItem()) : NULL; + if (view_model) + { + cb_fav.get()->setTargetLandmarkId(view_model->getUUID()); + } + + LLPointer callback = cb_fav; + if (cb) + { + callback = new LLBoostFuncInventoryCallback(boost::bind(drop_to_favorites_cb, _1, cb, cb_fav)); + } + + copy_inventory_item( + gAgent.getID(), + inv_item->getPermissions().getOwner(), + inv_item->getUUID(), + mUUID, + std::string(), + callback); +} + +void LLFolderBridge::dropToOutfit(LLInventoryItem* inv_item, bool move_is_into_current_outfit, LLPointer cb) +{ + if((inv_item->getInventoryType() == LLInventoryType::IT_TEXTURE) || (inv_item->getInventoryType() == LLInventoryType::IT_SNAPSHOT)) + { + const LLUUID &my_outifts_id = getInventoryModel()->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + if(mUUID != my_outifts_id) + { + // Legacy: prior to thumbnails images in outfits were used for outfit gallery. + LLNotificationsUtil::add("ThumbnailOutfitPhoto"); + } + return; + } + + // BAP - should skip if dup. + if (move_is_into_current_outfit) + { + LLAppearanceMgr::instance().wearItemOnAvatar(inv_item->getUUID(), true, true); + } + else + { + LLPointer cb = NULL; + link_inventory_object(mUUID, LLConstPointer(inv_item), cb); + } +} + +void LLFolderBridge::dropToMyOutfits(LLInventoryCategory* inv_cat, LLPointer cb) +{ + // make a folder in the My Outfits directory. + const LLUUID dest_id = getInventoryModel()->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + + // Note: creation will take time, so passing folder id to callback is slightly unreliable, + // but so is collecting and passing descendants' ids + inventory_func_type func = boost::bind(&LLFolderBridge::outfitFolderCreatedCallback, this, inv_cat->getUUID(), _1, cb); + gInventory.createNewCategory(dest_id, + LLFolderType::FT_OUTFIT, + inv_cat->getName(), + func, + inv_cat->getThumbnailUUID()); +} + +void LLFolderBridge::outfitFolderCreatedCallback(LLUUID cat_source_id, LLUUID cat_dest_id, LLPointer cb) +{ + LLInventoryModel::cat_array_t* categories; + LLInventoryModel::item_array_t* items; + getInventoryModel()->getDirectDescendentsOf(cat_source_id, categories, items); + + LLInventoryObject::const_object_list_t link_array; + + + LLInventoryModel::item_array_t::iterator iter = items->begin(); + LLInventoryModel::item_array_t::iterator end = items->end(); + while (iter!=end) + { + const LLViewerInventoryItem* item = (*iter); + // By this point everything is supposed to be filtered, + // but there was a delay to create folder so something could have changed + LLInventoryType::EType inv_type = item->getInventoryType(); + if ((inv_type == LLInventoryType::IT_WEARABLE) || + (inv_type == LLInventoryType::IT_GESTURE) || + (inv_type == LLInventoryType::IT_ATTACHMENT) || + (inv_type == LLInventoryType::IT_OBJECT) || + (inv_type == LLInventoryType::IT_SNAPSHOT) || + (inv_type == LLInventoryType::IT_TEXTURE)) + { + link_array.push_back(LLConstPointer(item)); + } + iter++; + } + + if (!link_array.empty()) + { + link_inventory_array(cat_dest_id, link_array, cb); + } +} + +// Callback for drop item if DAMA required... +void LLFolderBridge::callback_dropItemIntoFolder(const LLSD& notification, const LLSD& response, LLInventoryItem* inv_item) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // YES + { + std::string tooltip_msg; + dragItemIntoFolder(inv_item, true, tooltip_msg, false); + } +} + +// Callback for drop category if DAMA required... +void LLFolderBridge::callback_dropCategoryIntoFolder(const LLSD& notification, const LLSD& response, LLInventoryCategory* inv_category) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // YES + { + std::string tooltip_msg; + dragCategoryIntoFolder(inv_category, true, tooltip_msg, false, false); + } +} + +// This is used both for testing whether an item can be dropped +// into the folder, as well as performing the actual drop, depending +// if drop == true. +bool LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, + bool drop, + std::string& tooltip_msg, + bool user_confirm, + LLPointer cb) +{ + LLInventoryModel* model = getInventoryModel(); + + if (!model || !inv_item) return false; + if (!isAgentInventory()) return false; // cannot drag into library + if (!isAgentAvatarValid()) return false; + + LLInventoryPanel* destination_panel = mInventoryPanel.get(); + if (!destination_panel) return false; + + LLInventoryFilter* filter = getInventoryFilter(); + if (!filter) return false; + + const LLUUID ¤t_outfit_id = model->findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + const LLUUID &favorites_id = model->findCategoryUUIDForType(LLFolderType::FT_FAVORITE); + const LLUUID &landmarks_id = model->findCategoryUUIDForType(LLFolderType::FT_LANDMARK); + const LLUUID &marketplacelistings_id = model->findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + const LLUUID &my_outifts_id = model->findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + const LLUUID from_folder_uuid = inv_item->getParentUUID(); + + const bool move_is_into_current_outfit = (mUUID == current_outfit_id); + const bool move_is_into_favorites = (mUUID == favorites_id); + const bool move_is_into_my_outfits = (mUUID == my_outifts_id) || model->isObjectDescendentOf(mUUID, my_outifts_id); + const bool move_is_into_outfit = move_is_into_my_outfits || (getCategory() && getCategory()->getPreferredType()==LLFolderType::FT_OUTFIT); + const bool move_is_into_landmarks = (mUUID == landmarks_id) || model->isObjectDescendentOf(mUUID, landmarks_id); + const bool move_is_into_marketplacelistings = model->isObjectDescendentOf(mUUID, marketplacelistings_id); + const bool move_is_from_marketplacelistings = model->isObjectDescendentOf(inv_item->getUUID(), marketplacelistings_id); + + LLToolDragAndDrop::ESource source = LLToolDragAndDrop::getInstance()->getSource(); + bool accept = false; + U64 filter_types = filter->getFilterTypes(); + // We shouldn't allow to drop non recent items into recent tab (or some similar transactions) + // while we are allowing to interact with regular filtered inventory + bool use_filter = filter_types && (filter_types&LLInventoryFilter::FILTERTYPE_DATE || (filter_types&LLInventoryFilter::FILTERTYPE_OBJECT)==0); + LLViewerObject* object = NULL; + if(LLToolDragAndDrop::SOURCE_AGENT == source) + { + const LLUUID &trash_id = model->findCategoryUUIDForType(LLFolderType::FT_TRASH); + + const bool move_is_into_trash = (mUUID == trash_id) || model->isObjectDescendentOf(mUUID, trash_id); + const bool move_is_outof_current_outfit = LLAppearanceMgr::instance().getIsInCOF(inv_item->getUUID()); + + //-------------------------------------------------------------------------------- + // Determine if item can be moved. + // + + bool is_movable = true; + + switch (inv_item->getActualType()) + { + case LLAssetType::AT_CATEGORY: + is_movable = !LLFolderType::lookupIsProtectedType(((LLInventoryCategory*)inv_item)->getPreferredType()); + break; + default: + break; + } + // Can't explicitly drag things out of the COF. + if (move_is_outof_current_outfit) + { + is_movable = false; + } + if (move_is_into_trash) + { + is_movable &= inv_item->getIsLinkType() || !get_is_item_worn(inv_item->getUUID()); + } + if (is_movable) + { + // Don't allow creating duplicates in the Calling Card/Friends + // subfolders, see bug EXT-1599. Check is item direct descendent + // of target folder and forbid item's movement if it so. + // Note: isItemDirectDescendentOfCategory checks if + // passed category is in the Calling Card/Friends folder + is_movable &= !LLFriendCardsManager::instance().isObjDirectDescendentOfCategory(inv_item, getCategory()); + } + + // + //-------------------------------------------------------------------------------- + + //-------------------------------------------------------------------------------- + // Determine if item can be moved & dropped + // Note: if user_confirm is false, we already went through those accept logic test and can skip them + + accept = true; + + if (user_confirm && !is_movable) + { + accept = false; + } + else if (user_confirm && (mUUID == inv_item->getParentUUID()) && !move_is_into_favorites) + { + accept = false; + } + else if (user_confirm && (move_is_into_current_outfit || move_is_into_outfit)) + { + accept = can_move_to_outfit(inv_item, move_is_into_current_outfit); + } + else if (user_confirm && (move_is_into_favorites || move_is_into_landmarks)) + { + accept = can_move_to_landmarks(inv_item); + } + else if (user_confirm && move_is_into_marketplacelistings) + { + const LLViewerInventoryCategory * master_folder = model->getFirstDescendantOf(marketplacelistings_id, mUUID); + LLViewerInventoryCategory * dest_folder = getCategory(); + accept = can_move_item_to_marketplace(master_folder, dest_folder, inv_item, tooltip_msg, LLToolDragAndDrop::instance().getCargoCount() - LLToolDragAndDrop::instance().getCargoIndex()); + } + + // Check that the folder can accept this item based on folder/item type compatibility (e.g. stock folder compatibility) + if (user_confirm && accept) + { + LLViewerInventoryCategory * dest_folder = getCategory(); + accept = dest_folder->acceptItem(inv_item); + } + + LLInventoryPanel* active_panel = LLInventoryPanel::getActiveInventoryPanel(false); + + // Check whether the item being dragged from active inventory panel + // passes the filter of the destination panel. + if (user_confirm && accept && active_panel && use_filter) + { + LLFolderViewItem* fv_item = active_panel->getItemByID(inv_item->getUUID()); + if (!fv_item) return false; + + accept = filter->check(fv_item->getViewModelItem()); + } + + if (accept && drop) + { + if (inv_item->getType() == LLAssetType::AT_GESTURE + && LLGestureMgr::instance().isGestureActive(inv_item->getUUID()) && move_is_into_trash) + { + LLGestureMgr::instance().deactivateGesture(inv_item->getUUID()); + } + // If an item is being dragged between windows, unselect everything in the active window + // so that we don't follow the selection to its new location (which is very annoying). + // RN: a better solution would be to deselect automatically when an item is moved + // and then select any item that is dropped only in the panel that it is dropped in + if (active_panel && (destination_panel != active_panel)) + { + active_panel->unSelectAll(); + } + // Dropping in or out of marketplace needs (sometimes) confirmation + if (user_confirm && (move_is_from_marketplacelistings || move_is_into_marketplacelistings)) + { + if ((move_is_from_marketplacelistings && (LLMarketplaceData::instance().isInActiveFolder(inv_item->getUUID()) + || LLMarketplaceData::instance().isListedAndActive(inv_item->getUUID()))) || + (move_is_into_marketplacelistings && LLMarketplaceData::instance().isInActiveFolder(mUUID))) + { + LLNotificationsUtil::add("ConfirmMerchantActiveChange", LLSD(), LLSD(), boost::bind(&LLFolderBridge::callback_dropItemIntoFolder, this, _1, _2, inv_item)); + return true; + } + if (move_is_into_marketplacelistings && !move_is_from_marketplacelistings) + { + LLNotificationsUtil::add("ConfirmMerchantMoveInventory", LLSD(), LLSD(), boost::bind(&LLFolderBridge::callback_dropItemIntoFolder, this, _1, _2, inv_item)); + return true; + } + } + + //-------------------------------------------------------------------------------- + // Destination folder logic + // + + // REORDER + // (only reorder the item in Favorites folder) + if ((mUUID == inv_item->getParentUUID()) && move_is_into_favorites) + { + LLFolderViewItem* itemp = destination_panel->getRootFolder()->getDraggingOverItem(); + if (itemp) + { + LLUUID srcItemId = inv_item->getUUID(); + LLUUID destItemId = static_cast(itemp->getViewModelItem())->getUUID(); + LLFavoritesOrderStorage::instance().rearrangeFavoriteLandmarks(srcItemId, destItemId); + } + } + + // FAVORITES folder + // (copy the item) + else if (move_is_into_favorites) + { + dropToFavorites(inv_item, cb); + } + // CURRENT OUTFIT or OUTFIT folder + // (link the item) + else if (move_is_into_current_outfit || move_is_into_outfit) + { + dropToOutfit(inv_item, move_is_into_current_outfit, cb); + } + // MARKETPLACE LISTINGS folder + // Move the item + else if (move_is_into_marketplacelistings) + { + move_item_to_marketplacelistings(inv_item, mUUID); + if (cb) cb->fire(inv_item->getUUID()); + } + // NORMAL or TRASH folder + // (move the item, restamp if into trash) + else + { + // set up observer to select item once drag and drop from inbox is complete + if (gInventory.isObjectDescendentOf(inv_item->getUUID(), gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX))) + { + set_dad_inbox_object(inv_item->getUUID()); + } + + LLInvFVBridge::changeItemParent( + model, + (LLViewerInventoryItem*)inv_item, + mUUID, + move_is_into_trash); + if (cb) cb->fire(inv_item->getUUID()); + } + + if (move_is_from_marketplacelistings) + { + // If we move from an active (listed) listing, checks that it's still valid, if not, unlist + LLUUID version_folder_id = LLMarketplaceData::instance().getActiveFolder(from_folder_uuid); + if (version_folder_id.notNull()) + { + LLMarketplaceValidator::getInstance()->validateMarketplaceListings( + version_folder_id, + [version_folder_id](bool result) + { + if (!result) + { + LLMarketplaceData::instance().activateListing(version_folder_id, false); + } + }); + } + } + + // + //-------------------------------------------------------------------------------- + } + } + else if (LLToolDragAndDrop::SOURCE_WORLD == source) + { + // Make sure the object exists. If we allowed dragging from + // anonymous objects, it would be possible to bypass + // permissions. + object = gObjectList.findObject(inv_item->getParentUUID()); + if (!object) + { + LL_INFOS() << "Object not found for drop." << LL_ENDL; + return false; + } + + // coming from a task. Need to figure out if the person can + // move/copy this item. + LLPermissions perm(inv_item->getPermissions()); + bool is_move = false; + if ((perm.allowCopyBy(gAgent.getID(), gAgent.getGroupID()) + && perm.allowTransferTo(gAgent.getID()))) + // || gAgent.isGodlike()) + { + accept = true; + } + else if(object->permYouOwner()) + { + // If the object cannot be copied, but the object the + // inventory is owned by the agent, then the item can be + // moved from the task to agent inventory. + is_move = true; + accept = true; + } + + // Don't allow placing an original item into Current Outfit or an outfit folder + // because they must contain only links to wearable items. + // *TODO: Probably we should create a link to an item if it was dragged to outfit or COF. + if (move_is_into_current_outfit || move_is_into_outfit) + { + accept = false; + } + // Don't allow to move a single item to Favorites or Landmarks + // if it is not a landmark or a link to a landmark. + else if ((move_is_into_favorites || move_is_into_landmarks) + && !can_move_to_landmarks(inv_item)) + { + accept = false; + } + else if (move_is_into_marketplacelistings) + { + tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); + accept = false; + } + + // Check whether the item being dragged from in world + // passes the filter of the destination panel. + if (accept && use_filter) + { + accept = filter->check(inv_item); + } + + if (accept && drop) + { + LLUUID item_id = inv_item->getUUID(); + std::shared_ptr move_inv (new LLMoveInv()); + move_inv->mObjectID = inv_item->getParentUUID(); + two_uuids_t item_pair(mUUID, item_id); + move_inv->mMoveList.push_back(item_pair); + if (cb) + { + move_inv->mCallback = [item_id, cb](S32, void*, const LLMoveInv* move_inv) mutable + { cb->fire(item_id); }; + } + move_inv->mUserData = NULL; + if(is_move) + { + warn_move_inventory(object, move_inv); + } + else + { + // store dad inventory item to select added one later. See EXT-4347 + set_dad_inventory_item(inv_item, mUUID); + + LLNotification::Params params("MoveInventoryFromObject"); + params.functor.function(boost::bind(move_task_inventory_callback, _1, _2, move_inv)); + LLNotifications::instance().forceResponse(params, 0); + } + } + } + else if(LLToolDragAndDrop::SOURCE_NOTECARD == source) + { + if (move_is_into_marketplacelistings) + { + tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); + accept = false; + } + else if ((inv_item->getActualType() == LLAssetType::AT_SETTINGS) && !LLEnvironment::instance().isInventoryEnabled()) + { + tooltip_msg = LLTrans::getString("NoEnvironmentSettings"); + accept = false; + } + else + { + // Don't allow placing an original item from a notecard to Current Outfit or an outfit folder + // because they must contain only links to wearable items. + accept = !(move_is_into_current_outfit || move_is_into_outfit); + } + + // Check whether the item being dragged from notecard + // passes the filter of the destination panel. + if (accept && use_filter) + { + accept = filter->check(inv_item); + } + + if (accept && drop) + { + copy_inventory_from_notecard(mUUID, // Drop to the chosen destination folder + LLToolDragAndDrop::getInstance()->getObjectID(), + LLToolDragAndDrop::getInstance()->getSourceID(), + inv_item); + } + } + else if(LLToolDragAndDrop::SOURCE_LIBRARY == source) + { + LLViewerInventoryItem* item = (LLViewerInventoryItem*)inv_item; + if(item && item->isFinished()) + { + accept = true; + + if (move_is_into_marketplacelistings) + { + tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); + accept = false; + } + else if (move_is_into_current_outfit || move_is_into_outfit) + { + accept = can_move_to_outfit(inv_item, move_is_into_current_outfit); + } + // Don't allow to move a single item to Favorites or Landmarks + // if it is not a landmark or a link to a landmark. + else if (move_is_into_favorites || move_is_into_landmarks) + { + accept = can_move_to_landmarks(inv_item); + } + + LLInventoryPanel* active_panel = LLInventoryPanel::getActiveInventoryPanel(false); + + // Check whether the item being dragged from the library + // passes the filter of the destination panel. + if (accept && active_panel && use_filter) + { + LLFolderViewItem* fv_item = active_panel->getItemByID(inv_item->getUUID()); + if (!fv_item) return false; + + accept = filter->check(fv_item->getViewModelItem()); + } + + if (accept && drop) + { + // FAVORITES folder + // (copy the item) + if (move_is_into_favorites) + { + dropToFavorites(inv_item, cb); + } + // CURRENT OUTFIT or OUTFIT folder + // (link the item) + else if (move_is_into_current_outfit || move_is_into_outfit) + { + dropToOutfit(inv_item, move_is_into_current_outfit, cb); + } + else + { + copy_inventory_item( + gAgent.getID(), + inv_item->getPermissions().getOwner(), + inv_item->getUUID(), + mUUID, + std::string(), + cb); + } + } + } + } + else + { + LL_WARNS() << "unhandled drag source" << LL_ENDL; + } + return accept; +} + +// static +bool check_category(LLInventoryModel* model, + const LLUUID& cat_id, + LLInventoryPanel* active_panel, + LLInventoryFilter* filter) +{ + if (!model || !active_panel || !filter) + return false; + + if (!filter->checkFolder(cat_id)) + { + return false; + } + + LLInventoryModel::cat_array_t descendent_categories; + LLInventoryModel::item_array_t descendent_items; + model->collectDescendents(cat_id, descendent_categories, descendent_items, true); + + S32 num_descendent_categories = descendent_categories.size(); + S32 num_descendent_items = descendent_items.size(); + + if (num_descendent_categories + num_descendent_items == 0) + { + // Empty folder should be checked as any other folder view item. + // If we are filtering by date the folder should not pass because + // it doesn't have its own creation date. See LLInvFVBridge::getCreationDate(). + return check_item(cat_id, active_panel, filter); + } + + for (S32 i = 0; i < num_descendent_categories; ++i) + { + LLInventoryCategory* category = descendent_categories[i]; + if(!check_category(model, category->getUUID(), active_panel, filter)) + { + return false; + } + } + + for (S32 i = 0; i < num_descendent_items; ++i) + { + LLViewerInventoryItem* item = descendent_items[i]; + if(!check_item(item->getUUID(), active_panel, filter)) + { + return false; + } + } + + return true; +} + +// static +bool check_item(const LLUUID& item_id, + LLInventoryPanel* active_panel, + LLInventoryFilter* filter) +{ + if (!active_panel || !filter) return false; + + LLFolderViewItem* fv_item = active_panel->getItemByID(item_id); + if (!fv_item) return false; + + return filter->check(fv_item->getViewModelItem()); +} + +// +=================================================+ +// | LLTextureBridge | +// +=================================================+ + +LLUIImagePtr LLTextureBridge::getIcon() const +{ + return LLInventoryIcon::getIcon(LLAssetType::AT_TEXTURE, mInvType); +} + +void LLTextureBridge::openItem() +{ + LLViewerInventoryItem* item = getItem(); + + if (item) + { + LLInvFVBridgeAction::doAction(item->getType(),mUUID,getInventoryModel()); + } +} + +bool LLTextureBridge::canSaveTexture(void) +{ + const LLInventoryModel* model = getInventoryModel(); + if(!model) + { + return false; + } + + const LLViewerInventoryItem *item = model->getItem(mUUID); + if (item) + { + return item->checkPermissionsSet(PERM_ITEM_UNRESTRICTED); + } + return false; +} + +void LLTextureBridge::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + LL_DEBUGS() << "LLTextureBridge::buildContextMenu()" << LL_ENDL; + menuentry_vec_t items; + menuentry_vec_t disabled_items; + if(isItemInTrash()) + { + addTrashContextMenuOptions(items, disabled_items); + } + else if (isMarketplaceListingsFolder()) + { + addMarketplaceContextMenuOptions(flags, items, disabled_items); + items.push_back(std::string("Properties")); + getClipboardEntries(false, items, disabled_items, flags); + } + else + { + items.push_back(std::string("Share")); + if (!canShare()) + { + disabled_items.push_back(std::string("Share")); + } + + addOpenRightClickMenuOption(items); + items.push_back(std::string("Properties")); + + getClipboardEntries(true, items, disabled_items, flags); + + items.push_back(std::string("Texture Separator")); + + if ((flags & ITEM_IN_MULTI_SELECTION) != 0) + { + items.push_back(std::string("Save Selected As")); + } + else + { + items.push_back(std::string("Save As")); + if (!canSaveTexture()) + { + disabled_items.push_back(std::string("Save As")); + } + } + + } + addLinkReplaceMenuOption(items, disabled_items); + hide_context_entries(menu, items, disabled_items); +} + +// virtual +void LLTextureBridge::performAction(LLInventoryModel* model, std::string action) +{ + if ("save_as" == action) + { + LLPreviewTexture* preview_texture = LLFloaterReg::getTypedInstance("preview_texture", mUUID); + if (preview_texture) + { + preview_texture->openToSave(); + preview_texture->saveAs(); + } + } + else if ("save_selected_as" == action) + { + openItem(); + if (canSaveTexture()) + { + LLPreviewTexture* preview_texture = LLFloaterReg::getTypedInstance("preview_texture", mUUID); + if (preview_texture) + { + preview_texture->saveMultipleToFile(mFileName); + } + } + else + { + LL_WARNS() << "You don't have permission to save " << getName() << " to disk." << LL_ENDL; + } + + } + else LLItemBridge::performAction(model, action); +} + +// +=================================================+ +// | LLSoundBridge | +// +=================================================+ + +void LLSoundBridge::openItem() +{ + const LLViewerInventoryItem* item = getItem(); + if (item) + { + LLInvFVBridgeAction::doAction(item->getType(),mUUID,getInventoryModel()); + } +} + +void LLSoundBridge::openSoundPreview(void* which) +{ + LLSoundBridge *me = (LLSoundBridge *)which; + LLFloaterReg::showInstance("preview_sound", LLSD(me->mUUID), TAKE_FOCUS_YES); +} + +void LLSoundBridge::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + LL_DEBUGS() << "LLSoundBridge::buildContextMenu()" << LL_ENDL; + menuentry_vec_t items; + menuentry_vec_t disabled_items; + + if (isMarketplaceListingsFolder()) + { + addMarketplaceContextMenuOptions(flags, items, disabled_items); + items.push_back(std::string("Properties")); + getClipboardEntries(false, items, disabled_items, flags); + } + else + { + if (isItemInTrash()) + { + addTrashContextMenuOptions(items, disabled_items); + } + else + { + items.push_back(std::string("Share")); + if (!canShare()) + { + disabled_items.push_back(std::string("Share")); + } + items.push_back(std::string("Sound Open")); + items.push_back(std::string("Properties")); + + getClipboardEntries(true, items, disabled_items, flags); + } + + items.push_back(std::string("Sound Separator")); + items.push_back(std::string("Sound Play")); + } + + addLinkReplaceMenuOption(items, disabled_items); + hide_context_entries(menu, items, disabled_items); +} + +void LLSoundBridge::performAction(LLInventoryModel* model, std::string action) +{ + if ("sound_play" == action) + { + LLViewerInventoryItem* item = getItem(); + if(item) + { + send_sound_trigger(item->getAssetUUID(), SOUND_GAIN); + } + } + else if ("open" == action) + { + openSoundPreview((void*)this); + } + else LLItemBridge::performAction(model, action); +} + +// +=================================================+ +// | LLLandmarkBridge | +// +=================================================+ + +LLLandmarkBridge::LLLandmarkBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid, + U32 flags/* = 0x00*/) : + LLItemBridge(inventory, root, uuid) +{ + mVisited = false; + if (flags & LLInventoryItemFlags::II_FLAGS_LANDMARK_VISITED) + { + mVisited = true; + } +} + +LLUIImagePtr LLLandmarkBridge::getIcon() const +{ + return LLInventoryIcon::getIcon(LLAssetType::AT_LANDMARK, LLInventoryType::IT_LANDMARK, mVisited, false); +} + +void LLLandmarkBridge::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + menuentry_vec_t items; + menuentry_vec_t disabled_items; + + LL_DEBUGS() << "LLLandmarkBridge::buildContextMenu()" << LL_ENDL; + if (isMarketplaceListingsFolder()) + { + addMarketplaceContextMenuOptions(flags, items, disabled_items); + items.push_back(std::string("Properties")); + getClipboardEntries(false, items, disabled_items, flags); + } + else + { + if(isItemInTrash()) + { + addTrashContextMenuOptions(items, disabled_items); + } + else + { + items.push_back(std::string("Share")); + if (!canShare()) + { + disabled_items.push_back(std::string("Share")); + } + items.push_back(std::string("Landmark Open")); + items.push_back(std::string("Properties")); + + getClipboardEntries(true, items, disabled_items, flags); + } + + items.push_back(std::string("Landmark Separator")); + items.push_back(std::string("url_copy")); + items.push_back(std::string("About Landmark")); + items.push_back(std::string("show_on_map")); + } + + // Disable "About Landmark" menu item for + // multiple landmarks selected. Only one landmark + // info panel can be shown at a time. + if ((flags & FIRST_SELECTED_ITEM) == 0) + { + disabled_items.push_back(std::string("url_copy")); + disabled_items.push_back(std::string("About Landmark")); + } + + addLinkReplaceMenuOption(items, disabled_items); + hide_context_entries(menu, items, disabled_items); +} + +// Convenience function for the two functions below. +void teleport_via_landmark(const LLUUID& asset_id) +{ + gAgent.teleportViaLandmark( asset_id ); + + // we now automatically track the landmark you're teleporting to + // because you'll probably arrive at a telehub instead + LLFloaterWorldMap* floater_world_map = LLFloaterWorldMap::getInstance(); + if( floater_world_map ) + { + floater_world_map->trackLandmark( asset_id ); + } +} + +// virtual +void LLLandmarkBridge::performAction(LLInventoryModel* model, std::string action) +{ + if ("teleport" == action) + { + LLViewerInventoryItem* item = getItem(); + if(item) + { + teleport_via_landmark(item->getAssetUUID()); + } + } + else if ("about" == action) + { + LLViewerInventoryItem* item = getItem(); + if(item) + { + LLSD key; + key["type"] = "landmark"; + key["id"] = item->getUUID(); + + LLFloaterSidePanelContainer::showPanel("places", key); + } + } + else + { + LLItemBridge::performAction(model, action); + } +} + +static bool open_landmark_callback(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + + LLUUID asset_id = notification["payload"]["asset_id"].asUUID(); + if (option == 0) + { + teleport_via_landmark(asset_id); + } + + return false; +} +static LLNotificationFunctorRegistration open_landmark_callback_reg("TeleportFromLandmark", open_landmark_callback); + + +void LLLandmarkBridge::openItem() +{ + LLViewerInventoryItem* item = getItem(); + + if (item) + { + LLInvFVBridgeAction::doAction(item->getType(),mUUID,getInventoryModel()); + } +} + + +// +=================================================+ +// | LLCallingCardObserver | +// +=================================================+ +class LLCallingCardObserver : public LLFriendObserver +{ +public: + LLCallingCardObserver(LLCallingCardBridge* bridge) : mBridgep(bridge) {} + virtual ~LLCallingCardObserver() { mBridgep = NULL; } + virtual void changed(U32 mask) + { + if (mask & LLFriendObserver::ONLINE) + { + mBridgep->refreshFolderViewItem(); + mBridgep->checkSearchBySuffixChanges(); + } + } +protected: + LLCallingCardBridge* mBridgep; +}; + +// +=================================================+ +// | LLCallingCardBridge | +// +=================================================+ + +LLCallingCardBridge::LLCallingCardBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid ) : + LLItemBridge(inventory, root, uuid) +{ + mObserver = new LLCallingCardObserver(this); + mCreatorUUID = getItem()->getCreatorUUID(); + LLAvatarTracker::instance().addParticularFriendObserver(mCreatorUUID, mObserver); +} + +LLCallingCardBridge::~LLCallingCardBridge() +{ + LLAvatarTracker::instance().removeParticularFriendObserver(mCreatorUUID, mObserver); + + delete mObserver; +} + +void LLCallingCardBridge::refreshFolderViewItem() +{ + LLInventoryPanel* panel = mInventoryPanel.get(); + LLFolderViewItem* itemp = panel ? panel->getItemByID(mUUID) : NULL; + if (itemp) + { + itemp->refresh(); + } +} + +void LLCallingCardBridge::checkSearchBySuffixChanges() +{ + if (!mDisplayName.empty()) + { + // changes in mDisplayName are processed by rename function and here it will be always same + // suffixes are also of fixed length, and we are processing change of one at a time, + // so it should be safe to use length (note: mSearchableName is capitalized) + S32 old_length = mSearchableName.length(); + S32 new_length = mDisplayName.length() + getLabelSuffix().length(); + if (old_length == new_length) + { + return; + } + mSearchableName.assign(mDisplayName); + mSearchableName.append(getLabelSuffix()); + LLStringUtil::toUpper(mSearchableName); + if (new_lengthgetFilterSubString()) == std::string::npos) + { + // string no longer contains substring + // we either have to update all parents manually or restart filter. + // dirtyFilter will not work here due to obsolete descendants' generations + getInventoryFilter()->setModified(LLFolderViewFilter::FILTER_MORE_RESTRICTIVE); + } + } + else + { + if (getInventoryFilter()) + { + // mSearchableName became longer, we gained additional suffix and need to repeat filter check. + dirtyFilter(); + } + } + } +} + +// virtual +void LLCallingCardBridge::performAction(LLInventoryModel* model, std::string action) +{ + if ("begin_im" == action) + { + LLViewerInventoryItem *item = getItem(); + if (item && (item->getCreatorUUID() != gAgent.getID()) && + (!item->getCreatorUUID().isNull())) + { + std::string callingcard_name = LLCacheName::getDefaultName(); + LLAvatarName av_name; + if (LLAvatarNameCache::get(item->getCreatorUUID(), &av_name)) + { + callingcard_name = av_name.getCompleteName(); + } + LLUUID session_id = gIMMgr->addSession(callingcard_name, IM_NOTHING_SPECIAL, item->getCreatorUUID()); + if (session_id != LLUUID::null) + { + LLFloaterIMContainer::getInstance()->showConversation(session_id); + } + } + } + else if ("lure" == action) + { + LLViewerInventoryItem *item = getItem(); + if (item && (item->getCreatorUUID() != gAgent.getID()) && + (!item->getCreatorUUID().isNull())) + { + LLAvatarActions::offerTeleport(item->getCreatorUUID()); + } + } + else if ("request_lure" == action) + { + LLViewerInventoryItem *item = getItem(); + if (item && (item->getCreatorUUID() != gAgent.getID()) && + (!item->getCreatorUUID().isNull())) + { + LLAvatarActions::teleportRequest(item->getCreatorUUID()); + } + } + + else LLItemBridge::performAction(model, action); +} + +LLUIImagePtr LLCallingCardBridge::getIcon() const +{ + bool online = false; + LLViewerInventoryItem* item = getItem(); + if(item) + { + online = LLAvatarTracker::instance().isBuddyOnline(item->getCreatorUUID()); + } + return LLInventoryIcon::getIcon(LLAssetType::AT_CALLINGCARD, LLInventoryType::IT_CALLINGCARD, online, false); +} + +std::string LLCallingCardBridge::getLabelSuffix() const +{ + LLViewerInventoryItem* item = getItem(); + if( item && LLAvatarTracker::instance().isBuddyOnline(item->getCreatorUUID()) ) + { + return LLItemBridge::getLabelSuffix() + " online"; + } + else + { + return LLItemBridge::getLabelSuffix(); + } +} + +void LLCallingCardBridge::openItem() +{ + LLViewerInventoryItem* item = getItem(); + + if (item) + { + LLInvFVBridgeAction::doAction(item->getType(),mUUID,getInventoryModel()); + } +/* + LLViewerInventoryItem* item = getItem(); + if(item && !item->getCreatorUUID().isNull()) + { + LLAvatarActions::showProfile(item->getCreatorUUID()); + } +*/ +} + +void LLCallingCardBridge::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + LL_DEBUGS() << "LLCallingCardBridge::buildContextMenu()" << LL_ENDL; + menuentry_vec_t items; + menuentry_vec_t disabled_items; + + if(isItemInTrash()) + { + addTrashContextMenuOptions(items, disabled_items); + } + else if (isMarketplaceListingsFolder()) + { + addMarketplaceContextMenuOptions(flags, items, disabled_items); + items.push_back(std::string("Properties")); + getClipboardEntries(false, items, disabled_items, flags); + } + else + { + items.push_back(std::string("Share")); + if (!canShare()) + { + disabled_items.push_back(std::string("Share")); + } + if ((flags & FIRST_SELECTED_ITEM) == 0) + { + disabled_items.push_back(std::string("Open")); + } + addOpenRightClickMenuOption(items); + items.push_back(std::string("Properties")); + + getClipboardEntries(true, items, disabled_items, flags); + + LLInventoryItem* item = getItem(); + bool good_card = (item + && (LLUUID::null != item->getCreatorUUID()) + && (item->getCreatorUUID() != gAgent.getID())); + bool user_online = false; + if (item) + { + user_online = (LLAvatarTracker::instance().isBuddyOnline(item->getCreatorUUID())); + } + items.push_back(std::string("Send Instant Message Separator")); + items.push_back(std::string("Send Instant Message")); + items.push_back(std::string("Offer Teleport...")); + items.push_back(std::string("Request Teleport...")); + items.push_back(std::string("Conference Chat")); + + if (!good_card) + { + disabled_items.push_back(std::string("Send Instant Message")); + } + if (!good_card || !user_online) + { + disabled_items.push_back(std::string("Offer Teleport...")); + disabled_items.push_back(std::string("Request Teleport...")); + disabled_items.push_back(std::string("Conference Chat")); + } + } + addLinkReplaceMenuOption(items, disabled_items); + hide_context_entries(menu, items, disabled_items); +} + +bool LLCallingCardBridge::dragOrDrop(MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + std::string& tooltip_msg) +{ + LLViewerInventoryItem* item = getItem(); + bool rv = false; + if(item) + { + // check the type + switch(cargo_type) + { + case DAD_TEXTURE: + case DAD_SOUND: + case DAD_LANDMARK: + case DAD_SCRIPT: + case DAD_CLOTHING: + case DAD_OBJECT: + case DAD_NOTECARD: + case DAD_BODYPART: + case DAD_ANIMATION: + case DAD_GESTURE: + case DAD_MESH: + case DAD_SETTINGS: + case DAD_MATERIAL: + { + LLInventoryItem* inv_item = (LLInventoryItem*)cargo_data; + const LLPermissions& perm = inv_item->getPermissions(); + if(gInventory.getItem(inv_item->getUUID()) + && perm.allowOperationBy(PERM_TRANSFER, gAgent.getID())) + { + rv = true; + if(drop) + { + LLGiveInventory::doGiveInventoryItem(item->getCreatorUUID(), + (LLInventoryItem*)cargo_data); + } + } + else + { + // It's not in the user's inventory (it's probably in + // an object's contents), so disallow dragging it here. + // You can't give something you don't yet have. + rv = false; + } + break; + } + case DAD_CATEGORY: + { + LLInventoryCategory* inv_cat = (LLInventoryCategory*)cargo_data; + if( gInventory.getCategory( inv_cat->getUUID() ) ) + { + rv = true; + if(drop) + { + LLGiveInventory::doGiveInventoryCategory( + item->getCreatorUUID(), + inv_cat); + } + } + else + { + // It's not in the user's inventory (it's probably in + // an object's contents), so disallow dragging it here. + // You can't give something you don't yet have. + rv = false; + } + break; + } + default: + break; + } + } + return rv; +} + +// +=================================================+ +// | LLNotecardBridge | +// +=================================================+ + +void LLNotecardBridge::openItem() +{ + LLViewerInventoryItem* item = getItem(); + if (item) + { + LLInvFVBridgeAction::doAction(item->getType(),mUUID,getInventoryModel()); + } +} + +void LLNotecardBridge::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + LL_DEBUGS() << "LLNotecardBridge::buildContextMenu()" << LL_ENDL; + + if (isMarketplaceListingsFolder()) + { + menuentry_vec_t items; + menuentry_vec_t disabled_items; + addMarketplaceContextMenuOptions(flags, items, disabled_items); + items.push_back(std::string("Properties")); + getClipboardEntries(false, items, disabled_items, flags); + hide_context_entries(menu, items, disabled_items); + } + else + { + LLItemBridge::buildContextMenu(menu, flags); + } +} + +// +=================================================+ +// | LLGestureBridge | +// +=================================================+ + +LLFontGL::StyleFlags LLGestureBridge::getLabelStyle() const +{ + if( LLGestureMgr::instance().isGestureActive(mUUID) ) + { + return LLFontGL::BOLD; + } + else + { + return LLFontGL::NORMAL; + } +} + +std::string LLGestureBridge::getLabelSuffix() const +{ + if( LLGestureMgr::instance().isGestureActive(mUUID) ) + { + LLStringUtil::format_map_t args; + args["[GESLABEL]"] = LLItemBridge::getLabelSuffix(); + return LLTrans::getString("ActiveGesture", args); + } + else + { + return LLItemBridge::getLabelSuffix(); + } +} + +// virtual +void LLGestureBridge::performAction(LLInventoryModel* model, std::string action) +{ + if (isAddAction(action)) + { + LLGestureMgr::instance().activateGesture(mUUID); + + LLViewerInventoryItem* item = gInventory.getItem(mUUID); + if (!item) return; + + // Since we just changed the suffix to indicate (active) + // the server doesn't need to know, just the viewer. + gInventory.updateItem(item); + gInventory.notifyObservers(); + } + else if ("deactivate" == action || isRemoveAction(action)) + { + LLGestureMgr::instance().deactivateGesture(mUUID); + + LLViewerInventoryItem* item = gInventory.getItem(mUUID); + if (!item) return; + + // Since we just changed the suffix to indicate (active) + // the server doesn't need to know, just the viewer. + gInventory.updateItem(item); + gInventory.notifyObservers(); + } + else if("play" == action) + { + if(!LLGestureMgr::instance().isGestureActive(mUUID)) + { + // we need to inform server about gesture activating to be consistent with LLPreviewGesture and LLGestureComboList. + bool inform_server = true; + bool deactivate_similar = false; + LLGestureMgr::instance().setGestureLoadedCallback(mUUID, boost::bind(&LLGestureBridge::playGesture, mUUID)); + LLViewerInventoryItem* item = gInventory.getItem(mUUID); + llassert(item); + if (item) + { + LLGestureMgr::instance().activateGestureWithAsset(mUUID, item->getAssetUUID(), inform_server, deactivate_similar); + } + } + else + { + playGesture(mUUID); + } + } + else LLItemBridge::performAction(model, action); +} + +void LLGestureBridge::openItem() +{ + LLViewerInventoryItem* item = getItem(); + + if (item) + { + LLInvFVBridgeAction::doAction(item->getType(),mUUID,getInventoryModel()); + } +/* + LLViewerInventoryItem* item = getItem(); + if (item) + { + LLPreviewGesture* preview = LLPreviewGesture::show(mUUID, LLUUID::null); + preview->setFocus(true); + } +*/ +} + +bool LLGestureBridge::removeItem() +{ + // Grab class information locally since *this may be deleted + // within this function. Not a great pattern... + const LLInventoryModel* model = getInventoryModel(); + if(!model) + { + return false; + } + const LLUUID item_id = mUUID; + + // This will also force close the preview window, if it exists. + // This may actually delete *this, if mUUID is in the COF. + LLGestureMgr::instance().deactivateGesture(item_id); + + // If deactivateGesture deleted *this, then return out immediately. + if (!model->getObject(item_id)) + { + return true; + } + + return LLItemBridge::removeItem(); +} + +void LLGestureBridge::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + LL_DEBUGS() << "LLGestureBridge::buildContextMenu()" << LL_ENDL; + menuentry_vec_t items; + menuentry_vec_t disabled_items; + if(isItemInTrash()) + { + addTrashContextMenuOptions(items, disabled_items); + } + else if (isMarketplaceListingsFolder()) + { + addMarketplaceContextMenuOptions(flags, items, disabled_items); + items.push_back(std::string("Properties")); + getClipboardEntries(false, items, disabled_items, flags); + } + else + { + items.push_back(std::string("Share")); + if (!canShare()) + { + disabled_items.push_back(std::string("Share")); + } + + addOpenRightClickMenuOption(items); + items.push_back(std::string("Properties")); + + getClipboardEntries(true, items, disabled_items, flags); + + items.push_back(std::string("Gesture Separator")); + if (LLGestureMgr::instance().isGestureActive(getUUID())) + { + items.push_back(std::string("Deactivate")); + } + else + { + items.push_back(std::string("Activate")); + } + items.push_back(std::string("PlayGesture")); + } + addLinkReplaceMenuOption(items, disabled_items); + hide_context_entries(menu, items, disabled_items); +} + +// static +void LLGestureBridge::playGesture(const LLUUID& item_id) +{ + if (LLGestureMgr::instance().isGesturePlaying(item_id)) + { + LLGestureMgr::instance().stopGesture(item_id); + } + else + { + LLGestureMgr::instance().playGesture(item_id); + } +} + + +// +=================================================+ +// | LLAnimationBridge | +// +=================================================+ + +void LLAnimationBridge::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + menuentry_vec_t items; + menuentry_vec_t disabled_items; + + LL_DEBUGS() << "LLAnimationBridge::buildContextMenu()" << LL_ENDL; + if (isMarketplaceListingsFolder()) + { + addMarketplaceContextMenuOptions(flags, items, disabled_items); + items.push_back(std::string("Properties")); + getClipboardEntries(false, items, disabled_items, flags); + } + else + { + if(isItemInTrash()) + { + addTrashContextMenuOptions(items, disabled_items); + } + else + { + items.push_back(std::string("Share")); + if (!canShare()) + { + disabled_items.push_back(std::string("Share")); + } + items.push_back(std::string("Animation Open")); + items.push_back(std::string("Properties")); + + getClipboardEntries(true, items, disabled_items, flags); + } + + items.push_back(std::string("Animation Separator")); + items.push_back(std::string("Animation Play")); + items.push_back(std::string("Animation Audition")); + } + + addLinkReplaceMenuOption(items, disabled_items); + hide_context_entries(menu, items, disabled_items); +} + +// virtual +void LLAnimationBridge::performAction(LLInventoryModel* model, std::string action) +{ + if ((action == "playworld") || (action == "playlocal")) + { + if (getItem()) + { + LLSD::String activate = "NONE"; + if ("playworld" == action) activate = "Inworld"; + if ("playlocal" == action) activate = "Locally"; + + LLPreviewAnim* preview = LLFloaterReg::showTypedInstance("preview_anim", LLSD(mUUID)); + if (preview) + { + preview->play(activate); + } + } + } + else + { + LLItemBridge::performAction(model, action); + } +} + +void LLAnimationBridge::openItem() +{ + LLViewerInventoryItem* item = getItem(); + + if (item) + { + LLInvFVBridgeAction::doAction(item->getType(),mUUID,getInventoryModel()); + } +/* + LLViewerInventoryItem* item = getItem(); + if (item) + { + LLFloaterReg::showInstance("preview_anim", LLSD(mUUID), TAKE_FOCUS_YES); + } +*/ +} + +// +=================================================+ +// | LLObjectBridge | +// +=================================================+ + +// static +LLUUID LLObjectBridge::sContextMenuItemID; + +LLObjectBridge::LLObjectBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid, + LLInventoryType::EType type, + U32 flags) : + LLItemBridge(inventory, root, uuid) +{ + mAttachPt = (flags & 0xff); // low bye of inventory flags + mIsMultiObject = is_flag_set(flags, LLInventoryItemFlags::II_FLAGS_OBJECT_HAS_MULTIPLE_ITEMS); + mInvType = type; +} + +LLUIImagePtr LLObjectBridge::getIcon() const +{ + return LLInventoryIcon::getIcon(LLAssetType::AT_OBJECT, mInvType, mAttachPt, mIsMultiObject); +} + +LLInventoryObject* LLObjectBridge::getObject() const +{ + LLInventoryObject* object = NULL; + LLInventoryModel* model = getInventoryModel(); + if(model) + { + object = (LLInventoryObject*)model->getObject(mUUID); + } + return object; +} + +LLViewerInventoryItem* LLObjectBridge::getItem() const +{ + LLInventoryModel* model = getInventoryModel(); + if (model) + { + return model->getItem(mUUID); + } + return NULL; +} + +LLViewerInventoryCategory* LLObjectBridge::getCategory() const +{ + LLInventoryModel* model = getInventoryModel(); + if (model) + { + return model->getCategory(mUUID); + } + return NULL; +} + +// virtual +void LLObjectBridge::performAction(LLInventoryModel* model, std::string action) +{ + if (isAddAction(action)) + { + LLUUID object_id = mUUID; + LLViewerInventoryItem* item; + item = (LLViewerInventoryItem*)gInventory.getItem(object_id); + if(item && gInventory.isObjectDescendentOf(object_id, gInventory.getRootFolderID())) + { + rez_attachment(item, NULL, true); // Replace if "Wear"ing. + } + else if(item && item->isFinished()) + { + // must be in library. copy it to our inventory and put it on. + LLPointer cb = new LLBoostFuncInventoryCallback(boost::bind(rez_attachment_cb, _1, (LLViewerJointAttachment*)0)); + copy_inventory_item( + gAgent.getID(), + item->getPermissions().getOwner(), + item->getUUID(), + LLUUID::null, + std::string(), + cb); + } + gFocusMgr.setKeyboardFocus(NULL); + } + else if ("wear_add" == action) + { + LLAppearanceMgr::instance().wearItemOnAvatar(mUUID, true, false); // Don't replace if adding. + } + else if ("touch" == action) + { + handle_attachment_touch(mUUID); + } + else if ("edit" == action) + { + handle_attachment_edit(mUUID); + } + else if (isRemoveAction(action)) + { + LLAppearanceMgr::instance().removeItemFromAvatar(mUUID); + } + else LLItemBridge::performAction(model, action); +} + +void LLObjectBridge::openItem() +{ + // object double-click action is to wear/unwear object + performAction(getInventoryModel(), + get_is_item_worn(mUUID) ? "detach" : "attach"); +} + +std::string LLObjectBridge::getLabelSuffix() const +{ + if (get_is_item_worn(mUUID)) + { + if (!isAgentAvatarValid()) // Error condition, can't figure out attach point + { + return LLItemBridge::getLabelSuffix() + LLTrans::getString("worn"); + } + std::string attachment_point_name; + if (gAgentAvatarp->getAttachedPointName(mUUID, attachment_point_name)) + { + LLStringUtil::format_map_t args; + args["[ATTACHMENT_POINT]"] = LLTrans::getString(attachment_point_name); + + return LLItemBridge::getLabelSuffix() + LLTrans::getString("WornOnAttachmentPoint", args); + } + else + { + LLStringUtil::format_map_t args; + args["[ATTACHMENT_ERROR]"] = LLTrans::getString(attachment_point_name); + return LLItemBridge::getLabelSuffix() + LLTrans::getString("AttachmentErrorMessage", args); + } + } + return LLItemBridge::getLabelSuffix(); +} + +void rez_attachment(LLViewerInventoryItem* item, LLViewerJointAttachment* attachment, bool replace) +{ + const LLUUID& item_id = item->getLinkedUUID(); + + // Check for duplicate request. + if (isAgentAvatarValid() && + gAgentAvatarp->isWearingAttachment(item_id)) + { + LL_WARNS() << "ATT duplicate attachment request, ignoring" << LL_ENDL; + return; + } + + S32 attach_pt = 0; + if (isAgentAvatarValid() && attachment) + { + for (LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); + iter != gAgentAvatarp->mAttachmentPoints.end(); ++iter) + { + if (iter->second == attachment) + { + attach_pt = iter->first; + break; + } + } + } + + LLSD payload; + payload["item_id"] = item_id; // Wear the base object in case this is a link. + payload["attachment_point"] = attach_pt; + payload["is_add"] = !replace; + + if (replace && + (attachment && attachment->getNumObjects() > 0)) + { + LLNotificationsUtil::add("ReplaceAttachment", LLSD(), payload, confirm_attachment_rez); + } + else + { + LLNotifications::instance().forceResponse(LLNotification::Params("ReplaceAttachment").payload(payload), 0/*YES*/); + } +} + +bool confirm_attachment_rez(const LLSD& notification, const LLSD& response) +{ + if (!gAgentAvatarp->canAttachMoreObjects()) + { + LLSD args; + args["MAX_ATTACHMENTS"] = llformat("%d", gAgentAvatarp->getMaxAttachments()); + LLNotificationsUtil::add("MaxAttachmentsOnOutfit", args); + return false; + } + + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0/*YES*/) + { + LLUUID item_id = notification["payload"]["item_id"].asUUID(); + LLViewerInventoryItem* itemp = gInventory.getItem(item_id); + + if (itemp) + { + // Queue up attachments to be sent in next idle tick, this way the + // attachments are batched up all into one message versus each attachment + // being sent in its own separate attachments message. + U8 attachment_pt = notification["payload"]["attachment_point"].asInteger(); + bool is_add = notification["payload"]["is_add"].asBoolean(); + + LL_DEBUGS("Avatar") << "ATT calling addAttachmentRequest " << (itemp ? itemp->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL; + LLAttachmentsMgr::instance().addAttachmentRequest(item_id, attachment_pt, is_add); + } + } + return false; +} +static LLNotificationFunctorRegistration confirm_replace_attachment_rez_reg("ReplaceAttachment", confirm_attachment_rez); + +void LLObjectBridge::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + menuentry_vec_t items; + menuentry_vec_t disabled_items; + if(isItemInTrash()) + { + addTrashContextMenuOptions(items, disabled_items); + } + else if (isMarketplaceListingsFolder()) + { + addMarketplaceContextMenuOptions(flags, items, disabled_items); + items.push_back(std::string("Properties")); + getClipboardEntries(false, items, disabled_items, flags); + } + else + { + items.push_back(std::string("Share")); + if (!canShare()) + { + disabled_items.push_back(std::string("Share")); + } + + items.push_back(std::string("Properties")); + + getClipboardEntries(true, items, disabled_items, flags); + + LLObjectBridge::sContextMenuItemID = mUUID; + + LLInventoryItem *item = getItem(); + if(item) + { + if (!isAgentAvatarValid()) return; + + if( get_is_item_worn( mUUID ) ) + { + items.push_back(std::string("Wearable And Object Separator")); + + items.push_back(std::string("Attachment Touch")); + if ( ((flags & FIRST_SELECTED_ITEM) == 0) || !enable_attachment_touch(mUUID) ) + { + disabled_items.push_back(std::string("Attachment Touch")); + } + + items.push_back(std::string("Wearable Edit")); + if ( ((flags & FIRST_SELECTED_ITEM) == 0) || !get_is_item_editable(mUUID) ) + { + disabled_items.push_back(std::string("Wearable Edit")); + } + + items.push_back(std::string("Detach From Yourself")); + } + else if (!isItemInTrash() && !isLinkedObjectInTrash() && !isLinkedObjectMissing() && !isCOFFolder()) + { + items.push_back(std::string("Wearable And Object Separator")); + items.push_back(std::string("Wearable And Object Wear")); + items.push_back(std::string("Wearable Add")); + items.push_back(std::string("Attach To")); + items.push_back(std::string("Attach To HUD")); + // commented out for DEV-32347 + //items.push_back(std::string("Restore to Last Position")); + + if (!gAgentAvatarp->canAttachMoreObjects()) + { + disabled_items.push_back(std::string("Wearable And Object Wear")); + disabled_items.push_back(std::string("Wearable Add")); + disabled_items.push_back(std::string("Attach To")); + disabled_items.push_back(std::string("Attach To HUD")); + } + LLMenuGL* attach_menu = menu.findChildMenuByName("Attach To", true); + LLMenuGL* attach_hud_menu = menu.findChildMenuByName("Attach To HUD", true); + if (attach_menu + && (attach_menu->getChildCount() == 0) + && attach_hud_menu + && (attach_hud_menu->getChildCount() == 0) + && isAgentAvatarValid()) + { + for (LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); + iter != gAgentAvatarp->mAttachmentPoints.end(); ) + { + LLVOAvatar::attachment_map_t::iterator curiter = iter++; + LLViewerJointAttachment* attachment = curiter->second; + LLMenuItemCallGL::Params p; + std::string submenu_name = attachment->getName(); + if (LLTrans::getString(submenu_name) != "") + { + p.name = (" ")+LLTrans::getString(submenu_name)+" "; + } + else + { + p.name = submenu_name; + } + LLSD cbparams; + cbparams["index"] = curiter->first; + cbparams["label"] = p.name; + p.on_click.function_name = "Inventory.AttachObject"; + p.on_click.parameter = LLSD(attachment->getName()); + p.on_enable.function_name = "Attachment.Label"; + p.on_enable.parameter = cbparams; + LLView* parent = attachment->getIsHUDAttachment() ? attach_hud_menu : attach_menu; + LLUICtrlFactory::create(p, parent); + items.push_back(p.name); + } + } + } + } + } + addLinkReplaceMenuOption(items, disabled_items); + hide_context_entries(menu, items, disabled_items); +} + +bool LLObjectBridge::renameItem(const std::string& new_name) +{ + if(!isItemRenameable()) + return false; + LLPreview::dirty(mUUID); + LLInventoryModel* model = getInventoryModel(); + if(!model) + return false; + LLViewerInventoryItem* item = getItem(); + if(item && (item->getName() != new_name)) + { + LLPointer new_item = new LLViewerInventoryItem(item); + new_item->rename(new_name); + new_item->updateServer(false); + model->updateItem(new_item); + model->notifyObservers(); + buildDisplayName(); + + if (isAgentAvatarValid()) + { + LLViewerObject* obj = gAgentAvatarp->getWornAttachment( item->getUUID() ); + if(obj) + { + LLSelectMgr::getInstance()->deselectAll(); + LLSelectMgr::getInstance()->addAsIndividual( obj, SELECT_ALL_TES, false ); + LLSelectMgr::getInstance()->selectionSetObjectName( new_name ); + LLSelectMgr::getInstance()->deselectAll(); + } + } + } + // return false because we either notified observers (& therefore + // rebuilt) or we didn't update. + return false; +} + +// +=================================================+ +// | LLLSLTextBridge | +// +=================================================+ + +void LLLSLTextBridge::openItem() +{ + LLViewerInventoryItem* item = getItem(); + + if (item) + { + LLInvFVBridgeAction::doAction(item->getType(),mUUID,getInventoryModel()); + } +} + +// +=================================================+ +// | LLWearableBridge | +// +=================================================+ + +LLWearableBridge::LLWearableBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid, + LLAssetType::EType asset_type, + LLInventoryType::EType inv_type, + LLWearableType::EType wearable_type) : + LLItemBridge(inventory, root, uuid), + mAssetType( asset_type ), + mWearableType(wearable_type) +{ + mInvType = inv_type; +} + +bool LLWearableBridge::renameItem(const std::string& new_name) +{ + if (get_is_item_worn(mUUID)) + { + gAgentWearables.setWearableName( mUUID, new_name ); + } + return LLItemBridge::renameItem(new_name); +} + +std::string LLWearableBridge::getLabelSuffix() const +{ + if (get_is_item_worn(mUUID)) + { + // e.g. "(worn)" + return LLItemBridge::getLabelSuffix() + LLTrans::getString("worn"); + } + else + { + return LLItemBridge::getLabelSuffix(); + } +} + +LLUIImagePtr LLWearableBridge::getIcon() const +{ + return LLInventoryIcon::getIcon(mAssetType, mInvType, mWearableType, false); +} + +// virtual +void LLWearableBridge::performAction(LLInventoryModel* model, std::string action) +{ + if (isAddAction(action)) + { + wearOnAvatar(); + } + else if ("wear_add" == action) + { + wearAddOnAvatar(); + } + else if ("edit" == action) + { + editOnAvatar(); + return; + } + else if (isRemoveAction(action)) + { + removeFromAvatar(); + return; + } + else LLItemBridge::performAction(model, action); +} + +void LLWearableBridge::openItem() +{ + performAction(getInventoryModel(), + get_is_item_worn(mUUID) ? "take_off" : "wear"); +} + +void LLWearableBridge::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + LL_DEBUGS() << "LLWearableBridge::buildContextMenu()" << LL_ENDL; + menuentry_vec_t items; + menuentry_vec_t disabled_items; + if(isItemInTrash()) + { + addTrashContextMenuOptions(items, disabled_items); + } + else if (isMarketplaceListingsFolder()) + { + addMarketplaceContextMenuOptions(flags, items, disabled_items); + items.push_back(std::string("Properties")); + getClipboardEntries(false, items, disabled_items, flags); + } + else + { // FWIW, it looks like SUPPRESS_OPEN_ITEM is not set anywhere + bool can_open = ((flags & SUPPRESS_OPEN_ITEM) != SUPPRESS_OPEN_ITEM); + + // If we have clothing, don't add "Open" as it's the same action as "Wear" SL-18976 + LLViewerInventoryItem* item = getItem(); + if (can_open && item) + { + can_open = (item->getType() != LLAssetType::AT_CLOTHING) && + (item->getType() != LLAssetType::AT_BODYPART); + } + if (isLinkedObjectMissing()) + { + can_open = false; + } + items.push_back(std::string("Share")); + if (!canShare()) + { + disabled_items.push_back(std::string("Share")); + } + + if (can_open) + { + addOpenRightClickMenuOption(items); + } + else + { + disabled_items.push_back(std::string("Open")); + disabled_items.push_back(std::string("Open Original")); + } + + items.push_back(std::string("Properties")); + + getClipboardEntries(true, items, disabled_items, flags); + + items.push_back(std::string("Wearable And Object Separator")); + items.push_back(std::string("Wearable Edit")); + + if (((flags & FIRST_SELECTED_ITEM) == 0) || (item && !gAgentWearables.isWearableModifiable(item->getUUID()))) + { + disabled_items.push_back(std::string("Wearable Edit")); + } + // Don't allow items to be worn if their baseobj is in the trash. + if (isLinkedObjectInTrash() || isLinkedObjectMissing() || isCOFFolder()) + { + disabled_items.push_back(std::string("Wearable And Object Wear")); + disabled_items.push_back(std::string("Wearable Add")); + disabled_items.push_back(std::string("Wearable Edit")); + } + + // Disable wear and take off based on whether the item is worn. + if(item) + { + switch (item->getType()) + { + case LLAssetType::AT_CLOTHING: + items.push_back(std::string("Take Off")); + // Fallthrough since clothing and bodypart share wear options + case LLAssetType::AT_BODYPART: + if (get_is_item_worn(item->getUUID())) + { + disabled_items.push_back(std::string("Wearable And Object Wear")); + disabled_items.push_back(std::string("Wearable Add")); + } + else + { + items.push_back(std::string("Wearable And Object Wear")); + disabled_items.push_back(std::string("Take Off")); + disabled_items.push_back(std::string("Wearable Edit")); + } + + if (LLWearableType::getInstance()->getAllowMultiwear(mWearableType)) + { + items.push_back(std::string("Wearable Add")); + if (!gAgentWearables.canAddWearable(mWearableType)) + { + disabled_items.push_back(std::string("Wearable Add")); + } + } + break; + default: + break; + } + } + } + addLinkReplaceMenuOption(items, disabled_items); + hide_context_entries(menu, items, disabled_items); +} + +// Called from menus +// static +bool LLWearableBridge::canWearOnAvatar(void* user_data) +{ + LLWearableBridge* self = (LLWearableBridge*)user_data; + if(!self) return false; + if(!self->isAgentInventory()) + { + LLViewerInventoryItem* item = (LLViewerInventoryItem*)self->getItem(); + if(!item || !item->isFinished()) return false; + } + return (!get_is_item_worn(self->mUUID)); +} + +// Called from menus +// static +void LLWearableBridge::onWearOnAvatar(void* user_data) +{ + LLWearableBridge* self = (LLWearableBridge*)user_data; + if(!self) return; + self->wearOnAvatar(); +} + +void LLWearableBridge::wearOnAvatar() +{ + // TODO: investigate wearables may not be loaded at this point EXT-8231 + + LLViewerInventoryItem* item = getItem(); + if(item) + { + LLAppearanceMgr::instance().wearItemOnAvatar(item->getUUID(), true, true); + } +} + +void LLWearableBridge::wearAddOnAvatar() +{ + // TODO: investigate wearables may not be loaded at this point EXT-8231 + + LLViewerInventoryItem* item = getItem(); + if(item) + { + LLAppearanceMgr::instance().wearItemOnAvatar(item->getUUID(), true, false); + } +} + +// static +void LLWearableBridge::onWearOnAvatarArrived( LLViewerWearable* wearable, void* userdata ) +{ + LLUUID* item_id = (LLUUID*) userdata; + if(wearable) + { + LLViewerInventoryItem* item = NULL; + item = (LLViewerInventoryItem*)gInventory.getItem(*item_id); + if(item) + { + if(item->getAssetUUID() == wearable->getAssetID()) + { + gAgentWearables.setWearableItem(item, wearable); + gInventory.notifyObservers(); + //self->getFolderItem()->refreshFromRoot(); + } + else + { + LL_INFOS() << "By the time wearable asset arrived, its inv item already pointed to a different asset." << LL_ENDL; + } + } + } + delete item_id; +} + +// static +// BAP remove the "add" code path once everything is fully COF-ified. +void LLWearableBridge::onWearAddOnAvatarArrived( LLViewerWearable* wearable, void* userdata ) +{ + LLUUID* item_id = (LLUUID*) userdata; + if(wearable) + { + LLViewerInventoryItem* item = NULL; + item = (LLViewerInventoryItem*)gInventory.getItem(*item_id); + if(item) + { + if(item->getAssetUUID() == wearable->getAssetID()) + { + bool do_append = true; + gAgentWearables.setWearableItem(item, wearable, do_append); + gInventory.notifyObservers(); + //self->getFolderItem()->refreshFromRoot(); + } + else + { + LL_INFOS() << "By the time wearable asset arrived, its inv item already pointed to a different asset." << LL_ENDL; + } + } + } + delete item_id; +} + +// static +bool LLWearableBridge::canEditOnAvatar(void* user_data) +{ + LLWearableBridge* self = (LLWearableBridge*)user_data; + if(!self) return false; + + return (get_is_item_worn(self->mUUID)); +} + +// static +void LLWearableBridge::onEditOnAvatar(void* user_data) +{ + LLWearableBridge* self = (LLWearableBridge*)user_data; + if(self) + { + self->editOnAvatar(); + } +} + +void LLWearableBridge::editOnAvatar() +{ + LLAgentWearables::editWearable(mUUID); +} + +// static +bool LLWearableBridge::canRemoveFromAvatar(void* user_data) +{ + LLWearableBridge* self = (LLWearableBridge*)user_data; + if( self && (LLAssetType::AT_BODYPART != self->mAssetType) ) + { + return get_is_item_worn( self->mUUID ); + } + return false; +} + +void LLWearableBridge::removeFromAvatar() +{ + LL_WARNS() << "safe to remove?" << LL_ENDL; + if (get_is_item_worn(mUUID)) + { + LLAppearanceMgr::instance().removeItemFromAvatar(mUUID); + } +} + + +// +=================================================+ +// | LLLinkItemBridge | +// +=================================================+ +// For broken item links + +std::string LLLinkItemBridge::sPrefix("Link: "); + +void LLLinkItemBridge::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + // *TODO: Translate + LL_DEBUGS() << "LLLink::buildContextMenu()" << LL_ENDL; + menuentry_vec_t items; + menuentry_vec_t disabled_items; + + items.push_back(std::string("Find Original")); + disabled_items.push_back(std::string("Find Original")); + + if(isItemInTrash()) + { + addTrashContextMenuOptions(items, disabled_items); + } + else + { + items.push_back(std::string("Properties")); + addDeleteContextMenuOptions(items, disabled_items); + } + addLinkReplaceMenuOption(items, disabled_items); + hide_context_entries(menu, items, disabled_items); +} + +// +=================================================+ +// | LLSettingsBridge | +// +=================================================+ + +LLSettingsBridge::LLSettingsBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid, + LLSettingsType::type_e settings_type): + LLItemBridge(inventory, root, uuid), + mSettingsType(settings_type) +{ +} + +LLUIImagePtr LLSettingsBridge::getIcon() const +{ + return LLInventoryIcon::getIcon(LLAssetType::AT_SETTINGS, LLInventoryType::IT_SETTINGS, mSettingsType, false); +} + +void LLSettingsBridge::performAction(LLInventoryModel* model, std::string action) +{ + if ("apply_settings_local" == action) + { + // Single item only + LLViewerInventoryItem* item = static_cast(getItem()); + if (!item) + return; + LLUUID asset_id = item->getAssetUUID(); + LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, asset_id, LLEnvironment::TRANSITION_INSTANT); + LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); + } + else if ("apply_settings_parcel" == action) + { + // Single item only + LLViewerInventoryItem* item = static_cast(getItem()); + if (!item) + return; + LLUUID asset_id = item->getAssetUUID(); + std::string name = item->getName(); + + U32 flags(0); + + if (!item->getPermissions().allowOperationBy(PERM_MODIFY, gAgent.getID())) + flags |= LLSettingsBase::FLAG_NOMOD; + if (!item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID())) + flags |= LLSettingsBase::FLAG_NOTRANS; + + LLParcel *parcel = LLViewerParcelMgr::instance().getAgentOrSelectedParcel(); + if (!parcel) + { + LL_WARNS("INVENTORY") << "could not identify parcel." << LL_ENDL; + return; + } + S32 parcel_id = parcel->getLocalID(); + + LL_DEBUGS("ENVIRONMENT") << "Applying asset ID " << asset_id << " to parcel " << parcel_id << LL_ENDL; + LLEnvironment::instance().updateParcel(parcel_id, asset_id, name, LLEnvironment::NO_TRACK, -1, -1, flags); + LLEnvironment::instance().setSharedEnvironment(); + } + else + LLItemBridge::performAction(model, action); +} + +void LLSettingsBridge::openItem() +{ + LLViewerInventoryItem* item = getItem(); + if (item) + { + if (item->getPermissions().getOwner() != gAgent.getID()) + LLNotificationsUtil::add("NoEditFromLibrary"); + else + LLInvFVBridgeAction::doAction(item->getType(), mUUID, getInventoryModel()); + } +} + +void LLSettingsBridge::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + LL_DEBUGS() << "LLSettingsBridge::buildContextMenu()" << LL_ENDL; + menuentry_vec_t items; + menuentry_vec_t disabled_items; + + if (isMarketplaceListingsFolder()) + { + menuentry_vec_t items; + menuentry_vec_t disabled_items; + addMarketplaceContextMenuOptions(flags, items, disabled_items); + items.push_back(std::string("Properties")); + getClipboardEntries(false, items, disabled_items, flags); + hide_context_entries(menu, items, disabled_items); + } + else if (isItemInTrash()) + { + addTrashContextMenuOptions(items, disabled_items); + } + else + { + items.push_back(std::string("Share")); + if (!canShare()) + { + disabled_items.push_back(std::string("Share")); + } + + addOpenRightClickMenuOption(items); + items.push_back(std::string("Properties")); + + getClipboardEntries(true, items, disabled_items, flags); + + items.push_back("Settings Separator"); + items.push_back("Settings Apply Local"); + + items.push_back("Settings Apply Parcel"); + if (!canUpdateParcel()) + disabled_items.push_back("Settings Apply Parcel"); + + items.push_back("Settings Apply Region"); + if (!canUpdateRegion()) + disabled_items.push_back("Settings Apply Region"); + } + addLinkReplaceMenuOption(items, disabled_items); + hide_context_entries(menu, items, disabled_items); +} + +bool LLSettingsBridge::renameItem(const std::string& new_name) +{ + /*TODO: change internal settings name? */ + return LLItemBridge::renameItem(new_name); +} + +bool LLSettingsBridge::isItemRenameable() const +{ + LLViewerInventoryItem* item = getItem(); + if (item) + { + return (item->getPermissions().allowModifyBy(gAgent.getID())); + } + return false; +} + +bool LLSettingsBridge::canUpdateParcel() const +{ + return LLEnvironment::instance().canAgentUpdateParcelEnvironment(); +} + +bool LLSettingsBridge::canUpdateRegion() const +{ + return LLEnvironment::instance().canAgentUpdateRegionEnvironment(); +} + + +// +=================================================+ +// | LLMaterialBridge | +// +=================================================+ + +void LLMaterialBridge::openItem() +{ + LLViewerInventoryItem* item = getItem(); + if (item) + { + LLInvFVBridgeAction::doAction(item->getType(),mUUID,getInventoryModel()); + } +} + +void LLMaterialBridge::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + LL_DEBUGS() << "LLMaterialBridge::buildContextMenu()" << LL_ENDL; + + if (isMarketplaceListingsFolder()) + { + menuentry_vec_t items; + menuentry_vec_t disabled_items; + addMarketplaceContextMenuOptions(flags, items, disabled_items); + items.push_back(std::string("Properties")); + getClipboardEntries(false, items, disabled_items, flags); + hide_context_entries(menu, items, disabled_items); + } + else + { + LLItemBridge::buildContextMenu(menu, flags); + } +} + + +// +=================================================+ +// | LLLinkBridge | +// +=================================================+ +// For broken folder links. +std::string LLLinkFolderBridge::sPrefix("Link: "); +LLUIImagePtr LLLinkFolderBridge::getIcon() const +{ + LLFolderType::EType folder_type = LLFolderType::FT_NONE; + const LLInventoryObject *obj = getInventoryObject(); + if (obj) + { + LLViewerInventoryCategory* cat = NULL; + LLInventoryModel* model = getInventoryModel(); + if(model) + { + cat = (LLViewerInventoryCategory*)model->getCategory(obj->getLinkedUUID()); + if (cat) + { + folder_type = cat->getPreferredType(); + } + } + } + return LLFolderBridge::getIcon(folder_type); +} + +void LLLinkFolderBridge::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + // *TODO: Translate + LL_DEBUGS() << "LLLink::buildContextMenu()" << LL_ENDL; + menuentry_vec_t items; + menuentry_vec_t disabled_items; + + if (isItemInTrash()) + { + addTrashContextMenuOptions(items, disabled_items); + } + else + { + items.push_back(std::string("Find Original")); + addDeleteContextMenuOptions(items, disabled_items); + } + hide_context_entries(menu, items, disabled_items); +} +void LLLinkFolderBridge::performAction(LLInventoryModel* model, std::string action) +{ + if ("goto" == action) + { + gotoItem(); + return; + } + LLItemBridge::performAction(model,action); +} +void LLLinkFolderBridge::gotoItem() +{ + LLItemBridge::gotoItem(); + + const LLUUID &cat_uuid = getFolderID(); + if (!cat_uuid.isNull()) + { + LLFolderViewItem *base_folder = LLInventoryPanel::getActiveInventoryPanel()->getItemByID(cat_uuid); + if (base_folder) + { + base_folder->setOpen(true); + } + } +} +const LLUUID &LLLinkFolderBridge::getFolderID() const +{ + if (LLViewerInventoryItem *link_item = getItem()) + { + if (const LLViewerInventoryCategory *cat = link_item->getLinkedCategory()) + { + const LLUUID& cat_uuid = cat->getUUID(); + return cat_uuid; + } + } + return LLUUID::null; +} + +void LLUnknownItemBridge::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + menuentry_vec_t items; + menuentry_vec_t disabled_items; + items.push_back(std::string("Properties")); + items.push_back(std::string("Rename")); + hide_context_entries(menu, items, disabled_items); +} + +LLUIImagePtr LLUnknownItemBridge::getIcon() const +{ + return LLInventoryIcon::getIcon(LLAssetType::AT_UNKNOWN, mInvType); +} + + +/******************************************************************************** + ** + ** BRIDGE ACTIONS + **/ + +// static +void LLInvFVBridgeAction::doAction(LLAssetType::EType asset_type, + const LLUUID& uuid, + LLInventoryModel* model) +{ + // Perform indirection in case of link. + const LLUUID& linked_uuid = gInventory.getLinkedItemID(uuid); + + LLInvFVBridgeAction* action = createAction(asset_type,linked_uuid,model); + if(action) + { + action->doIt(); + delete action; + } +} + +// static +void LLInvFVBridgeAction::doAction(const LLUUID& uuid, LLInventoryModel* model) +{ + llassert(model); + LLViewerInventoryItem* item = model->getItem(uuid); + llassert(item); + if (item) + { + LLAssetType::EType asset_type = item->getType(); + LLInvFVBridgeAction* action = createAction(asset_type,uuid,model); + if(action) + { + action->doIt(); + delete action; + } + } +} + +LLViewerInventoryItem* LLInvFVBridgeAction::getItem() const +{ + if (mModel) + return (LLViewerInventoryItem*)mModel->getItem(mUUID); + return NULL; +} + +class LLTextureBridgeAction: public LLInvFVBridgeAction +{ + friend class LLInvFVBridgeAction; +public: + virtual void doIt() + { + if (getItem()) + { + LLFloaterReg::showInstance("preview_texture", LLSD(mUUID), TAKE_FOCUS_YES); + } + LLInvFVBridgeAction::doIt(); + } + virtual ~LLTextureBridgeAction(){} +protected: + LLTextureBridgeAction(const LLUUID& id,LLInventoryModel* model) : LLInvFVBridgeAction(id,model) {} +}; + +class LLSoundBridgeAction: public LLInvFVBridgeAction +{ + friend class LLInvFVBridgeAction; +public: + virtual void doIt() + { + LLViewerInventoryItem* item = getItem(); + if (item) + { + send_sound_trigger(item->getAssetUUID(), SOUND_GAIN); + } + LLInvFVBridgeAction::doIt(); + } + virtual ~LLSoundBridgeAction(){} +protected: + LLSoundBridgeAction(const LLUUID& id,LLInventoryModel* model) : LLInvFVBridgeAction(id,model) {} +}; + +class LLLandmarkBridgeAction: public LLInvFVBridgeAction +{ + friend class LLInvFVBridgeAction; +public: + virtual void doIt() + { + LLViewerInventoryItem* item = getItem(); + if (item) + { + // Opening (double-clicking) a landmark immediately teleports, + // but warns you the first time. + LLSD payload; + payload["asset_id"] = item->getAssetUUID(); + + LLSD args; + args["LOCATION"] = item->getName(); + + LLNotificationsUtil::add("TeleportFromLandmark", args, payload); + } + LLInvFVBridgeAction::doIt(); + } + virtual ~LLLandmarkBridgeAction(){} +protected: + LLLandmarkBridgeAction(const LLUUID& id,LLInventoryModel* model) : LLInvFVBridgeAction(id,model) {} +}; + +class LLCallingCardBridgeAction: public LLInvFVBridgeAction +{ + friend class LLInvFVBridgeAction; +public: + virtual void doIt() + { + LLViewerInventoryItem* item = getItem(); + if (item && item->getCreatorUUID().notNull()) + { + LLAvatarActions::showProfile(item->getCreatorUUID()); + } + LLInvFVBridgeAction::doIt(); + } + virtual ~LLCallingCardBridgeAction(){} +protected: + LLCallingCardBridgeAction(const LLUUID& id,LLInventoryModel* model) : LLInvFVBridgeAction(id,model) {} + +}; + +class LLNotecardBridgeAction +: public LLInvFVBridgeAction +{ + friend class LLInvFVBridgeAction; +public: + virtual void doIt() + { + LLViewerInventoryItem* item = getItem(); + if (item) + { + LLFloaterReg::showInstance("preview_notecard", LLSD(item->getUUID()), TAKE_FOCUS_YES); + } + LLInvFVBridgeAction::doIt(); + } + virtual ~LLNotecardBridgeAction(){} +protected: + LLNotecardBridgeAction(const LLUUID& id,LLInventoryModel* model) : LLInvFVBridgeAction(id,model) {} +}; + +class LLGestureBridgeAction: public LLInvFVBridgeAction +{ + friend class LLInvFVBridgeAction; +public: + virtual void doIt() + { + LLViewerInventoryItem* item = getItem(); + if (item) + { + LLPreviewGesture* preview = LLPreviewGesture::show(mUUID, LLUUID::null); + preview->setFocus(true); + } + LLInvFVBridgeAction::doIt(); + } + virtual ~LLGestureBridgeAction(){} +protected: + LLGestureBridgeAction(const LLUUID& id,LLInventoryModel* model) : LLInvFVBridgeAction(id,model) {} +}; + +class LLAnimationBridgeAction: public LLInvFVBridgeAction +{ + friend class LLInvFVBridgeAction; +public: + virtual void doIt() + { + LLViewerInventoryItem* item = getItem(); + if (item) + { + LLFloaterReg::showInstance("preview_anim", LLSD(mUUID), TAKE_FOCUS_YES); + } + LLInvFVBridgeAction::doIt(); + } + virtual ~LLAnimationBridgeAction(){} +protected: + LLAnimationBridgeAction(const LLUUID& id,LLInventoryModel* model) : LLInvFVBridgeAction(id,model) {} +}; + +class LLObjectBridgeAction: public LLInvFVBridgeAction +{ + friend class LLInvFVBridgeAction; +public: + virtual void doIt() + { + attachOrDetach(); + } + virtual ~LLObjectBridgeAction(){} +protected: + LLObjectBridgeAction(const LLUUID& id,LLInventoryModel* model) : LLInvFVBridgeAction(id,model) {} + void attachOrDetach(); +}; + +void LLObjectBridgeAction::attachOrDetach() +{ + if (get_is_item_worn(mUUID)) + { + LLAppearanceMgr::instance().removeItemFromAvatar(mUUID); + } + else + { + LLAppearanceMgr::instance().wearItemOnAvatar(mUUID, true, false); // Don't replace if adding. + } +} + +class LLLSLTextBridgeAction: public LLInvFVBridgeAction +{ + friend class LLInvFVBridgeAction; +public: + virtual void doIt() + { + LLViewerInventoryItem* item = getItem(); + if (item) + { + LLFloaterReg::showInstance("preview_script", LLSD(mUUID), TAKE_FOCUS_YES); + } + LLInvFVBridgeAction::doIt(); + } + virtual ~LLLSLTextBridgeAction(){} +protected: + LLLSLTextBridgeAction(const LLUUID& id,LLInventoryModel* model) : LLInvFVBridgeAction(id,model) {} +}; + +class LLWearableBridgeAction: public LLInvFVBridgeAction +{ + friend class LLInvFVBridgeAction; +public: + virtual void doIt() + { + wearOnAvatar(); + } + + virtual ~LLWearableBridgeAction(){} +protected: + LLWearableBridgeAction(const LLUUID& id,LLInventoryModel* model) : LLInvFVBridgeAction(id,model) {} + bool isItemInTrash() const; + // return true if the item is in agent inventory. if false, it + // must be lost or in the inventory library. + bool isAgentInventory() const; + void wearOnAvatar(); +}; + +bool LLWearableBridgeAction::isItemInTrash() const +{ + if(!mModel) return false; + const LLUUID trash_id = mModel->findCategoryUUIDForType(LLFolderType::FT_TRASH); + return mModel->isObjectDescendentOf(mUUID, trash_id); +} + +bool LLWearableBridgeAction::isAgentInventory() const +{ + if(!mModel) return false; + if(gInventory.getRootFolderID() == mUUID) return true; + return mModel->isObjectDescendentOf(mUUID, gInventory.getRootFolderID()); +} + +void LLWearableBridgeAction::wearOnAvatar() +{ + // TODO: investigate wearables may not be loaded at this point EXT-8231 + + LLViewerInventoryItem* item = getItem(); + if(item) + { + if (get_is_item_worn(mUUID)) + { + if(item->getType() != LLAssetType::AT_BODYPART) + { + LLAppearanceMgr::instance().removeItemFromAvatar(item->getUUID()); + } + } + else + { + LLAppearanceMgr::instance().wearItemOnAvatar(item->getUUID(), true, true); + } + } +} + +class LLSettingsBridgeAction + : public LLInvFVBridgeAction +{ + friend class LLInvFVBridgeAction; +public: + virtual void doIt() + { + LLViewerInventoryItem* item = getItem(); + if (item) + { + LLSettingsType::type_e type = item->getSettingsType(); + switch (type) + { + case LLSettingsType::ST_SKY: + LLFloaterReg::showInstance("env_fixed_environmentent_sky", LLSDMap("inventory_id", item->getUUID()), TAKE_FOCUS_YES); + break; + case LLSettingsType::ST_WATER: + LLFloaterReg::showInstance("env_fixed_environmentent_water", LLSDMap("inventory_id", item->getUUID()), TAKE_FOCUS_YES); + break; + case LLSettingsType::ST_DAYCYCLE: + LLFloaterReg::showInstance("env_edit_extdaycycle", LLSDMap("inventory_id", item->getUUID())("edit_context", "inventory"), TAKE_FOCUS_YES); + break; + default: + break; + } + } + LLInvFVBridgeAction::doIt(); + } + virtual ~LLSettingsBridgeAction(){} +protected: + LLSettingsBridgeAction(const LLUUID& id, LLInventoryModel* model) : LLInvFVBridgeAction(id, model) {} +}; + +class LLMaterialBridgeAction : public LLInvFVBridgeAction +{ + friend class LLInvFVBridgeAction; +public: + void doIt() override + { + LLViewerInventoryItem* item = getItem(); + if (item) + { + LLFloaterReg::showInstance("material_editor", LLSD(item->getUUID()), TAKE_FOCUS_YES); + } + LLInvFVBridgeAction::doIt(); + } + ~LLMaterialBridgeAction() = default; +private: + LLMaterialBridgeAction(const LLUUID& id,LLInventoryModel* model) : LLInvFVBridgeAction(id,model) {} +}; + + +LLInvFVBridgeAction* LLInvFVBridgeAction::createAction(LLAssetType::EType asset_type, + const LLUUID& uuid, + LLInventoryModel* model) +{ + LLInvFVBridgeAction* action = NULL; + switch(asset_type) + { + case LLAssetType::AT_TEXTURE: + action = new LLTextureBridgeAction(uuid,model); + break; + case LLAssetType::AT_SOUND: + action = new LLSoundBridgeAction(uuid,model); + break; + case LLAssetType::AT_LANDMARK: + action = new LLLandmarkBridgeAction(uuid,model); + break; + case LLAssetType::AT_CALLINGCARD: + action = new LLCallingCardBridgeAction(uuid,model); + break; + case LLAssetType::AT_OBJECT: + action = new LLObjectBridgeAction(uuid,model); + break; + case LLAssetType::AT_NOTECARD: + action = new LLNotecardBridgeAction(uuid,model); + break; + case LLAssetType::AT_ANIMATION: + action = new LLAnimationBridgeAction(uuid,model); + break; + case LLAssetType::AT_GESTURE: + action = new LLGestureBridgeAction(uuid,model); + break; + case LLAssetType::AT_LSL_TEXT: + action = new LLLSLTextBridgeAction(uuid,model); + break; + case LLAssetType::AT_CLOTHING: + case LLAssetType::AT_BODYPART: + action = new LLWearableBridgeAction(uuid,model); + break; + case LLAssetType::AT_SETTINGS: + action = new LLSettingsBridgeAction(uuid, model); + break; + case LLAssetType::AT_MATERIAL: + action = new LLMaterialBridgeAction(uuid, model); + break; + default: + break; + } + return action; +} + +/** Bridge Actions + ** + ********************************************************************************/ + +/************************************************************************/ +/* Recent Inventory Panel related classes */ +/************************************************************************/ +void LLRecentItemsFolderBridge::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + menuentry_vec_t disabled_items, items; + buildContextMenuOptions(flags, items, disabled_items); + + items.erase(std::remove(items.begin(), items.end(), std::string("New Folder")), items.end()); + + hide_context_entries(menu, items, disabled_items); +} + +LLInvFVBridge* LLRecentInventoryBridgeBuilder::createBridge( + LLAssetType::EType asset_type, + LLAssetType::EType actual_asset_type, + LLInventoryType::EType inv_type, + LLInventoryPanel* inventory, + LLFolderViewModelInventory* view_model, + LLFolderView* root, + const LLUUID& uuid, + U32 flags /*= 0x00*/ ) const +{ + LLInvFVBridge* new_listener = NULL; + if (asset_type == LLAssetType::AT_CATEGORY + && actual_asset_type != LLAssetType::AT_LINK_FOLDER) + { + new_listener = new LLRecentItemsFolderBridge(inv_type, inventory, root, uuid); + } + else + { + new_listener = LLInventoryFolderViewModelBuilder::createBridge(asset_type, + actual_asset_type, + inv_type, + inventory, + view_model, + root, + uuid, + flags); + } + return new_listener; +} + +LLFolderViewGroupedItemBridge::LLFolderViewGroupedItemBridge() +{ +} + +void LLFolderViewGroupedItemBridge::groupFilterContextMenu(folder_view_item_deque& selected_items, LLMenuGL& menu) +{ + uuid_vec_t ids; + menuentry_vec_t disabled_items; + if (get_selection_item_uuids(selected_items, ids)) + { + if (!LLAppearanceMgr::instance().canAddWearables(ids) && canWearSelected(ids)) + { + disabled_items.push_back(std::string("Wearable And Object Wear")); + disabled_items.push_back(std::string("Wearable Add")); + disabled_items.push_back(std::string("Attach To")); + disabled_items.push_back(std::string("Attach To HUD")); + } + } + disable_context_entries_if_present(menu, disabled_items); +} + +bool LLFolderViewGroupedItemBridge::canWearSelected(const uuid_vec_t& item_ids) const +{ + for (uuid_vec_t::const_iterator it = item_ids.begin(); it != item_ids.end(); ++it) + { + const LLViewerInventoryItem* item = gInventory.getItem(*it); + if (!item || (item->getType() >= LLAssetType::AT_COUNT) || (item->getType() <= LLAssetType::AT_NONE)) + { + return false; + } + } + return true; +} +// EOF diff --git a/indra/newview/llinventorybridge.h b/indra/newview/llinventorybridge.h index 3d250a02d0..746b79ce87 100644 --- a/indra/newview/llinventorybridge.h +++ b/indra/newview/llinventorybridge.h @@ -1,825 +1,825 @@ -/** - * @file llinventorybridge.h - * @brief Implementation of the Inventory-Folder-View-Bridge classes. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLINVENTORYBRIDGE_H -#define LL_LLINVENTORYBRIDGE_H - -#include "llcallingcard.h" -#include "llfolderviewmodel.h" -#include "llinventorymodel.h" -#include "llinventoryobserver.h" -#include "llinventorypanel.h" -#include "llviewercontrol.h" -#include "llviewerwearable.h" -#include "lltooldraganddrop.h" -#include "lllandmarklist.h" -#include "llfolderviewitem.h" -#include "llsettingsbase.h" - -class LLInventoryFilter; -class LLInventoryPanel; -class LLInventoryModel; -class LLMenuGL; -class LLCallingCardObserver; -class LLViewerJointAttachment; -class LLFolderView; -struct LLMoveInv; - -typedef std::vector menuentry_vec_t; -typedef std::pair two_uuids_t; -typedef std::list two_uuids_list_t; -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLInvFVBridge -// -// Short for Inventory-Folder-View-Bridge. This is an -// implementation class to be able to view inventory items. -// -// You'll want to call LLInvItemFVELister::createBridge() to actually create -// an instance of this class. This helps encapsulate the -// functionality a bit. (except for folders, you can create those -// manually...) -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLInvFVBridge : public LLFolderViewModelItemInventory -{ -public: - // This method is a convenience function which creates the correct - // type of bridge based on some basic information - static LLInvFVBridge* createBridge(LLAssetType::EType asset_type, - LLAssetType::EType actual_asset_type, - LLInventoryType::EType inv_type, - LLInventoryPanel* inventory, - LLFolderViewModelInventory* view_model, - LLFolderView* root, - const LLUUID& uuid, - U32 flags = 0x00); - virtual ~LLInvFVBridge() {} - - bool canShare() const; - bool canListOnMarketplace() const; - bool canListOnMarketplaceNow() const; - - //-------------------------------------------------------------------- - // LLInvFVBridge functionality - //-------------------------------------------------------------------- - virtual const LLUUID& getUUID() const { return mUUID; } - virtual const LLUUID& getThumbnailUUID() const { return LLUUID::null; } - virtual void clearDisplayName() { mDisplayName.clear(); } - virtual void restoreItem() {} - virtual void restoreToWorld() {} - - //-------------------------------------------------------------------- - // Inherited LLFolderViewModelItemInventory functions - //-------------------------------------------------------------------- - virtual const std::string& getName() const; - virtual const std::string& getDisplayName() const; - const std::string& getSearchableName() const { return mSearchableName; } - - std::string getSearchableDescription() const; - std::string getSearchableCreatorName() const; - std::string getSearchableUUIDString() const; - - virtual PermissionMask getPermissionMask() const; - virtual LLFolderType::EType getPreferredType() const; - virtual time_t getCreationDate() const; - virtual void setCreationDate(time_t creation_date_utc); - virtual LLFontGL::StyleFlags getLabelStyle() const { return LLFontGL::NORMAL; } - virtual std::string getLabelSuffix() const { return LLStringUtil::null; } - virtual void openItem() {} - virtual void closeItem() {} - virtual void navigateToFolder(bool new_window = false, bool change_mode = false); - virtual void showProperties(); - virtual bool isItemRenameable() const { return true; } - virtual bool isMultiPreviewAllowed() { return true; } - //virtual bool renameItem(const std::string& new_name) {} - virtual bool isItemRemovable(bool check_worn = true) const; - virtual bool isItemMovable() const; - virtual bool isItemInTrash() const; - virtual bool isItemInOutfits() const; - virtual bool isLink() const; - virtual bool isLibraryItem() const; - //virtual bool removeItem() = 0; - virtual void removeBatch(std::vector& batch); - virtual void move(LLFolderViewModelItem* new_parent_bridge) {} - virtual bool isItemCopyable(bool can_copy_as_link = true) const { return false; } - virtual bool copyToClipboard() const; - virtual bool cutToClipboard(); - virtual bool isCutToClipboard(); - virtual bool isClipboardPasteable() const; - virtual bool isClipboardPasteableAsLink() const; - virtual void pasteFromClipboard() {} - virtual void pasteLinkFromClipboard() {} - void getClipboardEntries(bool show_asset_id, menuentry_vec_t &items, - menuentry_vec_t &disabled_items, U32 flags); - virtual void buildContextMenu(LLMenuGL& menu, U32 flags); - virtual LLToolDragAndDrop::ESource getDragSource() const; - virtual bool startDrag(EDragAndDropType* type, LLUUID* id) const; - virtual bool dragOrDrop(MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - std::string& tooltip_msg) { return false; } - virtual LLInventoryType::EType getInventoryType() const { return mInvType; } - virtual LLWearableType::EType getWearableType() const { return LLWearableType::WT_NONE; } - virtual LLSettingsType::type_e getSettingsType() const { return LLSettingsType::ST_NONE; } - EInventorySortGroup getSortGroup() const { return SG_ITEM; } - virtual LLInventoryObject* getInventoryObject() const; - - - //-------------------------------------------------------------------- - // Convenience functions for adding various common menu options. - //-------------------------------------------------------------------- -protected: - virtual void addTrashContextMenuOptions(menuentry_vec_t &items, - menuentry_vec_t &disabled_items); - virtual void addDeleteContextMenuOptions(menuentry_vec_t &items, - menuentry_vec_t &disabled_items); - virtual void addOpenRightClickMenuOption(menuentry_vec_t &items); - virtual void addMarketplaceContextMenuOptions(U32 flags, - menuentry_vec_t &items, - menuentry_vec_t &disabled_items); - virtual void addLinkReplaceMenuOption(menuentry_vec_t& items, - menuentry_vec_t& disabled_items); - - virtual bool canMenuDelete(); - virtual bool canMenuCut(); - -protected: - LLInvFVBridge(LLInventoryPanel* inventory, LLFolderView* root, const LLUUID& uuid); - - LLInventoryModel* getInventoryModel() const; - LLInventoryFilter* getInventoryFilter() const; - - bool isLinkedObjectInTrash() const; // Is this obj or its baseobj in the trash? - bool isLinkedObjectMissing() const; // Is this a linked obj whose baseobj is not in inventory? - - bool isAgentInventory() const; // false if lost or in the inventory library - bool isCOFFolder() const; // true if COF or descendant of - bool isInboxFolder() const; // true if COF or descendant of marketplace inbox - - bool isMarketplaceListingsFolder() const; // true if descendant of Marketplace listings folder - - virtual bool isItemPermissive() const; - static void changeItemParent(LLInventoryModel* model, - LLViewerInventoryItem* item, - const LLUUID& new_parent, - bool restamp); - static void changeCategoryParent(LLInventoryModel* model, - LLViewerInventoryCategory* item, - const LLUUID& new_parent, - bool restamp); - void removeBatchNoCheck(std::vector& batch); - - bool callback_cutToClipboard(const LLSD& notification, const LLSD& response); - bool perform_cutToClipboard(); - - LLHandle mInventoryPanel; - LLFolderView* mRoot; - const LLUUID mUUID; // item id - LLInventoryType::EType mInvType; - bool mIsLink; - LLTimer mTimeSinceRequestStart; - mutable std::string mDisplayName; - mutable std::string mSearchableName; - - void purgeItem(LLInventoryModel *model, const LLUUID &uuid); - void removeObject(LLInventoryModel *model, const LLUUID &uuid); - virtual void buildDisplayName() const {} -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLInventoryFolderViewModelBuilder -// -// This class intended to build Folder View Model via LLInvFVBridge::createBridge. -// It can be overridden with another way of creation necessary Inventory Folder View Models. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLInventoryFolderViewModelBuilder -{ -public: - LLInventoryFolderViewModelBuilder() {} - virtual ~LLInventoryFolderViewModelBuilder() {} - virtual LLInvFVBridge* createBridge(LLAssetType::EType asset_type, - LLAssetType::EType actual_asset_type, - LLInventoryType::EType inv_type, - LLInventoryPanel* inventory, - LLFolderViewModelInventory* view_model, - LLFolderView* root, - const LLUUID& uuid, - U32 flags = 0x00) const; -}; - -class LLItemBridge : public LLInvFVBridge -{ -public: - LLItemBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid) : - LLInvFVBridge(inventory, root, uuid) {} - - typedef boost::function slurl_callback_t; - - virtual void performAction(LLInventoryModel* model, std::string action); - virtual void selectItem(); - virtual void restoreItem(); - virtual void restoreToWorld(); - virtual void gotoItem(); - virtual LLUIImagePtr getIcon() const; - virtual std::string getLabelSuffix() const; - virtual LLFontGL::StyleFlags getLabelStyle() const; - virtual PermissionMask getPermissionMask() const; - virtual time_t getCreationDate() const; - virtual bool isItemRenameable() const; - virtual bool renameItem(const std::string& new_name); - virtual bool removeItem(); - virtual bool isItemCopyable(bool can_copy_as_link = true) const; - virtual bool hasChildren() const { return false; } - virtual bool isUpToDate() const { return true; } - virtual LLUIImagePtr getIconOverlay() const; - - LLViewerInventoryItem* getItem() const; - virtual const LLUUID& getThumbnailUUID() const; - -protected: - bool confirmRemoveItem(const LLSD& notification, const LLSD& response); - virtual bool isItemPermissive() const; - virtual void buildDisplayName() const; - void doActionOnCurSelectedLandmark(LLLandmarkList::loaded_callback_t cb); - -private: - void doShowOnMap(LLLandmark* landmark); -}; - -class LLFolderBridge : public LLInvFVBridge -{ -public: - LLFolderBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid); - - ~LLFolderBridge(); - - bool dragItemIntoFolder(LLInventoryItem* inv_item, bool drop, std::string& tooltip_msg, bool user_confirm = true, LLPointer cb = NULL); - bool dragCategoryIntoFolder(LLInventoryCategory* inv_category, bool drop, std::string& tooltip_msg, bool is_link = false, bool user_confirm = true, LLPointer cb = NULL); - void callback_dropItemIntoFolder(const LLSD& notification, const LLSD& response, LLInventoryItem* inv_item); - void callback_dropCategoryIntoFolder(const LLSD& notification, const LLSD& response, LLInventoryCategory* inv_category); - - virtual void buildDisplayName() const; - - virtual void performAction(LLInventoryModel* model, std::string action); - virtual void openItem(); - virtual void closeItem(); - virtual bool isItemRenameable() const; - virtual void selectItem(); - virtual void restoreItem(); - - virtual LLFolderType::EType getPreferredType() const; - virtual LLUIImagePtr getIcon() const; - virtual LLUIImagePtr getIconOpen() const; - virtual LLUIImagePtr getIconOverlay() const; - static LLUIImagePtr getIcon(LLFolderType::EType preferred_type); - virtual std::string getLabelSuffix() const; - virtual LLFontGL::StyleFlags getLabelStyle() const; - virtual const LLUUID& getThumbnailUUID() const; - - void setShowDescendantsCount(bool show_count) {mShowDescendantsCount = show_count;} - - virtual bool renameItem(const std::string& new_name); - - virtual bool removeItem(); - bool removeSystemFolder(); - bool removeItemResponse(const LLSD& notification, const LLSD& response); - void updateHierarchyCreationDate(time_t date); - - virtual void pasteFromClipboard(); - virtual void pasteLinkFromClipboard(); - virtual void buildContextMenu(LLMenuGL& menu, U32 flags); - virtual bool hasChildren() const; - virtual bool dragOrDrop(MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - std::string& tooltip_msg); - - virtual bool isItemRemovable(bool check_worn = true) const; - virtual bool isItemMovable() const ; - virtual bool isUpToDate() const; - virtual bool isItemCopyable(bool can_copy_as_link = true) const; - virtual bool isClipboardPasteable() const; - virtual bool isClipboardPasteableAsLink() const; - - EInventorySortGroup getSortGroup() const; - virtual void update(); - - static void createWearable(LLFolderBridge* bridge, LLWearableType::EType type); - - LLViewerInventoryCategory* getCategory() const; - LLHandle getHandle() { mHandle.bind(this); return mHandle; } - - bool isLoading() { return mIsLoading; } - -protected: - void buildContextMenuOptions(U32 flags, menuentry_vec_t& items, menuentry_vec_t& disabled_items); - void buildContextMenuFolderOptions(U32 flags, menuentry_vec_t& items, menuentry_vec_t& disabled_items); - void addOpenFolderMenuOptions(U32 flags, menuentry_vec_t& items); - - //-------------------------------------------------------------------- - // Menu callbacks - //-------------------------------------------------------------------- - static void pasteClipboard(void* user_data); - static void createNewShirt(void* user_data); - static void createNewPants(void* user_data); - static void createNewShoes(void* user_data); - static void createNewSocks(void* user_data); - static void createNewJacket(void* user_data); - static void createNewSkirt(void* user_data); - static void createNewGloves(void* user_data); - static void createNewUndershirt(void* user_data); - static void createNewUnderpants(void* user_data); - static void createNewShape(void* user_data); - static void createNewSkin(void* user_data); - static void createNewHair(void* user_data); - static void createNewEyes(void* user_data); - - bool checkFolderForContentsOfType(LLInventoryModel* model, LLInventoryCollectFunctor& typeToCheck); - - void modifyOutfit(bool append); - void copyOutfitToClipboard(); - void determineFolderType(); - - void dropToFavorites(LLInventoryItem* inv_item, LLPointer cb = NULL); - void dropToOutfit(LLInventoryItem* inv_item, bool move_is_into_current_outfit, LLPointer cb = NULL); - void dropToMyOutfits(LLInventoryCategory* inv_cat, LLPointer cb = NULL); - - //-------------------------------------------------------------------- - // Messy hacks for handling folder options - //-------------------------------------------------------------------- -public: - static LLHandle sSelf; - static void staticFolderOptionsMenu(); - -protected: - void outfitFolderCreatedCallback(LLUUID cat_source_id, LLUUID cat_dest_id, LLPointer cb); - void callback_pasteFromClipboard(const LLSD& notification, const LLSD& response); - void perform_pasteFromClipboard(); - void gatherMessage(std::string& message, S32 depth, LLError::ELevel log_level); - LLUIImagePtr getFolderIcon(bool is_open) const; - - bool mCallingCards; - bool mWearables; - bool mIsLoading; - bool mShowDescendantsCount; - LLTimer mTimeSinceRequestStart; - std::string mMessage; - LLRootHandle mHandle; - -private: - // checking if folder is cutable or deletable is expensive, - // cache values and split check over frames - static void onCanDeleteIdle(void* user_data); - void initCanDeleteProcessing(LLInventoryModel* model, S32 version); - void completeDeleteProcessing(); - bool canMenuDelete(); - bool canMenuCut(); - - enum ECanDeleteState - { - CDS_INIT_FOLDER_CHECK, - CDS_PROCESSING_ITEMS, - CDS_PROCESSING_FOLDERS, - CDS_DONE, - }; - - ECanDeleteState mCanDeleteFolderState; - LLInventoryModel::cat_array_t mFoldersToCheck; - LLInventoryModel::item_array_t mItemsToCheck; - S32 mLastCheckedVersion; - S32 mInProgressVersion; - bool mCanDelete; - bool mCanCut; -}; - -class LLTextureBridge : public LLItemBridge -{ -public: - LLTextureBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid, - LLInventoryType::EType type) : - LLItemBridge(inventory, root, uuid) - { - mInvType = type; - } - virtual LLUIImagePtr getIcon() const; - virtual void openItem(); - virtual void buildContextMenu(LLMenuGL& menu, U32 flags); - virtual void performAction(LLInventoryModel* model, std::string action); - bool canSaveTexture(void); - void setFileName(const std::string& file_name) { mFileName = file_name; } -protected: - std::string mFileName; -}; - -class LLSoundBridge : public LLItemBridge -{ -public: - LLSoundBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid) : - LLItemBridge(inventory, root, uuid) {} - virtual void openItem(); - virtual void buildContextMenu(LLMenuGL& menu, U32 flags); - virtual void performAction(LLInventoryModel* model, std::string action); - static void openSoundPreview(void*); -}; - -class LLLandmarkBridge : public LLItemBridge -{ -public: - LLLandmarkBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid, - U32 flags = 0x00); - virtual void performAction(LLInventoryModel* model, std::string action); - virtual void buildContextMenu(LLMenuGL& menu, U32 flags); - virtual LLUIImagePtr getIcon() const; - virtual void openItem(); -protected: - bool mVisited; -}; - -class LLCallingCardBridge : public LLItemBridge -{ -public: - LLCallingCardBridge(LLInventoryPanel* inventory, - LLFolderView* folder, - const LLUUID& uuid ); - ~LLCallingCardBridge(); - virtual std::string getLabelSuffix() const; - //virtual const std::string& getDisplayName() const; - virtual LLUIImagePtr getIcon() const; - virtual void performAction(LLInventoryModel* model, std::string action); - virtual void openItem(); - virtual void buildContextMenu(LLMenuGL& menu, U32 flags); - virtual bool dragOrDrop(MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - std::string& tooltip_msg); - void refreshFolderViewItem(); - void checkSearchBySuffixChanges(); -protected: - LLCallingCardObserver* mObserver; - LLUUID mCreatorUUID; -}; - -class LLNotecardBridge : public LLItemBridge -{ -public: - LLNotecardBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid) : - LLItemBridge(inventory, root, uuid) {} - virtual void openItem(); - virtual void buildContextMenu(LLMenuGL& menu, U32 flags); -}; - -class LLGestureBridge : public LLItemBridge -{ -public: - LLGestureBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid) : - LLItemBridge(inventory, root, uuid) {} - // Only suffix for gesture items, not task items, because only - // gestures in your inventory can be active. - virtual LLFontGL::StyleFlags getLabelStyle() const; - virtual std::string getLabelSuffix() const; - virtual void performAction(LLInventoryModel* model, std::string action); - virtual void openItem(); - virtual bool removeItem(); - virtual void buildContextMenu(LLMenuGL& menu, U32 flags); - static void playGesture(const LLUUID& item_id); -}; - -class LLAnimationBridge : public LLItemBridge -{ -public: - LLAnimationBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid) : - LLItemBridge(inventory, root, uuid) {} - virtual void performAction(LLInventoryModel* model, std::string action); - virtual void buildContextMenu(LLMenuGL& menu, U32 flags); - virtual void openItem(); -}; - -class LLObjectBridge : public LLItemBridge -{ -public: - LLObjectBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid, - LLInventoryType::EType type, - U32 flags); - virtual LLUIImagePtr getIcon() const; - virtual void performAction(LLInventoryModel* model, std::string action); - virtual void openItem(); - virtual bool isItemWearable() const { return true; } - virtual std::string getLabelSuffix() const; - virtual void buildContextMenu(LLMenuGL& menu, U32 flags); - virtual bool renameItem(const std::string& new_name); - LLInventoryObject* getObject() const; - LLViewerInventoryItem* getItem() const; - LLViewerInventoryCategory* getCategory() const; -protected: - static LLUUID sContextMenuItemID; // Only valid while the context menu is open. - U32 mAttachPt; - bool mIsMultiObject; -}; - -class LLLSLTextBridge : public LLItemBridge -{ -public: - LLLSLTextBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid ) : - LLItemBridge(inventory, root, uuid) {} - virtual void openItem(); -}; - -class LLWearableBridge : public LLItemBridge -{ -public: - LLWearableBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid, - LLAssetType::EType asset_type, - LLInventoryType::EType inv_type, - LLWearableType::EType wearable_type); - virtual LLUIImagePtr getIcon() const; - virtual void performAction(LLInventoryModel* model, std::string action); - virtual void openItem(); - virtual bool isItemWearable() const { return true; } - virtual void buildContextMenu(LLMenuGL& menu, U32 flags); - virtual std::string getLabelSuffix() const; - virtual bool renameItem(const std::string& new_name); - virtual LLWearableType::EType getWearableType() const { return mWearableType; } - - static void onWearOnAvatar( void* userdata ); // Access to wearOnAvatar() from menu - static bool canWearOnAvatar( void* userdata ); - static void onWearOnAvatarArrived( LLViewerWearable* wearable, void* userdata ); - void wearOnAvatar(); - - static void onWearAddOnAvatarArrived( LLViewerWearable* wearable, void* userdata ); - void wearAddOnAvatar(); - - static bool canEditOnAvatar( void* userdata ); // Access to editOnAvatar() from menu - static void onEditOnAvatar( void* userdata ); - void editOnAvatar(); - - static bool canRemoveFromAvatar( void* userdata ); - void removeFromAvatar(); -protected: - LLAssetType::EType mAssetType; - LLWearableType::EType mWearableType; -}; - -class LLLinkItemBridge : public LLItemBridge -{ -public: - LLLinkItemBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid) : - LLItemBridge(inventory, root, uuid) {} - virtual const std::string& getPrefix() { return sPrefix; } - virtual void buildContextMenu(LLMenuGL& menu, U32 flags); -protected: - static std::string sPrefix; -}; - -class LLUnknownItemBridge : public LLItemBridge -{ -public: - LLUnknownItemBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid) : - LLItemBridge(inventory, root, uuid) {} - virtual LLUIImagePtr getIcon() const; - virtual void buildContextMenu(LLMenuGL& menu, U32 flags); -}; - -class LLLinkFolderBridge : public LLItemBridge -{ -public: - LLLinkFolderBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid) : - LLItemBridge(inventory, root, uuid) {} - virtual const std::string& getPrefix() { return sPrefix; } - virtual LLUIImagePtr getIcon() const; - virtual void buildContextMenu(LLMenuGL& menu, U32 flags); - virtual void performAction(LLInventoryModel* model, std::string action); - virtual void gotoItem(); -protected: - const LLUUID &getFolderID() const; - static std::string sPrefix; -}; - - -class LLSettingsBridge : public LLItemBridge -{ -public: - LLSettingsBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid, - LLSettingsType::type_e settings_type); - virtual LLUIImagePtr getIcon() const; - virtual void performAction(LLInventoryModel* model, std::string action); - virtual void openItem(); - virtual bool isMultiPreviewAllowed() { return false; } - virtual void buildContextMenu(LLMenuGL& menu, U32 flags); - virtual bool renameItem(const std::string& new_name); - virtual bool isItemRenameable() const; - virtual LLSettingsType::type_e getSettingsType() const { return mSettingsType; } - -protected: - bool canUpdateRegion() const; - bool canUpdateParcel() const; - - LLSettingsType::type_e mSettingsType; - -}; - -class LLMaterialBridge : public LLItemBridge -{ -public: - LLMaterialBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid) : - LLItemBridge(inventory, root, uuid) {} - virtual void openItem(); - virtual void buildContextMenu(LLMenuGL& menu, U32 flags); -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLInvFVBridgeAction -// -// This is an implementation class to be able to -// perform action to view inventory items. -// -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLInvFVBridgeAction -{ -public: - // This method is a convenience function which creates the correct - // type of bridge action based on some basic information. - static LLInvFVBridgeAction* createAction(LLAssetType::EType asset_type, - const LLUUID& uuid, - LLInventoryModel* model); - static void doAction(LLAssetType::EType asset_type, - const LLUUID& uuid, LLInventoryModel* model); - static void doAction(const LLUUID& uuid, LLInventoryModel* model); - - virtual void doIt() {}; - virtual ~LLInvFVBridgeAction() {} // need this because of warning on OSX -protected: - LLInvFVBridgeAction(const LLUUID& id, LLInventoryModel* model) : - mUUID(id), mModel(model) {} - LLViewerInventoryItem* getItem() const; -protected: - const LLUUID& mUUID; // item id - LLInventoryModel* mModel; -}; - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Recent Inventory Panel related classes -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -// Overridden version of the Inventory-Folder-View-Bridge for Folders -class LLRecentItemsFolderBridge : public LLFolderBridge -{ - friend class LLInvFVBridgeAction; -public: - // Creates context menu for Folders related to Recent Inventory Panel. - // Uses base logic and than removes from visible items "New..." menu items. - LLRecentItemsFolderBridge(LLInventoryType::EType type, - LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid) : - LLFolderBridge(inventory, root, uuid) - { - mInvType = type; - } - /*virtual*/ void buildContextMenu(LLMenuGL& menu, U32 flags); -}; - -// Bridge builder to create Inventory-Folder-View-Bridge for Recent Inventory Panel -class LLRecentInventoryBridgeBuilder : public LLInventoryFolderViewModelBuilder -{ -public: - LLRecentInventoryBridgeBuilder() {} - // Overrides FolderBridge for Recent Inventory Panel. - // It use base functionality for bridges other than FolderBridge. - virtual LLInvFVBridge* createBridge(LLAssetType::EType asset_type, - LLAssetType::EType actual_asset_type, - LLInventoryType::EType inv_type, - LLInventoryPanel* inventory, - LLFolderViewModelInventory* view_model, - LLFolderView* root, - const LLUUID& uuid, - U32 flags = 0x00) const; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Marketplace Inventory Panel related classes -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLMarketplaceFolderBridge : public LLFolderBridge -{ -public: - // Overloads some display related methods specific to folders in a marketplace floater context - LLMarketplaceFolderBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid); - - virtual LLUIImagePtr getIcon() const; - virtual LLUIImagePtr getIconOpen() const; - virtual std::string getLabelSuffix() const; - virtual LLFontGL::StyleFlags getLabelStyle() const; - -private: - LLUIImagePtr getMarketplaceFolderIcon(bool is_open) const; - // Those members are mutable because they are cached variablse to speed up display, not a state variables - mutable S32 m_depth; - mutable S32 m_stockCountCache; -}; - - -void rez_attachment(LLViewerInventoryItem* item, - LLViewerJointAttachment* attachment, - bool replace = false); - -// Move items from an in-world object's "Contents" folder to a specified -// folder in agent inventory. -bool move_inv_category_world_to_agent(const LLUUID& object_id, - const LLUUID& category_id, - bool drop, - std::function callback = NULL, - void* user_data = NULL, - LLInventoryFilter* filter = NULL); - -// Utility function to hide all entries except those in the list -// Can be called multiple times on the same menu (e.g. if multiple items -// are selected). If "append" is false, then only common enabled items -// are set as enabled. -void hide_context_entries(LLMenuGL& menu, - const menuentry_vec_t &entries_to_show, - const menuentry_vec_t &disabled_entries); - -// Helper functions to classify actions. -bool isAddAction(const std::string& action); -bool isRemoveAction(const std::string& action); -bool isMarketplaceCopyAction(const std::string& action); -bool isMarketplaceSendAction(const std::string& action); - -class LLFolderViewGroupedItemBridge: public LLFolderViewGroupedItemModel -{ -public: - LLFolderViewGroupedItemBridge(); - virtual void groupFilterContextMenu(folder_view_item_deque& selected_items, LLMenuGL& menu); - bool canWearSelected(const uuid_vec_t& item_ids) const; -}; - -struct LLMoveInv -{ - LLUUID mObjectID; - LLUUID mCategoryID; - two_uuids_list_t mMoveList; - std::function mCallback; - void* mUserData; -}; - -void warn_move_inventory(LLViewerObject* object, std::shared_ptr move_inv); -bool move_task_inventory_callback(const LLSD& notification, const LLSD& response, std::shared_ptr); - -#endif // LL_LLINVENTORYBRIDGE_H +/** + * @file llinventorybridge.h + * @brief Implementation of the Inventory-Folder-View-Bridge classes. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLINVENTORYBRIDGE_H +#define LL_LLINVENTORYBRIDGE_H + +#include "llcallingcard.h" +#include "llfolderviewmodel.h" +#include "llinventorymodel.h" +#include "llinventoryobserver.h" +#include "llinventorypanel.h" +#include "llviewercontrol.h" +#include "llviewerwearable.h" +#include "lltooldraganddrop.h" +#include "lllandmarklist.h" +#include "llfolderviewitem.h" +#include "llsettingsbase.h" + +class LLInventoryFilter; +class LLInventoryPanel; +class LLInventoryModel; +class LLMenuGL; +class LLCallingCardObserver; +class LLViewerJointAttachment; +class LLFolderView; +struct LLMoveInv; + +typedef std::vector menuentry_vec_t; +typedef std::pair two_uuids_t; +typedef std::list two_uuids_list_t; +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLInvFVBridge +// +// Short for Inventory-Folder-View-Bridge. This is an +// implementation class to be able to view inventory items. +// +// You'll want to call LLInvItemFVELister::createBridge() to actually create +// an instance of this class. This helps encapsulate the +// functionality a bit. (except for folders, you can create those +// manually...) +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLInvFVBridge : public LLFolderViewModelItemInventory +{ +public: + // This method is a convenience function which creates the correct + // type of bridge based on some basic information + static LLInvFVBridge* createBridge(LLAssetType::EType asset_type, + LLAssetType::EType actual_asset_type, + LLInventoryType::EType inv_type, + LLInventoryPanel* inventory, + LLFolderViewModelInventory* view_model, + LLFolderView* root, + const LLUUID& uuid, + U32 flags = 0x00); + virtual ~LLInvFVBridge() {} + + bool canShare() const; + bool canListOnMarketplace() const; + bool canListOnMarketplaceNow() const; + + //-------------------------------------------------------------------- + // LLInvFVBridge functionality + //-------------------------------------------------------------------- + virtual const LLUUID& getUUID() const { return mUUID; } + virtual const LLUUID& getThumbnailUUID() const { return LLUUID::null; } + virtual void clearDisplayName() { mDisplayName.clear(); } + virtual void restoreItem() {} + virtual void restoreToWorld() {} + + //-------------------------------------------------------------------- + // Inherited LLFolderViewModelItemInventory functions + //-------------------------------------------------------------------- + virtual const std::string& getName() const; + virtual const std::string& getDisplayName() const; + const std::string& getSearchableName() const { return mSearchableName; } + + std::string getSearchableDescription() const; + std::string getSearchableCreatorName() const; + std::string getSearchableUUIDString() const; + + virtual PermissionMask getPermissionMask() const; + virtual LLFolderType::EType getPreferredType() const; + virtual time_t getCreationDate() const; + virtual void setCreationDate(time_t creation_date_utc); + virtual LLFontGL::StyleFlags getLabelStyle() const { return LLFontGL::NORMAL; } + virtual std::string getLabelSuffix() const { return LLStringUtil::null; } + virtual void openItem() {} + virtual void closeItem() {} + virtual void navigateToFolder(bool new_window = false, bool change_mode = false); + virtual void showProperties(); + virtual bool isItemRenameable() const { return true; } + virtual bool isMultiPreviewAllowed() { return true; } + //virtual bool renameItem(const std::string& new_name) {} + virtual bool isItemRemovable(bool check_worn = true) const; + virtual bool isItemMovable() const; + virtual bool isItemInTrash() const; + virtual bool isItemInOutfits() const; + virtual bool isLink() const; + virtual bool isLibraryItem() const; + //virtual bool removeItem() = 0; + virtual void removeBatch(std::vector& batch); + virtual void move(LLFolderViewModelItem* new_parent_bridge) {} + virtual bool isItemCopyable(bool can_copy_as_link = true) const { return false; } + virtual bool copyToClipboard() const; + virtual bool cutToClipboard(); + virtual bool isCutToClipboard(); + virtual bool isClipboardPasteable() const; + virtual bool isClipboardPasteableAsLink() const; + virtual void pasteFromClipboard() {} + virtual void pasteLinkFromClipboard() {} + void getClipboardEntries(bool show_asset_id, menuentry_vec_t &items, + menuentry_vec_t &disabled_items, U32 flags); + virtual void buildContextMenu(LLMenuGL& menu, U32 flags); + virtual LLToolDragAndDrop::ESource getDragSource() const; + virtual bool startDrag(EDragAndDropType* type, LLUUID* id) const; + virtual bool dragOrDrop(MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + std::string& tooltip_msg) { return false; } + virtual LLInventoryType::EType getInventoryType() const { return mInvType; } + virtual LLWearableType::EType getWearableType() const { return LLWearableType::WT_NONE; } + virtual LLSettingsType::type_e getSettingsType() const { return LLSettingsType::ST_NONE; } + EInventorySortGroup getSortGroup() const { return SG_ITEM; } + virtual LLInventoryObject* getInventoryObject() const; + + + //-------------------------------------------------------------------- + // Convenience functions for adding various common menu options. + //-------------------------------------------------------------------- +protected: + virtual void addTrashContextMenuOptions(menuentry_vec_t &items, + menuentry_vec_t &disabled_items); + virtual void addDeleteContextMenuOptions(menuentry_vec_t &items, + menuentry_vec_t &disabled_items); + virtual void addOpenRightClickMenuOption(menuentry_vec_t &items); + virtual void addMarketplaceContextMenuOptions(U32 flags, + menuentry_vec_t &items, + menuentry_vec_t &disabled_items); + virtual void addLinkReplaceMenuOption(menuentry_vec_t& items, + menuentry_vec_t& disabled_items); + + virtual bool canMenuDelete(); + virtual bool canMenuCut(); + +protected: + LLInvFVBridge(LLInventoryPanel* inventory, LLFolderView* root, const LLUUID& uuid); + + LLInventoryModel* getInventoryModel() const; + LLInventoryFilter* getInventoryFilter() const; + + bool isLinkedObjectInTrash() const; // Is this obj or its baseobj in the trash? + bool isLinkedObjectMissing() const; // Is this a linked obj whose baseobj is not in inventory? + + bool isAgentInventory() const; // false if lost or in the inventory library + bool isCOFFolder() const; // true if COF or descendant of + bool isInboxFolder() const; // true if COF or descendant of marketplace inbox + + bool isMarketplaceListingsFolder() const; // true if descendant of Marketplace listings folder + + virtual bool isItemPermissive() const; + static void changeItemParent(LLInventoryModel* model, + LLViewerInventoryItem* item, + const LLUUID& new_parent, + bool restamp); + static void changeCategoryParent(LLInventoryModel* model, + LLViewerInventoryCategory* item, + const LLUUID& new_parent, + bool restamp); + void removeBatchNoCheck(std::vector& batch); + + bool callback_cutToClipboard(const LLSD& notification, const LLSD& response); + bool perform_cutToClipboard(); + + LLHandle mInventoryPanel; + LLFolderView* mRoot; + const LLUUID mUUID; // item id + LLInventoryType::EType mInvType; + bool mIsLink; + LLTimer mTimeSinceRequestStart; + mutable std::string mDisplayName; + mutable std::string mSearchableName; + + void purgeItem(LLInventoryModel *model, const LLUUID &uuid); + void removeObject(LLInventoryModel *model, const LLUUID &uuid); + virtual void buildDisplayName() const {} +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLInventoryFolderViewModelBuilder +// +// This class intended to build Folder View Model via LLInvFVBridge::createBridge. +// It can be overridden with another way of creation necessary Inventory Folder View Models. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLInventoryFolderViewModelBuilder +{ +public: + LLInventoryFolderViewModelBuilder() {} + virtual ~LLInventoryFolderViewModelBuilder() {} + virtual LLInvFVBridge* createBridge(LLAssetType::EType asset_type, + LLAssetType::EType actual_asset_type, + LLInventoryType::EType inv_type, + LLInventoryPanel* inventory, + LLFolderViewModelInventory* view_model, + LLFolderView* root, + const LLUUID& uuid, + U32 flags = 0x00) const; +}; + +class LLItemBridge : public LLInvFVBridge +{ +public: + LLItemBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid) : + LLInvFVBridge(inventory, root, uuid) {} + + typedef boost::function slurl_callback_t; + + virtual void performAction(LLInventoryModel* model, std::string action); + virtual void selectItem(); + virtual void restoreItem(); + virtual void restoreToWorld(); + virtual void gotoItem(); + virtual LLUIImagePtr getIcon() const; + virtual std::string getLabelSuffix() const; + virtual LLFontGL::StyleFlags getLabelStyle() const; + virtual PermissionMask getPermissionMask() const; + virtual time_t getCreationDate() const; + virtual bool isItemRenameable() const; + virtual bool renameItem(const std::string& new_name); + virtual bool removeItem(); + virtual bool isItemCopyable(bool can_copy_as_link = true) const; + virtual bool hasChildren() const { return false; } + virtual bool isUpToDate() const { return true; } + virtual LLUIImagePtr getIconOverlay() const; + + LLViewerInventoryItem* getItem() const; + virtual const LLUUID& getThumbnailUUID() const; + +protected: + bool confirmRemoveItem(const LLSD& notification, const LLSD& response); + virtual bool isItemPermissive() const; + virtual void buildDisplayName() const; + void doActionOnCurSelectedLandmark(LLLandmarkList::loaded_callback_t cb); + +private: + void doShowOnMap(LLLandmark* landmark); +}; + +class LLFolderBridge : public LLInvFVBridge +{ +public: + LLFolderBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid); + + ~LLFolderBridge(); + + bool dragItemIntoFolder(LLInventoryItem* inv_item, bool drop, std::string& tooltip_msg, bool user_confirm = true, LLPointer cb = NULL); + bool dragCategoryIntoFolder(LLInventoryCategory* inv_category, bool drop, std::string& tooltip_msg, bool is_link = false, bool user_confirm = true, LLPointer cb = NULL); + void callback_dropItemIntoFolder(const LLSD& notification, const LLSD& response, LLInventoryItem* inv_item); + void callback_dropCategoryIntoFolder(const LLSD& notification, const LLSD& response, LLInventoryCategory* inv_category); + + virtual void buildDisplayName() const; + + virtual void performAction(LLInventoryModel* model, std::string action); + virtual void openItem(); + virtual void closeItem(); + virtual bool isItemRenameable() const; + virtual void selectItem(); + virtual void restoreItem(); + + virtual LLFolderType::EType getPreferredType() const; + virtual LLUIImagePtr getIcon() const; + virtual LLUIImagePtr getIconOpen() const; + virtual LLUIImagePtr getIconOverlay() const; + static LLUIImagePtr getIcon(LLFolderType::EType preferred_type); + virtual std::string getLabelSuffix() const; + virtual LLFontGL::StyleFlags getLabelStyle() const; + virtual const LLUUID& getThumbnailUUID() const; + + void setShowDescendantsCount(bool show_count) {mShowDescendantsCount = show_count;} + + virtual bool renameItem(const std::string& new_name); + + virtual bool removeItem(); + bool removeSystemFolder(); + bool removeItemResponse(const LLSD& notification, const LLSD& response); + void updateHierarchyCreationDate(time_t date); + + virtual void pasteFromClipboard(); + virtual void pasteLinkFromClipboard(); + virtual void buildContextMenu(LLMenuGL& menu, U32 flags); + virtual bool hasChildren() const; + virtual bool dragOrDrop(MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + std::string& tooltip_msg); + + virtual bool isItemRemovable(bool check_worn = true) const; + virtual bool isItemMovable() const ; + virtual bool isUpToDate() const; + virtual bool isItemCopyable(bool can_copy_as_link = true) const; + virtual bool isClipboardPasteable() const; + virtual bool isClipboardPasteableAsLink() const; + + EInventorySortGroup getSortGroup() const; + virtual void update(); + + static void createWearable(LLFolderBridge* bridge, LLWearableType::EType type); + + LLViewerInventoryCategory* getCategory() const; + LLHandle getHandle() { mHandle.bind(this); return mHandle; } + + bool isLoading() { return mIsLoading; } + +protected: + void buildContextMenuOptions(U32 flags, menuentry_vec_t& items, menuentry_vec_t& disabled_items); + void buildContextMenuFolderOptions(U32 flags, menuentry_vec_t& items, menuentry_vec_t& disabled_items); + void addOpenFolderMenuOptions(U32 flags, menuentry_vec_t& items); + + //-------------------------------------------------------------------- + // Menu callbacks + //-------------------------------------------------------------------- + static void pasteClipboard(void* user_data); + static void createNewShirt(void* user_data); + static void createNewPants(void* user_data); + static void createNewShoes(void* user_data); + static void createNewSocks(void* user_data); + static void createNewJacket(void* user_data); + static void createNewSkirt(void* user_data); + static void createNewGloves(void* user_data); + static void createNewUndershirt(void* user_data); + static void createNewUnderpants(void* user_data); + static void createNewShape(void* user_data); + static void createNewSkin(void* user_data); + static void createNewHair(void* user_data); + static void createNewEyes(void* user_data); + + bool checkFolderForContentsOfType(LLInventoryModel* model, LLInventoryCollectFunctor& typeToCheck); + + void modifyOutfit(bool append); + void copyOutfitToClipboard(); + void determineFolderType(); + + void dropToFavorites(LLInventoryItem* inv_item, LLPointer cb = NULL); + void dropToOutfit(LLInventoryItem* inv_item, bool move_is_into_current_outfit, LLPointer cb = NULL); + void dropToMyOutfits(LLInventoryCategory* inv_cat, LLPointer cb = NULL); + + //-------------------------------------------------------------------- + // Messy hacks for handling folder options + //-------------------------------------------------------------------- +public: + static LLHandle sSelf; + static void staticFolderOptionsMenu(); + +protected: + void outfitFolderCreatedCallback(LLUUID cat_source_id, LLUUID cat_dest_id, LLPointer cb); + void callback_pasteFromClipboard(const LLSD& notification, const LLSD& response); + void perform_pasteFromClipboard(); + void gatherMessage(std::string& message, S32 depth, LLError::ELevel log_level); + LLUIImagePtr getFolderIcon(bool is_open) const; + + bool mCallingCards; + bool mWearables; + bool mIsLoading; + bool mShowDescendantsCount; + LLTimer mTimeSinceRequestStart; + std::string mMessage; + LLRootHandle mHandle; + +private: + // checking if folder is cutable or deletable is expensive, + // cache values and split check over frames + static void onCanDeleteIdle(void* user_data); + void initCanDeleteProcessing(LLInventoryModel* model, S32 version); + void completeDeleteProcessing(); + bool canMenuDelete(); + bool canMenuCut(); + + enum ECanDeleteState + { + CDS_INIT_FOLDER_CHECK, + CDS_PROCESSING_ITEMS, + CDS_PROCESSING_FOLDERS, + CDS_DONE, + }; + + ECanDeleteState mCanDeleteFolderState; + LLInventoryModel::cat_array_t mFoldersToCheck; + LLInventoryModel::item_array_t mItemsToCheck; + S32 mLastCheckedVersion; + S32 mInProgressVersion; + bool mCanDelete; + bool mCanCut; +}; + +class LLTextureBridge : public LLItemBridge +{ +public: + LLTextureBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid, + LLInventoryType::EType type) : + LLItemBridge(inventory, root, uuid) + { + mInvType = type; + } + virtual LLUIImagePtr getIcon() const; + virtual void openItem(); + virtual void buildContextMenu(LLMenuGL& menu, U32 flags); + virtual void performAction(LLInventoryModel* model, std::string action); + bool canSaveTexture(void); + void setFileName(const std::string& file_name) { mFileName = file_name; } +protected: + std::string mFileName; +}; + +class LLSoundBridge : public LLItemBridge +{ +public: + LLSoundBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid) : + LLItemBridge(inventory, root, uuid) {} + virtual void openItem(); + virtual void buildContextMenu(LLMenuGL& menu, U32 flags); + virtual void performAction(LLInventoryModel* model, std::string action); + static void openSoundPreview(void*); +}; + +class LLLandmarkBridge : public LLItemBridge +{ +public: + LLLandmarkBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid, + U32 flags = 0x00); + virtual void performAction(LLInventoryModel* model, std::string action); + virtual void buildContextMenu(LLMenuGL& menu, U32 flags); + virtual LLUIImagePtr getIcon() const; + virtual void openItem(); +protected: + bool mVisited; +}; + +class LLCallingCardBridge : public LLItemBridge +{ +public: + LLCallingCardBridge(LLInventoryPanel* inventory, + LLFolderView* folder, + const LLUUID& uuid ); + ~LLCallingCardBridge(); + virtual std::string getLabelSuffix() const; + //virtual const std::string& getDisplayName() const; + virtual LLUIImagePtr getIcon() const; + virtual void performAction(LLInventoryModel* model, std::string action); + virtual void openItem(); + virtual void buildContextMenu(LLMenuGL& menu, U32 flags); + virtual bool dragOrDrop(MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + std::string& tooltip_msg); + void refreshFolderViewItem(); + void checkSearchBySuffixChanges(); +protected: + LLCallingCardObserver* mObserver; + LLUUID mCreatorUUID; +}; + +class LLNotecardBridge : public LLItemBridge +{ +public: + LLNotecardBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid) : + LLItemBridge(inventory, root, uuid) {} + virtual void openItem(); + virtual void buildContextMenu(LLMenuGL& menu, U32 flags); +}; + +class LLGestureBridge : public LLItemBridge +{ +public: + LLGestureBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid) : + LLItemBridge(inventory, root, uuid) {} + // Only suffix for gesture items, not task items, because only + // gestures in your inventory can be active. + virtual LLFontGL::StyleFlags getLabelStyle() const; + virtual std::string getLabelSuffix() const; + virtual void performAction(LLInventoryModel* model, std::string action); + virtual void openItem(); + virtual bool removeItem(); + virtual void buildContextMenu(LLMenuGL& menu, U32 flags); + static void playGesture(const LLUUID& item_id); +}; + +class LLAnimationBridge : public LLItemBridge +{ +public: + LLAnimationBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid) : + LLItemBridge(inventory, root, uuid) {} + virtual void performAction(LLInventoryModel* model, std::string action); + virtual void buildContextMenu(LLMenuGL& menu, U32 flags); + virtual void openItem(); +}; + +class LLObjectBridge : public LLItemBridge +{ +public: + LLObjectBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid, + LLInventoryType::EType type, + U32 flags); + virtual LLUIImagePtr getIcon() const; + virtual void performAction(LLInventoryModel* model, std::string action); + virtual void openItem(); + virtual bool isItemWearable() const { return true; } + virtual std::string getLabelSuffix() const; + virtual void buildContextMenu(LLMenuGL& menu, U32 flags); + virtual bool renameItem(const std::string& new_name); + LLInventoryObject* getObject() const; + LLViewerInventoryItem* getItem() const; + LLViewerInventoryCategory* getCategory() const; +protected: + static LLUUID sContextMenuItemID; // Only valid while the context menu is open. + U32 mAttachPt; + bool mIsMultiObject; +}; + +class LLLSLTextBridge : public LLItemBridge +{ +public: + LLLSLTextBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid ) : + LLItemBridge(inventory, root, uuid) {} + virtual void openItem(); +}; + +class LLWearableBridge : public LLItemBridge +{ +public: + LLWearableBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid, + LLAssetType::EType asset_type, + LLInventoryType::EType inv_type, + LLWearableType::EType wearable_type); + virtual LLUIImagePtr getIcon() const; + virtual void performAction(LLInventoryModel* model, std::string action); + virtual void openItem(); + virtual bool isItemWearable() const { return true; } + virtual void buildContextMenu(LLMenuGL& menu, U32 flags); + virtual std::string getLabelSuffix() const; + virtual bool renameItem(const std::string& new_name); + virtual LLWearableType::EType getWearableType() const { return mWearableType; } + + static void onWearOnAvatar( void* userdata ); // Access to wearOnAvatar() from menu + static bool canWearOnAvatar( void* userdata ); + static void onWearOnAvatarArrived( LLViewerWearable* wearable, void* userdata ); + void wearOnAvatar(); + + static void onWearAddOnAvatarArrived( LLViewerWearable* wearable, void* userdata ); + void wearAddOnAvatar(); + + static bool canEditOnAvatar( void* userdata ); // Access to editOnAvatar() from menu + static void onEditOnAvatar( void* userdata ); + void editOnAvatar(); + + static bool canRemoveFromAvatar( void* userdata ); + void removeFromAvatar(); +protected: + LLAssetType::EType mAssetType; + LLWearableType::EType mWearableType; +}; + +class LLLinkItemBridge : public LLItemBridge +{ +public: + LLLinkItemBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid) : + LLItemBridge(inventory, root, uuid) {} + virtual const std::string& getPrefix() { return sPrefix; } + virtual void buildContextMenu(LLMenuGL& menu, U32 flags); +protected: + static std::string sPrefix; +}; + +class LLUnknownItemBridge : public LLItemBridge +{ +public: + LLUnknownItemBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid) : + LLItemBridge(inventory, root, uuid) {} + virtual LLUIImagePtr getIcon() const; + virtual void buildContextMenu(LLMenuGL& menu, U32 flags); +}; + +class LLLinkFolderBridge : public LLItemBridge +{ +public: + LLLinkFolderBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid) : + LLItemBridge(inventory, root, uuid) {} + virtual const std::string& getPrefix() { return sPrefix; } + virtual LLUIImagePtr getIcon() const; + virtual void buildContextMenu(LLMenuGL& menu, U32 flags); + virtual void performAction(LLInventoryModel* model, std::string action); + virtual void gotoItem(); +protected: + const LLUUID &getFolderID() const; + static std::string sPrefix; +}; + + +class LLSettingsBridge : public LLItemBridge +{ +public: + LLSettingsBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid, + LLSettingsType::type_e settings_type); + virtual LLUIImagePtr getIcon() const; + virtual void performAction(LLInventoryModel* model, std::string action); + virtual void openItem(); + virtual bool isMultiPreviewAllowed() { return false; } + virtual void buildContextMenu(LLMenuGL& menu, U32 flags); + virtual bool renameItem(const std::string& new_name); + virtual bool isItemRenameable() const; + virtual LLSettingsType::type_e getSettingsType() const { return mSettingsType; } + +protected: + bool canUpdateRegion() const; + bool canUpdateParcel() const; + + LLSettingsType::type_e mSettingsType; + +}; + +class LLMaterialBridge : public LLItemBridge +{ +public: + LLMaterialBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid) : + LLItemBridge(inventory, root, uuid) {} + virtual void openItem(); + virtual void buildContextMenu(LLMenuGL& menu, U32 flags); +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLInvFVBridgeAction +// +// This is an implementation class to be able to +// perform action to view inventory items. +// +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLInvFVBridgeAction +{ +public: + // This method is a convenience function which creates the correct + // type of bridge action based on some basic information. + static LLInvFVBridgeAction* createAction(LLAssetType::EType asset_type, + const LLUUID& uuid, + LLInventoryModel* model); + static void doAction(LLAssetType::EType asset_type, + const LLUUID& uuid, LLInventoryModel* model); + static void doAction(const LLUUID& uuid, LLInventoryModel* model); + + virtual void doIt() {}; + virtual ~LLInvFVBridgeAction() {} // need this because of warning on OSX +protected: + LLInvFVBridgeAction(const LLUUID& id, LLInventoryModel* model) : + mUUID(id), mModel(model) {} + LLViewerInventoryItem* getItem() const; +protected: + const LLUUID& mUUID; // item id + LLInventoryModel* mModel; +}; + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Recent Inventory Panel related classes +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +// Overridden version of the Inventory-Folder-View-Bridge for Folders +class LLRecentItemsFolderBridge : public LLFolderBridge +{ + friend class LLInvFVBridgeAction; +public: + // Creates context menu for Folders related to Recent Inventory Panel. + // Uses base logic and than removes from visible items "New..." menu items. + LLRecentItemsFolderBridge(LLInventoryType::EType type, + LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid) : + LLFolderBridge(inventory, root, uuid) + { + mInvType = type; + } + /*virtual*/ void buildContextMenu(LLMenuGL& menu, U32 flags); +}; + +// Bridge builder to create Inventory-Folder-View-Bridge for Recent Inventory Panel +class LLRecentInventoryBridgeBuilder : public LLInventoryFolderViewModelBuilder +{ +public: + LLRecentInventoryBridgeBuilder() {} + // Overrides FolderBridge for Recent Inventory Panel. + // It use base functionality for bridges other than FolderBridge. + virtual LLInvFVBridge* createBridge(LLAssetType::EType asset_type, + LLAssetType::EType actual_asset_type, + LLInventoryType::EType inv_type, + LLInventoryPanel* inventory, + LLFolderViewModelInventory* view_model, + LLFolderView* root, + const LLUUID& uuid, + U32 flags = 0x00) const; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Marketplace Inventory Panel related classes +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLMarketplaceFolderBridge : public LLFolderBridge +{ +public: + // Overloads some display related methods specific to folders in a marketplace floater context + LLMarketplaceFolderBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid); + + virtual LLUIImagePtr getIcon() const; + virtual LLUIImagePtr getIconOpen() const; + virtual std::string getLabelSuffix() const; + virtual LLFontGL::StyleFlags getLabelStyle() const; + +private: + LLUIImagePtr getMarketplaceFolderIcon(bool is_open) const; + // Those members are mutable because they are cached variablse to speed up display, not a state variables + mutable S32 m_depth; + mutable S32 m_stockCountCache; +}; + + +void rez_attachment(LLViewerInventoryItem* item, + LLViewerJointAttachment* attachment, + bool replace = false); + +// Move items from an in-world object's "Contents" folder to a specified +// folder in agent inventory. +bool move_inv_category_world_to_agent(const LLUUID& object_id, + const LLUUID& category_id, + bool drop, + std::function callback = NULL, + void* user_data = NULL, + LLInventoryFilter* filter = NULL); + +// Utility function to hide all entries except those in the list +// Can be called multiple times on the same menu (e.g. if multiple items +// are selected). If "append" is false, then only common enabled items +// are set as enabled. +void hide_context_entries(LLMenuGL& menu, + const menuentry_vec_t &entries_to_show, + const menuentry_vec_t &disabled_entries); + +// Helper functions to classify actions. +bool isAddAction(const std::string& action); +bool isRemoveAction(const std::string& action); +bool isMarketplaceCopyAction(const std::string& action); +bool isMarketplaceSendAction(const std::string& action); + +class LLFolderViewGroupedItemBridge: public LLFolderViewGroupedItemModel +{ +public: + LLFolderViewGroupedItemBridge(); + virtual void groupFilterContextMenu(folder_view_item_deque& selected_items, LLMenuGL& menu); + bool canWearSelected(const uuid_vec_t& item_ids) const; +}; + +struct LLMoveInv +{ + LLUUID mObjectID; + LLUUID mCategoryID; + two_uuids_list_t mMoveList; + std::function mCallback; + void* mUserData; +}; + +void warn_move_inventory(LLViewerObject* object, std::shared_ptr move_inv); +bool move_task_inventory_callback(const LLSD& notification, const LLSD& response, std::shared_ptr); + +#endif // LL_LLINVENTORYBRIDGE_H diff --git a/indra/newview/llinventoryfilter.cpp b/indra/newview/llinventoryfilter.cpp index 8312cfae03..6aca3947cf 100644 --- a/indra/newview/llinventoryfilter.cpp +++ b/indra/newview/llinventoryfilter.cpp @@ -1,1721 +1,1721 @@ -/** -* @file llinventoryfilter.cpp -* @brief Support for filtering your inventory to only display a subset of the -* available items. -* -* $LicenseInfo:firstyear=2005&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2010, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ -#include "llviewerprecompiledheaders.h" - -#include "llinventoryfilter.h" - -// viewer includes -#include "llagent.h" -#include "llfolderviewmodel.h" -#include "llfolderviewitem.h" -#include "llinventorymodel.h" -#include "llinventorymodelbackgroundfetch.h" -#include "llinventoryfunctions.h" -#include "llmarketplacefunctions.h" -#include "llregex.h" -#include "llviewercontrol.h" -#include "llfolderview.h" -#include "llinventorybridge.h" -#include "llviewerfoldertype.h" -#include "llradiogroup.h" -#include "llstartup.h" - -// linden library includes -#include "llclipboard.h" -#include "lltrans.h" - -LLInventoryFilter::FilterOps::FilterOps(const Params& p) -: mFilterObjectTypes(p.object_types), - mFilterCategoryTypes(p.category_types), - mFilterWearableTypes(p.wearable_types), - mFilterSettingsTypes(p.settings_types), - mMinDate(p.date_range.min_date), - mMaxDate(p.date_range.max_date), - mHoursAgo(p.hours_ago), - mDateSearchDirection(p.date_search_direction), - mShowFolderState(p.show_folder_state), - mFilterCreatorType(p.creator_type), - mPermissions(p.permissions), - mFilterTypes(p.types), - mFilterUUID(p.uuid), - mFilterLinks(p.links), - mFilterThumbnails(p.thumbnails), - mSearchVisibility(p.search_visibility) -{ -} - -///---------------------------------------------------------------------------- -/// Class LLInventoryFilter -///---------------------------------------------------------------------------- -LLInventoryFilter::LLInventoryFilter(const Params& p) -: mName(p.name), - mFilterModified(FILTER_NONE), - mEmptyLookupMessage("InventoryNoMatchingItems"), - mDefaultEmptyLookupMessage(""), - mFilterOps(p.filter_ops), - mBackupFilterOps(mFilterOps), - mFilterSubString(p.substring), - mCurrentGeneration(0), - mFirstRequiredGeneration(0), - mFirstSuccessGeneration(0), - mSearchType(SEARCHTYPE_NAME), - mSingleFolderMode(false) -{ - // copy mFilterOps into mDefaultFilterOps - markDefault(); - mUsername = gAgentUsername; - LLStringUtil::toUpper(mUsername); -} - -bool LLInventoryFilter::check(const LLFolderViewModelItem* item) -{ - const LLFolderViewModelItemInventory* listener = dynamic_cast(item); - - // If it's a folder and we're showing all folders, return automatically. - const bool is_folder = listener->getInventoryType() == LLInventoryType::IT_CATEGORY; - if (is_folder && (mFilterOps.mShowFolderState == LLInventoryFilter::SHOW_ALL_FOLDERS)) - { - return true; - } - - std::string desc = listener->getSearchableCreatorName(); - switch(mSearchType) - { - case SEARCHTYPE_CREATOR: - desc = listener->getSearchableCreatorName(); - break; - case SEARCHTYPE_DESCRIPTION: - desc = listener->getSearchableDescription(); - break; - case SEARCHTYPE_UUID: - desc = listener->getSearchableUUIDString(); - break; - case SEARCHTYPE_NAME: - default: - desc = listener->getSearchableName(); - break; - } - - - bool passed = true; - if (!mExactToken.empty() && (mSearchType == SEARCHTYPE_NAME)) - { - passed = false; - typedef boost::tokenizer > tokenizer; - boost::char_separator sep(" "); - tokenizer tokens(desc, sep); - - for (auto token_iter : tokens) - { - if (token_iter == mExactToken) - { - passed = true; - break; - } - } - } - else if ((mFilterTokens.size() > 0) && (mSearchType == SEARCHTYPE_NAME)) - { - for (auto token_iter : mFilterTokens) - { - if (desc.find(token_iter) == std::string::npos) - { - return false; - } - } - } - else - { - passed = (mFilterSubString.size() ? desc.find(mFilterSubString) != std::string::npos : true); - } - - passed = passed && checkAgainstFilterType(listener); - passed = passed && checkAgainstPermissions(listener); - passed = passed && checkAgainstFilterLinks(listener); - passed = passed && checkAgainstCreator(listener); - passed = passed && checkAgainstSearchVisibility(listener); - - passed = passed && checkAgainstFilterThumbnails(listener->getUUID()); - - return passed; -} - -bool LLInventoryFilter::check(const LLInventoryItem* item) -{ - const bool passed_string = (mFilterSubString.size() ? item->getName().find(mFilterSubString) != std::string::npos : true); - const bool passed_filtertype = checkAgainstFilterType(item); - const bool passed_permissions = checkAgainstPermissions(item); - - return passed_filtertype && passed_permissions && passed_string; -} - -bool LLInventoryFilter::checkFolder(const LLFolderViewModelItem* item) const -{ - const LLFolderViewModelItemInventory* listener = dynamic_cast(item); - if (!listener) - { - LL_ERRS() << "Folder view event listener not found." << LL_ENDL; - return false; - } - - const LLUUID folder_id = listener->getUUID(); - - return checkFolder(folder_id); -} - -bool LLInventoryFilter::checkFolder(const LLUUID& folder_id) const -{ - // we're showing all folders, overriding filter - if (mFilterOps.mShowFolderState == LLInventoryFilter::SHOW_ALL_FOLDERS) - { - return true; - } - - // when applying a filter, matching folders get their contents downloaded first - // but make sure we are not interfering with pre-download - if (isNotDefault() - && LLStartUp::getStartupState() > STATE_WEARABLES_WAIT - && !LLInventoryModelBackgroundFetch::instance().inventoryFetchInProgress()) - { - LLViewerInventoryCategory* cat = gInventory.getCategory(folder_id); - if ((!cat && folder_id.notNull())) - { - // Shouldn't happen? Server provides full list of folders on startup - LLInventoryModelBackgroundFetch::instance().start(folder_id, false); - } - else if (cat && cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) - { - // At the moment background fetch only cares about VERSION_UNKNOWN, - // so do not check isCategoryComplete that compares descendant count, - // but if that is nesesary, do a forced scheduleFolderFetch. - cat->fetch(); - } - } - - if (!checkAgainstFilterThumbnails(folder_id)) - { - return false; - } - - // Marketplace folder filtering - const U32 filterTypes = mFilterOps.mFilterTypes; - const U32 marketplace_filter = FILTERTYPE_MARKETPLACE_ACTIVE | FILTERTYPE_MARKETPLACE_INACTIVE | - FILTERTYPE_MARKETPLACE_UNASSOCIATED | FILTERTYPE_MARKETPLACE_LISTING_FOLDER | - FILTERTYPE_NO_MARKETPLACE_ITEMS; - if (filterTypes & marketplace_filter) - { - S32 depth = depth_nesting_in_marketplace(folder_id); - - if (filterTypes & FILTERTYPE_NO_MARKETPLACE_ITEMS) - { - if (depth >= 0) - { - return false; - } - } - - if (filterTypes & FILTERTYPE_MARKETPLACE_LISTING_FOLDER) - { - if (depth > 1) - { - return false; - } - } - - if (depth > 0) - { - LLUUID listing_uuid = nested_parent_id(folder_id, depth); - if (filterTypes & FILTERTYPE_MARKETPLACE_ACTIVE) - { - if (!LLMarketplaceData::instance().getActivationState(listing_uuid)) - { - return false; - } - } - else if (filterTypes & FILTERTYPE_MARKETPLACE_INACTIVE) - { - if (!LLMarketplaceData::instance().isListed(listing_uuid) || LLMarketplaceData::instance().getActivationState(listing_uuid)) - { - return false; - } - } - else if (filterTypes & FILTERTYPE_MARKETPLACE_UNASSOCIATED) - { - if (LLMarketplaceData::instance().isListed(listing_uuid)) - { - return false; - } - } - } - } - - // show folder links - LLViewerInventoryItem* item = gInventory.getItem(folder_id); - if (item && item->getActualType() == LLAssetType::AT_LINK_FOLDER) - { - return true; - } - - if (mFilterOps.mFilterTypes & FILTERTYPE_CATEGORY) - { - // Can only filter categories for items in your inventory - // (e.g. versus in-world object contents). - const LLViewerInventoryCategory *cat = gInventory.getCategory(folder_id); - if (!cat) - return folder_id.isNull(); - LLFolderType::EType cat_type = cat->getPreferredType(); - if (cat_type != LLFolderType::FT_NONE && (1LL << cat_type & mFilterOps.mFilterCategoryTypes) == U64(0)) - return false; - } - - return true; -} - -bool LLInventoryFilter::checkAgainstFilterType(const LLFolderViewModelItemInventory* listener) const -{ - if (!listener) return false; - - LLInventoryType::EType object_type = listener->getInventoryType(); - const LLUUID object_id = listener->getUUID(); - const LLInventoryObject *object = gInventory.getObject(object_id); - - const U32 filterTypes = mFilterOps.mFilterTypes; - - //////////////////////////////////////////////////////////////////////////////// - // FILTERTYPE_OBJECT - // Pass if this item's type is of the correct filter type - if (filterTypes & FILTERTYPE_OBJECT) - { - switch (object_type) - { - case LLInventoryType::IT_NONE: - // If it has no type, pass it, unless it's a link. - if (object && object->getIsLinkType()) - { - return false; - } - break; - case LLInventoryType::IT_UNKNOWN: - { - // Unknows are only shown when we show every type. - // Unknows are 255 and won't fit in 64 bits. - if (mFilterOps.mFilterObjectTypes != 0xffffffffffffffffULL) - { - return false; - } - break; - } - default: - if ((1LL << object_type & mFilterOps.mFilterObjectTypes) == U64(0)) - { - return false; - } - break; - } - } - - if(filterTypes & FILTERTYPE_WORN) - { - if (!get_is_item_worn(object_id)) - { - return false; - } - } - - //////////////////////////////////////////////////////////////////////////////// - // FILTERTYPE_UUID - // Pass if this item is the target UUID or if it links to the target UUID - if (filterTypes & FILTERTYPE_UUID) - { - if (!object) return false; - - if (object->getLinkedUUID() != mFilterOps.mFilterUUID) - return false; - } - - //////////////////////////////////////////////////////////////////////////////// - // FILTERTYPE_DATE - // Pass if this item is within the date range. - if (filterTypes & FILTERTYPE_DATE) - { - const U16 HOURS_TO_SECONDS = 3600; - time_t earliest = time_corrected() - mFilterOps.mHoursAgo * HOURS_TO_SECONDS; - - if (mFilterOps.mMinDate > time_min() && mFilterOps.mMinDate < earliest) - { - earliest = mFilterOps.mMinDate; - } - else if (!mFilterOps.mHoursAgo) - { - earliest = 0; - } - - if (FILTERDATEDIRECTION_NEWER == mFilterOps.mDateSearchDirection || isSinceLogoff()) - { - if (listener->getCreationDate() < earliest || - listener->getCreationDate() > mFilterOps.mMaxDate) - return false; - } - else - { - if (listener->getCreationDate() > earliest || - listener->getCreationDate() > mFilterOps.mMaxDate) - return false; - } - } - - //////////////////////////////////////////////////////////////////////////////// - // FILTERTYPE_WEARABLE - // Pass if this item is a wearable of the appropriate type - if (filterTypes & FILTERTYPE_WEARABLE) - { - LLWearableType::EType type = listener->getWearableType(); - if ((object_type == LLInventoryType::IT_WEARABLE) && - (((0x1LL << type) & mFilterOps.mFilterWearableTypes) == 0)) - { - return false; - } - } - - //////////////////////////////////////////////////////////////////////////////// - // FILTERTYPE_SETTINGS - // Pass if this item is a setting of the appropriate type - if (filterTypes & FILTERTYPE_SETTINGS) - { - LLSettingsType::type_e type = listener->getSettingsType(); - if ((object_type == LLInventoryType::IT_SETTINGS) && - (((0x1LL << type) & mFilterOps.mFilterSettingsTypes) == 0)) - { - return false; - } - } - - //////////////////////////////////////////////////////////////////////////////// - // FILTERTYPE_EMPTYFOLDERS - // Pass if this item is a folder and is not a system folder that should be hidden - if (filterTypes & FILTERTYPE_EMPTYFOLDERS) - { - if (object_type == LLInventoryType::IT_CATEGORY) - { - bool is_hidden_if_empty = LLViewerFolderType::lookupIsHiddenIfEmpty(listener->getPreferredType()); - if (is_hidden_if_empty) - { - // Force the fetching of those folders so they are hidden if they really are empty... - // But don't interfere with startup download - if (LLStartUp::getStartupState() > STATE_WEARABLES_WAIT) - { - gInventory.fetchDescendentsOf(object_id); - } - - LLInventoryModel::cat_array_t* cat_array = NULL; - LLInventoryModel::item_array_t* item_array = NULL; - gInventory.getDirectDescendentsOf(object_id,cat_array,item_array); - S32 descendents_actual = 0; - if(cat_array && item_array) - { - descendents_actual = cat_array->size() + item_array->size(); - } - if (descendents_actual == 0) - { - return false; - } - } - } - } - - return true; -} - -bool LLInventoryFilter::checkAgainstFilterType(const LLInventoryItem* item) const -{ - LLInventoryType::EType object_type = item->getInventoryType(); - - const U32 filterTypes = mFilterOps.mFilterTypes; - - //////////////////////////////////////////////////////////////////////////////// - // FILTERTYPE_OBJECT - // Pass if this item's type is of the correct filter type - if (filterTypes & FILTERTYPE_OBJECT) - { - switch (object_type) - { - case LLInventoryType::IT_NONE: - // If it has no type, pass it, unless it's a link. - if (item && item->getIsLinkType()) - { - return false; - } - break; - case LLInventoryType::IT_UNKNOWN: - { - // Unknows are only shown when we show every type. - // Unknows are 255 and won't fit in 64 bits. - if (mFilterOps.mFilterObjectTypes != 0xffffffffffffffffULL) - { - return false; - } - break; - } - default: - if ((1LL << object_type & mFilterOps.mFilterObjectTypes) == U64(0)) - { - return false; - } - break; - } - } - - //////////////////////////////////////////////////////////////////////////////// - // FILTERTYPE_UUID - // Pass if this item is the target UUID or if it links to the target UUID - if (filterTypes & FILTERTYPE_UUID) - { - if (!item) return false; - - if (item->getLinkedUUID() != mFilterOps.mFilterUUID) - return false; - } - - //////////////////////////////////////////////////////////////////////////////// - // FILTERTYPE_DATE - // Pass if this item is within the date range. - if (filterTypes & FILTERTYPE_DATE) - { - // We don't get the updated item creation date for the task inventory or - // a notecard embedded item. See LLTaskInvFVBridge::getCreationDate(). - return false; - } - - return true; -} - -// Items and folders that are on the clipboard or, recursively, in a folder which -// is on the clipboard must be filtered out if the clipboard is in the "cut" mode. -bool LLInventoryFilter::checkAgainstClipboard(const LLUUID& object_id) const -{ - if (LLClipboard::instance().isCutMode()) - { - LL_PROFILE_ZONE_SCOPED; - LLUUID current_id = object_id; - LLInventoryObject *current_object = gInventory.getObject(object_id); - while (current_id.notNull() && current_object) - { - if (LLClipboard::instance().isOnClipboard(current_id)) - { - return false; - } - current_id = current_object->getParentUUID(); - if (current_id.notNull()) - { - current_object = gInventory.getObject(current_id); - } - } - } - return true; -} - -bool LLInventoryFilter::checkAgainstPermissions(const LLFolderViewModelItemInventory* listener) const -{ - if (!listener) return false; - - PermissionMask perm = listener->getPermissionMask(); - const LLInvFVBridge *bridge = dynamic_cast(listener); - if (bridge && bridge->isLink()) - { - const LLUUID& linked_uuid = gInventory.getLinkedItemID(bridge->getUUID()); - const LLViewerInventoryItem *linked_item = gInventory.getItem(linked_uuid); - if (linked_item) - perm = linked_item->getPermissionMask(); - } - return (perm & mFilterOps.mPermissions) == mFilterOps.mPermissions; -} - -bool LLInventoryFilter::checkAgainstPermissions(const LLInventoryItem* item) const -{ - if (!item) return false; - - LLPointer new_item = new LLViewerInventoryItem(item); - PermissionMask perm = new_item->getPermissionMask(); - new_item = NULL; - - return (perm & mFilterOps.mPermissions) == mFilterOps.mPermissions; -} - -bool LLInventoryFilter::checkAgainstFilterLinks(const LLFolderViewModelItemInventory* listener) const -{ - if (!listener) return true; - - const LLUUID object_id = listener->getUUID(); - const LLInventoryObject *object = gInventory.getObject(object_id); - if (!object) return true; - - const bool is_link = object->getIsLinkType(); - if (is_link && (mFilterOps.mFilterLinks == FILTERLINK_EXCLUDE_LINKS)) - return false; - if (!is_link && (mFilterOps.mFilterLinks == FILTERLINK_ONLY_LINKS)) - return false; - return true; -} - -bool LLInventoryFilter::checkAgainstFilterThumbnails(const LLUUID& object_id) const -{ - const LLInventoryObject *object = gInventory.getObject(object_id); - if (!object) return true; - - const bool is_thumbnail = object->getThumbnailUUID().notNull(); - if (is_thumbnail && (mFilterOps.mFilterThumbnails == FILTER_EXCLUDE_THUMBNAILS)) - return false; - if (!is_thumbnail && (mFilterOps.mFilterThumbnails == FILTER_ONLY_THUMBNAILS)) - return false; - return true; -} - -bool LLInventoryFilter::checkAgainstCreator(const LLFolderViewModelItemInventory* listener) const -{ - if (!listener) return true; - const bool is_folder = listener->getInventoryType() == LLInventoryType::IT_CATEGORY; - switch (mFilterOps.mFilterCreatorType) - { - case FILTERCREATOR_SELF: - if(is_folder) return false; - return (listener->getSearchableCreatorName() == mUsername); - case FILTERCREATOR_OTHERS: - if(is_folder) return false; - return (listener->getSearchableCreatorName() != mUsername); - case FILTERCREATOR_ALL: - default: - return true; - } -} - -bool LLInventoryFilter::checkAgainstSearchVisibility(const LLFolderViewModelItemInventory* listener) const -{ - if (!listener || !hasFilterString()) return true; - - const LLUUID object_id = listener->getUUID(); - const LLInventoryObject *object = gInventory.getObject(object_id); - if (!object) return true; - - const bool is_link = object->getIsLinkType(); - if (is_link && ((mFilterOps.mSearchVisibility & VISIBILITY_LINKS) == 0)) - return false; - - if (listener->isItemInOutfits() && ((mFilterOps.mSearchVisibility & VISIBILITY_OUTFITS) == 0)) - return false; - - if (listener->isItemInTrash() && ((mFilterOps.mSearchVisibility & VISIBILITY_TRASH) == 0)) - return false; - - if (!listener->isAgentInventory() && ((mFilterOps.mSearchVisibility & VISIBILITY_LIBRARY) == 0)) - return false; - - return true; -} - -const std::string& LLInventoryFilter::getFilterSubString(bool trim) const -{ - return mFilterSubString; -} - -std::string::size_type LLInventoryFilter::getStringMatchOffset(LLFolderViewModelItem* item) const -{ - if (mSearchType == SEARCHTYPE_NAME) - { - return mFilterSubString.size() ? item->getSearchableName().find(mFilterSubString) : std::string::npos; - } - else - { - return std::string::npos; - } -} - -bool LLInventoryFilter::isDefault() const -{ - return !isNotDefault(); -} - -// has user modified default filter params? -bool LLInventoryFilter::isNotDefault() const -{ - S32 not_default = 0; - - not_default |= (mFilterOps.mFilterObjectTypes != mDefaultFilterOps.mFilterObjectTypes); - not_default |= (mFilterOps.mFilterCategoryTypes != mDefaultFilterOps.mFilterCategoryTypes); - not_default |= (mFilterOps.mFilterWearableTypes != mDefaultFilterOps.mFilterWearableTypes); - not_default |= (mFilterOps.mFilterTypes != mDefaultFilterOps.mFilterTypes); - not_default |= (mFilterOps.mFilterLinks != mDefaultFilterOps.mFilterLinks); - not_default |= (mFilterSubString.size()); - not_default |= (mFilterOps.mPermissions != mDefaultFilterOps.mPermissions); - not_default |= (mFilterOps.mMinDate != mDefaultFilterOps.mMinDate); - not_default |= (mFilterOps.mMaxDate != mDefaultFilterOps.mMaxDate); - not_default |= (mFilterOps.mHoursAgo != mDefaultFilterOps.mHoursAgo); - - return not_default != 0; -} - -bool LLInventoryFilter::isActive() const -{ - return mFilterOps.mFilterObjectTypes != 0xffffffffffffffffULL - || mFilterOps.mFilterCategoryTypes != 0xffffffffffffffffULL - || mFilterOps.mFilterWearableTypes != 0xffffffffffffffffULL - || mFilterOps.mFilterTypes != FILTERTYPE_OBJECT - || mFilterOps.mFilterLinks != FILTERLINK_INCLUDE_LINKS - || mFilterSubString.size() - || mFilterOps.mPermissions != PERM_NONE - || mFilterOps.mMinDate != time_min() - || mFilterOps.mMaxDate != time_max() - || mFilterOps.mHoursAgo != 0; -} - -bool LLInventoryFilter::isModified() const -{ - return mFilterModified != FILTER_NONE; -} - -void LLInventoryFilter::updateFilterTypes(U64 types, U64& current_types) -{ - if (current_types != types) - { - // keep current items only if no type bits getting turned off - bool fewer_bits_set = (current_types & ~types) != 0; - bool more_bits_set = (~current_types & types) != 0; - - current_types = types; - if (more_bits_set && fewer_bits_set) - { - // neither less or more restrictive, both simultaneously - // so we need to filter from scratch - setModified(FILTER_RESTART); - } - else if (more_bits_set) - { - // target is only one of all requested types so more type bits == less restrictive - setModified(FILTER_LESS_RESTRICTIVE); - } - else if (fewer_bits_set) - { - setModified(FILTER_MORE_RESTRICTIVE); - } - } -} - -void LLInventoryFilter::setSearchType(ESearchType type) -{ - if(mSearchType != type) - { - mSearchType = type; - setModified(); - } -} - -void LLInventoryFilter::setFilterCreator(EFilterCreatorType type) -{ - if (mFilterOps.mFilterCreatorType != type) - { - mFilterOps.mFilterCreatorType = type; - setModified(); - } -} - -void LLInventoryFilter::setFilterObjectTypes(U64 types) -{ - updateFilterTypes(types, mFilterOps.mFilterObjectTypes); - mFilterOps.mFilterTypes |= FILTERTYPE_OBJECT; -} - -void LLInventoryFilter::setFilterCategoryTypes(U64 types) -{ - updateFilterTypes(types, mFilterOps.mFilterCategoryTypes); - mFilterOps.mFilterTypes |= FILTERTYPE_CATEGORY; -} - -void LLInventoryFilter::setFilterWearableTypes(U64 types) -{ - updateFilterTypes(types, mFilterOps.mFilterWearableTypes); - mFilterOps.mFilterTypes |= FILTERTYPE_WEARABLE; -} - -void LLInventoryFilter::setFilterSettingsTypes(U64 types) -{ - updateFilterTypes(types, mFilterOps.mFilterSettingsTypes); - mFilterOps.mFilterTypes |= FILTERTYPE_SETTINGS; -} - -void LLInventoryFilter::setFilterThumbnails(U64 filter_thumbnails) -{ - if (mFilterOps.mFilterThumbnails != filter_thumbnails) - { - if (mFilterOps.mFilterThumbnails == FILTER_EXCLUDE_THUMBNAILS - && filter_thumbnails == FILTER_ONLY_THUMBNAILS) - { - setModified(FILTER_RESTART); - } - else if (mFilterOps.mFilterThumbnails == FILTER_ONLY_THUMBNAILS - && filter_thumbnails == FILTER_EXCLUDE_THUMBNAILS) - { - setModified(FILTER_RESTART); - } - else if (mFilterOps.mFilterThumbnails == FILTER_INCLUDE_THUMBNAILS) - { - setModified(FILTER_MORE_RESTRICTIVE); - } - else - { - setModified(FILTER_LESS_RESTRICTIVE); - } - } - mFilterOps.mFilterThumbnails = filter_thumbnails; -} - -void LLInventoryFilter::setFilterEmptySystemFolders() -{ - mFilterOps.mFilterTypes |= FILTERTYPE_EMPTYFOLDERS; -} - -void LLInventoryFilter::setFilterWorn() -{ - mFilterOps.mFilterTypes |= FILTERTYPE_WORN; -} - -void LLInventoryFilter::setFilterMarketplaceActiveFolders() -{ - mFilterOps.mFilterTypes |= FILTERTYPE_MARKETPLACE_ACTIVE; -} - -void LLInventoryFilter::setFilterMarketplaceInactiveFolders() -{ - mFilterOps.mFilterTypes |= FILTERTYPE_MARKETPLACE_INACTIVE; -} - -void LLInventoryFilter::setFilterMarketplaceUnassociatedFolders() -{ - mFilterOps.mFilterTypes |= FILTERTYPE_MARKETPLACE_UNASSOCIATED; -} - -void LLInventoryFilter::setFilterMarketplaceListingFolders(bool select_only_listing_folders) -{ - if (select_only_listing_folders) - { - mFilterOps.mFilterTypes |= FILTERTYPE_MARKETPLACE_LISTING_FOLDER; - setModified(FILTER_MORE_RESTRICTIVE); - } - else - { - mFilterOps.mFilterTypes &= ~FILTERTYPE_MARKETPLACE_LISTING_FOLDER; - setModified(FILTER_LESS_RESTRICTIVE); - } -} - - -void LLInventoryFilter::toggleSearchVisibilityLinks() -{ - bool hide_links = mFilterOps.mSearchVisibility & VISIBILITY_LINKS; - if (hide_links) - { - mFilterOps.mSearchVisibility &= ~VISIBILITY_LINKS; - } - else - { - mFilterOps.mSearchVisibility |= VISIBILITY_LINKS; - } - - if (hasFilterString()) - { - setModified(hide_links ? FILTER_MORE_RESTRICTIVE : FILTER_LESS_RESTRICTIVE); - } -} - -void LLInventoryFilter::toggleSearchVisibilityOutfits() -{ - bool hide_outfits = mFilterOps.mSearchVisibility & VISIBILITY_OUTFITS; - if (hide_outfits) - { - mFilterOps.mSearchVisibility &= ~VISIBILITY_OUTFITS; - } - else - { - mFilterOps.mSearchVisibility |= VISIBILITY_OUTFITS; - } - - if (hasFilterString()) - { - setModified(hide_outfits ? FILTER_MORE_RESTRICTIVE : FILTER_LESS_RESTRICTIVE); - } -} - -void LLInventoryFilter::toggleSearchVisibilityTrash() -{ - bool hide_trash = mFilterOps.mSearchVisibility & VISIBILITY_TRASH; - if (hide_trash) - { - mFilterOps.mSearchVisibility &= ~VISIBILITY_TRASH; - } - else - { - mFilterOps.mSearchVisibility |= VISIBILITY_TRASH; - } - - if (hasFilterString()) - { - setModified(hide_trash ? FILTER_MORE_RESTRICTIVE : FILTER_LESS_RESTRICTIVE); - } -} - -void LLInventoryFilter::toggleSearchVisibilityLibrary() -{ - bool hide_library = mFilterOps.mSearchVisibility & VISIBILITY_LIBRARY; - if (hide_library) - { - mFilterOps.mSearchVisibility &= ~VISIBILITY_LIBRARY; - } - else - { - mFilterOps.mSearchVisibility |= VISIBILITY_LIBRARY; - } - - if (hasFilterString()) - { - setModified(hide_library ? FILTER_MORE_RESTRICTIVE : FILTER_LESS_RESTRICTIVE); - } -} - -void LLInventoryFilter::setFilterNoMarketplaceFolder() -{ - mFilterOps.mFilterTypes |= FILTERTYPE_NO_MARKETPLACE_ITEMS; -} - -void LLInventoryFilter::setFilterUUID(const LLUUID& object_id) -{ - if (mFilterOps.mFilterUUID == LLUUID::null) - { - setModified(FILTER_MORE_RESTRICTIVE); - } - else - { - setModified(FILTER_RESTART); - } - mFilterOps.mFilterUUID = object_id; - mFilterOps.mFilterTypes = FILTERTYPE_UUID; -} - -void LLInventoryFilter::setFilterSubString(const std::string& string) -{ - std::string filter_sub_string_new = string; - mFilterSubStringOrig = string; - LLStringUtil::trimHead(filter_sub_string_new); - LLStringUtil::toUpper(filter_sub_string_new); - - if (mFilterSubString != filter_sub_string_new) - { - - mFilterTokens.clear(); - if (filter_sub_string_new.find_first_of("+") != std::string::npos) - { - typedef boost::tokenizer > tokenizer; - boost::char_separator sep("+"); - tokenizer tokens(filter_sub_string_new, sep); - - for (auto token_iter : tokens) - { - mFilterTokens.push_back(token_iter); - } - } - - std::string old_token = mExactToken; - mExactToken.clear(); - bool exact_token_changed = false; - if (mFilterTokens.empty() && filter_sub_string_new.size() > 2) - { - boost::regex mPattern = boost::regex("\"\\s*([^<]*)?\\s*\"", - boost::regex::perl | boost::regex::icase); - boost::match_results matches; - mExactToken = (ll_regex_match(filter_sub_string_new, matches, mPattern) && matches[1].matched) - ? matches[1] - : LLStringUtil::null; - if ((old_token.empty() && !mExactToken.empty()) - || (!old_token.empty() && mExactToken.empty())) - { - exact_token_changed = true; - } - } - - // hitting BACKSPACE, for example - const bool less_restrictive = mFilterSubString.size() >= filter_sub_string_new.size() - && !mFilterSubString.substr(0, filter_sub_string_new.size()).compare(filter_sub_string_new); - - // appending new characters - const bool more_restrictive = mFilterSubString.size() < filter_sub_string_new.size() - && !filter_sub_string_new.substr(0, mFilterSubString.size()).compare(mFilterSubString); - - mFilterSubString = filter_sub_string_new; - if (exact_token_changed) - { - setModified(FILTER_RESTART); - } - else if (less_restrictive) - { - setModified(FILTER_LESS_RESTRICTIVE); - } - else if (more_restrictive) - { - setModified(FILTER_MORE_RESTRICTIVE); - } - else - { - setModified(FILTER_RESTART); - } - - // Cancel out filter links once the search string is modified - if (mFilterOps.mFilterLinks == FILTERLINK_ONLY_LINKS) - { - if (mBackupFilterOps.mFilterLinks == FILTERLINK_ONLY_LINKS) - { - // we started viewer/floater in 'only links' mode - mFilterOps.mFilterLinks = FILTERLINK_INCLUDE_LINKS; - } - else - { - mFilterOps = mBackupFilterOps; - setModified(FILTER_RESTART); - } - } - - // Cancel out UUID once the search string is modified - if (mFilterOps.mFilterTypes == FILTERTYPE_UUID) - { - mFilterOps.mFilterTypes &= ~FILTERTYPE_UUID; - mFilterOps.mFilterUUID = LLUUID::null; - setModified(FILTER_RESTART); - } - } -} - -void LLInventoryFilter::setSearchVisibilityTypes(U32 types) -{ - if (mFilterOps.mSearchVisibility != types) - { - // keep current items only if no perm bits getting turned off - bool fewer_bits_set = (mFilterOps.mSearchVisibility & ~types); - bool more_bits_set = (~mFilterOps.mSearchVisibility & types); - mFilterOps.mSearchVisibility = types; - - if (more_bits_set && fewer_bits_set) - { - setModified(FILTER_RESTART); - } - else if (more_bits_set) - { - // target must have all requested permission bits, so more bits == more restrictive - setModified(FILTER_MORE_RESTRICTIVE); - } - else if (fewer_bits_set) - { - setModified(FILTER_LESS_RESTRICTIVE); - } - } -} - -void LLInventoryFilter::setSearchVisibilityTypes(const Params& params) -{ - if (!params.validateBlock()) - { - return; - } - - if (params.filter_ops.search_visibility.isProvided()) - { - setSearchVisibilityTypes(params.filter_ops.search_visibility); - } -} - -void LLInventoryFilter::setFilterPermissions(PermissionMask perms) -{ - if (mFilterOps.mPermissions != perms) - { - // keep current items only if no perm bits getting turned off - bool fewer_bits_set = (mFilterOps.mPermissions & ~perms); - bool more_bits_set = (~mFilterOps.mPermissions & perms); - mFilterOps.mPermissions = perms; - - if (more_bits_set && fewer_bits_set) - { - setModified(FILTER_RESTART); - } - else if (more_bits_set) - { - // target must have all requested permission bits, so more bits == more restrictive - setModified(FILTER_MORE_RESTRICTIVE); - } - else if (fewer_bits_set) - { - setModified(FILTER_LESS_RESTRICTIVE); - } - } -} - -void LLInventoryFilter::setDateRange(time_t min_date, time_t max_date) -{ - mFilterOps.mHoursAgo = 0; - if (mFilterOps.mMinDate != min_date) - { - mFilterOps.mMinDate = min_date; - setModified(); - } - if (mFilterOps.mMaxDate != llmax(mFilterOps.mMinDate, max_date)) - { - mFilterOps.mMaxDate = llmax(mFilterOps.mMinDate, max_date); - setModified(); - } - - if (areDateLimitsSet()) - { - mFilterOps.mFilterTypes |= FILTERTYPE_DATE; - } - else - { - mFilterOps.mFilterTypes &= ~FILTERTYPE_DATE; - } -} - -void LLInventoryFilter::setDateRangeLastLogoff(bool sl) -{ - static LLCachedControl s_last_logoff(gSavedPerAccountSettings, "LastLogoff", 0); - if (sl && !isSinceLogoff()) - { - setDateRange(s_last_logoff(), time_max()); - setModified(); - } - if (!sl && isSinceLogoff()) - { - setDateRange(time_min(), time_max()); - setModified(); - } - - if (areDateLimitsSet()) - { - mFilterOps.mFilterTypes |= FILTERTYPE_DATE; - } - else - { - mFilterOps.mFilterTypes &= ~FILTERTYPE_DATE; - } -} - -bool LLInventoryFilter::isSinceLogoff() const -{ - static LLCachedControl s_last_logoff(gSavedSettings, "LastLogoff", 0); - - return (mFilterOps.mMinDate == (time_t)s_last_logoff()) && - (mFilterOps.mMaxDate == time_max()) && - (mFilterOps.mFilterTypes & FILTERTYPE_DATE); -} - -void LLInventoryFilter::clearModified() -{ - mFilterModified = FILTER_NONE; -} - -void LLInventoryFilter::setHoursAgo(U32 hours) -{ - if (mFilterOps.mHoursAgo != hours) - { - bool are_date_limits_valid = mFilterOps.mMinDate == time_min() && mFilterOps.mMaxDate == time_max(); - - bool is_increasing = hours > mFilterOps.mHoursAgo; - bool is_decreasing = hours < mFilterOps.mHoursAgo; - bool is_increasing_from_zero = is_increasing && !mFilterOps.mHoursAgo && !isSinceLogoff(); - - // *NOTE: need to cache last filter time, in case filter goes stale - bool less_restrictive; - bool more_restrictive; - if (FILTERDATEDIRECTION_NEWER == mFilterOps.mDateSearchDirection) - { - less_restrictive = ((are_date_limits_valid && ((is_increasing && mFilterOps.mHoursAgo))) || !hours); - more_restrictive = ((are_date_limits_valid && (!is_increasing && hours)) || is_increasing_from_zero); - } - else - { - less_restrictive = ((are_date_limits_valid && ((is_decreasing && mFilterOps.mHoursAgo))) || !hours); - more_restrictive = ((are_date_limits_valid && (!is_decreasing && hours)) || is_increasing_from_zero); - } - - mFilterOps.mHoursAgo = hours; - mFilterOps.mMinDate = time_min(); - mFilterOps.mMaxDate = time_max(); - if (less_restrictive) - { - setModified(FILTER_LESS_RESTRICTIVE); - } - else if (more_restrictive) - { - setModified(FILTER_MORE_RESTRICTIVE); - } - else - { - setModified(FILTER_RESTART); - } - } - - if (areDateLimitsSet()) - { - mFilterOps.mFilterTypes |= FILTERTYPE_DATE; - } - else - { - mFilterOps.mFilterTypes &= ~FILTERTYPE_DATE; - } -} - -void LLInventoryFilter::setDateSearchDirection(U32 direction) -{ - if (direction != mFilterOps.mDateSearchDirection) - { - mFilterOps.mDateSearchDirection = direction; - setModified(FILTER_RESTART); - } -} - -U32 LLInventoryFilter::getDateSearchDirection() const -{ - return mFilterOps.mDateSearchDirection; -} - -void LLInventoryFilter::setFilterLinks(U64 filter_links) -{ - if (mFilterOps.mFilterLinks != filter_links) - { - if (mFilterOps.mFilterLinks == FILTERLINK_EXCLUDE_LINKS || - mFilterOps.mFilterLinks == FILTERLINK_ONLY_LINKS) - setModified(FILTER_MORE_RESTRICTIVE); - else - setModified(FILTER_LESS_RESTRICTIVE); - } - mFilterOps.mFilterLinks = filter_links; -} - -void LLInventoryFilter::setShowFolderState(EFolderShow state) -{ - if (mFilterOps.mShowFolderState != state) - { - mFilterOps.mShowFolderState = state; - if (state == SHOW_NON_EMPTY_FOLDERS) - { - // showing fewer folders than before - setModified(FILTER_MORE_RESTRICTIVE); - } - else if (state == SHOW_ALL_FOLDERS) - { - // showing same folders as before and then some - setModified(FILTER_LESS_RESTRICTIVE); - } - else - { - setModified(); - } - } -} - -void LLInventoryFilter::setFindAllLinksMode(const std::string &search_name, const LLUUID& search_id) -{ - // Save a copy of settings so that we will be able to restore it later - // but make sure we are not searching for links already - if(mFilterOps.mFilterLinks != FILTERLINK_ONLY_LINKS) - { - mBackupFilterOps = mFilterOps; - } - - // set search options - setFilterSubString(search_name); - setFilterUUID(search_id); - setShowFolderState(SHOW_NON_EMPTY_FOLDERS); - setFilterLinks(FILTERLINK_ONLY_LINKS); -} - -void LLInventoryFilter::markDefault() -{ - mDefaultFilterOps = mFilterOps; -} - -void LLInventoryFilter::resetDefault() -{ - mFilterOps = mDefaultFilterOps; - setModified(); -} - -void LLInventoryFilter::setModified(EFilterModified behavior) -{ - mFilterText.clear(); - mCurrentGeneration++; - - if (mFilterModified == FILTER_NONE) - { - mFilterModified = behavior; - } - else if (mFilterModified != behavior) - { - // trying to do both less restrictive and more restrictive filter - // basically means restart from scratch - mFilterModified = FILTER_RESTART; - } - - // if not keeping current filter results, update last valid as well - switch(mFilterModified) - { - case FILTER_RESTART: - mFirstRequiredGeneration = mCurrentGeneration; - mFirstSuccessGeneration = mCurrentGeneration; - break; - case FILTER_LESS_RESTRICTIVE: - mFirstRequiredGeneration = mCurrentGeneration; - break; - case FILTER_MORE_RESTRICTIVE: - mFirstSuccessGeneration = mCurrentGeneration; - break; - default: - LL_ERRS() << "Bad filter behavior specified" << LL_ENDL; - } -} - -bool LLInventoryFilter::isFilterObjectTypesWith(LLInventoryType::EType t) const -{ - return mFilterOps.mFilterObjectTypes & (1LL << t); -} - -const std::string& LLInventoryFilter::getFilterText() -{ - if (!mFilterText.empty()) - { - return mFilterText; - } - - std::string filtered_types; - std::string not_filtered_types; - bool filtered_by_type = false; - bool filtered_by_all_types = true; - S32 num_filter_types = 0; - - mFilterText.clear(); - - if (isFilterObjectTypesWith(LLInventoryType::IT_ANIMATION)) - { - filtered_types += LLTrans::getString("Animations"); - filtered_by_type = true; - num_filter_types++; - } - else - { - not_filtered_types += LLTrans::getString("Animations"); - - filtered_by_all_types = false; - } - - if (isFilterObjectTypesWith(LLInventoryType::IT_CALLINGCARD)) - { - filtered_types += LLTrans::getString("Calling Cards"); - filtered_by_type = true; - num_filter_types++; - } - else - { - not_filtered_types += LLTrans::getString("Calling Cards"); - filtered_by_all_types = false; - } - - if (isFilterObjectTypesWith(LLInventoryType::IT_WEARABLE)) - { - filtered_types += LLTrans::getString("Clothing"); - filtered_by_type = true; - num_filter_types++; - } - else - { - not_filtered_types += LLTrans::getString("Clothing"); - filtered_by_all_types = false; - } - - if (isFilterObjectTypesWith(LLInventoryType::IT_GESTURE)) - { - filtered_types += LLTrans::getString("Gestures"); - filtered_by_type = true; - num_filter_types++; - } - else - { - not_filtered_types += LLTrans::getString("Gestures"); - filtered_by_all_types = false; - } - - if (isFilterObjectTypesWith(LLInventoryType::IT_LANDMARK)) - { - filtered_types += LLTrans::getString("Landmarks"); - filtered_by_type = true; - num_filter_types++; - } - else - { - not_filtered_types += LLTrans::getString("Landmarks"); - filtered_by_all_types = false; - } - - if (isFilterObjectTypesWith(LLInventoryType::IT_MATERIAL)) - { - filtered_types += LLTrans::getString("Materials"); - filtered_by_type = true; - num_filter_types++; - } - else - { - not_filtered_types += LLTrans::getString("Materials"); - filtered_by_all_types = false; - } - - if (isFilterObjectTypesWith(LLInventoryType::IT_NOTECARD)) - { - filtered_types += LLTrans::getString("Notecards"); - filtered_by_type = true; - num_filter_types++; - } - else - { - not_filtered_types += LLTrans::getString("Notecards"); - filtered_by_all_types = false; - } - - if (isFilterObjectTypesWith(LLInventoryType::IT_OBJECT) && isFilterObjectTypesWith(LLInventoryType::IT_ATTACHMENT)) - { - filtered_types += LLTrans::getString("Objects"); - filtered_by_type = true; - num_filter_types++; - } - else - { - not_filtered_types += LLTrans::getString("Objects"); - filtered_by_all_types = false; - } - - if (isFilterObjectTypesWith(LLInventoryType::IT_LSL)) - { - filtered_types += LLTrans::getString("Scripts"); - filtered_by_type = true; - num_filter_types++; - } - else - { - not_filtered_types += LLTrans::getString("Scripts"); - filtered_by_all_types = false; - } - - if (isFilterObjectTypesWith(LLInventoryType::IT_SOUND)) - { - filtered_types += LLTrans::getString("Sounds"); - filtered_by_type = true; - num_filter_types++; - } - else - { - not_filtered_types += LLTrans::getString("Sounds"); - filtered_by_all_types = false; - } - - if (isFilterObjectTypesWith(LLInventoryType::IT_TEXTURE)) - { - filtered_types += LLTrans::getString("Textures"); - filtered_by_type = true; - num_filter_types++; - } - else - { - not_filtered_types += LLTrans::getString("Textures"); - filtered_by_all_types = false; - } - - if (isFilterObjectTypesWith(LLInventoryType::IT_SNAPSHOT)) - { - filtered_types += LLTrans::getString("Snapshots"); - filtered_by_type = true; - num_filter_types++; - } - else - { - not_filtered_types += LLTrans::getString("Snapshots"); - filtered_by_all_types = false; - } - - if (isFilterObjectTypesWith(LLInventoryType::IT_SETTINGS)) - { - filtered_types += LLTrans::getString("Settings"); - filtered_by_type = true; - num_filter_types++; - } - else - { - not_filtered_types += LLTrans::getString("Settings"); - filtered_by_all_types = false; - } - - if (!LLInventoryModelBackgroundFetch::instance().folderFetchActive() - && filtered_by_type - && !filtered_by_all_types) - { - mFilterText += " - "; - if (num_filter_types < 5) - { - mFilterText += filtered_types; - } - else - { - mFilterText += LLTrans::getString("No Filters"); - mFilterText += not_filtered_types; - } - // remove the ',' at the end - mFilterText.erase(mFilterText.size() - 1, 1); - } - - if (isSinceLogoff()) - { - mFilterText += LLTrans::getString("Since Logoff"); - } - return mFilterText; -} - - -LLInventoryFilter& LLInventoryFilter::operator=( const LLInventoryFilter& other ) -{ - setFilterObjectTypes(other.getFilterObjectTypes()); - setDateRange(other.getMinDate(), other.getMaxDate()); - setHoursAgo(other.getHoursAgo()); - setDateSearchDirection(other.getDateSearchDirection()); - setShowFolderState(other.getShowFolderState()); - setFilterPermissions(other.getFilterPermissions()); - setFilterSubString(other.getFilterSubString()); - setDateRangeLastLogoff(other.isSinceLogoff()); - return *this; -} - - -void LLInventoryFilter::toParams(Params& params) const -{ - params.filter_ops.types = getFilterObjectTypes(); - params.filter_ops.category_types = getFilterCategoryTypes(); - if (getFilterObjectTypes() & FILTERTYPE_WEARABLE) - { - params.filter_ops.wearable_types = getFilterWearableTypes(); - } - params.filter_ops.date_range.min_date = getMinDate(); - params.filter_ops.date_range.max_date = getMaxDate(); - params.filter_ops.hours_ago = getHoursAgo(); - params.filter_ops.date_search_direction = getDateSearchDirection(); - params.filter_ops.show_folder_state = getShowFolderState(); - params.filter_ops.creator_type = getFilterCreatorType(); - params.filter_ops.permissions = getFilterPermissions(); - params.filter_ops.search_visibility = getSearchVisibilityTypes(); - params.substring = getFilterSubString(); - params.since_logoff = isSinceLogoff(); -} - -void LLInventoryFilter::fromParams(const Params& params) -{ - if (!params.validateBlock()) - { - return; - } - - setFilterObjectTypes(params.filter_ops.types); - setFilterCategoryTypes(params.filter_ops.category_types); - if (params.filter_ops.wearable_types.isProvided()) - { - setFilterWearableTypes(params.filter_ops.wearable_types); - } - setDateRange(params.filter_ops.date_range.min_date, params.filter_ops.date_range.max_date); - setHoursAgo(params.filter_ops.hours_ago); - setDateSearchDirection(params.filter_ops.date_search_direction); - setShowFolderState(params.filter_ops.show_folder_state); - setFilterCreator(params.filter_ops.creator_type); - setFilterPermissions(params.filter_ops.permissions); - setSearchVisibilityTypes(params.filter_ops.search_visibility); - setFilterSubString(params.substring); - setDateRangeLastLogoff(params.since_logoff); -} - -U64 LLInventoryFilter::getFilterTypes() const -{ - return mFilterOps.mFilterTypes; -} - -U64 LLInventoryFilter::getFilterObjectTypes() const -{ - return mFilterOps.mFilterObjectTypes; -} - -U64 LLInventoryFilter::getFilterCategoryTypes() const -{ - return mFilterOps.mFilterCategoryTypes; -} - -U64 LLInventoryFilter::getFilterWearableTypes() const -{ - return mFilterOps.mFilterWearableTypes; -} - -U64 LLInventoryFilter::getFilterSettingsTypes() const -{ - return mFilterOps.mFilterSettingsTypes; -} - -U64 LLInventoryFilter::getSearchVisibilityTypes() const -{ - return mFilterOps.mSearchVisibility; -} - -U64 LLInventoryFilter::getFilterThumbnails() const -{ - return mFilterOps.mFilterThumbnails; -} - -bool LLInventoryFilter::hasFilterString() const -{ - return mFilterSubString.size() > 0; -} - -std::string::size_type LLInventoryFilter::getFilterStringSize() const -{ - return mFilterSubString.size(); -} - -PermissionMask LLInventoryFilter::getFilterPermissions() const -{ - return mFilterOps.mPermissions; -} - -time_t LLInventoryFilter::getMinDate() const -{ - return mFilterOps.mMinDate; -} - -time_t LLInventoryFilter::getMaxDate() const -{ - return mFilterOps.mMaxDate; -} -U32 LLInventoryFilter::getHoursAgo() const -{ - return mFilterOps.mHoursAgo; -} -U64 LLInventoryFilter::getFilterLinks() const -{ - return mFilterOps.mFilterLinks; -} -LLInventoryFilter::EFolderShow LLInventoryFilter::getShowFolderState() const -{ - return mFilterOps.mShowFolderState; -} - -LLInventoryFilter::EFilterCreatorType LLInventoryFilter::getFilterCreatorType() const -{ - return mFilterOps.mFilterCreatorType; -} - -bool LLInventoryFilter::isTimedOut() -{ - return mFilterTime.hasExpired(); -} - -void LLInventoryFilter::resetTime(S32 timeout) -{ - mFilterTime.reset(); - F32 time_in_sec = (F32)(timeout)/1000.0; - mFilterTime.setTimerExpirySec(time_in_sec); -} - -S32 LLInventoryFilter::getCurrentGeneration() const -{ - return mCurrentGeneration; -} -S32 LLInventoryFilter::getFirstSuccessGeneration() const -{ - return mFirstSuccessGeneration; -} -S32 LLInventoryFilter::getFirstRequiredGeneration() const -{ - return mFirstRequiredGeneration; -} - -void LLInventoryFilter::setEmptyLookupMessage(const std::string& message) -{ - mEmptyLookupMessage = message; -} - -void LLInventoryFilter::setDefaultEmptyLookupMessage(const std::string& message) -{ - mDefaultEmptyLookupMessage = message; -} - -std::string LLInventoryFilter::getEmptyLookupMessage(bool is_empty_folder) const -{ - if ((isDefault() || is_empty_folder) && !mDefaultEmptyLookupMessage.empty()) - { - return LLTrans::getString(mDefaultEmptyLookupMessage); - } - else - { - LLStringUtil::format_map_t args; - args["[SEARCH_TERM]"] = LLURI::escape(getFilterSubStringOrig()); - - return LLTrans::getString(mEmptyLookupMessage, args); - } - -} - -bool LLInventoryFilter::areDateLimitsSet() -{ - return mFilterOps.mMinDate != time_min() - || mFilterOps.mMaxDate != time_max() - || mFilterOps.mHoursAgo != 0; -} - -bool LLInventoryFilter::showAllResults() const -{ - return hasFilterString() && !mSingleFolderMode; -} - - - -bool LLInventoryFilter::FilterOps::DateRange::validateBlock( bool emit_errors /*= true*/ ) const -{ - bool valid = LLInitParam::Block::validateBlock(emit_errors); - if (valid) - { - if (max_date() < min_date()) - { - if (emit_errors) - { - LL_WARNS() << "max_date should be greater or equal to min_date" << LL_ENDL; - } - valid = false; - } - } - return valid; -} +/** +* @file llinventoryfilter.cpp +* @brief Support for filtering your inventory to only display a subset of the +* available items. +* +* $LicenseInfo:firstyear=2005&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2010, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#include "llviewerprecompiledheaders.h" + +#include "llinventoryfilter.h" + +// viewer includes +#include "llagent.h" +#include "llfolderviewmodel.h" +#include "llfolderviewitem.h" +#include "llinventorymodel.h" +#include "llinventorymodelbackgroundfetch.h" +#include "llinventoryfunctions.h" +#include "llmarketplacefunctions.h" +#include "llregex.h" +#include "llviewercontrol.h" +#include "llfolderview.h" +#include "llinventorybridge.h" +#include "llviewerfoldertype.h" +#include "llradiogroup.h" +#include "llstartup.h" + +// linden library includes +#include "llclipboard.h" +#include "lltrans.h" + +LLInventoryFilter::FilterOps::FilterOps(const Params& p) +: mFilterObjectTypes(p.object_types), + mFilterCategoryTypes(p.category_types), + mFilterWearableTypes(p.wearable_types), + mFilterSettingsTypes(p.settings_types), + mMinDate(p.date_range.min_date), + mMaxDate(p.date_range.max_date), + mHoursAgo(p.hours_ago), + mDateSearchDirection(p.date_search_direction), + mShowFolderState(p.show_folder_state), + mFilterCreatorType(p.creator_type), + mPermissions(p.permissions), + mFilterTypes(p.types), + mFilterUUID(p.uuid), + mFilterLinks(p.links), + mFilterThumbnails(p.thumbnails), + mSearchVisibility(p.search_visibility) +{ +} + +///---------------------------------------------------------------------------- +/// Class LLInventoryFilter +///---------------------------------------------------------------------------- +LLInventoryFilter::LLInventoryFilter(const Params& p) +: mName(p.name), + mFilterModified(FILTER_NONE), + mEmptyLookupMessage("InventoryNoMatchingItems"), + mDefaultEmptyLookupMessage(""), + mFilterOps(p.filter_ops), + mBackupFilterOps(mFilterOps), + mFilterSubString(p.substring), + mCurrentGeneration(0), + mFirstRequiredGeneration(0), + mFirstSuccessGeneration(0), + mSearchType(SEARCHTYPE_NAME), + mSingleFolderMode(false) +{ + // copy mFilterOps into mDefaultFilterOps + markDefault(); + mUsername = gAgentUsername; + LLStringUtil::toUpper(mUsername); +} + +bool LLInventoryFilter::check(const LLFolderViewModelItem* item) +{ + const LLFolderViewModelItemInventory* listener = dynamic_cast(item); + + // If it's a folder and we're showing all folders, return automatically. + const bool is_folder = listener->getInventoryType() == LLInventoryType::IT_CATEGORY; + if (is_folder && (mFilterOps.mShowFolderState == LLInventoryFilter::SHOW_ALL_FOLDERS)) + { + return true; + } + + std::string desc = listener->getSearchableCreatorName(); + switch(mSearchType) + { + case SEARCHTYPE_CREATOR: + desc = listener->getSearchableCreatorName(); + break; + case SEARCHTYPE_DESCRIPTION: + desc = listener->getSearchableDescription(); + break; + case SEARCHTYPE_UUID: + desc = listener->getSearchableUUIDString(); + break; + case SEARCHTYPE_NAME: + default: + desc = listener->getSearchableName(); + break; + } + + + bool passed = true; + if (!mExactToken.empty() && (mSearchType == SEARCHTYPE_NAME)) + { + passed = false; + typedef boost::tokenizer > tokenizer; + boost::char_separator sep(" "); + tokenizer tokens(desc, sep); + + for (auto token_iter : tokens) + { + if (token_iter == mExactToken) + { + passed = true; + break; + } + } + } + else if ((mFilterTokens.size() > 0) && (mSearchType == SEARCHTYPE_NAME)) + { + for (auto token_iter : mFilterTokens) + { + if (desc.find(token_iter) == std::string::npos) + { + return false; + } + } + } + else + { + passed = (mFilterSubString.size() ? desc.find(mFilterSubString) != std::string::npos : true); + } + + passed = passed && checkAgainstFilterType(listener); + passed = passed && checkAgainstPermissions(listener); + passed = passed && checkAgainstFilterLinks(listener); + passed = passed && checkAgainstCreator(listener); + passed = passed && checkAgainstSearchVisibility(listener); + + passed = passed && checkAgainstFilterThumbnails(listener->getUUID()); + + return passed; +} + +bool LLInventoryFilter::check(const LLInventoryItem* item) +{ + const bool passed_string = (mFilterSubString.size() ? item->getName().find(mFilterSubString) != std::string::npos : true); + const bool passed_filtertype = checkAgainstFilterType(item); + const bool passed_permissions = checkAgainstPermissions(item); + + return passed_filtertype && passed_permissions && passed_string; +} + +bool LLInventoryFilter::checkFolder(const LLFolderViewModelItem* item) const +{ + const LLFolderViewModelItemInventory* listener = dynamic_cast(item); + if (!listener) + { + LL_ERRS() << "Folder view event listener not found." << LL_ENDL; + return false; + } + + const LLUUID folder_id = listener->getUUID(); + + return checkFolder(folder_id); +} + +bool LLInventoryFilter::checkFolder(const LLUUID& folder_id) const +{ + // we're showing all folders, overriding filter + if (mFilterOps.mShowFolderState == LLInventoryFilter::SHOW_ALL_FOLDERS) + { + return true; + } + + // when applying a filter, matching folders get their contents downloaded first + // but make sure we are not interfering with pre-download + if (isNotDefault() + && LLStartUp::getStartupState() > STATE_WEARABLES_WAIT + && !LLInventoryModelBackgroundFetch::instance().inventoryFetchInProgress()) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(folder_id); + if ((!cat && folder_id.notNull())) + { + // Shouldn't happen? Server provides full list of folders on startup + LLInventoryModelBackgroundFetch::instance().start(folder_id, false); + } + else if (cat && cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) + { + // At the moment background fetch only cares about VERSION_UNKNOWN, + // so do not check isCategoryComplete that compares descendant count, + // but if that is nesesary, do a forced scheduleFolderFetch. + cat->fetch(); + } + } + + if (!checkAgainstFilterThumbnails(folder_id)) + { + return false; + } + + // Marketplace folder filtering + const U32 filterTypes = mFilterOps.mFilterTypes; + const U32 marketplace_filter = FILTERTYPE_MARKETPLACE_ACTIVE | FILTERTYPE_MARKETPLACE_INACTIVE | + FILTERTYPE_MARKETPLACE_UNASSOCIATED | FILTERTYPE_MARKETPLACE_LISTING_FOLDER | + FILTERTYPE_NO_MARKETPLACE_ITEMS; + if (filterTypes & marketplace_filter) + { + S32 depth = depth_nesting_in_marketplace(folder_id); + + if (filterTypes & FILTERTYPE_NO_MARKETPLACE_ITEMS) + { + if (depth >= 0) + { + return false; + } + } + + if (filterTypes & FILTERTYPE_MARKETPLACE_LISTING_FOLDER) + { + if (depth > 1) + { + return false; + } + } + + if (depth > 0) + { + LLUUID listing_uuid = nested_parent_id(folder_id, depth); + if (filterTypes & FILTERTYPE_MARKETPLACE_ACTIVE) + { + if (!LLMarketplaceData::instance().getActivationState(listing_uuid)) + { + return false; + } + } + else if (filterTypes & FILTERTYPE_MARKETPLACE_INACTIVE) + { + if (!LLMarketplaceData::instance().isListed(listing_uuid) || LLMarketplaceData::instance().getActivationState(listing_uuid)) + { + return false; + } + } + else if (filterTypes & FILTERTYPE_MARKETPLACE_UNASSOCIATED) + { + if (LLMarketplaceData::instance().isListed(listing_uuid)) + { + return false; + } + } + } + } + + // show folder links + LLViewerInventoryItem* item = gInventory.getItem(folder_id); + if (item && item->getActualType() == LLAssetType::AT_LINK_FOLDER) + { + return true; + } + + if (mFilterOps.mFilterTypes & FILTERTYPE_CATEGORY) + { + // Can only filter categories for items in your inventory + // (e.g. versus in-world object contents). + const LLViewerInventoryCategory *cat = gInventory.getCategory(folder_id); + if (!cat) + return folder_id.isNull(); + LLFolderType::EType cat_type = cat->getPreferredType(); + if (cat_type != LLFolderType::FT_NONE && (1LL << cat_type & mFilterOps.mFilterCategoryTypes) == U64(0)) + return false; + } + + return true; +} + +bool LLInventoryFilter::checkAgainstFilterType(const LLFolderViewModelItemInventory* listener) const +{ + if (!listener) return false; + + LLInventoryType::EType object_type = listener->getInventoryType(); + const LLUUID object_id = listener->getUUID(); + const LLInventoryObject *object = gInventory.getObject(object_id); + + const U32 filterTypes = mFilterOps.mFilterTypes; + + //////////////////////////////////////////////////////////////////////////////// + // FILTERTYPE_OBJECT + // Pass if this item's type is of the correct filter type + if (filterTypes & FILTERTYPE_OBJECT) + { + switch (object_type) + { + case LLInventoryType::IT_NONE: + // If it has no type, pass it, unless it's a link. + if (object && object->getIsLinkType()) + { + return false; + } + break; + case LLInventoryType::IT_UNKNOWN: + { + // Unknows are only shown when we show every type. + // Unknows are 255 and won't fit in 64 bits. + if (mFilterOps.mFilterObjectTypes != 0xffffffffffffffffULL) + { + return false; + } + break; + } + default: + if ((1LL << object_type & mFilterOps.mFilterObjectTypes) == U64(0)) + { + return false; + } + break; + } + } + + if(filterTypes & FILTERTYPE_WORN) + { + if (!get_is_item_worn(object_id)) + { + return false; + } + } + + //////////////////////////////////////////////////////////////////////////////// + // FILTERTYPE_UUID + // Pass if this item is the target UUID or if it links to the target UUID + if (filterTypes & FILTERTYPE_UUID) + { + if (!object) return false; + + if (object->getLinkedUUID() != mFilterOps.mFilterUUID) + return false; + } + + //////////////////////////////////////////////////////////////////////////////// + // FILTERTYPE_DATE + // Pass if this item is within the date range. + if (filterTypes & FILTERTYPE_DATE) + { + const U16 HOURS_TO_SECONDS = 3600; + time_t earliest = time_corrected() - mFilterOps.mHoursAgo * HOURS_TO_SECONDS; + + if (mFilterOps.mMinDate > time_min() && mFilterOps.mMinDate < earliest) + { + earliest = mFilterOps.mMinDate; + } + else if (!mFilterOps.mHoursAgo) + { + earliest = 0; + } + + if (FILTERDATEDIRECTION_NEWER == mFilterOps.mDateSearchDirection || isSinceLogoff()) + { + if (listener->getCreationDate() < earliest || + listener->getCreationDate() > mFilterOps.mMaxDate) + return false; + } + else + { + if (listener->getCreationDate() > earliest || + listener->getCreationDate() > mFilterOps.mMaxDate) + return false; + } + } + + //////////////////////////////////////////////////////////////////////////////// + // FILTERTYPE_WEARABLE + // Pass if this item is a wearable of the appropriate type + if (filterTypes & FILTERTYPE_WEARABLE) + { + LLWearableType::EType type = listener->getWearableType(); + if ((object_type == LLInventoryType::IT_WEARABLE) && + (((0x1LL << type) & mFilterOps.mFilterWearableTypes) == 0)) + { + return false; + } + } + + //////////////////////////////////////////////////////////////////////////////// + // FILTERTYPE_SETTINGS + // Pass if this item is a setting of the appropriate type + if (filterTypes & FILTERTYPE_SETTINGS) + { + LLSettingsType::type_e type = listener->getSettingsType(); + if ((object_type == LLInventoryType::IT_SETTINGS) && + (((0x1LL << type) & mFilterOps.mFilterSettingsTypes) == 0)) + { + return false; + } + } + + //////////////////////////////////////////////////////////////////////////////// + // FILTERTYPE_EMPTYFOLDERS + // Pass if this item is a folder and is not a system folder that should be hidden + if (filterTypes & FILTERTYPE_EMPTYFOLDERS) + { + if (object_type == LLInventoryType::IT_CATEGORY) + { + bool is_hidden_if_empty = LLViewerFolderType::lookupIsHiddenIfEmpty(listener->getPreferredType()); + if (is_hidden_if_empty) + { + // Force the fetching of those folders so they are hidden if they really are empty... + // But don't interfere with startup download + if (LLStartUp::getStartupState() > STATE_WEARABLES_WAIT) + { + gInventory.fetchDescendentsOf(object_id); + } + + LLInventoryModel::cat_array_t* cat_array = NULL; + LLInventoryModel::item_array_t* item_array = NULL; + gInventory.getDirectDescendentsOf(object_id,cat_array,item_array); + S32 descendents_actual = 0; + if(cat_array && item_array) + { + descendents_actual = cat_array->size() + item_array->size(); + } + if (descendents_actual == 0) + { + return false; + } + } + } + } + + return true; +} + +bool LLInventoryFilter::checkAgainstFilterType(const LLInventoryItem* item) const +{ + LLInventoryType::EType object_type = item->getInventoryType(); + + const U32 filterTypes = mFilterOps.mFilterTypes; + + //////////////////////////////////////////////////////////////////////////////// + // FILTERTYPE_OBJECT + // Pass if this item's type is of the correct filter type + if (filterTypes & FILTERTYPE_OBJECT) + { + switch (object_type) + { + case LLInventoryType::IT_NONE: + // If it has no type, pass it, unless it's a link. + if (item && item->getIsLinkType()) + { + return false; + } + break; + case LLInventoryType::IT_UNKNOWN: + { + // Unknows are only shown when we show every type. + // Unknows are 255 and won't fit in 64 bits. + if (mFilterOps.mFilterObjectTypes != 0xffffffffffffffffULL) + { + return false; + } + break; + } + default: + if ((1LL << object_type & mFilterOps.mFilterObjectTypes) == U64(0)) + { + return false; + } + break; + } + } + + //////////////////////////////////////////////////////////////////////////////// + // FILTERTYPE_UUID + // Pass if this item is the target UUID or if it links to the target UUID + if (filterTypes & FILTERTYPE_UUID) + { + if (!item) return false; + + if (item->getLinkedUUID() != mFilterOps.mFilterUUID) + return false; + } + + //////////////////////////////////////////////////////////////////////////////// + // FILTERTYPE_DATE + // Pass if this item is within the date range. + if (filterTypes & FILTERTYPE_DATE) + { + // We don't get the updated item creation date for the task inventory or + // a notecard embedded item. See LLTaskInvFVBridge::getCreationDate(). + return false; + } + + return true; +} + +// Items and folders that are on the clipboard or, recursively, in a folder which +// is on the clipboard must be filtered out if the clipboard is in the "cut" mode. +bool LLInventoryFilter::checkAgainstClipboard(const LLUUID& object_id) const +{ + if (LLClipboard::instance().isCutMode()) + { + LL_PROFILE_ZONE_SCOPED; + LLUUID current_id = object_id; + LLInventoryObject *current_object = gInventory.getObject(object_id); + while (current_id.notNull() && current_object) + { + if (LLClipboard::instance().isOnClipboard(current_id)) + { + return false; + } + current_id = current_object->getParentUUID(); + if (current_id.notNull()) + { + current_object = gInventory.getObject(current_id); + } + } + } + return true; +} + +bool LLInventoryFilter::checkAgainstPermissions(const LLFolderViewModelItemInventory* listener) const +{ + if (!listener) return false; + + PermissionMask perm = listener->getPermissionMask(); + const LLInvFVBridge *bridge = dynamic_cast(listener); + if (bridge && bridge->isLink()) + { + const LLUUID& linked_uuid = gInventory.getLinkedItemID(bridge->getUUID()); + const LLViewerInventoryItem *linked_item = gInventory.getItem(linked_uuid); + if (linked_item) + perm = linked_item->getPermissionMask(); + } + return (perm & mFilterOps.mPermissions) == mFilterOps.mPermissions; +} + +bool LLInventoryFilter::checkAgainstPermissions(const LLInventoryItem* item) const +{ + if (!item) return false; + + LLPointer new_item = new LLViewerInventoryItem(item); + PermissionMask perm = new_item->getPermissionMask(); + new_item = NULL; + + return (perm & mFilterOps.mPermissions) == mFilterOps.mPermissions; +} + +bool LLInventoryFilter::checkAgainstFilterLinks(const LLFolderViewModelItemInventory* listener) const +{ + if (!listener) return true; + + const LLUUID object_id = listener->getUUID(); + const LLInventoryObject *object = gInventory.getObject(object_id); + if (!object) return true; + + const bool is_link = object->getIsLinkType(); + if (is_link && (mFilterOps.mFilterLinks == FILTERLINK_EXCLUDE_LINKS)) + return false; + if (!is_link && (mFilterOps.mFilterLinks == FILTERLINK_ONLY_LINKS)) + return false; + return true; +} + +bool LLInventoryFilter::checkAgainstFilterThumbnails(const LLUUID& object_id) const +{ + const LLInventoryObject *object = gInventory.getObject(object_id); + if (!object) return true; + + const bool is_thumbnail = object->getThumbnailUUID().notNull(); + if (is_thumbnail && (mFilterOps.mFilterThumbnails == FILTER_EXCLUDE_THUMBNAILS)) + return false; + if (!is_thumbnail && (mFilterOps.mFilterThumbnails == FILTER_ONLY_THUMBNAILS)) + return false; + return true; +} + +bool LLInventoryFilter::checkAgainstCreator(const LLFolderViewModelItemInventory* listener) const +{ + if (!listener) return true; + const bool is_folder = listener->getInventoryType() == LLInventoryType::IT_CATEGORY; + switch (mFilterOps.mFilterCreatorType) + { + case FILTERCREATOR_SELF: + if(is_folder) return false; + return (listener->getSearchableCreatorName() == mUsername); + case FILTERCREATOR_OTHERS: + if(is_folder) return false; + return (listener->getSearchableCreatorName() != mUsername); + case FILTERCREATOR_ALL: + default: + return true; + } +} + +bool LLInventoryFilter::checkAgainstSearchVisibility(const LLFolderViewModelItemInventory* listener) const +{ + if (!listener || !hasFilterString()) return true; + + const LLUUID object_id = listener->getUUID(); + const LLInventoryObject *object = gInventory.getObject(object_id); + if (!object) return true; + + const bool is_link = object->getIsLinkType(); + if (is_link && ((mFilterOps.mSearchVisibility & VISIBILITY_LINKS) == 0)) + return false; + + if (listener->isItemInOutfits() && ((mFilterOps.mSearchVisibility & VISIBILITY_OUTFITS) == 0)) + return false; + + if (listener->isItemInTrash() && ((mFilterOps.mSearchVisibility & VISIBILITY_TRASH) == 0)) + return false; + + if (!listener->isAgentInventory() && ((mFilterOps.mSearchVisibility & VISIBILITY_LIBRARY) == 0)) + return false; + + return true; +} + +const std::string& LLInventoryFilter::getFilterSubString(bool trim) const +{ + return mFilterSubString; +} + +std::string::size_type LLInventoryFilter::getStringMatchOffset(LLFolderViewModelItem* item) const +{ + if (mSearchType == SEARCHTYPE_NAME) + { + return mFilterSubString.size() ? item->getSearchableName().find(mFilterSubString) : std::string::npos; + } + else + { + return std::string::npos; + } +} + +bool LLInventoryFilter::isDefault() const +{ + return !isNotDefault(); +} + +// has user modified default filter params? +bool LLInventoryFilter::isNotDefault() const +{ + S32 not_default = 0; + + not_default |= (mFilterOps.mFilterObjectTypes != mDefaultFilterOps.mFilterObjectTypes); + not_default |= (mFilterOps.mFilterCategoryTypes != mDefaultFilterOps.mFilterCategoryTypes); + not_default |= (mFilterOps.mFilterWearableTypes != mDefaultFilterOps.mFilterWearableTypes); + not_default |= (mFilterOps.mFilterTypes != mDefaultFilterOps.mFilterTypes); + not_default |= (mFilterOps.mFilterLinks != mDefaultFilterOps.mFilterLinks); + not_default |= (mFilterSubString.size()); + not_default |= (mFilterOps.mPermissions != mDefaultFilterOps.mPermissions); + not_default |= (mFilterOps.mMinDate != mDefaultFilterOps.mMinDate); + not_default |= (mFilterOps.mMaxDate != mDefaultFilterOps.mMaxDate); + not_default |= (mFilterOps.mHoursAgo != mDefaultFilterOps.mHoursAgo); + + return not_default != 0; +} + +bool LLInventoryFilter::isActive() const +{ + return mFilterOps.mFilterObjectTypes != 0xffffffffffffffffULL + || mFilterOps.mFilterCategoryTypes != 0xffffffffffffffffULL + || mFilterOps.mFilterWearableTypes != 0xffffffffffffffffULL + || mFilterOps.mFilterTypes != FILTERTYPE_OBJECT + || mFilterOps.mFilterLinks != FILTERLINK_INCLUDE_LINKS + || mFilterSubString.size() + || mFilterOps.mPermissions != PERM_NONE + || mFilterOps.mMinDate != time_min() + || mFilterOps.mMaxDate != time_max() + || mFilterOps.mHoursAgo != 0; +} + +bool LLInventoryFilter::isModified() const +{ + return mFilterModified != FILTER_NONE; +} + +void LLInventoryFilter::updateFilterTypes(U64 types, U64& current_types) +{ + if (current_types != types) + { + // keep current items only if no type bits getting turned off + bool fewer_bits_set = (current_types & ~types) != 0; + bool more_bits_set = (~current_types & types) != 0; + + current_types = types; + if (more_bits_set && fewer_bits_set) + { + // neither less or more restrictive, both simultaneously + // so we need to filter from scratch + setModified(FILTER_RESTART); + } + else if (more_bits_set) + { + // target is only one of all requested types so more type bits == less restrictive + setModified(FILTER_LESS_RESTRICTIVE); + } + else if (fewer_bits_set) + { + setModified(FILTER_MORE_RESTRICTIVE); + } + } +} + +void LLInventoryFilter::setSearchType(ESearchType type) +{ + if(mSearchType != type) + { + mSearchType = type; + setModified(); + } +} + +void LLInventoryFilter::setFilterCreator(EFilterCreatorType type) +{ + if (mFilterOps.mFilterCreatorType != type) + { + mFilterOps.mFilterCreatorType = type; + setModified(); + } +} + +void LLInventoryFilter::setFilterObjectTypes(U64 types) +{ + updateFilterTypes(types, mFilterOps.mFilterObjectTypes); + mFilterOps.mFilterTypes |= FILTERTYPE_OBJECT; +} + +void LLInventoryFilter::setFilterCategoryTypes(U64 types) +{ + updateFilterTypes(types, mFilterOps.mFilterCategoryTypes); + mFilterOps.mFilterTypes |= FILTERTYPE_CATEGORY; +} + +void LLInventoryFilter::setFilterWearableTypes(U64 types) +{ + updateFilterTypes(types, mFilterOps.mFilterWearableTypes); + mFilterOps.mFilterTypes |= FILTERTYPE_WEARABLE; +} + +void LLInventoryFilter::setFilterSettingsTypes(U64 types) +{ + updateFilterTypes(types, mFilterOps.mFilterSettingsTypes); + mFilterOps.mFilterTypes |= FILTERTYPE_SETTINGS; +} + +void LLInventoryFilter::setFilterThumbnails(U64 filter_thumbnails) +{ + if (mFilterOps.mFilterThumbnails != filter_thumbnails) + { + if (mFilterOps.mFilterThumbnails == FILTER_EXCLUDE_THUMBNAILS + && filter_thumbnails == FILTER_ONLY_THUMBNAILS) + { + setModified(FILTER_RESTART); + } + else if (mFilterOps.mFilterThumbnails == FILTER_ONLY_THUMBNAILS + && filter_thumbnails == FILTER_EXCLUDE_THUMBNAILS) + { + setModified(FILTER_RESTART); + } + else if (mFilterOps.mFilterThumbnails == FILTER_INCLUDE_THUMBNAILS) + { + setModified(FILTER_MORE_RESTRICTIVE); + } + else + { + setModified(FILTER_LESS_RESTRICTIVE); + } + } + mFilterOps.mFilterThumbnails = filter_thumbnails; +} + +void LLInventoryFilter::setFilterEmptySystemFolders() +{ + mFilterOps.mFilterTypes |= FILTERTYPE_EMPTYFOLDERS; +} + +void LLInventoryFilter::setFilterWorn() +{ + mFilterOps.mFilterTypes |= FILTERTYPE_WORN; +} + +void LLInventoryFilter::setFilterMarketplaceActiveFolders() +{ + mFilterOps.mFilterTypes |= FILTERTYPE_MARKETPLACE_ACTIVE; +} + +void LLInventoryFilter::setFilterMarketplaceInactiveFolders() +{ + mFilterOps.mFilterTypes |= FILTERTYPE_MARKETPLACE_INACTIVE; +} + +void LLInventoryFilter::setFilterMarketplaceUnassociatedFolders() +{ + mFilterOps.mFilterTypes |= FILTERTYPE_MARKETPLACE_UNASSOCIATED; +} + +void LLInventoryFilter::setFilterMarketplaceListingFolders(bool select_only_listing_folders) +{ + if (select_only_listing_folders) + { + mFilterOps.mFilterTypes |= FILTERTYPE_MARKETPLACE_LISTING_FOLDER; + setModified(FILTER_MORE_RESTRICTIVE); + } + else + { + mFilterOps.mFilterTypes &= ~FILTERTYPE_MARKETPLACE_LISTING_FOLDER; + setModified(FILTER_LESS_RESTRICTIVE); + } +} + + +void LLInventoryFilter::toggleSearchVisibilityLinks() +{ + bool hide_links = mFilterOps.mSearchVisibility & VISIBILITY_LINKS; + if (hide_links) + { + mFilterOps.mSearchVisibility &= ~VISIBILITY_LINKS; + } + else + { + mFilterOps.mSearchVisibility |= VISIBILITY_LINKS; + } + + if (hasFilterString()) + { + setModified(hide_links ? FILTER_MORE_RESTRICTIVE : FILTER_LESS_RESTRICTIVE); + } +} + +void LLInventoryFilter::toggleSearchVisibilityOutfits() +{ + bool hide_outfits = mFilterOps.mSearchVisibility & VISIBILITY_OUTFITS; + if (hide_outfits) + { + mFilterOps.mSearchVisibility &= ~VISIBILITY_OUTFITS; + } + else + { + mFilterOps.mSearchVisibility |= VISIBILITY_OUTFITS; + } + + if (hasFilterString()) + { + setModified(hide_outfits ? FILTER_MORE_RESTRICTIVE : FILTER_LESS_RESTRICTIVE); + } +} + +void LLInventoryFilter::toggleSearchVisibilityTrash() +{ + bool hide_trash = mFilterOps.mSearchVisibility & VISIBILITY_TRASH; + if (hide_trash) + { + mFilterOps.mSearchVisibility &= ~VISIBILITY_TRASH; + } + else + { + mFilterOps.mSearchVisibility |= VISIBILITY_TRASH; + } + + if (hasFilterString()) + { + setModified(hide_trash ? FILTER_MORE_RESTRICTIVE : FILTER_LESS_RESTRICTIVE); + } +} + +void LLInventoryFilter::toggleSearchVisibilityLibrary() +{ + bool hide_library = mFilterOps.mSearchVisibility & VISIBILITY_LIBRARY; + if (hide_library) + { + mFilterOps.mSearchVisibility &= ~VISIBILITY_LIBRARY; + } + else + { + mFilterOps.mSearchVisibility |= VISIBILITY_LIBRARY; + } + + if (hasFilterString()) + { + setModified(hide_library ? FILTER_MORE_RESTRICTIVE : FILTER_LESS_RESTRICTIVE); + } +} + +void LLInventoryFilter::setFilterNoMarketplaceFolder() +{ + mFilterOps.mFilterTypes |= FILTERTYPE_NO_MARKETPLACE_ITEMS; +} + +void LLInventoryFilter::setFilterUUID(const LLUUID& object_id) +{ + if (mFilterOps.mFilterUUID == LLUUID::null) + { + setModified(FILTER_MORE_RESTRICTIVE); + } + else + { + setModified(FILTER_RESTART); + } + mFilterOps.mFilterUUID = object_id; + mFilterOps.mFilterTypes = FILTERTYPE_UUID; +} + +void LLInventoryFilter::setFilterSubString(const std::string& string) +{ + std::string filter_sub_string_new = string; + mFilterSubStringOrig = string; + LLStringUtil::trimHead(filter_sub_string_new); + LLStringUtil::toUpper(filter_sub_string_new); + + if (mFilterSubString != filter_sub_string_new) + { + + mFilterTokens.clear(); + if (filter_sub_string_new.find_first_of("+") != std::string::npos) + { + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("+"); + tokenizer tokens(filter_sub_string_new, sep); + + for (auto token_iter : tokens) + { + mFilterTokens.push_back(token_iter); + } + } + + std::string old_token = mExactToken; + mExactToken.clear(); + bool exact_token_changed = false; + if (mFilterTokens.empty() && filter_sub_string_new.size() > 2) + { + boost::regex mPattern = boost::regex("\"\\s*([^<]*)?\\s*\"", + boost::regex::perl | boost::regex::icase); + boost::match_results matches; + mExactToken = (ll_regex_match(filter_sub_string_new, matches, mPattern) && matches[1].matched) + ? matches[1] + : LLStringUtil::null; + if ((old_token.empty() && !mExactToken.empty()) + || (!old_token.empty() && mExactToken.empty())) + { + exact_token_changed = true; + } + } + + // hitting BACKSPACE, for example + const bool less_restrictive = mFilterSubString.size() >= filter_sub_string_new.size() + && !mFilterSubString.substr(0, filter_sub_string_new.size()).compare(filter_sub_string_new); + + // appending new characters + const bool more_restrictive = mFilterSubString.size() < filter_sub_string_new.size() + && !filter_sub_string_new.substr(0, mFilterSubString.size()).compare(mFilterSubString); + + mFilterSubString = filter_sub_string_new; + if (exact_token_changed) + { + setModified(FILTER_RESTART); + } + else if (less_restrictive) + { + setModified(FILTER_LESS_RESTRICTIVE); + } + else if (more_restrictive) + { + setModified(FILTER_MORE_RESTRICTIVE); + } + else + { + setModified(FILTER_RESTART); + } + + // Cancel out filter links once the search string is modified + if (mFilterOps.mFilterLinks == FILTERLINK_ONLY_LINKS) + { + if (mBackupFilterOps.mFilterLinks == FILTERLINK_ONLY_LINKS) + { + // we started viewer/floater in 'only links' mode + mFilterOps.mFilterLinks = FILTERLINK_INCLUDE_LINKS; + } + else + { + mFilterOps = mBackupFilterOps; + setModified(FILTER_RESTART); + } + } + + // Cancel out UUID once the search string is modified + if (mFilterOps.mFilterTypes == FILTERTYPE_UUID) + { + mFilterOps.mFilterTypes &= ~FILTERTYPE_UUID; + mFilterOps.mFilterUUID = LLUUID::null; + setModified(FILTER_RESTART); + } + } +} + +void LLInventoryFilter::setSearchVisibilityTypes(U32 types) +{ + if (mFilterOps.mSearchVisibility != types) + { + // keep current items only if no perm bits getting turned off + bool fewer_bits_set = (mFilterOps.mSearchVisibility & ~types); + bool more_bits_set = (~mFilterOps.mSearchVisibility & types); + mFilterOps.mSearchVisibility = types; + + if (more_bits_set && fewer_bits_set) + { + setModified(FILTER_RESTART); + } + else if (more_bits_set) + { + // target must have all requested permission bits, so more bits == more restrictive + setModified(FILTER_MORE_RESTRICTIVE); + } + else if (fewer_bits_set) + { + setModified(FILTER_LESS_RESTRICTIVE); + } + } +} + +void LLInventoryFilter::setSearchVisibilityTypes(const Params& params) +{ + if (!params.validateBlock()) + { + return; + } + + if (params.filter_ops.search_visibility.isProvided()) + { + setSearchVisibilityTypes(params.filter_ops.search_visibility); + } +} + +void LLInventoryFilter::setFilterPermissions(PermissionMask perms) +{ + if (mFilterOps.mPermissions != perms) + { + // keep current items only if no perm bits getting turned off + bool fewer_bits_set = (mFilterOps.mPermissions & ~perms); + bool more_bits_set = (~mFilterOps.mPermissions & perms); + mFilterOps.mPermissions = perms; + + if (more_bits_set && fewer_bits_set) + { + setModified(FILTER_RESTART); + } + else if (more_bits_set) + { + // target must have all requested permission bits, so more bits == more restrictive + setModified(FILTER_MORE_RESTRICTIVE); + } + else if (fewer_bits_set) + { + setModified(FILTER_LESS_RESTRICTIVE); + } + } +} + +void LLInventoryFilter::setDateRange(time_t min_date, time_t max_date) +{ + mFilterOps.mHoursAgo = 0; + if (mFilterOps.mMinDate != min_date) + { + mFilterOps.mMinDate = min_date; + setModified(); + } + if (mFilterOps.mMaxDate != llmax(mFilterOps.mMinDate, max_date)) + { + mFilterOps.mMaxDate = llmax(mFilterOps.mMinDate, max_date); + setModified(); + } + + if (areDateLimitsSet()) + { + mFilterOps.mFilterTypes |= FILTERTYPE_DATE; + } + else + { + mFilterOps.mFilterTypes &= ~FILTERTYPE_DATE; + } +} + +void LLInventoryFilter::setDateRangeLastLogoff(bool sl) +{ + static LLCachedControl s_last_logoff(gSavedPerAccountSettings, "LastLogoff", 0); + if (sl && !isSinceLogoff()) + { + setDateRange(s_last_logoff(), time_max()); + setModified(); + } + if (!sl && isSinceLogoff()) + { + setDateRange(time_min(), time_max()); + setModified(); + } + + if (areDateLimitsSet()) + { + mFilterOps.mFilterTypes |= FILTERTYPE_DATE; + } + else + { + mFilterOps.mFilterTypes &= ~FILTERTYPE_DATE; + } +} + +bool LLInventoryFilter::isSinceLogoff() const +{ + static LLCachedControl s_last_logoff(gSavedSettings, "LastLogoff", 0); + + return (mFilterOps.mMinDate == (time_t)s_last_logoff()) && + (mFilterOps.mMaxDate == time_max()) && + (mFilterOps.mFilterTypes & FILTERTYPE_DATE); +} + +void LLInventoryFilter::clearModified() +{ + mFilterModified = FILTER_NONE; +} + +void LLInventoryFilter::setHoursAgo(U32 hours) +{ + if (mFilterOps.mHoursAgo != hours) + { + bool are_date_limits_valid = mFilterOps.mMinDate == time_min() && mFilterOps.mMaxDate == time_max(); + + bool is_increasing = hours > mFilterOps.mHoursAgo; + bool is_decreasing = hours < mFilterOps.mHoursAgo; + bool is_increasing_from_zero = is_increasing && !mFilterOps.mHoursAgo && !isSinceLogoff(); + + // *NOTE: need to cache last filter time, in case filter goes stale + bool less_restrictive; + bool more_restrictive; + if (FILTERDATEDIRECTION_NEWER == mFilterOps.mDateSearchDirection) + { + less_restrictive = ((are_date_limits_valid && ((is_increasing && mFilterOps.mHoursAgo))) || !hours); + more_restrictive = ((are_date_limits_valid && (!is_increasing && hours)) || is_increasing_from_zero); + } + else + { + less_restrictive = ((are_date_limits_valid && ((is_decreasing && mFilterOps.mHoursAgo))) || !hours); + more_restrictive = ((are_date_limits_valid && (!is_decreasing && hours)) || is_increasing_from_zero); + } + + mFilterOps.mHoursAgo = hours; + mFilterOps.mMinDate = time_min(); + mFilterOps.mMaxDate = time_max(); + if (less_restrictive) + { + setModified(FILTER_LESS_RESTRICTIVE); + } + else if (more_restrictive) + { + setModified(FILTER_MORE_RESTRICTIVE); + } + else + { + setModified(FILTER_RESTART); + } + } + + if (areDateLimitsSet()) + { + mFilterOps.mFilterTypes |= FILTERTYPE_DATE; + } + else + { + mFilterOps.mFilterTypes &= ~FILTERTYPE_DATE; + } +} + +void LLInventoryFilter::setDateSearchDirection(U32 direction) +{ + if (direction != mFilterOps.mDateSearchDirection) + { + mFilterOps.mDateSearchDirection = direction; + setModified(FILTER_RESTART); + } +} + +U32 LLInventoryFilter::getDateSearchDirection() const +{ + return mFilterOps.mDateSearchDirection; +} + +void LLInventoryFilter::setFilterLinks(U64 filter_links) +{ + if (mFilterOps.mFilterLinks != filter_links) + { + if (mFilterOps.mFilterLinks == FILTERLINK_EXCLUDE_LINKS || + mFilterOps.mFilterLinks == FILTERLINK_ONLY_LINKS) + setModified(FILTER_MORE_RESTRICTIVE); + else + setModified(FILTER_LESS_RESTRICTIVE); + } + mFilterOps.mFilterLinks = filter_links; +} + +void LLInventoryFilter::setShowFolderState(EFolderShow state) +{ + if (mFilterOps.mShowFolderState != state) + { + mFilterOps.mShowFolderState = state; + if (state == SHOW_NON_EMPTY_FOLDERS) + { + // showing fewer folders than before + setModified(FILTER_MORE_RESTRICTIVE); + } + else if (state == SHOW_ALL_FOLDERS) + { + // showing same folders as before and then some + setModified(FILTER_LESS_RESTRICTIVE); + } + else + { + setModified(); + } + } +} + +void LLInventoryFilter::setFindAllLinksMode(const std::string &search_name, const LLUUID& search_id) +{ + // Save a copy of settings so that we will be able to restore it later + // but make sure we are not searching for links already + if(mFilterOps.mFilterLinks != FILTERLINK_ONLY_LINKS) + { + mBackupFilterOps = mFilterOps; + } + + // set search options + setFilterSubString(search_name); + setFilterUUID(search_id); + setShowFolderState(SHOW_NON_EMPTY_FOLDERS); + setFilterLinks(FILTERLINK_ONLY_LINKS); +} + +void LLInventoryFilter::markDefault() +{ + mDefaultFilterOps = mFilterOps; +} + +void LLInventoryFilter::resetDefault() +{ + mFilterOps = mDefaultFilterOps; + setModified(); +} + +void LLInventoryFilter::setModified(EFilterModified behavior) +{ + mFilterText.clear(); + mCurrentGeneration++; + + if (mFilterModified == FILTER_NONE) + { + mFilterModified = behavior; + } + else if (mFilterModified != behavior) + { + // trying to do both less restrictive and more restrictive filter + // basically means restart from scratch + mFilterModified = FILTER_RESTART; + } + + // if not keeping current filter results, update last valid as well + switch(mFilterModified) + { + case FILTER_RESTART: + mFirstRequiredGeneration = mCurrentGeneration; + mFirstSuccessGeneration = mCurrentGeneration; + break; + case FILTER_LESS_RESTRICTIVE: + mFirstRequiredGeneration = mCurrentGeneration; + break; + case FILTER_MORE_RESTRICTIVE: + mFirstSuccessGeneration = mCurrentGeneration; + break; + default: + LL_ERRS() << "Bad filter behavior specified" << LL_ENDL; + } +} + +bool LLInventoryFilter::isFilterObjectTypesWith(LLInventoryType::EType t) const +{ + return mFilterOps.mFilterObjectTypes & (1LL << t); +} + +const std::string& LLInventoryFilter::getFilterText() +{ + if (!mFilterText.empty()) + { + return mFilterText; + } + + std::string filtered_types; + std::string not_filtered_types; + bool filtered_by_type = false; + bool filtered_by_all_types = true; + S32 num_filter_types = 0; + + mFilterText.clear(); + + if (isFilterObjectTypesWith(LLInventoryType::IT_ANIMATION)) + { + filtered_types += LLTrans::getString("Animations"); + filtered_by_type = true; + num_filter_types++; + } + else + { + not_filtered_types += LLTrans::getString("Animations"); + + filtered_by_all_types = false; + } + + if (isFilterObjectTypesWith(LLInventoryType::IT_CALLINGCARD)) + { + filtered_types += LLTrans::getString("Calling Cards"); + filtered_by_type = true; + num_filter_types++; + } + else + { + not_filtered_types += LLTrans::getString("Calling Cards"); + filtered_by_all_types = false; + } + + if (isFilterObjectTypesWith(LLInventoryType::IT_WEARABLE)) + { + filtered_types += LLTrans::getString("Clothing"); + filtered_by_type = true; + num_filter_types++; + } + else + { + not_filtered_types += LLTrans::getString("Clothing"); + filtered_by_all_types = false; + } + + if (isFilterObjectTypesWith(LLInventoryType::IT_GESTURE)) + { + filtered_types += LLTrans::getString("Gestures"); + filtered_by_type = true; + num_filter_types++; + } + else + { + not_filtered_types += LLTrans::getString("Gestures"); + filtered_by_all_types = false; + } + + if (isFilterObjectTypesWith(LLInventoryType::IT_LANDMARK)) + { + filtered_types += LLTrans::getString("Landmarks"); + filtered_by_type = true; + num_filter_types++; + } + else + { + not_filtered_types += LLTrans::getString("Landmarks"); + filtered_by_all_types = false; + } + + if (isFilterObjectTypesWith(LLInventoryType::IT_MATERIAL)) + { + filtered_types += LLTrans::getString("Materials"); + filtered_by_type = true; + num_filter_types++; + } + else + { + not_filtered_types += LLTrans::getString("Materials"); + filtered_by_all_types = false; + } + + if (isFilterObjectTypesWith(LLInventoryType::IT_NOTECARD)) + { + filtered_types += LLTrans::getString("Notecards"); + filtered_by_type = true; + num_filter_types++; + } + else + { + not_filtered_types += LLTrans::getString("Notecards"); + filtered_by_all_types = false; + } + + if (isFilterObjectTypesWith(LLInventoryType::IT_OBJECT) && isFilterObjectTypesWith(LLInventoryType::IT_ATTACHMENT)) + { + filtered_types += LLTrans::getString("Objects"); + filtered_by_type = true; + num_filter_types++; + } + else + { + not_filtered_types += LLTrans::getString("Objects"); + filtered_by_all_types = false; + } + + if (isFilterObjectTypesWith(LLInventoryType::IT_LSL)) + { + filtered_types += LLTrans::getString("Scripts"); + filtered_by_type = true; + num_filter_types++; + } + else + { + not_filtered_types += LLTrans::getString("Scripts"); + filtered_by_all_types = false; + } + + if (isFilterObjectTypesWith(LLInventoryType::IT_SOUND)) + { + filtered_types += LLTrans::getString("Sounds"); + filtered_by_type = true; + num_filter_types++; + } + else + { + not_filtered_types += LLTrans::getString("Sounds"); + filtered_by_all_types = false; + } + + if (isFilterObjectTypesWith(LLInventoryType::IT_TEXTURE)) + { + filtered_types += LLTrans::getString("Textures"); + filtered_by_type = true; + num_filter_types++; + } + else + { + not_filtered_types += LLTrans::getString("Textures"); + filtered_by_all_types = false; + } + + if (isFilterObjectTypesWith(LLInventoryType::IT_SNAPSHOT)) + { + filtered_types += LLTrans::getString("Snapshots"); + filtered_by_type = true; + num_filter_types++; + } + else + { + not_filtered_types += LLTrans::getString("Snapshots"); + filtered_by_all_types = false; + } + + if (isFilterObjectTypesWith(LLInventoryType::IT_SETTINGS)) + { + filtered_types += LLTrans::getString("Settings"); + filtered_by_type = true; + num_filter_types++; + } + else + { + not_filtered_types += LLTrans::getString("Settings"); + filtered_by_all_types = false; + } + + if (!LLInventoryModelBackgroundFetch::instance().folderFetchActive() + && filtered_by_type + && !filtered_by_all_types) + { + mFilterText += " - "; + if (num_filter_types < 5) + { + mFilterText += filtered_types; + } + else + { + mFilterText += LLTrans::getString("No Filters"); + mFilterText += not_filtered_types; + } + // remove the ',' at the end + mFilterText.erase(mFilterText.size() - 1, 1); + } + + if (isSinceLogoff()) + { + mFilterText += LLTrans::getString("Since Logoff"); + } + return mFilterText; +} + + +LLInventoryFilter& LLInventoryFilter::operator=( const LLInventoryFilter& other ) +{ + setFilterObjectTypes(other.getFilterObjectTypes()); + setDateRange(other.getMinDate(), other.getMaxDate()); + setHoursAgo(other.getHoursAgo()); + setDateSearchDirection(other.getDateSearchDirection()); + setShowFolderState(other.getShowFolderState()); + setFilterPermissions(other.getFilterPermissions()); + setFilterSubString(other.getFilterSubString()); + setDateRangeLastLogoff(other.isSinceLogoff()); + return *this; +} + + +void LLInventoryFilter::toParams(Params& params) const +{ + params.filter_ops.types = getFilterObjectTypes(); + params.filter_ops.category_types = getFilterCategoryTypes(); + if (getFilterObjectTypes() & FILTERTYPE_WEARABLE) + { + params.filter_ops.wearable_types = getFilterWearableTypes(); + } + params.filter_ops.date_range.min_date = getMinDate(); + params.filter_ops.date_range.max_date = getMaxDate(); + params.filter_ops.hours_ago = getHoursAgo(); + params.filter_ops.date_search_direction = getDateSearchDirection(); + params.filter_ops.show_folder_state = getShowFolderState(); + params.filter_ops.creator_type = getFilterCreatorType(); + params.filter_ops.permissions = getFilterPermissions(); + params.filter_ops.search_visibility = getSearchVisibilityTypes(); + params.substring = getFilterSubString(); + params.since_logoff = isSinceLogoff(); +} + +void LLInventoryFilter::fromParams(const Params& params) +{ + if (!params.validateBlock()) + { + return; + } + + setFilterObjectTypes(params.filter_ops.types); + setFilterCategoryTypes(params.filter_ops.category_types); + if (params.filter_ops.wearable_types.isProvided()) + { + setFilterWearableTypes(params.filter_ops.wearable_types); + } + setDateRange(params.filter_ops.date_range.min_date, params.filter_ops.date_range.max_date); + setHoursAgo(params.filter_ops.hours_ago); + setDateSearchDirection(params.filter_ops.date_search_direction); + setShowFolderState(params.filter_ops.show_folder_state); + setFilterCreator(params.filter_ops.creator_type); + setFilterPermissions(params.filter_ops.permissions); + setSearchVisibilityTypes(params.filter_ops.search_visibility); + setFilterSubString(params.substring); + setDateRangeLastLogoff(params.since_logoff); +} + +U64 LLInventoryFilter::getFilterTypes() const +{ + return mFilterOps.mFilterTypes; +} + +U64 LLInventoryFilter::getFilterObjectTypes() const +{ + return mFilterOps.mFilterObjectTypes; +} + +U64 LLInventoryFilter::getFilterCategoryTypes() const +{ + return mFilterOps.mFilterCategoryTypes; +} + +U64 LLInventoryFilter::getFilterWearableTypes() const +{ + return mFilterOps.mFilterWearableTypes; +} + +U64 LLInventoryFilter::getFilterSettingsTypes() const +{ + return mFilterOps.mFilterSettingsTypes; +} + +U64 LLInventoryFilter::getSearchVisibilityTypes() const +{ + return mFilterOps.mSearchVisibility; +} + +U64 LLInventoryFilter::getFilterThumbnails() const +{ + return mFilterOps.mFilterThumbnails; +} + +bool LLInventoryFilter::hasFilterString() const +{ + return mFilterSubString.size() > 0; +} + +std::string::size_type LLInventoryFilter::getFilterStringSize() const +{ + return mFilterSubString.size(); +} + +PermissionMask LLInventoryFilter::getFilterPermissions() const +{ + return mFilterOps.mPermissions; +} + +time_t LLInventoryFilter::getMinDate() const +{ + return mFilterOps.mMinDate; +} + +time_t LLInventoryFilter::getMaxDate() const +{ + return mFilterOps.mMaxDate; +} +U32 LLInventoryFilter::getHoursAgo() const +{ + return mFilterOps.mHoursAgo; +} +U64 LLInventoryFilter::getFilterLinks() const +{ + return mFilterOps.mFilterLinks; +} +LLInventoryFilter::EFolderShow LLInventoryFilter::getShowFolderState() const +{ + return mFilterOps.mShowFolderState; +} + +LLInventoryFilter::EFilterCreatorType LLInventoryFilter::getFilterCreatorType() const +{ + return mFilterOps.mFilterCreatorType; +} + +bool LLInventoryFilter::isTimedOut() +{ + return mFilterTime.hasExpired(); +} + +void LLInventoryFilter::resetTime(S32 timeout) +{ + mFilterTime.reset(); + F32 time_in_sec = (F32)(timeout)/1000.0; + mFilterTime.setTimerExpirySec(time_in_sec); +} + +S32 LLInventoryFilter::getCurrentGeneration() const +{ + return mCurrentGeneration; +} +S32 LLInventoryFilter::getFirstSuccessGeneration() const +{ + return mFirstSuccessGeneration; +} +S32 LLInventoryFilter::getFirstRequiredGeneration() const +{ + return mFirstRequiredGeneration; +} + +void LLInventoryFilter::setEmptyLookupMessage(const std::string& message) +{ + mEmptyLookupMessage = message; +} + +void LLInventoryFilter::setDefaultEmptyLookupMessage(const std::string& message) +{ + mDefaultEmptyLookupMessage = message; +} + +std::string LLInventoryFilter::getEmptyLookupMessage(bool is_empty_folder) const +{ + if ((isDefault() || is_empty_folder) && !mDefaultEmptyLookupMessage.empty()) + { + return LLTrans::getString(mDefaultEmptyLookupMessage); + } + else + { + LLStringUtil::format_map_t args; + args["[SEARCH_TERM]"] = LLURI::escape(getFilterSubStringOrig()); + + return LLTrans::getString(mEmptyLookupMessage, args); + } + +} + +bool LLInventoryFilter::areDateLimitsSet() +{ + return mFilterOps.mMinDate != time_min() + || mFilterOps.mMaxDate != time_max() + || mFilterOps.mHoursAgo != 0; +} + +bool LLInventoryFilter::showAllResults() const +{ + return hasFilterString() && !mSingleFolderMode; +} + + + +bool LLInventoryFilter::FilterOps::DateRange::validateBlock( bool emit_errors /*= true*/ ) const +{ + bool valid = LLInitParam::Block::validateBlock(emit_errors); + if (valid) + { + if (max_date() < min_date()) + { + if (emit_errors) + { + LL_WARNS() << "max_date should be greater or equal to min_date" << LL_ENDL; + } + valid = false; + } + } + return valid; +} diff --git a/indra/newview/llinventoryfilter.h b/indra/newview/llinventoryfilter.h index 49d4346171..7203c6f743 100644 --- a/indra/newview/llinventoryfilter.h +++ b/indra/newview/llinventoryfilter.h @@ -1,384 +1,384 @@ -/** -* @file llinventoryfilter.h -* @brief Support for filtering your inventory to only display a subset of the -* available items. -* -* $LicenseInfo:firstyear=2005&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2010, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ -#ifndef LLINVENTORYFILTER_H -#define LLINVENTORYFILTER_H - -#include "llinventorytype.h" -#include "llpermissionsflags.h" -#include "llfolderviewmodel.h" - -class LLFolderViewItem; -class LLFolderViewFolder; -class LLInventoryItem; - -class LLInventoryFilter : public LLFolderViewFilter -{ -public: - enum EFolderShow - { - SHOW_ALL_FOLDERS, - SHOW_NON_EMPTY_FOLDERS, - SHOW_NO_FOLDERS - }; - - enum EFilterType { - FILTERTYPE_NONE = 0, - FILTERTYPE_OBJECT = 0x1 << 0, // normal default search-by-object-type - FILTERTYPE_CATEGORY = 0x1 << 1, // search by folder type - FILTERTYPE_UUID = 0x1 << 2, // find the object with UUID and any links to it - FILTERTYPE_DATE = 0x1 << 3, // search by date range - FILTERTYPE_WEARABLE = 0x1 << 4, // search by wearable type - FILTERTYPE_EMPTYFOLDERS = 0x1 << 5, // pass if folder is not a system folder to be hidden if empty - FILTERTYPE_MARKETPLACE_ACTIVE = 0x1 << 6, // pass if folder is a marketplace active folder - FILTERTYPE_MARKETPLACE_INACTIVE = 0x1 << 7, // pass if folder is a marketplace inactive folder - FILTERTYPE_MARKETPLACE_UNASSOCIATED = 0x1 << 8, // pass if folder is a marketplace non associated (no market ID) folder - FILTERTYPE_MARKETPLACE_LISTING_FOLDER = 0x1 << 9, // pass iff folder is a listing folder - FILTERTYPE_NO_MARKETPLACE_ITEMS = 0x1 << 10, // pass iff folder is not under the marketplace - FILTERTYPE_WORN = 0x1 << 11, // pass if item is worn - FILTERTYPE_SETTINGS = 0x1 << 12, // pass if the item is a settings object - }; - - enum EFilterDateDirection - { - FILTERDATEDIRECTION_NEWER, - FILTERDATEDIRECTION_OLDER - }; - - enum EFilterLink - { - FILTERLINK_INCLUDE_LINKS, // show links too - FILTERLINK_EXCLUDE_LINKS, // don't show links - FILTERLINK_ONLY_LINKS // only show links - }; - - enum EFilterThumbnail - { - FILTER_INCLUDE_THUMBNAILS, - FILTER_EXCLUDE_THUMBNAILS, - FILTER_ONLY_THUMBNAILS - }; - - enum ESortOrderType - { - SO_NAME = 0, // Sort inventory by name - SO_DATE = 0x1, // Sort inventory by date - SO_FOLDERS_BY_NAME = 0x1 << 1, // Force folder sort by name - SO_SYSTEM_FOLDERS_TO_TOP = 0x1 << 2,// Force system folders to be on top - SO_FOLDERS_BY_WEIGHT = 0x1 << 3, // Force folder sort by weight, usually, amount of some elements in their descendants - }; - - enum ESearchType - { - SEARCHTYPE_NAME, - SEARCHTYPE_DESCRIPTION, - SEARCHTYPE_CREATOR, - SEARCHTYPE_UUID - }; - - enum EFilterCreatorType - { - FILTERCREATOR_ALL, - FILTERCREATOR_SELF, - FILTERCREATOR_OTHERS - }; - - enum ESearchVisibility - { - VISIBILITY_NONE = 0, - VISIBILITY_TRASH = 0x1 << 0, - VISIBILITY_LIBRARY = 0x1 << 1, - VISIBILITY_LINKS = 0x1 << 2, - VISIBILITY_OUTFITS = 0x1 << 3 - }; - - struct FilterOps - { - struct DateRange : public LLInitParam::Block - { - Optional min_date, - max_date; - - DateRange() - : min_date("min_date", time_min()), - max_date("max_date", time_max()) - {} - - bool validateBlock(bool emit_errors = true) const; - }; - - struct Params : public LLInitParam::Block - { - Optional types, - search_visibility; - Optional object_types, - wearable_types, - settings_types, - category_types; - - Optional links; - Optional uuid; - Optional date_range; - Optional hours_ago; - Optional date_search_direction; - Optional show_folder_state; - Optional permissions; - Optional creator_type; - Optional thumbnails; - - Params() - : types("filter_types", FILTERTYPE_OBJECT), - object_types("object_types", 0xffffFFFFffffFFFFULL), - wearable_types("wearable_types", 0xffffFFFFffffFFFFULL), - settings_types("settings_types", 0xffffFFFFffffFFFFULL), - thumbnails("thumbnails", FILTER_INCLUDE_THUMBNAILS), - category_types("category_types", 0xffffFFFFffffFFFFULL), - links("links", FILTERLINK_INCLUDE_LINKS), - search_visibility("search_visibility", 0xFFFFFFFF), - uuid("uuid"), - date_range("date_range"), - hours_ago("hours_ago", 0), - date_search_direction("date_search_direction", FILTERDATEDIRECTION_NEWER), - show_folder_state("show_folder_state", SHOW_NON_EMPTY_FOLDERS), - creator_type("creator_type", FILTERCREATOR_ALL), - permissions("permissions", PERM_NONE) - {} - }; - - FilterOps(const Params& = Params()); - - U32 mFilterTypes, - mSearchVisibility; - U64 mFilterObjectTypes, // For _OBJECT - mFilterWearableTypes, - mFilterSettingsTypes, // for _SETTINGS - mFilterThumbnails, - mFilterLinks, - mFilterCategoryTypes; // For _CATEGORY - LLUUID mFilterUUID; // for UUID - - time_t mMinDate, - mMaxDate; - U32 mHoursAgo; - U32 mDateSearchDirection; - - EFolderShow mShowFolderState; - PermissionMask mPermissions; - EFilterCreatorType mFilterCreatorType; - }; - - struct Params : public LLInitParam::Block - { - Optional name; - Optional filter_ops; - Optional substring; - Optional since_logoff; - - Params() - : name("name"), - filter_ops(""), - substring("substring"), - since_logoff("since_logoff") - {} - }; - - LLInventoryFilter(const Params& p = Params()); - LLInventoryFilter(const LLInventoryFilter& other) { *this = other; } - virtual ~LLInventoryFilter() {} - - // +-------------------------------------------------------------------+ - // + Parameters - // +-------------------------------------------------------------------+ - U64 getFilterTypes() const; - U64 getFilterObjectTypes() const; - U64 getFilterCategoryTypes() const; - U64 getFilterWearableTypes() const; - U64 getFilterSettingsTypes() const; - U64 getSearchVisibilityTypes() const; - U64 getFilterThumbnails() const; - - bool isFilterObjectTypesWith(LLInventoryType::EType t) const; - void setFilterObjectTypes(U64 types); - void setFilterCategoryTypes(U64 types); - void setFilterUUID(const LLUUID &object_id); - void setFilterWearableTypes(U64 types); - void setFilterSettingsTypes(U64 types); - void setFilterEmptySystemFolders(); - void setFilterWorn(); - void setFilterMarketplaceActiveFolders(); - void setFilterMarketplaceInactiveFolders(); - void setFilterMarketplaceUnassociatedFolders(); - void setFilterMarketplaceListingFolders(bool select_only_listing_folders); - void setFilterNoMarketplaceFolder(); - void setFilterThumbnails(U64 filter_thumbnails); - void updateFilterTypes(U64 types, U64& current_types); - void setSearchType(ESearchType type); - ESearchType getSearchType() { return mSearchType; } - void setFilterCreator(EFilterCreatorType type); - - void toggleSearchVisibilityLinks(); - void toggleSearchVisibilityTrash(); - void toggleSearchVisibilityOutfits(); - void toggleSearchVisibilityLibrary(); - void setSearchVisibilityTypes(U32 types); - void setSearchVisibilityTypes(const Params& params); - - void setFilterSubString(const std::string& string); - const std::string& getFilterSubString(bool trim = false) const; - const std::string& getFilterSubStringOrig() const { return mFilterSubStringOrig; } - bool hasFilterString() const; - - void setSingleFolderMode(bool is_single_folder) { mSingleFolderMode = is_single_folder; } - - void setFilterPermissions(PermissionMask perms); - PermissionMask getFilterPermissions() const; - - void setDateRange(time_t min_date, time_t max_date); - void setDateRangeLastLogoff(bool sl); - time_t getMinDate() const; - time_t getMaxDate() const; - - void setHoursAgo(U32 hours); - U32 getHoursAgo() const; - void setDateSearchDirection(U32 direction); - U32 getDateSearchDirection() const; - - void setFilterLinks(U64 filter_link); - U64 getFilterLinks() const; - - // sets params for Link-only search and backs up search settings for future restoration - void setFindAllLinksMode(const std::string &search_name, const LLUUID& search_id); - - // +-------------------------------------------------------------------+ - // + Execution And Results - // +-------------------------------------------------------------------+ - bool check(const LLFolderViewModelItem* listener); - bool check(const LLInventoryItem* item); - bool checkFolder(const LLFolderViewModelItem* listener) const; - bool checkFolder(const LLUUID& folder_id) const; - - bool showAllResults() const; - - std::string::size_type getStringMatchOffset(LLFolderViewModelItem* item) const; - std::string::size_type getFilterStringSize() const; - // +-------------------------------------------------------------------+ - // + Presentation - // +-------------------------------------------------------------------+ - void setShowFolderState( EFolderShow state); - EFolderShow getShowFolderState() const; - EFilterCreatorType getFilterCreatorType() const; - - void setEmptyLookupMessage(const std::string& message); - void setDefaultEmptyLookupMessage(const std::string& message); - std::string getEmptyLookupMessage(bool is_empty_folder = false) const; - - // +-------------------------------------------------------------------+ - // + Status - // +-------------------------------------------------------------------+ - bool isActive() const; - bool isModified() const; - bool isSinceLogoff() const; - void clearModified(); - const std::string& getName() const { return mName; } - const std::string& getFilterText(); - //RN: this is public to allow system to externally force a global refilter - void setModified(EFilterModified behavior = FILTER_RESTART); - - // +-------------------------------------------------------------------+ - // + Time - // +-------------------------------------------------------------------+ - void resetTime(S32 timeout); - bool isTimedOut(); - - // +-------------------------------------------------------------------+ - // + Default - // +-------------------------------------------------------------------+ - bool isDefault() const; - bool isNotDefault() const; - void markDefault(); - void resetDefault(); - - // +-------------------------------------------------------------------+ - // + Generation - // +-------------------------------------------------------------------+ - S32 getCurrentGeneration() const; - S32 getFirstSuccessGeneration() const; - S32 getFirstRequiredGeneration() const; - - - // +-------------------------------------------------------------------+ - // + Conversion - // +-------------------------------------------------------------------+ - void toParams(Params& params) const; - void fromParams(const Params& p); - - LLInventoryFilter& operator =(const LLInventoryFilter& other); - - bool checkAgainstFilterThumbnails(const LLUUID& object_id) const; - -private: - bool areDateLimitsSet(); - bool checkAgainstFilterType(const class LLFolderViewModelItemInventory* listener) const; - bool checkAgainstFilterType(const LLInventoryItem* item) const; - bool checkAgainstPermissions(const class LLFolderViewModelItemInventory* listener) const; - bool checkAgainstPermissions(const LLInventoryItem* item) const; - bool checkAgainstFilterLinks(const class LLFolderViewModelItemInventory* listener) const; - bool checkAgainstCreator(const class LLFolderViewModelItemInventory* listener) const; - bool checkAgainstSearchVisibility(const class LLFolderViewModelItemInventory* listener) const; - bool checkAgainstClipboard(const LLUUID& object_id) const; - - FilterOps mFilterOps; - FilterOps mDefaultFilterOps; - FilterOps mBackupFilterOps; // for backup purposes when leaving 'search link' mode - - std::string mFilterSubString; - std::string mFilterSubStringOrig; - std::string mUsername; - const std::string mName; - - S32 mCurrentGeneration; - // The following makes checking for pass/no pass possible even if the item is not checked against the current generation - // Any item that *did not pass* the "required generation" will *not pass* the current one - // Any item that *passes* the "success generation" will *pass* the current one - S32 mFirstRequiredGeneration; - S32 mFirstSuccessGeneration; - - EFilterModified mFilterModified; - LLTimer mFilterTime; - - std::string mFilterText; - std::string mEmptyLookupMessage; - std::string mDefaultEmptyLookupMessage; - - ESearchType mSearchType; - - std::vector mFilterTokens; - std::string mExactToken; - - bool mSingleFolderMode; -}; - -#endif +/** +* @file llinventoryfilter.h +* @brief Support for filtering your inventory to only display a subset of the +* available items. +* +* $LicenseInfo:firstyear=2005&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2010, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LLINVENTORYFILTER_H +#define LLINVENTORYFILTER_H + +#include "llinventorytype.h" +#include "llpermissionsflags.h" +#include "llfolderviewmodel.h" + +class LLFolderViewItem; +class LLFolderViewFolder; +class LLInventoryItem; + +class LLInventoryFilter : public LLFolderViewFilter +{ +public: + enum EFolderShow + { + SHOW_ALL_FOLDERS, + SHOW_NON_EMPTY_FOLDERS, + SHOW_NO_FOLDERS + }; + + enum EFilterType { + FILTERTYPE_NONE = 0, + FILTERTYPE_OBJECT = 0x1 << 0, // normal default search-by-object-type + FILTERTYPE_CATEGORY = 0x1 << 1, // search by folder type + FILTERTYPE_UUID = 0x1 << 2, // find the object with UUID and any links to it + FILTERTYPE_DATE = 0x1 << 3, // search by date range + FILTERTYPE_WEARABLE = 0x1 << 4, // search by wearable type + FILTERTYPE_EMPTYFOLDERS = 0x1 << 5, // pass if folder is not a system folder to be hidden if empty + FILTERTYPE_MARKETPLACE_ACTIVE = 0x1 << 6, // pass if folder is a marketplace active folder + FILTERTYPE_MARKETPLACE_INACTIVE = 0x1 << 7, // pass if folder is a marketplace inactive folder + FILTERTYPE_MARKETPLACE_UNASSOCIATED = 0x1 << 8, // pass if folder is a marketplace non associated (no market ID) folder + FILTERTYPE_MARKETPLACE_LISTING_FOLDER = 0x1 << 9, // pass iff folder is a listing folder + FILTERTYPE_NO_MARKETPLACE_ITEMS = 0x1 << 10, // pass iff folder is not under the marketplace + FILTERTYPE_WORN = 0x1 << 11, // pass if item is worn + FILTERTYPE_SETTINGS = 0x1 << 12, // pass if the item is a settings object + }; + + enum EFilterDateDirection + { + FILTERDATEDIRECTION_NEWER, + FILTERDATEDIRECTION_OLDER + }; + + enum EFilterLink + { + FILTERLINK_INCLUDE_LINKS, // show links too + FILTERLINK_EXCLUDE_LINKS, // don't show links + FILTERLINK_ONLY_LINKS // only show links + }; + + enum EFilterThumbnail + { + FILTER_INCLUDE_THUMBNAILS, + FILTER_EXCLUDE_THUMBNAILS, + FILTER_ONLY_THUMBNAILS + }; + + enum ESortOrderType + { + SO_NAME = 0, // Sort inventory by name + SO_DATE = 0x1, // Sort inventory by date + SO_FOLDERS_BY_NAME = 0x1 << 1, // Force folder sort by name + SO_SYSTEM_FOLDERS_TO_TOP = 0x1 << 2,// Force system folders to be on top + SO_FOLDERS_BY_WEIGHT = 0x1 << 3, // Force folder sort by weight, usually, amount of some elements in their descendants + }; + + enum ESearchType + { + SEARCHTYPE_NAME, + SEARCHTYPE_DESCRIPTION, + SEARCHTYPE_CREATOR, + SEARCHTYPE_UUID + }; + + enum EFilterCreatorType + { + FILTERCREATOR_ALL, + FILTERCREATOR_SELF, + FILTERCREATOR_OTHERS + }; + + enum ESearchVisibility + { + VISIBILITY_NONE = 0, + VISIBILITY_TRASH = 0x1 << 0, + VISIBILITY_LIBRARY = 0x1 << 1, + VISIBILITY_LINKS = 0x1 << 2, + VISIBILITY_OUTFITS = 0x1 << 3 + }; + + struct FilterOps + { + struct DateRange : public LLInitParam::Block + { + Optional min_date, + max_date; + + DateRange() + : min_date("min_date", time_min()), + max_date("max_date", time_max()) + {} + + bool validateBlock(bool emit_errors = true) const; + }; + + struct Params : public LLInitParam::Block + { + Optional types, + search_visibility; + Optional object_types, + wearable_types, + settings_types, + category_types; + + Optional links; + Optional uuid; + Optional date_range; + Optional hours_ago; + Optional date_search_direction; + Optional show_folder_state; + Optional permissions; + Optional creator_type; + Optional thumbnails; + + Params() + : types("filter_types", FILTERTYPE_OBJECT), + object_types("object_types", 0xffffFFFFffffFFFFULL), + wearable_types("wearable_types", 0xffffFFFFffffFFFFULL), + settings_types("settings_types", 0xffffFFFFffffFFFFULL), + thumbnails("thumbnails", FILTER_INCLUDE_THUMBNAILS), + category_types("category_types", 0xffffFFFFffffFFFFULL), + links("links", FILTERLINK_INCLUDE_LINKS), + search_visibility("search_visibility", 0xFFFFFFFF), + uuid("uuid"), + date_range("date_range"), + hours_ago("hours_ago", 0), + date_search_direction("date_search_direction", FILTERDATEDIRECTION_NEWER), + show_folder_state("show_folder_state", SHOW_NON_EMPTY_FOLDERS), + creator_type("creator_type", FILTERCREATOR_ALL), + permissions("permissions", PERM_NONE) + {} + }; + + FilterOps(const Params& = Params()); + + U32 mFilterTypes, + mSearchVisibility; + U64 mFilterObjectTypes, // For _OBJECT + mFilterWearableTypes, + mFilterSettingsTypes, // for _SETTINGS + mFilterThumbnails, + mFilterLinks, + mFilterCategoryTypes; // For _CATEGORY + LLUUID mFilterUUID; // for UUID + + time_t mMinDate, + mMaxDate; + U32 mHoursAgo; + U32 mDateSearchDirection; + + EFolderShow mShowFolderState; + PermissionMask mPermissions; + EFilterCreatorType mFilterCreatorType; + }; + + struct Params : public LLInitParam::Block + { + Optional name; + Optional filter_ops; + Optional substring; + Optional since_logoff; + + Params() + : name("name"), + filter_ops(""), + substring("substring"), + since_logoff("since_logoff") + {} + }; + + LLInventoryFilter(const Params& p = Params()); + LLInventoryFilter(const LLInventoryFilter& other) { *this = other; } + virtual ~LLInventoryFilter() {} + + // +-------------------------------------------------------------------+ + // + Parameters + // +-------------------------------------------------------------------+ + U64 getFilterTypes() const; + U64 getFilterObjectTypes() const; + U64 getFilterCategoryTypes() const; + U64 getFilterWearableTypes() const; + U64 getFilterSettingsTypes() const; + U64 getSearchVisibilityTypes() const; + U64 getFilterThumbnails() const; + + bool isFilterObjectTypesWith(LLInventoryType::EType t) const; + void setFilterObjectTypes(U64 types); + void setFilterCategoryTypes(U64 types); + void setFilterUUID(const LLUUID &object_id); + void setFilterWearableTypes(U64 types); + void setFilterSettingsTypes(U64 types); + void setFilterEmptySystemFolders(); + void setFilterWorn(); + void setFilterMarketplaceActiveFolders(); + void setFilterMarketplaceInactiveFolders(); + void setFilterMarketplaceUnassociatedFolders(); + void setFilterMarketplaceListingFolders(bool select_only_listing_folders); + void setFilterNoMarketplaceFolder(); + void setFilterThumbnails(U64 filter_thumbnails); + void updateFilterTypes(U64 types, U64& current_types); + void setSearchType(ESearchType type); + ESearchType getSearchType() { return mSearchType; } + void setFilterCreator(EFilterCreatorType type); + + void toggleSearchVisibilityLinks(); + void toggleSearchVisibilityTrash(); + void toggleSearchVisibilityOutfits(); + void toggleSearchVisibilityLibrary(); + void setSearchVisibilityTypes(U32 types); + void setSearchVisibilityTypes(const Params& params); + + void setFilterSubString(const std::string& string); + const std::string& getFilterSubString(bool trim = false) const; + const std::string& getFilterSubStringOrig() const { return mFilterSubStringOrig; } + bool hasFilterString() const; + + void setSingleFolderMode(bool is_single_folder) { mSingleFolderMode = is_single_folder; } + + void setFilterPermissions(PermissionMask perms); + PermissionMask getFilterPermissions() const; + + void setDateRange(time_t min_date, time_t max_date); + void setDateRangeLastLogoff(bool sl); + time_t getMinDate() const; + time_t getMaxDate() const; + + void setHoursAgo(U32 hours); + U32 getHoursAgo() const; + void setDateSearchDirection(U32 direction); + U32 getDateSearchDirection() const; + + void setFilterLinks(U64 filter_link); + U64 getFilterLinks() const; + + // sets params for Link-only search and backs up search settings for future restoration + void setFindAllLinksMode(const std::string &search_name, const LLUUID& search_id); + + // +-------------------------------------------------------------------+ + // + Execution And Results + // +-------------------------------------------------------------------+ + bool check(const LLFolderViewModelItem* listener); + bool check(const LLInventoryItem* item); + bool checkFolder(const LLFolderViewModelItem* listener) const; + bool checkFolder(const LLUUID& folder_id) const; + + bool showAllResults() const; + + std::string::size_type getStringMatchOffset(LLFolderViewModelItem* item) const; + std::string::size_type getFilterStringSize() const; + // +-------------------------------------------------------------------+ + // + Presentation + // +-------------------------------------------------------------------+ + void setShowFolderState( EFolderShow state); + EFolderShow getShowFolderState() const; + EFilterCreatorType getFilterCreatorType() const; + + void setEmptyLookupMessage(const std::string& message); + void setDefaultEmptyLookupMessage(const std::string& message); + std::string getEmptyLookupMessage(bool is_empty_folder = false) const; + + // +-------------------------------------------------------------------+ + // + Status + // +-------------------------------------------------------------------+ + bool isActive() const; + bool isModified() const; + bool isSinceLogoff() const; + void clearModified(); + const std::string& getName() const { return mName; } + const std::string& getFilterText(); + //RN: this is public to allow system to externally force a global refilter + void setModified(EFilterModified behavior = FILTER_RESTART); + + // +-------------------------------------------------------------------+ + // + Time + // +-------------------------------------------------------------------+ + void resetTime(S32 timeout); + bool isTimedOut(); + + // +-------------------------------------------------------------------+ + // + Default + // +-------------------------------------------------------------------+ + bool isDefault() const; + bool isNotDefault() const; + void markDefault(); + void resetDefault(); + + // +-------------------------------------------------------------------+ + // + Generation + // +-------------------------------------------------------------------+ + S32 getCurrentGeneration() const; + S32 getFirstSuccessGeneration() const; + S32 getFirstRequiredGeneration() const; + + + // +-------------------------------------------------------------------+ + // + Conversion + // +-------------------------------------------------------------------+ + void toParams(Params& params) const; + void fromParams(const Params& p); + + LLInventoryFilter& operator =(const LLInventoryFilter& other); + + bool checkAgainstFilterThumbnails(const LLUUID& object_id) const; + +private: + bool areDateLimitsSet(); + bool checkAgainstFilterType(const class LLFolderViewModelItemInventory* listener) const; + bool checkAgainstFilterType(const LLInventoryItem* item) const; + bool checkAgainstPermissions(const class LLFolderViewModelItemInventory* listener) const; + bool checkAgainstPermissions(const LLInventoryItem* item) const; + bool checkAgainstFilterLinks(const class LLFolderViewModelItemInventory* listener) const; + bool checkAgainstCreator(const class LLFolderViewModelItemInventory* listener) const; + bool checkAgainstSearchVisibility(const class LLFolderViewModelItemInventory* listener) const; + bool checkAgainstClipboard(const LLUUID& object_id) const; + + FilterOps mFilterOps; + FilterOps mDefaultFilterOps; + FilterOps mBackupFilterOps; // for backup purposes when leaving 'search link' mode + + std::string mFilterSubString; + std::string mFilterSubStringOrig; + std::string mUsername; + const std::string mName; + + S32 mCurrentGeneration; + // The following makes checking for pass/no pass possible even if the item is not checked against the current generation + // Any item that *did not pass* the "required generation" will *not pass* the current one + // Any item that *passes* the "success generation" will *pass* the current one + S32 mFirstRequiredGeneration; + S32 mFirstSuccessGeneration; + + EFilterModified mFilterModified; + LLTimer mFilterTime; + + std::string mFilterText; + std::string mEmptyLookupMessage; + std::string mDefaultEmptyLookupMessage; + + ESearchType mSearchType; + + std::vector mFilterTokens; + std::string mExactToken; + + bool mSingleFolderMode; +}; + +#endif diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp index 91f5f18671..5533c0328e 100644 --- a/indra/newview/llinventoryfunctions.cpp +++ b/indra/newview/llinventoryfunctions.cpp @@ -1,3711 +1,3711 @@ -/** - * @file llinventoryfunctions.cpp - * @brief Implementation of the inventory view and associated stuff. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include // for std::pair<> - -#include "llinventoryfunctions.h" - -// library includes -#include "llagent.h" -#include "llagentwearables.h" -#include "llcallingcard.h" -#include "llfloaterreg.h" -#include "llinventorydefines.h" -#include "llsdserialize.h" -#include "llfiltereditor.h" -#include "llspinctrl.h" -#include "llui.h" -#include "message.h" - -// newview includes -#include "llappearancemgr.h" -#include "llappviewer.h" -#include "llavataractions.h" -#include "llavatarnamecache.h" -#include "llclipboard.h" -#include "lldirpicker.h" -#include "lldonotdisturbnotificationstorage.h" -#include "llfloatermarketplacelistings.h" -#include "llfloatersidepanelcontainer.h" -#include "llfocusmgr.h" -#include "llfolderview.h" -#include "llgesturemgr.h" -#include "llgiveinventory.h" -#include "lliconctrl.h" -#include "llimview.h" -#include "llinventorybridge.h" -#include "llinventorymodel.h" -#include "llinventorypanel.h" -#include "lllineeditor.h" -#include "llmarketplacenotifications.h" -#include "llmarketplacefunctions.h" -#include "llmenugl.h" -#include "llnotificationsutil.h" -#include "llpanelmaininventory.h" -#include "llpreviewanim.h" -#include "llpreviewgesture.h" -#include "llpreviewnotecard.h" -#include "llpreviewscript.h" -#include "llpreviewsound.h" -#include "llpreviewtexture.h" -#include "llresmgr.h" -#include "llscrollbar.h" -#include "llscrollcontainer.h" -#include "llselectmgr.h" -#include "llsidepanelinventory.h" -#include "lltabcontainer.h" -#include "lltooldraganddrop.h" -#include "lltrans.h" -#include "lluictrlfactory.h" -#include "llviewermenu.h" -#include "llviewermessage.h" -#include "llviewerfoldertype.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llviewerwindow.h" -#include "llvoavatarself.h" -#include "llwearablelist.h" - -bool LLInventoryState::sWearNewClothing = false; -LLUUID LLInventoryState::sWearNewClothingTransactionID; -std::list LLInventoryAction::sMarketplaceFolders; -bool LLInventoryAction::sDeleteConfirmationDisplayed = false; - -// Helper function : callback to update a folder after inventory action happened in the background -void update_folder_cb(const LLUUID& dest_folder) -{ - LLViewerInventoryCategory* dest_cat = gInventory.getCategory(dest_folder); - gInventory.updateCategory(dest_cat); - gInventory.notifyObservers(); -} - -// Helper function : Count only the copyable items, i.e. skip the stock items (which are no copy) -S32 count_copyable_items(LLInventoryModel::item_array_t& items) -{ - S32 count = 0; - for (LLInventoryModel::item_array_t::const_iterator it = items.begin(); it != items.end(); ++it) - { - LLViewerInventoryItem* item = *it; - if (item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) - { - count++; - } - } - return count; -} - -// Helper function : Count only the non-copyable items, i.e. the stock items, skip the others -S32 count_stock_items(LLInventoryModel::item_array_t& items) -{ - S32 count = 0; - for (LLInventoryModel::item_array_t::const_iterator it = items.begin(); it != items.end(); ++it) - { - LLViewerInventoryItem* item = *it; - if (!item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) - { - count++; - } - } - return count; -} - -// Helper function : Count the number of stock folders -S32 count_stock_folders(LLInventoryModel::cat_array_t& categories) -{ - S32 count = 0; - for (LLInventoryModel::cat_array_t::const_iterator it = categories.begin(); it != categories.end(); ++it) - { - LLInventoryCategory* cat = *it; - if (cat->getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK) - { - count++; - } - } - return count; -} - -// Helper funtion : Count the number of items (not folders) in the descending hierarchy -S32 count_descendants_items(const LLUUID& cat_id) -{ - LLInventoryModel::cat_array_t* cat_array; - LLInventoryModel::item_array_t* item_array; - gInventory.getDirectDescendentsOf(cat_id,cat_array,item_array); - - S32 count = item_array->size(); - - LLInventoryModel::cat_array_t cat_array_copy = *cat_array; - for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) - { - LLViewerInventoryCategory* category = *iter; - count += count_descendants_items(category->getUUID()); - } - - return count; -} - -// Helper function : Returns true if the hierarchy contains nocopy items -bool contains_nocopy_items(const LLUUID& id) -{ - LLInventoryCategory* cat = gInventory.getCategory(id); - - if (cat) - { - // Get the content - LLInventoryModel::cat_array_t* cat_array; - LLInventoryModel::item_array_t* item_array; - gInventory.getDirectDescendentsOf(id,cat_array,item_array); - - // Check all the items: returns true upon encountering a nocopy item - for (LLInventoryModel::item_array_t::iterator iter = item_array->begin(); iter != item_array->end(); iter++) - { - LLInventoryItem* item = *iter; - LLViewerInventoryItem * inv_item = (LLViewerInventoryItem *) item; - if (!inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) - { - return true; - } - } - - // Check all the sub folders recursively - for (LLInventoryModel::cat_array_t::iterator iter = cat_array->begin(); iter != cat_array->end(); iter++) - { - LLViewerInventoryCategory* cat = *iter; - if (contains_nocopy_items(cat->getUUID())) - { - return true; - } - } - } - else - { - LLInventoryItem* item = gInventory.getItem(id); - LLViewerInventoryItem * inv_item = (LLViewerInventoryItem *) item; - if (!inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) - { - return true; - } - } - - // Exit without meeting a nocopy item - return false; -} - -// Generates a string containing the path to the item specified by id. -void append_path(const LLUUID& id, std::string& path) -{ - std::string temp; - const LLInventoryObject* obj = gInventory.getObject(id); - LLUUID parent_id; - if(obj) parent_id = obj->getParentUUID(); - std::string forward_slash("/"); - while(obj) - { - obj = gInventory.getCategory(parent_id); - if(obj) - { - temp.assign(forward_slash + obj->getName() + temp); - parent_id = obj->getParentUUID(); - } - } - path.append(temp); -} - -// Generates a string containing the path name of the object. -std::string make_path(const LLInventoryObject* object) -{ - std::string path; - append_path(object->getUUID(), path); - return path + "/" + object->getName(); -} - -// Generates a string containing the path name of the object specified by id. -std::string make_inventory_path(const LLUUID& id) -{ - if (LLInventoryObject* object = gInventory.getObject(id)) - return make_path(object); - return ""; -} - -// Generates a string containing the path name and id of the object. -std::string make_info(const LLInventoryObject* object) -{ - return "'" + make_path(object) + "' (" + object->getUUID().asString() + ")"; -} - -// Generates a string containing the path name and id of the object specified by id. -std::string make_inventory_info(const LLUUID& id) -{ - if (LLInventoryObject* object = gInventory.getObject(id)) - return make_info(object); - return " (" + id.asString() + ")"; -} - -void update_marketplace_folder_hierarchy(const LLUUID cat_id) -{ - // When changing the marketplace status of a folder, the only thing that needs to happen is - // for all observers of the folder to, possibly, change the display label of the folder - // so that's the only thing we change on the update mask. - gInventory.addChangedMask(LLInventoryObserver::LABEL, cat_id); - - // Update all descendent folders down - LLInventoryModel::cat_array_t* cat_array; - LLInventoryModel::item_array_t* item_array; - gInventory.getDirectDescendentsOf(cat_id,cat_array,item_array); - - LLInventoryModel::cat_array_t cat_array_copy = *cat_array; - for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) - { - LLInventoryCategory* category = *iter; - update_marketplace_folder_hierarchy(category->getUUID()); - } - return; -} - -void update_marketplace_category(const LLUUID& cur_uuid, bool perform_consistency_enforcement, bool skip_clear_listing) -{ - // When changing the marketplace status of an item, we usually have to change the status of all - // folders in the same listing. This is because the display of each folder is affected by the - // overall status of the whole listing. - // Consequently, the only way to correctly update an item anywhere in the marketplace is to - // update the whole listing from its listing root. - // This is not as bad as it seems as we only update folders, not items, and the folder nesting depth - // is limited to 4. - // We also take care of degenerated cases so we don't update all folders in the inventory by mistake. - - if (cur_uuid.isNull() - || gInventory.getCategory(cur_uuid) == NULL - || gInventory.getCategory(cur_uuid)->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) - { - return; - } - - // Grab marketplace listing data for this item - S32 depth = depth_nesting_in_marketplace(cur_uuid); - if (depth > 0) - { - // Retrieve the listing uuid this object is in - LLUUID listing_uuid = nested_parent_id(cur_uuid, depth); - LLViewerInventoryCategory* listing_cat = gInventory.getCategory(listing_uuid); - bool listing_cat_loaded = listing_cat != NULL && listing_cat->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN; - - // Verify marketplace data consistency for this listing - if (perform_consistency_enforcement - && listing_cat_loaded - && LLMarketplaceData::instance().isListed(listing_uuid)) - { - LLUUID version_folder_uuid = LLMarketplaceData::instance().getVersionFolder(listing_uuid); - S32 version_depth = depth_nesting_in_marketplace(version_folder_uuid); - if (version_folder_uuid.notNull() && (!gInventory.isObjectDescendentOf(version_folder_uuid, listing_uuid) || (version_depth != 2))) - { - LL_INFOS("SLM") << "Unlist and clear version folder as the version folder is not at the right place anymore!!" << LL_ENDL; - LLMarketplaceData::instance().setVersionFolder(listing_uuid, LLUUID::null,1); - } - else if (version_folder_uuid.notNull() - && gInventory.isCategoryComplete(version_folder_uuid) - && LLMarketplaceData::instance().getActivationState(version_folder_uuid) - && (count_descendants_items(version_folder_uuid) == 0) - && !LLMarketplaceData::instance().isUpdating(version_folder_uuid,version_depth)) - { - LL_INFOS("SLM") << "Unlist as the version folder is empty of any item!!" << LL_ENDL; - LLNotificationsUtil::add("AlertMerchantVersionFolderEmpty"); - LLMarketplaceData::instance().activateListing(listing_uuid, false,1); - } - } - - // Check if the count on hand needs to be updated on SLM - if (perform_consistency_enforcement - && listing_cat_loaded - && (compute_stock_count(listing_uuid) != LLMarketplaceData::instance().getCountOnHand(listing_uuid))) - { - LLMarketplaceData::instance().updateCountOnHand(listing_uuid,1); - } - // Update all descendents starting from the listing root - update_marketplace_folder_hierarchy(listing_uuid); - } - else if (depth == 0) - { - // If this is the marketplace listings root itself, update all descendents - if (gInventory.getCategory(cur_uuid)) - { - update_marketplace_folder_hierarchy(cur_uuid); - } - } - else - { - // If the folder is outside the marketplace listings root, clear its SLM data if needs be - if (perform_consistency_enforcement && !skip_clear_listing && LLMarketplaceData::instance().isListed(cur_uuid)) - { - LL_INFOS("SLM") << "Disassociate as the listing folder is not under the marketplace folder anymore!!" << LL_ENDL; - LLMarketplaceData::instance().clearListing(cur_uuid); - } - // Update all descendents if this is a category - if (gInventory.getCategory(cur_uuid)) - { - update_marketplace_folder_hierarchy(cur_uuid); - } - } - - return; -} - -// Iterate through the marketplace and flag for label change all categories that countain a stock folder (i.e. stock folders and embedding folders up the hierarchy) -void update_all_marketplace_count(const LLUUID& cat_id) -{ - // Get all descendent folders down - LLInventoryModel::cat_array_t* cat_array; - LLInventoryModel::item_array_t* item_array; - gInventory.getDirectDescendentsOf(cat_id,cat_array,item_array); - - LLInventoryModel::cat_array_t cat_array_copy = *cat_array; - for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) - { - LLInventoryCategory* category = *iter; - if (category->getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK) - { - // Listing containing stock folders needs to be updated but not others - // Note: we take advantage of the fact that stock folder *do not* contain sub folders to avoid a recursive call here - update_marketplace_category(category->getUUID()); - } - else - { - // Explore the contained folders recursively - update_all_marketplace_count(category->getUUID()); - } - } -} - -void update_all_marketplace_count() -{ - // Get the marketplace root and launch the recursive exploration - const LLUUID marketplace_listings_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - if (!marketplace_listings_uuid.isNull()) - { - update_all_marketplace_count(marketplace_listings_uuid); - } - return; -} - -void rename_category(LLInventoryModel* model, const LLUUID& cat_id, const std::string& new_name) -{ - LLViewerInventoryCategory* cat; - - if (!model || - !get_is_category_renameable(model, cat_id) || - (cat = model->getCategory(cat_id)) == NULL || - cat->getName() == new_name) - { - return; - } - - LLSD updates; - updates["name"] = new_name; - update_inventory_category(cat_id, updates, NULL); -} - -void copy_inventory_category(LLInventoryModel* model, - LLViewerInventoryCategory* cat, - const LLUUID& parent_id, - const LLUUID& root_copy_id, - bool move_no_copy_items) -{ - // Create the initial folder - inventory_func_type func = [model, cat, root_copy_id, move_no_copy_items](const LLUUID& new_id) - { - copy_inventory_category_content(new_id, model, cat, root_copy_id, move_no_copy_items); - }; - gInventory.createNewCategory(parent_id, LLFolderType::FT_NONE, cat->getName(), func, cat->getThumbnailUUID()); -} - -void copy_inventory_category(LLInventoryModel* model, - LLViewerInventoryCategory* cat, - const LLUUID& parent_id, - const LLUUID& root_copy_id, - bool move_no_copy_items, - inventory_func_type callback) -{ - // Create the initial folder - inventory_func_type func = [model, cat, root_copy_id, move_no_copy_items, callback](const LLUUID &new_id) - { - copy_inventory_category_content(new_id, model, cat, root_copy_id, move_no_copy_items); - if (callback) - { - callback(new_id); - } - }; - gInventory.createNewCategory(parent_id, LLFolderType::FT_NONE, cat->getName(), func, cat->getThumbnailUUID()); -} - -void copy_cb(const LLUUID& dest_folder, const LLUUID& root_id) -{ - // Decrement the count in root_id since that one item won't be copied over - LLMarketplaceData::instance().decrementValidationWaiting(root_id); - update_folder_cb(dest_folder); -}; - -void copy_inventory_category_content(const LLUUID& new_cat_uuid, LLInventoryModel* model, LLViewerInventoryCategory* cat, const LLUUID& root_copy_id, bool move_no_copy_items) -{ - model->notifyObservers(); - - // We need to exclude the initial root of the copy to avoid recursively copying the copy, etc... - LLUUID root_id = (root_copy_id.isNull() ? new_cat_uuid : root_copy_id); - - // Get the content of the folder - LLInventoryModel::cat_array_t* cat_array; - LLInventoryModel::item_array_t* item_array; - gInventory.getDirectDescendentsOf(cat->getUUID(), cat_array, item_array); - - // If root_copy_id is null, tell the marketplace model we'll be waiting for new items to be copied over for this folder - if (root_copy_id.isNull()) - { - LLMarketplaceData::instance().setValidationWaiting(root_id, count_descendants_items(cat->getUUID())); - } - - LLPointer cb; - if (root_copy_id.isNull()) - { - cb = new LLBoostFuncInventoryCallback(boost::bind(copy_cb, new_cat_uuid, root_id)); - } - else - { - cb = new LLBoostFuncInventoryCallback(boost::bind(update_folder_cb, new_cat_uuid)); - } - - // Copy all the items - LLInventoryModel::item_array_t item_array_copy = *item_array; - for (LLInventoryModel::item_array_t::iterator iter = item_array_copy.begin(); iter != item_array_copy.end(); iter++) - { - LLInventoryItem* item = *iter; - - if (item->getIsLinkType()) - { - link_inventory_object(new_cat_uuid, item->getLinkedUUID(), cb); - } - else if (!item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) - { - // If the item is nocopy, we do nothing or, optionally, move it - if (move_no_copy_items) - { - // Reparent the item - LLViewerInventoryItem * viewer_inv_item = (LLViewerInventoryItem *)item; - gInventory.changeItemParent(viewer_inv_item, new_cat_uuid, true); - } - if (root_copy_id.isNull()) - { - // Decrement the count in root_id since that one item won't be copied over - LLMarketplaceData::instance().decrementValidationWaiting(root_id); - } - } - else - { - copy_inventory_item( - gAgent.getID(), - item->getPermissions().getOwner(), - item->getUUID(), - new_cat_uuid, - std::string(), - cb); - } - } - - // Copy all the folders - LLInventoryModel::cat_array_t cat_array_copy = *cat_array; - for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) - { - LLViewerInventoryCategory* category = *iter; - if (category->getUUID() != root_id) - { - copy_inventory_category(model, category, new_cat_uuid, root_id, move_no_copy_items); - } - } -} - -class LLInventoryCollectAllItems : public LLInventoryCollectFunctor -{ -public: - virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) - { - return true; - } -}; - -bool get_is_parent_to_worn_item(const LLUUID& id) -{ - const LLViewerInventoryCategory* cat = gInventory.getCategory(id); - if (!cat) - { - return false; - } - - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - LLInventoryCollectAllItems collect_all; - gInventory.collectDescendentsIf(LLAppearanceMgr::instance().getCOF(), cats, items, LLInventoryModel::EXCLUDE_TRASH, collect_all); - - for (LLInventoryModel::item_array_t::const_iterator it = items.begin(); it != items.end(); ++it) - { - const LLViewerInventoryItem * const item = *it; - - llassert(item->getIsLinkType()); - - LLUUID linked_id = item->getLinkedUUID(); - const LLViewerInventoryItem * const linked_item = gInventory.getItem(linked_id); - - if (linked_item) - { - LLUUID parent_id = linked_item->getParentUUID(); - - while (!parent_id.isNull()) - { - LLInventoryCategory * parent_cat = gInventory.getCategory(parent_id); - - if (cat == parent_cat) - { - return true; - } - - parent_id = parent_cat->getParentUUID(); - } - } - } - - return false; -} - -bool get_is_item_worn(const LLUUID& id, const LLViewerInventoryItem* item) -{ - if (!item) - return false; - - if (item->getIsLinkType() && !gInventory.getItem(item->getLinkedUUID())) - { - return false; - } - - // Consider the item as worn if it has links in COF. - if (LLAppearanceMgr::instance().isLinkedInCOF(id)) - { - return true; - } - - switch(item->getType()) - { - case LLAssetType::AT_OBJECT: - { - if (isAgentAvatarValid() && gAgentAvatarp->isWearingAttachment(item->getLinkedUUID())) - return true; - break; - } - case LLAssetType::AT_BODYPART: - case LLAssetType::AT_CLOTHING: - if(gAgentWearables.isWearingItem(item->getLinkedUUID())) - return true; - break; - case LLAssetType::AT_GESTURE: - if (LLGestureMgr::instance().isGestureActive(item->getLinkedUUID())) - return true; - break; - default: - break; - } - return false; -} - -bool get_is_item_worn(const LLUUID& id) -{ - const LLViewerInventoryItem* item = gInventory.getItem(id); - return get_is_item_worn(id, item); -} - -bool get_is_item_worn(const LLViewerInventoryItem* item) -{ - if (!item) - { - return false; - } - return get_is_item_worn(item->getUUID(), item); -} - -bool get_can_item_be_worn(const LLUUID& id) -{ - const LLViewerInventoryItem* item = gInventory.getItem(id); - if (!item) - return false; - - if (LLAppearanceMgr::instance().isLinkedInCOF(item->getLinkedUUID())) - { - // an item having links in COF (i.e. a worn item) - return false; - } - - if (gInventory.isObjectDescendentOf(id, LLAppearanceMgr::instance().getCOF())) - { - // a non-link object in COF (should not normally happen) - return false; - } - - const LLUUID trash_id = gInventory.findCategoryUUIDForType( - LLFolderType::FT_TRASH); - - // item can't be worn if base obj in trash, see EXT-7015 - if (gInventory.isObjectDescendentOf(item->getLinkedUUID(), - trash_id)) - { - return false; - } - - switch(item->getType()) - { - case LLAssetType::AT_OBJECT: - { - if (isAgentAvatarValid() && gAgentAvatarp->isWearingAttachment(item->getLinkedUUID())) - { - // Already being worn - return false; - } - else - { - // Not being worn yet. - return true; - } - break; - } - case LLAssetType::AT_BODYPART: - case LLAssetType::AT_CLOTHING: - if(gAgentWearables.isWearingItem(item->getLinkedUUID())) - { - // Already being worn - return false; - } - else - { - // Not being worn yet. - return true; - } - break; - default: - break; - } - return false; -} - -bool get_is_item_removable(const LLInventoryModel* model, const LLUUID& id, bool check_worn) -{ - if (!model) - { - return false; - } - - // Can't delete an item that's in the library. - if (!model->isObjectDescendentOf(id, gInventory.getRootFolderID())) - { - return false; - } - - // Disable delete from COF folder; have users explicitly choose "detach/take off", - // unless the item is not worn but in the COF (i.e. is bugged). - const LLViewerInventoryItem* obj = model->getItem(id); - if (LLAppearanceMgr::instance().getIsProtectedCOFItem(obj)) - { - if (get_is_item_worn(id, obj)) - { - return false; - } - } - - if (obj && obj->getIsLinkType()) - { - return true; - } - if (check_worn && get_is_item_worn(id, obj)) - { - return false; - } - return true; -} - -bool get_is_item_editable(const LLUUID& inv_item_id) -{ - if (const LLInventoryItem* inv_item = gInventory.getLinkedItem(inv_item_id)) - { - switch (inv_item->getType()) - { - case LLAssetType::AT_BODYPART: - case LLAssetType::AT_CLOTHING: - return gAgentWearables.isWearableModifiable(inv_item_id); - case LLAssetType::AT_OBJECT: - return true; - default: - return false;; - } - } - return gAgentAvatarp->getWornAttachment(inv_item_id) != nullptr; -} - -void handle_item_edit(const LLUUID& inv_item_id) -{ - if (get_is_item_editable(inv_item_id)) - { - if (const LLInventoryItem* inv_item = gInventory.getLinkedItem(inv_item_id)) - { - switch (inv_item->getType()) - { - case LLAssetType::AT_BODYPART: - case LLAssetType::AT_CLOTHING: - LLAgentWearables::editWearable(inv_item_id); - break; - case LLAssetType::AT_OBJECT: - handle_attachment_edit(inv_item_id); - break; - default: - break; - } - } - else - { - handle_attachment_edit(inv_item_id); - } - } -} - -bool get_is_category_removable(const LLInventoryModel* model, const LLUUID& id) -{ - // NOTE: This function doesn't check the folder's children. - // See LLFolderBridge::isItemRemovable for a function that does - // consider the children. - - if (!model) - { - return false; - } - - if (!model->isObjectDescendentOf(id, gInventory.getRootFolderID())) - { - return false; - } - - if (!isAgentAvatarValid()) return false; - - const LLInventoryCategory* category = model->getCategory(id); - if (!category) - { - return false; - } - - const LLFolderType::EType folder_type = category->getPreferredType(); - - if (LLFolderType::lookupIsProtectedType(folder_type)) - { - return false; - } - - // Can't delete the outfit that is currently being worn. - if (folder_type == LLFolderType::FT_OUTFIT) - { - const LLViewerInventoryItem *base_outfit_link = LLAppearanceMgr::instance().getBaseOutfitLink(); - if (base_outfit_link && (category == base_outfit_link->getLinkedCategory())) - { - return false; - } - } - - return true; -} - -bool get_is_category_and_children_removable(LLInventoryModel* model, const LLUUID& folder_id, bool check_worn) -{ - if (!get_is_category_removable(model, folder_id)) - { - return false; - } - - const LLUUID mp_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - if (mp_id.notNull() && gInventory.isObjectDescendentOf(folder_id, mp_id)) - { - return false; - } - - LLInventoryModel::cat_array_t cat_array; - LLInventoryModel::item_array_t item_array; - model->collectDescendents( - folder_id, - cat_array, - item_array, - LLInventoryModel::EXCLUDE_TRASH); - - if (check_worn) - { - for (LLInventoryModel::item_array_t::value_type& item : item_array) - { - // Disable delete/cut from COF folder; have users explicitly choose "detach/take off", - // unless the item is not worn but in the COF (i.e. is bugged). - if (item) - { - if (LLAppearanceMgr::instance().getIsProtectedCOFItem(item)) - { - if (get_is_item_worn(item)) - { - return false; - } - } - - if (!item->getIsLinkType() && get_is_item_worn(item)) - { - return false; - } - } - } - } - - const LLViewerInventoryItem* base_outfit_link = LLAppearanceMgr::instance().getBaseOutfitLink(); - LLViewerInventoryCategory* outfit_linked_category = base_outfit_link ? base_outfit_link->getLinkedCategory() : nullptr; - for (LLInventoryModel::cat_array_t::value_type& cat : cat_array) - { - const LLFolderType::EType folder_type = cat->getPreferredType(); - if (LLFolderType::lookupIsProtectedType(folder_type)) - { - return false; - } - - // Can't delete the outfit that is currently being worn. - if (folder_type == LLFolderType::FT_OUTFIT) - { - if (cat == outfit_linked_category) - { - return false; - } - } - } - - return true; -} - -bool get_is_category_renameable(const LLInventoryModel* model, const LLUUID& id) -{ - if (!model) - { - return false; - } - - LLViewerInventoryCategory* cat = model->getCategory(id); - - if (cat && !LLFolderType::lookupIsProtectedType(cat->getPreferredType()) && - cat->getOwnerID() == gAgent.getID()) - { - return true; - } - return false; -} - -void show_task_item_profile(const LLUUID& item_uuid, const LLUUID& object_id) -{ - LLSD params; - params["id"] = item_uuid; - params["object"] = object_id; - - LLFloaterReg::showInstance("item_properties", params); -} - -void show_item_profile(const LLUUID& item_uuid) -{ - LLUUID linked_uuid = gInventory.getLinkedItemID(item_uuid); - LLFloaterReg::showInstance("item_properties", LLSD().with("id", linked_uuid)); -} - -void show_item_original(const LLUUID& item_uuid) -{ - static LLUICachedControl find_original_new_floater("FindOriginalOpenWindow", false); - - //show in a new single-folder window - if(find_original_new_floater) - { - const LLUUID& linked_item_uuid = gInventory.getLinkedItemID(item_uuid); - const LLInventoryObject *obj = gInventory.getObject(linked_item_uuid); - if (obj && obj->getParentUUID().notNull()) - { - LLPanelMainInventory::newFolderWindow(obj->getParentUUID(), linked_item_uuid); - } - } - //show in main Inventory - else - { - LLFloater* floater_inventory = LLFloaterReg::getInstance("inventory"); - if (!floater_inventory) - { - LL_WARNS() << "Could not find My Inventory floater" << LL_ENDL; - return; - } - LLSidepanelInventory *sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); - if (sidepanel_inventory) - { - LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); - if (main_inventory) - { - if(main_inventory->isSingleFolderMode()) - { - main_inventory->toggleViewMode(); - } - main_inventory->resetAllItemsFilters(); - } - reset_inventory_filter(); - - if (!LLFloaterReg::getTypedInstance("inventory")->isInVisibleChain()) - { - LLFloaterReg::toggleInstanceOrBringToFront("inventory"); - } - - const LLUUID inbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX); - if (gInventory.isObjectDescendentOf(gInventory.getLinkedItemID(item_uuid), inbox_id)) - { - if (sidepanel_inventory->getInboxPanel()) - { - sidepanel_inventory->openInbox(); - sidepanel_inventory->getInboxPanel()->setSelection(gInventory.getLinkedItemID(item_uuid), TAKE_FOCUS_YES); - } - } - else - { - sidepanel_inventory->selectAllItemsPanel(); - if (sidepanel_inventory->getActivePanel()) - { - sidepanel_inventory->getActivePanel()->setSelection(gInventory.getLinkedItemID(item_uuid), TAKE_FOCUS_YES); - } - } - } - } -} - - -void reset_inventory_filter() -{ - LLSidepanelInventory *sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); - if (sidepanel_inventory) - { - LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); - if (main_inventory) - { - main_inventory->onFilterEdit(""); - } - } -} - -void open_marketplace_listings() -{ - LLFloaterReg::showInstance("marketplace_listings"); -} - -///---------------------------------------------------------------------------- -// Marketplace functions -// -// Handles Copy and Move to or within the Marketplace listings folder. -// Handles creation of stock folders, nesting of listings and version folders, -// permission checking and listings validation. -///---------------------------------------------------------------------------- - -S32 depth_nesting_in_marketplace(LLUUID cur_uuid) -{ - // Get the marketplace listings root, exit with -1 (i.e. not under the marketplace listings root) if none - // Todo: findCategoryUUIDForType is somewhat expensive with large - // flat root folders yet we use depth_nesting_in_marketplace at - // every turn, find a way to correctly cache this id. - const LLUUID marketplace_listings_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - if (marketplace_listings_uuid.isNull()) - { - return -1; - } - // If not a descendant of the marketplace listings root, then the nesting depth is -1 by definition - if (!gInventory.isObjectDescendentOf(cur_uuid, marketplace_listings_uuid)) - { - return -1; - } - - // Iterate through the parents till we hit the marketplace listings root - // Note that the marketplace listings root itself will return 0 - S32 depth = 0; - LLInventoryObject* cur_object = gInventory.getObject(cur_uuid); - while (cur_uuid != marketplace_listings_uuid) - { - depth++; - cur_uuid = cur_object->getParentUUID(); - cur_object = gInventory.getCategory(cur_uuid); - } - return depth; -} - -// Returns the UUID of the marketplace listing this object is in -LLUUID nested_parent_id(LLUUID cur_uuid, S32 depth) -{ - if (depth < 1) - { - // For objects outside the marketplace listings root (or root itself), we return a NULL UUID - return LLUUID::null; - } - else if (depth == 1) - { - // Just under the root, we return the passed UUID itself if it's a folder, NULL otherwise (not a listing) - LLViewerInventoryCategory* cat = gInventory.getCategory(cur_uuid); - return (cat ? cur_uuid : LLUUID::null); - } - - // depth > 1 - LLInventoryObject* cur_object = gInventory.getObject(cur_uuid); - while (depth > 1) - { - depth--; - cur_uuid = cur_object->getParentUUID(); - cur_object = gInventory.getCategory(cur_uuid); - } - return cur_uuid; -} - -S32 compute_stock_count(LLUUID cat_uuid, bool force_count /* false */) -{ - // Handle the case of the folder being a stock folder immediately - LLViewerInventoryCategory* cat = gInventory.getCategory(cat_uuid); - if (!cat) - { - // Not a category so no stock count to speak of - return COMPUTE_STOCK_INFINITE; - } - if (cat->getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK) - { - if (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) - { - // If the folder is not completely fetched, we do not want to return any confusing value that could lead to unlisting - // "COMPUTE_STOCK_NOT_EVALUATED" denotes that a stock folder has a count that cannot be evaluated at this time (folder not up to date) - return COMPUTE_STOCK_NOT_EVALUATED; - } - // Note: stock folders are *not* supposed to have nested subfolders so we stop recursion here but we count only items (subfolders will be ignored) - // Note: we *always* give a stock count for stock folders, it's useful even if the listing is unassociated - LLInventoryModel::cat_array_t* cat_array; - LLInventoryModel::item_array_t* item_array; - gInventory.getDirectDescendentsOf(cat_uuid,cat_array,item_array); - return item_array->size(); - } - - // When force_count is true, we do not do any verification of the marketplace status and simply compute - // the stock amount based on the descendent hierarchy. This is used specifically when creating a listing. - if (!force_count) - { - // Grab marketplace data for this folder - S32 depth = depth_nesting_in_marketplace(cat_uuid); - LLUUID listing_uuid = nested_parent_id(cat_uuid, depth); - if (!LLMarketplaceData::instance().isListed(listing_uuid)) - { - // If not listed, the notion of stock is meaningless so it won't be computed for any level - return COMPUTE_STOCK_INFINITE; - } - - LLUUID version_folder_uuid = LLMarketplaceData::instance().getVersionFolder(listing_uuid); - // Handle the case of the first 2 levels : listing and version folders - if (depth == 1) - { - if (version_folder_uuid.notNull()) - { - // If there is a version folder, the stock value for the listing is the version folder stock - return compute_stock_count(version_folder_uuid, true); - } - else - { - // If there's no version folder associated, the notion of stock count has no meaning - return COMPUTE_STOCK_INFINITE; - } - } - else if (depth == 2) - { - if (version_folder_uuid.notNull() && (version_folder_uuid != cat_uuid)) - { - // If there is a version folder but we're not it, our stock count is meaningless - return COMPUTE_STOCK_INFINITE; - } - } - } - - // In all other cases, the stock count is the min of stock folders count found in the descendents - // "COMPUTE_STOCK_NOT_EVALUATED" denotes that a stock folder in the hierarchy has a count that cannot be evaluated at this time (folder not up to date) - LLInventoryModel::cat_array_t* cat_array; - LLInventoryModel::item_array_t* item_array; - gInventory.getDirectDescendentsOf(cat_uuid,cat_array,item_array); - - // "COMPUTE_STOCK_INFINITE" denotes a folder that doesn't countain any stock folders in its descendents - S32 curr_count = COMPUTE_STOCK_INFINITE; - - // Note: marketplace listings have a maximum depth nesting of 4 - LLInventoryModel::cat_array_t cat_array_copy = *cat_array; - for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) - { - LLInventoryCategory* category = *iter; - S32 count = compute_stock_count(category->getUUID(), true); - if ((curr_count == COMPUTE_STOCK_INFINITE) || ((count != COMPUTE_STOCK_INFINITE) && (count < curr_count))) - { - curr_count = count; - } - } - - return curr_count; -} - -// local helper -bool can_move_to_marketplace(LLInventoryItem* inv_item, std::string& tooltip_msg, bool resolve_links) -{ - // Collapse links directly to items/folders - LLViewerInventoryItem * viewer_inv_item = (LLViewerInventoryItem *) inv_item; - LLViewerInventoryItem * linked_item = viewer_inv_item->getLinkedItem(); - LLViewerInventoryCategory * linked_category = viewer_inv_item->getLinkedCategory(); - - // Linked items and folders cannot be put for sale - if (linked_category || linked_item) - { - tooltip_msg = LLTrans::getString("TooltipOutboxLinked"); - return false; - } - - // A category is always considered as passing... - if (linked_category != NULL) - { - return true; - } - - // Take the linked item if necessary - if (linked_item != NULL) - { - inv_item = linked_item; - } - - // Check that the agent has transfer permission on the item: this is required as a resident cannot - // put on sale items she cannot transfer. Proceed with move if we have permission. - bool allow_transfer = inv_item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID()); - if (!allow_transfer) - { - tooltip_msg = LLTrans::getString("TooltipOutboxNoTransfer"); - return false; - } - - // Check worn/not worn status: worn items cannot be put on the marketplace - bool worn = get_is_item_worn(inv_item->getUUID()); - if (worn) - { - tooltip_msg = LLTrans::getString("TooltipOutboxWorn"); - return false; - } - - // Check library status: library items cannot be put on the marketplace - if (!gInventory.isObjectDescendentOf(inv_item->getUUID(), gInventory.getRootFolderID())) - { - tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); - return false; - } - - // Check type: for the moment, calling cards cannot be put on the marketplace - bool calling_card = (LLAssetType::AT_CALLINGCARD == inv_item->getType()); - if (calling_card) - { - tooltip_msg = LLTrans::getString("TooltipOutboxCallingCard"); - return false; - } - - return true; -} - -// local helper -// Returns the max tree length (in folder nodes) down from the argument folder -int get_folder_levels(LLInventoryCategory* inv_cat) -{ - LLInventoryModel::cat_array_t* cats; - LLInventoryModel::item_array_t* items; - gInventory.getDirectDescendentsOf(inv_cat->getUUID(), cats, items); - - int max_child_levels = 0; - - for (S32 i=0; i < cats->size(); ++i) - { - LLInventoryCategory* category = cats->at(i); - max_child_levels = llmax(max_child_levels, get_folder_levels(category)); - } - - return 1 + max_child_levels; -} - -// local helper -// Returns the distance (in folder nodes) between the ancestor and its descendant. Returns -1 if not related. -int get_folder_path_length(const LLUUID& ancestor_id, const LLUUID& descendant_id) -{ - int depth = 0; - - if (ancestor_id == descendant_id) return depth; - - const LLInventoryCategory* category = gInventory.getCategory(descendant_id); - - while (category) - { - LLUUID parent_id = category->getParentUUID(); - - if (parent_id.isNull()) break; - - depth++; - - if (parent_id == ancestor_id) return depth; - - category = gInventory.getCategory(parent_id); - } - - LL_WARNS("SLM") << "get_folder_path_length() couldn't trace a path from the descendant to the ancestor" << LL_ENDL; - return -1; -} - -// local helper -// Returns true if all items within the argument folder are fit for sale, false otherwise -bool has_correct_permissions_for_sale(LLInventoryCategory* cat, std::string& error_msg) -{ - LLInventoryModel::cat_array_t* cat_array; - LLInventoryModel::item_array_t* item_array; - gInventory.getDirectDescendentsOf(cat->getUUID(),cat_array,item_array); - - LLInventoryModel::item_array_t item_array_copy = *item_array; - - for (LLInventoryModel::item_array_t::iterator iter = item_array_copy.begin(); iter != item_array_copy.end(); iter++) - { - LLInventoryItem* item = *iter; - if (!can_move_to_marketplace(item, error_msg, false)) - { - return false; - } - } - - LLInventoryModel::cat_array_t cat_array_copy = *cat_array; - - for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) - { - LLInventoryCategory* category = *iter; - if (!has_correct_permissions_for_sale(category, error_msg)) - { - return false; - } - } - return true; -} - -// Returns true if inv_item can be dropped in dest_folder, a folder nested in marketplace listings (or merchant inventory) under the root_folder root -// If returns is false, tooltip_msg contains an error message to display to the user (localized and all). -// bundle_size is the amount of sibling items that are getting moved to the marketplace at the same time. -bool can_move_item_to_marketplace(const LLInventoryCategory* root_folder, LLInventoryCategory* dest_folder, LLInventoryItem* inv_item, std::string& tooltip_msg, S32 bundle_size, bool from_paste) -{ - // Check stock folder type matches item type in marketplace listings or merchant outbox (even if of no use there for the moment) - LLViewerInventoryCategory* view_folder = dynamic_cast(dest_folder); - bool move_in_stock = (view_folder && (view_folder->getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK)); - bool accept = (view_folder && view_folder->acceptItem(inv_item)); - if (!accept) - { - tooltip_msg = LLTrans::getString("TooltipOutboxMixedStock"); - } - - // Check that the item has the right type and permissions to be sold on the marketplace - if (accept) - { - accept = can_move_to_marketplace(inv_item, tooltip_msg, true); - } - - // Check that the total amount of items won't violate the max limit on the marketplace - if (accept) - { - // If the dest folder is a stock folder, we do not count the incoming items toward the total (stock items are seen as one) - int existing_item_count = (move_in_stock ? 0 : bundle_size); - - // If the dest folder is a stock folder, we do assume that the incoming items are also stock items (they should anyway) - int existing_stock_count = (move_in_stock ? bundle_size : 0); - - int existing_folder_count = 0; - - // Get the version folder: that's where the counts start from - const LLViewerInventoryCategory * version_folder = ((root_folder && (root_folder != dest_folder)) ? gInventory.getFirstDescendantOf(root_folder->getUUID(), dest_folder->getUUID()) : NULL); - - if (version_folder) - { - if (!from_paste && gInventory.isObjectDescendentOf(inv_item->getUUID(), version_folder->getUUID())) - { - // Clear those counts or they will be counted twice because we're already inside the version category - existing_item_count = 0; - } - - LLInventoryModel::cat_array_t existing_categories; - LLInventoryModel::item_array_t existing_items; - - gInventory.collectDescendents(version_folder->getUUID(), existing_categories, existing_items, false); - - existing_item_count += count_copyable_items(existing_items) + count_stock_folders(existing_categories); - existing_stock_count += count_stock_items(existing_items); - existing_folder_count += existing_categories.size(); - - // If the incoming item is a nocopy (stock) item, we need to consider that it will create a stock folder - if (!inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID()) && !move_in_stock) - { - // Note : we do not assume that all incoming items are nocopy of different kinds... - existing_folder_count += 1; - } - } - - if (existing_item_count > gSavedSettings.getU32("InventoryOutboxMaxItemCount")) - { - LLStringUtil::format_map_t args; - U32 amount = gSavedSettings.getU32("InventoryOutboxMaxItemCount"); - args["[AMOUNT]"] = llformat("%d",amount); - tooltip_msg = LLTrans::getString("TooltipOutboxTooManyObjects", args); - accept = false; - } - else if (existing_stock_count > gSavedSettings.getU32("InventoryOutboxMaxStockItemCount")) - { - LLStringUtil::format_map_t args; - U32 amount = gSavedSettings.getU32("InventoryOutboxMaxStockItemCount"); - args["[AMOUNT]"] = llformat("%d",amount); - tooltip_msg = LLTrans::getString("TooltipOutboxTooManyStockItems", args); - accept = false; - } - else if (existing_folder_count > gSavedSettings.getU32("InventoryOutboxMaxFolderCount")) - { - LLStringUtil::format_map_t args; - U32 amount = gSavedSettings.getU32("InventoryOutboxMaxFolderCount"); - args["[AMOUNT]"] = llformat("%d",amount); - tooltip_msg = LLTrans::getString("TooltipOutboxTooManyFolders", args); - accept = false; - } - } - - return accept; -} - -// Returns true if inv_cat can be dropped in dest_folder, a folder nested in marketplace listings (or merchant inventory) under the root_folder root -// If returns is false, tooltip_msg contains an error message to display to the user (localized and all). -// bundle_size is the amount of sibling items that are getting moved to the marketplace at the same time. -bool can_move_folder_to_marketplace(const LLInventoryCategory* root_folder, LLInventoryCategory* dest_folder, LLInventoryCategory* inv_cat, std::string& tooltip_msg, S32 bundle_size, bool check_items, bool from_paste) -{ - bool accept = true; - - // Compute the nested folders level we'll add into with that incoming folder - int incoming_folder_depth = get_folder_levels(inv_cat); - // Compute the nested folders level we're inserting ourselves in - // Note: add 1 when inserting under a listing folder as we need to take the root listing folder in the count - int insertion_point_folder_depth = (root_folder ? get_folder_path_length(root_folder->getUUID(), dest_folder->getUUID()) + 1 : 1); - - // Get the version folder: that's where the folders and items counts start from - const LLViewerInventoryCategory * version_folder = (insertion_point_folder_depth >= 2 ? gInventory.getFirstDescendantOf(root_folder->getUUID(), dest_folder->getUUID()) : NULL); - - // Compare the whole with the nested folders depth limit - // Note: substract 2 as we leave root and version folder out of the count threshold - if ((incoming_folder_depth + insertion_point_folder_depth - 2) > (S32)(gSavedSettings.getU32("InventoryOutboxMaxFolderDepth"))) - { - LLStringUtil::format_map_t args; - U32 amount = gSavedSettings.getU32("InventoryOutboxMaxFolderDepth"); - args["[AMOUNT]"] = llformat("%d",amount); - tooltip_msg = LLTrans::getString("TooltipOutboxFolderLevels", args); - accept = false; - } - - if (accept) - { - LLInventoryModel::cat_array_t descendent_categories; - LLInventoryModel::item_array_t descendent_items; - gInventory.collectDescendents(inv_cat->getUUID(), descendent_categories, descendent_items, false); - - int dragged_folder_count = descendent_categories.size() + bundle_size; // Note: We assume that we're moving a bunch of folders in. That might be wrong... - int dragged_item_count = count_copyable_items(descendent_items) + count_stock_folders(descendent_categories); - int dragged_stock_count = count_stock_items(descendent_items); - int existing_item_count = 0; - int existing_stock_count = 0; - int existing_folder_count = 0; - - if (version_folder) - { - if (!from_paste && gInventory.isObjectDescendentOf(inv_cat->getUUID(), version_folder->getUUID())) - { - // Clear those counts or they will be counted twice because we're already inside the version category - dragged_folder_count = 0; - dragged_item_count = 0; - dragged_stock_count = 0; - } - - // Tally the total number of categories and items inside the root folder - LLInventoryModel::cat_array_t existing_categories; - LLInventoryModel::item_array_t existing_items; - gInventory.collectDescendents(version_folder->getUUID(), existing_categories, existing_items, false); - - existing_folder_count += existing_categories.size(); - existing_item_count += count_copyable_items(existing_items) + count_stock_folders(existing_categories); - existing_stock_count += count_stock_items(existing_items); - } - - const int total_folder_count = existing_folder_count + dragged_folder_count; - const int total_item_count = existing_item_count + dragged_item_count; - const int total_stock_count = existing_stock_count + dragged_stock_count; - - if (total_folder_count > gSavedSettings.getU32("InventoryOutboxMaxFolderCount")) - { - LLStringUtil::format_map_t args; - U32 amount = gSavedSettings.getU32("InventoryOutboxMaxFolderCount"); - args["[AMOUNT]"] = llformat("%d",amount); - tooltip_msg = LLTrans::getString("TooltipOutboxTooManyFolders", args); - accept = false; - } - else if (total_item_count > gSavedSettings.getU32("InventoryOutboxMaxItemCount")) - { - LLStringUtil::format_map_t args; - U32 amount = gSavedSettings.getU32("InventoryOutboxMaxItemCount"); - args["[AMOUNT]"] = llformat("%d",amount); - tooltip_msg = LLTrans::getString("TooltipOutboxTooManyObjects", args); - accept = false; - } - else if (total_stock_count > gSavedSettings.getU32("InventoryOutboxMaxStockItemCount")) - { - LLStringUtil::format_map_t args; - U32 amount = gSavedSettings.getU32("InventoryOutboxMaxStockItemCount"); - args["[AMOUNT]"] = llformat("%d",amount); - tooltip_msg = LLTrans::getString("TooltipOutboxTooManyStockItems", args); - accept = false; - } - - // Now check that each item in the folder can be moved in the marketplace - if (accept && check_items) - { - for (S32 i=0; i < descendent_items.size(); ++i) - { - LLInventoryItem* item = descendent_items[i]; - if (!can_move_to_marketplace(item, tooltip_msg, false)) - { - accept = false; - break; - } - } - } - } - - return accept; -} - -// Can happen asynhroneously!!! -bool move_item_to_marketplacelistings(LLInventoryItem* inv_item, LLUUID dest_folder, bool copy) -{ - // Get the marketplace listings depth of the destination folder, exit with error if not under marketplace - S32 depth = depth_nesting_in_marketplace(dest_folder); - if (depth < 0) - { - LLSD subs; - subs["[ERROR_CODE]"] = LLTrans::getString("Marketplace Error Prefix") + LLTrans::getString("Marketplace Error Not Merchant"); - LLNotificationsUtil::add("MerchantPasteFailed", subs); - return false; - } - - // We will collapse links into items/folders - LLViewerInventoryItem * viewer_inv_item = (LLViewerInventoryItem *) inv_item; - LLViewerInventoryCategory * linked_category = viewer_inv_item->getLinkedCategory(); - - if (linked_category != NULL) - { - // Move the linked folder directly - return move_folder_to_marketplacelistings(linked_category, dest_folder, copy); - } - else - { - // Grab the linked item if any - LLViewerInventoryItem * linked_item = viewer_inv_item->getLinkedItem(); - viewer_inv_item = (linked_item != NULL ? linked_item : viewer_inv_item); - - // If we want to copy but the item is no copy, fail silently (this is a common case that doesn't warrant notification) - if (copy && !viewer_inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) - { - return false; - } - - // Check that the agent has transfer permission on the item: this is required as a resident cannot - // put on sale items she cannot transfer. Proceed with move if we have permission. - std::string error_msg; - if (can_move_to_marketplace(inv_item, error_msg, true)) - { - // When moving an isolated item, we might need to create the folder structure to support it - - LLUUID item_id = inv_item->getUUID(); - std::function callback_create_stock = [copy, item_id](const LLUUID& new_cat_id) - { - if (new_cat_id.isNull()) - { - LL_WARNS() << "Failed to create category" << LL_ENDL; - LLSD subs; - subs["[ERROR_CODE]"] = - LLTrans::getString("Marketplace Error Prefix") + LLTrans::getString("Marketplace Error Not Accepted"); - LLNotificationsUtil::add("MerchantPasteFailed", subs); - return; - } - - // Verify we can have this item in that destination category - LLViewerInventoryCategory* dest_cat = gInventory.getCategory(new_cat_id); - LLViewerInventoryItem * viewer_inv_item = gInventory.getItem(item_id); - if (!dest_cat || !viewer_inv_item) - { - LL_WARNS() << "Move to marketplace: item or folder do not exist" << LL_ENDL; - - LLSD subs; - subs["[ERROR_CODE]"] = - LLTrans::getString("Marketplace Error Prefix") + LLTrans::getString("Marketplace Error Not Accepted"); - LLNotificationsUtil::add("MerchantPasteFailed", subs); - return; - } - if (!dest_cat->acceptItem(viewer_inv_item)) - { - LLSD subs; - subs["[ERROR_CODE]"] = LLTrans::getString("Marketplace Error Prefix") + LLTrans::getString("Marketplace Error Not Accepted"); - LLNotificationsUtil::add("MerchantPasteFailed", subs); - } - - if (copy) - { - // Copy the item - LLPointer cb = new LLBoostFuncInventoryCallback(boost::bind(update_folder_cb, new_cat_id)); - copy_inventory_item( - gAgent.getID(), - viewer_inv_item->getPermissions().getOwner(), - viewer_inv_item->getUUID(), - new_cat_id, - std::string(), - cb); - } - else - { - // Reparent the item - gInventory.changeItemParent(viewer_inv_item, new_cat_id, true); - } - }; - - std::function callback_dest_create = [item_id, callback_create_stock](const LLUUID& new_cat_id) - { - if (new_cat_id.isNull()) - { - LL_WARNS() << "Failed to create category" << LL_ENDL; - LLSD subs; - subs["[ERROR_CODE]"] = - LLTrans::getString("Marketplace Error Prefix") + LLTrans::getString("Marketplace Error Not Accepted"); - LLNotificationsUtil::add("MerchantPasteFailed", subs); - return; - } - - LLViewerInventoryCategory* dest_cat = gInventory.getCategory(new_cat_id); - LLViewerInventoryItem * viewer_inv_item = gInventory.getItem(item_id); - if (!viewer_inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID()) && - (dest_cat->getPreferredType() != LLFolderType::FT_MARKETPLACE_STOCK)) - { - // We need to create a stock folder to move a no copy item - gInventory.createNewCategory(new_cat_id, LLFolderType::FT_MARKETPLACE_STOCK, viewer_inv_item->getName(), callback_create_stock); - } - else - { - callback_create_stock(new_cat_id); - } - }; - - if (depth == 0) - { - // We need a listing folder - gInventory.createNewCategory(dest_folder, - LLFolderType::FT_NONE, - viewer_inv_item->getName(), - [callback_dest_create](const LLUUID &new_cat_id) - { - if (new_cat_id.isNull()) - { - LL_WARNS() << "Failed to create listing folder for marketpace" << LL_ENDL; - return; - } - LLViewerInventoryCategory *dest_cat = gInventory.getCategory(new_cat_id); - if (!dest_cat) - { - LL_WARNS() << "Failed to find freshly created listing folder" << LL_ENDL; - return; - } - // version folder - gInventory.createNewCategory(new_cat_id, - LLFolderType::FT_NONE, - dest_cat->getName(), - callback_dest_create); - }); - } - else if (depth == 1) - { - // We need a version folder - gInventory.createNewCategory(dest_folder, LLFolderType::FT_NONE, viewer_inv_item->getName(), callback_dest_create); - } - else - { - callback_dest_create(dest_folder); - } - } - else - { - LLSD subs; - subs["[ERROR_CODE]"] = LLTrans::getString("Marketplace Error Prefix") + error_msg; - LLNotificationsUtil::add("MerchantPasteFailed", subs); - return false; - } - } - - open_marketplace_listings(); - return true; -} - -bool move_folder_to_marketplacelistings(LLInventoryCategory* inv_cat, const LLUUID& dest_folder, bool copy, bool move_no_copy_items) -{ - // Check that we have adequate permission on all items being moved. Proceed if we do. - std::string error_msg; - if (has_correct_permissions_for_sale(inv_cat, error_msg)) - { - // Get the destination folder - LLViewerInventoryCategory* dest_cat = gInventory.getCategory(dest_folder); - - // Check it's not a stock folder - if (dest_cat->getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK) - { - LLSD subs; - subs["[ERROR_CODE]"] = LLTrans::getString("Marketplace Error Prefix") + LLTrans::getString("Marketplace Error Not Accepted"); - LLNotificationsUtil::add("MerchantPasteFailed", subs); - return false; - } - - // Get the parent folder of the moved item : we may have to update it - LLUUID src_folder = inv_cat->getParentUUID(); - - LLViewerInventoryCategory * viewer_inv_cat = (LLViewerInventoryCategory *) inv_cat; - if (copy) - { - // Copy the folder - copy_inventory_category(&gInventory, viewer_inv_cat, dest_folder, LLUUID::null, move_no_copy_items); - } - else - { - LL_INFOS("SLM") << "Move category " << make_info(viewer_inv_cat) << " to '" << make_inventory_path(dest_folder) << "'" << LL_ENDL; - // Reparent the folder - gInventory.changeCategoryParent(viewer_inv_cat, dest_folder, false); - // Check the destination folder recursively for no copy items and promote the including folders if any - LLMarketplaceValidator::getInstance()->validateMarketplaceListings(dest_folder); - } - - // Update the modified folders - update_marketplace_category(src_folder); - update_marketplace_category(dest_folder); - gInventory.notifyObservers(); - } - else - { - LLSD subs; - subs["[ERROR_CODE]"] = LLTrans::getString("Marketplace Error Prefix") + error_msg; - LLNotificationsUtil::add("MerchantPasteFailed", subs); - return false; - } - - open_marketplace_listings(); - return true; -} - -bool sort_alpha(const LLViewerInventoryCategory* cat1, const LLViewerInventoryCategory* cat2) -{ - return cat1->getName().compare(cat2->getName()) < 0; -} - -// Make all relevant business logic checks on the marketplace listings starting with the folder as argument. -// This function does no deletion of listings but a mere audit and raises issues to the user (through the -// optional callback cb). -// The only inventory changes that are done is to move and sort folders containing no-copy items to stock folders. -// @pending_callbacks - how many callbacks we are waiting for, must be inited before use -// @result - true if things validate, false if issues are raised, must be inited before use -typedef boost::function validation_result_callback_t; -void validate_marketplacelistings( - LLInventoryCategory* cat, - validation_result_callback_t cb_result, - LLMarketplaceValidator::validation_msg_callback_t cb_msg, - bool fix_hierarchy, - S32 depth, - bool notify_observers, - S32 &pending_callbacks, - bool &result) -{ - // Get the type and the depth of the folder - LLViewerInventoryCategory * viewer_cat = (LLViewerInventoryCategory *) (cat); - const LLFolderType::EType folder_type = cat->getPreferredType(); - if (depth < 0) - { - // If the depth argument was not provided, evaluate the depth directly - depth = depth_nesting_in_marketplace(cat->getUUID()); - } - if (depth < 0) - { - // If the folder is not under the marketplace listings root, we run validation as if it was a listing folder and prevent any hierarchy fix - // This allows the function to be used to pre-validate a folder anywhere in the inventory - depth = 1; - fix_hierarchy = false; - } - - // Set the indentation for print output (typically, audit button in marketplace folder floater) - std::string indent; - for (int i = 1; i < depth; i++) - { - indent += " "; - } - - // Check out that version folders are marketplace ready - if (depth == 2) - { - std::string message; - // Note: if we fix the hierarchy, we want to check the items individually, hence the last argument here - if (!can_move_folder_to_marketplace(cat, cat, cat, message, 0, fix_hierarchy)) - { - result = false; - if (cb_msg) - { - message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Error") + " " + message; - cb_msg(message,depth,LLError::LEVEL_ERROR); - } - } - } - - // Check out that stock folders are at the right level - if ((folder_type == LLFolderType::FT_MARKETPLACE_STOCK) && (depth <= 2)) - { - if (fix_hierarchy) - { - if (cb_msg) - { - std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Warning") + " " + LLTrans::getString("Marketplace Validation Warning Stock"); - cb_msg(message,depth,LLError::LEVEL_WARN); - } - - // Nest the stock folder one level deeper in a normal folder and restart from there - pending_callbacks++; - LLUUID parent_uuid = cat->getParentUUID(); - LLUUID cat_uuid = cat->getUUID(); - gInventory.createNewCategory(parent_uuid, - LLFolderType::FT_NONE, - cat->getName(), - [cat_uuid, cb_result, cb_msg, fix_hierarchy, depth](const LLUUID &new_cat_id) - { - if (new_cat_id.isNull()) - { - cb_result(0, false); - return; - } - LLInventoryCategory * move_cat = gInventory.getCategory(cat_uuid); - LLViewerInventoryCategory * viewer_cat = (LLViewerInventoryCategory *)(move_cat); - LLInventoryCategory * new_cat = gInventory.getCategory(new_cat_id); - gInventory.changeCategoryParent(viewer_cat, new_cat_id, false); - S32 pending = 0; - bool result = true; - validate_marketplacelistings(new_cat, cb_result, cb_msg, fix_hierarchy, depth + 1, true, pending, result); - cb_result(pending, result); - } - ); - result = false; - return; - } - else - { - result = false; - if (cb_msg) - { - std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Error") + " " + LLTrans::getString("Marketplace Validation Warning Stock"); - cb_msg(message,depth,LLError::LEVEL_ERROR); - } - } - } - - // Item sorting and validation : sorting and moving the various stock items is complicated as the set of constraints is high - // We need to: - // * separate non stock items, stock items per types in different folders - // * have stock items nested at depth 2 at least - // * never ever move the non-stock items - - LLInventoryModel::cat_array_t* cat_array; - LLInventoryModel::item_array_t* item_array; - gInventory.getDirectDescendentsOf(cat->getUUID(),cat_array,item_array); - - // We use a composite (type,permission) key on that map to store UUIDs of items of same (type,permissions) - std::map > items_vector; - - // Parse the items and create vectors of item UUIDs sorting copyable items and stock items of various types - bool has_bad_items = false; - LLInventoryModel::item_array_t item_array_copy = *item_array; - for (LLInventoryModel::item_array_t::iterator iter = item_array_copy.begin(); iter != item_array_copy.end(); iter++) - { - LLInventoryItem* item = *iter; - LLViewerInventoryItem * viewer_inv_item = (LLViewerInventoryItem *) item; - - // Test but skip items that shouldn't be there to start with, raise an error message for those - std::string error_msg; - if (!can_move_to_marketplace(item, error_msg, false)) - { - has_bad_items = true; - if (cb_msg && fix_hierarchy) - { - std::string message = indent + viewer_inv_item->getName() + LLTrans::getString("Marketplace Validation Error") + " " + error_msg; - cb_msg(message,depth,LLError::LEVEL_ERROR); - } - continue; - } - // Update the appropriate vector item for that type - LLInventoryType::EType type = LLInventoryType::IT_COUNT; // Default value for non stock items - U32 perms = 0; - if (!viewer_inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) - { - // Get the item type for stock items - type = viewer_inv_item->getInventoryType(); - perms = viewer_inv_item->getPermissions().getMaskNextOwner(); - } - U32 key = (((U32)(type) & 0xFF) << 24) | (perms & 0xFFFFFF); - items_vector[key].push_back(viewer_inv_item->getUUID()); - } - - // How many types of items? Which type is it if only one? - S32 count = items_vector.size(); - U32 default_key = (U32)(LLInventoryType::IT_COUNT) << 24; // This is the key for any normal copyable item - U32 unique_key = (count == 1 ? items_vector.begin()->first : default_key); // The key in the case of one item type only - - // If we have no items in there (only folders or empty), analyze a bit further - if ((count == 0) && !has_bad_items) - { - if (cat_array->size() == 0) - { - // So we have no item and no folder. That's at least a warning. - if (depth == 2) - { - // If this is an empty version folder, warn only (listing won't be delivered by AIS, but only AIS should unlist) - if (cb_msg) - { - std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Error Empty Version"); - cb_msg(message,depth,LLError::LEVEL_WARN); - } - } - else if ((folder_type == LLFolderType::FT_MARKETPLACE_STOCK) && (depth > 2)) - { - // If this is a legit but empty stock folder, warn only (listing must stay searchable when out of stock) - if (cb_msg) - { - std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Error Empty Stock"); - cb_msg(message,depth,LLError::LEVEL_WARN); - } - } - else if (cb_msg) - { - // We warn if there's nothing in a regular folder (may be it's an under construction listing) - std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Warning Empty"); - cb_msg(message,depth,LLError::LEVEL_WARN); - } - } - else - { - // Done with that folder : Print out the folder name unless we already found an error here - if (cb_msg && result && (depth >= 1)) - { - std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Log"); - cb_msg(message,depth,LLError::LEVEL_INFO); - } - } - } - // If we have a single type of items of the right type in the right place, we're done - else if ((count == 1) && !has_bad_items && (((unique_key == default_key) && (depth > 1)) || ((folder_type == LLFolderType::FT_MARKETPLACE_STOCK) && (depth > 2) && (cat_array->size() == 0)))) - { - // Done with that folder : Print out the folder name unless we already found an error here - if (cb_msg && result && (depth >= 1)) - { - std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Log"); - cb_msg(message,depth,LLError::LEVEL_INFO); - } - } - else - { - if (fix_hierarchy && !has_bad_items) - { - // Alert the user when an existing stock folder has to be split - if ((folder_type == LLFolderType::FT_MARKETPLACE_STOCK) && ((count >= 2) || (cat_array->size() > 0))) - { - LLNotificationsUtil::add("AlertMerchantStockFolderSplit"); - } - // If we have more than 1 type of items or we are at the listing level or we have stock/no stock type mismatch, wrap the items in subfolders - if ((count > 1) || (depth == 1) || - ((folder_type == LLFolderType::FT_MARKETPLACE_STOCK) && (unique_key == default_key)) || - ((folder_type != LLFolderType::FT_MARKETPLACE_STOCK) && (unique_key != default_key))) - { - // Create one folder per vector at the right depth and of the right type - std::map >::iterator items_vector_it = items_vector.begin(); - while (items_vector_it != items_vector.end()) - { - // Create a new folder - const LLUUID parent_uuid = (depth > 2 ? viewer_cat->getParentUUID() : viewer_cat->getUUID()); - const LLUUID origin_uuid = viewer_cat->getUUID(); - LLViewerInventoryItem* viewer_inv_item = gInventory.getItem(items_vector_it->second.back()); - std::string folder_name = (depth >= 1 ? viewer_cat->getName() : viewer_inv_item->getName()); - LLFolderType::EType new_folder_type = (items_vector_it->first == default_key ? LLFolderType::FT_NONE : LLFolderType::FT_MARKETPLACE_STOCK); - if (cb_msg) - { - std::string message = ""; - if (new_folder_type == LLFolderType::FT_MARKETPLACE_STOCK) - { - message = indent + folder_name + LLTrans::getString("Marketplace Validation Warning Create Stock"); - } - else - { - message = indent + folder_name + LLTrans::getString("Marketplace Validation Warning Create Version"); - } - cb_msg(message,depth,LLError::LEVEL_WARN); - } - - pending_callbacks++; - std::vector uuid_vector = items_vector_it->second; // needs to be a copy for lambda - gInventory.createNewCategory( - parent_uuid, - new_folder_type, - folder_name, - [uuid_vector, cb_result, cb_msg, depth, parent_uuid, origin_uuid, notify_observers](const LLUUID &new_category_id) - { - // Move each item to the new folder - std::vector::const_reverse_iterator iter = uuid_vector.rbegin(); - while (iter != uuid_vector.rend()) - { - LLViewerInventoryItem* viewer_inv_item = gInventory.getItem(*iter); - if (cb_msg) - { - std::string indent; - for (int i = 1; i < depth; i++) - { - indent += " "; - } - std::string message = indent + viewer_inv_item->getName() + LLTrans::getString("Marketplace Validation Warning Move"); - cb_msg(message, depth, LLError::LEVEL_WARN); - } - gInventory.changeItemParent(viewer_inv_item, new_category_id, true); - iter++; - } - - if (origin_uuid != parent_uuid) - { - // We might have moved last item from a folder, check if it needs to be removed - LLViewerInventoryCategory* cat = gInventory.getCategory(origin_uuid); - if (cat->getDescendentCount() == 0) - { - // Remove previous folder if it ends up empty - if (cb_msg) - { - std::string indent; - for (int i = 1; i < depth; i++) - { - indent += " "; - } - std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Warning Delete"); - cb_msg(message, depth, LLError::LEVEL_WARN); - } - gInventory.removeCategory(cat->getUUID()); - if (notify_observers) - { - gInventory.notifyObservers(); - } - } - } - - // Next type - update_marketplace_category(parent_uuid); - update_marketplace_category(new_category_id); - if (notify_observers) - { - gInventory.notifyObservers(); - } - cb_result(0, true); - } - ); - items_vector_it++; - } - } - // Stock folder should have no sub folder so reparent those up - if (folder_type == LLFolderType::FT_MARKETPLACE_STOCK) - { - LLUUID parent_uuid = cat->getParentUUID(); - gInventory.getDirectDescendentsOf(cat->getUUID(),cat_array,item_array); - LLInventoryModel::cat_array_t cat_array_copy = *cat_array; - for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) - { - LLViewerInventoryCategory * viewer_cat = (LLViewerInventoryCategory *) (*iter); - gInventory.changeCategoryParent(viewer_cat, parent_uuid, false); - validate_marketplacelistings(viewer_cat, cb_result, cb_msg, fix_hierarchy, depth, false, pending_callbacks, result); - } - } - } - else if (cb_msg) - { - // We are not fixing the hierarchy but reporting problems, report everything we can find - // Print the folder name - if (result && (depth >= 1)) - { - if ((folder_type == LLFolderType::FT_MARKETPLACE_STOCK) && (count >= 2)) - { - // Report if a stock folder contains a mix of items - result = false; - std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Error Mixed Stock"); - cb_msg(message,depth,LLError::LEVEL_ERROR); - } - else if ((folder_type == LLFolderType::FT_MARKETPLACE_STOCK) && (cat_array->size() != 0)) - { - // Report if a stock folder contains subfolders - result = false; - std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Error Subfolder In Stock"); - cb_msg(message,depth,LLError::LEVEL_ERROR); - } - else - { - // Simply print the folder name - std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Log"); - cb_msg(message,depth,LLError::LEVEL_INFO); - } - } - // Scan each item and report if there's a problem - LLInventoryModel::item_array_t item_array_copy = *item_array; - for (LLInventoryModel::item_array_t::iterator iter = item_array_copy.begin(); iter != item_array_copy.end(); iter++) - { - LLInventoryItem* item = *iter; - LLViewerInventoryItem * viewer_inv_item = (LLViewerInventoryItem *) item; - std::string error_msg; - if (!can_move_to_marketplace(item, error_msg, false)) - { - // Report items that shouldn't be there to start with - result = false; - std::string message = indent + " " + viewer_inv_item->getName() + LLTrans::getString("Marketplace Validation Error") + " " + error_msg; - cb_msg(message,depth,LLError::LEVEL_ERROR); - } - else if ((!viewer_inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) && (folder_type != LLFolderType::FT_MARKETPLACE_STOCK)) - { - // Report stock items that are misplaced - result = false; - std::string message = indent + " " + viewer_inv_item->getName() + LLTrans::getString("Marketplace Validation Error Stock Item"); - cb_msg(message,depth,LLError::LEVEL_ERROR); - } - else if (depth == 1) - { - // Report items not wrapped in version folder - result = false; - std::string message = indent + " " + viewer_inv_item->getName() + LLTrans::getString("Marketplace Validation Warning Unwrapped Item"); - cb_msg(message,depth,LLError::LEVEL_ERROR); - } - } - } - - // Clean up - if (viewer_cat->getDescendentCount() == 0) - { - // Remove the current folder if it ends up empty - if (cb_msg) - { - std::string message = indent + viewer_cat->getName() + LLTrans::getString("Marketplace Validation Warning Delete"); - cb_msg(message,depth,LLError::LEVEL_WARN); - } - gInventory.removeCategory(cat->getUUID()); - if (notify_observers) - { - gInventory.notifyObservers(); - } - result &=!has_bad_items; - return; - } - } - - // Recursion : Perform the same validation on each nested folder - gInventory.getDirectDescendentsOf(cat->getUUID(),cat_array,item_array); - LLInventoryModel::cat_array_t cat_array_copy = *cat_array; - // Sort the folders in alphabetical order first - std::sort(cat_array_copy.begin(), cat_array_copy.end(), sort_alpha); - - for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) - { - LLInventoryCategory* category = *iter; - validate_marketplacelistings(category, cb_result, cb_msg, fix_hierarchy, depth + 1, false, pending_callbacks, result); - } - - update_marketplace_category(cat->getUUID(), true, true); - if (notify_observers) - { - gInventory.notifyObservers(); - } - result &= !has_bad_items; -} - -void change_item_parent(const LLUUID& item_id, const LLUUID& new_parent_id) -{ - LLInventoryItem* inv_item = gInventory.getItem(item_id); - if (inv_item) - { - LLInventoryModel::update_list_t update; - LLInventoryModel::LLCategoryUpdate old_folder(inv_item->getParentUUID(), -1); - update.push_back(old_folder); - LLInventoryModel::LLCategoryUpdate new_folder(new_parent_id, 1); - update.push_back(new_folder); - gInventory.accountForUpdate(update); - - LLPointer new_item = new LLViewerInventoryItem(inv_item); - new_item->setParent(new_parent_id); - new_item->updateParentOnServer(false); - gInventory.updateItem(new_item); - gInventory.notifyObservers(); - } -} - -void move_items_to_folder(const LLUUID& new_cat_uuid, const uuid_vec_t& selected_uuids) -{ - for (uuid_vec_t::const_iterator it = selected_uuids.begin(); it != selected_uuids.end(); ++it) - { - LLInventoryItem* inv_item = gInventory.getItem(*it); - if (inv_item) - { - change_item_parent(*it, new_cat_uuid); - } - else - { - LLInventoryCategory* inv_cat = gInventory.getCategory(*it); - if (inv_cat && !LLFolderType::lookupIsProtectedType(inv_cat->getPreferredType())) - { - gInventory.changeCategoryParent((LLViewerInventoryCategory*)inv_cat, new_cat_uuid, false); - } - } - } - - LLFloater* floater_inventory = LLFloaterReg::getInstance("inventory"); - if (!floater_inventory) - { - LL_WARNS() << "Could not find My Inventory floater" << LL_ENDL; - return; - } - LLSidepanelInventory *sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); - if (sidepanel_inventory) - { - if (sidepanel_inventory->getActivePanel()) - { - sidepanel_inventory->getActivePanel()->setSelection(new_cat_uuid, TAKE_FOCUS_YES); - LLFolderViewItem* fv_folder = sidepanel_inventory->getActivePanel()->getItemByID(new_cat_uuid); - if (fv_folder) - { - fv_folder->setOpen(true); - } - } - } -} - -bool is_only_cats_selected(const uuid_vec_t& selected_uuids) -{ - for (uuid_vec_t::const_iterator it = selected_uuids.begin(); it != selected_uuids.end(); ++it) - { - LLInventoryCategory* inv_cat = gInventory.getCategory(*it); - if (!inv_cat) - { - return false; - } - } - return true; -} - -bool is_only_items_selected(const uuid_vec_t& selected_uuids) -{ - for (uuid_vec_t::const_iterator it = selected_uuids.begin(); it != selected_uuids.end(); ++it) - { - LLViewerInventoryItem* inv_item = gInventory.getItem(*it); - if (!inv_item) - { - return false; - } - } - return true; -} - - -void move_items_to_new_subfolder(const uuid_vec_t& selected_uuids, const std::string& folder_name) -{ - LLInventoryObject* first_item = gInventory.getObject(*selected_uuids.begin()); - if (!first_item) - { - return; - } - - inventory_func_type func = boost::bind(&move_items_to_folder, _1, selected_uuids); - gInventory.createNewCategory(first_item->getParentUUID(), LLFolderType::FT_NONE, folder_name, func); -} - -std::string get_category_path(LLUUID cat_id) -{ - LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id); - std::string localized_cat_name; - if (!LLTrans::findString(localized_cat_name, "InvFolder " + cat->getName())) - { - localized_cat_name = cat->getName(); - } - - if (cat->getParentUUID().notNull()) - { - return get_category_path(cat->getParentUUID()) + " > " + localized_cat_name; - } - else - { - return localized_cat_name; - } -} -// Returns true if the item can be moved to Current Outfit or any outfit folder. -bool can_move_to_outfit(LLInventoryItem* inv_item, bool move_is_into_current_outfit) -{ - LLInventoryType::EType inv_type = inv_item->getInventoryType(); - if ((inv_type != LLInventoryType::IT_WEARABLE) && - (inv_type != LLInventoryType::IT_GESTURE) && - (inv_type != LLInventoryType::IT_ATTACHMENT) && - (inv_type != LLInventoryType::IT_OBJECT) && - (inv_type != LLInventoryType::IT_SNAPSHOT) && - (inv_type != LLInventoryType::IT_TEXTURE)) - { - return false; - } - - U32 flags = inv_item->getFlags(); - if(flags & LLInventoryItemFlags::II_FLAGS_OBJECT_HAS_MULTIPLE_ITEMS) - { - return false; - } - - if((inv_type == LLInventoryType::IT_TEXTURE) || (inv_type == LLInventoryType::IT_SNAPSHOT)) - { - return !move_is_into_current_outfit; - } - - if (move_is_into_current_outfit && get_is_item_worn(inv_item->getUUID())) - { - return false; - } - - return true; -} - -// Returns true if item is a landmark or a link to a landmark -// and can be moved to Favorites or Landmarks folder. -bool can_move_to_landmarks(LLInventoryItem* inv_item) -{ - // Need to get the linked item to know its type because LLInventoryItem::getType() - // returns actual type AT_LINK for links, not the asset type of a linked item. - if (LLAssetType::AT_LINK == inv_item->getType()) - { - LLInventoryItem* linked_item = gInventory.getItem(inv_item->getLinkedUUID()); - if (linked_item) - { - return LLAssetType::AT_LANDMARK == linked_item->getType(); - } - } - - return LLAssetType::AT_LANDMARK == inv_item->getType(); -} - -// Returns true if folder's content can be moved to Current Outfit or any outfit folder. -bool can_move_to_my_outfits(LLInventoryModel* model, LLInventoryCategory* inv_cat, U32 wear_limit) -{ - LLInventoryModel::cat_array_t *cats; - LLInventoryModel::item_array_t *items; - model->getDirectDescendentsOf(inv_cat->getUUID(), cats, items); - - if (items->size() > wear_limit) - { - return false; - } - - if (items->size() == 0) - { - // Nothing to move(create) - return false; - } - - if (cats->size() > 0) - { - // We do not allow subfolders in outfits of "My Outfits" yet - return false; - } - - LLInventoryModel::item_array_t::iterator iter = items->begin(); - LLInventoryModel::item_array_t::iterator end = items->end(); - - while (iter != end) - { - LLViewerInventoryItem *item = *iter; - if (!can_move_to_outfit(item, false)) - { - return false; - } - iter++; - } - - return true; -} - -std::string get_localized_folder_name(LLUUID cat_uuid) -{ - std::string localized_root_name; - const LLViewerInventoryCategory* cat = gInventory.getCategory(cat_uuid); - if (cat) - { - LLFolderType::EType preferred_type = cat->getPreferredType(); - - // Translation of Accessories folder in Library inventory folder - bool accessories = false; - if(cat->getName() == "Accessories") - { - const LLUUID& parent_folder_id = cat->getParentUUID(); - accessories = (parent_folder_id == gInventory.getLibraryRootFolderID()); - } - - //"Accessories" inventory category has folder type FT_NONE. So, this folder - //can not be detected as protected with LLFolderType::lookupIsProtectedType - localized_root_name.assign(cat->getName()); - if (accessories || LLFolderType::lookupIsProtectedType(preferred_type)) - { - LLTrans::findString(localized_root_name, std::string("InvFolder ") + cat->getName(), LLSD()); - } - } - - return localized_root_name; -} - -void new_folder_window(const LLUUID& folder_id) -{ - LLPanelMainInventory::newFolderWindow(folder_id); -} - -void ungroup_folder_items(const LLUUID& folder_id) -{ - LLInventoryCategory* inv_cat = gInventory.getCategory(folder_id); - if (!inv_cat || LLFolderType::lookupIsProtectedType(inv_cat->getPreferredType())) - { - return; - } - const LLUUID &new_cat_uuid = inv_cat->getParentUUID(); - LLInventoryModel::cat_array_t* cat_array; - LLInventoryModel::item_array_t* item_array; - gInventory.getDirectDescendentsOf(inv_cat->getUUID(), cat_array, item_array); - LLInventoryModel::cat_array_t cats = *cat_array; - LLInventoryModel::item_array_t items = *item_array; - - for (LLInventoryModel::cat_array_t::const_iterator cat_iter = cats.begin(); cat_iter != cats.end(); ++cat_iter) - { - LLViewerInventoryCategory* cat = *cat_iter; - if (cat) - { - gInventory.changeCategoryParent(cat, new_cat_uuid, false); - } - } - for (LLInventoryModel::item_array_t::const_iterator item_iter = items.begin(); item_iter != items.end(); ++item_iter) - { - LLViewerInventoryItem* item = *item_iter; - if(item) - { - gInventory.changeItemParent(item, new_cat_uuid, false); - } - } - gInventory.removeCategory(inv_cat->getUUID()); - gInventory.notifyObservers(); -} - -std::string get_searchable_description(LLInventoryModel* model, const LLUUID& item_id) -{ - if (model) - { - const LLInventoryItem *item = model->getItem(item_id); - if(item) - { - std::string desc = item->getDescription(); - LLStringUtil::toUpper(desc); - return desc; - } - } - return LLStringUtil::null; -} - -std::string get_searchable_creator_name(LLInventoryModel* model, const LLUUID& item_id) -{ - if (model) - { - const LLInventoryItem *item = model->getItem(item_id); - if(item) - { - LLAvatarName av_name; - if (LLAvatarNameCache::get(item->getCreatorUUID(), &av_name)) - { - std::string username = av_name.getUserName(); - LLStringUtil::toUpper(username); - return username; - } - } - } - return LLStringUtil::null; -} - -std::string get_searchable_UUID(LLInventoryModel* model, const LLUUID& item_id) -{ - if (model) - { - const LLViewerInventoryItem *item = model->getItem(item_id); - if(item && (item->getIsFullPerm() || gAgent.isGodlikeWithoutAdminMenuFakery())) - { - std::string uuid = item->getAssetUUID().asString(); - LLStringUtil::toUpper(uuid); - return uuid; - } - } - return LLStringUtil::null; -} - -bool can_share_item(const LLUUID& item_id) -{ - bool can_share = false; - - if (gInventory.isObjectDescendentOf(item_id, gInventory.getRootFolderID())) - { - const LLViewerInventoryItem *item = gInventory.getItem(item_id); - if (item) - { - if (LLInventoryCollectFunctor::itemTransferCommonlyAllowed(item)) - { - can_share = LLGiveInventory::isInventoryGiveAcceptable(item); - } - } - else - { - can_share = (gInventory.getCategory(item_id) != NULL); - } - - const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - if ((item_id == trash_id) || gInventory.isObjectDescendentOf(item_id, trash_id)) - { - can_share = false; - } - } - - return can_share; -} -///---------------------------------------------------------------------------- -/// LLMarketplaceValidator implementations -///---------------------------------------------------------------------------- - - -LLMarketplaceValidator::LLMarketplaceValidator() - : mPendingCallbacks(0) - , mValidationInProgress(false) -{ -} - -LLMarketplaceValidator::~LLMarketplaceValidator() -{ -} - -void LLMarketplaceValidator::validateMarketplaceListings( - const LLUUID &category_id, - LLMarketplaceValidator::validation_done_callback_t cb_done, - LLMarketplaceValidator::validation_msg_callback_t cb_msg, - bool fix_hierarchy, - S32 depth) -{ - - mValidationQueue.emplace(category_id, cb_done, cb_msg, fix_hierarchy, depth); - if (!mValidationInProgress) - { - start(); - } -} - -void LLMarketplaceValidator::start() -{ - if (mValidationQueue.empty()) - { - mValidationInProgress = false; - return; - } - mValidationInProgress = true; - - const ValidationRequest &first = mValidationQueue.front(); - LLViewerInventoryCategory* cat = gInventory.getCategory(first.mCategoryId); - if (!cat) - { - LL_WARNS() << "Tried to validate a folder that doesn't exist" << LL_ENDL; - if (first.mCbDone) - { - first.mCbDone(false); - } - mValidationQueue.pop(); - start(); - return; - } - - validation_result_callback_t result_callback = [](S32 pending, bool result) - { - LLMarketplaceValidator* validator = LLMarketplaceValidator::getInstance(); - validator->mPendingCallbacks--; // we just got a callback - validator->mPendingCallbacks += pending; - validator->mPendingResult &= result; - if (validator->mPendingCallbacks <= 0) - { - llassert(validator->mPendingCallbacks == 0); // shouldn't be below 0 - const ValidationRequest &first = validator->mValidationQueue.front(); - if (first.mCbDone) - { - first.mCbDone(validator->mPendingResult); - } - validator->mValidationQueue.pop(); // done; - validator->start(); - } - }; - - mPendingResult = true; - mPendingCallbacks = 1; // do '1' in case something decides to callback immediately - - S32 pending_calbacks = 0; - bool result = true; - validate_marketplacelistings( - cat, - result_callback, - first.mCbMsg, - first.mFixHierarchy, - first.mDepth, - true, - pending_calbacks, - result); - - result_callback(pending_calbacks, result); -} - -LLMarketplaceValidator::ValidationRequest::ValidationRequest( - LLUUID category_id, - validation_done_callback_t cb_done, - validation_msg_callback_t cb_msg, - bool fix_hierarchy, - S32 depth) -: mCategoryId(category_id) -, mCbDone(cb_done) -, mCbMsg(cb_msg) -, mFixHierarchy(fix_hierarchy) -, mDepth(depth) -{} - -///---------------------------------------------------------------------------- -/// LLInventoryCollectFunctor implementations -///---------------------------------------------------------------------------- - -// static -bool LLInventoryCollectFunctor::itemTransferCommonlyAllowed(const LLInventoryItem* item) -{ - if (!item) - return false; - - switch(item->getType()) - { - case LLAssetType::AT_OBJECT: - case LLAssetType::AT_BODYPART: - case LLAssetType::AT_CLOTHING: - if (!get_is_item_worn(item->getUUID())) - return true; - break; - default: - return true; - break; - } - return false; -} - -bool LLIsType::operator()(LLInventoryCategory* cat, LLInventoryItem* item) -{ - if(mType == LLAssetType::AT_CATEGORY) - { - if(cat) return true; - } - if(item) - { - if(item->getType() == mType) return true; - } - return false; -} - -bool LLIsNotType::operator()(LLInventoryCategory* cat, LLInventoryItem* item) -{ - if(mType == LLAssetType::AT_CATEGORY) - { - if(cat) return false; - } - if(item) - { - if(item->getType() == mType) return false; - else return true; - } - return true; -} - -bool LLIsOfAssetType::operator()(LLInventoryCategory* cat, LLInventoryItem* item) -{ - if(mType == LLAssetType::AT_CATEGORY) - { - if(cat) return true; - } - if(item) - { - if(item->getActualType() == mType) return true; - } - return false; -} - -bool LLAssetIDAndTypeMatches::operator()(LLInventoryCategory* cat, LLInventoryItem* item) -{ - if (!item) return false; - return (item->getActualType() == mType && item->getAssetUUID() == mAssetID); -} - -bool LLIsValidItemLink::operator()(LLInventoryCategory* cat, LLInventoryItem* item) -{ - LLViewerInventoryItem *vitem = dynamic_cast(item); - if (!vitem) return false; - return (vitem->getActualType() == LLAssetType::AT_LINK && !vitem->getIsBrokenLink()); -} - -bool LLIsTypeWithPermissions::operator()(LLInventoryCategory* cat, LLInventoryItem* item) -{ - if(mType == LLAssetType::AT_CATEGORY) - { - if(cat) - { - return true; - } - } - if(item) - { - if(item->getType() == mType) - { - LLPermissions perm = item->getPermissions(); - if ((perm.getMaskBase() & mPerm) == mPerm) - { - return true; - } - } - } - return false; -} - -bool LLBuddyCollector::operator()(LLInventoryCategory* cat, - LLInventoryItem* item) -{ - if(item) - { - if((LLAssetType::AT_CALLINGCARD == item->getType()) - && (!item->getCreatorUUID().isNull()) - && (item->getCreatorUUID() != gAgent.getID())) - { - return true; - } - } - return false; -} - - -bool LLUniqueBuddyCollector::operator()(LLInventoryCategory* cat, - LLInventoryItem* item) -{ - if(item) - { - if((LLAssetType::AT_CALLINGCARD == item->getType()) - && (item->getCreatorUUID().notNull()) - && (item->getCreatorUUID() != gAgent.getID())) - { - mSeen.insert(item->getCreatorUUID()); - return true; - } - } - return false; -} - - -bool LLParticularBuddyCollector::operator()(LLInventoryCategory* cat, - LLInventoryItem* item) -{ - if(item) - { - if((LLAssetType::AT_CALLINGCARD == item->getType()) - && (item->getCreatorUUID() == mBuddyID)) - { - return true; - } - } - return false; -} - - -bool LLNameCategoryCollector::operator()( - LLInventoryCategory* cat, LLInventoryItem* item) -{ - if(cat) - { - if (!LLStringUtil::compareInsensitive(mName, cat->getName())) - { - return true; - } - } - return false; -} - -bool LLFindCOFValidItems::operator()(LLInventoryCategory* cat, - LLInventoryItem* item) -{ - // Valid COF items are: - // - links to wearables (body parts or clothing) - // - links to attachments - // - links to gestures - // - links to ensemble folders - LLViewerInventoryItem *linked_item = ((LLViewerInventoryItem*)item)->getLinkedItem(); - if (linked_item) - { - LLAssetType::EType type = linked_item->getType(); - return (type == LLAssetType::AT_CLOTHING || - type == LLAssetType::AT_BODYPART || - type == LLAssetType::AT_GESTURE || - type == LLAssetType::AT_OBJECT); - } - else - { - LLViewerInventoryCategory *linked_category = ((LLViewerInventoryItem*)item)->getLinkedCategory(); - // BAP remove AT_NONE support after ensembles are fully working? - return (linked_category && - ((linked_category->getPreferredType() == LLFolderType::FT_NONE) || - (LLFolderType::lookupIsEnsembleType(linked_category->getPreferredType())))); - } -} - -bool LLFindBrokenLinks::operator()(LLInventoryCategory* cat, - LLInventoryItem* item) -{ - // only for broken links getType will be a link - // otherwise it's supposed to have the type of an item - // it is linked too - if (item && LLAssetType::lookupIsLinkType(item->getType())) - { - return true; - } - return false; -} - -bool LLFindWearables::operator()(LLInventoryCategory* cat, - LLInventoryItem* item) -{ - if(item) - { - if((item->getType() == LLAssetType::AT_CLOTHING) - || (item->getType() == LLAssetType::AT_BODYPART)) - { - return true; - } - } - return false; -} - -LLFindWearablesEx::LLFindWearablesEx(bool is_worn, bool include_body_parts) -: mIsWorn(is_worn) -, mIncludeBodyParts(include_body_parts) -{} - -bool LLFindWearablesEx::operator()(LLInventoryCategory* cat, LLInventoryItem* item) -{ - LLViewerInventoryItem *vitem = dynamic_cast(item); - if (!vitem) return false; - - // Skip non-wearables. - if (!vitem->isWearableType() && vitem->getType() != LLAssetType::AT_OBJECT && vitem->getType() != LLAssetType::AT_GESTURE) - { - return false; - } - - // Skip body parts if requested. - if (!mIncludeBodyParts && vitem->getType() == LLAssetType::AT_BODYPART) - { - return false; - } - - // Skip broken links. - if (vitem->getIsBrokenLink()) - { - return false; - } - - return (bool) get_is_item_worn(item->getUUID()) == mIsWorn; -} - -bool LLFindWearablesOfType::operator()(LLInventoryCategory* cat, LLInventoryItem* item) -{ - if (!item) return false; - if (item->getType() != LLAssetType::AT_CLOTHING && - item->getType() != LLAssetType::AT_BODYPART) - { - return false; - } - - LLViewerInventoryItem *vitem = dynamic_cast(item); - if (!vitem || vitem->getWearableType() != mWearableType) return false; - - return true; -} - -void LLFindWearablesOfType::setType(LLWearableType::EType type) -{ - mWearableType = type; -} - -bool LLIsTextureType::operator()(LLInventoryCategory* cat, LLInventoryItem* item) -{ - return item && (item->getType() == LLAssetType::AT_TEXTURE); -} - -bool LLFindNonRemovableObjects::operator()(LLInventoryCategory* cat, LLInventoryItem* item) -{ - if (item) - { - return !get_is_item_removable(&gInventory, item->getUUID(), true); - } - if (cat) - { - return !get_is_category_removable(&gInventory, cat->getUUID()); - } - - LL_WARNS() << "Not a category and not an item?" << LL_ENDL; - return false; -} - -///---------------------------------------------------------------------------- -/// LLAssetIDMatches -///---------------------------------------------------------------------------- -bool LLAssetIDMatches::operator()(LLInventoryCategory* cat, LLInventoryItem* item) -{ - return (item && item->getAssetUUID() == mAssetID); -} - -///---------------------------------------------------------------------------- -/// LLLinkedItemIDMatches -///---------------------------------------------------------------------------- -bool LLLinkedItemIDMatches::operator()(LLInventoryCategory* cat, LLInventoryItem* item) -{ - return (item && - (item->getIsLinkType()) && - (item->getLinkedUUID() == mBaseItemID)); // A linked item's assetID will be the compared-to item's itemID. -} - -void LLSaveFolderState::setApply(bool apply) -{ - mApply = apply; - // before generating new list of open folders, clear the old one - if(!apply) - { - clearOpenFolders(); - } -} - -void LLSaveFolderState::doFolder(LLFolderViewFolder* folder) -{ - LLInvFVBridge* bridge = (LLInvFVBridge*)folder->getViewModelItem(); - if(!bridge) return; - - if(mApply) - { - // we're applying the open state - LLUUID id(bridge->getUUID()); - if(mOpenFolders.find(id) != mOpenFolders.end()) - { - if (!folder->isOpen()) - { - folder->setOpen(true); - } - } - else - { - // keep selected filter in its current state, this is less jarring to user - if (!folder->isSelected() && folder->isOpen()) - { - folder->setOpen(false); - } - } - } - else - { - // we're recording state at this point - if(folder->isOpen()) - { - mOpenFolders.insert(bridge->getUUID()); - } - } -} - -void LLOpenFilteredFolders::doItem(LLFolderViewItem *item) -{ - if (item->passedFilter()) - { - item->getParentFolder()->setOpenArrangeRecursively(true, LLFolderViewFolder::RECURSE_UP); - } -} - -void LLOpenFilteredFolders::doFolder(LLFolderViewFolder* folder) -{ - if (folder->LLFolderViewItem::passedFilter() && folder->getParentFolder()) - { - folder->getParentFolder()->setOpenArrangeRecursively(true, LLFolderViewFolder::RECURSE_UP); - } - // if this folder didn't pass the filter, and none of its descendants did - else if (!folder->getViewModelItem()->passedFilter() && !folder->getViewModelItem()->descendantsPassedFilter()) - { - folder->setOpenArrangeRecursively(false, LLFolderViewFolder::RECURSE_NO); - } -} - -void LLSelectFirstFilteredItem::doItem(LLFolderViewItem *item) -{ - if (item->passedFilter() && !mItemSelected) - { - item->getRoot()->setSelection(item, false, false); - if (item->getParentFolder()) - { - item->getParentFolder()->setOpenArrangeRecursively(true, LLFolderViewFolder::RECURSE_UP); - } - mItemSelected = true; - } -} - -void LLSelectFirstFilteredItem::doFolder(LLFolderViewFolder* folder) -{ - // Skip if folder or item already found, if not filtered or if no parent (root folder is not selectable) - if (!mFolderSelected && !mItemSelected && folder->LLFolderViewItem::passedFilter() && folder->getParentFolder()) - { - folder->getRoot()->setSelection(folder, false, false); - folder->getParentFolder()->setOpenArrangeRecursively(true, LLFolderViewFolder::RECURSE_UP); - mFolderSelected = true; - } -} - -void LLOpenFoldersWithSelection::doItem(LLFolderViewItem *item) -{ - if (item->getParentFolder() && item->isSelected()) - { - item->getParentFolder()->setOpenArrangeRecursively(true, LLFolderViewFolder::RECURSE_UP); - } -} - -void LLOpenFoldersWithSelection::doFolder(LLFolderViewFolder* folder) -{ - if (folder->getParentFolder() && folder->isSelected()) - { - folder->getParentFolder()->setOpenArrangeRecursively(true, LLFolderViewFolder::RECURSE_UP); - } -} - -// Callback for doToSelected if DAMA required... -void LLInventoryAction::callback_doToSelected(const LLSD& notification, const LLSD& response, class LLInventoryModel* model, class LLFolderView* root, const std::string& action) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) // YES - { - doToSelected(model, root, action, false); - } -} - -void LLInventoryAction::callback_copySelected(const LLSD& notification, const LLSD& response, class LLInventoryModel* model, class LLFolderView* root, const std::string& action) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) // YES, Move no copy item(s) - { - doToSelected(model, root, "copy_or_move_to_marketplace_listings", false); - } - else if (option == 1) // NO, Don't move no copy item(s) (leave them behind) - { - doToSelected(model, root, "copy_to_marketplace_listings", false); - } -} - -// Succeeds iff all selected items are bridges to objects, in which -// case returns their corresponding uuids. -bool get_selection_object_uuids(LLFolderView *root, uuid_vec_t& ids) -{ - uuid_vec_t results; - S32 non_object = 0; - LLFolderView::selected_items_t selectedItems = root->getSelectedItems(); - for(LLFolderView::selected_items_t::iterator it = selectedItems.begin(); it != selectedItems.end(); ++it) - { - LLObjectBridge *view_model = dynamic_cast((*it)->getViewModelItem()); - - if(view_model && view_model->getUUID().notNull()) - { - results.push_back(view_model->getUUID()); - } - else - { - non_object++; - } - } - if (non_object == 0) - { - ids = results; - return true; - } - return false; -} - - -void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root, const std::string& action, bool user_confirm) -{ - std::set selected_items = root->getSelectionList(); - if (selected_items.empty() - && action != "wear" - && action != "wear_add" - && !isRemoveAction(action)) - { - // Was item removed while user was checking menu? - // "wear" and removal exlusions are due to use of - // getInventorySelectedUUIDs() below - LL_WARNS("Inventory") << "Menu tried to operate on empty selection" << LL_ENDL; - - if (("copy" == action) || ("cut" == action)) - { - LLClipboard::instance().reset(); - } - - return; - } - - // Prompt the user and check for authorization for some marketplace active listing edits - if (user_confirm && (("delete" == action) || ("cut" == action) || ("rename" == action) || ("properties" == action) || ("task_properties" == action) || ("open" == action))) - { - std::set::iterator set_iter = selected_items.begin(); - LLFolderViewModelItemInventory * viewModel = NULL; - for (; set_iter != selected_items.end(); ++set_iter) - { - viewModel = dynamic_cast((*set_iter)->getViewModelItem()); - if (viewModel && (depth_nesting_in_marketplace(viewModel->getUUID()) >= 0)) - { - break; - } - } - if (set_iter != selected_items.end()) - { - if ("open" == action) - { - if (get_can_item_be_worn(viewModel->getUUID())) - { - // Wearing an object from any listing, active or not, is verbotten - LLNotificationsUtil::add("AlertMerchantListingCannotWear"); - return; - } - // Note: we do not prompt for change when opening items (e.g. textures or note cards) on the marketplace... - } - else if (LLMarketplaceData::instance().isInActiveFolder(viewModel->getUUID()) || - LLMarketplaceData::instance().isListedAndActive(viewModel->getUUID())) - { - // If item is in active listing, further confirmation is required - if ((("cut" == action) || ("delete" == action)) && (LLMarketplaceData::instance().isListed(viewModel->getUUID()) || LLMarketplaceData::instance().isVersionFolder(viewModel->getUUID()))) - { - // Cut or delete of the active version folder or listing folder itself will unlist the listing so ask that question specifically - LLNotificationsUtil::add("ConfirmMerchantUnlist", LLSD(), LLSD(), boost::bind(&LLInventoryAction::callback_doToSelected, _1, _2, model, root, action)); - return; - } - // Any other case will simply modify but not unlist a listing - LLNotificationsUtil::add("ConfirmMerchantActiveChange", LLSD(), LLSD(), boost::bind(&LLInventoryAction::callback_doToSelected, _1, _2, model, root, action)); - return; - } - // Cutting or deleting a whole listing needs confirmation as SLM will be archived and inaccessible to the user - else if (LLMarketplaceData::instance().isListed(viewModel->getUUID()) && (("cut" == action) || ("delete" == action))) - { - LLNotificationsUtil::add("ConfirmListingCutOrDelete", LLSD(), LLSD(), boost::bind(&LLInventoryAction::callback_doToSelected, _1, _2, model, root, action)); - return; - } - } - } - // Copying to the marketplace needs confirmation if nocopy items are involved - if (user_confirm && ("copy_to_marketplace_listings" == action)) - { - std::set::iterator set_iter = selected_items.begin(); - LLFolderViewModelItemInventory * viewModel = dynamic_cast((*set_iter)->getViewModelItem()); - if (contains_nocopy_items(viewModel->getUUID())) - { - LLNotificationsUtil::add("ConfirmCopyToMarketplace", LLSD(), LLSD(), boost::bind(&LLInventoryAction::callback_copySelected, _1, _2, model, root, action)); - return; - } - } - - // Keep track of the marketplace folders that will need update of their status/name after the operation is performed - buildMarketplaceFolders(root); - - if ("rename" == action) - { - root->startRenamingSelectedItem(); - // Update the marketplace listings that have been affected by the operation - updateMarketplaceFolders(); - return; - } - - if ("delete" == action) - { - const LLUUID &marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - bool marketplacelistings_item = false; - bool has_worn = false; - bool needs_replacement = false; - LLAllDescendentsPassedFilter f; - for (std::set::iterator it = selected_items.begin(); (it != selected_items.end()) && (f.allDescendentsPassedFilter()); ++it) - { - if (LLFolderViewFolder* folder = dynamic_cast(*it)) - { - folder->applyFunctorRecursively(f); - } - LLFolderViewModelItemInventory * viewModel = dynamic_cast((*it)->getViewModelItem()); - LLUUID obj_id = viewModel->getUUID(); - if (viewModel && gInventory.isObjectDescendentOf(obj_id, marketplacelistings_id)) - { - marketplacelistings_item = true; - break; - } - - LLViewerInventoryCategory* cat = gInventory.getCategory(obj_id); - if (cat) - { - LLInventoryModel::cat_array_t categories; - LLInventoryModel::item_array_t items; - - gInventory.collectDescendents(obj_id, categories, items, false); - - for (LLInventoryModel::item_array_t::value_type& item : items) - { - if (get_is_item_worn(item)) - { - has_worn = true; - LLWearableType::EType type = item->getWearableType(); - if (type == LLWearableType::WT_SHAPE - || type == LLWearableType::WT_SKIN - || type == LLWearableType::WT_HAIR - || type == LLWearableType::WT_EYES) - { - needs_replacement = true; - break; - } - } - } - if (needs_replacement) - { - break; - } - } - LLViewerInventoryItem* item = gInventory.getItem(obj_id); - if (item && get_is_item_worn(item)) - { - has_worn = true; - LLWearableType::EType type = item->getWearableType(); - if (type == LLWearableType::WT_SHAPE - || type == LLWearableType::WT_SKIN - || type == LLWearableType::WT_HAIR - || type == LLWearableType::WT_EYES) - { - needs_replacement = true; - break; - } - } - } - // Fall through to the generic confirmation if the user choose to ignore the specialized one - if (needs_replacement) - { - LLNotificationsUtil::add("CantDeleteRequiredClothing"); - } - else if (has_worn) - { - LLSD payload; - payload["has_worn"] = true; - LLNotificationsUtil::add("DeleteWornItems", LLSD(), payload, boost::bind(&LLInventoryAction::onItemsRemovalConfirmation, _1, _2, root->getHandle())); - } - else if ( (!f.allDescendentsPassedFilter()) && !marketplacelistings_item && (!LLNotifications::instance().getIgnored("DeleteFilteredItems")) ) - { - LLNotificationsUtil::add("DeleteFilteredItems", LLSD(), LLSD(), boost::bind(&LLInventoryAction::onItemsRemovalConfirmation, _1, _2, root->getHandle())); - } - else - { - if (!sDeleteConfirmationDisplayed) // ask for the confirmation at least once per session - { - LLNotifications::instance().setIgnored("DeleteItems", false); - sDeleteConfirmationDisplayed = true; - } - - LLSD args; - args["QUESTION"] = LLTrans::getString(root->getSelectedCount() > 1 ? "DeleteItems" : "DeleteItem"); - LLNotificationsUtil::add("DeleteItems", args, LLSD(), boost::bind(&LLInventoryAction::onItemsRemovalConfirmation, _1, _2, root->getHandle())); - } - // Note: marketplace listings will be updated in the callback if delete confirmed - return; - } - if (("copy" == action) || ("cut" == action)) - { - // Clear the clipboard before we start adding things on it - LLClipboard::instance().reset(); - } - if ("replace_links" == action) - { - LLSD params; - if (root->getSelectedCount() == 1) - { - LLFolderViewItem* folder_item = root->getSelectedItems().front(); - LLInvFVBridge* bridge = (LLInvFVBridge*)folder_item->getViewModelItem(); - - if (bridge) - { - LLInventoryObject* obj = bridge->getInventoryObject(); - if (obj && obj->getType() != LLAssetType::AT_CATEGORY && obj->getActualType() != LLAssetType::AT_LINK_FOLDER) - { - params = LLSD(obj->getUUID()); - } - } - } - LLFloaterReg::showInstance("linkreplace", params); - return; - } - - static const std::string change_folder_string = "change_folder_type_"; - if (action.length() > change_folder_string.length() && - (action.compare(0,change_folder_string.length(),"change_folder_type_") == 0)) - { - LLFolderType::EType new_folder_type = LLViewerFolderType::lookupTypeFromXUIName(action.substr(change_folder_string.length())); - LLFolderViewModelItemInventory* inventory_item = static_cast(root->getViewModelItem()); - LLViewerInventoryCategory *cat = model->getCategory(inventory_item->getUUID()); - if (!cat) return; - cat->changeType(new_folder_type); - // Update the marketplace listings that have been affected by the operation - updateMarketplaceFolders(); - return; - } - - - LLMultiPreview* multi_previewp = NULL; - LLMultiItemProperties* multi_itempropertiesp = nullptr; - - if (("task_open" == action || "open" == action) && selected_items.size() > 1) - { - bool open_multi_preview = true; - - if ("open" == action) - { - for (std::set::iterator set_iter = selected_items.begin(); set_iter != selected_items.end(); ++set_iter) - { - LLFolderViewItem* folder_item = *set_iter; - if (folder_item) - { - LLInvFVBridge* bridge = dynamic_cast(folder_item->getViewModelItem()); - if (!bridge || !bridge->isMultiPreviewAllowed()) - { - open_multi_preview = false; - break; - } - } - } - } - - if (open_multi_preview) - { - multi_previewp = new LLMultiPreview(); - gFloaterView->addChild(multi_previewp); - - LLFloater::setFloaterHost(multi_previewp); - } - - } - else if (("task_properties" == action || "properties" == action) && selected_items.size() > 1) - { - multi_itempropertiesp = new LLMultiItemProperties("item_properties"); - gFloaterView->addChild(multi_itempropertiesp); - LLFloater::setFloaterHost(multi_itempropertiesp); - } - - std::set selected_uuid_set = LLAvatarActions::getInventorySelectedUUIDs(); - - // copy list of applicable items into a vector for bulk handling - uuid_vec_t ids; - if (action == "wear" || action == "wear_add") - { - const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - const LLUUID mp_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - std::copy_if(selected_uuid_set.begin(), - selected_uuid_set.end(), - std::back_inserter(ids), - [trash_id, mp_id](LLUUID id) - { - if (get_is_item_worn(id) - || LLAppearanceMgr::instance().getIsInCOF(id) - || gInventory.isObjectDescendentOf(id, trash_id)) - { - return false; - } - if (mp_id.notNull() && gInventory.isObjectDescendentOf(id, mp_id)) - { - return false; - } - LLInventoryObject* obj = (LLInventoryObject*)gInventory.getObject(id); - if (!obj) - { - return false; - } - if (obj->getIsLinkType() && gInventory.isObjectDescendentOf(obj->getLinkedUUID(), trash_id)) - { - return false; - } - if (obj->getIsLinkType() && LLAssetType::lookupIsLinkType(obj->getType())) - { - // missing - return false; - } - return true; - } - ); - } - else if (isRemoveAction(action)) - { - std::copy_if(selected_uuid_set.begin(), - selected_uuid_set.end(), - std::back_inserter(ids), - [](LLUUID id) - { - return get_is_item_worn(id); - } - ); - } - else - { - for (std::set::iterator it = selected_items.begin(), end_it = selected_items.end(); - it != end_it; - ++it) - { - ids.push_back(static_cast((*it)->getViewModelItem())->getUUID()); - } - } - - // Check for actions that get handled in bulk - if (action == "wear") - { - wear_multiple(ids, true); - } - else if (action == "wear_add") - { - wear_multiple(ids, false); - } - else if (isRemoveAction(action)) - { - LLAppearanceMgr::instance().removeItemsFromAvatar(ids); - } - else if ("save_selected_as" == action) - { - (new LLDirPickerThread(boost::bind(&LLInventoryAction::saveMultipleTextures, _1, selected_items, model), std::string()))->getFile(); - } - else if ("new_folder_from_selected" == action) - { - - LLInventoryObject* first_item = gInventory.getObject(*ids.begin()); - if (!first_item) - { - return; - } - const LLUUID& parent_uuid = first_item->getParentUUID(); - for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it) - { - LLInventoryObject *item = gInventory.getObject(*it); - if (!item || item->getParentUUID() != parent_uuid) - { - LLNotificationsUtil::add("SameFolderRequired"); - return; - } - } - - LLSD args; - args["DESC"] = LLTrans::getString("New Folder"); - - LLNotificationsUtil::add("CreateSubfolder", args, LLSD(), - [ids](const LLSD& notification, const LLSD& response) - { - S32 opt = LLNotificationsUtil::getSelectedOption(notification, response); - if (opt == 0) - { - std::string settings_name = response["message"].asString(); - - LLInventoryObject::correctInventoryName(settings_name); - if (settings_name.empty()) - { - settings_name = LLTrans::getString("New Folder"); - } - move_items_to_new_subfolder(ids, settings_name); - } - }); - } - else if ("ungroup_folder_items" == action) - { - if (ids.size() == 1) - { - ungroup_folder_items(*ids.begin()); - } - } - else if ("thumbnail" == action) - { - if (selected_items.size() > 0) - { - LLSD data; - std::set::iterator set_iter; - for (set_iter = selected_items.begin(); set_iter != selected_items.end(); ++set_iter) - { - LLFolderViewItem* folder_item = *set_iter; - if (!folder_item) continue; - LLInvFVBridge* bridge = (LLInvFVBridge*)folder_item->getViewModelItem(); - if (!bridge) continue; - data.append(bridge->getUUID()); - } - LLFloaterReg::showInstance("change_item_thumbnail", data); - } - } - else - { - std::set::iterator set_iter; - for (set_iter = selected_items.begin(); set_iter != selected_items.end(); ++set_iter) - { - LLFolderViewItem* folder_item = *set_iter; - if(!folder_item) continue; - LLInvFVBridge* bridge = (LLInvFVBridge*)folder_item->getViewModelItem(); - if(!bridge) continue; - bridge->performAction(model, action); - } - if(root->isSingleFolderMode() && selected_items.empty()) - { - LLInvFVBridge* bridge = (LLInvFVBridge*)root->getViewModelItem(); - if(bridge) - { - bridge->performAction(model, action); - } - } - } - - // Update the marketplace listings that have been affected by the operation - updateMarketplaceFolders(); - - LLFloater::setFloaterHost(NULL); - if (multi_previewp) - { - multi_previewp->openFloater(LLSD()); - } - else if (multi_itempropertiesp) - { - multi_itempropertiesp->openFloater(LLSD()); - } -} - -void LLInventoryAction::saveMultipleTextures(const std::vector& filenames, std::set selected_items, LLInventoryModel* model) -{ - gSavedSettings.setString("TextureSaveLocation", filenames[0]); - - LLMultiPreview* multi_previewp = new LLMultiPreview(); - gFloaterView->addChild(multi_previewp); - - LLFloater::setFloaterHost(multi_previewp); - - std::map tex_names_map; - std::set::iterator set_iter; - - for (set_iter = selected_items.begin(); set_iter != selected_items.end(); ++set_iter) - { - LLFolderViewItem* folder_item = *set_iter; - if(!folder_item) continue; - LLTextureBridge* bridge = (LLTextureBridge*)folder_item->getViewModelItem(); - if(!bridge) continue; - - std::string tex_name = bridge->getName(); - if(!tex_names_map.insert(std::pair(tex_name, 0)).second) - { - tex_names_map[tex_name]++; - bridge->setFileName(tex_name + llformat("_%.3d", tex_names_map[tex_name])); - } - bridge->performAction(model, "save_selected_as"); - } - - LLFloater::setFloaterHost(NULL); - if (multi_previewp) - { - multi_previewp->openFloater(LLSD()); - } -} - -void LLInventoryAction::removeItemFromDND(LLFolderView* root) -{ - if(gAgent.isDoNotDisturb()) - { - //Get selected items - LLFolderView::selected_items_t selectedItems = root->getSelectedItems(); - LLFolderViewModelItemInventory * viewModel = NULL; - - //If user is in DND and deletes item, make sure the notification is not displayed by removing the notification - //from DND history and .xml file. Once this is done, upon exit of DND mode the item deleted will not show a notification. - for(LLFolderView::selected_items_t::iterator it = selectedItems.begin(); it != selectedItems.end(); ++it) - { - viewModel = dynamic_cast((*it)->getViewModelItem()); - - if(viewModel && viewModel->getUUID().notNull()) - { - //Will remove the item offer notification - LLDoNotDisturbNotificationStorage::instance().removeNotification(LLDoNotDisturbNotificationStorage::offerName, viewModel->getUUID()); - } - } - } -} - -void LLInventoryAction::onItemsRemovalConfirmation(const LLSD& notification, const LLSD& response, LLHandle root) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0 && !root.isDead() && !root.get()->isDead()) - { - bool has_worn = notification["payload"]["has_worn"].asBoolean(); - LLFolderView* folder_root = root.get(); - //Need to remove item from DND before item is removed from root folder view - //because once removed from root folder view the item is no longer a selected item - removeItemFromDND(folder_root); - - // removeSelectedItems will change selection, collect worn items beforehand - uuid_vec_t worn; - uuid_vec_t item_deletion_list; - uuid_vec_t cat_deletion_list; - if (has_worn) - { - //Get selected items - LLFolderView::selected_items_t selectedItems = folder_root->getSelectedItems(); - - //If user is in DND and deletes item, make sure the notification is not displayed by removing the notification - //from DND history and .xml file. Once this is done, upon exit of DND mode the item deleted will not show a notification. - for (LLFolderView::selected_items_t::iterator it = selectedItems.begin(); it != selectedItems.end(); ++it) - { - LLFolderViewModelItemInventory* viewModel = dynamic_cast((*it)->getViewModelItem()); - - LLUUID obj_id = viewModel->getUUID(); - LLViewerInventoryCategory* cat = gInventory.getCategory(obj_id); - bool cat_has_worn = false; - if (cat) - { - LLInventoryModel::cat_array_t categories; - LLInventoryModel::item_array_t items; - - gInventory.collectDescendents(obj_id, categories, items, false); - - for (LLInventoryModel::item_array_t::value_type& item : items) - { - if (get_is_item_worn(item)) - { - worn.push_back(item->getUUID()); - cat_has_worn = true; - } - } - if (cat_has_worn) - { - cat_deletion_list.push_back(obj_id); - } - } - LLViewerInventoryItem* item = gInventory.getItem(obj_id); - if (item && get_is_item_worn(item)) - { - worn.push_back(obj_id); - item_deletion_list.push_back(obj_id); - } - } - } - - // removeSelectedItems will check if items are worn before deletion, - // don't 'unwear' yet to prevent race conditions from unwearing - // and removing simultaneously - folder_root->removeSelectedItems(); - - // unwear then delete the rest - if (!worn.empty()) - { - // should fire once after every item gets detached - LLAppearanceMgr::instance().removeItemsFromAvatar(worn, - [item_deletion_list, cat_deletion_list]() - { - for (const LLUUID& id : item_deletion_list) - { - remove_inventory_item(id, NULL); - } - for (const LLUUID& id : cat_deletion_list) - { - remove_inventory_category(id, NULL); - } - }); - } - - // Update the marketplace listings that have been affected by the operation - updateMarketplaceFolders(); - } -} - -void LLInventoryAction::buildMarketplaceFolders(LLFolderView* root) -{ - // Make a list of all marketplace folders containing the elements in the selected list - // as well as the elements themselves. - // Once those elements are updated (cut, delete in particular but potentially any action), their - // containing folder will need to be updated as well as their initially containing folder. For - // instance, moving a stock folder from a listed folder to another will require an update of the - // target listing *and* the original listing. So we need to keep track of both. - // Note: do not however put the marketplace listings root itself in this list or the whole marketplace data will be rebuilt. - sMarketplaceFolders.clear(); - const LLUUID &marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - if (marketplacelistings_id.isNull()) - { - return; - } - - std::set selected_items = root->getSelectionList(); - std::set::iterator set_iter = selected_items.begin(); - LLFolderViewModelItemInventory * viewModel = NULL; - for (; set_iter != selected_items.end(); ++set_iter) - { - viewModel = dynamic_cast((*set_iter)->getViewModelItem()); - if (!viewModel || !viewModel->getInventoryObject()) continue; - if (gInventory.isObjectDescendentOf(viewModel->getInventoryObject()->getParentUUID(), marketplacelistings_id)) - { - const LLUUID &parent_id = viewModel->getInventoryObject()->getParentUUID(); - if (parent_id != marketplacelistings_id) - { - sMarketplaceFolders.push_back(parent_id); - } - const LLUUID &curr_id = viewModel->getInventoryObject()->getUUID(); - if (curr_id != marketplacelistings_id) - { - sMarketplaceFolders.push_back(curr_id); - } - } - } - // Suppress dupes in the list so we won't update listings twice - sMarketplaceFolders.sort(); - sMarketplaceFolders.unique(); -} - -void LLInventoryAction::updateMarketplaceFolders() -{ - while (!sMarketplaceFolders.empty()) - { - update_marketplace_category(sMarketplaceFolders.back()); - sMarketplaceFolders.pop_back(); - } -} - - +/** + * @file llinventoryfunctions.cpp + * @brief Implementation of the inventory view and associated stuff. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include // for std::pair<> + +#include "llinventoryfunctions.h" + +// library includes +#include "llagent.h" +#include "llagentwearables.h" +#include "llcallingcard.h" +#include "llfloaterreg.h" +#include "llinventorydefines.h" +#include "llsdserialize.h" +#include "llfiltereditor.h" +#include "llspinctrl.h" +#include "llui.h" +#include "message.h" + +// newview includes +#include "llappearancemgr.h" +#include "llappviewer.h" +#include "llavataractions.h" +#include "llavatarnamecache.h" +#include "llclipboard.h" +#include "lldirpicker.h" +#include "lldonotdisturbnotificationstorage.h" +#include "llfloatermarketplacelistings.h" +#include "llfloatersidepanelcontainer.h" +#include "llfocusmgr.h" +#include "llfolderview.h" +#include "llgesturemgr.h" +#include "llgiveinventory.h" +#include "lliconctrl.h" +#include "llimview.h" +#include "llinventorybridge.h" +#include "llinventorymodel.h" +#include "llinventorypanel.h" +#include "lllineeditor.h" +#include "llmarketplacenotifications.h" +#include "llmarketplacefunctions.h" +#include "llmenugl.h" +#include "llnotificationsutil.h" +#include "llpanelmaininventory.h" +#include "llpreviewanim.h" +#include "llpreviewgesture.h" +#include "llpreviewnotecard.h" +#include "llpreviewscript.h" +#include "llpreviewsound.h" +#include "llpreviewtexture.h" +#include "llresmgr.h" +#include "llscrollbar.h" +#include "llscrollcontainer.h" +#include "llselectmgr.h" +#include "llsidepanelinventory.h" +#include "lltabcontainer.h" +#include "lltooldraganddrop.h" +#include "lltrans.h" +#include "lluictrlfactory.h" +#include "llviewermenu.h" +#include "llviewermessage.h" +#include "llviewerfoldertype.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" +#include "llvoavatarself.h" +#include "llwearablelist.h" + +bool LLInventoryState::sWearNewClothing = false; +LLUUID LLInventoryState::sWearNewClothingTransactionID; +std::list LLInventoryAction::sMarketplaceFolders; +bool LLInventoryAction::sDeleteConfirmationDisplayed = false; + +// Helper function : callback to update a folder after inventory action happened in the background +void update_folder_cb(const LLUUID& dest_folder) +{ + LLViewerInventoryCategory* dest_cat = gInventory.getCategory(dest_folder); + gInventory.updateCategory(dest_cat); + gInventory.notifyObservers(); +} + +// Helper function : Count only the copyable items, i.e. skip the stock items (which are no copy) +S32 count_copyable_items(LLInventoryModel::item_array_t& items) +{ + S32 count = 0; + for (LLInventoryModel::item_array_t::const_iterator it = items.begin(); it != items.end(); ++it) + { + LLViewerInventoryItem* item = *it; + if (item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) + { + count++; + } + } + return count; +} + +// Helper function : Count only the non-copyable items, i.e. the stock items, skip the others +S32 count_stock_items(LLInventoryModel::item_array_t& items) +{ + S32 count = 0; + for (LLInventoryModel::item_array_t::const_iterator it = items.begin(); it != items.end(); ++it) + { + LLViewerInventoryItem* item = *it; + if (!item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) + { + count++; + } + } + return count; +} + +// Helper function : Count the number of stock folders +S32 count_stock_folders(LLInventoryModel::cat_array_t& categories) +{ + S32 count = 0; + for (LLInventoryModel::cat_array_t::const_iterator it = categories.begin(); it != categories.end(); ++it) + { + LLInventoryCategory* cat = *it; + if (cat->getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK) + { + count++; + } + } + return count; +} + +// Helper funtion : Count the number of items (not folders) in the descending hierarchy +S32 count_descendants_items(const LLUUID& cat_id) +{ + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(cat_id,cat_array,item_array); + + S32 count = item_array->size(); + + LLInventoryModel::cat_array_t cat_array_copy = *cat_array; + for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) + { + LLViewerInventoryCategory* category = *iter; + count += count_descendants_items(category->getUUID()); + } + + return count; +} + +// Helper function : Returns true if the hierarchy contains nocopy items +bool contains_nocopy_items(const LLUUID& id) +{ + LLInventoryCategory* cat = gInventory.getCategory(id); + + if (cat) + { + // Get the content + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(id,cat_array,item_array); + + // Check all the items: returns true upon encountering a nocopy item + for (LLInventoryModel::item_array_t::iterator iter = item_array->begin(); iter != item_array->end(); iter++) + { + LLInventoryItem* item = *iter; + LLViewerInventoryItem * inv_item = (LLViewerInventoryItem *) item; + if (!inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) + { + return true; + } + } + + // Check all the sub folders recursively + for (LLInventoryModel::cat_array_t::iterator iter = cat_array->begin(); iter != cat_array->end(); iter++) + { + LLViewerInventoryCategory* cat = *iter; + if (contains_nocopy_items(cat->getUUID())) + { + return true; + } + } + } + else + { + LLInventoryItem* item = gInventory.getItem(id); + LLViewerInventoryItem * inv_item = (LLViewerInventoryItem *) item; + if (!inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) + { + return true; + } + } + + // Exit without meeting a nocopy item + return false; +} + +// Generates a string containing the path to the item specified by id. +void append_path(const LLUUID& id, std::string& path) +{ + std::string temp; + const LLInventoryObject* obj = gInventory.getObject(id); + LLUUID parent_id; + if(obj) parent_id = obj->getParentUUID(); + std::string forward_slash("/"); + while(obj) + { + obj = gInventory.getCategory(parent_id); + if(obj) + { + temp.assign(forward_slash + obj->getName() + temp); + parent_id = obj->getParentUUID(); + } + } + path.append(temp); +} + +// Generates a string containing the path name of the object. +std::string make_path(const LLInventoryObject* object) +{ + std::string path; + append_path(object->getUUID(), path); + return path + "/" + object->getName(); +} + +// Generates a string containing the path name of the object specified by id. +std::string make_inventory_path(const LLUUID& id) +{ + if (LLInventoryObject* object = gInventory.getObject(id)) + return make_path(object); + return ""; +} + +// Generates a string containing the path name and id of the object. +std::string make_info(const LLInventoryObject* object) +{ + return "'" + make_path(object) + "' (" + object->getUUID().asString() + ")"; +} + +// Generates a string containing the path name and id of the object specified by id. +std::string make_inventory_info(const LLUUID& id) +{ + if (LLInventoryObject* object = gInventory.getObject(id)) + return make_info(object); + return " (" + id.asString() + ")"; +} + +void update_marketplace_folder_hierarchy(const LLUUID cat_id) +{ + // When changing the marketplace status of a folder, the only thing that needs to happen is + // for all observers of the folder to, possibly, change the display label of the folder + // so that's the only thing we change on the update mask. + gInventory.addChangedMask(LLInventoryObserver::LABEL, cat_id); + + // Update all descendent folders down + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(cat_id,cat_array,item_array); + + LLInventoryModel::cat_array_t cat_array_copy = *cat_array; + for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) + { + LLInventoryCategory* category = *iter; + update_marketplace_folder_hierarchy(category->getUUID()); + } + return; +} + +void update_marketplace_category(const LLUUID& cur_uuid, bool perform_consistency_enforcement, bool skip_clear_listing) +{ + // When changing the marketplace status of an item, we usually have to change the status of all + // folders in the same listing. This is because the display of each folder is affected by the + // overall status of the whole listing. + // Consequently, the only way to correctly update an item anywhere in the marketplace is to + // update the whole listing from its listing root. + // This is not as bad as it seems as we only update folders, not items, and the folder nesting depth + // is limited to 4. + // We also take care of degenerated cases so we don't update all folders in the inventory by mistake. + + if (cur_uuid.isNull() + || gInventory.getCategory(cur_uuid) == NULL + || gInventory.getCategory(cur_uuid)->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) + { + return; + } + + // Grab marketplace listing data for this item + S32 depth = depth_nesting_in_marketplace(cur_uuid); + if (depth > 0) + { + // Retrieve the listing uuid this object is in + LLUUID listing_uuid = nested_parent_id(cur_uuid, depth); + LLViewerInventoryCategory* listing_cat = gInventory.getCategory(listing_uuid); + bool listing_cat_loaded = listing_cat != NULL && listing_cat->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN; + + // Verify marketplace data consistency for this listing + if (perform_consistency_enforcement + && listing_cat_loaded + && LLMarketplaceData::instance().isListed(listing_uuid)) + { + LLUUID version_folder_uuid = LLMarketplaceData::instance().getVersionFolder(listing_uuid); + S32 version_depth = depth_nesting_in_marketplace(version_folder_uuid); + if (version_folder_uuid.notNull() && (!gInventory.isObjectDescendentOf(version_folder_uuid, listing_uuid) || (version_depth != 2))) + { + LL_INFOS("SLM") << "Unlist and clear version folder as the version folder is not at the right place anymore!!" << LL_ENDL; + LLMarketplaceData::instance().setVersionFolder(listing_uuid, LLUUID::null,1); + } + else if (version_folder_uuid.notNull() + && gInventory.isCategoryComplete(version_folder_uuid) + && LLMarketplaceData::instance().getActivationState(version_folder_uuid) + && (count_descendants_items(version_folder_uuid) == 0) + && !LLMarketplaceData::instance().isUpdating(version_folder_uuid,version_depth)) + { + LL_INFOS("SLM") << "Unlist as the version folder is empty of any item!!" << LL_ENDL; + LLNotificationsUtil::add("AlertMerchantVersionFolderEmpty"); + LLMarketplaceData::instance().activateListing(listing_uuid, false,1); + } + } + + // Check if the count on hand needs to be updated on SLM + if (perform_consistency_enforcement + && listing_cat_loaded + && (compute_stock_count(listing_uuid) != LLMarketplaceData::instance().getCountOnHand(listing_uuid))) + { + LLMarketplaceData::instance().updateCountOnHand(listing_uuid,1); + } + // Update all descendents starting from the listing root + update_marketplace_folder_hierarchy(listing_uuid); + } + else if (depth == 0) + { + // If this is the marketplace listings root itself, update all descendents + if (gInventory.getCategory(cur_uuid)) + { + update_marketplace_folder_hierarchy(cur_uuid); + } + } + else + { + // If the folder is outside the marketplace listings root, clear its SLM data if needs be + if (perform_consistency_enforcement && !skip_clear_listing && LLMarketplaceData::instance().isListed(cur_uuid)) + { + LL_INFOS("SLM") << "Disassociate as the listing folder is not under the marketplace folder anymore!!" << LL_ENDL; + LLMarketplaceData::instance().clearListing(cur_uuid); + } + // Update all descendents if this is a category + if (gInventory.getCategory(cur_uuid)) + { + update_marketplace_folder_hierarchy(cur_uuid); + } + } + + return; +} + +// Iterate through the marketplace and flag for label change all categories that countain a stock folder (i.e. stock folders and embedding folders up the hierarchy) +void update_all_marketplace_count(const LLUUID& cat_id) +{ + // Get all descendent folders down + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(cat_id,cat_array,item_array); + + LLInventoryModel::cat_array_t cat_array_copy = *cat_array; + for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) + { + LLInventoryCategory* category = *iter; + if (category->getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK) + { + // Listing containing stock folders needs to be updated but not others + // Note: we take advantage of the fact that stock folder *do not* contain sub folders to avoid a recursive call here + update_marketplace_category(category->getUUID()); + } + else + { + // Explore the contained folders recursively + update_all_marketplace_count(category->getUUID()); + } + } +} + +void update_all_marketplace_count() +{ + // Get the marketplace root and launch the recursive exploration + const LLUUID marketplace_listings_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + if (!marketplace_listings_uuid.isNull()) + { + update_all_marketplace_count(marketplace_listings_uuid); + } + return; +} + +void rename_category(LLInventoryModel* model, const LLUUID& cat_id, const std::string& new_name) +{ + LLViewerInventoryCategory* cat; + + if (!model || + !get_is_category_renameable(model, cat_id) || + (cat = model->getCategory(cat_id)) == NULL || + cat->getName() == new_name) + { + return; + } + + LLSD updates; + updates["name"] = new_name; + update_inventory_category(cat_id, updates, NULL); +} + +void copy_inventory_category(LLInventoryModel* model, + LLViewerInventoryCategory* cat, + const LLUUID& parent_id, + const LLUUID& root_copy_id, + bool move_no_copy_items) +{ + // Create the initial folder + inventory_func_type func = [model, cat, root_copy_id, move_no_copy_items](const LLUUID& new_id) + { + copy_inventory_category_content(new_id, model, cat, root_copy_id, move_no_copy_items); + }; + gInventory.createNewCategory(parent_id, LLFolderType::FT_NONE, cat->getName(), func, cat->getThumbnailUUID()); +} + +void copy_inventory_category(LLInventoryModel* model, + LLViewerInventoryCategory* cat, + const LLUUID& parent_id, + const LLUUID& root_copy_id, + bool move_no_copy_items, + inventory_func_type callback) +{ + // Create the initial folder + inventory_func_type func = [model, cat, root_copy_id, move_no_copy_items, callback](const LLUUID &new_id) + { + copy_inventory_category_content(new_id, model, cat, root_copy_id, move_no_copy_items); + if (callback) + { + callback(new_id); + } + }; + gInventory.createNewCategory(parent_id, LLFolderType::FT_NONE, cat->getName(), func, cat->getThumbnailUUID()); +} + +void copy_cb(const LLUUID& dest_folder, const LLUUID& root_id) +{ + // Decrement the count in root_id since that one item won't be copied over + LLMarketplaceData::instance().decrementValidationWaiting(root_id); + update_folder_cb(dest_folder); +}; + +void copy_inventory_category_content(const LLUUID& new_cat_uuid, LLInventoryModel* model, LLViewerInventoryCategory* cat, const LLUUID& root_copy_id, bool move_no_copy_items) +{ + model->notifyObservers(); + + // We need to exclude the initial root of the copy to avoid recursively copying the copy, etc... + LLUUID root_id = (root_copy_id.isNull() ? new_cat_uuid : root_copy_id); + + // Get the content of the folder + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(cat->getUUID(), cat_array, item_array); + + // If root_copy_id is null, tell the marketplace model we'll be waiting for new items to be copied over for this folder + if (root_copy_id.isNull()) + { + LLMarketplaceData::instance().setValidationWaiting(root_id, count_descendants_items(cat->getUUID())); + } + + LLPointer cb; + if (root_copy_id.isNull()) + { + cb = new LLBoostFuncInventoryCallback(boost::bind(copy_cb, new_cat_uuid, root_id)); + } + else + { + cb = new LLBoostFuncInventoryCallback(boost::bind(update_folder_cb, new_cat_uuid)); + } + + // Copy all the items + LLInventoryModel::item_array_t item_array_copy = *item_array; + for (LLInventoryModel::item_array_t::iterator iter = item_array_copy.begin(); iter != item_array_copy.end(); iter++) + { + LLInventoryItem* item = *iter; + + if (item->getIsLinkType()) + { + link_inventory_object(new_cat_uuid, item->getLinkedUUID(), cb); + } + else if (!item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) + { + // If the item is nocopy, we do nothing or, optionally, move it + if (move_no_copy_items) + { + // Reparent the item + LLViewerInventoryItem * viewer_inv_item = (LLViewerInventoryItem *)item; + gInventory.changeItemParent(viewer_inv_item, new_cat_uuid, true); + } + if (root_copy_id.isNull()) + { + // Decrement the count in root_id since that one item won't be copied over + LLMarketplaceData::instance().decrementValidationWaiting(root_id); + } + } + else + { + copy_inventory_item( + gAgent.getID(), + item->getPermissions().getOwner(), + item->getUUID(), + new_cat_uuid, + std::string(), + cb); + } + } + + // Copy all the folders + LLInventoryModel::cat_array_t cat_array_copy = *cat_array; + for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) + { + LLViewerInventoryCategory* category = *iter; + if (category->getUUID() != root_id) + { + copy_inventory_category(model, category, new_cat_uuid, root_id, move_no_copy_items); + } + } +} + +class LLInventoryCollectAllItems : public LLInventoryCollectFunctor +{ +public: + virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) + { + return true; + } +}; + +bool get_is_parent_to_worn_item(const LLUUID& id) +{ + const LLViewerInventoryCategory* cat = gInventory.getCategory(id); + if (!cat) + { + return false; + } + + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLInventoryCollectAllItems collect_all; + gInventory.collectDescendentsIf(LLAppearanceMgr::instance().getCOF(), cats, items, LLInventoryModel::EXCLUDE_TRASH, collect_all); + + for (LLInventoryModel::item_array_t::const_iterator it = items.begin(); it != items.end(); ++it) + { + const LLViewerInventoryItem * const item = *it; + + llassert(item->getIsLinkType()); + + LLUUID linked_id = item->getLinkedUUID(); + const LLViewerInventoryItem * const linked_item = gInventory.getItem(linked_id); + + if (linked_item) + { + LLUUID parent_id = linked_item->getParentUUID(); + + while (!parent_id.isNull()) + { + LLInventoryCategory * parent_cat = gInventory.getCategory(parent_id); + + if (cat == parent_cat) + { + return true; + } + + parent_id = parent_cat->getParentUUID(); + } + } + } + + return false; +} + +bool get_is_item_worn(const LLUUID& id, const LLViewerInventoryItem* item) +{ + if (!item) + return false; + + if (item->getIsLinkType() && !gInventory.getItem(item->getLinkedUUID())) + { + return false; + } + + // Consider the item as worn if it has links in COF. + if (LLAppearanceMgr::instance().isLinkedInCOF(id)) + { + return true; + } + + switch(item->getType()) + { + case LLAssetType::AT_OBJECT: + { + if (isAgentAvatarValid() && gAgentAvatarp->isWearingAttachment(item->getLinkedUUID())) + return true; + break; + } + case LLAssetType::AT_BODYPART: + case LLAssetType::AT_CLOTHING: + if(gAgentWearables.isWearingItem(item->getLinkedUUID())) + return true; + break; + case LLAssetType::AT_GESTURE: + if (LLGestureMgr::instance().isGestureActive(item->getLinkedUUID())) + return true; + break; + default: + break; + } + return false; +} + +bool get_is_item_worn(const LLUUID& id) +{ + const LLViewerInventoryItem* item = gInventory.getItem(id); + return get_is_item_worn(id, item); +} + +bool get_is_item_worn(const LLViewerInventoryItem* item) +{ + if (!item) + { + return false; + } + return get_is_item_worn(item->getUUID(), item); +} + +bool get_can_item_be_worn(const LLUUID& id) +{ + const LLViewerInventoryItem* item = gInventory.getItem(id); + if (!item) + return false; + + if (LLAppearanceMgr::instance().isLinkedInCOF(item->getLinkedUUID())) + { + // an item having links in COF (i.e. a worn item) + return false; + } + + if (gInventory.isObjectDescendentOf(id, LLAppearanceMgr::instance().getCOF())) + { + // a non-link object in COF (should not normally happen) + return false; + } + + const LLUUID trash_id = gInventory.findCategoryUUIDForType( + LLFolderType::FT_TRASH); + + // item can't be worn if base obj in trash, see EXT-7015 + if (gInventory.isObjectDescendentOf(item->getLinkedUUID(), + trash_id)) + { + return false; + } + + switch(item->getType()) + { + case LLAssetType::AT_OBJECT: + { + if (isAgentAvatarValid() && gAgentAvatarp->isWearingAttachment(item->getLinkedUUID())) + { + // Already being worn + return false; + } + else + { + // Not being worn yet. + return true; + } + break; + } + case LLAssetType::AT_BODYPART: + case LLAssetType::AT_CLOTHING: + if(gAgentWearables.isWearingItem(item->getLinkedUUID())) + { + // Already being worn + return false; + } + else + { + // Not being worn yet. + return true; + } + break; + default: + break; + } + return false; +} + +bool get_is_item_removable(const LLInventoryModel* model, const LLUUID& id, bool check_worn) +{ + if (!model) + { + return false; + } + + // Can't delete an item that's in the library. + if (!model->isObjectDescendentOf(id, gInventory.getRootFolderID())) + { + return false; + } + + // Disable delete from COF folder; have users explicitly choose "detach/take off", + // unless the item is not worn but in the COF (i.e. is bugged). + const LLViewerInventoryItem* obj = model->getItem(id); + if (LLAppearanceMgr::instance().getIsProtectedCOFItem(obj)) + { + if (get_is_item_worn(id, obj)) + { + return false; + } + } + + if (obj && obj->getIsLinkType()) + { + return true; + } + if (check_worn && get_is_item_worn(id, obj)) + { + return false; + } + return true; +} + +bool get_is_item_editable(const LLUUID& inv_item_id) +{ + if (const LLInventoryItem* inv_item = gInventory.getLinkedItem(inv_item_id)) + { + switch (inv_item->getType()) + { + case LLAssetType::AT_BODYPART: + case LLAssetType::AT_CLOTHING: + return gAgentWearables.isWearableModifiable(inv_item_id); + case LLAssetType::AT_OBJECT: + return true; + default: + return false;; + } + } + return gAgentAvatarp->getWornAttachment(inv_item_id) != nullptr; +} + +void handle_item_edit(const LLUUID& inv_item_id) +{ + if (get_is_item_editable(inv_item_id)) + { + if (const LLInventoryItem* inv_item = gInventory.getLinkedItem(inv_item_id)) + { + switch (inv_item->getType()) + { + case LLAssetType::AT_BODYPART: + case LLAssetType::AT_CLOTHING: + LLAgentWearables::editWearable(inv_item_id); + break; + case LLAssetType::AT_OBJECT: + handle_attachment_edit(inv_item_id); + break; + default: + break; + } + } + else + { + handle_attachment_edit(inv_item_id); + } + } +} + +bool get_is_category_removable(const LLInventoryModel* model, const LLUUID& id) +{ + // NOTE: This function doesn't check the folder's children. + // See LLFolderBridge::isItemRemovable for a function that does + // consider the children. + + if (!model) + { + return false; + } + + if (!model->isObjectDescendentOf(id, gInventory.getRootFolderID())) + { + return false; + } + + if (!isAgentAvatarValid()) return false; + + const LLInventoryCategory* category = model->getCategory(id); + if (!category) + { + return false; + } + + const LLFolderType::EType folder_type = category->getPreferredType(); + + if (LLFolderType::lookupIsProtectedType(folder_type)) + { + return false; + } + + // Can't delete the outfit that is currently being worn. + if (folder_type == LLFolderType::FT_OUTFIT) + { + const LLViewerInventoryItem *base_outfit_link = LLAppearanceMgr::instance().getBaseOutfitLink(); + if (base_outfit_link && (category == base_outfit_link->getLinkedCategory())) + { + return false; + } + } + + return true; +} + +bool get_is_category_and_children_removable(LLInventoryModel* model, const LLUUID& folder_id, bool check_worn) +{ + if (!get_is_category_removable(model, folder_id)) + { + return false; + } + + const LLUUID mp_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + if (mp_id.notNull() && gInventory.isObjectDescendentOf(folder_id, mp_id)) + { + return false; + } + + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + model->collectDescendents( + folder_id, + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH); + + if (check_worn) + { + for (LLInventoryModel::item_array_t::value_type& item : item_array) + { + // Disable delete/cut from COF folder; have users explicitly choose "detach/take off", + // unless the item is not worn but in the COF (i.e. is bugged). + if (item) + { + if (LLAppearanceMgr::instance().getIsProtectedCOFItem(item)) + { + if (get_is_item_worn(item)) + { + return false; + } + } + + if (!item->getIsLinkType() && get_is_item_worn(item)) + { + return false; + } + } + } + } + + const LLViewerInventoryItem* base_outfit_link = LLAppearanceMgr::instance().getBaseOutfitLink(); + LLViewerInventoryCategory* outfit_linked_category = base_outfit_link ? base_outfit_link->getLinkedCategory() : nullptr; + for (LLInventoryModel::cat_array_t::value_type& cat : cat_array) + { + const LLFolderType::EType folder_type = cat->getPreferredType(); + if (LLFolderType::lookupIsProtectedType(folder_type)) + { + return false; + } + + // Can't delete the outfit that is currently being worn. + if (folder_type == LLFolderType::FT_OUTFIT) + { + if (cat == outfit_linked_category) + { + return false; + } + } + } + + return true; +} + +bool get_is_category_renameable(const LLInventoryModel* model, const LLUUID& id) +{ + if (!model) + { + return false; + } + + LLViewerInventoryCategory* cat = model->getCategory(id); + + if (cat && !LLFolderType::lookupIsProtectedType(cat->getPreferredType()) && + cat->getOwnerID() == gAgent.getID()) + { + return true; + } + return false; +} + +void show_task_item_profile(const LLUUID& item_uuid, const LLUUID& object_id) +{ + LLSD params; + params["id"] = item_uuid; + params["object"] = object_id; + + LLFloaterReg::showInstance("item_properties", params); +} + +void show_item_profile(const LLUUID& item_uuid) +{ + LLUUID linked_uuid = gInventory.getLinkedItemID(item_uuid); + LLFloaterReg::showInstance("item_properties", LLSD().with("id", linked_uuid)); +} + +void show_item_original(const LLUUID& item_uuid) +{ + static LLUICachedControl find_original_new_floater("FindOriginalOpenWindow", false); + + //show in a new single-folder window + if(find_original_new_floater) + { + const LLUUID& linked_item_uuid = gInventory.getLinkedItemID(item_uuid); + const LLInventoryObject *obj = gInventory.getObject(linked_item_uuid); + if (obj && obj->getParentUUID().notNull()) + { + LLPanelMainInventory::newFolderWindow(obj->getParentUUID(), linked_item_uuid); + } + } + //show in main Inventory + else + { + LLFloater* floater_inventory = LLFloaterReg::getInstance("inventory"); + if (!floater_inventory) + { + LL_WARNS() << "Could not find My Inventory floater" << LL_ENDL; + return; + } + LLSidepanelInventory *sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); + if (sidepanel_inventory) + { + LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); + if (main_inventory) + { + if(main_inventory->isSingleFolderMode()) + { + main_inventory->toggleViewMode(); + } + main_inventory->resetAllItemsFilters(); + } + reset_inventory_filter(); + + if (!LLFloaterReg::getTypedInstance("inventory")->isInVisibleChain()) + { + LLFloaterReg::toggleInstanceOrBringToFront("inventory"); + } + + const LLUUID inbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX); + if (gInventory.isObjectDescendentOf(gInventory.getLinkedItemID(item_uuid), inbox_id)) + { + if (sidepanel_inventory->getInboxPanel()) + { + sidepanel_inventory->openInbox(); + sidepanel_inventory->getInboxPanel()->setSelection(gInventory.getLinkedItemID(item_uuid), TAKE_FOCUS_YES); + } + } + else + { + sidepanel_inventory->selectAllItemsPanel(); + if (sidepanel_inventory->getActivePanel()) + { + sidepanel_inventory->getActivePanel()->setSelection(gInventory.getLinkedItemID(item_uuid), TAKE_FOCUS_YES); + } + } + } + } +} + + +void reset_inventory_filter() +{ + LLSidepanelInventory *sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); + if (sidepanel_inventory) + { + LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); + if (main_inventory) + { + main_inventory->onFilterEdit(""); + } + } +} + +void open_marketplace_listings() +{ + LLFloaterReg::showInstance("marketplace_listings"); +} + +///---------------------------------------------------------------------------- +// Marketplace functions +// +// Handles Copy and Move to or within the Marketplace listings folder. +// Handles creation of stock folders, nesting of listings and version folders, +// permission checking and listings validation. +///---------------------------------------------------------------------------- + +S32 depth_nesting_in_marketplace(LLUUID cur_uuid) +{ + // Get the marketplace listings root, exit with -1 (i.e. not under the marketplace listings root) if none + // Todo: findCategoryUUIDForType is somewhat expensive with large + // flat root folders yet we use depth_nesting_in_marketplace at + // every turn, find a way to correctly cache this id. + const LLUUID marketplace_listings_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + if (marketplace_listings_uuid.isNull()) + { + return -1; + } + // If not a descendant of the marketplace listings root, then the nesting depth is -1 by definition + if (!gInventory.isObjectDescendentOf(cur_uuid, marketplace_listings_uuid)) + { + return -1; + } + + // Iterate through the parents till we hit the marketplace listings root + // Note that the marketplace listings root itself will return 0 + S32 depth = 0; + LLInventoryObject* cur_object = gInventory.getObject(cur_uuid); + while (cur_uuid != marketplace_listings_uuid) + { + depth++; + cur_uuid = cur_object->getParentUUID(); + cur_object = gInventory.getCategory(cur_uuid); + } + return depth; +} + +// Returns the UUID of the marketplace listing this object is in +LLUUID nested_parent_id(LLUUID cur_uuid, S32 depth) +{ + if (depth < 1) + { + // For objects outside the marketplace listings root (or root itself), we return a NULL UUID + return LLUUID::null; + } + else if (depth == 1) + { + // Just under the root, we return the passed UUID itself if it's a folder, NULL otherwise (not a listing) + LLViewerInventoryCategory* cat = gInventory.getCategory(cur_uuid); + return (cat ? cur_uuid : LLUUID::null); + } + + // depth > 1 + LLInventoryObject* cur_object = gInventory.getObject(cur_uuid); + while (depth > 1) + { + depth--; + cur_uuid = cur_object->getParentUUID(); + cur_object = gInventory.getCategory(cur_uuid); + } + return cur_uuid; +} + +S32 compute_stock_count(LLUUID cat_uuid, bool force_count /* false */) +{ + // Handle the case of the folder being a stock folder immediately + LLViewerInventoryCategory* cat = gInventory.getCategory(cat_uuid); + if (!cat) + { + // Not a category so no stock count to speak of + return COMPUTE_STOCK_INFINITE; + } + if (cat->getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK) + { + if (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) + { + // If the folder is not completely fetched, we do not want to return any confusing value that could lead to unlisting + // "COMPUTE_STOCK_NOT_EVALUATED" denotes that a stock folder has a count that cannot be evaluated at this time (folder not up to date) + return COMPUTE_STOCK_NOT_EVALUATED; + } + // Note: stock folders are *not* supposed to have nested subfolders so we stop recursion here but we count only items (subfolders will be ignored) + // Note: we *always* give a stock count for stock folders, it's useful even if the listing is unassociated + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(cat_uuid,cat_array,item_array); + return item_array->size(); + } + + // When force_count is true, we do not do any verification of the marketplace status and simply compute + // the stock amount based on the descendent hierarchy. This is used specifically when creating a listing. + if (!force_count) + { + // Grab marketplace data for this folder + S32 depth = depth_nesting_in_marketplace(cat_uuid); + LLUUID listing_uuid = nested_parent_id(cat_uuid, depth); + if (!LLMarketplaceData::instance().isListed(listing_uuid)) + { + // If not listed, the notion of stock is meaningless so it won't be computed for any level + return COMPUTE_STOCK_INFINITE; + } + + LLUUID version_folder_uuid = LLMarketplaceData::instance().getVersionFolder(listing_uuid); + // Handle the case of the first 2 levels : listing and version folders + if (depth == 1) + { + if (version_folder_uuid.notNull()) + { + // If there is a version folder, the stock value for the listing is the version folder stock + return compute_stock_count(version_folder_uuid, true); + } + else + { + // If there's no version folder associated, the notion of stock count has no meaning + return COMPUTE_STOCK_INFINITE; + } + } + else if (depth == 2) + { + if (version_folder_uuid.notNull() && (version_folder_uuid != cat_uuid)) + { + // If there is a version folder but we're not it, our stock count is meaningless + return COMPUTE_STOCK_INFINITE; + } + } + } + + // In all other cases, the stock count is the min of stock folders count found in the descendents + // "COMPUTE_STOCK_NOT_EVALUATED" denotes that a stock folder in the hierarchy has a count that cannot be evaluated at this time (folder not up to date) + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(cat_uuid,cat_array,item_array); + + // "COMPUTE_STOCK_INFINITE" denotes a folder that doesn't countain any stock folders in its descendents + S32 curr_count = COMPUTE_STOCK_INFINITE; + + // Note: marketplace listings have a maximum depth nesting of 4 + LLInventoryModel::cat_array_t cat_array_copy = *cat_array; + for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) + { + LLInventoryCategory* category = *iter; + S32 count = compute_stock_count(category->getUUID(), true); + if ((curr_count == COMPUTE_STOCK_INFINITE) || ((count != COMPUTE_STOCK_INFINITE) && (count < curr_count))) + { + curr_count = count; + } + } + + return curr_count; +} + +// local helper +bool can_move_to_marketplace(LLInventoryItem* inv_item, std::string& tooltip_msg, bool resolve_links) +{ + // Collapse links directly to items/folders + LLViewerInventoryItem * viewer_inv_item = (LLViewerInventoryItem *) inv_item; + LLViewerInventoryItem * linked_item = viewer_inv_item->getLinkedItem(); + LLViewerInventoryCategory * linked_category = viewer_inv_item->getLinkedCategory(); + + // Linked items and folders cannot be put for sale + if (linked_category || linked_item) + { + tooltip_msg = LLTrans::getString("TooltipOutboxLinked"); + return false; + } + + // A category is always considered as passing... + if (linked_category != NULL) + { + return true; + } + + // Take the linked item if necessary + if (linked_item != NULL) + { + inv_item = linked_item; + } + + // Check that the agent has transfer permission on the item: this is required as a resident cannot + // put on sale items she cannot transfer. Proceed with move if we have permission. + bool allow_transfer = inv_item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID()); + if (!allow_transfer) + { + tooltip_msg = LLTrans::getString("TooltipOutboxNoTransfer"); + return false; + } + + // Check worn/not worn status: worn items cannot be put on the marketplace + bool worn = get_is_item_worn(inv_item->getUUID()); + if (worn) + { + tooltip_msg = LLTrans::getString("TooltipOutboxWorn"); + return false; + } + + // Check library status: library items cannot be put on the marketplace + if (!gInventory.isObjectDescendentOf(inv_item->getUUID(), gInventory.getRootFolderID())) + { + tooltip_msg = LLTrans::getString("TooltipOutboxNotInInventory"); + return false; + } + + // Check type: for the moment, calling cards cannot be put on the marketplace + bool calling_card = (LLAssetType::AT_CALLINGCARD == inv_item->getType()); + if (calling_card) + { + tooltip_msg = LLTrans::getString("TooltipOutboxCallingCard"); + return false; + } + + return true; +} + +// local helper +// Returns the max tree length (in folder nodes) down from the argument folder +int get_folder_levels(LLInventoryCategory* inv_cat) +{ + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(inv_cat->getUUID(), cats, items); + + int max_child_levels = 0; + + for (S32 i=0; i < cats->size(); ++i) + { + LLInventoryCategory* category = cats->at(i); + max_child_levels = llmax(max_child_levels, get_folder_levels(category)); + } + + return 1 + max_child_levels; +} + +// local helper +// Returns the distance (in folder nodes) between the ancestor and its descendant. Returns -1 if not related. +int get_folder_path_length(const LLUUID& ancestor_id, const LLUUID& descendant_id) +{ + int depth = 0; + + if (ancestor_id == descendant_id) return depth; + + const LLInventoryCategory* category = gInventory.getCategory(descendant_id); + + while (category) + { + LLUUID parent_id = category->getParentUUID(); + + if (parent_id.isNull()) break; + + depth++; + + if (parent_id == ancestor_id) return depth; + + category = gInventory.getCategory(parent_id); + } + + LL_WARNS("SLM") << "get_folder_path_length() couldn't trace a path from the descendant to the ancestor" << LL_ENDL; + return -1; +} + +// local helper +// Returns true if all items within the argument folder are fit for sale, false otherwise +bool has_correct_permissions_for_sale(LLInventoryCategory* cat, std::string& error_msg) +{ + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(cat->getUUID(),cat_array,item_array); + + LLInventoryModel::item_array_t item_array_copy = *item_array; + + for (LLInventoryModel::item_array_t::iterator iter = item_array_copy.begin(); iter != item_array_copy.end(); iter++) + { + LLInventoryItem* item = *iter; + if (!can_move_to_marketplace(item, error_msg, false)) + { + return false; + } + } + + LLInventoryModel::cat_array_t cat_array_copy = *cat_array; + + for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) + { + LLInventoryCategory* category = *iter; + if (!has_correct_permissions_for_sale(category, error_msg)) + { + return false; + } + } + return true; +} + +// Returns true if inv_item can be dropped in dest_folder, a folder nested in marketplace listings (or merchant inventory) under the root_folder root +// If returns is false, tooltip_msg contains an error message to display to the user (localized and all). +// bundle_size is the amount of sibling items that are getting moved to the marketplace at the same time. +bool can_move_item_to_marketplace(const LLInventoryCategory* root_folder, LLInventoryCategory* dest_folder, LLInventoryItem* inv_item, std::string& tooltip_msg, S32 bundle_size, bool from_paste) +{ + // Check stock folder type matches item type in marketplace listings or merchant outbox (even if of no use there for the moment) + LLViewerInventoryCategory* view_folder = dynamic_cast(dest_folder); + bool move_in_stock = (view_folder && (view_folder->getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK)); + bool accept = (view_folder && view_folder->acceptItem(inv_item)); + if (!accept) + { + tooltip_msg = LLTrans::getString("TooltipOutboxMixedStock"); + } + + // Check that the item has the right type and permissions to be sold on the marketplace + if (accept) + { + accept = can_move_to_marketplace(inv_item, tooltip_msg, true); + } + + // Check that the total amount of items won't violate the max limit on the marketplace + if (accept) + { + // If the dest folder is a stock folder, we do not count the incoming items toward the total (stock items are seen as one) + int existing_item_count = (move_in_stock ? 0 : bundle_size); + + // If the dest folder is a stock folder, we do assume that the incoming items are also stock items (they should anyway) + int existing_stock_count = (move_in_stock ? bundle_size : 0); + + int existing_folder_count = 0; + + // Get the version folder: that's where the counts start from + const LLViewerInventoryCategory * version_folder = ((root_folder && (root_folder != dest_folder)) ? gInventory.getFirstDescendantOf(root_folder->getUUID(), dest_folder->getUUID()) : NULL); + + if (version_folder) + { + if (!from_paste && gInventory.isObjectDescendentOf(inv_item->getUUID(), version_folder->getUUID())) + { + // Clear those counts or they will be counted twice because we're already inside the version category + existing_item_count = 0; + } + + LLInventoryModel::cat_array_t existing_categories; + LLInventoryModel::item_array_t existing_items; + + gInventory.collectDescendents(version_folder->getUUID(), existing_categories, existing_items, false); + + existing_item_count += count_copyable_items(existing_items) + count_stock_folders(existing_categories); + existing_stock_count += count_stock_items(existing_items); + existing_folder_count += existing_categories.size(); + + // If the incoming item is a nocopy (stock) item, we need to consider that it will create a stock folder + if (!inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID()) && !move_in_stock) + { + // Note : we do not assume that all incoming items are nocopy of different kinds... + existing_folder_count += 1; + } + } + + if (existing_item_count > gSavedSettings.getU32("InventoryOutboxMaxItemCount")) + { + LLStringUtil::format_map_t args; + U32 amount = gSavedSettings.getU32("InventoryOutboxMaxItemCount"); + args["[AMOUNT]"] = llformat("%d",amount); + tooltip_msg = LLTrans::getString("TooltipOutboxTooManyObjects", args); + accept = false; + } + else if (existing_stock_count > gSavedSettings.getU32("InventoryOutboxMaxStockItemCount")) + { + LLStringUtil::format_map_t args; + U32 amount = gSavedSettings.getU32("InventoryOutboxMaxStockItemCount"); + args["[AMOUNT]"] = llformat("%d",amount); + tooltip_msg = LLTrans::getString("TooltipOutboxTooManyStockItems", args); + accept = false; + } + else if (existing_folder_count > gSavedSettings.getU32("InventoryOutboxMaxFolderCount")) + { + LLStringUtil::format_map_t args; + U32 amount = gSavedSettings.getU32("InventoryOutboxMaxFolderCount"); + args["[AMOUNT]"] = llformat("%d",amount); + tooltip_msg = LLTrans::getString("TooltipOutboxTooManyFolders", args); + accept = false; + } + } + + return accept; +} + +// Returns true if inv_cat can be dropped in dest_folder, a folder nested in marketplace listings (or merchant inventory) under the root_folder root +// If returns is false, tooltip_msg contains an error message to display to the user (localized and all). +// bundle_size is the amount of sibling items that are getting moved to the marketplace at the same time. +bool can_move_folder_to_marketplace(const LLInventoryCategory* root_folder, LLInventoryCategory* dest_folder, LLInventoryCategory* inv_cat, std::string& tooltip_msg, S32 bundle_size, bool check_items, bool from_paste) +{ + bool accept = true; + + // Compute the nested folders level we'll add into with that incoming folder + int incoming_folder_depth = get_folder_levels(inv_cat); + // Compute the nested folders level we're inserting ourselves in + // Note: add 1 when inserting under a listing folder as we need to take the root listing folder in the count + int insertion_point_folder_depth = (root_folder ? get_folder_path_length(root_folder->getUUID(), dest_folder->getUUID()) + 1 : 1); + + // Get the version folder: that's where the folders and items counts start from + const LLViewerInventoryCategory * version_folder = (insertion_point_folder_depth >= 2 ? gInventory.getFirstDescendantOf(root_folder->getUUID(), dest_folder->getUUID()) : NULL); + + // Compare the whole with the nested folders depth limit + // Note: substract 2 as we leave root and version folder out of the count threshold + if ((incoming_folder_depth + insertion_point_folder_depth - 2) > (S32)(gSavedSettings.getU32("InventoryOutboxMaxFolderDepth"))) + { + LLStringUtil::format_map_t args; + U32 amount = gSavedSettings.getU32("InventoryOutboxMaxFolderDepth"); + args["[AMOUNT]"] = llformat("%d",amount); + tooltip_msg = LLTrans::getString("TooltipOutboxFolderLevels", args); + accept = false; + } + + if (accept) + { + LLInventoryModel::cat_array_t descendent_categories; + LLInventoryModel::item_array_t descendent_items; + gInventory.collectDescendents(inv_cat->getUUID(), descendent_categories, descendent_items, false); + + int dragged_folder_count = descendent_categories.size() + bundle_size; // Note: We assume that we're moving a bunch of folders in. That might be wrong... + int dragged_item_count = count_copyable_items(descendent_items) + count_stock_folders(descendent_categories); + int dragged_stock_count = count_stock_items(descendent_items); + int existing_item_count = 0; + int existing_stock_count = 0; + int existing_folder_count = 0; + + if (version_folder) + { + if (!from_paste && gInventory.isObjectDescendentOf(inv_cat->getUUID(), version_folder->getUUID())) + { + // Clear those counts or they will be counted twice because we're already inside the version category + dragged_folder_count = 0; + dragged_item_count = 0; + dragged_stock_count = 0; + } + + // Tally the total number of categories and items inside the root folder + LLInventoryModel::cat_array_t existing_categories; + LLInventoryModel::item_array_t existing_items; + gInventory.collectDescendents(version_folder->getUUID(), existing_categories, existing_items, false); + + existing_folder_count += existing_categories.size(); + existing_item_count += count_copyable_items(existing_items) + count_stock_folders(existing_categories); + existing_stock_count += count_stock_items(existing_items); + } + + const int total_folder_count = existing_folder_count + dragged_folder_count; + const int total_item_count = existing_item_count + dragged_item_count; + const int total_stock_count = existing_stock_count + dragged_stock_count; + + if (total_folder_count > gSavedSettings.getU32("InventoryOutboxMaxFolderCount")) + { + LLStringUtil::format_map_t args; + U32 amount = gSavedSettings.getU32("InventoryOutboxMaxFolderCount"); + args["[AMOUNT]"] = llformat("%d",amount); + tooltip_msg = LLTrans::getString("TooltipOutboxTooManyFolders", args); + accept = false; + } + else if (total_item_count > gSavedSettings.getU32("InventoryOutboxMaxItemCount")) + { + LLStringUtil::format_map_t args; + U32 amount = gSavedSettings.getU32("InventoryOutboxMaxItemCount"); + args["[AMOUNT]"] = llformat("%d",amount); + tooltip_msg = LLTrans::getString("TooltipOutboxTooManyObjects", args); + accept = false; + } + else if (total_stock_count > gSavedSettings.getU32("InventoryOutboxMaxStockItemCount")) + { + LLStringUtil::format_map_t args; + U32 amount = gSavedSettings.getU32("InventoryOutboxMaxStockItemCount"); + args["[AMOUNT]"] = llformat("%d",amount); + tooltip_msg = LLTrans::getString("TooltipOutboxTooManyStockItems", args); + accept = false; + } + + // Now check that each item in the folder can be moved in the marketplace + if (accept && check_items) + { + for (S32 i=0; i < descendent_items.size(); ++i) + { + LLInventoryItem* item = descendent_items[i]; + if (!can_move_to_marketplace(item, tooltip_msg, false)) + { + accept = false; + break; + } + } + } + } + + return accept; +} + +// Can happen asynhroneously!!! +bool move_item_to_marketplacelistings(LLInventoryItem* inv_item, LLUUID dest_folder, bool copy) +{ + // Get the marketplace listings depth of the destination folder, exit with error if not under marketplace + S32 depth = depth_nesting_in_marketplace(dest_folder); + if (depth < 0) + { + LLSD subs; + subs["[ERROR_CODE]"] = LLTrans::getString("Marketplace Error Prefix") + LLTrans::getString("Marketplace Error Not Merchant"); + LLNotificationsUtil::add("MerchantPasteFailed", subs); + return false; + } + + // We will collapse links into items/folders + LLViewerInventoryItem * viewer_inv_item = (LLViewerInventoryItem *) inv_item; + LLViewerInventoryCategory * linked_category = viewer_inv_item->getLinkedCategory(); + + if (linked_category != NULL) + { + // Move the linked folder directly + return move_folder_to_marketplacelistings(linked_category, dest_folder, copy); + } + else + { + // Grab the linked item if any + LLViewerInventoryItem * linked_item = viewer_inv_item->getLinkedItem(); + viewer_inv_item = (linked_item != NULL ? linked_item : viewer_inv_item); + + // If we want to copy but the item is no copy, fail silently (this is a common case that doesn't warrant notification) + if (copy && !viewer_inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) + { + return false; + } + + // Check that the agent has transfer permission on the item: this is required as a resident cannot + // put on sale items she cannot transfer. Proceed with move if we have permission. + std::string error_msg; + if (can_move_to_marketplace(inv_item, error_msg, true)) + { + // When moving an isolated item, we might need to create the folder structure to support it + + LLUUID item_id = inv_item->getUUID(); + std::function callback_create_stock = [copy, item_id](const LLUUID& new_cat_id) + { + if (new_cat_id.isNull()) + { + LL_WARNS() << "Failed to create category" << LL_ENDL; + LLSD subs; + subs["[ERROR_CODE]"] = + LLTrans::getString("Marketplace Error Prefix") + LLTrans::getString("Marketplace Error Not Accepted"); + LLNotificationsUtil::add("MerchantPasteFailed", subs); + return; + } + + // Verify we can have this item in that destination category + LLViewerInventoryCategory* dest_cat = gInventory.getCategory(new_cat_id); + LLViewerInventoryItem * viewer_inv_item = gInventory.getItem(item_id); + if (!dest_cat || !viewer_inv_item) + { + LL_WARNS() << "Move to marketplace: item or folder do not exist" << LL_ENDL; + + LLSD subs; + subs["[ERROR_CODE]"] = + LLTrans::getString("Marketplace Error Prefix") + LLTrans::getString("Marketplace Error Not Accepted"); + LLNotificationsUtil::add("MerchantPasteFailed", subs); + return; + } + if (!dest_cat->acceptItem(viewer_inv_item)) + { + LLSD subs; + subs["[ERROR_CODE]"] = LLTrans::getString("Marketplace Error Prefix") + LLTrans::getString("Marketplace Error Not Accepted"); + LLNotificationsUtil::add("MerchantPasteFailed", subs); + } + + if (copy) + { + // Copy the item + LLPointer cb = new LLBoostFuncInventoryCallback(boost::bind(update_folder_cb, new_cat_id)); + copy_inventory_item( + gAgent.getID(), + viewer_inv_item->getPermissions().getOwner(), + viewer_inv_item->getUUID(), + new_cat_id, + std::string(), + cb); + } + else + { + // Reparent the item + gInventory.changeItemParent(viewer_inv_item, new_cat_id, true); + } + }; + + std::function callback_dest_create = [item_id, callback_create_stock](const LLUUID& new_cat_id) + { + if (new_cat_id.isNull()) + { + LL_WARNS() << "Failed to create category" << LL_ENDL; + LLSD subs; + subs["[ERROR_CODE]"] = + LLTrans::getString("Marketplace Error Prefix") + LLTrans::getString("Marketplace Error Not Accepted"); + LLNotificationsUtil::add("MerchantPasteFailed", subs); + return; + } + + LLViewerInventoryCategory* dest_cat = gInventory.getCategory(new_cat_id); + LLViewerInventoryItem * viewer_inv_item = gInventory.getItem(item_id); + if (!viewer_inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID()) && + (dest_cat->getPreferredType() != LLFolderType::FT_MARKETPLACE_STOCK)) + { + // We need to create a stock folder to move a no copy item + gInventory.createNewCategory(new_cat_id, LLFolderType::FT_MARKETPLACE_STOCK, viewer_inv_item->getName(), callback_create_stock); + } + else + { + callback_create_stock(new_cat_id); + } + }; + + if (depth == 0) + { + // We need a listing folder + gInventory.createNewCategory(dest_folder, + LLFolderType::FT_NONE, + viewer_inv_item->getName(), + [callback_dest_create](const LLUUID &new_cat_id) + { + if (new_cat_id.isNull()) + { + LL_WARNS() << "Failed to create listing folder for marketpace" << LL_ENDL; + return; + } + LLViewerInventoryCategory *dest_cat = gInventory.getCategory(new_cat_id); + if (!dest_cat) + { + LL_WARNS() << "Failed to find freshly created listing folder" << LL_ENDL; + return; + } + // version folder + gInventory.createNewCategory(new_cat_id, + LLFolderType::FT_NONE, + dest_cat->getName(), + callback_dest_create); + }); + } + else if (depth == 1) + { + // We need a version folder + gInventory.createNewCategory(dest_folder, LLFolderType::FT_NONE, viewer_inv_item->getName(), callback_dest_create); + } + else + { + callback_dest_create(dest_folder); + } + } + else + { + LLSD subs; + subs["[ERROR_CODE]"] = LLTrans::getString("Marketplace Error Prefix") + error_msg; + LLNotificationsUtil::add("MerchantPasteFailed", subs); + return false; + } + } + + open_marketplace_listings(); + return true; +} + +bool move_folder_to_marketplacelistings(LLInventoryCategory* inv_cat, const LLUUID& dest_folder, bool copy, bool move_no_copy_items) +{ + // Check that we have adequate permission on all items being moved. Proceed if we do. + std::string error_msg; + if (has_correct_permissions_for_sale(inv_cat, error_msg)) + { + // Get the destination folder + LLViewerInventoryCategory* dest_cat = gInventory.getCategory(dest_folder); + + // Check it's not a stock folder + if (dest_cat->getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK) + { + LLSD subs; + subs["[ERROR_CODE]"] = LLTrans::getString("Marketplace Error Prefix") + LLTrans::getString("Marketplace Error Not Accepted"); + LLNotificationsUtil::add("MerchantPasteFailed", subs); + return false; + } + + // Get the parent folder of the moved item : we may have to update it + LLUUID src_folder = inv_cat->getParentUUID(); + + LLViewerInventoryCategory * viewer_inv_cat = (LLViewerInventoryCategory *) inv_cat; + if (copy) + { + // Copy the folder + copy_inventory_category(&gInventory, viewer_inv_cat, dest_folder, LLUUID::null, move_no_copy_items); + } + else + { + LL_INFOS("SLM") << "Move category " << make_info(viewer_inv_cat) << " to '" << make_inventory_path(dest_folder) << "'" << LL_ENDL; + // Reparent the folder + gInventory.changeCategoryParent(viewer_inv_cat, dest_folder, false); + // Check the destination folder recursively for no copy items and promote the including folders if any + LLMarketplaceValidator::getInstance()->validateMarketplaceListings(dest_folder); + } + + // Update the modified folders + update_marketplace_category(src_folder); + update_marketplace_category(dest_folder); + gInventory.notifyObservers(); + } + else + { + LLSD subs; + subs["[ERROR_CODE]"] = LLTrans::getString("Marketplace Error Prefix") + error_msg; + LLNotificationsUtil::add("MerchantPasteFailed", subs); + return false; + } + + open_marketplace_listings(); + return true; +} + +bool sort_alpha(const LLViewerInventoryCategory* cat1, const LLViewerInventoryCategory* cat2) +{ + return cat1->getName().compare(cat2->getName()) < 0; +} + +// Make all relevant business logic checks on the marketplace listings starting with the folder as argument. +// This function does no deletion of listings but a mere audit and raises issues to the user (through the +// optional callback cb). +// The only inventory changes that are done is to move and sort folders containing no-copy items to stock folders. +// @pending_callbacks - how many callbacks we are waiting for, must be inited before use +// @result - true if things validate, false if issues are raised, must be inited before use +typedef boost::function validation_result_callback_t; +void validate_marketplacelistings( + LLInventoryCategory* cat, + validation_result_callback_t cb_result, + LLMarketplaceValidator::validation_msg_callback_t cb_msg, + bool fix_hierarchy, + S32 depth, + bool notify_observers, + S32 &pending_callbacks, + bool &result) +{ + // Get the type and the depth of the folder + LLViewerInventoryCategory * viewer_cat = (LLViewerInventoryCategory *) (cat); + const LLFolderType::EType folder_type = cat->getPreferredType(); + if (depth < 0) + { + // If the depth argument was not provided, evaluate the depth directly + depth = depth_nesting_in_marketplace(cat->getUUID()); + } + if (depth < 0) + { + // If the folder is not under the marketplace listings root, we run validation as if it was a listing folder and prevent any hierarchy fix + // This allows the function to be used to pre-validate a folder anywhere in the inventory + depth = 1; + fix_hierarchy = false; + } + + // Set the indentation for print output (typically, audit button in marketplace folder floater) + std::string indent; + for (int i = 1; i < depth; i++) + { + indent += " "; + } + + // Check out that version folders are marketplace ready + if (depth == 2) + { + std::string message; + // Note: if we fix the hierarchy, we want to check the items individually, hence the last argument here + if (!can_move_folder_to_marketplace(cat, cat, cat, message, 0, fix_hierarchy)) + { + result = false; + if (cb_msg) + { + message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Error") + " " + message; + cb_msg(message,depth,LLError::LEVEL_ERROR); + } + } + } + + // Check out that stock folders are at the right level + if ((folder_type == LLFolderType::FT_MARKETPLACE_STOCK) && (depth <= 2)) + { + if (fix_hierarchy) + { + if (cb_msg) + { + std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Warning") + " " + LLTrans::getString("Marketplace Validation Warning Stock"); + cb_msg(message,depth,LLError::LEVEL_WARN); + } + + // Nest the stock folder one level deeper in a normal folder and restart from there + pending_callbacks++; + LLUUID parent_uuid = cat->getParentUUID(); + LLUUID cat_uuid = cat->getUUID(); + gInventory.createNewCategory(parent_uuid, + LLFolderType::FT_NONE, + cat->getName(), + [cat_uuid, cb_result, cb_msg, fix_hierarchy, depth](const LLUUID &new_cat_id) + { + if (new_cat_id.isNull()) + { + cb_result(0, false); + return; + } + LLInventoryCategory * move_cat = gInventory.getCategory(cat_uuid); + LLViewerInventoryCategory * viewer_cat = (LLViewerInventoryCategory *)(move_cat); + LLInventoryCategory * new_cat = gInventory.getCategory(new_cat_id); + gInventory.changeCategoryParent(viewer_cat, new_cat_id, false); + S32 pending = 0; + bool result = true; + validate_marketplacelistings(new_cat, cb_result, cb_msg, fix_hierarchy, depth + 1, true, pending, result); + cb_result(pending, result); + } + ); + result = false; + return; + } + else + { + result = false; + if (cb_msg) + { + std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Error") + " " + LLTrans::getString("Marketplace Validation Warning Stock"); + cb_msg(message,depth,LLError::LEVEL_ERROR); + } + } + } + + // Item sorting and validation : sorting and moving the various stock items is complicated as the set of constraints is high + // We need to: + // * separate non stock items, stock items per types in different folders + // * have stock items nested at depth 2 at least + // * never ever move the non-stock items + + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(cat->getUUID(),cat_array,item_array); + + // We use a composite (type,permission) key on that map to store UUIDs of items of same (type,permissions) + std::map > items_vector; + + // Parse the items and create vectors of item UUIDs sorting copyable items and stock items of various types + bool has_bad_items = false; + LLInventoryModel::item_array_t item_array_copy = *item_array; + for (LLInventoryModel::item_array_t::iterator iter = item_array_copy.begin(); iter != item_array_copy.end(); iter++) + { + LLInventoryItem* item = *iter; + LLViewerInventoryItem * viewer_inv_item = (LLViewerInventoryItem *) item; + + // Test but skip items that shouldn't be there to start with, raise an error message for those + std::string error_msg; + if (!can_move_to_marketplace(item, error_msg, false)) + { + has_bad_items = true; + if (cb_msg && fix_hierarchy) + { + std::string message = indent + viewer_inv_item->getName() + LLTrans::getString("Marketplace Validation Error") + " " + error_msg; + cb_msg(message,depth,LLError::LEVEL_ERROR); + } + continue; + } + // Update the appropriate vector item for that type + LLInventoryType::EType type = LLInventoryType::IT_COUNT; // Default value for non stock items + U32 perms = 0; + if (!viewer_inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) + { + // Get the item type for stock items + type = viewer_inv_item->getInventoryType(); + perms = viewer_inv_item->getPermissions().getMaskNextOwner(); + } + U32 key = (((U32)(type) & 0xFF) << 24) | (perms & 0xFFFFFF); + items_vector[key].push_back(viewer_inv_item->getUUID()); + } + + // How many types of items? Which type is it if only one? + S32 count = items_vector.size(); + U32 default_key = (U32)(LLInventoryType::IT_COUNT) << 24; // This is the key for any normal copyable item + U32 unique_key = (count == 1 ? items_vector.begin()->first : default_key); // The key in the case of one item type only + + // If we have no items in there (only folders or empty), analyze a bit further + if ((count == 0) && !has_bad_items) + { + if (cat_array->size() == 0) + { + // So we have no item and no folder. That's at least a warning. + if (depth == 2) + { + // If this is an empty version folder, warn only (listing won't be delivered by AIS, but only AIS should unlist) + if (cb_msg) + { + std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Error Empty Version"); + cb_msg(message,depth,LLError::LEVEL_WARN); + } + } + else if ((folder_type == LLFolderType::FT_MARKETPLACE_STOCK) && (depth > 2)) + { + // If this is a legit but empty stock folder, warn only (listing must stay searchable when out of stock) + if (cb_msg) + { + std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Error Empty Stock"); + cb_msg(message,depth,LLError::LEVEL_WARN); + } + } + else if (cb_msg) + { + // We warn if there's nothing in a regular folder (may be it's an under construction listing) + std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Warning Empty"); + cb_msg(message,depth,LLError::LEVEL_WARN); + } + } + else + { + // Done with that folder : Print out the folder name unless we already found an error here + if (cb_msg && result && (depth >= 1)) + { + std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Log"); + cb_msg(message,depth,LLError::LEVEL_INFO); + } + } + } + // If we have a single type of items of the right type in the right place, we're done + else if ((count == 1) && !has_bad_items && (((unique_key == default_key) && (depth > 1)) || ((folder_type == LLFolderType::FT_MARKETPLACE_STOCK) && (depth > 2) && (cat_array->size() == 0)))) + { + // Done with that folder : Print out the folder name unless we already found an error here + if (cb_msg && result && (depth >= 1)) + { + std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Log"); + cb_msg(message,depth,LLError::LEVEL_INFO); + } + } + else + { + if (fix_hierarchy && !has_bad_items) + { + // Alert the user when an existing stock folder has to be split + if ((folder_type == LLFolderType::FT_MARKETPLACE_STOCK) && ((count >= 2) || (cat_array->size() > 0))) + { + LLNotificationsUtil::add("AlertMerchantStockFolderSplit"); + } + // If we have more than 1 type of items or we are at the listing level or we have stock/no stock type mismatch, wrap the items in subfolders + if ((count > 1) || (depth == 1) || + ((folder_type == LLFolderType::FT_MARKETPLACE_STOCK) && (unique_key == default_key)) || + ((folder_type != LLFolderType::FT_MARKETPLACE_STOCK) && (unique_key != default_key))) + { + // Create one folder per vector at the right depth and of the right type + std::map >::iterator items_vector_it = items_vector.begin(); + while (items_vector_it != items_vector.end()) + { + // Create a new folder + const LLUUID parent_uuid = (depth > 2 ? viewer_cat->getParentUUID() : viewer_cat->getUUID()); + const LLUUID origin_uuid = viewer_cat->getUUID(); + LLViewerInventoryItem* viewer_inv_item = gInventory.getItem(items_vector_it->second.back()); + std::string folder_name = (depth >= 1 ? viewer_cat->getName() : viewer_inv_item->getName()); + LLFolderType::EType new_folder_type = (items_vector_it->first == default_key ? LLFolderType::FT_NONE : LLFolderType::FT_MARKETPLACE_STOCK); + if (cb_msg) + { + std::string message = ""; + if (new_folder_type == LLFolderType::FT_MARKETPLACE_STOCK) + { + message = indent + folder_name + LLTrans::getString("Marketplace Validation Warning Create Stock"); + } + else + { + message = indent + folder_name + LLTrans::getString("Marketplace Validation Warning Create Version"); + } + cb_msg(message,depth,LLError::LEVEL_WARN); + } + + pending_callbacks++; + std::vector uuid_vector = items_vector_it->second; // needs to be a copy for lambda + gInventory.createNewCategory( + parent_uuid, + new_folder_type, + folder_name, + [uuid_vector, cb_result, cb_msg, depth, parent_uuid, origin_uuid, notify_observers](const LLUUID &new_category_id) + { + // Move each item to the new folder + std::vector::const_reverse_iterator iter = uuid_vector.rbegin(); + while (iter != uuid_vector.rend()) + { + LLViewerInventoryItem* viewer_inv_item = gInventory.getItem(*iter); + if (cb_msg) + { + std::string indent; + for (int i = 1; i < depth; i++) + { + indent += " "; + } + std::string message = indent + viewer_inv_item->getName() + LLTrans::getString("Marketplace Validation Warning Move"); + cb_msg(message, depth, LLError::LEVEL_WARN); + } + gInventory.changeItemParent(viewer_inv_item, new_category_id, true); + iter++; + } + + if (origin_uuid != parent_uuid) + { + // We might have moved last item from a folder, check if it needs to be removed + LLViewerInventoryCategory* cat = gInventory.getCategory(origin_uuid); + if (cat->getDescendentCount() == 0) + { + // Remove previous folder if it ends up empty + if (cb_msg) + { + std::string indent; + for (int i = 1; i < depth; i++) + { + indent += " "; + } + std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Warning Delete"); + cb_msg(message, depth, LLError::LEVEL_WARN); + } + gInventory.removeCategory(cat->getUUID()); + if (notify_observers) + { + gInventory.notifyObservers(); + } + } + } + + // Next type + update_marketplace_category(parent_uuid); + update_marketplace_category(new_category_id); + if (notify_observers) + { + gInventory.notifyObservers(); + } + cb_result(0, true); + } + ); + items_vector_it++; + } + } + // Stock folder should have no sub folder so reparent those up + if (folder_type == LLFolderType::FT_MARKETPLACE_STOCK) + { + LLUUID parent_uuid = cat->getParentUUID(); + gInventory.getDirectDescendentsOf(cat->getUUID(),cat_array,item_array); + LLInventoryModel::cat_array_t cat_array_copy = *cat_array; + for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) + { + LLViewerInventoryCategory * viewer_cat = (LLViewerInventoryCategory *) (*iter); + gInventory.changeCategoryParent(viewer_cat, parent_uuid, false); + validate_marketplacelistings(viewer_cat, cb_result, cb_msg, fix_hierarchy, depth, false, pending_callbacks, result); + } + } + } + else if (cb_msg) + { + // We are not fixing the hierarchy but reporting problems, report everything we can find + // Print the folder name + if (result && (depth >= 1)) + { + if ((folder_type == LLFolderType::FT_MARKETPLACE_STOCK) && (count >= 2)) + { + // Report if a stock folder contains a mix of items + result = false; + std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Error Mixed Stock"); + cb_msg(message,depth,LLError::LEVEL_ERROR); + } + else if ((folder_type == LLFolderType::FT_MARKETPLACE_STOCK) && (cat_array->size() != 0)) + { + // Report if a stock folder contains subfolders + result = false; + std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Error Subfolder In Stock"); + cb_msg(message,depth,LLError::LEVEL_ERROR); + } + else + { + // Simply print the folder name + std::string message = indent + cat->getName() + LLTrans::getString("Marketplace Validation Log"); + cb_msg(message,depth,LLError::LEVEL_INFO); + } + } + // Scan each item and report if there's a problem + LLInventoryModel::item_array_t item_array_copy = *item_array; + for (LLInventoryModel::item_array_t::iterator iter = item_array_copy.begin(); iter != item_array_copy.end(); iter++) + { + LLInventoryItem* item = *iter; + LLViewerInventoryItem * viewer_inv_item = (LLViewerInventoryItem *) item; + std::string error_msg; + if (!can_move_to_marketplace(item, error_msg, false)) + { + // Report items that shouldn't be there to start with + result = false; + std::string message = indent + " " + viewer_inv_item->getName() + LLTrans::getString("Marketplace Validation Error") + " " + error_msg; + cb_msg(message,depth,LLError::LEVEL_ERROR); + } + else if ((!viewer_inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) && (folder_type != LLFolderType::FT_MARKETPLACE_STOCK)) + { + // Report stock items that are misplaced + result = false; + std::string message = indent + " " + viewer_inv_item->getName() + LLTrans::getString("Marketplace Validation Error Stock Item"); + cb_msg(message,depth,LLError::LEVEL_ERROR); + } + else if (depth == 1) + { + // Report items not wrapped in version folder + result = false; + std::string message = indent + " " + viewer_inv_item->getName() + LLTrans::getString("Marketplace Validation Warning Unwrapped Item"); + cb_msg(message,depth,LLError::LEVEL_ERROR); + } + } + } + + // Clean up + if (viewer_cat->getDescendentCount() == 0) + { + // Remove the current folder if it ends up empty + if (cb_msg) + { + std::string message = indent + viewer_cat->getName() + LLTrans::getString("Marketplace Validation Warning Delete"); + cb_msg(message,depth,LLError::LEVEL_WARN); + } + gInventory.removeCategory(cat->getUUID()); + if (notify_observers) + { + gInventory.notifyObservers(); + } + result &=!has_bad_items; + return; + } + } + + // Recursion : Perform the same validation on each nested folder + gInventory.getDirectDescendentsOf(cat->getUUID(),cat_array,item_array); + LLInventoryModel::cat_array_t cat_array_copy = *cat_array; + // Sort the folders in alphabetical order first + std::sort(cat_array_copy.begin(), cat_array_copy.end(), sort_alpha); + + for (LLInventoryModel::cat_array_t::iterator iter = cat_array_copy.begin(); iter != cat_array_copy.end(); iter++) + { + LLInventoryCategory* category = *iter; + validate_marketplacelistings(category, cb_result, cb_msg, fix_hierarchy, depth + 1, false, pending_callbacks, result); + } + + update_marketplace_category(cat->getUUID(), true, true); + if (notify_observers) + { + gInventory.notifyObservers(); + } + result &= !has_bad_items; +} + +void change_item_parent(const LLUUID& item_id, const LLUUID& new_parent_id) +{ + LLInventoryItem* inv_item = gInventory.getItem(item_id); + if (inv_item) + { + LLInventoryModel::update_list_t update; + LLInventoryModel::LLCategoryUpdate old_folder(inv_item->getParentUUID(), -1); + update.push_back(old_folder); + LLInventoryModel::LLCategoryUpdate new_folder(new_parent_id, 1); + update.push_back(new_folder); + gInventory.accountForUpdate(update); + + LLPointer new_item = new LLViewerInventoryItem(inv_item); + new_item->setParent(new_parent_id); + new_item->updateParentOnServer(false); + gInventory.updateItem(new_item); + gInventory.notifyObservers(); + } +} + +void move_items_to_folder(const LLUUID& new_cat_uuid, const uuid_vec_t& selected_uuids) +{ + for (uuid_vec_t::const_iterator it = selected_uuids.begin(); it != selected_uuids.end(); ++it) + { + LLInventoryItem* inv_item = gInventory.getItem(*it); + if (inv_item) + { + change_item_parent(*it, new_cat_uuid); + } + else + { + LLInventoryCategory* inv_cat = gInventory.getCategory(*it); + if (inv_cat && !LLFolderType::lookupIsProtectedType(inv_cat->getPreferredType())) + { + gInventory.changeCategoryParent((LLViewerInventoryCategory*)inv_cat, new_cat_uuid, false); + } + } + } + + LLFloater* floater_inventory = LLFloaterReg::getInstance("inventory"); + if (!floater_inventory) + { + LL_WARNS() << "Could not find My Inventory floater" << LL_ENDL; + return; + } + LLSidepanelInventory *sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); + if (sidepanel_inventory) + { + if (sidepanel_inventory->getActivePanel()) + { + sidepanel_inventory->getActivePanel()->setSelection(new_cat_uuid, TAKE_FOCUS_YES); + LLFolderViewItem* fv_folder = sidepanel_inventory->getActivePanel()->getItemByID(new_cat_uuid); + if (fv_folder) + { + fv_folder->setOpen(true); + } + } + } +} + +bool is_only_cats_selected(const uuid_vec_t& selected_uuids) +{ + for (uuid_vec_t::const_iterator it = selected_uuids.begin(); it != selected_uuids.end(); ++it) + { + LLInventoryCategory* inv_cat = gInventory.getCategory(*it); + if (!inv_cat) + { + return false; + } + } + return true; +} + +bool is_only_items_selected(const uuid_vec_t& selected_uuids) +{ + for (uuid_vec_t::const_iterator it = selected_uuids.begin(); it != selected_uuids.end(); ++it) + { + LLViewerInventoryItem* inv_item = gInventory.getItem(*it); + if (!inv_item) + { + return false; + } + } + return true; +} + + +void move_items_to_new_subfolder(const uuid_vec_t& selected_uuids, const std::string& folder_name) +{ + LLInventoryObject* first_item = gInventory.getObject(*selected_uuids.begin()); + if (!first_item) + { + return; + } + + inventory_func_type func = boost::bind(&move_items_to_folder, _1, selected_uuids); + gInventory.createNewCategory(first_item->getParentUUID(), LLFolderType::FT_NONE, folder_name, func); +} + +std::string get_category_path(LLUUID cat_id) +{ + LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id); + std::string localized_cat_name; + if (!LLTrans::findString(localized_cat_name, "InvFolder " + cat->getName())) + { + localized_cat_name = cat->getName(); + } + + if (cat->getParentUUID().notNull()) + { + return get_category_path(cat->getParentUUID()) + " > " + localized_cat_name; + } + else + { + return localized_cat_name; + } +} +// Returns true if the item can be moved to Current Outfit or any outfit folder. +bool can_move_to_outfit(LLInventoryItem* inv_item, bool move_is_into_current_outfit) +{ + LLInventoryType::EType inv_type = inv_item->getInventoryType(); + if ((inv_type != LLInventoryType::IT_WEARABLE) && + (inv_type != LLInventoryType::IT_GESTURE) && + (inv_type != LLInventoryType::IT_ATTACHMENT) && + (inv_type != LLInventoryType::IT_OBJECT) && + (inv_type != LLInventoryType::IT_SNAPSHOT) && + (inv_type != LLInventoryType::IT_TEXTURE)) + { + return false; + } + + U32 flags = inv_item->getFlags(); + if(flags & LLInventoryItemFlags::II_FLAGS_OBJECT_HAS_MULTIPLE_ITEMS) + { + return false; + } + + if((inv_type == LLInventoryType::IT_TEXTURE) || (inv_type == LLInventoryType::IT_SNAPSHOT)) + { + return !move_is_into_current_outfit; + } + + if (move_is_into_current_outfit && get_is_item_worn(inv_item->getUUID())) + { + return false; + } + + return true; +} + +// Returns true if item is a landmark or a link to a landmark +// and can be moved to Favorites or Landmarks folder. +bool can_move_to_landmarks(LLInventoryItem* inv_item) +{ + // Need to get the linked item to know its type because LLInventoryItem::getType() + // returns actual type AT_LINK for links, not the asset type of a linked item. + if (LLAssetType::AT_LINK == inv_item->getType()) + { + LLInventoryItem* linked_item = gInventory.getItem(inv_item->getLinkedUUID()); + if (linked_item) + { + return LLAssetType::AT_LANDMARK == linked_item->getType(); + } + } + + return LLAssetType::AT_LANDMARK == inv_item->getType(); +} + +// Returns true if folder's content can be moved to Current Outfit or any outfit folder. +bool can_move_to_my_outfits(LLInventoryModel* model, LLInventoryCategory* inv_cat, U32 wear_limit) +{ + LLInventoryModel::cat_array_t *cats; + LLInventoryModel::item_array_t *items; + model->getDirectDescendentsOf(inv_cat->getUUID(), cats, items); + + if (items->size() > wear_limit) + { + return false; + } + + if (items->size() == 0) + { + // Nothing to move(create) + return false; + } + + if (cats->size() > 0) + { + // We do not allow subfolders in outfits of "My Outfits" yet + return false; + } + + LLInventoryModel::item_array_t::iterator iter = items->begin(); + LLInventoryModel::item_array_t::iterator end = items->end(); + + while (iter != end) + { + LLViewerInventoryItem *item = *iter; + if (!can_move_to_outfit(item, false)) + { + return false; + } + iter++; + } + + return true; +} + +std::string get_localized_folder_name(LLUUID cat_uuid) +{ + std::string localized_root_name; + const LLViewerInventoryCategory* cat = gInventory.getCategory(cat_uuid); + if (cat) + { + LLFolderType::EType preferred_type = cat->getPreferredType(); + + // Translation of Accessories folder in Library inventory folder + bool accessories = false; + if(cat->getName() == "Accessories") + { + const LLUUID& parent_folder_id = cat->getParentUUID(); + accessories = (parent_folder_id == gInventory.getLibraryRootFolderID()); + } + + //"Accessories" inventory category has folder type FT_NONE. So, this folder + //can not be detected as protected with LLFolderType::lookupIsProtectedType + localized_root_name.assign(cat->getName()); + if (accessories || LLFolderType::lookupIsProtectedType(preferred_type)) + { + LLTrans::findString(localized_root_name, std::string("InvFolder ") + cat->getName(), LLSD()); + } + } + + return localized_root_name; +} + +void new_folder_window(const LLUUID& folder_id) +{ + LLPanelMainInventory::newFolderWindow(folder_id); +} + +void ungroup_folder_items(const LLUUID& folder_id) +{ + LLInventoryCategory* inv_cat = gInventory.getCategory(folder_id); + if (!inv_cat || LLFolderType::lookupIsProtectedType(inv_cat->getPreferredType())) + { + return; + } + const LLUUID &new_cat_uuid = inv_cat->getParentUUID(); + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(inv_cat->getUUID(), cat_array, item_array); + LLInventoryModel::cat_array_t cats = *cat_array; + LLInventoryModel::item_array_t items = *item_array; + + for (LLInventoryModel::cat_array_t::const_iterator cat_iter = cats.begin(); cat_iter != cats.end(); ++cat_iter) + { + LLViewerInventoryCategory* cat = *cat_iter; + if (cat) + { + gInventory.changeCategoryParent(cat, new_cat_uuid, false); + } + } + for (LLInventoryModel::item_array_t::const_iterator item_iter = items.begin(); item_iter != items.end(); ++item_iter) + { + LLViewerInventoryItem* item = *item_iter; + if(item) + { + gInventory.changeItemParent(item, new_cat_uuid, false); + } + } + gInventory.removeCategory(inv_cat->getUUID()); + gInventory.notifyObservers(); +} + +std::string get_searchable_description(LLInventoryModel* model, const LLUUID& item_id) +{ + if (model) + { + const LLInventoryItem *item = model->getItem(item_id); + if(item) + { + std::string desc = item->getDescription(); + LLStringUtil::toUpper(desc); + return desc; + } + } + return LLStringUtil::null; +} + +std::string get_searchable_creator_name(LLInventoryModel* model, const LLUUID& item_id) +{ + if (model) + { + const LLInventoryItem *item = model->getItem(item_id); + if(item) + { + LLAvatarName av_name; + if (LLAvatarNameCache::get(item->getCreatorUUID(), &av_name)) + { + std::string username = av_name.getUserName(); + LLStringUtil::toUpper(username); + return username; + } + } + } + return LLStringUtil::null; +} + +std::string get_searchable_UUID(LLInventoryModel* model, const LLUUID& item_id) +{ + if (model) + { + const LLViewerInventoryItem *item = model->getItem(item_id); + if(item && (item->getIsFullPerm() || gAgent.isGodlikeWithoutAdminMenuFakery())) + { + std::string uuid = item->getAssetUUID().asString(); + LLStringUtil::toUpper(uuid); + return uuid; + } + } + return LLStringUtil::null; +} + +bool can_share_item(const LLUUID& item_id) +{ + bool can_share = false; + + if (gInventory.isObjectDescendentOf(item_id, gInventory.getRootFolderID())) + { + const LLViewerInventoryItem *item = gInventory.getItem(item_id); + if (item) + { + if (LLInventoryCollectFunctor::itemTransferCommonlyAllowed(item)) + { + can_share = LLGiveInventory::isInventoryGiveAcceptable(item); + } + } + else + { + can_share = (gInventory.getCategory(item_id) != NULL); + } + + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + if ((item_id == trash_id) || gInventory.isObjectDescendentOf(item_id, trash_id)) + { + can_share = false; + } + } + + return can_share; +} +///---------------------------------------------------------------------------- +/// LLMarketplaceValidator implementations +///---------------------------------------------------------------------------- + + +LLMarketplaceValidator::LLMarketplaceValidator() + : mPendingCallbacks(0) + , mValidationInProgress(false) +{ +} + +LLMarketplaceValidator::~LLMarketplaceValidator() +{ +} + +void LLMarketplaceValidator::validateMarketplaceListings( + const LLUUID &category_id, + LLMarketplaceValidator::validation_done_callback_t cb_done, + LLMarketplaceValidator::validation_msg_callback_t cb_msg, + bool fix_hierarchy, + S32 depth) +{ + + mValidationQueue.emplace(category_id, cb_done, cb_msg, fix_hierarchy, depth); + if (!mValidationInProgress) + { + start(); + } +} + +void LLMarketplaceValidator::start() +{ + if (mValidationQueue.empty()) + { + mValidationInProgress = false; + return; + } + mValidationInProgress = true; + + const ValidationRequest &first = mValidationQueue.front(); + LLViewerInventoryCategory* cat = gInventory.getCategory(first.mCategoryId); + if (!cat) + { + LL_WARNS() << "Tried to validate a folder that doesn't exist" << LL_ENDL; + if (first.mCbDone) + { + first.mCbDone(false); + } + mValidationQueue.pop(); + start(); + return; + } + + validation_result_callback_t result_callback = [](S32 pending, bool result) + { + LLMarketplaceValidator* validator = LLMarketplaceValidator::getInstance(); + validator->mPendingCallbacks--; // we just got a callback + validator->mPendingCallbacks += pending; + validator->mPendingResult &= result; + if (validator->mPendingCallbacks <= 0) + { + llassert(validator->mPendingCallbacks == 0); // shouldn't be below 0 + const ValidationRequest &first = validator->mValidationQueue.front(); + if (first.mCbDone) + { + first.mCbDone(validator->mPendingResult); + } + validator->mValidationQueue.pop(); // done; + validator->start(); + } + }; + + mPendingResult = true; + mPendingCallbacks = 1; // do '1' in case something decides to callback immediately + + S32 pending_calbacks = 0; + bool result = true; + validate_marketplacelistings( + cat, + result_callback, + first.mCbMsg, + first.mFixHierarchy, + first.mDepth, + true, + pending_calbacks, + result); + + result_callback(pending_calbacks, result); +} + +LLMarketplaceValidator::ValidationRequest::ValidationRequest( + LLUUID category_id, + validation_done_callback_t cb_done, + validation_msg_callback_t cb_msg, + bool fix_hierarchy, + S32 depth) +: mCategoryId(category_id) +, mCbDone(cb_done) +, mCbMsg(cb_msg) +, mFixHierarchy(fix_hierarchy) +, mDepth(depth) +{} + +///---------------------------------------------------------------------------- +/// LLInventoryCollectFunctor implementations +///---------------------------------------------------------------------------- + +// static +bool LLInventoryCollectFunctor::itemTransferCommonlyAllowed(const LLInventoryItem* item) +{ + if (!item) + return false; + + switch(item->getType()) + { + case LLAssetType::AT_OBJECT: + case LLAssetType::AT_BODYPART: + case LLAssetType::AT_CLOTHING: + if (!get_is_item_worn(item->getUUID())) + return true; + break; + default: + return true; + break; + } + return false; +} + +bool LLIsType::operator()(LLInventoryCategory* cat, LLInventoryItem* item) +{ + if(mType == LLAssetType::AT_CATEGORY) + { + if(cat) return true; + } + if(item) + { + if(item->getType() == mType) return true; + } + return false; +} + +bool LLIsNotType::operator()(LLInventoryCategory* cat, LLInventoryItem* item) +{ + if(mType == LLAssetType::AT_CATEGORY) + { + if(cat) return false; + } + if(item) + { + if(item->getType() == mType) return false; + else return true; + } + return true; +} + +bool LLIsOfAssetType::operator()(LLInventoryCategory* cat, LLInventoryItem* item) +{ + if(mType == LLAssetType::AT_CATEGORY) + { + if(cat) return true; + } + if(item) + { + if(item->getActualType() == mType) return true; + } + return false; +} + +bool LLAssetIDAndTypeMatches::operator()(LLInventoryCategory* cat, LLInventoryItem* item) +{ + if (!item) return false; + return (item->getActualType() == mType && item->getAssetUUID() == mAssetID); +} + +bool LLIsValidItemLink::operator()(LLInventoryCategory* cat, LLInventoryItem* item) +{ + LLViewerInventoryItem *vitem = dynamic_cast(item); + if (!vitem) return false; + return (vitem->getActualType() == LLAssetType::AT_LINK && !vitem->getIsBrokenLink()); +} + +bool LLIsTypeWithPermissions::operator()(LLInventoryCategory* cat, LLInventoryItem* item) +{ + if(mType == LLAssetType::AT_CATEGORY) + { + if(cat) + { + return true; + } + } + if(item) + { + if(item->getType() == mType) + { + LLPermissions perm = item->getPermissions(); + if ((perm.getMaskBase() & mPerm) == mPerm) + { + return true; + } + } + } + return false; +} + +bool LLBuddyCollector::operator()(LLInventoryCategory* cat, + LLInventoryItem* item) +{ + if(item) + { + if((LLAssetType::AT_CALLINGCARD == item->getType()) + && (!item->getCreatorUUID().isNull()) + && (item->getCreatorUUID() != gAgent.getID())) + { + return true; + } + } + return false; +} + + +bool LLUniqueBuddyCollector::operator()(LLInventoryCategory* cat, + LLInventoryItem* item) +{ + if(item) + { + if((LLAssetType::AT_CALLINGCARD == item->getType()) + && (item->getCreatorUUID().notNull()) + && (item->getCreatorUUID() != gAgent.getID())) + { + mSeen.insert(item->getCreatorUUID()); + return true; + } + } + return false; +} + + +bool LLParticularBuddyCollector::operator()(LLInventoryCategory* cat, + LLInventoryItem* item) +{ + if(item) + { + if((LLAssetType::AT_CALLINGCARD == item->getType()) + && (item->getCreatorUUID() == mBuddyID)) + { + return true; + } + } + return false; +} + + +bool LLNameCategoryCollector::operator()( + LLInventoryCategory* cat, LLInventoryItem* item) +{ + if(cat) + { + if (!LLStringUtil::compareInsensitive(mName, cat->getName())) + { + return true; + } + } + return false; +} + +bool LLFindCOFValidItems::operator()(LLInventoryCategory* cat, + LLInventoryItem* item) +{ + // Valid COF items are: + // - links to wearables (body parts or clothing) + // - links to attachments + // - links to gestures + // - links to ensemble folders + LLViewerInventoryItem *linked_item = ((LLViewerInventoryItem*)item)->getLinkedItem(); + if (linked_item) + { + LLAssetType::EType type = linked_item->getType(); + return (type == LLAssetType::AT_CLOTHING || + type == LLAssetType::AT_BODYPART || + type == LLAssetType::AT_GESTURE || + type == LLAssetType::AT_OBJECT); + } + else + { + LLViewerInventoryCategory *linked_category = ((LLViewerInventoryItem*)item)->getLinkedCategory(); + // BAP remove AT_NONE support after ensembles are fully working? + return (linked_category && + ((linked_category->getPreferredType() == LLFolderType::FT_NONE) || + (LLFolderType::lookupIsEnsembleType(linked_category->getPreferredType())))); + } +} + +bool LLFindBrokenLinks::operator()(LLInventoryCategory* cat, + LLInventoryItem* item) +{ + // only for broken links getType will be a link + // otherwise it's supposed to have the type of an item + // it is linked too + if (item && LLAssetType::lookupIsLinkType(item->getType())) + { + return true; + } + return false; +} + +bool LLFindWearables::operator()(LLInventoryCategory* cat, + LLInventoryItem* item) +{ + if(item) + { + if((item->getType() == LLAssetType::AT_CLOTHING) + || (item->getType() == LLAssetType::AT_BODYPART)) + { + return true; + } + } + return false; +} + +LLFindWearablesEx::LLFindWearablesEx(bool is_worn, bool include_body_parts) +: mIsWorn(is_worn) +, mIncludeBodyParts(include_body_parts) +{} + +bool LLFindWearablesEx::operator()(LLInventoryCategory* cat, LLInventoryItem* item) +{ + LLViewerInventoryItem *vitem = dynamic_cast(item); + if (!vitem) return false; + + // Skip non-wearables. + if (!vitem->isWearableType() && vitem->getType() != LLAssetType::AT_OBJECT && vitem->getType() != LLAssetType::AT_GESTURE) + { + return false; + } + + // Skip body parts if requested. + if (!mIncludeBodyParts && vitem->getType() == LLAssetType::AT_BODYPART) + { + return false; + } + + // Skip broken links. + if (vitem->getIsBrokenLink()) + { + return false; + } + + return (bool) get_is_item_worn(item->getUUID()) == mIsWorn; +} + +bool LLFindWearablesOfType::operator()(LLInventoryCategory* cat, LLInventoryItem* item) +{ + if (!item) return false; + if (item->getType() != LLAssetType::AT_CLOTHING && + item->getType() != LLAssetType::AT_BODYPART) + { + return false; + } + + LLViewerInventoryItem *vitem = dynamic_cast(item); + if (!vitem || vitem->getWearableType() != mWearableType) return false; + + return true; +} + +void LLFindWearablesOfType::setType(LLWearableType::EType type) +{ + mWearableType = type; +} + +bool LLIsTextureType::operator()(LLInventoryCategory* cat, LLInventoryItem* item) +{ + return item && (item->getType() == LLAssetType::AT_TEXTURE); +} + +bool LLFindNonRemovableObjects::operator()(LLInventoryCategory* cat, LLInventoryItem* item) +{ + if (item) + { + return !get_is_item_removable(&gInventory, item->getUUID(), true); + } + if (cat) + { + return !get_is_category_removable(&gInventory, cat->getUUID()); + } + + LL_WARNS() << "Not a category and not an item?" << LL_ENDL; + return false; +} + +///---------------------------------------------------------------------------- +/// LLAssetIDMatches +///---------------------------------------------------------------------------- +bool LLAssetIDMatches::operator()(LLInventoryCategory* cat, LLInventoryItem* item) +{ + return (item && item->getAssetUUID() == mAssetID); +} + +///---------------------------------------------------------------------------- +/// LLLinkedItemIDMatches +///---------------------------------------------------------------------------- +bool LLLinkedItemIDMatches::operator()(LLInventoryCategory* cat, LLInventoryItem* item) +{ + return (item && + (item->getIsLinkType()) && + (item->getLinkedUUID() == mBaseItemID)); // A linked item's assetID will be the compared-to item's itemID. +} + +void LLSaveFolderState::setApply(bool apply) +{ + mApply = apply; + // before generating new list of open folders, clear the old one + if(!apply) + { + clearOpenFolders(); + } +} + +void LLSaveFolderState::doFolder(LLFolderViewFolder* folder) +{ + LLInvFVBridge* bridge = (LLInvFVBridge*)folder->getViewModelItem(); + if(!bridge) return; + + if(mApply) + { + // we're applying the open state + LLUUID id(bridge->getUUID()); + if(mOpenFolders.find(id) != mOpenFolders.end()) + { + if (!folder->isOpen()) + { + folder->setOpen(true); + } + } + else + { + // keep selected filter in its current state, this is less jarring to user + if (!folder->isSelected() && folder->isOpen()) + { + folder->setOpen(false); + } + } + } + else + { + // we're recording state at this point + if(folder->isOpen()) + { + mOpenFolders.insert(bridge->getUUID()); + } + } +} + +void LLOpenFilteredFolders::doItem(LLFolderViewItem *item) +{ + if (item->passedFilter()) + { + item->getParentFolder()->setOpenArrangeRecursively(true, LLFolderViewFolder::RECURSE_UP); + } +} + +void LLOpenFilteredFolders::doFolder(LLFolderViewFolder* folder) +{ + if (folder->LLFolderViewItem::passedFilter() && folder->getParentFolder()) + { + folder->getParentFolder()->setOpenArrangeRecursively(true, LLFolderViewFolder::RECURSE_UP); + } + // if this folder didn't pass the filter, and none of its descendants did + else if (!folder->getViewModelItem()->passedFilter() && !folder->getViewModelItem()->descendantsPassedFilter()) + { + folder->setOpenArrangeRecursively(false, LLFolderViewFolder::RECURSE_NO); + } +} + +void LLSelectFirstFilteredItem::doItem(LLFolderViewItem *item) +{ + if (item->passedFilter() && !mItemSelected) + { + item->getRoot()->setSelection(item, false, false); + if (item->getParentFolder()) + { + item->getParentFolder()->setOpenArrangeRecursively(true, LLFolderViewFolder::RECURSE_UP); + } + mItemSelected = true; + } +} + +void LLSelectFirstFilteredItem::doFolder(LLFolderViewFolder* folder) +{ + // Skip if folder or item already found, if not filtered or if no parent (root folder is not selectable) + if (!mFolderSelected && !mItemSelected && folder->LLFolderViewItem::passedFilter() && folder->getParentFolder()) + { + folder->getRoot()->setSelection(folder, false, false); + folder->getParentFolder()->setOpenArrangeRecursively(true, LLFolderViewFolder::RECURSE_UP); + mFolderSelected = true; + } +} + +void LLOpenFoldersWithSelection::doItem(LLFolderViewItem *item) +{ + if (item->getParentFolder() && item->isSelected()) + { + item->getParentFolder()->setOpenArrangeRecursively(true, LLFolderViewFolder::RECURSE_UP); + } +} + +void LLOpenFoldersWithSelection::doFolder(LLFolderViewFolder* folder) +{ + if (folder->getParentFolder() && folder->isSelected()) + { + folder->getParentFolder()->setOpenArrangeRecursively(true, LLFolderViewFolder::RECURSE_UP); + } +} + +// Callback for doToSelected if DAMA required... +void LLInventoryAction::callback_doToSelected(const LLSD& notification, const LLSD& response, class LLInventoryModel* model, class LLFolderView* root, const std::string& action) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // YES + { + doToSelected(model, root, action, false); + } +} + +void LLInventoryAction::callback_copySelected(const LLSD& notification, const LLSD& response, class LLInventoryModel* model, class LLFolderView* root, const std::string& action) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // YES, Move no copy item(s) + { + doToSelected(model, root, "copy_or_move_to_marketplace_listings", false); + } + else if (option == 1) // NO, Don't move no copy item(s) (leave them behind) + { + doToSelected(model, root, "copy_to_marketplace_listings", false); + } +} + +// Succeeds iff all selected items are bridges to objects, in which +// case returns their corresponding uuids. +bool get_selection_object_uuids(LLFolderView *root, uuid_vec_t& ids) +{ + uuid_vec_t results; + S32 non_object = 0; + LLFolderView::selected_items_t selectedItems = root->getSelectedItems(); + for(LLFolderView::selected_items_t::iterator it = selectedItems.begin(); it != selectedItems.end(); ++it) + { + LLObjectBridge *view_model = dynamic_cast((*it)->getViewModelItem()); + + if(view_model && view_model->getUUID().notNull()) + { + results.push_back(view_model->getUUID()); + } + else + { + non_object++; + } + } + if (non_object == 0) + { + ids = results; + return true; + } + return false; +} + + +void LLInventoryAction::doToSelected(LLInventoryModel* model, LLFolderView* root, const std::string& action, bool user_confirm) +{ + std::set selected_items = root->getSelectionList(); + if (selected_items.empty() + && action != "wear" + && action != "wear_add" + && !isRemoveAction(action)) + { + // Was item removed while user was checking menu? + // "wear" and removal exlusions are due to use of + // getInventorySelectedUUIDs() below + LL_WARNS("Inventory") << "Menu tried to operate on empty selection" << LL_ENDL; + + if (("copy" == action) || ("cut" == action)) + { + LLClipboard::instance().reset(); + } + + return; + } + + // Prompt the user and check for authorization for some marketplace active listing edits + if (user_confirm && (("delete" == action) || ("cut" == action) || ("rename" == action) || ("properties" == action) || ("task_properties" == action) || ("open" == action))) + { + std::set::iterator set_iter = selected_items.begin(); + LLFolderViewModelItemInventory * viewModel = NULL; + for (; set_iter != selected_items.end(); ++set_iter) + { + viewModel = dynamic_cast((*set_iter)->getViewModelItem()); + if (viewModel && (depth_nesting_in_marketplace(viewModel->getUUID()) >= 0)) + { + break; + } + } + if (set_iter != selected_items.end()) + { + if ("open" == action) + { + if (get_can_item_be_worn(viewModel->getUUID())) + { + // Wearing an object from any listing, active or not, is verbotten + LLNotificationsUtil::add("AlertMerchantListingCannotWear"); + return; + } + // Note: we do not prompt for change when opening items (e.g. textures or note cards) on the marketplace... + } + else if (LLMarketplaceData::instance().isInActiveFolder(viewModel->getUUID()) || + LLMarketplaceData::instance().isListedAndActive(viewModel->getUUID())) + { + // If item is in active listing, further confirmation is required + if ((("cut" == action) || ("delete" == action)) && (LLMarketplaceData::instance().isListed(viewModel->getUUID()) || LLMarketplaceData::instance().isVersionFolder(viewModel->getUUID()))) + { + // Cut or delete of the active version folder or listing folder itself will unlist the listing so ask that question specifically + LLNotificationsUtil::add("ConfirmMerchantUnlist", LLSD(), LLSD(), boost::bind(&LLInventoryAction::callback_doToSelected, _1, _2, model, root, action)); + return; + } + // Any other case will simply modify but not unlist a listing + LLNotificationsUtil::add("ConfirmMerchantActiveChange", LLSD(), LLSD(), boost::bind(&LLInventoryAction::callback_doToSelected, _1, _2, model, root, action)); + return; + } + // Cutting or deleting a whole listing needs confirmation as SLM will be archived and inaccessible to the user + else if (LLMarketplaceData::instance().isListed(viewModel->getUUID()) && (("cut" == action) || ("delete" == action))) + { + LLNotificationsUtil::add("ConfirmListingCutOrDelete", LLSD(), LLSD(), boost::bind(&LLInventoryAction::callback_doToSelected, _1, _2, model, root, action)); + return; + } + } + } + // Copying to the marketplace needs confirmation if nocopy items are involved + if (user_confirm && ("copy_to_marketplace_listings" == action)) + { + std::set::iterator set_iter = selected_items.begin(); + LLFolderViewModelItemInventory * viewModel = dynamic_cast((*set_iter)->getViewModelItem()); + if (contains_nocopy_items(viewModel->getUUID())) + { + LLNotificationsUtil::add("ConfirmCopyToMarketplace", LLSD(), LLSD(), boost::bind(&LLInventoryAction::callback_copySelected, _1, _2, model, root, action)); + return; + } + } + + // Keep track of the marketplace folders that will need update of their status/name after the operation is performed + buildMarketplaceFolders(root); + + if ("rename" == action) + { + root->startRenamingSelectedItem(); + // Update the marketplace listings that have been affected by the operation + updateMarketplaceFolders(); + return; + } + + if ("delete" == action) + { + const LLUUID &marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + bool marketplacelistings_item = false; + bool has_worn = false; + bool needs_replacement = false; + LLAllDescendentsPassedFilter f; + for (std::set::iterator it = selected_items.begin(); (it != selected_items.end()) && (f.allDescendentsPassedFilter()); ++it) + { + if (LLFolderViewFolder* folder = dynamic_cast(*it)) + { + folder->applyFunctorRecursively(f); + } + LLFolderViewModelItemInventory * viewModel = dynamic_cast((*it)->getViewModelItem()); + LLUUID obj_id = viewModel->getUUID(); + if (viewModel && gInventory.isObjectDescendentOf(obj_id, marketplacelistings_id)) + { + marketplacelistings_item = true; + break; + } + + LLViewerInventoryCategory* cat = gInventory.getCategory(obj_id); + if (cat) + { + LLInventoryModel::cat_array_t categories; + LLInventoryModel::item_array_t items; + + gInventory.collectDescendents(obj_id, categories, items, false); + + for (LLInventoryModel::item_array_t::value_type& item : items) + { + if (get_is_item_worn(item)) + { + has_worn = true; + LLWearableType::EType type = item->getWearableType(); + if (type == LLWearableType::WT_SHAPE + || type == LLWearableType::WT_SKIN + || type == LLWearableType::WT_HAIR + || type == LLWearableType::WT_EYES) + { + needs_replacement = true; + break; + } + } + } + if (needs_replacement) + { + break; + } + } + LLViewerInventoryItem* item = gInventory.getItem(obj_id); + if (item && get_is_item_worn(item)) + { + has_worn = true; + LLWearableType::EType type = item->getWearableType(); + if (type == LLWearableType::WT_SHAPE + || type == LLWearableType::WT_SKIN + || type == LLWearableType::WT_HAIR + || type == LLWearableType::WT_EYES) + { + needs_replacement = true; + break; + } + } + } + // Fall through to the generic confirmation if the user choose to ignore the specialized one + if (needs_replacement) + { + LLNotificationsUtil::add("CantDeleteRequiredClothing"); + } + else if (has_worn) + { + LLSD payload; + payload["has_worn"] = true; + LLNotificationsUtil::add("DeleteWornItems", LLSD(), payload, boost::bind(&LLInventoryAction::onItemsRemovalConfirmation, _1, _2, root->getHandle())); + } + else if ( (!f.allDescendentsPassedFilter()) && !marketplacelistings_item && (!LLNotifications::instance().getIgnored("DeleteFilteredItems")) ) + { + LLNotificationsUtil::add("DeleteFilteredItems", LLSD(), LLSD(), boost::bind(&LLInventoryAction::onItemsRemovalConfirmation, _1, _2, root->getHandle())); + } + else + { + if (!sDeleteConfirmationDisplayed) // ask for the confirmation at least once per session + { + LLNotifications::instance().setIgnored("DeleteItems", false); + sDeleteConfirmationDisplayed = true; + } + + LLSD args; + args["QUESTION"] = LLTrans::getString(root->getSelectedCount() > 1 ? "DeleteItems" : "DeleteItem"); + LLNotificationsUtil::add("DeleteItems", args, LLSD(), boost::bind(&LLInventoryAction::onItemsRemovalConfirmation, _1, _2, root->getHandle())); + } + // Note: marketplace listings will be updated in the callback if delete confirmed + return; + } + if (("copy" == action) || ("cut" == action)) + { + // Clear the clipboard before we start adding things on it + LLClipboard::instance().reset(); + } + if ("replace_links" == action) + { + LLSD params; + if (root->getSelectedCount() == 1) + { + LLFolderViewItem* folder_item = root->getSelectedItems().front(); + LLInvFVBridge* bridge = (LLInvFVBridge*)folder_item->getViewModelItem(); + + if (bridge) + { + LLInventoryObject* obj = bridge->getInventoryObject(); + if (obj && obj->getType() != LLAssetType::AT_CATEGORY && obj->getActualType() != LLAssetType::AT_LINK_FOLDER) + { + params = LLSD(obj->getUUID()); + } + } + } + LLFloaterReg::showInstance("linkreplace", params); + return; + } + + static const std::string change_folder_string = "change_folder_type_"; + if (action.length() > change_folder_string.length() && + (action.compare(0,change_folder_string.length(),"change_folder_type_") == 0)) + { + LLFolderType::EType new_folder_type = LLViewerFolderType::lookupTypeFromXUIName(action.substr(change_folder_string.length())); + LLFolderViewModelItemInventory* inventory_item = static_cast(root->getViewModelItem()); + LLViewerInventoryCategory *cat = model->getCategory(inventory_item->getUUID()); + if (!cat) return; + cat->changeType(new_folder_type); + // Update the marketplace listings that have been affected by the operation + updateMarketplaceFolders(); + return; + } + + + LLMultiPreview* multi_previewp = NULL; + LLMultiItemProperties* multi_itempropertiesp = nullptr; + + if (("task_open" == action || "open" == action) && selected_items.size() > 1) + { + bool open_multi_preview = true; + + if ("open" == action) + { + for (std::set::iterator set_iter = selected_items.begin(); set_iter != selected_items.end(); ++set_iter) + { + LLFolderViewItem* folder_item = *set_iter; + if (folder_item) + { + LLInvFVBridge* bridge = dynamic_cast(folder_item->getViewModelItem()); + if (!bridge || !bridge->isMultiPreviewAllowed()) + { + open_multi_preview = false; + break; + } + } + } + } + + if (open_multi_preview) + { + multi_previewp = new LLMultiPreview(); + gFloaterView->addChild(multi_previewp); + + LLFloater::setFloaterHost(multi_previewp); + } + + } + else if (("task_properties" == action || "properties" == action) && selected_items.size() > 1) + { + multi_itempropertiesp = new LLMultiItemProperties("item_properties"); + gFloaterView->addChild(multi_itempropertiesp); + LLFloater::setFloaterHost(multi_itempropertiesp); + } + + std::set selected_uuid_set = LLAvatarActions::getInventorySelectedUUIDs(); + + // copy list of applicable items into a vector for bulk handling + uuid_vec_t ids; + if (action == "wear" || action == "wear_add") + { + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + const LLUUID mp_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + std::copy_if(selected_uuid_set.begin(), + selected_uuid_set.end(), + std::back_inserter(ids), + [trash_id, mp_id](LLUUID id) + { + if (get_is_item_worn(id) + || LLAppearanceMgr::instance().getIsInCOF(id) + || gInventory.isObjectDescendentOf(id, trash_id)) + { + return false; + } + if (mp_id.notNull() && gInventory.isObjectDescendentOf(id, mp_id)) + { + return false; + } + LLInventoryObject* obj = (LLInventoryObject*)gInventory.getObject(id); + if (!obj) + { + return false; + } + if (obj->getIsLinkType() && gInventory.isObjectDescendentOf(obj->getLinkedUUID(), trash_id)) + { + return false; + } + if (obj->getIsLinkType() && LLAssetType::lookupIsLinkType(obj->getType())) + { + // missing + return false; + } + return true; + } + ); + } + else if (isRemoveAction(action)) + { + std::copy_if(selected_uuid_set.begin(), + selected_uuid_set.end(), + std::back_inserter(ids), + [](LLUUID id) + { + return get_is_item_worn(id); + } + ); + } + else + { + for (std::set::iterator it = selected_items.begin(), end_it = selected_items.end(); + it != end_it; + ++it) + { + ids.push_back(static_cast((*it)->getViewModelItem())->getUUID()); + } + } + + // Check for actions that get handled in bulk + if (action == "wear") + { + wear_multiple(ids, true); + } + else if (action == "wear_add") + { + wear_multiple(ids, false); + } + else if (isRemoveAction(action)) + { + LLAppearanceMgr::instance().removeItemsFromAvatar(ids); + } + else if ("save_selected_as" == action) + { + (new LLDirPickerThread(boost::bind(&LLInventoryAction::saveMultipleTextures, _1, selected_items, model), std::string()))->getFile(); + } + else if ("new_folder_from_selected" == action) + { + + LLInventoryObject* first_item = gInventory.getObject(*ids.begin()); + if (!first_item) + { + return; + } + const LLUUID& parent_uuid = first_item->getParentUUID(); + for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it) + { + LLInventoryObject *item = gInventory.getObject(*it); + if (!item || item->getParentUUID() != parent_uuid) + { + LLNotificationsUtil::add("SameFolderRequired"); + return; + } + } + + LLSD args; + args["DESC"] = LLTrans::getString("New Folder"); + + LLNotificationsUtil::add("CreateSubfolder", args, LLSD(), + [ids](const LLSD& notification, const LLSD& response) + { + S32 opt = LLNotificationsUtil::getSelectedOption(notification, response); + if (opt == 0) + { + std::string settings_name = response["message"].asString(); + + LLInventoryObject::correctInventoryName(settings_name); + if (settings_name.empty()) + { + settings_name = LLTrans::getString("New Folder"); + } + move_items_to_new_subfolder(ids, settings_name); + } + }); + } + else if ("ungroup_folder_items" == action) + { + if (ids.size() == 1) + { + ungroup_folder_items(*ids.begin()); + } + } + else if ("thumbnail" == action) + { + if (selected_items.size() > 0) + { + LLSD data; + std::set::iterator set_iter; + for (set_iter = selected_items.begin(); set_iter != selected_items.end(); ++set_iter) + { + LLFolderViewItem* folder_item = *set_iter; + if (!folder_item) continue; + LLInvFVBridge* bridge = (LLInvFVBridge*)folder_item->getViewModelItem(); + if (!bridge) continue; + data.append(bridge->getUUID()); + } + LLFloaterReg::showInstance("change_item_thumbnail", data); + } + } + else + { + std::set::iterator set_iter; + for (set_iter = selected_items.begin(); set_iter != selected_items.end(); ++set_iter) + { + LLFolderViewItem* folder_item = *set_iter; + if(!folder_item) continue; + LLInvFVBridge* bridge = (LLInvFVBridge*)folder_item->getViewModelItem(); + if(!bridge) continue; + bridge->performAction(model, action); + } + if(root->isSingleFolderMode() && selected_items.empty()) + { + LLInvFVBridge* bridge = (LLInvFVBridge*)root->getViewModelItem(); + if(bridge) + { + bridge->performAction(model, action); + } + } + } + + // Update the marketplace listings that have been affected by the operation + updateMarketplaceFolders(); + + LLFloater::setFloaterHost(NULL); + if (multi_previewp) + { + multi_previewp->openFloater(LLSD()); + } + else if (multi_itempropertiesp) + { + multi_itempropertiesp->openFloater(LLSD()); + } +} + +void LLInventoryAction::saveMultipleTextures(const std::vector& filenames, std::set selected_items, LLInventoryModel* model) +{ + gSavedSettings.setString("TextureSaveLocation", filenames[0]); + + LLMultiPreview* multi_previewp = new LLMultiPreview(); + gFloaterView->addChild(multi_previewp); + + LLFloater::setFloaterHost(multi_previewp); + + std::map tex_names_map; + std::set::iterator set_iter; + + for (set_iter = selected_items.begin(); set_iter != selected_items.end(); ++set_iter) + { + LLFolderViewItem* folder_item = *set_iter; + if(!folder_item) continue; + LLTextureBridge* bridge = (LLTextureBridge*)folder_item->getViewModelItem(); + if(!bridge) continue; + + std::string tex_name = bridge->getName(); + if(!tex_names_map.insert(std::pair(tex_name, 0)).second) + { + tex_names_map[tex_name]++; + bridge->setFileName(tex_name + llformat("_%.3d", tex_names_map[tex_name])); + } + bridge->performAction(model, "save_selected_as"); + } + + LLFloater::setFloaterHost(NULL); + if (multi_previewp) + { + multi_previewp->openFloater(LLSD()); + } +} + +void LLInventoryAction::removeItemFromDND(LLFolderView* root) +{ + if(gAgent.isDoNotDisturb()) + { + //Get selected items + LLFolderView::selected_items_t selectedItems = root->getSelectedItems(); + LLFolderViewModelItemInventory * viewModel = NULL; + + //If user is in DND and deletes item, make sure the notification is not displayed by removing the notification + //from DND history and .xml file. Once this is done, upon exit of DND mode the item deleted will not show a notification. + for(LLFolderView::selected_items_t::iterator it = selectedItems.begin(); it != selectedItems.end(); ++it) + { + viewModel = dynamic_cast((*it)->getViewModelItem()); + + if(viewModel && viewModel->getUUID().notNull()) + { + //Will remove the item offer notification + LLDoNotDisturbNotificationStorage::instance().removeNotification(LLDoNotDisturbNotificationStorage::offerName, viewModel->getUUID()); + } + } + } +} + +void LLInventoryAction::onItemsRemovalConfirmation(const LLSD& notification, const LLSD& response, LLHandle root) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0 && !root.isDead() && !root.get()->isDead()) + { + bool has_worn = notification["payload"]["has_worn"].asBoolean(); + LLFolderView* folder_root = root.get(); + //Need to remove item from DND before item is removed from root folder view + //because once removed from root folder view the item is no longer a selected item + removeItemFromDND(folder_root); + + // removeSelectedItems will change selection, collect worn items beforehand + uuid_vec_t worn; + uuid_vec_t item_deletion_list; + uuid_vec_t cat_deletion_list; + if (has_worn) + { + //Get selected items + LLFolderView::selected_items_t selectedItems = folder_root->getSelectedItems(); + + //If user is in DND and deletes item, make sure the notification is not displayed by removing the notification + //from DND history and .xml file. Once this is done, upon exit of DND mode the item deleted will not show a notification. + for (LLFolderView::selected_items_t::iterator it = selectedItems.begin(); it != selectedItems.end(); ++it) + { + LLFolderViewModelItemInventory* viewModel = dynamic_cast((*it)->getViewModelItem()); + + LLUUID obj_id = viewModel->getUUID(); + LLViewerInventoryCategory* cat = gInventory.getCategory(obj_id); + bool cat_has_worn = false; + if (cat) + { + LLInventoryModel::cat_array_t categories; + LLInventoryModel::item_array_t items; + + gInventory.collectDescendents(obj_id, categories, items, false); + + for (LLInventoryModel::item_array_t::value_type& item : items) + { + if (get_is_item_worn(item)) + { + worn.push_back(item->getUUID()); + cat_has_worn = true; + } + } + if (cat_has_worn) + { + cat_deletion_list.push_back(obj_id); + } + } + LLViewerInventoryItem* item = gInventory.getItem(obj_id); + if (item && get_is_item_worn(item)) + { + worn.push_back(obj_id); + item_deletion_list.push_back(obj_id); + } + } + } + + // removeSelectedItems will check if items are worn before deletion, + // don't 'unwear' yet to prevent race conditions from unwearing + // and removing simultaneously + folder_root->removeSelectedItems(); + + // unwear then delete the rest + if (!worn.empty()) + { + // should fire once after every item gets detached + LLAppearanceMgr::instance().removeItemsFromAvatar(worn, + [item_deletion_list, cat_deletion_list]() + { + for (const LLUUID& id : item_deletion_list) + { + remove_inventory_item(id, NULL); + } + for (const LLUUID& id : cat_deletion_list) + { + remove_inventory_category(id, NULL); + } + }); + } + + // Update the marketplace listings that have been affected by the operation + updateMarketplaceFolders(); + } +} + +void LLInventoryAction::buildMarketplaceFolders(LLFolderView* root) +{ + // Make a list of all marketplace folders containing the elements in the selected list + // as well as the elements themselves. + // Once those elements are updated (cut, delete in particular but potentially any action), their + // containing folder will need to be updated as well as their initially containing folder. For + // instance, moving a stock folder from a listed folder to another will require an update of the + // target listing *and* the original listing. So we need to keep track of both. + // Note: do not however put the marketplace listings root itself in this list or the whole marketplace data will be rebuilt. + sMarketplaceFolders.clear(); + const LLUUID &marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + if (marketplacelistings_id.isNull()) + { + return; + } + + std::set selected_items = root->getSelectionList(); + std::set::iterator set_iter = selected_items.begin(); + LLFolderViewModelItemInventory * viewModel = NULL; + for (; set_iter != selected_items.end(); ++set_iter) + { + viewModel = dynamic_cast((*set_iter)->getViewModelItem()); + if (!viewModel || !viewModel->getInventoryObject()) continue; + if (gInventory.isObjectDescendentOf(viewModel->getInventoryObject()->getParentUUID(), marketplacelistings_id)) + { + const LLUUID &parent_id = viewModel->getInventoryObject()->getParentUUID(); + if (parent_id != marketplacelistings_id) + { + sMarketplaceFolders.push_back(parent_id); + } + const LLUUID &curr_id = viewModel->getInventoryObject()->getUUID(); + if (curr_id != marketplacelistings_id) + { + sMarketplaceFolders.push_back(curr_id); + } + } + } + // Suppress dupes in the list so we won't update listings twice + sMarketplaceFolders.sort(); + sMarketplaceFolders.unique(); +} + +void LLInventoryAction::updateMarketplaceFolders() +{ + while (!sMarketplaceFolders.empty()) + { + update_marketplace_category(sMarketplaceFolders.back()); + sMarketplaceFolders.pop_back(); + } +} + + diff --git a/indra/newview/llinventoryfunctions.h b/indra/newview/llinventoryfunctions.h index ad1f6e8ef7..a25c0d5ad6 100644 --- a/indra/newview/llinventoryfunctions.h +++ b/indra/newview/llinventoryfunctions.h @@ -1,601 +1,601 @@ -/** - * @file llinventoryfunctions.h - * @brief Miscellaneous inventory-related functions and classes - * class definition - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLINVENTORYFUNCTIONS_H -#define LL_LLINVENTORYFUNCTIONS_H - -#include "llinventorymodel.h" -#include "llinventory.h" -#include "llhandle.h" -#include "llwearabletype.h" - -// compute_stock_count() return error code -const S32 COMPUTE_STOCK_INFINITE = -1; -const S32 COMPUTE_STOCK_NOT_EVALUATED = -2; - -/******************************************************************************** - ** ** - ** MISCELLANEOUS GLOBAL FUNCTIONS - **/ - -// Is this a parent folder to a worn item -bool get_is_parent_to_worn_item(const LLUUID& id); - -// Is this item or its baseitem is worn, attached, etc... -bool get_is_item_worn(const LLUUID& id); -bool get_is_item_worn(const LLViewerInventoryItem* item); - -// Could this item be worn (correct type + not already being worn) -bool get_can_item_be_worn(const LLUUID& id); - -bool get_is_item_removable(const LLInventoryModel* model, const LLUUID& id, bool check_worn); - -// Performs the appropiate edit action (if one exists) for this item -bool get_is_item_editable(const LLUUID& inv_item_id); -void handle_item_edit(const LLUUID& inv_item_id); - -bool get_is_category_removable(const LLInventoryModel* model, const LLUUID& id); -bool get_is_category_and_children_removable(LLInventoryModel* model, const LLUUID& folder_id, bool check_worn); - -bool get_is_category_renameable(const LLInventoryModel* model, const LLUUID& id); - -void show_item_profile(const LLUUID& item_uuid); -void show_task_item_profile(const LLUUID& item_uuid, const LLUUID& object_id); - -void show_item_original(const LLUUID& item_uuid); -void reset_inventory_filter(); - -// Nudge the listing categories in the inventory to signal that their marketplace status changed -void update_marketplace_category(const LLUUID& cat_id, bool perform_consistency_enforcement = true, bool skip_clear_listing = false); -// Nudge all listing categories to signal that their marketplace status changed -void update_all_marketplace_count(); - -void rename_category(LLInventoryModel* model, const LLUUID& cat_id, const std::string& new_name); - -void copy_inventory_category(LLInventoryModel* model, LLViewerInventoryCategory* cat, const LLUUID& parent_id, const LLUUID& root_copy_id = LLUUID::null, bool move_no_copy_items = false); -void copy_inventory_category(LLInventoryModel* model, LLViewerInventoryCategory* cat, const LLUUID& parent_id, const LLUUID& root_copy_id, bool move_no_copy_items, inventory_func_type callback); - -void copy_inventory_category_content(const LLUUID& new_cat_uuid, LLInventoryModel* model, LLViewerInventoryCategory* cat, const LLUUID& root_copy_id, bool move_no_copy_items); - -// Generates a string containing the path to the object specified by id (not including the object name). -void append_path(const LLUUID& id, std::string& path); - -// Generates a string containing the path name of the object. -std::string make_path(const LLInventoryObject* object); -// Generates a string containing the path name of the object specified by id. -std::string make_inventory_path(const LLUUID& id); - -// Generates a string containing the path name and id of the object. -std::string make_info(const LLInventoryObject* object); -// Generates a string containing the path name and id of the object specified by id. -std::string make_inventory_info(const LLUUID& id); - -bool can_move_item_to_marketplace(const LLInventoryCategory* root_folder, LLInventoryCategory* dest_folder, LLInventoryItem* inv_item, std::string& tooltip_msg, S32 bundle_size = 1, bool from_paste = false); -bool can_move_folder_to_marketplace(const LLInventoryCategory* root_folder, LLInventoryCategory* dest_folder, LLInventoryCategory* inv_cat, std::string& tooltip_msg, S32 bundle_size = 1, bool check_items = true, bool from_paste = false); -bool move_item_to_marketplacelistings(LLInventoryItem* inv_item, LLUUID dest_folder, bool copy = false); -bool move_folder_to_marketplacelistings(LLInventoryCategory* inv_cat, const LLUUID& dest_folder, bool copy = false, bool move_no_copy_items = false); - -S32 depth_nesting_in_marketplace(LLUUID cur_uuid); -LLUUID nested_parent_id(LLUUID cur_uuid, S32 depth); -S32 compute_stock_count(LLUUID cat_uuid, bool force_count = false); - -void change_item_parent(const LLUUID& item_id, const LLUUID& new_parent_id); -void move_items_to_new_subfolder(const uuid_vec_t& selected_uuids, const std::string& folder_name); -void move_items_to_folder(const LLUUID& new_cat_uuid, const uuid_vec_t& selected_uuids); -bool is_only_cats_selected(const uuid_vec_t& selected_uuids); -bool is_only_items_selected(const uuid_vec_t& selected_uuids); -std::string get_category_path(LLUUID cat_id); - -bool can_move_to_outfit(LLInventoryItem* inv_item, bool move_is_into_current_outfit); -bool can_move_to_landmarks(LLInventoryItem* inv_item); -bool can_move_to_my_outfits(LLInventoryModel* model, LLInventoryCategory* inv_cat, U32 wear_limit); -std::string get_localized_folder_name(LLUUID cat_uuid); -void new_folder_window(const LLUUID& folder_id); -void ungroup_folder_items(const LLUUID& folder_id); -std::string get_searchable_description(LLInventoryModel* model, const LLUUID& item_id); -std::string get_searchable_creator_name(LLInventoryModel* model, const LLUUID& item_id); -std::string get_searchable_UUID(LLInventoryModel* model, const LLUUID& item_id); -bool can_share_item(const LLUUID& item_id); - -/** Miscellaneous global functions - ** ** - *******************************************************************************/ - -class LLMarketplaceValidator: public LLSingleton -{ - LLSINGLETON(LLMarketplaceValidator); - ~LLMarketplaceValidator(); - LOG_CLASS(LLMarketplaceValidator); -public: - - typedef boost::function validation_msg_callback_t; - typedef boost::function validation_done_callback_t; - - void validateMarketplaceListings( - const LLUUID &category_id, - validation_done_callback_t cb_done = NULL, - validation_msg_callback_t cb_msg = NULL, - bool fix_hierarchy = true, - S32 depth = -1); - -private: - void start(); - - class ValidationRequest - { - public: - ValidationRequest( - LLUUID category_id, - validation_done_callback_t cb_done, - validation_msg_callback_t cb_msg, - bool fix_hierarchy, - S32 depth); - LLUUID mCategoryId; - validation_done_callback_t mCbDone; - validation_msg_callback_t mCbMsg; - bool mFixHierarchy; - S32 mDepth; - }; - - bool mValidationInProgress; - S32 mPendingCallbacks; - bool mPendingResult; - // todo: might be a good idea to memorize requests by id and - // filter out ones that got multiple validation requests - std::queue mValidationQueue; -}; - -/******************************************************************************** - ** ** - ** INVENTORY COLLECTOR FUNCTIONS - **/ - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLInventoryCollectFunctor -// -// Base class for LLInventoryModel::collectDescendentsIf() method -// which accepts an instance of one of these objects to use as the -// function to determine if it should be added. Derive from this class -// and override the () operator to return true if you want to collect -// the category or item passed in. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLInventoryCollectFunctor -{ -public: - virtual ~LLInventoryCollectFunctor(){}; - virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) = 0; - - static bool itemTransferCommonlyAllowed(const LLInventoryItem* item); -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLAssetIDMatches -// -// This functor finds inventory items pointing to the specified asset -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLViewerInventoryItem; - -class LLAssetIDMatches : public LLInventoryCollectFunctor -{ -public: - LLAssetIDMatches(const LLUUID& asset_id) : mAssetID(asset_id) {} - virtual ~LLAssetIDMatches() {} - bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); - -protected: - LLUUID mAssetID; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLLinkedItemIDMatches -// -// This functor finds inventory items linked to the specific inventory id. -// Assumes the inventory id is itself not a linked item. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLLinkedItemIDMatches : public LLInventoryCollectFunctor -{ -public: - LLLinkedItemIDMatches(const LLUUID& item_id) : mBaseItemID(item_id) {} - virtual ~LLLinkedItemIDMatches() {} - bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); - -protected: - LLUUID mBaseItemID; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLIsType -// -// Implementation of a LLInventoryCollectFunctor which returns true if -// the type is the type passed in during construction. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLIsType : public LLInventoryCollectFunctor -{ -public: - LLIsType(LLAssetType::EType type) : mType(type) {} - virtual ~LLIsType() {} - virtual bool operator()(LLInventoryCategory* cat, - LLInventoryItem* item); -protected: - LLAssetType::EType mType; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLIsNotType -// -// Implementation of a LLInventoryCollectFunctor which returns false if the -// type is the type passed in during construction, otherwise false. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLIsNotType : public LLInventoryCollectFunctor -{ -public: - LLIsNotType(LLAssetType::EType type) : mType(type) {} - virtual ~LLIsNotType() {} - virtual bool operator()(LLInventoryCategory* cat, - LLInventoryItem* item); -protected: - LLAssetType::EType mType; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLIsOfAssetType -// -// Implementation of a LLInventoryCollectFunctor which returns true if -// the item or category is of asset type passed in during construction. -// Link types are treated as links, not as the types they point to. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLIsOfAssetType : public LLInventoryCollectFunctor -{ -public: - LLIsOfAssetType(LLAssetType::EType type) : mType(type) {} - virtual ~LLIsOfAssetType() {} - virtual bool operator()(LLInventoryCategory* cat, - LLInventoryItem* item); -protected: - LLAssetType::EType mType; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLAssetIDAndTypeMatches -// -// Implementation of a LLInventoryCollectFunctor which returns true if -// the item matches both asset type and asset id. -// This is needed in case you are looking for a specific type with default id -// (since null is default for multiple asset types) -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLAssetIDAndTypeMatches: public LLInventoryCollectFunctor -{ -public: - LLAssetIDAndTypeMatches(const LLUUID& asset_id, LLAssetType::EType type): mAssetID(asset_id), mType(type) {} - virtual ~LLAssetIDAndTypeMatches() {} - bool operator()(LLInventoryCategory* cat, - LLInventoryItem* item); - -protected: - LLUUID mAssetID; - LLAssetType::EType mType; -}; - -class LLIsValidItemLink : public LLInventoryCollectFunctor -{ -public: - virtual bool operator()(LLInventoryCategory* cat, - LLInventoryItem* item); -}; - -class LLIsTypeWithPermissions : public LLInventoryCollectFunctor -{ -public: - LLIsTypeWithPermissions(LLAssetType::EType type, const PermissionBit perms, const LLUUID &agent_id, const LLUUID &group_id) - : mType(type), mPerm(perms), mAgentID(agent_id), mGroupID(group_id) {} - virtual ~LLIsTypeWithPermissions() {} - virtual bool operator()(LLInventoryCategory* cat, - LLInventoryItem* item); -protected: - LLAssetType::EType mType; - PermissionBit mPerm; - LLUUID mAgentID; - LLUUID mGroupID; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLBuddyCollector -// -// Simple class that collects calling cards that are not null, and not -// the agent. Duplicates are possible. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLBuddyCollector : public LLInventoryCollectFunctor -{ -public: - LLBuddyCollector() {} - virtual ~LLBuddyCollector() {} - virtual bool operator()(LLInventoryCategory* cat, - LLInventoryItem* item); -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLUniqueBuddyCollector -// -// Simple class that collects calling cards that are not null, and not -// the agent. Duplicates are discarded. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLUniqueBuddyCollector : public LLInventoryCollectFunctor -{ -public: - LLUniqueBuddyCollector() {} - virtual ~LLUniqueBuddyCollector() {} - virtual bool operator()(LLInventoryCategory* cat, - LLInventoryItem* item); - -protected: - std::set mSeen; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLParticularBuddyCollector -// -// Simple class that collects calling cards that match a particular uuid -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLParticularBuddyCollector : public LLInventoryCollectFunctor -{ -public: - LLParticularBuddyCollector(const LLUUID& id) : mBuddyID(id) {} - virtual ~LLParticularBuddyCollector() {} - virtual bool operator()(LLInventoryCategory* cat, - LLInventoryItem* item); -protected: - LLUUID mBuddyID; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLNameCategoryCollector -// -// Collects categories based on case-insensitive match of prefix -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLNameCategoryCollector : public LLInventoryCollectFunctor -{ -public: - LLNameCategoryCollector(const std::string& name) : mName(name) {} - virtual ~LLNameCategoryCollector() {} - virtual bool operator()(LLInventoryCategory* cat, - LLInventoryItem* item); -protected: - std::string mName; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLFindCOFValidItems -// -// Collects items that can be legitimately linked to in the COF. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLFindCOFValidItems : public LLInventoryCollectFunctor -{ -public: - LLFindCOFValidItems() {} - virtual ~LLFindCOFValidItems() {} - virtual bool operator()(LLInventoryCategory* cat, - LLInventoryItem* item); - -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLFindBrokenLinks -// -// Collects broken links -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLFindBrokenLinks : public LLInventoryCollectFunctor -{ -public: - LLFindBrokenLinks() {} - virtual ~LLFindBrokenLinks() {} - virtual bool operator()(LLInventoryCategory* cat, - LLInventoryItem* item); -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLFindByMask -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLFindByMask : public LLInventoryCollectFunctor -{ -public: - LLFindByMask(U64 mask) - : mFilterMask(mask) - {} - - virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) - { - //converting an inventory type to a bitmap filter mask - if(item && (mFilterMask & (1LL << item->getInventoryType())) ) - { - return true; - } - - return false; - } - -private: - U64 mFilterMask; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLFindNonLinksByMask -// -// -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLFindNonLinksByMask : public LLInventoryCollectFunctor -{ -public: - LLFindNonLinksByMask(U64 mask) - : mFilterMask(mask) - {} - - virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) - { - if(item && !item->getIsLinkType() && (mFilterMask & (1LL << item->getInventoryType())) ) - { - return true; - } - - return false; - } - - void setFilterMask(U64 mask) - { - mFilterMask = mask; - } - -private: - U64 mFilterMask; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLFindWearables -// -// Collects wearables based on item type. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLFindWearables : public LLInventoryCollectFunctor -{ -public: - LLFindWearables() {} - virtual ~LLFindWearables() {} - virtual bool operator()(LLInventoryCategory* cat, - LLInventoryItem* item); -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLFindWearablesEx -// -// Collects wearables based on given criteria. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLFindWearablesEx : public LLInventoryCollectFunctor -{ -public: - LLFindWearablesEx(bool is_worn, bool include_body_parts = true); - virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); -private: - bool mIncludeBodyParts; - bool mIsWorn; -}; - -//Inventory collect functor collecting wearables of a specific wearable type -class LLFindWearablesOfType : public LLInventoryCollectFunctor -{ -public: - LLFindWearablesOfType(LLWearableType::EType type) : mWearableType(type) {} - virtual ~LLFindWearablesOfType() {} - virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); - void setType(LLWearableType::EType type); - -private: - LLWearableType::EType mWearableType; -}; - -class LLIsTextureType : public LLInventoryCollectFunctor -{ -public: - LLIsTextureType() {} - virtual ~LLIsTextureType() {} - virtual bool operator()(LLInventoryCategory* cat, - LLInventoryItem* item); -}; - -/** Filter out wearables-links */ -class LLFindActualWearablesOfType : public LLFindWearablesOfType -{ -public: - LLFindActualWearablesOfType(LLWearableType::EType type) : LLFindWearablesOfType(type) {} - virtual ~LLFindActualWearablesOfType() {} - virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) - { - if (item && item->getIsLinkType()) return false; - return LLFindWearablesOfType::operator()(cat, item); - } -}; - -/* Filters out items of a particular asset type */ -class LLIsTypeActual : public LLIsType -{ -public: - LLIsTypeActual(LLAssetType::EType type) : LLIsType(type) {} - virtual ~LLIsTypeActual() {} - virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) - { - if (item && item->getIsLinkType()) return false; - return LLIsType::operator()(cat, item); - } -}; - -// Collect non-removable folders and items. -class LLFindNonRemovableObjects : public LLInventoryCollectFunctor -{ -public: - virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); -}; - -/** Inventory Collector Functions - ** ** - *******************************************************************************/ -class LLFolderViewItem; -class LLFolderViewFolder; -class LLInventoryModel; -class LLFolderView; - -class LLInventoryState -{ -public: - // HACK: Until we can route this info through the instant message hierarchy - static bool sWearNewClothing; - static LLUUID sWearNewClothingTransactionID; // wear all clothing in this transaction -}; - -struct LLInventoryAction -{ - static void doToSelected(LLInventoryModel* model, LLFolderView* root, const std::string& action, bool user_confirm = true); - static void callback_doToSelected(const LLSD& notification, const LLSD& response, class LLInventoryModel* model, class LLFolderView* root, const std::string& action); - static void callback_copySelected(const LLSD& notification, const LLSD& response, class LLInventoryModel* model, class LLFolderView* root, const std::string& action); - static void onItemsRemovalConfirmation(const LLSD& notification, const LLSD& response, LLHandle root); - static void removeItemFromDND(LLFolderView* root); - - static void saveMultipleTextures(const std::vector& filenames, std::set selected_items, LLInventoryModel* model); - - static bool sDeleteConfirmationDisplayed; - -private: - static void buildMarketplaceFolders(LLFolderView* root); - static void updateMarketplaceFolders(); - static std::list sMarketplaceFolders; // Marketplace folders that will need update once the action is completed -}; - - -#endif // LL_LLINVENTORYFUNCTIONS_H - - - +/** + * @file llinventoryfunctions.h + * @brief Miscellaneous inventory-related functions and classes + * class definition + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLINVENTORYFUNCTIONS_H +#define LL_LLINVENTORYFUNCTIONS_H + +#include "llinventorymodel.h" +#include "llinventory.h" +#include "llhandle.h" +#include "llwearabletype.h" + +// compute_stock_count() return error code +const S32 COMPUTE_STOCK_INFINITE = -1; +const S32 COMPUTE_STOCK_NOT_EVALUATED = -2; + +/******************************************************************************** + ** ** + ** MISCELLANEOUS GLOBAL FUNCTIONS + **/ + +// Is this a parent folder to a worn item +bool get_is_parent_to_worn_item(const LLUUID& id); + +// Is this item or its baseitem is worn, attached, etc... +bool get_is_item_worn(const LLUUID& id); +bool get_is_item_worn(const LLViewerInventoryItem* item); + +// Could this item be worn (correct type + not already being worn) +bool get_can_item_be_worn(const LLUUID& id); + +bool get_is_item_removable(const LLInventoryModel* model, const LLUUID& id, bool check_worn); + +// Performs the appropiate edit action (if one exists) for this item +bool get_is_item_editable(const LLUUID& inv_item_id); +void handle_item_edit(const LLUUID& inv_item_id); + +bool get_is_category_removable(const LLInventoryModel* model, const LLUUID& id); +bool get_is_category_and_children_removable(LLInventoryModel* model, const LLUUID& folder_id, bool check_worn); + +bool get_is_category_renameable(const LLInventoryModel* model, const LLUUID& id); + +void show_item_profile(const LLUUID& item_uuid); +void show_task_item_profile(const LLUUID& item_uuid, const LLUUID& object_id); + +void show_item_original(const LLUUID& item_uuid); +void reset_inventory_filter(); + +// Nudge the listing categories in the inventory to signal that their marketplace status changed +void update_marketplace_category(const LLUUID& cat_id, bool perform_consistency_enforcement = true, bool skip_clear_listing = false); +// Nudge all listing categories to signal that their marketplace status changed +void update_all_marketplace_count(); + +void rename_category(LLInventoryModel* model, const LLUUID& cat_id, const std::string& new_name); + +void copy_inventory_category(LLInventoryModel* model, LLViewerInventoryCategory* cat, const LLUUID& parent_id, const LLUUID& root_copy_id = LLUUID::null, bool move_no_copy_items = false); +void copy_inventory_category(LLInventoryModel* model, LLViewerInventoryCategory* cat, const LLUUID& parent_id, const LLUUID& root_copy_id, bool move_no_copy_items, inventory_func_type callback); + +void copy_inventory_category_content(const LLUUID& new_cat_uuid, LLInventoryModel* model, LLViewerInventoryCategory* cat, const LLUUID& root_copy_id, bool move_no_copy_items); + +// Generates a string containing the path to the object specified by id (not including the object name). +void append_path(const LLUUID& id, std::string& path); + +// Generates a string containing the path name of the object. +std::string make_path(const LLInventoryObject* object); +// Generates a string containing the path name of the object specified by id. +std::string make_inventory_path(const LLUUID& id); + +// Generates a string containing the path name and id of the object. +std::string make_info(const LLInventoryObject* object); +// Generates a string containing the path name and id of the object specified by id. +std::string make_inventory_info(const LLUUID& id); + +bool can_move_item_to_marketplace(const LLInventoryCategory* root_folder, LLInventoryCategory* dest_folder, LLInventoryItem* inv_item, std::string& tooltip_msg, S32 bundle_size = 1, bool from_paste = false); +bool can_move_folder_to_marketplace(const LLInventoryCategory* root_folder, LLInventoryCategory* dest_folder, LLInventoryCategory* inv_cat, std::string& tooltip_msg, S32 bundle_size = 1, bool check_items = true, bool from_paste = false); +bool move_item_to_marketplacelistings(LLInventoryItem* inv_item, LLUUID dest_folder, bool copy = false); +bool move_folder_to_marketplacelistings(LLInventoryCategory* inv_cat, const LLUUID& dest_folder, bool copy = false, bool move_no_copy_items = false); + +S32 depth_nesting_in_marketplace(LLUUID cur_uuid); +LLUUID nested_parent_id(LLUUID cur_uuid, S32 depth); +S32 compute_stock_count(LLUUID cat_uuid, bool force_count = false); + +void change_item_parent(const LLUUID& item_id, const LLUUID& new_parent_id); +void move_items_to_new_subfolder(const uuid_vec_t& selected_uuids, const std::string& folder_name); +void move_items_to_folder(const LLUUID& new_cat_uuid, const uuid_vec_t& selected_uuids); +bool is_only_cats_selected(const uuid_vec_t& selected_uuids); +bool is_only_items_selected(const uuid_vec_t& selected_uuids); +std::string get_category_path(LLUUID cat_id); + +bool can_move_to_outfit(LLInventoryItem* inv_item, bool move_is_into_current_outfit); +bool can_move_to_landmarks(LLInventoryItem* inv_item); +bool can_move_to_my_outfits(LLInventoryModel* model, LLInventoryCategory* inv_cat, U32 wear_limit); +std::string get_localized_folder_name(LLUUID cat_uuid); +void new_folder_window(const LLUUID& folder_id); +void ungroup_folder_items(const LLUUID& folder_id); +std::string get_searchable_description(LLInventoryModel* model, const LLUUID& item_id); +std::string get_searchable_creator_name(LLInventoryModel* model, const LLUUID& item_id); +std::string get_searchable_UUID(LLInventoryModel* model, const LLUUID& item_id); +bool can_share_item(const LLUUID& item_id); + +/** Miscellaneous global functions + ** ** + *******************************************************************************/ + +class LLMarketplaceValidator: public LLSingleton +{ + LLSINGLETON(LLMarketplaceValidator); + ~LLMarketplaceValidator(); + LOG_CLASS(LLMarketplaceValidator); +public: + + typedef boost::function validation_msg_callback_t; + typedef boost::function validation_done_callback_t; + + void validateMarketplaceListings( + const LLUUID &category_id, + validation_done_callback_t cb_done = NULL, + validation_msg_callback_t cb_msg = NULL, + bool fix_hierarchy = true, + S32 depth = -1); + +private: + void start(); + + class ValidationRequest + { + public: + ValidationRequest( + LLUUID category_id, + validation_done_callback_t cb_done, + validation_msg_callback_t cb_msg, + bool fix_hierarchy, + S32 depth); + LLUUID mCategoryId; + validation_done_callback_t mCbDone; + validation_msg_callback_t mCbMsg; + bool mFixHierarchy; + S32 mDepth; + }; + + bool mValidationInProgress; + S32 mPendingCallbacks; + bool mPendingResult; + // todo: might be a good idea to memorize requests by id and + // filter out ones that got multiple validation requests + std::queue mValidationQueue; +}; + +/******************************************************************************** + ** ** + ** INVENTORY COLLECTOR FUNCTIONS + **/ + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLInventoryCollectFunctor +// +// Base class for LLInventoryModel::collectDescendentsIf() method +// which accepts an instance of one of these objects to use as the +// function to determine if it should be added. Derive from this class +// and override the () operator to return true if you want to collect +// the category or item passed in. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLInventoryCollectFunctor +{ +public: + virtual ~LLInventoryCollectFunctor(){}; + virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) = 0; + + static bool itemTransferCommonlyAllowed(const LLInventoryItem* item); +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLAssetIDMatches +// +// This functor finds inventory items pointing to the specified asset +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLViewerInventoryItem; + +class LLAssetIDMatches : public LLInventoryCollectFunctor +{ +public: + LLAssetIDMatches(const LLUUID& asset_id) : mAssetID(asset_id) {} + virtual ~LLAssetIDMatches() {} + bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); + +protected: + LLUUID mAssetID; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLLinkedItemIDMatches +// +// This functor finds inventory items linked to the specific inventory id. +// Assumes the inventory id is itself not a linked item. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLLinkedItemIDMatches : public LLInventoryCollectFunctor +{ +public: + LLLinkedItemIDMatches(const LLUUID& item_id) : mBaseItemID(item_id) {} + virtual ~LLLinkedItemIDMatches() {} + bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); + +protected: + LLUUID mBaseItemID; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLIsType +// +// Implementation of a LLInventoryCollectFunctor which returns true if +// the type is the type passed in during construction. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLIsType : public LLInventoryCollectFunctor +{ +public: + LLIsType(LLAssetType::EType type) : mType(type) {} + virtual ~LLIsType() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); +protected: + LLAssetType::EType mType; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLIsNotType +// +// Implementation of a LLInventoryCollectFunctor which returns false if the +// type is the type passed in during construction, otherwise false. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLIsNotType : public LLInventoryCollectFunctor +{ +public: + LLIsNotType(LLAssetType::EType type) : mType(type) {} + virtual ~LLIsNotType() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); +protected: + LLAssetType::EType mType; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLIsOfAssetType +// +// Implementation of a LLInventoryCollectFunctor which returns true if +// the item or category is of asset type passed in during construction. +// Link types are treated as links, not as the types they point to. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLIsOfAssetType : public LLInventoryCollectFunctor +{ +public: + LLIsOfAssetType(LLAssetType::EType type) : mType(type) {} + virtual ~LLIsOfAssetType() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); +protected: + LLAssetType::EType mType; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLAssetIDAndTypeMatches +// +// Implementation of a LLInventoryCollectFunctor which returns true if +// the item matches both asset type and asset id. +// This is needed in case you are looking for a specific type with default id +// (since null is default for multiple asset types) +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLAssetIDAndTypeMatches: public LLInventoryCollectFunctor +{ +public: + LLAssetIDAndTypeMatches(const LLUUID& asset_id, LLAssetType::EType type): mAssetID(asset_id), mType(type) {} + virtual ~LLAssetIDAndTypeMatches() {} + bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); + +protected: + LLUUID mAssetID; + LLAssetType::EType mType; +}; + +class LLIsValidItemLink : public LLInventoryCollectFunctor +{ +public: + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); +}; + +class LLIsTypeWithPermissions : public LLInventoryCollectFunctor +{ +public: + LLIsTypeWithPermissions(LLAssetType::EType type, const PermissionBit perms, const LLUUID &agent_id, const LLUUID &group_id) + : mType(type), mPerm(perms), mAgentID(agent_id), mGroupID(group_id) {} + virtual ~LLIsTypeWithPermissions() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); +protected: + LLAssetType::EType mType; + PermissionBit mPerm; + LLUUID mAgentID; + LLUUID mGroupID; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLBuddyCollector +// +// Simple class that collects calling cards that are not null, and not +// the agent. Duplicates are possible. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLBuddyCollector : public LLInventoryCollectFunctor +{ +public: + LLBuddyCollector() {} + virtual ~LLBuddyCollector() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLUniqueBuddyCollector +// +// Simple class that collects calling cards that are not null, and not +// the agent. Duplicates are discarded. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLUniqueBuddyCollector : public LLInventoryCollectFunctor +{ +public: + LLUniqueBuddyCollector() {} + virtual ~LLUniqueBuddyCollector() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); + +protected: + std::set mSeen; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLParticularBuddyCollector +// +// Simple class that collects calling cards that match a particular uuid +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLParticularBuddyCollector : public LLInventoryCollectFunctor +{ +public: + LLParticularBuddyCollector(const LLUUID& id) : mBuddyID(id) {} + virtual ~LLParticularBuddyCollector() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); +protected: + LLUUID mBuddyID; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLNameCategoryCollector +// +// Collects categories based on case-insensitive match of prefix +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLNameCategoryCollector : public LLInventoryCollectFunctor +{ +public: + LLNameCategoryCollector(const std::string& name) : mName(name) {} + virtual ~LLNameCategoryCollector() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); +protected: + std::string mName; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLFindCOFValidItems +// +// Collects items that can be legitimately linked to in the COF. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLFindCOFValidItems : public LLInventoryCollectFunctor +{ +public: + LLFindCOFValidItems() {} + virtual ~LLFindCOFValidItems() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); + +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLFindBrokenLinks +// +// Collects broken links +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLFindBrokenLinks : public LLInventoryCollectFunctor +{ +public: + LLFindBrokenLinks() {} + virtual ~LLFindBrokenLinks() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLFindByMask +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLFindByMask : public LLInventoryCollectFunctor +{ +public: + LLFindByMask(U64 mask) + : mFilterMask(mask) + {} + + virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) + { + //converting an inventory type to a bitmap filter mask + if(item && (mFilterMask & (1LL << item->getInventoryType())) ) + { + return true; + } + + return false; + } + +private: + U64 mFilterMask; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLFindNonLinksByMask +// +// +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLFindNonLinksByMask : public LLInventoryCollectFunctor +{ +public: + LLFindNonLinksByMask(U64 mask) + : mFilterMask(mask) + {} + + virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) + { + if(item && !item->getIsLinkType() && (mFilterMask & (1LL << item->getInventoryType())) ) + { + return true; + } + + return false; + } + + void setFilterMask(U64 mask) + { + mFilterMask = mask; + } + +private: + U64 mFilterMask; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLFindWearables +// +// Collects wearables based on item type. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLFindWearables : public LLInventoryCollectFunctor +{ +public: + LLFindWearables() {} + virtual ~LLFindWearables() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLFindWearablesEx +// +// Collects wearables based on given criteria. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLFindWearablesEx : public LLInventoryCollectFunctor +{ +public: + LLFindWearablesEx(bool is_worn, bool include_body_parts = true); + virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); +private: + bool mIncludeBodyParts; + bool mIsWorn; +}; + +//Inventory collect functor collecting wearables of a specific wearable type +class LLFindWearablesOfType : public LLInventoryCollectFunctor +{ +public: + LLFindWearablesOfType(LLWearableType::EType type) : mWearableType(type) {} + virtual ~LLFindWearablesOfType() {} + virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); + void setType(LLWearableType::EType type); + +private: + LLWearableType::EType mWearableType; +}; + +class LLIsTextureType : public LLInventoryCollectFunctor +{ +public: + LLIsTextureType() {} + virtual ~LLIsTextureType() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); +}; + +/** Filter out wearables-links */ +class LLFindActualWearablesOfType : public LLFindWearablesOfType +{ +public: + LLFindActualWearablesOfType(LLWearableType::EType type) : LLFindWearablesOfType(type) {} + virtual ~LLFindActualWearablesOfType() {} + virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) + { + if (item && item->getIsLinkType()) return false; + return LLFindWearablesOfType::operator()(cat, item); + } +}; + +/* Filters out items of a particular asset type */ +class LLIsTypeActual : public LLIsType +{ +public: + LLIsTypeActual(LLAssetType::EType type) : LLIsType(type) {} + virtual ~LLIsTypeActual() {} + virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) + { + if (item && item->getIsLinkType()) return false; + return LLIsType::operator()(cat, item); + } +}; + +// Collect non-removable folders and items. +class LLFindNonRemovableObjects : public LLInventoryCollectFunctor +{ +public: + virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); +}; + +/** Inventory Collector Functions + ** ** + *******************************************************************************/ +class LLFolderViewItem; +class LLFolderViewFolder; +class LLInventoryModel; +class LLFolderView; + +class LLInventoryState +{ +public: + // HACK: Until we can route this info through the instant message hierarchy + static bool sWearNewClothing; + static LLUUID sWearNewClothingTransactionID; // wear all clothing in this transaction +}; + +struct LLInventoryAction +{ + static void doToSelected(LLInventoryModel* model, LLFolderView* root, const std::string& action, bool user_confirm = true); + static void callback_doToSelected(const LLSD& notification, const LLSD& response, class LLInventoryModel* model, class LLFolderView* root, const std::string& action); + static void callback_copySelected(const LLSD& notification, const LLSD& response, class LLInventoryModel* model, class LLFolderView* root, const std::string& action); + static void onItemsRemovalConfirmation(const LLSD& notification, const LLSD& response, LLHandle root); + static void removeItemFromDND(LLFolderView* root); + + static void saveMultipleTextures(const std::vector& filenames, std::set selected_items, LLInventoryModel* model); + + static bool sDeleteConfirmationDisplayed; + +private: + static void buildMarketplaceFolders(LLFolderView* root); + static void updateMarketplaceFolders(); + static std::list sMarketplaceFolders; // Marketplace folders that will need update once the action is completed +}; + + +#endif // LL_LLINVENTORYFUNCTIONS_H + + + diff --git a/indra/newview/llinventorygallery.h b/indra/newview/llinventorygallery.h index 920a8538a1..afc7bdc9f8 100644 --- a/indra/newview/llinventorygallery.h +++ b/indra/newview/llinventorygallery.h @@ -1,424 +1,424 @@ -/** - * @file llinventorygallery.h - * @brief LLInventoryGallery class definition - * - * $LicenseInfo:firstyear=2023&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2023, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLINVENTORYGALLERY_H -#define LL_LLINVENTORYGALLERY_H - -#include "llgesturemgr.h" -#include "lllistcontextmenu.h" -#include "llpanel.h" -#include "llinventoryfilter.h" -#include "llinventoryobserver.h" -#include "llinventorymodel.h" - -class LLInventoryCategoriesObserver; -class LLInventoryGalleryItem; -class LLScrollContainer; -class LLTextBox; -class LLThumbnailsObserver; -class LLThumbnailCtrl; -class LLGalleryGestureObserver; - -class LLInventoryGalleryContextMenu; - -typedef boost::function callback_t; - -class LLInventoryGallery : public LLPanel, public LLEditMenuHandler -{ -public: - - typedef boost::signals2::signal selection_change_signal_t; - typedef boost::function selection_change_callback_t; - typedef std::deque selection_deque; - - struct Params - : public LLInitParam::Block - { - Optional row_panel_height; - Optional row_panel_width_factor; - Optional gallery_width_factor; - Optional vertical_gap; - Optional horizontal_gap; - Optional item_width; - Optional item_height; - Optional item_horizontal_gap; - Optional items_in_row; - - Params(); - }; - - static const LLInventoryGallery::Params& getDefaultParams(); - - LLInventoryGallery(const LLInventoryGallery::Params& params = getDefaultParams()); - ~LLInventoryGallery(); - - bool postBuild() override; - void initGallery(); - void draw() override; - void onVisibilityChange(bool new_visibility) override; - bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, - void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) override; - void startDrag(); - bool handleRightMouseDown(S32 x, S32 y, MASK mask) override; - bool handleKeyHere(KEY key, MASK mask) override; - void moveUp(MASK mask); - void moveDown(MASK mask); - void moveLeft(MASK mask); - void moveRight(MASK mask); - void toggleSelectionRange(S32 start_idx, S32 end_idx); - void toggleSelectionRangeFromLast(const LLUUID target); - - void onFocusLost() override; - void onFocusReceived() override; - - void setFilterSubString(const std::string& string); - std::string getFilterSubString() { return mFilterSubString; } - LLInventoryFilter& getFilter() const { return *mFilter; } - bool checkAgainstFilterType(const LLUUID& object_id); - - void getCurrentCategories(uuid_vec_t& vcur); - bool updateAddedItem(LLUUID item_id); // returns true if added item is visible - void updateRemovedItem(LLUUID item_id); - void updateChangedItemName(LLUUID item_id, std::string name); - void updateItemThumbnail(LLUUID item_id); - void updateWornItem(LLUUID item_id, bool is_worn); - - void updateMessageVisibility(); - - void setRootFolder(const LLUUID cat_id); - void updateRootFolder(); - LLUUID getRootFolder() { return mFolderID; } - bool isRootDirty() { return mRootDirty; } - boost::signals2::connection setRootChangedCallback(callback_t cb); - void onForwardFolder(); - void onBackwardFolder(); - void clearNavigationHistory(); - bool isBackwardAvailable(); - bool isForwardAvailable(); - - void setNavBackwardList(std::list backward_list) { mBackwardFolders = backward_list; } - void setNavForwardList(std::list forward_list) { mForwardFolders = forward_list; } - std::list getNavBackwardList() { return mBackwardFolders; } - std::list getNavForwardList() { return mForwardFolders; } - - LLUUID getOutfitImageID(LLUUID outfit_id); - - void refreshList(const LLUUID& category_id); - void onCOFChanged(); - void onGesturesChanged(); - void computeDifference(const LLInventoryModel::cat_array_t vcats, const LLInventoryModel::item_array_t vitems, uuid_vec_t& vadded, uuid_vec_t& vremoved); - - void deselectItem(const LLUUID& category_id); - void clearSelection(); - void changeItemSelection(const LLUUID& item_id, bool scroll_to_selection = false); - void addItemSelection(const LLUUID& item_id, bool scroll_to_selection = false); - bool toggleItemSelection(const LLUUID& item_id, bool scroll_to_selection = false); - void scrollToShowItem(const LLUUID& item_id); - void signalSelectionItemID(const LLUUID& category_id); - boost::signals2::connection setSelectionChangeCallback(selection_change_callback_t cb); - LLUUID getFirstSelectedItemID(); - - void setSearchType(LLInventoryFilter::ESearchType type); - LLInventoryFilter::ESearchType getSearchType() { return mSearchType; } - - bool areViewsInitialized(); - bool hasDescendents(const LLUUID& cat_id); - bool hasVisibleItems(); - void handleModifiedFilter(); - LLScrollContainer* getScrollableContainer() { return mScrollPanel; } - LLInventoryGalleryItem* getFirstSelectedItem(); - - // Copy & paste (LLEditMenuHandler) - void copy() override; - bool canCopy() const override; - - void cut() override; - bool canCut() const override; - - void paste() override; - bool canPaste() const override; - - // Copy & paste & delete - static void onDelete(const LLSD& notification, const LLSD& response, const selection_deque selected_ids); - void deleteSelection(); - bool canDeleteSelection(); - void pasteAsLink(); - void doCreate(const LLUUID& dest, const LLSD& userdata); - - void setSortOrder(U32 order, bool update = false); - U32 getSortOrder() { return mSortOrder; }; - - void claimEditHandler(); - void resetEditHandler(); - static bool isItemCopyable(const LLUUID & item_id); - - bool baseHandleDragAndDrop(LLUUID dest_id, bool drop, EDragAndDropType cargo_type, - void* cargo_data, EAcceptance* accept, std::string& tooltip_msg); - - void showContextMenu(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& item_id); - -protected: - void paste(const LLUUID& dest, - std::vector& objects, - bool is_cut_mode, - const LLUUID& marketplacelistings_id); - void pasteAsLink(const LLUUID& dest, - std::vector& objects, - const LLUUID& current_outfit_id, - const LLUUID& marketplacelistings_id, - const LLUUID& my_outifts_id); - - bool applyFilter(LLInventoryGalleryItem* item, const std::string& filter_substring); - bool checkAgainstFilters(LLInventoryGalleryItem* item, const std::string& filter_substring); - static void onIdle(void* userdata); - void dirtyRootFolder(); - - LLInventoryCategoriesObserver* mCategoriesObserver; - LLThumbnailsObserver* mThumbnailsObserver; - LLGalleryGestureObserver* mGestureObserver; - LLInventoryObserver* mInventoryObserver; - selection_deque mSelectedItemIDs; - selection_deque mItemsToSelect; - LLUUID mLastInteractedUUID; - bool mIsInitialized; - bool mRootDirty; - - selection_change_signal_t mSelectionChangeSignal; - boost::signals2::signal mRootChangedSignal; - LLUUID mFolderID; - std::list mBackwardFolders; - std::list mForwardFolders; - -private: - void addToGallery(LLInventoryGalleryItem* item); - void removeFromGalleryLast(LLInventoryGalleryItem* item, bool needs_reshape = true); - void removeFromGalleryMiddle(LLInventoryGalleryItem* item); - LLPanel* addLastRow(); - void removeLastRow(); - void moveRowUp(int row); - void moveRowDown(int row); - void moveRow(int row, int pos); - LLPanel* addToRow(LLPanel* row_stack, LLInventoryGalleryItem* item, int pos, int hgap); - void removeFromLastRow(LLInventoryGalleryItem* item); - void reArrangeRows(S32 row_diff = 0); - bool updateRowsIfNeeded(); - void updateGalleryWidth(); - - LLInventoryGalleryItem* buildGalleryItem(std::string name, LLUUID item_id, LLAssetType::EType type, LLUUID thumbnail_id, LLInventoryType::EType inventory_type, U32 flags, time_t creation_date, bool is_link, bool is_worn); - - void buildGalleryPanel(int row_count); - void reshapeGalleryPanel(int row_count); - LLPanel* buildItemPanel(int left); - LLPanel* buildRowPanel(int left, int bottom); - void moveRowPanel(LLPanel* stack, int left, int bottom); - - std::vector mRowPanels; - std::vector mItemPanels; - std::vector mUnusedRowPanels; - std::vector mUnusedItemPanels; - std::vector mItems; - std::vector mHiddenItems; - LLScrollContainer* mScrollPanel; - LLPanel* mGalleryPanel; - LLPanel* mLastRowPanel; - LLTextBox* mMessageTextBox; - int mRowCount; - int mItemsAddedCount; - bool mGalleryCreated; - bool mLoadThumbnailsImmediately; - bool mNeedsArrange; - - /* Params */ - int mRowPanelHeight; - int mVerticalGap; - int mHorizontalGap; - int mItemWidth; - int mItemHeight; - int mItemHorizontalGap; - int mItemsInRow; - int mRowPanelWidth; - int mGalleryWidth; - int mRowPanWidthFactor; - int mGalleryWidthFactor; - - LLInventoryGalleryContextMenu* mInventoryGalleryMenu; - LLInventoryGalleryContextMenu* mRootGalleryMenu; - std::string mFilterSubString; - LLInventoryFilter* mFilter; - U32 mSortOrder; - - typedef std::map gallery_item_map_t; - gallery_item_map_t mItemMap; - uuid_vec_t mCOFLinkedItems; - uuid_vec_t mActiveGestures; - uuid_set_t mItemBuildQuery; - std::map mItemIndexMap; - std::map mIndexToItemMap; - - LLInventoryFilter::ESearchType mSearchType; - std::string mUsername; -}; - -class LLInventoryGalleryItem : public LLPanel -{ -public: - struct Params : public LLInitParam::Block - {}; - - enum EInventorySortGroup - { - SG_SYSTEM_FOLDER, - SG_TRASH_FOLDER, - SG_NORMAL_FOLDER, - SG_ITEM - }; - - LLInventoryGalleryItem(const Params& p); - virtual ~LLInventoryGalleryItem(); - - bool postBuild(); - void draw(); - bool handleMouseDown(S32 x, S32 y, MASK mask); - bool handleRightMouseDown(S32 x, S32 y, MASK mask); - bool handleDoubleClick(S32 x, S32 y, MASK mask); - bool handleMouseUp(S32 x, S32 y, MASK mask); - bool handleHover(S32 x, S32 y, MASK mask); - bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg); - bool handleKeyHere(KEY key, MASK mask); - - void onFocusLost(); - void onFocusReceived(); - - LLFontGL* getTextFont(); - - void setItemName(std::string name); - bool isSelected() { return mSelected; } - void setSelected(bool value); - void setWorn(bool value); - void setUUID(LLUUID id) {mUUID = id;} - LLUUID getUUID() { return mUUID;} - - void setAssetIDStr(std::string asset_id) {mAssetIDStr = asset_id;} - std::string getAssetIDStr() { return mAssetIDStr;} - void setDescription(std::string desc) {mDesc = desc;} - std::string getDescription() { return mDesc;} - void setCreatorName(std::string name) {mCreatorName = name;} - std::string getCreatorName() { return mCreatorName;} - void setCreationDate(time_t date) {mCreationDate = date;} - time_t getCreationDate() { return mCreationDate;} - - std::string getItemName() {return mItemName;} - std::string getItemNameSuffix() {return mPermSuffix + mWornSuffix;} - bool isDefaultImage() {return mDefaultImage;} - - bool isHidden() {return mHidden;} - void setHidden(bool hidden) {mHidden = hidden;} - - void setType(LLAssetType::EType type, LLInventoryType::EType inventory_type, U32 flags, bool is_link); - LLAssetType::EType getAssetType() { return mType; } - void setThumbnail(LLUUID id); - void setGallery(LLInventoryGallery* gallery) { mGallery = gallery; } - void setLoadImmediately(bool val); - bool isFolder() { return mIsFolder; } - bool isLink() { return mIsLink; } - EInventorySortGroup getSortGroup() { return mSortGroup; } - - void updateNameText(); - -private: - bool isFadeItem(); - - LLUUID mUUID; - LLTextBox* mNameText; - LLPanel* mTextBgPanel; - LLThumbnailCtrl* mThumbnailCtrl; - bool mSelected; - bool mWorn; - bool mDefaultImage; - bool mHidden; - bool mIsFolder; - bool mIsLink; - S32 mCutGeneration; - bool mSelectedForCut; - - std::string mAssetIDStr; - std::string mDesc; - std::string mCreatorName; - time_t mCreationDate; - - EInventorySortGroup mSortGroup; - LLAssetType::EType mType; - std::string mItemName; - std::string mWornSuffix; - std::string mPermSuffix; - LLInventoryGallery* mGallery; -}; - -class LLThumbnailsObserver : public LLInventoryObserver -{ -public: - LLThumbnailsObserver(){}; - - virtual void changed(U32 mask); - bool addItem(const LLUUID& obj_id, callback_t cb); - void removeItem(const LLUUID& obj_id); - -protected: - - struct LLItemData - { - LLItemData(const LLUUID& obj_id, const LLUUID& thumbnail_id, callback_t cb) - : mItemID(obj_id) - , mCallback(cb) - , mThumbnailID(thumbnail_id) - {} - - callback_t mCallback; - LLUUID mItemID; - LLUUID mThumbnailID; - }; - - typedef std::map item_map_t; - typedef item_map_t::value_type item_map_value_t; - item_map_t mItemMap; -}; - -class LLGalleryGestureObserver : public LLGestureManagerObserver -{ -public: - LLGalleryGestureObserver(LLInventoryGallery* gallery) : mGallery(gallery) {} - virtual ~LLGalleryGestureObserver() {} - virtual void changed() { mGallery->onGesturesChanged(); } - -private: - LLInventoryGallery* mGallery; -}; - -#endif +/** + * @file llinventorygallery.h + * @brief LLInventoryGallery class definition + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLINVENTORYGALLERY_H +#define LL_LLINVENTORYGALLERY_H + +#include "llgesturemgr.h" +#include "lllistcontextmenu.h" +#include "llpanel.h" +#include "llinventoryfilter.h" +#include "llinventoryobserver.h" +#include "llinventorymodel.h" + +class LLInventoryCategoriesObserver; +class LLInventoryGalleryItem; +class LLScrollContainer; +class LLTextBox; +class LLThumbnailsObserver; +class LLThumbnailCtrl; +class LLGalleryGestureObserver; + +class LLInventoryGalleryContextMenu; + +typedef boost::function callback_t; + +class LLInventoryGallery : public LLPanel, public LLEditMenuHandler +{ +public: + + typedef boost::signals2::signal selection_change_signal_t; + typedef boost::function selection_change_callback_t; + typedef std::deque selection_deque; + + struct Params + : public LLInitParam::Block + { + Optional row_panel_height; + Optional row_panel_width_factor; + Optional gallery_width_factor; + Optional vertical_gap; + Optional horizontal_gap; + Optional item_width; + Optional item_height; + Optional item_horizontal_gap; + Optional items_in_row; + + Params(); + }; + + static const LLInventoryGallery::Params& getDefaultParams(); + + LLInventoryGallery(const LLInventoryGallery::Params& params = getDefaultParams()); + ~LLInventoryGallery(); + + bool postBuild() override; + void initGallery(); + void draw() override; + void onVisibilityChange(bool new_visibility) override; + bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, + void* cargo_data, EAcceptance* accept, std::string& tooltip_msg) override; + void startDrag(); + bool handleRightMouseDown(S32 x, S32 y, MASK mask) override; + bool handleKeyHere(KEY key, MASK mask) override; + void moveUp(MASK mask); + void moveDown(MASK mask); + void moveLeft(MASK mask); + void moveRight(MASK mask); + void toggleSelectionRange(S32 start_idx, S32 end_idx); + void toggleSelectionRangeFromLast(const LLUUID target); + + void onFocusLost() override; + void onFocusReceived() override; + + void setFilterSubString(const std::string& string); + std::string getFilterSubString() { return mFilterSubString; } + LLInventoryFilter& getFilter() const { return *mFilter; } + bool checkAgainstFilterType(const LLUUID& object_id); + + void getCurrentCategories(uuid_vec_t& vcur); + bool updateAddedItem(LLUUID item_id); // returns true if added item is visible + void updateRemovedItem(LLUUID item_id); + void updateChangedItemName(LLUUID item_id, std::string name); + void updateItemThumbnail(LLUUID item_id); + void updateWornItem(LLUUID item_id, bool is_worn); + + void updateMessageVisibility(); + + void setRootFolder(const LLUUID cat_id); + void updateRootFolder(); + LLUUID getRootFolder() { return mFolderID; } + bool isRootDirty() { return mRootDirty; } + boost::signals2::connection setRootChangedCallback(callback_t cb); + void onForwardFolder(); + void onBackwardFolder(); + void clearNavigationHistory(); + bool isBackwardAvailable(); + bool isForwardAvailable(); + + void setNavBackwardList(std::list backward_list) { mBackwardFolders = backward_list; } + void setNavForwardList(std::list forward_list) { mForwardFolders = forward_list; } + std::list getNavBackwardList() { return mBackwardFolders; } + std::list getNavForwardList() { return mForwardFolders; } + + LLUUID getOutfitImageID(LLUUID outfit_id); + + void refreshList(const LLUUID& category_id); + void onCOFChanged(); + void onGesturesChanged(); + void computeDifference(const LLInventoryModel::cat_array_t vcats, const LLInventoryModel::item_array_t vitems, uuid_vec_t& vadded, uuid_vec_t& vremoved); + + void deselectItem(const LLUUID& category_id); + void clearSelection(); + void changeItemSelection(const LLUUID& item_id, bool scroll_to_selection = false); + void addItemSelection(const LLUUID& item_id, bool scroll_to_selection = false); + bool toggleItemSelection(const LLUUID& item_id, bool scroll_to_selection = false); + void scrollToShowItem(const LLUUID& item_id); + void signalSelectionItemID(const LLUUID& category_id); + boost::signals2::connection setSelectionChangeCallback(selection_change_callback_t cb); + LLUUID getFirstSelectedItemID(); + + void setSearchType(LLInventoryFilter::ESearchType type); + LLInventoryFilter::ESearchType getSearchType() { return mSearchType; } + + bool areViewsInitialized(); + bool hasDescendents(const LLUUID& cat_id); + bool hasVisibleItems(); + void handleModifiedFilter(); + LLScrollContainer* getScrollableContainer() { return mScrollPanel; } + LLInventoryGalleryItem* getFirstSelectedItem(); + + // Copy & paste (LLEditMenuHandler) + void copy() override; + bool canCopy() const override; + + void cut() override; + bool canCut() const override; + + void paste() override; + bool canPaste() const override; + + // Copy & paste & delete + static void onDelete(const LLSD& notification, const LLSD& response, const selection_deque selected_ids); + void deleteSelection(); + bool canDeleteSelection(); + void pasteAsLink(); + void doCreate(const LLUUID& dest, const LLSD& userdata); + + void setSortOrder(U32 order, bool update = false); + U32 getSortOrder() { return mSortOrder; }; + + void claimEditHandler(); + void resetEditHandler(); + static bool isItemCopyable(const LLUUID & item_id); + + bool baseHandleDragAndDrop(LLUUID dest_id, bool drop, EDragAndDropType cargo_type, + void* cargo_data, EAcceptance* accept, std::string& tooltip_msg); + + void showContextMenu(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& item_id); + +protected: + void paste(const LLUUID& dest, + std::vector& objects, + bool is_cut_mode, + const LLUUID& marketplacelistings_id); + void pasteAsLink(const LLUUID& dest, + std::vector& objects, + const LLUUID& current_outfit_id, + const LLUUID& marketplacelistings_id, + const LLUUID& my_outifts_id); + + bool applyFilter(LLInventoryGalleryItem* item, const std::string& filter_substring); + bool checkAgainstFilters(LLInventoryGalleryItem* item, const std::string& filter_substring); + static void onIdle(void* userdata); + void dirtyRootFolder(); + + LLInventoryCategoriesObserver* mCategoriesObserver; + LLThumbnailsObserver* mThumbnailsObserver; + LLGalleryGestureObserver* mGestureObserver; + LLInventoryObserver* mInventoryObserver; + selection_deque mSelectedItemIDs; + selection_deque mItemsToSelect; + LLUUID mLastInteractedUUID; + bool mIsInitialized; + bool mRootDirty; + + selection_change_signal_t mSelectionChangeSignal; + boost::signals2::signal mRootChangedSignal; + LLUUID mFolderID; + std::list mBackwardFolders; + std::list mForwardFolders; + +private: + void addToGallery(LLInventoryGalleryItem* item); + void removeFromGalleryLast(LLInventoryGalleryItem* item, bool needs_reshape = true); + void removeFromGalleryMiddle(LLInventoryGalleryItem* item); + LLPanel* addLastRow(); + void removeLastRow(); + void moveRowUp(int row); + void moveRowDown(int row); + void moveRow(int row, int pos); + LLPanel* addToRow(LLPanel* row_stack, LLInventoryGalleryItem* item, int pos, int hgap); + void removeFromLastRow(LLInventoryGalleryItem* item); + void reArrangeRows(S32 row_diff = 0); + bool updateRowsIfNeeded(); + void updateGalleryWidth(); + + LLInventoryGalleryItem* buildGalleryItem(std::string name, LLUUID item_id, LLAssetType::EType type, LLUUID thumbnail_id, LLInventoryType::EType inventory_type, U32 flags, time_t creation_date, bool is_link, bool is_worn); + + void buildGalleryPanel(int row_count); + void reshapeGalleryPanel(int row_count); + LLPanel* buildItemPanel(int left); + LLPanel* buildRowPanel(int left, int bottom); + void moveRowPanel(LLPanel* stack, int left, int bottom); + + std::vector mRowPanels; + std::vector mItemPanels; + std::vector mUnusedRowPanels; + std::vector mUnusedItemPanels; + std::vector mItems; + std::vector mHiddenItems; + LLScrollContainer* mScrollPanel; + LLPanel* mGalleryPanel; + LLPanel* mLastRowPanel; + LLTextBox* mMessageTextBox; + int mRowCount; + int mItemsAddedCount; + bool mGalleryCreated; + bool mLoadThumbnailsImmediately; + bool mNeedsArrange; + + /* Params */ + int mRowPanelHeight; + int mVerticalGap; + int mHorizontalGap; + int mItemWidth; + int mItemHeight; + int mItemHorizontalGap; + int mItemsInRow; + int mRowPanelWidth; + int mGalleryWidth; + int mRowPanWidthFactor; + int mGalleryWidthFactor; + + LLInventoryGalleryContextMenu* mInventoryGalleryMenu; + LLInventoryGalleryContextMenu* mRootGalleryMenu; + std::string mFilterSubString; + LLInventoryFilter* mFilter; + U32 mSortOrder; + + typedef std::map gallery_item_map_t; + gallery_item_map_t mItemMap; + uuid_vec_t mCOFLinkedItems; + uuid_vec_t mActiveGestures; + uuid_set_t mItemBuildQuery; + std::map mItemIndexMap; + std::map mIndexToItemMap; + + LLInventoryFilter::ESearchType mSearchType; + std::string mUsername; +}; + +class LLInventoryGalleryItem : public LLPanel +{ +public: + struct Params : public LLInitParam::Block + {}; + + enum EInventorySortGroup + { + SG_SYSTEM_FOLDER, + SG_TRASH_FOLDER, + SG_NORMAL_FOLDER, + SG_ITEM + }; + + LLInventoryGalleryItem(const Params& p); + virtual ~LLInventoryGalleryItem(); + + bool postBuild(); + void draw(); + bool handleMouseDown(S32 x, S32 y, MASK mask); + bool handleRightMouseDown(S32 x, S32 y, MASK mask); + bool handleDoubleClick(S32 x, S32 y, MASK mask); + bool handleMouseUp(S32 x, S32 y, MASK mask); + bool handleHover(S32 x, S32 y, MASK mask); + bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + bool handleKeyHere(KEY key, MASK mask); + + void onFocusLost(); + void onFocusReceived(); + + LLFontGL* getTextFont(); + + void setItemName(std::string name); + bool isSelected() { return mSelected; } + void setSelected(bool value); + void setWorn(bool value); + void setUUID(LLUUID id) {mUUID = id;} + LLUUID getUUID() { return mUUID;} + + void setAssetIDStr(std::string asset_id) {mAssetIDStr = asset_id;} + std::string getAssetIDStr() { return mAssetIDStr;} + void setDescription(std::string desc) {mDesc = desc;} + std::string getDescription() { return mDesc;} + void setCreatorName(std::string name) {mCreatorName = name;} + std::string getCreatorName() { return mCreatorName;} + void setCreationDate(time_t date) {mCreationDate = date;} + time_t getCreationDate() { return mCreationDate;} + + std::string getItemName() {return mItemName;} + std::string getItemNameSuffix() {return mPermSuffix + mWornSuffix;} + bool isDefaultImage() {return mDefaultImage;} + + bool isHidden() {return mHidden;} + void setHidden(bool hidden) {mHidden = hidden;} + + void setType(LLAssetType::EType type, LLInventoryType::EType inventory_type, U32 flags, bool is_link); + LLAssetType::EType getAssetType() { return mType; } + void setThumbnail(LLUUID id); + void setGallery(LLInventoryGallery* gallery) { mGallery = gallery; } + void setLoadImmediately(bool val); + bool isFolder() { return mIsFolder; } + bool isLink() { return mIsLink; } + EInventorySortGroup getSortGroup() { return mSortGroup; } + + void updateNameText(); + +private: + bool isFadeItem(); + + LLUUID mUUID; + LLTextBox* mNameText; + LLPanel* mTextBgPanel; + LLThumbnailCtrl* mThumbnailCtrl; + bool mSelected; + bool mWorn; + bool mDefaultImage; + bool mHidden; + bool mIsFolder; + bool mIsLink; + S32 mCutGeneration; + bool mSelectedForCut; + + std::string mAssetIDStr; + std::string mDesc; + std::string mCreatorName; + time_t mCreationDate; + + EInventorySortGroup mSortGroup; + LLAssetType::EType mType; + std::string mItemName; + std::string mWornSuffix; + std::string mPermSuffix; + LLInventoryGallery* mGallery; +}; + +class LLThumbnailsObserver : public LLInventoryObserver +{ +public: + LLThumbnailsObserver(){}; + + virtual void changed(U32 mask); + bool addItem(const LLUUID& obj_id, callback_t cb); + void removeItem(const LLUUID& obj_id); + +protected: + + struct LLItemData + { + LLItemData(const LLUUID& obj_id, const LLUUID& thumbnail_id, callback_t cb) + : mItemID(obj_id) + , mCallback(cb) + , mThumbnailID(thumbnail_id) + {} + + callback_t mCallback; + LLUUID mItemID; + LLUUID mThumbnailID; + }; + + typedef std::map item_map_t; + typedef item_map_t::value_type item_map_value_t; + item_map_t mItemMap; +}; + +class LLGalleryGestureObserver : public LLGestureManagerObserver +{ +public: + LLGalleryGestureObserver(LLInventoryGallery* gallery) : mGallery(gallery) {} + virtual ~LLGalleryGestureObserver() {} + virtual void changed() { mGallery->onGesturesChanged(); } + +private: + LLInventoryGallery* mGallery; +}; + +#endif diff --git a/indra/newview/llinventoryicon.cpp b/indra/newview/llinventoryicon.cpp index 2dff50a7c3..94b8c4bebf 100644 --- a/indra/newview/llinventoryicon.cpp +++ b/indra/newview/llinventoryicon.cpp @@ -1,211 +1,211 @@ -/** - * @file llinventoryicon.cpp - * @brief Implementation of the inventory icon. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "linden_common.h" -#include "llinventoryicon.h" - -#include "lldictionary.h" -#include "llinventorydefines.h" -#include "llui.h" -#include "lluiimage.h" -#include "llwearabletype.h" -#include "llinventorysettings.h" - -struct IconEntry : public LLDictionaryEntry -{ - IconEntry(const std::string &item_name) - : - LLDictionaryEntry(item_name) - {} -}; - -class LLIconDictionary : public LLSingleton, - public LLDictionary -{ - LLSINGLETON(LLIconDictionary); -}; - -typedef LLPointer LLUIImagePtr; - -LLIconDictionary::LLIconDictionary() -{ - addEntry(LLInventoryType::ICONNAME_TEXTURE, new IconEntry("Inv_Texture")); - addEntry(LLInventoryType::ICONNAME_SOUND, new IconEntry("Inv_Sound")); - addEntry(LLInventoryType::ICONNAME_CALLINGCARD_ONLINE, new IconEntry("Inv_CallingCard")); - addEntry(LLInventoryType::ICONNAME_CALLINGCARD_OFFLINE, new IconEntry("Inv_CallingCard")); - addEntry(LLInventoryType::ICONNAME_LANDMARK, new IconEntry("Inv_Landmark")); - addEntry(LLInventoryType::ICONNAME_LANDMARK_VISITED, new IconEntry("Inv_Landmark")); - addEntry(LLInventoryType::ICONNAME_SCRIPT, new IconEntry("Inv_Script")); - addEntry(LLInventoryType::ICONNAME_CLOTHING, new IconEntry("Inv_Clothing")); - addEntry(LLInventoryType::ICONNAME_OBJECT, new IconEntry("Inv_Object")); - addEntry(LLInventoryType::ICONNAME_OBJECT_MULTI, new IconEntry("Inv_Object_Multi")); - addEntry(LLInventoryType::ICONNAME_NOTECARD, new IconEntry("Inv_Notecard")); - addEntry(LLInventoryType::ICONNAME_BODYPART, new IconEntry("Inv_Skin")); - addEntry(LLInventoryType::ICONNAME_SNAPSHOT, new IconEntry("Inv_Snapshot")); - - addEntry(LLInventoryType::ICONNAME_BODYPART_SHAPE, new IconEntry("Inv_BodyShape")); - addEntry(LLInventoryType::ICONNAME_BODYPART_SKIN, new IconEntry("Inv_Skin")); - addEntry(LLInventoryType::ICONNAME_BODYPART_HAIR, new IconEntry("Inv_Hair")); - addEntry(LLInventoryType::ICONNAME_BODYPART_EYES, new IconEntry("Inv_Eye")); - - addEntry(LLInventoryType::ICONNAME_CLOTHING_SHIRT, new IconEntry("Inv_Shirt")); - addEntry(LLInventoryType::ICONNAME_CLOTHING_PANTS, new IconEntry("Inv_Pants")); - addEntry(LLInventoryType::ICONNAME_CLOTHING_SHOES, new IconEntry("Inv_Shoe")); - addEntry(LLInventoryType::ICONNAME_CLOTHING_SOCKS, new IconEntry("Inv_Socks")); - addEntry(LLInventoryType::ICONNAME_CLOTHING_JACKET, new IconEntry("Inv_Jacket")); - addEntry(LLInventoryType::ICONNAME_CLOTHING_GLOVES, new IconEntry("Inv_Gloves")); - addEntry(LLInventoryType::ICONNAME_CLOTHING_UNDERSHIRT, new IconEntry("Inv_Undershirt")); - addEntry(LLInventoryType::ICONNAME_CLOTHING_UNDERPANTS, new IconEntry("Inv_Underpants")); - addEntry(LLInventoryType::ICONNAME_CLOTHING_SKIRT, new IconEntry("Inv_Skirt")); - addEntry(LLInventoryType::ICONNAME_CLOTHING_ALPHA, new IconEntry("Inv_Alpha")); - addEntry(LLInventoryType::ICONNAME_CLOTHING_TATTOO, new IconEntry("Inv_Tattoo")); - addEntry(LLInventoryType::ICONNAME_CLOTHING_UNIVERSAL, new IconEntry("Inv_Universal")); - addEntry(LLInventoryType::ICONNAME_ANIMATION, new IconEntry("Inv_Animation")); - addEntry(LLInventoryType::ICONNAME_GESTURE, new IconEntry("Inv_Gesture")); - - addEntry(LLInventoryType::ICONNAME_CLOTHING_PHYSICS, new IconEntry("Inv_Physics")); - - addEntry(LLInventoryType::ICONNAME_LINKITEM, new IconEntry("Inv_LinkItem")); - addEntry(LLInventoryType::ICONNAME_LINKFOLDER, new IconEntry("Inv_LinkFolder")); - addEntry(LLInventoryType::ICONNAME_MESH, new IconEntry("Inv_Mesh")); - - addEntry(LLInventoryType::ICONNAME_SETTINGS_SKY, new IconEntry("Inv_SettingsSky")); - addEntry(LLInventoryType::ICONNAME_SETTINGS_WATER, new IconEntry("Inv_SettingsWater")); - addEntry(LLInventoryType::ICONNAME_SETTINGS_DAY, new IconEntry("Inv_SettingsDay")); - addEntry(LLInventoryType::ICONNAME_SETTINGS, new IconEntry("Inv_Settings")); - - addEntry(LLInventoryType::ICONNAME_MATERIAL, new IconEntry("Inv_Material")); - - addEntry(LLInventoryType::ICONNAME_INVALID, new IconEntry("Inv_Invalid")); - addEntry(LLInventoryType::ICONNAME_UNKNOWN, new IconEntry("Inv_Unknown")); - - addEntry(LLInventoryType::ICONNAME_NONE, new IconEntry("NONE")); -} - -LLUIImagePtr LLInventoryIcon::getIcon(LLAssetType::EType asset_type, - LLInventoryType::EType inventory_type, - U32 misc_flag, - bool item_is_multi) -{ - const std::string& icon_name = getIconName(asset_type, inventory_type, misc_flag, item_is_multi); - return LLUI::getUIImage(icon_name); -} - -LLUIImagePtr LLInventoryIcon::getIcon(LLInventoryType::EIconName idx) -{ - return LLUI::getUIImage(getIconName(idx)); -} - -const std::string& LLInventoryIcon::getIconName(LLAssetType::EType asset_type, - LLInventoryType::EType inventory_type, - U32 misc_flag, - bool item_is_multi) -{ - LLInventoryType::EIconName idx = LLInventoryType::ICONNAME_OBJECT; - if (item_is_multi) - { - idx = LLInventoryType::ICONNAME_OBJECT_MULTI; - return getIconName(idx); - } - - switch(asset_type) - { - case LLAssetType::AT_TEXTURE: - idx = (inventory_type == LLInventoryType::IT_SNAPSHOT) ? LLInventoryType::ICONNAME_SNAPSHOT : LLInventoryType::ICONNAME_TEXTURE; - break; - case LLAssetType::AT_SOUND: - idx = LLInventoryType::ICONNAME_SOUND; - break; - case LLAssetType::AT_CALLINGCARD: - idx = (misc_flag != 0) ? LLInventoryType::ICONNAME_CALLINGCARD_ONLINE : LLInventoryType::ICONNAME_CALLINGCARD_OFFLINE; - break; - case LLAssetType::AT_LANDMARK: - idx = (misc_flag != 0) ? LLInventoryType::ICONNAME_LANDMARK_VISITED : LLInventoryType::ICONNAME_LANDMARK; - break; - case LLAssetType::AT_SCRIPT: - case LLAssetType::AT_LSL_TEXT: - case LLAssetType::AT_LSL_BYTECODE: - idx = LLInventoryType::ICONNAME_SCRIPT; - break; - case LLAssetType::AT_CLOTHING: - case LLAssetType::AT_BODYPART: - idx = assignWearableIcon(misc_flag); - break; - case LLAssetType::AT_NOTECARD: - idx = LLInventoryType::ICONNAME_NOTECARD; - break; - case LLAssetType::AT_ANIMATION: - idx = LLInventoryType::ICONNAME_ANIMATION; - break; - case LLAssetType::AT_GESTURE: - idx = LLInventoryType::ICONNAME_GESTURE; - break; - case LLAssetType::AT_LINK: - idx = LLInventoryType::ICONNAME_LINKITEM; - break; - case LLAssetType::AT_LINK_FOLDER: - idx = LLInventoryType::ICONNAME_LINKFOLDER; - break; - case LLAssetType::AT_OBJECT: - idx = LLInventoryType::ICONNAME_OBJECT; - break; - case LLAssetType::AT_MESH: - idx = LLInventoryType::ICONNAME_MESH; - case LLAssetType::AT_SETTINGS: - idx = assignSettingsIcon(misc_flag); - break; - case LLAssetType::AT_MATERIAL: - idx = LLInventoryType::ICONNAME_MATERIAL; - break; - case LLAssetType::AT_UNKNOWN: - idx = LLInventoryType::ICONNAME_UNKNOWN; - default: - break; - } - - return getIconName(idx); -} - - -const std::string& LLInventoryIcon::getIconName(LLInventoryType::EIconName idx) -{ - const IconEntry *entry = LLIconDictionary::instance().lookup(idx); - return entry->mName; -} - -LLInventoryType::EIconName LLInventoryIcon::assignWearableIcon(U32 misc_flag) -{ - const LLWearableType::EType wearable_type = LLWearableType::inventoryFlagsToWearableType(misc_flag); - return LLWearableType::getInstance()->getIconName(wearable_type); -} - -LLInventoryType::EIconName LLInventoryIcon::assignSettingsIcon(U32 misc_flag) -{ - LLSettingsType::type_e settings_type = LLSettingsType::fromInventoryFlags(misc_flag); - return LLSettingsType::getIconName(settings_type); -} +/** + * @file llinventoryicon.cpp + * @brief Implementation of the inventory icon. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "linden_common.h" +#include "llinventoryicon.h" + +#include "lldictionary.h" +#include "llinventorydefines.h" +#include "llui.h" +#include "lluiimage.h" +#include "llwearabletype.h" +#include "llinventorysettings.h" + +struct IconEntry : public LLDictionaryEntry +{ + IconEntry(const std::string &item_name) + : + LLDictionaryEntry(item_name) + {} +}; + +class LLIconDictionary : public LLSingleton, + public LLDictionary +{ + LLSINGLETON(LLIconDictionary); +}; + +typedef LLPointer LLUIImagePtr; + +LLIconDictionary::LLIconDictionary() +{ + addEntry(LLInventoryType::ICONNAME_TEXTURE, new IconEntry("Inv_Texture")); + addEntry(LLInventoryType::ICONNAME_SOUND, new IconEntry("Inv_Sound")); + addEntry(LLInventoryType::ICONNAME_CALLINGCARD_ONLINE, new IconEntry("Inv_CallingCard")); + addEntry(LLInventoryType::ICONNAME_CALLINGCARD_OFFLINE, new IconEntry("Inv_CallingCard")); + addEntry(LLInventoryType::ICONNAME_LANDMARK, new IconEntry("Inv_Landmark")); + addEntry(LLInventoryType::ICONNAME_LANDMARK_VISITED, new IconEntry("Inv_Landmark")); + addEntry(LLInventoryType::ICONNAME_SCRIPT, new IconEntry("Inv_Script")); + addEntry(LLInventoryType::ICONNAME_CLOTHING, new IconEntry("Inv_Clothing")); + addEntry(LLInventoryType::ICONNAME_OBJECT, new IconEntry("Inv_Object")); + addEntry(LLInventoryType::ICONNAME_OBJECT_MULTI, new IconEntry("Inv_Object_Multi")); + addEntry(LLInventoryType::ICONNAME_NOTECARD, new IconEntry("Inv_Notecard")); + addEntry(LLInventoryType::ICONNAME_BODYPART, new IconEntry("Inv_Skin")); + addEntry(LLInventoryType::ICONNAME_SNAPSHOT, new IconEntry("Inv_Snapshot")); + + addEntry(LLInventoryType::ICONNAME_BODYPART_SHAPE, new IconEntry("Inv_BodyShape")); + addEntry(LLInventoryType::ICONNAME_BODYPART_SKIN, new IconEntry("Inv_Skin")); + addEntry(LLInventoryType::ICONNAME_BODYPART_HAIR, new IconEntry("Inv_Hair")); + addEntry(LLInventoryType::ICONNAME_BODYPART_EYES, new IconEntry("Inv_Eye")); + + addEntry(LLInventoryType::ICONNAME_CLOTHING_SHIRT, new IconEntry("Inv_Shirt")); + addEntry(LLInventoryType::ICONNAME_CLOTHING_PANTS, new IconEntry("Inv_Pants")); + addEntry(LLInventoryType::ICONNAME_CLOTHING_SHOES, new IconEntry("Inv_Shoe")); + addEntry(LLInventoryType::ICONNAME_CLOTHING_SOCKS, new IconEntry("Inv_Socks")); + addEntry(LLInventoryType::ICONNAME_CLOTHING_JACKET, new IconEntry("Inv_Jacket")); + addEntry(LLInventoryType::ICONNAME_CLOTHING_GLOVES, new IconEntry("Inv_Gloves")); + addEntry(LLInventoryType::ICONNAME_CLOTHING_UNDERSHIRT, new IconEntry("Inv_Undershirt")); + addEntry(LLInventoryType::ICONNAME_CLOTHING_UNDERPANTS, new IconEntry("Inv_Underpants")); + addEntry(LLInventoryType::ICONNAME_CLOTHING_SKIRT, new IconEntry("Inv_Skirt")); + addEntry(LLInventoryType::ICONNAME_CLOTHING_ALPHA, new IconEntry("Inv_Alpha")); + addEntry(LLInventoryType::ICONNAME_CLOTHING_TATTOO, new IconEntry("Inv_Tattoo")); + addEntry(LLInventoryType::ICONNAME_CLOTHING_UNIVERSAL, new IconEntry("Inv_Universal")); + addEntry(LLInventoryType::ICONNAME_ANIMATION, new IconEntry("Inv_Animation")); + addEntry(LLInventoryType::ICONNAME_GESTURE, new IconEntry("Inv_Gesture")); + + addEntry(LLInventoryType::ICONNAME_CLOTHING_PHYSICS, new IconEntry("Inv_Physics")); + + addEntry(LLInventoryType::ICONNAME_LINKITEM, new IconEntry("Inv_LinkItem")); + addEntry(LLInventoryType::ICONNAME_LINKFOLDER, new IconEntry("Inv_LinkFolder")); + addEntry(LLInventoryType::ICONNAME_MESH, new IconEntry("Inv_Mesh")); + + addEntry(LLInventoryType::ICONNAME_SETTINGS_SKY, new IconEntry("Inv_SettingsSky")); + addEntry(LLInventoryType::ICONNAME_SETTINGS_WATER, new IconEntry("Inv_SettingsWater")); + addEntry(LLInventoryType::ICONNAME_SETTINGS_DAY, new IconEntry("Inv_SettingsDay")); + addEntry(LLInventoryType::ICONNAME_SETTINGS, new IconEntry("Inv_Settings")); + + addEntry(LLInventoryType::ICONNAME_MATERIAL, new IconEntry("Inv_Material")); + + addEntry(LLInventoryType::ICONNAME_INVALID, new IconEntry("Inv_Invalid")); + addEntry(LLInventoryType::ICONNAME_UNKNOWN, new IconEntry("Inv_Unknown")); + + addEntry(LLInventoryType::ICONNAME_NONE, new IconEntry("NONE")); +} + +LLUIImagePtr LLInventoryIcon::getIcon(LLAssetType::EType asset_type, + LLInventoryType::EType inventory_type, + U32 misc_flag, + bool item_is_multi) +{ + const std::string& icon_name = getIconName(asset_type, inventory_type, misc_flag, item_is_multi); + return LLUI::getUIImage(icon_name); +} + +LLUIImagePtr LLInventoryIcon::getIcon(LLInventoryType::EIconName idx) +{ + return LLUI::getUIImage(getIconName(idx)); +} + +const std::string& LLInventoryIcon::getIconName(LLAssetType::EType asset_type, + LLInventoryType::EType inventory_type, + U32 misc_flag, + bool item_is_multi) +{ + LLInventoryType::EIconName idx = LLInventoryType::ICONNAME_OBJECT; + if (item_is_multi) + { + idx = LLInventoryType::ICONNAME_OBJECT_MULTI; + return getIconName(idx); + } + + switch(asset_type) + { + case LLAssetType::AT_TEXTURE: + idx = (inventory_type == LLInventoryType::IT_SNAPSHOT) ? LLInventoryType::ICONNAME_SNAPSHOT : LLInventoryType::ICONNAME_TEXTURE; + break; + case LLAssetType::AT_SOUND: + idx = LLInventoryType::ICONNAME_SOUND; + break; + case LLAssetType::AT_CALLINGCARD: + idx = (misc_flag != 0) ? LLInventoryType::ICONNAME_CALLINGCARD_ONLINE : LLInventoryType::ICONNAME_CALLINGCARD_OFFLINE; + break; + case LLAssetType::AT_LANDMARK: + idx = (misc_flag != 0) ? LLInventoryType::ICONNAME_LANDMARK_VISITED : LLInventoryType::ICONNAME_LANDMARK; + break; + case LLAssetType::AT_SCRIPT: + case LLAssetType::AT_LSL_TEXT: + case LLAssetType::AT_LSL_BYTECODE: + idx = LLInventoryType::ICONNAME_SCRIPT; + break; + case LLAssetType::AT_CLOTHING: + case LLAssetType::AT_BODYPART: + idx = assignWearableIcon(misc_flag); + break; + case LLAssetType::AT_NOTECARD: + idx = LLInventoryType::ICONNAME_NOTECARD; + break; + case LLAssetType::AT_ANIMATION: + idx = LLInventoryType::ICONNAME_ANIMATION; + break; + case LLAssetType::AT_GESTURE: + idx = LLInventoryType::ICONNAME_GESTURE; + break; + case LLAssetType::AT_LINK: + idx = LLInventoryType::ICONNAME_LINKITEM; + break; + case LLAssetType::AT_LINK_FOLDER: + idx = LLInventoryType::ICONNAME_LINKFOLDER; + break; + case LLAssetType::AT_OBJECT: + idx = LLInventoryType::ICONNAME_OBJECT; + break; + case LLAssetType::AT_MESH: + idx = LLInventoryType::ICONNAME_MESH; + case LLAssetType::AT_SETTINGS: + idx = assignSettingsIcon(misc_flag); + break; + case LLAssetType::AT_MATERIAL: + idx = LLInventoryType::ICONNAME_MATERIAL; + break; + case LLAssetType::AT_UNKNOWN: + idx = LLInventoryType::ICONNAME_UNKNOWN; + default: + break; + } + + return getIconName(idx); +} + + +const std::string& LLInventoryIcon::getIconName(LLInventoryType::EIconName idx) +{ + const IconEntry *entry = LLIconDictionary::instance().lookup(idx); + return entry->mName; +} + +LLInventoryType::EIconName LLInventoryIcon::assignWearableIcon(U32 misc_flag) +{ + const LLWearableType::EType wearable_type = LLWearableType::inventoryFlagsToWearableType(misc_flag); + return LLWearableType::getInstance()->getIconName(wearable_type); +} + +LLInventoryType::EIconName LLInventoryIcon::assignSettingsIcon(U32 misc_flag) +{ + LLSettingsType::type_e settings_type = LLSettingsType::fromInventoryFlags(misc_flag); + return LLSettingsType::getIconName(settings_type); +} diff --git a/indra/newview/llinventoryicon.h b/indra/newview/llinventoryicon.h index 567758c833..32e2d8b29d 100644 --- a/indra/newview/llinventoryicon.h +++ b/indra/newview/llinventoryicon.h @@ -1,56 +1,56 @@ -/** - * @file llinventoryicon.h - * @brief Miscellaneous inventory-related functions and classes - * class definition - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLINVENTORYICON_H -#define LL_LLINVENTORYICON_H - -#include "llassettype.h" -#include "llinventorytype.h" - -class LLInventoryIcon -{ -public: - static const std::string& getIconName(LLAssetType::EType asset_type, - LLInventoryType::EType inventory_type = LLInventoryType::IT_NONE, - U32 misc_flag = 0, // different meanings depending on item type - bool item_is_multi = false); - static const std::string& getIconName(LLInventoryType::EIconName idx); - - static LLPointer getIcon(LLAssetType::EType asset_type, - LLInventoryType::EType inventory_type = LLInventoryType::IT_NONE, - U32 misc_flag = 0, // different meanings depending on item type - bool item_is_multi = false); - static LLPointer getIcon(LLInventoryType::EIconName idx); - -protected: - static LLInventoryType::EIconName assignWearableIcon(U32 misc_flag); - static LLInventoryType::EIconName assignSettingsIcon(U32 misc_flag); -}; -#endif // LL_LLINVENTORYICON_H - - - +/** + * @file llinventoryicon.h + * @brief Miscellaneous inventory-related functions and classes + * class definition + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLINVENTORYICON_H +#define LL_LLINVENTORYICON_H + +#include "llassettype.h" +#include "llinventorytype.h" + +class LLInventoryIcon +{ +public: + static const std::string& getIconName(LLAssetType::EType asset_type, + LLInventoryType::EType inventory_type = LLInventoryType::IT_NONE, + U32 misc_flag = 0, // different meanings depending on item type + bool item_is_multi = false); + static const std::string& getIconName(LLInventoryType::EIconName idx); + + static LLPointer getIcon(LLAssetType::EType asset_type, + LLInventoryType::EType inventory_type = LLInventoryType::IT_NONE, + U32 misc_flag = 0, // different meanings depending on item type + bool item_is_multi = false); + static LLPointer getIcon(LLInventoryType::EIconName idx); + +protected: + static LLInventoryType::EIconName assignWearableIcon(U32 misc_flag); + static LLInventoryType::EIconName assignSettingsIcon(U32 misc_flag); +}; +#endif // LL_LLINVENTORYICON_H + + + diff --git a/indra/newview/llinventorylistitem.cpp b/indra/newview/llinventorylistitem.cpp index dacdc25026..e210975a5a 100644 --- a/indra/newview/llinventorylistitem.cpp +++ b/indra/newview/llinventorylistitem.cpp @@ -1,474 +1,474 @@ -/** - * @file llinventorylistitem.cpp - * @brief Inventory list item panel. - * - * Class LLPanelInventoryListItemBase displays inventory item as an element - * of LLInventoryItemsList. - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llinventorylistitem.h" - -// llui -#include "lliconctrl.h" -#include "lltextbox.h" -#include "lltextutil.h" - -// newview -#include "llinventoryicon.h" -#include "llinventorymodel.h" -#include "llviewerinventory.h" - -static LLWidgetNameRegistry::StaticRegistrar sRegisterPanelInventoryListItemBaseParams(&typeid(LLPanelInventoryListItemBase::Params), "inventory_list_item"); - -static const S32 WIDGET_SPACING = 3; - -LLPanelInventoryListItemBase::Params::Params() -: default_style("default_style"), - worn_style("worn_style"), - hover_image("hover_image"), - selected_image("selected_image"), - separator_image("separator_image"), - item_icon("item_icon"), - item_name("item_name") -{}; - -LLPanelInventoryListItemBase* LLPanelInventoryListItemBase::create(LLViewerInventoryItem* item) -{ - LLPanelInventoryListItemBase* list_item = NULL; - if (item) - { - const LLPanelInventoryListItemBase::Params& params = LLUICtrlFactory::getDefaultParams(); - list_item = new LLPanelInventoryListItemBase(item, params); - list_item->initFromParams(params); - list_item->postBuild(); - } - return list_item; -} - -void LLPanelInventoryListItemBase::draw() -{ - if (getNeedsRefresh()) - { - LLViewerInventoryItem* inv_item = getItem(); - if (inv_item) - { - updateItem(inv_item->getName()); - } - setNeedsRefresh(false); - } - - if (mHovered && mHoverImage) - { - mHoverImage->draw(getLocalRect()); - } - - if (mSelected && mSelectedImage) - { - mSelectedImage->draw(getLocalRect()); - } - - if (mSeparatorVisible && mSeparatorImage) - { - // place under bottom of listitem, using image height - // item_pad in list using the item should be >= image height - // to avoid cropping of top of the next item. - LLRect separator_rect = getLocalRect(); - separator_rect.mTop = separator_rect.mBottom; - separator_rect.mBottom -= mSeparatorImage->getHeight(); - F32 alpha = getCurrentTransparency(); - mSeparatorImage->draw(separator_rect, UI_VERTEX_COLOR % alpha); - } - - LLPanel::draw(); -} - -// virtual -void LLPanelInventoryListItemBase::updateItem(const std::string& name, - EItemState item_state) -{ - setIconImage(mIconImage); - setTitle(name, mHighlightedText, item_state); -} - -void LLPanelInventoryListItemBase::addWidgetToLeftSide(const std::string& name, bool show_widget/* = true*/) -{ - LLUICtrl* ctrl = findChild(name); - if(ctrl) - { - addWidgetToLeftSide(ctrl, show_widget); - } -} - -void LLPanelInventoryListItemBase::addWidgetToLeftSide(LLUICtrl* ctrl, bool show_widget/* = true*/) -{ - mLeftSideWidgets.push_back(ctrl); - setShowWidget(ctrl, show_widget); -} - -void LLPanelInventoryListItemBase::addWidgetToRightSide(const std::string& name, bool show_widget/* = true*/) -{ - LLUICtrl* ctrl = findChild(name); - if(ctrl) - { - addWidgetToRightSide(ctrl, show_widget); - } -} - -void LLPanelInventoryListItemBase::addWidgetToRightSide(LLUICtrl* ctrl, bool show_widget/* = true*/) -{ - mRightSideWidgets.push_back(ctrl); - setShowWidget(ctrl, show_widget); -} - -void LLPanelInventoryListItemBase::setShowWidget(const std::string& name, bool show) -{ - LLUICtrl* widget = findChild(name); - if(widget) - { - setShowWidget(widget, show); - } -} - -void LLPanelInventoryListItemBase::setShowWidget(LLUICtrl* ctrl, bool show) -{ - // Enable state determines whether widget may become visible in setWidgetsVisible() - ctrl->setEnabled(show); -} - -bool LLPanelInventoryListItemBase::postBuild() -{ - LLViewerInventoryItem* inv_item = getItem(); - if (inv_item) - { - mIconImage = LLInventoryIcon::getIcon(inv_item->getType(), inv_item->getInventoryType(), inv_item->getFlags(), false); - updateItem(inv_item->getName()); - } - - setNeedsRefresh(true); - - setWidgetsVisible(false); - reshapeWidgets(); - - return true; -} - -void LLPanelInventoryListItemBase::setValue(const LLSD& value) -{ - if (!value.isMap()) return; - if (!value.has("selected")) return; - mSelected = value["selected"]; -} - -bool LLPanelInventoryListItemBase::handleHover(S32 x, S32 y, MASK mask) -{ - mHovered = true; - return LLPanel::handleHover(x, y, mask); -} - -void LLPanelInventoryListItemBase::onMouseLeave(S32 x, S32 y, MASK mask) -{ - mHovered = false; - LLPanel::onMouseLeave(x, y, mask); -} - -const std::string& LLPanelInventoryListItemBase::getItemName() const -{ - LLViewerInventoryItem* inv_item = getItem(); - if (NULL == inv_item) - { - return LLStringUtil::null; - } - return inv_item->getName(); -} - -LLAssetType::EType LLPanelInventoryListItemBase::getType() const -{ - LLViewerInventoryItem* inv_item = getItem(); - if (NULL == inv_item) - { - return LLAssetType::AT_NONE; - } - return inv_item->getType(); -} - -LLWearableType::EType LLPanelInventoryListItemBase::getWearableType() const -{ - LLViewerInventoryItem* inv_item = getItem(); - if (NULL == inv_item) - { - return LLWearableType::WT_NONE; - } - return inv_item->getWearableType(); -} - -const std::string& LLPanelInventoryListItemBase::getDescription() const -{ - LLViewerInventoryItem* inv_item = getItem(); - if (NULL == inv_item) - { - return LLStringUtil::null; - } - return inv_item->getActualDescription(); -} - -time_t LLPanelInventoryListItemBase::getCreationDate() const -{ - LLViewerInventoryItem* inv_item = getItem(); - if (NULL == inv_item) - { - return 0; - } - - return inv_item->getCreationDate(); -} - -LLViewerInventoryItem* LLPanelInventoryListItemBase::getItem() const -{ - return gInventory.getItem(mInventoryItemUUID); -} - -S32 LLPanelInventoryListItemBase::notify(const LLSD& info) -{ - S32 rv = 0; - if(info.has("match_filter")) - { - mHighlightedText = info["match_filter"].asString(); - - std::string test(mTitleCtrl->getText()); - LLStringUtil::toUpper(test); - - if(mHighlightedText.empty() || std::string::npos != test.find(mHighlightedText)) - { - rv = 0; // substring is found - } - else - { - rv = -1; - } - - setNeedsRefresh(true); - } - else - { - rv = LLPanel::notify(info); - } - return rv; -} - -LLPanelInventoryListItemBase::LLPanelInventoryListItemBase(LLViewerInventoryItem* item, const LLPanelInventoryListItemBase::Params& params) -: LLPanel(params), - mInventoryItemUUID(item ? item->getUUID() : LLUUID::null), - mIconCtrl(NULL), - mTitleCtrl(NULL), - mWidgetSpacing(WIDGET_SPACING), - mLeftWidgetsWidth(0), - mRightWidgetsWidth(0), - mNeedsRefresh(false), - mHovered(false), - mSelected(false), - mSeparatorVisible(false), - mHoverImage(params.hover_image), - mSelectedImage(params.selected_image), - mSeparatorImage(params.separator_image) -{ - LLIconCtrl::Params icon_params(params.item_icon); - applyXUILayout(icon_params, this); - - mIconCtrl = LLUICtrlFactory::create(icon_params); - if (!mIconCtrl) - { - LLIconCtrl::Params icon_params; - icon_params.name = "item_icon"; - mIconCtrl = LLUICtrlFactory::create(icon_params); - } - - if (mIconCtrl) - { - addChild(mIconCtrl); - } - else - { - LL_ERRS() << "Failed to create mIconCtrl" << LL_ENDL; - } - - LLTextBox::Params text_params(params.item_name); - applyXUILayout(text_params, this); - - mTitleCtrl = LLUICtrlFactory::create(text_params); - if (!mTitleCtrl) - { - LLTextBox::Params text_params; - text_params.name = "item_title"; - mTitleCtrl = LLUICtrlFactory::create(text_params); - } - - if (mTitleCtrl) - { - addChild(mTitleCtrl); - } - else - { - LL_ERRS() << "Failed to create mTitleCtrl" << LL_ENDL; - } -} - -class WidgetVisibilityChanger -{ -public: - WidgetVisibilityChanger(bool visible) : mVisible(visible){} - void operator()(LLUICtrl* widget) - { - // Disabled widgets never become visible. see LLPanelInventoryListItemBase::setShowWidget() - widget->setVisible(mVisible && widget->getEnabled()); - } -private: - bool mVisible; -}; - -void LLPanelInventoryListItemBase::setWidgetsVisible(bool visible) -{ - std::for_each(mLeftSideWidgets.begin(), mLeftSideWidgets.end(), WidgetVisibilityChanger(visible)); - std::for_each(mRightSideWidgets.begin(), mRightSideWidgets.end(), WidgetVisibilityChanger(visible)); -} - -void LLPanelInventoryListItemBase::reshapeWidgets() -{ - // disabled reshape left for now to reserve space for 'delete' button in LLPanelClothingListItem - /*reshapeLeftWidgets();*/ - reshapeRightWidgets(); - reshapeMiddleWidgets(); -} - -void LLPanelInventoryListItemBase::setIconImage(const LLUIImagePtr& image) -{ - if(image) - { - mIconImage = image; - mIconCtrl->setImage(mIconImage); - } -} - -void LLPanelInventoryListItemBase::setTitle(const std::string& title, - const std::string& highlit_text, - EItemState item_state) -{ - mTitleCtrl->setToolTip(title); - - LLStyle::Params style_params; - - const LLPanelInventoryListItemBase::Params& params = LLUICtrlFactory::getDefaultParams(); - - switch(item_state) - { - case IS_DEFAULT: - style_params = params.default_style(); - break; - case IS_WORN: - style_params = params.worn_style(); - break; - default:; - } - - LLTextUtil::textboxSetHighlightedVal( - mTitleCtrl, - style_params, - title, - highlit_text); -} - -bool LLPanelInventoryListItemBase::handleToolTip( S32 x, S32 y, MASK mask) -{ - LLRect text_box_rect = mTitleCtrl->getRect(); - if (text_box_rect.pointInRect(x, y) && - mTitleCtrl->getTextPixelWidth() <= text_box_rect.getWidth()) - { - return false; - } - return LLPanel::handleToolTip(x, y, mask); -} - -void LLPanelInventoryListItemBase::reshapeLeftWidgets() -{ - S32 widget_left = 0; - mLeftWidgetsWidth = 0; - - widget_array_t::const_iterator it = mLeftSideWidgets.begin(); - const widget_array_t::const_iterator it_end = mLeftSideWidgets.end(); - for( ; it_end != it; ++it) - { - LLUICtrl* widget = *it; - if(!widget->getVisible()) - { - continue; - } - LLRect widget_rect(widget->getRect()); - widget_rect.setLeftTopAndSize(widget_left, widget_rect.mTop, widget_rect.getWidth(), widget_rect.getHeight()); - widget->setShape(widget_rect); - - widget_left += widget_rect.getWidth() + getWidgetSpacing(); - mLeftWidgetsWidth = widget_rect.mRight; - } -} - -void LLPanelInventoryListItemBase::reshapeRightWidgets() -{ - S32 widget_right = getLocalRect().getWidth(); - S32 widget_left = widget_right; - - widget_array_t::const_reverse_iterator it = mRightSideWidgets.rbegin(); - const widget_array_t::const_reverse_iterator it_end = mRightSideWidgets.rend(); - for( ; it_end != it; ++it) - { - LLUICtrl* widget = *it; - if(!widget->getVisible()) - { - continue; - } - LLRect widget_rect(widget->getRect()); - widget_left = widget_right - widget_rect.getWidth(); - widget_rect.setLeftTopAndSize(widget_left, widget_rect.mTop, widget_rect.getWidth(), widget_rect.getHeight()); - widget->setShape(widget_rect); - - widget_right = widget_left - getWidgetSpacing(); - } - mRightWidgetsWidth = getLocalRect().getWidth() - widget_left; -} - -void LLPanelInventoryListItemBase::reshapeMiddleWidgets() -{ - LLRect icon_rect(mIconCtrl->getRect()); - icon_rect.setLeftTopAndSize(mLeftWidgetsWidth + getWidgetSpacing(), icon_rect.mTop, - icon_rect.getWidth(), icon_rect.getHeight()); - mIconCtrl->setShape(icon_rect); - - S32 name_left = icon_rect.mRight + getWidgetSpacing(); - S32 name_right = getLocalRect().getWidth() - mRightWidgetsWidth - getWidgetSpacing(); - LLRect name_rect(mTitleCtrl->getRect()); - name_rect.set(name_left, name_rect.mTop, name_right, name_rect.mBottom); - mTitleCtrl->setShape(name_rect); -} - -// EOF +/** + * @file llinventorylistitem.cpp + * @brief Inventory list item panel. + * + * Class LLPanelInventoryListItemBase displays inventory item as an element + * of LLInventoryItemsList. + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llinventorylistitem.h" + +// llui +#include "lliconctrl.h" +#include "lltextbox.h" +#include "lltextutil.h" + +// newview +#include "llinventoryicon.h" +#include "llinventorymodel.h" +#include "llviewerinventory.h" + +static LLWidgetNameRegistry::StaticRegistrar sRegisterPanelInventoryListItemBaseParams(&typeid(LLPanelInventoryListItemBase::Params), "inventory_list_item"); + +static const S32 WIDGET_SPACING = 3; + +LLPanelInventoryListItemBase::Params::Params() +: default_style("default_style"), + worn_style("worn_style"), + hover_image("hover_image"), + selected_image("selected_image"), + separator_image("separator_image"), + item_icon("item_icon"), + item_name("item_name") +{}; + +LLPanelInventoryListItemBase* LLPanelInventoryListItemBase::create(LLViewerInventoryItem* item) +{ + LLPanelInventoryListItemBase* list_item = NULL; + if (item) + { + const LLPanelInventoryListItemBase::Params& params = LLUICtrlFactory::getDefaultParams(); + list_item = new LLPanelInventoryListItemBase(item, params); + list_item->initFromParams(params); + list_item->postBuild(); + } + return list_item; +} + +void LLPanelInventoryListItemBase::draw() +{ + if (getNeedsRefresh()) + { + LLViewerInventoryItem* inv_item = getItem(); + if (inv_item) + { + updateItem(inv_item->getName()); + } + setNeedsRefresh(false); + } + + if (mHovered && mHoverImage) + { + mHoverImage->draw(getLocalRect()); + } + + if (mSelected && mSelectedImage) + { + mSelectedImage->draw(getLocalRect()); + } + + if (mSeparatorVisible && mSeparatorImage) + { + // place under bottom of listitem, using image height + // item_pad in list using the item should be >= image height + // to avoid cropping of top of the next item. + LLRect separator_rect = getLocalRect(); + separator_rect.mTop = separator_rect.mBottom; + separator_rect.mBottom -= mSeparatorImage->getHeight(); + F32 alpha = getCurrentTransparency(); + mSeparatorImage->draw(separator_rect, UI_VERTEX_COLOR % alpha); + } + + LLPanel::draw(); +} + +// virtual +void LLPanelInventoryListItemBase::updateItem(const std::string& name, + EItemState item_state) +{ + setIconImage(mIconImage); + setTitle(name, mHighlightedText, item_state); +} + +void LLPanelInventoryListItemBase::addWidgetToLeftSide(const std::string& name, bool show_widget/* = true*/) +{ + LLUICtrl* ctrl = findChild(name); + if(ctrl) + { + addWidgetToLeftSide(ctrl, show_widget); + } +} + +void LLPanelInventoryListItemBase::addWidgetToLeftSide(LLUICtrl* ctrl, bool show_widget/* = true*/) +{ + mLeftSideWidgets.push_back(ctrl); + setShowWidget(ctrl, show_widget); +} + +void LLPanelInventoryListItemBase::addWidgetToRightSide(const std::string& name, bool show_widget/* = true*/) +{ + LLUICtrl* ctrl = findChild(name); + if(ctrl) + { + addWidgetToRightSide(ctrl, show_widget); + } +} + +void LLPanelInventoryListItemBase::addWidgetToRightSide(LLUICtrl* ctrl, bool show_widget/* = true*/) +{ + mRightSideWidgets.push_back(ctrl); + setShowWidget(ctrl, show_widget); +} + +void LLPanelInventoryListItemBase::setShowWidget(const std::string& name, bool show) +{ + LLUICtrl* widget = findChild(name); + if(widget) + { + setShowWidget(widget, show); + } +} + +void LLPanelInventoryListItemBase::setShowWidget(LLUICtrl* ctrl, bool show) +{ + // Enable state determines whether widget may become visible in setWidgetsVisible() + ctrl->setEnabled(show); +} + +bool LLPanelInventoryListItemBase::postBuild() +{ + LLViewerInventoryItem* inv_item = getItem(); + if (inv_item) + { + mIconImage = LLInventoryIcon::getIcon(inv_item->getType(), inv_item->getInventoryType(), inv_item->getFlags(), false); + updateItem(inv_item->getName()); + } + + setNeedsRefresh(true); + + setWidgetsVisible(false); + reshapeWidgets(); + + return true; +} + +void LLPanelInventoryListItemBase::setValue(const LLSD& value) +{ + if (!value.isMap()) return; + if (!value.has("selected")) return; + mSelected = value["selected"]; +} + +bool LLPanelInventoryListItemBase::handleHover(S32 x, S32 y, MASK mask) +{ + mHovered = true; + return LLPanel::handleHover(x, y, mask); +} + +void LLPanelInventoryListItemBase::onMouseLeave(S32 x, S32 y, MASK mask) +{ + mHovered = false; + LLPanel::onMouseLeave(x, y, mask); +} + +const std::string& LLPanelInventoryListItemBase::getItemName() const +{ + LLViewerInventoryItem* inv_item = getItem(); + if (NULL == inv_item) + { + return LLStringUtil::null; + } + return inv_item->getName(); +} + +LLAssetType::EType LLPanelInventoryListItemBase::getType() const +{ + LLViewerInventoryItem* inv_item = getItem(); + if (NULL == inv_item) + { + return LLAssetType::AT_NONE; + } + return inv_item->getType(); +} + +LLWearableType::EType LLPanelInventoryListItemBase::getWearableType() const +{ + LLViewerInventoryItem* inv_item = getItem(); + if (NULL == inv_item) + { + return LLWearableType::WT_NONE; + } + return inv_item->getWearableType(); +} + +const std::string& LLPanelInventoryListItemBase::getDescription() const +{ + LLViewerInventoryItem* inv_item = getItem(); + if (NULL == inv_item) + { + return LLStringUtil::null; + } + return inv_item->getActualDescription(); +} + +time_t LLPanelInventoryListItemBase::getCreationDate() const +{ + LLViewerInventoryItem* inv_item = getItem(); + if (NULL == inv_item) + { + return 0; + } + + return inv_item->getCreationDate(); +} + +LLViewerInventoryItem* LLPanelInventoryListItemBase::getItem() const +{ + return gInventory.getItem(mInventoryItemUUID); +} + +S32 LLPanelInventoryListItemBase::notify(const LLSD& info) +{ + S32 rv = 0; + if(info.has("match_filter")) + { + mHighlightedText = info["match_filter"].asString(); + + std::string test(mTitleCtrl->getText()); + LLStringUtil::toUpper(test); + + if(mHighlightedText.empty() || std::string::npos != test.find(mHighlightedText)) + { + rv = 0; // substring is found + } + else + { + rv = -1; + } + + setNeedsRefresh(true); + } + else + { + rv = LLPanel::notify(info); + } + return rv; +} + +LLPanelInventoryListItemBase::LLPanelInventoryListItemBase(LLViewerInventoryItem* item, const LLPanelInventoryListItemBase::Params& params) +: LLPanel(params), + mInventoryItemUUID(item ? item->getUUID() : LLUUID::null), + mIconCtrl(NULL), + mTitleCtrl(NULL), + mWidgetSpacing(WIDGET_SPACING), + mLeftWidgetsWidth(0), + mRightWidgetsWidth(0), + mNeedsRefresh(false), + mHovered(false), + mSelected(false), + mSeparatorVisible(false), + mHoverImage(params.hover_image), + mSelectedImage(params.selected_image), + mSeparatorImage(params.separator_image) +{ + LLIconCtrl::Params icon_params(params.item_icon); + applyXUILayout(icon_params, this); + + mIconCtrl = LLUICtrlFactory::create(icon_params); + if (!mIconCtrl) + { + LLIconCtrl::Params icon_params; + icon_params.name = "item_icon"; + mIconCtrl = LLUICtrlFactory::create(icon_params); + } + + if (mIconCtrl) + { + addChild(mIconCtrl); + } + else + { + LL_ERRS() << "Failed to create mIconCtrl" << LL_ENDL; + } + + LLTextBox::Params text_params(params.item_name); + applyXUILayout(text_params, this); + + mTitleCtrl = LLUICtrlFactory::create(text_params); + if (!mTitleCtrl) + { + LLTextBox::Params text_params; + text_params.name = "item_title"; + mTitleCtrl = LLUICtrlFactory::create(text_params); + } + + if (mTitleCtrl) + { + addChild(mTitleCtrl); + } + else + { + LL_ERRS() << "Failed to create mTitleCtrl" << LL_ENDL; + } +} + +class WidgetVisibilityChanger +{ +public: + WidgetVisibilityChanger(bool visible) : mVisible(visible){} + void operator()(LLUICtrl* widget) + { + // Disabled widgets never become visible. see LLPanelInventoryListItemBase::setShowWidget() + widget->setVisible(mVisible && widget->getEnabled()); + } +private: + bool mVisible; +}; + +void LLPanelInventoryListItemBase::setWidgetsVisible(bool visible) +{ + std::for_each(mLeftSideWidgets.begin(), mLeftSideWidgets.end(), WidgetVisibilityChanger(visible)); + std::for_each(mRightSideWidgets.begin(), mRightSideWidgets.end(), WidgetVisibilityChanger(visible)); +} + +void LLPanelInventoryListItemBase::reshapeWidgets() +{ + // disabled reshape left for now to reserve space for 'delete' button in LLPanelClothingListItem + /*reshapeLeftWidgets();*/ + reshapeRightWidgets(); + reshapeMiddleWidgets(); +} + +void LLPanelInventoryListItemBase::setIconImage(const LLUIImagePtr& image) +{ + if(image) + { + mIconImage = image; + mIconCtrl->setImage(mIconImage); + } +} + +void LLPanelInventoryListItemBase::setTitle(const std::string& title, + const std::string& highlit_text, + EItemState item_state) +{ + mTitleCtrl->setToolTip(title); + + LLStyle::Params style_params; + + const LLPanelInventoryListItemBase::Params& params = LLUICtrlFactory::getDefaultParams(); + + switch(item_state) + { + case IS_DEFAULT: + style_params = params.default_style(); + break; + case IS_WORN: + style_params = params.worn_style(); + break; + default:; + } + + LLTextUtil::textboxSetHighlightedVal( + mTitleCtrl, + style_params, + title, + highlit_text); +} + +bool LLPanelInventoryListItemBase::handleToolTip( S32 x, S32 y, MASK mask) +{ + LLRect text_box_rect = mTitleCtrl->getRect(); + if (text_box_rect.pointInRect(x, y) && + mTitleCtrl->getTextPixelWidth() <= text_box_rect.getWidth()) + { + return false; + } + return LLPanel::handleToolTip(x, y, mask); +} + +void LLPanelInventoryListItemBase::reshapeLeftWidgets() +{ + S32 widget_left = 0; + mLeftWidgetsWidth = 0; + + widget_array_t::const_iterator it = mLeftSideWidgets.begin(); + const widget_array_t::const_iterator it_end = mLeftSideWidgets.end(); + for( ; it_end != it; ++it) + { + LLUICtrl* widget = *it; + if(!widget->getVisible()) + { + continue; + } + LLRect widget_rect(widget->getRect()); + widget_rect.setLeftTopAndSize(widget_left, widget_rect.mTop, widget_rect.getWidth(), widget_rect.getHeight()); + widget->setShape(widget_rect); + + widget_left += widget_rect.getWidth() + getWidgetSpacing(); + mLeftWidgetsWidth = widget_rect.mRight; + } +} + +void LLPanelInventoryListItemBase::reshapeRightWidgets() +{ + S32 widget_right = getLocalRect().getWidth(); + S32 widget_left = widget_right; + + widget_array_t::const_reverse_iterator it = mRightSideWidgets.rbegin(); + const widget_array_t::const_reverse_iterator it_end = mRightSideWidgets.rend(); + for( ; it_end != it; ++it) + { + LLUICtrl* widget = *it; + if(!widget->getVisible()) + { + continue; + } + LLRect widget_rect(widget->getRect()); + widget_left = widget_right - widget_rect.getWidth(); + widget_rect.setLeftTopAndSize(widget_left, widget_rect.mTop, widget_rect.getWidth(), widget_rect.getHeight()); + widget->setShape(widget_rect); + + widget_right = widget_left - getWidgetSpacing(); + } + mRightWidgetsWidth = getLocalRect().getWidth() - widget_left; +} + +void LLPanelInventoryListItemBase::reshapeMiddleWidgets() +{ + LLRect icon_rect(mIconCtrl->getRect()); + icon_rect.setLeftTopAndSize(mLeftWidgetsWidth + getWidgetSpacing(), icon_rect.mTop, + icon_rect.getWidth(), icon_rect.getHeight()); + mIconCtrl->setShape(icon_rect); + + S32 name_left = icon_rect.mRight + getWidgetSpacing(); + S32 name_right = getLocalRect().getWidth() - mRightWidgetsWidth - getWidgetSpacing(); + LLRect name_rect(mTitleCtrl->getRect()); + name_rect.set(name_left, name_rect.mTop, name_right, name_rect.mBottom); + mTitleCtrl->setShape(name_rect); +} + +// EOF diff --git a/indra/newview/llinventorylistitem.h b/indra/newview/llinventorylistitem.h index 4a8d1124e9..21540a380b 100644 --- a/indra/newview/llinventorylistitem.h +++ b/indra/newview/llinventorylistitem.h @@ -1,239 +1,239 @@ -/** - * @file llinventorylistitem.h - * @brief Inventory list item panel. - * - * Class LLPanelInventoryListItemBase displays inventory item as an element - * of LLInventoryItemsList. - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLINVENTORYLISTITEM_H -#define LL_LLINVENTORYLISTITEM_H - -// llcommon -#include "llassettype.h" - -// llui -#include "llpanel.h" -#include "llstyle.h" -#include "lliconctrl.h" -#include "lltextbox.h" - -// newview -#include "llwearabletype.h" - -class LLViewerInventoryItem; - -/** - * @class LLPanelInventoryListItemBase - * - * Base class for Inventory flat list item. Panel consists of inventory icon - * and inventory item name. - * This class is able to display widgets(buttons) on left(before icon) and right(after text-box) sides - * of panel. - * - * How to use (see LLPanelClothingListItem for example): - * - implement init() to build panel from xml - * - create new xml file, fill it with widgets you want to dynamically show/hide/reshape on left/right sides - * - redefine postBuild()(call base implementation) and add needed widgets to needed sides, - * - */ -class LLPanelInventoryListItemBase : public LLPanel -{ -public: - struct Params : public LLInitParam::Block - { - Optional default_style, - worn_style; - Optional hover_image, - selected_image, - separator_image; - Optional item_icon; - Optional item_name; - Params(); - }; - - typedef enum e_item_state { - IS_DEFAULT, - IS_WORN, - } EItemState; - - static LLPanelInventoryListItemBase* create(LLViewerInventoryItem* item); - - virtual void draw(); - - /** - * Let item know it need to be refreshed in next draw() - */ - void setNeedsRefresh(bool needs_refresh){ mNeedsRefresh = needs_refresh; } - - bool getNeedsRefresh(){ return mNeedsRefresh; } - - /** - * Add widget to left side - */ - void addWidgetToLeftSide(const std::string& name, bool show_widget = true); - void addWidgetToLeftSide(LLUICtrl* ctrl, bool show_widget = true); - - /** - * Add widget to right side, widget is supposed to be child of calling panel - */ - void addWidgetToRightSide(const std::string& name, bool show_widget = true); - void addWidgetToRightSide(LLUICtrl* ctrl, bool show_widget = true); - - /** - * Mark widgets as visible. Only visible widgets take part in reshaping children - */ - void setShowWidget(const std::string& name, bool show); - void setShowWidget(LLUICtrl* ctrl, bool show); - - /** - * Set spacing between widgets during reshape - */ - void setWidgetSpacing(S32 spacing) { mWidgetSpacing = spacing; } - - S32 getWidgetSpacing() { return mWidgetSpacing; } - - /** - * Inheritors need to call base implementation of postBuild() - */ - /*virtual*/ bool postBuild(); - - /** - * Handles item selection - */ - /*virtual*/ void setValue(const LLSD& value); - - /** - * Handles filter request - */ - /*virtual*/ S32 notify(const LLSD& info); - - /* Highlights item */ - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); - /* Removes item highlight */ - /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask); - - /** Get the name of a corresponding inventory item */ - const std::string& getItemName() const; - - /** Get the asset type of a corresponding inventory item */ - LLAssetType::EType getType() const; - - /** Get the wearable type of a corresponding inventory item */ - LLWearableType::EType getWearableType() const; - - /** Get the description of a corresponding inventory item */ - const std::string& getDescription() const; - - /** Get the creation date of a corresponding inventory item */ - time_t getCreationDate() const; - - /** Get the associated inventory item */ - LLViewerInventoryItem* getItem() const; - - void setSeparatorVisible(bool visible) { mSeparatorVisible = visible; } - void resetHighlight() { mHovered = false; } - - virtual ~LLPanelInventoryListItemBase(){} - -protected: - - LLPanelInventoryListItemBase(LLViewerInventoryItem* item, const Params& params); - - typedef std::vector widget_array_t; - - /** - * Called after inventory item was updated, update panel widgets to reflect inventory changes. - */ - virtual void updateItem(const std::string& name, - EItemState item_state = IS_DEFAULT); - - void setLeftWidgetsWidth(S32 width) { mLeftWidgetsWidth = width; } - void setRightWidgetsWidth(S32 width) { mRightWidgetsWidth = width; } - - /** - * Set all widgets from both side visible/invisible. Only enabled widgets - * (see setShowWidget()) can become visible - */ - virtual void setWidgetsVisible(bool visible); - - /** - * Reshape all child widgets - icon, text-box and side widgets - */ - virtual void reshapeWidgets(); - - /** set wearable type icon image */ - void setIconImage(const LLUIImagePtr& image); - - /** Set item title - inventory item name usually */ - void setTitle(const std::string& title, - const std::string& highlit_text, - EItemState item_state = IS_DEFAULT); - - /** - * Show tool tip if item name text size > panel size - */ - virtual bool handleToolTip( S32 x, S32 y, MASK mask); - - const LLUUID mInventoryItemUUID; - bool mHovered; - -private: - - /** reshape left side widgets - * Deprecated for now. Disabled reshape left for now to reserve space for 'delete' - * button in LLPanelClothingListItem according to Neal's comment (https://codereview.productengine.com/secondlife/r/325/) - */ - void reshapeLeftWidgets(); - - /** reshape right side widgets */ - void reshapeRightWidgets(); - - /** reshape remaining widgets */ - void reshapeMiddleWidgets(); - - - LLIconCtrl* mIconCtrl; - LLTextBox* mTitleCtrl; - - LLUIImagePtr mIconImage; - LLUIImagePtr mHoverImage; - LLUIImagePtr mSelectedImage; - LLUIImagePtr mSeparatorImage; - - bool mSelected; - bool mSeparatorVisible; - - std::string mHighlightedText; - - widget_array_t mLeftSideWidgets; - widget_array_t mRightSideWidgets; - S32 mWidgetSpacing; - - S32 mLeftWidgetsWidth; - S32 mRightWidgetsWidth; - bool mNeedsRefresh; -}; - -#endif //LL_LLINVENTORYLISTITEM_H +/** + * @file llinventorylistitem.h + * @brief Inventory list item panel. + * + * Class LLPanelInventoryListItemBase displays inventory item as an element + * of LLInventoryItemsList. + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLINVENTORYLISTITEM_H +#define LL_LLINVENTORYLISTITEM_H + +// llcommon +#include "llassettype.h" + +// llui +#include "llpanel.h" +#include "llstyle.h" +#include "lliconctrl.h" +#include "lltextbox.h" + +// newview +#include "llwearabletype.h" + +class LLViewerInventoryItem; + +/** + * @class LLPanelInventoryListItemBase + * + * Base class for Inventory flat list item. Panel consists of inventory icon + * and inventory item name. + * This class is able to display widgets(buttons) on left(before icon) and right(after text-box) sides + * of panel. + * + * How to use (see LLPanelClothingListItem for example): + * - implement init() to build panel from xml + * - create new xml file, fill it with widgets you want to dynamically show/hide/reshape on left/right sides + * - redefine postBuild()(call base implementation) and add needed widgets to needed sides, + * + */ +class LLPanelInventoryListItemBase : public LLPanel +{ +public: + struct Params : public LLInitParam::Block + { + Optional default_style, + worn_style; + Optional hover_image, + selected_image, + separator_image; + Optional item_icon; + Optional item_name; + Params(); + }; + + typedef enum e_item_state { + IS_DEFAULT, + IS_WORN, + } EItemState; + + static LLPanelInventoryListItemBase* create(LLViewerInventoryItem* item); + + virtual void draw(); + + /** + * Let item know it need to be refreshed in next draw() + */ + void setNeedsRefresh(bool needs_refresh){ mNeedsRefresh = needs_refresh; } + + bool getNeedsRefresh(){ return mNeedsRefresh; } + + /** + * Add widget to left side + */ + void addWidgetToLeftSide(const std::string& name, bool show_widget = true); + void addWidgetToLeftSide(LLUICtrl* ctrl, bool show_widget = true); + + /** + * Add widget to right side, widget is supposed to be child of calling panel + */ + void addWidgetToRightSide(const std::string& name, bool show_widget = true); + void addWidgetToRightSide(LLUICtrl* ctrl, bool show_widget = true); + + /** + * Mark widgets as visible. Only visible widgets take part in reshaping children + */ + void setShowWidget(const std::string& name, bool show); + void setShowWidget(LLUICtrl* ctrl, bool show); + + /** + * Set spacing between widgets during reshape + */ + void setWidgetSpacing(S32 spacing) { mWidgetSpacing = spacing; } + + S32 getWidgetSpacing() { return mWidgetSpacing; } + + /** + * Inheritors need to call base implementation of postBuild() + */ + /*virtual*/ bool postBuild(); + + /** + * Handles item selection + */ + /*virtual*/ void setValue(const LLSD& value); + + /** + * Handles filter request + */ + /*virtual*/ S32 notify(const LLSD& info); + + /* Highlights item */ + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); + /* Removes item highlight */ + /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask); + + /** Get the name of a corresponding inventory item */ + const std::string& getItemName() const; + + /** Get the asset type of a corresponding inventory item */ + LLAssetType::EType getType() const; + + /** Get the wearable type of a corresponding inventory item */ + LLWearableType::EType getWearableType() const; + + /** Get the description of a corresponding inventory item */ + const std::string& getDescription() const; + + /** Get the creation date of a corresponding inventory item */ + time_t getCreationDate() const; + + /** Get the associated inventory item */ + LLViewerInventoryItem* getItem() const; + + void setSeparatorVisible(bool visible) { mSeparatorVisible = visible; } + void resetHighlight() { mHovered = false; } + + virtual ~LLPanelInventoryListItemBase(){} + +protected: + + LLPanelInventoryListItemBase(LLViewerInventoryItem* item, const Params& params); + + typedef std::vector widget_array_t; + + /** + * Called after inventory item was updated, update panel widgets to reflect inventory changes. + */ + virtual void updateItem(const std::string& name, + EItemState item_state = IS_DEFAULT); + + void setLeftWidgetsWidth(S32 width) { mLeftWidgetsWidth = width; } + void setRightWidgetsWidth(S32 width) { mRightWidgetsWidth = width; } + + /** + * Set all widgets from both side visible/invisible. Only enabled widgets + * (see setShowWidget()) can become visible + */ + virtual void setWidgetsVisible(bool visible); + + /** + * Reshape all child widgets - icon, text-box and side widgets + */ + virtual void reshapeWidgets(); + + /** set wearable type icon image */ + void setIconImage(const LLUIImagePtr& image); + + /** Set item title - inventory item name usually */ + void setTitle(const std::string& title, + const std::string& highlit_text, + EItemState item_state = IS_DEFAULT); + + /** + * Show tool tip if item name text size > panel size + */ + virtual bool handleToolTip( S32 x, S32 y, MASK mask); + + const LLUUID mInventoryItemUUID; + bool mHovered; + +private: + + /** reshape left side widgets + * Deprecated for now. Disabled reshape left for now to reserve space for 'delete' + * button in LLPanelClothingListItem according to Neal's comment (https://codereview.productengine.com/secondlife/r/325/) + */ + void reshapeLeftWidgets(); + + /** reshape right side widgets */ + void reshapeRightWidgets(); + + /** reshape remaining widgets */ + void reshapeMiddleWidgets(); + + + LLIconCtrl* mIconCtrl; + LLTextBox* mTitleCtrl; + + LLUIImagePtr mIconImage; + LLUIImagePtr mHoverImage; + LLUIImagePtr mSelectedImage; + LLUIImagePtr mSeparatorImage; + + bool mSelected; + bool mSeparatorVisible; + + std::string mHighlightedText; + + widget_array_t mLeftSideWidgets; + widget_array_t mRightSideWidgets; + S32 mWidgetSpacing; + + S32 mLeftWidgetsWidth; + S32 mRightWidgetsWidth; + bool mNeedsRefresh; +}; + +#endif //LL_LLINVENTORYLISTITEM_H diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index 9995f8169d..f0e024605c 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -1,5059 +1,5059 @@ -/** - * @file llinventorymodel.cpp - * @brief Implementation of the inventory model used to track agent inventory. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2014, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include -#include - -#include "llinventorymodel.h" - -#include "llaisapi.h" -#include "llagent.h" -#include "llagentwearables.h" -#include "llappearancemgr.h" -#include "llavatarnamecache.h" -#include "llclipboard.h" -#include "lldispatcher.h" -#include "llinventorypanel.h" -#include "llinventorybridge.h" -#include "llinventoryfunctions.h" -#include "llinventorymodelbackgroundfetch.h" -#include "llinventoryobserver.h" -#include "llinventorypanel.h" -#include "llfloaterpreviewtrash.h" -#include "llnotificationsutil.h" -#include "llmarketplacefunctions.h" -#include "llwindow.h" -#include "llviewercontrol.h" -#include "llviewernetwork.h" -#include "llpreview.h" -#include "llviewergenericmessage.h" -#include "llviewermessage.h" -#include "llviewerfoldertype.h" -#include "llviewerwindow.h" -#include "llappviewer.h" -#include "llviewerregion.h" -#include "llcallbacklist.h" -#include "llvoavatarself.h" -#include "llgesturemgr.h" -#include "llsdserialize.h" -#include "llsdutil.h" -#include "bufferarray.h" -#include "bufferstream.h" -#include "llcorehttputil.h" -#include "hbxxh.h" -#include "llstartup.h" - -//#define DIFF_INVENTORY_FILES -#ifdef DIFF_INVENTORY_FILES -#include "process.h" -#endif - -#include -#include - -// Increment this if the inventory contents change in a non-backwards-compatible way. -// For viewer 2, the addition of link items makes a pre-viewer-2 cache incorrect. -const S32 LLInventoryModel::sCurrentInvCacheVersion = 3; -bool LLInventoryModel::sFirstTimeInViewer2 = true; - -S32 LLInventoryModel::sPendingSystemFolders = 0; - -///---------------------------------------------------------------------------- -/// Local function declarations, constants, enums, and typedefs -///---------------------------------------------------------------------------- - -//bool decompress_file(const char* src_filename, const char* dst_filename); -static const char PRODUCTION_CACHE_FORMAT_STRING[] = "%s.inv.llsd"; -static const char GRID_CACHE_FORMAT_STRING[] = "%s.%s.inv.llsd"; -static const char * const LOG_INV("Inventory"); - -struct InventoryIDPtrLess -{ - bool operator()(const LLViewerInventoryCategory* i1, const LLViewerInventoryCategory* i2) const - { - return (i1->getUUID() < i2->getUUID()); - } -}; - -class LLCanCache : public LLInventoryCollectFunctor -{ -public: - LLCanCache(LLInventoryModel* model) : mModel(model) {} - virtual ~LLCanCache() {} - virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); -protected: - LLInventoryModel* mModel; - std::set mCachedCatIDs; -}; - -bool LLCanCache::operator()(LLInventoryCategory* cat, LLInventoryItem* item) -{ - bool rv = false; - if(item) - { - if(mCachedCatIDs.find(item->getParentUUID()) != mCachedCatIDs.end()) - { - rv = true; - } - } - else if(cat) - { - // HACK: downcast - LLViewerInventoryCategory* c = (LLViewerInventoryCategory*)cat; - if(c->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN) - { - S32 descendents_server = c->getDescendentCount(); - S32 descendents_actual = c->getViewerDescendentCount(); - if(descendents_server == descendents_actual) - { - mCachedCatIDs.insert(c->getUUID()); - rv = true; - } - } - } - return rv; -} - -struct InventoryCallbackInfo -{ - InventoryCallbackInfo(U32 callback, const LLUUID& inv_id) : - mCallback(callback), mInvID(inv_id) {} - U32 mCallback; - LLUUID mInvID; -}; - -///---------------------------------------------------------------------------- -/// Class LLDispatchClassifiedClickThrough -///---------------------------------------------------------------------------- - -class LLDispatchBulkUpdateInventory : public LLDispatchHandler -{ -public: - virtual bool operator()( - const LLDispatcher* dispatcher, - const std::string& key, - const LLUUID& invoice, - const sparam_t& strings) - { - LLSD message; - - // Expect single string parameter in the form of a notation serialized LLSD. - sparam_t::const_iterator it = strings.begin(); - if (it != strings.end()) { - const std::string& llsdRaw = *it++; - std::istringstream llsdData(llsdRaw); - if (!LLSDSerialize::deserialize(message, llsdData, llsdRaw.length())) - { - LL_WARNS() << "LLDispatchBulkUpdateInventory: Attempted to read parameter data into LLSD but failed:" << llsdRaw << LL_ENDL; - } - } - - LLInventoryModel::update_map_t update; - LLInventoryModel::cat_array_t folders; - LLInventoryModel::item_array_t items; - std::list cblist; - uuid_vec_t wearable_ids; - - LLSD item_data = message["item_data"]; - if (item_data.isArray()) - { - for (LLSD::array_iterator itd = item_data.beginArray(); itd != item_data.endArray(); ++itd) - { - const LLSD &item(*itd); - - // Agent id probably should be in the root of the message - LLUUID agent_id = item["agent_id"].asUUID(); - if (agent_id != gAgent.getID()) - { - LL_WARNS() << "Got a BulkUpdateInventory for the wrong agent." << LL_ENDL; - return false; - } - - LLPointer titem = new LLViewerInventoryItem; - titem->unpackMessage(item); - LL_DEBUGS("Inventory") << "unpacked item '" << titem->getName() << "' in " - << titem->getParentUUID() << LL_ENDL; - // callback id might be no longer supported - U32 callback_id = item["callback_id"].asInteger(); - - if (titem->getUUID().notNull()) - { - items.push_back(titem); - cblist.push_back(InventoryCallbackInfo(callback_id, titem->getUUID())); - if (titem->getInventoryType() == LLInventoryType::IT_WEARABLE) - { - wearable_ids.push_back(titem->getUUID()); - } - - // examine update for changes. - LLViewerInventoryItem* itemp = gInventory.getItem(titem->getUUID()); - if (itemp) - { - if (titem->getParentUUID() == itemp->getParentUUID()) - { - update[titem->getParentUUID()]; - } - else - { - ++update[titem->getParentUUID()]; - --update[itemp->getParentUUID()]; - } - } - else - { - LLViewerInventoryCategory* folderp = gInventory.getCategory(titem->getParentUUID()); - if (folderp) - { - ++update[titem->getParentUUID()]; - } - } - } - else - { - cblist.push_back(InventoryCallbackInfo(callback_id, LLUUID::null)); - } - } - } - - LLSD folder_data = message["folder_data"]; - if (folder_data.isArray()) - { - for (LLSD::array_iterator itd = folder_data.beginArray(); itd != folder_data.endArray(); ++itd) - { - const LLSD &folder(*itd); - - LLPointer tfolder = new LLViewerInventoryCategory(gAgent.getID()); - tfolder->unpackMessage(folder); - - LL_DEBUGS("Inventory") << "unpacked folder '" << tfolder->getName() << "' (" - << tfolder->getUUID() << ") in " << tfolder->getParentUUID() - << LL_ENDL; - - // If the folder is a listing or a version folder, all we need to do is update the SLM data - int depth_folder = depth_nesting_in_marketplace(tfolder->getUUID()); - if ((depth_folder == 1) || (depth_folder == 2)) - { - // Trigger an SLM listing update - LLUUID listing_uuid = (depth_folder == 1 ? tfolder->getUUID() : tfolder->getParentUUID()); - S32 listing_id = LLMarketplaceData::instance().getListingID(listing_uuid); - LLMarketplaceData::instance().getListing(listing_id); - // In that case, there is no item to update so no callback -> we skip the rest of the update - } - else if (tfolder->getUUID().notNull()) - { - folders.push_back(tfolder); - LLViewerInventoryCategory* folderp = gInventory.getCategory(tfolder->getUUID()); - if (folderp) - { - if (tfolder->getParentUUID() == folderp->getParentUUID()) - { - update[tfolder->getParentUUID()]; - } - else - { - ++update[tfolder->getParentUUID()]; - --update[folderp->getParentUUID()]; - } - } - else - { - // we could not find the folder, so it is probably - // new. However, we only want to attempt accounting - // for the parent if we can find the parent. - folderp = gInventory.getCategory(tfolder->getParentUUID()); - if (folderp) - { - ++update[tfolder->getParentUUID()]; - } - } - } - } - } - - gInventory.accountForUpdate(update); - - for (LLInventoryModel::cat_array_t::iterator cit = folders.begin(); cit != folders.end(); ++cit) - { - gInventory.updateCategory(*cit); - } - for (LLInventoryModel::item_array_t::iterator iit = items.begin(); iit != items.end(); ++iit) - { - gInventory.updateItem(*iit); - } - gInventory.notifyObservers(); - - /* - Transaction id not included? - - // The incoming inventory could span more than one BulkInventoryUpdate packet, - // so record the transaction ID for this purchase, then wear all clothing - // that comes in as part of that transaction ID. JC - if (LLInventoryState::sWearNewClothing) - { - LLInventoryState::sWearNewClothingTransactionID = tid; - LLInventoryState::sWearNewClothing = false; - } - - if (tid.notNull() && tid == LLInventoryState::sWearNewClothingTransactionID) - { - count = wearable_ids.size(); - for (i = 0; i < count; ++i) - { - LLViewerInventoryItem* wearable_item; - wearable_item = gInventory.getItem(wearable_ids[i]); - LLAppearanceMgr::instance().wearItemOnAvatar(wearable_item->getUUID(), true, true); - } - } - */ - - if (LLInventoryState::sWearNewClothing && wearable_ids.size() > 0) - { - LLInventoryState::sWearNewClothing = false; - - size_t count = wearable_ids.size(); - for (S32 i = 0; i < count; ++i) - { - LLViewerInventoryItem* wearable_item; - wearable_item = gInventory.getItem(wearable_ids[i]); - LLAppearanceMgr::instance().wearItemOnAvatar(wearable_item->getUUID(), true, true); - } - } - - std::list::iterator inv_it; - for (inv_it = cblist.begin(); inv_it != cblist.end(); ++inv_it) - { - InventoryCallbackInfo cbinfo = (*inv_it); - gInventoryCallbacks.fire(cbinfo.mCallback, cbinfo.mInvID); - } - return true; - } -}; -static LLDispatchBulkUpdateInventory sBulkUpdateInventory; - -///---------------------------------------------------------------------------- -/// Class LLInventoryValidationInfo -///---------------------------------------------------------------------------- -LLInventoryValidationInfo::LLInventoryValidationInfo() -{ -} - -void LLInventoryValidationInfo::toOstream(std::ostream& os) const -{ - os << "mFatalErrorCount " << mFatalErrorCount - << " mWarningCount " << mWarningCount - << " mLoopCount " << mLoopCount - << " mOrphanedCount " << mOrphanedCount; -} - - -std::ostream& operator<<(std::ostream& os, const LLInventoryValidationInfo& v) -{ - v.toOstream(os); - return os; -} - -void LLInventoryValidationInfo::asLLSD(LLSD& sd) const -{ - sd["fatal_error_count"] = mFatalErrorCount; - sd["loop_count"] = mLoopCount; - sd["orphaned_count"] = mOrphanedCount; - sd["initialized"] = mInitialized; - sd["missing_system_folders_count"] = LLSD::Integer(mMissingRequiredSystemFolders.size()); - sd["fatal_no_root_folder"] = mFatalNoRootFolder; - sd["fatal_no_library_root_folder"] = mFatalNoLibraryRootFolder; - sd["fatal_qa_debug_mode"] = mFatalQADebugMode; - - sd["warning_count"] = mWarningCount; - if (mWarningCount>0) - { - sd["warnings"] = LLSD::emptyArray(); - for (auto const& it : mWarnings) - { - S32 val =LLSD::Integer(it.second); - if (val>0) - { - sd["warnings"][it.first] = val; - } - } - } - if (mMissingRequiredSystemFolders.size()>0) - { - sd["missing_system_folders"] = LLSD::emptyArray(); - for(auto ft: mMissingRequiredSystemFolders) - { - sd["missing_system_folders"].append(LLFolderType::lookup(ft)); - } - } - sd["duplicate_system_folders_count"] = LLSD::Integer(mDuplicateRequiredSystemFolders.size()); - if (mDuplicateRequiredSystemFolders.size()>0) - { - sd["duplicate_system_folders"] = LLSD::emptyArray(); - for(auto ft: mDuplicateRequiredSystemFolders) - { - sd["duplicate_system_folders"].append(LLFolderType::lookup(ft)); - } - } - -} - -///---------------------------------------------------------------------------- -/// Class LLInventoryModel -///---------------------------------------------------------------------------- - -// global for the agent inventory. -LLInventoryModel gInventory; - -// Default constructor -LLInventoryModel::LLInventoryModel() -: // These are now ordered, keep them that way. - mBacklinkMMap(), - mIsAgentInvUsable(false), - mRootFolderID(), - mLibraryRootFolderID(), - mLibraryOwnerID(), - mCategoryMap(), - mItemMap(), - mParentChildCategoryTree(), - mParentChildItemTree(), - mLastItem(NULL), - mIsNotifyObservers(false), - mModifyMask(LLInventoryObserver::ALL), - mChangedItemIDs(), - mBulkFecthCallbackSlot(), - mObservers(), - mHttpRequestFG(NULL), - mHttpRequestBG(NULL), - mHttpOptions(), - mHttpHeaders(), - mHttpPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), - mCategoryLock(), - mItemLock(), - mValidationInfo(new LLInventoryValidationInfo) -{} - - -// Destroys the object -LLInventoryModel::~LLInventoryModel() -{ - cleanupInventory(); -} - -void LLInventoryModel::cleanupInventory() -{ - empty(); - // Deleting one observer might erase others from the list, so always pop off the front - while (!mObservers.empty()) - { - observer_list_t::iterator iter = mObservers.begin(); - LLInventoryObserver* observer = *iter; - mObservers.erase(iter); - delete observer; - } - - if (mBulkFecthCallbackSlot.connected()) - { - mBulkFecthCallbackSlot.disconnect(); - } - mObservers.clear(); - - // Run down HTTP transport - mHttpHeaders.reset(); - mHttpOptions.reset(); - - delete mHttpRequestFG; - mHttpRequestFG = NULL; - delete mHttpRequestBG; - mHttpRequestBG = NULL; -} - -// This is a convenience function to check if one object has a parent -// chain up to the category specified by UUID. -bool LLInventoryModel::isObjectDescendentOf(const LLUUID& obj_id, - const LLUUID& cat_id) const -{ - if (obj_id == cat_id) return true; - - const LLInventoryObject* obj = getObject(obj_id); - while(obj) - { - const LLUUID& parent_id = obj->getParentUUID(); - if( parent_id.isNull() ) - { - return false; - } - if(parent_id == cat_id) - { - return true; - } - // Since we're scanning up the parents, we only need to check - // in the category list. - obj = getCategory(parent_id); - } - return false; -} - -const LLViewerInventoryCategory *LLInventoryModel::getFirstNondefaultParent(const LLUUID& obj_id) const -{ - const LLInventoryObject* obj = getObject(obj_id); - if(!obj) - { - LL_WARNS(LOG_INV) << "Non-existent object [ id: " << obj_id << " ] " << LL_ENDL; - return NULL; - } - // Search up the parent chain until we get to root or an acceptable folder. - // This assumes there are no cycles in the tree else we'll get a hang. - LLUUID parent_id = obj->getParentUUID(); - while (!parent_id.isNull()) - { - const LLViewerInventoryCategory *cat = getCategory(parent_id); - if (!cat) break; - const LLFolderType::EType folder_type = cat->getPreferredType(); - if (folder_type != LLFolderType::FT_NONE && - folder_type != LLFolderType::FT_ROOT_INVENTORY && - !LLFolderType::lookupIsEnsembleType(folder_type)) - { - return cat; - } - parent_id = cat->getParentUUID(); - } - return NULL; -} - -// -// Search up the parent chain until we get to the specified parent, then return the first child category under it -// -const LLViewerInventoryCategory* LLInventoryModel::getFirstDescendantOf(const LLUUID& master_parent_id, const LLUUID& obj_id) const -{ - if (master_parent_id == obj_id) - { - return NULL; - } - - const LLViewerInventoryCategory* current_cat = getCategory(obj_id); - - if (current_cat == NULL) - { - current_cat = getCategory(getObject(obj_id)->getParentUUID()); - } - - while (current_cat != NULL) - { - const LLUUID& current_parent_id = current_cat->getParentUUID(); - - if (current_parent_id == master_parent_id) - { - return current_cat; - } - - current_cat = getCategory(current_parent_id); - } - - return NULL; -} - -LLInventoryModel::EAncestorResult LLInventoryModel::getObjectTopmostAncestor(const LLUUID& object_id, LLUUID& result) const -{ - LLInventoryObject *object = getObject(object_id); - if (!object) - { - LL_WARNS(LOG_INV) << "Unable to trace topmost ancestor, initial object " << object_id << " does not exist" << LL_ENDL; - return ANCESTOR_MISSING; - } - - std::set object_ids{ object_id }; // loop protection - while (object->getParentUUID().notNull()) - { - LLUUID parent_id = object->getParentUUID(); - if (object_ids.find(parent_id) != object_ids.end()) - { - LL_WARNS(LOG_INV) << "Detected a loop on an object " << parent_id << " when searching for ancestor of " << object_id << LL_ENDL; - return ANCESTOR_LOOP; - } - object_ids.insert(parent_id); - LLInventoryObject *parent_object = getObject(parent_id); - if (!parent_object) - { - LL_WARNS(LOG_INV) << "unable to trace topmost ancestor of " << object_id << ", missing item for uuid " << parent_id << LL_ENDL; - return ANCESTOR_MISSING; - } - object = parent_object; - } - result = object->getUUID(); - return ANCESTOR_OK; -} - -// Get the object by id. Returns NULL if not found. -LLInventoryObject* LLInventoryModel::getObject(const LLUUID& id) const -{ - LLViewerInventoryCategory* cat = getCategory(id); - if (cat) - { - return cat; - } - LLViewerInventoryItem* item = getItem(id); - if (item) - { - return item; - } - return NULL; -} - -// Get the item by id. Returns NULL if not found. -LLViewerInventoryItem* LLInventoryModel::getItem(const LLUUID& id) const -{ - LLViewerInventoryItem* item = NULL; - if(mLastItem.notNull() && mLastItem->getUUID() == id) - { - item = mLastItem; - } - else - { - item_map_t::const_iterator iter = mItemMap.find(id); - if (iter != mItemMap.end()) - { - item = iter->second; - mLastItem = item; - } - } - return item; -} - -// Get the category by id. Returns NULL if not found -LLViewerInventoryCategory* LLInventoryModel::getCategory(const LLUUID& id) const -{ - LLViewerInventoryCategory* category = NULL; - cat_map_t::const_iterator iter = mCategoryMap.find(id); - if (iter != mCategoryMap.end()) - { - category = iter->second; - } - return category; -} - -S32 LLInventoryModel::getItemCount() const -{ - return mItemMap.size(); -} - -S32 LLInventoryModel::getCategoryCount() const -{ - return mCategoryMap.size(); -} - -// Return the direct descendents of the id provided. The array -// provided points straight into the guts of this object, and -// should only be used for read operations, since modifications -// may invalidate the internal state of the inventory. Set passed -// in values to NULL if the call fails. -void LLInventoryModel::getDirectDescendentsOf(const LLUUID& cat_id, - cat_array_t*& categories, - item_array_t*& items) const -{ - categories = get_ptr_in_map(mParentChildCategoryTree, cat_id); - items = get_ptr_in_map(mParentChildItemTree, cat_id); -} - -void LLInventoryModel::getDirectDescendentsOf(const LLUUID& cat_id, cat_array_t& categories, item_array_t& items, LLInventoryCollectFunctor& f) const -{ - if (cat_array_t* categoriesp = get_ptr_in_map(mParentChildCategoryTree, cat_id)) - { - for (LLViewerInventoryCategory* pFolder : *categoriesp) - { - if (f(pFolder, nullptr)) - { - categories.push_back(pFolder); - } - } - } - - if (item_array_t* itemsp = get_ptr_in_map(mParentChildItemTree, cat_id)) - { - for (LLViewerInventoryItem* pItem : *itemsp) - { - if (f(nullptr, pItem)) - { - items.push_back(pItem); - } - } - } -} - -LLInventoryModel::digest_t LLInventoryModel::hashDirectDescendentNames(const LLUUID& cat_id) const -{ - LLInventoryModel::cat_array_t* cat_array; - LLInventoryModel::item_array_t* item_array; - getDirectDescendentsOf(cat_id,cat_array,item_array); - if (!item_array) - { - return LLUUID::null; - } - HBXXH128 item_name_hash; - for (LLInventoryModel::item_array_t::const_iterator iter = item_array->begin(); - iter != item_array->end(); - iter++) - { - const LLViewerInventoryItem *item = (*iter); - if (!item) - continue; - item_name_hash.update(item->getName()); - } - return item_name_hash.digest(); -} - -// SJB: Added version to lock the arrays to catch potential logic bugs -void LLInventoryModel::lockDirectDescendentArrays(const LLUUID& cat_id, - cat_array_t*& categories, - item_array_t*& items) -{ - getDirectDescendentsOf(cat_id, categories, items); - if (categories) - { - mCategoryLock[cat_id] = true; - } - if (items) - { - mItemLock[cat_id] = true; - } -} - -void LLInventoryModel::unlockDirectDescendentArrays(const LLUUID& cat_id) -{ - mCategoryLock[cat_id] = false; - mItemLock[cat_id] = false; -} - -void LLInventoryModel::consolidateForType(const LLUUID& main_id, LLFolderType::EType type) -{ - // Make a list of folders that are not "main_id" and are of "type" - std::vector folder_ids; - for (cat_map_t::iterator cit = mCategoryMap.begin(); cit != mCategoryMap.end(); ++cit) - { - LLViewerInventoryCategory* cat = cit->second; - if ((cat->getPreferredType() == type) && (cat->getUUID() != main_id)) - { - folder_ids.push_back(cat->getUUID()); - } - } - - // Iterate through those folders - for (std::vector::iterator folder_ids_it = folder_ids.begin(); folder_ids_it != folder_ids.end(); ++folder_ids_it) - { - LLUUID folder_id = (*folder_ids_it); - - // Get the content of this folder - cat_array_t* cats; - item_array_t* items; - getDirectDescendentsOf(folder_id, cats, items); - - // Move all items to the main folder - // Note : we get the list of UUIDs and iterate on them instead of iterating directly on item_array_t - // elements. This is because moving elements modify the maps and, consequently, invalidate iterators on them. - // This "gather and iterate" method is verbose but resilient. - std::vector list_uuids; - for (item_array_t::const_iterator it = items->begin(); it != items->end(); ++it) - { - list_uuids.push_back((*it)->getUUID()); - } - for (std::vector::const_iterator it = list_uuids.begin(); it != list_uuids.end(); ++it) - { - LLViewerInventoryItem* item = getItem(*it); - changeItemParent(item, main_id, true); - } - - // Move all folders to the main folder - list_uuids.clear(); - for (cat_array_t::const_iterator it = cats->begin(); it != cats->end(); ++it) - { - list_uuids.push_back((*it)->getUUID()); - } - for (std::vector::const_iterator it = list_uuids.begin(); it != list_uuids.end(); ++it) - { - LLViewerInventoryCategory* cat = getCategory(*it); - changeCategoryParent(cat, main_id, true); - } - - // Purge the emptied folder - // Note that this might be a system folder, don't validate removability - LLViewerInventoryCategory* cat = getCategory(folder_id); - if (cat) - { - const LLUUID trash_id = findCategoryUUIDForType(LLFolderType::FT_TRASH); - if (trash_id.notNull()) - { - changeCategoryParent(cat, trash_id, true); - } - } - remove_inventory_category(folder_id, NULL); - } -} - -void LLInventoryModel::ensureCategoryForTypeExists(LLFolderType::EType preferred_type) -{ - LLUUID rv = LLUUID::null; - LLUUID root_id = gInventory.getRootFolderID(); - if (LLFolderType::FT_ROOT_INVENTORY == preferred_type) - { - rv = root_id; - } - else if (root_id.notNull()) - { - cat_array_t* cats = NULL; - cats = get_ptr_in_map(mParentChildCategoryTree, root_id); - if (cats) - { - S32 count = cats->size(); - for (S32 i = 0; i < count; ++i) - { - LLViewerInventoryCategory* p_cat = cats->at(i); - if (p_cat && p_cat->getPreferredType() == preferred_type) - { - const LLUUID& folder_id = cats->at(i)->getUUID(); - if (rv.isNull() || folder_id < rv) - { - rv = folder_id; - } - } - } - } - } - - if (rv.isNull() && root_id.notNull()) - { - - if (isInventoryUsable()) - { - createNewCategory( - root_id, - preferred_type, - LLStringUtil::null, - [preferred_type](const LLUUID &new_cat_id) - { - if (new_cat_id.isNull()) - { - LL_WARNS("Inventory") - << "Failed to create folder of type " << preferred_type - << LL_ENDL; - } - else - { - LL_WARNS("Inventory") << "Created category: " << new_cat_id - << " for type: " << preferred_type << LL_ENDL; - sPendingSystemFolders--; - } - } - ); - } - else - { - LL_WARNS("Inventory") << "Can't create requested folder, type " << preferred_type - << " because inventory is not usable" << LL_ENDL; - } - } - else - { - sPendingSystemFolders--; - } -} - -const LLUUID LLInventoryModel::findCategoryUUIDForTypeInRoot( - LLFolderType::EType preferred_type, - const LLUUID& root_id) const -{ - LLUUID rv = LLUUID::null; - if(LLFolderType::FT_ROOT_INVENTORY == preferred_type) - { - rv = root_id; - } - else if (root_id.notNull()) - { - cat_array_t* cats = NULL; - cats = get_ptr_in_map(mParentChildCategoryTree, root_id); - if(cats) - { - S32 count = cats->size(); - for(S32 i = 0; i < count; ++i) - { - LLViewerInventoryCategory* p_cat = cats->at(i); - if(p_cat && p_cat->getPreferredType() == preferred_type) - { - const LLUUID& folder_id = cats->at(i)->getUUID(); - if (rv.isNull() || folder_id < rv) - { - rv = folder_id; - } - } - } - } - } - - if(rv.isNull() - && root_id.notNull() - && preferred_type != LLFolderType::FT_MARKETPLACE_LISTINGS - && preferred_type != LLFolderType::FT_OUTBOX) - { - // if it does not exists, it should either be added - // to createCommonSystemCategories or server should - // have set it - llassert(!isInventoryUsable()); - LL_WARNS("Inventory") << "Tried to find folder, type " << preferred_type - << " but category does not exist" << LL_ENDL; - } - return rv; -} - -// findCategoryUUIDForType() returns the uuid of the category that -// specifies 'type' as what it defaults to containing. The category is -// not necessarily only for that type. *NOTE: This will create a new -// inventory category on the fly if one does not exist. -const LLUUID LLInventoryModel::findCategoryUUIDForType(LLFolderType::EType preferred_type) const -{ - return findCategoryUUIDForTypeInRoot(preferred_type, gInventory.getRootFolderID()); -} - -const LLUUID LLInventoryModel::findUserDefinedCategoryUUIDForType(LLFolderType::EType preferred_type) const -{ - LLUUID cat_id; - switch (preferred_type) - { - case LLFolderType::FT_OBJECT: - { - cat_id = LLUUID(gSavedPerAccountSettings.getString("ModelUploadFolder")); - break; - } - case LLFolderType::FT_TEXTURE: - { - cat_id = LLUUID(gSavedPerAccountSettings.getString("TextureUploadFolder")); - break; - } - case LLFolderType::FT_SOUND: - { - cat_id = LLUUID(gSavedPerAccountSettings.getString("SoundUploadFolder")); - break; - } - case LLFolderType::FT_ANIMATION: - { - cat_id = LLUUID(gSavedPerAccountSettings.getString("AnimationUploadFolder")); - break; - } - case LLFolderType::FT_MATERIAL: - { - cat_id = LLUUID(gSavedPerAccountSettings.getString("PBRUploadFolder")); - break; - } - default: - break; - } - - if (cat_id.isNull() || !getCategory(cat_id)) - { - cat_id = findCategoryUUIDForTypeInRoot(preferred_type, getRootFolderID()); - } - return cat_id; -} - -const LLUUID LLInventoryModel::findLibraryCategoryUUIDForType(LLFolderType::EType preferred_type) const -{ - return findCategoryUUIDForTypeInRoot(preferred_type, gInventory.getLibraryRootFolderID()); -} - -// Convenience function to create a new category. You could call -// updateCategory() with a newly generated UUID category, but this -// version will take care of details like what the name should be -// based on preferred type. -void LLInventoryModel::createNewCategory(const LLUUID& parent_id, - LLFolderType::EType preferred_type, - const std::string& pname, - inventory_func_type callback, - const LLUUID& thumbnail_id) -{ - LL_DEBUGS(LOG_INV) << "Create '" << pname << "' in '" << make_inventory_path(parent_id) << "'" << LL_ENDL; - if (!isInventoryUsable()) - { - LL_WARNS(LOG_INV) << "Inventory is not usable; can't create requested category of type " - << preferred_type << LL_ENDL; - if (callback) - { - callback(LLUUID::null); - } - return; - } - - if(LLFolderType::lookup(preferred_type) == LLFolderType::badLookup()) - { - LL_DEBUGS(LOG_INV) << "Attempt to create undefined category." << LL_ENDL; - if (callback) - { - callback(LLUUID::null); - } - return; - } - - if (preferred_type != LLFolderType::FT_NONE) - { - // Ultimately this should only be done for non-singleton - // types. Requires back-end changes to guarantee that others - // already exist. - LL_WARNS(LOG_INV) << "Creating new system folder, type " << preferred_type << LL_ENDL; - } - - std::string name = pname; - if (pname.empty()) - { - name.assign(LLViewerFolderType::lookupNewCategoryName(preferred_type)); - } - - if (AISAPI::isAvailable()) - { - LLSD new_inventory = LLSD::emptyMap(); - new_inventory["categories"] = LLSD::emptyArray(); - LLViewerInventoryCategory cat(LLUUID::null, parent_id, preferred_type, name, gAgent.getID()); - cat.setThumbnailUUID(thumbnail_id); - LLSD cat_sd = cat.asAISCreateCatLLSD(); - new_inventory["categories"].append(cat_sd); - AISAPI::CreateInventory( - parent_id, - new_inventory, - [this, callback, parent_id, preferred_type, name] (const LLUUID& new_category) - { - if (new_category.isNull()) - { - if (callback && !callback.empty()) - { - callback(new_category); - } - return; - } - - // todo: not needed since AIS does the accounting? - LLViewerInventoryCategory* folderp = gInventory.getCategory(new_category); - if (!folderp) - { - // Add the category to the internal representation - LLPointer cat = new LLViewerInventoryCategory( - new_category, - parent_id, - preferred_type, - name, - gAgent.getID()); - - LLInventoryModel::LLCategoryUpdate update(cat->getParentUUID(), 1); - accountForUpdate(update); - - cat->setVersion(LLViewerInventoryCategory::VERSION_INITIAL - 1); // accountForUpdate() will icrease version by 1 - cat->setDescendentCount(0); - updateCategory(cat); - } - - if (callback && !callback.empty()) - { - callback(new_category); - } - }); - return; - } - - LLViewerRegion* viewer_region = gAgent.getRegion(); - std::string url; - if ( viewer_region ) - url = viewer_region->getCapability("CreateInventoryCategory"); - - if (!url.empty()) - { - //Let's use the new capability. - LLUUID id; - id.generate(); - LLSD request, body; - body["folder_id"] = id; - body["parent_id"] = parent_id; - body["type"] = (LLSD::Integer) preferred_type; - body["name"] = name; - - request["message"] = "CreateInventoryCategory"; - request["payload"] = body; - - LL_DEBUGS(LOG_INV) << "Creating category via request: " << ll_pretty_print_sd(request) << LL_ENDL; - LLCoros::instance().launch("LLInventoryModel::createNewCategoryCoro", - boost::bind(&LLInventoryModel::createNewCategoryCoro, this, url, body, callback)); - return; - } - - if (callback) - { - callback(LLUUID::null); // Notify about failure - } -} - -void LLInventoryModel::createNewCategoryCoro(std::string url, LLSD postData, inventory_func_type callback) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("createNewCategoryCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); - - - httpOpts->setWantHeaders(true); - - LL_INFOS("HttpCoroutineAdapter", "genericPostCoro") << "Generic POST for " << url << LL_ENDL; - - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData, httpOpts); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS() << "HTTP failure attempting to create category." << LL_ENDL; - if (callback) - { - callback(LLUUID::null); - } - return; - } - - if (!result.has("folder_id")) - { - LL_WARNS() << "Malformed response contents" << ll_pretty_print_sd(result) << LL_ENDL; - if (callback) - { - callback(LLUUID::null); - } - return; - } - - LLUUID categoryId = result["folder_id"].asUUID(); - - LLViewerInventoryCategory* folderp = gInventory.getCategory(categoryId); - if (!folderp) - { - // Add the category to the internal representation - LLPointer cat = new LLViewerInventoryCategory(categoryId, - result["parent_id"].asUUID(), (LLFolderType::EType)result["type"].asInteger(), - result["name"].asString(), gAgent.getID()); - - LLInventoryModel::LLCategoryUpdate update(cat->getParentUUID(), 1); - accountForUpdate(update); - - cat->setVersion(LLViewerInventoryCategory::VERSION_INITIAL - 1); // accountForUpdate() will icrease version by 1 - cat->setDescendentCount(0); - updateCategory(cat); - } - else - { - // bulk processing was faster than coroutine (coro request->processBulkUpdateInventory->coro response) - // category already exists, but needs an update - if (folderp->getVersion() != LLViewerInventoryCategory::VERSION_INITIAL - || folderp->getDescendentCount() != LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN) - { - LL_WARNS() << "Inventory desync on folder creation. Newly created folder already has descendants or got a version.\n" - << "Name: " << folderp->getName() - << " Id: " << folderp->getUUID() - << " Version: " << folderp->getVersion() - << " Descendants: " << folderp->getDescendentCount() - << LL_ENDL; - } - // Recreate category with correct values - // Creating it anew just simplifies figuring out needed change-masks - // and making all needed updates, see updateCategory - LLPointer cat = new LLViewerInventoryCategory(categoryId, - result["parent_id"].asUUID(), (LLFolderType::EType)result["type"].asInteger(), - result["name"].asString(), gAgent.getID()); - - if (folderp->getParentUUID() != cat->getParentUUID()) - { - LL_WARNS() << "Inventory desync on folder creation. Newly created folder has wrong parent.\n" - << "Name: " << folderp->getName() - << " Id: " << folderp->getUUID() - << " Expected parent: " << cat->getParentUUID() - << " Actual parent: " << folderp->getParentUUID() - << LL_ENDL; - LLInventoryModel::LLCategoryUpdate update(cat->getParentUUID(), 1); - accountForUpdate(update); - } - // else: Do not update parent, parent is already aware of the change. See processBulkUpdateInventory - - cat->setVersion(LLViewerInventoryCategory::VERSION_INITIAL - 1); // accountForUpdate() will icrease version by 1 - cat->setDescendentCount(0); - updateCategory(cat); - } - - if (callback) - { - callback(categoryId); - } - -} - -// This is optimized for the case that we just want to know whether a -// category has any immediate children meeting a condition, without -// needing to recurse or build up any lists. -bool LLInventoryModel::hasMatchingDirectDescendent(const LLUUID& cat_id, - LLInventoryCollectFunctor& filter) -{ - LLInventoryModel::cat_array_t *cats; - LLInventoryModel::item_array_t *items; - getDirectDescendentsOf(cat_id, cats, items); - if (cats) - { - for (LLInventoryModel::cat_array_t::const_iterator it = cats->begin(); - it != cats->end(); ++it) - { - if (filter(*it,NULL)) - { - return true; - } - } - } - if (items) - { - for (LLInventoryModel::item_array_t::const_iterator it = items->begin(); - it != items->end(); ++it) - { - if (filter(NULL,*it)) - { - return true; - } - } - } - return false; -} - -// Starting with the object specified, add its descendents to the -// array provided, but do not add the inventory object specified by -// id. There is no guaranteed order. Neither array will be erased -// before adding objects to it. Do not store a copy of the pointers -// collected - use them, and collect them again later if you need to -// reference the same objects. - -class LLAlwaysCollect : public LLInventoryCollectFunctor -{ -public: - virtual ~LLAlwaysCollect() {} - virtual bool operator()(LLInventoryCategory* cat, - LLInventoryItem* item) - { - return true; - } -}; - -void LLInventoryModel::collectDescendents(const LLUUID& id, - cat_array_t& cats, - item_array_t& items, - bool include_trash) -{ - LLAlwaysCollect always; - collectDescendentsIf(id, cats, items, include_trash, always); -} - -void LLInventoryModel::collectDescendentsIf(const LLUUID& id, - cat_array_t& cats, - item_array_t& items, - bool include_trash, - LLInventoryCollectFunctor& add) -{ - // Start with categories - if(!include_trash) - { - const LLUUID trash_id = findCategoryUUIDForType(LLFolderType::FT_TRASH); - if(trash_id.notNull() && (trash_id == id)) - return; - } - cat_array_t* cat_array = get_ptr_in_map(mParentChildCategoryTree, id); - if(cat_array) - { - S32 count = cat_array->size(); - for(S32 i = 0; i < count; ++i) - { - LLViewerInventoryCategory* cat = cat_array->at(i); - if(add(cat,NULL)) - { - cats.push_back(cat); - } - collectDescendentsIf(cat->getUUID(), cats, items, include_trash, add); - } - } - - LLViewerInventoryItem* item = NULL; - item_array_t* item_array = get_ptr_in_map(mParentChildItemTree, id); - - // Move onto items - if(item_array) - { - S32 count = item_array->size(); - for(S32 i = 0; i < count; ++i) - { - item = item_array->at(i); - if(add(NULL, item)) - { - items.push_back(item); - } - } - } -} - -void LLInventoryModel::addChangedMaskForLinks(const LLUUID& object_id, U32 mask) -{ - const LLInventoryObject *obj = getObject(object_id); - if (!obj || obj->getIsLinkType()) - return; - - LLInventoryModel::item_array_t item_array = collectLinksTo(object_id); - for (LLInventoryModel::item_array_t::iterator iter = item_array.begin(); - iter != item_array.end(); - iter++) - { - LLViewerInventoryItem *linked_item = (*iter); - addChangedMask(mask, linked_item->getUUID()); - }; -} - -const LLUUID& LLInventoryModel::getLinkedItemID(const LLUUID& object_id) const -{ - const LLInventoryItem *item = gInventory.getItem(object_id); - if (!item) - { - return object_id; - } - - // Find the base item in case this a link (if it's not a link, - // this will just be inv_item_id) - return item->getLinkedUUID(); -} - -LLViewerInventoryItem* LLInventoryModel::getLinkedItem(const LLUUID& object_id) const -{ - return object_id.notNull() ? getItem(getLinkedItemID(object_id)) : NULL; -} - -LLInventoryModel::item_array_t LLInventoryModel::collectLinksTo(const LLUUID& id) -{ - // Get item list via collectDescendents (slow!) - item_array_t items; - const LLInventoryObject *obj = getObject(id); - // FIXME - should be as in next line, but this is causing a - // stack-smashing crash of cause TBD... check in the REBUILD code. - //if (obj && obj->getIsLinkType()) - if (!obj || obj->getIsLinkType()) - return items; - - std::pair range = mBacklinkMMap.equal_range(id); - for (backlink_mmap_t::iterator it = range.first; it != range.second; ++it) - { - LLViewerInventoryItem *item = getItem(it->second); - if (item) - { - items.push_back(item); - } - } - - return items; -} - -bool LLInventoryModel::isInventoryUsable() const -{ - bool result = false; - if(gInventory.getRootFolderID().notNull() && mIsAgentInvUsable) - { - result = true; - } - return result; -} - -// Calling this method with an inventory item will either change an -// existing item with a matching item_id, or will add the item to the -// current inventory. -U32 LLInventoryModel::updateItem(const LLViewerInventoryItem* item, U32 mask) -{ - if(item->getUUID().isNull()) - { - return mask; - } - - if(!isInventoryUsable()) - { - LL_WARNS(LOG_INV) << "Inventory is broken." << LL_ENDL; - return mask; - } - - if (item->getType() == LLAssetType::AT_MESH) - { - return mask; - } - - LLPointer old_item = getItem(item->getUUID()); - LLPointer new_item; - if(old_item) - { - // We already have an old item, modify its values - new_item = old_item; - LLUUID old_parent_id = old_item->getParentUUID(); - LLUUID new_parent_id = item->getParentUUID(); - bool update_parent_on_server = false; - - if (new_parent_id.isNull() && !LLApp::isExiting()) - { - if (old_parent_id.isNull()) - { - // Item with null parent will end in random location and then in Lost&Found, - // either move to default folder as if it is new item or don't move at all - LL_WARNS(LOG_INV) << "Update attempts to reparent item " << item->getUUID() - << " to null folder. Moving to Lost&Found. Old item name: " << old_item->getName() - << ". New name: " << item->getName() - << "." << LL_ENDL; - new_parent_id = findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); - update_parent_on_server = true; - } - else - { - // Probably not the best way to handle this, we might encounter real case of 'lost&found' at some point - LL_WARNS(LOG_INV) << "Update attempts to reparent item " << item->getUUID() - << " to null folder. Old parent not null. Moving to old parent. Old item name: " << old_item->getName() - << ". New name: " << item->getName() - << "." << LL_ENDL; - new_parent_id = old_parent_id; - update_parent_on_server = true; - } - } - - if(old_parent_id != new_parent_id) - { - // need to update the parent-child tree - item_array_t* item_array; - item_array = get_ptr_in_map(mParentChildItemTree, old_parent_id); - if(item_array) - { - vector_replace_with_last(*item_array, old_item); - } - item_array = get_ptr_in_map(mParentChildItemTree, new_parent_id); - if(item_array) - { - if (update_parent_on_server) - { - LLInventoryModel::LLCategoryUpdate update(new_parent_id, 1); - gInventory.accountForUpdate(update); - } - item_array->push_back(old_item); - } - mask |= LLInventoryObserver::STRUCTURE; - } - if(old_item->getName() != item->getName()) - { - mask |= LLInventoryObserver::LABEL; - } - if (old_item->getPermissions() != item->getPermissions()) - { - mask |= LLInventoryObserver::INTERNAL; - } - old_item->copyViewerItem(item); - if (update_parent_on_server) - { - // Parent id at server is null, so update server even if item already is in the same folder - old_item->setParent(new_parent_id); - new_item->updateParentOnServer(false); - } - mask |= LLInventoryObserver::INTERNAL; - } - else - { - // Simply add this item - new_item = new LLViewerInventoryItem(item); - addItem(new_item); - - if(item->getParentUUID().isNull()) - { - const LLUUID category_id = findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(new_item->getType())); - new_item->setParent(category_id); - item_array_t* item_array = get_ptr_in_map(mParentChildItemTree, category_id); - if( item_array ) - { - LLInventoryModel::LLCategoryUpdate update(category_id, 1); - gInventory.accountForUpdate(update); - - // *FIX: bit of a hack to call update server from here... - new_item->updateParentOnServer(false); - item_array->push_back(new_item); - } - else - { - LL_WARNS(LOG_INV) << "Couldn't find parent-child item tree for " << new_item->getName() << LL_ENDL; - } - } - else - { - // *NOTE: The general scheme is that if every byte of the - // uuid is 0, except for the last one or two,the use the - // last two bytes of the parent id, and match that up - // against the type. For now, we're only worried about - // lost & found. - LLUUID parent_id = item->getParentUUID(); - if(parent_id == CATEGORIZE_LOST_AND_FOUND_ID) - { - parent_id = findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); - new_item->setParent(parent_id); - LLInventoryModel::update_list_t update; - LLInventoryModel::LLCategoryUpdate new_folder(parent_id, 1); - update.push_back(new_folder); - accountForUpdate(update); - - } - item_array_t* item_array = get_ptr_in_map(mParentChildItemTree, parent_id); - if(item_array) - { - item_array->push_back(new_item); - } - else - { - // Whoops! No such parent, make one. - LL_INFOS(LOG_INV) << "Lost item: " << new_item->getUUID() << " - " - << new_item->getName() << LL_ENDL; - parent_id = findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); - new_item->setParent(parent_id); - item_array = get_ptr_in_map(mParentChildItemTree, parent_id); - if(item_array) - { - LLInventoryModel::LLCategoryUpdate update(parent_id, 1); - gInventory.accountForUpdate(update); - // *FIX: bit of a hack to call update server from - // here... - new_item->updateParentOnServer(false); - item_array->push_back(new_item); - } - else - { - LL_WARNS(LOG_INV) << "Lost and found Not there!!" << LL_ENDL; - } - } - } - mask |= LLInventoryObserver::ADD; - } - if(new_item->getType() == LLAssetType::AT_CALLINGCARD) - { - mask |= LLInventoryObserver::CALLING_CARD; - // Handle user created calling cards. - // Target ID is stored in the description field of the card. - LLUUID id; - std::string desc = new_item->getDescription(); - bool isId = desc.empty() ? false : id.set(desc, false); - if (isId) - { - // Valid UUID; set the item UUID and rename it - new_item->setCreator(id); - LLAvatarName av_name; - - if (LLAvatarNameCache::get(id, &av_name)) - { - new_item->rename(av_name.getUserName()); - mask |= LLInventoryObserver::LABEL; - } - else - { - // Fetch the current name - LLAvatarNameCache::get(id, - boost::bind(&LLViewerInventoryItem::onCallingCardNameLookup, new_item.get(), - _1, _2)); - } - - } - } - else if (new_item->getType() == LLAssetType::AT_GESTURE) - { - mask |= LLInventoryObserver::GESTURE; - } - addChangedMask(mask, new_item->getUUID()); - return mask; -} - -LLInventoryModel::cat_array_t* LLInventoryModel::getUnlockedCatArray(const LLUUID& id) -{ - cat_array_t* cat_array = get_ptr_in_map(mParentChildCategoryTree, id); - if (cat_array) - { - llassert_always(!mCategoryLock[id]); - } - return cat_array; -} - -LLInventoryModel::item_array_t* LLInventoryModel::getUnlockedItemArray(const LLUUID& id) -{ - item_array_t* item_array = get_ptr_in_map(mParentChildItemTree, id); - if (item_array) - { - llassert_always(!mItemLock[id]); - } - return item_array; -} - -// Calling this method with an inventory category will either change -// an existing item with the matching id, or it will add the category. -void LLInventoryModel::updateCategory(const LLViewerInventoryCategory* cat, U32 mask) -{ - if(!cat || cat->getUUID().isNull()) - { - return; - } - - if(!isInventoryUsable()) - { - LL_WARNS(LOG_INV) << "Inventory is broken." << LL_ENDL; - return; - } - - LLPointer old_cat = getCategory(cat->getUUID()); - if(old_cat) - { - // We already have an old category, modify its values - LLUUID old_parent_id = old_cat->getParentUUID(); - LLUUID new_parent_id = cat->getParentUUID(); - if(old_parent_id != new_parent_id) - { - // need to update the parent-child tree - cat_array_t* cat_array; - cat_array = getUnlockedCatArray(old_parent_id); - if(cat_array) - { - vector_replace_with_last(*cat_array, old_cat); - } - cat_array = getUnlockedCatArray(new_parent_id); - if(cat_array) - { - cat_array->push_back(old_cat); - } - mask |= LLInventoryObserver::STRUCTURE; - mask |= LLInventoryObserver::INTERNAL; - } - if(old_cat->getName() != cat->getName()) - { - mask |= LLInventoryObserver::LABEL; - } - // Under marketplace, category labels are quite complex and need extra upate - const LLUUID marketplace_id = findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - if (marketplace_id.notNull() && isObjectDescendentOf(cat->getUUID(), marketplace_id)) - { - mask |= LLInventoryObserver::LABEL; - } - old_cat->copyViewerCategory(cat); - addChangedMask(mask, cat->getUUID()); - } - else - { - // add this category - LLPointer new_cat = new LLViewerInventoryCategory(cat->getOwnerID()); - new_cat->copyViewerCategory(cat); - addCategory(new_cat); - - // make sure this category is correctly referenced by its parent. - cat_array_t* cat_array; - cat_array = getUnlockedCatArray(cat->getParentUUID()); - if(cat_array) - { - cat_array->push_back(new_cat); - } - - // make space in the tree for this category's children. - llassert_always(!mCategoryLock[new_cat->getUUID()]); - llassert_always(!mItemLock[new_cat->getUUID()]); - cat_array_t* catsp = new cat_array_t; - item_array_t* itemsp = new item_array_t; - mParentChildCategoryTree[new_cat->getUUID()] = catsp; - mParentChildItemTree[new_cat->getUUID()] = itemsp; - mask |= LLInventoryObserver::ADD; - addChangedMask(mask, cat->getUUID()); - } -} - -void LLInventoryModel::moveObject(const LLUUID& object_id, const LLUUID& cat_id) -{ - LL_DEBUGS(LOG_INV) << "LLInventoryModel::moveObject()" << LL_ENDL; - if(!isInventoryUsable()) - { - LL_WARNS(LOG_INV) << "Inventory is broken." << LL_ENDL; - return; - } - - if((object_id == cat_id) || !is_in_map(mCategoryMap, cat_id)) - { - LL_WARNS(LOG_INV) << "Could not move inventory object " << object_id << " to " - << cat_id << LL_ENDL; - return; - } - LLPointer cat = getCategory(object_id); - if(cat && (cat->getParentUUID() != cat_id)) - { - LL_DEBUGS(LOG_INV) << "Move category '" << make_path(cat) << "' to '" << make_inventory_path(cat_id) << "'" << LL_ENDL; - cat_array_t* cat_array; - cat_array = getUnlockedCatArray(cat->getParentUUID()); - if(cat_array) vector_replace_with_last(*cat_array, cat); - cat_array = getUnlockedCatArray(cat_id); - cat->setParent(cat_id); - if(cat_array) cat_array->push_back(cat); - addChangedMask(LLInventoryObserver::STRUCTURE, object_id); - return; - } - LLPointer item = getItem(object_id); - if(item && (item->getParentUUID() != cat_id)) - { - LL_DEBUGS(LOG_INV) << "Move item '" << make_path(item) << "' to '" << make_inventory_path(cat_id) << "'" << LL_ENDL; - item_array_t* item_array; - item_array = getUnlockedItemArray(item->getParentUUID()); - if(item_array) vector_replace_with_last(*item_array, item); - item_array = getUnlockedItemArray(cat_id); - item->setParent(cat_id); - if(item_array) item_array->push_back(item); - addChangedMask(LLInventoryObserver::STRUCTURE, object_id); - return; - } -} - -// Migrated from llinventoryfunctions -void LLInventoryModel::changeItemParent(LLViewerInventoryItem* item, - const LLUUID& new_parent_id, - bool restamp) -{ - if (item->getParentUUID() == new_parent_id) - { - LL_DEBUGS(LOG_INV) << make_info(item) << " is already in folder " << make_inventory_info(new_parent_id) << LL_ENDL; - } - else - { - LL_INFOS(LOG_INV) << "Move item " << make_info(item) - << " from " << make_inventory_info(item->getParentUUID()) - << " to " << make_inventory_info(new_parent_id) << LL_ENDL; - - LLInventoryModel::LLCategoryUpdate old_folder(item->getParentUUID(),-1); - accountForUpdate(old_folder); - LLInventoryModel::LLCategoryUpdate new_folder(new_parent_id, 1, false); - accountForUpdate(new_folder); - - LLPointer new_item = new LLViewerInventoryItem(item); - new_item->setParent(new_parent_id); - new_item->updateParentOnServer(restamp); - updateItem(new_item); - notifyObservers(); - } -} - -// Migrated from llinventoryfunctions -void LLInventoryModel::changeCategoryParent(LLViewerInventoryCategory* cat, - const LLUUID& new_parent_id, - bool restamp) -{ - if (!cat) - { - return; - } - - // Can't move a folder into a child of itself. - if (isObjectDescendentOf(new_parent_id, cat->getUUID())) - { - return; - } - - LL_INFOS(LOG_INV) << "Move category " << make_info(cat) - << " from " << make_inventory_info(cat->getParentUUID()) - << " to " << make_inventory_info(new_parent_id) << LL_ENDL; - - LLInventoryModel::LLCategoryUpdate old_folder(cat->getParentUUID(), -1); - accountForUpdate(old_folder); - LLInventoryModel::LLCategoryUpdate new_folder(new_parent_id, 1, false); - accountForUpdate(new_folder); - - LLPointer new_cat = new LLViewerInventoryCategory(cat); - new_cat->setParent(new_parent_id); - new_cat->updateParentOnServer(restamp); - updateCategory(new_cat); - notifyObservers(); -} - -void LLInventoryModel::rebuildBrockenLinks() -{ - // make sure we aren't adding expensive Rebuild to anything else. - notifyObservers(); - - for (const broken_links_t::value_type &link_list : mPossiblyBrockenLinks) - { - for (const LLUUID& link_id : link_list.second) - { - addChangedMask(LLInventoryObserver::REBUILD , link_id); - } - } - for (const LLUUID& link_id : mLinksRebuildList) - { - addChangedMask(LLInventoryObserver::REBUILD , link_id); - } - mPossiblyBrockenLinks.clear(); - mLinksRebuildList.clear(); - notifyObservers(); -} - -// Does not appear to be used currently. -void LLInventoryModel::onItemUpdated(const LLUUID& item_id, const LLSD& updates, bool update_parent_version) -{ - U32 mask = LLInventoryObserver::NONE; - - LLPointer item = gInventory.getItem(item_id); - LL_DEBUGS(LOG_INV) << "item_id: [" << item_id << "] name " << (item ? item->getName() : "(NOT FOUND)") << LL_ENDL; - if(item) - { - for (LLSD::map_const_iterator it = updates.beginMap(); - it != updates.endMap(); ++it) - { - if (it->first == "name") - { - LL_INFOS(LOG_INV) << "Updating name from " << item->getName() << " to " << it->second.asString() << LL_ENDL; - item->rename(it->second.asString()); - mask |= LLInventoryObserver::LABEL; - } - else if (it->first == "desc") - { - LL_INFOS(LOG_INV) << "Updating description from " << item->getActualDescription() - << " to " << it->second.asString() << LL_ENDL; - item->setDescription(it->second.asString()); - } - else - { - LL_ERRS(LOG_INV) << "unhandled updates for field: " << it->first << LL_ENDL; - } - } - mask |= LLInventoryObserver::INTERNAL; - addChangedMask(mask, item->getUUID()); - if (update_parent_version) - { - // Descendent count is unchanged, but folder version incremented. - LLInventoryModel::LLCategoryUpdate up(item->getParentUUID(), 0); - accountForUpdate(up); - } - notifyObservers(); // do we want to be able to make this optional? - } -} - -// Not used? -void LLInventoryModel::onCategoryUpdated(const LLUUID& cat_id, const LLSD& updates) -{ - U32 mask = LLInventoryObserver::NONE; - - LLPointer cat = gInventory.getCategory(cat_id); - LL_DEBUGS(LOG_INV) << "cat_id: [" << cat_id << "] name " << (cat ? cat->getName() : "(NOT FOUND)") << LL_ENDL; - if(cat) - { - for (LLSD::map_const_iterator it = updates.beginMap(); - it != updates.endMap(); ++it) - { - if (it->first == "name") - { - LL_INFOS(LOG_INV) << "Updating name from " << cat->getName() << " to " << it->second.asString() << LL_ENDL; - cat->rename(it->second.asString()); - mask |= LLInventoryObserver::LABEL; - } - else - { - LL_ERRS(LOG_INV) << "unhandled updates for field: " << it->first << LL_ENDL; - } - } - mask |= LLInventoryObserver::INTERNAL; - addChangedMask(mask, cat->getUUID()); - notifyObservers(); // do we want to be able to make this optional? - } -} - -// Update model after descendents have been purged. -void LLInventoryModel::onDescendentsPurgedFromServer(const LLUUID& object_id, bool fix_broken_links) -{ - LLPointer cat = getCategory(object_id); - if (cat.notNull()) - { - // do the cache accounting - S32 descendents = cat->getDescendentCount(); - if(descendents > 0) - { - LLInventoryModel::LLCategoryUpdate up(object_id, -descendents); - accountForUpdate(up); - } - - // we know that descendent count is 0, however since the - // accounting may actually not do an update, we should force - // it here. - cat->setDescendentCount(0); - - // unceremoniously remove anything we have locally stored. - LLInventoryModel::cat_array_t categories; - LLInventoryModel::item_array_t items; - collectDescendents(object_id, - categories, - items, - LLInventoryModel::INCLUDE_TRASH); - S32 count = items.size(); - - LLUUID uu_id; - for(S32 i = 0; i < count; ++i) - { - uu_id = items.at(i)->getUUID(); - - // This check prevents the deletion of a previously deleted item. - // This is necessary because deletion is not done in a hierarchical - // order. The current item may have been already deleted as a child - // of its deleted parent. - if (getItem(uu_id)) - { - deleteObject(uu_id, fix_broken_links); - } - } - - count = categories.size(); - // Slightly kludgy way to make sure categories are removed - // only after their child categories have gone away. - - // FIXME: Would probably make more sense to have this whole - // descendent-clearing thing be a post-order recursive - // function to get the leaf-up behavior automatically. - S32 deleted_count; - S32 total_deleted_count = 0; - do - { - deleted_count = 0; - for(S32 i = 0; i < count; ++i) - { - uu_id = categories.at(i)->getUUID(); - if (getCategory(uu_id)) - { - cat_array_t* cat_list = getUnlockedCatArray(uu_id); - if (!cat_list || (cat_list->size() == 0)) - { - deleteObject(uu_id, fix_broken_links); - deleted_count++; - } - } - } - total_deleted_count += deleted_count; - } - while (deleted_count > 0); - if (total_deleted_count != count) - { - LL_WARNS(LOG_INV) << "Unexpected count of categories deleted, got " - << total_deleted_count << " expected " << count << LL_ENDL; - } - //gInventory.validate(); - } -} - -// Update model after an item is confirmed as removed from -// server. Works for categories or items. -void LLInventoryModel::onObjectDeletedFromServer(const LLUUID& object_id, bool fix_broken_links, bool update_parent_version, bool do_notify_observers) -{ - LLPointer obj = getObject(object_id); - if(obj) - { - if (getCategory(object_id)) - { - // For category, need to delete/update all children first. - onDescendentsPurgedFromServer(object_id, fix_broken_links); - } - - - // From item/cat removeFromServer() - if (update_parent_version) - { - LLInventoryModel::LLCategoryUpdate up(obj->getParentUUID(), -1); - accountForUpdate(up); - } - - // From purgeObject() - LLViewerInventoryItem *item = getItem(object_id); - if (item && (item->getType() != LLAssetType::AT_LSL_TEXT)) - { - LLPreview::hide(object_id, true); - } - deleteObject(object_id, fix_broken_links, do_notify_observers); - } -} - - -// Delete a particular inventory object by ID. -void LLInventoryModel::deleteObject(const LLUUID& id, bool fix_broken_links, bool do_notify_observers) -{ - LL_DEBUGS(LOG_INV) << "LLInventoryModel::deleteObject()" << LL_ENDL; - LLPointer obj = getObject(id); - if (!obj) - { - LL_WARNS(LOG_INV) << "Deleting non-existent object [ id: " << id << " ] " << LL_ENDL; - return; - } - - //collect the links before removing the item from mItemMap - LLInventoryModel::item_array_t links = collectLinksTo(id); - - LL_DEBUGS(LOG_INV) << "Deleting inventory object " << id << LL_ENDL; - mLastItem = NULL; - LLUUID parent_id = obj->getParentUUID(); - mCategoryMap.erase(id); - mItemMap.erase(id); - //mInventory.erase(id); - item_array_t* item_list = getUnlockedItemArray(parent_id); - if(item_list) - { - LLPointer item = (LLViewerInventoryItem*)((LLInventoryObject*)obj); - vector_replace_with_last(*item_list, item); - } - cat_array_t* cat_list = getUnlockedCatArray(parent_id); - if(cat_list) - { - LLPointer cat = (LLViewerInventoryCategory*)((LLInventoryObject*)obj); - vector_replace_with_last(*cat_list, cat); - } - - // Note : We need to tell the inventory observers that those things are going to be deleted *before* the tree is cleared or they won't know what to delete (in views and view models) - addChangedMask(LLInventoryObserver::REMOVE, id); - gInventory.notifyObservers(); - - item_list = getUnlockedItemArray(id); - if(item_list) - { - if (item_list->size()) - { - LL_WARNS(LOG_INV) << "Deleting cat " << id << " while it still has child items" << LL_ENDL; - } - delete item_list; - mParentChildItemTree.erase(id); - } - cat_list = getUnlockedCatArray(id); - if(cat_list) - { - if (cat_list->size()) - { - LL_WARNS(LOG_INV) << "Deleting cat " << id << " while it still has child cats" << LL_ENDL; - } - delete cat_list; - mParentChildCategoryTree.erase(id); - } - addChangedMask(LLInventoryObserver::REMOVE, id); - - bool is_link_type = obj->getIsLinkType(); - if (is_link_type) - { - removeBacklinkInfo(obj->getUUID(), obj->getLinkedUUID()); - } - - // Can't have links to links, so there's no need for this update - // if the item removed is a link. Can also skip if source of the - // update is getting broken link info separately. - if (fix_broken_links && !is_link_type) - { - rebuildLinkItems(links); - } - obj = nullptr; // delete obj - if (do_notify_observers) - { - notifyObservers(); - } -} - -void LLInventoryModel::rebuildLinkItems(LLInventoryModel::item_array_t& items) -{ - // REBUILD is expensive, so clear the current change list first else - // everything else on the changelist will also get rebuilt. - if (items.size() > 0) - { - notifyObservers(); - for (LLInventoryModel::item_array_t::const_iterator iter = items.begin(); - iter != items.end(); - iter++) - { - const LLViewerInventoryItem *linked_item = (*iter); - if (linked_item) - { - addChangedMask(LLInventoryObserver::REBUILD, linked_item->getUUID()); - } - } - notifyObservers(); - } -} - -// Add/remove an observer. If the observer is destroyed, be sure to -// remove it. -void LLInventoryModel::addObserver(LLInventoryObserver* observer) -{ - mObservers.insert(observer); -} - -void LLInventoryModel::removeObserver(LLInventoryObserver* observer) -{ - mObservers.erase(observer); -} - -bool LLInventoryModel::containsObserver(LLInventoryObserver* observer) const -{ - return mObservers.find(observer) != mObservers.end(); -} - -void LLInventoryModel::idleNotifyObservers() -{ - // *FIX: Think I want this conditional or moved elsewhere... - handleResponses(true); - - if (mLinksRebuildList.size() > 0) - { - if (mModifyMask != LLInventoryObserver::NONE || (mChangedItemIDs.size() != 0)) - { - notifyObservers(); - } - for (const LLUUID& link_id : mLinksRebuildList) - { - addChangedMask(LLInventoryObserver::REBUILD , link_id); - } - mLinksRebuildList.clear(); - notifyObservers(); - } - - if (mModifyMask == LLInventoryObserver::NONE && (mChangedItemIDs.size() == 0)) - { - return; - } - notifyObservers(); -} - -// Call this method when it's time to update everyone on a new state. -void LLInventoryModel::notifyObservers() -{ - if (mIsNotifyObservers) - { - // Within notifyObservers, something called notifyObservers - // again. This type of recursion is unsafe because it causes items to be - // processed twice, and this can easily lead to infinite loops. - LL_WARNS(LOG_INV) << "Call was made to notifyObservers within notifyObservers!" << LL_ENDL; - return; - } - - mIsNotifyObservers = true; - for (observer_list_t::iterator iter = mObservers.begin(); - iter != mObservers.end(); ) - { - LLInventoryObserver* observer = *iter; - observer->changed(mModifyMask); - - // safe way to increment since changed may delete entries! (@!##%@!@&*!) - iter = mObservers.upper_bound(observer); - } - - // If there were any changes that arrived during notifyObservers, - // shedule them for next loop - mModifyMask = mModifyMaskBacklog; - mChangedItemIDs.clear(); - mChangedItemIDs.insert(mChangedItemIDsBacklog.begin(), mChangedItemIDsBacklog.end()); - mAddedItemIDs.clear(); - mAddedItemIDs.insert(mAddedItemIDsBacklog.begin(), mAddedItemIDsBacklog.end()); - - mModifyMaskBacklog = LLInventoryObserver::NONE; - mChangedItemIDsBacklog.clear(); - mAddedItemIDsBacklog.clear(); - - mIsNotifyObservers = false; -} - -// store flag for change -// and id of object change applies to -void LLInventoryModel::addChangedMask(U32 mask, const LLUUID& referent) -{ - if (mIsNotifyObservers) - { - // Something marked an item for change within a call to notifyObservers - // (which is in the process of processing the list of items marked for change). - // This means the change will have to be processed later. - // It's preferable for this not to happen, but it's not an issue unless code - // specifically wants to notifyObservers immediately (changes won't happen untill later) - LL_WARNS(LOG_INV) << "Adding changed mask within notify observers! Change's processing will be performed on idle." << LL_ENDL; - LLViewerInventoryItem *item = getItem(referent); - if (item) - { - LL_WARNS(LOG_INV) << "Item " << item->getName() << LL_ENDL; - } - else - { - LLViewerInventoryCategory *cat = getCategory(referent); - if (cat) - { - LL_WARNS(LOG_INV) << "Category " << cat->getName() << LL_ENDL; - } - } - } - - if (mIsNotifyObservers) - { - mModifyMaskBacklog |= mask; - } - else - { - mModifyMask |= mask; - } - - bool needs_update = false; - if (referent.notNull()) - { - if (mIsNotifyObservers) - { - needs_update = mChangedItemIDsBacklog.find(referent) == mChangedItemIDsBacklog.end(); - } - else - { - needs_update = mChangedItemIDs.find(referent) == mChangedItemIDs.end(); - } - } - - if (needs_update) - { - if (mIsNotifyObservers) - { - mChangedItemIDsBacklog.insert(referent); - } - else - { - mChangedItemIDs.insert(referent); - } - - if (mask != LLInventoryObserver::LABEL) - { - // Fix me: From DD-81, probably shouldn't be here, instead - // should be somewhere in an observer or in - // LLMarketplaceInventoryObserver::onIdleProcessQueue - update_marketplace_category(referent, false); - } - - if (mask & LLInventoryObserver::ADD) - { - if (mIsNotifyObservers) - { - mAddedItemIDsBacklog.insert(referent); - } - else - { - mAddedItemIDs.insert(referent); - } - } - - // Update all linked items. Starting with just LABEL because I'm - // not sure what else might need to be accounted for this. - if (mask & LLInventoryObserver::LABEL) - { - addChangedMaskForLinks(referent, LLInventoryObserver::LABEL); - } - } -} - -bool LLInventoryModel::fetchDescendentsOf(const LLUUID& folder_id) const -{ - if(folder_id.isNull()) - { - LL_WARNS(LOG_INV) << "Calling fetch descendents on NULL folder id!" << LL_ENDL; - return false; - } - LLViewerInventoryCategory* cat = getCategory(folder_id); - if(!cat) - { - LL_WARNS(LOG_INV) << "Asked to fetch descendents of non-existent folder: " - << folder_id << LL_ENDL; - return false; - } - //S32 known_descendents = 0; - ///cat_array_t* categories = get_ptr_in_map(mParentChildCategoryTree, folder_id); - //item_array_t* items = get_ptr_in_map(mParentChildItemTree, folder_id); - //if(categories) - //{ - // known_descendents += categories->size(); - //} - //if(items) - //{ - // known_descendents += items->size(); - //} - return cat->fetch(); -} - -//static -std::string LLInventoryModel::getInvCacheAddres(const LLUUID& owner_id) -{ - std::string inventory_addr; - std::string owner_id_str; - owner_id.toString(owner_id_str); - std::string path(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, owner_id_str)); - if (LLGridManager::getInstance()->isInProductionGrid()) - { - inventory_addr = llformat(PRODUCTION_CACHE_FORMAT_STRING, path.c_str()); - } - else - { - // NOTE: The inventory cache filenames now include the grid name. - // Add controls against directory traversal or problematic pathname lengths - // if your viewer uses grid names from an untrusted source. - const std::string& grid_id_str = LLGridManager::getInstance()->getGridId(); - const std::string& grid_id_lower = utf8str_tolower(grid_id_str); - inventory_addr = llformat(GRID_CACHE_FORMAT_STRING, path.c_str(), grid_id_lower.c_str()); - } - return inventory_addr; -} - -void LLInventoryModel::cache( - const LLUUID& parent_folder_id, - const LLUUID& agent_id) -{ - LL_DEBUGS(LOG_INV) << "Caching " << parent_folder_id << " for " << agent_id - << LL_ENDL; - LLViewerInventoryCategory* root_cat = getCategory(parent_folder_id); - if(!root_cat) return; - cat_array_t categories; - categories.push_back(root_cat); - item_array_t items; - - LLCanCache can_cache(this); - can_cache(root_cat, NULL); - collectDescendentsIf( - parent_folder_id, - categories, - items, - INCLUDE_TRASH, - can_cache); - // Use temporary file to avoid potential conflicts with other - // instances (even a 'read only' instance unzips into a file) - std::string temp_file = gDirUtilp->getTempFilename(); - saveToFile(temp_file, categories, items); - std::string gzip_filename = getInvCacheAddres(agent_id); - gzip_filename.append(".gz"); - if(gzip_file(temp_file, gzip_filename)) - { - LL_DEBUGS(LOG_INV) << "Successfully compressed " << temp_file << " to " << gzip_filename << LL_ENDL; - LLFile::remove(temp_file); - } - else - { - LL_WARNS(LOG_INV) << "Unable to compress " << temp_file << " into " << gzip_filename << LL_ENDL; - } -} - - -void LLInventoryModel::addCategory(LLViewerInventoryCategory* category) -{ - //LL_INFOS(LOG_INV) << "LLInventoryModel::addCategory()" << LL_ENDL; - if(category) - { - // We aren't displaying the Meshes folder - if (category->mPreferredType == LLFolderType::FT_MESH) - { - return; - } - - // try to localize default names first. See EXT-8319, EXT-7051. - category->localizeName(); - - // Insert category uniquely into the map - mCategoryMap[category->getUUID()] = category; // LLPointer will deref and delete the old one - //mInventory[category->getUUID()] = category; - } -} - -bool LLInventoryModel::hasBacklinkInfo(const LLUUID& link_id, const LLUUID& target_id) const -{ - std::pair range; - range = mBacklinkMMap.equal_range(target_id); - for (backlink_mmap_t::const_iterator it = range.first; it != range.second; ++it) - { - if (it->second == link_id) - { - return true; - } - } - return false; -} - -void LLInventoryModel::addBacklinkInfo(const LLUUID& link_id, const LLUUID& target_id) -{ - if (!hasBacklinkInfo(link_id, target_id)) - { - mBacklinkMMap.insert(std::make_pair(target_id, link_id)); - } -} - -void LLInventoryModel::removeBacklinkInfo(const LLUUID& link_id, const LLUUID& target_id) -{ - std::pair range; - range = mBacklinkMMap.equal_range(target_id); - for (backlink_mmap_t::iterator it = range.first; it != range.second; ) - { - if (it->second == link_id) - { - backlink_mmap_t::iterator delete_it = it; // iterator will be invalidated by erase. - ++it; - mBacklinkMMap.erase(delete_it); - } - else - { - ++it; - } - } -} - -void LLInventoryModel::addItem(LLViewerInventoryItem* item) -{ - llassert(item); - if(item) - { - if (item->getType() <= LLAssetType::AT_NONE) - { - LL_WARNS(LOG_INV) << "Got bad asset type for item [ name: " << item->getName() - << " type: " << item->getType() - << " inv-type: " << item->getInventoryType() << " ], ignoring." << LL_ENDL; - return; - } - - if (LLAssetType::lookup(item->getType()) == LLAssetType::BADLOOKUP) - { - if (item->getType() >= LLAssetType::AT_COUNT) - { - // Not yet supported. - LL_DEBUGS(LOG_INV) << "Got unknown asset type for item [ name: " << item->getName() - << " type: " << item->getType() - << " inv-type: " << item->getInventoryType() << " ]." << LL_ENDL; - } - else - { - LL_WARNS(LOG_INV) << "Got unknown asset type for item [ name: " << item->getName() - << " type: " << item->getType() - << " inv-type: " << item->getInventoryType() << " ]." << LL_ENDL; - } - } - - // This condition means that we tried to add a link without the baseobj being in memory. - // The item will show up as a broken link. - if (item->getIsBrokenLink()) - { - if (item->getAssetUUID().notNull() - && LLInventoryModelBackgroundFetch::getInstance()->folderFetchActive()) - { - // Schedule this link for a recheck as inventory gets loaded - // Todo: expand to cover not just an initial fetch - mPossiblyBrockenLinks[item->getAssetUUID()].insert(item->getUUID()); - - // Do a blank rebuild of links once fetch is done - if (!mBulkFecthCallbackSlot.connected()) - { - // Links might take a while to update this way, and there - // might be a lot of them. A better option might be to check - // links periodically with final check on fetch completion. - mBulkFecthCallbackSlot = - LLInventoryModelBackgroundFetch::getInstance()->setFetchCompletionCallback( - [this]() - { - // rebuild is just in case, primary purpose is to wipe - // the list since we won't be getting anything 'new' - // see mLinksRebuildList - rebuildBrockenLinks(); - mBulkFecthCallbackSlot.disconnect(); - }); - } - LL_DEBUGS(LOG_INV) << "Scheduling a link to be rebuilt later [ name: " << item->getName() - << " itemID: " << item->getUUID() - << " assetID: " << item->getAssetUUID() << " ) parent: " << item->getParentUUID() << LL_ENDL; - - } - else - { - LL_INFOS(LOG_INV) << "Adding broken link [ name: " << item->getName() - << " itemID: " << item->getUUID() - << " assetID: " << item->getAssetUUID() << " ) parent: " << item->getParentUUID() << LL_ENDL; - } - } - if (!mPossiblyBrockenLinks.empty()) - { - // check if we are waiting for this item - broken_links_t::iterator iter = mPossiblyBrockenLinks.find(item->getUUID()); - if (iter != mPossiblyBrockenLinks.end()) - { - mLinksRebuildList.insert(iter->second.begin() , iter->second.end()); - mPossiblyBrockenLinks.erase(iter); - } - } - if (item->getIsLinkType()) - { - // Add back-link from linked-to UUID. - const LLUUID& link_id = item->getUUID(); - const LLUUID& target_id = item->getLinkedUUID(); - addBacklinkInfo(link_id, target_id); - } - mItemMap[item->getUUID()] = item; - } -} - -// Empty the entire contents -void LLInventoryModel::empty() -{ -// LL_INFOS(LOG_INV) << "LLInventoryModel::empty()" << LL_ENDL; - std::for_each( - mParentChildCategoryTree.begin(), - mParentChildCategoryTree.end(), - DeletePairedPointer()); - mParentChildCategoryTree.clear(); - std::for_each( - mParentChildItemTree.begin(), - mParentChildItemTree.end(), - DeletePairedPointer()); - mParentChildItemTree.clear(); - mBacklinkMMap.clear(); // forget all backlink information. - mCategoryMap.clear(); // remove all references (should delete entries) - mItemMap.clear(); // remove all references (should delete entries) - mLastItem = NULL; - //mInventory.clear(); -} - -void LLInventoryModel::accountForUpdate(const LLCategoryUpdate& update) const -{ - LLViewerInventoryCategory* cat = getCategory(update.mCategoryID); - if(cat) - { - S32 version = cat->getVersion(); - if(version != LLViewerInventoryCategory::VERSION_UNKNOWN) - { - S32 descendents_server = cat->getDescendentCount(); - S32 descendents_actual = cat->getViewerDescendentCount(); - if(descendents_server == descendents_actual) - { - descendents_actual += update.mDescendentDelta; - cat->setDescendentCount(descendents_actual); - if (update.mChangeVersion) - { - cat->setVersion(++version); - } - LL_DEBUGS(LOG_INV) << "accounted: '" << cat->getName() << "' " - << version << " with " << descendents_actual - << " descendents." << LL_ENDL; - } - else - { - // Error condition, this means that the category did not register that - // it got new descendents (perhaps because it is still being loaded) - // which means its descendent count will be wrong. - LL_WARNS(LOG_INV) << "Accounting failed for '" << cat->getName() << "' version:" - << version << " due to mismatched descendent count: server == " - << descendents_server << ", viewer == " << descendents_actual << LL_ENDL; - } - } - else - { - LL_WARNS(LOG_INV) << "Accounting failed for '" << cat->getName() << "' version: unknown (" - << version << ")" << LL_ENDL; - } - } - else - { - LL_WARNS(LOG_INV) << "No category found for update " << update.mCategoryID << LL_ENDL; - } -} - -void LLInventoryModel::accountForUpdate( - const LLInventoryModel::update_list_t& update) const -{ - update_list_t::const_iterator it = update.begin(); - update_list_t::const_iterator end = update.end(); - for(; it != end; ++it) - { - accountForUpdate(*it); - } -} - -void LLInventoryModel::accountForUpdate( - const LLInventoryModel::update_map_t& update) const -{ - LLCategoryUpdate up; - update_map_t::const_iterator it = update.begin(); - update_map_t::const_iterator end = update.end(); - for(; it != end; ++it) - { - up.mCategoryID = (*it).first; - up.mDescendentDelta = (*it).second.mValue; - accountForUpdate(up); - } -} - -LLInventoryModel::EHasChildren LLInventoryModel::categoryHasChildren( - const LLUUID& cat_id) const -{ - LLViewerInventoryCategory* cat = getCategory(cat_id); - if(!cat) return CHILDREN_NO; - if(cat->getDescendentCount() > 0) - { - return CHILDREN_YES; - } - if(cat->getDescendentCount() == 0) - { - return CHILDREN_NO; - } - if((cat->getDescendentCount() == LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN) - || (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN)) - { - return CHILDREN_MAYBE; - } - - // Shouldn't have to run this, but who knows. - parent_cat_map_t::const_iterator cat_it = mParentChildCategoryTree.find(cat->getUUID()); - if (cat_it != mParentChildCategoryTree.end() && cat_it->second->size() > 0) - { - return CHILDREN_YES; - } - parent_item_map_t::const_iterator item_it = mParentChildItemTree.find(cat->getUUID()); - if (item_it != mParentChildItemTree.end() && item_it->second->size() > 0) - { - return CHILDREN_YES; - } - - return CHILDREN_NO; -} - -bool LLInventoryModel::isCategoryComplete(const LLUUID& cat_id) const -{ - LLViewerInventoryCategory* cat = getCategory(cat_id); - if(cat && (cat->getVersion()!=LLViewerInventoryCategory::VERSION_UNKNOWN)) - { - S32 descendents_server = cat->getDescendentCount(); - S32 descendents_actual = cat->getViewerDescendentCount(); - if(descendents_server == descendents_actual) - { - return true; - } - } - return false; -} - -bool LLInventoryModel::loadSkeleton( - const LLSD& options, - const LLUUID& owner_id) -{ - LL_PROFILE_ZONE_SCOPED; - LL_DEBUGS(LOG_INV) << "importing inventory skeleton for " << owner_id << LL_ENDL; - - typedef std::set, InventoryIDPtrLess> cat_set_t; - cat_set_t temp_cats; - bool rv = true; - - for(LLSD::array_const_iterator it = options.beginArray(), - end = options.endArray(); it != end; ++it) - { - LLSD name = (*it)["name"]; - LLSD folder_id = (*it)["folder_id"]; - LLSD parent_id = (*it)["parent_id"]; - LLSD version = (*it)["version"]; - if(name.isDefined() - && folder_id.isDefined() - && parent_id.isDefined() - && version.isDefined() - && folder_id.asUUID().notNull() // if an id is null, it locks the viewer. - ) - { - LLPointer cat = new LLViewerInventoryCategory(owner_id); - cat->rename(name.asString()); - cat->setUUID(folder_id.asUUID()); - cat->setParent(parent_id.asUUID()); - - LLFolderType::EType preferred_type = LLFolderType::FT_NONE; - LLSD type_default = (*it)["type_default"]; - if(type_default.isDefined()) - { - preferred_type = (LLFolderType::EType)type_default.asInteger(); - } - cat->setPreferredType(preferred_type); - cat->setVersion(version.asInteger()); - temp_cats.insert(cat); - } - else - { - LL_WARNS(LOG_INV) << "Unable to import near " << name.asString() << LL_ENDL; - rv = false; - } - } - - S32 cached_category_count = 0; - S32 cached_item_count = 0; - if(!temp_cats.empty()) - { - update_map_t child_counts; - cat_array_t categories; - item_array_t items; - changed_items_t categories_to_update; - item_array_t possible_broken_links; - cat_set_t invalid_categories; // Used to mark categories that weren't successfully loaded. - std::string inventory_filename = getInvCacheAddres(owner_id); - const S32 NO_VERSION = LLViewerInventoryCategory::VERSION_UNKNOWN; - std::string gzip_filename(inventory_filename); - gzip_filename.append(".gz"); - LLFILE* fp = LLFile::fopen(gzip_filename, "rb"); - bool remove_inventory_file = false; - if (LLAppViewer::instance()->isSecondInstance()) - { - // Safeguard viewer against trying to unpack file twice - // ex: user logs into two accounts simultaneously, so two - // viewers are trying to unpack library into same file - // - // Would be better to do it in gunzip_file, but it doesn't - // have access to llfilesystem - inventory_filename = gDirUtilp->getTempFilename(); - remove_inventory_file = true; - } - if(fp) - { - fclose(fp); - fp = NULL; - if(gunzip_file(gzip_filename, inventory_filename)) - { - // we only want to remove the inventory file if it was - // gzipped before we loaded, and we successfully - // gunziped it. - remove_inventory_file = true; - } - else - { - LL_INFOS(LOG_INV) << "Unable to gunzip " << gzip_filename << LL_ENDL; - } - } - bool is_cache_obsolete = false; - if (loadFromFile(inventory_filename, categories, items, categories_to_update, is_cache_obsolete)) - { - // We were able to find a cache of files. So, use what we - // found to generate a set of categories we should add. We - // will go through each category loaded and if the version - // does not match, invalidate the version. - S32 count = categories.size(); - cat_set_t::iterator not_cached = temp_cats.end(); - std::set cached_ids; - for(S32 i = 0; i < count; ++i) - { - LLViewerInventoryCategory* cat = categories[i]; - cat_set_t::iterator cit = temp_cats.find(cat); - if (cit == temp_cats.end()) - { - continue; // cache corruption?? not sure why this happens -SJB - } - LLViewerInventoryCategory* tcat = *cit; - - if (categories_to_update.find(tcat->getUUID()) != categories_to_update.end()) - { - tcat->setVersion(NO_VERSION); - LL_WARNS() << "folder to update: " << tcat->getName() << LL_ENDL; - } - - // we can safely ignore anything loaded from file, but - // not sent down in the skeleton. Must have been removed from inventory. - if (cit == not_cached) - { - continue; - } - else if (cat->getVersion() != tcat->getVersion()) - { - // if the cached version does not match the server version, - // throw away the version we have so we can fetch the - // correct contents the next time the viewer opens the folder. - tcat->setVersion(NO_VERSION); - } - else - { - cached_ids.insert(tcat->getUUID()); - - // At the moment download does not provide a thumbnail - // uuid, use the one from cache - tcat->setThumbnailUUID(cat->getThumbnailUUID()); - } - } - - // go ahead and add the cats returned during the download - std::set::const_iterator not_cached_id = cached_ids.end(); - cached_category_count = cached_ids.size(); - for(cat_set_t::iterator it = temp_cats.begin(); it != temp_cats.end(); ++it) - { - if(cached_ids.find((*it)->getUUID()) == not_cached_id) - { - // this check is performed so that we do not - // mark new folders in the skeleton (and not in cache) - // as being cached. - LLViewerInventoryCategory *llvic = (*it); - llvic->setVersion(NO_VERSION); - } - addCategory(*it); - ++child_counts[(*it)->getParentUUID()]; - } - - // Add all the items loaded which are parented to a - // category with a correctly cached parent - S32 bad_link_count = 0; - S32 good_link_count = 0; - S32 recovered_link_count = 0; - cat_map_t::iterator unparented = mCategoryMap.end(); - for(item_array_t::const_iterator item_iter = items.begin(); - item_iter != items.end(); - ++item_iter) - { - LLViewerInventoryItem *item = (*item_iter).get(); - const cat_map_t::iterator cit = mCategoryMap.find(item->getParentUUID()); - - if(cit != unparented) - { - const LLViewerInventoryCategory* cat = cit->second.get(); - if(cat->getVersion() != NO_VERSION) - { - // This can happen if the linked object's baseobj is removed from the cache but the linked object is still in the cache. - if (item->getIsBrokenLink()) - { - //bad_link_count++; - LL_DEBUGS(LOG_INV) << "Attempted to add cached link item without baseobj present ( name: " - << item->getName() << " itemID: " << item->getUUID() - << " assetID: " << item->getAssetUUID() - << " ). Ignoring and invalidating " << cat->getName() << " . " << LL_ENDL; - possible_broken_links.push_back(item); - continue; - } - else if (item->getIsLinkType()) - { - good_link_count++; - } - addItem(item); - cached_item_count += 1; - ++child_counts[cat->getUUID()]; - } - } - } - if (possible_broken_links.size() > 0) - { - for(item_array_t::const_iterator item_iter = possible_broken_links.begin(); - item_iter != possible_broken_links.end(); - ++item_iter) - { - LLViewerInventoryItem *item = (*item_iter).get(); - const cat_map_t::iterator cit = mCategoryMap.find(item->getParentUUID()); - const LLViewerInventoryCategory* cat = cit->second.get(); - if (item->getIsBrokenLink()) - { - bad_link_count++; - invalid_categories.insert(cit->second); - //LL_INFOS(LOG_INV) << "link still broken: " << item->getName() << " in folder " << cat->getName() << LL_ENDL; - } - else - { - // was marked as broken because of loading order, its actually fine to load - addItem(item); - cached_item_count += 1; - ++child_counts[cat->getUUID()]; - recovered_link_count++; - } - } - - LL_DEBUGS(LOG_INV) << "Attempted to add " << bad_link_count - << " cached link items without baseobj present. " - << good_link_count << " link items were successfully added. " - << recovered_link_count << " links added in recovery. " - << "The corresponding categories were invalidated." << LL_ENDL; - } - - } - else - { - // go ahead and add everything after stripping the version - // information. - for(cat_set_t::iterator it = temp_cats.begin(); it != temp_cats.end(); ++it) - { - LLViewerInventoryCategory *llvic = (*it); - llvic->setVersion(NO_VERSION); - addCategory(*it); - } - } - - // Invalidate all categories that failed fetching descendents for whatever - // reason (e.g. one of the descendents was a broken link). - for (cat_set_t::iterator invalid_cat_it = invalid_categories.begin(); - invalid_cat_it != invalid_categories.end(); - invalid_cat_it++) - { - LLViewerInventoryCategory* cat = (*invalid_cat_it).get(); - cat->setVersion(NO_VERSION); - LL_DEBUGS(LOG_INV) << "Invalidating category name: " << cat->getName() << " UUID: " << cat->getUUID() << " due to invalid descendents cache" << LL_ENDL; - } - if (invalid_categories.size() > 0) - { - LL_DEBUGS(LOG_INV) << "Invalidated " << invalid_categories.size() << " categories due to invalid descendents cache" << LL_ENDL; - } - - // At this point, we need to set the known descendents for each - // category which successfully cached so that we do not - // needlessly fetch descendents for categories which we have. - update_map_t::const_iterator no_child_counts = child_counts.end(); - for(cat_set_t::iterator it = temp_cats.begin(); it != temp_cats.end(); ++it) - { - LLViewerInventoryCategory* cat = (*it).get(); - if(cat->getVersion() != NO_VERSION) - { - update_map_t::const_iterator the_count = child_counts.find(cat->getUUID()); - if(the_count != no_child_counts) - { - const S32 num_descendents = (*the_count).second.mValue; - cat->setDescendentCount(num_descendents); - } - else - { - cat->setDescendentCount(0); - } - } - } - - if(remove_inventory_file) - { - // clean up the gunzipped file. - LLFile::remove(inventory_filename); - } - if(is_cache_obsolete && !LLAppViewer::instance()->isSecondInstance()) - { - // If out of date, remove the gzipped file too. - LL_WARNS(LOG_INV) << "Inv cache out of date, removing" << LL_ENDL; - LLFile::remove(gzip_filename); - } - categories.clear(); // will unref and delete entries - } - - LL_INFOS(LOG_INV) << "Successfully loaded " << cached_category_count - << " categories and " << cached_item_count << " items from cache." - << LL_ENDL; - - return rv; -} - -// This is a brute force method to rebuild the entire parent-child -// relations. The overall operation has O(NlogN) performance, which -// should be sufficient for our needs. -void LLInventoryModel::buildParentChildMap() -{ - LL_INFOS(LOG_INV) << "LLInventoryModel::buildParentChildMap()" << LL_ENDL; - - // *NOTE: I am skipping the logic around folder version - // synchronization here because it seems if a folder is lost, we - // might actually want to invalidate it at that point - not - // attempt to cache. More time & thought is necessary. - - // First the categories. We'll copy all of the categories into a - // temporary container to iterate over (oh for real iterators.) - // While we're at it, we'll allocate the arrays in the trees. - cat_array_t cats; - cat_array_t* catsp; - item_array_t* itemsp; - - for(cat_map_t::iterator cit = mCategoryMap.begin(); cit != mCategoryMap.end(); ++cit) - { - LLViewerInventoryCategory* cat = cit->second; - cats.push_back(cat); - if (mParentChildCategoryTree.count(cat->getUUID()) == 0) - { - llassert_always(!mCategoryLock[cat->getUUID()]); - catsp = new cat_array_t; - mParentChildCategoryTree[cat->getUUID()] = catsp; - } - if (mParentChildItemTree.count(cat->getUUID()) == 0) - { - llassert_always(!mItemLock[cat->getUUID()]); - itemsp = new item_array_t; - mParentChildItemTree[cat->getUUID()] = itemsp; - } - } - - // Insert a special parent for the root - so that lookups on - // LLUUID::null as the parent work correctly. This is kind of a - // blatent wastes of space since we allocate a block of memory for - // the array, but whatever - it's not that much space. - if (mParentChildCategoryTree.count(LLUUID::null) == 0) - { - catsp = new cat_array_t; - mParentChildCategoryTree[LLUUID::null] = catsp; - } - - // Now we have a structure with all of the categories that we can - // iterate over and insert into the correct place in the child - // category tree. - S32 count = cats.size(); - S32 i; - S32 lost = 0; - cat_array_t lost_cats; - for(i = 0; i < count; ++i) - { - LLViewerInventoryCategory* cat = cats.at(i); - catsp = getUnlockedCatArray(cat->getParentUUID()); - if(catsp && - // Only the two root folders should be children of null. - // Others should go to lost & found. - (cat->getParentUUID().notNull() || - cat->getPreferredType() == LLFolderType::FT_ROOT_INVENTORY )) - { - catsp->push_back(cat); - } - else - { - // *NOTE: This process could be a lot more efficient if we - // used the new MoveInventoryFolder message, but we would - // have to continue to do the update & build here. So, to - // implement it, we would need a set or map of uuid pairs - // which would be (folder_id, new_parent_id) to be sent up - // to the server. - LL_INFOS(LOG_INV) << "Lost category: " << cat->getUUID() << " - " - << cat->getName() << LL_ENDL; - ++lost; - lost_cats.push_back(cat); - } - } - if(lost) - { - LL_WARNS(LOG_INV) << "Found " << lost << " lost categories." << LL_ENDL; - } - - // Do moves in a separate pass to make sure we've properly filed - // the FT_LOST_AND_FOUND category before we try to find its UUID. - for(i = 0; igetPreferredType(); - if(LLFolderType::FT_NONE == pref) - { - cat->setParent(findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND)); - } - else if(LLFolderType::FT_ROOT_INVENTORY == pref) - { - // it's the root - cat->setParent(LLUUID::null); - } - else - { - // it's a protected folder. - cat->setParent(gInventory.getRootFolderID()); - } - // FIXME note that updateServer() fails with protected - // types, so this will not work as intended in that case. - // UpdateServer uses AIS, AIS cat move is not implemented yet - // cat->updateServer(true); - - // MoveInventoryFolder message, intentionally per item - cat->updateParentOnServer(false); - catsp = getUnlockedCatArray(cat->getParentUUID()); - if(catsp) - { - catsp->push_back(cat); - } - else - { - LL_WARNS(LOG_INV) << "Lost and found Not there!!" << LL_ENDL; - } - } - - const bool COF_exists = (findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT) != LLUUID::null); - sFirstTimeInViewer2 = !COF_exists || gAgent.isFirstLogin(); - - - // Now the items. We allocated in the last step, so now all we - // have to do is iterate over the items and put them in the right - // place. - item_array_t items; - if(!mItemMap.empty()) - { - LLPointer item; - for(item_map_t::iterator iit = mItemMap.begin(); iit != mItemMap.end(); ++iit) - { - item = (*iit).second; - items.push_back(item); - } - } - count = items.size(); - lost = 0; - uuid_vec_t lost_item_ids; - for(i = 0; i < count; ++i) - { - LLPointer item; - item = items.at(i); - itemsp = getUnlockedItemArray(item->getParentUUID()); - if(itemsp) - { - itemsp->push_back(item); - } - else - { - LL_INFOS(LOG_INV) << "Lost item: " << item->getUUID() << " - " - << item->getName() << LL_ENDL; - ++lost; - // plop it into the lost & found. - // - item->setParent(findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND)); - // move it later using a special message to move items. If - // we update server here, the client might crash. - //item->updateServer(); - lost_item_ids.push_back(item->getUUID()); - itemsp = getUnlockedItemArray(item->getParentUUID()); - if(itemsp) - { - itemsp->push_back(item); - } - else - { - LL_WARNS(LOG_INV) << "Lost and found Not there!!" << LL_ENDL; - } - } - } - if(lost) - { - LL_WARNS(LOG_INV) << "Found " << lost << " lost items." << LL_ENDL; - LLMessageSystem* msg = gMessageSystem; - bool start_new_message = true; - const LLUUID lnf = findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); - for(uuid_vec_t::iterator it = lost_item_ids.begin() ; it < lost_item_ids.end(); ++it) - { - if(start_new_message) - { - start_new_message = false; - msg->newMessageFast(_PREHASH_MoveInventoryItem); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addBOOLFast(_PREHASH_Stamp, false); - } - msg->nextBlockFast(_PREHASH_InventoryData); - msg->addUUIDFast(_PREHASH_ItemID, (*it)); - msg->addUUIDFast(_PREHASH_FolderID, lnf); - msg->addString("NewName", NULL); - if(msg->isSendFull(NULL)) - { - start_new_message = true; - gAgent.sendReliableMessage(); - } - } - if(!start_new_message) - { - gAgent.sendReliableMessage(); - } - } - - const LLUUID &agent_inv_root_id = gInventory.getRootFolderID(); - if (agent_inv_root_id.notNull()) - { - cat_array_t* catsp = get_ptr_in_map(mParentChildCategoryTree, agent_inv_root_id); - if(catsp) - { - // *HACK - fix root inventory folder - // some accounts has pbroken inventory root folders - - std::string name = "My Inventory"; - for (parent_cat_map_t::const_iterator it = mParentChildCategoryTree.begin(), - it_end = mParentChildCategoryTree.end(); it != it_end; ++it) - { - cat_array_t* cat_array = it->second; - for (cat_array_t::const_iterator cat_it = cat_array->begin(), - cat_it_end = cat_array->end(); cat_it != cat_it_end; ++cat_it) - { - LLPointer category = *cat_it; - - if(category && category->getPreferredType() != LLFolderType::FT_ROOT_INVENTORY) - continue; - if ( category && 0 == LLStringUtil::compareInsensitive(name, category->getName()) ) - { - if(category->getUUID()!=mRootFolderID) - { - LLUUID& new_inv_root_folder_id = const_cast(mRootFolderID); - new_inv_root_folder_id = category->getUUID(); - } - } - } - } - - LLPointer validation_info = validate(); - if (validation_info->mFatalErrorCount > 0) - { - // Fatal inventory error. Will not be able to engage in many inventory operations. - // This should be followed by an error dialog leading to logout. - LL_WARNS("Inventory") << "Fatal errors were found in validate(): unable to initialize inventory! " - << "Will not be able to do normal inventory operations in this session." - << LL_ENDL; - mIsAgentInvUsable = false; - } - else - { - mIsAgentInvUsable = true; - } - validation_info->mInitialized = true; - mValidationInfo = validation_info; - - // notifyObservers() has been moved to - // llstartup/idle_startup() after this func completes. - // Allows some system categories to be created before - // observers start firing. - } - } -} - -// Would normally do this at construction but that's too early -// in the process for gInventory. Have the first requestPost() -// call set things up. -void LLInventoryModel::initHttpRequest() -{ - if (! mHttpRequestFG) - { - // Haven't initialized, get to it - LLAppCoreHttp & app_core_http(LLAppViewer::instance()->getAppCoreHttp()); - - mHttpRequestFG = new LLCore::HttpRequest; - mHttpRequestBG = new LLCore::HttpRequest; - mHttpOptions = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); - mHttpOptions->setTransferTimeout(300); - mHttpOptions->setUseRetryAfter(true); - // mHttpOptions->setTrace(2); // Do tracing of requests - mHttpHeaders = LLCore::HttpHeaders::ptr_t(new LLCore::HttpHeaders); - mHttpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_LLSD_XML); - mHttpHeaders->append(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_LLSD_XML); - mHttpPolicyClass = app_core_http.getPolicy(LLAppCoreHttp::AP_INVENTORY); - } - - if (!gGenericDispatcher.isHandlerPresent("BulkUpdateInventory")) - { - gGenericDispatcher.addHandler("BulkUpdateInventory", &sBulkUpdateInventory); - } -} - -void LLInventoryModel::handleResponses(bool foreground) -{ - if (foreground && mHttpRequestFG) - { - mHttpRequestFG->update(0); - } - else if (! foreground && mHttpRequestBG) - { - mHttpRequestBG->update(50000L); - } -} - -LLCore::HttpHandle LLInventoryModel::requestPost(bool foreground, - const std::string & url, - const LLSD & body, - const LLCore::HttpHandler::ptr_t &handler, - const char * const message) -{ - if (! mHttpRequestFG) - { - // We do the initialization late and lazily as this class is - // statically-constructed and not all the bits are ready at - // that time. - initHttpRequest(); - } - - LLCore::HttpRequest * request(foreground ? mHttpRequestFG : mHttpRequestBG); - LLCore::HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); - - handle = LLCoreHttpUtil::requestPostWithLLSD(request, - mHttpPolicyClass, - url, - body, - mHttpOptions, - mHttpHeaders, - handler); - if (LLCORE_HTTP_HANDLE_INVALID == handle) - { - LLCore::HttpStatus status(request->getStatus()); - LL_WARNS(LOG_INV) << "HTTP POST request failed for " << message - << ", Status: " << status.toTerseString() - << " Reason: '" << status.toString() << "'" - << LL_ENDL; - } - return handle; -} - -void LLInventoryModel::createCommonSystemCategories() -{ - //amount of System Folder we should wait for - sPendingSystemFolders = 9; - - gInventory.ensureCategoryForTypeExists(LLFolderType::FT_TRASH); - gInventory.ensureCategoryForTypeExists(LLFolderType::FT_FAVORITE); - gInventory.ensureCategoryForTypeExists(LLFolderType::FT_CALLINGCARD); - gInventory.ensureCategoryForTypeExists(LLFolderType::FT_MY_OUTFITS); - gInventory.ensureCategoryForTypeExists(LLFolderType::FT_CURRENT_OUTFIT); - gInventory.ensureCategoryForTypeExists(LLFolderType::FT_LANDMARK); // folder should exist before user tries to 'landmark this' - gInventory.ensureCategoryForTypeExists(LLFolderType::FT_SETTINGS); - gInventory.ensureCategoryForTypeExists(LLFolderType::FT_MATERIAL); // probably should be server created - gInventory.ensureCategoryForTypeExists(LLFolderType::FT_INBOX); -} - -struct LLUUIDAndName -{ - LLUUIDAndName() {} - LLUUIDAndName(const LLUUID& id, const std::string& name); - bool operator==(const LLUUIDAndName& rhs) const; - bool operator<(const LLUUIDAndName& rhs) const; - bool operator>(const LLUUIDAndName& rhs) const; - - LLUUID mID; - std::string mName; -}; - -LLUUIDAndName::LLUUIDAndName(const LLUUID& id, const std::string& name) : - mID(id), mName(name) -{ -} - -bool LLUUIDAndName::operator==(const LLUUIDAndName& rhs) const -{ - return ((mID == rhs.mID) && (mName == rhs.mName)); -} - -bool LLUUIDAndName::operator<(const LLUUIDAndName& rhs) const -{ - return (mID < rhs.mID); -} - -bool LLUUIDAndName::operator>(const LLUUIDAndName& rhs) const -{ - return (mID > rhs.mID); -} - -// static -bool LLInventoryModel::loadFromFile(const std::string& filename, - LLInventoryModel::cat_array_t& categories, - LLInventoryModel::item_array_t& items, - LLInventoryModel::changed_items_t& cats_to_update, - bool &is_cache_obsolete) -{ - LL_PROFILE_ZONE_NAMED("inventory load from file"); - - if(filename.empty()) - { - LL_ERRS(LOG_INV) << "filename is Null!" << LL_ENDL; - return false; - } - LL_INFOS(LOG_INV) << "loading inventory from: (" << filename << ")" << LL_ENDL; - - llifstream file(filename.c_str()); - - if (!file.is_open()) - { - LL_INFOS(LOG_INV) << "unable to load inventory from: " << filename << LL_ENDL; - return false; - } - - is_cache_obsolete = true; // Obsolete until proven current - - //U64 lines_count = 0U; - std::string line; - LLPointer parser = new LLSDNotationParser(); - while (std::getline(file, line)) - { - LLSD s_item; - std::istringstream iss(line); - if (parser->parse(iss, s_item, line.length()) == LLSDParser::PARSE_FAILURE) - { - LL_WARNS(LOG_INV)<< "Parsing inventory cache failed" << LL_ENDL; - break; - } - - if (s_item.has("inv_cache_version")) - { - S32 version = s_item["inv_cache_version"].asInteger(); - if (version == sCurrentInvCacheVersion) - { - // Cache is up to date - is_cache_obsolete = false; - continue; - } - else - { - LL_WARNS(LOG_INV)<< "Inventory cache is out of date" << LL_ENDL; - break; - } - } - else if (s_item.has("cat_id")) - { - if (is_cache_obsolete) - break; - - LLPointer inv_cat = new LLViewerInventoryCategory(LLUUID::null); - if(inv_cat->importLLSD(s_item)) - { - categories.push_back(inv_cat); - } - } - else if (s_item.has("item_id")) - { - if (is_cache_obsolete) - break; - - LLPointer inv_item = new LLViewerInventoryItem; - if( inv_item->fromLLSD(s_item) ) - { - if(inv_item->getUUID().isNull()) - { - LL_DEBUGS(LOG_INV) << "Ignoring inventory with null item id: " - << inv_item->getName() << LL_ENDL; - } - else - { - if (inv_item->getType() == LLAssetType::AT_UNKNOWN) - { - cats_to_update.insert(inv_item->getParentUUID()); - } - else - { - items.push_back(inv_item); - } - } - } - } - -// TODO(brad) - figure out how to reenable this without breaking everything else -// static constexpr U64 BATCH_SIZE = 512U; -// if ((++lines_count % BATCH_SIZE) == 0) -// { -// // SL-19968 - make sure message system code gets a chance to run every so often -// pump_idle_startup_network(); -// } - } - - file.close(); - - return !is_cache_obsolete; -} - -// static -bool LLInventoryModel::saveToFile(const std::string& filename, - const cat_array_t& categories, - const item_array_t& items) -{ - if (filename.empty()) - { - LL_ERRS(LOG_INV) << "Filename is Null!" << LL_ENDL; - return false; - } - - LL_INFOS(LOG_INV) << "saving inventory to: (" << filename << ")" << LL_ENDL; - - try - { - llofstream fileXML(filename.c_str()); - if (!fileXML.is_open()) - { - LL_WARNS(LOG_INV) << "Failed to open file. Unable to save inventory to: " << filename << LL_ENDL; - return false; - } - - LLSD cache_ver; - cache_ver["inv_cache_version"] = sCurrentInvCacheVersion; - - if (fileXML.fail()) - { - LL_WARNS(LOG_INV) << "Failed to write cache version to file. Unable to save inventory to: " << filename << LL_ENDL; - return false; - } - - fileXML << LLSDOStreamer(cache_ver) << std::endl; - - S32 count = categories.size(); - S32 cat_count = 0; - S32 i; - for (i = 0; i < count; ++i) - { - LLViewerInventoryCategory* cat = categories[i]; - if (cat->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN) - { - fileXML << LLSDOStreamer(cat->exportLLSD()) << std::endl; - cat_count++; - } - - if (fileXML.fail()) - { - LL_WARNS(LOG_INV) << "Failed to write a folder to file. Unable to save inventory to: " << filename << LL_ENDL; - return false; - } - } - - S32 it_count = items.size(); - for (i = 0; i < it_count; ++i) - { - fileXML << LLSDOStreamer(items[i]->asLLSD()) << std::endl; - - if (fileXML.fail()) - { - LL_WARNS(LOG_INV) << "Failed to write an item to file. Unable to save inventory to: " << filename << LL_ENDL; - return false; - } - } - fileXML.flush(); - - fileXML.close(); - - LL_INFOS(LOG_INV) << "Inventory saved: " << cat_count << " categories, " << it_count << " items." << LL_ENDL; - } - catch (...) - { - LOG_UNHANDLED_EXCEPTION(""); - LL_INFOS(LOG_INV) << "Failed to save inventory to: (" << filename << ")" << LL_ENDL; - return false; - } - - return true; -} - -// message handling functionality -// static -void LLInventoryModel::registerCallbacks(LLMessageSystem* msg) -{ - //msg->setHandlerFuncFast(_PREHASH_InventoryUpdate, - // processInventoryUpdate, - // NULL); - //msg->setHandlerFuncFast(_PREHASH_UseCachedInventory, - // processUseCachedInventory, - // NULL); - msg->setHandlerFuncFast(_PREHASH_UpdateCreateInventoryItem, - processUpdateCreateInventoryItem, - NULL); - msg->setHandlerFuncFast(_PREHASH_RemoveInventoryItem, - processRemoveInventoryItem, - NULL); - msg->setHandlerFuncFast(_PREHASH_RemoveInventoryFolder, - processRemoveInventoryFolder, - NULL); - msg->setHandlerFuncFast(_PREHASH_RemoveInventoryObjects, - processRemoveInventoryObjects, - NULL); - msg->setHandlerFuncFast(_PREHASH_SaveAssetIntoInventory, - processSaveAssetIntoInventory, - NULL); - msg->setHandlerFuncFast(_PREHASH_BulkUpdateInventory, - processBulkUpdateInventory, - NULL); - msg->setHandlerFunc("MoveInventoryItem", processMoveInventoryItem); -} - - -// static -void LLInventoryModel::processUpdateCreateInventoryItem(LLMessageSystem* msg, void**) -{ - // do accounting and highlight new items if they arrive - if (gInventory.messageUpdateCore(msg, true, LLInventoryObserver::UPDATE_CREATE)) - { - U32 callback_id; - LLUUID item_id; - msg->getUUIDFast(_PREHASH_InventoryData, _PREHASH_ItemID, item_id); - msg->getU32Fast(_PREHASH_InventoryData, _PREHASH_CallbackID, callback_id); - - gInventoryCallbacks.fire(callback_id, item_id); - - // Message system at the moment doesn't support Thumbnails and potential - // newer features so just rerequest whole item - // - // todo: instead of unpacking message fully, - // grab only an item_id, then fetch - LLInventoryModelBackgroundFetch::instance().scheduleItemFetch(item_id, true); - } - -} - -bool LLInventoryModel::messageUpdateCore(LLMessageSystem* msg, bool account, U32 mask) -{ - //make sure our added inventory observer is active - start_new_inventory_observer(); - - LLUUID agent_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); - if(agent_id != gAgent.getID()) - { - LL_WARNS(LOG_INV) << "Got a inventory update for the wrong agent: " << agent_id - << LL_ENDL; - return false; - } - item_array_t items; - update_map_t update; - S32 count = msg->getNumberOfBlocksFast(_PREHASH_InventoryData); - // Does this loop ever execute more than once? - for(S32 i = 0; i < count; ++i) - { - LLPointer titem = new LLViewerInventoryItem; - titem->unpackMessage(msg, _PREHASH_InventoryData, i); - LL_DEBUGS(LOG_INV) << "LLInventoryModel::messageUpdateCore() item id: " - << titem->getUUID() << LL_ENDL; - items.push_back(titem); - // examine update for changes. - LLViewerInventoryItem* itemp = gInventory.getItem(titem->getUUID()); - if(itemp) - { - if(titem->getParentUUID() == itemp->getParentUUID()) - { - update[titem->getParentUUID()]; - } - else - { - ++update[titem->getParentUUID()]; - --update[itemp->getParentUUID()]; - } - } - else - { - ++update[titem->getParentUUID()]; - } - } - if(account) - { - gInventory.accountForUpdate(update); - } - - if (account) - { - mask |= LLInventoryObserver::CREATE; - } - //as above, this loop never seems to loop more than once per call - for (item_array_t::iterator it = items.begin(); it != items.end(); ++it) - { - gInventory.updateItem(*it, mask); - } - gInventory.notifyObservers(); - gViewerWindow->getWindow()->decBusyCount(); - - return true; -} - -// static -void LLInventoryModel::removeInventoryItem(LLUUID agent_id, LLMessageSystem* msg, const char* msg_label) -{ - LLUUID item_id; - S32 count = msg->getNumberOfBlocksFast(msg_label); - LL_DEBUGS(LOG_INV) << "Message has " << count << " item blocks" << LL_ENDL; - uuid_vec_t item_ids; - update_map_t update; - for(S32 i = 0; i < count; ++i) - { - msg->getUUIDFast(msg_label, _PREHASH_ItemID, item_id, i); - LL_DEBUGS(LOG_INV) << "Checking for item-to-be-removed " << item_id << LL_ENDL; - LLViewerInventoryItem* itemp = gInventory.getItem(item_id); - if(itemp) - { - LL_DEBUGS(LOG_INV) << "Item will be removed " << item_id << LL_ENDL; - // we only bother with the delete and account if we found - // the item - this is usually a back-up for permissions, - // so frequently the item will already be gone. - --update[itemp->getParentUUID()]; - item_ids.push_back(item_id); - } - } - gInventory.accountForUpdate(update); - for(uuid_vec_t::iterator it = item_ids.begin(); it != item_ids.end(); ++it) - { - LL_DEBUGS(LOG_INV) << "Calling deleteObject " << *it << LL_ENDL; - gInventory.deleteObject(*it); - } -} - -// static -void LLInventoryModel::processRemoveInventoryItem(LLMessageSystem* msg, void**) -{ - LL_DEBUGS(LOG_INV) << "LLInventoryModel::processRemoveInventoryItem()" << LL_ENDL; - LLUUID agent_id, item_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); - if(agent_id != gAgent.getID()) - { - LL_WARNS(LOG_INV) << "Got a RemoveInventoryItem for the wrong agent." - << LL_ENDL; - return; - } - LLInventoryModel::removeInventoryItem(agent_id, msg, _PREHASH_InventoryData); - gInventory.notifyObservers(); -} - -// static -void LLInventoryModel::removeInventoryFolder(LLUUID agent_id, - LLMessageSystem* msg) -{ - LLUUID folder_id; - uuid_vec_t folder_ids; - update_map_t update; - S32 count = msg->getNumberOfBlocksFast(_PREHASH_FolderData); - for(S32 i = 0; i < count; ++i) - { - msg->getUUIDFast(_PREHASH_FolderData, _PREHASH_FolderID, folder_id, i); - LLViewerInventoryCategory* folderp = gInventory.getCategory(folder_id); - if(folderp) - { - --update[folderp->getParentUUID()]; - folder_ids.push_back(folder_id); - } - } - gInventory.accountForUpdate(update); - for(uuid_vec_t::iterator it = folder_ids.begin(); it != folder_ids.end(); ++it) - { - gInventory.deleteObject(*it); - } -} - -// static -void LLInventoryModel::processRemoveInventoryFolder(LLMessageSystem* msg, - void**) -{ - LL_DEBUGS() << "LLInventoryModel::processRemoveInventoryFolder()" << LL_ENDL; - LLUUID agent_id, session_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_SessionID, session_id); - if(agent_id != gAgent.getID()) - { - LL_WARNS() << "Got a RemoveInventoryFolder for the wrong agent." - << LL_ENDL; - return; - } - LLInventoryModel::removeInventoryFolder( agent_id, msg ); - gInventory.notifyObservers(); -} - -// static -void LLInventoryModel::processRemoveInventoryObjects(LLMessageSystem* msg, - void**) -{ - LL_DEBUGS() << "LLInventoryModel::processRemoveInventoryObjects()" << LL_ENDL; - LLUUID agent_id, session_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_SessionID, session_id); - if(agent_id != gAgent.getID()) - { - LL_WARNS() << "Got a RemoveInventoryObjects for the wrong agent." - << LL_ENDL; - return; - } - LLInventoryModel::removeInventoryFolder( agent_id, msg ); - LLInventoryModel::removeInventoryItem( agent_id, msg, _PREHASH_ItemData ); - gInventory.notifyObservers(); -} - -// static -void LLInventoryModel::processSaveAssetIntoInventory(LLMessageSystem* msg, - void**) -{ - LLUUID agent_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); - if(agent_id != gAgent.getID()) - { - LL_WARNS() << "Got a SaveAssetIntoInventory message for the wrong agent." - << LL_ENDL; - return; - } - - LLUUID item_id; - msg->getUUIDFast(_PREHASH_InventoryData, _PREHASH_ItemID, item_id); - - // The viewer ignores the asset id because this message is only - // used for attachments/objects, so the asset id is not used in - // the viewer anyway. - LL_DEBUGS() << "LLInventoryModel::processSaveAssetIntoInventory itemID=" - << item_id << LL_ENDL; - LLViewerInventoryItem* item = gInventory.getItem( item_id ); - if( item ) - { - LLCategoryUpdate up(item->getParentUUID(), 0); - gInventory.accountForUpdate(up); - gInventory.addChangedMask( LLInventoryObserver::INTERNAL, item_id); - gInventory.notifyObservers(); - } - else - { - LL_INFOS() << "LLInventoryModel::processSaveAssetIntoInventory item" - " not found: " << item_id << LL_ENDL; - } - if(gViewerWindow) - { - gViewerWindow->getWindow()->decBusyCount(); - } -} - -// static -void LLInventoryModel::processBulkUpdateInventory(LLMessageSystem* msg, void**) -{ - LLUUID agent_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); - if(agent_id != gAgent.getID()) - { - LL_WARNS() << "Got a BulkUpdateInventory for the wrong agent." << LL_ENDL; - return; - } - LLUUID tid; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_TransactionID, tid); -#ifndef LL_RELEASE_FOR_DOWNLOAD - LL_DEBUGS("Inventory") << "Bulk inventory: " << tid << LL_ENDL; -#endif - - update_map_t update; - cat_array_t folders; - S32 count; - S32 i; - count = msg->getNumberOfBlocksFast(_PREHASH_FolderData); - for(i = 0; i < count; ++i) - { - LLPointer tfolder = new LLViewerInventoryCategory(gAgent.getID()); - tfolder->unpackMessage(msg, _PREHASH_FolderData, i); - LL_DEBUGS("Inventory") << "unpacked folder '" << tfolder->getName() << "' (" - << tfolder->getUUID() << ") in " << tfolder->getParentUUID() - << LL_ENDL; - - // If the folder is a listing or a version folder, all we need to do is update the SLM data - int depth_folder = depth_nesting_in_marketplace(tfolder->getUUID()); - if ((depth_folder == 1) || (depth_folder == 2)) - { - // Trigger an SLM listing update - LLUUID listing_uuid = (depth_folder == 1 ? tfolder->getUUID() : tfolder->getParentUUID()); - S32 listing_id = LLMarketplaceData::instance().getListingID(listing_uuid); - LLMarketplaceData::instance().getListing(listing_id); - // In that case, there is no item to update so no callback -> we skip the rest of the update - } - else if(tfolder->getUUID().notNull()) - { - folders.push_back(tfolder); - LLViewerInventoryCategory* folderp = gInventory.getCategory(tfolder->getUUID()); - if(folderp) - { - if (folderp->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN) - { - if (tfolder->getParentUUID() == folderp->getParentUUID()) - { - update[tfolder->getParentUUID()]; - } - else - { - ++update[tfolder->getParentUUID()]; - --update[folderp->getParentUUID()]; - } - } - else - { - folderp->fetch(); - } - } - else - { - // we could not find the folder, so it is probably - // new. However, we only want to attempt accounting - // for the parent if we can find the parent. - folderp = gInventory.getCategory(tfolder->getParentUUID()); - if(folderp) - { - if (folderp->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN) - { - ++update[tfolder->getParentUUID()]; - } - else - { - folderp->fetch(); - } - } - } - } - } - - - count = msg->getNumberOfBlocksFast(_PREHASH_ItemData); - uuid_vec_t wearable_ids; - item_array_t items; - std::list cblist; - for(i = 0; i < count; ++i) - { - LLPointer titem = new LLViewerInventoryItem; - titem->unpackMessage(msg, _PREHASH_ItemData, i); - LL_DEBUGS("Inventory") << "unpacked item '" << titem->getName() << "' in " - << titem->getParentUUID() << LL_ENDL; - U32 callback_id; - msg->getU32Fast(_PREHASH_ItemData, _PREHASH_CallbackID, callback_id); - if(titem->getUUID().notNull() ) // && callback_id.notNull() ) - { - items.push_back(titem); - cblist.push_back(InventoryCallbackInfo(callback_id, titem->getUUID())); - if (titem->getInventoryType() == LLInventoryType::IT_WEARABLE) - { - wearable_ids.push_back(titem->getUUID()); - } - // examine update for changes. - LLViewerInventoryItem* itemp = gInventory.getItem(titem->getUUID()); - if(itemp) - { - if(titem->getParentUUID() == itemp->getParentUUID()) - { - update[titem->getParentUUID()]; - } - else - { - ++update[titem->getParentUUID()]; - --update[itemp->getParentUUID()]; - } - } - else - { - LLViewerInventoryCategory* folderp = gInventory.getCategory(titem->getParentUUID()); - if(folderp) - { - if (folderp->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN) - { - ++update[titem->getParentUUID()]; - } - else - { - folderp->fetch(); - } - } - } - } - else - { - cblist.push_back(InventoryCallbackInfo(callback_id, LLUUID::null)); - } - } - gInventory.accountForUpdate(update); - - for (cat_array_t::iterator cit = folders.begin(); cit != folders.end(); ++cit) - { - gInventory.updateCategory(*cit); - if ((*cit)->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN) - { - // Temporary workaround: just fetch the item using AIS to get missing fields. - // If this works fine we might want to extract 'ids only' from the message - // then use AIS as a primary fetcher - LLInventoryModelBackgroundFetch::instance().scheduleFolderFetch((*cit)->getUUID(), true /*force, since it has changes*/); - } - // else already called fetch() above - } - for (item_array_t::iterator iit = items.begin(); iit != items.end(); ++iit) - { - gInventory.updateItem(*iit); - - // Temporary workaround: just fetch the item using AIS to get missing fields. - // If this works fine we might want to extract 'ids only' from the message - // then use AIS as a primary fetcher - LLInventoryModelBackgroundFetch::instance().scheduleItemFetch((*iit)->getUUID(), true); - } - gInventory.notifyObservers(); - - // The incoming inventory could span more than one BulkInventoryUpdate packet, - // so record the transaction ID for this purchase, then wear all clothing - // that comes in as part of that transaction ID. JC - if (LLInventoryState::sWearNewClothing) - { - LLInventoryState::sWearNewClothingTransactionID = tid; - LLInventoryState::sWearNewClothing = false; - } - - if (tid.notNull() && tid == LLInventoryState::sWearNewClothingTransactionID) - { - count = wearable_ids.size(); - for (i = 0; i < count; ++i) - { - LLViewerInventoryItem* wearable_item; - wearable_item = gInventory.getItem(wearable_ids[i]); - LLAppearanceMgr::instance().wearItemOnAvatar(wearable_item->getUUID(), true, true); - } - } - - std::list::iterator inv_it; - for (inv_it = cblist.begin(); inv_it != cblist.end(); ++inv_it) - { - InventoryCallbackInfo cbinfo = (*inv_it); - gInventoryCallbacks.fire(cbinfo.mCallback, cbinfo.mInvID); - } -} - -// static -void LLInventoryModel::processMoveInventoryItem(LLMessageSystem* msg, void**) -{ - LL_DEBUGS() << "LLInventoryModel::processMoveInventoryItem()" << LL_ENDL; - LLUUID agent_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); - if(agent_id != gAgent.getID()) - { - LL_WARNS() << "Got a MoveInventoryItem message for the wrong agent." - << LL_ENDL; - return; - } - - LLUUID item_id; - LLUUID folder_id; - std::string new_name; - bool anything_changed = false; - S32 count = msg->getNumberOfBlocksFast(_PREHASH_InventoryData); - for(S32 i = 0; i < count; ++i) - { - msg->getUUIDFast(_PREHASH_InventoryData, _PREHASH_ItemID, item_id, i); - LLViewerInventoryItem* item = gInventory.getItem(item_id); - if(item) - { - LLPointer new_item = new LLViewerInventoryItem(item); - msg->getUUIDFast(_PREHASH_InventoryData, _PREHASH_FolderID, folder_id, i); - msg->getString("InventoryData", "NewName", new_name, i); - - LL_DEBUGS() << "moving item " << item_id << " to folder " - << folder_id << LL_ENDL; - update_list_t update; - LLCategoryUpdate old_folder(item->getParentUUID(), -1); - update.push_back(old_folder); - LLCategoryUpdate new_folder(folder_id, 1); - update.push_back(new_folder); - gInventory.accountForUpdate(update); - - new_item->setParent(folder_id); - if (new_name.length() > 0) - { - new_item->rename(new_name); - } - gInventory.updateItem(new_item); - anything_changed = true; - } - else - { - LL_INFOS() << "LLInventoryModel::processMoveInventoryItem item not found: " << item_id << LL_ENDL; - } - } - if(anything_changed) - { - gInventory.notifyObservers(); - } -} - -//---------------------------------------------------------------------------- -// Trash: LLFolderType::FT_TRASH, "ConfirmEmptyTrash" -// Lost&Found: LLFolderType::FT_LOST_AND_FOUND, "ConfirmEmptyLostAndFound" - -bool LLInventoryModel::callbackEmptyFolderType(const LLSD& notification, const LLSD& response, LLFolderType::EType preferred_type) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) // YES - { - const LLUUID folder_id = findCategoryUUIDForType(preferred_type); - purge_descendents_of(folder_id, NULL); - } - return false; -} - -void LLInventoryModel::emptyFolderType(const std::string notification, LLFolderType::EType preferred_type) -{ - if (!notification.empty()) - { - LLSD args; - if(LLFolderType::FT_TRASH == preferred_type) - { - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - const LLUUID trash_id = findCategoryUUIDForType(preferred_type); - gInventory.collectDescendents(trash_id, cats, items, LLInventoryModel::INCLUDE_TRASH); //All descendants - S32 item_count = items.size() + cats.size(); - args["COUNT"] = item_count; - } - LLNotificationsUtil::add(notification, args, LLSD(), - boost::bind(&LLInventoryModel::callbackEmptyFolderType, this, _1, _2, preferred_type)); - } - else - { - const LLUUID folder_id = findCategoryUUIDForType(preferred_type); - purge_descendents_of(folder_id, NULL); - } -} - -//---------------------------------------------------------------------------- - -void LLInventoryModel::removeItem(const LLUUID& item_id) -{ - LLViewerInventoryItem* item = getItem(item_id); - if (! item) - { - LL_WARNS("Inventory") << "couldn't find inventory item " << item_id << LL_ENDL; - } - else - { - const LLUUID new_parent = findCategoryUUIDForType(LLFolderType::FT_TRASH); - if (new_parent.notNull()) - { - LL_INFOS("Inventory") << "Moving to Trash (" << new_parent << "):" << LL_ENDL; - changeItemParent(item, new_parent, true); - } - } -} - -void LLInventoryModel::removeCategory(const LLUUID& category_id) -{ - if (! get_is_category_removable(this, category_id)) - { - return; - } - - // Look for any gestures and deactivate them - LLInventoryModel::cat_array_t descendent_categories; - LLInventoryModel::item_array_t descendent_items; - collectDescendents(category_id, descendent_categories, descendent_items, false); - - for (LLInventoryModel::item_array_t::const_iterator iter = descendent_items.begin(); - iter != descendent_items.end(); - ++iter) - { - const LLViewerInventoryItem* item = (*iter); - const LLUUID& item_id = item->getUUID(); - if (item->getType() == LLAssetType::AT_GESTURE - && LLGestureMgr::instance().isGestureActive(item_id)) - { - LLGestureMgr::instance().deactivateGesture(item_id); - } - } - - LLViewerInventoryCategory* cat = getCategory(category_id); - if (cat) - { - const LLUUID trash_id = findCategoryUUIDForType(LLFolderType::FT_TRASH); - if (trash_id.notNull()) - { - changeCategoryParent(cat, trash_id, true); - } - } - - checkTrashOverflow(); -} - -void LLInventoryModel::removeObject(const LLUUID& object_id) -{ - if(object_id.isNull()) - { - return; - } - - LLInventoryObject* obj = getObject(object_id); - if (dynamic_cast(obj)) - { - removeItem(object_id); - } - else if (dynamic_cast(obj)) - { - removeCategory(object_id); - } - else if (obj) - { - LL_WARNS("Inventory") << "object ID " << object_id - << " is an object of unrecognized class " - << typeid(*obj).name() << LL_ENDL; - } - else - { - LL_WARNS("Inventory") << "object ID " << object_id << " not found" << LL_ENDL; - } -} - -bool callback_preview_trash_folder(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) // YES - { - LLFloaterPreviewTrash::show(); - } - return false; -} - -void LLInventoryModel::checkTrashOverflow() -{ - static LLCachedControl trash_max_capacity(gSavedSettings, "InventoryTrashMaxCapacity"); - - // Collect all descendants including those in subfolders. - // - // Note: Do we really need content of subfolders? - // This was made to prevent download of trash folder timeouting - // viewer and sub-folders are supposed to download independently. - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - const LLUUID trash_id = findCategoryUUIDForType(LLFolderType::FT_TRASH); - gInventory.collectDescendents(trash_id, cats, items, LLInventoryModel::INCLUDE_TRASH); - S32 item_count = items.size() + cats.size(); - - if (item_count >= trash_max_capacity) - { - if (LLFloaterPreviewTrash::isVisible()) - { - // bring to front - LLFloaterPreviewTrash::show(); - } - else - { - LLNotificationsUtil::add("TrashIsFull", LLSD(), LLSD(), - boost::bind(callback_preview_trash_folder, _1, _2)); - } - } -} - -const LLUUID &LLInventoryModel::getRootFolderID() const -{ - return mRootFolderID; -} - -void LLInventoryModel::setRootFolderID(const LLUUID& val) -{ - mRootFolderID = val; -} - -const LLUUID &LLInventoryModel::getLibraryRootFolderID() const -{ - return mLibraryRootFolderID; -} - -void LLInventoryModel::setLibraryRootFolderID(const LLUUID& val) -{ - mLibraryRootFolderID = val; -} - -const LLUUID &LLInventoryModel::getLibraryOwnerID() const -{ - return mLibraryOwnerID; -} - -void LLInventoryModel::setLibraryOwnerID(const LLUUID& val) -{ - mLibraryOwnerID = val; -} - -// static -bool LLInventoryModel::getIsFirstTimeInViewer2() -{ - // Do not call this before parentchild map is built. - if (!gInventory.mIsAgentInvUsable) - { - LL_WARNS() << "Parent Child Map not yet built; guessing as first time in viewer2." << LL_ENDL; - return true; - } - - return sFirstTimeInViewer2; -} - -LLInventoryModel::item_array_t::iterator LLInventoryModel::findItemIterByUUID(LLInventoryModel::item_array_t& items, const LLUUID& id) -{ - LLInventoryModel::item_array_t::iterator curr_item = items.begin(); - - while (curr_item != items.end()) - { - if ((*curr_item)->getUUID() == id) - { - break; - } - ++curr_item; - } - - return curr_item; -} - -// static -// * @param[in, out] items - vector with items to be updated. It should be sorted in a right way -// * before calling this method. -// * @param src_item_id - LLUUID of inventory item to be moved in new position -// * @param dest_item_id - LLUUID of inventory item before (or after) which source item should -// * be placed. -// * @param insert_before - bool indicating if src_item_id should be placed before or after -// * dest_item_id. Default is true. -void LLInventoryModel::updateItemsOrder(LLInventoryModel::item_array_t& items, const LLUUID& src_item_id, const LLUUID& dest_item_id, bool insert_before) -{ - LLInventoryModel::item_array_t::iterator it_src = findItemIterByUUID(items, src_item_id); - LLInventoryModel::item_array_t::iterator it_dest = findItemIterByUUID(items, dest_item_id); - - // If one of the passed UUID is not in the item list, bail out - if ((it_src == items.end()) || (it_dest == items.end())) - return; - - // Erase the source element from the list, keep a copy before erasing. - LLViewerInventoryItem* src_item = *it_src; - items.erase(it_src); - - // Note: Target iterator is not valid anymore because the container was changed, so update it. - it_dest = findItemIterByUUID(items, dest_item_id); - - // Go to the next element if one wishes to insert after the dest element - if (!insert_before) - { - ++it_dest; - } - - // Reinsert the source item in the right place - if (it_dest != items.end()) - { - items.insert(it_dest, src_item); - } - else - { - // Append to the list if it_dest reached the end - items.push_back(src_item); - } -} - -// See also LLInventorySort where landmarks in the Favorites folder are sorted. -class LLViewerInventoryItemSort -{ -public: - bool operator()(const LLPointer& a, const LLPointer& b) - { - return a->getSortField() < b->getSortField(); - } -}; - -//---------------------------------------------------------------------------- - -// *NOTE: DEBUG functionality -void LLInventoryModel::dumpInventory() const -{ - LL_INFOS() << "\nBegin Inventory Dump\n**********************:" << LL_ENDL; - LL_INFOS() << "mCategory[] contains " << mCategoryMap.size() << " items." << LL_ENDL; - for(cat_map_t::const_iterator cit = mCategoryMap.begin(); cit != mCategoryMap.end(); ++cit) - { - const LLViewerInventoryCategory* cat = cit->second; - if(cat) - { - LL_INFOS() << " " << cat->getUUID() << " '" << cat->getName() << "' " - << cat->getVersion() << " " << cat->getDescendentCount() - << LL_ENDL; - } - else - { - LL_INFOS() << " NULL!" << LL_ENDL; - } - } - LL_INFOS() << "mItemMap[] contains " << mItemMap.size() << " items." << LL_ENDL; - for(item_map_t::const_iterator iit = mItemMap.begin(); iit != mItemMap.end(); ++iit) - { - const LLViewerInventoryItem* item = iit->second; - if(item) - { - LL_INFOS() << " " << item->getUUID() << " " - << item->getName() << LL_ENDL; - } - else - { - LL_INFOS() << " NULL!" << LL_ENDL; - } - } - LL_INFOS() << "\n**********************\nEnd Inventory Dump" << LL_ENDL; -} - -// Do various integrity checks on model, logging issues found and -// returning an overall good/bad flag. -LLPointer LLInventoryModel::validate() const -{ - LLPointer validation_info = new LLInventoryValidationInfo; - S32 fatal_errs = 0; - S32 warning_count= 0; - S32 loop_count = 0; - S32 orphaned_count = 0; - - if (getRootFolderID().isNull()) - { - LL_WARNS("Inventory") << "Fatal inventory corruption: no root folder id" << LL_ENDL; - validation_info->mFatalNoRootFolder = true; - fatal_errs++; - } - if (getLibraryRootFolderID().isNull()) - { - // Probably shouldn't be a fatality, inventory can function without a library - LL_WARNS("Inventory") << "Fatal inventory corruption: no library root folder id" << LL_ENDL; - validation_info->mFatalNoLibraryRootFolder = true; - fatal_errs++; - } - - if (mCategoryMap.size() + 1 != mParentChildCategoryTree.size()) - { - // ParentChild should be one larger because of the special entry for null uuid. - LL_INFOS("Inventory") << "unexpected sizes: cat map size " << mCategoryMap.size() - << " parent/child " << mParentChildCategoryTree.size() << LL_ENDL; - - validation_info->mWarnings["category_map_size"]++; - warning_count++; - } - S32 cat_lock = 0; - S32 item_lock = 0; - S32 desc_unknown_count = 0; - S32 version_unknown_count = 0; - - typedef std::map ft_count_map; - ft_count_map ft_counts_under_root; - ft_count_map ft_counts_elsewhere; - - // Loop over all categories and check. - for(cat_map_t::const_iterator cit = mCategoryMap.begin(); cit != mCategoryMap.end(); ++cit) - { - const LLUUID& cat_id = cit->first; - const LLViewerInventoryCategory *cat = cit->second; - if (!cat) - { - LL_WARNS("Inventory") << "null cat" << LL_ENDL; - validation_info->mWarnings["null_cat"]++; - warning_count++; - continue; - } - LLUUID topmost_ancestor_id; - // Will leave as null uuid on failure - EAncestorResult res = getObjectTopmostAncestor(cat_id, topmost_ancestor_id); - switch (res) - { - case ANCESTOR_MISSING: - orphaned_count++; - break; - case ANCESTOR_LOOP: - loop_count++; - break; - case ANCESTOR_OK: - break; - default: - LL_WARNS("Inventory") << "Unknown ancestor error for " << cat_id << LL_ENDL; - validation_info->mWarnings["unknown_ancestor_status"]++; - warning_count++; - break; - } - - if (cat_id != cat->getUUID()) - { - LL_WARNS("Inventory") << "cat id/index mismatch " << cat_id << " " << cat->getUUID() << LL_ENDL; - validation_info->mWarnings["cat_id_index_mismatch"]++; - warning_count++; - } - - if (cat->getParentUUID().isNull()) - { - if (cat_id != getRootFolderID() && cat_id != getLibraryRootFolderID()) - { - LL_WARNS("Inventory") << "cat " << cat_id << " has no parent, but is not root (" - << getRootFolderID() << ") or library root (" - << getLibraryRootFolderID() << ")" << LL_ENDL; - validation_info->mWarnings["null_parent"]++; - warning_count++; - } - } - cat_array_t* cats; - item_array_t* items; - getDirectDescendentsOf(cat_id,cats,items); - if (!cats || !items) - { - LL_WARNS("Inventory") << "invalid direct descendents for " << cat_id << LL_ENDL; - validation_info->mWarnings["direct_descendents"]++; - warning_count++; - continue; - } - if (cat->getDescendentCount() == LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN) - { - desc_unknown_count++; - } - else if (cats->size() + items->size() != cat->getDescendentCount()) - { - // In the case of library this is not unexpected, since - // different user accounts may be getting the library - // contents from different inventory hosts. - if (topmost_ancestor_id.isNull() || topmost_ancestor_id != getLibraryRootFolderID()) - { - LL_WARNS("Inventory") << "invalid desc count for " << cat_id << " [" << getFullPath(cat) << "]" - << " cached " << cat->getDescendentCount() - << " expected " << cats->size() << "+" << items->size() - << "=" << cats->size() +items->size() << LL_ENDL; - validation_info->mWarnings["invalid_descendent_count"]++; - warning_count++; - } - } - if (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) - { - version_unknown_count++; - } - auto cat_lock_it = mCategoryLock.find(cat_id); - if (cat_lock_it != mCategoryLock.end() && cat_lock_it->second) - { - cat_lock++; - } - auto item_lock_it = mItemLock.find(cat_id); - if (item_lock_it != mItemLock.end() && item_lock_it->second) - { - item_lock++; - } - for (S32 i = 0; isize(); i++) - { - LLViewerInventoryItem *item = items->at(i); - - if (!item) - { - LL_WARNS("Inventory") << "null item at index " << i << " for cat " << cat_id << LL_ENDL; - validation_info->mWarnings["null_item_at_index"]++; - warning_count++; - continue; - } - - const LLUUID& item_id = item->getUUID(); - - if (item->getParentUUID() != cat_id) - { - LL_WARNS("Inventory") << "wrong parent for " << item_id << " found " - << item->getParentUUID() << " expected " << cat_id - << LL_ENDL; - validation_info->mWarnings["wrong_parent_for_item"]++; - warning_count++; - } - - - // Entries in items and mItemMap should correspond. - item_map_t::const_iterator it = mItemMap.find(item_id); - if (it == mItemMap.end()) - { - LL_WARNS("Inventory") << "item " << item_id << " found as child of " - << cat_id << " but not in top level mItemMap" << LL_ENDL; - validation_info->mWarnings["item_not_in_top_map"]++; - warning_count++; - } - else - { - LLViewerInventoryItem *top_item = it->second; - if (top_item != item) - { - LL_WARNS("Inventory") << "item mismatch, item_id " << item_id - << " top level entry is different, uuid " << top_item->getUUID() << LL_ENDL; - } - } - - // Topmost ancestor should be root or library. - LLUUID topmost_ancestor_id; - EAncestorResult found = getObjectTopmostAncestor(item_id, topmost_ancestor_id); - if (found != ANCESTOR_OK) - { - LL_WARNS("Inventory") << "unable to find topmost ancestor for " << item_id << LL_ENDL; - validation_info->mWarnings["topmost_ancestor_not_found"]++; - warning_count++; - } - else - { - if (topmost_ancestor_id != getRootFolderID() && - topmost_ancestor_id != getLibraryRootFolderID()) - { - LL_WARNS("Inventory") << "unrecognized top level ancestor for " << item_id - << " got " << topmost_ancestor_id - << " expected " << getRootFolderID() - << " or " << getLibraryRootFolderID() << LL_ENDL; - validation_info->mWarnings["topmost_ancestor_not_recognized"]++; - warning_count++; - } - } - } - - // Does this category appear as a child of its supposed parent? - const LLUUID& parent_id = cat->getParentUUID(); - if (!parent_id.isNull()) - { - cat_array_t* cats; - item_array_t* items; - getDirectDescendentsOf(parent_id,cats,items); - if (!cats) - { - LL_WARNS("Inventory") << "cat " << cat_id << " name [" << cat->getName() - << "] orphaned - no child cat array for alleged parent " << parent_id << LL_ENDL; - orphaned_count++; - } - else - { - bool found = false; - for (S32 i = 0; isize(); i++) - { - LLViewerInventoryCategory *kid_cat = cats->at(i); - if (kid_cat == cat) - { - found = true; - break; - } - } - if (!found) - { - LL_WARNS("Inventory") << "cat " << cat_id << " name [" << cat->getName() - << "] orphaned - not found in child cat array of alleged parent " << parent_id << LL_ENDL; - orphaned_count++; - } - } - } - - // Update count of preferred types - LLFolderType::EType folder_type = cat->getPreferredType(); - bool cat_is_in_library = false; - LLUUID topmost_id; - if (getObjectTopmostAncestor(cat->getUUID(),topmost_id) == ANCESTOR_OK && topmost_id == getLibraryRootFolderID()) - { - cat_is_in_library = true; - } - if (!cat_is_in_library) - { - if (getRootFolderID().notNull() && (cat->getUUID()==getRootFolderID() || cat->getParentUUID()==getRootFolderID())) - { - ft_counts_under_root[folder_type]++; - if (folder_type != LLFolderType::FT_NONE) - { - LL_DEBUGS("Inventory") << "Under root cat: " << getFullPath(cat) << " folder_type " << folder_type << LL_ENDL; - } - } - else - { - ft_counts_elsewhere[folder_type]++; - if (folder_type != LLFolderType::FT_NONE) - { - LL_DEBUGS("Inventory") << "Elsewhere cat: " << getFullPath(cat) << " folder_type " << folder_type << LL_ENDL; - } - } - } - } - - // Loop over all items and check - for(item_map_t::const_iterator iit = mItemMap.begin(); iit != mItemMap.end(); ++iit) - { - const LLUUID& item_id = iit->first; - LLViewerInventoryItem *item = iit->second; - if (item->getUUID() != item_id) - { - LL_WARNS("Inventory") << "item_id " << item_id << " does not match " << item->getUUID() << LL_ENDL; - validation_info->mWarnings["item_id_mismatch"]++; - warning_count++; - } - - const LLUUID& parent_id = item->getParentUUID(); - if (parent_id.isNull()) - { - LL_WARNS("Inventory") << "item " << item_id << " name [" << item->getName() << "] has null parent id!" << LL_ENDL; - orphaned_count++; - } - else - { - cat_array_t* cats; - item_array_t* items; - getDirectDescendentsOf(parent_id,cats,items); - if (!items) - { - LL_WARNS("Inventory") << "item " << item_id << " name [" << item->getName() - << "] orphaned - alleged parent has no child items list " << parent_id << LL_ENDL; - orphaned_count++; - } - else - { - bool found = false; - for (S32 i=0; isize(); ++i) - { - if (items->at(i) == item) - { - found = true; - break; - } - } - if (!found) - { - LL_WARNS("Inventory") << "item " << item_id << " name [" << item->getName() - << "] orphaned - not found as child of alleged parent " << parent_id << LL_ENDL; - orphaned_count++; - } - } - - } - // Link checking - if (item->getIsLinkType()) - { - const LLUUID& link_id = item->getUUID(); - const LLUUID& target_id = item->getLinkedUUID(); - LLViewerInventoryItem *target_item = getItem(target_id); - LLViewerInventoryCategory *target_cat = getCategory(target_id); - // Linked-to UUID should have back reference to this link. - if (!hasBacklinkInfo(link_id, target_id)) - { - LL_WARNS("Inventory") << "link " << item->getUUID() << " type " << item->getActualType() - << " missing backlink info at target_id " << target_id - << LL_ENDL; - orphaned_count++; - } - // Links should have referents. - if (item->getActualType() == LLAssetType::AT_LINK && !target_item) - { - LL_WARNS("Inventory") << "broken item link " << item->getName() << " id " << item->getUUID() << LL_ENDL; - orphaned_count++; - } - else if (item->getActualType() == LLAssetType::AT_LINK_FOLDER && !target_cat) - { - LL_WARNS("Inventory") << "broken folder link " << item->getName() << " id " << item->getUUID() << LL_ENDL; - orphaned_count++; - } - if (target_item && target_item->getIsLinkType()) - { - LL_WARNS("Inventory") << "link " << item->getName() << " references a link item " - << target_item->getName() << " " << target_item->getUUID() << LL_ENDL; - } - - // Links should not have backlinks. - std::pair range = mBacklinkMMap.equal_range(link_id); - if (range.first != range.second) - { - LL_WARNS("Inventory") << "Link item " << item->getName() << " has backlinks!" << LL_ENDL; - } - } - else - { - // Check the backlinks of a non-link item. - const LLUUID& target_id = item->getUUID(); - std::pair range = mBacklinkMMap.equal_range(target_id); - for (backlink_mmap_t::const_iterator it = range.first; it != range.second; ++it) - { - const LLUUID& link_id = it->second; - LLViewerInventoryItem *link_item = getItem(link_id); - if (!link_item || !link_item->getIsLinkType()) - { - LL_WARNS("Inventory") << "invalid backlink from target " << item->getName() << " to " << link_id << LL_ENDL; - } - } - } - } - - // Check system folders - for (auto fit=ft_counts_under_root.begin(); fit != ft_counts_under_root.end(); ++fit) - { - LL_DEBUGS("Inventory") << "Folder type " << fit->first << " count " << fit->second << " under root" << LL_ENDL; - } - for (auto fit=ft_counts_elsewhere.begin(); fit != ft_counts_elsewhere.end(); ++fit) - { - LL_DEBUGS("Inventory") << "Folder type " << fit->first << " count " << fit->second << " elsewhere" << LL_ENDL; - } - - static LLCachedControl fake_system_folder_issues(gSavedSettings, "QAModeFakeSystemFolderIssues", false); - static std::default_random_engine e{}; - static std::uniform_int_distribution<> distrib(0, 1); - for (S32 ft=LLFolderType::FT_TEXTURE; ft(ft); - if (LLFolderType::lookup(folder_type)==LLFolderType::badLookup()) - { - continue; - } - bool is_automatic = LLFolderType::lookupIsAutomaticType(folder_type); - bool is_singleton = LLFolderType::lookupIsSingletonType(folder_type); - S32 count_under_root = ft_counts_under_root[folder_type]; - S32 count_elsewhere = ft_counts_elsewhere[folder_type]; - if (fake_system_folder_issues) - { - // Force all counts to be either 0 or 2, thus flagged as an error. - count_under_root = 2*distrib(e); - count_elsewhere = 2*distrib(e); - validation_info->mFatalQADebugMode = true; - } - if (is_singleton) - { - if (count_under_root==0) - { - LL_WARNS("Inventory") << "Expected system folder type " << ft << " was not found under root" << LL_ENDL; - // Need to create, if allowed. - if (is_automatic) - { - LL_WARNS("Inventory") << "Fatal inventory corruption: cannot create system folder of type " << ft << LL_ENDL; - validation_info->mMissingRequiredSystemFolders.insert(folder_type); - fatal_errs++; - } - else - { - // Can create, and will when needed. - // (Not sure this is really a warning, but worth logging) - validation_info->mWarnings["missing_system_folder_can_create"]++; - warning_count++; - } - } - else if (count_under_root > 1) - { - validation_info->mDuplicateRequiredSystemFolders.insert(folder_type); - if (!is_automatic - && folder_type != LLFolderType::FT_SETTINGS - // FT_MATERIAL might need to be automatic like the rest of upload folders - && folder_type != LLFolderType::FT_MATERIAL - ) - { - // It is a fatal problem or can lead to fatal problems for COF, - // outfits, trash and other non-automatic folders. - validation_info->mFatalSystemDuplicate++; - fatal_errs++; - LL_WARNS("Inventory") << "Fatal inventory corruption: system folder type has excess copies under root, type " << ft << " count " << count_under_root << LL_ENDL; - } - else - { - // For automatic folders it's not a fatal issue and shouldn't - // break inventory or other functionality further - // Exception: FT_SETTINGS is not automatic, but only deserves a warning. - validation_info->mWarnings["non_fatal_system_duplicate_under_root"]++; - warning_count++; - LL_WARNS("Inventory") << "System folder type has excess copies under root, type " << ft << " count " << count_under_root << LL_ENDL; - } - } - if (count_elsewhere > 0) - { - LL_WARNS("Inventory") << "Found " << count_elsewhere << " extra folders of type " << ft << " outside of root" << LL_ENDL; - validation_info->mWarnings["non_fatal_system_duplicate_elsewhere"]++; - warning_count++; - } - } - } - - - if (cat_lock > 0 || item_lock > 0) - { - LL_INFOS("Inventory") << "Found locks on some categories: sub-cat arrays " - << cat_lock << ", item arrays " << item_lock << LL_ENDL; - } - if (desc_unknown_count != 0) - { - LL_DEBUGS() << "Found " << desc_unknown_count << " cats with unknown descendent count" << LL_ENDL; - } - if (version_unknown_count != 0) - { - LL_DEBUGS("Inventory") << "Found " << version_unknown_count << " cats with unknown version" << LL_ENDL; - } - - // FIXME need to fail login and tell user to retry, contact support if problem persists. - bool valid = (fatal_errs == 0); - LL_INFOS("Inventory") << "Validate done, fatal errors: " << fatal_errs << ", warnings: " << warning_count << ", valid: " << valid << LL_ENDL; - - validation_info->mFatalErrorCount = fatal_errs; - validation_info->mWarningCount = warning_count; - validation_info->mLoopCount = loop_count; - validation_info->mOrphanedCount = orphaned_count; - - return validation_info; -} - -// Provides a unix-style path from root, like "/My Inventory/Clothing/.../myshirt" -std::string LLInventoryModel::getFullPath(const LLInventoryObject *obj) const -{ - std::vector path_elts; - std::map visited; - while (obj != NULL && !visited[obj->getUUID()]) - { - path_elts.push_back(obj->getName()); - // avoid infinite loop in the unlikely event of a cycle - visited[obj->getUUID()] = true; - obj = getObject(obj->getParentUUID()); - } - std::stringstream s; - std::string delim("/"); - std::reverse(path_elts.begin(), path_elts.end()); - std::string result = "/" + boost::algorithm::join(path_elts, delim); - return result; -} - -///---------------------------------------------------------------------------- -/// Local function definitions -///---------------------------------------------------------------------------- - - -#if 0 -bool decompress_file(const char* src_filename, const char* dst_filename) -{ - bool rv = false; - gzFile src = NULL; - U8* buffer = NULL; - LLFILE* dst = NULL; - S32 bytes = 0; - const S32 DECOMPRESS_BUFFER_SIZE = 32000; - - // open the files - src = gzopen(src_filename, "rb"); - if(!src) goto err_decompress; - dst = LLFile::fopen(dst_filename, "wb"); - if(!dst) goto err_decompress; - - // decompress. - buffer = new U8[DECOMPRESS_BUFFER_SIZE + 1]; - - do - { - bytes = gzread(src, buffer, DECOMPRESS_BUFFER_SIZE); - if (bytes < 0) - { - goto err_decompress; - } - - fwrite(buffer, bytes, 1, dst); - } while(gzeof(src) == 0); - - // success - rv = true; - - err_decompress: - if(src != NULL) gzclose(src); - if(buffer != NULL) delete[] buffer; - if(dst != NULL) fclose(dst); - return rv; -} -#endif - - -///---------------------------------------------------------------------------- -/// Class LLInventoryModel::FetchItemHttpHandler -///---------------------------------------------------------------------------- - -LLInventoryModel::FetchItemHttpHandler::FetchItemHttpHandler(const LLSD & request_sd) - : LLCore::HttpHandler(), - mRequestSD(request_sd) -{} - -LLInventoryModel::FetchItemHttpHandler::~FetchItemHttpHandler() -{} - -void LLInventoryModel::FetchItemHttpHandler::onCompleted(LLCore::HttpHandle handle, - LLCore::HttpResponse * response) -{ - do // Single-pass do-while used for common exit handling - { - LLCore::HttpStatus status(response->getStatus()); - // status = LLCore::HttpStatus(404); // Dev tool to force error handling - if (! status) - { - processFailure(status, response); - break; // Goto common exit - } - - LLCore::BufferArray * body(response->getBody()); - // body = NULL; // Dev tool to force error handling - if (! body || ! body->size()) - { - LL_WARNS(LOG_INV) << "Missing data in inventory item query." << LL_ENDL; - processFailure("HTTP response for inventory item query missing body", response); - break; // Goto common exit - } - - // body->write(0, "Garbage Response", 16); // Dev tool to force error handling - LLSD body_llsd; - if (! LLCoreHttpUtil::responseToLLSD(response, true, body_llsd)) - { - // INFOS-level logging will occur on the parsed failure - processFailure("HTTP response for inventory item query has malformed LLSD", response); - break; // Goto common exit - } - - // Expect top-level structure to be a map - // body_llsd = LLSD::emptyArray(); // Dev tool to force error handling - if (! body_llsd.isMap()) - { - processFailure("LLSD response for inventory item not a map", response); - break; // Goto common exit - } - - // Check for 200-with-error failures - // - // Original Responder-based serivce model didn't check for these errors. - // It may be more robust to ignore this condition. With aggregated requests, - // an error in one inventory item might take down the entire request. - // So if this instead broke up the aggregated items into single requests, - // maybe that would make progress. Or perhaps there's structured information - // that can tell us what went wrong. Need to dig into this and firm up - // the API. - // - // body_llsd["error"] = LLSD::emptyMap(); // Dev tool to force error handling - // body_llsd["error"]["identifier"] = "Development"; - // body_llsd["error"]["message"] = "You left development code in the viewer"; - if (body_llsd.has("error")) - { - processFailure("Inventory application error (200-with-error)", response); - break; // Goto common exit - } - - // Okay, process data if possible - processData(body_llsd, response); - } - while (false); -} - -void LLInventoryModel::FetchItemHttpHandler::processData(LLSD & content, LLCore::HttpResponse * response) -{ - start_new_inventory_observer(); - -#if 0 - LLUUID agent_id; - agent_id = content["agent_id"].asUUID(); - if (agent_id != gAgent.getID()) - { - LL_WARNS(LOG_INV) << "Got a inventory update for the wrong agent: " << agent_id - << LL_ENDL; - return; - } -#endif - - LLInventoryModel::item_array_t items; - LLInventoryModel::update_map_t update; - LLUUID folder_id; - LLSD content_items(content["items"]); - const S32 count(content_items.size()); - - // Does this loop ever execute more than once? - for (S32 i(0); i < count; ++i) - { - LLPointer titem = new LLViewerInventoryItem; - titem->unpackMessage(content_items[i]); - - LL_DEBUGS(LOG_INV) << "ItemHttpHandler::httpSuccess item id: " - << titem->getUUID() << LL_ENDL; - items.push_back(titem); - - // examine update for changes. - LLViewerInventoryItem * itemp(gInventory.getItem(titem->getUUID())); - - if (itemp) - { - if (titem->getParentUUID() == itemp->getParentUUID()) - { - update[titem->getParentUUID()]; - } - else - { - ++update[titem->getParentUUID()]; - --update[itemp->getParentUUID()]; - } - } - else - { - ++update[titem->getParentUUID()]; - } - - if (folder_id.isNull()) - { - folder_id = titem->getParentUUID(); - } - } - - // as above, this loop never seems to loop more than once per call - for (LLInventoryModel::item_array_t::iterator it = items.begin(); it != items.end(); ++it) - { - gInventory.updateItem(*it); - } - gInventory.notifyObservers(); - gViewerWindow->getWindow()->decBusyCount(); -} - - -void LLInventoryModel::FetchItemHttpHandler::processFailure(LLCore::HttpStatus status, LLCore::HttpResponse * response) -{ - const std::string & ct(response->getContentType()); - LL_WARNS(LOG_INV) << "Inventory item fetch failure\n" - << "[Status: " << status.toTerseString() << "]\n" - << "[Reason: " << status.toString() << "]\n" - << "[Content-type: " << ct << "]\n" - << "[Content (abridged): " - << LLCoreHttpUtil::responseToString(response) << "]" << LL_ENDL; - gInventory.notifyObservers(); -} - -void LLInventoryModel::FetchItemHttpHandler::processFailure(const char * const reason, LLCore::HttpResponse * response) -{ - LL_WARNS(LOG_INV) << "Inventory item fetch failure\n" - << "[Status: internal error]\n" - << "[Reason: " << reason << "]\n" - << "[Content (abridged): " - << LLCoreHttpUtil::responseToString(response) << "]" << LL_ENDL; - gInventory.notifyObservers(); -} - +/** + * @file llinventorymodel.cpp + * @brief Implementation of the inventory model used to track agent inventory. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include +#include + +#include "llinventorymodel.h" + +#include "llaisapi.h" +#include "llagent.h" +#include "llagentwearables.h" +#include "llappearancemgr.h" +#include "llavatarnamecache.h" +#include "llclipboard.h" +#include "lldispatcher.h" +#include "llinventorypanel.h" +#include "llinventorybridge.h" +#include "llinventoryfunctions.h" +#include "llinventorymodelbackgroundfetch.h" +#include "llinventoryobserver.h" +#include "llinventorypanel.h" +#include "llfloaterpreviewtrash.h" +#include "llnotificationsutil.h" +#include "llmarketplacefunctions.h" +#include "llwindow.h" +#include "llviewercontrol.h" +#include "llviewernetwork.h" +#include "llpreview.h" +#include "llviewergenericmessage.h" +#include "llviewermessage.h" +#include "llviewerfoldertype.h" +#include "llviewerwindow.h" +#include "llappviewer.h" +#include "llviewerregion.h" +#include "llcallbacklist.h" +#include "llvoavatarself.h" +#include "llgesturemgr.h" +#include "llsdserialize.h" +#include "llsdutil.h" +#include "bufferarray.h" +#include "bufferstream.h" +#include "llcorehttputil.h" +#include "hbxxh.h" +#include "llstartup.h" + +//#define DIFF_INVENTORY_FILES +#ifdef DIFF_INVENTORY_FILES +#include "process.h" +#endif + +#include +#include + +// Increment this if the inventory contents change in a non-backwards-compatible way. +// For viewer 2, the addition of link items makes a pre-viewer-2 cache incorrect. +const S32 LLInventoryModel::sCurrentInvCacheVersion = 3; +bool LLInventoryModel::sFirstTimeInViewer2 = true; + +S32 LLInventoryModel::sPendingSystemFolders = 0; + +///---------------------------------------------------------------------------- +/// Local function declarations, constants, enums, and typedefs +///---------------------------------------------------------------------------- + +//bool decompress_file(const char* src_filename, const char* dst_filename); +static const char PRODUCTION_CACHE_FORMAT_STRING[] = "%s.inv.llsd"; +static const char GRID_CACHE_FORMAT_STRING[] = "%s.%s.inv.llsd"; +static const char * const LOG_INV("Inventory"); + +struct InventoryIDPtrLess +{ + bool operator()(const LLViewerInventoryCategory* i1, const LLViewerInventoryCategory* i2) const + { + return (i1->getUUID() < i2->getUUID()); + } +}; + +class LLCanCache : public LLInventoryCollectFunctor +{ +public: + LLCanCache(LLInventoryModel* model) : mModel(model) {} + virtual ~LLCanCache() {} + virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); +protected: + LLInventoryModel* mModel; + std::set mCachedCatIDs; +}; + +bool LLCanCache::operator()(LLInventoryCategory* cat, LLInventoryItem* item) +{ + bool rv = false; + if(item) + { + if(mCachedCatIDs.find(item->getParentUUID()) != mCachedCatIDs.end()) + { + rv = true; + } + } + else if(cat) + { + // HACK: downcast + LLViewerInventoryCategory* c = (LLViewerInventoryCategory*)cat; + if(c->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN) + { + S32 descendents_server = c->getDescendentCount(); + S32 descendents_actual = c->getViewerDescendentCount(); + if(descendents_server == descendents_actual) + { + mCachedCatIDs.insert(c->getUUID()); + rv = true; + } + } + } + return rv; +} + +struct InventoryCallbackInfo +{ + InventoryCallbackInfo(U32 callback, const LLUUID& inv_id) : + mCallback(callback), mInvID(inv_id) {} + U32 mCallback; + LLUUID mInvID; +}; + +///---------------------------------------------------------------------------- +/// Class LLDispatchClassifiedClickThrough +///---------------------------------------------------------------------------- + +class LLDispatchBulkUpdateInventory : public LLDispatchHandler +{ +public: + virtual bool operator()( + const LLDispatcher* dispatcher, + const std::string& key, + const LLUUID& invoice, + const sparam_t& strings) + { + LLSD message; + + // Expect single string parameter in the form of a notation serialized LLSD. + sparam_t::const_iterator it = strings.begin(); + if (it != strings.end()) { + const std::string& llsdRaw = *it++; + std::istringstream llsdData(llsdRaw); + if (!LLSDSerialize::deserialize(message, llsdData, llsdRaw.length())) + { + LL_WARNS() << "LLDispatchBulkUpdateInventory: Attempted to read parameter data into LLSD but failed:" << llsdRaw << LL_ENDL; + } + } + + LLInventoryModel::update_map_t update; + LLInventoryModel::cat_array_t folders; + LLInventoryModel::item_array_t items; + std::list cblist; + uuid_vec_t wearable_ids; + + LLSD item_data = message["item_data"]; + if (item_data.isArray()) + { + for (LLSD::array_iterator itd = item_data.beginArray(); itd != item_data.endArray(); ++itd) + { + const LLSD &item(*itd); + + // Agent id probably should be in the root of the message + LLUUID agent_id = item["agent_id"].asUUID(); + if (agent_id != gAgent.getID()) + { + LL_WARNS() << "Got a BulkUpdateInventory for the wrong agent." << LL_ENDL; + return false; + } + + LLPointer titem = new LLViewerInventoryItem; + titem->unpackMessage(item); + LL_DEBUGS("Inventory") << "unpacked item '" << titem->getName() << "' in " + << titem->getParentUUID() << LL_ENDL; + // callback id might be no longer supported + U32 callback_id = item["callback_id"].asInteger(); + + if (titem->getUUID().notNull()) + { + items.push_back(titem); + cblist.push_back(InventoryCallbackInfo(callback_id, titem->getUUID())); + if (titem->getInventoryType() == LLInventoryType::IT_WEARABLE) + { + wearable_ids.push_back(titem->getUUID()); + } + + // examine update for changes. + LLViewerInventoryItem* itemp = gInventory.getItem(titem->getUUID()); + if (itemp) + { + if (titem->getParentUUID() == itemp->getParentUUID()) + { + update[titem->getParentUUID()]; + } + else + { + ++update[titem->getParentUUID()]; + --update[itemp->getParentUUID()]; + } + } + else + { + LLViewerInventoryCategory* folderp = gInventory.getCategory(titem->getParentUUID()); + if (folderp) + { + ++update[titem->getParentUUID()]; + } + } + } + else + { + cblist.push_back(InventoryCallbackInfo(callback_id, LLUUID::null)); + } + } + } + + LLSD folder_data = message["folder_data"]; + if (folder_data.isArray()) + { + for (LLSD::array_iterator itd = folder_data.beginArray(); itd != folder_data.endArray(); ++itd) + { + const LLSD &folder(*itd); + + LLPointer tfolder = new LLViewerInventoryCategory(gAgent.getID()); + tfolder->unpackMessage(folder); + + LL_DEBUGS("Inventory") << "unpacked folder '" << tfolder->getName() << "' (" + << tfolder->getUUID() << ") in " << tfolder->getParentUUID() + << LL_ENDL; + + // If the folder is a listing or a version folder, all we need to do is update the SLM data + int depth_folder = depth_nesting_in_marketplace(tfolder->getUUID()); + if ((depth_folder == 1) || (depth_folder == 2)) + { + // Trigger an SLM listing update + LLUUID listing_uuid = (depth_folder == 1 ? tfolder->getUUID() : tfolder->getParentUUID()); + S32 listing_id = LLMarketplaceData::instance().getListingID(listing_uuid); + LLMarketplaceData::instance().getListing(listing_id); + // In that case, there is no item to update so no callback -> we skip the rest of the update + } + else if (tfolder->getUUID().notNull()) + { + folders.push_back(tfolder); + LLViewerInventoryCategory* folderp = gInventory.getCategory(tfolder->getUUID()); + if (folderp) + { + if (tfolder->getParentUUID() == folderp->getParentUUID()) + { + update[tfolder->getParentUUID()]; + } + else + { + ++update[tfolder->getParentUUID()]; + --update[folderp->getParentUUID()]; + } + } + else + { + // we could not find the folder, so it is probably + // new. However, we only want to attempt accounting + // for the parent if we can find the parent. + folderp = gInventory.getCategory(tfolder->getParentUUID()); + if (folderp) + { + ++update[tfolder->getParentUUID()]; + } + } + } + } + } + + gInventory.accountForUpdate(update); + + for (LLInventoryModel::cat_array_t::iterator cit = folders.begin(); cit != folders.end(); ++cit) + { + gInventory.updateCategory(*cit); + } + for (LLInventoryModel::item_array_t::iterator iit = items.begin(); iit != items.end(); ++iit) + { + gInventory.updateItem(*iit); + } + gInventory.notifyObservers(); + + /* + Transaction id not included? + + // The incoming inventory could span more than one BulkInventoryUpdate packet, + // so record the transaction ID for this purchase, then wear all clothing + // that comes in as part of that transaction ID. JC + if (LLInventoryState::sWearNewClothing) + { + LLInventoryState::sWearNewClothingTransactionID = tid; + LLInventoryState::sWearNewClothing = false; + } + + if (tid.notNull() && tid == LLInventoryState::sWearNewClothingTransactionID) + { + count = wearable_ids.size(); + for (i = 0; i < count; ++i) + { + LLViewerInventoryItem* wearable_item; + wearable_item = gInventory.getItem(wearable_ids[i]); + LLAppearanceMgr::instance().wearItemOnAvatar(wearable_item->getUUID(), true, true); + } + } + */ + + if (LLInventoryState::sWearNewClothing && wearable_ids.size() > 0) + { + LLInventoryState::sWearNewClothing = false; + + size_t count = wearable_ids.size(); + for (S32 i = 0; i < count; ++i) + { + LLViewerInventoryItem* wearable_item; + wearable_item = gInventory.getItem(wearable_ids[i]); + LLAppearanceMgr::instance().wearItemOnAvatar(wearable_item->getUUID(), true, true); + } + } + + std::list::iterator inv_it; + for (inv_it = cblist.begin(); inv_it != cblist.end(); ++inv_it) + { + InventoryCallbackInfo cbinfo = (*inv_it); + gInventoryCallbacks.fire(cbinfo.mCallback, cbinfo.mInvID); + } + return true; + } +}; +static LLDispatchBulkUpdateInventory sBulkUpdateInventory; + +///---------------------------------------------------------------------------- +/// Class LLInventoryValidationInfo +///---------------------------------------------------------------------------- +LLInventoryValidationInfo::LLInventoryValidationInfo() +{ +} + +void LLInventoryValidationInfo::toOstream(std::ostream& os) const +{ + os << "mFatalErrorCount " << mFatalErrorCount + << " mWarningCount " << mWarningCount + << " mLoopCount " << mLoopCount + << " mOrphanedCount " << mOrphanedCount; +} + + +std::ostream& operator<<(std::ostream& os, const LLInventoryValidationInfo& v) +{ + v.toOstream(os); + return os; +} + +void LLInventoryValidationInfo::asLLSD(LLSD& sd) const +{ + sd["fatal_error_count"] = mFatalErrorCount; + sd["loop_count"] = mLoopCount; + sd["orphaned_count"] = mOrphanedCount; + sd["initialized"] = mInitialized; + sd["missing_system_folders_count"] = LLSD::Integer(mMissingRequiredSystemFolders.size()); + sd["fatal_no_root_folder"] = mFatalNoRootFolder; + sd["fatal_no_library_root_folder"] = mFatalNoLibraryRootFolder; + sd["fatal_qa_debug_mode"] = mFatalQADebugMode; + + sd["warning_count"] = mWarningCount; + if (mWarningCount>0) + { + sd["warnings"] = LLSD::emptyArray(); + for (auto const& it : mWarnings) + { + S32 val =LLSD::Integer(it.second); + if (val>0) + { + sd["warnings"][it.first] = val; + } + } + } + if (mMissingRequiredSystemFolders.size()>0) + { + sd["missing_system_folders"] = LLSD::emptyArray(); + for(auto ft: mMissingRequiredSystemFolders) + { + sd["missing_system_folders"].append(LLFolderType::lookup(ft)); + } + } + sd["duplicate_system_folders_count"] = LLSD::Integer(mDuplicateRequiredSystemFolders.size()); + if (mDuplicateRequiredSystemFolders.size()>0) + { + sd["duplicate_system_folders"] = LLSD::emptyArray(); + for(auto ft: mDuplicateRequiredSystemFolders) + { + sd["duplicate_system_folders"].append(LLFolderType::lookup(ft)); + } + } + +} + +///---------------------------------------------------------------------------- +/// Class LLInventoryModel +///---------------------------------------------------------------------------- + +// global for the agent inventory. +LLInventoryModel gInventory; + +// Default constructor +LLInventoryModel::LLInventoryModel() +: // These are now ordered, keep them that way. + mBacklinkMMap(), + mIsAgentInvUsable(false), + mRootFolderID(), + mLibraryRootFolderID(), + mLibraryOwnerID(), + mCategoryMap(), + mItemMap(), + mParentChildCategoryTree(), + mParentChildItemTree(), + mLastItem(NULL), + mIsNotifyObservers(false), + mModifyMask(LLInventoryObserver::ALL), + mChangedItemIDs(), + mBulkFecthCallbackSlot(), + mObservers(), + mHttpRequestFG(NULL), + mHttpRequestBG(NULL), + mHttpOptions(), + mHttpHeaders(), + mHttpPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), + mCategoryLock(), + mItemLock(), + mValidationInfo(new LLInventoryValidationInfo) +{} + + +// Destroys the object +LLInventoryModel::~LLInventoryModel() +{ + cleanupInventory(); +} + +void LLInventoryModel::cleanupInventory() +{ + empty(); + // Deleting one observer might erase others from the list, so always pop off the front + while (!mObservers.empty()) + { + observer_list_t::iterator iter = mObservers.begin(); + LLInventoryObserver* observer = *iter; + mObservers.erase(iter); + delete observer; + } + + if (mBulkFecthCallbackSlot.connected()) + { + mBulkFecthCallbackSlot.disconnect(); + } + mObservers.clear(); + + // Run down HTTP transport + mHttpHeaders.reset(); + mHttpOptions.reset(); + + delete mHttpRequestFG; + mHttpRequestFG = NULL; + delete mHttpRequestBG; + mHttpRequestBG = NULL; +} + +// This is a convenience function to check if one object has a parent +// chain up to the category specified by UUID. +bool LLInventoryModel::isObjectDescendentOf(const LLUUID& obj_id, + const LLUUID& cat_id) const +{ + if (obj_id == cat_id) return true; + + const LLInventoryObject* obj = getObject(obj_id); + while(obj) + { + const LLUUID& parent_id = obj->getParentUUID(); + if( parent_id.isNull() ) + { + return false; + } + if(parent_id == cat_id) + { + return true; + } + // Since we're scanning up the parents, we only need to check + // in the category list. + obj = getCategory(parent_id); + } + return false; +} + +const LLViewerInventoryCategory *LLInventoryModel::getFirstNondefaultParent(const LLUUID& obj_id) const +{ + const LLInventoryObject* obj = getObject(obj_id); + if(!obj) + { + LL_WARNS(LOG_INV) << "Non-existent object [ id: " << obj_id << " ] " << LL_ENDL; + return NULL; + } + // Search up the parent chain until we get to root or an acceptable folder. + // This assumes there are no cycles in the tree else we'll get a hang. + LLUUID parent_id = obj->getParentUUID(); + while (!parent_id.isNull()) + { + const LLViewerInventoryCategory *cat = getCategory(parent_id); + if (!cat) break; + const LLFolderType::EType folder_type = cat->getPreferredType(); + if (folder_type != LLFolderType::FT_NONE && + folder_type != LLFolderType::FT_ROOT_INVENTORY && + !LLFolderType::lookupIsEnsembleType(folder_type)) + { + return cat; + } + parent_id = cat->getParentUUID(); + } + return NULL; +} + +// +// Search up the parent chain until we get to the specified parent, then return the first child category under it +// +const LLViewerInventoryCategory* LLInventoryModel::getFirstDescendantOf(const LLUUID& master_parent_id, const LLUUID& obj_id) const +{ + if (master_parent_id == obj_id) + { + return NULL; + } + + const LLViewerInventoryCategory* current_cat = getCategory(obj_id); + + if (current_cat == NULL) + { + current_cat = getCategory(getObject(obj_id)->getParentUUID()); + } + + while (current_cat != NULL) + { + const LLUUID& current_parent_id = current_cat->getParentUUID(); + + if (current_parent_id == master_parent_id) + { + return current_cat; + } + + current_cat = getCategory(current_parent_id); + } + + return NULL; +} + +LLInventoryModel::EAncestorResult LLInventoryModel::getObjectTopmostAncestor(const LLUUID& object_id, LLUUID& result) const +{ + LLInventoryObject *object = getObject(object_id); + if (!object) + { + LL_WARNS(LOG_INV) << "Unable to trace topmost ancestor, initial object " << object_id << " does not exist" << LL_ENDL; + return ANCESTOR_MISSING; + } + + std::set object_ids{ object_id }; // loop protection + while (object->getParentUUID().notNull()) + { + LLUUID parent_id = object->getParentUUID(); + if (object_ids.find(parent_id) != object_ids.end()) + { + LL_WARNS(LOG_INV) << "Detected a loop on an object " << parent_id << " when searching for ancestor of " << object_id << LL_ENDL; + return ANCESTOR_LOOP; + } + object_ids.insert(parent_id); + LLInventoryObject *parent_object = getObject(parent_id); + if (!parent_object) + { + LL_WARNS(LOG_INV) << "unable to trace topmost ancestor of " << object_id << ", missing item for uuid " << parent_id << LL_ENDL; + return ANCESTOR_MISSING; + } + object = parent_object; + } + result = object->getUUID(); + return ANCESTOR_OK; +} + +// Get the object by id. Returns NULL if not found. +LLInventoryObject* LLInventoryModel::getObject(const LLUUID& id) const +{ + LLViewerInventoryCategory* cat = getCategory(id); + if (cat) + { + return cat; + } + LLViewerInventoryItem* item = getItem(id); + if (item) + { + return item; + } + return NULL; +} + +// Get the item by id. Returns NULL if not found. +LLViewerInventoryItem* LLInventoryModel::getItem(const LLUUID& id) const +{ + LLViewerInventoryItem* item = NULL; + if(mLastItem.notNull() && mLastItem->getUUID() == id) + { + item = mLastItem; + } + else + { + item_map_t::const_iterator iter = mItemMap.find(id); + if (iter != mItemMap.end()) + { + item = iter->second; + mLastItem = item; + } + } + return item; +} + +// Get the category by id. Returns NULL if not found +LLViewerInventoryCategory* LLInventoryModel::getCategory(const LLUUID& id) const +{ + LLViewerInventoryCategory* category = NULL; + cat_map_t::const_iterator iter = mCategoryMap.find(id); + if (iter != mCategoryMap.end()) + { + category = iter->second; + } + return category; +} + +S32 LLInventoryModel::getItemCount() const +{ + return mItemMap.size(); +} + +S32 LLInventoryModel::getCategoryCount() const +{ + return mCategoryMap.size(); +} + +// Return the direct descendents of the id provided. The array +// provided points straight into the guts of this object, and +// should only be used for read operations, since modifications +// may invalidate the internal state of the inventory. Set passed +// in values to NULL if the call fails. +void LLInventoryModel::getDirectDescendentsOf(const LLUUID& cat_id, + cat_array_t*& categories, + item_array_t*& items) const +{ + categories = get_ptr_in_map(mParentChildCategoryTree, cat_id); + items = get_ptr_in_map(mParentChildItemTree, cat_id); +} + +void LLInventoryModel::getDirectDescendentsOf(const LLUUID& cat_id, cat_array_t& categories, item_array_t& items, LLInventoryCollectFunctor& f) const +{ + if (cat_array_t* categoriesp = get_ptr_in_map(mParentChildCategoryTree, cat_id)) + { + for (LLViewerInventoryCategory* pFolder : *categoriesp) + { + if (f(pFolder, nullptr)) + { + categories.push_back(pFolder); + } + } + } + + if (item_array_t* itemsp = get_ptr_in_map(mParentChildItemTree, cat_id)) + { + for (LLViewerInventoryItem* pItem : *itemsp) + { + if (f(nullptr, pItem)) + { + items.push_back(pItem); + } + } + } +} + +LLInventoryModel::digest_t LLInventoryModel::hashDirectDescendentNames(const LLUUID& cat_id) const +{ + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + getDirectDescendentsOf(cat_id,cat_array,item_array); + if (!item_array) + { + return LLUUID::null; + } + HBXXH128 item_name_hash; + for (LLInventoryModel::item_array_t::const_iterator iter = item_array->begin(); + iter != item_array->end(); + iter++) + { + const LLViewerInventoryItem *item = (*iter); + if (!item) + continue; + item_name_hash.update(item->getName()); + } + return item_name_hash.digest(); +} + +// SJB: Added version to lock the arrays to catch potential logic bugs +void LLInventoryModel::lockDirectDescendentArrays(const LLUUID& cat_id, + cat_array_t*& categories, + item_array_t*& items) +{ + getDirectDescendentsOf(cat_id, categories, items); + if (categories) + { + mCategoryLock[cat_id] = true; + } + if (items) + { + mItemLock[cat_id] = true; + } +} + +void LLInventoryModel::unlockDirectDescendentArrays(const LLUUID& cat_id) +{ + mCategoryLock[cat_id] = false; + mItemLock[cat_id] = false; +} + +void LLInventoryModel::consolidateForType(const LLUUID& main_id, LLFolderType::EType type) +{ + // Make a list of folders that are not "main_id" and are of "type" + std::vector folder_ids; + for (cat_map_t::iterator cit = mCategoryMap.begin(); cit != mCategoryMap.end(); ++cit) + { + LLViewerInventoryCategory* cat = cit->second; + if ((cat->getPreferredType() == type) && (cat->getUUID() != main_id)) + { + folder_ids.push_back(cat->getUUID()); + } + } + + // Iterate through those folders + for (std::vector::iterator folder_ids_it = folder_ids.begin(); folder_ids_it != folder_ids.end(); ++folder_ids_it) + { + LLUUID folder_id = (*folder_ids_it); + + // Get the content of this folder + cat_array_t* cats; + item_array_t* items; + getDirectDescendentsOf(folder_id, cats, items); + + // Move all items to the main folder + // Note : we get the list of UUIDs and iterate on them instead of iterating directly on item_array_t + // elements. This is because moving elements modify the maps and, consequently, invalidate iterators on them. + // This "gather and iterate" method is verbose but resilient. + std::vector list_uuids; + for (item_array_t::const_iterator it = items->begin(); it != items->end(); ++it) + { + list_uuids.push_back((*it)->getUUID()); + } + for (std::vector::const_iterator it = list_uuids.begin(); it != list_uuids.end(); ++it) + { + LLViewerInventoryItem* item = getItem(*it); + changeItemParent(item, main_id, true); + } + + // Move all folders to the main folder + list_uuids.clear(); + for (cat_array_t::const_iterator it = cats->begin(); it != cats->end(); ++it) + { + list_uuids.push_back((*it)->getUUID()); + } + for (std::vector::const_iterator it = list_uuids.begin(); it != list_uuids.end(); ++it) + { + LLViewerInventoryCategory* cat = getCategory(*it); + changeCategoryParent(cat, main_id, true); + } + + // Purge the emptied folder + // Note that this might be a system folder, don't validate removability + LLViewerInventoryCategory* cat = getCategory(folder_id); + if (cat) + { + const LLUUID trash_id = findCategoryUUIDForType(LLFolderType::FT_TRASH); + if (trash_id.notNull()) + { + changeCategoryParent(cat, trash_id, true); + } + } + remove_inventory_category(folder_id, NULL); + } +} + +void LLInventoryModel::ensureCategoryForTypeExists(LLFolderType::EType preferred_type) +{ + LLUUID rv = LLUUID::null; + LLUUID root_id = gInventory.getRootFolderID(); + if (LLFolderType::FT_ROOT_INVENTORY == preferred_type) + { + rv = root_id; + } + else if (root_id.notNull()) + { + cat_array_t* cats = NULL; + cats = get_ptr_in_map(mParentChildCategoryTree, root_id); + if (cats) + { + S32 count = cats->size(); + for (S32 i = 0; i < count; ++i) + { + LLViewerInventoryCategory* p_cat = cats->at(i); + if (p_cat && p_cat->getPreferredType() == preferred_type) + { + const LLUUID& folder_id = cats->at(i)->getUUID(); + if (rv.isNull() || folder_id < rv) + { + rv = folder_id; + } + } + } + } + } + + if (rv.isNull() && root_id.notNull()) + { + + if (isInventoryUsable()) + { + createNewCategory( + root_id, + preferred_type, + LLStringUtil::null, + [preferred_type](const LLUUID &new_cat_id) + { + if (new_cat_id.isNull()) + { + LL_WARNS("Inventory") + << "Failed to create folder of type " << preferred_type + << LL_ENDL; + } + else + { + LL_WARNS("Inventory") << "Created category: " << new_cat_id + << " for type: " << preferred_type << LL_ENDL; + sPendingSystemFolders--; + } + } + ); + } + else + { + LL_WARNS("Inventory") << "Can't create requested folder, type " << preferred_type + << " because inventory is not usable" << LL_ENDL; + } + } + else + { + sPendingSystemFolders--; + } +} + +const LLUUID LLInventoryModel::findCategoryUUIDForTypeInRoot( + LLFolderType::EType preferred_type, + const LLUUID& root_id) const +{ + LLUUID rv = LLUUID::null; + if(LLFolderType::FT_ROOT_INVENTORY == preferred_type) + { + rv = root_id; + } + else if (root_id.notNull()) + { + cat_array_t* cats = NULL; + cats = get_ptr_in_map(mParentChildCategoryTree, root_id); + if(cats) + { + S32 count = cats->size(); + for(S32 i = 0; i < count; ++i) + { + LLViewerInventoryCategory* p_cat = cats->at(i); + if(p_cat && p_cat->getPreferredType() == preferred_type) + { + const LLUUID& folder_id = cats->at(i)->getUUID(); + if (rv.isNull() || folder_id < rv) + { + rv = folder_id; + } + } + } + } + } + + if(rv.isNull() + && root_id.notNull() + && preferred_type != LLFolderType::FT_MARKETPLACE_LISTINGS + && preferred_type != LLFolderType::FT_OUTBOX) + { + // if it does not exists, it should either be added + // to createCommonSystemCategories or server should + // have set it + llassert(!isInventoryUsable()); + LL_WARNS("Inventory") << "Tried to find folder, type " << preferred_type + << " but category does not exist" << LL_ENDL; + } + return rv; +} + +// findCategoryUUIDForType() returns the uuid of the category that +// specifies 'type' as what it defaults to containing. The category is +// not necessarily only for that type. *NOTE: This will create a new +// inventory category on the fly if one does not exist. +const LLUUID LLInventoryModel::findCategoryUUIDForType(LLFolderType::EType preferred_type) const +{ + return findCategoryUUIDForTypeInRoot(preferred_type, gInventory.getRootFolderID()); +} + +const LLUUID LLInventoryModel::findUserDefinedCategoryUUIDForType(LLFolderType::EType preferred_type) const +{ + LLUUID cat_id; + switch (preferred_type) + { + case LLFolderType::FT_OBJECT: + { + cat_id = LLUUID(gSavedPerAccountSettings.getString("ModelUploadFolder")); + break; + } + case LLFolderType::FT_TEXTURE: + { + cat_id = LLUUID(gSavedPerAccountSettings.getString("TextureUploadFolder")); + break; + } + case LLFolderType::FT_SOUND: + { + cat_id = LLUUID(gSavedPerAccountSettings.getString("SoundUploadFolder")); + break; + } + case LLFolderType::FT_ANIMATION: + { + cat_id = LLUUID(gSavedPerAccountSettings.getString("AnimationUploadFolder")); + break; + } + case LLFolderType::FT_MATERIAL: + { + cat_id = LLUUID(gSavedPerAccountSettings.getString("PBRUploadFolder")); + break; + } + default: + break; + } + + if (cat_id.isNull() || !getCategory(cat_id)) + { + cat_id = findCategoryUUIDForTypeInRoot(preferred_type, getRootFolderID()); + } + return cat_id; +} + +const LLUUID LLInventoryModel::findLibraryCategoryUUIDForType(LLFolderType::EType preferred_type) const +{ + return findCategoryUUIDForTypeInRoot(preferred_type, gInventory.getLibraryRootFolderID()); +} + +// Convenience function to create a new category. You could call +// updateCategory() with a newly generated UUID category, but this +// version will take care of details like what the name should be +// based on preferred type. +void LLInventoryModel::createNewCategory(const LLUUID& parent_id, + LLFolderType::EType preferred_type, + const std::string& pname, + inventory_func_type callback, + const LLUUID& thumbnail_id) +{ + LL_DEBUGS(LOG_INV) << "Create '" << pname << "' in '" << make_inventory_path(parent_id) << "'" << LL_ENDL; + if (!isInventoryUsable()) + { + LL_WARNS(LOG_INV) << "Inventory is not usable; can't create requested category of type " + << preferred_type << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + + if(LLFolderType::lookup(preferred_type) == LLFolderType::badLookup()) + { + LL_DEBUGS(LOG_INV) << "Attempt to create undefined category." << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + + if (preferred_type != LLFolderType::FT_NONE) + { + // Ultimately this should only be done for non-singleton + // types. Requires back-end changes to guarantee that others + // already exist. + LL_WARNS(LOG_INV) << "Creating new system folder, type " << preferred_type << LL_ENDL; + } + + std::string name = pname; + if (pname.empty()) + { + name.assign(LLViewerFolderType::lookupNewCategoryName(preferred_type)); + } + + if (AISAPI::isAvailable()) + { + LLSD new_inventory = LLSD::emptyMap(); + new_inventory["categories"] = LLSD::emptyArray(); + LLViewerInventoryCategory cat(LLUUID::null, parent_id, preferred_type, name, gAgent.getID()); + cat.setThumbnailUUID(thumbnail_id); + LLSD cat_sd = cat.asAISCreateCatLLSD(); + new_inventory["categories"].append(cat_sd); + AISAPI::CreateInventory( + parent_id, + new_inventory, + [this, callback, parent_id, preferred_type, name] (const LLUUID& new_category) + { + if (new_category.isNull()) + { + if (callback && !callback.empty()) + { + callback(new_category); + } + return; + } + + // todo: not needed since AIS does the accounting? + LLViewerInventoryCategory* folderp = gInventory.getCategory(new_category); + if (!folderp) + { + // Add the category to the internal representation + LLPointer cat = new LLViewerInventoryCategory( + new_category, + parent_id, + preferred_type, + name, + gAgent.getID()); + + LLInventoryModel::LLCategoryUpdate update(cat->getParentUUID(), 1); + accountForUpdate(update); + + cat->setVersion(LLViewerInventoryCategory::VERSION_INITIAL - 1); // accountForUpdate() will icrease version by 1 + cat->setDescendentCount(0); + updateCategory(cat); + } + + if (callback && !callback.empty()) + { + callback(new_category); + } + }); + return; + } + + LLViewerRegion* viewer_region = gAgent.getRegion(); + std::string url; + if ( viewer_region ) + url = viewer_region->getCapability("CreateInventoryCategory"); + + if (!url.empty()) + { + //Let's use the new capability. + LLUUID id; + id.generate(); + LLSD request, body; + body["folder_id"] = id; + body["parent_id"] = parent_id; + body["type"] = (LLSD::Integer) preferred_type; + body["name"] = name; + + request["message"] = "CreateInventoryCategory"; + request["payload"] = body; + + LL_DEBUGS(LOG_INV) << "Creating category via request: " << ll_pretty_print_sd(request) << LL_ENDL; + LLCoros::instance().launch("LLInventoryModel::createNewCategoryCoro", + boost::bind(&LLInventoryModel::createNewCategoryCoro, this, url, body, callback)); + return; + } + + if (callback) + { + callback(LLUUID::null); // Notify about failure + } +} + +void LLInventoryModel::createNewCategoryCoro(std::string url, LLSD postData, inventory_func_type callback) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("createNewCategoryCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + + + httpOpts->setWantHeaders(true); + + LL_INFOS("HttpCoroutineAdapter", "genericPostCoro") << "Generic POST for " << url << LL_ENDL; + + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData, httpOpts); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS() << "HTTP failure attempting to create category." << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + + if (!result.has("folder_id")) + { + LL_WARNS() << "Malformed response contents" << ll_pretty_print_sd(result) << LL_ENDL; + if (callback) + { + callback(LLUUID::null); + } + return; + } + + LLUUID categoryId = result["folder_id"].asUUID(); + + LLViewerInventoryCategory* folderp = gInventory.getCategory(categoryId); + if (!folderp) + { + // Add the category to the internal representation + LLPointer cat = new LLViewerInventoryCategory(categoryId, + result["parent_id"].asUUID(), (LLFolderType::EType)result["type"].asInteger(), + result["name"].asString(), gAgent.getID()); + + LLInventoryModel::LLCategoryUpdate update(cat->getParentUUID(), 1); + accountForUpdate(update); + + cat->setVersion(LLViewerInventoryCategory::VERSION_INITIAL - 1); // accountForUpdate() will icrease version by 1 + cat->setDescendentCount(0); + updateCategory(cat); + } + else + { + // bulk processing was faster than coroutine (coro request->processBulkUpdateInventory->coro response) + // category already exists, but needs an update + if (folderp->getVersion() != LLViewerInventoryCategory::VERSION_INITIAL + || folderp->getDescendentCount() != LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN) + { + LL_WARNS() << "Inventory desync on folder creation. Newly created folder already has descendants or got a version.\n" + << "Name: " << folderp->getName() + << " Id: " << folderp->getUUID() + << " Version: " << folderp->getVersion() + << " Descendants: " << folderp->getDescendentCount() + << LL_ENDL; + } + // Recreate category with correct values + // Creating it anew just simplifies figuring out needed change-masks + // and making all needed updates, see updateCategory + LLPointer cat = new LLViewerInventoryCategory(categoryId, + result["parent_id"].asUUID(), (LLFolderType::EType)result["type"].asInteger(), + result["name"].asString(), gAgent.getID()); + + if (folderp->getParentUUID() != cat->getParentUUID()) + { + LL_WARNS() << "Inventory desync on folder creation. Newly created folder has wrong parent.\n" + << "Name: " << folderp->getName() + << " Id: " << folderp->getUUID() + << " Expected parent: " << cat->getParentUUID() + << " Actual parent: " << folderp->getParentUUID() + << LL_ENDL; + LLInventoryModel::LLCategoryUpdate update(cat->getParentUUID(), 1); + accountForUpdate(update); + } + // else: Do not update parent, parent is already aware of the change. See processBulkUpdateInventory + + cat->setVersion(LLViewerInventoryCategory::VERSION_INITIAL - 1); // accountForUpdate() will icrease version by 1 + cat->setDescendentCount(0); + updateCategory(cat); + } + + if (callback) + { + callback(categoryId); + } + +} + +// This is optimized for the case that we just want to know whether a +// category has any immediate children meeting a condition, without +// needing to recurse or build up any lists. +bool LLInventoryModel::hasMatchingDirectDescendent(const LLUUID& cat_id, + LLInventoryCollectFunctor& filter) +{ + LLInventoryModel::cat_array_t *cats; + LLInventoryModel::item_array_t *items; + getDirectDescendentsOf(cat_id, cats, items); + if (cats) + { + for (LLInventoryModel::cat_array_t::const_iterator it = cats->begin(); + it != cats->end(); ++it) + { + if (filter(*it,NULL)) + { + return true; + } + } + } + if (items) + { + for (LLInventoryModel::item_array_t::const_iterator it = items->begin(); + it != items->end(); ++it) + { + if (filter(NULL,*it)) + { + return true; + } + } + } + return false; +} + +// Starting with the object specified, add its descendents to the +// array provided, but do not add the inventory object specified by +// id. There is no guaranteed order. Neither array will be erased +// before adding objects to it. Do not store a copy of the pointers +// collected - use them, and collect them again later if you need to +// reference the same objects. + +class LLAlwaysCollect : public LLInventoryCollectFunctor +{ +public: + virtual ~LLAlwaysCollect() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item) + { + return true; + } +}; + +void LLInventoryModel::collectDescendents(const LLUUID& id, + cat_array_t& cats, + item_array_t& items, + bool include_trash) +{ + LLAlwaysCollect always; + collectDescendentsIf(id, cats, items, include_trash, always); +} + +void LLInventoryModel::collectDescendentsIf(const LLUUID& id, + cat_array_t& cats, + item_array_t& items, + bool include_trash, + LLInventoryCollectFunctor& add) +{ + // Start with categories + if(!include_trash) + { + const LLUUID trash_id = findCategoryUUIDForType(LLFolderType::FT_TRASH); + if(trash_id.notNull() && (trash_id == id)) + return; + } + cat_array_t* cat_array = get_ptr_in_map(mParentChildCategoryTree, id); + if(cat_array) + { + S32 count = cat_array->size(); + for(S32 i = 0; i < count; ++i) + { + LLViewerInventoryCategory* cat = cat_array->at(i); + if(add(cat,NULL)) + { + cats.push_back(cat); + } + collectDescendentsIf(cat->getUUID(), cats, items, include_trash, add); + } + } + + LLViewerInventoryItem* item = NULL; + item_array_t* item_array = get_ptr_in_map(mParentChildItemTree, id); + + // Move onto items + if(item_array) + { + S32 count = item_array->size(); + for(S32 i = 0; i < count; ++i) + { + item = item_array->at(i); + if(add(NULL, item)) + { + items.push_back(item); + } + } + } +} + +void LLInventoryModel::addChangedMaskForLinks(const LLUUID& object_id, U32 mask) +{ + const LLInventoryObject *obj = getObject(object_id); + if (!obj || obj->getIsLinkType()) + return; + + LLInventoryModel::item_array_t item_array = collectLinksTo(object_id); + for (LLInventoryModel::item_array_t::iterator iter = item_array.begin(); + iter != item_array.end(); + iter++) + { + LLViewerInventoryItem *linked_item = (*iter); + addChangedMask(mask, linked_item->getUUID()); + }; +} + +const LLUUID& LLInventoryModel::getLinkedItemID(const LLUUID& object_id) const +{ + const LLInventoryItem *item = gInventory.getItem(object_id); + if (!item) + { + return object_id; + } + + // Find the base item in case this a link (if it's not a link, + // this will just be inv_item_id) + return item->getLinkedUUID(); +} + +LLViewerInventoryItem* LLInventoryModel::getLinkedItem(const LLUUID& object_id) const +{ + return object_id.notNull() ? getItem(getLinkedItemID(object_id)) : NULL; +} + +LLInventoryModel::item_array_t LLInventoryModel::collectLinksTo(const LLUUID& id) +{ + // Get item list via collectDescendents (slow!) + item_array_t items; + const LLInventoryObject *obj = getObject(id); + // FIXME - should be as in next line, but this is causing a + // stack-smashing crash of cause TBD... check in the REBUILD code. + //if (obj && obj->getIsLinkType()) + if (!obj || obj->getIsLinkType()) + return items; + + std::pair range = mBacklinkMMap.equal_range(id); + for (backlink_mmap_t::iterator it = range.first; it != range.second; ++it) + { + LLViewerInventoryItem *item = getItem(it->second); + if (item) + { + items.push_back(item); + } + } + + return items; +} + +bool LLInventoryModel::isInventoryUsable() const +{ + bool result = false; + if(gInventory.getRootFolderID().notNull() && mIsAgentInvUsable) + { + result = true; + } + return result; +} + +// Calling this method with an inventory item will either change an +// existing item with a matching item_id, or will add the item to the +// current inventory. +U32 LLInventoryModel::updateItem(const LLViewerInventoryItem* item, U32 mask) +{ + if(item->getUUID().isNull()) + { + return mask; + } + + if(!isInventoryUsable()) + { + LL_WARNS(LOG_INV) << "Inventory is broken." << LL_ENDL; + return mask; + } + + if (item->getType() == LLAssetType::AT_MESH) + { + return mask; + } + + LLPointer old_item = getItem(item->getUUID()); + LLPointer new_item; + if(old_item) + { + // We already have an old item, modify its values + new_item = old_item; + LLUUID old_parent_id = old_item->getParentUUID(); + LLUUID new_parent_id = item->getParentUUID(); + bool update_parent_on_server = false; + + if (new_parent_id.isNull() && !LLApp::isExiting()) + { + if (old_parent_id.isNull()) + { + // Item with null parent will end in random location and then in Lost&Found, + // either move to default folder as if it is new item or don't move at all + LL_WARNS(LOG_INV) << "Update attempts to reparent item " << item->getUUID() + << " to null folder. Moving to Lost&Found. Old item name: " << old_item->getName() + << ". New name: " << item->getName() + << "." << LL_ENDL; + new_parent_id = findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); + update_parent_on_server = true; + } + else + { + // Probably not the best way to handle this, we might encounter real case of 'lost&found' at some point + LL_WARNS(LOG_INV) << "Update attempts to reparent item " << item->getUUID() + << " to null folder. Old parent not null. Moving to old parent. Old item name: " << old_item->getName() + << ". New name: " << item->getName() + << "." << LL_ENDL; + new_parent_id = old_parent_id; + update_parent_on_server = true; + } + } + + if(old_parent_id != new_parent_id) + { + // need to update the parent-child tree + item_array_t* item_array; + item_array = get_ptr_in_map(mParentChildItemTree, old_parent_id); + if(item_array) + { + vector_replace_with_last(*item_array, old_item); + } + item_array = get_ptr_in_map(mParentChildItemTree, new_parent_id); + if(item_array) + { + if (update_parent_on_server) + { + LLInventoryModel::LLCategoryUpdate update(new_parent_id, 1); + gInventory.accountForUpdate(update); + } + item_array->push_back(old_item); + } + mask |= LLInventoryObserver::STRUCTURE; + } + if(old_item->getName() != item->getName()) + { + mask |= LLInventoryObserver::LABEL; + } + if (old_item->getPermissions() != item->getPermissions()) + { + mask |= LLInventoryObserver::INTERNAL; + } + old_item->copyViewerItem(item); + if (update_parent_on_server) + { + // Parent id at server is null, so update server even if item already is in the same folder + old_item->setParent(new_parent_id); + new_item->updateParentOnServer(false); + } + mask |= LLInventoryObserver::INTERNAL; + } + else + { + // Simply add this item + new_item = new LLViewerInventoryItem(item); + addItem(new_item); + + if(item->getParentUUID().isNull()) + { + const LLUUID category_id = findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(new_item->getType())); + new_item->setParent(category_id); + item_array_t* item_array = get_ptr_in_map(mParentChildItemTree, category_id); + if( item_array ) + { + LLInventoryModel::LLCategoryUpdate update(category_id, 1); + gInventory.accountForUpdate(update); + + // *FIX: bit of a hack to call update server from here... + new_item->updateParentOnServer(false); + item_array->push_back(new_item); + } + else + { + LL_WARNS(LOG_INV) << "Couldn't find parent-child item tree for " << new_item->getName() << LL_ENDL; + } + } + else + { + // *NOTE: The general scheme is that if every byte of the + // uuid is 0, except for the last one or two,the use the + // last two bytes of the parent id, and match that up + // against the type. For now, we're only worried about + // lost & found. + LLUUID parent_id = item->getParentUUID(); + if(parent_id == CATEGORIZE_LOST_AND_FOUND_ID) + { + parent_id = findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); + new_item->setParent(parent_id); + LLInventoryModel::update_list_t update; + LLInventoryModel::LLCategoryUpdate new_folder(parent_id, 1); + update.push_back(new_folder); + accountForUpdate(update); + + } + item_array_t* item_array = get_ptr_in_map(mParentChildItemTree, parent_id); + if(item_array) + { + item_array->push_back(new_item); + } + else + { + // Whoops! No such parent, make one. + LL_INFOS(LOG_INV) << "Lost item: " << new_item->getUUID() << " - " + << new_item->getName() << LL_ENDL; + parent_id = findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); + new_item->setParent(parent_id); + item_array = get_ptr_in_map(mParentChildItemTree, parent_id); + if(item_array) + { + LLInventoryModel::LLCategoryUpdate update(parent_id, 1); + gInventory.accountForUpdate(update); + // *FIX: bit of a hack to call update server from + // here... + new_item->updateParentOnServer(false); + item_array->push_back(new_item); + } + else + { + LL_WARNS(LOG_INV) << "Lost and found Not there!!" << LL_ENDL; + } + } + } + mask |= LLInventoryObserver::ADD; + } + if(new_item->getType() == LLAssetType::AT_CALLINGCARD) + { + mask |= LLInventoryObserver::CALLING_CARD; + // Handle user created calling cards. + // Target ID is stored in the description field of the card. + LLUUID id; + std::string desc = new_item->getDescription(); + bool isId = desc.empty() ? false : id.set(desc, false); + if (isId) + { + // Valid UUID; set the item UUID and rename it + new_item->setCreator(id); + LLAvatarName av_name; + + if (LLAvatarNameCache::get(id, &av_name)) + { + new_item->rename(av_name.getUserName()); + mask |= LLInventoryObserver::LABEL; + } + else + { + // Fetch the current name + LLAvatarNameCache::get(id, + boost::bind(&LLViewerInventoryItem::onCallingCardNameLookup, new_item.get(), + _1, _2)); + } + + } + } + else if (new_item->getType() == LLAssetType::AT_GESTURE) + { + mask |= LLInventoryObserver::GESTURE; + } + addChangedMask(mask, new_item->getUUID()); + return mask; +} + +LLInventoryModel::cat_array_t* LLInventoryModel::getUnlockedCatArray(const LLUUID& id) +{ + cat_array_t* cat_array = get_ptr_in_map(mParentChildCategoryTree, id); + if (cat_array) + { + llassert_always(!mCategoryLock[id]); + } + return cat_array; +} + +LLInventoryModel::item_array_t* LLInventoryModel::getUnlockedItemArray(const LLUUID& id) +{ + item_array_t* item_array = get_ptr_in_map(mParentChildItemTree, id); + if (item_array) + { + llassert_always(!mItemLock[id]); + } + return item_array; +} + +// Calling this method with an inventory category will either change +// an existing item with the matching id, or it will add the category. +void LLInventoryModel::updateCategory(const LLViewerInventoryCategory* cat, U32 mask) +{ + if(!cat || cat->getUUID().isNull()) + { + return; + } + + if(!isInventoryUsable()) + { + LL_WARNS(LOG_INV) << "Inventory is broken." << LL_ENDL; + return; + } + + LLPointer old_cat = getCategory(cat->getUUID()); + if(old_cat) + { + // We already have an old category, modify its values + LLUUID old_parent_id = old_cat->getParentUUID(); + LLUUID new_parent_id = cat->getParentUUID(); + if(old_parent_id != new_parent_id) + { + // need to update the parent-child tree + cat_array_t* cat_array; + cat_array = getUnlockedCatArray(old_parent_id); + if(cat_array) + { + vector_replace_with_last(*cat_array, old_cat); + } + cat_array = getUnlockedCatArray(new_parent_id); + if(cat_array) + { + cat_array->push_back(old_cat); + } + mask |= LLInventoryObserver::STRUCTURE; + mask |= LLInventoryObserver::INTERNAL; + } + if(old_cat->getName() != cat->getName()) + { + mask |= LLInventoryObserver::LABEL; + } + // Under marketplace, category labels are quite complex and need extra upate + const LLUUID marketplace_id = findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + if (marketplace_id.notNull() && isObjectDescendentOf(cat->getUUID(), marketplace_id)) + { + mask |= LLInventoryObserver::LABEL; + } + old_cat->copyViewerCategory(cat); + addChangedMask(mask, cat->getUUID()); + } + else + { + // add this category + LLPointer new_cat = new LLViewerInventoryCategory(cat->getOwnerID()); + new_cat->copyViewerCategory(cat); + addCategory(new_cat); + + // make sure this category is correctly referenced by its parent. + cat_array_t* cat_array; + cat_array = getUnlockedCatArray(cat->getParentUUID()); + if(cat_array) + { + cat_array->push_back(new_cat); + } + + // make space in the tree for this category's children. + llassert_always(!mCategoryLock[new_cat->getUUID()]); + llassert_always(!mItemLock[new_cat->getUUID()]); + cat_array_t* catsp = new cat_array_t; + item_array_t* itemsp = new item_array_t; + mParentChildCategoryTree[new_cat->getUUID()] = catsp; + mParentChildItemTree[new_cat->getUUID()] = itemsp; + mask |= LLInventoryObserver::ADD; + addChangedMask(mask, cat->getUUID()); + } +} + +void LLInventoryModel::moveObject(const LLUUID& object_id, const LLUUID& cat_id) +{ + LL_DEBUGS(LOG_INV) << "LLInventoryModel::moveObject()" << LL_ENDL; + if(!isInventoryUsable()) + { + LL_WARNS(LOG_INV) << "Inventory is broken." << LL_ENDL; + return; + } + + if((object_id == cat_id) || !is_in_map(mCategoryMap, cat_id)) + { + LL_WARNS(LOG_INV) << "Could not move inventory object " << object_id << " to " + << cat_id << LL_ENDL; + return; + } + LLPointer cat = getCategory(object_id); + if(cat && (cat->getParentUUID() != cat_id)) + { + LL_DEBUGS(LOG_INV) << "Move category '" << make_path(cat) << "' to '" << make_inventory_path(cat_id) << "'" << LL_ENDL; + cat_array_t* cat_array; + cat_array = getUnlockedCatArray(cat->getParentUUID()); + if(cat_array) vector_replace_with_last(*cat_array, cat); + cat_array = getUnlockedCatArray(cat_id); + cat->setParent(cat_id); + if(cat_array) cat_array->push_back(cat); + addChangedMask(LLInventoryObserver::STRUCTURE, object_id); + return; + } + LLPointer item = getItem(object_id); + if(item && (item->getParentUUID() != cat_id)) + { + LL_DEBUGS(LOG_INV) << "Move item '" << make_path(item) << "' to '" << make_inventory_path(cat_id) << "'" << LL_ENDL; + item_array_t* item_array; + item_array = getUnlockedItemArray(item->getParentUUID()); + if(item_array) vector_replace_with_last(*item_array, item); + item_array = getUnlockedItemArray(cat_id); + item->setParent(cat_id); + if(item_array) item_array->push_back(item); + addChangedMask(LLInventoryObserver::STRUCTURE, object_id); + return; + } +} + +// Migrated from llinventoryfunctions +void LLInventoryModel::changeItemParent(LLViewerInventoryItem* item, + const LLUUID& new_parent_id, + bool restamp) +{ + if (item->getParentUUID() == new_parent_id) + { + LL_DEBUGS(LOG_INV) << make_info(item) << " is already in folder " << make_inventory_info(new_parent_id) << LL_ENDL; + } + else + { + LL_INFOS(LOG_INV) << "Move item " << make_info(item) + << " from " << make_inventory_info(item->getParentUUID()) + << " to " << make_inventory_info(new_parent_id) << LL_ENDL; + + LLInventoryModel::LLCategoryUpdate old_folder(item->getParentUUID(),-1); + accountForUpdate(old_folder); + LLInventoryModel::LLCategoryUpdate new_folder(new_parent_id, 1, false); + accountForUpdate(new_folder); + + LLPointer new_item = new LLViewerInventoryItem(item); + new_item->setParent(new_parent_id); + new_item->updateParentOnServer(restamp); + updateItem(new_item); + notifyObservers(); + } +} + +// Migrated from llinventoryfunctions +void LLInventoryModel::changeCategoryParent(LLViewerInventoryCategory* cat, + const LLUUID& new_parent_id, + bool restamp) +{ + if (!cat) + { + return; + } + + // Can't move a folder into a child of itself. + if (isObjectDescendentOf(new_parent_id, cat->getUUID())) + { + return; + } + + LL_INFOS(LOG_INV) << "Move category " << make_info(cat) + << " from " << make_inventory_info(cat->getParentUUID()) + << " to " << make_inventory_info(new_parent_id) << LL_ENDL; + + LLInventoryModel::LLCategoryUpdate old_folder(cat->getParentUUID(), -1); + accountForUpdate(old_folder); + LLInventoryModel::LLCategoryUpdate new_folder(new_parent_id, 1, false); + accountForUpdate(new_folder); + + LLPointer new_cat = new LLViewerInventoryCategory(cat); + new_cat->setParent(new_parent_id); + new_cat->updateParentOnServer(restamp); + updateCategory(new_cat); + notifyObservers(); +} + +void LLInventoryModel::rebuildBrockenLinks() +{ + // make sure we aren't adding expensive Rebuild to anything else. + notifyObservers(); + + for (const broken_links_t::value_type &link_list : mPossiblyBrockenLinks) + { + for (const LLUUID& link_id : link_list.second) + { + addChangedMask(LLInventoryObserver::REBUILD , link_id); + } + } + for (const LLUUID& link_id : mLinksRebuildList) + { + addChangedMask(LLInventoryObserver::REBUILD , link_id); + } + mPossiblyBrockenLinks.clear(); + mLinksRebuildList.clear(); + notifyObservers(); +} + +// Does not appear to be used currently. +void LLInventoryModel::onItemUpdated(const LLUUID& item_id, const LLSD& updates, bool update_parent_version) +{ + U32 mask = LLInventoryObserver::NONE; + + LLPointer item = gInventory.getItem(item_id); + LL_DEBUGS(LOG_INV) << "item_id: [" << item_id << "] name " << (item ? item->getName() : "(NOT FOUND)") << LL_ENDL; + if(item) + { + for (LLSD::map_const_iterator it = updates.beginMap(); + it != updates.endMap(); ++it) + { + if (it->first == "name") + { + LL_INFOS(LOG_INV) << "Updating name from " << item->getName() << " to " << it->second.asString() << LL_ENDL; + item->rename(it->second.asString()); + mask |= LLInventoryObserver::LABEL; + } + else if (it->first == "desc") + { + LL_INFOS(LOG_INV) << "Updating description from " << item->getActualDescription() + << " to " << it->second.asString() << LL_ENDL; + item->setDescription(it->second.asString()); + } + else + { + LL_ERRS(LOG_INV) << "unhandled updates for field: " << it->first << LL_ENDL; + } + } + mask |= LLInventoryObserver::INTERNAL; + addChangedMask(mask, item->getUUID()); + if (update_parent_version) + { + // Descendent count is unchanged, but folder version incremented. + LLInventoryModel::LLCategoryUpdate up(item->getParentUUID(), 0); + accountForUpdate(up); + } + notifyObservers(); // do we want to be able to make this optional? + } +} + +// Not used? +void LLInventoryModel::onCategoryUpdated(const LLUUID& cat_id, const LLSD& updates) +{ + U32 mask = LLInventoryObserver::NONE; + + LLPointer cat = gInventory.getCategory(cat_id); + LL_DEBUGS(LOG_INV) << "cat_id: [" << cat_id << "] name " << (cat ? cat->getName() : "(NOT FOUND)") << LL_ENDL; + if(cat) + { + for (LLSD::map_const_iterator it = updates.beginMap(); + it != updates.endMap(); ++it) + { + if (it->first == "name") + { + LL_INFOS(LOG_INV) << "Updating name from " << cat->getName() << " to " << it->second.asString() << LL_ENDL; + cat->rename(it->second.asString()); + mask |= LLInventoryObserver::LABEL; + } + else + { + LL_ERRS(LOG_INV) << "unhandled updates for field: " << it->first << LL_ENDL; + } + } + mask |= LLInventoryObserver::INTERNAL; + addChangedMask(mask, cat->getUUID()); + notifyObservers(); // do we want to be able to make this optional? + } +} + +// Update model after descendents have been purged. +void LLInventoryModel::onDescendentsPurgedFromServer(const LLUUID& object_id, bool fix_broken_links) +{ + LLPointer cat = getCategory(object_id); + if (cat.notNull()) + { + // do the cache accounting + S32 descendents = cat->getDescendentCount(); + if(descendents > 0) + { + LLInventoryModel::LLCategoryUpdate up(object_id, -descendents); + accountForUpdate(up); + } + + // we know that descendent count is 0, however since the + // accounting may actually not do an update, we should force + // it here. + cat->setDescendentCount(0); + + // unceremoniously remove anything we have locally stored. + LLInventoryModel::cat_array_t categories; + LLInventoryModel::item_array_t items; + collectDescendents(object_id, + categories, + items, + LLInventoryModel::INCLUDE_TRASH); + S32 count = items.size(); + + LLUUID uu_id; + for(S32 i = 0; i < count; ++i) + { + uu_id = items.at(i)->getUUID(); + + // This check prevents the deletion of a previously deleted item. + // This is necessary because deletion is not done in a hierarchical + // order. The current item may have been already deleted as a child + // of its deleted parent. + if (getItem(uu_id)) + { + deleteObject(uu_id, fix_broken_links); + } + } + + count = categories.size(); + // Slightly kludgy way to make sure categories are removed + // only after their child categories have gone away. + + // FIXME: Would probably make more sense to have this whole + // descendent-clearing thing be a post-order recursive + // function to get the leaf-up behavior automatically. + S32 deleted_count; + S32 total_deleted_count = 0; + do + { + deleted_count = 0; + for(S32 i = 0; i < count; ++i) + { + uu_id = categories.at(i)->getUUID(); + if (getCategory(uu_id)) + { + cat_array_t* cat_list = getUnlockedCatArray(uu_id); + if (!cat_list || (cat_list->size() == 0)) + { + deleteObject(uu_id, fix_broken_links); + deleted_count++; + } + } + } + total_deleted_count += deleted_count; + } + while (deleted_count > 0); + if (total_deleted_count != count) + { + LL_WARNS(LOG_INV) << "Unexpected count of categories deleted, got " + << total_deleted_count << " expected " << count << LL_ENDL; + } + //gInventory.validate(); + } +} + +// Update model after an item is confirmed as removed from +// server. Works for categories or items. +void LLInventoryModel::onObjectDeletedFromServer(const LLUUID& object_id, bool fix_broken_links, bool update_parent_version, bool do_notify_observers) +{ + LLPointer obj = getObject(object_id); + if(obj) + { + if (getCategory(object_id)) + { + // For category, need to delete/update all children first. + onDescendentsPurgedFromServer(object_id, fix_broken_links); + } + + + // From item/cat removeFromServer() + if (update_parent_version) + { + LLInventoryModel::LLCategoryUpdate up(obj->getParentUUID(), -1); + accountForUpdate(up); + } + + // From purgeObject() + LLViewerInventoryItem *item = getItem(object_id); + if (item && (item->getType() != LLAssetType::AT_LSL_TEXT)) + { + LLPreview::hide(object_id, true); + } + deleteObject(object_id, fix_broken_links, do_notify_observers); + } +} + + +// Delete a particular inventory object by ID. +void LLInventoryModel::deleteObject(const LLUUID& id, bool fix_broken_links, bool do_notify_observers) +{ + LL_DEBUGS(LOG_INV) << "LLInventoryModel::deleteObject()" << LL_ENDL; + LLPointer obj = getObject(id); + if (!obj) + { + LL_WARNS(LOG_INV) << "Deleting non-existent object [ id: " << id << " ] " << LL_ENDL; + return; + } + + //collect the links before removing the item from mItemMap + LLInventoryModel::item_array_t links = collectLinksTo(id); + + LL_DEBUGS(LOG_INV) << "Deleting inventory object " << id << LL_ENDL; + mLastItem = NULL; + LLUUID parent_id = obj->getParentUUID(); + mCategoryMap.erase(id); + mItemMap.erase(id); + //mInventory.erase(id); + item_array_t* item_list = getUnlockedItemArray(parent_id); + if(item_list) + { + LLPointer item = (LLViewerInventoryItem*)((LLInventoryObject*)obj); + vector_replace_with_last(*item_list, item); + } + cat_array_t* cat_list = getUnlockedCatArray(parent_id); + if(cat_list) + { + LLPointer cat = (LLViewerInventoryCategory*)((LLInventoryObject*)obj); + vector_replace_with_last(*cat_list, cat); + } + + // Note : We need to tell the inventory observers that those things are going to be deleted *before* the tree is cleared or they won't know what to delete (in views and view models) + addChangedMask(LLInventoryObserver::REMOVE, id); + gInventory.notifyObservers(); + + item_list = getUnlockedItemArray(id); + if(item_list) + { + if (item_list->size()) + { + LL_WARNS(LOG_INV) << "Deleting cat " << id << " while it still has child items" << LL_ENDL; + } + delete item_list; + mParentChildItemTree.erase(id); + } + cat_list = getUnlockedCatArray(id); + if(cat_list) + { + if (cat_list->size()) + { + LL_WARNS(LOG_INV) << "Deleting cat " << id << " while it still has child cats" << LL_ENDL; + } + delete cat_list; + mParentChildCategoryTree.erase(id); + } + addChangedMask(LLInventoryObserver::REMOVE, id); + + bool is_link_type = obj->getIsLinkType(); + if (is_link_type) + { + removeBacklinkInfo(obj->getUUID(), obj->getLinkedUUID()); + } + + // Can't have links to links, so there's no need for this update + // if the item removed is a link. Can also skip if source of the + // update is getting broken link info separately. + if (fix_broken_links && !is_link_type) + { + rebuildLinkItems(links); + } + obj = nullptr; // delete obj + if (do_notify_observers) + { + notifyObservers(); + } +} + +void LLInventoryModel::rebuildLinkItems(LLInventoryModel::item_array_t& items) +{ + // REBUILD is expensive, so clear the current change list first else + // everything else on the changelist will also get rebuilt. + if (items.size() > 0) + { + notifyObservers(); + for (LLInventoryModel::item_array_t::const_iterator iter = items.begin(); + iter != items.end(); + iter++) + { + const LLViewerInventoryItem *linked_item = (*iter); + if (linked_item) + { + addChangedMask(LLInventoryObserver::REBUILD, linked_item->getUUID()); + } + } + notifyObservers(); + } +} + +// Add/remove an observer. If the observer is destroyed, be sure to +// remove it. +void LLInventoryModel::addObserver(LLInventoryObserver* observer) +{ + mObservers.insert(observer); +} + +void LLInventoryModel::removeObserver(LLInventoryObserver* observer) +{ + mObservers.erase(observer); +} + +bool LLInventoryModel::containsObserver(LLInventoryObserver* observer) const +{ + return mObservers.find(observer) != mObservers.end(); +} + +void LLInventoryModel::idleNotifyObservers() +{ + // *FIX: Think I want this conditional or moved elsewhere... + handleResponses(true); + + if (mLinksRebuildList.size() > 0) + { + if (mModifyMask != LLInventoryObserver::NONE || (mChangedItemIDs.size() != 0)) + { + notifyObservers(); + } + for (const LLUUID& link_id : mLinksRebuildList) + { + addChangedMask(LLInventoryObserver::REBUILD , link_id); + } + mLinksRebuildList.clear(); + notifyObservers(); + } + + if (mModifyMask == LLInventoryObserver::NONE && (mChangedItemIDs.size() == 0)) + { + return; + } + notifyObservers(); +} + +// Call this method when it's time to update everyone on a new state. +void LLInventoryModel::notifyObservers() +{ + if (mIsNotifyObservers) + { + // Within notifyObservers, something called notifyObservers + // again. This type of recursion is unsafe because it causes items to be + // processed twice, and this can easily lead to infinite loops. + LL_WARNS(LOG_INV) << "Call was made to notifyObservers within notifyObservers!" << LL_ENDL; + return; + } + + mIsNotifyObservers = true; + for (observer_list_t::iterator iter = mObservers.begin(); + iter != mObservers.end(); ) + { + LLInventoryObserver* observer = *iter; + observer->changed(mModifyMask); + + // safe way to increment since changed may delete entries! (@!##%@!@&*!) + iter = mObservers.upper_bound(observer); + } + + // If there were any changes that arrived during notifyObservers, + // shedule them for next loop + mModifyMask = mModifyMaskBacklog; + mChangedItemIDs.clear(); + mChangedItemIDs.insert(mChangedItemIDsBacklog.begin(), mChangedItemIDsBacklog.end()); + mAddedItemIDs.clear(); + mAddedItemIDs.insert(mAddedItemIDsBacklog.begin(), mAddedItemIDsBacklog.end()); + + mModifyMaskBacklog = LLInventoryObserver::NONE; + mChangedItemIDsBacklog.clear(); + mAddedItemIDsBacklog.clear(); + + mIsNotifyObservers = false; +} + +// store flag for change +// and id of object change applies to +void LLInventoryModel::addChangedMask(U32 mask, const LLUUID& referent) +{ + if (mIsNotifyObservers) + { + // Something marked an item for change within a call to notifyObservers + // (which is in the process of processing the list of items marked for change). + // This means the change will have to be processed later. + // It's preferable for this not to happen, but it's not an issue unless code + // specifically wants to notifyObservers immediately (changes won't happen untill later) + LL_WARNS(LOG_INV) << "Adding changed mask within notify observers! Change's processing will be performed on idle." << LL_ENDL; + LLViewerInventoryItem *item = getItem(referent); + if (item) + { + LL_WARNS(LOG_INV) << "Item " << item->getName() << LL_ENDL; + } + else + { + LLViewerInventoryCategory *cat = getCategory(referent); + if (cat) + { + LL_WARNS(LOG_INV) << "Category " << cat->getName() << LL_ENDL; + } + } + } + + if (mIsNotifyObservers) + { + mModifyMaskBacklog |= mask; + } + else + { + mModifyMask |= mask; + } + + bool needs_update = false; + if (referent.notNull()) + { + if (mIsNotifyObservers) + { + needs_update = mChangedItemIDsBacklog.find(referent) == mChangedItemIDsBacklog.end(); + } + else + { + needs_update = mChangedItemIDs.find(referent) == mChangedItemIDs.end(); + } + } + + if (needs_update) + { + if (mIsNotifyObservers) + { + mChangedItemIDsBacklog.insert(referent); + } + else + { + mChangedItemIDs.insert(referent); + } + + if (mask != LLInventoryObserver::LABEL) + { + // Fix me: From DD-81, probably shouldn't be here, instead + // should be somewhere in an observer or in + // LLMarketplaceInventoryObserver::onIdleProcessQueue + update_marketplace_category(referent, false); + } + + if (mask & LLInventoryObserver::ADD) + { + if (mIsNotifyObservers) + { + mAddedItemIDsBacklog.insert(referent); + } + else + { + mAddedItemIDs.insert(referent); + } + } + + // Update all linked items. Starting with just LABEL because I'm + // not sure what else might need to be accounted for this. + if (mask & LLInventoryObserver::LABEL) + { + addChangedMaskForLinks(referent, LLInventoryObserver::LABEL); + } + } +} + +bool LLInventoryModel::fetchDescendentsOf(const LLUUID& folder_id) const +{ + if(folder_id.isNull()) + { + LL_WARNS(LOG_INV) << "Calling fetch descendents on NULL folder id!" << LL_ENDL; + return false; + } + LLViewerInventoryCategory* cat = getCategory(folder_id); + if(!cat) + { + LL_WARNS(LOG_INV) << "Asked to fetch descendents of non-existent folder: " + << folder_id << LL_ENDL; + return false; + } + //S32 known_descendents = 0; + ///cat_array_t* categories = get_ptr_in_map(mParentChildCategoryTree, folder_id); + //item_array_t* items = get_ptr_in_map(mParentChildItemTree, folder_id); + //if(categories) + //{ + // known_descendents += categories->size(); + //} + //if(items) + //{ + // known_descendents += items->size(); + //} + return cat->fetch(); +} + +//static +std::string LLInventoryModel::getInvCacheAddres(const LLUUID& owner_id) +{ + std::string inventory_addr; + std::string owner_id_str; + owner_id.toString(owner_id_str); + std::string path(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, owner_id_str)); + if (LLGridManager::getInstance()->isInProductionGrid()) + { + inventory_addr = llformat(PRODUCTION_CACHE_FORMAT_STRING, path.c_str()); + } + else + { + // NOTE: The inventory cache filenames now include the grid name. + // Add controls against directory traversal or problematic pathname lengths + // if your viewer uses grid names from an untrusted source. + const std::string& grid_id_str = LLGridManager::getInstance()->getGridId(); + const std::string& grid_id_lower = utf8str_tolower(grid_id_str); + inventory_addr = llformat(GRID_CACHE_FORMAT_STRING, path.c_str(), grid_id_lower.c_str()); + } + return inventory_addr; +} + +void LLInventoryModel::cache( + const LLUUID& parent_folder_id, + const LLUUID& agent_id) +{ + LL_DEBUGS(LOG_INV) << "Caching " << parent_folder_id << " for " << agent_id + << LL_ENDL; + LLViewerInventoryCategory* root_cat = getCategory(parent_folder_id); + if(!root_cat) return; + cat_array_t categories; + categories.push_back(root_cat); + item_array_t items; + + LLCanCache can_cache(this); + can_cache(root_cat, NULL); + collectDescendentsIf( + parent_folder_id, + categories, + items, + INCLUDE_TRASH, + can_cache); + // Use temporary file to avoid potential conflicts with other + // instances (even a 'read only' instance unzips into a file) + std::string temp_file = gDirUtilp->getTempFilename(); + saveToFile(temp_file, categories, items); + std::string gzip_filename = getInvCacheAddres(agent_id); + gzip_filename.append(".gz"); + if(gzip_file(temp_file, gzip_filename)) + { + LL_DEBUGS(LOG_INV) << "Successfully compressed " << temp_file << " to " << gzip_filename << LL_ENDL; + LLFile::remove(temp_file); + } + else + { + LL_WARNS(LOG_INV) << "Unable to compress " << temp_file << " into " << gzip_filename << LL_ENDL; + } +} + + +void LLInventoryModel::addCategory(LLViewerInventoryCategory* category) +{ + //LL_INFOS(LOG_INV) << "LLInventoryModel::addCategory()" << LL_ENDL; + if(category) + { + // We aren't displaying the Meshes folder + if (category->mPreferredType == LLFolderType::FT_MESH) + { + return; + } + + // try to localize default names first. See EXT-8319, EXT-7051. + category->localizeName(); + + // Insert category uniquely into the map + mCategoryMap[category->getUUID()] = category; // LLPointer will deref and delete the old one + //mInventory[category->getUUID()] = category; + } +} + +bool LLInventoryModel::hasBacklinkInfo(const LLUUID& link_id, const LLUUID& target_id) const +{ + std::pair range; + range = mBacklinkMMap.equal_range(target_id); + for (backlink_mmap_t::const_iterator it = range.first; it != range.second; ++it) + { + if (it->second == link_id) + { + return true; + } + } + return false; +} + +void LLInventoryModel::addBacklinkInfo(const LLUUID& link_id, const LLUUID& target_id) +{ + if (!hasBacklinkInfo(link_id, target_id)) + { + mBacklinkMMap.insert(std::make_pair(target_id, link_id)); + } +} + +void LLInventoryModel::removeBacklinkInfo(const LLUUID& link_id, const LLUUID& target_id) +{ + std::pair range; + range = mBacklinkMMap.equal_range(target_id); + for (backlink_mmap_t::iterator it = range.first; it != range.second; ) + { + if (it->second == link_id) + { + backlink_mmap_t::iterator delete_it = it; // iterator will be invalidated by erase. + ++it; + mBacklinkMMap.erase(delete_it); + } + else + { + ++it; + } + } +} + +void LLInventoryModel::addItem(LLViewerInventoryItem* item) +{ + llassert(item); + if(item) + { + if (item->getType() <= LLAssetType::AT_NONE) + { + LL_WARNS(LOG_INV) << "Got bad asset type for item [ name: " << item->getName() + << " type: " << item->getType() + << " inv-type: " << item->getInventoryType() << " ], ignoring." << LL_ENDL; + return; + } + + if (LLAssetType::lookup(item->getType()) == LLAssetType::BADLOOKUP) + { + if (item->getType() >= LLAssetType::AT_COUNT) + { + // Not yet supported. + LL_DEBUGS(LOG_INV) << "Got unknown asset type for item [ name: " << item->getName() + << " type: " << item->getType() + << " inv-type: " << item->getInventoryType() << " ]." << LL_ENDL; + } + else + { + LL_WARNS(LOG_INV) << "Got unknown asset type for item [ name: " << item->getName() + << " type: " << item->getType() + << " inv-type: " << item->getInventoryType() << " ]." << LL_ENDL; + } + } + + // This condition means that we tried to add a link without the baseobj being in memory. + // The item will show up as a broken link. + if (item->getIsBrokenLink()) + { + if (item->getAssetUUID().notNull() + && LLInventoryModelBackgroundFetch::getInstance()->folderFetchActive()) + { + // Schedule this link for a recheck as inventory gets loaded + // Todo: expand to cover not just an initial fetch + mPossiblyBrockenLinks[item->getAssetUUID()].insert(item->getUUID()); + + // Do a blank rebuild of links once fetch is done + if (!mBulkFecthCallbackSlot.connected()) + { + // Links might take a while to update this way, and there + // might be a lot of them. A better option might be to check + // links periodically with final check on fetch completion. + mBulkFecthCallbackSlot = + LLInventoryModelBackgroundFetch::getInstance()->setFetchCompletionCallback( + [this]() + { + // rebuild is just in case, primary purpose is to wipe + // the list since we won't be getting anything 'new' + // see mLinksRebuildList + rebuildBrockenLinks(); + mBulkFecthCallbackSlot.disconnect(); + }); + } + LL_DEBUGS(LOG_INV) << "Scheduling a link to be rebuilt later [ name: " << item->getName() + << " itemID: " << item->getUUID() + << " assetID: " << item->getAssetUUID() << " ) parent: " << item->getParentUUID() << LL_ENDL; + + } + else + { + LL_INFOS(LOG_INV) << "Adding broken link [ name: " << item->getName() + << " itemID: " << item->getUUID() + << " assetID: " << item->getAssetUUID() << " ) parent: " << item->getParentUUID() << LL_ENDL; + } + } + if (!mPossiblyBrockenLinks.empty()) + { + // check if we are waiting for this item + broken_links_t::iterator iter = mPossiblyBrockenLinks.find(item->getUUID()); + if (iter != mPossiblyBrockenLinks.end()) + { + mLinksRebuildList.insert(iter->second.begin() , iter->second.end()); + mPossiblyBrockenLinks.erase(iter); + } + } + if (item->getIsLinkType()) + { + // Add back-link from linked-to UUID. + const LLUUID& link_id = item->getUUID(); + const LLUUID& target_id = item->getLinkedUUID(); + addBacklinkInfo(link_id, target_id); + } + mItemMap[item->getUUID()] = item; + } +} + +// Empty the entire contents +void LLInventoryModel::empty() +{ +// LL_INFOS(LOG_INV) << "LLInventoryModel::empty()" << LL_ENDL; + std::for_each( + mParentChildCategoryTree.begin(), + mParentChildCategoryTree.end(), + DeletePairedPointer()); + mParentChildCategoryTree.clear(); + std::for_each( + mParentChildItemTree.begin(), + mParentChildItemTree.end(), + DeletePairedPointer()); + mParentChildItemTree.clear(); + mBacklinkMMap.clear(); // forget all backlink information. + mCategoryMap.clear(); // remove all references (should delete entries) + mItemMap.clear(); // remove all references (should delete entries) + mLastItem = NULL; + //mInventory.clear(); +} + +void LLInventoryModel::accountForUpdate(const LLCategoryUpdate& update) const +{ + LLViewerInventoryCategory* cat = getCategory(update.mCategoryID); + if(cat) + { + S32 version = cat->getVersion(); + if(version != LLViewerInventoryCategory::VERSION_UNKNOWN) + { + S32 descendents_server = cat->getDescendentCount(); + S32 descendents_actual = cat->getViewerDescendentCount(); + if(descendents_server == descendents_actual) + { + descendents_actual += update.mDescendentDelta; + cat->setDescendentCount(descendents_actual); + if (update.mChangeVersion) + { + cat->setVersion(++version); + } + LL_DEBUGS(LOG_INV) << "accounted: '" << cat->getName() << "' " + << version << " with " << descendents_actual + << " descendents." << LL_ENDL; + } + else + { + // Error condition, this means that the category did not register that + // it got new descendents (perhaps because it is still being loaded) + // which means its descendent count will be wrong. + LL_WARNS(LOG_INV) << "Accounting failed for '" << cat->getName() << "' version:" + << version << " due to mismatched descendent count: server == " + << descendents_server << ", viewer == " << descendents_actual << LL_ENDL; + } + } + else + { + LL_WARNS(LOG_INV) << "Accounting failed for '" << cat->getName() << "' version: unknown (" + << version << ")" << LL_ENDL; + } + } + else + { + LL_WARNS(LOG_INV) << "No category found for update " << update.mCategoryID << LL_ENDL; + } +} + +void LLInventoryModel::accountForUpdate( + const LLInventoryModel::update_list_t& update) const +{ + update_list_t::const_iterator it = update.begin(); + update_list_t::const_iterator end = update.end(); + for(; it != end; ++it) + { + accountForUpdate(*it); + } +} + +void LLInventoryModel::accountForUpdate( + const LLInventoryModel::update_map_t& update) const +{ + LLCategoryUpdate up; + update_map_t::const_iterator it = update.begin(); + update_map_t::const_iterator end = update.end(); + for(; it != end; ++it) + { + up.mCategoryID = (*it).first; + up.mDescendentDelta = (*it).second.mValue; + accountForUpdate(up); + } +} + +LLInventoryModel::EHasChildren LLInventoryModel::categoryHasChildren( + const LLUUID& cat_id) const +{ + LLViewerInventoryCategory* cat = getCategory(cat_id); + if(!cat) return CHILDREN_NO; + if(cat->getDescendentCount() > 0) + { + return CHILDREN_YES; + } + if(cat->getDescendentCount() == 0) + { + return CHILDREN_NO; + } + if((cat->getDescendentCount() == LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN) + || (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN)) + { + return CHILDREN_MAYBE; + } + + // Shouldn't have to run this, but who knows. + parent_cat_map_t::const_iterator cat_it = mParentChildCategoryTree.find(cat->getUUID()); + if (cat_it != mParentChildCategoryTree.end() && cat_it->second->size() > 0) + { + return CHILDREN_YES; + } + parent_item_map_t::const_iterator item_it = mParentChildItemTree.find(cat->getUUID()); + if (item_it != mParentChildItemTree.end() && item_it->second->size() > 0) + { + return CHILDREN_YES; + } + + return CHILDREN_NO; +} + +bool LLInventoryModel::isCategoryComplete(const LLUUID& cat_id) const +{ + LLViewerInventoryCategory* cat = getCategory(cat_id); + if(cat && (cat->getVersion()!=LLViewerInventoryCategory::VERSION_UNKNOWN)) + { + S32 descendents_server = cat->getDescendentCount(); + S32 descendents_actual = cat->getViewerDescendentCount(); + if(descendents_server == descendents_actual) + { + return true; + } + } + return false; +} + +bool LLInventoryModel::loadSkeleton( + const LLSD& options, + const LLUUID& owner_id) +{ + LL_PROFILE_ZONE_SCOPED; + LL_DEBUGS(LOG_INV) << "importing inventory skeleton for " << owner_id << LL_ENDL; + + typedef std::set, InventoryIDPtrLess> cat_set_t; + cat_set_t temp_cats; + bool rv = true; + + for(LLSD::array_const_iterator it = options.beginArray(), + end = options.endArray(); it != end; ++it) + { + LLSD name = (*it)["name"]; + LLSD folder_id = (*it)["folder_id"]; + LLSD parent_id = (*it)["parent_id"]; + LLSD version = (*it)["version"]; + if(name.isDefined() + && folder_id.isDefined() + && parent_id.isDefined() + && version.isDefined() + && folder_id.asUUID().notNull() // if an id is null, it locks the viewer. + ) + { + LLPointer cat = new LLViewerInventoryCategory(owner_id); + cat->rename(name.asString()); + cat->setUUID(folder_id.asUUID()); + cat->setParent(parent_id.asUUID()); + + LLFolderType::EType preferred_type = LLFolderType::FT_NONE; + LLSD type_default = (*it)["type_default"]; + if(type_default.isDefined()) + { + preferred_type = (LLFolderType::EType)type_default.asInteger(); + } + cat->setPreferredType(preferred_type); + cat->setVersion(version.asInteger()); + temp_cats.insert(cat); + } + else + { + LL_WARNS(LOG_INV) << "Unable to import near " << name.asString() << LL_ENDL; + rv = false; + } + } + + S32 cached_category_count = 0; + S32 cached_item_count = 0; + if(!temp_cats.empty()) + { + update_map_t child_counts; + cat_array_t categories; + item_array_t items; + changed_items_t categories_to_update; + item_array_t possible_broken_links; + cat_set_t invalid_categories; // Used to mark categories that weren't successfully loaded. + std::string inventory_filename = getInvCacheAddres(owner_id); + const S32 NO_VERSION = LLViewerInventoryCategory::VERSION_UNKNOWN; + std::string gzip_filename(inventory_filename); + gzip_filename.append(".gz"); + LLFILE* fp = LLFile::fopen(gzip_filename, "rb"); + bool remove_inventory_file = false; + if (LLAppViewer::instance()->isSecondInstance()) + { + // Safeguard viewer against trying to unpack file twice + // ex: user logs into two accounts simultaneously, so two + // viewers are trying to unpack library into same file + // + // Would be better to do it in gunzip_file, but it doesn't + // have access to llfilesystem + inventory_filename = gDirUtilp->getTempFilename(); + remove_inventory_file = true; + } + if(fp) + { + fclose(fp); + fp = NULL; + if(gunzip_file(gzip_filename, inventory_filename)) + { + // we only want to remove the inventory file if it was + // gzipped before we loaded, and we successfully + // gunziped it. + remove_inventory_file = true; + } + else + { + LL_INFOS(LOG_INV) << "Unable to gunzip " << gzip_filename << LL_ENDL; + } + } + bool is_cache_obsolete = false; + if (loadFromFile(inventory_filename, categories, items, categories_to_update, is_cache_obsolete)) + { + // We were able to find a cache of files. So, use what we + // found to generate a set of categories we should add. We + // will go through each category loaded and if the version + // does not match, invalidate the version. + S32 count = categories.size(); + cat_set_t::iterator not_cached = temp_cats.end(); + std::set cached_ids; + for(S32 i = 0; i < count; ++i) + { + LLViewerInventoryCategory* cat = categories[i]; + cat_set_t::iterator cit = temp_cats.find(cat); + if (cit == temp_cats.end()) + { + continue; // cache corruption?? not sure why this happens -SJB + } + LLViewerInventoryCategory* tcat = *cit; + + if (categories_to_update.find(tcat->getUUID()) != categories_to_update.end()) + { + tcat->setVersion(NO_VERSION); + LL_WARNS() << "folder to update: " << tcat->getName() << LL_ENDL; + } + + // we can safely ignore anything loaded from file, but + // not sent down in the skeleton. Must have been removed from inventory. + if (cit == not_cached) + { + continue; + } + else if (cat->getVersion() != tcat->getVersion()) + { + // if the cached version does not match the server version, + // throw away the version we have so we can fetch the + // correct contents the next time the viewer opens the folder. + tcat->setVersion(NO_VERSION); + } + else + { + cached_ids.insert(tcat->getUUID()); + + // At the moment download does not provide a thumbnail + // uuid, use the one from cache + tcat->setThumbnailUUID(cat->getThumbnailUUID()); + } + } + + // go ahead and add the cats returned during the download + std::set::const_iterator not_cached_id = cached_ids.end(); + cached_category_count = cached_ids.size(); + for(cat_set_t::iterator it = temp_cats.begin(); it != temp_cats.end(); ++it) + { + if(cached_ids.find((*it)->getUUID()) == not_cached_id) + { + // this check is performed so that we do not + // mark new folders in the skeleton (and not in cache) + // as being cached. + LLViewerInventoryCategory *llvic = (*it); + llvic->setVersion(NO_VERSION); + } + addCategory(*it); + ++child_counts[(*it)->getParentUUID()]; + } + + // Add all the items loaded which are parented to a + // category with a correctly cached parent + S32 bad_link_count = 0; + S32 good_link_count = 0; + S32 recovered_link_count = 0; + cat_map_t::iterator unparented = mCategoryMap.end(); + for(item_array_t::const_iterator item_iter = items.begin(); + item_iter != items.end(); + ++item_iter) + { + LLViewerInventoryItem *item = (*item_iter).get(); + const cat_map_t::iterator cit = mCategoryMap.find(item->getParentUUID()); + + if(cit != unparented) + { + const LLViewerInventoryCategory* cat = cit->second.get(); + if(cat->getVersion() != NO_VERSION) + { + // This can happen if the linked object's baseobj is removed from the cache but the linked object is still in the cache. + if (item->getIsBrokenLink()) + { + //bad_link_count++; + LL_DEBUGS(LOG_INV) << "Attempted to add cached link item without baseobj present ( name: " + << item->getName() << " itemID: " << item->getUUID() + << " assetID: " << item->getAssetUUID() + << " ). Ignoring and invalidating " << cat->getName() << " . " << LL_ENDL; + possible_broken_links.push_back(item); + continue; + } + else if (item->getIsLinkType()) + { + good_link_count++; + } + addItem(item); + cached_item_count += 1; + ++child_counts[cat->getUUID()]; + } + } + } + if (possible_broken_links.size() > 0) + { + for(item_array_t::const_iterator item_iter = possible_broken_links.begin(); + item_iter != possible_broken_links.end(); + ++item_iter) + { + LLViewerInventoryItem *item = (*item_iter).get(); + const cat_map_t::iterator cit = mCategoryMap.find(item->getParentUUID()); + const LLViewerInventoryCategory* cat = cit->second.get(); + if (item->getIsBrokenLink()) + { + bad_link_count++; + invalid_categories.insert(cit->second); + //LL_INFOS(LOG_INV) << "link still broken: " << item->getName() << " in folder " << cat->getName() << LL_ENDL; + } + else + { + // was marked as broken because of loading order, its actually fine to load + addItem(item); + cached_item_count += 1; + ++child_counts[cat->getUUID()]; + recovered_link_count++; + } + } + + LL_DEBUGS(LOG_INV) << "Attempted to add " << bad_link_count + << " cached link items without baseobj present. " + << good_link_count << " link items were successfully added. " + << recovered_link_count << " links added in recovery. " + << "The corresponding categories were invalidated." << LL_ENDL; + } + + } + else + { + // go ahead and add everything after stripping the version + // information. + for(cat_set_t::iterator it = temp_cats.begin(); it != temp_cats.end(); ++it) + { + LLViewerInventoryCategory *llvic = (*it); + llvic->setVersion(NO_VERSION); + addCategory(*it); + } + } + + // Invalidate all categories that failed fetching descendents for whatever + // reason (e.g. one of the descendents was a broken link). + for (cat_set_t::iterator invalid_cat_it = invalid_categories.begin(); + invalid_cat_it != invalid_categories.end(); + invalid_cat_it++) + { + LLViewerInventoryCategory* cat = (*invalid_cat_it).get(); + cat->setVersion(NO_VERSION); + LL_DEBUGS(LOG_INV) << "Invalidating category name: " << cat->getName() << " UUID: " << cat->getUUID() << " due to invalid descendents cache" << LL_ENDL; + } + if (invalid_categories.size() > 0) + { + LL_DEBUGS(LOG_INV) << "Invalidated " << invalid_categories.size() << " categories due to invalid descendents cache" << LL_ENDL; + } + + // At this point, we need to set the known descendents for each + // category which successfully cached so that we do not + // needlessly fetch descendents for categories which we have. + update_map_t::const_iterator no_child_counts = child_counts.end(); + for(cat_set_t::iterator it = temp_cats.begin(); it != temp_cats.end(); ++it) + { + LLViewerInventoryCategory* cat = (*it).get(); + if(cat->getVersion() != NO_VERSION) + { + update_map_t::const_iterator the_count = child_counts.find(cat->getUUID()); + if(the_count != no_child_counts) + { + const S32 num_descendents = (*the_count).second.mValue; + cat->setDescendentCount(num_descendents); + } + else + { + cat->setDescendentCount(0); + } + } + } + + if(remove_inventory_file) + { + // clean up the gunzipped file. + LLFile::remove(inventory_filename); + } + if(is_cache_obsolete && !LLAppViewer::instance()->isSecondInstance()) + { + // If out of date, remove the gzipped file too. + LL_WARNS(LOG_INV) << "Inv cache out of date, removing" << LL_ENDL; + LLFile::remove(gzip_filename); + } + categories.clear(); // will unref and delete entries + } + + LL_INFOS(LOG_INV) << "Successfully loaded " << cached_category_count + << " categories and " << cached_item_count << " items from cache." + << LL_ENDL; + + return rv; +} + +// This is a brute force method to rebuild the entire parent-child +// relations. The overall operation has O(NlogN) performance, which +// should be sufficient for our needs. +void LLInventoryModel::buildParentChildMap() +{ + LL_INFOS(LOG_INV) << "LLInventoryModel::buildParentChildMap()" << LL_ENDL; + + // *NOTE: I am skipping the logic around folder version + // synchronization here because it seems if a folder is lost, we + // might actually want to invalidate it at that point - not + // attempt to cache. More time & thought is necessary. + + // First the categories. We'll copy all of the categories into a + // temporary container to iterate over (oh for real iterators.) + // While we're at it, we'll allocate the arrays in the trees. + cat_array_t cats; + cat_array_t* catsp; + item_array_t* itemsp; + + for(cat_map_t::iterator cit = mCategoryMap.begin(); cit != mCategoryMap.end(); ++cit) + { + LLViewerInventoryCategory* cat = cit->second; + cats.push_back(cat); + if (mParentChildCategoryTree.count(cat->getUUID()) == 0) + { + llassert_always(!mCategoryLock[cat->getUUID()]); + catsp = new cat_array_t; + mParentChildCategoryTree[cat->getUUID()] = catsp; + } + if (mParentChildItemTree.count(cat->getUUID()) == 0) + { + llassert_always(!mItemLock[cat->getUUID()]); + itemsp = new item_array_t; + mParentChildItemTree[cat->getUUID()] = itemsp; + } + } + + // Insert a special parent for the root - so that lookups on + // LLUUID::null as the parent work correctly. This is kind of a + // blatent wastes of space since we allocate a block of memory for + // the array, but whatever - it's not that much space. + if (mParentChildCategoryTree.count(LLUUID::null) == 0) + { + catsp = new cat_array_t; + mParentChildCategoryTree[LLUUID::null] = catsp; + } + + // Now we have a structure with all of the categories that we can + // iterate over and insert into the correct place in the child + // category tree. + S32 count = cats.size(); + S32 i; + S32 lost = 0; + cat_array_t lost_cats; + for(i = 0; i < count; ++i) + { + LLViewerInventoryCategory* cat = cats.at(i); + catsp = getUnlockedCatArray(cat->getParentUUID()); + if(catsp && + // Only the two root folders should be children of null. + // Others should go to lost & found. + (cat->getParentUUID().notNull() || + cat->getPreferredType() == LLFolderType::FT_ROOT_INVENTORY )) + { + catsp->push_back(cat); + } + else + { + // *NOTE: This process could be a lot more efficient if we + // used the new MoveInventoryFolder message, but we would + // have to continue to do the update & build here. So, to + // implement it, we would need a set or map of uuid pairs + // which would be (folder_id, new_parent_id) to be sent up + // to the server. + LL_INFOS(LOG_INV) << "Lost category: " << cat->getUUID() << " - " + << cat->getName() << LL_ENDL; + ++lost; + lost_cats.push_back(cat); + } + } + if(lost) + { + LL_WARNS(LOG_INV) << "Found " << lost << " lost categories." << LL_ENDL; + } + + // Do moves in a separate pass to make sure we've properly filed + // the FT_LOST_AND_FOUND category before we try to find its UUID. + for(i = 0; igetPreferredType(); + if(LLFolderType::FT_NONE == pref) + { + cat->setParent(findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND)); + } + else if(LLFolderType::FT_ROOT_INVENTORY == pref) + { + // it's the root + cat->setParent(LLUUID::null); + } + else + { + // it's a protected folder. + cat->setParent(gInventory.getRootFolderID()); + } + // FIXME note that updateServer() fails with protected + // types, so this will not work as intended in that case. + // UpdateServer uses AIS, AIS cat move is not implemented yet + // cat->updateServer(true); + + // MoveInventoryFolder message, intentionally per item + cat->updateParentOnServer(false); + catsp = getUnlockedCatArray(cat->getParentUUID()); + if(catsp) + { + catsp->push_back(cat); + } + else + { + LL_WARNS(LOG_INV) << "Lost and found Not there!!" << LL_ENDL; + } + } + + const bool COF_exists = (findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT) != LLUUID::null); + sFirstTimeInViewer2 = !COF_exists || gAgent.isFirstLogin(); + + + // Now the items. We allocated in the last step, so now all we + // have to do is iterate over the items and put them in the right + // place. + item_array_t items; + if(!mItemMap.empty()) + { + LLPointer item; + for(item_map_t::iterator iit = mItemMap.begin(); iit != mItemMap.end(); ++iit) + { + item = (*iit).second; + items.push_back(item); + } + } + count = items.size(); + lost = 0; + uuid_vec_t lost_item_ids; + for(i = 0; i < count; ++i) + { + LLPointer item; + item = items.at(i); + itemsp = getUnlockedItemArray(item->getParentUUID()); + if(itemsp) + { + itemsp->push_back(item); + } + else + { + LL_INFOS(LOG_INV) << "Lost item: " << item->getUUID() << " - " + << item->getName() << LL_ENDL; + ++lost; + // plop it into the lost & found. + // + item->setParent(findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND)); + // move it later using a special message to move items. If + // we update server here, the client might crash. + //item->updateServer(); + lost_item_ids.push_back(item->getUUID()); + itemsp = getUnlockedItemArray(item->getParentUUID()); + if(itemsp) + { + itemsp->push_back(item); + } + else + { + LL_WARNS(LOG_INV) << "Lost and found Not there!!" << LL_ENDL; + } + } + } + if(lost) + { + LL_WARNS(LOG_INV) << "Found " << lost << " lost items." << LL_ENDL; + LLMessageSystem* msg = gMessageSystem; + bool start_new_message = true; + const LLUUID lnf = findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); + for(uuid_vec_t::iterator it = lost_item_ids.begin() ; it < lost_item_ids.end(); ++it) + { + if(start_new_message) + { + start_new_message = false; + msg->newMessageFast(_PREHASH_MoveInventoryItem); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addBOOLFast(_PREHASH_Stamp, false); + } + msg->nextBlockFast(_PREHASH_InventoryData); + msg->addUUIDFast(_PREHASH_ItemID, (*it)); + msg->addUUIDFast(_PREHASH_FolderID, lnf); + msg->addString("NewName", NULL); + if(msg->isSendFull(NULL)) + { + start_new_message = true; + gAgent.sendReliableMessage(); + } + } + if(!start_new_message) + { + gAgent.sendReliableMessage(); + } + } + + const LLUUID &agent_inv_root_id = gInventory.getRootFolderID(); + if (agent_inv_root_id.notNull()) + { + cat_array_t* catsp = get_ptr_in_map(mParentChildCategoryTree, agent_inv_root_id); + if(catsp) + { + // *HACK - fix root inventory folder + // some accounts has pbroken inventory root folders + + std::string name = "My Inventory"; + for (parent_cat_map_t::const_iterator it = mParentChildCategoryTree.begin(), + it_end = mParentChildCategoryTree.end(); it != it_end; ++it) + { + cat_array_t* cat_array = it->second; + for (cat_array_t::const_iterator cat_it = cat_array->begin(), + cat_it_end = cat_array->end(); cat_it != cat_it_end; ++cat_it) + { + LLPointer category = *cat_it; + + if(category && category->getPreferredType() != LLFolderType::FT_ROOT_INVENTORY) + continue; + if ( category && 0 == LLStringUtil::compareInsensitive(name, category->getName()) ) + { + if(category->getUUID()!=mRootFolderID) + { + LLUUID& new_inv_root_folder_id = const_cast(mRootFolderID); + new_inv_root_folder_id = category->getUUID(); + } + } + } + } + + LLPointer validation_info = validate(); + if (validation_info->mFatalErrorCount > 0) + { + // Fatal inventory error. Will not be able to engage in many inventory operations. + // This should be followed by an error dialog leading to logout. + LL_WARNS("Inventory") << "Fatal errors were found in validate(): unable to initialize inventory! " + << "Will not be able to do normal inventory operations in this session." + << LL_ENDL; + mIsAgentInvUsable = false; + } + else + { + mIsAgentInvUsable = true; + } + validation_info->mInitialized = true; + mValidationInfo = validation_info; + + // notifyObservers() has been moved to + // llstartup/idle_startup() after this func completes. + // Allows some system categories to be created before + // observers start firing. + } + } +} + +// Would normally do this at construction but that's too early +// in the process for gInventory. Have the first requestPost() +// call set things up. +void LLInventoryModel::initHttpRequest() +{ + if (! mHttpRequestFG) + { + // Haven't initialized, get to it + LLAppCoreHttp & app_core_http(LLAppViewer::instance()->getAppCoreHttp()); + + mHttpRequestFG = new LLCore::HttpRequest; + mHttpRequestBG = new LLCore::HttpRequest; + mHttpOptions = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); + mHttpOptions->setTransferTimeout(300); + mHttpOptions->setUseRetryAfter(true); + // mHttpOptions->setTrace(2); // Do tracing of requests + mHttpHeaders = LLCore::HttpHeaders::ptr_t(new LLCore::HttpHeaders); + mHttpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_LLSD_XML); + mHttpHeaders->append(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_LLSD_XML); + mHttpPolicyClass = app_core_http.getPolicy(LLAppCoreHttp::AP_INVENTORY); + } + + if (!gGenericDispatcher.isHandlerPresent("BulkUpdateInventory")) + { + gGenericDispatcher.addHandler("BulkUpdateInventory", &sBulkUpdateInventory); + } +} + +void LLInventoryModel::handleResponses(bool foreground) +{ + if (foreground && mHttpRequestFG) + { + mHttpRequestFG->update(0); + } + else if (! foreground && mHttpRequestBG) + { + mHttpRequestBG->update(50000L); + } +} + +LLCore::HttpHandle LLInventoryModel::requestPost(bool foreground, + const std::string & url, + const LLSD & body, + const LLCore::HttpHandler::ptr_t &handler, + const char * const message) +{ + if (! mHttpRequestFG) + { + // We do the initialization late and lazily as this class is + // statically-constructed and not all the bits are ready at + // that time. + initHttpRequest(); + } + + LLCore::HttpRequest * request(foreground ? mHttpRequestFG : mHttpRequestBG); + LLCore::HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); + + handle = LLCoreHttpUtil::requestPostWithLLSD(request, + mHttpPolicyClass, + url, + body, + mHttpOptions, + mHttpHeaders, + handler); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + LLCore::HttpStatus status(request->getStatus()); + LL_WARNS(LOG_INV) << "HTTP POST request failed for " << message + << ", Status: " << status.toTerseString() + << " Reason: '" << status.toString() << "'" + << LL_ENDL; + } + return handle; +} + +void LLInventoryModel::createCommonSystemCategories() +{ + //amount of System Folder we should wait for + sPendingSystemFolders = 9; + + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_TRASH); + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_FAVORITE); + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_CALLINGCARD); + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_MY_OUTFITS); + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_CURRENT_OUTFIT); + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_LANDMARK); // folder should exist before user tries to 'landmark this' + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_SETTINGS); + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_MATERIAL); // probably should be server created + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_INBOX); +} + +struct LLUUIDAndName +{ + LLUUIDAndName() {} + LLUUIDAndName(const LLUUID& id, const std::string& name); + bool operator==(const LLUUIDAndName& rhs) const; + bool operator<(const LLUUIDAndName& rhs) const; + bool operator>(const LLUUIDAndName& rhs) const; + + LLUUID mID; + std::string mName; +}; + +LLUUIDAndName::LLUUIDAndName(const LLUUID& id, const std::string& name) : + mID(id), mName(name) +{ +} + +bool LLUUIDAndName::operator==(const LLUUIDAndName& rhs) const +{ + return ((mID == rhs.mID) && (mName == rhs.mName)); +} + +bool LLUUIDAndName::operator<(const LLUUIDAndName& rhs) const +{ + return (mID < rhs.mID); +} + +bool LLUUIDAndName::operator>(const LLUUIDAndName& rhs) const +{ + return (mID > rhs.mID); +} + +// static +bool LLInventoryModel::loadFromFile(const std::string& filename, + LLInventoryModel::cat_array_t& categories, + LLInventoryModel::item_array_t& items, + LLInventoryModel::changed_items_t& cats_to_update, + bool &is_cache_obsolete) +{ + LL_PROFILE_ZONE_NAMED("inventory load from file"); + + if(filename.empty()) + { + LL_ERRS(LOG_INV) << "filename is Null!" << LL_ENDL; + return false; + } + LL_INFOS(LOG_INV) << "loading inventory from: (" << filename << ")" << LL_ENDL; + + llifstream file(filename.c_str()); + + if (!file.is_open()) + { + LL_INFOS(LOG_INV) << "unable to load inventory from: " << filename << LL_ENDL; + return false; + } + + is_cache_obsolete = true; // Obsolete until proven current + + //U64 lines_count = 0U; + std::string line; + LLPointer parser = new LLSDNotationParser(); + while (std::getline(file, line)) + { + LLSD s_item; + std::istringstream iss(line); + if (parser->parse(iss, s_item, line.length()) == LLSDParser::PARSE_FAILURE) + { + LL_WARNS(LOG_INV)<< "Parsing inventory cache failed" << LL_ENDL; + break; + } + + if (s_item.has("inv_cache_version")) + { + S32 version = s_item["inv_cache_version"].asInteger(); + if (version == sCurrentInvCacheVersion) + { + // Cache is up to date + is_cache_obsolete = false; + continue; + } + else + { + LL_WARNS(LOG_INV)<< "Inventory cache is out of date" << LL_ENDL; + break; + } + } + else if (s_item.has("cat_id")) + { + if (is_cache_obsolete) + break; + + LLPointer inv_cat = new LLViewerInventoryCategory(LLUUID::null); + if(inv_cat->importLLSD(s_item)) + { + categories.push_back(inv_cat); + } + } + else if (s_item.has("item_id")) + { + if (is_cache_obsolete) + break; + + LLPointer inv_item = new LLViewerInventoryItem; + if( inv_item->fromLLSD(s_item) ) + { + if(inv_item->getUUID().isNull()) + { + LL_DEBUGS(LOG_INV) << "Ignoring inventory with null item id: " + << inv_item->getName() << LL_ENDL; + } + else + { + if (inv_item->getType() == LLAssetType::AT_UNKNOWN) + { + cats_to_update.insert(inv_item->getParentUUID()); + } + else + { + items.push_back(inv_item); + } + } + } + } + +// TODO(brad) - figure out how to reenable this without breaking everything else +// static constexpr U64 BATCH_SIZE = 512U; +// if ((++lines_count % BATCH_SIZE) == 0) +// { +// // SL-19968 - make sure message system code gets a chance to run every so often +// pump_idle_startup_network(); +// } + } + + file.close(); + + return !is_cache_obsolete; +} + +// static +bool LLInventoryModel::saveToFile(const std::string& filename, + const cat_array_t& categories, + const item_array_t& items) +{ + if (filename.empty()) + { + LL_ERRS(LOG_INV) << "Filename is Null!" << LL_ENDL; + return false; + } + + LL_INFOS(LOG_INV) << "saving inventory to: (" << filename << ")" << LL_ENDL; + + try + { + llofstream fileXML(filename.c_str()); + if (!fileXML.is_open()) + { + LL_WARNS(LOG_INV) << "Failed to open file. Unable to save inventory to: " << filename << LL_ENDL; + return false; + } + + LLSD cache_ver; + cache_ver["inv_cache_version"] = sCurrentInvCacheVersion; + + if (fileXML.fail()) + { + LL_WARNS(LOG_INV) << "Failed to write cache version to file. Unable to save inventory to: " << filename << LL_ENDL; + return false; + } + + fileXML << LLSDOStreamer(cache_ver) << std::endl; + + S32 count = categories.size(); + S32 cat_count = 0; + S32 i; + for (i = 0; i < count; ++i) + { + LLViewerInventoryCategory* cat = categories[i]; + if (cat->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN) + { + fileXML << LLSDOStreamer(cat->exportLLSD()) << std::endl; + cat_count++; + } + + if (fileXML.fail()) + { + LL_WARNS(LOG_INV) << "Failed to write a folder to file. Unable to save inventory to: " << filename << LL_ENDL; + return false; + } + } + + S32 it_count = items.size(); + for (i = 0; i < it_count; ++i) + { + fileXML << LLSDOStreamer(items[i]->asLLSD()) << std::endl; + + if (fileXML.fail()) + { + LL_WARNS(LOG_INV) << "Failed to write an item to file. Unable to save inventory to: " << filename << LL_ENDL; + return false; + } + } + fileXML.flush(); + + fileXML.close(); + + LL_INFOS(LOG_INV) << "Inventory saved: " << cat_count << " categories, " << it_count << " items." << LL_ENDL; + } + catch (...) + { + LOG_UNHANDLED_EXCEPTION(""); + LL_INFOS(LOG_INV) << "Failed to save inventory to: (" << filename << ")" << LL_ENDL; + return false; + } + + return true; +} + +// message handling functionality +// static +void LLInventoryModel::registerCallbacks(LLMessageSystem* msg) +{ + //msg->setHandlerFuncFast(_PREHASH_InventoryUpdate, + // processInventoryUpdate, + // NULL); + //msg->setHandlerFuncFast(_PREHASH_UseCachedInventory, + // processUseCachedInventory, + // NULL); + msg->setHandlerFuncFast(_PREHASH_UpdateCreateInventoryItem, + processUpdateCreateInventoryItem, + NULL); + msg->setHandlerFuncFast(_PREHASH_RemoveInventoryItem, + processRemoveInventoryItem, + NULL); + msg->setHandlerFuncFast(_PREHASH_RemoveInventoryFolder, + processRemoveInventoryFolder, + NULL); + msg->setHandlerFuncFast(_PREHASH_RemoveInventoryObjects, + processRemoveInventoryObjects, + NULL); + msg->setHandlerFuncFast(_PREHASH_SaveAssetIntoInventory, + processSaveAssetIntoInventory, + NULL); + msg->setHandlerFuncFast(_PREHASH_BulkUpdateInventory, + processBulkUpdateInventory, + NULL); + msg->setHandlerFunc("MoveInventoryItem", processMoveInventoryItem); +} + + +// static +void LLInventoryModel::processUpdateCreateInventoryItem(LLMessageSystem* msg, void**) +{ + // do accounting and highlight new items if they arrive + if (gInventory.messageUpdateCore(msg, true, LLInventoryObserver::UPDATE_CREATE)) + { + U32 callback_id; + LLUUID item_id; + msg->getUUIDFast(_PREHASH_InventoryData, _PREHASH_ItemID, item_id); + msg->getU32Fast(_PREHASH_InventoryData, _PREHASH_CallbackID, callback_id); + + gInventoryCallbacks.fire(callback_id, item_id); + + // Message system at the moment doesn't support Thumbnails and potential + // newer features so just rerequest whole item + // + // todo: instead of unpacking message fully, + // grab only an item_id, then fetch + LLInventoryModelBackgroundFetch::instance().scheduleItemFetch(item_id, true); + } + +} + +bool LLInventoryModel::messageUpdateCore(LLMessageSystem* msg, bool account, U32 mask) +{ + //make sure our added inventory observer is active + start_new_inventory_observer(); + + LLUUID agent_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); + if(agent_id != gAgent.getID()) + { + LL_WARNS(LOG_INV) << "Got a inventory update for the wrong agent: " << agent_id + << LL_ENDL; + return false; + } + item_array_t items; + update_map_t update; + S32 count = msg->getNumberOfBlocksFast(_PREHASH_InventoryData); + // Does this loop ever execute more than once? + for(S32 i = 0; i < count; ++i) + { + LLPointer titem = new LLViewerInventoryItem; + titem->unpackMessage(msg, _PREHASH_InventoryData, i); + LL_DEBUGS(LOG_INV) << "LLInventoryModel::messageUpdateCore() item id: " + << titem->getUUID() << LL_ENDL; + items.push_back(titem); + // examine update for changes. + LLViewerInventoryItem* itemp = gInventory.getItem(titem->getUUID()); + if(itemp) + { + if(titem->getParentUUID() == itemp->getParentUUID()) + { + update[titem->getParentUUID()]; + } + else + { + ++update[titem->getParentUUID()]; + --update[itemp->getParentUUID()]; + } + } + else + { + ++update[titem->getParentUUID()]; + } + } + if(account) + { + gInventory.accountForUpdate(update); + } + + if (account) + { + mask |= LLInventoryObserver::CREATE; + } + //as above, this loop never seems to loop more than once per call + for (item_array_t::iterator it = items.begin(); it != items.end(); ++it) + { + gInventory.updateItem(*it, mask); + } + gInventory.notifyObservers(); + gViewerWindow->getWindow()->decBusyCount(); + + return true; +} + +// static +void LLInventoryModel::removeInventoryItem(LLUUID agent_id, LLMessageSystem* msg, const char* msg_label) +{ + LLUUID item_id; + S32 count = msg->getNumberOfBlocksFast(msg_label); + LL_DEBUGS(LOG_INV) << "Message has " << count << " item blocks" << LL_ENDL; + uuid_vec_t item_ids; + update_map_t update; + for(S32 i = 0; i < count; ++i) + { + msg->getUUIDFast(msg_label, _PREHASH_ItemID, item_id, i); + LL_DEBUGS(LOG_INV) << "Checking for item-to-be-removed " << item_id << LL_ENDL; + LLViewerInventoryItem* itemp = gInventory.getItem(item_id); + if(itemp) + { + LL_DEBUGS(LOG_INV) << "Item will be removed " << item_id << LL_ENDL; + // we only bother with the delete and account if we found + // the item - this is usually a back-up for permissions, + // so frequently the item will already be gone. + --update[itemp->getParentUUID()]; + item_ids.push_back(item_id); + } + } + gInventory.accountForUpdate(update); + for(uuid_vec_t::iterator it = item_ids.begin(); it != item_ids.end(); ++it) + { + LL_DEBUGS(LOG_INV) << "Calling deleteObject " << *it << LL_ENDL; + gInventory.deleteObject(*it); + } +} + +// static +void LLInventoryModel::processRemoveInventoryItem(LLMessageSystem* msg, void**) +{ + LL_DEBUGS(LOG_INV) << "LLInventoryModel::processRemoveInventoryItem()" << LL_ENDL; + LLUUID agent_id, item_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); + if(agent_id != gAgent.getID()) + { + LL_WARNS(LOG_INV) << "Got a RemoveInventoryItem for the wrong agent." + << LL_ENDL; + return; + } + LLInventoryModel::removeInventoryItem(agent_id, msg, _PREHASH_InventoryData); + gInventory.notifyObservers(); +} + +// static +void LLInventoryModel::removeInventoryFolder(LLUUID agent_id, + LLMessageSystem* msg) +{ + LLUUID folder_id; + uuid_vec_t folder_ids; + update_map_t update; + S32 count = msg->getNumberOfBlocksFast(_PREHASH_FolderData); + for(S32 i = 0; i < count; ++i) + { + msg->getUUIDFast(_PREHASH_FolderData, _PREHASH_FolderID, folder_id, i); + LLViewerInventoryCategory* folderp = gInventory.getCategory(folder_id); + if(folderp) + { + --update[folderp->getParentUUID()]; + folder_ids.push_back(folder_id); + } + } + gInventory.accountForUpdate(update); + for(uuid_vec_t::iterator it = folder_ids.begin(); it != folder_ids.end(); ++it) + { + gInventory.deleteObject(*it); + } +} + +// static +void LLInventoryModel::processRemoveInventoryFolder(LLMessageSystem* msg, + void**) +{ + LL_DEBUGS() << "LLInventoryModel::processRemoveInventoryFolder()" << LL_ENDL; + LLUUID agent_id, session_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_SessionID, session_id); + if(agent_id != gAgent.getID()) + { + LL_WARNS() << "Got a RemoveInventoryFolder for the wrong agent." + << LL_ENDL; + return; + } + LLInventoryModel::removeInventoryFolder( agent_id, msg ); + gInventory.notifyObservers(); +} + +// static +void LLInventoryModel::processRemoveInventoryObjects(LLMessageSystem* msg, + void**) +{ + LL_DEBUGS() << "LLInventoryModel::processRemoveInventoryObjects()" << LL_ENDL; + LLUUID agent_id, session_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_SessionID, session_id); + if(agent_id != gAgent.getID()) + { + LL_WARNS() << "Got a RemoveInventoryObjects for the wrong agent." + << LL_ENDL; + return; + } + LLInventoryModel::removeInventoryFolder( agent_id, msg ); + LLInventoryModel::removeInventoryItem( agent_id, msg, _PREHASH_ItemData ); + gInventory.notifyObservers(); +} + +// static +void LLInventoryModel::processSaveAssetIntoInventory(LLMessageSystem* msg, + void**) +{ + LLUUID agent_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); + if(agent_id != gAgent.getID()) + { + LL_WARNS() << "Got a SaveAssetIntoInventory message for the wrong agent." + << LL_ENDL; + return; + } + + LLUUID item_id; + msg->getUUIDFast(_PREHASH_InventoryData, _PREHASH_ItemID, item_id); + + // The viewer ignores the asset id because this message is only + // used for attachments/objects, so the asset id is not used in + // the viewer anyway. + LL_DEBUGS() << "LLInventoryModel::processSaveAssetIntoInventory itemID=" + << item_id << LL_ENDL; + LLViewerInventoryItem* item = gInventory.getItem( item_id ); + if( item ) + { + LLCategoryUpdate up(item->getParentUUID(), 0); + gInventory.accountForUpdate(up); + gInventory.addChangedMask( LLInventoryObserver::INTERNAL, item_id); + gInventory.notifyObservers(); + } + else + { + LL_INFOS() << "LLInventoryModel::processSaveAssetIntoInventory item" + " not found: " << item_id << LL_ENDL; + } + if(gViewerWindow) + { + gViewerWindow->getWindow()->decBusyCount(); + } +} + +// static +void LLInventoryModel::processBulkUpdateInventory(LLMessageSystem* msg, void**) +{ + LLUUID agent_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); + if(agent_id != gAgent.getID()) + { + LL_WARNS() << "Got a BulkUpdateInventory for the wrong agent." << LL_ENDL; + return; + } + LLUUID tid; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_TransactionID, tid); +#ifndef LL_RELEASE_FOR_DOWNLOAD + LL_DEBUGS("Inventory") << "Bulk inventory: " << tid << LL_ENDL; +#endif + + update_map_t update; + cat_array_t folders; + S32 count; + S32 i; + count = msg->getNumberOfBlocksFast(_PREHASH_FolderData); + for(i = 0; i < count; ++i) + { + LLPointer tfolder = new LLViewerInventoryCategory(gAgent.getID()); + tfolder->unpackMessage(msg, _PREHASH_FolderData, i); + LL_DEBUGS("Inventory") << "unpacked folder '" << tfolder->getName() << "' (" + << tfolder->getUUID() << ") in " << tfolder->getParentUUID() + << LL_ENDL; + + // If the folder is a listing or a version folder, all we need to do is update the SLM data + int depth_folder = depth_nesting_in_marketplace(tfolder->getUUID()); + if ((depth_folder == 1) || (depth_folder == 2)) + { + // Trigger an SLM listing update + LLUUID listing_uuid = (depth_folder == 1 ? tfolder->getUUID() : tfolder->getParentUUID()); + S32 listing_id = LLMarketplaceData::instance().getListingID(listing_uuid); + LLMarketplaceData::instance().getListing(listing_id); + // In that case, there is no item to update so no callback -> we skip the rest of the update + } + else if(tfolder->getUUID().notNull()) + { + folders.push_back(tfolder); + LLViewerInventoryCategory* folderp = gInventory.getCategory(tfolder->getUUID()); + if(folderp) + { + if (folderp->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN) + { + if (tfolder->getParentUUID() == folderp->getParentUUID()) + { + update[tfolder->getParentUUID()]; + } + else + { + ++update[tfolder->getParentUUID()]; + --update[folderp->getParentUUID()]; + } + } + else + { + folderp->fetch(); + } + } + else + { + // we could not find the folder, so it is probably + // new. However, we only want to attempt accounting + // for the parent if we can find the parent. + folderp = gInventory.getCategory(tfolder->getParentUUID()); + if(folderp) + { + if (folderp->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN) + { + ++update[tfolder->getParentUUID()]; + } + else + { + folderp->fetch(); + } + } + } + } + } + + + count = msg->getNumberOfBlocksFast(_PREHASH_ItemData); + uuid_vec_t wearable_ids; + item_array_t items; + std::list cblist; + for(i = 0; i < count; ++i) + { + LLPointer titem = new LLViewerInventoryItem; + titem->unpackMessage(msg, _PREHASH_ItemData, i); + LL_DEBUGS("Inventory") << "unpacked item '" << titem->getName() << "' in " + << titem->getParentUUID() << LL_ENDL; + U32 callback_id; + msg->getU32Fast(_PREHASH_ItemData, _PREHASH_CallbackID, callback_id); + if(titem->getUUID().notNull() ) // && callback_id.notNull() ) + { + items.push_back(titem); + cblist.push_back(InventoryCallbackInfo(callback_id, titem->getUUID())); + if (titem->getInventoryType() == LLInventoryType::IT_WEARABLE) + { + wearable_ids.push_back(titem->getUUID()); + } + // examine update for changes. + LLViewerInventoryItem* itemp = gInventory.getItem(titem->getUUID()); + if(itemp) + { + if(titem->getParentUUID() == itemp->getParentUUID()) + { + update[titem->getParentUUID()]; + } + else + { + ++update[titem->getParentUUID()]; + --update[itemp->getParentUUID()]; + } + } + else + { + LLViewerInventoryCategory* folderp = gInventory.getCategory(titem->getParentUUID()); + if(folderp) + { + if (folderp->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN) + { + ++update[titem->getParentUUID()]; + } + else + { + folderp->fetch(); + } + } + } + } + else + { + cblist.push_back(InventoryCallbackInfo(callback_id, LLUUID::null)); + } + } + gInventory.accountForUpdate(update); + + for (cat_array_t::iterator cit = folders.begin(); cit != folders.end(); ++cit) + { + gInventory.updateCategory(*cit); + if ((*cit)->getVersion() != LLViewerInventoryCategory::VERSION_UNKNOWN) + { + // Temporary workaround: just fetch the item using AIS to get missing fields. + // If this works fine we might want to extract 'ids only' from the message + // then use AIS as a primary fetcher + LLInventoryModelBackgroundFetch::instance().scheduleFolderFetch((*cit)->getUUID(), true /*force, since it has changes*/); + } + // else already called fetch() above + } + for (item_array_t::iterator iit = items.begin(); iit != items.end(); ++iit) + { + gInventory.updateItem(*iit); + + // Temporary workaround: just fetch the item using AIS to get missing fields. + // If this works fine we might want to extract 'ids only' from the message + // then use AIS as a primary fetcher + LLInventoryModelBackgroundFetch::instance().scheduleItemFetch((*iit)->getUUID(), true); + } + gInventory.notifyObservers(); + + // The incoming inventory could span more than one BulkInventoryUpdate packet, + // so record the transaction ID for this purchase, then wear all clothing + // that comes in as part of that transaction ID. JC + if (LLInventoryState::sWearNewClothing) + { + LLInventoryState::sWearNewClothingTransactionID = tid; + LLInventoryState::sWearNewClothing = false; + } + + if (tid.notNull() && tid == LLInventoryState::sWearNewClothingTransactionID) + { + count = wearable_ids.size(); + for (i = 0; i < count; ++i) + { + LLViewerInventoryItem* wearable_item; + wearable_item = gInventory.getItem(wearable_ids[i]); + LLAppearanceMgr::instance().wearItemOnAvatar(wearable_item->getUUID(), true, true); + } + } + + std::list::iterator inv_it; + for (inv_it = cblist.begin(); inv_it != cblist.end(); ++inv_it) + { + InventoryCallbackInfo cbinfo = (*inv_it); + gInventoryCallbacks.fire(cbinfo.mCallback, cbinfo.mInvID); + } +} + +// static +void LLInventoryModel::processMoveInventoryItem(LLMessageSystem* msg, void**) +{ + LL_DEBUGS() << "LLInventoryModel::processMoveInventoryItem()" << LL_ENDL; + LLUUID agent_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); + if(agent_id != gAgent.getID()) + { + LL_WARNS() << "Got a MoveInventoryItem message for the wrong agent." + << LL_ENDL; + return; + } + + LLUUID item_id; + LLUUID folder_id; + std::string new_name; + bool anything_changed = false; + S32 count = msg->getNumberOfBlocksFast(_PREHASH_InventoryData); + for(S32 i = 0; i < count; ++i) + { + msg->getUUIDFast(_PREHASH_InventoryData, _PREHASH_ItemID, item_id, i); + LLViewerInventoryItem* item = gInventory.getItem(item_id); + if(item) + { + LLPointer new_item = new LLViewerInventoryItem(item); + msg->getUUIDFast(_PREHASH_InventoryData, _PREHASH_FolderID, folder_id, i); + msg->getString("InventoryData", "NewName", new_name, i); + + LL_DEBUGS() << "moving item " << item_id << " to folder " + << folder_id << LL_ENDL; + update_list_t update; + LLCategoryUpdate old_folder(item->getParentUUID(), -1); + update.push_back(old_folder); + LLCategoryUpdate new_folder(folder_id, 1); + update.push_back(new_folder); + gInventory.accountForUpdate(update); + + new_item->setParent(folder_id); + if (new_name.length() > 0) + { + new_item->rename(new_name); + } + gInventory.updateItem(new_item); + anything_changed = true; + } + else + { + LL_INFOS() << "LLInventoryModel::processMoveInventoryItem item not found: " << item_id << LL_ENDL; + } + } + if(anything_changed) + { + gInventory.notifyObservers(); + } +} + +//---------------------------------------------------------------------------- +// Trash: LLFolderType::FT_TRASH, "ConfirmEmptyTrash" +// Lost&Found: LLFolderType::FT_LOST_AND_FOUND, "ConfirmEmptyLostAndFound" + +bool LLInventoryModel::callbackEmptyFolderType(const LLSD& notification, const LLSD& response, LLFolderType::EType preferred_type) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // YES + { + const LLUUID folder_id = findCategoryUUIDForType(preferred_type); + purge_descendents_of(folder_id, NULL); + } + return false; +} + +void LLInventoryModel::emptyFolderType(const std::string notification, LLFolderType::EType preferred_type) +{ + if (!notification.empty()) + { + LLSD args; + if(LLFolderType::FT_TRASH == preferred_type) + { + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + const LLUUID trash_id = findCategoryUUIDForType(preferred_type); + gInventory.collectDescendents(trash_id, cats, items, LLInventoryModel::INCLUDE_TRASH); //All descendants + S32 item_count = items.size() + cats.size(); + args["COUNT"] = item_count; + } + LLNotificationsUtil::add(notification, args, LLSD(), + boost::bind(&LLInventoryModel::callbackEmptyFolderType, this, _1, _2, preferred_type)); + } + else + { + const LLUUID folder_id = findCategoryUUIDForType(preferred_type); + purge_descendents_of(folder_id, NULL); + } +} + +//---------------------------------------------------------------------------- + +void LLInventoryModel::removeItem(const LLUUID& item_id) +{ + LLViewerInventoryItem* item = getItem(item_id); + if (! item) + { + LL_WARNS("Inventory") << "couldn't find inventory item " << item_id << LL_ENDL; + } + else + { + const LLUUID new_parent = findCategoryUUIDForType(LLFolderType::FT_TRASH); + if (new_parent.notNull()) + { + LL_INFOS("Inventory") << "Moving to Trash (" << new_parent << "):" << LL_ENDL; + changeItemParent(item, new_parent, true); + } + } +} + +void LLInventoryModel::removeCategory(const LLUUID& category_id) +{ + if (! get_is_category_removable(this, category_id)) + { + return; + } + + // Look for any gestures and deactivate them + LLInventoryModel::cat_array_t descendent_categories; + LLInventoryModel::item_array_t descendent_items; + collectDescendents(category_id, descendent_categories, descendent_items, false); + + for (LLInventoryModel::item_array_t::const_iterator iter = descendent_items.begin(); + iter != descendent_items.end(); + ++iter) + { + const LLViewerInventoryItem* item = (*iter); + const LLUUID& item_id = item->getUUID(); + if (item->getType() == LLAssetType::AT_GESTURE + && LLGestureMgr::instance().isGestureActive(item_id)) + { + LLGestureMgr::instance().deactivateGesture(item_id); + } + } + + LLViewerInventoryCategory* cat = getCategory(category_id); + if (cat) + { + const LLUUID trash_id = findCategoryUUIDForType(LLFolderType::FT_TRASH); + if (trash_id.notNull()) + { + changeCategoryParent(cat, trash_id, true); + } + } + + checkTrashOverflow(); +} + +void LLInventoryModel::removeObject(const LLUUID& object_id) +{ + if(object_id.isNull()) + { + return; + } + + LLInventoryObject* obj = getObject(object_id); + if (dynamic_cast(obj)) + { + removeItem(object_id); + } + else if (dynamic_cast(obj)) + { + removeCategory(object_id); + } + else if (obj) + { + LL_WARNS("Inventory") << "object ID " << object_id + << " is an object of unrecognized class " + << typeid(*obj).name() << LL_ENDL; + } + else + { + LL_WARNS("Inventory") << "object ID " << object_id << " not found" << LL_ENDL; + } +} + +bool callback_preview_trash_folder(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // YES + { + LLFloaterPreviewTrash::show(); + } + return false; +} + +void LLInventoryModel::checkTrashOverflow() +{ + static LLCachedControl trash_max_capacity(gSavedSettings, "InventoryTrashMaxCapacity"); + + // Collect all descendants including those in subfolders. + // + // Note: Do we really need content of subfolders? + // This was made to prevent download of trash folder timeouting + // viewer and sub-folders are supposed to download independently. + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + const LLUUID trash_id = findCategoryUUIDForType(LLFolderType::FT_TRASH); + gInventory.collectDescendents(trash_id, cats, items, LLInventoryModel::INCLUDE_TRASH); + S32 item_count = items.size() + cats.size(); + + if (item_count >= trash_max_capacity) + { + if (LLFloaterPreviewTrash::isVisible()) + { + // bring to front + LLFloaterPreviewTrash::show(); + } + else + { + LLNotificationsUtil::add("TrashIsFull", LLSD(), LLSD(), + boost::bind(callback_preview_trash_folder, _1, _2)); + } + } +} + +const LLUUID &LLInventoryModel::getRootFolderID() const +{ + return mRootFolderID; +} + +void LLInventoryModel::setRootFolderID(const LLUUID& val) +{ + mRootFolderID = val; +} + +const LLUUID &LLInventoryModel::getLibraryRootFolderID() const +{ + return mLibraryRootFolderID; +} + +void LLInventoryModel::setLibraryRootFolderID(const LLUUID& val) +{ + mLibraryRootFolderID = val; +} + +const LLUUID &LLInventoryModel::getLibraryOwnerID() const +{ + return mLibraryOwnerID; +} + +void LLInventoryModel::setLibraryOwnerID(const LLUUID& val) +{ + mLibraryOwnerID = val; +} + +// static +bool LLInventoryModel::getIsFirstTimeInViewer2() +{ + // Do not call this before parentchild map is built. + if (!gInventory.mIsAgentInvUsable) + { + LL_WARNS() << "Parent Child Map not yet built; guessing as first time in viewer2." << LL_ENDL; + return true; + } + + return sFirstTimeInViewer2; +} + +LLInventoryModel::item_array_t::iterator LLInventoryModel::findItemIterByUUID(LLInventoryModel::item_array_t& items, const LLUUID& id) +{ + LLInventoryModel::item_array_t::iterator curr_item = items.begin(); + + while (curr_item != items.end()) + { + if ((*curr_item)->getUUID() == id) + { + break; + } + ++curr_item; + } + + return curr_item; +} + +// static +// * @param[in, out] items - vector with items to be updated. It should be sorted in a right way +// * before calling this method. +// * @param src_item_id - LLUUID of inventory item to be moved in new position +// * @param dest_item_id - LLUUID of inventory item before (or after) which source item should +// * be placed. +// * @param insert_before - bool indicating if src_item_id should be placed before or after +// * dest_item_id. Default is true. +void LLInventoryModel::updateItemsOrder(LLInventoryModel::item_array_t& items, const LLUUID& src_item_id, const LLUUID& dest_item_id, bool insert_before) +{ + LLInventoryModel::item_array_t::iterator it_src = findItemIterByUUID(items, src_item_id); + LLInventoryModel::item_array_t::iterator it_dest = findItemIterByUUID(items, dest_item_id); + + // If one of the passed UUID is not in the item list, bail out + if ((it_src == items.end()) || (it_dest == items.end())) + return; + + // Erase the source element from the list, keep a copy before erasing. + LLViewerInventoryItem* src_item = *it_src; + items.erase(it_src); + + // Note: Target iterator is not valid anymore because the container was changed, so update it. + it_dest = findItemIterByUUID(items, dest_item_id); + + // Go to the next element if one wishes to insert after the dest element + if (!insert_before) + { + ++it_dest; + } + + // Reinsert the source item in the right place + if (it_dest != items.end()) + { + items.insert(it_dest, src_item); + } + else + { + // Append to the list if it_dest reached the end + items.push_back(src_item); + } +} + +// See also LLInventorySort where landmarks in the Favorites folder are sorted. +class LLViewerInventoryItemSort +{ +public: + bool operator()(const LLPointer& a, const LLPointer& b) + { + return a->getSortField() < b->getSortField(); + } +}; + +//---------------------------------------------------------------------------- + +// *NOTE: DEBUG functionality +void LLInventoryModel::dumpInventory() const +{ + LL_INFOS() << "\nBegin Inventory Dump\n**********************:" << LL_ENDL; + LL_INFOS() << "mCategory[] contains " << mCategoryMap.size() << " items." << LL_ENDL; + for(cat_map_t::const_iterator cit = mCategoryMap.begin(); cit != mCategoryMap.end(); ++cit) + { + const LLViewerInventoryCategory* cat = cit->second; + if(cat) + { + LL_INFOS() << " " << cat->getUUID() << " '" << cat->getName() << "' " + << cat->getVersion() << " " << cat->getDescendentCount() + << LL_ENDL; + } + else + { + LL_INFOS() << " NULL!" << LL_ENDL; + } + } + LL_INFOS() << "mItemMap[] contains " << mItemMap.size() << " items." << LL_ENDL; + for(item_map_t::const_iterator iit = mItemMap.begin(); iit != mItemMap.end(); ++iit) + { + const LLViewerInventoryItem* item = iit->second; + if(item) + { + LL_INFOS() << " " << item->getUUID() << " " + << item->getName() << LL_ENDL; + } + else + { + LL_INFOS() << " NULL!" << LL_ENDL; + } + } + LL_INFOS() << "\n**********************\nEnd Inventory Dump" << LL_ENDL; +} + +// Do various integrity checks on model, logging issues found and +// returning an overall good/bad flag. +LLPointer LLInventoryModel::validate() const +{ + LLPointer validation_info = new LLInventoryValidationInfo; + S32 fatal_errs = 0; + S32 warning_count= 0; + S32 loop_count = 0; + S32 orphaned_count = 0; + + if (getRootFolderID().isNull()) + { + LL_WARNS("Inventory") << "Fatal inventory corruption: no root folder id" << LL_ENDL; + validation_info->mFatalNoRootFolder = true; + fatal_errs++; + } + if (getLibraryRootFolderID().isNull()) + { + // Probably shouldn't be a fatality, inventory can function without a library + LL_WARNS("Inventory") << "Fatal inventory corruption: no library root folder id" << LL_ENDL; + validation_info->mFatalNoLibraryRootFolder = true; + fatal_errs++; + } + + if (mCategoryMap.size() + 1 != mParentChildCategoryTree.size()) + { + // ParentChild should be one larger because of the special entry for null uuid. + LL_INFOS("Inventory") << "unexpected sizes: cat map size " << mCategoryMap.size() + << " parent/child " << mParentChildCategoryTree.size() << LL_ENDL; + + validation_info->mWarnings["category_map_size"]++; + warning_count++; + } + S32 cat_lock = 0; + S32 item_lock = 0; + S32 desc_unknown_count = 0; + S32 version_unknown_count = 0; + + typedef std::map ft_count_map; + ft_count_map ft_counts_under_root; + ft_count_map ft_counts_elsewhere; + + // Loop over all categories and check. + for(cat_map_t::const_iterator cit = mCategoryMap.begin(); cit != mCategoryMap.end(); ++cit) + { + const LLUUID& cat_id = cit->first; + const LLViewerInventoryCategory *cat = cit->second; + if (!cat) + { + LL_WARNS("Inventory") << "null cat" << LL_ENDL; + validation_info->mWarnings["null_cat"]++; + warning_count++; + continue; + } + LLUUID topmost_ancestor_id; + // Will leave as null uuid on failure + EAncestorResult res = getObjectTopmostAncestor(cat_id, topmost_ancestor_id); + switch (res) + { + case ANCESTOR_MISSING: + orphaned_count++; + break; + case ANCESTOR_LOOP: + loop_count++; + break; + case ANCESTOR_OK: + break; + default: + LL_WARNS("Inventory") << "Unknown ancestor error for " << cat_id << LL_ENDL; + validation_info->mWarnings["unknown_ancestor_status"]++; + warning_count++; + break; + } + + if (cat_id != cat->getUUID()) + { + LL_WARNS("Inventory") << "cat id/index mismatch " << cat_id << " " << cat->getUUID() << LL_ENDL; + validation_info->mWarnings["cat_id_index_mismatch"]++; + warning_count++; + } + + if (cat->getParentUUID().isNull()) + { + if (cat_id != getRootFolderID() && cat_id != getLibraryRootFolderID()) + { + LL_WARNS("Inventory") << "cat " << cat_id << " has no parent, but is not root (" + << getRootFolderID() << ") or library root (" + << getLibraryRootFolderID() << ")" << LL_ENDL; + validation_info->mWarnings["null_parent"]++; + warning_count++; + } + } + cat_array_t* cats; + item_array_t* items; + getDirectDescendentsOf(cat_id,cats,items); + if (!cats || !items) + { + LL_WARNS("Inventory") << "invalid direct descendents for " << cat_id << LL_ENDL; + validation_info->mWarnings["direct_descendents"]++; + warning_count++; + continue; + } + if (cat->getDescendentCount() == LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN) + { + desc_unknown_count++; + } + else if (cats->size() + items->size() != cat->getDescendentCount()) + { + // In the case of library this is not unexpected, since + // different user accounts may be getting the library + // contents from different inventory hosts. + if (topmost_ancestor_id.isNull() || topmost_ancestor_id != getLibraryRootFolderID()) + { + LL_WARNS("Inventory") << "invalid desc count for " << cat_id << " [" << getFullPath(cat) << "]" + << " cached " << cat->getDescendentCount() + << " expected " << cats->size() << "+" << items->size() + << "=" << cats->size() +items->size() << LL_ENDL; + validation_info->mWarnings["invalid_descendent_count"]++; + warning_count++; + } + } + if (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) + { + version_unknown_count++; + } + auto cat_lock_it = mCategoryLock.find(cat_id); + if (cat_lock_it != mCategoryLock.end() && cat_lock_it->second) + { + cat_lock++; + } + auto item_lock_it = mItemLock.find(cat_id); + if (item_lock_it != mItemLock.end() && item_lock_it->second) + { + item_lock++; + } + for (S32 i = 0; isize(); i++) + { + LLViewerInventoryItem *item = items->at(i); + + if (!item) + { + LL_WARNS("Inventory") << "null item at index " << i << " for cat " << cat_id << LL_ENDL; + validation_info->mWarnings["null_item_at_index"]++; + warning_count++; + continue; + } + + const LLUUID& item_id = item->getUUID(); + + if (item->getParentUUID() != cat_id) + { + LL_WARNS("Inventory") << "wrong parent for " << item_id << " found " + << item->getParentUUID() << " expected " << cat_id + << LL_ENDL; + validation_info->mWarnings["wrong_parent_for_item"]++; + warning_count++; + } + + + // Entries in items and mItemMap should correspond. + item_map_t::const_iterator it = mItemMap.find(item_id); + if (it == mItemMap.end()) + { + LL_WARNS("Inventory") << "item " << item_id << " found as child of " + << cat_id << " but not in top level mItemMap" << LL_ENDL; + validation_info->mWarnings["item_not_in_top_map"]++; + warning_count++; + } + else + { + LLViewerInventoryItem *top_item = it->second; + if (top_item != item) + { + LL_WARNS("Inventory") << "item mismatch, item_id " << item_id + << " top level entry is different, uuid " << top_item->getUUID() << LL_ENDL; + } + } + + // Topmost ancestor should be root or library. + LLUUID topmost_ancestor_id; + EAncestorResult found = getObjectTopmostAncestor(item_id, topmost_ancestor_id); + if (found != ANCESTOR_OK) + { + LL_WARNS("Inventory") << "unable to find topmost ancestor for " << item_id << LL_ENDL; + validation_info->mWarnings["topmost_ancestor_not_found"]++; + warning_count++; + } + else + { + if (topmost_ancestor_id != getRootFolderID() && + topmost_ancestor_id != getLibraryRootFolderID()) + { + LL_WARNS("Inventory") << "unrecognized top level ancestor for " << item_id + << " got " << topmost_ancestor_id + << " expected " << getRootFolderID() + << " or " << getLibraryRootFolderID() << LL_ENDL; + validation_info->mWarnings["topmost_ancestor_not_recognized"]++; + warning_count++; + } + } + } + + // Does this category appear as a child of its supposed parent? + const LLUUID& parent_id = cat->getParentUUID(); + if (!parent_id.isNull()) + { + cat_array_t* cats; + item_array_t* items; + getDirectDescendentsOf(parent_id,cats,items); + if (!cats) + { + LL_WARNS("Inventory") << "cat " << cat_id << " name [" << cat->getName() + << "] orphaned - no child cat array for alleged parent " << parent_id << LL_ENDL; + orphaned_count++; + } + else + { + bool found = false; + for (S32 i = 0; isize(); i++) + { + LLViewerInventoryCategory *kid_cat = cats->at(i); + if (kid_cat == cat) + { + found = true; + break; + } + } + if (!found) + { + LL_WARNS("Inventory") << "cat " << cat_id << " name [" << cat->getName() + << "] orphaned - not found in child cat array of alleged parent " << parent_id << LL_ENDL; + orphaned_count++; + } + } + } + + // Update count of preferred types + LLFolderType::EType folder_type = cat->getPreferredType(); + bool cat_is_in_library = false; + LLUUID topmost_id; + if (getObjectTopmostAncestor(cat->getUUID(),topmost_id) == ANCESTOR_OK && topmost_id == getLibraryRootFolderID()) + { + cat_is_in_library = true; + } + if (!cat_is_in_library) + { + if (getRootFolderID().notNull() && (cat->getUUID()==getRootFolderID() || cat->getParentUUID()==getRootFolderID())) + { + ft_counts_under_root[folder_type]++; + if (folder_type != LLFolderType::FT_NONE) + { + LL_DEBUGS("Inventory") << "Under root cat: " << getFullPath(cat) << " folder_type " << folder_type << LL_ENDL; + } + } + else + { + ft_counts_elsewhere[folder_type]++; + if (folder_type != LLFolderType::FT_NONE) + { + LL_DEBUGS("Inventory") << "Elsewhere cat: " << getFullPath(cat) << " folder_type " << folder_type << LL_ENDL; + } + } + } + } + + // Loop over all items and check + for(item_map_t::const_iterator iit = mItemMap.begin(); iit != mItemMap.end(); ++iit) + { + const LLUUID& item_id = iit->first; + LLViewerInventoryItem *item = iit->second; + if (item->getUUID() != item_id) + { + LL_WARNS("Inventory") << "item_id " << item_id << " does not match " << item->getUUID() << LL_ENDL; + validation_info->mWarnings["item_id_mismatch"]++; + warning_count++; + } + + const LLUUID& parent_id = item->getParentUUID(); + if (parent_id.isNull()) + { + LL_WARNS("Inventory") << "item " << item_id << " name [" << item->getName() << "] has null parent id!" << LL_ENDL; + orphaned_count++; + } + else + { + cat_array_t* cats; + item_array_t* items; + getDirectDescendentsOf(parent_id,cats,items); + if (!items) + { + LL_WARNS("Inventory") << "item " << item_id << " name [" << item->getName() + << "] orphaned - alleged parent has no child items list " << parent_id << LL_ENDL; + orphaned_count++; + } + else + { + bool found = false; + for (S32 i=0; isize(); ++i) + { + if (items->at(i) == item) + { + found = true; + break; + } + } + if (!found) + { + LL_WARNS("Inventory") << "item " << item_id << " name [" << item->getName() + << "] orphaned - not found as child of alleged parent " << parent_id << LL_ENDL; + orphaned_count++; + } + } + + } + // Link checking + if (item->getIsLinkType()) + { + const LLUUID& link_id = item->getUUID(); + const LLUUID& target_id = item->getLinkedUUID(); + LLViewerInventoryItem *target_item = getItem(target_id); + LLViewerInventoryCategory *target_cat = getCategory(target_id); + // Linked-to UUID should have back reference to this link. + if (!hasBacklinkInfo(link_id, target_id)) + { + LL_WARNS("Inventory") << "link " << item->getUUID() << " type " << item->getActualType() + << " missing backlink info at target_id " << target_id + << LL_ENDL; + orphaned_count++; + } + // Links should have referents. + if (item->getActualType() == LLAssetType::AT_LINK && !target_item) + { + LL_WARNS("Inventory") << "broken item link " << item->getName() << " id " << item->getUUID() << LL_ENDL; + orphaned_count++; + } + else if (item->getActualType() == LLAssetType::AT_LINK_FOLDER && !target_cat) + { + LL_WARNS("Inventory") << "broken folder link " << item->getName() << " id " << item->getUUID() << LL_ENDL; + orphaned_count++; + } + if (target_item && target_item->getIsLinkType()) + { + LL_WARNS("Inventory") << "link " << item->getName() << " references a link item " + << target_item->getName() << " " << target_item->getUUID() << LL_ENDL; + } + + // Links should not have backlinks. + std::pair range = mBacklinkMMap.equal_range(link_id); + if (range.first != range.second) + { + LL_WARNS("Inventory") << "Link item " << item->getName() << " has backlinks!" << LL_ENDL; + } + } + else + { + // Check the backlinks of a non-link item. + const LLUUID& target_id = item->getUUID(); + std::pair range = mBacklinkMMap.equal_range(target_id); + for (backlink_mmap_t::const_iterator it = range.first; it != range.second; ++it) + { + const LLUUID& link_id = it->second; + LLViewerInventoryItem *link_item = getItem(link_id); + if (!link_item || !link_item->getIsLinkType()) + { + LL_WARNS("Inventory") << "invalid backlink from target " << item->getName() << " to " << link_id << LL_ENDL; + } + } + } + } + + // Check system folders + for (auto fit=ft_counts_under_root.begin(); fit != ft_counts_under_root.end(); ++fit) + { + LL_DEBUGS("Inventory") << "Folder type " << fit->first << " count " << fit->second << " under root" << LL_ENDL; + } + for (auto fit=ft_counts_elsewhere.begin(); fit != ft_counts_elsewhere.end(); ++fit) + { + LL_DEBUGS("Inventory") << "Folder type " << fit->first << " count " << fit->second << " elsewhere" << LL_ENDL; + } + + static LLCachedControl fake_system_folder_issues(gSavedSettings, "QAModeFakeSystemFolderIssues", false); + static std::default_random_engine e{}; + static std::uniform_int_distribution<> distrib(0, 1); + for (S32 ft=LLFolderType::FT_TEXTURE; ft(ft); + if (LLFolderType::lookup(folder_type)==LLFolderType::badLookup()) + { + continue; + } + bool is_automatic = LLFolderType::lookupIsAutomaticType(folder_type); + bool is_singleton = LLFolderType::lookupIsSingletonType(folder_type); + S32 count_under_root = ft_counts_under_root[folder_type]; + S32 count_elsewhere = ft_counts_elsewhere[folder_type]; + if (fake_system_folder_issues) + { + // Force all counts to be either 0 or 2, thus flagged as an error. + count_under_root = 2*distrib(e); + count_elsewhere = 2*distrib(e); + validation_info->mFatalQADebugMode = true; + } + if (is_singleton) + { + if (count_under_root==0) + { + LL_WARNS("Inventory") << "Expected system folder type " << ft << " was not found under root" << LL_ENDL; + // Need to create, if allowed. + if (is_automatic) + { + LL_WARNS("Inventory") << "Fatal inventory corruption: cannot create system folder of type " << ft << LL_ENDL; + validation_info->mMissingRequiredSystemFolders.insert(folder_type); + fatal_errs++; + } + else + { + // Can create, and will when needed. + // (Not sure this is really a warning, but worth logging) + validation_info->mWarnings["missing_system_folder_can_create"]++; + warning_count++; + } + } + else if (count_under_root > 1) + { + validation_info->mDuplicateRequiredSystemFolders.insert(folder_type); + if (!is_automatic + && folder_type != LLFolderType::FT_SETTINGS + // FT_MATERIAL might need to be automatic like the rest of upload folders + && folder_type != LLFolderType::FT_MATERIAL + ) + { + // It is a fatal problem or can lead to fatal problems for COF, + // outfits, trash and other non-automatic folders. + validation_info->mFatalSystemDuplicate++; + fatal_errs++; + LL_WARNS("Inventory") << "Fatal inventory corruption: system folder type has excess copies under root, type " << ft << " count " << count_under_root << LL_ENDL; + } + else + { + // For automatic folders it's not a fatal issue and shouldn't + // break inventory or other functionality further + // Exception: FT_SETTINGS is not automatic, but only deserves a warning. + validation_info->mWarnings["non_fatal_system_duplicate_under_root"]++; + warning_count++; + LL_WARNS("Inventory") << "System folder type has excess copies under root, type " << ft << " count " << count_under_root << LL_ENDL; + } + } + if (count_elsewhere > 0) + { + LL_WARNS("Inventory") << "Found " << count_elsewhere << " extra folders of type " << ft << " outside of root" << LL_ENDL; + validation_info->mWarnings["non_fatal_system_duplicate_elsewhere"]++; + warning_count++; + } + } + } + + + if (cat_lock > 0 || item_lock > 0) + { + LL_INFOS("Inventory") << "Found locks on some categories: sub-cat arrays " + << cat_lock << ", item arrays " << item_lock << LL_ENDL; + } + if (desc_unknown_count != 0) + { + LL_DEBUGS() << "Found " << desc_unknown_count << " cats with unknown descendent count" << LL_ENDL; + } + if (version_unknown_count != 0) + { + LL_DEBUGS("Inventory") << "Found " << version_unknown_count << " cats with unknown version" << LL_ENDL; + } + + // FIXME need to fail login and tell user to retry, contact support if problem persists. + bool valid = (fatal_errs == 0); + LL_INFOS("Inventory") << "Validate done, fatal errors: " << fatal_errs << ", warnings: " << warning_count << ", valid: " << valid << LL_ENDL; + + validation_info->mFatalErrorCount = fatal_errs; + validation_info->mWarningCount = warning_count; + validation_info->mLoopCount = loop_count; + validation_info->mOrphanedCount = orphaned_count; + + return validation_info; +} + +// Provides a unix-style path from root, like "/My Inventory/Clothing/.../myshirt" +std::string LLInventoryModel::getFullPath(const LLInventoryObject *obj) const +{ + std::vector path_elts; + std::map visited; + while (obj != NULL && !visited[obj->getUUID()]) + { + path_elts.push_back(obj->getName()); + // avoid infinite loop in the unlikely event of a cycle + visited[obj->getUUID()] = true; + obj = getObject(obj->getParentUUID()); + } + std::stringstream s; + std::string delim("/"); + std::reverse(path_elts.begin(), path_elts.end()); + std::string result = "/" + boost::algorithm::join(path_elts, delim); + return result; +} + +///---------------------------------------------------------------------------- +/// Local function definitions +///---------------------------------------------------------------------------- + + +#if 0 +bool decompress_file(const char* src_filename, const char* dst_filename) +{ + bool rv = false; + gzFile src = NULL; + U8* buffer = NULL; + LLFILE* dst = NULL; + S32 bytes = 0; + const S32 DECOMPRESS_BUFFER_SIZE = 32000; + + // open the files + src = gzopen(src_filename, "rb"); + if(!src) goto err_decompress; + dst = LLFile::fopen(dst_filename, "wb"); + if(!dst) goto err_decompress; + + // decompress. + buffer = new U8[DECOMPRESS_BUFFER_SIZE + 1]; + + do + { + bytes = gzread(src, buffer, DECOMPRESS_BUFFER_SIZE); + if (bytes < 0) + { + goto err_decompress; + } + + fwrite(buffer, bytes, 1, dst); + } while(gzeof(src) == 0); + + // success + rv = true; + + err_decompress: + if(src != NULL) gzclose(src); + if(buffer != NULL) delete[] buffer; + if(dst != NULL) fclose(dst); + return rv; +} +#endif + + +///---------------------------------------------------------------------------- +/// Class LLInventoryModel::FetchItemHttpHandler +///---------------------------------------------------------------------------- + +LLInventoryModel::FetchItemHttpHandler::FetchItemHttpHandler(const LLSD & request_sd) + : LLCore::HttpHandler(), + mRequestSD(request_sd) +{} + +LLInventoryModel::FetchItemHttpHandler::~FetchItemHttpHandler() +{} + +void LLInventoryModel::FetchItemHttpHandler::onCompleted(LLCore::HttpHandle handle, + LLCore::HttpResponse * response) +{ + do // Single-pass do-while used for common exit handling + { + LLCore::HttpStatus status(response->getStatus()); + // status = LLCore::HttpStatus(404); // Dev tool to force error handling + if (! status) + { + processFailure(status, response); + break; // Goto common exit + } + + LLCore::BufferArray * body(response->getBody()); + // body = NULL; // Dev tool to force error handling + if (! body || ! body->size()) + { + LL_WARNS(LOG_INV) << "Missing data in inventory item query." << LL_ENDL; + processFailure("HTTP response for inventory item query missing body", response); + break; // Goto common exit + } + + // body->write(0, "Garbage Response", 16); // Dev tool to force error handling + LLSD body_llsd; + if (! LLCoreHttpUtil::responseToLLSD(response, true, body_llsd)) + { + // INFOS-level logging will occur on the parsed failure + processFailure("HTTP response for inventory item query has malformed LLSD", response); + break; // Goto common exit + } + + // Expect top-level structure to be a map + // body_llsd = LLSD::emptyArray(); // Dev tool to force error handling + if (! body_llsd.isMap()) + { + processFailure("LLSD response for inventory item not a map", response); + break; // Goto common exit + } + + // Check for 200-with-error failures + // + // Original Responder-based serivce model didn't check for these errors. + // It may be more robust to ignore this condition. With aggregated requests, + // an error in one inventory item might take down the entire request. + // So if this instead broke up the aggregated items into single requests, + // maybe that would make progress. Or perhaps there's structured information + // that can tell us what went wrong. Need to dig into this and firm up + // the API. + // + // body_llsd["error"] = LLSD::emptyMap(); // Dev tool to force error handling + // body_llsd["error"]["identifier"] = "Development"; + // body_llsd["error"]["message"] = "You left development code in the viewer"; + if (body_llsd.has("error")) + { + processFailure("Inventory application error (200-with-error)", response); + break; // Goto common exit + } + + // Okay, process data if possible + processData(body_llsd, response); + } + while (false); +} + +void LLInventoryModel::FetchItemHttpHandler::processData(LLSD & content, LLCore::HttpResponse * response) +{ + start_new_inventory_observer(); + +#if 0 + LLUUID agent_id; + agent_id = content["agent_id"].asUUID(); + if (agent_id != gAgent.getID()) + { + LL_WARNS(LOG_INV) << "Got a inventory update for the wrong agent: " << agent_id + << LL_ENDL; + return; + } +#endif + + LLInventoryModel::item_array_t items; + LLInventoryModel::update_map_t update; + LLUUID folder_id; + LLSD content_items(content["items"]); + const S32 count(content_items.size()); + + // Does this loop ever execute more than once? + for (S32 i(0); i < count; ++i) + { + LLPointer titem = new LLViewerInventoryItem; + titem->unpackMessage(content_items[i]); + + LL_DEBUGS(LOG_INV) << "ItemHttpHandler::httpSuccess item id: " + << titem->getUUID() << LL_ENDL; + items.push_back(titem); + + // examine update for changes. + LLViewerInventoryItem * itemp(gInventory.getItem(titem->getUUID())); + + if (itemp) + { + if (titem->getParentUUID() == itemp->getParentUUID()) + { + update[titem->getParentUUID()]; + } + else + { + ++update[titem->getParentUUID()]; + --update[itemp->getParentUUID()]; + } + } + else + { + ++update[titem->getParentUUID()]; + } + + if (folder_id.isNull()) + { + folder_id = titem->getParentUUID(); + } + } + + // as above, this loop never seems to loop more than once per call + for (LLInventoryModel::item_array_t::iterator it = items.begin(); it != items.end(); ++it) + { + gInventory.updateItem(*it); + } + gInventory.notifyObservers(); + gViewerWindow->getWindow()->decBusyCount(); +} + + +void LLInventoryModel::FetchItemHttpHandler::processFailure(LLCore::HttpStatus status, LLCore::HttpResponse * response) +{ + const std::string & ct(response->getContentType()); + LL_WARNS(LOG_INV) << "Inventory item fetch failure\n" + << "[Status: " << status.toTerseString() << "]\n" + << "[Reason: " << status.toString() << "]\n" + << "[Content-type: " << ct << "]\n" + << "[Content (abridged): " + << LLCoreHttpUtil::responseToString(response) << "]" << LL_ENDL; + gInventory.notifyObservers(); +} + +void LLInventoryModel::FetchItemHttpHandler::processFailure(const char * const reason, LLCore::HttpResponse * response) +{ + LL_WARNS(LOG_INV) << "Inventory item fetch failure\n" + << "[Status: internal error]\n" + << "[Reason: " << reason << "]\n" + << "[Content (abridged): " + << LLCoreHttpUtil::responseToString(response) << "]" << LL_ENDL; + gInventory.notifyObservers(); +} + diff --git a/indra/newview/llinventorymodel.h b/indra/newview/llinventorymodel.h index 0ab7dcb99b..1472b705e4 100644 --- a/indra/newview/llinventorymodel.h +++ b/indra/newview/llinventorymodel.h @@ -1,721 +1,721 @@ -/** - * @file llinventorymodel.h - * @brief LLInventoryModel class header file - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLINVENTORYMODEL_H -#define LL_LLINVENTORYMODEL_H - -#include -#include -#include -#include - -#include "llassettype.h" -#include "llfoldertype.h" -#include "llframetimer.h" -#include "lluuid.h" -#include "llpermissionsflags.h" -#include "llviewerinventory.h" -#include "llstring.h" -#include "httpcommon.h" -#include "httprequest.h" -#include "httpoptions.h" -#include "httpheaders.h" -#include "httphandler.h" -#include "lleventcoro.h" -#include "llcoros.h" - -class LLInventoryObserver; -class LLInventoryObject; -class LLInventoryItem; -class LLInventoryCategory; -class LLMessageSystem; -class LLInventoryCollectFunctor; - -///---------------------------------------------------------------------------- -/// LLInventoryValidationInfo -///---------------------------------------------------------------------------- -class LLInventoryValidationInfo: public LLRefCount -{ -public: - LLInventoryValidationInfo(); - void toOstream(std::ostream& os) const; - void asLLSD(LLSD& sd) const; - - bool mInitialized{false}; - S32 mWarningCount{0}; - std::map mWarnings; - - S32 mLoopCount{0}; // Presence of folders whose ancestors loop onto themselves - S32 mOrphanedCount{0}; // Missing or orphaned items, links and folders - - S32 mFatalErrorCount{0}; - bool mFatalNoRootFolder{false}; - S32 mFatalSystemDuplicate{0}; - bool mFatalNoLibraryRootFolder{false}; - bool mFatalQADebugMode{false}; - - std::set mMissingRequiredSystemFolders; - std::set mDuplicateRequiredSystemFolders; -}; -std::ostream& operator<<(std::ostream& s, const LLInventoryValidationInfo& v); - -///---------------------------------------------------------------------------- -// LLInventoryModel -// -// Represents a collection of inventory, and provides efficient ways to access -// that information. -// NOTE: This class could in theory be used for any place where you need -// inventory, though it optimizes for time efficiency - not space efficiency, -// probably making it inappropriate for use on tasks. -///---------------------------------------------------------------------------- -class LLInventoryModel -{ - LOG_CLASS(LLInventoryModel); - -public: - enum EHasChildren - { - CHILDREN_NO, - CHILDREN_YES, - CHILDREN_MAYBE - }; - - typedef std::vector > cat_array_t; - typedef std::vector > item_array_t; - typedef std::set changed_items_t; - - // Rider: This is using the old responder patter. It should be refactored to - // take advantage of coroutines. - - // HTTP handler for individual item requests (inventory or library). - // Background item requests are derived from this in the background - // inventory system. All folder requests are also located there - // but have their own handler derived from HttpHandler. - class FetchItemHttpHandler : public LLCore::HttpHandler - { - public: - LOG_CLASS(FetchItemHttpHandler); - - FetchItemHttpHandler(const LLSD & request_sd); - virtual ~FetchItemHttpHandler(); - - protected: - FetchItemHttpHandler(const FetchItemHttpHandler &); // Not defined - void operator=(const FetchItemHttpHandler &); // Not defined - - public: - virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); - - private: - void processData(LLSD & body, LLCore::HttpResponse * response); - void processFailure(LLCore::HttpStatus status, LLCore::HttpResponse * response); - void processFailure(const char * const reason, LLCore::HttpResponse * response); - - private: - LLSD mRequestSD; - }; - -/******************************************************************************** - ** ** - ** INITIALIZATION/SETUP - **/ - - //-------------------------------------------------------------------- - // Constructors / Destructors - //-------------------------------------------------------------------- -public: - LLInventoryModel(); - ~LLInventoryModel(); - void cleanupInventory(); -protected: - void empty(); // empty the entire contents - - //-------------------------------------------------------------------- - // Initialization - //-------------------------------------------------------------------- -public: - // The inventory model usage is sensitive to the initial construction of the model - bool isInventoryUsable() const; -private: - bool mIsAgentInvUsable; // used to handle an invalid inventory state - - // One-time initialization of HTTP system. - void initHttpRequest(); - - //-------------------------------------------------------------------- - // Root Folders - //-------------------------------------------------------------------- -public: - // The following are set during login with data from the server - void setRootFolderID(const LLUUID& id); - void setLibraryOwnerID(const LLUUID& id); - void setLibraryRootFolderID(const LLUUID& id); - - const LLUUID &getRootFolderID() const; - const LLUUID &getLibraryOwnerID() const; - const LLUUID &getLibraryRootFolderID() const; -private: - LLUUID mRootFolderID; - LLUUID mLibraryRootFolderID; - LLUUID mLibraryOwnerID; - - //-------------------------------------------------------------------- - // Structure - //-------------------------------------------------------------------- -public: - // Methods to load up inventory skeleton & meat. These are used - // during authentication. Returns true if everything parsed. - bool loadSkeleton(const LLSD& options, const LLUUID& owner_id); - void buildParentChildMap(); // brute force method to rebuild the entire parent-child relations - void createCommonSystemCategories(); - - static std::string getInvCacheAddres(const LLUUID& owner_id); - - // Call on logout to save a terse representation. - void cache(const LLUUID& parent_folder_id, const LLUUID& agent_id); -private: - // Information for tracking the actual inventory. We index this - // information in a lot of different ways so we can access - // the inventory using several different identifiers. - // mInventory member data is the 'master' list of inventory, and - // mCategoryMap and mItemMap store uuid->object mappings. - typedef std::map > cat_map_t; - typedef std::map > item_map_t; - cat_map_t mCategoryMap; - item_map_t mItemMap; - // This last set of indices is used to map parents to children. - typedef std::map parent_cat_map_t; - typedef std::map parent_item_map_t; - parent_cat_map_t mParentChildCategoryTree; - parent_item_map_t mParentChildItemTree; - - // Track links to items and categories. We do not store item or - // category pointers here, because broken links are also supported. - typedef std::multimap backlink_mmap_t; - backlink_mmap_t mBacklinkMMap; // key = target_id: ID of item, values = link_ids: IDs of item or folder links referencing it. - // For internal use only - bool hasBacklinkInfo(const LLUUID& link_id, const LLUUID& target_id) const; - void addBacklinkInfo(const LLUUID& link_id, const LLUUID& target_id); - void removeBacklinkInfo(const LLUUID& link_id, const LLUUID& target_id); - - //-------------------------------------------------------------------- - // Login - //-------------------------------------------------------------------- -public: - static bool getIsFirstTimeInViewer2(); - static bool isSysFoldersReady() { return (sPendingSystemFolders == 0); } - -private: - static bool sFirstTimeInViewer2; - const static S32 sCurrentInvCacheVersion; // expected inventory cache version - - static S32 sPendingSystemFolders; - -/** Initialization/Setup - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** ACCESSORS - **/ - - //-------------------------------------------------------------------- - // Descendants - //-------------------------------------------------------------------- -public: - // Make sure we have the descendants in the structure. Returns true - // if a fetch was performed. - bool fetchDescendentsOf(const LLUUID& folder_id) const; - - // Return the direct descendants of the id provided.Set passed - // in values to NULL if the call fails. - // NOTE: The array provided points straight into the guts of - // this object, and should only be used for read operations, since - // modifications may invalidate the internal state of the inventory. - void getDirectDescendentsOf(const LLUUID& cat_id, - cat_array_t*& categories, - item_array_t*& items) const; - void getDirectDescendentsOf(const LLUUID& cat_id, cat_array_t& categories, item_array_t& items, LLInventoryCollectFunctor& f) const; - - typedef LLUUID digest_t; // To clarify the actual usage of this "UUID" - // Compute a hash of direct descendant names (for detecting child name changes) - digest_t hashDirectDescendentNames(const LLUUID& cat_id) const; - - // Starting with the object specified, add its descendants to the - // array provided, but do not add the inventory object specified - // by id. There is no guaranteed order. - // NOTE: Neither array will be erased before adding objects to it. - // Do not store a copy of the pointers collected - use them, and - // collect them again later if you need to reference the same objects. - enum { - EXCLUDE_TRASH = false, - INCLUDE_TRASH = true - }; - // Simpler existence test if matches don't actually need to be collected. - bool hasMatchingDirectDescendent(const LLUUID& cat_id, - LLInventoryCollectFunctor& filter); - void collectDescendents(const LLUUID& id, - cat_array_t& categories, - item_array_t& items, - bool include_trash); - void collectDescendentsIf(const LLUUID& id, - cat_array_t& categories, - item_array_t& items, - bool include_trash, - LLInventoryCollectFunctor& add); - - // Collect all items in inventory that are linked to item_id. - // Assumes item_id is itself not a linked item. - item_array_t collectLinksTo(const LLUUID& item_id); - - // Check if one object has a parent chain up to the category specified by UUID. - bool isObjectDescendentOf(const LLUUID& obj_id, const LLUUID& cat_id) const; - - enum EAncestorResult{ - ANCESTOR_OK = 0, - ANCESTOR_MISSING = 1, - ANCESTOR_LOOP = 2, - }; - // Follow parent chain to the top. - EAncestorResult getObjectTopmostAncestor(const LLUUID& object_id, LLUUID& result) const; - - //-------------------------------------------------------------------- - // Find - //-------------------------------------------------------------------- -public: - - // Checks if category exists ("My Inventory" only), if it does not, creates it - void ensureCategoryForTypeExists(LLFolderType::EType preferred_type); - - const LLUUID findCategoryUUIDForTypeInRoot( - LLFolderType::EType preferred_type, - const LLUUID& root_id) const; - - // Returns the uuid of the category that specifies 'type' as what it - // defaults to containing. The category is not necessarily only for that type. - // NOTE: If create_folder is true, this will create a new inventory category - // on the fly if one does not exist. *NOTE: if find_in_library is true it - // will search in the user's library folder instead of "My Inventory" - const LLUUID findCategoryUUIDForType(LLFolderType::EType preferred_type) const; - // will search in the user's library folder instead of "My Inventory" - const LLUUID findLibraryCategoryUUIDForType(LLFolderType::EType preferred_type) const; - // Returns user specified category for uploads, returns default id if there are no - // user specified one or it does not exist, creates default category if it is missing. - const LLUUID findUserDefinedCategoryUUIDForType(LLFolderType::EType preferred_type) const; - - // Get whatever special folder this object is a child of, if any. - const LLViewerInventoryCategory *getFirstNondefaultParent(const LLUUID& obj_id) const; - - // Get first descendant of the child object under the specified parent - const LLViewerInventoryCategory *getFirstDescendantOf(const LLUUID& master_parent_id, const LLUUID& obj_id) const; - - // Get the object by id. Returns NULL if not found. - // NOTE: Use the pointer returned for read operations - do - // not modify the object values in place or you will break stuff. - LLInventoryObject* getObject(const LLUUID& id) const; - - // Get the item by id. Returns NULL if not found. - // NOTE: Use the pointer for read operations - use the - // updateItem() method to actually modify values. - LLViewerInventoryItem* getItem(const LLUUID& id) const; - - // Get the category by id. Returns NULL if not found. - // NOTE: Use the pointer for read operations - use the - // updateCategory() method to actually modify values. - LLViewerInventoryCategory* getCategory(const LLUUID& id) const; - - // Get the inventoryID or item that this item points to, else just return object_id - const LLUUID& getLinkedItemID(const LLUUID& object_id) const; - LLViewerInventoryItem* getLinkedItem(const LLUUID& object_id) const; - - // Copy content of all folders of type "type" into folder "id" and delete/purge the empty folders - // Note : This method has been designed for FT_OUTBOX (aka Merchant Outbox) but can be used for other categories - void consolidateForType(const LLUUID& id, LLFolderType::EType type); - -private: - mutable LLPointer mLastItem; // cache recent lookups - - //-------------------------------------------------------------------- - // Count - //-------------------------------------------------------------------- -public: - // Return the number of items or categories - S32 getItemCount() const; - S32 getCategoryCount() const; - -/** Accessors - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** MUTATORS - **/ - -public: - // Change an existing item with a matching item_id or add the item - // to the current inventory. Returns the change mask generated by - // the update. No notification will be sent to observers. This - // method will only generate network traffic if the item had to be - // reparented. - // NOTE: In usage, you will want to perform cache accounting - // operations in LLInventoryModel::accountForUpdate() or - // LLViewerInventoryItem::updateServer() before calling this method. - U32 updateItem(const LLViewerInventoryItem* item, U32 mask = 0); - - // Change an existing item with the matching id or add - // the category. No notification will be sent to observers. This - // method will only generate network traffic if the item had to be - // reparented. - // NOTE: In usage, you will want to perform cache accounting - // operations in accountForUpdate() or LLViewerInventoryCategory:: - // updateServer() before calling this method. - void updateCategory(const LLViewerInventoryCategory* cat, U32 mask = 0); - - // Move the specified object id to the specified category and - // update the internal structures. No cache accounting, - // observer notification, or server update is performed. - void moveObject(const LLUUID& object_id, const LLUUID& cat_id); - - // Migrated from llinventoryfunctions - void changeItemParent(LLViewerInventoryItem* item, - const LLUUID& new_parent_id, - bool restamp); - - // Migrated from llinventoryfunctions - void changeCategoryParent(LLViewerInventoryCategory* cat, - const LLUUID& new_parent_id, - bool restamp); - - // Marks links from a "possibly" broken list for a rebuild - // clears the list - void rebuildBrockenLinks(); - bool hasPosiblyBrockenLinks() const { return mPossiblyBrockenLinks.size() > 0; } - - //-------------------------------------------------------------------- - // Delete - //-------------------------------------------------------------------- -public: - - // Update model after an item is confirmed as removed from - // server. Works for categories or items. - void onObjectDeletedFromServer(const LLUUID& item_id, - bool fix_broken_links = true, - bool update_parent_version = true, - bool do_notify_observers = true); - - // Update model after all descendants removed from server. - void onDescendentsPurgedFromServer(const LLUUID& object_id, bool fix_broken_links = true); - - // Update model after an existing item gets updated on server. - void onItemUpdated(const LLUUID& item_id, const LLSD& updates, bool update_parent_version); - - // Update model after an existing category gets updated on server. - void onCategoryUpdated(const LLUUID& cat_id, const LLSD& updates); - - // Delete a particular inventory object by ID. Will purge one - // object from the internal data structures, maintaining a - // consistent internal state. No cache accounting, observer - // notification, or server update is performed. - void deleteObject(const LLUUID& id, bool fix_broken_links = true, bool do_notify_observers = true); - /// move Item item_id to Trash - void removeItem(const LLUUID& item_id); - /// move Category category_id to Trash - void removeCategory(const LLUUID& category_id); - /// removeItem() or removeCategory(), whichever is appropriate - void removeObject(const LLUUID& object_id); - - // "TrashIsFull" when trash exceeds maximum capacity - void checkTrashOverflow(); - -protected: - void rebuildLinkItems(LLInventoryModel::item_array_t& items); - - //-------------------------------------------------------------------- - // Reorder - //-------------------------------------------------------------------- -public: - // Changes items order by insertion of the item identified by src_item_id - // before (or after) the item identified by dest_item_id. Both items must exist in items array. - // Sorting is stored after method is finished. Only src_item_id is moved before (or after) dest_item_id. - // The parameter "insert_before" controls on which side of dest_item_id src_item_id gets reinserted. - static void updateItemsOrder(LLInventoryModel::item_array_t& items, - const LLUUID& src_item_id, - const LLUUID& dest_item_id, - bool insert_before = true); - // Gets an iterator on an item vector knowing only the item UUID. - // Returns end() of the vector if not found. - static LLInventoryModel::item_array_t::iterator findItemIterByUUID(LLInventoryModel::item_array_t& items, const LLUUID& id); - - - // Rearranges Landmarks inside Favorites folder. - // Moves source landmark before target one. - void rearrangeFavoriteLandmarks(const LLUUID& source_item_id, const LLUUID& target_item_id); - //void saveItemsOrder(const LLInventoryModel::item_array_t& items); - - //-------------------------------------------------------------------- - // Creation - //-------------------------------------------------------------------- -public: - // Returns the UUID of the new category. If you want to use the default - // name based on type, pass in a NULL to the 'name' parameter. - void createNewCategory(const LLUUID& parent_id, - LLFolderType::EType preferred_type, - const std::string& name, - inventory_func_type callback = NULL, - const LLUUID& thumbnail_id = LLUUID::null); -protected: - // Internal methods that add inventory and make sure that all of - // the internal data structures are consistent. These methods - // should be passed pointers of newly created objects, and the - // instance will take over the memory management from there. - void addCategory(LLViewerInventoryCategory* category); - void addItem(LLViewerInventoryItem* item); - - void createNewCategoryCoro(std::string url, LLSD postData, inventory_func_type callback); - -/** Mutators - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** CATEGORY ACCOUNTING - **/ - -public: - // Represents the number of items added or removed from a category. - struct LLCategoryUpdate - { - LLCategoryUpdate() : mDescendentDelta(0), mChangeVersion(true) {} - LLCategoryUpdate(const LLUUID& category_id, S32 delta, bool change_version = true) : - mCategoryID(category_id), - mDescendentDelta(delta), - mChangeVersion(change_version) {} - LLUUID mCategoryID; - S32 mDescendentDelta; - bool mChangeVersion; - }; - typedef std::vector update_list_t; - - // This exists to make it easier to account for deltas in a map. - struct LLInitializedS32 - { - LLInitializedS32() : mValue(0) {} - LLInitializedS32(S32 value) : mValue(value) {} - S32 mValue; - LLInitializedS32& operator++() { ++mValue; return *this; } - LLInitializedS32& operator--() { --mValue; return *this; } - }; - typedef std::map update_map_t; - - // Call when there are category updates. Call them *before* the - // actual update so the method can do descendent accounting correctly. - void accountForUpdate(const LLCategoryUpdate& update) const; - void accountForUpdate(const update_list_t& updates) const; - void accountForUpdate(const update_map_t& updates) const; - - // Return (yes/no/maybe) child status of category children. - EHasChildren categoryHasChildren(const LLUUID& cat_id) const; - - // Returns true if category version is known and theoretical - // descendents == actual descendents. - bool isCategoryComplete(const LLUUID& cat_id) const; - -/** Category Accounting - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** NOTIFICATIONS - **/ - -public: - // Called by the idle loop. Only updates if new state is detected. Call - // notifyObservers() manually to update regardless of whether state change - // has been indicated. - void idleNotifyObservers(); - - // Call to explicitly update everyone on a new state. - void notifyObservers(); - - // Allows outsiders to tell the inventory if something has - // been changed 'under the hood', but outside the control of the - // inventory. The next notify will include that notification. - void addChangedMask(U32 mask, const LLUUID& referent); - - const changed_items_t& getChangedIDs() const { return mChangedItemIDs; } - const changed_items_t& getAddedIDs() const { return mAddedItemIDs; } -protected: - // Updates all linked items pointing to this id. - void addChangedMaskForLinks(const LLUUID& object_id, U32 mask); -private: - // Flag set when notifyObservers is being called, to look for bugs - // where it's called recursively. - bool mIsNotifyObservers; - // Variables used to track what has changed since the last notify. - U32 mModifyMask; - changed_items_t mChangedItemIDs; - changed_items_t mAddedItemIDs; - // Fallback when notifyObservers is in progress - U32 mModifyMaskBacklog; - changed_items_t mChangedItemIDsBacklog; - changed_items_t mAddedItemIDsBacklog; - typedef std::map broken_links_t; - broken_links_t mPossiblyBrockenLinks; // there can be multiple links per item - changed_items_t mLinksRebuildList; - boost::signals2::connection mBulkFecthCallbackSlot; - - - //-------------------------------------------------------------------- - // Observers - //-------------------------------------------------------------------- -public: - // If the observer is destroyed, be sure to remove it. - void addObserver(LLInventoryObserver* observer); - void removeObserver(LLInventoryObserver* observer); - bool containsObserver(LLInventoryObserver* observer) const; -private: - typedef std::set observer_list_t; - observer_list_t mObservers; - -/** Notifications - ** ** - *******************************************************************************/ - - -/******************************************************************************** - ** ** - ** HTTP Transport - **/ -public: - // Invoke handler completion method (onCompleted) for all - // requests that are ready. - void handleResponses(bool foreground); - - // Request an inventory HTTP operation to either the - // foreground or background processor. These are actually - // the same service queue but the background requests are - // seviced more slowly effectively de-prioritizing new - // requests. - LLCore::HttpHandle requestPost(bool foreground, - const std::string & url, - const LLSD & body, - const LLCore::HttpHandler::ptr_t &handler, - const char * const message); - -private: - // Usual plumbing for LLCore:: HTTP operations. - LLCore::HttpRequest * mHttpRequestFG; - LLCore::HttpRequest * mHttpRequestBG; - LLCore::HttpOptions::ptr_t mHttpOptions; - LLCore::HttpHeaders::ptr_t mHttpHeaders; - LLCore::HttpRequest::policy_t mHttpPolicyClass; - -/** HTTP Transport - ** ** - *******************************************************************************/ - - -/******************************************************************************** - ** ** - ** MISCELLANEOUS - **/ - - //-------------------------------------------------------------------- - // Callbacks - //-------------------------------------------------------------------- -public: - // Trigger a notification and empty the folder type (FT_TRASH or FT_LOST_AND_FOUND) if confirmed - void emptyFolderType(const std::string notification, LLFolderType::EType folder_type); - bool callbackEmptyFolderType(const LLSD& notification, const LLSD& response, LLFolderType::EType preferred_type); - static void registerCallbacks(LLMessageSystem* msg); - - //-------------------------------------------------------------------- - // File I/O - //-------------------------------------------------------------------- -protected: - static bool loadFromFile(const std::string& filename, - cat_array_t& categories, - item_array_t& items, - changed_items_t& cats_to_update, - bool& is_cache_obsolete); - static bool saveToFile(const std::string& filename, - const cat_array_t& categories, - const item_array_t& items); - - //-------------------------------------------------------------------- - // Message handling functionality - //-------------------------------------------------------------------- -public: - static void processUpdateCreateInventoryItem(LLMessageSystem* msg, void**); - static void removeInventoryItem(LLUUID agent_id, LLMessageSystem* msg, const char* msg_label); - static void processRemoveInventoryItem(LLMessageSystem* msg, void**); - static void removeInventoryFolder(LLUUID agent_id, LLMessageSystem* msg); - static void processRemoveInventoryFolder(LLMessageSystem* msg, void**); - static void processRemoveInventoryObjects(LLMessageSystem* msg, void**); - static void processSaveAssetIntoInventory(LLMessageSystem* msg, void**); - static void processBulkUpdateInventory(LLMessageSystem* msg, void**); - static void processMoveInventoryItem(LLMessageSystem* msg, void**); -protected: - bool messageUpdateCore(LLMessageSystem* msg, bool do_accounting, U32 mask = 0x0); - - //-------------------------------------------------------------------- - // Locks - //-------------------------------------------------------------------- -public: - void lockDirectDescendentArrays(const LLUUID& cat_id, - cat_array_t*& categories, - item_array_t*& items); - void unlockDirectDescendentArrays(const LLUUID& cat_id); -protected: - cat_array_t* getUnlockedCatArray(const LLUUID& id); - item_array_t* getUnlockedItemArray(const LLUUID& id); -private: - std::map mCategoryLock; - std::map mItemLock; - - //-------------------------------------------------------------------- - // Debugging - //-------------------------------------------------------------------- -public: - void dumpInventory() const; - LLPointer validate() const; - LLPointer mValidationInfo; - std::string getFullPath(const LLInventoryObject *obj) const; - -/** Miscellaneous - ** ** - *******************************************************************************/ -}; - -// a special inventory model for the agent -extern LLInventoryModel gInventory; - -#endif // LL_LLINVENTORYMODEL_H - +/** + * @file llinventorymodel.h + * @brief LLInventoryModel class header file + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLINVENTORYMODEL_H +#define LL_LLINVENTORYMODEL_H + +#include +#include +#include +#include + +#include "llassettype.h" +#include "llfoldertype.h" +#include "llframetimer.h" +#include "lluuid.h" +#include "llpermissionsflags.h" +#include "llviewerinventory.h" +#include "llstring.h" +#include "httpcommon.h" +#include "httprequest.h" +#include "httpoptions.h" +#include "httpheaders.h" +#include "httphandler.h" +#include "lleventcoro.h" +#include "llcoros.h" + +class LLInventoryObserver; +class LLInventoryObject; +class LLInventoryItem; +class LLInventoryCategory; +class LLMessageSystem; +class LLInventoryCollectFunctor; + +///---------------------------------------------------------------------------- +/// LLInventoryValidationInfo +///---------------------------------------------------------------------------- +class LLInventoryValidationInfo: public LLRefCount +{ +public: + LLInventoryValidationInfo(); + void toOstream(std::ostream& os) const; + void asLLSD(LLSD& sd) const; + + bool mInitialized{false}; + S32 mWarningCount{0}; + std::map mWarnings; + + S32 mLoopCount{0}; // Presence of folders whose ancestors loop onto themselves + S32 mOrphanedCount{0}; // Missing or orphaned items, links and folders + + S32 mFatalErrorCount{0}; + bool mFatalNoRootFolder{false}; + S32 mFatalSystemDuplicate{0}; + bool mFatalNoLibraryRootFolder{false}; + bool mFatalQADebugMode{false}; + + std::set mMissingRequiredSystemFolders; + std::set mDuplicateRequiredSystemFolders; +}; +std::ostream& operator<<(std::ostream& s, const LLInventoryValidationInfo& v); + +///---------------------------------------------------------------------------- +// LLInventoryModel +// +// Represents a collection of inventory, and provides efficient ways to access +// that information. +// NOTE: This class could in theory be used for any place where you need +// inventory, though it optimizes for time efficiency - not space efficiency, +// probably making it inappropriate for use on tasks. +///---------------------------------------------------------------------------- +class LLInventoryModel +{ + LOG_CLASS(LLInventoryModel); + +public: + enum EHasChildren + { + CHILDREN_NO, + CHILDREN_YES, + CHILDREN_MAYBE + }; + + typedef std::vector > cat_array_t; + typedef std::vector > item_array_t; + typedef std::set changed_items_t; + + // Rider: This is using the old responder patter. It should be refactored to + // take advantage of coroutines. + + // HTTP handler for individual item requests (inventory or library). + // Background item requests are derived from this in the background + // inventory system. All folder requests are also located there + // but have their own handler derived from HttpHandler. + class FetchItemHttpHandler : public LLCore::HttpHandler + { + public: + LOG_CLASS(FetchItemHttpHandler); + + FetchItemHttpHandler(const LLSD & request_sd); + virtual ~FetchItemHttpHandler(); + + protected: + FetchItemHttpHandler(const FetchItemHttpHandler &); // Not defined + void operator=(const FetchItemHttpHandler &); // Not defined + + public: + virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); + + private: + void processData(LLSD & body, LLCore::HttpResponse * response); + void processFailure(LLCore::HttpStatus status, LLCore::HttpResponse * response); + void processFailure(const char * const reason, LLCore::HttpResponse * response); + + private: + LLSD mRequestSD; + }; + +/******************************************************************************** + ** ** + ** INITIALIZATION/SETUP + **/ + + //-------------------------------------------------------------------- + // Constructors / Destructors + //-------------------------------------------------------------------- +public: + LLInventoryModel(); + ~LLInventoryModel(); + void cleanupInventory(); +protected: + void empty(); // empty the entire contents + + //-------------------------------------------------------------------- + // Initialization + //-------------------------------------------------------------------- +public: + // The inventory model usage is sensitive to the initial construction of the model + bool isInventoryUsable() const; +private: + bool mIsAgentInvUsable; // used to handle an invalid inventory state + + // One-time initialization of HTTP system. + void initHttpRequest(); + + //-------------------------------------------------------------------- + // Root Folders + //-------------------------------------------------------------------- +public: + // The following are set during login with data from the server + void setRootFolderID(const LLUUID& id); + void setLibraryOwnerID(const LLUUID& id); + void setLibraryRootFolderID(const LLUUID& id); + + const LLUUID &getRootFolderID() const; + const LLUUID &getLibraryOwnerID() const; + const LLUUID &getLibraryRootFolderID() const; +private: + LLUUID mRootFolderID; + LLUUID mLibraryRootFolderID; + LLUUID mLibraryOwnerID; + + //-------------------------------------------------------------------- + // Structure + //-------------------------------------------------------------------- +public: + // Methods to load up inventory skeleton & meat. These are used + // during authentication. Returns true if everything parsed. + bool loadSkeleton(const LLSD& options, const LLUUID& owner_id); + void buildParentChildMap(); // brute force method to rebuild the entire parent-child relations + void createCommonSystemCategories(); + + static std::string getInvCacheAddres(const LLUUID& owner_id); + + // Call on logout to save a terse representation. + void cache(const LLUUID& parent_folder_id, const LLUUID& agent_id); +private: + // Information for tracking the actual inventory. We index this + // information in a lot of different ways so we can access + // the inventory using several different identifiers. + // mInventory member data is the 'master' list of inventory, and + // mCategoryMap and mItemMap store uuid->object mappings. + typedef std::map > cat_map_t; + typedef std::map > item_map_t; + cat_map_t mCategoryMap; + item_map_t mItemMap; + // This last set of indices is used to map parents to children. + typedef std::map parent_cat_map_t; + typedef std::map parent_item_map_t; + parent_cat_map_t mParentChildCategoryTree; + parent_item_map_t mParentChildItemTree; + + // Track links to items and categories. We do not store item or + // category pointers here, because broken links are also supported. + typedef std::multimap backlink_mmap_t; + backlink_mmap_t mBacklinkMMap; // key = target_id: ID of item, values = link_ids: IDs of item or folder links referencing it. + // For internal use only + bool hasBacklinkInfo(const LLUUID& link_id, const LLUUID& target_id) const; + void addBacklinkInfo(const LLUUID& link_id, const LLUUID& target_id); + void removeBacklinkInfo(const LLUUID& link_id, const LLUUID& target_id); + + //-------------------------------------------------------------------- + // Login + //-------------------------------------------------------------------- +public: + static bool getIsFirstTimeInViewer2(); + static bool isSysFoldersReady() { return (sPendingSystemFolders == 0); } + +private: + static bool sFirstTimeInViewer2; + const static S32 sCurrentInvCacheVersion; // expected inventory cache version + + static S32 sPendingSystemFolders; + +/** Initialization/Setup + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** ACCESSORS + **/ + + //-------------------------------------------------------------------- + // Descendants + //-------------------------------------------------------------------- +public: + // Make sure we have the descendants in the structure. Returns true + // if a fetch was performed. + bool fetchDescendentsOf(const LLUUID& folder_id) const; + + // Return the direct descendants of the id provided.Set passed + // in values to NULL if the call fails. + // NOTE: The array provided points straight into the guts of + // this object, and should only be used for read operations, since + // modifications may invalidate the internal state of the inventory. + void getDirectDescendentsOf(const LLUUID& cat_id, + cat_array_t*& categories, + item_array_t*& items) const; + void getDirectDescendentsOf(const LLUUID& cat_id, cat_array_t& categories, item_array_t& items, LLInventoryCollectFunctor& f) const; + + typedef LLUUID digest_t; // To clarify the actual usage of this "UUID" + // Compute a hash of direct descendant names (for detecting child name changes) + digest_t hashDirectDescendentNames(const LLUUID& cat_id) const; + + // Starting with the object specified, add its descendants to the + // array provided, but do not add the inventory object specified + // by id. There is no guaranteed order. + // NOTE: Neither array will be erased before adding objects to it. + // Do not store a copy of the pointers collected - use them, and + // collect them again later if you need to reference the same objects. + enum { + EXCLUDE_TRASH = false, + INCLUDE_TRASH = true + }; + // Simpler existence test if matches don't actually need to be collected. + bool hasMatchingDirectDescendent(const LLUUID& cat_id, + LLInventoryCollectFunctor& filter); + void collectDescendents(const LLUUID& id, + cat_array_t& categories, + item_array_t& items, + bool include_trash); + void collectDescendentsIf(const LLUUID& id, + cat_array_t& categories, + item_array_t& items, + bool include_trash, + LLInventoryCollectFunctor& add); + + // Collect all items in inventory that are linked to item_id. + // Assumes item_id is itself not a linked item. + item_array_t collectLinksTo(const LLUUID& item_id); + + // Check if one object has a parent chain up to the category specified by UUID. + bool isObjectDescendentOf(const LLUUID& obj_id, const LLUUID& cat_id) const; + + enum EAncestorResult{ + ANCESTOR_OK = 0, + ANCESTOR_MISSING = 1, + ANCESTOR_LOOP = 2, + }; + // Follow parent chain to the top. + EAncestorResult getObjectTopmostAncestor(const LLUUID& object_id, LLUUID& result) const; + + //-------------------------------------------------------------------- + // Find + //-------------------------------------------------------------------- +public: + + // Checks if category exists ("My Inventory" only), if it does not, creates it + void ensureCategoryForTypeExists(LLFolderType::EType preferred_type); + + const LLUUID findCategoryUUIDForTypeInRoot( + LLFolderType::EType preferred_type, + const LLUUID& root_id) const; + + // Returns the uuid of the category that specifies 'type' as what it + // defaults to containing. The category is not necessarily only for that type. + // NOTE: If create_folder is true, this will create a new inventory category + // on the fly if one does not exist. *NOTE: if find_in_library is true it + // will search in the user's library folder instead of "My Inventory" + const LLUUID findCategoryUUIDForType(LLFolderType::EType preferred_type) const; + // will search in the user's library folder instead of "My Inventory" + const LLUUID findLibraryCategoryUUIDForType(LLFolderType::EType preferred_type) const; + // Returns user specified category for uploads, returns default id if there are no + // user specified one or it does not exist, creates default category if it is missing. + const LLUUID findUserDefinedCategoryUUIDForType(LLFolderType::EType preferred_type) const; + + // Get whatever special folder this object is a child of, if any. + const LLViewerInventoryCategory *getFirstNondefaultParent(const LLUUID& obj_id) const; + + // Get first descendant of the child object under the specified parent + const LLViewerInventoryCategory *getFirstDescendantOf(const LLUUID& master_parent_id, const LLUUID& obj_id) const; + + // Get the object by id. Returns NULL if not found. + // NOTE: Use the pointer returned for read operations - do + // not modify the object values in place or you will break stuff. + LLInventoryObject* getObject(const LLUUID& id) const; + + // Get the item by id. Returns NULL if not found. + // NOTE: Use the pointer for read operations - use the + // updateItem() method to actually modify values. + LLViewerInventoryItem* getItem(const LLUUID& id) const; + + // Get the category by id. Returns NULL if not found. + // NOTE: Use the pointer for read operations - use the + // updateCategory() method to actually modify values. + LLViewerInventoryCategory* getCategory(const LLUUID& id) const; + + // Get the inventoryID or item that this item points to, else just return object_id + const LLUUID& getLinkedItemID(const LLUUID& object_id) const; + LLViewerInventoryItem* getLinkedItem(const LLUUID& object_id) const; + + // Copy content of all folders of type "type" into folder "id" and delete/purge the empty folders + // Note : This method has been designed for FT_OUTBOX (aka Merchant Outbox) but can be used for other categories + void consolidateForType(const LLUUID& id, LLFolderType::EType type); + +private: + mutable LLPointer mLastItem; // cache recent lookups + + //-------------------------------------------------------------------- + // Count + //-------------------------------------------------------------------- +public: + // Return the number of items or categories + S32 getItemCount() const; + S32 getCategoryCount() const; + +/** Accessors + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** MUTATORS + **/ + +public: + // Change an existing item with a matching item_id or add the item + // to the current inventory. Returns the change mask generated by + // the update. No notification will be sent to observers. This + // method will only generate network traffic if the item had to be + // reparented. + // NOTE: In usage, you will want to perform cache accounting + // operations in LLInventoryModel::accountForUpdate() or + // LLViewerInventoryItem::updateServer() before calling this method. + U32 updateItem(const LLViewerInventoryItem* item, U32 mask = 0); + + // Change an existing item with the matching id or add + // the category. No notification will be sent to observers. This + // method will only generate network traffic if the item had to be + // reparented. + // NOTE: In usage, you will want to perform cache accounting + // operations in accountForUpdate() or LLViewerInventoryCategory:: + // updateServer() before calling this method. + void updateCategory(const LLViewerInventoryCategory* cat, U32 mask = 0); + + // Move the specified object id to the specified category and + // update the internal structures. No cache accounting, + // observer notification, or server update is performed. + void moveObject(const LLUUID& object_id, const LLUUID& cat_id); + + // Migrated from llinventoryfunctions + void changeItemParent(LLViewerInventoryItem* item, + const LLUUID& new_parent_id, + bool restamp); + + // Migrated from llinventoryfunctions + void changeCategoryParent(LLViewerInventoryCategory* cat, + const LLUUID& new_parent_id, + bool restamp); + + // Marks links from a "possibly" broken list for a rebuild + // clears the list + void rebuildBrockenLinks(); + bool hasPosiblyBrockenLinks() const { return mPossiblyBrockenLinks.size() > 0; } + + //-------------------------------------------------------------------- + // Delete + //-------------------------------------------------------------------- +public: + + // Update model after an item is confirmed as removed from + // server. Works for categories or items. + void onObjectDeletedFromServer(const LLUUID& item_id, + bool fix_broken_links = true, + bool update_parent_version = true, + bool do_notify_observers = true); + + // Update model after all descendants removed from server. + void onDescendentsPurgedFromServer(const LLUUID& object_id, bool fix_broken_links = true); + + // Update model after an existing item gets updated on server. + void onItemUpdated(const LLUUID& item_id, const LLSD& updates, bool update_parent_version); + + // Update model after an existing category gets updated on server. + void onCategoryUpdated(const LLUUID& cat_id, const LLSD& updates); + + // Delete a particular inventory object by ID. Will purge one + // object from the internal data structures, maintaining a + // consistent internal state. No cache accounting, observer + // notification, or server update is performed. + void deleteObject(const LLUUID& id, bool fix_broken_links = true, bool do_notify_observers = true); + /// move Item item_id to Trash + void removeItem(const LLUUID& item_id); + /// move Category category_id to Trash + void removeCategory(const LLUUID& category_id); + /// removeItem() or removeCategory(), whichever is appropriate + void removeObject(const LLUUID& object_id); + + // "TrashIsFull" when trash exceeds maximum capacity + void checkTrashOverflow(); + +protected: + void rebuildLinkItems(LLInventoryModel::item_array_t& items); + + //-------------------------------------------------------------------- + // Reorder + //-------------------------------------------------------------------- +public: + // Changes items order by insertion of the item identified by src_item_id + // before (or after) the item identified by dest_item_id. Both items must exist in items array. + // Sorting is stored after method is finished. Only src_item_id is moved before (or after) dest_item_id. + // The parameter "insert_before" controls on which side of dest_item_id src_item_id gets reinserted. + static void updateItemsOrder(LLInventoryModel::item_array_t& items, + const LLUUID& src_item_id, + const LLUUID& dest_item_id, + bool insert_before = true); + // Gets an iterator on an item vector knowing only the item UUID. + // Returns end() of the vector if not found. + static LLInventoryModel::item_array_t::iterator findItemIterByUUID(LLInventoryModel::item_array_t& items, const LLUUID& id); + + + // Rearranges Landmarks inside Favorites folder. + // Moves source landmark before target one. + void rearrangeFavoriteLandmarks(const LLUUID& source_item_id, const LLUUID& target_item_id); + //void saveItemsOrder(const LLInventoryModel::item_array_t& items); + + //-------------------------------------------------------------------- + // Creation + //-------------------------------------------------------------------- +public: + // Returns the UUID of the new category. If you want to use the default + // name based on type, pass in a NULL to the 'name' parameter. + void createNewCategory(const LLUUID& parent_id, + LLFolderType::EType preferred_type, + const std::string& name, + inventory_func_type callback = NULL, + const LLUUID& thumbnail_id = LLUUID::null); +protected: + // Internal methods that add inventory and make sure that all of + // the internal data structures are consistent. These methods + // should be passed pointers of newly created objects, and the + // instance will take over the memory management from there. + void addCategory(LLViewerInventoryCategory* category); + void addItem(LLViewerInventoryItem* item); + + void createNewCategoryCoro(std::string url, LLSD postData, inventory_func_type callback); + +/** Mutators + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** CATEGORY ACCOUNTING + **/ + +public: + // Represents the number of items added or removed from a category. + struct LLCategoryUpdate + { + LLCategoryUpdate() : mDescendentDelta(0), mChangeVersion(true) {} + LLCategoryUpdate(const LLUUID& category_id, S32 delta, bool change_version = true) : + mCategoryID(category_id), + mDescendentDelta(delta), + mChangeVersion(change_version) {} + LLUUID mCategoryID; + S32 mDescendentDelta; + bool mChangeVersion; + }; + typedef std::vector update_list_t; + + // This exists to make it easier to account for deltas in a map. + struct LLInitializedS32 + { + LLInitializedS32() : mValue(0) {} + LLInitializedS32(S32 value) : mValue(value) {} + S32 mValue; + LLInitializedS32& operator++() { ++mValue; return *this; } + LLInitializedS32& operator--() { --mValue; return *this; } + }; + typedef std::map update_map_t; + + // Call when there are category updates. Call them *before* the + // actual update so the method can do descendent accounting correctly. + void accountForUpdate(const LLCategoryUpdate& update) const; + void accountForUpdate(const update_list_t& updates) const; + void accountForUpdate(const update_map_t& updates) const; + + // Return (yes/no/maybe) child status of category children. + EHasChildren categoryHasChildren(const LLUUID& cat_id) const; + + // Returns true if category version is known and theoretical + // descendents == actual descendents. + bool isCategoryComplete(const LLUUID& cat_id) const; + +/** Category Accounting + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** NOTIFICATIONS + **/ + +public: + // Called by the idle loop. Only updates if new state is detected. Call + // notifyObservers() manually to update regardless of whether state change + // has been indicated. + void idleNotifyObservers(); + + // Call to explicitly update everyone on a new state. + void notifyObservers(); + + // Allows outsiders to tell the inventory if something has + // been changed 'under the hood', but outside the control of the + // inventory. The next notify will include that notification. + void addChangedMask(U32 mask, const LLUUID& referent); + + const changed_items_t& getChangedIDs() const { return mChangedItemIDs; } + const changed_items_t& getAddedIDs() const { return mAddedItemIDs; } +protected: + // Updates all linked items pointing to this id. + void addChangedMaskForLinks(const LLUUID& object_id, U32 mask); +private: + // Flag set when notifyObservers is being called, to look for bugs + // where it's called recursively. + bool mIsNotifyObservers; + // Variables used to track what has changed since the last notify. + U32 mModifyMask; + changed_items_t mChangedItemIDs; + changed_items_t mAddedItemIDs; + // Fallback when notifyObservers is in progress + U32 mModifyMaskBacklog; + changed_items_t mChangedItemIDsBacklog; + changed_items_t mAddedItemIDsBacklog; + typedef std::map broken_links_t; + broken_links_t mPossiblyBrockenLinks; // there can be multiple links per item + changed_items_t mLinksRebuildList; + boost::signals2::connection mBulkFecthCallbackSlot; + + + //-------------------------------------------------------------------- + // Observers + //-------------------------------------------------------------------- +public: + // If the observer is destroyed, be sure to remove it. + void addObserver(LLInventoryObserver* observer); + void removeObserver(LLInventoryObserver* observer); + bool containsObserver(LLInventoryObserver* observer) const; +private: + typedef std::set observer_list_t; + observer_list_t mObservers; + +/** Notifications + ** ** + *******************************************************************************/ + + +/******************************************************************************** + ** ** + ** HTTP Transport + **/ +public: + // Invoke handler completion method (onCompleted) for all + // requests that are ready. + void handleResponses(bool foreground); + + // Request an inventory HTTP operation to either the + // foreground or background processor. These are actually + // the same service queue but the background requests are + // seviced more slowly effectively de-prioritizing new + // requests. + LLCore::HttpHandle requestPost(bool foreground, + const std::string & url, + const LLSD & body, + const LLCore::HttpHandler::ptr_t &handler, + const char * const message); + +private: + // Usual plumbing for LLCore:: HTTP operations. + LLCore::HttpRequest * mHttpRequestFG; + LLCore::HttpRequest * mHttpRequestBG; + LLCore::HttpOptions::ptr_t mHttpOptions; + LLCore::HttpHeaders::ptr_t mHttpHeaders; + LLCore::HttpRequest::policy_t mHttpPolicyClass; + +/** HTTP Transport + ** ** + *******************************************************************************/ + + +/******************************************************************************** + ** ** + ** MISCELLANEOUS + **/ + + //-------------------------------------------------------------------- + // Callbacks + //-------------------------------------------------------------------- +public: + // Trigger a notification and empty the folder type (FT_TRASH or FT_LOST_AND_FOUND) if confirmed + void emptyFolderType(const std::string notification, LLFolderType::EType folder_type); + bool callbackEmptyFolderType(const LLSD& notification, const LLSD& response, LLFolderType::EType preferred_type); + static void registerCallbacks(LLMessageSystem* msg); + + //-------------------------------------------------------------------- + // File I/O + //-------------------------------------------------------------------- +protected: + static bool loadFromFile(const std::string& filename, + cat_array_t& categories, + item_array_t& items, + changed_items_t& cats_to_update, + bool& is_cache_obsolete); + static bool saveToFile(const std::string& filename, + const cat_array_t& categories, + const item_array_t& items); + + //-------------------------------------------------------------------- + // Message handling functionality + //-------------------------------------------------------------------- +public: + static void processUpdateCreateInventoryItem(LLMessageSystem* msg, void**); + static void removeInventoryItem(LLUUID agent_id, LLMessageSystem* msg, const char* msg_label); + static void processRemoveInventoryItem(LLMessageSystem* msg, void**); + static void removeInventoryFolder(LLUUID agent_id, LLMessageSystem* msg); + static void processRemoveInventoryFolder(LLMessageSystem* msg, void**); + static void processRemoveInventoryObjects(LLMessageSystem* msg, void**); + static void processSaveAssetIntoInventory(LLMessageSystem* msg, void**); + static void processBulkUpdateInventory(LLMessageSystem* msg, void**); + static void processMoveInventoryItem(LLMessageSystem* msg, void**); +protected: + bool messageUpdateCore(LLMessageSystem* msg, bool do_accounting, U32 mask = 0x0); + + //-------------------------------------------------------------------- + // Locks + //-------------------------------------------------------------------- +public: + void lockDirectDescendentArrays(const LLUUID& cat_id, + cat_array_t*& categories, + item_array_t*& items); + void unlockDirectDescendentArrays(const LLUUID& cat_id); +protected: + cat_array_t* getUnlockedCatArray(const LLUUID& id); + item_array_t* getUnlockedItemArray(const LLUUID& id); +private: + std::map mCategoryLock; + std::map mItemLock; + + //-------------------------------------------------------------------- + // Debugging + //-------------------------------------------------------------------- +public: + void dumpInventory() const; + LLPointer validate() const; + LLPointer mValidationInfo; + std::string getFullPath(const LLInventoryObject *obj) const; + +/** Miscellaneous + ** ** + *******************************************************************************/ +}; + +// a special inventory model for the agent +extern LLInventoryModel gInventory; + +#endif // LL_LLINVENTORYMODEL_H + diff --git a/indra/newview/llinventorymodelbackgroundfetch.cpp b/indra/newview/llinventorymodelbackgroundfetch.cpp index 5975f685e3..9b0dc2fd50 100644 --- a/indra/newview/llinventorymodelbackgroundfetch.cpp +++ b/indra/newview/llinventorymodelbackgroundfetch.cpp @@ -1,1619 +1,1619 @@ -/** - * @file llinventorymodelbackgroundfetch.cpp - * @brief Implementation of background fetching of inventory. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2014, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llinventorymodelbackgroundfetch.h" - -#include "llaisapi.h" -#include "llagent.h" -#include "llappviewer.h" -#include "llcallbacklist.h" -#include "llinventorymodel.h" -#include "llinventorypanel.h" -#include "llnotificationsutil.h" -#include "llstartup.h" -#include "llviewercontrol.h" -#include "llviewerinventory.h" -#include "llviewermessage.h" -#include "llviewerregion.h" -#include "llviewerwindow.h" -#include "llhttpconstants.h" -#include "bufferarray.h" -#include "bufferstream.h" -#include "llcorehttputil.h" - -// History (may be apocryphal) -// -// Around V2, an HTTP inventory download mechanism was added -// along with inventory LINK items referencing other inventory -// items. As part of this, at login, the entire inventory -// structure is downloaded 'in the background' using the -// backgroundFetch()/bulkFetch() methods. The UDP path can -// still be used and is found in the 'DEPRECATED OLD CODE' -// section. -// -// The old UDP path implemented a throttle that adapted -// itself during running. The mechanism survived info HTTP -// somewhat but was pinned to poll the HTTP plumbing at -// 0.5S intervals. The reasons for this particular value -// have been lost. It's possible to switch between UDP -// and HTTP while this is happening but there may be -// surprises in what happens in that case. -// -// Conversion to llcorehttp reduced the number of connections -// used but batches more data and queues more requests (but -// doesn't due pipelining due to libcurl restrictions). The -// poll interval above was re-examined and reduced to get -// inventory into the viewer more quickly. -// -// Possible future work: -// -// * Don't download the entire heirarchy in one go (which -// might have been how V1 worked). Implications for -// links (which may not have a valid target) and search -// which would then be missing data. -// -// * Review the download rate throttling. Slow then fast? -// Detect bandwidth usage and speed up when it drops? -// -// * An error on a fetch could be due to one item in the batch. -// If the batch were broken up, perhaps more of the inventory -// would download. (Handwave here, not certain this is an -// issue in practice.) -// -// * Conversion to AISv3. -// - - -namespace -{ - -///---------------------------------------------------------------------------- -/// Class ::BGItemHttpHandler -///---------------------------------------------------------------------------- - -// -// Http request handler class for single inventory item requests. -// -// We'll use a handler-per-request pattern here rather than -// a shared handler. Mainly convenient as this was converted -// from a Responder class model. -// -// Derives from and is identical to the normal FetchItemHttpHandler -// except that: 1) it uses the background request object which is -// updated more slowly than the foreground and 2) keeps a count of -// active requests on the LLInventoryModelBackgroundFetch object -// to indicate outstanding operations are in-flight. -// -class BGItemHttpHandler : public LLInventoryModel::FetchItemHttpHandler -{ - LOG_CLASS(BGItemHttpHandler); - -public: - BGItemHttpHandler(const LLSD & request_sd) - : LLInventoryModel::FetchItemHttpHandler(request_sd) - { - LLInventoryModelBackgroundFetch::instance().incrFetchCount(1); - } - - virtual ~BGItemHttpHandler() - { - LLInventoryModelBackgroundFetch::instance().incrFetchCount(-1); - } - -protected: - BGItemHttpHandler(const BGItemHttpHandler &); // Not defined - void operator=(const BGItemHttpHandler &); // Not defined -}; - - -///---------------------------------------------------------------------------- -/// Class ::BGFolderHttpHandler -///---------------------------------------------------------------------------- - -// Http request handler class for folders. -// -// Handler for FetchInventoryDescendents2 and FetchLibDescendents2 -// caps requests for folders. -// -class BGFolderHttpHandler : public LLCore::HttpHandler -{ - LOG_CLASS(BGFolderHttpHandler); - -public: - BGFolderHttpHandler(const LLSD & request_sd, const uuid_vec_t & recursive_cats) - : LLCore::HttpHandler(), - mRequestSD(request_sd), - mRecursiveCatUUIDs(recursive_cats) - { - LLInventoryModelBackgroundFetch::instance().incrFetchCount(1); - } - - virtual ~BGFolderHttpHandler() - { - LLInventoryModelBackgroundFetch::instance().incrFetchCount(-1); - } - -protected: - BGFolderHttpHandler(const BGFolderHttpHandler &); // Not defined - void operator=(const BGFolderHttpHandler &); // Not defined - -public: - virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); - - bool getIsRecursive(const LLUUID & cat_id) const; - -private: - void processData(LLSD & body, LLCore::HttpResponse * response); - void processFailure(LLCore::HttpStatus status, LLCore::HttpResponse * response); - void processFailure(const char * const reason, LLCore::HttpResponse * response); - -private: - LLSD mRequestSD; - const uuid_vec_t mRecursiveCatUUIDs; // hack for storing away which cat fetches are recursive -}; - - -const char * const LOG_INV("Inventory"); - -} // end of namespace anonymous - - -///---------------------------------------------------------------------------- -/// Class LLInventoryModelBackgroundFetch -///---------------------------------------------------------------------------- - -LLInventoryModelBackgroundFetch::LLInventoryModelBackgroundFetch(): - mBackgroundFetchActive(false), - mFolderFetchActive(false), - mFetchCount(0), - mLastFetchCount(0), - mFetchFolderCount(0), - mAllRecursiveFoldersFetched(false), - mRecursiveInventoryFetchStarted(false), - mRecursiveLibraryFetchStarted(false), - mRecursiveMarketplaceFetchStarted(false), - mMinTimeBetweenFetches(0.3f) -{} - -LLInventoryModelBackgroundFetch::~LLInventoryModelBackgroundFetch() -{ - gIdleCallbacks.deleteFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); -} - -bool LLInventoryModelBackgroundFetch::isBulkFetchProcessingComplete() const -{ - return mFetchFolderQueue.empty() && mFetchItemQueue.empty() && mFetchCount <= 0; -} - -bool LLInventoryModelBackgroundFetch::isFolderFetchProcessingComplete() const -{ - return mFetchFolderQueue.empty() && mFetchFolderCount <= 0; -} - -bool LLInventoryModelBackgroundFetch::libraryFetchStarted() const -{ - return mRecursiveLibraryFetchStarted; -} - -bool LLInventoryModelBackgroundFetch::libraryFetchCompleted() const -{ - return libraryFetchStarted() && fetchQueueContainsNoDescendentsOf(gInventory.getLibraryRootFolderID()); -} - -bool LLInventoryModelBackgroundFetch::libraryFetchInProgress() const -{ - return libraryFetchStarted() && !libraryFetchCompleted(); -} - -bool LLInventoryModelBackgroundFetch::inventoryFetchStarted() const -{ - return mRecursiveInventoryFetchStarted; -} - -bool LLInventoryModelBackgroundFetch::inventoryFetchCompleted() const -{ - return inventoryFetchStarted() && fetchQueueContainsNoDescendentsOf(gInventory.getRootFolderID()); -} - -bool LLInventoryModelBackgroundFetch::inventoryFetchInProgress() const -{ - return inventoryFetchStarted() && ! inventoryFetchCompleted(); -} - -bool LLInventoryModelBackgroundFetch::isEverythingFetched() const -{ - return mAllRecursiveFoldersFetched; -} - -bool LLInventoryModelBackgroundFetch::folderFetchActive() const -{ - return mFolderFetchActive; -} - -void LLInventoryModelBackgroundFetch::addRequestAtFront(const LLUUID & id, bool recursive, bool is_category) -{ - EFetchType recursion_type = recursive ? FT_RECURSIVE : FT_DEFAULT; - if (is_category) - { - mFetchFolderQueue.push_front(FetchQueueInfo(id, recursion_type, is_category)); - } - else - { - mFetchItemQueue.push_front(FetchQueueInfo(id, recursion_type, is_category)); - } -} - -void LLInventoryModelBackgroundFetch::addRequestAtBack(const LLUUID & id, bool recursive, bool is_category) -{ - EFetchType recursion_type = recursive ? FT_RECURSIVE : FT_DEFAULT; - if (is_category) - { - mFetchFolderQueue.push_back(FetchQueueInfo(id, recursion_type, is_category)); - } - else - { - mFetchItemQueue.push_back(FetchQueueInfo(id, recursion_type, is_category)); - } -} - -void LLInventoryModelBackgroundFetch::start(const LLUUID& id, bool recursive) -{ - LLViewerInventoryCategory * cat(gInventory.getCategory(id)); - - if (cat || (id.isNull() && ! isEverythingFetched())) - { - // it's a folder, do a bulk fetch - LL_DEBUGS(LOG_INV) << "Start fetching category: " << id << ", recursive: " << recursive << LL_ENDL; - - mBackgroundFetchActive = true; - mFolderFetchActive = true; - EFetchType recursion_type = recursive ? FT_RECURSIVE : FT_DEFAULT; - if (id.isNull()) - { - if (! mRecursiveInventoryFetchStarted) - { - mRecursiveInventoryFetchStarted |= recursive; - if (recursive && AISAPI::isAvailable()) - { - // Not only root folder can be massive, but - // most system folders will be requested independently - // so request root folder and content separately - mFetchFolderQueue.push_front(FetchQueueInfo(gInventory.getRootFolderID(), FT_FOLDER_AND_CONTENT)); - } - else - { - mFetchFolderQueue.push_back(FetchQueueInfo(gInventory.getRootFolderID(), recursion_type)); - } - gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); - } - if (! mRecursiveLibraryFetchStarted) - { - mRecursiveLibraryFetchStarted |= recursive; - mFetchFolderQueue.push_back(FetchQueueInfo(gInventory.getLibraryRootFolderID(), recursion_type)); - gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); - } - } - else if (recursive && cat && cat->getPreferredType() == LLFolderType::FT_MARKETPLACE_LISTINGS) - { - if (mFetchFolderQueue.empty() || mFetchFolderQueue.back().mUUID != id) - { - if (recursive && AISAPI::isAvailable()) - { - // Request marketplace folder and content separately - mFetchFolderQueue.push_front(FetchQueueInfo(id, FT_FOLDER_AND_CONTENT)); - } - else - { - mFetchFolderQueue.push_front(FetchQueueInfo(id, recursion_type)); - } - gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); - mRecursiveMarketplaceFetchStarted = true; - } - } - else - { - if (AISAPI::isAvailable()) - { - if (mFetchFolderQueue.empty() || mFetchFolderQueue.back().mUUID != id) - { - // On AIS make sure root goes to the top and follow up recursive - // fetches, not individual requests - mFetchFolderQueue.push_back(FetchQueueInfo(id, recursion_type)); - gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); - } - } - else if (mFetchFolderQueue.empty() || mFetchFolderQueue.front().mUUID != id) - { - // Specific folder requests go to front of queue. - mFetchFolderQueue.push_front(FetchQueueInfo(id, recursion_type)); - gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); - } - - if (id == gInventory.getLibraryRootFolderID()) - { - mRecursiveLibraryFetchStarted |= recursive; - } - if (id == gInventory.getRootFolderID()) - { - mRecursiveInventoryFetchStarted |= recursive; - } - } - } - else if (LLViewerInventoryItem * itemp = gInventory.getItem(id)) - { - if (! itemp->mIsComplete) - { - scheduleItemFetch(id); - } - } -} - -void LLInventoryModelBackgroundFetch::scheduleFolderFetch(const LLUUID& cat_id, bool forced) -{ - if (mFetchFolderQueue.empty() || mFetchFolderQueue.front().mUUID != cat_id) - { - mBackgroundFetchActive = true; - mFolderFetchActive = true; - - if (forced) - { - // check if already requested - if (mForceFetchSet.find(cat_id) == mForceFetchSet.end()) - { - mForceFetchSet.insert(cat_id); - mFetchItemQueue.push_front(FetchQueueInfo(cat_id, FT_FORCED)); - } - } - else - { - // Specific folder requests go to front of queue. - // version presence acts as dupplicate prevention for normal fetches - mFetchItemQueue.push_front(FetchQueueInfo(cat_id, FT_DEFAULT)); - } - - gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); - } -} - -void LLInventoryModelBackgroundFetch::scheduleItemFetch(const LLUUID& item_id, bool forced) -{ - if (mFetchItemQueue.empty() || mFetchItemQueue.front().mUUID != item_id) - { - mBackgroundFetchActive = true; - if (forced) - { - // check if already requested - if (mForceFetchSet.find(item_id) == mForceFetchSet.end()) - { - mForceFetchSet.insert(item_id); - mFetchItemQueue.push_front(FetchQueueInfo(item_id, FT_FORCED, false)); - } - } - else - { - // 'isFinished' being set acts as dupplicate prevention for normal fetches - mFetchItemQueue.push_front(FetchQueueInfo(item_id, FT_DEFAULT, false)); - } - - gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); - } -} - -void LLInventoryModelBackgroundFetch::fetchFolderAndLinks(const LLUUID& cat_id, nullary_func_t callback) -{ - LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); - if (cat) - { - // Mark folder (update timer) so that background fetch won't request it - cat->setFetching(LLViewerInventoryCategory::FETCH_RECURSIVE); - } - incrFetchFolderCount(1); - mExpectedFolderIds.push_back(cat_id); - - // Assume that we have no relevant cache. Fetch folder, and items folder's links point to. - AISAPI::FetchCategoryLinks(cat_id, - [callback, cat_id](const LLUUID& id) - { - callback(); - if (id.isNull()) - { - LL_WARNS() << "Failed to fetch category links " << cat_id << LL_ENDL; - } - LLInventoryModelBackgroundFetch::getInstance()->onAISFolderCalback(cat_id, id, FT_DEFAULT); - }); - - // start idle loop to track completion - mBackgroundFetchActive = true; - mFolderFetchActive = true; - gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); -} - -void LLInventoryModelBackgroundFetch::fetchCOF(nullary_func_t callback) -{ - LLUUID cat_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); - LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); - if (cat) - { - // Mark cof (update timer) so that background fetch won't request it - cat->setFetching(LLViewerInventoryCategory::FETCH_RECURSIVE); - } - incrFetchFolderCount(1); - mExpectedFolderIds.push_back(cat_id); - // For reliability assume that we have no relevant cache, so - // fetch cof along with items cof's links point to. - AISAPI::FetchCOF([callback](const LLUUID& id) - { - callback(); - LLUUID cat_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); - LLInventoryModelBackgroundFetch::getInstance()->onAISFolderCalback(cat_id, id, FT_DEFAULT); - }); - - // start idle loop to track completion - mBackgroundFetchActive = true; - mFolderFetchActive = true; - gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); -} - -void LLInventoryModelBackgroundFetch::findLostItems() -{ - mBackgroundFetchActive = true; - mFolderFetchActive = true; - mFetchFolderQueue.push_back(FetchQueueInfo(LLUUID::null, FT_RECURSIVE)); - gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); -} - -void LLInventoryModelBackgroundFetch::setAllFoldersFetched() -{ - if (mRecursiveInventoryFetchStarted && - mRecursiveLibraryFetchStarted) - { - mAllRecursiveFoldersFetched = true; - //LL_INFOS(LOG_INV) << "All folders fetched, validating" << LL_ENDL; - //gInventory.validate(); - } - - mFolderFetchActive = false; - if (isBulkFetchProcessingComplete()) - { - mBackgroundFetchActive = false; - } - - // For now only informs about initial fetch being done - mFoldersFetchedSignal(); - - LL_INFOS(LOG_INV) << "Inventory background fetch completed" << LL_ENDL; -} - -boost::signals2::connection LLInventoryModelBackgroundFetch::setFetchCompletionCallback(folders_fetched_callback_t cb) -{ - return mFoldersFetchedSignal.connect(cb); -} - -void LLInventoryModelBackgroundFetch::backgroundFetchCB(void *) -{ - LLInventoryModelBackgroundFetch::instance().backgroundFetch(); -} - -void LLInventoryModelBackgroundFetch::backgroundFetch() -{ - if (mBackgroundFetchActive) - { - if (AISAPI::isAvailable()) - { - bulkFetchViaAis(); - } - else if (gAgent.getRegion() && gAgent.getRegion()->capabilitiesReceived()) - { - // If we'll be using the capability, we'll be sending batches and the background thing isn't as important. - bulkFetch(); - } - } -} - -void LLInventoryModelBackgroundFetch::incrFetchCount(S32 fetching) -{ - mFetchCount += fetching; - if (mFetchCount < 0) - { - LL_WARNS_ONCE(LOG_INV) << "Inventory fetch count fell below zero (0)." << LL_ENDL; - mFetchCount = 0; - } -} -void LLInventoryModelBackgroundFetch::incrFetchFolderCount(S32 fetching) -{ - incrFetchCount(fetching); - mFetchFolderCount += fetching; - if (mFetchCount < 0) - { - LL_WARNS_ONCE(LOG_INV) << "Inventory fetch count fell below zero (0)." << LL_ENDL; - mFetchFolderCount = 0; - } -} - -void ais_simple_item_callback(const LLUUID& inv_id) -{ - LL_DEBUGS(LOG_INV , "AIS3") << "Response for " << inv_id << LL_ENDL; - LLInventoryModelBackgroundFetch::instance().incrFetchCount(-1); -} - -void LLInventoryModelBackgroundFetch::onAISContentCalback( - const LLUUID& request_id, - const uuid_vec_t& content_ids, - const LLUUID& response_id, - EFetchType fetch_type) -{ - // Don't push_front on failure - there is a chance it was fired from inside bulkFetchViaAis - incrFetchFolderCount(-1); - - uuid_vec_t::const_iterator folder_iter = content_ids.begin(); - uuid_vec_t::const_iterator folder_end = content_ids.end(); - while (folder_iter != folder_end) - { - std::list::const_iterator found = std::find(mExpectedFolderIds.begin(), mExpectedFolderIds.end(), *folder_iter); - if (found != mExpectedFolderIds.end()) - { - mExpectedFolderIds.erase(found); - } - - LLViewerInventoryCategory* cat(gInventory.getCategory(*folder_iter)); - if (cat) - { - cat->setFetching(LLViewerInventoryCategory::FETCH_NONE); - } - if (response_id.isNull()) - { - // Failed to fetch, get it individually - mFetchFolderQueue.push_back(FetchQueueInfo(*folder_iter, FT_RECURSIVE)); - } - else - { - // push descendant back to verify they are fetched fully (ex: didn't encounter depth limit) - LLInventoryModel::cat_array_t* categories(NULL); - LLInventoryModel::item_array_t* items(NULL); - gInventory.getDirectDescendentsOf(*folder_iter, categories, items); - if (categories) - { - for (LLInventoryModel::cat_array_t::const_iterator it = categories->begin(); - it != categories->end(); - ++it) - { - mFetchFolderQueue.push_back(FetchQueueInfo((*it)->getUUID(), FT_RECURSIVE)); - } - } - } - - folder_iter++; - } - - if (!mFetchFolderQueue.empty()) - { - mBackgroundFetchActive = true; - mFolderFetchActive = true; - gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); - } -} -void LLInventoryModelBackgroundFetch::onAISFolderCalback(const LLUUID &request_id, const LLUUID &response_id, EFetchType fetch_type) -{ - // Don't push_front on failure - there is a chance it was fired from inside bulkFetchViaAis - incrFetchFolderCount(-1); - std::list::const_iterator found = std::find(mExpectedFolderIds.begin(), mExpectedFolderIds.end(), request_id); - if (found != mExpectedFolderIds.end()) - { - mExpectedFolderIds.erase(found); - } - else - { - // ais shouldn't respond twice - llassert(false); - LL_WARNS() << "Unexpected folder response for " << request_id << LL_ENDL; - } - - if (request_id.isNull()) - { - // orhans, no other actions needed - return; - } - - LLViewerInventoryCategory::EFetchType new_state = LLViewerInventoryCategory::FETCH_NONE; - bool request_descendants = false; - if (response_id.isNull()) // Failure - { - LL_DEBUGS(LOG_INV , "AIS3") << "Failure response for folder " << request_id << LL_ENDL; - if (fetch_type == FT_RECURSIVE) - { - // A full recursive request failed. - // Try requesting folder and nested content separately - mFetchFolderQueue.push_back(FetchQueueInfo(request_id, FT_FOLDER_AND_CONTENT)); - } - else if (fetch_type == FT_FOLDER_AND_CONTENT) - { - LL_WARNS() << "Failed to download folder: " << request_id << " Requesting known content separately" << LL_ENDL; - mFetchFolderQueue.push_back(FetchQueueInfo(request_id, FT_CONTENT_RECURSIVE)); - - // set folder's version to prevent viewer from trying to request folder indefinetely - LLViewerInventoryCategory* cat(gInventory.getCategory(request_id)); - if (cat && cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) - { - cat->setVersion(0); - } - // back off for a bit in case something tries to force-request immediately - new_state = LLViewerInventoryCategory::FETCH_FAILED; - } - } - else - { - if (fetch_type == FT_RECURSIVE) - { - // Got the folder and content, now verify content - // Request content even for FT_RECURSIVE in case of changes, failures - // or if depth limit gets imlemented. - // This shouldn't redownload folders if they already have version - request_descendants = true; - LL_DEBUGS(LOG_INV, "AIS3") << "Got folder " << request_id << ". Requesting content" << LL_ENDL; - } - else if (fetch_type == FT_FOLDER_AND_CONTENT) - { - // readd folder for content request - mFetchFolderQueue.push_front(FetchQueueInfo(request_id, FT_CONTENT_RECURSIVE)); - } - else - { - LL_DEBUGS(LOG_INV, "AIS3") << "Got folder " << request_id << "." << LL_ENDL; - } - - } - - if (request_descendants) - { - LLInventoryModel::cat_array_t* categories(NULL); - LLInventoryModel::item_array_t* items(NULL); - gInventory.getDirectDescendentsOf(request_id, categories, items); - if (categories) - { - for (LLInventoryModel::cat_array_t::const_iterator it = categories->begin(); - it != categories->end(); - ++it) - { - mFetchFolderQueue.push_back(FetchQueueInfo((*it)->getUUID(), FT_RECURSIVE)); - } - } - } - - if (!mFetchFolderQueue.empty()) - { - mBackgroundFetchActive = true; - mFolderFetchActive = true; - gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); - } - - // done - LLViewerInventoryCategory * cat(gInventory.getCategory(request_id)); - if (cat) - { - cat->setFetching(new_state); - } -} - -static LLTrace::BlockTimerStatHandle FTM_BULK_FETCH("Bulk Fetch"); - -void LLInventoryModelBackgroundFetch::bulkFetchViaAis() -{ - LL_RECORD_BLOCK_TIME(FTM_BULK_FETCH); - //Background fetch is called from gIdleCallbacks in a loop until background fetch is stopped. - if (gDisconnected) - { - return; - } - - static LLCachedControl ais_pool(gSavedSettings, "PoolSizeAIS", 20); - // Don't have too many requests at once, AIS throttles - // Reserve one request for actions outside of fetch (like renames) - const U32 max_concurrent_fetches = llclamp(ais_pool - 1, 1, 50); - - if (mFetchCount >= max_concurrent_fetches) - { - return; - } - - // Don't loop for too long (in case of large, fully loaded inventory) - F64 curent_time = LLTimer::getTotalSeconds(); - const F64 max_time = LLStartUp::getStartupState() > STATE_WEARABLES_WAIT - ? 0.006f // 6 ms - : 1.f; - const F64 end_time = curent_time + max_time; - S32 last_fetch_count = mFetchCount; - - while (!mFetchFolderQueue.empty() && mFetchCount < max_concurrent_fetches && curent_time < end_time) - { - const FetchQueueInfo & fetch_info(mFetchFolderQueue.front()); - bulkFetchViaAis(fetch_info); - mFetchFolderQueue.pop_front(); - curent_time = LLTimer::getTotalSeconds(); - } - - // Ideally we shouldn't fetch items if recursive fetch isn't done, - // but there is a chance some request will start timeouting and recursive - // fetch will get stuck on a signle folder, don't block item fetch in such case - while (!mFetchItemQueue.empty() && mFetchCount < max_concurrent_fetches && curent_time < end_time) - { - const FetchQueueInfo& fetch_info(mFetchItemQueue.front()); - bulkFetchViaAis(fetch_info); - mFetchItemQueue.pop_front(); - curent_time = LLTimer::getTotalSeconds(); - } - - if (last_fetch_count != mFetchCount // if anything was added - || mLastFetchCount != mFetchCount) // if anything was substracted - { - LL_DEBUGS(LOG_INV , "AIS3") << "Total active fetches: " << mLastFetchCount << "->" << last_fetch_count << "->" << mFetchCount - << ", scheduled folder fetches: " << (S32)mFetchFolderQueue.size() - << ", scheduled item fetches: " << (S32)mFetchItemQueue.size() - << LL_ENDL; - mLastFetchCount = mFetchCount; - - if (!mExpectedFolderIds.empty()) - { - // A folder seem to be stack fetching on QA account, print oldest folder out - LL_DEBUGS(LOG_INV , "AIS3") << "Oldest expected folder: "; - std::list::const_iterator iter = mExpectedFolderIds.begin(); - LL_CONT << *iter; - if ((*iter).notNull()) - { - LLViewerInventoryCategory* cat(gInventory.getCategory(*iter)); - if (cat) - { - LL_CONT << " Folder name: " << cat->getName() << " Parent: " << cat->getParentUUID(); - } - else - { - LL_CONT << " This folder doesn't exist"; - } - } - else - { - LL_CONT << " Orphans request"; - } - LL_CONT << LL_ENDL; - } - } - - if (isFolderFetchProcessingComplete() && mFolderFetchActive) - { - if (!mRecursiveInventoryFetchStarted || mRecursiveMarketplaceFetchStarted) - { - setAllFoldersFetched(); - } - else - { - // Intent is for marketplace request to happen after - // main inventory is done, unless requested by floater - mRecursiveMarketplaceFetchStarted = true; - const LLUUID& marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - if (marketplacelistings_id.notNull()) - { - mFetchFolderQueue.push_front(FetchQueueInfo(marketplacelistings_id, FT_FOLDER_AND_CONTENT)); - } - else - { - setAllFoldersFetched(); - } - } - - } - - if (isBulkFetchProcessingComplete()) - { - mBackgroundFetchActive = false; - } -} - -void LLInventoryModelBackgroundFetch::bulkFetchViaAis(const FetchQueueInfo& fetch_info) -{ - if (fetch_info.mIsCategory) - { - const LLUUID & cat_id(fetch_info.mUUID); - if (cat_id.isNull()) - { - incrFetchFolderCount(1); - mExpectedFolderIds.push_back(cat_id); - // Lost and found - // Should it actually be recursive? - AISAPI::FetchOrphans([](const LLUUID& response_id) - { - LLInventoryModelBackgroundFetch::instance().onAISFolderCalback(LLUUID::null, - response_id, - FT_DEFAULT); - }); - } - else - { - LLViewerInventoryCategory * cat(gInventory.getCategory(cat_id)); - if (cat) - { - if (fetch_info.mFetchType == FT_CONTENT_RECURSIVE) - { - // fetch content only, ignore cat itself - uuid_vec_t children; - LLInventoryModel::cat_array_t* categories(NULL); - LLInventoryModel::item_array_t* items(NULL); - gInventory.getDirectDescendentsOf(cat_id, categories, items); - - LLViewerInventoryCategory::EFetchType target_state = LLViewerInventoryCategory::FETCH_RECURSIVE; - bool content_done = true; - - // Top limit is 'as many as you can put into url' - static LLCachedControl ais_batch(gSavedSettings, "BatchSizeAIS3", 20); - S32 batch_limit = llclamp(ais_batch(), 1, 40); - - for (LLInventoryModel::cat_array_t::iterator it = categories->begin(); - it != categories->end(); - ++it) - { - LLViewerInventoryCategory* child_cat = (*it); - if (LLViewerInventoryCategory::VERSION_UNKNOWN != child_cat->getVersion() - || child_cat->getFetching() >= target_state) - { - continue; - } - - if (child_cat->getPreferredType() == LLFolderType::FT_MARKETPLACE_LISTINGS) - { - // special case, marketplace will fetch that as needed - continue; - } - - children.push_back(child_cat->getUUID()); - mExpectedFolderIds.push_back(child_cat->getUUID()); - child_cat->setFetching(target_state); - - if (children.size() >= batch_limit) - { - content_done = false; - break; - } - } - - if (!children.empty()) - { - // increment before call in case of immediate callback - incrFetchFolderCount(1); - - EFetchType type = fetch_info.mFetchType; - LLUUID cat_id = cat->getUUID(); // need a copy for lambda - AISAPI::completion_t cb = [cat_id, children, type](const LLUUID& response_id) - { - LLInventoryModelBackgroundFetch::instance().onAISContentCalback(cat_id, children, response_id, type); - }; - - AISAPI::ITEM_TYPE item_type = AISAPI::INVENTORY; - if (ALEXANDRIA_LINDEN_ID == cat->getOwnerID()) - { - item_type = AISAPI::LIBRARY; - } - - AISAPI::FetchCategorySubset(cat_id, children, item_type, true, cb, 0); - } - - if (content_done) - { - // This will have a bit of overlap with onAISContentCalback, - // but something else might have dowloaded folders, so verify - // every child that is complete has it's children done as well - for (LLInventoryModel::cat_array_t::iterator it = categories->begin(); - it != categories->end(); - ++it) - { - LLViewerInventoryCategory* child_cat = (*it); - if (LLViewerInventoryCategory::VERSION_UNKNOWN != child_cat->getVersion()) - { - mFetchFolderQueue.push_back(FetchQueueInfo(child_cat->getUUID(), FT_RECURSIVE)); - } - } - } - else - { - // send it back to get the rest - mFetchFolderQueue.push_back(FetchQueueInfo(cat_id, FT_CONTENT_RECURSIVE)); - } - } - else if (LLViewerInventoryCategory::VERSION_UNKNOWN == cat->getVersion() - || fetch_info.mFetchType == FT_FORCED) - { - LLViewerInventoryCategory::EFetchType target_state = - fetch_info.mFetchType > FT_CONTENT_RECURSIVE - ? LLViewerInventoryCategory::FETCH_RECURSIVE - : LLViewerInventoryCategory::FETCH_NORMAL; - // start again if we did a non-recursive fetch before - // to get all children in a single request - if (cat->getFetching() < target_state) - { - // increment before call in case of immediate callback - incrFetchFolderCount(1); - cat->setFetching(target_state); - mExpectedFolderIds.push_back(cat_id); - - EFetchType type = fetch_info.mFetchType; - LLUUID cat_cb_id = cat_id; - AISAPI::completion_t cb = [cat_cb_id, type](const LLUUID& response_id) - { - LLInventoryModelBackgroundFetch::instance().onAISFolderCalback(cat_cb_id, response_id , type); - }; - - AISAPI::ITEM_TYPE item_type = AISAPI::INVENTORY; - if (ALEXANDRIA_LINDEN_ID == cat->getOwnerID()) - { - item_type = AISAPI::LIBRARY; - } - - AISAPI::FetchCategoryChildren(cat_id , item_type , type == FT_RECURSIVE , cb, 0); - } - } - else - { - // Already fetched, check if anything inside needs fetching - if (fetch_info.mFetchType == FT_RECURSIVE - || fetch_info.mFetchType == FT_FOLDER_AND_CONTENT) - { - LLInventoryModel::cat_array_t * categories(NULL); - LLInventoryModel::item_array_t * items(NULL); - gInventory.getDirectDescendentsOf(cat_id, categories, items); - for (LLInventoryModel::cat_array_t::const_iterator it = categories->begin(); - it != categories->end(); - ++it) - { - // not push_front to not cause an infinite loop - mFetchFolderQueue.push_back(FetchQueueInfo((*it)->getUUID(), FT_RECURSIVE)); - } - } - } - } // else try to fetch folder either way? - } - } - else - { - LLViewerInventoryItem * itemp(gInventory.getItem(fetch_info.mUUID)); - - if (itemp) - { - if (!itemp->isFinished() || fetch_info.mFetchType == FT_FORCED) - { - mFetchCount++; - if (itemp->getPermissions().getOwner() == gAgent.getID()) - { - AISAPI::FetchItem(fetch_info.mUUID, AISAPI::INVENTORY, ais_simple_item_callback); - } - else - { - AISAPI::FetchItem(fetch_info.mUUID, AISAPI::LIBRARY, ais_simple_item_callback); - } - } - } - else // We don't know it, assume incomplete - { - // Assume agent's inventory, library wouldn't have gotten here - mFetchCount++; - AISAPI::FetchItem(fetch_info.mUUID, AISAPI::INVENTORY, ais_simple_item_callback); - } - } - - if (fetch_info.mFetchType == FT_FORCED) - { - mForceFetchSet.erase(fetch_info.mUUID); - } -} - -// Bundle up a bunch of requests to send all at once. -void LLInventoryModelBackgroundFetch::bulkFetch() -{ - LL_RECORD_BLOCK_TIME(FTM_BULK_FETCH); - //Background fetch is called from gIdleCallbacks in a loop until background fetch is stopped. - //If there are items in mFetchQueue, we want to check the time since the last bulkFetch was - //sent. If it exceeds our retry time, go ahead and fire off another batch. - LLViewerRegion * region(gAgent.getRegion()); - if (! region || gDisconnected || LLApp::isExiting()) - { - return; - } - - // *TODO: These values could be tweaked at runtime to effect - // a fast/slow fetch throttle. Once login is complete and the scene - // is mostly loaded, we could turn up the throttle and fill missing - // inventory more quickly. - static const U32 max_batch_size(10); - static const S32 max_concurrent_fetches(12); // Outstanding requests, not connections - - if (mFetchCount) - { - // Process completed background HTTP requests - gInventory.handleResponses(false); - // Just processed a bunch of items. - // Note: do we really need notifyObservers() here? - // OnIdle it will be called anyway due to Add flag for processed item. - // It seems like in some cases we are updaiting on fail (no flag), - // but is there anything to update? - gInventory.notifyObservers(); - } - - if (mFetchCount > max_concurrent_fetches) - { - return; - } - - U32 item_count(0); - U32 folder_count(0); - - const U32 sort_order(gSavedSettings.getU32(LLInventoryPanel::DEFAULT_SORT_ORDER) & 0x1); - - // *TODO: Think I'd like to get a shared pointer to this and share it - // among all the folder requests. - uuid_vec_t recursive_cats; - uuid_vec_t all_cats; // dupplicate avoidance - - LLSD folder_request_body; - LLSD folder_request_body_lib; - LLSD item_request_body; - LLSD item_request_body_lib; - - while (! mFetchFolderQueue.empty() - && (item_count + folder_count) < max_batch_size) - { - const FetchQueueInfo & fetch_info(mFetchFolderQueue.front()); - if (fetch_info.mIsCategory) - { - const LLUUID & cat_id(fetch_info.mUUID); - if (cat_id.isNull()) //DEV-17797 Lost and found - { - LLSD folder_sd; - folder_sd["folder_id"] = LLUUID::null.asString(); - folder_sd["owner_id"] = gAgent.getID(); - folder_sd["sort_order"] = LLSD::Integer(sort_order); - folder_sd["fetch_folders"] = LLSD::Boolean(false); - folder_sd["fetch_items"] = LLSD::Boolean(true); - folder_request_body["folders"].append(folder_sd); - folder_count++; - } - else - { - const LLViewerInventoryCategory * cat(gInventory.getCategory(cat_id)); - if (cat) - { - if (LLViewerInventoryCategory::VERSION_UNKNOWN == cat->getVersion()) - { - if (std::find(all_cats.begin(), all_cats.end(), cat_id) == all_cats.end()) - { - LLSD folder_sd; - folder_sd["folder_id"] = cat->getUUID(); - folder_sd["owner_id"] = cat->getOwnerID(); - folder_sd["sort_order"] = LLSD::Integer(sort_order); - folder_sd["fetch_folders"] = LLSD::Boolean(true); //(LLSD::Boolean)sFullFetchStarted; - folder_sd["fetch_items"] = LLSD::Boolean(true); - - if (ALEXANDRIA_LINDEN_ID == cat->getOwnerID()) - { - folder_request_body_lib["folders"].append(folder_sd); - } - else - { - folder_request_body["folders"].append(folder_sd); - } - folder_count++; - } - } - else - { - // May already have this folder, but append child folders to list. - if (fetch_info.mFetchType >= FT_CONTENT_RECURSIVE) - { - LLInventoryModel::cat_array_t * categories(NULL); - LLInventoryModel::item_array_t * items(NULL); - gInventory.getDirectDescendentsOf(cat_id, categories, items); - for (LLInventoryModel::cat_array_t::const_iterator it = categories->begin(); - it != categories->end(); - ++it) - { - mFetchFolderQueue.push_back(FetchQueueInfo((*it)->getUUID(), fetch_info.mFetchType)); - } - } - } - } - } - if (fetch_info.mFetchType >= FT_CONTENT_RECURSIVE) - { - recursive_cats.push_back(cat_id); - } - all_cats.push_back(cat_id); - } - - mFetchFolderQueue.pop_front(); - } - - - while (!mFetchItemQueue.empty() - && (item_count + folder_count) < max_batch_size) - { - const FetchQueueInfo & fetch_info(mFetchItemQueue.front()); - - LLViewerInventoryItem * itemp(gInventory.getItem(fetch_info.mUUID)); - - if (itemp) - { - LLSD item_sd; - item_sd["owner_id"] = itemp->getPermissions().getOwner(); - item_sd["item_id"] = itemp->getUUID(); - if (itemp->getPermissions().getOwner() == gAgent.getID()) - { - item_request_body.append(item_sd); - } - else - { - item_request_body_lib.append(item_sd); - } - //itemp->fetchFromServer(); - item_count++; - } - - mFetchItemQueue.pop_front(); - } - - // Issue HTTP POST requests to fetch folders and items - - if (item_count + folder_count > 0) - { - if (folder_count) - { - if (folder_request_body["folders"].size()) - { - const std::string url(region->getCapability("FetchInventoryDescendents2")); - - if (! url.empty()) - { - LLCore::HttpHandler::ptr_t handler(new BGFolderHttpHandler(folder_request_body, recursive_cats)); - gInventory.requestPost(false, url, folder_request_body, handler, "Inventory Folder"); - } - } - - if (folder_request_body_lib["folders"].size()) - { - const std::string url(region->getCapability("FetchLibDescendents2")); - - if (! url.empty()) - { - LLCore::HttpHandler::ptr_t handler(new BGFolderHttpHandler(folder_request_body_lib, recursive_cats)); - gInventory.requestPost(false, url, folder_request_body_lib, handler, "Library Folder"); - } - } - } // if (folder_count) - - if (item_count) - { - if (item_request_body.size()) - { - const std::string url(region->getCapability("FetchInventory2")); - - if (! url.empty()) - { - LLSD body; - body["items"] = item_request_body; - LLCore::HttpHandler::ptr_t handler(new BGItemHttpHandler(body)); - gInventory.requestPost(false, url, body, handler, "Inventory Item"); - } - } - - if (item_request_body_lib.size()) - { - const std::string url(region->getCapability("FetchLib2")); - - if (! url.empty()) - { - LLSD body; - body["items"] = item_request_body_lib; - LLCore::HttpHandler::ptr_t handler(new BGItemHttpHandler(body)); - gInventory.requestPost(false, url, body, handler, "Library Item"); - } - } - } // if (item_count) - - mFetchTimer.reset(); - } - else if (isBulkFetchProcessingComplete()) - { - setAllFoldersFetched(); - } -} - -bool LLInventoryModelBackgroundFetch::fetchQueueContainsNoDescendentsOf(const LLUUID & cat_id) const -{ - for (fetch_queue_t::const_iterator it = mFetchFolderQueue.begin(); - it != mFetchFolderQueue.end(); - ++it) - { - const LLUUID & fetch_id = (*it).mUUID; - if (gInventory.isObjectDescendentOf(fetch_id, cat_id)) - return false; - } - for (fetch_queue_t::const_iterator it = mFetchItemQueue.begin(); - it != mFetchItemQueue.end(); - ++it) - { - const LLUUID & fetch_id = (*it).mUUID; - if (gInventory.isObjectDescendentOf(fetch_id, cat_id)) - return false; - } - return true; -} - - -namespace -{ - -///---------------------------------------------------------------------------- -/// Class ::BGFolderHttpHandler -///---------------------------------------------------------------------------- - -void BGFolderHttpHandler::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) -{ - do // Single-pass do-while used for common exit handling - { - LLCore::HttpStatus status(response->getStatus()); - // status = LLCore::HttpStatus(404); // Dev tool to force error handling - if (! status) - { - processFailure(status, response); - break; // Goto common exit - } - - // Response body should be present. - LLCore::BufferArray * body(response->getBody()); - // body = NULL; // Dev tool to force error handling - if (! body || ! body->size()) - { - LL_WARNS(LOG_INV) << "Missing data in inventory folder query." << LL_ENDL; - processFailure("HTTP response missing expected body", response); - break; // Goto common exit - } - - // Could test 'Content-Type' header but probably unreliable. - - // Convert response to LLSD - // body->write(0, "Garbage Response", 16); // Dev tool to force error handling - LLSD body_llsd; - if (! LLCoreHttpUtil::responseToLLSD(response, true, body_llsd)) - { - // INFOS-level logging will occur on the parsed failure - processFailure("HTTP response contained malformed LLSD", response); - break; // goto common exit - } - - // Expect top-level structure to be a map - // body_llsd = LLSD::emptyArray(); // Dev tool to force error handling - if (! body_llsd.isMap()) - { - processFailure("LLSD response not a map", response); - break; // goto common exit - } - - // Check for 200-with-error failures - // - // See comments in llinventorymodel.cpp about this mode of error. - // - // body_llsd["error"] = LLSD::emptyMap(); // Dev tool to force error handling - // body_llsd["error"]["identifier"] = "Development"; - // body_llsd["error"]["message"] = "You left development code in the viewer"; - if (body_llsd.has("error")) - { - processFailure("Inventory application error (200-with-error)", response); - break; // goto common exit - } - - // Okay, process data if possible - processData(body_llsd, response); - } - while (false); -} - - -void BGFolderHttpHandler::processData(LLSD & content, LLCore::HttpResponse * response) -{ - LLInventoryModelBackgroundFetch * fetcher(LLInventoryModelBackgroundFetch::getInstance()); - - // API V2 and earlier should probably be testing for "error" map - // in response as an application-level error. - - // Instead, we assume success and attempt to extract information. - if (content.has("folders")) - { - LLSD folders(content["folders"]); - - for (LLSD::array_const_iterator folder_it = folders.beginArray(); - folder_it != folders.endArray(); - ++folder_it) - { - LLSD folder_sd(*folder_it); - - //LLUUID agent_id = folder_sd["agent_id"]; - - //if(agent_id != gAgent.getID()) //This should never happen. - //{ - // LL_WARNS(LOG_INV) << "Got a UpdateInventoryItem for the wrong agent." - // << LL_ENDL; - // break; - //} - - LLUUID parent_id(folder_sd["folder_id"].asUUID()); - LLUUID owner_id(folder_sd["owner_id"].asUUID()); - S32 version(folder_sd["version"].asInteger()); - S32 descendents(folder_sd["descendents"].asInteger()); - LLPointer tcategory = new LLViewerInventoryCategory(owner_id); - - if (parent_id.isNull()) - { - LLSD items(folder_sd["items"]); - LLPointer titem = new LLViewerInventoryItem; - - for (LLSD::array_const_iterator item_it = items.beginArray(); - item_it != items.endArray(); - ++item_it) - { - const LLUUID lost_uuid(gInventory.findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND)); - - if (lost_uuid.notNull()) - { - LLSD item(*item_it); - - titem->unpackMessage(item); - - LLInventoryModel::update_list_t update; - LLInventoryModel::LLCategoryUpdate new_folder(lost_uuid, 1); - update.push_back(new_folder); - gInventory.accountForUpdate(update); - - titem->setParent(lost_uuid); - titem->updateParentOnServer(false); - gInventory.updateItem(titem); - } - } - } - - LLViewerInventoryCategory * pcat(gInventory.getCategory(parent_id)); - if (! pcat) - { - continue; - } - - LLSD categories(folder_sd["categories"]); - for (LLSD::array_const_iterator category_it = categories.beginArray(); - category_it != categories.endArray(); - ++category_it) - { - LLSD category(*category_it); - tcategory->fromLLSD(category); - - const bool recursive(getIsRecursive(tcategory->getUUID())); - if (recursive) - { - fetcher->addRequestAtBack(tcategory->getUUID(), recursive, true); - } - else if (! gInventory.isCategoryComplete(tcategory->getUUID())) - { - gInventory.updateCategory(tcategory); - } - } - - LLSD items(folder_sd["items"]); - LLPointer titem = new LLViewerInventoryItem; - for (LLSD::array_const_iterator item_it = items.beginArray(); - item_it != items.endArray(); - ++item_it) - { - LLSD item(*item_it); - titem->unpackMessage(item); - - gInventory.updateItem(titem); - } - - // Set version and descendentcount according to message. - LLViewerInventoryCategory * cat(gInventory.getCategory(parent_id)); - if (cat) - { - cat->setVersion(version); - cat->setDescendentCount(descendents); - cat->determineFolderType(); - } - } - } - - if (content.has("bad_folders")) - { - LLSD bad_folders(content["bad_folders"]); - for (LLSD::array_const_iterator folder_it = bad_folders.beginArray(); - folder_it != bad_folders.endArray(); - ++folder_it) - { - // *TODO: Stop copying data [ed: this isn't copying data] - LLSD folder_sd(*folder_it); - - // These folders failed on the dataserver. We probably don't want to retry them. - LL_WARNS(LOG_INV) << "Folder " << folder_sd["folder_id"].asString() - << "Error: " << folder_sd["error"].asString() << LL_ENDL; - } - } - - if (fetcher->isBulkFetchProcessingComplete()) - { - fetcher->setAllFoldersFetched(); - } -} - - -void BGFolderHttpHandler::processFailure(LLCore::HttpStatus status, LLCore::HttpResponse * response) -{ - const std::string & ct(response->getContentType()); - LL_WARNS(LOG_INV) << "Inventory folder fetch failure\n" - << "[Status: " << status.toTerseString() << "]\n" - << "[Reason: " << status.toString() << "]\n" - << "[Content-type: " << ct << "]\n" - << "[Content (abridged): " - << LLCoreHttpUtil::responseToString(response) << "]" << LL_ENDL; - - // Could use a 404 test here to try to detect revoked caps... - - if(status == LLCore::HttpStatus(HTTP_FORBIDDEN)) - { - // Too large, split into two if possible - if (gDisconnected || LLApp::isExiting()) - { - return; - } - - const std::string url(gAgent.getRegionCapability("FetchInventoryDescendents2")); - if (url.empty()) - { - LL_WARNS(LOG_INV) << "Failed to get AIS2 cap" << LL_ENDL; - return; - } - - S32 size = mRequestSD["folders"].size(); - - if (size > 1) - { - // Can split, assume that this isn't the library - LLSD folders; - uuid_vec_t recursive_cats; - LLSD::array_iterator iter = mRequestSD["folders"].beginArray(); - LLSD::array_iterator end = mRequestSD["folders"].endArray(); - while (iter != end) - { - folders.append(*iter); - LLUUID folder_id = iter->get("folder_id").asUUID(); - if (std::find(mRecursiveCatUUIDs.begin(), mRecursiveCatUUIDs.end(), folder_id) != mRecursiveCatUUIDs.end()) - { - recursive_cats.push_back(folder_id); - } - if (folders.size() == (S32)(size / 2)) - { - LLSD request_body; - request_body["folders"] = folders; - LLCore::HttpHandler::ptr_t handler(new BGFolderHttpHandler(request_body, recursive_cats)); - gInventory.requestPost(false, url, request_body, handler, "Inventory Folder"); - recursive_cats.clear(); - folders.clear(); - } - iter++; - } - - LLSD request_body; - request_body["folders"] = folders; - LLCore::HttpHandler::ptr_t handler(new BGFolderHttpHandler(request_body, recursive_cats)); - gInventory.requestPost(false, url, request_body, handler, "Inventory Folder"); - return; - } - else - { - // Can't split - LLNotificationsUtil::add("InventoryLimitReachedAIS"); - } - } - - // This was originally the request retry logic for the inventory - // request which tested on HTTP_INTERNAL_ERROR status. This - // retry logic was unbounded and lacked discrimination as to the - // cause of the retry. The new http library should be doing - // adquately on retries but I want to keep the structure of a - // retry for reference. - LLInventoryModelBackgroundFetch *fetcher = LLInventoryModelBackgroundFetch::getInstance(); - if (false) - { - // timed out or curl failure - for (LLSD::array_const_iterator folder_it = mRequestSD["folders"].beginArray(); - folder_it != mRequestSD["folders"].endArray(); - ++folder_it) - { - LLSD folder_sd(*folder_it); - LLUUID folder_id(folder_sd["folder_id"].asUUID()); - const bool recursive = getIsRecursive(folder_id); - fetcher->addRequestAtFront(folder_id, recursive, true); - } - } - else - { - if (fetcher->isBulkFetchProcessingComplete()) - { - fetcher->setAllFoldersFetched(); - } - } -} - - -void BGFolderHttpHandler::processFailure(const char * const reason, LLCore::HttpResponse * response) -{ - LL_WARNS(LOG_INV) << "Inventory folder fetch failure\n" - << "[Status: internal error]\n" - << "[Reason: " << reason << "]\n" - << "[Content (abridged): " - << LLCoreHttpUtil::responseToString(response) << "]" << LL_ENDL; - - // Reverse of previous processFailure() method, this is invoked - // when response structure is found to be invalid. Original - // always re-issued the request (without limit). This does - // the same but be aware that this may be a source of problems. - // Philosophy is that inventory folders are so essential to - // operation that this is a reasonable action. - LLInventoryModelBackgroundFetch *fetcher = LLInventoryModelBackgroundFetch::getInstance(); - if (true) - { - for (LLSD::array_const_iterator folder_it = mRequestSD["folders"].beginArray(); - folder_it != mRequestSD["folders"].endArray(); - ++folder_it) - { - LLSD folder_sd(*folder_it); - LLUUID folder_id(folder_sd["folder_id"].asUUID()); - const bool recursive = getIsRecursive(folder_id); - fetcher->addRequestAtFront(folder_id, recursive, true); - } - } - else - { - if (fetcher->isBulkFetchProcessingComplete()) - { - fetcher->setAllFoldersFetched(); - } - } -} - - -bool BGFolderHttpHandler::getIsRecursive(const LLUUID & cat_id) const -{ - return std::find(mRecursiveCatUUIDs.begin(), mRecursiveCatUUIDs.end(), cat_id) != mRecursiveCatUUIDs.end(); -} - -///---------------------------------------------------------------------------- -/// Class ::BGItemHttpHandler -///---------------------------------------------------------------------------- - -// Nothing to implement here. All ctor/dtor changes. - -} // end namespace anonymous +/** + * @file llinventorymodelbackgroundfetch.cpp + * @brief Implementation of background fetching of inventory. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llinventorymodelbackgroundfetch.h" + +#include "llaisapi.h" +#include "llagent.h" +#include "llappviewer.h" +#include "llcallbacklist.h" +#include "llinventorymodel.h" +#include "llinventorypanel.h" +#include "llnotificationsutil.h" +#include "llstartup.h" +#include "llviewercontrol.h" +#include "llviewerinventory.h" +#include "llviewermessage.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" +#include "llhttpconstants.h" +#include "bufferarray.h" +#include "bufferstream.h" +#include "llcorehttputil.h" + +// History (may be apocryphal) +// +// Around V2, an HTTP inventory download mechanism was added +// along with inventory LINK items referencing other inventory +// items. As part of this, at login, the entire inventory +// structure is downloaded 'in the background' using the +// backgroundFetch()/bulkFetch() methods. The UDP path can +// still be used and is found in the 'DEPRECATED OLD CODE' +// section. +// +// The old UDP path implemented a throttle that adapted +// itself during running. The mechanism survived info HTTP +// somewhat but was pinned to poll the HTTP plumbing at +// 0.5S intervals. The reasons for this particular value +// have been lost. It's possible to switch between UDP +// and HTTP while this is happening but there may be +// surprises in what happens in that case. +// +// Conversion to llcorehttp reduced the number of connections +// used but batches more data and queues more requests (but +// doesn't due pipelining due to libcurl restrictions). The +// poll interval above was re-examined and reduced to get +// inventory into the viewer more quickly. +// +// Possible future work: +// +// * Don't download the entire heirarchy in one go (which +// might have been how V1 worked). Implications for +// links (which may not have a valid target) and search +// which would then be missing data. +// +// * Review the download rate throttling. Slow then fast? +// Detect bandwidth usage and speed up when it drops? +// +// * An error on a fetch could be due to one item in the batch. +// If the batch were broken up, perhaps more of the inventory +// would download. (Handwave here, not certain this is an +// issue in practice.) +// +// * Conversion to AISv3. +// + + +namespace +{ + +///---------------------------------------------------------------------------- +/// Class ::BGItemHttpHandler +///---------------------------------------------------------------------------- + +// +// Http request handler class for single inventory item requests. +// +// We'll use a handler-per-request pattern here rather than +// a shared handler. Mainly convenient as this was converted +// from a Responder class model. +// +// Derives from and is identical to the normal FetchItemHttpHandler +// except that: 1) it uses the background request object which is +// updated more slowly than the foreground and 2) keeps a count of +// active requests on the LLInventoryModelBackgroundFetch object +// to indicate outstanding operations are in-flight. +// +class BGItemHttpHandler : public LLInventoryModel::FetchItemHttpHandler +{ + LOG_CLASS(BGItemHttpHandler); + +public: + BGItemHttpHandler(const LLSD & request_sd) + : LLInventoryModel::FetchItemHttpHandler(request_sd) + { + LLInventoryModelBackgroundFetch::instance().incrFetchCount(1); + } + + virtual ~BGItemHttpHandler() + { + LLInventoryModelBackgroundFetch::instance().incrFetchCount(-1); + } + +protected: + BGItemHttpHandler(const BGItemHttpHandler &); // Not defined + void operator=(const BGItemHttpHandler &); // Not defined +}; + + +///---------------------------------------------------------------------------- +/// Class ::BGFolderHttpHandler +///---------------------------------------------------------------------------- + +// Http request handler class for folders. +// +// Handler for FetchInventoryDescendents2 and FetchLibDescendents2 +// caps requests for folders. +// +class BGFolderHttpHandler : public LLCore::HttpHandler +{ + LOG_CLASS(BGFolderHttpHandler); + +public: + BGFolderHttpHandler(const LLSD & request_sd, const uuid_vec_t & recursive_cats) + : LLCore::HttpHandler(), + mRequestSD(request_sd), + mRecursiveCatUUIDs(recursive_cats) + { + LLInventoryModelBackgroundFetch::instance().incrFetchCount(1); + } + + virtual ~BGFolderHttpHandler() + { + LLInventoryModelBackgroundFetch::instance().incrFetchCount(-1); + } + +protected: + BGFolderHttpHandler(const BGFolderHttpHandler &); // Not defined + void operator=(const BGFolderHttpHandler &); // Not defined + +public: + virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); + + bool getIsRecursive(const LLUUID & cat_id) const; + +private: + void processData(LLSD & body, LLCore::HttpResponse * response); + void processFailure(LLCore::HttpStatus status, LLCore::HttpResponse * response); + void processFailure(const char * const reason, LLCore::HttpResponse * response); + +private: + LLSD mRequestSD; + const uuid_vec_t mRecursiveCatUUIDs; // hack for storing away which cat fetches are recursive +}; + + +const char * const LOG_INV("Inventory"); + +} // end of namespace anonymous + + +///---------------------------------------------------------------------------- +/// Class LLInventoryModelBackgroundFetch +///---------------------------------------------------------------------------- + +LLInventoryModelBackgroundFetch::LLInventoryModelBackgroundFetch(): + mBackgroundFetchActive(false), + mFolderFetchActive(false), + mFetchCount(0), + mLastFetchCount(0), + mFetchFolderCount(0), + mAllRecursiveFoldersFetched(false), + mRecursiveInventoryFetchStarted(false), + mRecursiveLibraryFetchStarted(false), + mRecursiveMarketplaceFetchStarted(false), + mMinTimeBetweenFetches(0.3f) +{} + +LLInventoryModelBackgroundFetch::~LLInventoryModelBackgroundFetch() +{ + gIdleCallbacks.deleteFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); +} + +bool LLInventoryModelBackgroundFetch::isBulkFetchProcessingComplete() const +{ + return mFetchFolderQueue.empty() && mFetchItemQueue.empty() && mFetchCount <= 0; +} + +bool LLInventoryModelBackgroundFetch::isFolderFetchProcessingComplete() const +{ + return mFetchFolderQueue.empty() && mFetchFolderCount <= 0; +} + +bool LLInventoryModelBackgroundFetch::libraryFetchStarted() const +{ + return mRecursiveLibraryFetchStarted; +} + +bool LLInventoryModelBackgroundFetch::libraryFetchCompleted() const +{ + return libraryFetchStarted() && fetchQueueContainsNoDescendentsOf(gInventory.getLibraryRootFolderID()); +} + +bool LLInventoryModelBackgroundFetch::libraryFetchInProgress() const +{ + return libraryFetchStarted() && !libraryFetchCompleted(); +} + +bool LLInventoryModelBackgroundFetch::inventoryFetchStarted() const +{ + return mRecursiveInventoryFetchStarted; +} + +bool LLInventoryModelBackgroundFetch::inventoryFetchCompleted() const +{ + return inventoryFetchStarted() && fetchQueueContainsNoDescendentsOf(gInventory.getRootFolderID()); +} + +bool LLInventoryModelBackgroundFetch::inventoryFetchInProgress() const +{ + return inventoryFetchStarted() && ! inventoryFetchCompleted(); +} + +bool LLInventoryModelBackgroundFetch::isEverythingFetched() const +{ + return mAllRecursiveFoldersFetched; +} + +bool LLInventoryModelBackgroundFetch::folderFetchActive() const +{ + return mFolderFetchActive; +} + +void LLInventoryModelBackgroundFetch::addRequestAtFront(const LLUUID & id, bool recursive, bool is_category) +{ + EFetchType recursion_type = recursive ? FT_RECURSIVE : FT_DEFAULT; + if (is_category) + { + mFetchFolderQueue.push_front(FetchQueueInfo(id, recursion_type, is_category)); + } + else + { + mFetchItemQueue.push_front(FetchQueueInfo(id, recursion_type, is_category)); + } +} + +void LLInventoryModelBackgroundFetch::addRequestAtBack(const LLUUID & id, bool recursive, bool is_category) +{ + EFetchType recursion_type = recursive ? FT_RECURSIVE : FT_DEFAULT; + if (is_category) + { + mFetchFolderQueue.push_back(FetchQueueInfo(id, recursion_type, is_category)); + } + else + { + mFetchItemQueue.push_back(FetchQueueInfo(id, recursion_type, is_category)); + } +} + +void LLInventoryModelBackgroundFetch::start(const LLUUID& id, bool recursive) +{ + LLViewerInventoryCategory * cat(gInventory.getCategory(id)); + + if (cat || (id.isNull() && ! isEverythingFetched())) + { + // it's a folder, do a bulk fetch + LL_DEBUGS(LOG_INV) << "Start fetching category: " << id << ", recursive: " << recursive << LL_ENDL; + + mBackgroundFetchActive = true; + mFolderFetchActive = true; + EFetchType recursion_type = recursive ? FT_RECURSIVE : FT_DEFAULT; + if (id.isNull()) + { + if (! mRecursiveInventoryFetchStarted) + { + mRecursiveInventoryFetchStarted |= recursive; + if (recursive && AISAPI::isAvailable()) + { + // Not only root folder can be massive, but + // most system folders will be requested independently + // so request root folder and content separately + mFetchFolderQueue.push_front(FetchQueueInfo(gInventory.getRootFolderID(), FT_FOLDER_AND_CONTENT)); + } + else + { + mFetchFolderQueue.push_back(FetchQueueInfo(gInventory.getRootFolderID(), recursion_type)); + } + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + } + if (! mRecursiveLibraryFetchStarted) + { + mRecursiveLibraryFetchStarted |= recursive; + mFetchFolderQueue.push_back(FetchQueueInfo(gInventory.getLibraryRootFolderID(), recursion_type)); + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + } + } + else if (recursive && cat && cat->getPreferredType() == LLFolderType::FT_MARKETPLACE_LISTINGS) + { + if (mFetchFolderQueue.empty() || mFetchFolderQueue.back().mUUID != id) + { + if (recursive && AISAPI::isAvailable()) + { + // Request marketplace folder and content separately + mFetchFolderQueue.push_front(FetchQueueInfo(id, FT_FOLDER_AND_CONTENT)); + } + else + { + mFetchFolderQueue.push_front(FetchQueueInfo(id, recursion_type)); + } + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + mRecursiveMarketplaceFetchStarted = true; + } + } + else + { + if (AISAPI::isAvailable()) + { + if (mFetchFolderQueue.empty() || mFetchFolderQueue.back().mUUID != id) + { + // On AIS make sure root goes to the top and follow up recursive + // fetches, not individual requests + mFetchFolderQueue.push_back(FetchQueueInfo(id, recursion_type)); + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + } + } + else if (mFetchFolderQueue.empty() || mFetchFolderQueue.front().mUUID != id) + { + // Specific folder requests go to front of queue. + mFetchFolderQueue.push_front(FetchQueueInfo(id, recursion_type)); + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + } + + if (id == gInventory.getLibraryRootFolderID()) + { + mRecursiveLibraryFetchStarted |= recursive; + } + if (id == gInventory.getRootFolderID()) + { + mRecursiveInventoryFetchStarted |= recursive; + } + } + } + else if (LLViewerInventoryItem * itemp = gInventory.getItem(id)) + { + if (! itemp->mIsComplete) + { + scheduleItemFetch(id); + } + } +} + +void LLInventoryModelBackgroundFetch::scheduleFolderFetch(const LLUUID& cat_id, bool forced) +{ + if (mFetchFolderQueue.empty() || mFetchFolderQueue.front().mUUID != cat_id) + { + mBackgroundFetchActive = true; + mFolderFetchActive = true; + + if (forced) + { + // check if already requested + if (mForceFetchSet.find(cat_id) == mForceFetchSet.end()) + { + mForceFetchSet.insert(cat_id); + mFetchItemQueue.push_front(FetchQueueInfo(cat_id, FT_FORCED)); + } + } + else + { + // Specific folder requests go to front of queue. + // version presence acts as dupplicate prevention for normal fetches + mFetchItemQueue.push_front(FetchQueueInfo(cat_id, FT_DEFAULT)); + } + + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + } +} + +void LLInventoryModelBackgroundFetch::scheduleItemFetch(const LLUUID& item_id, bool forced) +{ + if (mFetchItemQueue.empty() || mFetchItemQueue.front().mUUID != item_id) + { + mBackgroundFetchActive = true; + if (forced) + { + // check if already requested + if (mForceFetchSet.find(item_id) == mForceFetchSet.end()) + { + mForceFetchSet.insert(item_id); + mFetchItemQueue.push_front(FetchQueueInfo(item_id, FT_FORCED, false)); + } + } + else + { + // 'isFinished' being set acts as dupplicate prevention for normal fetches + mFetchItemQueue.push_front(FetchQueueInfo(item_id, FT_DEFAULT, false)); + } + + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + } +} + +void LLInventoryModelBackgroundFetch::fetchFolderAndLinks(const LLUUID& cat_id, nullary_func_t callback) +{ + LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); + if (cat) + { + // Mark folder (update timer) so that background fetch won't request it + cat->setFetching(LLViewerInventoryCategory::FETCH_RECURSIVE); + } + incrFetchFolderCount(1); + mExpectedFolderIds.push_back(cat_id); + + // Assume that we have no relevant cache. Fetch folder, and items folder's links point to. + AISAPI::FetchCategoryLinks(cat_id, + [callback, cat_id](const LLUUID& id) + { + callback(); + if (id.isNull()) + { + LL_WARNS() << "Failed to fetch category links " << cat_id << LL_ENDL; + } + LLInventoryModelBackgroundFetch::getInstance()->onAISFolderCalback(cat_id, id, FT_DEFAULT); + }); + + // start idle loop to track completion + mBackgroundFetchActive = true; + mFolderFetchActive = true; + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); +} + +void LLInventoryModelBackgroundFetch::fetchCOF(nullary_func_t callback) +{ + LLUUID cat_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); + if (cat) + { + // Mark cof (update timer) so that background fetch won't request it + cat->setFetching(LLViewerInventoryCategory::FETCH_RECURSIVE); + } + incrFetchFolderCount(1); + mExpectedFolderIds.push_back(cat_id); + // For reliability assume that we have no relevant cache, so + // fetch cof along with items cof's links point to. + AISAPI::FetchCOF([callback](const LLUUID& id) + { + callback(); + LLUUID cat_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + LLInventoryModelBackgroundFetch::getInstance()->onAISFolderCalback(cat_id, id, FT_DEFAULT); + }); + + // start idle loop to track completion + mBackgroundFetchActive = true; + mFolderFetchActive = true; + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); +} + +void LLInventoryModelBackgroundFetch::findLostItems() +{ + mBackgroundFetchActive = true; + mFolderFetchActive = true; + mFetchFolderQueue.push_back(FetchQueueInfo(LLUUID::null, FT_RECURSIVE)); + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); +} + +void LLInventoryModelBackgroundFetch::setAllFoldersFetched() +{ + if (mRecursiveInventoryFetchStarted && + mRecursiveLibraryFetchStarted) + { + mAllRecursiveFoldersFetched = true; + //LL_INFOS(LOG_INV) << "All folders fetched, validating" << LL_ENDL; + //gInventory.validate(); + } + + mFolderFetchActive = false; + if (isBulkFetchProcessingComplete()) + { + mBackgroundFetchActive = false; + } + + // For now only informs about initial fetch being done + mFoldersFetchedSignal(); + + LL_INFOS(LOG_INV) << "Inventory background fetch completed" << LL_ENDL; +} + +boost::signals2::connection LLInventoryModelBackgroundFetch::setFetchCompletionCallback(folders_fetched_callback_t cb) +{ + return mFoldersFetchedSignal.connect(cb); +} + +void LLInventoryModelBackgroundFetch::backgroundFetchCB(void *) +{ + LLInventoryModelBackgroundFetch::instance().backgroundFetch(); +} + +void LLInventoryModelBackgroundFetch::backgroundFetch() +{ + if (mBackgroundFetchActive) + { + if (AISAPI::isAvailable()) + { + bulkFetchViaAis(); + } + else if (gAgent.getRegion() && gAgent.getRegion()->capabilitiesReceived()) + { + // If we'll be using the capability, we'll be sending batches and the background thing isn't as important. + bulkFetch(); + } + } +} + +void LLInventoryModelBackgroundFetch::incrFetchCount(S32 fetching) +{ + mFetchCount += fetching; + if (mFetchCount < 0) + { + LL_WARNS_ONCE(LOG_INV) << "Inventory fetch count fell below zero (0)." << LL_ENDL; + mFetchCount = 0; + } +} +void LLInventoryModelBackgroundFetch::incrFetchFolderCount(S32 fetching) +{ + incrFetchCount(fetching); + mFetchFolderCount += fetching; + if (mFetchCount < 0) + { + LL_WARNS_ONCE(LOG_INV) << "Inventory fetch count fell below zero (0)." << LL_ENDL; + mFetchFolderCount = 0; + } +} + +void ais_simple_item_callback(const LLUUID& inv_id) +{ + LL_DEBUGS(LOG_INV , "AIS3") << "Response for " << inv_id << LL_ENDL; + LLInventoryModelBackgroundFetch::instance().incrFetchCount(-1); +} + +void LLInventoryModelBackgroundFetch::onAISContentCalback( + const LLUUID& request_id, + const uuid_vec_t& content_ids, + const LLUUID& response_id, + EFetchType fetch_type) +{ + // Don't push_front on failure - there is a chance it was fired from inside bulkFetchViaAis + incrFetchFolderCount(-1); + + uuid_vec_t::const_iterator folder_iter = content_ids.begin(); + uuid_vec_t::const_iterator folder_end = content_ids.end(); + while (folder_iter != folder_end) + { + std::list::const_iterator found = std::find(mExpectedFolderIds.begin(), mExpectedFolderIds.end(), *folder_iter); + if (found != mExpectedFolderIds.end()) + { + mExpectedFolderIds.erase(found); + } + + LLViewerInventoryCategory* cat(gInventory.getCategory(*folder_iter)); + if (cat) + { + cat->setFetching(LLViewerInventoryCategory::FETCH_NONE); + } + if (response_id.isNull()) + { + // Failed to fetch, get it individually + mFetchFolderQueue.push_back(FetchQueueInfo(*folder_iter, FT_RECURSIVE)); + } + else + { + // push descendant back to verify they are fetched fully (ex: didn't encounter depth limit) + LLInventoryModel::cat_array_t* categories(NULL); + LLInventoryModel::item_array_t* items(NULL); + gInventory.getDirectDescendentsOf(*folder_iter, categories, items); + if (categories) + { + for (LLInventoryModel::cat_array_t::const_iterator it = categories->begin(); + it != categories->end(); + ++it) + { + mFetchFolderQueue.push_back(FetchQueueInfo((*it)->getUUID(), FT_RECURSIVE)); + } + } + } + + folder_iter++; + } + + if (!mFetchFolderQueue.empty()) + { + mBackgroundFetchActive = true; + mFolderFetchActive = true; + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + } +} +void LLInventoryModelBackgroundFetch::onAISFolderCalback(const LLUUID &request_id, const LLUUID &response_id, EFetchType fetch_type) +{ + // Don't push_front on failure - there is a chance it was fired from inside bulkFetchViaAis + incrFetchFolderCount(-1); + std::list::const_iterator found = std::find(mExpectedFolderIds.begin(), mExpectedFolderIds.end(), request_id); + if (found != mExpectedFolderIds.end()) + { + mExpectedFolderIds.erase(found); + } + else + { + // ais shouldn't respond twice + llassert(false); + LL_WARNS() << "Unexpected folder response for " << request_id << LL_ENDL; + } + + if (request_id.isNull()) + { + // orhans, no other actions needed + return; + } + + LLViewerInventoryCategory::EFetchType new_state = LLViewerInventoryCategory::FETCH_NONE; + bool request_descendants = false; + if (response_id.isNull()) // Failure + { + LL_DEBUGS(LOG_INV , "AIS3") << "Failure response for folder " << request_id << LL_ENDL; + if (fetch_type == FT_RECURSIVE) + { + // A full recursive request failed. + // Try requesting folder and nested content separately + mFetchFolderQueue.push_back(FetchQueueInfo(request_id, FT_FOLDER_AND_CONTENT)); + } + else if (fetch_type == FT_FOLDER_AND_CONTENT) + { + LL_WARNS() << "Failed to download folder: " << request_id << " Requesting known content separately" << LL_ENDL; + mFetchFolderQueue.push_back(FetchQueueInfo(request_id, FT_CONTENT_RECURSIVE)); + + // set folder's version to prevent viewer from trying to request folder indefinetely + LLViewerInventoryCategory* cat(gInventory.getCategory(request_id)); + if (cat && cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) + { + cat->setVersion(0); + } + // back off for a bit in case something tries to force-request immediately + new_state = LLViewerInventoryCategory::FETCH_FAILED; + } + } + else + { + if (fetch_type == FT_RECURSIVE) + { + // Got the folder and content, now verify content + // Request content even for FT_RECURSIVE in case of changes, failures + // or if depth limit gets imlemented. + // This shouldn't redownload folders if they already have version + request_descendants = true; + LL_DEBUGS(LOG_INV, "AIS3") << "Got folder " << request_id << ". Requesting content" << LL_ENDL; + } + else if (fetch_type == FT_FOLDER_AND_CONTENT) + { + // readd folder for content request + mFetchFolderQueue.push_front(FetchQueueInfo(request_id, FT_CONTENT_RECURSIVE)); + } + else + { + LL_DEBUGS(LOG_INV, "AIS3") << "Got folder " << request_id << "." << LL_ENDL; + } + + } + + if (request_descendants) + { + LLInventoryModel::cat_array_t* categories(NULL); + LLInventoryModel::item_array_t* items(NULL); + gInventory.getDirectDescendentsOf(request_id, categories, items); + if (categories) + { + for (LLInventoryModel::cat_array_t::const_iterator it = categories->begin(); + it != categories->end(); + ++it) + { + mFetchFolderQueue.push_back(FetchQueueInfo((*it)->getUUID(), FT_RECURSIVE)); + } + } + } + + if (!mFetchFolderQueue.empty()) + { + mBackgroundFetchActive = true; + mFolderFetchActive = true; + gIdleCallbacks.addFunction(&LLInventoryModelBackgroundFetch::backgroundFetchCB, NULL); + } + + // done + LLViewerInventoryCategory * cat(gInventory.getCategory(request_id)); + if (cat) + { + cat->setFetching(new_state); + } +} + +static LLTrace::BlockTimerStatHandle FTM_BULK_FETCH("Bulk Fetch"); + +void LLInventoryModelBackgroundFetch::bulkFetchViaAis() +{ + LL_RECORD_BLOCK_TIME(FTM_BULK_FETCH); + //Background fetch is called from gIdleCallbacks in a loop until background fetch is stopped. + if (gDisconnected) + { + return; + } + + static LLCachedControl ais_pool(gSavedSettings, "PoolSizeAIS", 20); + // Don't have too many requests at once, AIS throttles + // Reserve one request for actions outside of fetch (like renames) + const U32 max_concurrent_fetches = llclamp(ais_pool - 1, 1, 50); + + if (mFetchCount >= max_concurrent_fetches) + { + return; + } + + // Don't loop for too long (in case of large, fully loaded inventory) + F64 curent_time = LLTimer::getTotalSeconds(); + const F64 max_time = LLStartUp::getStartupState() > STATE_WEARABLES_WAIT + ? 0.006f // 6 ms + : 1.f; + const F64 end_time = curent_time + max_time; + S32 last_fetch_count = mFetchCount; + + while (!mFetchFolderQueue.empty() && mFetchCount < max_concurrent_fetches && curent_time < end_time) + { + const FetchQueueInfo & fetch_info(mFetchFolderQueue.front()); + bulkFetchViaAis(fetch_info); + mFetchFolderQueue.pop_front(); + curent_time = LLTimer::getTotalSeconds(); + } + + // Ideally we shouldn't fetch items if recursive fetch isn't done, + // but there is a chance some request will start timeouting and recursive + // fetch will get stuck on a signle folder, don't block item fetch in such case + while (!mFetchItemQueue.empty() && mFetchCount < max_concurrent_fetches && curent_time < end_time) + { + const FetchQueueInfo& fetch_info(mFetchItemQueue.front()); + bulkFetchViaAis(fetch_info); + mFetchItemQueue.pop_front(); + curent_time = LLTimer::getTotalSeconds(); + } + + if (last_fetch_count != mFetchCount // if anything was added + || mLastFetchCount != mFetchCount) // if anything was substracted + { + LL_DEBUGS(LOG_INV , "AIS3") << "Total active fetches: " << mLastFetchCount << "->" << last_fetch_count << "->" << mFetchCount + << ", scheduled folder fetches: " << (S32)mFetchFolderQueue.size() + << ", scheduled item fetches: " << (S32)mFetchItemQueue.size() + << LL_ENDL; + mLastFetchCount = mFetchCount; + + if (!mExpectedFolderIds.empty()) + { + // A folder seem to be stack fetching on QA account, print oldest folder out + LL_DEBUGS(LOG_INV , "AIS3") << "Oldest expected folder: "; + std::list::const_iterator iter = mExpectedFolderIds.begin(); + LL_CONT << *iter; + if ((*iter).notNull()) + { + LLViewerInventoryCategory* cat(gInventory.getCategory(*iter)); + if (cat) + { + LL_CONT << " Folder name: " << cat->getName() << " Parent: " << cat->getParentUUID(); + } + else + { + LL_CONT << " This folder doesn't exist"; + } + } + else + { + LL_CONT << " Orphans request"; + } + LL_CONT << LL_ENDL; + } + } + + if (isFolderFetchProcessingComplete() && mFolderFetchActive) + { + if (!mRecursiveInventoryFetchStarted || mRecursiveMarketplaceFetchStarted) + { + setAllFoldersFetched(); + } + else + { + // Intent is for marketplace request to happen after + // main inventory is done, unless requested by floater + mRecursiveMarketplaceFetchStarted = true; + const LLUUID& marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + if (marketplacelistings_id.notNull()) + { + mFetchFolderQueue.push_front(FetchQueueInfo(marketplacelistings_id, FT_FOLDER_AND_CONTENT)); + } + else + { + setAllFoldersFetched(); + } + } + + } + + if (isBulkFetchProcessingComplete()) + { + mBackgroundFetchActive = false; + } +} + +void LLInventoryModelBackgroundFetch::bulkFetchViaAis(const FetchQueueInfo& fetch_info) +{ + if (fetch_info.mIsCategory) + { + const LLUUID & cat_id(fetch_info.mUUID); + if (cat_id.isNull()) + { + incrFetchFolderCount(1); + mExpectedFolderIds.push_back(cat_id); + // Lost and found + // Should it actually be recursive? + AISAPI::FetchOrphans([](const LLUUID& response_id) + { + LLInventoryModelBackgroundFetch::instance().onAISFolderCalback(LLUUID::null, + response_id, + FT_DEFAULT); + }); + } + else + { + LLViewerInventoryCategory * cat(gInventory.getCategory(cat_id)); + if (cat) + { + if (fetch_info.mFetchType == FT_CONTENT_RECURSIVE) + { + // fetch content only, ignore cat itself + uuid_vec_t children; + LLInventoryModel::cat_array_t* categories(NULL); + LLInventoryModel::item_array_t* items(NULL); + gInventory.getDirectDescendentsOf(cat_id, categories, items); + + LLViewerInventoryCategory::EFetchType target_state = LLViewerInventoryCategory::FETCH_RECURSIVE; + bool content_done = true; + + // Top limit is 'as many as you can put into url' + static LLCachedControl ais_batch(gSavedSettings, "BatchSizeAIS3", 20); + S32 batch_limit = llclamp(ais_batch(), 1, 40); + + for (LLInventoryModel::cat_array_t::iterator it = categories->begin(); + it != categories->end(); + ++it) + { + LLViewerInventoryCategory* child_cat = (*it); + if (LLViewerInventoryCategory::VERSION_UNKNOWN != child_cat->getVersion() + || child_cat->getFetching() >= target_state) + { + continue; + } + + if (child_cat->getPreferredType() == LLFolderType::FT_MARKETPLACE_LISTINGS) + { + // special case, marketplace will fetch that as needed + continue; + } + + children.push_back(child_cat->getUUID()); + mExpectedFolderIds.push_back(child_cat->getUUID()); + child_cat->setFetching(target_state); + + if (children.size() >= batch_limit) + { + content_done = false; + break; + } + } + + if (!children.empty()) + { + // increment before call in case of immediate callback + incrFetchFolderCount(1); + + EFetchType type = fetch_info.mFetchType; + LLUUID cat_id = cat->getUUID(); // need a copy for lambda + AISAPI::completion_t cb = [cat_id, children, type](const LLUUID& response_id) + { + LLInventoryModelBackgroundFetch::instance().onAISContentCalback(cat_id, children, response_id, type); + }; + + AISAPI::ITEM_TYPE item_type = AISAPI::INVENTORY; + if (ALEXANDRIA_LINDEN_ID == cat->getOwnerID()) + { + item_type = AISAPI::LIBRARY; + } + + AISAPI::FetchCategorySubset(cat_id, children, item_type, true, cb, 0); + } + + if (content_done) + { + // This will have a bit of overlap with onAISContentCalback, + // but something else might have dowloaded folders, so verify + // every child that is complete has it's children done as well + for (LLInventoryModel::cat_array_t::iterator it = categories->begin(); + it != categories->end(); + ++it) + { + LLViewerInventoryCategory* child_cat = (*it); + if (LLViewerInventoryCategory::VERSION_UNKNOWN != child_cat->getVersion()) + { + mFetchFolderQueue.push_back(FetchQueueInfo(child_cat->getUUID(), FT_RECURSIVE)); + } + } + } + else + { + // send it back to get the rest + mFetchFolderQueue.push_back(FetchQueueInfo(cat_id, FT_CONTENT_RECURSIVE)); + } + } + else if (LLViewerInventoryCategory::VERSION_UNKNOWN == cat->getVersion() + || fetch_info.mFetchType == FT_FORCED) + { + LLViewerInventoryCategory::EFetchType target_state = + fetch_info.mFetchType > FT_CONTENT_RECURSIVE + ? LLViewerInventoryCategory::FETCH_RECURSIVE + : LLViewerInventoryCategory::FETCH_NORMAL; + // start again if we did a non-recursive fetch before + // to get all children in a single request + if (cat->getFetching() < target_state) + { + // increment before call in case of immediate callback + incrFetchFolderCount(1); + cat->setFetching(target_state); + mExpectedFolderIds.push_back(cat_id); + + EFetchType type = fetch_info.mFetchType; + LLUUID cat_cb_id = cat_id; + AISAPI::completion_t cb = [cat_cb_id, type](const LLUUID& response_id) + { + LLInventoryModelBackgroundFetch::instance().onAISFolderCalback(cat_cb_id, response_id , type); + }; + + AISAPI::ITEM_TYPE item_type = AISAPI::INVENTORY; + if (ALEXANDRIA_LINDEN_ID == cat->getOwnerID()) + { + item_type = AISAPI::LIBRARY; + } + + AISAPI::FetchCategoryChildren(cat_id , item_type , type == FT_RECURSIVE , cb, 0); + } + } + else + { + // Already fetched, check if anything inside needs fetching + if (fetch_info.mFetchType == FT_RECURSIVE + || fetch_info.mFetchType == FT_FOLDER_AND_CONTENT) + { + LLInventoryModel::cat_array_t * categories(NULL); + LLInventoryModel::item_array_t * items(NULL); + gInventory.getDirectDescendentsOf(cat_id, categories, items); + for (LLInventoryModel::cat_array_t::const_iterator it = categories->begin(); + it != categories->end(); + ++it) + { + // not push_front to not cause an infinite loop + mFetchFolderQueue.push_back(FetchQueueInfo((*it)->getUUID(), FT_RECURSIVE)); + } + } + } + } // else try to fetch folder either way? + } + } + else + { + LLViewerInventoryItem * itemp(gInventory.getItem(fetch_info.mUUID)); + + if (itemp) + { + if (!itemp->isFinished() || fetch_info.mFetchType == FT_FORCED) + { + mFetchCount++; + if (itemp->getPermissions().getOwner() == gAgent.getID()) + { + AISAPI::FetchItem(fetch_info.mUUID, AISAPI::INVENTORY, ais_simple_item_callback); + } + else + { + AISAPI::FetchItem(fetch_info.mUUID, AISAPI::LIBRARY, ais_simple_item_callback); + } + } + } + else // We don't know it, assume incomplete + { + // Assume agent's inventory, library wouldn't have gotten here + mFetchCount++; + AISAPI::FetchItem(fetch_info.mUUID, AISAPI::INVENTORY, ais_simple_item_callback); + } + } + + if (fetch_info.mFetchType == FT_FORCED) + { + mForceFetchSet.erase(fetch_info.mUUID); + } +} + +// Bundle up a bunch of requests to send all at once. +void LLInventoryModelBackgroundFetch::bulkFetch() +{ + LL_RECORD_BLOCK_TIME(FTM_BULK_FETCH); + //Background fetch is called from gIdleCallbacks in a loop until background fetch is stopped. + //If there are items in mFetchQueue, we want to check the time since the last bulkFetch was + //sent. If it exceeds our retry time, go ahead and fire off another batch. + LLViewerRegion * region(gAgent.getRegion()); + if (! region || gDisconnected || LLApp::isExiting()) + { + return; + } + + // *TODO: These values could be tweaked at runtime to effect + // a fast/slow fetch throttle. Once login is complete and the scene + // is mostly loaded, we could turn up the throttle and fill missing + // inventory more quickly. + static const U32 max_batch_size(10); + static const S32 max_concurrent_fetches(12); // Outstanding requests, not connections + + if (mFetchCount) + { + // Process completed background HTTP requests + gInventory.handleResponses(false); + // Just processed a bunch of items. + // Note: do we really need notifyObservers() here? + // OnIdle it will be called anyway due to Add flag for processed item. + // It seems like in some cases we are updaiting on fail (no flag), + // but is there anything to update? + gInventory.notifyObservers(); + } + + if (mFetchCount > max_concurrent_fetches) + { + return; + } + + U32 item_count(0); + U32 folder_count(0); + + const U32 sort_order(gSavedSettings.getU32(LLInventoryPanel::DEFAULT_SORT_ORDER) & 0x1); + + // *TODO: Think I'd like to get a shared pointer to this and share it + // among all the folder requests. + uuid_vec_t recursive_cats; + uuid_vec_t all_cats; // dupplicate avoidance + + LLSD folder_request_body; + LLSD folder_request_body_lib; + LLSD item_request_body; + LLSD item_request_body_lib; + + while (! mFetchFolderQueue.empty() + && (item_count + folder_count) < max_batch_size) + { + const FetchQueueInfo & fetch_info(mFetchFolderQueue.front()); + if (fetch_info.mIsCategory) + { + const LLUUID & cat_id(fetch_info.mUUID); + if (cat_id.isNull()) //DEV-17797 Lost and found + { + LLSD folder_sd; + folder_sd["folder_id"] = LLUUID::null.asString(); + folder_sd["owner_id"] = gAgent.getID(); + folder_sd["sort_order"] = LLSD::Integer(sort_order); + folder_sd["fetch_folders"] = LLSD::Boolean(false); + folder_sd["fetch_items"] = LLSD::Boolean(true); + folder_request_body["folders"].append(folder_sd); + folder_count++; + } + else + { + const LLViewerInventoryCategory * cat(gInventory.getCategory(cat_id)); + if (cat) + { + if (LLViewerInventoryCategory::VERSION_UNKNOWN == cat->getVersion()) + { + if (std::find(all_cats.begin(), all_cats.end(), cat_id) == all_cats.end()) + { + LLSD folder_sd; + folder_sd["folder_id"] = cat->getUUID(); + folder_sd["owner_id"] = cat->getOwnerID(); + folder_sd["sort_order"] = LLSD::Integer(sort_order); + folder_sd["fetch_folders"] = LLSD::Boolean(true); //(LLSD::Boolean)sFullFetchStarted; + folder_sd["fetch_items"] = LLSD::Boolean(true); + + if (ALEXANDRIA_LINDEN_ID == cat->getOwnerID()) + { + folder_request_body_lib["folders"].append(folder_sd); + } + else + { + folder_request_body["folders"].append(folder_sd); + } + folder_count++; + } + } + else + { + // May already have this folder, but append child folders to list. + if (fetch_info.mFetchType >= FT_CONTENT_RECURSIVE) + { + LLInventoryModel::cat_array_t * categories(NULL); + LLInventoryModel::item_array_t * items(NULL); + gInventory.getDirectDescendentsOf(cat_id, categories, items); + for (LLInventoryModel::cat_array_t::const_iterator it = categories->begin(); + it != categories->end(); + ++it) + { + mFetchFolderQueue.push_back(FetchQueueInfo((*it)->getUUID(), fetch_info.mFetchType)); + } + } + } + } + } + if (fetch_info.mFetchType >= FT_CONTENT_RECURSIVE) + { + recursive_cats.push_back(cat_id); + } + all_cats.push_back(cat_id); + } + + mFetchFolderQueue.pop_front(); + } + + + while (!mFetchItemQueue.empty() + && (item_count + folder_count) < max_batch_size) + { + const FetchQueueInfo & fetch_info(mFetchItemQueue.front()); + + LLViewerInventoryItem * itemp(gInventory.getItem(fetch_info.mUUID)); + + if (itemp) + { + LLSD item_sd; + item_sd["owner_id"] = itemp->getPermissions().getOwner(); + item_sd["item_id"] = itemp->getUUID(); + if (itemp->getPermissions().getOwner() == gAgent.getID()) + { + item_request_body.append(item_sd); + } + else + { + item_request_body_lib.append(item_sd); + } + //itemp->fetchFromServer(); + item_count++; + } + + mFetchItemQueue.pop_front(); + } + + // Issue HTTP POST requests to fetch folders and items + + if (item_count + folder_count > 0) + { + if (folder_count) + { + if (folder_request_body["folders"].size()) + { + const std::string url(region->getCapability("FetchInventoryDescendents2")); + + if (! url.empty()) + { + LLCore::HttpHandler::ptr_t handler(new BGFolderHttpHandler(folder_request_body, recursive_cats)); + gInventory.requestPost(false, url, folder_request_body, handler, "Inventory Folder"); + } + } + + if (folder_request_body_lib["folders"].size()) + { + const std::string url(region->getCapability("FetchLibDescendents2")); + + if (! url.empty()) + { + LLCore::HttpHandler::ptr_t handler(new BGFolderHttpHandler(folder_request_body_lib, recursive_cats)); + gInventory.requestPost(false, url, folder_request_body_lib, handler, "Library Folder"); + } + } + } // if (folder_count) + + if (item_count) + { + if (item_request_body.size()) + { + const std::string url(region->getCapability("FetchInventory2")); + + if (! url.empty()) + { + LLSD body; + body["items"] = item_request_body; + LLCore::HttpHandler::ptr_t handler(new BGItemHttpHandler(body)); + gInventory.requestPost(false, url, body, handler, "Inventory Item"); + } + } + + if (item_request_body_lib.size()) + { + const std::string url(region->getCapability("FetchLib2")); + + if (! url.empty()) + { + LLSD body; + body["items"] = item_request_body_lib; + LLCore::HttpHandler::ptr_t handler(new BGItemHttpHandler(body)); + gInventory.requestPost(false, url, body, handler, "Library Item"); + } + } + } // if (item_count) + + mFetchTimer.reset(); + } + else if (isBulkFetchProcessingComplete()) + { + setAllFoldersFetched(); + } +} + +bool LLInventoryModelBackgroundFetch::fetchQueueContainsNoDescendentsOf(const LLUUID & cat_id) const +{ + for (fetch_queue_t::const_iterator it = mFetchFolderQueue.begin(); + it != mFetchFolderQueue.end(); + ++it) + { + const LLUUID & fetch_id = (*it).mUUID; + if (gInventory.isObjectDescendentOf(fetch_id, cat_id)) + return false; + } + for (fetch_queue_t::const_iterator it = mFetchItemQueue.begin(); + it != mFetchItemQueue.end(); + ++it) + { + const LLUUID & fetch_id = (*it).mUUID; + if (gInventory.isObjectDescendentOf(fetch_id, cat_id)) + return false; + } + return true; +} + + +namespace +{ + +///---------------------------------------------------------------------------- +/// Class ::BGFolderHttpHandler +///---------------------------------------------------------------------------- + +void BGFolderHttpHandler::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) +{ + do // Single-pass do-while used for common exit handling + { + LLCore::HttpStatus status(response->getStatus()); + // status = LLCore::HttpStatus(404); // Dev tool to force error handling + if (! status) + { + processFailure(status, response); + break; // Goto common exit + } + + // Response body should be present. + LLCore::BufferArray * body(response->getBody()); + // body = NULL; // Dev tool to force error handling + if (! body || ! body->size()) + { + LL_WARNS(LOG_INV) << "Missing data in inventory folder query." << LL_ENDL; + processFailure("HTTP response missing expected body", response); + break; // Goto common exit + } + + // Could test 'Content-Type' header but probably unreliable. + + // Convert response to LLSD + // body->write(0, "Garbage Response", 16); // Dev tool to force error handling + LLSD body_llsd; + if (! LLCoreHttpUtil::responseToLLSD(response, true, body_llsd)) + { + // INFOS-level logging will occur on the parsed failure + processFailure("HTTP response contained malformed LLSD", response); + break; // goto common exit + } + + // Expect top-level structure to be a map + // body_llsd = LLSD::emptyArray(); // Dev tool to force error handling + if (! body_llsd.isMap()) + { + processFailure("LLSD response not a map", response); + break; // goto common exit + } + + // Check for 200-with-error failures + // + // See comments in llinventorymodel.cpp about this mode of error. + // + // body_llsd["error"] = LLSD::emptyMap(); // Dev tool to force error handling + // body_llsd["error"]["identifier"] = "Development"; + // body_llsd["error"]["message"] = "You left development code in the viewer"; + if (body_llsd.has("error")) + { + processFailure("Inventory application error (200-with-error)", response); + break; // goto common exit + } + + // Okay, process data if possible + processData(body_llsd, response); + } + while (false); +} + + +void BGFolderHttpHandler::processData(LLSD & content, LLCore::HttpResponse * response) +{ + LLInventoryModelBackgroundFetch * fetcher(LLInventoryModelBackgroundFetch::getInstance()); + + // API V2 and earlier should probably be testing for "error" map + // in response as an application-level error. + + // Instead, we assume success and attempt to extract information. + if (content.has("folders")) + { + LLSD folders(content["folders"]); + + for (LLSD::array_const_iterator folder_it = folders.beginArray(); + folder_it != folders.endArray(); + ++folder_it) + { + LLSD folder_sd(*folder_it); + + //LLUUID agent_id = folder_sd["agent_id"]; + + //if(agent_id != gAgent.getID()) //This should never happen. + //{ + // LL_WARNS(LOG_INV) << "Got a UpdateInventoryItem for the wrong agent." + // << LL_ENDL; + // break; + //} + + LLUUID parent_id(folder_sd["folder_id"].asUUID()); + LLUUID owner_id(folder_sd["owner_id"].asUUID()); + S32 version(folder_sd["version"].asInteger()); + S32 descendents(folder_sd["descendents"].asInteger()); + LLPointer tcategory = new LLViewerInventoryCategory(owner_id); + + if (parent_id.isNull()) + { + LLSD items(folder_sd["items"]); + LLPointer titem = new LLViewerInventoryItem; + + for (LLSD::array_const_iterator item_it = items.beginArray(); + item_it != items.endArray(); + ++item_it) + { + const LLUUID lost_uuid(gInventory.findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND)); + + if (lost_uuid.notNull()) + { + LLSD item(*item_it); + + titem->unpackMessage(item); + + LLInventoryModel::update_list_t update; + LLInventoryModel::LLCategoryUpdate new_folder(lost_uuid, 1); + update.push_back(new_folder); + gInventory.accountForUpdate(update); + + titem->setParent(lost_uuid); + titem->updateParentOnServer(false); + gInventory.updateItem(titem); + } + } + } + + LLViewerInventoryCategory * pcat(gInventory.getCategory(parent_id)); + if (! pcat) + { + continue; + } + + LLSD categories(folder_sd["categories"]); + for (LLSD::array_const_iterator category_it = categories.beginArray(); + category_it != categories.endArray(); + ++category_it) + { + LLSD category(*category_it); + tcategory->fromLLSD(category); + + const bool recursive(getIsRecursive(tcategory->getUUID())); + if (recursive) + { + fetcher->addRequestAtBack(tcategory->getUUID(), recursive, true); + } + else if (! gInventory.isCategoryComplete(tcategory->getUUID())) + { + gInventory.updateCategory(tcategory); + } + } + + LLSD items(folder_sd["items"]); + LLPointer titem = new LLViewerInventoryItem; + for (LLSD::array_const_iterator item_it = items.beginArray(); + item_it != items.endArray(); + ++item_it) + { + LLSD item(*item_it); + titem->unpackMessage(item); + + gInventory.updateItem(titem); + } + + // Set version and descendentcount according to message. + LLViewerInventoryCategory * cat(gInventory.getCategory(parent_id)); + if (cat) + { + cat->setVersion(version); + cat->setDescendentCount(descendents); + cat->determineFolderType(); + } + } + } + + if (content.has("bad_folders")) + { + LLSD bad_folders(content["bad_folders"]); + for (LLSD::array_const_iterator folder_it = bad_folders.beginArray(); + folder_it != bad_folders.endArray(); + ++folder_it) + { + // *TODO: Stop copying data [ed: this isn't copying data] + LLSD folder_sd(*folder_it); + + // These folders failed on the dataserver. We probably don't want to retry them. + LL_WARNS(LOG_INV) << "Folder " << folder_sd["folder_id"].asString() + << "Error: " << folder_sd["error"].asString() << LL_ENDL; + } + } + + if (fetcher->isBulkFetchProcessingComplete()) + { + fetcher->setAllFoldersFetched(); + } +} + + +void BGFolderHttpHandler::processFailure(LLCore::HttpStatus status, LLCore::HttpResponse * response) +{ + const std::string & ct(response->getContentType()); + LL_WARNS(LOG_INV) << "Inventory folder fetch failure\n" + << "[Status: " << status.toTerseString() << "]\n" + << "[Reason: " << status.toString() << "]\n" + << "[Content-type: " << ct << "]\n" + << "[Content (abridged): " + << LLCoreHttpUtil::responseToString(response) << "]" << LL_ENDL; + + // Could use a 404 test here to try to detect revoked caps... + + if(status == LLCore::HttpStatus(HTTP_FORBIDDEN)) + { + // Too large, split into two if possible + if (gDisconnected || LLApp::isExiting()) + { + return; + } + + const std::string url(gAgent.getRegionCapability("FetchInventoryDescendents2")); + if (url.empty()) + { + LL_WARNS(LOG_INV) << "Failed to get AIS2 cap" << LL_ENDL; + return; + } + + S32 size = mRequestSD["folders"].size(); + + if (size > 1) + { + // Can split, assume that this isn't the library + LLSD folders; + uuid_vec_t recursive_cats; + LLSD::array_iterator iter = mRequestSD["folders"].beginArray(); + LLSD::array_iterator end = mRequestSD["folders"].endArray(); + while (iter != end) + { + folders.append(*iter); + LLUUID folder_id = iter->get("folder_id").asUUID(); + if (std::find(mRecursiveCatUUIDs.begin(), mRecursiveCatUUIDs.end(), folder_id) != mRecursiveCatUUIDs.end()) + { + recursive_cats.push_back(folder_id); + } + if (folders.size() == (S32)(size / 2)) + { + LLSD request_body; + request_body["folders"] = folders; + LLCore::HttpHandler::ptr_t handler(new BGFolderHttpHandler(request_body, recursive_cats)); + gInventory.requestPost(false, url, request_body, handler, "Inventory Folder"); + recursive_cats.clear(); + folders.clear(); + } + iter++; + } + + LLSD request_body; + request_body["folders"] = folders; + LLCore::HttpHandler::ptr_t handler(new BGFolderHttpHandler(request_body, recursive_cats)); + gInventory.requestPost(false, url, request_body, handler, "Inventory Folder"); + return; + } + else + { + // Can't split + LLNotificationsUtil::add("InventoryLimitReachedAIS"); + } + } + + // This was originally the request retry logic for the inventory + // request which tested on HTTP_INTERNAL_ERROR status. This + // retry logic was unbounded and lacked discrimination as to the + // cause of the retry. The new http library should be doing + // adquately on retries but I want to keep the structure of a + // retry for reference. + LLInventoryModelBackgroundFetch *fetcher = LLInventoryModelBackgroundFetch::getInstance(); + if (false) + { + // timed out or curl failure + for (LLSD::array_const_iterator folder_it = mRequestSD["folders"].beginArray(); + folder_it != mRequestSD["folders"].endArray(); + ++folder_it) + { + LLSD folder_sd(*folder_it); + LLUUID folder_id(folder_sd["folder_id"].asUUID()); + const bool recursive = getIsRecursive(folder_id); + fetcher->addRequestAtFront(folder_id, recursive, true); + } + } + else + { + if (fetcher->isBulkFetchProcessingComplete()) + { + fetcher->setAllFoldersFetched(); + } + } +} + + +void BGFolderHttpHandler::processFailure(const char * const reason, LLCore::HttpResponse * response) +{ + LL_WARNS(LOG_INV) << "Inventory folder fetch failure\n" + << "[Status: internal error]\n" + << "[Reason: " << reason << "]\n" + << "[Content (abridged): " + << LLCoreHttpUtil::responseToString(response) << "]" << LL_ENDL; + + // Reverse of previous processFailure() method, this is invoked + // when response structure is found to be invalid. Original + // always re-issued the request (without limit). This does + // the same but be aware that this may be a source of problems. + // Philosophy is that inventory folders are so essential to + // operation that this is a reasonable action. + LLInventoryModelBackgroundFetch *fetcher = LLInventoryModelBackgroundFetch::getInstance(); + if (true) + { + for (LLSD::array_const_iterator folder_it = mRequestSD["folders"].beginArray(); + folder_it != mRequestSD["folders"].endArray(); + ++folder_it) + { + LLSD folder_sd(*folder_it); + LLUUID folder_id(folder_sd["folder_id"].asUUID()); + const bool recursive = getIsRecursive(folder_id); + fetcher->addRequestAtFront(folder_id, recursive, true); + } + } + else + { + if (fetcher->isBulkFetchProcessingComplete()) + { + fetcher->setAllFoldersFetched(); + } + } +} + + +bool BGFolderHttpHandler::getIsRecursive(const LLUUID & cat_id) const +{ + return std::find(mRecursiveCatUUIDs.begin(), mRecursiveCatUUIDs.end(), cat_id) != mRecursiveCatUUIDs.end(); +} + +///---------------------------------------------------------------------------- +/// Class ::BGItemHttpHandler +///---------------------------------------------------------------------------- + +// Nothing to implement here. All ctor/dtor changes. + +} // end namespace anonymous diff --git a/indra/newview/llinventorymodelbackgroundfetch.h b/indra/newview/llinventorymodelbackgroundfetch.h index 83efbe411a..b3fbe66c69 100644 --- a/indra/newview/llinventorymodelbackgroundfetch.h +++ b/indra/newview/llinventorymodelbackgroundfetch.h @@ -1,145 +1,145 @@ -/** - * @file llinventorymodelbackgroundfetch.h - * @brief LLInventoryModelBackgroundFetch class header file - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLINVENTORYMODELBACKGROUNDFETCH_H -#define LL_LLINVENTORYMODELBACKGROUNDFETCH_H - -#include "llsingleton.h" -#include "lluuid.h" -#include "httpcommon.h" -#include "httprequest.h" -#include "httpoptions.h" -#include "httpheaders.h" -#include "httphandler.h" - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLInventoryModelBackgroundFetch -// -// This class handles background fetches, which are fetches of -// inventory folder. Fetches can be recursive or not. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLInventoryModelBackgroundFetch : public LLSingleton -{ - LLSINGLETON(LLInventoryModelBackgroundFetch); - ~LLInventoryModelBackgroundFetch(); -public: - - // Start background breadth-first fetching of inventory contents. - // This gets triggered when performing a filter-search. - void start(const LLUUID& cat_id = LLUUID::null, bool recursive = true); - void scheduleFolderFetch(const LLUUID& cat_id, bool forced = false); - void scheduleItemFetch(const LLUUID& item_id, bool forced = false); - - typedef boost::function nullary_func_t; - // AIS3 only, Fetches folder and everithing links inside the folder point to - // Intended for outfits - void fetchFolderAndLinks(const LLUUID& cat_id, nullary_func_t callback); - // AIS3 only - void fetchCOF(nullary_func_t callback); - - bool folderFetchActive() const; - bool isEverythingFetched() const; // completing the fetch once per session should be sufficient - - bool libraryFetchStarted() const; - bool libraryFetchCompleted() const; - bool libraryFetchInProgress() const; - - bool inventoryFetchStarted() const; - bool inventoryFetchCompleted() const; - bool inventoryFetchInProgress() const; - - void findLostItems(); - void incrFetchCount(S32 fetching); - void incrFetchFolderCount(S32 fetching); - - bool isBulkFetchProcessingComplete() const; - void setAllFoldersFetched(); - - typedef boost::function folders_fetched_callback_t; - boost::signals2::connection setFetchCompletionCallback(folders_fetched_callback_t cb); - - void addRequestAtFront(const LLUUID & id, bool recursive, bool is_category); - void addRequestAtBack(const LLUUID & id, bool recursive, bool is_category); - -protected: - bool isFolderFetchProcessingComplete() const; - - typedef enum { - FT_DEFAULT = 0, - FT_FORCED, // request non-recursively even if already loaded - FT_CONTENT_RECURSIVE, // request content recursively - FT_FOLDER_AND_CONTENT, // request folder, then content recursively - FT_RECURSIVE, // request everything recursively - } EFetchType; - struct FetchQueueInfo - { - FetchQueueInfo(const LLUUID& id, EFetchType recursive, bool is_category = true) - : mUUID(id), - mIsCategory(is_category), - mFetchType(recursive) - {} - - LLUUID mUUID; - bool mIsCategory; - EFetchType mFetchType; - }; - typedef std::deque fetch_queue_t; - - void onAISContentCalback(const LLUUID& request_id, const uuid_vec_t &content_ids, const LLUUID& response_id, EFetchType fetch_type); - void onAISFolderCalback(const LLUUID &request_id, const LLUUID &response_id, EFetchType fetch_type); - void bulkFetchViaAis(); - void bulkFetchViaAis(const FetchQueueInfo& fetch_info); - void bulkFetch(); - - void backgroundFetch(); - static void backgroundFetchCB(void*); // background fetch idle function - - bool fetchQueueContainsNoDescendentsOf(const LLUUID& cat_id) const; - -private: - bool mRecursiveInventoryFetchStarted; - bool mRecursiveLibraryFetchStarted; - bool mRecursiveMarketplaceFetchStarted; // AIS3 specific - bool mAllRecursiveFoldersFetched; - typedef boost::signals2::signal folders_fetched_signal_t; - folders_fetched_signal_t mFoldersFetchedSignal; - - bool mBackgroundFetchActive; - bool mFolderFetchActive; - S32 mFetchCount; - S32 mLastFetchCount; // for debug - S32 mFetchFolderCount; - - LLFrameTimer mFetchTimer; - F32 mMinTimeBetweenFetches; - fetch_queue_t mFetchFolderQueue; - fetch_queue_t mFetchItemQueue; - uuid_set_t mForceFetchSet; - std::list mExpectedFolderIds; // for debug, should this track time? -}; - -#endif // LL_LLINVENTORYMODELBACKGROUNDFETCH_H - +/** + * @file llinventorymodelbackgroundfetch.h + * @brief LLInventoryModelBackgroundFetch class header file + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLINVENTORYMODELBACKGROUNDFETCH_H +#define LL_LLINVENTORYMODELBACKGROUNDFETCH_H + +#include "llsingleton.h" +#include "lluuid.h" +#include "httpcommon.h" +#include "httprequest.h" +#include "httpoptions.h" +#include "httpheaders.h" +#include "httphandler.h" + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLInventoryModelBackgroundFetch +// +// This class handles background fetches, which are fetches of +// inventory folder. Fetches can be recursive or not. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLInventoryModelBackgroundFetch : public LLSingleton +{ + LLSINGLETON(LLInventoryModelBackgroundFetch); + ~LLInventoryModelBackgroundFetch(); +public: + + // Start background breadth-first fetching of inventory contents. + // This gets triggered when performing a filter-search. + void start(const LLUUID& cat_id = LLUUID::null, bool recursive = true); + void scheduleFolderFetch(const LLUUID& cat_id, bool forced = false); + void scheduleItemFetch(const LLUUID& item_id, bool forced = false); + + typedef boost::function nullary_func_t; + // AIS3 only, Fetches folder and everithing links inside the folder point to + // Intended for outfits + void fetchFolderAndLinks(const LLUUID& cat_id, nullary_func_t callback); + // AIS3 only + void fetchCOF(nullary_func_t callback); + + bool folderFetchActive() const; + bool isEverythingFetched() const; // completing the fetch once per session should be sufficient + + bool libraryFetchStarted() const; + bool libraryFetchCompleted() const; + bool libraryFetchInProgress() const; + + bool inventoryFetchStarted() const; + bool inventoryFetchCompleted() const; + bool inventoryFetchInProgress() const; + + void findLostItems(); + void incrFetchCount(S32 fetching); + void incrFetchFolderCount(S32 fetching); + + bool isBulkFetchProcessingComplete() const; + void setAllFoldersFetched(); + + typedef boost::function folders_fetched_callback_t; + boost::signals2::connection setFetchCompletionCallback(folders_fetched_callback_t cb); + + void addRequestAtFront(const LLUUID & id, bool recursive, bool is_category); + void addRequestAtBack(const LLUUID & id, bool recursive, bool is_category); + +protected: + bool isFolderFetchProcessingComplete() const; + + typedef enum { + FT_DEFAULT = 0, + FT_FORCED, // request non-recursively even if already loaded + FT_CONTENT_RECURSIVE, // request content recursively + FT_FOLDER_AND_CONTENT, // request folder, then content recursively + FT_RECURSIVE, // request everything recursively + } EFetchType; + struct FetchQueueInfo + { + FetchQueueInfo(const LLUUID& id, EFetchType recursive, bool is_category = true) + : mUUID(id), + mIsCategory(is_category), + mFetchType(recursive) + {} + + LLUUID mUUID; + bool mIsCategory; + EFetchType mFetchType; + }; + typedef std::deque fetch_queue_t; + + void onAISContentCalback(const LLUUID& request_id, const uuid_vec_t &content_ids, const LLUUID& response_id, EFetchType fetch_type); + void onAISFolderCalback(const LLUUID &request_id, const LLUUID &response_id, EFetchType fetch_type); + void bulkFetchViaAis(); + void bulkFetchViaAis(const FetchQueueInfo& fetch_info); + void bulkFetch(); + + void backgroundFetch(); + static void backgroundFetchCB(void*); // background fetch idle function + + bool fetchQueueContainsNoDescendentsOf(const LLUUID& cat_id) const; + +private: + bool mRecursiveInventoryFetchStarted; + bool mRecursiveLibraryFetchStarted; + bool mRecursiveMarketplaceFetchStarted; // AIS3 specific + bool mAllRecursiveFoldersFetched; + typedef boost::signals2::signal folders_fetched_signal_t; + folders_fetched_signal_t mFoldersFetchedSignal; + + bool mBackgroundFetchActive; + bool mFolderFetchActive; + S32 mFetchCount; + S32 mLastFetchCount; // for debug + S32 mFetchFolderCount; + + LLFrameTimer mFetchTimer; + F32 mMinTimeBetweenFetches; + fetch_queue_t mFetchFolderQueue; + fetch_queue_t mFetchItemQueue; + uuid_set_t mForceFetchSet; + std::list mExpectedFolderIds; // for debug, should this track time? +}; + +#endif // LL_LLINVENTORYMODELBACKGROUNDFETCH_H + diff --git a/indra/newview/llinventoryobserver.cpp b/indra/newview/llinventoryobserver.cpp index a714933a1a..3aed82e259 100644 --- a/indra/newview/llinventoryobserver.cpp +++ b/indra/newview/llinventoryobserver.cpp @@ -1,866 +1,866 @@ -/** - * @file llinventoryobserver.cpp - * @brief Implementation of the inventory observers used to track agent inventory. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llinventoryobserver.h" - -#include "llassetstorage.h" -#include "llcrc.h" -#include "lldir.h" -#include "llsys.h" -#include "llxfermanager.h" -#include "message.h" - -#include "llagent.h" -#include "llagentwearables.h" -#include "llaisapi.h" -#include "llfloater.h" -#include "llfocusmgr.h" -#include "llinventorymodelbackgroundfetch.h" -#include "llinventorybridge.h" -#include "llinventoryfunctions.h" -#include "llinventorymodel.h" -#include "llviewermessage.h" -#include "llviewerwindow.h" -#include "llviewerregion.h" -#include "llappviewer.h" -#include "lldbstrings.h" -#include "llviewerstats.h" -#include "llnotificationsutil.h" -#include "llcallbacklist.h" -#include "llpreview.h" -#include "llviewercontrol.h" -#include "llvoavatarself.h" -#include "llsdutil.h" -#include - -const S32 LLInventoryFetchItemsObserver::MAX_INDIVIDUAL_ITEM_REQUESTS = 7; -const F32 LLInventoryFetchItemsObserver::FETCH_TIMER_EXPIRY = 60.0f; - - -LLInventoryObserver::LLInventoryObserver() -{ -} - -// virtual -LLInventoryObserver::~LLInventoryObserver() -{ -} - -LLInventoryFetchObserver::LLInventoryFetchObserver(const LLUUID& id) -{ - mIDs.clear(); - if (id != LLUUID::null) - { - setFetchID(id); - } -} - -LLInventoryFetchObserver::LLInventoryFetchObserver(const uuid_vec_t& ids) -{ - setFetchIDs(ids); -} - -bool LLInventoryFetchObserver::isFinished() const -{ - return mIncomplete.empty(); -} - -void LLInventoryFetchObserver::setFetchIDs(const uuid_vec_t& ids) -{ - mIDs = ids; -} -void LLInventoryFetchObserver::setFetchID(const LLUUID& id) -{ - mIDs.clear(); - mIDs.push_back(id); -} - - -void LLInventoryCompletionObserver::changed(U32 mask) -{ - // scan through the incomplete items and move or erase them as - // appropriate. - if (!mIncomplete.empty()) - { - for (uuid_vec_t::iterator it = mIncomplete.begin(); it < mIncomplete.end(); ) - { - const LLViewerInventoryItem* item = gInventory.getItem(*it); - if (!item) - { - it = mIncomplete.erase(it); - continue; - } - if (item->isFinished()) - { - mComplete.push_back(*it); - it = mIncomplete.erase(it); - continue; - } - ++it; - } - if (mIncomplete.empty()) - { - done(); - } - } -} - -void LLInventoryCompletionObserver::watchItem(const LLUUID& id) -{ - if (id.notNull()) - { - mIncomplete.push_back(id); - } -} - -LLInventoryFetchItemsObserver::LLInventoryFetchItemsObserver(const LLUUID& item_id) : - LLInventoryFetchObserver(item_id) -{ - mIDs.clear(); - mIDs.push_back(item_id); -} - -LLInventoryFetchItemsObserver::LLInventoryFetchItemsObserver(const uuid_vec_t& item_ids) : - LLInventoryFetchObserver(item_ids) -{ -} - -void LLInventoryFetchItemsObserver::changed(U32 mask) -{ - LL_DEBUGS("InventoryFetch") << this << " remaining incomplete " << mIncomplete.size() - << " complete " << mComplete.size() - << " wait period " << mFetchingPeriod.getRemainingTimeF32() - << LL_ENDL; - - // scan through the incomplete items and move or erase them as - // appropriate. - if (!mIncomplete.empty()) - { - if (!LLInventoryModelBackgroundFetch::getInstance()->isEverythingFetched()) - { - // Folders have a priority over items and they download items as well - // Wait untill initial folder fetch is done - LL_DEBUGS("InventoryFetch") << "Folder fetch in progress, resetting fetch timer" << LL_ENDL; - - mFetchingPeriod.reset(); - mFetchingPeriod.setTimerExpirySec(FETCH_TIMER_EXPIRY); - } - - // Have we exceeded max wait time? - bool timeout_expired = mFetchingPeriod.hasExpired(); - - for (uuid_vec_t::iterator it = mIncomplete.begin(); it < mIncomplete.end(); ) - { - const LLUUID& item_id = (*it); - LLViewerInventoryItem* item = gInventory.getItem(item_id); - if (item && item->isFinished()) - { - mComplete.push_back(item_id); - it = mIncomplete.erase(it); - } - else - { - if (timeout_expired) - { - // Just concede that this item hasn't arrived in reasonable time and continue on. - LL_WARNS("InventoryFetch") << "Fetcher timed out when fetching inventory item UUID: " << item_id << LL_ENDL; - it = mIncomplete.erase(it); - } - else - { - // Keep trying. - ++it; - } - } - } - - } - - if (mIncomplete.empty()) - { - LL_DEBUGS("InventoryFetch") << this << " done at remaining incomplete " - << mIncomplete.size() << " complete " << mComplete.size() << LL_ENDL; - done(); - } - //LL_INFOS() << "LLInventoryFetchItemsObserver::changed() mComplete size " << mComplete.size() << LL_ENDL; - //LL_INFOS() << "LLInventoryFetchItemsObserver::changed() mIncomplete size " << mIncomplete.size() << LL_ENDL; -} - -void fetch_items_from_llsd(const LLSD& items_llsd) -{ - if (!items_llsd.size() || gDisconnected) return; - - LLSD body; - body[0]["cap_name"] = "FetchInventory2"; - body[1]["cap_name"] = "FetchLib2"; - for (S32 i=0; igetCapability(body[i]["cap_name"].asString()); - if (!url.empty()) - { - body[i]["agent_id"] = gAgent.getID(); - LLCore::HttpHandler::ptr_t handler(new LLInventoryModel::FetchItemHttpHandler(body[i])); - gInventory.requestPost(true, url, body[i], handler, (i ? "Library Item" : "Inventory Item")); - continue; - } - else - { - LL_WARNS("INVENTORY") << "Failed to get capability." << LL_ENDL; - } - - } -} - -void LLInventoryFetchItemsObserver::startFetch() -{ - bool aisv3 = AISAPI::isAvailable(); - - LLSD items_llsd; - - typedef std::map requests_by_folders_t; - requests_by_folders_t requests; - for (uuid_vec_t::const_iterator it = mIDs.begin(); it < mIDs.end(); ++it) - { - LLViewerInventoryItem* item = gInventory.getItem(*it); - if (item && item->isFinished()) - { - // It's complete, so put it on the complete container. - mComplete.push_back(*it); - continue; - } - - // Ignore categories since they're not items. We - // could also just add this to mComplete but not sure what the - // side-effects would be, so ignoring to be safe. - LLViewerInventoryCategory* cat = gInventory.getCategory(*it); - if (cat) - { - continue; - } - - if ((*it).isNull()) - { - LL_WARNS("Inventory") << "Skip fetching for a NULL uuid" << LL_ENDL; - continue; - } - - // It's incomplete, so put it on the incomplete container, and - // pack this on the message. - mIncomplete.push_back(*it); - - if (aisv3) - { - if (item) - { - LLUUID parent_id = item->getParentUUID(); - requests[parent_id].push_back(*it); - } - else - { - // Can happen for gestures and calling cards if server notified us before they fetched - // Request by id without checking for an item. - LLInventoryModelBackgroundFetch::getInstance()->scheduleItemFetch(*it); - } - } - else - { - // Prepare the data to fetch - LLSD item_entry; - if (item) - { - item_entry["owner_id"] = item->getPermissions().getOwner(); - } - else - { - // assume it's agent inventory. - item_entry["owner_id"] = gAgent.getID(); - } - item_entry["item_id"] = (*it); - items_llsd.append(item_entry); - } - } - - mFetchingPeriod.reset(); - mFetchingPeriod.setTimerExpirySec(FETCH_TIMER_EXPIRY); - - if (aisv3) - { - for (requests_by_folders_t::value_type &folder : requests) - { - LLViewerInventoryCategory* cat = gInventory.getCategory(folder.first); - if (cat) - { - if (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) - { - // start fetching whole folder since it's not ready either way - cat->fetch(); - } - else if (folder.second.size() > MAX_INDIVIDUAL_ITEM_REQUESTS) - { - // requesting one by one will take a while - // do whole folder - LLInventoryModelBackgroundFetch::getInstance()->scheduleFolderFetch(folder.first, true); - } - else if (cat->getViewerDescendentCount() <= folder.second.size() - || cat->getDescendentCount() <= folder.second.size()) - { - // Start fetching whole folder since we need all items - LLInventoryModelBackgroundFetch::getInstance()->scheduleFolderFetch(folder.first, true); - } - else - { - // get items one by one - for (LLUUID& item_id : folder.second) - { - LLInventoryModelBackgroundFetch::getInstance()->scheduleItemFetch(item_id); - } - } - } - else - { - // Isn't supposed to happen? We should have all folders - // and if item exists, folder is supposed to exist as well. - llassert(false); - LL_WARNS("Inventory") << "Missing folder: " << folder.first << " fetching items individually" << LL_ENDL; - - // get items one by one - for (LLUUID& item_id : folder.second) - { - LLInventoryModelBackgroundFetch::getInstance()->scheduleItemFetch(item_id); - } - } - } - } - else - { - fetch_items_from_llsd(items_llsd); - } - -} - -LLInventoryFetchDescendentsObserver::LLInventoryFetchDescendentsObserver(const LLUUID& cat_id) : - LLInventoryFetchObserver(cat_id) -{ -} - -LLInventoryFetchDescendentsObserver::LLInventoryFetchDescendentsObserver(const uuid_vec_t& cat_ids) : - LLInventoryFetchObserver(cat_ids) -{ -} - -// virtual -void LLInventoryFetchDescendentsObserver::changed(U32 mask) -{ - for (uuid_vec_t::iterator it = mIncomplete.begin(); it < mIncomplete.end();) - { - const LLViewerInventoryCategory* cat = gInventory.getCategory(*it); - if (!cat) - { - it = mIncomplete.erase(it); - continue; - } - if (isCategoryComplete(cat)) - { - mComplete.push_back(*it); - it = mIncomplete.erase(it); - continue; - } - ++it; - } - - if (mIncomplete.empty()) - { - done(); - } - else - { - LLInventoryModelBackgroundFetch* fetcher = LLInventoryModelBackgroundFetch::getInstance(); - if (fetcher->isEverythingFetched() - && !fetcher->folderFetchActive()) - { - // If fetcher is done with folders yet we are waiting, fetch either - // failed or version is somehow stuck at -1 - done(); - } - } -} - -void LLInventoryFetchDescendentsObserver::startFetch() -{ - for (uuid_vec_t::const_iterator it = mIDs.begin(); it != mIDs.end(); ++it) - { - LLViewerInventoryCategory* cat = gInventory.getCategory(*it); - if (!cat) continue; - if (!isCategoryComplete(cat)) - { - //blindly fetch it without seeing if anything else is fetching it. - LLInventoryModelBackgroundFetch::getInstance()->scheduleFolderFetch(*it, true); - mIncomplete.push_back(*it); //Add to list of things being downloaded for this observer. - } - else - { - mComplete.push_back(*it); - } - } -} - -bool LLInventoryFetchDescendentsObserver::isCategoryComplete(const LLViewerInventoryCategory* cat) const -{ - const S32 version = cat->getVersion(); - const S32 expected_num_descendents = cat->getDescendentCount(); - if ((version == LLViewerInventoryCategory::VERSION_UNKNOWN) || - (expected_num_descendents == LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN)) - { - return false; - } - // it might be complete - check known descendents against - // currently available. - LLInventoryModel::cat_array_t* cats; - LLInventoryModel::item_array_t* items; - gInventory.getDirectDescendentsOf(cat->getUUID(), cats, items); - if (!cats || !items) - { - LL_WARNS() << "Category '" << cat->getName() << "' descendents corrupted, fetch failed." << LL_ENDL; - // NULL means the call failed -- cats/items map doesn't exist (note: this does NOT mean - // that the cat just doesn't have any items or subfolders). - // Unrecoverable, so just return done so that this observer can be cleared - // from memory. - return true; - } - const S32 current_num_known_descendents = cats->size() + items->size(); - - // Got the number of descendents that we were expecting, so we're done. - if (current_num_known_descendents == expected_num_descendents) - { - return true; - } - - // Error condition, but recoverable. This happens if something was added to the - // category before it was initialized, so accountForUpdate didn't update descendent - // count and thus the category thinks it has fewer descendents than it actually has. - if (current_num_known_descendents >= expected_num_descendents) - { - LL_WARNS() << "Category '" << cat->getName() << "' expected descendentcount:" << expected_num_descendents << " descendents but got descendentcount:" << current_num_known_descendents << LL_ENDL; - const_cast(cat)->setDescendentCount(current_num_known_descendents); - return true; - } - return false; -} - -LLInventoryFetchComboObserver::LLInventoryFetchComboObserver(const uuid_vec_t& folder_ids, - const uuid_vec_t& item_ids) -{ - mFetchDescendents = new LLInventoryFetchDescendentsObserver(folder_ids); - - uuid_vec_t pruned_item_ids; - for (uuid_vec_t::const_iterator item_iter = item_ids.begin(); - item_iter != item_ids.end(); - ++item_iter) - { - const LLUUID& item_id = (*item_iter); - const LLViewerInventoryItem* item = gInventory.getItem(item_id); - if (item && std::find(folder_ids.begin(), folder_ids.end(), item->getParentUUID()) == folder_ids.end()) - { - continue; - } - pruned_item_ids.push_back(item_id); - } - - mFetchItems = new LLInventoryFetchItemsObserver(pruned_item_ids); - mFetchDescendents = new LLInventoryFetchDescendentsObserver(folder_ids); -} - -LLInventoryFetchComboObserver::~LLInventoryFetchComboObserver() -{ - mFetchItems->done(); - mFetchDescendents->done(); - delete mFetchItems; - delete mFetchDescendents; -} - -void LLInventoryFetchComboObserver::changed(U32 mask) -{ - mFetchItems->changed(mask); - mFetchDescendents->changed(mask); - if (mFetchItems->isFinished() && mFetchDescendents->isFinished()) - { - done(); - } -} - -void LLInventoryFetchComboObserver::startFetch() -{ - mFetchItems->startFetch(); - mFetchDescendents->startFetch(); -} - -// See comment preceding LLInventoryAddedObserver::changed() for some -// concerns that also apply to this observer. -void LLInventoryAddItemByAssetObserver::changed(U32 mask) -{ - if(!(mask & LLInventoryObserver::ADD) || - !(mask & LLInventoryObserver::CREATE) || - !(mask & LLInventoryObserver::UPDATE_CREATE)) - { - return; - } - - // nothing is watched - if (mWatchedAssets.size() == 0) - { - return; - } - - const uuid_set_t& added = gInventory.getAddedIDs(); - for (uuid_set_t::iterator it = added.begin(); it != added.end(); ++it) - { - LLInventoryItem *item = gInventory.getItem(*it); - if (!item) - { - continue; - } - const LLUUID& asset_uuid = item->getAssetUUID(); - if (item->getUUID().notNull() && asset_uuid.notNull()) - { - if (isAssetWatched(asset_uuid)) - { - LL_DEBUGS("Inventory_Move") << "Found asset UUID: " << asset_uuid << LL_ENDL; - mAddedItems.push_back(item->getUUID()); - } - } - } - - if (mAddedItems.size() == mWatchedAssets.size()) - { - LL_DEBUGS("Inventory_Move") << "All watched items are added & processed." << LL_ENDL; - done(); - mAddedItems.clear(); - - // Unable to clean watched items here due to somebody can require to check them in current frame. - // set dirty state to clean them while next watch cycle. - mIsDirty = true; - } -} - -void LLInventoryAddItemByAssetObserver::watchAsset(const LLUUID& asset_id) -{ - if(asset_id.notNull()) - { - if (mIsDirty) - { - LL_DEBUGS("Inventory_Move") << "Watched items are dirty. Clean them." << LL_ENDL; - mWatchedAssets.clear(); - mIsDirty = false; - } - - mWatchedAssets.push_back(asset_id); - onAssetAdded(asset_id); - } -} - -bool LLInventoryAddItemByAssetObserver::isAssetWatched( const LLUUID& asset_id ) -{ - return std::find(mWatchedAssets.begin(), mWatchedAssets.end(), asset_id) != mWatchedAssets.end(); -} - -// This observer used to explicitly check for whether it was being -// called as a result of an UpdateCreateInventoryItem message. It has -// now been decoupled enough that it's not actually checking the -// message system, but now we have the special UPDATE_CREATE flag -// being used for the same purpose. Fixing this, as we would need to -// do to get rid of the message, is somewhat subtle because there's no -// particular obvious criterion for when creating a new item should -// trigger this observer and when it shouldn't. For example, creating -// a new notecard with new->notecard causes a preview window to pop up -// via the derived class LLOpenTaskOffer, but creating a new notecard -// by copy and paste does not, solely because one goes through -// UpdateCreateInventoryItem and the other doesn't. -void LLInventoryAddedObserver::changed(U32 mask) -{ - if (!(mask & LLInventoryObserver::ADD) || - !(mask & LLInventoryObserver::CREATE) || - !(mask & LLInventoryObserver::UPDATE_CREATE)) - { - return; - } - - if (!gInventory.getAddedIDs().empty()) - { - done(); - } -} - -void LLInventoryCategoryAddedObserver::changed(U32 mask) -{ - if (!(mask & LLInventoryObserver::ADD)) - { - return; - } - - const LLInventoryModel::changed_items_t& added_ids = gInventory.getAddedIDs(); - - for (LLInventoryModel::changed_items_t::const_iterator cit = added_ids.begin(); cit != added_ids.end(); ++cit) - { - LLViewerInventoryCategory* cat = gInventory.getCategory(*cit); - - if (cat) - { - mAddedCategories.push_back(cat); - } - } - - if (!mAddedCategories.empty()) - { - done(); - - mAddedCategories.clear(); - } -} - -void LLInventoryCategoriesObserver::changed(U32 mask) -{ - if (!mCategoryMap.size()) - return; - - std::vector deleted_categories_ids; - - for (category_map_t::iterator iter = mCategoryMap.begin(); - iter != mCategoryMap.end(); - ++iter) - { - const LLUUID& cat_id = (*iter).first; - LLCategoryData& cat_data = (*iter).second; - - LLViewerInventoryCategory* category = gInventory.getCategory(cat_id); - if (!category) - { - LL_WARNS() << "Category : Category id = " << cat_id << " disappeared" << LL_ENDL; - cat_data.mCallback(); - // Keep track of those deleted categories so we can remove them - deleted_categories_ids.push_back(cat_id); - continue; - } - - const S32 version = category->getVersion(); - const S32 expected_num_descendents = category->getDescendentCount(); - if ((version == LLViewerInventoryCategory::VERSION_UNKNOWN) || - (expected_num_descendents == LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN)) - { - continue; - } - - // Check number of known descendents to find out whether it has changed. - LLInventoryModel::cat_array_t* cats; - LLInventoryModel::item_array_t* items; - gInventory.getDirectDescendentsOf(cat_id, cats, items); - if (!cats || !items) - { - LL_WARNS() << "Category '" << category->getName() << "' descendents corrupted, fetch failed." << LL_ENDL; - // NULL means the call failed -- cats/items map doesn't exist (note: this does NOT mean - // that the cat just doesn't have any items or subfolders). - // Unrecoverable, so just skip this category. - - llassert(cats != NULL && items != NULL); - - continue; - } - - const S32 current_num_known_descendents = cats->size() + items->size(); - - bool cat_changed = false; - - // If category version or descendents count has changed - // update category data in mCategoryMap - if (version != cat_data.mVersion || current_num_known_descendents != cat_data.mDescendentsCount) - { - cat_data.mVersion = version; - cat_data.mDescendentsCount = current_num_known_descendents; - cat_changed = true; - } - - // If any item names have changed, update the name hash - // Only need to check if (a) name hash has not previously been - // computed, or (b) a name has changed. - if (!cat_data.mIsNameHashInitialized || (mask & LLInventoryObserver::LABEL)) - { - digest_t item_name_hash = gInventory.hashDirectDescendentNames(cat_id); - if (cat_data.mItemNameHash != item_name_hash) - { - cat_data.mIsNameHashInitialized = true; - cat_data.mItemNameHash = item_name_hash; - cat_changed = true; - } - } - - const LLUUID thumbnail_id = category->getThumbnailUUID(); - if (cat_data.mThumbnailId != thumbnail_id) - { - cat_data.mThumbnailId = thumbnail_id; - cat_changed = true; - } - - // If anything has changed above, fire the callback. - if (cat_changed) - cat_data.mCallback(); - } - - // Remove deleted categories from the list - for (std::vector::iterator deleted_id = deleted_categories_ids.begin(); deleted_id != deleted_categories_ids.end(); ++deleted_id) - { - removeCategory(*deleted_id); - } -} - -bool LLInventoryCategoriesObserver::addCategory(const LLUUID& cat_id, callback_t cb, bool init_name_hash) -{ - S32 version = LLViewerInventoryCategory::VERSION_UNKNOWN; - S32 current_num_known_descendents = LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN; - bool can_be_added = true; - LLUUID thumbnail_id; - - LLViewerInventoryCategory* category = gInventory.getCategory(cat_id); - // If category could not be retrieved it might mean that - // inventory is unusable at the moment so the category is - // stored with VERSION_UNKNOWN and DESCENDENT_COUNT_UNKNOWN, - // it may be updated later. - if (category) - { - // Inventory category version is used to find out if some changes - // to a category have been made. - version = category->getVersion(); - thumbnail_id = category->getThumbnailUUID(); - - LLInventoryModel::cat_array_t* cats; - LLInventoryModel::item_array_t* items; - gInventory.getDirectDescendentsOf(cat_id, cats, items); - if (!cats || !items) - { - LL_WARNS() << "Category '" << category->getName() << "' descendents corrupted, fetch failed." << LL_ENDL; - // NULL means the call failed -- cats/items map doesn't exist (note: this does NOT mean - // that the cat just doesn't have any items or subfolders). - // Unrecoverable, so just return "false" meaning that the category can't be observed. - can_be_added = false; - - llassert(cats != NULL && items != NULL); - } - else - { - current_num_known_descendents = cats->size() + items->size(); - } - } - - if (can_be_added) - { - if(init_name_hash) - { - digest_t item_name_hash = gInventory.hashDirectDescendentNames(cat_id); - mCategoryMap.insert(category_map_value_t(cat_id,LLCategoryData(cat_id, thumbnail_id, cb, version, current_num_known_descendents,item_name_hash))); - } - else - { - mCategoryMap.insert(category_map_value_t(cat_id,LLCategoryData(cat_id, thumbnail_id, cb, version, current_num_known_descendents))); - } - } - - return can_be_added; -} - -void LLInventoryCategoriesObserver::removeCategory(const LLUUID& cat_id) -{ - mCategoryMap.erase(cat_id); -} - -LLInventoryCategoriesObserver::LLCategoryData::LLCategoryData( - const LLUUID& cat_id, const LLUUID& thumbnail_id, callback_t cb, S32 version, S32 num_descendents) - - : mCatID(cat_id) - , mCallback(cb) - , mVersion(version) - , mDescendentsCount(num_descendents) - , mThumbnailId(thumbnail_id) - , mIsNameHashInitialized(false) -{ -} - -LLInventoryCategoriesObserver::LLCategoryData::LLCategoryData( - const LLUUID& cat_id, const LLUUID& thumbnail_id, callback_t cb, S32 version, S32 num_descendents, const digest_t& name_hash) - - : mCatID(cat_id) - , mCallback(cb) - , mVersion(version) - , mDescendentsCount(num_descendents) - , mThumbnailId(thumbnail_id) - , mIsNameHashInitialized(true) - , mItemNameHash(name_hash) -{ -} - -void LLScrollOnRenameObserver::changed(U32 mask) -{ - if (mask & LLInventoryObserver::LABEL) - { - const uuid_set_t& changed_item_ids = gInventory.getChangedIDs(); - for (uuid_set_t::const_iterator it = changed_item_ids.begin(); it != changed_item_ids.end(); ++it) - { - const LLUUID& id = *it; - if (id == mUUID) - { - mView->scrollToShowSelection(); - - gInventory.removeObserver(this); - delete this; - return; - } - } - } -} +/** + * @file llinventoryobserver.cpp + * @brief Implementation of the inventory observers used to track agent inventory. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llinventoryobserver.h" + +#include "llassetstorage.h" +#include "llcrc.h" +#include "lldir.h" +#include "llsys.h" +#include "llxfermanager.h" +#include "message.h" + +#include "llagent.h" +#include "llagentwearables.h" +#include "llaisapi.h" +#include "llfloater.h" +#include "llfocusmgr.h" +#include "llinventorymodelbackgroundfetch.h" +#include "llinventorybridge.h" +#include "llinventoryfunctions.h" +#include "llinventorymodel.h" +#include "llviewermessage.h" +#include "llviewerwindow.h" +#include "llviewerregion.h" +#include "llappviewer.h" +#include "lldbstrings.h" +#include "llviewerstats.h" +#include "llnotificationsutil.h" +#include "llcallbacklist.h" +#include "llpreview.h" +#include "llviewercontrol.h" +#include "llvoavatarself.h" +#include "llsdutil.h" +#include + +const S32 LLInventoryFetchItemsObserver::MAX_INDIVIDUAL_ITEM_REQUESTS = 7; +const F32 LLInventoryFetchItemsObserver::FETCH_TIMER_EXPIRY = 60.0f; + + +LLInventoryObserver::LLInventoryObserver() +{ +} + +// virtual +LLInventoryObserver::~LLInventoryObserver() +{ +} + +LLInventoryFetchObserver::LLInventoryFetchObserver(const LLUUID& id) +{ + mIDs.clear(); + if (id != LLUUID::null) + { + setFetchID(id); + } +} + +LLInventoryFetchObserver::LLInventoryFetchObserver(const uuid_vec_t& ids) +{ + setFetchIDs(ids); +} + +bool LLInventoryFetchObserver::isFinished() const +{ + return mIncomplete.empty(); +} + +void LLInventoryFetchObserver::setFetchIDs(const uuid_vec_t& ids) +{ + mIDs = ids; +} +void LLInventoryFetchObserver::setFetchID(const LLUUID& id) +{ + mIDs.clear(); + mIDs.push_back(id); +} + + +void LLInventoryCompletionObserver::changed(U32 mask) +{ + // scan through the incomplete items and move or erase them as + // appropriate. + if (!mIncomplete.empty()) + { + for (uuid_vec_t::iterator it = mIncomplete.begin(); it < mIncomplete.end(); ) + { + const LLViewerInventoryItem* item = gInventory.getItem(*it); + if (!item) + { + it = mIncomplete.erase(it); + continue; + } + if (item->isFinished()) + { + mComplete.push_back(*it); + it = mIncomplete.erase(it); + continue; + } + ++it; + } + if (mIncomplete.empty()) + { + done(); + } + } +} + +void LLInventoryCompletionObserver::watchItem(const LLUUID& id) +{ + if (id.notNull()) + { + mIncomplete.push_back(id); + } +} + +LLInventoryFetchItemsObserver::LLInventoryFetchItemsObserver(const LLUUID& item_id) : + LLInventoryFetchObserver(item_id) +{ + mIDs.clear(); + mIDs.push_back(item_id); +} + +LLInventoryFetchItemsObserver::LLInventoryFetchItemsObserver(const uuid_vec_t& item_ids) : + LLInventoryFetchObserver(item_ids) +{ +} + +void LLInventoryFetchItemsObserver::changed(U32 mask) +{ + LL_DEBUGS("InventoryFetch") << this << " remaining incomplete " << mIncomplete.size() + << " complete " << mComplete.size() + << " wait period " << mFetchingPeriod.getRemainingTimeF32() + << LL_ENDL; + + // scan through the incomplete items and move or erase them as + // appropriate. + if (!mIncomplete.empty()) + { + if (!LLInventoryModelBackgroundFetch::getInstance()->isEverythingFetched()) + { + // Folders have a priority over items and they download items as well + // Wait untill initial folder fetch is done + LL_DEBUGS("InventoryFetch") << "Folder fetch in progress, resetting fetch timer" << LL_ENDL; + + mFetchingPeriod.reset(); + mFetchingPeriod.setTimerExpirySec(FETCH_TIMER_EXPIRY); + } + + // Have we exceeded max wait time? + bool timeout_expired = mFetchingPeriod.hasExpired(); + + for (uuid_vec_t::iterator it = mIncomplete.begin(); it < mIncomplete.end(); ) + { + const LLUUID& item_id = (*it); + LLViewerInventoryItem* item = gInventory.getItem(item_id); + if (item && item->isFinished()) + { + mComplete.push_back(item_id); + it = mIncomplete.erase(it); + } + else + { + if (timeout_expired) + { + // Just concede that this item hasn't arrived in reasonable time and continue on. + LL_WARNS("InventoryFetch") << "Fetcher timed out when fetching inventory item UUID: " << item_id << LL_ENDL; + it = mIncomplete.erase(it); + } + else + { + // Keep trying. + ++it; + } + } + } + + } + + if (mIncomplete.empty()) + { + LL_DEBUGS("InventoryFetch") << this << " done at remaining incomplete " + << mIncomplete.size() << " complete " << mComplete.size() << LL_ENDL; + done(); + } + //LL_INFOS() << "LLInventoryFetchItemsObserver::changed() mComplete size " << mComplete.size() << LL_ENDL; + //LL_INFOS() << "LLInventoryFetchItemsObserver::changed() mIncomplete size " << mIncomplete.size() << LL_ENDL; +} + +void fetch_items_from_llsd(const LLSD& items_llsd) +{ + if (!items_llsd.size() || gDisconnected) return; + + LLSD body; + body[0]["cap_name"] = "FetchInventory2"; + body[1]["cap_name"] = "FetchLib2"; + for (S32 i=0; igetCapability(body[i]["cap_name"].asString()); + if (!url.empty()) + { + body[i]["agent_id"] = gAgent.getID(); + LLCore::HttpHandler::ptr_t handler(new LLInventoryModel::FetchItemHttpHandler(body[i])); + gInventory.requestPost(true, url, body[i], handler, (i ? "Library Item" : "Inventory Item")); + continue; + } + else + { + LL_WARNS("INVENTORY") << "Failed to get capability." << LL_ENDL; + } + + } +} + +void LLInventoryFetchItemsObserver::startFetch() +{ + bool aisv3 = AISAPI::isAvailable(); + + LLSD items_llsd; + + typedef std::map requests_by_folders_t; + requests_by_folders_t requests; + for (uuid_vec_t::const_iterator it = mIDs.begin(); it < mIDs.end(); ++it) + { + LLViewerInventoryItem* item = gInventory.getItem(*it); + if (item && item->isFinished()) + { + // It's complete, so put it on the complete container. + mComplete.push_back(*it); + continue; + } + + // Ignore categories since they're not items. We + // could also just add this to mComplete but not sure what the + // side-effects would be, so ignoring to be safe. + LLViewerInventoryCategory* cat = gInventory.getCategory(*it); + if (cat) + { + continue; + } + + if ((*it).isNull()) + { + LL_WARNS("Inventory") << "Skip fetching for a NULL uuid" << LL_ENDL; + continue; + } + + // It's incomplete, so put it on the incomplete container, and + // pack this on the message. + mIncomplete.push_back(*it); + + if (aisv3) + { + if (item) + { + LLUUID parent_id = item->getParentUUID(); + requests[parent_id].push_back(*it); + } + else + { + // Can happen for gestures and calling cards if server notified us before they fetched + // Request by id without checking for an item. + LLInventoryModelBackgroundFetch::getInstance()->scheduleItemFetch(*it); + } + } + else + { + // Prepare the data to fetch + LLSD item_entry; + if (item) + { + item_entry["owner_id"] = item->getPermissions().getOwner(); + } + else + { + // assume it's agent inventory. + item_entry["owner_id"] = gAgent.getID(); + } + item_entry["item_id"] = (*it); + items_llsd.append(item_entry); + } + } + + mFetchingPeriod.reset(); + mFetchingPeriod.setTimerExpirySec(FETCH_TIMER_EXPIRY); + + if (aisv3) + { + for (requests_by_folders_t::value_type &folder : requests) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(folder.first); + if (cat) + { + if (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) + { + // start fetching whole folder since it's not ready either way + cat->fetch(); + } + else if (folder.second.size() > MAX_INDIVIDUAL_ITEM_REQUESTS) + { + // requesting one by one will take a while + // do whole folder + LLInventoryModelBackgroundFetch::getInstance()->scheduleFolderFetch(folder.first, true); + } + else if (cat->getViewerDescendentCount() <= folder.second.size() + || cat->getDescendentCount() <= folder.second.size()) + { + // Start fetching whole folder since we need all items + LLInventoryModelBackgroundFetch::getInstance()->scheduleFolderFetch(folder.first, true); + } + else + { + // get items one by one + for (LLUUID& item_id : folder.second) + { + LLInventoryModelBackgroundFetch::getInstance()->scheduleItemFetch(item_id); + } + } + } + else + { + // Isn't supposed to happen? We should have all folders + // and if item exists, folder is supposed to exist as well. + llassert(false); + LL_WARNS("Inventory") << "Missing folder: " << folder.first << " fetching items individually" << LL_ENDL; + + // get items one by one + for (LLUUID& item_id : folder.second) + { + LLInventoryModelBackgroundFetch::getInstance()->scheduleItemFetch(item_id); + } + } + } + } + else + { + fetch_items_from_llsd(items_llsd); + } + +} + +LLInventoryFetchDescendentsObserver::LLInventoryFetchDescendentsObserver(const LLUUID& cat_id) : + LLInventoryFetchObserver(cat_id) +{ +} + +LLInventoryFetchDescendentsObserver::LLInventoryFetchDescendentsObserver(const uuid_vec_t& cat_ids) : + LLInventoryFetchObserver(cat_ids) +{ +} + +// virtual +void LLInventoryFetchDescendentsObserver::changed(U32 mask) +{ + for (uuid_vec_t::iterator it = mIncomplete.begin(); it < mIncomplete.end();) + { + const LLViewerInventoryCategory* cat = gInventory.getCategory(*it); + if (!cat) + { + it = mIncomplete.erase(it); + continue; + } + if (isCategoryComplete(cat)) + { + mComplete.push_back(*it); + it = mIncomplete.erase(it); + continue; + } + ++it; + } + + if (mIncomplete.empty()) + { + done(); + } + else + { + LLInventoryModelBackgroundFetch* fetcher = LLInventoryModelBackgroundFetch::getInstance(); + if (fetcher->isEverythingFetched() + && !fetcher->folderFetchActive()) + { + // If fetcher is done with folders yet we are waiting, fetch either + // failed or version is somehow stuck at -1 + done(); + } + } +} + +void LLInventoryFetchDescendentsObserver::startFetch() +{ + for (uuid_vec_t::const_iterator it = mIDs.begin(); it != mIDs.end(); ++it) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(*it); + if (!cat) continue; + if (!isCategoryComplete(cat)) + { + //blindly fetch it without seeing if anything else is fetching it. + LLInventoryModelBackgroundFetch::getInstance()->scheduleFolderFetch(*it, true); + mIncomplete.push_back(*it); //Add to list of things being downloaded for this observer. + } + else + { + mComplete.push_back(*it); + } + } +} + +bool LLInventoryFetchDescendentsObserver::isCategoryComplete(const LLViewerInventoryCategory* cat) const +{ + const S32 version = cat->getVersion(); + const S32 expected_num_descendents = cat->getDescendentCount(); + if ((version == LLViewerInventoryCategory::VERSION_UNKNOWN) || + (expected_num_descendents == LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN)) + { + return false; + } + // it might be complete - check known descendents against + // currently available. + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(cat->getUUID(), cats, items); + if (!cats || !items) + { + LL_WARNS() << "Category '" << cat->getName() << "' descendents corrupted, fetch failed." << LL_ENDL; + // NULL means the call failed -- cats/items map doesn't exist (note: this does NOT mean + // that the cat just doesn't have any items or subfolders). + // Unrecoverable, so just return done so that this observer can be cleared + // from memory. + return true; + } + const S32 current_num_known_descendents = cats->size() + items->size(); + + // Got the number of descendents that we were expecting, so we're done. + if (current_num_known_descendents == expected_num_descendents) + { + return true; + } + + // Error condition, but recoverable. This happens if something was added to the + // category before it was initialized, so accountForUpdate didn't update descendent + // count and thus the category thinks it has fewer descendents than it actually has. + if (current_num_known_descendents >= expected_num_descendents) + { + LL_WARNS() << "Category '" << cat->getName() << "' expected descendentcount:" << expected_num_descendents << " descendents but got descendentcount:" << current_num_known_descendents << LL_ENDL; + const_cast(cat)->setDescendentCount(current_num_known_descendents); + return true; + } + return false; +} + +LLInventoryFetchComboObserver::LLInventoryFetchComboObserver(const uuid_vec_t& folder_ids, + const uuid_vec_t& item_ids) +{ + mFetchDescendents = new LLInventoryFetchDescendentsObserver(folder_ids); + + uuid_vec_t pruned_item_ids; + for (uuid_vec_t::const_iterator item_iter = item_ids.begin(); + item_iter != item_ids.end(); + ++item_iter) + { + const LLUUID& item_id = (*item_iter); + const LLViewerInventoryItem* item = gInventory.getItem(item_id); + if (item && std::find(folder_ids.begin(), folder_ids.end(), item->getParentUUID()) == folder_ids.end()) + { + continue; + } + pruned_item_ids.push_back(item_id); + } + + mFetchItems = new LLInventoryFetchItemsObserver(pruned_item_ids); + mFetchDescendents = new LLInventoryFetchDescendentsObserver(folder_ids); +} + +LLInventoryFetchComboObserver::~LLInventoryFetchComboObserver() +{ + mFetchItems->done(); + mFetchDescendents->done(); + delete mFetchItems; + delete mFetchDescendents; +} + +void LLInventoryFetchComboObserver::changed(U32 mask) +{ + mFetchItems->changed(mask); + mFetchDescendents->changed(mask); + if (mFetchItems->isFinished() && mFetchDescendents->isFinished()) + { + done(); + } +} + +void LLInventoryFetchComboObserver::startFetch() +{ + mFetchItems->startFetch(); + mFetchDescendents->startFetch(); +} + +// See comment preceding LLInventoryAddedObserver::changed() for some +// concerns that also apply to this observer. +void LLInventoryAddItemByAssetObserver::changed(U32 mask) +{ + if(!(mask & LLInventoryObserver::ADD) || + !(mask & LLInventoryObserver::CREATE) || + !(mask & LLInventoryObserver::UPDATE_CREATE)) + { + return; + } + + // nothing is watched + if (mWatchedAssets.size() == 0) + { + return; + } + + const uuid_set_t& added = gInventory.getAddedIDs(); + for (uuid_set_t::iterator it = added.begin(); it != added.end(); ++it) + { + LLInventoryItem *item = gInventory.getItem(*it); + if (!item) + { + continue; + } + const LLUUID& asset_uuid = item->getAssetUUID(); + if (item->getUUID().notNull() && asset_uuid.notNull()) + { + if (isAssetWatched(asset_uuid)) + { + LL_DEBUGS("Inventory_Move") << "Found asset UUID: " << asset_uuid << LL_ENDL; + mAddedItems.push_back(item->getUUID()); + } + } + } + + if (mAddedItems.size() == mWatchedAssets.size()) + { + LL_DEBUGS("Inventory_Move") << "All watched items are added & processed." << LL_ENDL; + done(); + mAddedItems.clear(); + + // Unable to clean watched items here due to somebody can require to check them in current frame. + // set dirty state to clean them while next watch cycle. + mIsDirty = true; + } +} + +void LLInventoryAddItemByAssetObserver::watchAsset(const LLUUID& asset_id) +{ + if(asset_id.notNull()) + { + if (mIsDirty) + { + LL_DEBUGS("Inventory_Move") << "Watched items are dirty. Clean them." << LL_ENDL; + mWatchedAssets.clear(); + mIsDirty = false; + } + + mWatchedAssets.push_back(asset_id); + onAssetAdded(asset_id); + } +} + +bool LLInventoryAddItemByAssetObserver::isAssetWatched( const LLUUID& asset_id ) +{ + return std::find(mWatchedAssets.begin(), mWatchedAssets.end(), asset_id) != mWatchedAssets.end(); +} + +// This observer used to explicitly check for whether it was being +// called as a result of an UpdateCreateInventoryItem message. It has +// now been decoupled enough that it's not actually checking the +// message system, but now we have the special UPDATE_CREATE flag +// being used for the same purpose. Fixing this, as we would need to +// do to get rid of the message, is somewhat subtle because there's no +// particular obvious criterion for when creating a new item should +// trigger this observer and when it shouldn't. For example, creating +// a new notecard with new->notecard causes a preview window to pop up +// via the derived class LLOpenTaskOffer, but creating a new notecard +// by copy and paste does not, solely because one goes through +// UpdateCreateInventoryItem and the other doesn't. +void LLInventoryAddedObserver::changed(U32 mask) +{ + if (!(mask & LLInventoryObserver::ADD) || + !(mask & LLInventoryObserver::CREATE) || + !(mask & LLInventoryObserver::UPDATE_CREATE)) + { + return; + } + + if (!gInventory.getAddedIDs().empty()) + { + done(); + } +} + +void LLInventoryCategoryAddedObserver::changed(U32 mask) +{ + if (!(mask & LLInventoryObserver::ADD)) + { + return; + } + + const LLInventoryModel::changed_items_t& added_ids = gInventory.getAddedIDs(); + + for (LLInventoryModel::changed_items_t::const_iterator cit = added_ids.begin(); cit != added_ids.end(); ++cit) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(*cit); + + if (cat) + { + mAddedCategories.push_back(cat); + } + } + + if (!mAddedCategories.empty()) + { + done(); + + mAddedCategories.clear(); + } +} + +void LLInventoryCategoriesObserver::changed(U32 mask) +{ + if (!mCategoryMap.size()) + return; + + std::vector deleted_categories_ids; + + for (category_map_t::iterator iter = mCategoryMap.begin(); + iter != mCategoryMap.end(); + ++iter) + { + const LLUUID& cat_id = (*iter).first; + LLCategoryData& cat_data = (*iter).second; + + LLViewerInventoryCategory* category = gInventory.getCategory(cat_id); + if (!category) + { + LL_WARNS() << "Category : Category id = " << cat_id << " disappeared" << LL_ENDL; + cat_data.mCallback(); + // Keep track of those deleted categories so we can remove them + deleted_categories_ids.push_back(cat_id); + continue; + } + + const S32 version = category->getVersion(); + const S32 expected_num_descendents = category->getDescendentCount(); + if ((version == LLViewerInventoryCategory::VERSION_UNKNOWN) || + (expected_num_descendents == LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN)) + { + continue; + } + + // Check number of known descendents to find out whether it has changed. + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(cat_id, cats, items); + if (!cats || !items) + { + LL_WARNS() << "Category '" << category->getName() << "' descendents corrupted, fetch failed." << LL_ENDL; + // NULL means the call failed -- cats/items map doesn't exist (note: this does NOT mean + // that the cat just doesn't have any items or subfolders). + // Unrecoverable, so just skip this category. + + llassert(cats != NULL && items != NULL); + + continue; + } + + const S32 current_num_known_descendents = cats->size() + items->size(); + + bool cat_changed = false; + + // If category version or descendents count has changed + // update category data in mCategoryMap + if (version != cat_data.mVersion || current_num_known_descendents != cat_data.mDescendentsCount) + { + cat_data.mVersion = version; + cat_data.mDescendentsCount = current_num_known_descendents; + cat_changed = true; + } + + // If any item names have changed, update the name hash + // Only need to check if (a) name hash has not previously been + // computed, or (b) a name has changed. + if (!cat_data.mIsNameHashInitialized || (mask & LLInventoryObserver::LABEL)) + { + digest_t item_name_hash = gInventory.hashDirectDescendentNames(cat_id); + if (cat_data.mItemNameHash != item_name_hash) + { + cat_data.mIsNameHashInitialized = true; + cat_data.mItemNameHash = item_name_hash; + cat_changed = true; + } + } + + const LLUUID thumbnail_id = category->getThumbnailUUID(); + if (cat_data.mThumbnailId != thumbnail_id) + { + cat_data.mThumbnailId = thumbnail_id; + cat_changed = true; + } + + // If anything has changed above, fire the callback. + if (cat_changed) + cat_data.mCallback(); + } + + // Remove deleted categories from the list + for (std::vector::iterator deleted_id = deleted_categories_ids.begin(); deleted_id != deleted_categories_ids.end(); ++deleted_id) + { + removeCategory(*deleted_id); + } +} + +bool LLInventoryCategoriesObserver::addCategory(const LLUUID& cat_id, callback_t cb, bool init_name_hash) +{ + S32 version = LLViewerInventoryCategory::VERSION_UNKNOWN; + S32 current_num_known_descendents = LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN; + bool can_be_added = true; + LLUUID thumbnail_id; + + LLViewerInventoryCategory* category = gInventory.getCategory(cat_id); + // If category could not be retrieved it might mean that + // inventory is unusable at the moment so the category is + // stored with VERSION_UNKNOWN and DESCENDENT_COUNT_UNKNOWN, + // it may be updated later. + if (category) + { + // Inventory category version is used to find out if some changes + // to a category have been made. + version = category->getVersion(); + thumbnail_id = category->getThumbnailUUID(); + + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(cat_id, cats, items); + if (!cats || !items) + { + LL_WARNS() << "Category '" << category->getName() << "' descendents corrupted, fetch failed." << LL_ENDL; + // NULL means the call failed -- cats/items map doesn't exist (note: this does NOT mean + // that the cat just doesn't have any items or subfolders). + // Unrecoverable, so just return "false" meaning that the category can't be observed. + can_be_added = false; + + llassert(cats != NULL && items != NULL); + } + else + { + current_num_known_descendents = cats->size() + items->size(); + } + } + + if (can_be_added) + { + if(init_name_hash) + { + digest_t item_name_hash = gInventory.hashDirectDescendentNames(cat_id); + mCategoryMap.insert(category_map_value_t(cat_id,LLCategoryData(cat_id, thumbnail_id, cb, version, current_num_known_descendents,item_name_hash))); + } + else + { + mCategoryMap.insert(category_map_value_t(cat_id,LLCategoryData(cat_id, thumbnail_id, cb, version, current_num_known_descendents))); + } + } + + return can_be_added; +} + +void LLInventoryCategoriesObserver::removeCategory(const LLUUID& cat_id) +{ + mCategoryMap.erase(cat_id); +} + +LLInventoryCategoriesObserver::LLCategoryData::LLCategoryData( + const LLUUID& cat_id, const LLUUID& thumbnail_id, callback_t cb, S32 version, S32 num_descendents) + + : mCatID(cat_id) + , mCallback(cb) + , mVersion(version) + , mDescendentsCount(num_descendents) + , mThumbnailId(thumbnail_id) + , mIsNameHashInitialized(false) +{ +} + +LLInventoryCategoriesObserver::LLCategoryData::LLCategoryData( + const LLUUID& cat_id, const LLUUID& thumbnail_id, callback_t cb, S32 version, S32 num_descendents, const digest_t& name_hash) + + : mCatID(cat_id) + , mCallback(cb) + , mVersion(version) + , mDescendentsCount(num_descendents) + , mThumbnailId(thumbnail_id) + , mIsNameHashInitialized(true) + , mItemNameHash(name_hash) +{ +} + +void LLScrollOnRenameObserver::changed(U32 mask) +{ + if (mask & LLInventoryObserver::LABEL) + { + const uuid_set_t& changed_item_ids = gInventory.getChangedIDs(); + for (uuid_set_t::const_iterator it = changed_item_ids.begin(); it != changed_item_ids.end(); ++it) + { + const LLUUID& id = *it; + if (id == mUUID) + { + mView->scrollToShowSelection(); + + gInventory.removeObserver(this); + delete this; + return; + } + } + } +} diff --git a/indra/newview/llinventoryobserver.h b/indra/newview/llinventoryobserver.h index 0be0522594..950b02d3cf 100644 --- a/indra/newview/llinventoryobserver.h +++ b/indra/newview/llinventoryobserver.h @@ -1,314 +1,314 @@ -/** - * @file llinventoryobserver.h - * @brief LLInventoryObserver class header file - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLINVENTORYOBSERVERS_H -#define LL_LLINVENTORYOBSERVERS_H - -#include "lluuid.h" -#include -#include - -class LLViewerInventoryCategory; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLInventoryObserver -// -// A simple abstract base class that can relay messages when the inventory -// changes. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLInventoryObserver -{ -public: - // This enumeration is a way to refer to what changed in a more - // human readable format. You can mask the value provided by - // chaged() to see if the observer is interested in the change. - enum - { - NONE = 0, - LABEL = 1, // Name changed - INTERNAL = 2, // Internal change (e.g. asset uuid different) - ADD = 4, // Something added - REMOVE = 8, // Something deleted - STRUCTURE = 16, // Structural change (e.g. item or folder moved) - CALLING_CARD = 32, // Calling card change (e.g. online, grant status, cancel) - GESTURE = 64, - REBUILD = 128, // Item UI changed (e.g. item type different) - SORT = 256, // Folder needs to be resorted. - CREATE = 512, // With ADD, item has just been created. - // unfortunately a particular message is still associated with some unique semantics. - UPDATE_CREATE = 1024, // With ADD, item added via UpdateCreateInventoryItem - ALL = 0xffffffff - }; - LLInventoryObserver(); - virtual ~LLInventoryObserver(); - virtual void changed(U32 mask) = 0; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLInventoryFetchObserver -// -// Abstract class to handle fetching items, folders, etc. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLInventoryFetchObserver : public LLInventoryObserver -{ -public: - LLInventoryFetchObserver(const LLUUID& id = LLUUID::null); // single item - LLInventoryFetchObserver(const uuid_vec_t& ids); // multiple items - void setFetchID(const LLUUID& id); - void setFetchIDs(const uuid_vec_t& ids); - - bool isFinished() const; - - virtual void startFetch() = 0; - virtual void changed(U32 mask) = 0; - virtual void done() {}; -protected: - uuid_vec_t mComplete; - uuid_vec_t mIncomplete; - uuid_vec_t mIDs; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLInventoryFetchItemsObserver -// -// Fetches inventory items, calls done() when all inventory has arrived. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLInventoryFetchItemsObserver : public LLInventoryFetchObserver -{ -public: - LLInventoryFetchItemsObserver(const LLUUID& item_id = LLUUID::null); - LLInventoryFetchItemsObserver(const uuid_vec_t& item_ids); - - /*virtual*/ void startFetch(); - /*virtual*/ void changed(U32 mask); - - // For attempts to group requests if too many items are requested - static const S32 MAX_INDIVIDUAL_ITEM_REQUESTS; -private: - LLTimer mFetchingPeriod; - - /** - * Period of waiting a notification when requested items get added into inventory. - */ - static const F32 FETCH_TIMER_EXPIRY; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLInventoryFetchDescendentsObserver -// -// Fetches children of a category/folder, calls done() when all -// inventory has arrived. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLInventoryFetchDescendentsObserver : public LLInventoryFetchObserver -{ -public: - LLInventoryFetchDescendentsObserver(const LLUUID& cat_id = LLUUID::null); - LLInventoryFetchDescendentsObserver(const uuid_vec_t& cat_ids); - - virtual void startFetch(); - /*virtual*/ void changed(U32 mask); -protected: - bool isCategoryComplete(const LLViewerInventoryCategory* cat) const; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLInventoryFetchComboObserver -// -// Does an appropriate combination of fetch descendents and -// item fetches based on completion of categories and items. This is optimized -// to not fetch item_ids that are descendents of any of the folder_ids. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLInventoryFetchComboObserver : public LLInventoryObserver -{ -public: - LLInventoryFetchComboObserver(const uuid_vec_t& folder_ids, - const uuid_vec_t& item_ids); - ~LLInventoryFetchComboObserver(); - /*virtual*/ void changed(U32 mask); - void startFetch(); - - virtual void done() = 0; -protected: - LLInventoryFetchItemsObserver *mFetchItems; - LLInventoryFetchDescendentsObserver *mFetchDescendents; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLInventoryMovedObserver -// -// This class is used as a base class for doing something when all the -// item for observed asset ids were added into the inventory. -// Derive a class from this class and implement the done() method to do -// something useful. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLInventoryAddItemByAssetObserver : public LLInventoryObserver -{ -public: - LLInventoryAddItemByAssetObserver() : mIsDirty(false) {} - virtual void changed(U32 mask); - - void watchAsset(const LLUUID& asset_id); - bool isAssetWatched(const LLUUID& asset_id); - -protected: - virtual void onAssetAdded(const LLUUID& asset_id) {} - virtual void done() = 0; - - typedef std::vector item_ref_t; - item_ref_t mAddedItems; - item_ref_t mWatchedAssets; - -private: - bool mIsDirty; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLInventoryAddedObserver -// -// Base class for doing something when a new item arrives in inventory. -// It does not watch for a certain UUID, rather it acts when anything is added -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLInventoryAddedObserver : public LLInventoryObserver -{ -public: - LLInventoryAddedObserver() {} - /*virtual*/ void changed(U32 mask); - -protected: - virtual void done() = 0; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLInventoryCategoryAddedObserver -// -// Base class for doing something when a new category is created in the -// inventory. -// It does not watch for a certain UUID, rather it acts when anything is added -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLInventoryCategoryAddedObserver : public LLInventoryObserver -{ -public: - - typedef std::vector cat_vec_t; - - LLInventoryCategoryAddedObserver() : mAddedCategories() {} - /*virtual*/ void changed(U32 mask); - -protected: - virtual void done() = 0; - - cat_vec_t mAddedCategories; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLInventoryCompletionObserver -// -// Base class for doing something when when all observed items are locally -// complete. Implements the changed() method of LLInventoryObserver -// and declares a new method named done() which is called when all watched items -// have complete information in the inventory model. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLInventoryCompletionObserver : public LLInventoryObserver -{ -public: - LLInventoryCompletionObserver() {} - /*virtual*/ void changed(U32 mask); - - void watchItem(const LLUUID& id); - -protected: - virtual void done() = 0; - - uuid_vec_t mComplete; - uuid_vec_t mIncomplete; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLInventoryCategoriesObserver -// -// This class is used for monitoring a list of inventory categories -// and firing a callback when there are changes in any of them. -// Categories are identified by their UUIDs. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLInventoryCategoriesObserver : public LLInventoryObserver -{ -public: - typedef boost::function callback_t; - - LLInventoryCategoriesObserver() {}; - virtual void changed(U32 mask); - - /** - * Add cat_id to the list of observed categories with a - * callback fired on category being changed. - * - * @return "true" if category was added, "false" if it could - * not be found. - */ - bool addCategory(const LLUUID& cat_id, callback_t cb, bool init_name_hash = false); - void removeCategory(const LLUUID& cat_id); - -protected: - typedef LLUUID digest_t; // To clarify the actual usage of this "UUID" - struct LLCategoryData - { - LLCategoryData(const LLUUID& cat_id, const LLUUID& thumbnail_id, callback_t cb, S32 version, S32 num_descendents); - LLCategoryData(const LLUUID& cat_id, const LLUUID& thumbnail_id, callback_t cb, S32 version, S32 num_descendents, const digest_t& name_hash); - callback_t mCallback; - S32 mVersion; - S32 mDescendentsCount; - digest_t mItemNameHash; - bool mIsNameHashInitialized; - LLUUID mCatID; - LLUUID mThumbnailId; - }; - - typedef std::map category_map_t; - typedef category_map_t::value_type category_map_value_t; - - category_map_t mCategoryMap; -}; - -class LLFolderView; - -// Force a FolderView to scroll after an item in the corresponding view has been renamed. -class LLScrollOnRenameObserver: public LLInventoryObserver -{ -public: - LLFolderView *mView; - LLUUID mUUID; - - LLScrollOnRenameObserver(const LLUUID& uuid, LLFolderView *view): - mUUID(uuid), - mView(view) - { - } - /* virtual */ void changed(U32 mask); -}; - - -#endif // LL_LLINVENTORYOBSERVERS_H +/** + * @file llinventoryobserver.h + * @brief LLInventoryObserver class header file + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLINVENTORYOBSERVERS_H +#define LL_LLINVENTORYOBSERVERS_H + +#include "lluuid.h" +#include +#include + +class LLViewerInventoryCategory; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLInventoryObserver +// +// A simple abstract base class that can relay messages when the inventory +// changes. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLInventoryObserver +{ +public: + // This enumeration is a way to refer to what changed in a more + // human readable format. You can mask the value provided by + // chaged() to see if the observer is interested in the change. + enum + { + NONE = 0, + LABEL = 1, // Name changed + INTERNAL = 2, // Internal change (e.g. asset uuid different) + ADD = 4, // Something added + REMOVE = 8, // Something deleted + STRUCTURE = 16, // Structural change (e.g. item or folder moved) + CALLING_CARD = 32, // Calling card change (e.g. online, grant status, cancel) + GESTURE = 64, + REBUILD = 128, // Item UI changed (e.g. item type different) + SORT = 256, // Folder needs to be resorted. + CREATE = 512, // With ADD, item has just been created. + // unfortunately a particular message is still associated with some unique semantics. + UPDATE_CREATE = 1024, // With ADD, item added via UpdateCreateInventoryItem + ALL = 0xffffffff + }; + LLInventoryObserver(); + virtual ~LLInventoryObserver(); + virtual void changed(U32 mask) = 0; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLInventoryFetchObserver +// +// Abstract class to handle fetching items, folders, etc. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLInventoryFetchObserver : public LLInventoryObserver +{ +public: + LLInventoryFetchObserver(const LLUUID& id = LLUUID::null); // single item + LLInventoryFetchObserver(const uuid_vec_t& ids); // multiple items + void setFetchID(const LLUUID& id); + void setFetchIDs(const uuid_vec_t& ids); + + bool isFinished() const; + + virtual void startFetch() = 0; + virtual void changed(U32 mask) = 0; + virtual void done() {}; +protected: + uuid_vec_t mComplete; + uuid_vec_t mIncomplete; + uuid_vec_t mIDs; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLInventoryFetchItemsObserver +// +// Fetches inventory items, calls done() when all inventory has arrived. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLInventoryFetchItemsObserver : public LLInventoryFetchObserver +{ +public: + LLInventoryFetchItemsObserver(const LLUUID& item_id = LLUUID::null); + LLInventoryFetchItemsObserver(const uuid_vec_t& item_ids); + + /*virtual*/ void startFetch(); + /*virtual*/ void changed(U32 mask); + + // For attempts to group requests if too many items are requested + static const S32 MAX_INDIVIDUAL_ITEM_REQUESTS; +private: + LLTimer mFetchingPeriod; + + /** + * Period of waiting a notification when requested items get added into inventory. + */ + static const F32 FETCH_TIMER_EXPIRY; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLInventoryFetchDescendentsObserver +// +// Fetches children of a category/folder, calls done() when all +// inventory has arrived. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLInventoryFetchDescendentsObserver : public LLInventoryFetchObserver +{ +public: + LLInventoryFetchDescendentsObserver(const LLUUID& cat_id = LLUUID::null); + LLInventoryFetchDescendentsObserver(const uuid_vec_t& cat_ids); + + virtual void startFetch(); + /*virtual*/ void changed(U32 mask); +protected: + bool isCategoryComplete(const LLViewerInventoryCategory* cat) const; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLInventoryFetchComboObserver +// +// Does an appropriate combination of fetch descendents and +// item fetches based on completion of categories and items. This is optimized +// to not fetch item_ids that are descendents of any of the folder_ids. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLInventoryFetchComboObserver : public LLInventoryObserver +{ +public: + LLInventoryFetchComboObserver(const uuid_vec_t& folder_ids, + const uuid_vec_t& item_ids); + ~LLInventoryFetchComboObserver(); + /*virtual*/ void changed(U32 mask); + void startFetch(); + + virtual void done() = 0; +protected: + LLInventoryFetchItemsObserver *mFetchItems; + LLInventoryFetchDescendentsObserver *mFetchDescendents; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLInventoryMovedObserver +// +// This class is used as a base class for doing something when all the +// item for observed asset ids were added into the inventory. +// Derive a class from this class and implement the done() method to do +// something useful. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLInventoryAddItemByAssetObserver : public LLInventoryObserver +{ +public: + LLInventoryAddItemByAssetObserver() : mIsDirty(false) {} + virtual void changed(U32 mask); + + void watchAsset(const LLUUID& asset_id); + bool isAssetWatched(const LLUUID& asset_id); + +protected: + virtual void onAssetAdded(const LLUUID& asset_id) {} + virtual void done() = 0; + + typedef std::vector item_ref_t; + item_ref_t mAddedItems; + item_ref_t mWatchedAssets; + +private: + bool mIsDirty; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLInventoryAddedObserver +// +// Base class for doing something when a new item arrives in inventory. +// It does not watch for a certain UUID, rather it acts when anything is added +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLInventoryAddedObserver : public LLInventoryObserver +{ +public: + LLInventoryAddedObserver() {} + /*virtual*/ void changed(U32 mask); + +protected: + virtual void done() = 0; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLInventoryCategoryAddedObserver +// +// Base class for doing something when a new category is created in the +// inventory. +// It does not watch for a certain UUID, rather it acts when anything is added +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLInventoryCategoryAddedObserver : public LLInventoryObserver +{ +public: + + typedef std::vector cat_vec_t; + + LLInventoryCategoryAddedObserver() : mAddedCategories() {} + /*virtual*/ void changed(U32 mask); + +protected: + virtual void done() = 0; + + cat_vec_t mAddedCategories; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLInventoryCompletionObserver +// +// Base class for doing something when when all observed items are locally +// complete. Implements the changed() method of LLInventoryObserver +// and declares a new method named done() which is called when all watched items +// have complete information in the inventory model. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLInventoryCompletionObserver : public LLInventoryObserver +{ +public: + LLInventoryCompletionObserver() {} + /*virtual*/ void changed(U32 mask); + + void watchItem(const LLUUID& id); + +protected: + virtual void done() = 0; + + uuid_vec_t mComplete; + uuid_vec_t mIncomplete; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLInventoryCategoriesObserver +// +// This class is used for monitoring a list of inventory categories +// and firing a callback when there are changes in any of them. +// Categories are identified by their UUIDs. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLInventoryCategoriesObserver : public LLInventoryObserver +{ +public: + typedef boost::function callback_t; + + LLInventoryCategoriesObserver() {}; + virtual void changed(U32 mask); + + /** + * Add cat_id to the list of observed categories with a + * callback fired on category being changed. + * + * @return "true" if category was added, "false" if it could + * not be found. + */ + bool addCategory(const LLUUID& cat_id, callback_t cb, bool init_name_hash = false); + void removeCategory(const LLUUID& cat_id); + +protected: + typedef LLUUID digest_t; // To clarify the actual usage of this "UUID" + struct LLCategoryData + { + LLCategoryData(const LLUUID& cat_id, const LLUUID& thumbnail_id, callback_t cb, S32 version, S32 num_descendents); + LLCategoryData(const LLUUID& cat_id, const LLUUID& thumbnail_id, callback_t cb, S32 version, S32 num_descendents, const digest_t& name_hash); + callback_t mCallback; + S32 mVersion; + S32 mDescendentsCount; + digest_t mItemNameHash; + bool mIsNameHashInitialized; + LLUUID mCatID; + LLUUID mThumbnailId; + }; + + typedef std::map category_map_t; + typedef category_map_t::value_type category_map_value_t; + + category_map_t mCategoryMap; +}; + +class LLFolderView; + +// Force a FolderView to scroll after an item in the corresponding view has been renamed. +class LLScrollOnRenameObserver: public LLInventoryObserver +{ +public: + LLFolderView *mView; + LLUUID mUUID; + + LLScrollOnRenameObserver(const LLUUID& uuid, LLFolderView *view): + mUUID(uuid), + mView(view) + { + } + /* virtual */ void changed(U32 mask); +}; + + +#endif // LL_LLINVENTORYOBSERVERS_H diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp index d2c9dffc44..d338e308ab 100644 --- a/indra/newview/llinventorypanel.cpp +++ b/indra/newview/llinventorypanel.cpp @@ -1,2589 +1,2589 @@ -/* - * @file llinventorypanel.cpp - * @brief Implementation of the inventory panel and associated stuff. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llinventorypanel.h" - -#include // for std::pair<> - -#include "llagent.h" -#include "llagentwearables.h" -#include "llappearancemgr.h" -#include "llavataractions.h" -#include "llclipboard.h" -#include "llfloaterreg.h" -#include "llfloatersidepanelcontainer.h" -#include "llfolderview.h" -#include "llfolderviewitem.h" -#include "llfloaterimcontainer.h" -#include "llimview.h" -#include "llinspecttexture.h" -#include "llinventorybridge.h" -#include "llinventoryfunctions.h" -#include "llinventorymodelbackgroundfetch.h" -#include "llnotificationsutil.h" -#include "llpanelmaininventory.h" -#include "llpreview.h" -#include "llsidepanelinventory.h" -#include "llstartup.h" -#include "lltrans.h" -#include "llviewerassettype.h" -#include "llviewerattachmenu.h" -#include "llviewerfoldertype.h" -#include "llvoavatarself.h" - -class LLInventoryRecentItemsPanel; -class LLAssetFilteredInventoryPanel; - -static LLDefaultChildRegistry::Register r("inventory_panel"); -static LLDefaultChildRegistry::Register t_recent_inventory_panel("recent_inventory_panel"); -static LLDefaultChildRegistry::Register t_asset_filtered_inv_panel("asset_filtered_inv_panel"); - -const std::string LLInventoryPanel::DEFAULT_SORT_ORDER = std::string("InventorySortOrder"); -const std::string LLInventoryPanel::RECENTITEMS_SORT_ORDER = std::string("RecentItemsSortOrder"); -const std::string LLInventoryPanel::INHERIT_SORT_ORDER = std::string(""); -static const LLInventoryFolderViewModelBuilder INVENTORY_BRIDGE_BUILDER; - -// statics -bool LLInventoryPanel::sColorSetInitialized = false; -LLUIColor LLInventoryPanel::sDefaultColor; -LLUIColor LLInventoryPanel::sDefaultHighlightColor; -LLUIColor LLInventoryPanel::sLibraryColor; -LLUIColor LLInventoryPanel::sLinkColor; - -const LLColor4U DEFAULT_WHITE(255, 255, 255); - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLInventoryPanelObserver -// -// Bridge to support knowing when the inventory has changed. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLInventoryPanelObserver : public LLInventoryObserver -{ -public: - LLInventoryPanelObserver(LLInventoryPanel* ip) : mIP(ip) {} - virtual ~LLInventoryPanelObserver() {} - virtual void changed(U32 mask) - { - mIP->modelChanged(mask); - } -protected: - LLInventoryPanel* mIP; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLInvPanelComplObserver -// -// Calls specified callback when all specified items become complete. -// -// Usage: -// observer = new LLInvPanelComplObserver(boost::bind(onComplete)); -// inventory->addObserver(observer); -// observer->reset(); // (optional) -// observer->watchItem(incomplete_item1_id); -// observer->watchItem(incomplete_item2_id); -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLInvPanelComplObserver : public LLInventoryCompletionObserver -{ -public: - typedef boost::function callback_t; - - LLInvPanelComplObserver(callback_t cb) - : mCallback(cb) - { - } - - void reset(); - -private: - /*virtual*/ void done(); - - /// Called when all the items are complete. - callback_t mCallback; -}; - -void LLInvPanelComplObserver::reset() -{ - mIncomplete.clear(); - mComplete.clear(); -} - -void LLInvPanelComplObserver::done() -{ - mCallback(); -} - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLInventoryPanel -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -LLInventoryPanel::LLInventoryPanel(const LLInventoryPanel::Params& p) : - LLPanel(p), - mInventoryObserver(NULL), - mCompletionObserver(NULL), - mScroller(NULL), - mSortOrderSetting(p.sort_order_setting), - mInventory(p.inventory), //inventory("", &gInventory) - mAcceptsDragAndDrop(p.accepts_drag_and_drop), - mAllowMultiSelect(p.allow_multi_select), - mAllowDrag(p.allow_drag), - mShowItemLinkOverlays(p.show_item_link_overlays), - mShowEmptyMessage(p.show_empty_message), - mSuppressFolderMenu(p.suppress_folder_menu), - mSuppressOpenItemAction(false), - mBuildViewsOnInit(p.preinitialize_views), - mViewsInitialized(VIEWS_UNINITIALIZED), - mInvFVBridgeBuilder(NULL), - mInventoryViewModel(p.name), - mGroupedItemBridge(new LLFolderViewGroupedItemBridge), - mFocusSelection(false), - mBuildChildrenViews(true), - mRootInited(false) -{ - mInvFVBridgeBuilder = &INVENTORY_BRIDGE_BUILDER; - - if (!sColorSetInitialized) - { - sDefaultColor = LLUIColorTable::instance().getColor("InventoryItemColor", DEFAULT_WHITE); - sDefaultHighlightColor = LLUIColorTable::instance().getColor("MenuItemHighlightFgColor", DEFAULT_WHITE); - sLibraryColor = LLUIColorTable::instance().getColor("InventoryItemLibraryColor", DEFAULT_WHITE); - sLinkColor = LLUIColorTable::instance().getColor("InventoryItemLinkColor", DEFAULT_WHITE); - sColorSetInitialized = true; - } - - // context menu callbacks - mCommitCallbackRegistrar.add("Inventory.DoToSelected", boost::bind(&LLInventoryPanel::doToSelected, this, _2)); - mCommitCallbackRegistrar.add("Inventory.EmptyTrash", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyTrash", LLFolderType::FT_TRASH)); - mCommitCallbackRegistrar.add("Inventory.EmptyLostAndFound", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyLostAndFound", LLFolderType::FT_LOST_AND_FOUND)); - mCommitCallbackRegistrar.add("Inventory.DoCreate", boost::bind(&LLInventoryPanel::doCreate, this, _2)); - mCommitCallbackRegistrar.add("Inventory.AttachObject", boost::bind(&LLInventoryPanel::attachObject, this, _2)); - mCommitCallbackRegistrar.add("Inventory.BeginIMSession", boost::bind(&LLInventoryPanel::beginIMSession, this)); - mCommitCallbackRegistrar.add("Inventory.Share", boost::bind(&LLAvatarActions::shareWithAvatars, this)); - mCommitCallbackRegistrar.add("Inventory.FileUploadLocation", boost::bind(&LLInventoryPanel::fileUploadLocation, this, _2)); - mCommitCallbackRegistrar.add("Inventory.OpenNewFolderWindow", boost::bind(&LLInventoryPanel::openSingleViewInventory, this, LLUUID())); -} - -LLFolderView * LLInventoryPanel::createFolderRoot(LLUUID root_id ) -{ - LLFolderView::Params p(mParams.folder_view); - p.name = getName(); - p.title = getLabel(); - p.rect = LLRect(0, 0, getRect().getWidth(), 0); - p.parent_panel = this; - p.tool_tip = p.name; - p.listener = mInvFVBridgeBuilder->createBridge( LLAssetType::AT_CATEGORY, - LLAssetType::AT_CATEGORY, - LLInventoryType::IT_CATEGORY, - this, - &mInventoryViewModel, - NULL, - root_id); - p.view_model = &mInventoryViewModel; - p.grouped_item_model = mGroupedItemBridge; - p.use_label_suffix = mParams.use_label_suffix; - p.allow_multiselect = mAllowMultiSelect; - p.allow_drag = mAllowDrag; - p.show_empty_message = mShowEmptyMessage; - p.suppress_folder_menu = mSuppressFolderMenu; - p.show_item_link_overlays = mShowItemLinkOverlays; - p.root = NULL; - p.allow_drop = mParams.allow_drop_on_root; - p.options_menu = "menu_inventory.xml"; - - LLFolderView* fv = LLUICtrlFactory::create(p); - fv->setCallbackRegistrar(&mCommitCallbackRegistrar); - fv->setEnableRegistrar(&mEnableCallbackRegistrar); - - return fv; -} - -void LLInventoryPanel::clearFolderRoot() -{ - gIdleCallbacks.deleteFunction(idle, this); - gIdleCallbacks.deleteFunction(onIdle, this); - - if (mInventoryObserver) - { - mInventory->removeObserver(mInventoryObserver); - delete mInventoryObserver; - mInventoryObserver = NULL; - } - if (mCompletionObserver) - { - mInventory->removeObserver(mCompletionObserver); - delete mCompletionObserver; - mCompletionObserver = NULL; - } - - if (mScroller) - { - removeChild(mScroller); - delete mScroller; - mScroller = NULL; - } -} - -void LLInventoryPanel::initFromParams(const LLInventoryPanel::Params& params) -{ - // save off copy of params - mParams = params; - - initFolderRoot(); - - // Initialize base class params. - LLPanel::initFromParams(mParams); -} - -LLInventoryPanel::~LLInventoryPanel() -{ - U32 sort_order = getFolderViewModel()->getSorter().getSortOrder(); - if (mSortOrderSetting != INHERIT_SORT_ORDER) - { - gSavedSettings.setU32(mSortOrderSetting, sort_order); - } - - clearFolderRoot(); -} - -void LLInventoryPanel::initFolderRoot() -{ - // Clear up the root view - // Note: This needs to be done *before* we build the new folder view - LLUUID root_id = getRootFolderID(); - if (mFolderRoot.get()) - { - removeItemID(root_id); - mFolderRoot.get()->destroyView(); - } - - mCommitCallbackRegistrar.pushScope(); // registered as a widget; need to push callback scope ourselves - { - // Determine the root folder in case specified, and - // build the views starting with that folder. - LLFolderView* folder_view = createFolderRoot(root_id); - mFolderRoot = folder_view->getHandle(); - mRootInited = true; - - addItemID(root_id, mFolderRoot.get()); - } - mCommitCallbackRegistrar.popScope(); - mFolderRoot.get()->setCallbackRegistrar(&mCommitCallbackRegistrar); - mFolderRoot.get()->setEnableRegistrar(&mEnableCallbackRegistrar); - - // Scroller - LLRect scroller_view_rect = getRect(); - scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom); - LLScrollContainer::Params scroller_params(mParams.scroll()); - scroller_params.rect(scroller_view_rect); - mScroller = LLUICtrlFactory::create(scroller_params); - addChild(mScroller); - mScroller->addChild(mFolderRoot.get()); - mFolderRoot.get()->setScrollContainer(mScroller); - mFolderRoot.get()->setFollowsAll(); - mFolderRoot.get()->addChild(mFolderRoot.get()->mStatusTextBox); - - if (mSelectionCallback) - { - mFolderRoot.get()->setSelectCallback(mSelectionCallback); - } - - // Set up the callbacks from the inventory we're viewing, and then build everything. - mInventoryObserver = new LLInventoryPanelObserver(this); - mInventory->addObserver(mInventoryObserver); - - mCompletionObserver = new LLInvPanelComplObserver(boost::bind(&LLInventoryPanel::onItemsCompletion, this)); - mInventory->addObserver(mCompletionObserver); - - if (mBuildViewsOnInit) - { - initializeViewBuilding(); - } - - if (mSortOrderSetting != INHERIT_SORT_ORDER) - { - setSortOrder(gSavedSettings.getU32(mSortOrderSetting)); - } - else - { - setSortOrder(gSavedSettings.getU32(DEFAULT_SORT_ORDER)); - } - - // hide inbox - if (!gSavedSettings.getBOOL("InventoryOutboxMakeVisible")) - { - getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << LLFolderType::FT_INBOX)); - } - // hide marketplace listing box, unless we are a marketplace panel - if (!gSavedSettings.getBOOL("InventoryOutboxMakeVisible") && !mParams.use_marketplace_folders) - { - getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << LLFolderType::FT_MARKETPLACE_LISTINGS)); - } - - // set the filter for the empty folder if the debug setting is on - if (gSavedSettings.getBOOL("DebugHideEmptySystemFolders")) - { - getFilter().setFilterEmptySystemFolders(); - } - - // keep track of the clipboard state so that we avoid filtering too much - mClipboardState = LLClipboard::instance().getGeneration(); -} - -void LLInventoryPanel::initializeViewBuilding() -{ - if (mViewsInitialized == VIEWS_UNINITIALIZED) - { - LL_DEBUGS("Inventory") << "Setting views for " << getName() << " to initialize" << LL_ENDL; - // Build view of inventory if we need default full hierarchy and inventory is ready, otherwise do in onIdle. - // Initializing views takes a while so always do it onIdle if viewer already loaded. - if (mInventory->isInventoryUsable() - && LLStartUp::getStartupState() <= STATE_WEARABLES_WAIT) - { - // Usually this happens on login, so we have less time constraits, but too long and we can cause a disconnect - const F64 max_time = 20.f; - initializeViews(max_time); - } - else - { - mViewsInitialized = VIEWS_INITIALIZING; - gIdleCallbacks.addFunction(onIdle, (void*)this); - } - } -} - -/*virtual*/ -void LLInventoryPanel::onVisibilityChange(bool new_visibility) -{ - if (new_visibility && mViewsInitialized == VIEWS_UNINITIALIZED) - { - // first call can be from tab initialization - if (gFloaterView->getParentFloater(this) != NULL) - { - initializeViewBuilding(); - } - } - LLPanel::onVisibilityChange(new_visibility); -} - -void LLInventoryPanel::draw() -{ - // Select the desired item (in case it wasn't loaded when the selection was requested) - updateSelection(); - - LLPanel::draw(); -} - -const LLInventoryFilter& LLInventoryPanel::getFilter() const -{ - return getFolderViewModel()->getFilter(); -} - -LLInventoryFilter& LLInventoryPanel::getFilter() -{ - return getFolderViewModel()->getFilter(); -} - -void LLInventoryPanel::setFilterTypes(U64 types, LLInventoryFilter::EFilterType filter_type) -{ - if (filter_type == LLInventoryFilter::FILTERTYPE_OBJECT) - { - getFilter().setFilterObjectTypes(types); - } - if (filter_type == LLInventoryFilter::FILTERTYPE_CATEGORY) - getFilter().setFilterCategoryTypes(types); -} - -void LLInventoryPanel::setFilterWorn() -{ - getFilter().setFilterWorn(); -} - -U32 LLInventoryPanel::getFilterObjectTypes() const -{ - return getFilter().getFilterObjectTypes(); -} - -U32 LLInventoryPanel::getFilterPermMask() const -{ - return getFilter().getFilterPermissions(); -} - - -void LLInventoryPanel::setFilterPermMask(PermissionMask filter_perm_mask) -{ - getFilter().setFilterPermissions(filter_perm_mask); -} - -void LLInventoryPanel::setFilterWearableTypes(U64 types) -{ - getFilter().setFilterWearableTypes(types); -} - -void LLInventoryPanel::setFilterSettingsTypes(U64 filter) -{ - getFilter().setFilterSettingsTypes(filter); -} - -void LLInventoryPanel::setFilterSubString(const std::string& string) -{ - getFilter().setFilterSubString(string); -} - -const std::string LLInventoryPanel::getFilterSubString() -{ - return getFilter().getFilterSubString(); -} - -void LLInventoryPanel::setSortOrder(U32 order) -{ - LLInventorySort sorter(order); - if (order != getFolderViewModel()->getSorter().getSortOrder()) - { - getFolderViewModel()->setSorter(sorter); - mFolderRoot.get()->arrangeAll(); - // try to keep selection onscreen, even if it wasn't to start with - mFolderRoot.get()->scrollToShowSelection(); - } -} - -U32 LLInventoryPanel::getSortOrder() const -{ - return getFolderViewModel()->getSorter().getSortOrder(); -} - -void LLInventoryPanel::setSinceLogoff(bool sl) -{ - getFilter().setDateRangeLastLogoff(sl); -} - -void LLInventoryPanel::setHoursAgo(U32 hours) -{ - getFilter().setHoursAgo(hours); -} - -void LLInventoryPanel::setDateSearchDirection(U32 direction) -{ - getFilter().setDateSearchDirection(direction); -} - -void LLInventoryPanel::setFilterLinks(U64 filter_links) -{ - getFilter().setFilterLinks(filter_links); -} - -void LLInventoryPanel::setSearchType(LLInventoryFilter::ESearchType type) -{ - getFilter().setSearchType(type); -} - -LLInventoryFilter::ESearchType LLInventoryPanel::getSearchType() -{ - return getFilter().getSearchType(); -} - -void LLInventoryPanel::setShowFolderState(LLInventoryFilter::EFolderShow show) -{ - getFilter().setShowFolderState(show); -} - -LLInventoryFilter::EFolderShow LLInventoryPanel::getShowFolderState() -{ - return getFilter().getShowFolderState(); -} - -void LLInventoryPanel::itemChanged(const LLUUID& item_id, U32 mask, const LLInventoryObject* model_item) -{ - LLFolderViewItem* view_item = getItemByID(item_id); - LLFolderViewModelItemInventory* viewmodel_item = - static_cast(view_item ? view_item->getViewModelItem() : NULL); - - // LLFolderViewFolder is derived from LLFolderViewItem so dynamic_cast from item - // to folder is the fast way to get a folder without searching through folders tree. - LLFolderViewFolder* view_folder = NULL; - - // Check requires as this item might have already been deleted - // as a child of its deleted parent. - if (model_item && view_item) - { - view_folder = dynamic_cast(view_item); - } - - // if folder is not fully initialized (likely due to delayed load on idle) - // and we are not rebuilding, try updating children - if (view_folder - && !view_folder->areChildrenInited() - && ( (mask & LLInventoryObserver::REBUILD) == 0)) - { - LLInventoryObject const* objectp = mInventory->getObject(item_id); - if (objectp) - { - view_item = buildNewViews(item_id, objectp, view_item, BUILD_ONE_FOLDER); - } - } - - ////////////////////////////// - // LABEL Operation - // Empty out the display name for relabel. - if (mask & LLInventoryObserver::LABEL) - { - if (view_item) - { - // Request refresh on this item (also flags for filtering) - LLInvFVBridge* bridge = (LLInvFVBridge*)view_item->getViewModelItem(); - if(bridge) - { - // Clear the display name first, so it gets properly re-built during refresh() - bridge->clearDisplayName(); - - view_item->refresh(); - } - LLFolderViewFolder* parent = view_item->getParentFolder(); - if(parent) - { - parent->getViewModelItem()->dirtyDescendantsFilter(); - } - } - } - - ////////////////////////////// - // REBUILD Operation - // Destroy and regenerate the UI. - if (mask & LLInventoryObserver::REBUILD) - { - if (model_item && view_item && viewmodel_item) - { - const LLUUID& idp = viewmodel_item->getUUID(); - view_item->destroyView(); - removeItemID(idp); - } - - LLInventoryObject const* objectp = mInventory->getObject(item_id); - if (objectp) - { - // providing NULL directly avoids unnessesary getItemByID calls - view_item = buildNewViews(item_id, objectp, NULL, BUILD_ONE_FOLDER); - } - else - { - view_item = NULL; - } - - viewmodel_item = - static_cast(view_item ? view_item->getViewModelItem() : NULL); - view_folder = dynamic_cast(view_item); - } - - ////////////////////////////// - // INTERNAL Operation - // This could be anything. For now, just refresh the item. - if (mask & LLInventoryObserver::INTERNAL) - { - if (view_item) - { - view_item->refresh(); - } - } - - ////////////////////////////// - // SORT Operation - // Sort the folder. - if (mask & LLInventoryObserver::SORT) - { - if (view_folder) - { - view_folder->getViewModelItem()->requestSort(); - } - } - - // We don't typically care which of these masks the item is actually flagged with, since the masks - // may not be accurate (e.g. in the main inventory panel, I move an item from My Inventory into - // Landmarks; this is a STRUCTURE change for that panel but is an ADD change for the Landmarks - // panel). What's relevant is that the item and UI are probably out of sync and thus need to be - // resynchronized. - if (mask & (LLInventoryObserver::STRUCTURE | - LLInventoryObserver::ADD | - LLInventoryObserver::REMOVE)) - { - ////////////////////////////// - // ADD Operation - // Item exists in memory but a UI element hasn't been created for it. - if (model_item && !view_item) - { - // Add the UI element for this item. - LLInventoryObject const* objectp = mInventory->getObject(item_id); - if (objectp) - { - // providing NULL directly avoids unnessesary getItemByID calls - buildNewViews(item_id, objectp, NULL, BUILD_ONE_FOLDER); - } - - // Select any newly created object that has the auto rename at top of folder root set. - if(mFolderRoot.get()->getRoot()->needsAutoRename()) - { - setSelection(item_id, false); - } - updateFolderLabel(model_item->getParentUUID()); - } - - ////////////////////////////// - // STRUCTURE Operation - // This item already exists in both memory and UI. It was probably reparented. - else if (model_item && view_item) - { - LLFolderViewFolder* old_parent = view_item->getParentFolder(); - // Don't process the item if it is the root - if (old_parent) - { - LLFolderViewModelItemInventory* viewmodel_folder = static_cast(old_parent->getViewModelItem()); - LLFolderViewFolder* new_parent = (LLFolderViewFolder*)getItemByID(model_item->getParentUUID()); - // Item has been moved. - if (old_parent != new_parent) - { - if (new_parent != NULL) - { - // Item is to be moved and we found its new parent in the panel's directory, so move the item's UI. - view_item->addToFolder(new_parent); - addItemID(viewmodel_item->getUUID(), view_item); - if (mInventory) - { - const LLUUID trash_id = mInventory->findCategoryUUIDForType(LLFolderType::FT_TRASH); - if (trash_id != model_item->getParentUUID() && (mask & LLInventoryObserver::INTERNAL) && new_parent->isOpen()) - { - setSelection(item_id, false); - } - } - updateFolderLabel(model_item->getParentUUID()); - } - else - { - // Remove the item ID before destroying the view because the view-model-item gets - // destroyed when the view is destroyed - removeItemID(viewmodel_item->getUUID()); - - // Item is to be moved outside the panel's directory (e.g. moved to trash for a panel that - // doesn't include trash). Just remove the item's UI. - view_item->destroyView(); - } - if(viewmodel_folder) - { - updateFolderLabel(viewmodel_folder->getUUID()); - } - old_parent->getViewModelItem()->dirtyDescendantsFilter(); - } - } - } - - ////////////////////////////// - // REMOVE Operation - // This item has been removed from memory, but its associated UI element still exists. - else if (!model_item && view_item && viewmodel_item) - { - // Remove the item's UI. - LLFolderViewFolder* parent = view_item->getParentFolder(); - removeItemID(viewmodel_item->getUUID()); - view_item->destroyView(); - if(parent) - { - parent->getViewModelItem()->dirtyDescendantsFilter(); - LLFolderViewModelItemInventory* viewmodel_folder = static_cast(parent->getViewModelItem()); - if(viewmodel_folder) - { - updateFolderLabel(viewmodel_folder->getUUID()); - } - } - } - } -} - -// Called when something changed in the global model (new item, item coming through the wire, rename, move, etc...) (CHUI-849) -void LLInventoryPanel::modelChanged(U32 mask) -{ - LL_PROFILE_ZONE_SCOPED; - - if (mViewsInitialized != VIEWS_INITIALIZED) return; - - const LLInventoryModel* model = getModel(); - if (!model) return; - - const LLInventoryModel::changed_items_t& changed_items = model->getChangedIDs(); - if (changed_items.empty()) return; - - for (LLInventoryModel::changed_items_t::const_iterator items_iter = changed_items.begin(); - items_iter != changed_items.end(); - ++items_iter) - { - const LLUUID& item_id = (*items_iter); - const LLInventoryObject* model_item = model->getObject(item_id); - itemChanged(item_id, mask, model_item); - } -} - -LLUUID LLInventoryPanel::getRootFolderID() -{ - LLUUID root_id; - if (mFolderRoot.get() && mFolderRoot.get()->getViewModelItem()) - { - root_id = static_cast(mFolderRoot.get()->getViewModelItem())->getUUID(); - } - else - { - if (mParams.start_folder.id.isChosen()) - { - root_id = mParams.start_folder.id; - } - else - { - const LLFolderType::EType preferred_type = mParams.start_folder.type.isChosen() - ? mParams.start_folder.type - : LLViewerFolderType::lookupTypeFromNewCategoryName(mParams.start_folder.name); - - if ("LIBRARY" == mParams.start_folder.name()) - { - root_id = gInventory.getLibraryRootFolderID(); - } - else if (preferred_type != LLFolderType::FT_NONE) - { - LLStringExplicit label(mParams.start_folder.name()); - setLabel(label); - - root_id = gInventory.findCategoryUUIDForType(preferred_type); - if (root_id.isNull()) - { - LL_WARNS() << "Could not find folder of type " << preferred_type << LL_ENDL; - root_id.generateNewID(); - } - } - } - } - return root_id; -} - -// static -void LLInventoryPanel::onIdle(void *userdata) -{ - if (!gInventory.isInventoryUsable()) - return; - - LLInventoryPanel *self = (LLInventoryPanel*)userdata; - if (self->mViewsInitialized <= VIEWS_INITIALIZING) - { - const F64 max_time = 0.001f; // 1 ms, in this case we need only root folders - self->initializeViews(max_time); // Shedules LLInventoryPanel::idle() - } - if (self->mViewsInitialized >= VIEWS_BUILDING) - { - gIdleCallbacks.deleteFunction(onIdle, (void*)self); - } -} - -struct DirtyFilterFunctor : public LLFolderViewFunctor -{ - /*virtual*/ void doFolder(LLFolderViewFolder* folder) - { - folder->getViewModelItem()->dirtyFilter(); - } - /*virtual*/ void doItem(LLFolderViewItem* item) - { - item->getViewModelItem()->dirtyFilter(); - } -}; - -void LLInventoryPanel::idle(void* user_data) -{ - LLInventoryPanel* panel = (LLInventoryPanel*)user_data; - // Nudge the filter if the clipboard state changed - if (panel->mClipboardState != LLClipboard::instance().getGeneration()) - { - panel->mClipboardState = LLClipboard::instance().getGeneration(); - const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - LLFolderViewFolder* trash_folder = panel->getFolderByID(trash_id); - if (trash_folder) - { - DirtyFilterFunctor dirtyFilterFunctor; - trash_folder->applyFunctorToChildren(dirtyFilterFunctor); - } - - } - - bool in_visible_chain = panel->isInVisibleChain(); - - if (!panel->mBuildViewsQueue.empty()) - { - const F64 max_time = in_visible_chain ? 0.006f : 0.001f; // 6 ms - F64 curent_time = LLTimer::getTotalSeconds(); - panel->mBuildViewsEndTime = curent_time + max_time; - - // things added last are closer to root thus of higher priority - std::deque priority_list; - priority_list.swap(panel->mBuildViewsQueue); - - while (curent_time < panel->mBuildViewsEndTime - && !priority_list.empty()) - { - LLUUID item_id = priority_list.back(); - priority_list.pop_back(); - - LLInventoryObject const* objectp = panel->mInventory->getObject(item_id); - if (objectp && panel->typedViewsFilter(item_id, objectp)) - { - LLFolderViewItem* folder_view_item = panel->getItemByID(item_id); - if (!folder_view_item || !folder_view_item->areChildrenInited()) - { - const LLUUID &parent_id = objectp->getParentUUID(); - LLFolderViewFolder* parent_folder = (LLFolderViewFolder*)panel->getItemByID(parent_id); - panel->buildViewsTree(item_id, parent_id, objectp, folder_view_item, parent_folder, BUILD_TIMELIMIT); - } - } - curent_time = LLTimer::getTotalSeconds(); - } - while (!priority_list.empty()) - { - // items in priority_list are of higher priority - panel->mBuildViewsQueue.push_back(priority_list.front()); - priority_list.pop_front(); - } - if (panel->mBuildViewsQueue.empty()) - { - panel->mViewsInitialized = VIEWS_INITIALIZED; - } - } - - // Take into account the fact that the root folder might be invalidated - if (panel->mFolderRoot.get()) - { - panel->mFolderRoot.get()->update(); - // while dragging, update selection rendering to reflect single/multi drag status - if (LLToolDragAndDrop::getInstance()->hasMouseCapture()) - { - EAcceptance last_accept = LLToolDragAndDrop::getInstance()->getLastAccept(); - if (last_accept == ACCEPT_YES_SINGLE || last_accept == ACCEPT_YES_COPY_SINGLE) - { - panel->mFolderRoot.get()->setShowSingleSelection(true); - } - else - { - panel->mFolderRoot.get()->setShowSingleSelection(false); - } - } - else - { - panel->mFolderRoot.get()->setShowSingleSelection(false); - } - } - else - { - LL_WARNS() << "Inventory : Deleted folder root detected on panel" << LL_ENDL; - panel->clearFolderRoot(); - } -} - - -void LLInventoryPanel::initializeViews(F64 max_time) -{ - if (!gInventory.isInventoryUsable()) return; - if (!mRootInited) return; - - mViewsInitialized = VIEWS_BUILDING; - - F64 curent_time = LLTimer::getTotalSeconds(); - mBuildViewsEndTime = curent_time + max_time; - - // init everything - LLUUID root_id = getRootFolderID(); - if (root_id.notNull()) - { - buildNewViews(getRootFolderID()); - } - else - { - // Default case: always add "My Inventory" root first, "Library" root second - // If we run out of time, this still should create root folders - buildNewViews(gInventory.getRootFolderID()); // My Inventory - buildNewViews(gInventory.getLibraryRootFolderID()); // Library - } - - if (mBuildViewsQueue.empty()) - { - mViewsInitialized = VIEWS_INITIALIZED; - } - - gIdleCallbacks.addFunction(idle, this); - - if(mParams.open_first_folder) - { - openStartFolderOrMyInventory(); - } - - // Special case for new user login - if (gAgent.isFirstLogin()) - { - // Auto open the user's library - LLFolderViewFolder* lib_folder = getFolderByID(gInventory.getLibraryRootFolderID()); - if (lib_folder) - { - lib_folder->setOpen(true); - } - - // Auto close the user's my inventory folder - LLFolderViewFolder* my_inv_folder = getFolderByID(gInventory.getRootFolderID()); - if (my_inv_folder) - { - my_inv_folder->setOpenArrangeRecursively(false, LLFolderViewFolder::RECURSE_DOWN); - } - } -} - - -LLFolderViewFolder * LLInventoryPanel::createFolderViewFolder(LLInvFVBridge * bridge, bool allow_drop) -{ - LLFolderViewFolder::Params params(mParams.folder); - - params.name = bridge->getDisplayName(); - params.root = mFolderRoot.get(); - params.listener = bridge; - params.tool_tip = params.name; - params.allow_drop = allow_drop; - - params.font_color = (bridge->isLibraryItem() ? sLibraryColor : sDefaultColor); - params.font_highlight_color = (bridge->isLibraryItem() ? sLibraryColor : sDefaultHighlightColor); - - return LLUICtrlFactory::create(params); -} - -LLFolderViewItem * LLInventoryPanel::createFolderViewItem(LLInvFVBridge * bridge) -{ - LLFolderViewItem::Params params(mParams.item); - - params.name = bridge->getDisplayName(); - params.creation_date = bridge->getCreationDate(); - params.root = mFolderRoot.get(); - params.listener = bridge; - params.rect = LLRect (0, 0, 0, 0); - params.tool_tip = params.name; - - params.font_color = (bridge->isLibraryItem() ? sLibraryColor : sDefaultColor); - params.font_highlight_color = (bridge->isLibraryItem() ? sLibraryColor : sDefaultHighlightColor); - - return LLUICtrlFactory::create(params); -} - -LLFolderViewItem* LLInventoryPanel::buildNewViews(const LLUUID& id) -{ - LLInventoryObject const* objectp = mInventory->getObject(id); - return buildNewViews(id, objectp); -} - -LLFolderViewItem* LLInventoryPanel::buildNewViews(const LLUUID& id, LLInventoryObject const* objectp) -{ - if (!objectp) - { - return NULL; - } - if (!typedViewsFilter(id, objectp)) - { - // if certain types are not allowed permanently, no reason to create views - return NULL; - } - - const LLUUID &parent_id = objectp->getParentUUID(); - LLFolderViewItem* folder_view_item = getItemByID(id); - LLFolderViewFolder* parent_folder = (LLFolderViewFolder*)getItemByID(parent_id); - - return buildViewsTree(id, parent_id, objectp, folder_view_item, parent_folder, BUILD_TIMELIMIT); -} - -LLFolderViewItem* LLInventoryPanel::buildNewViews(const LLUUID& id, - LLInventoryObject const* objectp, - LLFolderViewItem *folder_view_item, - const EBuildModes &mode) -{ - if (!objectp) - { - return NULL; - } - if (!typedViewsFilter(id, objectp)) - { - // if certain types are not allowed permanently, no reason to create views - return NULL; - } - - const LLUUID &parent_id = objectp->getParentUUID(); - LLFolderViewFolder* parent_folder = (LLFolderViewFolder*)getItemByID(parent_id); - - return buildViewsTree(id, parent_id, objectp, folder_view_item, parent_folder, mode); -} - -LLFolderViewItem* LLInventoryPanel::buildViewsTree(const LLUUID& id, - const LLUUID& parent_id, - LLInventoryObject const* objectp, - LLFolderViewItem *folder_view_item, - LLFolderViewFolder *parent_folder, - const EBuildModes &mode, - S32 depth) -{ - depth++; - - // Force the creation of an extra root level folder item if required by the inventory panel (default is "false") - bool allow_drop = true; - bool create_root = false; - if (mParams.show_root_folder) - { - LLUUID root_id = getRootFolderID(); - if (root_id == id) - { - // We insert an extra level that's seen by the UI but has no influence on the model - parent_folder = dynamic_cast(folder_view_item); - folder_view_item = NULL; - allow_drop = mParams.allow_drop_on_root; - create_root = true; - } - } - - if (!folder_view_item && parent_folder) - { - if (objectp->getType() <= LLAssetType::AT_NONE) - { - LL_WARNS() << "LLInventoryPanel::buildViewsTree called with invalid objectp->mType : " - << ((S32)objectp->getType()) << " name " << objectp->getName() << " UUID " << objectp->getUUID() - << LL_ENDL; - return NULL; - } - - if (objectp->getType() >= LLAssetType::AT_COUNT) - { - // Example: Happens when we add assets of new, not yet supported type to library - LL_DEBUGS("Inventory") << "LLInventoryPanel::buildViewsTree called with unknown objectp->mType : " - << ((S32) objectp->getType()) << " name " << objectp->getName() << " UUID " << objectp->getUUID() - << LL_ENDL; - - LLInventoryItem* item = (LLInventoryItem*)objectp; - if (item) - { - LLInvFVBridge* new_listener = mInvFVBridgeBuilder->createBridge(LLAssetType::AT_UNKNOWN, - LLAssetType::AT_UNKNOWN, - LLInventoryType::IT_UNKNOWN, - this, - &mInventoryViewModel, - mFolderRoot.get(), - item->getUUID(), - item->getFlags()); - - if (new_listener) - { - folder_view_item = createFolderViewItem(new_listener); - } - } - } - - if ((objectp->getType() == LLAssetType::AT_CATEGORY) && - (objectp->getActualType() != LLAssetType::AT_LINK_FOLDER)) - { - LLInvFVBridge* new_listener = mInvFVBridgeBuilder->createBridge(LLAssetType::AT_CATEGORY, - (mParams.use_marketplace_folders ? LLAssetType::AT_MARKETPLACE_FOLDER : LLAssetType::AT_CATEGORY), - LLInventoryType::IT_CATEGORY, - this, - &mInventoryViewModel, - mFolderRoot.get(), - objectp->getUUID()); - if (new_listener) - { - folder_view_item = createFolderViewFolder(new_listener,allow_drop); - } - } - else - { - // Build new view for item. - LLInventoryItem* item = (LLInventoryItem*)objectp; - LLInvFVBridge* new_listener = mInvFVBridgeBuilder->createBridge(item->getType(), - item->getActualType(), - item->getInventoryType(), - this, - &mInventoryViewModel, - mFolderRoot.get(), - item->getUUID(), - item->getFlags()); - - if (new_listener) - { - folder_view_item = createFolderViewItem(new_listener); - } - } - - if (folder_view_item) - { - llassert(parent_folder != NULL); - folder_view_item->addToFolder(parent_folder); - addItemID(id, folder_view_item); - // In the case of the root folder been shown, open that folder by default once the widget is created - if (create_root) - { - folder_view_item->setOpen(true); - } - } - } - - bool create_children = folder_view_item && objectp->getType() == LLAssetType::AT_CATEGORY - && (mBuildChildrenViews || depth == 0); - - if (create_children) - { - switch (mode) - { - case BUILD_TIMELIMIT: - { - F64 curent_time = LLTimer::getTotalSeconds(); - // If function is out of time, we want to shedule it into mBuildViewsQueue - // If we have time, no matter how little, create views for all children - // - // This creates children in 'bulk' to make sure folder has either - // 'empty and incomplete' or 'complete' states with nothing in between. - // Folders are marked as mIsFolderComplete == false by default, - // later arrange() will update mIsFolderComplete by child count - if (mBuildViewsEndTime < curent_time) - { - create_children = false; - // run it again for the sake of creating children - if (mBuildChildrenViews || depth == 0) - { - mBuildViewsQueue.push_back(id); - } - } - else - { - create_children = true; - folder_view_item->setChildrenInited(mBuildChildrenViews); - } - break; - } - case BUILD_NO_CHILDREN: - { - create_children = false; - // run it to create children, current caller is only interested in current view - if (mBuildChildrenViews || depth == 0) - { - mBuildViewsQueue.push_back(id); - } - break; - } - case BUILD_ONE_FOLDER: - { - // This view loads chindren, following ones don't - // Note: Might be better idea to do 'depth' instead, - // It also will help to prioritize root folder's content - create_children = true; - folder_view_item->setChildrenInited(true); - break; - } - case BUILD_NO_LIMIT: - default: - { - // keep working till everything exists - create_children = true; - folder_view_item->setChildrenInited(true); - } - } - } - - // If this is a folder, add the children of the folder and recursively add any - // child folders. - if (create_children) - { - LLViewerInventoryCategory::cat_array_t* categories; - LLViewerInventoryItem::item_array_t* items; - mInventory->lockDirectDescendentArrays(id, categories, items); - - // Make sure panel won't lock in a loop over existing items if - // folder is enormous and at least some work gets done - const S32 MIN_ITEMS_PER_CALL = 500; - const S32 starting_item_count = mItemMap.size(); - - LLFolderViewFolder *parentp = dynamic_cast(folder_view_item); - bool done = true; - - if(categories) - { - bool has_folders = parentp->getFoldersCount() > 0; - for (LLViewerInventoryCategory::cat_array_t::const_iterator cat_iter = categories->begin(); - cat_iter != categories->end(); - ++cat_iter) - { - const LLViewerInventoryCategory* cat = (*cat_iter); - if (typedViewsFilter(cat->getUUID(), cat)) - { - if (has_folders) - { - // This can be optimized: we don't need to call getItemByID() - // each time, especially since content is growing, we can just - // iter over copy of mItemMap in some way - LLFolderViewItem* view_itemp = getItemByID(cat->getUUID()); - buildViewsTree(cat->getUUID(), id, cat, view_itemp, parentp, (mode == BUILD_ONE_FOLDER ? BUILD_NO_CHILDREN : mode), depth); - } - else - { - buildViewsTree(cat->getUUID(), id, cat, NULL, parentp, (mode == BUILD_ONE_FOLDER ? BUILD_NO_CHILDREN : mode), depth); - } - } - - if (!mBuildChildrenViews - && mode == BUILD_TIMELIMIT - && MIN_ITEMS_PER_CALL + starting_item_count < mItemMap.size()) - { - // Single folder view, check if we still have time - // - // Todo: make sure this causes no dupplciates, breaks nothing, - // especially filters and arrange - F64 curent_time = LLTimer::getTotalSeconds(); - if (mBuildViewsEndTime < curent_time) - { - mBuildViewsQueue.push_back(id); - done = false; - break; - } - } - } - } - - if(items) - { - for (LLViewerInventoryItem::item_array_t::const_iterator item_iter = items->begin(); - item_iter != items->end(); - ++item_iter) - { - // At the moment we have to build folder's items in bulk and ignore mBuildViewsEndTime - const LLViewerInventoryItem* item = (*item_iter); - if (typedViewsFilter(item->getUUID(), item)) - { - // This can be optimized: we don't need to call getItemByID() - // each time, especially since content is growing, we can just - // iter over copy of mItemMap in some way - LLFolderViewItem* view_itemp = getItemByID(item->getUUID()); - buildViewsTree(item->getUUID(), id, item, view_itemp, parentp, mode, depth); - } - - if (!mBuildChildrenViews - && mode == BUILD_TIMELIMIT - && MIN_ITEMS_PER_CALL + starting_item_count < mItemMap.size()) - { - // Single folder view, check if we still have time - // - // Todo: make sure this causes no dupplciates, breaks nothing, - // especially filters and arrange - F64 curent_time = LLTimer::getTotalSeconds(); - if (mBuildViewsEndTime < curent_time) - { - mBuildViewsQueue.push_back(id); - done = false; - break; - } - } - } - } - - if (!mBuildChildrenViews && done) - { - // flat list is done initializing folder - folder_view_item->setChildrenInited(true); - } - mInventory->unlockDirectDescendentArrays(id); - } - - return folder_view_item; -} - -// bit of a hack to make sure the inventory is open. -void LLInventoryPanel::openStartFolderOrMyInventory() -{ - // Find My Inventory folder and open it up by name - for (LLView *child = mFolderRoot.get()->getFirstChild(); child; child = mFolderRoot.get()->findNextSibling(child)) - { - LLFolderViewFolder *fchild = dynamic_cast(child); - if (fchild - && fchild->getViewModelItem() - && fchild->getViewModelItem()->getName() == "My Inventory") - { - fchild->setOpen(true); - break; - } - } -} - -void LLInventoryPanel::onItemsCompletion() -{ - if (mFolderRoot.get()) mFolderRoot.get()->updateMenu(); -} - -void LLInventoryPanel::openSelected() -{ - LLFolderViewItem* folder_item = mFolderRoot.get()->getCurSelectedItem(); - if(!folder_item) return; - LLInvFVBridge* bridge = (LLInvFVBridge*)folder_item->getViewModelItem(); - if(!bridge) return; - bridge->openItem(); -} - -void LLInventoryPanel::unSelectAll() -{ - mFolderRoot.get()->setSelection(NULL, false, false); -} - - -bool LLInventoryPanel::handleHover(S32 x, S32 y, MASK mask) -{ - bool handled = LLView::handleHover(x, y, mask); - if(handled) - { - // getCursor gets current cursor, setCursor sets next cursor - // check that children didn't set own 'next' cursor - ECursorType cursor = getWindow()->getNextCursor(); - if (LLInventoryModelBackgroundFetch::instance().folderFetchActive() && cursor == UI_CURSOR_ARROW) - { - // replace arrow cursor with arrow and hourglass cursor - getWindow()->setCursor(UI_CURSOR_WORKING); - } - } - else - { - getWindow()->setCursor(UI_CURSOR_ARROW); - } - return true; -} - -bool LLInventoryPanel::handleToolTip(S32 x, S32 y, MASK mask) -{ - if (const LLFolderViewItem* hover_item_p = (!mFolderRoot.isDead()) ? mFolderRoot.get()->getHoveredItem() : nullptr) - { - if (const LLFolderViewModelItemInventory* vm_item_p = static_cast(hover_item_p->getViewModelItem())) - { - LLSD params; - params["inv_type"] = vm_item_p->getInventoryType(); - params["thumbnail_id"] = vm_item_p->getThumbnailUUID(); - params["item_id"] = vm_item_p->getUUID(); - - // tooltip should only show over folder, but screen - // rect includes items under folder as well - LLRect actionable_rect = hover_item_p->calcScreenRect(); - if (hover_item_p->isOpen() && hover_item_p->hasVisibleChildren()) - { - actionable_rect.mBottom = actionable_rect.mTop - hover_item_p->getItemHeight(); - } - - LLToolTipMgr::instance().show(LLToolTip::Params() - .message(hover_item_p->getToolTip()) - .sticky_rect(actionable_rect) - .delay_time(LLView::getTooltipTimeout()) - .create_callback(boost::bind(&LLInspectTextureUtil::createInventoryToolTip, _1)) - .create_params(params)); - return true; - } - } - return LLPanel::handleToolTip(x, y, mask); -} - -bool LLInventoryPanel::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - bool handled = false; - - if (mAcceptsDragAndDrop) - { - handled = LLPanel::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); - - // If folder view is empty the (x, y) point won't be in its rect - // so the handler must be called explicitly. - // but only if was not handled before. See EXT-6746. - if (!handled && mParams.allow_drop_on_root && !mFolderRoot.get()->hasVisibleChildren()) - { - handled = mFolderRoot.get()->handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); - } - - if (handled) - { - mFolderRoot.get()->setDragAndDropThisFrame(); - } - } - - return handled; -} - -void LLInventoryPanel::onFocusLost() -{ - // inventory no longer handles cut/copy/paste/delete - if (LLEditMenuHandler::gEditMenuHandler == mFolderRoot.get()) - { - LLEditMenuHandler::gEditMenuHandler = NULL; - } - - LLPanel::onFocusLost(); -} - -void LLInventoryPanel::onFocusReceived() -{ - // inventory now handles cut/copy/paste/delete - LLEditMenuHandler::gEditMenuHandler = mFolderRoot.get(); - - LLPanel::onFocusReceived(); -} - -void LLInventoryPanel::onFolderOpening(const LLUUID &id) -{ - LLFolderViewItem* folder = getItemByID(id); - if (folder && !folder->areChildrenInited()) - { - // Last item in list will be processed first. - // This might result in dupplicates in list, but it - // isn't critical, views won't be created twice - mBuildViewsQueue.push_back(id); - } -} - -bool LLInventoryPanel::addBadge(LLBadge * badge) -{ - bool badge_added = false; - - if (acceptsBadge()) - { - badge_added = badge->addToView(mFolderRoot.get()); - } - - return badge_added; -} - -void LLInventoryPanel::openAllFolders() -{ - mFolderRoot.get()->setOpenArrangeRecursively(true, LLFolderViewFolder::RECURSE_DOWN); - mFolderRoot.get()->arrangeAll(); -} - -void LLInventoryPanel::setSelection(const LLUUID& obj_id, bool take_keyboard_focus) -{ - // Don't select objects in COF (e.g. to prevent refocus when items are worn). - const LLInventoryObject *obj = mInventory->getObject(obj_id); - if (obj && obj->getParentUUID() == LLAppearanceMgr::instance().getCOF()) - { - return; - } - setSelectionByID(obj_id, take_keyboard_focus); -} - -void LLInventoryPanel::setSelectCallback(const boost::function& items, bool user_action)>& cb) -{ - if (mFolderRoot.get()) - { - mFolderRoot.get()->setSelectCallback(cb); - } - mSelectionCallback = cb; -} - -void LLInventoryPanel::clearSelection() -{ - mSelectThisID.setNull(); - mFocusSelection = false; -} - -LLInventoryPanel::selected_items_t LLInventoryPanel::getSelectedItems() const -{ - return mFolderRoot.get()->getSelectionList(); -} - -void LLInventoryPanel::onSelectionChange(const std::deque& items, bool user_action) -{ - // Schedule updating the folder view context menu when all selected items become complete (STORM-373). - mCompletionObserver->reset(); - for (std::deque::const_iterator it = items.begin(); it != items.end(); ++it) - { - LLFolderViewModelItemInventory* view_model = static_cast((*it)->getViewModelItem()); - if (view_model) - { - LLUUID id = view_model->getUUID(); - if (!(*it)->areChildrenInited()) - { - const F64 max_time = 0.0001f; - mBuildViewsEndTime = LLTimer::getTotalSeconds() + max_time; - buildNewViews(id); - } - LLViewerInventoryItem* inv_item = mInventory->getItem(id); - - if (inv_item && !inv_item->isFinished()) - { - mCompletionObserver->watchItem(id); - } - } - } - - LLFolderView* fv = mFolderRoot.get(); - if (fv->needsAutoRename()) // auto-selecting a new user-created asset and preparing to rename - { - fv->setNeedsAutoRename(false); - if (items.size()) // new asset is visible and selected - { - fv->startRenamingSelectedItem(); - } - else - { - LL_DEBUGS("Inventory") << "Failed to start renemr, no items selected" << LL_ENDL; - } - } - - std::set selected_items = mFolderRoot.get()->getSelectionList(); - LLFolderViewItem* prev_folder_item = getItemByID(mPreviousSelectedFolder); - - if (selected_items.size() == 1) - { - std::set::const_iterator iter = selected_items.begin(); - LLFolderViewItem* folder_item = (*iter); - if(folder_item && (folder_item != prev_folder_item)) - { - LLFolderViewModelItemInventory* fve_listener = static_cast(folder_item->getViewModelItem()); - if (fve_listener && (fve_listener->getInventoryType() == LLInventoryType::IT_CATEGORY)) - { - if (fve_listener->getInventoryObject() && fve_listener->getInventoryObject()->getIsLinkType()) - { - return; - } - - if(prev_folder_item) - { - LLFolderBridge* prev_bridge = (LLFolderBridge*)prev_folder_item->getViewModelItem(); - if(prev_bridge) - { - prev_bridge->clearDisplayName(); - prev_bridge->setShowDescendantsCount(false); - prev_folder_item->refresh(); - } - } - - LLFolderBridge* bridge = (LLFolderBridge*)folder_item->getViewModelItem(); - if(bridge) - { - bridge->clearDisplayName(); - bridge->setShowDescendantsCount(true); - folder_item->refresh(); - mPreviousSelectedFolder = bridge->getUUID(); - } - } - } - } - else - { - if(prev_folder_item) - { - LLFolderBridge* prev_bridge = (LLFolderBridge*)prev_folder_item->getViewModelItem(); - if(prev_bridge) - { - prev_bridge->clearDisplayName(); - prev_bridge->setShowDescendantsCount(false); - prev_folder_item->refresh(); - } - } - mPreviousSelectedFolder = LLUUID(); - } - -} - -void LLInventoryPanel::updateFolderLabel(const LLUUID& folder_id) -{ - if(folder_id != mPreviousSelectedFolder) return; - - LLFolderViewItem* folder_item = getItemByID(mPreviousSelectedFolder); - if(folder_item) - { - LLFolderBridge* bridge = (LLFolderBridge*)folder_item->getViewModelItem(); - if(bridge) - { - bridge->clearDisplayName(); - bridge->setShowDescendantsCount(true); - folder_item->refresh(); - } - } -} - -void LLInventoryPanel::doCreate(const LLSD& userdata) -{ - reset_inventory_filter(); - menu_create_inventory_item(this, LLFolderBridge::sSelf.get(), userdata); -} - -bool LLInventoryPanel::beginIMSession() -{ - std::set selected_items = mFolderRoot.get()->getSelectionList(); - - std::string name; - - std::vector members; - EInstantMessage type = IM_SESSION_CONFERENCE_START; - - std::set::const_iterator iter; - for (iter = selected_items.begin(); iter != selected_items.end(); iter++) - { - - LLFolderViewItem* folder_item = (*iter); - - if(folder_item) - { - LLFolderViewModelItemInventory* fve_listener = static_cast(folder_item->getViewModelItem()); - if (fve_listener && (fve_listener->getInventoryType() == LLInventoryType::IT_CATEGORY)) - { - - LLFolderBridge* bridge = (LLFolderBridge*)folder_item->getViewModelItem(); - if(!bridge) return true; - LLViewerInventoryCategory* cat = bridge->getCategory(); - if(!cat) return true; - name = cat->getName(); - LLUniqueBuddyCollector is_buddy; - LLInventoryModel::cat_array_t cat_array; - LLInventoryModel::item_array_t item_array; - gInventory.collectDescendentsIf(bridge->getUUID(), - cat_array, - item_array, - LLInventoryModel::EXCLUDE_TRASH, - is_buddy); - S32 count = item_array.size(); - if(count > 0) - { - //*TODO by what to replace that? - //LLFloaterReg::showInstance("communicate"); - - // create the session - LLAvatarTracker& at = LLAvatarTracker::instance(); - LLUUID id; - for(S32 i = 0; i < count; ++i) - { - id = item_array.at(i)->getCreatorUUID(); - if(at.isBuddyOnline(id)) - { - members.push_back(id); - } - } - } - } - else - { - LLInvFVBridge* listenerp = (LLInvFVBridge*)folder_item->getViewModelItem(); - - if (listenerp->getInventoryType() == LLInventoryType::IT_CALLINGCARD) - { - LLInventoryItem* inv_item = gInventory.getItem(listenerp->getUUID()); - - if (inv_item) - { - LLAvatarTracker& at = LLAvatarTracker::instance(); - LLUUID id = inv_item->getCreatorUUID(); - - if(at.isBuddyOnline(id)) - { - members.push_back(id); - } - } - } //if IT_CALLINGCARD - } //if !IT_CATEGORY - } - } //for selected_items - - // the session_id is randomly generated UUID which will be replaced later - // with a server side generated number - - if (name.empty()) - { - name = LLTrans::getString("conference-title"); - } - - LLUUID session_id = gIMMgr->addSession(name, type, members[0], members); - if (session_id != LLUUID::null) - { - LLFloaterIMContainer::getInstance()->showConversation(session_id); - } - - return true; -} - -void LLInventoryPanel::fileUploadLocation(const LLSD& userdata) -{ - const std::string param = userdata.asString(); - if (param == "model") - { - gSavedPerAccountSettings.setString("ModelUploadFolder", LLFolderBridge::sSelf.get()->getUUID().asString()); - } - else if (param == "texture") - { - gSavedPerAccountSettings.setString("TextureUploadFolder", LLFolderBridge::sSelf.get()->getUUID().asString()); - } - else if (param == "sound") - { - gSavedPerAccountSettings.setString("SoundUploadFolder", LLFolderBridge::sSelf.get()->getUUID().asString()); - } - else if (param == "animation") - { - gSavedPerAccountSettings.setString("AnimationUploadFolder", LLFolderBridge::sSelf.get()->getUUID().asString()); - } - else if (param == "pbr_material") - { - gSavedPerAccountSettings.setString("PBRUploadFolder", LLFolderBridge::sSelf.get()->getUUID().asString()); - } -} - -void LLInventoryPanel::openSingleViewInventory(LLUUID folder_id) -{ - LLPanelMainInventory::newFolderWindow(folder_id.isNull() ? LLFolderBridge::sSelf.get()->getUUID() : folder_id); -} - -void LLInventoryPanel::purgeSelectedItems() -{ - if (!mFolderRoot.get()) return; - - const std::set inventory_selected = mFolderRoot.get()->getSelectionList(); - if (inventory_selected.empty()) return; - LLSD args; - S32 count = inventory_selected.size(); - std::vector selected_items; - for (std::set::const_iterator it = inventory_selected.begin(), end_it = inventory_selected.end(); - it != end_it; - ++it) - { - LLUUID item_id = static_cast((*it)->getViewModelItem())->getUUID(); - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - gInventory.collectDescendents(item_id, cats, items, LLInventoryModel::INCLUDE_TRASH); - count += items.size() + cats.size(); - selected_items.push_back(item_id); - } - args["COUNT"] = count; - LLNotificationsUtil::add("PurgeSelectedItems", args, LLSD(), boost::bind(callbackPurgeSelectedItems, _1, _2, selected_items)); -} - -// static -void LLInventoryPanel::callbackPurgeSelectedItems(const LLSD& notification, const LLSD& response, const std::vector inventory_selected) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) - { - if (inventory_selected.empty()) return; - - for (auto it : inventory_selected) - { - remove_inventory_object(it, NULL); - } - } -} - -bool LLInventoryPanel::attachObject(const LLSD& userdata) -{ - // Copy selected item UUIDs to a vector. - std::set selected_items = mFolderRoot.get()->getSelectionList(); - uuid_vec_t items; - for (std::set::const_iterator set_iter = selected_items.begin(); - set_iter != selected_items.end(); - ++set_iter) - { - items.push_back(static_cast((*set_iter)->getViewModelItem())->getUUID()); - } - - // Attach selected items. - LLViewerAttachMenu::attachObjects(items, userdata.asString()); - - gFocusMgr.setKeyboardFocus(NULL); - - return true; -} - -bool LLInventoryPanel::getSinceLogoff() -{ - return getFilter().isSinceLogoff(); -} - -// DEBUG ONLY -// static -void LLInventoryPanel::dumpSelectionInformation(void* user_data) -{ - LLInventoryPanel* iv = (LLInventoryPanel*)user_data; - iv->mFolderRoot.get()->dumpSelectionInformation(); -} - -bool is_inventorysp_active() -{ - LLSidepanelInventory *sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); - if (!sidepanel_inventory || !sidepanel_inventory->isInVisibleChain()) return false; - return sidepanel_inventory->isMainInventoryPanelActive(); -} - -// static -LLInventoryPanel* LLInventoryPanel::getActiveInventoryPanel(bool auto_open) -{ - S32 z_min = S32_MAX; - LLInventoryPanel* res = NULL; - LLFloater* active_inv_floaterp = NULL; - - LLFloater* floater_inventory = LLFloaterReg::getInstance("inventory"); - if (!floater_inventory) - { - LL_WARNS() << "Could not find My Inventory floater" << LL_ENDL; - return nullptr; - } - - LLSidepanelInventory *inventory_panel = LLFloaterSidePanelContainer::getPanel("inventory"); - - // Iterate through the inventory floaters and return whichever is on top. - LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("inventory"); - for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); iter != inst_list.end(); ++iter) - { - LLFloaterSidePanelContainer* inventory_floater = dynamic_cast(*iter); - inventory_panel = inventory_floater->findChild("main_panel"); - - if (inventory_floater && inventory_panel && inventory_floater->getVisible()) - { - S32 z_order = gFloaterView->getZOrder(inventory_floater); - if (z_order < z_min) - { - res = inventory_panel->getActivePanel(); - z_min = z_order; - active_inv_floaterp = inventory_floater; - } - } - } - - if (res) - { - // Make sure the floater is not minimized (STORM-438). - if (active_inv_floaterp && active_inv_floaterp->isMinimized()) - { - active_inv_floaterp->setMinimized(false); - } - } - else if (auto_open) - { - floater_inventory->openFloater(); - - res = inventory_panel->getActivePanel(); - } - - return res; -} - -//static -void LLInventoryPanel::openInventoryPanelAndSetSelection(bool auto_open, const LLUUID& obj_id, - bool use_main_panel, bool take_keyboard_focus, bool reset_filter) -{ - LLSidepanelInventory* sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); - sidepanel_inventory->showInventoryPanel(); - - LLUUID cat_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX); - bool in_inbox = gInventory.isObjectDescendentOf(obj_id, cat_id); - if (!in_inbox && use_main_panel) - { - sidepanel_inventory->selectAllItemsPanel(); - } - - if (!auto_open) - { - LLFloater* inventory_floater = LLFloaterSidePanelContainer::getTopmostInventoryFloater(); - if (inventory_floater && inventory_floater->getVisible()) - { - LLSidepanelInventory *inventory_panel = inventory_floater->findChild("main_panel"); - LLPanelMainInventory* main_panel = inventory_panel->getMainInventoryPanel(); - if (main_panel->isSingleFolderMode() && main_panel->isGalleryViewMode()) - { - LL_DEBUGS("Inventory") << "Opening gallery panel for item" << obj_id << LL_ENDL; - main_panel->setGallerySelection(obj_id); - return; - } - } - } - - if (use_main_panel) - { - LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); - if (main_inventory && main_inventory->isSingleFolderMode()) - { - const LLInventoryObject *obj = gInventory.getObject(obj_id); - if (obj) - { - LL_DEBUGS("Inventory") << "Opening main inventory panel for item" << obj_id << LL_ENDL; - main_inventory->setSingleFolderViewRoot(obj->getParentUUID(), false); - main_inventory->setGallerySelection(obj_id); - return; - } - } - } - - LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(auto_open); - if (active_panel) - { - LL_DEBUGS("Messaging", "Inventory") << "Highlighting" << obj_id << LL_ENDL; - - if (reset_filter) - { - reset_inventory_filter(); - } - - if (in_inbox) - { - sidepanel_inventory->openInbox(); - LLInventoryPanel* inventory_panel = sidepanel_inventory->getInboxPanel(); - if (inventory_panel) - { - inventory_panel->setSelection(obj_id, take_keyboard_focus); - } - } - else if (auto_open) - { - LLFloater* floater_inventory = LLFloaterReg::getInstance("inventory"); - if (floater_inventory) - { - floater_inventory->setFocus(true); - } - active_panel->setSelection(obj_id, take_keyboard_focus); - } - else - { - // Created items are going to receive proper focus from callbacks - active_panel->setSelection(obj_id, take_keyboard_focus); - } - } -} - -void LLInventoryPanel::setSFViewAndOpenFolder(const LLInventoryPanel* panel, const LLUUID& folder_id) -{ - LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("inventory"); - for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); iter != inst_list.end(); ++iter) - { - LLFloaterSidePanelContainer* inventory_floater = dynamic_cast(*iter); - LLSidepanelInventory* sidepanel_inventory = inventory_floater->findChild("main_panel"); - - LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); - if (main_inventory && panel->hasAncestor(main_inventory) && !main_inventory->isSingleFolderMode()) - { - main_inventory->initSingleFolderRoot(folder_id); - main_inventory->toggleViewMode(); - main_inventory->setSingleFolderViewRoot(folder_id, false); - } - } -} - -void LLInventoryPanel::addHideFolderType(LLFolderType::EType folder_type) -{ - getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << folder_type)); -} - -bool LLInventoryPanel::getIsHiddenFolderType(LLFolderType::EType folder_type) const -{ - return !(getFilter().getFilterCategoryTypes() & (1ULL << folder_type)); -} - -void LLInventoryPanel::addItemID( const LLUUID& id, LLFolderViewItem* itemp ) -{ - mItemMap[id] = itemp; -} - -void LLInventoryPanel::removeItemID(const LLUUID& id) -{ - LLInventoryModel::cat_array_t categories; - LLInventoryModel::item_array_t items; - gInventory.collectDescendents(id, categories, items, true); - - mItemMap.erase(id); - - for (LLInventoryModel::cat_array_t::iterator it = categories.begin(), end_it = categories.end(); - it != end_it; - ++it) - { - mItemMap.erase((*it)->getUUID()); -} - - for (LLInventoryModel::item_array_t::iterator it = items.begin(), end_it = items.end(); - it != end_it; - ++it) - { - mItemMap.erase((*it)->getUUID()); - } -} - -LLFolderViewItem* LLInventoryPanel::getItemByID(const LLUUID& id) -{ - LL_PROFILE_ZONE_SCOPED; - - std::map::iterator map_it; - map_it = mItemMap.find(id); - if (map_it != mItemMap.end()) - { - return map_it->second; - } - - return NULL; -} - -LLFolderViewFolder* LLInventoryPanel::getFolderByID(const LLUUID& id) -{ - LLFolderViewItem* item = getItemByID(id); - return dynamic_cast(item); -} - - -void LLInventoryPanel::setSelectionByID( const LLUUID& obj_id, bool take_keyboard_focus ) -{ - LLFolderViewItem* itemp = getItemByID(obj_id); - - if (itemp && !itemp->areChildrenInited()) - { - LLInventoryObject const* objectp = mInventory->getObject(obj_id); - if (objectp) - { - buildNewViews(obj_id, objectp, itemp, BUILD_ONE_FOLDER); - } - } - - if(itemp && itemp->getViewModelItem()) - { - itemp->arrangeAndSet(true, take_keyboard_focus); - mSelectThisID.setNull(); - mFocusSelection = false; - return; - } - else - { - // save the desired item to be selected later (if/when ready) - mFocusSelection = take_keyboard_focus; - mSelectThisID = obj_id; - } -} - -void LLInventoryPanel::updateSelection() -{ - if (mSelectThisID.notNull()) - { - setSelectionByID(mSelectThisID, mFocusSelection); - } -} - -void LLInventoryPanel::doToSelected(const LLSD& userdata) -{ - if (("purge" == userdata.asString())) - { - purgeSelectedItems(); - return; - } - LLInventoryAction::doToSelected(mInventory, mFolderRoot.get(), userdata.asString()); - - return; -} - -bool LLInventoryPanel::handleKeyHere( KEY key, MASK mask ) -{ - bool handled = false; - switch (key) - { - case KEY_RETURN: - // Open selected items if enter key hit on the inventory panel - if (mask == MASK_NONE) - { - if (mSuppressOpenItemAction) - { - LLFolderViewItem* folder_item = mFolderRoot.get()->getCurSelectedItem(); - if(folder_item) - { - LLInvFVBridge* bridge = (LLInvFVBridge*)folder_item->getViewModelItem(); - if(bridge && (bridge->getInventoryType() != LLInventoryType::IT_CATEGORY)) - { - return handled; - } - } - } - LLInventoryAction::doToSelected(mInventory, mFolderRoot.get(), "open"); - handled = true; - } - break; - case KEY_DELETE: -#if LL_DARWIN - case KEY_BACKSPACE: -#endif - // Delete selected items if delete or backspace key hit on the inventory panel - // Note: on Mac laptop keyboards, backspace and delete are one and the same - if (isSelectionRemovable() && (mask == MASK_NONE)) - { - LLInventoryAction::doToSelected(mInventory, mFolderRoot.get(), "delete"); - handled = true; - } - break; - } - return handled; -} - -bool LLInventoryPanel::isSelectionRemovable() -{ - bool can_delete = false; - if (mFolderRoot.get()) - { - std::set selection_set = mFolderRoot.get()->getSelectionList(); - if (!selection_set.empty()) - { - can_delete = true; - for (std::set::iterator iter = selection_set.begin(); - iter != selection_set.end(); - ++iter) - { - LLFolderViewItem *item = *iter; - const LLFolderViewModelItemInventory *listener = static_cast(item->getViewModelItem()); - if (!listener) - { - can_delete = false; - } - else - { - can_delete &= listener->isItemRemovable() && !listener->isItemInTrash(); - } - } - } - } - return can_delete; -} - -/************************************************************************/ -/* Recent Inventory Panel related class */ -/************************************************************************/ -static const LLRecentInventoryBridgeBuilder RECENT_ITEMS_BUILDER; -class LLInventoryRecentItemsPanel : public LLInventoryPanel -{ -public: - struct Params : public LLInitParam::Block - {}; - - void initFromParams(const Params& p) - { - LLInventoryPanel::initFromParams(p); - // turn on inbox for recent items - getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() | (1ULL << LLFolderType::FT_INBOX)); - // turn off marketplace for recent items - getFilter().setFilterNoMarketplaceFolder(); - } - -protected: - LLInventoryRecentItemsPanel (const Params&); - friend class LLUICtrlFactory; -}; - -LLInventoryRecentItemsPanel::LLInventoryRecentItemsPanel( const Params& params) -: LLInventoryPanel(params) -{ - // replace bridge builder to have necessary View bridges. - mInvFVBridgeBuilder = &RECENT_ITEMS_BUILDER; -} - -static LLDefaultChildRegistry::Register t_single_folder_inventory_panel("single_folder_inventory_panel"); - -LLInventorySingleFolderPanel::LLInventorySingleFolderPanel(const Params& params) - : LLInventoryPanel(params) -{ - mBuildChildrenViews = false; - getFilter().setSingleFolderMode(true); - getFilter().setEmptyLookupMessage("InventorySingleFolderNoMatches"); - getFilter().setDefaultEmptyLookupMessage("InventorySingleFolderEmpty"); - - mCommitCallbackRegistrar.replace("Inventory.DoToSelected", boost::bind(&LLInventorySingleFolderPanel::doToSelected, this, _2)); - mCommitCallbackRegistrar.replace("Inventory.DoCreate", boost::bind(&LLInventorySingleFolderPanel::doCreate, this, _2)); - mCommitCallbackRegistrar.replace("Inventory.Share", boost::bind(&LLInventorySingleFolderPanel::doShare, this)); -} - -LLInventorySingleFolderPanel::~LLInventorySingleFolderPanel() -{ -} - -void LLInventorySingleFolderPanel::initFromParams(const Params& p) -{ - mFolderID = gInventory.getRootFolderID(); - - mParams = p; - LLPanel::initFromParams(mParams); -} - -void LLInventorySingleFolderPanel::onFocusReceived() -{ - // Tab support, when tabbing into this view, select first item - // (ideally needs to account for scroll) - bool select_first = mSelectThisID.isNull() && mFolderRoot.get() && mFolderRoot.get()->getSelectedCount() == 0; - - if (select_first) - { - LLFolderViewFolder::folders_t::const_iterator folders_it = mFolderRoot.get()->getFoldersBegin(); - LLFolderViewFolder::folders_t::const_iterator folders_end = mFolderRoot.get()->getFoldersEnd(); - - for (; folders_it != folders_end; ++folders_it) - { - const LLFolderViewFolder* folder_view = *folders_it; - if (folder_view->getVisible()) - { - const LLFolderViewModelItemInventory* modelp = static_cast(folder_view->getViewModelItem()); - setSelectionByID(modelp->getUUID(), true); - // quick and dirty fix: don't scroll on switching focus - // todo: better 'tab' support, one that would work for LLInventoryPanel - mFolderRoot.get()->stopAutoScollining(); - select_first = false; - break; - } - } - } - - if (select_first) - { - LLFolderViewFolder::items_t::const_iterator items_it = mFolderRoot.get()->getItemsBegin(); - LLFolderViewFolder::items_t::const_iterator items_end = mFolderRoot.get()->getItemsEnd(); - - for (; items_it != items_end; ++items_it) - { - const LLFolderViewItem* item_view = *items_it; - if (item_view->getVisible()) - { - const LLFolderViewModelItemInventory* modelp = static_cast(item_view->getViewModelItem()); - setSelectionByID(modelp->getUUID(), true); - mFolderRoot.get()->stopAutoScollining(); - break; - } - } - } - LLInventoryPanel::onFocusReceived(); -} - -void LLInventorySingleFolderPanel::initFolderRoot(const LLUUID& start_folder_id) -{ - if(mRootInited) return; - - mRootInited = true; - if(start_folder_id.notNull()) - { - mFolderID = start_folder_id; - } - - mParams.open_first_folder = false; - mParams.start_folder.id = mFolderID; - - LLInventoryPanel::initFolderRoot(); - mFolderRoot.get()->setSingleFolderMode(true); -} - -void LLInventorySingleFolderPanel::changeFolderRoot(const LLUUID& new_id) -{ - if (mFolderID != new_id) - { - if(mFolderID.notNull()) - { - mBackwardFolders.push_back(mFolderID); - } - mFolderID = new_id; - updateSingleFolderRoot(); - } -} - -void LLInventorySingleFolderPanel::onForwardFolder() -{ - if(isForwardAvailable()) - { - mBackwardFolders.push_back(mFolderID); - mFolderID = mForwardFolders.back(); - mForwardFolders.pop_back(); - updateSingleFolderRoot(); - } -} - -void LLInventorySingleFolderPanel::onBackwardFolder() -{ - if(isBackwardAvailable()) - { - mForwardFolders.push_back(mFolderID); - mFolderID = mBackwardFolders.back(); - mBackwardFolders.pop_back(); - updateSingleFolderRoot(); - } -} - -void LLInventorySingleFolderPanel::clearNavigationHistory() -{ - mForwardFolders.clear(); - mBackwardFolders.clear(); -} - -bool LLInventorySingleFolderPanel::isBackwardAvailable() -{ - return (!mBackwardFolders.empty() && (mFolderID != mBackwardFolders.back())); -} - -bool LLInventorySingleFolderPanel::isForwardAvailable() -{ - return (!mForwardFolders.empty() && (mFolderID != mForwardFolders.back())); -} - -boost::signals2::connection LLInventorySingleFolderPanel::setRootChangedCallback(root_changed_callback_t cb) -{ - return mRootChangedSignal.connect(cb); -} - -void LLInventorySingleFolderPanel::updateSingleFolderRoot() -{ - if (mFolderID != getRootFolderID()) - { - mRootChangedSignal(); - - LLUUID root_id = mFolderID; - if (mFolderRoot.get()) - { - mItemMap.clear(); - mFolderRoot.get()->destroyRoot(); - } - - mCommitCallbackRegistrar.pushScope(); - { - LLFolderView* folder_view = createFolderRoot(root_id); - folder_view->setChildrenInited(false); - mFolderRoot = folder_view->getHandle(); - mFolderRoot.get()->setSingleFolderMode(true); - addItemID(root_id, mFolderRoot.get()); - - LLRect scroller_view_rect = getRect(); - scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom); - LLScrollContainer::Params scroller_params(mParams.scroll()); - scroller_params.rect(scroller_view_rect); - - if (mScroller) - { - removeChild(mScroller); - delete mScroller; - mScroller = NULL; - } - mScroller = LLUICtrlFactory::create(scroller_params); - addChild(mScroller); - mScroller->addChild(mFolderRoot.get()); - mFolderRoot.get()->setScrollContainer(mScroller); - mFolderRoot.get()->setFollowsAll(); - mFolderRoot.get()->addChild(mFolderRoot.get()->mStatusTextBox); - - if (!mSelectionCallback.empty()) - { - mFolderRoot.get()->setSelectCallback(mSelectionCallback); - } - } - mCommitCallbackRegistrar.popScope(); - mFolderRoot.get()->setCallbackRegistrar(&mCommitCallbackRegistrar); - - buildNewViews(mFolderID); - - LLFloater* root_floater = gFloaterView->getParentFloater(this); - if(root_floater) - { - root_floater->setFocus(true); - } - } -} - -bool LLInventorySingleFolderPanel::hasVisibleItems() -{ - return mFolderRoot.get()->hasVisibleChildren(); -} - -void LLInventorySingleFolderPanel::doCreate(const LLSD& userdata) -{ - std::string type_name = userdata.asString(); - LLUUID dest_id = LLFolderBridge::sSelf.get()->getUUID(); - if (("category" == type_name) || ("outfit" == type_name)) - { - changeFolderRoot(dest_id); - } - reset_inventory_filter(); - menu_create_inventory_item(this, dest_id, userdata); -} - -void LLInventorySingleFolderPanel::doToSelected(const LLSD& userdata) -{ - if (("open_in_current_window" == userdata.asString())) - { - changeFolderRoot(LLFolderBridge::sSelf.get()->getUUID()); - return; - } - LLInventoryPanel::doToSelected(userdata); -} - -void LLInventorySingleFolderPanel::doShare() -{ - LLAvatarActions::shareWithAvatars(this); -} -/************************************************************************/ -/* Asset Pre-Filtered Inventory Panel related class */ -/************************************************************************/ - -LLAssetFilteredInventoryPanel::LLAssetFilteredInventoryPanel(const Params& p) - : LLInventoryPanel(p) -{ -} - - -void LLAssetFilteredInventoryPanel::initFromParams(const Params& p) -{ - // Init asset types - std::string types = p.filter_asset_types.getValue(); - - typedef boost::tokenizer > tokenizer; - boost::char_separator sep("|"); - tokenizer tokens(types, sep); - tokenizer::iterator token_iter = tokens.begin(); - - memset(mAssetTypes, 0, LLAssetType::AT_COUNT * sizeof(bool)); - while (token_iter != tokens.end()) - { - const std::string& token_str = *token_iter; - LLAssetType::EType asset_type = LLAssetType::lookup(token_str); - if (asset_type > LLAssetType::AT_NONE && asset_type < LLAssetType::AT_COUNT) - { - mAssetTypes[asset_type] = true; - } - ++token_iter; - } - - // Init drag types - memset(mDragTypes, 0, EDragAndDropType::DAD_COUNT * sizeof(bool)); - for (S32 i = 0; i < LLAssetType::AT_COUNT; i++) - { - if (mAssetTypes[i]) - { - EDragAndDropType drag_type = LLViewerAssetType::lookupDragAndDropType((LLAssetType::EType)i); - if (drag_type != DAD_NONE) - { - mDragTypes[drag_type] = true; - } - } - } - // Always show AT_CATEGORY, but it shouldn't get into mDragTypes - mAssetTypes[LLAssetType::AT_CATEGORY] = true; - - // Init the panel - LLInventoryPanel::initFromParams(p); - U64 filter_cats = getFilter().getFilterCategoryTypes(); - filter_cats &= ~(1ULL << LLFolderType::FT_MARKETPLACE_LISTINGS); - getFilter().setFilterCategoryTypes(filter_cats); - getFilter().setFilterNoMarketplaceFolder(); -} - -bool LLAssetFilteredInventoryPanel::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - bool result = false; - - if (mAcceptsDragAndDrop) - { - // Don't allow DAD_CATEGORY here since it can contain other items besides required assets - // We should see everything we drop! - if (mDragTypes[cargo_type]) - { - result = LLInventoryPanel::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); - } - } - - return result; -} - -/*virtual*/ -bool LLAssetFilteredInventoryPanel::typedViewsFilter(const LLUUID& id, LLInventoryObject const* objectp) -{ - if (!objectp) - { - return false; - } - LLAssetType::EType asset_type = objectp->getType(); - - if (asset_type < 0 || asset_type >= LLAssetType::AT_COUNT) - { - return false; - } - - if (!mAssetTypes[asset_type]) - { - return false; - } - - return true; -} - -void LLAssetFilteredInventoryPanel::itemChanged(const LLUUID& id, U32 mask, const LLInventoryObject* model_item) -{ - if (!model_item && !getItemByID(id)) - { - // remove operation, but item is not in panel already - return; - } - - if (model_item) - { - LLAssetType::EType asset_type = model_item->getType(); - - if (asset_type < 0 - || asset_type >= LLAssetType::AT_COUNT - || !mAssetTypes[asset_type]) - { - return; - } - } - - LLInventoryPanel::itemChanged(id, mask, model_item); -} - -namespace LLInitParam -{ - void TypeValues::declareValues() - { - declare(LLFolderType::lookup(LLFolderType::FT_TEXTURE) , LLFolderType::FT_TEXTURE); - declare(LLFolderType::lookup(LLFolderType::FT_SOUND) , LLFolderType::FT_SOUND); - declare(LLFolderType::lookup(LLFolderType::FT_CALLINGCARD) , LLFolderType::FT_CALLINGCARD); - declare(LLFolderType::lookup(LLFolderType::FT_LANDMARK) , LLFolderType::FT_LANDMARK); - declare(LLFolderType::lookup(LLFolderType::FT_CLOTHING) , LLFolderType::FT_CLOTHING); - declare(LLFolderType::lookup(LLFolderType::FT_OBJECT) , LLFolderType::FT_OBJECT); - declare(LLFolderType::lookup(LLFolderType::FT_NOTECARD) , LLFolderType::FT_NOTECARD); - declare(LLFolderType::lookup(LLFolderType::FT_ROOT_INVENTORY) , LLFolderType::FT_ROOT_INVENTORY); - declare(LLFolderType::lookup(LLFolderType::FT_LSL_TEXT) , LLFolderType::FT_LSL_TEXT); - declare(LLFolderType::lookup(LLFolderType::FT_BODYPART) , LLFolderType::FT_BODYPART); - declare(LLFolderType::lookup(LLFolderType::FT_TRASH) , LLFolderType::FT_TRASH); - declare(LLFolderType::lookup(LLFolderType::FT_SNAPSHOT_CATEGORY), LLFolderType::FT_SNAPSHOT_CATEGORY); - declare(LLFolderType::lookup(LLFolderType::FT_LOST_AND_FOUND) , LLFolderType::FT_LOST_AND_FOUND); - declare(LLFolderType::lookup(LLFolderType::FT_ANIMATION) , LLFolderType::FT_ANIMATION); - declare(LLFolderType::lookup(LLFolderType::FT_GESTURE) , LLFolderType::FT_GESTURE); - declare(LLFolderType::lookup(LLFolderType::FT_FAVORITE) , LLFolderType::FT_FAVORITE); - declare(LLFolderType::lookup(LLFolderType::FT_ENSEMBLE_START) , LLFolderType::FT_ENSEMBLE_START); - declare(LLFolderType::lookup(LLFolderType::FT_ENSEMBLE_END) , LLFolderType::FT_ENSEMBLE_END); - declare(LLFolderType::lookup(LLFolderType::FT_CURRENT_OUTFIT) , LLFolderType::FT_CURRENT_OUTFIT); - declare(LLFolderType::lookup(LLFolderType::FT_OUTFIT) , LLFolderType::FT_OUTFIT); - declare(LLFolderType::lookup(LLFolderType::FT_MY_OUTFITS) , LLFolderType::FT_MY_OUTFITS); - declare(LLFolderType::lookup(LLFolderType::FT_MESH ) , LLFolderType::FT_MESH ); - declare(LLFolderType::lookup(LLFolderType::FT_INBOX) , LLFolderType::FT_INBOX); - declare(LLFolderType::lookup(LLFolderType::FT_OUTBOX) , LLFolderType::FT_OUTBOX); - declare(LLFolderType::lookup(LLFolderType::FT_BASIC_ROOT) , LLFolderType::FT_BASIC_ROOT); - declare(LLFolderType::lookup(LLFolderType::FT_SETTINGS) , LLFolderType::FT_SETTINGS); - declare(LLFolderType::lookup(LLFolderType::FT_MATERIAL) , LLFolderType::FT_MATERIAL); - declare(LLFolderType::lookup(LLFolderType::FT_MARKETPLACE_LISTINGS) , LLFolderType::FT_MARKETPLACE_LISTINGS); - declare(LLFolderType::lookup(LLFolderType::FT_MARKETPLACE_STOCK), LLFolderType::FT_MARKETPLACE_STOCK); - declare(LLFolderType::lookup(LLFolderType::FT_MARKETPLACE_VERSION), LLFolderType::FT_MARKETPLACE_VERSION); - } -} +/* + * @file llinventorypanel.cpp + * @brief Implementation of the inventory panel and associated stuff. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llinventorypanel.h" + +#include // for std::pair<> + +#include "llagent.h" +#include "llagentwearables.h" +#include "llappearancemgr.h" +#include "llavataractions.h" +#include "llclipboard.h" +#include "llfloaterreg.h" +#include "llfloatersidepanelcontainer.h" +#include "llfolderview.h" +#include "llfolderviewitem.h" +#include "llfloaterimcontainer.h" +#include "llimview.h" +#include "llinspecttexture.h" +#include "llinventorybridge.h" +#include "llinventoryfunctions.h" +#include "llinventorymodelbackgroundfetch.h" +#include "llnotificationsutil.h" +#include "llpanelmaininventory.h" +#include "llpreview.h" +#include "llsidepanelinventory.h" +#include "llstartup.h" +#include "lltrans.h" +#include "llviewerassettype.h" +#include "llviewerattachmenu.h" +#include "llviewerfoldertype.h" +#include "llvoavatarself.h" + +class LLInventoryRecentItemsPanel; +class LLAssetFilteredInventoryPanel; + +static LLDefaultChildRegistry::Register r("inventory_panel"); +static LLDefaultChildRegistry::Register t_recent_inventory_panel("recent_inventory_panel"); +static LLDefaultChildRegistry::Register t_asset_filtered_inv_panel("asset_filtered_inv_panel"); + +const std::string LLInventoryPanel::DEFAULT_SORT_ORDER = std::string("InventorySortOrder"); +const std::string LLInventoryPanel::RECENTITEMS_SORT_ORDER = std::string("RecentItemsSortOrder"); +const std::string LLInventoryPanel::INHERIT_SORT_ORDER = std::string(""); +static const LLInventoryFolderViewModelBuilder INVENTORY_BRIDGE_BUILDER; + +// statics +bool LLInventoryPanel::sColorSetInitialized = false; +LLUIColor LLInventoryPanel::sDefaultColor; +LLUIColor LLInventoryPanel::sDefaultHighlightColor; +LLUIColor LLInventoryPanel::sLibraryColor; +LLUIColor LLInventoryPanel::sLinkColor; + +const LLColor4U DEFAULT_WHITE(255, 255, 255); + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLInventoryPanelObserver +// +// Bridge to support knowing when the inventory has changed. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLInventoryPanelObserver : public LLInventoryObserver +{ +public: + LLInventoryPanelObserver(LLInventoryPanel* ip) : mIP(ip) {} + virtual ~LLInventoryPanelObserver() {} + virtual void changed(U32 mask) + { + mIP->modelChanged(mask); + } +protected: + LLInventoryPanel* mIP; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLInvPanelComplObserver +// +// Calls specified callback when all specified items become complete. +// +// Usage: +// observer = new LLInvPanelComplObserver(boost::bind(onComplete)); +// inventory->addObserver(observer); +// observer->reset(); // (optional) +// observer->watchItem(incomplete_item1_id); +// observer->watchItem(incomplete_item2_id); +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLInvPanelComplObserver : public LLInventoryCompletionObserver +{ +public: + typedef boost::function callback_t; + + LLInvPanelComplObserver(callback_t cb) + : mCallback(cb) + { + } + + void reset(); + +private: + /*virtual*/ void done(); + + /// Called when all the items are complete. + callback_t mCallback; +}; + +void LLInvPanelComplObserver::reset() +{ + mIncomplete.clear(); + mComplete.clear(); +} + +void LLInvPanelComplObserver::done() +{ + mCallback(); +} + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLInventoryPanel +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +LLInventoryPanel::LLInventoryPanel(const LLInventoryPanel::Params& p) : + LLPanel(p), + mInventoryObserver(NULL), + mCompletionObserver(NULL), + mScroller(NULL), + mSortOrderSetting(p.sort_order_setting), + mInventory(p.inventory), //inventory("", &gInventory) + mAcceptsDragAndDrop(p.accepts_drag_and_drop), + mAllowMultiSelect(p.allow_multi_select), + mAllowDrag(p.allow_drag), + mShowItemLinkOverlays(p.show_item_link_overlays), + mShowEmptyMessage(p.show_empty_message), + mSuppressFolderMenu(p.suppress_folder_menu), + mSuppressOpenItemAction(false), + mBuildViewsOnInit(p.preinitialize_views), + mViewsInitialized(VIEWS_UNINITIALIZED), + mInvFVBridgeBuilder(NULL), + mInventoryViewModel(p.name), + mGroupedItemBridge(new LLFolderViewGroupedItemBridge), + mFocusSelection(false), + mBuildChildrenViews(true), + mRootInited(false) +{ + mInvFVBridgeBuilder = &INVENTORY_BRIDGE_BUILDER; + + if (!sColorSetInitialized) + { + sDefaultColor = LLUIColorTable::instance().getColor("InventoryItemColor", DEFAULT_WHITE); + sDefaultHighlightColor = LLUIColorTable::instance().getColor("MenuItemHighlightFgColor", DEFAULT_WHITE); + sLibraryColor = LLUIColorTable::instance().getColor("InventoryItemLibraryColor", DEFAULT_WHITE); + sLinkColor = LLUIColorTable::instance().getColor("InventoryItemLinkColor", DEFAULT_WHITE); + sColorSetInitialized = true; + } + + // context menu callbacks + mCommitCallbackRegistrar.add("Inventory.DoToSelected", boost::bind(&LLInventoryPanel::doToSelected, this, _2)); + mCommitCallbackRegistrar.add("Inventory.EmptyTrash", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyTrash", LLFolderType::FT_TRASH)); + mCommitCallbackRegistrar.add("Inventory.EmptyLostAndFound", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyLostAndFound", LLFolderType::FT_LOST_AND_FOUND)); + mCommitCallbackRegistrar.add("Inventory.DoCreate", boost::bind(&LLInventoryPanel::doCreate, this, _2)); + mCommitCallbackRegistrar.add("Inventory.AttachObject", boost::bind(&LLInventoryPanel::attachObject, this, _2)); + mCommitCallbackRegistrar.add("Inventory.BeginIMSession", boost::bind(&LLInventoryPanel::beginIMSession, this)); + mCommitCallbackRegistrar.add("Inventory.Share", boost::bind(&LLAvatarActions::shareWithAvatars, this)); + mCommitCallbackRegistrar.add("Inventory.FileUploadLocation", boost::bind(&LLInventoryPanel::fileUploadLocation, this, _2)); + mCommitCallbackRegistrar.add("Inventory.OpenNewFolderWindow", boost::bind(&LLInventoryPanel::openSingleViewInventory, this, LLUUID())); +} + +LLFolderView * LLInventoryPanel::createFolderRoot(LLUUID root_id ) +{ + LLFolderView::Params p(mParams.folder_view); + p.name = getName(); + p.title = getLabel(); + p.rect = LLRect(0, 0, getRect().getWidth(), 0); + p.parent_panel = this; + p.tool_tip = p.name; + p.listener = mInvFVBridgeBuilder->createBridge( LLAssetType::AT_CATEGORY, + LLAssetType::AT_CATEGORY, + LLInventoryType::IT_CATEGORY, + this, + &mInventoryViewModel, + NULL, + root_id); + p.view_model = &mInventoryViewModel; + p.grouped_item_model = mGroupedItemBridge; + p.use_label_suffix = mParams.use_label_suffix; + p.allow_multiselect = mAllowMultiSelect; + p.allow_drag = mAllowDrag; + p.show_empty_message = mShowEmptyMessage; + p.suppress_folder_menu = mSuppressFolderMenu; + p.show_item_link_overlays = mShowItemLinkOverlays; + p.root = NULL; + p.allow_drop = mParams.allow_drop_on_root; + p.options_menu = "menu_inventory.xml"; + + LLFolderView* fv = LLUICtrlFactory::create(p); + fv->setCallbackRegistrar(&mCommitCallbackRegistrar); + fv->setEnableRegistrar(&mEnableCallbackRegistrar); + + return fv; +} + +void LLInventoryPanel::clearFolderRoot() +{ + gIdleCallbacks.deleteFunction(idle, this); + gIdleCallbacks.deleteFunction(onIdle, this); + + if (mInventoryObserver) + { + mInventory->removeObserver(mInventoryObserver); + delete mInventoryObserver; + mInventoryObserver = NULL; + } + if (mCompletionObserver) + { + mInventory->removeObserver(mCompletionObserver); + delete mCompletionObserver; + mCompletionObserver = NULL; + } + + if (mScroller) + { + removeChild(mScroller); + delete mScroller; + mScroller = NULL; + } +} + +void LLInventoryPanel::initFromParams(const LLInventoryPanel::Params& params) +{ + // save off copy of params + mParams = params; + + initFolderRoot(); + + // Initialize base class params. + LLPanel::initFromParams(mParams); +} + +LLInventoryPanel::~LLInventoryPanel() +{ + U32 sort_order = getFolderViewModel()->getSorter().getSortOrder(); + if (mSortOrderSetting != INHERIT_SORT_ORDER) + { + gSavedSettings.setU32(mSortOrderSetting, sort_order); + } + + clearFolderRoot(); +} + +void LLInventoryPanel::initFolderRoot() +{ + // Clear up the root view + // Note: This needs to be done *before* we build the new folder view + LLUUID root_id = getRootFolderID(); + if (mFolderRoot.get()) + { + removeItemID(root_id); + mFolderRoot.get()->destroyView(); + } + + mCommitCallbackRegistrar.pushScope(); // registered as a widget; need to push callback scope ourselves + { + // Determine the root folder in case specified, and + // build the views starting with that folder. + LLFolderView* folder_view = createFolderRoot(root_id); + mFolderRoot = folder_view->getHandle(); + mRootInited = true; + + addItemID(root_id, mFolderRoot.get()); + } + mCommitCallbackRegistrar.popScope(); + mFolderRoot.get()->setCallbackRegistrar(&mCommitCallbackRegistrar); + mFolderRoot.get()->setEnableRegistrar(&mEnableCallbackRegistrar); + + // Scroller + LLRect scroller_view_rect = getRect(); + scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom); + LLScrollContainer::Params scroller_params(mParams.scroll()); + scroller_params.rect(scroller_view_rect); + mScroller = LLUICtrlFactory::create(scroller_params); + addChild(mScroller); + mScroller->addChild(mFolderRoot.get()); + mFolderRoot.get()->setScrollContainer(mScroller); + mFolderRoot.get()->setFollowsAll(); + mFolderRoot.get()->addChild(mFolderRoot.get()->mStatusTextBox); + + if (mSelectionCallback) + { + mFolderRoot.get()->setSelectCallback(mSelectionCallback); + } + + // Set up the callbacks from the inventory we're viewing, and then build everything. + mInventoryObserver = new LLInventoryPanelObserver(this); + mInventory->addObserver(mInventoryObserver); + + mCompletionObserver = new LLInvPanelComplObserver(boost::bind(&LLInventoryPanel::onItemsCompletion, this)); + mInventory->addObserver(mCompletionObserver); + + if (mBuildViewsOnInit) + { + initializeViewBuilding(); + } + + if (mSortOrderSetting != INHERIT_SORT_ORDER) + { + setSortOrder(gSavedSettings.getU32(mSortOrderSetting)); + } + else + { + setSortOrder(gSavedSettings.getU32(DEFAULT_SORT_ORDER)); + } + + // hide inbox + if (!gSavedSettings.getBOOL("InventoryOutboxMakeVisible")) + { + getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << LLFolderType::FT_INBOX)); + } + // hide marketplace listing box, unless we are a marketplace panel + if (!gSavedSettings.getBOOL("InventoryOutboxMakeVisible") && !mParams.use_marketplace_folders) + { + getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << LLFolderType::FT_MARKETPLACE_LISTINGS)); + } + + // set the filter for the empty folder if the debug setting is on + if (gSavedSettings.getBOOL("DebugHideEmptySystemFolders")) + { + getFilter().setFilterEmptySystemFolders(); + } + + // keep track of the clipboard state so that we avoid filtering too much + mClipboardState = LLClipboard::instance().getGeneration(); +} + +void LLInventoryPanel::initializeViewBuilding() +{ + if (mViewsInitialized == VIEWS_UNINITIALIZED) + { + LL_DEBUGS("Inventory") << "Setting views for " << getName() << " to initialize" << LL_ENDL; + // Build view of inventory if we need default full hierarchy and inventory is ready, otherwise do in onIdle. + // Initializing views takes a while so always do it onIdle if viewer already loaded. + if (mInventory->isInventoryUsable() + && LLStartUp::getStartupState() <= STATE_WEARABLES_WAIT) + { + // Usually this happens on login, so we have less time constraits, but too long and we can cause a disconnect + const F64 max_time = 20.f; + initializeViews(max_time); + } + else + { + mViewsInitialized = VIEWS_INITIALIZING; + gIdleCallbacks.addFunction(onIdle, (void*)this); + } + } +} + +/*virtual*/ +void LLInventoryPanel::onVisibilityChange(bool new_visibility) +{ + if (new_visibility && mViewsInitialized == VIEWS_UNINITIALIZED) + { + // first call can be from tab initialization + if (gFloaterView->getParentFloater(this) != NULL) + { + initializeViewBuilding(); + } + } + LLPanel::onVisibilityChange(new_visibility); +} + +void LLInventoryPanel::draw() +{ + // Select the desired item (in case it wasn't loaded when the selection was requested) + updateSelection(); + + LLPanel::draw(); +} + +const LLInventoryFilter& LLInventoryPanel::getFilter() const +{ + return getFolderViewModel()->getFilter(); +} + +LLInventoryFilter& LLInventoryPanel::getFilter() +{ + return getFolderViewModel()->getFilter(); +} + +void LLInventoryPanel::setFilterTypes(U64 types, LLInventoryFilter::EFilterType filter_type) +{ + if (filter_type == LLInventoryFilter::FILTERTYPE_OBJECT) + { + getFilter().setFilterObjectTypes(types); + } + if (filter_type == LLInventoryFilter::FILTERTYPE_CATEGORY) + getFilter().setFilterCategoryTypes(types); +} + +void LLInventoryPanel::setFilterWorn() +{ + getFilter().setFilterWorn(); +} + +U32 LLInventoryPanel::getFilterObjectTypes() const +{ + return getFilter().getFilterObjectTypes(); +} + +U32 LLInventoryPanel::getFilterPermMask() const +{ + return getFilter().getFilterPermissions(); +} + + +void LLInventoryPanel::setFilterPermMask(PermissionMask filter_perm_mask) +{ + getFilter().setFilterPermissions(filter_perm_mask); +} + +void LLInventoryPanel::setFilterWearableTypes(U64 types) +{ + getFilter().setFilterWearableTypes(types); +} + +void LLInventoryPanel::setFilterSettingsTypes(U64 filter) +{ + getFilter().setFilterSettingsTypes(filter); +} + +void LLInventoryPanel::setFilterSubString(const std::string& string) +{ + getFilter().setFilterSubString(string); +} + +const std::string LLInventoryPanel::getFilterSubString() +{ + return getFilter().getFilterSubString(); +} + +void LLInventoryPanel::setSortOrder(U32 order) +{ + LLInventorySort sorter(order); + if (order != getFolderViewModel()->getSorter().getSortOrder()) + { + getFolderViewModel()->setSorter(sorter); + mFolderRoot.get()->arrangeAll(); + // try to keep selection onscreen, even if it wasn't to start with + mFolderRoot.get()->scrollToShowSelection(); + } +} + +U32 LLInventoryPanel::getSortOrder() const +{ + return getFolderViewModel()->getSorter().getSortOrder(); +} + +void LLInventoryPanel::setSinceLogoff(bool sl) +{ + getFilter().setDateRangeLastLogoff(sl); +} + +void LLInventoryPanel::setHoursAgo(U32 hours) +{ + getFilter().setHoursAgo(hours); +} + +void LLInventoryPanel::setDateSearchDirection(U32 direction) +{ + getFilter().setDateSearchDirection(direction); +} + +void LLInventoryPanel::setFilterLinks(U64 filter_links) +{ + getFilter().setFilterLinks(filter_links); +} + +void LLInventoryPanel::setSearchType(LLInventoryFilter::ESearchType type) +{ + getFilter().setSearchType(type); +} + +LLInventoryFilter::ESearchType LLInventoryPanel::getSearchType() +{ + return getFilter().getSearchType(); +} + +void LLInventoryPanel::setShowFolderState(LLInventoryFilter::EFolderShow show) +{ + getFilter().setShowFolderState(show); +} + +LLInventoryFilter::EFolderShow LLInventoryPanel::getShowFolderState() +{ + return getFilter().getShowFolderState(); +} + +void LLInventoryPanel::itemChanged(const LLUUID& item_id, U32 mask, const LLInventoryObject* model_item) +{ + LLFolderViewItem* view_item = getItemByID(item_id); + LLFolderViewModelItemInventory* viewmodel_item = + static_cast(view_item ? view_item->getViewModelItem() : NULL); + + // LLFolderViewFolder is derived from LLFolderViewItem so dynamic_cast from item + // to folder is the fast way to get a folder without searching through folders tree. + LLFolderViewFolder* view_folder = NULL; + + // Check requires as this item might have already been deleted + // as a child of its deleted parent. + if (model_item && view_item) + { + view_folder = dynamic_cast(view_item); + } + + // if folder is not fully initialized (likely due to delayed load on idle) + // and we are not rebuilding, try updating children + if (view_folder + && !view_folder->areChildrenInited() + && ( (mask & LLInventoryObserver::REBUILD) == 0)) + { + LLInventoryObject const* objectp = mInventory->getObject(item_id); + if (objectp) + { + view_item = buildNewViews(item_id, objectp, view_item, BUILD_ONE_FOLDER); + } + } + + ////////////////////////////// + // LABEL Operation + // Empty out the display name for relabel. + if (mask & LLInventoryObserver::LABEL) + { + if (view_item) + { + // Request refresh on this item (also flags for filtering) + LLInvFVBridge* bridge = (LLInvFVBridge*)view_item->getViewModelItem(); + if(bridge) + { + // Clear the display name first, so it gets properly re-built during refresh() + bridge->clearDisplayName(); + + view_item->refresh(); + } + LLFolderViewFolder* parent = view_item->getParentFolder(); + if(parent) + { + parent->getViewModelItem()->dirtyDescendantsFilter(); + } + } + } + + ////////////////////////////// + // REBUILD Operation + // Destroy and regenerate the UI. + if (mask & LLInventoryObserver::REBUILD) + { + if (model_item && view_item && viewmodel_item) + { + const LLUUID& idp = viewmodel_item->getUUID(); + view_item->destroyView(); + removeItemID(idp); + } + + LLInventoryObject const* objectp = mInventory->getObject(item_id); + if (objectp) + { + // providing NULL directly avoids unnessesary getItemByID calls + view_item = buildNewViews(item_id, objectp, NULL, BUILD_ONE_FOLDER); + } + else + { + view_item = NULL; + } + + viewmodel_item = + static_cast(view_item ? view_item->getViewModelItem() : NULL); + view_folder = dynamic_cast(view_item); + } + + ////////////////////////////// + // INTERNAL Operation + // This could be anything. For now, just refresh the item. + if (mask & LLInventoryObserver::INTERNAL) + { + if (view_item) + { + view_item->refresh(); + } + } + + ////////////////////////////// + // SORT Operation + // Sort the folder. + if (mask & LLInventoryObserver::SORT) + { + if (view_folder) + { + view_folder->getViewModelItem()->requestSort(); + } + } + + // We don't typically care which of these masks the item is actually flagged with, since the masks + // may not be accurate (e.g. in the main inventory panel, I move an item from My Inventory into + // Landmarks; this is a STRUCTURE change for that panel but is an ADD change for the Landmarks + // panel). What's relevant is that the item and UI are probably out of sync and thus need to be + // resynchronized. + if (mask & (LLInventoryObserver::STRUCTURE | + LLInventoryObserver::ADD | + LLInventoryObserver::REMOVE)) + { + ////////////////////////////// + // ADD Operation + // Item exists in memory but a UI element hasn't been created for it. + if (model_item && !view_item) + { + // Add the UI element for this item. + LLInventoryObject const* objectp = mInventory->getObject(item_id); + if (objectp) + { + // providing NULL directly avoids unnessesary getItemByID calls + buildNewViews(item_id, objectp, NULL, BUILD_ONE_FOLDER); + } + + // Select any newly created object that has the auto rename at top of folder root set. + if(mFolderRoot.get()->getRoot()->needsAutoRename()) + { + setSelection(item_id, false); + } + updateFolderLabel(model_item->getParentUUID()); + } + + ////////////////////////////// + // STRUCTURE Operation + // This item already exists in both memory and UI. It was probably reparented. + else if (model_item && view_item) + { + LLFolderViewFolder* old_parent = view_item->getParentFolder(); + // Don't process the item if it is the root + if (old_parent) + { + LLFolderViewModelItemInventory* viewmodel_folder = static_cast(old_parent->getViewModelItem()); + LLFolderViewFolder* new_parent = (LLFolderViewFolder*)getItemByID(model_item->getParentUUID()); + // Item has been moved. + if (old_parent != new_parent) + { + if (new_parent != NULL) + { + // Item is to be moved and we found its new parent in the panel's directory, so move the item's UI. + view_item->addToFolder(new_parent); + addItemID(viewmodel_item->getUUID(), view_item); + if (mInventory) + { + const LLUUID trash_id = mInventory->findCategoryUUIDForType(LLFolderType::FT_TRASH); + if (trash_id != model_item->getParentUUID() && (mask & LLInventoryObserver::INTERNAL) && new_parent->isOpen()) + { + setSelection(item_id, false); + } + } + updateFolderLabel(model_item->getParentUUID()); + } + else + { + // Remove the item ID before destroying the view because the view-model-item gets + // destroyed when the view is destroyed + removeItemID(viewmodel_item->getUUID()); + + // Item is to be moved outside the panel's directory (e.g. moved to trash for a panel that + // doesn't include trash). Just remove the item's UI. + view_item->destroyView(); + } + if(viewmodel_folder) + { + updateFolderLabel(viewmodel_folder->getUUID()); + } + old_parent->getViewModelItem()->dirtyDescendantsFilter(); + } + } + } + + ////////////////////////////// + // REMOVE Operation + // This item has been removed from memory, but its associated UI element still exists. + else if (!model_item && view_item && viewmodel_item) + { + // Remove the item's UI. + LLFolderViewFolder* parent = view_item->getParentFolder(); + removeItemID(viewmodel_item->getUUID()); + view_item->destroyView(); + if(parent) + { + parent->getViewModelItem()->dirtyDescendantsFilter(); + LLFolderViewModelItemInventory* viewmodel_folder = static_cast(parent->getViewModelItem()); + if(viewmodel_folder) + { + updateFolderLabel(viewmodel_folder->getUUID()); + } + } + } + } +} + +// Called when something changed in the global model (new item, item coming through the wire, rename, move, etc...) (CHUI-849) +void LLInventoryPanel::modelChanged(U32 mask) +{ + LL_PROFILE_ZONE_SCOPED; + + if (mViewsInitialized != VIEWS_INITIALIZED) return; + + const LLInventoryModel* model = getModel(); + if (!model) return; + + const LLInventoryModel::changed_items_t& changed_items = model->getChangedIDs(); + if (changed_items.empty()) return; + + for (LLInventoryModel::changed_items_t::const_iterator items_iter = changed_items.begin(); + items_iter != changed_items.end(); + ++items_iter) + { + const LLUUID& item_id = (*items_iter); + const LLInventoryObject* model_item = model->getObject(item_id); + itemChanged(item_id, mask, model_item); + } +} + +LLUUID LLInventoryPanel::getRootFolderID() +{ + LLUUID root_id; + if (mFolderRoot.get() && mFolderRoot.get()->getViewModelItem()) + { + root_id = static_cast(mFolderRoot.get()->getViewModelItem())->getUUID(); + } + else + { + if (mParams.start_folder.id.isChosen()) + { + root_id = mParams.start_folder.id; + } + else + { + const LLFolderType::EType preferred_type = mParams.start_folder.type.isChosen() + ? mParams.start_folder.type + : LLViewerFolderType::lookupTypeFromNewCategoryName(mParams.start_folder.name); + + if ("LIBRARY" == mParams.start_folder.name()) + { + root_id = gInventory.getLibraryRootFolderID(); + } + else if (preferred_type != LLFolderType::FT_NONE) + { + LLStringExplicit label(mParams.start_folder.name()); + setLabel(label); + + root_id = gInventory.findCategoryUUIDForType(preferred_type); + if (root_id.isNull()) + { + LL_WARNS() << "Could not find folder of type " << preferred_type << LL_ENDL; + root_id.generateNewID(); + } + } + } + } + return root_id; +} + +// static +void LLInventoryPanel::onIdle(void *userdata) +{ + if (!gInventory.isInventoryUsable()) + return; + + LLInventoryPanel *self = (LLInventoryPanel*)userdata; + if (self->mViewsInitialized <= VIEWS_INITIALIZING) + { + const F64 max_time = 0.001f; // 1 ms, in this case we need only root folders + self->initializeViews(max_time); // Shedules LLInventoryPanel::idle() + } + if (self->mViewsInitialized >= VIEWS_BUILDING) + { + gIdleCallbacks.deleteFunction(onIdle, (void*)self); + } +} + +struct DirtyFilterFunctor : public LLFolderViewFunctor +{ + /*virtual*/ void doFolder(LLFolderViewFolder* folder) + { + folder->getViewModelItem()->dirtyFilter(); + } + /*virtual*/ void doItem(LLFolderViewItem* item) + { + item->getViewModelItem()->dirtyFilter(); + } +}; + +void LLInventoryPanel::idle(void* user_data) +{ + LLInventoryPanel* panel = (LLInventoryPanel*)user_data; + // Nudge the filter if the clipboard state changed + if (panel->mClipboardState != LLClipboard::instance().getGeneration()) + { + panel->mClipboardState = LLClipboard::instance().getGeneration(); + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + LLFolderViewFolder* trash_folder = panel->getFolderByID(trash_id); + if (trash_folder) + { + DirtyFilterFunctor dirtyFilterFunctor; + trash_folder->applyFunctorToChildren(dirtyFilterFunctor); + } + + } + + bool in_visible_chain = panel->isInVisibleChain(); + + if (!panel->mBuildViewsQueue.empty()) + { + const F64 max_time = in_visible_chain ? 0.006f : 0.001f; // 6 ms + F64 curent_time = LLTimer::getTotalSeconds(); + panel->mBuildViewsEndTime = curent_time + max_time; + + // things added last are closer to root thus of higher priority + std::deque priority_list; + priority_list.swap(panel->mBuildViewsQueue); + + while (curent_time < panel->mBuildViewsEndTime + && !priority_list.empty()) + { + LLUUID item_id = priority_list.back(); + priority_list.pop_back(); + + LLInventoryObject const* objectp = panel->mInventory->getObject(item_id); + if (objectp && panel->typedViewsFilter(item_id, objectp)) + { + LLFolderViewItem* folder_view_item = panel->getItemByID(item_id); + if (!folder_view_item || !folder_view_item->areChildrenInited()) + { + const LLUUID &parent_id = objectp->getParentUUID(); + LLFolderViewFolder* parent_folder = (LLFolderViewFolder*)panel->getItemByID(parent_id); + panel->buildViewsTree(item_id, parent_id, objectp, folder_view_item, parent_folder, BUILD_TIMELIMIT); + } + } + curent_time = LLTimer::getTotalSeconds(); + } + while (!priority_list.empty()) + { + // items in priority_list are of higher priority + panel->mBuildViewsQueue.push_back(priority_list.front()); + priority_list.pop_front(); + } + if (panel->mBuildViewsQueue.empty()) + { + panel->mViewsInitialized = VIEWS_INITIALIZED; + } + } + + // Take into account the fact that the root folder might be invalidated + if (panel->mFolderRoot.get()) + { + panel->mFolderRoot.get()->update(); + // while dragging, update selection rendering to reflect single/multi drag status + if (LLToolDragAndDrop::getInstance()->hasMouseCapture()) + { + EAcceptance last_accept = LLToolDragAndDrop::getInstance()->getLastAccept(); + if (last_accept == ACCEPT_YES_SINGLE || last_accept == ACCEPT_YES_COPY_SINGLE) + { + panel->mFolderRoot.get()->setShowSingleSelection(true); + } + else + { + panel->mFolderRoot.get()->setShowSingleSelection(false); + } + } + else + { + panel->mFolderRoot.get()->setShowSingleSelection(false); + } + } + else + { + LL_WARNS() << "Inventory : Deleted folder root detected on panel" << LL_ENDL; + panel->clearFolderRoot(); + } +} + + +void LLInventoryPanel::initializeViews(F64 max_time) +{ + if (!gInventory.isInventoryUsable()) return; + if (!mRootInited) return; + + mViewsInitialized = VIEWS_BUILDING; + + F64 curent_time = LLTimer::getTotalSeconds(); + mBuildViewsEndTime = curent_time + max_time; + + // init everything + LLUUID root_id = getRootFolderID(); + if (root_id.notNull()) + { + buildNewViews(getRootFolderID()); + } + else + { + // Default case: always add "My Inventory" root first, "Library" root second + // If we run out of time, this still should create root folders + buildNewViews(gInventory.getRootFolderID()); // My Inventory + buildNewViews(gInventory.getLibraryRootFolderID()); // Library + } + + if (mBuildViewsQueue.empty()) + { + mViewsInitialized = VIEWS_INITIALIZED; + } + + gIdleCallbacks.addFunction(idle, this); + + if(mParams.open_first_folder) + { + openStartFolderOrMyInventory(); + } + + // Special case for new user login + if (gAgent.isFirstLogin()) + { + // Auto open the user's library + LLFolderViewFolder* lib_folder = getFolderByID(gInventory.getLibraryRootFolderID()); + if (lib_folder) + { + lib_folder->setOpen(true); + } + + // Auto close the user's my inventory folder + LLFolderViewFolder* my_inv_folder = getFolderByID(gInventory.getRootFolderID()); + if (my_inv_folder) + { + my_inv_folder->setOpenArrangeRecursively(false, LLFolderViewFolder::RECURSE_DOWN); + } + } +} + + +LLFolderViewFolder * LLInventoryPanel::createFolderViewFolder(LLInvFVBridge * bridge, bool allow_drop) +{ + LLFolderViewFolder::Params params(mParams.folder); + + params.name = bridge->getDisplayName(); + params.root = mFolderRoot.get(); + params.listener = bridge; + params.tool_tip = params.name; + params.allow_drop = allow_drop; + + params.font_color = (bridge->isLibraryItem() ? sLibraryColor : sDefaultColor); + params.font_highlight_color = (bridge->isLibraryItem() ? sLibraryColor : sDefaultHighlightColor); + + return LLUICtrlFactory::create(params); +} + +LLFolderViewItem * LLInventoryPanel::createFolderViewItem(LLInvFVBridge * bridge) +{ + LLFolderViewItem::Params params(mParams.item); + + params.name = bridge->getDisplayName(); + params.creation_date = bridge->getCreationDate(); + params.root = mFolderRoot.get(); + params.listener = bridge; + params.rect = LLRect (0, 0, 0, 0); + params.tool_tip = params.name; + + params.font_color = (bridge->isLibraryItem() ? sLibraryColor : sDefaultColor); + params.font_highlight_color = (bridge->isLibraryItem() ? sLibraryColor : sDefaultHighlightColor); + + return LLUICtrlFactory::create(params); +} + +LLFolderViewItem* LLInventoryPanel::buildNewViews(const LLUUID& id) +{ + LLInventoryObject const* objectp = mInventory->getObject(id); + return buildNewViews(id, objectp); +} + +LLFolderViewItem* LLInventoryPanel::buildNewViews(const LLUUID& id, LLInventoryObject const* objectp) +{ + if (!objectp) + { + return NULL; + } + if (!typedViewsFilter(id, objectp)) + { + // if certain types are not allowed permanently, no reason to create views + return NULL; + } + + const LLUUID &parent_id = objectp->getParentUUID(); + LLFolderViewItem* folder_view_item = getItemByID(id); + LLFolderViewFolder* parent_folder = (LLFolderViewFolder*)getItemByID(parent_id); + + return buildViewsTree(id, parent_id, objectp, folder_view_item, parent_folder, BUILD_TIMELIMIT); +} + +LLFolderViewItem* LLInventoryPanel::buildNewViews(const LLUUID& id, + LLInventoryObject const* objectp, + LLFolderViewItem *folder_view_item, + const EBuildModes &mode) +{ + if (!objectp) + { + return NULL; + } + if (!typedViewsFilter(id, objectp)) + { + // if certain types are not allowed permanently, no reason to create views + return NULL; + } + + const LLUUID &parent_id = objectp->getParentUUID(); + LLFolderViewFolder* parent_folder = (LLFolderViewFolder*)getItemByID(parent_id); + + return buildViewsTree(id, parent_id, objectp, folder_view_item, parent_folder, mode); +} + +LLFolderViewItem* LLInventoryPanel::buildViewsTree(const LLUUID& id, + const LLUUID& parent_id, + LLInventoryObject const* objectp, + LLFolderViewItem *folder_view_item, + LLFolderViewFolder *parent_folder, + const EBuildModes &mode, + S32 depth) +{ + depth++; + + // Force the creation of an extra root level folder item if required by the inventory panel (default is "false") + bool allow_drop = true; + bool create_root = false; + if (mParams.show_root_folder) + { + LLUUID root_id = getRootFolderID(); + if (root_id == id) + { + // We insert an extra level that's seen by the UI but has no influence on the model + parent_folder = dynamic_cast(folder_view_item); + folder_view_item = NULL; + allow_drop = mParams.allow_drop_on_root; + create_root = true; + } + } + + if (!folder_view_item && parent_folder) + { + if (objectp->getType() <= LLAssetType::AT_NONE) + { + LL_WARNS() << "LLInventoryPanel::buildViewsTree called with invalid objectp->mType : " + << ((S32)objectp->getType()) << " name " << objectp->getName() << " UUID " << objectp->getUUID() + << LL_ENDL; + return NULL; + } + + if (objectp->getType() >= LLAssetType::AT_COUNT) + { + // Example: Happens when we add assets of new, not yet supported type to library + LL_DEBUGS("Inventory") << "LLInventoryPanel::buildViewsTree called with unknown objectp->mType : " + << ((S32) objectp->getType()) << " name " << objectp->getName() << " UUID " << objectp->getUUID() + << LL_ENDL; + + LLInventoryItem* item = (LLInventoryItem*)objectp; + if (item) + { + LLInvFVBridge* new_listener = mInvFVBridgeBuilder->createBridge(LLAssetType::AT_UNKNOWN, + LLAssetType::AT_UNKNOWN, + LLInventoryType::IT_UNKNOWN, + this, + &mInventoryViewModel, + mFolderRoot.get(), + item->getUUID(), + item->getFlags()); + + if (new_listener) + { + folder_view_item = createFolderViewItem(new_listener); + } + } + } + + if ((objectp->getType() == LLAssetType::AT_CATEGORY) && + (objectp->getActualType() != LLAssetType::AT_LINK_FOLDER)) + { + LLInvFVBridge* new_listener = mInvFVBridgeBuilder->createBridge(LLAssetType::AT_CATEGORY, + (mParams.use_marketplace_folders ? LLAssetType::AT_MARKETPLACE_FOLDER : LLAssetType::AT_CATEGORY), + LLInventoryType::IT_CATEGORY, + this, + &mInventoryViewModel, + mFolderRoot.get(), + objectp->getUUID()); + if (new_listener) + { + folder_view_item = createFolderViewFolder(new_listener,allow_drop); + } + } + else + { + // Build new view for item. + LLInventoryItem* item = (LLInventoryItem*)objectp; + LLInvFVBridge* new_listener = mInvFVBridgeBuilder->createBridge(item->getType(), + item->getActualType(), + item->getInventoryType(), + this, + &mInventoryViewModel, + mFolderRoot.get(), + item->getUUID(), + item->getFlags()); + + if (new_listener) + { + folder_view_item = createFolderViewItem(new_listener); + } + } + + if (folder_view_item) + { + llassert(parent_folder != NULL); + folder_view_item->addToFolder(parent_folder); + addItemID(id, folder_view_item); + // In the case of the root folder been shown, open that folder by default once the widget is created + if (create_root) + { + folder_view_item->setOpen(true); + } + } + } + + bool create_children = folder_view_item && objectp->getType() == LLAssetType::AT_CATEGORY + && (mBuildChildrenViews || depth == 0); + + if (create_children) + { + switch (mode) + { + case BUILD_TIMELIMIT: + { + F64 curent_time = LLTimer::getTotalSeconds(); + // If function is out of time, we want to shedule it into mBuildViewsQueue + // If we have time, no matter how little, create views for all children + // + // This creates children in 'bulk' to make sure folder has either + // 'empty and incomplete' or 'complete' states with nothing in between. + // Folders are marked as mIsFolderComplete == false by default, + // later arrange() will update mIsFolderComplete by child count + if (mBuildViewsEndTime < curent_time) + { + create_children = false; + // run it again for the sake of creating children + if (mBuildChildrenViews || depth == 0) + { + mBuildViewsQueue.push_back(id); + } + } + else + { + create_children = true; + folder_view_item->setChildrenInited(mBuildChildrenViews); + } + break; + } + case BUILD_NO_CHILDREN: + { + create_children = false; + // run it to create children, current caller is only interested in current view + if (mBuildChildrenViews || depth == 0) + { + mBuildViewsQueue.push_back(id); + } + break; + } + case BUILD_ONE_FOLDER: + { + // This view loads chindren, following ones don't + // Note: Might be better idea to do 'depth' instead, + // It also will help to prioritize root folder's content + create_children = true; + folder_view_item->setChildrenInited(true); + break; + } + case BUILD_NO_LIMIT: + default: + { + // keep working till everything exists + create_children = true; + folder_view_item->setChildrenInited(true); + } + } + } + + // If this is a folder, add the children of the folder and recursively add any + // child folders. + if (create_children) + { + LLViewerInventoryCategory::cat_array_t* categories; + LLViewerInventoryItem::item_array_t* items; + mInventory->lockDirectDescendentArrays(id, categories, items); + + // Make sure panel won't lock in a loop over existing items if + // folder is enormous and at least some work gets done + const S32 MIN_ITEMS_PER_CALL = 500; + const S32 starting_item_count = mItemMap.size(); + + LLFolderViewFolder *parentp = dynamic_cast(folder_view_item); + bool done = true; + + if(categories) + { + bool has_folders = parentp->getFoldersCount() > 0; + for (LLViewerInventoryCategory::cat_array_t::const_iterator cat_iter = categories->begin(); + cat_iter != categories->end(); + ++cat_iter) + { + const LLViewerInventoryCategory* cat = (*cat_iter); + if (typedViewsFilter(cat->getUUID(), cat)) + { + if (has_folders) + { + // This can be optimized: we don't need to call getItemByID() + // each time, especially since content is growing, we can just + // iter over copy of mItemMap in some way + LLFolderViewItem* view_itemp = getItemByID(cat->getUUID()); + buildViewsTree(cat->getUUID(), id, cat, view_itemp, parentp, (mode == BUILD_ONE_FOLDER ? BUILD_NO_CHILDREN : mode), depth); + } + else + { + buildViewsTree(cat->getUUID(), id, cat, NULL, parentp, (mode == BUILD_ONE_FOLDER ? BUILD_NO_CHILDREN : mode), depth); + } + } + + if (!mBuildChildrenViews + && mode == BUILD_TIMELIMIT + && MIN_ITEMS_PER_CALL + starting_item_count < mItemMap.size()) + { + // Single folder view, check if we still have time + // + // Todo: make sure this causes no dupplciates, breaks nothing, + // especially filters and arrange + F64 curent_time = LLTimer::getTotalSeconds(); + if (mBuildViewsEndTime < curent_time) + { + mBuildViewsQueue.push_back(id); + done = false; + break; + } + } + } + } + + if(items) + { + for (LLViewerInventoryItem::item_array_t::const_iterator item_iter = items->begin(); + item_iter != items->end(); + ++item_iter) + { + // At the moment we have to build folder's items in bulk and ignore mBuildViewsEndTime + const LLViewerInventoryItem* item = (*item_iter); + if (typedViewsFilter(item->getUUID(), item)) + { + // This can be optimized: we don't need to call getItemByID() + // each time, especially since content is growing, we can just + // iter over copy of mItemMap in some way + LLFolderViewItem* view_itemp = getItemByID(item->getUUID()); + buildViewsTree(item->getUUID(), id, item, view_itemp, parentp, mode, depth); + } + + if (!mBuildChildrenViews + && mode == BUILD_TIMELIMIT + && MIN_ITEMS_PER_CALL + starting_item_count < mItemMap.size()) + { + // Single folder view, check if we still have time + // + // Todo: make sure this causes no dupplciates, breaks nothing, + // especially filters and arrange + F64 curent_time = LLTimer::getTotalSeconds(); + if (mBuildViewsEndTime < curent_time) + { + mBuildViewsQueue.push_back(id); + done = false; + break; + } + } + } + } + + if (!mBuildChildrenViews && done) + { + // flat list is done initializing folder + folder_view_item->setChildrenInited(true); + } + mInventory->unlockDirectDescendentArrays(id); + } + + return folder_view_item; +} + +// bit of a hack to make sure the inventory is open. +void LLInventoryPanel::openStartFolderOrMyInventory() +{ + // Find My Inventory folder and open it up by name + for (LLView *child = mFolderRoot.get()->getFirstChild(); child; child = mFolderRoot.get()->findNextSibling(child)) + { + LLFolderViewFolder *fchild = dynamic_cast(child); + if (fchild + && fchild->getViewModelItem() + && fchild->getViewModelItem()->getName() == "My Inventory") + { + fchild->setOpen(true); + break; + } + } +} + +void LLInventoryPanel::onItemsCompletion() +{ + if (mFolderRoot.get()) mFolderRoot.get()->updateMenu(); +} + +void LLInventoryPanel::openSelected() +{ + LLFolderViewItem* folder_item = mFolderRoot.get()->getCurSelectedItem(); + if(!folder_item) return; + LLInvFVBridge* bridge = (LLInvFVBridge*)folder_item->getViewModelItem(); + if(!bridge) return; + bridge->openItem(); +} + +void LLInventoryPanel::unSelectAll() +{ + mFolderRoot.get()->setSelection(NULL, false, false); +} + + +bool LLInventoryPanel::handleHover(S32 x, S32 y, MASK mask) +{ + bool handled = LLView::handleHover(x, y, mask); + if(handled) + { + // getCursor gets current cursor, setCursor sets next cursor + // check that children didn't set own 'next' cursor + ECursorType cursor = getWindow()->getNextCursor(); + if (LLInventoryModelBackgroundFetch::instance().folderFetchActive() && cursor == UI_CURSOR_ARROW) + { + // replace arrow cursor with arrow and hourglass cursor + getWindow()->setCursor(UI_CURSOR_WORKING); + } + } + else + { + getWindow()->setCursor(UI_CURSOR_ARROW); + } + return true; +} + +bool LLInventoryPanel::handleToolTip(S32 x, S32 y, MASK mask) +{ + if (const LLFolderViewItem* hover_item_p = (!mFolderRoot.isDead()) ? mFolderRoot.get()->getHoveredItem() : nullptr) + { + if (const LLFolderViewModelItemInventory* vm_item_p = static_cast(hover_item_p->getViewModelItem())) + { + LLSD params; + params["inv_type"] = vm_item_p->getInventoryType(); + params["thumbnail_id"] = vm_item_p->getThumbnailUUID(); + params["item_id"] = vm_item_p->getUUID(); + + // tooltip should only show over folder, but screen + // rect includes items under folder as well + LLRect actionable_rect = hover_item_p->calcScreenRect(); + if (hover_item_p->isOpen() && hover_item_p->hasVisibleChildren()) + { + actionable_rect.mBottom = actionable_rect.mTop - hover_item_p->getItemHeight(); + } + + LLToolTipMgr::instance().show(LLToolTip::Params() + .message(hover_item_p->getToolTip()) + .sticky_rect(actionable_rect) + .delay_time(LLView::getTooltipTimeout()) + .create_callback(boost::bind(&LLInspectTextureUtil::createInventoryToolTip, _1)) + .create_params(params)); + return true; + } + } + return LLPanel::handleToolTip(x, y, mask); +} + +bool LLInventoryPanel::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + bool handled = false; + + if (mAcceptsDragAndDrop) + { + handled = LLPanel::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); + + // If folder view is empty the (x, y) point won't be in its rect + // so the handler must be called explicitly. + // but only if was not handled before. See EXT-6746. + if (!handled && mParams.allow_drop_on_root && !mFolderRoot.get()->hasVisibleChildren()) + { + handled = mFolderRoot.get()->handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); + } + + if (handled) + { + mFolderRoot.get()->setDragAndDropThisFrame(); + } + } + + return handled; +} + +void LLInventoryPanel::onFocusLost() +{ + // inventory no longer handles cut/copy/paste/delete + if (LLEditMenuHandler::gEditMenuHandler == mFolderRoot.get()) + { + LLEditMenuHandler::gEditMenuHandler = NULL; + } + + LLPanel::onFocusLost(); +} + +void LLInventoryPanel::onFocusReceived() +{ + // inventory now handles cut/copy/paste/delete + LLEditMenuHandler::gEditMenuHandler = mFolderRoot.get(); + + LLPanel::onFocusReceived(); +} + +void LLInventoryPanel::onFolderOpening(const LLUUID &id) +{ + LLFolderViewItem* folder = getItemByID(id); + if (folder && !folder->areChildrenInited()) + { + // Last item in list will be processed first. + // This might result in dupplicates in list, but it + // isn't critical, views won't be created twice + mBuildViewsQueue.push_back(id); + } +} + +bool LLInventoryPanel::addBadge(LLBadge * badge) +{ + bool badge_added = false; + + if (acceptsBadge()) + { + badge_added = badge->addToView(mFolderRoot.get()); + } + + return badge_added; +} + +void LLInventoryPanel::openAllFolders() +{ + mFolderRoot.get()->setOpenArrangeRecursively(true, LLFolderViewFolder::RECURSE_DOWN); + mFolderRoot.get()->arrangeAll(); +} + +void LLInventoryPanel::setSelection(const LLUUID& obj_id, bool take_keyboard_focus) +{ + // Don't select objects in COF (e.g. to prevent refocus when items are worn). + const LLInventoryObject *obj = mInventory->getObject(obj_id); + if (obj && obj->getParentUUID() == LLAppearanceMgr::instance().getCOF()) + { + return; + } + setSelectionByID(obj_id, take_keyboard_focus); +} + +void LLInventoryPanel::setSelectCallback(const boost::function& items, bool user_action)>& cb) +{ + if (mFolderRoot.get()) + { + mFolderRoot.get()->setSelectCallback(cb); + } + mSelectionCallback = cb; +} + +void LLInventoryPanel::clearSelection() +{ + mSelectThisID.setNull(); + mFocusSelection = false; +} + +LLInventoryPanel::selected_items_t LLInventoryPanel::getSelectedItems() const +{ + return mFolderRoot.get()->getSelectionList(); +} + +void LLInventoryPanel::onSelectionChange(const std::deque& items, bool user_action) +{ + // Schedule updating the folder view context menu when all selected items become complete (STORM-373). + mCompletionObserver->reset(); + for (std::deque::const_iterator it = items.begin(); it != items.end(); ++it) + { + LLFolderViewModelItemInventory* view_model = static_cast((*it)->getViewModelItem()); + if (view_model) + { + LLUUID id = view_model->getUUID(); + if (!(*it)->areChildrenInited()) + { + const F64 max_time = 0.0001f; + mBuildViewsEndTime = LLTimer::getTotalSeconds() + max_time; + buildNewViews(id); + } + LLViewerInventoryItem* inv_item = mInventory->getItem(id); + + if (inv_item && !inv_item->isFinished()) + { + mCompletionObserver->watchItem(id); + } + } + } + + LLFolderView* fv = mFolderRoot.get(); + if (fv->needsAutoRename()) // auto-selecting a new user-created asset and preparing to rename + { + fv->setNeedsAutoRename(false); + if (items.size()) // new asset is visible and selected + { + fv->startRenamingSelectedItem(); + } + else + { + LL_DEBUGS("Inventory") << "Failed to start renemr, no items selected" << LL_ENDL; + } + } + + std::set selected_items = mFolderRoot.get()->getSelectionList(); + LLFolderViewItem* prev_folder_item = getItemByID(mPreviousSelectedFolder); + + if (selected_items.size() == 1) + { + std::set::const_iterator iter = selected_items.begin(); + LLFolderViewItem* folder_item = (*iter); + if(folder_item && (folder_item != prev_folder_item)) + { + LLFolderViewModelItemInventory* fve_listener = static_cast(folder_item->getViewModelItem()); + if (fve_listener && (fve_listener->getInventoryType() == LLInventoryType::IT_CATEGORY)) + { + if (fve_listener->getInventoryObject() && fve_listener->getInventoryObject()->getIsLinkType()) + { + return; + } + + if(prev_folder_item) + { + LLFolderBridge* prev_bridge = (LLFolderBridge*)prev_folder_item->getViewModelItem(); + if(prev_bridge) + { + prev_bridge->clearDisplayName(); + prev_bridge->setShowDescendantsCount(false); + prev_folder_item->refresh(); + } + } + + LLFolderBridge* bridge = (LLFolderBridge*)folder_item->getViewModelItem(); + if(bridge) + { + bridge->clearDisplayName(); + bridge->setShowDescendantsCount(true); + folder_item->refresh(); + mPreviousSelectedFolder = bridge->getUUID(); + } + } + } + } + else + { + if(prev_folder_item) + { + LLFolderBridge* prev_bridge = (LLFolderBridge*)prev_folder_item->getViewModelItem(); + if(prev_bridge) + { + prev_bridge->clearDisplayName(); + prev_bridge->setShowDescendantsCount(false); + prev_folder_item->refresh(); + } + } + mPreviousSelectedFolder = LLUUID(); + } + +} + +void LLInventoryPanel::updateFolderLabel(const LLUUID& folder_id) +{ + if(folder_id != mPreviousSelectedFolder) return; + + LLFolderViewItem* folder_item = getItemByID(mPreviousSelectedFolder); + if(folder_item) + { + LLFolderBridge* bridge = (LLFolderBridge*)folder_item->getViewModelItem(); + if(bridge) + { + bridge->clearDisplayName(); + bridge->setShowDescendantsCount(true); + folder_item->refresh(); + } + } +} + +void LLInventoryPanel::doCreate(const LLSD& userdata) +{ + reset_inventory_filter(); + menu_create_inventory_item(this, LLFolderBridge::sSelf.get(), userdata); +} + +bool LLInventoryPanel::beginIMSession() +{ + std::set selected_items = mFolderRoot.get()->getSelectionList(); + + std::string name; + + std::vector members; + EInstantMessage type = IM_SESSION_CONFERENCE_START; + + std::set::const_iterator iter; + for (iter = selected_items.begin(); iter != selected_items.end(); iter++) + { + + LLFolderViewItem* folder_item = (*iter); + + if(folder_item) + { + LLFolderViewModelItemInventory* fve_listener = static_cast(folder_item->getViewModelItem()); + if (fve_listener && (fve_listener->getInventoryType() == LLInventoryType::IT_CATEGORY)) + { + + LLFolderBridge* bridge = (LLFolderBridge*)folder_item->getViewModelItem(); + if(!bridge) return true; + LLViewerInventoryCategory* cat = bridge->getCategory(); + if(!cat) return true; + name = cat->getName(); + LLUniqueBuddyCollector is_buddy; + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + gInventory.collectDescendentsIf(bridge->getUUID(), + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH, + is_buddy); + S32 count = item_array.size(); + if(count > 0) + { + //*TODO by what to replace that? + //LLFloaterReg::showInstance("communicate"); + + // create the session + LLAvatarTracker& at = LLAvatarTracker::instance(); + LLUUID id; + for(S32 i = 0; i < count; ++i) + { + id = item_array.at(i)->getCreatorUUID(); + if(at.isBuddyOnline(id)) + { + members.push_back(id); + } + } + } + } + else + { + LLInvFVBridge* listenerp = (LLInvFVBridge*)folder_item->getViewModelItem(); + + if (listenerp->getInventoryType() == LLInventoryType::IT_CALLINGCARD) + { + LLInventoryItem* inv_item = gInventory.getItem(listenerp->getUUID()); + + if (inv_item) + { + LLAvatarTracker& at = LLAvatarTracker::instance(); + LLUUID id = inv_item->getCreatorUUID(); + + if(at.isBuddyOnline(id)) + { + members.push_back(id); + } + } + } //if IT_CALLINGCARD + } //if !IT_CATEGORY + } + } //for selected_items + + // the session_id is randomly generated UUID which will be replaced later + // with a server side generated number + + if (name.empty()) + { + name = LLTrans::getString("conference-title"); + } + + LLUUID session_id = gIMMgr->addSession(name, type, members[0], members); + if (session_id != LLUUID::null) + { + LLFloaterIMContainer::getInstance()->showConversation(session_id); + } + + return true; +} + +void LLInventoryPanel::fileUploadLocation(const LLSD& userdata) +{ + const std::string param = userdata.asString(); + if (param == "model") + { + gSavedPerAccountSettings.setString("ModelUploadFolder", LLFolderBridge::sSelf.get()->getUUID().asString()); + } + else if (param == "texture") + { + gSavedPerAccountSettings.setString("TextureUploadFolder", LLFolderBridge::sSelf.get()->getUUID().asString()); + } + else if (param == "sound") + { + gSavedPerAccountSettings.setString("SoundUploadFolder", LLFolderBridge::sSelf.get()->getUUID().asString()); + } + else if (param == "animation") + { + gSavedPerAccountSettings.setString("AnimationUploadFolder", LLFolderBridge::sSelf.get()->getUUID().asString()); + } + else if (param == "pbr_material") + { + gSavedPerAccountSettings.setString("PBRUploadFolder", LLFolderBridge::sSelf.get()->getUUID().asString()); + } +} + +void LLInventoryPanel::openSingleViewInventory(LLUUID folder_id) +{ + LLPanelMainInventory::newFolderWindow(folder_id.isNull() ? LLFolderBridge::sSelf.get()->getUUID() : folder_id); +} + +void LLInventoryPanel::purgeSelectedItems() +{ + if (!mFolderRoot.get()) return; + + const std::set inventory_selected = mFolderRoot.get()->getSelectionList(); + if (inventory_selected.empty()) return; + LLSD args; + S32 count = inventory_selected.size(); + std::vector selected_items; + for (std::set::const_iterator it = inventory_selected.begin(), end_it = inventory_selected.end(); + it != end_it; + ++it) + { + LLUUID item_id = static_cast((*it)->getViewModelItem())->getUUID(); + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + gInventory.collectDescendents(item_id, cats, items, LLInventoryModel::INCLUDE_TRASH); + count += items.size() + cats.size(); + selected_items.push_back(item_id); + } + args["COUNT"] = count; + LLNotificationsUtil::add("PurgeSelectedItems", args, LLSD(), boost::bind(callbackPurgeSelectedItems, _1, _2, selected_items)); +} + +// static +void LLInventoryPanel::callbackPurgeSelectedItems(const LLSD& notification, const LLSD& response, const std::vector inventory_selected) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) + { + if (inventory_selected.empty()) return; + + for (auto it : inventory_selected) + { + remove_inventory_object(it, NULL); + } + } +} + +bool LLInventoryPanel::attachObject(const LLSD& userdata) +{ + // Copy selected item UUIDs to a vector. + std::set selected_items = mFolderRoot.get()->getSelectionList(); + uuid_vec_t items; + for (std::set::const_iterator set_iter = selected_items.begin(); + set_iter != selected_items.end(); + ++set_iter) + { + items.push_back(static_cast((*set_iter)->getViewModelItem())->getUUID()); + } + + // Attach selected items. + LLViewerAttachMenu::attachObjects(items, userdata.asString()); + + gFocusMgr.setKeyboardFocus(NULL); + + return true; +} + +bool LLInventoryPanel::getSinceLogoff() +{ + return getFilter().isSinceLogoff(); +} + +// DEBUG ONLY +// static +void LLInventoryPanel::dumpSelectionInformation(void* user_data) +{ + LLInventoryPanel* iv = (LLInventoryPanel*)user_data; + iv->mFolderRoot.get()->dumpSelectionInformation(); +} + +bool is_inventorysp_active() +{ + LLSidepanelInventory *sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); + if (!sidepanel_inventory || !sidepanel_inventory->isInVisibleChain()) return false; + return sidepanel_inventory->isMainInventoryPanelActive(); +} + +// static +LLInventoryPanel* LLInventoryPanel::getActiveInventoryPanel(bool auto_open) +{ + S32 z_min = S32_MAX; + LLInventoryPanel* res = NULL; + LLFloater* active_inv_floaterp = NULL; + + LLFloater* floater_inventory = LLFloaterReg::getInstance("inventory"); + if (!floater_inventory) + { + LL_WARNS() << "Could not find My Inventory floater" << LL_ENDL; + return nullptr; + } + + LLSidepanelInventory *inventory_panel = LLFloaterSidePanelContainer::getPanel("inventory"); + + // Iterate through the inventory floaters and return whichever is on top. + LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("inventory"); + for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); iter != inst_list.end(); ++iter) + { + LLFloaterSidePanelContainer* inventory_floater = dynamic_cast(*iter); + inventory_panel = inventory_floater->findChild("main_panel"); + + if (inventory_floater && inventory_panel && inventory_floater->getVisible()) + { + S32 z_order = gFloaterView->getZOrder(inventory_floater); + if (z_order < z_min) + { + res = inventory_panel->getActivePanel(); + z_min = z_order; + active_inv_floaterp = inventory_floater; + } + } + } + + if (res) + { + // Make sure the floater is not minimized (STORM-438). + if (active_inv_floaterp && active_inv_floaterp->isMinimized()) + { + active_inv_floaterp->setMinimized(false); + } + } + else if (auto_open) + { + floater_inventory->openFloater(); + + res = inventory_panel->getActivePanel(); + } + + return res; +} + +//static +void LLInventoryPanel::openInventoryPanelAndSetSelection(bool auto_open, const LLUUID& obj_id, + bool use_main_panel, bool take_keyboard_focus, bool reset_filter) +{ + LLSidepanelInventory* sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); + sidepanel_inventory->showInventoryPanel(); + + LLUUID cat_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX); + bool in_inbox = gInventory.isObjectDescendentOf(obj_id, cat_id); + if (!in_inbox && use_main_panel) + { + sidepanel_inventory->selectAllItemsPanel(); + } + + if (!auto_open) + { + LLFloater* inventory_floater = LLFloaterSidePanelContainer::getTopmostInventoryFloater(); + if (inventory_floater && inventory_floater->getVisible()) + { + LLSidepanelInventory *inventory_panel = inventory_floater->findChild("main_panel"); + LLPanelMainInventory* main_panel = inventory_panel->getMainInventoryPanel(); + if (main_panel->isSingleFolderMode() && main_panel->isGalleryViewMode()) + { + LL_DEBUGS("Inventory") << "Opening gallery panel for item" << obj_id << LL_ENDL; + main_panel->setGallerySelection(obj_id); + return; + } + } + } + + if (use_main_panel) + { + LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); + if (main_inventory && main_inventory->isSingleFolderMode()) + { + const LLInventoryObject *obj = gInventory.getObject(obj_id); + if (obj) + { + LL_DEBUGS("Inventory") << "Opening main inventory panel for item" << obj_id << LL_ENDL; + main_inventory->setSingleFolderViewRoot(obj->getParentUUID(), false); + main_inventory->setGallerySelection(obj_id); + return; + } + } + } + + LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(auto_open); + if (active_panel) + { + LL_DEBUGS("Messaging", "Inventory") << "Highlighting" << obj_id << LL_ENDL; + + if (reset_filter) + { + reset_inventory_filter(); + } + + if (in_inbox) + { + sidepanel_inventory->openInbox(); + LLInventoryPanel* inventory_panel = sidepanel_inventory->getInboxPanel(); + if (inventory_panel) + { + inventory_panel->setSelection(obj_id, take_keyboard_focus); + } + } + else if (auto_open) + { + LLFloater* floater_inventory = LLFloaterReg::getInstance("inventory"); + if (floater_inventory) + { + floater_inventory->setFocus(true); + } + active_panel->setSelection(obj_id, take_keyboard_focus); + } + else + { + // Created items are going to receive proper focus from callbacks + active_panel->setSelection(obj_id, take_keyboard_focus); + } + } +} + +void LLInventoryPanel::setSFViewAndOpenFolder(const LLInventoryPanel* panel, const LLUUID& folder_id) +{ + LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("inventory"); + for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); iter != inst_list.end(); ++iter) + { + LLFloaterSidePanelContainer* inventory_floater = dynamic_cast(*iter); + LLSidepanelInventory* sidepanel_inventory = inventory_floater->findChild("main_panel"); + + LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); + if (main_inventory && panel->hasAncestor(main_inventory) && !main_inventory->isSingleFolderMode()) + { + main_inventory->initSingleFolderRoot(folder_id); + main_inventory->toggleViewMode(); + main_inventory->setSingleFolderViewRoot(folder_id, false); + } + } +} + +void LLInventoryPanel::addHideFolderType(LLFolderType::EType folder_type) +{ + getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() & ~(1ULL << folder_type)); +} + +bool LLInventoryPanel::getIsHiddenFolderType(LLFolderType::EType folder_type) const +{ + return !(getFilter().getFilterCategoryTypes() & (1ULL << folder_type)); +} + +void LLInventoryPanel::addItemID( const LLUUID& id, LLFolderViewItem* itemp ) +{ + mItemMap[id] = itemp; +} + +void LLInventoryPanel::removeItemID(const LLUUID& id) +{ + LLInventoryModel::cat_array_t categories; + LLInventoryModel::item_array_t items; + gInventory.collectDescendents(id, categories, items, true); + + mItemMap.erase(id); + + for (LLInventoryModel::cat_array_t::iterator it = categories.begin(), end_it = categories.end(); + it != end_it; + ++it) + { + mItemMap.erase((*it)->getUUID()); +} + + for (LLInventoryModel::item_array_t::iterator it = items.begin(), end_it = items.end(); + it != end_it; + ++it) + { + mItemMap.erase((*it)->getUUID()); + } +} + +LLFolderViewItem* LLInventoryPanel::getItemByID(const LLUUID& id) +{ + LL_PROFILE_ZONE_SCOPED; + + std::map::iterator map_it; + map_it = mItemMap.find(id); + if (map_it != mItemMap.end()) + { + return map_it->second; + } + + return NULL; +} + +LLFolderViewFolder* LLInventoryPanel::getFolderByID(const LLUUID& id) +{ + LLFolderViewItem* item = getItemByID(id); + return dynamic_cast(item); +} + + +void LLInventoryPanel::setSelectionByID( const LLUUID& obj_id, bool take_keyboard_focus ) +{ + LLFolderViewItem* itemp = getItemByID(obj_id); + + if (itemp && !itemp->areChildrenInited()) + { + LLInventoryObject const* objectp = mInventory->getObject(obj_id); + if (objectp) + { + buildNewViews(obj_id, objectp, itemp, BUILD_ONE_FOLDER); + } + } + + if(itemp && itemp->getViewModelItem()) + { + itemp->arrangeAndSet(true, take_keyboard_focus); + mSelectThisID.setNull(); + mFocusSelection = false; + return; + } + else + { + // save the desired item to be selected later (if/when ready) + mFocusSelection = take_keyboard_focus; + mSelectThisID = obj_id; + } +} + +void LLInventoryPanel::updateSelection() +{ + if (mSelectThisID.notNull()) + { + setSelectionByID(mSelectThisID, mFocusSelection); + } +} + +void LLInventoryPanel::doToSelected(const LLSD& userdata) +{ + if (("purge" == userdata.asString())) + { + purgeSelectedItems(); + return; + } + LLInventoryAction::doToSelected(mInventory, mFolderRoot.get(), userdata.asString()); + + return; +} + +bool LLInventoryPanel::handleKeyHere( KEY key, MASK mask ) +{ + bool handled = false; + switch (key) + { + case KEY_RETURN: + // Open selected items if enter key hit on the inventory panel + if (mask == MASK_NONE) + { + if (mSuppressOpenItemAction) + { + LLFolderViewItem* folder_item = mFolderRoot.get()->getCurSelectedItem(); + if(folder_item) + { + LLInvFVBridge* bridge = (LLInvFVBridge*)folder_item->getViewModelItem(); + if(bridge && (bridge->getInventoryType() != LLInventoryType::IT_CATEGORY)) + { + return handled; + } + } + } + LLInventoryAction::doToSelected(mInventory, mFolderRoot.get(), "open"); + handled = true; + } + break; + case KEY_DELETE: +#if LL_DARWIN + case KEY_BACKSPACE: +#endif + // Delete selected items if delete or backspace key hit on the inventory panel + // Note: on Mac laptop keyboards, backspace and delete are one and the same + if (isSelectionRemovable() && (mask == MASK_NONE)) + { + LLInventoryAction::doToSelected(mInventory, mFolderRoot.get(), "delete"); + handled = true; + } + break; + } + return handled; +} + +bool LLInventoryPanel::isSelectionRemovable() +{ + bool can_delete = false; + if (mFolderRoot.get()) + { + std::set selection_set = mFolderRoot.get()->getSelectionList(); + if (!selection_set.empty()) + { + can_delete = true; + for (std::set::iterator iter = selection_set.begin(); + iter != selection_set.end(); + ++iter) + { + LLFolderViewItem *item = *iter; + const LLFolderViewModelItemInventory *listener = static_cast(item->getViewModelItem()); + if (!listener) + { + can_delete = false; + } + else + { + can_delete &= listener->isItemRemovable() && !listener->isItemInTrash(); + } + } + } + } + return can_delete; +} + +/************************************************************************/ +/* Recent Inventory Panel related class */ +/************************************************************************/ +static const LLRecentInventoryBridgeBuilder RECENT_ITEMS_BUILDER; +class LLInventoryRecentItemsPanel : public LLInventoryPanel +{ +public: + struct Params : public LLInitParam::Block + {}; + + void initFromParams(const Params& p) + { + LLInventoryPanel::initFromParams(p); + // turn on inbox for recent items + getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() | (1ULL << LLFolderType::FT_INBOX)); + // turn off marketplace for recent items + getFilter().setFilterNoMarketplaceFolder(); + } + +protected: + LLInventoryRecentItemsPanel (const Params&); + friend class LLUICtrlFactory; +}; + +LLInventoryRecentItemsPanel::LLInventoryRecentItemsPanel( const Params& params) +: LLInventoryPanel(params) +{ + // replace bridge builder to have necessary View bridges. + mInvFVBridgeBuilder = &RECENT_ITEMS_BUILDER; +} + +static LLDefaultChildRegistry::Register t_single_folder_inventory_panel("single_folder_inventory_panel"); + +LLInventorySingleFolderPanel::LLInventorySingleFolderPanel(const Params& params) + : LLInventoryPanel(params) +{ + mBuildChildrenViews = false; + getFilter().setSingleFolderMode(true); + getFilter().setEmptyLookupMessage("InventorySingleFolderNoMatches"); + getFilter().setDefaultEmptyLookupMessage("InventorySingleFolderEmpty"); + + mCommitCallbackRegistrar.replace("Inventory.DoToSelected", boost::bind(&LLInventorySingleFolderPanel::doToSelected, this, _2)); + mCommitCallbackRegistrar.replace("Inventory.DoCreate", boost::bind(&LLInventorySingleFolderPanel::doCreate, this, _2)); + mCommitCallbackRegistrar.replace("Inventory.Share", boost::bind(&LLInventorySingleFolderPanel::doShare, this)); +} + +LLInventorySingleFolderPanel::~LLInventorySingleFolderPanel() +{ +} + +void LLInventorySingleFolderPanel::initFromParams(const Params& p) +{ + mFolderID = gInventory.getRootFolderID(); + + mParams = p; + LLPanel::initFromParams(mParams); +} + +void LLInventorySingleFolderPanel::onFocusReceived() +{ + // Tab support, when tabbing into this view, select first item + // (ideally needs to account for scroll) + bool select_first = mSelectThisID.isNull() && mFolderRoot.get() && mFolderRoot.get()->getSelectedCount() == 0; + + if (select_first) + { + LLFolderViewFolder::folders_t::const_iterator folders_it = mFolderRoot.get()->getFoldersBegin(); + LLFolderViewFolder::folders_t::const_iterator folders_end = mFolderRoot.get()->getFoldersEnd(); + + for (; folders_it != folders_end; ++folders_it) + { + const LLFolderViewFolder* folder_view = *folders_it; + if (folder_view->getVisible()) + { + const LLFolderViewModelItemInventory* modelp = static_cast(folder_view->getViewModelItem()); + setSelectionByID(modelp->getUUID(), true); + // quick and dirty fix: don't scroll on switching focus + // todo: better 'tab' support, one that would work for LLInventoryPanel + mFolderRoot.get()->stopAutoScollining(); + select_first = false; + break; + } + } + } + + if (select_first) + { + LLFolderViewFolder::items_t::const_iterator items_it = mFolderRoot.get()->getItemsBegin(); + LLFolderViewFolder::items_t::const_iterator items_end = mFolderRoot.get()->getItemsEnd(); + + for (; items_it != items_end; ++items_it) + { + const LLFolderViewItem* item_view = *items_it; + if (item_view->getVisible()) + { + const LLFolderViewModelItemInventory* modelp = static_cast(item_view->getViewModelItem()); + setSelectionByID(modelp->getUUID(), true); + mFolderRoot.get()->stopAutoScollining(); + break; + } + } + } + LLInventoryPanel::onFocusReceived(); +} + +void LLInventorySingleFolderPanel::initFolderRoot(const LLUUID& start_folder_id) +{ + if(mRootInited) return; + + mRootInited = true; + if(start_folder_id.notNull()) + { + mFolderID = start_folder_id; + } + + mParams.open_first_folder = false; + mParams.start_folder.id = mFolderID; + + LLInventoryPanel::initFolderRoot(); + mFolderRoot.get()->setSingleFolderMode(true); +} + +void LLInventorySingleFolderPanel::changeFolderRoot(const LLUUID& new_id) +{ + if (mFolderID != new_id) + { + if(mFolderID.notNull()) + { + mBackwardFolders.push_back(mFolderID); + } + mFolderID = new_id; + updateSingleFolderRoot(); + } +} + +void LLInventorySingleFolderPanel::onForwardFolder() +{ + if(isForwardAvailable()) + { + mBackwardFolders.push_back(mFolderID); + mFolderID = mForwardFolders.back(); + mForwardFolders.pop_back(); + updateSingleFolderRoot(); + } +} + +void LLInventorySingleFolderPanel::onBackwardFolder() +{ + if(isBackwardAvailable()) + { + mForwardFolders.push_back(mFolderID); + mFolderID = mBackwardFolders.back(); + mBackwardFolders.pop_back(); + updateSingleFolderRoot(); + } +} + +void LLInventorySingleFolderPanel::clearNavigationHistory() +{ + mForwardFolders.clear(); + mBackwardFolders.clear(); +} + +bool LLInventorySingleFolderPanel::isBackwardAvailable() +{ + return (!mBackwardFolders.empty() && (mFolderID != mBackwardFolders.back())); +} + +bool LLInventorySingleFolderPanel::isForwardAvailable() +{ + return (!mForwardFolders.empty() && (mFolderID != mForwardFolders.back())); +} + +boost::signals2::connection LLInventorySingleFolderPanel::setRootChangedCallback(root_changed_callback_t cb) +{ + return mRootChangedSignal.connect(cb); +} + +void LLInventorySingleFolderPanel::updateSingleFolderRoot() +{ + if (mFolderID != getRootFolderID()) + { + mRootChangedSignal(); + + LLUUID root_id = mFolderID; + if (mFolderRoot.get()) + { + mItemMap.clear(); + mFolderRoot.get()->destroyRoot(); + } + + mCommitCallbackRegistrar.pushScope(); + { + LLFolderView* folder_view = createFolderRoot(root_id); + folder_view->setChildrenInited(false); + mFolderRoot = folder_view->getHandle(); + mFolderRoot.get()->setSingleFolderMode(true); + addItemID(root_id, mFolderRoot.get()); + + LLRect scroller_view_rect = getRect(); + scroller_view_rect.translate(-scroller_view_rect.mLeft, -scroller_view_rect.mBottom); + LLScrollContainer::Params scroller_params(mParams.scroll()); + scroller_params.rect(scroller_view_rect); + + if (mScroller) + { + removeChild(mScroller); + delete mScroller; + mScroller = NULL; + } + mScroller = LLUICtrlFactory::create(scroller_params); + addChild(mScroller); + mScroller->addChild(mFolderRoot.get()); + mFolderRoot.get()->setScrollContainer(mScroller); + mFolderRoot.get()->setFollowsAll(); + mFolderRoot.get()->addChild(mFolderRoot.get()->mStatusTextBox); + + if (!mSelectionCallback.empty()) + { + mFolderRoot.get()->setSelectCallback(mSelectionCallback); + } + } + mCommitCallbackRegistrar.popScope(); + mFolderRoot.get()->setCallbackRegistrar(&mCommitCallbackRegistrar); + + buildNewViews(mFolderID); + + LLFloater* root_floater = gFloaterView->getParentFloater(this); + if(root_floater) + { + root_floater->setFocus(true); + } + } +} + +bool LLInventorySingleFolderPanel::hasVisibleItems() +{ + return mFolderRoot.get()->hasVisibleChildren(); +} + +void LLInventorySingleFolderPanel::doCreate(const LLSD& userdata) +{ + std::string type_name = userdata.asString(); + LLUUID dest_id = LLFolderBridge::sSelf.get()->getUUID(); + if (("category" == type_name) || ("outfit" == type_name)) + { + changeFolderRoot(dest_id); + } + reset_inventory_filter(); + menu_create_inventory_item(this, dest_id, userdata); +} + +void LLInventorySingleFolderPanel::doToSelected(const LLSD& userdata) +{ + if (("open_in_current_window" == userdata.asString())) + { + changeFolderRoot(LLFolderBridge::sSelf.get()->getUUID()); + return; + } + LLInventoryPanel::doToSelected(userdata); +} + +void LLInventorySingleFolderPanel::doShare() +{ + LLAvatarActions::shareWithAvatars(this); +} +/************************************************************************/ +/* Asset Pre-Filtered Inventory Panel related class */ +/************************************************************************/ + +LLAssetFilteredInventoryPanel::LLAssetFilteredInventoryPanel(const Params& p) + : LLInventoryPanel(p) +{ +} + + +void LLAssetFilteredInventoryPanel::initFromParams(const Params& p) +{ + // Init asset types + std::string types = p.filter_asset_types.getValue(); + + typedef boost::tokenizer > tokenizer; + boost::char_separator sep("|"); + tokenizer tokens(types, sep); + tokenizer::iterator token_iter = tokens.begin(); + + memset(mAssetTypes, 0, LLAssetType::AT_COUNT * sizeof(bool)); + while (token_iter != tokens.end()) + { + const std::string& token_str = *token_iter; + LLAssetType::EType asset_type = LLAssetType::lookup(token_str); + if (asset_type > LLAssetType::AT_NONE && asset_type < LLAssetType::AT_COUNT) + { + mAssetTypes[asset_type] = true; + } + ++token_iter; + } + + // Init drag types + memset(mDragTypes, 0, EDragAndDropType::DAD_COUNT * sizeof(bool)); + for (S32 i = 0; i < LLAssetType::AT_COUNT; i++) + { + if (mAssetTypes[i]) + { + EDragAndDropType drag_type = LLViewerAssetType::lookupDragAndDropType((LLAssetType::EType)i); + if (drag_type != DAD_NONE) + { + mDragTypes[drag_type] = true; + } + } + } + // Always show AT_CATEGORY, but it shouldn't get into mDragTypes + mAssetTypes[LLAssetType::AT_CATEGORY] = true; + + // Init the panel + LLInventoryPanel::initFromParams(p); + U64 filter_cats = getFilter().getFilterCategoryTypes(); + filter_cats &= ~(1ULL << LLFolderType::FT_MARKETPLACE_LISTINGS); + getFilter().setFilterCategoryTypes(filter_cats); + getFilter().setFilterNoMarketplaceFolder(); +} + +bool LLAssetFilteredInventoryPanel::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + bool result = false; + + if (mAcceptsDragAndDrop) + { + // Don't allow DAD_CATEGORY here since it can contain other items besides required assets + // We should see everything we drop! + if (mDragTypes[cargo_type]) + { + result = LLInventoryPanel::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); + } + } + + return result; +} + +/*virtual*/ +bool LLAssetFilteredInventoryPanel::typedViewsFilter(const LLUUID& id, LLInventoryObject const* objectp) +{ + if (!objectp) + { + return false; + } + LLAssetType::EType asset_type = objectp->getType(); + + if (asset_type < 0 || asset_type >= LLAssetType::AT_COUNT) + { + return false; + } + + if (!mAssetTypes[asset_type]) + { + return false; + } + + return true; +} + +void LLAssetFilteredInventoryPanel::itemChanged(const LLUUID& id, U32 mask, const LLInventoryObject* model_item) +{ + if (!model_item && !getItemByID(id)) + { + // remove operation, but item is not in panel already + return; + } + + if (model_item) + { + LLAssetType::EType asset_type = model_item->getType(); + + if (asset_type < 0 + || asset_type >= LLAssetType::AT_COUNT + || !mAssetTypes[asset_type]) + { + return; + } + } + + LLInventoryPanel::itemChanged(id, mask, model_item); +} + +namespace LLInitParam +{ + void TypeValues::declareValues() + { + declare(LLFolderType::lookup(LLFolderType::FT_TEXTURE) , LLFolderType::FT_TEXTURE); + declare(LLFolderType::lookup(LLFolderType::FT_SOUND) , LLFolderType::FT_SOUND); + declare(LLFolderType::lookup(LLFolderType::FT_CALLINGCARD) , LLFolderType::FT_CALLINGCARD); + declare(LLFolderType::lookup(LLFolderType::FT_LANDMARK) , LLFolderType::FT_LANDMARK); + declare(LLFolderType::lookup(LLFolderType::FT_CLOTHING) , LLFolderType::FT_CLOTHING); + declare(LLFolderType::lookup(LLFolderType::FT_OBJECT) , LLFolderType::FT_OBJECT); + declare(LLFolderType::lookup(LLFolderType::FT_NOTECARD) , LLFolderType::FT_NOTECARD); + declare(LLFolderType::lookup(LLFolderType::FT_ROOT_INVENTORY) , LLFolderType::FT_ROOT_INVENTORY); + declare(LLFolderType::lookup(LLFolderType::FT_LSL_TEXT) , LLFolderType::FT_LSL_TEXT); + declare(LLFolderType::lookup(LLFolderType::FT_BODYPART) , LLFolderType::FT_BODYPART); + declare(LLFolderType::lookup(LLFolderType::FT_TRASH) , LLFolderType::FT_TRASH); + declare(LLFolderType::lookup(LLFolderType::FT_SNAPSHOT_CATEGORY), LLFolderType::FT_SNAPSHOT_CATEGORY); + declare(LLFolderType::lookup(LLFolderType::FT_LOST_AND_FOUND) , LLFolderType::FT_LOST_AND_FOUND); + declare(LLFolderType::lookup(LLFolderType::FT_ANIMATION) , LLFolderType::FT_ANIMATION); + declare(LLFolderType::lookup(LLFolderType::FT_GESTURE) , LLFolderType::FT_GESTURE); + declare(LLFolderType::lookup(LLFolderType::FT_FAVORITE) , LLFolderType::FT_FAVORITE); + declare(LLFolderType::lookup(LLFolderType::FT_ENSEMBLE_START) , LLFolderType::FT_ENSEMBLE_START); + declare(LLFolderType::lookup(LLFolderType::FT_ENSEMBLE_END) , LLFolderType::FT_ENSEMBLE_END); + declare(LLFolderType::lookup(LLFolderType::FT_CURRENT_OUTFIT) , LLFolderType::FT_CURRENT_OUTFIT); + declare(LLFolderType::lookup(LLFolderType::FT_OUTFIT) , LLFolderType::FT_OUTFIT); + declare(LLFolderType::lookup(LLFolderType::FT_MY_OUTFITS) , LLFolderType::FT_MY_OUTFITS); + declare(LLFolderType::lookup(LLFolderType::FT_MESH ) , LLFolderType::FT_MESH ); + declare(LLFolderType::lookup(LLFolderType::FT_INBOX) , LLFolderType::FT_INBOX); + declare(LLFolderType::lookup(LLFolderType::FT_OUTBOX) , LLFolderType::FT_OUTBOX); + declare(LLFolderType::lookup(LLFolderType::FT_BASIC_ROOT) , LLFolderType::FT_BASIC_ROOT); + declare(LLFolderType::lookup(LLFolderType::FT_SETTINGS) , LLFolderType::FT_SETTINGS); + declare(LLFolderType::lookup(LLFolderType::FT_MATERIAL) , LLFolderType::FT_MATERIAL); + declare(LLFolderType::lookup(LLFolderType::FT_MARKETPLACE_LISTINGS) , LLFolderType::FT_MARKETPLACE_LISTINGS); + declare(LLFolderType::lookup(LLFolderType::FT_MARKETPLACE_STOCK), LLFolderType::FT_MARKETPLACE_STOCK); + declare(LLFolderType::lookup(LLFolderType::FT_MARKETPLACE_VERSION), LLFolderType::FT_MARKETPLACE_VERSION); + } +} diff --git a/indra/newview/llinventorypanel.h b/indra/newview/llinventorypanel.h index bc85644068..6dead2cf6d 100644 --- a/indra/newview/llinventorypanel.h +++ b/indra/newview/llinventorypanel.h @@ -1,489 +1,489 @@ -/** - * @file llinventorypanel.h - * @brief LLInventoryPanel - * class definition - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLINVENTORYPANEL_H -#define LL_LLINVENTORYPANEL_H - -#include "llassetstorage.h" -#include "llfolderviewitem.h" -#include "llfolderviewmodelinventory.h" -#include "llfloater.h" -#include "llinventory.h" -#include "llinventoryfilter.h" -#include "llinventorymodel.h" -#include "llscrollcontainer.h" -#include "lluictrlfactory.h" -#include - -class LLInvFVBridge; -class LLInventoryFolderViewModelBuilder; -class LLInvPanelComplObserver; -class LLFolderViewModelInventory; -class LLFolderViewGroupedItemBridge; - -namespace LLInitParam -{ - template<> - struct TypeValues : public TypeValuesHelper - { - static void declareValues(); - }; -} - -class LLInventoryPanel : public LLPanel -{ - //-------------------------------------------------------------------- - // Data - //-------------------------------------------------------------------- -public: - struct Filter : public LLInitParam::Block - { - Optional sort_order; - Optional types; - Optional search_string; - - Filter() - : sort_order("sort_order"), - types("types", 0xffffffff), - search_string("search_string") - {} - }; - - struct StartFolder : public LLInitParam::ChoiceBlock - { - Alternative name; - Alternative id; - Alternative type; - - StartFolder() - : name("name"), - id("id"), - type("type") - {} - }; - - struct Params - : public LLInitParam::Block - { - Optional sort_order_setting; - Optional inventory; - Optional allow_multi_select; - Optional allow_drag; - Optional show_item_link_overlays; - Optional filter; - Optional start_folder; - Optional use_label_suffix; - Optional show_empty_message; - Optional suppress_folder_menu; - Optional show_root_folder; - Optional allow_drop_on_root; - Optional use_marketplace_folders; - Optional scroll; - Optional accepts_drag_and_drop; - Optional folder_view; - Optional folder; - Optional item; - Optional open_first_folder; - - // All item and folder views will be initialized on init if true (default) - // Will initialize on visibility change otherwise. - Optional preinitialize_views; - - Params() - : sort_order_setting("sort_order_setting"), - inventory("", &gInventory), - allow_multi_select("allow_multi_select", true), - allow_drag("allow_drag", true), - show_item_link_overlays("show_item_link_overlays", false), - suppress_folder_menu("suppress_folder_menu", false), - filter("filter"), - start_folder("start_folder"), - use_label_suffix("use_label_suffix", true), - show_empty_message("show_empty_message", true), - show_root_folder("show_root_folder", false), - allow_drop_on_root("allow_drop_on_root", true), - use_marketplace_folders("use_marketplace_folders", false), - open_first_folder("open_first_folder", true), - scroll("scroll"), - accepts_drag_and_drop("accepts_drag_and_drop"), - folder_view("folder_view"), - folder("folder"), - item("item"), - preinitialize_views("preinitialize_views", true) - {} - }; - - struct InventoryState : public LLInitParam::Block - { - Mandatory filter; - Mandatory sort; - }; - - //-------------------------------------------------------------------- - // Initialization - //-------------------------------------------------------------------- -protected: - LLInventoryPanel(const Params&); - void initFromParams(const Params&); - - friend class LLUICtrlFactory; -public: - virtual ~LLInventoryPanel(); - -public: - typedef std::set selected_items_t; - - LLInventoryModel* getModel() { return mInventory; } - LLFolderViewModelInventory& getRootViewModel() { return mInventoryViewModel; } - - // LLView methods - /*virtual*/ void onVisibilityChange(bool new_visibility) override; - void draw() override; - /*virtual*/ bool handleKeyHere( KEY key, MASK mask ) override; - bool handleHover(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) override; - bool handleToolTip(S32 x, S32 y, MASK mask) override; - // LLUICtrl methods - /*virtual*/ void onFocusLost() override; - /*virtual*/ void onFocusReceived() override; - void onFolderOpening(const LLUUID &id); - - // LLBadgeHolder methods - bool addBadge(LLBadge * badge) override; - - // Call this method to set the selection. - void openAllFolders(); - void setSelection(const LLUUID& obj_id, bool take_keyboard_focus); - void setSelectCallback(const boost::function& items, bool user_action)>& cb); - void clearSelection(); - selected_items_t getSelectedItems() const; - - bool isSelectionRemovable(); - LLInventoryFilter& getFilter(); - const LLInventoryFilter& getFilter() const; - void setFilterTypes(U64 filter, LLInventoryFilter::EFilterType = LLInventoryFilter::FILTERTYPE_OBJECT); - void setFilterWorn(); - U32 getFilterObjectTypes() const; - void setFilterPermMask(PermissionMask filter_perm_mask); - U32 getFilterPermMask() const; - void setFilterWearableTypes(U64 filter); - void setFilterSettingsTypes(U64 filter); - void setFilterSubString(const std::string& string); - const std::string getFilterSubString(); - void setSinceLogoff(bool sl); - void setHoursAgo(U32 hours); - void setDateSearchDirection(U32 direction); - bool getSinceLogoff(); - void setFilterLinks(U64 filter_links); - void setSearchType(LLInventoryFilter::ESearchType type); - LLInventoryFilter::ESearchType getSearchType(); - - void setShowFolderState(LLInventoryFilter::EFolderShow show); - LLInventoryFilter::EFolderShow getShowFolderState(); - // This method is called when something has changed about the inventory. - void modelChanged(U32 mask); - LLFolderView* getRootFolder() { return mFolderRoot.get(); } - LLUUID getRootFolderID(); - LLScrollContainer* getScrollableContainer() { return mScroller; } - bool getAllowDropOnRoot() { return mParams.allow_drop_on_root; } - bool areViewsInitialized() { return mViewsInitialized == VIEWS_INITIALIZED && mFolderRoot.get() && !mFolderRoot.get()->needsArrange(); } - - void onSelectionChange(const std::deque &items, bool user_action); - - LLHandle getInventoryPanelHandle() const { return getDerivedHandle(); } - - // Callbacks - void doToSelected(const LLSD& userdata); - void doCreate(const LLSD& userdata); - bool beginIMSession(); - void fileUploadLocation(const LLSD& userdata); - void openSingleViewInventory(LLUUID folder_id = LLUUID()); - void purgeSelectedItems(); - bool attachObject(const LLSD& userdata); - static void idle(void* user_data); - - void updateFolderLabel(const LLUUID& folder_id); - - // DEBUG ONLY: - static void dumpSelectionInformation(void* user_data); - - void openSelected(); - void unSelectAll(); - - static void onIdle(void* user_data); - - // Find whichever inventory panel is active / on top. - // "Auto_open" determines if we open an inventory panel if none are open. - static LLInventoryPanel *getActiveInventoryPanel(bool auto_open = true); - - static void openInventoryPanelAndSetSelection(bool auto_open, - const LLUUID& obj_id, - bool use_main_panel = false, - bool take_keyboard_focus = true, - bool reset_filter = false); - static void setSFViewAndOpenFolder(const LLInventoryPanel* panel, const LLUUID& folder_id); - void addItemID(const LLUUID& id, LLFolderViewItem* itemp); - void removeItemID(const LLUUID& id); - LLFolderViewItem* getItemByID(const LLUUID& id); - LLFolderViewFolder* getFolderByID(const LLUUID& id); - void setSelectionByID(const LLUUID& obj_id, bool take_keyboard_focus); - void updateSelection(); - - void setSuppressOpenItemAction(bool supress_open_item) { mSuppressOpenItemAction = supress_open_item; } - - LLFolderViewModelInventory* getFolderViewModel() { return &mInventoryViewModel; } - const LLFolderViewModelInventory* getFolderViewModel() const { return &mInventoryViewModel; } - - // Clean up stuff when the folder root gets deleted - void clearFolderRoot(); - - static void callbackPurgeSelectedItems(const LLSD& notification, const LLSD& response, const std::vector inventory_selected); - - void changeFolderRoot(const LLUUID& new_id) {}; - void initFolderRoot(); - void initializeViewBuilding(); - -protected: - void openStartFolderOrMyInventory(); // open the first level of inventory - void onItemsCompletion(); // called when selected items are complete - - LLUUID mSelectThisID; - LLInventoryModel* mInventory; - LLInventoryObserver* mInventoryObserver; - LLInvPanelComplObserver* mCompletionObserver; - bool mFocusSelection; - bool mAcceptsDragAndDrop; - bool mAllowMultiSelect; - bool mAllowDrag; - bool mShowItemLinkOverlays; // Shows link graphic over inventory item icons - bool mShowEmptyMessage; - bool mSuppressFolderMenu; - bool mSuppressOpenItemAction; - - LLHandle mFolderRoot; - LLScrollContainer* mScroller; - - LLUUID mPreviousSelectedFolder; - - LLFolderViewModelInventory mInventoryViewModel; - LLPointer mGroupedItemBridge; - Params mParams; // stored copy of parameter block - - std::map mItemMap; - /** - * Pointer to LLInventoryFolderViewModelBuilder. - * - * It is set in LLInventoryPanel's constructor and can be overridden in derived classes with - * another implementation. - * Take into account it will not be deleted by LLInventoryPanel itself. - */ - const LLInventoryFolderViewModelBuilder* mInvFVBridgeBuilder; - - bool mBuildChildrenViews; // build root and children - bool mRootInited; - - - //-------------------------------------------------------------------- - // Sorting - //-------------------------------------------------------------------- -public: - static const std::string DEFAULT_SORT_ORDER; - static const std::string RECENTITEMS_SORT_ORDER; - static const std::string INHERIT_SORT_ORDER; - - void setSortOrder(U32 order); - U32 getSortOrder() const; - -private: - std::string mSortOrderSetting; - int mClipboardState; - - //-------------------------------------------------------------------- - // Hidden folders - //-------------------------------------------------------------------- -public: - void addHideFolderType(LLFolderType::EType folder_type); -protected: - // Builds the UI. Call this once the inventory is usable. - void initializeViews(F64 max_time); - - // Specific inventory colors - static bool sColorSetInitialized; - static LLUIColor sDefaultColor; - static LLUIColor sDefaultHighlightColor; - static LLUIColor sLibraryColor; - static LLUIColor sLinkColor; - - enum EBuildModes - { - BUILD_NO_LIMIT, - BUILD_TIMELIMIT, // requires mBuildViewsEndTime - BUILD_ONE_FOLDER, - BUILD_NO_CHILDREN, - }; - - // All buildNewViews() use BUILD_TIMELIMIT by default - // and expect time limit mBuildViewsEndTime to be set - LLFolderViewItem* buildNewViews(const LLUUID& id); - LLFolderViewItem* buildNewViews(const LLUUID& id, - LLInventoryObject const* objectp); - LLFolderViewItem* buildNewViews(const LLUUID& id, - LLInventoryObject const* objectp, - LLFolderViewItem *target_view, - const EBuildModes &mode = BUILD_TIMELIMIT); - - // if certain types are not allowed, no reason to create views - virtual bool typedViewsFilter(const LLUUID& id, LLInventoryObject const* objectp) { return true; } - - virtual void itemChanged(const LLUUID& item_id, U32 mask, const LLInventoryObject* model_item); - bool getIsHiddenFolderType(LLFolderType::EType folder_type) const; - - virtual LLFolderView * createFolderRoot(LLUUID root_id ); - virtual LLFolderViewFolder* createFolderViewFolder(LLInvFVBridge * bridge, bool allow_drop); - virtual LLFolderViewItem* createFolderViewItem(LLInvFVBridge * bridge); - - boost::function& items, bool user_action)> mSelectionCallback; -private: - // buildViewsTree does not include some checks and is meant - // for recursive use, use buildNewViews() for first call - LLFolderViewItem* buildViewsTree(const LLUUID& id, - const LLUUID& parent_id, - LLInventoryObject const* objectp, - LLFolderViewItem *target_view, - LLFolderViewFolder *parent_folder_view, - const EBuildModes &mode, - S32 depth = -1); - - typedef enum e_views_initialization_state - { - VIEWS_UNINITIALIZED = 0, - VIEWS_INITIALIZING, - VIEWS_BUILDING, // Root folder exists - VIEWS_INITIALIZED, - } EViewsInitializationState; - - bool mBuildViewsOnInit; - EViewsInitializationState mViewsInitialized; // Whether views have been generated - F64 mBuildViewsEndTime; // Stop building views past this timestamp - std::deque mBuildViewsQueue; -}; - - -class LLInventorySingleFolderPanel : public LLInventoryPanel -{ -public: - struct Params : public LLInitParam::Block - {}; - - void initFromParams(const Params& p); - void onFocusReceived() override; - - bool isSelectionRemovable() { return false; } - - void initFolderRoot(const LLUUID& start_folder_id = LLUUID::null); - - void changeFolderRoot(const LLUUID& new_id); - void onForwardFolder(); - void onBackwardFolder(); - void clearNavigationHistory(); - LLUUID getSingleFolderRoot() { return mFolderID; } - - void doCreate(const LLSD& userdata); - void doToSelected(const LLSD& userdata); - void doShare(); - - bool isBackwardAvailable(); - bool isForwardAvailable(); - - bool hasVisibleItems(); - - void setNavBackwardList(std::list backward_list) { mBackwardFolders = backward_list; } - void setNavForwardList(std::list forward_list) { mForwardFolders = forward_list; } - std::list getNavBackwardList() { return mBackwardFolders; } - std::list getNavForwardList() { return mForwardFolders; } - - typedef boost::function root_changed_callback_t; - boost::signals2::connection setRootChangedCallback(root_changed_callback_t cb); - -protected: - LLInventorySingleFolderPanel(const Params& params); - ~LLInventorySingleFolderPanel(); - void updateSingleFolderRoot(); - - friend class LLUICtrlFactory; - - LLUUID mFolderID; - std::list mBackwardFolders; - std::list mForwardFolders; - - boost::signals2::signal mRootChangedSignal; -}; - -/************************************************************************/ -/* Asset Pre-Filtered Inventory Panel related class */ -/* Exchanges filter's flexibility for speed of generation and */ -/* improved performance */ -/************************************************************************/ - -class LLAssetFilteredInventoryPanel : public LLInventoryPanel -{ -public: - struct Params - : public LLInitParam::Block - { - Mandatory filter_asset_types; - - Params() : filter_asset_types("filter_asset_types") {} - }; - - void initFromParams(const Params& p); -protected: - LLAssetFilteredInventoryPanel(const Params& p); - friend class LLUICtrlFactory; -public: - ~LLAssetFilteredInventoryPanel() {} - - /*virtual*/ bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) override; - -protected: - /*virtual*/ bool typedViewsFilter(const LLUUID& id, LLInventoryObject const* objectp) override; - /*virtual*/ void itemChanged(const LLUUID& item_id, U32 mask, const LLInventoryObject* model_item) override; - -private: - bool mAssetTypes[LLAssetType::AT_COUNT]; - bool mDragTypes[EDragAndDropType::DAD_COUNT]; -}; - -#endif // LL_LLINVENTORYPANEL_H +/** + * @file llinventorypanel.h + * @brief LLInventoryPanel + * class definition + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLINVENTORYPANEL_H +#define LL_LLINVENTORYPANEL_H + +#include "llassetstorage.h" +#include "llfolderviewitem.h" +#include "llfolderviewmodelinventory.h" +#include "llfloater.h" +#include "llinventory.h" +#include "llinventoryfilter.h" +#include "llinventorymodel.h" +#include "llscrollcontainer.h" +#include "lluictrlfactory.h" +#include + +class LLInvFVBridge; +class LLInventoryFolderViewModelBuilder; +class LLInvPanelComplObserver; +class LLFolderViewModelInventory; +class LLFolderViewGroupedItemBridge; + +namespace LLInitParam +{ + template<> + struct TypeValues : public TypeValuesHelper + { + static void declareValues(); + }; +} + +class LLInventoryPanel : public LLPanel +{ + //-------------------------------------------------------------------- + // Data + //-------------------------------------------------------------------- +public: + struct Filter : public LLInitParam::Block + { + Optional sort_order; + Optional types; + Optional search_string; + + Filter() + : sort_order("sort_order"), + types("types", 0xffffffff), + search_string("search_string") + {} + }; + + struct StartFolder : public LLInitParam::ChoiceBlock + { + Alternative name; + Alternative id; + Alternative type; + + StartFolder() + : name("name"), + id("id"), + type("type") + {} + }; + + struct Params + : public LLInitParam::Block + { + Optional sort_order_setting; + Optional inventory; + Optional allow_multi_select; + Optional allow_drag; + Optional show_item_link_overlays; + Optional filter; + Optional start_folder; + Optional use_label_suffix; + Optional show_empty_message; + Optional suppress_folder_menu; + Optional show_root_folder; + Optional allow_drop_on_root; + Optional use_marketplace_folders; + Optional scroll; + Optional accepts_drag_and_drop; + Optional folder_view; + Optional folder; + Optional item; + Optional open_first_folder; + + // All item and folder views will be initialized on init if true (default) + // Will initialize on visibility change otherwise. + Optional preinitialize_views; + + Params() + : sort_order_setting("sort_order_setting"), + inventory("", &gInventory), + allow_multi_select("allow_multi_select", true), + allow_drag("allow_drag", true), + show_item_link_overlays("show_item_link_overlays", false), + suppress_folder_menu("suppress_folder_menu", false), + filter("filter"), + start_folder("start_folder"), + use_label_suffix("use_label_suffix", true), + show_empty_message("show_empty_message", true), + show_root_folder("show_root_folder", false), + allow_drop_on_root("allow_drop_on_root", true), + use_marketplace_folders("use_marketplace_folders", false), + open_first_folder("open_first_folder", true), + scroll("scroll"), + accepts_drag_and_drop("accepts_drag_and_drop"), + folder_view("folder_view"), + folder("folder"), + item("item"), + preinitialize_views("preinitialize_views", true) + {} + }; + + struct InventoryState : public LLInitParam::Block + { + Mandatory filter; + Mandatory sort; + }; + + //-------------------------------------------------------------------- + // Initialization + //-------------------------------------------------------------------- +protected: + LLInventoryPanel(const Params&); + void initFromParams(const Params&); + + friend class LLUICtrlFactory; +public: + virtual ~LLInventoryPanel(); + +public: + typedef std::set selected_items_t; + + LLInventoryModel* getModel() { return mInventory; } + LLFolderViewModelInventory& getRootViewModel() { return mInventoryViewModel; } + + // LLView methods + /*virtual*/ void onVisibilityChange(bool new_visibility) override; + void draw() override; + /*virtual*/ bool handleKeyHere( KEY key, MASK mask ) override; + bool handleHover(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) override; + bool handleToolTip(S32 x, S32 y, MASK mask) override; + // LLUICtrl methods + /*virtual*/ void onFocusLost() override; + /*virtual*/ void onFocusReceived() override; + void onFolderOpening(const LLUUID &id); + + // LLBadgeHolder methods + bool addBadge(LLBadge * badge) override; + + // Call this method to set the selection. + void openAllFolders(); + void setSelection(const LLUUID& obj_id, bool take_keyboard_focus); + void setSelectCallback(const boost::function& items, bool user_action)>& cb); + void clearSelection(); + selected_items_t getSelectedItems() const; + + bool isSelectionRemovable(); + LLInventoryFilter& getFilter(); + const LLInventoryFilter& getFilter() const; + void setFilterTypes(U64 filter, LLInventoryFilter::EFilterType = LLInventoryFilter::FILTERTYPE_OBJECT); + void setFilterWorn(); + U32 getFilterObjectTypes() const; + void setFilterPermMask(PermissionMask filter_perm_mask); + U32 getFilterPermMask() const; + void setFilterWearableTypes(U64 filter); + void setFilterSettingsTypes(U64 filter); + void setFilterSubString(const std::string& string); + const std::string getFilterSubString(); + void setSinceLogoff(bool sl); + void setHoursAgo(U32 hours); + void setDateSearchDirection(U32 direction); + bool getSinceLogoff(); + void setFilterLinks(U64 filter_links); + void setSearchType(LLInventoryFilter::ESearchType type); + LLInventoryFilter::ESearchType getSearchType(); + + void setShowFolderState(LLInventoryFilter::EFolderShow show); + LLInventoryFilter::EFolderShow getShowFolderState(); + // This method is called when something has changed about the inventory. + void modelChanged(U32 mask); + LLFolderView* getRootFolder() { return mFolderRoot.get(); } + LLUUID getRootFolderID(); + LLScrollContainer* getScrollableContainer() { return mScroller; } + bool getAllowDropOnRoot() { return mParams.allow_drop_on_root; } + bool areViewsInitialized() { return mViewsInitialized == VIEWS_INITIALIZED && mFolderRoot.get() && !mFolderRoot.get()->needsArrange(); } + + void onSelectionChange(const std::deque &items, bool user_action); + + LLHandle getInventoryPanelHandle() const { return getDerivedHandle(); } + + // Callbacks + void doToSelected(const LLSD& userdata); + void doCreate(const LLSD& userdata); + bool beginIMSession(); + void fileUploadLocation(const LLSD& userdata); + void openSingleViewInventory(LLUUID folder_id = LLUUID()); + void purgeSelectedItems(); + bool attachObject(const LLSD& userdata); + static void idle(void* user_data); + + void updateFolderLabel(const LLUUID& folder_id); + + // DEBUG ONLY: + static void dumpSelectionInformation(void* user_data); + + void openSelected(); + void unSelectAll(); + + static void onIdle(void* user_data); + + // Find whichever inventory panel is active / on top. + // "Auto_open" determines if we open an inventory panel if none are open. + static LLInventoryPanel *getActiveInventoryPanel(bool auto_open = true); + + static void openInventoryPanelAndSetSelection(bool auto_open, + const LLUUID& obj_id, + bool use_main_panel = false, + bool take_keyboard_focus = true, + bool reset_filter = false); + static void setSFViewAndOpenFolder(const LLInventoryPanel* panel, const LLUUID& folder_id); + void addItemID(const LLUUID& id, LLFolderViewItem* itemp); + void removeItemID(const LLUUID& id); + LLFolderViewItem* getItemByID(const LLUUID& id); + LLFolderViewFolder* getFolderByID(const LLUUID& id); + void setSelectionByID(const LLUUID& obj_id, bool take_keyboard_focus); + void updateSelection(); + + void setSuppressOpenItemAction(bool supress_open_item) { mSuppressOpenItemAction = supress_open_item; } + + LLFolderViewModelInventory* getFolderViewModel() { return &mInventoryViewModel; } + const LLFolderViewModelInventory* getFolderViewModel() const { return &mInventoryViewModel; } + + // Clean up stuff when the folder root gets deleted + void clearFolderRoot(); + + static void callbackPurgeSelectedItems(const LLSD& notification, const LLSD& response, const std::vector inventory_selected); + + void changeFolderRoot(const LLUUID& new_id) {}; + void initFolderRoot(); + void initializeViewBuilding(); + +protected: + void openStartFolderOrMyInventory(); // open the first level of inventory + void onItemsCompletion(); // called when selected items are complete + + LLUUID mSelectThisID; + LLInventoryModel* mInventory; + LLInventoryObserver* mInventoryObserver; + LLInvPanelComplObserver* mCompletionObserver; + bool mFocusSelection; + bool mAcceptsDragAndDrop; + bool mAllowMultiSelect; + bool mAllowDrag; + bool mShowItemLinkOverlays; // Shows link graphic over inventory item icons + bool mShowEmptyMessage; + bool mSuppressFolderMenu; + bool mSuppressOpenItemAction; + + LLHandle mFolderRoot; + LLScrollContainer* mScroller; + + LLUUID mPreviousSelectedFolder; + + LLFolderViewModelInventory mInventoryViewModel; + LLPointer mGroupedItemBridge; + Params mParams; // stored copy of parameter block + + std::map mItemMap; + /** + * Pointer to LLInventoryFolderViewModelBuilder. + * + * It is set in LLInventoryPanel's constructor and can be overridden in derived classes with + * another implementation. + * Take into account it will not be deleted by LLInventoryPanel itself. + */ + const LLInventoryFolderViewModelBuilder* mInvFVBridgeBuilder; + + bool mBuildChildrenViews; // build root and children + bool mRootInited; + + + //-------------------------------------------------------------------- + // Sorting + //-------------------------------------------------------------------- +public: + static const std::string DEFAULT_SORT_ORDER; + static const std::string RECENTITEMS_SORT_ORDER; + static const std::string INHERIT_SORT_ORDER; + + void setSortOrder(U32 order); + U32 getSortOrder() const; + +private: + std::string mSortOrderSetting; + int mClipboardState; + + //-------------------------------------------------------------------- + // Hidden folders + //-------------------------------------------------------------------- +public: + void addHideFolderType(LLFolderType::EType folder_type); +protected: + // Builds the UI. Call this once the inventory is usable. + void initializeViews(F64 max_time); + + // Specific inventory colors + static bool sColorSetInitialized; + static LLUIColor sDefaultColor; + static LLUIColor sDefaultHighlightColor; + static LLUIColor sLibraryColor; + static LLUIColor sLinkColor; + + enum EBuildModes + { + BUILD_NO_LIMIT, + BUILD_TIMELIMIT, // requires mBuildViewsEndTime + BUILD_ONE_FOLDER, + BUILD_NO_CHILDREN, + }; + + // All buildNewViews() use BUILD_TIMELIMIT by default + // and expect time limit mBuildViewsEndTime to be set + LLFolderViewItem* buildNewViews(const LLUUID& id); + LLFolderViewItem* buildNewViews(const LLUUID& id, + LLInventoryObject const* objectp); + LLFolderViewItem* buildNewViews(const LLUUID& id, + LLInventoryObject const* objectp, + LLFolderViewItem *target_view, + const EBuildModes &mode = BUILD_TIMELIMIT); + + // if certain types are not allowed, no reason to create views + virtual bool typedViewsFilter(const LLUUID& id, LLInventoryObject const* objectp) { return true; } + + virtual void itemChanged(const LLUUID& item_id, U32 mask, const LLInventoryObject* model_item); + bool getIsHiddenFolderType(LLFolderType::EType folder_type) const; + + virtual LLFolderView * createFolderRoot(LLUUID root_id ); + virtual LLFolderViewFolder* createFolderViewFolder(LLInvFVBridge * bridge, bool allow_drop); + virtual LLFolderViewItem* createFolderViewItem(LLInvFVBridge * bridge); + + boost::function& items, bool user_action)> mSelectionCallback; +private: + // buildViewsTree does not include some checks and is meant + // for recursive use, use buildNewViews() for first call + LLFolderViewItem* buildViewsTree(const LLUUID& id, + const LLUUID& parent_id, + LLInventoryObject const* objectp, + LLFolderViewItem *target_view, + LLFolderViewFolder *parent_folder_view, + const EBuildModes &mode, + S32 depth = -1); + + typedef enum e_views_initialization_state + { + VIEWS_UNINITIALIZED = 0, + VIEWS_INITIALIZING, + VIEWS_BUILDING, // Root folder exists + VIEWS_INITIALIZED, + } EViewsInitializationState; + + bool mBuildViewsOnInit; + EViewsInitializationState mViewsInitialized; // Whether views have been generated + F64 mBuildViewsEndTime; // Stop building views past this timestamp + std::deque mBuildViewsQueue; +}; + + +class LLInventorySingleFolderPanel : public LLInventoryPanel +{ +public: + struct Params : public LLInitParam::Block + {}; + + void initFromParams(const Params& p); + void onFocusReceived() override; + + bool isSelectionRemovable() { return false; } + + void initFolderRoot(const LLUUID& start_folder_id = LLUUID::null); + + void changeFolderRoot(const LLUUID& new_id); + void onForwardFolder(); + void onBackwardFolder(); + void clearNavigationHistory(); + LLUUID getSingleFolderRoot() { return mFolderID; } + + void doCreate(const LLSD& userdata); + void doToSelected(const LLSD& userdata); + void doShare(); + + bool isBackwardAvailable(); + bool isForwardAvailable(); + + bool hasVisibleItems(); + + void setNavBackwardList(std::list backward_list) { mBackwardFolders = backward_list; } + void setNavForwardList(std::list forward_list) { mForwardFolders = forward_list; } + std::list getNavBackwardList() { return mBackwardFolders; } + std::list getNavForwardList() { return mForwardFolders; } + + typedef boost::function root_changed_callback_t; + boost::signals2::connection setRootChangedCallback(root_changed_callback_t cb); + +protected: + LLInventorySingleFolderPanel(const Params& params); + ~LLInventorySingleFolderPanel(); + void updateSingleFolderRoot(); + + friend class LLUICtrlFactory; + + LLUUID mFolderID; + std::list mBackwardFolders; + std::list mForwardFolders; + + boost::signals2::signal mRootChangedSignal; +}; + +/************************************************************************/ +/* Asset Pre-Filtered Inventory Panel related class */ +/* Exchanges filter's flexibility for speed of generation and */ +/* improved performance */ +/************************************************************************/ + +class LLAssetFilteredInventoryPanel : public LLInventoryPanel +{ +public: + struct Params + : public LLInitParam::Block + { + Mandatory filter_asset_types; + + Params() : filter_asset_types("filter_asset_types") {} + }; + + void initFromParams(const Params& p); +protected: + LLAssetFilteredInventoryPanel(const Params& p); + friend class LLUICtrlFactory; +public: + ~LLAssetFilteredInventoryPanel() {} + + /*virtual*/ bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) override; + +protected: + /*virtual*/ bool typedViewsFilter(const LLUUID& id, LLInventoryObject const* objectp) override; + /*virtual*/ void itemChanged(const LLUUID& item_id, U32 mask, const LLInventoryObject* model_item) override; + +private: + bool mAssetTypes[LLAssetType::AT_COUNT]; + bool mDragTypes[EDragAndDropType::DAD_COUNT]; +}; + +#endif // LL_LLINVENTORYPANEL_H diff --git a/indra/newview/lljoystickbutton.cpp b/indra/newview/lljoystickbutton.cpp index 18180b9923..00dbf9a9f8 100644 --- a/indra/newview/lljoystickbutton.cpp +++ b/indra/newview/lljoystickbutton.cpp @@ -1,944 +1,944 @@ -/** - * @file lljoystickbutton.cpp - * @brief LLJoystick class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lljoystickbutton.h" - -// Library includes -#include "llcoord.h" -#include "indra_constants.h" -#include "llrender.h" - -// Project includes -#include "llui.h" -#include "llagent.h" -#include "llagentcamera.h" -#include "llviewercamera.h" -#include "llviewertexture.h" -#include "llviewertexturelist.h" -#include "llviewerwindow.h" -#include "llmoveview.h" - -#include "llglheaders.h" - -static LLDefaultChildRegistry::Register r1("joystick_slide"); -static LLDefaultChildRegistry::Register r2("joystick_turn"); -static LLDefaultChildRegistry::Register r3("joystick_rotate"); -static LLDefaultChildRegistry::Register r5("joystick_track"); -static LLDefaultChildRegistry::Register r6("joystick_quat"); - - -const F32 NUDGE_TIME = 0.25f; // in seconds -const F32 ORBIT_NUDGE_RATE = 0.05f; // fraction of normal speed - -const S32 CENTER_DOT_RADIUS = 7; - -// -// Public Methods -// -void QuadrantNames::declareValues() -{ - declare("origin", JQ_ORIGIN); - declare("up", JQ_UP); - declare("down", JQ_DOWN); - declare("left", JQ_LEFT); - declare("right", JQ_RIGHT); -} - - -LLJoystick::LLJoystick(const LLJoystick::Params& p) -: LLButton(p), - mInitialOffset(0, 0), - mLastMouse(0, 0), - mFirstMouse(0, 0), - mVertSlopNear(0), - mVertSlopFar(0), - mHorizSlopNear(0), - mHorizSlopFar(0), - mHeldDown(false), - mHeldDownTimer(), - mInitialQuadrant(p.quadrant) -{ - setHeldDownCallback(&LLJoystick::onBtnHeldDown, this); -} - - -void LLJoystick::updateSlop() -{ - mVertSlopNear = getRect().getHeight(); - mVertSlopFar = getRect().getHeight() * 2; - - mHorizSlopNear = getRect().getWidth(); - mHorizSlopFar = getRect().getWidth() * 2; - - // Compute initial mouse offset based on initial quadrant. - // Place the mouse evenly between the near and far zones. - switch (mInitialQuadrant) - { - case JQ_ORIGIN: - mInitialOffset.set(0, 0); - break; - - case JQ_UP: - mInitialOffset.mX = 0; - mInitialOffset.mY = (mVertSlopNear + mVertSlopFar) / 2; - break; - - case JQ_DOWN: - mInitialOffset.mX = 0; - mInitialOffset.mY = - (mVertSlopNear + mVertSlopFar) / 2; - break; - - case JQ_LEFT: - mInitialOffset.mX = - (mHorizSlopNear + mHorizSlopFar) / 2; - mInitialOffset.mY = 0; - break; - - case JQ_RIGHT: - mInitialOffset.mX = (mHorizSlopNear + mHorizSlopFar) / 2; - mInitialOffset.mY = 0; - break; - - default: - LL_ERRS() << "LLJoystick::LLJoystick() - bad switch case" << LL_ENDL; - break; - } - - return; -} - -bool LLJoystick::pointInCircle(S32 x, S32 y) const -{ - if(this->getLocalRect().getHeight() != this->getLocalRect().getWidth()) - { - LL_WARNS() << "Joystick shape is not square"<getLocalRect().getHeight()/2; - bool in_circle = (x - center) * (x - center) + (y - center) * (y - center) <= center * center; - - return in_circle; -} - -bool LLJoystick::pointInCenterDot(S32 x, S32 y, S32 radius) const -{ - if (this->getLocalRect().getHeight() != this->getLocalRect().getWidth()) - { - LL_WARNS() << "Joystick shape is not square" << LL_ENDL; - return true; - } - - S32 center = this->getLocalRect().getHeight() / 2; - - bool in_center_circle = (x - center) * (x - center) + (y - center) * (y - center) <= radius * radius; - - return in_center_circle; -} - -bool LLJoystick::handleMouseDown(S32 x, S32 y, MASK mask) -{ - //LL_INFOS() << "joystick mouse down " << x << ", " << y << LL_ENDL; - bool handles = false; - - if(pointInCircle(x, y)) - { - mLastMouse.set(x, y); - mFirstMouse.set(x, y); - mMouseDownTimer.reset(); - handles = LLButton::handleMouseDown(x, y, mask); - } - - return handles; -} - - -bool LLJoystick::handleMouseUp(S32 x, S32 y, MASK mask) -{ - // LL_INFOS() << "joystick mouse up " << x << ", " << y << LL_ENDL; - - if( hasMouseCapture() ) - { - mLastMouse.set(x, y); - mHeldDown = false; - onMouseUp(); - } - - return LLButton::handleMouseUp(x, y, mask); -} - - -bool LLJoystick::handleHover(S32 x, S32 y, MASK mask) -{ - if( hasMouseCapture() ) - { - mLastMouse.set(x, y); - } - - return LLButton::handleHover(x, y, mask); -} - -F32 LLJoystick::getElapsedHeldDownTime() -{ - if( mHeldDown ) - { - return getHeldDownTime(); - } - else - { - return 0.f; - } -} - -// static -void LLJoystick::onBtnHeldDown(void *userdata) -{ - LLJoystick *self = (LLJoystick *)userdata; - if (self) - { - self->mHeldDown = true; - self->onHeldDown(); - } -} - -EJoystickQuadrant LLJoystick::selectQuadrant(LLXMLNodePtr node) -{ - - EJoystickQuadrant quadrant = JQ_RIGHT; - - if (node->hasAttribute("quadrant")) - { - std::string quadrant_name; - node->getAttributeString("quadrant", quadrant_name); - - quadrant = quadrantFromName(quadrant_name); - } - return quadrant; -} - - -std::string LLJoystick::nameFromQuadrant(EJoystickQuadrant quadrant) -{ - if (quadrant == JQ_ORIGIN) return std::string("origin"); - else if (quadrant == JQ_UP) return std::string("up"); - else if (quadrant == JQ_DOWN) return std::string("down"); - else if (quadrant == JQ_LEFT) return std::string("left"); - else if (quadrant == JQ_RIGHT) return std::string("right"); - else return std::string(); -} - - -EJoystickQuadrant LLJoystick::quadrantFromName(const std::string& sQuadrant) -{ - EJoystickQuadrant quadrant = JQ_RIGHT; - - if (sQuadrant == "origin") - { - quadrant = JQ_ORIGIN; - } - else if (sQuadrant == "up") - { - quadrant = JQ_UP; - } - else if (sQuadrant == "down") - { - quadrant = JQ_DOWN; - } - else if (sQuadrant == "left") - { - quadrant = JQ_LEFT; - } - else if (sQuadrant == "right") - { - quadrant = JQ_RIGHT; - } - - return quadrant; -} - - -//------------------------------------------------------------------------------- -// LLJoystickAgentTurn -//------------------------------------------------------------------------------- - -void LLJoystickAgentTurn::onHeldDown() -{ - F32 time = getElapsedHeldDownTime(); - updateSlop(); - - //LL_INFOS() << "move forward/backward (and/or turn)" << LL_ENDL; - - S32 dx = mLastMouse.mX - mFirstMouse.mX + mInitialOffset.mX; - S32 dy = mLastMouse.mY - mFirstMouse.mY + mInitialOffset.mY; - - float m = (float) (dx)/abs(dy); - - if (m > 1) { - m = 1; - } - else if (m < -1) { - m = -1; - } - gAgent.moveYaw(-LLFloaterMove::getYawRate(time)*m); - - - // handle forward/back movement - if (dy > mVertSlopFar) - { - // ...if mouse is forward of run region run forward - gAgent.moveAt(1); - } - else if (dy > mVertSlopNear) - { - if( time < NUDGE_TIME ) - { - gAgent.moveAtNudge(1); - } - else - { - // ...else if mouse is forward of walk region walk forward - // JC 9/5/2002 - Always run / move quickly. - gAgent.moveAt(1); - } - } - else if (dy < -mVertSlopFar) - { - // ...else if mouse is behind run region run backward - gAgent.moveAt(-1); - } - else if (dy < -mVertSlopNear) - { - if( time < NUDGE_TIME ) - { - gAgent.moveAtNudge(-1); - } - else - { - // ...else if mouse is behind walk region walk backward - // JC 9/5/2002 - Always run / move quickly. - gAgent.moveAt(-1); - } - } -} - -//------------------------------------------------------------------------------- -// LLJoystickAgentSlide -//------------------------------------------------------------------------------- - -void LLJoystickAgentSlide::onMouseUp() -{ - F32 time = getElapsedHeldDownTime(); - if( time < NUDGE_TIME ) - { - switch (mInitialQuadrant) - { - case JQ_LEFT: - gAgent.moveLeftNudge(1); - break; - - case JQ_RIGHT: - gAgent.moveLeftNudge(-1); - break; - - default: - break; - } - } -} - -void LLJoystickAgentSlide::onHeldDown() -{ - //LL_INFOS() << "slide left/right (and/or move forward/backward)" << LL_ENDL; - - updateSlop(); - - S32 dx = mLastMouse.mX - mFirstMouse.mX + mInitialOffset.mX; - S32 dy = mLastMouse.mY - mFirstMouse.mY + mInitialOffset.mY; - - // handle left-right sliding - if (dx > mHorizSlopNear) - { - gAgent.moveLeft(-1); - } - else if (dx < -mHorizSlopNear) - { - gAgent.moveLeft(1); - } - - // handle forward/back movement - if (dy > mVertSlopFar) - { - // ...if mouse is forward of run region run forward - gAgent.moveAt(1); - } - else if (dy > mVertSlopNear) - { - // ...else if mouse is forward of walk region walk forward - gAgent.moveAtNudge(1); - } - else if (dy < -mVertSlopFar) - { - // ...else if mouse is behind run region run backward - gAgent.moveAt(-1); - } - else if (dy < -mVertSlopNear) - { - // ...else if mouse is behind walk region walk backward - gAgent.moveAtNudge(-1); - } -} - - -//------------------------------------------------------------------------------- -// LLJoystickCameraRotate -//------------------------------------------------------------------------------- - -LLJoystickCameraRotate::LLJoystickCameraRotate(const LLJoystickCameraRotate::Params& p) -: LLJoystick(p), - mInLeft( false ), - mInTop( false ), - mInRight( false ), - mInBottom( false ), - mInCenter( false ) -{ - mCenterImageName = "Cam_Rotate_Center"; -} - - -void LLJoystickCameraRotate::updateSlop() -{ - // do the initial offset calculation based on mousedown location - - // small fixed slop region - mVertSlopNear = 16; - mVertSlopFar = 32; - - mHorizSlopNear = 16; - mHorizSlopFar = 32; - - return; -} - - -bool LLJoystickCameraRotate::handleMouseDown(S32 x, S32 y, MASK mask) -{ - gAgent.setMovementLocked(true); - updateSlop(); - - // Set initial offset based on initial click location - S32 horiz_center = getRect().getWidth() / 2; - S32 vert_center = getRect().getHeight() / 2; - - S32 dx = x - horiz_center; - S32 dy = y - vert_center; - - if (pointInCenterDot(x, y, CENTER_DOT_RADIUS)) - { - mInitialOffset.mX = 0; - mInitialOffset.mY = 0; - mInitialQuadrant = JQ_ORIGIN; - mInCenter = true; - - resetJoystickCamera(); - } - else if (dy > dx && dy > -dx) - { - // top - mInitialOffset.mX = 0; - mInitialOffset.mY = (mVertSlopNear + mVertSlopFar) / 2; - mInitialQuadrant = JQ_UP; - } - else if (dy > dx && dy <= -dx) - { - // left - mInitialOffset.mX = - (mHorizSlopNear + mHorizSlopFar) / 2; - mInitialOffset.mY = 0; - mInitialQuadrant = JQ_LEFT; - } - else if (dy <= dx && dy <= -dx) - { - // bottom - mInitialOffset.mX = 0; - mInitialOffset.mY = - (mVertSlopNear + mVertSlopFar) / 2; - mInitialQuadrant = JQ_DOWN; - } - else - { - // right - mInitialOffset.mX = (mHorizSlopNear + mHorizSlopFar) / 2; - mInitialOffset.mY = 0; - mInitialQuadrant = JQ_RIGHT; - } - - return LLJoystick::handleMouseDown(x, y, mask); -} - -bool LLJoystickCameraRotate::handleMouseUp(S32 x, S32 y, MASK mask) -{ - gAgent.setMovementLocked(false); - mInCenter = false; - return LLJoystick::handleMouseUp(x, y, mask); -} - -bool LLJoystickCameraRotate::handleHover(S32 x, S32 y, MASK mask) -{ - if (!pointInCenterDot(x, y, CENTER_DOT_RADIUS)) - { - mInCenter = false; - } - - return LLJoystick::handleHover(x, y, mask); -} - -void LLJoystickCameraRotate::onHeldDown() -{ - updateSlop(); - - S32 dx = mLastMouse.mX - mFirstMouse.mX + mInitialOffset.mX; - S32 dy = mLastMouse.mY - mFirstMouse.mY + mInitialOffset.mY; - - // left-right rotation - if (dx > mHorizSlopNear) - { - gAgentCamera.unlockView(); - gAgentCamera.setOrbitLeftKey(getOrbitRate()); - } - else if (dx < -mHorizSlopNear) - { - gAgentCamera.unlockView(); - gAgentCamera.setOrbitRightKey(getOrbitRate()); - } - - // over/under rotation - if (dy > mVertSlopNear) - { - gAgentCamera.unlockView(); - gAgentCamera.setOrbitUpKey(getOrbitRate()); - } - else if (dy < -mVertSlopNear) - { - gAgentCamera.unlockView(); - gAgentCamera.setOrbitDownKey(getOrbitRate()); - } -} - -void LLJoystickCameraRotate::resetJoystickCamera() -{ - gAgentCamera.resetCameraOrbit(); -} - -F32 LLJoystickCameraRotate::getOrbitRate() -{ - F32 time = getElapsedHeldDownTime(); - if( time < NUDGE_TIME ) - { - F32 rate = ORBIT_NUDGE_RATE + time * (1 - ORBIT_NUDGE_RATE)/ NUDGE_TIME; - //LL_INFOS() << rate << LL_ENDL; - return rate; - } - else - { - return 1; - } -} - - -// Only used for drawing -void LLJoystickCameraRotate::setToggleState( bool left, bool top, bool right, bool bottom ) -{ - mInLeft = left; - mInTop = top; - mInRight = right; - mInBottom = bottom; -} - -void LLJoystickCameraRotate::draw() -{ - LLGLSUIDefault gls_ui; - - getImageUnselected()->draw( 0, 0 ); - LLPointer image = getImageSelected(); - - if (mInCenter) - { - drawRotatedImage(LLUI::getUIImage(mCenterImageName), 0); - } - else - { - if (mInTop) - { - drawRotatedImage(getImageSelected(), 0); - } - - if (mInRight) - { - drawRotatedImage(getImageSelected(), 1); - } - - if (mInBottom) - { - drawRotatedImage(getImageSelected(), 2); - } - - if (mInLeft) - { - drawRotatedImage(getImageSelected(), 3); - } - } -} - -// Draws image rotated by multiples of 90 degrees -void LLJoystickCameraRotate::drawRotatedImage( LLPointer image, S32 rotations ) -{ - S32 width = image->getWidth(); - S32 height = image->getHeight(); - LLTexture* texture = image->getImage(); - - /* - * Scale texture coordinate system - * to handle the different between image size and size of texture. - * If we will use default matrix, - * it may break texture mapping after rotation. - * see EXT-2023 Camera floater: arrows became shifted when pressed. - */ - F32 uv[][2] = - { - { (F32)width/texture->getWidth(), (F32)height/texture->getHeight() }, - { 0.f, (F32)height/texture->getHeight() }, - { 0.f, 0.f }, - { (F32)width/texture->getWidth(), 0.f } - }; - - gGL.getTexUnit(0)->bind(texture); - - gGL.color4fv(UI_VERTEX_COLOR.mV); - - gGL.begin(LLRender::QUADS); - { - gGL.texCoord2fv( uv[ (rotations + 0) % 4]); - gGL.vertex2i(width, height ); - - gGL.texCoord2fv( uv[ (rotations + 1) % 4]); - gGL.vertex2i(0, height ); - - gGL.texCoord2fv( uv[ (rotations + 2) % 4]); - gGL.vertex2i(0, 0); - - gGL.texCoord2fv( uv[ (rotations + 3) % 4]); - gGL.vertex2i(width, 0); - } - gGL.end(); -} - - - -//------------------------------------------------------------------------------- -// LLJoystickCameraTrack -//------------------------------------------------------------------------------- - -LLJoystickCameraTrack::Params::Params() -{ - held_down_delay.seconds(0.0); -} - -LLJoystickCameraTrack::LLJoystickCameraTrack(const LLJoystickCameraTrack::Params& p) -: LLJoystickCameraRotate(p) -{ - mCenterImageName = "Cam_Tracking_Center"; -} - - -void LLJoystickCameraTrack::onHeldDown() -{ - updateSlop(); - - S32 dx = mLastMouse.mX - mFirstMouse.mX + mInitialOffset.mX; - S32 dy = mLastMouse.mY - mFirstMouse.mY + mInitialOffset.mY; - - if (dx > mVertSlopNear) - { - gAgentCamera.unlockView(); - gAgentCamera.setPanRightKey(getOrbitRate()); - } - else if (dx < -mVertSlopNear) - { - gAgentCamera.unlockView(); - gAgentCamera.setPanLeftKey(getOrbitRate()); - } - - // over/under rotation - if (dy > mVertSlopNear) - { - gAgentCamera.unlockView(); - gAgentCamera.setPanUpKey(getOrbitRate()); - } - else if (dy < -mVertSlopNear) - { - gAgentCamera.unlockView(); - gAgentCamera.setPanDownKey(getOrbitRate()); - } -} - -void LLJoystickCameraTrack::resetJoystickCamera() -{ - gAgentCamera.resetCameraPan(); -} - -//------------------------------------------------------------------------------- -// LLJoystickQuaternion -//------------------------------------------------------------------------------- - -LLJoystickQuaternion::Params::Params() -{ -} - -LLJoystickQuaternion::LLJoystickQuaternion(const LLJoystickQuaternion::Params &p): - LLJoystick(p), - mInLeft(false), - mInTop(false), - mInRight(false), - mInBottom(false), - mVectorZero(0.0f, 0.0f, 1.0f), - mRotation(), - mUpDnAxis(1.0f, 0.0f, 0.0f), - mLfRtAxis(0.0f, 0.0f, 1.0f), - mXAxisIndex(2), // left & right across the control - mYAxisIndex(0), // up & down across the control - mZAxisIndex(1) // tested for above and below -{ - for (int i = 0; i < 3; ++i) - { - mLfRtAxis.mV[i] = (mXAxisIndex == i) ? 1.0 : 0.0; - mUpDnAxis.mV[i] = (mYAxisIndex == i) ? 1.0 : 0.0; - } -} - -void LLJoystickQuaternion::setToggleState(bool left, bool top, bool right, bool bottom) -{ - mInLeft = left; - mInTop = top; - mInRight = right; - mInBottom = bottom; -} - -bool LLJoystickQuaternion::handleMouseDown(S32 x, S32 y, MASK mask) -{ - updateSlop(); - - // Set initial offset based on initial click location - S32 horiz_center = getRect().getWidth() / 2; - S32 vert_center = getRect().getHeight() / 2; - - S32 dx = x - horiz_center; - S32 dy = y - vert_center; - - if (dy > dx && dy > -dx) - { - // top - mInitialOffset.mX = 0; - mInitialOffset.mY = (mVertSlopNear + mVertSlopFar) / 2; - mInitialQuadrant = JQ_UP; - } - else if (dy > dx && dy <= -dx) - { - // left - mInitialOffset.mX = -(mHorizSlopNear + mHorizSlopFar) / 2; - mInitialOffset.mY = 0; - mInitialQuadrant = JQ_LEFT; - } - else if (dy <= dx && dy <= -dx) - { - // bottom - mInitialOffset.mX = 0; - mInitialOffset.mY = -(mVertSlopNear + mVertSlopFar) / 2; - mInitialQuadrant = JQ_DOWN; - } - else - { - // right - mInitialOffset.mX = (mHorizSlopNear + mHorizSlopFar) / 2; - mInitialOffset.mY = 0; - mInitialQuadrant = JQ_RIGHT; - } - - return LLJoystick::handleMouseDown(x, y, mask); -} - -bool LLJoystickQuaternion::handleMouseUp(S32 x, S32 y, MASK mask) -{ - return LLJoystick::handleMouseUp(x, y, mask); -} - -void LLJoystickQuaternion::onHeldDown() -{ - LLVector3 axis; - updateSlop(); - - S32 dx = mLastMouse.mX - mFirstMouse.mX + mInitialOffset.mX; - S32 dy = mLastMouse.mY - mFirstMouse.mY + mInitialOffset.mY; - - // left-right rotation - if (dx > mHorizSlopNear) - { - axis += mUpDnAxis; - } - else if (dx < -mHorizSlopNear) - { - axis -= mUpDnAxis; - } - - // over/under rotation - if (dy > mVertSlopNear) - { - axis += mLfRtAxis; - } - else if (dy < -mVertSlopNear) - { - axis -= mLfRtAxis; - } - - if (axis.isNull()) - return; - - axis.normalize(); - - LLQuaternion delta; - delta.setAngleAxis(0.0523599f, axis); // about 3deg - - mRotation *= delta; - setValue(mRotation.getValue()); - onCommit(); -} - -void LLJoystickQuaternion::draw() -{ - LLGLSUIDefault gls_ui; - - getImageUnselected()->draw(0, 0); - LLPointer image = getImageSelected(); - - if (mInTop) - { - drawRotatedImage(getImageSelected(), 0); - } - - if (mInRight) - { - drawRotatedImage(getImageSelected(), 1); - } - - if (mInBottom) - { - drawRotatedImage(getImageSelected(), 2); - } - - if (mInLeft) - { - drawRotatedImage(getImageSelected(), 3); - } - - LLVector3 draw_point = mVectorZero * mRotation; - S32 halfwidth = getRect().getWidth() / 2; - S32 halfheight = getRect().getHeight() / 2; - draw_point.mV[mXAxisIndex] = (draw_point.mV[mXAxisIndex] + 1.0) * halfwidth; - draw_point.mV[mYAxisIndex] = (draw_point.mV[mYAxisIndex] + 1.0) * halfheight; - - gl_circle_2d(draw_point.mV[mXAxisIndex], draw_point.mV[mYAxisIndex], 4, 8, - draw_point.mV[mZAxisIndex] >= 0.f); - -} - -F32 LLJoystickQuaternion::getOrbitRate() -{ - return 1; -} - -void LLJoystickQuaternion::updateSlop() -{ - // small fixed slop region - mVertSlopNear = 16; - mVertSlopFar = 32; - - mHorizSlopNear = 16; - mHorizSlopFar = 32; -} - -void LLJoystickQuaternion::drawRotatedImage(LLPointer image, S32 rotations) -{ - S32 width = image->getWidth(); - S32 height = image->getHeight(); - LLTexture* texture = image->getImage(); - - /* - * Scale texture coordinate system - * to handle the different between image size and size of texture. - */ - F32 uv[][2] = - { - { (F32)width / texture->getWidth(), (F32)height / texture->getHeight() }, - { 0.f, (F32)height / texture->getHeight() }, - { 0.f, 0.f }, - { (F32)width / texture->getWidth(), 0.f } - }; - - gGL.getTexUnit(0)->bind(texture); - - gGL.color4fv(UI_VERTEX_COLOR.mV); - - gGL.begin(LLRender::QUADS); - { - gGL.texCoord2fv(uv[(rotations + 0) % 4]); - gGL.vertex2i(width, height); - - gGL.texCoord2fv(uv[(rotations + 1) % 4]); - gGL.vertex2i(0, height); - - gGL.texCoord2fv(uv[(rotations + 2) % 4]); - gGL.vertex2i(0, 0); - - gGL.texCoord2fv(uv[(rotations + 3) % 4]); - gGL.vertex2i(width, 0); - } - gGL.end(); -} - -void LLJoystickQuaternion::setRotation(const LLQuaternion &value) -{ - if (value != mRotation) - { - mRotation = value; - mRotation.normalize(); - LLJoystick::setValue(mRotation.getValue()); - } -} - -LLQuaternion LLJoystickQuaternion::getRotation() const -{ - return mRotation; -} - - +/** + * @file lljoystickbutton.cpp + * @brief LLJoystick class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lljoystickbutton.h" + +// Library includes +#include "llcoord.h" +#include "indra_constants.h" +#include "llrender.h" + +// Project includes +#include "llui.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llviewercamera.h" +#include "llviewertexture.h" +#include "llviewertexturelist.h" +#include "llviewerwindow.h" +#include "llmoveview.h" + +#include "llglheaders.h" + +static LLDefaultChildRegistry::Register r1("joystick_slide"); +static LLDefaultChildRegistry::Register r2("joystick_turn"); +static LLDefaultChildRegistry::Register r3("joystick_rotate"); +static LLDefaultChildRegistry::Register r5("joystick_track"); +static LLDefaultChildRegistry::Register r6("joystick_quat"); + + +const F32 NUDGE_TIME = 0.25f; // in seconds +const F32 ORBIT_NUDGE_RATE = 0.05f; // fraction of normal speed + +const S32 CENTER_DOT_RADIUS = 7; + +// +// Public Methods +// +void QuadrantNames::declareValues() +{ + declare("origin", JQ_ORIGIN); + declare("up", JQ_UP); + declare("down", JQ_DOWN); + declare("left", JQ_LEFT); + declare("right", JQ_RIGHT); +} + + +LLJoystick::LLJoystick(const LLJoystick::Params& p) +: LLButton(p), + mInitialOffset(0, 0), + mLastMouse(0, 0), + mFirstMouse(0, 0), + mVertSlopNear(0), + mVertSlopFar(0), + mHorizSlopNear(0), + mHorizSlopFar(0), + mHeldDown(false), + mHeldDownTimer(), + mInitialQuadrant(p.quadrant) +{ + setHeldDownCallback(&LLJoystick::onBtnHeldDown, this); +} + + +void LLJoystick::updateSlop() +{ + mVertSlopNear = getRect().getHeight(); + mVertSlopFar = getRect().getHeight() * 2; + + mHorizSlopNear = getRect().getWidth(); + mHorizSlopFar = getRect().getWidth() * 2; + + // Compute initial mouse offset based on initial quadrant. + // Place the mouse evenly between the near and far zones. + switch (mInitialQuadrant) + { + case JQ_ORIGIN: + mInitialOffset.set(0, 0); + break; + + case JQ_UP: + mInitialOffset.mX = 0; + mInitialOffset.mY = (mVertSlopNear + mVertSlopFar) / 2; + break; + + case JQ_DOWN: + mInitialOffset.mX = 0; + mInitialOffset.mY = - (mVertSlopNear + mVertSlopFar) / 2; + break; + + case JQ_LEFT: + mInitialOffset.mX = - (mHorizSlopNear + mHorizSlopFar) / 2; + mInitialOffset.mY = 0; + break; + + case JQ_RIGHT: + mInitialOffset.mX = (mHorizSlopNear + mHorizSlopFar) / 2; + mInitialOffset.mY = 0; + break; + + default: + LL_ERRS() << "LLJoystick::LLJoystick() - bad switch case" << LL_ENDL; + break; + } + + return; +} + +bool LLJoystick::pointInCircle(S32 x, S32 y) const +{ + if(this->getLocalRect().getHeight() != this->getLocalRect().getWidth()) + { + LL_WARNS() << "Joystick shape is not square"<getLocalRect().getHeight()/2; + bool in_circle = (x - center) * (x - center) + (y - center) * (y - center) <= center * center; + + return in_circle; +} + +bool LLJoystick::pointInCenterDot(S32 x, S32 y, S32 radius) const +{ + if (this->getLocalRect().getHeight() != this->getLocalRect().getWidth()) + { + LL_WARNS() << "Joystick shape is not square" << LL_ENDL; + return true; + } + + S32 center = this->getLocalRect().getHeight() / 2; + + bool in_center_circle = (x - center) * (x - center) + (y - center) * (y - center) <= radius * radius; + + return in_center_circle; +} + +bool LLJoystick::handleMouseDown(S32 x, S32 y, MASK mask) +{ + //LL_INFOS() << "joystick mouse down " << x << ", " << y << LL_ENDL; + bool handles = false; + + if(pointInCircle(x, y)) + { + mLastMouse.set(x, y); + mFirstMouse.set(x, y); + mMouseDownTimer.reset(); + handles = LLButton::handleMouseDown(x, y, mask); + } + + return handles; +} + + +bool LLJoystick::handleMouseUp(S32 x, S32 y, MASK mask) +{ + // LL_INFOS() << "joystick mouse up " << x << ", " << y << LL_ENDL; + + if( hasMouseCapture() ) + { + mLastMouse.set(x, y); + mHeldDown = false; + onMouseUp(); + } + + return LLButton::handleMouseUp(x, y, mask); +} + + +bool LLJoystick::handleHover(S32 x, S32 y, MASK mask) +{ + if( hasMouseCapture() ) + { + mLastMouse.set(x, y); + } + + return LLButton::handleHover(x, y, mask); +} + +F32 LLJoystick::getElapsedHeldDownTime() +{ + if( mHeldDown ) + { + return getHeldDownTime(); + } + else + { + return 0.f; + } +} + +// static +void LLJoystick::onBtnHeldDown(void *userdata) +{ + LLJoystick *self = (LLJoystick *)userdata; + if (self) + { + self->mHeldDown = true; + self->onHeldDown(); + } +} + +EJoystickQuadrant LLJoystick::selectQuadrant(LLXMLNodePtr node) +{ + + EJoystickQuadrant quadrant = JQ_RIGHT; + + if (node->hasAttribute("quadrant")) + { + std::string quadrant_name; + node->getAttributeString("quadrant", quadrant_name); + + quadrant = quadrantFromName(quadrant_name); + } + return quadrant; +} + + +std::string LLJoystick::nameFromQuadrant(EJoystickQuadrant quadrant) +{ + if (quadrant == JQ_ORIGIN) return std::string("origin"); + else if (quadrant == JQ_UP) return std::string("up"); + else if (quadrant == JQ_DOWN) return std::string("down"); + else if (quadrant == JQ_LEFT) return std::string("left"); + else if (quadrant == JQ_RIGHT) return std::string("right"); + else return std::string(); +} + + +EJoystickQuadrant LLJoystick::quadrantFromName(const std::string& sQuadrant) +{ + EJoystickQuadrant quadrant = JQ_RIGHT; + + if (sQuadrant == "origin") + { + quadrant = JQ_ORIGIN; + } + else if (sQuadrant == "up") + { + quadrant = JQ_UP; + } + else if (sQuadrant == "down") + { + quadrant = JQ_DOWN; + } + else if (sQuadrant == "left") + { + quadrant = JQ_LEFT; + } + else if (sQuadrant == "right") + { + quadrant = JQ_RIGHT; + } + + return quadrant; +} + + +//------------------------------------------------------------------------------- +// LLJoystickAgentTurn +//------------------------------------------------------------------------------- + +void LLJoystickAgentTurn::onHeldDown() +{ + F32 time = getElapsedHeldDownTime(); + updateSlop(); + + //LL_INFOS() << "move forward/backward (and/or turn)" << LL_ENDL; + + S32 dx = mLastMouse.mX - mFirstMouse.mX + mInitialOffset.mX; + S32 dy = mLastMouse.mY - mFirstMouse.mY + mInitialOffset.mY; + + float m = (float) (dx)/abs(dy); + + if (m > 1) { + m = 1; + } + else if (m < -1) { + m = -1; + } + gAgent.moveYaw(-LLFloaterMove::getYawRate(time)*m); + + + // handle forward/back movement + if (dy > mVertSlopFar) + { + // ...if mouse is forward of run region run forward + gAgent.moveAt(1); + } + else if (dy > mVertSlopNear) + { + if( time < NUDGE_TIME ) + { + gAgent.moveAtNudge(1); + } + else + { + // ...else if mouse is forward of walk region walk forward + // JC 9/5/2002 - Always run / move quickly. + gAgent.moveAt(1); + } + } + else if (dy < -mVertSlopFar) + { + // ...else if mouse is behind run region run backward + gAgent.moveAt(-1); + } + else if (dy < -mVertSlopNear) + { + if( time < NUDGE_TIME ) + { + gAgent.moveAtNudge(-1); + } + else + { + // ...else if mouse is behind walk region walk backward + // JC 9/5/2002 - Always run / move quickly. + gAgent.moveAt(-1); + } + } +} + +//------------------------------------------------------------------------------- +// LLJoystickAgentSlide +//------------------------------------------------------------------------------- + +void LLJoystickAgentSlide::onMouseUp() +{ + F32 time = getElapsedHeldDownTime(); + if( time < NUDGE_TIME ) + { + switch (mInitialQuadrant) + { + case JQ_LEFT: + gAgent.moveLeftNudge(1); + break; + + case JQ_RIGHT: + gAgent.moveLeftNudge(-1); + break; + + default: + break; + } + } +} + +void LLJoystickAgentSlide::onHeldDown() +{ + //LL_INFOS() << "slide left/right (and/or move forward/backward)" << LL_ENDL; + + updateSlop(); + + S32 dx = mLastMouse.mX - mFirstMouse.mX + mInitialOffset.mX; + S32 dy = mLastMouse.mY - mFirstMouse.mY + mInitialOffset.mY; + + // handle left-right sliding + if (dx > mHorizSlopNear) + { + gAgent.moveLeft(-1); + } + else if (dx < -mHorizSlopNear) + { + gAgent.moveLeft(1); + } + + // handle forward/back movement + if (dy > mVertSlopFar) + { + // ...if mouse is forward of run region run forward + gAgent.moveAt(1); + } + else if (dy > mVertSlopNear) + { + // ...else if mouse is forward of walk region walk forward + gAgent.moveAtNudge(1); + } + else if (dy < -mVertSlopFar) + { + // ...else if mouse is behind run region run backward + gAgent.moveAt(-1); + } + else if (dy < -mVertSlopNear) + { + // ...else if mouse is behind walk region walk backward + gAgent.moveAtNudge(-1); + } +} + + +//------------------------------------------------------------------------------- +// LLJoystickCameraRotate +//------------------------------------------------------------------------------- + +LLJoystickCameraRotate::LLJoystickCameraRotate(const LLJoystickCameraRotate::Params& p) +: LLJoystick(p), + mInLeft( false ), + mInTop( false ), + mInRight( false ), + mInBottom( false ), + mInCenter( false ) +{ + mCenterImageName = "Cam_Rotate_Center"; +} + + +void LLJoystickCameraRotate::updateSlop() +{ + // do the initial offset calculation based on mousedown location + + // small fixed slop region + mVertSlopNear = 16; + mVertSlopFar = 32; + + mHorizSlopNear = 16; + mHorizSlopFar = 32; + + return; +} + + +bool LLJoystickCameraRotate::handleMouseDown(S32 x, S32 y, MASK mask) +{ + gAgent.setMovementLocked(true); + updateSlop(); + + // Set initial offset based on initial click location + S32 horiz_center = getRect().getWidth() / 2; + S32 vert_center = getRect().getHeight() / 2; + + S32 dx = x - horiz_center; + S32 dy = y - vert_center; + + if (pointInCenterDot(x, y, CENTER_DOT_RADIUS)) + { + mInitialOffset.mX = 0; + mInitialOffset.mY = 0; + mInitialQuadrant = JQ_ORIGIN; + mInCenter = true; + + resetJoystickCamera(); + } + else if (dy > dx && dy > -dx) + { + // top + mInitialOffset.mX = 0; + mInitialOffset.mY = (mVertSlopNear + mVertSlopFar) / 2; + mInitialQuadrant = JQ_UP; + } + else if (dy > dx && dy <= -dx) + { + // left + mInitialOffset.mX = - (mHorizSlopNear + mHorizSlopFar) / 2; + mInitialOffset.mY = 0; + mInitialQuadrant = JQ_LEFT; + } + else if (dy <= dx && dy <= -dx) + { + // bottom + mInitialOffset.mX = 0; + mInitialOffset.mY = - (mVertSlopNear + mVertSlopFar) / 2; + mInitialQuadrant = JQ_DOWN; + } + else + { + // right + mInitialOffset.mX = (mHorizSlopNear + mHorizSlopFar) / 2; + mInitialOffset.mY = 0; + mInitialQuadrant = JQ_RIGHT; + } + + return LLJoystick::handleMouseDown(x, y, mask); +} + +bool LLJoystickCameraRotate::handleMouseUp(S32 x, S32 y, MASK mask) +{ + gAgent.setMovementLocked(false); + mInCenter = false; + return LLJoystick::handleMouseUp(x, y, mask); +} + +bool LLJoystickCameraRotate::handleHover(S32 x, S32 y, MASK mask) +{ + if (!pointInCenterDot(x, y, CENTER_DOT_RADIUS)) + { + mInCenter = false; + } + + return LLJoystick::handleHover(x, y, mask); +} + +void LLJoystickCameraRotate::onHeldDown() +{ + updateSlop(); + + S32 dx = mLastMouse.mX - mFirstMouse.mX + mInitialOffset.mX; + S32 dy = mLastMouse.mY - mFirstMouse.mY + mInitialOffset.mY; + + // left-right rotation + if (dx > mHorizSlopNear) + { + gAgentCamera.unlockView(); + gAgentCamera.setOrbitLeftKey(getOrbitRate()); + } + else if (dx < -mHorizSlopNear) + { + gAgentCamera.unlockView(); + gAgentCamera.setOrbitRightKey(getOrbitRate()); + } + + // over/under rotation + if (dy > mVertSlopNear) + { + gAgentCamera.unlockView(); + gAgentCamera.setOrbitUpKey(getOrbitRate()); + } + else if (dy < -mVertSlopNear) + { + gAgentCamera.unlockView(); + gAgentCamera.setOrbitDownKey(getOrbitRate()); + } +} + +void LLJoystickCameraRotate::resetJoystickCamera() +{ + gAgentCamera.resetCameraOrbit(); +} + +F32 LLJoystickCameraRotate::getOrbitRate() +{ + F32 time = getElapsedHeldDownTime(); + if( time < NUDGE_TIME ) + { + F32 rate = ORBIT_NUDGE_RATE + time * (1 - ORBIT_NUDGE_RATE)/ NUDGE_TIME; + //LL_INFOS() << rate << LL_ENDL; + return rate; + } + else + { + return 1; + } +} + + +// Only used for drawing +void LLJoystickCameraRotate::setToggleState( bool left, bool top, bool right, bool bottom ) +{ + mInLeft = left; + mInTop = top; + mInRight = right; + mInBottom = bottom; +} + +void LLJoystickCameraRotate::draw() +{ + LLGLSUIDefault gls_ui; + + getImageUnselected()->draw( 0, 0 ); + LLPointer image = getImageSelected(); + + if (mInCenter) + { + drawRotatedImage(LLUI::getUIImage(mCenterImageName), 0); + } + else + { + if (mInTop) + { + drawRotatedImage(getImageSelected(), 0); + } + + if (mInRight) + { + drawRotatedImage(getImageSelected(), 1); + } + + if (mInBottom) + { + drawRotatedImage(getImageSelected(), 2); + } + + if (mInLeft) + { + drawRotatedImage(getImageSelected(), 3); + } + } +} + +// Draws image rotated by multiples of 90 degrees +void LLJoystickCameraRotate::drawRotatedImage( LLPointer image, S32 rotations ) +{ + S32 width = image->getWidth(); + S32 height = image->getHeight(); + LLTexture* texture = image->getImage(); + + /* + * Scale texture coordinate system + * to handle the different between image size and size of texture. + * If we will use default matrix, + * it may break texture mapping after rotation. + * see EXT-2023 Camera floater: arrows became shifted when pressed. + */ + F32 uv[][2] = + { + { (F32)width/texture->getWidth(), (F32)height/texture->getHeight() }, + { 0.f, (F32)height/texture->getHeight() }, + { 0.f, 0.f }, + { (F32)width/texture->getWidth(), 0.f } + }; + + gGL.getTexUnit(0)->bind(texture); + + gGL.color4fv(UI_VERTEX_COLOR.mV); + + gGL.begin(LLRender::QUADS); + { + gGL.texCoord2fv( uv[ (rotations + 0) % 4]); + gGL.vertex2i(width, height ); + + gGL.texCoord2fv( uv[ (rotations + 1) % 4]); + gGL.vertex2i(0, height ); + + gGL.texCoord2fv( uv[ (rotations + 2) % 4]); + gGL.vertex2i(0, 0); + + gGL.texCoord2fv( uv[ (rotations + 3) % 4]); + gGL.vertex2i(width, 0); + } + gGL.end(); +} + + + +//------------------------------------------------------------------------------- +// LLJoystickCameraTrack +//------------------------------------------------------------------------------- + +LLJoystickCameraTrack::Params::Params() +{ + held_down_delay.seconds(0.0); +} + +LLJoystickCameraTrack::LLJoystickCameraTrack(const LLJoystickCameraTrack::Params& p) +: LLJoystickCameraRotate(p) +{ + mCenterImageName = "Cam_Tracking_Center"; +} + + +void LLJoystickCameraTrack::onHeldDown() +{ + updateSlop(); + + S32 dx = mLastMouse.mX - mFirstMouse.mX + mInitialOffset.mX; + S32 dy = mLastMouse.mY - mFirstMouse.mY + mInitialOffset.mY; + + if (dx > mVertSlopNear) + { + gAgentCamera.unlockView(); + gAgentCamera.setPanRightKey(getOrbitRate()); + } + else if (dx < -mVertSlopNear) + { + gAgentCamera.unlockView(); + gAgentCamera.setPanLeftKey(getOrbitRate()); + } + + // over/under rotation + if (dy > mVertSlopNear) + { + gAgentCamera.unlockView(); + gAgentCamera.setPanUpKey(getOrbitRate()); + } + else if (dy < -mVertSlopNear) + { + gAgentCamera.unlockView(); + gAgentCamera.setPanDownKey(getOrbitRate()); + } +} + +void LLJoystickCameraTrack::resetJoystickCamera() +{ + gAgentCamera.resetCameraPan(); +} + +//------------------------------------------------------------------------------- +// LLJoystickQuaternion +//------------------------------------------------------------------------------- + +LLJoystickQuaternion::Params::Params() +{ +} + +LLJoystickQuaternion::LLJoystickQuaternion(const LLJoystickQuaternion::Params &p): + LLJoystick(p), + mInLeft(false), + mInTop(false), + mInRight(false), + mInBottom(false), + mVectorZero(0.0f, 0.0f, 1.0f), + mRotation(), + mUpDnAxis(1.0f, 0.0f, 0.0f), + mLfRtAxis(0.0f, 0.0f, 1.0f), + mXAxisIndex(2), // left & right across the control + mYAxisIndex(0), // up & down across the control + mZAxisIndex(1) // tested for above and below +{ + for (int i = 0; i < 3; ++i) + { + mLfRtAxis.mV[i] = (mXAxisIndex == i) ? 1.0 : 0.0; + mUpDnAxis.mV[i] = (mYAxisIndex == i) ? 1.0 : 0.0; + } +} + +void LLJoystickQuaternion::setToggleState(bool left, bool top, bool right, bool bottom) +{ + mInLeft = left; + mInTop = top; + mInRight = right; + mInBottom = bottom; +} + +bool LLJoystickQuaternion::handleMouseDown(S32 x, S32 y, MASK mask) +{ + updateSlop(); + + // Set initial offset based on initial click location + S32 horiz_center = getRect().getWidth() / 2; + S32 vert_center = getRect().getHeight() / 2; + + S32 dx = x - horiz_center; + S32 dy = y - vert_center; + + if (dy > dx && dy > -dx) + { + // top + mInitialOffset.mX = 0; + mInitialOffset.mY = (mVertSlopNear + mVertSlopFar) / 2; + mInitialQuadrant = JQ_UP; + } + else if (dy > dx && dy <= -dx) + { + // left + mInitialOffset.mX = -(mHorizSlopNear + mHorizSlopFar) / 2; + mInitialOffset.mY = 0; + mInitialQuadrant = JQ_LEFT; + } + else if (dy <= dx && dy <= -dx) + { + // bottom + mInitialOffset.mX = 0; + mInitialOffset.mY = -(mVertSlopNear + mVertSlopFar) / 2; + mInitialQuadrant = JQ_DOWN; + } + else + { + // right + mInitialOffset.mX = (mHorizSlopNear + mHorizSlopFar) / 2; + mInitialOffset.mY = 0; + mInitialQuadrant = JQ_RIGHT; + } + + return LLJoystick::handleMouseDown(x, y, mask); +} + +bool LLJoystickQuaternion::handleMouseUp(S32 x, S32 y, MASK mask) +{ + return LLJoystick::handleMouseUp(x, y, mask); +} + +void LLJoystickQuaternion::onHeldDown() +{ + LLVector3 axis; + updateSlop(); + + S32 dx = mLastMouse.mX - mFirstMouse.mX + mInitialOffset.mX; + S32 dy = mLastMouse.mY - mFirstMouse.mY + mInitialOffset.mY; + + // left-right rotation + if (dx > mHorizSlopNear) + { + axis += mUpDnAxis; + } + else if (dx < -mHorizSlopNear) + { + axis -= mUpDnAxis; + } + + // over/under rotation + if (dy > mVertSlopNear) + { + axis += mLfRtAxis; + } + else if (dy < -mVertSlopNear) + { + axis -= mLfRtAxis; + } + + if (axis.isNull()) + return; + + axis.normalize(); + + LLQuaternion delta; + delta.setAngleAxis(0.0523599f, axis); // about 3deg + + mRotation *= delta; + setValue(mRotation.getValue()); + onCommit(); +} + +void LLJoystickQuaternion::draw() +{ + LLGLSUIDefault gls_ui; + + getImageUnselected()->draw(0, 0); + LLPointer image = getImageSelected(); + + if (mInTop) + { + drawRotatedImage(getImageSelected(), 0); + } + + if (mInRight) + { + drawRotatedImage(getImageSelected(), 1); + } + + if (mInBottom) + { + drawRotatedImage(getImageSelected(), 2); + } + + if (mInLeft) + { + drawRotatedImage(getImageSelected(), 3); + } + + LLVector3 draw_point = mVectorZero * mRotation; + S32 halfwidth = getRect().getWidth() / 2; + S32 halfheight = getRect().getHeight() / 2; + draw_point.mV[mXAxisIndex] = (draw_point.mV[mXAxisIndex] + 1.0) * halfwidth; + draw_point.mV[mYAxisIndex] = (draw_point.mV[mYAxisIndex] + 1.0) * halfheight; + + gl_circle_2d(draw_point.mV[mXAxisIndex], draw_point.mV[mYAxisIndex], 4, 8, + draw_point.mV[mZAxisIndex] >= 0.f); + +} + +F32 LLJoystickQuaternion::getOrbitRate() +{ + return 1; +} + +void LLJoystickQuaternion::updateSlop() +{ + // small fixed slop region + mVertSlopNear = 16; + mVertSlopFar = 32; + + mHorizSlopNear = 16; + mHorizSlopFar = 32; +} + +void LLJoystickQuaternion::drawRotatedImage(LLPointer image, S32 rotations) +{ + S32 width = image->getWidth(); + S32 height = image->getHeight(); + LLTexture* texture = image->getImage(); + + /* + * Scale texture coordinate system + * to handle the different between image size and size of texture. + */ + F32 uv[][2] = + { + { (F32)width / texture->getWidth(), (F32)height / texture->getHeight() }, + { 0.f, (F32)height / texture->getHeight() }, + { 0.f, 0.f }, + { (F32)width / texture->getWidth(), 0.f } + }; + + gGL.getTexUnit(0)->bind(texture); + + gGL.color4fv(UI_VERTEX_COLOR.mV); + + gGL.begin(LLRender::QUADS); + { + gGL.texCoord2fv(uv[(rotations + 0) % 4]); + gGL.vertex2i(width, height); + + gGL.texCoord2fv(uv[(rotations + 1) % 4]); + gGL.vertex2i(0, height); + + gGL.texCoord2fv(uv[(rotations + 2) % 4]); + gGL.vertex2i(0, 0); + + gGL.texCoord2fv(uv[(rotations + 3) % 4]); + gGL.vertex2i(width, 0); + } + gGL.end(); +} + +void LLJoystickQuaternion::setRotation(const LLQuaternion &value) +{ + if (value != mRotation) + { + mRotation = value; + mRotation.normalize(); + LLJoystick::setValue(mRotation.getValue()); + } +} + +LLQuaternion LLJoystickQuaternion::getRotation() const +{ + return mRotation; +} + + diff --git a/indra/newview/lljoystickbutton.h b/indra/newview/lljoystickbutton.h index 445aaef478..12394ca63e 100644 --- a/indra/newview/lljoystickbutton.h +++ b/indra/newview/lljoystickbutton.h @@ -1,232 +1,232 @@ -/** - * @file lljoystickbutton.h - * @brief LLJoystick class definition - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLJOYSTICKBUTTON_H -#define LL_LLJOYSTICKBUTTON_H - -#include "llbutton.h" -#include "llcoord.h" -#include "llviewertexture.h" -#include "llquaternion.h" - -typedef enum e_joystick_quadrant -{ - JQ_ORIGIN, - JQ_UP, - JQ_DOWN, - JQ_LEFT, - JQ_RIGHT -} EJoystickQuadrant; - -struct QuadrantNames : public LLInitParam::TypeValuesHelper -{ - static void declareValues(); -}; - -class LLJoystick -: public LLButton -{ -public: - struct Params - : public LLInitParam::Block - { - Optional quadrant; - - Params() - : quadrant("quadrant", JQ_ORIGIN) - { - changeDefault(label, ""); - } - }; - LLJoystick(const Params&); - - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - virtual bool handleHover(S32 x, S32 y, MASK mask); - - virtual void onMouseUp() {} - virtual void onHeldDown() = 0; - F32 getElapsedHeldDownTime(); - - static void onBtnHeldDown(void *userdata); // called by llbutton callback handler - void setInitialQuadrant(EJoystickQuadrant initial) { mInitialQuadrant = initial; }; - - /** - * Checks if click location is inside joystick circle. - * - * Image containing circle is square and this square has adherent points with joystick - * circle. Make sure to change method according to shape other than square. - */ - bool pointInCircle(S32 x, S32 y) const; - bool pointInCenterDot(S32 x, S32 y, S32 radius) const; - - static std::string nameFromQuadrant(const EJoystickQuadrant quadrant); - static EJoystickQuadrant quadrantFromName(const std::string& name); - static EJoystickQuadrant selectQuadrant(LLXMLNodePtr node); - - -protected: - virtual void updateSlop(); // recompute slop margins - -protected: - EJoystickQuadrant mInitialQuadrant; // mousedown = click in this quadrant - LLCoordGL mInitialOffset; // pretend mouse started here - LLCoordGL mLastMouse; // where was mouse on last hover event - LLCoordGL mFirstMouse; // when mouse clicked, where was it - S32 mVertSlopNear; // where the slop regions end - S32 mVertSlopFar; // where the slop regions end - S32 mHorizSlopNear; // where the slop regions end - S32 mHorizSlopFar; // where the slop regions end - bool mHeldDown; - LLFrameTimer mHeldDownTimer; -}; - - -// Turn agent left and right, move forward and back -class LLJoystickAgentTurn -: public LLJoystick -{ -public: - struct Params : public LLJoystick::Params {}; - LLJoystickAgentTurn(const Params& p) : LLJoystick(p) {} - virtual void onHeldDown(); -}; - - -// Slide left and right, move forward and back -class LLJoystickAgentSlide -: public LLJoystick -{ -public: - struct Params : public LLJoystick::Params {}; - LLJoystickAgentSlide(const Params& p) : LLJoystick(p) {} - - virtual void onHeldDown(); - virtual void onMouseUp(); -}; - - -// Rotate camera around the focus point -class LLJoystickCameraRotate -: public LLJoystick -{ -public: - struct Params - : public LLInitParam::Block - { - Params() - { - changeDefault(held_down_delay.seconds, 0.0); - } - }; - - LLJoystickCameraRotate(const LLJoystickCameraRotate::Params&); - - virtual void setToggleState( bool left, bool top, bool right, bool bottom ); - - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - virtual bool handleHover(S32 x, S32 y, MASK mask); - virtual void onHeldDown(); - virtual void resetJoystickCamera(); - virtual void draw(); - -protected: - F32 getOrbitRate(); - virtual void updateSlop(); - void drawRotatedImage( LLPointer image, S32 rotations ); - -protected: - bool mInLeft; - bool mInTop; - bool mInRight; - bool mInBottom; - bool mInCenter; - - std::string mCenterImageName; -}; - - -// Track the camera focus point forward/backward and side to side -class LLJoystickCameraTrack -: public LLJoystickCameraRotate -{ -public: - struct Params - : public LLInitParam::Block - { - Params(); - }; - - LLJoystickCameraTrack(const LLJoystickCameraTrack::Params&); - virtual void onHeldDown(); - virtual void resetJoystickCamera(); -}; - -// -class LLJoystickQuaternion : - public LLJoystick -{ -public: - struct Params : - public LLInitParam::Block - { - Params(); - }; - - LLJoystickQuaternion(const LLJoystickQuaternion::Params &); - - virtual void setToggleState(bool left, bool top, bool right, bool bottom); - - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - virtual void onHeldDown(); - virtual void draw(); - - void setRotation(const LLQuaternion &value); - LLQuaternion getRotation() const; - -protected: - F32 getOrbitRate(); - virtual void updateSlop(); - void drawRotatedImage(LLPointer image, S32 rotations); - - bool mInLeft; - bool mInTop; - bool mInRight; - bool mInBottom; - - S32 mXAxisIndex; - S32 mYAxisIndex; - S32 mZAxisIndex; - - LLVector3 mVectorZero; - LLQuaternion mRotation; - LLVector3 mUpDnAxis; - LLVector3 mLfRtAxis; -}; - -#endif // LL_LLJOYSTICKBUTTON_H +/** + * @file lljoystickbutton.h + * @brief LLJoystick class definition + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLJOYSTICKBUTTON_H +#define LL_LLJOYSTICKBUTTON_H + +#include "llbutton.h" +#include "llcoord.h" +#include "llviewertexture.h" +#include "llquaternion.h" + +typedef enum e_joystick_quadrant +{ + JQ_ORIGIN, + JQ_UP, + JQ_DOWN, + JQ_LEFT, + JQ_RIGHT +} EJoystickQuadrant; + +struct QuadrantNames : public LLInitParam::TypeValuesHelper +{ + static void declareValues(); +}; + +class LLJoystick +: public LLButton +{ +public: + struct Params + : public LLInitParam::Block + { + Optional quadrant; + + Params() + : quadrant("quadrant", JQ_ORIGIN) + { + changeDefault(label, ""); + } + }; + LLJoystick(const Params&); + + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + virtual bool handleHover(S32 x, S32 y, MASK mask); + + virtual void onMouseUp() {} + virtual void onHeldDown() = 0; + F32 getElapsedHeldDownTime(); + + static void onBtnHeldDown(void *userdata); // called by llbutton callback handler + void setInitialQuadrant(EJoystickQuadrant initial) { mInitialQuadrant = initial; }; + + /** + * Checks if click location is inside joystick circle. + * + * Image containing circle is square and this square has adherent points with joystick + * circle. Make sure to change method according to shape other than square. + */ + bool pointInCircle(S32 x, S32 y) const; + bool pointInCenterDot(S32 x, S32 y, S32 radius) const; + + static std::string nameFromQuadrant(const EJoystickQuadrant quadrant); + static EJoystickQuadrant quadrantFromName(const std::string& name); + static EJoystickQuadrant selectQuadrant(LLXMLNodePtr node); + + +protected: + virtual void updateSlop(); // recompute slop margins + +protected: + EJoystickQuadrant mInitialQuadrant; // mousedown = click in this quadrant + LLCoordGL mInitialOffset; // pretend mouse started here + LLCoordGL mLastMouse; // where was mouse on last hover event + LLCoordGL mFirstMouse; // when mouse clicked, where was it + S32 mVertSlopNear; // where the slop regions end + S32 mVertSlopFar; // where the slop regions end + S32 mHorizSlopNear; // where the slop regions end + S32 mHorizSlopFar; // where the slop regions end + bool mHeldDown; + LLFrameTimer mHeldDownTimer; +}; + + +// Turn agent left and right, move forward and back +class LLJoystickAgentTurn +: public LLJoystick +{ +public: + struct Params : public LLJoystick::Params {}; + LLJoystickAgentTurn(const Params& p) : LLJoystick(p) {} + virtual void onHeldDown(); +}; + + +// Slide left and right, move forward and back +class LLJoystickAgentSlide +: public LLJoystick +{ +public: + struct Params : public LLJoystick::Params {}; + LLJoystickAgentSlide(const Params& p) : LLJoystick(p) {} + + virtual void onHeldDown(); + virtual void onMouseUp(); +}; + + +// Rotate camera around the focus point +class LLJoystickCameraRotate +: public LLJoystick +{ +public: + struct Params + : public LLInitParam::Block + { + Params() + { + changeDefault(held_down_delay.seconds, 0.0); + } + }; + + LLJoystickCameraRotate(const LLJoystickCameraRotate::Params&); + + virtual void setToggleState( bool left, bool top, bool right, bool bottom ); + + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + virtual bool handleHover(S32 x, S32 y, MASK mask); + virtual void onHeldDown(); + virtual void resetJoystickCamera(); + virtual void draw(); + +protected: + F32 getOrbitRate(); + virtual void updateSlop(); + void drawRotatedImage( LLPointer image, S32 rotations ); + +protected: + bool mInLeft; + bool mInTop; + bool mInRight; + bool mInBottom; + bool mInCenter; + + std::string mCenterImageName; +}; + + +// Track the camera focus point forward/backward and side to side +class LLJoystickCameraTrack +: public LLJoystickCameraRotate +{ +public: + struct Params + : public LLInitParam::Block + { + Params(); + }; + + LLJoystickCameraTrack(const LLJoystickCameraTrack::Params&); + virtual void onHeldDown(); + virtual void resetJoystickCamera(); +}; + +// +class LLJoystickQuaternion : + public LLJoystick +{ +public: + struct Params : + public LLInitParam::Block + { + Params(); + }; + + LLJoystickQuaternion(const LLJoystickQuaternion::Params &); + + virtual void setToggleState(bool left, bool top, bool right, bool bottom); + + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + virtual void onHeldDown(); + virtual void draw(); + + void setRotation(const LLQuaternion &value); + LLQuaternion getRotation() const; + +protected: + F32 getOrbitRate(); + virtual void updateSlop(); + void drawRotatedImage(LLPointer image, S32 rotations); + + bool mInLeft; + bool mInTop; + bool mInRight; + bool mInBottom; + + S32 mXAxisIndex; + S32 mYAxisIndex; + S32 mZAxisIndex; + + LLVector3 mVectorZero; + LLQuaternion mRotation; + LLVector3 mUpDnAxis; + LLVector3 mLfRtAxis; +}; + +#endif // LL_LLJOYSTICKBUTTON_H diff --git a/indra/newview/lllandmarkactions.cpp b/indra/newview/lllandmarkactions.cpp index e9914732a8..73425e9f4c 100644 --- a/indra/newview/lllandmarkactions.cpp +++ b/indra/newview/lllandmarkactions.cpp @@ -1,398 +1,398 @@ -/** -* @file lllandmarkactions.cpp -* @brief LLLandmarkActions class implementation -* -* $LicenseInfo:firstyear=2001&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2010, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#include "llviewerprecompiledheaders.h" -#include "lllandmarkactions.h" - -#include "roles_constants.h" - -#include "llinventory.h" -#include "llinventoryfunctions.h" -#include "lllandmark.h" -#include "llparcel.h" -#include "llregionhandle.h" - -#include "llnotificationsutil.h" - -#include "llagent.h" -#include "llagentui.h" -#include "llinventorymodel.h" -#include "lllandmarklist.h" -#include "llslurl.h" -#include "llstring.h" -#include "llviewerinventory.h" -#include "llviewerparcelmgr.h" -#include "llworldmapmessage.h" -#include "llviewerwindow.h" -#include "llwindow.h" -#include "llworldmap.h" - -void copy_slurl_to_clipboard_callback(const std::string& slurl); - -class LLFetchlLandmarkByPos : public LLInventoryCollectFunctor -{ -private: - LLVector3d mPos; -public: - LLFetchlLandmarkByPos(const LLVector3d& pos) : - mPos(pos) - {} - - /*virtual*/ bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) - { - if (!item || item->getType() != LLAssetType::AT_LANDMARK) - return false; - - LLLandmark* landmark = gLandmarkList.getAsset(item->getAssetUUID()); - if (!landmark) // the landmark not been loaded yet - return false; - - LLVector3d landmark_global_pos; - if (!landmark->getGlobalPos(landmark_global_pos)) - return false; - //we have to round off each coordinates to compare positions properly - return ll_round(mPos.mdV[VX]) == ll_round(landmark_global_pos.mdV[VX]) - && ll_round(mPos.mdV[VY]) == ll_round(landmark_global_pos.mdV[VY]) - && ll_round(mPos.mdV[VZ]) == ll_round(landmark_global_pos.mdV[VZ]); - } -}; - -class LLFetchLandmarksByName : public LLInventoryCollectFunctor -{ -private: - std::string name; - bool use_substring; - //this member will be contain copy of founded items to keep the result unique - std::set check_duplicate; - -public: -LLFetchLandmarksByName(std::string &landmark_name, bool if_use_substring) -:name(landmark_name), -use_substring(if_use_substring) - { - LLStringUtil::toLower(name); - } - -public: - /*virtual*/ bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) - { - if (!item || item->getType() != LLAssetType::AT_LANDMARK) - return false; - - LLLandmark* landmark = gLandmarkList.getAsset(item->getAssetUUID()); - if (!landmark) // the landmark not been loaded yet - return false; - - bool acceptable = false; - std::string landmark_name = item->getName(); - LLStringUtil::toLower(landmark_name); - if(use_substring) - { - acceptable = landmark_name.find( name ) != std::string::npos; - } - else - { - acceptable = landmark_name == name; - } - if(acceptable){ - if(check_duplicate.find(landmark_name) != check_duplicate.end()){ - // we have duplicated items in landmarks - acceptable = false; - }else{ - check_duplicate.insert(landmark_name); - } - } - - return acceptable; - } -}; - -// Returns true if the given inventory item is a landmark pointing to the current parcel. -// Used to find out if there is at least one landmark from current parcel. -class LLFirstAgentParcelLandmark : public LLInventoryCollectFunctor -{ -private: - bool mFounded;// to avoid unnecessary check - -public: - LLFirstAgentParcelLandmark(): mFounded(false){} - - /*virtual*/ bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) - { - if (mFounded || !item || item->getType() != LLAssetType::AT_LANDMARK) - return false; - - LLLandmark* landmark = gLandmarkList.getAsset(item->getAssetUUID()); - if (!landmark) // the landmark not been loaded yet - return false; - - LLVector3d landmark_global_pos; - if (!landmark->getGlobalPos(landmark_global_pos)) - return false; - mFounded = LLViewerParcelMgr::getInstance()->inAgentParcel(landmark_global_pos); - return mFounded; - } -}; - -static void fetch_landmarks(LLInventoryModel::cat_array_t& cats, - LLInventoryModel::item_array_t& items, - LLInventoryCollectFunctor& add) -{ - // Look in "My Favorites" - const LLUUID favorites_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); - gInventory.collectDescendentsIf(favorites_folder_id, - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - add); - - // Look in "Landmarks" - const LLUUID landmarks_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK); - gInventory.collectDescendentsIf(landmarks_folder_id, - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - add); -} - -LLInventoryModel::item_array_t LLLandmarkActions::fetchLandmarksByName(std::string& name, bool use_substring) -{ - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - LLFetchLandmarksByName by_name(name, use_substring); - fetch_landmarks(cats, items, by_name); - - return items; -} - -bool LLLandmarkActions::landmarkAlreadyExists() -{ - // Determine whether there are landmarks pointing to the current global agent position. - return findLandmarkForAgentPos() != NULL; -} - -//static -bool LLLandmarkActions::hasParcelLandmark() -{ - LLFirstAgentParcelLandmark get_first_agent_landmark; - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - fetch_landmarks(cats, items, get_first_agent_landmark); - return !items.empty(); - -} - -// *TODO: This could be made more efficient by only fetching the FIRST -// landmark that meets the criteria -LLViewerInventoryItem* LLLandmarkActions::findLandmarkForGlobalPos(const LLVector3d &pos) -{ - // Determine whether there are landmarks pointing to the current parcel. - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - LLFetchlLandmarkByPos is_current_pos_landmark(pos); - fetch_landmarks(cats, items, is_current_pos_landmark); - - if(items.empty()) - { - return NULL; - } - - return items[0]; -} - -LLViewerInventoryItem* LLLandmarkActions::findLandmarkForAgentPos() -{ - return findLandmarkForGlobalPos(gAgent.getPositionGlobal()); -} - -void LLLandmarkActions::createLandmarkHere( - const std::string& name, - const std::string& desc, - const LLUUID& folder_id) -{ - if(!gAgent.getRegion()) - { - LL_WARNS() << "No agent region" << LL_ENDL; - return; - } - LLParcel* agent_parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - if (!agent_parcel) - { - LL_WARNS() << "No agent parcel" << LL_ENDL; - return; - } - - create_inventory_item(gAgent.getID(), gAgent.getSessionID(), - folder_id, LLTransactionID::tnull, - name, desc, - LLAssetType::AT_LANDMARK, - LLInventoryType::IT_LANDMARK, - NO_INV_SUBTYPE, PERM_ALL, - NULL); -} - -void LLLandmarkActions::createLandmarkHere() -{ - std::string landmark_name, landmark_desc; - - LLAgentUI::buildLocationString(landmark_name, LLAgentUI::LOCATION_FORMAT_LANDMARK); - LLAgentUI::buildLocationString(landmark_desc, LLAgentUI::LOCATION_FORMAT_FULL); - const LLUUID folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK); - - createLandmarkHere(landmark_name, landmark_desc, folder_id); -} - -void LLLandmarkActions::getSLURLfromPosGlobal(const LLVector3d& global_pos, slurl_callback_t cb, bool escaped /* = true */) -{ - std::string sim_name; - bool gotSimName = LLWorldMap::getInstance()->simNameFromPosGlobal(global_pos, sim_name); - if (gotSimName) - { - std::string slurl = LLSLURL(sim_name, global_pos).getSLURLString(); - cb(slurl); - - return; - } - else - { - U64 new_region_handle = to_region_handle(global_pos); - - LLWorldMapMessage::url_callback_t url_cb = boost::bind(&LLLandmarkActions::onRegionResponseSLURL, - cb, - global_pos, - escaped, - _2); - - LLWorldMapMessage::getInstance()->sendHandleRegionRequest(new_region_handle, url_cb, std::string("unused"), false); - } -} - -void LLLandmarkActions::getRegionNameAndCoordsFromPosGlobal(const LLVector3d& global_pos, region_name_and_coords_callback_t cb) -{ - std::string sim_name; - LLSimInfo* sim_infop = LLWorldMap::getInstance()->simInfoFromPosGlobal(global_pos); - if (sim_infop) - { - LLVector3 pos = sim_infop->getLocalPos(global_pos); - std::string name = sim_infop->getName() ; - cb(name, ll_round(pos.mV[VX]), ll_round(pos.mV[VY]),ll_round(pos.mV[VZ])); - } - else - { - U64 new_region_handle = to_region_handle(global_pos); - - LLWorldMapMessage::url_callback_t url_cb = boost::bind(&LLLandmarkActions::onRegionResponseNameAndCoords, - cb, - global_pos, - _1); - - LLWorldMapMessage::getInstance()->sendHandleRegionRequest(new_region_handle, url_cb, std::string("unused"), false); - } -} - -void LLLandmarkActions::onRegionResponseSLURL(slurl_callback_t cb, - const LLVector3d& global_pos, - bool escaped, - const std::string& url) -{ - std::string sim_name; - std::string slurl; - bool gotSimName = LLWorldMap::getInstance()->simNameFromPosGlobal(global_pos, sim_name); - if (gotSimName) - { - slurl = LLSLURL(sim_name, global_pos).getSLURLString(); - } - else - { - slurl = ""; - } - - cb(slurl); -} - -void LLLandmarkActions::onRegionResponseNameAndCoords(region_name_and_coords_callback_t cb, - const LLVector3d& global_pos, - U64 region_handle) -{ - LLSimInfo* sim_infop = LLWorldMap::getInstance()->simInfoFromHandle(region_handle); - if (sim_infop) - { - LLVector3 local_pos = sim_infop->getLocalPos(global_pos); - std::string name = sim_infop->getName() ; - cb(name, ll_round(local_pos.mV[VX]), ll_round(local_pos.mV[VY]), ll_round(local_pos.mV[VZ])); - } -} - -bool LLLandmarkActions::getLandmarkGlobalPos(const LLUUID& landmarkInventoryItemID, LLVector3d& posGlobal) -{ - LLViewerInventoryItem* item = gInventory.getItem(landmarkInventoryItemID); - if (NULL == item) - return false; - - const LLUUID& asset_id = item->getAssetUUID(); - - LLLandmark* landmark = gLandmarkList.getAsset(asset_id, NULL); - if (NULL == landmark) - return false; - - return landmark->getGlobalPos(posGlobal); -} - -LLLandmark* LLLandmarkActions::getLandmark(const LLUUID& landmarkInventoryItemID, LLLandmarkList::loaded_callback_t cb) -{ - LLViewerInventoryItem* item = gInventory.getItem(landmarkInventoryItemID); - if (NULL == item) - return NULL; - - const LLUUID& asset_id = item->getAssetUUID(); - - LLLandmark* landmark = gLandmarkList.getAsset(asset_id, cb); - if (landmark) - { - return landmark; - } - - return NULL; -} - -void LLLandmarkActions::copySLURLtoClipboard(const LLUUID& landmarkInventoryItemID) -{ - LLLandmark* landmark = LLLandmarkActions::getLandmark(landmarkInventoryItemID); - if(landmark) - { - LLVector3d global_pos; - landmark->getGlobalPos(global_pos); - LLLandmarkActions::getSLURLfromPosGlobal(global_pos,©_slurl_to_clipboard_callback,true); - } -} - -void copy_slurl_to_clipboard_callback(const std::string& slurl) -{ - gViewerWindow->getWindow()->copyTextToClipboard(utf8str_to_wstring(slurl)); - LLSD args; - args["SLURL"] = slurl; - LLNotificationsUtil::add("CopySLURL", args); -} +/** +* @file lllandmarkactions.cpp +* @brief LLLandmarkActions class implementation +* +* $LicenseInfo:firstyear=2001&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2010, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" +#include "lllandmarkactions.h" + +#include "roles_constants.h" + +#include "llinventory.h" +#include "llinventoryfunctions.h" +#include "lllandmark.h" +#include "llparcel.h" +#include "llregionhandle.h" + +#include "llnotificationsutil.h" + +#include "llagent.h" +#include "llagentui.h" +#include "llinventorymodel.h" +#include "lllandmarklist.h" +#include "llslurl.h" +#include "llstring.h" +#include "llviewerinventory.h" +#include "llviewerparcelmgr.h" +#include "llworldmapmessage.h" +#include "llviewerwindow.h" +#include "llwindow.h" +#include "llworldmap.h" + +void copy_slurl_to_clipboard_callback(const std::string& slurl); + +class LLFetchlLandmarkByPos : public LLInventoryCollectFunctor +{ +private: + LLVector3d mPos; +public: + LLFetchlLandmarkByPos(const LLVector3d& pos) : + mPos(pos) + {} + + /*virtual*/ bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) + { + if (!item || item->getType() != LLAssetType::AT_LANDMARK) + return false; + + LLLandmark* landmark = gLandmarkList.getAsset(item->getAssetUUID()); + if (!landmark) // the landmark not been loaded yet + return false; + + LLVector3d landmark_global_pos; + if (!landmark->getGlobalPos(landmark_global_pos)) + return false; + //we have to round off each coordinates to compare positions properly + return ll_round(mPos.mdV[VX]) == ll_round(landmark_global_pos.mdV[VX]) + && ll_round(mPos.mdV[VY]) == ll_round(landmark_global_pos.mdV[VY]) + && ll_round(mPos.mdV[VZ]) == ll_round(landmark_global_pos.mdV[VZ]); + } +}; + +class LLFetchLandmarksByName : public LLInventoryCollectFunctor +{ +private: + std::string name; + bool use_substring; + //this member will be contain copy of founded items to keep the result unique + std::set check_duplicate; + +public: +LLFetchLandmarksByName(std::string &landmark_name, bool if_use_substring) +:name(landmark_name), +use_substring(if_use_substring) + { + LLStringUtil::toLower(name); + } + +public: + /*virtual*/ bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) + { + if (!item || item->getType() != LLAssetType::AT_LANDMARK) + return false; + + LLLandmark* landmark = gLandmarkList.getAsset(item->getAssetUUID()); + if (!landmark) // the landmark not been loaded yet + return false; + + bool acceptable = false; + std::string landmark_name = item->getName(); + LLStringUtil::toLower(landmark_name); + if(use_substring) + { + acceptable = landmark_name.find( name ) != std::string::npos; + } + else + { + acceptable = landmark_name == name; + } + if(acceptable){ + if(check_duplicate.find(landmark_name) != check_duplicate.end()){ + // we have duplicated items in landmarks + acceptable = false; + }else{ + check_duplicate.insert(landmark_name); + } + } + + return acceptable; + } +}; + +// Returns true if the given inventory item is a landmark pointing to the current parcel. +// Used to find out if there is at least one landmark from current parcel. +class LLFirstAgentParcelLandmark : public LLInventoryCollectFunctor +{ +private: + bool mFounded;// to avoid unnecessary check + +public: + LLFirstAgentParcelLandmark(): mFounded(false){} + + /*virtual*/ bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) + { + if (mFounded || !item || item->getType() != LLAssetType::AT_LANDMARK) + return false; + + LLLandmark* landmark = gLandmarkList.getAsset(item->getAssetUUID()); + if (!landmark) // the landmark not been loaded yet + return false; + + LLVector3d landmark_global_pos; + if (!landmark->getGlobalPos(landmark_global_pos)) + return false; + mFounded = LLViewerParcelMgr::getInstance()->inAgentParcel(landmark_global_pos); + return mFounded; + } +}; + +static void fetch_landmarks(LLInventoryModel::cat_array_t& cats, + LLInventoryModel::item_array_t& items, + LLInventoryCollectFunctor& add) +{ + // Look in "My Favorites" + const LLUUID favorites_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); + gInventory.collectDescendentsIf(favorites_folder_id, + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + add); + + // Look in "Landmarks" + const LLUUID landmarks_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK); + gInventory.collectDescendentsIf(landmarks_folder_id, + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + add); +} + +LLInventoryModel::item_array_t LLLandmarkActions::fetchLandmarksByName(std::string& name, bool use_substring) +{ + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLFetchLandmarksByName by_name(name, use_substring); + fetch_landmarks(cats, items, by_name); + + return items; +} + +bool LLLandmarkActions::landmarkAlreadyExists() +{ + // Determine whether there are landmarks pointing to the current global agent position. + return findLandmarkForAgentPos() != NULL; +} + +//static +bool LLLandmarkActions::hasParcelLandmark() +{ + LLFirstAgentParcelLandmark get_first_agent_landmark; + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + fetch_landmarks(cats, items, get_first_agent_landmark); + return !items.empty(); + +} + +// *TODO: This could be made more efficient by only fetching the FIRST +// landmark that meets the criteria +LLViewerInventoryItem* LLLandmarkActions::findLandmarkForGlobalPos(const LLVector3d &pos) +{ + // Determine whether there are landmarks pointing to the current parcel. + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLFetchlLandmarkByPos is_current_pos_landmark(pos); + fetch_landmarks(cats, items, is_current_pos_landmark); + + if(items.empty()) + { + return NULL; + } + + return items[0]; +} + +LLViewerInventoryItem* LLLandmarkActions::findLandmarkForAgentPos() +{ + return findLandmarkForGlobalPos(gAgent.getPositionGlobal()); +} + +void LLLandmarkActions::createLandmarkHere( + const std::string& name, + const std::string& desc, + const LLUUID& folder_id) +{ + if(!gAgent.getRegion()) + { + LL_WARNS() << "No agent region" << LL_ENDL; + return; + } + LLParcel* agent_parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if (!agent_parcel) + { + LL_WARNS() << "No agent parcel" << LL_ENDL; + return; + } + + create_inventory_item(gAgent.getID(), gAgent.getSessionID(), + folder_id, LLTransactionID::tnull, + name, desc, + LLAssetType::AT_LANDMARK, + LLInventoryType::IT_LANDMARK, + NO_INV_SUBTYPE, PERM_ALL, + NULL); +} + +void LLLandmarkActions::createLandmarkHere() +{ + std::string landmark_name, landmark_desc; + + LLAgentUI::buildLocationString(landmark_name, LLAgentUI::LOCATION_FORMAT_LANDMARK); + LLAgentUI::buildLocationString(landmark_desc, LLAgentUI::LOCATION_FORMAT_FULL); + const LLUUID folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK); + + createLandmarkHere(landmark_name, landmark_desc, folder_id); +} + +void LLLandmarkActions::getSLURLfromPosGlobal(const LLVector3d& global_pos, slurl_callback_t cb, bool escaped /* = true */) +{ + std::string sim_name; + bool gotSimName = LLWorldMap::getInstance()->simNameFromPosGlobal(global_pos, sim_name); + if (gotSimName) + { + std::string slurl = LLSLURL(sim_name, global_pos).getSLURLString(); + cb(slurl); + + return; + } + else + { + U64 new_region_handle = to_region_handle(global_pos); + + LLWorldMapMessage::url_callback_t url_cb = boost::bind(&LLLandmarkActions::onRegionResponseSLURL, + cb, + global_pos, + escaped, + _2); + + LLWorldMapMessage::getInstance()->sendHandleRegionRequest(new_region_handle, url_cb, std::string("unused"), false); + } +} + +void LLLandmarkActions::getRegionNameAndCoordsFromPosGlobal(const LLVector3d& global_pos, region_name_and_coords_callback_t cb) +{ + std::string sim_name; + LLSimInfo* sim_infop = LLWorldMap::getInstance()->simInfoFromPosGlobal(global_pos); + if (sim_infop) + { + LLVector3 pos = sim_infop->getLocalPos(global_pos); + std::string name = sim_infop->getName() ; + cb(name, ll_round(pos.mV[VX]), ll_round(pos.mV[VY]),ll_round(pos.mV[VZ])); + } + else + { + U64 new_region_handle = to_region_handle(global_pos); + + LLWorldMapMessage::url_callback_t url_cb = boost::bind(&LLLandmarkActions::onRegionResponseNameAndCoords, + cb, + global_pos, + _1); + + LLWorldMapMessage::getInstance()->sendHandleRegionRequest(new_region_handle, url_cb, std::string("unused"), false); + } +} + +void LLLandmarkActions::onRegionResponseSLURL(slurl_callback_t cb, + const LLVector3d& global_pos, + bool escaped, + const std::string& url) +{ + std::string sim_name; + std::string slurl; + bool gotSimName = LLWorldMap::getInstance()->simNameFromPosGlobal(global_pos, sim_name); + if (gotSimName) + { + slurl = LLSLURL(sim_name, global_pos).getSLURLString(); + } + else + { + slurl = ""; + } + + cb(slurl); +} + +void LLLandmarkActions::onRegionResponseNameAndCoords(region_name_and_coords_callback_t cb, + const LLVector3d& global_pos, + U64 region_handle) +{ + LLSimInfo* sim_infop = LLWorldMap::getInstance()->simInfoFromHandle(region_handle); + if (sim_infop) + { + LLVector3 local_pos = sim_infop->getLocalPos(global_pos); + std::string name = sim_infop->getName() ; + cb(name, ll_round(local_pos.mV[VX]), ll_round(local_pos.mV[VY]), ll_round(local_pos.mV[VZ])); + } +} + +bool LLLandmarkActions::getLandmarkGlobalPos(const LLUUID& landmarkInventoryItemID, LLVector3d& posGlobal) +{ + LLViewerInventoryItem* item = gInventory.getItem(landmarkInventoryItemID); + if (NULL == item) + return false; + + const LLUUID& asset_id = item->getAssetUUID(); + + LLLandmark* landmark = gLandmarkList.getAsset(asset_id, NULL); + if (NULL == landmark) + return false; + + return landmark->getGlobalPos(posGlobal); +} + +LLLandmark* LLLandmarkActions::getLandmark(const LLUUID& landmarkInventoryItemID, LLLandmarkList::loaded_callback_t cb) +{ + LLViewerInventoryItem* item = gInventory.getItem(landmarkInventoryItemID); + if (NULL == item) + return NULL; + + const LLUUID& asset_id = item->getAssetUUID(); + + LLLandmark* landmark = gLandmarkList.getAsset(asset_id, cb); + if (landmark) + { + return landmark; + } + + return NULL; +} + +void LLLandmarkActions::copySLURLtoClipboard(const LLUUID& landmarkInventoryItemID) +{ + LLLandmark* landmark = LLLandmarkActions::getLandmark(landmarkInventoryItemID); + if(landmark) + { + LLVector3d global_pos; + landmark->getGlobalPos(global_pos); + LLLandmarkActions::getSLURLfromPosGlobal(global_pos,©_slurl_to_clipboard_callback,true); + } +} + +void copy_slurl_to_clipboard_callback(const std::string& slurl) +{ + gViewerWindow->getWindow()->copyTextToClipboard(utf8str_to_wstring(slurl)); + LLSD args; + args["SLURL"] = slurl; + LLNotificationsUtil::add("CopySLURL", args); +} diff --git a/indra/newview/lllandmarkactions.h b/indra/newview/lllandmarkactions.h index 285354fdb7..1abf10e110 100644 --- a/indra/newview/lllandmarkactions.h +++ b/indra/newview/lllandmarkactions.h @@ -1,132 +1,132 @@ -/** - * @file lllandmarkactions.h - * @brief LLLandmark class declaration - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLLANDMARKACTIONS_H -#define LL_LLLANDMARKACTIONS_H - -#include "llinventorymodel.h" - -#include "lllandmarklist.h" - -class LLLandmark; - -/** - * @brief Provides helper functions to manage landmarks - */ -class LLLandmarkActions -{ -public: - typedef boost::function slurl_callback_t; - typedef boost::function region_name_and_coords_callback_t; - - /** - * @brief Fetches landmark LLViewerInventoryItems for the given landmark name. - */ - static LLInventoryModel::item_array_t fetchLandmarksByName(std::string& name, bool if_use_substring); - /** - * @brief Checks whether landmark exists for current agent position. - */ - static bool landmarkAlreadyExists(); - - /** - * @brief Checks whether landmark exists for current agent parcel. - */ - static bool hasParcelLandmark(); - - /** - * @brief Searches landmark for global position. - * @return Returns landmark or NULL. - * - * *TODO: dzaporozhan: There can be many landmarks for single parcel. - */ - static LLViewerInventoryItem* findLandmarkForGlobalPos(const LLVector3d &pos); - - /** - * @brief Searches landmark for agent global position. - * @return Returns landmark or NULL. - * - * *TODO: dzaporozhan: There can be many landmarks for single parcel. - */ - static LLViewerInventoryItem* findLandmarkForAgentPos(); - - /** - * @brief Creates landmark for current parcel. - */ - static void createLandmarkHere(); - - /** - * @brief Creates landmark for current parcel. - */ - static void createLandmarkHere( - const std::string& name, - const std::string& desc, - const LLUUID& folder_id); - /** - * @brief Creates SLURL for given global position. - */ - static void getSLURLfromPosGlobal(const LLVector3d& global_pos, slurl_callback_t cb, bool escaped = true); - - static void getRegionNameAndCoordsFromPosGlobal(const LLVector3d& global_pos, region_name_and_coords_callback_t cb); - - /** - * @brief Gets landmark global position specified by inventory LLUUID. - * Found position is placed into "posGlobal" variable. - *. - * @return - true if specified item exists in Inventory and an appropriate landmark found. - * and its position is known, false otherwise. - */ - // *TODO: mantipov: profide callback for cases, when Landmark is not loaded yet. - static bool getLandmarkGlobalPos(const LLUUID& landmarkInventoryItemID, LLVector3d& posGlobal); - - /** - * @brief Retrieve a landmark from gLandmarkList by inventory item's id - * If a landmark is not currently in the gLandmarkList a callback "cb" is called when it is loaded. - * - * @return pointer to loaded landmark from gLandmarkList or NULL if landmark does not exist or wasn't loaded. - */ - static LLLandmark* getLandmark(const LLUUID& landmarkInventoryItemID, LLLandmarkList::loaded_callback_t cb = NULL); - - /** - * @brief Performs standard action of copying of SLURL from landmark to user's clipboard. - * This action requires additional server request. The user will be notified by info message, - * when URL is copied . - */ - static void copySLURLtoClipboard(const LLUUID& landmarkInventoryItemID); - -private: - LLLandmarkActions(); - LLLandmarkActions(const LLLandmarkActions&); - - static void onRegionResponseSLURL(slurl_callback_t cb, - const LLVector3d& global_pos, - bool escaped, - const std::string& url); - static void onRegionResponseNameAndCoords(region_name_and_coords_callback_t cb, - const LLVector3d& global_pos, - U64 region_handle); -}; - -#endif //LL_LLLANDMARKACTIONS_H +/** + * @file lllandmarkactions.h + * @brief LLLandmark class declaration + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLLANDMARKACTIONS_H +#define LL_LLLANDMARKACTIONS_H + +#include "llinventorymodel.h" + +#include "lllandmarklist.h" + +class LLLandmark; + +/** + * @brief Provides helper functions to manage landmarks + */ +class LLLandmarkActions +{ +public: + typedef boost::function slurl_callback_t; + typedef boost::function region_name_and_coords_callback_t; + + /** + * @brief Fetches landmark LLViewerInventoryItems for the given landmark name. + */ + static LLInventoryModel::item_array_t fetchLandmarksByName(std::string& name, bool if_use_substring); + /** + * @brief Checks whether landmark exists for current agent position. + */ + static bool landmarkAlreadyExists(); + + /** + * @brief Checks whether landmark exists for current agent parcel. + */ + static bool hasParcelLandmark(); + + /** + * @brief Searches landmark for global position. + * @return Returns landmark or NULL. + * + * *TODO: dzaporozhan: There can be many landmarks for single parcel. + */ + static LLViewerInventoryItem* findLandmarkForGlobalPos(const LLVector3d &pos); + + /** + * @brief Searches landmark for agent global position. + * @return Returns landmark or NULL. + * + * *TODO: dzaporozhan: There can be many landmarks for single parcel. + */ + static LLViewerInventoryItem* findLandmarkForAgentPos(); + + /** + * @brief Creates landmark for current parcel. + */ + static void createLandmarkHere(); + + /** + * @brief Creates landmark for current parcel. + */ + static void createLandmarkHere( + const std::string& name, + const std::string& desc, + const LLUUID& folder_id); + /** + * @brief Creates SLURL for given global position. + */ + static void getSLURLfromPosGlobal(const LLVector3d& global_pos, slurl_callback_t cb, bool escaped = true); + + static void getRegionNameAndCoordsFromPosGlobal(const LLVector3d& global_pos, region_name_and_coords_callback_t cb); + + /** + * @brief Gets landmark global position specified by inventory LLUUID. + * Found position is placed into "posGlobal" variable. + *. + * @return - true if specified item exists in Inventory and an appropriate landmark found. + * and its position is known, false otherwise. + */ + // *TODO: mantipov: profide callback for cases, when Landmark is not loaded yet. + static bool getLandmarkGlobalPos(const LLUUID& landmarkInventoryItemID, LLVector3d& posGlobal); + + /** + * @brief Retrieve a landmark from gLandmarkList by inventory item's id + * If a landmark is not currently in the gLandmarkList a callback "cb" is called when it is loaded. + * + * @return pointer to loaded landmark from gLandmarkList or NULL if landmark does not exist or wasn't loaded. + */ + static LLLandmark* getLandmark(const LLUUID& landmarkInventoryItemID, LLLandmarkList::loaded_callback_t cb = NULL); + + /** + * @brief Performs standard action of copying of SLURL from landmark to user's clipboard. + * This action requires additional server request. The user will be notified by info message, + * when URL is copied . + */ + static void copySLURLtoClipboard(const LLUUID& landmarkInventoryItemID); + +private: + LLLandmarkActions(); + LLLandmarkActions(const LLLandmarkActions&); + + static void onRegionResponseSLURL(slurl_callback_t cb, + const LLVector3d& global_pos, + bool escaped, + const std::string& url); + static void onRegionResponseNameAndCoords(region_name_and_coords_callback_t cb, + const LLVector3d& global_pos, + U64 region_handle); +}; + +#endif //LL_LLLANDMARKACTIONS_H diff --git a/indra/newview/lllandmarklist.cpp b/indra/newview/lllandmarklist.cpp index d28b53b1a1..b88986ce25 100644 --- a/indra/newview/lllandmarklist.cpp +++ b/indra/newview/lllandmarklist.cpp @@ -1,236 +1,236 @@ -/** - * @file lllandmarklist.cpp - * @brief Landmark asset list class - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lllandmarklist.h" - -#include "message.h" -#include "llassetstorage.h" - -#include "llappviewer.h" -#include "llagent.h" -#include "llfilesystem.h" -#include "llviewerstats.h" - -// Globals -LLLandmarkList gLandmarkList; - -//////////////////////////////////////////////////////////////////////////// -// LLLandmarkList - -LLLandmarkList::~LLLandmarkList() -{ - std::for_each(mList.begin(), mList.end(), DeletePairedPointer()); - mList.clear(); -} - -LLLandmark* LLLandmarkList::getAsset(const LLUUID& asset_uuid, loaded_callback_t cb) -{ - LLLandmark* landmark = get_ptr_in_map(mList, asset_uuid); - if(landmark) - { - LLVector3d dummy; - if(cb && !landmark->getGlobalPos(dummy)) - { - // landmark is not completely loaded yet - loaded_callback_map_t::value_type vt(asset_uuid, cb); - mLoadedCallbackMap.insert(vt); - } - return landmark; - } - else - { - if ( mBadList.find(asset_uuid) != mBadList.end() ) - { - return NULL; - } - - if (cb) - { - // Multiple different sources can request same landmark, - // mLoadedCallbackMap is a multimap that allows multiple pairs with same key - // Todo: this might need to be improved to not hold identical callbacks multiple times - loaded_callback_map_t::value_type vt(asset_uuid, cb); - mLoadedCallbackMap.insert(vt); - } - - landmark_requested_list_t::iterator iter = mRequestedList.find(asset_uuid); - if (iter != mRequestedList.end()) - { - const F32 rerequest_time = 30.f; // 30 seconds between requests - if (gFrameTimeSeconds - iter->second < rerequest_time) - { - return NULL; - } - } - - mRequestedList[asset_uuid] = gFrameTimeSeconds; - - // Note that getAssetData can callback immediately and cleans mRequestedList - gAssetStorage->getAssetData(asset_uuid, - LLAssetType::AT_LANDMARK, - LLLandmarkList::processGetAssetReply, - NULL); - } - return NULL; -} - -// static -void LLLandmarkList::processGetAssetReply( - const LLUUID& uuid, - LLAssetType::EType type, - void* user_data, - S32 status, - LLExtStat ext_status ) -{ - if( status == 0 ) - { - LLFileSystem file(uuid, type); - S32 file_length = file.getSize(); - - if (file_length > 0) - { - std::vector buffer(file_length + 1); - file.read((U8*)&buffer[0], file_length); - buffer[file_length] = 0; - - LLLandmark* landmark = LLLandmark::constructFromString(&buffer[0], buffer.size()); - if (landmark) - { - gLandmarkList.mList[uuid] = landmark; - gLandmarkList.mRequestedList.erase(uuid); - - LLVector3d pos; - if (!landmark->getGlobalPos(pos)) - { - LLUUID region_id; - if (landmark->getRegionID(region_id)) - { - LLLandmark::requestRegionHandle( - gMessageSystem, - gAgent.getRegionHost(), - region_id, - boost::bind(&LLLandmarkList::onRegionHandle, &gLandmarkList, uuid)); - } - - // the callback will be called when we get the region handle. - } - else - { - gLandmarkList.makeCallbacks(uuid); - } - } - else - { - // failed to parse, shouldn't happen - gLandmarkList.eraseCallbacks(uuid); - } - } - else - { - // got a good status, but no file, shouldn't happen - gLandmarkList.eraseCallbacks(uuid); - } - } - else - { - // SJB: No use case for a notification here. Use LL_DEBUGS() instead - if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status ) - { - LL_WARNS("Landmarks") << "Missing Landmark" << LL_ENDL; - //LLNotificationsUtil::add("LandmarkMissing"); - } - else - { - LL_WARNS("Landmarks") << "Unable to load Landmark" << LL_ENDL; - //LLNotificationsUtil::add("UnableToLoadLandmark"); - } - - gLandmarkList.mBadList.insert(uuid); - gLandmarkList.mRequestedList.erase(uuid); //mBadList effectively blocks any load, so no point keeping id in requests - gLandmarkList.eraseCallbacks(uuid); - } -} - -bool LLLandmarkList::isAssetInLoadedCallbackMap(const LLUUID& asset_uuid) -{ - return mLoadedCallbackMap.find(asset_uuid) != mLoadedCallbackMap.end(); -} - -bool LLLandmarkList::assetExists(const LLUUID& asset_uuid) -{ - return mList.count(asset_uuid) != 0 || mBadList.count(asset_uuid) != 0; -} - -void LLLandmarkList::onRegionHandle(const LLUUID& landmark_id) -{ - LLLandmark* landmark = getAsset(landmark_id); - if (!landmark) - { - LL_WARNS() << "Got region handle but the landmark " << landmark_id << " not found." << LL_ENDL; - eraseCallbacks(landmark_id); - return; - } - - // Calculate landmark global position. - // This should succeed since the region handle is available. - LLVector3d pos; - if (!landmark->getGlobalPos(pos)) - { - LL_WARNS() << "Got region handle but the landmark " << landmark_id << " global position is still unknown." << LL_ENDL; - eraseCallbacks(landmark_id); - return; - } - - // Call this even if no landmark exists to clean mLoadedCallbackMap - makeCallbacks(landmark_id); -} - -void LLLandmarkList::eraseCallbacks(const LLUUID& landmark_id) -{ - mLoadedCallbackMap.erase(landmark_id); -} - -void LLLandmarkList::makeCallbacks(const LLUUID& landmark_id) -{ - LLLandmark* landmark = getAsset(landmark_id); - - if (!landmark) - { - LL_WARNS() << "Landmark " << landmark_id << " to make callbacks for not found." << LL_ENDL; - } - - // make all the callbacks here. - loaded_callback_map_t::iterator it; - while((it = mLoadedCallbackMap.find(landmark_id)) != mLoadedCallbackMap.end()) - { - if (landmark) - (*it).second(landmark); - - mLoadedCallbackMap.erase(it); - } -} +/** + * @file lllandmarklist.cpp + * @brief Landmark asset list class + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lllandmarklist.h" + +#include "message.h" +#include "llassetstorage.h" + +#include "llappviewer.h" +#include "llagent.h" +#include "llfilesystem.h" +#include "llviewerstats.h" + +// Globals +LLLandmarkList gLandmarkList; + +//////////////////////////////////////////////////////////////////////////// +// LLLandmarkList + +LLLandmarkList::~LLLandmarkList() +{ + std::for_each(mList.begin(), mList.end(), DeletePairedPointer()); + mList.clear(); +} + +LLLandmark* LLLandmarkList::getAsset(const LLUUID& asset_uuid, loaded_callback_t cb) +{ + LLLandmark* landmark = get_ptr_in_map(mList, asset_uuid); + if(landmark) + { + LLVector3d dummy; + if(cb && !landmark->getGlobalPos(dummy)) + { + // landmark is not completely loaded yet + loaded_callback_map_t::value_type vt(asset_uuid, cb); + mLoadedCallbackMap.insert(vt); + } + return landmark; + } + else + { + if ( mBadList.find(asset_uuid) != mBadList.end() ) + { + return NULL; + } + + if (cb) + { + // Multiple different sources can request same landmark, + // mLoadedCallbackMap is a multimap that allows multiple pairs with same key + // Todo: this might need to be improved to not hold identical callbacks multiple times + loaded_callback_map_t::value_type vt(asset_uuid, cb); + mLoadedCallbackMap.insert(vt); + } + + landmark_requested_list_t::iterator iter = mRequestedList.find(asset_uuid); + if (iter != mRequestedList.end()) + { + const F32 rerequest_time = 30.f; // 30 seconds between requests + if (gFrameTimeSeconds - iter->second < rerequest_time) + { + return NULL; + } + } + + mRequestedList[asset_uuid] = gFrameTimeSeconds; + + // Note that getAssetData can callback immediately and cleans mRequestedList + gAssetStorage->getAssetData(asset_uuid, + LLAssetType::AT_LANDMARK, + LLLandmarkList::processGetAssetReply, + NULL); + } + return NULL; +} + +// static +void LLLandmarkList::processGetAssetReply( + const LLUUID& uuid, + LLAssetType::EType type, + void* user_data, + S32 status, + LLExtStat ext_status ) +{ + if( status == 0 ) + { + LLFileSystem file(uuid, type); + S32 file_length = file.getSize(); + + if (file_length > 0) + { + std::vector buffer(file_length + 1); + file.read((U8*)&buffer[0], file_length); + buffer[file_length] = 0; + + LLLandmark* landmark = LLLandmark::constructFromString(&buffer[0], buffer.size()); + if (landmark) + { + gLandmarkList.mList[uuid] = landmark; + gLandmarkList.mRequestedList.erase(uuid); + + LLVector3d pos; + if (!landmark->getGlobalPos(pos)) + { + LLUUID region_id; + if (landmark->getRegionID(region_id)) + { + LLLandmark::requestRegionHandle( + gMessageSystem, + gAgent.getRegionHost(), + region_id, + boost::bind(&LLLandmarkList::onRegionHandle, &gLandmarkList, uuid)); + } + + // the callback will be called when we get the region handle. + } + else + { + gLandmarkList.makeCallbacks(uuid); + } + } + else + { + // failed to parse, shouldn't happen + gLandmarkList.eraseCallbacks(uuid); + } + } + else + { + // got a good status, but no file, shouldn't happen + gLandmarkList.eraseCallbacks(uuid); + } + } + else + { + // SJB: No use case for a notification here. Use LL_DEBUGS() instead + if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status ) + { + LL_WARNS("Landmarks") << "Missing Landmark" << LL_ENDL; + //LLNotificationsUtil::add("LandmarkMissing"); + } + else + { + LL_WARNS("Landmarks") << "Unable to load Landmark" << LL_ENDL; + //LLNotificationsUtil::add("UnableToLoadLandmark"); + } + + gLandmarkList.mBadList.insert(uuid); + gLandmarkList.mRequestedList.erase(uuid); //mBadList effectively blocks any load, so no point keeping id in requests + gLandmarkList.eraseCallbacks(uuid); + } +} + +bool LLLandmarkList::isAssetInLoadedCallbackMap(const LLUUID& asset_uuid) +{ + return mLoadedCallbackMap.find(asset_uuid) != mLoadedCallbackMap.end(); +} + +bool LLLandmarkList::assetExists(const LLUUID& asset_uuid) +{ + return mList.count(asset_uuid) != 0 || mBadList.count(asset_uuid) != 0; +} + +void LLLandmarkList::onRegionHandle(const LLUUID& landmark_id) +{ + LLLandmark* landmark = getAsset(landmark_id); + if (!landmark) + { + LL_WARNS() << "Got region handle but the landmark " << landmark_id << " not found." << LL_ENDL; + eraseCallbacks(landmark_id); + return; + } + + // Calculate landmark global position. + // This should succeed since the region handle is available. + LLVector3d pos; + if (!landmark->getGlobalPos(pos)) + { + LL_WARNS() << "Got region handle but the landmark " << landmark_id << " global position is still unknown." << LL_ENDL; + eraseCallbacks(landmark_id); + return; + } + + // Call this even if no landmark exists to clean mLoadedCallbackMap + makeCallbacks(landmark_id); +} + +void LLLandmarkList::eraseCallbacks(const LLUUID& landmark_id) +{ + mLoadedCallbackMap.erase(landmark_id); +} + +void LLLandmarkList::makeCallbacks(const LLUUID& landmark_id) +{ + LLLandmark* landmark = getAsset(landmark_id); + + if (!landmark) + { + LL_WARNS() << "Landmark " << landmark_id << " to make callbacks for not found." << LL_ENDL; + } + + // make all the callbacks here. + loaded_callback_map_t::iterator it; + while((it = mLoadedCallbackMap.find(landmark_id)) != mLoadedCallbackMap.end()) + { + if (landmark) + (*it).second(landmark); + + mLoadedCallbackMap.erase(it); + } +} diff --git a/indra/newview/lllandmarklist.h b/indra/newview/lllandmarklist.h index 1ea445350b..508148abde 100644 --- a/indra/newview/lllandmarklist.h +++ b/indra/newview/lllandmarklist.h @@ -1,88 +1,88 @@ -/** - * @file lllandmarklist.h - * @brief Landmark asset list class - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLLANDMARKLIST_H -#define LL_LLLANDMARKLIST_H - -#include -#include -#include "lllandmark.h" -#include "lluuid.h" -#include "llassetstorage.h" - -class LLMessageSystem; -class LLLineEditor; -class LLInventoryItem; - -class LLLandmarkList -{ -public: - typedef boost::function loaded_callback_t; - - LLLandmarkList() {} - ~LLLandmarkList(); - - //S32 getLength() { return mList.getLength(); } - //const LLLandmark* getFirst() { return mList.getFirstData(); } - //const LLLandmark* getNext() { return mList.getNextData(); } - - bool assetExists(const LLUUID& asset_uuid); - LLLandmark* getAsset(const LLUUID& asset_uuid, loaded_callback_t cb = NULL); - static void processGetAssetReply( - const LLUUID& uuid, - LLAssetType::EType type, - void* user_data, - S32 status, - LLExtStat ext_status ); - - // Returns true if loading the landmark with given asset_uuid has been requested - // but is not complete yet. - bool isAssetInLoadedCallbackMap(const LLUUID& asset_uuid); - -protected: - void onRegionHandle(const LLUUID& landmark_id); - void eraseCallbacks(const LLUUID& landmark_id); - void makeCallbacks(const LLUUID& landmark_id); - - typedef std::map landmark_list_t; - landmark_list_t mList; - - typedef std::set landmark_uuid_list_t; - landmark_uuid_list_t mBadList; - - typedef std::map landmark_requested_list_t; - landmark_requested_list_t mRequestedList; - - // *TODO: make the callback multimap a template class and make use of it - // here and in LLLandmark. - typedef std::multimap loaded_callback_map_t; - loaded_callback_map_t mLoadedCallbackMap; -}; - - -extern LLLandmarkList gLandmarkList; - -#endif // LL_LLLANDMARKLIST_H +/** + * @file lllandmarklist.h + * @brief Landmark asset list class + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLLANDMARKLIST_H +#define LL_LLLANDMARKLIST_H + +#include +#include +#include "lllandmark.h" +#include "lluuid.h" +#include "llassetstorage.h" + +class LLMessageSystem; +class LLLineEditor; +class LLInventoryItem; + +class LLLandmarkList +{ +public: + typedef boost::function loaded_callback_t; + + LLLandmarkList() {} + ~LLLandmarkList(); + + //S32 getLength() { return mList.getLength(); } + //const LLLandmark* getFirst() { return mList.getFirstData(); } + //const LLLandmark* getNext() { return mList.getNextData(); } + + bool assetExists(const LLUUID& asset_uuid); + LLLandmark* getAsset(const LLUUID& asset_uuid, loaded_callback_t cb = NULL); + static void processGetAssetReply( + const LLUUID& uuid, + LLAssetType::EType type, + void* user_data, + S32 status, + LLExtStat ext_status ); + + // Returns true if loading the landmark with given asset_uuid has been requested + // but is not complete yet. + bool isAssetInLoadedCallbackMap(const LLUUID& asset_uuid); + +protected: + void onRegionHandle(const LLUUID& landmark_id); + void eraseCallbacks(const LLUUID& landmark_id); + void makeCallbacks(const LLUUID& landmark_id); + + typedef std::map landmark_list_t; + landmark_list_t mList; + + typedef std::set landmark_uuid_list_t; + landmark_uuid_list_t mBadList; + + typedef std::map landmark_requested_list_t; + landmark_requested_list_t mRequestedList; + + // *TODO: make the callback multimap a template class and make use of it + // here and in LLLandmark. + typedef std::multimap loaded_callback_map_t; + loaded_callback_map_t mLoadedCallbackMap; +}; + + +extern LLLandmarkList gLandmarkList; + +#endif // LL_LLLANDMARKLIST_H diff --git a/indra/newview/lllegacyatmospherics.cpp b/indra/newview/lllegacyatmospherics.cpp index a30b0625ae..1403845d07 100644 --- a/indra/newview/lllegacyatmospherics.cpp +++ b/indra/newview/lllegacyatmospherics.cpp @@ -1,799 +1,799 @@ -/** - * @file lllegacyatmospherics.cpp - * @brief LLAtmospherics class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lllegacyatmospherics.h" - -#include "llfeaturemanager.h" -#include "llviewercontrol.h" -#include "llframetimer.h" - -#include "llagent.h" -#include "llagentcamera.h" -#include "lldrawable.h" -#include "llface.h" -#include "llglheaders.h" -#include "llsky.h" -#include "llviewercamera.h" -#include "llviewertexturelist.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llworld.h" -#include "pipeline.h" -#include "v3colorutil.h" - -#include "llsettingssky.h" -#include "llenvironment.h" -#include "lldrawpoolwater.h" - -class LLFastLn -{ -public: - LLFastLn() - { - mTable[0] = 0; - for( S32 i = 1; i < 257; i++ ) - { - mTable[i] = log((F32)i); - } - } - - F32 ln( F32 x ) - { - const F32 OO_255 = 0.003921568627450980392156862745098f; - const F32 LN_255 = 5.5412635451584261462455391880218f; - - if( x < OO_255 ) - { - return log(x); - } - else - if( x < 1 ) - { - x *= 255.f; - S32 index = llfloor(x); - F32 t = x - index; - F32 low = mTable[index]; - F32 high = mTable[index + 1]; - return low + t * (high - low) - LN_255; - } - else - if( x <= 255 ) - { - S32 index = llfloor(x); - F32 t = x - index; - F32 low = mTable[index]; - F32 high = mTable[index + 1]; - return low + t * (high - low); - } - else - { - return log( x ); - } - } - - F32 pow( F32 x, F32 y ) - { - return (F32)LL_FAST_EXP(y * ln(x)); - } - - -private: - F32 mTable[257]; // index 0 is unused -}; - -static LLFastLn gFastLn; - - -// Functions used a lot. - -inline F32 LLHaze::calcPhase(const F32 cos_theta) const -{ - const F32 g2 = mG * mG; - const F32 den = 1 + g2 - 2 * mG * cos_theta; - return (1 - g2) * gFastLn.pow(den, -1.5); -} - -inline void color_pow(LLColor3 &col, const F32 e) -{ - col.mV[0] = gFastLn.pow(col.mV[0], e); - col.mV[1] = gFastLn.pow(col.mV[1], e); - col.mV[2] = gFastLn.pow(col.mV[2], e); -} - -inline LLColor3 color_norm(const LLColor3 &col) -{ - const F32 m = color_max(col); - if (m > 1.f) - { - return 1.f/m * col; - } - else return col; -} - -inline void color_gamma_correct(LLColor3 &col) -{ - const F32 gamma_inv = 1.f/1.2f; - if (col.mV[0] != 0.f) - { - col.mV[0] = gFastLn.pow(col.mV[0], gamma_inv); - } - if (col.mV[1] != 0.f) - { - col.mV[1] = gFastLn.pow(col.mV[1], gamma_inv); - } - if (col.mV[2] != 0.f) - { - col.mV[2] = gFastLn.pow(col.mV[2], gamma_inv); - } -} - -static LLColor3 calc_air_sca_sea_level() -{ - static LLColor3 WAVE_LEN(675, 520, 445); - static LLColor3 refr_ind = refr_ind_calc(WAVE_LEN); - static LLColor3 n21 = refr_ind * refr_ind - LLColor3(1, 1, 1); - static LLColor3 n4 = n21 * n21; - static LLColor3 wl2 = WAVE_LEN * WAVE_LEN * 1e-6f; - static LLColor3 wl4 = wl2 * wl2; - static LLColor3 mult_const = fsigma * 2.0f/ 3.0f * 1e24f * (F_PI * F_PI) * n4; - static F32 dens_div_N = F32( ATM_SEA_LEVEL_NDENS / Ndens2); - return dens_div_N * mult_const.divide(wl4); -} - -// static constants. -LLColor3 const LLHaze::sAirScaSeaLevel = calc_air_sca_sea_level(); -F32 const LLHaze::sAirScaIntense = color_intens(LLHaze::sAirScaSeaLevel); -F32 const LLHaze::sAirScaAvg = LLHaze::sAirScaIntense / 3.f; - -/*************************************** - Atmospherics -***************************************/ - -LLAtmospherics::LLAtmospherics() -: mCloudDensity(0.2f), - mWind(0.f), - mWorldScale(1.f) -{ - /// WL PARAMS - mInitialized = false; - mAmbientScale = gSavedSettings.getF32("SkyAmbientScale"); - mNightColorShift = gSavedSettings.getColor3("SkyNightColorShift"); - mFogColor.mV[VRED] = mFogColor.mV[VGREEN] = mFogColor.mV[VBLUE] = 0.5f; - mFogColor.mV[VALPHA] = 0.0f; - mFogRatio = 1.2f; - mHazeConcentration = 0.f; - mInterpVal = 0.f; -} - - -LLAtmospherics::~LLAtmospherics() -{ -} - -void LLAtmospherics::init() -{ - const F32 haze_int = color_intens(mHaze.calcSigSca(0)); - mHazeConcentration = haze_int / (color_intens(mHaze.calcAirSca(0)) + haze_int); - mInitialized = true; -} - -// This cubemap is used as "environmentMap" in indra/newview/app_settings/shaders/class2/deferred/softenLightF.glsl -LLColor4 LLAtmospherics::calcSkyColorInDir(const LLSettingsSky::ptr_t &psky, AtmosphericsVars& vars, const LLVector3 &dir, bool isShiny, bool low_end) -{ - const F32 sky_saturation = 0.25f; - const F32 land_saturation = 0.1f; - - if (isShiny && dir.mV[VZ] < -0.02f) - { - LLColor4 col; - LLColor3 desat_fog = LLColor3(mFogColor); - F32 brightness = desat_fog.brightness();// NOTE: Linear brightness! - // So that shiny somewhat shows up at night. - if (brightness < 0.15f) - { - brightness = 0.15f; - desat_fog = smear(0.15f); - } - F32 greyscale_sat = brightness * (1.0f - land_saturation); - desat_fog = desat_fog * land_saturation + smear(greyscale_sat); - if (low_end) - { - col = LLColor4(desat_fog, 0.f); - } - else - { - col = LLColor4(desat_fog * 0.5f, 0.f); - } - float x = 1.0f-fabsf(-0.1f-dir.mV[VZ]); - x *= x; - col.mV[0] *= x*x; - col.mV[1] *= powf(x, 2.5f); - col.mV[2] *= x*x*x; - return col; - } - - // undo OGL_TO_CFR_ROTATION and negate vertical direction. - LLVector3 Pn = LLVector3(-dir[1] , -dir[2], -dir[0]); - - //calculates hazeColor - calcSkyColorWLVert(psky, Pn, vars); - - if (isShiny) - { - F32 brightness = vars.hazeColor.brightness(); - F32 greyscale_sat = brightness * (1.0f - sky_saturation); - LLColor3 sky_color = vars.hazeColor * sky_saturation + smear(greyscale_sat); - //sky_color *= (0.5f + 0.5f * brightness); // SL-12574 EEP sky is being attenuated too much - return LLColor4(sky_color, 0.0f); - } - - LLColor3 sky_color = low_end ? vars.hazeColor * 2.0f : psky->gammaCorrect(vars.hazeColor * 2.0f, vars.gamma); - - return LLColor4(sky_color, 0.0f); -} - -// NOTE: Keep these in sync! -// indra\newview\app_settings\shaders\class1\deferred\skyV.glsl -// indra\newview\app_settings\shaders\class1\deferred\cloudsV.glsl -// indra\newview\lllegacyatmospherics.cpp -void LLAtmospherics::calcSkyColorWLVert(const LLSettingsSky::ptr_t &psky, LLVector3 & Pn, AtmosphericsVars& vars) -{ - const LLColor3 blue_density = vars.blue_density; - const LLColor3 blue_horizon = vars.blue_horizon; - const F32 haze_horizon = vars.haze_horizon; - const F32 haze_density = vars.haze_density; - const F32 density_multiplier = vars.density_multiplier; - - F32 max_y = vars.max_y; - LLVector4 sun_norm = vars.sun_norm; - - // project the direction ray onto the sky dome. - F32 phi = acos(Pn[1]); - F32 sinA = sin(F_PI - phi); - if (fabsf(sinA) < 0.01f) - { //avoid division by zero - sinA = 0.01f; - } - - F32 Plen = vars.dome_radius * sin(F_PI + phi + asin(vars.dome_offset * sinA)) / sinA; - - Pn *= Plen; - - // Set altitude - if (Pn[1] > 0.f) - { - Pn *= (max_y / Pn[1]); - } - else - { - Pn *= (-32000.f / Pn[1]); - } - - Plen = Pn.length(); - Pn /= Plen; - - // Initialize temp variables - LLColor3 sunlight = vars.sunlight; - LLColor3 ambient = vars.ambient; - - LLColor3 glow = vars.glow; - F32 cloud_shadow = vars.cloud_shadow; - - // Sunlight attenuation effect (hue and brightness) due to atmosphere - // this is used later for sunlight modulation at various altitudes - LLColor3 light_atten = vars.light_atten; - LLColor3 light_transmittance = psky->getLightTransmittanceFast(vars.total_density, vars.density_multiplier, Plen); - (void)light_transmittance; // silence Clang warn-error - - // Calculate relative weights - LLColor3 temp2(0.f, 0.f, 0.f); - LLColor3 temp1 = vars.total_density; - - LLColor3 blue_weight = componentDiv(blue_density, temp1); - LLColor3 blue_factor = blue_horizon * blue_weight; - LLColor3 haze_weight = componentDiv(smear(haze_density), temp1); - LLColor3 haze_factor = haze_horizon * haze_weight; - - - // Compute sunlight from P & lightnorm (for long rays like sky) - temp2.mV[1] = llmax(F_APPROXIMATELY_ZERO, llmax(0.f, Pn[1]) * 1.0f + sun_norm.mV[1] ); - - temp2.mV[1] = 1.f / temp2.mV[1]; - componentMultBy(sunlight, componentExp((light_atten * -1.f) * temp2.mV[1])); - componentMultBy(sunlight, light_transmittance); - - // Distance - temp2.mV[2] = Plen * density_multiplier; - - // Transparency (-> temp1) - temp1 = componentExp((temp1 * -1.f) * temp2.mV[2]); - - // Compute haze glow - temp2.mV[0] = Pn * LLVector3(sun_norm); - - temp2.mV[0] = 1.f - temp2.mV[0]; - // temp2.x is 0 at the sun and increases away from sun - temp2.mV[0] = llmax(temp2.mV[0], .001f); - // Set a minimum "angle" (smaller glow.y allows tighter, brighter hotspot) - - // Higher glow.x gives dimmer glow (because next step is 1 / "angle") - temp2.mV[0] *= glow.mV[0]; - - temp2.mV[0] = pow(temp2.mV[0], glow.mV[2]); - // glow.z should be negative, so we're doing a sort of (1 / "angle") function - - // Add "minimum anti-solar illumination" - temp2.mV[0] += .25f; - - - // Haze color above cloud - vars.hazeColor = (blue_factor * (sunlight + ambient) + componentMult(haze_factor, sunlight * temp2.mV[0] + ambient)); - - // Increase ambient when there are more clouds - LLColor3 tmpAmbient = ambient + (LLColor3::white - ambient) * cloud_shadow * 0.5f; - - // Dim sunlight by cloud shadow percentage - sunlight *= (1.f - cloud_shadow); - - // Haze color below cloud - vars.hazeColorBelowCloud = (blue_factor * (sunlight + tmpAmbient) + componentMult(haze_factor, sunlight * temp2.mV[0] + tmpAmbient)); - - // Final atmosphere additive - componentMultBy(vars.hazeColor, LLColor3::white - temp1); - -/* - // SL-12574 - - // Attenuate cloud color by atmosphere - temp1 = componentSqrt(temp1); //less atmos opacity (more transparency) below clouds - - // At horizon, blend high altitude sky color towards the darker color below the clouds - vars.hazeColor += componentMult(vars.hazeColorBelowCloud - vars.hazeColor, LLColor3::white - componentSqrt(temp1)); -*/ -} - -void LLAtmospherics::updateFog(const F32 distance, const LLVector3& tosun_in) -{ - LLVector3 tosun = tosun_in; - - if (!gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_FOG)) - { - return; - } - - LLColor4 target_fog(0.f, 0.2f, 0.5f, 0.f); - - const F32 water_height = gAgent.getRegion() ? gAgent.getRegion()->getWaterHeight() : 0.f; - // LLWorld::getInstance()->getWaterHeight(); - F32 camera_height = gAgentCamera.getCameraPositionAgent().mV[2]; - - F32 near_clip_height = LLViewerCamera::getInstance()->getAtAxis().mV[VZ] * LLViewerCamera::getInstance()->getNear(); - camera_height += near_clip_height; - - F32 fog_distance = 0.f; - LLColor3 res_color[3]; - - LLColor3 sky_fog_color = LLColor3::white; - LLColor3 render_fog_color = LLColor3::white; - - const F32 tosun_z = tosun.mV[VZ]; - tosun.mV[VZ] = 0.f; - tosun.normalize(); - LLVector3 perp_tosun; - perp_tosun.mV[VX] = -tosun.mV[VY]; - perp_tosun.mV[VY] = tosun.mV[VX]; - LLVector3 tosun_45 = tosun + perp_tosun; - tosun_45.normalize(); - - F32 delta = 0.06f; - tosun.mV[VZ] = delta; - perp_tosun.mV[VZ] = delta; - tosun_45.mV[VZ] = delta; - tosun.normalize(); - perp_tosun.normalize(); - tosun_45.normalize(); - - // Sky colors, just slightly above the horizon in the direction of the sun, perpendicular to the sun, and at a 45 degree angle to the sun. - AtmosphericsVars vars; - - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - - // NOTE: This is very similar to LLVOSky::cacheEnvironment() - // Differences: - // vars.sun_norm - // vars.sunlight - // invariants across whole sky tex process... - vars.blue_density = psky->getBlueDensity(); - vars.blue_horizon = psky->getBlueHorizon(); - vars.haze_density = psky->getHazeDensity(); - vars.haze_horizon = psky->getHazeHorizon(); - vars.density_multiplier = psky->getDensityMultiplier(); - vars.distance_multiplier = psky->getDistanceMultiplier(); - vars.max_y = psky->getMaxY(); - vars.sun_norm = LLEnvironment::instance().getSunDirectionCFR(); - vars.sunlight = psky->getSunlightColor(); - vars.ambient = psky->getAmbientColor(); - vars.glow = psky->getGlow(); - vars.cloud_shadow = psky->getCloudShadow(); - vars.dome_radius = psky->getDomeRadius(); - vars.dome_offset = psky->getDomeOffset(); - vars.light_atten = psky->getLightAttenuation(vars.max_y); - vars.light_transmittance = psky->getLightTransmittance(vars.max_y); - vars.total_density = psky->getTotalDensity(); - vars.gamma = psky->getGamma(); - - res_color[0] = calcSkyColorInDir(psky, vars, tosun); - res_color[1] = calcSkyColorInDir(psky, vars, perp_tosun); - res_color[2] = calcSkyColorInDir(psky, vars, tosun_45); - - sky_fog_color = color_norm(res_color[0] + res_color[1] + res_color[2]); - - F32 full_off = -0.25f; - F32 full_on = 0.00f; - F32 on = (tosun_z - full_off) / (full_on - full_off); - on = llclamp(on, 0.01f, 1.f); - sky_fog_color *= 0.5f * on; - - - // We need to clamp these to non-zero, in order for the gamma correction to work. 0^y = ??? - S32 i; - for (i = 0; i < 3; i++) - { - sky_fog_color.mV[i] = llmax(0.0001f, sky_fog_color.mV[i]); - } - - color_gamma_correct(sky_fog_color); - - render_fog_color = sky_fog_color; - - fog_distance = mFogRatio * distance; - - if (camera_height > water_height) - { - LLColor4 fog(render_fog_color); - mGLFogCol = fog; - } - else - { - LLSettingsWater::ptr_t pwater = LLEnvironment::instance().getCurrentWater(); - F32 depth = water_height - camera_height; - LLColor4 water_fog_color(pwater->getWaterFogColor()); - - // adjust the color based on depth. We're doing linear approximations - float depth_scale = gSavedSettings.getF32("WaterGLFogDepthScale"); - float depth_modifier = 1.0f - llmin(llmax(depth / depth_scale, 0.01f), - gSavedSettings.getF32("WaterGLFogDepthFloor")); - - LLColor4 fogCol = water_fog_color * depth_modifier; - fogCol.setAlpha(1); - - // set the gl fog color - mGLFogCol = fogCol; - } - - mFogColor = sky_fog_color; - mFogColor.setAlpha(1); - - LLDrawPoolWater::sWaterFogEnd = fog_distance*2.2f; - - stop_glerror(); -} - -// Functions used a lot. -F32 color_norm_pow(LLColor3& col, F32 e, bool postmultiply) -{ - F32 mv = color_max(col); - if (0 == mv) - { - return 0; - } - - col *= 1.f / mv; - color_pow(col, e); - if (postmultiply) - { - col *= mv; - } - return mv; -} - -// Returns angle (RADIANs) between the horizontal projection of "v" and the x_axis. -// Range of output is 0.0f to 2pi //359.99999...f -// Returns 0.0f when "v" = +/- z_axis. -F32 azimuth(const LLVector3 &v) -{ - F32 azimuth = 0.0f; - if (v.mV[VX] == 0.0f) - { - if (v.mV[VY] > 0.0f) - { - azimuth = F_PI * 0.5f; - } - else if (v.mV[VY] < 0.0f) - { - azimuth = F_PI * 1.5f;// 270.f; - } - } - else - { - azimuth = (F32) atan(v.mV[VY] / v.mV[VX]); - if (v.mV[VX] < 0.0f) - { - azimuth += F_PI; - } - else if (v.mV[VY] < 0.0f) - { - azimuth += F_PI * 2; - } - } - return azimuth; -} - -bool operator==(const AtmosphericsVars& a, const AtmosphericsVars& b) -{ - if (a.hazeColor != b.hazeColor) - { - return false; - } - - if (a.hazeColorBelowCloud != b.hazeColorBelowCloud) - { - return false; - } - - if (a.cloudColorSun != b.cloudColorSun) - { - return false; - } - - if (a.cloudColorAmbient != b.cloudColorAmbient) - { - return false; - } - - if (a.cloudDensity != b.cloudDensity) - { - return false; - } - - if (a.density_multiplier != b.density_multiplier) - { - return false; - } - - if (a.haze_horizon != b.haze_horizon) - { - return false; - } - - if (a.haze_density != b.haze_density) - { - return false; - } - - if (a.blue_horizon != b.blue_horizon) - { - return false; - } - - if (a.blue_density != b.blue_density) - { - return false; - } - - if (a.dome_offset != b.dome_offset) - { - return false; - } - - if (a.dome_radius != b.dome_radius) - { - return false; - } - - if (a.cloud_shadow != b.cloud_shadow) - { - return false; - } - - if (a.glow != b.glow) - { - return false; - } - - if (a.ambient != b.ambient) - { - return false; - } - - if (a.sunlight != b.sunlight) - { - return false; - } - - if (a.sun_norm != b.sun_norm) - { - return false; - } - - if (a.gamma != b.gamma) - { - return false; - } - - if (a.max_y != b.max_y) - { - return false; - } - - if (a.distance_multiplier != b.distance_multiplier) - { - return false; - } - - // light_atten, light_transmittance, total_density - // are ignored as they always change when the values above do - // they're just shared calc across the sky map generation to save cycles - - return true; -} - -bool approximatelyEqual(const F32 &a, const F32 &b, const F32 &fraction_treshold) -{ - F32 diff = fabs(a - b); - if (diff < F_APPROXIMATELY_ZERO || diff < llmax(fabs(a), fabs(b)) * fraction_treshold) - { - return true; - } - return false; -} - -bool approximatelyEqual(const LLColor3 &a, const LLColor3 &b, const F32 &fraction_treshold) -{ - return approximatelyEqual(a.mV[0], b.mV[0], fraction_treshold) - && approximatelyEqual(a.mV[1], b.mV[1], fraction_treshold) - && approximatelyEqual(a.mV[2], b.mV[2], fraction_treshold); -} - -bool approximatelyEqual(const LLVector4 &a, const LLVector4 &b, const F32 &fraction_treshold) -{ - return approximatelyEqual(a.mV[0], b.mV[0], fraction_treshold) - && approximatelyEqual(a.mV[1], b.mV[1], fraction_treshold) - && approximatelyEqual(a.mV[2], b.mV[2], fraction_treshold) - && approximatelyEqual(a.mV[3], b.mV[3], fraction_treshold); -} - -bool approximatelyEqual(const AtmosphericsVars& a, const AtmosphericsVars& b, const F32 fraction_treshold) -{ - if (!approximatelyEqual(a.hazeColor, b.hazeColor, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.hazeColorBelowCloud, b.hazeColorBelowCloud, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.cloudColorSun, b.cloudColorSun, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.cloudColorAmbient, b.cloudColorAmbient, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.cloudDensity, b.cloudDensity, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.density_multiplier, b.density_multiplier, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.haze_horizon, b.haze_horizon, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.haze_density, b.haze_density, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.blue_horizon, b.blue_horizon, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.blue_density, b.blue_density, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.dome_offset, b.dome_offset, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.dome_radius, b.dome_radius, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.cloud_shadow, b.cloud_shadow, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.glow, b.glow, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.ambient, b.ambient, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.sunlight, b.sunlight, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.sun_norm, b.sun_norm, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.gamma, b.gamma, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.max_y, b.max_y, fraction_treshold)) - { - return false; - } - - if (!approximatelyEqual(a.distance_multiplier, b.distance_multiplier, fraction_treshold)) - { - return false; - } - - // light_atten, light_transmittance, total_density - // are ignored as they always change when the values above do - // they're just shared calc across the sky map generation to save cycles - - return true; -} - +/** + * @file lllegacyatmospherics.cpp + * @brief LLAtmospherics class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lllegacyatmospherics.h" + +#include "llfeaturemanager.h" +#include "llviewercontrol.h" +#include "llframetimer.h" + +#include "llagent.h" +#include "llagentcamera.h" +#include "lldrawable.h" +#include "llface.h" +#include "llglheaders.h" +#include "llsky.h" +#include "llviewercamera.h" +#include "llviewertexturelist.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llworld.h" +#include "pipeline.h" +#include "v3colorutil.h" + +#include "llsettingssky.h" +#include "llenvironment.h" +#include "lldrawpoolwater.h" + +class LLFastLn +{ +public: + LLFastLn() + { + mTable[0] = 0; + for( S32 i = 1; i < 257; i++ ) + { + mTable[i] = log((F32)i); + } + } + + F32 ln( F32 x ) + { + const F32 OO_255 = 0.003921568627450980392156862745098f; + const F32 LN_255 = 5.5412635451584261462455391880218f; + + if( x < OO_255 ) + { + return log(x); + } + else + if( x < 1 ) + { + x *= 255.f; + S32 index = llfloor(x); + F32 t = x - index; + F32 low = mTable[index]; + F32 high = mTable[index + 1]; + return low + t * (high - low) - LN_255; + } + else + if( x <= 255 ) + { + S32 index = llfloor(x); + F32 t = x - index; + F32 low = mTable[index]; + F32 high = mTable[index + 1]; + return low + t * (high - low); + } + else + { + return log( x ); + } + } + + F32 pow( F32 x, F32 y ) + { + return (F32)LL_FAST_EXP(y * ln(x)); + } + + +private: + F32 mTable[257]; // index 0 is unused +}; + +static LLFastLn gFastLn; + + +// Functions used a lot. + +inline F32 LLHaze::calcPhase(const F32 cos_theta) const +{ + const F32 g2 = mG * mG; + const F32 den = 1 + g2 - 2 * mG * cos_theta; + return (1 - g2) * gFastLn.pow(den, -1.5); +} + +inline void color_pow(LLColor3 &col, const F32 e) +{ + col.mV[0] = gFastLn.pow(col.mV[0], e); + col.mV[1] = gFastLn.pow(col.mV[1], e); + col.mV[2] = gFastLn.pow(col.mV[2], e); +} + +inline LLColor3 color_norm(const LLColor3 &col) +{ + const F32 m = color_max(col); + if (m > 1.f) + { + return 1.f/m * col; + } + else return col; +} + +inline void color_gamma_correct(LLColor3 &col) +{ + const F32 gamma_inv = 1.f/1.2f; + if (col.mV[0] != 0.f) + { + col.mV[0] = gFastLn.pow(col.mV[0], gamma_inv); + } + if (col.mV[1] != 0.f) + { + col.mV[1] = gFastLn.pow(col.mV[1], gamma_inv); + } + if (col.mV[2] != 0.f) + { + col.mV[2] = gFastLn.pow(col.mV[2], gamma_inv); + } +} + +static LLColor3 calc_air_sca_sea_level() +{ + static LLColor3 WAVE_LEN(675, 520, 445); + static LLColor3 refr_ind = refr_ind_calc(WAVE_LEN); + static LLColor3 n21 = refr_ind * refr_ind - LLColor3(1, 1, 1); + static LLColor3 n4 = n21 * n21; + static LLColor3 wl2 = WAVE_LEN * WAVE_LEN * 1e-6f; + static LLColor3 wl4 = wl2 * wl2; + static LLColor3 mult_const = fsigma * 2.0f/ 3.0f * 1e24f * (F_PI * F_PI) * n4; + static F32 dens_div_N = F32( ATM_SEA_LEVEL_NDENS / Ndens2); + return dens_div_N * mult_const.divide(wl4); +} + +// static constants. +LLColor3 const LLHaze::sAirScaSeaLevel = calc_air_sca_sea_level(); +F32 const LLHaze::sAirScaIntense = color_intens(LLHaze::sAirScaSeaLevel); +F32 const LLHaze::sAirScaAvg = LLHaze::sAirScaIntense / 3.f; + +/*************************************** + Atmospherics +***************************************/ + +LLAtmospherics::LLAtmospherics() +: mCloudDensity(0.2f), + mWind(0.f), + mWorldScale(1.f) +{ + /// WL PARAMS + mInitialized = false; + mAmbientScale = gSavedSettings.getF32("SkyAmbientScale"); + mNightColorShift = gSavedSettings.getColor3("SkyNightColorShift"); + mFogColor.mV[VRED] = mFogColor.mV[VGREEN] = mFogColor.mV[VBLUE] = 0.5f; + mFogColor.mV[VALPHA] = 0.0f; + mFogRatio = 1.2f; + mHazeConcentration = 0.f; + mInterpVal = 0.f; +} + + +LLAtmospherics::~LLAtmospherics() +{ +} + +void LLAtmospherics::init() +{ + const F32 haze_int = color_intens(mHaze.calcSigSca(0)); + mHazeConcentration = haze_int / (color_intens(mHaze.calcAirSca(0)) + haze_int); + mInitialized = true; +} + +// This cubemap is used as "environmentMap" in indra/newview/app_settings/shaders/class2/deferred/softenLightF.glsl +LLColor4 LLAtmospherics::calcSkyColorInDir(const LLSettingsSky::ptr_t &psky, AtmosphericsVars& vars, const LLVector3 &dir, bool isShiny, bool low_end) +{ + const F32 sky_saturation = 0.25f; + const F32 land_saturation = 0.1f; + + if (isShiny && dir.mV[VZ] < -0.02f) + { + LLColor4 col; + LLColor3 desat_fog = LLColor3(mFogColor); + F32 brightness = desat_fog.brightness();// NOTE: Linear brightness! + // So that shiny somewhat shows up at night. + if (brightness < 0.15f) + { + brightness = 0.15f; + desat_fog = smear(0.15f); + } + F32 greyscale_sat = brightness * (1.0f - land_saturation); + desat_fog = desat_fog * land_saturation + smear(greyscale_sat); + if (low_end) + { + col = LLColor4(desat_fog, 0.f); + } + else + { + col = LLColor4(desat_fog * 0.5f, 0.f); + } + float x = 1.0f-fabsf(-0.1f-dir.mV[VZ]); + x *= x; + col.mV[0] *= x*x; + col.mV[1] *= powf(x, 2.5f); + col.mV[2] *= x*x*x; + return col; + } + + // undo OGL_TO_CFR_ROTATION and negate vertical direction. + LLVector3 Pn = LLVector3(-dir[1] , -dir[2], -dir[0]); + + //calculates hazeColor + calcSkyColorWLVert(psky, Pn, vars); + + if (isShiny) + { + F32 brightness = vars.hazeColor.brightness(); + F32 greyscale_sat = brightness * (1.0f - sky_saturation); + LLColor3 sky_color = vars.hazeColor * sky_saturation + smear(greyscale_sat); + //sky_color *= (0.5f + 0.5f * brightness); // SL-12574 EEP sky is being attenuated too much + return LLColor4(sky_color, 0.0f); + } + + LLColor3 sky_color = low_end ? vars.hazeColor * 2.0f : psky->gammaCorrect(vars.hazeColor * 2.0f, vars.gamma); + + return LLColor4(sky_color, 0.0f); +} + +// NOTE: Keep these in sync! +// indra\newview\app_settings\shaders\class1\deferred\skyV.glsl +// indra\newview\app_settings\shaders\class1\deferred\cloudsV.glsl +// indra\newview\lllegacyatmospherics.cpp +void LLAtmospherics::calcSkyColorWLVert(const LLSettingsSky::ptr_t &psky, LLVector3 & Pn, AtmosphericsVars& vars) +{ + const LLColor3 blue_density = vars.blue_density; + const LLColor3 blue_horizon = vars.blue_horizon; + const F32 haze_horizon = vars.haze_horizon; + const F32 haze_density = vars.haze_density; + const F32 density_multiplier = vars.density_multiplier; + + F32 max_y = vars.max_y; + LLVector4 sun_norm = vars.sun_norm; + + // project the direction ray onto the sky dome. + F32 phi = acos(Pn[1]); + F32 sinA = sin(F_PI - phi); + if (fabsf(sinA) < 0.01f) + { //avoid division by zero + sinA = 0.01f; + } + + F32 Plen = vars.dome_radius * sin(F_PI + phi + asin(vars.dome_offset * sinA)) / sinA; + + Pn *= Plen; + + // Set altitude + if (Pn[1] > 0.f) + { + Pn *= (max_y / Pn[1]); + } + else + { + Pn *= (-32000.f / Pn[1]); + } + + Plen = Pn.length(); + Pn /= Plen; + + // Initialize temp variables + LLColor3 sunlight = vars.sunlight; + LLColor3 ambient = vars.ambient; + + LLColor3 glow = vars.glow; + F32 cloud_shadow = vars.cloud_shadow; + + // Sunlight attenuation effect (hue and brightness) due to atmosphere + // this is used later for sunlight modulation at various altitudes + LLColor3 light_atten = vars.light_atten; + LLColor3 light_transmittance = psky->getLightTransmittanceFast(vars.total_density, vars.density_multiplier, Plen); + (void)light_transmittance; // silence Clang warn-error + + // Calculate relative weights + LLColor3 temp2(0.f, 0.f, 0.f); + LLColor3 temp1 = vars.total_density; + + LLColor3 blue_weight = componentDiv(blue_density, temp1); + LLColor3 blue_factor = blue_horizon * blue_weight; + LLColor3 haze_weight = componentDiv(smear(haze_density), temp1); + LLColor3 haze_factor = haze_horizon * haze_weight; + + + // Compute sunlight from P & lightnorm (for long rays like sky) + temp2.mV[1] = llmax(F_APPROXIMATELY_ZERO, llmax(0.f, Pn[1]) * 1.0f + sun_norm.mV[1] ); + + temp2.mV[1] = 1.f / temp2.mV[1]; + componentMultBy(sunlight, componentExp((light_atten * -1.f) * temp2.mV[1])); + componentMultBy(sunlight, light_transmittance); + + // Distance + temp2.mV[2] = Plen * density_multiplier; + + // Transparency (-> temp1) + temp1 = componentExp((temp1 * -1.f) * temp2.mV[2]); + + // Compute haze glow + temp2.mV[0] = Pn * LLVector3(sun_norm); + + temp2.mV[0] = 1.f - temp2.mV[0]; + // temp2.x is 0 at the sun and increases away from sun + temp2.mV[0] = llmax(temp2.mV[0], .001f); + // Set a minimum "angle" (smaller glow.y allows tighter, brighter hotspot) + + // Higher glow.x gives dimmer glow (because next step is 1 / "angle") + temp2.mV[0] *= glow.mV[0]; + + temp2.mV[0] = pow(temp2.mV[0], glow.mV[2]); + // glow.z should be negative, so we're doing a sort of (1 / "angle") function + + // Add "minimum anti-solar illumination" + temp2.mV[0] += .25f; + + + // Haze color above cloud + vars.hazeColor = (blue_factor * (sunlight + ambient) + componentMult(haze_factor, sunlight * temp2.mV[0] + ambient)); + + // Increase ambient when there are more clouds + LLColor3 tmpAmbient = ambient + (LLColor3::white - ambient) * cloud_shadow * 0.5f; + + // Dim sunlight by cloud shadow percentage + sunlight *= (1.f - cloud_shadow); + + // Haze color below cloud + vars.hazeColorBelowCloud = (blue_factor * (sunlight + tmpAmbient) + componentMult(haze_factor, sunlight * temp2.mV[0] + tmpAmbient)); + + // Final atmosphere additive + componentMultBy(vars.hazeColor, LLColor3::white - temp1); + +/* + // SL-12574 + + // Attenuate cloud color by atmosphere + temp1 = componentSqrt(temp1); //less atmos opacity (more transparency) below clouds + + // At horizon, blend high altitude sky color towards the darker color below the clouds + vars.hazeColor += componentMult(vars.hazeColorBelowCloud - vars.hazeColor, LLColor3::white - componentSqrt(temp1)); +*/ +} + +void LLAtmospherics::updateFog(const F32 distance, const LLVector3& tosun_in) +{ + LLVector3 tosun = tosun_in; + + if (!gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_FOG)) + { + return; + } + + LLColor4 target_fog(0.f, 0.2f, 0.5f, 0.f); + + const F32 water_height = gAgent.getRegion() ? gAgent.getRegion()->getWaterHeight() : 0.f; + // LLWorld::getInstance()->getWaterHeight(); + F32 camera_height = gAgentCamera.getCameraPositionAgent().mV[2]; + + F32 near_clip_height = LLViewerCamera::getInstance()->getAtAxis().mV[VZ] * LLViewerCamera::getInstance()->getNear(); + camera_height += near_clip_height; + + F32 fog_distance = 0.f; + LLColor3 res_color[3]; + + LLColor3 sky_fog_color = LLColor3::white; + LLColor3 render_fog_color = LLColor3::white; + + const F32 tosun_z = tosun.mV[VZ]; + tosun.mV[VZ] = 0.f; + tosun.normalize(); + LLVector3 perp_tosun; + perp_tosun.mV[VX] = -tosun.mV[VY]; + perp_tosun.mV[VY] = tosun.mV[VX]; + LLVector3 tosun_45 = tosun + perp_tosun; + tosun_45.normalize(); + + F32 delta = 0.06f; + tosun.mV[VZ] = delta; + perp_tosun.mV[VZ] = delta; + tosun_45.mV[VZ] = delta; + tosun.normalize(); + perp_tosun.normalize(); + tosun_45.normalize(); + + // Sky colors, just slightly above the horizon in the direction of the sun, perpendicular to the sun, and at a 45 degree angle to the sun. + AtmosphericsVars vars; + + LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); + + // NOTE: This is very similar to LLVOSky::cacheEnvironment() + // Differences: + // vars.sun_norm + // vars.sunlight + // invariants across whole sky tex process... + vars.blue_density = psky->getBlueDensity(); + vars.blue_horizon = psky->getBlueHorizon(); + vars.haze_density = psky->getHazeDensity(); + vars.haze_horizon = psky->getHazeHorizon(); + vars.density_multiplier = psky->getDensityMultiplier(); + vars.distance_multiplier = psky->getDistanceMultiplier(); + vars.max_y = psky->getMaxY(); + vars.sun_norm = LLEnvironment::instance().getSunDirectionCFR(); + vars.sunlight = psky->getSunlightColor(); + vars.ambient = psky->getAmbientColor(); + vars.glow = psky->getGlow(); + vars.cloud_shadow = psky->getCloudShadow(); + vars.dome_radius = psky->getDomeRadius(); + vars.dome_offset = psky->getDomeOffset(); + vars.light_atten = psky->getLightAttenuation(vars.max_y); + vars.light_transmittance = psky->getLightTransmittance(vars.max_y); + vars.total_density = psky->getTotalDensity(); + vars.gamma = psky->getGamma(); + + res_color[0] = calcSkyColorInDir(psky, vars, tosun); + res_color[1] = calcSkyColorInDir(psky, vars, perp_tosun); + res_color[2] = calcSkyColorInDir(psky, vars, tosun_45); + + sky_fog_color = color_norm(res_color[0] + res_color[1] + res_color[2]); + + F32 full_off = -0.25f; + F32 full_on = 0.00f; + F32 on = (tosun_z - full_off) / (full_on - full_off); + on = llclamp(on, 0.01f, 1.f); + sky_fog_color *= 0.5f * on; + + + // We need to clamp these to non-zero, in order for the gamma correction to work. 0^y = ??? + S32 i; + for (i = 0; i < 3; i++) + { + sky_fog_color.mV[i] = llmax(0.0001f, sky_fog_color.mV[i]); + } + + color_gamma_correct(sky_fog_color); + + render_fog_color = sky_fog_color; + + fog_distance = mFogRatio * distance; + + if (camera_height > water_height) + { + LLColor4 fog(render_fog_color); + mGLFogCol = fog; + } + else + { + LLSettingsWater::ptr_t pwater = LLEnvironment::instance().getCurrentWater(); + F32 depth = water_height - camera_height; + LLColor4 water_fog_color(pwater->getWaterFogColor()); + + // adjust the color based on depth. We're doing linear approximations + float depth_scale = gSavedSettings.getF32("WaterGLFogDepthScale"); + float depth_modifier = 1.0f - llmin(llmax(depth / depth_scale, 0.01f), + gSavedSettings.getF32("WaterGLFogDepthFloor")); + + LLColor4 fogCol = water_fog_color * depth_modifier; + fogCol.setAlpha(1); + + // set the gl fog color + mGLFogCol = fogCol; + } + + mFogColor = sky_fog_color; + mFogColor.setAlpha(1); + + LLDrawPoolWater::sWaterFogEnd = fog_distance*2.2f; + + stop_glerror(); +} + +// Functions used a lot. +F32 color_norm_pow(LLColor3& col, F32 e, bool postmultiply) +{ + F32 mv = color_max(col); + if (0 == mv) + { + return 0; + } + + col *= 1.f / mv; + color_pow(col, e); + if (postmultiply) + { + col *= mv; + } + return mv; +} + +// Returns angle (RADIANs) between the horizontal projection of "v" and the x_axis. +// Range of output is 0.0f to 2pi //359.99999...f +// Returns 0.0f when "v" = +/- z_axis. +F32 azimuth(const LLVector3 &v) +{ + F32 azimuth = 0.0f; + if (v.mV[VX] == 0.0f) + { + if (v.mV[VY] > 0.0f) + { + azimuth = F_PI * 0.5f; + } + else if (v.mV[VY] < 0.0f) + { + azimuth = F_PI * 1.5f;// 270.f; + } + } + else + { + azimuth = (F32) atan(v.mV[VY] / v.mV[VX]); + if (v.mV[VX] < 0.0f) + { + azimuth += F_PI; + } + else if (v.mV[VY] < 0.0f) + { + azimuth += F_PI * 2; + } + } + return azimuth; +} + +bool operator==(const AtmosphericsVars& a, const AtmosphericsVars& b) +{ + if (a.hazeColor != b.hazeColor) + { + return false; + } + + if (a.hazeColorBelowCloud != b.hazeColorBelowCloud) + { + return false; + } + + if (a.cloudColorSun != b.cloudColorSun) + { + return false; + } + + if (a.cloudColorAmbient != b.cloudColorAmbient) + { + return false; + } + + if (a.cloudDensity != b.cloudDensity) + { + return false; + } + + if (a.density_multiplier != b.density_multiplier) + { + return false; + } + + if (a.haze_horizon != b.haze_horizon) + { + return false; + } + + if (a.haze_density != b.haze_density) + { + return false; + } + + if (a.blue_horizon != b.blue_horizon) + { + return false; + } + + if (a.blue_density != b.blue_density) + { + return false; + } + + if (a.dome_offset != b.dome_offset) + { + return false; + } + + if (a.dome_radius != b.dome_radius) + { + return false; + } + + if (a.cloud_shadow != b.cloud_shadow) + { + return false; + } + + if (a.glow != b.glow) + { + return false; + } + + if (a.ambient != b.ambient) + { + return false; + } + + if (a.sunlight != b.sunlight) + { + return false; + } + + if (a.sun_norm != b.sun_norm) + { + return false; + } + + if (a.gamma != b.gamma) + { + return false; + } + + if (a.max_y != b.max_y) + { + return false; + } + + if (a.distance_multiplier != b.distance_multiplier) + { + return false; + } + + // light_atten, light_transmittance, total_density + // are ignored as they always change when the values above do + // they're just shared calc across the sky map generation to save cycles + + return true; +} + +bool approximatelyEqual(const F32 &a, const F32 &b, const F32 &fraction_treshold) +{ + F32 diff = fabs(a - b); + if (diff < F_APPROXIMATELY_ZERO || diff < llmax(fabs(a), fabs(b)) * fraction_treshold) + { + return true; + } + return false; +} + +bool approximatelyEqual(const LLColor3 &a, const LLColor3 &b, const F32 &fraction_treshold) +{ + return approximatelyEqual(a.mV[0], b.mV[0], fraction_treshold) + && approximatelyEqual(a.mV[1], b.mV[1], fraction_treshold) + && approximatelyEqual(a.mV[2], b.mV[2], fraction_treshold); +} + +bool approximatelyEqual(const LLVector4 &a, const LLVector4 &b, const F32 &fraction_treshold) +{ + return approximatelyEqual(a.mV[0], b.mV[0], fraction_treshold) + && approximatelyEqual(a.mV[1], b.mV[1], fraction_treshold) + && approximatelyEqual(a.mV[2], b.mV[2], fraction_treshold) + && approximatelyEqual(a.mV[3], b.mV[3], fraction_treshold); +} + +bool approximatelyEqual(const AtmosphericsVars& a, const AtmosphericsVars& b, const F32 fraction_treshold) +{ + if (!approximatelyEqual(a.hazeColor, b.hazeColor, fraction_treshold)) + { + return false; + } + + if (!approximatelyEqual(a.hazeColorBelowCloud, b.hazeColorBelowCloud, fraction_treshold)) + { + return false; + } + + if (!approximatelyEqual(a.cloudColorSun, b.cloudColorSun, fraction_treshold)) + { + return false; + } + + if (!approximatelyEqual(a.cloudColorAmbient, b.cloudColorAmbient, fraction_treshold)) + { + return false; + } + + if (!approximatelyEqual(a.cloudDensity, b.cloudDensity, fraction_treshold)) + { + return false; + } + + if (!approximatelyEqual(a.density_multiplier, b.density_multiplier, fraction_treshold)) + { + return false; + } + + if (!approximatelyEqual(a.haze_horizon, b.haze_horizon, fraction_treshold)) + { + return false; + } + + if (!approximatelyEqual(a.haze_density, b.haze_density, fraction_treshold)) + { + return false; + } + + if (!approximatelyEqual(a.blue_horizon, b.blue_horizon, fraction_treshold)) + { + return false; + } + + if (!approximatelyEqual(a.blue_density, b.blue_density, fraction_treshold)) + { + return false; + } + + if (!approximatelyEqual(a.dome_offset, b.dome_offset, fraction_treshold)) + { + return false; + } + + if (!approximatelyEqual(a.dome_radius, b.dome_radius, fraction_treshold)) + { + return false; + } + + if (!approximatelyEqual(a.cloud_shadow, b.cloud_shadow, fraction_treshold)) + { + return false; + } + + if (!approximatelyEqual(a.glow, b.glow, fraction_treshold)) + { + return false; + } + + if (!approximatelyEqual(a.ambient, b.ambient, fraction_treshold)) + { + return false; + } + + if (!approximatelyEqual(a.sunlight, b.sunlight, fraction_treshold)) + { + return false; + } + + if (!approximatelyEqual(a.sun_norm, b.sun_norm, fraction_treshold)) + { + return false; + } + + if (!approximatelyEqual(a.gamma, b.gamma, fraction_treshold)) + { + return false; + } + + if (!approximatelyEqual(a.max_y, b.max_y, fraction_treshold)) + { + return false; + } + + if (!approximatelyEqual(a.distance_multiplier, b.distance_multiplier, fraction_treshold)) + { + return false; + } + + // light_atten, light_transmittance, total_density + // are ignored as they always change when the values above do + // they're just shared calc across the sky map generation to save cycles + + return true; +} + diff --git a/indra/newview/lllegacyatmospherics.h b/indra/newview/lllegacyatmospherics.h index d22ede3d46..17a01c1e04 100644 --- a/indra/newview/lllegacyatmospherics.h +++ b/indra/newview/lllegacyatmospherics.h @@ -1,282 +1,282 @@ -/** - * @file lllegacyatmospherics.h - * @brief LLVOSky class header file - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLLEGACYATMOSPHERICS_H -#define LL_LLLEGACYATMOSPHERICS_H - -#include "stdtypes.h" -#include "v3color.h" -#include "v4coloru.h" -#include "llviewertexture.h" -#include "llviewerobject.h" -#include "llframetimer.h" -#include "v3colorutil.h" -#include "llsettingssky.h" - -////////////////////////////////// -// -// Lots of constants -// -// Will clean these up at some point... -// - -const F32 HORIZON_DIST = 1024.0f; -const F32 ATM_EXP_FALLOFF = 0.000126f; -const F32 ATM_SEA_LEVEL_NDENS = 2.55e25f; -const F32 ATM_HEIGHT = 100000.f; - -// constants used in calculation of scattering coeff of clear air -const F32 sigma = 0.035f; -const F32 fsigma = (6.f + 3.f * sigma) / (6.f-7.f*sigma); -const F64 Ndens = 2.55e25; -const F64 Ndens2 = Ndens*Ndens; - -class LLFace; -class LLHaze; - -LL_FORCE_INLINE LLColor3 refr_ind_calc(const LLColor3 &wave_length) -{ - LLColor3 refr_ind; - for (S32 i = 0; i < 3; ++i) - { - const F32 wl2 = wave_length.mV[i] * wave_length.mV[i] * 1e-6f; - refr_ind.mV[i] = 6.43e3f + ( 2.95e6f / ( 146.0f - 1.f/wl2 ) ) + ( 2.55e4f / ( 41.0f - 1.f/wl2 ) ); - refr_ind.mV[i] *= 1.0e-8f; - refr_ind.mV[i] += 1.f; - } - return refr_ind; -} - - -class LLHaze -{ -public: - LLHaze() : mG(0), mFalloff(1), mAbsCoef(0.f) {mSigSca.setToBlack();} - LLHaze(const F32 g, const LLColor3& sca, const F32 fo = 2.f) : - mG(g), mSigSca(0.25f/F_PI * sca), mFalloff(fo), mAbsCoef(0.f) - { - mAbsCoef = color_intens(mSigSca) / sAirScaIntense; - } - - LLHaze(const F32 g, const F32 sca, const F32 fo = 2.f) : mG(g), - mSigSca(0.25f/F_PI * LLColor3(sca, sca, sca)), mFalloff(fo) - { - mAbsCoef = 0.01f * sca / sAirScaAvg; - } - -/* Proportion of light that is scattered into 'path' from 'in' over distance dt. */ -/* assumes that vectors 'path' and 'in' are normalized. Scattering coef / 2pi */ - - LL_FORCE_INLINE LLColor3 calcAirSca(const F32 h) - { - return calcFalloff(h) * sAirScaSeaLevel; - } - - LL_FORCE_INLINE void calcAirSca(const F32 h, LLColor3 &result) - { - result = sAirScaSeaLevel; - result *= calcFalloff(h); - } - - F32 getG() const { return mG; } - - void setG(const F32 g) - { - mG = g; - } - - const LLColor3& getSigSca() const // sea level - { - return mSigSca; - } - - void setSigSca(const LLColor3& s) - { - mSigSca = s; - mAbsCoef = 0.01f * color_intens(mSigSca) / sAirScaIntense; - } - - void setSigSca(const F32 s0, const F32 s1, const F32 s2) - { - mSigSca = sAirScaAvg * LLColor3 (s0, s1, s2); - mAbsCoef = 0.01f * (s0 + s1 + s2) / 3; - } - - F32 getFalloff() const - { - return mFalloff; - } - - void setFalloff(const F32 fo) - { - mFalloff = fo; - } - - F32 getAbsCoef() const - { - return mAbsCoef; - } - - inline static F32 calcFalloff(const F32 h) - { - return (h <= 0) ? 1.0f : (F32)LL_FAST_EXP(-ATM_EXP_FALLOFF * h); - } - - inline LLColor3 calcSigSca(const F32 h) const - { - return calcFalloff(h * mFalloff) * mSigSca; - } - - inline void calcSigSca(const F32 h, LLColor3 &result) const - { - result = mSigSca; - result *= calcFalloff(h * mFalloff); - } - - LLColor3 calcSigExt(const F32 h) const - { - return calcFalloff(h * mFalloff) * (1 + mAbsCoef) * mSigSca; - } - - F32 calcPhase(const F32 cos_theta) const; - -private: - static LLColor3 const sAirScaSeaLevel; - static F32 const sAirScaIntense; - static F32 const sAirScaAvg; - -protected: - F32 mG; - LLColor3 mSigSca; - F32 mFalloff; // 1 - slow, >1 - faster - F32 mAbsCoef; -}; - - -class LLCubeMap; - -class AtmosphericsVars -{ -public: - AtmosphericsVars() - : hazeColor(0,0,0) - , hazeColorBelowCloud(0,0,0) - , cloudColorSun(0,0,0) - , cloudColorAmbient(0,0,0) - , cloudDensity(0.0f) - , blue_density() - , blue_horizon() - , haze_density(0.0f) - , haze_horizon(0.0f) - , density_multiplier(0.0f) - , max_y(0.0f) - , gamma(1.0f) - , sun_norm(0.0f, 1.0f, 0.0f, 1.0f) - , sunlight() - , ambient() - , glow() - , cloud_shadow(1.0f) - , dome_radius(1.0f) - , dome_offset(1.0f) - , light_atten() - , light_transmittance() - { - } - - friend bool operator==(const AtmosphericsVars& a, const AtmosphericsVars& b); - // returns true if values are within treshold of each other. - friend bool approximatelyEqual(const AtmosphericsVars& a, const AtmosphericsVars& b, const F32 fraction_treshold); - - LLColor3 hazeColor; - LLColor3 hazeColorBelowCloud; - LLColor3 cloudColorSun; - LLColor3 cloudColorAmbient; - F32 cloudDensity; - LLColor3 blue_density; - LLColor3 blue_horizon; - F32 haze_density; - F32 haze_horizon; - F32 density_multiplier; - F32 distance_multiplier; - F32 max_y; - F32 gamma; - LLVector4 sun_norm; - LLColor3 sunlight; - LLColor3 ambient; - LLColor3 glow; - F32 cloud_shadow; - F32 dome_radius; - F32 dome_offset; - LLColor3 light_atten; - LLColor3 light_transmittance; - LLColor3 total_density; -}; - -class LLAtmospherics -{ -public: - LLAtmospherics(); - ~LLAtmospherics(); - - void init(); - void updateFog(const F32 distance, const LLVector3& tosun); - - const LLHaze& getHaze() const { return mHaze; } - LLHaze& getHaze() { return mHaze; } - F32 getHazeConcentration() const { return mHazeConcentration; } - void setHaze(const LLHaze& h) { mHaze = h; } - void setFogRatio(const F32 fog_ratio) { mFogRatio = fog_ratio; } - - F32 getFogRatio() const { return mFogRatio; } - LLColor4 getFogColor() const { return mFogColor; } - LLColor4 getGLFogColor() const { return mGLFogCol; } - - void setCloudDensity(F32 cloud_density) { mCloudDensity = cloud_density; } - void setWind ( const LLVector3& wind ) { mWind = wind.length(); } - - LLColor4 calcSkyColorInDir(const LLSettingsSky::ptr_t &psky, AtmosphericsVars& vars, const LLVector3& dir, bool isShiny = false, const bool low_end = false); - -protected: - - void calcSkyColorWLVert(const LLSettingsSky::ptr_t &psky, LLVector3 & Pn, AtmosphericsVars& vars); - - LLHaze mHaze; - F32 mHazeConcentration; - F32 mCloudDensity; - F32 mWind; - bool mInitialized; - LLVector3 mLastLightingDirection; - LLColor3 mLastTotalAmbient; - F32 mAmbientScale; - LLColor3 mNightColorShift; - F32 mInterpVal; - LLColor4 mFogColor; - LLColor4 mGLFogCol; - F32 mFogRatio; - F32 mWorldScale; -}; - -#endif +/** + * @file lllegacyatmospherics.h + * @brief LLVOSky class header file + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLLEGACYATMOSPHERICS_H +#define LL_LLLEGACYATMOSPHERICS_H + +#include "stdtypes.h" +#include "v3color.h" +#include "v4coloru.h" +#include "llviewertexture.h" +#include "llviewerobject.h" +#include "llframetimer.h" +#include "v3colorutil.h" +#include "llsettingssky.h" + +////////////////////////////////// +// +// Lots of constants +// +// Will clean these up at some point... +// + +const F32 HORIZON_DIST = 1024.0f; +const F32 ATM_EXP_FALLOFF = 0.000126f; +const F32 ATM_SEA_LEVEL_NDENS = 2.55e25f; +const F32 ATM_HEIGHT = 100000.f; + +// constants used in calculation of scattering coeff of clear air +const F32 sigma = 0.035f; +const F32 fsigma = (6.f + 3.f * sigma) / (6.f-7.f*sigma); +const F64 Ndens = 2.55e25; +const F64 Ndens2 = Ndens*Ndens; + +class LLFace; +class LLHaze; + +LL_FORCE_INLINE LLColor3 refr_ind_calc(const LLColor3 &wave_length) +{ + LLColor3 refr_ind; + for (S32 i = 0; i < 3; ++i) + { + const F32 wl2 = wave_length.mV[i] * wave_length.mV[i] * 1e-6f; + refr_ind.mV[i] = 6.43e3f + ( 2.95e6f / ( 146.0f - 1.f/wl2 ) ) + ( 2.55e4f / ( 41.0f - 1.f/wl2 ) ); + refr_ind.mV[i] *= 1.0e-8f; + refr_ind.mV[i] += 1.f; + } + return refr_ind; +} + + +class LLHaze +{ +public: + LLHaze() : mG(0), mFalloff(1), mAbsCoef(0.f) {mSigSca.setToBlack();} + LLHaze(const F32 g, const LLColor3& sca, const F32 fo = 2.f) : + mG(g), mSigSca(0.25f/F_PI * sca), mFalloff(fo), mAbsCoef(0.f) + { + mAbsCoef = color_intens(mSigSca) / sAirScaIntense; + } + + LLHaze(const F32 g, const F32 sca, const F32 fo = 2.f) : mG(g), + mSigSca(0.25f/F_PI * LLColor3(sca, sca, sca)), mFalloff(fo) + { + mAbsCoef = 0.01f * sca / sAirScaAvg; + } + +/* Proportion of light that is scattered into 'path' from 'in' over distance dt. */ +/* assumes that vectors 'path' and 'in' are normalized. Scattering coef / 2pi */ + + LL_FORCE_INLINE LLColor3 calcAirSca(const F32 h) + { + return calcFalloff(h) * sAirScaSeaLevel; + } + + LL_FORCE_INLINE void calcAirSca(const F32 h, LLColor3 &result) + { + result = sAirScaSeaLevel; + result *= calcFalloff(h); + } + + F32 getG() const { return mG; } + + void setG(const F32 g) + { + mG = g; + } + + const LLColor3& getSigSca() const // sea level + { + return mSigSca; + } + + void setSigSca(const LLColor3& s) + { + mSigSca = s; + mAbsCoef = 0.01f * color_intens(mSigSca) / sAirScaIntense; + } + + void setSigSca(const F32 s0, const F32 s1, const F32 s2) + { + mSigSca = sAirScaAvg * LLColor3 (s0, s1, s2); + mAbsCoef = 0.01f * (s0 + s1 + s2) / 3; + } + + F32 getFalloff() const + { + return mFalloff; + } + + void setFalloff(const F32 fo) + { + mFalloff = fo; + } + + F32 getAbsCoef() const + { + return mAbsCoef; + } + + inline static F32 calcFalloff(const F32 h) + { + return (h <= 0) ? 1.0f : (F32)LL_FAST_EXP(-ATM_EXP_FALLOFF * h); + } + + inline LLColor3 calcSigSca(const F32 h) const + { + return calcFalloff(h * mFalloff) * mSigSca; + } + + inline void calcSigSca(const F32 h, LLColor3 &result) const + { + result = mSigSca; + result *= calcFalloff(h * mFalloff); + } + + LLColor3 calcSigExt(const F32 h) const + { + return calcFalloff(h * mFalloff) * (1 + mAbsCoef) * mSigSca; + } + + F32 calcPhase(const F32 cos_theta) const; + +private: + static LLColor3 const sAirScaSeaLevel; + static F32 const sAirScaIntense; + static F32 const sAirScaAvg; + +protected: + F32 mG; + LLColor3 mSigSca; + F32 mFalloff; // 1 - slow, >1 - faster + F32 mAbsCoef; +}; + + +class LLCubeMap; + +class AtmosphericsVars +{ +public: + AtmosphericsVars() + : hazeColor(0,0,0) + , hazeColorBelowCloud(0,0,0) + , cloudColorSun(0,0,0) + , cloudColorAmbient(0,0,0) + , cloudDensity(0.0f) + , blue_density() + , blue_horizon() + , haze_density(0.0f) + , haze_horizon(0.0f) + , density_multiplier(0.0f) + , max_y(0.0f) + , gamma(1.0f) + , sun_norm(0.0f, 1.0f, 0.0f, 1.0f) + , sunlight() + , ambient() + , glow() + , cloud_shadow(1.0f) + , dome_radius(1.0f) + , dome_offset(1.0f) + , light_atten() + , light_transmittance() + { + } + + friend bool operator==(const AtmosphericsVars& a, const AtmosphericsVars& b); + // returns true if values are within treshold of each other. + friend bool approximatelyEqual(const AtmosphericsVars& a, const AtmosphericsVars& b, const F32 fraction_treshold); + + LLColor3 hazeColor; + LLColor3 hazeColorBelowCloud; + LLColor3 cloudColorSun; + LLColor3 cloudColorAmbient; + F32 cloudDensity; + LLColor3 blue_density; + LLColor3 blue_horizon; + F32 haze_density; + F32 haze_horizon; + F32 density_multiplier; + F32 distance_multiplier; + F32 max_y; + F32 gamma; + LLVector4 sun_norm; + LLColor3 sunlight; + LLColor3 ambient; + LLColor3 glow; + F32 cloud_shadow; + F32 dome_radius; + F32 dome_offset; + LLColor3 light_atten; + LLColor3 light_transmittance; + LLColor3 total_density; +}; + +class LLAtmospherics +{ +public: + LLAtmospherics(); + ~LLAtmospherics(); + + void init(); + void updateFog(const F32 distance, const LLVector3& tosun); + + const LLHaze& getHaze() const { return mHaze; } + LLHaze& getHaze() { return mHaze; } + F32 getHazeConcentration() const { return mHazeConcentration; } + void setHaze(const LLHaze& h) { mHaze = h; } + void setFogRatio(const F32 fog_ratio) { mFogRatio = fog_ratio; } + + F32 getFogRatio() const { return mFogRatio; } + LLColor4 getFogColor() const { return mFogColor; } + LLColor4 getGLFogColor() const { return mGLFogCol; } + + void setCloudDensity(F32 cloud_density) { mCloudDensity = cloud_density; } + void setWind ( const LLVector3& wind ) { mWind = wind.length(); } + + LLColor4 calcSkyColorInDir(const LLSettingsSky::ptr_t &psky, AtmosphericsVars& vars, const LLVector3& dir, bool isShiny = false, const bool low_end = false); + +protected: + + void calcSkyColorWLVert(const LLSettingsSky::ptr_t &psky, LLVector3 & Pn, AtmosphericsVars& vars); + + LLHaze mHaze; + F32 mHazeConcentration; + F32 mCloudDensity; + F32 mWind; + bool mInitialized; + LLVector3 mLastLightingDirection; + LLColor3 mLastTotalAmbient; + F32 mAmbientScale; + LLColor3 mNightColorShift; + F32 mInterpVal; + LLColor4 mFogColor; + LLColor4 mGLFogCol; + F32 mFogRatio; + F32 mWorldScale; +}; + +#endif diff --git a/indra/newview/lllocalbitmaps.cpp b/indra/newview/lllocalbitmaps.cpp index 5f097775d2..8aadf1f533 100644 --- a/indra/newview/lllocalbitmaps.cpp +++ b/indra/newview/lllocalbitmaps.cpp @@ -1,1274 +1,1274 @@ -/** - * @file lllocalbitmaps.cpp - * @author Vaalith Jinn - * @brief Local Bitmaps source - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/* precompiled headers */ -#include "llviewerprecompiledheaders.h" - -/* own header */ -#include "lllocalbitmaps.h" - -/* boost: will not compile unless equivalent is undef'd, beware. */ -#include "fix_macros.h" -#include - -/* image compression headers. */ -#include "llimagebmp.h" -#include "llimagetga.h" -#include "llimagejpeg.h" -#include "llimagepng.h" - -/* time headers */ -#include -#include - -/* misc headers */ -#include "llgltfmaterial.h" -#include "llscrolllistctrl.h" -#include "lllocaltextureobject.h" -#include "llviewertexturelist.h" -#include "llviewerobjectlist.h" -#include "llviewerobject.h" -#include "llface.h" -#include "llvoavatarself.h" -#include "llviewerwearable.h" -#include "llagentwearables.h" -#include "lltexlayerparams.h" -#include "llvovolume.h" -#include "llnotificationsutil.h" -#include "pipeline.h" -#include "llmaterialmgr.h" -#include "llimagedimensionsinfo.h" -#include "llinventoryicon.h" -#include "llviewercontrol.h" -#include "lltrans.h" -#include "llviewerdisplay.h" - -/*=======================================*/ -/* Formal declarations, constants, etc. */ -/*=======================================*/ - -static const F32 LL_LOCAL_TIMER_HEARTBEAT = 3.0; -static const bool LL_LOCAL_USE_MIPMAPS = true; -static const S32 LL_LOCAL_DISCARD_LEVEL = 0; -static const bool LL_LOCAL_SLAM_FOR_DEBUG = true; -static const bool LL_LOCAL_REPLACE_ON_DEL = true; -static const S32 LL_LOCAL_UPDATE_RETRIES = 5; - -/*=======================================*/ -/* LLLocalBitmap: unit class */ -/*=======================================*/ -LLLocalBitmap::LLLocalBitmap(std::string filename) - : mFilename(filename) - , mShortName(gDirUtilp->getBaseFileName(filename, true)) - , mValid(false) - , mLastModified() - , mLinkStatus(LS_ON) - , mUpdateRetries(LL_LOCAL_UPDATE_RETRIES) -{ - mTrackingID.generate(); - - /* extension */ - std::string temp_exten = gDirUtilp->getExtension(mFilename); - - if (temp_exten == "bmp") - { - mExtension = ET_IMG_BMP; - } - else if (temp_exten == "tga") - { - mExtension = ET_IMG_TGA; - } - else if (temp_exten == "jpg" || temp_exten == "jpeg") - { - mExtension = ET_IMG_JPG; - } - else if (temp_exten == "png") - { - mExtension = ET_IMG_PNG; - } - else - { - LL_WARNS() << "File of no valid extension given, local bitmap creation aborted." << "\n" - << "Filename: " << mFilename << LL_ENDL; - return; // no valid extension. - } - - /* next phase of unit creation is nearly the same as an update cycle. - we're running updateSelf as a special case with the optional UT_FIRSTUSE - which omits the parts associated with removing the outdated texture */ - mValid = updateSelf(UT_FIRSTUSE); -} - -LLLocalBitmap::~LLLocalBitmap() -{ - // replace IDs with defaults, if set to do so. - if(LL_LOCAL_REPLACE_ON_DEL && mValid && gAgentAvatarp) // fix for STORM-1837 - { - replaceIDs(mWorldID, IMG_DEFAULT); - LLLocalBitmapMgr::getInstance()->doRebake(); - } - - for (LLPointer &mat : mGLTFMaterialWithLocalTextures) - { - mat->removeLocalTextureTracking(getTrackingID()); - } - - mChangedSignal(getTrackingID(), getWorldID(), LLUUID()); - mChangedSignal.disconnect_all_slots(); - - // delete self from gimagelist - LLViewerFetchedTexture* image = gTextureList.findImage(mWorldID, TEX_LIST_STANDARD); - gTextureList.deleteImage(image); - - if (image) - { - image->unref(); - } -} - -/* accessors */ -std::string LLLocalBitmap::getFilename() const -{ - return mFilename; -} - -std::string LLLocalBitmap::getShortName() const -{ - return mShortName; -} - -LLUUID LLLocalBitmap::getTrackingID() const -{ - return mTrackingID; -} - -LLUUID LLLocalBitmap::getWorldID() const -{ - return mWorldID; -} - -bool LLLocalBitmap::getValid() const -{ - return mValid; -} - -/* update functions */ -bool LLLocalBitmap::updateSelf(EUpdateType optional_firstupdate) -{ - bool updated = false; - - if (mLinkStatus == LS_ON) - { - // verifying that the file exists - if (gDirUtilp->fileExists(mFilename)) - { - // verifying that the file has indeed been modified - -#ifndef LL_WINDOWS - const std::time_t temp_time = boost::filesystem::last_write_time(boost::filesystem::path(mFilename)); -#else - const std::time_t temp_time = boost::filesystem::last_write_time(boost::filesystem::path(utf8str_to_utf16str(mFilename))); -#endif - LLSD new_last_modified = asctime(localtime(&temp_time)); - - if (mLastModified.asString() != new_last_modified.asString()) - { - /* loading the image file and decoding it, here is a critical point which, - if fails, invalidates the whole update (or unit creation) process. */ - LLPointer raw_image = new LLImageRaw(); - if (decodeBitmap(raw_image)) - { - // decode is successful, we can safely proceed. - LLUUID old_id = LLUUID::null; - if ((optional_firstupdate != UT_FIRSTUSE) && !mWorldID.isNull()) - { - old_id = mWorldID; - } - mWorldID.generate(); - mLastModified = new_last_modified; - - LLPointer texture = new LLViewerFetchedTexture - ("file://"+mFilename, FTT_LOCAL_FILE, mWorldID, LL_LOCAL_USE_MIPMAPS); - - texture->createGLTexture(LL_LOCAL_DISCARD_LEVEL, raw_image); - texture->setCachedRawImage(LL_LOCAL_DISCARD_LEVEL, raw_image); - texture->ref(); - - gTextureList.addImage(texture, TEX_LIST_STANDARD); - - if (optional_firstupdate != UT_FIRSTUSE) - { - // seek out everything old_id uses and replace it with mWorldID - replaceIDs(old_id, mWorldID); - - // remove old_id from gimagelist - LLViewerFetchedTexture* image = gTextureList.findImage(old_id, TEX_LIST_STANDARD); - if (image != NULL) - { - gTextureList.deleteImage(image); - image->unref(); - } - } - - mUpdateRetries = LL_LOCAL_UPDATE_RETRIES; - updated = true; - } - - // if decoding failed, we get here and it will attempt to decode it in the next cycles - // until mUpdateRetries runs out. this is done because some software lock the bitmap while writing to it - else - { - if (mUpdateRetries) - { - mUpdateRetries--; - } - else - { - LL_WARNS() << "During the update process the following file was found" << "\n" - << "but could not be opened or decoded for " << LL_LOCAL_UPDATE_RETRIES << " attempts." << "\n" - << "Filename: " << mFilename << "\n" - << "Disabling further update attempts for this file." << LL_ENDL; - - LLSD notif_args; - notif_args["FNAME"] = mFilename; - notif_args["NRETRIES"] = LL_LOCAL_UPDATE_RETRIES; - LLNotificationsUtil::add("LocalBitmapsUpdateFailedFinal", notif_args); - - mLinkStatus = LS_BROKEN; - } - } - } - - } // end if file exists - - else - { - LL_WARNS() << "During the update process, the following file was not found." << "\n" - << "Filename: " << mFilename << "\n" - << "Disabling further update attempts for this file." << LL_ENDL; - - LLSD notif_args; - notif_args["FNAME"] = mFilename; - LLNotificationsUtil::add("LocalBitmapsUpdateFileNotFound", notif_args); - - mLinkStatus = LS_BROKEN; - } - } - - return updated; -} - -boost::signals2::connection LLLocalBitmap::setChangedCallback(const LLLocalTextureCallback& cb) -{ - return mChangedSignal.connect(cb); -} - -void LLLocalBitmap::addGLTFMaterial(LLGLTFMaterial* mat) -{ - if (!mat) - { - return; - } - - mat_list_t::iterator end = mGLTFMaterialWithLocalTextures.end(); - for (mat_list_t::iterator it = mGLTFMaterialWithLocalTextures.begin(); it != end;) - { - if (it->get() == mat) - { - return; - } - - if ((*it)->getNumRefs() == 1) - { - it = mGLTFMaterialWithLocalTextures.erase(it); - end = mGLTFMaterialWithLocalTextures.end(); - } - else - { - it++; - } - } - - mat->addLocalTextureTracking(getTrackingID(), getWorldID()); - mGLTFMaterialWithLocalTextures.push_back(mat); -} - -bool LLLocalBitmap::decodeBitmap(LLPointer rawimg) -{ - bool decode_successful = false; - - switch (mExtension) - { - case ET_IMG_BMP: - { - LLPointer bmp_image = new LLImageBMP; - if (bmp_image->load(mFilename) && bmp_image->decode(rawimg, 0.0f)) - { - rawimg->biasedScaleToPowerOfTwo(LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); - decode_successful = true; - } - break; - } - - case ET_IMG_TGA: - { - LLPointer tga_image = new LLImageTGA; - if ((tga_image->load(mFilename) && tga_image->decode(rawimg)) - && ((tga_image->getComponents() == 3) || (tga_image->getComponents() == 4))) - { - rawimg->biasedScaleToPowerOfTwo(LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); - decode_successful = true; - } - break; - } - - case ET_IMG_JPG: - { - LLPointer jpeg_image = new LLImageJPEG; - if (jpeg_image->load(mFilename) && jpeg_image->decode(rawimg, 0.0f)) - { - rawimg->biasedScaleToPowerOfTwo(LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); - decode_successful = true; - } - break; - } - - case ET_IMG_PNG: - { - LLPointer png_image = new LLImagePNG; - if (png_image->load(mFilename) && png_image->decode(rawimg, 0.0f)) - { - rawimg->biasedScaleToPowerOfTwo(LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); - decode_successful = true; - } - break; - } - - default: - { - // separating this into -several- LL_WARNS() calls because in the extremely unlikely case that this happens - // accessing mFilename and any other object properties might very well crash the viewer. - // getting here should be impossible, or there's been a pretty serious bug. - - LL_WARNS() << "During a decode attempt, the following local bitmap had no properly assigned extension." << LL_ENDL; - LL_WARNS() << "Filename: " << mFilename << LL_ENDL; - LL_WARNS() << "Disabling further update attempts for this file." << LL_ENDL; - mLinkStatus = LS_BROKEN; - } - } - - return decode_successful; -} - -void LLLocalBitmap::replaceIDs(const LLUUID& old_id, LLUUID new_id) -{ - // checking for misuse. - if (old_id == new_id) - { - LL_INFOS() << "An attempt was made to replace a texture with itself. (matching UUIDs)" << "\n" - << "Texture UUID: " << old_id.asString() << LL_ENDL; - return; - } - - mChangedSignal(getTrackingID(), old_id, new_id); - - // processing updates per channel; makes the process scalable. - // the only actual difference is in SetTE* call i.e. SetTETexture, SetTENormal, etc. - updateUserPrims(old_id, new_id, LLRender::DIFFUSE_MAP); - updateUserPrims(old_id, new_id, LLRender::NORMAL_MAP); - updateUserPrims(old_id, new_id, LLRender::SPECULAR_MAP); - - updateUserVolumes(old_id, new_id, LLRender::LIGHT_TEX); - updateUserVolumes(old_id, new_id, LLRender::SCULPT_TEX); // isn't there supposed to be an IMG_DEFAULT_SCULPT or something? - - // default safeguard image for layers - if( new_id == IMG_DEFAULT ) - { - new_id = IMG_DEFAULT_AVATAR; - } - - /* It doesn't actually update all of those, it merely checks if any of them - contain the referenced ID and if so, updates. */ - updateUserLayers(old_id, new_id, LLWearableType::WT_ALPHA); - updateUserLayers(old_id, new_id, LLWearableType::WT_EYES); - updateUserLayers(old_id, new_id, LLWearableType::WT_GLOVES); - updateUserLayers(old_id, new_id, LLWearableType::WT_JACKET); - updateUserLayers(old_id, new_id, LLWearableType::WT_PANTS); - updateUserLayers(old_id, new_id, LLWearableType::WT_SHIRT); - updateUserLayers(old_id, new_id, LLWearableType::WT_SHOES); - updateUserLayers(old_id, new_id, LLWearableType::WT_SKIN); - updateUserLayers(old_id, new_id, LLWearableType::WT_SKIRT); - updateUserLayers(old_id, new_id, LLWearableType::WT_SOCKS); - updateUserLayers(old_id, new_id, LLWearableType::WT_TATTOO); - updateUserLayers(old_id, new_id, LLWearableType::WT_UNIVERSAL); - updateUserLayers(old_id, new_id, LLWearableType::WT_UNDERPANTS); - updateUserLayers(old_id, new_id, LLWearableType::WT_UNDERSHIRT); - - updateGLTFMaterials(old_id, new_id); -} - -// this function sorts the faces from a getFaceList[getNumFaces] into a list of objects -// in order to prevent multiple sendTEUpdate calls per object during updateUserPrims -std::vector LLLocalBitmap::prepUpdateObjects(LLUUID old_id, U32 channel) -{ - std::vector obj_list; - LLViewerFetchedTexture* old_texture = gTextureList.findImage(old_id, TEX_LIST_STANDARD); - - for(U32 face_iterator = 0; face_iterator < old_texture->getNumFaces(channel); face_iterator++) - { - // getting an object from a face - LLFace* face_to_object = (*old_texture->getFaceList(channel))[face_iterator]; - - if(face_to_object) - { - LLViewerObject* affected_object = face_to_object->getViewerObject(); - - if(affected_object) - { - - // we have an object, we'll take it's UUID and compare it to - // whatever we already have in the returnable object list. - // if there is a match - we do not add (to prevent duplicates) - LLUUID mainlist_obj_id = affected_object->getID(); - bool add_object = true; - - // begin looking for duplicates - std::vector::iterator objlist_iter = obj_list.begin(); - for(; (objlist_iter != obj_list.end()) && add_object; objlist_iter++) - { - LLViewerObject* obj = *objlist_iter; - if (obj->getID() == mainlist_obj_id) - { - add_object = false; // duplicate found. - } - } - // end looking for duplicates - - if(add_object) - { - obj_list.push_back(affected_object); - } - - } - - } - - } // end of face-iterating for() - - return obj_list; -} - -void LLLocalBitmap::updateUserPrims(LLUUID old_id, LLUUID new_id, U32 channel) -{ - std::vector objectlist = prepUpdateObjects(old_id, channel); - - for(std::vector::iterator object_iterator = objectlist.begin(); - object_iterator != objectlist.end(); object_iterator++) - { - LLViewerObject* object = *object_iterator; - - if(object) - { - bool update_tex = false; - bool update_mat = false; - S32 num_faces = object->getNumFaces(); - - for (U8 face_iter = 0; face_iter < num_faces; face_iter++) - { - if (object->mDrawable) - { - LLFace* face = object->mDrawable->getFace(face_iter); - if (face && face->getTexture(channel) && face->getTexture(channel)->getID() == old_id) - { - // these things differ per channel, unless there already is a universal - // texture setting function to setTE that takes channel as a param? - // p.s.: switch for now, might become if - if an extra test is needed to verify before touching normalmap/specmap - switch(channel) - { - case LLRender::DIFFUSE_MAP: - { - object->setTETexture(face_iter, new_id); - update_tex = true; - break; - } - - case LLRender::NORMAL_MAP: - { - object->setTENormalMap(face_iter, new_id); - update_mat = true; - update_tex = true; - break; - } - - case LLRender::SPECULAR_MAP: - { - object->setTESpecularMap(face_iter, new_id); - update_mat = true; - update_tex = true; - break; - } - } - // end switch - - } - } - } - - if (update_tex) - { - object->sendTEUpdate(); - } - - if (update_mat) - { - object->mDrawable->getVOVolume()->faceMappingChanged(); - } - } - } -} - -void LLLocalBitmap::updateUserVolumes(LLUUID old_id, LLUUID new_id, U32 channel) -{ - LLViewerFetchedTexture* old_texture = gTextureList.findImage(old_id, TEX_LIST_STANDARD); - for (U32 volume_iter = 0; volume_iter < old_texture->getNumVolumes(channel); volume_iter++) - { - LLVOVolume* volobjp = (*old_texture->getVolumeList(channel))[volume_iter]; - switch (channel) - { - case LLRender::LIGHT_TEX: - { - if (volobjp->getLightTextureID() == old_id) - { - volobjp->setLightTextureID(new_id); - } - break; - } - case LLRender::SCULPT_TEX: - { - LLViewerObject* object = (LLViewerObject*)volobjp; - - if (object) - { - if (object->isSculpted() && object->getVolume() && - object->getVolume()->getParams().getSculptID() == old_id) - { - LLSculptParams* old_params = (LLSculptParams*)object->getParameterEntry(LLNetworkData::PARAMS_SCULPT); - LLSculptParams new_params(*old_params); - new_params.setSculptTexture(new_id, (*old_params).getSculptType()); - object->setParameterEntry(LLNetworkData::PARAMS_SCULPT, new_params, true); - } - } - } - } - } -} - -void LLLocalBitmap::updateUserLayers(LLUUID old_id, LLUUID new_id, LLWearableType::EType type) -{ - U32 count = gAgentWearables.getWearableCount(type); - for(U32 wearable_iter = 0; wearable_iter < count; wearable_iter++) - { - LLViewerWearable* wearable = gAgentWearables.getViewerWearable(type, wearable_iter); - if (wearable) - { - std::vector texture_list = wearable->getLocalTextureListSeq(); - for(std::vector::iterator texture_iter = texture_list.begin(); - texture_iter != texture_list.end(); texture_iter++) - { - LLLocalTextureObject* lto = *texture_iter; - - if (lto && lto->getID() == old_id) - { - U32 local_texlayer_index = 0; /* can't keep that as static const, gives errors, so i'm leaving this var here */ - LLAvatarAppearanceDefines::EBakedTextureIndex baked_texind = - lto->getTexLayer(local_texlayer_index)->getTexLayerSet()->getBakedTexIndex(); - - LLAvatarAppearanceDefines::ETextureIndex reg_texind = getTexIndex(type, baked_texind); - if (reg_texind != LLAvatarAppearanceDefines::TEX_NUM_INDICES) - { - U32 index; - if (gAgentWearables.getWearableIndex(wearable,index)) - { - gAgentAvatarp->setLocalTexture(reg_texind, gTextureList.getImage(new_id), false, index); - gAgentAvatarp->wearableUpdated(type); - /* telling the manager to rebake once update cycle is fully done */ - LLLocalBitmapMgr::getInstance()->setNeedsRebake(); - } - } - - } - } - } - } -} - -void LLLocalBitmap::updateGLTFMaterials(LLUUID old_id, LLUUID new_id) -{ - // Might be a better idea to hold this in LLGLTFMaterialList - mat_list_t::iterator end = mGLTFMaterialWithLocalTextures.end(); - for (mat_list_t::iterator it = mGLTFMaterialWithLocalTextures.begin(); it != end;) - { - if ((*it)->getNumRefs() == 1) - { - // render and override materials are often recreated, - // clean up any remains - it = mGLTFMaterialWithLocalTextures.erase(it); - end = mGLTFMaterialWithLocalTextures.end(); - } - else if ((*it)->replaceLocalTexture(mTrackingID, old_id, new_id)) - { - it++; - } - else - { - // Matching id not found, no longer in use - // material would clean itself, remove from the list - it = mGLTFMaterialWithLocalTextures.erase(it); - end = mGLTFMaterialWithLocalTextures.end(); - } - } - - // Render material consists of base and override materials, make sure replaceLocalTexture - // gets called for base and override before applyOverride - end = mGLTFMaterialWithLocalTextures.end(); - for (mat_list_t::iterator it = mGLTFMaterialWithLocalTextures.begin(); it != end;) - { - LLFetchedGLTFMaterial* fetched_mat = dynamic_cast((*it).get()); - if (fetched_mat) - { - for (LLTextureEntry* entry : fetched_mat->mTextureEntires) - { - // Normally a change in applied material id is supposed to - // drop overrides thus reset material, but local materials - // currently reuse their existing asset id, and purpose is - // to preview how material will work in-world, overrides - // included, so do an override to render update instead. - LLGLTFMaterial* override_mat = entry->getGLTFMaterialOverride(); - if (override_mat) - { - // do not create a new material, reuse existing pointer - LLFetchedGLTFMaterial* render_mat = (LLFetchedGLTFMaterial*)entry->getGLTFRenderMaterial(); - if (render_mat) - { - llassert(dynamic_cast(entry->getGLTFRenderMaterial()) != nullptr); - { - *render_mat = *fetched_mat; - } - render_mat->applyOverride(*override_mat); - } - } - } - } - ++it; - } -} - -LLAvatarAppearanceDefines::ETextureIndex LLLocalBitmap::getTexIndex( - LLWearableType::EType type, LLAvatarAppearanceDefines::EBakedTextureIndex baked_texind) -{ - LLAvatarAppearanceDefines::ETextureIndex result = LLAvatarAppearanceDefines::TEX_NUM_INDICES; // using as a default/fail return. - - switch(type) - { - case LLWearableType::WT_ALPHA: - { - switch(baked_texind) - { - case LLAvatarAppearanceDefines::BAKED_EYES: - { - result = LLAvatarAppearanceDefines::TEX_EYES_ALPHA; - break; - } - - case LLAvatarAppearanceDefines::BAKED_HAIR: - { - result = LLAvatarAppearanceDefines::TEX_HAIR_ALPHA; - break; - } - - case LLAvatarAppearanceDefines::BAKED_HEAD: - { - result = LLAvatarAppearanceDefines::TEX_HEAD_ALPHA; - break; - } - - case LLAvatarAppearanceDefines::BAKED_LOWER: - { - result = LLAvatarAppearanceDefines::TEX_LOWER_ALPHA; - break; - } - case LLAvatarAppearanceDefines::BAKED_UPPER: - { - result = LLAvatarAppearanceDefines::TEX_UPPER_ALPHA; - break; - } - - default: - { - break; - } - - } - break; - - } - - case LLWearableType::WT_EYES: - { - if (baked_texind == LLAvatarAppearanceDefines::BAKED_EYES) - { - result = LLAvatarAppearanceDefines::TEX_EYES_IRIS; - } - - break; - } - - case LLWearableType::WT_GLOVES: - { - if (baked_texind == LLAvatarAppearanceDefines::BAKED_UPPER) - { - result = LLAvatarAppearanceDefines::TEX_UPPER_GLOVES; - } - - break; - } - - case LLWearableType::WT_JACKET: - { - if (baked_texind == LLAvatarAppearanceDefines::BAKED_LOWER) - { - result = LLAvatarAppearanceDefines::TEX_LOWER_JACKET; - } - else if (baked_texind == LLAvatarAppearanceDefines::BAKED_UPPER) - { - result = LLAvatarAppearanceDefines::TEX_UPPER_JACKET; - } - - break; - } - - case LLWearableType::WT_PANTS: - { - if (baked_texind == LLAvatarAppearanceDefines::BAKED_LOWER) - { - result = LLAvatarAppearanceDefines::TEX_LOWER_PANTS; - } - - break; - } - - case LLWearableType::WT_SHIRT: - { - if (baked_texind == LLAvatarAppearanceDefines::BAKED_UPPER) - { - result = LLAvatarAppearanceDefines::TEX_UPPER_SHIRT; - } - - break; - } - - case LLWearableType::WT_SHOES: - { - if (baked_texind == LLAvatarAppearanceDefines::BAKED_LOWER) - { - result = LLAvatarAppearanceDefines::TEX_LOWER_SHOES; - } - - break; - } - - case LLWearableType::WT_SKIN: - { - switch(baked_texind) - { - case LLAvatarAppearanceDefines::BAKED_HEAD: - { - result = LLAvatarAppearanceDefines::TEX_HEAD_BODYPAINT; - break; - } - - case LLAvatarAppearanceDefines::BAKED_LOWER: - { - result = LLAvatarAppearanceDefines::TEX_LOWER_BODYPAINT; - break; - } - case LLAvatarAppearanceDefines::BAKED_UPPER: - { - result = LLAvatarAppearanceDefines::TEX_UPPER_BODYPAINT; - break; - } - - default: - { - break; - } - - } - break; - } - - case LLWearableType::WT_SKIRT: - { - if (baked_texind == LLAvatarAppearanceDefines::BAKED_SKIRT) - { - result = LLAvatarAppearanceDefines::TEX_SKIRT; - } - - break; - } - - case LLWearableType::WT_SOCKS: - { - if (baked_texind == LLAvatarAppearanceDefines::BAKED_LOWER) - { - result = LLAvatarAppearanceDefines::TEX_LOWER_SOCKS; - } - - break; - } - - case LLWearableType::WT_TATTOO: - { - switch (baked_texind) - { - case LLAvatarAppearanceDefines::BAKED_HEAD: - { - result = LLAvatarAppearanceDefines::TEX_HEAD_TATTOO; - break; - } - - case LLAvatarAppearanceDefines::BAKED_LOWER: - { - result = LLAvatarAppearanceDefines::TEX_LOWER_TATTOO; - break; - } - case LLAvatarAppearanceDefines::BAKED_UPPER: - { - result = LLAvatarAppearanceDefines::TEX_UPPER_TATTOO; - break; - } - default: - { - break; - } - } - break; - - } - case LLWearableType::WT_UNIVERSAL: - { - switch (baked_texind) - { - - case LLAvatarAppearanceDefines::BAKED_SKIRT: - { - result = LLAvatarAppearanceDefines::TEX_SKIRT_TATTOO; - break; - } - case LLAvatarAppearanceDefines::BAKED_EYES: - { - result = LLAvatarAppearanceDefines::TEX_EYES_TATTOO; - break; - } - case LLAvatarAppearanceDefines::BAKED_HAIR: - { - result = LLAvatarAppearanceDefines::TEX_HAIR_TATTOO; - break; - } - case LLAvatarAppearanceDefines::BAKED_LEFT_ARM: - { - result = LLAvatarAppearanceDefines::TEX_LEFT_ARM_TATTOO; - break; - } - case LLAvatarAppearanceDefines::BAKED_LEFT_LEG: - { - result = LLAvatarAppearanceDefines::TEX_LEFT_LEG_TATTOO; - break; - } - case LLAvatarAppearanceDefines::BAKED_AUX1: - { - result = LLAvatarAppearanceDefines::TEX_AUX1_TATTOO; - break; - } - case LLAvatarAppearanceDefines::BAKED_AUX2: - { - result = LLAvatarAppearanceDefines::TEX_AUX2_TATTOO; - break; - } - case LLAvatarAppearanceDefines::BAKED_AUX3: - { - result = LLAvatarAppearanceDefines::TEX_AUX3_TATTOO; - break; - } - case LLAvatarAppearanceDefines::BAKED_UPPER: - { - result = LLAvatarAppearanceDefines::TEX_UPPER_UNIVERSAL_TATTOO; - break; - } - case LLAvatarAppearanceDefines::BAKED_LOWER: - { - result = LLAvatarAppearanceDefines::TEX_LOWER_UNIVERSAL_TATTOO; - break; - } - case LLAvatarAppearanceDefines::BAKED_HEAD: - { - result = LLAvatarAppearanceDefines::TEX_HEAD_UNIVERSAL_TATTOO; - break; - } - - - default: - { - break; - } - - } - break; - } - - case LLWearableType::WT_UNDERPANTS: - { - if (baked_texind == LLAvatarAppearanceDefines::BAKED_LOWER) - { - result = LLAvatarAppearanceDefines::TEX_LOWER_UNDERPANTS; - } - - break; - } - - case LLWearableType::WT_UNDERSHIRT: - { - if (baked_texind == LLAvatarAppearanceDefines::BAKED_UPPER) - { - result = LLAvatarAppearanceDefines::TEX_UPPER_UNDERSHIRT; - } - - break; - } - - default: - { - LL_WARNS() << "Unknown wearable type: " << (int)type << "\n" - << "Baked Texture Index: " << (int)baked_texind << "\n" - << "Filename: " << mFilename << "\n" - << "TrackingID: " << mTrackingID << "\n" - << "InworldID: " << mWorldID << LL_ENDL; - } - - } - return result; -} - -/*=======================================*/ -/* LLLocalBitmapTimer: timer class */ -/*=======================================*/ -LLLocalBitmapTimer::LLLocalBitmapTimer() : LLEventTimer(LL_LOCAL_TIMER_HEARTBEAT) -{ -} - -LLLocalBitmapTimer::~LLLocalBitmapTimer() -{ -} - -void LLLocalBitmapTimer::startTimer() -{ - mEventTimer.start(); -} - -void LLLocalBitmapTimer::stopTimer() -{ - mEventTimer.stop(); -} - -bool LLLocalBitmapTimer::isRunning() -{ - return mEventTimer.getStarted(); -} - -bool LLLocalBitmapTimer::tick() -{ - LLLocalBitmapMgr::getInstance()->doUpdates(); - return false; -} - -/*=======================================*/ -/* LLLocalBitmapMgr: manager class */ -/*=======================================*/ -LLLocalBitmapMgr::LLLocalBitmapMgr() -{ -} - -LLLocalBitmapMgr::~LLLocalBitmapMgr() -{ - std::for_each(mBitmapList.begin(), mBitmapList.end(), DeletePointer()); - mBitmapList.clear(); -} - -bool LLLocalBitmapMgr::addUnit(const std::vector& filenames) -{ - bool add_successful = false; - std::vector::const_iterator iter = filenames.begin(); - while (iter != filenames.end()) - { - if (!iter->empty() && addUnit(*iter).notNull()) - { - add_successful = true; - } - iter++; - } - return add_successful; -} - -LLUUID LLLocalBitmapMgr::addUnit(const std::string& filename) -{ - if (!checkTextureDimensions(filename)) - { - return LLUUID::null; - } - - LLLocalBitmap* unit = new LLLocalBitmap(filename); - - if (unit->getValid()) - { - mBitmapList.push_back(unit); - return unit->getTrackingID(); - } - else - { - LL_WARNS() << "Attempted to add invalid or unreadable image file, attempt cancelled.\n" - << "Filename: " << filename << LL_ENDL; - - LLSD notif_args; - notif_args["FNAME"] = filename; - LLNotificationsUtil::add("LocalBitmapsVerifyFail", notif_args); - - delete unit; - unit = NULL; - } - - return LLUUID::null; -} - -bool LLLocalBitmapMgr::checkTextureDimensions(std::string filename) -{ - std::string exten = gDirUtilp->getExtension(filename); - U32 codec = LLImageBase::getCodecFromExtension(exten); - std::string mImageLoadError; - LLImageDimensionsInfo image_info; - if (!image_info.load(filename,codec)) - { - LLSD args; - args["NAME"] = gDirUtilp->getBaseFileName(filename); - if (!image_info.getWarningName().empty()) - { - args["REASON"] = LLTrans::getString(image_info.getWarningName()); - } - LLNotificationsUtil::add("CannotUploadTexture", args); - return false; - } - - S32 max_width = gSavedSettings.getS32("max_texture_dimension_X"); - S32 max_height = gSavedSettings.getS32("max_texture_dimension_Y"); - - if ((image_info.getWidth() > max_width) || (image_info.getHeight() > max_height)) - { - LLStringUtil::format_map_t args; - args["WIDTH"] = llformat("%d", max_width); - args["HEIGHT"] = llformat("%d", max_height); - mImageLoadError = LLTrans::getString("texture_load_dimensions_error", args); - - LLSD notif_args; - notif_args["REASON"] = mImageLoadError; - notif_args["NAME"] = gDirUtilp->getBaseFileName(filename); - LLNotificationsUtil::add("CannotUploadTexture", notif_args); - - return false; - } - - return true; -} - -void LLLocalBitmapMgr::delUnit(LLUUID tracking_id) -{ - if (!mBitmapList.empty()) - { - std::vector to_delete; - for (local_list_iter iter = mBitmapList.begin(); iter != mBitmapList.end(); iter++) - { /* finding which ones we want deleted and making a separate list */ - LLLocalBitmap* unit = *iter; - if (unit->getTrackingID() == tracking_id) - { - to_delete.push_back(unit); - } - } - - for(std::vector::iterator del_iter = to_delete.begin(); - del_iter != to_delete.end(); del_iter++) - { /* iterating over a temporary list, hence preserving the iterator validity while deleting. */ - LLLocalBitmap* unit = *del_iter; - mBitmapList.remove(unit); - delete unit; - unit = NULL; - } - } -} - -LLUUID LLLocalBitmapMgr::getWorldID(const LLUUID &tracking_id) const -{ - LLUUID world_id = LLUUID::null; - - for (local_list_citer iter = mBitmapList.begin(); iter != mBitmapList.end(); iter++) - { - LLLocalBitmap* unit = *iter; - if (unit->getTrackingID() == tracking_id) - { - world_id = unit->getWorldID(); - } - } - - return world_id; -} - -bool LLLocalBitmapMgr::isLocal(const LLUUID &world_id) const -{ - for (local_list_citer iter = mBitmapList.begin(); iter != mBitmapList.end(); iter++) - { - LLLocalBitmap* unit = *iter; - if (unit->getWorldID() == world_id) - { - return true; - } - } - return false; -} - -std::string LLLocalBitmapMgr::getFilename(const LLUUID &tracking_id) const -{ - std::string filename = ""; - - for (local_list_citer iter = mBitmapList.begin(); iter != mBitmapList.end(); iter++) - { - LLLocalBitmap* unit = *iter; - if (unit->getTrackingID() == tracking_id) - { - filename = unit->getFilename(); - } - } - - return filename; -} - -boost::signals2::connection LLLocalBitmapMgr::setOnChangedCallback(const LLUUID tracking_id, const LLLocalBitmap::LLLocalTextureCallback &cb) -{ - for (local_list_iter iter = mBitmapList.begin(); iter != mBitmapList.end(); iter++) - { - LLLocalBitmap* unit = *iter; - if (unit->getTrackingID() == tracking_id) - { - return unit->setChangedCallback(cb); - } - } - - return boost::signals2::connection(); -} - -void LLLocalBitmapMgr::associateGLTFMaterial(const LLUUID tracking_id, LLGLTFMaterial* mat) -{ - for (local_list_iter iter = mBitmapList.begin(); iter != mBitmapList.end(); iter++) - { - LLLocalBitmap* unit = *iter; - if (unit->getTrackingID() == tracking_id) - { - unit->addGLTFMaterial(mat); - } - } -} - -void LLLocalBitmapMgr::feedScrollList(LLScrollListCtrl* ctrl) -{ - if (ctrl) - { - std::string icon_name = LLInventoryIcon::getIconName( - LLAssetType::AT_TEXTURE, - LLInventoryType::IT_NONE); - - if (!mBitmapList.empty()) - { - for (local_list_iter iter = mBitmapList.begin(); - iter != mBitmapList.end(); iter++) - { - LLSD element; - - element["columns"][0]["column"] = "icon"; - element["columns"][0]["type"] = "icon"; - element["columns"][0]["value"] = icon_name; - - element["columns"][1]["column"] = "unit_name"; - element["columns"][1]["type"] = "text"; - element["columns"][1]["value"] = (*iter)->getShortName(); - - LLSD data; - data["id"] = (*iter)->getTrackingID(); - data["type"] = (S32)LLAssetType::AT_TEXTURE; - element["value"] = data; - - ctrl->addElement(element); - } - } - } - -} - -void LLLocalBitmapMgr::doUpdates() -{ - // preventing theoretical overlap in cases with huge number of loaded images. - mTimer.stopTimer(); - mNeedsRebake = false; - - for (local_list_iter iter = mBitmapList.begin(); iter != mBitmapList.end(); iter++) - { - (*iter)->updateSelf(); - } - - doRebake(); - mTimer.startTimer(); -} - -void LLLocalBitmapMgr::setNeedsRebake() -{ - mNeedsRebake = true; -} - -void LLLocalBitmapMgr::doRebake() -{ /* separated that from doUpdates to insure a rebake can be called separately during deletion */ - if (mNeedsRebake) - { - gAgentAvatarp->forceBakeAllTextures(LL_LOCAL_SLAM_FOR_DEBUG); - mNeedsRebake = false; - } -} - +/** + * @file lllocalbitmaps.cpp + * @author Vaalith Jinn + * @brief Local Bitmaps source + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/* precompiled headers */ +#include "llviewerprecompiledheaders.h" + +/* own header */ +#include "lllocalbitmaps.h" + +/* boost: will not compile unless equivalent is undef'd, beware. */ +#include "fix_macros.h" +#include + +/* image compression headers. */ +#include "llimagebmp.h" +#include "llimagetga.h" +#include "llimagejpeg.h" +#include "llimagepng.h" + +/* time headers */ +#include +#include + +/* misc headers */ +#include "llgltfmaterial.h" +#include "llscrolllistctrl.h" +#include "lllocaltextureobject.h" +#include "llviewertexturelist.h" +#include "llviewerobjectlist.h" +#include "llviewerobject.h" +#include "llface.h" +#include "llvoavatarself.h" +#include "llviewerwearable.h" +#include "llagentwearables.h" +#include "lltexlayerparams.h" +#include "llvovolume.h" +#include "llnotificationsutil.h" +#include "pipeline.h" +#include "llmaterialmgr.h" +#include "llimagedimensionsinfo.h" +#include "llinventoryicon.h" +#include "llviewercontrol.h" +#include "lltrans.h" +#include "llviewerdisplay.h" + +/*=======================================*/ +/* Formal declarations, constants, etc. */ +/*=======================================*/ + +static const F32 LL_LOCAL_TIMER_HEARTBEAT = 3.0; +static const bool LL_LOCAL_USE_MIPMAPS = true; +static const S32 LL_LOCAL_DISCARD_LEVEL = 0; +static const bool LL_LOCAL_SLAM_FOR_DEBUG = true; +static const bool LL_LOCAL_REPLACE_ON_DEL = true; +static const S32 LL_LOCAL_UPDATE_RETRIES = 5; + +/*=======================================*/ +/* LLLocalBitmap: unit class */ +/*=======================================*/ +LLLocalBitmap::LLLocalBitmap(std::string filename) + : mFilename(filename) + , mShortName(gDirUtilp->getBaseFileName(filename, true)) + , mValid(false) + , mLastModified() + , mLinkStatus(LS_ON) + , mUpdateRetries(LL_LOCAL_UPDATE_RETRIES) +{ + mTrackingID.generate(); + + /* extension */ + std::string temp_exten = gDirUtilp->getExtension(mFilename); + + if (temp_exten == "bmp") + { + mExtension = ET_IMG_BMP; + } + else if (temp_exten == "tga") + { + mExtension = ET_IMG_TGA; + } + else if (temp_exten == "jpg" || temp_exten == "jpeg") + { + mExtension = ET_IMG_JPG; + } + else if (temp_exten == "png") + { + mExtension = ET_IMG_PNG; + } + else + { + LL_WARNS() << "File of no valid extension given, local bitmap creation aborted." << "\n" + << "Filename: " << mFilename << LL_ENDL; + return; // no valid extension. + } + + /* next phase of unit creation is nearly the same as an update cycle. + we're running updateSelf as a special case with the optional UT_FIRSTUSE + which omits the parts associated with removing the outdated texture */ + mValid = updateSelf(UT_FIRSTUSE); +} + +LLLocalBitmap::~LLLocalBitmap() +{ + // replace IDs with defaults, if set to do so. + if(LL_LOCAL_REPLACE_ON_DEL && mValid && gAgentAvatarp) // fix for STORM-1837 + { + replaceIDs(mWorldID, IMG_DEFAULT); + LLLocalBitmapMgr::getInstance()->doRebake(); + } + + for (LLPointer &mat : mGLTFMaterialWithLocalTextures) + { + mat->removeLocalTextureTracking(getTrackingID()); + } + + mChangedSignal(getTrackingID(), getWorldID(), LLUUID()); + mChangedSignal.disconnect_all_slots(); + + // delete self from gimagelist + LLViewerFetchedTexture* image = gTextureList.findImage(mWorldID, TEX_LIST_STANDARD); + gTextureList.deleteImage(image); + + if (image) + { + image->unref(); + } +} + +/* accessors */ +std::string LLLocalBitmap::getFilename() const +{ + return mFilename; +} + +std::string LLLocalBitmap::getShortName() const +{ + return mShortName; +} + +LLUUID LLLocalBitmap::getTrackingID() const +{ + return mTrackingID; +} + +LLUUID LLLocalBitmap::getWorldID() const +{ + return mWorldID; +} + +bool LLLocalBitmap::getValid() const +{ + return mValid; +} + +/* update functions */ +bool LLLocalBitmap::updateSelf(EUpdateType optional_firstupdate) +{ + bool updated = false; + + if (mLinkStatus == LS_ON) + { + // verifying that the file exists + if (gDirUtilp->fileExists(mFilename)) + { + // verifying that the file has indeed been modified + +#ifndef LL_WINDOWS + const std::time_t temp_time = boost::filesystem::last_write_time(boost::filesystem::path(mFilename)); +#else + const std::time_t temp_time = boost::filesystem::last_write_time(boost::filesystem::path(utf8str_to_utf16str(mFilename))); +#endif + LLSD new_last_modified = asctime(localtime(&temp_time)); + + if (mLastModified.asString() != new_last_modified.asString()) + { + /* loading the image file and decoding it, here is a critical point which, + if fails, invalidates the whole update (or unit creation) process. */ + LLPointer raw_image = new LLImageRaw(); + if (decodeBitmap(raw_image)) + { + // decode is successful, we can safely proceed. + LLUUID old_id = LLUUID::null; + if ((optional_firstupdate != UT_FIRSTUSE) && !mWorldID.isNull()) + { + old_id = mWorldID; + } + mWorldID.generate(); + mLastModified = new_last_modified; + + LLPointer texture = new LLViewerFetchedTexture + ("file://"+mFilename, FTT_LOCAL_FILE, mWorldID, LL_LOCAL_USE_MIPMAPS); + + texture->createGLTexture(LL_LOCAL_DISCARD_LEVEL, raw_image); + texture->setCachedRawImage(LL_LOCAL_DISCARD_LEVEL, raw_image); + texture->ref(); + + gTextureList.addImage(texture, TEX_LIST_STANDARD); + + if (optional_firstupdate != UT_FIRSTUSE) + { + // seek out everything old_id uses and replace it with mWorldID + replaceIDs(old_id, mWorldID); + + // remove old_id from gimagelist + LLViewerFetchedTexture* image = gTextureList.findImage(old_id, TEX_LIST_STANDARD); + if (image != NULL) + { + gTextureList.deleteImage(image); + image->unref(); + } + } + + mUpdateRetries = LL_LOCAL_UPDATE_RETRIES; + updated = true; + } + + // if decoding failed, we get here and it will attempt to decode it in the next cycles + // until mUpdateRetries runs out. this is done because some software lock the bitmap while writing to it + else + { + if (mUpdateRetries) + { + mUpdateRetries--; + } + else + { + LL_WARNS() << "During the update process the following file was found" << "\n" + << "but could not be opened or decoded for " << LL_LOCAL_UPDATE_RETRIES << " attempts." << "\n" + << "Filename: " << mFilename << "\n" + << "Disabling further update attempts for this file." << LL_ENDL; + + LLSD notif_args; + notif_args["FNAME"] = mFilename; + notif_args["NRETRIES"] = LL_LOCAL_UPDATE_RETRIES; + LLNotificationsUtil::add("LocalBitmapsUpdateFailedFinal", notif_args); + + mLinkStatus = LS_BROKEN; + } + } + } + + } // end if file exists + + else + { + LL_WARNS() << "During the update process, the following file was not found." << "\n" + << "Filename: " << mFilename << "\n" + << "Disabling further update attempts for this file." << LL_ENDL; + + LLSD notif_args; + notif_args["FNAME"] = mFilename; + LLNotificationsUtil::add("LocalBitmapsUpdateFileNotFound", notif_args); + + mLinkStatus = LS_BROKEN; + } + } + + return updated; +} + +boost::signals2::connection LLLocalBitmap::setChangedCallback(const LLLocalTextureCallback& cb) +{ + return mChangedSignal.connect(cb); +} + +void LLLocalBitmap::addGLTFMaterial(LLGLTFMaterial* mat) +{ + if (!mat) + { + return; + } + + mat_list_t::iterator end = mGLTFMaterialWithLocalTextures.end(); + for (mat_list_t::iterator it = mGLTFMaterialWithLocalTextures.begin(); it != end;) + { + if (it->get() == mat) + { + return; + } + + if ((*it)->getNumRefs() == 1) + { + it = mGLTFMaterialWithLocalTextures.erase(it); + end = mGLTFMaterialWithLocalTextures.end(); + } + else + { + it++; + } + } + + mat->addLocalTextureTracking(getTrackingID(), getWorldID()); + mGLTFMaterialWithLocalTextures.push_back(mat); +} + +bool LLLocalBitmap::decodeBitmap(LLPointer rawimg) +{ + bool decode_successful = false; + + switch (mExtension) + { + case ET_IMG_BMP: + { + LLPointer bmp_image = new LLImageBMP; + if (bmp_image->load(mFilename) && bmp_image->decode(rawimg, 0.0f)) + { + rawimg->biasedScaleToPowerOfTwo(LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); + decode_successful = true; + } + break; + } + + case ET_IMG_TGA: + { + LLPointer tga_image = new LLImageTGA; + if ((tga_image->load(mFilename) && tga_image->decode(rawimg)) + && ((tga_image->getComponents() == 3) || (tga_image->getComponents() == 4))) + { + rawimg->biasedScaleToPowerOfTwo(LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); + decode_successful = true; + } + break; + } + + case ET_IMG_JPG: + { + LLPointer jpeg_image = new LLImageJPEG; + if (jpeg_image->load(mFilename) && jpeg_image->decode(rawimg, 0.0f)) + { + rawimg->biasedScaleToPowerOfTwo(LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); + decode_successful = true; + } + break; + } + + case ET_IMG_PNG: + { + LLPointer png_image = new LLImagePNG; + if (png_image->load(mFilename) && png_image->decode(rawimg, 0.0f)) + { + rawimg->biasedScaleToPowerOfTwo(LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT); + decode_successful = true; + } + break; + } + + default: + { + // separating this into -several- LL_WARNS() calls because in the extremely unlikely case that this happens + // accessing mFilename and any other object properties might very well crash the viewer. + // getting here should be impossible, or there's been a pretty serious bug. + + LL_WARNS() << "During a decode attempt, the following local bitmap had no properly assigned extension." << LL_ENDL; + LL_WARNS() << "Filename: " << mFilename << LL_ENDL; + LL_WARNS() << "Disabling further update attempts for this file." << LL_ENDL; + mLinkStatus = LS_BROKEN; + } + } + + return decode_successful; +} + +void LLLocalBitmap::replaceIDs(const LLUUID& old_id, LLUUID new_id) +{ + // checking for misuse. + if (old_id == new_id) + { + LL_INFOS() << "An attempt was made to replace a texture with itself. (matching UUIDs)" << "\n" + << "Texture UUID: " << old_id.asString() << LL_ENDL; + return; + } + + mChangedSignal(getTrackingID(), old_id, new_id); + + // processing updates per channel; makes the process scalable. + // the only actual difference is in SetTE* call i.e. SetTETexture, SetTENormal, etc. + updateUserPrims(old_id, new_id, LLRender::DIFFUSE_MAP); + updateUserPrims(old_id, new_id, LLRender::NORMAL_MAP); + updateUserPrims(old_id, new_id, LLRender::SPECULAR_MAP); + + updateUserVolumes(old_id, new_id, LLRender::LIGHT_TEX); + updateUserVolumes(old_id, new_id, LLRender::SCULPT_TEX); // isn't there supposed to be an IMG_DEFAULT_SCULPT or something? + + // default safeguard image for layers + if( new_id == IMG_DEFAULT ) + { + new_id = IMG_DEFAULT_AVATAR; + } + + /* It doesn't actually update all of those, it merely checks if any of them + contain the referenced ID and if so, updates. */ + updateUserLayers(old_id, new_id, LLWearableType::WT_ALPHA); + updateUserLayers(old_id, new_id, LLWearableType::WT_EYES); + updateUserLayers(old_id, new_id, LLWearableType::WT_GLOVES); + updateUserLayers(old_id, new_id, LLWearableType::WT_JACKET); + updateUserLayers(old_id, new_id, LLWearableType::WT_PANTS); + updateUserLayers(old_id, new_id, LLWearableType::WT_SHIRT); + updateUserLayers(old_id, new_id, LLWearableType::WT_SHOES); + updateUserLayers(old_id, new_id, LLWearableType::WT_SKIN); + updateUserLayers(old_id, new_id, LLWearableType::WT_SKIRT); + updateUserLayers(old_id, new_id, LLWearableType::WT_SOCKS); + updateUserLayers(old_id, new_id, LLWearableType::WT_TATTOO); + updateUserLayers(old_id, new_id, LLWearableType::WT_UNIVERSAL); + updateUserLayers(old_id, new_id, LLWearableType::WT_UNDERPANTS); + updateUserLayers(old_id, new_id, LLWearableType::WT_UNDERSHIRT); + + updateGLTFMaterials(old_id, new_id); +} + +// this function sorts the faces from a getFaceList[getNumFaces] into a list of objects +// in order to prevent multiple sendTEUpdate calls per object during updateUserPrims +std::vector LLLocalBitmap::prepUpdateObjects(LLUUID old_id, U32 channel) +{ + std::vector obj_list; + LLViewerFetchedTexture* old_texture = gTextureList.findImage(old_id, TEX_LIST_STANDARD); + + for(U32 face_iterator = 0; face_iterator < old_texture->getNumFaces(channel); face_iterator++) + { + // getting an object from a face + LLFace* face_to_object = (*old_texture->getFaceList(channel))[face_iterator]; + + if(face_to_object) + { + LLViewerObject* affected_object = face_to_object->getViewerObject(); + + if(affected_object) + { + + // we have an object, we'll take it's UUID and compare it to + // whatever we already have in the returnable object list. + // if there is a match - we do not add (to prevent duplicates) + LLUUID mainlist_obj_id = affected_object->getID(); + bool add_object = true; + + // begin looking for duplicates + std::vector::iterator objlist_iter = obj_list.begin(); + for(; (objlist_iter != obj_list.end()) && add_object; objlist_iter++) + { + LLViewerObject* obj = *objlist_iter; + if (obj->getID() == mainlist_obj_id) + { + add_object = false; // duplicate found. + } + } + // end looking for duplicates + + if(add_object) + { + obj_list.push_back(affected_object); + } + + } + + } + + } // end of face-iterating for() + + return obj_list; +} + +void LLLocalBitmap::updateUserPrims(LLUUID old_id, LLUUID new_id, U32 channel) +{ + std::vector objectlist = prepUpdateObjects(old_id, channel); + + for(std::vector::iterator object_iterator = objectlist.begin(); + object_iterator != objectlist.end(); object_iterator++) + { + LLViewerObject* object = *object_iterator; + + if(object) + { + bool update_tex = false; + bool update_mat = false; + S32 num_faces = object->getNumFaces(); + + for (U8 face_iter = 0; face_iter < num_faces; face_iter++) + { + if (object->mDrawable) + { + LLFace* face = object->mDrawable->getFace(face_iter); + if (face && face->getTexture(channel) && face->getTexture(channel)->getID() == old_id) + { + // these things differ per channel, unless there already is a universal + // texture setting function to setTE that takes channel as a param? + // p.s.: switch for now, might become if - if an extra test is needed to verify before touching normalmap/specmap + switch(channel) + { + case LLRender::DIFFUSE_MAP: + { + object->setTETexture(face_iter, new_id); + update_tex = true; + break; + } + + case LLRender::NORMAL_MAP: + { + object->setTENormalMap(face_iter, new_id); + update_mat = true; + update_tex = true; + break; + } + + case LLRender::SPECULAR_MAP: + { + object->setTESpecularMap(face_iter, new_id); + update_mat = true; + update_tex = true; + break; + } + } + // end switch + + } + } + } + + if (update_tex) + { + object->sendTEUpdate(); + } + + if (update_mat) + { + object->mDrawable->getVOVolume()->faceMappingChanged(); + } + } + } +} + +void LLLocalBitmap::updateUserVolumes(LLUUID old_id, LLUUID new_id, U32 channel) +{ + LLViewerFetchedTexture* old_texture = gTextureList.findImage(old_id, TEX_LIST_STANDARD); + for (U32 volume_iter = 0; volume_iter < old_texture->getNumVolumes(channel); volume_iter++) + { + LLVOVolume* volobjp = (*old_texture->getVolumeList(channel))[volume_iter]; + switch (channel) + { + case LLRender::LIGHT_TEX: + { + if (volobjp->getLightTextureID() == old_id) + { + volobjp->setLightTextureID(new_id); + } + break; + } + case LLRender::SCULPT_TEX: + { + LLViewerObject* object = (LLViewerObject*)volobjp; + + if (object) + { + if (object->isSculpted() && object->getVolume() && + object->getVolume()->getParams().getSculptID() == old_id) + { + LLSculptParams* old_params = (LLSculptParams*)object->getParameterEntry(LLNetworkData::PARAMS_SCULPT); + LLSculptParams new_params(*old_params); + new_params.setSculptTexture(new_id, (*old_params).getSculptType()); + object->setParameterEntry(LLNetworkData::PARAMS_SCULPT, new_params, true); + } + } + } + } + } +} + +void LLLocalBitmap::updateUserLayers(LLUUID old_id, LLUUID new_id, LLWearableType::EType type) +{ + U32 count = gAgentWearables.getWearableCount(type); + for(U32 wearable_iter = 0; wearable_iter < count; wearable_iter++) + { + LLViewerWearable* wearable = gAgentWearables.getViewerWearable(type, wearable_iter); + if (wearable) + { + std::vector texture_list = wearable->getLocalTextureListSeq(); + for(std::vector::iterator texture_iter = texture_list.begin(); + texture_iter != texture_list.end(); texture_iter++) + { + LLLocalTextureObject* lto = *texture_iter; + + if (lto && lto->getID() == old_id) + { + U32 local_texlayer_index = 0; /* can't keep that as static const, gives errors, so i'm leaving this var here */ + LLAvatarAppearanceDefines::EBakedTextureIndex baked_texind = + lto->getTexLayer(local_texlayer_index)->getTexLayerSet()->getBakedTexIndex(); + + LLAvatarAppearanceDefines::ETextureIndex reg_texind = getTexIndex(type, baked_texind); + if (reg_texind != LLAvatarAppearanceDefines::TEX_NUM_INDICES) + { + U32 index; + if (gAgentWearables.getWearableIndex(wearable,index)) + { + gAgentAvatarp->setLocalTexture(reg_texind, gTextureList.getImage(new_id), false, index); + gAgentAvatarp->wearableUpdated(type); + /* telling the manager to rebake once update cycle is fully done */ + LLLocalBitmapMgr::getInstance()->setNeedsRebake(); + } + } + + } + } + } + } +} + +void LLLocalBitmap::updateGLTFMaterials(LLUUID old_id, LLUUID new_id) +{ + // Might be a better idea to hold this in LLGLTFMaterialList + mat_list_t::iterator end = mGLTFMaterialWithLocalTextures.end(); + for (mat_list_t::iterator it = mGLTFMaterialWithLocalTextures.begin(); it != end;) + { + if ((*it)->getNumRefs() == 1) + { + // render and override materials are often recreated, + // clean up any remains + it = mGLTFMaterialWithLocalTextures.erase(it); + end = mGLTFMaterialWithLocalTextures.end(); + } + else if ((*it)->replaceLocalTexture(mTrackingID, old_id, new_id)) + { + it++; + } + else + { + // Matching id not found, no longer in use + // material would clean itself, remove from the list + it = mGLTFMaterialWithLocalTextures.erase(it); + end = mGLTFMaterialWithLocalTextures.end(); + } + } + + // Render material consists of base and override materials, make sure replaceLocalTexture + // gets called for base and override before applyOverride + end = mGLTFMaterialWithLocalTextures.end(); + for (mat_list_t::iterator it = mGLTFMaterialWithLocalTextures.begin(); it != end;) + { + LLFetchedGLTFMaterial* fetched_mat = dynamic_cast((*it).get()); + if (fetched_mat) + { + for (LLTextureEntry* entry : fetched_mat->mTextureEntires) + { + // Normally a change in applied material id is supposed to + // drop overrides thus reset material, but local materials + // currently reuse their existing asset id, and purpose is + // to preview how material will work in-world, overrides + // included, so do an override to render update instead. + LLGLTFMaterial* override_mat = entry->getGLTFMaterialOverride(); + if (override_mat) + { + // do not create a new material, reuse existing pointer + LLFetchedGLTFMaterial* render_mat = (LLFetchedGLTFMaterial*)entry->getGLTFRenderMaterial(); + if (render_mat) + { + llassert(dynamic_cast(entry->getGLTFRenderMaterial()) != nullptr); + { + *render_mat = *fetched_mat; + } + render_mat->applyOverride(*override_mat); + } + } + } + } + ++it; + } +} + +LLAvatarAppearanceDefines::ETextureIndex LLLocalBitmap::getTexIndex( + LLWearableType::EType type, LLAvatarAppearanceDefines::EBakedTextureIndex baked_texind) +{ + LLAvatarAppearanceDefines::ETextureIndex result = LLAvatarAppearanceDefines::TEX_NUM_INDICES; // using as a default/fail return. + + switch(type) + { + case LLWearableType::WT_ALPHA: + { + switch(baked_texind) + { + case LLAvatarAppearanceDefines::BAKED_EYES: + { + result = LLAvatarAppearanceDefines::TEX_EYES_ALPHA; + break; + } + + case LLAvatarAppearanceDefines::BAKED_HAIR: + { + result = LLAvatarAppearanceDefines::TEX_HAIR_ALPHA; + break; + } + + case LLAvatarAppearanceDefines::BAKED_HEAD: + { + result = LLAvatarAppearanceDefines::TEX_HEAD_ALPHA; + break; + } + + case LLAvatarAppearanceDefines::BAKED_LOWER: + { + result = LLAvatarAppearanceDefines::TEX_LOWER_ALPHA; + break; + } + case LLAvatarAppearanceDefines::BAKED_UPPER: + { + result = LLAvatarAppearanceDefines::TEX_UPPER_ALPHA; + break; + } + + default: + { + break; + } + + } + break; + + } + + case LLWearableType::WT_EYES: + { + if (baked_texind == LLAvatarAppearanceDefines::BAKED_EYES) + { + result = LLAvatarAppearanceDefines::TEX_EYES_IRIS; + } + + break; + } + + case LLWearableType::WT_GLOVES: + { + if (baked_texind == LLAvatarAppearanceDefines::BAKED_UPPER) + { + result = LLAvatarAppearanceDefines::TEX_UPPER_GLOVES; + } + + break; + } + + case LLWearableType::WT_JACKET: + { + if (baked_texind == LLAvatarAppearanceDefines::BAKED_LOWER) + { + result = LLAvatarAppearanceDefines::TEX_LOWER_JACKET; + } + else if (baked_texind == LLAvatarAppearanceDefines::BAKED_UPPER) + { + result = LLAvatarAppearanceDefines::TEX_UPPER_JACKET; + } + + break; + } + + case LLWearableType::WT_PANTS: + { + if (baked_texind == LLAvatarAppearanceDefines::BAKED_LOWER) + { + result = LLAvatarAppearanceDefines::TEX_LOWER_PANTS; + } + + break; + } + + case LLWearableType::WT_SHIRT: + { + if (baked_texind == LLAvatarAppearanceDefines::BAKED_UPPER) + { + result = LLAvatarAppearanceDefines::TEX_UPPER_SHIRT; + } + + break; + } + + case LLWearableType::WT_SHOES: + { + if (baked_texind == LLAvatarAppearanceDefines::BAKED_LOWER) + { + result = LLAvatarAppearanceDefines::TEX_LOWER_SHOES; + } + + break; + } + + case LLWearableType::WT_SKIN: + { + switch(baked_texind) + { + case LLAvatarAppearanceDefines::BAKED_HEAD: + { + result = LLAvatarAppearanceDefines::TEX_HEAD_BODYPAINT; + break; + } + + case LLAvatarAppearanceDefines::BAKED_LOWER: + { + result = LLAvatarAppearanceDefines::TEX_LOWER_BODYPAINT; + break; + } + case LLAvatarAppearanceDefines::BAKED_UPPER: + { + result = LLAvatarAppearanceDefines::TEX_UPPER_BODYPAINT; + break; + } + + default: + { + break; + } + + } + break; + } + + case LLWearableType::WT_SKIRT: + { + if (baked_texind == LLAvatarAppearanceDefines::BAKED_SKIRT) + { + result = LLAvatarAppearanceDefines::TEX_SKIRT; + } + + break; + } + + case LLWearableType::WT_SOCKS: + { + if (baked_texind == LLAvatarAppearanceDefines::BAKED_LOWER) + { + result = LLAvatarAppearanceDefines::TEX_LOWER_SOCKS; + } + + break; + } + + case LLWearableType::WT_TATTOO: + { + switch (baked_texind) + { + case LLAvatarAppearanceDefines::BAKED_HEAD: + { + result = LLAvatarAppearanceDefines::TEX_HEAD_TATTOO; + break; + } + + case LLAvatarAppearanceDefines::BAKED_LOWER: + { + result = LLAvatarAppearanceDefines::TEX_LOWER_TATTOO; + break; + } + case LLAvatarAppearanceDefines::BAKED_UPPER: + { + result = LLAvatarAppearanceDefines::TEX_UPPER_TATTOO; + break; + } + default: + { + break; + } + } + break; + + } + case LLWearableType::WT_UNIVERSAL: + { + switch (baked_texind) + { + + case LLAvatarAppearanceDefines::BAKED_SKIRT: + { + result = LLAvatarAppearanceDefines::TEX_SKIRT_TATTOO; + break; + } + case LLAvatarAppearanceDefines::BAKED_EYES: + { + result = LLAvatarAppearanceDefines::TEX_EYES_TATTOO; + break; + } + case LLAvatarAppearanceDefines::BAKED_HAIR: + { + result = LLAvatarAppearanceDefines::TEX_HAIR_TATTOO; + break; + } + case LLAvatarAppearanceDefines::BAKED_LEFT_ARM: + { + result = LLAvatarAppearanceDefines::TEX_LEFT_ARM_TATTOO; + break; + } + case LLAvatarAppearanceDefines::BAKED_LEFT_LEG: + { + result = LLAvatarAppearanceDefines::TEX_LEFT_LEG_TATTOO; + break; + } + case LLAvatarAppearanceDefines::BAKED_AUX1: + { + result = LLAvatarAppearanceDefines::TEX_AUX1_TATTOO; + break; + } + case LLAvatarAppearanceDefines::BAKED_AUX2: + { + result = LLAvatarAppearanceDefines::TEX_AUX2_TATTOO; + break; + } + case LLAvatarAppearanceDefines::BAKED_AUX3: + { + result = LLAvatarAppearanceDefines::TEX_AUX3_TATTOO; + break; + } + case LLAvatarAppearanceDefines::BAKED_UPPER: + { + result = LLAvatarAppearanceDefines::TEX_UPPER_UNIVERSAL_TATTOO; + break; + } + case LLAvatarAppearanceDefines::BAKED_LOWER: + { + result = LLAvatarAppearanceDefines::TEX_LOWER_UNIVERSAL_TATTOO; + break; + } + case LLAvatarAppearanceDefines::BAKED_HEAD: + { + result = LLAvatarAppearanceDefines::TEX_HEAD_UNIVERSAL_TATTOO; + break; + } + + + default: + { + break; + } + + } + break; + } + + case LLWearableType::WT_UNDERPANTS: + { + if (baked_texind == LLAvatarAppearanceDefines::BAKED_LOWER) + { + result = LLAvatarAppearanceDefines::TEX_LOWER_UNDERPANTS; + } + + break; + } + + case LLWearableType::WT_UNDERSHIRT: + { + if (baked_texind == LLAvatarAppearanceDefines::BAKED_UPPER) + { + result = LLAvatarAppearanceDefines::TEX_UPPER_UNDERSHIRT; + } + + break; + } + + default: + { + LL_WARNS() << "Unknown wearable type: " << (int)type << "\n" + << "Baked Texture Index: " << (int)baked_texind << "\n" + << "Filename: " << mFilename << "\n" + << "TrackingID: " << mTrackingID << "\n" + << "InworldID: " << mWorldID << LL_ENDL; + } + + } + return result; +} + +/*=======================================*/ +/* LLLocalBitmapTimer: timer class */ +/*=======================================*/ +LLLocalBitmapTimer::LLLocalBitmapTimer() : LLEventTimer(LL_LOCAL_TIMER_HEARTBEAT) +{ +} + +LLLocalBitmapTimer::~LLLocalBitmapTimer() +{ +} + +void LLLocalBitmapTimer::startTimer() +{ + mEventTimer.start(); +} + +void LLLocalBitmapTimer::stopTimer() +{ + mEventTimer.stop(); +} + +bool LLLocalBitmapTimer::isRunning() +{ + return mEventTimer.getStarted(); +} + +bool LLLocalBitmapTimer::tick() +{ + LLLocalBitmapMgr::getInstance()->doUpdates(); + return false; +} + +/*=======================================*/ +/* LLLocalBitmapMgr: manager class */ +/*=======================================*/ +LLLocalBitmapMgr::LLLocalBitmapMgr() +{ +} + +LLLocalBitmapMgr::~LLLocalBitmapMgr() +{ + std::for_each(mBitmapList.begin(), mBitmapList.end(), DeletePointer()); + mBitmapList.clear(); +} + +bool LLLocalBitmapMgr::addUnit(const std::vector& filenames) +{ + bool add_successful = false; + std::vector::const_iterator iter = filenames.begin(); + while (iter != filenames.end()) + { + if (!iter->empty() && addUnit(*iter).notNull()) + { + add_successful = true; + } + iter++; + } + return add_successful; +} + +LLUUID LLLocalBitmapMgr::addUnit(const std::string& filename) +{ + if (!checkTextureDimensions(filename)) + { + return LLUUID::null; + } + + LLLocalBitmap* unit = new LLLocalBitmap(filename); + + if (unit->getValid()) + { + mBitmapList.push_back(unit); + return unit->getTrackingID(); + } + else + { + LL_WARNS() << "Attempted to add invalid or unreadable image file, attempt cancelled.\n" + << "Filename: " << filename << LL_ENDL; + + LLSD notif_args; + notif_args["FNAME"] = filename; + LLNotificationsUtil::add("LocalBitmapsVerifyFail", notif_args); + + delete unit; + unit = NULL; + } + + return LLUUID::null; +} + +bool LLLocalBitmapMgr::checkTextureDimensions(std::string filename) +{ + std::string exten = gDirUtilp->getExtension(filename); + U32 codec = LLImageBase::getCodecFromExtension(exten); + std::string mImageLoadError; + LLImageDimensionsInfo image_info; + if (!image_info.load(filename,codec)) + { + LLSD args; + args["NAME"] = gDirUtilp->getBaseFileName(filename); + if (!image_info.getWarningName().empty()) + { + args["REASON"] = LLTrans::getString(image_info.getWarningName()); + } + LLNotificationsUtil::add("CannotUploadTexture", args); + return false; + } + + S32 max_width = gSavedSettings.getS32("max_texture_dimension_X"); + S32 max_height = gSavedSettings.getS32("max_texture_dimension_Y"); + + if ((image_info.getWidth() > max_width) || (image_info.getHeight() > max_height)) + { + LLStringUtil::format_map_t args; + args["WIDTH"] = llformat("%d", max_width); + args["HEIGHT"] = llformat("%d", max_height); + mImageLoadError = LLTrans::getString("texture_load_dimensions_error", args); + + LLSD notif_args; + notif_args["REASON"] = mImageLoadError; + notif_args["NAME"] = gDirUtilp->getBaseFileName(filename); + LLNotificationsUtil::add("CannotUploadTexture", notif_args); + + return false; + } + + return true; +} + +void LLLocalBitmapMgr::delUnit(LLUUID tracking_id) +{ + if (!mBitmapList.empty()) + { + std::vector to_delete; + for (local_list_iter iter = mBitmapList.begin(); iter != mBitmapList.end(); iter++) + { /* finding which ones we want deleted and making a separate list */ + LLLocalBitmap* unit = *iter; + if (unit->getTrackingID() == tracking_id) + { + to_delete.push_back(unit); + } + } + + for(std::vector::iterator del_iter = to_delete.begin(); + del_iter != to_delete.end(); del_iter++) + { /* iterating over a temporary list, hence preserving the iterator validity while deleting. */ + LLLocalBitmap* unit = *del_iter; + mBitmapList.remove(unit); + delete unit; + unit = NULL; + } + } +} + +LLUUID LLLocalBitmapMgr::getWorldID(const LLUUID &tracking_id) const +{ + LLUUID world_id = LLUUID::null; + + for (local_list_citer iter = mBitmapList.begin(); iter != mBitmapList.end(); iter++) + { + LLLocalBitmap* unit = *iter; + if (unit->getTrackingID() == tracking_id) + { + world_id = unit->getWorldID(); + } + } + + return world_id; +} + +bool LLLocalBitmapMgr::isLocal(const LLUUID &world_id) const +{ + for (local_list_citer iter = mBitmapList.begin(); iter != mBitmapList.end(); iter++) + { + LLLocalBitmap* unit = *iter; + if (unit->getWorldID() == world_id) + { + return true; + } + } + return false; +} + +std::string LLLocalBitmapMgr::getFilename(const LLUUID &tracking_id) const +{ + std::string filename = ""; + + for (local_list_citer iter = mBitmapList.begin(); iter != mBitmapList.end(); iter++) + { + LLLocalBitmap* unit = *iter; + if (unit->getTrackingID() == tracking_id) + { + filename = unit->getFilename(); + } + } + + return filename; +} + +boost::signals2::connection LLLocalBitmapMgr::setOnChangedCallback(const LLUUID tracking_id, const LLLocalBitmap::LLLocalTextureCallback &cb) +{ + for (local_list_iter iter = mBitmapList.begin(); iter != mBitmapList.end(); iter++) + { + LLLocalBitmap* unit = *iter; + if (unit->getTrackingID() == tracking_id) + { + return unit->setChangedCallback(cb); + } + } + + return boost::signals2::connection(); +} + +void LLLocalBitmapMgr::associateGLTFMaterial(const LLUUID tracking_id, LLGLTFMaterial* mat) +{ + for (local_list_iter iter = mBitmapList.begin(); iter != mBitmapList.end(); iter++) + { + LLLocalBitmap* unit = *iter; + if (unit->getTrackingID() == tracking_id) + { + unit->addGLTFMaterial(mat); + } + } +} + +void LLLocalBitmapMgr::feedScrollList(LLScrollListCtrl* ctrl) +{ + if (ctrl) + { + std::string icon_name = LLInventoryIcon::getIconName( + LLAssetType::AT_TEXTURE, + LLInventoryType::IT_NONE); + + if (!mBitmapList.empty()) + { + for (local_list_iter iter = mBitmapList.begin(); + iter != mBitmapList.end(); iter++) + { + LLSD element; + + element["columns"][0]["column"] = "icon"; + element["columns"][0]["type"] = "icon"; + element["columns"][0]["value"] = icon_name; + + element["columns"][1]["column"] = "unit_name"; + element["columns"][1]["type"] = "text"; + element["columns"][1]["value"] = (*iter)->getShortName(); + + LLSD data; + data["id"] = (*iter)->getTrackingID(); + data["type"] = (S32)LLAssetType::AT_TEXTURE; + element["value"] = data; + + ctrl->addElement(element); + } + } + } + +} + +void LLLocalBitmapMgr::doUpdates() +{ + // preventing theoretical overlap in cases with huge number of loaded images. + mTimer.stopTimer(); + mNeedsRebake = false; + + for (local_list_iter iter = mBitmapList.begin(); iter != mBitmapList.end(); iter++) + { + (*iter)->updateSelf(); + } + + doRebake(); + mTimer.startTimer(); +} + +void LLLocalBitmapMgr::setNeedsRebake() +{ + mNeedsRebake = true; +} + +void LLLocalBitmapMgr::doRebake() +{ /* separated that from doUpdates to insure a rebake can be called separately during deletion */ + if (mNeedsRebake) + { + gAgentAvatarp->forceBakeAllTextures(LL_LOCAL_SLAM_FOR_DEBUG); + mNeedsRebake = false; + } +} + diff --git a/indra/newview/lllocalbitmaps.h b/indra/newview/lllocalbitmaps.h index 9b6581c7ce..e0cd9d172f 100644 --- a/indra/newview/lllocalbitmaps.h +++ b/indra/newview/lllocalbitmaps.h @@ -1,158 +1,158 @@ -/** - * @file lllocalbitmaps.h - * @author Vaalith Jinn - * @brief Local Bitmaps header - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LOCALBITMAPS_H -#define LL_LOCALBITMAPS_H - -#include "llavatarappearancedefines.h" -#include "lleventtimer.h" -#include "llpointer.h" -#include "llwearabletype.h" - -class LLScrollListCtrl; -class LLImageRaw; -class LLViewerObject; -class LLGLTFMaterial; - -class LLLocalBitmap -{ - public: /* main */ - LLLocalBitmap(std::string filename); - ~LLLocalBitmap(); - - public: /* accessors */ - std::string getFilename() const; - std::string getShortName() const; - LLUUID getTrackingID() const; - LLUUID getWorldID() const; - bool getValid() const; - - public: /* self update public section */ - enum EUpdateType - { - UT_FIRSTUSE, - UT_REGUPDATE - }; - - bool updateSelf(EUpdateType = UT_REGUPDATE); - - typedef boost::signals2::signal LLLocalTextureChangedSignal; - typedef LLLocalTextureChangedSignal::slot_type LLLocalTextureCallback; - boost::signals2::connection setChangedCallback(const LLLocalTextureCallback& cb); - void addGLTFMaterial(LLGLTFMaterial* mat); - - private: /* self update private section */ - bool decodeBitmap(LLPointer raw); - void replaceIDs(const LLUUID &old_id, LLUUID new_id); - std::vector prepUpdateObjects(LLUUID old_id, U32 channel); - void updateUserPrims(LLUUID old_id, LLUUID new_id, U32 channel); - void updateUserVolumes(LLUUID old_id, LLUUID new_id, U32 channel); - void updateUserLayers(LLUUID old_id, LLUUID new_id, LLWearableType::EType type); - void updateGLTFMaterials(LLUUID old_id, LLUUID new_id); - LLAvatarAppearanceDefines::ETextureIndex getTexIndex(LLWearableType::EType type, LLAvatarAppearanceDefines::EBakedTextureIndex baked_texind); - - private: /* private enums */ - enum ELinkStatus - { - LS_ON, - LS_BROKEN, - }; - - enum EExtension - { - ET_IMG_BMP, - ET_IMG_TGA, - ET_IMG_JPG, - ET_IMG_PNG - }; - - private: /* members */ - std::string mFilename; - std::string mShortName; - LLUUID mTrackingID; - LLUUID mWorldID; - bool mValid; - LLSD mLastModified; - EExtension mExtension; - ELinkStatus mLinkStatus; - S32 mUpdateRetries; - LLLocalTextureChangedSignal mChangedSignal; - - // Store a list of accosiated materials - // Might be a better idea to hold this in LLGLTFMaterialList - typedef std::vector > mat_list_t; - mat_list_t mGLTFMaterialWithLocalTextures; - -}; - -class LLLocalBitmapTimer : public LLEventTimer -{ - public: - LLLocalBitmapTimer(); - ~LLLocalBitmapTimer(); - - public: - void startTimer(); - void stopTimer(); - bool isRunning(); - bool tick(); - -}; - -class LLLocalBitmapMgr : public LLSingleton -{ - LLSINGLETON(LLLocalBitmapMgr); - ~LLLocalBitmapMgr(); -public: - bool addUnit(const std::vector& filenames); - LLUUID addUnit(const std::string& filename); - void delUnit(LLUUID tracking_id); - bool checkTextureDimensions(std::string filename); - - LLUUID getWorldID(const LLUUID &tracking_id) const; - bool isLocal(const LLUUID& world_id) const; - std::string getFilename(const LLUUID &tracking_id) const; - boost::signals2::connection setOnChangedCallback(const LLUUID tracking_id, const LLLocalBitmap::LLLocalTextureCallback& cb); - void associateGLTFMaterial(const LLUUID tracking_id, LLGLTFMaterial* mat); - - void feedScrollList(LLScrollListCtrl* ctrl); - void doUpdates(); - void setNeedsRebake(); - void doRebake(); - -private: - std::list mBitmapList; - LLLocalBitmapTimer mTimer; - bool mNeedsRebake; - typedef std::list::iterator local_list_iter; - typedef std::list::const_iterator local_list_citer; -}; - -#endif - +/** + * @file lllocalbitmaps.h + * @author Vaalith Jinn + * @brief Local Bitmaps header + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LOCALBITMAPS_H +#define LL_LOCALBITMAPS_H + +#include "llavatarappearancedefines.h" +#include "lleventtimer.h" +#include "llpointer.h" +#include "llwearabletype.h" + +class LLScrollListCtrl; +class LLImageRaw; +class LLViewerObject; +class LLGLTFMaterial; + +class LLLocalBitmap +{ + public: /* main */ + LLLocalBitmap(std::string filename); + ~LLLocalBitmap(); + + public: /* accessors */ + std::string getFilename() const; + std::string getShortName() const; + LLUUID getTrackingID() const; + LLUUID getWorldID() const; + bool getValid() const; + + public: /* self update public section */ + enum EUpdateType + { + UT_FIRSTUSE, + UT_REGUPDATE + }; + + bool updateSelf(EUpdateType = UT_REGUPDATE); + + typedef boost::signals2::signal LLLocalTextureChangedSignal; + typedef LLLocalTextureChangedSignal::slot_type LLLocalTextureCallback; + boost::signals2::connection setChangedCallback(const LLLocalTextureCallback& cb); + void addGLTFMaterial(LLGLTFMaterial* mat); + + private: /* self update private section */ + bool decodeBitmap(LLPointer raw); + void replaceIDs(const LLUUID &old_id, LLUUID new_id); + std::vector prepUpdateObjects(LLUUID old_id, U32 channel); + void updateUserPrims(LLUUID old_id, LLUUID new_id, U32 channel); + void updateUserVolumes(LLUUID old_id, LLUUID new_id, U32 channel); + void updateUserLayers(LLUUID old_id, LLUUID new_id, LLWearableType::EType type); + void updateGLTFMaterials(LLUUID old_id, LLUUID new_id); + LLAvatarAppearanceDefines::ETextureIndex getTexIndex(LLWearableType::EType type, LLAvatarAppearanceDefines::EBakedTextureIndex baked_texind); + + private: /* private enums */ + enum ELinkStatus + { + LS_ON, + LS_BROKEN, + }; + + enum EExtension + { + ET_IMG_BMP, + ET_IMG_TGA, + ET_IMG_JPG, + ET_IMG_PNG + }; + + private: /* members */ + std::string mFilename; + std::string mShortName; + LLUUID mTrackingID; + LLUUID mWorldID; + bool mValid; + LLSD mLastModified; + EExtension mExtension; + ELinkStatus mLinkStatus; + S32 mUpdateRetries; + LLLocalTextureChangedSignal mChangedSignal; + + // Store a list of accosiated materials + // Might be a better idea to hold this in LLGLTFMaterialList + typedef std::vector > mat_list_t; + mat_list_t mGLTFMaterialWithLocalTextures; + +}; + +class LLLocalBitmapTimer : public LLEventTimer +{ + public: + LLLocalBitmapTimer(); + ~LLLocalBitmapTimer(); + + public: + void startTimer(); + void stopTimer(); + bool isRunning(); + bool tick(); + +}; + +class LLLocalBitmapMgr : public LLSingleton +{ + LLSINGLETON(LLLocalBitmapMgr); + ~LLLocalBitmapMgr(); +public: + bool addUnit(const std::vector& filenames); + LLUUID addUnit(const std::string& filename); + void delUnit(LLUUID tracking_id); + bool checkTextureDimensions(std::string filename); + + LLUUID getWorldID(const LLUUID &tracking_id) const; + bool isLocal(const LLUUID& world_id) const; + std::string getFilename(const LLUUID &tracking_id) const; + boost::signals2::connection setOnChangedCallback(const LLUUID tracking_id, const LLLocalBitmap::LLLocalTextureCallback& cb); + void associateGLTFMaterial(const LLUUID tracking_id, LLGLTFMaterial* mat); + + void feedScrollList(LLScrollListCtrl* ctrl); + void doUpdates(); + void setNeedsRebake(); + void doRebake(); + +private: + std::list mBitmapList; + LLLocalBitmapTimer mTimer; + bool mNeedsRebake; + typedef std::list::iterator local_list_iter; + typedef std::list::const_iterator local_list_citer; +}; + +#endif + diff --git a/indra/newview/lllocalgltfmaterials.h b/indra/newview/lllocalgltfmaterials.h index c638454014..b806b54508 100644 --- a/indra/newview/lllocalgltfmaterials.h +++ b/indra/newview/lllocalgltfmaterials.h @@ -1,119 +1,119 @@ -/** - * @file lllocalrendermaterials.h - * @brief Local GLTF materials header - * - * $LicenseInfo:firstyear=2022&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2022, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LOCALGLTFMATERIALS_H -#define LL_LOCALGLTFMATERIALS_H - -#include "lleventtimer.h" -#include "llpointer.h" -#include "llgltfmateriallist.h" - -class LLScrollListCtrl; -class LLGLTFMaterial; -class LLViewerObject; -class LLTextureEntry; - -class LLLocalGLTFMaterial : public LLFetchedGLTFMaterial -{ -public: /* main */ - LLLocalGLTFMaterial(std::string filename, S32 index); - virtual ~LLLocalGLTFMaterial(); - -public: /* accessors */ - std::string getFilename() const; - std::string getShortName() const; - LLUUID getTrackingID() const; - LLUUID getWorldID() const; - S32 getIndexInFile() const; - -public: - bool updateSelf(); - -private: - bool loadMaterial(); - -private: /* private enums */ - enum ELinkStatus - { - LS_ON, - LS_BROKEN, - }; - - enum EExtension - { - ET_MATERIAL_GLTF, - ET_MATERIAL_GLB, - }; - -private: /* members */ - std::string mFilename; - std::string mShortName; - LLUUID mTrackingID; - LLUUID mWorldID; - LLSD mLastModified; - EExtension mExtension; - ELinkStatus mLinkStatus; - S32 mUpdateRetries; - S32 mMaterialIndex; // Single file can have more than one -}; - -class LLLocalGLTFMaterialTimer : public LLEventTimer -{ -public: - LLLocalGLTFMaterialTimer(); - ~LLLocalGLTFMaterialTimer(); - -public: - void startTimer(); - void stopTimer(); - bool isRunning(); - bool tick(); -}; - -class LLLocalGLTFMaterialMgr : public LLSingleton -{ - LLSINGLETON(LLLocalGLTFMaterialMgr); - ~LLLocalGLTFMaterialMgr(); -public: - S32 addUnit(const std::vector& filenames); - S32 addUnit(const std::string& filename); // file can hold multiple materials - void delUnit(LLUUID tracking_id); - - LLUUID getWorldID(LLUUID tracking_id); - bool isLocal(LLUUID world_id); - void getFilenameAndIndex(LLUUID tracking_id, std::string &filename, S32 &index); - - void feedScrollList(LLScrollListCtrl* ctrl); - void doUpdates(); - -private: - std::list > mMaterialList; - LLLocalGLTFMaterialTimer mTimer; - typedef std::list >::iterator local_list_iter; -}; - -#endif // LL_LOCALGLTFMATERIALS_H - +/** + * @file lllocalrendermaterials.h + * @brief Local GLTF materials header + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2022, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LOCALGLTFMATERIALS_H +#define LL_LOCALGLTFMATERIALS_H + +#include "lleventtimer.h" +#include "llpointer.h" +#include "llgltfmateriallist.h" + +class LLScrollListCtrl; +class LLGLTFMaterial; +class LLViewerObject; +class LLTextureEntry; + +class LLLocalGLTFMaterial : public LLFetchedGLTFMaterial +{ +public: /* main */ + LLLocalGLTFMaterial(std::string filename, S32 index); + virtual ~LLLocalGLTFMaterial(); + +public: /* accessors */ + std::string getFilename() const; + std::string getShortName() const; + LLUUID getTrackingID() const; + LLUUID getWorldID() const; + S32 getIndexInFile() const; + +public: + bool updateSelf(); + +private: + bool loadMaterial(); + +private: /* private enums */ + enum ELinkStatus + { + LS_ON, + LS_BROKEN, + }; + + enum EExtension + { + ET_MATERIAL_GLTF, + ET_MATERIAL_GLB, + }; + +private: /* members */ + std::string mFilename; + std::string mShortName; + LLUUID mTrackingID; + LLUUID mWorldID; + LLSD mLastModified; + EExtension mExtension; + ELinkStatus mLinkStatus; + S32 mUpdateRetries; + S32 mMaterialIndex; // Single file can have more than one +}; + +class LLLocalGLTFMaterialTimer : public LLEventTimer +{ +public: + LLLocalGLTFMaterialTimer(); + ~LLLocalGLTFMaterialTimer(); + +public: + void startTimer(); + void stopTimer(); + bool isRunning(); + bool tick(); +}; + +class LLLocalGLTFMaterialMgr : public LLSingleton +{ + LLSINGLETON(LLLocalGLTFMaterialMgr); + ~LLLocalGLTFMaterialMgr(); +public: + S32 addUnit(const std::vector& filenames); + S32 addUnit(const std::string& filename); // file can hold multiple materials + void delUnit(LLUUID tracking_id); + + LLUUID getWorldID(LLUUID tracking_id); + bool isLocal(LLUUID world_id); + void getFilenameAndIndex(LLUUID tracking_id, std::string &filename, S32 &index); + + void feedScrollList(LLScrollListCtrl* ctrl); + void doUpdates(); + +private: + std::list > mMaterialList; + LLLocalGLTFMaterialTimer mTimer; + typedef std::list >::iterator local_list_iter; +}; + +#endif // LL_LOCALGLTFMATERIALS_H + diff --git a/indra/newview/lllocationinputctrl.cpp b/indra/newview/lllocationinputctrl.cpp index 39e578eb80..54dd5792a0 100644 --- a/indra/newview/lllocationinputctrl.cpp +++ b/indra/newview/lllocationinputctrl.cpp @@ -1,1290 +1,1290 @@ -/** - * @file lllocationinputctrl.cpp - * @brief Combobox-like location input control - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -// file includes -#include "lllocationinputctrl.h" - -// common includes -#include "llbutton.h" -#include "llfocusmgr.h" -#include "llhelp.h" -#include "llmenugl.h" -#include "llparcel.h" -#include "llstring.h" -#include "lltrans.h" -#include "lluictrlfactory.h" -#include "lltooltip.h" -#include "llnotificationsutil.h" -#include "llregionflags.h" - -// newview includes -#include "llagent.h" -#include "llfloaterreg.h" -#include "llfloatersidepanelcontainer.h" -#include "llinventoryobserver.h" -#include "lllandmarkactions.h" -#include "lllandmarklist.h" -#include "llpathfindingmanager.h" -#include "llpathfindingnavmesh.h" -#include "llpathfindingnavmeshstatus.h" -#include "llteleporthistory.h" -#include "llslurl.h" -#include "llstatusbar.h" // getHealth() -#include "lltrans.h" -#include "llviewerinventory.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" -#include "llviewercontrol.h" -#include "llviewermenu.h" -#include "llurllineeditorctrl.h" -#include "llagentui.h" - -#include "llmenuoptionpathfindingrebakenavmesh.h" -#include "llpathfindingmanager.h" - -//============================================================================ -/* - * "ADD LANDMARK" BUTTON UPDATING LOGIC - * - * If the current parcel has been landmarked, we should draw - * a special image on the button. - * - * To avoid determining the appropriate image on every draw() we do that - * only in the following cases: - * 1) Navbar is shown for the first time after login. - * 2) Agent moves to another parcel. - * 3) A landmark is created or removed. - * - * The first case is handled by the handleLoginComplete() method. - * - * The second case is handled by setting the "agent parcel changed" callback - * on LLViewerParcelMgr. - * - * The third case is the most complex one. We have two inventory observers for that: - * one is designated to handle adding landmarks, the other handles removal. - * Let's see how the former works. - * - * When we get notified about landmark addition, the landmark position is unknown yet. What we can - * do at that point is initiate loading the landmark data by LLLandmarkList and set the - * "loading finished" callback on it. Finally, when the callback is triggered, - * we can determine whether the landmark refers to a point within the current parcel - * and choose the appropriate image for the "Add landmark" button. - */ - -/** - * Initiates loading the landmarks that have been just added. - * - * Once the loading is complete we'll be notified - * with the callback we set for LLLandmarkList. - */ -class LLAddLandmarkObserver : public LLInventoryAddedObserver -{ -public: - LLAddLandmarkObserver(LLLocationInputCtrl* input) : mInput(input) {} - -private: - /*virtual*/ void done() - { - const uuid_set_t& added = gInventory.getAddedIDs(); - for (uuid_set_t::const_iterator it = added.begin(); it != added.end(); ++it) - { - LLInventoryItem* item = gInventory.getItem(*it); - if (!item || item->getType() != LLAssetType::AT_LANDMARK) - continue; - - // Start loading the landmark. - LLLandmark* lm = gLandmarkList.getAsset( - item->getAssetUUID(), - boost::bind(&LLLocationInputCtrl::onLandmarkLoaded, mInput, _1)); - if (lm) - { - // Already loaded? Great, handle it immediately (the callback won't be called). - mInput->onLandmarkLoaded(lm); - } - } - } - - LLLocationInputCtrl* mInput; -}; - -/** - * Updates the "Add landmark" button once a landmark gets removed. - */ -class LLRemoveLandmarkObserver : public LLInventoryObserver -{ -public: - LLRemoveLandmarkObserver(LLLocationInputCtrl* input) : mInput(input) {} - -private: - /*virtual*/ void changed(U32 mask) - { - if (mask & (~(LLInventoryObserver::LABEL| - LLInventoryObserver::INTERNAL| - LLInventoryObserver::ADD| - LLInventoryObserver::CREATE| - LLInventoryObserver::UPDATE_CREATE))) - { - mInput->updateAddLandmarkButton(); - } - } - - LLLocationInputCtrl* mInput; -}; - -class LLParcelChangeObserver : public LLParcelObserver -{ -public: - LLParcelChangeObserver(LLLocationInputCtrl* input) : mInput(input) {} - -private: - /*virtual*/ void changed() - { - if (mInput) - { - mInput->refreshParcelIcons(); - } - } - - LLLocationInputCtrl* mInput; -}; - -//============================================================================ - - -static LLDefaultChildRegistry::Register r("location_input"); - -LLLocationInputCtrl::Params::Params() -: icon_maturity_general("icon_maturity_general"), - icon_maturity_adult("icon_maturity_adult"), - icon_maturity_moderate("icon_maturity_moderate"), - add_landmark_image_enabled("add_landmark_image_enabled"), - add_landmark_image_disabled("add_landmark_image_disabled"), - add_landmark_image_hover("add_landmark_image_hover"), - add_landmark_image_selected("add_landmark_image_selected"), - add_landmark_hpad("add_landmark_hpad", 0), - icon_hpad("icon_hpad", 0), - add_landmark_button("add_landmark_button"), - for_sale_button("for_sale_button"), - info_button("info_button"), - maturity_button("maturity_button"), - voice_icon("voice_icon"), - fly_icon("fly_icon"), - push_icon("push_icon"), - build_icon("build_icon"), - scripts_icon("scripts_icon"), - damage_icon("damage_icon"), - damage_text("damage_text"), - see_avatars_icon("see_avatars_icon"), - maturity_help_topic("maturity_help_topic"), - pathfinding_dirty_icon("pathfinding_dirty_icon"), - pathfinding_disabled_icon("pathfinding_disabled_icon") -{ -} - -LLLocationInputCtrl::LLLocationInputCtrl(const LLLocationInputCtrl::Params& p) -: LLComboBox(p), - mIconHPad(p.icon_hpad), - mAddLandmarkHPad(p.add_landmark_hpad), - mLocationContextMenu(NULL), - mAddLandmarkBtn(NULL), - mForSaleBtn(NULL), - mInfoBtn(NULL), - mRegionCrossingSlot(), - mNavMeshSlot(), - mIsNavMeshDirty(false), - mLandmarkImageOn(NULL), - mLandmarkImageOff(NULL), - mIconMaturityGeneral(NULL), - mIconMaturityAdult(NULL), - mIconMaturityModerate(NULL), - mMaturityHelpTopic(p.maturity_help_topic) -{ - // Lets replace default LLLineEditor with LLLocationLineEditor - // to make needed escaping while copying and cutting url - delete mTextEntry; - - // Can't access old mTextEntry fields as they are protected, so lets build new params - // That is C&P from LLComboBox::createLineEditor function - S32 arrow_width = mArrowImage ? mArrowImage->getWidth() : 0; - LLRect text_entry_rect(0, getRect().getHeight(), getRect().getWidth(), 0); - text_entry_rect.mRight -= llmax(8,arrow_width) + 2 * BTN_DROP_SHADOW; - - LLLineEditor::Params params = p.combo_editor; - params.rect(text_entry_rect); - params.default_text(LLStringUtil::null); - params.max_length.bytes(p.max_chars); - params.keystroke_callback(boost::bind(&LLLocationInputCtrl::onTextEntry, this, _1)); - params.commit_on_focus_lost(false); - params.follows.flags(FOLLOWS_ALL); - mTextEntry = LLUICtrlFactory::create(params); - mTextEntry->resetContextMenu(); - addChild(mTextEntry); - // LLLineEditor is replaced with LLLocationLineEditor - - // "Place information" button. - LLButton::Params info_params = p.info_button; - mInfoBtn = LLUICtrlFactory::create(info_params); - mInfoBtn->setClickedCallback(boost::bind(&LLLocationInputCtrl::onInfoButtonClicked, this)); - addChild(mInfoBtn); - - // "Add landmark" button. - LLButton::Params al_params = p.add_landmark_button; - - // Image for unselected state will be set in updateAddLandmarkButton(), - // it will be either mLandmarkOn or mLandmarkOff - if (p.add_landmark_image_enabled()) - { - mLandmarkImageOn = p.add_landmark_image_enabled; - } - if (p.add_landmark_image_disabled()) - { - mLandmarkImageOff = p.add_landmark_image_disabled; - } - - if(p.add_landmark_image_selected) - { - al_params.image_selected = p.add_landmark_image_selected; - } - if (p.add_landmark_image_hover()) - { - al_params.image_hover_unselected = p.add_landmark_image_hover; - } - - al_params.click_callback.function(boost::bind(&LLLocationInputCtrl::onAddLandmarkButtonClicked, this)); - mAddLandmarkBtn = LLUICtrlFactory::create(al_params); - enableAddLandmarkButton(true); - addChild(mAddLandmarkBtn); - - if (p.icon_maturity_general()) - { - mIconMaturityGeneral = p.icon_maturity_general; - } - if (p.icon_maturity_adult()) - { - mIconMaturityAdult = p.icon_maturity_adult; - } - if(p.icon_maturity_moderate()) - { - mIconMaturityModerate = p.icon_maturity_moderate; - } - - LLButton::Params maturity_button = p.maturity_button; - mMaturityButton = LLUICtrlFactory::create(maturity_button); - addChild(mMaturityButton); - - LLButton::Params for_sale_button = p.for_sale_button; - for_sale_button.tool_tip = LLTrans::getString("LocationCtrlForSaleTooltip"); - for_sale_button.click_callback.function( - boost::bind(&LLLocationInputCtrl::onForSaleButtonClicked, this)); - mForSaleBtn = LLUICtrlFactory::create( for_sale_button ); - addChild(mForSaleBtn); - - // Parcel property icons - // Must be mouse-opaque so cursor stays as an arrow when hovering to - // see tooltip. - LLIconCtrl::Params voice_icon = p.voice_icon; - voice_icon.tool_tip = LLTrans::getString("LocationCtrlVoiceTooltip"); - voice_icon.mouse_opaque = true; - mParcelIcon[VOICE_ICON] = LLUICtrlFactory::create(voice_icon); - mParcelIcon[VOICE_ICON]->setMouseDownCallback(boost::bind(&LLLocationInputCtrl::onParcelIconClick, this, VOICE_ICON)); - addChild(mParcelIcon[VOICE_ICON]); - - LLIconCtrl::Params fly_icon = p.fly_icon; - fly_icon.tool_tip = LLTrans::getString("LocationCtrlFlyTooltip"); - fly_icon.mouse_opaque = true; - mParcelIcon[FLY_ICON] = LLUICtrlFactory::create(fly_icon); - mParcelIcon[FLY_ICON]->setMouseDownCallback(boost::bind(&LLLocationInputCtrl::onParcelIconClick, this, FLY_ICON)); - addChild(mParcelIcon[FLY_ICON]); - - LLIconCtrl::Params push_icon = p.push_icon; - push_icon.tool_tip = LLTrans::getString("LocationCtrlPushTooltip"); - push_icon.mouse_opaque = true; - mParcelIcon[PUSH_ICON] = LLUICtrlFactory::create(push_icon); - mParcelIcon[PUSH_ICON]->setMouseDownCallback(boost::bind(&LLLocationInputCtrl::onParcelIconClick, this, PUSH_ICON)); - addChild(mParcelIcon[PUSH_ICON]); - - LLIconCtrl::Params build_icon = p.build_icon; - build_icon.tool_tip = LLTrans::getString("LocationCtrlBuildTooltip"); - build_icon.mouse_opaque = true; - mParcelIcon[BUILD_ICON] = LLUICtrlFactory::create(build_icon); - mParcelIcon[BUILD_ICON]->setMouseDownCallback(boost::bind(&LLLocationInputCtrl::onParcelIconClick, this, BUILD_ICON)); - addChild(mParcelIcon[BUILD_ICON]); - - LLIconCtrl::Params scripts_icon = p.scripts_icon; - scripts_icon.tool_tip = LLTrans::getString("LocationCtrlScriptsTooltip"); - scripts_icon.mouse_opaque = true; - mParcelIcon[SCRIPTS_ICON] = LLUICtrlFactory::create(scripts_icon); - mParcelIcon[SCRIPTS_ICON]->setMouseDownCallback(boost::bind(&LLLocationInputCtrl::onParcelIconClick, this, SCRIPTS_ICON)); - addChild(mParcelIcon[SCRIPTS_ICON]); - - LLIconCtrl::Params damage_icon = p.damage_icon; - damage_icon.tool_tip = LLTrans::getString("LocationCtrlDamageTooltip"); - damage_icon.mouse_opaque = true; - mParcelIcon[DAMAGE_ICON] = LLUICtrlFactory::create(damage_icon); - mParcelIcon[DAMAGE_ICON]->setMouseDownCallback(boost::bind(&LLLocationInputCtrl::onParcelIconClick, this, DAMAGE_ICON)); - addChild(mParcelIcon[DAMAGE_ICON]); - - LLIconCtrl::Params pathfinding_dirty_icon = p.pathfinding_dirty_icon; - pathfinding_dirty_icon.tool_tip = LLTrans::getString("LocationCtrlPathfindingDirtyTooltip"); - pathfinding_dirty_icon.mouse_opaque = true; - mParcelIcon[PATHFINDING_DIRTY_ICON] = LLUICtrlFactory::create(pathfinding_dirty_icon); - mParcelIcon[PATHFINDING_DIRTY_ICON]->setMouseDownCallback(boost::bind(&LLLocationInputCtrl::onParcelIconClick, this, PATHFINDING_DIRTY_ICON)); - addChild(mParcelIcon[PATHFINDING_DIRTY_ICON]); - - LLIconCtrl::Params pathfinding_disabled_icon = p.pathfinding_disabled_icon; - pathfinding_disabled_icon.tool_tip = LLTrans::getString("LocationCtrlPathfindingDisabledTooltip"); - pathfinding_disabled_icon.mouse_opaque = true; - mParcelIcon[PATHFINDING_DISABLED_ICON] = LLUICtrlFactory::create(pathfinding_disabled_icon); - mParcelIcon[PATHFINDING_DISABLED_ICON]->setMouseDownCallback(boost::bind(&LLLocationInputCtrl::onParcelIconClick, this, PATHFINDING_DISABLED_ICON)); - addChild(mParcelIcon[PATHFINDING_DISABLED_ICON]); - - LLTextBox::Params damage_text = p.damage_text; - damage_text.tool_tip = LLTrans::getString("LocationCtrlDamageTooltip"); - damage_text.mouse_opaque = true; - mDamageText = LLUICtrlFactory::create(damage_text); - addChild(mDamageText); - - LLIconCtrl::Params see_avatars_icon = p.see_avatars_icon; - see_avatars_icon.tool_tip = LLTrans::getString("LocationCtrlSeeAVsTooltip"); - see_avatars_icon.mouse_opaque = true; - mParcelIcon[SEE_AVATARS_ICON] = LLUICtrlFactory::create(see_avatars_icon); - mParcelIcon[SEE_AVATARS_ICON]->setMouseDownCallback(boost::bind(&LLLocationInputCtrl::onParcelIconClick, this, SEE_AVATARS_ICON)); - addChild(mParcelIcon[SEE_AVATARS_ICON]); - - // Register callbacks and load the location field context menu (NB: the order matters). - LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Navbar.Action", boost::bind(&LLLocationInputCtrl::onLocationContextMenuItemClicked, this, _2)); - LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Navbar.EnableMenuItem", boost::bind(&LLLocationInputCtrl::onLocationContextMenuItemEnabled, this, _2)); - - setPrearrangeCallback(boost::bind(&LLLocationInputCtrl::onLocationPrearrange, this, _2)); - getTextEntry()->setMouseUpCallback(boost::bind(&LLLocationInputCtrl::changeLocationPresentation, this)); - - // Load the location field context menu - mLocationContextMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_navbar.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - if (!mLocationContextMenu) - { - LL_WARNS() << "Error loading navigation bar context menu" << LL_ENDL; - - } - //don't show default context menu - getTextEntry()->setShowContextMenu(false); - getTextEntry()->setRightMouseDownCallback(boost::bind(&LLLocationInputCtrl::onTextEditorRightClicked, this, _2, _3, _4)); - updateWidgetlayout(); - - // Connecting signal for updating location on "Show Coordinates" setting change. - LLControlVariable* coordinates_control = gSavedSettings.getControl("NavBarShowCoordinates").get(); - if (coordinates_control) - { - mCoordinatesControlConnection = coordinates_control->getSignal()->connect(boost::bind(&LLLocationInputCtrl::refreshLocation, this)); - } - - // Connecting signal for updating parcel icons on "Show Parcel Properties" setting change. - LLControlVariable* parcel_properties_control = gSavedSettings.getControl("NavBarShowParcelProperties").get(); - if (parcel_properties_control) - { - mParcelPropertiesControlConnection = parcel_properties_control->getSignal()->connect(boost::bind(&LLLocationInputCtrl::refreshParcelIcons, this)); - } - - // - Make the "Add landmark" button updated when either current parcel gets changed - // or a landmark gets created or removed from the inventory. - // - Update the location string on parcel change. - mParcelMgrConnection = gAgent.addParcelChangedCallback( - boost::bind(&LLLocationInputCtrl::onAgentParcelChange, this)); - // LLLocationHistory instance is being created before the location input control, so we have to update initial state of button manually. - mButton->setEnabled(LLLocationHistory::instance().getItemCount() > 0); - mLocationHistoryConnection = LLLocationHistory::getInstance()->setChangedCallback( - boost::bind(&LLLocationInputCtrl::onLocationHistoryChanged, this,_1)); - - mRegionCrossingSlot = gAgent.addRegionChangedCallback(boost::bind(&LLLocationInputCtrl::onRegionBoundaryCrossed, this)); - createNavMeshStatusListenerForCurrentRegion(); - - mRemoveLandmarkObserver = new LLRemoveLandmarkObserver(this); - mAddLandmarkObserver = new LLAddLandmarkObserver(this); - gInventory.addObserver(mRemoveLandmarkObserver); - gInventory.addObserver(mAddLandmarkObserver); - - mParcelChangeObserver = new LLParcelChangeObserver(this); - LLViewerParcelMgr::getInstance()->addObserver(mParcelChangeObserver); - - mAddLandmarkTooltip = LLTrans::getString("LocationCtrlAddLandmarkTooltip"); - mEditLandmarkTooltip = LLTrans::getString("LocationCtrlEditLandmarkTooltip"); - mButton->setToolTip(LLTrans::getString("LocationCtrlComboBtnTooltip")); - mInfoBtn->setToolTip(LLTrans::getString("LocationCtrlInfoBtnTooltip")); -} - -LLLocationInputCtrl::~LLLocationInputCtrl() -{ - gInventory.removeObserver(mRemoveLandmarkObserver); - gInventory.removeObserver(mAddLandmarkObserver); - delete mRemoveLandmarkObserver; - delete mAddLandmarkObserver; - - LLViewerParcelMgr::getInstance()->removeObserver(mParcelChangeObserver); - delete mParcelChangeObserver; - - mRegionCrossingSlot.disconnect(); - mNavMeshSlot.disconnect(); - mCoordinatesControlConnection.disconnect(); - mParcelPropertiesControlConnection.disconnect(); - mParcelMgrConnection.disconnect(); - mLocationHistoryConnection.disconnect(); -} - -void LLLocationInputCtrl::setEnabled(bool enabled) -{ - LLComboBox::setEnabled(enabled); - mAddLandmarkBtn->setEnabled(enabled); -} - -void LLLocationInputCtrl::hideList() -{ - LLComboBox::hideList(); - if (mTextEntry && hasFocus()) - focusTextEntry(); -} - -bool LLLocationInputCtrl::handleToolTip(S32 x, S32 y, MASK mask) -{ - - if(mAddLandmarkBtn->parentPointInView(x,y)) - { - updateAddLandmarkTooltip(); - } - // Let the buttons show their tooltips. - if (LLUICtrl::handleToolTip(x, y, mask)) - { - if (mList->getRect().pointInRect(x, y)) - { - S32 loc_x, loc_y; - //x,y - contain coordinates related to the location input control, but without taking the expanded list into account - //So we have to convert it again into local coordinates of mList - localPointToOtherView(x,y,&loc_x,&loc_y,mList); - - LLScrollListItem* item = mList->hitItem(loc_x,loc_y); - if (item) - { - LLSD value = item->getValue(); - if (value.has("tooltip")) - { - LLToolTipMgr::instance().show(value["tooltip"]); - } - } - } - - return true; - } - - return false; -} - -bool LLLocationInputCtrl::handleKeyHere(KEY key, MASK mask) -{ - bool result = LLComboBox::handleKeyHere(key, mask); - - if (key == KEY_DOWN && hasFocus() && mList->getItemCount() != 0 && !mList->getVisible()) - { - showList(); - } - - return result; -} - -void LLLocationInputCtrl::onTextEntry(LLLineEditor* line_editor) -{ - KEY key = gKeyboard->currentKey(); - MASK mask = gKeyboard->currentMask(true); - - // Typing? (moving cursor should not affect showing the list) - bool typing = mask != MASK_CONTROL && key != KEY_LEFT && key != KEY_RIGHT && key != KEY_HOME && key != KEY_END; - bool pasting = mask == MASK_CONTROL && key == 'V'; - - if (line_editor->getText().empty()) - { - prearrangeList(); // resets filter - hideList(); - } - else if (typing || pasting) - { - prearrangeList(line_editor->getText()); - if (mList->getItemCount() != 0) - { - showList(); - focusTextEntry(); - } - else - { - // Hide the list if it's empty. - hideList(); - } - } - - LLComboBox::onTextEntry(line_editor); -} - -/** - * Useful if we want to just set the text entry value, no matter what the list contains. - * - * This is faster than setTextEntry(). - */ -void LLLocationInputCtrl::setText(const LLStringExplicit& text) -{ - if (mTextEntry) - { - mTextEntry->setText(text); - } - mHasAutocompletedText = false; -} - -void LLLocationInputCtrl::setFocus(bool b) -{ - LLComboBox::setFocus(b); - - if (mTextEntry && b && !mList->getVisible()) - { - mTextEntry->setFocus(true); - } -} - -void LLLocationInputCtrl::handleLoginComplete() -{ - // An agent parcel update hasn't occurred yet, so we have to - // manually set location and the appropriate "Add landmark" icon. - refresh(); -} - -//== private methods ========================================================= - -void LLLocationInputCtrl::onFocusReceived() -{ - prearrangeList(); -} - -void LLLocationInputCtrl::onFocusLost() -{ - LLUICtrl::onFocusLost(); - refreshLocation(); - - // Setting cursor to 0 to show the left edge of the text. See STORM-370. - mTextEntry->setCursor(0); - - if(mTextEntry->hasSelection()){ - mTextEntry->deselect(); - } -} - -void LLLocationInputCtrl::draw() -{ - static LLUICachedControl show_coords("NavBarShowCoordinates", false); - if(!hasFocus() && show_coords) - { - refreshLocation(); - } - - static LLUICachedControl show_icons("NavBarShowParcelProperties", false); - if (show_icons) - { - refreshHealth(); - } - LLComboBox::draw(); -} - -void LLLocationInputCtrl::reshape(S32 width, S32 height, bool called_from_parent) -{ - LLComboBox::reshape(width, height, called_from_parent); - - // Setting cursor to 0 to show the left edge of the text. See EXT-4967. - mTextEntry->setCursor(0); - if (mTextEntry->hasSelection()) - { - // Deselecting because selection position is changed together with - // cursor position change. - mTextEntry->deselect(); - } - - if (isHumanReadableLocationVisible) - { - refreshMaturityButton(); - } -} - -void LLLocationInputCtrl::onInfoButtonClicked() -{ - LLFloaterSidePanelContainer::showPanel("places", LLSD().with("type", "agent")); -} - -void LLLocationInputCtrl::onForSaleButtonClicked() -{ - handle_buy_land(); -} - -void LLLocationInputCtrl::onAddLandmarkButtonClicked() -{ - LLViewerInventoryItem* landmark = LLLandmarkActions::findLandmarkForAgentPos(); - // Landmark exists, open it for preview and edit - if(landmark && landmark->getUUID().notNull()) - { - LLSD key; - key["type"] = "landmark"; - key["id"] = landmark->getUUID(); - - LLFloaterSidePanelContainer::showPanel("places", key); - } - else - { - LLFloaterReg::showInstance("add_landmark"); - } -} - -void LLLocationInputCtrl::onAgentParcelChange() -{ - refresh(); -} - -void LLLocationInputCtrl::onRegionBoundaryCrossed() -{ - createNavMeshStatusListenerForCurrentRegion(); -} - -void LLLocationInputCtrl::onNavMeshStatusChange(const LLPathfindingNavMeshStatus &pNavMeshStatus) -{ - mIsNavMeshDirty = pNavMeshStatus.isValid() && (pNavMeshStatus.getStatus() != LLPathfindingNavMeshStatus::kComplete); - refreshParcelIcons(); -} - -void LLLocationInputCtrl::onLandmarkLoaded(LLLandmark* lm) -{ - (void) lm; - updateAddLandmarkButton(); -} - -void LLLocationInputCtrl::onLocationHistoryChanged(LLLocationHistory::EChangeType event) -{ - if(event == LLLocationHistory::LOAD) - { - rebuildLocationHistory(); - } - mButton->setEnabled(LLLocationHistory::instance().getItemCount() > 0); -} - -void LLLocationInputCtrl::onLocationPrearrange(const LLSD& data) -{ - std::string filter = data.asString(); - rebuildLocationHistory(filter); - - //Let's add landmarks to the top of the list if any - if(!filter.empty() ) - { - LLInventoryModel::item_array_t landmark_items = LLLandmarkActions::fetchLandmarksByName(filter, true); - - for(U32 i=0; i < landmark_items.size(); i++) - { - LLSD value; - //TODO:: DO we need tooltip for Landmark?? - - value["item_type"] = LANDMARK; - value["AssetUUID"] = landmark_items[i]->getAssetUUID(); - addLocationHistoryEntry(landmark_items[i]->getName(), value); - - } - //Let's add teleport history items - LLTeleportHistory* th = LLTeleportHistory::getInstance(); - LLTeleportHistory::slurl_list_t th_items = th->getItems(); - - std::set new_item_titles;// duplicate control - LLTeleportHistory::slurl_list_t::iterator result = std::find_if( - th_items.begin(), th_items.end(), boost::bind( - &LLLocationInputCtrl::findTeleportItemsByTitle, this, - _1, filter)); - - while (result != th_items.end()) - { - //mTitile format - region_name[, parcel_name] - //mFullTitile format - region_name[, parcel_name] (local_x,local_y, local_z) - if (new_item_titles.insert(result->mFullTitle).second) - { - LLSD value; - value["item_type"] = TELEPORT_HISTORY; - value["global_pos"] = result->mGlobalPos.getValue(); - std::string region_name = result->mTitle.substr(0, result->mTitle.find(',')); - //TODO*: add Surl to teleportitem or parse region name from title - value["tooltip"] = LLSLURL(region_name, result->mGlobalPos).getSLURLString(); - addLocationHistoryEntry(result->getTitle(), value); - } - result = std::find_if(result + 1, th_items.end(), boost::bind( - &LLLocationInputCtrl::findTeleportItemsByTitle, this, - _1, filter)); - } - } - sortByName(); - - mList->mouseOverHighlightNthItem(-1); // Clear highlight on the last selected item. -} - -bool LLLocationInputCtrl::findTeleportItemsByTitle(const LLTeleportHistoryItem& item, const std::string& filter) -{ - return item.mTitle.find(filter) != std::string::npos; -} - -void LLLocationInputCtrl::onTextEditorRightClicked(S32 x, S32 y, MASK mask) -{ - if (mLocationContextMenu) - { - updateContextMenu(); - mLocationContextMenu->buildDrawLabels(); - mLocationContextMenu->updateParent(LLMenuGL::sMenuContainer); - hideList(); - setFocus(true); - changeLocationPresentation(); - LLMenuGL::showPopup(this, mLocationContextMenu, x, y); - } -} - -void LLLocationInputCtrl::refresh() -{ - refreshLocation(); // update location string - refreshParcelIcons(); - updateAddLandmarkButton(); // indicate whether current parcel has been landmarked -} - -void LLLocationInputCtrl::refreshLocation() -{ - // Is one of our children focused? - if (LLUICtrl::hasFocus() || mButton->hasFocus() || mList->hasFocus() || - (mTextEntry && mTextEntry->hasFocus()) || - (mAddLandmarkBtn->hasFocus())) - { - LL_WARNS() << "Location input should not be refreshed when having focus" << LL_ENDL; - return; - } - - // Update location field. - std::string location_name; - LLAgentUI::ELocationFormat format = - (gSavedSettings.getBOOL("NavBarShowCoordinates") - ? LLAgentUI::LOCATION_FORMAT_FULL - : LLAgentUI::LOCATION_FORMAT_NO_COORDS); - - if (!LLAgentUI::buildLocationString(location_name, format)) - { - location_name = "???"; - } - // store human-readable location to compare it in changeLocationPresentation() - mHumanReadableLocation = location_name; - setText(location_name); - isHumanReadableLocationVisible = true; - - refreshMaturityButton(); -} - -// returns new right edge -static S32 layout_widget(LLUICtrl* widget, S32 right) -{ - if (widget->getVisible()) - { - LLRect rect = widget->getRect(); - rect.mLeft = right - rect.getWidth(); - rect.mRight = right; - widget->setRect( rect ); - right -= rect.getWidth(); - } - return right; -} - -void LLLocationInputCtrl::refreshParcelIcons() -{ - // Our "cursor" moving right to left - S32 x = mAddLandmarkBtn->getRect().mLeft; - - LLViewerParcelMgr* vpm = LLViewerParcelMgr::getInstance(); - - LLViewerRegion* agent_region = gAgent.getRegion(); - LLParcel* agent_parcel = vpm->getAgentParcel(); - if (!agent_region || !agent_parcel) - return; - - mForSaleBtn->setVisible(vpm->canAgentBuyParcel(agent_parcel, false)); - - x = layout_widget(mForSaleBtn, x); - - if (gSavedSettings.getBOOL("NavBarShowParcelProperties")) - { - LLParcel* current_parcel; - LLViewerRegion* selection_region = vpm->getSelectionRegion(); - LLParcel* selected_parcel = vpm->getParcelSelection()->getParcel(); - - // If agent is in selected parcel we use its properties because - // they are updated more often by LLViewerParcelMgr than agent parcel properties. - // See LLViewerParcelMgr::processParcelProperties(). - // This is needed to reflect parcel restrictions changes without having to leave - // the parcel and then enter it again. See EXT-2987 - if (selected_parcel && selected_parcel->getLocalID() == agent_parcel->getLocalID() - && selection_region == agent_region) - { - current_parcel = selected_parcel; - } - else - { - current_parcel = agent_parcel; - } - - bool allow_voice = vpm->allowAgentVoice(agent_region, current_parcel); - bool allow_fly = vpm->allowAgentFly(agent_region, current_parcel); - bool allow_push = vpm->allowAgentPush(agent_region, current_parcel); - bool allow_build = vpm->allowAgentBuild(current_parcel); // true when anyone is allowed to build. See EXT-4610. - bool allow_scripts = vpm->allowAgentScripts(agent_region, current_parcel); - bool allow_damage = vpm->allowAgentDamage(agent_region, current_parcel); - bool see_avs = current_parcel->getSeeAVs(); - bool pathfinding_dynamic_enabled = agent_region->dynamicPathfindingEnabled(); - - // Most icons are "block this ability" - mParcelIcon[VOICE_ICON]->setVisible( !allow_voice ); - mParcelIcon[FLY_ICON]->setVisible( !allow_fly ); - mParcelIcon[PUSH_ICON]->setVisible( !allow_push ); - mParcelIcon[BUILD_ICON]->setVisible( !allow_build ); - mParcelIcon[SCRIPTS_ICON]->setVisible( !allow_scripts ); - mParcelIcon[DAMAGE_ICON]->setVisible( allow_damage ); - mParcelIcon[PATHFINDING_DIRTY_ICON]->setVisible(mIsNavMeshDirty); - mParcelIcon[PATHFINDING_DISABLED_ICON]->setVisible(!mIsNavMeshDirty && !pathfinding_dynamic_enabled); - - mDamageText->setVisible(allow_damage); - mParcelIcon[SEE_AVATARS_ICON]->setVisible( !see_avs ); - - // Padding goes to left of both landmark star and for sale btn - x -= mAddLandmarkHPad; - - // Slide the parcel icons rect from right to left, adjusting rectangles - for (S32 i = 0; i < ICON_COUNT; ++i) - { - x = layout_widget(mParcelIcon[i], x); - x -= mIconHPad; - } - x = layout_widget(mDamageText, x); - x -= mIconHPad; - } - else - { - for (S32 i = 0; i < ICON_COUNT; ++i) - { - mParcelIcon[i]->setVisible(false); - } - mDamageText->setVisible(false); - } - - if (mTextEntry) - { - S32 left_pad, right_pad; - mTextEntry->getTextPadding(&left_pad, &right_pad); - right_pad = mTextEntry->getRect().mRight - x; - mTextEntry->setTextPadding(left_pad, right_pad); - } -} - -void LLLocationInputCtrl::refreshHealth() -{ - // *FIXME: Status bar owns health information, should be in agent - if (gStatusBar) - { - static S32 last_health = -1; - S32 health = gStatusBar->getHealth(); - if (health != last_health) - { - std::string text = llformat("%d%%", health); - mDamageText->setText(text); - last_health = health; - } - } -} - -void LLLocationInputCtrl::refreshMaturityButton() -{ - // Updating maturity rating icon. - LLViewerRegion* region = gAgent.getRegion(); - if (!region) - return; - - bool button_visible = true; - LLPointer rating_image = NULL; - std::string rating_tooltip; - - U8 sim_access = region->getSimAccess(); - switch(sim_access) - { - case SIM_ACCESS_PG: - rating_image = mIconMaturityGeneral; - rating_tooltip = LLTrans::getString("LocationCtrlGeneralIconTooltip"); - break; - - case SIM_ACCESS_ADULT: - rating_image = mIconMaturityAdult; - rating_tooltip = LLTrans::getString("LocationCtrlAdultIconTooltip"); - break; - - case SIM_ACCESS_MATURE: - rating_image = mIconMaturityModerate; - rating_tooltip = LLTrans::getString("LocationCtrlModerateIconTooltip"); - break; - - default: - button_visible = false; - break; - } - - mMaturityButton->setVisible(button_visible); - mMaturityButton->setToolTip(rating_tooltip); - if(rating_image) - { - mMaturityButton->setImageUnselected(rating_image); - mMaturityButton->setImagePressed(rating_image); - } - if (mMaturityButton->getVisible()) - { - positionMaturityButton(); - } -} - -void LLLocationInputCtrl::positionMaturityButton() -{ - const LLFontGL* font = mTextEntry->getFont(); - if (!font) - return; - - S32 left_pad, right_pad; - mTextEntry->getTextPadding(&left_pad, &right_pad); - - // Calculate the right edge of rendered text + a whitespace. - left_pad = left_pad + font->getWidth(mTextEntry->getText()) + font->getWidth(" "); - - LLRect rect = mMaturityButton->getRect(); - mMaturityButton->setRect(rect.setOriginAndSize(left_pad, rect.mBottom, rect.getWidth(), rect.getHeight())); - - // Hide icon if it text area is not width enough to display it, show otherwise. - mMaturityButton->setVisible(rect.mRight < mTextEntry->getRect().getWidth() - right_pad); -} - -void LLLocationInputCtrl::addLocationHistoryEntry(const std::string& title, const LLSD& value) -{ - // SL-20286 : Duplication of autocomplete results occurs when entering some search queries in the navigation bar - // Exclude visual duplicates (items with the same titles) in the dropdown list - LLScrollListItem* item = mList->getItemByLabel(title); - if (!item) - { - add(title, value); - } -} - -void LLLocationInputCtrl::rebuildLocationHistory(const std::string& filter) -{ - LLLocationHistory::location_list_t filtered_items; - const LLLocationHistory::location_list_t* itemsp = NULL; - LLLocationHistory* lh = LLLocationHistory::getInstance(); - - if (filter.empty()) - { - itemsp = &lh->getItems(); - } - else - { - lh->getMatchingItems(filter, filtered_items); - itemsp = &filtered_items; - } - - removeall(); - for (LLLocationHistory::location_list_t::const_reverse_iterator it = itemsp->rbegin(); it != itemsp->rend(); it++) - { - LLSD value; - value["tooltip"] = it->getToolTip(); - //location history can contain only typed locations - value["item_type"] = TYPED_REGION_SLURL; - value["global_pos"] = it->mGlobalPos.getValue(); - addLocationHistoryEntry(it->getLocation(), value); - } -} - -void LLLocationInputCtrl::focusTextEntry() -{ - // We can't use "mTextEntry->setFocus(true)" instead because - // if the "select_on_focus" parameter is true it places the cursor - // at the beginning (after selecting text), thus screwing up updateSelection(). - if (mTextEntry) - { - gFocusMgr.setKeyboardFocus(mTextEntry); - - // Enable the text entry to handle accelerator keys (EXT-8104). - LLEditMenuHandler::gEditMenuHandler = mTextEntry; - } -} - -void LLLocationInputCtrl::enableAddLandmarkButton(bool val) -{ - // We don't want to disable the button because it should be click able at any time, - // instead switch images. - LLUIImage* img = val ? mLandmarkImageOn : mLandmarkImageOff; - if(img) - { - mAddLandmarkBtn->setImageUnselected(img); - } -} - -// Change the "Add landmark" button image -// depending on whether current parcel has been landmarked. -void LLLocationInputCtrl::updateAddLandmarkButton() -{ - enableAddLandmarkButton(LLLandmarkActions::hasParcelLandmark()); -} -void LLLocationInputCtrl::updateAddLandmarkTooltip() -{ - std::string tooltip; - if(LLLandmarkActions::landmarkAlreadyExists()) - { - tooltip = mEditLandmarkTooltip; - } - else - { - tooltip = mAddLandmarkTooltip; - } - mAddLandmarkBtn->setToolTip(tooltip); -} - -void LLLocationInputCtrl::updateContextMenu(){ - - if (mLocationContextMenu) - { - LLMenuItemGL* landmarkItem = mLocationContextMenu->getChild("Landmark"); - if (!LLLandmarkActions::landmarkAlreadyExists()) - { - landmarkItem->setLabel(LLTrans::getString("AddLandmarkNavBarMenu")); - } - else - { - landmarkItem->setLabel(LLTrans::getString("EditLandmarkNavBarMenu")); - } - } -} -void LLLocationInputCtrl::updateWidgetlayout() -{ - const LLRect& rect = getLocalRect(); - const LLRect& hist_btn_rect = mButton->getRect(); - - // Info button is set in the XUI XML location_input.xml - - // "Add Landmark" button - LLRect al_btn_rect = mAddLandmarkBtn->getRect(); - al_btn_rect.translate( - hist_btn_rect.mLeft - mIconHPad - al_btn_rect.getWidth(), - (rect.getHeight() - al_btn_rect.getHeight()) / 2); - mAddLandmarkBtn->setRect(al_btn_rect); -} - -void LLLocationInputCtrl::changeLocationPresentation() -{ - if (!mTextEntry) - return; - - //change location presentation only if user does not select/paste anything and - //human-readable region name is being displayed - if(!mTextEntry->hasSelection() && mTextEntry->getText() == mHumanReadableLocation) - { - //needs unescaped one - LLSLURL slurl; - LLAgentUI::buildSLURL(slurl, false); - mTextEntry->setText(LLURI::unescape(slurl.getSLURLString())); - mTextEntry->selectAll(); - - mMaturityButton->setVisible(false); - - isHumanReadableLocationVisible = false; - } -} - -void LLLocationInputCtrl::onLocationContextMenuItemClicked(const LLSD& userdata) -{ - std::string item = userdata.asString(); - - if (item == "show_coordinates") - { - gSavedSettings.setBOOL("NavBarShowCoordinates",!gSavedSettings.getBOOL("NavBarShowCoordinates")); - } - else if (item == "show_properties") - { - gSavedSettings.setBOOL("NavBarShowParcelProperties", - !gSavedSettings.getBOOL("NavBarShowParcelProperties")); - } - else if (item == "landmark") - { - LLViewerInventoryItem* landmark = LLLandmarkActions::findLandmarkForAgentPos(); - - if(!landmark) - { - LLFloaterReg::showInstance("add_landmark"); - } - else - { - LLFloaterSidePanelContainer::showPanel("places", LLSD().with("type", "landmark").with("id",landmark->getUUID())); - - } - } - else if (item == "cut") - { - mTextEntry->cut(); - } - else if (item == "copy") - { - mTextEntry->copy(); - } - else if (item == "paste") - { - mTextEntry->paste(); - } - else if (item == "delete") - { - mTextEntry->deleteSelection(); - } - else if (item == "select_all") - { - mTextEntry->selectAll(); - } -} - -bool LLLocationInputCtrl::onLocationContextMenuItemEnabled(const LLSD& userdata) -{ - std::string item = userdata.asString(); - - if (item == "can_cut") - { - return mTextEntry->canCut(); - } - else if (item == "can_copy") - { - return mTextEntry->canCopy(); - } - else if (item == "can_paste") - { - return mTextEntry->canPaste(); - } - else if (item == "can_delete") - { - return mTextEntry->canDeselect(); - } - else if (item == "can_select_all") - { - return mTextEntry->canSelectAll() && (mTextEntry->getLength() > 0); - } - else if(item == "show_coordinates") - { - return gSavedSettings.getBOOL("NavBarShowCoordinates"); - } - - return false; -} - -void LLLocationInputCtrl::callbackRebakeRegion(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) // OK - { - if (LLPathfindingManager::getInstance() != NULL) - { - LLMenuOptionPathfindingRebakeNavmesh::getInstance()->sendRequestRebakeNavmesh(); - } - } -} - -void LLLocationInputCtrl::onParcelIconClick(EParcelIcon icon) -{ - switch (icon) - { - case VOICE_ICON: - LLNotificationsUtil::add("NoVoice"); - break; - case FLY_ICON: - LLNotificationsUtil::add("NoFly"); - break; - case PUSH_ICON: - LLNotificationsUtil::add("PushRestricted"); - break; - case BUILD_ICON: - LLNotificationsUtil::add("NoBuild"); - break; - case PATHFINDING_DIRTY_ICON: - if (LLPathfindingManager::getInstance() != NULL) - { - LLMenuOptionPathfindingRebakeNavmesh *rebakeInstance = LLMenuOptionPathfindingRebakeNavmesh::getInstance(); - if (rebakeInstance && rebakeInstance->canRebakeRegion() && (rebakeInstance->getMode() == LLMenuOptionPathfindingRebakeNavmesh::kRebakeNavMesh_Available)) - { - LLNotificationsUtil::add("PathfindingDirtyRebake", LLSD(), LLSD(), - boost::bind(&LLLocationInputCtrl::callbackRebakeRegion, this, _1, _2)); - break; - } - } - LLNotificationsUtil::add("PathfindingDirty"); - break; - case PATHFINDING_DISABLED_ICON: - LLNotificationsUtil::add("DynamicPathfindingDisabled"); - break; - case SCRIPTS_ICON: - { - LLViewerRegion* region = gAgent.getRegion(); - if(region && region->getRegionFlag(REGION_FLAGS_ESTATE_SKIP_SCRIPTS)) - { - LLNotificationsUtil::add("ScriptsStopped"); - } - else if(region && region->getRegionFlag(REGION_FLAGS_SKIP_SCRIPTS)) - { - LLNotificationsUtil::add("ScriptsNotRunning"); - } - else - { - LLNotificationsUtil::add("NoOutsideScripts"); - } - break; - } - case DAMAGE_ICON: - LLNotificationsUtil::add("NotSafe"); - break; - case SEE_AVATARS_ICON: - LLNotificationsUtil::add("SeeAvatars"); - break; - case ICON_COUNT: - break; - // no default to get compiler warning when a new icon gets added - } -} - -void LLLocationInputCtrl::createNavMeshStatusListenerForCurrentRegion() -{ - if (mNavMeshSlot.connected()) - { - mNavMeshSlot.disconnect(); - } - - LLViewerRegion *currentRegion = gAgent.getRegion(); - if (currentRegion != NULL) - { - mNavMeshSlot = LLPathfindingManager::getInstance()->registerNavMeshListenerForRegion(currentRegion, boost::bind(&LLLocationInputCtrl::onNavMeshStatusChange, this, _2)); - LLPathfindingManager::getInstance()->requestGetNavMeshForRegion(currentRegion, true); - } -} +/** + * @file lllocationinputctrl.cpp + * @brief Combobox-like location input control + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +// file includes +#include "lllocationinputctrl.h" + +// common includes +#include "llbutton.h" +#include "llfocusmgr.h" +#include "llhelp.h" +#include "llmenugl.h" +#include "llparcel.h" +#include "llstring.h" +#include "lltrans.h" +#include "lluictrlfactory.h" +#include "lltooltip.h" +#include "llnotificationsutil.h" +#include "llregionflags.h" + +// newview includes +#include "llagent.h" +#include "llfloaterreg.h" +#include "llfloatersidepanelcontainer.h" +#include "llinventoryobserver.h" +#include "lllandmarkactions.h" +#include "lllandmarklist.h" +#include "llpathfindingmanager.h" +#include "llpathfindingnavmesh.h" +#include "llpathfindingnavmeshstatus.h" +#include "llteleporthistory.h" +#include "llslurl.h" +#include "llstatusbar.h" // getHealth() +#include "lltrans.h" +#include "llviewerinventory.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "llviewercontrol.h" +#include "llviewermenu.h" +#include "llurllineeditorctrl.h" +#include "llagentui.h" + +#include "llmenuoptionpathfindingrebakenavmesh.h" +#include "llpathfindingmanager.h" + +//============================================================================ +/* + * "ADD LANDMARK" BUTTON UPDATING LOGIC + * + * If the current parcel has been landmarked, we should draw + * a special image on the button. + * + * To avoid determining the appropriate image on every draw() we do that + * only in the following cases: + * 1) Navbar is shown for the first time after login. + * 2) Agent moves to another parcel. + * 3) A landmark is created or removed. + * + * The first case is handled by the handleLoginComplete() method. + * + * The second case is handled by setting the "agent parcel changed" callback + * on LLViewerParcelMgr. + * + * The third case is the most complex one. We have two inventory observers for that: + * one is designated to handle adding landmarks, the other handles removal. + * Let's see how the former works. + * + * When we get notified about landmark addition, the landmark position is unknown yet. What we can + * do at that point is initiate loading the landmark data by LLLandmarkList and set the + * "loading finished" callback on it. Finally, when the callback is triggered, + * we can determine whether the landmark refers to a point within the current parcel + * and choose the appropriate image for the "Add landmark" button. + */ + +/** + * Initiates loading the landmarks that have been just added. + * + * Once the loading is complete we'll be notified + * with the callback we set for LLLandmarkList. + */ +class LLAddLandmarkObserver : public LLInventoryAddedObserver +{ +public: + LLAddLandmarkObserver(LLLocationInputCtrl* input) : mInput(input) {} + +private: + /*virtual*/ void done() + { + const uuid_set_t& added = gInventory.getAddedIDs(); + for (uuid_set_t::const_iterator it = added.begin(); it != added.end(); ++it) + { + LLInventoryItem* item = gInventory.getItem(*it); + if (!item || item->getType() != LLAssetType::AT_LANDMARK) + continue; + + // Start loading the landmark. + LLLandmark* lm = gLandmarkList.getAsset( + item->getAssetUUID(), + boost::bind(&LLLocationInputCtrl::onLandmarkLoaded, mInput, _1)); + if (lm) + { + // Already loaded? Great, handle it immediately (the callback won't be called). + mInput->onLandmarkLoaded(lm); + } + } + } + + LLLocationInputCtrl* mInput; +}; + +/** + * Updates the "Add landmark" button once a landmark gets removed. + */ +class LLRemoveLandmarkObserver : public LLInventoryObserver +{ +public: + LLRemoveLandmarkObserver(LLLocationInputCtrl* input) : mInput(input) {} + +private: + /*virtual*/ void changed(U32 mask) + { + if (mask & (~(LLInventoryObserver::LABEL| + LLInventoryObserver::INTERNAL| + LLInventoryObserver::ADD| + LLInventoryObserver::CREATE| + LLInventoryObserver::UPDATE_CREATE))) + { + mInput->updateAddLandmarkButton(); + } + } + + LLLocationInputCtrl* mInput; +}; + +class LLParcelChangeObserver : public LLParcelObserver +{ +public: + LLParcelChangeObserver(LLLocationInputCtrl* input) : mInput(input) {} + +private: + /*virtual*/ void changed() + { + if (mInput) + { + mInput->refreshParcelIcons(); + } + } + + LLLocationInputCtrl* mInput; +}; + +//============================================================================ + + +static LLDefaultChildRegistry::Register r("location_input"); + +LLLocationInputCtrl::Params::Params() +: icon_maturity_general("icon_maturity_general"), + icon_maturity_adult("icon_maturity_adult"), + icon_maturity_moderate("icon_maturity_moderate"), + add_landmark_image_enabled("add_landmark_image_enabled"), + add_landmark_image_disabled("add_landmark_image_disabled"), + add_landmark_image_hover("add_landmark_image_hover"), + add_landmark_image_selected("add_landmark_image_selected"), + add_landmark_hpad("add_landmark_hpad", 0), + icon_hpad("icon_hpad", 0), + add_landmark_button("add_landmark_button"), + for_sale_button("for_sale_button"), + info_button("info_button"), + maturity_button("maturity_button"), + voice_icon("voice_icon"), + fly_icon("fly_icon"), + push_icon("push_icon"), + build_icon("build_icon"), + scripts_icon("scripts_icon"), + damage_icon("damage_icon"), + damage_text("damage_text"), + see_avatars_icon("see_avatars_icon"), + maturity_help_topic("maturity_help_topic"), + pathfinding_dirty_icon("pathfinding_dirty_icon"), + pathfinding_disabled_icon("pathfinding_disabled_icon") +{ +} + +LLLocationInputCtrl::LLLocationInputCtrl(const LLLocationInputCtrl::Params& p) +: LLComboBox(p), + mIconHPad(p.icon_hpad), + mAddLandmarkHPad(p.add_landmark_hpad), + mLocationContextMenu(NULL), + mAddLandmarkBtn(NULL), + mForSaleBtn(NULL), + mInfoBtn(NULL), + mRegionCrossingSlot(), + mNavMeshSlot(), + mIsNavMeshDirty(false), + mLandmarkImageOn(NULL), + mLandmarkImageOff(NULL), + mIconMaturityGeneral(NULL), + mIconMaturityAdult(NULL), + mIconMaturityModerate(NULL), + mMaturityHelpTopic(p.maturity_help_topic) +{ + // Lets replace default LLLineEditor with LLLocationLineEditor + // to make needed escaping while copying and cutting url + delete mTextEntry; + + // Can't access old mTextEntry fields as they are protected, so lets build new params + // That is C&P from LLComboBox::createLineEditor function + S32 arrow_width = mArrowImage ? mArrowImage->getWidth() : 0; + LLRect text_entry_rect(0, getRect().getHeight(), getRect().getWidth(), 0); + text_entry_rect.mRight -= llmax(8,arrow_width) + 2 * BTN_DROP_SHADOW; + + LLLineEditor::Params params = p.combo_editor; + params.rect(text_entry_rect); + params.default_text(LLStringUtil::null); + params.max_length.bytes(p.max_chars); + params.keystroke_callback(boost::bind(&LLLocationInputCtrl::onTextEntry, this, _1)); + params.commit_on_focus_lost(false); + params.follows.flags(FOLLOWS_ALL); + mTextEntry = LLUICtrlFactory::create(params); + mTextEntry->resetContextMenu(); + addChild(mTextEntry); + // LLLineEditor is replaced with LLLocationLineEditor + + // "Place information" button. + LLButton::Params info_params = p.info_button; + mInfoBtn = LLUICtrlFactory::create(info_params); + mInfoBtn->setClickedCallback(boost::bind(&LLLocationInputCtrl::onInfoButtonClicked, this)); + addChild(mInfoBtn); + + // "Add landmark" button. + LLButton::Params al_params = p.add_landmark_button; + + // Image for unselected state will be set in updateAddLandmarkButton(), + // it will be either mLandmarkOn or mLandmarkOff + if (p.add_landmark_image_enabled()) + { + mLandmarkImageOn = p.add_landmark_image_enabled; + } + if (p.add_landmark_image_disabled()) + { + mLandmarkImageOff = p.add_landmark_image_disabled; + } + + if(p.add_landmark_image_selected) + { + al_params.image_selected = p.add_landmark_image_selected; + } + if (p.add_landmark_image_hover()) + { + al_params.image_hover_unselected = p.add_landmark_image_hover; + } + + al_params.click_callback.function(boost::bind(&LLLocationInputCtrl::onAddLandmarkButtonClicked, this)); + mAddLandmarkBtn = LLUICtrlFactory::create(al_params); + enableAddLandmarkButton(true); + addChild(mAddLandmarkBtn); + + if (p.icon_maturity_general()) + { + mIconMaturityGeneral = p.icon_maturity_general; + } + if (p.icon_maturity_adult()) + { + mIconMaturityAdult = p.icon_maturity_adult; + } + if(p.icon_maturity_moderate()) + { + mIconMaturityModerate = p.icon_maturity_moderate; + } + + LLButton::Params maturity_button = p.maturity_button; + mMaturityButton = LLUICtrlFactory::create(maturity_button); + addChild(mMaturityButton); + + LLButton::Params for_sale_button = p.for_sale_button; + for_sale_button.tool_tip = LLTrans::getString("LocationCtrlForSaleTooltip"); + for_sale_button.click_callback.function( + boost::bind(&LLLocationInputCtrl::onForSaleButtonClicked, this)); + mForSaleBtn = LLUICtrlFactory::create( for_sale_button ); + addChild(mForSaleBtn); + + // Parcel property icons + // Must be mouse-opaque so cursor stays as an arrow when hovering to + // see tooltip. + LLIconCtrl::Params voice_icon = p.voice_icon; + voice_icon.tool_tip = LLTrans::getString("LocationCtrlVoiceTooltip"); + voice_icon.mouse_opaque = true; + mParcelIcon[VOICE_ICON] = LLUICtrlFactory::create(voice_icon); + mParcelIcon[VOICE_ICON]->setMouseDownCallback(boost::bind(&LLLocationInputCtrl::onParcelIconClick, this, VOICE_ICON)); + addChild(mParcelIcon[VOICE_ICON]); + + LLIconCtrl::Params fly_icon = p.fly_icon; + fly_icon.tool_tip = LLTrans::getString("LocationCtrlFlyTooltip"); + fly_icon.mouse_opaque = true; + mParcelIcon[FLY_ICON] = LLUICtrlFactory::create(fly_icon); + mParcelIcon[FLY_ICON]->setMouseDownCallback(boost::bind(&LLLocationInputCtrl::onParcelIconClick, this, FLY_ICON)); + addChild(mParcelIcon[FLY_ICON]); + + LLIconCtrl::Params push_icon = p.push_icon; + push_icon.tool_tip = LLTrans::getString("LocationCtrlPushTooltip"); + push_icon.mouse_opaque = true; + mParcelIcon[PUSH_ICON] = LLUICtrlFactory::create(push_icon); + mParcelIcon[PUSH_ICON]->setMouseDownCallback(boost::bind(&LLLocationInputCtrl::onParcelIconClick, this, PUSH_ICON)); + addChild(mParcelIcon[PUSH_ICON]); + + LLIconCtrl::Params build_icon = p.build_icon; + build_icon.tool_tip = LLTrans::getString("LocationCtrlBuildTooltip"); + build_icon.mouse_opaque = true; + mParcelIcon[BUILD_ICON] = LLUICtrlFactory::create(build_icon); + mParcelIcon[BUILD_ICON]->setMouseDownCallback(boost::bind(&LLLocationInputCtrl::onParcelIconClick, this, BUILD_ICON)); + addChild(mParcelIcon[BUILD_ICON]); + + LLIconCtrl::Params scripts_icon = p.scripts_icon; + scripts_icon.tool_tip = LLTrans::getString("LocationCtrlScriptsTooltip"); + scripts_icon.mouse_opaque = true; + mParcelIcon[SCRIPTS_ICON] = LLUICtrlFactory::create(scripts_icon); + mParcelIcon[SCRIPTS_ICON]->setMouseDownCallback(boost::bind(&LLLocationInputCtrl::onParcelIconClick, this, SCRIPTS_ICON)); + addChild(mParcelIcon[SCRIPTS_ICON]); + + LLIconCtrl::Params damage_icon = p.damage_icon; + damage_icon.tool_tip = LLTrans::getString("LocationCtrlDamageTooltip"); + damage_icon.mouse_opaque = true; + mParcelIcon[DAMAGE_ICON] = LLUICtrlFactory::create(damage_icon); + mParcelIcon[DAMAGE_ICON]->setMouseDownCallback(boost::bind(&LLLocationInputCtrl::onParcelIconClick, this, DAMAGE_ICON)); + addChild(mParcelIcon[DAMAGE_ICON]); + + LLIconCtrl::Params pathfinding_dirty_icon = p.pathfinding_dirty_icon; + pathfinding_dirty_icon.tool_tip = LLTrans::getString("LocationCtrlPathfindingDirtyTooltip"); + pathfinding_dirty_icon.mouse_opaque = true; + mParcelIcon[PATHFINDING_DIRTY_ICON] = LLUICtrlFactory::create(pathfinding_dirty_icon); + mParcelIcon[PATHFINDING_DIRTY_ICON]->setMouseDownCallback(boost::bind(&LLLocationInputCtrl::onParcelIconClick, this, PATHFINDING_DIRTY_ICON)); + addChild(mParcelIcon[PATHFINDING_DIRTY_ICON]); + + LLIconCtrl::Params pathfinding_disabled_icon = p.pathfinding_disabled_icon; + pathfinding_disabled_icon.tool_tip = LLTrans::getString("LocationCtrlPathfindingDisabledTooltip"); + pathfinding_disabled_icon.mouse_opaque = true; + mParcelIcon[PATHFINDING_DISABLED_ICON] = LLUICtrlFactory::create(pathfinding_disabled_icon); + mParcelIcon[PATHFINDING_DISABLED_ICON]->setMouseDownCallback(boost::bind(&LLLocationInputCtrl::onParcelIconClick, this, PATHFINDING_DISABLED_ICON)); + addChild(mParcelIcon[PATHFINDING_DISABLED_ICON]); + + LLTextBox::Params damage_text = p.damage_text; + damage_text.tool_tip = LLTrans::getString("LocationCtrlDamageTooltip"); + damage_text.mouse_opaque = true; + mDamageText = LLUICtrlFactory::create(damage_text); + addChild(mDamageText); + + LLIconCtrl::Params see_avatars_icon = p.see_avatars_icon; + see_avatars_icon.tool_tip = LLTrans::getString("LocationCtrlSeeAVsTooltip"); + see_avatars_icon.mouse_opaque = true; + mParcelIcon[SEE_AVATARS_ICON] = LLUICtrlFactory::create(see_avatars_icon); + mParcelIcon[SEE_AVATARS_ICON]->setMouseDownCallback(boost::bind(&LLLocationInputCtrl::onParcelIconClick, this, SEE_AVATARS_ICON)); + addChild(mParcelIcon[SEE_AVATARS_ICON]); + + // Register callbacks and load the location field context menu (NB: the order matters). + LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Navbar.Action", boost::bind(&LLLocationInputCtrl::onLocationContextMenuItemClicked, this, _2)); + LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Navbar.EnableMenuItem", boost::bind(&LLLocationInputCtrl::onLocationContextMenuItemEnabled, this, _2)); + + setPrearrangeCallback(boost::bind(&LLLocationInputCtrl::onLocationPrearrange, this, _2)); + getTextEntry()->setMouseUpCallback(boost::bind(&LLLocationInputCtrl::changeLocationPresentation, this)); + + // Load the location field context menu + mLocationContextMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_navbar.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + if (!mLocationContextMenu) + { + LL_WARNS() << "Error loading navigation bar context menu" << LL_ENDL; + + } + //don't show default context menu + getTextEntry()->setShowContextMenu(false); + getTextEntry()->setRightMouseDownCallback(boost::bind(&LLLocationInputCtrl::onTextEditorRightClicked, this, _2, _3, _4)); + updateWidgetlayout(); + + // Connecting signal for updating location on "Show Coordinates" setting change. + LLControlVariable* coordinates_control = gSavedSettings.getControl("NavBarShowCoordinates").get(); + if (coordinates_control) + { + mCoordinatesControlConnection = coordinates_control->getSignal()->connect(boost::bind(&LLLocationInputCtrl::refreshLocation, this)); + } + + // Connecting signal for updating parcel icons on "Show Parcel Properties" setting change. + LLControlVariable* parcel_properties_control = gSavedSettings.getControl("NavBarShowParcelProperties").get(); + if (parcel_properties_control) + { + mParcelPropertiesControlConnection = parcel_properties_control->getSignal()->connect(boost::bind(&LLLocationInputCtrl::refreshParcelIcons, this)); + } + + // - Make the "Add landmark" button updated when either current parcel gets changed + // or a landmark gets created or removed from the inventory. + // - Update the location string on parcel change. + mParcelMgrConnection = gAgent.addParcelChangedCallback( + boost::bind(&LLLocationInputCtrl::onAgentParcelChange, this)); + // LLLocationHistory instance is being created before the location input control, so we have to update initial state of button manually. + mButton->setEnabled(LLLocationHistory::instance().getItemCount() > 0); + mLocationHistoryConnection = LLLocationHistory::getInstance()->setChangedCallback( + boost::bind(&LLLocationInputCtrl::onLocationHistoryChanged, this,_1)); + + mRegionCrossingSlot = gAgent.addRegionChangedCallback(boost::bind(&LLLocationInputCtrl::onRegionBoundaryCrossed, this)); + createNavMeshStatusListenerForCurrentRegion(); + + mRemoveLandmarkObserver = new LLRemoveLandmarkObserver(this); + mAddLandmarkObserver = new LLAddLandmarkObserver(this); + gInventory.addObserver(mRemoveLandmarkObserver); + gInventory.addObserver(mAddLandmarkObserver); + + mParcelChangeObserver = new LLParcelChangeObserver(this); + LLViewerParcelMgr::getInstance()->addObserver(mParcelChangeObserver); + + mAddLandmarkTooltip = LLTrans::getString("LocationCtrlAddLandmarkTooltip"); + mEditLandmarkTooltip = LLTrans::getString("LocationCtrlEditLandmarkTooltip"); + mButton->setToolTip(LLTrans::getString("LocationCtrlComboBtnTooltip")); + mInfoBtn->setToolTip(LLTrans::getString("LocationCtrlInfoBtnTooltip")); +} + +LLLocationInputCtrl::~LLLocationInputCtrl() +{ + gInventory.removeObserver(mRemoveLandmarkObserver); + gInventory.removeObserver(mAddLandmarkObserver); + delete mRemoveLandmarkObserver; + delete mAddLandmarkObserver; + + LLViewerParcelMgr::getInstance()->removeObserver(mParcelChangeObserver); + delete mParcelChangeObserver; + + mRegionCrossingSlot.disconnect(); + mNavMeshSlot.disconnect(); + mCoordinatesControlConnection.disconnect(); + mParcelPropertiesControlConnection.disconnect(); + mParcelMgrConnection.disconnect(); + mLocationHistoryConnection.disconnect(); +} + +void LLLocationInputCtrl::setEnabled(bool enabled) +{ + LLComboBox::setEnabled(enabled); + mAddLandmarkBtn->setEnabled(enabled); +} + +void LLLocationInputCtrl::hideList() +{ + LLComboBox::hideList(); + if (mTextEntry && hasFocus()) + focusTextEntry(); +} + +bool LLLocationInputCtrl::handleToolTip(S32 x, S32 y, MASK mask) +{ + + if(mAddLandmarkBtn->parentPointInView(x,y)) + { + updateAddLandmarkTooltip(); + } + // Let the buttons show their tooltips. + if (LLUICtrl::handleToolTip(x, y, mask)) + { + if (mList->getRect().pointInRect(x, y)) + { + S32 loc_x, loc_y; + //x,y - contain coordinates related to the location input control, but without taking the expanded list into account + //So we have to convert it again into local coordinates of mList + localPointToOtherView(x,y,&loc_x,&loc_y,mList); + + LLScrollListItem* item = mList->hitItem(loc_x,loc_y); + if (item) + { + LLSD value = item->getValue(); + if (value.has("tooltip")) + { + LLToolTipMgr::instance().show(value["tooltip"]); + } + } + } + + return true; + } + + return false; +} + +bool LLLocationInputCtrl::handleKeyHere(KEY key, MASK mask) +{ + bool result = LLComboBox::handleKeyHere(key, mask); + + if (key == KEY_DOWN && hasFocus() && mList->getItemCount() != 0 && !mList->getVisible()) + { + showList(); + } + + return result; +} + +void LLLocationInputCtrl::onTextEntry(LLLineEditor* line_editor) +{ + KEY key = gKeyboard->currentKey(); + MASK mask = gKeyboard->currentMask(true); + + // Typing? (moving cursor should not affect showing the list) + bool typing = mask != MASK_CONTROL && key != KEY_LEFT && key != KEY_RIGHT && key != KEY_HOME && key != KEY_END; + bool pasting = mask == MASK_CONTROL && key == 'V'; + + if (line_editor->getText().empty()) + { + prearrangeList(); // resets filter + hideList(); + } + else if (typing || pasting) + { + prearrangeList(line_editor->getText()); + if (mList->getItemCount() != 0) + { + showList(); + focusTextEntry(); + } + else + { + // Hide the list if it's empty. + hideList(); + } + } + + LLComboBox::onTextEntry(line_editor); +} + +/** + * Useful if we want to just set the text entry value, no matter what the list contains. + * + * This is faster than setTextEntry(). + */ +void LLLocationInputCtrl::setText(const LLStringExplicit& text) +{ + if (mTextEntry) + { + mTextEntry->setText(text); + } + mHasAutocompletedText = false; +} + +void LLLocationInputCtrl::setFocus(bool b) +{ + LLComboBox::setFocus(b); + + if (mTextEntry && b && !mList->getVisible()) + { + mTextEntry->setFocus(true); + } +} + +void LLLocationInputCtrl::handleLoginComplete() +{ + // An agent parcel update hasn't occurred yet, so we have to + // manually set location and the appropriate "Add landmark" icon. + refresh(); +} + +//== private methods ========================================================= + +void LLLocationInputCtrl::onFocusReceived() +{ + prearrangeList(); +} + +void LLLocationInputCtrl::onFocusLost() +{ + LLUICtrl::onFocusLost(); + refreshLocation(); + + // Setting cursor to 0 to show the left edge of the text. See STORM-370. + mTextEntry->setCursor(0); + + if(mTextEntry->hasSelection()){ + mTextEntry->deselect(); + } +} + +void LLLocationInputCtrl::draw() +{ + static LLUICachedControl show_coords("NavBarShowCoordinates", false); + if(!hasFocus() && show_coords) + { + refreshLocation(); + } + + static LLUICachedControl show_icons("NavBarShowParcelProperties", false); + if (show_icons) + { + refreshHealth(); + } + LLComboBox::draw(); +} + +void LLLocationInputCtrl::reshape(S32 width, S32 height, bool called_from_parent) +{ + LLComboBox::reshape(width, height, called_from_parent); + + // Setting cursor to 0 to show the left edge of the text. See EXT-4967. + mTextEntry->setCursor(0); + if (mTextEntry->hasSelection()) + { + // Deselecting because selection position is changed together with + // cursor position change. + mTextEntry->deselect(); + } + + if (isHumanReadableLocationVisible) + { + refreshMaturityButton(); + } +} + +void LLLocationInputCtrl::onInfoButtonClicked() +{ + LLFloaterSidePanelContainer::showPanel("places", LLSD().with("type", "agent")); +} + +void LLLocationInputCtrl::onForSaleButtonClicked() +{ + handle_buy_land(); +} + +void LLLocationInputCtrl::onAddLandmarkButtonClicked() +{ + LLViewerInventoryItem* landmark = LLLandmarkActions::findLandmarkForAgentPos(); + // Landmark exists, open it for preview and edit + if(landmark && landmark->getUUID().notNull()) + { + LLSD key; + key["type"] = "landmark"; + key["id"] = landmark->getUUID(); + + LLFloaterSidePanelContainer::showPanel("places", key); + } + else + { + LLFloaterReg::showInstance("add_landmark"); + } +} + +void LLLocationInputCtrl::onAgentParcelChange() +{ + refresh(); +} + +void LLLocationInputCtrl::onRegionBoundaryCrossed() +{ + createNavMeshStatusListenerForCurrentRegion(); +} + +void LLLocationInputCtrl::onNavMeshStatusChange(const LLPathfindingNavMeshStatus &pNavMeshStatus) +{ + mIsNavMeshDirty = pNavMeshStatus.isValid() && (pNavMeshStatus.getStatus() != LLPathfindingNavMeshStatus::kComplete); + refreshParcelIcons(); +} + +void LLLocationInputCtrl::onLandmarkLoaded(LLLandmark* lm) +{ + (void) lm; + updateAddLandmarkButton(); +} + +void LLLocationInputCtrl::onLocationHistoryChanged(LLLocationHistory::EChangeType event) +{ + if(event == LLLocationHistory::LOAD) + { + rebuildLocationHistory(); + } + mButton->setEnabled(LLLocationHistory::instance().getItemCount() > 0); +} + +void LLLocationInputCtrl::onLocationPrearrange(const LLSD& data) +{ + std::string filter = data.asString(); + rebuildLocationHistory(filter); + + //Let's add landmarks to the top of the list if any + if(!filter.empty() ) + { + LLInventoryModel::item_array_t landmark_items = LLLandmarkActions::fetchLandmarksByName(filter, true); + + for(U32 i=0; i < landmark_items.size(); i++) + { + LLSD value; + //TODO:: DO we need tooltip for Landmark?? + + value["item_type"] = LANDMARK; + value["AssetUUID"] = landmark_items[i]->getAssetUUID(); + addLocationHistoryEntry(landmark_items[i]->getName(), value); + + } + //Let's add teleport history items + LLTeleportHistory* th = LLTeleportHistory::getInstance(); + LLTeleportHistory::slurl_list_t th_items = th->getItems(); + + std::set new_item_titles;// duplicate control + LLTeleportHistory::slurl_list_t::iterator result = std::find_if( + th_items.begin(), th_items.end(), boost::bind( + &LLLocationInputCtrl::findTeleportItemsByTitle, this, + _1, filter)); + + while (result != th_items.end()) + { + //mTitile format - region_name[, parcel_name] + //mFullTitile format - region_name[, parcel_name] (local_x,local_y, local_z) + if (new_item_titles.insert(result->mFullTitle).second) + { + LLSD value; + value["item_type"] = TELEPORT_HISTORY; + value["global_pos"] = result->mGlobalPos.getValue(); + std::string region_name = result->mTitle.substr(0, result->mTitle.find(',')); + //TODO*: add Surl to teleportitem or parse region name from title + value["tooltip"] = LLSLURL(region_name, result->mGlobalPos).getSLURLString(); + addLocationHistoryEntry(result->getTitle(), value); + } + result = std::find_if(result + 1, th_items.end(), boost::bind( + &LLLocationInputCtrl::findTeleportItemsByTitle, this, + _1, filter)); + } + } + sortByName(); + + mList->mouseOverHighlightNthItem(-1); // Clear highlight on the last selected item. +} + +bool LLLocationInputCtrl::findTeleportItemsByTitle(const LLTeleportHistoryItem& item, const std::string& filter) +{ + return item.mTitle.find(filter) != std::string::npos; +} + +void LLLocationInputCtrl::onTextEditorRightClicked(S32 x, S32 y, MASK mask) +{ + if (mLocationContextMenu) + { + updateContextMenu(); + mLocationContextMenu->buildDrawLabels(); + mLocationContextMenu->updateParent(LLMenuGL::sMenuContainer); + hideList(); + setFocus(true); + changeLocationPresentation(); + LLMenuGL::showPopup(this, mLocationContextMenu, x, y); + } +} + +void LLLocationInputCtrl::refresh() +{ + refreshLocation(); // update location string + refreshParcelIcons(); + updateAddLandmarkButton(); // indicate whether current parcel has been landmarked +} + +void LLLocationInputCtrl::refreshLocation() +{ + // Is one of our children focused? + if (LLUICtrl::hasFocus() || mButton->hasFocus() || mList->hasFocus() || + (mTextEntry && mTextEntry->hasFocus()) || + (mAddLandmarkBtn->hasFocus())) + { + LL_WARNS() << "Location input should not be refreshed when having focus" << LL_ENDL; + return; + } + + // Update location field. + std::string location_name; + LLAgentUI::ELocationFormat format = + (gSavedSettings.getBOOL("NavBarShowCoordinates") + ? LLAgentUI::LOCATION_FORMAT_FULL + : LLAgentUI::LOCATION_FORMAT_NO_COORDS); + + if (!LLAgentUI::buildLocationString(location_name, format)) + { + location_name = "???"; + } + // store human-readable location to compare it in changeLocationPresentation() + mHumanReadableLocation = location_name; + setText(location_name); + isHumanReadableLocationVisible = true; + + refreshMaturityButton(); +} + +// returns new right edge +static S32 layout_widget(LLUICtrl* widget, S32 right) +{ + if (widget->getVisible()) + { + LLRect rect = widget->getRect(); + rect.mLeft = right - rect.getWidth(); + rect.mRight = right; + widget->setRect( rect ); + right -= rect.getWidth(); + } + return right; +} + +void LLLocationInputCtrl::refreshParcelIcons() +{ + // Our "cursor" moving right to left + S32 x = mAddLandmarkBtn->getRect().mLeft; + + LLViewerParcelMgr* vpm = LLViewerParcelMgr::getInstance(); + + LLViewerRegion* agent_region = gAgent.getRegion(); + LLParcel* agent_parcel = vpm->getAgentParcel(); + if (!agent_region || !agent_parcel) + return; + + mForSaleBtn->setVisible(vpm->canAgentBuyParcel(agent_parcel, false)); + + x = layout_widget(mForSaleBtn, x); + + if (gSavedSettings.getBOOL("NavBarShowParcelProperties")) + { + LLParcel* current_parcel; + LLViewerRegion* selection_region = vpm->getSelectionRegion(); + LLParcel* selected_parcel = vpm->getParcelSelection()->getParcel(); + + // If agent is in selected parcel we use its properties because + // they are updated more often by LLViewerParcelMgr than agent parcel properties. + // See LLViewerParcelMgr::processParcelProperties(). + // This is needed to reflect parcel restrictions changes without having to leave + // the parcel and then enter it again. See EXT-2987 + if (selected_parcel && selected_parcel->getLocalID() == agent_parcel->getLocalID() + && selection_region == agent_region) + { + current_parcel = selected_parcel; + } + else + { + current_parcel = agent_parcel; + } + + bool allow_voice = vpm->allowAgentVoice(agent_region, current_parcel); + bool allow_fly = vpm->allowAgentFly(agent_region, current_parcel); + bool allow_push = vpm->allowAgentPush(agent_region, current_parcel); + bool allow_build = vpm->allowAgentBuild(current_parcel); // true when anyone is allowed to build. See EXT-4610. + bool allow_scripts = vpm->allowAgentScripts(agent_region, current_parcel); + bool allow_damage = vpm->allowAgentDamage(agent_region, current_parcel); + bool see_avs = current_parcel->getSeeAVs(); + bool pathfinding_dynamic_enabled = agent_region->dynamicPathfindingEnabled(); + + // Most icons are "block this ability" + mParcelIcon[VOICE_ICON]->setVisible( !allow_voice ); + mParcelIcon[FLY_ICON]->setVisible( !allow_fly ); + mParcelIcon[PUSH_ICON]->setVisible( !allow_push ); + mParcelIcon[BUILD_ICON]->setVisible( !allow_build ); + mParcelIcon[SCRIPTS_ICON]->setVisible( !allow_scripts ); + mParcelIcon[DAMAGE_ICON]->setVisible( allow_damage ); + mParcelIcon[PATHFINDING_DIRTY_ICON]->setVisible(mIsNavMeshDirty); + mParcelIcon[PATHFINDING_DISABLED_ICON]->setVisible(!mIsNavMeshDirty && !pathfinding_dynamic_enabled); + + mDamageText->setVisible(allow_damage); + mParcelIcon[SEE_AVATARS_ICON]->setVisible( !see_avs ); + + // Padding goes to left of both landmark star and for sale btn + x -= mAddLandmarkHPad; + + // Slide the parcel icons rect from right to left, adjusting rectangles + for (S32 i = 0; i < ICON_COUNT; ++i) + { + x = layout_widget(mParcelIcon[i], x); + x -= mIconHPad; + } + x = layout_widget(mDamageText, x); + x -= mIconHPad; + } + else + { + for (S32 i = 0; i < ICON_COUNT; ++i) + { + mParcelIcon[i]->setVisible(false); + } + mDamageText->setVisible(false); + } + + if (mTextEntry) + { + S32 left_pad, right_pad; + mTextEntry->getTextPadding(&left_pad, &right_pad); + right_pad = mTextEntry->getRect().mRight - x; + mTextEntry->setTextPadding(left_pad, right_pad); + } +} + +void LLLocationInputCtrl::refreshHealth() +{ + // *FIXME: Status bar owns health information, should be in agent + if (gStatusBar) + { + static S32 last_health = -1; + S32 health = gStatusBar->getHealth(); + if (health != last_health) + { + std::string text = llformat("%d%%", health); + mDamageText->setText(text); + last_health = health; + } + } +} + +void LLLocationInputCtrl::refreshMaturityButton() +{ + // Updating maturity rating icon. + LLViewerRegion* region = gAgent.getRegion(); + if (!region) + return; + + bool button_visible = true; + LLPointer rating_image = NULL; + std::string rating_tooltip; + + U8 sim_access = region->getSimAccess(); + switch(sim_access) + { + case SIM_ACCESS_PG: + rating_image = mIconMaturityGeneral; + rating_tooltip = LLTrans::getString("LocationCtrlGeneralIconTooltip"); + break; + + case SIM_ACCESS_ADULT: + rating_image = mIconMaturityAdult; + rating_tooltip = LLTrans::getString("LocationCtrlAdultIconTooltip"); + break; + + case SIM_ACCESS_MATURE: + rating_image = mIconMaturityModerate; + rating_tooltip = LLTrans::getString("LocationCtrlModerateIconTooltip"); + break; + + default: + button_visible = false; + break; + } + + mMaturityButton->setVisible(button_visible); + mMaturityButton->setToolTip(rating_tooltip); + if(rating_image) + { + mMaturityButton->setImageUnselected(rating_image); + mMaturityButton->setImagePressed(rating_image); + } + if (mMaturityButton->getVisible()) + { + positionMaturityButton(); + } +} + +void LLLocationInputCtrl::positionMaturityButton() +{ + const LLFontGL* font = mTextEntry->getFont(); + if (!font) + return; + + S32 left_pad, right_pad; + mTextEntry->getTextPadding(&left_pad, &right_pad); + + // Calculate the right edge of rendered text + a whitespace. + left_pad = left_pad + font->getWidth(mTextEntry->getText()) + font->getWidth(" "); + + LLRect rect = mMaturityButton->getRect(); + mMaturityButton->setRect(rect.setOriginAndSize(left_pad, rect.mBottom, rect.getWidth(), rect.getHeight())); + + // Hide icon if it text area is not width enough to display it, show otherwise. + mMaturityButton->setVisible(rect.mRight < mTextEntry->getRect().getWidth() - right_pad); +} + +void LLLocationInputCtrl::addLocationHistoryEntry(const std::string& title, const LLSD& value) +{ + // SL-20286 : Duplication of autocomplete results occurs when entering some search queries in the navigation bar + // Exclude visual duplicates (items with the same titles) in the dropdown list + LLScrollListItem* item = mList->getItemByLabel(title); + if (!item) + { + add(title, value); + } +} + +void LLLocationInputCtrl::rebuildLocationHistory(const std::string& filter) +{ + LLLocationHistory::location_list_t filtered_items; + const LLLocationHistory::location_list_t* itemsp = NULL; + LLLocationHistory* lh = LLLocationHistory::getInstance(); + + if (filter.empty()) + { + itemsp = &lh->getItems(); + } + else + { + lh->getMatchingItems(filter, filtered_items); + itemsp = &filtered_items; + } + + removeall(); + for (LLLocationHistory::location_list_t::const_reverse_iterator it = itemsp->rbegin(); it != itemsp->rend(); it++) + { + LLSD value; + value["tooltip"] = it->getToolTip(); + //location history can contain only typed locations + value["item_type"] = TYPED_REGION_SLURL; + value["global_pos"] = it->mGlobalPos.getValue(); + addLocationHistoryEntry(it->getLocation(), value); + } +} + +void LLLocationInputCtrl::focusTextEntry() +{ + // We can't use "mTextEntry->setFocus(true)" instead because + // if the "select_on_focus" parameter is true it places the cursor + // at the beginning (after selecting text), thus screwing up updateSelection(). + if (mTextEntry) + { + gFocusMgr.setKeyboardFocus(mTextEntry); + + // Enable the text entry to handle accelerator keys (EXT-8104). + LLEditMenuHandler::gEditMenuHandler = mTextEntry; + } +} + +void LLLocationInputCtrl::enableAddLandmarkButton(bool val) +{ + // We don't want to disable the button because it should be click able at any time, + // instead switch images. + LLUIImage* img = val ? mLandmarkImageOn : mLandmarkImageOff; + if(img) + { + mAddLandmarkBtn->setImageUnselected(img); + } +} + +// Change the "Add landmark" button image +// depending on whether current parcel has been landmarked. +void LLLocationInputCtrl::updateAddLandmarkButton() +{ + enableAddLandmarkButton(LLLandmarkActions::hasParcelLandmark()); +} +void LLLocationInputCtrl::updateAddLandmarkTooltip() +{ + std::string tooltip; + if(LLLandmarkActions::landmarkAlreadyExists()) + { + tooltip = mEditLandmarkTooltip; + } + else + { + tooltip = mAddLandmarkTooltip; + } + mAddLandmarkBtn->setToolTip(tooltip); +} + +void LLLocationInputCtrl::updateContextMenu(){ + + if (mLocationContextMenu) + { + LLMenuItemGL* landmarkItem = mLocationContextMenu->getChild("Landmark"); + if (!LLLandmarkActions::landmarkAlreadyExists()) + { + landmarkItem->setLabel(LLTrans::getString("AddLandmarkNavBarMenu")); + } + else + { + landmarkItem->setLabel(LLTrans::getString("EditLandmarkNavBarMenu")); + } + } +} +void LLLocationInputCtrl::updateWidgetlayout() +{ + const LLRect& rect = getLocalRect(); + const LLRect& hist_btn_rect = mButton->getRect(); + + // Info button is set in the XUI XML location_input.xml + + // "Add Landmark" button + LLRect al_btn_rect = mAddLandmarkBtn->getRect(); + al_btn_rect.translate( + hist_btn_rect.mLeft - mIconHPad - al_btn_rect.getWidth(), + (rect.getHeight() - al_btn_rect.getHeight()) / 2); + mAddLandmarkBtn->setRect(al_btn_rect); +} + +void LLLocationInputCtrl::changeLocationPresentation() +{ + if (!mTextEntry) + return; + + //change location presentation only if user does not select/paste anything and + //human-readable region name is being displayed + if(!mTextEntry->hasSelection() && mTextEntry->getText() == mHumanReadableLocation) + { + //needs unescaped one + LLSLURL slurl; + LLAgentUI::buildSLURL(slurl, false); + mTextEntry->setText(LLURI::unescape(slurl.getSLURLString())); + mTextEntry->selectAll(); + + mMaturityButton->setVisible(false); + + isHumanReadableLocationVisible = false; + } +} + +void LLLocationInputCtrl::onLocationContextMenuItemClicked(const LLSD& userdata) +{ + std::string item = userdata.asString(); + + if (item == "show_coordinates") + { + gSavedSettings.setBOOL("NavBarShowCoordinates",!gSavedSettings.getBOOL("NavBarShowCoordinates")); + } + else if (item == "show_properties") + { + gSavedSettings.setBOOL("NavBarShowParcelProperties", + !gSavedSettings.getBOOL("NavBarShowParcelProperties")); + } + else if (item == "landmark") + { + LLViewerInventoryItem* landmark = LLLandmarkActions::findLandmarkForAgentPos(); + + if(!landmark) + { + LLFloaterReg::showInstance("add_landmark"); + } + else + { + LLFloaterSidePanelContainer::showPanel("places", LLSD().with("type", "landmark").with("id",landmark->getUUID())); + + } + } + else if (item == "cut") + { + mTextEntry->cut(); + } + else if (item == "copy") + { + mTextEntry->copy(); + } + else if (item == "paste") + { + mTextEntry->paste(); + } + else if (item == "delete") + { + mTextEntry->deleteSelection(); + } + else if (item == "select_all") + { + mTextEntry->selectAll(); + } +} + +bool LLLocationInputCtrl::onLocationContextMenuItemEnabled(const LLSD& userdata) +{ + std::string item = userdata.asString(); + + if (item == "can_cut") + { + return mTextEntry->canCut(); + } + else if (item == "can_copy") + { + return mTextEntry->canCopy(); + } + else if (item == "can_paste") + { + return mTextEntry->canPaste(); + } + else if (item == "can_delete") + { + return mTextEntry->canDeselect(); + } + else if (item == "can_select_all") + { + return mTextEntry->canSelectAll() && (mTextEntry->getLength() > 0); + } + else if(item == "show_coordinates") + { + return gSavedSettings.getBOOL("NavBarShowCoordinates"); + } + + return false; +} + +void LLLocationInputCtrl::callbackRebakeRegion(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // OK + { + if (LLPathfindingManager::getInstance() != NULL) + { + LLMenuOptionPathfindingRebakeNavmesh::getInstance()->sendRequestRebakeNavmesh(); + } + } +} + +void LLLocationInputCtrl::onParcelIconClick(EParcelIcon icon) +{ + switch (icon) + { + case VOICE_ICON: + LLNotificationsUtil::add("NoVoice"); + break; + case FLY_ICON: + LLNotificationsUtil::add("NoFly"); + break; + case PUSH_ICON: + LLNotificationsUtil::add("PushRestricted"); + break; + case BUILD_ICON: + LLNotificationsUtil::add("NoBuild"); + break; + case PATHFINDING_DIRTY_ICON: + if (LLPathfindingManager::getInstance() != NULL) + { + LLMenuOptionPathfindingRebakeNavmesh *rebakeInstance = LLMenuOptionPathfindingRebakeNavmesh::getInstance(); + if (rebakeInstance && rebakeInstance->canRebakeRegion() && (rebakeInstance->getMode() == LLMenuOptionPathfindingRebakeNavmesh::kRebakeNavMesh_Available)) + { + LLNotificationsUtil::add("PathfindingDirtyRebake", LLSD(), LLSD(), + boost::bind(&LLLocationInputCtrl::callbackRebakeRegion, this, _1, _2)); + break; + } + } + LLNotificationsUtil::add("PathfindingDirty"); + break; + case PATHFINDING_DISABLED_ICON: + LLNotificationsUtil::add("DynamicPathfindingDisabled"); + break; + case SCRIPTS_ICON: + { + LLViewerRegion* region = gAgent.getRegion(); + if(region && region->getRegionFlag(REGION_FLAGS_ESTATE_SKIP_SCRIPTS)) + { + LLNotificationsUtil::add("ScriptsStopped"); + } + else if(region && region->getRegionFlag(REGION_FLAGS_SKIP_SCRIPTS)) + { + LLNotificationsUtil::add("ScriptsNotRunning"); + } + else + { + LLNotificationsUtil::add("NoOutsideScripts"); + } + break; + } + case DAMAGE_ICON: + LLNotificationsUtil::add("NotSafe"); + break; + case SEE_AVATARS_ICON: + LLNotificationsUtil::add("SeeAvatars"); + break; + case ICON_COUNT: + break; + // no default to get compiler warning when a new icon gets added + } +} + +void LLLocationInputCtrl::createNavMeshStatusListenerForCurrentRegion() +{ + if (mNavMeshSlot.connected()) + { + mNavMeshSlot.disconnect(); + } + + LLViewerRegion *currentRegion = gAgent.getRegion(); + if (currentRegion != NULL) + { + mNavMeshSlot = LLPathfindingManager::getInstance()->registerNavMeshListenerForRegion(currentRegion, boost::bind(&LLLocationInputCtrl::onNavMeshStatusChange, this, _2)); + LLPathfindingManager::getInstance()->requestGetNavMeshForRegion(currentRegion, true); + } +} diff --git a/indra/newview/lllocationinputctrl.h b/indra/newview/lllocationinputctrl.h index dd759cd7d1..56e5555ba5 100644 --- a/indra/newview/lllocationinputctrl.h +++ b/indra/newview/lllocationinputctrl.h @@ -1,213 +1,213 @@ -/** - * @file lllocationinputctrl.h - * @brief Combobox-like location input control - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLLOCATIONINPUTCTRL_H -#define LL_LLLOCATIONINPUTCTRL_H - -#include "llcombobox.h" -#include "lliconctrl.h" // Params -#include "lltextbox.h" // Params -#include "lllocationhistory.h" -#include "llpathfindingnavmesh.h" - -class LLLandmark; - -// internals -class LLAddLandmarkObserver; -class LLRemoveLandmarkObserver; -class LLParcelChangeObserver; -class LLMenuGL; -class LLTeleportHistoryItem; -class LLPathfindingNavMeshStatus; - -/** - * Location input control. - * - * @see LLNavigationBar - */ -class LLLocationInputCtrl -: public LLComboBox -{ - LOG_CLASS(LLLocationInputCtrl); - friend class LLAddLandmarkObserver; - friend class LLRemoveLandmarkObserver; - friend class LLParcelChangeObserver; - -public: - struct Params - : public LLInitParam::Block - { - Optional icon_maturity_general, - icon_maturity_adult, - icon_maturity_moderate, - add_landmark_image_enabled, - add_landmark_image_disabled, - add_landmark_image_hover, - add_landmark_image_selected; - Optional maturity_help_topic; - Optional icon_hpad, - add_landmark_hpad; - Optional maturity_button, - add_landmark_button, - for_sale_button, - info_button; - Optional voice_icon, - fly_icon, - push_icon, - build_icon, - scripts_icon, - damage_icon, - see_avatars_icon, - pathfinding_dirty_icon, - pathfinding_disabled_icon; - Optional damage_text; - Params(); - }; - - // LLView interface - /*virtual*/ void setEnabled(bool enabled); - /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleKeyHere(KEY key, MASK mask); - /*virtual*/ void onFocusReceived(); - /*virtual*/ void onFocusLost(); - /*virtual*/ void draw(); - /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); - //======================================================================== - - // LLUICtrl interface - /*virtual*/ void setFocus(bool b); - //======================================================================== - - // LLComboBox interface - void hideList(); - void onTextEntry(LLLineEditor* line_editor); - //======================================================================== - - LLLineEditor* getTextEntry() const { return mTextEntry; } - void handleLoginComplete(); - - bool isNavMeshDirty() { return mIsNavMeshDirty; } - -private: - - enum EParcelIcon - { - VOICE_ICON = 0, - FLY_ICON, // 1 - PUSH_ICON, // 2 - BUILD_ICON, // 3 - SCRIPTS_ICON, // 4 - DAMAGE_ICON, // 5 - SEE_AVATARS_ICON, // 6 - PATHFINDING_DIRTY_ICON, // 7 - PATHFINDING_DISABLED_ICON,// 8 - ICON_COUNT // 9 total - }; - - friend class LLUICtrlFactory; - LLLocationInputCtrl(const Params&); - virtual ~LLLocationInputCtrl(); - - void focusTextEntry(); - /** - * Changes the "Add landmark" button image - * depending on whether current parcel has been landmarked. - */ - void enableAddLandmarkButton(bool val); - void refresh(); - void refreshLocation(); - void refreshParcelIcons(); - // Refresh the value in the health percentage text field - void refreshHealth(); - void refreshMaturityButton(); - void positionMaturityButton(); - - void addLocationHistoryEntry(const std::string& title, const LLSD& value); - void rebuildLocationHistory(const std::string& filter = LLStringUtil::null); - bool findTeleportItemsByTitle(const LLTeleportHistoryItem& item, const std::string& filter); - void setText(const LLStringExplicit& text); - void updateAddLandmarkButton(); - void updateAddLandmarkTooltip(); - void updateContextMenu(); - void updateWidgetlayout(); - void changeLocationPresentation(); - - void onInfoButtonClicked(); - void onLocationHistoryChanged(LLLocationHistory::EChangeType event); - void onLocationPrearrange(const LLSD& data); - void onTextEditorRightClicked(S32 x, S32 y, MASK mask); - void onLandmarkLoaded(LLLandmark* lm); - void onForSaleButtonClicked(); - void onAddLandmarkButtonClicked(); - void onAgentParcelChange(); - void onRegionBoundaryCrossed(); - void onNavMeshStatusChange(const LLPathfindingNavMeshStatus &pNavMeshStatus); - // callbacks - bool onLocationContextMenuItemEnabled(const LLSD& userdata); - void onLocationContextMenuItemClicked(const LLSD& userdata); - void callbackRebakeRegion(const LLSD& notification, const LLSD& response); - void onParcelIconClick(EParcelIcon icon); - - void createNavMeshStatusListenerForCurrentRegion(); - - LLMenuGL* mLocationContextMenu; - LLButton* mAddLandmarkBtn; - LLButton* mForSaleBtn; - LLButton* mInfoBtn; - S32 mIconHPad; // pad between all icons - S32 mAddLandmarkHPad; // pad to left of landmark star - - LLButton* mMaturityButton; - LLIconCtrl* mParcelIcon[ICON_COUNT]; - LLTextBox* mDamageText; - - LLAddLandmarkObserver* mAddLandmarkObserver; - LLRemoveLandmarkObserver* mRemoveLandmarkObserver; - LLParcelChangeObserver* mParcelChangeObserver; - - boost::signals2::connection mCoordinatesControlConnection; - boost::signals2::connection mParcelPropertiesControlConnection; - boost::signals2::connection mParcelMgrConnection; - boost::signals2::connection mLocationHistoryConnection; - boost::signals2::connection mRegionCrossingSlot; - LLPathfindingNavMesh::navmesh_slot_t mNavMeshSlot; - bool mIsNavMeshDirty; - LLUIImage* mLandmarkImageOn; - LLUIImage* mLandmarkImageOff; - LLPointer mIconMaturityGeneral; - LLPointer mIconMaturityAdult; - LLPointer mIconMaturityModerate; - LLPointer mIconPathfindingDynamic; - - std::string mAddLandmarkTooltip; - std::string mEditLandmarkTooltip; - // this field holds a human-readable form of the location string, it is needed to be able to compare copy-pated value and real location - std::string mHumanReadableLocation; - bool isHumanReadableLocationVisible; - std::string mMaturityHelpTopic; -}; - -#endif +/** + * @file lllocationinputctrl.h + * @brief Combobox-like location input control + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLLOCATIONINPUTCTRL_H +#define LL_LLLOCATIONINPUTCTRL_H + +#include "llcombobox.h" +#include "lliconctrl.h" // Params +#include "lltextbox.h" // Params +#include "lllocationhistory.h" +#include "llpathfindingnavmesh.h" + +class LLLandmark; + +// internals +class LLAddLandmarkObserver; +class LLRemoveLandmarkObserver; +class LLParcelChangeObserver; +class LLMenuGL; +class LLTeleportHistoryItem; +class LLPathfindingNavMeshStatus; + +/** + * Location input control. + * + * @see LLNavigationBar + */ +class LLLocationInputCtrl +: public LLComboBox +{ + LOG_CLASS(LLLocationInputCtrl); + friend class LLAddLandmarkObserver; + friend class LLRemoveLandmarkObserver; + friend class LLParcelChangeObserver; + +public: + struct Params + : public LLInitParam::Block + { + Optional icon_maturity_general, + icon_maturity_adult, + icon_maturity_moderate, + add_landmark_image_enabled, + add_landmark_image_disabled, + add_landmark_image_hover, + add_landmark_image_selected; + Optional maturity_help_topic; + Optional icon_hpad, + add_landmark_hpad; + Optional maturity_button, + add_landmark_button, + for_sale_button, + info_button; + Optional voice_icon, + fly_icon, + push_icon, + build_icon, + scripts_icon, + damage_icon, + see_avatars_icon, + pathfinding_dirty_icon, + pathfinding_disabled_icon; + Optional damage_text; + Params(); + }; + + // LLView interface + /*virtual*/ void setEnabled(bool enabled); + /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleKeyHere(KEY key, MASK mask); + /*virtual*/ void onFocusReceived(); + /*virtual*/ void onFocusLost(); + /*virtual*/ void draw(); + /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); + //======================================================================== + + // LLUICtrl interface + /*virtual*/ void setFocus(bool b); + //======================================================================== + + // LLComboBox interface + void hideList(); + void onTextEntry(LLLineEditor* line_editor); + //======================================================================== + + LLLineEditor* getTextEntry() const { return mTextEntry; } + void handleLoginComplete(); + + bool isNavMeshDirty() { return mIsNavMeshDirty; } + +private: + + enum EParcelIcon + { + VOICE_ICON = 0, + FLY_ICON, // 1 + PUSH_ICON, // 2 + BUILD_ICON, // 3 + SCRIPTS_ICON, // 4 + DAMAGE_ICON, // 5 + SEE_AVATARS_ICON, // 6 + PATHFINDING_DIRTY_ICON, // 7 + PATHFINDING_DISABLED_ICON,// 8 + ICON_COUNT // 9 total + }; + + friend class LLUICtrlFactory; + LLLocationInputCtrl(const Params&); + virtual ~LLLocationInputCtrl(); + + void focusTextEntry(); + /** + * Changes the "Add landmark" button image + * depending on whether current parcel has been landmarked. + */ + void enableAddLandmarkButton(bool val); + void refresh(); + void refreshLocation(); + void refreshParcelIcons(); + // Refresh the value in the health percentage text field + void refreshHealth(); + void refreshMaturityButton(); + void positionMaturityButton(); + + void addLocationHistoryEntry(const std::string& title, const LLSD& value); + void rebuildLocationHistory(const std::string& filter = LLStringUtil::null); + bool findTeleportItemsByTitle(const LLTeleportHistoryItem& item, const std::string& filter); + void setText(const LLStringExplicit& text); + void updateAddLandmarkButton(); + void updateAddLandmarkTooltip(); + void updateContextMenu(); + void updateWidgetlayout(); + void changeLocationPresentation(); + + void onInfoButtonClicked(); + void onLocationHistoryChanged(LLLocationHistory::EChangeType event); + void onLocationPrearrange(const LLSD& data); + void onTextEditorRightClicked(S32 x, S32 y, MASK mask); + void onLandmarkLoaded(LLLandmark* lm); + void onForSaleButtonClicked(); + void onAddLandmarkButtonClicked(); + void onAgentParcelChange(); + void onRegionBoundaryCrossed(); + void onNavMeshStatusChange(const LLPathfindingNavMeshStatus &pNavMeshStatus); + // callbacks + bool onLocationContextMenuItemEnabled(const LLSD& userdata); + void onLocationContextMenuItemClicked(const LLSD& userdata); + void callbackRebakeRegion(const LLSD& notification, const LLSD& response); + void onParcelIconClick(EParcelIcon icon); + + void createNavMeshStatusListenerForCurrentRegion(); + + LLMenuGL* mLocationContextMenu; + LLButton* mAddLandmarkBtn; + LLButton* mForSaleBtn; + LLButton* mInfoBtn; + S32 mIconHPad; // pad between all icons + S32 mAddLandmarkHPad; // pad to left of landmark star + + LLButton* mMaturityButton; + LLIconCtrl* mParcelIcon[ICON_COUNT]; + LLTextBox* mDamageText; + + LLAddLandmarkObserver* mAddLandmarkObserver; + LLRemoveLandmarkObserver* mRemoveLandmarkObserver; + LLParcelChangeObserver* mParcelChangeObserver; + + boost::signals2::connection mCoordinatesControlConnection; + boost::signals2::connection mParcelPropertiesControlConnection; + boost::signals2::connection mParcelMgrConnection; + boost::signals2::connection mLocationHistoryConnection; + boost::signals2::connection mRegionCrossingSlot; + LLPathfindingNavMesh::navmesh_slot_t mNavMeshSlot; + bool mIsNavMeshDirty; + LLUIImage* mLandmarkImageOn; + LLUIImage* mLandmarkImageOff; + LLPointer mIconMaturityGeneral; + LLPointer mIconMaturityAdult; + LLPointer mIconMaturityModerate; + LLPointer mIconPathfindingDynamic; + + std::string mAddLandmarkTooltip; + std::string mEditLandmarkTooltip; + // this field holds a human-readable form of the location string, it is needed to be able to compare copy-pated value and real location + std::string mHumanReadableLocation; + bool isHumanReadableLocationVisible; + std::string mMaturityHelpTopic; +}; + +#endif diff --git a/indra/newview/lllogchat.cpp b/indra/newview/lllogchat.cpp index f98bb5429d..adced99a95 100644 --- a/indra/newview/lllogchat.cpp +++ b/indra/newview/lllogchat.cpp @@ -1,1267 +1,1267 @@ -/** - * @file lllogchat.cpp - * @brief LLLogChat class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llfloaterconversationpreview.h" -#include "llagent.h" -#include "llagentui.h" -#include "llavatarnamecache.h" -#include "lllogchat.h" -#include "llregex.h" -#include "lltrans.h" -#include "llviewercontrol.h" - -#include "lldiriterator.h" -#include "llfloaterimsessiontab.h" -#include "llinstantmessage.h" -#include "llsingleton.h" // for LLSingleton - -#include -#include -#include - -#if LL_MSVC -#pragma warning(push) -// disable warning about boost::lexical_cast unreachable code -// when it fails to parse the string -#pragma warning (disable:4702) -#endif - -#include -#if LL_MSVC -#pragma warning(pop) // Restore all warnings to the previous state -#endif - -#include -#include - -const S32 LOG_RECALL_SIZE = 20480; - -const std::string LL_IM_TIME("time"); -const std::string LL_IM_DATE_TIME("datetime"); -const std::string LL_IM_TEXT("message"); -const std::string LL_IM_FROM("from"); -const std::string LL_IM_FROM_ID("from_id"); -const std::string LL_TRANSCRIPT_FILE_EXTENSION("txt"); - -const std::string GROUP_CHAT_SUFFIX(" (group)"); - -const static char IM_SYMBOL_SEPARATOR(':'); -const static std::string IM_SEPARATOR(std::string() + IM_SYMBOL_SEPARATOR + " "); -const static std::string NEW_LINE("\n"); -const static std::string NEW_LINE_SPACE_PREFIX("\n "); -const static std::string TWO_SPACES(" "); -const static std::string MULTI_LINE_PREFIX(" "); - -/** - * Chat log lines - timestamp and name are optional but message text is mandatory. - * - * Typical plain text chat log lines: - * - * SuperCar: You aren't the owner - * [2:59] SuperCar: You aren't the owner - * [2009/11/20 3:00] SuperCar: You aren't the owner - * Katar Ivercourt is Offline - * [3:00] Katar Ivercourt is Offline - * [2009/11/20 3:01] Corba ProductEngine is Offline - * - * Note: "You" was used as an avatar names in viewers of previous versions - */ -const static boost::regex TIMESTAMP_AND_STUFF("^(\\[\\d{4}/\\d{1,2}/\\d{1,2}\\s+\\d{1,2}:\\d{2}\\]\\s+|\\[\\d{1,2}:\\d{2}\\]\\s+)?(.*)$"); -const static boost::regex TIMESTAMP("^(\\[\\d{4}/\\d{1,2}/\\d{1,2}\\s+\\d{1,2}:\\d{2}\\]|\\[\\d{1,2}:\\d{2}\\]).*"); - -/** - * Regular expression suitable to match names like - * "You", "Second Life", "Igor ProductEngine", "Object", "Mega House" - */ -const static boost::regex NAME_AND_TEXT("([^:]+[:]{1})?(\\s*)(.*)"); - -/** - * These are recognizers for matching the names of ad-hoc conferences when generating the log file name - * On invited side, an ad-hoc is named like " Conference 2010/11/19 03:43 f0f4" - * On initiating side, an ad-hoc is named like Ad-hoc Conference hash" - * If the naming system for ad-hoc conferences are change in LLIMModel::LLIMSession::buildHistoryFileName() - * then these definition need to be adjusted as well. - */ -const static boost::regex INBOUND_CONFERENCE("^[a-zA-Z]{1,31} [a-zA-Z]{1,31} Conference [0-9]{4}/[0-9]{2}/[0-9]{2} [0-9]{2}:[0-9]{2} [0-9a-f]{4}"); -const static boost::regex OUTBOUND_CONFERENCE("^Ad-hoc Conference hash[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"); - -//is used to parse complex object names like "Xstreet SL Terminal v2.2.5 st" -const static std::string NAME_TEXT_DIVIDER(": "); - -// is used for timestamps adjusting -const static char* DATE_FORMAT("%Y/%m/%d %H:%M"); -const static char* TIME_FORMAT("%H:%M"); - -const static int IDX_TIMESTAMP = 1; -const static int IDX_STUFF = 2; -const static int IDX_NAME = 1; -const static int IDX_TEXT = 3; - -using namespace boost::posix_time; -using namespace boost::gregorian; - -void append_to_last_message(std::list& messages, const std::string& line) -{ - if (!messages.size()) return; - - std::string im_text = messages.back()[LL_IM_TEXT].asString(); - im_text.append(line); - messages.back()[LL_IM_TEXT] = im_text; -} - -const char* remove_utf8_bom(const char* buf) -{ - const char* start = buf; - if (start[0] == (char)0xEF && start[1] == (char)0xBB && start[2] == (char)0xBF) - { // If string starts with the magic bytes, return pointer after it. - start += 3; - } - return start; -} - -class LLLogChatTimeScanner: public LLSingleton -{ - LLSINGLETON(LLLogChatTimeScanner); - -public: - date getTodayPacificDate() - { - typedef boost::date_time::local_adjustor pst; - typedef boost::date_time::local_adjustor pdt; - time_t t_time = time(NULL); - ptime p_time = LLStringOps::getPacificDaylightTime() - ? pdt::utc_to_local(from_time_t(t_time)) - : pst::utc_to_local(from_time_t(t_time)); - struct tm s_tm = to_tm(p_time); - return date_from_tm(s_tm); - } - - void checkAndCutOffDate(std::string& time_str) - { - // Cuts off the "%Y/%m/%d" from string for todays timestamps. - // Assume that passed string has at least "%H:%M" time format. - date log_date(not_a_date_time); - date today(getTodayPacificDate()); - - // Parse the passed date - mDateStream.str(LLStringUtil::null); - mDateStream << time_str; - mDateStream >> log_date; - mDateStream.clear(); - - days zero_days(0); - days days_alive = today - log_date; - - if ( days_alive == zero_days ) - { - // Yep, today's so strip "%Y/%m/%d" info - ptime stripped_time(not_a_date_time); - - mTimeStream.str(LLStringUtil::null); - mTimeStream << time_str; - mTimeStream >> stripped_time; - mTimeStream.clear(); - - time_str.clear(); - - mTimeStream.str(LLStringUtil::null); - mTimeStream << stripped_time; - mTimeStream >> time_str; - mTimeStream.clear(); - } - - LL_DEBUGS("LLChatLogParser") - << " log_date: " - << log_date - << " today: " - << today - << " days alive: " - << days_alive - << " new time: " - << time_str - << LL_ENDL; - } - - -private: - std::stringstream mDateStream; - std::stringstream mTimeStream; -}; - -inline -LLLogChatTimeScanner::LLLogChatTimeScanner() -{ - // Note, date/time facets will be destroyed by string streams - mDateStream.imbue(std::locale(mDateStream.getloc(), new date_input_facet(DATE_FORMAT))); - mTimeStream.imbue(std::locale(mTimeStream.getloc(), new time_facet(TIME_FORMAT))); - mTimeStream.imbue(std::locale(mTimeStream.getloc(), new time_input_facet(DATE_FORMAT))); -} - -LLLogChat::LLLogChat() -: mSaveHistorySignal(NULL) // only needed in preferences -{ - mHistoryThreadsMutex = new LLMutex(); -} - -LLLogChat::~LLLogChat() -{ - delete mHistoryThreadsMutex; - mHistoryThreadsMutex = NULL; - - if (mSaveHistorySignal) - { - mSaveHistorySignal->disconnect_all_slots(); - delete mSaveHistorySignal; - mSaveHistorySignal = NULL; - } -} - - -//static -std::string LLLogChat::makeLogFileName(std::string filename) -{ - /** - * Testing for in bound and out bound ad-hoc file names - * if it is then skip date stamping. - **/ - - boost::match_results matches; - bool inboundConf = ll_regex_match(filename, matches, INBOUND_CONFERENCE); - bool outboundConf = ll_regex_match(filename, matches, OUTBOUND_CONFERENCE); - if (!(inboundConf || outboundConf)) - { - if( gSavedPerAccountSettings.getBOOL("LogFileNamewithDate") ) - { - time_t now; - time(&now); - char dbuffer[20]; /* Flawfinder: ignore */ - if (filename == "chat") - { - strftime(dbuffer, 20, "-%Y-%m-%d", localtime(&now)); - } - else - { - strftime(dbuffer, 20, "-%Y-%m", localtime(&now)); - } - filename += dbuffer; - } - } - - filename = cleanFileName(filename); - filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_ACCOUNT_CHAT_LOGS, filename); - if (!filename.empty()) - { - filename += '.' + LL_TRANSCRIPT_FILE_EXTENSION; - } - - return filename; -} - -//static -void LLLogChat::renameLogFile(const std::string& old_filename, const std::string& new_filename) -{ - std::string new_name = cleanFileName(new_filename); - std::string old_name = cleanFileName(old_filename); - new_name = gDirUtilp->getExpandedFilename(LL_PATH_PER_ACCOUNT_CHAT_LOGS, new_name); - old_name = gDirUtilp->getExpandedFilename(LL_PATH_PER_ACCOUNT_CHAT_LOGS, old_name); - - if (new_name.empty() || old_name.empty()) - { - return; - } - - new_name += '.' + LL_TRANSCRIPT_FILE_EXTENSION; - old_name += '.' + LL_TRANSCRIPT_FILE_EXTENSION; - - if (!LLFile::isfile(new_name) && LLFile::isfile(old_name)) - { - LLFile::rename(old_name, new_name); - } -} - -std::string LLLogChat::cleanFileName(std::string filename) -{ - std::string invalidChars = "\"\'\\/?*:.<>|[]{}~"; // Cannot match glob or illegal filename chars - std::string::size_type position = filename.find_first_of(invalidChars); - while (position != filename.npos) - { - filename[position] = '_'; - position = filename.find_first_of(invalidChars, position); - } - return filename; -} - -std::string LLLogChat::timestamp2LogString(U32 timestamp, bool withdate) -{ - std::string timeStr; - if (withdate) - { - timeStr = "[" + LLTrans::getString ("TimeYear") + "]/[" - + LLTrans::getString ("TimeMonth") + "]/[" - + LLTrans::getString ("TimeDay") + "] [" - + LLTrans::getString ("TimeHour") + "]:[" - + LLTrans::getString ("TimeMin") + "]"; - } - else - { - timeStr = "[" + LLTrans::getString("TimeHour") + "]:[" - + LLTrans::getString ("TimeMin")+"]"; - } - - LLSD substitution; - if (timestamp == 0) - { - substitution["datetime"] = (S32)time_corrected(); - } - else - { // timestamp is correct utc already - substitution["datetime"] = (S32)timestamp; - } - - LLStringUtil::format (timeStr, substitution); - return timeStr; -} - - -//static -void LLLogChat::saveHistory(const std::string& filename, - const std::string& from, - const LLUUID& from_id, - const std::string& line) -{ - std::string tmp_filename = filename; - LLStringUtil::trim(tmp_filename); - if (tmp_filename.empty()) - { - std::string warn = "Chat history filename [" + filename + "] is empty!"; - LL_WARNS() << warn << LL_ENDL; - llassert(tmp_filename.size()); - return; - } - - llofstream file(LLLogChat::makeLogFileName(filename).c_str(), std::ios_base::app); - if (!file.is_open()) - { - LL_WARNS() << "Couldn't open chat history log! - " + filename << LL_ENDL; - return; - } - - LLSD item; - - if (gSavedPerAccountSettings.getBOOL("LogTimestamp")) - item["time"] = LLLogChat::timestamp2LogString(0, gSavedPerAccountSettings.getBOOL("LogTimestampDate")); - - item["from_id"] = from_id; - item["message"] = line; - - //adding "Second Life:" for all system messages to make chat log history parsing more reliable - if (from.empty() && from_id.isNull()) - { - item["from"] = SYSTEM_FROM; - } - else - { - item["from"] = from; - } - - file << LLChatLogFormatter(item) << std::endl; - - file.close(); - - LLLogChat::getInstance()->triggerHistorySignal(); -} - -// static -void LLLogChat::loadChatHistory(const std::string& file_name, std::list& messages, const LLSD& load_params, bool is_group) -{ - if (file_name.empty()) - { - LL_WARNS("LLLogChat::loadChatHistory") << "Local history file name is empty!" << LL_ENDL; - return ; - } - - bool load_all_history = load_params.has("load_all_history") ? load_params["load_all_history"].asBoolean() : false; - - // Stat the file to find it and get the last history entry time - llstat stat_data; - - std::string log_file_name = LLLogChat::makeLogFileName(file_name); - LL_DEBUGS("ChatHistory") << "First attempt to stat chat history file " << log_file_name << LL_ENDL; - - S32 no_stat = LLFile::stat(log_file_name, &stat_data); - - if (no_stat) - { - if (is_group) - { - std::string old_name(file_name); - old_name.erase(old_name.size() - GROUP_CHAT_SUFFIX.size()); // trim off " (group)" - log_file_name = LLLogChat::makeLogFileName(old_name); - LL_DEBUGS("ChatHistory") << "Attempting to stat adjusted chat history file " << log_file_name << LL_ENDL; - no_stat = LLFile::stat(log_file_name, &stat_data); - if (!no_stat) - { // Found it without "(group)", copy to new naming style. We already have the mod time in stat_data - log_file_name = LLLogChat::makeLogFileName(file_name); - LL_DEBUGS("ChatHistory") << "Attempt to stat copied history file " << log_file_name << LL_ENDL; - LLFile::copy(LLLogChat::makeLogFileName(old_name), log_file_name); - } - } - if (no_stat) - { - log_file_name = LLLogChat::oldLogFileName(file_name); - LL_DEBUGS("ChatHistory") << "Attempt to stat old history file name " << log_file_name << LL_ENDL; - no_stat = LLFile::stat(log_file_name, &stat_data); - if (no_stat) - { - LL_DEBUGS("ChatHistory") << "No previous conversation log file found for " << file_name << LL_ENDL; - return; //No previous conversation with this name. - } - } - } - - // If we got here, we managed to stat the file. - // Open the file to read - LLFILE* fptr = LLFile::fopen(log_file_name, "r"); /*Flawfinder: ignore*/ - if (!fptr) - { // Ok, this is strange but not really tragic in the big picture of things - LL_WARNS("ChatHistory") << "Unable to read file " << log_file_name << " after stat was successful" << LL_ENDL; - return; - } - - S32 save_num_messages = messages.size(); - - char buffer[LOG_RECALL_SIZE]; /*Flawfinder: ignore*/ - char *bptr; - S32 len; - bool firstline = true; - - if (load_all_history || fseek(fptr, (LOG_RECALL_SIZE - 1) * -1 , SEEK_END)) - { //We need to load the whole historyFile or it's smaller than recall size, so get it all. - firstline = false; - if (fseek(fptr, 0, SEEK_SET)) - { - fclose(fptr); - return; - } - } - while (fgets(buffer, LOG_RECALL_SIZE, fptr) && !feof(fptr)) - { - len = strlen(buffer) - 1; /*Flawfinder: ignore*/ - // backfill any end of line characters with nulls - for (bptr = (buffer + len); (*bptr == '\n' || *bptr == '\r') && bptr>buffer; bptr--) *bptr='\0'; - - if (firstline) - { - firstline = false; - continue; - } - - std::string line(remove_utf8_bom(buffer)); - - //updated 1.23 plain text log format requires a space added before subsequent lines in a multilined message - if (' ' == line[0]) - { - line.erase(0, MULTI_LINE_PREFIX.length()); - append_to_last_message(messages, '\n' + line); - } - else if (0 == len && ('\n' == line[0] || '\r' == line[0])) - { - //to support old format's multilined messages with new lines used to divide paragraphs - append_to_last_message(messages, line); - } - else - { - LLSD item; - if (!LLChatLogParser::parse(line, item, load_params)) - { - item[LL_IM_TEXT] = line; - } - messages.push_back(item); - } - } - fclose(fptr); - - LL_DEBUGS("ChatHistory") << "Read " << (messages.size() - save_num_messages) - << " messages of chat history from " << log_file_name - << " file mod time " << (F64)stat_data.st_mtime << LL_ENDL; -} - -bool LLLogChat::historyThreadsFinished(LLUUID session_id) -{ - LLMutexLock lock(historyThreadsMutex()); - bool finished = true; - std::map::iterator it = mLoadHistoryThreads.find(session_id); - if (it != mLoadHistoryThreads.end()) - { - finished = it->second->isFinished(); - } - if (!finished) - { - return false; - } - std::map::iterator dit = mDeleteHistoryThreads.find(session_id); - if (dit != mDeleteHistoryThreads.end()) - { - finished = finished && dit->second->isFinished(); - } - return finished; -} - -LLLoadHistoryThread* LLLogChat::getLoadHistoryThread(LLUUID session_id) -{ - LLMutexLock lock(historyThreadsMutex()); - std::map::iterator it = mLoadHistoryThreads.find(session_id); - if (it != mLoadHistoryThreads.end()) - { - return it->second; - } - return NULL; -} - -LLDeleteHistoryThread* LLLogChat::getDeleteHistoryThread(LLUUID session_id) -{ - LLMutexLock lock(historyThreadsMutex()); - std::map::iterator it = mDeleteHistoryThreads.find(session_id); - if (it != mDeleteHistoryThreads.end()) - { - return it->second; - } - return NULL; -} - -bool LLLogChat::addLoadHistoryThread(LLUUID& session_id, LLLoadHistoryThread* lthread) -{ - LLMutexLock lock(historyThreadsMutex()); - std::map::const_iterator it = mLoadHistoryThreads.find(session_id); - if (it != mLoadHistoryThreads.end()) - { - return false; - } - mLoadHistoryThreads[session_id] = lthread; - return true; -} - -bool LLLogChat::addDeleteHistoryThread(LLUUID& session_id, LLDeleteHistoryThread* dthread) -{ - LLMutexLock lock(historyThreadsMutex()); - std::map::const_iterator it = mDeleteHistoryThreads.find(session_id); - if (it != mDeleteHistoryThreads.end()) - { - return false; - } - mDeleteHistoryThreads[session_id] = dthread; - return true; -} - -void LLLogChat::cleanupHistoryThreads() -{ - LLMutexLock lock(historyThreadsMutex()); - std::vector uuids; - std::map::iterator lit = mLoadHistoryThreads.begin(); - for (; lit != mLoadHistoryThreads.end(); lit++) - { - if (lit->second->isFinished() && mDeleteHistoryThreads[lit->first]->isFinished()) - { - delete lit->second; - delete mDeleteHistoryThreads[lit->first]; - uuids.push_back(lit->first); - } - } - std::vector::iterator uuid_it = uuids.begin(); - for ( ;uuid_it != uuids.end(); uuid_it++) - { - mLoadHistoryThreads.erase(*uuid_it); - mDeleteHistoryThreads.erase(*uuid_it); - } -} - -LLMutex* LLLogChat::historyThreadsMutex() -{ - return mHistoryThreadsMutex; -} - -void LLLogChat::triggerHistorySignal() -{ - if (NULL != mSaveHistorySignal) - { - (*mSaveHistorySignal)(); - } -} - -// static -std::string LLLogChat::oldLogFileName(std::string filename) -{ - // get Users log directory - std::string directory = gDirUtilp->getPerAccountChatLogsDir(); - - // add final OS dependent delimiter - directory += gDirUtilp->getDirDelimiter(); - - // lest make sure the file name has no invalid characters before making the pattern - filename = cleanFileName(filename); - - // create search pattern - std::string pattern = filename + ( filename == "chat" ? "-???\?-?\?-??.txt" : "-???\?-??.txt"); - - std::vector allfiles; - LLDirIterator iter(directory, pattern); - std::string scanResult; - - while (iter.next(scanResult)) - { - allfiles.push_back(scanResult); - } - - if (allfiles.size() == 0) // if no result from date search, return generic filename - { - scanResult = directory + filename + '.' + LL_TRANSCRIPT_FILE_EXTENSION; - } - else - { - sort(allfiles.begin(), allfiles.end()); - scanResult = directory + allfiles.back(); - // this file is now the most recent version of the file. - } - - return scanResult; -} - -bool LLLogChat::transcriptFilesExist() -{ - std::string pattern = "*." + LL_TRANSCRIPT_FILE_EXTENSION; - // get Users log directory - std::string dirname = gDirUtilp->getPerAccountChatLogsDir(); - - // add final OS dependent delimiter - dirname += gDirUtilp->getDirDelimiter(); - - LLDirIterator iter(dirname, pattern); - std::string filename; - while (iter.next(filename)) - { - std::string fullname = gDirUtilp->add(dirname, filename); - if (isTranscriptFileFound(fullname)) - { - return true; - } - } - return false; -} -// static -void LLLogChat::findTranscriptFiles(std::string pattern, std::vector& list_of_transcriptions) -{ - // get Users log directory - std::string dirname = gDirUtilp->getPerAccountChatLogsDir(); - - // add final OS dependent delimiter - dirname += gDirUtilp->getDirDelimiter(); - - LLDirIterator iter(dirname, pattern); - std::string filename; - while (iter.next(filename)) - { - std::string fullname = gDirUtilp->add(dirname, filename); - if (isTranscriptFileFound(fullname)) - { - list_of_transcriptions.push_back(fullname); - } - } -} - -// static -void LLLogChat::getListOfTranscriptFiles(std::vector& list_of_transcriptions) -{ - // create search pattern - std::string pattern = "*." + LL_TRANSCRIPT_FILE_EXTENSION; - findTranscriptFiles(pattern, list_of_transcriptions); -} - -// static -void LLLogChat::getListOfTranscriptBackupFiles(std::vector& list_of_transcriptions) -{ - // create search pattern - std::string pattern = "*." + LL_TRANSCRIPT_FILE_EXTENSION + ".backup*"; - findTranscriptFiles(pattern, list_of_transcriptions); -} - -boost::signals2::connection LLLogChat::setSaveHistorySignal(const save_history_signal_t::slot_type& cb) -{ - if (NULL == mSaveHistorySignal) - { - mSaveHistorySignal = new save_history_signal_t(); - } - - return mSaveHistorySignal->connect(cb); -} - -//static -bool LLLogChat::moveTranscripts(const std::string originDirectory, - const std::string targetDirectory, - std::vector& listOfFilesToMove, - std::vector& listOfFilesMoved) -{ - std::string newFullPath; - bool movedAllTranscripts = true; - std::string backupFileName; - unsigned backupFileCount; - - for (const std::string& fullpath : listOfFilesToMove) - { - backupFileCount = 0; - newFullPath = targetDirectory + fullpath.substr(originDirectory.length(), std::string::npos); - - //The target directory contains that file already, so lets store it - if(LLFile::isfile(newFullPath)) - { - backupFileName = newFullPath + ".backup"; - - //If needed store backup file as .backup1 etc. - while(LLFile::isfile(backupFileName)) - { - ++backupFileCount; - backupFileName = newFullPath + ".backup" + std::to_string(backupFileCount); - } - - //Rename the file to its backup name so it is not overwritten - LLFile::rename(newFullPath, backupFileName); - } - - S32 retry_count = 0; - while (retry_count < 5) - { - //success is zero - if (LLFile::rename(fullpath, newFullPath) != 0) - { - retry_count++; - S32 result = errno; - LL_WARNS("LLLogChat::moveTranscripts") << "Problem renaming " << fullpath << " - errorcode: " - << result << " attempt " << retry_count << LL_ENDL; - - ms_sleep(100); - } - else - { - listOfFilesMoved.push_back(newFullPath); - - if (retry_count) - { - LL_WARNS("LLLogChat::moveTranscripts") << "Successfully renamed " << fullpath << LL_ENDL; - } - break; - } - } - } - - if(listOfFilesMoved.size() != listOfFilesToMove.size()) - { - movedAllTranscripts = false; - } - - return movedAllTranscripts; -} - -//static -bool LLLogChat::moveTranscripts(const std::string currentDirectory, - const std::string newDirectory, - std::vector& listOfFilesToMove) -{ - std::vector listOfFilesMoved; - return moveTranscripts(currentDirectory, newDirectory, listOfFilesToMove, listOfFilesMoved); -} - -//static -void LLLogChat::deleteTranscripts() -{ - std::vector list_of_transcriptions; - getListOfTranscriptFiles(list_of_transcriptions); - getListOfTranscriptBackupFiles(list_of_transcriptions); - - for (const std::string& fullpath : list_of_transcriptions) - { - S32 retry_count = 0; - while (retry_count < 5) - { - if (0 != LLFile::remove(fullpath)) - { - retry_count++; - S32 result = errno; - LL_WARNS("LLLogChat::deleteTranscripts") << "Problem removing " << fullpath << " - errorcode: " - << result << " attempt " << retry_count << LL_ENDL; - - if(retry_count >= 5) - { - LL_WARNS("LLLogChat::deleteTranscripts") << "Failed to remove " << fullpath << LL_ENDL; - return; - } - - ms_sleep(100); - } - else - { - if (retry_count) - { - LL_WARNS("LLLogChat::deleteTranscripts") << "Successfully removed " << fullpath << LL_ENDL; - } - break; - } - } - } - - LLFloaterIMSessionTab::processChatHistoryStyleUpdate(true); -} - -// static -bool LLLogChat::isTranscriptExist(const LLUUID& avatar_id, bool is_group) -{ - LLAvatarName avatar_name; - LLAvatarNameCache::get(avatar_id, &avatar_name); - std::string avatar_user_name = avatar_name.getAccountName(); - if(!is_group) - { - std::replace(avatar_user_name.begin(), avatar_user_name.end(), '.', '_'); - return isTranscriptFileFound(makeLogFileName(avatar_user_name)); - } - else - { - std::string file_name; - gCacheName->getGroupName(avatar_id, file_name); - file_name = makeLogFileName(file_name + GROUP_CHAT_SUFFIX); - return isTranscriptFileFound(file_name); - } - return false; -} - -bool LLLogChat::isNearbyTranscriptExist() -{ - return isTranscriptFileFound(makeLogFileName("chat"));; -} - -bool LLLogChat::isAdHocTranscriptExist(std::string file_name) -{ - return isTranscriptFileFound(makeLogFileName(file_name));; -} - -// static -bool LLLogChat::isTranscriptFileFound(std::string fullname) -{ - bool result = false; - LLFILE * filep = LLFile::fopen(fullname, "rb"); - if (NULL != filep) - { - if (makeLogFileName("chat") == fullname) - { - LLFile::close(filep); - return true; - } - char buffer[LOG_RECALL_SIZE]; - - fseek(filep, 0, SEEK_END); // seek to end of file - S32 bytes_to_read = ftell(filep); // get current file pointer - fseek(filep, 0, SEEK_SET); // seek back to beginning of file - - // limit the number characters to read from file - if (bytes_to_read >= LOG_RECALL_SIZE) - { - bytes_to_read = LOG_RECALL_SIZE - 1; - } - - if (bytes_to_read > 0 && NULL != fgets(buffer, bytes_to_read, filep)) - { - //matching a timestamp - boost::match_results matches; - std::string line(remove_utf8_bom(buffer)); - if (ll_regex_match(line, matches, TIMESTAMP)) - { - result = true; - } - } - LLFile::close(filep); - } - return result; -} - -//*TODO mark object's names in a special way so that they will be distinguishable form avatar name -//which are more strict by its nature (only firstname and secondname) -//Example, an object's name can be written like "Object " -void LLChatLogFormatter::format(const LLSD& im, std::ostream& ostr) const -{ - if (!im.isMap()) - { - LL_WARNS() << "invalid LLSD type of an instant message" << LL_ENDL; - return; - } - - if (im[LL_IM_TIME].isDefined()) - { - std::string timestamp = im[LL_IM_TIME].asString(); - boost::trim(timestamp); - ostr << '[' << timestamp << ']' << TWO_SPACES; - } - - //*TODO mark object's names in a special way so that they will be distinguishable from avatar name - //which are more strict by its nature (only firstname and secondname) - //Example, an object's name can be written like "Object " - if (im[LL_IM_FROM].isDefined()) - { - std::string from = im[LL_IM_FROM].asString(); - boost::trim(from); - - std::size_t found = from.find(IM_SYMBOL_SEPARATOR); - std::size_t len = from.size(); - std::size_t start = 0; - while (found != std::string::npos) - { - std::size_t sub_len = found - start; - if (sub_len > 0) - { - ostr << from.substr(start, sub_len); - } - LLURI::encodeCharacter(ostr, IM_SYMBOL_SEPARATOR); - start = found + 1; - found = from.find(IM_SYMBOL_SEPARATOR, start); - } - if (start < len) - { - std::string str_end = from.substr(start, len - start); - ostr << str_end; - } - if (len > 0) - { - ostr << IM_SEPARATOR; - } - } - - if (im[LL_IM_TEXT].isDefined()) - { - std::string im_text = im[LL_IM_TEXT].asString(); - - //multilined text will be saved with prepended spaces - boost::replace_all(im_text, NEW_LINE, NEW_LINE_SPACE_PREFIX); - ostr << im_text; - } -} - -bool LLChatLogParser::parse(std::string& raw, LLSD& im, const LLSD& parse_params) -{ - if (!raw.length()) return false; - - bool cut_off_todays_date = parse_params.has("cut_off_todays_date") ? parse_params["cut_off_todays_date"].asBoolean() : true; - im = LLSD::emptyMap(); - - //matching a timestamp - boost::match_results matches; - if (!ll_regex_match(raw, matches, TIMESTAMP_AND_STUFF)) return false; - - bool has_timestamp = matches[IDX_TIMESTAMP].matched; - if (has_timestamp) - { - //timestamp was successfully parsed - std::string timestamp = matches[IDX_TIMESTAMP]; - boost::trim(timestamp); - timestamp.erase(0, 1); - timestamp.erase(timestamp.length()-1, 1); - - im[LL_IM_DATE_TIME] = timestamp; // Retain full date-time for merging chat histories - - if (cut_off_todays_date) - { - LLLogChatTimeScanner::instance().checkAndCutOffDate(timestamp); - } - - im[LL_IM_TIME] = timestamp; - } - else - { //timestamp is optional - im[LL_IM_DATE_TIME] = ""; - im[LL_IM_TIME] = ""; - } - - bool has_stuff = matches[IDX_STUFF].matched; - if (!has_stuff) - { - return false; //*TODO should return false or not? - } - - //matching a name and a text - std::string stuff = matches[IDX_STUFF]; - boost::match_results name_and_text; - if (!ll_regex_match(stuff, name_and_text, NAME_AND_TEXT)) return false; - - bool has_name = name_and_text[IDX_NAME].matched; - std::string name = LLURI::unescape(name_and_text[IDX_NAME]); - - //we don't need a name/text separator - if (has_name && name.length() && name[name.length()-1] == ':') - { - name.erase(name.length()-1, 1); - } - - if (!has_name || name == SYSTEM_FROM) - { - //name is optional too - im[LL_IM_FROM] = SYSTEM_FROM; - im[LL_IM_FROM_ID] = LLUUID::null; - } - - //possibly a case of complex object names consisting of 3+ words - if (!has_name) - { - std::string::size_type divider_pos = stuff.find(NAME_TEXT_DIVIDER); - if (divider_pos != std::string::npos && divider_pos < (stuff.length() - NAME_TEXT_DIVIDER.length())) - { - im[LL_IM_FROM] = LLURI::unescape(stuff.substr(0, divider_pos)); - im[LL_IM_TEXT] = stuff.substr(divider_pos + NAME_TEXT_DIVIDER.length()); - return true; - } - } - - if (!has_name) - { - //text is mandatory - im[LL_IM_TEXT] = stuff; - return true; //parse as a message from Second Life - } - - bool has_text = name_and_text[IDX_TEXT].matched; - if (!has_text) return false; - - //for parsing logs created in very old versions of a viewer - if (name == "You") - { - std::string agent_name; - LLAgentUI::buildFullname(agent_name); - im[LL_IM_FROM] = agent_name; - im[LL_IM_FROM_ID] = gAgentID; - } - else - { - im[LL_IM_FROM] = name; - } - - im[LL_IM_TEXT] = name_and_text[IDX_TEXT]; - return true; //parsed name and message text, maybe have a timestamp too -} - -LLDeleteHistoryThread::LLDeleteHistoryThread(std::list* messages, LLLoadHistoryThread* loadThread) - : LLActionThread("delete chat history"), - mMessages(messages), - mLoadThread(loadThread) -{ -} -LLDeleteHistoryThread::~LLDeleteHistoryThread() -{ -} -void LLDeleteHistoryThread::run() -{ - if (mLoadThread != NULL) - { - mLoadThread->waitFinished(); - } - if (NULL != mMessages) - { - delete mMessages; - } - mMessages = NULL; - setFinished(); -} - -LLActionThread::LLActionThread(const std::string& name) - : LLThread(name), - mMutex(), - mRunCondition(), - mFinished(false) -{ -} - -LLActionThread::~LLActionThread() -{ -} - -void LLActionThread::waitFinished() -{ - mMutex.lock(); - if (!mFinished) - { - mMutex.unlock(); - mRunCondition.wait(); - } - else - { - mMutex.unlock(); - } -} - -void LLActionThread::setFinished() -{ - mMutex.lock(); - mFinished = true; - mMutex.unlock(); - mRunCondition.signal(); -} - -LLLoadHistoryThread::LLLoadHistoryThread(const std::string& file_name, std::list* messages, const LLSD& load_params) - : LLActionThread("load chat history"), - mMessages(messages), - mFileName(file_name), - mLoadParams(load_params), - mNewLoad(true), - mLoadEndSignal(NULL) -{ -} - -LLLoadHistoryThread::~LLLoadHistoryThread() -{ -} - -void LLLoadHistoryThread::run() -{ - if(mNewLoad) - { - loadHistory(mFileName, mMessages, mLoadParams); - int count = mMessages->size(); - LL_INFOS() << "mMessages->size(): " << count << LL_ENDL; - setFinished(); - } -} - -void LLLoadHistoryThread::loadHistory(const std::string& file_name, std::list* messages, const LLSD& load_params) -{ - if (file_name.empty()) - { - LL_WARNS("LLLogChat::loadHistory") << "Session name is Empty!" << LL_ENDL; - return ; - } - - bool load_all_history = load_params.has("load_all_history") ? load_params["load_all_history"].asBoolean() : false; - LLFILE* fptr = LLFile::fopen(LLLogChat::makeLogFileName(file_name), "r");/*Flawfinder: ignore*/ - - if (!fptr) - { - bool is_group = load_params.has("is_group") ? load_params["is_group"].asBoolean() : false; - if (is_group) - { - std::string old_name(file_name); - old_name.erase(old_name.size() - GROUP_CHAT_SUFFIX.size()); - fptr = LLFile::fopen(LLLogChat::makeLogFileName(old_name), "r"); - if (fptr) - { - fclose(fptr); - LLFile::copy(LLLogChat::makeLogFileName(old_name), LLLogChat::makeLogFileName(file_name)); - } - fptr = LLFile::fopen(LLLogChat::makeLogFileName(file_name), "r"); - } - if (!fptr) - { - fptr = LLFile::fopen(LLLogChat::oldLogFileName(file_name), "r");/*Flawfinder: ignore*/ - if (!fptr) - { - mNewLoad = false; - (*mLoadEndSignal)(messages, file_name); - return; //No previous conversation with this name. - } - } - } - - char buffer[LOG_RECALL_SIZE]; /*Flawfinder: ignore*/ - - char *bptr; - S32 len; - bool firstline = true; - - if (load_all_history || fseek(fptr, (LOG_RECALL_SIZE - 1) * -1 , SEEK_END)) - { //We need to load the whole historyFile or it's smaller than recall size, so get it all. - firstline = false; - if (fseek(fptr, 0, SEEK_SET)) - { - fclose(fptr); - mNewLoad = false; - (*mLoadEndSignal)(messages, file_name); - return; - } - } - - - while (fgets(buffer, LOG_RECALL_SIZE, fptr) && !feof(fptr)) - { - len = strlen(buffer) - 1; /*Flawfinder: ignore*/ - - for (bptr = (buffer + len); (*bptr == '\n' || *bptr == '\r') && bptr>buffer; bptr--) *bptr='\0'; - - - if (firstline) - { - firstline = false; - continue; - } - std::string line(remove_utf8_bom(buffer)); - - //updated 1.23 plaint text log format requires a space added before subsequent lines in a multilined message - if (' ' == line[0]) - { - line.erase(0, MULTI_LINE_PREFIX.length()); - append_to_last_message(*messages, '\n' + line); - } - else if (0 == len && ('\n' == line[0] || '\r' == line[0])) - { - //to support old format's multilined messages with new lines used to divide paragraphs - append_to_last_message(*messages, line); - } - else - { - LLSD item; - if (!LLChatLogParser::parse(line, item, load_params)) - { - item[LL_IM_TEXT] = line; - } - messages->push_back(item); - } - } - - fclose(fptr); - mNewLoad = false; - (*mLoadEndSignal)(messages, file_name); -} - -boost::signals2::connection LLLoadHistoryThread::setLoadEndSignal(const load_end_signal_t::slot_type& cb) -{ - if (NULL == mLoadEndSignal) - { - mLoadEndSignal = new load_end_signal_t(); - } - - return mLoadEndSignal->connect(cb); -} - -void LLLoadHistoryThread::removeLoadEndSignal(const load_end_signal_t::slot_type& cb) -{ - if (NULL != mLoadEndSignal) - { - mLoadEndSignal->disconnect_all_slots(); - delete mLoadEndSignal; - } - mLoadEndSignal = NULL; -} +/** + * @file lllogchat.cpp + * @brief LLLogChat class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llfloaterconversationpreview.h" +#include "llagent.h" +#include "llagentui.h" +#include "llavatarnamecache.h" +#include "lllogchat.h" +#include "llregex.h" +#include "lltrans.h" +#include "llviewercontrol.h" + +#include "lldiriterator.h" +#include "llfloaterimsessiontab.h" +#include "llinstantmessage.h" +#include "llsingleton.h" // for LLSingleton + +#include +#include +#include + +#if LL_MSVC +#pragma warning(push) +// disable warning about boost::lexical_cast unreachable code +// when it fails to parse the string +#pragma warning (disable:4702) +#endif + +#include +#if LL_MSVC +#pragma warning(pop) // Restore all warnings to the previous state +#endif + +#include +#include + +const S32 LOG_RECALL_SIZE = 20480; + +const std::string LL_IM_TIME("time"); +const std::string LL_IM_DATE_TIME("datetime"); +const std::string LL_IM_TEXT("message"); +const std::string LL_IM_FROM("from"); +const std::string LL_IM_FROM_ID("from_id"); +const std::string LL_TRANSCRIPT_FILE_EXTENSION("txt"); + +const std::string GROUP_CHAT_SUFFIX(" (group)"); + +const static char IM_SYMBOL_SEPARATOR(':'); +const static std::string IM_SEPARATOR(std::string() + IM_SYMBOL_SEPARATOR + " "); +const static std::string NEW_LINE("\n"); +const static std::string NEW_LINE_SPACE_PREFIX("\n "); +const static std::string TWO_SPACES(" "); +const static std::string MULTI_LINE_PREFIX(" "); + +/** + * Chat log lines - timestamp and name are optional but message text is mandatory. + * + * Typical plain text chat log lines: + * + * SuperCar: You aren't the owner + * [2:59] SuperCar: You aren't the owner + * [2009/11/20 3:00] SuperCar: You aren't the owner + * Katar Ivercourt is Offline + * [3:00] Katar Ivercourt is Offline + * [2009/11/20 3:01] Corba ProductEngine is Offline + * + * Note: "You" was used as an avatar names in viewers of previous versions + */ +const static boost::regex TIMESTAMP_AND_STUFF("^(\\[\\d{4}/\\d{1,2}/\\d{1,2}\\s+\\d{1,2}:\\d{2}\\]\\s+|\\[\\d{1,2}:\\d{2}\\]\\s+)?(.*)$"); +const static boost::regex TIMESTAMP("^(\\[\\d{4}/\\d{1,2}/\\d{1,2}\\s+\\d{1,2}:\\d{2}\\]|\\[\\d{1,2}:\\d{2}\\]).*"); + +/** + * Regular expression suitable to match names like + * "You", "Second Life", "Igor ProductEngine", "Object", "Mega House" + */ +const static boost::regex NAME_AND_TEXT("([^:]+[:]{1})?(\\s*)(.*)"); + +/** + * These are recognizers for matching the names of ad-hoc conferences when generating the log file name + * On invited side, an ad-hoc is named like " Conference 2010/11/19 03:43 f0f4" + * On initiating side, an ad-hoc is named like Ad-hoc Conference hash" + * If the naming system for ad-hoc conferences are change in LLIMModel::LLIMSession::buildHistoryFileName() + * then these definition need to be adjusted as well. + */ +const static boost::regex INBOUND_CONFERENCE("^[a-zA-Z]{1,31} [a-zA-Z]{1,31} Conference [0-9]{4}/[0-9]{2}/[0-9]{2} [0-9]{2}:[0-9]{2} [0-9a-f]{4}"); +const static boost::regex OUTBOUND_CONFERENCE("^Ad-hoc Conference hash[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}"); + +//is used to parse complex object names like "Xstreet SL Terminal v2.2.5 st" +const static std::string NAME_TEXT_DIVIDER(": "); + +// is used for timestamps adjusting +const static char* DATE_FORMAT("%Y/%m/%d %H:%M"); +const static char* TIME_FORMAT("%H:%M"); + +const static int IDX_TIMESTAMP = 1; +const static int IDX_STUFF = 2; +const static int IDX_NAME = 1; +const static int IDX_TEXT = 3; + +using namespace boost::posix_time; +using namespace boost::gregorian; + +void append_to_last_message(std::list& messages, const std::string& line) +{ + if (!messages.size()) return; + + std::string im_text = messages.back()[LL_IM_TEXT].asString(); + im_text.append(line); + messages.back()[LL_IM_TEXT] = im_text; +} + +const char* remove_utf8_bom(const char* buf) +{ + const char* start = buf; + if (start[0] == (char)0xEF && start[1] == (char)0xBB && start[2] == (char)0xBF) + { // If string starts with the magic bytes, return pointer after it. + start += 3; + } + return start; +} + +class LLLogChatTimeScanner: public LLSingleton +{ + LLSINGLETON(LLLogChatTimeScanner); + +public: + date getTodayPacificDate() + { + typedef boost::date_time::local_adjustor pst; + typedef boost::date_time::local_adjustor pdt; + time_t t_time = time(NULL); + ptime p_time = LLStringOps::getPacificDaylightTime() + ? pdt::utc_to_local(from_time_t(t_time)) + : pst::utc_to_local(from_time_t(t_time)); + struct tm s_tm = to_tm(p_time); + return date_from_tm(s_tm); + } + + void checkAndCutOffDate(std::string& time_str) + { + // Cuts off the "%Y/%m/%d" from string for todays timestamps. + // Assume that passed string has at least "%H:%M" time format. + date log_date(not_a_date_time); + date today(getTodayPacificDate()); + + // Parse the passed date + mDateStream.str(LLStringUtil::null); + mDateStream << time_str; + mDateStream >> log_date; + mDateStream.clear(); + + days zero_days(0); + days days_alive = today - log_date; + + if ( days_alive == zero_days ) + { + // Yep, today's so strip "%Y/%m/%d" info + ptime stripped_time(not_a_date_time); + + mTimeStream.str(LLStringUtil::null); + mTimeStream << time_str; + mTimeStream >> stripped_time; + mTimeStream.clear(); + + time_str.clear(); + + mTimeStream.str(LLStringUtil::null); + mTimeStream << stripped_time; + mTimeStream >> time_str; + mTimeStream.clear(); + } + + LL_DEBUGS("LLChatLogParser") + << " log_date: " + << log_date + << " today: " + << today + << " days alive: " + << days_alive + << " new time: " + << time_str + << LL_ENDL; + } + + +private: + std::stringstream mDateStream; + std::stringstream mTimeStream; +}; + +inline +LLLogChatTimeScanner::LLLogChatTimeScanner() +{ + // Note, date/time facets will be destroyed by string streams + mDateStream.imbue(std::locale(mDateStream.getloc(), new date_input_facet(DATE_FORMAT))); + mTimeStream.imbue(std::locale(mTimeStream.getloc(), new time_facet(TIME_FORMAT))); + mTimeStream.imbue(std::locale(mTimeStream.getloc(), new time_input_facet(DATE_FORMAT))); +} + +LLLogChat::LLLogChat() +: mSaveHistorySignal(NULL) // only needed in preferences +{ + mHistoryThreadsMutex = new LLMutex(); +} + +LLLogChat::~LLLogChat() +{ + delete mHistoryThreadsMutex; + mHistoryThreadsMutex = NULL; + + if (mSaveHistorySignal) + { + mSaveHistorySignal->disconnect_all_slots(); + delete mSaveHistorySignal; + mSaveHistorySignal = NULL; + } +} + + +//static +std::string LLLogChat::makeLogFileName(std::string filename) +{ + /** + * Testing for in bound and out bound ad-hoc file names + * if it is then skip date stamping. + **/ + + boost::match_results matches; + bool inboundConf = ll_regex_match(filename, matches, INBOUND_CONFERENCE); + bool outboundConf = ll_regex_match(filename, matches, OUTBOUND_CONFERENCE); + if (!(inboundConf || outboundConf)) + { + if( gSavedPerAccountSettings.getBOOL("LogFileNamewithDate") ) + { + time_t now; + time(&now); + char dbuffer[20]; /* Flawfinder: ignore */ + if (filename == "chat") + { + strftime(dbuffer, 20, "-%Y-%m-%d", localtime(&now)); + } + else + { + strftime(dbuffer, 20, "-%Y-%m", localtime(&now)); + } + filename += dbuffer; + } + } + + filename = cleanFileName(filename); + filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_ACCOUNT_CHAT_LOGS, filename); + if (!filename.empty()) + { + filename += '.' + LL_TRANSCRIPT_FILE_EXTENSION; + } + + return filename; +} + +//static +void LLLogChat::renameLogFile(const std::string& old_filename, const std::string& new_filename) +{ + std::string new_name = cleanFileName(new_filename); + std::string old_name = cleanFileName(old_filename); + new_name = gDirUtilp->getExpandedFilename(LL_PATH_PER_ACCOUNT_CHAT_LOGS, new_name); + old_name = gDirUtilp->getExpandedFilename(LL_PATH_PER_ACCOUNT_CHAT_LOGS, old_name); + + if (new_name.empty() || old_name.empty()) + { + return; + } + + new_name += '.' + LL_TRANSCRIPT_FILE_EXTENSION; + old_name += '.' + LL_TRANSCRIPT_FILE_EXTENSION; + + if (!LLFile::isfile(new_name) && LLFile::isfile(old_name)) + { + LLFile::rename(old_name, new_name); + } +} + +std::string LLLogChat::cleanFileName(std::string filename) +{ + std::string invalidChars = "\"\'\\/?*:.<>|[]{}~"; // Cannot match glob or illegal filename chars + std::string::size_type position = filename.find_first_of(invalidChars); + while (position != filename.npos) + { + filename[position] = '_'; + position = filename.find_first_of(invalidChars, position); + } + return filename; +} + +std::string LLLogChat::timestamp2LogString(U32 timestamp, bool withdate) +{ + std::string timeStr; + if (withdate) + { + timeStr = "[" + LLTrans::getString ("TimeYear") + "]/[" + + LLTrans::getString ("TimeMonth") + "]/[" + + LLTrans::getString ("TimeDay") + "] [" + + LLTrans::getString ("TimeHour") + "]:[" + + LLTrans::getString ("TimeMin") + "]"; + } + else + { + timeStr = "[" + LLTrans::getString("TimeHour") + "]:[" + + LLTrans::getString ("TimeMin")+"]"; + } + + LLSD substitution; + if (timestamp == 0) + { + substitution["datetime"] = (S32)time_corrected(); + } + else + { // timestamp is correct utc already + substitution["datetime"] = (S32)timestamp; + } + + LLStringUtil::format (timeStr, substitution); + return timeStr; +} + + +//static +void LLLogChat::saveHistory(const std::string& filename, + const std::string& from, + const LLUUID& from_id, + const std::string& line) +{ + std::string tmp_filename = filename; + LLStringUtil::trim(tmp_filename); + if (tmp_filename.empty()) + { + std::string warn = "Chat history filename [" + filename + "] is empty!"; + LL_WARNS() << warn << LL_ENDL; + llassert(tmp_filename.size()); + return; + } + + llofstream file(LLLogChat::makeLogFileName(filename).c_str(), std::ios_base::app); + if (!file.is_open()) + { + LL_WARNS() << "Couldn't open chat history log! - " + filename << LL_ENDL; + return; + } + + LLSD item; + + if (gSavedPerAccountSettings.getBOOL("LogTimestamp")) + item["time"] = LLLogChat::timestamp2LogString(0, gSavedPerAccountSettings.getBOOL("LogTimestampDate")); + + item["from_id"] = from_id; + item["message"] = line; + + //adding "Second Life:" for all system messages to make chat log history parsing more reliable + if (from.empty() && from_id.isNull()) + { + item["from"] = SYSTEM_FROM; + } + else + { + item["from"] = from; + } + + file << LLChatLogFormatter(item) << std::endl; + + file.close(); + + LLLogChat::getInstance()->triggerHistorySignal(); +} + +// static +void LLLogChat::loadChatHistory(const std::string& file_name, std::list& messages, const LLSD& load_params, bool is_group) +{ + if (file_name.empty()) + { + LL_WARNS("LLLogChat::loadChatHistory") << "Local history file name is empty!" << LL_ENDL; + return ; + } + + bool load_all_history = load_params.has("load_all_history") ? load_params["load_all_history"].asBoolean() : false; + + // Stat the file to find it and get the last history entry time + llstat stat_data; + + std::string log_file_name = LLLogChat::makeLogFileName(file_name); + LL_DEBUGS("ChatHistory") << "First attempt to stat chat history file " << log_file_name << LL_ENDL; + + S32 no_stat = LLFile::stat(log_file_name, &stat_data); + + if (no_stat) + { + if (is_group) + { + std::string old_name(file_name); + old_name.erase(old_name.size() - GROUP_CHAT_SUFFIX.size()); // trim off " (group)" + log_file_name = LLLogChat::makeLogFileName(old_name); + LL_DEBUGS("ChatHistory") << "Attempting to stat adjusted chat history file " << log_file_name << LL_ENDL; + no_stat = LLFile::stat(log_file_name, &stat_data); + if (!no_stat) + { // Found it without "(group)", copy to new naming style. We already have the mod time in stat_data + log_file_name = LLLogChat::makeLogFileName(file_name); + LL_DEBUGS("ChatHistory") << "Attempt to stat copied history file " << log_file_name << LL_ENDL; + LLFile::copy(LLLogChat::makeLogFileName(old_name), log_file_name); + } + } + if (no_stat) + { + log_file_name = LLLogChat::oldLogFileName(file_name); + LL_DEBUGS("ChatHistory") << "Attempt to stat old history file name " << log_file_name << LL_ENDL; + no_stat = LLFile::stat(log_file_name, &stat_data); + if (no_stat) + { + LL_DEBUGS("ChatHistory") << "No previous conversation log file found for " << file_name << LL_ENDL; + return; //No previous conversation with this name. + } + } + } + + // If we got here, we managed to stat the file. + // Open the file to read + LLFILE* fptr = LLFile::fopen(log_file_name, "r"); /*Flawfinder: ignore*/ + if (!fptr) + { // Ok, this is strange but not really tragic in the big picture of things + LL_WARNS("ChatHistory") << "Unable to read file " << log_file_name << " after stat was successful" << LL_ENDL; + return; + } + + S32 save_num_messages = messages.size(); + + char buffer[LOG_RECALL_SIZE]; /*Flawfinder: ignore*/ + char *bptr; + S32 len; + bool firstline = true; + + if (load_all_history || fseek(fptr, (LOG_RECALL_SIZE - 1) * -1 , SEEK_END)) + { //We need to load the whole historyFile or it's smaller than recall size, so get it all. + firstline = false; + if (fseek(fptr, 0, SEEK_SET)) + { + fclose(fptr); + return; + } + } + while (fgets(buffer, LOG_RECALL_SIZE, fptr) && !feof(fptr)) + { + len = strlen(buffer) - 1; /*Flawfinder: ignore*/ + // backfill any end of line characters with nulls + for (bptr = (buffer + len); (*bptr == '\n' || *bptr == '\r') && bptr>buffer; bptr--) *bptr='\0'; + + if (firstline) + { + firstline = false; + continue; + } + + std::string line(remove_utf8_bom(buffer)); + + //updated 1.23 plain text log format requires a space added before subsequent lines in a multilined message + if (' ' == line[0]) + { + line.erase(0, MULTI_LINE_PREFIX.length()); + append_to_last_message(messages, '\n' + line); + } + else if (0 == len && ('\n' == line[0] || '\r' == line[0])) + { + //to support old format's multilined messages with new lines used to divide paragraphs + append_to_last_message(messages, line); + } + else + { + LLSD item; + if (!LLChatLogParser::parse(line, item, load_params)) + { + item[LL_IM_TEXT] = line; + } + messages.push_back(item); + } + } + fclose(fptr); + + LL_DEBUGS("ChatHistory") << "Read " << (messages.size() - save_num_messages) + << " messages of chat history from " << log_file_name + << " file mod time " << (F64)stat_data.st_mtime << LL_ENDL; +} + +bool LLLogChat::historyThreadsFinished(LLUUID session_id) +{ + LLMutexLock lock(historyThreadsMutex()); + bool finished = true; + std::map::iterator it = mLoadHistoryThreads.find(session_id); + if (it != mLoadHistoryThreads.end()) + { + finished = it->second->isFinished(); + } + if (!finished) + { + return false; + } + std::map::iterator dit = mDeleteHistoryThreads.find(session_id); + if (dit != mDeleteHistoryThreads.end()) + { + finished = finished && dit->second->isFinished(); + } + return finished; +} + +LLLoadHistoryThread* LLLogChat::getLoadHistoryThread(LLUUID session_id) +{ + LLMutexLock lock(historyThreadsMutex()); + std::map::iterator it = mLoadHistoryThreads.find(session_id); + if (it != mLoadHistoryThreads.end()) + { + return it->second; + } + return NULL; +} + +LLDeleteHistoryThread* LLLogChat::getDeleteHistoryThread(LLUUID session_id) +{ + LLMutexLock lock(historyThreadsMutex()); + std::map::iterator it = mDeleteHistoryThreads.find(session_id); + if (it != mDeleteHistoryThreads.end()) + { + return it->second; + } + return NULL; +} + +bool LLLogChat::addLoadHistoryThread(LLUUID& session_id, LLLoadHistoryThread* lthread) +{ + LLMutexLock lock(historyThreadsMutex()); + std::map::const_iterator it = mLoadHistoryThreads.find(session_id); + if (it != mLoadHistoryThreads.end()) + { + return false; + } + mLoadHistoryThreads[session_id] = lthread; + return true; +} + +bool LLLogChat::addDeleteHistoryThread(LLUUID& session_id, LLDeleteHistoryThread* dthread) +{ + LLMutexLock lock(historyThreadsMutex()); + std::map::const_iterator it = mDeleteHistoryThreads.find(session_id); + if (it != mDeleteHistoryThreads.end()) + { + return false; + } + mDeleteHistoryThreads[session_id] = dthread; + return true; +} + +void LLLogChat::cleanupHistoryThreads() +{ + LLMutexLock lock(historyThreadsMutex()); + std::vector uuids; + std::map::iterator lit = mLoadHistoryThreads.begin(); + for (; lit != mLoadHistoryThreads.end(); lit++) + { + if (lit->second->isFinished() && mDeleteHistoryThreads[lit->first]->isFinished()) + { + delete lit->second; + delete mDeleteHistoryThreads[lit->first]; + uuids.push_back(lit->first); + } + } + std::vector::iterator uuid_it = uuids.begin(); + for ( ;uuid_it != uuids.end(); uuid_it++) + { + mLoadHistoryThreads.erase(*uuid_it); + mDeleteHistoryThreads.erase(*uuid_it); + } +} + +LLMutex* LLLogChat::historyThreadsMutex() +{ + return mHistoryThreadsMutex; +} + +void LLLogChat::triggerHistorySignal() +{ + if (NULL != mSaveHistorySignal) + { + (*mSaveHistorySignal)(); + } +} + +// static +std::string LLLogChat::oldLogFileName(std::string filename) +{ + // get Users log directory + std::string directory = gDirUtilp->getPerAccountChatLogsDir(); + + // add final OS dependent delimiter + directory += gDirUtilp->getDirDelimiter(); + + // lest make sure the file name has no invalid characters before making the pattern + filename = cleanFileName(filename); + + // create search pattern + std::string pattern = filename + ( filename == "chat" ? "-???\?-?\?-??.txt" : "-???\?-??.txt"); + + std::vector allfiles; + LLDirIterator iter(directory, pattern); + std::string scanResult; + + while (iter.next(scanResult)) + { + allfiles.push_back(scanResult); + } + + if (allfiles.size() == 0) // if no result from date search, return generic filename + { + scanResult = directory + filename + '.' + LL_TRANSCRIPT_FILE_EXTENSION; + } + else + { + sort(allfiles.begin(), allfiles.end()); + scanResult = directory + allfiles.back(); + // this file is now the most recent version of the file. + } + + return scanResult; +} + +bool LLLogChat::transcriptFilesExist() +{ + std::string pattern = "*." + LL_TRANSCRIPT_FILE_EXTENSION; + // get Users log directory + std::string dirname = gDirUtilp->getPerAccountChatLogsDir(); + + // add final OS dependent delimiter + dirname += gDirUtilp->getDirDelimiter(); + + LLDirIterator iter(dirname, pattern); + std::string filename; + while (iter.next(filename)) + { + std::string fullname = gDirUtilp->add(dirname, filename); + if (isTranscriptFileFound(fullname)) + { + return true; + } + } + return false; +} +// static +void LLLogChat::findTranscriptFiles(std::string pattern, std::vector& list_of_transcriptions) +{ + // get Users log directory + std::string dirname = gDirUtilp->getPerAccountChatLogsDir(); + + // add final OS dependent delimiter + dirname += gDirUtilp->getDirDelimiter(); + + LLDirIterator iter(dirname, pattern); + std::string filename; + while (iter.next(filename)) + { + std::string fullname = gDirUtilp->add(dirname, filename); + if (isTranscriptFileFound(fullname)) + { + list_of_transcriptions.push_back(fullname); + } + } +} + +// static +void LLLogChat::getListOfTranscriptFiles(std::vector& list_of_transcriptions) +{ + // create search pattern + std::string pattern = "*." + LL_TRANSCRIPT_FILE_EXTENSION; + findTranscriptFiles(pattern, list_of_transcriptions); +} + +// static +void LLLogChat::getListOfTranscriptBackupFiles(std::vector& list_of_transcriptions) +{ + // create search pattern + std::string pattern = "*." + LL_TRANSCRIPT_FILE_EXTENSION + ".backup*"; + findTranscriptFiles(pattern, list_of_transcriptions); +} + +boost::signals2::connection LLLogChat::setSaveHistorySignal(const save_history_signal_t::slot_type& cb) +{ + if (NULL == mSaveHistorySignal) + { + mSaveHistorySignal = new save_history_signal_t(); + } + + return mSaveHistorySignal->connect(cb); +} + +//static +bool LLLogChat::moveTranscripts(const std::string originDirectory, + const std::string targetDirectory, + std::vector& listOfFilesToMove, + std::vector& listOfFilesMoved) +{ + std::string newFullPath; + bool movedAllTranscripts = true; + std::string backupFileName; + unsigned backupFileCount; + + for (const std::string& fullpath : listOfFilesToMove) + { + backupFileCount = 0; + newFullPath = targetDirectory + fullpath.substr(originDirectory.length(), std::string::npos); + + //The target directory contains that file already, so lets store it + if(LLFile::isfile(newFullPath)) + { + backupFileName = newFullPath + ".backup"; + + //If needed store backup file as .backup1 etc. + while(LLFile::isfile(backupFileName)) + { + ++backupFileCount; + backupFileName = newFullPath + ".backup" + std::to_string(backupFileCount); + } + + //Rename the file to its backup name so it is not overwritten + LLFile::rename(newFullPath, backupFileName); + } + + S32 retry_count = 0; + while (retry_count < 5) + { + //success is zero + if (LLFile::rename(fullpath, newFullPath) != 0) + { + retry_count++; + S32 result = errno; + LL_WARNS("LLLogChat::moveTranscripts") << "Problem renaming " << fullpath << " - errorcode: " + << result << " attempt " << retry_count << LL_ENDL; + + ms_sleep(100); + } + else + { + listOfFilesMoved.push_back(newFullPath); + + if (retry_count) + { + LL_WARNS("LLLogChat::moveTranscripts") << "Successfully renamed " << fullpath << LL_ENDL; + } + break; + } + } + } + + if(listOfFilesMoved.size() != listOfFilesToMove.size()) + { + movedAllTranscripts = false; + } + + return movedAllTranscripts; +} + +//static +bool LLLogChat::moveTranscripts(const std::string currentDirectory, + const std::string newDirectory, + std::vector& listOfFilesToMove) +{ + std::vector listOfFilesMoved; + return moveTranscripts(currentDirectory, newDirectory, listOfFilesToMove, listOfFilesMoved); +} + +//static +void LLLogChat::deleteTranscripts() +{ + std::vector list_of_transcriptions; + getListOfTranscriptFiles(list_of_transcriptions); + getListOfTranscriptBackupFiles(list_of_transcriptions); + + for (const std::string& fullpath : list_of_transcriptions) + { + S32 retry_count = 0; + while (retry_count < 5) + { + if (0 != LLFile::remove(fullpath)) + { + retry_count++; + S32 result = errno; + LL_WARNS("LLLogChat::deleteTranscripts") << "Problem removing " << fullpath << " - errorcode: " + << result << " attempt " << retry_count << LL_ENDL; + + if(retry_count >= 5) + { + LL_WARNS("LLLogChat::deleteTranscripts") << "Failed to remove " << fullpath << LL_ENDL; + return; + } + + ms_sleep(100); + } + else + { + if (retry_count) + { + LL_WARNS("LLLogChat::deleteTranscripts") << "Successfully removed " << fullpath << LL_ENDL; + } + break; + } + } + } + + LLFloaterIMSessionTab::processChatHistoryStyleUpdate(true); +} + +// static +bool LLLogChat::isTranscriptExist(const LLUUID& avatar_id, bool is_group) +{ + LLAvatarName avatar_name; + LLAvatarNameCache::get(avatar_id, &avatar_name); + std::string avatar_user_name = avatar_name.getAccountName(); + if(!is_group) + { + std::replace(avatar_user_name.begin(), avatar_user_name.end(), '.', '_'); + return isTranscriptFileFound(makeLogFileName(avatar_user_name)); + } + else + { + std::string file_name; + gCacheName->getGroupName(avatar_id, file_name); + file_name = makeLogFileName(file_name + GROUP_CHAT_SUFFIX); + return isTranscriptFileFound(file_name); + } + return false; +} + +bool LLLogChat::isNearbyTranscriptExist() +{ + return isTranscriptFileFound(makeLogFileName("chat"));; +} + +bool LLLogChat::isAdHocTranscriptExist(std::string file_name) +{ + return isTranscriptFileFound(makeLogFileName(file_name));; +} + +// static +bool LLLogChat::isTranscriptFileFound(std::string fullname) +{ + bool result = false; + LLFILE * filep = LLFile::fopen(fullname, "rb"); + if (NULL != filep) + { + if (makeLogFileName("chat") == fullname) + { + LLFile::close(filep); + return true; + } + char buffer[LOG_RECALL_SIZE]; + + fseek(filep, 0, SEEK_END); // seek to end of file + S32 bytes_to_read = ftell(filep); // get current file pointer + fseek(filep, 0, SEEK_SET); // seek back to beginning of file + + // limit the number characters to read from file + if (bytes_to_read >= LOG_RECALL_SIZE) + { + bytes_to_read = LOG_RECALL_SIZE - 1; + } + + if (bytes_to_read > 0 && NULL != fgets(buffer, bytes_to_read, filep)) + { + //matching a timestamp + boost::match_results matches; + std::string line(remove_utf8_bom(buffer)); + if (ll_regex_match(line, matches, TIMESTAMP)) + { + result = true; + } + } + LLFile::close(filep); + } + return result; +} + +//*TODO mark object's names in a special way so that they will be distinguishable form avatar name +//which are more strict by its nature (only firstname and secondname) +//Example, an object's name can be written like "Object " +void LLChatLogFormatter::format(const LLSD& im, std::ostream& ostr) const +{ + if (!im.isMap()) + { + LL_WARNS() << "invalid LLSD type of an instant message" << LL_ENDL; + return; + } + + if (im[LL_IM_TIME].isDefined()) + { + std::string timestamp = im[LL_IM_TIME].asString(); + boost::trim(timestamp); + ostr << '[' << timestamp << ']' << TWO_SPACES; + } + + //*TODO mark object's names in a special way so that they will be distinguishable from avatar name + //which are more strict by its nature (only firstname and secondname) + //Example, an object's name can be written like "Object " + if (im[LL_IM_FROM].isDefined()) + { + std::string from = im[LL_IM_FROM].asString(); + boost::trim(from); + + std::size_t found = from.find(IM_SYMBOL_SEPARATOR); + std::size_t len = from.size(); + std::size_t start = 0; + while (found != std::string::npos) + { + std::size_t sub_len = found - start; + if (sub_len > 0) + { + ostr << from.substr(start, sub_len); + } + LLURI::encodeCharacter(ostr, IM_SYMBOL_SEPARATOR); + start = found + 1; + found = from.find(IM_SYMBOL_SEPARATOR, start); + } + if (start < len) + { + std::string str_end = from.substr(start, len - start); + ostr << str_end; + } + if (len > 0) + { + ostr << IM_SEPARATOR; + } + } + + if (im[LL_IM_TEXT].isDefined()) + { + std::string im_text = im[LL_IM_TEXT].asString(); + + //multilined text will be saved with prepended spaces + boost::replace_all(im_text, NEW_LINE, NEW_LINE_SPACE_PREFIX); + ostr << im_text; + } +} + +bool LLChatLogParser::parse(std::string& raw, LLSD& im, const LLSD& parse_params) +{ + if (!raw.length()) return false; + + bool cut_off_todays_date = parse_params.has("cut_off_todays_date") ? parse_params["cut_off_todays_date"].asBoolean() : true; + im = LLSD::emptyMap(); + + //matching a timestamp + boost::match_results matches; + if (!ll_regex_match(raw, matches, TIMESTAMP_AND_STUFF)) return false; + + bool has_timestamp = matches[IDX_TIMESTAMP].matched; + if (has_timestamp) + { + //timestamp was successfully parsed + std::string timestamp = matches[IDX_TIMESTAMP]; + boost::trim(timestamp); + timestamp.erase(0, 1); + timestamp.erase(timestamp.length()-1, 1); + + im[LL_IM_DATE_TIME] = timestamp; // Retain full date-time for merging chat histories + + if (cut_off_todays_date) + { + LLLogChatTimeScanner::instance().checkAndCutOffDate(timestamp); + } + + im[LL_IM_TIME] = timestamp; + } + else + { //timestamp is optional + im[LL_IM_DATE_TIME] = ""; + im[LL_IM_TIME] = ""; + } + + bool has_stuff = matches[IDX_STUFF].matched; + if (!has_stuff) + { + return false; //*TODO should return false or not? + } + + //matching a name and a text + std::string stuff = matches[IDX_STUFF]; + boost::match_results name_and_text; + if (!ll_regex_match(stuff, name_and_text, NAME_AND_TEXT)) return false; + + bool has_name = name_and_text[IDX_NAME].matched; + std::string name = LLURI::unescape(name_and_text[IDX_NAME]); + + //we don't need a name/text separator + if (has_name && name.length() && name[name.length()-1] == ':') + { + name.erase(name.length()-1, 1); + } + + if (!has_name || name == SYSTEM_FROM) + { + //name is optional too + im[LL_IM_FROM] = SYSTEM_FROM; + im[LL_IM_FROM_ID] = LLUUID::null; + } + + //possibly a case of complex object names consisting of 3+ words + if (!has_name) + { + std::string::size_type divider_pos = stuff.find(NAME_TEXT_DIVIDER); + if (divider_pos != std::string::npos && divider_pos < (stuff.length() - NAME_TEXT_DIVIDER.length())) + { + im[LL_IM_FROM] = LLURI::unescape(stuff.substr(0, divider_pos)); + im[LL_IM_TEXT] = stuff.substr(divider_pos + NAME_TEXT_DIVIDER.length()); + return true; + } + } + + if (!has_name) + { + //text is mandatory + im[LL_IM_TEXT] = stuff; + return true; //parse as a message from Second Life + } + + bool has_text = name_and_text[IDX_TEXT].matched; + if (!has_text) return false; + + //for parsing logs created in very old versions of a viewer + if (name == "You") + { + std::string agent_name; + LLAgentUI::buildFullname(agent_name); + im[LL_IM_FROM] = agent_name; + im[LL_IM_FROM_ID] = gAgentID; + } + else + { + im[LL_IM_FROM] = name; + } + + im[LL_IM_TEXT] = name_and_text[IDX_TEXT]; + return true; //parsed name and message text, maybe have a timestamp too +} + +LLDeleteHistoryThread::LLDeleteHistoryThread(std::list* messages, LLLoadHistoryThread* loadThread) + : LLActionThread("delete chat history"), + mMessages(messages), + mLoadThread(loadThread) +{ +} +LLDeleteHistoryThread::~LLDeleteHistoryThread() +{ +} +void LLDeleteHistoryThread::run() +{ + if (mLoadThread != NULL) + { + mLoadThread->waitFinished(); + } + if (NULL != mMessages) + { + delete mMessages; + } + mMessages = NULL; + setFinished(); +} + +LLActionThread::LLActionThread(const std::string& name) + : LLThread(name), + mMutex(), + mRunCondition(), + mFinished(false) +{ +} + +LLActionThread::~LLActionThread() +{ +} + +void LLActionThread::waitFinished() +{ + mMutex.lock(); + if (!mFinished) + { + mMutex.unlock(); + mRunCondition.wait(); + } + else + { + mMutex.unlock(); + } +} + +void LLActionThread::setFinished() +{ + mMutex.lock(); + mFinished = true; + mMutex.unlock(); + mRunCondition.signal(); +} + +LLLoadHistoryThread::LLLoadHistoryThread(const std::string& file_name, std::list* messages, const LLSD& load_params) + : LLActionThread("load chat history"), + mMessages(messages), + mFileName(file_name), + mLoadParams(load_params), + mNewLoad(true), + mLoadEndSignal(NULL) +{ +} + +LLLoadHistoryThread::~LLLoadHistoryThread() +{ +} + +void LLLoadHistoryThread::run() +{ + if(mNewLoad) + { + loadHistory(mFileName, mMessages, mLoadParams); + int count = mMessages->size(); + LL_INFOS() << "mMessages->size(): " << count << LL_ENDL; + setFinished(); + } +} + +void LLLoadHistoryThread::loadHistory(const std::string& file_name, std::list* messages, const LLSD& load_params) +{ + if (file_name.empty()) + { + LL_WARNS("LLLogChat::loadHistory") << "Session name is Empty!" << LL_ENDL; + return ; + } + + bool load_all_history = load_params.has("load_all_history") ? load_params["load_all_history"].asBoolean() : false; + LLFILE* fptr = LLFile::fopen(LLLogChat::makeLogFileName(file_name), "r");/*Flawfinder: ignore*/ + + if (!fptr) + { + bool is_group = load_params.has("is_group") ? load_params["is_group"].asBoolean() : false; + if (is_group) + { + std::string old_name(file_name); + old_name.erase(old_name.size() - GROUP_CHAT_SUFFIX.size()); + fptr = LLFile::fopen(LLLogChat::makeLogFileName(old_name), "r"); + if (fptr) + { + fclose(fptr); + LLFile::copy(LLLogChat::makeLogFileName(old_name), LLLogChat::makeLogFileName(file_name)); + } + fptr = LLFile::fopen(LLLogChat::makeLogFileName(file_name), "r"); + } + if (!fptr) + { + fptr = LLFile::fopen(LLLogChat::oldLogFileName(file_name), "r");/*Flawfinder: ignore*/ + if (!fptr) + { + mNewLoad = false; + (*mLoadEndSignal)(messages, file_name); + return; //No previous conversation with this name. + } + } + } + + char buffer[LOG_RECALL_SIZE]; /*Flawfinder: ignore*/ + + char *bptr; + S32 len; + bool firstline = true; + + if (load_all_history || fseek(fptr, (LOG_RECALL_SIZE - 1) * -1 , SEEK_END)) + { //We need to load the whole historyFile or it's smaller than recall size, so get it all. + firstline = false; + if (fseek(fptr, 0, SEEK_SET)) + { + fclose(fptr); + mNewLoad = false; + (*mLoadEndSignal)(messages, file_name); + return; + } + } + + + while (fgets(buffer, LOG_RECALL_SIZE, fptr) && !feof(fptr)) + { + len = strlen(buffer) - 1; /*Flawfinder: ignore*/ + + for (bptr = (buffer + len); (*bptr == '\n' || *bptr == '\r') && bptr>buffer; bptr--) *bptr='\0'; + + + if (firstline) + { + firstline = false; + continue; + } + std::string line(remove_utf8_bom(buffer)); + + //updated 1.23 plaint text log format requires a space added before subsequent lines in a multilined message + if (' ' == line[0]) + { + line.erase(0, MULTI_LINE_PREFIX.length()); + append_to_last_message(*messages, '\n' + line); + } + else if (0 == len && ('\n' == line[0] || '\r' == line[0])) + { + //to support old format's multilined messages with new lines used to divide paragraphs + append_to_last_message(*messages, line); + } + else + { + LLSD item; + if (!LLChatLogParser::parse(line, item, load_params)) + { + item[LL_IM_TEXT] = line; + } + messages->push_back(item); + } + } + + fclose(fptr); + mNewLoad = false; + (*mLoadEndSignal)(messages, file_name); +} + +boost::signals2::connection LLLoadHistoryThread::setLoadEndSignal(const load_end_signal_t::slot_type& cb) +{ + if (NULL == mLoadEndSignal) + { + mLoadEndSignal = new load_end_signal_t(); + } + + return mLoadEndSignal->connect(cb); +} + +void LLLoadHistoryThread::removeLoadEndSignal(const load_end_signal_t::slot_type& cb) +{ + if (NULL != mLoadEndSignal) + { + mLoadEndSignal->disconnect_all_slots(); + delete mLoadEndSignal; + } + mLoadEndSignal = NULL; +} diff --git a/indra/newview/lllogininstance.cpp b/indra/newview/lllogininstance.cpp index c24a13f3b2..282a273be6 100644 --- a/indra/newview/lllogininstance.cpp +++ b/indra/newview/lllogininstance.cpp @@ -1,630 +1,630 @@ -/** - * @file lllogininstance.cpp - * @brief Viewer's host for a login connection. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lllogininstance.h" - -// llcommon -#include "llevents.h" -#include "stringize.h" -#include "llsdserialize.h" - -// llmessage (!) -#include "llfiltersd2xmlrpc.h" // for xml_escape_string() - -// login -#include "lllogin.h" - -// newview -#include "llhasheduniqueid.h" -#include "llviewernetwork.h" -#include "llviewercontrol.h" -#include "llversioninfo.h" -#include "llslurl.h" -#include "llstartup.h" -#include "llfloaterreg.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "llwindow.h" -#include "llviewerwindow.h" -#include "llprogressview.h" -#include "llsecapi.h" -#include "llstartup.h" -#include "llmachineid.h" -#include "llevents.h" -#include "llappviewer.h" -#include "llsdserialize.h" -#include "lltrans.h" - -#include -#include -#include - -const S32 LOGIN_MAX_RETRIES = 0; // Viewer should not autmatically retry login -const F32 LOGIN_SRV_TIMEOUT_MIN = 10; -const F32 LOGIN_SRV_TIMEOUT_MAX = 120; -const F32 LOGIN_DNS_TIMEOUT_FACTOR = 0.9; // make DNS wait shorter then retry time - -class LLLoginInstance::Disposable { -public: - virtual ~Disposable() {} -}; - -static const char * const TOS_REPLY_PUMP = "lllogininstance_tos_callback"; -static const char * const TOS_LISTENER_NAME = "lllogininstance_tos"; - -std::string construct_start_string(); - -// LLLoginInstance -//----------------------------------------------------------------------------- - - -LLLoginInstance::LLLoginInstance() : - mLoginModule(new LLLogin()), - mNotifications(NULL), - mLoginState("offline"), - mSaveMFA(true), - mAttemptComplete(false), - mTransferRate(0.0f), - mDispatcher("LLLoginInstance", "change") -{ - mLoginModule->getEventPump().listen("lllogininstance", - boost::bind(&LLLoginInstance::handleLoginEvent, this, _1)); - // This internal use of LLEventDispatcher doesn't really need - // per-function descriptions. - mDispatcher.add("fail.login", "", boost::bind(&LLLoginInstance::handleLoginFailure, this, _1)); - mDispatcher.add("connect", "", boost::bind(&LLLoginInstance::handleLoginSuccess, this, _1)); - mDispatcher.add("disconnect", "", boost::bind(&LLLoginInstance::handleDisconnect, this, _1)); - mDispatcher.add("indeterminate", "", boost::bind(&LLLoginInstance::handleIndeterminate, this, _1)); -} - -void LLLoginInstance::setPlatformInfo(const std::string platform, - const std::string platform_version, - const std::string platform_name) -{ - mPlatform = platform; - mPlatformVersion = platform_version; - mPlatformVersionName = platform_name; -} - -LLLoginInstance::~LLLoginInstance() -{ -} - -void LLLoginInstance::connect(LLPointer credentials) -{ - std::vector uris; - LLGridManager::getInstance()->getLoginURIs(uris); - if (uris.size() < 1) - { - LL_WARNS() << "Failed to get login URIs during connect. No connect for you!" << LL_ENDL; - return; - } - connect(uris.front(), credentials); -} - -void LLLoginInstance::connect(const std::string& uri, LLPointer credentials) -{ - mAttemptComplete = false; // Reset attempt complete at this point! - constructAuthParams(credentials); - mLoginModule->connect(uri, mRequestData); -} - -void LLLoginInstance::reconnect() -{ - // Sort of like connect, only using the pre-existing - // request params. - std::vector uris; - LLGridManager::getInstance()->getLoginURIs(uris); - mLoginModule->connect(uris.front(), mRequestData); - gViewerWindow->setShowProgress(true); -} - -void LLLoginInstance::disconnect() -{ - mAttemptComplete = false; // Reset attempt complete at this point! - mRequestData.clear(); - mLoginModule->disconnect(); -} - -LLSD LLLoginInstance::getResponse() -{ - return mResponseData; -} - -void LLLoginInstance::constructAuthParams(LLPointer user_credential) -{ - // Set up auth request options. -//#define LL_MINIMIAL_REQUESTED_OPTIONS - LLSD requested_options; - // *Note: this is where gUserAuth used to be created. - requested_options.append("inventory-root"); - requested_options.append("inventory-skeleton"); - //requested_options.append("inventory-meat"); - //requested_options.append("inventory-skel-targets"); -#if (!defined LL_MINIMIAL_REQUESTED_OPTIONS) - - // Not requesting library will trigger mFatalNoLibraryRootFolder - requested_options.append("inventory-lib-root"); - requested_options.append("inventory-lib-owner"); - requested_options.append("inventory-skel-lib"); - // requested_options.append("inventory-meat-lib"); - - requested_options.append("initial-outfit"); - requested_options.append("gestures"); - requested_options.append("display_names"); - requested_options.append("event_categories"); - requested_options.append("event_notifications"); - requested_options.append("classified_categories"); - requested_options.append("adult_compliant"); - requested_options.append("buddy-list"); - requested_options.append("newuser-config"); - requested_options.append("ui-config"); - - //send this info to login.cgi for stats gathering - //since viewerstats isn't reliable enough - requested_options.append("advanced-mode"); - -#endif - requested_options.append("max-agent-groups"); - requested_options.append("map-server-url"); - requested_options.append("voice-config"); - requested_options.append("tutorial_setting"); - requested_options.append("login-flags"); - requested_options.append("global-textures"); - if(gSavedSettings.getBOOL("ConnectAsGod")) - { - gSavedSettings.setBOOL("UseDebugMenus", true); - requested_options.append("god-connect"); - } - - LLSD request_params; - - unsigned char hashed_unique_id_string[MD5HEX_STR_SIZE]; - if ( ! llHashedUniqueID(hashed_unique_id_string) ) - { - - LL_WARNS("LLLogin") << "Not providing a unique id in request params" << LL_ENDL; - - } - request_params["start"] = construct_start_string(); - request_params["agree_to_tos"] = false; // Always false here. Set true in - request_params["read_critical"] = false; // handleTOSResponse - request_params["last_exec_event"] = mLastExecEvent; - request_params["last_exec_duration"] = mLastExecDuration; - request_params["mac"] = (char*)hashed_unique_id_string; - request_params["version"] = LLVersionInfo::instance().getVersion(); - request_params["channel"] = LLVersionInfo::instance().getChannel(); - request_params["platform"] = mPlatform; - request_params["address_size"] = ADDRESS_SIZE; - request_params["platform_version"] = mPlatformVersion; - request_params["platform_string"] = mPlatformVersionName; - request_params["id0"] = mSerialNumber; - request_params["host_id"] = gSavedSettings.getString("HostID"); - request_params["extended_errors"] = true; // request message_id and message_args - request_params["token"] = ""; - - // log request_params _before_ adding the credentials or sensitive MFA hash data - LL_DEBUGS("LLLogin") << "Login parameters: " << LLSDOStreamer(request_params) << LL_ENDL; - - // Copy the credentials into the request after logging the rest - LLSD credentials(user_credential->getLoginParams()); - for (LLSD::map_const_iterator it = credentials.beginMap(); - it != credentials.endMap(); - it++ - ) - { - request_params[it->first] = it->second; - } - - std::string mfa_hash = gSavedSettings.getString("MFAHash"); //non-persistent to enable testing - std::string grid(LLGridManager::getInstance()->getGridId()); - std::string user_id = user_credential->userID(); - if (gSecAPIHandler) - { - if (mfa_hash.empty()) - { - // normal execution, mfa_hash was not set from debug setting so load from protected store - LLSD data_map = gSecAPIHandler->getProtectedData("mfa_hash", grid); - if (data_map.isMap() && data_map.has(user_id)) - { - mfa_hash = data_map[user_id].asString(); - } - } - else - { - // SL-16888 the mfa_hash is being overridden for testing so save it for consistency for future login requests - gSecAPIHandler->addToProtectedMap("mfa_hash", grid, user_id, mfa_hash); - } - } - else - { - LL_WARNS() << "unable to access protected store for mfa_hash" << LL_ENDL; - } - - request_params["mfa_hash"] = mfa_hash; - - // Specify desired timeout/retry options - LLSD http_params; - F32 srv_timeout = llclamp(gSavedSettings.getF32("LoginSRVTimeout"), LOGIN_SRV_TIMEOUT_MIN, LOGIN_SRV_TIMEOUT_MAX); - http_params["timeout"] = srv_timeout; - http_params["retries"] = LOGIN_MAX_RETRIES; - http_params["DNSCacheTimeout"] = srv_timeout * LOGIN_DNS_TIMEOUT_FACTOR; //Default: indefinite - - mRequestData.clear(); - mRequestData["method"] = "login_to_simulator"; - mRequestData["params"] = request_params; - mRequestData["options"] = requested_options; - mRequestData["http_params"] = http_params; -#if LL_RELEASE_FOR_DOWNLOAD - mRequestData["wait_for_updater"] = LLAppViewer::instance()->waitForUpdater(); -#else - mRequestData["wait_for_updater"] = false; -#endif -} - -bool LLLoginInstance::handleLoginEvent(const LLSD& event) -{ - LL_DEBUGS("LLLogin") << "LoginListener called!: \n" << event << LL_ENDL; - - if(!(event.has("state") && event.has("change") && event.has("progress"))) - { - LL_ERRS("LLLogin") << "Unknown message from LLLogin: " << event << LL_ENDL; - } - - mLoginState = event["state"].asString(); - mResponseData = event["data"]; - - if(event.has("transfer_rate")) - { - mTransferRate = event["transfer_rate"].asReal(); - } - - // Call the method registered in constructor, if any, for more specific - // handling - mDispatcher.try_call(event); - return false; -} - -void LLLoginInstance::handleLoginFailure(const LLSD& event) -{ - // TODO: we are handling failure in two separate places - - // here and in STATE_LOGIN_PROCESS_RESPONSE processing - // consider uniting them. - - // Login has failed. - // Figure out why and respond... - LLSD response = event["data"]; - LLSD updater = response["updater"]; - - // Always provide a response to the updater, if in fact the updater - // contacted us, if in fact the ping contains a 'reply' key. Most code - // paths tell it not to proceed with updating. - ResponsePtr resp(std::make_shared - (LLSDMap("update", false), updater)); - - std::string reason_response = response["reason"].asString(); - std::string message_response = response["message"].asString(); - LL_DEBUGS("LLLogin") << "reason " << reason_response - << " message " << message_response - << LL_ENDL; - // For the cases of critical message or TOS agreement, - // start the TOS dialog. The dialog response will be handled - // by the LLLoginInstance::handleTOSResponse() callback. - // The callback intiates the login attempt next step, either - // to reconnect or to end the attempt in failure. - if(reason_response == "tos") - { - LL_INFOS("LLLogin") << " ToS" << LL_ENDL; - - LLSD data(LLSD::emptyMap()); - data["message"] = message_response; - data["reply_pump"] = TOS_REPLY_PUMP; - if (gViewerWindow) - gViewerWindow->setShowProgress(false); - LLFloaterReg::showInstance("message_tos", data); - LLEventPumps::instance().obtain(TOS_REPLY_PUMP) - .listen(TOS_LISTENER_NAME, - boost::bind(&LLLoginInstance::handleTOSResponse, - this, _1, "agree_to_tos")); - } - else if(reason_response == "critical") - { - LL_INFOS("LLLogin") << "LLLoginInstance::handleLoginFailure Crit" << LL_ENDL; - - LLSD data(LLSD::emptyMap()); - data["message"] = message_response; - data["reply_pump"] = TOS_REPLY_PUMP; - if(response.has("error_code")) - { - data["error_code"] = response["error_code"]; - } - if(response.has("certificate")) - { - data["certificate"] = response["certificate"]; - } - - if (gViewerWindow) - gViewerWindow->setShowProgress(false); - - LLFloaterReg::showInstance("message_critical", data); - LLEventPumps::instance().obtain(TOS_REPLY_PUMP) - .listen(TOS_LISTENER_NAME, - boost::bind(&LLLoginInstance::handleTOSResponse, - this, _1, "read_critical")); - } - else if(reason_response == "update") - { - // This can happen if the user clicked Login quickly, before we heard - // back from the Viewer Version Manager, but login failed because - // login.cgi is insisting on a required update. We were called with an - // event that bundles both the login.cgi 'response' and the - // synchronization event from the 'updater'. - std::string login_version = response["message_args"]["VERSION"]; - std::string vvm_version = updater["VERSION"]; - std::string relnotes = updater["URL"]; - LL_WARNS("LLLogin") << "Login failed because an update to version " << login_version << " is required." << LL_ENDL; - // vvm_version might be empty because we might not have gotten - // SLVersionChecker's LoginSync handshake. But if it IS populated, it - // should (!) be the same as the version we got from login.cgi. - if ((! vvm_version.empty()) && vvm_version != login_version) - { - LL_WARNS("LLLogin") << "VVM update version " << vvm_version - << " differs from login version " << login_version - << "; presenting VVM version to match release notes URL" - << LL_ENDL; - login_version = vvm_version; - } - if (relnotes.empty() || relnotes.find("://") == std::string::npos) - { - relnotes = LLTrans::getString("RELEASE_NOTES_BASE_URL"); - if (!LLStringUtil::endsWith(relnotes, "/")) - relnotes += "/"; - relnotes += LLURI::escape(login_version) + ".html"; - } - - if (gViewerWindow) - gViewerWindow->setShowProgress(false); - - LLSD args; - args["VERSION"] = login_version; - args["URL"] = relnotes; - - if (updater.isUndefined()) - { - // If the updater failed to shake hands, better advise the user to - // download the update him/herself. - LLNotificationsUtil::add( - "RequiredUpdate", - args, - updater, - boost::bind(&LLLoginInstance::handleLoginDisallowed, this, _1, _2)); - } - else - { - // If we've heard from the updater that an update is required, - // then display the prompt that assures the user we'll take care - // of it. This is the one case in which we bind 'resp': - // instead of destroying our Response object (and thus sending a - // negative reply to the updater) as soon as we exit this - // function, bind our shared_ptr so it gets passed into - // syncWithUpdater. That ensures that the response is delayed - // until the user has responded to the notification. - LLNotificationsUtil::add( - "PauseForUpdate", - args, - updater, - boost::bind(&LLLoginInstance::syncWithUpdater, this, resp, _1, _2)); - } - } - else if(reason_response == "mfa_challenge") - { - LL_DEBUGS("LLLogin") << " MFA challenge" << LL_ENDL; - - if (gViewerWindow) - { - gViewerWindow->setShowProgress(false); - } - - showMFAChallange(LLTrans::getString(response["message_id"])); - } - else if( reason_response == "key" - || reason_response == "presence" - || reason_response == "connect" - || !message_response.empty() // will be handled in STATE_LOGIN_PROCESS_RESPONSE - || !response["message_id"].asString().empty() - ) - { - // these are events that have already been communicated elsewhere - attemptComplete(); - } - else - { - LL_WARNS("LLLogin") << "Login failed for an unknown reason: " << LLSDOStreamer(response) << LL_ENDL; - - if (gViewerWindow) - gViewerWindow->setShowProgress(false); - - LLNotificationsUtil::add("LoginFailedUnknown", LLSD::emptyMap(), LLSD::emptyMap(), boost::bind(&LLLoginInstance::handleLoginDisallowed, this, _1, _2)); - } -} - -void LLLoginInstance::syncWithUpdater(ResponsePtr resp, const LLSD& notification, const LLSD& response) -{ - LL_INFOS("LLLogin") << "LLLoginInstance::syncWithUpdater" << LL_ENDL; - // 'resp' points to an instance of LLEventAPI::Response that will be - // destroyed as soon as we return and the notification response functor is - // unregistered. Modify it so that it tells the updater to go ahead and - // perform the update. Naturally, if we allowed the user a choice as to - // whether to proceed or not, this assignment would reflect the user's - // selection. - (*resp)["update"] = true; - attemptComplete(); -} - -void LLLoginInstance::handleLoginDisallowed(const LLSD& notification, const LLSD& response) -{ - attemptComplete(); -} - -void LLLoginInstance::handleLoginSuccess(const LLSD& event) -{ - LL_INFOS("LLLogin") << "LLLoginInstance::handleLoginSuccess" << LL_ENDL; - - attemptComplete(); - mRequestData.clear(); -} - -void LLLoginInstance::handleDisconnect(const LLSD& event) -{ - // placeholder - - LL_INFOS("LLLogin") << "LLLoginInstance::handleDisconnect placeholder " << LL_ENDL; -} - -void LLLoginInstance::handleIndeterminate(const LLSD& event) -{ - // The indeterminate response means that the server - // gave the viewer a new url and params to try. - // The login module handles the retry, but it gives us the - // server response so that we may show - // the user some status. - - LLSD message = event.get("data").get("message"); - if(message.isDefined()) - { - LL_INFOS("LLLogin") << "LLLoginInstance::handleIndeterminate " << message.asString() << LL_ENDL; - - LLSD progress_update; - progress_update["desc"] = message; - LLEventPumps::getInstance()->obtain("LLProgressView").post(progress_update); - } -} - -bool LLLoginInstance::handleTOSResponse(bool accepted, const std::string& key) -{ - if(accepted) - { - LL_INFOS("LLLogin") << "LLLoginInstance::handleTOSResponse: accepted " << LL_ENDL; - - // Set the request data to true and retry login. - mRequestData["params"][key] = true; - - if (!mRequestData["params"]["token"].asString().empty()) - { - // SL-18511 this TOS failure happened while we are in the middle of an MFA challenge/response. - // the previously entered token is very likely expired, so prompt again - showMFAChallange(LLTrans::getString("LoginFailedAuthenticationMFARequired")); - } - else - { - reconnect(); - } - } - else - { - LL_INFOS("LLLogin") << "LLLoginInstance::handleTOSResponse: attemptComplete" << LL_ENDL; - - attemptComplete(); - } - - LLEventPumps::instance().obtain(TOS_REPLY_PUMP).stopListening(TOS_LISTENER_NAME); - return true; -} - -void LLLoginInstance::showMFAChallange(const std::string& message) -{ - LLSD args(llsd::map("MESSAGE", message)); - LLSD payload; - if (gSavedSettings.getBOOL("RememberUser")) - { - LLNotificationsUtil::add("PromptMFATokenWithSave", args, payload, - boost::bind(&LLLoginInstance::handleMFAChallenge, this, _1, _2)); - } - else - { - LLNotificationsUtil::add("PromptMFAToken", args, payload, - boost::bind(&LLLoginInstance::handleMFAChallenge, this, _1, _2)); - } -} - -bool LLLoginInstance::handleMFAChallenge(LLSD const & notif, LLSD const & response) -{ - bool continue_clicked = response["continue"].asBoolean(); - std::string token = response["token"].asString(); - LL_DEBUGS("LLLogin") << "PromptMFAToken: response: " << response << " continue_clicked" << continue_clicked << LL_ENDL; - - // strip out whitespace - SL-17034/BUG-231938 - token = boost::regex_replace(token, boost::regex("\\s"), ""); - - if (continue_clicked && !token.empty()) - { - LL_INFOS("LLLogin") << "PromptMFAToken: token submitted" << LL_ENDL; - - // Set the request data to true and retry login. - mRequestData["params"]["token"] = token; - mSaveMFA = response.has("ignore") ? response["ignore"].asBoolean() : false; - reconnect(); - } else { - LL_INFOS("LLLogin") << "PromptMFAToken: no token, attemptComplete" << LL_ENDL; - attemptComplete(); - } - return true; -} - -std::string construct_start_string() -{ - std::string start; - LLSLURL start_slurl = LLStartUp::getStartSLURL(); - switch(start_slurl.getType()) - { - case LLSLURL::LOCATION: - { - // a startup URL was specified - LLVector3 position = start_slurl.getPosition(); - std::string unescaped_start = - STRINGIZE( "uri:" - << start_slurl.getRegion() << "&" - << position[VX] << "&" - << position[VY] << "&" - << position[VZ]); - start = xml_escape_string(unescaped_start); - break; - } - case LLSLURL::HOME_LOCATION: - { - start = "home"; - break; - } - default: - { - start = "last"; - } - } - return start; -} - +/** + * @file lllogininstance.cpp + * @brief Viewer's host for a login connection. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lllogininstance.h" + +// llcommon +#include "llevents.h" +#include "stringize.h" +#include "llsdserialize.h" + +// llmessage (!) +#include "llfiltersd2xmlrpc.h" // for xml_escape_string() + +// login +#include "lllogin.h" + +// newview +#include "llhasheduniqueid.h" +#include "llviewernetwork.h" +#include "llviewercontrol.h" +#include "llversioninfo.h" +#include "llslurl.h" +#include "llstartup.h" +#include "llfloaterreg.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "llwindow.h" +#include "llviewerwindow.h" +#include "llprogressview.h" +#include "llsecapi.h" +#include "llstartup.h" +#include "llmachineid.h" +#include "llevents.h" +#include "llappviewer.h" +#include "llsdserialize.h" +#include "lltrans.h" + +#include +#include +#include + +const S32 LOGIN_MAX_RETRIES = 0; // Viewer should not autmatically retry login +const F32 LOGIN_SRV_TIMEOUT_MIN = 10; +const F32 LOGIN_SRV_TIMEOUT_MAX = 120; +const F32 LOGIN_DNS_TIMEOUT_FACTOR = 0.9; // make DNS wait shorter then retry time + +class LLLoginInstance::Disposable { +public: + virtual ~Disposable() {} +}; + +static const char * const TOS_REPLY_PUMP = "lllogininstance_tos_callback"; +static const char * const TOS_LISTENER_NAME = "lllogininstance_tos"; + +std::string construct_start_string(); + +// LLLoginInstance +//----------------------------------------------------------------------------- + + +LLLoginInstance::LLLoginInstance() : + mLoginModule(new LLLogin()), + mNotifications(NULL), + mLoginState("offline"), + mSaveMFA(true), + mAttemptComplete(false), + mTransferRate(0.0f), + mDispatcher("LLLoginInstance", "change") +{ + mLoginModule->getEventPump().listen("lllogininstance", + boost::bind(&LLLoginInstance::handleLoginEvent, this, _1)); + // This internal use of LLEventDispatcher doesn't really need + // per-function descriptions. + mDispatcher.add("fail.login", "", boost::bind(&LLLoginInstance::handleLoginFailure, this, _1)); + mDispatcher.add("connect", "", boost::bind(&LLLoginInstance::handleLoginSuccess, this, _1)); + mDispatcher.add("disconnect", "", boost::bind(&LLLoginInstance::handleDisconnect, this, _1)); + mDispatcher.add("indeterminate", "", boost::bind(&LLLoginInstance::handleIndeterminate, this, _1)); +} + +void LLLoginInstance::setPlatformInfo(const std::string platform, + const std::string platform_version, + const std::string platform_name) +{ + mPlatform = platform; + mPlatformVersion = platform_version; + mPlatformVersionName = platform_name; +} + +LLLoginInstance::~LLLoginInstance() +{ +} + +void LLLoginInstance::connect(LLPointer credentials) +{ + std::vector uris; + LLGridManager::getInstance()->getLoginURIs(uris); + if (uris.size() < 1) + { + LL_WARNS() << "Failed to get login URIs during connect. No connect for you!" << LL_ENDL; + return; + } + connect(uris.front(), credentials); +} + +void LLLoginInstance::connect(const std::string& uri, LLPointer credentials) +{ + mAttemptComplete = false; // Reset attempt complete at this point! + constructAuthParams(credentials); + mLoginModule->connect(uri, mRequestData); +} + +void LLLoginInstance::reconnect() +{ + // Sort of like connect, only using the pre-existing + // request params. + std::vector uris; + LLGridManager::getInstance()->getLoginURIs(uris); + mLoginModule->connect(uris.front(), mRequestData); + gViewerWindow->setShowProgress(true); +} + +void LLLoginInstance::disconnect() +{ + mAttemptComplete = false; // Reset attempt complete at this point! + mRequestData.clear(); + mLoginModule->disconnect(); +} + +LLSD LLLoginInstance::getResponse() +{ + return mResponseData; +} + +void LLLoginInstance::constructAuthParams(LLPointer user_credential) +{ + // Set up auth request options. +//#define LL_MINIMIAL_REQUESTED_OPTIONS + LLSD requested_options; + // *Note: this is where gUserAuth used to be created. + requested_options.append("inventory-root"); + requested_options.append("inventory-skeleton"); + //requested_options.append("inventory-meat"); + //requested_options.append("inventory-skel-targets"); +#if (!defined LL_MINIMIAL_REQUESTED_OPTIONS) + + // Not requesting library will trigger mFatalNoLibraryRootFolder + requested_options.append("inventory-lib-root"); + requested_options.append("inventory-lib-owner"); + requested_options.append("inventory-skel-lib"); + // requested_options.append("inventory-meat-lib"); + + requested_options.append("initial-outfit"); + requested_options.append("gestures"); + requested_options.append("display_names"); + requested_options.append("event_categories"); + requested_options.append("event_notifications"); + requested_options.append("classified_categories"); + requested_options.append("adult_compliant"); + requested_options.append("buddy-list"); + requested_options.append("newuser-config"); + requested_options.append("ui-config"); + + //send this info to login.cgi for stats gathering + //since viewerstats isn't reliable enough + requested_options.append("advanced-mode"); + +#endif + requested_options.append("max-agent-groups"); + requested_options.append("map-server-url"); + requested_options.append("voice-config"); + requested_options.append("tutorial_setting"); + requested_options.append("login-flags"); + requested_options.append("global-textures"); + if(gSavedSettings.getBOOL("ConnectAsGod")) + { + gSavedSettings.setBOOL("UseDebugMenus", true); + requested_options.append("god-connect"); + } + + LLSD request_params; + + unsigned char hashed_unique_id_string[MD5HEX_STR_SIZE]; + if ( ! llHashedUniqueID(hashed_unique_id_string) ) + { + + LL_WARNS("LLLogin") << "Not providing a unique id in request params" << LL_ENDL; + + } + request_params["start"] = construct_start_string(); + request_params["agree_to_tos"] = false; // Always false here. Set true in + request_params["read_critical"] = false; // handleTOSResponse + request_params["last_exec_event"] = mLastExecEvent; + request_params["last_exec_duration"] = mLastExecDuration; + request_params["mac"] = (char*)hashed_unique_id_string; + request_params["version"] = LLVersionInfo::instance().getVersion(); + request_params["channel"] = LLVersionInfo::instance().getChannel(); + request_params["platform"] = mPlatform; + request_params["address_size"] = ADDRESS_SIZE; + request_params["platform_version"] = mPlatformVersion; + request_params["platform_string"] = mPlatformVersionName; + request_params["id0"] = mSerialNumber; + request_params["host_id"] = gSavedSettings.getString("HostID"); + request_params["extended_errors"] = true; // request message_id and message_args + request_params["token"] = ""; + + // log request_params _before_ adding the credentials or sensitive MFA hash data + LL_DEBUGS("LLLogin") << "Login parameters: " << LLSDOStreamer(request_params) << LL_ENDL; + + // Copy the credentials into the request after logging the rest + LLSD credentials(user_credential->getLoginParams()); + for (LLSD::map_const_iterator it = credentials.beginMap(); + it != credentials.endMap(); + it++ + ) + { + request_params[it->first] = it->second; + } + + std::string mfa_hash = gSavedSettings.getString("MFAHash"); //non-persistent to enable testing + std::string grid(LLGridManager::getInstance()->getGridId()); + std::string user_id = user_credential->userID(); + if (gSecAPIHandler) + { + if (mfa_hash.empty()) + { + // normal execution, mfa_hash was not set from debug setting so load from protected store + LLSD data_map = gSecAPIHandler->getProtectedData("mfa_hash", grid); + if (data_map.isMap() && data_map.has(user_id)) + { + mfa_hash = data_map[user_id].asString(); + } + } + else + { + // SL-16888 the mfa_hash is being overridden for testing so save it for consistency for future login requests + gSecAPIHandler->addToProtectedMap("mfa_hash", grid, user_id, mfa_hash); + } + } + else + { + LL_WARNS() << "unable to access protected store for mfa_hash" << LL_ENDL; + } + + request_params["mfa_hash"] = mfa_hash; + + // Specify desired timeout/retry options + LLSD http_params; + F32 srv_timeout = llclamp(gSavedSettings.getF32("LoginSRVTimeout"), LOGIN_SRV_TIMEOUT_MIN, LOGIN_SRV_TIMEOUT_MAX); + http_params["timeout"] = srv_timeout; + http_params["retries"] = LOGIN_MAX_RETRIES; + http_params["DNSCacheTimeout"] = srv_timeout * LOGIN_DNS_TIMEOUT_FACTOR; //Default: indefinite + + mRequestData.clear(); + mRequestData["method"] = "login_to_simulator"; + mRequestData["params"] = request_params; + mRequestData["options"] = requested_options; + mRequestData["http_params"] = http_params; +#if LL_RELEASE_FOR_DOWNLOAD + mRequestData["wait_for_updater"] = LLAppViewer::instance()->waitForUpdater(); +#else + mRequestData["wait_for_updater"] = false; +#endif +} + +bool LLLoginInstance::handleLoginEvent(const LLSD& event) +{ + LL_DEBUGS("LLLogin") << "LoginListener called!: \n" << event << LL_ENDL; + + if(!(event.has("state") && event.has("change") && event.has("progress"))) + { + LL_ERRS("LLLogin") << "Unknown message from LLLogin: " << event << LL_ENDL; + } + + mLoginState = event["state"].asString(); + mResponseData = event["data"]; + + if(event.has("transfer_rate")) + { + mTransferRate = event["transfer_rate"].asReal(); + } + + // Call the method registered in constructor, if any, for more specific + // handling + mDispatcher.try_call(event); + return false; +} + +void LLLoginInstance::handleLoginFailure(const LLSD& event) +{ + // TODO: we are handling failure in two separate places - + // here and in STATE_LOGIN_PROCESS_RESPONSE processing + // consider uniting them. + + // Login has failed. + // Figure out why and respond... + LLSD response = event["data"]; + LLSD updater = response["updater"]; + + // Always provide a response to the updater, if in fact the updater + // contacted us, if in fact the ping contains a 'reply' key. Most code + // paths tell it not to proceed with updating. + ResponsePtr resp(std::make_shared + (LLSDMap("update", false), updater)); + + std::string reason_response = response["reason"].asString(); + std::string message_response = response["message"].asString(); + LL_DEBUGS("LLLogin") << "reason " << reason_response + << " message " << message_response + << LL_ENDL; + // For the cases of critical message or TOS agreement, + // start the TOS dialog. The dialog response will be handled + // by the LLLoginInstance::handleTOSResponse() callback. + // The callback intiates the login attempt next step, either + // to reconnect or to end the attempt in failure. + if(reason_response == "tos") + { + LL_INFOS("LLLogin") << " ToS" << LL_ENDL; + + LLSD data(LLSD::emptyMap()); + data["message"] = message_response; + data["reply_pump"] = TOS_REPLY_PUMP; + if (gViewerWindow) + gViewerWindow->setShowProgress(false); + LLFloaterReg::showInstance("message_tos", data); + LLEventPumps::instance().obtain(TOS_REPLY_PUMP) + .listen(TOS_LISTENER_NAME, + boost::bind(&LLLoginInstance::handleTOSResponse, + this, _1, "agree_to_tos")); + } + else if(reason_response == "critical") + { + LL_INFOS("LLLogin") << "LLLoginInstance::handleLoginFailure Crit" << LL_ENDL; + + LLSD data(LLSD::emptyMap()); + data["message"] = message_response; + data["reply_pump"] = TOS_REPLY_PUMP; + if(response.has("error_code")) + { + data["error_code"] = response["error_code"]; + } + if(response.has("certificate")) + { + data["certificate"] = response["certificate"]; + } + + if (gViewerWindow) + gViewerWindow->setShowProgress(false); + + LLFloaterReg::showInstance("message_critical", data); + LLEventPumps::instance().obtain(TOS_REPLY_PUMP) + .listen(TOS_LISTENER_NAME, + boost::bind(&LLLoginInstance::handleTOSResponse, + this, _1, "read_critical")); + } + else if(reason_response == "update") + { + // This can happen if the user clicked Login quickly, before we heard + // back from the Viewer Version Manager, but login failed because + // login.cgi is insisting on a required update. We were called with an + // event that bundles both the login.cgi 'response' and the + // synchronization event from the 'updater'. + std::string login_version = response["message_args"]["VERSION"]; + std::string vvm_version = updater["VERSION"]; + std::string relnotes = updater["URL"]; + LL_WARNS("LLLogin") << "Login failed because an update to version " << login_version << " is required." << LL_ENDL; + // vvm_version might be empty because we might not have gotten + // SLVersionChecker's LoginSync handshake. But if it IS populated, it + // should (!) be the same as the version we got from login.cgi. + if ((! vvm_version.empty()) && vvm_version != login_version) + { + LL_WARNS("LLLogin") << "VVM update version " << vvm_version + << " differs from login version " << login_version + << "; presenting VVM version to match release notes URL" + << LL_ENDL; + login_version = vvm_version; + } + if (relnotes.empty() || relnotes.find("://") == std::string::npos) + { + relnotes = LLTrans::getString("RELEASE_NOTES_BASE_URL"); + if (!LLStringUtil::endsWith(relnotes, "/")) + relnotes += "/"; + relnotes += LLURI::escape(login_version) + ".html"; + } + + if (gViewerWindow) + gViewerWindow->setShowProgress(false); + + LLSD args; + args["VERSION"] = login_version; + args["URL"] = relnotes; + + if (updater.isUndefined()) + { + // If the updater failed to shake hands, better advise the user to + // download the update him/herself. + LLNotificationsUtil::add( + "RequiredUpdate", + args, + updater, + boost::bind(&LLLoginInstance::handleLoginDisallowed, this, _1, _2)); + } + else + { + // If we've heard from the updater that an update is required, + // then display the prompt that assures the user we'll take care + // of it. This is the one case in which we bind 'resp': + // instead of destroying our Response object (and thus sending a + // negative reply to the updater) as soon as we exit this + // function, bind our shared_ptr so it gets passed into + // syncWithUpdater. That ensures that the response is delayed + // until the user has responded to the notification. + LLNotificationsUtil::add( + "PauseForUpdate", + args, + updater, + boost::bind(&LLLoginInstance::syncWithUpdater, this, resp, _1, _2)); + } + } + else if(reason_response == "mfa_challenge") + { + LL_DEBUGS("LLLogin") << " MFA challenge" << LL_ENDL; + + if (gViewerWindow) + { + gViewerWindow->setShowProgress(false); + } + + showMFAChallange(LLTrans::getString(response["message_id"])); + } + else if( reason_response == "key" + || reason_response == "presence" + || reason_response == "connect" + || !message_response.empty() // will be handled in STATE_LOGIN_PROCESS_RESPONSE + || !response["message_id"].asString().empty() + ) + { + // these are events that have already been communicated elsewhere + attemptComplete(); + } + else + { + LL_WARNS("LLLogin") << "Login failed for an unknown reason: " << LLSDOStreamer(response) << LL_ENDL; + + if (gViewerWindow) + gViewerWindow->setShowProgress(false); + + LLNotificationsUtil::add("LoginFailedUnknown", LLSD::emptyMap(), LLSD::emptyMap(), boost::bind(&LLLoginInstance::handleLoginDisallowed, this, _1, _2)); + } +} + +void LLLoginInstance::syncWithUpdater(ResponsePtr resp, const LLSD& notification, const LLSD& response) +{ + LL_INFOS("LLLogin") << "LLLoginInstance::syncWithUpdater" << LL_ENDL; + // 'resp' points to an instance of LLEventAPI::Response that will be + // destroyed as soon as we return and the notification response functor is + // unregistered. Modify it so that it tells the updater to go ahead and + // perform the update. Naturally, if we allowed the user a choice as to + // whether to proceed or not, this assignment would reflect the user's + // selection. + (*resp)["update"] = true; + attemptComplete(); +} + +void LLLoginInstance::handleLoginDisallowed(const LLSD& notification, const LLSD& response) +{ + attemptComplete(); +} + +void LLLoginInstance::handleLoginSuccess(const LLSD& event) +{ + LL_INFOS("LLLogin") << "LLLoginInstance::handleLoginSuccess" << LL_ENDL; + + attemptComplete(); + mRequestData.clear(); +} + +void LLLoginInstance::handleDisconnect(const LLSD& event) +{ + // placeholder + + LL_INFOS("LLLogin") << "LLLoginInstance::handleDisconnect placeholder " << LL_ENDL; +} + +void LLLoginInstance::handleIndeterminate(const LLSD& event) +{ + // The indeterminate response means that the server + // gave the viewer a new url and params to try. + // The login module handles the retry, but it gives us the + // server response so that we may show + // the user some status. + + LLSD message = event.get("data").get("message"); + if(message.isDefined()) + { + LL_INFOS("LLLogin") << "LLLoginInstance::handleIndeterminate " << message.asString() << LL_ENDL; + + LLSD progress_update; + progress_update["desc"] = message; + LLEventPumps::getInstance()->obtain("LLProgressView").post(progress_update); + } +} + +bool LLLoginInstance::handleTOSResponse(bool accepted, const std::string& key) +{ + if(accepted) + { + LL_INFOS("LLLogin") << "LLLoginInstance::handleTOSResponse: accepted " << LL_ENDL; + + // Set the request data to true and retry login. + mRequestData["params"][key] = true; + + if (!mRequestData["params"]["token"].asString().empty()) + { + // SL-18511 this TOS failure happened while we are in the middle of an MFA challenge/response. + // the previously entered token is very likely expired, so prompt again + showMFAChallange(LLTrans::getString("LoginFailedAuthenticationMFARequired")); + } + else + { + reconnect(); + } + } + else + { + LL_INFOS("LLLogin") << "LLLoginInstance::handleTOSResponse: attemptComplete" << LL_ENDL; + + attemptComplete(); + } + + LLEventPumps::instance().obtain(TOS_REPLY_PUMP).stopListening(TOS_LISTENER_NAME); + return true; +} + +void LLLoginInstance::showMFAChallange(const std::string& message) +{ + LLSD args(llsd::map("MESSAGE", message)); + LLSD payload; + if (gSavedSettings.getBOOL("RememberUser")) + { + LLNotificationsUtil::add("PromptMFATokenWithSave", args, payload, + boost::bind(&LLLoginInstance::handleMFAChallenge, this, _1, _2)); + } + else + { + LLNotificationsUtil::add("PromptMFAToken", args, payload, + boost::bind(&LLLoginInstance::handleMFAChallenge, this, _1, _2)); + } +} + +bool LLLoginInstance::handleMFAChallenge(LLSD const & notif, LLSD const & response) +{ + bool continue_clicked = response["continue"].asBoolean(); + std::string token = response["token"].asString(); + LL_DEBUGS("LLLogin") << "PromptMFAToken: response: " << response << " continue_clicked" << continue_clicked << LL_ENDL; + + // strip out whitespace - SL-17034/BUG-231938 + token = boost::regex_replace(token, boost::regex("\\s"), ""); + + if (continue_clicked && !token.empty()) + { + LL_INFOS("LLLogin") << "PromptMFAToken: token submitted" << LL_ENDL; + + // Set the request data to true and retry login. + mRequestData["params"]["token"] = token; + mSaveMFA = response.has("ignore") ? response["ignore"].asBoolean() : false; + reconnect(); + } else { + LL_INFOS("LLLogin") << "PromptMFAToken: no token, attemptComplete" << LL_ENDL; + attemptComplete(); + } + return true; +} + +std::string construct_start_string() +{ + std::string start; + LLSLURL start_slurl = LLStartUp::getStartSLURL(); + switch(start_slurl.getType()) + { + case LLSLURL::LOCATION: + { + // a startup URL was specified + LLVector3 position = start_slurl.getPosition(); + std::string unescaped_start = + STRINGIZE( "uri:" + << start_slurl.getRegion() << "&" + << position[VX] << "&" + << position[VY] << "&" + << position[VZ]); + start = xml_escape_string(unescaped_start); + break; + } + case LLSLURL::HOME_LOCATION: + { + start = "home"; + break; + } + default: + { + start = "last"; + } + } + return start; +} + diff --git a/indra/newview/llmanip.cpp b/indra/newview/llmanip.cpp index 7480dc3c0f..8983b50980 100644 --- a/indra/newview/llmanip.cpp +++ b/indra/newview/llmanip.cpp @@ -1,622 +1,622 @@ -/** - * @file llmanip.cpp - * @brief LLManip class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llmanip.h" - -#include "llmath.h" -#include "v3math.h" -#include "llgl.h" -#include "llrender.h" -#include "llprimitive.h" -#include "llview.h" -#include "llviewertexturelist.h" - -#include "llagent.h" -#include "llagentcamera.h" -#include "llviewercontrol.h" -#include "lldrawable.h" -#include "llfontgl.h" -#include "llhudrender.h" -#include "llselectmgr.h" -#include "llui.h" -#include "llviewercamera.h" -#include "llviewerjoint.h" -#include "llviewerobject.h" -#include "llviewerwindow.h" -#include "llvoavatar.h" -#include "llworld.h" // for LLWorld::getInstance() -#include "llresmgr.h" -#include "pipeline.h" -#include "llglheaders.h" -#include "lluiimage.h" -// Local constants... -const S32 VERTICAL_OFFSET = 50; - -F32 LLManip::sHelpTextVisibleTime = 2.f; -F32 LLManip::sHelpTextFadeTime = 2.f; -S32 LLManip::sNumTimesHelpTextShown = 0; -S32 LLManip::sMaxTimesShowHelpText = 5; -F32 LLManip::sGridMaxSubdivisionLevel = 32.f; -F32 LLManip::sGridMinSubdivisionLevel = 1.f / 32.f; -LLVector2 LLManip::sTickLabelSpacing(60.f, 25.f); - - -//static -void LLManip::rebuild(LLViewerObject* vobj) -{ - LLDrawable* drawablep = vobj->mDrawable; - if (drawablep && drawablep->getVOVolume()) - { - gPipeline.markRebuild(drawablep,LLDrawable::REBUILD_VOLUME); - drawablep->setState(LLDrawable::MOVE_UNDAMPED); // force to UNDAMPED - drawablep->updateMove(); - LLSpatialGroup* group = drawablep->getSpatialGroup(); - if (group) - { - group->dirtyGeom(); - gPipeline.markRebuild(group); - } - - LLViewerObject::const_child_list_t& child_list = vobj->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(), endIter = child_list.end(); - iter != endIter; ++iter) - { - LLViewerObject* child = *iter; - rebuild(child); - } - } -} - -////////////////////////////////////////////////////////////////////////////// -// LLManip - - -LLManip::LLManip( const std::string& name, LLToolComposite* composite ) - : - LLTool( name, composite ), - mInSnapRegime(false), - mHighlightedPart(LL_NO_PART), - mManipPart(LL_NO_PART) -{ -} - -void LLManip::getManipNormal(LLViewerObject* object, EManipPart manip, LLVector3 &normal) -{ - LLVector3 grid_origin; - LLVector3 grid_scale; - LLQuaternion grid_rotation; - - LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale); - - if (manip >= LL_X_ARROW && manip <= LL_Z_ARROW) - { - LLVector3 arrow_axis; - getManipAxis(object, manip, arrow_axis); - - LLVector3 cross = arrow_axis % LLViewerCamera::getInstance()->getAtAxis(); - normal = cross % arrow_axis; - normal.normVec(); - } - else if (manip >= LL_YZ_PLANE && manip <= LL_XY_PLANE) - { - switch (manip) - { - case LL_YZ_PLANE: - normal = LLVector3::x_axis; - break; - case LL_XZ_PLANE: - normal = LLVector3::y_axis; - break; - case LL_XY_PLANE: - normal = LLVector3::z_axis; - break; - default: - break; - } - normal.rotVec(grid_rotation); - } - else - { - normal.clearVec(); - } -} - - -bool LLManip::getManipAxis(LLViewerObject* object, EManipPart manip, LLVector3 &axis) -{ - LLVector3 grid_origin; - LLVector3 grid_scale; - LLQuaternion grid_rotation; - - LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale); - - if (manip == LL_X_ARROW) - { - axis = LLVector3::x_axis; - } - else if (manip == LL_Y_ARROW) - { - axis = LLVector3::y_axis; - } - else if (manip == LL_Z_ARROW) - { - axis = LLVector3::z_axis; - } - else - { - return false; - } - - axis.rotVec( grid_rotation ); - return true; -} - -F32 LLManip::getSubdivisionLevel(const LLVector3 &reference_point, const LLVector3 &translate_axis, F32 grid_scale, S32 min_pixel_spacing, F32 min_subdivisions, F32 max_subdivisions) -{ - //update current snap subdivision level - LLVector3 cam_to_reference; - if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) - { - cam_to_reference = LLVector3(1.f / gAgentCamera.mHUDCurZoom, 0.f, 0.f); - } - else - { - cam_to_reference = reference_point - LLViewerCamera::getInstance()->getOrigin(); - } - F32 current_range = cam_to_reference.normVec(); - - F32 projected_translation_axis_length = (translate_axis % cam_to_reference).magVec(); - F32 subdivisions = llmax(projected_translation_axis_length * grid_scale / (current_range / LLViewerCamera::getInstance()->getPixelMeterRatio() * min_pixel_spacing), 0.f); - // figure out nearest power of 2 that subdivides grid_scale with result > min_pixel_spacing - subdivisions = llclamp((F32)pow(2.f, llfloor(log(subdivisions) / log(2.f))), min_subdivisions, max_subdivisions); - - return subdivisions; -} - -void LLManip::handleSelect() -{ - mObjectSelection = LLSelectMgr::getInstance()->getEditSelection(); -} - -void LLManip::handleDeselect() -{ - mHighlightedPart = LL_NO_PART; - mManipPart = LL_NO_PART; - mObjectSelection = NULL; -} - -LLObjectSelectionHandle LLManip::getSelection() -{ - return mObjectSelection; -} - -bool LLManip::handleHover(S32 x, S32 y, MASK mask) -{ - // We only handle the event if mousedown started with us - if( hasMouseCapture() ) - { - if( mObjectSelection->isEmpty() ) - { - // Somehow the object got deselected while we were dragging it. - // Release the mouse - setMouseCapture( false ); - } - - LL_DEBUGS("UserInput") << "hover handled by LLManip (active)" << LL_ENDL; - } - else - { - LL_DEBUGS("UserInput") << "hover handled by LLManip (inactive)" << LL_ENDL; - } - gViewerWindow->setCursor(UI_CURSOR_ARROW); - return true; -} - - -bool LLManip::handleMouseUp(S32 x, S32 y, MASK mask) -{ - bool handled = false; - if( hasMouseCapture() ) - { - handled = true; - setMouseCapture( false ); - } - return handled; -} - -void LLManip::updateGridSettings() -{ - sGridMaxSubdivisionLevel = gSavedSettings.getBOOL("GridSubUnit") ? (F32)gSavedSettings.getS32("GridSubdivision") : 1.f; -} - -bool LLManip::getMousePointOnPlaneAgent(LLVector3& point, S32 x, S32 y, LLVector3 origin, LLVector3 normal) -{ - LLVector3d origin_double = gAgent.getPosGlobalFromAgent(origin); - LLVector3d global_point; - bool result = getMousePointOnPlaneGlobal(global_point, x, y, origin_double, normal); - point = gAgent.getPosAgentFromGlobal(global_point); - return result; -} - -bool LLManip::getMousePointOnPlaneGlobal(LLVector3d& point, S32 x, S32 y, LLVector3d origin, LLVector3 normal) const -{ - if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) - { - bool result = false; - F32 mouse_x = ((F32)x / gViewerWindow->getWorldViewWidthScaled() - 0.5f) * LLViewerCamera::getInstance()->getAspect() / gAgentCamera.mHUDCurZoom; - F32 mouse_y = ((F32)y / gViewerWindow->getWorldViewHeightScaled() - 0.5f) / gAgentCamera.mHUDCurZoom; - - LLVector3 origin_agent = gAgent.getPosAgentFromGlobal(origin); - LLVector3 mouse_pos = LLVector3(0.f, -mouse_x, mouse_y); - if (llabs(normal.mV[VX]) < 0.001f) - { - // use largish value that should be outside HUD manipulation range - mouse_pos.mV[VX] = 10.f; - } - else - { - mouse_pos.mV[VX] = (normal * (origin_agent - mouse_pos)) - / (normal.mV[VX]); - result = true; - } - - point = gAgent.getPosGlobalFromAgent(mouse_pos); - return result; - } - else - { - return gViewerWindow->mousePointOnPlaneGlobal( - point, x, y, origin, normal ); - } - - //return false; -} - -// Given the line defined by mouse cursor (a1 + a_param*(a2-a1)) and the line defined by b1 + b_param*(b2-b1), -// returns a_param and b_param for the points where lines are closest to each other. -// Returns false if the two lines are parallel. -bool LLManip::nearestPointOnLineFromMouse( S32 x, S32 y, const LLVector3& b1, const LLVector3& b2, F32 &a_param, F32 &b_param ) -{ - LLVector3 a1; - LLVector3 a2; - - if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) - { - F32 mouse_x = (((F32)x / gViewerWindow->getWindowWidthScaled()) - 0.5f) * LLViewerCamera::getInstance()->getAspect() / gAgentCamera.mHUDCurZoom; - F32 mouse_y = (((F32)y / gViewerWindow->getWindowHeightScaled()) - 0.5f) / gAgentCamera.mHUDCurZoom; - a1 = LLVector3(llmin(b1.mV[VX] - 0.1f, b2.mV[VX] - 0.1f, 0.f), -mouse_x, mouse_y); - a2 = a1 + LLVector3(1.f, 0.f, 0.f); - } - else - { - a1 = gAgentCamera.getCameraPositionAgent(); - a2 = gAgentCamera.getCameraPositionAgent() + LLVector3(gViewerWindow->mouseDirectionGlobal(x, y)); - } - - bool parallel = true; - LLVector3 a = a2 - a1; - LLVector3 b = b2 - b1; - - LLVector3 normal; - F32 dist, denom; - normal = (b % a) % b; // normal to plane (P) through b and (shortest line between a and b) - normal.normVec(); - dist = b1 * normal; // distance from origin to P - - denom = normal * a; - if( (denom < -F_APPROXIMATELY_ZERO) || (F_APPROXIMATELY_ZERO < denom) ) - { - a_param = (dist - normal * a1) / denom; - parallel = false; - } - - normal = (a % b) % a; // normal to plane (P) through a and (shortest line between a and b) - normal.normVec(); - dist = a1 * normal; // distance from origin to P - denom = normal * b; - if( (denom < -F_APPROXIMATELY_ZERO) || (F_APPROXIMATELY_ZERO < denom) ) - { - b_param = (dist - normal * b1) / denom; - parallel = false; - } - - return parallel; -} - -LLVector3 LLManip::getSavedPivotPoint() const -{ - return LLSelectMgr::getInstance()->getSavedBBoxOfSelection().getCenterAgent(); -} - -LLVector3 LLManip::getPivotPoint() -{ - if (mObjectSelection->getFirstObject() && mObjectSelection->getObjectCount() == 1 && mObjectSelection->getSelectType() != SELECT_TYPE_HUD) - { - return mObjectSelection->getFirstObject()->getPivotPositionAgent(); - } - return LLSelectMgr::getInstance()->getBBoxOfSelection().getCenterAgent(); -} - - -void LLManip::renderGuidelines(bool draw_x, bool draw_y, bool draw_z) -{ - LLVector3 grid_origin; - LLQuaternion grid_rot; - LLVector3 grid_scale; - LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rot, grid_scale); - - const bool children_ok = true; - LLViewerObject* object = mObjectSelection->getFirstRootObject(children_ok); - if (!object) - { - return; - } - - //LLVector3 center_agent = LLSelectMgr::getInstance()->getBBoxOfSelection().getCenterAgent(); - LLVector3 center_agent = getPivotPoint(); - - gGL.pushMatrix(); - { - gGL.translatef(center_agent.mV[VX], center_agent.mV[VY], center_agent.mV[VZ]); - - F32 angle_radians, x, y, z; - - grid_rot.getAngleAxis(&angle_radians, &x, &y, &z); - gGL.rotatef(angle_radians * RAD_TO_DEG, x, y, z); - - F32 region_size = LLWorld::getInstance()->getRegionWidthInMeters(); - - const F32 LINE_ALPHA = 0.33f; - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - LLUI::setLineWidth(1.5f); - - if (draw_x) - { - gGL.color4f(1.f, 0.f, 0.f, LINE_ALPHA); - gGL.begin(LLRender::LINES); - gGL.vertex3f( -region_size, 0.f, 0.f ); - gGL.vertex3f( region_size, 0.f, 0.f ); - gGL.end(); - } - - if (draw_y) - { - gGL.color4f(0.f, 1.f, 0.f, LINE_ALPHA); - gGL.begin(LLRender::LINES); - gGL.vertex3f( 0.f, -region_size, 0.f ); - gGL.vertex3f( 0.f, region_size, 0.f ); - gGL.end(); - } - - if (draw_z) - { - gGL.color4f(0.f, 0.f, 1.f, LINE_ALPHA); - gGL.begin(LLRender::LINES); - gGL.vertex3f( 0.f, 0.f, -region_size ); - gGL.vertex3f( 0.f, 0.f, region_size ); - gGL.end(); - } - LLUI::setLineWidth(1.0f); - } - gGL.popMatrix(); -} - -void LLManip::renderXYZ(const LLVector3 &vec) -{ - const S32 PAD = 10; - std::string feedback_string; - S32 window_center_x = gViewerWindow->getWorldViewRectScaled().getWidth() / 2; - S32 window_center_y = gViewerWindow->getWorldViewRectScaled().getHeight() / 2; - S32 vertical_offset = window_center_y - VERTICAL_OFFSET; - - - gGL.pushMatrix(); - { - LLUIImagePtr imagep = LLUI::getUIImage("Rounded_Square"); - gViewerWindow->setup2DRender(); - const LLVector2& display_scale = gViewerWindow->getDisplayScale(); - gGL.color4f(0.f, 0.f, 0.f, 0.7f); - - imagep->draw( - (window_center_x - 115) * display_scale.mV[VX], - (window_center_y + vertical_offset - PAD) * display_scale.mV[VY], - 235 * display_scale.mV[VX], - (PAD * 2 + 10) * display_scale.mV[VY], - LLColor4(0.f, 0.f, 0.f, 0.7f) ); - - LLFontGL* font = LLFontGL::getFontSansSerif(); - LLLocale locale(LLLocale::USER_LOCALE); - LLGLDepthTest gls_depth(GL_FALSE); - - // render drop shadowed text (manually because of bigger 'distance') - F32 right_x; - feedback_string = llformat("X: %.3f", vec.mV[VX]); - font->render(utf8str_to_wstring(feedback_string), 0, window_center_x - 102.f + 1.f, window_center_y + vertical_offset - 2.f, LLColor4::black, - LLFontGL::LEFT, LLFontGL::BASELINE, - LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, 1000, &right_x); - - feedback_string = llformat("Y: %.3f", vec.mV[VY]); - font->render(utf8str_to_wstring(feedback_string), 0, window_center_x - 27.f + 1.f, window_center_y + vertical_offset - 2.f, LLColor4::black, - LLFontGL::LEFT, LLFontGL::BASELINE, - LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, 1000, &right_x); - - feedback_string = llformat("Z: %.3f", vec.mV[VZ]); - font->render(utf8str_to_wstring(feedback_string), 0, window_center_x + 48.f + 1.f, window_center_y + vertical_offset - 2.f, LLColor4::black, - LLFontGL::LEFT, LLFontGL::BASELINE, - LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, 1000, &right_x); - - // render text on top - feedback_string = llformat("X: %.3f", vec.mV[VX]); - font->render(utf8str_to_wstring(feedback_string), 0, window_center_x - 102.f, window_center_y + vertical_offset, LLColor4(1.f, 0.5f, 0.5f, 1.f), - LLFontGL::LEFT, LLFontGL::BASELINE, - LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, 1000, &right_x); - - feedback_string = llformat("Y: %.3f", vec.mV[VY]); - font->render(utf8str_to_wstring(feedback_string), 0, window_center_x - 27.f, window_center_y + vertical_offset, LLColor4(0.5f, 1.f, 0.5f, 1.f), - LLFontGL::LEFT, LLFontGL::BASELINE, - LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, 1000, &right_x); - - feedback_string = llformat("Z: %.3f", vec.mV[VZ]); - font->render(utf8str_to_wstring(feedback_string), 0, window_center_x + 48.f, window_center_y + vertical_offset, LLColor4(0.5f, 0.5f, 1.f, 1.f), - LLFontGL::LEFT, LLFontGL::BASELINE, - LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, 1000, &right_x); - } - gGL.popMatrix(); - - gViewerWindow->setup3DRender(); -} - -void LLManip::renderTickText(const LLVector3& pos, const std::string& text, const LLColor4 &color) -{ - const LLFontGL* big_fontp = LLFontGL::getFontSansSerif(); - - bool hud_selection = mObjectSelection->getSelectType() == SELECT_TYPE_HUD; - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - LLVector3 render_pos = pos; - if (hud_selection) - { - F32 zoom_amt = gAgentCamera.mHUDCurZoom; - F32 inv_zoom_amt = 1.f / zoom_amt; - // scale text back up to counter-act zoom level - render_pos = pos * zoom_amt; - gGL.scalef(inv_zoom_amt, inv_zoom_amt, inv_zoom_amt); - } - - // render shadow first - LLColor4 shadow_color = LLColor4::black; - shadow_color.mV[VALPHA] = color.mV[VALPHA] * 0.5f; - gViewerWindow->setup3DViewport(1, -1); - hud_render_utf8text(text, render_pos, *big_fontp, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, -0.5f * big_fontp->getWidthF32(text), 3.f, shadow_color, mObjectSelection->getSelectType() == SELECT_TYPE_HUD); - gViewerWindow->setup3DViewport(); - hud_render_utf8text(text, render_pos, *big_fontp, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, -0.5f * big_fontp->getWidthF32(text), 3.f, color, mObjectSelection->getSelectType() == SELECT_TYPE_HUD); - - gGL.popMatrix(); -} - -void LLManip::renderTickValue(const LLVector3& pos, F32 value, const std::string& suffix, const LLColor4 &color) -{ - LLLocale locale(LLLocale::USER_LOCALE); - - const LLFontGL* big_fontp = LLFontGL::getFontSansSerif(); - const LLFontGL* small_fontp = LLFontGL::getFontSansSerifSmall(); - - std::string val_string; - std::string fraction_string; - F32 val_to_print = ll_round(value, 0.001f); - S32 fractional_portion = ll_round(fmodf(llabs(val_to_print), 1.f) * 100.f); - if (val_to_print < 0.f) - { - if (fractional_portion == 0) - { - val_string = llformat("-%d%s", lltrunc(llabs(val_to_print)), suffix.c_str()); - } - else - { - val_string = llformat("-%d", lltrunc(llabs(val_to_print))); - } - } - else - { - if (fractional_portion == 0) - { - val_string = llformat("%d%s", lltrunc(llabs(val_to_print)), suffix.c_str()); - } - else - { - val_string = llformat("%d", lltrunc(val_to_print)); - } - } - - bool hud_selection = mObjectSelection->getSelectType() == SELECT_TYPE_HUD; - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - { - LLVector3 render_pos = pos; - if (hud_selection) - { - F32 zoom_amt = gAgentCamera.mHUDCurZoom; - F32 inv_zoom_amt = 1.f / zoom_amt; - // scale text back up to counter-act zoom level - render_pos = pos * zoom_amt; - gGL.scalef(inv_zoom_amt, inv_zoom_amt, inv_zoom_amt); - } - - LLColor4 shadow_color = LLColor4::black; - shadow_color.mV[VALPHA] = color.mV[VALPHA] * 0.5f; - - if (fractional_portion != 0) - { - fraction_string = llformat("%c%02d%s", LLResMgr::getInstance()->getDecimalPoint(), fractional_portion, suffix.c_str()); - - hud_render_utf8text(val_string, render_pos, *big_fontp, LLFontGL::NORMAL, LLFontGL::DROP_SHADOW, -1.f * big_fontp->getWidthF32(val_string), 3.f, color, hud_selection); - hud_render_utf8text(fraction_string, render_pos, *small_fontp, LLFontGL::NORMAL, LLFontGL::DROP_SHADOW, 1.f, 3.f, color, hud_selection); - } - else - { - hud_render_utf8text(val_string, render_pos, *big_fontp, LLFontGL::NORMAL, LLFontGL::DROP_SHADOW, -0.5f * big_fontp->getWidthF32(val_string), 3.f, color, hud_selection); - } - } - gGL.popMatrix(); -} - -LLColor4 LLManip::setupSnapGuideRenderPass(S32 pass) -{ - static LLColor4 grid_color_fg = LLUIColorTable::instance().getColor("GridlineColor"); - static LLColor4 grid_color_bg = LLUIColorTable::instance().getColor("GridlineBGColor"); - static LLColor4 grid_color_shadow = LLUIColorTable::instance().getColor("GridlineShadowColor"); - - LLColor4 line_color; - F32 line_alpha = gSavedSettings.getF32("GridOpacity"); - - switch(pass) - { - case 0: - // shadow - gViewerWindow->setup3DViewport(1, -1); - line_color = grid_color_shadow; - line_color.mV[VALPHA] *= line_alpha; - LLUI::setLineWidth(2.f); - break; - case 1: - // hidden lines - gViewerWindow->setup3DViewport(); - line_color = grid_color_bg; - line_color.mV[VALPHA] *= line_alpha; - LLUI::setLineWidth(1.f); - break; - case 2: - // visible lines - line_color = grid_color_fg; - line_color.mV[VALPHA] *= line_alpha; - break; - } - - return line_color; -} +/** + * @file llmanip.cpp + * @brief LLManip class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llmanip.h" + +#include "llmath.h" +#include "v3math.h" +#include "llgl.h" +#include "llrender.h" +#include "llprimitive.h" +#include "llview.h" +#include "llviewertexturelist.h" + +#include "llagent.h" +#include "llagentcamera.h" +#include "llviewercontrol.h" +#include "lldrawable.h" +#include "llfontgl.h" +#include "llhudrender.h" +#include "llselectmgr.h" +#include "llui.h" +#include "llviewercamera.h" +#include "llviewerjoint.h" +#include "llviewerobject.h" +#include "llviewerwindow.h" +#include "llvoavatar.h" +#include "llworld.h" // for LLWorld::getInstance() +#include "llresmgr.h" +#include "pipeline.h" +#include "llglheaders.h" +#include "lluiimage.h" +// Local constants... +const S32 VERTICAL_OFFSET = 50; + +F32 LLManip::sHelpTextVisibleTime = 2.f; +F32 LLManip::sHelpTextFadeTime = 2.f; +S32 LLManip::sNumTimesHelpTextShown = 0; +S32 LLManip::sMaxTimesShowHelpText = 5; +F32 LLManip::sGridMaxSubdivisionLevel = 32.f; +F32 LLManip::sGridMinSubdivisionLevel = 1.f / 32.f; +LLVector2 LLManip::sTickLabelSpacing(60.f, 25.f); + + +//static +void LLManip::rebuild(LLViewerObject* vobj) +{ + LLDrawable* drawablep = vobj->mDrawable; + if (drawablep && drawablep->getVOVolume()) + { + gPipeline.markRebuild(drawablep,LLDrawable::REBUILD_VOLUME); + drawablep->setState(LLDrawable::MOVE_UNDAMPED); // force to UNDAMPED + drawablep->updateMove(); + LLSpatialGroup* group = drawablep->getSpatialGroup(); + if (group) + { + group->dirtyGeom(); + gPipeline.markRebuild(group); + } + + LLViewerObject::const_child_list_t& child_list = vobj->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(), endIter = child_list.end(); + iter != endIter; ++iter) + { + LLViewerObject* child = *iter; + rebuild(child); + } + } +} + +////////////////////////////////////////////////////////////////////////////// +// LLManip + + +LLManip::LLManip( const std::string& name, LLToolComposite* composite ) + : + LLTool( name, composite ), + mInSnapRegime(false), + mHighlightedPart(LL_NO_PART), + mManipPart(LL_NO_PART) +{ +} + +void LLManip::getManipNormal(LLViewerObject* object, EManipPart manip, LLVector3 &normal) +{ + LLVector3 grid_origin; + LLVector3 grid_scale; + LLQuaternion grid_rotation; + + LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale); + + if (manip >= LL_X_ARROW && manip <= LL_Z_ARROW) + { + LLVector3 arrow_axis; + getManipAxis(object, manip, arrow_axis); + + LLVector3 cross = arrow_axis % LLViewerCamera::getInstance()->getAtAxis(); + normal = cross % arrow_axis; + normal.normVec(); + } + else if (manip >= LL_YZ_PLANE && manip <= LL_XY_PLANE) + { + switch (manip) + { + case LL_YZ_PLANE: + normal = LLVector3::x_axis; + break; + case LL_XZ_PLANE: + normal = LLVector3::y_axis; + break; + case LL_XY_PLANE: + normal = LLVector3::z_axis; + break; + default: + break; + } + normal.rotVec(grid_rotation); + } + else + { + normal.clearVec(); + } +} + + +bool LLManip::getManipAxis(LLViewerObject* object, EManipPart manip, LLVector3 &axis) +{ + LLVector3 grid_origin; + LLVector3 grid_scale; + LLQuaternion grid_rotation; + + LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale); + + if (manip == LL_X_ARROW) + { + axis = LLVector3::x_axis; + } + else if (manip == LL_Y_ARROW) + { + axis = LLVector3::y_axis; + } + else if (manip == LL_Z_ARROW) + { + axis = LLVector3::z_axis; + } + else + { + return false; + } + + axis.rotVec( grid_rotation ); + return true; +} + +F32 LLManip::getSubdivisionLevel(const LLVector3 &reference_point, const LLVector3 &translate_axis, F32 grid_scale, S32 min_pixel_spacing, F32 min_subdivisions, F32 max_subdivisions) +{ + //update current snap subdivision level + LLVector3 cam_to_reference; + if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) + { + cam_to_reference = LLVector3(1.f / gAgentCamera.mHUDCurZoom, 0.f, 0.f); + } + else + { + cam_to_reference = reference_point - LLViewerCamera::getInstance()->getOrigin(); + } + F32 current_range = cam_to_reference.normVec(); + + F32 projected_translation_axis_length = (translate_axis % cam_to_reference).magVec(); + F32 subdivisions = llmax(projected_translation_axis_length * grid_scale / (current_range / LLViewerCamera::getInstance()->getPixelMeterRatio() * min_pixel_spacing), 0.f); + // figure out nearest power of 2 that subdivides grid_scale with result > min_pixel_spacing + subdivisions = llclamp((F32)pow(2.f, llfloor(log(subdivisions) / log(2.f))), min_subdivisions, max_subdivisions); + + return subdivisions; +} + +void LLManip::handleSelect() +{ + mObjectSelection = LLSelectMgr::getInstance()->getEditSelection(); +} + +void LLManip::handleDeselect() +{ + mHighlightedPart = LL_NO_PART; + mManipPart = LL_NO_PART; + mObjectSelection = NULL; +} + +LLObjectSelectionHandle LLManip::getSelection() +{ + return mObjectSelection; +} + +bool LLManip::handleHover(S32 x, S32 y, MASK mask) +{ + // We only handle the event if mousedown started with us + if( hasMouseCapture() ) + { + if( mObjectSelection->isEmpty() ) + { + // Somehow the object got deselected while we were dragging it. + // Release the mouse + setMouseCapture( false ); + } + + LL_DEBUGS("UserInput") << "hover handled by LLManip (active)" << LL_ENDL; + } + else + { + LL_DEBUGS("UserInput") << "hover handled by LLManip (inactive)" << LL_ENDL; + } + gViewerWindow->setCursor(UI_CURSOR_ARROW); + return true; +} + + +bool LLManip::handleMouseUp(S32 x, S32 y, MASK mask) +{ + bool handled = false; + if( hasMouseCapture() ) + { + handled = true; + setMouseCapture( false ); + } + return handled; +} + +void LLManip::updateGridSettings() +{ + sGridMaxSubdivisionLevel = gSavedSettings.getBOOL("GridSubUnit") ? (F32)gSavedSettings.getS32("GridSubdivision") : 1.f; +} + +bool LLManip::getMousePointOnPlaneAgent(LLVector3& point, S32 x, S32 y, LLVector3 origin, LLVector3 normal) +{ + LLVector3d origin_double = gAgent.getPosGlobalFromAgent(origin); + LLVector3d global_point; + bool result = getMousePointOnPlaneGlobal(global_point, x, y, origin_double, normal); + point = gAgent.getPosAgentFromGlobal(global_point); + return result; +} + +bool LLManip::getMousePointOnPlaneGlobal(LLVector3d& point, S32 x, S32 y, LLVector3d origin, LLVector3 normal) const +{ + if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) + { + bool result = false; + F32 mouse_x = ((F32)x / gViewerWindow->getWorldViewWidthScaled() - 0.5f) * LLViewerCamera::getInstance()->getAspect() / gAgentCamera.mHUDCurZoom; + F32 mouse_y = ((F32)y / gViewerWindow->getWorldViewHeightScaled() - 0.5f) / gAgentCamera.mHUDCurZoom; + + LLVector3 origin_agent = gAgent.getPosAgentFromGlobal(origin); + LLVector3 mouse_pos = LLVector3(0.f, -mouse_x, mouse_y); + if (llabs(normal.mV[VX]) < 0.001f) + { + // use largish value that should be outside HUD manipulation range + mouse_pos.mV[VX] = 10.f; + } + else + { + mouse_pos.mV[VX] = (normal * (origin_agent - mouse_pos)) + / (normal.mV[VX]); + result = true; + } + + point = gAgent.getPosGlobalFromAgent(mouse_pos); + return result; + } + else + { + return gViewerWindow->mousePointOnPlaneGlobal( + point, x, y, origin, normal ); + } + + //return false; +} + +// Given the line defined by mouse cursor (a1 + a_param*(a2-a1)) and the line defined by b1 + b_param*(b2-b1), +// returns a_param and b_param for the points where lines are closest to each other. +// Returns false if the two lines are parallel. +bool LLManip::nearestPointOnLineFromMouse( S32 x, S32 y, const LLVector3& b1, const LLVector3& b2, F32 &a_param, F32 &b_param ) +{ + LLVector3 a1; + LLVector3 a2; + + if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) + { + F32 mouse_x = (((F32)x / gViewerWindow->getWindowWidthScaled()) - 0.5f) * LLViewerCamera::getInstance()->getAspect() / gAgentCamera.mHUDCurZoom; + F32 mouse_y = (((F32)y / gViewerWindow->getWindowHeightScaled()) - 0.5f) / gAgentCamera.mHUDCurZoom; + a1 = LLVector3(llmin(b1.mV[VX] - 0.1f, b2.mV[VX] - 0.1f, 0.f), -mouse_x, mouse_y); + a2 = a1 + LLVector3(1.f, 0.f, 0.f); + } + else + { + a1 = gAgentCamera.getCameraPositionAgent(); + a2 = gAgentCamera.getCameraPositionAgent() + LLVector3(gViewerWindow->mouseDirectionGlobal(x, y)); + } + + bool parallel = true; + LLVector3 a = a2 - a1; + LLVector3 b = b2 - b1; + + LLVector3 normal; + F32 dist, denom; + normal = (b % a) % b; // normal to plane (P) through b and (shortest line between a and b) + normal.normVec(); + dist = b1 * normal; // distance from origin to P + + denom = normal * a; + if( (denom < -F_APPROXIMATELY_ZERO) || (F_APPROXIMATELY_ZERO < denom) ) + { + a_param = (dist - normal * a1) / denom; + parallel = false; + } + + normal = (a % b) % a; // normal to plane (P) through a and (shortest line between a and b) + normal.normVec(); + dist = a1 * normal; // distance from origin to P + denom = normal * b; + if( (denom < -F_APPROXIMATELY_ZERO) || (F_APPROXIMATELY_ZERO < denom) ) + { + b_param = (dist - normal * b1) / denom; + parallel = false; + } + + return parallel; +} + +LLVector3 LLManip::getSavedPivotPoint() const +{ + return LLSelectMgr::getInstance()->getSavedBBoxOfSelection().getCenterAgent(); +} + +LLVector3 LLManip::getPivotPoint() +{ + if (mObjectSelection->getFirstObject() && mObjectSelection->getObjectCount() == 1 && mObjectSelection->getSelectType() != SELECT_TYPE_HUD) + { + return mObjectSelection->getFirstObject()->getPivotPositionAgent(); + } + return LLSelectMgr::getInstance()->getBBoxOfSelection().getCenterAgent(); +} + + +void LLManip::renderGuidelines(bool draw_x, bool draw_y, bool draw_z) +{ + LLVector3 grid_origin; + LLQuaternion grid_rot; + LLVector3 grid_scale; + LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rot, grid_scale); + + const bool children_ok = true; + LLViewerObject* object = mObjectSelection->getFirstRootObject(children_ok); + if (!object) + { + return; + } + + //LLVector3 center_agent = LLSelectMgr::getInstance()->getBBoxOfSelection().getCenterAgent(); + LLVector3 center_agent = getPivotPoint(); + + gGL.pushMatrix(); + { + gGL.translatef(center_agent.mV[VX], center_agent.mV[VY], center_agent.mV[VZ]); + + F32 angle_radians, x, y, z; + + grid_rot.getAngleAxis(&angle_radians, &x, &y, &z); + gGL.rotatef(angle_radians * RAD_TO_DEG, x, y, z); + + F32 region_size = LLWorld::getInstance()->getRegionWidthInMeters(); + + const F32 LINE_ALPHA = 0.33f; + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + LLUI::setLineWidth(1.5f); + + if (draw_x) + { + gGL.color4f(1.f, 0.f, 0.f, LINE_ALPHA); + gGL.begin(LLRender::LINES); + gGL.vertex3f( -region_size, 0.f, 0.f ); + gGL.vertex3f( region_size, 0.f, 0.f ); + gGL.end(); + } + + if (draw_y) + { + gGL.color4f(0.f, 1.f, 0.f, LINE_ALPHA); + gGL.begin(LLRender::LINES); + gGL.vertex3f( 0.f, -region_size, 0.f ); + gGL.vertex3f( 0.f, region_size, 0.f ); + gGL.end(); + } + + if (draw_z) + { + gGL.color4f(0.f, 0.f, 1.f, LINE_ALPHA); + gGL.begin(LLRender::LINES); + gGL.vertex3f( 0.f, 0.f, -region_size ); + gGL.vertex3f( 0.f, 0.f, region_size ); + gGL.end(); + } + LLUI::setLineWidth(1.0f); + } + gGL.popMatrix(); +} + +void LLManip::renderXYZ(const LLVector3 &vec) +{ + const S32 PAD = 10; + std::string feedback_string; + S32 window_center_x = gViewerWindow->getWorldViewRectScaled().getWidth() / 2; + S32 window_center_y = gViewerWindow->getWorldViewRectScaled().getHeight() / 2; + S32 vertical_offset = window_center_y - VERTICAL_OFFSET; + + + gGL.pushMatrix(); + { + LLUIImagePtr imagep = LLUI::getUIImage("Rounded_Square"); + gViewerWindow->setup2DRender(); + const LLVector2& display_scale = gViewerWindow->getDisplayScale(); + gGL.color4f(0.f, 0.f, 0.f, 0.7f); + + imagep->draw( + (window_center_x - 115) * display_scale.mV[VX], + (window_center_y + vertical_offset - PAD) * display_scale.mV[VY], + 235 * display_scale.mV[VX], + (PAD * 2 + 10) * display_scale.mV[VY], + LLColor4(0.f, 0.f, 0.f, 0.7f) ); + + LLFontGL* font = LLFontGL::getFontSansSerif(); + LLLocale locale(LLLocale::USER_LOCALE); + LLGLDepthTest gls_depth(GL_FALSE); + + // render drop shadowed text (manually because of bigger 'distance') + F32 right_x; + feedback_string = llformat("X: %.3f", vec.mV[VX]); + font->render(utf8str_to_wstring(feedback_string), 0, window_center_x - 102.f + 1.f, window_center_y + vertical_offset - 2.f, LLColor4::black, + LLFontGL::LEFT, LLFontGL::BASELINE, + LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, 1000, &right_x); + + feedback_string = llformat("Y: %.3f", vec.mV[VY]); + font->render(utf8str_to_wstring(feedback_string), 0, window_center_x - 27.f + 1.f, window_center_y + vertical_offset - 2.f, LLColor4::black, + LLFontGL::LEFT, LLFontGL::BASELINE, + LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, 1000, &right_x); + + feedback_string = llformat("Z: %.3f", vec.mV[VZ]); + font->render(utf8str_to_wstring(feedback_string), 0, window_center_x + 48.f + 1.f, window_center_y + vertical_offset - 2.f, LLColor4::black, + LLFontGL::LEFT, LLFontGL::BASELINE, + LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, 1000, &right_x); + + // render text on top + feedback_string = llformat("X: %.3f", vec.mV[VX]); + font->render(utf8str_to_wstring(feedback_string), 0, window_center_x - 102.f, window_center_y + vertical_offset, LLColor4(1.f, 0.5f, 0.5f, 1.f), + LLFontGL::LEFT, LLFontGL::BASELINE, + LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, 1000, &right_x); + + feedback_string = llformat("Y: %.3f", vec.mV[VY]); + font->render(utf8str_to_wstring(feedback_string), 0, window_center_x - 27.f, window_center_y + vertical_offset, LLColor4(0.5f, 1.f, 0.5f, 1.f), + LLFontGL::LEFT, LLFontGL::BASELINE, + LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, 1000, &right_x); + + feedback_string = llformat("Z: %.3f", vec.mV[VZ]); + font->render(utf8str_to_wstring(feedback_string), 0, window_center_x + 48.f, window_center_y + vertical_offset, LLColor4(0.5f, 0.5f, 1.f, 1.f), + LLFontGL::LEFT, LLFontGL::BASELINE, + LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, 1000, &right_x); + } + gGL.popMatrix(); + + gViewerWindow->setup3DRender(); +} + +void LLManip::renderTickText(const LLVector3& pos, const std::string& text, const LLColor4 &color) +{ + const LLFontGL* big_fontp = LLFontGL::getFontSansSerif(); + + bool hud_selection = mObjectSelection->getSelectType() == SELECT_TYPE_HUD; + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + LLVector3 render_pos = pos; + if (hud_selection) + { + F32 zoom_amt = gAgentCamera.mHUDCurZoom; + F32 inv_zoom_amt = 1.f / zoom_amt; + // scale text back up to counter-act zoom level + render_pos = pos * zoom_amt; + gGL.scalef(inv_zoom_amt, inv_zoom_amt, inv_zoom_amt); + } + + // render shadow first + LLColor4 shadow_color = LLColor4::black; + shadow_color.mV[VALPHA] = color.mV[VALPHA] * 0.5f; + gViewerWindow->setup3DViewport(1, -1); + hud_render_utf8text(text, render_pos, *big_fontp, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, -0.5f * big_fontp->getWidthF32(text), 3.f, shadow_color, mObjectSelection->getSelectType() == SELECT_TYPE_HUD); + gViewerWindow->setup3DViewport(); + hud_render_utf8text(text, render_pos, *big_fontp, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, -0.5f * big_fontp->getWidthF32(text), 3.f, color, mObjectSelection->getSelectType() == SELECT_TYPE_HUD); + + gGL.popMatrix(); +} + +void LLManip::renderTickValue(const LLVector3& pos, F32 value, const std::string& suffix, const LLColor4 &color) +{ + LLLocale locale(LLLocale::USER_LOCALE); + + const LLFontGL* big_fontp = LLFontGL::getFontSansSerif(); + const LLFontGL* small_fontp = LLFontGL::getFontSansSerifSmall(); + + std::string val_string; + std::string fraction_string; + F32 val_to_print = ll_round(value, 0.001f); + S32 fractional_portion = ll_round(fmodf(llabs(val_to_print), 1.f) * 100.f); + if (val_to_print < 0.f) + { + if (fractional_portion == 0) + { + val_string = llformat("-%d%s", lltrunc(llabs(val_to_print)), suffix.c_str()); + } + else + { + val_string = llformat("-%d", lltrunc(llabs(val_to_print))); + } + } + else + { + if (fractional_portion == 0) + { + val_string = llformat("%d%s", lltrunc(llabs(val_to_print)), suffix.c_str()); + } + else + { + val_string = llformat("%d", lltrunc(val_to_print)); + } + } + + bool hud_selection = mObjectSelection->getSelectType() == SELECT_TYPE_HUD; + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + { + LLVector3 render_pos = pos; + if (hud_selection) + { + F32 zoom_amt = gAgentCamera.mHUDCurZoom; + F32 inv_zoom_amt = 1.f / zoom_amt; + // scale text back up to counter-act zoom level + render_pos = pos * zoom_amt; + gGL.scalef(inv_zoom_amt, inv_zoom_amt, inv_zoom_amt); + } + + LLColor4 shadow_color = LLColor4::black; + shadow_color.mV[VALPHA] = color.mV[VALPHA] * 0.5f; + + if (fractional_portion != 0) + { + fraction_string = llformat("%c%02d%s", LLResMgr::getInstance()->getDecimalPoint(), fractional_portion, suffix.c_str()); + + hud_render_utf8text(val_string, render_pos, *big_fontp, LLFontGL::NORMAL, LLFontGL::DROP_SHADOW, -1.f * big_fontp->getWidthF32(val_string), 3.f, color, hud_selection); + hud_render_utf8text(fraction_string, render_pos, *small_fontp, LLFontGL::NORMAL, LLFontGL::DROP_SHADOW, 1.f, 3.f, color, hud_selection); + } + else + { + hud_render_utf8text(val_string, render_pos, *big_fontp, LLFontGL::NORMAL, LLFontGL::DROP_SHADOW, -0.5f * big_fontp->getWidthF32(val_string), 3.f, color, hud_selection); + } + } + gGL.popMatrix(); +} + +LLColor4 LLManip::setupSnapGuideRenderPass(S32 pass) +{ + static LLColor4 grid_color_fg = LLUIColorTable::instance().getColor("GridlineColor"); + static LLColor4 grid_color_bg = LLUIColorTable::instance().getColor("GridlineBGColor"); + static LLColor4 grid_color_shadow = LLUIColorTable::instance().getColor("GridlineShadowColor"); + + LLColor4 line_color; + F32 line_alpha = gSavedSettings.getF32("GridOpacity"); + + switch(pass) + { + case 0: + // shadow + gViewerWindow->setup3DViewport(1, -1); + line_color = grid_color_shadow; + line_color.mV[VALPHA] *= line_alpha; + LLUI::setLineWidth(2.f); + break; + case 1: + // hidden lines + gViewerWindow->setup3DViewport(); + line_color = grid_color_bg; + line_color.mV[VALPHA] *= line_alpha; + LLUI::setLineWidth(1.f); + break; + case 2: + // visible lines + line_color = grid_color_fg; + line_color.mV[VALPHA] *= line_alpha; + break; + } + + return line_color; +} diff --git a/indra/newview/llmanip.h b/indra/newview/llmanip.h index fdf7b6bcf8..6208eca967 100644 --- a/indra/newview/llmanip.h +++ b/indra/newview/llmanip.h @@ -1,165 +1,165 @@ -/** - * @file llmanip.h - * @brief LLManip class definition - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_MANIP_H -#define LL_MANIP_H - -#include "lltool.h" -//#include "v3math.h" - -class LLView; -class LLTextBox; -class LLViewerObject; -class LLToolComposite; -class LLVector3; -class LLObjectSelection; - -const S32 MIN_DIVISION_PIXEL_WIDTH = 3; - -class LLManip : public LLTool -{ -public: - typedef enum e_manip_part - { - LL_NO_PART = 0, - - // Translation - LL_X_ARROW, - LL_Y_ARROW, - LL_Z_ARROW, - - LL_YZ_PLANE, - LL_XZ_PLANE, - LL_XY_PLANE, - - // Scale - LL_CORNER_NNN, - LL_CORNER_NNP, - LL_CORNER_NPN, - LL_CORNER_NPP, - LL_CORNER_PNN, - LL_CORNER_PNP, - LL_CORNER_PPN, - LL_CORNER_PPP, - - // Faces - LL_FACE_POSZ, - LL_FACE_POSX, - LL_FACE_POSY, - LL_FACE_NEGX, - LL_FACE_NEGY, - LL_FACE_NEGZ, - - // Edges - LL_EDGE_NEGX_NEGY, - LL_EDGE_NEGX_POSY, - LL_EDGE_POSX_NEGY, - LL_EDGE_POSX_POSY, - - LL_EDGE_NEGY_NEGZ, - LL_EDGE_NEGY_POSZ, - LL_EDGE_POSY_NEGZ, - LL_EDGE_POSY_POSZ, - - LL_EDGE_NEGZ_NEGX, - LL_EDGE_NEGZ_POSX, - LL_EDGE_POSZ_NEGX, - LL_EDGE_POSZ_POSX, - - // Rotation Manip - LL_ROT_GENERAL, - LL_ROT_X, - LL_ROT_Y, - LL_ROT_Z, - LL_ROT_ROLL - } EManipPart; - - // For use in loops and range checking. - typedef enum e_select_part_ranges - { - LL_ARROW_MIN = LL_X_ARROW, - LL_ARROW_MAX = LL_Z_ARROW, - - LL_CORNER_MIN = LL_CORNER_NNN, - LL_CORNER_MAX = LL_CORNER_PPP, - - LL_FACE_MIN = LL_FACE_POSZ, - LL_FACE_MAX = LL_FACE_NEGZ, - - LL_EDGE_MIN = LL_EDGE_NEGX_NEGY, - LL_EDGE_MAX = LL_EDGE_POSZ_POSX - } EManipPartRanges; -public: - static void rebuild(LLViewerObject* vobj); - - LLManip( const std::string& name, LLToolComposite* composite ); - - virtual bool handleMouseDownOnPart(S32 x, S32 y, MASK mask) = 0; - void renderGuidelines(bool draw_x = true, bool draw_y = true, bool draw_z = true); - static void renderXYZ(const LLVector3 &vec); - - /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); - virtual void highlightManipulators(S32 x, S32 y) = 0; - virtual void handleSelect(); - virtual void handleDeselect(); - virtual bool canAffectSelection() = 0; - - EManipPart getHighlightedPart() { return mHighlightedPart; } - - LLSafeHandle getSelection(); - -protected: - LLVector3 getSavedPivotPoint() const; - LLVector3 getPivotPoint(); - void getManipNormal(LLViewerObject* object, EManipPart manip, LLVector3 &normal); - bool getManipAxis(LLViewerObject* object, EManipPart manip, LLVector3 &axis); - F32 getSubdivisionLevel(const LLVector3 &reference_point, const LLVector3 &translate_axis, F32 grid_scale, S32 min_pixel_spacing = MIN_DIVISION_PIXEL_WIDTH, F32 min_subdivisions = sGridMinSubdivisionLevel, F32 max_subdivisions = sGridMaxSubdivisionLevel); - void renderTickValue(const LLVector3& pos, F32 value, const std::string& suffix, const LLColor4 &color); - void renderTickText(const LLVector3& pos, const std::string& suffix, const LLColor4 &color); - void updateGridSettings(); - bool getMousePointOnPlaneGlobal(LLVector3d& point, S32 x, S32 y, LLVector3d origin, LLVector3 normal) const; - bool getMousePointOnPlaneAgent(LLVector3& point, S32 x, S32 y, LLVector3 origin, LLVector3 normal); - bool nearestPointOnLineFromMouse( S32 x, S32 y, const LLVector3& b1, const LLVector3& b2, F32 &a_param, F32 &b_param ); - LLColor4 setupSnapGuideRenderPass(S32 pass); -protected: - LLFrameTimer mHelpTextTimer; - bool mInSnapRegime; - LLSafeHandle mObjectSelection; - EManipPart mHighlightedPart; - EManipPart mManipPart; - - static F32 sHelpTextVisibleTime; - static F32 sHelpTextFadeTime; - static S32 sNumTimesHelpTextShown; - static S32 sMaxTimesShowHelpText; - static F32 sGridMaxSubdivisionLevel; - static F32 sGridMinSubdivisionLevel; - static LLVector2 sTickLabelSpacing; -}; - - -#endif +/** + * @file llmanip.h + * @brief LLManip class definition + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_MANIP_H +#define LL_MANIP_H + +#include "lltool.h" +//#include "v3math.h" + +class LLView; +class LLTextBox; +class LLViewerObject; +class LLToolComposite; +class LLVector3; +class LLObjectSelection; + +const S32 MIN_DIVISION_PIXEL_WIDTH = 3; + +class LLManip : public LLTool +{ +public: + typedef enum e_manip_part + { + LL_NO_PART = 0, + + // Translation + LL_X_ARROW, + LL_Y_ARROW, + LL_Z_ARROW, + + LL_YZ_PLANE, + LL_XZ_PLANE, + LL_XY_PLANE, + + // Scale + LL_CORNER_NNN, + LL_CORNER_NNP, + LL_CORNER_NPN, + LL_CORNER_NPP, + LL_CORNER_PNN, + LL_CORNER_PNP, + LL_CORNER_PPN, + LL_CORNER_PPP, + + // Faces + LL_FACE_POSZ, + LL_FACE_POSX, + LL_FACE_POSY, + LL_FACE_NEGX, + LL_FACE_NEGY, + LL_FACE_NEGZ, + + // Edges + LL_EDGE_NEGX_NEGY, + LL_EDGE_NEGX_POSY, + LL_EDGE_POSX_NEGY, + LL_EDGE_POSX_POSY, + + LL_EDGE_NEGY_NEGZ, + LL_EDGE_NEGY_POSZ, + LL_EDGE_POSY_NEGZ, + LL_EDGE_POSY_POSZ, + + LL_EDGE_NEGZ_NEGX, + LL_EDGE_NEGZ_POSX, + LL_EDGE_POSZ_NEGX, + LL_EDGE_POSZ_POSX, + + // Rotation Manip + LL_ROT_GENERAL, + LL_ROT_X, + LL_ROT_Y, + LL_ROT_Z, + LL_ROT_ROLL + } EManipPart; + + // For use in loops and range checking. + typedef enum e_select_part_ranges + { + LL_ARROW_MIN = LL_X_ARROW, + LL_ARROW_MAX = LL_Z_ARROW, + + LL_CORNER_MIN = LL_CORNER_NNN, + LL_CORNER_MAX = LL_CORNER_PPP, + + LL_FACE_MIN = LL_FACE_POSZ, + LL_FACE_MAX = LL_FACE_NEGZ, + + LL_EDGE_MIN = LL_EDGE_NEGX_NEGY, + LL_EDGE_MAX = LL_EDGE_POSZ_POSX + } EManipPartRanges; +public: + static void rebuild(LLViewerObject* vobj); + + LLManip( const std::string& name, LLToolComposite* composite ); + + virtual bool handleMouseDownOnPart(S32 x, S32 y, MASK mask) = 0; + void renderGuidelines(bool draw_x = true, bool draw_y = true, bool draw_z = true); + static void renderXYZ(const LLVector3 &vec); + + /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); + virtual void highlightManipulators(S32 x, S32 y) = 0; + virtual void handleSelect(); + virtual void handleDeselect(); + virtual bool canAffectSelection() = 0; + + EManipPart getHighlightedPart() { return mHighlightedPart; } + + LLSafeHandle getSelection(); + +protected: + LLVector3 getSavedPivotPoint() const; + LLVector3 getPivotPoint(); + void getManipNormal(LLViewerObject* object, EManipPart manip, LLVector3 &normal); + bool getManipAxis(LLViewerObject* object, EManipPart manip, LLVector3 &axis); + F32 getSubdivisionLevel(const LLVector3 &reference_point, const LLVector3 &translate_axis, F32 grid_scale, S32 min_pixel_spacing = MIN_DIVISION_PIXEL_WIDTH, F32 min_subdivisions = sGridMinSubdivisionLevel, F32 max_subdivisions = sGridMaxSubdivisionLevel); + void renderTickValue(const LLVector3& pos, F32 value, const std::string& suffix, const LLColor4 &color); + void renderTickText(const LLVector3& pos, const std::string& suffix, const LLColor4 &color); + void updateGridSettings(); + bool getMousePointOnPlaneGlobal(LLVector3d& point, S32 x, S32 y, LLVector3d origin, LLVector3 normal) const; + bool getMousePointOnPlaneAgent(LLVector3& point, S32 x, S32 y, LLVector3 origin, LLVector3 normal); + bool nearestPointOnLineFromMouse( S32 x, S32 y, const LLVector3& b1, const LLVector3& b2, F32 &a_param, F32 &b_param ); + LLColor4 setupSnapGuideRenderPass(S32 pass); +protected: + LLFrameTimer mHelpTextTimer; + bool mInSnapRegime; + LLSafeHandle mObjectSelection; + EManipPart mHighlightedPart; + EManipPart mManipPart; + + static F32 sHelpTextVisibleTime; + static F32 sHelpTextFadeTime; + static S32 sNumTimesHelpTextShown; + static S32 sMaxTimesShowHelpText; + static F32 sGridMaxSubdivisionLevel; + static F32 sGridMinSubdivisionLevel; + static LLVector2 sTickLabelSpacing; +}; + + +#endif diff --git a/indra/newview/llmaniprotate.cpp b/indra/newview/llmaniprotate.cpp index 73424cf90e..c2f4200852 100644 --- a/indra/newview/llmaniprotate.cpp +++ b/indra/newview/llmaniprotate.cpp @@ -1,1950 +1,1950 @@ -/** - * @file llmaniprotate.cpp - * @brief LLManipRotate class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llmaniprotate.h" - -// library includes -#include "llmath.h" -#include "llgl.h" -#include "llrender.h" -#include "v4color.h" -#include "llprimitive.h" -#include "llview.h" -#include "llfontgl.h" - -// viewer includes -#include "llagent.h" -#include "llagentcamera.h" -#include "llbox.h" -#include "llbutton.h" -#include "llviewercontrol.h" -#include "llcriticaldamp.h" -#include "lltooltip.h" -#include "llfloatertools.h" -#include "llselectmgr.h" -#include "llstatusbar.h" -#include "llui.h" -#include "llvoavatar.h" -#include "llviewercamera.h" -#include "llviewerobject.h" -#include "llviewerobject.h" -#include "llviewershadermgr.h" -#include "llviewerwindow.h" -#include "llworld.h" -#include "pipeline.h" -#include "lldrawable.h" -#include "llglheaders.h" -#include "lltrans.h" -#include "llvoavatarself.h" -#include "llhudrender.h" - -const F32 RADIUS_PIXELS = 100.f; // size in screen space -const F32 SQ_RADIUS = RADIUS_PIXELS * RADIUS_PIXELS; -const F32 WIDTH_PIXELS = 8; -const S32 CIRCLE_STEPS = 100; -const F32 MAX_MANIP_SELECT_DISTANCE = 100.f; -const F32 SNAP_ANGLE_INCREMENT = 5.625f; -const F32 SNAP_ANGLE_DETENTE = SNAP_ANGLE_INCREMENT; -const F32 SNAP_GUIDE_RADIUS_1 = 2.8f; -const F32 SNAP_GUIDE_RADIUS_2 = 2.4f; -const F32 SNAP_GUIDE_RADIUS_3 = 2.2f; -const F32 SNAP_GUIDE_RADIUS_4 = 2.1f; -const F32 SNAP_GUIDE_RADIUS_5 = 2.05f; -const F32 SNAP_GUIDE_INNER_RADIUS = 2.f; -const F32 AXIS_ONTO_CAM_TOLERANCE = cos( 80.f * DEG_TO_RAD ); -const F32 SELECTED_MANIPULATOR_SCALE = 1.05f; -const F32 MANIPULATOR_SCALE_HALF_LIFE = 0.07f; - -extern void handle_reset_rotation(void*); // in LLViewerWindow - -LLManipRotate::LLManipRotate( LLToolComposite* composite ) -: LLManip( std::string("Rotate"), composite ), - mRotationCenter(), - mCenterScreen(), - mRotation(), - mMouseDown(), - mMouseCur(), - mRadiusMeters(0.f), - mCenterToCam(), - mCenterToCamNorm(), - mCenterToCamMag(0.f), - mCenterToProfilePlane(), - mCenterToProfilePlaneMag(0.f), - mSendUpdateOnMouseUp( false ), - mSmoothRotate( false ), - mCamEdgeOn(false), - mManipulatorScales(1.f, 1.f, 1.f, 1.f) -{ } - -void LLManipRotate::handleSelect() -{ - // *FIX: put this in mouseDown? - LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); - if (gFloaterTools) - { - gFloaterTools->setStatusText("rotate"); - } - LLManip::handleSelect(); -} - -void LLManipRotate::render() -{ - LLGLSUIDefault gls_ui; - gGL.getTexUnit(0)->bind(LLViewerFetchedTexture::sWhiteImagep); - LLGLDepthTest gls_depth(GL_TRUE); - LLGLEnable gl_blend(GL_BLEND); - - // You can rotate if you can move - LLViewerObject* first_object = mObjectSelection->getFirstMoveableObject(true); - if( !first_object ) - { - return; - } - - if( !updateVisiblity() ) - { - return; - } - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) - { - F32 zoom = gAgentCamera.mHUDCurZoom; - gGL.scalef(zoom, zoom, zoom); - } - - - LLVector3 center = gAgent.getPosAgentFromGlobal( mRotationCenter ); - - LLColor4 highlight_outside( 1.f, 1.f, 0.f, 1.f ); - LLColor4 highlight_inside( 0.7f, 0.7f, 0.f, 0.5f ); - F32 width_meters = WIDTH_PIXELS * mRadiusMeters / RADIUS_PIXELS; - - gGL.pushMatrix(); - { - - // are we in the middle of a constrained drag? - if (mManipPart >= LL_ROT_X && mManipPart <= LL_ROT_Z) - { - renderSnapGuides(); - } - else - { - gDebugProgram.bind(); - - LLGLEnable cull_face(GL_CULL_FACE); - LLGLDepthTest gls_depth(GL_FALSE); - gGL.pushMatrix(); - { - // Draw "sphere" (intersection of sphere with tangent cone that has apex at camera) - gGL.translatef( mCenterToProfilePlane.mV[VX], mCenterToProfilePlane.mV[VY], mCenterToProfilePlane.mV[VZ] ); - gGL.translatef( center.mV[VX], center.mV[VY], center.mV[VZ] ); - - // Inverse change of basis vectors - LLVector3 forward = mCenterToCamNorm; - LLVector3 left = gAgent.getUpAxis() % forward; - left.normVec(); - LLVector3 up = forward % left; - - LLVector4 a(-forward); - a.mV[3] = 0; - LLVector4 b(up); - b.mV[3] = 0; - LLVector4 c(left); - c.mV[3] = 0; - LLMatrix4 mat; - mat.initRows(a, b, c, LLVector4(0.f, 0.f, 0.f, 1.f)); - - gGL.multMatrix( &mat.mMatrix[0][0] ); - - gGL.rotatef( -90, 0.f, 1.f, 0.f); - LLColor4 color; - if (mManipPart == LL_ROT_ROLL || mHighlightedPart == LL_ROT_ROLL) - { - color.setVec(0.8f, 0.8f, 0.8f, 0.8f); - gGL.scalef(mManipulatorScales.mV[VW], mManipulatorScales.mV[VW], mManipulatorScales.mV[VW]); - } - else - { - color.setVec( 0.7f, 0.7f, 0.7f, 0.6f ); - } - gGL.diffuseColor4fv(color.mV); - gl_washer_2d(mRadiusMeters + width_meters, mRadiusMeters, CIRCLE_STEPS, color, color); - - - if (mManipPart == LL_NO_PART) - { - gGL.color4f( 0.7f, 0.7f, 0.7f, 0.3f ); - gGL.diffuseColor4f(0.7f, 0.7f, 0.7f, 0.3f); - gl_circle_2d( 0, 0, mRadiusMeters, CIRCLE_STEPS, true ); - } - - gGL.flush(); - } - gGL.popMatrix(); - - gUIProgram.bind(); - } - - gGL.translatef( center.mV[VX], center.mV[VY], center.mV[VZ] ); - - LLQuaternion rot; - F32 angle_radians, x, y, z; - - LLVector3 grid_origin; - LLVector3 grid_scale; - LLQuaternion grid_rotation; - - LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale); - - grid_rotation.getAngleAxis(&angle_radians, &x, &y, &z); - gGL.rotatef(angle_radians * RAD_TO_DEG, x, y, z); - - - gDebugProgram.bind(); - - if (mManipPart == LL_ROT_Z) - { - mManipulatorScales = lerp(mManipulatorScales, LLVector4(1.f, 1.f, SELECTED_MANIPULATOR_SCALE, 1.f), LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE)); - gGL.pushMatrix(); - { - // selected part - gGL.scalef(mManipulatorScales.mV[VZ], mManipulatorScales.mV[VZ], mManipulatorScales.mV[VZ]); - renderActiveRing( mRadiusMeters, width_meters, LLColor4( 0.f, 0.f, 1.f, 1.f) , LLColor4( 0.f, 0.f, 1.f, 0.3f )); - } - gGL.popMatrix(); - } - else if (mManipPart == LL_ROT_Y) - { - mManipulatorScales = lerp(mManipulatorScales, LLVector4(1.f, SELECTED_MANIPULATOR_SCALE, 1.f, 1.f), LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE)); - gGL.pushMatrix(); - { - gGL.rotatef( 90.f, 1.f, 0.f, 0.f ); - gGL.scalef(mManipulatorScales.mV[VY], mManipulatorScales.mV[VY], mManipulatorScales.mV[VY]); - renderActiveRing( mRadiusMeters, width_meters, LLColor4( 0.f, 1.f, 0.f, 1.f), LLColor4( 0.f, 1.f, 0.f, 0.3f)); - } - gGL.popMatrix(); - } - else if (mManipPart == LL_ROT_X) - { - mManipulatorScales = lerp(mManipulatorScales, LLVector4(SELECTED_MANIPULATOR_SCALE, 1.f, 1.f, 1.f), LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE)); - gGL.pushMatrix(); - { - gGL.rotatef( 90.f, 0.f, 1.f, 0.f ); - gGL.scalef(mManipulatorScales.mV[VX], mManipulatorScales.mV[VX], mManipulatorScales.mV[VX]); - renderActiveRing( mRadiusMeters, width_meters, LLColor4( 1.f, 0.f, 0.f, 1.f), LLColor4( 1.f, 0.f, 0.f, 0.3f)); - } - gGL.popMatrix(); - } - else if (mManipPart == LL_ROT_ROLL) - { - mManipulatorScales = lerp(mManipulatorScales, LLVector4(1.f, 1.f, 1.f, SELECTED_MANIPULATOR_SCALE), LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE)); - } - else if (mManipPart == LL_NO_PART) - { - if (mHighlightedPart == LL_NO_PART) - { - mManipulatorScales = lerp(mManipulatorScales, LLVector4(1.f, 1.f, 1.f, 1.f), LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE)); - } - - LLGLEnable cull_face(GL_CULL_FACE); - LLGLEnable clip_plane0(GL_CLIP_PLANE0); - LLGLDepthTest gls_depth(GL_FALSE); - //LLGLDisable gls_stencil(GL_STENCIL_TEST); - - // First pass: centers. Second pass: sides. - for( S32 i=0; i<2; i++ ) - { - - gGL.pushMatrix(); - { - if (mHighlightedPart == LL_ROT_Z) - { - mManipulatorScales = lerp(mManipulatorScales, LLVector4(1.f, 1.f, SELECTED_MANIPULATOR_SCALE, 1.f), LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE)); - gGL.scalef(mManipulatorScales.mV[VZ], mManipulatorScales.mV[VZ], mManipulatorScales.mV[VZ]); - // hovering over part - gl_ring( mRadiusMeters, width_meters, LLColor4( 0.f, 0.f, 1.f, 1.f ), LLColor4( 0.f, 0.f, 1.f, 0.5f ), CIRCLE_STEPS, i); - } - else - { - // default - gl_ring( mRadiusMeters, width_meters, LLColor4( 0.f, 0.f, 0.8f, 0.8f ), LLColor4( 0.f, 0.f, 0.8f, 0.4f ), CIRCLE_STEPS, i); - } - } - gGL.popMatrix(); - - gGL.pushMatrix(); - { - gGL.rotatef( 90.f, 1.f, 0.f, 0.f ); - if (mHighlightedPart == LL_ROT_Y) - { - mManipulatorScales = lerp(mManipulatorScales, LLVector4(1.f, SELECTED_MANIPULATOR_SCALE, 1.f, 1.f), LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE)); - gGL.scalef(mManipulatorScales.mV[VY], mManipulatorScales.mV[VY], mManipulatorScales.mV[VY]); - // hovering over part - gl_ring( mRadiusMeters, width_meters, LLColor4( 0.f, 1.f, 0.f, 1.f ), LLColor4( 0.f, 1.f, 0.f, 0.5f ), CIRCLE_STEPS, i); - } - else - { - // default - gl_ring( mRadiusMeters, width_meters, LLColor4( 0.f, 0.8f, 0.f, 0.8f ), LLColor4( 0.f, 0.8f, 0.f, 0.4f ), CIRCLE_STEPS, i); - } - } - gGL.popMatrix(); - - gGL.pushMatrix(); - { - gGL.rotatef( 90.f, 0.f, 1.f, 0.f ); - if (mHighlightedPart == LL_ROT_X) - { - mManipulatorScales = lerp(mManipulatorScales, LLVector4(SELECTED_MANIPULATOR_SCALE, 1.f, 1.f, 1.f), LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE)); - gGL.scalef(mManipulatorScales.mV[VX], mManipulatorScales.mV[VX], mManipulatorScales.mV[VX]); - - // hovering over part - gl_ring( mRadiusMeters, width_meters, LLColor4( 1.f, 0.f, 0.f, 1.f ), LLColor4( 1.f, 0.f, 0.f, 0.5f ), CIRCLE_STEPS, i); - } - else - { - // default - gl_ring( mRadiusMeters, width_meters, LLColor4( 0.8f, 0.f, 0.f, 0.8f ), LLColor4( 0.8f, 0.f, 0.f, 0.4f ), CIRCLE_STEPS, i); - } - } - gGL.popMatrix(); - - if (mHighlightedPart == LL_ROT_ROLL) - { - mManipulatorScales = lerp(mManipulatorScales, LLVector4(1.f, 1.f, 1.f, SELECTED_MANIPULATOR_SCALE), LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE)); - } - - } - - } - - gUIProgram.bind(); - } - gGL.popMatrix(); - gGL.popMatrix(); - - - LLVector3 euler_angles; - LLQuaternion object_rot = first_object->getRotationEdit(); - object_rot.getEulerAngles(&(euler_angles.mV[VX]), &(euler_angles.mV[VY]), &(euler_angles.mV[VZ])); - euler_angles *= RAD_TO_DEG; - euler_angles.mV[VX] = ll_round(fmodf(euler_angles.mV[VX] + 360.f, 360.f), 0.05f); - euler_angles.mV[VY] = ll_round(fmodf(euler_angles.mV[VY] + 360.f, 360.f), 0.05f); - euler_angles.mV[VZ] = ll_round(fmodf(euler_angles.mV[VZ] + 360.f, 360.f), 0.05f); - - renderXYZ(euler_angles); -} - -bool LLManipRotate::handleMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - LLViewerObject* first_object = mObjectSelection->getFirstMoveableObject(true); - if( first_object ) - { - if( mHighlightedPart != LL_NO_PART ) - { - handled = handleMouseDownOnPart( x, y, mask ); - } - } - - return handled; -} - -// Assumes that one of the parts of the manipulator was hit. -bool LLManipRotate::handleMouseDownOnPart( S32 x, S32 y, MASK mask ) -{ - bool can_rotate = canAffectSelection(); - if (!can_rotate) - { - return false; - } - - highlightManipulators(x, y); - S32 hit_part = mHighlightedPart; - // we just started a drag, so save initial object positions - LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_ROTATE); - - // save selection center - mRotationCenter = gAgent.getPosGlobalFromAgent( getPivotPoint() ); //LLSelectMgr::getInstance()->getSelectionCenterGlobal(); - - mManipPart = (EManipPart)hit_part; - LLVector3 center = gAgent.getPosAgentFromGlobal( mRotationCenter ); - - if( mManipPart == LL_ROT_GENERAL) - { - mMouseDown = intersectMouseWithSphere( x, y, center, mRadiusMeters); - } - else - { - // Project onto the plane of the ring - LLVector3 axis = getConstraintAxis(); - - F32 axis_onto_cam = llabs( axis * mCenterToCamNorm ); - const F32 AXIS_ONTO_CAM_TOL = cos( 85.f * DEG_TO_RAD ); - if( axis_onto_cam < AXIS_ONTO_CAM_TOL ) - { - LLVector3 up_from_axis = mCenterToCamNorm % axis; - up_from_axis.normVec(); - LLVector3 cur_intersection; - getMousePointOnPlaneAgent(cur_intersection, x, y, center, mCenterToCam); - cur_intersection -= center; - mMouseDown = projected_vec(cur_intersection, up_from_axis); - F32 mouse_depth = SNAP_GUIDE_INNER_RADIUS * mRadiusMeters; - F32 mouse_dist_sqrd = mMouseDown.magVecSquared(); - if (mouse_dist_sqrd > 0.0001f) - { - mouse_depth = sqrtf((SNAP_GUIDE_INNER_RADIUS * mRadiusMeters) * (SNAP_GUIDE_INNER_RADIUS * mRadiusMeters) - - mouse_dist_sqrd); - } - LLVector3 projected_center_to_cam = mCenterToCamNorm - projected_vec(mCenterToCamNorm, axis); - mMouseDown += mouse_depth * projected_center_to_cam; - - } - else - { - mMouseDown = findNearestPointOnRing( x, y, center, axis ) - center; - mMouseDown.normVec(); - } - } - - mMouseCur = mMouseDown; - mAgentSelfAtAxis = gAgent.getAtAxis(); // no point checking if avatar was selected, just save the value - - // Route future Mouse messages here preemptively. (Release on mouse up.) - setMouseCapture( true ); - LLSelectMgr::getInstance()->enableSilhouette(false); - - mHelpTextTimer.reset(); - sNumTimesHelpTextShown++; - return true; -} - - -LLVector3 LLManipRotate::findNearestPointOnRing( S32 x, S32 y, const LLVector3& center, const LLVector3& axis ) -{ - // Project the delta onto the ring and rescale it by the radius so that it's _on_ the ring. - LLVector3 proj_onto_ring; - getMousePointOnPlaneAgent(proj_onto_ring, x, y, center, axis); - proj_onto_ring -= center; - proj_onto_ring.normVec(); - - return center + proj_onto_ring * mRadiusMeters; -} - -bool LLManipRotate::handleMouseUp(S32 x, S32 y, MASK mask) -{ - // first, perform normal processing in case this was a quick-click - handleHover(x, y, mask); - - if( hasMouseCapture() ) - { - for (LLObjectSelection::iterator iter = mObjectSelection->begin(); - iter != mObjectSelection->end(); iter++) - { - LLSelectNode* selectNode = *iter; - LLViewerObject* object = selectNode->getObject(); - LLViewerObject* root_object = (object == NULL) ? NULL : object->getRootEdit(); - - // have permission to move and object is root of selection or individually selected - if (object->permMove() && !object->isPermanentEnforced() && - ((root_object == NULL) || !root_object->isPermanentEnforced()) && - (object->isRootEdit() || selectNode->mIndividualSelection)) - { - object->mUnselectedChildrenPositions.clear() ; - } - } - - mManipPart = LL_NO_PART; - - // Might have missed last update due to timing. - LLSelectMgr::getInstance()->sendMultipleUpdate( UPD_ROTATION | UPD_POSITION ); - LLSelectMgr::getInstance()->enableSilhouette(true); - //gAgent.setObjectTracking(gSavedSettings.getBOOL("TrackFocusObject")); - - LLSelectMgr::getInstance()->updateSelectionCenter(); - LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); - } - - return LLManip::handleMouseUp(x, y, mask); -} - - -bool LLManipRotate::handleHover(S32 x, S32 y, MASK mask) -{ - if( hasMouseCapture() ) - { - if( mObjectSelection->isEmpty() ) - { - // Somehow the object got deselected while we were dragging it. - setMouseCapture( false ); - } - else - { - drag(x, y); - } - - LL_DEBUGS("UserInput") << "hover handled by LLManipRotate (active)" << LL_ENDL; - } - else - { - highlightManipulators(x, y); - LL_DEBUGS("UserInput") << "hover handled by LLManipRotate (inactive)" << LL_ENDL; - } - - gViewerWindow->setCursor(UI_CURSOR_TOOLROTATE); - return true; -} - - -LLVector3 LLManipRotate::projectToSphere( F32 x, F32 y, bool* on_sphere ) -{ - F32 z = 0.f; - F32 dist_squared = x*x + y*y; - - *on_sphere = dist_squared <= SQ_RADIUS; - if( *on_sphere ) - { - z = sqrt(SQ_RADIUS - dist_squared); - } - return LLVector3( x, y, z ); -} - -// Freeform rotation -void LLManipRotate::drag( S32 x, S32 y ) -{ - if( !updateVisiblity() ) - { - return; - } - - if( mManipPart == LL_ROT_GENERAL ) - { - mRotation = dragUnconstrained(x, y); - } - else - { - mRotation = dragConstrained(x, y); - } - - bool damped = mSmoothRotate; - mSmoothRotate = false; - - for (LLObjectSelection::iterator iter = mObjectSelection->begin(); - iter != mObjectSelection->end(); iter++) - { - LLSelectNode* selectNode = *iter; - LLViewerObject* object = selectNode->getObject(); - LLViewerObject* root_object = (object == NULL) ? NULL : object->getRootEdit(); - - // have permission to move and object is root of selection or individually selected - if (object->permMove() && !object->isPermanentEnforced() && - ((root_object == NULL) || !root_object->isPermanentEnforced()) && - (object->isRootEdit() || selectNode->mIndividualSelection)) - { - if (!object->isRootEdit()) - { - // child objects should not update if parent is selected - LLViewerObject* editable_root = (LLViewerObject*)object->getParent(); - if (editable_root->isSelected()) - { - // we will be moved properly by our parent, so skip - continue; - } - } - - LLQuaternion new_rot = selectNode->mSavedRotation * mRotation; - std::vector& child_positions = object->mUnselectedChildrenPositions ; - std::vector child_rotations; - if (object->isRootEdit() && selectNode->mIndividualSelection) - { - object->saveUnselectedChildrenRotation(child_rotations) ; - object->saveUnselectedChildrenPosition(child_positions) ; - } - - if (object->getParent() && object->mDrawable.notNull()) - { - LLQuaternion invParentRotation = object->mDrawable->mXform.getParent()->getWorldRotation(); - invParentRotation.transQuat(); - - object->setRotation(new_rot * invParentRotation, damped); - rebuild(object); - } - else - { - object->setRotation(new_rot, damped); - LLVOAvatar* avatar = object->asAvatar(); - if (avatar && avatar->isSelf() - && LLSelectMgr::getInstance()->mAllowSelectAvatar - && !object->getParent()) - { - // Normal avatars use object's orienttion, but self uses - // separate LLCoordFrame - // See LVOAvatar::updateOrientation() - if (gAgentCamera.getFocusOnAvatar()) - { - //Don't rotate camera with avatar - gAgentCamera.setFocusOnAvatar(false, false, false); - } - - LLVector3 at_axis = mAgentSelfAtAxis; - at_axis *= mRotation; - at_axis.mV[VZ] = 0.f; - at_axis.normalize(); - gAgent.resetAxes(at_axis); - } - rebuild(object); - } - - // for individually selected roots, we need to counterrotate all the children - if (object->isRootEdit() && selectNode->mIndividualSelection) - { - //RN: must do non-damped updates on these objects so relative rotation appears constant - // instead of having two competing slerps making the child objects appear to "wobble" - object->resetChildrenRotationAndPosition(child_rotations, child_positions) ; - } - } - } - - // update positions - for (LLObjectSelection::iterator iter = mObjectSelection->begin(); - iter != mObjectSelection->end(); iter++) - { - LLSelectNode* selectNode = *iter; - LLViewerObject* object = selectNode->getObject(); - LLViewerObject* root_object = (object == NULL) ? NULL : object->getRootEdit(); - - // to avoid cumulative position changes we calculate the objects new position using its saved position - if (object && object->permMove() && !object->isPermanentEnforced() && - ((root_object == NULL) || !root_object->isPermanentEnforced())) - { - LLVector3 center = gAgent.getPosAgentFromGlobal( mRotationCenter ); - - LLVector3 old_position; - LLVector3 new_position; - - if (object->isAttachment() && object->mDrawable.notNull()) - { - // need to work in drawable space to handle selected items from multiple attachments - // (which have no shared frame of reference other than their render positions) - LLXform* parent_xform = object->mDrawable->getXform()->getParent(); - new_position = (selectNode->mSavedPositionLocal * parent_xform->getWorldRotation()) + parent_xform->getWorldPosition(); - old_position = (object->getPosition() * parent_xform->getWorldRotation()) + parent_xform->getWorldPosition();//object->getRenderPosition(); - } - else - { - new_position = gAgent.getPosAgentFromGlobal( selectNode->mSavedPositionGlobal ); - old_position = object->getPositionAgent(); - } - - new_position = (new_position - center) * mRotation; // new relative rotated position - new_position += center; - - if (object->isRootEdit() && !object->isAttachment()) - { - LLVector3d new_pos_global = gAgent.getPosGlobalFromAgent(new_position); - new_pos_global = LLWorld::getInstance()->clipToVisibleRegions(selectNode->mSavedPositionGlobal, new_pos_global); - new_position = gAgent.getPosAgentFromGlobal(new_pos_global); - } - - // for individually selected child objects - if (!object->isRootEdit() && selectNode->mIndividualSelection) - { - LLViewerObject* parentp = (LLViewerObject*)object->getParent(); - if (!parentp->isSelected()) - { - if (object->isAttachment() && object->mDrawable.notNull()) - { - // find position relative to render position of parent - object->setPosition((new_position - parentp->getRenderPosition()) * ~parentp->getRenderRotation()); - rebuild(object); - } - else - { - object->setPositionParent((new_position - parentp->getPositionAgent()) * ~parentp->getRotationRegion()); - rebuild(object); - } - } - } - else if (object->isRootEdit()) - { - if (object->isAttachment() && object->mDrawable.notNull()) - { - LLXform* parent_xform = object->mDrawable->getXform()->getParent(); - object->setPosition((new_position - parent_xform->getWorldPosition()) * ~parent_xform->getWorldRotation()); - rebuild(object); - } - else - { - object->setPositionAgent(new_position); - rebuild(object); - } - } - - // for individually selected roots, we need to counter-translate all unselected children - if (object->isRootEdit() && selectNode->mIndividualSelection) - { - // only offset by parent's translation as we've already countered parent's rotation - rebuild(object); - object->resetChildrenPosition(old_position - new_position) ; - } - } - } - - // store changes to override updates - for (LLObjectSelection::iterator iter = LLSelectMgr::getInstance()->getSelection()->begin(); - iter != LLSelectMgr::getInstance()->getSelection()->end(); iter++) - { - LLSelectNode* selectNode = *iter; - LLViewerObject*cur = selectNode->getObject(); - LLViewerObject *root_object = (cur == NULL) ? NULL : cur->getRootEdit(); - if( cur->permModify() && cur->permMove() && !cur->isPermanentEnforced() && - ((root_object == NULL) || !root_object->isPermanentEnforced()) && - (!cur->isAvatar() || LLSelectMgr::getInstance()->mAllowSelectAvatar)) - { - selectNode->mLastRotation = cur->getRotation(); - selectNode->mLastPositionLocal = cur->getPosition(); - } - } - - LLSelectMgr::getInstance()->updateSelectionCenter(); - - // RN: just clear focus so camera doesn't follow spurious object updates - gAgentCamera.clearFocusObject(); - dialog_refresh_all(); -} - -void LLManipRotate::renderActiveRing( F32 radius, F32 width, const LLColor4& front_color, const LLColor4& back_color) -{ - LLGLEnable cull_face(GL_CULL_FACE); - { - gl_ring(radius, width, back_color, back_color * 0.5f, CIRCLE_STEPS, false); - gl_ring(radius, width, back_color, back_color * 0.5f, CIRCLE_STEPS, true); - } - { - LLGLDepthTest gls_depth(GL_FALSE); - gl_ring(radius, width, front_color, front_color * 0.5f, CIRCLE_STEPS, false); - gl_ring(radius, width, front_color, front_color * 0.5f, CIRCLE_STEPS, true); - } -} - -void LLManipRotate::renderSnapGuides() -{ - static LLCachedControl snap_enabled(gSavedSettings, "SnapEnabled", true); - if (!snap_enabled) - { - return; - } - - LLVector3 grid_origin; - LLVector3 grid_scale; - LLQuaternion grid_rotation; - - LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale, true); - - LLVector3 constraint_axis = getConstraintAxis(); - - LLVector3 center = gAgent.getPosAgentFromGlobal( mRotationCenter ); - LLVector3 cam_at_axis; - if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) - { - cam_at_axis.setVec(1.f, 0.f, 0.f); - } - else - { - cam_at_axis = center - gAgentCamera.getCameraPositionAgent(); - cam_at_axis.normVec(); - } - - LLVector3 world_snap_axis; - LLVector3 test_axis = constraint_axis; - - bool constrain_to_ref_object = false; - if (mObjectSelection->getSelectType() == SELECT_TYPE_ATTACHMENT && isAgentAvatarValid()) - { - test_axis = test_axis * ~grid_rotation; - } - else if (LLSelectMgr::getInstance()->getGridMode() == GRID_MODE_REF_OBJECT) - { - test_axis = test_axis * ~grid_rotation; - constrain_to_ref_object = true; - } - - test_axis.abs(); - - // find closest global/reference axis to local constraint axis; - if (test_axis.mV[VX] > test_axis.mV[VY] && test_axis.mV[VX] > test_axis.mV[VZ]) - { - world_snap_axis = LLVector3::y_axis; - } - else if (test_axis.mV[VY] > test_axis.mV[VZ]) - { - world_snap_axis = LLVector3::z_axis; - } - else - { - world_snap_axis = LLVector3::x_axis; - } - - LLVector3 projected_snap_axis = world_snap_axis; - if (mObjectSelection->getSelectType() == SELECT_TYPE_ATTACHMENT && isAgentAvatarValid()) - { - projected_snap_axis = projected_snap_axis * grid_rotation; - } - else if (constrain_to_ref_object) - { - projected_snap_axis = projected_snap_axis * grid_rotation; - } - - // project world snap axis onto constraint plane - projected_snap_axis -= projected_vec(projected_snap_axis, constraint_axis); - projected_snap_axis.normVec(); - - S32 num_rings = mCamEdgeOn ? 2 : 1; - for (S32 ring_num = 0; ring_num < num_rings; ring_num++) - { - LLVector3 center = gAgent.getPosAgentFromGlobal( mRotationCenter ); - - if (mCamEdgeOn) - { - // draw two opposing rings - if (ring_num == 0) - { - center += constraint_axis * mRadiusMeters * 0.5f; - } - else - { - center -= constraint_axis * mRadiusMeters * 0.5f; - } - } - - LLGLDepthTest gls_depth(GL_FALSE); - for (S32 pass = 0; pass < 3; pass++) - { - // render snap guide ring - gGL.pushMatrix(); - - LLQuaternion snap_guide_rot; - F32 angle_radians, x, y, z; - snap_guide_rot.shortestArc(LLVector3::z_axis, getConstraintAxis()); - snap_guide_rot.getAngleAxis(&angle_radians, &x, &y, &z); - gGL.translatef(center.mV[VX], center.mV[VY], center.mV[VZ]); - gGL.rotatef(angle_radians * RAD_TO_DEG, x, y, z); - - LLColor4 line_color = setupSnapGuideRenderPass(pass); - - gGL.color4fv(line_color.mV); - - if (mCamEdgeOn) - { - // render an arc - LLVector3 edge_normal = cam_at_axis % constraint_axis; - edge_normal.normVec(); - LLVector3 x_axis_snap = LLVector3::x_axis * snap_guide_rot; - LLVector3 y_axis_snap = LLVector3::y_axis * snap_guide_rot; - - F32 end_angle = atan2(y_axis_snap * edge_normal, x_axis_snap * edge_normal); - //F32 start_angle = angle_between((-1.f * LLVector3::x_axis) * snap_guide_rot, edge_normal); - F32 start_angle = end_angle - F_PI; - gl_arc_2d(0.f, 0.f, mRadiusMeters * SNAP_GUIDE_INNER_RADIUS, CIRCLE_STEPS, false, start_angle, end_angle); - } - else - { - gl_circle_2d(0.f, 0.f, mRadiusMeters * SNAP_GUIDE_INNER_RADIUS, CIRCLE_STEPS, false); - } - gGL.popMatrix(); - - for (S32 i = 0; i < 64; i++) - { - bool render_text = true; - F32 deg = 5.625f * (F32)i; - LLVector3 inner_point; - LLVector3 outer_point; - LLVector3 text_point; - LLQuaternion rot(deg * DEG_TO_RAD, constraint_axis); - gGL.begin(LLRender::LINES); - { - inner_point = (projected_snap_axis * mRadiusMeters * SNAP_GUIDE_INNER_RADIUS * rot) + center; - F32 tick_length = 0.f; - if (i % 16 == 0) - { - tick_length = mRadiusMeters * (SNAP_GUIDE_RADIUS_1 - SNAP_GUIDE_INNER_RADIUS); - } - else if (i % 8 == 0) - { - tick_length = mRadiusMeters * (SNAP_GUIDE_RADIUS_2 - SNAP_GUIDE_INNER_RADIUS); - } - else if (i % 4 == 0) - { - tick_length = mRadiusMeters * (SNAP_GUIDE_RADIUS_3 - SNAP_GUIDE_INNER_RADIUS); - } - else if (i % 2 == 0) - { - tick_length = mRadiusMeters * (SNAP_GUIDE_RADIUS_4 - SNAP_GUIDE_INNER_RADIUS); - } - else - { - tick_length = mRadiusMeters * (SNAP_GUIDE_RADIUS_5 - SNAP_GUIDE_INNER_RADIUS); - } - - if (mCamEdgeOn) - { - // don't draw ticks that are on back side of circle - F32 dot = cam_at_axis * (projected_snap_axis * rot); - if (dot > 0.f) - { - outer_point = inner_point; - render_text = false; - } - else - { - if (ring_num == 0) - { - outer_point = inner_point + (constraint_axis * tick_length) * rot; - } - else - { - outer_point = inner_point - (constraint_axis * tick_length) * rot; - } - } - } - else - { - outer_point = inner_point + (projected_snap_axis * tick_length) * rot; - } - - text_point = outer_point + (projected_snap_axis * mRadiusMeters * 0.1f) * rot; - - gGL.vertex3fv(inner_point.mV); - gGL.vertex3fv(outer_point.mV); - } - gGL.end(); - - //RN: text rendering does own shadow pass, so only render once - if (pass == 1 && render_text && i % 16 == 0) - { - if (world_snap_axis.mV[VX]) - { - if (i == 0) - { - renderTickText(text_point, mObjectSelection->isAttachment() ? LLTrans::getString("Direction_Forward") : LLTrans::getString("Direction_East"), LLColor4::white); - } - else if (i == 16) - { - if (constraint_axis.mV[VZ] > 0.f) - { - renderTickText(text_point, mObjectSelection->isAttachment() ? LLTrans::getString("Direction_Left") : LLTrans::getString("Direction_North"), LLColor4::white); - } - else - { - renderTickText(text_point, mObjectSelection->isAttachment() ? LLTrans::getString("Direction_Right") : LLTrans::getString("Direction_South"), LLColor4::white); - } - } - else if (i == 32) - { - renderTickText(text_point, mObjectSelection->isAttachment() ? LLTrans::getString("Direction_Back") : LLTrans::getString("Direction_West"), LLColor4::white); - } - else - { - if (constraint_axis.mV[VZ] > 0.f) - { - renderTickText(text_point, mObjectSelection->isAttachment() ? LLTrans::getString("Direction_Right") : LLTrans::getString("Direction_South"), LLColor4::white); - } - else - { - renderTickText(text_point, mObjectSelection->isAttachment() ? LLTrans::getString("Direction_Left") : LLTrans::getString("Direction_North"), LLColor4::white); - } - } - } - else if (world_snap_axis.mV[VY]) - { - if (i == 0) - { - renderTickText(text_point, mObjectSelection->isAttachment() ? LLTrans::getString("Direction_Left") : LLTrans::getString("Direction_North"), LLColor4::white); - } - else if (i == 16) - { - if (constraint_axis.mV[VX] > 0.f) - { - renderTickText(text_point, LLTrans::getString("Direction_Up"), LLColor4::white); - } - else - { - renderTickText(text_point, LLTrans::getString("Direction_Down"), LLColor4::white); - } - } - else if (i == 32) - { - renderTickText(text_point, mObjectSelection->isAttachment() ? LLTrans::getString("Direction_Right") : LLTrans::getString("Direction_South"), LLColor4::white); - } - else - { - if (constraint_axis.mV[VX] > 0.f) - { - renderTickText(text_point, LLTrans::getString("Direction_Down"), LLColor4::white); - } - else - { - renderTickText(text_point, LLTrans::getString("Direction_Up"), LLColor4::white); - } - } - } - else if (world_snap_axis.mV[VZ]) - { - if (i == 0) - { - renderTickText(text_point, LLTrans::getString("Direction_Up"), LLColor4::white); - } - else if (i == 16) - { - if (constraint_axis.mV[VY] > 0.f) - { - renderTickText(text_point, mObjectSelection->isAttachment() ? LLTrans::getString("Direction_Forward") : LLTrans::getString("Direction_East"), LLColor4::white); - } - else - { - renderTickText(text_point, mObjectSelection->isAttachment() ? LLTrans::getString("Direction_Back") : LLTrans::getString("Direction_West"), LLColor4::white); - } - } - else if (i == 32) - { - renderTickText(text_point, LLTrans::getString("Direction_Down"), LLColor4::white); - } - else - { - if (constraint_axis.mV[VY] > 0.f) - { - renderTickText(text_point, mObjectSelection->isAttachment() ? LLTrans::getString("Direction_Back") : LLTrans::getString("Direction_West"), LLColor4::white); - } - else - { - renderTickText(text_point, mObjectSelection->isAttachment() ? LLTrans::getString("Direction_Forward") : LLTrans::getString("Direction_East"), LLColor4::white); - } - } - } - } - gGL.color4fv(line_color.mV); - } - - // now render projected object axis - if (mInSnapRegime) - { - LLVector3 object_axis; - getObjectAxisClosestToMouse(object_axis); - - // project onto constraint plane - LLSelectNode* first_node = mObjectSelection->getFirstMoveableNode(true); - object_axis = object_axis * first_node->getObject()->getRenderRotation(); - object_axis = object_axis - (object_axis * getConstraintAxis()) * getConstraintAxis(); - object_axis.normVec(); - object_axis = object_axis * SNAP_GUIDE_INNER_RADIUS * mRadiusMeters + center; - LLVector3 line_start = center; - - gGL.begin(LLRender::LINES); - { - gGL.vertex3fv(line_start.mV); - gGL.vertex3fv(object_axis.mV); - } - gGL.end(); - - // draw snap guide arrow - gGL.begin(LLRender::TRIANGLES); - { - LLVector3 arrow_dir; - LLVector3 arrow_span = (object_axis - line_start) % getConstraintAxis(); - arrow_span.normVec(); - - arrow_dir = mCamEdgeOn ? getConstraintAxis() : object_axis - line_start; - arrow_dir.normVec(); - if (ring_num == 1) - { - arrow_dir *= -1.f; - } - gGL.vertex3fv((object_axis + arrow_dir * mRadiusMeters * 0.1f).mV); - gGL.vertex3fv((object_axis + arrow_span * mRadiusMeters * 0.1f).mV); - gGL.vertex3fv((object_axis - arrow_span * mRadiusMeters * 0.1f).mV); - } - gGL.end(); - - { - LLGLDepthTest gls_depth(GL_TRUE); - gGL.begin(LLRender::LINES); - { - gGL.vertex3fv(line_start.mV); - gGL.vertex3fv(object_axis.mV); - } - gGL.end(); - - // draw snap guide arrow - gGL.begin(LLRender::TRIANGLES); - { - LLVector3 arrow_dir; - LLVector3 arrow_span = (object_axis - line_start) % getConstraintAxis(); - arrow_span.normVec(); - - arrow_dir = mCamEdgeOn ? getConstraintAxis() : object_axis - line_start; - arrow_dir.normVec(); - if (ring_num == 1) - { - arrow_dir *= -1.f; - } - - gGL.vertex3fv((object_axis + arrow_dir * mRadiusMeters * 0.1f).mV); - gGL.vertex3fv((object_axis + arrow_span * mRadiusMeters * 0.1f).mV); - gGL.vertex3fv((object_axis - arrow_span * mRadiusMeters * 0.1f).mV); - } - gGL.end(); - } - } - } - } - - - // render help text - if (mObjectSelection->getSelectType() != SELECT_TYPE_HUD) - { - if (mHelpTextTimer.getElapsedTimeF32() < sHelpTextVisibleTime + sHelpTextFadeTime && sNumTimesHelpTextShown < sMaxTimesShowHelpText) - { - LLVector3 selection_center_start = LLSelectMgr::getInstance()->getSavedBBoxOfSelection().getCenterAgent(); - - LLVector3 offset_dir = LLViewerCamera::getInstance()->getUpAxis(); - - F32 line_alpha = gSavedSettings.getF32("GridOpacity"); - - LLVector3 help_text_pos = selection_center_start + (mRadiusMeters * 3.f * offset_dir); - const LLFontGL* big_fontp = LLFontGL::getFontSansSerif(); - - std::string help_text = LLTrans::getString("manip_hint1"); - LLColor4 help_text_color = LLColor4::white; - help_text_color.mV[VALPHA] = clamp_rescale(mHelpTextTimer.getElapsedTimeF32(), sHelpTextVisibleTime, sHelpTextVisibleTime + sHelpTextFadeTime, line_alpha, 0.f); - hud_render_utf8text(help_text, help_text_pos, *big_fontp, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, -0.5f * big_fontp->getWidthF32(help_text), 3.f, help_text_color, false); - help_text = LLTrans::getString("manip_hint2"); - help_text_pos -= offset_dir * mRadiusMeters * 0.4f; - hud_render_utf8text(help_text, help_text_pos, *big_fontp, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, -0.5f * big_fontp->getWidthF32(help_text), 3.f, help_text_color, false); - } - } -} - -// Returns true if center of sphere is visible. Also sets a bunch of member variables that are used later (e.g. mCenterToCam) -bool LLManipRotate::updateVisiblity() -{ - // Don't want to recalculate the center of the selection during a drag. - // Due to packet delays, sometimes half the objects in the selection have their - // new position and half have their old one. This creates subtle errors in the - // computed center position for that frame. Unfortunately, these errors - // accumulate. The result is objects seem to "fly apart" during rotations. - // JC - 03.26.2002 - if (!hasMouseCapture()) - { - mRotationCenter = gAgent.getPosGlobalFromAgent( getPivotPoint() );//LLSelectMgr::getInstance()->getSelectionCenterGlobal(); - } - - bool visible = false; - - //Assume that UI scale factor is equivalent for X and Y axis - F32 ui_scale_factor = LLUI::getScaleFactor().mV[VX]; - - LLVector3 center = gAgent.getPosAgentFromGlobal( mRotationCenter ); - if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) - { - mCenterToCam = LLVector3(-1.f / gAgentCamera.mHUDCurZoom, 0.f, 0.f); - mCenterToCamNorm = mCenterToCam; - mCenterToCamMag = mCenterToCamNorm.normVec(); - - mRadiusMeters = RADIUS_PIXELS / (F32) LLViewerCamera::getInstance()->getViewHeightInPixels(); - mRadiusMeters /= gAgentCamera.mHUDCurZoom; - mRadiusMeters *= ui_scale_factor; - - mCenterToProfilePlaneMag = mRadiusMeters * mRadiusMeters / mCenterToCamMag; - mCenterToProfilePlane = -mCenterToProfilePlaneMag * mCenterToCamNorm; - - // x axis range is (-aspect * 0.5f, +aspect * 0.5) - // y axis range is (-0.5, 0.5) - // so use getWorldViewHeightRaw as scale factor when converting to pixel coordinates - mCenterScreen.set((S32)((0.5f - center.mV[VY]) / gAgentCamera.mHUDCurZoom * gViewerWindow->getWorldViewHeightScaled()), - (S32)((center.mV[VZ] + 0.5f) / gAgentCamera.mHUDCurZoom * gViewerWindow->getWorldViewHeightScaled())); - visible = true; - } - else - { - visible = LLViewerCamera::getInstance()->projectPosAgentToScreen(center, mCenterScreen ); - if( visible ) - { - mCenterToCam = gAgentCamera.getCameraPositionAgent() - center; - mCenterToCamNorm = mCenterToCam; - mCenterToCamMag = mCenterToCamNorm.normVec(); - LLVector3 cameraAtAxis = LLViewerCamera::getInstance()->getAtAxis(); - cameraAtAxis.normVec(); - - F32 z_dist = -1.f * (mCenterToCam * cameraAtAxis); - - // Don't drag manip if object too far away - if (gSavedSettings.getBOOL("LimitSelectDistance")) - { - F32 max_select_distance = gSavedSettings.getF32("MaxSelectDistance"); - if (dist_vec_squared(gAgent.getPositionAgent(), center) > (max_select_distance * max_select_distance)) - { - visible = false; - } - } - - if (mCenterToCamMag > 0.001f) - { - F32 fraction_of_fov = RADIUS_PIXELS / (F32) LLViewerCamera::getInstance()->getViewHeightInPixels(); - F32 apparent_angle = fraction_of_fov * LLViewerCamera::getInstance()->getView(); // radians - mRadiusMeters = z_dist * tan(apparent_angle); - mRadiusMeters *= ui_scale_factor; - - mCenterToProfilePlaneMag = mRadiusMeters * mRadiusMeters / mCenterToCamMag; - mCenterToProfilePlane = -mCenterToProfilePlaneMag * mCenterToCamNorm; - } - else - { - visible = false; - } - } - } - - mCamEdgeOn = false; - F32 axis_onto_cam = mManipPart >= LL_ROT_X ? llabs( getConstraintAxis() * mCenterToCamNorm ) : 0.f; - if( axis_onto_cam < AXIS_ONTO_CAM_TOLERANCE ) - { - mCamEdgeOn = true; - } - - return visible; -} - -LLQuaternion LLManipRotate::dragUnconstrained( S32 x, S32 y ) -{ - LLVector3 cam = gAgentCamera.getCameraPositionAgent(); - LLVector3 center = gAgent.getPosAgentFromGlobal( mRotationCenter ); - - mMouseCur = intersectMouseWithSphere( x, y, center, mRadiusMeters); - - F32 delta_x = (F32)(mCenterScreen.mX - x); - F32 delta_y = (F32)(mCenterScreen.mY - y); - - F32 dist_from_sphere_center = sqrt(delta_x * delta_x + delta_y * delta_y); - - LLVector3 axis = mMouseDown % mMouseCur; - F32 angle = atan2(sqrtf(axis * axis), mMouseDown * mMouseCur); - axis.normVec(); - LLQuaternion sphere_rot( angle, axis ); - - if (is_approx_zero(1.f - mMouseDown * mMouseCur)) - { - return LLQuaternion::DEFAULT; - } - else if (dist_from_sphere_center < RADIUS_PIXELS) - { - return sphere_rot; - } - else - { - LLVector3 intersection; - getMousePointOnPlaneAgent( intersection, x, y, center + mCenterToProfilePlane, mCenterToCamNorm ); - - // amount dragging in sphere from center to periphery would rotate object - F32 in_sphere_angle = F_PI_BY_TWO; - F32 dist_to_tangent_point = mRadiusMeters; - if( !is_approx_zero( mCenterToProfilePlaneMag ) ) - { - dist_to_tangent_point = sqrt( mRadiusMeters * mRadiusMeters - mCenterToProfilePlaneMag * mCenterToProfilePlaneMag ); - in_sphere_angle = atan2( dist_to_tangent_point, mCenterToProfilePlaneMag ); - } - - LLVector3 profile_center_to_intersection = intersection - (center + mCenterToProfilePlane); - F32 dist_to_intersection = profile_center_to_intersection.normVec(); - F32 angle = (-1.f + dist_to_intersection / dist_to_tangent_point) * in_sphere_angle; - - LLVector3 axis; - if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) - { - axis = LLVector3(-1.f, 0.f, 0.f) % profile_center_to_intersection; - } - else - { - axis = (cam - center) % profile_center_to_intersection; - axis.normVec(); - } - return sphere_rot * LLQuaternion( angle, axis ); - } -} - -LLVector3 LLManipRotate::getConstraintAxis() -{ - LLVector3 axis; - if( LL_ROT_ROLL == mManipPart ) - { - axis = mCenterToCamNorm; - } - else - { - S32 axis_dir = mManipPart - LL_ROT_X; - if ((axis_dir >= LL_NO_PART) && (axis_dir < LL_Z_ARROW)) - { - axis.mV[axis_dir] = 1.f; - } - else - { -#ifndef LL_RELEASE_FOR_DOWNLOAD - LL_ERRS() << "Got bogus hit part in LLManipRotate::getConstraintAxis():" << mManipPart << LL_ENDL; -#else - LL_WARNS() << "Got bogus hit part in LLManipRotate::getConstraintAxis():" << mManipPart << LL_ENDL; -#endif - axis.mV[0] = 1.f; - } - - LLVector3 grid_origin; - LLVector3 grid_scale; - LLQuaternion grid_rotation; - - LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale); - - LLSelectNode* first_node = mObjectSelection->getFirstMoveableNode(true); - if (first_node) - { - // *FIX: get agent local attachment grid working - // Put rotation into frame of first selected root object - axis = axis * grid_rotation; - } - } - - return axis; -} - -LLQuaternion LLManipRotate::dragConstrained( S32 x, S32 y ) -{ - LLSelectNode* first_object_node = mObjectSelection->getFirstMoveableNode(true); - LLVector3 constraint_axis = getConstraintAxis(); - LLVector3 center = gAgent.getPosAgentFromGlobal( mRotationCenter ); - - F32 angle = 0.f; - - // build snap axes - LLVector3 grid_origin; - LLVector3 grid_scale; - LLQuaternion grid_rotation; - - LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale); - - LLVector3 axis1; - LLVector3 axis2; - - LLVector3 test_axis = constraint_axis; - if (mObjectSelection->getSelectType() == SELECT_TYPE_ATTACHMENT && isAgentAvatarValid()) - { - test_axis = test_axis * ~grid_rotation; - } - else if (LLSelectMgr::getInstance()->getGridMode() == GRID_MODE_REF_OBJECT) - { - test_axis = test_axis * ~grid_rotation; - } - test_axis.abs(); - - // find closest global axis to constraint axis; - if (test_axis.mV[VX] > test_axis.mV[VY] && test_axis.mV[VX] > test_axis.mV[VZ]) - { - axis1 = LLVector3::y_axis; - } - else if (test_axis.mV[VY] > test_axis.mV[VZ]) - { - axis1 = LLVector3::z_axis; - } - else - { - axis1 = LLVector3::x_axis; - } - - if (mObjectSelection->getSelectType() == SELECT_TYPE_ATTACHMENT && isAgentAvatarValid()) - { - axis1 = axis1 * grid_rotation; - } - else if (LLSelectMgr::getInstance()->getGridMode() == GRID_MODE_REF_OBJECT) - { - axis1 = axis1 * grid_rotation; - } - - //project axis onto constraint plane - axis1 -= (axis1 * constraint_axis) * constraint_axis; - axis1.normVec(); - - // calculate third and final axis - axis2 = constraint_axis % axis1; - - //F32 axis_onto_cam = llabs( constraint_axis * mCenterToCamNorm ); - if( mCamEdgeOn ) - { - // We're looking at the ring edge-on. - LLVector3 snap_plane_center = (center + (constraint_axis * mRadiusMeters * 0.5f)); - LLVector3 cam_to_snap_plane; - if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) - { - cam_to_snap_plane.setVec(1.f, 0.f, 0.f); - } - else - { - cam_to_snap_plane = snap_plane_center - gAgentCamera.getCameraPositionAgent(); - cam_to_snap_plane.normVec(); - } - - LLVector3 projected_mouse; - bool hit = getMousePointOnPlaneAgent(projected_mouse, x, y, snap_plane_center, constraint_axis); - projected_mouse -= snap_plane_center; - - if (gSavedSettings.getBOOL("SnapEnabled")) { - S32 snap_plane = 0; - - F32 dot = cam_to_snap_plane * constraint_axis; - if (llabs(dot) < 0.01f) - { - // looking at ring edge on, project onto view plane and check if mouse is past ring - getMousePointOnPlaneAgent(projected_mouse, x, y, snap_plane_center, cam_to_snap_plane); - projected_mouse -= snap_plane_center; - dot = projected_mouse * constraint_axis; - if (projected_mouse * constraint_axis > 0) - { - snap_plane = 1; - } - projected_mouse -= dot * constraint_axis; - } - else if (dot > 0.f) - { - // look for mouse position outside and in front of snap circle - if (hit && projected_mouse.magVec() > SNAP_GUIDE_INNER_RADIUS * mRadiusMeters && projected_mouse * cam_to_snap_plane < 0.f) - { - snap_plane = 1; - } - } - else - { - // look for mouse position inside or in back of snap circle - if (projected_mouse.magVec() < SNAP_GUIDE_INNER_RADIUS * mRadiusMeters || projected_mouse * cam_to_snap_plane > 0.f || !hit) - { - snap_plane = 1; - } - } - - if (snap_plane == 0) - { - // try other plane - snap_plane_center = (center - (constraint_axis * mRadiusMeters * 0.5f)); - if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) - { - cam_to_snap_plane.setVec(1.f, 0.f, 0.f); - } - else - { - cam_to_snap_plane = snap_plane_center - gAgentCamera.getCameraPositionAgent(); - cam_to_snap_plane.normVec(); - } - - hit = getMousePointOnPlaneAgent(projected_mouse, x, y, snap_plane_center, constraint_axis); - projected_mouse -= snap_plane_center; - - dot = cam_to_snap_plane * constraint_axis; - if (llabs(dot) < 0.01f) - { - // looking at ring edge on, project onto view plane and check if mouse is past ring - getMousePointOnPlaneAgent(projected_mouse, x, y, snap_plane_center, cam_to_snap_plane); - projected_mouse -= snap_plane_center; - dot = projected_mouse * constraint_axis; - if (projected_mouse * constraint_axis < 0) - { - snap_plane = 2; - } - projected_mouse -= dot * constraint_axis; - } - else if (dot < 0.f) - { - // look for mouse position outside and in front of snap circle - if (hit && projected_mouse.magVec() > SNAP_GUIDE_INNER_RADIUS * mRadiusMeters && projected_mouse * cam_to_snap_plane < 0.f) - { - snap_plane = 2; - } - } - else - { - // look for mouse position inside or in back of snap circle - if (projected_mouse.magVec() < SNAP_GUIDE_INNER_RADIUS * mRadiusMeters || projected_mouse * cam_to_snap_plane > 0.f || !hit) - { - snap_plane = 2; - } - } - } - - if (snap_plane > 0) - { - LLVector3 cam_at_axis; - if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) - { - cam_at_axis.setVec(1.f, 0.f, 0.f); - } - else - { - cam_at_axis = snap_plane_center - gAgentCamera.getCameraPositionAgent(); - cam_at_axis.normVec(); - } - - // first, project mouse onto screen plane at point tangent to rotation radius. - getMousePointOnPlaneAgent(projected_mouse, x, y, snap_plane_center, cam_at_axis); - // project that point onto rotation plane - projected_mouse -= snap_plane_center; - projected_mouse -= projected_vec(projected_mouse, constraint_axis); - - F32 mouse_lateral_dist = llmin(SNAP_GUIDE_INNER_RADIUS * mRadiusMeters, projected_mouse.magVec()); - F32 mouse_depth = SNAP_GUIDE_INNER_RADIUS * mRadiusMeters; - if (llabs(mouse_lateral_dist) > 0.01f) - { - mouse_depth = sqrtf((SNAP_GUIDE_INNER_RADIUS * mRadiusMeters) * (SNAP_GUIDE_INNER_RADIUS * mRadiusMeters) - - (mouse_lateral_dist * mouse_lateral_dist)); - } - LLVector3 projected_camera_at = cam_at_axis - projected_vec(cam_at_axis, constraint_axis); - projected_mouse -= mouse_depth * projected_camera_at; - - if (!mInSnapRegime) - { - mSmoothRotate = true; - } - mInSnapRegime = true; - // 0 to 360 deg - F32 mouse_angle = fmodf(atan2(projected_mouse * axis1, projected_mouse * axis2) * RAD_TO_DEG + 360.f, 360.f); - - F32 relative_mouse_angle = fmodf(mouse_angle + (SNAP_ANGLE_DETENTE / 2), SNAP_ANGLE_INCREMENT); - - LLVector3 object_axis; - getObjectAxisClosestToMouse(object_axis); - if (first_object_node) - { - object_axis = object_axis * first_object_node->mSavedRotation; - } - - // project onto constraint plane - object_axis = object_axis - (object_axis * getConstraintAxis()) * getConstraintAxis(); - object_axis.normVec(); - - if (relative_mouse_angle < SNAP_ANGLE_DETENTE) - { - F32 quantized_mouse_angle = mouse_angle - (relative_mouse_angle - (SNAP_ANGLE_DETENTE * 0.5f)); - angle = (quantized_mouse_angle * DEG_TO_RAD) - atan2(object_axis * axis1, object_axis * axis2); - } - else - { - angle = (mouse_angle * DEG_TO_RAD) - atan2(object_axis * axis1, object_axis * axis2); - } - return LLQuaternion( -angle, constraint_axis ); - } - else - { - if (mInSnapRegime) - { - mSmoothRotate = true; - } - mInSnapRegime = false; - } - } - else { - if (mInSnapRegime) - { - mSmoothRotate = true; - } - mInSnapRegime = false; - } - - if (!mInSnapRegime) - { - LLVector3 up_from_axis = mCenterToCamNorm % constraint_axis; - up_from_axis.normVec(); - LLVector3 cur_intersection; - getMousePointOnPlaneAgent(cur_intersection, x, y, center, mCenterToCam); - cur_intersection -= center; - mMouseCur = projected_vec(cur_intersection, up_from_axis); - F32 mouse_depth = SNAP_GUIDE_INNER_RADIUS * mRadiusMeters; - F32 mouse_dist_sqrd = mMouseCur.magVecSquared(); - if (mouse_dist_sqrd > 0.0001f) - { - mouse_depth = sqrtf((SNAP_GUIDE_INNER_RADIUS * mRadiusMeters) * (SNAP_GUIDE_INNER_RADIUS * mRadiusMeters) - - mouse_dist_sqrd); - } - LLVector3 projected_center_to_cam = mCenterToCamNorm - projected_vec(mCenterToCamNorm, constraint_axis); - mMouseCur += mouse_depth * projected_center_to_cam; - - F32 dist = (cur_intersection * up_from_axis) - (mMouseDown * up_from_axis); - angle = dist / (SNAP_GUIDE_INNER_RADIUS * mRadiusMeters) * -F_PI_BY_TWO; - } - } - else - { - LLVector3 projected_mouse; - getMousePointOnPlaneAgent(projected_mouse, x, y, center, constraint_axis); - projected_mouse -= center; - mMouseCur = projected_mouse; - mMouseCur.normVec(); - - if (!first_object_node) - { - return LLQuaternion::DEFAULT; - } - - if (gSavedSettings.getBOOL("SnapEnabled") && projected_mouse.magVec() > SNAP_GUIDE_INNER_RADIUS * mRadiusMeters) - { - if (!mInSnapRegime) - { - mSmoothRotate = true; - } - mInSnapRegime = true; - // 0 to 360 deg - F32 mouse_angle = fmodf(atan2(projected_mouse * axis1, projected_mouse * axis2) * RAD_TO_DEG + 360.f, 360.f); - - F32 relative_mouse_angle = fmodf(mouse_angle + (SNAP_ANGLE_DETENTE / 2), SNAP_ANGLE_INCREMENT); - - LLVector3 object_axis; - getObjectAxisClosestToMouse(object_axis); - object_axis = object_axis * first_object_node->mSavedRotation; - - // project onto constraint plane - object_axis = object_axis - (object_axis * getConstraintAxis()) * getConstraintAxis(); - object_axis.normVec(); - - if (relative_mouse_angle < SNAP_ANGLE_DETENTE) - { - F32 quantized_mouse_angle = mouse_angle - (relative_mouse_angle - (SNAP_ANGLE_DETENTE * 0.5f)); - angle = (quantized_mouse_angle * DEG_TO_RAD) - atan2(object_axis * axis1, object_axis * axis2); - } - else - { - angle = (mouse_angle * DEG_TO_RAD) - atan2(object_axis * axis1, object_axis * axis2); - } - return LLQuaternion( -angle, constraint_axis ); - } - else - { - if (mInSnapRegime) - { - mSmoothRotate = true; - } - mInSnapRegime = false; - } - - LLVector3 cross_product = mMouseDown % mMouseCur; - angle = atan2(sqrtf(cross_product * cross_product), mMouseCur * mMouseDown); - F32 dir = cross_product * constraint_axis; // cross product - if( dir < 0.f ) - { - angle *= -1.f; - } - } - - F32 rot_step = gSavedSettings.getF32("RotationStep"); - F32 step_size = DEG_TO_RAD * rot_step; - angle -= fmod(angle, step_size); - - return LLQuaternion( angle, constraint_axis ); -} - - - -LLVector3 LLManipRotate::intersectMouseWithSphere( S32 x, S32 y, const LLVector3& sphere_center, F32 sphere_radius) -{ - LLVector3 ray_pt; - LLVector3 ray_dir; - mouseToRay( x, y, &ray_pt, &ray_dir); - return intersectRayWithSphere( ray_pt, ray_dir, sphere_center, sphere_radius ); -} - -LLVector3 LLManipRotate::intersectRayWithSphere( const LLVector3& ray_pt, const LLVector3& ray_dir, const LLVector3& sphere_center, F32 sphere_radius) -{ - LLVector3 ray_pt_to_center = sphere_center - ray_pt; - F32 center_distance = ray_pt_to_center.normVec(); - - F32 dot = ray_dir * ray_pt_to_center; - - if (dot == 0.f) - { - return LLVector3::zero; - } - - // point which ray hits plane centered on sphere origin, facing ray origin - LLVector3 intersection_sphere_plane = ray_pt + (ray_dir * center_distance / dot); - // vector from sphere origin to the point, normalized to sphere radius - LLVector3 sphere_center_to_intersection = (intersection_sphere_plane - sphere_center) / sphere_radius; - - F32 dist_squared = sphere_center_to_intersection.magVecSquared(); - LLVector3 result; - - if (dist_squared > 1.f) - { - result = sphere_center_to_intersection; - result.normVec(); - } - else - { - result = sphere_center_to_intersection - ray_dir * sqrt(1.f - dist_squared); - } - - return result; -} - -// Utility function. Should probably be moved to another class. -// x,y - mouse position in scaled window coordinates (NOT GL viewport coordinates) -//static -void LLManipRotate::mouseToRay( S32 x, S32 y, LLVector3* ray_pt, LLVector3* ray_dir ) -{ - if (LLSelectMgr::getInstance()->getSelection()->getSelectType() == SELECT_TYPE_HUD) - { - F32 mouse_x = (((F32)x / gViewerWindow->getWorldViewRectScaled().getWidth()) - 0.5f) / gAgentCamera.mHUDCurZoom; - F32 mouse_y = ((((F32)y) / gViewerWindow->getWorldViewRectScaled().getHeight()) - 0.5f) / gAgentCamera.mHUDCurZoom; - - *ray_pt = LLVector3(-1.f, -mouse_x, mouse_y); - *ray_dir = LLVector3(1.f, 0.f, 0.f); - } - else - { - *ray_pt = gAgentCamera.getCameraPositionAgent(); - *ray_dir = gViewerWindow->mouseDirectionGlobal(x, y); - } -} - -void LLManipRotate::highlightManipulators( S32 x, S32 y ) -{ - mHighlightedPart = LL_NO_PART; - - //LLBBox bbox = LLSelectMgr::getInstance()->getBBoxOfSelection(); - LLViewerObject *first_object = mObjectSelection->getFirstMoveableObject(true); - - if (!first_object) - { - return; - } - - LLVector3 rotation_center = gAgent.getPosAgentFromGlobal(mRotationCenter); - LLVector3 mouse_dir_x; - LLVector3 mouse_dir_y; - LLVector3 mouse_dir_z; - LLVector3 intersection_roll; - - LLVector3 grid_origin; - LLVector3 grid_scale; - LLQuaternion grid_rotation; - - LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale); - - LLVector3 rot_x_axis = LLVector3::x_axis * grid_rotation; - LLVector3 rot_y_axis = LLVector3::y_axis * grid_rotation; - LLVector3 rot_z_axis = LLVector3::z_axis * grid_rotation; - - F32 proj_rot_x_axis = llabs(rot_x_axis * mCenterToCamNorm); - F32 proj_rot_y_axis = llabs(rot_y_axis * mCenterToCamNorm); - F32 proj_rot_z_axis = llabs(rot_z_axis * mCenterToCamNorm); - - F32 min_select_distance = 0.f; - F32 cur_select_distance = 0.f; - - // test x - getMousePointOnPlaneAgent(mouse_dir_x, x, y, rotation_center, rot_x_axis); - mouse_dir_x -= rotation_center; - // push intersection point out when working at obtuse angle to make ring easier to hit - mouse_dir_x *= 1.f + (1.f - llabs(rot_x_axis * mCenterToCamNorm)) * 0.1f; - - // test y - getMousePointOnPlaneAgent(mouse_dir_y, x, y, rotation_center, rot_y_axis); - mouse_dir_y -= rotation_center; - mouse_dir_y *= 1.f + (1.f - llabs(rot_y_axis * mCenterToCamNorm)) * 0.1f; - - // test z - getMousePointOnPlaneAgent(mouse_dir_z, x, y, rotation_center, rot_z_axis); - mouse_dir_z -= rotation_center; - mouse_dir_z *= 1.f + (1.f - llabs(rot_z_axis * mCenterToCamNorm)) * 0.1f; - - // test roll - getMousePointOnPlaneAgent(intersection_roll, x, y, rotation_center, mCenterToCamNorm); - intersection_roll -= rotation_center; - - F32 dist_x = mouse_dir_x.normVec(); - F32 dist_y = mouse_dir_y.normVec(); - F32 dist_z = mouse_dir_z.normVec(); - - F32 distance_threshold = (MAX_MANIP_SELECT_DISTANCE * mRadiusMeters) / gViewerWindow->getWorldViewHeightScaled(); - - if (llabs(dist_x - mRadiusMeters) * llmax(0.05f, proj_rot_x_axis) < distance_threshold) - { - // selected x - cur_select_distance = dist_x * mouse_dir_x * mCenterToCamNorm; - if (cur_select_distance >= -0.05f && (min_select_distance == 0.f || cur_select_distance > min_select_distance)) - { - min_select_distance = cur_select_distance; - mHighlightedPart = LL_ROT_X; - } - } - if (llabs(dist_y - mRadiusMeters) * llmax(0.05f, proj_rot_y_axis) < distance_threshold) - { - // selected y - cur_select_distance = dist_y * mouse_dir_y * mCenterToCamNorm; - if (cur_select_distance >= -0.05f && (min_select_distance == 0.f || cur_select_distance > min_select_distance)) - { - min_select_distance = cur_select_distance; - mHighlightedPart = LL_ROT_Y; - } - } - if (llabs(dist_z - mRadiusMeters) * llmax(0.05f, proj_rot_z_axis) < distance_threshold) - { - // selected z - cur_select_distance = dist_z * mouse_dir_z * mCenterToCamNorm; - if (cur_select_distance >= -0.05f && (min_select_distance == 0.f || cur_select_distance > min_select_distance)) - { - min_select_distance = cur_select_distance; - mHighlightedPart = LL_ROT_Z; - } - } - - // test for edge-on intersections - if (proj_rot_x_axis < 0.05f) - { - if ((proj_rot_y_axis > 0.05f && (dist_y * llabs(mouse_dir_y * rot_x_axis) < distance_threshold) && dist_y < mRadiusMeters) || - (proj_rot_z_axis > 0.05f && (dist_z * llabs(mouse_dir_z * rot_x_axis) < distance_threshold) && dist_z < mRadiusMeters)) - { - mHighlightedPart = LL_ROT_X; - } - } - - if (proj_rot_y_axis < 0.05f) - { - if ((proj_rot_x_axis > 0.05f && (dist_x * llabs(mouse_dir_x * rot_y_axis) < distance_threshold) && dist_x < mRadiusMeters) || - (proj_rot_z_axis > 0.05f && (dist_z * llabs(mouse_dir_z * rot_y_axis) < distance_threshold) && dist_z < mRadiusMeters)) - { - mHighlightedPart = LL_ROT_Y; - } - } - - if (proj_rot_z_axis < 0.05f) - { - if ((proj_rot_x_axis > 0.05f && (dist_x * llabs(mouse_dir_x * rot_z_axis) < distance_threshold) && dist_x < mRadiusMeters) || - (proj_rot_y_axis > 0.05f && (dist_y * llabs(mouse_dir_y * rot_z_axis) < distance_threshold) && dist_y < mRadiusMeters)) - { - mHighlightedPart = LL_ROT_Z; - } - } - - // test for roll - if (mHighlightedPart == LL_NO_PART) - { - F32 roll_distance = intersection_roll.magVec(); - F32 width_meters = WIDTH_PIXELS * mRadiusMeters / RADIUS_PIXELS; - - // use larger distance threshold for roll as it is checked only if something else wasn't highlighted - if (llabs(roll_distance - (mRadiusMeters + (width_meters * 2.f))) < distance_threshold * 2.f) - { - mHighlightedPart = LL_ROT_ROLL; - } - else if (roll_distance < mRadiusMeters) - { - mHighlightedPart = LL_ROT_GENERAL; - } - } -} - -S32 LLManipRotate::getObjectAxisClosestToMouse(LLVector3& object_axis) -{ - LLSelectNode* first_object_node = mObjectSelection->getFirstMoveableNode(true); - - if (!first_object_node) - { - object_axis.clearVec(); - return -1; - } - - LLQuaternion obj_rotation = first_object_node->mSavedRotation; - LLVector3 mouse_down_object = mMouseDown * ~obj_rotation; - LLVector3 mouse_down_abs = mouse_down_object; - mouse_down_abs.abs(); - - S32 axis_index = 0; - if (mouse_down_abs.mV[VX] > mouse_down_abs.mV[VY] && mouse_down_abs.mV[VX] > mouse_down_abs.mV[VZ]) - { - if (mouse_down_object.mV[VX] > 0.f) - { - object_axis = LLVector3::x_axis; - } - else - { - object_axis = LLVector3::x_axis_neg; - } - axis_index = VX; - } - else if (mouse_down_abs.mV[VY] > mouse_down_abs.mV[VZ]) - { - if (mouse_down_object.mV[VY] > 0.f) - { - object_axis = LLVector3::y_axis; - } - else - { - object_axis = LLVector3::y_axis_neg; - } - axis_index = VY; - } - else - { - if (mouse_down_object.mV[VZ] > 0.f) - { - object_axis = LLVector3::z_axis; - } - else - { - object_axis = LLVector3::z_axis_neg; - } - axis_index = VZ; - } - - return axis_index; -} - -//virtual -bool LLManipRotate::canAffectSelection() -{ - bool can_rotate = mObjectSelection->getObjectCount() != 0; - if (can_rotate) - { - struct f : public LLSelectedObjectFunctor - { - virtual bool apply(LLViewerObject* objectp) - { - LLViewerObject *root_object = (objectp == NULL) ? NULL : objectp->getRootEdit(); - return objectp->permMove() && !objectp->isPermanentEnforced() && - ((root_object == NULL) || !root_object->isPermanentEnforced()) && - (objectp->permModify() || !gSavedSettings.getBOOL("EditLinkedParts")); - } - } func; - can_rotate = mObjectSelection->applyToObjects(&func); - } - return can_rotate; -} - +/** + * @file llmaniprotate.cpp + * @brief LLManipRotate class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llmaniprotate.h" + +// library includes +#include "llmath.h" +#include "llgl.h" +#include "llrender.h" +#include "v4color.h" +#include "llprimitive.h" +#include "llview.h" +#include "llfontgl.h" + +// viewer includes +#include "llagent.h" +#include "llagentcamera.h" +#include "llbox.h" +#include "llbutton.h" +#include "llviewercontrol.h" +#include "llcriticaldamp.h" +#include "lltooltip.h" +#include "llfloatertools.h" +#include "llselectmgr.h" +#include "llstatusbar.h" +#include "llui.h" +#include "llvoavatar.h" +#include "llviewercamera.h" +#include "llviewerobject.h" +#include "llviewerobject.h" +#include "llviewershadermgr.h" +#include "llviewerwindow.h" +#include "llworld.h" +#include "pipeline.h" +#include "lldrawable.h" +#include "llglheaders.h" +#include "lltrans.h" +#include "llvoavatarself.h" +#include "llhudrender.h" + +const F32 RADIUS_PIXELS = 100.f; // size in screen space +const F32 SQ_RADIUS = RADIUS_PIXELS * RADIUS_PIXELS; +const F32 WIDTH_PIXELS = 8; +const S32 CIRCLE_STEPS = 100; +const F32 MAX_MANIP_SELECT_DISTANCE = 100.f; +const F32 SNAP_ANGLE_INCREMENT = 5.625f; +const F32 SNAP_ANGLE_DETENTE = SNAP_ANGLE_INCREMENT; +const F32 SNAP_GUIDE_RADIUS_1 = 2.8f; +const F32 SNAP_GUIDE_RADIUS_2 = 2.4f; +const F32 SNAP_GUIDE_RADIUS_3 = 2.2f; +const F32 SNAP_GUIDE_RADIUS_4 = 2.1f; +const F32 SNAP_GUIDE_RADIUS_5 = 2.05f; +const F32 SNAP_GUIDE_INNER_RADIUS = 2.f; +const F32 AXIS_ONTO_CAM_TOLERANCE = cos( 80.f * DEG_TO_RAD ); +const F32 SELECTED_MANIPULATOR_SCALE = 1.05f; +const F32 MANIPULATOR_SCALE_HALF_LIFE = 0.07f; + +extern void handle_reset_rotation(void*); // in LLViewerWindow + +LLManipRotate::LLManipRotate( LLToolComposite* composite ) +: LLManip( std::string("Rotate"), composite ), + mRotationCenter(), + mCenterScreen(), + mRotation(), + mMouseDown(), + mMouseCur(), + mRadiusMeters(0.f), + mCenterToCam(), + mCenterToCamNorm(), + mCenterToCamMag(0.f), + mCenterToProfilePlane(), + mCenterToProfilePlaneMag(0.f), + mSendUpdateOnMouseUp( false ), + mSmoothRotate( false ), + mCamEdgeOn(false), + mManipulatorScales(1.f, 1.f, 1.f, 1.f) +{ } + +void LLManipRotate::handleSelect() +{ + // *FIX: put this in mouseDown? + LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); + if (gFloaterTools) + { + gFloaterTools->setStatusText("rotate"); + } + LLManip::handleSelect(); +} + +void LLManipRotate::render() +{ + LLGLSUIDefault gls_ui; + gGL.getTexUnit(0)->bind(LLViewerFetchedTexture::sWhiteImagep); + LLGLDepthTest gls_depth(GL_TRUE); + LLGLEnable gl_blend(GL_BLEND); + + // You can rotate if you can move + LLViewerObject* first_object = mObjectSelection->getFirstMoveableObject(true); + if( !first_object ) + { + return; + } + + if( !updateVisiblity() ) + { + return; + } + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) + { + F32 zoom = gAgentCamera.mHUDCurZoom; + gGL.scalef(zoom, zoom, zoom); + } + + + LLVector3 center = gAgent.getPosAgentFromGlobal( mRotationCenter ); + + LLColor4 highlight_outside( 1.f, 1.f, 0.f, 1.f ); + LLColor4 highlight_inside( 0.7f, 0.7f, 0.f, 0.5f ); + F32 width_meters = WIDTH_PIXELS * mRadiusMeters / RADIUS_PIXELS; + + gGL.pushMatrix(); + { + + // are we in the middle of a constrained drag? + if (mManipPart >= LL_ROT_X && mManipPart <= LL_ROT_Z) + { + renderSnapGuides(); + } + else + { + gDebugProgram.bind(); + + LLGLEnable cull_face(GL_CULL_FACE); + LLGLDepthTest gls_depth(GL_FALSE); + gGL.pushMatrix(); + { + // Draw "sphere" (intersection of sphere with tangent cone that has apex at camera) + gGL.translatef( mCenterToProfilePlane.mV[VX], mCenterToProfilePlane.mV[VY], mCenterToProfilePlane.mV[VZ] ); + gGL.translatef( center.mV[VX], center.mV[VY], center.mV[VZ] ); + + // Inverse change of basis vectors + LLVector3 forward = mCenterToCamNorm; + LLVector3 left = gAgent.getUpAxis() % forward; + left.normVec(); + LLVector3 up = forward % left; + + LLVector4 a(-forward); + a.mV[3] = 0; + LLVector4 b(up); + b.mV[3] = 0; + LLVector4 c(left); + c.mV[3] = 0; + LLMatrix4 mat; + mat.initRows(a, b, c, LLVector4(0.f, 0.f, 0.f, 1.f)); + + gGL.multMatrix( &mat.mMatrix[0][0] ); + + gGL.rotatef( -90, 0.f, 1.f, 0.f); + LLColor4 color; + if (mManipPart == LL_ROT_ROLL || mHighlightedPart == LL_ROT_ROLL) + { + color.setVec(0.8f, 0.8f, 0.8f, 0.8f); + gGL.scalef(mManipulatorScales.mV[VW], mManipulatorScales.mV[VW], mManipulatorScales.mV[VW]); + } + else + { + color.setVec( 0.7f, 0.7f, 0.7f, 0.6f ); + } + gGL.diffuseColor4fv(color.mV); + gl_washer_2d(mRadiusMeters + width_meters, mRadiusMeters, CIRCLE_STEPS, color, color); + + + if (mManipPart == LL_NO_PART) + { + gGL.color4f( 0.7f, 0.7f, 0.7f, 0.3f ); + gGL.diffuseColor4f(0.7f, 0.7f, 0.7f, 0.3f); + gl_circle_2d( 0, 0, mRadiusMeters, CIRCLE_STEPS, true ); + } + + gGL.flush(); + } + gGL.popMatrix(); + + gUIProgram.bind(); + } + + gGL.translatef( center.mV[VX], center.mV[VY], center.mV[VZ] ); + + LLQuaternion rot; + F32 angle_radians, x, y, z; + + LLVector3 grid_origin; + LLVector3 grid_scale; + LLQuaternion grid_rotation; + + LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale); + + grid_rotation.getAngleAxis(&angle_radians, &x, &y, &z); + gGL.rotatef(angle_radians * RAD_TO_DEG, x, y, z); + + + gDebugProgram.bind(); + + if (mManipPart == LL_ROT_Z) + { + mManipulatorScales = lerp(mManipulatorScales, LLVector4(1.f, 1.f, SELECTED_MANIPULATOR_SCALE, 1.f), LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE)); + gGL.pushMatrix(); + { + // selected part + gGL.scalef(mManipulatorScales.mV[VZ], mManipulatorScales.mV[VZ], mManipulatorScales.mV[VZ]); + renderActiveRing( mRadiusMeters, width_meters, LLColor4( 0.f, 0.f, 1.f, 1.f) , LLColor4( 0.f, 0.f, 1.f, 0.3f )); + } + gGL.popMatrix(); + } + else if (mManipPart == LL_ROT_Y) + { + mManipulatorScales = lerp(mManipulatorScales, LLVector4(1.f, SELECTED_MANIPULATOR_SCALE, 1.f, 1.f), LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE)); + gGL.pushMatrix(); + { + gGL.rotatef( 90.f, 1.f, 0.f, 0.f ); + gGL.scalef(mManipulatorScales.mV[VY], mManipulatorScales.mV[VY], mManipulatorScales.mV[VY]); + renderActiveRing( mRadiusMeters, width_meters, LLColor4( 0.f, 1.f, 0.f, 1.f), LLColor4( 0.f, 1.f, 0.f, 0.3f)); + } + gGL.popMatrix(); + } + else if (mManipPart == LL_ROT_X) + { + mManipulatorScales = lerp(mManipulatorScales, LLVector4(SELECTED_MANIPULATOR_SCALE, 1.f, 1.f, 1.f), LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE)); + gGL.pushMatrix(); + { + gGL.rotatef( 90.f, 0.f, 1.f, 0.f ); + gGL.scalef(mManipulatorScales.mV[VX], mManipulatorScales.mV[VX], mManipulatorScales.mV[VX]); + renderActiveRing( mRadiusMeters, width_meters, LLColor4( 1.f, 0.f, 0.f, 1.f), LLColor4( 1.f, 0.f, 0.f, 0.3f)); + } + gGL.popMatrix(); + } + else if (mManipPart == LL_ROT_ROLL) + { + mManipulatorScales = lerp(mManipulatorScales, LLVector4(1.f, 1.f, 1.f, SELECTED_MANIPULATOR_SCALE), LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE)); + } + else if (mManipPart == LL_NO_PART) + { + if (mHighlightedPart == LL_NO_PART) + { + mManipulatorScales = lerp(mManipulatorScales, LLVector4(1.f, 1.f, 1.f, 1.f), LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE)); + } + + LLGLEnable cull_face(GL_CULL_FACE); + LLGLEnable clip_plane0(GL_CLIP_PLANE0); + LLGLDepthTest gls_depth(GL_FALSE); + //LLGLDisable gls_stencil(GL_STENCIL_TEST); + + // First pass: centers. Second pass: sides. + for( S32 i=0; i<2; i++ ) + { + + gGL.pushMatrix(); + { + if (mHighlightedPart == LL_ROT_Z) + { + mManipulatorScales = lerp(mManipulatorScales, LLVector4(1.f, 1.f, SELECTED_MANIPULATOR_SCALE, 1.f), LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE)); + gGL.scalef(mManipulatorScales.mV[VZ], mManipulatorScales.mV[VZ], mManipulatorScales.mV[VZ]); + // hovering over part + gl_ring( mRadiusMeters, width_meters, LLColor4( 0.f, 0.f, 1.f, 1.f ), LLColor4( 0.f, 0.f, 1.f, 0.5f ), CIRCLE_STEPS, i); + } + else + { + // default + gl_ring( mRadiusMeters, width_meters, LLColor4( 0.f, 0.f, 0.8f, 0.8f ), LLColor4( 0.f, 0.f, 0.8f, 0.4f ), CIRCLE_STEPS, i); + } + } + gGL.popMatrix(); + + gGL.pushMatrix(); + { + gGL.rotatef( 90.f, 1.f, 0.f, 0.f ); + if (mHighlightedPart == LL_ROT_Y) + { + mManipulatorScales = lerp(mManipulatorScales, LLVector4(1.f, SELECTED_MANIPULATOR_SCALE, 1.f, 1.f), LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE)); + gGL.scalef(mManipulatorScales.mV[VY], mManipulatorScales.mV[VY], mManipulatorScales.mV[VY]); + // hovering over part + gl_ring( mRadiusMeters, width_meters, LLColor4( 0.f, 1.f, 0.f, 1.f ), LLColor4( 0.f, 1.f, 0.f, 0.5f ), CIRCLE_STEPS, i); + } + else + { + // default + gl_ring( mRadiusMeters, width_meters, LLColor4( 0.f, 0.8f, 0.f, 0.8f ), LLColor4( 0.f, 0.8f, 0.f, 0.4f ), CIRCLE_STEPS, i); + } + } + gGL.popMatrix(); + + gGL.pushMatrix(); + { + gGL.rotatef( 90.f, 0.f, 1.f, 0.f ); + if (mHighlightedPart == LL_ROT_X) + { + mManipulatorScales = lerp(mManipulatorScales, LLVector4(SELECTED_MANIPULATOR_SCALE, 1.f, 1.f, 1.f), LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE)); + gGL.scalef(mManipulatorScales.mV[VX], mManipulatorScales.mV[VX], mManipulatorScales.mV[VX]); + + // hovering over part + gl_ring( mRadiusMeters, width_meters, LLColor4( 1.f, 0.f, 0.f, 1.f ), LLColor4( 1.f, 0.f, 0.f, 0.5f ), CIRCLE_STEPS, i); + } + else + { + // default + gl_ring( mRadiusMeters, width_meters, LLColor4( 0.8f, 0.f, 0.f, 0.8f ), LLColor4( 0.8f, 0.f, 0.f, 0.4f ), CIRCLE_STEPS, i); + } + } + gGL.popMatrix(); + + if (mHighlightedPart == LL_ROT_ROLL) + { + mManipulatorScales = lerp(mManipulatorScales, LLVector4(1.f, 1.f, 1.f, SELECTED_MANIPULATOR_SCALE), LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE)); + } + + } + + } + + gUIProgram.bind(); + } + gGL.popMatrix(); + gGL.popMatrix(); + + + LLVector3 euler_angles; + LLQuaternion object_rot = first_object->getRotationEdit(); + object_rot.getEulerAngles(&(euler_angles.mV[VX]), &(euler_angles.mV[VY]), &(euler_angles.mV[VZ])); + euler_angles *= RAD_TO_DEG; + euler_angles.mV[VX] = ll_round(fmodf(euler_angles.mV[VX] + 360.f, 360.f), 0.05f); + euler_angles.mV[VY] = ll_round(fmodf(euler_angles.mV[VY] + 360.f, 360.f), 0.05f); + euler_angles.mV[VZ] = ll_round(fmodf(euler_angles.mV[VZ] + 360.f, 360.f), 0.05f); + + renderXYZ(euler_angles); +} + +bool LLManipRotate::handleMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + LLViewerObject* first_object = mObjectSelection->getFirstMoveableObject(true); + if( first_object ) + { + if( mHighlightedPart != LL_NO_PART ) + { + handled = handleMouseDownOnPart( x, y, mask ); + } + } + + return handled; +} + +// Assumes that one of the parts of the manipulator was hit. +bool LLManipRotate::handleMouseDownOnPart( S32 x, S32 y, MASK mask ) +{ + bool can_rotate = canAffectSelection(); + if (!can_rotate) + { + return false; + } + + highlightManipulators(x, y); + S32 hit_part = mHighlightedPart; + // we just started a drag, so save initial object positions + LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_ROTATE); + + // save selection center + mRotationCenter = gAgent.getPosGlobalFromAgent( getPivotPoint() ); //LLSelectMgr::getInstance()->getSelectionCenterGlobal(); + + mManipPart = (EManipPart)hit_part; + LLVector3 center = gAgent.getPosAgentFromGlobal( mRotationCenter ); + + if( mManipPart == LL_ROT_GENERAL) + { + mMouseDown = intersectMouseWithSphere( x, y, center, mRadiusMeters); + } + else + { + // Project onto the plane of the ring + LLVector3 axis = getConstraintAxis(); + + F32 axis_onto_cam = llabs( axis * mCenterToCamNorm ); + const F32 AXIS_ONTO_CAM_TOL = cos( 85.f * DEG_TO_RAD ); + if( axis_onto_cam < AXIS_ONTO_CAM_TOL ) + { + LLVector3 up_from_axis = mCenterToCamNorm % axis; + up_from_axis.normVec(); + LLVector3 cur_intersection; + getMousePointOnPlaneAgent(cur_intersection, x, y, center, mCenterToCam); + cur_intersection -= center; + mMouseDown = projected_vec(cur_intersection, up_from_axis); + F32 mouse_depth = SNAP_GUIDE_INNER_RADIUS * mRadiusMeters; + F32 mouse_dist_sqrd = mMouseDown.magVecSquared(); + if (mouse_dist_sqrd > 0.0001f) + { + mouse_depth = sqrtf((SNAP_GUIDE_INNER_RADIUS * mRadiusMeters) * (SNAP_GUIDE_INNER_RADIUS * mRadiusMeters) - + mouse_dist_sqrd); + } + LLVector3 projected_center_to_cam = mCenterToCamNorm - projected_vec(mCenterToCamNorm, axis); + mMouseDown += mouse_depth * projected_center_to_cam; + + } + else + { + mMouseDown = findNearestPointOnRing( x, y, center, axis ) - center; + mMouseDown.normVec(); + } + } + + mMouseCur = mMouseDown; + mAgentSelfAtAxis = gAgent.getAtAxis(); // no point checking if avatar was selected, just save the value + + // Route future Mouse messages here preemptively. (Release on mouse up.) + setMouseCapture( true ); + LLSelectMgr::getInstance()->enableSilhouette(false); + + mHelpTextTimer.reset(); + sNumTimesHelpTextShown++; + return true; +} + + +LLVector3 LLManipRotate::findNearestPointOnRing( S32 x, S32 y, const LLVector3& center, const LLVector3& axis ) +{ + // Project the delta onto the ring and rescale it by the radius so that it's _on_ the ring. + LLVector3 proj_onto_ring; + getMousePointOnPlaneAgent(proj_onto_ring, x, y, center, axis); + proj_onto_ring -= center; + proj_onto_ring.normVec(); + + return center + proj_onto_ring * mRadiusMeters; +} + +bool LLManipRotate::handleMouseUp(S32 x, S32 y, MASK mask) +{ + // first, perform normal processing in case this was a quick-click + handleHover(x, y, mask); + + if( hasMouseCapture() ) + { + for (LLObjectSelection::iterator iter = mObjectSelection->begin(); + iter != mObjectSelection->end(); iter++) + { + LLSelectNode* selectNode = *iter; + LLViewerObject* object = selectNode->getObject(); + LLViewerObject* root_object = (object == NULL) ? NULL : object->getRootEdit(); + + // have permission to move and object is root of selection or individually selected + if (object->permMove() && !object->isPermanentEnforced() && + ((root_object == NULL) || !root_object->isPermanentEnforced()) && + (object->isRootEdit() || selectNode->mIndividualSelection)) + { + object->mUnselectedChildrenPositions.clear() ; + } + } + + mManipPart = LL_NO_PART; + + // Might have missed last update due to timing. + LLSelectMgr::getInstance()->sendMultipleUpdate( UPD_ROTATION | UPD_POSITION ); + LLSelectMgr::getInstance()->enableSilhouette(true); + //gAgent.setObjectTracking(gSavedSettings.getBOOL("TrackFocusObject")); + + LLSelectMgr::getInstance()->updateSelectionCenter(); + LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); + } + + return LLManip::handleMouseUp(x, y, mask); +} + + +bool LLManipRotate::handleHover(S32 x, S32 y, MASK mask) +{ + if( hasMouseCapture() ) + { + if( mObjectSelection->isEmpty() ) + { + // Somehow the object got deselected while we were dragging it. + setMouseCapture( false ); + } + else + { + drag(x, y); + } + + LL_DEBUGS("UserInput") << "hover handled by LLManipRotate (active)" << LL_ENDL; + } + else + { + highlightManipulators(x, y); + LL_DEBUGS("UserInput") << "hover handled by LLManipRotate (inactive)" << LL_ENDL; + } + + gViewerWindow->setCursor(UI_CURSOR_TOOLROTATE); + return true; +} + + +LLVector3 LLManipRotate::projectToSphere( F32 x, F32 y, bool* on_sphere ) +{ + F32 z = 0.f; + F32 dist_squared = x*x + y*y; + + *on_sphere = dist_squared <= SQ_RADIUS; + if( *on_sphere ) + { + z = sqrt(SQ_RADIUS - dist_squared); + } + return LLVector3( x, y, z ); +} + +// Freeform rotation +void LLManipRotate::drag( S32 x, S32 y ) +{ + if( !updateVisiblity() ) + { + return; + } + + if( mManipPart == LL_ROT_GENERAL ) + { + mRotation = dragUnconstrained(x, y); + } + else + { + mRotation = dragConstrained(x, y); + } + + bool damped = mSmoothRotate; + mSmoothRotate = false; + + for (LLObjectSelection::iterator iter = mObjectSelection->begin(); + iter != mObjectSelection->end(); iter++) + { + LLSelectNode* selectNode = *iter; + LLViewerObject* object = selectNode->getObject(); + LLViewerObject* root_object = (object == NULL) ? NULL : object->getRootEdit(); + + // have permission to move and object is root of selection or individually selected + if (object->permMove() && !object->isPermanentEnforced() && + ((root_object == NULL) || !root_object->isPermanentEnforced()) && + (object->isRootEdit() || selectNode->mIndividualSelection)) + { + if (!object->isRootEdit()) + { + // child objects should not update if parent is selected + LLViewerObject* editable_root = (LLViewerObject*)object->getParent(); + if (editable_root->isSelected()) + { + // we will be moved properly by our parent, so skip + continue; + } + } + + LLQuaternion new_rot = selectNode->mSavedRotation * mRotation; + std::vector& child_positions = object->mUnselectedChildrenPositions ; + std::vector child_rotations; + if (object->isRootEdit() && selectNode->mIndividualSelection) + { + object->saveUnselectedChildrenRotation(child_rotations) ; + object->saveUnselectedChildrenPosition(child_positions) ; + } + + if (object->getParent() && object->mDrawable.notNull()) + { + LLQuaternion invParentRotation = object->mDrawable->mXform.getParent()->getWorldRotation(); + invParentRotation.transQuat(); + + object->setRotation(new_rot * invParentRotation, damped); + rebuild(object); + } + else + { + object->setRotation(new_rot, damped); + LLVOAvatar* avatar = object->asAvatar(); + if (avatar && avatar->isSelf() + && LLSelectMgr::getInstance()->mAllowSelectAvatar + && !object->getParent()) + { + // Normal avatars use object's orienttion, but self uses + // separate LLCoordFrame + // See LVOAvatar::updateOrientation() + if (gAgentCamera.getFocusOnAvatar()) + { + //Don't rotate camera with avatar + gAgentCamera.setFocusOnAvatar(false, false, false); + } + + LLVector3 at_axis = mAgentSelfAtAxis; + at_axis *= mRotation; + at_axis.mV[VZ] = 0.f; + at_axis.normalize(); + gAgent.resetAxes(at_axis); + } + rebuild(object); + } + + // for individually selected roots, we need to counterrotate all the children + if (object->isRootEdit() && selectNode->mIndividualSelection) + { + //RN: must do non-damped updates on these objects so relative rotation appears constant + // instead of having two competing slerps making the child objects appear to "wobble" + object->resetChildrenRotationAndPosition(child_rotations, child_positions) ; + } + } + } + + // update positions + for (LLObjectSelection::iterator iter = mObjectSelection->begin(); + iter != mObjectSelection->end(); iter++) + { + LLSelectNode* selectNode = *iter; + LLViewerObject* object = selectNode->getObject(); + LLViewerObject* root_object = (object == NULL) ? NULL : object->getRootEdit(); + + // to avoid cumulative position changes we calculate the objects new position using its saved position + if (object && object->permMove() && !object->isPermanentEnforced() && + ((root_object == NULL) || !root_object->isPermanentEnforced())) + { + LLVector3 center = gAgent.getPosAgentFromGlobal( mRotationCenter ); + + LLVector3 old_position; + LLVector3 new_position; + + if (object->isAttachment() && object->mDrawable.notNull()) + { + // need to work in drawable space to handle selected items from multiple attachments + // (which have no shared frame of reference other than their render positions) + LLXform* parent_xform = object->mDrawable->getXform()->getParent(); + new_position = (selectNode->mSavedPositionLocal * parent_xform->getWorldRotation()) + parent_xform->getWorldPosition(); + old_position = (object->getPosition() * parent_xform->getWorldRotation()) + parent_xform->getWorldPosition();//object->getRenderPosition(); + } + else + { + new_position = gAgent.getPosAgentFromGlobal( selectNode->mSavedPositionGlobal ); + old_position = object->getPositionAgent(); + } + + new_position = (new_position - center) * mRotation; // new relative rotated position + new_position += center; + + if (object->isRootEdit() && !object->isAttachment()) + { + LLVector3d new_pos_global = gAgent.getPosGlobalFromAgent(new_position); + new_pos_global = LLWorld::getInstance()->clipToVisibleRegions(selectNode->mSavedPositionGlobal, new_pos_global); + new_position = gAgent.getPosAgentFromGlobal(new_pos_global); + } + + // for individually selected child objects + if (!object->isRootEdit() && selectNode->mIndividualSelection) + { + LLViewerObject* parentp = (LLViewerObject*)object->getParent(); + if (!parentp->isSelected()) + { + if (object->isAttachment() && object->mDrawable.notNull()) + { + // find position relative to render position of parent + object->setPosition((new_position - parentp->getRenderPosition()) * ~parentp->getRenderRotation()); + rebuild(object); + } + else + { + object->setPositionParent((new_position - parentp->getPositionAgent()) * ~parentp->getRotationRegion()); + rebuild(object); + } + } + } + else if (object->isRootEdit()) + { + if (object->isAttachment() && object->mDrawable.notNull()) + { + LLXform* parent_xform = object->mDrawable->getXform()->getParent(); + object->setPosition((new_position - parent_xform->getWorldPosition()) * ~parent_xform->getWorldRotation()); + rebuild(object); + } + else + { + object->setPositionAgent(new_position); + rebuild(object); + } + } + + // for individually selected roots, we need to counter-translate all unselected children + if (object->isRootEdit() && selectNode->mIndividualSelection) + { + // only offset by parent's translation as we've already countered parent's rotation + rebuild(object); + object->resetChildrenPosition(old_position - new_position) ; + } + } + } + + // store changes to override updates + for (LLObjectSelection::iterator iter = LLSelectMgr::getInstance()->getSelection()->begin(); + iter != LLSelectMgr::getInstance()->getSelection()->end(); iter++) + { + LLSelectNode* selectNode = *iter; + LLViewerObject*cur = selectNode->getObject(); + LLViewerObject *root_object = (cur == NULL) ? NULL : cur->getRootEdit(); + if( cur->permModify() && cur->permMove() && !cur->isPermanentEnforced() && + ((root_object == NULL) || !root_object->isPermanentEnforced()) && + (!cur->isAvatar() || LLSelectMgr::getInstance()->mAllowSelectAvatar)) + { + selectNode->mLastRotation = cur->getRotation(); + selectNode->mLastPositionLocal = cur->getPosition(); + } + } + + LLSelectMgr::getInstance()->updateSelectionCenter(); + + // RN: just clear focus so camera doesn't follow spurious object updates + gAgentCamera.clearFocusObject(); + dialog_refresh_all(); +} + +void LLManipRotate::renderActiveRing( F32 radius, F32 width, const LLColor4& front_color, const LLColor4& back_color) +{ + LLGLEnable cull_face(GL_CULL_FACE); + { + gl_ring(radius, width, back_color, back_color * 0.5f, CIRCLE_STEPS, false); + gl_ring(radius, width, back_color, back_color * 0.5f, CIRCLE_STEPS, true); + } + { + LLGLDepthTest gls_depth(GL_FALSE); + gl_ring(radius, width, front_color, front_color * 0.5f, CIRCLE_STEPS, false); + gl_ring(radius, width, front_color, front_color * 0.5f, CIRCLE_STEPS, true); + } +} + +void LLManipRotate::renderSnapGuides() +{ + static LLCachedControl snap_enabled(gSavedSettings, "SnapEnabled", true); + if (!snap_enabled) + { + return; + } + + LLVector3 grid_origin; + LLVector3 grid_scale; + LLQuaternion grid_rotation; + + LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale, true); + + LLVector3 constraint_axis = getConstraintAxis(); + + LLVector3 center = gAgent.getPosAgentFromGlobal( mRotationCenter ); + LLVector3 cam_at_axis; + if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) + { + cam_at_axis.setVec(1.f, 0.f, 0.f); + } + else + { + cam_at_axis = center - gAgentCamera.getCameraPositionAgent(); + cam_at_axis.normVec(); + } + + LLVector3 world_snap_axis; + LLVector3 test_axis = constraint_axis; + + bool constrain_to_ref_object = false; + if (mObjectSelection->getSelectType() == SELECT_TYPE_ATTACHMENT && isAgentAvatarValid()) + { + test_axis = test_axis * ~grid_rotation; + } + else if (LLSelectMgr::getInstance()->getGridMode() == GRID_MODE_REF_OBJECT) + { + test_axis = test_axis * ~grid_rotation; + constrain_to_ref_object = true; + } + + test_axis.abs(); + + // find closest global/reference axis to local constraint axis; + if (test_axis.mV[VX] > test_axis.mV[VY] && test_axis.mV[VX] > test_axis.mV[VZ]) + { + world_snap_axis = LLVector3::y_axis; + } + else if (test_axis.mV[VY] > test_axis.mV[VZ]) + { + world_snap_axis = LLVector3::z_axis; + } + else + { + world_snap_axis = LLVector3::x_axis; + } + + LLVector3 projected_snap_axis = world_snap_axis; + if (mObjectSelection->getSelectType() == SELECT_TYPE_ATTACHMENT && isAgentAvatarValid()) + { + projected_snap_axis = projected_snap_axis * grid_rotation; + } + else if (constrain_to_ref_object) + { + projected_snap_axis = projected_snap_axis * grid_rotation; + } + + // project world snap axis onto constraint plane + projected_snap_axis -= projected_vec(projected_snap_axis, constraint_axis); + projected_snap_axis.normVec(); + + S32 num_rings = mCamEdgeOn ? 2 : 1; + for (S32 ring_num = 0; ring_num < num_rings; ring_num++) + { + LLVector3 center = gAgent.getPosAgentFromGlobal( mRotationCenter ); + + if (mCamEdgeOn) + { + // draw two opposing rings + if (ring_num == 0) + { + center += constraint_axis * mRadiusMeters * 0.5f; + } + else + { + center -= constraint_axis * mRadiusMeters * 0.5f; + } + } + + LLGLDepthTest gls_depth(GL_FALSE); + for (S32 pass = 0; pass < 3; pass++) + { + // render snap guide ring + gGL.pushMatrix(); + + LLQuaternion snap_guide_rot; + F32 angle_radians, x, y, z; + snap_guide_rot.shortestArc(LLVector3::z_axis, getConstraintAxis()); + snap_guide_rot.getAngleAxis(&angle_radians, &x, &y, &z); + gGL.translatef(center.mV[VX], center.mV[VY], center.mV[VZ]); + gGL.rotatef(angle_radians * RAD_TO_DEG, x, y, z); + + LLColor4 line_color = setupSnapGuideRenderPass(pass); + + gGL.color4fv(line_color.mV); + + if (mCamEdgeOn) + { + // render an arc + LLVector3 edge_normal = cam_at_axis % constraint_axis; + edge_normal.normVec(); + LLVector3 x_axis_snap = LLVector3::x_axis * snap_guide_rot; + LLVector3 y_axis_snap = LLVector3::y_axis * snap_guide_rot; + + F32 end_angle = atan2(y_axis_snap * edge_normal, x_axis_snap * edge_normal); + //F32 start_angle = angle_between((-1.f * LLVector3::x_axis) * snap_guide_rot, edge_normal); + F32 start_angle = end_angle - F_PI; + gl_arc_2d(0.f, 0.f, mRadiusMeters * SNAP_GUIDE_INNER_RADIUS, CIRCLE_STEPS, false, start_angle, end_angle); + } + else + { + gl_circle_2d(0.f, 0.f, mRadiusMeters * SNAP_GUIDE_INNER_RADIUS, CIRCLE_STEPS, false); + } + gGL.popMatrix(); + + for (S32 i = 0; i < 64; i++) + { + bool render_text = true; + F32 deg = 5.625f * (F32)i; + LLVector3 inner_point; + LLVector3 outer_point; + LLVector3 text_point; + LLQuaternion rot(deg * DEG_TO_RAD, constraint_axis); + gGL.begin(LLRender::LINES); + { + inner_point = (projected_snap_axis * mRadiusMeters * SNAP_GUIDE_INNER_RADIUS * rot) + center; + F32 tick_length = 0.f; + if (i % 16 == 0) + { + tick_length = mRadiusMeters * (SNAP_GUIDE_RADIUS_1 - SNAP_GUIDE_INNER_RADIUS); + } + else if (i % 8 == 0) + { + tick_length = mRadiusMeters * (SNAP_GUIDE_RADIUS_2 - SNAP_GUIDE_INNER_RADIUS); + } + else if (i % 4 == 0) + { + tick_length = mRadiusMeters * (SNAP_GUIDE_RADIUS_3 - SNAP_GUIDE_INNER_RADIUS); + } + else if (i % 2 == 0) + { + tick_length = mRadiusMeters * (SNAP_GUIDE_RADIUS_4 - SNAP_GUIDE_INNER_RADIUS); + } + else + { + tick_length = mRadiusMeters * (SNAP_GUIDE_RADIUS_5 - SNAP_GUIDE_INNER_RADIUS); + } + + if (mCamEdgeOn) + { + // don't draw ticks that are on back side of circle + F32 dot = cam_at_axis * (projected_snap_axis * rot); + if (dot > 0.f) + { + outer_point = inner_point; + render_text = false; + } + else + { + if (ring_num == 0) + { + outer_point = inner_point + (constraint_axis * tick_length) * rot; + } + else + { + outer_point = inner_point - (constraint_axis * tick_length) * rot; + } + } + } + else + { + outer_point = inner_point + (projected_snap_axis * tick_length) * rot; + } + + text_point = outer_point + (projected_snap_axis * mRadiusMeters * 0.1f) * rot; + + gGL.vertex3fv(inner_point.mV); + gGL.vertex3fv(outer_point.mV); + } + gGL.end(); + + //RN: text rendering does own shadow pass, so only render once + if (pass == 1 && render_text && i % 16 == 0) + { + if (world_snap_axis.mV[VX]) + { + if (i == 0) + { + renderTickText(text_point, mObjectSelection->isAttachment() ? LLTrans::getString("Direction_Forward") : LLTrans::getString("Direction_East"), LLColor4::white); + } + else if (i == 16) + { + if (constraint_axis.mV[VZ] > 0.f) + { + renderTickText(text_point, mObjectSelection->isAttachment() ? LLTrans::getString("Direction_Left") : LLTrans::getString("Direction_North"), LLColor4::white); + } + else + { + renderTickText(text_point, mObjectSelection->isAttachment() ? LLTrans::getString("Direction_Right") : LLTrans::getString("Direction_South"), LLColor4::white); + } + } + else if (i == 32) + { + renderTickText(text_point, mObjectSelection->isAttachment() ? LLTrans::getString("Direction_Back") : LLTrans::getString("Direction_West"), LLColor4::white); + } + else + { + if (constraint_axis.mV[VZ] > 0.f) + { + renderTickText(text_point, mObjectSelection->isAttachment() ? LLTrans::getString("Direction_Right") : LLTrans::getString("Direction_South"), LLColor4::white); + } + else + { + renderTickText(text_point, mObjectSelection->isAttachment() ? LLTrans::getString("Direction_Left") : LLTrans::getString("Direction_North"), LLColor4::white); + } + } + } + else if (world_snap_axis.mV[VY]) + { + if (i == 0) + { + renderTickText(text_point, mObjectSelection->isAttachment() ? LLTrans::getString("Direction_Left") : LLTrans::getString("Direction_North"), LLColor4::white); + } + else if (i == 16) + { + if (constraint_axis.mV[VX] > 0.f) + { + renderTickText(text_point, LLTrans::getString("Direction_Up"), LLColor4::white); + } + else + { + renderTickText(text_point, LLTrans::getString("Direction_Down"), LLColor4::white); + } + } + else if (i == 32) + { + renderTickText(text_point, mObjectSelection->isAttachment() ? LLTrans::getString("Direction_Right") : LLTrans::getString("Direction_South"), LLColor4::white); + } + else + { + if (constraint_axis.mV[VX] > 0.f) + { + renderTickText(text_point, LLTrans::getString("Direction_Down"), LLColor4::white); + } + else + { + renderTickText(text_point, LLTrans::getString("Direction_Up"), LLColor4::white); + } + } + } + else if (world_snap_axis.mV[VZ]) + { + if (i == 0) + { + renderTickText(text_point, LLTrans::getString("Direction_Up"), LLColor4::white); + } + else if (i == 16) + { + if (constraint_axis.mV[VY] > 0.f) + { + renderTickText(text_point, mObjectSelection->isAttachment() ? LLTrans::getString("Direction_Forward") : LLTrans::getString("Direction_East"), LLColor4::white); + } + else + { + renderTickText(text_point, mObjectSelection->isAttachment() ? LLTrans::getString("Direction_Back") : LLTrans::getString("Direction_West"), LLColor4::white); + } + } + else if (i == 32) + { + renderTickText(text_point, LLTrans::getString("Direction_Down"), LLColor4::white); + } + else + { + if (constraint_axis.mV[VY] > 0.f) + { + renderTickText(text_point, mObjectSelection->isAttachment() ? LLTrans::getString("Direction_Back") : LLTrans::getString("Direction_West"), LLColor4::white); + } + else + { + renderTickText(text_point, mObjectSelection->isAttachment() ? LLTrans::getString("Direction_Forward") : LLTrans::getString("Direction_East"), LLColor4::white); + } + } + } + } + gGL.color4fv(line_color.mV); + } + + // now render projected object axis + if (mInSnapRegime) + { + LLVector3 object_axis; + getObjectAxisClosestToMouse(object_axis); + + // project onto constraint plane + LLSelectNode* first_node = mObjectSelection->getFirstMoveableNode(true); + object_axis = object_axis * first_node->getObject()->getRenderRotation(); + object_axis = object_axis - (object_axis * getConstraintAxis()) * getConstraintAxis(); + object_axis.normVec(); + object_axis = object_axis * SNAP_GUIDE_INNER_RADIUS * mRadiusMeters + center; + LLVector3 line_start = center; + + gGL.begin(LLRender::LINES); + { + gGL.vertex3fv(line_start.mV); + gGL.vertex3fv(object_axis.mV); + } + gGL.end(); + + // draw snap guide arrow + gGL.begin(LLRender::TRIANGLES); + { + LLVector3 arrow_dir; + LLVector3 arrow_span = (object_axis - line_start) % getConstraintAxis(); + arrow_span.normVec(); + + arrow_dir = mCamEdgeOn ? getConstraintAxis() : object_axis - line_start; + arrow_dir.normVec(); + if (ring_num == 1) + { + arrow_dir *= -1.f; + } + gGL.vertex3fv((object_axis + arrow_dir * mRadiusMeters * 0.1f).mV); + gGL.vertex3fv((object_axis + arrow_span * mRadiusMeters * 0.1f).mV); + gGL.vertex3fv((object_axis - arrow_span * mRadiusMeters * 0.1f).mV); + } + gGL.end(); + + { + LLGLDepthTest gls_depth(GL_TRUE); + gGL.begin(LLRender::LINES); + { + gGL.vertex3fv(line_start.mV); + gGL.vertex3fv(object_axis.mV); + } + gGL.end(); + + // draw snap guide arrow + gGL.begin(LLRender::TRIANGLES); + { + LLVector3 arrow_dir; + LLVector3 arrow_span = (object_axis - line_start) % getConstraintAxis(); + arrow_span.normVec(); + + arrow_dir = mCamEdgeOn ? getConstraintAxis() : object_axis - line_start; + arrow_dir.normVec(); + if (ring_num == 1) + { + arrow_dir *= -1.f; + } + + gGL.vertex3fv((object_axis + arrow_dir * mRadiusMeters * 0.1f).mV); + gGL.vertex3fv((object_axis + arrow_span * mRadiusMeters * 0.1f).mV); + gGL.vertex3fv((object_axis - arrow_span * mRadiusMeters * 0.1f).mV); + } + gGL.end(); + } + } + } + } + + + // render help text + if (mObjectSelection->getSelectType() != SELECT_TYPE_HUD) + { + if (mHelpTextTimer.getElapsedTimeF32() < sHelpTextVisibleTime + sHelpTextFadeTime && sNumTimesHelpTextShown < sMaxTimesShowHelpText) + { + LLVector3 selection_center_start = LLSelectMgr::getInstance()->getSavedBBoxOfSelection().getCenterAgent(); + + LLVector3 offset_dir = LLViewerCamera::getInstance()->getUpAxis(); + + F32 line_alpha = gSavedSettings.getF32("GridOpacity"); + + LLVector3 help_text_pos = selection_center_start + (mRadiusMeters * 3.f * offset_dir); + const LLFontGL* big_fontp = LLFontGL::getFontSansSerif(); + + std::string help_text = LLTrans::getString("manip_hint1"); + LLColor4 help_text_color = LLColor4::white; + help_text_color.mV[VALPHA] = clamp_rescale(mHelpTextTimer.getElapsedTimeF32(), sHelpTextVisibleTime, sHelpTextVisibleTime + sHelpTextFadeTime, line_alpha, 0.f); + hud_render_utf8text(help_text, help_text_pos, *big_fontp, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, -0.5f * big_fontp->getWidthF32(help_text), 3.f, help_text_color, false); + help_text = LLTrans::getString("manip_hint2"); + help_text_pos -= offset_dir * mRadiusMeters * 0.4f; + hud_render_utf8text(help_text, help_text_pos, *big_fontp, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, -0.5f * big_fontp->getWidthF32(help_text), 3.f, help_text_color, false); + } + } +} + +// Returns true if center of sphere is visible. Also sets a bunch of member variables that are used later (e.g. mCenterToCam) +bool LLManipRotate::updateVisiblity() +{ + // Don't want to recalculate the center of the selection during a drag. + // Due to packet delays, sometimes half the objects in the selection have their + // new position and half have their old one. This creates subtle errors in the + // computed center position for that frame. Unfortunately, these errors + // accumulate. The result is objects seem to "fly apart" during rotations. + // JC - 03.26.2002 + if (!hasMouseCapture()) + { + mRotationCenter = gAgent.getPosGlobalFromAgent( getPivotPoint() );//LLSelectMgr::getInstance()->getSelectionCenterGlobal(); + } + + bool visible = false; + + //Assume that UI scale factor is equivalent for X and Y axis + F32 ui_scale_factor = LLUI::getScaleFactor().mV[VX]; + + LLVector3 center = gAgent.getPosAgentFromGlobal( mRotationCenter ); + if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) + { + mCenterToCam = LLVector3(-1.f / gAgentCamera.mHUDCurZoom, 0.f, 0.f); + mCenterToCamNorm = mCenterToCam; + mCenterToCamMag = mCenterToCamNorm.normVec(); + + mRadiusMeters = RADIUS_PIXELS / (F32) LLViewerCamera::getInstance()->getViewHeightInPixels(); + mRadiusMeters /= gAgentCamera.mHUDCurZoom; + mRadiusMeters *= ui_scale_factor; + + mCenterToProfilePlaneMag = mRadiusMeters * mRadiusMeters / mCenterToCamMag; + mCenterToProfilePlane = -mCenterToProfilePlaneMag * mCenterToCamNorm; + + // x axis range is (-aspect * 0.5f, +aspect * 0.5) + // y axis range is (-0.5, 0.5) + // so use getWorldViewHeightRaw as scale factor when converting to pixel coordinates + mCenterScreen.set((S32)((0.5f - center.mV[VY]) / gAgentCamera.mHUDCurZoom * gViewerWindow->getWorldViewHeightScaled()), + (S32)((center.mV[VZ] + 0.5f) / gAgentCamera.mHUDCurZoom * gViewerWindow->getWorldViewHeightScaled())); + visible = true; + } + else + { + visible = LLViewerCamera::getInstance()->projectPosAgentToScreen(center, mCenterScreen ); + if( visible ) + { + mCenterToCam = gAgentCamera.getCameraPositionAgent() - center; + mCenterToCamNorm = mCenterToCam; + mCenterToCamMag = mCenterToCamNorm.normVec(); + LLVector3 cameraAtAxis = LLViewerCamera::getInstance()->getAtAxis(); + cameraAtAxis.normVec(); + + F32 z_dist = -1.f * (mCenterToCam * cameraAtAxis); + + // Don't drag manip if object too far away + if (gSavedSettings.getBOOL("LimitSelectDistance")) + { + F32 max_select_distance = gSavedSettings.getF32("MaxSelectDistance"); + if (dist_vec_squared(gAgent.getPositionAgent(), center) > (max_select_distance * max_select_distance)) + { + visible = false; + } + } + + if (mCenterToCamMag > 0.001f) + { + F32 fraction_of_fov = RADIUS_PIXELS / (F32) LLViewerCamera::getInstance()->getViewHeightInPixels(); + F32 apparent_angle = fraction_of_fov * LLViewerCamera::getInstance()->getView(); // radians + mRadiusMeters = z_dist * tan(apparent_angle); + mRadiusMeters *= ui_scale_factor; + + mCenterToProfilePlaneMag = mRadiusMeters * mRadiusMeters / mCenterToCamMag; + mCenterToProfilePlane = -mCenterToProfilePlaneMag * mCenterToCamNorm; + } + else + { + visible = false; + } + } + } + + mCamEdgeOn = false; + F32 axis_onto_cam = mManipPart >= LL_ROT_X ? llabs( getConstraintAxis() * mCenterToCamNorm ) : 0.f; + if( axis_onto_cam < AXIS_ONTO_CAM_TOLERANCE ) + { + mCamEdgeOn = true; + } + + return visible; +} + +LLQuaternion LLManipRotate::dragUnconstrained( S32 x, S32 y ) +{ + LLVector3 cam = gAgentCamera.getCameraPositionAgent(); + LLVector3 center = gAgent.getPosAgentFromGlobal( mRotationCenter ); + + mMouseCur = intersectMouseWithSphere( x, y, center, mRadiusMeters); + + F32 delta_x = (F32)(mCenterScreen.mX - x); + F32 delta_y = (F32)(mCenterScreen.mY - y); + + F32 dist_from_sphere_center = sqrt(delta_x * delta_x + delta_y * delta_y); + + LLVector3 axis = mMouseDown % mMouseCur; + F32 angle = atan2(sqrtf(axis * axis), mMouseDown * mMouseCur); + axis.normVec(); + LLQuaternion sphere_rot( angle, axis ); + + if (is_approx_zero(1.f - mMouseDown * mMouseCur)) + { + return LLQuaternion::DEFAULT; + } + else if (dist_from_sphere_center < RADIUS_PIXELS) + { + return sphere_rot; + } + else + { + LLVector3 intersection; + getMousePointOnPlaneAgent( intersection, x, y, center + mCenterToProfilePlane, mCenterToCamNorm ); + + // amount dragging in sphere from center to periphery would rotate object + F32 in_sphere_angle = F_PI_BY_TWO; + F32 dist_to_tangent_point = mRadiusMeters; + if( !is_approx_zero( mCenterToProfilePlaneMag ) ) + { + dist_to_tangent_point = sqrt( mRadiusMeters * mRadiusMeters - mCenterToProfilePlaneMag * mCenterToProfilePlaneMag ); + in_sphere_angle = atan2( dist_to_tangent_point, mCenterToProfilePlaneMag ); + } + + LLVector3 profile_center_to_intersection = intersection - (center + mCenterToProfilePlane); + F32 dist_to_intersection = profile_center_to_intersection.normVec(); + F32 angle = (-1.f + dist_to_intersection / dist_to_tangent_point) * in_sphere_angle; + + LLVector3 axis; + if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) + { + axis = LLVector3(-1.f, 0.f, 0.f) % profile_center_to_intersection; + } + else + { + axis = (cam - center) % profile_center_to_intersection; + axis.normVec(); + } + return sphere_rot * LLQuaternion( angle, axis ); + } +} + +LLVector3 LLManipRotate::getConstraintAxis() +{ + LLVector3 axis; + if( LL_ROT_ROLL == mManipPart ) + { + axis = mCenterToCamNorm; + } + else + { + S32 axis_dir = mManipPart - LL_ROT_X; + if ((axis_dir >= LL_NO_PART) && (axis_dir < LL_Z_ARROW)) + { + axis.mV[axis_dir] = 1.f; + } + else + { +#ifndef LL_RELEASE_FOR_DOWNLOAD + LL_ERRS() << "Got bogus hit part in LLManipRotate::getConstraintAxis():" << mManipPart << LL_ENDL; +#else + LL_WARNS() << "Got bogus hit part in LLManipRotate::getConstraintAxis():" << mManipPart << LL_ENDL; +#endif + axis.mV[0] = 1.f; + } + + LLVector3 grid_origin; + LLVector3 grid_scale; + LLQuaternion grid_rotation; + + LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale); + + LLSelectNode* first_node = mObjectSelection->getFirstMoveableNode(true); + if (first_node) + { + // *FIX: get agent local attachment grid working + // Put rotation into frame of first selected root object + axis = axis * grid_rotation; + } + } + + return axis; +} + +LLQuaternion LLManipRotate::dragConstrained( S32 x, S32 y ) +{ + LLSelectNode* first_object_node = mObjectSelection->getFirstMoveableNode(true); + LLVector3 constraint_axis = getConstraintAxis(); + LLVector3 center = gAgent.getPosAgentFromGlobal( mRotationCenter ); + + F32 angle = 0.f; + + // build snap axes + LLVector3 grid_origin; + LLVector3 grid_scale; + LLQuaternion grid_rotation; + + LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale); + + LLVector3 axis1; + LLVector3 axis2; + + LLVector3 test_axis = constraint_axis; + if (mObjectSelection->getSelectType() == SELECT_TYPE_ATTACHMENT && isAgentAvatarValid()) + { + test_axis = test_axis * ~grid_rotation; + } + else if (LLSelectMgr::getInstance()->getGridMode() == GRID_MODE_REF_OBJECT) + { + test_axis = test_axis * ~grid_rotation; + } + test_axis.abs(); + + // find closest global axis to constraint axis; + if (test_axis.mV[VX] > test_axis.mV[VY] && test_axis.mV[VX] > test_axis.mV[VZ]) + { + axis1 = LLVector3::y_axis; + } + else if (test_axis.mV[VY] > test_axis.mV[VZ]) + { + axis1 = LLVector3::z_axis; + } + else + { + axis1 = LLVector3::x_axis; + } + + if (mObjectSelection->getSelectType() == SELECT_TYPE_ATTACHMENT && isAgentAvatarValid()) + { + axis1 = axis1 * grid_rotation; + } + else if (LLSelectMgr::getInstance()->getGridMode() == GRID_MODE_REF_OBJECT) + { + axis1 = axis1 * grid_rotation; + } + + //project axis onto constraint plane + axis1 -= (axis1 * constraint_axis) * constraint_axis; + axis1.normVec(); + + // calculate third and final axis + axis2 = constraint_axis % axis1; + + //F32 axis_onto_cam = llabs( constraint_axis * mCenterToCamNorm ); + if( mCamEdgeOn ) + { + // We're looking at the ring edge-on. + LLVector3 snap_plane_center = (center + (constraint_axis * mRadiusMeters * 0.5f)); + LLVector3 cam_to_snap_plane; + if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) + { + cam_to_snap_plane.setVec(1.f, 0.f, 0.f); + } + else + { + cam_to_snap_plane = snap_plane_center - gAgentCamera.getCameraPositionAgent(); + cam_to_snap_plane.normVec(); + } + + LLVector3 projected_mouse; + bool hit = getMousePointOnPlaneAgent(projected_mouse, x, y, snap_plane_center, constraint_axis); + projected_mouse -= snap_plane_center; + + if (gSavedSettings.getBOOL("SnapEnabled")) { + S32 snap_plane = 0; + + F32 dot = cam_to_snap_plane * constraint_axis; + if (llabs(dot) < 0.01f) + { + // looking at ring edge on, project onto view plane and check if mouse is past ring + getMousePointOnPlaneAgent(projected_mouse, x, y, snap_plane_center, cam_to_snap_plane); + projected_mouse -= snap_plane_center; + dot = projected_mouse * constraint_axis; + if (projected_mouse * constraint_axis > 0) + { + snap_plane = 1; + } + projected_mouse -= dot * constraint_axis; + } + else if (dot > 0.f) + { + // look for mouse position outside and in front of snap circle + if (hit && projected_mouse.magVec() > SNAP_GUIDE_INNER_RADIUS * mRadiusMeters && projected_mouse * cam_to_snap_plane < 0.f) + { + snap_plane = 1; + } + } + else + { + // look for mouse position inside or in back of snap circle + if (projected_mouse.magVec() < SNAP_GUIDE_INNER_RADIUS * mRadiusMeters || projected_mouse * cam_to_snap_plane > 0.f || !hit) + { + snap_plane = 1; + } + } + + if (snap_plane == 0) + { + // try other plane + snap_plane_center = (center - (constraint_axis * mRadiusMeters * 0.5f)); + if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) + { + cam_to_snap_plane.setVec(1.f, 0.f, 0.f); + } + else + { + cam_to_snap_plane = snap_plane_center - gAgentCamera.getCameraPositionAgent(); + cam_to_snap_plane.normVec(); + } + + hit = getMousePointOnPlaneAgent(projected_mouse, x, y, snap_plane_center, constraint_axis); + projected_mouse -= snap_plane_center; + + dot = cam_to_snap_plane * constraint_axis; + if (llabs(dot) < 0.01f) + { + // looking at ring edge on, project onto view plane and check if mouse is past ring + getMousePointOnPlaneAgent(projected_mouse, x, y, snap_plane_center, cam_to_snap_plane); + projected_mouse -= snap_plane_center; + dot = projected_mouse * constraint_axis; + if (projected_mouse * constraint_axis < 0) + { + snap_plane = 2; + } + projected_mouse -= dot * constraint_axis; + } + else if (dot < 0.f) + { + // look for mouse position outside and in front of snap circle + if (hit && projected_mouse.magVec() > SNAP_GUIDE_INNER_RADIUS * mRadiusMeters && projected_mouse * cam_to_snap_plane < 0.f) + { + snap_plane = 2; + } + } + else + { + // look for mouse position inside or in back of snap circle + if (projected_mouse.magVec() < SNAP_GUIDE_INNER_RADIUS * mRadiusMeters || projected_mouse * cam_to_snap_plane > 0.f || !hit) + { + snap_plane = 2; + } + } + } + + if (snap_plane > 0) + { + LLVector3 cam_at_axis; + if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) + { + cam_at_axis.setVec(1.f, 0.f, 0.f); + } + else + { + cam_at_axis = snap_plane_center - gAgentCamera.getCameraPositionAgent(); + cam_at_axis.normVec(); + } + + // first, project mouse onto screen plane at point tangent to rotation radius. + getMousePointOnPlaneAgent(projected_mouse, x, y, snap_plane_center, cam_at_axis); + // project that point onto rotation plane + projected_mouse -= snap_plane_center; + projected_mouse -= projected_vec(projected_mouse, constraint_axis); + + F32 mouse_lateral_dist = llmin(SNAP_GUIDE_INNER_RADIUS * mRadiusMeters, projected_mouse.magVec()); + F32 mouse_depth = SNAP_GUIDE_INNER_RADIUS * mRadiusMeters; + if (llabs(mouse_lateral_dist) > 0.01f) + { + mouse_depth = sqrtf((SNAP_GUIDE_INNER_RADIUS * mRadiusMeters) * (SNAP_GUIDE_INNER_RADIUS * mRadiusMeters) - + (mouse_lateral_dist * mouse_lateral_dist)); + } + LLVector3 projected_camera_at = cam_at_axis - projected_vec(cam_at_axis, constraint_axis); + projected_mouse -= mouse_depth * projected_camera_at; + + if (!mInSnapRegime) + { + mSmoothRotate = true; + } + mInSnapRegime = true; + // 0 to 360 deg + F32 mouse_angle = fmodf(atan2(projected_mouse * axis1, projected_mouse * axis2) * RAD_TO_DEG + 360.f, 360.f); + + F32 relative_mouse_angle = fmodf(mouse_angle + (SNAP_ANGLE_DETENTE / 2), SNAP_ANGLE_INCREMENT); + + LLVector3 object_axis; + getObjectAxisClosestToMouse(object_axis); + if (first_object_node) + { + object_axis = object_axis * first_object_node->mSavedRotation; + } + + // project onto constraint plane + object_axis = object_axis - (object_axis * getConstraintAxis()) * getConstraintAxis(); + object_axis.normVec(); + + if (relative_mouse_angle < SNAP_ANGLE_DETENTE) + { + F32 quantized_mouse_angle = mouse_angle - (relative_mouse_angle - (SNAP_ANGLE_DETENTE * 0.5f)); + angle = (quantized_mouse_angle * DEG_TO_RAD) - atan2(object_axis * axis1, object_axis * axis2); + } + else + { + angle = (mouse_angle * DEG_TO_RAD) - atan2(object_axis * axis1, object_axis * axis2); + } + return LLQuaternion( -angle, constraint_axis ); + } + else + { + if (mInSnapRegime) + { + mSmoothRotate = true; + } + mInSnapRegime = false; + } + } + else { + if (mInSnapRegime) + { + mSmoothRotate = true; + } + mInSnapRegime = false; + } + + if (!mInSnapRegime) + { + LLVector3 up_from_axis = mCenterToCamNorm % constraint_axis; + up_from_axis.normVec(); + LLVector3 cur_intersection; + getMousePointOnPlaneAgent(cur_intersection, x, y, center, mCenterToCam); + cur_intersection -= center; + mMouseCur = projected_vec(cur_intersection, up_from_axis); + F32 mouse_depth = SNAP_GUIDE_INNER_RADIUS * mRadiusMeters; + F32 mouse_dist_sqrd = mMouseCur.magVecSquared(); + if (mouse_dist_sqrd > 0.0001f) + { + mouse_depth = sqrtf((SNAP_GUIDE_INNER_RADIUS * mRadiusMeters) * (SNAP_GUIDE_INNER_RADIUS * mRadiusMeters) - + mouse_dist_sqrd); + } + LLVector3 projected_center_to_cam = mCenterToCamNorm - projected_vec(mCenterToCamNorm, constraint_axis); + mMouseCur += mouse_depth * projected_center_to_cam; + + F32 dist = (cur_intersection * up_from_axis) - (mMouseDown * up_from_axis); + angle = dist / (SNAP_GUIDE_INNER_RADIUS * mRadiusMeters) * -F_PI_BY_TWO; + } + } + else + { + LLVector3 projected_mouse; + getMousePointOnPlaneAgent(projected_mouse, x, y, center, constraint_axis); + projected_mouse -= center; + mMouseCur = projected_mouse; + mMouseCur.normVec(); + + if (!first_object_node) + { + return LLQuaternion::DEFAULT; + } + + if (gSavedSettings.getBOOL("SnapEnabled") && projected_mouse.magVec() > SNAP_GUIDE_INNER_RADIUS * mRadiusMeters) + { + if (!mInSnapRegime) + { + mSmoothRotate = true; + } + mInSnapRegime = true; + // 0 to 360 deg + F32 mouse_angle = fmodf(atan2(projected_mouse * axis1, projected_mouse * axis2) * RAD_TO_DEG + 360.f, 360.f); + + F32 relative_mouse_angle = fmodf(mouse_angle + (SNAP_ANGLE_DETENTE / 2), SNAP_ANGLE_INCREMENT); + + LLVector3 object_axis; + getObjectAxisClosestToMouse(object_axis); + object_axis = object_axis * first_object_node->mSavedRotation; + + // project onto constraint plane + object_axis = object_axis - (object_axis * getConstraintAxis()) * getConstraintAxis(); + object_axis.normVec(); + + if (relative_mouse_angle < SNAP_ANGLE_DETENTE) + { + F32 quantized_mouse_angle = mouse_angle - (relative_mouse_angle - (SNAP_ANGLE_DETENTE * 0.5f)); + angle = (quantized_mouse_angle * DEG_TO_RAD) - atan2(object_axis * axis1, object_axis * axis2); + } + else + { + angle = (mouse_angle * DEG_TO_RAD) - atan2(object_axis * axis1, object_axis * axis2); + } + return LLQuaternion( -angle, constraint_axis ); + } + else + { + if (mInSnapRegime) + { + mSmoothRotate = true; + } + mInSnapRegime = false; + } + + LLVector3 cross_product = mMouseDown % mMouseCur; + angle = atan2(sqrtf(cross_product * cross_product), mMouseCur * mMouseDown); + F32 dir = cross_product * constraint_axis; // cross product + if( dir < 0.f ) + { + angle *= -1.f; + } + } + + F32 rot_step = gSavedSettings.getF32("RotationStep"); + F32 step_size = DEG_TO_RAD * rot_step; + angle -= fmod(angle, step_size); + + return LLQuaternion( angle, constraint_axis ); +} + + + +LLVector3 LLManipRotate::intersectMouseWithSphere( S32 x, S32 y, const LLVector3& sphere_center, F32 sphere_radius) +{ + LLVector3 ray_pt; + LLVector3 ray_dir; + mouseToRay( x, y, &ray_pt, &ray_dir); + return intersectRayWithSphere( ray_pt, ray_dir, sphere_center, sphere_radius ); +} + +LLVector3 LLManipRotate::intersectRayWithSphere( const LLVector3& ray_pt, const LLVector3& ray_dir, const LLVector3& sphere_center, F32 sphere_radius) +{ + LLVector3 ray_pt_to_center = sphere_center - ray_pt; + F32 center_distance = ray_pt_to_center.normVec(); + + F32 dot = ray_dir * ray_pt_to_center; + + if (dot == 0.f) + { + return LLVector3::zero; + } + + // point which ray hits plane centered on sphere origin, facing ray origin + LLVector3 intersection_sphere_plane = ray_pt + (ray_dir * center_distance / dot); + // vector from sphere origin to the point, normalized to sphere radius + LLVector3 sphere_center_to_intersection = (intersection_sphere_plane - sphere_center) / sphere_radius; + + F32 dist_squared = sphere_center_to_intersection.magVecSquared(); + LLVector3 result; + + if (dist_squared > 1.f) + { + result = sphere_center_to_intersection; + result.normVec(); + } + else + { + result = sphere_center_to_intersection - ray_dir * sqrt(1.f - dist_squared); + } + + return result; +} + +// Utility function. Should probably be moved to another class. +// x,y - mouse position in scaled window coordinates (NOT GL viewport coordinates) +//static +void LLManipRotate::mouseToRay( S32 x, S32 y, LLVector3* ray_pt, LLVector3* ray_dir ) +{ + if (LLSelectMgr::getInstance()->getSelection()->getSelectType() == SELECT_TYPE_HUD) + { + F32 mouse_x = (((F32)x / gViewerWindow->getWorldViewRectScaled().getWidth()) - 0.5f) / gAgentCamera.mHUDCurZoom; + F32 mouse_y = ((((F32)y) / gViewerWindow->getWorldViewRectScaled().getHeight()) - 0.5f) / gAgentCamera.mHUDCurZoom; + + *ray_pt = LLVector3(-1.f, -mouse_x, mouse_y); + *ray_dir = LLVector3(1.f, 0.f, 0.f); + } + else + { + *ray_pt = gAgentCamera.getCameraPositionAgent(); + *ray_dir = gViewerWindow->mouseDirectionGlobal(x, y); + } +} + +void LLManipRotate::highlightManipulators( S32 x, S32 y ) +{ + mHighlightedPart = LL_NO_PART; + + //LLBBox bbox = LLSelectMgr::getInstance()->getBBoxOfSelection(); + LLViewerObject *first_object = mObjectSelection->getFirstMoveableObject(true); + + if (!first_object) + { + return; + } + + LLVector3 rotation_center = gAgent.getPosAgentFromGlobal(mRotationCenter); + LLVector3 mouse_dir_x; + LLVector3 mouse_dir_y; + LLVector3 mouse_dir_z; + LLVector3 intersection_roll; + + LLVector3 grid_origin; + LLVector3 grid_scale; + LLQuaternion grid_rotation; + + LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale); + + LLVector3 rot_x_axis = LLVector3::x_axis * grid_rotation; + LLVector3 rot_y_axis = LLVector3::y_axis * grid_rotation; + LLVector3 rot_z_axis = LLVector3::z_axis * grid_rotation; + + F32 proj_rot_x_axis = llabs(rot_x_axis * mCenterToCamNorm); + F32 proj_rot_y_axis = llabs(rot_y_axis * mCenterToCamNorm); + F32 proj_rot_z_axis = llabs(rot_z_axis * mCenterToCamNorm); + + F32 min_select_distance = 0.f; + F32 cur_select_distance = 0.f; + + // test x + getMousePointOnPlaneAgent(mouse_dir_x, x, y, rotation_center, rot_x_axis); + mouse_dir_x -= rotation_center; + // push intersection point out when working at obtuse angle to make ring easier to hit + mouse_dir_x *= 1.f + (1.f - llabs(rot_x_axis * mCenterToCamNorm)) * 0.1f; + + // test y + getMousePointOnPlaneAgent(mouse_dir_y, x, y, rotation_center, rot_y_axis); + mouse_dir_y -= rotation_center; + mouse_dir_y *= 1.f + (1.f - llabs(rot_y_axis * mCenterToCamNorm)) * 0.1f; + + // test z + getMousePointOnPlaneAgent(mouse_dir_z, x, y, rotation_center, rot_z_axis); + mouse_dir_z -= rotation_center; + mouse_dir_z *= 1.f + (1.f - llabs(rot_z_axis * mCenterToCamNorm)) * 0.1f; + + // test roll + getMousePointOnPlaneAgent(intersection_roll, x, y, rotation_center, mCenterToCamNorm); + intersection_roll -= rotation_center; + + F32 dist_x = mouse_dir_x.normVec(); + F32 dist_y = mouse_dir_y.normVec(); + F32 dist_z = mouse_dir_z.normVec(); + + F32 distance_threshold = (MAX_MANIP_SELECT_DISTANCE * mRadiusMeters) / gViewerWindow->getWorldViewHeightScaled(); + + if (llabs(dist_x - mRadiusMeters) * llmax(0.05f, proj_rot_x_axis) < distance_threshold) + { + // selected x + cur_select_distance = dist_x * mouse_dir_x * mCenterToCamNorm; + if (cur_select_distance >= -0.05f && (min_select_distance == 0.f || cur_select_distance > min_select_distance)) + { + min_select_distance = cur_select_distance; + mHighlightedPart = LL_ROT_X; + } + } + if (llabs(dist_y - mRadiusMeters) * llmax(0.05f, proj_rot_y_axis) < distance_threshold) + { + // selected y + cur_select_distance = dist_y * mouse_dir_y * mCenterToCamNorm; + if (cur_select_distance >= -0.05f && (min_select_distance == 0.f || cur_select_distance > min_select_distance)) + { + min_select_distance = cur_select_distance; + mHighlightedPart = LL_ROT_Y; + } + } + if (llabs(dist_z - mRadiusMeters) * llmax(0.05f, proj_rot_z_axis) < distance_threshold) + { + // selected z + cur_select_distance = dist_z * mouse_dir_z * mCenterToCamNorm; + if (cur_select_distance >= -0.05f && (min_select_distance == 0.f || cur_select_distance > min_select_distance)) + { + min_select_distance = cur_select_distance; + mHighlightedPart = LL_ROT_Z; + } + } + + // test for edge-on intersections + if (proj_rot_x_axis < 0.05f) + { + if ((proj_rot_y_axis > 0.05f && (dist_y * llabs(mouse_dir_y * rot_x_axis) < distance_threshold) && dist_y < mRadiusMeters) || + (proj_rot_z_axis > 0.05f && (dist_z * llabs(mouse_dir_z * rot_x_axis) < distance_threshold) && dist_z < mRadiusMeters)) + { + mHighlightedPart = LL_ROT_X; + } + } + + if (proj_rot_y_axis < 0.05f) + { + if ((proj_rot_x_axis > 0.05f && (dist_x * llabs(mouse_dir_x * rot_y_axis) < distance_threshold) && dist_x < mRadiusMeters) || + (proj_rot_z_axis > 0.05f && (dist_z * llabs(mouse_dir_z * rot_y_axis) < distance_threshold) && dist_z < mRadiusMeters)) + { + mHighlightedPart = LL_ROT_Y; + } + } + + if (proj_rot_z_axis < 0.05f) + { + if ((proj_rot_x_axis > 0.05f && (dist_x * llabs(mouse_dir_x * rot_z_axis) < distance_threshold) && dist_x < mRadiusMeters) || + (proj_rot_y_axis > 0.05f && (dist_y * llabs(mouse_dir_y * rot_z_axis) < distance_threshold) && dist_y < mRadiusMeters)) + { + mHighlightedPart = LL_ROT_Z; + } + } + + // test for roll + if (mHighlightedPart == LL_NO_PART) + { + F32 roll_distance = intersection_roll.magVec(); + F32 width_meters = WIDTH_PIXELS * mRadiusMeters / RADIUS_PIXELS; + + // use larger distance threshold for roll as it is checked only if something else wasn't highlighted + if (llabs(roll_distance - (mRadiusMeters + (width_meters * 2.f))) < distance_threshold * 2.f) + { + mHighlightedPart = LL_ROT_ROLL; + } + else if (roll_distance < mRadiusMeters) + { + mHighlightedPart = LL_ROT_GENERAL; + } + } +} + +S32 LLManipRotate::getObjectAxisClosestToMouse(LLVector3& object_axis) +{ + LLSelectNode* first_object_node = mObjectSelection->getFirstMoveableNode(true); + + if (!first_object_node) + { + object_axis.clearVec(); + return -1; + } + + LLQuaternion obj_rotation = first_object_node->mSavedRotation; + LLVector3 mouse_down_object = mMouseDown * ~obj_rotation; + LLVector3 mouse_down_abs = mouse_down_object; + mouse_down_abs.abs(); + + S32 axis_index = 0; + if (mouse_down_abs.mV[VX] > mouse_down_abs.mV[VY] && mouse_down_abs.mV[VX] > mouse_down_abs.mV[VZ]) + { + if (mouse_down_object.mV[VX] > 0.f) + { + object_axis = LLVector3::x_axis; + } + else + { + object_axis = LLVector3::x_axis_neg; + } + axis_index = VX; + } + else if (mouse_down_abs.mV[VY] > mouse_down_abs.mV[VZ]) + { + if (mouse_down_object.mV[VY] > 0.f) + { + object_axis = LLVector3::y_axis; + } + else + { + object_axis = LLVector3::y_axis_neg; + } + axis_index = VY; + } + else + { + if (mouse_down_object.mV[VZ] > 0.f) + { + object_axis = LLVector3::z_axis; + } + else + { + object_axis = LLVector3::z_axis_neg; + } + axis_index = VZ; + } + + return axis_index; +} + +//virtual +bool LLManipRotate::canAffectSelection() +{ + bool can_rotate = mObjectSelection->getObjectCount() != 0; + if (can_rotate) + { + struct f : public LLSelectedObjectFunctor + { + virtual bool apply(LLViewerObject* objectp) + { + LLViewerObject *root_object = (objectp == NULL) ? NULL : objectp->getRootEdit(); + return objectp->permMove() && !objectp->isPermanentEnforced() && + ((root_object == NULL) || !root_object->isPermanentEnforced()) && + (objectp->permModify() || !gSavedSettings.getBOOL("EditLinkedParts")); + } + } func; + can_rotate = mObjectSelection->applyToObjects(&func); + } + return can_rotate; +} + diff --git a/indra/newview/llmaniprotate.h b/indra/newview/llmaniprotate.h index 3f1361c88d..fa764a9ced 100644 --- a/indra/newview/llmaniprotate.h +++ b/indra/newview/llmaniprotate.h @@ -1,116 +1,116 @@ -/** - * @file llmaniprotate.h - * @brief LLManipRotate class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLMANIPROTATE_H -#define LL_LLMANIPROTATE_H - -#include "lltool.h" -#include "v3math.h" -#include "v4math.h" -#include "llquaternion.h" -#include "llregionposition.h" -#include "llmanip.h" -#include "llviewerobject.h" - -class LLToolComposite; -class LLColor4; - -class LLManipRotate : public LLManip -{ -public: - class ManipulatorHandle - { - public: - LLVector3 mAxisU; - LLVector3 mAxisV; - U32 mManipID; - - ManipulatorHandle(LLVector3 axis_u, LLVector3 axis_v, U32 id) : mAxisU(axis_u), mAxisV(axis_v), mManipID(id){} - }; - - LLManipRotate( LLToolComposite* composite ); - - virtual bool handleMouseDown( S32 x, S32 y, MASK mask ); - virtual bool handleMouseUp( S32 x, S32 y, MASK mask ); - virtual bool handleHover( S32 x, S32 y, MASK mask ); - virtual void render(); - - virtual void handleSelect(); - - virtual bool handleMouseDownOnPart(S32 x, S32 y, MASK mask); - virtual void highlightManipulators(S32 x, S32 y); - virtual bool canAffectSelection(); - -private: - void updateHoverView(); - - void drag( S32 x, S32 y ); - LLVector3 projectToSphere( F32 x, F32 y, bool* on_sphere ); - - void renderSnapGuides(); - void renderActiveRing(F32 radius, F32 width, const LLColor4& center_color, const LLColor4& side_color); - - bool updateVisiblity(); - LLVector3 findNearestPointOnRing( S32 x, S32 y, const LLVector3& center, const LLVector3& axis ); - - LLQuaternion dragUnconstrained( S32 x, S32 y ); - LLQuaternion dragConstrained( S32 x, S32 y ); - LLVector3 getConstraintAxis(); - S32 getObjectAxisClosestToMouse(LLVector3& axis); - - // Utility functions - static void mouseToRay( S32 x, S32 y, LLVector3* ray_pt, LLVector3* ray_dir ); - static LLVector3 intersectMouseWithSphere( S32 x, S32 y, const LLVector3& sphere_center, F32 sphere_radius ); - static LLVector3 intersectRayWithSphere( const LLVector3& ray_pt, const LLVector3& ray_dir, const LLVector3& sphere_center, F32 sphere_radius); - -private: - LLVector3d mRotationCenter; - LLCoordGL mCenterScreen; -// S32 mLastHoverMouseX; // used to suppress hover if mouse doesn't move -// S32 mLastHoverMouseY; - LLQuaternion mRotation; - - LLVector3 mMouseDown; - LLVector3 mMouseCur; - LLVector3 mAgentSelfAtAxis; // Own agent uses separate rotation method - F32 mRadiusMeters; - - LLVector3 mCenterToCam; - LLVector3 mCenterToCamNorm; - F32 mCenterToCamMag; - LLVector3 mCenterToProfilePlane; - F32 mCenterToProfilePlaneMag; - - bool mSendUpdateOnMouseUp; - - bool mSmoothRotate; - bool mCamEdgeOn; - - LLVector4 mManipulatorVertices[6]; - LLVector4 mManipulatorScales; -}; - -#endif // LL_LLMANIPROTATE_H +/** + * @file llmaniprotate.h + * @brief LLManipRotate class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLMANIPROTATE_H +#define LL_LLMANIPROTATE_H + +#include "lltool.h" +#include "v3math.h" +#include "v4math.h" +#include "llquaternion.h" +#include "llregionposition.h" +#include "llmanip.h" +#include "llviewerobject.h" + +class LLToolComposite; +class LLColor4; + +class LLManipRotate : public LLManip +{ +public: + class ManipulatorHandle + { + public: + LLVector3 mAxisU; + LLVector3 mAxisV; + U32 mManipID; + + ManipulatorHandle(LLVector3 axis_u, LLVector3 axis_v, U32 id) : mAxisU(axis_u), mAxisV(axis_v), mManipID(id){} + }; + + LLManipRotate( LLToolComposite* composite ); + + virtual bool handleMouseDown( S32 x, S32 y, MASK mask ); + virtual bool handleMouseUp( S32 x, S32 y, MASK mask ); + virtual bool handleHover( S32 x, S32 y, MASK mask ); + virtual void render(); + + virtual void handleSelect(); + + virtual bool handleMouseDownOnPart(S32 x, S32 y, MASK mask); + virtual void highlightManipulators(S32 x, S32 y); + virtual bool canAffectSelection(); + +private: + void updateHoverView(); + + void drag( S32 x, S32 y ); + LLVector3 projectToSphere( F32 x, F32 y, bool* on_sphere ); + + void renderSnapGuides(); + void renderActiveRing(F32 radius, F32 width, const LLColor4& center_color, const LLColor4& side_color); + + bool updateVisiblity(); + LLVector3 findNearestPointOnRing( S32 x, S32 y, const LLVector3& center, const LLVector3& axis ); + + LLQuaternion dragUnconstrained( S32 x, S32 y ); + LLQuaternion dragConstrained( S32 x, S32 y ); + LLVector3 getConstraintAxis(); + S32 getObjectAxisClosestToMouse(LLVector3& axis); + + // Utility functions + static void mouseToRay( S32 x, S32 y, LLVector3* ray_pt, LLVector3* ray_dir ); + static LLVector3 intersectMouseWithSphere( S32 x, S32 y, const LLVector3& sphere_center, F32 sphere_radius ); + static LLVector3 intersectRayWithSphere( const LLVector3& ray_pt, const LLVector3& ray_dir, const LLVector3& sphere_center, F32 sphere_radius); + +private: + LLVector3d mRotationCenter; + LLCoordGL mCenterScreen; +// S32 mLastHoverMouseX; // used to suppress hover if mouse doesn't move +// S32 mLastHoverMouseY; + LLQuaternion mRotation; + + LLVector3 mMouseDown; + LLVector3 mMouseCur; + LLVector3 mAgentSelfAtAxis; // Own agent uses separate rotation method + F32 mRadiusMeters; + + LLVector3 mCenterToCam; + LLVector3 mCenterToCamNorm; + F32 mCenterToCamMag; + LLVector3 mCenterToProfilePlane; + F32 mCenterToProfilePlaneMag; + + bool mSendUpdateOnMouseUp; + + bool mSmoothRotate; + bool mCamEdgeOn; + + LLVector4 mManipulatorVertices[6]; + LLVector4 mManipulatorScales; +}; + +#endif // LL_LLMANIPROTATE_H diff --git a/indra/newview/llmanipscale.cpp b/indra/newview/llmanipscale.cpp index fdd72bc4c3..c4f3f01ea1 100644 --- a/indra/newview/llmanipscale.cpp +++ b/indra/newview/llmanipscale.cpp @@ -1,2099 +1,2099 @@ -/** - * @file llmanipscale.cpp - * @brief LLManipScale class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llmanipscale.h" - -// library includes -#include "llmath.h" -#include "v3math.h" -#include "llquaternion.h" -#include "llgl.h" -#include "llrender.h" -#include "v4color.h" -#include "llprimitive.h" - -// viewer includes -#include "llagent.h" -#include "llagentcamera.h" -#include "llbbox.h" -#include "llbox.h" -#include "llviewercontrol.h" -#include "llcriticaldamp.h" -#include "lldrawable.h" -#include "llfloatertools.h" -#include "llglheaders.h" -#include "llselectmgr.h" -#include "llstatusbar.h" -#include "llui.h" -#include "llviewercamera.h" -#include "llviewerobject.h" -#include "llviewerregion.h" -#include "llviewerwindow.h" -#include "llhudrender.h" -#include "llworld.h" -#include "v2math.h" -#include "llvoavatar.h" -#include "llmeshrepository.h" -#include "lltrans.h" - -const F32 MAX_MANIP_SELECT_DISTANCE_SQUARED = 11.f * 11.f; -const F32 SNAP_GUIDE_SCREEN_OFFSET = 0.05f; -const F32 SNAP_GUIDE_SCREEN_LENGTH = 0.7f; -const F32 SELECTED_MANIPULATOR_SCALE = 1.2f; -const F32 MANIPULATOR_SCALE_HALF_LIFE = 0.07f; - -const LLManip::EManipPart MANIPULATOR_IDS[LLManipScale::NUM_MANIPULATORS] = -{ - LLManip::LL_CORNER_NNN, - LLManip::LL_CORNER_NNP, - LLManip::LL_CORNER_NPN, - LLManip::LL_CORNER_NPP, - LLManip::LL_CORNER_PNN, - LLManip::LL_CORNER_PNP, - LLManip::LL_CORNER_PPN, - LLManip::LL_CORNER_PPP, - LLManip::LL_FACE_POSZ, - LLManip::LL_FACE_POSX, - LLManip::LL_FACE_POSY, - LLManip::LL_FACE_NEGX, - LLManip::LL_FACE_NEGY, - LLManip::LL_FACE_NEGZ -}; - -F32 get_default_max_prim_scale(bool is_flora) -{ - // a bit of a hack, but if it's foilage, we don't want to use the - // new larger scale which would result in giant trees and grass - if (gMeshRepo.meshRezEnabled() && - !is_flora) - { - return DEFAULT_MAX_PRIM_SCALE; - } - else - { - return DEFAULT_MAX_PRIM_SCALE_NO_MESH; - } -} - -// static -void LLManipScale::setUniform(bool b) -{ - gSavedSettings.setBOOL("ScaleUniform", b); -} - -// static -void LLManipScale::setShowAxes(bool b) -{ - gSavedSettings.setBOOL("ScaleShowAxes", b); -} - -// static -void LLManipScale::setStretchTextures(bool b) -{ - gSavedSettings.setBOOL("ScaleStretchTextures", b); -} - -// static -bool LLManipScale::getUniform() -{ - return gSavedSettings.getBOOL("ScaleUniform"); -} - -// static -bool LLManipScale::getShowAxes() -{ - return gSavedSettings.getBOOL("ScaleShowAxes"); -} - -// static -bool LLManipScale::getStretchTextures() -{ - return gSavedSettings.getBOOL("ScaleStretchTextures"); -} - -inline void LLManipScale::conditionalHighlight( U32 part, const LLColor4* highlight, const LLColor4* normal ) -{ - LLColor4 default_highlight( 1.f, 1.f, 1.f, 1.f ); - LLColor4 default_normal( 0.7f, 0.7f, 0.7f, 0.6f ); - LLColor4 invisible(0.f, 0.f, 0.f, 0.f); - - for (S32 i = 0; i < NUM_MANIPULATORS; i++) - { - if((U32)MANIPULATOR_IDS[i] == part) - { - mScaledBoxHandleSize = mManipulatorScales[i] * mBoxHandleSize[i]; - break; - } - } - - if (mManipPart != (S32)LL_NO_PART && mManipPart != (S32)part) - { - gGL.color4fv( invisible.mV ); - } - else if( mHighlightedPart == (S32)part ) - { - gGL.color4fv( highlight ? highlight->mV : default_highlight.mV ); - } - else - { - gGL.color4fv( normal ? normal->mV : default_normal.mV ); - } -} - -void LLManipScale::handleSelect() -{ - LLBBox bbox = LLSelectMgr::getInstance()->getBBoxOfSelection(); - updateSnapGuides(bbox); - LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); - if (gFloaterTools) - { - gFloaterTools->setStatusText("scale"); - } - LLManip::handleSelect(); -} - -LLManipScale::LLManipScale( LLToolComposite* composite ) - : - LLManip( std::string("Scale"), composite ), - mScaledBoxHandleSize( 1.f ), - mLastMouseX( -1 ), - mLastMouseY( -1 ), - mSendUpdateOnMouseUp( false ), - mLastUpdateFlags( 0 ), - mScaleSnapUnit1(1.f), - mScaleSnapUnit2(1.f), - mSnapRegimeOffset(0.f), - mTickPixelSpacing1(0.f), - mTickPixelSpacing2(0.f), - mSnapGuideLength(0.f), - mSnapRegime(SNAP_REGIME_NONE), - mScaleSnappedValue(0.f) -{ - for (S32 i = 0; i < NUM_MANIPULATORS; i++) - { - mManipulatorScales[i] = 1.f; - mBoxHandleSize[i] = 1.f; - } -} - -LLManipScale::~LLManipScale() -{ - for_each(mProjectedManipulators.begin(), mProjectedManipulators.end(), DeletePointer()); -} - -void LLManipScale::render() -{ - LLGLSUIDefault gls_ui; - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - LLGLDepthTest gls_depth(GL_TRUE); - LLGLEnable gl_blend(GL_BLEND); - LLBBox bbox = LLSelectMgr::getInstance()->getBBoxOfSelection(); - - if( canAffectSelection() ) - { - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) - { - F32 zoom = gAgentCamera.mHUDCurZoom; - gGL.scalef(zoom, zoom, zoom); - } - - //////////////////////////////////////////////////////////////////////// - // Calculate size of drag handles - - const F32 BOX_HANDLE_BASE_SIZE = 50.0f; // box size in pixels = BOX_HANDLE_BASE_SIZE * BOX_HANDLE_BASE_FACTOR - const F32 BOX_HANDLE_BASE_FACTOR = 0.2f; - - //Assume that UI scale factor is equivalent for X and Y axis - F32 ui_scale_factor = LLUI::getScaleFactor().mV[VX]; - - if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) - { - for (S32 i = 0; i < NUM_MANIPULATORS; i++) - { - mBoxHandleSize[i] = BOX_HANDLE_BASE_SIZE * BOX_HANDLE_BASE_FACTOR / (F32) LLViewerCamera::getInstance()->getViewHeightInPixels(); - mBoxHandleSize[i] /= gAgentCamera.mHUDCurZoom; - mBoxHandleSize[i] *= ui_scale_factor; - } - } - else - { - for (S32 i = 0; i < NUM_MANIPULATORS; i++) - { - LLVector3 manipulator_pos = bbox.localToAgent(unitVectorToLocalBBoxExtent(partToUnitVector(MANIPULATOR_IDS[i]), bbox)); - F32 range_squared = dist_vec_squared(gAgentCamera.getCameraPositionAgent(), manipulator_pos); - F32 range_from_agent_squared = dist_vec_squared(gAgent.getPositionAgent(), manipulator_pos); - - // Don't draw manip if object too far away - if (gSavedSettings.getBOOL("LimitSelectDistance")) - { - F32 max_select_distance = gSavedSettings.getF32("MaxSelectDistance"); - if (range_from_agent_squared > max_select_distance * max_select_distance) - { - return; - } - } - - if (range_squared > 0.001f * 0.001f) - { - // range != zero - F32 fraction_of_fov = BOX_HANDLE_BASE_SIZE / (F32) LLViewerCamera::getInstance()->getViewHeightInPixels(); - F32 apparent_angle = fraction_of_fov * LLViewerCamera::getInstance()->getView(); // radians - mBoxHandleSize[i] = (F32) sqrtf(range_squared) * tan(apparent_angle) * BOX_HANDLE_BASE_FACTOR; - } - else - { - // range == zero - mBoxHandleSize[i] = BOX_HANDLE_BASE_FACTOR; - } - mBoxHandleSize[i] *= ui_scale_factor; - } - } - - //////////////////////////////////////////////////////////////////////// - // Draw bounding box - - LLVector3 pos_agent = bbox.getPositionAgent(); - LLQuaternion rot = bbox.getRotation(); - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - { - gGL.translatef(pos_agent.mV[VX], pos_agent.mV[VY], pos_agent.mV[VZ]); - - F32 angle_radians, x, y, z; - rot.getAngleAxis(&angle_radians, &x, &y, &z); - gGL.rotatef(angle_radians * RAD_TO_DEG, x, y, z); - - - { - LLGLEnable poly_offset(GL_POLYGON_OFFSET_FILL); - glPolygonOffset( -2.f, -2.f); - - renderCorners( bbox ); - renderFaces( bbox ); - - if (mManipPart != LL_NO_PART) - { - renderGuidelinesPart( bbox ); - } - - glPolygonOffset( 0.f, 0.f); - } - } - gGL.popMatrix(); - - if (mManipPart != LL_NO_PART) - { - renderSnapGuides(bbox); - } - gGL.popMatrix(); - - renderXYZ(bbox.getExtentLocal()); - } -} - -bool LLManipScale::handleMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - if(mHighlightedPart != LL_NO_PART) - { - handled = handleMouseDownOnPart( x, y, mask ); - } - - return handled; -} - -// Assumes that one of the arrows on an object was hit. -bool LLManipScale::handleMouseDownOnPart( S32 x, S32 y, MASK mask ) -{ - bool can_scale = canAffectSelection(); - if (!can_scale) - { - return false; - } - - highlightManipulators(x, y); - S32 hit_part = mHighlightedPart; - - LLSelectMgr::getInstance()->enableSilhouette(false); - mManipPart = (EManipPart)hit_part; - - LLBBox bbox = LLSelectMgr::getInstance()->getBBoxOfSelection(); - LLVector3 box_center_agent = bbox.getCenterAgent(); - LLVector3 box_corner_agent = bbox.localToAgent( unitVectorToLocalBBoxExtent( partToUnitVector( mManipPart ), bbox ) ); - - updateSnapGuides(bbox); - - mFirstClickX = x; - mFirstClickY = y; - mIsFirstClick = true; - - mDragStartPointGlobal = gAgent.getPosGlobalFromAgent(box_corner_agent); - mDragStartCenterGlobal = gAgent.getPosGlobalFromAgent(box_center_agent); - LLVector3 far_corner_agent = bbox.localToAgent( unitVectorToLocalBBoxExtent( -1.f * partToUnitVector( mManipPart ), bbox ) ); - mDragFarHitGlobal = gAgent.getPosGlobalFromAgent(far_corner_agent); - mDragPointGlobal = mDragStartPointGlobal; - - // we just started a drag, so save initial object positions, orientations, and scales - LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_SCALE); - // Route future Mouse messages here preemptively. (Release on mouse up.) - setMouseCapture( true ); - - mHelpTextTimer.reset(); - sNumTimesHelpTextShown++; - return true; -} - - -bool LLManipScale::handleMouseUp(S32 x, S32 y, MASK mask) -{ - // first, perform normal processing in case this was a quick-click - handleHover(x, y, mask); - - if( hasMouseCapture() ) - { - if( (LL_FACE_MIN <= (S32)mManipPart) - && ((S32)mManipPart <= LL_FACE_MAX) ) - { - sendUpdates(true,true,false); - } - else - if( (LL_CORNER_MIN <= (S32)mManipPart) - && ((S32)mManipPart <= LL_CORNER_MAX) ) - { - sendUpdates(true,true,true); - } - - //send texture update - LLSelectMgr::getInstance()->adjustTexturesByScale(true, getStretchTextures()); - - LLSelectMgr::getInstance()->enableSilhouette(true); - mManipPart = LL_NO_PART; - - // Might have missed last update due to UPDATE_DELAY timing - LLSelectMgr::getInstance()->sendMultipleUpdate( mLastUpdateFlags ); - - //gAgent.setObjectTracking(gSavedSettings.getBOOL("TrackFocusObject")); - LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); - } - return LLManip::handleMouseUp(x, y, mask); -} - - -bool LLManipScale::handleHover(S32 x, S32 y, MASK mask) -{ - if( hasMouseCapture() ) - { - if( mObjectSelection->isEmpty() ) - { - // Somehow the object got deselected while we were dragging it. - setMouseCapture( false ); - } - else - { - if((mFirstClickX != x) || (mFirstClickY != y)) - { - mIsFirstClick = false; - } - - if(!mIsFirstClick) - { - drag( x, y ); - } - } - LL_DEBUGS("UserInput") << "hover handled by LLManipScale (active)" << LL_ENDL; - } - else - { - mSnapRegime = SNAP_REGIME_NONE; - // not dragging... - highlightManipulators(x, y); - } - - // Patch up textures, if possible. - LLSelectMgr::getInstance()->adjustTexturesByScale(false, getStretchTextures()); - - gViewerWindow->setCursor(UI_CURSOR_TOOLSCALE); - return true; -} - -void LLManipScale::highlightManipulators(S32 x, S32 y) -{ - mHighlightedPart = LL_NO_PART; - - // If we have something selected, try to hit its manipulator handles. - // Don't do this with nothing selected, as it kills the framerate. - LLBBox bbox = LLSelectMgr::getInstance()->getBBoxOfSelection(); - - if( canAffectSelection() ) - { - LLMatrix4 transform; - if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) - { - LLVector4 translation(bbox.getPositionAgent()); - transform.initRotTrans(bbox.getRotation(), translation); - LLMatrix4 cfr(OGL_TO_CFR_ROTATION); - transform *= cfr; - LLMatrix4 window_scale; - F32 zoom_level = 2.f * gAgentCamera.mHUDCurZoom; - window_scale.initAll(LLVector3(zoom_level / LLViewerCamera::getInstance()->getAspect(), zoom_level, 0.f), - LLQuaternion::DEFAULT, - LLVector3::zero); - transform *= window_scale; - } - else - { - LLMatrix4 projMatrix = LLViewerCamera::getInstance()->getProjection(); - LLMatrix4 modelView = LLViewerCamera::getInstance()->getModelview(); - transform.initAll(LLVector3(1.f, 1.f, 1.f), bbox.getRotation(), bbox.getPositionAgent()); - - transform *= modelView; - transform *= projMatrix; - } - - LLVector3 min = bbox.getMinLocal(); - LLVector3 max = bbox.getMaxLocal(); - LLVector3 ctr = bbox.getCenterLocal(); - - S32 numManips = 0; - // corners - mManipulatorVertices[numManips++] = LLVector4(min.mV[VX], min.mV[VY], min.mV[VZ], 1.f); - mManipulatorVertices[numManips++] = LLVector4(min.mV[VX], min.mV[VY], max.mV[VZ], 1.f); - mManipulatorVertices[numManips++] = LLVector4(min.mV[VX], max.mV[VY], min.mV[VZ], 1.f); - mManipulatorVertices[numManips++] = LLVector4(min.mV[VX], max.mV[VY], max.mV[VZ], 1.f); - mManipulatorVertices[numManips++] = LLVector4(max.mV[VX], min.mV[VY], min.mV[VZ], 1.f); - mManipulatorVertices[numManips++] = LLVector4(max.mV[VX], min.mV[VY], max.mV[VZ], 1.f); - mManipulatorVertices[numManips++] = LLVector4(max.mV[VX], max.mV[VY], min.mV[VZ], 1.f); - mManipulatorVertices[numManips++] = LLVector4(max.mV[VX], max.mV[VY], max.mV[VZ], 1.f); - - // 1-D highlights are applicable iff one object is selected - if( mObjectSelection->getObjectCount() == 1 ) - { - // face centers - mManipulatorVertices[numManips++] = LLVector4(ctr.mV[VX], ctr.mV[VY], max.mV[VZ], 1.f); - mManipulatorVertices[numManips++] = LLVector4(max.mV[VX], ctr.mV[VY], ctr.mV[VZ], 1.f); - mManipulatorVertices[numManips++] = LLVector4(ctr.mV[VX], max.mV[VY], ctr.mV[VZ], 1.f); - mManipulatorVertices[numManips++] = LLVector4(min.mV[VX], ctr.mV[VY], ctr.mV[VZ], 1.f); - mManipulatorVertices[numManips++] = LLVector4(ctr.mV[VX], min.mV[VY], ctr.mV[VZ], 1.f); - mManipulatorVertices[numManips++] = LLVector4(ctr.mV[VX], ctr.mV[VY], min.mV[VZ], 1.f); - } - - for_each(mProjectedManipulators.begin(), mProjectedManipulators.end(), DeletePointer()); - mProjectedManipulators.clear(); - - for (S32 i = 0; i < numManips; i++) - { - LLVector4 projectedVertex = mManipulatorVertices[i] * transform; - projectedVertex = projectedVertex * (1.f / projectedVertex.mV[VW]); - - ManipulatorHandle* projManipulator = new ManipulatorHandle(LLVector3(projectedVertex.mV[VX], projectedVertex.mV[VY], - projectedVertex.mV[VZ]), MANIPULATOR_IDS[i], (i < 7) ? SCALE_MANIP_CORNER : SCALE_MANIP_FACE); - mProjectedManipulators.insert(projManipulator); - } - - LLRect world_view_rect = gViewerWindow->getWorldViewRectScaled(); - F32 half_width = (F32)world_view_rect.getWidth() / 2.f; - F32 half_height = (F32)world_view_rect.getHeight() / 2.f; - LLVector2 manip2d; - LLVector2 mousePos((F32)x - half_width, (F32)y - half_height); - LLVector2 delta; - - mHighlightedPart = LL_NO_PART; - - for (manipulator_list_t::iterator iter = mProjectedManipulators.begin(); - iter != mProjectedManipulators.end(); ++iter) - { - ManipulatorHandle* manipulator = *iter; - { - manip2d.set(manipulator->mPosition.mV[VX] * half_width, manipulator->mPosition.mV[VY] * half_height); - - delta = manip2d - mousePos; - if (delta.lengthSquared() < MAX_MANIP_SELECT_DISTANCE_SQUARED) - { - mHighlightedPart = manipulator->mManipID; - - //LL_INFOS() << "Tried: " << mHighlightedPart << LL_ENDL; - break; - } - } - } - } - - for (S32 i = 0; i < NUM_MANIPULATORS; i++) - { - if (mHighlightedPart == MANIPULATOR_IDS[i]) - { - mManipulatorScales[i] = lerp(mManipulatorScales[i], SELECTED_MANIPULATOR_SCALE, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE)); - } - else - { - mManipulatorScales[i] = lerp(mManipulatorScales[i], 1.f, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE)); - } - } - - LL_DEBUGS("UserInput") << "hover handled by LLManipScale (inactive)" << LL_ENDL; -} - - -void LLManipScale::renderFaces( const LLBBox& bbox ) -{ - // Don't bother to render the drag handles for 1-D scaling if - // more than one object is selected or if it is an attachment - if ( mObjectSelection->getObjectCount() > 1 ) - { - return; - } - - // This is a flattened representation of the box as render here - // . - // (+++) (++-) /|\t - // +------------+ | (texture coordinates) - // | | | - // | 1 | (*) --->s - // | +X | - // | | - // (+++) (+-+)| |(+--) (++-) (+++) - // +------------+------------+------------+------------+ - // |0 3|3 7|7 4|4 0| - // | 0 | 4 | 5 | 2 | - // | +Z | -Y | -Z | +Y | - // | | | | | - // |1 2|2 6|6 5|5 1| - // +------------+------------+------------+------------+ - // (-++) (--+)| |(---) (-+-) (-++) - // | 3 | - // | -X | - // | | - // | | - // +------------+ - // (-++) (-+-) - - LLColor4 highlight_color( 1.f, 1.f, 1.f, 0.5f); - LLColor4 normal_color( 1.f, 1.f, 1.f, 0.3f); - - LLColor4 x_highlight_color( 1.f, 0.2f, 0.2f, 1.0f); - LLColor4 x_normal_color( 0.6f, 0.f, 0.f, 0.4f); - - LLColor4 y_highlight_color( 0.2f, 1.f, 0.2f, 1.0f); - LLColor4 y_normal_color( 0.f, 0.6f, 0.f, 0.4f); - - LLColor4 z_highlight_color( 0.2f, 0.2f, 1.f, 1.0f); - LLColor4 z_normal_color( 0.f, 0.f, 0.6f, 0.4f); - - LLColor4 default_normal_color( 0.7f, 0.7f, 0.7f, 0.15f ); - - const LLVector3& min = bbox.getMinLocal(); - const LLVector3& max = bbox.getMaxLocal(); - LLVector3 ctr = bbox.getCenterLocal(); - - if (mManipPart == LL_NO_PART) - { - gGL.color4fv( default_normal_color.mV ); - LLGLDepthTest gls_depth(GL_FALSE); - gGL.begin(LLRender::QUADS); - { - // Face 0 - gGL.vertex3f(min.mV[VX], max.mV[VY], max.mV[VZ]); - gGL.vertex3f(min.mV[VX], min.mV[VY], max.mV[VZ]); - gGL.vertex3f(max.mV[VX], min.mV[VY], max.mV[VZ]); - gGL.vertex3f(max.mV[VX], max.mV[VY], max.mV[VZ]); - - // Face 1 - gGL.vertex3f(max.mV[VX], min.mV[VY], max.mV[VZ]); - gGL.vertex3f(max.mV[VX], min.mV[VY], min.mV[VZ]); - gGL.vertex3f(max.mV[VX], max.mV[VY], min.mV[VZ]); - gGL.vertex3f(max.mV[VX], max.mV[VY], max.mV[VZ]); - - // Face 2 - gGL.vertex3f(min.mV[VX], max.mV[VY], min.mV[VZ]); - gGL.vertex3f(min.mV[VX], max.mV[VY], max.mV[VZ]); - gGL.vertex3f(max.mV[VX], max.mV[VY], max.mV[VZ]); - gGL.vertex3f(max.mV[VX], max.mV[VY], min.mV[VZ]); - - // Face 3 - gGL.vertex3f(min.mV[VX], max.mV[VY], max.mV[VZ]); - gGL.vertex3f(min.mV[VX], max.mV[VY], min.mV[VZ]); - gGL.vertex3f(min.mV[VX], min.mV[VY], min.mV[VZ]); - gGL.vertex3f(min.mV[VX], min.mV[VY], max.mV[VZ]); - - // Face 4 - gGL.vertex3f(min.mV[VX], min.mV[VY], max.mV[VZ]); - gGL.vertex3f(min.mV[VX], min.mV[VY], min.mV[VZ]); - gGL.vertex3f(max.mV[VX], min.mV[VY], min.mV[VZ]); - gGL.vertex3f(max.mV[VX], min.mV[VY], max.mV[VZ]); - - // Face 5 - gGL.vertex3f(min.mV[VX], min.mV[VY], min.mV[VZ]); - gGL.vertex3f(min.mV[VX], max.mV[VY], min.mV[VZ]); - gGL.vertex3f(max.mV[VX], max.mV[VY], min.mV[VZ]); - gGL.vertex3f(max.mV[VX], min.mV[VY], min.mV[VZ]); - } - gGL.end(); - } - - // Find nearest vertex - LLVector3 orientWRTHead = bbox.agentToLocalBasis( bbox.getCenterAgent() - gAgentCamera.getCameraPositionAgent() ); - U32 nearest = - (orientWRTHead.mV[0] < 0.0f ? 1 : 0) + - (orientWRTHead.mV[1] < 0.0f ? 2 : 0) + - (orientWRTHead.mV[2] < 0.0f ? 4 : 0); - - // opposite faces on Linden cubes: - // 0 & 5 - // 1 & 3 - // 2 & 4 - - // Table of order to draw faces, based on nearest vertex - static U32 face_list[8][6] = { - { 2,0,1, 4,5,3 }, // v6 F201 F453 - { 2,0,3, 4,5,1 }, // v7 F203 F451 - { 4,0,1, 2,5,3 }, // v5 F401 F253 - { 4,0,3, 2,5,1 }, // v4 F403 F251 - { 2,5,1, 4,0,3 }, // v2 F251 F403 - { 2,5,3, 4,0,1 }, // v3 F253 F401 - { 4,5,1, 2,0,3 }, // v1 F451 F203 - { 4,5,3, 2,0,1 } // v0 F453 F201 - }; - - { - LLGLDepthTest gls_depth(GL_FALSE); - - for (S32 i = 0; i < 6; i++) - { - U32 face = face_list[nearest][i]; - switch( face ) - { - case 0: - conditionalHighlight( LL_FACE_POSZ, &z_highlight_color, &z_normal_color ); - renderAxisHandle( 8, ctr, LLVector3( ctr.mV[VX], ctr.mV[VY], max.mV[VZ] ) ); - break; - - case 1: - conditionalHighlight( LL_FACE_POSX, &x_highlight_color, &x_normal_color ); - renderAxisHandle( 9, ctr, LLVector3( max.mV[VX], ctr.mV[VY], ctr.mV[VZ] ) ); - break; - - case 2: - conditionalHighlight( LL_FACE_POSY, &y_highlight_color, &y_normal_color ); - renderAxisHandle( 10, ctr, LLVector3( ctr.mV[VX], max.mV[VY], ctr.mV[VZ] ) ); - break; - - case 3: - conditionalHighlight( LL_FACE_NEGX, &x_highlight_color, &x_normal_color ); - renderAxisHandle( 11, ctr, LLVector3( min.mV[VX], ctr.mV[VY], ctr.mV[VZ] ) ); - break; - - case 4: - conditionalHighlight( LL_FACE_NEGY, &y_highlight_color, &y_normal_color ); - renderAxisHandle( 12, ctr, LLVector3( ctr.mV[VX], min.mV[VY], ctr.mV[VZ] ) ); - break; - - case 5: - conditionalHighlight( LL_FACE_NEGZ, &z_highlight_color, &z_normal_color ); - renderAxisHandle( 13, ctr, LLVector3( ctr.mV[VX], ctr.mV[VY], min.mV[VZ] ) ); - break; - } - } - } -} - -void LLManipScale::renderCorners( const LLBBox& bbox ) -{ - U32 part = LL_CORNER_NNN; - - F32 x_offset = bbox.getMinLocal().mV[VX]; - for( S32 i=0; i < 2; i++ ) - { - F32 y_offset = bbox.getMinLocal().mV[VY]; - for( S32 j=0; j < 2; j++ ) - { - F32 z_offset = bbox.getMinLocal().mV[VZ]; - for( S32 k=0; k < 2; k++ ) - { - conditionalHighlight( part ); - renderBoxHandle( x_offset, y_offset, z_offset ); - part++; - - z_offset = bbox.getMaxLocal().mV[VZ]; - - } - y_offset = bbox.getMaxLocal().mV[VY]; - } - x_offset = bbox.getMaxLocal().mV[VX]; - } -} - - -void LLManipScale::renderBoxHandle( F32 x, F32 y, F32 z ) -{ - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - LLGLDepthTest gls_depth(GL_FALSE); - //LLGLDisable gls_stencil(GL_STENCIL_TEST); - - gGL.pushMatrix(); - { - gGL.translatef( x, y, z ); - gGL.scalef( mScaledBoxHandleSize, mScaledBoxHandleSize, mScaledBoxHandleSize ); - gBox.render(); - } - gGL.popMatrix(); -} - - -void LLManipScale::renderAxisHandle( U32 handle_index, const LLVector3& start, const LLVector3& end ) -{ - if( getShowAxes() ) - { - // Draws a single "jacks" style handle: a long, retangular box from start to end. - LLVector3 offset_start = end - start; - offset_start.normalize(); - offset_start = start + mBoxHandleSize[handle_index] * offset_start; - - LLVector3 delta = end - offset_start; - LLVector3 pos = offset_start + 0.5f * delta; - - gGL.pushMatrix(); - { - gGL.translatef( pos.mV[VX], pos.mV[VY], pos.mV[VZ] ); - gGL.scalef( - mBoxHandleSize[handle_index] + llabs(delta.mV[VX]), - mBoxHandleSize[handle_index] + llabs(delta.mV[VY]), - mBoxHandleSize[handle_index] + llabs(delta.mV[VZ])); - gBox.render(); - } - gGL.popMatrix(); - } - else - { - renderBoxHandle( end.mV[VX], end.mV[VY], end.mV[VZ] ); - } -} - -// General scale call -void LLManipScale::drag( S32 x, S32 y ) -{ - if( (LL_FACE_MIN <= (S32)mManipPart) - && ((S32)mManipPart <= LL_FACE_MAX) ) - { - dragFace( x, y ); - } - else if( (LL_CORNER_MIN <= (S32)mManipPart) - && ((S32)mManipPart <= LL_CORNER_MAX) ) - { - dragCorner( x, y ); - } - - // store changes to override updates - for (LLObjectSelection::iterator iter = LLSelectMgr::getInstance()->getSelection()->begin(); - iter != LLSelectMgr::getInstance()->getSelection()->end(); iter++) - { - LLSelectNode* selectNode = *iter; - LLViewerObject*cur = selectNode->getObject(); - LLViewerObject *root_object = (cur == NULL) ? NULL : cur->getRootEdit(); - if( cur->permModify() && cur->permMove() && !cur->isPermanentEnforced() && - ((root_object == NULL) || !root_object->isPermanentEnforced()) && - !cur->isAvatar()) - { - selectNode->mLastScale = cur->getScale(); - selectNode->mLastPositionLocal = cur->getPosition(); - } - } - - LLSelectMgr::getInstance()->updateSelectionCenter(); - gAgentCamera.clearFocusObject(); -} - -// Scale on three axis simultaneously -void LLManipScale::dragCorner( S32 x, S32 y ) -{ - // Suppress scale if mouse hasn't moved. - if (x == mLastMouseX && y == mLastMouseY) - { - return; - } - mLastMouseX = x; - mLastMouseY = y; - - LLVector3 drag_start_point_agent = gAgent.getPosAgentFromGlobal(mDragStartPointGlobal); - LLVector3 drag_start_center_agent = gAgent.getPosAgentFromGlobal(mDragStartCenterGlobal); - - LLVector3d drag_start_dir_d; - drag_start_dir_d.set(mDragStartPointGlobal - mDragStartCenterGlobal); - - F32 s = 0; - F32 t = 0; - nearestPointOnLineFromMouse(x, y, - drag_start_center_agent, - drag_start_point_agent, - s, t ); - - if( s <= 0 ) // we only care about intersections in front of the camera - { - return; - } - mDragPointGlobal = lerp(mDragStartCenterGlobal, mDragStartPointGlobal, t); - - LLBBox bbox = LLSelectMgr::getInstance()->getBBoxOfSelection(); - F32 scale_factor = 1.f; - F32 max_scale = partToMaxScale(mManipPart, bbox); - F32 min_scale = partToMinScale(mManipPart, bbox); - bool uniform = LLManipScale::getUniform(); - - // check for snapping - LLVector3 mouse_on_plane1; - getMousePointOnPlaneAgent(mouse_on_plane1, x, y, mScaleCenter, mScalePlaneNormal1); - mouse_on_plane1 -= mScaleCenter; - - LLVector3 mouse_on_plane2; - getMousePointOnPlaneAgent(mouse_on_plane2, x, y, mScaleCenter, mScalePlaneNormal2); - mouse_on_plane2 -= mScaleCenter; - - LLVector3 projected_drag_pos1 = inverse_projected_vec(mScaleDir, orthogonal_component(mouse_on_plane1, mSnapGuideDir1)); - LLVector3 projected_drag_pos2 = inverse_projected_vec(mScaleDir, orthogonal_component(mouse_on_plane2, mSnapGuideDir2)); - - bool snap_enabled = gSavedSettings.getBOOL("SnapEnabled"); - if (snap_enabled && (mouse_on_plane1 - projected_drag_pos1) * mSnapGuideDir1 > mSnapRegimeOffset) - { - F32 drag_dist = mScaleDir * projected_drag_pos1; // Projecting the drag position allows for negative results, vs using the length which will result in a "reverse scaling" bug. - - F32 cur_subdivisions = llclamp(getSubdivisionLevel(mScaleCenter + projected_drag_pos1, mScaleDir, mScaleSnapUnit1, mTickPixelSpacing1), sGridMinSubdivisionLevel, sGridMaxSubdivisionLevel); - F32 snap_dist = mScaleSnapUnit1 / (2.f * cur_subdivisions); - F32 relative_snap_dist = fmodf(drag_dist + snap_dist, mScaleSnapUnit1 / cur_subdivisions); - - mScaleSnappedValue = llclamp((drag_dist - (relative_snap_dist - snap_dist)), min_scale, max_scale); - scale_factor = mScaleSnappedValue / dist_vec(drag_start_point_agent, drag_start_center_agent); - mScaleSnappedValue /= mScaleSnapUnit1 * 2.f; - mSnapRegime = SNAP_REGIME_UPPER; - - if (!uniform) - { - scale_factor *= 0.5f; - } - } - else if (snap_enabled && (mouse_on_plane2 - projected_drag_pos2) * mSnapGuideDir2 > mSnapRegimeOffset ) - { - F32 drag_dist = mScaleDir * projected_drag_pos2; // Projecting the drag position allows for negative results, vs using the length which will result in a "reverse scaling" bug. - - F32 cur_subdivisions = llclamp(getSubdivisionLevel(mScaleCenter + projected_drag_pos2, mScaleDir, mScaleSnapUnit2, mTickPixelSpacing2), sGridMinSubdivisionLevel, sGridMaxSubdivisionLevel); - F32 snap_dist = mScaleSnapUnit2 / (2.f * cur_subdivisions); - F32 relative_snap_dist = fmodf(drag_dist + snap_dist, mScaleSnapUnit2 / cur_subdivisions); - - mScaleSnappedValue = llclamp((drag_dist - (relative_snap_dist - snap_dist)), min_scale, max_scale); - scale_factor = mScaleSnappedValue / dist_vec(drag_start_point_agent, drag_start_center_agent); - mScaleSnappedValue /= mScaleSnapUnit2 * 2.f; - mSnapRegime = SNAP_REGIME_LOWER; - - if (!uniform) - { - scale_factor *= 0.5f; - } - } - else - { - mSnapRegime = SNAP_REGIME_NONE; - scale_factor = t; - if (!uniform) - { - scale_factor = 0.5f + (scale_factor * 0.5f); - } - } - - - F32 max_scale_factor = get_default_max_prim_scale() / MIN_PRIM_SCALE; - F32 min_scale_factor = MIN_PRIM_SCALE / get_default_max_prim_scale(); - - // find max and min scale factors that will make biggest object hit max absolute scale and smallest object hit min absolute scale - for (LLObjectSelection::iterator iter = mObjectSelection->begin(); - iter != mObjectSelection->end(); iter++) - { - LLSelectNode* selectNode = *iter; - LLViewerObject* cur = selectNode->getObject(); - LLViewerObject *root_object = (cur == NULL) ? NULL : cur->getRootEdit(); - if( cur->permModify() && cur->permMove() && !cur->isPermanentEnforced() && - ((root_object == NULL) || !root_object->isPermanentEnforced()) && - !cur->isAvatar() ) - { - const LLVector3& scale = selectNode->mSavedScale; - - F32 cur_max_scale_factor = llmin( get_default_max_prim_scale(LLPickInfo::isFlora(cur)) / scale.mV[VX], get_default_max_prim_scale(LLPickInfo::isFlora(cur)) / scale.mV[VY], get_default_max_prim_scale(LLPickInfo::isFlora(cur)) / scale.mV[VZ] ); - max_scale_factor = llmin( max_scale_factor, cur_max_scale_factor ); - - F32 cur_min_scale_factor = llmax( MIN_PRIM_SCALE / scale.mV[VX], MIN_PRIM_SCALE / scale.mV[VY], MIN_PRIM_SCALE / scale.mV[VZ] ); - min_scale_factor = llmax( min_scale_factor, cur_min_scale_factor ); - } - } - - scale_factor = llclamp( scale_factor, min_scale_factor, max_scale_factor ); - - LLVector3d drag_global = uniform ? mDragStartCenterGlobal : mDragFarHitGlobal; - - // do the root objects i.e. (true == cur->isRootEdit()) - for (LLObjectSelection::iterator iter = mObjectSelection->begin(); - iter != mObjectSelection->end(); iter++) - { - LLSelectNode* selectNode = *iter; - LLViewerObject* cur = selectNode->getObject(); - LLViewerObject *root_object = (cur == NULL) ? NULL : cur->getRootEdit(); - if( cur->permModify() && cur->permMove() && !cur->isPermanentEnforced() && - ((root_object == NULL) || !root_object->isPermanentEnforced()) && - !cur->isAvatar() && cur->isRootEdit() ) - { - const LLVector3& scale = selectNode->mSavedScale; - cur->setScale( scale_factor * scale ); - - LLVector3 delta_pos; - LLVector3 original_pos = cur->getPositionEdit(); - LLVector3d new_pos_global = drag_global + (selectNode->mSavedPositionGlobal - drag_global) * scale_factor; - if (!cur->isAttachment()) - { - new_pos_global = LLWorld::getInstance()->clipToVisibleRegions(selectNode->mSavedPositionGlobal, new_pos_global); - } - cur->setPositionAbsoluteGlobal( new_pos_global ); - rebuild(cur); - - delta_pos = cur->getPositionEdit() - original_pos; - - if (selectNode->mIndividualSelection) - { - // counter-translate child objects if we are moving the root as an individual - LLViewerObject::const_child_list_t& child_list = cur->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); iter++) - { - LLViewerObject* childp = *iter; - - if (cur->isAttachment()) - { - LLVector3 child_pos = childp->getPosition() - (delta_pos * ~cur->getRotationEdit()); - childp->setPosition(child_pos); - } - else - { - LLVector3d child_pos_delta(delta_pos); - // RN: this updates drawable position instantly - childp->setPositionAbsoluteGlobal(childp->getPositionGlobal() - child_pos_delta); - } - rebuild(childp); - } - } - } - } - // do the child objects i.e. (false == cur->isRootEdit()) - for (LLObjectSelection::iterator iter = mObjectSelection->begin(); - iter != mObjectSelection->end(); iter++) - { - LLSelectNode* selectNode = *iter; - LLViewerObject*cur = selectNode->getObject(); - LLViewerObject *root_object = (cur == NULL) ? NULL : cur->getRootEdit(); - if( cur->permModify() && cur->permMove() && !cur->isPermanentEnforced() && - ((root_object == NULL) || !root_object->isPermanentEnforced()) && - !cur->isAvatar() && !cur->isRootEdit() ) - { - const LLVector3& scale = selectNode->mSavedScale; - cur->setScale( scale_factor * scale, false ); - - if (!selectNode->mIndividualSelection) - { - cur->setPosition(selectNode->mSavedPositionLocal * scale_factor); - } - - rebuild(cur); - } - } -} - -// Scale on a single axis -void LLManipScale::dragFace( S32 x, S32 y ) -{ - // Suppress scale if mouse hasn't moved. - if (x == mLastMouseX && y == mLastMouseY) - { - return; - } - - mLastMouseX = x; - mLastMouseY = y; - - LLVector3d drag_start_point_global = mDragStartPointGlobal; - LLVector3d drag_start_center_global = mDragStartCenterGlobal; - LLVector3 drag_start_point_agent = gAgent.getPosAgentFromGlobal(drag_start_point_global); - LLVector3 drag_start_center_agent = gAgent.getPosAgentFromGlobal(drag_start_center_global); - - LLVector3d drag_start_dir_d; - drag_start_dir_d.set(drag_start_point_global - drag_start_center_global); - LLVector3 drag_start_dir_f; - drag_start_dir_f.set(drag_start_dir_d); - - LLBBox bbox = LLSelectMgr::getInstance()->getBBoxOfSelection(); - - F32 s = 0; - F32 t = 0; - - nearestPointOnLineFromMouse(x, - y, - drag_start_center_agent, - drag_start_point_agent, - s, t ); - - if( s <= 0 ) // we only care about intersections in front of the camera - { - return; - } - - LLVector3d drag_point_global = drag_start_center_global + t * drag_start_dir_d; - LLVector3 part_dir_local = partToUnitVector( mManipPart ); - - // check for snapping - LLVector3 mouse_on_plane; - getMousePointOnPlaneAgent(mouse_on_plane, x, y, mScaleCenter, mScalePlaneNormal1 ); - - LLVector3 mouse_on_scale_line = mScaleCenter + projected_vec(mouse_on_plane - mScaleCenter, mScaleDir); - LLVector3 drag_delta(mouse_on_scale_line - drag_start_point_agent); - F32 max_drag_dist = partToMaxScale(mManipPart, bbox); - F32 min_drag_dist = partToMinScale(mManipPart, bbox); - - bool uniform = LLManipScale::getUniform(); - if( uniform ) - { - drag_delta *= 2.f; - } - - LLVector3 scale_center_to_mouse = mouse_on_plane - mScaleCenter; - F32 dist_from_scale_line = dist_vec(scale_center_to_mouse, (mouse_on_scale_line - mScaleCenter)); - F32 dist_along_scale_line = scale_center_to_mouse * mScaleDir; - - bool snap_enabled = gSavedSettings.getBOOL("SnapEnabled"); - - if (snap_enabled && dist_from_scale_line > mSnapRegimeOffset) - { - mSnapRegime = static_cast(SNAP_REGIME_UPPER | SNAP_REGIME_LOWER); // A face drag doesn't have split regimes. - - if (dist_along_scale_line > max_drag_dist) - { - mScaleSnappedValue = max_drag_dist; - - LLVector3 clamp_point = mScaleCenter + max_drag_dist * mScaleDir; - drag_delta.set(clamp_point - drag_start_point_agent); - } - else if (dist_along_scale_line < min_drag_dist) - { - mScaleSnappedValue = min_drag_dist; - - LLVector3 clamp_point = mScaleCenter + min_drag_dist * mScaleDir; - drag_delta.set(clamp_point - drag_start_point_agent); - } - else - { - F32 drag_dist = scale_center_to_mouse * mScaleDir; - F32 cur_subdivisions = llclamp(getSubdivisionLevel(mScaleCenter + mScaleDir * drag_dist, mScaleDir, mScaleSnapUnit1, mTickPixelSpacing1), sGridMinSubdivisionLevel, sGridMaxSubdivisionLevel); - F32 snap_dist = mScaleSnapUnit1 / (2.f * cur_subdivisions); - F32 relative_snap_dist = fmodf(drag_dist + snap_dist, mScaleSnapUnit1 / cur_subdivisions); - relative_snap_dist -= snap_dist; - - //make sure that values that the scale is "snapped to" - //do not exceed/go under the applicable max/mins - //this causes the box to shift displacements ever so slightly - //although the "snap value" should go down to 0 - //see Jira 1027 - relative_snap_dist = llclamp(relative_snap_dist, - drag_dist - max_drag_dist, - drag_dist - min_drag_dist); - - mScaleSnappedValue = (drag_dist - relative_snap_dist) / (mScaleSnapUnit1 * 2.f); - - if (llabs(relative_snap_dist) < snap_dist) - { - LLVector3 drag_correction = relative_snap_dist * mScaleDir; - if (uniform) - { - drag_correction *= 2.f; - } - - drag_delta -= drag_correction; - } - } - } - else - { - mSnapRegime = SNAP_REGIME_NONE; - } - - LLVector3 dir_agent; - if( part_dir_local.mV[VX] ) - { - dir_agent = bbox.localToAgentBasis( LLVector3::x_axis ); - } - else if( part_dir_local.mV[VY] ) - { - dir_agent = bbox.localToAgentBasis( LLVector3::y_axis ); - } - else if( part_dir_local.mV[VZ] ) - { - dir_agent = bbox.localToAgentBasis( LLVector3::z_axis ); - } - stretchFace( - projected_vec(drag_start_dir_f, dir_agent) + drag_start_center_agent, - projected_vec(drag_delta, dir_agent)); - - mDragPointGlobal = drag_point_global; -} - -void LLManipScale::sendUpdates( bool send_position_update, bool send_scale_update, bool corner ) -{ - // Throttle updates to 10 per second. - static LLTimer update_timer; - F32 elapsed_time = update_timer.getElapsedTimeF32(); - const F32 UPDATE_DELAY = 0.1f; // min time between transmitted updates - - if( send_scale_update || send_position_update ) - { - U32 update_flags = UPD_NONE; - if (send_position_update) update_flags |= UPD_POSITION; - if (send_scale_update) update_flags |= UPD_SCALE; - -// bool send_type = SEND_INDIVIDUALS; - if (corner) - { - update_flags |= UPD_UNIFORM; - } - // keep this up to date for sendonmouseup - mLastUpdateFlags = update_flags; - - // enforce minimum update delay and don't stream updates on sub-object selections - if( elapsed_time > UPDATE_DELAY && !gSavedSettings.getBOOL("EditLinkedParts") ) - { - LLSelectMgr::getInstance()->sendMultipleUpdate( update_flags ); - update_timer.reset(); - mSendUpdateOnMouseUp = false; - } - else - { - mSendUpdateOnMouseUp = true; - } - dialog_refresh_all(); - } -} - -// Rescales in a single dimension. Either uniform (standard) or one-sided (scale plus translation) -// depending on mUniform. Handles multiple selection and objects that are not aligned to the bounding box. -void LLManipScale::stretchFace( const LLVector3& drag_start_agent, const LLVector3& drag_delta_agent ) -{ - LLVector3 drag_start_center_agent = gAgent.getPosAgentFromGlobal(mDragStartCenterGlobal); - - for (LLObjectSelection::iterator iter = mObjectSelection->begin(); - iter != mObjectSelection->end(); iter++) - { - LLSelectNode* selectNode = *iter; - LLViewerObject*cur = selectNode->getObject(); - LLViewerObject *root_object = (cur == NULL) ? NULL : cur->getRootEdit(); - if( cur->permModify() && cur->permMove() && !cur->isPermanentEnforced() && - ((root_object == NULL) || !root_object->isPermanentEnforced()) && - !cur->isAvatar() ) - { - LLBBox cur_bbox = cur->getBoundingBoxAgent(); - LLVector3 start_local = cur_bbox.agentToLocal( drag_start_agent ); - LLVector3 end_local = cur_bbox.agentToLocal( drag_start_agent + drag_delta_agent); - LLVector3 start_center_local = cur_bbox.agentToLocal( drag_start_center_agent ); - LLVector3 axis = nearestAxis( start_local - start_center_local ); - S32 axis_index = axis.mV[0] ? 0 : (axis.mV[1] ? 1 : 2 ); - - LLVector3 delta_local = end_local - start_local; - F32 delta_local_mag = delta_local.length(); - LLVector3 dir_local; - if (delta_local_mag == 0.f) - { - dir_local = axis; - } - else - { - dir_local = delta_local / delta_local_mag; // normalized delta_local - } - - F32 denom = axis * dir_local; - F32 desired_delta_size = is_approx_zero(denom) ? 0.f : (delta_local_mag / denom); // in meters - F32 desired_scale = llclamp(selectNode->mSavedScale.mV[axis_index] + desired_delta_size, MIN_PRIM_SCALE, get_default_max_prim_scale(LLPickInfo::isFlora(cur))); - // propagate scale constraint back to position offset - desired_delta_size = desired_scale - selectNode->mSavedScale.mV[axis_index]; // propagate constraint back to position - - LLVector3 scale = cur->getScale(); - scale.mV[axis_index] = desired_scale; - cur->setScale(scale, false); - rebuild(cur); - LLVector3 delta_pos; - if( !getUniform() ) - { - LLVector3 delta_pos_local = axis * (0.5f * desired_delta_size); - LLVector3d delta_pos_global; - delta_pos_global.set(cur_bbox.localToAgent( delta_pos_local ) - cur_bbox.getCenterAgent()); - LLVector3 cur_pos = cur->getPositionEdit(); - - if (cur->isRootEdit() && !cur->isAttachment()) - { - LLVector3d new_pos_global = LLWorld::getInstance()->clipToVisibleRegions(selectNode->mSavedPositionGlobal, selectNode->mSavedPositionGlobal + delta_pos_global); - cur->setPositionGlobal( new_pos_global ); - } - else - { - LLXform* parent_xform = cur->mDrawable->getXform()->getParent(); - LLVector3 new_pos_local; - // this works in attachment point space using world space delta - if (parent_xform) - { - new_pos_local = selectNode->mSavedPositionLocal + (LLVector3(delta_pos_global) * ~parent_xform->getWorldRotation()); - } - else - { - new_pos_local = selectNode->mSavedPositionLocal + LLVector3(delta_pos_global); - } - cur->setPosition(new_pos_local); - } - delta_pos = cur->getPositionEdit() - cur_pos; - } - if (cur->isRootEdit() && selectNode->mIndividualSelection) - { - // counter-translate child objects if we are moving the root as an individual - LLViewerObject::const_child_list_t& child_list = cur->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); iter++) - { - LLViewerObject* childp = *iter; - if (!getUniform()) - { - LLVector3 child_pos = childp->getPosition() - (delta_pos * ~cur->getRotationEdit()); - childp->setPosition(child_pos); - rebuild(childp); - } - } - } - } - } -} - - -void LLManipScale::renderGuidelinesPart( const LLBBox& bbox ) -{ - LLVector3 guideline_start = bbox.getCenterLocal(); - - LLVector3 guideline_end = unitVectorToLocalBBoxExtent( partToUnitVector( mManipPart ), bbox ); - - if (!getUniform()) - { - guideline_start = unitVectorToLocalBBoxExtent( -partToUnitVector( mManipPart ), bbox ); - } - - guideline_end -= guideline_start; - guideline_end.normalize(); - guideline_end *= LLWorld::getInstance()->getRegionWidthInMeters(); - guideline_end += guideline_start; - - { - LLGLDepthTest gls_depth(GL_TRUE); - gl_line_3d( guideline_start, guideline_end, LLColor4(1.f, 1.f, 1.f, 0.5f) ); - } - { - LLGLDepthTest gls_depth(GL_FALSE); - gl_line_3d( guideline_start, guideline_end, LLColor4(1.f, 1.f, 1.f, 0.25f) ); - } -} - -void LLManipScale::updateSnapGuides(const LLBBox& bbox) -{ - LLVector3 grid_origin; - LLVector3 grid_scale; - LLQuaternion grid_rotation; - LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale); - - bool uniform = LLManipScale::getUniform(); - - LLVector3 box_corner_agent = bbox.localToAgent(unitVectorToLocalBBoxExtent( partToUnitVector( mManipPart ), bbox )); - mScaleCenter = uniform ? bbox.getCenterAgent() : bbox.localToAgent(unitVectorToLocalBBoxExtent( -1.f * partToUnitVector( mManipPart ), bbox )); - mScaleDir = box_corner_agent - mScaleCenter; - mScaleDir.normalize(); - - if(mObjectSelection->getSelectType() == SELECT_TYPE_HUD) - { - mSnapRegimeOffset = SNAP_GUIDE_SCREEN_OFFSET / gAgentCamera.mHUDCurZoom; - } - else - { - F32 object_distance = dist_vec(box_corner_agent, LLViewerCamera::getInstance()->getOrigin()); - mSnapRegimeOffset = (SNAP_GUIDE_SCREEN_OFFSET * gViewerWindow->getWorldViewWidthRaw() * object_distance) / LLViewerCamera::getInstance()->getPixelMeterRatio(); - } - LLVector3 cam_at_axis; - F32 snap_guide_length; - if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) - { - cam_at_axis.set(1.f, 0.f, 0.f); - snap_guide_length = SNAP_GUIDE_SCREEN_LENGTH / gAgentCamera.mHUDCurZoom; - } - else - { - cam_at_axis = LLViewerCamera::getInstance()->getAtAxis(); - F32 manipulator_distance = dist_vec(box_corner_agent, LLViewerCamera::getInstance()->getOrigin()); - snap_guide_length = (SNAP_GUIDE_SCREEN_LENGTH * gViewerWindow->getWorldViewWidthRaw() * manipulator_distance) / LLViewerCamera::getInstance()->getPixelMeterRatio(); - } - - mSnapGuideLength = snap_guide_length / llmax(0.1f, (llmin(mSnapGuideDir1 * cam_at_axis, mSnapGuideDir2 * cam_at_axis))); - - LLVector3 off_axis_dir = mScaleDir % cam_at_axis; - off_axis_dir.normalize(); - - if( (LL_FACE_MIN <= (S32)mManipPart) && ((S32)mManipPart <= LL_FACE_MAX) ) - { - LLVector3 bbox_relative_cam_dir = off_axis_dir * ~bbox.getRotation(); - bbox_relative_cam_dir.abs(); - if (bbox_relative_cam_dir.mV[VX] > bbox_relative_cam_dir.mV[VY] && bbox_relative_cam_dir.mV[VX] > bbox_relative_cam_dir.mV[VZ]) - { - mSnapGuideDir1 = LLVector3::x_axis * bbox.getRotation(); - } - else if (bbox_relative_cam_dir.mV[VY] > bbox_relative_cam_dir.mV[VZ]) - { - mSnapGuideDir1 = LLVector3::y_axis * bbox.getRotation(); - } - else - { - mSnapGuideDir1 = LLVector3::z_axis * bbox.getRotation(); - } - - LLVector3 scale_snap = grid_scale; - mScaleSnapUnit1 = scale_snap.scaleVec(partToUnitVector( mManipPart )).length(); - mScaleSnapUnit2 = mScaleSnapUnit1; - mSnapGuideDir1 *= mSnapGuideDir1 * LLViewerCamera::getInstance()->getUpAxis() > 0.f ? 1.f : -1.f; - mSnapGuideDir2 = mSnapGuideDir1 * -1.f; - mSnapDir1 = mScaleDir; - mSnapDir2 = mScaleDir; - } - else if( (LL_CORNER_MIN <= (S32)mManipPart) && ((S32)mManipPart <= LL_CORNER_MAX) ) - { - LLVector3 local_camera_dir; - if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) - { - local_camera_dir = LLVector3(-1.f, 0.f, 0.f) * ~bbox.getRotation(); - } - else - { - local_camera_dir = (LLViewerCamera::getInstance()->getOrigin() - box_corner_agent) * ~bbox.getRotation(); - local_camera_dir.normalize(); - } - - LLVector3 axis_flip; - switch (mManipPart) - { - case LL_CORNER_NNN: - axis_flip.set(1.f, 1.f, 1.f); - break; - case LL_CORNER_NNP: - axis_flip.set(1.f, 1.f, -1.f); - break; - case LL_CORNER_NPN: - axis_flip.set(1.f, -1.f, 1.f); - break; - case LL_CORNER_NPP: - axis_flip.set(1.f, -1.f, -1.f); - break; - case LL_CORNER_PNN: - axis_flip.set(-1.f, 1.f, 1.f); - break; - case LL_CORNER_PNP: - axis_flip.set(-1.f, 1.f, -1.f); - break; - case LL_CORNER_PPN: - axis_flip.set(-1.f, -1.f, 1.f); - break; - case LL_CORNER_PPP: - axis_flip.set(-1.f, -1.f, -1.f); - break; - default: - break; - } - - // account for which side of the object the camera is located and negate appropriate axes - local_camera_dir.scaleVec(axis_flip); - - // normalize to object scale - LLVector3 bbox_extent = bbox.getExtentLocal(); - local_camera_dir.scaleVec(LLVector3(1.f / bbox_extent.mV[VX], 1.f / bbox_extent.mV[VY], 1.f / bbox_extent.mV[VZ])); - - S32 scale_face = -1; - - if ((local_camera_dir.mV[VX] > 0.f) == (local_camera_dir.mV[VY] > 0.f)) - { - if ((local_camera_dir.mV[VZ] > 0.f) == (local_camera_dir.mV[VY] > 0.f)) - { - LLVector3 local_camera_dir_abs = local_camera_dir; - local_camera_dir_abs.abs(); - // all neighboring faces of bbox are pointing towards camera or away from camera - // use largest magnitude face for snap guides - if (local_camera_dir_abs.mV[VX] > local_camera_dir_abs.mV[VY]) - { - if (local_camera_dir_abs.mV[VX] > local_camera_dir_abs.mV[VZ]) - { - scale_face = VX; - } - else - { - scale_face = VZ; - } - } - else // y > x - { - if (local_camera_dir_abs.mV[VY] > local_camera_dir_abs.mV[VZ]) - { - scale_face = VY; - } - else - { - scale_face = VZ; - } - } - } - else - { - // z axis facing opposite direction from x and y relative to camera, use x and y for snap guides - scale_face = VZ; - } - } - else // x and y axes are facing in opposite directions relative to camera - { - if ((local_camera_dir.mV[VZ] > 0.f) == (local_camera_dir.mV[VY] > 0.f)) - { - // x axis facing opposite direction from y and z relative to camera, use y and z for snap guides - scale_face = VX; - } - else - { - // y axis facing opposite direction from x and z relative to camera, use x and z for snap guides - scale_face = VY; - } - } - - switch(scale_face) - { - case VX: - // x axis face being scaled, use y and z for snap guides - mSnapGuideDir1 = LLVector3::y_axis.scaledVec(axis_flip); - mScaleSnapUnit1 = grid_scale.mV[VZ]; - mSnapGuideDir2 = LLVector3::z_axis.scaledVec(axis_flip); - mScaleSnapUnit2 = grid_scale.mV[VY]; - break; - case VY: - // y axis facing being scaled, use x and z for snap guides - mSnapGuideDir1 = LLVector3::x_axis.scaledVec(axis_flip); - mScaleSnapUnit1 = grid_scale.mV[VZ]; - mSnapGuideDir2 = LLVector3::z_axis.scaledVec(axis_flip); - mScaleSnapUnit2 = grid_scale.mV[VX]; - break; - case VZ: - // z axis facing being scaled, use x and y for snap guides - mSnapGuideDir1 = LLVector3::x_axis.scaledVec(axis_flip); - mScaleSnapUnit1 = grid_scale.mV[VY]; - mSnapGuideDir2 = LLVector3::y_axis.scaledVec(axis_flip); - mScaleSnapUnit2 = grid_scale.mV[VX]; - break; - default: - mSnapGuideDir1.setZero(); - mScaleSnapUnit1 = 0.f; - - mSnapGuideDir2.setZero(); - mScaleSnapUnit2 = 0.f; - break; - } - - mSnapGuideDir1.rotVec(bbox.getRotation()); - mSnapGuideDir2.rotVec(bbox.getRotation()); - mSnapDir1 = -1.f * mSnapGuideDir2; - mSnapDir2 = -1.f * mSnapGuideDir1; - } - - mScalePlaneNormal1 = mSnapGuideDir1 % mScaleDir; - mScalePlaneNormal1.normalize(); - - mScalePlaneNormal2 = mSnapGuideDir2 % mScaleDir; - mScalePlaneNormal2.normalize(); - - mScaleSnapUnit1 = mScaleSnapUnit1 / (mSnapDir1 * mScaleDir); - mScaleSnapUnit2 = mScaleSnapUnit2 / (mSnapDir2 * mScaleDir); - - mTickPixelSpacing1 = ll_round((F32)MIN_DIVISION_PIXEL_WIDTH / (mScaleDir % mSnapGuideDir1).length()); - mTickPixelSpacing2 = ll_round((F32)MIN_DIVISION_PIXEL_WIDTH / (mScaleDir % mSnapGuideDir2).length()); - - if (uniform) - { - mScaleSnapUnit1 *= 0.5f; - mScaleSnapUnit2 *= 0.5f; - } -} - -void LLManipScale::renderSnapGuides(const LLBBox& bbox) -{ - if (!gSavedSettings.getBOOL("SnapEnabled")) - { - return; - } - - F32 grid_alpha = gSavedSettings.getF32("GridOpacity"); - - F32 max_point_on_scale_line = partToMaxScale(mManipPart, bbox); - LLVector3 drag_point = gAgent.getPosAgentFromGlobal(mDragPointGlobal); - - updateGridSettings(); - - S32 pass; - for (pass = 0; pass < 3; pass++) - { - LLColor4 tick_color = setupSnapGuideRenderPass(pass); - - gGL.begin(LLRender::LINES); - LLVector3 line_mid = mScaleCenter + (mScaleSnappedValue * mScaleDir) + (mSnapGuideDir1 * mSnapRegimeOffset); - LLVector3 line_start = line_mid - (mScaleDir * (llmin(mScaleSnappedValue, mSnapGuideLength * 0.5f))); - LLVector3 line_end = line_mid + (mScaleDir * llmin(max_point_on_scale_line - mScaleSnappedValue, mSnapGuideLength * 0.5f)); - - gGL.color4f(tick_color.mV[VRED], tick_color.mV[VGREEN], tick_color.mV[VBLUE], tick_color.mV[VALPHA] * 0.1f); - gGL.vertex3fv(line_start.mV); - gGL.color4fv(tick_color.mV); - gGL.vertex3fv(line_mid.mV); - gGL.vertex3fv(line_mid.mV); - gGL.color4f(tick_color.mV[VRED], tick_color.mV[VGREEN], tick_color.mV[VBLUE], tick_color.mV[VALPHA] * 0.1f); - gGL.vertex3fv(line_end.mV); - - line_mid = mScaleCenter + (mScaleSnappedValue * mScaleDir) + (mSnapGuideDir2 * mSnapRegimeOffset); - line_start = line_mid - (mScaleDir * (llmin(mScaleSnappedValue, mSnapGuideLength * 0.5f))); - line_end = line_mid + (mScaleDir * llmin(max_point_on_scale_line - mScaleSnappedValue, mSnapGuideLength * 0.5f)); - gGL.vertex3fv(line_start.mV); - gGL.color4fv(tick_color.mV); - gGL.vertex3fv(line_mid.mV); - gGL.vertex3fv(line_mid.mV); - gGL.color4f(tick_color.mV[VRED], tick_color.mV[VGREEN], tick_color.mV[VBLUE], tick_color.mV[VALPHA] * 0.1f); - gGL.vertex3fv(line_end.mV); - gGL.end(); - } - - { - LLGLDepthTest gls_depth(GL_FALSE); - - F32 dist_grid_axis = llmax(0.f, (drag_point - mScaleCenter) * mScaleDir); - - F32 smallest_subdivision1 = mScaleSnapUnit1 / sGridMaxSubdivisionLevel; - F32 smallest_subdivision2 = mScaleSnapUnit2 / sGridMaxSubdivisionLevel; - - F32 dist_scale_units_1 = dist_grid_axis / smallest_subdivision1; - F32 dist_scale_units_2 = dist_grid_axis / smallest_subdivision2; - - // find distance to nearest smallest grid unit - F32 grid_multiple1 = llfloor(dist_scale_units_1); - F32 grid_multiple2 = llfloor(dist_scale_units_2); - F32 grid_offset1 = fmodf(dist_grid_axis, smallest_subdivision1); - F32 grid_offset2 = fmodf(dist_grid_axis, smallest_subdivision2); - - // how many smallest grid units are we away from largest grid scale? - S32 sub_div_offset_1 = ll_round(fmod(dist_grid_axis - grid_offset1, mScaleSnapUnit1 / sGridMinSubdivisionLevel) / smallest_subdivision1); - S32 sub_div_offset_2 = ll_round(fmod(dist_grid_axis - grid_offset2, mScaleSnapUnit2 / sGridMinSubdivisionLevel) / smallest_subdivision2); - - S32 num_ticks_per_side1 = llmax(1, lltrunc(0.5f * mSnapGuideLength / smallest_subdivision1)); - S32 num_ticks_per_side2 = llmax(1, lltrunc(0.5f * mSnapGuideLength / smallest_subdivision2)); - S32 ticks_from_scale_center_1 = lltrunc(dist_scale_units_1); - S32 ticks_from_scale_center_2 = lltrunc(dist_scale_units_2); - S32 max_ticks1 = llceil(max_point_on_scale_line / smallest_subdivision1 - dist_scale_units_1); - S32 max_ticks2 = llceil(max_point_on_scale_line / smallest_subdivision2 - dist_scale_units_2); - S32 start_tick = 0; - S32 stop_tick = 0; - - if (mSnapRegime != SNAP_REGIME_NONE) - { - // draw snap guide line - gGL.begin(LLRender::LINES); - LLVector3 snap_line_center = bbox.localToAgent(unitVectorToLocalBBoxExtent( partToUnitVector( mManipPart ), bbox )); - - LLVector3 snap_line_start = snap_line_center + (mSnapGuideDir1 * mSnapRegimeOffset); - LLVector3 snap_line_end = snap_line_center + (mSnapGuideDir2 * mSnapRegimeOffset); - - gGL.color4f(1.f, 1.f, 1.f, grid_alpha); - gGL.vertex3fv(snap_line_start.mV); - gGL.vertex3fv(snap_line_center.mV); - gGL.vertex3fv(snap_line_center.mV); - gGL.vertex3fv(snap_line_end.mV); - gGL.end(); - - // draw snap guide arrow - gGL.begin(LLRender::TRIANGLES); - { - //gGLSNoCullFaces.set(); - gGL.color4f(1.f, 1.f, 1.f, grid_alpha); - - LLVector3 arrow_dir; - LLVector3 arrow_span = mScaleDir; - - arrow_dir = snap_line_start - snap_line_center; - arrow_dir.normalize(); - gGL.vertex3fv((snap_line_start + arrow_dir * mSnapRegimeOffset * 0.1f).mV); - gGL.vertex3fv((snap_line_start + arrow_span * mSnapRegimeOffset * 0.1f).mV); - gGL.vertex3fv((snap_line_start - arrow_span * mSnapRegimeOffset * 0.1f).mV); - - arrow_dir = snap_line_end - snap_line_center; - arrow_dir.normalize(); - gGL.vertex3fv((snap_line_end + arrow_dir * mSnapRegimeOffset * 0.1f).mV); - gGL.vertex3fv((snap_line_end + arrow_span * mSnapRegimeOffset * 0.1f).mV); - gGL.vertex3fv((snap_line_end - arrow_span * mSnapRegimeOffset * 0.1f).mV); - } - gGL.end(); - } - - LLVector2 screen_translate_axis(llabs(mScaleDir * LLViewerCamera::getInstance()->getLeftAxis()), llabs(mScaleDir * LLViewerCamera::getInstance()->getUpAxis())); - screen_translate_axis.normalize(); - - S32 tick_label_spacing = ll_round(screen_translate_axis * sTickLabelSpacing); - - for (pass = 0; pass < 3; pass++) - { - LLColor4 tick_color = setupSnapGuideRenderPass(pass); - - start_tick = -(llmin(ticks_from_scale_center_1, num_ticks_per_side1)); - stop_tick = llmin(max_ticks1, num_ticks_per_side1); - - gGL.begin(LLRender::LINES); - // draw first row of ticks - for (S32 i = start_tick; i <= stop_tick; i++) - { - F32 alpha = (1.f - (1.f * ((F32)llabs(i) / (F32)num_ticks_per_side1))); - LLVector3 tick_pos = mScaleCenter + (mScaleDir * (grid_multiple1 + i) * smallest_subdivision1); - - //No need check this condition to prevent tick position scaling (FIX MAINT-5207/5208) - //F32 cur_subdivisions = llclamp(getSubdivisionLevel(tick_pos, mScaleDir, mScaleSnapUnit1, mTickPixelSpacing1), sGridMinSubdivisionLevel, sGridMaxSubdivisionLevel); - /*if (fmodf((F32)(i + sub_div_offset_1), (sGridMaxSubdivisionLevel / cur_subdivisions)) != 0.f) - { - continue; - }*/ - - F32 tick_scale = 1.f; - for (F32 division_level = sGridMaxSubdivisionLevel; division_level >= sGridMinSubdivisionLevel; division_level /= 2.f) - { - if (fmodf((F32)(i + sub_div_offset_1), division_level) == 0.f) - { - break; - } - tick_scale *= 0.7f; - } - - gGL.color4f(tick_color.mV[VRED], tick_color.mV[VGREEN], tick_color.mV[VBLUE], tick_color.mV[VALPHA] * alpha); - LLVector3 tick_start = tick_pos + (mSnapGuideDir1 * mSnapRegimeOffset); - LLVector3 tick_end = tick_start + (mSnapGuideDir1 * mSnapRegimeOffset * tick_scale); - gGL.vertex3fv(tick_start.mV); - gGL.vertex3fv(tick_end.mV); - } - - // draw opposite row of ticks - start_tick = -(llmin(ticks_from_scale_center_2, num_ticks_per_side2)); - stop_tick = llmin(max_ticks2, num_ticks_per_side2); - - for (S32 i = start_tick; i <= stop_tick; i++) - { - F32 alpha = (1.f - (1.f * ((F32)llabs(i) / (F32)num_ticks_per_side2))); - LLVector3 tick_pos = mScaleCenter + (mScaleDir * (grid_multiple2 + i) * smallest_subdivision2); - - //No need check this condition to prevent tick position scaling (FIX MAINT-5207/5208) - //F32 cur_subdivisions = llclamp(getSubdivisionLevel(tick_pos, mScaleDir, mScaleSnapUnit2, mTickPixelSpacing2), sGridMinSubdivisionLevel, sGridMaxSubdivisionLevel); - /*if (fmodf((F32)(i + sub_div_offset_2), (sGridMaxSubdivisionLevel / cur_subdivisions)) != 0.f) - { - continue; - }*/ - - F32 tick_scale = 1.f; - for (F32 division_level = sGridMaxSubdivisionLevel; division_level >= sGridMinSubdivisionLevel; division_level /= 2.f) - { - if (fmodf((F32)(i + sub_div_offset_2), division_level) == 0.f) - { - break; - } - tick_scale *= 0.7f; - } - - gGL.color4f(tick_color.mV[VRED], tick_color.mV[VGREEN], tick_color.mV[VBLUE], tick_color.mV[VALPHA] * alpha); - LLVector3 tick_start = tick_pos + (mSnapGuideDir2 * mSnapRegimeOffset); - LLVector3 tick_end = tick_start + (mSnapGuideDir2 * mSnapRegimeOffset * tick_scale); - gGL.vertex3fv(tick_start.mV); - gGL.vertex3fv(tick_end.mV); - } - gGL.end(); - } - - // render upper tick labels - start_tick = -(llmin(ticks_from_scale_center_1, num_ticks_per_side1)); - stop_tick = llmin(max_ticks1, num_ticks_per_side1); - - F32 grid_resolution = mObjectSelection->getSelectType() == SELECT_TYPE_HUD ? 0.25f : llmax(gSavedSettings.getF32("GridResolution"), 0.001f); - S32 label_sub_div_offset_1 = ll_round(fmod(dist_grid_axis - grid_offset1, mScaleSnapUnit1 * 32.f) / smallest_subdivision1); - S32 label_sub_div_offset_2 = ll_round(fmod(dist_grid_axis - grid_offset2, mScaleSnapUnit2 * 32.f) / smallest_subdivision2); - - for (S32 i = start_tick; i <= stop_tick; i++) - { - F32 tick_scale = 1.f; - F32 alpha = grid_alpha * (1.f - (0.5f * ((F32)llabs(i) / (F32)num_ticks_per_side1))); - LLVector3 tick_pos = mScaleCenter + (mScaleDir * (grid_multiple1 + i) * smallest_subdivision1); - - for (F32 division_level = sGridMaxSubdivisionLevel; division_level >= sGridMinSubdivisionLevel; division_level /= 2.f) - { - if (fmodf((F32)(i + label_sub_div_offset_1), division_level) == 0.f) - { - break; - } - tick_scale *= 0.7f; - } - - if (fmodf((F32)(i + label_sub_div_offset_1), (sGridMaxSubdivisionLevel / llmin(sGridMaxSubdivisionLevel, getSubdivisionLevel(tick_pos, mScaleDir, mScaleSnapUnit1, tick_label_spacing)))) == 0.f) - { - LLVector3 text_origin = tick_pos + (mSnapGuideDir1 * mSnapRegimeOffset * (1.f + tick_scale)); - - EGridMode grid_mode = LLSelectMgr::getInstance()->getGridMode(); - F32 tick_value; - if (grid_mode == GRID_MODE_WORLD) - { - tick_value = (grid_multiple1 + i) / (sGridMaxSubdivisionLevel / grid_resolution); - } - else - { - tick_value = (grid_multiple1 + i) / (2.f * sGridMaxSubdivisionLevel); - } - - F32 text_highlight = 0.8f; - - // Highlight this text if the tick value matches the snapped to value, and if either the second set of ticks isn't going to be shown or cursor is in the first snap regime. - if (is_approx_equal(tick_value, mScaleSnappedValue) && (mScaleSnapUnit2 == mScaleSnapUnit1 || (mSnapRegime & SNAP_REGIME_UPPER))) - { - text_highlight = 1.f; - } - - renderTickValue(text_origin, tick_value, grid_mode == GRID_MODE_WORLD ? std::string("m") : std::string("x"), LLColor4(text_highlight, text_highlight, text_highlight, alpha)); - } - } - - // label ticks on opposite side, only can happen in scaling modes that effect more than one axis and when the object's axis don't have the same scale. A differing scale indicates both conditions. - if (mScaleSnapUnit2 != mScaleSnapUnit1) - { - start_tick = -(llmin(ticks_from_scale_center_2, num_ticks_per_side2)); - stop_tick = llmin(max_ticks2, num_ticks_per_side2); - for (S32 i = start_tick; i <= stop_tick; i++) - { - F32 tick_scale = 1.f; - F32 alpha = grid_alpha * (1.f - (0.5f * ((F32)llabs(i) / (F32)num_ticks_per_side2))); - LLVector3 tick_pos = mScaleCenter + (mScaleDir * (grid_multiple2 + i) * smallest_subdivision2); - - for (F32 division_level = sGridMaxSubdivisionLevel; division_level >= sGridMinSubdivisionLevel; division_level /= 2.f) - { - if (fmodf((F32)(i + label_sub_div_offset_2), division_level) == 0.f) - { - break; - } - tick_scale *= 0.7f; - } - - if (fmodf((F32)(i + label_sub_div_offset_2), (sGridMaxSubdivisionLevel / llmin(sGridMaxSubdivisionLevel, getSubdivisionLevel(tick_pos, mScaleDir, mScaleSnapUnit2, tick_label_spacing)))) == 0.f) - { - LLVector3 text_origin = tick_pos + (mSnapGuideDir2 * mSnapRegimeOffset * (1.f + tick_scale)); - - EGridMode grid_mode = LLSelectMgr::getInstance()->getGridMode(); - F32 tick_value; - if (grid_mode == GRID_MODE_WORLD) - { - tick_value = (grid_multiple2 + i) / (sGridMaxSubdivisionLevel / grid_resolution); - } - else - { - tick_value = (grid_multiple2 + i) / (2.f * sGridMaxSubdivisionLevel); - } - - F32 text_highlight = 0.8f; - - if (is_approx_equal(tick_value, mScaleSnappedValue) && (mSnapRegime & SNAP_REGIME_LOWER)) - { - text_highlight = 1.f; - } - - renderTickValue(text_origin, tick_value, grid_mode == GRID_MODE_WORLD ? std::string("m") : std::string("x"), LLColor4(text_highlight, text_highlight, text_highlight, alpha)); - } - } - } - - - // render help text - if (mObjectSelection->getSelectType() != SELECT_TYPE_HUD) - { - if (mHelpTextTimer.getElapsedTimeF32() < sHelpTextVisibleTime + sHelpTextFadeTime && sNumTimesHelpTextShown < sMaxTimesShowHelpText) - { - LLVector3 selection_center_start = LLSelectMgr::getInstance()->getSavedBBoxOfSelection().getCenterAgent(); - - LLVector3 offset_dir; - if (mSnapGuideDir1 * LLViewerCamera::getInstance()->getAtAxis() > mSnapGuideDir2 * LLViewerCamera::getInstance()->getAtAxis()) - { - offset_dir = mSnapGuideDir2; - } - else - { - offset_dir = mSnapGuideDir1; - } - - LLVector3 help_text_pos = selection_center_start + (mSnapRegimeOffset * 5.f * offset_dir); - const LLFontGL* big_fontp = LLFontGL::getFontSansSerif(); - - std::string help_text = LLTrans::getString("manip_hint1"); - LLColor4 help_text_color = LLColor4::white; - help_text_color.mV[VALPHA] = clamp_rescale(mHelpTextTimer.getElapsedTimeF32(), sHelpTextVisibleTime, sHelpTextVisibleTime + sHelpTextFadeTime, grid_alpha, 0.f); - hud_render_utf8text(help_text, help_text_pos, *big_fontp, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, -0.5f * big_fontp->getWidthF32(help_text), 3.f, help_text_color, false); - help_text = LLTrans::getString("manip_hint2"); - help_text_pos -= LLViewerCamera::getInstance()->getUpAxis() * mSnapRegimeOffset * 0.4f; - hud_render_utf8text(help_text, help_text_pos, *big_fontp, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, -0.5f * big_fontp->getWidthF32(help_text), 3.f, help_text_color, false); - } - } - } -} - -// Returns unit vector in direction of part of an origin-centered cube -LLVector3 LLManipScale::partToUnitVector( S32 part ) const -{ - if ( (LL_FACE_MIN <= part) && (part <= LL_FACE_MAX) ) - { - return faceToUnitVector( part ); - } - else if ( (LL_CORNER_MIN <= part) && (part <= LL_CORNER_MAX) ) - { - return cornerToUnitVector( part ); - } - else if ( (LL_EDGE_MIN <= part) && (part <= LL_EDGE_MAX ) ) - { - return edgeToUnitVector( part ); - } - return LLVector3(); -} - - -// Returns unit vector in direction of face of an origin-centered cube -LLVector3 LLManipScale::faceToUnitVector( S32 part ) const -{ - llassert( (LL_FACE_MIN <= part) && (part <= LL_FACE_MAX) ); - LLVector3 vec; - switch( part ) - { - case LL_FACE_POSX: - vec.set( 1.f, 0.f, 0.f ); - break; - case LL_FACE_NEGX: - vec.set( -1.f, 0.f, 0.f ); - break; - case LL_FACE_POSY: - vec.set( 0.f, 1.f, 0.f ); - break; - case LL_FACE_NEGY: - vec.set( 0.f, -1.f, 0.f ); - break; - case LL_FACE_POSZ: - vec.set( 0.f, 0.f, 1.f ); - break; - case LL_FACE_NEGZ: - vec.set( 0.f, 0.f, -1.f ); - break; - default: - vec.clear(); - } - - return vec; -} - - -// Returns unit vector in direction of corner of an origin-centered cube -LLVector3 LLManipScale::cornerToUnitVector( S32 part ) const -{ - llassert( (LL_CORNER_MIN <= part) && (part <= LL_CORNER_MAX) ); - LLVector3 vec; - switch(part) - { - case LL_CORNER_NNN: - vec.set(-OO_SQRT3, -OO_SQRT3, -OO_SQRT3); - break; - case LL_CORNER_NNP: - vec.set(-OO_SQRT3, -OO_SQRT3, OO_SQRT3); - break; - case LL_CORNER_NPN: - vec.set(-OO_SQRT3, OO_SQRT3, -OO_SQRT3); - break; - case LL_CORNER_NPP: - vec.set(-OO_SQRT3, OO_SQRT3, OO_SQRT3); - break; - case LL_CORNER_PNN: - vec.set(OO_SQRT3, -OO_SQRT3, -OO_SQRT3); - break; - case LL_CORNER_PNP: - vec.set(OO_SQRT3, -OO_SQRT3, OO_SQRT3); - break; - case LL_CORNER_PPN: - vec.set(OO_SQRT3, OO_SQRT3, -OO_SQRT3); - break; - case LL_CORNER_PPP: - vec.set(OO_SQRT3, OO_SQRT3, OO_SQRT3); - break; - default: - vec.clear(); - } - - return vec; -} - -// Returns unit vector in direction of edge of an origin-centered cube -LLVector3 LLManipScale::edgeToUnitVector( S32 part ) const -{ - llassert( (LL_EDGE_MIN <= part) && (part <= LL_EDGE_MAX) ); - part -= LL_EDGE_MIN; - S32 rotation = part >> 2; // Edge between which faces: 0 => XY, 1 => YZ, 2 => ZX - LLVector3 v; - v.mV[rotation] = (part & 1) ? F_SQRT2 : -F_SQRT2; - v.mV[(rotation+1) % 3] = (part & 2) ? F_SQRT2 : -F_SQRT2; - // v.mV[(rotation+2) % 3] defaults to 0. - return v; -} - -// Non-linear scale of origin-centered unit cube to non-origin-centered, non-symetrical bounding box -LLVector3 LLManipScale::unitVectorToLocalBBoxExtent( const LLVector3& v, const LLBBox& bbox ) const -{ - const LLVector3& min = bbox.getMinLocal(); - const LLVector3& max = bbox.getMaxLocal(); - LLVector3 ctr = bbox.getCenterLocal(); - - return LLVector3( - v.mV[0] ? (v.mV[0]>0 ? max.mV[0] : min.mV[0] ) : ctr.mV[0], - v.mV[1] ? (v.mV[1]>0 ? max.mV[1] : min.mV[1] ) : ctr.mV[1], - v.mV[2] ? (v.mV[2]>0 ? max.mV[2] : min.mV[2] ) : ctr.mV[2] ); -} - -// returns max allowable scale along a given stretch axis -F32 LLManipScale::partToMaxScale( S32 part, const LLBBox &bbox ) const -{ - F32 max_scale_factor = 0.f; - LLVector3 bbox_extents = unitVectorToLocalBBoxExtent( partToUnitVector( part ), bbox ); - bbox_extents.abs(); - F32 max_extent = 0.f; - for (U32 i = VX; i <= VZ; i++) - { - if (bbox_extents.mV[i] > max_extent) - { - max_extent = bbox_extents.mV[i]; - } - } - max_scale_factor = bbox_extents.length() * get_default_max_prim_scale() / max_extent; - - if (getUniform()) - { - max_scale_factor *= 0.5f; - } - - return max_scale_factor; -} - -// returns min allowable scale along a given stretch axis -F32 LLManipScale::partToMinScale( S32 part, const LLBBox &bbox ) const -{ - LLVector3 bbox_extents = unitVectorToLocalBBoxExtent( partToUnitVector( part ), bbox ); - bbox_extents.abs(); - F32 min_extent = get_default_max_prim_scale(); - for (U32 i = VX; i <= VZ; i++) - { - if (bbox_extents.mV[i] > 0.f && bbox_extents.mV[i] < min_extent) - { - min_extent = bbox_extents.mV[i]; - } - } - F32 min_scale_factor = bbox_extents.length() * MIN_PRIM_SCALE / min_extent; - - if (getUniform()) - { - min_scale_factor *= 0.5f; - } - - return min_scale_factor; -} - -// Returns the axis aligned unit vector closest to v. -LLVector3 LLManipScale::nearestAxis( const LLVector3& v ) const -{ - // Note: yes, this is a slow but easy implementation - // assumes v is normalized - - F32 coords[][3] = - { - { 1.f, 0.f, 0.f }, - { 0.f, 1.f, 0.f }, - { 0.f, 0.f, 1.f }, - {-1.f, 0.f, 0.f }, - { 0.f,-1.f, 0.f }, - { 0.f, 0.f,-1.f } - }; - - F32 cosine[6]; - cosine[0] = v * LLVector3( coords[0] ); - cosine[1] = v * LLVector3( coords[1] ); - cosine[2] = v * LLVector3( coords[2] ); - cosine[3] = -cosine[0]; - cosine[4] = -cosine[1]; - cosine[5] = -cosine[2]; - - F32 greatest_cos = cosine[0]; - S32 greatest_index = 0; - for( S32 i=1; i<6; i++ ) - { - if( greatest_cos < cosine[i] ) - { - greatest_cos = cosine[i]; - greatest_index = i; - } - } - - return LLVector3( coords[greatest_index] ); -} - -// virtual -bool LLManipScale::canAffectSelection() -{ - // An selection is scalable if you are allowed to both edit and move - // everything in it, and it does not have any sitting agents - bool can_scale = mObjectSelection->getObjectCount() != 0; - if (can_scale) - { - struct f : public LLSelectedObjectFunctor - { - virtual bool apply(LLViewerObject* objectp) - { - LLViewerObject *root_object = (objectp == NULL) ? NULL : objectp->getRootEdit(); - return objectp->permModify() && objectp->permMove() && !objectp->isPermanentEnforced() && - (root_object == NULL || (!root_object->isPermanentEnforced() && !root_object->isSeat())) && - !objectp->isSeat(); - } - } func; - can_scale = mObjectSelection->applyToObjects(&func); - } - return can_scale; -} +/** + * @file llmanipscale.cpp + * @brief LLManipScale class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llmanipscale.h" + +// library includes +#include "llmath.h" +#include "v3math.h" +#include "llquaternion.h" +#include "llgl.h" +#include "llrender.h" +#include "v4color.h" +#include "llprimitive.h" + +// viewer includes +#include "llagent.h" +#include "llagentcamera.h" +#include "llbbox.h" +#include "llbox.h" +#include "llviewercontrol.h" +#include "llcriticaldamp.h" +#include "lldrawable.h" +#include "llfloatertools.h" +#include "llglheaders.h" +#include "llselectmgr.h" +#include "llstatusbar.h" +#include "llui.h" +#include "llviewercamera.h" +#include "llviewerobject.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" +#include "llhudrender.h" +#include "llworld.h" +#include "v2math.h" +#include "llvoavatar.h" +#include "llmeshrepository.h" +#include "lltrans.h" + +const F32 MAX_MANIP_SELECT_DISTANCE_SQUARED = 11.f * 11.f; +const F32 SNAP_GUIDE_SCREEN_OFFSET = 0.05f; +const F32 SNAP_GUIDE_SCREEN_LENGTH = 0.7f; +const F32 SELECTED_MANIPULATOR_SCALE = 1.2f; +const F32 MANIPULATOR_SCALE_HALF_LIFE = 0.07f; + +const LLManip::EManipPart MANIPULATOR_IDS[LLManipScale::NUM_MANIPULATORS] = +{ + LLManip::LL_CORNER_NNN, + LLManip::LL_CORNER_NNP, + LLManip::LL_CORNER_NPN, + LLManip::LL_CORNER_NPP, + LLManip::LL_CORNER_PNN, + LLManip::LL_CORNER_PNP, + LLManip::LL_CORNER_PPN, + LLManip::LL_CORNER_PPP, + LLManip::LL_FACE_POSZ, + LLManip::LL_FACE_POSX, + LLManip::LL_FACE_POSY, + LLManip::LL_FACE_NEGX, + LLManip::LL_FACE_NEGY, + LLManip::LL_FACE_NEGZ +}; + +F32 get_default_max_prim_scale(bool is_flora) +{ + // a bit of a hack, but if it's foilage, we don't want to use the + // new larger scale which would result in giant trees and grass + if (gMeshRepo.meshRezEnabled() && + !is_flora) + { + return DEFAULT_MAX_PRIM_SCALE; + } + else + { + return DEFAULT_MAX_PRIM_SCALE_NO_MESH; + } +} + +// static +void LLManipScale::setUniform(bool b) +{ + gSavedSettings.setBOOL("ScaleUniform", b); +} + +// static +void LLManipScale::setShowAxes(bool b) +{ + gSavedSettings.setBOOL("ScaleShowAxes", b); +} + +// static +void LLManipScale::setStretchTextures(bool b) +{ + gSavedSettings.setBOOL("ScaleStretchTextures", b); +} + +// static +bool LLManipScale::getUniform() +{ + return gSavedSettings.getBOOL("ScaleUniform"); +} + +// static +bool LLManipScale::getShowAxes() +{ + return gSavedSettings.getBOOL("ScaleShowAxes"); +} + +// static +bool LLManipScale::getStretchTextures() +{ + return gSavedSettings.getBOOL("ScaleStretchTextures"); +} + +inline void LLManipScale::conditionalHighlight( U32 part, const LLColor4* highlight, const LLColor4* normal ) +{ + LLColor4 default_highlight( 1.f, 1.f, 1.f, 1.f ); + LLColor4 default_normal( 0.7f, 0.7f, 0.7f, 0.6f ); + LLColor4 invisible(0.f, 0.f, 0.f, 0.f); + + for (S32 i = 0; i < NUM_MANIPULATORS; i++) + { + if((U32)MANIPULATOR_IDS[i] == part) + { + mScaledBoxHandleSize = mManipulatorScales[i] * mBoxHandleSize[i]; + break; + } + } + + if (mManipPart != (S32)LL_NO_PART && mManipPart != (S32)part) + { + gGL.color4fv( invisible.mV ); + } + else if( mHighlightedPart == (S32)part ) + { + gGL.color4fv( highlight ? highlight->mV : default_highlight.mV ); + } + else + { + gGL.color4fv( normal ? normal->mV : default_normal.mV ); + } +} + +void LLManipScale::handleSelect() +{ + LLBBox bbox = LLSelectMgr::getInstance()->getBBoxOfSelection(); + updateSnapGuides(bbox); + LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); + if (gFloaterTools) + { + gFloaterTools->setStatusText("scale"); + } + LLManip::handleSelect(); +} + +LLManipScale::LLManipScale( LLToolComposite* composite ) + : + LLManip( std::string("Scale"), composite ), + mScaledBoxHandleSize( 1.f ), + mLastMouseX( -1 ), + mLastMouseY( -1 ), + mSendUpdateOnMouseUp( false ), + mLastUpdateFlags( 0 ), + mScaleSnapUnit1(1.f), + mScaleSnapUnit2(1.f), + mSnapRegimeOffset(0.f), + mTickPixelSpacing1(0.f), + mTickPixelSpacing2(0.f), + mSnapGuideLength(0.f), + mSnapRegime(SNAP_REGIME_NONE), + mScaleSnappedValue(0.f) +{ + for (S32 i = 0; i < NUM_MANIPULATORS; i++) + { + mManipulatorScales[i] = 1.f; + mBoxHandleSize[i] = 1.f; + } +} + +LLManipScale::~LLManipScale() +{ + for_each(mProjectedManipulators.begin(), mProjectedManipulators.end(), DeletePointer()); +} + +void LLManipScale::render() +{ + LLGLSUIDefault gls_ui; + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + LLGLDepthTest gls_depth(GL_TRUE); + LLGLEnable gl_blend(GL_BLEND); + LLBBox bbox = LLSelectMgr::getInstance()->getBBoxOfSelection(); + + if( canAffectSelection() ) + { + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) + { + F32 zoom = gAgentCamera.mHUDCurZoom; + gGL.scalef(zoom, zoom, zoom); + } + + //////////////////////////////////////////////////////////////////////// + // Calculate size of drag handles + + const F32 BOX_HANDLE_BASE_SIZE = 50.0f; // box size in pixels = BOX_HANDLE_BASE_SIZE * BOX_HANDLE_BASE_FACTOR + const F32 BOX_HANDLE_BASE_FACTOR = 0.2f; + + //Assume that UI scale factor is equivalent for X and Y axis + F32 ui_scale_factor = LLUI::getScaleFactor().mV[VX]; + + if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) + { + for (S32 i = 0; i < NUM_MANIPULATORS; i++) + { + mBoxHandleSize[i] = BOX_HANDLE_BASE_SIZE * BOX_HANDLE_BASE_FACTOR / (F32) LLViewerCamera::getInstance()->getViewHeightInPixels(); + mBoxHandleSize[i] /= gAgentCamera.mHUDCurZoom; + mBoxHandleSize[i] *= ui_scale_factor; + } + } + else + { + for (S32 i = 0; i < NUM_MANIPULATORS; i++) + { + LLVector3 manipulator_pos = bbox.localToAgent(unitVectorToLocalBBoxExtent(partToUnitVector(MANIPULATOR_IDS[i]), bbox)); + F32 range_squared = dist_vec_squared(gAgentCamera.getCameraPositionAgent(), manipulator_pos); + F32 range_from_agent_squared = dist_vec_squared(gAgent.getPositionAgent(), manipulator_pos); + + // Don't draw manip if object too far away + if (gSavedSettings.getBOOL("LimitSelectDistance")) + { + F32 max_select_distance = gSavedSettings.getF32("MaxSelectDistance"); + if (range_from_agent_squared > max_select_distance * max_select_distance) + { + return; + } + } + + if (range_squared > 0.001f * 0.001f) + { + // range != zero + F32 fraction_of_fov = BOX_HANDLE_BASE_SIZE / (F32) LLViewerCamera::getInstance()->getViewHeightInPixels(); + F32 apparent_angle = fraction_of_fov * LLViewerCamera::getInstance()->getView(); // radians + mBoxHandleSize[i] = (F32) sqrtf(range_squared) * tan(apparent_angle) * BOX_HANDLE_BASE_FACTOR; + } + else + { + // range == zero + mBoxHandleSize[i] = BOX_HANDLE_BASE_FACTOR; + } + mBoxHandleSize[i] *= ui_scale_factor; + } + } + + //////////////////////////////////////////////////////////////////////// + // Draw bounding box + + LLVector3 pos_agent = bbox.getPositionAgent(); + LLQuaternion rot = bbox.getRotation(); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + { + gGL.translatef(pos_agent.mV[VX], pos_agent.mV[VY], pos_agent.mV[VZ]); + + F32 angle_radians, x, y, z; + rot.getAngleAxis(&angle_radians, &x, &y, &z); + gGL.rotatef(angle_radians * RAD_TO_DEG, x, y, z); + + + { + LLGLEnable poly_offset(GL_POLYGON_OFFSET_FILL); + glPolygonOffset( -2.f, -2.f); + + renderCorners( bbox ); + renderFaces( bbox ); + + if (mManipPart != LL_NO_PART) + { + renderGuidelinesPart( bbox ); + } + + glPolygonOffset( 0.f, 0.f); + } + } + gGL.popMatrix(); + + if (mManipPart != LL_NO_PART) + { + renderSnapGuides(bbox); + } + gGL.popMatrix(); + + renderXYZ(bbox.getExtentLocal()); + } +} + +bool LLManipScale::handleMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + if(mHighlightedPart != LL_NO_PART) + { + handled = handleMouseDownOnPart( x, y, mask ); + } + + return handled; +} + +// Assumes that one of the arrows on an object was hit. +bool LLManipScale::handleMouseDownOnPart( S32 x, S32 y, MASK mask ) +{ + bool can_scale = canAffectSelection(); + if (!can_scale) + { + return false; + } + + highlightManipulators(x, y); + S32 hit_part = mHighlightedPart; + + LLSelectMgr::getInstance()->enableSilhouette(false); + mManipPart = (EManipPart)hit_part; + + LLBBox bbox = LLSelectMgr::getInstance()->getBBoxOfSelection(); + LLVector3 box_center_agent = bbox.getCenterAgent(); + LLVector3 box_corner_agent = bbox.localToAgent( unitVectorToLocalBBoxExtent( partToUnitVector( mManipPart ), bbox ) ); + + updateSnapGuides(bbox); + + mFirstClickX = x; + mFirstClickY = y; + mIsFirstClick = true; + + mDragStartPointGlobal = gAgent.getPosGlobalFromAgent(box_corner_agent); + mDragStartCenterGlobal = gAgent.getPosGlobalFromAgent(box_center_agent); + LLVector3 far_corner_agent = bbox.localToAgent( unitVectorToLocalBBoxExtent( -1.f * partToUnitVector( mManipPart ), bbox ) ); + mDragFarHitGlobal = gAgent.getPosGlobalFromAgent(far_corner_agent); + mDragPointGlobal = mDragStartPointGlobal; + + // we just started a drag, so save initial object positions, orientations, and scales + LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_SCALE); + // Route future Mouse messages here preemptively. (Release on mouse up.) + setMouseCapture( true ); + + mHelpTextTimer.reset(); + sNumTimesHelpTextShown++; + return true; +} + + +bool LLManipScale::handleMouseUp(S32 x, S32 y, MASK mask) +{ + // first, perform normal processing in case this was a quick-click + handleHover(x, y, mask); + + if( hasMouseCapture() ) + { + if( (LL_FACE_MIN <= (S32)mManipPart) + && ((S32)mManipPart <= LL_FACE_MAX) ) + { + sendUpdates(true,true,false); + } + else + if( (LL_CORNER_MIN <= (S32)mManipPart) + && ((S32)mManipPart <= LL_CORNER_MAX) ) + { + sendUpdates(true,true,true); + } + + //send texture update + LLSelectMgr::getInstance()->adjustTexturesByScale(true, getStretchTextures()); + + LLSelectMgr::getInstance()->enableSilhouette(true); + mManipPart = LL_NO_PART; + + // Might have missed last update due to UPDATE_DELAY timing + LLSelectMgr::getInstance()->sendMultipleUpdate( mLastUpdateFlags ); + + //gAgent.setObjectTracking(gSavedSettings.getBOOL("TrackFocusObject")); + LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); + } + return LLManip::handleMouseUp(x, y, mask); +} + + +bool LLManipScale::handleHover(S32 x, S32 y, MASK mask) +{ + if( hasMouseCapture() ) + { + if( mObjectSelection->isEmpty() ) + { + // Somehow the object got deselected while we were dragging it. + setMouseCapture( false ); + } + else + { + if((mFirstClickX != x) || (mFirstClickY != y)) + { + mIsFirstClick = false; + } + + if(!mIsFirstClick) + { + drag( x, y ); + } + } + LL_DEBUGS("UserInput") << "hover handled by LLManipScale (active)" << LL_ENDL; + } + else + { + mSnapRegime = SNAP_REGIME_NONE; + // not dragging... + highlightManipulators(x, y); + } + + // Patch up textures, if possible. + LLSelectMgr::getInstance()->adjustTexturesByScale(false, getStretchTextures()); + + gViewerWindow->setCursor(UI_CURSOR_TOOLSCALE); + return true; +} + +void LLManipScale::highlightManipulators(S32 x, S32 y) +{ + mHighlightedPart = LL_NO_PART; + + // If we have something selected, try to hit its manipulator handles. + // Don't do this with nothing selected, as it kills the framerate. + LLBBox bbox = LLSelectMgr::getInstance()->getBBoxOfSelection(); + + if( canAffectSelection() ) + { + LLMatrix4 transform; + if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) + { + LLVector4 translation(bbox.getPositionAgent()); + transform.initRotTrans(bbox.getRotation(), translation); + LLMatrix4 cfr(OGL_TO_CFR_ROTATION); + transform *= cfr; + LLMatrix4 window_scale; + F32 zoom_level = 2.f * gAgentCamera.mHUDCurZoom; + window_scale.initAll(LLVector3(zoom_level / LLViewerCamera::getInstance()->getAspect(), zoom_level, 0.f), + LLQuaternion::DEFAULT, + LLVector3::zero); + transform *= window_scale; + } + else + { + LLMatrix4 projMatrix = LLViewerCamera::getInstance()->getProjection(); + LLMatrix4 modelView = LLViewerCamera::getInstance()->getModelview(); + transform.initAll(LLVector3(1.f, 1.f, 1.f), bbox.getRotation(), bbox.getPositionAgent()); + + transform *= modelView; + transform *= projMatrix; + } + + LLVector3 min = bbox.getMinLocal(); + LLVector3 max = bbox.getMaxLocal(); + LLVector3 ctr = bbox.getCenterLocal(); + + S32 numManips = 0; + // corners + mManipulatorVertices[numManips++] = LLVector4(min.mV[VX], min.mV[VY], min.mV[VZ], 1.f); + mManipulatorVertices[numManips++] = LLVector4(min.mV[VX], min.mV[VY], max.mV[VZ], 1.f); + mManipulatorVertices[numManips++] = LLVector4(min.mV[VX], max.mV[VY], min.mV[VZ], 1.f); + mManipulatorVertices[numManips++] = LLVector4(min.mV[VX], max.mV[VY], max.mV[VZ], 1.f); + mManipulatorVertices[numManips++] = LLVector4(max.mV[VX], min.mV[VY], min.mV[VZ], 1.f); + mManipulatorVertices[numManips++] = LLVector4(max.mV[VX], min.mV[VY], max.mV[VZ], 1.f); + mManipulatorVertices[numManips++] = LLVector4(max.mV[VX], max.mV[VY], min.mV[VZ], 1.f); + mManipulatorVertices[numManips++] = LLVector4(max.mV[VX], max.mV[VY], max.mV[VZ], 1.f); + + // 1-D highlights are applicable iff one object is selected + if( mObjectSelection->getObjectCount() == 1 ) + { + // face centers + mManipulatorVertices[numManips++] = LLVector4(ctr.mV[VX], ctr.mV[VY], max.mV[VZ], 1.f); + mManipulatorVertices[numManips++] = LLVector4(max.mV[VX], ctr.mV[VY], ctr.mV[VZ], 1.f); + mManipulatorVertices[numManips++] = LLVector4(ctr.mV[VX], max.mV[VY], ctr.mV[VZ], 1.f); + mManipulatorVertices[numManips++] = LLVector4(min.mV[VX], ctr.mV[VY], ctr.mV[VZ], 1.f); + mManipulatorVertices[numManips++] = LLVector4(ctr.mV[VX], min.mV[VY], ctr.mV[VZ], 1.f); + mManipulatorVertices[numManips++] = LLVector4(ctr.mV[VX], ctr.mV[VY], min.mV[VZ], 1.f); + } + + for_each(mProjectedManipulators.begin(), mProjectedManipulators.end(), DeletePointer()); + mProjectedManipulators.clear(); + + for (S32 i = 0; i < numManips; i++) + { + LLVector4 projectedVertex = mManipulatorVertices[i] * transform; + projectedVertex = projectedVertex * (1.f / projectedVertex.mV[VW]); + + ManipulatorHandle* projManipulator = new ManipulatorHandle(LLVector3(projectedVertex.mV[VX], projectedVertex.mV[VY], + projectedVertex.mV[VZ]), MANIPULATOR_IDS[i], (i < 7) ? SCALE_MANIP_CORNER : SCALE_MANIP_FACE); + mProjectedManipulators.insert(projManipulator); + } + + LLRect world_view_rect = gViewerWindow->getWorldViewRectScaled(); + F32 half_width = (F32)world_view_rect.getWidth() / 2.f; + F32 half_height = (F32)world_view_rect.getHeight() / 2.f; + LLVector2 manip2d; + LLVector2 mousePos((F32)x - half_width, (F32)y - half_height); + LLVector2 delta; + + mHighlightedPart = LL_NO_PART; + + for (manipulator_list_t::iterator iter = mProjectedManipulators.begin(); + iter != mProjectedManipulators.end(); ++iter) + { + ManipulatorHandle* manipulator = *iter; + { + manip2d.set(manipulator->mPosition.mV[VX] * half_width, manipulator->mPosition.mV[VY] * half_height); + + delta = manip2d - mousePos; + if (delta.lengthSquared() < MAX_MANIP_SELECT_DISTANCE_SQUARED) + { + mHighlightedPart = manipulator->mManipID; + + //LL_INFOS() << "Tried: " << mHighlightedPart << LL_ENDL; + break; + } + } + } + } + + for (S32 i = 0; i < NUM_MANIPULATORS; i++) + { + if (mHighlightedPart == MANIPULATOR_IDS[i]) + { + mManipulatorScales[i] = lerp(mManipulatorScales[i], SELECTED_MANIPULATOR_SCALE, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE)); + } + else + { + mManipulatorScales[i] = lerp(mManipulatorScales[i], 1.f, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE)); + } + } + + LL_DEBUGS("UserInput") << "hover handled by LLManipScale (inactive)" << LL_ENDL; +} + + +void LLManipScale::renderFaces( const LLBBox& bbox ) +{ + // Don't bother to render the drag handles for 1-D scaling if + // more than one object is selected or if it is an attachment + if ( mObjectSelection->getObjectCount() > 1 ) + { + return; + } + + // This is a flattened representation of the box as render here + // . + // (+++) (++-) /|\t + // +------------+ | (texture coordinates) + // | | | + // | 1 | (*) --->s + // | +X | + // | | + // (+++) (+-+)| |(+--) (++-) (+++) + // +------------+------------+------------+------------+ + // |0 3|3 7|7 4|4 0| + // | 0 | 4 | 5 | 2 | + // | +Z | -Y | -Z | +Y | + // | | | | | + // |1 2|2 6|6 5|5 1| + // +------------+------------+------------+------------+ + // (-++) (--+)| |(---) (-+-) (-++) + // | 3 | + // | -X | + // | | + // | | + // +------------+ + // (-++) (-+-) + + LLColor4 highlight_color( 1.f, 1.f, 1.f, 0.5f); + LLColor4 normal_color( 1.f, 1.f, 1.f, 0.3f); + + LLColor4 x_highlight_color( 1.f, 0.2f, 0.2f, 1.0f); + LLColor4 x_normal_color( 0.6f, 0.f, 0.f, 0.4f); + + LLColor4 y_highlight_color( 0.2f, 1.f, 0.2f, 1.0f); + LLColor4 y_normal_color( 0.f, 0.6f, 0.f, 0.4f); + + LLColor4 z_highlight_color( 0.2f, 0.2f, 1.f, 1.0f); + LLColor4 z_normal_color( 0.f, 0.f, 0.6f, 0.4f); + + LLColor4 default_normal_color( 0.7f, 0.7f, 0.7f, 0.15f ); + + const LLVector3& min = bbox.getMinLocal(); + const LLVector3& max = bbox.getMaxLocal(); + LLVector3 ctr = bbox.getCenterLocal(); + + if (mManipPart == LL_NO_PART) + { + gGL.color4fv( default_normal_color.mV ); + LLGLDepthTest gls_depth(GL_FALSE); + gGL.begin(LLRender::QUADS); + { + // Face 0 + gGL.vertex3f(min.mV[VX], max.mV[VY], max.mV[VZ]); + gGL.vertex3f(min.mV[VX], min.mV[VY], max.mV[VZ]); + gGL.vertex3f(max.mV[VX], min.mV[VY], max.mV[VZ]); + gGL.vertex3f(max.mV[VX], max.mV[VY], max.mV[VZ]); + + // Face 1 + gGL.vertex3f(max.mV[VX], min.mV[VY], max.mV[VZ]); + gGL.vertex3f(max.mV[VX], min.mV[VY], min.mV[VZ]); + gGL.vertex3f(max.mV[VX], max.mV[VY], min.mV[VZ]); + gGL.vertex3f(max.mV[VX], max.mV[VY], max.mV[VZ]); + + // Face 2 + gGL.vertex3f(min.mV[VX], max.mV[VY], min.mV[VZ]); + gGL.vertex3f(min.mV[VX], max.mV[VY], max.mV[VZ]); + gGL.vertex3f(max.mV[VX], max.mV[VY], max.mV[VZ]); + gGL.vertex3f(max.mV[VX], max.mV[VY], min.mV[VZ]); + + // Face 3 + gGL.vertex3f(min.mV[VX], max.mV[VY], max.mV[VZ]); + gGL.vertex3f(min.mV[VX], max.mV[VY], min.mV[VZ]); + gGL.vertex3f(min.mV[VX], min.mV[VY], min.mV[VZ]); + gGL.vertex3f(min.mV[VX], min.mV[VY], max.mV[VZ]); + + // Face 4 + gGL.vertex3f(min.mV[VX], min.mV[VY], max.mV[VZ]); + gGL.vertex3f(min.mV[VX], min.mV[VY], min.mV[VZ]); + gGL.vertex3f(max.mV[VX], min.mV[VY], min.mV[VZ]); + gGL.vertex3f(max.mV[VX], min.mV[VY], max.mV[VZ]); + + // Face 5 + gGL.vertex3f(min.mV[VX], min.mV[VY], min.mV[VZ]); + gGL.vertex3f(min.mV[VX], max.mV[VY], min.mV[VZ]); + gGL.vertex3f(max.mV[VX], max.mV[VY], min.mV[VZ]); + gGL.vertex3f(max.mV[VX], min.mV[VY], min.mV[VZ]); + } + gGL.end(); + } + + // Find nearest vertex + LLVector3 orientWRTHead = bbox.agentToLocalBasis( bbox.getCenterAgent() - gAgentCamera.getCameraPositionAgent() ); + U32 nearest = + (orientWRTHead.mV[0] < 0.0f ? 1 : 0) + + (orientWRTHead.mV[1] < 0.0f ? 2 : 0) + + (orientWRTHead.mV[2] < 0.0f ? 4 : 0); + + // opposite faces on Linden cubes: + // 0 & 5 + // 1 & 3 + // 2 & 4 + + // Table of order to draw faces, based on nearest vertex + static U32 face_list[8][6] = { + { 2,0,1, 4,5,3 }, // v6 F201 F453 + { 2,0,3, 4,5,1 }, // v7 F203 F451 + { 4,0,1, 2,5,3 }, // v5 F401 F253 + { 4,0,3, 2,5,1 }, // v4 F403 F251 + { 2,5,1, 4,0,3 }, // v2 F251 F403 + { 2,5,3, 4,0,1 }, // v3 F253 F401 + { 4,5,1, 2,0,3 }, // v1 F451 F203 + { 4,5,3, 2,0,1 } // v0 F453 F201 + }; + + { + LLGLDepthTest gls_depth(GL_FALSE); + + for (S32 i = 0; i < 6; i++) + { + U32 face = face_list[nearest][i]; + switch( face ) + { + case 0: + conditionalHighlight( LL_FACE_POSZ, &z_highlight_color, &z_normal_color ); + renderAxisHandle( 8, ctr, LLVector3( ctr.mV[VX], ctr.mV[VY], max.mV[VZ] ) ); + break; + + case 1: + conditionalHighlight( LL_FACE_POSX, &x_highlight_color, &x_normal_color ); + renderAxisHandle( 9, ctr, LLVector3( max.mV[VX], ctr.mV[VY], ctr.mV[VZ] ) ); + break; + + case 2: + conditionalHighlight( LL_FACE_POSY, &y_highlight_color, &y_normal_color ); + renderAxisHandle( 10, ctr, LLVector3( ctr.mV[VX], max.mV[VY], ctr.mV[VZ] ) ); + break; + + case 3: + conditionalHighlight( LL_FACE_NEGX, &x_highlight_color, &x_normal_color ); + renderAxisHandle( 11, ctr, LLVector3( min.mV[VX], ctr.mV[VY], ctr.mV[VZ] ) ); + break; + + case 4: + conditionalHighlight( LL_FACE_NEGY, &y_highlight_color, &y_normal_color ); + renderAxisHandle( 12, ctr, LLVector3( ctr.mV[VX], min.mV[VY], ctr.mV[VZ] ) ); + break; + + case 5: + conditionalHighlight( LL_FACE_NEGZ, &z_highlight_color, &z_normal_color ); + renderAxisHandle( 13, ctr, LLVector3( ctr.mV[VX], ctr.mV[VY], min.mV[VZ] ) ); + break; + } + } + } +} + +void LLManipScale::renderCorners( const LLBBox& bbox ) +{ + U32 part = LL_CORNER_NNN; + + F32 x_offset = bbox.getMinLocal().mV[VX]; + for( S32 i=0; i < 2; i++ ) + { + F32 y_offset = bbox.getMinLocal().mV[VY]; + for( S32 j=0; j < 2; j++ ) + { + F32 z_offset = bbox.getMinLocal().mV[VZ]; + for( S32 k=0; k < 2; k++ ) + { + conditionalHighlight( part ); + renderBoxHandle( x_offset, y_offset, z_offset ); + part++; + + z_offset = bbox.getMaxLocal().mV[VZ]; + + } + y_offset = bbox.getMaxLocal().mV[VY]; + } + x_offset = bbox.getMaxLocal().mV[VX]; + } +} + + +void LLManipScale::renderBoxHandle( F32 x, F32 y, F32 z ) +{ + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + LLGLDepthTest gls_depth(GL_FALSE); + //LLGLDisable gls_stencil(GL_STENCIL_TEST); + + gGL.pushMatrix(); + { + gGL.translatef( x, y, z ); + gGL.scalef( mScaledBoxHandleSize, mScaledBoxHandleSize, mScaledBoxHandleSize ); + gBox.render(); + } + gGL.popMatrix(); +} + + +void LLManipScale::renderAxisHandle( U32 handle_index, const LLVector3& start, const LLVector3& end ) +{ + if( getShowAxes() ) + { + // Draws a single "jacks" style handle: a long, retangular box from start to end. + LLVector3 offset_start = end - start; + offset_start.normalize(); + offset_start = start + mBoxHandleSize[handle_index] * offset_start; + + LLVector3 delta = end - offset_start; + LLVector3 pos = offset_start + 0.5f * delta; + + gGL.pushMatrix(); + { + gGL.translatef( pos.mV[VX], pos.mV[VY], pos.mV[VZ] ); + gGL.scalef( + mBoxHandleSize[handle_index] + llabs(delta.mV[VX]), + mBoxHandleSize[handle_index] + llabs(delta.mV[VY]), + mBoxHandleSize[handle_index] + llabs(delta.mV[VZ])); + gBox.render(); + } + gGL.popMatrix(); + } + else + { + renderBoxHandle( end.mV[VX], end.mV[VY], end.mV[VZ] ); + } +} + +// General scale call +void LLManipScale::drag( S32 x, S32 y ) +{ + if( (LL_FACE_MIN <= (S32)mManipPart) + && ((S32)mManipPart <= LL_FACE_MAX) ) + { + dragFace( x, y ); + } + else if( (LL_CORNER_MIN <= (S32)mManipPart) + && ((S32)mManipPart <= LL_CORNER_MAX) ) + { + dragCorner( x, y ); + } + + // store changes to override updates + for (LLObjectSelection::iterator iter = LLSelectMgr::getInstance()->getSelection()->begin(); + iter != LLSelectMgr::getInstance()->getSelection()->end(); iter++) + { + LLSelectNode* selectNode = *iter; + LLViewerObject*cur = selectNode->getObject(); + LLViewerObject *root_object = (cur == NULL) ? NULL : cur->getRootEdit(); + if( cur->permModify() && cur->permMove() && !cur->isPermanentEnforced() && + ((root_object == NULL) || !root_object->isPermanentEnforced()) && + !cur->isAvatar()) + { + selectNode->mLastScale = cur->getScale(); + selectNode->mLastPositionLocal = cur->getPosition(); + } + } + + LLSelectMgr::getInstance()->updateSelectionCenter(); + gAgentCamera.clearFocusObject(); +} + +// Scale on three axis simultaneously +void LLManipScale::dragCorner( S32 x, S32 y ) +{ + // Suppress scale if mouse hasn't moved. + if (x == mLastMouseX && y == mLastMouseY) + { + return; + } + mLastMouseX = x; + mLastMouseY = y; + + LLVector3 drag_start_point_agent = gAgent.getPosAgentFromGlobal(mDragStartPointGlobal); + LLVector3 drag_start_center_agent = gAgent.getPosAgentFromGlobal(mDragStartCenterGlobal); + + LLVector3d drag_start_dir_d; + drag_start_dir_d.set(mDragStartPointGlobal - mDragStartCenterGlobal); + + F32 s = 0; + F32 t = 0; + nearestPointOnLineFromMouse(x, y, + drag_start_center_agent, + drag_start_point_agent, + s, t ); + + if( s <= 0 ) // we only care about intersections in front of the camera + { + return; + } + mDragPointGlobal = lerp(mDragStartCenterGlobal, mDragStartPointGlobal, t); + + LLBBox bbox = LLSelectMgr::getInstance()->getBBoxOfSelection(); + F32 scale_factor = 1.f; + F32 max_scale = partToMaxScale(mManipPart, bbox); + F32 min_scale = partToMinScale(mManipPart, bbox); + bool uniform = LLManipScale::getUniform(); + + // check for snapping + LLVector3 mouse_on_plane1; + getMousePointOnPlaneAgent(mouse_on_plane1, x, y, mScaleCenter, mScalePlaneNormal1); + mouse_on_plane1 -= mScaleCenter; + + LLVector3 mouse_on_plane2; + getMousePointOnPlaneAgent(mouse_on_plane2, x, y, mScaleCenter, mScalePlaneNormal2); + mouse_on_plane2 -= mScaleCenter; + + LLVector3 projected_drag_pos1 = inverse_projected_vec(mScaleDir, orthogonal_component(mouse_on_plane1, mSnapGuideDir1)); + LLVector3 projected_drag_pos2 = inverse_projected_vec(mScaleDir, orthogonal_component(mouse_on_plane2, mSnapGuideDir2)); + + bool snap_enabled = gSavedSettings.getBOOL("SnapEnabled"); + if (snap_enabled && (mouse_on_plane1 - projected_drag_pos1) * mSnapGuideDir1 > mSnapRegimeOffset) + { + F32 drag_dist = mScaleDir * projected_drag_pos1; // Projecting the drag position allows for negative results, vs using the length which will result in a "reverse scaling" bug. + + F32 cur_subdivisions = llclamp(getSubdivisionLevel(mScaleCenter + projected_drag_pos1, mScaleDir, mScaleSnapUnit1, mTickPixelSpacing1), sGridMinSubdivisionLevel, sGridMaxSubdivisionLevel); + F32 snap_dist = mScaleSnapUnit1 / (2.f * cur_subdivisions); + F32 relative_snap_dist = fmodf(drag_dist + snap_dist, mScaleSnapUnit1 / cur_subdivisions); + + mScaleSnappedValue = llclamp((drag_dist - (relative_snap_dist - snap_dist)), min_scale, max_scale); + scale_factor = mScaleSnappedValue / dist_vec(drag_start_point_agent, drag_start_center_agent); + mScaleSnappedValue /= mScaleSnapUnit1 * 2.f; + mSnapRegime = SNAP_REGIME_UPPER; + + if (!uniform) + { + scale_factor *= 0.5f; + } + } + else if (snap_enabled && (mouse_on_plane2 - projected_drag_pos2) * mSnapGuideDir2 > mSnapRegimeOffset ) + { + F32 drag_dist = mScaleDir * projected_drag_pos2; // Projecting the drag position allows for negative results, vs using the length which will result in a "reverse scaling" bug. + + F32 cur_subdivisions = llclamp(getSubdivisionLevel(mScaleCenter + projected_drag_pos2, mScaleDir, mScaleSnapUnit2, mTickPixelSpacing2), sGridMinSubdivisionLevel, sGridMaxSubdivisionLevel); + F32 snap_dist = mScaleSnapUnit2 / (2.f * cur_subdivisions); + F32 relative_snap_dist = fmodf(drag_dist + snap_dist, mScaleSnapUnit2 / cur_subdivisions); + + mScaleSnappedValue = llclamp((drag_dist - (relative_snap_dist - snap_dist)), min_scale, max_scale); + scale_factor = mScaleSnappedValue / dist_vec(drag_start_point_agent, drag_start_center_agent); + mScaleSnappedValue /= mScaleSnapUnit2 * 2.f; + mSnapRegime = SNAP_REGIME_LOWER; + + if (!uniform) + { + scale_factor *= 0.5f; + } + } + else + { + mSnapRegime = SNAP_REGIME_NONE; + scale_factor = t; + if (!uniform) + { + scale_factor = 0.5f + (scale_factor * 0.5f); + } + } + + + F32 max_scale_factor = get_default_max_prim_scale() / MIN_PRIM_SCALE; + F32 min_scale_factor = MIN_PRIM_SCALE / get_default_max_prim_scale(); + + // find max and min scale factors that will make biggest object hit max absolute scale and smallest object hit min absolute scale + for (LLObjectSelection::iterator iter = mObjectSelection->begin(); + iter != mObjectSelection->end(); iter++) + { + LLSelectNode* selectNode = *iter; + LLViewerObject* cur = selectNode->getObject(); + LLViewerObject *root_object = (cur == NULL) ? NULL : cur->getRootEdit(); + if( cur->permModify() && cur->permMove() && !cur->isPermanentEnforced() && + ((root_object == NULL) || !root_object->isPermanentEnforced()) && + !cur->isAvatar() ) + { + const LLVector3& scale = selectNode->mSavedScale; + + F32 cur_max_scale_factor = llmin( get_default_max_prim_scale(LLPickInfo::isFlora(cur)) / scale.mV[VX], get_default_max_prim_scale(LLPickInfo::isFlora(cur)) / scale.mV[VY], get_default_max_prim_scale(LLPickInfo::isFlora(cur)) / scale.mV[VZ] ); + max_scale_factor = llmin( max_scale_factor, cur_max_scale_factor ); + + F32 cur_min_scale_factor = llmax( MIN_PRIM_SCALE / scale.mV[VX], MIN_PRIM_SCALE / scale.mV[VY], MIN_PRIM_SCALE / scale.mV[VZ] ); + min_scale_factor = llmax( min_scale_factor, cur_min_scale_factor ); + } + } + + scale_factor = llclamp( scale_factor, min_scale_factor, max_scale_factor ); + + LLVector3d drag_global = uniform ? mDragStartCenterGlobal : mDragFarHitGlobal; + + // do the root objects i.e. (true == cur->isRootEdit()) + for (LLObjectSelection::iterator iter = mObjectSelection->begin(); + iter != mObjectSelection->end(); iter++) + { + LLSelectNode* selectNode = *iter; + LLViewerObject* cur = selectNode->getObject(); + LLViewerObject *root_object = (cur == NULL) ? NULL : cur->getRootEdit(); + if( cur->permModify() && cur->permMove() && !cur->isPermanentEnforced() && + ((root_object == NULL) || !root_object->isPermanentEnforced()) && + !cur->isAvatar() && cur->isRootEdit() ) + { + const LLVector3& scale = selectNode->mSavedScale; + cur->setScale( scale_factor * scale ); + + LLVector3 delta_pos; + LLVector3 original_pos = cur->getPositionEdit(); + LLVector3d new_pos_global = drag_global + (selectNode->mSavedPositionGlobal - drag_global) * scale_factor; + if (!cur->isAttachment()) + { + new_pos_global = LLWorld::getInstance()->clipToVisibleRegions(selectNode->mSavedPositionGlobal, new_pos_global); + } + cur->setPositionAbsoluteGlobal( new_pos_global ); + rebuild(cur); + + delta_pos = cur->getPositionEdit() - original_pos; + + if (selectNode->mIndividualSelection) + { + // counter-translate child objects if we are moving the root as an individual + LLViewerObject::const_child_list_t& child_list = cur->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); iter++) + { + LLViewerObject* childp = *iter; + + if (cur->isAttachment()) + { + LLVector3 child_pos = childp->getPosition() - (delta_pos * ~cur->getRotationEdit()); + childp->setPosition(child_pos); + } + else + { + LLVector3d child_pos_delta(delta_pos); + // RN: this updates drawable position instantly + childp->setPositionAbsoluteGlobal(childp->getPositionGlobal() - child_pos_delta); + } + rebuild(childp); + } + } + } + } + // do the child objects i.e. (false == cur->isRootEdit()) + for (LLObjectSelection::iterator iter = mObjectSelection->begin(); + iter != mObjectSelection->end(); iter++) + { + LLSelectNode* selectNode = *iter; + LLViewerObject*cur = selectNode->getObject(); + LLViewerObject *root_object = (cur == NULL) ? NULL : cur->getRootEdit(); + if( cur->permModify() && cur->permMove() && !cur->isPermanentEnforced() && + ((root_object == NULL) || !root_object->isPermanentEnforced()) && + !cur->isAvatar() && !cur->isRootEdit() ) + { + const LLVector3& scale = selectNode->mSavedScale; + cur->setScale( scale_factor * scale, false ); + + if (!selectNode->mIndividualSelection) + { + cur->setPosition(selectNode->mSavedPositionLocal * scale_factor); + } + + rebuild(cur); + } + } +} + +// Scale on a single axis +void LLManipScale::dragFace( S32 x, S32 y ) +{ + // Suppress scale if mouse hasn't moved. + if (x == mLastMouseX && y == mLastMouseY) + { + return; + } + + mLastMouseX = x; + mLastMouseY = y; + + LLVector3d drag_start_point_global = mDragStartPointGlobal; + LLVector3d drag_start_center_global = mDragStartCenterGlobal; + LLVector3 drag_start_point_agent = gAgent.getPosAgentFromGlobal(drag_start_point_global); + LLVector3 drag_start_center_agent = gAgent.getPosAgentFromGlobal(drag_start_center_global); + + LLVector3d drag_start_dir_d; + drag_start_dir_d.set(drag_start_point_global - drag_start_center_global); + LLVector3 drag_start_dir_f; + drag_start_dir_f.set(drag_start_dir_d); + + LLBBox bbox = LLSelectMgr::getInstance()->getBBoxOfSelection(); + + F32 s = 0; + F32 t = 0; + + nearestPointOnLineFromMouse(x, + y, + drag_start_center_agent, + drag_start_point_agent, + s, t ); + + if( s <= 0 ) // we only care about intersections in front of the camera + { + return; + } + + LLVector3d drag_point_global = drag_start_center_global + t * drag_start_dir_d; + LLVector3 part_dir_local = partToUnitVector( mManipPart ); + + // check for snapping + LLVector3 mouse_on_plane; + getMousePointOnPlaneAgent(mouse_on_plane, x, y, mScaleCenter, mScalePlaneNormal1 ); + + LLVector3 mouse_on_scale_line = mScaleCenter + projected_vec(mouse_on_plane - mScaleCenter, mScaleDir); + LLVector3 drag_delta(mouse_on_scale_line - drag_start_point_agent); + F32 max_drag_dist = partToMaxScale(mManipPart, bbox); + F32 min_drag_dist = partToMinScale(mManipPart, bbox); + + bool uniform = LLManipScale::getUniform(); + if( uniform ) + { + drag_delta *= 2.f; + } + + LLVector3 scale_center_to_mouse = mouse_on_plane - mScaleCenter; + F32 dist_from_scale_line = dist_vec(scale_center_to_mouse, (mouse_on_scale_line - mScaleCenter)); + F32 dist_along_scale_line = scale_center_to_mouse * mScaleDir; + + bool snap_enabled = gSavedSettings.getBOOL("SnapEnabled"); + + if (snap_enabled && dist_from_scale_line > mSnapRegimeOffset) + { + mSnapRegime = static_cast(SNAP_REGIME_UPPER | SNAP_REGIME_LOWER); // A face drag doesn't have split regimes. + + if (dist_along_scale_line > max_drag_dist) + { + mScaleSnappedValue = max_drag_dist; + + LLVector3 clamp_point = mScaleCenter + max_drag_dist * mScaleDir; + drag_delta.set(clamp_point - drag_start_point_agent); + } + else if (dist_along_scale_line < min_drag_dist) + { + mScaleSnappedValue = min_drag_dist; + + LLVector3 clamp_point = mScaleCenter + min_drag_dist * mScaleDir; + drag_delta.set(clamp_point - drag_start_point_agent); + } + else + { + F32 drag_dist = scale_center_to_mouse * mScaleDir; + F32 cur_subdivisions = llclamp(getSubdivisionLevel(mScaleCenter + mScaleDir * drag_dist, mScaleDir, mScaleSnapUnit1, mTickPixelSpacing1), sGridMinSubdivisionLevel, sGridMaxSubdivisionLevel); + F32 snap_dist = mScaleSnapUnit1 / (2.f * cur_subdivisions); + F32 relative_snap_dist = fmodf(drag_dist + snap_dist, mScaleSnapUnit1 / cur_subdivisions); + relative_snap_dist -= snap_dist; + + //make sure that values that the scale is "snapped to" + //do not exceed/go under the applicable max/mins + //this causes the box to shift displacements ever so slightly + //although the "snap value" should go down to 0 + //see Jira 1027 + relative_snap_dist = llclamp(relative_snap_dist, + drag_dist - max_drag_dist, + drag_dist - min_drag_dist); + + mScaleSnappedValue = (drag_dist - relative_snap_dist) / (mScaleSnapUnit1 * 2.f); + + if (llabs(relative_snap_dist) < snap_dist) + { + LLVector3 drag_correction = relative_snap_dist * mScaleDir; + if (uniform) + { + drag_correction *= 2.f; + } + + drag_delta -= drag_correction; + } + } + } + else + { + mSnapRegime = SNAP_REGIME_NONE; + } + + LLVector3 dir_agent; + if( part_dir_local.mV[VX] ) + { + dir_agent = bbox.localToAgentBasis( LLVector3::x_axis ); + } + else if( part_dir_local.mV[VY] ) + { + dir_agent = bbox.localToAgentBasis( LLVector3::y_axis ); + } + else if( part_dir_local.mV[VZ] ) + { + dir_agent = bbox.localToAgentBasis( LLVector3::z_axis ); + } + stretchFace( + projected_vec(drag_start_dir_f, dir_agent) + drag_start_center_agent, + projected_vec(drag_delta, dir_agent)); + + mDragPointGlobal = drag_point_global; +} + +void LLManipScale::sendUpdates( bool send_position_update, bool send_scale_update, bool corner ) +{ + // Throttle updates to 10 per second. + static LLTimer update_timer; + F32 elapsed_time = update_timer.getElapsedTimeF32(); + const F32 UPDATE_DELAY = 0.1f; // min time between transmitted updates + + if( send_scale_update || send_position_update ) + { + U32 update_flags = UPD_NONE; + if (send_position_update) update_flags |= UPD_POSITION; + if (send_scale_update) update_flags |= UPD_SCALE; + +// bool send_type = SEND_INDIVIDUALS; + if (corner) + { + update_flags |= UPD_UNIFORM; + } + // keep this up to date for sendonmouseup + mLastUpdateFlags = update_flags; + + // enforce minimum update delay and don't stream updates on sub-object selections + if( elapsed_time > UPDATE_DELAY && !gSavedSettings.getBOOL("EditLinkedParts") ) + { + LLSelectMgr::getInstance()->sendMultipleUpdate( update_flags ); + update_timer.reset(); + mSendUpdateOnMouseUp = false; + } + else + { + mSendUpdateOnMouseUp = true; + } + dialog_refresh_all(); + } +} + +// Rescales in a single dimension. Either uniform (standard) or one-sided (scale plus translation) +// depending on mUniform. Handles multiple selection and objects that are not aligned to the bounding box. +void LLManipScale::stretchFace( const LLVector3& drag_start_agent, const LLVector3& drag_delta_agent ) +{ + LLVector3 drag_start_center_agent = gAgent.getPosAgentFromGlobal(mDragStartCenterGlobal); + + for (LLObjectSelection::iterator iter = mObjectSelection->begin(); + iter != mObjectSelection->end(); iter++) + { + LLSelectNode* selectNode = *iter; + LLViewerObject*cur = selectNode->getObject(); + LLViewerObject *root_object = (cur == NULL) ? NULL : cur->getRootEdit(); + if( cur->permModify() && cur->permMove() && !cur->isPermanentEnforced() && + ((root_object == NULL) || !root_object->isPermanentEnforced()) && + !cur->isAvatar() ) + { + LLBBox cur_bbox = cur->getBoundingBoxAgent(); + LLVector3 start_local = cur_bbox.agentToLocal( drag_start_agent ); + LLVector3 end_local = cur_bbox.agentToLocal( drag_start_agent + drag_delta_agent); + LLVector3 start_center_local = cur_bbox.agentToLocal( drag_start_center_agent ); + LLVector3 axis = nearestAxis( start_local - start_center_local ); + S32 axis_index = axis.mV[0] ? 0 : (axis.mV[1] ? 1 : 2 ); + + LLVector3 delta_local = end_local - start_local; + F32 delta_local_mag = delta_local.length(); + LLVector3 dir_local; + if (delta_local_mag == 0.f) + { + dir_local = axis; + } + else + { + dir_local = delta_local / delta_local_mag; // normalized delta_local + } + + F32 denom = axis * dir_local; + F32 desired_delta_size = is_approx_zero(denom) ? 0.f : (delta_local_mag / denom); // in meters + F32 desired_scale = llclamp(selectNode->mSavedScale.mV[axis_index] + desired_delta_size, MIN_PRIM_SCALE, get_default_max_prim_scale(LLPickInfo::isFlora(cur))); + // propagate scale constraint back to position offset + desired_delta_size = desired_scale - selectNode->mSavedScale.mV[axis_index]; // propagate constraint back to position + + LLVector3 scale = cur->getScale(); + scale.mV[axis_index] = desired_scale; + cur->setScale(scale, false); + rebuild(cur); + LLVector3 delta_pos; + if( !getUniform() ) + { + LLVector3 delta_pos_local = axis * (0.5f * desired_delta_size); + LLVector3d delta_pos_global; + delta_pos_global.set(cur_bbox.localToAgent( delta_pos_local ) - cur_bbox.getCenterAgent()); + LLVector3 cur_pos = cur->getPositionEdit(); + + if (cur->isRootEdit() && !cur->isAttachment()) + { + LLVector3d new_pos_global = LLWorld::getInstance()->clipToVisibleRegions(selectNode->mSavedPositionGlobal, selectNode->mSavedPositionGlobal + delta_pos_global); + cur->setPositionGlobal( new_pos_global ); + } + else + { + LLXform* parent_xform = cur->mDrawable->getXform()->getParent(); + LLVector3 new_pos_local; + // this works in attachment point space using world space delta + if (parent_xform) + { + new_pos_local = selectNode->mSavedPositionLocal + (LLVector3(delta_pos_global) * ~parent_xform->getWorldRotation()); + } + else + { + new_pos_local = selectNode->mSavedPositionLocal + LLVector3(delta_pos_global); + } + cur->setPosition(new_pos_local); + } + delta_pos = cur->getPositionEdit() - cur_pos; + } + if (cur->isRootEdit() && selectNode->mIndividualSelection) + { + // counter-translate child objects if we are moving the root as an individual + LLViewerObject::const_child_list_t& child_list = cur->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); iter++) + { + LLViewerObject* childp = *iter; + if (!getUniform()) + { + LLVector3 child_pos = childp->getPosition() - (delta_pos * ~cur->getRotationEdit()); + childp->setPosition(child_pos); + rebuild(childp); + } + } + } + } + } +} + + +void LLManipScale::renderGuidelinesPart( const LLBBox& bbox ) +{ + LLVector3 guideline_start = bbox.getCenterLocal(); + + LLVector3 guideline_end = unitVectorToLocalBBoxExtent( partToUnitVector( mManipPart ), bbox ); + + if (!getUniform()) + { + guideline_start = unitVectorToLocalBBoxExtent( -partToUnitVector( mManipPart ), bbox ); + } + + guideline_end -= guideline_start; + guideline_end.normalize(); + guideline_end *= LLWorld::getInstance()->getRegionWidthInMeters(); + guideline_end += guideline_start; + + { + LLGLDepthTest gls_depth(GL_TRUE); + gl_line_3d( guideline_start, guideline_end, LLColor4(1.f, 1.f, 1.f, 0.5f) ); + } + { + LLGLDepthTest gls_depth(GL_FALSE); + gl_line_3d( guideline_start, guideline_end, LLColor4(1.f, 1.f, 1.f, 0.25f) ); + } +} + +void LLManipScale::updateSnapGuides(const LLBBox& bbox) +{ + LLVector3 grid_origin; + LLVector3 grid_scale; + LLQuaternion grid_rotation; + LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale); + + bool uniform = LLManipScale::getUniform(); + + LLVector3 box_corner_agent = bbox.localToAgent(unitVectorToLocalBBoxExtent( partToUnitVector( mManipPart ), bbox )); + mScaleCenter = uniform ? bbox.getCenterAgent() : bbox.localToAgent(unitVectorToLocalBBoxExtent( -1.f * partToUnitVector( mManipPart ), bbox )); + mScaleDir = box_corner_agent - mScaleCenter; + mScaleDir.normalize(); + + if(mObjectSelection->getSelectType() == SELECT_TYPE_HUD) + { + mSnapRegimeOffset = SNAP_GUIDE_SCREEN_OFFSET / gAgentCamera.mHUDCurZoom; + } + else + { + F32 object_distance = dist_vec(box_corner_agent, LLViewerCamera::getInstance()->getOrigin()); + mSnapRegimeOffset = (SNAP_GUIDE_SCREEN_OFFSET * gViewerWindow->getWorldViewWidthRaw() * object_distance) / LLViewerCamera::getInstance()->getPixelMeterRatio(); + } + LLVector3 cam_at_axis; + F32 snap_guide_length; + if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) + { + cam_at_axis.set(1.f, 0.f, 0.f); + snap_guide_length = SNAP_GUIDE_SCREEN_LENGTH / gAgentCamera.mHUDCurZoom; + } + else + { + cam_at_axis = LLViewerCamera::getInstance()->getAtAxis(); + F32 manipulator_distance = dist_vec(box_corner_agent, LLViewerCamera::getInstance()->getOrigin()); + snap_guide_length = (SNAP_GUIDE_SCREEN_LENGTH * gViewerWindow->getWorldViewWidthRaw() * manipulator_distance) / LLViewerCamera::getInstance()->getPixelMeterRatio(); + } + + mSnapGuideLength = snap_guide_length / llmax(0.1f, (llmin(mSnapGuideDir1 * cam_at_axis, mSnapGuideDir2 * cam_at_axis))); + + LLVector3 off_axis_dir = mScaleDir % cam_at_axis; + off_axis_dir.normalize(); + + if( (LL_FACE_MIN <= (S32)mManipPart) && ((S32)mManipPart <= LL_FACE_MAX) ) + { + LLVector3 bbox_relative_cam_dir = off_axis_dir * ~bbox.getRotation(); + bbox_relative_cam_dir.abs(); + if (bbox_relative_cam_dir.mV[VX] > bbox_relative_cam_dir.mV[VY] && bbox_relative_cam_dir.mV[VX] > bbox_relative_cam_dir.mV[VZ]) + { + mSnapGuideDir1 = LLVector3::x_axis * bbox.getRotation(); + } + else if (bbox_relative_cam_dir.mV[VY] > bbox_relative_cam_dir.mV[VZ]) + { + mSnapGuideDir1 = LLVector3::y_axis * bbox.getRotation(); + } + else + { + mSnapGuideDir1 = LLVector3::z_axis * bbox.getRotation(); + } + + LLVector3 scale_snap = grid_scale; + mScaleSnapUnit1 = scale_snap.scaleVec(partToUnitVector( mManipPart )).length(); + mScaleSnapUnit2 = mScaleSnapUnit1; + mSnapGuideDir1 *= mSnapGuideDir1 * LLViewerCamera::getInstance()->getUpAxis() > 0.f ? 1.f : -1.f; + mSnapGuideDir2 = mSnapGuideDir1 * -1.f; + mSnapDir1 = mScaleDir; + mSnapDir2 = mScaleDir; + } + else if( (LL_CORNER_MIN <= (S32)mManipPart) && ((S32)mManipPart <= LL_CORNER_MAX) ) + { + LLVector3 local_camera_dir; + if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) + { + local_camera_dir = LLVector3(-1.f, 0.f, 0.f) * ~bbox.getRotation(); + } + else + { + local_camera_dir = (LLViewerCamera::getInstance()->getOrigin() - box_corner_agent) * ~bbox.getRotation(); + local_camera_dir.normalize(); + } + + LLVector3 axis_flip; + switch (mManipPart) + { + case LL_CORNER_NNN: + axis_flip.set(1.f, 1.f, 1.f); + break; + case LL_CORNER_NNP: + axis_flip.set(1.f, 1.f, -1.f); + break; + case LL_CORNER_NPN: + axis_flip.set(1.f, -1.f, 1.f); + break; + case LL_CORNER_NPP: + axis_flip.set(1.f, -1.f, -1.f); + break; + case LL_CORNER_PNN: + axis_flip.set(-1.f, 1.f, 1.f); + break; + case LL_CORNER_PNP: + axis_flip.set(-1.f, 1.f, -1.f); + break; + case LL_CORNER_PPN: + axis_flip.set(-1.f, -1.f, 1.f); + break; + case LL_CORNER_PPP: + axis_flip.set(-1.f, -1.f, -1.f); + break; + default: + break; + } + + // account for which side of the object the camera is located and negate appropriate axes + local_camera_dir.scaleVec(axis_flip); + + // normalize to object scale + LLVector3 bbox_extent = bbox.getExtentLocal(); + local_camera_dir.scaleVec(LLVector3(1.f / bbox_extent.mV[VX], 1.f / bbox_extent.mV[VY], 1.f / bbox_extent.mV[VZ])); + + S32 scale_face = -1; + + if ((local_camera_dir.mV[VX] > 0.f) == (local_camera_dir.mV[VY] > 0.f)) + { + if ((local_camera_dir.mV[VZ] > 0.f) == (local_camera_dir.mV[VY] > 0.f)) + { + LLVector3 local_camera_dir_abs = local_camera_dir; + local_camera_dir_abs.abs(); + // all neighboring faces of bbox are pointing towards camera or away from camera + // use largest magnitude face for snap guides + if (local_camera_dir_abs.mV[VX] > local_camera_dir_abs.mV[VY]) + { + if (local_camera_dir_abs.mV[VX] > local_camera_dir_abs.mV[VZ]) + { + scale_face = VX; + } + else + { + scale_face = VZ; + } + } + else // y > x + { + if (local_camera_dir_abs.mV[VY] > local_camera_dir_abs.mV[VZ]) + { + scale_face = VY; + } + else + { + scale_face = VZ; + } + } + } + else + { + // z axis facing opposite direction from x and y relative to camera, use x and y for snap guides + scale_face = VZ; + } + } + else // x and y axes are facing in opposite directions relative to camera + { + if ((local_camera_dir.mV[VZ] > 0.f) == (local_camera_dir.mV[VY] > 0.f)) + { + // x axis facing opposite direction from y and z relative to camera, use y and z for snap guides + scale_face = VX; + } + else + { + // y axis facing opposite direction from x and z relative to camera, use x and z for snap guides + scale_face = VY; + } + } + + switch(scale_face) + { + case VX: + // x axis face being scaled, use y and z for snap guides + mSnapGuideDir1 = LLVector3::y_axis.scaledVec(axis_flip); + mScaleSnapUnit1 = grid_scale.mV[VZ]; + mSnapGuideDir2 = LLVector3::z_axis.scaledVec(axis_flip); + mScaleSnapUnit2 = grid_scale.mV[VY]; + break; + case VY: + // y axis facing being scaled, use x and z for snap guides + mSnapGuideDir1 = LLVector3::x_axis.scaledVec(axis_flip); + mScaleSnapUnit1 = grid_scale.mV[VZ]; + mSnapGuideDir2 = LLVector3::z_axis.scaledVec(axis_flip); + mScaleSnapUnit2 = grid_scale.mV[VX]; + break; + case VZ: + // z axis facing being scaled, use x and y for snap guides + mSnapGuideDir1 = LLVector3::x_axis.scaledVec(axis_flip); + mScaleSnapUnit1 = grid_scale.mV[VY]; + mSnapGuideDir2 = LLVector3::y_axis.scaledVec(axis_flip); + mScaleSnapUnit2 = grid_scale.mV[VX]; + break; + default: + mSnapGuideDir1.setZero(); + mScaleSnapUnit1 = 0.f; + + mSnapGuideDir2.setZero(); + mScaleSnapUnit2 = 0.f; + break; + } + + mSnapGuideDir1.rotVec(bbox.getRotation()); + mSnapGuideDir2.rotVec(bbox.getRotation()); + mSnapDir1 = -1.f * mSnapGuideDir2; + mSnapDir2 = -1.f * mSnapGuideDir1; + } + + mScalePlaneNormal1 = mSnapGuideDir1 % mScaleDir; + mScalePlaneNormal1.normalize(); + + mScalePlaneNormal2 = mSnapGuideDir2 % mScaleDir; + mScalePlaneNormal2.normalize(); + + mScaleSnapUnit1 = mScaleSnapUnit1 / (mSnapDir1 * mScaleDir); + mScaleSnapUnit2 = mScaleSnapUnit2 / (mSnapDir2 * mScaleDir); + + mTickPixelSpacing1 = ll_round((F32)MIN_DIVISION_PIXEL_WIDTH / (mScaleDir % mSnapGuideDir1).length()); + mTickPixelSpacing2 = ll_round((F32)MIN_DIVISION_PIXEL_WIDTH / (mScaleDir % mSnapGuideDir2).length()); + + if (uniform) + { + mScaleSnapUnit1 *= 0.5f; + mScaleSnapUnit2 *= 0.5f; + } +} + +void LLManipScale::renderSnapGuides(const LLBBox& bbox) +{ + if (!gSavedSettings.getBOOL("SnapEnabled")) + { + return; + } + + F32 grid_alpha = gSavedSettings.getF32("GridOpacity"); + + F32 max_point_on_scale_line = partToMaxScale(mManipPart, bbox); + LLVector3 drag_point = gAgent.getPosAgentFromGlobal(mDragPointGlobal); + + updateGridSettings(); + + S32 pass; + for (pass = 0; pass < 3; pass++) + { + LLColor4 tick_color = setupSnapGuideRenderPass(pass); + + gGL.begin(LLRender::LINES); + LLVector3 line_mid = mScaleCenter + (mScaleSnappedValue * mScaleDir) + (mSnapGuideDir1 * mSnapRegimeOffset); + LLVector3 line_start = line_mid - (mScaleDir * (llmin(mScaleSnappedValue, mSnapGuideLength * 0.5f))); + LLVector3 line_end = line_mid + (mScaleDir * llmin(max_point_on_scale_line - mScaleSnappedValue, mSnapGuideLength * 0.5f)); + + gGL.color4f(tick_color.mV[VRED], tick_color.mV[VGREEN], tick_color.mV[VBLUE], tick_color.mV[VALPHA] * 0.1f); + gGL.vertex3fv(line_start.mV); + gGL.color4fv(tick_color.mV); + gGL.vertex3fv(line_mid.mV); + gGL.vertex3fv(line_mid.mV); + gGL.color4f(tick_color.mV[VRED], tick_color.mV[VGREEN], tick_color.mV[VBLUE], tick_color.mV[VALPHA] * 0.1f); + gGL.vertex3fv(line_end.mV); + + line_mid = mScaleCenter + (mScaleSnappedValue * mScaleDir) + (mSnapGuideDir2 * mSnapRegimeOffset); + line_start = line_mid - (mScaleDir * (llmin(mScaleSnappedValue, mSnapGuideLength * 0.5f))); + line_end = line_mid + (mScaleDir * llmin(max_point_on_scale_line - mScaleSnappedValue, mSnapGuideLength * 0.5f)); + gGL.vertex3fv(line_start.mV); + gGL.color4fv(tick_color.mV); + gGL.vertex3fv(line_mid.mV); + gGL.vertex3fv(line_mid.mV); + gGL.color4f(tick_color.mV[VRED], tick_color.mV[VGREEN], tick_color.mV[VBLUE], tick_color.mV[VALPHA] * 0.1f); + gGL.vertex3fv(line_end.mV); + gGL.end(); + } + + { + LLGLDepthTest gls_depth(GL_FALSE); + + F32 dist_grid_axis = llmax(0.f, (drag_point - mScaleCenter) * mScaleDir); + + F32 smallest_subdivision1 = mScaleSnapUnit1 / sGridMaxSubdivisionLevel; + F32 smallest_subdivision2 = mScaleSnapUnit2 / sGridMaxSubdivisionLevel; + + F32 dist_scale_units_1 = dist_grid_axis / smallest_subdivision1; + F32 dist_scale_units_2 = dist_grid_axis / smallest_subdivision2; + + // find distance to nearest smallest grid unit + F32 grid_multiple1 = llfloor(dist_scale_units_1); + F32 grid_multiple2 = llfloor(dist_scale_units_2); + F32 grid_offset1 = fmodf(dist_grid_axis, smallest_subdivision1); + F32 grid_offset2 = fmodf(dist_grid_axis, smallest_subdivision2); + + // how many smallest grid units are we away from largest grid scale? + S32 sub_div_offset_1 = ll_round(fmod(dist_grid_axis - grid_offset1, mScaleSnapUnit1 / sGridMinSubdivisionLevel) / smallest_subdivision1); + S32 sub_div_offset_2 = ll_round(fmod(dist_grid_axis - grid_offset2, mScaleSnapUnit2 / sGridMinSubdivisionLevel) / smallest_subdivision2); + + S32 num_ticks_per_side1 = llmax(1, lltrunc(0.5f * mSnapGuideLength / smallest_subdivision1)); + S32 num_ticks_per_side2 = llmax(1, lltrunc(0.5f * mSnapGuideLength / smallest_subdivision2)); + S32 ticks_from_scale_center_1 = lltrunc(dist_scale_units_1); + S32 ticks_from_scale_center_2 = lltrunc(dist_scale_units_2); + S32 max_ticks1 = llceil(max_point_on_scale_line / smallest_subdivision1 - dist_scale_units_1); + S32 max_ticks2 = llceil(max_point_on_scale_line / smallest_subdivision2 - dist_scale_units_2); + S32 start_tick = 0; + S32 stop_tick = 0; + + if (mSnapRegime != SNAP_REGIME_NONE) + { + // draw snap guide line + gGL.begin(LLRender::LINES); + LLVector3 snap_line_center = bbox.localToAgent(unitVectorToLocalBBoxExtent( partToUnitVector( mManipPart ), bbox )); + + LLVector3 snap_line_start = snap_line_center + (mSnapGuideDir1 * mSnapRegimeOffset); + LLVector3 snap_line_end = snap_line_center + (mSnapGuideDir2 * mSnapRegimeOffset); + + gGL.color4f(1.f, 1.f, 1.f, grid_alpha); + gGL.vertex3fv(snap_line_start.mV); + gGL.vertex3fv(snap_line_center.mV); + gGL.vertex3fv(snap_line_center.mV); + gGL.vertex3fv(snap_line_end.mV); + gGL.end(); + + // draw snap guide arrow + gGL.begin(LLRender::TRIANGLES); + { + //gGLSNoCullFaces.set(); + gGL.color4f(1.f, 1.f, 1.f, grid_alpha); + + LLVector3 arrow_dir; + LLVector3 arrow_span = mScaleDir; + + arrow_dir = snap_line_start - snap_line_center; + arrow_dir.normalize(); + gGL.vertex3fv((snap_line_start + arrow_dir * mSnapRegimeOffset * 0.1f).mV); + gGL.vertex3fv((snap_line_start + arrow_span * mSnapRegimeOffset * 0.1f).mV); + gGL.vertex3fv((snap_line_start - arrow_span * mSnapRegimeOffset * 0.1f).mV); + + arrow_dir = snap_line_end - snap_line_center; + arrow_dir.normalize(); + gGL.vertex3fv((snap_line_end + arrow_dir * mSnapRegimeOffset * 0.1f).mV); + gGL.vertex3fv((snap_line_end + arrow_span * mSnapRegimeOffset * 0.1f).mV); + gGL.vertex3fv((snap_line_end - arrow_span * mSnapRegimeOffset * 0.1f).mV); + } + gGL.end(); + } + + LLVector2 screen_translate_axis(llabs(mScaleDir * LLViewerCamera::getInstance()->getLeftAxis()), llabs(mScaleDir * LLViewerCamera::getInstance()->getUpAxis())); + screen_translate_axis.normalize(); + + S32 tick_label_spacing = ll_round(screen_translate_axis * sTickLabelSpacing); + + for (pass = 0; pass < 3; pass++) + { + LLColor4 tick_color = setupSnapGuideRenderPass(pass); + + start_tick = -(llmin(ticks_from_scale_center_1, num_ticks_per_side1)); + stop_tick = llmin(max_ticks1, num_ticks_per_side1); + + gGL.begin(LLRender::LINES); + // draw first row of ticks + for (S32 i = start_tick; i <= stop_tick; i++) + { + F32 alpha = (1.f - (1.f * ((F32)llabs(i) / (F32)num_ticks_per_side1))); + LLVector3 tick_pos = mScaleCenter + (mScaleDir * (grid_multiple1 + i) * smallest_subdivision1); + + //No need check this condition to prevent tick position scaling (FIX MAINT-5207/5208) + //F32 cur_subdivisions = llclamp(getSubdivisionLevel(tick_pos, mScaleDir, mScaleSnapUnit1, mTickPixelSpacing1), sGridMinSubdivisionLevel, sGridMaxSubdivisionLevel); + /*if (fmodf((F32)(i + sub_div_offset_1), (sGridMaxSubdivisionLevel / cur_subdivisions)) != 0.f) + { + continue; + }*/ + + F32 tick_scale = 1.f; + for (F32 division_level = sGridMaxSubdivisionLevel; division_level >= sGridMinSubdivisionLevel; division_level /= 2.f) + { + if (fmodf((F32)(i + sub_div_offset_1), division_level) == 0.f) + { + break; + } + tick_scale *= 0.7f; + } + + gGL.color4f(tick_color.mV[VRED], tick_color.mV[VGREEN], tick_color.mV[VBLUE], tick_color.mV[VALPHA] * alpha); + LLVector3 tick_start = tick_pos + (mSnapGuideDir1 * mSnapRegimeOffset); + LLVector3 tick_end = tick_start + (mSnapGuideDir1 * mSnapRegimeOffset * tick_scale); + gGL.vertex3fv(tick_start.mV); + gGL.vertex3fv(tick_end.mV); + } + + // draw opposite row of ticks + start_tick = -(llmin(ticks_from_scale_center_2, num_ticks_per_side2)); + stop_tick = llmin(max_ticks2, num_ticks_per_side2); + + for (S32 i = start_tick; i <= stop_tick; i++) + { + F32 alpha = (1.f - (1.f * ((F32)llabs(i) / (F32)num_ticks_per_side2))); + LLVector3 tick_pos = mScaleCenter + (mScaleDir * (grid_multiple2 + i) * smallest_subdivision2); + + //No need check this condition to prevent tick position scaling (FIX MAINT-5207/5208) + //F32 cur_subdivisions = llclamp(getSubdivisionLevel(tick_pos, mScaleDir, mScaleSnapUnit2, mTickPixelSpacing2), sGridMinSubdivisionLevel, sGridMaxSubdivisionLevel); + /*if (fmodf((F32)(i + sub_div_offset_2), (sGridMaxSubdivisionLevel / cur_subdivisions)) != 0.f) + { + continue; + }*/ + + F32 tick_scale = 1.f; + for (F32 division_level = sGridMaxSubdivisionLevel; division_level >= sGridMinSubdivisionLevel; division_level /= 2.f) + { + if (fmodf((F32)(i + sub_div_offset_2), division_level) == 0.f) + { + break; + } + tick_scale *= 0.7f; + } + + gGL.color4f(tick_color.mV[VRED], tick_color.mV[VGREEN], tick_color.mV[VBLUE], tick_color.mV[VALPHA] * alpha); + LLVector3 tick_start = tick_pos + (mSnapGuideDir2 * mSnapRegimeOffset); + LLVector3 tick_end = tick_start + (mSnapGuideDir2 * mSnapRegimeOffset * tick_scale); + gGL.vertex3fv(tick_start.mV); + gGL.vertex3fv(tick_end.mV); + } + gGL.end(); + } + + // render upper tick labels + start_tick = -(llmin(ticks_from_scale_center_1, num_ticks_per_side1)); + stop_tick = llmin(max_ticks1, num_ticks_per_side1); + + F32 grid_resolution = mObjectSelection->getSelectType() == SELECT_TYPE_HUD ? 0.25f : llmax(gSavedSettings.getF32("GridResolution"), 0.001f); + S32 label_sub_div_offset_1 = ll_round(fmod(dist_grid_axis - grid_offset1, mScaleSnapUnit1 * 32.f) / smallest_subdivision1); + S32 label_sub_div_offset_2 = ll_round(fmod(dist_grid_axis - grid_offset2, mScaleSnapUnit2 * 32.f) / smallest_subdivision2); + + for (S32 i = start_tick; i <= stop_tick; i++) + { + F32 tick_scale = 1.f; + F32 alpha = grid_alpha * (1.f - (0.5f * ((F32)llabs(i) / (F32)num_ticks_per_side1))); + LLVector3 tick_pos = mScaleCenter + (mScaleDir * (grid_multiple1 + i) * smallest_subdivision1); + + for (F32 division_level = sGridMaxSubdivisionLevel; division_level >= sGridMinSubdivisionLevel; division_level /= 2.f) + { + if (fmodf((F32)(i + label_sub_div_offset_1), division_level) == 0.f) + { + break; + } + tick_scale *= 0.7f; + } + + if (fmodf((F32)(i + label_sub_div_offset_1), (sGridMaxSubdivisionLevel / llmin(sGridMaxSubdivisionLevel, getSubdivisionLevel(tick_pos, mScaleDir, mScaleSnapUnit1, tick_label_spacing)))) == 0.f) + { + LLVector3 text_origin = tick_pos + (mSnapGuideDir1 * mSnapRegimeOffset * (1.f + tick_scale)); + + EGridMode grid_mode = LLSelectMgr::getInstance()->getGridMode(); + F32 tick_value; + if (grid_mode == GRID_MODE_WORLD) + { + tick_value = (grid_multiple1 + i) / (sGridMaxSubdivisionLevel / grid_resolution); + } + else + { + tick_value = (grid_multiple1 + i) / (2.f * sGridMaxSubdivisionLevel); + } + + F32 text_highlight = 0.8f; + + // Highlight this text if the tick value matches the snapped to value, and if either the second set of ticks isn't going to be shown or cursor is in the first snap regime. + if (is_approx_equal(tick_value, mScaleSnappedValue) && (mScaleSnapUnit2 == mScaleSnapUnit1 || (mSnapRegime & SNAP_REGIME_UPPER))) + { + text_highlight = 1.f; + } + + renderTickValue(text_origin, tick_value, grid_mode == GRID_MODE_WORLD ? std::string("m") : std::string("x"), LLColor4(text_highlight, text_highlight, text_highlight, alpha)); + } + } + + // label ticks on opposite side, only can happen in scaling modes that effect more than one axis and when the object's axis don't have the same scale. A differing scale indicates both conditions. + if (mScaleSnapUnit2 != mScaleSnapUnit1) + { + start_tick = -(llmin(ticks_from_scale_center_2, num_ticks_per_side2)); + stop_tick = llmin(max_ticks2, num_ticks_per_side2); + for (S32 i = start_tick; i <= stop_tick; i++) + { + F32 tick_scale = 1.f; + F32 alpha = grid_alpha * (1.f - (0.5f * ((F32)llabs(i) / (F32)num_ticks_per_side2))); + LLVector3 tick_pos = mScaleCenter + (mScaleDir * (grid_multiple2 + i) * smallest_subdivision2); + + for (F32 division_level = sGridMaxSubdivisionLevel; division_level >= sGridMinSubdivisionLevel; division_level /= 2.f) + { + if (fmodf((F32)(i + label_sub_div_offset_2), division_level) == 0.f) + { + break; + } + tick_scale *= 0.7f; + } + + if (fmodf((F32)(i + label_sub_div_offset_2), (sGridMaxSubdivisionLevel / llmin(sGridMaxSubdivisionLevel, getSubdivisionLevel(tick_pos, mScaleDir, mScaleSnapUnit2, tick_label_spacing)))) == 0.f) + { + LLVector3 text_origin = tick_pos + (mSnapGuideDir2 * mSnapRegimeOffset * (1.f + tick_scale)); + + EGridMode grid_mode = LLSelectMgr::getInstance()->getGridMode(); + F32 tick_value; + if (grid_mode == GRID_MODE_WORLD) + { + tick_value = (grid_multiple2 + i) / (sGridMaxSubdivisionLevel / grid_resolution); + } + else + { + tick_value = (grid_multiple2 + i) / (2.f * sGridMaxSubdivisionLevel); + } + + F32 text_highlight = 0.8f; + + if (is_approx_equal(tick_value, mScaleSnappedValue) && (mSnapRegime & SNAP_REGIME_LOWER)) + { + text_highlight = 1.f; + } + + renderTickValue(text_origin, tick_value, grid_mode == GRID_MODE_WORLD ? std::string("m") : std::string("x"), LLColor4(text_highlight, text_highlight, text_highlight, alpha)); + } + } + } + + + // render help text + if (mObjectSelection->getSelectType() != SELECT_TYPE_HUD) + { + if (mHelpTextTimer.getElapsedTimeF32() < sHelpTextVisibleTime + sHelpTextFadeTime && sNumTimesHelpTextShown < sMaxTimesShowHelpText) + { + LLVector3 selection_center_start = LLSelectMgr::getInstance()->getSavedBBoxOfSelection().getCenterAgent(); + + LLVector3 offset_dir; + if (mSnapGuideDir1 * LLViewerCamera::getInstance()->getAtAxis() > mSnapGuideDir2 * LLViewerCamera::getInstance()->getAtAxis()) + { + offset_dir = mSnapGuideDir2; + } + else + { + offset_dir = mSnapGuideDir1; + } + + LLVector3 help_text_pos = selection_center_start + (mSnapRegimeOffset * 5.f * offset_dir); + const LLFontGL* big_fontp = LLFontGL::getFontSansSerif(); + + std::string help_text = LLTrans::getString("manip_hint1"); + LLColor4 help_text_color = LLColor4::white; + help_text_color.mV[VALPHA] = clamp_rescale(mHelpTextTimer.getElapsedTimeF32(), sHelpTextVisibleTime, sHelpTextVisibleTime + sHelpTextFadeTime, grid_alpha, 0.f); + hud_render_utf8text(help_text, help_text_pos, *big_fontp, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, -0.5f * big_fontp->getWidthF32(help_text), 3.f, help_text_color, false); + help_text = LLTrans::getString("manip_hint2"); + help_text_pos -= LLViewerCamera::getInstance()->getUpAxis() * mSnapRegimeOffset * 0.4f; + hud_render_utf8text(help_text, help_text_pos, *big_fontp, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, -0.5f * big_fontp->getWidthF32(help_text), 3.f, help_text_color, false); + } + } + } +} + +// Returns unit vector in direction of part of an origin-centered cube +LLVector3 LLManipScale::partToUnitVector( S32 part ) const +{ + if ( (LL_FACE_MIN <= part) && (part <= LL_FACE_MAX) ) + { + return faceToUnitVector( part ); + } + else if ( (LL_CORNER_MIN <= part) && (part <= LL_CORNER_MAX) ) + { + return cornerToUnitVector( part ); + } + else if ( (LL_EDGE_MIN <= part) && (part <= LL_EDGE_MAX ) ) + { + return edgeToUnitVector( part ); + } + return LLVector3(); +} + + +// Returns unit vector in direction of face of an origin-centered cube +LLVector3 LLManipScale::faceToUnitVector( S32 part ) const +{ + llassert( (LL_FACE_MIN <= part) && (part <= LL_FACE_MAX) ); + LLVector3 vec; + switch( part ) + { + case LL_FACE_POSX: + vec.set( 1.f, 0.f, 0.f ); + break; + case LL_FACE_NEGX: + vec.set( -1.f, 0.f, 0.f ); + break; + case LL_FACE_POSY: + vec.set( 0.f, 1.f, 0.f ); + break; + case LL_FACE_NEGY: + vec.set( 0.f, -1.f, 0.f ); + break; + case LL_FACE_POSZ: + vec.set( 0.f, 0.f, 1.f ); + break; + case LL_FACE_NEGZ: + vec.set( 0.f, 0.f, -1.f ); + break; + default: + vec.clear(); + } + + return vec; +} + + +// Returns unit vector in direction of corner of an origin-centered cube +LLVector3 LLManipScale::cornerToUnitVector( S32 part ) const +{ + llassert( (LL_CORNER_MIN <= part) && (part <= LL_CORNER_MAX) ); + LLVector3 vec; + switch(part) + { + case LL_CORNER_NNN: + vec.set(-OO_SQRT3, -OO_SQRT3, -OO_SQRT3); + break; + case LL_CORNER_NNP: + vec.set(-OO_SQRT3, -OO_SQRT3, OO_SQRT3); + break; + case LL_CORNER_NPN: + vec.set(-OO_SQRT3, OO_SQRT3, -OO_SQRT3); + break; + case LL_CORNER_NPP: + vec.set(-OO_SQRT3, OO_SQRT3, OO_SQRT3); + break; + case LL_CORNER_PNN: + vec.set(OO_SQRT3, -OO_SQRT3, -OO_SQRT3); + break; + case LL_CORNER_PNP: + vec.set(OO_SQRT3, -OO_SQRT3, OO_SQRT3); + break; + case LL_CORNER_PPN: + vec.set(OO_SQRT3, OO_SQRT3, -OO_SQRT3); + break; + case LL_CORNER_PPP: + vec.set(OO_SQRT3, OO_SQRT3, OO_SQRT3); + break; + default: + vec.clear(); + } + + return vec; +} + +// Returns unit vector in direction of edge of an origin-centered cube +LLVector3 LLManipScale::edgeToUnitVector( S32 part ) const +{ + llassert( (LL_EDGE_MIN <= part) && (part <= LL_EDGE_MAX) ); + part -= LL_EDGE_MIN; + S32 rotation = part >> 2; // Edge between which faces: 0 => XY, 1 => YZ, 2 => ZX + LLVector3 v; + v.mV[rotation] = (part & 1) ? F_SQRT2 : -F_SQRT2; + v.mV[(rotation+1) % 3] = (part & 2) ? F_SQRT2 : -F_SQRT2; + // v.mV[(rotation+2) % 3] defaults to 0. + return v; +} + +// Non-linear scale of origin-centered unit cube to non-origin-centered, non-symetrical bounding box +LLVector3 LLManipScale::unitVectorToLocalBBoxExtent( const LLVector3& v, const LLBBox& bbox ) const +{ + const LLVector3& min = bbox.getMinLocal(); + const LLVector3& max = bbox.getMaxLocal(); + LLVector3 ctr = bbox.getCenterLocal(); + + return LLVector3( + v.mV[0] ? (v.mV[0]>0 ? max.mV[0] : min.mV[0] ) : ctr.mV[0], + v.mV[1] ? (v.mV[1]>0 ? max.mV[1] : min.mV[1] ) : ctr.mV[1], + v.mV[2] ? (v.mV[2]>0 ? max.mV[2] : min.mV[2] ) : ctr.mV[2] ); +} + +// returns max allowable scale along a given stretch axis +F32 LLManipScale::partToMaxScale( S32 part, const LLBBox &bbox ) const +{ + F32 max_scale_factor = 0.f; + LLVector3 bbox_extents = unitVectorToLocalBBoxExtent( partToUnitVector( part ), bbox ); + bbox_extents.abs(); + F32 max_extent = 0.f; + for (U32 i = VX; i <= VZ; i++) + { + if (bbox_extents.mV[i] > max_extent) + { + max_extent = bbox_extents.mV[i]; + } + } + max_scale_factor = bbox_extents.length() * get_default_max_prim_scale() / max_extent; + + if (getUniform()) + { + max_scale_factor *= 0.5f; + } + + return max_scale_factor; +} + +// returns min allowable scale along a given stretch axis +F32 LLManipScale::partToMinScale( S32 part, const LLBBox &bbox ) const +{ + LLVector3 bbox_extents = unitVectorToLocalBBoxExtent( partToUnitVector( part ), bbox ); + bbox_extents.abs(); + F32 min_extent = get_default_max_prim_scale(); + for (U32 i = VX; i <= VZ; i++) + { + if (bbox_extents.mV[i] > 0.f && bbox_extents.mV[i] < min_extent) + { + min_extent = bbox_extents.mV[i]; + } + } + F32 min_scale_factor = bbox_extents.length() * MIN_PRIM_SCALE / min_extent; + + if (getUniform()) + { + min_scale_factor *= 0.5f; + } + + return min_scale_factor; +} + +// Returns the axis aligned unit vector closest to v. +LLVector3 LLManipScale::nearestAxis( const LLVector3& v ) const +{ + // Note: yes, this is a slow but easy implementation + // assumes v is normalized + + F32 coords[][3] = + { + { 1.f, 0.f, 0.f }, + { 0.f, 1.f, 0.f }, + { 0.f, 0.f, 1.f }, + {-1.f, 0.f, 0.f }, + { 0.f,-1.f, 0.f }, + { 0.f, 0.f,-1.f } + }; + + F32 cosine[6]; + cosine[0] = v * LLVector3( coords[0] ); + cosine[1] = v * LLVector3( coords[1] ); + cosine[2] = v * LLVector3( coords[2] ); + cosine[3] = -cosine[0]; + cosine[4] = -cosine[1]; + cosine[5] = -cosine[2]; + + F32 greatest_cos = cosine[0]; + S32 greatest_index = 0; + for( S32 i=1; i<6; i++ ) + { + if( greatest_cos < cosine[i] ) + { + greatest_cos = cosine[i]; + greatest_index = i; + } + } + + return LLVector3( coords[greatest_index] ); +} + +// virtual +bool LLManipScale::canAffectSelection() +{ + // An selection is scalable if you are allowed to both edit and move + // everything in it, and it does not have any sitting agents + bool can_scale = mObjectSelection->getObjectCount() != 0; + if (can_scale) + { + struct f : public LLSelectedObjectFunctor + { + virtual bool apply(LLViewerObject* objectp) + { + LLViewerObject *root_object = (objectp == NULL) ? NULL : objectp->getRootEdit(); + return objectp->permModify() && objectp->permMove() && !objectp->isPermanentEnforced() && + (root_object == NULL || (!root_object->isPermanentEnforced() && !root_object->isSeat())) && + !objectp->isSeat(); + } + } func; + can_scale = mObjectSelection->applyToObjects(&func); + } + return can_scale; +} diff --git a/indra/newview/llmanipscale.h b/indra/newview/llmanipscale.h index 68ec3cf1ea..8a615cb7e4 100644 --- a/indra/newview/llmanipscale.h +++ b/indra/newview/llmanipscale.h @@ -1,179 +1,179 @@ -/** - * @file llmanipscale.h - * @brief LLManipScale class definition - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_MANIPSCALE_H -#define LL_MANIPSCALE_H - -// llmanipscale.h -// -// copyright 2001-2002, linden research inc - - -#include "lltool.h" -#include "v3math.h" -#include "v4math.h" -#include "llmanip.h" -#include "llviewerobject.h" -#include "llbbox.h" - - -F32 get_default_max_prim_scale(bool is_flora = false); - -class LLToolComposite; -class LLColor4; - -typedef enum e_scale_manipulator_type -{ - SCALE_MANIP_CORNER, - SCALE_MANIP_FACE -} EScaleManipulatorType; - -typedef enum e_snap_regimes -{ - SNAP_REGIME_NONE = 0, //!< The cursor is not in either of the snap regimes. - SNAP_REGIME_UPPER = 0x1, //!< The cursor is, non-exclusively, in the first of the snap regimes. Prefer to treat as bitmask. - SNAP_REGIME_LOWER = 0x2 //!< The cursor is, non-exclusively, in the second of the snap regimes. Prefer to treat as bitmask. -} ESnapRegimes; - - -class LLManipScale : public LLManip -{ -public: - class ManipulatorHandle - { - public: - LLVector3 mPosition; - EManipPart mManipID; - EScaleManipulatorType mType; - - ManipulatorHandle(LLVector3 pos, EManipPart id, EScaleManipulatorType type):mPosition(pos), mManipID(id), mType(type){} - }; - static const S32 NUM_MANIPULATORS = 14; - - LLManipScale( LLToolComposite* composite ); - ~LLManipScale(); - - virtual bool handleMouseDown( S32 x, S32 y, MASK mask ); - virtual bool handleMouseUp( S32 x, S32 y, MASK mask ); - virtual bool handleHover( S32 x, S32 y, MASK mask ); - virtual void render(); - virtual void handleSelect(); - - virtual bool handleMouseDownOnPart(S32 x, S32 y, MASK mask); - virtual void highlightManipulators(S32 x, S32 y); // decided which manipulator, if any, should be highlighted by mouse hover - virtual bool canAffectSelection(); - - static void setUniform( bool b ); - static bool getUniform(); - static void setStretchTextures( bool b ); - static bool getStretchTextures(); - static void setShowAxes( bool b ); - static bool getShowAxes(); - -private: - void renderCorners( const LLBBox& local_bbox ); - void renderFaces( const LLBBox& local_bbox ); - void renderBoxHandle( F32 x, F32 y, F32 z ); - void renderAxisHandle( U32 part, const LLVector3& start, const LLVector3& end ); - void renderGuidelinesPart( const LLBBox& local_bbox ); - void renderSnapGuides( const LLBBox& local_bbox ); - - void revert(); - - inline void conditionalHighlight( U32 part, const LLColor4* highlight = NULL, const LLColor4* normal = NULL ); - - void drag( S32 x, S32 y ); - void dragFace( S32 x, S32 y ); - void dragCorner( S32 x, S32 y ); - - void sendUpdates( bool send_position_update, bool send_scale_update, bool corner = false); - - LLVector3 faceToUnitVector( S32 part ) const; - LLVector3 cornerToUnitVector( S32 part ) const; - LLVector3 edgeToUnitVector( S32 part ) const; - LLVector3 partToUnitVector( S32 part ) const; - LLVector3 unitVectorToLocalBBoxExtent( const LLVector3& v, const LLBBox& bbox ) const; - F32 partToMaxScale( S32 part, const LLBBox& bbox ) const; - F32 partToMinScale( S32 part, const LLBBox& bbox ) const; - LLVector3 nearestAxis( const LLVector3& v ) const; - - void stretchFace( const LLVector3& drag_start_agent, const LLVector3& drag_delta_agent); - - void adjustTextureRepeats(); // Adjusts texture coords based on mSavedScale and current scale, only works for boxes - - void updateSnapGuides(const LLBBox& bbox); -private: - - struct compare_manipulators - { - bool operator() (const ManipulatorHandle* const a, const ManipulatorHandle* const b) const - { - if (a->mType != b->mType) - return a->mType < b->mType; - else if (a->mPosition.mV[VZ] != b->mPosition.mV[VZ]) - return a->mPosition.mV[VZ] < b->mPosition.mV[VZ]; - else - return a->mManipID < b->mManipID; - } - }; - - - F32 mScaledBoxHandleSize; //!< Handle size after scaling for selection feedback. - LLVector3d mDragStartPointGlobal; - LLVector3d mDragStartCenterGlobal; //!< The center of the bounding box of all selected objects at time of drag start. - LLVector3d mDragPointGlobal; - LLVector3d mDragFarHitGlobal; - S32 mLastMouseX; - S32 mLastMouseY; - bool mSendUpdateOnMouseUp; - U32 mLastUpdateFlags; - typedef std::set manipulator_list_t; - manipulator_list_t mProjectedManipulators; - LLVector4 mManipulatorVertices[14]; - F32 mScaleSnapUnit1; //!< Size of snap multiples for the upper scale. - F32 mScaleSnapUnit2; //!< Size of snap multiples for the lower scale. - LLVector3 mScalePlaneNormal1; //!< Normal of plane in which scale occurs that most faces camera. - LLVector3 mScalePlaneNormal2; //!< Normal of plane in which scale occurs that most faces camera. - LLVector3 mSnapGuideDir1; //!< The direction in which the upper snap guide tick marks face. - LLVector3 mSnapGuideDir2; //!< The direction in which the lower snap guide tick marks face. - LLVector3 mSnapDir1; //!< The direction in which the upper snap guides face. - LLVector3 mSnapDir2; //!< The direction in which the lower snap guides face. - F32 mSnapRegimeOffset; //!< How far off the scale axis centerline the mouse can be before it exits/enters the snap regime. - F32 mTickPixelSpacing1; //!< The pixel spacing between snap guide tick marks for the upper scale. - F32 mTickPixelSpacing2; //!< The pixel spacing between snap guide tick marks for the lower scale. - F32 mSnapGuideLength; - LLVector3 mScaleCenter; //!< The location of the origin of the scaling operation. - LLVector3 mScaleDir; //!< The direction of the scaling action. In face-dragging this is aligned with one of the cardinal axis relative to the prim, but in corner-dragging this is along the diagonal. - F32 mScaleSnappedValue; //!< The distance of the current position nearest the mouse location, measured along mScaleDir. Is measured either from the center or from the far face/corner depending upon whether uniform scaling is true or false respectively. - ESnapRegimes mSnapRegime; //mType != b->mType) + return a->mType < b->mType; + else if (a->mPosition.mV[VZ] != b->mPosition.mV[VZ]) + return a->mPosition.mV[VZ] < b->mPosition.mV[VZ]; + else + return a->mManipID < b->mManipID; + } + }; + + + F32 mScaledBoxHandleSize; //!< Handle size after scaling for selection feedback. + LLVector3d mDragStartPointGlobal; + LLVector3d mDragStartCenterGlobal; //!< The center of the bounding box of all selected objects at time of drag start. + LLVector3d mDragPointGlobal; + LLVector3d mDragFarHitGlobal; + S32 mLastMouseX; + S32 mLastMouseY; + bool mSendUpdateOnMouseUp; + U32 mLastUpdateFlags; + typedef std::set manipulator_list_t; + manipulator_list_t mProjectedManipulators; + LLVector4 mManipulatorVertices[14]; + F32 mScaleSnapUnit1; //!< Size of snap multiples for the upper scale. + F32 mScaleSnapUnit2; //!< Size of snap multiples for the lower scale. + LLVector3 mScalePlaneNormal1; //!< Normal of plane in which scale occurs that most faces camera. + LLVector3 mScalePlaneNormal2; //!< Normal of plane in which scale occurs that most faces camera. + LLVector3 mSnapGuideDir1; //!< The direction in which the upper snap guide tick marks face. + LLVector3 mSnapGuideDir2; //!< The direction in which the lower snap guide tick marks face. + LLVector3 mSnapDir1; //!< The direction in which the upper snap guides face. + LLVector3 mSnapDir2; //!< The direction in which the lower snap guides face. + F32 mSnapRegimeOffset; //!< How far off the scale axis centerline the mouse can be before it exits/enters the snap regime. + F32 mTickPixelSpacing1; //!< The pixel spacing between snap guide tick marks for the upper scale. + F32 mTickPixelSpacing2; //!< The pixel spacing between snap guide tick marks for the lower scale. + F32 mSnapGuideLength; + LLVector3 mScaleCenter; //!< The location of the origin of the scaling operation. + LLVector3 mScaleDir; //!< The direction of the scaling action. In face-dragging this is aligned with one of the cardinal axis relative to the prim, but in corner-dragging this is along the diagonal. + F32 mScaleSnappedValue; //!< The distance of the current position nearest the mouse location, measured along mScaleDir. Is measured either from the center or from the far face/corner depending upon whether uniform scaling is true or false respectively. + ESnapRegimes mSnapRegime; // sGridTex = NULL ; - -const LLManip::EManipPart MANIPULATOR_IDS[9] = -{ - LLManip::LL_X_ARROW, - LLManip::LL_Y_ARROW, - LLManip::LL_Z_ARROW, - LLManip::LL_X_ARROW, - LLManip::LL_Y_ARROW, - LLManip::LL_Z_ARROW, - LLManip::LL_YZ_PLANE, - LLManip::LL_XZ_PLANE, - LLManip::LL_XY_PLANE -}; - -const U32 ARROW_TO_AXIS[4] = -{ - VX, - VX, - VY, - VZ -}; - -// Sort manipulator handles by their screen-space projection -struct ClosestToCamera -{ - bool operator()(const LLManipTranslate::ManipulatorHandle& a, - const LLManipTranslate::ManipulatorHandle& b) const - { - return a.mEndPosition.mV[VZ] < b.mEndPosition.mV[VZ]; - } -}; - -LLManipTranslate::LLManipTranslate( LLToolComposite* composite ) -: LLManip( std::string("Move"), composite ), - mLastHoverMouseX(-1), - mLastHoverMouseY(-1), - mMouseOutsideSlop(false), - mCopyMadeThisDrag(false), - mMouseDownX(-1), - mMouseDownY(-1), - mAxisArrowLength(50), - mConeSize(0), - mArrowLengthMeters(0.f), - mGridSizeMeters(1.f), - mPlaneManipOffsetMeters(0.f), - mUpdateTimer(), - mSnapOffsetMeters(0.f), - mSubdivisions(10.f), - mInSnapRegime(false), - mArrowScales(1.f, 1.f, 1.f), - mPlaneScales(1.f, 1.f, 1.f), - mPlaneManipPositions(1.f, 1.f, 1.f, 1.f) -{ - if (sGridTex.isNull()) - { - restoreGL(); - } -} - -//static -U32 LLManipTranslate::getGridTexName() -{ - if(sGridTex.isNull()) - { - restoreGL() ; - } - - return sGridTex.isNull() ? 0 : sGridTex->getTexName() ; -} - -//static -void LLManipTranslate::destroyGL() -{ - if (sGridTex) - { - sGridTex = NULL ; - } -} - -//static -void LLManipTranslate::restoreGL() -{ - //generate grid texture - U32 rez = 512; - U32 mip = 0; - - destroyGL() ; - sGridTex = LLViewerTextureManager::getLocalTexture() ; - if(!sGridTex->createGLTexture()) - { - sGridTex = NULL ; - return ; - } - - GLuint* d = new GLuint[rez*rez]; - - gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, sGridTex->getTexName(), true); - gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_TRILINEAR); - - while (rez >= 1) - { - for (U32 i = 0; i < rez*rez; i++) - { - d[i] = 0x00FFFFFF; - } - - U32 subcol = 0xFFFFFFFF; - if (rez >= 4) - { //large grain grid - for (U32 i = 0; i < rez; i++) - { - if (rez <= 16) - { - if (rez == 16) - { - subcol = 0xA0FFFFFF; - } - else if (rez == 8) - { - subcol = 0x80FFFFFF; - } - else - { - subcol = 0x40FFFFFF; - } - } - else - { - subcol = 0xFFFFFFFF; - } - d[i *rez+ 0 ] = subcol; - d[0 *rez+ i ] = subcol; - if (rez >= 32) - { - d[i *rez+ (rez-1)] = subcol; - d[(rez-1) *rez+ i ] = subcol; - } - - if (rez >= 64) - { - subcol = 0xFFFFFFFF; - - if (i > 0 && i < (rez-1)) - { - d[i *rez+ 1 ] = subcol; - d[i *rez+ (rez-2)] = subcol; - d[1 *rez+ i ] = subcol; - d[(rez-2) *rez+ i ] = subcol; - } - } - } - } - - subcol = 0x50A0A0A0; - if (rez >= 128) - { //small grain grid - for (U32 i = 8; i < rez; i+=8) - { - for (U32 j = 2; j < rez-2; j++) - { - d[i *rez+ j] = subcol; - d[j *rez+ i] = subcol; - } - } - } - if (rez >= 64) - { //medium grain grid - if (rez == 64) - { - subcol = 0x50A0A0A0; - } - else - { - subcol = 0xA0D0D0D0; - } - - for (U32 i = 32; i < rez; i+=32) - { - U32 pi = i-1; - for (U32 j = 2; j < rez-2; j++) - { - d[i *rez+ j] = subcol; - d[j *rez+ i] = subcol; - - if (rez > 128) - { - d[pi *rez+ j] = subcol; - d[j *rez+ pi] = subcol; - } - } - } - } - LLImageGL::setManualImage(GL_TEXTURE_2D, mip, GL_RGBA, rez, rez, GL_RGBA, GL_UNSIGNED_BYTE, d); - rez = rez >> 1; - mip++; - } - delete [] d; -} - - -LLManipTranslate::~LLManipTranslate() -{ -} - - -void LLManipTranslate::handleSelect() -{ - LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); - if (gFloaterTools) - { - gFloaterTools->setStatusText("move"); - } - LLManip::handleSelect(); -} - -bool LLManipTranslate::handleMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - // didn't click in any UI object, so must have clicked in the world - if( (mHighlightedPart == LL_X_ARROW || - mHighlightedPart == LL_Y_ARROW || - mHighlightedPart == LL_Z_ARROW || - mHighlightedPart == LL_YZ_PLANE || - mHighlightedPart == LL_XZ_PLANE || - mHighlightedPart == LL_XY_PLANE ) ) - { - handled = handleMouseDownOnPart( x, y, mask ); - } - - return handled; -} - -// Assumes that one of the arrows on an object was hit. -bool LLManipTranslate::handleMouseDownOnPart( S32 x, S32 y, MASK mask ) -{ - bool can_move = canAffectSelection(); - if (!can_move) - { - return false; - } - - highlightManipulators(x, y); - S32 hit_part = mHighlightedPart; - - if( (hit_part != LL_X_ARROW) && - (hit_part != LL_Y_ARROW) && - (hit_part != LL_Z_ARROW) && - (hit_part != LL_YZ_PLANE) && - (hit_part != LL_XZ_PLANE) && - (hit_part != LL_XY_PLANE) ) - { - return true; - } - - mHelpTextTimer.reset(); - sNumTimesHelpTextShown++; - - LLSelectMgr::getInstance()->getGrid(mGridOrigin, mGridRotation, mGridScale); - - LLSelectMgr::getInstance()->enableSilhouette(false); - - // we just started a drag, so save initial object positions - LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_MOVE); - - mManipPart = (EManipPart)hit_part; - mMouseDownX = x; - mMouseDownY = y; - mMouseOutsideSlop = false; - - LLVector3 axis; - - LLSelectNode *selectNode = mObjectSelection->getFirstMoveableNode(true); - - if (!selectNode) - { - // didn't find the object in our selection...oh well - LL_WARNS() << "Trying to translate an unselected object" << LL_ENDL; - return true; - } - - LLViewerObject *selected_object = selectNode->getObject(); - if (!selected_object) - { - // somehow we lost the object! - LL_WARNS() << "Translate manip lost the object, no selected object" << LL_ENDL; - gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE); - return true; - } - - // Compute unit vectors for arrow hit and a plane through that vector - bool axis_exists = getManipAxis(selected_object, mManipPart, axis); - getManipNormal(selected_object, mManipPart, mManipNormal); - - //LLVector3 select_center_agent = gAgent.getPosAgentFromGlobal(LLSelectMgr::getInstance()->getSelectionCenterGlobal()); - // TomY: The above should (?) be identical to the below - LLVector3 select_center_agent = getPivotPoint(); - mSubdivisions = getSubdivisionLevel(select_center_agent, axis_exists ? axis : LLVector3::z_axis, getMinGridScale()); - - // if we clicked on a planar manipulator, recenter mouse cursor - if (mManipPart >= LL_YZ_PLANE && mManipPart <= LL_XY_PLANE) - { - LLCoordGL mouse_pos; - if (!LLViewerCamera::getInstance()->projectPosAgentToScreen(select_center_agent, mouse_pos)) - { - // mouse_pos may be nonsense - LL_WARNS() << "Failed to project object center to screen" << LL_ENDL; - } - else if (gSavedSettings.getBOOL("SnapToMouseCursor")) - { - LLUI::getInstance()->setMousePositionScreen(mouse_pos.mX, mouse_pos.mY); - x = mouse_pos.mX; - y = mouse_pos.mY; - } - } - - LLSelectMgr::getInstance()->updateSelectionCenter(); - LLVector3d object_start_global = gAgent.getPosGlobalFromAgent(getPivotPoint()); - getMousePointOnPlaneGlobal(mDragCursorStartGlobal, x, y, object_start_global, mManipNormal); - mDragSelectionStartGlobal = object_start_global; - mCopyMadeThisDrag = false; - - // Route future Mouse messages here preemptively. (Release on mouse up.) - setMouseCapture( true ); - - return true; -} - -bool LLManipTranslate::handleHover(S32 x, S32 y, MASK mask) -{ - // Translation tool only works if mouse button is down. - // Bail out if mouse not down. - if( !hasMouseCapture() ) - { - LL_DEBUGS("UserInput") << "hover handled by LLManipTranslate (inactive)" << LL_ENDL; - // Always show cursor - // gViewerWindow->setCursor(UI_CURSOR_ARROW); - gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE); - - highlightManipulators(x, y); - return true; - } - - // Handle auto-rotation if necessary. - LLRect world_rect = gViewerWindow->getWorldViewRectScaled(); - const F32 ROTATE_ANGLE_PER_SECOND = 30.f * DEG_TO_RAD; - const S32 ROTATE_H_MARGIN = world_rect.getWidth() / 20; - const F32 rotate_angle = ROTATE_ANGLE_PER_SECOND / gFPSClamped; - bool rotated = false; - - // ...build mode moves camera about focus point - if (mObjectSelection->getSelectType() != SELECT_TYPE_HUD) - { - if (x < ROTATE_H_MARGIN) - { - gAgentCamera.cameraOrbitAround(rotate_angle); - rotated = true; - } - else if (x > world_rect.getWidth() - ROTATE_H_MARGIN) - { - gAgentCamera.cameraOrbitAround(-rotate_angle); - rotated = true; - } - } - - // Suppress processing if mouse hasn't actually moved. - // This may cause problems if the camera moves outside of the - // rotation above. - if( x == mLastHoverMouseX && y == mLastHoverMouseY && !rotated) - { - LL_DEBUGS("UserInput") << "hover handled by LLManipTranslate (mouse unmoved)" << LL_ENDL; - gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE); - return true; - } - mLastHoverMouseX = x; - mLastHoverMouseY = y; - - // Suppress if mouse hasn't moved past the initial slop region - // Reset once we start moving - if( !mMouseOutsideSlop ) - { - if (abs(mMouseDownX - x) < MOUSE_DRAG_SLOP && abs(mMouseDownY - y) < MOUSE_DRAG_SLOP ) - { - LL_DEBUGS("UserInput") << "hover handled by LLManipTranslate (mouse inside slop)" << LL_ENDL; - gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE); - return true; - } - else - { - // ...just went outside the slop region - mMouseOutsideSlop = true; - // If holding down shift, leave behind a copy. - if (mask == MASK_COPY) - { - // ...we're trying to make a copy - LLSelectMgr::getInstance()->selectDuplicate(LLVector3::zero, false); - mCopyMadeThisDrag = true; - - // When we make the copy, we don't want to do any other processing. - // If so, the object will also be moved, and the copy will be offset. - LL_DEBUGS("UserInput") << "hover handled by LLManipTranslate (made copy)" << LL_ENDL; - gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE); - } - } - } - - // Throttle updates to 10 per second. - - LLVector3 axis_f; - LLVector3d axis_d; - - // pick the first object to constrain to grid w/ common origin - // this is so we don't screw up groups - LLSelectNode* selectNode = mObjectSelection->getFirstMoveableNode(true); - if (!selectNode) - { - // somehow we lost the object! - LL_WARNS() << "Translate manip lost the object, no selectNode" << LL_ENDL; - gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE); - return true; - } - - LLViewerObject* object = selectNode->getObject(); - if (!object) - { - // somehow we lost the object! - LL_WARNS() << "Translate manip lost the object, no object in selectNode" << LL_ENDL; - gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE); - return true; - } - - // Compute unit vectors for arrow hit and a plane through that vector - bool axis_exists = getManipAxis(object, mManipPart, axis_f); // TODO: move this - - axis_d.setVec(axis_f); - - LLSelectMgr::getInstance()->updateSelectionCenter(); - LLVector3d current_pos_global = gAgent.getPosGlobalFromAgent(getPivotPoint()); - - mSubdivisions = getSubdivisionLevel(getPivotPoint(), axis_f, getMinGridScale()); - - // Project the cursor onto that plane - LLVector3d relative_move; - getMousePointOnPlaneGlobal(relative_move, x, y, current_pos_global, mManipNormal);\ - relative_move -= mDragCursorStartGlobal; - - // You can't move more than some distance from your original mousedown point. - if (gSavedSettings.getBOOL("LimitDragDistance")) - { - F32 max_drag_distance = gSavedSettings.getF32("MaxDragDistance"); - - if (relative_move.magVecSquared() > max_drag_distance * max_drag_distance) - { - LL_DEBUGS("UserInput") << "hover handled by LLManipTranslate (too far)" << LL_ENDL; - gViewerWindow->setCursor(UI_CURSOR_NOLOCKED); - return true; - } - } - - F64 axis_magnitude = relative_move * axis_d; // dot product - LLVector3d cursor_point_snap_line; - - F64 off_axis_magnitude; - - getMousePointOnPlaneGlobal(cursor_point_snap_line, x, y, current_pos_global, mSnapOffsetAxis % axis_f); - off_axis_magnitude = axis_exists ? llabs((cursor_point_snap_line - current_pos_global) * LLVector3d(mSnapOffsetAxis)) : 0.f; - - if (gSavedSettings.getBOOL("SnapEnabled")) - { - if (off_axis_magnitude > mSnapOffsetMeters) - { - mInSnapRegime = true; - LLVector3 cursor_snap_agent = gAgent.getPosAgentFromGlobal(cursor_point_snap_line); - - F32 cursor_grid_dist = (cursor_snap_agent - mGridOrigin) * axis_f; - - F32 snap_dist = getMinGridScale() / (2.f * mSubdivisions); - F32 relative_snap_dist = fmodf(llabs(cursor_grid_dist) + snap_dist, getMinGridScale() / mSubdivisions); - if (relative_snap_dist < snap_dist * 2.f) - { - if (cursor_grid_dist > 0.f) - { - cursor_grid_dist -= relative_snap_dist - snap_dist; - } - else - { - cursor_grid_dist += relative_snap_dist - snap_dist; - } - } - - F32 object_start_on_axis = (gAgent.getPosAgentFromGlobal(mDragSelectionStartGlobal) - mGridOrigin) * axis_f; - axis_magnitude = cursor_grid_dist - object_start_on_axis; - } - else if (mManipPart >= LL_YZ_PLANE && mManipPart <= LL_XY_PLANE) - { - // subtract offset from object center - LLVector3d cursor_point_global; - getMousePointOnPlaneGlobal( cursor_point_global, x, y, current_pos_global, mManipNormal ); - cursor_point_global -= (mDragCursorStartGlobal - mDragSelectionStartGlobal); - - // snap to planar grid - LLVector3 cursor_point_agent = gAgent.getPosAgentFromGlobal(cursor_point_global); - LLVector3 camera_plane_projection = LLViewerCamera::getInstance()->getAtAxis(); - camera_plane_projection -= projected_vec(camera_plane_projection, mManipNormal); - camera_plane_projection.normVec(); - LLVector3 camera_projected_dir = camera_plane_projection; - camera_plane_projection.rotVec(~mGridRotation); - camera_plane_projection.scaleVec(mGridScale); - camera_plane_projection.abs(); - F32 max_grid_scale; - if (camera_plane_projection.mV[VX] > camera_plane_projection.mV[VY] && - camera_plane_projection.mV[VX] > camera_plane_projection.mV[VZ]) - { - max_grid_scale = mGridScale.mV[VX]; - } - else if (camera_plane_projection.mV[VY] > camera_plane_projection.mV[VZ]) - { - max_grid_scale = mGridScale.mV[VY]; - } - else - { - max_grid_scale = mGridScale.mV[VZ]; - } - - F32 num_subdivisions = getSubdivisionLevel(getPivotPoint(), camera_projected_dir, max_grid_scale); - - F32 grid_scale_a; - F32 grid_scale_b; - LLVector3 cursor_point_grid = (cursor_point_agent - mGridOrigin) * ~mGridRotation; - - switch (mManipPart) - { - case LL_YZ_PLANE: - grid_scale_a = mGridScale.mV[VY] / num_subdivisions; - grid_scale_b = mGridScale.mV[VZ] / num_subdivisions; - cursor_point_grid.mV[VY] -= fmod(cursor_point_grid.mV[VY] + grid_scale_a * 0.5f, grid_scale_a) - grid_scale_a * 0.5f; - cursor_point_grid.mV[VZ] -= fmod(cursor_point_grid.mV[VZ] + grid_scale_b * 0.5f, grid_scale_b) - grid_scale_b * 0.5f; - break; - case LL_XZ_PLANE: - grid_scale_a = mGridScale.mV[VX] / num_subdivisions; - grid_scale_b = mGridScale.mV[VZ] / num_subdivisions; - cursor_point_grid.mV[VX] -= fmod(cursor_point_grid.mV[VX] + grid_scale_a * 0.5f, grid_scale_a) - grid_scale_a * 0.5f; - cursor_point_grid.mV[VZ] -= fmod(cursor_point_grid.mV[VZ] + grid_scale_b * 0.5f, grid_scale_b) - grid_scale_b * 0.5f; - break; - case LL_XY_PLANE: - grid_scale_a = mGridScale.mV[VX] / num_subdivisions; - grid_scale_b = mGridScale.mV[VY] / num_subdivisions; - cursor_point_grid.mV[VX] -= fmod(cursor_point_grid.mV[VX] + grid_scale_a * 0.5f, grid_scale_a) - grid_scale_a * 0.5f; - cursor_point_grid.mV[VY] -= fmod(cursor_point_grid.mV[VY] + grid_scale_b * 0.5f, grid_scale_b) - grid_scale_b * 0.5f; - break; - default: - break; - } - cursor_point_agent = (cursor_point_grid * mGridRotation) + mGridOrigin; - relative_move.setVec(cursor_point_agent - gAgent.getPosAgentFromGlobal(mDragSelectionStartGlobal)); - mInSnapRegime = true; - } - else - { - mInSnapRegime = false; - } - } - else - { - mInSnapRegime = false; - } - - // Clamp to arrow direction - // *FIX: does this apply anymore? - if (!axis_exists) - { - axis_magnitude = relative_move.normVec(); - axis_d.setVec(relative_move); - axis_d.normVec(); - axis_f.setVec(axis_d); - } - - LLVector3d clamped_relative_move = axis_magnitude * axis_d; // scalar multiply - LLVector3 clamped_relative_move_f = (F32)axis_magnitude * axis_f; // scalar multiply - - for (LLObjectSelection::iterator iter = mObjectSelection->begin(); - iter != mObjectSelection->end(); iter++) - { - LLSelectNode* selectNode = *iter; - LLViewerObject* object = selectNode->getObject(); - - // Only apply motion to root objects and objects selected - // as "individual". - if (!object->isRootEdit() && !selectNode->mIndividualSelection) - { - continue; - } - - if (!object->isRootEdit()) - { - // child objects should not update if parent is selected - LLViewerObject* editable_root = (LLViewerObject*)object->getParent(); - if (editable_root->isSelected()) - { - // we will be moved properly by our parent, so skip - continue; - } - } - - LLViewerObject* root_object = (object == NULL) ? NULL : object->getRootEdit(); - if (object->permMove() && !object->isPermanentEnforced() && - ((root_object == NULL) || !root_object->isPermanentEnforced())) - { - // handle attachments in local space - if (object->isAttachment() && object->mDrawable.notNull()) - { - // calculate local version of relative move - LLQuaternion objWorldRotation = object->mDrawable->mXform.getParent()->getWorldRotation(); - objWorldRotation.transQuat(); - - LLVector3 old_position_local = object->getPosition(); - LLVector3 new_position_local = selectNode->mSavedPositionLocal + (clamped_relative_move_f * objWorldRotation); - - //RN: I forget, but we need to do this because of snapping which doesn't often result - // in position changes even when the mouse moves - object->setPosition(new_position_local); - rebuild(object); - gAgentAvatarp->clampAttachmentPositions(); - new_position_local = object->getPosition(); - - if (selectNode->mIndividualSelection) - { - // counter-translate child objects if we are moving the root as an individual - object->resetChildrenPosition(old_position_local - new_position_local, true) ; - } - } - else - { - // compute new position to send to simulators, but don't set it yet. - // We need the old position to know which simulator to send the move message to. - LLVector3d new_position_global = selectNode->mSavedPositionGlobal + clamped_relative_move; - - // Don't let object centers go too far underground - F64 min_height = LLWorld::getInstance()->getMinAllowedZ(object, object->getPositionGlobal()); - if (new_position_global.mdV[VZ] < min_height) - { - new_position_global.mdV[VZ] = min_height; - } - - // For safety, cap heights where objects can be dragged - if (new_position_global.mdV[VZ] > MAX_OBJECT_Z) - { - new_position_global.mdV[VZ] = MAX_OBJECT_Z; - } - - // Grass is always drawn on the ground, so clamp its position to the ground - if (object->getPCode() == LL_PCODE_LEGACY_GRASS) - { - new_position_global.mdV[VZ] = LLWorld::getInstance()->resolveLandHeightGlobal(new_position_global) + 1.f; - } - - if (object->isRootEdit()) - { - new_position_global = LLWorld::getInstance()->clipToVisibleRegions(object->getPositionGlobal(), new_position_global); - } - - // PR: Only update if changed - LLVector3 old_position_agent = object->getPositionAgent(); - LLVector3 new_position_agent = gAgent.getPosAgentFromGlobal(new_position_global); - if (object->isRootEdit()) - { - // finally, move parent object after children have calculated new offsets - object->setPositionAgent(new_position_agent); - rebuild(object); - } - else - { - LLViewerObject* root_object = object->getRootEdit(); - new_position_agent -= root_object->getPositionAgent(); - new_position_agent = new_position_agent * ~root_object->getRotation(); - object->setPositionParent(new_position_agent, false); - rebuild(object); - } - - if (selectNode->mIndividualSelection) - { - // counter-translate child objects if we are moving the root as an individual - object->resetChildrenPosition(old_position_agent - new_position_agent, true) ; - } - } - selectNode->mLastPositionLocal = object->getPosition(); - } - } - - LLSelectMgr::getInstance()->updateSelectionCenter(); - gAgentCamera.clearFocusObject(); - dialog_refresh_all(); // ??? is this necessary? - - LL_DEBUGS("UserInput") << "hover handled by LLManipTranslate (active)" << LL_ENDL; - gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE); - return true; -} - -void LLManipTranslate::highlightManipulators(S32 x, S32 y) -{ - mHighlightedPart = LL_NO_PART; - - if (!mObjectSelection->getObjectCount()) - { - return; - } - - //LLBBox bbox = LLSelectMgr::getInstance()->getBBoxOfSelection(); - LLMatrix4 projMatrix = LLViewerCamera::getInstance()->getProjection(); - LLMatrix4 modelView = LLViewerCamera::getInstance()->getModelview(); - - LLVector3 object_position = getPivotPoint(); - - LLVector3 grid_origin; - LLVector3 grid_scale; - LLQuaternion grid_rotation; - - LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale); - - LLVector3 relative_camera_dir; - - LLMatrix4 transform; - - if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) - { - relative_camera_dir = LLVector3(1.f, 0.f, 0.f) * ~grid_rotation; - LLVector4 translation(object_position); - transform.initRotTrans(grid_rotation, translation); - LLMatrix4 cfr(OGL_TO_CFR_ROTATION); - transform *= cfr; - LLMatrix4 window_scale; - F32 zoom_level = 2.f * gAgentCamera.mHUDCurZoom; - window_scale.initAll(LLVector3(zoom_level / LLViewerCamera::getInstance()->getAspect(), zoom_level, 0.f), - LLQuaternion::DEFAULT, - LLVector3::zero); - transform *= window_scale; - } - else - { - relative_camera_dir = (object_position - LLViewerCamera::getInstance()->getOrigin()) * ~grid_rotation; - relative_camera_dir.normVec(); - - transform.initRotTrans(grid_rotation, LLVector4(object_position)); - transform *= modelView; - transform *= projMatrix; - } - - S32 numManips = 0; - - // edges - mManipulatorVertices[numManips++] = LLVector4(mArrowLengthMeters * MANIPULATOR_HOTSPOT_START, 0.f, 0.f, 1.f); - mManipulatorVertices[numManips++] = LLVector4(mArrowLengthMeters * MANIPULATOR_HOTSPOT_END, 0.f, 0.f, 1.f); - - mManipulatorVertices[numManips++] = LLVector4(0.f, mArrowLengthMeters * MANIPULATOR_HOTSPOT_START, 0.f, 1.f); - mManipulatorVertices[numManips++] = LLVector4(0.f, mArrowLengthMeters * MANIPULATOR_HOTSPOT_END, 0.f, 1.f); - - mManipulatorVertices[numManips++] = LLVector4(0.f, 0.f, mArrowLengthMeters * MANIPULATOR_HOTSPOT_START, 1.f); - mManipulatorVertices[numManips++] = LLVector4(0.f, 0.f, mArrowLengthMeters * MANIPULATOR_HOTSPOT_END, 1.f); - - mManipulatorVertices[numManips++] = LLVector4(mArrowLengthMeters * -MANIPULATOR_HOTSPOT_START, 0.f, 0.f, 1.f); - mManipulatorVertices[numManips++] = LLVector4(mArrowLengthMeters * -MANIPULATOR_HOTSPOT_END, 0.f, 0.f, 1.f); - - mManipulatorVertices[numManips++] = LLVector4(0.f, mArrowLengthMeters * -MANIPULATOR_HOTSPOT_START, 0.f, 1.f); - mManipulatorVertices[numManips++] = LLVector4(0.f, mArrowLengthMeters * -MANIPULATOR_HOTSPOT_END, 0.f, 1.f); - - mManipulatorVertices[numManips++] = LLVector4(0.f, 0.f, mArrowLengthMeters * -MANIPULATOR_HOTSPOT_START, 1.f); - mManipulatorVertices[numManips++] = LLVector4(0.f, 0.f, mArrowLengthMeters * -MANIPULATOR_HOTSPOT_END, 1.f); - - S32 num_arrow_manips = numManips; - - // planar manipulators - bool planar_manip_yz_visible = false; - bool planar_manip_xz_visible = false; - bool planar_manip_xy_visible = false; - - mManipulatorVertices[numManips] = LLVector4(0.f, mPlaneManipOffsetMeters * (1.f - PLANE_TICK_SIZE * 0.5f), mPlaneManipOffsetMeters * (1.f - PLANE_TICK_SIZE * 0.5f), 1.f); - mManipulatorVertices[numManips++].scaleVec(mPlaneManipPositions); - mManipulatorVertices[numManips] = LLVector4(0.f, mPlaneManipOffsetMeters * (1.f + PLANE_TICK_SIZE * 0.5f), mPlaneManipOffsetMeters * (1.f + PLANE_TICK_SIZE * 0.5f), 1.f); - mManipulatorVertices[numManips++].scaleVec(mPlaneManipPositions); - if (llabs(relative_camera_dir.mV[VX]) > MIN_PLANE_MANIP_DOT_PRODUCT) - { - planar_manip_yz_visible = true; - } - - mManipulatorVertices[numManips] = LLVector4(mPlaneManipOffsetMeters * (1.f - PLANE_TICK_SIZE * 0.5f), 0.f, mPlaneManipOffsetMeters * (1.f - PLANE_TICK_SIZE * 0.5f), 1.f); - mManipulatorVertices[numManips++].scaleVec(mPlaneManipPositions); - mManipulatorVertices[numManips] = LLVector4(mPlaneManipOffsetMeters * (1.f + PLANE_TICK_SIZE * 0.5f), 0.f, mPlaneManipOffsetMeters * (1.f + PLANE_TICK_SIZE * 0.5f), 1.f); - mManipulatorVertices[numManips++].scaleVec(mPlaneManipPositions); - if (llabs(relative_camera_dir.mV[VY]) > MIN_PLANE_MANIP_DOT_PRODUCT) - { - planar_manip_xz_visible = true; - } - - mManipulatorVertices[numManips] = LLVector4(mPlaneManipOffsetMeters * (1.f - PLANE_TICK_SIZE * 0.5f), mPlaneManipOffsetMeters * (1.f - PLANE_TICK_SIZE * 0.5f), 0.f, 1.f); - mManipulatorVertices[numManips++].scaleVec(mPlaneManipPositions); - mManipulatorVertices[numManips] = LLVector4(mPlaneManipOffsetMeters * (1.f + PLANE_TICK_SIZE * 0.5f), mPlaneManipOffsetMeters * (1.f + PLANE_TICK_SIZE * 0.5f), 0.f, 1.f); - mManipulatorVertices[numManips++].scaleVec(mPlaneManipPositions); - if (llabs(relative_camera_dir.mV[VZ]) > MIN_PLANE_MANIP_DOT_PRODUCT) - { - planar_manip_xy_visible = true; - } - - // Project up to 9 manipulators to screen space 2*X, 2*Y, 2*Z, 3*planes - std::vector projected_manipulators; - projected_manipulators.reserve(9); - - for (S32 i = 0; i < num_arrow_manips; i+= 2) - { - LLVector4 projected_start = mManipulatorVertices[i] * transform; - projected_start = projected_start * (1.f / projected_start.mV[VW]); - - LLVector4 projected_end = mManipulatorVertices[i + 1] * transform; - projected_end = projected_end * (1.f / projected_end.mV[VW]); - - ManipulatorHandle projected_manip( - LLVector3(projected_start.mV[VX], projected_start.mV[VY], projected_start.mV[VZ]), - LLVector3(projected_end.mV[VX], projected_end.mV[VY], projected_end.mV[VZ]), - MANIPULATOR_IDS[i / 2], - 10.f); // 10 pixel hotspot for arrows - projected_manipulators.push_back(projected_manip); - } - - if (planar_manip_yz_visible) - { - S32 i = num_arrow_manips; - LLVector4 projected_start = mManipulatorVertices[i] * transform; - projected_start = projected_start * (1.f / projected_start.mV[VW]); - - LLVector4 projected_end = mManipulatorVertices[i + 1] * transform; - projected_end = projected_end * (1.f / projected_end.mV[VW]); - - ManipulatorHandle projected_manip( - LLVector3(projected_start.mV[VX], projected_start.mV[VY], projected_start.mV[VZ]), - LLVector3(projected_end.mV[VX], projected_end.mV[VY], projected_end.mV[VZ]), - MANIPULATOR_IDS[i / 2], - 20.f); // 20 pixels for planar manipulators - projected_manipulators.push_back(projected_manip); - } - - if (planar_manip_xz_visible) - { - S32 i = num_arrow_manips + 2; - LLVector4 projected_start = mManipulatorVertices[i] * transform; - projected_start = projected_start * (1.f / projected_start.mV[VW]); - - LLVector4 projected_end = mManipulatorVertices[i + 1] * transform; - projected_end = projected_end * (1.f / projected_end.mV[VW]); - - ManipulatorHandle projected_manip( - LLVector3(projected_start.mV[VX], projected_start.mV[VY], projected_start.mV[VZ]), - LLVector3(projected_end.mV[VX], projected_end.mV[VY], projected_end.mV[VZ]), - MANIPULATOR_IDS[i / 2], - 20.f); // 20 pixels for planar manipulators - projected_manipulators.push_back(projected_manip); - } - - if (planar_manip_xy_visible) - { - S32 i = num_arrow_manips + 4; - LLVector4 projected_start = mManipulatorVertices[i] * transform; - projected_start = projected_start * (1.f / projected_start.mV[VW]); - - LLVector4 projected_end = mManipulatorVertices[i + 1] * transform; - projected_end = projected_end * (1.f / projected_end.mV[VW]); - - ManipulatorHandle projected_manip( - LLVector3(projected_start.mV[VX], projected_start.mV[VY], projected_start.mV[VZ]), - LLVector3(projected_end.mV[VX], projected_end.mV[VY], projected_end.mV[VZ]), - MANIPULATOR_IDS[i / 2], - 20.f); // 20 pixels for planar manipulators - projected_manipulators.push_back(projected_manip); - } - - LLVector2 manip_start_2d; - LLVector2 manip_end_2d; - LLVector2 manip_dir; - LLRect world_view_rect = gViewerWindow->getWorldViewRectScaled(); - F32 half_width = (F32)world_view_rect.getWidth() / 2.f; - F32 half_height = (F32)world_view_rect.getHeight() / 2.f; - LLVector2 mousePos((F32)x - half_width, (F32)y - half_height); - LLVector2 mouse_delta; - - // Keep order consistent with insertion via stable_sort - std::stable_sort( projected_manipulators.begin(), - projected_manipulators.end(), - ClosestToCamera() ); - - std::vector::iterator it = projected_manipulators.begin(); - for ( ; it != projected_manipulators.end(); ++it) - { - ManipulatorHandle& manipulator = *it; - { - manip_start_2d.setVec(manipulator.mStartPosition.mV[VX] * half_width, manipulator.mStartPosition.mV[VY] * half_height); - manip_end_2d.setVec(manipulator.mEndPosition.mV[VX] * half_width, manipulator.mEndPosition.mV[VY] * half_height); - manip_dir = manip_end_2d - manip_start_2d; - - mouse_delta = mousePos - manip_start_2d; - - F32 manip_length = manip_dir.normVec(); - - F32 mouse_pos_manip = mouse_delta * manip_dir; - F32 mouse_dist_manip_squared = mouse_delta.magVecSquared() - (mouse_pos_manip * mouse_pos_manip); - - if (mouse_pos_manip > 0.f && - mouse_pos_manip < manip_length && - mouse_dist_manip_squared < manipulator.mHotSpotRadius * manipulator.mHotSpotRadius) - { - mHighlightedPart = manipulator.mManipID; - break; - } - } - } -} - -F32 LLManipTranslate::getMinGridScale() -{ - F32 scale; - switch (mManipPart) - { - case LL_NO_PART: - default: - scale = 1.f; - break; - case LL_X_ARROW: - scale = mGridScale.mV[VX]; - break; - case LL_Y_ARROW: - scale = mGridScale.mV[VY]; - break; - case LL_Z_ARROW: - scale = mGridScale.mV[VZ]; - break; - case LL_YZ_PLANE: - scale = llmin(mGridScale.mV[VY], mGridScale.mV[VZ]); - break; - case LL_XZ_PLANE: - scale = llmin(mGridScale.mV[VX], mGridScale.mV[VZ]); - break; - case LL_XY_PLANE: - scale = llmin(mGridScale.mV[VX], mGridScale.mV[VY]); - break; - } - - return scale; -} - - -bool LLManipTranslate::handleMouseUp(S32 x, S32 y, MASK mask) -{ - // first, perform normal processing in case this was a quick-click - handleHover(x, y, mask); - - if(hasMouseCapture()) - { - // make sure arrow colors go back to normal - mManipPart = LL_NO_PART; - LLSelectMgr::getInstance()->enableSilhouette(true); - - // Might have missed last update due to UPDATE_DELAY timing. - LLSelectMgr::getInstance()->sendMultipleUpdate( UPD_POSITION ); - - mInSnapRegime = false; - LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); - //gAgent.setObjectTracking(gSavedSettings.getBOOL("TrackFocusObject")); - } - - return LLManip::handleMouseUp(x, y, mask); -} - - -void LLManipTranslate::render() -{ - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) - { - F32 zoom = gAgentCamera.mHUDCurZoom; - gGL.scalef(zoom, zoom, zoom); - } - { - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); - renderGuidelines(); - } - { - //LLGLDisable gls_stencil(GL_STENCIL_TEST); - renderTranslationHandles(); - renderSnapGuides(); - } - gGL.popMatrix(); - - renderText(); -} - -void LLManipTranslate::renderSnapGuides() -{ - if (!gSavedSettings.getBOOL("SnapEnabled")) - { - return; - } - - F32 max_subdivisions = sGridMaxSubdivisionLevel;//(F32)gSavedSettings.getS32("GridSubdivision"); - F32 line_alpha = gSavedSettings.getF32("GridOpacity"); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - LLGLDepthTest gls_depth(GL_TRUE); - LLGLDisable gls_cull(GL_CULL_FACE); - LLVector3 translate_axis; - - if (mManipPart == LL_NO_PART) - { - return; - } - - LLSelectNode *first_node = mObjectSelection->getFirstMoveableNode(true); - if (!first_node) - { - return; - } - - updateGridSettings(); - - F32 smallest_grid_unit_scale = getMinGridScale() / max_subdivisions; - LLVector3 grid_origin; - LLVector3 grid_scale; - LLQuaternion grid_rotation; - - LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale); - LLVector3 saved_selection_center = getSavedPivotPoint(); //LLSelectMgr::getInstance()->getSavedBBoxOfSelection().getCenterAgent(); - LLVector3 selection_center = getPivotPoint(); - - LLViewerObject *first_object = first_node->getObject(); - - //pick appropriate projection plane for snap rulers according to relative camera position - if (mManipPart >= LL_X_ARROW && mManipPart <= LL_Z_ARROW) - { - LLVector3 normal; - LLColor4 inner_color; - LLManip::EManipPart temp_manip = mManipPart; - switch (mManipPart) - { - case LL_X_ARROW: - normal.setVec(1,0,0); - inner_color.setVec(0,1,1,line_alpha); - mManipPart = LL_YZ_PLANE; - break; - case LL_Y_ARROW: - normal.setVec(0,1,0); - inner_color.setVec(1,0,1,line_alpha); - mManipPart = LL_XZ_PLANE; - break; - case LL_Z_ARROW: - normal.setVec(0,0,1); - inner_color.setVec(1,1,0,line_alpha); - mManipPart = LL_XY_PLANE; - break; - default: - break; - } - - highlightIntersection(normal, selection_center, grid_rotation, inner_color); - mManipPart = temp_manip; - getManipAxis(first_object, mManipPart, translate_axis); - - LLVector3 at_axis_abs; - if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) - { - at_axis_abs = LLVector3::x_axis * ~grid_rotation; - } - else - { - at_axis_abs = saved_selection_center - LLViewerCamera::getInstance()->getOrigin(); - at_axis_abs.normVec(); - - at_axis_abs = at_axis_abs * ~grid_rotation; - } - at_axis_abs.abs(); - - if (at_axis_abs.mV[VX] > at_axis_abs.mV[VY] && at_axis_abs.mV[VX] > at_axis_abs.mV[VZ]) - { - if (mManipPart == LL_Y_ARROW) - { - mSnapOffsetAxis = LLVector3::z_axis; - } - else if (mManipPart == LL_Z_ARROW) - { - mSnapOffsetAxis = LLVector3::y_axis; - } - else if (at_axis_abs.mV[VY] > at_axis_abs.mV[VZ]) - { - mSnapOffsetAxis = LLVector3::z_axis; - } - else - { - mSnapOffsetAxis = LLVector3::y_axis; - } - } - else if (at_axis_abs.mV[VY] > at_axis_abs.mV[VZ]) - { - if (mManipPart == LL_X_ARROW) - { - mSnapOffsetAxis = LLVector3::z_axis; - } - else if (mManipPart == LL_Z_ARROW) - { - mSnapOffsetAxis = LLVector3::x_axis; - } - else if (at_axis_abs.mV[VX] > at_axis_abs.mV[VZ]) - { - mSnapOffsetAxis = LLVector3::z_axis; - } - else - { - mSnapOffsetAxis = LLVector3::x_axis; - } - } - else - { - if (mManipPart == LL_X_ARROW) - { - mSnapOffsetAxis = LLVector3::y_axis; - } - else if (mManipPart == LL_Y_ARROW) - { - mSnapOffsetAxis = LLVector3::x_axis; - } - else if (at_axis_abs.mV[VX] > at_axis_abs.mV[VY]) - { - mSnapOffsetAxis = LLVector3::y_axis; - } - else - { - mSnapOffsetAxis = LLVector3::x_axis; - } - } - - mSnapOffsetAxis = mSnapOffsetAxis * grid_rotation; - - F32 guide_size_meters; - - if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) - { - guide_size_meters = 1.f / gAgentCamera.mHUDCurZoom; - mSnapOffsetMeters = mArrowLengthMeters * 1.5f; - } - else - { - LLVector3 cam_to_selection = getPivotPoint() - LLViewerCamera::getInstance()->getOrigin(); - F32 current_range = cam_to_selection.normVec(); - guide_size_meters = SNAP_GUIDE_SCREEN_SIZE * gViewerWindow->getWorldViewHeightRaw() * current_range / LLViewerCamera::getInstance()->getPixelMeterRatio(); - - F32 fraction_of_fov = mAxisArrowLength / (F32) LLViewerCamera::getInstance()->getViewHeightInPixels(); - F32 apparent_angle = fraction_of_fov * LLViewerCamera::getInstance()->getView(); // radians - F32 offset_at_camera = tan(apparent_angle) * 1.5f; - F32 range = dist_vec(gAgent.getPosAgentFromGlobal(first_node->mSavedPositionGlobal), LLViewerCamera::getInstance()->getOrigin()); - mSnapOffsetMeters = range * offset_at_camera; - } - - LLVector3 tick_start; - LLVector3 tick_end; - - // how far away from grid origin is the selection along the axis of translation? - F32 dist_grid_axis = (selection_center - mGridOrigin) * translate_axis; - // find distance to nearest smallest grid unit - F32 offset_nearest_grid_unit = fmodf(dist_grid_axis, smallest_grid_unit_scale); - // how many smallest grid units are we away from largest grid scale? - S32 sub_div_offset = ll_round(fmodf(dist_grid_axis - offset_nearest_grid_unit, getMinGridScale() / sGridMinSubdivisionLevel) / smallest_grid_unit_scale); - S32 num_ticks_per_side = llmax(1, llfloor(0.5f * guide_size_meters / smallest_grid_unit_scale)); - - LLGLDepthTest gls_depth(GL_FALSE); - - for (S32 pass = 0; pass < 3; pass++) - { - LLColor4 line_color = setupSnapGuideRenderPass(pass); - LLGLDepthTest gls_depth(pass != 1); - - gGL.begin(LLRender::LINES); - { - LLVector3 line_start = selection_center + (mSnapOffsetMeters * mSnapOffsetAxis) + (translate_axis * (guide_size_meters * 0.5f + offset_nearest_grid_unit)); - LLVector3 line_end = selection_center + (mSnapOffsetMeters * mSnapOffsetAxis) - (translate_axis * (guide_size_meters * 0.5f + offset_nearest_grid_unit)); - LLVector3 line_mid = (line_start + line_end) * 0.5f; - - gGL.color4f(line_color.mV[VX], line_color.mV[VY], line_color.mV[VZ], line_color.mV[VW] * 0.2f); - gGL.vertex3fv(line_start.mV); - gGL.color4f(line_color.mV[VX], line_color.mV[VY], line_color.mV[VZ], line_color.mV[VW]); - gGL.vertex3fv(line_mid.mV); - gGL.vertex3fv(line_mid.mV); - gGL.color4f(line_color.mV[VX], line_color.mV[VY], line_color.mV[VZ], line_color.mV[VW] * 0.2f); - gGL.vertex3fv(line_end.mV); - - line_start.setVec(selection_center + (mSnapOffsetAxis * -mSnapOffsetMeters) + (translate_axis * guide_size_meters * 0.5f)); - line_end.setVec(selection_center + (mSnapOffsetAxis * -mSnapOffsetMeters) - (translate_axis * guide_size_meters * 0.5f)); - line_mid = (line_start + line_end) * 0.5f; - - gGL.color4f(line_color.mV[VX], line_color.mV[VY], line_color.mV[VZ], line_color.mV[VW] * 0.2f); - gGL.vertex3fv(line_start.mV); - gGL.color4f(line_color.mV[VX], line_color.mV[VY], line_color.mV[VZ], line_color.mV[VW]); - gGL.vertex3fv(line_mid.mV); - gGL.vertex3fv(line_mid.mV); - gGL.color4f(line_color.mV[VX], line_color.mV[VY], line_color.mV[VZ], line_color.mV[VW] * 0.2f); - gGL.vertex3fv(line_end.mV); - - for (S32 i = -num_ticks_per_side; i <= num_ticks_per_side; i++) - { - tick_start = selection_center + (translate_axis * (smallest_grid_unit_scale * (F32)i - offset_nearest_grid_unit)); - - //No need check this condition to prevent tick position scaling (FIX MAINT-5207/5208) - //F32 cur_subdivisions = getSubdivisionLevel(tick_start, translate_axis, getMinGridScale()); - /*if (fmodf((F32)(i + sub_div_offset), (max_subdivisions / cur_subdivisions)) != 0.f) - { - continue; - }*/ - - // add in off-axis offset - tick_start += (mSnapOffsetAxis * mSnapOffsetMeters); - - F32 tick_scale = 1.f; - for (F32 division_level = max_subdivisions; division_level >= sGridMinSubdivisionLevel; division_level /= 2.f) - { - if (fmodf((F32)(i + sub_div_offset), division_level) == 0.f) - { - break; - } - tick_scale *= 0.7f; - } - -// S32 num_ticks_to_fade = is_sub_tick ? num_ticks_per_side / 2 : num_ticks_per_side; -// F32 alpha = line_alpha * (1.f - (0.8f * ((F32)llabs(i) / (F32)num_ticks_to_fade))); - - tick_end = tick_start + (mSnapOffsetAxis * mSnapOffsetMeters * tick_scale); - - gGL.color4f(line_color.mV[VX], line_color.mV[VY], line_color.mV[VZ], line_color.mV[VW]); - gGL.vertex3fv(tick_start.mV); - gGL.vertex3fv(tick_end.mV); - - tick_start = selection_center + (mSnapOffsetAxis * -mSnapOffsetMeters) + - (translate_axis * (getMinGridScale() / (F32)(max_subdivisions) * (F32)i - offset_nearest_grid_unit)); - tick_end = tick_start - (mSnapOffsetAxis * mSnapOffsetMeters * tick_scale); - - gGL.vertex3fv(tick_start.mV); - gGL.vertex3fv(tick_end.mV); - } - } - gGL.end(); - - if (mInSnapRegime) - { - LLVector3 line_start = selection_center - mSnapOffsetAxis * mSnapOffsetMeters; - LLVector3 line_end = selection_center + mSnapOffsetAxis * mSnapOffsetMeters; - - gGL.begin(LLRender::LINES); - { - gGL.color4f(line_color.mV[VX], line_color.mV[VY], line_color.mV[VZ], line_color.mV[VW]); - - gGL.vertex3fv(line_start.mV); - gGL.vertex3fv(line_end.mV); - } - gGL.end(); - - // draw snap guide arrow - gGL.begin(LLRender::TRIANGLES); - { - gGL.color4f(line_color.mV[VX], line_color.mV[VY], line_color.mV[VZ], line_color.mV[VW]); - - LLVector3 arrow_dir; - LLVector3 arrow_span = translate_axis; - - arrow_dir = -mSnapOffsetAxis; - gGL.vertex3fv((line_start + arrow_dir * mConeSize * SNAP_ARROW_SCALE).mV); - gGL.vertex3fv((line_start + arrow_span * mConeSize * SNAP_ARROW_SCALE).mV); - gGL.vertex3fv((line_start - arrow_span * mConeSize * SNAP_ARROW_SCALE).mV); - - arrow_dir = mSnapOffsetAxis; - gGL.vertex3fv((line_end + arrow_dir * mConeSize * SNAP_ARROW_SCALE).mV); - gGL.vertex3fv((line_end + arrow_span * mConeSize * SNAP_ARROW_SCALE).mV); - gGL.vertex3fv((line_end - arrow_span * mConeSize * SNAP_ARROW_SCALE).mV); - } - gGL.end(); - } - } - - sub_div_offset = ll_round(fmod(dist_grid_axis - offset_nearest_grid_unit, getMinGridScale() * 32.f) / smallest_grid_unit_scale); - - LLVector2 screen_translate_axis(llabs(translate_axis * LLViewerCamera::getInstance()->getLeftAxis()), llabs(translate_axis * LLViewerCamera::getInstance()->getUpAxis())); - screen_translate_axis.normVec(); - - S32 tick_label_spacing = ll_round(screen_translate_axis * sTickLabelSpacing); - - // render tickmark values - for (S32 i = -num_ticks_per_side; i <= num_ticks_per_side; i++) - { - LLVector3 tick_pos = selection_center + (translate_axis * ((smallest_grid_unit_scale * (F32)i) - offset_nearest_grid_unit)); - F32 alpha = line_alpha * (1.f - (0.5f * ((F32)llabs(i) / (F32)num_ticks_per_side))); - - F32 tick_scale = 1.f; - for (F32 division_level = max_subdivisions; division_level >= sGridMinSubdivisionLevel; division_level /= 2.f) - { - if (fmodf((F32)(i + sub_div_offset), division_level) == 0.f) - { - break; - } - tick_scale *= 0.7f; - } - - if (fmodf((F32)(i + sub_div_offset), (max_subdivisions / getSubdivisionLevel(tick_pos, translate_axis, getMinGridScale(), tick_label_spacing))) == 0.f) - { - F32 snap_offset_meters; - - if (mSnapOffsetAxis * LLViewerCamera::getInstance()->getUpAxis() > 0.f) - { - snap_offset_meters = mSnapOffsetMeters; - } - else - { - snap_offset_meters = -mSnapOffsetMeters; - } - LLVector3 text_origin = selection_center + - (translate_axis * ((smallest_grid_unit_scale * (F32)i) - offset_nearest_grid_unit)) + - (mSnapOffsetAxis * snap_offset_meters * (1.f + tick_scale)); - - LLVector3 tick_offset = (tick_pos - mGridOrigin) * ~mGridRotation; - F32 offset_val = 0.5f * tick_offset.mV[ARROW_TO_AXIS[mManipPart]] / getMinGridScale(); - EGridMode grid_mode = LLSelectMgr::getInstance()->getGridMode(); - F32 text_highlight = 0.8f; - if(i - ll_round(offset_nearest_grid_unit / smallest_grid_unit_scale) == 0 && mInSnapRegime) - { - text_highlight = 1.f; - } - - if (grid_mode == GRID_MODE_WORLD) - { - // rescale units to meters from multiple of grid scale - offset_val *= 2.f * grid_scale[ARROW_TO_AXIS[mManipPart]]; - renderTickValue(text_origin, offset_val, std::string("m"), LLColor4(text_highlight, text_highlight, text_highlight, alpha)); - } - else - { - renderTickValue(text_origin, offset_val, std::string("x"), LLColor4(text_highlight, text_highlight, text_highlight, alpha)); - } - } - } - if (mObjectSelection->getSelectType() != SELECT_TYPE_HUD) - { - // render helpful text - if (mHelpTextTimer.getElapsedTimeF32() < sHelpTextVisibleTime + sHelpTextFadeTime && sNumTimesHelpTextShown < sMaxTimesShowHelpText) - { - F32 snap_offset_meters_up; - if (mSnapOffsetAxis * LLViewerCamera::getInstance()->getUpAxis() > 0.f) - { - snap_offset_meters_up = mSnapOffsetMeters; - } - else - { - snap_offset_meters_up = -mSnapOffsetMeters; - } - - LLVector3 selection_center_start = getSavedPivotPoint();//LLSelectMgr::getInstance()->getSavedBBoxOfSelection().getCenterAgent(); - - LLVector3 help_text_pos = selection_center_start + (snap_offset_meters_up * 3.f * mSnapOffsetAxis); - const LLFontGL* big_fontp = LLFontGL::getFontSansSerif(); - - std::string help_text = LLTrans::getString("manip_hint1"); - LLColor4 help_text_color = LLColor4::white; - help_text_color.mV[VALPHA] = clamp_rescale(mHelpTextTimer.getElapsedTimeF32(), sHelpTextVisibleTime, sHelpTextVisibleTime + sHelpTextFadeTime, line_alpha, 0.f); - hud_render_utf8text(help_text, help_text_pos, *big_fontp, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, -0.5f * big_fontp->getWidthF32(help_text), 3.f, help_text_color, false); - help_text = LLTrans::getString("manip_hint2"); - help_text_pos -= LLViewerCamera::getInstance()->getUpAxis() * mSnapOffsetMeters * 0.2f; - hud_render_utf8text(help_text, help_text_pos, *big_fontp, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, -0.5f * big_fontp->getWidthF32(help_text), 3.f, help_text_color, false); - } - } - } - else - { - // render gridlines for planar snapping - - F32 u = 0, v = 0; - LLColor4 inner_color; - LLVector3 normal; - LLVector3 grid_center = selection_center - grid_origin; - F32 usc = 1; - F32 vsc = 1; - - grid_center *= ~grid_rotation; - - switch (mManipPart) - { - case LL_YZ_PLANE: - u = grid_center.mV[VY]; - v = grid_center.mV[VZ]; - usc = grid_scale.mV[VY]; - vsc = grid_scale.mV[VZ]; - inner_color.setVec(0,1,1,line_alpha); - normal.setVec(1,0,0); - break; - case LL_XZ_PLANE: - u = grid_center.mV[VX]; - v = grid_center.mV[VZ]; - usc = grid_scale.mV[VX]; - vsc = grid_scale.mV[VZ]; - inner_color.setVec(1,0,1,line_alpha); - normal.setVec(0,1,0); - break; - case LL_XY_PLANE: - u = grid_center.mV[VX]; - v = grid_center.mV[VY]; - usc = grid_scale.mV[VX]; - vsc = grid_scale.mV[VY]; - inner_color.setVec(1,1,0,line_alpha); - normal.setVec(0,0,1); - break; - default: - break; - } - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - highlightIntersection(normal, selection_center, grid_rotation, inner_color); - - gGL.pushMatrix(); - - F32 x,y,z,angle_radians; - grid_rotation.getAngleAxis(&angle_radians, &x, &y, &z); - gGL.translatef(selection_center.mV[VX], selection_center.mV[VY], selection_center.mV[VZ]); - gGL.rotatef(angle_radians * RAD_TO_DEG, x, y, z); - - F32 sz = mGridSizeMeters; - F32 tiles = sz; - - gGL.matrixMode(LLRender::MM_TEXTURE); - gGL.pushMatrix(); - usc = 1.0f/usc; - vsc = 1.0f/vsc; - - while (usc > vsc*4.0f) - { - usc *= 0.5f; - } - while (vsc > usc * 4.0f) - { - vsc *= 0.5f; - } - - gGL.scalef(usc, vsc, 1.0f); - gGL.translatef(u, v, 0); - - float a = line_alpha; - - { - //draw grid behind objects - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); - - { - //LLGLDisable stencil(GL_STENCIL_TEST); - { - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE, GL_GREATER); - gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, getGridTexName()); - gGL.flush(); - gGL.blendFunc(LLRender::BF_ZERO, LLRender::BF_ONE_MINUS_SOURCE_ALPHA); - renderGrid(u,v,tiles,0.9f, 0.9f, 0.9f,a*0.15f); - gGL.flush(); - gGL.setSceneBlendType(LLRender::BT_ALPHA); - } - - { - //draw black overlay - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - renderGrid(u,v,tiles,0.0f, 0.0f, 0.0f,a*0.16f); - - //draw grid top - gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, getGridTexName()); - renderGrid(u,v,tiles,1,1,1,a); - - gGL.popMatrix(); - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.popMatrix(); - } - - { - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); - renderGuidelines(); - } - - { - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE, GL_GREATER); - gGL.flush(); - - switch (mManipPart) - { - case LL_YZ_PLANE: - renderGuidelines(false, true, true); - break; - case LL_XZ_PLANE: - renderGuidelines(true, false, true); - break; - case LL_XY_PLANE: - renderGuidelines(true, true, false); - break; - default: - break; - } - gGL.flush(); - } - } - } - } -} - -void LLManipTranslate::renderGrid(F32 x, F32 y, F32 size, F32 r, F32 g, F32 b, F32 a) -{ - F32 d = size*0.5f; - - for (F32 xx = -size-d; xx < size+d; xx += d) - { - gGL.begin(LLRender::TRIANGLE_STRIP); - for (F32 yy = -size-d; yy < size+d; yy += d) - { - float dx, dy, da; - - dx = xx; dy = yy; - da = sqrtf(llmax(0.0f, 1.0f-sqrtf(dx*dx+dy*dy)/size))*a; - gGL.texCoord2f(dx, dy); - renderGridVert(dx,dy,r,g,b,da); - - dx = xx+d; dy = yy; - da = sqrtf(llmax(0.0f, 1.0f-sqrtf(dx*dx+dy*dy)/size))*a; - gGL.texCoord2f(dx, dy); - renderGridVert(dx,dy,r,g,b,da); - - dx = xx; dy = yy+d; - da = sqrtf(llmax(0.0f, 1.0f-sqrtf(dx*dx+dy*dy)/size))*a; - gGL.texCoord2f(dx, dy); - renderGridVert(dx,dy,r,g,b,da); - - dx = xx+d; dy = yy+d; - da = sqrtf(llmax(0.0f, 1.0f-sqrtf(dx*dx+dy*dy)/size))*a; - gGL.texCoord2f(dx, dy); - renderGridVert(dx,dy,r,g,b,da); - } - gGL.end(); - } - - -} - -void LLManipTranslate::highlightIntersection(LLVector3 normal, - LLVector3 selection_center, - LLQuaternion grid_rotation, - LLColor4 inner_color) -{ -#if 0 // DEPRECATED - if (!gSavedSettings.getBOOL("GridCrossSections")) - { - return; - } - - - LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; - - - static const U32 types[] = { LLRenderPass::PASS_SIMPLE, LLRenderPass::PASS_ALPHA, LLRenderPass::PASS_FULLBRIGHT, LLRenderPass::PASS_SHINY }; - static const U32 num_types = LL_ARRAY_SIZE(types); - - GLuint stencil_mask = 0xFFFFFFFF; - //stencil in volumes - - gGL.flush(); - - if (shader) - { - gClipProgram.bind(); - } - - { - //glStencilMask(stencil_mask); //deprecated - //glClearStencil(1); - //glClear(GL_STENCIL_BUFFER_BIT); - LLGLEnable cull_face(GL_CULL_FACE); - //LLGLEnable stencil(GL_STENCIL_TEST); - LLGLDepthTest depth (GL_TRUE, GL_FALSE, GL_ALWAYS); - //glStencilFunc(GL_ALWAYS, 0, stencil_mask); - gGL.setColorMask(false, false); - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - gGL.diffuseColor4f(1,1,1,1); - - //setup clip plane - normal = normal * grid_rotation; - if (normal * (LLViewerCamera::getInstance()->getOrigin()-selection_center) < 0) - { - normal = -normal; - } - F32 d = -(selection_center * normal); - glh::vec4f plane(normal.mV[0], normal.mV[1], normal.mV[2], d ); - - gGL.getModelviewMatrix().inverse().mult_vec_matrix(plane); - - static LLStaticHashedString sClipPlane("clip_plane"); - gClipProgram.uniform4fv(sClipPlane, 1, plane.v); - - bool particles = gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_PARTICLES); - bool clouds = gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_CLOUDS); - - if (particles) - { - LLPipeline::toggleRenderType(LLPipeline::RENDER_TYPE_PARTICLES); - } - if (clouds) - { - LLPipeline::toggleRenderType(LLPipeline::RENDER_TYPE_CLOUDS); - } - - //stencil in volumes - //glStencilOp(GL_INCR, GL_INCR, GL_INCR); - glCullFace(GL_FRONT); - for (U32 i = 0; i < num_types; i++) - { - gPipeline.renderObjects(types[i], LLVertexBuffer::MAP_VERTEX, false); - } - - //glStencilOp(GL_DECR, GL_DECR, GL_DECR); - glCullFace(GL_BACK); - for (U32 i = 0; i < num_types; i++) - { - gPipeline.renderObjects(types[i], LLVertexBuffer::MAP_VERTEX, false); - } - - if (particles) - { - LLPipeline::toggleRenderType(LLPipeline::RENDER_TYPE_PARTICLES); - } - if (clouds) - { - LLPipeline::toggleRenderType(LLPipeline::RENDER_TYPE_CLOUDS); - } - - gGL.setColorMask(true, false); - } - gGL.color4f(1,1,1,1); - - gGL.pushMatrix(); - - F32 x,y,z,angle_radians; - grid_rotation.getAngleAxis(&angle_radians, &x, &y, &z); - gGL.translatef(selection_center.mV[VX], selection_center.mV[VY], selection_center.mV[VZ]); - gGL.rotatef(angle_radians * RAD_TO_DEG, x, y, z); - - F32 sz = mGridSizeMeters; - F32 tiles = sz; - - if (shader) - { - shader->bind(); - } - - if (shader) - { - shader->bind(); - } - - //draw volume/plane intersections - { - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - LLGLDepthTest depth(GL_FALSE); - //LLGLEnable stencil(GL_STENCIL_TEST); - glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); - glStencilFunc(GL_EQUAL, 0, stencil_mask); - renderGrid(0,0,tiles,inner_color.mV[0], inner_color.mV[1], inner_color.mV[2], 0.25f); - } - - glStencilFunc(GL_ALWAYS, 255, 0xFFFFFFFF); - glStencilMask(0xFFFFFFFF); - glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); - - gGL.popMatrix(); -#endif -} - -void LLManipTranslate::renderText() -{ - if (mObjectSelection->getRootObjectCount() && !mObjectSelection->isAttachment()) - { - LLVector3 pos = getPivotPoint(); - renderXYZ(pos); - } - else - { - const bool children_ok = true; - LLViewerObject* objectp = mObjectSelection->getFirstRootObject(children_ok); - if (objectp) - { - renderXYZ(objectp->getPositionEdit()); - } - } -} - -void LLManipTranslate::renderTranslationHandles() -{ - LLVector3 grid_origin; - LLVector3 grid_scale; - LLQuaternion grid_rotation; - LLGLDepthTest gls_depth(GL_FALSE); - - LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale); - LLVector3 at_axis; - if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) - { - at_axis = LLVector3::x_axis * ~grid_rotation; - } - else - { - at_axis = LLViewerCamera::getInstance()->getAtAxis() * ~grid_rotation; - } - - if (at_axis.mV[VX] > 0.f) - { - mPlaneManipPositions.mV[VX] = 1.f; - } - else - { - mPlaneManipPositions.mV[VX] = -1.f; - } - - if (at_axis.mV[VY] > 0.f) - { - mPlaneManipPositions.mV[VY] = 1.f; - } - else - { - mPlaneManipPositions.mV[VY] = -1.f; - } - - if (at_axis.mV[VZ] > 0.f) - { - mPlaneManipPositions.mV[VZ] = 1.f; - } - else - { - mPlaneManipPositions.mV[VZ] = -1.f; - } - - LLViewerObject *first_object = mObjectSelection->getFirstMoveableObject(true); - if (!first_object) return; - - LLVector3 selection_center = getPivotPoint(); - - // Drag handles - if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) - { - mArrowLengthMeters = mAxisArrowLength / gViewerWindow->getWorldViewHeightRaw(); - mArrowLengthMeters /= gAgentCamera.mHUDCurZoom; - } - else - { - LLVector3 camera_pos_agent = gAgentCamera.getCameraPositionAgent(); - F32 range = dist_vec(camera_pos_agent, selection_center); - F32 range_from_agent = dist_vec(gAgent.getPositionAgent(), selection_center); - - // Don't draw handles if you're too far away - if (gSavedSettings.getBOOL("LimitSelectDistance")) - { - if (range_from_agent > gSavedSettings.getF32("MaxSelectDistance")) - { - return; - } - } - - if (range > 0.001f) - { - // range != zero - F32 fraction_of_fov = mAxisArrowLength / (F32) LLViewerCamera::getInstance()->getViewHeightInPixels(); - F32 apparent_angle = fraction_of_fov * LLViewerCamera::getInstance()->getView(); // radians - mArrowLengthMeters = range * tan(apparent_angle); - } - else - { - // range == zero - mArrowLengthMeters = 1.0f; - } - } - //Assume that UI scale factor is equivalent for X and Y axis - F32 ui_scale_factor = LLUI::getScaleFactor().mV[VX]; - mArrowLengthMeters *= ui_scale_factor; - - mPlaneManipOffsetMeters = mArrowLengthMeters * 1.8f; - mGridSizeMeters = gSavedSettings.getF32("GridDrawSize"); - mConeSize = mArrowLengthMeters / 4.f; - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - { - gGL.translatef(selection_center.mV[VX], selection_center.mV[VY], selection_center.mV[VZ]); - - F32 angle_radians, x, y, z; - grid_rotation.getAngleAxis(&angle_radians, &x, &y, &z); - - gGL.rotatef(angle_radians * RAD_TO_DEG, x, y, z); - - LLQuaternion invRotation = grid_rotation; - invRotation.conjQuat(); - - LLVector3 relative_camera_dir; - - if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) - { - relative_camera_dir = LLVector3::x_axis * invRotation; - } - else - { - relative_camera_dir = (selection_center - LLViewerCamera::getInstance()->getOrigin()) * invRotation; - } - relative_camera_dir.normVec(); - - { - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - LLGLDisable cull_face(GL_CULL_FACE); - - LLColor4 color1; - LLColor4 color2; - - // update manipulator sizes - for (S32 index = 0; index < 3; index++) - { - if (index == mManipPart - LL_X_ARROW || index == mHighlightedPart - LL_X_ARROW) - { - mArrowScales.mV[index] = lerp(mArrowScales.mV[index], SELECTED_ARROW_SCALE, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE )); - mPlaneScales.mV[index] = lerp(mPlaneScales.mV[index], 1.f, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE )); - } - else if (index == mManipPart - LL_YZ_PLANE || index == mHighlightedPart - LL_YZ_PLANE) - { - mArrowScales.mV[index] = lerp(mArrowScales.mV[index], 1.f, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE )); - mPlaneScales.mV[index] = lerp(mPlaneScales.mV[index], SELECTED_ARROW_SCALE, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE )); - } - else - { - mArrowScales.mV[index] = lerp(mArrowScales.mV[index], 1.f, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE )); - mPlaneScales.mV[index] = lerp(mPlaneScales.mV[index], 1.f, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE )); - } - } - - if ((mManipPart == LL_NO_PART || mManipPart == LL_YZ_PLANE) && llabs(relative_camera_dir.mV[VX]) > MIN_PLANE_MANIP_DOT_PRODUCT) - { - // render YZ plane manipulator - gGL.pushMatrix(); - gGL.scalef(mPlaneManipPositions.mV[VX], mPlaneManipPositions.mV[VY], mPlaneManipPositions.mV[VZ]); - gGL.translatef(0.f, mPlaneManipOffsetMeters, mPlaneManipOffsetMeters); - gGL.scalef(mPlaneScales.mV[VX], mPlaneScales.mV[VX], mPlaneScales.mV[VX]); - if (mHighlightedPart == LL_YZ_PLANE) - { - color1.setVec(0.f, 1.f, 0.f, 1.f); - color2.setVec(0.f, 0.f, 1.f, 1.f); - } - else - { - color1.setVec(0.f, 1.f, 0.f, 0.6f); - color2.setVec(0.f, 0.f, 1.f, 0.6f); - } - gGL.begin(LLRender::TRIANGLES); - { - gGL.color4fv(color1.mV); - gGL.vertex3f(0.f, mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f)); - gGL.vertex3f(0.f, mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.75f)); - gGL.vertex3f(0.f, mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f)); - - gGL.color4fv(color2.mV); - gGL.vertex3f(0.f, mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f)); - gGL.vertex3f(0.f, mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.75f), mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f)); - gGL.vertex3f(0.f, mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f)); - } - gGL.end(); - - LLUI::setLineWidth(3.0f); - gGL.begin(LLRender::LINES); - { - gGL.color4f(0.f, 0.f, 0.f, 0.3f); - gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); - gGL.vertex3f(0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); - gGL.vertex3f(0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); - gGL.vertex3f(0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.1f); - gGL.vertex3f(0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); - gGL.vertex3f(0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.4f); - - gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); - gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f); - gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f); - gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.1f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f); - gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f); - gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.4f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f); - } - gGL.end(); - LLUI::setLineWidth(1.0f); - gGL.popMatrix(); - } - - if ((mManipPart == LL_NO_PART || mManipPart == LL_XZ_PLANE) && llabs(relative_camera_dir.mV[VY]) > MIN_PLANE_MANIP_DOT_PRODUCT) - { - // render XZ plane manipulator - gGL.pushMatrix(); - gGL.scalef(mPlaneManipPositions.mV[VX], mPlaneManipPositions.mV[VY], mPlaneManipPositions.mV[VZ]); - gGL.translatef(mPlaneManipOffsetMeters, 0.f, mPlaneManipOffsetMeters); - gGL.scalef(mPlaneScales.mV[VY], mPlaneScales.mV[VY], mPlaneScales.mV[VY]); - if (mHighlightedPart == LL_XZ_PLANE) - { - color1.setVec(0.f, 0.f, 1.f, 1.f); - color2.setVec(1.f, 0.f, 0.f, 1.f); - } - else - { - color1.setVec(0.f, 0.f, 1.f, 0.6f); - color2.setVec(1.f, 0.f, 0.f, 0.6f); - } - - gGL.begin(LLRender::TRIANGLES); - { - gGL.color4fv(color1.mV); - gGL.vertex3f(mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f), 0.f, mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f)); - gGL.vertex3f(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.75f), 0.f, mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f)); - gGL.vertex3f(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), 0.f, mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f)); - - gGL.color4fv(color2.mV); - gGL.vertex3f(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), 0.f, mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f)); - gGL.vertex3f(mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f), 0.f, mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.75f)); - gGL.vertex3f(mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f), 0.f, mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f)); - } - gGL.end(); - - LLUI::setLineWidth(3.0f); - gGL.begin(LLRender::LINES); - { - gGL.color4f(0.f, 0.f, 0.f, 0.3f); - gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, 0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); - gGL.vertex3f(mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f, 0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); - gGL.vertex3f(mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f, 0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); - gGL.vertex3f(mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f, 0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.1f); - gGL.vertex3f(mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f, 0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); - gGL.vertex3f(mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f, 0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.4f); - - gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, 0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); - gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, 0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f); - gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, 0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f); - gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.1f, 0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f); - gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, 0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f); - gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.4f, 0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f); - } - gGL.end(); - LLUI::setLineWidth(1.0f); - - gGL.popMatrix(); - } - - if ((mManipPart == LL_NO_PART || mManipPart == LL_XY_PLANE) && llabs(relative_camera_dir.mV[VZ]) > MIN_PLANE_MANIP_DOT_PRODUCT) - { - // render XY plane manipulator - gGL.pushMatrix(); - gGL.scalef(mPlaneManipPositions.mV[VX], mPlaneManipPositions.mV[VY], mPlaneManipPositions.mV[VZ]); - -/* Y - ^ - v1 - | \ - |<- v0 - | /| \ - v2__v__v3 > X -*/ - LLVector3 v0,v1,v2,v3; -#if 0 - // This should theoretically work but looks off; could be tuned later -SJB - gGL.translatef(-mPlaneManipOffsetMeters, -mPlaneManipOffsetMeters, 0.f); - v0 = LLVector3(mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.25f), 0.f); - v1 = LLVector3(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.75f), 0.f); - v2 = LLVector3(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), 0.f); - v3 = LLVector3(mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.75f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), 0.f); -#else - gGL.translatef(mPlaneManipOffsetMeters, mPlaneManipOffsetMeters, 0.f); - v0 = LLVector3(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), 0.f); - v1 = LLVector3(mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.75f), 0.f); - v2 = LLVector3(mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.25f), 0.f); - v3 = LLVector3(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.75f), mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.25f), 0.f); -#endif - gGL.scalef(mPlaneScales.mV[VZ], mPlaneScales.mV[VZ], mPlaneScales.mV[VZ]); - if (mHighlightedPart == LL_XY_PLANE) - { - color1.setVec(1.f, 0.f, 0.f, 1.f); - color2.setVec(0.f, 1.f, 0.f, 1.f); - } - else - { - color1.setVec(0.8f, 0.f, 0.f, 0.6f); - color2.setVec(0.f, 0.8f, 0.f, 0.6f); - } - - gGL.begin(LLRender::TRIANGLES); - { - gGL.color4fv(color1.mV); - gGL.vertex3fv(v0.mV); - gGL.vertex3fv(v1.mV); - gGL.vertex3fv(v2.mV); - - gGL.color4fv(color2.mV); - gGL.vertex3fv(v2.mV); - gGL.vertex3fv(v3.mV); - gGL.vertex3fv(v0.mV); - } - gGL.end(); - - LLUI::setLineWidth(3.0f); - gGL.begin(LLRender::LINES); - { - gGL.color4f(0.f, 0.f, 0.f, 0.3f); - LLVector3 v12 = (v1 + v2) * .5f; - gGL.vertex3fv(v0.mV); - gGL.vertex3fv(v12.mV); - gGL.vertex3fv(v12.mV); - gGL.vertex3fv((v12 + (v0-v12)*.3f + (v2-v12)*.3f).mV); - gGL.vertex3fv(v12.mV); - gGL.vertex3fv((v12 + (v0-v12)*.3f + (v1-v12)*.3f).mV); - - LLVector3 v23 = (v2 + v3) * .5f; - gGL.vertex3fv(v0.mV); - gGL.vertex3fv(v23.mV); - gGL.vertex3fv(v23.mV); - gGL.vertex3fv((v23 + (v0-v23)*.3f + (v3-v23)*.3f).mV); - gGL.vertex3fv(v23.mV); - gGL.vertex3fv((v23 + (v0-v23)*.3f + (v2-v23)*.3f).mV); - } - gGL.end(); - LLUI::setLineWidth(1.0f); - - gGL.popMatrix(); - } - } - { - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - // Since we draw handles with depth testing off, we need to draw them in the - // proper depth order. - - // Copied from LLDrawable::updateGeometry - LLVector3 pos_agent = first_object->getPositionAgent(); - LLVector3 camera_agent = gAgentCamera.getCameraPositionAgent(); - LLVector3 headPos = pos_agent - camera_agent; - - LLVector3 orientWRTHead = headPos * invRotation; - - // Find nearest vertex - U32 nearest = (orientWRTHead.mV[0] < 0.0f ? 1 : 0) + - (orientWRTHead.mV[1] < 0.0f ? 2 : 0) + - (orientWRTHead.mV[2] < 0.0f ? 4 : 0); - - // opposite faces on Linden cubes: - // 0 & 5 - // 1 & 3 - // 2 & 4 - - // Table of order to draw faces, based on nearest vertex - static U32 face_list[8][NUM_AXES*2] = { - { 2,0,1, 4,5,3 }, // v6 F201 F453 - { 2,0,3, 4,5,1 }, // v7 F203 F451 - { 4,0,1, 2,5,3 }, // v5 F401 F253 - { 4,0,3, 2,5,1 }, // v4 F403 F251 - { 2,5,1, 4,0,3 }, // v2 F251 F403 - { 2,5,3, 4,0,1 }, // v3 F253 F401 - { 4,5,1, 2,0,3 }, // v1 F451 F203 - { 4,5,3, 2,0,1 }, // v0 F453 F201 - }; - static const EManipPart which_arrow[6] = { - LL_Z_ARROW, - LL_X_ARROW, - LL_Y_ARROW, - LL_X_ARROW, - LL_Y_ARROW, - LL_Z_ARROW}; - - // draw arrows for deeper faces first, closer faces last - LLVector3 camera_axis; - if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) - { - camera_axis = LLVector3::x_axis; - } - else - { - camera_axis.setVec(gAgentCamera.getCameraPositionAgent() - first_object->getPositionAgent()); - } - - for (U32 i = 0; i < NUM_AXES*2; i++) - { - U32 face = face_list[nearest][i]; - - LLVector3 arrow_axis; - getManipAxis(first_object, which_arrow[face], arrow_axis); - - renderArrow(which_arrow[face], - mManipPart, - (face >= 3) ? -mConeSize : mConeSize, - (face >= 3) ? -mArrowLengthMeters : mArrowLengthMeters, - mConeSize, - false); - } - } - } - gGL.popMatrix(); -} - - -void LLManipTranslate::renderArrow(S32 which_arrow, S32 selected_arrow, F32 box_size, F32 arrow_size, F32 handle_size, bool reverse_direction) -{ - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - LLGLEnable gls_blend(GL_BLEND); - - for (S32 pass = 1; pass <= 2; pass++) - { - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE, pass == 1 ? GL_LEQUAL : GL_GREATER); - gGL.pushMatrix(); - - S32 index = 0; - - index = ARROW_TO_AXIS[which_arrow]; - - // assign a color for this arrow - LLColor4 color; // black - if (which_arrow == selected_arrow || which_arrow == mHighlightedPart) - { - color.mV[index] = (pass == 1) ? 1.f : 0.5f; - } - else if (selected_arrow != LL_NO_PART) - { - color.mV[VALPHA] = 0.f; - } - else - { - color.mV[index] = pass == 1 ? .8f : .35f ; // red, green, or blue - color.mV[VALPHA] = 0.6f; - } - gGL.color4fv( color.mV ); - - LLVector3 vec; - - { - LLUI::setLineWidth(2.0f); - gGL.begin(LLRender::LINES); - vec.mV[index] = box_size; - gGL.vertex3f(vec.mV[0], vec.mV[1], vec.mV[2]); - - vec.mV[index] = arrow_size; - gGL.vertex3f(vec.mV[0], vec.mV[1], vec.mV[2]); - gGL.end(); - LLUI::setLineWidth(1.0f); - } - - gGL.translatef(vec.mV[0], vec.mV[1], vec.mV[2]); - gGL.scalef(handle_size, handle_size, handle_size); - - F32 rot = 0.0f; - LLVector3 axis; - - switch(which_arrow) - { - case LL_X_ARROW: - rot = reverse_direction ? -90.0f : 90.0f; - axis.mV[1] = 1.0f; - break; - case LL_Y_ARROW: - rot = reverse_direction ? 90.0f : -90.0f; - axis.mV[0] = 1.0f; - break; - case LL_Z_ARROW: - rot = reverse_direction ? 180.0f : 0.0f; - axis.mV[0] = 1.0f; - break; - default: - LL_ERRS() << "renderArrow called with bad arrow " << which_arrow << LL_ENDL; - break; - } - - gGL.diffuseColor4fv(color.mV); - gGL.rotatef(rot, axis.mV[0], axis.mV[1], axis.mV[2]); - gGL.scalef(mArrowScales.mV[index], mArrowScales.mV[index], mArrowScales.mV[index] * 1.5f); - - gCone.render(); - - gGL.popMatrix(); - } -} - -void LLManipTranslate::renderGridVert(F32 x_trans, F32 y_trans, F32 r, F32 g, F32 b, F32 alpha) -{ - gGL.color4f(r, g, b, alpha); - switch (mManipPart) - { - case LL_YZ_PLANE: - gGL.vertex3f(0, x_trans, y_trans); - break; - case LL_XZ_PLANE: - gGL.vertex3f(x_trans, 0, y_trans); - break; - case LL_XY_PLANE: - gGL.vertex3f(x_trans, y_trans, 0); - break; - default: - gGL.vertex3f(0,0,0); - break; - } - -} - -// virtual -bool LLManipTranslate::canAffectSelection() -{ - bool can_move = mObjectSelection->getObjectCount() != 0; - if (can_move) - { - struct f : public LLSelectedObjectFunctor - { - virtual bool apply(LLViewerObject* objectp) - { - LLViewerObject *root_object = (objectp == NULL) ? NULL : objectp->getRootEdit(); - return objectp->permMove() && !objectp->isPermanentEnforced() && - ((root_object == NULL) || !root_object->isPermanentEnforced()) && - (objectp->permModify() || !gSavedSettings.getBOOL("EditLinkedParts")); - } - } func; - can_move = mObjectSelection->applyToObjects(&func); - } - return can_move; -} +/** + * @file llmaniptranslate.cpp + * @brief LLManipTranslate class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/** + * Positioning tool + */ + +#include "llviewerprecompiledheaders.h" + +#include "llmaniptranslate.h" + +#include "llgl.h" +#include "llrender.h" + +#include "llagent.h" +#include "llagentcamera.h" +#include "llbbox.h" +#include "llbox.h" +#include "llviewercontrol.h" +#include "llcriticaldamp.h" +#include "llcylinder.h" +#include "lldrawable.h" +#include "llfloatertools.h" +#include "llfontgl.h" +#include "llglheaders.h" +#include "llhudrender.h" +#include "llresmgr.h" +#include "llselectmgr.h" +#include "llrendersphere.h" +#include "llstatusbar.h" +#include "lltoolmgr.h" +#include "llviewercamera.h" +#include "llviewerjoint.h" +#include "llviewerobject.h" +#include "llviewerwindow.h" +#include "llvoavatarself.h" +#include "llworld.h" +#include "llui.h" +#include "pipeline.h" +#include "llviewershadermgr.h" +#include "lltrans.h" + +const S32 NUM_AXES = 3; +const S32 MOUSE_DRAG_SLOP = 2; // pixels +const F32 SELECTED_ARROW_SCALE = 1.3f; +const F32 MANIPULATOR_HOTSPOT_START = 0.2f; +const F32 MANIPULATOR_HOTSPOT_END = 1.2f; +const F32 SNAP_GUIDE_SCREEN_SIZE = 0.7f; +const F32 MIN_PLANE_MANIP_DOT_PRODUCT = 0.25f; +const F32 PLANE_TICK_SIZE = 0.4f; +const F32 MANIPULATOR_SCALE_HALF_LIFE = 0.07f; +const F32 SNAP_ARROW_SCALE = 0.7f; + +static LLPointer sGridTex = NULL ; + +const LLManip::EManipPart MANIPULATOR_IDS[9] = +{ + LLManip::LL_X_ARROW, + LLManip::LL_Y_ARROW, + LLManip::LL_Z_ARROW, + LLManip::LL_X_ARROW, + LLManip::LL_Y_ARROW, + LLManip::LL_Z_ARROW, + LLManip::LL_YZ_PLANE, + LLManip::LL_XZ_PLANE, + LLManip::LL_XY_PLANE +}; + +const U32 ARROW_TO_AXIS[4] = +{ + VX, + VX, + VY, + VZ +}; + +// Sort manipulator handles by their screen-space projection +struct ClosestToCamera +{ + bool operator()(const LLManipTranslate::ManipulatorHandle& a, + const LLManipTranslate::ManipulatorHandle& b) const + { + return a.mEndPosition.mV[VZ] < b.mEndPosition.mV[VZ]; + } +}; + +LLManipTranslate::LLManipTranslate( LLToolComposite* composite ) +: LLManip( std::string("Move"), composite ), + mLastHoverMouseX(-1), + mLastHoverMouseY(-1), + mMouseOutsideSlop(false), + mCopyMadeThisDrag(false), + mMouseDownX(-1), + mMouseDownY(-1), + mAxisArrowLength(50), + mConeSize(0), + mArrowLengthMeters(0.f), + mGridSizeMeters(1.f), + mPlaneManipOffsetMeters(0.f), + mUpdateTimer(), + mSnapOffsetMeters(0.f), + mSubdivisions(10.f), + mInSnapRegime(false), + mArrowScales(1.f, 1.f, 1.f), + mPlaneScales(1.f, 1.f, 1.f), + mPlaneManipPositions(1.f, 1.f, 1.f, 1.f) +{ + if (sGridTex.isNull()) + { + restoreGL(); + } +} + +//static +U32 LLManipTranslate::getGridTexName() +{ + if(sGridTex.isNull()) + { + restoreGL() ; + } + + return sGridTex.isNull() ? 0 : sGridTex->getTexName() ; +} + +//static +void LLManipTranslate::destroyGL() +{ + if (sGridTex) + { + sGridTex = NULL ; + } +} + +//static +void LLManipTranslate::restoreGL() +{ + //generate grid texture + U32 rez = 512; + U32 mip = 0; + + destroyGL() ; + sGridTex = LLViewerTextureManager::getLocalTexture() ; + if(!sGridTex->createGLTexture()) + { + sGridTex = NULL ; + return ; + } + + GLuint* d = new GLuint[rez*rez]; + + gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, sGridTex->getTexName(), true); + gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_TRILINEAR); + + while (rez >= 1) + { + for (U32 i = 0; i < rez*rez; i++) + { + d[i] = 0x00FFFFFF; + } + + U32 subcol = 0xFFFFFFFF; + if (rez >= 4) + { //large grain grid + for (U32 i = 0; i < rez; i++) + { + if (rez <= 16) + { + if (rez == 16) + { + subcol = 0xA0FFFFFF; + } + else if (rez == 8) + { + subcol = 0x80FFFFFF; + } + else + { + subcol = 0x40FFFFFF; + } + } + else + { + subcol = 0xFFFFFFFF; + } + d[i *rez+ 0 ] = subcol; + d[0 *rez+ i ] = subcol; + if (rez >= 32) + { + d[i *rez+ (rez-1)] = subcol; + d[(rez-1) *rez+ i ] = subcol; + } + + if (rez >= 64) + { + subcol = 0xFFFFFFFF; + + if (i > 0 && i < (rez-1)) + { + d[i *rez+ 1 ] = subcol; + d[i *rez+ (rez-2)] = subcol; + d[1 *rez+ i ] = subcol; + d[(rez-2) *rez+ i ] = subcol; + } + } + } + } + + subcol = 0x50A0A0A0; + if (rez >= 128) + { //small grain grid + for (U32 i = 8; i < rez; i+=8) + { + for (U32 j = 2; j < rez-2; j++) + { + d[i *rez+ j] = subcol; + d[j *rez+ i] = subcol; + } + } + } + if (rez >= 64) + { //medium grain grid + if (rez == 64) + { + subcol = 0x50A0A0A0; + } + else + { + subcol = 0xA0D0D0D0; + } + + for (U32 i = 32; i < rez; i+=32) + { + U32 pi = i-1; + for (U32 j = 2; j < rez-2; j++) + { + d[i *rez+ j] = subcol; + d[j *rez+ i] = subcol; + + if (rez > 128) + { + d[pi *rez+ j] = subcol; + d[j *rez+ pi] = subcol; + } + } + } + } + LLImageGL::setManualImage(GL_TEXTURE_2D, mip, GL_RGBA, rez, rez, GL_RGBA, GL_UNSIGNED_BYTE, d); + rez = rez >> 1; + mip++; + } + delete [] d; +} + + +LLManipTranslate::~LLManipTranslate() +{ +} + + +void LLManipTranslate::handleSelect() +{ + LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); + if (gFloaterTools) + { + gFloaterTools->setStatusText("move"); + } + LLManip::handleSelect(); +} + +bool LLManipTranslate::handleMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + // didn't click in any UI object, so must have clicked in the world + if( (mHighlightedPart == LL_X_ARROW || + mHighlightedPart == LL_Y_ARROW || + mHighlightedPart == LL_Z_ARROW || + mHighlightedPart == LL_YZ_PLANE || + mHighlightedPart == LL_XZ_PLANE || + mHighlightedPart == LL_XY_PLANE ) ) + { + handled = handleMouseDownOnPart( x, y, mask ); + } + + return handled; +} + +// Assumes that one of the arrows on an object was hit. +bool LLManipTranslate::handleMouseDownOnPart( S32 x, S32 y, MASK mask ) +{ + bool can_move = canAffectSelection(); + if (!can_move) + { + return false; + } + + highlightManipulators(x, y); + S32 hit_part = mHighlightedPart; + + if( (hit_part != LL_X_ARROW) && + (hit_part != LL_Y_ARROW) && + (hit_part != LL_Z_ARROW) && + (hit_part != LL_YZ_PLANE) && + (hit_part != LL_XZ_PLANE) && + (hit_part != LL_XY_PLANE) ) + { + return true; + } + + mHelpTextTimer.reset(); + sNumTimesHelpTextShown++; + + LLSelectMgr::getInstance()->getGrid(mGridOrigin, mGridRotation, mGridScale); + + LLSelectMgr::getInstance()->enableSilhouette(false); + + // we just started a drag, so save initial object positions + LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_MOVE); + + mManipPart = (EManipPart)hit_part; + mMouseDownX = x; + mMouseDownY = y; + mMouseOutsideSlop = false; + + LLVector3 axis; + + LLSelectNode *selectNode = mObjectSelection->getFirstMoveableNode(true); + + if (!selectNode) + { + // didn't find the object in our selection...oh well + LL_WARNS() << "Trying to translate an unselected object" << LL_ENDL; + return true; + } + + LLViewerObject *selected_object = selectNode->getObject(); + if (!selected_object) + { + // somehow we lost the object! + LL_WARNS() << "Translate manip lost the object, no selected object" << LL_ENDL; + gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE); + return true; + } + + // Compute unit vectors for arrow hit and a plane through that vector + bool axis_exists = getManipAxis(selected_object, mManipPart, axis); + getManipNormal(selected_object, mManipPart, mManipNormal); + + //LLVector3 select_center_agent = gAgent.getPosAgentFromGlobal(LLSelectMgr::getInstance()->getSelectionCenterGlobal()); + // TomY: The above should (?) be identical to the below + LLVector3 select_center_agent = getPivotPoint(); + mSubdivisions = getSubdivisionLevel(select_center_agent, axis_exists ? axis : LLVector3::z_axis, getMinGridScale()); + + // if we clicked on a planar manipulator, recenter mouse cursor + if (mManipPart >= LL_YZ_PLANE && mManipPart <= LL_XY_PLANE) + { + LLCoordGL mouse_pos; + if (!LLViewerCamera::getInstance()->projectPosAgentToScreen(select_center_agent, mouse_pos)) + { + // mouse_pos may be nonsense + LL_WARNS() << "Failed to project object center to screen" << LL_ENDL; + } + else if (gSavedSettings.getBOOL("SnapToMouseCursor")) + { + LLUI::getInstance()->setMousePositionScreen(mouse_pos.mX, mouse_pos.mY); + x = mouse_pos.mX; + y = mouse_pos.mY; + } + } + + LLSelectMgr::getInstance()->updateSelectionCenter(); + LLVector3d object_start_global = gAgent.getPosGlobalFromAgent(getPivotPoint()); + getMousePointOnPlaneGlobal(mDragCursorStartGlobal, x, y, object_start_global, mManipNormal); + mDragSelectionStartGlobal = object_start_global; + mCopyMadeThisDrag = false; + + // Route future Mouse messages here preemptively. (Release on mouse up.) + setMouseCapture( true ); + + return true; +} + +bool LLManipTranslate::handleHover(S32 x, S32 y, MASK mask) +{ + // Translation tool only works if mouse button is down. + // Bail out if mouse not down. + if( !hasMouseCapture() ) + { + LL_DEBUGS("UserInput") << "hover handled by LLManipTranslate (inactive)" << LL_ENDL; + // Always show cursor + // gViewerWindow->setCursor(UI_CURSOR_ARROW); + gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE); + + highlightManipulators(x, y); + return true; + } + + // Handle auto-rotation if necessary. + LLRect world_rect = gViewerWindow->getWorldViewRectScaled(); + const F32 ROTATE_ANGLE_PER_SECOND = 30.f * DEG_TO_RAD; + const S32 ROTATE_H_MARGIN = world_rect.getWidth() / 20; + const F32 rotate_angle = ROTATE_ANGLE_PER_SECOND / gFPSClamped; + bool rotated = false; + + // ...build mode moves camera about focus point + if (mObjectSelection->getSelectType() != SELECT_TYPE_HUD) + { + if (x < ROTATE_H_MARGIN) + { + gAgentCamera.cameraOrbitAround(rotate_angle); + rotated = true; + } + else if (x > world_rect.getWidth() - ROTATE_H_MARGIN) + { + gAgentCamera.cameraOrbitAround(-rotate_angle); + rotated = true; + } + } + + // Suppress processing if mouse hasn't actually moved. + // This may cause problems if the camera moves outside of the + // rotation above. + if( x == mLastHoverMouseX && y == mLastHoverMouseY && !rotated) + { + LL_DEBUGS("UserInput") << "hover handled by LLManipTranslate (mouse unmoved)" << LL_ENDL; + gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE); + return true; + } + mLastHoverMouseX = x; + mLastHoverMouseY = y; + + // Suppress if mouse hasn't moved past the initial slop region + // Reset once we start moving + if( !mMouseOutsideSlop ) + { + if (abs(mMouseDownX - x) < MOUSE_DRAG_SLOP && abs(mMouseDownY - y) < MOUSE_DRAG_SLOP ) + { + LL_DEBUGS("UserInput") << "hover handled by LLManipTranslate (mouse inside slop)" << LL_ENDL; + gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE); + return true; + } + else + { + // ...just went outside the slop region + mMouseOutsideSlop = true; + // If holding down shift, leave behind a copy. + if (mask == MASK_COPY) + { + // ...we're trying to make a copy + LLSelectMgr::getInstance()->selectDuplicate(LLVector3::zero, false); + mCopyMadeThisDrag = true; + + // When we make the copy, we don't want to do any other processing. + // If so, the object will also be moved, and the copy will be offset. + LL_DEBUGS("UserInput") << "hover handled by LLManipTranslate (made copy)" << LL_ENDL; + gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE); + } + } + } + + // Throttle updates to 10 per second. + + LLVector3 axis_f; + LLVector3d axis_d; + + // pick the first object to constrain to grid w/ common origin + // this is so we don't screw up groups + LLSelectNode* selectNode = mObjectSelection->getFirstMoveableNode(true); + if (!selectNode) + { + // somehow we lost the object! + LL_WARNS() << "Translate manip lost the object, no selectNode" << LL_ENDL; + gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE); + return true; + } + + LLViewerObject* object = selectNode->getObject(); + if (!object) + { + // somehow we lost the object! + LL_WARNS() << "Translate manip lost the object, no object in selectNode" << LL_ENDL; + gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE); + return true; + } + + // Compute unit vectors for arrow hit and a plane through that vector + bool axis_exists = getManipAxis(object, mManipPart, axis_f); // TODO: move this + + axis_d.setVec(axis_f); + + LLSelectMgr::getInstance()->updateSelectionCenter(); + LLVector3d current_pos_global = gAgent.getPosGlobalFromAgent(getPivotPoint()); + + mSubdivisions = getSubdivisionLevel(getPivotPoint(), axis_f, getMinGridScale()); + + // Project the cursor onto that plane + LLVector3d relative_move; + getMousePointOnPlaneGlobal(relative_move, x, y, current_pos_global, mManipNormal);\ + relative_move -= mDragCursorStartGlobal; + + // You can't move more than some distance from your original mousedown point. + if (gSavedSettings.getBOOL("LimitDragDistance")) + { + F32 max_drag_distance = gSavedSettings.getF32("MaxDragDistance"); + + if (relative_move.magVecSquared() > max_drag_distance * max_drag_distance) + { + LL_DEBUGS("UserInput") << "hover handled by LLManipTranslate (too far)" << LL_ENDL; + gViewerWindow->setCursor(UI_CURSOR_NOLOCKED); + return true; + } + } + + F64 axis_magnitude = relative_move * axis_d; // dot product + LLVector3d cursor_point_snap_line; + + F64 off_axis_magnitude; + + getMousePointOnPlaneGlobal(cursor_point_snap_line, x, y, current_pos_global, mSnapOffsetAxis % axis_f); + off_axis_magnitude = axis_exists ? llabs((cursor_point_snap_line - current_pos_global) * LLVector3d(mSnapOffsetAxis)) : 0.f; + + if (gSavedSettings.getBOOL("SnapEnabled")) + { + if (off_axis_magnitude > mSnapOffsetMeters) + { + mInSnapRegime = true; + LLVector3 cursor_snap_agent = gAgent.getPosAgentFromGlobal(cursor_point_snap_line); + + F32 cursor_grid_dist = (cursor_snap_agent - mGridOrigin) * axis_f; + + F32 snap_dist = getMinGridScale() / (2.f * mSubdivisions); + F32 relative_snap_dist = fmodf(llabs(cursor_grid_dist) + snap_dist, getMinGridScale() / mSubdivisions); + if (relative_snap_dist < snap_dist * 2.f) + { + if (cursor_grid_dist > 0.f) + { + cursor_grid_dist -= relative_snap_dist - snap_dist; + } + else + { + cursor_grid_dist += relative_snap_dist - snap_dist; + } + } + + F32 object_start_on_axis = (gAgent.getPosAgentFromGlobal(mDragSelectionStartGlobal) - mGridOrigin) * axis_f; + axis_magnitude = cursor_grid_dist - object_start_on_axis; + } + else if (mManipPart >= LL_YZ_PLANE && mManipPart <= LL_XY_PLANE) + { + // subtract offset from object center + LLVector3d cursor_point_global; + getMousePointOnPlaneGlobal( cursor_point_global, x, y, current_pos_global, mManipNormal ); + cursor_point_global -= (mDragCursorStartGlobal - mDragSelectionStartGlobal); + + // snap to planar grid + LLVector3 cursor_point_agent = gAgent.getPosAgentFromGlobal(cursor_point_global); + LLVector3 camera_plane_projection = LLViewerCamera::getInstance()->getAtAxis(); + camera_plane_projection -= projected_vec(camera_plane_projection, mManipNormal); + camera_plane_projection.normVec(); + LLVector3 camera_projected_dir = camera_plane_projection; + camera_plane_projection.rotVec(~mGridRotation); + camera_plane_projection.scaleVec(mGridScale); + camera_plane_projection.abs(); + F32 max_grid_scale; + if (camera_plane_projection.mV[VX] > camera_plane_projection.mV[VY] && + camera_plane_projection.mV[VX] > camera_plane_projection.mV[VZ]) + { + max_grid_scale = mGridScale.mV[VX]; + } + else if (camera_plane_projection.mV[VY] > camera_plane_projection.mV[VZ]) + { + max_grid_scale = mGridScale.mV[VY]; + } + else + { + max_grid_scale = mGridScale.mV[VZ]; + } + + F32 num_subdivisions = getSubdivisionLevel(getPivotPoint(), camera_projected_dir, max_grid_scale); + + F32 grid_scale_a; + F32 grid_scale_b; + LLVector3 cursor_point_grid = (cursor_point_agent - mGridOrigin) * ~mGridRotation; + + switch (mManipPart) + { + case LL_YZ_PLANE: + grid_scale_a = mGridScale.mV[VY] / num_subdivisions; + grid_scale_b = mGridScale.mV[VZ] / num_subdivisions; + cursor_point_grid.mV[VY] -= fmod(cursor_point_grid.mV[VY] + grid_scale_a * 0.5f, grid_scale_a) - grid_scale_a * 0.5f; + cursor_point_grid.mV[VZ] -= fmod(cursor_point_grid.mV[VZ] + grid_scale_b * 0.5f, grid_scale_b) - grid_scale_b * 0.5f; + break; + case LL_XZ_PLANE: + grid_scale_a = mGridScale.mV[VX] / num_subdivisions; + grid_scale_b = mGridScale.mV[VZ] / num_subdivisions; + cursor_point_grid.mV[VX] -= fmod(cursor_point_grid.mV[VX] + grid_scale_a * 0.5f, grid_scale_a) - grid_scale_a * 0.5f; + cursor_point_grid.mV[VZ] -= fmod(cursor_point_grid.mV[VZ] + grid_scale_b * 0.5f, grid_scale_b) - grid_scale_b * 0.5f; + break; + case LL_XY_PLANE: + grid_scale_a = mGridScale.mV[VX] / num_subdivisions; + grid_scale_b = mGridScale.mV[VY] / num_subdivisions; + cursor_point_grid.mV[VX] -= fmod(cursor_point_grid.mV[VX] + grid_scale_a * 0.5f, grid_scale_a) - grid_scale_a * 0.5f; + cursor_point_grid.mV[VY] -= fmod(cursor_point_grid.mV[VY] + grid_scale_b * 0.5f, grid_scale_b) - grid_scale_b * 0.5f; + break; + default: + break; + } + cursor_point_agent = (cursor_point_grid * mGridRotation) + mGridOrigin; + relative_move.setVec(cursor_point_agent - gAgent.getPosAgentFromGlobal(mDragSelectionStartGlobal)); + mInSnapRegime = true; + } + else + { + mInSnapRegime = false; + } + } + else + { + mInSnapRegime = false; + } + + // Clamp to arrow direction + // *FIX: does this apply anymore? + if (!axis_exists) + { + axis_magnitude = relative_move.normVec(); + axis_d.setVec(relative_move); + axis_d.normVec(); + axis_f.setVec(axis_d); + } + + LLVector3d clamped_relative_move = axis_magnitude * axis_d; // scalar multiply + LLVector3 clamped_relative_move_f = (F32)axis_magnitude * axis_f; // scalar multiply + + for (LLObjectSelection::iterator iter = mObjectSelection->begin(); + iter != mObjectSelection->end(); iter++) + { + LLSelectNode* selectNode = *iter; + LLViewerObject* object = selectNode->getObject(); + + // Only apply motion to root objects and objects selected + // as "individual". + if (!object->isRootEdit() && !selectNode->mIndividualSelection) + { + continue; + } + + if (!object->isRootEdit()) + { + // child objects should not update if parent is selected + LLViewerObject* editable_root = (LLViewerObject*)object->getParent(); + if (editable_root->isSelected()) + { + // we will be moved properly by our parent, so skip + continue; + } + } + + LLViewerObject* root_object = (object == NULL) ? NULL : object->getRootEdit(); + if (object->permMove() && !object->isPermanentEnforced() && + ((root_object == NULL) || !root_object->isPermanentEnforced())) + { + // handle attachments in local space + if (object->isAttachment() && object->mDrawable.notNull()) + { + // calculate local version of relative move + LLQuaternion objWorldRotation = object->mDrawable->mXform.getParent()->getWorldRotation(); + objWorldRotation.transQuat(); + + LLVector3 old_position_local = object->getPosition(); + LLVector3 new_position_local = selectNode->mSavedPositionLocal + (clamped_relative_move_f * objWorldRotation); + + //RN: I forget, but we need to do this because of snapping which doesn't often result + // in position changes even when the mouse moves + object->setPosition(new_position_local); + rebuild(object); + gAgentAvatarp->clampAttachmentPositions(); + new_position_local = object->getPosition(); + + if (selectNode->mIndividualSelection) + { + // counter-translate child objects if we are moving the root as an individual + object->resetChildrenPosition(old_position_local - new_position_local, true) ; + } + } + else + { + // compute new position to send to simulators, but don't set it yet. + // We need the old position to know which simulator to send the move message to. + LLVector3d new_position_global = selectNode->mSavedPositionGlobal + clamped_relative_move; + + // Don't let object centers go too far underground + F64 min_height = LLWorld::getInstance()->getMinAllowedZ(object, object->getPositionGlobal()); + if (new_position_global.mdV[VZ] < min_height) + { + new_position_global.mdV[VZ] = min_height; + } + + // For safety, cap heights where objects can be dragged + if (new_position_global.mdV[VZ] > MAX_OBJECT_Z) + { + new_position_global.mdV[VZ] = MAX_OBJECT_Z; + } + + // Grass is always drawn on the ground, so clamp its position to the ground + if (object->getPCode() == LL_PCODE_LEGACY_GRASS) + { + new_position_global.mdV[VZ] = LLWorld::getInstance()->resolveLandHeightGlobal(new_position_global) + 1.f; + } + + if (object->isRootEdit()) + { + new_position_global = LLWorld::getInstance()->clipToVisibleRegions(object->getPositionGlobal(), new_position_global); + } + + // PR: Only update if changed + LLVector3 old_position_agent = object->getPositionAgent(); + LLVector3 new_position_agent = gAgent.getPosAgentFromGlobal(new_position_global); + if (object->isRootEdit()) + { + // finally, move parent object after children have calculated new offsets + object->setPositionAgent(new_position_agent); + rebuild(object); + } + else + { + LLViewerObject* root_object = object->getRootEdit(); + new_position_agent -= root_object->getPositionAgent(); + new_position_agent = new_position_agent * ~root_object->getRotation(); + object->setPositionParent(new_position_agent, false); + rebuild(object); + } + + if (selectNode->mIndividualSelection) + { + // counter-translate child objects if we are moving the root as an individual + object->resetChildrenPosition(old_position_agent - new_position_agent, true) ; + } + } + selectNode->mLastPositionLocal = object->getPosition(); + } + } + + LLSelectMgr::getInstance()->updateSelectionCenter(); + gAgentCamera.clearFocusObject(); + dialog_refresh_all(); // ??? is this necessary? + + LL_DEBUGS("UserInput") << "hover handled by LLManipTranslate (active)" << LL_ENDL; + gViewerWindow->setCursor(UI_CURSOR_TOOLTRANSLATE); + return true; +} + +void LLManipTranslate::highlightManipulators(S32 x, S32 y) +{ + mHighlightedPart = LL_NO_PART; + + if (!mObjectSelection->getObjectCount()) + { + return; + } + + //LLBBox bbox = LLSelectMgr::getInstance()->getBBoxOfSelection(); + LLMatrix4 projMatrix = LLViewerCamera::getInstance()->getProjection(); + LLMatrix4 modelView = LLViewerCamera::getInstance()->getModelview(); + + LLVector3 object_position = getPivotPoint(); + + LLVector3 grid_origin; + LLVector3 grid_scale; + LLQuaternion grid_rotation; + + LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale); + + LLVector3 relative_camera_dir; + + LLMatrix4 transform; + + if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) + { + relative_camera_dir = LLVector3(1.f, 0.f, 0.f) * ~grid_rotation; + LLVector4 translation(object_position); + transform.initRotTrans(grid_rotation, translation); + LLMatrix4 cfr(OGL_TO_CFR_ROTATION); + transform *= cfr; + LLMatrix4 window_scale; + F32 zoom_level = 2.f * gAgentCamera.mHUDCurZoom; + window_scale.initAll(LLVector3(zoom_level / LLViewerCamera::getInstance()->getAspect(), zoom_level, 0.f), + LLQuaternion::DEFAULT, + LLVector3::zero); + transform *= window_scale; + } + else + { + relative_camera_dir = (object_position - LLViewerCamera::getInstance()->getOrigin()) * ~grid_rotation; + relative_camera_dir.normVec(); + + transform.initRotTrans(grid_rotation, LLVector4(object_position)); + transform *= modelView; + transform *= projMatrix; + } + + S32 numManips = 0; + + // edges + mManipulatorVertices[numManips++] = LLVector4(mArrowLengthMeters * MANIPULATOR_HOTSPOT_START, 0.f, 0.f, 1.f); + mManipulatorVertices[numManips++] = LLVector4(mArrowLengthMeters * MANIPULATOR_HOTSPOT_END, 0.f, 0.f, 1.f); + + mManipulatorVertices[numManips++] = LLVector4(0.f, mArrowLengthMeters * MANIPULATOR_HOTSPOT_START, 0.f, 1.f); + mManipulatorVertices[numManips++] = LLVector4(0.f, mArrowLengthMeters * MANIPULATOR_HOTSPOT_END, 0.f, 1.f); + + mManipulatorVertices[numManips++] = LLVector4(0.f, 0.f, mArrowLengthMeters * MANIPULATOR_HOTSPOT_START, 1.f); + mManipulatorVertices[numManips++] = LLVector4(0.f, 0.f, mArrowLengthMeters * MANIPULATOR_HOTSPOT_END, 1.f); + + mManipulatorVertices[numManips++] = LLVector4(mArrowLengthMeters * -MANIPULATOR_HOTSPOT_START, 0.f, 0.f, 1.f); + mManipulatorVertices[numManips++] = LLVector4(mArrowLengthMeters * -MANIPULATOR_HOTSPOT_END, 0.f, 0.f, 1.f); + + mManipulatorVertices[numManips++] = LLVector4(0.f, mArrowLengthMeters * -MANIPULATOR_HOTSPOT_START, 0.f, 1.f); + mManipulatorVertices[numManips++] = LLVector4(0.f, mArrowLengthMeters * -MANIPULATOR_HOTSPOT_END, 0.f, 1.f); + + mManipulatorVertices[numManips++] = LLVector4(0.f, 0.f, mArrowLengthMeters * -MANIPULATOR_HOTSPOT_START, 1.f); + mManipulatorVertices[numManips++] = LLVector4(0.f, 0.f, mArrowLengthMeters * -MANIPULATOR_HOTSPOT_END, 1.f); + + S32 num_arrow_manips = numManips; + + // planar manipulators + bool planar_manip_yz_visible = false; + bool planar_manip_xz_visible = false; + bool planar_manip_xy_visible = false; + + mManipulatorVertices[numManips] = LLVector4(0.f, mPlaneManipOffsetMeters * (1.f - PLANE_TICK_SIZE * 0.5f), mPlaneManipOffsetMeters * (1.f - PLANE_TICK_SIZE * 0.5f), 1.f); + mManipulatorVertices[numManips++].scaleVec(mPlaneManipPositions); + mManipulatorVertices[numManips] = LLVector4(0.f, mPlaneManipOffsetMeters * (1.f + PLANE_TICK_SIZE * 0.5f), mPlaneManipOffsetMeters * (1.f + PLANE_TICK_SIZE * 0.5f), 1.f); + mManipulatorVertices[numManips++].scaleVec(mPlaneManipPositions); + if (llabs(relative_camera_dir.mV[VX]) > MIN_PLANE_MANIP_DOT_PRODUCT) + { + planar_manip_yz_visible = true; + } + + mManipulatorVertices[numManips] = LLVector4(mPlaneManipOffsetMeters * (1.f - PLANE_TICK_SIZE * 0.5f), 0.f, mPlaneManipOffsetMeters * (1.f - PLANE_TICK_SIZE * 0.5f), 1.f); + mManipulatorVertices[numManips++].scaleVec(mPlaneManipPositions); + mManipulatorVertices[numManips] = LLVector4(mPlaneManipOffsetMeters * (1.f + PLANE_TICK_SIZE * 0.5f), 0.f, mPlaneManipOffsetMeters * (1.f + PLANE_TICK_SIZE * 0.5f), 1.f); + mManipulatorVertices[numManips++].scaleVec(mPlaneManipPositions); + if (llabs(relative_camera_dir.mV[VY]) > MIN_PLANE_MANIP_DOT_PRODUCT) + { + planar_manip_xz_visible = true; + } + + mManipulatorVertices[numManips] = LLVector4(mPlaneManipOffsetMeters * (1.f - PLANE_TICK_SIZE * 0.5f), mPlaneManipOffsetMeters * (1.f - PLANE_TICK_SIZE * 0.5f), 0.f, 1.f); + mManipulatorVertices[numManips++].scaleVec(mPlaneManipPositions); + mManipulatorVertices[numManips] = LLVector4(mPlaneManipOffsetMeters * (1.f + PLANE_TICK_SIZE * 0.5f), mPlaneManipOffsetMeters * (1.f + PLANE_TICK_SIZE * 0.5f), 0.f, 1.f); + mManipulatorVertices[numManips++].scaleVec(mPlaneManipPositions); + if (llabs(relative_camera_dir.mV[VZ]) > MIN_PLANE_MANIP_DOT_PRODUCT) + { + planar_manip_xy_visible = true; + } + + // Project up to 9 manipulators to screen space 2*X, 2*Y, 2*Z, 3*planes + std::vector projected_manipulators; + projected_manipulators.reserve(9); + + for (S32 i = 0; i < num_arrow_manips; i+= 2) + { + LLVector4 projected_start = mManipulatorVertices[i] * transform; + projected_start = projected_start * (1.f / projected_start.mV[VW]); + + LLVector4 projected_end = mManipulatorVertices[i + 1] * transform; + projected_end = projected_end * (1.f / projected_end.mV[VW]); + + ManipulatorHandle projected_manip( + LLVector3(projected_start.mV[VX], projected_start.mV[VY], projected_start.mV[VZ]), + LLVector3(projected_end.mV[VX], projected_end.mV[VY], projected_end.mV[VZ]), + MANIPULATOR_IDS[i / 2], + 10.f); // 10 pixel hotspot for arrows + projected_manipulators.push_back(projected_manip); + } + + if (planar_manip_yz_visible) + { + S32 i = num_arrow_manips; + LLVector4 projected_start = mManipulatorVertices[i] * transform; + projected_start = projected_start * (1.f / projected_start.mV[VW]); + + LLVector4 projected_end = mManipulatorVertices[i + 1] * transform; + projected_end = projected_end * (1.f / projected_end.mV[VW]); + + ManipulatorHandle projected_manip( + LLVector3(projected_start.mV[VX], projected_start.mV[VY], projected_start.mV[VZ]), + LLVector3(projected_end.mV[VX], projected_end.mV[VY], projected_end.mV[VZ]), + MANIPULATOR_IDS[i / 2], + 20.f); // 20 pixels for planar manipulators + projected_manipulators.push_back(projected_manip); + } + + if (planar_manip_xz_visible) + { + S32 i = num_arrow_manips + 2; + LLVector4 projected_start = mManipulatorVertices[i] * transform; + projected_start = projected_start * (1.f / projected_start.mV[VW]); + + LLVector4 projected_end = mManipulatorVertices[i + 1] * transform; + projected_end = projected_end * (1.f / projected_end.mV[VW]); + + ManipulatorHandle projected_manip( + LLVector3(projected_start.mV[VX], projected_start.mV[VY], projected_start.mV[VZ]), + LLVector3(projected_end.mV[VX], projected_end.mV[VY], projected_end.mV[VZ]), + MANIPULATOR_IDS[i / 2], + 20.f); // 20 pixels for planar manipulators + projected_manipulators.push_back(projected_manip); + } + + if (planar_manip_xy_visible) + { + S32 i = num_arrow_manips + 4; + LLVector4 projected_start = mManipulatorVertices[i] * transform; + projected_start = projected_start * (1.f / projected_start.mV[VW]); + + LLVector4 projected_end = mManipulatorVertices[i + 1] * transform; + projected_end = projected_end * (1.f / projected_end.mV[VW]); + + ManipulatorHandle projected_manip( + LLVector3(projected_start.mV[VX], projected_start.mV[VY], projected_start.mV[VZ]), + LLVector3(projected_end.mV[VX], projected_end.mV[VY], projected_end.mV[VZ]), + MANIPULATOR_IDS[i / 2], + 20.f); // 20 pixels for planar manipulators + projected_manipulators.push_back(projected_manip); + } + + LLVector2 manip_start_2d; + LLVector2 manip_end_2d; + LLVector2 manip_dir; + LLRect world_view_rect = gViewerWindow->getWorldViewRectScaled(); + F32 half_width = (F32)world_view_rect.getWidth() / 2.f; + F32 half_height = (F32)world_view_rect.getHeight() / 2.f; + LLVector2 mousePos((F32)x - half_width, (F32)y - half_height); + LLVector2 mouse_delta; + + // Keep order consistent with insertion via stable_sort + std::stable_sort( projected_manipulators.begin(), + projected_manipulators.end(), + ClosestToCamera() ); + + std::vector::iterator it = projected_manipulators.begin(); + for ( ; it != projected_manipulators.end(); ++it) + { + ManipulatorHandle& manipulator = *it; + { + manip_start_2d.setVec(manipulator.mStartPosition.mV[VX] * half_width, manipulator.mStartPosition.mV[VY] * half_height); + manip_end_2d.setVec(manipulator.mEndPosition.mV[VX] * half_width, manipulator.mEndPosition.mV[VY] * half_height); + manip_dir = manip_end_2d - manip_start_2d; + + mouse_delta = mousePos - manip_start_2d; + + F32 manip_length = manip_dir.normVec(); + + F32 mouse_pos_manip = mouse_delta * manip_dir; + F32 mouse_dist_manip_squared = mouse_delta.magVecSquared() - (mouse_pos_manip * mouse_pos_manip); + + if (mouse_pos_manip > 0.f && + mouse_pos_manip < manip_length && + mouse_dist_manip_squared < manipulator.mHotSpotRadius * manipulator.mHotSpotRadius) + { + mHighlightedPart = manipulator.mManipID; + break; + } + } + } +} + +F32 LLManipTranslate::getMinGridScale() +{ + F32 scale; + switch (mManipPart) + { + case LL_NO_PART: + default: + scale = 1.f; + break; + case LL_X_ARROW: + scale = mGridScale.mV[VX]; + break; + case LL_Y_ARROW: + scale = mGridScale.mV[VY]; + break; + case LL_Z_ARROW: + scale = mGridScale.mV[VZ]; + break; + case LL_YZ_PLANE: + scale = llmin(mGridScale.mV[VY], mGridScale.mV[VZ]); + break; + case LL_XZ_PLANE: + scale = llmin(mGridScale.mV[VX], mGridScale.mV[VZ]); + break; + case LL_XY_PLANE: + scale = llmin(mGridScale.mV[VX], mGridScale.mV[VY]); + break; + } + + return scale; +} + + +bool LLManipTranslate::handleMouseUp(S32 x, S32 y, MASK mask) +{ + // first, perform normal processing in case this was a quick-click + handleHover(x, y, mask); + + if(hasMouseCapture()) + { + // make sure arrow colors go back to normal + mManipPart = LL_NO_PART; + LLSelectMgr::getInstance()->enableSilhouette(true); + + // Might have missed last update due to UPDATE_DELAY timing. + LLSelectMgr::getInstance()->sendMultipleUpdate( UPD_POSITION ); + + mInSnapRegime = false; + LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); + //gAgent.setObjectTracking(gSavedSettings.getBOOL("TrackFocusObject")); + } + + return LLManip::handleMouseUp(x, y, mask); +} + + +void LLManipTranslate::render() +{ + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) + { + F32 zoom = gAgentCamera.mHUDCurZoom; + gGL.scalef(zoom, zoom, zoom); + } + { + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); + renderGuidelines(); + } + { + //LLGLDisable gls_stencil(GL_STENCIL_TEST); + renderTranslationHandles(); + renderSnapGuides(); + } + gGL.popMatrix(); + + renderText(); +} + +void LLManipTranslate::renderSnapGuides() +{ + if (!gSavedSettings.getBOOL("SnapEnabled")) + { + return; + } + + F32 max_subdivisions = sGridMaxSubdivisionLevel;//(F32)gSavedSettings.getS32("GridSubdivision"); + F32 line_alpha = gSavedSettings.getF32("GridOpacity"); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + LLGLDepthTest gls_depth(GL_TRUE); + LLGLDisable gls_cull(GL_CULL_FACE); + LLVector3 translate_axis; + + if (mManipPart == LL_NO_PART) + { + return; + } + + LLSelectNode *first_node = mObjectSelection->getFirstMoveableNode(true); + if (!first_node) + { + return; + } + + updateGridSettings(); + + F32 smallest_grid_unit_scale = getMinGridScale() / max_subdivisions; + LLVector3 grid_origin; + LLVector3 grid_scale; + LLQuaternion grid_rotation; + + LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale); + LLVector3 saved_selection_center = getSavedPivotPoint(); //LLSelectMgr::getInstance()->getSavedBBoxOfSelection().getCenterAgent(); + LLVector3 selection_center = getPivotPoint(); + + LLViewerObject *first_object = first_node->getObject(); + + //pick appropriate projection plane for snap rulers according to relative camera position + if (mManipPart >= LL_X_ARROW && mManipPart <= LL_Z_ARROW) + { + LLVector3 normal; + LLColor4 inner_color; + LLManip::EManipPart temp_manip = mManipPart; + switch (mManipPart) + { + case LL_X_ARROW: + normal.setVec(1,0,0); + inner_color.setVec(0,1,1,line_alpha); + mManipPart = LL_YZ_PLANE; + break; + case LL_Y_ARROW: + normal.setVec(0,1,0); + inner_color.setVec(1,0,1,line_alpha); + mManipPart = LL_XZ_PLANE; + break; + case LL_Z_ARROW: + normal.setVec(0,0,1); + inner_color.setVec(1,1,0,line_alpha); + mManipPart = LL_XY_PLANE; + break; + default: + break; + } + + highlightIntersection(normal, selection_center, grid_rotation, inner_color); + mManipPart = temp_manip; + getManipAxis(first_object, mManipPart, translate_axis); + + LLVector3 at_axis_abs; + if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) + { + at_axis_abs = LLVector3::x_axis * ~grid_rotation; + } + else + { + at_axis_abs = saved_selection_center - LLViewerCamera::getInstance()->getOrigin(); + at_axis_abs.normVec(); + + at_axis_abs = at_axis_abs * ~grid_rotation; + } + at_axis_abs.abs(); + + if (at_axis_abs.mV[VX] > at_axis_abs.mV[VY] && at_axis_abs.mV[VX] > at_axis_abs.mV[VZ]) + { + if (mManipPart == LL_Y_ARROW) + { + mSnapOffsetAxis = LLVector3::z_axis; + } + else if (mManipPart == LL_Z_ARROW) + { + mSnapOffsetAxis = LLVector3::y_axis; + } + else if (at_axis_abs.mV[VY] > at_axis_abs.mV[VZ]) + { + mSnapOffsetAxis = LLVector3::z_axis; + } + else + { + mSnapOffsetAxis = LLVector3::y_axis; + } + } + else if (at_axis_abs.mV[VY] > at_axis_abs.mV[VZ]) + { + if (mManipPart == LL_X_ARROW) + { + mSnapOffsetAxis = LLVector3::z_axis; + } + else if (mManipPart == LL_Z_ARROW) + { + mSnapOffsetAxis = LLVector3::x_axis; + } + else if (at_axis_abs.mV[VX] > at_axis_abs.mV[VZ]) + { + mSnapOffsetAxis = LLVector3::z_axis; + } + else + { + mSnapOffsetAxis = LLVector3::x_axis; + } + } + else + { + if (mManipPart == LL_X_ARROW) + { + mSnapOffsetAxis = LLVector3::y_axis; + } + else if (mManipPart == LL_Y_ARROW) + { + mSnapOffsetAxis = LLVector3::x_axis; + } + else if (at_axis_abs.mV[VX] > at_axis_abs.mV[VY]) + { + mSnapOffsetAxis = LLVector3::y_axis; + } + else + { + mSnapOffsetAxis = LLVector3::x_axis; + } + } + + mSnapOffsetAxis = mSnapOffsetAxis * grid_rotation; + + F32 guide_size_meters; + + if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) + { + guide_size_meters = 1.f / gAgentCamera.mHUDCurZoom; + mSnapOffsetMeters = mArrowLengthMeters * 1.5f; + } + else + { + LLVector3 cam_to_selection = getPivotPoint() - LLViewerCamera::getInstance()->getOrigin(); + F32 current_range = cam_to_selection.normVec(); + guide_size_meters = SNAP_GUIDE_SCREEN_SIZE * gViewerWindow->getWorldViewHeightRaw() * current_range / LLViewerCamera::getInstance()->getPixelMeterRatio(); + + F32 fraction_of_fov = mAxisArrowLength / (F32) LLViewerCamera::getInstance()->getViewHeightInPixels(); + F32 apparent_angle = fraction_of_fov * LLViewerCamera::getInstance()->getView(); // radians + F32 offset_at_camera = tan(apparent_angle) * 1.5f; + F32 range = dist_vec(gAgent.getPosAgentFromGlobal(first_node->mSavedPositionGlobal), LLViewerCamera::getInstance()->getOrigin()); + mSnapOffsetMeters = range * offset_at_camera; + } + + LLVector3 tick_start; + LLVector3 tick_end; + + // how far away from grid origin is the selection along the axis of translation? + F32 dist_grid_axis = (selection_center - mGridOrigin) * translate_axis; + // find distance to nearest smallest grid unit + F32 offset_nearest_grid_unit = fmodf(dist_grid_axis, smallest_grid_unit_scale); + // how many smallest grid units are we away from largest grid scale? + S32 sub_div_offset = ll_round(fmodf(dist_grid_axis - offset_nearest_grid_unit, getMinGridScale() / sGridMinSubdivisionLevel) / smallest_grid_unit_scale); + S32 num_ticks_per_side = llmax(1, llfloor(0.5f * guide_size_meters / smallest_grid_unit_scale)); + + LLGLDepthTest gls_depth(GL_FALSE); + + for (S32 pass = 0; pass < 3; pass++) + { + LLColor4 line_color = setupSnapGuideRenderPass(pass); + LLGLDepthTest gls_depth(pass != 1); + + gGL.begin(LLRender::LINES); + { + LLVector3 line_start = selection_center + (mSnapOffsetMeters * mSnapOffsetAxis) + (translate_axis * (guide_size_meters * 0.5f + offset_nearest_grid_unit)); + LLVector3 line_end = selection_center + (mSnapOffsetMeters * mSnapOffsetAxis) - (translate_axis * (guide_size_meters * 0.5f + offset_nearest_grid_unit)); + LLVector3 line_mid = (line_start + line_end) * 0.5f; + + gGL.color4f(line_color.mV[VX], line_color.mV[VY], line_color.mV[VZ], line_color.mV[VW] * 0.2f); + gGL.vertex3fv(line_start.mV); + gGL.color4f(line_color.mV[VX], line_color.mV[VY], line_color.mV[VZ], line_color.mV[VW]); + gGL.vertex3fv(line_mid.mV); + gGL.vertex3fv(line_mid.mV); + gGL.color4f(line_color.mV[VX], line_color.mV[VY], line_color.mV[VZ], line_color.mV[VW] * 0.2f); + gGL.vertex3fv(line_end.mV); + + line_start.setVec(selection_center + (mSnapOffsetAxis * -mSnapOffsetMeters) + (translate_axis * guide_size_meters * 0.5f)); + line_end.setVec(selection_center + (mSnapOffsetAxis * -mSnapOffsetMeters) - (translate_axis * guide_size_meters * 0.5f)); + line_mid = (line_start + line_end) * 0.5f; + + gGL.color4f(line_color.mV[VX], line_color.mV[VY], line_color.mV[VZ], line_color.mV[VW] * 0.2f); + gGL.vertex3fv(line_start.mV); + gGL.color4f(line_color.mV[VX], line_color.mV[VY], line_color.mV[VZ], line_color.mV[VW]); + gGL.vertex3fv(line_mid.mV); + gGL.vertex3fv(line_mid.mV); + gGL.color4f(line_color.mV[VX], line_color.mV[VY], line_color.mV[VZ], line_color.mV[VW] * 0.2f); + gGL.vertex3fv(line_end.mV); + + for (S32 i = -num_ticks_per_side; i <= num_ticks_per_side; i++) + { + tick_start = selection_center + (translate_axis * (smallest_grid_unit_scale * (F32)i - offset_nearest_grid_unit)); + + //No need check this condition to prevent tick position scaling (FIX MAINT-5207/5208) + //F32 cur_subdivisions = getSubdivisionLevel(tick_start, translate_axis, getMinGridScale()); + /*if (fmodf((F32)(i + sub_div_offset), (max_subdivisions / cur_subdivisions)) != 0.f) + { + continue; + }*/ + + // add in off-axis offset + tick_start += (mSnapOffsetAxis * mSnapOffsetMeters); + + F32 tick_scale = 1.f; + for (F32 division_level = max_subdivisions; division_level >= sGridMinSubdivisionLevel; division_level /= 2.f) + { + if (fmodf((F32)(i + sub_div_offset), division_level) == 0.f) + { + break; + } + tick_scale *= 0.7f; + } + +// S32 num_ticks_to_fade = is_sub_tick ? num_ticks_per_side / 2 : num_ticks_per_side; +// F32 alpha = line_alpha * (1.f - (0.8f * ((F32)llabs(i) / (F32)num_ticks_to_fade))); + + tick_end = tick_start + (mSnapOffsetAxis * mSnapOffsetMeters * tick_scale); + + gGL.color4f(line_color.mV[VX], line_color.mV[VY], line_color.mV[VZ], line_color.mV[VW]); + gGL.vertex3fv(tick_start.mV); + gGL.vertex3fv(tick_end.mV); + + tick_start = selection_center + (mSnapOffsetAxis * -mSnapOffsetMeters) + + (translate_axis * (getMinGridScale() / (F32)(max_subdivisions) * (F32)i - offset_nearest_grid_unit)); + tick_end = tick_start - (mSnapOffsetAxis * mSnapOffsetMeters * tick_scale); + + gGL.vertex3fv(tick_start.mV); + gGL.vertex3fv(tick_end.mV); + } + } + gGL.end(); + + if (mInSnapRegime) + { + LLVector3 line_start = selection_center - mSnapOffsetAxis * mSnapOffsetMeters; + LLVector3 line_end = selection_center + mSnapOffsetAxis * mSnapOffsetMeters; + + gGL.begin(LLRender::LINES); + { + gGL.color4f(line_color.mV[VX], line_color.mV[VY], line_color.mV[VZ], line_color.mV[VW]); + + gGL.vertex3fv(line_start.mV); + gGL.vertex3fv(line_end.mV); + } + gGL.end(); + + // draw snap guide arrow + gGL.begin(LLRender::TRIANGLES); + { + gGL.color4f(line_color.mV[VX], line_color.mV[VY], line_color.mV[VZ], line_color.mV[VW]); + + LLVector3 arrow_dir; + LLVector3 arrow_span = translate_axis; + + arrow_dir = -mSnapOffsetAxis; + gGL.vertex3fv((line_start + arrow_dir * mConeSize * SNAP_ARROW_SCALE).mV); + gGL.vertex3fv((line_start + arrow_span * mConeSize * SNAP_ARROW_SCALE).mV); + gGL.vertex3fv((line_start - arrow_span * mConeSize * SNAP_ARROW_SCALE).mV); + + arrow_dir = mSnapOffsetAxis; + gGL.vertex3fv((line_end + arrow_dir * mConeSize * SNAP_ARROW_SCALE).mV); + gGL.vertex3fv((line_end + arrow_span * mConeSize * SNAP_ARROW_SCALE).mV); + gGL.vertex3fv((line_end - arrow_span * mConeSize * SNAP_ARROW_SCALE).mV); + } + gGL.end(); + } + } + + sub_div_offset = ll_round(fmod(dist_grid_axis - offset_nearest_grid_unit, getMinGridScale() * 32.f) / smallest_grid_unit_scale); + + LLVector2 screen_translate_axis(llabs(translate_axis * LLViewerCamera::getInstance()->getLeftAxis()), llabs(translate_axis * LLViewerCamera::getInstance()->getUpAxis())); + screen_translate_axis.normVec(); + + S32 tick_label_spacing = ll_round(screen_translate_axis * sTickLabelSpacing); + + // render tickmark values + for (S32 i = -num_ticks_per_side; i <= num_ticks_per_side; i++) + { + LLVector3 tick_pos = selection_center + (translate_axis * ((smallest_grid_unit_scale * (F32)i) - offset_nearest_grid_unit)); + F32 alpha = line_alpha * (1.f - (0.5f * ((F32)llabs(i) / (F32)num_ticks_per_side))); + + F32 tick_scale = 1.f; + for (F32 division_level = max_subdivisions; division_level >= sGridMinSubdivisionLevel; division_level /= 2.f) + { + if (fmodf((F32)(i + sub_div_offset), division_level) == 0.f) + { + break; + } + tick_scale *= 0.7f; + } + + if (fmodf((F32)(i + sub_div_offset), (max_subdivisions / getSubdivisionLevel(tick_pos, translate_axis, getMinGridScale(), tick_label_spacing))) == 0.f) + { + F32 snap_offset_meters; + + if (mSnapOffsetAxis * LLViewerCamera::getInstance()->getUpAxis() > 0.f) + { + snap_offset_meters = mSnapOffsetMeters; + } + else + { + snap_offset_meters = -mSnapOffsetMeters; + } + LLVector3 text_origin = selection_center + + (translate_axis * ((smallest_grid_unit_scale * (F32)i) - offset_nearest_grid_unit)) + + (mSnapOffsetAxis * snap_offset_meters * (1.f + tick_scale)); + + LLVector3 tick_offset = (tick_pos - mGridOrigin) * ~mGridRotation; + F32 offset_val = 0.5f * tick_offset.mV[ARROW_TO_AXIS[mManipPart]] / getMinGridScale(); + EGridMode grid_mode = LLSelectMgr::getInstance()->getGridMode(); + F32 text_highlight = 0.8f; + if(i - ll_round(offset_nearest_grid_unit / smallest_grid_unit_scale) == 0 && mInSnapRegime) + { + text_highlight = 1.f; + } + + if (grid_mode == GRID_MODE_WORLD) + { + // rescale units to meters from multiple of grid scale + offset_val *= 2.f * grid_scale[ARROW_TO_AXIS[mManipPart]]; + renderTickValue(text_origin, offset_val, std::string("m"), LLColor4(text_highlight, text_highlight, text_highlight, alpha)); + } + else + { + renderTickValue(text_origin, offset_val, std::string("x"), LLColor4(text_highlight, text_highlight, text_highlight, alpha)); + } + } + } + if (mObjectSelection->getSelectType() != SELECT_TYPE_HUD) + { + // render helpful text + if (mHelpTextTimer.getElapsedTimeF32() < sHelpTextVisibleTime + sHelpTextFadeTime && sNumTimesHelpTextShown < sMaxTimesShowHelpText) + { + F32 snap_offset_meters_up; + if (mSnapOffsetAxis * LLViewerCamera::getInstance()->getUpAxis() > 0.f) + { + snap_offset_meters_up = mSnapOffsetMeters; + } + else + { + snap_offset_meters_up = -mSnapOffsetMeters; + } + + LLVector3 selection_center_start = getSavedPivotPoint();//LLSelectMgr::getInstance()->getSavedBBoxOfSelection().getCenterAgent(); + + LLVector3 help_text_pos = selection_center_start + (snap_offset_meters_up * 3.f * mSnapOffsetAxis); + const LLFontGL* big_fontp = LLFontGL::getFontSansSerif(); + + std::string help_text = LLTrans::getString("manip_hint1"); + LLColor4 help_text_color = LLColor4::white; + help_text_color.mV[VALPHA] = clamp_rescale(mHelpTextTimer.getElapsedTimeF32(), sHelpTextVisibleTime, sHelpTextVisibleTime + sHelpTextFadeTime, line_alpha, 0.f); + hud_render_utf8text(help_text, help_text_pos, *big_fontp, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, -0.5f * big_fontp->getWidthF32(help_text), 3.f, help_text_color, false); + help_text = LLTrans::getString("manip_hint2"); + help_text_pos -= LLViewerCamera::getInstance()->getUpAxis() * mSnapOffsetMeters * 0.2f; + hud_render_utf8text(help_text, help_text_pos, *big_fontp, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, -0.5f * big_fontp->getWidthF32(help_text), 3.f, help_text_color, false); + } + } + } + else + { + // render gridlines for planar snapping + + F32 u = 0, v = 0; + LLColor4 inner_color; + LLVector3 normal; + LLVector3 grid_center = selection_center - grid_origin; + F32 usc = 1; + F32 vsc = 1; + + grid_center *= ~grid_rotation; + + switch (mManipPart) + { + case LL_YZ_PLANE: + u = grid_center.mV[VY]; + v = grid_center.mV[VZ]; + usc = grid_scale.mV[VY]; + vsc = grid_scale.mV[VZ]; + inner_color.setVec(0,1,1,line_alpha); + normal.setVec(1,0,0); + break; + case LL_XZ_PLANE: + u = grid_center.mV[VX]; + v = grid_center.mV[VZ]; + usc = grid_scale.mV[VX]; + vsc = grid_scale.mV[VZ]; + inner_color.setVec(1,0,1,line_alpha); + normal.setVec(0,1,0); + break; + case LL_XY_PLANE: + u = grid_center.mV[VX]; + v = grid_center.mV[VY]; + usc = grid_scale.mV[VX]; + vsc = grid_scale.mV[VY]; + inner_color.setVec(1,1,0,line_alpha); + normal.setVec(0,0,1); + break; + default: + break; + } + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + highlightIntersection(normal, selection_center, grid_rotation, inner_color); + + gGL.pushMatrix(); + + F32 x,y,z,angle_radians; + grid_rotation.getAngleAxis(&angle_radians, &x, &y, &z); + gGL.translatef(selection_center.mV[VX], selection_center.mV[VY], selection_center.mV[VZ]); + gGL.rotatef(angle_radians * RAD_TO_DEG, x, y, z); + + F32 sz = mGridSizeMeters; + F32 tiles = sz; + + gGL.matrixMode(LLRender::MM_TEXTURE); + gGL.pushMatrix(); + usc = 1.0f/usc; + vsc = 1.0f/vsc; + + while (usc > vsc*4.0f) + { + usc *= 0.5f; + } + while (vsc > usc * 4.0f) + { + vsc *= 0.5f; + } + + gGL.scalef(usc, vsc, 1.0f); + gGL.translatef(u, v, 0); + + float a = line_alpha; + + { + //draw grid behind objects + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); + + { + //LLGLDisable stencil(GL_STENCIL_TEST); + { + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE, GL_GREATER); + gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, getGridTexName()); + gGL.flush(); + gGL.blendFunc(LLRender::BF_ZERO, LLRender::BF_ONE_MINUS_SOURCE_ALPHA); + renderGrid(u,v,tiles,0.9f, 0.9f, 0.9f,a*0.15f); + gGL.flush(); + gGL.setSceneBlendType(LLRender::BT_ALPHA); + } + + { + //draw black overlay + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + renderGrid(u,v,tiles,0.0f, 0.0f, 0.0f,a*0.16f); + + //draw grid top + gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, getGridTexName()); + renderGrid(u,v,tiles,1,1,1,a); + + gGL.popMatrix(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.popMatrix(); + } + + { + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); + renderGuidelines(); + } + + { + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE, GL_GREATER); + gGL.flush(); + + switch (mManipPart) + { + case LL_YZ_PLANE: + renderGuidelines(false, true, true); + break; + case LL_XZ_PLANE: + renderGuidelines(true, false, true); + break; + case LL_XY_PLANE: + renderGuidelines(true, true, false); + break; + default: + break; + } + gGL.flush(); + } + } + } + } +} + +void LLManipTranslate::renderGrid(F32 x, F32 y, F32 size, F32 r, F32 g, F32 b, F32 a) +{ + F32 d = size*0.5f; + + for (F32 xx = -size-d; xx < size+d; xx += d) + { + gGL.begin(LLRender::TRIANGLE_STRIP); + for (F32 yy = -size-d; yy < size+d; yy += d) + { + float dx, dy, da; + + dx = xx; dy = yy; + da = sqrtf(llmax(0.0f, 1.0f-sqrtf(dx*dx+dy*dy)/size))*a; + gGL.texCoord2f(dx, dy); + renderGridVert(dx,dy,r,g,b,da); + + dx = xx+d; dy = yy; + da = sqrtf(llmax(0.0f, 1.0f-sqrtf(dx*dx+dy*dy)/size))*a; + gGL.texCoord2f(dx, dy); + renderGridVert(dx,dy,r,g,b,da); + + dx = xx; dy = yy+d; + da = sqrtf(llmax(0.0f, 1.0f-sqrtf(dx*dx+dy*dy)/size))*a; + gGL.texCoord2f(dx, dy); + renderGridVert(dx,dy,r,g,b,da); + + dx = xx+d; dy = yy+d; + da = sqrtf(llmax(0.0f, 1.0f-sqrtf(dx*dx+dy*dy)/size))*a; + gGL.texCoord2f(dx, dy); + renderGridVert(dx,dy,r,g,b,da); + } + gGL.end(); + } + + +} + +void LLManipTranslate::highlightIntersection(LLVector3 normal, + LLVector3 selection_center, + LLQuaternion grid_rotation, + LLColor4 inner_color) +{ +#if 0 // DEPRECATED + if (!gSavedSettings.getBOOL("GridCrossSections")) + { + return; + } + + + LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; + + + static const U32 types[] = { LLRenderPass::PASS_SIMPLE, LLRenderPass::PASS_ALPHA, LLRenderPass::PASS_FULLBRIGHT, LLRenderPass::PASS_SHINY }; + static const U32 num_types = LL_ARRAY_SIZE(types); + + GLuint stencil_mask = 0xFFFFFFFF; + //stencil in volumes + + gGL.flush(); + + if (shader) + { + gClipProgram.bind(); + } + + { + //glStencilMask(stencil_mask); //deprecated + //glClearStencil(1); + //glClear(GL_STENCIL_BUFFER_BIT); + LLGLEnable cull_face(GL_CULL_FACE); + //LLGLEnable stencil(GL_STENCIL_TEST); + LLGLDepthTest depth (GL_TRUE, GL_FALSE, GL_ALWAYS); + //glStencilFunc(GL_ALWAYS, 0, stencil_mask); + gGL.setColorMask(false, false); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + gGL.diffuseColor4f(1,1,1,1); + + //setup clip plane + normal = normal * grid_rotation; + if (normal * (LLViewerCamera::getInstance()->getOrigin()-selection_center) < 0) + { + normal = -normal; + } + F32 d = -(selection_center * normal); + glh::vec4f plane(normal.mV[0], normal.mV[1], normal.mV[2], d ); + + gGL.getModelviewMatrix().inverse().mult_vec_matrix(plane); + + static LLStaticHashedString sClipPlane("clip_plane"); + gClipProgram.uniform4fv(sClipPlane, 1, plane.v); + + bool particles = gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_PARTICLES); + bool clouds = gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_CLOUDS); + + if (particles) + { + LLPipeline::toggleRenderType(LLPipeline::RENDER_TYPE_PARTICLES); + } + if (clouds) + { + LLPipeline::toggleRenderType(LLPipeline::RENDER_TYPE_CLOUDS); + } + + //stencil in volumes + //glStencilOp(GL_INCR, GL_INCR, GL_INCR); + glCullFace(GL_FRONT); + for (U32 i = 0; i < num_types; i++) + { + gPipeline.renderObjects(types[i], LLVertexBuffer::MAP_VERTEX, false); + } + + //glStencilOp(GL_DECR, GL_DECR, GL_DECR); + glCullFace(GL_BACK); + for (U32 i = 0; i < num_types; i++) + { + gPipeline.renderObjects(types[i], LLVertexBuffer::MAP_VERTEX, false); + } + + if (particles) + { + LLPipeline::toggleRenderType(LLPipeline::RENDER_TYPE_PARTICLES); + } + if (clouds) + { + LLPipeline::toggleRenderType(LLPipeline::RENDER_TYPE_CLOUDS); + } + + gGL.setColorMask(true, false); + } + gGL.color4f(1,1,1,1); + + gGL.pushMatrix(); + + F32 x,y,z,angle_radians; + grid_rotation.getAngleAxis(&angle_radians, &x, &y, &z); + gGL.translatef(selection_center.mV[VX], selection_center.mV[VY], selection_center.mV[VZ]); + gGL.rotatef(angle_radians * RAD_TO_DEG, x, y, z); + + F32 sz = mGridSizeMeters; + F32 tiles = sz; + + if (shader) + { + shader->bind(); + } + + if (shader) + { + shader->bind(); + } + + //draw volume/plane intersections + { + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + LLGLDepthTest depth(GL_FALSE); + //LLGLEnable stencil(GL_STENCIL_TEST); + glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); + glStencilFunc(GL_EQUAL, 0, stencil_mask); + renderGrid(0,0,tiles,inner_color.mV[0], inner_color.mV[1], inner_color.mV[2], 0.25f); + } + + glStencilFunc(GL_ALWAYS, 255, 0xFFFFFFFF); + glStencilMask(0xFFFFFFFF); + glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + + gGL.popMatrix(); +#endif +} + +void LLManipTranslate::renderText() +{ + if (mObjectSelection->getRootObjectCount() && !mObjectSelection->isAttachment()) + { + LLVector3 pos = getPivotPoint(); + renderXYZ(pos); + } + else + { + const bool children_ok = true; + LLViewerObject* objectp = mObjectSelection->getFirstRootObject(children_ok); + if (objectp) + { + renderXYZ(objectp->getPositionEdit()); + } + } +} + +void LLManipTranslate::renderTranslationHandles() +{ + LLVector3 grid_origin; + LLVector3 grid_scale; + LLQuaternion grid_rotation; + LLGLDepthTest gls_depth(GL_FALSE); + + LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale); + LLVector3 at_axis; + if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) + { + at_axis = LLVector3::x_axis * ~grid_rotation; + } + else + { + at_axis = LLViewerCamera::getInstance()->getAtAxis() * ~grid_rotation; + } + + if (at_axis.mV[VX] > 0.f) + { + mPlaneManipPositions.mV[VX] = 1.f; + } + else + { + mPlaneManipPositions.mV[VX] = -1.f; + } + + if (at_axis.mV[VY] > 0.f) + { + mPlaneManipPositions.mV[VY] = 1.f; + } + else + { + mPlaneManipPositions.mV[VY] = -1.f; + } + + if (at_axis.mV[VZ] > 0.f) + { + mPlaneManipPositions.mV[VZ] = 1.f; + } + else + { + mPlaneManipPositions.mV[VZ] = -1.f; + } + + LLViewerObject *first_object = mObjectSelection->getFirstMoveableObject(true); + if (!first_object) return; + + LLVector3 selection_center = getPivotPoint(); + + // Drag handles + if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) + { + mArrowLengthMeters = mAxisArrowLength / gViewerWindow->getWorldViewHeightRaw(); + mArrowLengthMeters /= gAgentCamera.mHUDCurZoom; + } + else + { + LLVector3 camera_pos_agent = gAgentCamera.getCameraPositionAgent(); + F32 range = dist_vec(camera_pos_agent, selection_center); + F32 range_from_agent = dist_vec(gAgent.getPositionAgent(), selection_center); + + // Don't draw handles if you're too far away + if (gSavedSettings.getBOOL("LimitSelectDistance")) + { + if (range_from_agent > gSavedSettings.getF32("MaxSelectDistance")) + { + return; + } + } + + if (range > 0.001f) + { + // range != zero + F32 fraction_of_fov = mAxisArrowLength / (F32) LLViewerCamera::getInstance()->getViewHeightInPixels(); + F32 apparent_angle = fraction_of_fov * LLViewerCamera::getInstance()->getView(); // radians + mArrowLengthMeters = range * tan(apparent_angle); + } + else + { + // range == zero + mArrowLengthMeters = 1.0f; + } + } + //Assume that UI scale factor is equivalent for X and Y axis + F32 ui_scale_factor = LLUI::getScaleFactor().mV[VX]; + mArrowLengthMeters *= ui_scale_factor; + + mPlaneManipOffsetMeters = mArrowLengthMeters * 1.8f; + mGridSizeMeters = gSavedSettings.getF32("GridDrawSize"); + mConeSize = mArrowLengthMeters / 4.f; + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + { + gGL.translatef(selection_center.mV[VX], selection_center.mV[VY], selection_center.mV[VZ]); + + F32 angle_radians, x, y, z; + grid_rotation.getAngleAxis(&angle_radians, &x, &y, &z); + + gGL.rotatef(angle_radians * RAD_TO_DEG, x, y, z); + + LLQuaternion invRotation = grid_rotation; + invRotation.conjQuat(); + + LLVector3 relative_camera_dir; + + if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) + { + relative_camera_dir = LLVector3::x_axis * invRotation; + } + else + { + relative_camera_dir = (selection_center - LLViewerCamera::getInstance()->getOrigin()) * invRotation; + } + relative_camera_dir.normVec(); + + { + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + LLGLDisable cull_face(GL_CULL_FACE); + + LLColor4 color1; + LLColor4 color2; + + // update manipulator sizes + for (S32 index = 0; index < 3; index++) + { + if (index == mManipPart - LL_X_ARROW || index == mHighlightedPart - LL_X_ARROW) + { + mArrowScales.mV[index] = lerp(mArrowScales.mV[index], SELECTED_ARROW_SCALE, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE )); + mPlaneScales.mV[index] = lerp(mPlaneScales.mV[index], 1.f, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE )); + } + else if (index == mManipPart - LL_YZ_PLANE || index == mHighlightedPart - LL_YZ_PLANE) + { + mArrowScales.mV[index] = lerp(mArrowScales.mV[index], 1.f, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE )); + mPlaneScales.mV[index] = lerp(mPlaneScales.mV[index], SELECTED_ARROW_SCALE, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE )); + } + else + { + mArrowScales.mV[index] = lerp(mArrowScales.mV[index], 1.f, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE )); + mPlaneScales.mV[index] = lerp(mPlaneScales.mV[index], 1.f, LLSmoothInterpolation::getInterpolant(MANIPULATOR_SCALE_HALF_LIFE )); + } + } + + if ((mManipPart == LL_NO_PART || mManipPart == LL_YZ_PLANE) && llabs(relative_camera_dir.mV[VX]) > MIN_PLANE_MANIP_DOT_PRODUCT) + { + // render YZ plane manipulator + gGL.pushMatrix(); + gGL.scalef(mPlaneManipPositions.mV[VX], mPlaneManipPositions.mV[VY], mPlaneManipPositions.mV[VZ]); + gGL.translatef(0.f, mPlaneManipOffsetMeters, mPlaneManipOffsetMeters); + gGL.scalef(mPlaneScales.mV[VX], mPlaneScales.mV[VX], mPlaneScales.mV[VX]); + if (mHighlightedPart == LL_YZ_PLANE) + { + color1.setVec(0.f, 1.f, 0.f, 1.f); + color2.setVec(0.f, 0.f, 1.f, 1.f); + } + else + { + color1.setVec(0.f, 1.f, 0.f, 0.6f); + color2.setVec(0.f, 0.f, 1.f, 0.6f); + } + gGL.begin(LLRender::TRIANGLES); + { + gGL.color4fv(color1.mV); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f)); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.75f)); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f)); + + gGL.color4fv(color2.mV); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f)); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.75f), mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f)); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f)); + } + gGL.end(); + + LLUI::setLineWidth(3.0f); + gGL.begin(LLRender::LINES); + { + gGL.color4f(0.f, 0.f, 0.f, 0.3f); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.1f); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.4f); + + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.1f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.4f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f); + } + gGL.end(); + LLUI::setLineWidth(1.0f); + gGL.popMatrix(); + } + + if ((mManipPart == LL_NO_PART || mManipPart == LL_XZ_PLANE) && llabs(relative_camera_dir.mV[VY]) > MIN_PLANE_MANIP_DOT_PRODUCT) + { + // render XZ plane manipulator + gGL.pushMatrix(); + gGL.scalef(mPlaneManipPositions.mV[VX], mPlaneManipPositions.mV[VY], mPlaneManipPositions.mV[VZ]); + gGL.translatef(mPlaneManipOffsetMeters, 0.f, mPlaneManipOffsetMeters); + gGL.scalef(mPlaneScales.mV[VY], mPlaneScales.mV[VY], mPlaneScales.mV[VY]); + if (mHighlightedPart == LL_XZ_PLANE) + { + color1.setVec(0.f, 0.f, 1.f, 1.f); + color2.setVec(1.f, 0.f, 0.f, 1.f); + } + else + { + color1.setVec(0.f, 0.f, 1.f, 0.6f); + color2.setVec(1.f, 0.f, 0.f, 0.6f); + } + + gGL.begin(LLRender::TRIANGLES); + { + gGL.color4fv(color1.mV); + gGL.vertex3f(mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f), 0.f, mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f)); + gGL.vertex3f(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.75f), 0.f, mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f)); + gGL.vertex3f(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), 0.f, mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f)); + + gGL.color4fv(color2.mV); + gGL.vertex3f(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), 0.f, mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f)); + gGL.vertex3f(mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f), 0.f, mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.75f)); + gGL.vertex3f(mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f), 0.f, mPlaneManipOffsetMeters * (PLANE_TICK_SIZE * 0.25f)); + } + gGL.end(); + + LLUI::setLineWidth(3.0f); + gGL.begin(LLRender::LINES); + { + gGL.color4f(0.f, 0.f, 0.f, 0.3f); + gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, 0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f, 0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f, 0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f, 0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.1f); + gGL.vertex3f(mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f, 0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f, 0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.4f); + + gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, 0.f, mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, 0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, 0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.1f, 0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f); + gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.25f, 0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.25f); + gGL.vertex3f(mPlaneManipOffsetMeters * -PLANE_TICK_SIZE * 0.4f, 0.f, mPlaneManipOffsetMeters * PLANE_TICK_SIZE * 0.1f); + } + gGL.end(); + LLUI::setLineWidth(1.0f); + + gGL.popMatrix(); + } + + if ((mManipPart == LL_NO_PART || mManipPart == LL_XY_PLANE) && llabs(relative_camera_dir.mV[VZ]) > MIN_PLANE_MANIP_DOT_PRODUCT) + { + // render XY plane manipulator + gGL.pushMatrix(); + gGL.scalef(mPlaneManipPositions.mV[VX], mPlaneManipPositions.mV[VY], mPlaneManipPositions.mV[VZ]); + +/* Y + ^ + v1 + | \ + |<- v0 + | /| \ + v2__v__v3 > X +*/ + LLVector3 v0,v1,v2,v3; +#if 0 + // This should theoretically work but looks off; could be tuned later -SJB + gGL.translatef(-mPlaneManipOffsetMeters, -mPlaneManipOffsetMeters, 0.f); + v0 = LLVector3(mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.25f), 0.f); + v1 = LLVector3(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.75f), 0.f); + v2 = LLVector3(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), 0.f); + v3 = LLVector3(mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.75f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), 0.f); +#else + gGL.translatef(mPlaneManipOffsetMeters, mPlaneManipOffsetMeters, 0.f); + v0 = LLVector3(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.25f), 0.f); + v1 = LLVector3(mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.75f), 0.f); + v2 = LLVector3(mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.25f), mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.25f), 0.f); + v3 = LLVector3(mPlaneManipOffsetMeters * (-PLANE_TICK_SIZE * 0.75f), mPlaneManipOffsetMeters * ( PLANE_TICK_SIZE * 0.25f), 0.f); +#endif + gGL.scalef(mPlaneScales.mV[VZ], mPlaneScales.mV[VZ], mPlaneScales.mV[VZ]); + if (mHighlightedPart == LL_XY_PLANE) + { + color1.setVec(1.f, 0.f, 0.f, 1.f); + color2.setVec(0.f, 1.f, 0.f, 1.f); + } + else + { + color1.setVec(0.8f, 0.f, 0.f, 0.6f); + color2.setVec(0.f, 0.8f, 0.f, 0.6f); + } + + gGL.begin(LLRender::TRIANGLES); + { + gGL.color4fv(color1.mV); + gGL.vertex3fv(v0.mV); + gGL.vertex3fv(v1.mV); + gGL.vertex3fv(v2.mV); + + gGL.color4fv(color2.mV); + gGL.vertex3fv(v2.mV); + gGL.vertex3fv(v3.mV); + gGL.vertex3fv(v0.mV); + } + gGL.end(); + + LLUI::setLineWidth(3.0f); + gGL.begin(LLRender::LINES); + { + gGL.color4f(0.f, 0.f, 0.f, 0.3f); + LLVector3 v12 = (v1 + v2) * .5f; + gGL.vertex3fv(v0.mV); + gGL.vertex3fv(v12.mV); + gGL.vertex3fv(v12.mV); + gGL.vertex3fv((v12 + (v0-v12)*.3f + (v2-v12)*.3f).mV); + gGL.vertex3fv(v12.mV); + gGL.vertex3fv((v12 + (v0-v12)*.3f + (v1-v12)*.3f).mV); + + LLVector3 v23 = (v2 + v3) * .5f; + gGL.vertex3fv(v0.mV); + gGL.vertex3fv(v23.mV); + gGL.vertex3fv(v23.mV); + gGL.vertex3fv((v23 + (v0-v23)*.3f + (v3-v23)*.3f).mV); + gGL.vertex3fv(v23.mV); + gGL.vertex3fv((v23 + (v0-v23)*.3f + (v2-v23)*.3f).mV); + } + gGL.end(); + LLUI::setLineWidth(1.0f); + + gGL.popMatrix(); + } + } + { + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + // Since we draw handles with depth testing off, we need to draw them in the + // proper depth order. + + // Copied from LLDrawable::updateGeometry + LLVector3 pos_agent = first_object->getPositionAgent(); + LLVector3 camera_agent = gAgentCamera.getCameraPositionAgent(); + LLVector3 headPos = pos_agent - camera_agent; + + LLVector3 orientWRTHead = headPos * invRotation; + + // Find nearest vertex + U32 nearest = (orientWRTHead.mV[0] < 0.0f ? 1 : 0) + + (orientWRTHead.mV[1] < 0.0f ? 2 : 0) + + (orientWRTHead.mV[2] < 0.0f ? 4 : 0); + + // opposite faces on Linden cubes: + // 0 & 5 + // 1 & 3 + // 2 & 4 + + // Table of order to draw faces, based on nearest vertex + static U32 face_list[8][NUM_AXES*2] = { + { 2,0,1, 4,5,3 }, // v6 F201 F453 + { 2,0,3, 4,5,1 }, // v7 F203 F451 + { 4,0,1, 2,5,3 }, // v5 F401 F253 + { 4,0,3, 2,5,1 }, // v4 F403 F251 + { 2,5,1, 4,0,3 }, // v2 F251 F403 + { 2,5,3, 4,0,1 }, // v3 F253 F401 + { 4,5,1, 2,0,3 }, // v1 F451 F203 + { 4,5,3, 2,0,1 }, // v0 F453 F201 + }; + static const EManipPart which_arrow[6] = { + LL_Z_ARROW, + LL_X_ARROW, + LL_Y_ARROW, + LL_X_ARROW, + LL_Y_ARROW, + LL_Z_ARROW}; + + // draw arrows for deeper faces first, closer faces last + LLVector3 camera_axis; + if (mObjectSelection->getSelectType() == SELECT_TYPE_HUD) + { + camera_axis = LLVector3::x_axis; + } + else + { + camera_axis.setVec(gAgentCamera.getCameraPositionAgent() - first_object->getPositionAgent()); + } + + for (U32 i = 0; i < NUM_AXES*2; i++) + { + U32 face = face_list[nearest][i]; + + LLVector3 arrow_axis; + getManipAxis(first_object, which_arrow[face], arrow_axis); + + renderArrow(which_arrow[face], + mManipPart, + (face >= 3) ? -mConeSize : mConeSize, + (face >= 3) ? -mArrowLengthMeters : mArrowLengthMeters, + mConeSize, + false); + } + } + } + gGL.popMatrix(); +} + + +void LLManipTranslate::renderArrow(S32 which_arrow, S32 selected_arrow, F32 box_size, F32 arrow_size, F32 handle_size, bool reverse_direction) +{ + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + LLGLEnable gls_blend(GL_BLEND); + + for (S32 pass = 1; pass <= 2; pass++) + { + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE, pass == 1 ? GL_LEQUAL : GL_GREATER); + gGL.pushMatrix(); + + S32 index = 0; + + index = ARROW_TO_AXIS[which_arrow]; + + // assign a color for this arrow + LLColor4 color; // black + if (which_arrow == selected_arrow || which_arrow == mHighlightedPart) + { + color.mV[index] = (pass == 1) ? 1.f : 0.5f; + } + else if (selected_arrow != LL_NO_PART) + { + color.mV[VALPHA] = 0.f; + } + else + { + color.mV[index] = pass == 1 ? .8f : .35f ; // red, green, or blue + color.mV[VALPHA] = 0.6f; + } + gGL.color4fv( color.mV ); + + LLVector3 vec; + + { + LLUI::setLineWidth(2.0f); + gGL.begin(LLRender::LINES); + vec.mV[index] = box_size; + gGL.vertex3f(vec.mV[0], vec.mV[1], vec.mV[2]); + + vec.mV[index] = arrow_size; + gGL.vertex3f(vec.mV[0], vec.mV[1], vec.mV[2]); + gGL.end(); + LLUI::setLineWidth(1.0f); + } + + gGL.translatef(vec.mV[0], vec.mV[1], vec.mV[2]); + gGL.scalef(handle_size, handle_size, handle_size); + + F32 rot = 0.0f; + LLVector3 axis; + + switch(which_arrow) + { + case LL_X_ARROW: + rot = reverse_direction ? -90.0f : 90.0f; + axis.mV[1] = 1.0f; + break; + case LL_Y_ARROW: + rot = reverse_direction ? 90.0f : -90.0f; + axis.mV[0] = 1.0f; + break; + case LL_Z_ARROW: + rot = reverse_direction ? 180.0f : 0.0f; + axis.mV[0] = 1.0f; + break; + default: + LL_ERRS() << "renderArrow called with bad arrow " << which_arrow << LL_ENDL; + break; + } + + gGL.diffuseColor4fv(color.mV); + gGL.rotatef(rot, axis.mV[0], axis.mV[1], axis.mV[2]); + gGL.scalef(mArrowScales.mV[index], mArrowScales.mV[index], mArrowScales.mV[index] * 1.5f); + + gCone.render(); + + gGL.popMatrix(); + } +} + +void LLManipTranslate::renderGridVert(F32 x_trans, F32 y_trans, F32 r, F32 g, F32 b, F32 alpha) +{ + gGL.color4f(r, g, b, alpha); + switch (mManipPart) + { + case LL_YZ_PLANE: + gGL.vertex3f(0, x_trans, y_trans); + break; + case LL_XZ_PLANE: + gGL.vertex3f(x_trans, 0, y_trans); + break; + case LL_XY_PLANE: + gGL.vertex3f(x_trans, y_trans, 0); + break; + default: + gGL.vertex3f(0,0,0); + break; + } + +} + +// virtual +bool LLManipTranslate::canAffectSelection() +{ + bool can_move = mObjectSelection->getObjectCount() != 0; + if (can_move) + { + struct f : public LLSelectedObjectFunctor + { + virtual bool apply(LLViewerObject* objectp) + { + LLViewerObject *root_object = (objectp == NULL) ? NULL : objectp->getRootEdit(); + return objectp->permMove() && !objectp->isPermanentEnforced() && + ((root_object == NULL) || !root_object->isPermanentEnforced()) && + (objectp->permModify() || !gSavedSettings.getBOOL("EditLinkedParts")); + } + } func; + can_move = mObjectSelection->applyToObjects(&func); + } + return can_move; +} diff --git a/indra/newview/llmaniptranslate.h b/indra/newview/llmaniptranslate.h index ba49549595..c0109db658 100644 --- a/indra/newview/llmaniptranslate.h +++ b/indra/newview/llmaniptranslate.h @@ -1,114 +1,114 @@ -/** - * @file llmaniptranslate.h - * @brief LLManipTranslate class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLMANIPTRANSLATE_H -#define LL_LLMANIPTRANSLATE_H - -#include "llmanip.h" -#include "lltimer.h" -#include "v4math.h" -#include "llquaternion.h" - -class LLManipTranslate : public LLManip -{ -public: - class ManipulatorHandle - { - public: - LLVector3 mStartPosition; - LLVector3 mEndPosition; - EManipPart mManipID; - F32 mHotSpotRadius; - - ManipulatorHandle(LLVector3 start_pos, LLVector3 end_pos, EManipPart id, F32 radius):mStartPosition(start_pos), mEndPosition(end_pos), mManipID(id), mHotSpotRadius(radius){} - }; - - - LLManipTranslate( LLToolComposite* composite ); - virtual ~LLManipTranslate(); - - static U32 getGridTexName() ; - static void destroyGL(); - static void restoreGL(); - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - virtual bool handleHover(S32 x, S32 y, MASK mask); - virtual void render(); - virtual void handleSelect(); - - virtual void highlightManipulators(S32 x, S32 y); - virtual bool handleMouseDownOnPart(S32 x, S32 y, MASK mask); - virtual bool canAffectSelection(); - -protected: - enum EHandleType { - HANDLE_CONE, - HANDLE_BOX, - HANDLE_SPHERE - }; - - void renderArrow(S32 which_arrow, S32 selected_arrow, F32 box_size, F32 arrow_size, F32 handle_size, bool reverse_direction); - void renderTranslationHandles(); - void renderText(); - void renderSnapGuides(); - void renderGrid(F32 x, F32 y, F32 size, F32 r, F32 g, F32 b, F32 a); - void renderGridVert(F32 x_trans, F32 y_trans, F32 r, F32 g, F32 b, F32 alpha); - void highlightIntersection(LLVector3 normal, - LLVector3 selection_center, - LLQuaternion grid_rotation, - LLColor4 inner_color); - F32 getMinGridScale(); - -private: - S32 mLastHoverMouseX; - S32 mLastHoverMouseY; - bool mMouseOutsideSlop; // true after mouse goes outside slop region - bool mCopyMadeThisDrag; - S32 mMouseDownX; - S32 mMouseDownY; - F32 mAxisArrowLength; // pixels - F32 mConeSize; // meters, world space - F32 mArrowLengthMeters; // meters - F32 mGridSizeMeters; - F32 mPlaneManipOffsetMeters; - LLVector3 mManipNormal; - LLVector3d mDragCursorStartGlobal; - LLVector3d mDragSelectionStartGlobal; - LLTimer mUpdateTimer; - LLVector4 mManipulatorVertices[18]; - F32 mSnapOffsetMeters; - LLVector3 mSnapOffsetAxis; - LLQuaternion mGridRotation; - LLVector3 mGridOrigin; - LLVector3 mGridScale; - F32 mSubdivisions; - bool mInSnapRegime; - LLVector3 mArrowScales; - LLVector3 mPlaneScales; - LLVector4 mPlaneManipPositions; -}; - -#endif +/** + * @file llmaniptranslate.h + * @brief LLManipTranslate class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLMANIPTRANSLATE_H +#define LL_LLMANIPTRANSLATE_H + +#include "llmanip.h" +#include "lltimer.h" +#include "v4math.h" +#include "llquaternion.h" + +class LLManipTranslate : public LLManip +{ +public: + class ManipulatorHandle + { + public: + LLVector3 mStartPosition; + LLVector3 mEndPosition; + EManipPart mManipID; + F32 mHotSpotRadius; + + ManipulatorHandle(LLVector3 start_pos, LLVector3 end_pos, EManipPart id, F32 radius):mStartPosition(start_pos), mEndPosition(end_pos), mManipID(id), mHotSpotRadius(radius){} + }; + + + LLManipTranslate( LLToolComposite* composite ); + virtual ~LLManipTranslate(); + + static U32 getGridTexName() ; + static void destroyGL(); + static void restoreGL(); + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + virtual bool handleHover(S32 x, S32 y, MASK mask); + virtual void render(); + virtual void handleSelect(); + + virtual void highlightManipulators(S32 x, S32 y); + virtual bool handleMouseDownOnPart(S32 x, S32 y, MASK mask); + virtual bool canAffectSelection(); + +protected: + enum EHandleType { + HANDLE_CONE, + HANDLE_BOX, + HANDLE_SPHERE + }; + + void renderArrow(S32 which_arrow, S32 selected_arrow, F32 box_size, F32 arrow_size, F32 handle_size, bool reverse_direction); + void renderTranslationHandles(); + void renderText(); + void renderSnapGuides(); + void renderGrid(F32 x, F32 y, F32 size, F32 r, F32 g, F32 b, F32 a); + void renderGridVert(F32 x_trans, F32 y_trans, F32 r, F32 g, F32 b, F32 alpha); + void highlightIntersection(LLVector3 normal, + LLVector3 selection_center, + LLQuaternion grid_rotation, + LLColor4 inner_color); + F32 getMinGridScale(); + +private: + S32 mLastHoverMouseX; + S32 mLastHoverMouseY; + bool mMouseOutsideSlop; // true after mouse goes outside slop region + bool mCopyMadeThisDrag; + S32 mMouseDownX; + S32 mMouseDownY; + F32 mAxisArrowLength; // pixels + F32 mConeSize; // meters, world space + F32 mArrowLengthMeters; // meters + F32 mGridSizeMeters; + F32 mPlaneManipOffsetMeters; + LLVector3 mManipNormal; + LLVector3d mDragCursorStartGlobal; + LLVector3d mDragSelectionStartGlobal; + LLTimer mUpdateTimer; + LLVector4 mManipulatorVertices[18]; + F32 mSnapOffsetMeters; + LLVector3 mSnapOffsetAxis; + LLQuaternion mGridRotation; + LLVector3 mGridOrigin; + LLVector3 mGridScale; + F32 mSubdivisions; + bool mInSnapRegime; + LLVector3 mArrowScales; + LLVector3 mPlaneScales; + LLVector4 mPlaneManipPositions; +}; + +#endif diff --git a/indra/newview/llmarketplacefunctions.cpp b/indra/newview/llmarketplacefunctions.cpp index db75bf84bb..7b8211ded8 100644 --- a/indra/newview/llmarketplacefunctions.cpp +++ b/indra/newview/llmarketplacefunctions.cpp @@ -1,1902 +1,1902 @@ -/** - * @file llmarketplacefunctions.cpp - * @brief Implementation of assorted functions related to the marketplace - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llmarketplacefunctions.h" - -#include "llagent.h" -#include "llbufferstream.h" -#include "llcallbacklist.h" -#include "llinventoryfunctions.h" -#include "llinventoryobserver.h" -#include "llnotificationsutil.h" -#include "llsdserialize.h" -#include "lltimer.h" -#include "lltrans.h" -#include "llviewercontrol.h" -#include "llviewerinventory.h" -#include "llviewermedia.h" -#include "llviewernetwork.h" -#include "llviewerregion.h" -#include "lleventcoro.h" -#include "llcoros.h" -#include "llcorehttputil.h" - -#include "llsdutil.h" -// -// Helpers -// - -namespace { - - static std::string getMarketplaceDomain() - { - std::string domain = "secondlife.com"; - - if (!LLGridManager::getInstance()->isInProductionGrid()) - { - const std::string& grid_id = LLGridManager::getInstance()->getGridId(); - const std::string& grid_id_lower = utf8str_tolower(grid_id); - - if (grid_id_lower == "damballah") - { - domain = "secondlife-staging.com"; - } - else - { - domain = llformat("%s.lindenlab.com", grid_id_lower.c_str()); - } - } - - return domain; - } - - static std::string getMarketplaceURL(const std::string& urlStringName) - { - LLStringUtil::format_map_t domain_arg; - domain_arg["[MARKETPLACE_DOMAIN_NAME]"] = getMarketplaceDomain(); - - std::string marketplace_url = LLTrans::getString(urlStringName, domain_arg); - - return marketplace_url; - } - - // Get the version folder: if there is only one subfolder, we will use it as a version folder - LLUUID getVersionFolderIfUnique(const LLUUID& folder_id) - { - LLUUID version_id = LLUUID::null; - LLInventoryModel::cat_array_t* categories; - LLInventoryModel::item_array_t* items; - gInventory.getDirectDescendentsOf(folder_id, categories, items); - if (categories->size() == 1) - { - version_id = categories->begin()->get()->getUUID(); - } - else - { - LLNotificationsUtil::add("AlertMerchantListingActivateRequired"); - } - return version_id; - } - - /////////////////////////////////////////////////////////////////////////////// - // SLM Reporters - void log_SLM_warning(const std::string& request, U32 status, const std::string& reason, const std::string& code, const LLSD& result) - { - - LL_WARNS("SLM") << "SLM API : Responder to " << request << ". status : " << status << ", reason : " << reason << ", code : " << code << ", description : " << ll_pretty_print_sd(result) << LL_ENDL; - if ((status == 422) && (result.has(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT) && - result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT].isArray() && - result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT].size() > 4)) - { - // Unprocessable Entity : Special case that error as it is a frequent answer when trying to list an incomplete listing - LLNotificationsUtil::add("MerchantUnprocessableEntity"); - } - else - { - // Prompt the user with the warning (so they know why things are failing) - LLSD subs; - // We do show long descriptions in the alert (unlikely to be readable). The description string will be in the log though. - std::string description; - if (result.has(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT)) - { - LLSD content = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT]; - if (content.isArray()) - { - for (LLSD::array_iterator it = content.beginArray(); it != content.endArray(); ++it) - { - if (!description.empty()) - description += "\n"; - description += (*it).asString(); - } - } - else - { - description = content.asString(); - } - } - else - { - description = result.asString(); - } - std::string reason_lc = reason; - LLStringUtil::toLower(reason_lc); - if (!description.empty() && reason_lc.find("unknown") != std::string::npos) - { - subs["[ERROR_REASON]"] = ""; - } - else - { - subs["[ERROR_REASON]"] = "'" + reason +"'\n"; - } - subs["[ERROR_DESCRIPTION]"] = description; - LLNotificationsUtil::add("MerchantTransactionFailed", subs); - } - - } - - void log_SLM_infos(const std::string& request, U32 status, const std::string& body) - { - if (gSavedSettings.getBOOL("MarketplaceListingsLogging")) - { - LL_INFOS("SLM") << "SLM API : Responder to " << request << ". status : " << status << ", body or description : " << body << LL_ENDL; - } - } - - void log_SLM_infos(const std::string& request, U32 status, const LLSD& body) - { - log_SLM_infos(request, status, std::string(ll_pretty_print_sd(body))); - } - -} - - -#if 1 -namespace LLMarketplaceImport -{ - // Basic interface for this namespace - - bool hasSessionCookie(); - bool inProgress(); - bool resultPending(); - S32 getResultStatus(); - const LLSD& getResults(); - - bool establishMarketplaceSessionCookie(); - bool pollStatus(); - bool triggerImport(); - - // Internal state variables - - static std::string sMarketplaceCookie = ""; - static LLSD sImportId = LLSD::emptyMap(); - static bool sImportInProgress = false; - static bool sImportPostPending = false; - static bool sImportGetPending = false; - static S32 sImportResultStatus = 0; - static LLSD sImportResults = LLSD::emptyMap(); - - // Responders - - void marketplacePostCoro(std::string url) - { - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("marketplacePostCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); - LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); - - httpOpts->setWantHeaders(true); - httpOpts->setFollowRedirects(true); - - httpHeaders->append(HTTP_OUT_HEADER_ACCEPT, "*/*"); - httpHeaders->append(HTTP_OUT_HEADER_CONNECTION, "Keep-Alive"); - httpHeaders->append(HTTP_OUT_HEADER_COOKIE, sMarketplaceCookie); - httpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_XML); - httpHeaders->append(HTTP_OUT_HEADER_USER_AGENT, LLViewerMedia::getInstance()->getCurrentUserAgent()); - - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, LLSD(), httpOpts, httpHeaders); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - S32 httpCode = status.getType(); - if ((httpCode == MarketplaceErrorCodes::IMPORT_REDIRECT) || - (httpCode == MarketplaceErrorCodes::IMPORT_AUTHENTICATION_ERROR) || - // MAINT-2301 : we determined we can safely ignore that error in that context - (httpCode == MarketplaceErrorCodes::IMPORT_JOB_TIMEOUT)) - { - if (gSavedSettings.getBOOL("InventoryOutboxLogging")) - { - LL_INFOS() << " SLM POST : Ignoring time out status and treating it as success" << LL_ENDL; - } - httpCode = MarketplaceErrorCodes::IMPORT_DONE; - } - - if (httpCode >= MarketplaceErrorCodes::IMPORT_BAD_REQUEST) - { - if (gSavedSettings.getBOOL("InventoryOutboxLogging")) - { - LL_INFOS() << " SLM POST clearing marketplace cookie due to client or server error" << LL_ENDL; - } - sMarketplaceCookie.clear(); - } - - sImportInProgress = (httpCode == MarketplaceErrorCodes::IMPORT_DONE); - sImportPostPending = false; - sImportResultStatus = httpCode; - - { - std::stringstream str; - LLSDSerialize::toPrettyXML(result, str); - - LL_INFOS() << "Full results:\n" << str.str() << "\n" << LL_ENDL; - } - - result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); - sImportId = result; - - } - - void marketplaceGetCoro(std::string url, bool buildHeaders) - { - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("marketplaceGetCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpHeaders::ptr_t httpHeaders; - LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); - - httpOpts->setWantHeaders(true); - httpOpts->setFollowRedirects(!sMarketplaceCookie.empty()); - - if (buildHeaders) - { - httpHeaders = LLCore::HttpHeaders::ptr_t(new LLCore::HttpHeaders); - - httpHeaders->append(HTTP_OUT_HEADER_ACCEPT, "*/*"); - httpHeaders->append(HTTP_OUT_HEADER_COOKIE, sMarketplaceCookie); - httpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_LLSD_XML); - httpHeaders->append(HTTP_OUT_HEADER_USER_AGENT, LLViewerMedia::getInstance()->getCurrentUserAgent()); - } - else - { - httpHeaders = LLViewerMedia::getInstance()->getHttpHeaders(); - } - - LLSD result = httpAdapter->getAndSuspend(httpRequest, url, httpOpts, httpHeaders); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - LLSD resultHeaders = httpResults[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS]; - - if (sMarketplaceCookie.empty() && resultHeaders.has(HTTP_IN_HEADER_SET_COOKIE)) - { - sMarketplaceCookie = resultHeaders[HTTP_IN_HEADER_SET_COOKIE].asString(); - } - - // MAINT-2452 : Do not clear the cookie on IMPORT_DONE_WITH_ERRORS : Happens when trying to import objects with wrong permissions - // ACME-1221 : Do not clear the cookie on IMPORT_NOT_FOUND : Happens for newly created Merchant accounts that are initially empty - S32 httpCode = status.getType(); - if ((httpCode >= MarketplaceErrorCodes::IMPORT_BAD_REQUEST) && - (httpCode != MarketplaceErrorCodes::IMPORT_DONE_WITH_ERRORS) && - (httpCode != MarketplaceErrorCodes::IMPORT_NOT_FOUND)) - { - if (gSavedSettings.getBOOL("InventoryOutboxLogging")) - { - LL_INFOS() << " SLM GET clearing marketplace cookie due to client or server error" << LL_ENDL; - } - sMarketplaceCookie.clear(); - } - else if (gSavedSettings.getBOOL("InventoryOutboxLogging") && (httpCode >= MarketplaceErrorCodes::IMPORT_BAD_REQUEST)) - { - LL_INFOS() << " SLM GET : Got error status = " << httpCode << ", but marketplace cookie not cleared." << LL_ENDL; - } - - sImportInProgress = (httpCode == MarketplaceErrorCodes::IMPORT_PROCESSING); - sImportGetPending = false; - sImportResultStatus = httpCode; - - result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); - sImportResults = result; - - - } - - // Basic API - - bool hasSessionCookie() - { - return !sMarketplaceCookie.empty(); - } - - bool inProgress() - { - return sImportInProgress; - } - - bool resultPending() - { - return (sImportPostPending || sImportGetPending); - } - - S32 getResultStatus() - { - return sImportResultStatus; - } - - const LLSD& getResults() - { - return sImportResults; - } - - static std::string getInventoryImportURL() - { - std::string url = getMarketplaceURL("MarketplaceURL"); - - url += "api/1/"; - url += gAgent.getID().getString(); - url += "/inventory/import/"; - - return url; - } - - bool establishMarketplaceSessionCookie() - { - if (hasSessionCookie()) - { - return false; - } - - sImportInProgress = true; - sImportGetPending = true; - - std::string url = getInventoryImportURL(); - - LLCoros::instance().launch("marketplaceGetCoro", - boost::bind(&marketplaceGetCoro, url, false)); - - return true; - } - - bool pollStatus() - { - if (!hasSessionCookie()) - { - return false; - } - - sImportGetPending = true; - - std::string url = getInventoryImportURL(); - - url += sImportId.asString(); - - LLCoros::instance().launch("marketplaceGetCoro", - boost::bind(&marketplaceGetCoro, url, true)); - - return true; - } - - bool triggerImport() - { - if (!hasSessionCookie()) - { - return false; - } - - sImportId = LLSD::emptyMap(); - sImportInProgress = true; - sImportPostPending = true; - sImportResultStatus = MarketplaceErrorCodes::IMPORT_PROCESSING; - sImportResults = LLSD::emptyMap(); - - std::string url = getInventoryImportURL(); - - LLCoros::instance().launch("marketplacePostCoro", - boost::bind(&marketplacePostCoro, url)); - - return true; - } -} -#endif - -// -// Interface class -// -static const F32 MARKET_IMPORTER_UPDATE_FREQUENCY = 1.0f; - -//static -void LLMarketplaceInventoryImporter::update() -{ - if (instanceExists()) - { - static LLTimer update_timer; - if (update_timer.hasExpired()) - { - LLMarketplaceInventoryImporter::instance().updateImport(); - update_timer.setTimerExpirySec(MARKET_IMPORTER_UPDATE_FREQUENCY); - } - } -} - -LLMarketplaceInventoryImporter::LLMarketplaceInventoryImporter() - : mAutoTriggerImport(false) - , mImportInProgress(false) - , mInitialized(false) - , mMarketPlaceStatus(MarketplaceStatusCodes::MARKET_PLACE_NOT_INITIALIZED) - , mErrorInitSignal(NULL) - , mStatusChangedSignal(NULL) - , mStatusReportSignal(NULL) -{ -} - -boost::signals2::connection LLMarketplaceInventoryImporter::setInitializationErrorCallback(const status_report_signal_t::slot_type& cb) -{ - if (mErrorInitSignal == NULL) - { - mErrorInitSignal = new status_report_signal_t(); - } - - return mErrorInitSignal->connect(cb); -} - -boost::signals2::connection LLMarketplaceInventoryImporter::setStatusChangedCallback(const status_changed_signal_t::slot_type& cb) -{ - if (mStatusChangedSignal == NULL) - { - mStatusChangedSignal = new status_changed_signal_t(); - } - - return mStatusChangedSignal->connect(cb); -} - -boost::signals2::connection LLMarketplaceInventoryImporter::setStatusReportCallback(const status_report_signal_t::slot_type& cb) -{ - if (mStatusReportSignal == NULL) - { - mStatusReportSignal = new status_report_signal_t(); - } - - return mStatusReportSignal->connect(cb); -} - -void LLMarketplaceInventoryImporter::initialize() -{ - if (mInitialized) - { - return; - } - - if (!LLMarketplaceImport::hasSessionCookie()) - { - mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_INITIALIZING; - LLMarketplaceImport::establishMarketplaceSessionCookie(); - } - else - { - mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_MERCHANT; - } -} - -void LLMarketplaceInventoryImporter::reinitializeAndTriggerImport() -{ - mInitialized = false; - mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_NOT_INITIALIZED; - initialize(); - mAutoTriggerImport = true; -} - -bool LLMarketplaceInventoryImporter::triggerImport() -{ - const bool import_triggered = LLMarketplaceImport::triggerImport(); - - if (!import_triggered) - { - reinitializeAndTriggerImport(); - } - - return import_triggered; -} - -void LLMarketplaceInventoryImporter::updateImport() -{ - const bool in_progress = LLMarketplaceImport::inProgress(); - - if (in_progress && !LLMarketplaceImport::resultPending()) - { - const bool polling_status = LLMarketplaceImport::pollStatus(); - - if (!polling_status) - { - reinitializeAndTriggerImport(); - } - } - - if (mImportInProgress != in_progress) - { - mImportInProgress = in_progress; - - // If we are no longer in progress - if (!mImportInProgress) - { - // Look for results success - mInitialized = LLMarketplaceImport::hasSessionCookie(); - - // Report results - if (mStatusReportSignal) - { - (*mStatusReportSignal)(LLMarketplaceImport::getResultStatus(), LLMarketplaceImport::getResults()); - } - - if (mInitialized) - { - mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_MERCHANT; - // Follow up with auto trigger of import - if (mAutoTriggerImport) - { - mAutoTriggerImport = false; - mImportInProgress = triggerImport(); - } - } - else - { - U32 status = LLMarketplaceImport::getResultStatus(); - if ((status == MarketplaceErrorCodes::IMPORT_FORBIDDEN) || - (status == MarketplaceErrorCodes::IMPORT_AUTHENTICATION_ERROR)) - { - mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_NOT_MERCHANT; - } - else if (status == MarketplaceErrorCodes::IMPORT_SERVER_API_DISABLED) - { - mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_MIGRATED_MERCHANT; - } - else - { - mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE; - } - if (mErrorInitSignal && (mMarketPlaceStatus == MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE)) - { - (*mErrorInitSignal)(LLMarketplaceImport::getResultStatus(), LLMarketplaceImport::getResults()); - } - } - } - } - - // Make sure we trigger the status change with the final state (in case of auto trigger after initialize) - if (mStatusChangedSignal) - { - (*mStatusChangedSignal)(mImportInProgress); - } -} - -// -// Direct Delivery : Marketplace tuples and data -// -class LLMarketplaceInventoryObserver : public LLInventoryObserver -{ -public: - LLMarketplaceInventoryObserver() {} - virtual ~LLMarketplaceInventoryObserver() {} - virtual void changed(U32 mask); - -private: - static void onIdleProcessQueue(void *userdata); - - // doesn't hold just marketplace related ids - static std::set sStructureQueue; - static bool sProcessingQueue; -}; - -std::set LLMarketplaceInventoryObserver::sStructureQueue; -bool LLMarketplaceInventoryObserver::sProcessingQueue = false; - -void LLMarketplaceInventoryObserver::changed(U32 mask) -{ - if (mask & (LLInventoryObserver::INTERNAL | LLInventoryObserver::STRUCTURE)) - { - // When things are changed in the inventory, this can trigger a host of changes in the marketplace listings folder: - // * stock counts changing : no copy items coming in and out will change the stock count on folders - // * version and listing folders : moving those might invalidate the marketplace data itself - // Since we should cannot raise inventory change while the observer is called (the list will be cleared - // once observers are called) we need to raise a flag in the inventory to signal that things have been dirtied. - const std::set& changed_items = gInventory.getChangedIDs(); - sStructureQueue.insert(changed_items.begin(), changed_items.end()); - } - - if (!sProcessingQueue && !sStructureQueue.empty()) - { - gIdleCallbacks.addFunction(onIdleProcessQueue, NULL); - // can do without sProcessingQueue, but it's usufull for simplicity and reliability - sProcessingQueue = true; - } -} - -void LLMarketplaceInventoryObserver::onIdleProcessQueue(void *userdata) -{ - U64 start_time = LLTimer::getTotalTime(); // microseconds - const U64 MAX_PROCESSING_TIME = 1000; - U64 stop_time = start_time + MAX_PROCESSING_TIME; - - while (!sStructureQueue.empty() && LLTimer::getTotalTime() < stop_time) - { - std::set::const_iterator id_it = sStructureQueue.begin(); - LLInventoryObject* obj = gInventory.getObject(*id_it); - if (obj) - { - if (LLAssetType::AT_CATEGORY == obj->getType()) - { - // If it's a folder known to the marketplace, let's check it's in proper shape - if (LLMarketplaceData::instance().isListed(*id_it) || LLMarketplaceData::instance().isVersionFolder(*id_it)) - { - // can trigger notifyObservers - // can cause more structural changes - LLMarketplaceValidator::getInstance()->validateMarketplaceListings(obj->getUUID()); - } - } - else - { - // If it's not a category, it's an item... - LLInventoryItem* item = (LLInventoryItem*)(obj); - // If it's a no copy item, we may need to update the label count of marketplace listings - if (!item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) - { - LLMarketplaceData::instance().setDirtyCount(); - } - } - } - - // sStructureQueue could have been modified in validate_marketplacelistings - // adding items does not invalidate existing iterator - sStructureQueue.erase(id_it); - } - - if (LLApp::isExiting() || sStructureQueue.empty()) - { - // Nothing to do anymore - gIdleCallbacks.deleteFunction(onIdleProcessQueue, NULL); - sProcessingQueue = false; - } -} - -// Tuple == Item -LLMarketplaceTuple::LLMarketplaceTuple() : - mListingFolderId(), - mListingId(0), - mVersionFolderId(), - mIsActive(false), - mEditURL("") -{ -} - -LLMarketplaceTuple::LLMarketplaceTuple(const LLUUID& folder_id) : - mListingFolderId(folder_id), - mListingId(0), - mVersionFolderId(), - mIsActive(false), - mEditURL("") -{ -} - -LLMarketplaceTuple::LLMarketplaceTuple(const LLUUID& folder_id, S32 listing_id, const LLUUID& version_id, bool is_listed) : - mListingFolderId(folder_id), - mListingId(listing_id), - mVersionFolderId(version_id), - mIsActive(is_listed), - mEditURL("") -{ -} - - -// Data map -LLMarketplaceData::LLMarketplaceData() : - mMarketPlaceStatus(MarketplaceStatusCodes::MARKET_PLACE_NOT_INITIALIZED), - mMarketPlaceDataFetched(MarketplaceFetchCodes::MARKET_FETCH_NOT_DONE), - mStatusUpdatedSignal(NULL), - mDataFetchedSignal(NULL), - mDirtyCount(false) -{ - mInventoryObserver = new LLMarketplaceInventoryObserver; - gInventory.addObserver(mInventoryObserver); -} - -LLMarketplaceData::~LLMarketplaceData() -{ - gInventory.removeObserver(mInventoryObserver); -} - - -LLSD LLMarketplaceData::getMarketplaceStringSubstitutions() -{ - std::string marketplace_url = getMarketplaceURL("MarketplaceURL"); - std::string marketplace_url_create = getMarketplaceURL("MarketplaceURL_CreateStore"); - std::string marketplace_url_dashboard = getMarketplaceURL("MarketplaceURL_Dashboard"); - std::string marketplace_url_imports = getMarketplaceURL("MarketplaceURL_Imports"); - std::string marketplace_url_info = getMarketplaceURL("MarketplaceURL_LearnMore"); - - LLSD marketplace_sub_map; - - marketplace_sub_map["[MARKETPLACE_URL]"] = marketplace_url; - marketplace_sub_map["[MARKETPLACE_CREATE_STORE_URL]"] = marketplace_url_create; - marketplace_sub_map["[MARKETPLACE_LEARN_MORE_URL]"] = marketplace_url_info; - marketplace_sub_map["[MARKETPLACE_DASHBOARD_URL]"] = marketplace_url_dashboard; - marketplace_sub_map["[MARKETPLACE_IMPORTS_URL]"] = marketplace_url_imports; - - return marketplace_sub_map; -} - -void LLMarketplaceData::initializeSLM(const status_updated_signal_t::slot_type& cb) -{ - if (mStatusUpdatedSignal == NULL) - { - mStatusUpdatedSignal = new status_updated_signal_t(); - } - mStatusUpdatedSignal->connect(cb); - - if (mMarketPlaceStatus != MarketplaceStatusCodes::MARKET_PLACE_NOT_INITIALIZED) - { - // If already initialized, just confirm the status so the callback gets called - if (mMarketPlaceFailureReason.empty()) - { - setSLMStatus(mMarketPlaceStatus); - } - else - { - setSLMConnectionFailure(mMarketPlaceFailureReason); - } - } - else - { - mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_INITIALIZING; - - LLCoros::instance().launch("getMerchantStatus", - boost::bind(&LLMarketplaceData::getMerchantStatusCoro, this)); - } -} - -void LLMarketplaceData::getMerchantStatusCoro() -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); - - httpOpts->setFollowRedirects(true); - - std::string url = getSLMConnectURL("/merchant"); - if (url.empty()) - { - LL_WARNS("Marketplace") << "No marketplace capability on Sim" << LL_ENDL; - setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE); - return; - } - - LLSD result = httpAdapter->getAndSuspend(httpRequest, url, httpOpts); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - S32 httpCode = status.getType(); - - if (httpCode == HTTP_NOT_FOUND) - { - log_SLM_infos("Get /merchant", httpCode, std::string("User is not a merchant")); - LLMarketplaceData::instance().setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_NOT_MERCHANT); - } - else if (httpCode == HTTP_SERVICE_UNAVAILABLE) - { - log_SLM_infos("Get /merchant", httpCode, std::string("Merchant is not migrated")); - LLMarketplaceData::instance().setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_NOT_MIGRATED_MERCHANT); - } - else - { - LL_WARNS("SLM") << "SLM Merchant Request failed with status: " << httpCode - << ", reason : " << status.toString() - << ", code : " << result["error_code"].asString() - << ", description : " << result["error_description"].asString() << LL_ENDL; - std::string reason = status.toString(); - if (reason.empty()) - { - reason = result["error_code"].asString(); - } - // Since user might not even have a marketplace, there is no reason to report the error - // to the user, instead write it down into listings' floater - LLMarketplaceData::instance().setSLMConnectionFailure(reason); - } - return; - } - - log_SLM_infos("Get /merchant", status.getType(), std::string("User is a merchant")); - setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_MERCHANT); -} - -void LLMarketplaceData::setDataFetchedSignal(const status_updated_signal_t::slot_type& cb) -{ - if (mDataFetchedSignal == NULL) - { - mDataFetchedSignal = new status_updated_signal_t(); - } - mDataFetchedSignal->connect(cb); -} - -// Get/Post/Put requests to the SLM Server using the SLM API -void LLMarketplaceData::getSLMListings() -{ - const LLUUID marketplaceFolderId = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - setUpdating(marketplaceFolderId, true); - - LLCoros::instance().launch("getSLMListings", - boost::bind(&LLMarketplaceData::getSLMListingsCoro, this, marketplaceFolderId)); -} - -void LLMarketplaceData::getSLMListingsCoro(LLUUID folderId) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); - - httpHeaders->append("Accept", "application/json"); - httpHeaders->append("Content-Type", "application/json"); - - std::string url = getSLMConnectURL("/listings"); - - LLSD result = httpAdapter->getJsonAndSuspend(httpRequest, url, httpHeaders); - - setUpdating(folderId, false); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - log_SLM_warning("Get /listings", status.getType(), status.toString(), "", result); - setSLMDataFetched(MarketplaceFetchCodes::MARKET_FETCH_FAILED); - update_marketplace_category(folderId, false); - gInventory.notifyObservers(); - return; - } - - log_SLM_infos("Get /listings", static_cast(status.getType()), result); - - // Extract the info from the results - for (LLSD::array_iterator it = result["listings"].beginArray(); - it != result["listings"].endArray(); ++it) - { - LLSD listing = *it; - - int listingId = listing["id"].asInteger(); - bool isListed = listing["is_listed"].asBoolean(); - std::string editUrl = listing["edit_url"].asString(); - LLUUID folderUuid = listing["inventory_info"]["listing_folder_id"].asUUID(); - LLUUID versionUuid = listing["inventory_info"]["version_folder_id"].asUUID(); - int count = listing["inventory_info"]["count_on_hand"].asInteger(); - - if (folderUuid.notNull()) - { - addListing(folderUuid, listingId, versionUuid, isListed, editUrl, count); - } - } - - // Update all folders under the root - setSLMDataFetched(MarketplaceFetchCodes::MARKET_FETCH_DONE); - update_marketplace_category(folderId, false); - gInventory.notifyObservers(); -} - -void LLMarketplaceData::getSLMListing(S32 listingId) -{ - LLUUID folderId = getListingFolder(listingId); - setUpdating(folderId, true); - - LLCoros::instance().launch("getSingleListingCoro", - boost::bind(&LLMarketplaceData::getSingleListingCoro, this, listingId, folderId)); -} - -void LLMarketplaceData::getSingleListingCoro(S32 listingId, LLUUID folderId) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); - - httpHeaders->append("Accept", "application/json"); - httpHeaders->append("Content-Type", "application/json"); - - std::string url = getSLMConnectURL("/listing/") + llformat("%d", listingId); - - LLSD result = httpAdapter->getJsonAndSuspend(httpRequest, url, httpHeaders); - - setUpdating(folderId, false); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - if (status.getType() == HTTP_NOT_FOUND) - { - // That listing does not exist -> delete its record from the local SLM data store - deleteListing(folderId, false); - } - else - { - log_SLM_warning("Get /listing", status.getType(), status.toString(), "", result); - } - - update_marketplace_category(folderId, false); - gInventory.notifyObservers(); - return; - } - - log_SLM_infos("Get /listings", static_cast(status.getType()), result); - - - // Extract the info from the results - for (LLSD::array_iterator it = result["listings"].beginArray(); - it != result["listings"].endArray(); ++it) - { - LLSD listing = *it; - - int resListingId = listing["id"].asInteger(); - bool isListed = listing["is_listed"].asBoolean(); - std::string editUrl = listing["edit_url"].asString(); - LLUUID folderUuid = listing["inventory_info"]["listing_folder_id"].asUUID(); - LLUUID versionUuid = listing["inventory_info"]["version_folder_id"].asUUID(); - int count = listing["inventory_info"]["count_on_hand"].asInteger(); - - // Update that listing - setListingID(folderUuid, resListingId, false); - setVersionFolderID(folderUuid, versionUuid, false); - setActivationState(folderUuid, isListed, false); - setListingURL(folderUuid, editUrl, false); - setCountOnHand(folderUuid, count, false); - update_marketplace_category(folderUuid, false); - gInventory.notifyObservers(); - } -} - -void LLMarketplaceData::createSLMListing(const LLUUID& folder_id, const LLUUID& version_id, S32 count) -{ - setUpdating(folder_id, true); - LLCoros::instance().launch("createSLMListingCoro", - boost::bind(&LLMarketplaceData::createSLMListingCoro, this, folder_id, version_id, count)); -} - -void LLMarketplaceData::createSLMListingCoro(LLUUID folderId, LLUUID versionId, S32 count) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); - - httpHeaders->append("Accept", "application/json"); - httpHeaders->append("Content-Type", "application/json"); - - LLViewerInventoryCategory* category = gInventory.getCategory(folderId); - LLSD invInfo; - invInfo["listing_folder_id"] = folderId; - invInfo["version_folder_id"] = versionId; - invInfo["count_on_hand"] = count; - LLSD listing; - listing["name"] = category->getName(); - listing["inventory_info"] = invInfo; - LLSD postData; - postData["listing"] = listing; - - std::string url = getSLMConnectURL("/listings"); - - LLSD result = httpAdapter->postJsonAndSuspend(httpRequest, url, postData, httpHeaders); - - setUpdating(folderId, false); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - log_SLM_warning("Post /listings", status.getType(), status.toString(), "", result); - update_marketplace_category(folderId, false); - gInventory.notifyObservers(); - return; - } - - log_SLM_infos("Post /listings", status.getType(), result); - - if (!result.has("listings") || !result["listings"].isArray() || result["listings"].size() == 0) - { - LL_INFOS("SLM") << "Received an empty response for folder " << folderId << LL_ENDL; - return; - } - - // Extract the info from the results - for (LLSD::array_iterator it = result["listings"].beginArray(); - it != result["listings"].endArray(); ++it) - { - LLSD listing = *it; - - int listingId = listing["id"].asInteger(); - bool isListed = listing["is_listed"].asBoolean(); - std::string editUrl = listing["edit_url"].asString(); - LLUUID folderUuid = listing["inventory_info"]["listing_folder_id"].asUUID(); - LLUUID versionUuid = listing["inventory_info"]["version_folder_id"].asUUID(); - int count = listing["inventory_info"]["count_on_hand"].asInteger(); - - addListing(folderUuid, listingId, versionUuid, isListed, editUrl, count); - update_marketplace_category(folderUuid, false); - gInventory.notifyObservers(); - } - -} - -void LLMarketplaceData::updateSLMListing(const LLUUID& folder_id, S32 listing_id, const LLUUID& version_id, bool is_listed, S32 count) -{ - setUpdating(folder_id, true); - LLCoros::instance().launch("updateSLMListingCoro", - boost::bind(&LLMarketplaceData::updateSLMListingCoro, this, folder_id, listing_id, version_id, is_listed, count)); -} - -void LLMarketplaceData::updateSLMListingCoro(LLUUID folderId, S32 listingId, LLUUID versionId, bool isListed, S32 count) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); - - httpHeaders->append("Accept", "application/json"); - httpHeaders->append("Content-Type", "application/json"); - - LLSD invInfo; - invInfo["listing_folder_id"] = folderId; - invInfo["version_folder_id"] = versionId; - invInfo["count_on_hand"] = count; - LLSD listing; - listing["inventory_info"] = invInfo; - listing["id"] = listingId; - listing["is_listed"] = isListed; - LLSD postData; - postData["listing"] = listing; - - std::string url = getSLMConnectURL("/listing/") + llformat("%d", listingId); - LLSD result = httpAdapter->putJsonAndSuspend(httpRequest, url, postData, httpHeaders); - - setUpdating(folderId, false); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - log_SLM_warning("Put /listing", status.getType(), status.toString(), "", result); - update_marketplace_category(folderId, false); - gInventory.notifyObservers(); - return; - } - - log_SLM_infos("Put /listing", status.getType(), result); - - if (!result.has("listings") || !result["listings"].isArray() || result["listings"].size() == 0) - { - LL_INFOS("SLM") << "Received an empty response for listing " << listingId << " folder " << folderId << LL_ENDL; - // Try to get listing more directly after a delay - const float FORCE_UPDATE_TIMEOUT = 5.0; - llcoro::suspendUntilTimeout(FORCE_UPDATE_TIMEOUT); - if (!LLApp::isExiting() && LLMarketplaceData::instanceExists()) - { - getSLMListing(listingId); - } - return; - } - - // Extract the info from the Json string - for (LLSD::array_iterator it = result["listings"].beginArray(); - it != result["listings"].endArray(); ++it) - { - LLSD listing = *it; - - int listing_id = listing["id"].asInteger(); - bool is_listed = listing["is_listed"].asBoolean(); - std::string edit_url = listing["edit_url"].asString(); - LLUUID folderUuid = listing["inventory_info"]["listing_folder_id"].asUUID(); - LLUUID versionUuid = listing["inventory_info"]["version_folder_id"].asUUID(); - int onHand = listing["inventory_info"]["count_on_hand"].asInteger(); - - // Update that listing - setListingID(folderUuid, listing_id, false); - setVersionFolderID(folderUuid, versionUuid, false); - setActivationState(folderUuid, is_listed, false); - setListingURL(folderUuid, edit_url, false); - setCountOnHand(folderUuid, onHand, false); - update_marketplace_category(folderUuid, false); - gInventory.notifyObservers(); - - // Show a notification alert if what we got is not what we expected - // (this actually doesn't result in an error status from the SLM API protocol) - if ((isListed != is_listed) || (versionId != versionUuid)) - { - LLSD subs; - subs["[URL]"] = edit_url; - LLNotificationsUtil::add("AlertMerchantListingNotUpdated", subs); - } - } - -} - -void LLMarketplaceData::associateSLMListing(const LLUUID& folder_id, S32 listing_id, const LLUUID& version_id, const LLUUID& source_folder_id) -{ - setUpdating(folder_id, true); - setUpdating(source_folder_id, true); - LLCoros::instance().launch("associateSLMListingCoro", - boost::bind(&LLMarketplaceData::associateSLMListingCoro, this, folder_id, listing_id, version_id, source_folder_id)); -} - -void LLMarketplaceData::associateSLMListingCoro(LLUUID folderId, S32 listingId, LLUUID versionId, LLUUID sourceFolderId) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); - - httpHeaders->append("Accept", "application/json"); - httpHeaders->append("Content-Type", "application/json"); - - LLSD invInfo; - invInfo["listing_folder_id"] = folderId; - invInfo["version_folder_id"] = versionId; - LLSD listing; - listing["id"] = listingId; - listing["inventory_info"] = invInfo; - LLSD postData; - postData["listing"] = listing; - - // Send request - std::string url = getSLMConnectURL("/associate_inventory/") + llformat("%d", listingId); - - LLSD result = httpAdapter->putJsonAndSuspend(httpRequest, url, postData, httpHeaders); - - setUpdating(folderId, false); - setUpdating(sourceFolderId, false); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - log_SLM_warning("Put /associate_inventory", status.getType(), status.toString(), "", result); - update_marketplace_category(folderId, false); - update_marketplace_category(sourceFolderId, false); - gInventory.notifyObservers(); - return; - } - - log_SLM_infos("Put /associate_inventory", status.getType(), result); - - for (LLSD::array_iterator it = result["listings"].beginArray(); - it != result["listings"].endArray(); ++it) - { - LLSD listing = *it; - - int listing_id = listing["id"].asInteger(); - bool is_listed = listing["is_listed"].asBoolean(); - std::string edit_url = listing["edit_url"].asString(); - LLUUID folder_uuid = listing["inventory_info"]["listing_folder_id"].asUUID(); - LLUUID version_uuid = listing["inventory_info"]["version_folder_id"].asUUID(); - int count = listing["inventory_info"]["count_on_hand"].asInteger(); - - // Check that the listing ID is not already associated to some other record - LLUUID old_listing = LLMarketplaceData::instance().getListingFolder(listing_id); - if (old_listing.notNull()) - { - // If it is already used, unlist the old record (we can't have 2 listings with the same listing ID) - deleteListing(old_listing); - } - - // Add the new association - addListing(folder_uuid, listing_id, version_uuid, is_listed, edit_url, count); - update_marketplace_category(folder_uuid, false); - gInventory.notifyObservers(); - - // The stock count needs to be updated with the new local count now - updateCountOnHand(folder_uuid, 1); - } - - // Always update the source folder so its widget updates - update_marketplace_category(sourceFolderId, false); -} - -void LLMarketplaceData::deleteSLMListing(S32 listingId) -{ - LLCoros::instance().launch("deleteSLMListingCoro", - boost::bind(&LLMarketplaceData::deleteSLMListingCoro, this, listingId)); -} - -void LLMarketplaceData::deleteSLMListingCoro(S32 listingId) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); - - httpHeaders->append("Accept", "application/json"); - httpHeaders->append("Content-Type", "application/json"); - - std::string url = getSLMConnectURL("/listing/") + llformat("%d", listingId); - LLUUID folderId = getListingFolder(listingId); - - setUpdating(folderId, true); - - LLSD result = httpAdapter->deleteJsonAndSuspend(httpRequest, url, httpHeaders); - - setUpdating(folderId, false); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - log_SLM_warning("Delete /listing", status.getType(), status.toString(), "", result); - update_marketplace_category(folderId, false); - gInventory.notifyObservers(); - return; - } - - log_SLM_infos("Delete /listing", status.getType(), result); - - for (LLSD::array_iterator it = result["listings"].beginArray(); - it != result["listings"].endArray(); ++it) - { - LLSD listing = *it; - - int listing_id = listing["id"].asInteger(); - LLUUID folder_id = LLMarketplaceData::instance().getListingFolder(listing_id); - deleteListing(folder_id); - } - -} - -std::string LLMarketplaceData::getSLMConnectURL(const std::string& route) -{ - std::string url; - LLViewerRegion *regionp = gAgent.getRegion(); - if (regionp) - { - // Get DirectDelivery cap - url = regionp->getCapability("DirectDelivery"); - if (!url.empty()) - { - url += route; - } - } - return url; -} - -void LLMarketplaceData::setSLMStatus(U32 status) -{ - mMarketPlaceStatus = status; - mMarketPlaceFailureReason.clear(); - if (mStatusUpdatedSignal) - { - (*mStatusUpdatedSignal)(); - } -} - -void LLMarketplaceData::setSLMConnectionFailure(const std::string& reason) -{ - mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE; - mMarketPlaceFailureReason = reason; - if (mStatusUpdatedSignal) - { - (*mStatusUpdatedSignal)(); - } -} - -void LLMarketplaceData::setSLMDataFetched(U32 status) -{ - mMarketPlaceDataFetched = status; - if (mDataFetchedSignal) - { - (*mDataFetchedSignal)(); - } -} - -bool LLMarketplaceData::isSLMDataFetched() -{ - return mMarketPlaceDataFetched == MarketplaceFetchCodes::MARKET_FETCH_DONE; -} - -// Creation / Deletion / Update -// Methods publicly called -bool LLMarketplaceData::createListing(const LLUUID& folder_id) -{ - if (isListed(folder_id)) - { - // Listing already exists -> exit with error - return false; - } - - // Get the version folder: if there is only one subfolder, we will set it as a version folder immediately - S32 count = -1; - LLUUID version_id = getVersionFolderIfUnique(folder_id); - if (version_id.notNull()) - { - count = compute_stock_count(version_id, true); - } - - // Validate the count on hand - if (count == COMPUTE_STOCK_NOT_EVALUATED) - { - // If the count on hand cannot be evaluated, we will consider it empty (out of stock) at creation time - // It will get reevaluated and updated once the items are fetched - count = 0; - } - - // Post the listing creation request to SLM - createSLMListing(folder_id, version_id, count); - - return true; -} - -bool LLMarketplaceData::clearListing(const LLUUID& folder_id, S32 depth) -{ - if (folder_id.isNull()) - { - // Folder doesn't exists -> exit with error - return false; - } - - // Evaluate the depth if it wasn't passed as a parameter - if (depth < 0) - { - depth = depth_nesting_in_marketplace(folder_id); - - } - // Folder id can be the root of the listing or not so we need to retrieve the root first - LLUUID listing_uuid = (isListed(folder_id) ? folder_id : nested_parent_id(folder_id, depth)); - S32 listing_id = getListingID(listing_uuid); - - if (listing_id == 0) - { - // Listing doesn't exists -> exit with error - return false; - } - - // Update the SLM Server so that this listing is deleted (actually, archived...) - deleteSLMListing(listing_id); - - return true; -} - -bool LLMarketplaceData::getListing(const LLUUID& folder_id, S32 depth) -{ - if (folder_id.isNull()) - { - // Folder doesn't exists -> exit with error - return false; - } - - // Evaluate the depth if it wasn't passed as a parameter - if (depth < 0) - { - depth = depth_nesting_in_marketplace(folder_id); - - } - // Folder id can be the root of the listing or not so we need to retrieve the root first - LLUUID listing_uuid = (isListed(folder_id) ? folder_id : nested_parent_id(folder_id, depth)); - S32 listing_id = getListingID(listing_uuid); - - if (listing_id == 0) - { - // Listing doesn't exists -> exit with error - return false; - } - - // Get listing data from SLM - getSLMListing(listing_id); - - return true; -} - -bool LLMarketplaceData::getListing(S32 listing_id) -{ - if (listing_id == 0) - { - return false; - } - - // Get listing data from SLM - getSLMListing(listing_id); - return true; -} - -bool LLMarketplaceData::activateListing(const LLUUID& folder_id, bool activate, S32 depth) -{ - // Evaluate the depth if it wasn't passed as a parameter - if (depth < 0) - { - depth = depth_nesting_in_marketplace(folder_id); - - } - // Folder id can be the root of the listing or not so we need to retrieve the root first - LLUUID listing_uuid = nested_parent_id(folder_id, depth); - S32 listing_id = getListingID(listing_uuid); - if (listing_id == 0) - { - // Listing doesn't exists -> exit with error - return false; - } - - if (getActivationState(listing_uuid) == activate) - { - // If activation state is unchanged, no point spamming SLM with an update - return true; - } - - LLUUID version_uuid = getVersionFolder(listing_uuid); - - // Also update the count on hand - S32 count = compute_stock_count(folder_id); - if (count == COMPUTE_STOCK_NOT_EVALUATED) - { - // If the count on hand cannot be evaluated locally, we should not change that SLM value - // We are assuming that this issue is local and should not modify server side values - count = getCountOnHand(listing_uuid); - } - - // Post the listing update request to SLM - updateSLMListing(listing_uuid, listing_id, version_uuid, activate, count); - - return true; -} - -bool LLMarketplaceData::setVersionFolder(const LLUUID& folder_id, const LLUUID& version_id, S32 depth) -{ - // Evaluate the depth if it wasn't passed as a parameter - if (depth < 0) - { - depth = depth_nesting_in_marketplace(folder_id); - - } - // Folder id can be the root of the listing or not so we need to retrieve the root first - LLUUID listing_uuid = nested_parent_id(folder_id, depth); - S32 listing_id = getListingID(listing_uuid); - if (listing_id == 0) - { - // Listing doesn't exists -> exit with error - return false; - } - - if (getVersionFolder(listing_uuid) == version_id) - { - // If version folder is unchanged, no point spamming SLM with an update - return true; - } - - // Note: if the version_id is cleared, we need to unlist the listing, otherwise, state unchanged - bool is_listed = (version_id.isNull() ? false : getActivationState(listing_uuid)); - - // Also update the count on hand - S32 count = compute_stock_count(version_id); - if (count == COMPUTE_STOCK_NOT_EVALUATED) - { - // If the count on hand cannot be evaluated, we will consider it empty (out of stock) when resetting the version folder - // It will get reevaluated and updated once the items are fetched - count = 0; - } - - // Post the listing update request to SLM - updateSLMListing(listing_uuid, listing_id, version_id, is_listed, count); - - return true; -} - -bool LLMarketplaceData::updateCountOnHand(const LLUUID& folder_id, S32 depth) -{ - // Evaluate the depth if it wasn't passed as a parameter - if (depth < 0) - { - depth = depth_nesting_in_marketplace(folder_id); - - } - // Folder id can be the root of the listing or not so we need to retrieve the root first - LLUUID listing_uuid = nested_parent_id(folder_id, depth); - S32 listing_id = getListingID(listing_uuid); - if (listing_id == 0) - { - // Listing doesn't exists -> exit with error - return false; - } - - // Compute the new count on hand - S32 count = compute_stock_count(folder_id); - - if (count == getCountOnHand(listing_uuid)) - { - // If count on hand is unchanged, no point spamming SLM with an update - return true; - } - else if (count == COMPUTE_STOCK_NOT_EVALUATED) - { - // If local count on hand is not known at that point, do *not* force an update to SLM - return false; - } - - // Get the unchanged values - bool is_listed = getActivationState(listing_uuid); - LLUUID version_uuid = getVersionFolder(listing_uuid); - - - // Post the listing update request to SLM - updateSLMListing(listing_uuid, listing_id, version_uuid, is_listed, count); - - // Force the local value as it prevents spamming (count update may occur in burst when restocking) - // Note that if SLM has a good reason to return a different value, it'll be updated by the responder - setCountOnHand(listing_uuid, count, false); - - return true; -} - -bool LLMarketplaceData::associateListing(const LLUUID& folder_id, const LLUUID& source_folder_id, S32 listing_id) -{ - if (isListed(folder_id)) - { - // Listing already exists -> exit with error - return false; - } - - // Get the version folder: if there is only one subfolder, we will set it as a version folder immediately - LLUUID version_id = getVersionFolderIfUnique(folder_id); - - // Post the listing associate request to SLM - associateSLMListing(folder_id, listing_id, version_id, source_folder_id); - - return true; -} - -// Methods privately called or called by SLM responders to perform changes -bool LLMarketplaceData::addListing(const LLUUID& folder_id, S32 listing_id, const LLUUID& version_id, bool is_listed, const std::string& edit_url, S32 count) -{ - mMarketplaceItems[folder_id] = LLMarketplaceTuple(folder_id, listing_id, version_id, is_listed); - mMarketplaceItems[folder_id].mEditURL = edit_url; - mMarketplaceItems[folder_id].mCountOnHand = count; - if (version_id.notNull()) - { - mVersionFolders[version_id] = folder_id; - } - return true; -} - -bool LLMarketplaceData::deleteListing(const LLUUID& folder_id, bool update) -{ - LLUUID version_folder = getVersionFolder(folder_id); - - if (mMarketplaceItems.erase(folder_id) != 1) - { - return false; - } - mVersionFolders.erase(version_folder); - - if (update) - { - update_marketplace_category(folder_id, false); - gInventory.notifyObservers(); - } - return true; -} - -bool LLMarketplaceData::deleteListing(S32 listing_id, bool update) -{ - if (listing_id == 0) - { - return false; - } - - LLUUID folder_id = getListingFolder(listing_id); - return deleteListing(folder_id, update); -} - -// Accessors -bool LLMarketplaceData::getActivationState(const LLUUID& folder_id) -{ - // Listing folder case - marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); - if (it != mMarketplaceItems.end()) - { - return (it->second).mIsActive; - } - // Version folder case - version_folders_list_t::iterator it_version = mVersionFolders.find(folder_id); - if (it_version != mVersionFolders.end()) - { - marketplace_items_list_t::iterator it = mMarketplaceItems.find(it_version->second); - if (it != mMarketplaceItems.end()) - { - return (it->second).mIsActive; - } - } - return false; -} - -S32 LLMarketplaceData::getListingID(const LLUUID& folder_id) -{ - marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); - return (it == mMarketplaceItems.end() ? 0 : (it->second).mListingId); -} - -S32 LLMarketplaceData::getCountOnHand(const LLUUID& folder_id) -{ - marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); - return (it == mMarketplaceItems.end() ? -1 : (it->second).mCountOnHand); -} - -LLUUID LLMarketplaceData::getVersionFolder(const LLUUID& folder_id) -{ - marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); - return (it == mMarketplaceItems.end() ? LLUUID::null : (it->second).mVersionFolderId); -} - -// Reverse lookup : find the listing folder id from the listing id -LLUUID LLMarketplaceData::getListingFolder(S32 listing_id) -{ - marketplace_items_list_t::iterator it = mMarketplaceItems.begin(); - while (it != mMarketplaceItems.end()) - { - if ((it->second).mListingId == listing_id) - { - return (it->second).mListingFolderId; - } - it++; - } - return LLUUID::null; -} - -std::string LLMarketplaceData::getListingURL(const LLUUID& folder_id, S32 depth) -{ - // Evaluate the depth if it wasn't passed as a parameter - if (depth < 0) - { - depth = depth_nesting_in_marketplace(folder_id); - - } - - LLUUID listing_uuid = nested_parent_id(folder_id, depth); - - marketplace_items_list_t::iterator it = mMarketplaceItems.find(listing_uuid); - return (it == mMarketplaceItems.end() ? "" : (it->second).mEditURL); -} - -bool LLMarketplaceData::isListed(const LLUUID& folder_id) -{ - marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); - return (it != mMarketplaceItems.end()); -} - -bool LLMarketplaceData::isListedAndActive(const LLUUID& folder_id) -{ - return (isListed(folder_id) && getActivationState(folder_id)); -} - -bool LLMarketplaceData::isVersionFolder(const LLUUID& folder_id) -{ - version_folders_list_t::iterator it = mVersionFolders.find(folder_id); - return (it != mVersionFolders.end()); -} - -bool LLMarketplaceData::isInActiveFolder(const LLUUID& obj_id, S32 depth) -{ - // Evaluate the depth if it wasn't passed as a parameter - if (depth < 0) - { - depth = depth_nesting_in_marketplace(obj_id); - - } - - LLUUID listing_uuid = nested_parent_id(obj_id, depth); - bool active = getActivationState(listing_uuid); - LLUUID version_uuid = getVersionFolder(listing_uuid); - return (active && ((obj_id == version_uuid) || gInventory.isObjectDescendentOf(obj_id, version_uuid))); -} - -LLUUID LLMarketplaceData::getActiveFolder(const LLUUID& obj_id, S32 depth) -{ - // Evaluate the depth if it wasn't passed as a parameter - if (depth < 0) - { - depth = depth_nesting_in_marketplace(obj_id); - - } - - LLUUID listing_uuid = nested_parent_id(obj_id, depth); - return (getActivationState(listing_uuid) ? getVersionFolder(listing_uuid) : LLUUID::null); -} - -bool LLMarketplaceData::isUpdating(const LLUUID& folder_id, S32 depth) -{ - // Evaluate the depth if it wasn't passed as a parameter - if (depth < 0) - { - depth = depth_nesting_in_marketplace(folder_id); - } - if ((depth <= 0) || (depth > 2)) - { - // Only listing and version folders though are concerned by that status - return false; - } - else - { - const LLUUID marketplace_listings_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - std::set::iterator it = mPendingUpdateSet.find(marketplace_listings_uuid); - if (it != mPendingUpdateSet.end()) - { - // If we're waiting for data for the marketplace listings root, we are in the updating process for all - return true; - } - else - { - // Check if the listing folder is waiting or data - LLUUID listing_uuid = nested_parent_id(folder_id, depth); - it = mPendingUpdateSet.find(listing_uuid); - return (it != mPendingUpdateSet.end()); - } - } -} - -void LLMarketplaceData::setUpdating(const LLUUID& folder_id, bool isUpdating) -{ - std::set::iterator it = mPendingUpdateSet.find(folder_id); - if (it != mPendingUpdateSet.end()) - { - mPendingUpdateSet.erase(it); - } - if (isUpdating) - { - mPendingUpdateSet.insert(folder_id); - } -} - -void LLMarketplaceData::setValidationWaiting(const LLUUID& folder_id, S32 count) -{ - mValidationWaitingList[folder_id] = count; -} - -void LLMarketplaceData::decrementValidationWaiting(const LLUUID& folder_id, S32 count) -{ - waiting_list_t::iterator found = mValidationWaitingList.find(folder_id); - if (found != mValidationWaitingList.end()) - { - found->second -= count; - if (found->second <= 0) - { - mValidationWaitingList.erase(found); - LLMarketplaceValidator::getInstance()->validateMarketplaceListings(folder_id); - update_marketplace_category(folder_id); - gInventory.notifyObservers(); - } - } -} - -// Private Modifiers -bool LLMarketplaceData::setListingID(const LLUUID& folder_id, S32 listing_id, bool update) -{ - marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); - if (it == mMarketplaceItems.end()) - { - return false; - } - - (it->second).mListingId = listing_id; - - if (update) - { - update_marketplace_category(folder_id, false); - gInventory.notifyObservers(); - } - return true; -} - -bool LLMarketplaceData::setCountOnHand(const LLUUID& folder_id, S32 count, bool update) -{ - marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); - if (it == mMarketplaceItems.end()) - { - return false; - } - - (it->second).mCountOnHand = count; - - return true; -} - -bool LLMarketplaceData::setVersionFolderID(const LLUUID& folder_id, const LLUUID& version_id, bool update) -{ - marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); - if (it == mMarketplaceItems.end()) - { - return false; - } - - LLUUID old_version_id = (it->second).mVersionFolderId; - if (old_version_id == version_id) - { - return false; - } - - (it->second).mVersionFolderId = version_id; - mVersionFolders.erase(old_version_id); - if (version_id.notNull()) - { - mVersionFolders[version_id] = folder_id; - } - - if (update) - { - update_marketplace_category(old_version_id, false); - update_marketplace_category(version_id, false); - gInventory.notifyObservers(); - } - return true; -} - -bool LLMarketplaceData::setActivationState(const LLUUID& folder_id, bool activate, bool update) -{ - marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); - if (it == mMarketplaceItems.end()) - { - return false; - } - - (it->second).mIsActive = activate; - - if (update) - { - update_marketplace_category((it->second).mListingFolderId, false); - gInventory.notifyObservers(); - } - return true; -} - -bool LLMarketplaceData::setListingURL(const LLUUID& folder_id, const std::string& edit_url, bool update) -{ - marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); - if (it == mMarketplaceItems.end()) - { - return false; - } - - (it->second).mEditURL = edit_url; - return true; -} - +/** + * @file llmarketplacefunctions.cpp + * @brief Implementation of assorted functions related to the marketplace + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llmarketplacefunctions.h" + +#include "llagent.h" +#include "llbufferstream.h" +#include "llcallbacklist.h" +#include "llinventoryfunctions.h" +#include "llinventoryobserver.h" +#include "llnotificationsutil.h" +#include "llsdserialize.h" +#include "lltimer.h" +#include "lltrans.h" +#include "llviewercontrol.h" +#include "llviewerinventory.h" +#include "llviewermedia.h" +#include "llviewernetwork.h" +#include "llviewerregion.h" +#include "lleventcoro.h" +#include "llcoros.h" +#include "llcorehttputil.h" + +#include "llsdutil.h" +// +// Helpers +// + +namespace { + + static std::string getMarketplaceDomain() + { + std::string domain = "secondlife.com"; + + if (!LLGridManager::getInstance()->isInProductionGrid()) + { + const std::string& grid_id = LLGridManager::getInstance()->getGridId(); + const std::string& grid_id_lower = utf8str_tolower(grid_id); + + if (grid_id_lower == "damballah") + { + domain = "secondlife-staging.com"; + } + else + { + domain = llformat("%s.lindenlab.com", grid_id_lower.c_str()); + } + } + + return domain; + } + + static std::string getMarketplaceURL(const std::string& urlStringName) + { + LLStringUtil::format_map_t domain_arg; + domain_arg["[MARKETPLACE_DOMAIN_NAME]"] = getMarketplaceDomain(); + + std::string marketplace_url = LLTrans::getString(urlStringName, domain_arg); + + return marketplace_url; + } + + // Get the version folder: if there is only one subfolder, we will use it as a version folder + LLUUID getVersionFolderIfUnique(const LLUUID& folder_id) + { + LLUUID version_id = LLUUID::null; + LLInventoryModel::cat_array_t* categories; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(folder_id, categories, items); + if (categories->size() == 1) + { + version_id = categories->begin()->get()->getUUID(); + } + else + { + LLNotificationsUtil::add("AlertMerchantListingActivateRequired"); + } + return version_id; + } + + /////////////////////////////////////////////////////////////////////////////// + // SLM Reporters + void log_SLM_warning(const std::string& request, U32 status, const std::string& reason, const std::string& code, const LLSD& result) + { + + LL_WARNS("SLM") << "SLM API : Responder to " << request << ". status : " << status << ", reason : " << reason << ", code : " << code << ", description : " << ll_pretty_print_sd(result) << LL_ENDL; + if ((status == 422) && (result.has(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT) && + result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT].isArray() && + result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT].size() > 4)) + { + // Unprocessable Entity : Special case that error as it is a frequent answer when trying to list an incomplete listing + LLNotificationsUtil::add("MerchantUnprocessableEntity"); + } + else + { + // Prompt the user with the warning (so they know why things are failing) + LLSD subs; + // We do show long descriptions in the alert (unlikely to be readable). The description string will be in the log though. + std::string description; + if (result.has(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT)) + { + LLSD content = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_CONTENT]; + if (content.isArray()) + { + for (LLSD::array_iterator it = content.beginArray(); it != content.endArray(); ++it) + { + if (!description.empty()) + description += "\n"; + description += (*it).asString(); + } + } + else + { + description = content.asString(); + } + } + else + { + description = result.asString(); + } + std::string reason_lc = reason; + LLStringUtil::toLower(reason_lc); + if (!description.empty() && reason_lc.find("unknown") != std::string::npos) + { + subs["[ERROR_REASON]"] = ""; + } + else + { + subs["[ERROR_REASON]"] = "'" + reason +"'\n"; + } + subs["[ERROR_DESCRIPTION]"] = description; + LLNotificationsUtil::add("MerchantTransactionFailed", subs); + } + + } + + void log_SLM_infos(const std::string& request, U32 status, const std::string& body) + { + if (gSavedSettings.getBOOL("MarketplaceListingsLogging")) + { + LL_INFOS("SLM") << "SLM API : Responder to " << request << ". status : " << status << ", body or description : " << body << LL_ENDL; + } + } + + void log_SLM_infos(const std::string& request, U32 status, const LLSD& body) + { + log_SLM_infos(request, status, std::string(ll_pretty_print_sd(body))); + } + +} + + +#if 1 +namespace LLMarketplaceImport +{ + // Basic interface for this namespace + + bool hasSessionCookie(); + bool inProgress(); + bool resultPending(); + S32 getResultStatus(); + const LLSD& getResults(); + + bool establishMarketplaceSessionCookie(); + bool pollStatus(); + bool triggerImport(); + + // Internal state variables + + static std::string sMarketplaceCookie = ""; + static LLSD sImportId = LLSD::emptyMap(); + static bool sImportInProgress = false; + static bool sImportPostPending = false; + static bool sImportGetPending = false; + static S32 sImportResultStatus = 0; + static LLSD sImportResults = LLSD::emptyMap(); + + // Responders + + void marketplacePostCoro(std::string url) + { + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("marketplacePostCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + + httpOpts->setWantHeaders(true); + httpOpts->setFollowRedirects(true); + + httpHeaders->append(HTTP_OUT_HEADER_ACCEPT, "*/*"); + httpHeaders->append(HTTP_OUT_HEADER_CONNECTION, "Keep-Alive"); + httpHeaders->append(HTTP_OUT_HEADER_COOKIE, sMarketplaceCookie); + httpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_XML); + httpHeaders->append(HTTP_OUT_HEADER_USER_AGENT, LLViewerMedia::getInstance()->getCurrentUserAgent()); + + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, LLSD(), httpOpts, httpHeaders); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + S32 httpCode = status.getType(); + if ((httpCode == MarketplaceErrorCodes::IMPORT_REDIRECT) || + (httpCode == MarketplaceErrorCodes::IMPORT_AUTHENTICATION_ERROR) || + // MAINT-2301 : we determined we can safely ignore that error in that context + (httpCode == MarketplaceErrorCodes::IMPORT_JOB_TIMEOUT)) + { + if (gSavedSettings.getBOOL("InventoryOutboxLogging")) + { + LL_INFOS() << " SLM POST : Ignoring time out status and treating it as success" << LL_ENDL; + } + httpCode = MarketplaceErrorCodes::IMPORT_DONE; + } + + if (httpCode >= MarketplaceErrorCodes::IMPORT_BAD_REQUEST) + { + if (gSavedSettings.getBOOL("InventoryOutboxLogging")) + { + LL_INFOS() << " SLM POST clearing marketplace cookie due to client or server error" << LL_ENDL; + } + sMarketplaceCookie.clear(); + } + + sImportInProgress = (httpCode == MarketplaceErrorCodes::IMPORT_DONE); + sImportPostPending = false; + sImportResultStatus = httpCode; + + { + std::stringstream str; + LLSDSerialize::toPrettyXML(result, str); + + LL_INFOS() << "Full results:\n" << str.str() << "\n" << LL_ENDL; + } + + result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); + sImportId = result; + + } + + void marketplaceGetCoro(std::string url, bool buildHeaders) + { + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("marketplaceGetCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpHeaders::ptr_t httpHeaders; + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + + httpOpts->setWantHeaders(true); + httpOpts->setFollowRedirects(!sMarketplaceCookie.empty()); + + if (buildHeaders) + { + httpHeaders = LLCore::HttpHeaders::ptr_t(new LLCore::HttpHeaders); + + httpHeaders->append(HTTP_OUT_HEADER_ACCEPT, "*/*"); + httpHeaders->append(HTTP_OUT_HEADER_COOKIE, sMarketplaceCookie); + httpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_LLSD_XML); + httpHeaders->append(HTTP_OUT_HEADER_USER_AGENT, LLViewerMedia::getInstance()->getCurrentUserAgent()); + } + else + { + httpHeaders = LLViewerMedia::getInstance()->getHttpHeaders(); + } + + LLSD result = httpAdapter->getAndSuspend(httpRequest, url, httpOpts, httpHeaders); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + LLSD resultHeaders = httpResults[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS]; + + if (sMarketplaceCookie.empty() && resultHeaders.has(HTTP_IN_HEADER_SET_COOKIE)) + { + sMarketplaceCookie = resultHeaders[HTTP_IN_HEADER_SET_COOKIE].asString(); + } + + // MAINT-2452 : Do not clear the cookie on IMPORT_DONE_WITH_ERRORS : Happens when trying to import objects with wrong permissions + // ACME-1221 : Do not clear the cookie on IMPORT_NOT_FOUND : Happens for newly created Merchant accounts that are initially empty + S32 httpCode = status.getType(); + if ((httpCode >= MarketplaceErrorCodes::IMPORT_BAD_REQUEST) && + (httpCode != MarketplaceErrorCodes::IMPORT_DONE_WITH_ERRORS) && + (httpCode != MarketplaceErrorCodes::IMPORT_NOT_FOUND)) + { + if (gSavedSettings.getBOOL("InventoryOutboxLogging")) + { + LL_INFOS() << " SLM GET clearing marketplace cookie due to client or server error" << LL_ENDL; + } + sMarketplaceCookie.clear(); + } + else if (gSavedSettings.getBOOL("InventoryOutboxLogging") && (httpCode >= MarketplaceErrorCodes::IMPORT_BAD_REQUEST)) + { + LL_INFOS() << " SLM GET : Got error status = " << httpCode << ", but marketplace cookie not cleared." << LL_ENDL; + } + + sImportInProgress = (httpCode == MarketplaceErrorCodes::IMPORT_PROCESSING); + sImportGetPending = false; + sImportResultStatus = httpCode; + + result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); + sImportResults = result; + + + } + + // Basic API + + bool hasSessionCookie() + { + return !sMarketplaceCookie.empty(); + } + + bool inProgress() + { + return sImportInProgress; + } + + bool resultPending() + { + return (sImportPostPending || sImportGetPending); + } + + S32 getResultStatus() + { + return sImportResultStatus; + } + + const LLSD& getResults() + { + return sImportResults; + } + + static std::string getInventoryImportURL() + { + std::string url = getMarketplaceURL("MarketplaceURL"); + + url += "api/1/"; + url += gAgent.getID().getString(); + url += "/inventory/import/"; + + return url; + } + + bool establishMarketplaceSessionCookie() + { + if (hasSessionCookie()) + { + return false; + } + + sImportInProgress = true; + sImportGetPending = true; + + std::string url = getInventoryImportURL(); + + LLCoros::instance().launch("marketplaceGetCoro", + boost::bind(&marketplaceGetCoro, url, false)); + + return true; + } + + bool pollStatus() + { + if (!hasSessionCookie()) + { + return false; + } + + sImportGetPending = true; + + std::string url = getInventoryImportURL(); + + url += sImportId.asString(); + + LLCoros::instance().launch("marketplaceGetCoro", + boost::bind(&marketplaceGetCoro, url, true)); + + return true; + } + + bool triggerImport() + { + if (!hasSessionCookie()) + { + return false; + } + + sImportId = LLSD::emptyMap(); + sImportInProgress = true; + sImportPostPending = true; + sImportResultStatus = MarketplaceErrorCodes::IMPORT_PROCESSING; + sImportResults = LLSD::emptyMap(); + + std::string url = getInventoryImportURL(); + + LLCoros::instance().launch("marketplacePostCoro", + boost::bind(&marketplacePostCoro, url)); + + return true; + } +} +#endif + +// +// Interface class +// +static const F32 MARKET_IMPORTER_UPDATE_FREQUENCY = 1.0f; + +//static +void LLMarketplaceInventoryImporter::update() +{ + if (instanceExists()) + { + static LLTimer update_timer; + if (update_timer.hasExpired()) + { + LLMarketplaceInventoryImporter::instance().updateImport(); + update_timer.setTimerExpirySec(MARKET_IMPORTER_UPDATE_FREQUENCY); + } + } +} + +LLMarketplaceInventoryImporter::LLMarketplaceInventoryImporter() + : mAutoTriggerImport(false) + , mImportInProgress(false) + , mInitialized(false) + , mMarketPlaceStatus(MarketplaceStatusCodes::MARKET_PLACE_NOT_INITIALIZED) + , mErrorInitSignal(NULL) + , mStatusChangedSignal(NULL) + , mStatusReportSignal(NULL) +{ +} + +boost::signals2::connection LLMarketplaceInventoryImporter::setInitializationErrorCallback(const status_report_signal_t::slot_type& cb) +{ + if (mErrorInitSignal == NULL) + { + mErrorInitSignal = new status_report_signal_t(); + } + + return mErrorInitSignal->connect(cb); +} + +boost::signals2::connection LLMarketplaceInventoryImporter::setStatusChangedCallback(const status_changed_signal_t::slot_type& cb) +{ + if (mStatusChangedSignal == NULL) + { + mStatusChangedSignal = new status_changed_signal_t(); + } + + return mStatusChangedSignal->connect(cb); +} + +boost::signals2::connection LLMarketplaceInventoryImporter::setStatusReportCallback(const status_report_signal_t::slot_type& cb) +{ + if (mStatusReportSignal == NULL) + { + mStatusReportSignal = new status_report_signal_t(); + } + + return mStatusReportSignal->connect(cb); +} + +void LLMarketplaceInventoryImporter::initialize() +{ + if (mInitialized) + { + return; + } + + if (!LLMarketplaceImport::hasSessionCookie()) + { + mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_INITIALIZING; + LLMarketplaceImport::establishMarketplaceSessionCookie(); + } + else + { + mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_MERCHANT; + } +} + +void LLMarketplaceInventoryImporter::reinitializeAndTriggerImport() +{ + mInitialized = false; + mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_NOT_INITIALIZED; + initialize(); + mAutoTriggerImport = true; +} + +bool LLMarketplaceInventoryImporter::triggerImport() +{ + const bool import_triggered = LLMarketplaceImport::triggerImport(); + + if (!import_triggered) + { + reinitializeAndTriggerImport(); + } + + return import_triggered; +} + +void LLMarketplaceInventoryImporter::updateImport() +{ + const bool in_progress = LLMarketplaceImport::inProgress(); + + if (in_progress && !LLMarketplaceImport::resultPending()) + { + const bool polling_status = LLMarketplaceImport::pollStatus(); + + if (!polling_status) + { + reinitializeAndTriggerImport(); + } + } + + if (mImportInProgress != in_progress) + { + mImportInProgress = in_progress; + + // If we are no longer in progress + if (!mImportInProgress) + { + // Look for results success + mInitialized = LLMarketplaceImport::hasSessionCookie(); + + // Report results + if (mStatusReportSignal) + { + (*mStatusReportSignal)(LLMarketplaceImport::getResultStatus(), LLMarketplaceImport::getResults()); + } + + if (mInitialized) + { + mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_MERCHANT; + // Follow up with auto trigger of import + if (mAutoTriggerImport) + { + mAutoTriggerImport = false; + mImportInProgress = triggerImport(); + } + } + else + { + U32 status = LLMarketplaceImport::getResultStatus(); + if ((status == MarketplaceErrorCodes::IMPORT_FORBIDDEN) || + (status == MarketplaceErrorCodes::IMPORT_AUTHENTICATION_ERROR)) + { + mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_NOT_MERCHANT; + } + else if (status == MarketplaceErrorCodes::IMPORT_SERVER_API_DISABLED) + { + mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_MIGRATED_MERCHANT; + } + else + { + mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE; + } + if (mErrorInitSignal && (mMarketPlaceStatus == MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE)) + { + (*mErrorInitSignal)(LLMarketplaceImport::getResultStatus(), LLMarketplaceImport::getResults()); + } + } + } + } + + // Make sure we trigger the status change with the final state (in case of auto trigger after initialize) + if (mStatusChangedSignal) + { + (*mStatusChangedSignal)(mImportInProgress); + } +} + +// +// Direct Delivery : Marketplace tuples and data +// +class LLMarketplaceInventoryObserver : public LLInventoryObserver +{ +public: + LLMarketplaceInventoryObserver() {} + virtual ~LLMarketplaceInventoryObserver() {} + virtual void changed(U32 mask); + +private: + static void onIdleProcessQueue(void *userdata); + + // doesn't hold just marketplace related ids + static std::set sStructureQueue; + static bool sProcessingQueue; +}; + +std::set LLMarketplaceInventoryObserver::sStructureQueue; +bool LLMarketplaceInventoryObserver::sProcessingQueue = false; + +void LLMarketplaceInventoryObserver::changed(U32 mask) +{ + if (mask & (LLInventoryObserver::INTERNAL | LLInventoryObserver::STRUCTURE)) + { + // When things are changed in the inventory, this can trigger a host of changes in the marketplace listings folder: + // * stock counts changing : no copy items coming in and out will change the stock count on folders + // * version and listing folders : moving those might invalidate the marketplace data itself + // Since we should cannot raise inventory change while the observer is called (the list will be cleared + // once observers are called) we need to raise a flag in the inventory to signal that things have been dirtied. + const std::set& changed_items = gInventory.getChangedIDs(); + sStructureQueue.insert(changed_items.begin(), changed_items.end()); + } + + if (!sProcessingQueue && !sStructureQueue.empty()) + { + gIdleCallbacks.addFunction(onIdleProcessQueue, NULL); + // can do without sProcessingQueue, but it's usufull for simplicity and reliability + sProcessingQueue = true; + } +} + +void LLMarketplaceInventoryObserver::onIdleProcessQueue(void *userdata) +{ + U64 start_time = LLTimer::getTotalTime(); // microseconds + const U64 MAX_PROCESSING_TIME = 1000; + U64 stop_time = start_time + MAX_PROCESSING_TIME; + + while (!sStructureQueue.empty() && LLTimer::getTotalTime() < stop_time) + { + std::set::const_iterator id_it = sStructureQueue.begin(); + LLInventoryObject* obj = gInventory.getObject(*id_it); + if (obj) + { + if (LLAssetType::AT_CATEGORY == obj->getType()) + { + // If it's a folder known to the marketplace, let's check it's in proper shape + if (LLMarketplaceData::instance().isListed(*id_it) || LLMarketplaceData::instance().isVersionFolder(*id_it)) + { + // can trigger notifyObservers + // can cause more structural changes + LLMarketplaceValidator::getInstance()->validateMarketplaceListings(obj->getUUID()); + } + } + else + { + // If it's not a category, it's an item... + LLInventoryItem* item = (LLInventoryItem*)(obj); + // If it's a no copy item, we may need to update the label count of marketplace listings + if (!item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) + { + LLMarketplaceData::instance().setDirtyCount(); + } + } + } + + // sStructureQueue could have been modified in validate_marketplacelistings + // adding items does not invalidate existing iterator + sStructureQueue.erase(id_it); + } + + if (LLApp::isExiting() || sStructureQueue.empty()) + { + // Nothing to do anymore + gIdleCallbacks.deleteFunction(onIdleProcessQueue, NULL); + sProcessingQueue = false; + } +} + +// Tuple == Item +LLMarketplaceTuple::LLMarketplaceTuple() : + mListingFolderId(), + mListingId(0), + mVersionFolderId(), + mIsActive(false), + mEditURL("") +{ +} + +LLMarketplaceTuple::LLMarketplaceTuple(const LLUUID& folder_id) : + mListingFolderId(folder_id), + mListingId(0), + mVersionFolderId(), + mIsActive(false), + mEditURL("") +{ +} + +LLMarketplaceTuple::LLMarketplaceTuple(const LLUUID& folder_id, S32 listing_id, const LLUUID& version_id, bool is_listed) : + mListingFolderId(folder_id), + mListingId(listing_id), + mVersionFolderId(version_id), + mIsActive(is_listed), + mEditURL("") +{ +} + + +// Data map +LLMarketplaceData::LLMarketplaceData() : + mMarketPlaceStatus(MarketplaceStatusCodes::MARKET_PLACE_NOT_INITIALIZED), + mMarketPlaceDataFetched(MarketplaceFetchCodes::MARKET_FETCH_NOT_DONE), + mStatusUpdatedSignal(NULL), + mDataFetchedSignal(NULL), + mDirtyCount(false) +{ + mInventoryObserver = new LLMarketplaceInventoryObserver; + gInventory.addObserver(mInventoryObserver); +} + +LLMarketplaceData::~LLMarketplaceData() +{ + gInventory.removeObserver(mInventoryObserver); +} + + +LLSD LLMarketplaceData::getMarketplaceStringSubstitutions() +{ + std::string marketplace_url = getMarketplaceURL("MarketplaceURL"); + std::string marketplace_url_create = getMarketplaceURL("MarketplaceURL_CreateStore"); + std::string marketplace_url_dashboard = getMarketplaceURL("MarketplaceURL_Dashboard"); + std::string marketplace_url_imports = getMarketplaceURL("MarketplaceURL_Imports"); + std::string marketplace_url_info = getMarketplaceURL("MarketplaceURL_LearnMore"); + + LLSD marketplace_sub_map; + + marketplace_sub_map["[MARKETPLACE_URL]"] = marketplace_url; + marketplace_sub_map["[MARKETPLACE_CREATE_STORE_URL]"] = marketplace_url_create; + marketplace_sub_map["[MARKETPLACE_LEARN_MORE_URL]"] = marketplace_url_info; + marketplace_sub_map["[MARKETPLACE_DASHBOARD_URL]"] = marketplace_url_dashboard; + marketplace_sub_map["[MARKETPLACE_IMPORTS_URL]"] = marketplace_url_imports; + + return marketplace_sub_map; +} + +void LLMarketplaceData::initializeSLM(const status_updated_signal_t::slot_type& cb) +{ + if (mStatusUpdatedSignal == NULL) + { + mStatusUpdatedSignal = new status_updated_signal_t(); + } + mStatusUpdatedSignal->connect(cb); + + if (mMarketPlaceStatus != MarketplaceStatusCodes::MARKET_PLACE_NOT_INITIALIZED) + { + // If already initialized, just confirm the status so the callback gets called + if (mMarketPlaceFailureReason.empty()) + { + setSLMStatus(mMarketPlaceStatus); + } + else + { + setSLMConnectionFailure(mMarketPlaceFailureReason); + } + } + else + { + mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_INITIALIZING; + + LLCoros::instance().launch("getMerchantStatus", + boost::bind(&LLMarketplaceData::getMerchantStatusCoro, this)); + } +} + +void LLMarketplaceData::getMerchantStatusCoro() +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + + httpOpts->setFollowRedirects(true); + + std::string url = getSLMConnectURL("/merchant"); + if (url.empty()) + { + LL_WARNS("Marketplace") << "No marketplace capability on Sim" << LL_ENDL; + setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE); + return; + } + + LLSD result = httpAdapter->getAndSuspend(httpRequest, url, httpOpts); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + S32 httpCode = status.getType(); + + if (httpCode == HTTP_NOT_FOUND) + { + log_SLM_infos("Get /merchant", httpCode, std::string("User is not a merchant")); + LLMarketplaceData::instance().setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_NOT_MERCHANT); + } + else if (httpCode == HTTP_SERVICE_UNAVAILABLE) + { + log_SLM_infos("Get /merchant", httpCode, std::string("Merchant is not migrated")); + LLMarketplaceData::instance().setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_NOT_MIGRATED_MERCHANT); + } + else + { + LL_WARNS("SLM") << "SLM Merchant Request failed with status: " << httpCode + << ", reason : " << status.toString() + << ", code : " << result["error_code"].asString() + << ", description : " << result["error_description"].asString() << LL_ENDL; + std::string reason = status.toString(); + if (reason.empty()) + { + reason = result["error_code"].asString(); + } + // Since user might not even have a marketplace, there is no reason to report the error + // to the user, instead write it down into listings' floater + LLMarketplaceData::instance().setSLMConnectionFailure(reason); + } + return; + } + + log_SLM_infos("Get /merchant", status.getType(), std::string("User is a merchant")); + setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_MERCHANT); +} + +void LLMarketplaceData::setDataFetchedSignal(const status_updated_signal_t::slot_type& cb) +{ + if (mDataFetchedSignal == NULL) + { + mDataFetchedSignal = new status_updated_signal_t(); + } + mDataFetchedSignal->connect(cb); +} + +// Get/Post/Put requests to the SLM Server using the SLM API +void LLMarketplaceData::getSLMListings() +{ + const LLUUID marketplaceFolderId = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + setUpdating(marketplaceFolderId, true); + + LLCoros::instance().launch("getSLMListings", + boost::bind(&LLMarketplaceData::getSLMListingsCoro, this, marketplaceFolderId)); +} + +void LLMarketplaceData::getSLMListingsCoro(LLUUID folderId) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); + + httpHeaders->append("Accept", "application/json"); + httpHeaders->append("Content-Type", "application/json"); + + std::string url = getSLMConnectURL("/listings"); + + LLSD result = httpAdapter->getJsonAndSuspend(httpRequest, url, httpHeaders); + + setUpdating(folderId, false); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + log_SLM_warning("Get /listings", status.getType(), status.toString(), "", result); + setSLMDataFetched(MarketplaceFetchCodes::MARKET_FETCH_FAILED); + update_marketplace_category(folderId, false); + gInventory.notifyObservers(); + return; + } + + log_SLM_infos("Get /listings", static_cast(status.getType()), result); + + // Extract the info from the results + for (LLSD::array_iterator it = result["listings"].beginArray(); + it != result["listings"].endArray(); ++it) + { + LLSD listing = *it; + + int listingId = listing["id"].asInteger(); + bool isListed = listing["is_listed"].asBoolean(); + std::string editUrl = listing["edit_url"].asString(); + LLUUID folderUuid = listing["inventory_info"]["listing_folder_id"].asUUID(); + LLUUID versionUuid = listing["inventory_info"]["version_folder_id"].asUUID(); + int count = listing["inventory_info"]["count_on_hand"].asInteger(); + + if (folderUuid.notNull()) + { + addListing(folderUuid, listingId, versionUuid, isListed, editUrl, count); + } + } + + // Update all folders under the root + setSLMDataFetched(MarketplaceFetchCodes::MARKET_FETCH_DONE); + update_marketplace_category(folderId, false); + gInventory.notifyObservers(); +} + +void LLMarketplaceData::getSLMListing(S32 listingId) +{ + LLUUID folderId = getListingFolder(listingId); + setUpdating(folderId, true); + + LLCoros::instance().launch("getSingleListingCoro", + boost::bind(&LLMarketplaceData::getSingleListingCoro, this, listingId, folderId)); +} + +void LLMarketplaceData::getSingleListingCoro(S32 listingId, LLUUID folderId) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); + + httpHeaders->append("Accept", "application/json"); + httpHeaders->append("Content-Type", "application/json"); + + std::string url = getSLMConnectURL("/listing/") + llformat("%d", listingId); + + LLSD result = httpAdapter->getJsonAndSuspend(httpRequest, url, httpHeaders); + + setUpdating(folderId, false); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + if (status.getType() == HTTP_NOT_FOUND) + { + // That listing does not exist -> delete its record from the local SLM data store + deleteListing(folderId, false); + } + else + { + log_SLM_warning("Get /listing", status.getType(), status.toString(), "", result); + } + + update_marketplace_category(folderId, false); + gInventory.notifyObservers(); + return; + } + + log_SLM_infos("Get /listings", static_cast(status.getType()), result); + + + // Extract the info from the results + for (LLSD::array_iterator it = result["listings"].beginArray(); + it != result["listings"].endArray(); ++it) + { + LLSD listing = *it; + + int resListingId = listing["id"].asInteger(); + bool isListed = listing["is_listed"].asBoolean(); + std::string editUrl = listing["edit_url"].asString(); + LLUUID folderUuid = listing["inventory_info"]["listing_folder_id"].asUUID(); + LLUUID versionUuid = listing["inventory_info"]["version_folder_id"].asUUID(); + int count = listing["inventory_info"]["count_on_hand"].asInteger(); + + // Update that listing + setListingID(folderUuid, resListingId, false); + setVersionFolderID(folderUuid, versionUuid, false); + setActivationState(folderUuid, isListed, false); + setListingURL(folderUuid, editUrl, false); + setCountOnHand(folderUuid, count, false); + update_marketplace_category(folderUuid, false); + gInventory.notifyObservers(); + } +} + +void LLMarketplaceData::createSLMListing(const LLUUID& folder_id, const LLUUID& version_id, S32 count) +{ + setUpdating(folder_id, true); + LLCoros::instance().launch("createSLMListingCoro", + boost::bind(&LLMarketplaceData::createSLMListingCoro, this, folder_id, version_id, count)); +} + +void LLMarketplaceData::createSLMListingCoro(LLUUID folderId, LLUUID versionId, S32 count) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); + + httpHeaders->append("Accept", "application/json"); + httpHeaders->append("Content-Type", "application/json"); + + LLViewerInventoryCategory* category = gInventory.getCategory(folderId); + LLSD invInfo; + invInfo["listing_folder_id"] = folderId; + invInfo["version_folder_id"] = versionId; + invInfo["count_on_hand"] = count; + LLSD listing; + listing["name"] = category->getName(); + listing["inventory_info"] = invInfo; + LLSD postData; + postData["listing"] = listing; + + std::string url = getSLMConnectURL("/listings"); + + LLSD result = httpAdapter->postJsonAndSuspend(httpRequest, url, postData, httpHeaders); + + setUpdating(folderId, false); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + log_SLM_warning("Post /listings", status.getType(), status.toString(), "", result); + update_marketplace_category(folderId, false); + gInventory.notifyObservers(); + return; + } + + log_SLM_infos("Post /listings", status.getType(), result); + + if (!result.has("listings") || !result["listings"].isArray() || result["listings"].size() == 0) + { + LL_INFOS("SLM") << "Received an empty response for folder " << folderId << LL_ENDL; + return; + } + + // Extract the info from the results + for (LLSD::array_iterator it = result["listings"].beginArray(); + it != result["listings"].endArray(); ++it) + { + LLSD listing = *it; + + int listingId = listing["id"].asInteger(); + bool isListed = listing["is_listed"].asBoolean(); + std::string editUrl = listing["edit_url"].asString(); + LLUUID folderUuid = listing["inventory_info"]["listing_folder_id"].asUUID(); + LLUUID versionUuid = listing["inventory_info"]["version_folder_id"].asUUID(); + int count = listing["inventory_info"]["count_on_hand"].asInteger(); + + addListing(folderUuid, listingId, versionUuid, isListed, editUrl, count); + update_marketplace_category(folderUuid, false); + gInventory.notifyObservers(); + } + +} + +void LLMarketplaceData::updateSLMListing(const LLUUID& folder_id, S32 listing_id, const LLUUID& version_id, bool is_listed, S32 count) +{ + setUpdating(folder_id, true); + LLCoros::instance().launch("updateSLMListingCoro", + boost::bind(&LLMarketplaceData::updateSLMListingCoro, this, folder_id, listing_id, version_id, is_listed, count)); +} + +void LLMarketplaceData::updateSLMListingCoro(LLUUID folderId, S32 listingId, LLUUID versionId, bool isListed, S32 count) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); + + httpHeaders->append("Accept", "application/json"); + httpHeaders->append("Content-Type", "application/json"); + + LLSD invInfo; + invInfo["listing_folder_id"] = folderId; + invInfo["version_folder_id"] = versionId; + invInfo["count_on_hand"] = count; + LLSD listing; + listing["inventory_info"] = invInfo; + listing["id"] = listingId; + listing["is_listed"] = isListed; + LLSD postData; + postData["listing"] = listing; + + std::string url = getSLMConnectURL("/listing/") + llformat("%d", listingId); + LLSD result = httpAdapter->putJsonAndSuspend(httpRequest, url, postData, httpHeaders); + + setUpdating(folderId, false); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + log_SLM_warning("Put /listing", status.getType(), status.toString(), "", result); + update_marketplace_category(folderId, false); + gInventory.notifyObservers(); + return; + } + + log_SLM_infos("Put /listing", status.getType(), result); + + if (!result.has("listings") || !result["listings"].isArray() || result["listings"].size() == 0) + { + LL_INFOS("SLM") << "Received an empty response for listing " << listingId << " folder " << folderId << LL_ENDL; + // Try to get listing more directly after a delay + const float FORCE_UPDATE_TIMEOUT = 5.0; + llcoro::suspendUntilTimeout(FORCE_UPDATE_TIMEOUT); + if (!LLApp::isExiting() && LLMarketplaceData::instanceExists()) + { + getSLMListing(listingId); + } + return; + } + + // Extract the info from the Json string + for (LLSD::array_iterator it = result["listings"].beginArray(); + it != result["listings"].endArray(); ++it) + { + LLSD listing = *it; + + int listing_id = listing["id"].asInteger(); + bool is_listed = listing["is_listed"].asBoolean(); + std::string edit_url = listing["edit_url"].asString(); + LLUUID folderUuid = listing["inventory_info"]["listing_folder_id"].asUUID(); + LLUUID versionUuid = listing["inventory_info"]["version_folder_id"].asUUID(); + int onHand = listing["inventory_info"]["count_on_hand"].asInteger(); + + // Update that listing + setListingID(folderUuid, listing_id, false); + setVersionFolderID(folderUuid, versionUuid, false); + setActivationState(folderUuid, is_listed, false); + setListingURL(folderUuid, edit_url, false); + setCountOnHand(folderUuid, onHand, false); + update_marketplace_category(folderUuid, false); + gInventory.notifyObservers(); + + // Show a notification alert if what we got is not what we expected + // (this actually doesn't result in an error status from the SLM API protocol) + if ((isListed != is_listed) || (versionId != versionUuid)) + { + LLSD subs; + subs["[URL]"] = edit_url; + LLNotificationsUtil::add("AlertMerchantListingNotUpdated", subs); + } + } + +} + +void LLMarketplaceData::associateSLMListing(const LLUUID& folder_id, S32 listing_id, const LLUUID& version_id, const LLUUID& source_folder_id) +{ + setUpdating(folder_id, true); + setUpdating(source_folder_id, true); + LLCoros::instance().launch("associateSLMListingCoro", + boost::bind(&LLMarketplaceData::associateSLMListingCoro, this, folder_id, listing_id, version_id, source_folder_id)); +} + +void LLMarketplaceData::associateSLMListingCoro(LLUUID folderId, S32 listingId, LLUUID versionId, LLUUID sourceFolderId) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); + + httpHeaders->append("Accept", "application/json"); + httpHeaders->append("Content-Type", "application/json"); + + LLSD invInfo; + invInfo["listing_folder_id"] = folderId; + invInfo["version_folder_id"] = versionId; + LLSD listing; + listing["id"] = listingId; + listing["inventory_info"] = invInfo; + LLSD postData; + postData["listing"] = listing; + + // Send request + std::string url = getSLMConnectURL("/associate_inventory/") + llformat("%d", listingId); + + LLSD result = httpAdapter->putJsonAndSuspend(httpRequest, url, postData, httpHeaders); + + setUpdating(folderId, false); + setUpdating(sourceFolderId, false); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + log_SLM_warning("Put /associate_inventory", status.getType(), status.toString(), "", result); + update_marketplace_category(folderId, false); + update_marketplace_category(sourceFolderId, false); + gInventory.notifyObservers(); + return; + } + + log_SLM_infos("Put /associate_inventory", status.getType(), result); + + for (LLSD::array_iterator it = result["listings"].beginArray(); + it != result["listings"].endArray(); ++it) + { + LLSD listing = *it; + + int listing_id = listing["id"].asInteger(); + bool is_listed = listing["is_listed"].asBoolean(); + std::string edit_url = listing["edit_url"].asString(); + LLUUID folder_uuid = listing["inventory_info"]["listing_folder_id"].asUUID(); + LLUUID version_uuid = listing["inventory_info"]["version_folder_id"].asUUID(); + int count = listing["inventory_info"]["count_on_hand"].asInteger(); + + // Check that the listing ID is not already associated to some other record + LLUUID old_listing = LLMarketplaceData::instance().getListingFolder(listing_id); + if (old_listing.notNull()) + { + // If it is already used, unlist the old record (we can't have 2 listings with the same listing ID) + deleteListing(old_listing); + } + + // Add the new association + addListing(folder_uuid, listing_id, version_uuid, is_listed, edit_url, count); + update_marketplace_category(folder_uuid, false); + gInventory.notifyObservers(); + + // The stock count needs to be updated with the new local count now + updateCountOnHand(folder_uuid, 1); + } + + // Always update the source folder so its widget updates + update_marketplace_category(sourceFolderId, false); +} + +void LLMarketplaceData::deleteSLMListing(S32 listingId) +{ + LLCoros::instance().launch("deleteSLMListingCoro", + boost::bind(&LLMarketplaceData::deleteSLMListingCoro, this, listingId)); +} + +void LLMarketplaceData::deleteSLMListingCoro(S32 listingId) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); + + httpHeaders->append("Accept", "application/json"); + httpHeaders->append("Content-Type", "application/json"); + + std::string url = getSLMConnectURL("/listing/") + llformat("%d", listingId); + LLUUID folderId = getListingFolder(listingId); + + setUpdating(folderId, true); + + LLSD result = httpAdapter->deleteJsonAndSuspend(httpRequest, url, httpHeaders); + + setUpdating(folderId, false); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + log_SLM_warning("Delete /listing", status.getType(), status.toString(), "", result); + update_marketplace_category(folderId, false); + gInventory.notifyObservers(); + return; + } + + log_SLM_infos("Delete /listing", status.getType(), result); + + for (LLSD::array_iterator it = result["listings"].beginArray(); + it != result["listings"].endArray(); ++it) + { + LLSD listing = *it; + + int listing_id = listing["id"].asInteger(); + LLUUID folder_id = LLMarketplaceData::instance().getListingFolder(listing_id); + deleteListing(folder_id); + } + +} + +std::string LLMarketplaceData::getSLMConnectURL(const std::string& route) +{ + std::string url; + LLViewerRegion *regionp = gAgent.getRegion(); + if (regionp) + { + // Get DirectDelivery cap + url = regionp->getCapability("DirectDelivery"); + if (!url.empty()) + { + url += route; + } + } + return url; +} + +void LLMarketplaceData::setSLMStatus(U32 status) +{ + mMarketPlaceStatus = status; + mMarketPlaceFailureReason.clear(); + if (mStatusUpdatedSignal) + { + (*mStatusUpdatedSignal)(); + } +} + +void LLMarketplaceData::setSLMConnectionFailure(const std::string& reason) +{ + mMarketPlaceStatus = MarketplaceStatusCodes::MARKET_PLACE_CONNECTION_FAILURE; + mMarketPlaceFailureReason = reason; + if (mStatusUpdatedSignal) + { + (*mStatusUpdatedSignal)(); + } +} + +void LLMarketplaceData::setSLMDataFetched(U32 status) +{ + mMarketPlaceDataFetched = status; + if (mDataFetchedSignal) + { + (*mDataFetchedSignal)(); + } +} + +bool LLMarketplaceData::isSLMDataFetched() +{ + return mMarketPlaceDataFetched == MarketplaceFetchCodes::MARKET_FETCH_DONE; +} + +// Creation / Deletion / Update +// Methods publicly called +bool LLMarketplaceData::createListing(const LLUUID& folder_id) +{ + if (isListed(folder_id)) + { + // Listing already exists -> exit with error + return false; + } + + // Get the version folder: if there is only one subfolder, we will set it as a version folder immediately + S32 count = -1; + LLUUID version_id = getVersionFolderIfUnique(folder_id); + if (version_id.notNull()) + { + count = compute_stock_count(version_id, true); + } + + // Validate the count on hand + if (count == COMPUTE_STOCK_NOT_EVALUATED) + { + // If the count on hand cannot be evaluated, we will consider it empty (out of stock) at creation time + // It will get reevaluated and updated once the items are fetched + count = 0; + } + + // Post the listing creation request to SLM + createSLMListing(folder_id, version_id, count); + + return true; +} + +bool LLMarketplaceData::clearListing(const LLUUID& folder_id, S32 depth) +{ + if (folder_id.isNull()) + { + // Folder doesn't exists -> exit with error + return false; + } + + // Evaluate the depth if it wasn't passed as a parameter + if (depth < 0) + { + depth = depth_nesting_in_marketplace(folder_id); + + } + // Folder id can be the root of the listing or not so we need to retrieve the root first + LLUUID listing_uuid = (isListed(folder_id) ? folder_id : nested_parent_id(folder_id, depth)); + S32 listing_id = getListingID(listing_uuid); + + if (listing_id == 0) + { + // Listing doesn't exists -> exit with error + return false; + } + + // Update the SLM Server so that this listing is deleted (actually, archived...) + deleteSLMListing(listing_id); + + return true; +} + +bool LLMarketplaceData::getListing(const LLUUID& folder_id, S32 depth) +{ + if (folder_id.isNull()) + { + // Folder doesn't exists -> exit with error + return false; + } + + // Evaluate the depth if it wasn't passed as a parameter + if (depth < 0) + { + depth = depth_nesting_in_marketplace(folder_id); + + } + // Folder id can be the root of the listing or not so we need to retrieve the root first + LLUUID listing_uuid = (isListed(folder_id) ? folder_id : nested_parent_id(folder_id, depth)); + S32 listing_id = getListingID(listing_uuid); + + if (listing_id == 0) + { + // Listing doesn't exists -> exit with error + return false; + } + + // Get listing data from SLM + getSLMListing(listing_id); + + return true; +} + +bool LLMarketplaceData::getListing(S32 listing_id) +{ + if (listing_id == 0) + { + return false; + } + + // Get listing data from SLM + getSLMListing(listing_id); + return true; +} + +bool LLMarketplaceData::activateListing(const LLUUID& folder_id, bool activate, S32 depth) +{ + // Evaluate the depth if it wasn't passed as a parameter + if (depth < 0) + { + depth = depth_nesting_in_marketplace(folder_id); + + } + // Folder id can be the root of the listing or not so we need to retrieve the root first + LLUUID listing_uuid = nested_parent_id(folder_id, depth); + S32 listing_id = getListingID(listing_uuid); + if (listing_id == 0) + { + // Listing doesn't exists -> exit with error + return false; + } + + if (getActivationState(listing_uuid) == activate) + { + // If activation state is unchanged, no point spamming SLM with an update + return true; + } + + LLUUID version_uuid = getVersionFolder(listing_uuid); + + // Also update the count on hand + S32 count = compute_stock_count(folder_id); + if (count == COMPUTE_STOCK_NOT_EVALUATED) + { + // If the count on hand cannot be evaluated locally, we should not change that SLM value + // We are assuming that this issue is local and should not modify server side values + count = getCountOnHand(listing_uuid); + } + + // Post the listing update request to SLM + updateSLMListing(listing_uuid, listing_id, version_uuid, activate, count); + + return true; +} + +bool LLMarketplaceData::setVersionFolder(const LLUUID& folder_id, const LLUUID& version_id, S32 depth) +{ + // Evaluate the depth if it wasn't passed as a parameter + if (depth < 0) + { + depth = depth_nesting_in_marketplace(folder_id); + + } + // Folder id can be the root of the listing or not so we need to retrieve the root first + LLUUID listing_uuid = nested_parent_id(folder_id, depth); + S32 listing_id = getListingID(listing_uuid); + if (listing_id == 0) + { + // Listing doesn't exists -> exit with error + return false; + } + + if (getVersionFolder(listing_uuid) == version_id) + { + // If version folder is unchanged, no point spamming SLM with an update + return true; + } + + // Note: if the version_id is cleared, we need to unlist the listing, otherwise, state unchanged + bool is_listed = (version_id.isNull() ? false : getActivationState(listing_uuid)); + + // Also update the count on hand + S32 count = compute_stock_count(version_id); + if (count == COMPUTE_STOCK_NOT_EVALUATED) + { + // If the count on hand cannot be evaluated, we will consider it empty (out of stock) when resetting the version folder + // It will get reevaluated and updated once the items are fetched + count = 0; + } + + // Post the listing update request to SLM + updateSLMListing(listing_uuid, listing_id, version_id, is_listed, count); + + return true; +} + +bool LLMarketplaceData::updateCountOnHand(const LLUUID& folder_id, S32 depth) +{ + // Evaluate the depth if it wasn't passed as a parameter + if (depth < 0) + { + depth = depth_nesting_in_marketplace(folder_id); + + } + // Folder id can be the root of the listing or not so we need to retrieve the root first + LLUUID listing_uuid = nested_parent_id(folder_id, depth); + S32 listing_id = getListingID(listing_uuid); + if (listing_id == 0) + { + // Listing doesn't exists -> exit with error + return false; + } + + // Compute the new count on hand + S32 count = compute_stock_count(folder_id); + + if (count == getCountOnHand(listing_uuid)) + { + // If count on hand is unchanged, no point spamming SLM with an update + return true; + } + else if (count == COMPUTE_STOCK_NOT_EVALUATED) + { + // If local count on hand is not known at that point, do *not* force an update to SLM + return false; + } + + // Get the unchanged values + bool is_listed = getActivationState(listing_uuid); + LLUUID version_uuid = getVersionFolder(listing_uuid); + + + // Post the listing update request to SLM + updateSLMListing(listing_uuid, listing_id, version_uuid, is_listed, count); + + // Force the local value as it prevents spamming (count update may occur in burst when restocking) + // Note that if SLM has a good reason to return a different value, it'll be updated by the responder + setCountOnHand(listing_uuid, count, false); + + return true; +} + +bool LLMarketplaceData::associateListing(const LLUUID& folder_id, const LLUUID& source_folder_id, S32 listing_id) +{ + if (isListed(folder_id)) + { + // Listing already exists -> exit with error + return false; + } + + // Get the version folder: if there is only one subfolder, we will set it as a version folder immediately + LLUUID version_id = getVersionFolderIfUnique(folder_id); + + // Post the listing associate request to SLM + associateSLMListing(folder_id, listing_id, version_id, source_folder_id); + + return true; +} + +// Methods privately called or called by SLM responders to perform changes +bool LLMarketplaceData::addListing(const LLUUID& folder_id, S32 listing_id, const LLUUID& version_id, bool is_listed, const std::string& edit_url, S32 count) +{ + mMarketplaceItems[folder_id] = LLMarketplaceTuple(folder_id, listing_id, version_id, is_listed); + mMarketplaceItems[folder_id].mEditURL = edit_url; + mMarketplaceItems[folder_id].mCountOnHand = count; + if (version_id.notNull()) + { + mVersionFolders[version_id] = folder_id; + } + return true; +} + +bool LLMarketplaceData::deleteListing(const LLUUID& folder_id, bool update) +{ + LLUUID version_folder = getVersionFolder(folder_id); + + if (mMarketplaceItems.erase(folder_id) != 1) + { + return false; + } + mVersionFolders.erase(version_folder); + + if (update) + { + update_marketplace_category(folder_id, false); + gInventory.notifyObservers(); + } + return true; +} + +bool LLMarketplaceData::deleteListing(S32 listing_id, bool update) +{ + if (listing_id == 0) + { + return false; + } + + LLUUID folder_id = getListingFolder(listing_id); + return deleteListing(folder_id, update); +} + +// Accessors +bool LLMarketplaceData::getActivationState(const LLUUID& folder_id) +{ + // Listing folder case + marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); + if (it != mMarketplaceItems.end()) + { + return (it->second).mIsActive; + } + // Version folder case + version_folders_list_t::iterator it_version = mVersionFolders.find(folder_id); + if (it_version != mVersionFolders.end()) + { + marketplace_items_list_t::iterator it = mMarketplaceItems.find(it_version->second); + if (it != mMarketplaceItems.end()) + { + return (it->second).mIsActive; + } + } + return false; +} + +S32 LLMarketplaceData::getListingID(const LLUUID& folder_id) +{ + marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); + return (it == mMarketplaceItems.end() ? 0 : (it->second).mListingId); +} + +S32 LLMarketplaceData::getCountOnHand(const LLUUID& folder_id) +{ + marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); + return (it == mMarketplaceItems.end() ? -1 : (it->second).mCountOnHand); +} + +LLUUID LLMarketplaceData::getVersionFolder(const LLUUID& folder_id) +{ + marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); + return (it == mMarketplaceItems.end() ? LLUUID::null : (it->second).mVersionFolderId); +} + +// Reverse lookup : find the listing folder id from the listing id +LLUUID LLMarketplaceData::getListingFolder(S32 listing_id) +{ + marketplace_items_list_t::iterator it = mMarketplaceItems.begin(); + while (it != mMarketplaceItems.end()) + { + if ((it->second).mListingId == listing_id) + { + return (it->second).mListingFolderId; + } + it++; + } + return LLUUID::null; +} + +std::string LLMarketplaceData::getListingURL(const LLUUID& folder_id, S32 depth) +{ + // Evaluate the depth if it wasn't passed as a parameter + if (depth < 0) + { + depth = depth_nesting_in_marketplace(folder_id); + + } + + LLUUID listing_uuid = nested_parent_id(folder_id, depth); + + marketplace_items_list_t::iterator it = mMarketplaceItems.find(listing_uuid); + return (it == mMarketplaceItems.end() ? "" : (it->second).mEditURL); +} + +bool LLMarketplaceData::isListed(const LLUUID& folder_id) +{ + marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); + return (it != mMarketplaceItems.end()); +} + +bool LLMarketplaceData::isListedAndActive(const LLUUID& folder_id) +{ + return (isListed(folder_id) && getActivationState(folder_id)); +} + +bool LLMarketplaceData::isVersionFolder(const LLUUID& folder_id) +{ + version_folders_list_t::iterator it = mVersionFolders.find(folder_id); + return (it != mVersionFolders.end()); +} + +bool LLMarketplaceData::isInActiveFolder(const LLUUID& obj_id, S32 depth) +{ + // Evaluate the depth if it wasn't passed as a parameter + if (depth < 0) + { + depth = depth_nesting_in_marketplace(obj_id); + + } + + LLUUID listing_uuid = nested_parent_id(obj_id, depth); + bool active = getActivationState(listing_uuid); + LLUUID version_uuid = getVersionFolder(listing_uuid); + return (active && ((obj_id == version_uuid) || gInventory.isObjectDescendentOf(obj_id, version_uuid))); +} + +LLUUID LLMarketplaceData::getActiveFolder(const LLUUID& obj_id, S32 depth) +{ + // Evaluate the depth if it wasn't passed as a parameter + if (depth < 0) + { + depth = depth_nesting_in_marketplace(obj_id); + + } + + LLUUID listing_uuid = nested_parent_id(obj_id, depth); + return (getActivationState(listing_uuid) ? getVersionFolder(listing_uuid) : LLUUID::null); +} + +bool LLMarketplaceData::isUpdating(const LLUUID& folder_id, S32 depth) +{ + // Evaluate the depth if it wasn't passed as a parameter + if (depth < 0) + { + depth = depth_nesting_in_marketplace(folder_id); + } + if ((depth <= 0) || (depth > 2)) + { + // Only listing and version folders though are concerned by that status + return false; + } + else + { + const LLUUID marketplace_listings_uuid = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + std::set::iterator it = mPendingUpdateSet.find(marketplace_listings_uuid); + if (it != mPendingUpdateSet.end()) + { + // If we're waiting for data for the marketplace listings root, we are in the updating process for all + return true; + } + else + { + // Check if the listing folder is waiting or data + LLUUID listing_uuid = nested_parent_id(folder_id, depth); + it = mPendingUpdateSet.find(listing_uuid); + return (it != mPendingUpdateSet.end()); + } + } +} + +void LLMarketplaceData::setUpdating(const LLUUID& folder_id, bool isUpdating) +{ + std::set::iterator it = mPendingUpdateSet.find(folder_id); + if (it != mPendingUpdateSet.end()) + { + mPendingUpdateSet.erase(it); + } + if (isUpdating) + { + mPendingUpdateSet.insert(folder_id); + } +} + +void LLMarketplaceData::setValidationWaiting(const LLUUID& folder_id, S32 count) +{ + mValidationWaitingList[folder_id] = count; +} + +void LLMarketplaceData::decrementValidationWaiting(const LLUUID& folder_id, S32 count) +{ + waiting_list_t::iterator found = mValidationWaitingList.find(folder_id); + if (found != mValidationWaitingList.end()) + { + found->second -= count; + if (found->second <= 0) + { + mValidationWaitingList.erase(found); + LLMarketplaceValidator::getInstance()->validateMarketplaceListings(folder_id); + update_marketplace_category(folder_id); + gInventory.notifyObservers(); + } + } +} + +// Private Modifiers +bool LLMarketplaceData::setListingID(const LLUUID& folder_id, S32 listing_id, bool update) +{ + marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); + if (it == mMarketplaceItems.end()) + { + return false; + } + + (it->second).mListingId = listing_id; + + if (update) + { + update_marketplace_category(folder_id, false); + gInventory.notifyObservers(); + } + return true; +} + +bool LLMarketplaceData::setCountOnHand(const LLUUID& folder_id, S32 count, bool update) +{ + marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); + if (it == mMarketplaceItems.end()) + { + return false; + } + + (it->second).mCountOnHand = count; + + return true; +} + +bool LLMarketplaceData::setVersionFolderID(const LLUUID& folder_id, const LLUUID& version_id, bool update) +{ + marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); + if (it == mMarketplaceItems.end()) + { + return false; + } + + LLUUID old_version_id = (it->second).mVersionFolderId; + if (old_version_id == version_id) + { + return false; + } + + (it->second).mVersionFolderId = version_id; + mVersionFolders.erase(old_version_id); + if (version_id.notNull()) + { + mVersionFolders[version_id] = folder_id; + } + + if (update) + { + update_marketplace_category(old_version_id, false); + update_marketplace_category(version_id, false); + gInventory.notifyObservers(); + } + return true; +} + +bool LLMarketplaceData::setActivationState(const LLUUID& folder_id, bool activate, bool update) +{ + marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); + if (it == mMarketplaceItems.end()) + { + return false; + } + + (it->second).mIsActive = activate; + + if (update) + { + update_marketplace_category((it->second).mListingFolderId, false); + gInventory.notifyObservers(); + } + return true; +} + +bool LLMarketplaceData::setListingURL(const LLUUID& folder_id, const std::string& edit_url, bool update) +{ + marketplace_items_list_t::iterator it = mMarketplaceItems.find(folder_id); + if (it == mMarketplaceItems.end()) + { + return false; + } + + (it->second).mEditURL = edit_url; + return true; +} + diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp index 10f4e2440b..7b0e8d0e89 100644 --- a/indra/newview/llmaterialeditor.cpp +++ b/indra/newview/llmaterialeditor.cpp @@ -1,3770 +1,3770 @@ -/** - * @file llmaterialeditor.cpp - * @brief Implementation of the gltf material editor - * - * $LicenseInfo:firstyear=2022&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llmaterialeditor.h" - -#include "llagent.h" -#include "llagentbenefits.h" -#include "llappviewer.h" -#include "llcolorswatch.h" -#include "llcombobox.h" -#include "llfloaterreg.h" -#include "llfilesystem.h" -#include "llgltfmateriallist.h" -#include "llinventorymodel.h" -#include "llinventoryobserver.h" -#include "llinventoryfunctions.h" -#include "lllocalgltfmaterials.h" -#include "llnotificationsutil.h" -#include "lltexturectrl.h" -#include "lltrans.h" -#include "llviewercontrol.h" -#include "llviewermenufile.h" -#include "llviewertexture.h" -#include "llsdutil.h" -#include "llselectmgr.h" -#include "llstatusbar.h" // can_afford_transaction() -#include "lltoolpie.h" -#include "llviewerinventory.h" -#include "llinventory.h" -#include "llviewerregion.h" -#include "llvovolume.h" -#include "roles_constants.h" -#include "llviewerobjectlist.h" -#include "llsdserialize.h" -#include "llimagej2c.h" -#include "llviewertexturelist.h" -#include "llfloaterperms.h" - -#include "tinygltf/tiny_gltf.h" -#include "lltinygltfhelper.h" -#include - - -const std::string MATERIAL_BASE_COLOR_DEFAULT_NAME = "Base Color"; -const std::string MATERIAL_NORMAL_DEFAULT_NAME = "Normal"; -const std::string MATERIAL_METALLIC_DEFAULT_NAME = "Metallic Roughness"; -const std::string MATERIAL_EMISSIVE_DEFAULT_NAME = "Emissive"; - -// Dirty flags -static const U32 MATERIAL_BASE_COLOR_DIRTY = 0x1 << 0; -static const U32 MATERIAL_BASE_COLOR_TEX_DIRTY = 0x1 << 1; - -static const U32 MATERIAL_NORMAL_TEX_DIRTY = 0x1 << 2; - -static const U32 MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY = 0x1 << 3; -static const U32 MATERIAL_METALLIC_ROUGHTNESS_METALNESS_DIRTY = 0x1 << 4; -static const U32 MATERIAL_METALLIC_ROUGHTNESS_ROUGHNESS_DIRTY = 0x1 << 5; - -static const U32 MATERIAL_EMISIVE_COLOR_DIRTY = 0x1 << 6; -static const U32 MATERIAL_EMISIVE_TEX_DIRTY = 0x1 << 7; - -static const U32 MATERIAL_DOUBLE_SIDED_DIRTY = 0x1 << 8; -static const U32 MATERIAL_ALPHA_MODE_DIRTY = 0x1 << 9; -static const U32 MATERIAL_ALPHA_CUTOFF_DIRTY = 0x1 << 10; - -LLUUID LLMaterialEditor::mOverrideObjectId; -S32 LLMaterialEditor::mOverrideObjectTE = -1; -bool LLMaterialEditor::mOverrideInProgress = false; -bool LLMaterialEditor::mSelectionNeedsUpdate = true; - -LLFloaterComboOptions::LLFloaterComboOptions() - : LLFloater(LLSD()) -{ - buildFromFile("floater_combobox_ok_cancel.xml"); -} - -LLFloaterComboOptions::~LLFloaterComboOptions() -{ - -} - -bool LLFloaterComboOptions::postBuild() -{ - mConfirmButton = getChild("combo_ok", true); - mCancelButton = getChild("combo_cancel", true); - mComboOptions = getChild("combo_options", true); - mComboText = getChild("combo_text", true); - - mConfirmButton->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) {onConfirm(); }); - mCancelButton->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) {onCancel(); }); - - return true; -} - -LLFloaterComboOptions* LLFloaterComboOptions::showUI( - combo_callback callback, - const std::string &title, - const std::string &description, - const std::list &options) -{ - LLFloaterComboOptions* combo_picker = new LLFloaterComboOptions(); - if (combo_picker) - { - combo_picker->mCallback = callback; - combo_picker->setTitle(title); - - combo_picker->mComboText->setText(description); - - std::list::const_iterator iter = options.begin(); - std::list::const_iterator end = options.end(); - for (; iter != end; iter++) - { - combo_picker->mComboOptions->addSimpleElement(*iter); - } - combo_picker->mComboOptions->selectFirstItem(); - - combo_picker->openFloater(LLSD(title)); - combo_picker->setFocus(true); - combo_picker->center(); - } - return combo_picker; -} - -LLFloaterComboOptions* LLFloaterComboOptions::showUI( - combo_callback callback, - const std::string &title, - const std::string &description, - const std::string &ok_text, - const std::string &cancel_text, - const std::list &options) -{ - LLFloaterComboOptions* combo_picker = showUI(callback, title, description, options); - if (combo_picker) - { - combo_picker->mConfirmButton->setLabel(ok_text); - combo_picker->mCancelButton->setLabel(cancel_text); - } - return combo_picker; -} - -void LLFloaterComboOptions::onConfirm() -{ - mCallback(mComboOptions->getSimple(), mComboOptions->getCurrentIndex()); - closeFloater(); -} - -void LLFloaterComboOptions::onCancel() -{ - mCallback(std::string(), -1); - closeFloater(); -} - -class LLMaterialEditorCopiedCallback : public LLInventoryCallback -{ -public: - LLMaterialEditorCopiedCallback( - const std::string &buffer, - const LLSD &old_key, - bool has_unsaved_changes) - : mBuffer(buffer), - mOldKey(old_key), - mHasUnsavedChanges(has_unsaved_changes) - {} - - LLMaterialEditorCopiedCallback( - const LLSD &old_key, - const std::string &new_name) - : mOldKey(old_key), - mNewName(new_name), - mHasUnsavedChanges(false) - {} - - virtual void fire(const LLUUID& inv_item_id) - { - if (!mNewName.empty()) - { - // making a copy from a notecard doesn't change name, do it now - LLViewerInventoryItem* item = gInventory.getItem(inv_item_id); - if (item->getName() != mNewName) - { - LLSD updates; - updates["name"] = mNewName; - update_inventory_item(inv_item_id, updates, NULL); - } - } - LLMaterialEditor::finishSaveAs(mOldKey, inv_item_id, mBuffer, mHasUnsavedChanges); - } - -private: - std::string mBuffer; - LLSD mOldKey; - std::string mNewName; - bool mHasUnsavedChanges; -}; - -///---------------------------------------------------------------------------- -/// Class LLSelectedTEGetMatData -/// For finding selected applicable inworld material -///---------------------------------------------------------------------------- - -struct LLSelectedTEGetMatData : public LLSelectedTEFunctor -{ - LLSelectedTEGetMatData(bool for_override); - - bool apply(LLViewerObject* objectp, S32 te_index); - - bool mIsOverride; - bool mIdenticalTexColor; - bool mIdenticalTexMetal; - bool mIdenticalTexEmissive; - bool mIdenticalTexNormal; - bool mFirst; - LLUUID mTexColorId; - LLUUID mTexMetalId; - LLUUID mTexEmissiveId; - LLUUID mTexNormalId; - LLUUID mObjectId; - LLViewerObject* mObject = nullptr; - S32 mObjectTE; - LLUUID mMaterialId; - LLPointer mMaterial; - LLPointer mLocalMaterial; -}; - -LLSelectedTEGetMatData::LLSelectedTEGetMatData(bool for_override) - : mIsOverride(for_override) - , mIdenticalTexColor(true) - , mIdenticalTexMetal(true) - , mIdenticalTexEmissive(true) - , mIdenticalTexNormal(true) - , mObjectTE(-1) - , mFirst(true) -{} - -bool LLSelectedTEGetMatData::apply(LLViewerObject* objectp, S32 te_index) -{ - if (!objectp) - { - return false; - } - LLUUID mat_id = objectp->getRenderMaterialID(te_index); - mMaterialId = mat_id; - bool can_use = mIsOverride ? objectp->permModify() : objectp->permCopy(); - LLTextureEntry *tep = objectp->getTE(te_index); - // We might want to disable this entirely if at least - // something in selection is no-copy or no modify - // or has no base material - if (can_use && tep && mat_id.notNull()) - { - if (mIsOverride) - { - LLPointer mat = tep->getGLTFRenderMaterial(); - - LLUUID tex_color_id; - LLUUID tex_metal_id; - LLUUID tex_emissive_id; - LLUUID tex_normal_id; - llassert(mat.notNull()); // by this point shouldn't be null - if (mat.notNull()) - { - tex_color_id = mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR]; - tex_metal_id = mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS]; - tex_emissive_id = mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE]; - tex_normal_id = mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL]; - } - if (mFirst) - { - mMaterial = mat; - mTexColorId = tex_color_id; - mTexMetalId = tex_metal_id; - mTexEmissiveId = tex_emissive_id; - mTexNormalId = tex_normal_id; - mObjectTE = te_index; - mObject = objectp; - mObjectId = objectp->getID(); - mFirst = false; - } - else - { - if (mTexColorId != tex_color_id) - { - mIdenticalTexColor = false; - } - if (mTexMetalId != tex_metal_id) - { - mIdenticalTexMetal = false; - } - if (mTexEmissiveId != tex_emissive_id) - { - mIdenticalTexEmissive = false; - } - if (mTexNormalId != tex_normal_id) - { - mIdenticalTexNormal = false; - } - } - } - else - { - LLGLTFMaterial *mat = tep->getGLTFMaterial(); - LLLocalGLTFMaterial *local_mat = dynamic_cast(mat); - - mObject = objectp; - mObjectId = objectp->getID(); - if (local_mat) - { - mLocalMaterial = local_mat; - } - mMaterial = tep->getGLTFRenderMaterial(); - - if (mMaterial.isNull()) - { - // Shouldn't be possible? - LL_WARNS("MaterialEditor") << "Object has material id, but no material" << LL_ENDL; - mMaterial = gGLTFMaterialList.getMaterial(mat_id); - } - } - return true; - } - return false; -} - -class LLSelectedTEUpdateOverrides: public LLSelectedNodeFunctor -{ -public: - LLSelectedTEUpdateOverrides(LLMaterialEditor* me) : mEditor(me) {} - - virtual bool apply(LLSelectNode* nodep); - - LLMaterialEditor* mEditor; -}; - -bool LLSelectedTEUpdateOverrides::apply(LLSelectNode* nodep) -{ - LLViewerObject* objectp = nodep->getObject(); - if (!objectp) - { - return false; - } - S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); // avatars have TEs but no faces - for (S32 te_index = 0; te_index < num_tes; ++te_index) - { - - LLTextureEntry* tep = objectp->getTE(te_index); - LLGLTFMaterial* override_mat = tep->getGLTFMaterialOverride(); - if (mEditor->updateMaterialLocalSubscription(override_mat)) - { - LLGLTFMaterial* render_mat = tep->getGLTFRenderMaterial(); - mEditor->updateMaterialLocalSubscription(render_mat); - } - } - - return true; -} - -///---------------------------------------------------------------------------- -/// Class LLMaterialEditor -///---------------------------------------------------------------------------- - -// Default constructor -LLMaterialEditor::LLMaterialEditor(const LLSD& key) - : LLPreview(key) - , mUnsavedChanges(0) - , mRevertedChanges(0) - , mExpectedUploadCost(0) - , mUploadingTexturesCount(0) - , mUploadingTexturesFailure(false) -{ - const LLInventoryItem* item = getItem(); - if (item) - { - mAssetID = item->getAssetUUID(); - } -} - -LLMaterialEditor::~LLMaterialEditor() -{ -} - -void LLMaterialEditor::setObjectID(const LLUUID& object_id) -{ - LLPreview::setObjectID(object_id); - const LLInventoryItem* item = getItem(); - if (item) - { - mAssetID = item->getAssetUUID(); - } -} - -void LLMaterialEditor::setAuxItem(const LLInventoryItem* item) -{ - LLPreview::setAuxItem(item); - if (item) - { - mAssetID = item->getAssetUUID(); - } -} - -bool LLMaterialEditor::postBuild() -{ - // if this is a 'live editor' instance, it is also - // single instance and uses live overrides - mIsOverride = getIsSingleInstance(); - - mBaseColorTextureCtrl = getChild("base_color_texture"); - mMetallicTextureCtrl = getChild("metallic_roughness_texture"); - mEmissiveTextureCtrl = getChild("emissive_texture"); - mNormalTextureCtrl = getChild("normal_texture"); - mBaseColorCtrl = getChild("base color"); - mEmissiveColorCtrl = getChild("emissive color"); - - if (!gAgent.isGodlike()) - { - // Only allow fully permissive textures - mBaseColorTextureCtrl->setFilterPermissionMasks(PERM_COPY | PERM_TRANSFER); - mMetallicTextureCtrl->setFilterPermissionMasks(PERM_COPY | PERM_TRANSFER); - mEmissiveTextureCtrl->setFilterPermissionMasks(PERM_COPY | PERM_TRANSFER); - mNormalTextureCtrl->setFilterPermissionMasks(PERM_COPY | PERM_TRANSFER); - } - - // Texture callback - mBaseColorTextureCtrl->setCommitCallback(boost::bind(&LLMaterialEditor::onCommitTexture, this, _1, _2, MATERIAL_BASE_COLOR_TEX_DIRTY)); - mMetallicTextureCtrl->setCommitCallback(boost::bind(&LLMaterialEditor::onCommitTexture, this, _1, _2, MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY)); - mEmissiveTextureCtrl->setCommitCallback(boost::bind(&LLMaterialEditor::onCommitTexture, this, _1, _2, MATERIAL_EMISIVE_TEX_DIRTY)); - mNormalTextureCtrl->setCommitCallback(boost::bind(&LLMaterialEditor::onCommitTexture, this, _1, _2, MATERIAL_NORMAL_TEX_DIRTY)); - - mNormalTextureCtrl->setBlankImageAssetID(BLANK_OBJECT_NORMAL); - - if (mIsOverride) - { - // Live editing needs a recovery mechanism on cancel - mBaseColorTextureCtrl->setOnCancelCallback(boost::bind(&LLMaterialEditor::onCancelCtrl, this, _1, _2, MATERIAL_BASE_COLOR_TEX_DIRTY)); - mMetallicTextureCtrl->setOnCancelCallback(boost::bind(&LLMaterialEditor::onCancelCtrl, this, _1, _2, MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY)); - mEmissiveTextureCtrl->setOnCancelCallback(boost::bind(&LLMaterialEditor::onCancelCtrl, this, _1, _2, MATERIAL_EMISIVE_TEX_DIRTY)); - mNormalTextureCtrl->setOnCancelCallback(boost::bind(&LLMaterialEditor::onCancelCtrl, this, _1, _2, MATERIAL_NORMAL_TEX_DIRTY)); - - // Save applied changes on 'OK' to our recovery mechanism. - mBaseColorTextureCtrl->setOnSelectCallback(boost::bind(&LLMaterialEditor::onSelectCtrl, this, _1, _2, MATERIAL_BASE_COLOR_TEX_DIRTY)); - mMetallicTextureCtrl->setOnSelectCallback(boost::bind(&LLMaterialEditor::onSelectCtrl, this, _1, _2, MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY)); - mEmissiveTextureCtrl->setOnSelectCallback(boost::bind(&LLMaterialEditor::onSelectCtrl, this, _1, _2, MATERIAL_EMISIVE_TEX_DIRTY)); - mNormalTextureCtrl->setOnSelectCallback(boost::bind(&LLMaterialEditor::onSelectCtrl, this, _1, _2, MATERIAL_NORMAL_TEX_DIRTY)); - } - else - { - mBaseColorTextureCtrl->setCanApplyImmediately(false); - mMetallicTextureCtrl->setCanApplyImmediately(false); - mEmissiveTextureCtrl->setCanApplyImmediately(false); - mNormalTextureCtrl->setCanApplyImmediately(false); - } - - if (!mIsOverride) - { - childSetAction("save", boost::bind(&LLMaterialEditor::onClickSave, this)); - childSetAction("save_as", boost::bind(&LLMaterialEditor::onClickSaveAs, this)); - childSetAction("cancel", boost::bind(&LLMaterialEditor::onClickCancel, this)); - } - - if (mIsOverride) - { - childSetVisible("base_color_upload_fee", false); - childSetVisible("metallic_upload_fee", false); - childSetVisible("emissive_upload_fee", false); - childSetVisible("normal_upload_fee", false); - } - else - { - S32 upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost(); - getChild("base_color_upload_fee")->setTextArg("[FEE]", llformat("%d", upload_cost)); - getChild("metallic_upload_fee")->setTextArg("[FEE]", llformat("%d", upload_cost)); - getChild("emissive_upload_fee")->setTextArg("[FEE]", llformat("%d", upload_cost)); - getChild("normal_upload_fee")->setTextArg("[FEE]", llformat("%d", upload_cost)); - } - - boost::function changes_callback = [this](LLUICtrl * ctrl, void* userData) - { - const U32 *flag = (const U32*)userData; - markChangesUnsaved(*flag); - // Apply changes to object live - applyToSelection(); - }; - - childSetCommitCallback("double sided", changes_callback, (void*)&MATERIAL_DOUBLE_SIDED_DIRTY); - - // BaseColor - mBaseColorCtrl->setCommitCallback(changes_callback, (void*)&MATERIAL_BASE_COLOR_DIRTY); - if (mIsOverride) - { - mBaseColorCtrl->setOnCancelCallback(boost::bind(&LLMaterialEditor::onCancelCtrl, this, _1, _2, MATERIAL_BASE_COLOR_DIRTY)); - mBaseColorCtrl->setOnSelectCallback(boost::bind(&LLMaterialEditor::onSelectCtrl, this, _1, _2, MATERIAL_BASE_COLOR_DIRTY)); - } - else - { - mBaseColorCtrl->setCanApplyImmediately(false); - } - // transparency is a part of base color - childSetCommitCallback("transparency", changes_callback, (void*)&MATERIAL_BASE_COLOR_DIRTY); - childSetCommitCallback("alpha mode", changes_callback, (void*)&MATERIAL_ALPHA_MODE_DIRTY); - childSetCommitCallback("alpha cutoff", changes_callback, (void*)&MATERIAL_ALPHA_CUTOFF_DIRTY); - - // Metallic-Roughness - childSetCommitCallback("metalness factor", changes_callback, (void*)&MATERIAL_METALLIC_ROUGHTNESS_METALNESS_DIRTY); - childSetCommitCallback("roughness factor", changes_callback, (void*)&MATERIAL_METALLIC_ROUGHTNESS_ROUGHNESS_DIRTY); - - // Emissive - mEmissiveColorCtrl->setCommitCallback(changes_callback, (void*)&MATERIAL_EMISIVE_COLOR_DIRTY); - if (mIsOverride) - { - mEmissiveColorCtrl->setOnCancelCallback(boost::bind(&LLMaterialEditor::onCancelCtrl, this, _1, _2, MATERIAL_EMISIVE_COLOR_DIRTY)); - mEmissiveColorCtrl->setOnSelectCallback(boost::bind(&LLMaterialEditor::onSelectCtrl, this, _1, _2, MATERIAL_EMISIVE_COLOR_DIRTY)); - } - else - { - mEmissiveColorCtrl->setCanApplyImmediately(false); - } - - if (!mIsOverride) - { - // "unsaved_changes" doesn't exist in live editor - childSetVisible("unsaved_changes", mUnsavedChanges); - - // Doesn't exist in live editor - getChild("total_upload_fee")->setTextArg("[FEE]", llformat("%d", 0)); - } - - // Todo: - // Disable/enable setCanApplyImmediately() based on - // working from inventory, upload or editing inworld - - return LLPreview::postBuild(); -} - -void LLMaterialEditor::onClickCloseBtn(bool app_quitting) -{ - if (app_quitting || mIsOverride) - { - closeFloater(app_quitting); - } - else - { - onClickCancel(); - } -} - -void LLMaterialEditor::onClose(bool app_quitting) -{ - if (mSelectionUpdateSlot.connected()) - { - mSelectionUpdateSlot.disconnect(); - } - for (mat_connection_map_t::value_type &cn : mTextureChangesUpdates) - { - cn.second.mConnection.disconnect(); - } - mTextureChangesUpdates.clear(); - - LLPreview::onClose(app_quitting); -} - -void LLMaterialEditor::draw() -{ - if (mIsOverride) - { - if (mSelectionNeedsUpdate) - { - mSelectionNeedsUpdate = false; - clearTextures(); - setFromSelection(); - } - } - LLPreview::draw(); -} - -void LLMaterialEditor::handleReshape(const LLRect& new_rect, bool by_user) -{ - if (by_user) - { - const LLRect old_rect = getRect(); - LLRect clamp_rect(new_rect); - clamp_rect.mRight = clamp_rect.mLeft + old_rect.getWidth(); - LLPreview::handleReshape(clamp_rect, by_user); - } - else - { - LLPreview::handleReshape(new_rect, by_user); - } -} - -LLUUID LLMaterialEditor::getBaseColorId() -{ - return mBaseColorTextureCtrl->getValue().asUUID(); -} - -void LLMaterialEditor::setBaseColorId(const LLUUID& id) -{ - mBaseColorTextureCtrl->setValue(id); - mBaseColorTextureCtrl->setDefaultImageAssetID(id); - mBaseColorTextureCtrl->setTentative(false); -} - -void LLMaterialEditor::setBaseColorUploadId(const LLUUID& id) -{ - // Might be better to use local textures and - // assign a fee in case of a local texture - if (id.notNull()) - { - // todo: this does not account for posibility of texture - // being from inventory, need to check that - childSetValue("base_color_upload_fee", getString("upload_fee_string")); - // Only set if we will need to upload this texture - mBaseColorTextureUploadId = id; - } - markChangesUnsaved(MATERIAL_BASE_COLOR_TEX_DIRTY); -} - -LLColor4 LLMaterialEditor::getBaseColor() -{ - LLColor4 ret = linearColor4(LLColor4(mBaseColorCtrl->getValue())); - ret.mV[3] = getTransparency(); - return ret; -} - -void LLMaterialEditor::setBaseColor(const LLColor4& color) -{ - mBaseColorCtrl->setValue(srgbColor4(color).getValue()); - setTransparency(color.mV[3]); -} - -F32 LLMaterialEditor::getTransparency() -{ - return childGetValue("transparency").asReal(); -} - -void LLMaterialEditor::setTransparency(F32 transparency) -{ - childSetValue("transparency", transparency); -} - -std::string LLMaterialEditor::getAlphaMode() -{ - return childGetValue("alpha mode").asString(); -} - -void LLMaterialEditor::setAlphaMode(const std::string& alpha_mode) -{ - childSetValue("alpha mode", alpha_mode); -} - -F32 LLMaterialEditor::getAlphaCutoff() -{ - return childGetValue("alpha cutoff").asReal(); -} - -void LLMaterialEditor::setAlphaCutoff(F32 alpha_cutoff) -{ - childSetValue("alpha cutoff", alpha_cutoff); -} - -void LLMaterialEditor::setMaterialName(const std::string &name) -{ - setTitle(name); - mMaterialName = name; -} - -LLUUID LLMaterialEditor::getMetallicRoughnessId() -{ - return mMetallicTextureCtrl->getValue().asUUID(); -} - -void LLMaterialEditor::setMetallicRoughnessId(const LLUUID& id) -{ - mMetallicTextureCtrl->setValue(id); - mMetallicTextureCtrl->setDefaultImageAssetID(id); - mMetallicTextureCtrl->setTentative(false); -} - -void LLMaterialEditor::setMetallicRoughnessUploadId(const LLUUID& id) -{ - if (id.notNull()) - { - // todo: this does not account for posibility of texture - // being from inventory, need to check that - childSetValue("metallic_upload_fee", getString("upload_fee_string")); - mMetallicTextureUploadId = id; - } - markChangesUnsaved(MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY); -} - -F32 LLMaterialEditor::getMetalnessFactor() -{ - return childGetValue("metalness factor").asReal(); -} - -void LLMaterialEditor::setMetalnessFactor(F32 factor) -{ - childSetValue("metalness factor", factor); -} - -F32 LLMaterialEditor::getRoughnessFactor() -{ - return childGetValue("roughness factor").asReal(); -} - -void LLMaterialEditor::setRoughnessFactor(F32 factor) -{ - childSetValue("roughness factor", factor); -} - -LLUUID LLMaterialEditor::getEmissiveId() -{ - return mEmissiveTextureCtrl->getValue().asUUID(); -} - -void LLMaterialEditor::setEmissiveId(const LLUUID& id) -{ - mEmissiveTextureCtrl->setValue(id); - mEmissiveTextureCtrl->setDefaultImageAssetID(id); - mEmissiveTextureCtrl->setTentative(false); -} - -void LLMaterialEditor::setEmissiveUploadId(const LLUUID& id) -{ - if (id.notNull()) - { - // todo: this does not account for posibility of texture - // being from inventory, need to check that - childSetValue("emissive_upload_fee", getString("upload_fee_string")); - mEmissiveTextureUploadId = id; - } - markChangesUnsaved(MATERIAL_EMISIVE_TEX_DIRTY); -} - -LLColor4 LLMaterialEditor::getEmissiveColor() -{ - return linearColor4(LLColor4(mEmissiveColorCtrl->getValue())); -} - -void LLMaterialEditor::setEmissiveColor(const LLColor4& color) -{ - mEmissiveColorCtrl->setValue(srgbColor4(color).getValue()); -} - -LLUUID LLMaterialEditor::getNormalId() -{ - return mNormalTextureCtrl->getValue().asUUID(); -} - -void LLMaterialEditor::setNormalId(const LLUUID& id) -{ - mNormalTextureCtrl->setValue(id); - mNormalTextureCtrl->setDefaultImageAssetID(id); - mNormalTextureCtrl->setTentative(false); -} - -void LLMaterialEditor::setNormalUploadId(const LLUUID& id) -{ - if (id.notNull()) - { - // todo: this does not account for posibility of texture - // being from inventory, need to check that - childSetValue("normal_upload_fee", getString("upload_fee_string")); - mNormalTextureUploadId = id; - } - markChangesUnsaved(MATERIAL_NORMAL_TEX_DIRTY); -} - -bool LLMaterialEditor::getDoubleSided() -{ - return childGetValue("double sided").asBoolean(); -} - -void LLMaterialEditor::setDoubleSided(bool double_sided) -{ - childSetValue("double sided", double_sided); -} - -void LLMaterialEditor::resetUnsavedChanges() -{ - mUnsavedChanges = 0; - mRevertedChanges = 0; - if (!mIsOverride) - { - childSetVisible("unsaved_changes", false); - setCanSave(false); - - mExpectedUploadCost = 0; - getChild("total_upload_fee")->setTextArg("[FEE]", llformat("%d", mExpectedUploadCost)); - } -} - -void LLMaterialEditor::markChangesUnsaved(U32 dirty_flag) -{ - mUnsavedChanges |= dirty_flag; - if (mIsOverride) - { - // at the moment live editing (mIsOverride) applies everything 'live' - // and "unsaved_changes", save/cancel buttons don't exist there - return; - } - - childSetVisible("unsaved_changes", mUnsavedChanges); - - if (mUnsavedChanges) - { - const LLInventoryItem* item = getItem(); - if (item) - { - //LLPermissions perm(item->getPermissions()); - bool allow_modify = canModify(mObjectUUID, item); - bool source_library = mObjectUUID.isNull() && gInventory.isObjectDescendentOf(mItemUUID, gInventory.getLibraryRootFolderID()); - bool source_notecard = mNotecardInventoryID.notNull(); - - setCanSave(allow_modify && !source_library && !source_notecard); - } - } - else - { - setCanSave(false); - } - - S32 upload_texture_count = 0; - if (mBaseColorTextureUploadId.notNull() && mBaseColorTextureUploadId == getBaseColorId()) - { - upload_texture_count++; - } - if (mMetallicTextureUploadId.notNull() && mMetallicTextureUploadId == getMetallicRoughnessId()) - { - upload_texture_count++; - } - if (mEmissiveTextureUploadId.notNull() && mEmissiveTextureUploadId == getEmissiveId()) - { - upload_texture_count++; - } - if (mNormalTextureUploadId.notNull() && mNormalTextureUploadId == getNormalId()) - { - upload_texture_count++; - } - - mExpectedUploadCost = upload_texture_count * LLAgentBenefitsMgr::current().getTextureUploadCost(); - getChild("total_upload_fee")->setTextArg("[FEE]", llformat("%d", mExpectedUploadCost)); -} - -void LLMaterialEditor::setCanSaveAs(bool value) -{ - if (!mIsOverride) - { - childSetEnabled("save_as", value); - } -} - -void LLMaterialEditor::setCanSave(bool value) -{ - if (!mIsOverride) - { - childSetEnabled("save", value); - } -} - -void LLMaterialEditor::setEnableEditing(bool can_modify) -{ - childSetEnabled("double sided", can_modify); - - // BaseColor - childSetEnabled("base color", can_modify); - childSetEnabled("transparency", can_modify); - childSetEnabled("alpha mode", can_modify); - childSetEnabled("alpha cutoff", can_modify); - - // Metallic-Roughness - childSetEnabled("metalness factor", can_modify); - childSetEnabled("roughness factor", can_modify); - - // Metallic-Roughness - childSetEnabled("metalness factor", can_modify); - childSetEnabled("roughness factor", can_modify); - - // Emissive - childSetEnabled("emissive color", can_modify); - - mBaseColorTextureCtrl->setEnabled(can_modify); - mMetallicTextureCtrl->setEnabled(can_modify); - mEmissiveTextureCtrl->setEnabled(can_modify); - mNormalTextureCtrl->setEnabled(can_modify); -} - -void LLMaterialEditor::subscribeToLocalTexture(S32 dirty_flag, const LLUUID& tracking_id) -{ - if (mTextureChangesUpdates[dirty_flag].mTrackingId != tracking_id) - { - mTextureChangesUpdates[dirty_flag].mConnection.disconnect(); - mTextureChangesUpdates[dirty_flag].mTrackingId = tracking_id; - mTextureChangesUpdates[dirty_flag].mConnection = LLLocalBitmapMgr::getInstance()->setOnChangedCallback(tracking_id, - [this, dirty_flag](const LLUUID& tracking_id, const LLUUID& old_id, const LLUUID& new_id) - { - if (new_id.isNull()) - { - mTextureChangesUpdates[dirty_flag].mConnection.disconnect(); - //mTextureChangesUpdates.erase(dirty_flag); - } - else - { - replaceLocalTexture(old_id, new_id); - } - }); - } -} - -LLUUID LLMaterialEditor::getLocalTextureTrackingIdFromFlag(U32 flag) -{ - mat_connection_map_t::iterator found = mTextureChangesUpdates.find(flag); - if (found != mTextureChangesUpdates.end()) - { - return found->second.mTrackingId; - } - return LLUUID(); -} - -bool LLMaterialEditor::updateMaterialLocalSubscription(LLGLTFMaterial* mat) -{ - if (!mat) - { - return false; - } - - bool res = false; - for (mat_connection_map_t::value_type& cn : mTextureChangesUpdates) - { - LLUUID world_id = LLLocalBitmapMgr::getInstance()->getWorldID(cn.second.mTrackingId); - if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR]) - { - LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(cn.second.mTrackingId, mat); - res = true; - continue; - } - if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS]) - { - LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(cn.second.mTrackingId, mat); - res = true; - continue; - } - if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE]) - { - LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(cn.second.mTrackingId, mat); - res = true; - continue; - } - if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL]) - { - LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(cn.second.mTrackingId, mat); - res = true; - continue; - } - } - return res; -} - -void LLMaterialEditor::replaceLocalTexture(const LLUUID& old_id, const LLUUID& new_id) -{ - // todo: might be a good idea to set mBaseColorTextureUploadId here - // and when texturectrl picks a local texture - if (getBaseColorId() == old_id) - { - mBaseColorTextureCtrl->setValue(new_id); - } - if (mBaseColorTextureCtrl->getDefaultImageAssetID() == old_id) - { - mBaseColorTextureCtrl->setDefaultImageAssetID(new_id); - } - - if (getMetallicRoughnessId() == old_id) - { - mMetallicTextureCtrl->setValue(new_id); - } - if (mMetallicTextureCtrl->getDefaultImageAssetID() == old_id) - { - mMetallicTextureCtrl->setDefaultImageAssetID(new_id); - } - - if (getEmissiveId() == old_id) - { - mEmissiveTextureCtrl->setValue(new_id); - } - if (mEmissiveTextureCtrl->getDefaultImageAssetID() == old_id) - { - mEmissiveTextureCtrl->setDefaultImageAssetID(new_id); - } - - if (getNormalId() == old_id) - { - mNormalTextureCtrl->setValue(new_id); - } - if (mNormalTextureCtrl->getDefaultImageAssetID() == old_id) - { - mNormalTextureCtrl->setDefaultImageAssetID(new_id); - } -} - -void LLMaterialEditor::onCommitTexture(LLUICtrl* ctrl, const LLSD& data, S32 dirty_flag) -{ - if (!mIsOverride) - { - std::string upload_fee_ctrl_name; - LLUUID old_uuid; - - switch (dirty_flag) - { - case MATERIAL_BASE_COLOR_TEX_DIRTY: - { - upload_fee_ctrl_name = "base_color_upload_fee"; - old_uuid = mBaseColorTextureUploadId; - break; - } - case MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY: - { - upload_fee_ctrl_name = "metallic_upload_fee"; - old_uuid = mMetallicTextureUploadId; - break; - } - case MATERIAL_EMISIVE_TEX_DIRTY: - { - upload_fee_ctrl_name = "emissive_upload_fee"; - old_uuid = mEmissiveTextureUploadId; - break; - } - case MATERIAL_NORMAL_TEX_DIRTY: - { - upload_fee_ctrl_name = "normal_upload_fee"; - old_uuid = mNormalTextureUploadId; - break; - } - default: - break; - } - LLUUID new_val = ctrl->getValue().asUUID(); - if (new_val == old_uuid && old_uuid.notNull()) - { - childSetValue(upload_fee_ctrl_name, getString("upload_fee_string")); - } - else - { - // Texture picker has 'apply now' with 'cancel' support. - // Don't clean mBaseColorJ2C and mBaseColorFetched, it's our - // storage in case user decides to cancel changes. - // Without mBaseColorFetched, viewer will eventually cleanup - // the texture that is not in use - childSetValue(upload_fee_ctrl_name, getString("no_upload_fee_string")); - } - } - - LLTextureCtrl* tex_ctrl = (LLTextureCtrl*)ctrl; - if (tex_ctrl->isImageLocal()) - { - subscribeToLocalTexture(dirty_flag, tex_ctrl->getLocalTrackingID()); - } - else - { - // unsubcribe potential old callabck - mat_connection_map_t::iterator found = mTextureChangesUpdates.find(dirty_flag); - if (found != mTextureChangesUpdates.end()) - { - found->second.mConnection.disconnect(); - } - } - - markChangesUnsaved(dirty_flag); - applyToSelection(); -} - -void LLMaterialEditor::onCancelCtrl(LLUICtrl* ctrl, const LLSD& data, S32 dirty_flag) -{ - mRevertedChanges |= dirty_flag; - applyToSelection(); -} - -void update_local_texture(LLUICtrl* ctrl, LLGLTFMaterial* mat) -{ - LLTextureCtrl* tex_ctrl = (LLTextureCtrl*)ctrl; - if (tex_ctrl->isImageLocal()) - { - // subscrive material to updates of local textures - LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tex_ctrl->getLocalTrackingID(), mat); - } -} - -void LLMaterialEditor::onSelectCtrl(LLUICtrl* ctrl, const LLSD& data, S32 dirty_flag) -{ - mUnsavedChanges |= dirty_flag; - applyToSelection(); - - struct f : public LLSelectedNodeFunctor - { - f(LLUICtrl* ctrl, S32 dirty_flag) : mCtrl(ctrl), mDirtyFlag(dirty_flag) - { - } - - virtual bool apply(LLSelectNode* nodep) - { - LLViewerObject* objectp = nodep->getObject(); - if (!objectp) - { - return false; - } - S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); // avatars have TEs but no faces - for (S32 te = 0; te < num_tes; ++te) - { - if (nodep->isTESelected(te) && nodep->mSavedGLTFOverrideMaterials.size() > te) - { - if (nodep->mSavedGLTFOverrideMaterials[te].isNull()) - { - // populate with default values, default values basically mean 'not in use' - nodep->mSavedGLTFOverrideMaterials[te] = new LLGLTFMaterial(); - } - - switch (mDirtyFlag) - { - //Textures - case MATERIAL_BASE_COLOR_TEX_DIRTY: - { - nodep->mSavedGLTFOverrideMaterials[te]->setBaseColorId(mCtrl->getValue().asUUID(), true); - update_local_texture(mCtrl, nodep->mSavedGLTFOverrideMaterials[te].get()); - break; - } - case MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY: - { - nodep->mSavedGLTFOverrideMaterials[te]->setOcclusionRoughnessMetallicId(mCtrl->getValue().asUUID(), true); - update_local_texture(mCtrl, nodep->mSavedGLTFOverrideMaterials[te].get()); - break; - } - case MATERIAL_EMISIVE_TEX_DIRTY: - { - nodep->mSavedGLTFOverrideMaterials[te]->setEmissiveId(mCtrl->getValue().asUUID(), true); - update_local_texture(mCtrl, nodep->mSavedGLTFOverrideMaterials[te].get()); - break; - } - case MATERIAL_NORMAL_TEX_DIRTY: - { - nodep->mSavedGLTFOverrideMaterials[te]->setNormalId(mCtrl->getValue().asUUID(), true); - update_local_texture(mCtrl, nodep->mSavedGLTFOverrideMaterials[te].get()); - break; - } - // Colors - case MATERIAL_BASE_COLOR_DIRTY: - { - LLColor4 ret = linearColor4(LLColor4(mCtrl->getValue())); - // except transparency - ret.mV[3] = nodep->mSavedGLTFOverrideMaterials[te]->mBaseColor.mV[3]; - nodep->mSavedGLTFOverrideMaterials[te]->setBaseColorFactor(ret, true); - break; - } - case MATERIAL_EMISIVE_COLOR_DIRTY: - { - nodep->mSavedGLTFOverrideMaterials[te]->setEmissiveColorFactor(LLColor3(mCtrl->getValue()), true); - break; - } - default: - break; - } - } - } - return true; - } - - LLUICtrl* mCtrl; - S32 mDirtyFlag; - } func(ctrl, dirty_flag); - - LLSelectMgr::getInstance()->getSelection()->applyToNodes(&func); -} - -void LLMaterialEditor::onClickSave() -{ - if (!capabilitiesAvailable()) - { - LLNotificationsUtil::add("MissingMaterialCaps"); - return; - } - if (!can_afford_transaction(mExpectedUploadCost)) - { - LLSD args; - args["COST"] = llformat("%d", mExpectedUploadCost); - LLNotificationsUtil::add("ErrorCannotAffordUpload", args); - return; - } - - applyToSelection(); - saveIfNeeded(); -} - -std::string LLMaterialEditor::getEncodedAsset() -{ - LLSD asset; - asset["version"] = LLGLTFMaterial::ASSET_VERSION; - asset["type"] = LLGLTFMaterial::ASSET_TYPE; - LLGLTFMaterial mat; - getGLTFMaterial(&mat); - asset["data"] = mat.asJSON(); - - std::ostringstream str; - LLSDSerialize::serialize(asset, str, LLSDSerialize::LLSD_BINARY); - - return str.str(); -} - -bool LLMaterialEditor::decodeAsset(const std::vector& buffer) -{ - LLSD asset; - - std::istrstream str(&buffer[0], buffer.size()); - if (LLSDSerialize::deserialize(asset, str, buffer.size())) - { - if (asset.has("version") && LLGLTFMaterial::isAcceptedVersion(asset["version"].asString())) - { - if (asset.has("type") && asset["type"] == LLGLTFMaterial::ASSET_TYPE) - { - if (asset.has("data") && asset["data"].isString()) - { - std::string data = asset["data"]; - - tinygltf::TinyGLTF gltf; - tinygltf::TinyGLTF loader; - std::string error_msg; - std::string warn_msg; - - tinygltf::Model model_in; - - if (loader.LoadASCIIFromString(&model_in, &error_msg, &warn_msg, data.c_str(), data.length(), "")) - { - // assets are only supposed to have one item - // *NOTE: This duplicates some functionality from - // LLGLTFMaterial::fromJSON, but currently does the job - // better for the material editor use case. - // However, LLGLTFMaterial::asJSON should always be - // used when uploading materials, to ensure the - // asset is valid. - return setFromGltfModel(model_in, 0, true); - } - else - { - LL_WARNS("MaterialEditor") << "Floater " << getKey() << " Failed to decode material asset: " << LL_NEWLINE - << warn_msg << LL_NEWLINE - << error_msg << LL_ENDL; - } - } - } - } - else - { - LL_WARNS("MaterialEditor") << "Invalid LLSD content "<< asset << " for flaoter " << getKey() << LL_ENDL; - } - } - else - { - LL_WARNS("MaterialEditor") << "Failed to deserialize material LLSD for flaoter " << getKey() << LL_ENDL; - } - - return false; -} - -/** - * Build a description of the material we just imported. - * Currently this means a list of the textures present but we - * may eventually want to make it more complete - will be guided - * by what the content creators say they need. - */ -const std::string LLMaterialEditor::buildMaterialDescription() -{ - std::ostringstream desc; - desc << LLTrans::getString("Material Texture Name Header"); - - // add the texture names for each just so long as the material - // we loaded has an entry for it (i think testing the texture - // control UUI for NULL is a valid metric for if it was loaded - // or not but I suspect this code will change a lot so may need - // to revisit - if (!mBaseColorTextureCtrl->getValue().asUUID().isNull()) - { - desc << mBaseColorName; - desc << ", "; - } - if (!mMetallicTextureCtrl->getValue().asUUID().isNull()) - { - desc << mMetallicRoughnessName; - desc << ", "; - } - if (!mEmissiveTextureCtrl->getValue().asUUID().isNull()) - { - desc << mEmissiveName; - desc << ", "; - } - if (!mNormalTextureCtrl->getValue().asUUID().isNull()) - { - desc << mNormalName; - } - - // trim last char if it's a ',' in case there is no normal texture - // present and the code above inserts one - // (no need to check for string length - always has initial string) - std::string::iterator iter = desc.str().end() - 1; - if (*iter == ',') - { - desc.str().erase(iter); - } - - // sanitize the material description so that it's compatible with the inventory - // note: split this up because clang doesn't like operating directly on the - // str() - error: lvalue reference to type 'basic_string<...>' cannot bind to a - // temporary of type 'basic_string<...>' - std::string inv_desc = desc.str(); - LLInventoryObject::correctInventoryName(inv_desc); - - return inv_desc; -} - -bool LLMaterialEditor::saveIfNeeded() -{ - if (mUploadingTexturesCount > 0) - { - // Upload already in progress, wait until - // textures upload will retry saving on callback. - // Also should prevent some failure-callbacks - return true; - } - - if (saveTextures() > 0) - { - // started texture upload - setEnabled(false); - return true; - } - - std::string buffer = getEncodedAsset(); - - const LLInventoryItem* item = getItem(); - // save it out to database - if (item) - { - if (!updateInventoryItem(buffer, mItemUUID, mObjectUUID)) - { - return false; - } - - if (mCloseAfterSave) - { - closeFloater(); - } - else - { - mAssetStatus = PREVIEW_ASSET_LOADING; - setEnabled(false); - } - } - else - { - // Make a new inventory item and set upload permissions - LLPermissions local_permissions; - local_permissions.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null); - - if (mIsOverride) - { - // Shouldn't happen, but just in case it ever changes - U32 everyone_perm = LLFloaterPerms::getEveryonePerms("Materials"); - U32 group_perm = LLFloaterPerms::getGroupPerms("Materials"); - U32 next_owner_perm = LLFloaterPerms::getNextOwnerPerms("Materials"); - local_permissions.initMasks(PERM_ALL, PERM_ALL, everyone_perm, group_perm, next_owner_perm); - - } - else - { - // Uploads are supposed to use Upload permissions, not material permissions - U32 everyone_perm = LLFloaterPerms::getEveryonePerms("Uploads"); - U32 group_perm = LLFloaterPerms::getGroupPerms("Uploads"); - U32 next_owner_perm = LLFloaterPerms::getNextOwnerPerms("Uploads"); - local_permissions.initMasks(PERM_ALL, PERM_ALL, everyone_perm, group_perm, next_owner_perm); - } - - std::string res_desc = buildMaterialDescription(); - createInventoryItem(buffer, mMaterialName, res_desc, local_permissions); - - // We do not update floater with uploaded asset yet, so just close it. - closeFloater(); - } - - return true; -} - -// static -bool LLMaterialEditor::updateInventoryItem(const std::string &buffer, const LLUUID &item_id, const LLUUID &task_id) -{ - const LLViewerRegion* region = gAgent.getRegion(); - if (!region) - { - LL_WARNS("MaterialEditor") << "Not connected to a region, cannot save material." << LL_ENDL; - return false; - } - std::string agent_url = region->getCapability("UpdateMaterialAgentInventory"); - std::string task_url = region->getCapability("UpdateMaterialTaskInventory"); - - if (!agent_url.empty() && !task_url.empty()) - { - std::string url; - LLResourceUploadInfo::ptr_t uploadInfo; - - if (task_id.isNull() && !agent_url.empty()) - { - uploadInfo = std::make_shared(item_id, LLAssetType::AT_MATERIAL, buffer, - [](LLUUID itemId, LLUUID newAssetId, LLUUID newItemId, LLSD) - { - // done callback - LLMaterialEditor::finishInventoryUpload(itemId, newAssetId, newItemId); - }, - [](LLUUID itemId, LLUUID taskId, LLSD response, std::string reason) - { - // failure callback - LLMaterialEditor* me = LLFloaterReg::findTypedInstance("material_editor", LLSD(itemId)); - if (me) - { - me->setEnabled(true); - } - return true; - } - ); - url = agent_url; - } - else if (!task_id.isNull() && !task_url.empty()) - { - uploadInfo = std::make_shared(task_id, item_id, LLAssetType::AT_MATERIAL, buffer, - [](LLUUID itemId, LLUUID task_id, LLUUID newAssetId, LLSD) - { - // done callback - LLMaterialEditor::finishTaskUpload(itemId, newAssetId, task_id); - }, - [](LLUUID itemId, LLUUID task_id, LLSD response, std::string reason) - { - // failure callback - LLSD floater_key; - floater_key["taskid"] = task_id; - floater_key["itemid"] = itemId; - LLMaterialEditor* me = LLFloaterReg::findTypedInstance("material_editor", floater_key); - if (me) - { - me->setEnabled(true); - } - return true; - } - ); - url = task_url; - } - - if (!url.empty() && uploadInfo) - { - LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo); - } - else - { - return false; - } - - } - else // !gAssetStorage - { - LL_WARNS("MaterialEditor") << "Not connected to an materials capable region." << LL_ENDL; - return false; - } - - // todo: apply permissions from textures here if server doesn't - // if any texture is 'no transfer', material should be 'no transfer' as well - - return true; -} - -// Callback intended for when a material is saved from an object and needs to -// be modified to reflect the new asset/name. -class LLObjectsMaterialItemCallback : public LLInventoryCallback -{ -public: - LLObjectsMaterialItemCallback(const LLPermissions& permissions, const std::string& asset_data, const std::string& new_name) - : mPermissions(permissions), - mAssetData(asset_data), - mNewName(new_name) - { - } - - void fire(const LLUUID& inv_item_id) override - { - LLViewerInventoryItem* item = gInventory.getItem(inv_item_id); - if (!item) - { - return; - } - - // Name may or may not have already been applied - const bool changed_name = item->getName() != mNewName; - // create_inventory_item/copy_inventory_item don't allow presetting some permissions, fix it now - const bool changed_permissions = item->getPermissions() != mPermissions; - const bool changed = changed_name || changed_permissions; - LLSD updates; - if (changed) - { - if (changed_name) - { - updates["name"] = mNewName; - } - if (changed_permissions) - { - updates["permissions"] = ll_create_sd_from_permissions(mPermissions); - } - update_inventory_item(inv_item_id, updates, NULL); - } - - // from reference in LLSettingsVOBase::createInventoryItem()/updateInventoryItem() - LLResourceUploadInfo::ptr_t uploadInfo = - std::make_shared( - inv_item_id, - LLAssetType::AT_MATERIAL, - mAssetData, - [changed, updates](LLUUID item_id, LLUUID new_asset_id, LLUUID new_item_id, LLSD response) - { - // done callback - LL_INFOS("Material") << "inventory item uploaded. item: " << item_id << " new_item_id: " << new_item_id << " response: " << response << LL_ENDL; - - // *HACK: Sometimes permissions do not stick in the UI. They are correct on the server-side, though. - if (changed) - { - update_inventory_item(new_item_id, updates, NULL); - } - }, - nullptr // failure callback, floater already closed - ); - - const LLViewerRegion* region = gAgent.getRegion(); - if (region) - { - std::string agent_url(region->getCapability("UpdateMaterialAgentInventory")); - if (agent_url.empty()) - { - LL_ERRS("MaterialEditor") << "missing required agent inventory cap url" << LL_ENDL; - } - LLViewerAssetUpload::EnqueueInventoryUpload(agent_url, uploadInfo); - } - } -private: - LLPermissions mPermissions; - std::string mAssetData; - std::string mNewName; -}; - -void LLMaterialEditor::createInventoryItem(const std::string &buffer, const std::string &name, const std::string &desc, const LLPermissions& permissions) -{ - // gen a new uuid for this asset - LLTransactionID tid; - tid.generate(); // timestamp-based randomization + uniquification - LLUUID parent = gInventory.findUserDefinedCategoryUUIDForType(LLFolderType::FT_MATERIAL); - const U8 subtype = NO_INV_SUBTYPE; // TODO maybe use AT_SETTINGS and LLSettingsType::ST_MATERIAL ? - - LLPointer cb = new LLObjectsMaterialItemCallback(permissions, buffer, name); - create_inventory_item(gAgent.getID(), gAgent.getSessionID(), parent, tid, name, desc, - LLAssetType::AT_MATERIAL, LLInventoryType::IT_MATERIAL, subtype, permissions.getMaskNextOwner(), - cb); -} - -void LLMaterialEditor::finishInventoryUpload(LLUUID itemId, LLUUID newAssetId, LLUUID newItemId) -{ - // Update the UI with the new asset. - LLMaterialEditor* me = LLFloaterReg::findTypedInstance("material_editor", LLSD(itemId)); - if (me) - { - if (newItemId.isNull()) - { - me->setAssetId(newAssetId); - me->refreshFromInventory(); - } - else if (newItemId.notNull()) - { - // Not supposed to happen? - me->refreshFromInventory(newItemId); - } - else - { - me->refreshFromInventory(itemId); - } - - if (me && !me->mTextureChangesUpdates.empty()) - { - const LLInventoryItem* item = me->getItem(); - if (item) - { - // local materials were assigned, force load material and init tracking - LLGLTFMaterial* mat = gGLTFMaterialList.getMaterial(item->getAssetUUID()); - for (mat_connection_map_t::value_type &val : me->mTextureChangesUpdates) - { - LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(val.second.mTrackingId, mat); - } - } - } - } -} - -void LLMaterialEditor::finishTaskUpload(LLUUID itemId, LLUUID newAssetId, LLUUID taskId) -{ - LLSD floater_key; - floater_key["taskid"] = taskId; - floater_key["itemid"] = itemId; - LLMaterialEditor* me = LLFloaterReg::findTypedInstance("material_editor", floater_key); - if (me) - { - me->setAssetId(newAssetId); - me->refreshFromInventory(); - me->setEnabled(true); - - if (me && !me->mTextureChangesUpdates.empty()) - { - // local materials were assigned, force load material and init tracking - LLGLTFMaterial* mat = gGLTFMaterialList.getMaterial(newAssetId); - for (mat_connection_map_t::value_type &val : me->mTextureChangesUpdates) - { - LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(val.second.mTrackingId, mat); - } - } - } -} - -void LLMaterialEditor::finishSaveAs( - const LLSD &oldKey, - const LLUUID &newItemId, - const std::string &buffer, - bool has_unsaved_changes) -{ - LLMaterialEditor* me = LLFloaterReg::findTypedInstance("material_editor", oldKey); - LLViewerInventoryItem* item = gInventory.getItem(newItemId); - if (item) - { - if (me) - { - me->mItemUUID = newItemId; - me->mObjectUUID = LLUUID::null; - me->mNotecardInventoryID = LLUUID::null; - me->mNotecardObjectID = LLUUID::null; - me->mAuxItem = nullptr; - me->setKey(LLSD(newItemId)); // for findTypedInstance - me->setMaterialName(item->getName()); - if (has_unsaved_changes) - { - if (!updateInventoryItem(buffer, newItemId, LLUUID::null)) - { - me->setEnabled(true); - } - } - else - { - me->loadAsset(); - me->setEnabled(true); - - // Local texure support - if (!me->mTextureChangesUpdates.empty()) - { - // local materials were assigned, force load material and init tracking - LLGLTFMaterial* mat = gGLTFMaterialList.getMaterial(item->getAssetUUID()); - for (mat_connection_map_t::value_type &val : me->mTextureChangesUpdates) - { - LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(val.second.mTrackingId, mat); - } - } - } - } - else if(has_unsaved_changes) - { - updateInventoryItem(buffer, newItemId, LLUUID::null); - } - } - else if (me) - { - me->setEnabled(true); - LL_WARNS("MaterialEditor") << "Item does not exist, floater " << me->getKey() << LL_ENDL; - } -} - -void LLMaterialEditor::refreshFromInventory(const LLUUID& new_item_id) -{ - if (mIsOverride) - { - // refreshFromInventory shouldn't be called for overrides, - // but just in case. - LL_WARNS("MaterialEditor") << "Tried to refresh from inventory for live editor" << LL_ENDL; - return; - } - LLSD old_key = getKey(); - if (new_item_id.notNull()) - { - mItemUUID = new_item_id; - if (mNotecardInventoryID.notNull()) - { - LLSD floater_key; - floater_key["objectid"] = mNotecardObjectID; - floater_key["notecardid"] = mNotecardInventoryID; - setKey(floater_key); - } - else if (mObjectUUID.notNull()) - { - LLSD floater_key; - floater_key["taskid"] = new_item_id; - floater_key["itemid"] = mObjectUUID; - setKey(floater_key); - } - else - { - setKey(LLSD(new_item_id)); - } - } - LL_DEBUGS("MaterialEditor") << "New floater key: " << getKey() << " Old key: " << old_key << LL_ENDL; - loadAsset(); -} - - -void LLMaterialEditor::onClickSaveAs() -{ - if (!LLMaterialEditor::capabilitiesAvailable()) - { - LLNotificationsUtil::add("MissingMaterialCaps"); - return; - } - - if (!can_afford_transaction(mExpectedUploadCost)) - { - LLSD args; - args["COST"] = llformat("%d", mExpectedUploadCost); - LLNotificationsUtil::add("ErrorCannotAffordUpload", args); - return; - } - - LLSD args; - args["DESC"] = mMaterialName; - - LLNotificationsUtil::add("SaveMaterialAs", args, LLSD(), boost::bind(&LLMaterialEditor::onSaveAsMsgCallback, this, _1, _2)); -} - -void LLMaterialEditor::onSaveAsMsgCallback(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (0 == option) - { - std::string new_name = response["message"].asString(); - LLInventoryObject::correctInventoryName(new_name); - if (!new_name.empty()) - { - const LLInventoryItem* item; - if (mNotecardInventoryID.notNull()) - { - item = mAuxItem.get(); - } - else - { - item = getItem(); - } - if (item) - { - const LLUUID &marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - LLUUID parent_id = item->getParentUUID(); - if (mObjectUUID.notNull() || marketplacelistings_id == parent_id || gInventory.isObjectDescendentOf(item->getUUID(), gInventory.getLibraryRootFolderID())) - { - parent_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MATERIAL); - } - - // A two step process, first copy an existing item, then create new asset - if (mNotecardInventoryID.notNull()) - { - LLPointer cb = new LLMaterialEditorCopiedCallback(getKey(), new_name); - copy_inventory_from_notecard(parent_id, - mNotecardObjectID, - mNotecardInventoryID, - mAuxItem.get(), - gInventoryCallbacks.registerCB(cb)); - } - else - { - std::string buffer = getEncodedAsset(); - LLPointer cb = new LLMaterialEditorCopiedCallback(buffer, getKey(), mUnsavedChanges); - copy_inventory_item( - gAgent.getID(), - item->getPermissions().getOwner(), - item->getUUID(), - parent_id, - new_name, - cb); - } - - mAssetStatus = PREVIEW_ASSET_LOADING; - setEnabled(false); - } - else - { - setMaterialName(new_name); - onClickSave(); - } - } - else - { - LLNotificationsUtil::add("InvalidMaterialName", LLSD(), LLSD(), [this](const LLSD& notification, const LLSD& response) - { - LLNotificationsUtil::add("SaveMaterialAs", LLSD().with("DESC", mMaterialName), LLSD(), - boost::bind(&LLMaterialEditor::onSaveAsMsgCallback, this, _1, _2)); - }); - } - } -} - -void LLMaterialEditor::onClickCancel() -{ - if (mUnsavedChanges) - { - LLNotificationsUtil::add("UsavedMaterialChanges", LLSD(), LLSD(), boost::bind(&LLMaterialEditor::onCancelMsgCallback, this, _1, _2)); - } - else - { - closeFloater(); - } -} - -void LLMaterialEditor::onCancelMsgCallback(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (0 == option) - { - closeFloater(); - } -} - -static void pack_textures( - LLPointer& base_color_img, - LLPointer& normal_img, - LLPointer& mr_img, - LLPointer& emissive_img, - LLPointer& occlusion_img, - LLPointer& base_color_j2c, - LLPointer& normal_j2c, - LLPointer& mr_j2c, - LLPointer& emissive_j2c) -{ - // NOTE : remove log spam and lossless vs lossy comparisons when the logs are no longer useful - - if (base_color_img) - { - base_color_j2c = LLViewerTextureList::convertToUploadFile(base_color_img); - LL_DEBUGS("MaterialEditor") << "BaseColor: " << base_color_j2c->getDataSize() << LL_ENDL; - } - - if (normal_img) - { - // create a losslessly compressed version of the normal map - normal_j2c = LLViewerTextureList::convertToUploadFile(normal_img, 1024, false, true); - LL_DEBUGS("MaterialEditor") << "Normal: " << normal_j2c->getDataSize() << LL_ENDL; - } - - if (mr_img) - { - mr_j2c = LLViewerTextureList::convertToUploadFile(mr_img); - LL_DEBUGS("MaterialEditor") << "Metallic/Roughness: " << mr_j2c->getDataSize() << LL_ENDL; - } - - if (emissive_img) - { - emissive_j2c = LLViewerTextureList::convertToUploadFile(emissive_img); - LL_DEBUGS("MaterialEditor") << "Emissive: " << emissive_j2c->getDataSize() << LL_ENDL; - } -} - -void LLMaterialEditor::uploadMaterialFromModel(const std::string& filename, tinygltf::Model& model_in, S32 index) -{ - if (index < 0 || !LLMaterialEditor::capabilitiesAvailable()) - { - return; - } - - if (model_in.materials.empty()) - { - // materials are missing - return; - } - - if (index >= 0 && model_in.materials.size() <= index) - { - // material is missing - return; - } - - // Todo: no point in loading whole editor - // This uses 'filename' to make sure multiple bulk uploads work - // instead of fighting for a single instance. - LLMaterialEditor* me = (LLMaterialEditor*)LLFloaterReg::getInstance("material_editor", LLSD().with("filename", filename).with("index", LLSD::Integer(index))); - me->loadMaterial(model_in, filename, index, false); - me->saveIfNeeded(); -} - - -void LLMaterialEditor::loadMaterialFromFile(const std::string& filename, S32 index) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; - - tinygltf::TinyGLTF loader; - std::string error_msg; - std::string warn_msg; - - bool loaded = false; - tinygltf::Model model_in; - - std::string filename_lc = filename; - LLStringUtil::toLower(filename_lc); - - // Load a tinygltf model fom a file. Assumes that the input filename has already been - // been sanitized to one of (.gltf , .glb) extensions, so does a simple find to distinguish. - if (std::string::npos == filename_lc.rfind(".gltf")) - { // file is binary - loaded = loader.LoadBinaryFromFile(&model_in, &error_msg, &warn_msg, filename); - } - else - { // file is ascii - loaded = loader.LoadASCIIFromFile(&model_in, &error_msg, &warn_msg, filename); - } - - if (!loaded) - { - LLNotificationsUtil::add("CannotUploadMaterial"); - return; - } - - if (model_in.materials.empty()) - { - // materials are missing - LLNotificationsUtil::add("CannotUploadMaterial"); - return; - } - - if (index >= 0 && model_in.materials.size() <= index) - { - // material is missing - LLNotificationsUtil::add("CannotUploadMaterial"); - return; - } - - LLMaterialEditor* me = (LLMaterialEditor*)LLFloaterReg::getInstance("material_editor"); - - if (index >= 0) - { - // Prespecified material - me->loadMaterial(model_in, filename, index); - } - else if (model_in.materials.size() == 1) - { - // Only one, just load it - me->loadMaterial(model_in, filename, 0); - } - else - { - // Promt user to select material - std::list material_list; - std::vector::const_iterator mat_iter = model_in.materials.begin(); - std::vector::const_iterator mat_end = model_in.materials.end(); - - for (; mat_iter != mat_end; mat_iter++) - { - std::string mat_name = mat_iter->name; - if (mat_name.empty()) - { - material_list.push_back("Material " + std::to_string(material_list.size())); - } - else - { - material_list.push_back(mat_name); - } - } - - material_list.push_back(me->getString("material_batch_import_text")); - - LLFloaterComboOptions::showUI( - [me, model_in, filename](const std::string& option, S32 index) - { - me->loadMaterial(model_in, filename, index); - }, - me->getString("material_selection_title"), - me->getString("material_selection_text"), - material_list - ); - } -} - -void LLMaterialEditor::onSelectionChanged() -{ - // Drop selection updates if we are waiting for - // overrides to finish applying to not reset values - // (might need a timeout) - if (!mOverrideInProgress) - { - // mUpdateSignal triggers a lot per frame, breakwater - mSelectionNeedsUpdate = true; - } -} - -void LLMaterialEditor::updateLive() -{ - mSelectionNeedsUpdate = true; - mOverrideInProgress = false; -} - -void LLMaterialEditor::loadLive() -{ - LLMaterialEditor* me = (LLMaterialEditor*)LLFloaterReg::getInstance("live_material_editor"); - if (me) - { - me->mOverrideInProgress = false; - me->setFromSelection(); - - // Set up for selection changes updates - if (!me->mSelectionUpdateSlot.connected()) - { - me->mSelectionUpdateSlot = LLSelectMgr::instance().mUpdateSignal.connect(boost::bind(&LLMaterialEditor::onSelectionChanged, me)); - } - - me->openFloater(); - me->setFocus(true); - } -} - -namespace -{ - // Which inventory to consult for item permissions - enum class ItemSource - { - // Consult the permissions of the item in the object's inventory. If - // the item is not present, then usage of the asset is allowed. - OBJECT, - // Consult the permissions of the item in the agent's inventory. If - // the item is not present, then usage of the asset is not allowed. - AGENT - }; - - class LLAssetIDMatchesWithPerms : public LLInventoryCollectFunctor - { - public: - LLAssetIDMatchesWithPerms(const LLUUID& asset_id, const std::vector& ops) : mAssetID(asset_id), mOps(ops) {} - virtual ~LLAssetIDMatchesWithPerms() {} - bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) - { - if (!item || item->getAssetUUID() != mAssetID) - { - return false; - } - LLPermissions item_permissions = item->getPermissions(); - for (PermissionBit op : mOps) - { - if (!gAgent.allowOperation(op, item_permissions, GP_OBJECT_MANIPULATE)) - { - return false; - } - } - return true; - } - - protected: - LLUUID mAssetID; - std::vector mOps; - }; -}; - -// *NOTE: permissions_out includes user preferences for new item creation (LLFloaterPerms) -bool can_use_objects_material(LLSelectedTEGetMatData& func, const std::vector& ops, const ItemSource item_source, LLPermissions& permissions_out, LLViewerInventoryItem*& item_out) -{ - if (!LLMaterialEditor::capabilitiesAvailable()) - { - return false; - } - - // func.mIsOverride=true is used for the singleton material editor floater - // associated with the build floater. This flag also excludes objects from - // the selection that do not satisfy PERM_MODIFY. - llassert(func.mIsOverride); - LLSelectMgr::getInstance()->getSelection()->applyToTEs(&func, true /*first applicable*/); - - if (item_source == ItemSource::AGENT) - { - func.mObjectId = LLUUID::null; - } - LLViewerObject* selected_object = func.mObject; - if (!selected_object) - { - // LLSelectedTEGetMatData can fail if there are no selected faces - // with materials, but we expect at least some object is selected. - llassert(LLSelectMgr::getInstance()->getSelection()->getFirstObject()); - return false; - } - if (selected_object->isInventoryPending()) - { - return false; - } - for (PermissionBit op : ops) - { - if (op == PERM_MODIFY && selected_object->isPermanentEnforced()) - { - return false; - } - } - - // Look for the item to base permissions off of - item_out = nullptr; - const bool blank_material = func.mMaterialId == LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID; - if (!blank_material) - { - LLAssetIDMatchesWithPerms item_has_perms(func.mMaterialId, ops); - if (item_source == ItemSource::OBJECT) - { - LLViewerInventoryItem* item = selected_object->getInventoryItemByAsset(func.mMaterialId); - if (item && !item_has_perms(nullptr, item)) - { - return false; - } - item_out = item; - } - else - { - llassert(item_source == ItemSource::AGENT); - - LLViewerInventoryCategory::cat_array_t cats; - LLViewerInventoryItem::item_array_t items; - gInventory.collectDescendentsIf(LLUUID::null, - cats, - items, - // *NOTE: PBRPickerAgentListener will need - // to be changed if checking the trash is - // disabled - LLInventoryModel::INCLUDE_TRASH, - item_has_perms); - if (items.empty()) - { - return false; - } - item_out = items[0]; - } - } - - LLPermissions item_permissions; - if (item_out) - { - item_permissions = item_out->getPermissions(); - // Update flags for new owner - if (!item_permissions.setOwnerAndGroup(LLUUID::null, gAgent.getID(), LLUUID::null, true)) - { - llassert(false); - return false; - } - } - else - { - item_permissions.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null); - } - - // Use root object for permissions checking - LLViewerObject* root_object = selected_object->getRootEdit(); - LLPermissions* object_permissions_p = LLSelectMgr::getInstance()->findObjectPermissions(root_object); - LLPermissions object_permissions; - if (object_permissions_p) - { - object_permissions.set(*object_permissions_p); - for (PermissionBit op : ops) - { - if (!gAgent.allowOperation(op, object_permissions, GP_OBJECT_MANIPULATE)) - { - return false; - } - } - // Update flags for new owner - if (!object_permissions.setOwnerAndGroup(LLUUID::null, gAgent.getID(), LLUUID::null, true)) - { - llassert(false); - return false; - } - } - else - { - object_permissions.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null); - } - - LLPermissions floater_perm; - floater_perm.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null); - floater_perm.setMaskEveryone(LLFloaterPerms::getEveryonePerms("Materials")); - floater_perm.setMaskGroup(LLFloaterPerms::getGroupPerms("Materials")); - floater_perm.setMaskNext(LLFloaterPerms::getNextOwnerPerms("Materials")); - - // *NOTE: A close inspection of LLPermissions::accumulate shows that - // conflicting UUIDs will be unset. This is acceptable behavior for now. - // The server will populate creator info based on the item creation method - // used. - // *NOTE: As far as I'm aware, there is currently no good way to preserve - // creation history when there's no material item present. In that case, - // the agent who saved the material will be considered the creator. - // -Cosmic,2023-08-07 - if (item_source == ItemSource::AGENT) - { - llassert(blank_material || item_out); // See comment at ItemSource::AGENT definition - - permissions_out.set(item_permissions); - } - else - { - llassert(item_source == ItemSource::OBJECT); - - if (item_out) - { - permissions_out.set(item_permissions); - } - else - { - permissions_out.set(object_permissions); - } - } - permissions_out.accumulate(floater_perm); - - return true; -} - -bool LLMaterialEditor::canModifyObjectsMaterial() -{ - LLSelectedTEGetMatData func(true); - LLPermissions permissions; - LLViewerInventoryItem* item_out; - return can_use_objects_material(func, std::vector({PERM_MODIFY}), ItemSource::OBJECT, permissions, item_out); -} - -bool LLMaterialEditor::canSaveObjectsMaterial() -{ - LLSelectedTEGetMatData func(true); - LLPermissions permissions; - LLViewerInventoryItem* item_out; - return can_use_objects_material(func, std::vector({PERM_COPY, PERM_MODIFY}), ItemSource::AGENT, permissions, item_out); -} - -bool LLMaterialEditor::canClipboardObjectsMaterial() -{ - if (LLSelectMgr::getInstance()->getSelection()->getObjectCount() != 1) - { - return false; - } - - struct LLSelectedTEGetNullMat : public LLSelectedTEFunctor - { - bool apply(LLViewerObject* objectp, S32 te_index) - { - return objectp->getRenderMaterialID(te_index).isNull(); - } - } null_func; - - if (LLSelectMgr::getInstance()->getSelection()->applyToTEs(&null_func)) - { - return true; - } - - LLSelectedTEGetMatData func(true); - LLPermissions permissions; - LLViewerInventoryItem* item_out; - return can_use_objects_material(func, std::vector({PERM_COPY, PERM_MODIFY, PERM_TRANSFER}), ItemSource::OBJECT, permissions, item_out); -} - -void LLMaterialEditor::saveObjectsMaterialAs() -{ - LLSelectedTEGetMatData func(true); - LLPermissions permissions; - LLViewerInventoryItem* item = nullptr; - bool allowed = can_use_objects_material(func, std::vector({PERM_COPY, PERM_MODIFY}), ItemSource::AGENT, permissions, item); - if (!allowed) - { - LL_WARNS("MaterialEditor") << "Failed to save GLTF material from object" << LL_ENDL; - return; - } - const LLUUID item_id = item ? item->getUUID() : LLUUID::null; - saveObjectsMaterialAs(func.mMaterial, func.mLocalMaterial, permissions, func.mObjectId, item_id); -} - - -void LLMaterialEditor::saveObjectsMaterialAs(const LLGLTFMaterial* render_material, const LLLocalGLTFMaterial *local_material, const LLPermissions& permissions, const LLUUID& object_id, const LLUUID& item_id) -{ - if (local_material) - { - // This is a local material, reload it from file - // so that user won't end up with grey textures - // on next login. - LLMaterialEditor::loadMaterialFromFile(local_material->getFilename(), local_material->getIndexInFile()); - - LLMaterialEditor* me = (LLMaterialEditor*)LLFloaterReg::getInstance("material_editor"); - if (me) - { - // don't use override material here, it has 'hacked ids' - // and values, use end result, apply it on top of local. - const LLColor4& base_color = render_material->mBaseColor; - me->setBaseColor(LLColor3(base_color)); - me->setTransparency(base_color[VW]); - me->setMetalnessFactor(render_material->mMetallicFactor); - me->setRoughnessFactor(render_material->mRoughnessFactor); - me->setEmissiveColor(render_material->mEmissiveColor); - me->setDoubleSided(render_material->mDoubleSided); - me->setAlphaMode(render_material->getAlphaMode()); - me->setAlphaCutoff(render_material->mAlphaCutoff); - - // most things like colors we can apply without verifying - // but texture ids are going to be different from both, base and override - // so only apply override id if there is actually a difference - if (local_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR] != render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR]) - { - me->setBaseColorId(render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR]); - me->childSetValue("base_color_upload_fee", me->getString("no_upload_fee_string")); - } - if (local_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL] != render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL]) - { - me->setNormalId(render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL]); - me->childSetValue("normal_upload_fee", me->getString("no_upload_fee_string")); - } - if (local_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS] != render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS]) - { - me->setMetallicRoughnessId(render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS]); - me->childSetValue("metallic_upload_fee", me->getString("no_upload_fee_string")); - } - if (local_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE] != render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE]) - { - me->setEmissiveId(render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE]); - me->childSetValue("emissive_upload_fee", me->getString("no_upload_fee_string")); - } - - // recalculate upload prices - me->markChangesUnsaved(0); - } - - return; - } - - LLSD payload; - if (render_material) - { - // Make a copy of the render material with unsupported transforms removed - LLGLTFMaterial asset_material = *render_material; - asset_material.sanitizeAssetMaterial(); - // Serialize the sanitized render material - payload["data"] = asset_material.asJSON(); - } - else - { - // Menu shouldn't allow this, but as a fallback - // pick defaults from a blank material - LLGLTFMaterial blank_mat; - payload["data"] = blank_mat.asJSON(); - LL_WARNS() << "Got no material when trying to save material" << LL_ENDL; - } - - LLSD args; - args["DESC"] = LLTrans::getString("New Material"); - - if (local_material) - { - LLPermissions local_permissions; - local_permissions.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null); - LLNotificationsUtil::add("SaveMaterialAs", args, payload, boost::bind(&LLMaterialEditor::onSaveObjectsMaterialAsMsgCallback, _1, _2, local_permissions)); - } - else - { - llassert(object_id.isNull()); // Case for copying item from object inventory is no longer implemented - LLNotificationsUtil::add("SaveMaterialAs", args, payload, boost::bind(&LLMaterialEditor::onSaveObjectsMaterialAsMsgCallback, _1, _2, permissions)); - } -} - -// static -void LLMaterialEditor::onSaveObjectsMaterialAsMsgCallback(const LLSD& notification, const LLSD& response, const LLPermissions& permissions) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (0 != option) - { - return; - } - - LLSD asset; - asset["version"] = LLGLTFMaterial::ASSET_VERSION; - asset["type"] = LLGLTFMaterial::ASSET_TYPE; - // This is the string serialized from LLGLTFMaterial::asJSON - asset["data"] = notification["payload"]["data"]; - - std::ostringstream str; - LLSDSerialize::serialize(asset, str, LLSDSerialize::LLSD_BINARY); - - std::string new_name = response["message"].asString(); - LLInventoryObject::correctInventoryName(new_name); - if (new_name.empty()) - { - return; - } - - createInventoryItem(str.str(), new_name, std::string(), permissions); -} - -const void upload_bulk(const std::vector& filenames, LLFilePicker::ELoadFilter type); - -void LLMaterialEditor::loadMaterial(const tinygltf::Model &model_in, const std::string &filename, S32 index, bool open_floater) -{ - if (index == model_in.materials.size()) - { - // bulk upload all the things - upload_bulk({ filename }, LLFilePicker::FFLOAD_MATERIAL); - return; - } - - if (model_in.materials.size() <= index) - { - return; - } - std::string folder = gDirUtilp->getDirName(filename); - - tinygltf::Material material_in = model_in.materials[index]; - - tinygltf::Model model_out; - model_out.asset.version = "2.0"; - model_out.materials.resize(1); - - // get base color texture - LLPointer base_color_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.pbrMetallicRoughness.baseColorTexture.index, mBaseColorName); - // get normal map - LLPointer normal_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.normalTexture.index, mNormalName); - // get metallic-roughness texture - LLPointer mr_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.pbrMetallicRoughness.metallicRoughnessTexture.index, mMetallicRoughnessName); - // get emissive texture - LLPointer emissive_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.emissiveTexture.index, mEmissiveName); - // get occlusion map if needed - LLPointer occlusion_img; - if (material_in.occlusionTexture.index != material_in.pbrMetallicRoughness.metallicRoughnessTexture.index) - { - std::string tmp; - occlusion_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.occlusionTexture.index, tmp); - } - - LLTinyGLTFHelper::initFetchedTextures(material_in, base_color_img, normal_img, mr_img, emissive_img, occlusion_img, - mBaseColorFetched, mNormalFetched, mMetallicRoughnessFetched, mEmissiveFetched); - pack_textures(base_color_img, normal_img, mr_img, emissive_img, occlusion_img, - mBaseColorJ2C, mNormalJ2C, mMetallicRoughnessJ2C, mEmissiveJ2C); - - LLUUID base_color_id; - if (mBaseColorFetched.notNull()) - { - mBaseColorFetched->forceToSaveRawImage(0, F32_MAX); - base_color_id = mBaseColorFetched->getID(); - - if (mBaseColorName.empty()) - { - mBaseColorName = MATERIAL_BASE_COLOR_DEFAULT_NAME; - } - } - - LLUUID normal_id; - if (mNormalFetched.notNull()) - { - mNormalFetched->forceToSaveRawImage(0, F32_MAX); - normal_id = mNormalFetched->getID(); - - if (mNormalName.empty()) - { - mNormalName = MATERIAL_NORMAL_DEFAULT_NAME; - } - } - - LLUUID mr_id; - if (mMetallicRoughnessFetched.notNull()) - { - mMetallicRoughnessFetched->forceToSaveRawImage(0, F32_MAX); - mr_id = mMetallicRoughnessFetched->getID(); - - if (mMetallicRoughnessName.empty()) - { - mMetallicRoughnessName = MATERIAL_METALLIC_DEFAULT_NAME; - } - } - - LLUUID emissive_id; - if (mEmissiveFetched.notNull()) - { - mEmissiveFetched->forceToSaveRawImage(0, F32_MAX); - emissive_id = mEmissiveFetched->getID(); - - if (mEmissiveName.empty()) - { - mEmissiveName = MATERIAL_EMISSIVE_DEFAULT_NAME; - } - } - - setBaseColorId(base_color_id); - setBaseColorUploadId(base_color_id); - setMetallicRoughnessId(mr_id); - setMetallicRoughnessUploadId(mr_id); - setEmissiveId(emissive_id); - setEmissiveUploadId(emissive_id); - setNormalId(normal_id); - setNormalUploadId(normal_id); - - setFromGltfModel(model_in, index); - - setFromGltfMetaData(filename, model_in, index); - - if (getDoubleSided()) - { - // SL-19392 Double sided materials double the number of pixels that must be rasterized, - // and a great many tools that export GLTF simply leave double sided enabled whether - // or not it is necessary. - LL_DEBUGS("MaterialEditor") << "Defaulting Double Sided to disabled on import" << LL_ENDL; - setDoubleSided(false); - } - - markChangesUnsaved(U32_MAX); - - if (open_floater) - { - openFloater(getKey()); - setFocus(true); - setCanSave(true); - setCanSaveAs(true); - - applyToSelection(); - } -} - -bool LLMaterialEditor::setFromGltfModel(const tinygltf::Model& model, S32 index, bool set_textures) -{ - if (model.materials.size() > index) - { - const tinygltf::Material& material_in = model.materials[index]; - - if (set_textures) - { - S32 index; - LLUUID id; - - // get base color texture - index = material_in.pbrMetallicRoughness.baseColorTexture.index; - if (index >= 0) - { - id.set(model.images[index].uri); - setBaseColorId(id); - } - else - { - setBaseColorId(LLUUID::null); - } - - // get normal map - index = material_in.normalTexture.index; - if (index >= 0) - { - id.set(model.images[index].uri); - setNormalId(id); - } - else - { - setNormalId(LLUUID::null); - } - - // get metallic-roughness texture - index = material_in.pbrMetallicRoughness.metallicRoughnessTexture.index; - if (index >= 0) - { - id.set(model.images[index].uri); - setMetallicRoughnessId(id); - } - else - { - setMetallicRoughnessId(LLUUID::null); - } - - // get emissive texture - index = material_in.emissiveTexture.index; - if (index >= 0) - { - id.set(model.images[index].uri); - setEmissiveId(id); - } - else - { - setEmissiveId(LLUUID::null); - } - } - - setAlphaMode(material_in.alphaMode); - setAlphaCutoff(material_in.alphaCutoff); - - setBaseColor(LLTinyGLTFHelper::getColor(material_in.pbrMetallicRoughness.baseColorFactor)); - setEmissiveColor(LLTinyGLTFHelper::getColor(material_in.emissiveFactor)); - - setMetalnessFactor(material_in.pbrMetallicRoughness.metallicFactor); - setRoughnessFactor(material_in.pbrMetallicRoughness.roughnessFactor); - - setDoubleSided(material_in.doubleSided); - } - - return true; -} - -/** - * Build a texture name from the contents of the (in tinyGLFT parlance) - * Image URI. This often is filepath to the original image on the users' - * local file system. - */ -const std::string LLMaterialEditor::getImageNameFromUri(std::string image_uri, const std::string texture_type) -{ - // getBaseFileName() works differently on each platform and file patchs - // can contain both types of delimiter so unify them then extract the - // base name (no path or extension) - std::replace(image_uri.begin(), image_uri.end(), '\\', gDirUtilp->getDirDelimiter()[0]); - std::replace(image_uri.begin(), image_uri.end(), '/', gDirUtilp->getDirDelimiter()[0]); - const bool strip_extension = true; - std::string stripped_uri = gDirUtilp->getBaseFileName(image_uri, strip_extension); - - // sometimes they can be really long and unwieldy - 64 chars is enough for anyone :) - const int max_texture_name_length = 64; - if (stripped_uri.length() > max_texture_name_length) - { - stripped_uri = stripped_uri.substr(0, max_texture_name_length - 1); - } - - // We intend to append the type of texture (base color, emissive etc.) to the - // name of the texture but sometimes the creator already did that. To try - // to avoid repeats (not perfect), we look for the texture type in the name - // and if we find it, do not append the type, later on. One way this fails - // (and it's fine for now) is I see some texture/image uris have a name like - // "metallic roughness" and of course, that doesn't match our predefined - // name "metallicroughness" - consider fix later.. - bool name_includes_type = false; - std::string stripped_uri_lower = stripped_uri; - LLStringUtil::toLower(stripped_uri_lower); - stripped_uri_lower.erase(std::remove_if(stripped_uri_lower.begin(), stripped_uri_lower.end(), isspace), stripped_uri_lower.end()); - std::string texture_type_lower = texture_type; - LLStringUtil::toLower(texture_type_lower); - texture_type_lower.erase(std::remove_if(texture_type_lower.begin(), texture_type_lower.end(), isspace), texture_type_lower.end()); - if (stripped_uri_lower.find(texture_type_lower) != std::string::npos) - { - name_includes_type = true; - } - - // uri doesn't include the type at all - if (!name_includes_type) - { - // uri doesn't include the type and the uri is not empty - // so we can include everything - if (stripped_uri.length() > 0) - { - // example "DamagedHelmet: base layer" - return STRINGIZE( - mMaterialNameShort << - ": " << - stripped_uri << - " (" << - texture_type << - ")" - ); - } - else - // uri doesn't include the type (because the uri is empty) - // so we must reorganize the string a bit to include the name - // and an explicit name type - { - // example "DamagedHelmet: (Emissive)" - return STRINGIZE( - mMaterialNameShort << - " (" << - texture_type << - ")" - ); - } - } - else - // uri includes the type so just use it directly with the - // name of the material - { - return STRINGIZE( - // example: AlienBust: normal_layer - mMaterialNameShort << - ": " << - stripped_uri - ); - } -} - -/** - * Update the metadata for the material based on what we find in the loaded - * file (along with some assumptions and interpretations...). Fields include - * the name of the material, a material description and the names of the - * composite textures. - */ -void LLMaterialEditor::setFromGltfMetaData(const std::string& filename, const tinygltf::Model& model, S32 index) -{ - // Use the name (without any path/extension) of the file that was - // uploaded as the base of the material name. Then if the name of the - // scene is present and not blank, append that and use the result as - // the name of the material. This is a first pass at creating a - // naming scheme that is useful to real content creators and hopefully - // avoid 500 materials in your inventory called "scene" or "Default" - const bool strip_extension = true; - std::string base_filename = gDirUtilp->getBaseFileName(filename, strip_extension); - - // Extract the name of the scene. Note it is often blank or some very - // generic name like "Scene" or "Default" so using this in the name - // is less useful than you might imagine. - std::string material_name; - if (model.materials.size() > index && !model.materials[index].name.empty()) - { - material_name = model.materials[index].name; - } - else if (model.scenes.size() > 0) - { - const tinygltf::Scene& scene_in = model.scenes[0]; - if (scene_in.name.length()) - { - material_name = scene_in.name; - } - else - { - // scene name is empty so no point using it - } - } - else - { - // scene name isn't present so no point using it - } - - // If we have a valid material or scene name, use it to build the short and - // long versions of the material name. The long version is used - // as you might expect, for the material name. The short version is - // used as part of the image/texture name - the theory is that will - // allow content creators to track the material and the corresponding - // textures - if (material_name.length()) - { - mMaterialNameShort = base_filename; - - mMaterialName = STRINGIZE( - base_filename << - " " << - "(" << - material_name << - ")" - ); - } - else - // otherwise, just use the trimmed filename as is - { - mMaterialNameShort = base_filename; - mMaterialName = base_filename; - } - - // sanitize the material name so that it's compatible with the inventory - LLInventoryObject::correctInventoryName(mMaterialName); - LLInventoryObject::correctInventoryName(mMaterialNameShort); - - // We also set the title of the floater to match the - // name of the material - setTitle(mMaterialName); - - /** - * Extract / derive the names of each composite texture. For each, the - * index is used to to determine which of the "Images" is used. If the index - * is -1 then that texture type is not present in the material (Seems to be - * quite common that a material is missing 1 or more types of texture) - */ - if (model.materials.size() > index) - { - const tinygltf::Material& first_material = model.materials[index]; - - mBaseColorName = MATERIAL_BASE_COLOR_DEFAULT_NAME; - // note: unlike the other textures, base color doesn't have its own entry - // in the tinyGLTF Material struct. Rather, it is taken from a - // sub-texture in the pbrMetallicRoughness member - int index = first_material.pbrMetallicRoughness.baseColorTexture.index; - if (index > -1 && index < model.images.size()) - { - // sanitize the name we decide to use for each texture - std::string texture_name = getImageNameFromUri(model.images[index].uri, MATERIAL_BASE_COLOR_DEFAULT_NAME); - LLInventoryObject::correctInventoryName(texture_name); - mBaseColorName = texture_name; - } - - mEmissiveName = MATERIAL_EMISSIVE_DEFAULT_NAME; - index = first_material.emissiveTexture.index; - if (index > -1 && index < model.images.size()) - { - std::string texture_name = getImageNameFromUri(model.images[index].uri, MATERIAL_EMISSIVE_DEFAULT_NAME); - LLInventoryObject::correctInventoryName(texture_name); - mEmissiveName = texture_name; - } - - mMetallicRoughnessName = MATERIAL_METALLIC_DEFAULT_NAME; - index = first_material.pbrMetallicRoughness.metallicRoughnessTexture.index; - if (index > -1 && index < model.images.size()) - { - std::string texture_name = getImageNameFromUri(model.images[index].uri, MATERIAL_METALLIC_DEFAULT_NAME); - LLInventoryObject::correctInventoryName(texture_name); - mMetallicRoughnessName = texture_name; - } - - mNormalName = MATERIAL_NORMAL_DEFAULT_NAME; - index = first_material.normalTexture.index; - if (index > -1 && index < model.images.size()) - { - std::string texture_name = getImageNameFromUri(model.images[index].uri, MATERIAL_NORMAL_DEFAULT_NAME); - LLInventoryObject::correctInventoryName(texture_name); - mNormalName = texture_name; - } - } -} - -void LLMaterialEditor::importMaterial() -{ - LLFilePickerReplyThread::startPicker( - [](const std::vector& filenames, LLFilePicker::ELoadFilter load_filter, LLFilePicker::ESaveFilter save_filter) - { - if (LLAppViewer::instance()->quitRequested()) - { - return; - } - if (filenames.size() > 0) - { - LLMaterialEditor::loadMaterialFromFile(filenames[0], -1); - } - }, - LLFilePicker::FFLOAD_MATERIAL, - true); -} - -class LLRenderMaterialFunctor : public LLSelectedTEFunctor -{ -public: - LLRenderMaterialFunctor(const LLUUID &id) - : mMatId(id) - { - } - - bool apply(LLViewerObject* objectp, S32 te) override - { - if (objectp && objectp->permModify() && objectp->getVolume()) - { - LLVOVolume* vobjp = (LLVOVolume*)objectp; - vobjp->setRenderMaterialID(te, mMatId, false /*preview only*/); - vobjp->updateTEMaterialTextures(te); - } - return true; - } -private: - LLUUID mMatId; -}; - -class LLRenderMaterialOverrideFunctor : public LLSelectedNodeFunctor -{ -public: - LLRenderMaterialOverrideFunctor( - LLMaterialEditor * me, - const LLUUID &report_on_object_id, - S32 report_on_te) - : mEditor(me) - , mSuccess(false) - , mObjectId(report_on_object_id) - , mObjectTE(report_on_te) - { - } - - virtual bool apply(LLSelectNode* nodep) override - { - LLViewerObject* objectp = nodep->getObject(); - if (!objectp || !objectp->permModify() || !objectp->getVolume()) - { - return false; - } - S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); // avatars have TEs but no faces - - // post override from given object and te to the simulator - // requestData should have: - // object_id - UUID of LLViewerObject - // side - S32 index of texture entry - // gltf_json - String of GLTF json for override data - - for (S32 te = 0; te < num_tes; ++te) - { - if (!nodep->isTESelected(te)) - { - continue; - } - - // Get material from object - // Selection can cover multiple objects, and live editor is - // supposed to overwrite changed values only - LLTextureEntry* tep = objectp->getTE(te); - - if (tep->getGLTFMaterial() == nullptr) - { - // overrides are not supposed to work or apply if - // there is no base material to work from - continue; - } - - LLPointer material = tep->getGLTFMaterialOverride(); - // make a copy to not invalidate existing - // material for multiple objects - if (material.isNull()) - { - // Start with a material override which does not make any changes - material = new LLGLTFMaterial(); - } - else - { - material = new LLGLTFMaterial(*material); - } - - U32 changed_flags = mEditor->getUnsavedChangesFlags(); - U32 reverted_flags = mEditor->getRevertedChangesFlags(); - - LLPointer revert_mat; - if (nodep->mSavedGLTFOverrideMaterials.size() > te) - { - if (nodep->mSavedGLTFOverrideMaterials[te].notNull()) - { - revert_mat = nodep->mSavedGLTFOverrideMaterials[te]; - } - else - { - // mSavedGLTFOverrideMaterials[te] being present but null - // means we need to use a default value - revert_mat = new LLGLTFMaterial(); - } - } - // else can not revert at all - - // Override object's values with values from editor where appropriate - if (changed_flags & MATERIAL_BASE_COLOR_DIRTY) - { - material->setBaseColorFactor(mEditor->getBaseColor(), true); - } - else if ((reverted_flags & MATERIAL_BASE_COLOR_DIRTY) && revert_mat.notNull()) - { - material->setBaseColorFactor(revert_mat->mBaseColor, false); - } - - if (changed_flags & MATERIAL_BASE_COLOR_TEX_DIRTY) - { - material->setBaseColorId(mEditor->getBaseColorId(), true); - LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_BASE_COLOR_TEX_DIRTY); - if (tracking_id.notNull()) - { - LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material); - } - } - else if ((reverted_flags & MATERIAL_BASE_COLOR_TEX_DIRTY) && revert_mat.notNull()) - { - material->setBaseColorId(revert_mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR], false); - LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_BASE_COLOR_TEX_DIRTY); - if (tracking_id.notNull()) - { - LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material); - } - } - - if (changed_flags & MATERIAL_NORMAL_TEX_DIRTY) - { - material->setNormalId(mEditor->getNormalId(), true); - LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_NORMAL_TEX_DIRTY); - if (tracking_id.notNull()) - { - LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material); - } - } - else if ((reverted_flags & MATERIAL_NORMAL_TEX_DIRTY) && revert_mat.notNull()) - { - material->setNormalId(revert_mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL], false); - LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_NORMAL_TEX_DIRTY); - if (tracking_id.notNull()) - { - LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material); - } - } - - if (changed_flags & MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY) - { - material->setOcclusionRoughnessMetallicId(mEditor->getMetallicRoughnessId(), true); - LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY); - if (tracking_id.notNull()) - { - LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material); - } - } - else if ((reverted_flags & MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY) && revert_mat.notNull()) - { - material->setOcclusionRoughnessMetallicId(revert_mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS], false); - LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY); - if (tracking_id.notNull()) - { - LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material); - } - } - - if (changed_flags & MATERIAL_METALLIC_ROUGHTNESS_METALNESS_DIRTY) - { - material->setMetallicFactor(mEditor->getMetalnessFactor(), true); - } - else if ((reverted_flags & MATERIAL_METALLIC_ROUGHTNESS_METALNESS_DIRTY) && revert_mat.notNull()) - { - material->setMetallicFactor(revert_mat->mMetallicFactor, false); - } - - if (changed_flags & MATERIAL_METALLIC_ROUGHTNESS_ROUGHNESS_DIRTY) - { - material->setRoughnessFactor(mEditor->getRoughnessFactor(), true); - } - else if ((reverted_flags & MATERIAL_METALLIC_ROUGHTNESS_ROUGHNESS_DIRTY) && revert_mat.notNull()) - { - material->setRoughnessFactor(revert_mat->mRoughnessFactor, false); - } - - if (changed_flags & MATERIAL_EMISIVE_COLOR_DIRTY) - { - material->setEmissiveColorFactor(LLColor3(mEditor->getEmissiveColor()), true); - } - else if ((reverted_flags & MATERIAL_EMISIVE_COLOR_DIRTY) && revert_mat.notNull()) - { - material->setEmissiveColorFactor(revert_mat->mEmissiveColor, false); - } - - if (changed_flags & MATERIAL_EMISIVE_TEX_DIRTY) - { - material->setEmissiveId(mEditor->getEmissiveId(), true); - LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_EMISIVE_TEX_DIRTY); - if (tracking_id.notNull()) - { - LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material); - } - } - else if ((reverted_flags & MATERIAL_EMISIVE_TEX_DIRTY) && revert_mat.notNull()) - { - material->setEmissiveId(revert_mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE], false); - LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_EMISIVE_TEX_DIRTY); - if (tracking_id.notNull()) - { - LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material); - } - } - - if (changed_flags & MATERIAL_DOUBLE_SIDED_DIRTY) - { - material->setDoubleSided(mEditor->getDoubleSided(), true); - } - else if ((reverted_flags & MATERIAL_DOUBLE_SIDED_DIRTY) && revert_mat.notNull()) - { - material->setDoubleSided(revert_mat->mDoubleSided, false); - } - - if (changed_flags & MATERIAL_ALPHA_MODE_DIRTY) - { - material->setAlphaMode(mEditor->getAlphaMode(), true); - } - else if ((reverted_flags & MATERIAL_ALPHA_MODE_DIRTY) && revert_mat.notNull()) - { - material->setAlphaMode(revert_mat->mAlphaMode, false); - } - - if (changed_flags & MATERIAL_ALPHA_CUTOFF_DIRTY) - { - material->setAlphaCutoff(mEditor->getAlphaCutoff(), true); - } - else if ((reverted_flags & MATERIAL_ALPHA_CUTOFF_DIRTY) && revert_mat.notNull()) - { - material->setAlphaCutoff(revert_mat->mAlphaCutoff, false); - } - - if (mObjectTE == te - && mObjectId == objectp->getID()) - { - mSuccess = true; - } - LLGLTFMaterialList::queueModify(objectp, te, material); - } - return true; - } - - static void modifyCallback(bool success) - { - if (!success) - { - // something went wrong update selection - LLMaterialEditor::updateLive(); - } - // else we will get updateLive() from panel face - } - - bool getResult() { return mSuccess; } - -private: - LLMaterialEditor * mEditor; - LLUUID mObjectId; - S32 mObjectTE; - bool mSuccess; -}; - -void LLMaterialEditor::applyToSelection() -{ - if (!mIsOverride) - { - // Only apply if working with 'live' materials - // Might need a better way to distinguish 'live' mode. - // But only one live edit is supposed to work at a time - // as a pair to tools floater. - return; - } - - std::string url = gAgent.getRegionCapability("ModifyMaterialParams"); - if (!url.empty()) - { - // Don't send data if there is nothing to send. - // Some UI elements will cause multiple commits, - // like spin ctrls on click and on down - if (mUnsavedChanges != 0 || mRevertedChanges != 0) - { - mOverrideInProgress = true; - LLObjectSelectionHandle selected_objects = LLSelectMgr::getInstance()->getSelection(); - LLRenderMaterialOverrideFunctor override_func(this, mOverrideObjectId, mOverrideObjectTE); - selected_objects->applyToNodes(&override_func); - - void(*done_callback)(bool) = LLRenderMaterialOverrideFunctor::modifyCallback; - - LLGLTFMaterialList::flushUpdates(done_callback); - - if (!override_func.getResult()) - { - // OverrideFunctor didn't find expected object or face - mOverrideInProgress = false; - } - - // we posted all changes - mUnsavedChanges = 0; - mRevertedChanges = 0; - } - } - else - { - LL_WARNS("MaterialEditor") << "Not connected to materials capable region, missing ModifyMaterialParams cap" << LL_ENDL; - - // Fallback local preview. Will be removed once override systems is finished and new cap is deployed everywhere. - LLPointer mat = new LLFetchedGLTFMaterial(); - getGLTFMaterial(mat); - static const LLUUID placeholder("984e183e-7811-4b05-a502-d79c6f978a98"); - gGLTFMaterialList.addMaterial(placeholder, mat); - LLRenderMaterialFunctor mat_func(placeholder); - LLObjectSelectionHandle selected_objects = LLSelectMgr::getInstance()->getSelection(); - selected_objects->applyToTEs(&mat_func); - } -} - -// Get a dump of the json representation of the current state of the editor UI -// in GLTF format, excluding transforms as they are not supported in material -// assets. (See also LLGLTFMaterial::sanitizeAssetMaterial()) -void LLMaterialEditor::getGLTFMaterial(LLGLTFMaterial* mat) -{ - mat->mBaseColor = getBaseColor(); - mat->mBaseColor.mV[3] = getTransparency(); - mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR] = getBaseColorId(); - - mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL] = getNormalId(); - - mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS] = getMetallicRoughnessId(); - mat->mMetallicFactor = getMetalnessFactor(); - mat->mRoughnessFactor = getRoughnessFactor(); - - mat->mEmissiveColor = getEmissiveColor(); - mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE] = getEmissiveId(); - - mat->mDoubleSided = getDoubleSided(); - mat->setAlphaMode(getAlphaMode()); - mat->mAlphaCutoff = getAlphaCutoff(); -} - -void LLMaterialEditor::setFromGLTFMaterial(LLGLTFMaterial* mat) -{ - setBaseColor(mat->mBaseColor); - setBaseColorId(mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR]); - setNormalId(mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL]); - - setMetallicRoughnessId(mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS]); - setMetalnessFactor(mat->mMetallicFactor); - setRoughnessFactor(mat->mRoughnessFactor); - - setEmissiveColor(mat->mEmissiveColor); - setEmissiveId(mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE]); - - setDoubleSided(mat->mDoubleSided); - setAlphaMode(mat->getAlphaMode()); - setAlphaCutoff(mat->mAlphaCutoff); - - if (mat->hasLocalTextures()) - { - for (LLGLTFMaterial::local_tex_map_t::value_type &val : mat->mTrackingIdToLocalTexture) - { - LLUUID world_id = LLLocalBitmapMgr::getInstance()->getWorldID(val.first); - if (val.second != world_id) - { - LL_WARNS() << "world id mismatch" << LL_ENDL; - } - if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR]) - { - subscribeToLocalTexture(MATERIAL_BASE_COLOR_TEX_DIRTY, val.first); - } - if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS]) - { - subscribeToLocalTexture(MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY, val.first); - } - if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE]) - { - subscribeToLocalTexture(MATERIAL_EMISIVE_TEX_DIRTY, val.first); - } - if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL]) - { - subscribeToLocalTexture(MATERIAL_NORMAL_TEX_DIRTY, val.first); - } - } - } -} - -bool LLMaterialEditor::setFromSelection() -{ - LLObjectSelectionHandle selected_objects = LLSelectMgr::getInstance()->getSelection(); - LLSelectedTEGetMatData func(mIsOverride); - - selected_objects->applyToTEs(&func); - mHasSelection = !selected_objects->isEmpty(); - mSelectionNeedsUpdate = false; - - if (func.mMaterial.notNull()) - { - setFromGLTFMaterial(func.mMaterial); - LLViewerObject* selected_object = func.mObject; - const LLViewerInventoryItem* item = selected_object->getInventoryItemByAsset(func.mMaterialId); - const bool allow_modify = !item || canModify(selected_object, item); - setEnableEditing(allow_modify); - - // todo: apply local texture data to all materials in selection - } - else - { - // pick defaults from a blank material; - LLGLTFMaterial blank_mat; - setFromGLTFMaterial(&blank_mat); - if (mIsOverride) - { - setEnableEditing(false); - } - } - - if (mIsOverride) - { - mBaseColorTextureCtrl->setTentative(!func.mIdenticalTexColor); - mMetallicTextureCtrl->setTentative(!func.mIdenticalTexMetal); - mEmissiveTextureCtrl->setTentative(!func.mIdenticalTexEmissive); - mNormalTextureCtrl->setTentative(!func.mIdenticalTexNormal); - - // Memorize selection data for filtering further updates - mOverrideObjectId = func.mObjectId; - mOverrideObjectTE = func.mObjectTE; - - // Ovverdired might have been updated, - // refresh state of local textures in overrides - // - // Todo: this probably shouldn't be here, but in localbitmap, - // subscried to all material overrides if we want copied - // objects to get properly updated as well - LLSelectedTEUpdateOverrides local_tex_func(this); - selected_objects->applyToNodes(&local_tex_func); - } - - return func.mMaterial.notNull(); -} - - -void LLMaterialEditor::loadAsset() -{ - const LLInventoryItem* item; - if (mNotecardInventoryID.notNull()) - { - item = mAuxItem.get(); - } - else - { - item = getItem(); - } - - bool fail = false; - - if (item) - { - LLPermissions perm(item->getPermissions()); - bool allow_copy = gAgent.allowOperation(PERM_COPY, perm, GP_OBJECT_MANIPULATE); - bool allow_modify = canModify(mObjectUUID, item); - bool source_library = mObjectUUID.isNull() && gInventory.isObjectDescendentOf(mItemUUID, gInventory.getLibraryRootFolderID()); - - setCanSaveAs(allow_copy); - setMaterialName(item->getName()); - - { - mAssetID = item->getAssetUUID(); - - if (mAssetID.isNull()) - { - mAssetStatus = PREVIEW_ASSET_LOADED; - loadDefaults(); - resetUnsavedChanges(); - setEnableEditing(allow_modify && !source_library); - } - else - { - LLHost source_sim = LLHost(); - LLSD* user_data = new LLSD(); - - if (mNotecardInventoryID.notNull()) - { - user_data->with("objectid", mNotecardObjectID).with("notecardid", mNotecardInventoryID); - } - else if (mObjectUUID.notNull()) - { - LLViewerObject* objectp = gObjectList.findObject(mObjectUUID); - if (objectp && objectp->getRegion()) - { - source_sim = objectp->getRegion()->getHost(); - } - else - { - // The object that we're trying to look at disappeared, bail. - LL_WARNS("MaterialEditor") << "Can't find object " << mObjectUUID << " associated with material." << LL_ENDL; - mAssetID.setNull(); - mAssetStatus = PREVIEW_ASSET_LOADED; - resetUnsavedChanges(); - setEnableEditing(allow_modify && !source_library); - return; - } - user_data->with("taskid", mObjectUUID).with("itemid", mItemUUID); - } - else - { - user_data = new LLSD(mItemUUID); - } - - setEnableEditing(false); // wait for it to load - - mAssetStatus = PREVIEW_ASSET_LOADING; - - // May callback immediately - gAssetStorage->getAssetData(item->getAssetUUID(), - LLAssetType::AT_MATERIAL, - &onLoadComplete, - (void*)user_data, - true); - } - } - } - else if (mObjectUUID.notNull() && mItemUUID.notNull()) - { - LLViewerObject* objectp = gObjectList.findObject(mObjectUUID); - if (objectp && (objectp->isInventoryPending() || objectp->isInventoryDirty())) - { - // It's a material in object's inventory and we failed to get it because inventory is not up to date. - // Subscribe for callback and retry at inventoryChanged() - registerVOInventoryListener(objectp, NULL); //removes previous listener - - if (objectp->isInventoryDirty()) - { - objectp->requestInventory(); - } - } - else - { - fail = true; - } - } - else - { - fail = true; - } - - if (fail) - { - /*editor->setText(LLStringUtil::null); - editor->makePristine(); - editor->setEnabled(true);*/ - // Don't set asset status here; we may not have set the item id yet - // (e.g. when this gets called initially) - //mAssetStatus = PREVIEW_ASSET_LOADED; - } -} - -// static -void LLMaterialEditor::onLoadComplete(const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status) -{ - LLSD* floater_key = (LLSD*)user_data; - LL_DEBUGS("MaterialEditor") << "loading " << asset_uuid << " for " << *floater_key << LL_ENDL; - LLMaterialEditor* editor = LLFloaterReg::findTypedInstance("material_editor", *floater_key); - if (editor) - { - if (asset_uuid != editor->mAssetID) - { - LL_WARNS("MaterialEditor") << "Asset id mismatch, expected: " << editor->mAssetID << " got: " << asset_uuid << LL_ENDL; - } - if (0 == status) - { - LLFileSystem file(asset_uuid, type, LLFileSystem::READ); - - S32 file_length = file.getSize(); - - std::vector buffer(file_length + 1); - file.read((U8*)&buffer[0], file_length); - - editor->decodeAsset(buffer); - - bool allow_modify = editor->canModify(editor->mObjectUUID, editor->getItem()); - bool source_library = editor->mObjectUUID.isNull() && gInventory.isObjectDescendentOf(editor->mItemUUID, gInventory.getLibraryRootFolderID()); - editor->setEnableEditing(allow_modify && !source_library); - editor->resetUnsavedChanges(); - editor->mAssetStatus = PREVIEW_ASSET_LOADED; - editor->setEnabled(true); // ready for use - } - else - { - if (LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status || - LL_ERR_FILE_EMPTY == status) - { - LLNotificationsUtil::add("MaterialMissing"); - } - else if (LL_ERR_INSUFFICIENT_PERMISSIONS == status) - { - // Not supposed to happen? - LL_WARNS("MaterialEditor") << "No permission to view material " << asset_uuid << LL_ENDL; - LLNotificationsUtil::add("MaterialNoPermissions"); - } - else - { - LLNotificationsUtil::add("UnableToLoadMaterial"); - } - editor->setEnableEditing(false); - - LL_WARNS("MaterialEditor") << "Problem loading material: " << status << LL_ENDL; - editor->mAssetStatus = PREVIEW_ASSET_ERROR; - } - } - else - { - LL_DEBUGS("MaterialEditor") << "Floater " << *floater_key << " does not exist." << LL_ENDL; - } - delete floater_key; -} - -void LLMaterialEditor::inventoryChanged(LLViewerObject* object, - LLInventoryObject::object_list_t* inventory, - S32 serial_num, - void* user_data) -{ - removeVOInventoryListener(); - loadAsset(); -} - - -void LLMaterialEditor::saveTexture(LLImageJ2C* img, const std::string& name, const LLUUID& asset_id, upload_callback_f cb) -{ - LLImageDataSharedLock lock(img); - - if (asset_id.isNull() - || img == nullptr - || img->getDataSize() == 0) - { - return; - } - - // copy image bytes into string - std::string buffer; - buffer.assign((const char*) img->getData(), img->getDataSize()); - - U32 expected_upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost(); - - LLSD key = getKey(); - std::function failed_upload([key](LLUUID assetId, LLSD response, std::string reason) - { - LLMaterialEditor* me = LLFloaterReg::findTypedInstance("material_editor", key); - if (me) - { - me->setFailedToUploadTexture(); - } - return true; // handled - }); - - LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared( - buffer, - asset_id, - name, - name, - 0, - LLFolderType::FT_TEXTURE, - LLInventoryType::IT_TEXTURE, - LLAssetType::AT_TEXTURE, - LLFloaterPerms::getNextOwnerPerms("Uploads"), - LLFloaterPerms::getGroupPerms("Uploads"), - LLFloaterPerms::getEveryonePerms("Uploads"), - expected_upload_cost, - false, - cb, - failed_upload)); - - upload_new_resource(uploadInfo); -} - -void LLMaterialEditor::setFailedToUploadTexture() -{ - mUploadingTexturesFailure = true; - mUploadingTexturesCount--; - if (mUploadingTexturesCount == 0) - { - setEnabled(true); - } -} - -S32 LLMaterialEditor::saveTextures() -{ - mUploadingTexturesFailure = false; // not supposed to get here if already uploading - - S32 work_count = 0; - LLSD key = getKey(); // must be locally declared for lambda's capture to work - if (mBaseColorTextureUploadId == getBaseColorId() && mBaseColorTextureUploadId.notNull()) - { - mUploadingTexturesCount++; - work_count++; - - // For ease of inventory management, we prepend the material name. - std::string name = mMaterialName + ": " + mBaseColorName; - - saveTexture(mBaseColorJ2C, name, mBaseColorTextureUploadId, [key](LLUUID newAssetId, LLSD response) - { - LLMaterialEditor* me = LLFloaterReg::findTypedInstance("material_editor", key); - if (me) - { - if (response["success"].asBoolean()) - { - me->setBaseColorId(newAssetId); - - // discard upload buffers once texture have been saved - me->mBaseColorJ2C = nullptr; - me->mBaseColorFetched = nullptr; - me->mBaseColorTextureUploadId.setNull(); - - me->mUploadingTexturesCount--; - - if (!me->mUploadingTexturesFailure) - { - // try saving - me->saveIfNeeded(); - } - else if (me->mUploadingTexturesCount == 0) - { - me->setEnabled(true); - } - } - else - { - // stop upload if possible, unblock and let user decide - me->setFailedToUploadTexture(); - } - } - }); - } - if (mNormalTextureUploadId == getNormalId() && mNormalTextureUploadId.notNull()) - { - mUploadingTexturesCount++; - work_count++; - - // For ease of inventory management, we prepend the material name. - std::string name = mMaterialName + ": " + mNormalName; - - saveTexture(mNormalJ2C, name, mNormalTextureUploadId, [key](LLUUID newAssetId, LLSD response) - { - LLMaterialEditor* me = LLFloaterReg::findTypedInstance("material_editor", key); - if (me) - { - if (response["success"].asBoolean()) - { - me->setNormalId(newAssetId); - - // discard upload buffers once texture have been saved - me->mNormalJ2C = nullptr; - me->mNormalFetched = nullptr; - me->mNormalTextureUploadId.setNull(); - - me->mUploadingTexturesCount--; - - if (!me->mUploadingTexturesFailure) - { - // try saving - me->saveIfNeeded(); - } - else if (me->mUploadingTexturesCount == 0) - { - me->setEnabled(true); - } - } - else - { - // stop upload if possible, unblock and let user decide - me->setFailedToUploadTexture(); - } - } - }); - } - if (mMetallicTextureUploadId == getMetallicRoughnessId() && mMetallicTextureUploadId.notNull()) - { - mUploadingTexturesCount++; - work_count++; - - // For ease of inventory management, we prepend the material name. - std::string name = mMaterialName + ": " + mMetallicRoughnessName; - - saveTexture(mMetallicRoughnessJ2C, name, mMetallicTextureUploadId, [key](LLUUID newAssetId, LLSD response) - { - LLMaterialEditor* me = LLFloaterReg::findTypedInstance("material_editor", key); - if (me) - { - if (response["success"].asBoolean()) - { - me->setMetallicRoughnessId(newAssetId); - - // discard upload buffers once texture have been saved - me->mMetallicRoughnessJ2C = nullptr; - me->mMetallicRoughnessFetched = nullptr; - me->mMetallicTextureUploadId.setNull(); - - me->mUploadingTexturesCount--; - - if (!me->mUploadingTexturesFailure) - { - // try saving - me->saveIfNeeded(); - } - else if (me->mUploadingTexturesCount == 0) - { - me->setEnabled(true); - } - } - else - { - // stop upload if possible, unblock and let user decide - me->setFailedToUploadTexture(); - } - } - }); - } - - if (mEmissiveTextureUploadId == getEmissiveId() && mEmissiveTextureUploadId.notNull()) - { - mUploadingTexturesCount++; - work_count++; - - // For ease of inventory management, we prepend the material name. - std::string name = mMaterialName + ": " + mEmissiveName; - - saveTexture(mEmissiveJ2C, name, mEmissiveTextureUploadId, [key](LLUUID newAssetId, LLSD response) - { - LLMaterialEditor* me = LLFloaterReg::findTypedInstance("material_editor", LLSD(key)); - if (me) - { - if (response["success"].asBoolean()) - { - me->setEmissiveId(newAssetId); - - // discard upload buffers once texture have been saved - me->mEmissiveJ2C = nullptr; - me->mEmissiveFetched = nullptr; - me->mEmissiveTextureUploadId.setNull(); - - me->mUploadingTexturesCount--; - - if (!me->mUploadingTexturesFailure) - { - // try saving - me->saveIfNeeded(); - } - else if (me->mUploadingTexturesCount == 0) - { - me->setEnabled(true); - } - } - else - { - // stop upload if possible, unblock and let user decide - me->setFailedToUploadTexture(); - } - } - }); - } - - if (!work_count) - { - // Discard upload buffers once textures have been confirmed as saved. - // Otherwise we keep buffers for potential upload failure recovery. - clearTextures(); - } - - // asset storage can callback immediately, causing a decrease - // of mUploadingTexturesCount, report amount of work scheduled - // not amount of work remaining - return work_count; -} - -void LLMaterialEditor::clearTextures() -{ - mBaseColorJ2C = nullptr; - mNormalJ2C = nullptr; - mEmissiveJ2C = nullptr; - mMetallicRoughnessJ2C = nullptr; - - mBaseColorFetched = nullptr; - mNormalFetched = nullptr; - mMetallicRoughnessFetched = nullptr; - mEmissiveFetched = nullptr; - - mBaseColorTextureUploadId.setNull(); - mNormalTextureUploadId.setNull(); - mMetallicTextureUploadId.setNull(); - mEmissiveTextureUploadId.setNull(); -} - -void LLMaterialEditor::loadDefaults() -{ - tinygltf::Model model_in; - model_in.materials.resize(1); - setFromGltfModel(model_in, 0, true); -} - -bool LLMaterialEditor::capabilitiesAvailable() -{ - const LLViewerRegion* region = gAgent.getRegion(); - if (!region) - { - LL_WARNS("MaterialEditor") << "Not connected to a region, cannot save material." << LL_ENDL; - return false; - } - std::string agent_url = region->getCapability("UpdateMaterialAgentInventory"); - std::string task_url = region->getCapability("UpdateMaterialTaskInventory"); - - return (!agent_url.empty() && !task_url.empty()); -} - +/** + * @file llmaterialeditor.cpp + * @brief Implementation of the gltf material editor + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llmaterialeditor.h" + +#include "llagent.h" +#include "llagentbenefits.h" +#include "llappviewer.h" +#include "llcolorswatch.h" +#include "llcombobox.h" +#include "llfloaterreg.h" +#include "llfilesystem.h" +#include "llgltfmateriallist.h" +#include "llinventorymodel.h" +#include "llinventoryobserver.h" +#include "llinventoryfunctions.h" +#include "lllocalgltfmaterials.h" +#include "llnotificationsutil.h" +#include "lltexturectrl.h" +#include "lltrans.h" +#include "llviewercontrol.h" +#include "llviewermenufile.h" +#include "llviewertexture.h" +#include "llsdutil.h" +#include "llselectmgr.h" +#include "llstatusbar.h" // can_afford_transaction() +#include "lltoolpie.h" +#include "llviewerinventory.h" +#include "llinventory.h" +#include "llviewerregion.h" +#include "llvovolume.h" +#include "roles_constants.h" +#include "llviewerobjectlist.h" +#include "llsdserialize.h" +#include "llimagej2c.h" +#include "llviewertexturelist.h" +#include "llfloaterperms.h" + +#include "tinygltf/tiny_gltf.h" +#include "lltinygltfhelper.h" +#include + + +const std::string MATERIAL_BASE_COLOR_DEFAULT_NAME = "Base Color"; +const std::string MATERIAL_NORMAL_DEFAULT_NAME = "Normal"; +const std::string MATERIAL_METALLIC_DEFAULT_NAME = "Metallic Roughness"; +const std::string MATERIAL_EMISSIVE_DEFAULT_NAME = "Emissive"; + +// Dirty flags +static const U32 MATERIAL_BASE_COLOR_DIRTY = 0x1 << 0; +static const U32 MATERIAL_BASE_COLOR_TEX_DIRTY = 0x1 << 1; + +static const U32 MATERIAL_NORMAL_TEX_DIRTY = 0x1 << 2; + +static const U32 MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY = 0x1 << 3; +static const U32 MATERIAL_METALLIC_ROUGHTNESS_METALNESS_DIRTY = 0x1 << 4; +static const U32 MATERIAL_METALLIC_ROUGHTNESS_ROUGHNESS_DIRTY = 0x1 << 5; + +static const U32 MATERIAL_EMISIVE_COLOR_DIRTY = 0x1 << 6; +static const U32 MATERIAL_EMISIVE_TEX_DIRTY = 0x1 << 7; + +static const U32 MATERIAL_DOUBLE_SIDED_DIRTY = 0x1 << 8; +static const U32 MATERIAL_ALPHA_MODE_DIRTY = 0x1 << 9; +static const U32 MATERIAL_ALPHA_CUTOFF_DIRTY = 0x1 << 10; + +LLUUID LLMaterialEditor::mOverrideObjectId; +S32 LLMaterialEditor::mOverrideObjectTE = -1; +bool LLMaterialEditor::mOverrideInProgress = false; +bool LLMaterialEditor::mSelectionNeedsUpdate = true; + +LLFloaterComboOptions::LLFloaterComboOptions() + : LLFloater(LLSD()) +{ + buildFromFile("floater_combobox_ok_cancel.xml"); +} + +LLFloaterComboOptions::~LLFloaterComboOptions() +{ + +} + +bool LLFloaterComboOptions::postBuild() +{ + mConfirmButton = getChild("combo_ok", true); + mCancelButton = getChild("combo_cancel", true); + mComboOptions = getChild("combo_options", true); + mComboText = getChild("combo_text", true); + + mConfirmButton->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) {onConfirm(); }); + mCancelButton->setCommitCallback([this](LLUICtrl* ctrl, const LLSD& param) {onCancel(); }); + + return true; +} + +LLFloaterComboOptions* LLFloaterComboOptions::showUI( + combo_callback callback, + const std::string &title, + const std::string &description, + const std::list &options) +{ + LLFloaterComboOptions* combo_picker = new LLFloaterComboOptions(); + if (combo_picker) + { + combo_picker->mCallback = callback; + combo_picker->setTitle(title); + + combo_picker->mComboText->setText(description); + + std::list::const_iterator iter = options.begin(); + std::list::const_iterator end = options.end(); + for (; iter != end; iter++) + { + combo_picker->mComboOptions->addSimpleElement(*iter); + } + combo_picker->mComboOptions->selectFirstItem(); + + combo_picker->openFloater(LLSD(title)); + combo_picker->setFocus(true); + combo_picker->center(); + } + return combo_picker; +} + +LLFloaterComboOptions* LLFloaterComboOptions::showUI( + combo_callback callback, + const std::string &title, + const std::string &description, + const std::string &ok_text, + const std::string &cancel_text, + const std::list &options) +{ + LLFloaterComboOptions* combo_picker = showUI(callback, title, description, options); + if (combo_picker) + { + combo_picker->mConfirmButton->setLabel(ok_text); + combo_picker->mCancelButton->setLabel(cancel_text); + } + return combo_picker; +} + +void LLFloaterComboOptions::onConfirm() +{ + mCallback(mComboOptions->getSimple(), mComboOptions->getCurrentIndex()); + closeFloater(); +} + +void LLFloaterComboOptions::onCancel() +{ + mCallback(std::string(), -1); + closeFloater(); +} + +class LLMaterialEditorCopiedCallback : public LLInventoryCallback +{ +public: + LLMaterialEditorCopiedCallback( + const std::string &buffer, + const LLSD &old_key, + bool has_unsaved_changes) + : mBuffer(buffer), + mOldKey(old_key), + mHasUnsavedChanges(has_unsaved_changes) + {} + + LLMaterialEditorCopiedCallback( + const LLSD &old_key, + const std::string &new_name) + : mOldKey(old_key), + mNewName(new_name), + mHasUnsavedChanges(false) + {} + + virtual void fire(const LLUUID& inv_item_id) + { + if (!mNewName.empty()) + { + // making a copy from a notecard doesn't change name, do it now + LLViewerInventoryItem* item = gInventory.getItem(inv_item_id); + if (item->getName() != mNewName) + { + LLSD updates; + updates["name"] = mNewName; + update_inventory_item(inv_item_id, updates, NULL); + } + } + LLMaterialEditor::finishSaveAs(mOldKey, inv_item_id, mBuffer, mHasUnsavedChanges); + } + +private: + std::string mBuffer; + LLSD mOldKey; + std::string mNewName; + bool mHasUnsavedChanges; +}; + +///---------------------------------------------------------------------------- +/// Class LLSelectedTEGetMatData +/// For finding selected applicable inworld material +///---------------------------------------------------------------------------- + +struct LLSelectedTEGetMatData : public LLSelectedTEFunctor +{ + LLSelectedTEGetMatData(bool for_override); + + bool apply(LLViewerObject* objectp, S32 te_index); + + bool mIsOverride; + bool mIdenticalTexColor; + bool mIdenticalTexMetal; + bool mIdenticalTexEmissive; + bool mIdenticalTexNormal; + bool mFirst; + LLUUID mTexColorId; + LLUUID mTexMetalId; + LLUUID mTexEmissiveId; + LLUUID mTexNormalId; + LLUUID mObjectId; + LLViewerObject* mObject = nullptr; + S32 mObjectTE; + LLUUID mMaterialId; + LLPointer mMaterial; + LLPointer mLocalMaterial; +}; + +LLSelectedTEGetMatData::LLSelectedTEGetMatData(bool for_override) + : mIsOverride(for_override) + , mIdenticalTexColor(true) + , mIdenticalTexMetal(true) + , mIdenticalTexEmissive(true) + , mIdenticalTexNormal(true) + , mObjectTE(-1) + , mFirst(true) +{} + +bool LLSelectedTEGetMatData::apply(LLViewerObject* objectp, S32 te_index) +{ + if (!objectp) + { + return false; + } + LLUUID mat_id = objectp->getRenderMaterialID(te_index); + mMaterialId = mat_id; + bool can_use = mIsOverride ? objectp->permModify() : objectp->permCopy(); + LLTextureEntry *tep = objectp->getTE(te_index); + // We might want to disable this entirely if at least + // something in selection is no-copy or no modify + // or has no base material + if (can_use && tep && mat_id.notNull()) + { + if (mIsOverride) + { + LLPointer mat = tep->getGLTFRenderMaterial(); + + LLUUID tex_color_id; + LLUUID tex_metal_id; + LLUUID tex_emissive_id; + LLUUID tex_normal_id; + llassert(mat.notNull()); // by this point shouldn't be null + if (mat.notNull()) + { + tex_color_id = mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR]; + tex_metal_id = mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS]; + tex_emissive_id = mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE]; + tex_normal_id = mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL]; + } + if (mFirst) + { + mMaterial = mat; + mTexColorId = tex_color_id; + mTexMetalId = tex_metal_id; + mTexEmissiveId = tex_emissive_id; + mTexNormalId = tex_normal_id; + mObjectTE = te_index; + mObject = objectp; + mObjectId = objectp->getID(); + mFirst = false; + } + else + { + if (mTexColorId != tex_color_id) + { + mIdenticalTexColor = false; + } + if (mTexMetalId != tex_metal_id) + { + mIdenticalTexMetal = false; + } + if (mTexEmissiveId != tex_emissive_id) + { + mIdenticalTexEmissive = false; + } + if (mTexNormalId != tex_normal_id) + { + mIdenticalTexNormal = false; + } + } + } + else + { + LLGLTFMaterial *mat = tep->getGLTFMaterial(); + LLLocalGLTFMaterial *local_mat = dynamic_cast(mat); + + mObject = objectp; + mObjectId = objectp->getID(); + if (local_mat) + { + mLocalMaterial = local_mat; + } + mMaterial = tep->getGLTFRenderMaterial(); + + if (mMaterial.isNull()) + { + // Shouldn't be possible? + LL_WARNS("MaterialEditor") << "Object has material id, but no material" << LL_ENDL; + mMaterial = gGLTFMaterialList.getMaterial(mat_id); + } + } + return true; + } + return false; +} + +class LLSelectedTEUpdateOverrides: public LLSelectedNodeFunctor +{ +public: + LLSelectedTEUpdateOverrides(LLMaterialEditor* me) : mEditor(me) {} + + virtual bool apply(LLSelectNode* nodep); + + LLMaterialEditor* mEditor; +}; + +bool LLSelectedTEUpdateOverrides::apply(LLSelectNode* nodep) +{ + LLViewerObject* objectp = nodep->getObject(); + if (!objectp) + { + return false; + } + S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); // avatars have TEs but no faces + for (S32 te_index = 0; te_index < num_tes; ++te_index) + { + + LLTextureEntry* tep = objectp->getTE(te_index); + LLGLTFMaterial* override_mat = tep->getGLTFMaterialOverride(); + if (mEditor->updateMaterialLocalSubscription(override_mat)) + { + LLGLTFMaterial* render_mat = tep->getGLTFRenderMaterial(); + mEditor->updateMaterialLocalSubscription(render_mat); + } + } + + return true; +} + +///---------------------------------------------------------------------------- +/// Class LLMaterialEditor +///---------------------------------------------------------------------------- + +// Default constructor +LLMaterialEditor::LLMaterialEditor(const LLSD& key) + : LLPreview(key) + , mUnsavedChanges(0) + , mRevertedChanges(0) + , mExpectedUploadCost(0) + , mUploadingTexturesCount(0) + , mUploadingTexturesFailure(false) +{ + const LLInventoryItem* item = getItem(); + if (item) + { + mAssetID = item->getAssetUUID(); + } +} + +LLMaterialEditor::~LLMaterialEditor() +{ +} + +void LLMaterialEditor::setObjectID(const LLUUID& object_id) +{ + LLPreview::setObjectID(object_id); + const LLInventoryItem* item = getItem(); + if (item) + { + mAssetID = item->getAssetUUID(); + } +} + +void LLMaterialEditor::setAuxItem(const LLInventoryItem* item) +{ + LLPreview::setAuxItem(item); + if (item) + { + mAssetID = item->getAssetUUID(); + } +} + +bool LLMaterialEditor::postBuild() +{ + // if this is a 'live editor' instance, it is also + // single instance and uses live overrides + mIsOverride = getIsSingleInstance(); + + mBaseColorTextureCtrl = getChild("base_color_texture"); + mMetallicTextureCtrl = getChild("metallic_roughness_texture"); + mEmissiveTextureCtrl = getChild("emissive_texture"); + mNormalTextureCtrl = getChild("normal_texture"); + mBaseColorCtrl = getChild("base color"); + mEmissiveColorCtrl = getChild("emissive color"); + + if (!gAgent.isGodlike()) + { + // Only allow fully permissive textures + mBaseColorTextureCtrl->setFilterPermissionMasks(PERM_COPY | PERM_TRANSFER); + mMetallicTextureCtrl->setFilterPermissionMasks(PERM_COPY | PERM_TRANSFER); + mEmissiveTextureCtrl->setFilterPermissionMasks(PERM_COPY | PERM_TRANSFER); + mNormalTextureCtrl->setFilterPermissionMasks(PERM_COPY | PERM_TRANSFER); + } + + // Texture callback + mBaseColorTextureCtrl->setCommitCallback(boost::bind(&LLMaterialEditor::onCommitTexture, this, _1, _2, MATERIAL_BASE_COLOR_TEX_DIRTY)); + mMetallicTextureCtrl->setCommitCallback(boost::bind(&LLMaterialEditor::onCommitTexture, this, _1, _2, MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY)); + mEmissiveTextureCtrl->setCommitCallback(boost::bind(&LLMaterialEditor::onCommitTexture, this, _1, _2, MATERIAL_EMISIVE_TEX_DIRTY)); + mNormalTextureCtrl->setCommitCallback(boost::bind(&LLMaterialEditor::onCommitTexture, this, _1, _2, MATERIAL_NORMAL_TEX_DIRTY)); + + mNormalTextureCtrl->setBlankImageAssetID(BLANK_OBJECT_NORMAL); + + if (mIsOverride) + { + // Live editing needs a recovery mechanism on cancel + mBaseColorTextureCtrl->setOnCancelCallback(boost::bind(&LLMaterialEditor::onCancelCtrl, this, _1, _2, MATERIAL_BASE_COLOR_TEX_DIRTY)); + mMetallicTextureCtrl->setOnCancelCallback(boost::bind(&LLMaterialEditor::onCancelCtrl, this, _1, _2, MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY)); + mEmissiveTextureCtrl->setOnCancelCallback(boost::bind(&LLMaterialEditor::onCancelCtrl, this, _1, _2, MATERIAL_EMISIVE_TEX_DIRTY)); + mNormalTextureCtrl->setOnCancelCallback(boost::bind(&LLMaterialEditor::onCancelCtrl, this, _1, _2, MATERIAL_NORMAL_TEX_DIRTY)); + + // Save applied changes on 'OK' to our recovery mechanism. + mBaseColorTextureCtrl->setOnSelectCallback(boost::bind(&LLMaterialEditor::onSelectCtrl, this, _1, _2, MATERIAL_BASE_COLOR_TEX_DIRTY)); + mMetallicTextureCtrl->setOnSelectCallback(boost::bind(&LLMaterialEditor::onSelectCtrl, this, _1, _2, MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY)); + mEmissiveTextureCtrl->setOnSelectCallback(boost::bind(&LLMaterialEditor::onSelectCtrl, this, _1, _2, MATERIAL_EMISIVE_TEX_DIRTY)); + mNormalTextureCtrl->setOnSelectCallback(boost::bind(&LLMaterialEditor::onSelectCtrl, this, _1, _2, MATERIAL_NORMAL_TEX_DIRTY)); + } + else + { + mBaseColorTextureCtrl->setCanApplyImmediately(false); + mMetallicTextureCtrl->setCanApplyImmediately(false); + mEmissiveTextureCtrl->setCanApplyImmediately(false); + mNormalTextureCtrl->setCanApplyImmediately(false); + } + + if (!mIsOverride) + { + childSetAction("save", boost::bind(&LLMaterialEditor::onClickSave, this)); + childSetAction("save_as", boost::bind(&LLMaterialEditor::onClickSaveAs, this)); + childSetAction("cancel", boost::bind(&LLMaterialEditor::onClickCancel, this)); + } + + if (mIsOverride) + { + childSetVisible("base_color_upload_fee", false); + childSetVisible("metallic_upload_fee", false); + childSetVisible("emissive_upload_fee", false); + childSetVisible("normal_upload_fee", false); + } + else + { + S32 upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost(); + getChild("base_color_upload_fee")->setTextArg("[FEE]", llformat("%d", upload_cost)); + getChild("metallic_upload_fee")->setTextArg("[FEE]", llformat("%d", upload_cost)); + getChild("emissive_upload_fee")->setTextArg("[FEE]", llformat("%d", upload_cost)); + getChild("normal_upload_fee")->setTextArg("[FEE]", llformat("%d", upload_cost)); + } + + boost::function changes_callback = [this](LLUICtrl * ctrl, void* userData) + { + const U32 *flag = (const U32*)userData; + markChangesUnsaved(*flag); + // Apply changes to object live + applyToSelection(); + }; + + childSetCommitCallback("double sided", changes_callback, (void*)&MATERIAL_DOUBLE_SIDED_DIRTY); + + // BaseColor + mBaseColorCtrl->setCommitCallback(changes_callback, (void*)&MATERIAL_BASE_COLOR_DIRTY); + if (mIsOverride) + { + mBaseColorCtrl->setOnCancelCallback(boost::bind(&LLMaterialEditor::onCancelCtrl, this, _1, _2, MATERIAL_BASE_COLOR_DIRTY)); + mBaseColorCtrl->setOnSelectCallback(boost::bind(&LLMaterialEditor::onSelectCtrl, this, _1, _2, MATERIAL_BASE_COLOR_DIRTY)); + } + else + { + mBaseColorCtrl->setCanApplyImmediately(false); + } + // transparency is a part of base color + childSetCommitCallback("transparency", changes_callback, (void*)&MATERIAL_BASE_COLOR_DIRTY); + childSetCommitCallback("alpha mode", changes_callback, (void*)&MATERIAL_ALPHA_MODE_DIRTY); + childSetCommitCallback("alpha cutoff", changes_callback, (void*)&MATERIAL_ALPHA_CUTOFF_DIRTY); + + // Metallic-Roughness + childSetCommitCallback("metalness factor", changes_callback, (void*)&MATERIAL_METALLIC_ROUGHTNESS_METALNESS_DIRTY); + childSetCommitCallback("roughness factor", changes_callback, (void*)&MATERIAL_METALLIC_ROUGHTNESS_ROUGHNESS_DIRTY); + + // Emissive + mEmissiveColorCtrl->setCommitCallback(changes_callback, (void*)&MATERIAL_EMISIVE_COLOR_DIRTY); + if (mIsOverride) + { + mEmissiveColorCtrl->setOnCancelCallback(boost::bind(&LLMaterialEditor::onCancelCtrl, this, _1, _2, MATERIAL_EMISIVE_COLOR_DIRTY)); + mEmissiveColorCtrl->setOnSelectCallback(boost::bind(&LLMaterialEditor::onSelectCtrl, this, _1, _2, MATERIAL_EMISIVE_COLOR_DIRTY)); + } + else + { + mEmissiveColorCtrl->setCanApplyImmediately(false); + } + + if (!mIsOverride) + { + // "unsaved_changes" doesn't exist in live editor + childSetVisible("unsaved_changes", mUnsavedChanges); + + // Doesn't exist in live editor + getChild("total_upload_fee")->setTextArg("[FEE]", llformat("%d", 0)); + } + + // Todo: + // Disable/enable setCanApplyImmediately() based on + // working from inventory, upload or editing inworld + + return LLPreview::postBuild(); +} + +void LLMaterialEditor::onClickCloseBtn(bool app_quitting) +{ + if (app_quitting || mIsOverride) + { + closeFloater(app_quitting); + } + else + { + onClickCancel(); + } +} + +void LLMaterialEditor::onClose(bool app_quitting) +{ + if (mSelectionUpdateSlot.connected()) + { + mSelectionUpdateSlot.disconnect(); + } + for (mat_connection_map_t::value_type &cn : mTextureChangesUpdates) + { + cn.second.mConnection.disconnect(); + } + mTextureChangesUpdates.clear(); + + LLPreview::onClose(app_quitting); +} + +void LLMaterialEditor::draw() +{ + if (mIsOverride) + { + if (mSelectionNeedsUpdate) + { + mSelectionNeedsUpdate = false; + clearTextures(); + setFromSelection(); + } + } + LLPreview::draw(); +} + +void LLMaterialEditor::handleReshape(const LLRect& new_rect, bool by_user) +{ + if (by_user) + { + const LLRect old_rect = getRect(); + LLRect clamp_rect(new_rect); + clamp_rect.mRight = clamp_rect.mLeft + old_rect.getWidth(); + LLPreview::handleReshape(clamp_rect, by_user); + } + else + { + LLPreview::handleReshape(new_rect, by_user); + } +} + +LLUUID LLMaterialEditor::getBaseColorId() +{ + return mBaseColorTextureCtrl->getValue().asUUID(); +} + +void LLMaterialEditor::setBaseColorId(const LLUUID& id) +{ + mBaseColorTextureCtrl->setValue(id); + mBaseColorTextureCtrl->setDefaultImageAssetID(id); + mBaseColorTextureCtrl->setTentative(false); +} + +void LLMaterialEditor::setBaseColorUploadId(const LLUUID& id) +{ + // Might be better to use local textures and + // assign a fee in case of a local texture + if (id.notNull()) + { + // todo: this does not account for posibility of texture + // being from inventory, need to check that + childSetValue("base_color_upload_fee", getString("upload_fee_string")); + // Only set if we will need to upload this texture + mBaseColorTextureUploadId = id; + } + markChangesUnsaved(MATERIAL_BASE_COLOR_TEX_DIRTY); +} + +LLColor4 LLMaterialEditor::getBaseColor() +{ + LLColor4 ret = linearColor4(LLColor4(mBaseColorCtrl->getValue())); + ret.mV[3] = getTransparency(); + return ret; +} + +void LLMaterialEditor::setBaseColor(const LLColor4& color) +{ + mBaseColorCtrl->setValue(srgbColor4(color).getValue()); + setTransparency(color.mV[3]); +} + +F32 LLMaterialEditor::getTransparency() +{ + return childGetValue("transparency").asReal(); +} + +void LLMaterialEditor::setTransparency(F32 transparency) +{ + childSetValue("transparency", transparency); +} + +std::string LLMaterialEditor::getAlphaMode() +{ + return childGetValue("alpha mode").asString(); +} + +void LLMaterialEditor::setAlphaMode(const std::string& alpha_mode) +{ + childSetValue("alpha mode", alpha_mode); +} + +F32 LLMaterialEditor::getAlphaCutoff() +{ + return childGetValue("alpha cutoff").asReal(); +} + +void LLMaterialEditor::setAlphaCutoff(F32 alpha_cutoff) +{ + childSetValue("alpha cutoff", alpha_cutoff); +} + +void LLMaterialEditor::setMaterialName(const std::string &name) +{ + setTitle(name); + mMaterialName = name; +} + +LLUUID LLMaterialEditor::getMetallicRoughnessId() +{ + return mMetallicTextureCtrl->getValue().asUUID(); +} + +void LLMaterialEditor::setMetallicRoughnessId(const LLUUID& id) +{ + mMetallicTextureCtrl->setValue(id); + mMetallicTextureCtrl->setDefaultImageAssetID(id); + mMetallicTextureCtrl->setTentative(false); +} + +void LLMaterialEditor::setMetallicRoughnessUploadId(const LLUUID& id) +{ + if (id.notNull()) + { + // todo: this does not account for posibility of texture + // being from inventory, need to check that + childSetValue("metallic_upload_fee", getString("upload_fee_string")); + mMetallicTextureUploadId = id; + } + markChangesUnsaved(MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY); +} + +F32 LLMaterialEditor::getMetalnessFactor() +{ + return childGetValue("metalness factor").asReal(); +} + +void LLMaterialEditor::setMetalnessFactor(F32 factor) +{ + childSetValue("metalness factor", factor); +} + +F32 LLMaterialEditor::getRoughnessFactor() +{ + return childGetValue("roughness factor").asReal(); +} + +void LLMaterialEditor::setRoughnessFactor(F32 factor) +{ + childSetValue("roughness factor", factor); +} + +LLUUID LLMaterialEditor::getEmissiveId() +{ + return mEmissiveTextureCtrl->getValue().asUUID(); +} + +void LLMaterialEditor::setEmissiveId(const LLUUID& id) +{ + mEmissiveTextureCtrl->setValue(id); + mEmissiveTextureCtrl->setDefaultImageAssetID(id); + mEmissiveTextureCtrl->setTentative(false); +} + +void LLMaterialEditor::setEmissiveUploadId(const LLUUID& id) +{ + if (id.notNull()) + { + // todo: this does not account for posibility of texture + // being from inventory, need to check that + childSetValue("emissive_upload_fee", getString("upload_fee_string")); + mEmissiveTextureUploadId = id; + } + markChangesUnsaved(MATERIAL_EMISIVE_TEX_DIRTY); +} + +LLColor4 LLMaterialEditor::getEmissiveColor() +{ + return linearColor4(LLColor4(mEmissiveColorCtrl->getValue())); +} + +void LLMaterialEditor::setEmissiveColor(const LLColor4& color) +{ + mEmissiveColorCtrl->setValue(srgbColor4(color).getValue()); +} + +LLUUID LLMaterialEditor::getNormalId() +{ + return mNormalTextureCtrl->getValue().asUUID(); +} + +void LLMaterialEditor::setNormalId(const LLUUID& id) +{ + mNormalTextureCtrl->setValue(id); + mNormalTextureCtrl->setDefaultImageAssetID(id); + mNormalTextureCtrl->setTentative(false); +} + +void LLMaterialEditor::setNormalUploadId(const LLUUID& id) +{ + if (id.notNull()) + { + // todo: this does not account for posibility of texture + // being from inventory, need to check that + childSetValue("normal_upload_fee", getString("upload_fee_string")); + mNormalTextureUploadId = id; + } + markChangesUnsaved(MATERIAL_NORMAL_TEX_DIRTY); +} + +bool LLMaterialEditor::getDoubleSided() +{ + return childGetValue("double sided").asBoolean(); +} + +void LLMaterialEditor::setDoubleSided(bool double_sided) +{ + childSetValue("double sided", double_sided); +} + +void LLMaterialEditor::resetUnsavedChanges() +{ + mUnsavedChanges = 0; + mRevertedChanges = 0; + if (!mIsOverride) + { + childSetVisible("unsaved_changes", false); + setCanSave(false); + + mExpectedUploadCost = 0; + getChild("total_upload_fee")->setTextArg("[FEE]", llformat("%d", mExpectedUploadCost)); + } +} + +void LLMaterialEditor::markChangesUnsaved(U32 dirty_flag) +{ + mUnsavedChanges |= dirty_flag; + if (mIsOverride) + { + // at the moment live editing (mIsOverride) applies everything 'live' + // and "unsaved_changes", save/cancel buttons don't exist there + return; + } + + childSetVisible("unsaved_changes", mUnsavedChanges); + + if (mUnsavedChanges) + { + const LLInventoryItem* item = getItem(); + if (item) + { + //LLPermissions perm(item->getPermissions()); + bool allow_modify = canModify(mObjectUUID, item); + bool source_library = mObjectUUID.isNull() && gInventory.isObjectDescendentOf(mItemUUID, gInventory.getLibraryRootFolderID()); + bool source_notecard = mNotecardInventoryID.notNull(); + + setCanSave(allow_modify && !source_library && !source_notecard); + } + } + else + { + setCanSave(false); + } + + S32 upload_texture_count = 0; + if (mBaseColorTextureUploadId.notNull() && mBaseColorTextureUploadId == getBaseColorId()) + { + upload_texture_count++; + } + if (mMetallicTextureUploadId.notNull() && mMetallicTextureUploadId == getMetallicRoughnessId()) + { + upload_texture_count++; + } + if (mEmissiveTextureUploadId.notNull() && mEmissiveTextureUploadId == getEmissiveId()) + { + upload_texture_count++; + } + if (mNormalTextureUploadId.notNull() && mNormalTextureUploadId == getNormalId()) + { + upload_texture_count++; + } + + mExpectedUploadCost = upload_texture_count * LLAgentBenefitsMgr::current().getTextureUploadCost(); + getChild("total_upload_fee")->setTextArg("[FEE]", llformat("%d", mExpectedUploadCost)); +} + +void LLMaterialEditor::setCanSaveAs(bool value) +{ + if (!mIsOverride) + { + childSetEnabled("save_as", value); + } +} + +void LLMaterialEditor::setCanSave(bool value) +{ + if (!mIsOverride) + { + childSetEnabled("save", value); + } +} + +void LLMaterialEditor::setEnableEditing(bool can_modify) +{ + childSetEnabled("double sided", can_modify); + + // BaseColor + childSetEnabled("base color", can_modify); + childSetEnabled("transparency", can_modify); + childSetEnabled("alpha mode", can_modify); + childSetEnabled("alpha cutoff", can_modify); + + // Metallic-Roughness + childSetEnabled("metalness factor", can_modify); + childSetEnabled("roughness factor", can_modify); + + // Metallic-Roughness + childSetEnabled("metalness factor", can_modify); + childSetEnabled("roughness factor", can_modify); + + // Emissive + childSetEnabled("emissive color", can_modify); + + mBaseColorTextureCtrl->setEnabled(can_modify); + mMetallicTextureCtrl->setEnabled(can_modify); + mEmissiveTextureCtrl->setEnabled(can_modify); + mNormalTextureCtrl->setEnabled(can_modify); +} + +void LLMaterialEditor::subscribeToLocalTexture(S32 dirty_flag, const LLUUID& tracking_id) +{ + if (mTextureChangesUpdates[dirty_flag].mTrackingId != tracking_id) + { + mTextureChangesUpdates[dirty_flag].mConnection.disconnect(); + mTextureChangesUpdates[dirty_flag].mTrackingId = tracking_id; + mTextureChangesUpdates[dirty_flag].mConnection = LLLocalBitmapMgr::getInstance()->setOnChangedCallback(tracking_id, + [this, dirty_flag](const LLUUID& tracking_id, const LLUUID& old_id, const LLUUID& new_id) + { + if (new_id.isNull()) + { + mTextureChangesUpdates[dirty_flag].mConnection.disconnect(); + //mTextureChangesUpdates.erase(dirty_flag); + } + else + { + replaceLocalTexture(old_id, new_id); + } + }); + } +} + +LLUUID LLMaterialEditor::getLocalTextureTrackingIdFromFlag(U32 flag) +{ + mat_connection_map_t::iterator found = mTextureChangesUpdates.find(flag); + if (found != mTextureChangesUpdates.end()) + { + return found->second.mTrackingId; + } + return LLUUID(); +} + +bool LLMaterialEditor::updateMaterialLocalSubscription(LLGLTFMaterial* mat) +{ + if (!mat) + { + return false; + } + + bool res = false; + for (mat_connection_map_t::value_type& cn : mTextureChangesUpdates) + { + LLUUID world_id = LLLocalBitmapMgr::getInstance()->getWorldID(cn.second.mTrackingId); + if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR]) + { + LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(cn.second.mTrackingId, mat); + res = true; + continue; + } + if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS]) + { + LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(cn.second.mTrackingId, mat); + res = true; + continue; + } + if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE]) + { + LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(cn.second.mTrackingId, mat); + res = true; + continue; + } + if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL]) + { + LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(cn.second.mTrackingId, mat); + res = true; + continue; + } + } + return res; +} + +void LLMaterialEditor::replaceLocalTexture(const LLUUID& old_id, const LLUUID& new_id) +{ + // todo: might be a good idea to set mBaseColorTextureUploadId here + // and when texturectrl picks a local texture + if (getBaseColorId() == old_id) + { + mBaseColorTextureCtrl->setValue(new_id); + } + if (mBaseColorTextureCtrl->getDefaultImageAssetID() == old_id) + { + mBaseColorTextureCtrl->setDefaultImageAssetID(new_id); + } + + if (getMetallicRoughnessId() == old_id) + { + mMetallicTextureCtrl->setValue(new_id); + } + if (mMetallicTextureCtrl->getDefaultImageAssetID() == old_id) + { + mMetallicTextureCtrl->setDefaultImageAssetID(new_id); + } + + if (getEmissiveId() == old_id) + { + mEmissiveTextureCtrl->setValue(new_id); + } + if (mEmissiveTextureCtrl->getDefaultImageAssetID() == old_id) + { + mEmissiveTextureCtrl->setDefaultImageAssetID(new_id); + } + + if (getNormalId() == old_id) + { + mNormalTextureCtrl->setValue(new_id); + } + if (mNormalTextureCtrl->getDefaultImageAssetID() == old_id) + { + mNormalTextureCtrl->setDefaultImageAssetID(new_id); + } +} + +void LLMaterialEditor::onCommitTexture(LLUICtrl* ctrl, const LLSD& data, S32 dirty_flag) +{ + if (!mIsOverride) + { + std::string upload_fee_ctrl_name; + LLUUID old_uuid; + + switch (dirty_flag) + { + case MATERIAL_BASE_COLOR_TEX_DIRTY: + { + upload_fee_ctrl_name = "base_color_upload_fee"; + old_uuid = mBaseColorTextureUploadId; + break; + } + case MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY: + { + upload_fee_ctrl_name = "metallic_upload_fee"; + old_uuid = mMetallicTextureUploadId; + break; + } + case MATERIAL_EMISIVE_TEX_DIRTY: + { + upload_fee_ctrl_name = "emissive_upload_fee"; + old_uuid = mEmissiveTextureUploadId; + break; + } + case MATERIAL_NORMAL_TEX_DIRTY: + { + upload_fee_ctrl_name = "normal_upload_fee"; + old_uuid = mNormalTextureUploadId; + break; + } + default: + break; + } + LLUUID new_val = ctrl->getValue().asUUID(); + if (new_val == old_uuid && old_uuid.notNull()) + { + childSetValue(upload_fee_ctrl_name, getString("upload_fee_string")); + } + else + { + // Texture picker has 'apply now' with 'cancel' support. + // Don't clean mBaseColorJ2C and mBaseColorFetched, it's our + // storage in case user decides to cancel changes. + // Without mBaseColorFetched, viewer will eventually cleanup + // the texture that is not in use + childSetValue(upload_fee_ctrl_name, getString("no_upload_fee_string")); + } + } + + LLTextureCtrl* tex_ctrl = (LLTextureCtrl*)ctrl; + if (tex_ctrl->isImageLocal()) + { + subscribeToLocalTexture(dirty_flag, tex_ctrl->getLocalTrackingID()); + } + else + { + // unsubcribe potential old callabck + mat_connection_map_t::iterator found = mTextureChangesUpdates.find(dirty_flag); + if (found != mTextureChangesUpdates.end()) + { + found->second.mConnection.disconnect(); + } + } + + markChangesUnsaved(dirty_flag); + applyToSelection(); +} + +void LLMaterialEditor::onCancelCtrl(LLUICtrl* ctrl, const LLSD& data, S32 dirty_flag) +{ + mRevertedChanges |= dirty_flag; + applyToSelection(); +} + +void update_local_texture(LLUICtrl* ctrl, LLGLTFMaterial* mat) +{ + LLTextureCtrl* tex_ctrl = (LLTextureCtrl*)ctrl; + if (tex_ctrl->isImageLocal()) + { + // subscrive material to updates of local textures + LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tex_ctrl->getLocalTrackingID(), mat); + } +} + +void LLMaterialEditor::onSelectCtrl(LLUICtrl* ctrl, const LLSD& data, S32 dirty_flag) +{ + mUnsavedChanges |= dirty_flag; + applyToSelection(); + + struct f : public LLSelectedNodeFunctor + { + f(LLUICtrl* ctrl, S32 dirty_flag) : mCtrl(ctrl), mDirtyFlag(dirty_flag) + { + } + + virtual bool apply(LLSelectNode* nodep) + { + LLViewerObject* objectp = nodep->getObject(); + if (!objectp) + { + return false; + } + S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); // avatars have TEs but no faces + for (S32 te = 0; te < num_tes; ++te) + { + if (nodep->isTESelected(te) && nodep->mSavedGLTFOverrideMaterials.size() > te) + { + if (nodep->mSavedGLTFOverrideMaterials[te].isNull()) + { + // populate with default values, default values basically mean 'not in use' + nodep->mSavedGLTFOverrideMaterials[te] = new LLGLTFMaterial(); + } + + switch (mDirtyFlag) + { + //Textures + case MATERIAL_BASE_COLOR_TEX_DIRTY: + { + nodep->mSavedGLTFOverrideMaterials[te]->setBaseColorId(mCtrl->getValue().asUUID(), true); + update_local_texture(mCtrl, nodep->mSavedGLTFOverrideMaterials[te].get()); + break; + } + case MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY: + { + nodep->mSavedGLTFOverrideMaterials[te]->setOcclusionRoughnessMetallicId(mCtrl->getValue().asUUID(), true); + update_local_texture(mCtrl, nodep->mSavedGLTFOverrideMaterials[te].get()); + break; + } + case MATERIAL_EMISIVE_TEX_DIRTY: + { + nodep->mSavedGLTFOverrideMaterials[te]->setEmissiveId(mCtrl->getValue().asUUID(), true); + update_local_texture(mCtrl, nodep->mSavedGLTFOverrideMaterials[te].get()); + break; + } + case MATERIAL_NORMAL_TEX_DIRTY: + { + nodep->mSavedGLTFOverrideMaterials[te]->setNormalId(mCtrl->getValue().asUUID(), true); + update_local_texture(mCtrl, nodep->mSavedGLTFOverrideMaterials[te].get()); + break; + } + // Colors + case MATERIAL_BASE_COLOR_DIRTY: + { + LLColor4 ret = linearColor4(LLColor4(mCtrl->getValue())); + // except transparency + ret.mV[3] = nodep->mSavedGLTFOverrideMaterials[te]->mBaseColor.mV[3]; + nodep->mSavedGLTFOverrideMaterials[te]->setBaseColorFactor(ret, true); + break; + } + case MATERIAL_EMISIVE_COLOR_DIRTY: + { + nodep->mSavedGLTFOverrideMaterials[te]->setEmissiveColorFactor(LLColor3(mCtrl->getValue()), true); + break; + } + default: + break; + } + } + } + return true; + } + + LLUICtrl* mCtrl; + S32 mDirtyFlag; + } func(ctrl, dirty_flag); + + LLSelectMgr::getInstance()->getSelection()->applyToNodes(&func); +} + +void LLMaterialEditor::onClickSave() +{ + if (!capabilitiesAvailable()) + { + LLNotificationsUtil::add("MissingMaterialCaps"); + return; + } + if (!can_afford_transaction(mExpectedUploadCost)) + { + LLSD args; + args["COST"] = llformat("%d", mExpectedUploadCost); + LLNotificationsUtil::add("ErrorCannotAffordUpload", args); + return; + } + + applyToSelection(); + saveIfNeeded(); +} + +std::string LLMaterialEditor::getEncodedAsset() +{ + LLSD asset; + asset["version"] = LLGLTFMaterial::ASSET_VERSION; + asset["type"] = LLGLTFMaterial::ASSET_TYPE; + LLGLTFMaterial mat; + getGLTFMaterial(&mat); + asset["data"] = mat.asJSON(); + + std::ostringstream str; + LLSDSerialize::serialize(asset, str, LLSDSerialize::LLSD_BINARY); + + return str.str(); +} + +bool LLMaterialEditor::decodeAsset(const std::vector& buffer) +{ + LLSD asset; + + std::istrstream str(&buffer[0], buffer.size()); + if (LLSDSerialize::deserialize(asset, str, buffer.size())) + { + if (asset.has("version") && LLGLTFMaterial::isAcceptedVersion(asset["version"].asString())) + { + if (asset.has("type") && asset["type"] == LLGLTFMaterial::ASSET_TYPE) + { + if (asset.has("data") && asset["data"].isString()) + { + std::string data = asset["data"]; + + tinygltf::TinyGLTF gltf; + tinygltf::TinyGLTF loader; + std::string error_msg; + std::string warn_msg; + + tinygltf::Model model_in; + + if (loader.LoadASCIIFromString(&model_in, &error_msg, &warn_msg, data.c_str(), data.length(), "")) + { + // assets are only supposed to have one item + // *NOTE: This duplicates some functionality from + // LLGLTFMaterial::fromJSON, but currently does the job + // better for the material editor use case. + // However, LLGLTFMaterial::asJSON should always be + // used when uploading materials, to ensure the + // asset is valid. + return setFromGltfModel(model_in, 0, true); + } + else + { + LL_WARNS("MaterialEditor") << "Floater " << getKey() << " Failed to decode material asset: " << LL_NEWLINE + << warn_msg << LL_NEWLINE + << error_msg << LL_ENDL; + } + } + } + } + else + { + LL_WARNS("MaterialEditor") << "Invalid LLSD content "<< asset << " for flaoter " << getKey() << LL_ENDL; + } + } + else + { + LL_WARNS("MaterialEditor") << "Failed to deserialize material LLSD for flaoter " << getKey() << LL_ENDL; + } + + return false; +} + +/** + * Build a description of the material we just imported. + * Currently this means a list of the textures present but we + * may eventually want to make it more complete - will be guided + * by what the content creators say they need. + */ +const std::string LLMaterialEditor::buildMaterialDescription() +{ + std::ostringstream desc; + desc << LLTrans::getString("Material Texture Name Header"); + + // add the texture names for each just so long as the material + // we loaded has an entry for it (i think testing the texture + // control UUI for NULL is a valid metric for if it was loaded + // or not but I suspect this code will change a lot so may need + // to revisit + if (!mBaseColorTextureCtrl->getValue().asUUID().isNull()) + { + desc << mBaseColorName; + desc << ", "; + } + if (!mMetallicTextureCtrl->getValue().asUUID().isNull()) + { + desc << mMetallicRoughnessName; + desc << ", "; + } + if (!mEmissiveTextureCtrl->getValue().asUUID().isNull()) + { + desc << mEmissiveName; + desc << ", "; + } + if (!mNormalTextureCtrl->getValue().asUUID().isNull()) + { + desc << mNormalName; + } + + // trim last char if it's a ',' in case there is no normal texture + // present and the code above inserts one + // (no need to check for string length - always has initial string) + std::string::iterator iter = desc.str().end() - 1; + if (*iter == ',') + { + desc.str().erase(iter); + } + + // sanitize the material description so that it's compatible with the inventory + // note: split this up because clang doesn't like operating directly on the + // str() - error: lvalue reference to type 'basic_string<...>' cannot bind to a + // temporary of type 'basic_string<...>' + std::string inv_desc = desc.str(); + LLInventoryObject::correctInventoryName(inv_desc); + + return inv_desc; +} + +bool LLMaterialEditor::saveIfNeeded() +{ + if (mUploadingTexturesCount > 0) + { + // Upload already in progress, wait until + // textures upload will retry saving on callback. + // Also should prevent some failure-callbacks + return true; + } + + if (saveTextures() > 0) + { + // started texture upload + setEnabled(false); + return true; + } + + std::string buffer = getEncodedAsset(); + + const LLInventoryItem* item = getItem(); + // save it out to database + if (item) + { + if (!updateInventoryItem(buffer, mItemUUID, mObjectUUID)) + { + return false; + } + + if (mCloseAfterSave) + { + closeFloater(); + } + else + { + mAssetStatus = PREVIEW_ASSET_LOADING; + setEnabled(false); + } + } + else + { + // Make a new inventory item and set upload permissions + LLPermissions local_permissions; + local_permissions.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null); + + if (mIsOverride) + { + // Shouldn't happen, but just in case it ever changes + U32 everyone_perm = LLFloaterPerms::getEveryonePerms("Materials"); + U32 group_perm = LLFloaterPerms::getGroupPerms("Materials"); + U32 next_owner_perm = LLFloaterPerms::getNextOwnerPerms("Materials"); + local_permissions.initMasks(PERM_ALL, PERM_ALL, everyone_perm, group_perm, next_owner_perm); + + } + else + { + // Uploads are supposed to use Upload permissions, not material permissions + U32 everyone_perm = LLFloaterPerms::getEveryonePerms("Uploads"); + U32 group_perm = LLFloaterPerms::getGroupPerms("Uploads"); + U32 next_owner_perm = LLFloaterPerms::getNextOwnerPerms("Uploads"); + local_permissions.initMasks(PERM_ALL, PERM_ALL, everyone_perm, group_perm, next_owner_perm); + } + + std::string res_desc = buildMaterialDescription(); + createInventoryItem(buffer, mMaterialName, res_desc, local_permissions); + + // We do not update floater with uploaded asset yet, so just close it. + closeFloater(); + } + + return true; +} + +// static +bool LLMaterialEditor::updateInventoryItem(const std::string &buffer, const LLUUID &item_id, const LLUUID &task_id) +{ + const LLViewerRegion* region = gAgent.getRegion(); + if (!region) + { + LL_WARNS("MaterialEditor") << "Not connected to a region, cannot save material." << LL_ENDL; + return false; + } + std::string agent_url = region->getCapability("UpdateMaterialAgentInventory"); + std::string task_url = region->getCapability("UpdateMaterialTaskInventory"); + + if (!agent_url.empty() && !task_url.empty()) + { + std::string url; + LLResourceUploadInfo::ptr_t uploadInfo; + + if (task_id.isNull() && !agent_url.empty()) + { + uploadInfo = std::make_shared(item_id, LLAssetType::AT_MATERIAL, buffer, + [](LLUUID itemId, LLUUID newAssetId, LLUUID newItemId, LLSD) + { + // done callback + LLMaterialEditor::finishInventoryUpload(itemId, newAssetId, newItemId); + }, + [](LLUUID itemId, LLUUID taskId, LLSD response, std::string reason) + { + // failure callback + LLMaterialEditor* me = LLFloaterReg::findTypedInstance("material_editor", LLSD(itemId)); + if (me) + { + me->setEnabled(true); + } + return true; + } + ); + url = agent_url; + } + else if (!task_id.isNull() && !task_url.empty()) + { + uploadInfo = std::make_shared(task_id, item_id, LLAssetType::AT_MATERIAL, buffer, + [](LLUUID itemId, LLUUID task_id, LLUUID newAssetId, LLSD) + { + // done callback + LLMaterialEditor::finishTaskUpload(itemId, newAssetId, task_id); + }, + [](LLUUID itemId, LLUUID task_id, LLSD response, std::string reason) + { + // failure callback + LLSD floater_key; + floater_key["taskid"] = task_id; + floater_key["itemid"] = itemId; + LLMaterialEditor* me = LLFloaterReg::findTypedInstance("material_editor", floater_key); + if (me) + { + me->setEnabled(true); + } + return true; + } + ); + url = task_url; + } + + if (!url.empty() && uploadInfo) + { + LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo); + } + else + { + return false; + } + + } + else // !gAssetStorage + { + LL_WARNS("MaterialEditor") << "Not connected to an materials capable region." << LL_ENDL; + return false; + } + + // todo: apply permissions from textures here if server doesn't + // if any texture is 'no transfer', material should be 'no transfer' as well + + return true; +} + +// Callback intended for when a material is saved from an object and needs to +// be modified to reflect the new asset/name. +class LLObjectsMaterialItemCallback : public LLInventoryCallback +{ +public: + LLObjectsMaterialItemCallback(const LLPermissions& permissions, const std::string& asset_data, const std::string& new_name) + : mPermissions(permissions), + mAssetData(asset_data), + mNewName(new_name) + { + } + + void fire(const LLUUID& inv_item_id) override + { + LLViewerInventoryItem* item = gInventory.getItem(inv_item_id); + if (!item) + { + return; + } + + // Name may or may not have already been applied + const bool changed_name = item->getName() != mNewName; + // create_inventory_item/copy_inventory_item don't allow presetting some permissions, fix it now + const bool changed_permissions = item->getPermissions() != mPermissions; + const bool changed = changed_name || changed_permissions; + LLSD updates; + if (changed) + { + if (changed_name) + { + updates["name"] = mNewName; + } + if (changed_permissions) + { + updates["permissions"] = ll_create_sd_from_permissions(mPermissions); + } + update_inventory_item(inv_item_id, updates, NULL); + } + + // from reference in LLSettingsVOBase::createInventoryItem()/updateInventoryItem() + LLResourceUploadInfo::ptr_t uploadInfo = + std::make_shared( + inv_item_id, + LLAssetType::AT_MATERIAL, + mAssetData, + [changed, updates](LLUUID item_id, LLUUID new_asset_id, LLUUID new_item_id, LLSD response) + { + // done callback + LL_INFOS("Material") << "inventory item uploaded. item: " << item_id << " new_item_id: " << new_item_id << " response: " << response << LL_ENDL; + + // *HACK: Sometimes permissions do not stick in the UI. They are correct on the server-side, though. + if (changed) + { + update_inventory_item(new_item_id, updates, NULL); + } + }, + nullptr // failure callback, floater already closed + ); + + const LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + std::string agent_url(region->getCapability("UpdateMaterialAgentInventory")); + if (agent_url.empty()) + { + LL_ERRS("MaterialEditor") << "missing required agent inventory cap url" << LL_ENDL; + } + LLViewerAssetUpload::EnqueueInventoryUpload(agent_url, uploadInfo); + } + } +private: + LLPermissions mPermissions; + std::string mAssetData; + std::string mNewName; +}; + +void LLMaterialEditor::createInventoryItem(const std::string &buffer, const std::string &name, const std::string &desc, const LLPermissions& permissions) +{ + // gen a new uuid for this asset + LLTransactionID tid; + tid.generate(); // timestamp-based randomization + uniquification + LLUUID parent = gInventory.findUserDefinedCategoryUUIDForType(LLFolderType::FT_MATERIAL); + const U8 subtype = NO_INV_SUBTYPE; // TODO maybe use AT_SETTINGS and LLSettingsType::ST_MATERIAL ? + + LLPointer cb = new LLObjectsMaterialItemCallback(permissions, buffer, name); + create_inventory_item(gAgent.getID(), gAgent.getSessionID(), parent, tid, name, desc, + LLAssetType::AT_MATERIAL, LLInventoryType::IT_MATERIAL, subtype, permissions.getMaskNextOwner(), + cb); +} + +void LLMaterialEditor::finishInventoryUpload(LLUUID itemId, LLUUID newAssetId, LLUUID newItemId) +{ + // Update the UI with the new asset. + LLMaterialEditor* me = LLFloaterReg::findTypedInstance("material_editor", LLSD(itemId)); + if (me) + { + if (newItemId.isNull()) + { + me->setAssetId(newAssetId); + me->refreshFromInventory(); + } + else if (newItemId.notNull()) + { + // Not supposed to happen? + me->refreshFromInventory(newItemId); + } + else + { + me->refreshFromInventory(itemId); + } + + if (me && !me->mTextureChangesUpdates.empty()) + { + const LLInventoryItem* item = me->getItem(); + if (item) + { + // local materials were assigned, force load material and init tracking + LLGLTFMaterial* mat = gGLTFMaterialList.getMaterial(item->getAssetUUID()); + for (mat_connection_map_t::value_type &val : me->mTextureChangesUpdates) + { + LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(val.second.mTrackingId, mat); + } + } + } + } +} + +void LLMaterialEditor::finishTaskUpload(LLUUID itemId, LLUUID newAssetId, LLUUID taskId) +{ + LLSD floater_key; + floater_key["taskid"] = taskId; + floater_key["itemid"] = itemId; + LLMaterialEditor* me = LLFloaterReg::findTypedInstance("material_editor", floater_key); + if (me) + { + me->setAssetId(newAssetId); + me->refreshFromInventory(); + me->setEnabled(true); + + if (me && !me->mTextureChangesUpdates.empty()) + { + // local materials were assigned, force load material and init tracking + LLGLTFMaterial* mat = gGLTFMaterialList.getMaterial(newAssetId); + for (mat_connection_map_t::value_type &val : me->mTextureChangesUpdates) + { + LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(val.second.mTrackingId, mat); + } + } + } +} + +void LLMaterialEditor::finishSaveAs( + const LLSD &oldKey, + const LLUUID &newItemId, + const std::string &buffer, + bool has_unsaved_changes) +{ + LLMaterialEditor* me = LLFloaterReg::findTypedInstance("material_editor", oldKey); + LLViewerInventoryItem* item = gInventory.getItem(newItemId); + if (item) + { + if (me) + { + me->mItemUUID = newItemId; + me->mObjectUUID = LLUUID::null; + me->mNotecardInventoryID = LLUUID::null; + me->mNotecardObjectID = LLUUID::null; + me->mAuxItem = nullptr; + me->setKey(LLSD(newItemId)); // for findTypedInstance + me->setMaterialName(item->getName()); + if (has_unsaved_changes) + { + if (!updateInventoryItem(buffer, newItemId, LLUUID::null)) + { + me->setEnabled(true); + } + } + else + { + me->loadAsset(); + me->setEnabled(true); + + // Local texure support + if (!me->mTextureChangesUpdates.empty()) + { + // local materials were assigned, force load material and init tracking + LLGLTFMaterial* mat = gGLTFMaterialList.getMaterial(item->getAssetUUID()); + for (mat_connection_map_t::value_type &val : me->mTextureChangesUpdates) + { + LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(val.second.mTrackingId, mat); + } + } + } + } + else if(has_unsaved_changes) + { + updateInventoryItem(buffer, newItemId, LLUUID::null); + } + } + else if (me) + { + me->setEnabled(true); + LL_WARNS("MaterialEditor") << "Item does not exist, floater " << me->getKey() << LL_ENDL; + } +} + +void LLMaterialEditor::refreshFromInventory(const LLUUID& new_item_id) +{ + if (mIsOverride) + { + // refreshFromInventory shouldn't be called for overrides, + // but just in case. + LL_WARNS("MaterialEditor") << "Tried to refresh from inventory for live editor" << LL_ENDL; + return; + } + LLSD old_key = getKey(); + if (new_item_id.notNull()) + { + mItemUUID = new_item_id; + if (mNotecardInventoryID.notNull()) + { + LLSD floater_key; + floater_key["objectid"] = mNotecardObjectID; + floater_key["notecardid"] = mNotecardInventoryID; + setKey(floater_key); + } + else if (mObjectUUID.notNull()) + { + LLSD floater_key; + floater_key["taskid"] = new_item_id; + floater_key["itemid"] = mObjectUUID; + setKey(floater_key); + } + else + { + setKey(LLSD(new_item_id)); + } + } + LL_DEBUGS("MaterialEditor") << "New floater key: " << getKey() << " Old key: " << old_key << LL_ENDL; + loadAsset(); +} + + +void LLMaterialEditor::onClickSaveAs() +{ + if (!LLMaterialEditor::capabilitiesAvailable()) + { + LLNotificationsUtil::add("MissingMaterialCaps"); + return; + } + + if (!can_afford_transaction(mExpectedUploadCost)) + { + LLSD args; + args["COST"] = llformat("%d", mExpectedUploadCost); + LLNotificationsUtil::add("ErrorCannotAffordUpload", args); + return; + } + + LLSD args; + args["DESC"] = mMaterialName; + + LLNotificationsUtil::add("SaveMaterialAs", args, LLSD(), boost::bind(&LLMaterialEditor::onSaveAsMsgCallback, this, _1, _2)); +} + +void LLMaterialEditor::onSaveAsMsgCallback(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (0 == option) + { + std::string new_name = response["message"].asString(); + LLInventoryObject::correctInventoryName(new_name); + if (!new_name.empty()) + { + const LLInventoryItem* item; + if (mNotecardInventoryID.notNull()) + { + item = mAuxItem.get(); + } + else + { + item = getItem(); + } + if (item) + { + const LLUUID &marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + LLUUID parent_id = item->getParentUUID(); + if (mObjectUUID.notNull() || marketplacelistings_id == parent_id || gInventory.isObjectDescendentOf(item->getUUID(), gInventory.getLibraryRootFolderID())) + { + parent_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MATERIAL); + } + + // A two step process, first copy an existing item, then create new asset + if (mNotecardInventoryID.notNull()) + { + LLPointer cb = new LLMaterialEditorCopiedCallback(getKey(), new_name); + copy_inventory_from_notecard(parent_id, + mNotecardObjectID, + mNotecardInventoryID, + mAuxItem.get(), + gInventoryCallbacks.registerCB(cb)); + } + else + { + std::string buffer = getEncodedAsset(); + LLPointer cb = new LLMaterialEditorCopiedCallback(buffer, getKey(), mUnsavedChanges); + copy_inventory_item( + gAgent.getID(), + item->getPermissions().getOwner(), + item->getUUID(), + parent_id, + new_name, + cb); + } + + mAssetStatus = PREVIEW_ASSET_LOADING; + setEnabled(false); + } + else + { + setMaterialName(new_name); + onClickSave(); + } + } + else + { + LLNotificationsUtil::add("InvalidMaterialName", LLSD(), LLSD(), [this](const LLSD& notification, const LLSD& response) + { + LLNotificationsUtil::add("SaveMaterialAs", LLSD().with("DESC", mMaterialName), LLSD(), + boost::bind(&LLMaterialEditor::onSaveAsMsgCallback, this, _1, _2)); + }); + } + } +} + +void LLMaterialEditor::onClickCancel() +{ + if (mUnsavedChanges) + { + LLNotificationsUtil::add("UsavedMaterialChanges", LLSD(), LLSD(), boost::bind(&LLMaterialEditor::onCancelMsgCallback, this, _1, _2)); + } + else + { + closeFloater(); + } +} + +void LLMaterialEditor::onCancelMsgCallback(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (0 == option) + { + closeFloater(); + } +} + +static void pack_textures( + LLPointer& base_color_img, + LLPointer& normal_img, + LLPointer& mr_img, + LLPointer& emissive_img, + LLPointer& occlusion_img, + LLPointer& base_color_j2c, + LLPointer& normal_j2c, + LLPointer& mr_j2c, + LLPointer& emissive_j2c) +{ + // NOTE : remove log spam and lossless vs lossy comparisons when the logs are no longer useful + + if (base_color_img) + { + base_color_j2c = LLViewerTextureList::convertToUploadFile(base_color_img); + LL_DEBUGS("MaterialEditor") << "BaseColor: " << base_color_j2c->getDataSize() << LL_ENDL; + } + + if (normal_img) + { + // create a losslessly compressed version of the normal map + normal_j2c = LLViewerTextureList::convertToUploadFile(normal_img, 1024, false, true); + LL_DEBUGS("MaterialEditor") << "Normal: " << normal_j2c->getDataSize() << LL_ENDL; + } + + if (mr_img) + { + mr_j2c = LLViewerTextureList::convertToUploadFile(mr_img); + LL_DEBUGS("MaterialEditor") << "Metallic/Roughness: " << mr_j2c->getDataSize() << LL_ENDL; + } + + if (emissive_img) + { + emissive_j2c = LLViewerTextureList::convertToUploadFile(emissive_img); + LL_DEBUGS("MaterialEditor") << "Emissive: " << emissive_j2c->getDataSize() << LL_ENDL; + } +} + +void LLMaterialEditor::uploadMaterialFromModel(const std::string& filename, tinygltf::Model& model_in, S32 index) +{ + if (index < 0 || !LLMaterialEditor::capabilitiesAvailable()) + { + return; + } + + if (model_in.materials.empty()) + { + // materials are missing + return; + } + + if (index >= 0 && model_in.materials.size() <= index) + { + // material is missing + return; + } + + // Todo: no point in loading whole editor + // This uses 'filename' to make sure multiple bulk uploads work + // instead of fighting for a single instance. + LLMaterialEditor* me = (LLMaterialEditor*)LLFloaterReg::getInstance("material_editor", LLSD().with("filename", filename).with("index", LLSD::Integer(index))); + me->loadMaterial(model_in, filename, index, false); + me->saveIfNeeded(); +} + + +void LLMaterialEditor::loadMaterialFromFile(const std::string& filename, S32 index) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; + + tinygltf::TinyGLTF loader; + std::string error_msg; + std::string warn_msg; + + bool loaded = false; + tinygltf::Model model_in; + + std::string filename_lc = filename; + LLStringUtil::toLower(filename_lc); + + // Load a tinygltf model fom a file. Assumes that the input filename has already been + // been sanitized to one of (.gltf , .glb) extensions, so does a simple find to distinguish. + if (std::string::npos == filename_lc.rfind(".gltf")) + { // file is binary + loaded = loader.LoadBinaryFromFile(&model_in, &error_msg, &warn_msg, filename); + } + else + { // file is ascii + loaded = loader.LoadASCIIFromFile(&model_in, &error_msg, &warn_msg, filename); + } + + if (!loaded) + { + LLNotificationsUtil::add("CannotUploadMaterial"); + return; + } + + if (model_in.materials.empty()) + { + // materials are missing + LLNotificationsUtil::add("CannotUploadMaterial"); + return; + } + + if (index >= 0 && model_in.materials.size() <= index) + { + // material is missing + LLNotificationsUtil::add("CannotUploadMaterial"); + return; + } + + LLMaterialEditor* me = (LLMaterialEditor*)LLFloaterReg::getInstance("material_editor"); + + if (index >= 0) + { + // Prespecified material + me->loadMaterial(model_in, filename, index); + } + else if (model_in.materials.size() == 1) + { + // Only one, just load it + me->loadMaterial(model_in, filename, 0); + } + else + { + // Promt user to select material + std::list material_list; + std::vector::const_iterator mat_iter = model_in.materials.begin(); + std::vector::const_iterator mat_end = model_in.materials.end(); + + for (; mat_iter != mat_end; mat_iter++) + { + std::string mat_name = mat_iter->name; + if (mat_name.empty()) + { + material_list.push_back("Material " + std::to_string(material_list.size())); + } + else + { + material_list.push_back(mat_name); + } + } + + material_list.push_back(me->getString("material_batch_import_text")); + + LLFloaterComboOptions::showUI( + [me, model_in, filename](const std::string& option, S32 index) + { + me->loadMaterial(model_in, filename, index); + }, + me->getString("material_selection_title"), + me->getString("material_selection_text"), + material_list + ); + } +} + +void LLMaterialEditor::onSelectionChanged() +{ + // Drop selection updates if we are waiting for + // overrides to finish applying to not reset values + // (might need a timeout) + if (!mOverrideInProgress) + { + // mUpdateSignal triggers a lot per frame, breakwater + mSelectionNeedsUpdate = true; + } +} + +void LLMaterialEditor::updateLive() +{ + mSelectionNeedsUpdate = true; + mOverrideInProgress = false; +} + +void LLMaterialEditor::loadLive() +{ + LLMaterialEditor* me = (LLMaterialEditor*)LLFloaterReg::getInstance("live_material_editor"); + if (me) + { + me->mOverrideInProgress = false; + me->setFromSelection(); + + // Set up for selection changes updates + if (!me->mSelectionUpdateSlot.connected()) + { + me->mSelectionUpdateSlot = LLSelectMgr::instance().mUpdateSignal.connect(boost::bind(&LLMaterialEditor::onSelectionChanged, me)); + } + + me->openFloater(); + me->setFocus(true); + } +} + +namespace +{ + // Which inventory to consult for item permissions + enum class ItemSource + { + // Consult the permissions of the item in the object's inventory. If + // the item is not present, then usage of the asset is allowed. + OBJECT, + // Consult the permissions of the item in the agent's inventory. If + // the item is not present, then usage of the asset is not allowed. + AGENT + }; + + class LLAssetIDMatchesWithPerms : public LLInventoryCollectFunctor + { + public: + LLAssetIDMatchesWithPerms(const LLUUID& asset_id, const std::vector& ops) : mAssetID(asset_id), mOps(ops) {} + virtual ~LLAssetIDMatchesWithPerms() {} + bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) + { + if (!item || item->getAssetUUID() != mAssetID) + { + return false; + } + LLPermissions item_permissions = item->getPermissions(); + for (PermissionBit op : mOps) + { + if (!gAgent.allowOperation(op, item_permissions, GP_OBJECT_MANIPULATE)) + { + return false; + } + } + return true; + } + + protected: + LLUUID mAssetID; + std::vector mOps; + }; +}; + +// *NOTE: permissions_out includes user preferences for new item creation (LLFloaterPerms) +bool can_use_objects_material(LLSelectedTEGetMatData& func, const std::vector& ops, const ItemSource item_source, LLPermissions& permissions_out, LLViewerInventoryItem*& item_out) +{ + if (!LLMaterialEditor::capabilitiesAvailable()) + { + return false; + } + + // func.mIsOverride=true is used for the singleton material editor floater + // associated with the build floater. This flag also excludes objects from + // the selection that do not satisfy PERM_MODIFY. + llassert(func.mIsOverride); + LLSelectMgr::getInstance()->getSelection()->applyToTEs(&func, true /*first applicable*/); + + if (item_source == ItemSource::AGENT) + { + func.mObjectId = LLUUID::null; + } + LLViewerObject* selected_object = func.mObject; + if (!selected_object) + { + // LLSelectedTEGetMatData can fail if there are no selected faces + // with materials, but we expect at least some object is selected. + llassert(LLSelectMgr::getInstance()->getSelection()->getFirstObject()); + return false; + } + if (selected_object->isInventoryPending()) + { + return false; + } + for (PermissionBit op : ops) + { + if (op == PERM_MODIFY && selected_object->isPermanentEnforced()) + { + return false; + } + } + + // Look for the item to base permissions off of + item_out = nullptr; + const bool blank_material = func.mMaterialId == LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID; + if (!blank_material) + { + LLAssetIDMatchesWithPerms item_has_perms(func.mMaterialId, ops); + if (item_source == ItemSource::OBJECT) + { + LLViewerInventoryItem* item = selected_object->getInventoryItemByAsset(func.mMaterialId); + if (item && !item_has_perms(nullptr, item)) + { + return false; + } + item_out = item; + } + else + { + llassert(item_source == ItemSource::AGENT); + + LLViewerInventoryCategory::cat_array_t cats; + LLViewerInventoryItem::item_array_t items; + gInventory.collectDescendentsIf(LLUUID::null, + cats, + items, + // *NOTE: PBRPickerAgentListener will need + // to be changed if checking the trash is + // disabled + LLInventoryModel::INCLUDE_TRASH, + item_has_perms); + if (items.empty()) + { + return false; + } + item_out = items[0]; + } + } + + LLPermissions item_permissions; + if (item_out) + { + item_permissions = item_out->getPermissions(); + // Update flags for new owner + if (!item_permissions.setOwnerAndGroup(LLUUID::null, gAgent.getID(), LLUUID::null, true)) + { + llassert(false); + return false; + } + } + else + { + item_permissions.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null); + } + + // Use root object for permissions checking + LLViewerObject* root_object = selected_object->getRootEdit(); + LLPermissions* object_permissions_p = LLSelectMgr::getInstance()->findObjectPermissions(root_object); + LLPermissions object_permissions; + if (object_permissions_p) + { + object_permissions.set(*object_permissions_p); + for (PermissionBit op : ops) + { + if (!gAgent.allowOperation(op, object_permissions, GP_OBJECT_MANIPULATE)) + { + return false; + } + } + // Update flags for new owner + if (!object_permissions.setOwnerAndGroup(LLUUID::null, gAgent.getID(), LLUUID::null, true)) + { + llassert(false); + return false; + } + } + else + { + object_permissions.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null); + } + + LLPermissions floater_perm; + floater_perm.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null); + floater_perm.setMaskEveryone(LLFloaterPerms::getEveryonePerms("Materials")); + floater_perm.setMaskGroup(LLFloaterPerms::getGroupPerms("Materials")); + floater_perm.setMaskNext(LLFloaterPerms::getNextOwnerPerms("Materials")); + + // *NOTE: A close inspection of LLPermissions::accumulate shows that + // conflicting UUIDs will be unset. This is acceptable behavior for now. + // The server will populate creator info based on the item creation method + // used. + // *NOTE: As far as I'm aware, there is currently no good way to preserve + // creation history when there's no material item present. In that case, + // the agent who saved the material will be considered the creator. + // -Cosmic,2023-08-07 + if (item_source == ItemSource::AGENT) + { + llassert(blank_material || item_out); // See comment at ItemSource::AGENT definition + + permissions_out.set(item_permissions); + } + else + { + llassert(item_source == ItemSource::OBJECT); + + if (item_out) + { + permissions_out.set(item_permissions); + } + else + { + permissions_out.set(object_permissions); + } + } + permissions_out.accumulate(floater_perm); + + return true; +} + +bool LLMaterialEditor::canModifyObjectsMaterial() +{ + LLSelectedTEGetMatData func(true); + LLPermissions permissions; + LLViewerInventoryItem* item_out; + return can_use_objects_material(func, std::vector({PERM_MODIFY}), ItemSource::OBJECT, permissions, item_out); +} + +bool LLMaterialEditor::canSaveObjectsMaterial() +{ + LLSelectedTEGetMatData func(true); + LLPermissions permissions; + LLViewerInventoryItem* item_out; + return can_use_objects_material(func, std::vector({PERM_COPY, PERM_MODIFY}), ItemSource::AGENT, permissions, item_out); +} + +bool LLMaterialEditor::canClipboardObjectsMaterial() +{ + if (LLSelectMgr::getInstance()->getSelection()->getObjectCount() != 1) + { + return false; + } + + struct LLSelectedTEGetNullMat : public LLSelectedTEFunctor + { + bool apply(LLViewerObject* objectp, S32 te_index) + { + return objectp->getRenderMaterialID(te_index).isNull(); + } + } null_func; + + if (LLSelectMgr::getInstance()->getSelection()->applyToTEs(&null_func)) + { + return true; + } + + LLSelectedTEGetMatData func(true); + LLPermissions permissions; + LLViewerInventoryItem* item_out; + return can_use_objects_material(func, std::vector({PERM_COPY, PERM_MODIFY, PERM_TRANSFER}), ItemSource::OBJECT, permissions, item_out); +} + +void LLMaterialEditor::saveObjectsMaterialAs() +{ + LLSelectedTEGetMatData func(true); + LLPermissions permissions; + LLViewerInventoryItem* item = nullptr; + bool allowed = can_use_objects_material(func, std::vector({PERM_COPY, PERM_MODIFY}), ItemSource::AGENT, permissions, item); + if (!allowed) + { + LL_WARNS("MaterialEditor") << "Failed to save GLTF material from object" << LL_ENDL; + return; + } + const LLUUID item_id = item ? item->getUUID() : LLUUID::null; + saveObjectsMaterialAs(func.mMaterial, func.mLocalMaterial, permissions, func.mObjectId, item_id); +} + + +void LLMaterialEditor::saveObjectsMaterialAs(const LLGLTFMaterial* render_material, const LLLocalGLTFMaterial *local_material, const LLPermissions& permissions, const LLUUID& object_id, const LLUUID& item_id) +{ + if (local_material) + { + // This is a local material, reload it from file + // so that user won't end up with grey textures + // on next login. + LLMaterialEditor::loadMaterialFromFile(local_material->getFilename(), local_material->getIndexInFile()); + + LLMaterialEditor* me = (LLMaterialEditor*)LLFloaterReg::getInstance("material_editor"); + if (me) + { + // don't use override material here, it has 'hacked ids' + // and values, use end result, apply it on top of local. + const LLColor4& base_color = render_material->mBaseColor; + me->setBaseColor(LLColor3(base_color)); + me->setTransparency(base_color[VW]); + me->setMetalnessFactor(render_material->mMetallicFactor); + me->setRoughnessFactor(render_material->mRoughnessFactor); + me->setEmissiveColor(render_material->mEmissiveColor); + me->setDoubleSided(render_material->mDoubleSided); + me->setAlphaMode(render_material->getAlphaMode()); + me->setAlphaCutoff(render_material->mAlphaCutoff); + + // most things like colors we can apply without verifying + // but texture ids are going to be different from both, base and override + // so only apply override id if there is actually a difference + if (local_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR] != render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR]) + { + me->setBaseColorId(render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR]); + me->childSetValue("base_color_upload_fee", me->getString("no_upload_fee_string")); + } + if (local_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL] != render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL]) + { + me->setNormalId(render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL]); + me->childSetValue("normal_upload_fee", me->getString("no_upload_fee_string")); + } + if (local_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS] != render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS]) + { + me->setMetallicRoughnessId(render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS]); + me->childSetValue("metallic_upload_fee", me->getString("no_upload_fee_string")); + } + if (local_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE] != render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE]) + { + me->setEmissiveId(render_material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE]); + me->childSetValue("emissive_upload_fee", me->getString("no_upload_fee_string")); + } + + // recalculate upload prices + me->markChangesUnsaved(0); + } + + return; + } + + LLSD payload; + if (render_material) + { + // Make a copy of the render material with unsupported transforms removed + LLGLTFMaterial asset_material = *render_material; + asset_material.sanitizeAssetMaterial(); + // Serialize the sanitized render material + payload["data"] = asset_material.asJSON(); + } + else + { + // Menu shouldn't allow this, but as a fallback + // pick defaults from a blank material + LLGLTFMaterial blank_mat; + payload["data"] = blank_mat.asJSON(); + LL_WARNS() << "Got no material when trying to save material" << LL_ENDL; + } + + LLSD args; + args["DESC"] = LLTrans::getString("New Material"); + + if (local_material) + { + LLPermissions local_permissions; + local_permissions.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null); + LLNotificationsUtil::add("SaveMaterialAs", args, payload, boost::bind(&LLMaterialEditor::onSaveObjectsMaterialAsMsgCallback, _1, _2, local_permissions)); + } + else + { + llassert(object_id.isNull()); // Case for copying item from object inventory is no longer implemented + LLNotificationsUtil::add("SaveMaterialAs", args, payload, boost::bind(&LLMaterialEditor::onSaveObjectsMaterialAsMsgCallback, _1, _2, permissions)); + } +} + +// static +void LLMaterialEditor::onSaveObjectsMaterialAsMsgCallback(const LLSD& notification, const LLSD& response, const LLPermissions& permissions) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (0 != option) + { + return; + } + + LLSD asset; + asset["version"] = LLGLTFMaterial::ASSET_VERSION; + asset["type"] = LLGLTFMaterial::ASSET_TYPE; + // This is the string serialized from LLGLTFMaterial::asJSON + asset["data"] = notification["payload"]["data"]; + + std::ostringstream str; + LLSDSerialize::serialize(asset, str, LLSDSerialize::LLSD_BINARY); + + std::string new_name = response["message"].asString(); + LLInventoryObject::correctInventoryName(new_name); + if (new_name.empty()) + { + return; + } + + createInventoryItem(str.str(), new_name, std::string(), permissions); +} + +const void upload_bulk(const std::vector& filenames, LLFilePicker::ELoadFilter type); + +void LLMaterialEditor::loadMaterial(const tinygltf::Model &model_in, const std::string &filename, S32 index, bool open_floater) +{ + if (index == model_in.materials.size()) + { + // bulk upload all the things + upload_bulk({ filename }, LLFilePicker::FFLOAD_MATERIAL); + return; + } + + if (model_in.materials.size() <= index) + { + return; + } + std::string folder = gDirUtilp->getDirName(filename); + + tinygltf::Material material_in = model_in.materials[index]; + + tinygltf::Model model_out; + model_out.asset.version = "2.0"; + model_out.materials.resize(1); + + // get base color texture + LLPointer base_color_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.pbrMetallicRoughness.baseColorTexture.index, mBaseColorName); + // get normal map + LLPointer normal_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.normalTexture.index, mNormalName); + // get metallic-roughness texture + LLPointer mr_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.pbrMetallicRoughness.metallicRoughnessTexture.index, mMetallicRoughnessName); + // get emissive texture + LLPointer emissive_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.emissiveTexture.index, mEmissiveName); + // get occlusion map if needed + LLPointer occlusion_img; + if (material_in.occlusionTexture.index != material_in.pbrMetallicRoughness.metallicRoughnessTexture.index) + { + std::string tmp; + occlusion_img = LLTinyGLTFHelper::getTexture(folder, model_in, material_in.occlusionTexture.index, tmp); + } + + LLTinyGLTFHelper::initFetchedTextures(material_in, base_color_img, normal_img, mr_img, emissive_img, occlusion_img, + mBaseColorFetched, mNormalFetched, mMetallicRoughnessFetched, mEmissiveFetched); + pack_textures(base_color_img, normal_img, mr_img, emissive_img, occlusion_img, + mBaseColorJ2C, mNormalJ2C, mMetallicRoughnessJ2C, mEmissiveJ2C); + + LLUUID base_color_id; + if (mBaseColorFetched.notNull()) + { + mBaseColorFetched->forceToSaveRawImage(0, F32_MAX); + base_color_id = mBaseColorFetched->getID(); + + if (mBaseColorName.empty()) + { + mBaseColorName = MATERIAL_BASE_COLOR_DEFAULT_NAME; + } + } + + LLUUID normal_id; + if (mNormalFetched.notNull()) + { + mNormalFetched->forceToSaveRawImage(0, F32_MAX); + normal_id = mNormalFetched->getID(); + + if (mNormalName.empty()) + { + mNormalName = MATERIAL_NORMAL_DEFAULT_NAME; + } + } + + LLUUID mr_id; + if (mMetallicRoughnessFetched.notNull()) + { + mMetallicRoughnessFetched->forceToSaveRawImage(0, F32_MAX); + mr_id = mMetallicRoughnessFetched->getID(); + + if (mMetallicRoughnessName.empty()) + { + mMetallicRoughnessName = MATERIAL_METALLIC_DEFAULT_NAME; + } + } + + LLUUID emissive_id; + if (mEmissiveFetched.notNull()) + { + mEmissiveFetched->forceToSaveRawImage(0, F32_MAX); + emissive_id = mEmissiveFetched->getID(); + + if (mEmissiveName.empty()) + { + mEmissiveName = MATERIAL_EMISSIVE_DEFAULT_NAME; + } + } + + setBaseColorId(base_color_id); + setBaseColorUploadId(base_color_id); + setMetallicRoughnessId(mr_id); + setMetallicRoughnessUploadId(mr_id); + setEmissiveId(emissive_id); + setEmissiveUploadId(emissive_id); + setNormalId(normal_id); + setNormalUploadId(normal_id); + + setFromGltfModel(model_in, index); + + setFromGltfMetaData(filename, model_in, index); + + if (getDoubleSided()) + { + // SL-19392 Double sided materials double the number of pixels that must be rasterized, + // and a great many tools that export GLTF simply leave double sided enabled whether + // or not it is necessary. + LL_DEBUGS("MaterialEditor") << "Defaulting Double Sided to disabled on import" << LL_ENDL; + setDoubleSided(false); + } + + markChangesUnsaved(U32_MAX); + + if (open_floater) + { + openFloater(getKey()); + setFocus(true); + setCanSave(true); + setCanSaveAs(true); + + applyToSelection(); + } +} + +bool LLMaterialEditor::setFromGltfModel(const tinygltf::Model& model, S32 index, bool set_textures) +{ + if (model.materials.size() > index) + { + const tinygltf::Material& material_in = model.materials[index]; + + if (set_textures) + { + S32 index; + LLUUID id; + + // get base color texture + index = material_in.pbrMetallicRoughness.baseColorTexture.index; + if (index >= 0) + { + id.set(model.images[index].uri); + setBaseColorId(id); + } + else + { + setBaseColorId(LLUUID::null); + } + + // get normal map + index = material_in.normalTexture.index; + if (index >= 0) + { + id.set(model.images[index].uri); + setNormalId(id); + } + else + { + setNormalId(LLUUID::null); + } + + // get metallic-roughness texture + index = material_in.pbrMetallicRoughness.metallicRoughnessTexture.index; + if (index >= 0) + { + id.set(model.images[index].uri); + setMetallicRoughnessId(id); + } + else + { + setMetallicRoughnessId(LLUUID::null); + } + + // get emissive texture + index = material_in.emissiveTexture.index; + if (index >= 0) + { + id.set(model.images[index].uri); + setEmissiveId(id); + } + else + { + setEmissiveId(LLUUID::null); + } + } + + setAlphaMode(material_in.alphaMode); + setAlphaCutoff(material_in.alphaCutoff); + + setBaseColor(LLTinyGLTFHelper::getColor(material_in.pbrMetallicRoughness.baseColorFactor)); + setEmissiveColor(LLTinyGLTFHelper::getColor(material_in.emissiveFactor)); + + setMetalnessFactor(material_in.pbrMetallicRoughness.metallicFactor); + setRoughnessFactor(material_in.pbrMetallicRoughness.roughnessFactor); + + setDoubleSided(material_in.doubleSided); + } + + return true; +} + +/** + * Build a texture name from the contents of the (in tinyGLFT parlance) + * Image URI. This often is filepath to the original image on the users' + * local file system. + */ +const std::string LLMaterialEditor::getImageNameFromUri(std::string image_uri, const std::string texture_type) +{ + // getBaseFileName() works differently on each platform and file patchs + // can contain both types of delimiter so unify them then extract the + // base name (no path or extension) + std::replace(image_uri.begin(), image_uri.end(), '\\', gDirUtilp->getDirDelimiter()[0]); + std::replace(image_uri.begin(), image_uri.end(), '/', gDirUtilp->getDirDelimiter()[0]); + const bool strip_extension = true; + std::string stripped_uri = gDirUtilp->getBaseFileName(image_uri, strip_extension); + + // sometimes they can be really long and unwieldy - 64 chars is enough for anyone :) + const int max_texture_name_length = 64; + if (stripped_uri.length() > max_texture_name_length) + { + stripped_uri = stripped_uri.substr(0, max_texture_name_length - 1); + } + + // We intend to append the type of texture (base color, emissive etc.) to the + // name of the texture but sometimes the creator already did that. To try + // to avoid repeats (not perfect), we look for the texture type in the name + // and if we find it, do not append the type, later on. One way this fails + // (and it's fine for now) is I see some texture/image uris have a name like + // "metallic roughness" and of course, that doesn't match our predefined + // name "metallicroughness" - consider fix later.. + bool name_includes_type = false; + std::string stripped_uri_lower = stripped_uri; + LLStringUtil::toLower(stripped_uri_lower); + stripped_uri_lower.erase(std::remove_if(stripped_uri_lower.begin(), stripped_uri_lower.end(), isspace), stripped_uri_lower.end()); + std::string texture_type_lower = texture_type; + LLStringUtil::toLower(texture_type_lower); + texture_type_lower.erase(std::remove_if(texture_type_lower.begin(), texture_type_lower.end(), isspace), texture_type_lower.end()); + if (stripped_uri_lower.find(texture_type_lower) != std::string::npos) + { + name_includes_type = true; + } + + // uri doesn't include the type at all + if (!name_includes_type) + { + // uri doesn't include the type and the uri is not empty + // so we can include everything + if (stripped_uri.length() > 0) + { + // example "DamagedHelmet: base layer" + return STRINGIZE( + mMaterialNameShort << + ": " << + stripped_uri << + " (" << + texture_type << + ")" + ); + } + else + // uri doesn't include the type (because the uri is empty) + // so we must reorganize the string a bit to include the name + // and an explicit name type + { + // example "DamagedHelmet: (Emissive)" + return STRINGIZE( + mMaterialNameShort << + " (" << + texture_type << + ")" + ); + } + } + else + // uri includes the type so just use it directly with the + // name of the material + { + return STRINGIZE( + // example: AlienBust: normal_layer + mMaterialNameShort << + ": " << + stripped_uri + ); + } +} + +/** + * Update the metadata for the material based on what we find in the loaded + * file (along with some assumptions and interpretations...). Fields include + * the name of the material, a material description and the names of the + * composite textures. + */ +void LLMaterialEditor::setFromGltfMetaData(const std::string& filename, const tinygltf::Model& model, S32 index) +{ + // Use the name (without any path/extension) of the file that was + // uploaded as the base of the material name. Then if the name of the + // scene is present and not blank, append that and use the result as + // the name of the material. This is a first pass at creating a + // naming scheme that is useful to real content creators and hopefully + // avoid 500 materials in your inventory called "scene" or "Default" + const bool strip_extension = true; + std::string base_filename = gDirUtilp->getBaseFileName(filename, strip_extension); + + // Extract the name of the scene. Note it is often blank or some very + // generic name like "Scene" or "Default" so using this in the name + // is less useful than you might imagine. + std::string material_name; + if (model.materials.size() > index && !model.materials[index].name.empty()) + { + material_name = model.materials[index].name; + } + else if (model.scenes.size() > 0) + { + const tinygltf::Scene& scene_in = model.scenes[0]; + if (scene_in.name.length()) + { + material_name = scene_in.name; + } + else + { + // scene name is empty so no point using it + } + } + else + { + // scene name isn't present so no point using it + } + + // If we have a valid material or scene name, use it to build the short and + // long versions of the material name. The long version is used + // as you might expect, for the material name. The short version is + // used as part of the image/texture name - the theory is that will + // allow content creators to track the material and the corresponding + // textures + if (material_name.length()) + { + mMaterialNameShort = base_filename; + + mMaterialName = STRINGIZE( + base_filename << + " " << + "(" << + material_name << + ")" + ); + } + else + // otherwise, just use the trimmed filename as is + { + mMaterialNameShort = base_filename; + mMaterialName = base_filename; + } + + // sanitize the material name so that it's compatible with the inventory + LLInventoryObject::correctInventoryName(mMaterialName); + LLInventoryObject::correctInventoryName(mMaterialNameShort); + + // We also set the title of the floater to match the + // name of the material + setTitle(mMaterialName); + + /** + * Extract / derive the names of each composite texture. For each, the + * index is used to to determine which of the "Images" is used. If the index + * is -1 then that texture type is not present in the material (Seems to be + * quite common that a material is missing 1 or more types of texture) + */ + if (model.materials.size() > index) + { + const tinygltf::Material& first_material = model.materials[index]; + + mBaseColorName = MATERIAL_BASE_COLOR_DEFAULT_NAME; + // note: unlike the other textures, base color doesn't have its own entry + // in the tinyGLTF Material struct. Rather, it is taken from a + // sub-texture in the pbrMetallicRoughness member + int index = first_material.pbrMetallicRoughness.baseColorTexture.index; + if (index > -1 && index < model.images.size()) + { + // sanitize the name we decide to use for each texture + std::string texture_name = getImageNameFromUri(model.images[index].uri, MATERIAL_BASE_COLOR_DEFAULT_NAME); + LLInventoryObject::correctInventoryName(texture_name); + mBaseColorName = texture_name; + } + + mEmissiveName = MATERIAL_EMISSIVE_DEFAULT_NAME; + index = first_material.emissiveTexture.index; + if (index > -1 && index < model.images.size()) + { + std::string texture_name = getImageNameFromUri(model.images[index].uri, MATERIAL_EMISSIVE_DEFAULT_NAME); + LLInventoryObject::correctInventoryName(texture_name); + mEmissiveName = texture_name; + } + + mMetallicRoughnessName = MATERIAL_METALLIC_DEFAULT_NAME; + index = first_material.pbrMetallicRoughness.metallicRoughnessTexture.index; + if (index > -1 && index < model.images.size()) + { + std::string texture_name = getImageNameFromUri(model.images[index].uri, MATERIAL_METALLIC_DEFAULT_NAME); + LLInventoryObject::correctInventoryName(texture_name); + mMetallicRoughnessName = texture_name; + } + + mNormalName = MATERIAL_NORMAL_DEFAULT_NAME; + index = first_material.normalTexture.index; + if (index > -1 && index < model.images.size()) + { + std::string texture_name = getImageNameFromUri(model.images[index].uri, MATERIAL_NORMAL_DEFAULT_NAME); + LLInventoryObject::correctInventoryName(texture_name); + mNormalName = texture_name; + } + } +} + +void LLMaterialEditor::importMaterial() +{ + LLFilePickerReplyThread::startPicker( + [](const std::vector& filenames, LLFilePicker::ELoadFilter load_filter, LLFilePicker::ESaveFilter save_filter) + { + if (LLAppViewer::instance()->quitRequested()) + { + return; + } + if (filenames.size() > 0) + { + LLMaterialEditor::loadMaterialFromFile(filenames[0], -1); + } + }, + LLFilePicker::FFLOAD_MATERIAL, + true); +} + +class LLRenderMaterialFunctor : public LLSelectedTEFunctor +{ +public: + LLRenderMaterialFunctor(const LLUUID &id) + : mMatId(id) + { + } + + bool apply(LLViewerObject* objectp, S32 te) override + { + if (objectp && objectp->permModify() && objectp->getVolume()) + { + LLVOVolume* vobjp = (LLVOVolume*)objectp; + vobjp->setRenderMaterialID(te, mMatId, false /*preview only*/); + vobjp->updateTEMaterialTextures(te); + } + return true; + } +private: + LLUUID mMatId; +}; + +class LLRenderMaterialOverrideFunctor : public LLSelectedNodeFunctor +{ +public: + LLRenderMaterialOverrideFunctor( + LLMaterialEditor * me, + const LLUUID &report_on_object_id, + S32 report_on_te) + : mEditor(me) + , mSuccess(false) + , mObjectId(report_on_object_id) + , mObjectTE(report_on_te) + { + } + + virtual bool apply(LLSelectNode* nodep) override + { + LLViewerObject* objectp = nodep->getObject(); + if (!objectp || !objectp->permModify() || !objectp->getVolume()) + { + return false; + } + S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); // avatars have TEs but no faces + + // post override from given object and te to the simulator + // requestData should have: + // object_id - UUID of LLViewerObject + // side - S32 index of texture entry + // gltf_json - String of GLTF json for override data + + for (S32 te = 0; te < num_tes; ++te) + { + if (!nodep->isTESelected(te)) + { + continue; + } + + // Get material from object + // Selection can cover multiple objects, and live editor is + // supposed to overwrite changed values only + LLTextureEntry* tep = objectp->getTE(te); + + if (tep->getGLTFMaterial() == nullptr) + { + // overrides are not supposed to work or apply if + // there is no base material to work from + continue; + } + + LLPointer material = tep->getGLTFMaterialOverride(); + // make a copy to not invalidate existing + // material for multiple objects + if (material.isNull()) + { + // Start with a material override which does not make any changes + material = new LLGLTFMaterial(); + } + else + { + material = new LLGLTFMaterial(*material); + } + + U32 changed_flags = mEditor->getUnsavedChangesFlags(); + U32 reverted_flags = mEditor->getRevertedChangesFlags(); + + LLPointer revert_mat; + if (nodep->mSavedGLTFOverrideMaterials.size() > te) + { + if (nodep->mSavedGLTFOverrideMaterials[te].notNull()) + { + revert_mat = nodep->mSavedGLTFOverrideMaterials[te]; + } + else + { + // mSavedGLTFOverrideMaterials[te] being present but null + // means we need to use a default value + revert_mat = new LLGLTFMaterial(); + } + } + // else can not revert at all + + // Override object's values with values from editor where appropriate + if (changed_flags & MATERIAL_BASE_COLOR_DIRTY) + { + material->setBaseColorFactor(mEditor->getBaseColor(), true); + } + else if ((reverted_flags & MATERIAL_BASE_COLOR_DIRTY) && revert_mat.notNull()) + { + material->setBaseColorFactor(revert_mat->mBaseColor, false); + } + + if (changed_flags & MATERIAL_BASE_COLOR_TEX_DIRTY) + { + material->setBaseColorId(mEditor->getBaseColorId(), true); + LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_BASE_COLOR_TEX_DIRTY); + if (tracking_id.notNull()) + { + LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material); + } + } + else if ((reverted_flags & MATERIAL_BASE_COLOR_TEX_DIRTY) && revert_mat.notNull()) + { + material->setBaseColorId(revert_mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR], false); + LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_BASE_COLOR_TEX_DIRTY); + if (tracking_id.notNull()) + { + LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material); + } + } + + if (changed_flags & MATERIAL_NORMAL_TEX_DIRTY) + { + material->setNormalId(mEditor->getNormalId(), true); + LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_NORMAL_TEX_DIRTY); + if (tracking_id.notNull()) + { + LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material); + } + } + else if ((reverted_flags & MATERIAL_NORMAL_TEX_DIRTY) && revert_mat.notNull()) + { + material->setNormalId(revert_mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL], false); + LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_NORMAL_TEX_DIRTY); + if (tracking_id.notNull()) + { + LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material); + } + } + + if (changed_flags & MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY) + { + material->setOcclusionRoughnessMetallicId(mEditor->getMetallicRoughnessId(), true); + LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY); + if (tracking_id.notNull()) + { + LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material); + } + } + else if ((reverted_flags & MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY) && revert_mat.notNull()) + { + material->setOcclusionRoughnessMetallicId(revert_mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS], false); + LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY); + if (tracking_id.notNull()) + { + LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material); + } + } + + if (changed_flags & MATERIAL_METALLIC_ROUGHTNESS_METALNESS_DIRTY) + { + material->setMetallicFactor(mEditor->getMetalnessFactor(), true); + } + else if ((reverted_flags & MATERIAL_METALLIC_ROUGHTNESS_METALNESS_DIRTY) && revert_mat.notNull()) + { + material->setMetallicFactor(revert_mat->mMetallicFactor, false); + } + + if (changed_flags & MATERIAL_METALLIC_ROUGHTNESS_ROUGHNESS_DIRTY) + { + material->setRoughnessFactor(mEditor->getRoughnessFactor(), true); + } + else if ((reverted_flags & MATERIAL_METALLIC_ROUGHTNESS_ROUGHNESS_DIRTY) && revert_mat.notNull()) + { + material->setRoughnessFactor(revert_mat->mRoughnessFactor, false); + } + + if (changed_flags & MATERIAL_EMISIVE_COLOR_DIRTY) + { + material->setEmissiveColorFactor(LLColor3(mEditor->getEmissiveColor()), true); + } + else if ((reverted_flags & MATERIAL_EMISIVE_COLOR_DIRTY) && revert_mat.notNull()) + { + material->setEmissiveColorFactor(revert_mat->mEmissiveColor, false); + } + + if (changed_flags & MATERIAL_EMISIVE_TEX_DIRTY) + { + material->setEmissiveId(mEditor->getEmissiveId(), true); + LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_EMISIVE_TEX_DIRTY); + if (tracking_id.notNull()) + { + LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material); + } + } + else if ((reverted_flags & MATERIAL_EMISIVE_TEX_DIRTY) && revert_mat.notNull()) + { + material->setEmissiveId(revert_mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE], false); + LLUUID tracking_id = mEditor->getLocalTextureTrackingIdFromFlag(MATERIAL_EMISIVE_TEX_DIRTY); + if (tracking_id.notNull()) + { + LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(tracking_id, material); + } + } + + if (changed_flags & MATERIAL_DOUBLE_SIDED_DIRTY) + { + material->setDoubleSided(mEditor->getDoubleSided(), true); + } + else if ((reverted_flags & MATERIAL_DOUBLE_SIDED_DIRTY) && revert_mat.notNull()) + { + material->setDoubleSided(revert_mat->mDoubleSided, false); + } + + if (changed_flags & MATERIAL_ALPHA_MODE_DIRTY) + { + material->setAlphaMode(mEditor->getAlphaMode(), true); + } + else if ((reverted_flags & MATERIAL_ALPHA_MODE_DIRTY) && revert_mat.notNull()) + { + material->setAlphaMode(revert_mat->mAlphaMode, false); + } + + if (changed_flags & MATERIAL_ALPHA_CUTOFF_DIRTY) + { + material->setAlphaCutoff(mEditor->getAlphaCutoff(), true); + } + else if ((reverted_flags & MATERIAL_ALPHA_CUTOFF_DIRTY) && revert_mat.notNull()) + { + material->setAlphaCutoff(revert_mat->mAlphaCutoff, false); + } + + if (mObjectTE == te + && mObjectId == objectp->getID()) + { + mSuccess = true; + } + LLGLTFMaterialList::queueModify(objectp, te, material); + } + return true; + } + + static void modifyCallback(bool success) + { + if (!success) + { + // something went wrong update selection + LLMaterialEditor::updateLive(); + } + // else we will get updateLive() from panel face + } + + bool getResult() { return mSuccess; } + +private: + LLMaterialEditor * mEditor; + LLUUID mObjectId; + S32 mObjectTE; + bool mSuccess; +}; + +void LLMaterialEditor::applyToSelection() +{ + if (!mIsOverride) + { + // Only apply if working with 'live' materials + // Might need a better way to distinguish 'live' mode. + // But only one live edit is supposed to work at a time + // as a pair to tools floater. + return; + } + + std::string url = gAgent.getRegionCapability("ModifyMaterialParams"); + if (!url.empty()) + { + // Don't send data if there is nothing to send. + // Some UI elements will cause multiple commits, + // like spin ctrls on click and on down + if (mUnsavedChanges != 0 || mRevertedChanges != 0) + { + mOverrideInProgress = true; + LLObjectSelectionHandle selected_objects = LLSelectMgr::getInstance()->getSelection(); + LLRenderMaterialOverrideFunctor override_func(this, mOverrideObjectId, mOverrideObjectTE); + selected_objects->applyToNodes(&override_func); + + void(*done_callback)(bool) = LLRenderMaterialOverrideFunctor::modifyCallback; + + LLGLTFMaterialList::flushUpdates(done_callback); + + if (!override_func.getResult()) + { + // OverrideFunctor didn't find expected object or face + mOverrideInProgress = false; + } + + // we posted all changes + mUnsavedChanges = 0; + mRevertedChanges = 0; + } + } + else + { + LL_WARNS("MaterialEditor") << "Not connected to materials capable region, missing ModifyMaterialParams cap" << LL_ENDL; + + // Fallback local preview. Will be removed once override systems is finished and new cap is deployed everywhere. + LLPointer mat = new LLFetchedGLTFMaterial(); + getGLTFMaterial(mat); + static const LLUUID placeholder("984e183e-7811-4b05-a502-d79c6f978a98"); + gGLTFMaterialList.addMaterial(placeholder, mat); + LLRenderMaterialFunctor mat_func(placeholder); + LLObjectSelectionHandle selected_objects = LLSelectMgr::getInstance()->getSelection(); + selected_objects->applyToTEs(&mat_func); + } +} + +// Get a dump of the json representation of the current state of the editor UI +// in GLTF format, excluding transforms as they are not supported in material +// assets. (See also LLGLTFMaterial::sanitizeAssetMaterial()) +void LLMaterialEditor::getGLTFMaterial(LLGLTFMaterial* mat) +{ + mat->mBaseColor = getBaseColor(); + mat->mBaseColor.mV[3] = getTransparency(); + mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR] = getBaseColorId(); + + mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL] = getNormalId(); + + mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS] = getMetallicRoughnessId(); + mat->mMetallicFactor = getMetalnessFactor(); + mat->mRoughnessFactor = getRoughnessFactor(); + + mat->mEmissiveColor = getEmissiveColor(); + mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE] = getEmissiveId(); + + mat->mDoubleSided = getDoubleSided(); + mat->setAlphaMode(getAlphaMode()); + mat->mAlphaCutoff = getAlphaCutoff(); +} + +void LLMaterialEditor::setFromGLTFMaterial(LLGLTFMaterial* mat) +{ + setBaseColor(mat->mBaseColor); + setBaseColorId(mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR]); + setNormalId(mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL]); + + setMetallicRoughnessId(mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS]); + setMetalnessFactor(mat->mMetallicFactor); + setRoughnessFactor(mat->mRoughnessFactor); + + setEmissiveColor(mat->mEmissiveColor); + setEmissiveId(mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE]); + + setDoubleSided(mat->mDoubleSided); + setAlphaMode(mat->getAlphaMode()); + setAlphaCutoff(mat->mAlphaCutoff); + + if (mat->hasLocalTextures()) + { + for (LLGLTFMaterial::local_tex_map_t::value_type &val : mat->mTrackingIdToLocalTexture) + { + LLUUID world_id = LLLocalBitmapMgr::getInstance()->getWorldID(val.first); + if (val.second != world_id) + { + LL_WARNS() << "world id mismatch" << LL_ENDL; + } + if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR]) + { + subscribeToLocalTexture(MATERIAL_BASE_COLOR_TEX_DIRTY, val.first); + } + if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS]) + { + subscribeToLocalTexture(MATERIAL_METALLIC_ROUGHTNESS_TEX_DIRTY, val.first); + } + if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE]) + { + subscribeToLocalTexture(MATERIAL_EMISIVE_TEX_DIRTY, val.first); + } + if (world_id == mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL]) + { + subscribeToLocalTexture(MATERIAL_NORMAL_TEX_DIRTY, val.first); + } + } + } +} + +bool LLMaterialEditor::setFromSelection() +{ + LLObjectSelectionHandle selected_objects = LLSelectMgr::getInstance()->getSelection(); + LLSelectedTEGetMatData func(mIsOverride); + + selected_objects->applyToTEs(&func); + mHasSelection = !selected_objects->isEmpty(); + mSelectionNeedsUpdate = false; + + if (func.mMaterial.notNull()) + { + setFromGLTFMaterial(func.mMaterial); + LLViewerObject* selected_object = func.mObject; + const LLViewerInventoryItem* item = selected_object->getInventoryItemByAsset(func.mMaterialId); + const bool allow_modify = !item || canModify(selected_object, item); + setEnableEditing(allow_modify); + + // todo: apply local texture data to all materials in selection + } + else + { + // pick defaults from a blank material; + LLGLTFMaterial blank_mat; + setFromGLTFMaterial(&blank_mat); + if (mIsOverride) + { + setEnableEditing(false); + } + } + + if (mIsOverride) + { + mBaseColorTextureCtrl->setTentative(!func.mIdenticalTexColor); + mMetallicTextureCtrl->setTentative(!func.mIdenticalTexMetal); + mEmissiveTextureCtrl->setTentative(!func.mIdenticalTexEmissive); + mNormalTextureCtrl->setTentative(!func.mIdenticalTexNormal); + + // Memorize selection data for filtering further updates + mOverrideObjectId = func.mObjectId; + mOverrideObjectTE = func.mObjectTE; + + // Ovverdired might have been updated, + // refresh state of local textures in overrides + // + // Todo: this probably shouldn't be here, but in localbitmap, + // subscried to all material overrides if we want copied + // objects to get properly updated as well + LLSelectedTEUpdateOverrides local_tex_func(this); + selected_objects->applyToNodes(&local_tex_func); + } + + return func.mMaterial.notNull(); +} + + +void LLMaterialEditor::loadAsset() +{ + const LLInventoryItem* item; + if (mNotecardInventoryID.notNull()) + { + item = mAuxItem.get(); + } + else + { + item = getItem(); + } + + bool fail = false; + + if (item) + { + LLPermissions perm(item->getPermissions()); + bool allow_copy = gAgent.allowOperation(PERM_COPY, perm, GP_OBJECT_MANIPULATE); + bool allow_modify = canModify(mObjectUUID, item); + bool source_library = mObjectUUID.isNull() && gInventory.isObjectDescendentOf(mItemUUID, gInventory.getLibraryRootFolderID()); + + setCanSaveAs(allow_copy); + setMaterialName(item->getName()); + + { + mAssetID = item->getAssetUUID(); + + if (mAssetID.isNull()) + { + mAssetStatus = PREVIEW_ASSET_LOADED; + loadDefaults(); + resetUnsavedChanges(); + setEnableEditing(allow_modify && !source_library); + } + else + { + LLHost source_sim = LLHost(); + LLSD* user_data = new LLSD(); + + if (mNotecardInventoryID.notNull()) + { + user_data->with("objectid", mNotecardObjectID).with("notecardid", mNotecardInventoryID); + } + else if (mObjectUUID.notNull()) + { + LLViewerObject* objectp = gObjectList.findObject(mObjectUUID); + if (objectp && objectp->getRegion()) + { + source_sim = objectp->getRegion()->getHost(); + } + else + { + // The object that we're trying to look at disappeared, bail. + LL_WARNS("MaterialEditor") << "Can't find object " << mObjectUUID << " associated with material." << LL_ENDL; + mAssetID.setNull(); + mAssetStatus = PREVIEW_ASSET_LOADED; + resetUnsavedChanges(); + setEnableEditing(allow_modify && !source_library); + return; + } + user_data->with("taskid", mObjectUUID).with("itemid", mItemUUID); + } + else + { + user_data = new LLSD(mItemUUID); + } + + setEnableEditing(false); // wait for it to load + + mAssetStatus = PREVIEW_ASSET_LOADING; + + // May callback immediately + gAssetStorage->getAssetData(item->getAssetUUID(), + LLAssetType::AT_MATERIAL, + &onLoadComplete, + (void*)user_data, + true); + } + } + } + else if (mObjectUUID.notNull() && mItemUUID.notNull()) + { + LLViewerObject* objectp = gObjectList.findObject(mObjectUUID); + if (objectp && (objectp->isInventoryPending() || objectp->isInventoryDirty())) + { + // It's a material in object's inventory and we failed to get it because inventory is not up to date. + // Subscribe for callback and retry at inventoryChanged() + registerVOInventoryListener(objectp, NULL); //removes previous listener + + if (objectp->isInventoryDirty()) + { + objectp->requestInventory(); + } + } + else + { + fail = true; + } + } + else + { + fail = true; + } + + if (fail) + { + /*editor->setText(LLStringUtil::null); + editor->makePristine(); + editor->setEnabled(true);*/ + // Don't set asset status here; we may not have set the item id yet + // (e.g. when this gets called initially) + //mAssetStatus = PREVIEW_ASSET_LOADED; + } +} + +// static +void LLMaterialEditor::onLoadComplete(const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status) +{ + LLSD* floater_key = (LLSD*)user_data; + LL_DEBUGS("MaterialEditor") << "loading " << asset_uuid << " for " << *floater_key << LL_ENDL; + LLMaterialEditor* editor = LLFloaterReg::findTypedInstance("material_editor", *floater_key); + if (editor) + { + if (asset_uuid != editor->mAssetID) + { + LL_WARNS("MaterialEditor") << "Asset id mismatch, expected: " << editor->mAssetID << " got: " << asset_uuid << LL_ENDL; + } + if (0 == status) + { + LLFileSystem file(asset_uuid, type, LLFileSystem::READ); + + S32 file_length = file.getSize(); + + std::vector buffer(file_length + 1); + file.read((U8*)&buffer[0], file_length); + + editor->decodeAsset(buffer); + + bool allow_modify = editor->canModify(editor->mObjectUUID, editor->getItem()); + bool source_library = editor->mObjectUUID.isNull() && gInventory.isObjectDescendentOf(editor->mItemUUID, gInventory.getLibraryRootFolderID()); + editor->setEnableEditing(allow_modify && !source_library); + editor->resetUnsavedChanges(); + editor->mAssetStatus = PREVIEW_ASSET_LOADED; + editor->setEnabled(true); // ready for use + } + else + { + if (LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status || + LL_ERR_FILE_EMPTY == status) + { + LLNotificationsUtil::add("MaterialMissing"); + } + else if (LL_ERR_INSUFFICIENT_PERMISSIONS == status) + { + // Not supposed to happen? + LL_WARNS("MaterialEditor") << "No permission to view material " << asset_uuid << LL_ENDL; + LLNotificationsUtil::add("MaterialNoPermissions"); + } + else + { + LLNotificationsUtil::add("UnableToLoadMaterial"); + } + editor->setEnableEditing(false); + + LL_WARNS("MaterialEditor") << "Problem loading material: " << status << LL_ENDL; + editor->mAssetStatus = PREVIEW_ASSET_ERROR; + } + } + else + { + LL_DEBUGS("MaterialEditor") << "Floater " << *floater_key << " does not exist." << LL_ENDL; + } + delete floater_key; +} + +void LLMaterialEditor::inventoryChanged(LLViewerObject* object, + LLInventoryObject::object_list_t* inventory, + S32 serial_num, + void* user_data) +{ + removeVOInventoryListener(); + loadAsset(); +} + + +void LLMaterialEditor::saveTexture(LLImageJ2C* img, const std::string& name, const LLUUID& asset_id, upload_callback_f cb) +{ + LLImageDataSharedLock lock(img); + + if (asset_id.isNull() + || img == nullptr + || img->getDataSize() == 0) + { + return; + } + + // copy image bytes into string + std::string buffer; + buffer.assign((const char*) img->getData(), img->getDataSize()); + + U32 expected_upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost(); + + LLSD key = getKey(); + std::function failed_upload([key](LLUUID assetId, LLSD response, std::string reason) + { + LLMaterialEditor* me = LLFloaterReg::findTypedInstance("material_editor", key); + if (me) + { + me->setFailedToUploadTexture(); + } + return true; // handled + }); + + LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared( + buffer, + asset_id, + name, + name, + 0, + LLFolderType::FT_TEXTURE, + LLInventoryType::IT_TEXTURE, + LLAssetType::AT_TEXTURE, + LLFloaterPerms::getNextOwnerPerms("Uploads"), + LLFloaterPerms::getGroupPerms("Uploads"), + LLFloaterPerms::getEveryonePerms("Uploads"), + expected_upload_cost, + false, + cb, + failed_upload)); + + upload_new_resource(uploadInfo); +} + +void LLMaterialEditor::setFailedToUploadTexture() +{ + mUploadingTexturesFailure = true; + mUploadingTexturesCount--; + if (mUploadingTexturesCount == 0) + { + setEnabled(true); + } +} + +S32 LLMaterialEditor::saveTextures() +{ + mUploadingTexturesFailure = false; // not supposed to get here if already uploading + + S32 work_count = 0; + LLSD key = getKey(); // must be locally declared for lambda's capture to work + if (mBaseColorTextureUploadId == getBaseColorId() && mBaseColorTextureUploadId.notNull()) + { + mUploadingTexturesCount++; + work_count++; + + // For ease of inventory management, we prepend the material name. + std::string name = mMaterialName + ": " + mBaseColorName; + + saveTexture(mBaseColorJ2C, name, mBaseColorTextureUploadId, [key](LLUUID newAssetId, LLSD response) + { + LLMaterialEditor* me = LLFloaterReg::findTypedInstance("material_editor", key); + if (me) + { + if (response["success"].asBoolean()) + { + me->setBaseColorId(newAssetId); + + // discard upload buffers once texture have been saved + me->mBaseColorJ2C = nullptr; + me->mBaseColorFetched = nullptr; + me->mBaseColorTextureUploadId.setNull(); + + me->mUploadingTexturesCount--; + + if (!me->mUploadingTexturesFailure) + { + // try saving + me->saveIfNeeded(); + } + else if (me->mUploadingTexturesCount == 0) + { + me->setEnabled(true); + } + } + else + { + // stop upload if possible, unblock and let user decide + me->setFailedToUploadTexture(); + } + } + }); + } + if (mNormalTextureUploadId == getNormalId() && mNormalTextureUploadId.notNull()) + { + mUploadingTexturesCount++; + work_count++; + + // For ease of inventory management, we prepend the material name. + std::string name = mMaterialName + ": " + mNormalName; + + saveTexture(mNormalJ2C, name, mNormalTextureUploadId, [key](LLUUID newAssetId, LLSD response) + { + LLMaterialEditor* me = LLFloaterReg::findTypedInstance("material_editor", key); + if (me) + { + if (response["success"].asBoolean()) + { + me->setNormalId(newAssetId); + + // discard upload buffers once texture have been saved + me->mNormalJ2C = nullptr; + me->mNormalFetched = nullptr; + me->mNormalTextureUploadId.setNull(); + + me->mUploadingTexturesCount--; + + if (!me->mUploadingTexturesFailure) + { + // try saving + me->saveIfNeeded(); + } + else if (me->mUploadingTexturesCount == 0) + { + me->setEnabled(true); + } + } + else + { + // stop upload if possible, unblock and let user decide + me->setFailedToUploadTexture(); + } + } + }); + } + if (mMetallicTextureUploadId == getMetallicRoughnessId() && mMetallicTextureUploadId.notNull()) + { + mUploadingTexturesCount++; + work_count++; + + // For ease of inventory management, we prepend the material name. + std::string name = mMaterialName + ": " + mMetallicRoughnessName; + + saveTexture(mMetallicRoughnessJ2C, name, mMetallicTextureUploadId, [key](LLUUID newAssetId, LLSD response) + { + LLMaterialEditor* me = LLFloaterReg::findTypedInstance("material_editor", key); + if (me) + { + if (response["success"].asBoolean()) + { + me->setMetallicRoughnessId(newAssetId); + + // discard upload buffers once texture have been saved + me->mMetallicRoughnessJ2C = nullptr; + me->mMetallicRoughnessFetched = nullptr; + me->mMetallicTextureUploadId.setNull(); + + me->mUploadingTexturesCount--; + + if (!me->mUploadingTexturesFailure) + { + // try saving + me->saveIfNeeded(); + } + else if (me->mUploadingTexturesCount == 0) + { + me->setEnabled(true); + } + } + else + { + // stop upload if possible, unblock and let user decide + me->setFailedToUploadTexture(); + } + } + }); + } + + if (mEmissiveTextureUploadId == getEmissiveId() && mEmissiveTextureUploadId.notNull()) + { + mUploadingTexturesCount++; + work_count++; + + // For ease of inventory management, we prepend the material name. + std::string name = mMaterialName + ": " + mEmissiveName; + + saveTexture(mEmissiveJ2C, name, mEmissiveTextureUploadId, [key](LLUUID newAssetId, LLSD response) + { + LLMaterialEditor* me = LLFloaterReg::findTypedInstance("material_editor", LLSD(key)); + if (me) + { + if (response["success"].asBoolean()) + { + me->setEmissiveId(newAssetId); + + // discard upload buffers once texture have been saved + me->mEmissiveJ2C = nullptr; + me->mEmissiveFetched = nullptr; + me->mEmissiveTextureUploadId.setNull(); + + me->mUploadingTexturesCount--; + + if (!me->mUploadingTexturesFailure) + { + // try saving + me->saveIfNeeded(); + } + else if (me->mUploadingTexturesCount == 0) + { + me->setEnabled(true); + } + } + else + { + // stop upload if possible, unblock and let user decide + me->setFailedToUploadTexture(); + } + } + }); + } + + if (!work_count) + { + // Discard upload buffers once textures have been confirmed as saved. + // Otherwise we keep buffers for potential upload failure recovery. + clearTextures(); + } + + // asset storage can callback immediately, causing a decrease + // of mUploadingTexturesCount, report amount of work scheduled + // not amount of work remaining + return work_count; +} + +void LLMaterialEditor::clearTextures() +{ + mBaseColorJ2C = nullptr; + mNormalJ2C = nullptr; + mEmissiveJ2C = nullptr; + mMetallicRoughnessJ2C = nullptr; + + mBaseColorFetched = nullptr; + mNormalFetched = nullptr; + mMetallicRoughnessFetched = nullptr; + mEmissiveFetched = nullptr; + + mBaseColorTextureUploadId.setNull(); + mNormalTextureUploadId.setNull(); + mMetallicTextureUploadId.setNull(); + mEmissiveTextureUploadId.setNull(); +} + +void LLMaterialEditor::loadDefaults() +{ + tinygltf::Model model_in; + model_in.materials.resize(1); + setFromGltfModel(model_in, 0, true); +} + +bool LLMaterialEditor::capabilitiesAvailable() +{ + const LLViewerRegion* region = gAgent.getRegion(); + if (!region) + { + LL_WARNS("MaterialEditor") << "Not connected to a region, cannot save material." << LL_ENDL; + return false; + } + std::string agent_url = region->getCapability("UpdateMaterialAgentInventory"); + std::string task_url = region->getCapability("UpdateMaterialTaskInventory"); + + return (!agent_url.empty() && !task_url.empty()); +} + diff --git a/indra/newview/llmaterialeditor.h b/indra/newview/llmaterialeditor.h index 88e79a3cf0..be4aa219cd 100644 --- a/indra/newview/llmaterialeditor.h +++ b/indra/newview/llmaterialeditor.h @@ -1,322 +1,322 @@ -/** - * @file llmaterialeditor.h - * @brief LLMaterialEditor class header file - * - * $LicenseInfo:firstyear=2022&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#pragma once - -#include "llpreview.h" -#include "llvoinventorylistener.h" -#include "llimagej2c.h" -#include "llviewertexture.h" - -class LLButton; -class LLColorSwatchCtrl; -class LLComboBox; -class LLGLTFMaterial; -class LLLocalGLTFMaterial; -class LLTextureCtrl; -class LLTextBox; -class LLViewerInventoryItem; - -namespace tinygltf -{ - class Model; -} - -// todo: Consider making into a notification or just merging with -// presets. Layout is identical to camera/graphics presets so there -// is no point in having multiple separate xmls and classes. -class LLFloaterComboOptions : public LLFloater -{ -public: - typedef std::function combo_callback; - LLFloaterComboOptions(); - - virtual ~LLFloaterComboOptions(); - /*virtual*/ bool postBuild(); - - static LLFloaterComboOptions* showUI( - combo_callback callback, - const std::string &title, - const std::string &description, - const std::list &options); - - static LLFloaterComboOptions* showUI( - combo_callback callback, - const std::string &title, - const std::string &description, - const std::string &ok_text, - const std::string &cancel_text, - const std::list &options); - -private: - void onConfirm(); - void onCancel(); - -protected: - combo_callback mCallback; - - LLButton *mConfirmButton; - LLButton *mCancelButton; - LLComboBox *mComboOptions; - LLTextBox *mComboText; -}; - -class LLMaterialEditor : public LLPreview, public LLVOInventoryListener -{ public: - LLMaterialEditor(const LLSD& key); - ~LLMaterialEditor(); - - bool setFromGltfModel(const tinygltf::Model& model, S32 index, bool set_textures = false); - - void setFromGltfMetaData(const std::string& filename, const tinygltf::Model& model, S32 index); - - // open a file dialog and select a gltf/glb file for import - static void importMaterial(); - - // for live preview, apply current material to currently selected object - void applyToSelection(); - - // get a dump of the json representation of the current state of the editor UI as a material object - void getGLTFMaterial(LLGLTFMaterial* mat); - - void loadAsset() override; - // @index if -1 and file contains more than one material, - // will promt to select specific one - static void uploadMaterialFromModel(const std::string& filename, tinygltf::Model& model, S32 index); - static void loadMaterialFromFile(const std::string& filename, S32 index = -1); - - void onSelectionChanged(); // live overrides selection changes - - static void updateLive(); - static void loadLive(); - - static bool canModifyObjectsMaterial(); - static bool canSaveObjectsMaterial(); - static bool canClipboardObjectsMaterial(); - static void saveObjectsMaterialAs(); - static void onSaveObjectsMaterialAsMsgCallback(const LLSD& notification, const LLSD& response, const LLPermissions& permissions); - - static void onLoadComplete(const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status); - - void inventoryChanged(LLViewerObject* object, LLInventoryObject::object_list_t* inventory, S32 serial_num, void* user_data) override; - - typedef std::function upload_callback_f; - void saveTexture(LLImageJ2C* img, const std::string& name, const LLUUID& asset_id, upload_callback_f cb); - void setFailedToUploadTexture(); - - // save textures to inventory if needed - // returns amount of scheduled uploads - S32 saveTextures(); - void clearTextures(); - - void onClickSave(); - - void getGLTFModel(tinygltf::Model& model); - - std::string getEncodedAsset(); - - bool decodeAsset(const std::vector& buffer); - - bool saveIfNeeded(); - - static void finishInventoryUpload(LLUUID itemId, LLUUID newAssetId, LLUUID newItemId); - - static void finishTaskUpload(LLUUID itemId, LLUUID newAssetId, LLUUID taskId); - - static void finishSaveAs( - const LLSD &oldKey, - const LLUUID &newItemId, - const std::string &buffer, - bool has_unsaved_changes); - - void refreshFromInventory(const LLUUID& new_item_id = LLUUID::null); - - void onClickSaveAs(); - void onSaveAsMsgCallback(const LLSD& notification, const LLSD& response); - void onClickCancel(); - void onCancelMsgCallback(const LLSD& notification, const LLSD& response); - - // llpreview - void setObjectID(const LLUUID& object_id) override; - void setAuxItem(const LLInventoryItem* item) override; - - // llpanel - bool postBuild() override; - void onClickCloseBtn(bool app_quitting = false) override; - - void onClose(bool app_quitting) override; - void draw() override; - void handleReshape(const LLRect& new_rect, bool by_user = false) override; - - LLUUID getBaseColorId(); - void setBaseColorId(const LLUUID& id); - void setBaseColorUploadId(const LLUUID& id); - - LLColor4 getBaseColor(); - - // sets both base color and transparency - void setBaseColor(const LLColor4& color); - - F32 getTransparency(); - void setTransparency(F32 transparency); - - std::string getAlphaMode(); - void setAlphaMode(const std::string& alpha_mode); - - F32 getAlphaCutoff(); - void setAlphaCutoff(F32 alpha_cutoff); - - void setMaterialName(const std::string &name); - - LLUUID getMetallicRoughnessId(); - void setMetallicRoughnessId(const LLUUID& id); - void setMetallicRoughnessUploadId(const LLUUID& id); - - F32 getMetalnessFactor(); - void setMetalnessFactor(F32 factor); - - F32 getRoughnessFactor(); - void setRoughnessFactor(F32 factor); - - LLUUID getEmissiveId(); - void setEmissiveId(const LLUUID& id); - void setEmissiveUploadId(const LLUUID& id); - - LLColor4 getEmissiveColor(); - void setEmissiveColor(const LLColor4& color); - - LLUUID getNormalId(); - void setNormalId(const LLUUID& id); - void setNormalUploadId(const LLUUID& id); - - bool getDoubleSided(); - void setDoubleSided(bool double_sided); - - void setCanSaveAs(bool value); - void setCanSave(bool value); - void setEnableEditing(bool can_modify); - - void subscribeToLocalTexture(S32 dirty_flag, const LLUUID& tracking_id); - void replaceLocalTexture(const LLUUID& old_id, const LLUUID& new_id); // Local texture support - void onCommitTexture(LLUICtrl* ctrl, const LLSD& data, S32 dirty_flag); - void onCancelCtrl(LLUICtrl* ctrl, const LLSD& data, S32 dirty_flag); - void onSelectCtrl(LLUICtrl* ctrl, const LLSD& data, S32 dirty_flag); - - // initialize the UI from a default GLTF material - void loadDefaults(); - - U32 getUnsavedChangesFlags() { return mUnsavedChanges; } - U32 getRevertedChangesFlags() { return mRevertedChanges; } - LLUUID getLocalTextureTrackingIdFromFlag(U32 flag); - bool updateMaterialLocalSubscription(LLGLTFMaterial* mat); - - static bool capabilitiesAvailable(); - -private: - static void saveObjectsMaterialAs(const LLGLTFMaterial *render_material, const LLLocalGLTFMaterial *local_material, const LLPermissions& permissions, const LLUUID& object_id /* = LLUUID::null */, const LLUUID& item /* = LLUUID::null */); - - static bool updateInventoryItem(const std::string &buffer, const LLUUID &item_id, const LLUUID &task_id); - static void createInventoryItem(const std::string &buffer, const std::string &name, const std::string &desc, const LLPermissions& permissions); - - void setFromGLTFMaterial(LLGLTFMaterial* mat); - bool setFromSelection(); - - void loadMaterial(const tinygltf::Model &model, const std::string & filename, S32 index, bool open_floater = true); - - friend class LLMaterialFilePicker; - - LLUUID mAssetID; - - LLTextureCtrl* mBaseColorTextureCtrl; - LLTextureCtrl* mMetallicTextureCtrl; - LLTextureCtrl* mEmissiveTextureCtrl; - LLTextureCtrl* mNormalTextureCtrl; - LLColorSwatchCtrl* mBaseColorCtrl; - LLColorSwatchCtrl* mEmissiveColorCtrl; - - // 'Default' texture, unless it's null or from inventory is the one with the fee - LLUUID mBaseColorTextureUploadId; - LLUUID mMetallicTextureUploadId; - LLUUID mEmissiveTextureUploadId; - LLUUID mNormalTextureUploadId; - - // last known name of each texture - std::string mBaseColorName; - std::string mNormalName; - std::string mMetallicRoughnessName; - std::string mEmissiveName; - - // keep pointers to fetched textures or viewer will remove them - // if user temporary selects something else with 'apply now' - LLPointer mBaseColorFetched; - LLPointer mNormalFetched; - LLPointer mMetallicRoughnessFetched; - LLPointer mEmissiveFetched; - - // J2C versions of packed buffers for uploading - LLPointer mBaseColorJ2C; - LLPointer mNormalJ2C; - LLPointer mMetallicRoughnessJ2C; - LLPointer mEmissiveJ2C; - - // utility function for converting image uri into a texture name - const std::string getImageNameFromUri(std::string image_uri, const std::string texture_type); - - // utility function for building a description of the imported material - // based on what we know about it. - const std::string buildMaterialDescription(); - - void resetUnsavedChanges(); - void markChangesUnsaved(U32 dirty_flag); - - U32 mUnsavedChanges; // flags to indicate individual changed parameters - U32 mRevertedChanges; // flags to indicate individual reverted parameters - S32 mUploadingTexturesCount; - S32 mExpectedUploadCost; - bool mUploadingTexturesFailure; - std::string mMaterialNameShort; - std::string mMaterialName; - - // if true, this instance is live instance editing overrides - bool mIsOverride = false; - bool mHasSelection = false; - // local id, texture ids per face for object overrides - // for "cancel" support - static LLUUID mOverrideObjectId; // static to avoid searching for the floater - static S32 mOverrideObjectTE; - static bool mOverrideInProgress; - static bool mSelectionNeedsUpdate; - boost::signals2::connection mSelectionUpdateSlot; - - struct LocalTextureConnection - { - LLUUID mTrackingId; - boost::signals2::connection mConnection; - }; - typedef std::map mat_connection_map_t; - mat_connection_map_t mTextureChangesUpdates; -}; - +/** + * @file llmaterialeditor.h + * @brief LLMaterialEditor class header file + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#pragma once + +#include "llpreview.h" +#include "llvoinventorylistener.h" +#include "llimagej2c.h" +#include "llviewertexture.h" + +class LLButton; +class LLColorSwatchCtrl; +class LLComboBox; +class LLGLTFMaterial; +class LLLocalGLTFMaterial; +class LLTextureCtrl; +class LLTextBox; +class LLViewerInventoryItem; + +namespace tinygltf +{ + class Model; +} + +// todo: Consider making into a notification or just merging with +// presets. Layout is identical to camera/graphics presets so there +// is no point in having multiple separate xmls and classes. +class LLFloaterComboOptions : public LLFloater +{ +public: + typedef std::function combo_callback; + LLFloaterComboOptions(); + + virtual ~LLFloaterComboOptions(); + /*virtual*/ bool postBuild(); + + static LLFloaterComboOptions* showUI( + combo_callback callback, + const std::string &title, + const std::string &description, + const std::list &options); + + static LLFloaterComboOptions* showUI( + combo_callback callback, + const std::string &title, + const std::string &description, + const std::string &ok_text, + const std::string &cancel_text, + const std::list &options); + +private: + void onConfirm(); + void onCancel(); + +protected: + combo_callback mCallback; + + LLButton *mConfirmButton; + LLButton *mCancelButton; + LLComboBox *mComboOptions; + LLTextBox *mComboText; +}; + +class LLMaterialEditor : public LLPreview, public LLVOInventoryListener +{ public: + LLMaterialEditor(const LLSD& key); + ~LLMaterialEditor(); + + bool setFromGltfModel(const tinygltf::Model& model, S32 index, bool set_textures = false); + + void setFromGltfMetaData(const std::string& filename, const tinygltf::Model& model, S32 index); + + // open a file dialog and select a gltf/glb file for import + static void importMaterial(); + + // for live preview, apply current material to currently selected object + void applyToSelection(); + + // get a dump of the json representation of the current state of the editor UI as a material object + void getGLTFMaterial(LLGLTFMaterial* mat); + + void loadAsset() override; + // @index if -1 and file contains more than one material, + // will promt to select specific one + static void uploadMaterialFromModel(const std::string& filename, tinygltf::Model& model, S32 index); + static void loadMaterialFromFile(const std::string& filename, S32 index = -1); + + void onSelectionChanged(); // live overrides selection changes + + static void updateLive(); + static void loadLive(); + + static bool canModifyObjectsMaterial(); + static bool canSaveObjectsMaterial(); + static bool canClipboardObjectsMaterial(); + static void saveObjectsMaterialAs(); + static void onSaveObjectsMaterialAsMsgCallback(const LLSD& notification, const LLSD& response, const LLPermissions& permissions); + + static void onLoadComplete(const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status); + + void inventoryChanged(LLViewerObject* object, LLInventoryObject::object_list_t* inventory, S32 serial_num, void* user_data) override; + + typedef std::function upload_callback_f; + void saveTexture(LLImageJ2C* img, const std::string& name, const LLUUID& asset_id, upload_callback_f cb); + void setFailedToUploadTexture(); + + // save textures to inventory if needed + // returns amount of scheduled uploads + S32 saveTextures(); + void clearTextures(); + + void onClickSave(); + + void getGLTFModel(tinygltf::Model& model); + + std::string getEncodedAsset(); + + bool decodeAsset(const std::vector& buffer); + + bool saveIfNeeded(); + + static void finishInventoryUpload(LLUUID itemId, LLUUID newAssetId, LLUUID newItemId); + + static void finishTaskUpload(LLUUID itemId, LLUUID newAssetId, LLUUID taskId); + + static void finishSaveAs( + const LLSD &oldKey, + const LLUUID &newItemId, + const std::string &buffer, + bool has_unsaved_changes); + + void refreshFromInventory(const LLUUID& new_item_id = LLUUID::null); + + void onClickSaveAs(); + void onSaveAsMsgCallback(const LLSD& notification, const LLSD& response); + void onClickCancel(); + void onCancelMsgCallback(const LLSD& notification, const LLSD& response); + + // llpreview + void setObjectID(const LLUUID& object_id) override; + void setAuxItem(const LLInventoryItem* item) override; + + // llpanel + bool postBuild() override; + void onClickCloseBtn(bool app_quitting = false) override; + + void onClose(bool app_quitting) override; + void draw() override; + void handleReshape(const LLRect& new_rect, bool by_user = false) override; + + LLUUID getBaseColorId(); + void setBaseColorId(const LLUUID& id); + void setBaseColorUploadId(const LLUUID& id); + + LLColor4 getBaseColor(); + + // sets both base color and transparency + void setBaseColor(const LLColor4& color); + + F32 getTransparency(); + void setTransparency(F32 transparency); + + std::string getAlphaMode(); + void setAlphaMode(const std::string& alpha_mode); + + F32 getAlphaCutoff(); + void setAlphaCutoff(F32 alpha_cutoff); + + void setMaterialName(const std::string &name); + + LLUUID getMetallicRoughnessId(); + void setMetallicRoughnessId(const LLUUID& id); + void setMetallicRoughnessUploadId(const LLUUID& id); + + F32 getMetalnessFactor(); + void setMetalnessFactor(F32 factor); + + F32 getRoughnessFactor(); + void setRoughnessFactor(F32 factor); + + LLUUID getEmissiveId(); + void setEmissiveId(const LLUUID& id); + void setEmissiveUploadId(const LLUUID& id); + + LLColor4 getEmissiveColor(); + void setEmissiveColor(const LLColor4& color); + + LLUUID getNormalId(); + void setNormalId(const LLUUID& id); + void setNormalUploadId(const LLUUID& id); + + bool getDoubleSided(); + void setDoubleSided(bool double_sided); + + void setCanSaveAs(bool value); + void setCanSave(bool value); + void setEnableEditing(bool can_modify); + + void subscribeToLocalTexture(S32 dirty_flag, const LLUUID& tracking_id); + void replaceLocalTexture(const LLUUID& old_id, const LLUUID& new_id); // Local texture support + void onCommitTexture(LLUICtrl* ctrl, const LLSD& data, S32 dirty_flag); + void onCancelCtrl(LLUICtrl* ctrl, const LLSD& data, S32 dirty_flag); + void onSelectCtrl(LLUICtrl* ctrl, const LLSD& data, S32 dirty_flag); + + // initialize the UI from a default GLTF material + void loadDefaults(); + + U32 getUnsavedChangesFlags() { return mUnsavedChanges; } + U32 getRevertedChangesFlags() { return mRevertedChanges; } + LLUUID getLocalTextureTrackingIdFromFlag(U32 flag); + bool updateMaterialLocalSubscription(LLGLTFMaterial* mat); + + static bool capabilitiesAvailable(); + +private: + static void saveObjectsMaterialAs(const LLGLTFMaterial *render_material, const LLLocalGLTFMaterial *local_material, const LLPermissions& permissions, const LLUUID& object_id /* = LLUUID::null */, const LLUUID& item /* = LLUUID::null */); + + static bool updateInventoryItem(const std::string &buffer, const LLUUID &item_id, const LLUUID &task_id); + static void createInventoryItem(const std::string &buffer, const std::string &name, const std::string &desc, const LLPermissions& permissions); + + void setFromGLTFMaterial(LLGLTFMaterial* mat); + bool setFromSelection(); + + void loadMaterial(const tinygltf::Model &model, const std::string & filename, S32 index, bool open_floater = true); + + friend class LLMaterialFilePicker; + + LLUUID mAssetID; + + LLTextureCtrl* mBaseColorTextureCtrl; + LLTextureCtrl* mMetallicTextureCtrl; + LLTextureCtrl* mEmissiveTextureCtrl; + LLTextureCtrl* mNormalTextureCtrl; + LLColorSwatchCtrl* mBaseColorCtrl; + LLColorSwatchCtrl* mEmissiveColorCtrl; + + // 'Default' texture, unless it's null or from inventory is the one with the fee + LLUUID mBaseColorTextureUploadId; + LLUUID mMetallicTextureUploadId; + LLUUID mEmissiveTextureUploadId; + LLUUID mNormalTextureUploadId; + + // last known name of each texture + std::string mBaseColorName; + std::string mNormalName; + std::string mMetallicRoughnessName; + std::string mEmissiveName; + + // keep pointers to fetched textures or viewer will remove them + // if user temporary selects something else with 'apply now' + LLPointer mBaseColorFetched; + LLPointer mNormalFetched; + LLPointer mMetallicRoughnessFetched; + LLPointer mEmissiveFetched; + + // J2C versions of packed buffers for uploading + LLPointer mBaseColorJ2C; + LLPointer mNormalJ2C; + LLPointer mMetallicRoughnessJ2C; + LLPointer mEmissiveJ2C; + + // utility function for converting image uri into a texture name + const std::string getImageNameFromUri(std::string image_uri, const std::string texture_type); + + // utility function for building a description of the imported material + // based on what we know about it. + const std::string buildMaterialDescription(); + + void resetUnsavedChanges(); + void markChangesUnsaved(U32 dirty_flag); + + U32 mUnsavedChanges; // flags to indicate individual changed parameters + U32 mRevertedChanges; // flags to indicate individual reverted parameters + S32 mUploadingTexturesCount; + S32 mExpectedUploadCost; + bool mUploadingTexturesFailure; + std::string mMaterialNameShort; + std::string mMaterialName; + + // if true, this instance is live instance editing overrides + bool mIsOverride = false; + bool mHasSelection = false; + // local id, texture ids per face for object overrides + // for "cancel" support + static LLUUID mOverrideObjectId; // static to avoid searching for the floater + static S32 mOverrideObjectTE; + static bool mOverrideInProgress; + static bool mSelectionNeedsUpdate; + boost::signals2::connection mSelectionUpdateSlot; + + struct LocalTextureConnection + { + LLUUID mTrackingId; + boost::signals2::connection mConnection; + }; + typedef std::map mat_connection_map_t; + mat_connection_map_t mTextureChangesUpdates; +}; + diff --git a/indra/newview/llmaterialmgr.h b/indra/newview/llmaterialmgr.h index 6708a8b62f..1279b77ad4 100644 --- a/indra/newview/llmaterialmgr.h +++ b/indra/newview/llmaterialmgr.h @@ -1,164 +1,164 @@ -/** - * @file llmaterialmgr.h - * @brief Material manager - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLMATERIALMGR_H -#define LL_LLMATERIALMGR_H - -#include "llmaterial.h" -#include "llmaterialid.h" -#include "llsingleton.h" -#include "httprequest.h" -#include "httpheaders.h" -#include "httpoptions.h" - -class LLViewerRegion; - -class LLMaterialMgr : public LLSingleton -{ - LLSINGLETON(LLMaterialMgr); - virtual ~LLMaterialMgr(); - -public: - typedef std::map material_map_t; - - typedef boost::signals2::signal get_callback_t; - const LLMaterialPtr get(const LLUUID& region_id, const LLMaterialID& material_id); - boost::signals2::connection get(const LLUUID& region_id, const LLMaterialID& material_id, get_callback_t::slot_type cb); - - typedef boost::signals2::signal get_callback_te_t; - boost::signals2::connection getTE(const LLUUID& region_id, const LLMaterialID& material_id, U32 te, get_callback_te_t::slot_type cb); - - typedef boost::signals2::signal getall_callback_t; - void getAll(const LLUUID& region_id); - boost::signals2::connection getAll(const LLUUID& region_id, getall_callback_t::slot_type cb); - void put(const LLUUID& object_id, const U8 te, const LLMaterial& material); - void remove(const LLUUID& object_id, const U8 te); - - //explicitly add new material to material manager - void setLocalMaterial(const LLUUID& region_id, LLMaterialPtr material_ptr); - -private: - void clearGetQueues(const LLUUID& region_id); - bool isGetPending(const LLUUID& region_id, const LLMaterialID& material_id) const; - bool isGetAllPending(const LLUUID& region_id) const; - void markGetPending(const LLUUID& region_id, const LLMaterialID& material_id); - const LLMaterialPtr setMaterial(const LLUUID& region_id, const LLMaterialID& material_id, const LLSD& material_data); - void setMaterialCallbacks(const LLMaterialID& material_id, const LLMaterialPtr material_ptr); - - static void onIdle(void*); - - static void CapsRecvForRegion(const LLUUID& regionId, LLUUID regionTest, std::string pumpname); - - void processGetQueue(); - void processGetQueueCoro(); - void onGetResponse(bool success, const LLSD& content, const LLUUID& region_id); - void processGetAllQueue(); - void processGetAllQueueCoro(LLUUID regionId); - void onGetAllResponse(bool success, const LLSD& content, const LLUUID& region_id); - void processPutQueue(); - void onPutResponse(bool success, const LLSD& content); - void onRegionRemoved(LLViewerRegion* regionp); - -private: - // struct for TE-specific material ID query - class TEMaterialPair - { - public: - - U32 te; - LLMaterialID materialID; - - bool operator==(const TEMaterialPair& b) const { return (materialID == b.materialID) && (te == b.te); } - }; - - // definitions follow class - friend std::hash; - friend size_t hash_value(const TEMaterialPair&) noexcept; - - friend inline bool operator<( - const LLMaterialMgr::TEMaterialPair& lhs, - const LLMaterialMgr::TEMaterialPair& rhs) - { - return (lhs.te < rhs.te) ? true : - (lhs.materialID < rhs.materialID); - } - - typedef std::set material_queue_t; - typedef std::map get_queue_t; - typedef std::pair pending_material_t; - typedef std::map get_pending_map_t; - typedef std::map get_callback_map_t; - - - typedef boost::unordered_map get_callback_te_map_t; - typedef std::set getall_queue_t; - typedef std::map getall_pending_map_t; - typedef std::map getall_callback_map_t; - typedef std::map facematerial_map_t; - typedef std::map put_queue_t; - - - get_queue_t mGetQueue; - uuid_set_t mRegionGets; - get_pending_map_t mGetPending; - get_callback_map_t mGetCallbacks; - - get_callback_te_map_t mGetTECallbacks; - getall_queue_t mGetAllQueue; - getall_queue_t mGetAllRequested; - getall_pending_map_t mGetAllPending; - getall_callback_map_t mGetAllCallbacks; - put_queue_t mPutQueue; - material_map_t mMaterials; - - LLCore::HttpRequest::ptr_t mHttpRequest; - LLCore::HttpHeaders::ptr_t mHttpHeaders; - LLCore::HttpOptions::ptr_t mHttpOptions; - LLCore::HttpRequest::policy_t mHttpPolicy; - - U32 getMaxEntries(const LLViewerRegion* regionp); -}; - -// std::hash implementation for TEMaterialPair -namespace std -{ - template<> struct hash - { - inline size_t operator()(const LLMaterialMgr::TEMaterialPair& p) const noexcept - { - return size_t((p.te + 1) * p.materialID.getDigest64()); - } - }; -} - -// For use with boost containers. -inline size_t hash_value(const LLMaterialMgr::TEMaterialPair& p) noexcept -{ - return size_t((p.te + 1) * p.materialID.getDigest64()); -} - -#endif // LL_LLMATERIALMGR_H - +/** + * @file llmaterialmgr.h + * @brief Material manager + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLMATERIALMGR_H +#define LL_LLMATERIALMGR_H + +#include "llmaterial.h" +#include "llmaterialid.h" +#include "llsingleton.h" +#include "httprequest.h" +#include "httpheaders.h" +#include "httpoptions.h" + +class LLViewerRegion; + +class LLMaterialMgr : public LLSingleton +{ + LLSINGLETON(LLMaterialMgr); + virtual ~LLMaterialMgr(); + +public: + typedef std::map material_map_t; + + typedef boost::signals2::signal get_callback_t; + const LLMaterialPtr get(const LLUUID& region_id, const LLMaterialID& material_id); + boost::signals2::connection get(const LLUUID& region_id, const LLMaterialID& material_id, get_callback_t::slot_type cb); + + typedef boost::signals2::signal get_callback_te_t; + boost::signals2::connection getTE(const LLUUID& region_id, const LLMaterialID& material_id, U32 te, get_callback_te_t::slot_type cb); + + typedef boost::signals2::signal getall_callback_t; + void getAll(const LLUUID& region_id); + boost::signals2::connection getAll(const LLUUID& region_id, getall_callback_t::slot_type cb); + void put(const LLUUID& object_id, const U8 te, const LLMaterial& material); + void remove(const LLUUID& object_id, const U8 te); + + //explicitly add new material to material manager + void setLocalMaterial(const LLUUID& region_id, LLMaterialPtr material_ptr); + +private: + void clearGetQueues(const LLUUID& region_id); + bool isGetPending(const LLUUID& region_id, const LLMaterialID& material_id) const; + bool isGetAllPending(const LLUUID& region_id) const; + void markGetPending(const LLUUID& region_id, const LLMaterialID& material_id); + const LLMaterialPtr setMaterial(const LLUUID& region_id, const LLMaterialID& material_id, const LLSD& material_data); + void setMaterialCallbacks(const LLMaterialID& material_id, const LLMaterialPtr material_ptr); + + static void onIdle(void*); + + static void CapsRecvForRegion(const LLUUID& regionId, LLUUID regionTest, std::string pumpname); + + void processGetQueue(); + void processGetQueueCoro(); + void onGetResponse(bool success, const LLSD& content, const LLUUID& region_id); + void processGetAllQueue(); + void processGetAllQueueCoro(LLUUID regionId); + void onGetAllResponse(bool success, const LLSD& content, const LLUUID& region_id); + void processPutQueue(); + void onPutResponse(bool success, const LLSD& content); + void onRegionRemoved(LLViewerRegion* regionp); + +private: + // struct for TE-specific material ID query + class TEMaterialPair + { + public: + + U32 te; + LLMaterialID materialID; + + bool operator==(const TEMaterialPair& b) const { return (materialID == b.materialID) && (te == b.te); } + }; + + // definitions follow class + friend std::hash; + friend size_t hash_value(const TEMaterialPair&) noexcept; + + friend inline bool operator<( + const LLMaterialMgr::TEMaterialPair& lhs, + const LLMaterialMgr::TEMaterialPair& rhs) + { + return (lhs.te < rhs.te) ? true : + (lhs.materialID < rhs.materialID); + } + + typedef std::set material_queue_t; + typedef std::map get_queue_t; + typedef std::pair pending_material_t; + typedef std::map get_pending_map_t; + typedef std::map get_callback_map_t; + + + typedef boost::unordered_map get_callback_te_map_t; + typedef std::set getall_queue_t; + typedef std::map getall_pending_map_t; + typedef std::map getall_callback_map_t; + typedef std::map facematerial_map_t; + typedef std::map put_queue_t; + + + get_queue_t mGetQueue; + uuid_set_t mRegionGets; + get_pending_map_t mGetPending; + get_callback_map_t mGetCallbacks; + + get_callback_te_map_t mGetTECallbacks; + getall_queue_t mGetAllQueue; + getall_queue_t mGetAllRequested; + getall_pending_map_t mGetAllPending; + getall_callback_map_t mGetAllCallbacks; + put_queue_t mPutQueue; + material_map_t mMaterials; + + LLCore::HttpRequest::ptr_t mHttpRequest; + LLCore::HttpHeaders::ptr_t mHttpHeaders; + LLCore::HttpOptions::ptr_t mHttpOptions; + LLCore::HttpRequest::policy_t mHttpPolicy; + + U32 getMaxEntries(const LLViewerRegion* regionp); +}; + +// std::hash implementation for TEMaterialPair +namespace std +{ + template<> struct hash + { + inline size_t operator()(const LLMaterialMgr::TEMaterialPair& p) const noexcept + { + return size_t((p.te + 1) * p.materialID.getDigest64()); + } + }; +} + +// For use with boost containers. +inline size_t hash_value(const LLMaterialMgr::TEMaterialPair& p) noexcept +{ + return size_t((p.te + 1) * p.materialID.getDigest64()); +} + +#endif // LL_LLMATERIALMGR_H + diff --git a/indra/newview/llmediactrl.cpp b/indra/newview/llmediactrl.cpp index 67412f1b38..b39a976ebd 100644 --- a/indra/newview/llmediactrl.cpp +++ b/indra/newview/llmediactrl.cpp @@ -1,1256 +1,1256 @@ -/** - * @file LLMediaCtrl.cpp - * @brief Web browser UI control - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "lltooltip.h" - -#include "llmediactrl.h" - -// viewer includes -#include "llfloaterworldmap.h" -#include "lluictrlfactory.h" -#include "llurldispatcher.h" -#include "llviewborder.h" -#include "llviewercontrol.h" -#include "llviewermedia.h" -#include "llviewertexture.h" -#include "llviewerwindow.h" -#include "lldebugmessagebox.h" -#include "llweb.h" -#include "llrender.h" -#include "llpluginclassmedia.h" -#include "llslurl.h" -#include "lluictrlfactory.h" // LLDefaultChildRegistry -#include "llkeyboard.h" -#include "llviewermenu.h" -#include "llviewermenufile.h" // LLFilePickerThread - -// linden library includes -#include "llfocusmgr.h" -#include "llsdutil.h" -#include "lllayoutstack.h" -#include "lliconctrl.h" -#include "llhttpconstants.h" -#include "lltextbox.h" -#include "llbutton.h" -#include "llcheckboxctrl.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "lllineeditor.h" -#include "llfloaterwebcontent.h" -#include "llwindowshade.h" - -extern bool gRestoreGL; - -static LLDefaultChildRegistry::Register r("web_browser"); - -LLMediaCtrl::Params::Params() -: start_url("start_url"), - border_visible("border_visible", true), - decouple_texture_size("decouple_texture_size", false), - texture_width("texture_width", 1024), - texture_height("texture_height", 1024), - caret_color("caret_color"), - initial_mime_type("initial_mime_type"), - error_page_url("error_page_url"), - media_id("media_id"), - trusted_content("trusted_content", false), - focus_on_click("focus_on_click", true) -{ -} - -LLMediaCtrl::LLMediaCtrl( const Params& p) : - LLPanel( p ), - LLInstanceTracker(LLUUID::generateNewID()), - mTextureDepthBytes( 4 ), - mBorder(NULL), - mFrequentUpdates( true ), - mForceUpdate( false ), - mHomePageUrl( "" ), - mAlwaysRefresh( false ), - mMediaSource( 0 ), - mTakeFocusOnClick( p.focus_on_click ), - mCurrentNavUrl( "" ), - mStretchToFill( true ), - mMaintainAspectRatio ( true ), - mDecoupleTextureSize ( false ), - mUpdateScrolls( false ), - mTextureWidth ( 1024 ), - mTextureHeight ( 1024 ), - mClearCache(false), - mHomePageMimeType(p.initial_mime_type), - mErrorPageURL(p.error_page_url), - mTrusted(p.trusted_content), - mWindowShade(NULL), - mHoverTextChanged(false), - mAllowFileDownload(false) -{ - { - LLColor4 color = p.caret_color().get(); - setCaretColor( (unsigned int)color.mV[0], (unsigned int)color.mV[1], (unsigned int)color.mV[2] ); - } - - setHomePageUrl(p.start_url, p.initial_mime_type); - - setBorderVisible(p.border_visible); - - setDecoupleTextureSize(p.decouple_texture_size); - - setTextureSize(p.texture_width, p.texture_height); - - if(!getDecoupleTextureSize()) - { - S32 screen_width = ll_round((F32)getRect().getWidth() * LLUI::getScaleFactor().mV[VX]); - S32 screen_height = ll_round((F32)getRect().getHeight() * LLUI::getScaleFactor().mV[VY]); - - setTextureSize(screen_width, screen_height); - } - - mMediaTextureID = getKey(); - - // We don't need to create the media source up front anymore unless we have a non-empty home URL to navigate to. - if(!mHomePageUrl.empty()) - { - navigateHome(); - } - - LLWindowShade::Params params; - params.name = "notification_shade"; - params.rect = getLocalRect(); - params.follows.flags = FOLLOWS_ALL; - params.modal = true; - - mWindowShade = LLUICtrlFactory::create(params); - - addChild(mWindowShade); -} - -LLMediaCtrl::~LLMediaCtrl() -{ - auto menu = mContextMenuHandle.get(); - if (menu) - { - menu->die(); - mContextMenuHandle.markDead(); - } - - if (mMediaSource) - { - mMediaSource->remObserver( this ); - mMediaSource = NULL; - } -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLMediaCtrl::setBorderVisible( bool border_visible ) -{ - if ( mBorder ) - { - mBorder->setVisible( border_visible ); - }; -}; - -//////////////////////////////////////////////////////////////////////////////// -// -void LLMediaCtrl::setTakeFocusOnClick( bool take_focus ) -{ - mTakeFocusOnClick = take_focus; -} - -//////////////////////////////////////////////////////////////////////////////// -// -bool LLMediaCtrl::handleHover( S32 x, S32 y, MASK mask ) -{ - if (LLPanel::handleHover(x, y, mask)) return true; - convertInputCoords(x, y); - - if (mMediaSource) - { - mMediaSource->mouseMove(x, y, mask); - gViewerWindow->setCursor(mMediaSource->getLastSetCursor()); - } - - // TODO: Is this the right way to handle hover text changes driven by the plugin? - if(mHoverTextChanged) - { - mHoverTextChanged = false; - handleToolTip(x, y, mask); - } - - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -// -bool LLMediaCtrl::handleScrollWheel( S32 x, S32 y, S32 clicks ) -{ - if (LLPanel::handleScrollWheel(x, y, clicks)) return true; - if (mMediaSource && mMediaSource->hasMedia()) - { - convertInputCoords(x, y); - mMediaSource->scrollWheel(x, y, 0, clicks, gKeyboard->currentMask(true)); - } - - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -// -bool LLMediaCtrl::handleScrollHWheel(S32 x, S32 y, S32 clicks) -{ - if (LLPanel::handleScrollHWheel(x, y, clicks)) return true; - if (mMediaSource && mMediaSource->hasMedia()) - { - convertInputCoords(x, y); - mMediaSource->scrollWheel(x, y, clicks, 0, gKeyboard->currentMask(true)); - } - - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -// virtual -bool LLMediaCtrl::handleToolTip(S32 x, S32 y, MASK mask) -{ - std::string hover_text; - - if (mMediaSource && mMediaSource->hasMedia()) - hover_text = mMediaSource->getMediaPlugin()->getHoverText(); - - if(hover_text.empty()) - { - return false; - } - else - { - S32 screen_x, screen_y; - - localPointToScreen(x, y, &screen_x, &screen_y); - LLRect sticky_rect_screen; - sticky_rect_screen.setCenterAndSize(screen_x, screen_y, 20, 20); - - LLToolTipMgr::instance().show(LLToolTip::Params() - .message(hover_text) - .sticky_rect(sticky_rect_screen)); - } - - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -// -bool LLMediaCtrl::handleMouseUp( S32 x, S32 y, MASK mask ) -{ - if (LLPanel::handleMouseUp(x, y, mask)) return true; - convertInputCoords(x, y); - - if (mMediaSource) - { - mMediaSource->mouseUp(x, y, mask); - } - - gFocusMgr.setMouseCapture( NULL ); - - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -// -bool LLMediaCtrl::handleMouseDown( S32 x, S32 y, MASK mask ) -{ - if (LLPanel::handleMouseDown(x, y, mask)) return true; - convertInputCoords(x, y); - - if (mMediaSource) - mMediaSource->mouseDown(x, y, mask); - - gFocusMgr.setMouseCapture( this ); - - if (mTakeFocusOnClick) - { - setFocus( true ); - } - - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -// -bool LLMediaCtrl::handleRightMouseUp( S32 x, S32 y, MASK mask ) -{ - if (LLPanel::handleRightMouseUp(x, y, mask)) return true; - convertInputCoords(x, y); - - if (mMediaSource) - { - mMediaSource->mouseUp(x, y, mask, 1); - - // *HACK: LLMediaImplLLMozLib automatically takes focus on mouseup, - // in addition to the onFocusReceived() call below. Undo this. JC - if (!mTakeFocusOnClick) - { - mMediaSource->focus(false); - gViewerWindow->focusClient(); - } - } - - gFocusMgr.setMouseCapture( NULL ); - - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -// -bool LLMediaCtrl::handleRightMouseDown( S32 x, S32 y, MASK mask ) -{ - if (LLPanel::handleRightMouseDown(x, y, mask)) return true; - - S32 media_x = x, media_y = y; - convertInputCoords(media_x, media_y); - - if (mMediaSource) - mMediaSource->mouseDown(media_x, media_y, mask, 1); - - gFocusMgr.setMouseCapture( this ); - - if (mTakeFocusOnClick) - { - setFocus( true ); - } - - auto menu = mContextMenuHandle.get(); - if (!menu) - { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registar; - registar.add("Open.WebInspector", boost::bind(&LLMediaCtrl::onOpenWebInspector, this)); - - // stinson 05/05/2014 : use this as the parent of the context menu if the static menu - // container has yet to be created - LLPanel* menuParent = (LLMenuGL::sMenuContainer != NULL) ? dynamic_cast(LLMenuGL::sMenuContainer) : dynamic_cast(this); - llassert(menuParent != NULL); - menu = LLUICtrlFactory::getInstance()->createFromFile( - "menu_media_ctrl.xml", menuParent, LLViewerMenuHolderGL::child_registry_t::instance()); - if (menu) - { - mContextMenuHandle = menu->getHandle(); - } - } - - if (menu) - { - // hide/show debugging options - bool media_plugin_debugging_enabled = gSavedSettings.getBOOL("MediaPluginDebugging"); - menu->setItemVisible("open_webinspector", media_plugin_debugging_enabled ); - menu->setItemVisible("debug_separator", media_plugin_debugging_enabled ); - - menu->show(x, y); - LLMenuGL::showPopup(this, menu, x, y); - } - - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -// -bool LLMediaCtrl::handleDoubleClick( S32 x, S32 y, MASK mask ) -{ - if (LLPanel::handleDoubleClick(x, y, mask)) return true; - convertInputCoords(x, y); - - if (mMediaSource) - mMediaSource->mouseDoubleClick( x, y, mask); - - gFocusMgr.setMouseCapture( this ); - - if (mTakeFocusOnClick) - { - setFocus( true ); - } - - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLMediaCtrl::onFocusReceived() -{ - if (mMediaSource) - { - mMediaSource->focus(true); - - // Set focus for edit menu items - LLEditMenuHandler::gEditMenuHandler = mMediaSource; - } - - LLPanel::onFocusReceived(); -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLMediaCtrl::onFocusLost() -{ - if (mMediaSource) - { - mMediaSource->focus(false); - - if( LLEditMenuHandler::gEditMenuHandler == mMediaSource ) - { - // Clear focus for edit menu items - LLEditMenuHandler::gEditMenuHandler = NULL; - } - } - - gViewerWindow->focusClient(); - - LLPanel::onFocusLost(); -} - -//////////////////////////////////////////////////////////////////////////////// -// -bool LLMediaCtrl::postBuild () -{ - setVisibleCallback(boost::bind(&LLMediaCtrl::onVisibilityChanged, this, _2)); - - return true; -} - -void LLMediaCtrl::onOpenWebInspector() -{ - if (mMediaSource && mMediaSource->hasMedia()) - mMediaSource->getMediaPlugin()->showWebInspector( true ); -} - -//////////////////////////////////////////////////////////////////////////////// -// -bool LLMediaCtrl::handleKeyHere( KEY key, MASK mask ) -{ - bool result = false; - - if (mMediaSource) - { - result = mMediaSource->handleKeyHere(key, mask); - } - - if ( ! result ) - result = LLPanel::handleKeyHere(key, mask); - - return result; -} - -//////////////////////////////////////////////////////////////////////////////// -// -bool LLMediaCtrl::handleKeyUpHere(KEY key, MASK mask) -{ - bool result = false; - - if (mMediaSource) - { - result = mMediaSource->handleKeyUpHere(key, mask); - } - - if (!result) - result = LLPanel::handleKeyUpHere(key, mask); - - return result; -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLMediaCtrl::onVisibilityChange ( bool new_visibility ) -{ - LL_INFOS() << "visibility changed to " << (new_visibility?"true":"false") << LL_ENDL; - if(mMediaSource) - { - mMediaSource->setVisible( new_visibility ); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// -bool LLMediaCtrl::handleUnicodeCharHere(llwchar uni_char) -{ - bool result = false; - - if (mMediaSource) - { - result = mMediaSource->handleUnicodeCharHere(uni_char); - } - - if ( ! result ) - result = LLPanel::handleUnicodeCharHere(uni_char); - - return result; -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLMediaCtrl::onVisibilityChanged ( const LLSD& new_visibility ) -{ - // set state of frequent updates automatically if visibility changes - if ( new_visibility.asBoolean() ) - { - mFrequentUpdates = true; - } - else - { - mFrequentUpdates = false; - } -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLMediaCtrl::reshape( S32 width, S32 height, bool called_from_parent ) -{ - if(!getDecoupleTextureSize()) - { - S32 screen_width = ll_round((F32)width * LLUI::getScaleFactor().mV[VX]); - S32 screen_height = ll_round((F32)height * LLUI::getScaleFactor().mV[VY]); - - // when floater is minimized, these sizes are negative - if ( screen_height > 0 && screen_width > 0 ) - { - setTextureSize(screen_width, screen_height); - } - } - - LLUICtrl::reshape( width, height, called_from_parent ); -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLMediaCtrl::navigateBack() -{ - if (mMediaSource && mMediaSource->hasMedia()) - { - mMediaSource->getMediaPlugin()->browse_back(); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLMediaCtrl::navigateForward() -{ - if (mMediaSource && mMediaSource->hasMedia()) - { - mMediaSource->getMediaPlugin()->browse_forward(); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLMediaCtrl::navigateStop() -{ - if (mMediaSource && mMediaSource->hasMedia()) - { - mMediaSource->getMediaPlugin()->browse_stop(); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// -bool LLMediaCtrl::canNavigateBack() -{ - if (mMediaSource) - return mMediaSource->canNavigateBack(); - else - return false; -} - -//////////////////////////////////////////////////////////////////////////////// -// -bool LLMediaCtrl::canNavigateForward() -{ - if (mMediaSource) - return mMediaSource->canNavigateForward(); - else - return false; -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLMediaCtrl::clearCache() -{ - if(mMediaSource) - { - mMediaSource->clearCache(); - } - else - { - mClearCache = true; - } - -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLMediaCtrl::navigateTo( std::string url_in, std::string mime_type, bool clean_browser) -{ - // don't browse to anything that starts with secondlife:// or sl:// - const std::string protocol1 = "secondlife://"; - const std::string protocol2 = "sl://"; - if ((LLStringUtil::compareInsensitive(url_in.substr(0, protocol1.length()), protocol1) == 0) || - (LLStringUtil::compareInsensitive(url_in.substr(0, protocol2.length()), protocol2) == 0)) - { - // TODO: Print out/log this attempt? - // LL_INFOS() << "Rejecting attempt to load restricted website :" << urlIn << LL_ENDL; - return; - } - - if (ensureMediaSourceExists()) - { - mCurrentNavUrl = url_in; - mMediaSource->setSize(mTextureWidth, mTextureHeight); - mMediaSource->navigateTo(url_in, mime_type, mime_type.empty(), false, clean_browser); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLMediaCtrl::navigateToLocalPage( const std::string& subdir, const std::string& filename_in ) -{ - std::string filename(gDirUtilp->add(subdir, filename_in)); - std::string expanded_filename = gDirUtilp->findSkinnedFilename("html", filename); - - if (expanded_filename.empty()) - { - LL_WARNS() << "File " << filename << "not found" << LL_ENDL; - return; - } - if (ensureMediaSourceExists()) - { - mCurrentNavUrl = expanded_filename; - mMediaSource->setSize(mTextureWidth, mTextureHeight); - mMediaSource->navigateTo(expanded_filename, HTTP_CONTENT_TEXT_HTML, false); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLMediaCtrl::navigateHome() -{ - if (ensureMediaSourceExists()) - { - mMediaSource->setSize(mTextureWidth, mTextureHeight); - mMediaSource->navigateHome(); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLMediaCtrl::setHomePageUrl( const std::string& urlIn, const std::string& mime_type ) -{ - mHomePageUrl = urlIn; - if (mMediaSource) - { - mMediaSource->setHomeURL(mHomePageUrl, mime_type); - } -} - -void LLMediaCtrl::setTarget(const std::string& target) -{ - mTarget = target; - if (mMediaSource) - { - mMediaSource->setTarget(mTarget); - } -} - -void LLMediaCtrl::setErrorPageURL(const std::string& url) -{ - mErrorPageURL = url; -} - -const std::string& LLMediaCtrl::getErrorPageURL() -{ - return mErrorPageURL; -} - -//////////////////////////////////////////////////////////////////////////////// -// -bool LLMediaCtrl::setCaretColor(unsigned int red, unsigned int green, unsigned int blue) -{ - //NOOP - return false; -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLMediaCtrl::setTextureSize(S32 width, S32 height) -{ - mTextureWidth = width; - mTextureHeight = height; - - if(mMediaSource) - { - mMediaSource->setSize(mTextureWidth, mTextureHeight); - mForceUpdate = true; - } -} - -//////////////////////////////////////////////////////////////////////////////// -// -std::string LLMediaCtrl::getHomePageUrl() -{ - return mHomePageUrl; -} - -//////////////////////////////////////////////////////////////////////////////// -// -bool LLMediaCtrl::ensureMediaSourceExists() -{ - if(mMediaSource.isNull()) - { - // If we don't already have a media source, try to create one. - mMediaSource = LLViewerMedia::getInstance()->newMediaImpl(mMediaTextureID, mTextureWidth, mTextureHeight); - if ( mMediaSource ) - { - mMediaSource->setUsedInUI(true); - mMediaSource->setHomeURL(mHomePageUrl, mHomePageMimeType); - mMediaSource->setTarget(mTarget); - mMediaSource->setVisible( getVisible() ); - mMediaSource->addObserver( this ); - mMediaSource->setBackgroundColor( getBackgroundColor() ); - mMediaSource->setTrustedBrowser(mTrusted); - - F32 scale_factor = LLUI::getScaleFactor().mV[ VX ]; - if (scale_factor != mMediaSource->getPageZoomFactor()) - { - mMediaSource->setPageZoomFactor( scale_factor ); - mUpdateScrolls = true; - } - - if(mClearCache) - { - mMediaSource->clearCache(); - mClearCache = false; - } - } - else - { - LL_WARNS() << "media source create failed " << LL_ENDL; - // return; - } - } - - return !mMediaSource.isNull(); -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLMediaCtrl::unloadMediaSource() -{ - mMediaSource = NULL; -} - -//////////////////////////////////////////////////////////////////////////////// -// -LLPluginClassMedia* LLMediaCtrl::getMediaPlugin() -{ - return mMediaSource.isNull() ? NULL : mMediaSource->getMediaPlugin(); -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLMediaCtrl::draw() -{ - F32 alpha = getDrawContext().mAlpha; - - if ( gRestoreGL == 1 || mUpdateScrolls) - { - LLRect r = getRect(); - reshape( r.getWidth(), r.getHeight(), false ); - mUpdateScrolls = false; - return; - } - - // NOTE: optimization needed here - probably only need to do this once - // unless tearoffs change the parent which they probably do. - const LLUICtrl* ptr = findRootMostFocusRoot(); - if ( ptr && ptr->hasFocus() ) - { - setFrequentUpdates( true ); - } - else - { - setFrequentUpdates( false ); - }; - - bool draw_media = false; - - LLPluginClassMedia* media_plugin = NULL; - LLViewerMediaTexture* media_texture = NULL; - - if(mMediaSource && mMediaSource->hasMedia()) - { - media_plugin = mMediaSource->getMediaPlugin(); - - if(media_plugin && (media_plugin->textureValid())) - { - media_texture = LLViewerTextureManager::findMediaTexture(mMediaTextureID); - if(media_texture) - { - draw_media = true; - } - } - } - - bool background_visible = isBackgroundVisible(); - bool background_opaque = isBackgroundOpaque(); - - if(draw_media) - { - gGL.pushUIMatrix(); - { - F32 scale_factor = LLUI::getScaleFactor().mV[ VX ]; - if (scale_factor != mMediaSource->getPageZoomFactor()) - { - mMediaSource->setPageZoomFactor( scale_factor ); - mUpdateScrolls = true; - } - - // scale texture to fit the space using texture coords - gGL.getTexUnit(0)->bind(media_texture); - LLColor4 media_color = LLColor4::white % alpha; - gGL.color4fv( media_color.mV ); - F32 max_u = ( F32 )media_plugin->getWidth() / ( F32 )media_plugin->getTextureWidth(); - F32 max_v = ( F32 )media_plugin->getHeight() / ( F32 )media_plugin->getTextureHeight(); - - S32 x_offset, y_offset, width, height; - calcOffsetsAndSize(&x_offset, &y_offset, &width, &height); - - // draw the browser - gGL.begin( LLRender::QUADS ); - if (! media_plugin->getTextureCoordsOpenGL()) - { - // render using web browser reported width and height, instead of trying to invert GL scale - gGL.texCoord2f( max_u, 0.f ); - gGL.vertex2i( x_offset + width, y_offset + height ); - - gGL.texCoord2f( 0.f, 0.f ); - gGL.vertex2i( x_offset, y_offset + height ); - - gGL.texCoord2f( 0.f, max_v ); - gGL.vertex2i( x_offset, y_offset ); - - gGL.texCoord2f( max_u, max_v ); - gGL.vertex2i( x_offset + width, y_offset ); - } - else - { - // render using web browser reported width and height, instead of trying to invert GL scale - gGL.texCoord2f( max_u, max_v ); - gGL.vertex2i( x_offset + width, y_offset + height ); - - gGL.texCoord2f( 0.f, max_v ); - gGL.vertex2i( x_offset, y_offset + height ); - - gGL.texCoord2f( 0.f, 0.f ); - gGL.vertex2i( x_offset, y_offset ); - - gGL.texCoord2f( max_u, 0.f ); - gGL.vertex2i( x_offset + width, y_offset ); - } - gGL.end(); - } - gGL.popUIMatrix(); - - } - else - { - // Setting these will make LLPanel::draw draw the opaque background color. - setBackgroundVisible(true); - setBackgroundOpaque(true); - } - - // highlight if keyboard focus here. (TODO: this needs some work) - if ( mBorder && mBorder->getVisible() ) - mBorder->setKeyboardFocusHighlight( gFocusMgr.childHasKeyboardFocus( this ) ); - - LLPanel::draw(); - - // Restore the previous values - setBackgroundVisible(background_visible); - setBackgroundOpaque(background_opaque); -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLMediaCtrl::calcOffsetsAndSize(S32 *x_offset, S32 *y_offset, S32 *width, S32 *height) -{ - const LLRect &r = getRect(); - *x_offset = *y_offset = 0; - - if (mStretchToFill) - { - if (mMaintainAspectRatio && mMediaSource && mMediaSource->getMediaPlugin()) - { - F32 media_aspect = (F32)(mMediaSource->getMediaPlugin()->getWidth()) / (F32)(mMediaSource->getMediaPlugin()->getHeight()); - F32 view_aspect = (F32)(r.getWidth()) / (F32)(r.getHeight()); - if (media_aspect > view_aspect) - { - // max width, adjusted height - *width = r.getWidth(); - *height = llmin(llmax(ll_round(*width / media_aspect), 0), r.getHeight()); - } - else - { - // max height, adjusted width - *height = r.getHeight(); - *width = llmin(llmax(ll_round(*height * media_aspect), 0), r.getWidth()); - } - } - else - { - *width = r.getWidth(); - *height = r.getHeight(); - } - } - else - { - *width = llmin(mMediaSource->getMediaPlugin()->getWidth(), r.getWidth()); - *height = llmin(mMediaSource->getMediaPlugin()->getHeight(), r.getHeight()); - } - - *x_offset = (r.getWidth() - *width) / 2; - *y_offset = (r.getHeight() - *height) / 2; -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLMediaCtrl::convertInputCoords(S32& x, S32& y) -{ - S32 x_offset, y_offset, width, height; - calcOffsetsAndSize(&x_offset, &y_offset, &width, &height); - - x -= x_offset; - y -= y_offset; - - bool coords_opengl = false; - - if(mMediaSource && mMediaSource->hasMedia()) - { - coords_opengl = mMediaSource->getMediaPlugin()->getTextureCoordsOpenGL(); - } - - x = ll_round((F32)x * LLUI::getScaleFactor().mV[VX]); - if ( ! coords_opengl ) - { - y = ll_round((F32)(y) * LLUI::getScaleFactor().mV[VY]); - } - else - { - y = ll_round((F32)(getRect().getHeight() - y) * LLUI::getScaleFactor().mV[VY]); - }; -} - -//////////////////////////////////////////////////////////////////////////////// -// inherited from LLViewerMediaObserver -//virtual -void LLMediaCtrl::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) -{ - switch(event) - { - case MEDIA_EVENT_CONTENT_UPDATED: - { - // LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CONTENT_UPDATED " << LL_ENDL; - }; - break; - - case MEDIA_EVENT_TIME_DURATION_UPDATED: - { - // LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_TIME_DURATION_UPDATED, time is " << self->getCurrentTime() << " of " << self->getDuration() << LL_ENDL; - }; - break; - - case MEDIA_EVENT_SIZE_CHANGED: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_SIZE_CHANGED " << LL_ENDL; - LLRect r = getRect(); - reshape( r.getWidth(), r.getHeight(), false ); - }; - break; - - case MEDIA_EVENT_CURSOR_CHANGED: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CURSOR_CHANGED, new cursor is " << self->getCursorName() << LL_ENDL; - } - break; - - case MEDIA_EVENT_NAVIGATE_BEGIN: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_NAVIGATE_BEGIN, url is " << self->getNavigateURI() << LL_ENDL; - hideNotification(); - }; - break; - - case MEDIA_EVENT_NAVIGATE_COMPLETE: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_NAVIGATE_COMPLETE, result string is: " << self->getNavigateResultString() << LL_ENDL; - if(mHidingInitialLoad) - { - mHidingInitialLoad = false; - } - }; - break; - - case MEDIA_EVENT_PROGRESS_UPDATED: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PROGRESS_UPDATED, loading at " << self->getProgressPercent() << "%" << LL_ENDL; - }; - break; - - case MEDIA_EVENT_STATUS_TEXT_CHANGED: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_STATUS_TEXT_CHANGED, new status text is: " << self->getStatusText() << LL_ENDL; - }; - break; - - case MEDIA_EVENT_LOCATION_CHANGED: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_LOCATION_CHANGED, new uri is: " << self->getLocation() << LL_ENDL; - }; - break; - - case MEDIA_EVENT_NAVIGATE_ERROR_PAGE: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_NAVIGATE_ERROR_PAGE" << LL_ENDL; - if ( mErrorPageURL.length() > 0 ) - { - navigateTo(mErrorPageURL, HTTP_CONTENT_TEXT_HTML); - }; - }; - break; - - case MEDIA_EVENT_CLICK_LINK_HREF: - { - // retrieve the event parameters - std::string url = self->getClickURL(); - std::string target = self->isOverrideClickTarget() ? self->getOverrideClickTarget() : self->getClickTarget(); - std::string uuid = self->getClickUUID(); - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CLICK_LINK_HREF, target is \"" << target << "\", uri is " << url << LL_ENDL; - - // try as slurl first - if (!LLURLDispatcher::dispatch(url, "clicked", NULL, mTrusted)) - { - LLWeb::loadURL(url, target, uuid); - } - - // CP: removing this code because we no longer support popups so this breaks the flow. - // replaced with a bare call to LLWeb::LoadURL(...) - //LLNotification::Params notify_params; - //notify_params.name = "PopupAttempt"; - //notify_params.payload = LLSD().with("target", target).with("url", url).with("uuid", uuid).with("media_id", mMediaTextureID); - //notify_params.functor.function = boost::bind(&LLMediaCtrl::onPopup, this, _1, _2); - - //if (mTrusted) - //{ - // LLNotifications::instance().forceResponse(notify_params, 0); - //} - //else - //{ - // LLNotifications::instance().add(notify_params); - //} - break; - }; - - case MEDIA_EVENT_CLICK_LINK_NOFOLLOW: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CLICK_LINK_NOFOLLOW, uri is " << self->getClickURL() << LL_ENDL; - }; - break; - - case MEDIA_EVENT_PLUGIN_FAILED: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PLUGIN_FAILED" << LL_ENDL; - }; - break; - - case MEDIA_EVENT_PLUGIN_FAILED_LAUNCH: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PLUGIN_FAILED_LAUNCH" << LL_ENDL; - }; - break; - - case MEDIA_EVENT_NAME_CHANGED: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_NAME_CHANGED" << LL_ENDL; - }; - break; - - case MEDIA_EVENT_CLOSE_REQUEST: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CLOSE_REQUEST" << LL_ENDL; - } - break; - - case MEDIA_EVENT_PICK_FILE_REQUEST: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PICK_FILE_REQUEST" << LL_ENDL; - } - break; - - case MEDIA_EVENT_GEOMETRY_CHANGE: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_GEOMETRY_CHANGE, uuid is " << self->getClickUUID() << LL_ENDL; - } - break; - - case MEDIA_EVENT_AUTH_REQUEST: - { - LLNotification::Params auth_request_params; - auth_request_params.name = "AuthRequest"; - - // pass in host name and realm for site (may be zero length but will always exist) - LLSD args; - LLURL raw_url( self->getAuthURL().c_str() ); - args["HOST_NAME"] = raw_url.getAuthority(); - args["REALM"] = self->getAuthRealm(); - auth_request_params.substitutions = args; - - auth_request_params.payload = LLSD().with("media_id", mMediaTextureID); - auth_request_params.functor.function = boost::bind(&LLViewerMedia::authSubmitCallback, _1, _2); - LLNotifications::instance().add(auth_request_params); - }; - break; - - case MEDIA_EVENT_LINK_HOVERED: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_LINK_HOVERED, hover text is: " << self->getHoverText() << LL_ENDL; - mHoverTextChanged = true; - }; - break; - - case MEDIA_EVENT_FILE_DOWNLOAD: - { - if (mAllowFileDownload) - { - // pick a file from SAVE FILE dialog - // for now the only thing that should be allowed to save is 360s - std::string suggested_filename = self->getFileDownloadFilename(); - LLFilePicker::ESaveFilter filter = LLFilePicker::FFSAVE_ALL; - if (suggested_filename.find(".jpg") != std::string::npos || suggested_filename.find(".jpeg") != std::string::npos) - filter = LLFilePicker::FFSAVE_JPEG; - if (suggested_filename.find(".png") != std::string::npos) - filter = LLFilePicker::FFSAVE_PNG; - - (new LLMediaFilePicker(self, filter, suggested_filename))->getFile(); - } - else - { - // Media might be blocked, waiting for a file, - // send an empty response to unblock it - const std::vector empty_response; - self->sendPickFileResponse(empty_response); - - LLNotificationsUtil::add("MediaFileDownloadUnsupported"); - } - }; - break; - - case MEDIA_EVENT_DEBUG_MESSAGE: - { - LL_INFOS("media") << self->getDebugMessageText() << LL_ENDL; - }; - break; - }; - - // chain all events to any potential observers of this object. - emitEvent(self, event); -} - -//////////////////////////////////////////////////////////////////////////////// -// -std::string LLMediaCtrl::getCurrentNavUrl() -{ - return mCurrentNavUrl; -} - -void LLMediaCtrl::onPopup(const LLSD& notification, const LLSD& response) -{ - if (response["open"]) - { - LLWeb::loadURL(notification["payload"]["url"], notification["payload"]["target"], notification["payload"]["uuid"]); - } - else - { - // Make sure the opening instance knows its window open request was denied, so it can clean things up. - LLViewerMedia::getInstance()->proxyWindowClosed(notification["payload"]["uuid"]); - } -} - -void LLMediaCtrl::showNotification(LLNotificationPtr notify) -{ - LLWindowShade* shade = getChild("notification_shade"); - - if (notify->getIcon() == "Popup_Caution") - { - shade->setBackgroundImage(LLUI::getUIImage("Yellow_Gradient")); - shade->setTextColor(LLColor4::black); - shade->setCanClose(true); - } - else if (notify->getName() == "AuthRequest") - { - shade->setBackgroundImage(LLUI::getUIImage("Yellow_Gradient")); - shade->setTextColor(LLColor4::black); - shade->setCanClose(false); - } - else - { - //HACK: make this a property of the notification itself, "cancellable" - shade->setCanClose(false); - shade->setTextColor(LLUIColorTable::instance().getColor("LabelTextColor")); - } - - mWindowShade->show(notify); -} - -void LLMediaCtrl::hideNotification() -{ - if (mWindowShade) - { - mWindowShade->hide(); - } -} - -void LLMediaCtrl::setTrustedContent(bool trusted) -{ - mTrusted = trusted; - if (mMediaSource) - { - mMediaSource->setTrustedBrowser(trusted); - } -} - -bool LLMediaCtrl::wantsKeyUpKeyDown() const -{ - return true; -} - -bool LLMediaCtrl::wantsReturnKey() const -{ - return true; -} +/** + * @file LLMediaCtrl.cpp + * @brief Web browser UI control + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "lltooltip.h" + +#include "llmediactrl.h" + +// viewer includes +#include "llfloaterworldmap.h" +#include "lluictrlfactory.h" +#include "llurldispatcher.h" +#include "llviewborder.h" +#include "llviewercontrol.h" +#include "llviewermedia.h" +#include "llviewertexture.h" +#include "llviewerwindow.h" +#include "lldebugmessagebox.h" +#include "llweb.h" +#include "llrender.h" +#include "llpluginclassmedia.h" +#include "llslurl.h" +#include "lluictrlfactory.h" // LLDefaultChildRegistry +#include "llkeyboard.h" +#include "llviewermenu.h" +#include "llviewermenufile.h" // LLFilePickerThread + +// linden library includes +#include "llfocusmgr.h" +#include "llsdutil.h" +#include "lllayoutstack.h" +#include "lliconctrl.h" +#include "llhttpconstants.h" +#include "lltextbox.h" +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "lllineeditor.h" +#include "llfloaterwebcontent.h" +#include "llwindowshade.h" + +extern bool gRestoreGL; + +static LLDefaultChildRegistry::Register r("web_browser"); + +LLMediaCtrl::Params::Params() +: start_url("start_url"), + border_visible("border_visible", true), + decouple_texture_size("decouple_texture_size", false), + texture_width("texture_width", 1024), + texture_height("texture_height", 1024), + caret_color("caret_color"), + initial_mime_type("initial_mime_type"), + error_page_url("error_page_url"), + media_id("media_id"), + trusted_content("trusted_content", false), + focus_on_click("focus_on_click", true) +{ +} + +LLMediaCtrl::LLMediaCtrl( const Params& p) : + LLPanel( p ), + LLInstanceTracker(LLUUID::generateNewID()), + mTextureDepthBytes( 4 ), + mBorder(NULL), + mFrequentUpdates( true ), + mForceUpdate( false ), + mHomePageUrl( "" ), + mAlwaysRefresh( false ), + mMediaSource( 0 ), + mTakeFocusOnClick( p.focus_on_click ), + mCurrentNavUrl( "" ), + mStretchToFill( true ), + mMaintainAspectRatio ( true ), + mDecoupleTextureSize ( false ), + mUpdateScrolls( false ), + mTextureWidth ( 1024 ), + mTextureHeight ( 1024 ), + mClearCache(false), + mHomePageMimeType(p.initial_mime_type), + mErrorPageURL(p.error_page_url), + mTrusted(p.trusted_content), + mWindowShade(NULL), + mHoverTextChanged(false), + mAllowFileDownload(false) +{ + { + LLColor4 color = p.caret_color().get(); + setCaretColor( (unsigned int)color.mV[0], (unsigned int)color.mV[1], (unsigned int)color.mV[2] ); + } + + setHomePageUrl(p.start_url, p.initial_mime_type); + + setBorderVisible(p.border_visible); + + setDecoupleTextureSize(p.decouple_texture_size); + + setTextureSize(p.texture_width, p.texture_height); + + if(!getDecoupleTextureSize()) + { + S32 screen_width = ll_round((F32)getRect().getWidth() * LLUI::getScaleFactor().mV[VX]); + S32 screen_height = ll_round((F32)getRect().getHeight() * LLUI::getScaleFactor().mV[VY]); + + setTextureSize(screen_width, screen_height); + } + + mMediaTextureID = getKey(); + + // We don't need to create the media source up front anymore unless we have a non-empty home URL to navigate to. + if(!mHomePageUrl.empty()) + { + navigateHome(); + } + + LLWindowShade::Params params; + params.name = "notification_shade"; + params.rect = getLocalRect(); + params.follows.flags = FOLLOWS_ALL; + params.modal = true; + + mWindowShade = LLUICtrlFactory::create(params); + + addChild(mWindowShade); +} + +LLMediaCtrl::~LLMediaCtrl() +{ + auto menu = mContextMenuHandle.get(); + if (menu) + { + menu->die(); + mContextMenuHandle.markDead(); + } + + if (mMediaSource) + { + mMediaSource->remObserver( this ); + mMediaSource = NULL; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::setBorderVisible( bool border_visible ) +{ + if ( mBorder ) + { + mBorder->setVisible( border_visible ); + }; +}; + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::setTakeFocusOnClick( bool take_focus ) +{ + mTakeFocusOnClick = take_focus; +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLMediaCtrl::handleHover( S32 x, S32 y, MASK mask ) +{ + if (LLPanel::handleHover(x, y, mask)) return true; + convertInputCoords(x, y); + + if (mMediaSource) + { + mMediaSource->mouseMove(x, y, mask); + gViewerWindow->setCursor(mMediaSource->getLastSetCursor()); + } + + // TODO: Is this the right way to handle hover text changes driven by the plugin? + if(mHoverTextChanged) + { + mHoverTextChanged = false; + handleToolTip(x, y, mask); + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLMediaCtrl::handleScrollWheel( S32 x, S32 y, S32 clicks ) +{ + if (LLPanel::handleScrollWheel(x, y, clicks)) return true; + if (mMediaSource && mMediaSource->hasMedia()) + { + convertInputCoords(x, y); + mMediaSource->scrollWheel(x, y, 0, clicks, gKeyboard->currentMask(true)); + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLMediaCtrl::handleScrollHWheel(S32 x, S32 y, S32 clicks) +{ + if (LLPanel::handleScrollHWheel(x, y, clicks)) return true; + if (mMediaSource && mMediaSource->hasMedia()) + { + convertInputCoords(x, y); + mMediaSource->scrollWheel(x, y, clicks, 0, gKeyboard->currentMask(true)); + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// virtual +bool LLMediaCtrl::handleToolTip(S32 x, S32 y, MASK mask) +{ + std::string hover_text; + + if (mMediaSource && mMediaSource->hasMedia()) + hover_text = mMediaSource->getMediaPlugin()->getHoverText(); + + if(hover_text.empty()) + { + return false; + } + else + { + S32 screen_x, screen_y; + + localPointToScreen(x, y, &screen_x, &screen_y); + LLRect sticky_rect_screen; + sticky_rect_screen.setCenterAndSize(screen_x, screen_y, 20, 20); + + LLToolTipMgr::instance().show(LLToolTip::Params() + .message(hover_text) + .sticky_rect(sticky_rect_screen)); + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLMediaCtrl::handleMouseUp( S32 x, S32 y, MASK mask ) +{ + if (LLPanel::handleMouseUp(x, y, mask)) return true; + convertInputCoords(x, y); + + if (mMediaSource) + { + mMediaSource->mouseUp(x, y, mask); + } + + gFocusMgr.setMouseCapture( NULL ); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLMediaCtrl::handleMouseDown( S32 x, S32 y, MASK mask ) +{ + if (LLPanel::handleMouseDown(x, y, mask)) return true; + convertInputCoords(x, y); + + if (mMediaSource) + mMediaSource->mouseDown(x, y, mask); + + gFocusMgr.setMouseCapture( this ); + + if (mTakeFocusOnClick) + { + setFocus( true ); + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLMediaCtrl::handleRightMouseUp( S32 x, S32 y, MASK mask ) +{ + if (LLPanel::handleRightMouseUp(x, y, mask)) return true; + convertInputCoords(x, y); + + if (mMediaSource) + { + mMediaSource->mouseUp(x, y, mask, 1); + + // *HACK: LLMediaImplLLMozLib automatically takes focus on mouseup, + // in addition to the onFocusReceived() call below. Undo this. JC + if (!mTakeFocusOnClick) + { + mMediaSource->focus(false); + gViewerWindow->focusClient(); + } + } + + gFocusMgr.setMouseCapture( NULL ); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLMediaCtrl::handleRightMouseDown( S32 x, S32 y, MASK mask ) +{ + if (LLPanel::handleRightMouseDown(x, y, mask)) return true; + + S32 media_x = x, media_y = y; + convertInputCoords(media_x, media_y); + + if (mMediaSource) + mMediaSource->mouseDown(media_x, media_y, mask, 1); + + gFocusMgr.setMouseCapture( this ); + + if (mTakeFocusOnClick) + { + setFocus( true ); + } + + auto menu = mContextMenuHandle.get(); + if (!menu) + { + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registar; + registar.add("Open.WebInspector", boost::bind(&LLMediaCtrl::onOpenWebInspector, this)); + + // stinson 05/05/2014 : use this as the parent of the context menu if the static menu + // container has yet to be created + LLPanel* menuParent = (LLMenuGL::sMenuContainer != NULL) ? dynamic_cast(LLMenuGL::sMenuContainer) : dynamic_cast(this); + llassert(menuParent != NULL); + menu = LLUICtrlFactory::getInstance()->createFromFile( + "menu_media_ctrl.xml", menuParent, LLViewerMenuHolderGL::child_registry_t::instance()); + if (menu) + { + mContextMenuHandle = menu->getHandle(); + } + } + + if (menu) + { + // hide/show debugging options + bool media_plugin_debugging_enabled = gSavedSettings.getBOOL("MediaPluginDebugging"); + menu->setItemVisible("open_webinspector", media_plugin_debugging_enabled ); + menu->setItemVisible("debug_separator", media_plugin_debugging_enabled ); + + menu->show(x, y); + LLMenuGL::showPopup(this, menu, x, y); + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLMediaCtrl::handleDoubleClick( S32 x, S32 y, MASK mask ) +{ + if (LLPanel::handleDoubleClick(x, y, mask)) return true; + convertInputCoords(x, y); + + if (mMediaSource) + mMediaSource->mouseDoubleClick( x, y, mask); + + gFocusMgr.setMouseCapture( this ); + + if (mTakeFocusOnClick) + { + setFocus( true ); + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::onFocusReceived() +{ + if (mMediaSource) + { + mMediaSource->focus(true); + + // Set focus for edit menu items + LLEditMenuHandler::gEditMenuHandler = mMediaSource; + } + + LLPanel::onFocusReceived(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::onFocusLost() +{ + if (mMediaSource) + { + mMediaSource->focus(false); + + if( LLEditMenuHandler::gEditMenuHandler == mMediaSource ) + { + // Clear focus for edit menu items + LLEditMenuHandler::gEditMenuHandler = NULL; + } + } + + gViewerWindow->focusClient(); + + LLPanel::onFocusLost(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLMediaCtrl::postBuild () +{ + setVisibleCallback(boost::bind(&LLMediaCtrl::onVisibilityChanged, this, _2)); + + return true; +} + +void LLMediaCtrl::onOpenWebInspector() +{ + if (mMediaSource && mMediaSource->hasMedia()) + mMediaSource->getMediaPlugin()->showWebInspector( true ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLMediaCtrl::handleKeyHere( KEY key, MASK mask ) +{ + bool result = false; + + if (mMediaSource) + { + result = mMediaSource->handleKeyHere(key, mask); + } + + if ( ! result ) + result = LLPanel::handleKeyHere(key, mask); + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLMediaCtrl::handleKeyUpHere(KEY key, MASK mask) +{ + bool result = false; + + if (mMediaSource) + { + result = mMediaSource->handleKeyUpHere(key, mask); + } + + if (!result) + result = LLPanel::handleKeyUpHere(key, mask); + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::onVisibilityChange ( bool new_visibility ) +{ + LL_INFOS() << "visibility changed to " << (new_visibility?"true":"false") << LL_ENDL; + if(mMediaSource) + { + mMediaSource->setVisible( new_visibility ); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLMediaCtrl::handleUnicodeCharHere(llwchar uni_char) +{ + bool result = false; + + if (mMediaSource) + { + result = mMediaSource->handleUnicodeCharHere(uni_char); + } + + if ( ! result ) + result = LLPanel::handleUnicodeCharHere(uni_char); + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::onVisibilityChanged ( const LLSD& new_visibility ) +{ + // set state of frequent updates automatically if visibility changes + if ( new_visibility.asBoolean() ) + { + mFrequentUpdates = true; + } + else + { + mFrequentUpdates = false; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::reshape( S32 width, S32 height, bool called_from_parent ) +{ + if(!getDecoupleTextureSize()) + { + S32 screen_width = ll_round((F32)width * LLUI::getScaleFactor().mV[VX]); + S32 screen_height = ll_round((F32)height * LLUI::getScaleFactor().mV[VY]); + + // when floater is minimized, these sizes are negative + if ( screen_height > 0 && screen_width > 0 ) + { + setTextureSize(screen_width, screen_height); + } + } + + LLUICtrl::reshape( width, height, called_from_parent ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::navigateBack() +{ + if (mMediaSource && mMediaSource->hasMedia()) + { + mMediaSource->getMediaPlugin()->browse_back(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::navigateForward() +{ + if (mMediaSource && mMediaSource->hasMedia()) + { + mMediaSource->getMediaPlugin()->browse_forward(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::navigateStop() +{ + if (mMediaSource && mMediaSource->hasMedia()) + { + mMediaSource->getMediaPlugin()->browse_stop(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLMediaCtrl::canNavigateBack() +{ + if (mMediaSource) + return mMediaSource->canNavigateBack(); + else + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLMediaCtrl::canNavigateForward() +{ + if (mMediaSource) + return mMediaSource->canNavigateForward(); + else + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::clearCache() +{ + if(mMediaSource) + { + mMediaSource->clearCache(); + } + else + { + mClearCache = true; + } + +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::navigateTo( std::string url_in, std::string mime_type, bool clean_browser) +{ + // don't browse to anything that starts with secondlife:// or sl:// + const std::string protocol1 = "secondlife://"; + const std::string protocol2 = "sl://"; + if ((LLStringUtil::compareInsensitive(url_in.substr(0, protocol1.length()), protocol1) == 0) || + (LLStringUtil::compareInsensitive(url_in.substr(0, protocol2.length()), protocol2) == 0)) + { + // TODO: Print out/log this attempt? + // LL_INFOS() << "Rejecting attempt to load restricted website :" << urlIn << LL_ENDL; + return; + } + + if (ensureMediaSourceExists()) + { + mCurrentNavUrl = url_in; + mMediaSource->setSize(mTextureWidth, mTextureHeight); + mMediaSource->navigateTo(url_in, mime_type, mime_type.empty(), false, clean_browser); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::navigateToLocalPage( const std::string& subdir, const std::string& filename_in ) +{ + std::string filename(gDirUtilp->add(subdir, filename_in)); + std::string expanded_filename = gDirUtilp->findSkinnedFilename("html", filename); + + if (expanded_filename.empty()) + { + LL_WARNS() << "File " << filename << "not found" << LL_ENDL; + return; + } + if (ensureMediaSourceExists()) + { + mCurrentNavUrl = expanded_filename; + mMediaSource->setSize(mTextureWidth, mTextureHeight); + mMediaSource->navigateTo(expanded_filename, HTTP_CONTENT_TEXT_HTML, false); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::navigateHome() +{ + if (ensureMediaSourceExists()) + { + mMediaSource->setSize(mTextureWidth, mTextureHeight); + mMediaSource->navigateHome(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::setHomePageUrl( const std::string& urlIn, const std::string& mime_type ) +{ + mHomePageUrl = urlIn; + if (mMediaSource) + { + mMediaSource->setHomeURL(mHomePageUrl, mime_type); + } +} + +void LLMediaCtrl::setTarget(const std::string& target) +{ + mTarget = target; + if (mMediaSource) + { + mMediaSource->setTarget(mTarget); + } +} + +void LLMediaCtrl::setErrorPageURL(const std::string& url) +{ + mErrorPageURL = url; +} + +const std::string& LLMediaCtrl::getErrorPageURL() +{ + return mErrorPageURL; +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLMediaCtrl::setCaretColor(unsigned int red, unsigned int green, unsigned int blue) +{ + //NOOP + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::setTextureSize(S32 width, S32 height) +{ + mTextureWidth = width; + mTextureHeight = height; + + if(mMediaSource) + { + mMediaSource->setSize(mTextureWidth, mTextureHeight); + mForceUpdate = true; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +std::string LLMediaCtrl::getHomePageUrl() +{ + return mHomePageUrl; +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLMediaCtrl::ensureMediaSourceExists() +{ + if(mMediaSource.isNull()) + { + // If we don't already have a media source, try to create one. + mMediaSource = LLViewerMedia::getInstance()->newMediaImpl(mMediaTextureID, mTextureWidth, mTextureHeight); + if ( mMediaSource ) + { + mMediaSource->setUsedInUI(true); + mMediaSource->setHomeURL(mHomePageUrl, mHomePageMimeType); + mMediaSource->setTarget(mTarget); + mMediaSource->setVisible( getVisible() ); + mMediaSource->addObserver( this ); + mMediaSource->setBackgroundColor( getBackgroundColor() ); + mMediaSource->setTrustedBrowser(mTrusted); + + F32 scale_factor = LLUI::getScaleFactor().mV[ VX ]; + if (scale_factor != mMediaSource->getPageZoomFactor()) + { + mMediaSource->setPageZoomFactor( scale_factor ); + mUpdateScrolls = true; + } + + if(mClearCache) + { + mMediaSource->clearCache(); + mClearCache = false; + } + } + else + { + LL_WARNS() << "media source create failed " << LL_ENDL; + // return; + } + } + + return !mMediaSource.isNull(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::unloadMediaSource() +{ + mMediaSource = NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +// +LLPluginClassMedia* LLMediaCtrl::getMediaPlugin() +{ + return mMediaSource.isNull() ? NULL : mMediaSource->getMediaPlugin(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::draw() +{ + F32 alpha = getDrawContext().mAlpha; + + if ( gRestoreGL == 1 || mUpdateScrolls) + { + LLRect r = getRect(); + reshape( r.getWidth(), r.getHeight(), false ); + mUpdateScrolls = false; + return; + } + + // NOTE: optimization needed here - probably only need to do this once + // unless tearoffs change the parent which they probably do. + const LLUICtrl* ptr = findRootMostFocusRoot(); + if ( ptr && ptr->hasFocus() ) + { + setFrequentUpdates( true ); + } + else + { + setFrequentUpdates( false ); + }; + + bool draw_media = false; + + LLPluginClassMedia* media_plugin = NULL; + LLViewerMediaTexture* media_texture = NULL; + + if(mMediaSource && mMediaSource->hasMedia()) + { + media_plugin = mMediaSource->getMediaPlugin(); + + if(media_plugin && (media_plugin->textureValid())) + { + media_texture = LLViewerTextureManager::findMediaTexture(mMediaTextureID); + if(media_texture) + { + draw_media = true; + } + } + } + + bool background_visible = isBackgroundVisible(); + bool background_opaque = isBackgroundOpaque(); + + if(draw_media) + { + gGL.pushUIMatrix(); + { + F32 scale_factor = LLUI::getScaleFactor().mV[ VX ]; + if (scale_factor != mMediaSource->getPageZoomFactor()) + { + mMediaSource->setPageZoomFactor( scale_factor ); + mUpdateScrolls = true; + } + + // scale texture to fit the space using texture coords + gGL.getTexUnit(0)->bind(media_texture); + LLColor4 media_color = LLColor4::white % alpha; + gGL.color4fv( media_color.mV ); + F32 max_u = ( F32 )media_plugin->getWidth() / ( F32 )media_plugin->getTextureWidth(); + F32 max_v = ( F32 )media_plugin->getHeight() / ( F32 )media_plugin->getTextureHeight(); + + S32 x_offset, y_offset, width, height; + calcOffsetsAndSize(&x_offset, &y_offset, &width, &height); + + // draw the browser + gGL.begin( LLRender::QUADS ); + if (! media_plugin->getTextureCoordsOpenGL()) + { + // render using web browser reported width and height, instead of trying to invert GL scale + gGL.texCoord2f( max_u, 0.f ); + gGL.vertex2i( x_offset + width, y_offset + height ); + + gGL.texCoord2f( 0.f, 0.f ); + gGL.vertex2i( x_offset, y_offset + height ); + + gGL.texCoord2f( 0.f, max_v ); + gGL.vertex2i( x_offset, y_offset ); + + gGL.texCoord2f( max_u, max_v ); + gGL.vertex2i( x_offset + width, y_offset ); + } + else + { + // render using web browser reported width and height, instead of trying to invert GL scale + gGL.texCoord2f( max_u, max_v ); + gGL.vertex2i( x_offset + width, y_offset + height ); + + gGL.texCoord2f( 0.f, max_v ); + gGL.vertex2i( x_offset, y_offset + height ); + + gGL.texCoord2f( 0.f, 0.f ); + gGL.vertex2i( x_offset, y_offset ); + + gGL.texCoord2f( max_u, 0.f ); + gGL.vertex2i( x_offset + width, y_offset ); + } + gGL.end(); + } + gGL.popUIMatrix(); + + } + else + { + // Setting these will make LLPanel::draw draw the opaque background color. + setBackgroundVisible(true); + setBackgroundOpaque(true); + } + + // highlight if keyboard focus here. (TODO: this needs some work) + if ( mBorder && mBorder->getVisible() ) + mBorder->setKeyboardFocusHighlight( gFocusMgr.childHasKeyboardFocus( this ) ); + + LLPanel::draw(); + + // Restore the previous values + setBackgroundVisible(background_visible); + setBackgroundOpaque(background_opaque); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::calcOffsetsAndSize(S32 *x_offset, S32 *y_offset, S32 *width, S32 *height) +{ + const LLRect &r = getRect(); + *x_offset = *y_offset = 0; + + if (mStretchToFill) + { + if (mMaintainAspectRatio && mMediaSource && mMediaSource->getMediaPlugin()) + { + F32 media_aspect = (F32)(mMediaSource->getMediaPlugin()->getWidth()) / (F32)(mMediaSource->getMediaPlugin()->getHeight()); + F32 view_aspect = (F32)(r.getWidth()) / (F32)(r.getHeight()); + if (media_aspect > view_aspect) + { + // max width, adjusted height + *width = r.getWidth(); + *height = llmin(llmax(ll_round(*width / media_aspect), 0), r.getHeight()); + } + else + { + // max height, adjusted width + *height = r.getHeight(); + *width = llmin(llmax(ll_round(*height * media_aspect), 0), r.getWidth()); + } + } + else + { + *width = r.getWidth(); + *height = r.getHeight(); + } + } + else + { + *width = llmin(mMediaSource->getMediaPlugin()->getWidth(), r.getWidth()); + *height = llmin(mMediaSource->getMediaPlugin()->getHeight(), r.getHeight()); + } + + *x_offset = (r.getWidth() - *width) / 2; + *y_offset = (r.getHeight() - *height) / 2; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::convertInputCoords(S32& x, S32& y) +{ + S32 x_offset, y_offset, width, height; + calcOffsetsAndSize(&x_offset, &y_offset, &width, &height); + + x -= x_offset; + y -= y_offset; + + bool coords_opengl = false; + + if(mMediaSource && mMediaSource->hasMedia()) + { + coords_opengl = mMediaSource->getMediaPlugin()->getTextureCoordsOpenGL(); + } + + x = ll_round((F32)x * LLUI::getScaleFactor().mV[VX]); + if ( ! coords_opengl ) + { + y = ll_round((F32)(y) * LLUI::getScaleFactor().mV[VY]); + } + else + { + y = ll_round((F32)(getRect().getHeight() - y) * LLUI::getScaleFactor().mV[VY]); + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// inherited from LLViewerMediaObserver +//virtual +void LLMediaCtrl::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) +{ + switch(event) + { + case MEDIA_EVENT_CONTENT_UPDATED: + { + // LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CONTENT_UPDATED " << LL_ENDL; + }; + break; + + case MEDIA_EVENT_TIME_DURATION_UPDATED: + { + // LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_TIME_DURATION_UPDATED, time is " << self->getCurrentTime() << " of " << self->getDuration() << LL_ENDL; + }; + break; + + case MEDIA_EVENT_SIZE_CHANGED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_SIZE_CHANGED " << LL_ENDL; + LLRect r = getRect(); + reshape( r.getWidth(), r.getHeight(), false ); + }; + break; + + case MEDIA_EVENT_CURSOR_CHANGED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CURSOR_CHANGED, new cursor is " << self->getCursorName() << LL_ENDL; + } + break; + + case MEDIA_EVENT_NAVIGATE_BEGIN: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_NAVIGATE_BEGIN, url is " << self->getNavigateURI() << LL_ENDL; + hideNotification(); + }; + break; + + case MEDIA_EVENT_NAVIGATE_COMPLETE: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_NAVIGATE_COMPLETE, result string is: " << self->getNavigateResultString() << LL_ENDL; + if(mHidingInitialLoad) + { + mHidingInitialLoad = false; + } + }; + break; + + case MEDIA_EVENT_PROGRESS_UPDATED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PROGRESS_UPDATED, loading at " << self->getProgressPercent() << "%" << LL_ENDL; + }; + break; + + case MEDIA_EVENT_STATUS_TEXT_CHANGED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_STATUS_TEXT_CHANGED, new status text is: " << self->getStatusText() << LL_ENDL; + }; + break; + + case MEDIA_EVENT_LOCATION_CHANGED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_LOCATION_CHANGED, new uri is: " << self->getLocation() << LL_ENDL; + }; + break; + + case MEDIA_EVENT_NAVIGATE_ERROR_PAGE: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_NAVIGATE_ERROR_PAGE" << LL_ENDL; + if ( mErrorPageURL.length() > 0 ) + { + navigateTo(mErrorPageURL, HTTP_CONTENT_TEXT_HTML); + }; + }; + break; + + case MEDIA_EVENT_CLICK_LINK_HREF: + { + // retrieve the event parameters + std::string url = self->getClickURL(); + std::string target = self->isOverrideClickTarget() ? self->getOverrideClickTarget() : self->getClickTarget(); + std::string uuid = self->getClickUUID(); + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CLICK_LINK_HREF, target is \"" << target << "\", uri is " << url << LL_ENDL; + + // try as slurl first + if (!LLURLDispatcher::dispatch(url, "clicked", NULL, mTrusted)) + { + LLWeb::loadURL(url, target, uuid); + } + + // CP: removing this code because we no longer support popups so this breaks the flow. + // replaced with a bare call to LLWeb::LoadURL(...) + //LLNotification::Params notify_params; + //notify_params.name = "PopupAttempt"; + //notify_params.payload = LLSD().with("target", target).with("url", url).with("uuid", uuid).with("media_id", mMediaTextureID); + //notify_params.functor.function = boost::bind(&LLMediaCtrl::onPopup, this, _1, _2); + + //if (mTrusted) + //{ + // LLNotifications::instance().forceResponse(notify_params, 0); + //} + //else + //{ + // LLNotifications::instance().add(notify_params); + //} + break; + }; + + case MEDIA_EVENT_CLICK_LINK_NOFOLLOW: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CLICK_LINK_NOFOLLOW, uri is " << self->getClickURL() << LL_ENDL; + }; + break; + + case MEDIA_EVENT_PLUGIN_FAILED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PLUGIN_FAILED" << LL_ENDL; + }; + break; + + case MEDIA_EVENT_PLUGIN_FAILED_LAUNCH: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PLUGIN_FAILED_LAUNCH" << LL_ENDL; + }; + break; + + case MEDIA_EVENT_NAME_CHANGED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_NAME_CHANGED" << LL_ENDL; + }; + break; + + case MEDIA_EVENT_CLOSE_REQUEST: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CLOSE_REQUEST" << LL_ENDL; + } + break; + + case MEDIA_EVENT_PICK_FILE_REQUEST: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PICK_FILE_REQUEST" << LL_ENDL; + } + break; + + case MEDIA_EVENT_GEOMETRY_CHANGE: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_GEOMETRY_CHANGE, uuid is " << self->getClickUUID() << LL_ENDL; + } + break; + + case MEDIA_EVENT_AUTH_REQUEST: + { + LLNotification::Params auth_request_params; + auth_request_params.name = "AuthRequest"; + + // pass in host name and realm for site (may be zero length but will always exist) + LLSD args; + LLURL raw_url( self->getAuthURL().c_str() ); + args["HOST_NAME"] = raw_url.getAuthority(); + args["REALM"] = self->getAuthRealm(); + auth_request_params.substitutions = args; + + auth_request_params.payload = LLSD().with("media_id", mMediaTextureID); + auth_request_params.functor.function = boost::bind(&LLViewerMedia::authSubmitCallback, _1, _2); + LLNotifications::instance().add(auth_request_params); + }; + break; + + case MEDIA_EVENT_LINK_HOVERED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_LINK_HOVERED, hover text is: " << self->getHoverText() << LL_ENDL; + mHoverTextChanged = true; + }; + break; + + case MEDIA_EVENT_FILE_DOWNLOAD: + { + if (mAllowFileDownload) + { + // pick a file from SAVE FILE dialog + // for now the only thing that should be allowed to save is 360s + std::string suggested_filename = self->getFileDownloadFilename(); + LLFilePicker::ESaveFilter filter = LLFilePicker::FFSAVE_ALL; + if (suggested_filename.find(".jpg") != std::string::npos || suggested_filename.find(".jpeg") != std::string::npos) + filter = LLFilePicker::FFSAVE_JPEG; + if (suggested_filename.find(".png") != std::string::npos) + filter = LLFilePicker::FFSAVE_PNG; + + (new LLMediaFilePicker(self, filter, suggested_filename))->getFile(); + } + else + { + // Media might be blocked, waiting for a file, + // send an empty response to unblock it + const std::vector empty_response; + self->sendPickFileResponse(empty_response); + + LLNotificationsUtil::add("MediaFileDownloadUnsupported"); + } + }; + break; + + case MEDIA_EVENT_DEBUG_MESSAGE: + { + LL_INFOS("media") << self->getDebugMessageText() << LL_ENDL; + }; + break; + }; + + // chain all events to any potential observers of this object. + emitEvent(self, event); +} + +//////////////////////////////////////////////////////////////////////////////// +// +std::string LLMediaCtrl::getCurrentNavUrl() +{ + return mCurrentNavUrl; +} + +void LLMediaCtrl::onPopup(const LLSD& notification, const LLSD& response) +{ + if (response["open"]) + { + LLWeb::loadURL(notification["payload"]["url"], notification["payload"]["target"], notification["payload"]["uuid"]); + } + else + { + // Make sure the opening instance knows its window open request was denied, so it can clean things up. + LLViewerMedia::getInstance()->proxyWindowClosed(notification["payload"]["uuid"]); + } +} + +void LLMediaCtrl::showNotification(LLNotificationPtr notify) +{ + LLWindowShade* shade = getChild("notification_shade"); + + if (notify->getIcon() == "Popup_Caution") + { + shade->setBackgroundImage(LLUI::getUIImage("Yellow_Gradient")); + shade->setTextColor(LLColor4::black); + shade->setCanClose(true); + } + else if (notify->getName() == "AuthRequest") + { + shade->setBackgroundImage(LLUI::getUIImage("Yellow_Gradient")); + shade->setTextColor(LLColor4::black); + shade->setCanClose(false); + } + else + { + //HACK: make this a property of the notification itself, "cancellable" + shade->setCanClose(false); + shade->setTextColor(LLUIColorTable::instance().getColor("LabelTextColor")); + } + + mWindowShade->show(notify); +} + +void LLMediaCtrl::hideNotification() +{ + if (mWindowShade) + { + mWindowShade->hide(); + } +} + +void LLMediaCtrl::setTrustedContent(bool trusted) +{ + mTrusted = trusted; + if (mMediaSource) + { + mMediaSource->setTrustedBrowser(trusted); + } +} + +bool LLMediaCtrl::wantsKeyUpKeyDown() const +{ + return true; +} + +bool LLMediaCtrl::wantsReturnKey() const +{ + return true; +} diff --git a/indra/newview/llmediactrl.h b/indra/newview/llmediactrl.h index a219bb6ec4..9f9564af46 100644 --- a/indra/newview/llmediactrl.h +++ b/indra/newview/llmediactrl.h @@ -1,224 +1,224 @@ -/** - * @file llmediactrl.h - * @brief Web browser UI control - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLMediaCtrl_H -#define LL_LLMediaCtrl_H - -#include "llviewermedia.h" - -#include "lluictrl.h" -#include "llframetimer.h" -#include "llnotificationptr.h" - -class LLViewBorder; -class LLUICtrlFactory; -class LLContextMenu; - -//////////////////////////////////////////////////////////////////////////////// -// -class LLMediaCtrl : - public LLPanel, - public LLViewerMediaObserver, - public LLViewerMediaEventEmitter, - public LLInstanceTracker -{ - LOG_CLASS(LLMediaCtrl); -public: - - struct Params : public LLInitParam::Block - { - Optional start_url; - - Optional border_visible, - hide_loading, - decouple_texture_size, - trusted_content, - focus_on_click; - - Optional texture_width, - texture_height; - - Optional caret_color; - - Optional initial_mime_type; - Optional media_id; - Optional error_page_url; - - Params(); - }; - -protected: - LLMediaCtrl(const Params&); - friend class LLUICtrlFactory; - -public: - virtual ~LLMediaCtrl(); - - void setBorderVisible( bool border_visible ); - - // For the tutorial window, we don't want to take focus on clicks, - // as the examples include how to move around with the arrow - // keys. Thus we keep focus on the app by setting this false. - // Defaults to true. - void setTakeFocusOnClick( bool take_focus ); - - // handle mouse related methods - virtual bool handleHover( S32 x, S32 y, MASK mask ); - virtual bool handleMouseUp( S32 x, S32 y, MASK mask ); - virtual bool handleMouseDown( S32 x, S32 y, MASK mask ); - virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleRightMouseUp(S32 x, S32 y, MASK mask); - virtual bool handleDoubleClick( S32 x, S32 y, MASK mask ); - virtual bool handleScrollWheel( S32 x, S32 y, S32 clicks ); - virtual bool handleScrollHWheel( S32 x, S32 y, S32 clicks ); - virtual bool handleToolTip(S32 x, S32 y, MASK mask); - - // navigation - void navigateTo( std::string url_in, std::string mime_type = "", bool clean_browser = false); - void navigateBack(); - void navigateHome(); - void navigateForward(); - void navigateStop(); - void navigateToLocalPage( const std::string& subdir, const std::string& filename_in ); - bool canNavigateBack(); - bool canNavigateForward(); - std::string getCurrentNavUrl(); - - // By default, we do not handle "secondlife:///app/" SLURLs, because - // those can cause teleports, open windows, etc. We cannot be sure - // that each "click" is actually due to a user action, versus - // Javascript or some other mechanism. However, we need the search - // floater and login page to handle these URLs. Those are safe - // because we control the page content. See DEV-9530. JC. - void setHomePageUrl( const std::string& urlIn, const std::string& mime_type = LLStringUtil::null ); - std::string getHomePageUrl(); - - void setTarget(const std::string& target); - - void setErrorPageURL(const std::string& url); - const std::string& getErrorPageURL(); - - // Clear the browser cache when the instance gets loaded - void clearCache(); - - // accessor/mutator for flag that indicates if frequent updates to texture happen - bool getFrequentUpdates() { return mFrequentUpdates; }; - void setFrequentUpdates( bool frequentUpdatesIn ) { mFrequentUpdates = frequentUpdatesIn; }; - - void setAlwaysRefresh(bool refresh) { mAlwaysRefresh = refresh; } - bool getAlwaysRefresh() { return mAlwaysRefresh; } - - void setForceUpdate(bool force_update) { mForceUpdate = force_update; } - bool getForceUpdate() { return mForceUpdate; } - - bool ensureMediaSourceExists(); - void unloadMediaSource(); - - LLPluginClassMedia* getMediaPlugin(); - - bool setCaretColor( unsigned int red, unsigned int green, unsigned int blue ); - - void setDecoupleTextureSize(bool decouple) { mDecoupleTextureSize = decouple; } - bool getDecoupleTextureSize() { return mDecoupleTextureSize; } - - void setTextureSize(S32 width, S32 height); - - void showNotification(LLNotificationPtr notify); - void hideNotification(); - - void setTrustedContent(bool trusted); - - void setAllowFileDownload(bool allow) { mAllowFileDownload = allow; } - - // over-rides - virtual bool handleKeyHere( KEY key, MASK mask); - virtual bool handleKeyUpHere(KEY key, MASK mask); - virtual void onVisibilityChange ( bool new_visibility ); - virtual bool handleUnicodeCharHere(llwchar uni_char); - virtual void reshape( S32 width, S32 height, bool called_from_parent = true); - virtual void draw(); - virtual bool postBuild(); - - // focus overrides - void onFocusLost(); - void onFocusReceived(); - - // Incoming media event dispatcher - virtual void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event); - - // right click debugging item - void onOpenWebInspector(); - - LLUUID getTextureID() {return mMediaTextureID;} - - // The Browser windows want keyup and keydown events. Overridden from LLFocusableElement to return true. - virtual bool wantsKeyUpKeyDown() const; - virtual bool wantsReturnKey() const; - - virtual bool acceptsTextInput() const { return true; } - - protected: - void convertInputCoords(S32& x, S32& y); - - private: - void calcOffsetsAndSize(S32 *x_offset, S32 *y_offset, S32 *width, S32 *height); - - private: - void onVisibilityChanged ( const LLSD& new_visibility ); - void onPopup(const LLSD& notification, const LLSD& response); - - const S32 mTextureDepthBytes; - LLUUID mMediaTextureID; - LLViewBorder* mBorder; - bool mFrequentUpdates, - mForceUpdate, - mTrusted, - mAlwaysRefresh, - mTakeFocusOnClick, - mStretchToFill, - mMaintainAspectRatio, - mHideLoading, - mHidingInitialLoad, - mClearCache, - mHoverTextChanged, - mDecoupleTextureSize, - mUpdateScrolls, - mAllowFileDownload; - - std::string mHomePageUrl, - mHomePageMimeType, - mCurrentNavUrl, - mErrorPageURL, - mTarget; - viewer_media_t mMediaSource; - S32 mTextureWidth, - mTextureHeight; - - class LLWindowShade* mWindowShade; - LLHandle mContextMenuHandle; -}; - -#endif // LL_LLMediaCtrl_H +/** + * @file llmediactrl.h + * @brief Web browser UI control + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLMediaCtrl_H +#define LL_LLMediaCtrl_H + +#include "llviewermedia.h" + +#include "lluictrl.h" +#include "llframetimer.h" +#include "llnotificationptr.h" + +class LLViewBorder; +class LLUICtrlFactory; +class LLContextMenu; + +//////////////////////////////////////////////////////////////////////////////// +// +class LLMediaCtrl : + public LLPanel, + public LLViewerMediaObserver, + public LLViewerMediaEventEmitter, + public LLInstanceTracker +{ + LOG_CLASS(LLMediaCtrl); +public: + + struct Params : public LLInitParam::Block + { + Optional start_url; + + Optional border_visible, + hide_loading, + decouple_texture_size, + trusted_content, + focus_on_click; + + Optional texture_width, + texture_height; + + Optional caret_color; + + Optional initial_mime_type; + Optional media_id; + Optional error_page_url; + + Params(); + }; + +protected: + LLMediaCtrl(const Params&); + friend class LLUICtrlFactory; + +public: + virtual ~LLMediaCtrl(); + + void setBorderVisible( bool border_visible ); + + // For the tutorial window, we don't want to take focus on clicks, + // as the examples include how to move around with the arrow + // keys. Thus we keep focus on the app by setting this false. + // Defaults to true. + void setTakeFocusOnClick( bool take_focus ); + + // handle mouse related methods + virtual bool handleHover( S32 x, S32 y, MASK mask ); + virtual bool handleMouseUp( S32 x, S32 y, MASK mask ); + virtual bool handleMouseDown( S32 x, S32 y, MASK mask ); + virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleRightMouseUp(S32 x, S32 y, MASK mask); + virtual bool handleDoubleClick( S32 x, S32 y, MASK mask ); + virtual bool handleScrollWheel( S32 x, S32 y, S32 clicks ); + virtual bool handleScrollHWheel( S32 x, S32 y, S32 clicks ); + virtual bool handleToolTip(S32 x, S32 y, MASK mask); + + // navigation + void navigateTo( std::string url_in, std::string mime_type = "", bool clean_browser = false); + void navigateBack(); + void navigateHome(); + void navigateForward(); + void navigateStop(); + void navigateToLocalPage( const std::string& subdir, const std::string& filename_in ); + bool canNavigateBack(); + bool canNavigateForward(); + std::string getCurrentNavUrl(); + + // By default, we do not handle "secondlife:///app/" SLURLs, because + // those can cause teleports, open windows, etc. We cannot be sure + // that each "click" is actually due to a user action, versus + // Javascript or some other mechanism. However, we need the search + // floater and login page to handle these URLs. Those are safe + // because we control the page content. See DEV-9530. JC. + void setHomePageUrl( const std::string& urlIn, const std::string& mime_type = LLStringUtil::null ); + std::string getHomePageUrl(); + + void setTarget(const std::string& target); + + void setErrorPageURL(const std::string& url); + const std::string& getErrorPageURL(); + + // Clear the browser cache when the instance gets loaded + void clearCache(); + + // accessor/mutator for flag that indicates if frequent updates to texture happen + bool getFrequentUpdates() { return mFrequentUpdates; }; + void setFrequentUpdates( bool frequentUpdatesIn ) { mFrequentUpdates = frequentUpdatesIn; }; + + void setAlwaysRefresh(bool refresh) { mAlwaysRefresh = refresh; } + bool getAlwaysRefresh() { return mAlwaysRefresh; } + + void setForceUpdate(bool force_update) { mForceUpdate = force_update; } + bool getForceUpdate() { return mForceUpdate; } + + bool ensureMediaSourceExists(); + void unloadMediaSource(); + + LLPluginClassMedia* getMediaPlugin(); + + bool setCaretColor( unsigned int red, unsigned int green, unsigned int blue ); + + void setDecoupleTextureSize(bool decouple) { mDecoupleTextureSize = decouple; } + bool getDecoupleTextureSize() { return mDecoupleTextureSize; } + + void setTextureSize(S32 width, S32 height); + + void showNotification(LLNotificationPtr notify); + void hideNotification(); + + void setTrustedContent(bool trusted); + + void setAllowFileDownload(bool allow) { mAllowFileDownload = allow; } + + // over-rides + virtual bool handleKeyHere( KEY key, MASK mask); + virtual bool handleKeyUpHere(KEY key, MASK mask); + virtual void onVisibilityChange ( bool new_visibility ); + virtual bool handleUnicodeCharHere(llwchar uni_char); + virtual void reshape( S32 width, S32 height, bool called_from_parent = true); + virtual void draw(); + virtual bool postBuild(); + + // focus overrides + void onFocusLost(); + void onFocusReceived(); + + // Incoming media event dispatcher + virtual void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event); + + // right click debugging item + void onOpenWebInspector(); + + LLUUID getTextureID() {return mMediaTextureID;} + + // The Browser windows want keyup and keydown events. Overridden from LLFocusableElement to return true. + virtual bool wantsKeyUpKeyDown() const; + virtual bool wantsReturnKey() const; + + virtual bool acceptsTextInput() const { return true; } + + protected: + void convertInputCoords(S32& x, S32& y); + + private: + void calcOffsetsAndSize(S32 *x_offset, S32 *y_offset, S32 *width, S32 *height); + + private: + void onVisibilityChanged ( const LLSD& new_visibility ); + void onPopup(const LLSD& notification, const LLSD& response); + + const S32 mTextureDepthBytes; + LLUUID mMediaTextureID; + LLViewBorder* mBorder; + bool mFrequentUpdates, + mForceUpdate, + mTrusted, + mAlwaysRefresh, + mTakeFocusOnClick, + mStretchToFill, + mMaintainAspectRatio, + mHideLoading, + mHidingInitialLoad, + mClearCache, + mHoverTextChanged, + mDecoupleTextureSize, + mUpdateScrolls, + mAllowFileDownload; + + std::string mHomePageUrl, + mHomePageMimeType, + mCurrentNavUrl, + mErrorPageURL, + mTarget; + viewer_media_t mMediaSource; + S32 mTextureWidth, + mTextureHeight; + + class LLWindowShade* mWindowShade; + LLHandle mContextMenuHandle; +}; + +#endif // LL_LLMediaCtrl_H diff --git a/indra/newview/llmediadataclient.cpp b/indra/newview/llmediadataclient.cpp index f08d2dccb4..be8d543711 100644 --- a/indra/newview/llmediadataclient.cpp +++ b/indra/newview/llmediadataclient.cpp @@ -1,1127 +1,1127 @@ -/** - * @file llmediadataclient.cpp - * @brief class for queueing up requests for media data - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llmediadataclient.h" -#include "llviewercontrol.h" - -#if LL_MSVC -// disable boost::lexical_cast warning -#pragma warning (disable:4702) -#endif - -#include -#include - -#include "llhttpconstants.h" -#include "llsdutil.h" -#include "llmediaentry.h" -#include "lltextureentry.h" -#include "llviewerregion.h" -#include "llcorehttputil.h" - -// -// When making a request -// - obtain the "overall interest score" of the object. -// This would be the sum of the impls' interest scores. -// - put the request onto a queue sorted by this score -// (highest score at the front of the queue) -// - On a timer, once a second, pull off the head of the queue and send -// the request. -// - Any request that gets a 503 still goes through the retry logic -// - -/*************************************************************************************************************** - What's up with this queueing code? - - First, a bit of background: - - Media on a prim was added into the system in the Viewer 2.0 timeframe. In order to avoid changing the - network format of objects, an unused field in the object (the "MediaURL" string) was repurposed to - indicate that the object had media data, and also hold a sequence number and the UUID of the agent - who last updated the data. The actual media data for objects is accessed via the "ObjectMedia" capability. - Due to concerns about sim performance, requests to this capability are rate-limited to 5 requests every - 5 seconds per agent. - - The initial implementation of LLMediaDataClient used a single queue to manage requests to the "ObjectMedia" cap. - Requests to the cap were queued so that objects closer to the avatar were loaded in first, since they were most - likely to be the ones the media performance manager would load. - - This worked in some cases, but we found that it was possible for a scripted object that constantly updated its - media data to starve other objects, since the same queue contained both requests to load previously unseen media - data and requests to fetch media data in response to object updates. - - The solution for this we came up with was to have two queues. The sorted queue contains requests to fetch media - data for objects that don't have it yet, and the round-robin queue contains requests to update media data for - objects that have already completed their initial load. When both queues are non-empty, the code ping-pongs - between them so that updates can't completely block initial load-in. -**************************************************************************************************************/ - -// -// Forward decls -// -const F32 LLMediaDataClient::QUEUE_TIMER_DELAY = 1.0; // seconds(s) -const F32 LLMediaDataClient::UNAVAILABLE_RETRY_TIMER_DELAY = 5.0; // secs -const U32 LLMediaDataClient::MAX_RETRIES = 4; -const U32 LLMediaDataClient::MAX_SORTED_QUEUE_SIZE = 10000; -const U32 LLMediaDataClient::MAX_ROUND_ROBIN_QUEUE_SIZE = 10000; - -// << operators -std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::request_queue_t &q); -std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::Request &q); - - -//========================================================================= -/// Uniary Predicate for matching requests in collections by either the request -/// or by UUID -/// -class PredicateMatchRequest -{ -public: - PredicateMatchRequest(const LLMediaDataClient::Request::ptr_t &request, LLMediaDataClient::Request::Type matchType = LLMediaDataClient::Request::ANY); - PredicateMatchRequest(const LLUUID &id, LLMediaDataClient::Request::Type matchType = LLMediaDataClient::Request::ANY); - - PredicateMatchRequest(const PredicateMatchRequest &other); - - bool operator()(const LLMediaDataClient::Request::ptr_t &test) const; - -private: - LLMediaDataClient::Request::ptr_t mRequest; - LLMediaDataClient::Request::Type mMatchType; - LLUUID mId; -}; - - -PredicateMatchRequest::PredicateMatchRequest(const LLMediaDataClient::Request::ptr_t &request, LLMediaDataClient::Request::Type matchType) : - mRequest(request), - mMatchType(matchType), - mId() -{} - -PredicateMatchRequest::PredicateMatchRequest(const LLUUID &id, LLMediaDataClient::Request::Type matchType) : - mRequest(), - mMatchType(matchType), - mId(id) -{} - -PredicateMatchRequest::PredicateMatchRequest(const PredicateMatchRequest &other) -{ - mRequest = other.mRequest; - mMatchType = other.mMatchType; - mId = other.mId; -} - -bool PredicateMatchRequest::operator()(const LLMediaDataClient::Request::ptr_t &test) const -{ - if (mRequest) - return (mRequest->isMatch(test, mMatchType)); - else if (!mId.isNull()) - return ((test->getID() == mId) && ((mMatchType == LLMediaDataClient::Request::ANY) || (mMatchType == test->getType()))); - return false; -} - -//========================================================================= -/// -template -void mark_dead_and_remove_if(T &c, const PredicateMatchRequest &matchPred) -{ - for (typename T::iterator it = c.begin(); it != c.end();) - { - if (matchPred(*it)) - { - (*it)->markDead(); - it = c.erase(it); - } - else - { - ++it; - } - } -} - -////////////////////////////////////////////////////////////////////////////////////// -// -// LLMediaDataClient -// -////////////////////////////////////////////////////////////////////////////////////// - -LLMediaDataClient::LLMediaDataClient(F32 queue_timer_delay, F32 retry_timer_delay, - U32 max_retries, U32 max_sorted_queue_size, U32 max_round_robin_queue_size): - mQueueTimerDelay(queue_timer_delay), - mRetryTimerDelay(retry_timer_delay), - mMaxNumRetries(max_retries), - mMaxSortedQueueSize(max_sorted_queue_size), - mMaxRoundRobinQueueSize(max_round_robin_queue_size), - mQueueTimerIsRunning(false), - mHttpRequest(new LLCore::HttpRequest()), - mHttpHeaders(new LLCore::HttpHeaders()), - mHttpOpts(new LLCore::HttpOptions()), - mHttpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID) -{ - // *TODO: Look up real Policy ID -} - -LLMediaDataClient::~LLMediaDataClient() -{ - stopQueueTimer(); -} - -bool LLMediaDataClient::isEmpty() const -{ - return mQueue.empty(); -} - -bool LLMediaDataClient::isInQueue(const LLMediaDataClientObject::ptr_t &object) -{ - PredicateMatchRequest upred(object->getID()); - - if (std::find_if(mQueue.begin(), mQueue.end(), upred) != mQueue.end()) - return true; - if (std::find_if(mUnQueuedRequests.begin(), mUnQueuedRequests.end(), upred) != mUnQueuedRequests.end()) - return true; - - return false; -} - -void LLMediaDataClient::removeFromQueue(const LLMediaDataClientObject::ptr_t &object) -{ - LL_DEBUGS("LLMediaDataClient") << "removing requests matching ID " << object->getID() << LL_ENDL; - PredicateMatchRequest upred(object->getID()); - - mark_dead_and_remove_if(mQueue, upred); - mark_dead_and_remove_if(mUnQueuedRequests, upred); -} - -void LLMediaDataClient::startQueueTimer() -{ - if (! mQueueTimerIsRunning) - { - LL_DEBUGS("LLMediaDataClient") << "starting queue timer (delay=" << mQueueTimerDelay << " seconds)" << LL_ENDL; - // LLEventTimer automagically takes care of the lifetime of this object - new QueueTimer(mQueueTimerDelay, this); - } - else { - LL_DEBUGS("LLMediaDataClient") << "queue timer is already running" << LL_ENDL; - } -} - -void LLMediaDataClient::stopQueueTimer() -{ - mQueueTimerIsRunning = false; -} - -bool LLMediaDataClient::processQueueTimer() -{ - if (isDoneProcessing()) - return true; - - LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() started, queue size is: " << mQueue.size() << LL_ENDL; - LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() started, SORTED queue is: " << mQueue << LL_ENDL; - - serviceQueue(); - serviceHttp(); - - LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() finished, queue size is: " << mQueue.size() << LL_ENDL; - LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() finished, SORTED queue is: " << mQueue << LL_ENDL; - - return isDoneProcessing(); -} - -LLMediaDataClient::Request::ptr_t LLMediaDataClient::dequeue() -{ - Request::ptr_t request; - request_queue_t *queue_p = getQueue(); - - if (queue_p->empty()) - { - LL_DEBUGS("LLMediaDataClient") << "queue empty: " << (*queue_p) << LL_ENDL; - } - else - { - request = queue_p->front(); - - if(canServiceRequest(request)) - { - // We will be returning this request, so remove it from the queue. - queue_p->pop_front(); - } - else - { - // Don't return this request -- it's not ready to be serviced. - request.reset(); - } - } - - return request; -} - -void LLMediaDataClient::pushBack(Request::ptr_t request) -{ - request_queue_t *queue_p = getQueue(); - queue_p->push_front(request); -} - -void LLMediaDataClient::trackRequest(Request::ptr_t request) -{ - request_set_t::iterator iter = mUnQueuedRequests.find(request); - - if(iter != mUnQueuedRequests.end()) - { - LL_WARNS("LLMediaDataClient") << "Tracking already tracked request: " << *request << LL_ENDL; - } - else - { - mUnQueuedRequests.insert(request); - } -} - -void LLMediaDataClient::stopTrackingRequest(Request::ptr_t request) -{ - request_set_t::iterator iter = mUnQueuedRequests.find(request); - - if (iter != mUnQueuedRequests.end()) - { - mUnQueuedRequests.erase(iter); - } - else - { - LL_WARNS("LLMediaDataClient") << "Removing an untracked request: " << *request << LL_ENDL; - } -} - -bool LLMediaDataClient::isDoneProcessing() const -{ - return (isEmpty() && mUnQueuedRequests.empty()); -} - - -void LLMediaDataClient::serviceQueue() -{ - // Peel one off of the items from the queue and execute it - Request::ptr_t request; - - do - { - request = dequeue(); - - if(!request) - { - // Queue is empty. - return; - } - - if(request->isDead()) - { - LL_INFOS("LLMediaDataClient") << "Skipping dead request " << *request << LL_ENDL; - continue; - } - - } while(false); - - // try to send the HTTP message to the cap url - std::string url = request->getCapability(); - if (!url.empty()) - { - const LLSD &sd_payload = request->getPayload(); - LL_INFOS("LLMediaDataClient") << "Sending request for " << *request << LL_ENDL; - - // Add this request to the non-queued tracking list - trackRequest(request); - - // and make the post - LLCore::HttpHandler::ptr_t handler = request->createHandler(); - LLCore::HttpHandle handle = LLCoreHttpUtil::requestPostWithLLSD(mHttpRequest, mHttpPolicy, - url, sd_payload, mHttpOpts, mHttpHeaders, handler); - - if (handle == LLCORE_HTTP_HANDLE_INVALID) - { - LLCore::HttpStatus status = mHttpRequest->getStatus(); - LL_WARNS("LLMediaDataClient") << "'" << url << "' request POST failed. Reason " - << status.toTerseString() << " \"" << status.toString() << "\"" << LL_ENDL; - } - } - else - { - // Cap url doesn't exist. - - if(request->getRetryCount() < mMaxNumRetries) - { - LL_WARNS("LLMediaDataClient") << "Could not send request " << *request << " (empty cap url), will retry." << LL_ENDL; - // Put this request back at the head of its queue, and retry next time the queue timer fires. - request->incRetryCount(); - pushBack(request); - } - else - { - // This request has exceeded its maximum retry count. It will be dropped. - LL_WARNS("LLMediaDataClient") << "Could not send request " << *request << " for " << mMaxNumRetries << " tries, dropping request." << LL_ENDL; - } - - } -} - -void LLMediaDataClient::serviceHttp() -{ - mHttpRequest->update(0); -} - -// dump the queue -std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::request_queue_t &q) -{ - int i = 0; - LLMediaDataClient::request_queue_t::const_iterator iter = q.begin(); - LLMediaDataClient::request_queue_t::const_iterator end = q.end(); - while (iter != end) - { - s << "\t" << i << "]: " << (*iter)->getID().asString() << "(" << (*iter)->getObject()->getMediaInterest() << ")"; - iter++; - i++; - } - return s; -} - -////////////////////////////////////////////////////////////////////////////////////// -// -// LLMediaDataClient::QueueTimer -// Queue of LLMediaDataClientObject smart pointers to request media for. -// -////////////////////////////////////////////////////////////////////////////////////// - -LLMediaDataClient::QueueTimer::QueueTimer(F32 time, LLMediaDataClient *mdc) -: LLEventTimer(time), mMDC(mdc) -{ - mMDC->setIsRunning(true); -} - -// virtual -bool LLMediaDataClient::QueueTimer::tick() -{ - bool result = true; - - if (!mMDC.isNull()) - { - result = mMDC->processQueueTimer(); - - if(result) - { - // This timer won't fire again. - mMDC->setIsRunning(false); - mMDC = NULL; - } - } - - return result; -} - - -////////////////////////////////////////////////////////////////////////////////////// -// -// LLMediaDataClient::Responder::RetryTimer -// -////////////////////////////////////////////////////////////////////////////////////// - -LLMediaDataClient::RetryTimer::RetryTimer(F32 time, Request::ptr_t request) -: LLEventTimer(time), mRequest(request) -{ - mRequest->startTracking(); -} - -// virtual -bool LLMediaDataClient::RetryTimer::tick() -{ - mRequest->stopTracking(); - - if(mRequest->isDead()) - { - LL_INFOS("LLMediaDataClient") << "RetryTimer fired for dead request: " << *mRequest << ", aborting." << LL_ENDL; - } - else - { - LL_INFOS("LLMediaDataClient") << "RetryTimer fired for: " << *mRequest << ", retrying." << LL_ENDL; - mRequest->reEnqueue(); - } - - // Release the ref to the request. - mRequest.reset(); - - // Don't fire again - return true; -} - - -////////////////////////////////////////////////////////////////////////////////////// -// -// LLMediaDataClient::Request -// -////////////////////////////////////////////////////////////////////////////////////// -/*static*/U32 LLMediaDataClient::Request::sNum = 0; - -LLMediaDataClient::Request::Request(Type in_type, - LLMediaDataClientObject *obj, - LLMediaDataClient *mdc, - S32 face) -: mType(in_type), - mObject(obj), - mNum(++sNum), - mRetryCount(0), - mMDC(mdc), - mScore((F64)0.0), - mFace(face) -{ - mObjectID = mObject->getID(); -} - -const char *LLMediaDataClient::Request::getCapName() const -{ - if(mMDC) - return mMDC->getCapabilityName(); - - return ""; -} - -std::string LLMediaDataClient::Request::getCapability() const -{ - if(mMDC) - { - return getObject()->getCapabilityUrl(getCapName()); - } - - return ""; -} - -const char *LLMediaDataClient::Request::getTypeAsString() const -{ - Type t = getType(); - switch (t) - { - case GET: - return "GET"; - break; - case UPDATE: - return "UPDATE"; - break; - case NAVIGATE: - return "NAVIGATE"; - break; - case ANY: - return "ANY"; - break; - } - return ""; -} - - -void LLMediaDataClient::Request::reEnqueue() -{ - if(mMDC) - { - mMDC->enqueue(shared_from_this()); - } -} - -F32 LLMediaDataClient::Request::getRetryTimerDelay() const -{ - if(mMDC) - return mMDC->mRetryTimerDelay; - - return 0.0f; -} - -U32 LLMediaDataClient::Request::getMaxNumRetries() const -{ - if(mMDC) - return mMDC->mMaxNumRetries; - - return 0; -} - -void LLMediaDataClient::Request::updateScore() -{ - F64 tmp = mObject->getMediaInterest(); - if (tmp != mScore) - { - LL_DEBUGS("LLMediaDataClient") << "Score for " << mObject->getID() << " changed from " << mScore << " to " << tmp << LL_ENDL; - mScore = tmp; - } -} - -void LLMediaDataClient::Request::markDead() -{ - mMDC = NULL; -} - -bool LLMediaDataClient::Request::isDead() -{ - return ((mMDC == NULL) || mObject->isDead()); -} - -void LLMediaDataClient::Request::startTracking() -{ - if(mMDC) - mMDC->trackRequest(shared_from_this()); -} - -void LLMediaDataClient::Request::stopTracking() -{ - if(mMDC) - mMDC->stopTrackingRequest(shared_from_this()); -} - -std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::Request &r) -{ - s << "request: num=" << r.getNum() - << " type=" << r.getTypeAsString() - << " ID=" << r.getID() - << " face=" << r.getFace() - << " #retries=" << r.getRetryCount(); - return s; -} - -//======================================================================== - -LLMediaDataClient::Handler::Handler(const Request::ptr_t &request): - mRequest(request) -{ -} - - -void LLMediaDataClient::Handler::onSuccess(LLCore::HttpResponse * response, const LLSD &content) -{ - mRequest->stopTracking(); - - if (mRequest->isDead()) - { - LL_WARNS("LLMediaDataClient") << "dead request " << *mRequest << LL_ENDL; - return; - } - - LL_DEBUGS("LLMediaDataClientResponse") << *mRequest << LL_ENDL; -} - -void LLMediaDataClient::Handler::onFailure(LLCore::HttpResponse * response, LLCore::HttpStatus status) -{ - mRequest->stopTracking(); - - if (status == LLCore::HttpStatus(HTTP_SERVICE_UNAVAILABLE)) - { - F32 retry_timeout; - - retry_timeout = mRequest->getRetryTimerDelay(); - - mRequest->incRetryCount(); - - if (mRequest->getRetryCount() < mRequest->getMaxNumRetries()) - { - LL_INFOS("LLMediaDataClient") << *mRequest << " got SERVICE_UNAVAILABLE...retrying in " << retry_timeout << " seconds" << LL_ENDL; - - // Start timer (instances are automagically tracked by - // InstanceTracker<> and LLEventTimer) - new RetryTimer(F32(retry_timeout/*secs*/), mRequest); - } - else - { - LL_INFOS("LLMediaDataClient") << *mRequest << " got SERVICE_UNAVAILABLE...retry count " - << mRequest->getRetryCount() << " exceeds " << mRequest->getMaxNumRetries() << ", not retrying" << LL_ENDL; - } - } - else - { - LL_WARNS("LLMediaDataClient") << *mRequest << " HTTP failure " << LL_ENDL; - } -} - - -////////////////////////////////////////////////////////////////////////////////////// -// -// LLObjectMediaDataClient -// Subclass of LLMediaDataClient for the ObjectMedia cap -// -////////////////////////////////////////////////////////////////////////////////////// - -void LLObjectMediaDataClient::fetchMedia(LLMediaDataClientObject *object) -{ - // Create a get request and put it in the queue. - enqueue(Request::ptr_t(new RequestGet(object, this))); -} - -const char *LLObjectMediaDataClient::getCapabilityName() const -{ - return "ObjectMedia"; -} - -LLObjectMediaDataClient::request_queue_t *LLObjectMediaDataClient::getQueue() -{ - return (mCurrentQueueIsTheSortedQueue) ? &mQueue : &mRoundRobinQueue; -} - -void LLObjectMediaDataClient::sortQueue() -{ - if(!mQueue.empty()) - { - // score all elements in the sorted queue. - for(request_queue_t::iterator iter = mQueue.begin(); iter != mQueue.end(); iter++) - { - (*iter)->updateScore(); - } - - // Re-sort the list... - mQueue.sort(compareRequestScores); - - // ...then cull items over the max - U32 size = mQueue.size(); - if (size > mMaxSortedQueueSize) - { - U32 num_to_cull = (size - mMaxSortedQueueSize); - LL_INFOS_ONCE("LLMediaDataClient") << "sorted queue MAXED OUT! Culling " - << num_to_cull << " items" << LL_ENDL; - while (num_to_cull-- > 0) - { - mQueue.back()->markDead(); - mQueue.pop_back(); - } - } - } - -} - -// static -bool LLObjectMediaDataClient::compareRequestScores(const Request::ptr_t &o1, const Request::ptr_t &o2) -{ - if (!o2) return true; - if (!o1) return false; - return ( o1->getScore() > o2->getScore() ); -} - -void LLObjectMediaDataClient::enqueue(Request::ptr_t request) -{ - static LLCachedControl audio_streaming_enabled(gSavedSettings, "AudioStreamingMedia", true); - if (!audio_streaming_enabled) - { - LL_DEBUGS("LLMediaDataClient") << "not queueing request when Media is disabled " << *request << LL_ENDL; - return; - } - - if(request->isDead()) - { - LL_DEBUGS("LLMediaDataClient") << "not queueing dead request " << *request << LL_ENDL; - return; - } - - // Invariants: - // new requests always go into the sorted queue. - // - - bool is_new = request->isNew(); - - if(!is_new && (request->getType() == Request::GET)) - { - // For GET requests that are not new, if a matching request is already in the round robin queue, - // in flight, or being retried, leave it at its current position. - PredicateMatchRequest upred(request->getID(), Request::GET); - request_queue_t::iterator iter = std::find_if(mRoundRobinQueue.begin(), mRoundRobinQueue.end(), upred); - request_set_t::iterator iter2 = std::find_if(mUnQueuedRequests.begin(), mUnQueuedRequests.end(), upred); - - if( (iter != mRoundRobinQueue.end()) || (iter2 != mUnQueuedRequests.end()) ) - { - LL_DEBUGS("LLMediaDataClient") << "ALREADY THERE: NOT Queuing request for " << *request << LL_ENDL; - - return; - } - } - - // TODO: should an UPDATE cause pending GET requests for the same object to be removed from the queue? - // IF the update will cause an object update message to be sent out at some point in the future, it probably should. - - // Remove any existing requests of this type for this object - PredicateMatchRequest upred(request->getID(), request->getType()); - - mark_dead_and_remove_if(mQueue, upred); - mark_dead_and_remove_if(mRoundRobinQueue, upred); - mark_dead_and_remove_if(mUnQueuedRequests, upred); - - if (is_new) - { - LL_DEBUGS("LLMediaDataClient") << "Queuing SORTED request for " << *request << LL_ENDL; - - mQueue.push_back(request); - - LL_DEBUGS("LLMediaDataClientQueue") << "SORTED queue:" << mQueue << LL_ENDL; - } - else - { - if (mRoundRobinQueue.size() > mMaxRoundRobinQueueSize) - { - LL_INFOS_ONCE("LLMediaDataClient") << "RR QUEUE MAXED OUT!!!" << LL_ENDL; - LL_DEBUGS("LLMediaDataClient") << "Not queuing " << *request << LL_ENDL; - return; - } - - LL_DEBUGS("LLMediaDataClient") << "Queuing RR request for " << *request << LL_ENDL; - // Push the request on the pending queue - mRoundRobinQueue.push_back(request); - - LL_DEBUGS("LLMediaDataClientQueue") << "RR queue:" << mRoundRobinQueue << LL_ENDL; - } - // Start the timer if not already running - startQueueTimer(); -} - -bool LLObjectMediaDataClient::canServiceRequest(Request::ptr_t request) -{ - if(mCurrentQueueIsTheSortedQueue) - { - if(!request->getObject()->isInterestingEnough()) - { - LL_DEBUGS("LLMediaDataClient") << "Not fetching " << *request << ": not interesting enough" << LL_ENDL; - return false; - } - } - - return true; -}; - -void LLObjectMediaDataClient::swapCurrentQueue() -{ - // Swap - mCurrentQueueIsTheSortedQueue = !mCurrentQueueIsTheSortedQueue; - // If its empty, swap back - if (getQueue()->empty()) - { - mCurrentQueueIsTheSortedQueue = !mCurrentQueueIsTheSortedQueue; - } -} - -bool LLObjectMediaDataClient::isEmpty() const -{ - return mQueue.empty() && mRoundRobinQueue.empty(); -} - -bool LLObjectMediaDataClient::isInQueue(const LLMediaDataClientObject::ptr_t &object) -{ - // First, call parent impl. - if(LLMediaDataClient::isInQueue(object)) - return true; - - if (std::find_if(mRoundRobinQueue.begin(), mRoundRobinQueue.end(), PredicateMatchRequest(object->getID())) != mRoundRobinQueue.end()) - return true; - - return false; -} - -void LLObjectMediaDataClient::removeFromQueue(const LLMediaDataClientObject::ptr_t &object) -{ - // First, call parent impl. - LLMediaDataClient::removeFromQueue(object); - - mark_dead_and_remove_if(mRoundRobinQueue, PredicateMatchRequest(object->getID())); -} - -bool LLObjectMediaDataClient::processQueueTimer() -{ - if (isDoneProcessing()) - return true; - - LL_DEBUGS("LLMediaDataClient") << "started, SORTED queue size is: " << mQueue.size() - << ", RR queue size is: " << mRoundRobinQueue.size() << LL_ENDL; - LL_DEBUGS("LLMediaDataClientQueue") << " SORTED queue is: " << mQueue << LL_ENDL; - LL_DEBUGS("LLMediaDataClientQueue") << " RR queue is: " << mRoundRobinQueue << LL_ENDL; - -// purgeDeadRequests(); - - sortQueue(); - - LL_DEBUGS("LLMediaDataClientQueue") << "after sort, SORTED queue is: " << mQueue << LL_ENDL; - - serviceQueue(); - serviceHttp(); - - swapCurrentQueue(); - - LL_DEBUGS("LLMediaDataClient") << "finished, SORTED queue size is: " << mQueue.size() - << ", RR queue size is: " << mRoundRobinQueue.size() << LL_ENDL; - LL_DEBUGS("LLMediaDataClientQueue") << " SORTED queue is: " << mQueue << LL_ENDL; - LL_DEBUGS("LLMediaDataClientQueue") << " RR queue is: " << mRoundRobinQueue << LL_ENDL; - - return isDoneProcessing(); -} - -LLObjectMediaDataClient::RequestGet::RequestGet(LLMediaDataClientObject *obj, LLMediaDataClient *mdc): - LLMediaDataClient::Request(LLMediaDataClient::Request::GET, obj, mdc) -{ -} - -LLSD LLObjectMediaDataClient::RequestGet::getPayload() const -{ - LLSD result; - result["verb"] = "GET"; - result[LLTextureEntry::OBJECT_ID_KEY] = mObject->getID(); - - return result; -} - -LLCore::HttpHandler::ptr_t LLObjectMediaDataClient::RequestGet::createHandler() -{ - return LLCore::HttpHandler::ptr_t(new LLObjectMediaDataClient::Handler(shared_from_this())); -} - - -void LLObjectMediaDataClient::updateMedia(LLMediaDataClientObject *object) -{ - // Create an update request and put it in the queue. - enqueue(Request::ptr_t(new RequestUpdate(object, this))); -} - -LLObjectMediaDataClient::RequestUpdate::RequestUpdate(LLMediaDataClientObject *obj, LLMediaDataClient *mdc): - LLMediaDataClient::Request(LLMediaDataClient::Request::UPDATE, obj, mdc) -{ -} - -LLSD LLObjectMediaDataClient::RequestUpdate::getPayload() const -{ - LLSD result; - result["verb"] = "UPDATE"; - result[LLTextureEntry::OBJECT_ID_KEY] = mObject->getID(); - - LLSD object_media_data; - int i = 0; - int end = mObject->getMediaDataCount(); - for ( ; i < end ; ++i) - { - object_media_data.append(mObject->getMediaDataLLSD(i)); - } - - result[LLTextureEntry::OBJECT_MEDIA_DATA_KEY] = object_media_data; - - return result; -} - -LLCore::HttpHandler::ptr_t LLObjectMediaDataClient::RequestUpdate::createHandler() -{ - // This just uses the base class's responder. - return LLCore::HttpHandler::ptr_t(new LLMediaDataClient::Handler(shared_from_this())); -} - -void LLObjectMediaDataClient::Handler::onSuccess(LLCore::HttpResponse * response, const LLSD &content) -{ - LLMediaDataClient::Handler::onSuccess(response, content); - - if (getRequest()->isDead()) - { // warning emitted from base method. - return; - } - - if (!content.isMap()) - { - onFailure(response, LLCore::HttpStatus(HTTP_INTERNAL_ERROR, "Malformed response contents")); - return; - } - - // This responder is only used for GET requests, not UPDATE. - LL_DEBUGS("LLMediaDataClientResponse") << *(getRequest()) << " " << LL_ENDL; - - // Look for an error - if (content.has("error")) - { - const LLSD &error = content["error"]; - LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Error getting media data for object: code=" << - error["code"].asString() << ": " << error["message"].asString() << LL_ENDL; - - // XXX Warn user? - } - else - { - // Check the data - const LLUUID &object_id = content[LLTextureEntry::OBJECT_ID_KEY]; - if (object_id != getRequest()->getObject()->getID()) - { - // NOT good, wrong object id!! - LL_WARNS("LLMediaDataClient") << *(getRequest()) << " DROPPING response with wrong object id (" << object_id << ")" << LL_ENDL; - return; - } - - // Otherwise, update with object media data - getRequest()->getObject()->updateObjectMediaData(content[LLTextureEntry::OBJECT_MEDIA_DATA_KEY], - content[LLTextureEntry::MEDIA_VERSION_KEY]); - } - -} - - -////////////////////////////////////////////////////////////////////////////////////// -// -// LLObjectMediaNavigateClient -// Subclass of LLMediaDataClient for the ObjectMediaNavigate cap -// -////////////////////////////////////////////////////////////////////////////////////// - -const char *LLObjectMediaNavigateClient::getCapabilityName() const -{ - return "ObjectMediaNavigate"; -} - -void LLObjectMediaNavigateClient::enqueue(Request::ptr_t request) -{ - static LLCachedControl audio_streaming_enabled(gSavedSettings, "AudioStreamingMedia", true); - if (!audio_streaming_enabled) - { - LL_DEBUGS("LLMediaDataClient") << "not queueing request when Media is disabled " << *request << LL_ENDL; - return; - } - - if(request->isDead()) - { - LL_DEBUGS("LLMediaDataClient") << "not queuing dead request " << *request << LL_ENDL; - return; - } - - PredicateMatchRequest upred(request); - - // If there's already a matching request in the queue, remove it. - request_queue_t::iterator iter = std::find_if(mQueue.begin(), mQueue.end(), upred); - if(iter != mQueue.end()) - { - LL_DEBUGS("LLMediaDataClient") << "removing matching queued request " << (**iter) << LL_ENDL; - mQueue.erase(iter); - } - else - { - request_set_t::iterator set_iter = std::find_if(mUnQueuedRequests.begin(), mUnQueuedRequests.end(), upred); - if(set_iter != mUnQueuedRequests.end()) - { - LL_DEBUGS("LLMediaDataClient") << "removing matching unqueued request " << (**set_iter) << LL_ENDL; - mUnQueuedRequests.erase(set_iter); - } - } - -#if 0 - // Sadly, this doesn't work. It ends up creating a race condition when the user navigates and then hits the "back" button - // where the navigate-back appears to be spurious and doesn't get broadcast. - if(request->getObject()->isCurrentMediaUrl(request->getFace(), request->getURL())) - { - // This navigate request is trying to send the face to the current URL. Drop it. - LL_DEBUGS("LLMediaDataClient") << "dropping spurious request " << (*request) << LL_ENDL; - } - else -#endif - { - LL_DEBUGS("LLMediaDataClient") << "queuing new request " << (*request) << LL_ENDL; - mQueue.push_back(request); - - // Start the timer if not already running - startQueueTimer(); - } -} - -void LLObjectMediaNavigateClient::navigate(LLMediaDataClientObject *object, U8 texture_index, const std::string &url) -{ - -// LL_INFOS("LLMediaDataClient") << "navigate() initiated: " << ll_print_sd(sd_payload) << LL_ENDL; - - // Create a get request and put it in the queue. - enqueue(Request::ptr_t(new RequestNavigate(object, this, texture_index, url))); -} - -LLObjectMediaNavigateClient::RequestNavigate::RequestNavigate(LLMediaDataClientObject *obj, LLMediaDataClient *mdc, U8 texture_index, const std::string &url): - LLMediaDataClient::Request(LLMediaDataClient::Request::NAVIGATE, obj, mdc, (S32)texture_index), - mURL(url) -{ -} - -LLSD LLObjectMediaNavigateClient::RequestNavigate::getPayload() const -{ - LLSD result; - result[LLTextureEntry::OBJECT_ID_KEY] = getID(); - result[LLMediaEntry::CURRENT_URL_KEY] = mURL; - result[LLTextureEntry::TEXTURE_INDEX_KEY] = (LLSD::Integer)getFace(); - - return result; -} - -LLCore::HttpHandler::ptr_t LLObjectMediaNavigateClient::RequestNavigate::createHandler() -{ - return LLCore::HttpHandler::ptr_t(new LLObjectMediaNavigateClient::Handler(shared_from_this())); -} - -void LLObjectMediaNavigateClient::Handler::onSuccess(LLCore::HttpResponse * response, const LLSD &content) -{ - LLMediaDataClient::Handler::onSuccess(response, content); - - if (getRequest()->isDead()) - { // already warned. - return; - } - - LL_INFOS("LLMediaDataClient") << *(getRequest()) << " NAVIGATE returned" << LL_ENDL; - - if (content.has("error")) - { - const LLSD &error = content["error"]; - int error_code = error["code"]; - - if (ERROR_PERMISSION_DENIED_CODE == error_code) - { - mediaNavigateBounceBack(); - } - else - { - LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Error navigating: code=" << - error["code"].asString() << ": " << error["message"].asString() << LL_ENDL; - } - - // XXX Warn user? - } - else - { - // No action required. - LL_DEBUGS("LLMediaDataClientResponse") << *(getRequest()) << LL_ENDL; - } - -} - -void LLObjectMediaNavigateClient::Handler::onFailure(LLCore::HttpResponse * response, LLCore::HttpStatus status) -{ - LLMediaDataClient::Handler::onFailure(response, status); - - if (getRequest()->isDead()) - { // already warned. - return; - } - - if (status != LLCore::HttpStatus(HTTP_SERVICE_UNAVAILABLE)) - { - mediaNavigateBounceBack(); - } -} - -void LLObjectMediaNavigateClient::Handler::mediaNavigateBounceBack() -{ - LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Error navigating or denied." << LL_ENDL; - const LLSD &payload = getRequest()->getPayload(); - - // bounce the face back - getRequest()->getObject()->mediaNavigateBounceBack((LLSD::Integer)payload[LLTextureEntry::TEXTURE_INDEX_KEY]); -} +/** + * @file llmediadataclient.cpp + * @brief class for queueing up requests for media data + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llmediadataclient.h" +#include "llviewercontrol.h" + +#if LL_MSVC +// disable boost::lexical_cast warning +#pragma warning (disable:4702) +#endif + +#include +#include + +#include "llhttpconstants.h" +#include "llsdutil.h" +#include "llmediaentry.h" +#include "lltextureentry.h" +#include "llviewerregion.h" +#include "llcorehttputil.h" + +// +// When making a request +// - obtain the "overall interest score" of the object. +// This would be the sum of the impls' interest scores. +// - put the request onto a queue sorted by this score +// (highest score at the front of the queue) +// - On a timer, once a second, pull off the head of the queue and send +// the request. +// - Any request that gets a 503 still goes through the retry logic +// + +/*************************************************************************************************************** + What's up with this queueing code? + + First, a bit of background: + + Media on a prim was added into the system in the Viewer 2.0 timeframe. In order to avoid changing the + network format of objects, an unused field in the object (the "MediaURL" string) was repurposed to + indicate that the object had media data, and also hold a sequence number and the UUID of the agent + who last updated the data. The actual media data for objects is accessed via the "ObjectMedia" capability. + Due to concerns about sim performance, requests to this capability are rate-limited to 5 requests every + 5 seconds per agent. + + The initial implementation of LLMediaDataClient used a single queue to manage requests to the "ObjectMedia" cap. + Requests to the cap were queued so that objects closer to the avatar were loaded in first, since they were most + likely to be the ones the media performance manager would load. + + This worked in some cases, but we found that it was possible for a scripted object that constantly updated its + media data to starve other objects, since the same queue contained both requests to load previously unseen media + data and requests to fetch media data in response to object updates. + + The solution for this we came up with was to have two queues. The sorted queue contains requests to fetch media + data for objects that don't have it yet, and the round-robin queue contains requests to update media data for + objects that have already completed their initial load. When both queues are non-empty, the code ping-pongs + between them so that updates can't completely block initial load-in. +**************************************************************************************************************/ + +// +// Forward decls +// +const F32 LLMediaDataClient::QUEUE_TIMER_DELAY = 1.0; // seconds(s) +const F32 LLMediaDataClient::UNAVAILABLE_RETRY_TIMER_DELAY = 5.0; // secs +const U32 LLMediaDataClient::MAX_RETRIES = 4; +const U32 LLMediaDataClient::MAX_SORTED_QUEUE_SIZE = 10000; +const U32 LLMediaDataClient::MAX_ROUND_ROBIN_QUEUE_SIZE = 10000; + +// << operators +std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::request_queue_t &q); +std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::Request &q); + + +//========================================================================= +/// Uniary Predicate for matching requests in collections by either the request +/// or by UUID +/// +class PredicateMatchRequest +{ +public: + PredicateMatchRequest(const LLMediaDataClient::Request::ptr_t &request, LLMediaDataClient::Request::Type matchType = LLMediaDataClient::Request::ANY); + PredicateMatchRequest(const LLUUID &id, LLMediaDataClient::Request::Type matchType = LLMediaDataClient::Request::ANY); + + PredicateMatchRequest(const PredicateMatchRequest &other); + + bool operator()(const LLMediaDataClient::Request::ptr_t &test) const; + +private: + LLMediaDataClient::Request::ptr_t mRequest; + LLMediaDataClient::Request::Type mMatchType; + LLUUID mId; +}; + + +PredicateMatchRequest::PredicateMatchRequest(const LLMediaDataClient::Request::ptr_t &request, LLMediaDataClient::Request::Type matchType) : + mRequest(request), + mMatchType(matchType), + mId() +{} + +PredicateMatchRequest::PredicateMatchRequest(const LLUUID &id, LLMediaDataClient::Request::Type matchType) : + mRequest(), + mMatchType(matchType), + mId(id) +{} + +PredicateMatchRequest::PredicateMatchRequest(const PredicateMatchRequest &other) +{ + mRequest = other.mRequest; + mMatchType = other.mMatchType; + mId = other.mId; +} + +bool PredicateMatchRequest::operator()(const LLMediaDataClient::Request::ptr_t &test) const +{ + if (mRequest) + return (mRequest->isMatch(test, mMatchType)); + else if (!mId.isNull()) + return ((test->getID() == mId) && ((mMatchType == LLMediaDataClient::Request::ANY) || (mMatchType == test->getType()))); + return false; +} + +//========================================================================= +/// +template +void mark_dead_and_remove_if(T &c, const PredicateMatchRequest &matchPred) +{ + for (typename T::iterator it = c.begin(); it != c.end();) + { + if (matchPred(*it)) + { + (*it)->markDead(); + it = c.erase(it); + } + else + { + ++it; + } + } +} + +////////////////////////////////////////////////////////////////////////////////////// +// +// LLMediaDataClient +// +////////////////////////////////////////////////////////////////////////////////////// + +LLMediaDataClient::LLMediaDataClient(F32 queue_timer_delay, F32 retry_timer_delay, + U32 max_retries, U32 max_sorted_queue_size, U32 max_round_robin_queue_size): + mQueueTimerDelay(queue_timer_delay), + mRetryTimerDelay(retry_timer_delay), + mMaxNumRetries(max_retries), + mMaxSortedQueueSize(max_sorted_queue_size), + mMaxRoundRobinQueueSize(max_round_robin_queue_size), + mQueueTimerIsRunning(false), + mHttpRequest(new LLCore::HttpRequest()), + mHttpHeaders(new LLCore::HttpHeaders()), + mHttpOpts(new LLCore::HttpOptions()), + mHttpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID) +{ + // *TODO: Look up real Policy ID +} + +LLMediaDataClient::~LLMediaDataClient() +{ + stopQueueTimer(); +} + +bool LLMediaDataClient::isEmpty() const +{ + return mQueue.empty(); +} + +bool LLMediaDataClient::isInQueue(const LLMediaDataClientObject::ptr_t &object) +{ + PredicateMatchRequest upred(object->getID()); + + if (std::find_if(mQueue.begin(), mQueue.end(), upred) != mQueue.end()) + return true; + if (std::find_if(mUnQueuedRequests.begin(), mUnQueuedRequests.end(), upred) != mUnQueuedRequests.end()) + return true; + + return false; +} + +void LLMediaDataClient::removeFromQueue(const LLMediaDataClientObject::ptr_t &object) +{ + LL_DEBUGS("LLMediaDataClient") << "removing requests matching ID " << object->getID() << LL_ENDL; + PredicateMatchRequest upred(object->getID()); + + mark_dead_and_remove_if(mQueue, upred); + mark_dead_and_remove_if(mUnQueuedRequests, upred); +} + +void LLMediaDataClient::startQueueTimer() +{ + if (! mQueueTimerIsRunning) + { + LL_DEBUGS("LLMediaDataClient") << "starting queue timer (delay=" << mQueueTimerDelay << " seconds)" << LL_ENDL; + // LLEventTimer automagically takes care of the lifetime of this object + new QueueTimer(mQueueTimerDelay, this); + } + else { + LL_DEBUGS("LLMediaDataClient") << "queue timer is already running" << LL_ENDL; + } +} + +void LLMediaDataClient::stopQueueTimer() +{ + mQueueTimerIsRunning = false; +} + +bool LLMediaDataClient::processQueueTimer() +{ + if (isDoneProcessing()) + return true; + + LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() started, queue size is: " << mQueue.size() << LL_ENDL; + LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() started, SORTED queue is: " << mQueue << LL_ENDL; + + serviceQueue(); + serviceHttp(); + + LL_DEBUGS("LLMediaDataClient") << "QueueTimer::tick() finished, queue size is: " << mQueue.size() << LL_ENDL; + LL_DEBUGS("LLMediaDataClientQueue") << "QueueTimer::tick() finished, SORTED queue is: " << mQueue << LL_ENDL; + + return isDoneProcessing(); +} + +LLMediaDataClient::Request::ptr_t LLMediaDataClient::dequeue() +{ + Request::ptr_t request; + request_queue_t *queue_p = getQueue(); + + if (queue_p->empty()) + { + LL_DEBUGS("LLMediaDataClient") << "queue empty: " << (*queue_p) << LL_ENDL; + } + else + { + request = queue_p->front(); + + if(canServiceRequest(request)) + { + // We will be returning this request, so remove it from the queue. + queue_p->pop_front(); + } + else + { + // Don't return this request -- it's not ready to be serviced. + request.reset(); + } + } + + return request; +} + +void LLMediaDataClient::pushBack(Request::ptr_t request) +{ + request_queue_t *queue_p = getQueue(); + queue_p->push_front(request); +} + +void LLMediaDataClient::trackRequest(Request::ptr_t request) +{ + request_set_t::iterator iter = mUnQueuedRequests.find(request); + + if(iter != mUnQueuedRequests.end()) + { + LL_WARNS("LLMediaDataClient") << "Tracking already tracked request: " << *request << LL_ENDL; + } + else + { + mUnQueuedRequests.insert(request); + } +} + +void LLMediaDataClient::stopTrackingRequest(Request::ptr_t request) +{ + request_set_t::iterator iter = mUnQueuedRequests.find(request); + + if (iter != mUnQueuedRequests.end()) + { + mUnQueuedRequests.erase(iter); + } + else + { + LL_WARNS("LLMediaDataClient") << "Removing an untracked request: " << *request << LL_ENDL; + } +} + +bool LLMediaDataClient::isDoneProcessing() const +{ + return (isEmpty() && mUnQueuedRequests.empty()); +} + + +void LLMediaDataClient::serviceQueue() +{ + // Peel one off of the items from the queue and execute it + Request::ptr_t request; + + do + { + request = dequeue(); + + if(!request) + { + // Queue is empty. + return; + } + + if(request->isDead()) + { + LL_INFOS("LLMediaDataClient") << "Skipping dead request " << *request << LL_ENDL; + continue; + } + + } while(false); + + // try to send the HTTP message to the cap url + std::string url = request->getCapability(); + if (!url.empty()) + { + const LLSD &sd_payload = request->getPayload(); + LL_INFOS("LLMediaDataClient") << "Sending request for " << *request << LL_ENDL; + + // Add this request to the non-queued tracking list + trackRequest(request); + + // and make the post + LLCore::HttpHandler::ptr_t handler = request->createHandler(); + LLCore::HttpHandle handle = LLCoreHttpUtil::requestPostWithLLSD(mHttpRequest, mHttpPolicy, + url, sd_payload, mHttpOpts, mHttpHeaders, handler); + + if (handle == LLCORE_HTTP_HANDLE_INVALID) + { + LLCore::HttpStatus status = mHttpRequest->getStatus(); + LL_WARNS("LLMediaDataClient") << "'" << url << "' request POST failed. Reason " + << status.toTerseString() << " \"" << status.toString() << "\"" << LL_ENDL; + } + } + else + { + // Cap url doesn't exist. + + if(request->getRetryCount() < mMaxNumRetries) + { + LL_WARNS("LLMediaDataClient") << "Could not send request " << *request << " (empty cap url), will retry." << LL_ENDL; + // Put this request back at the head of its queue, and retry next time the queue timer fires. + request->incRetryCount(); + pushBack(request); + } + else + { + // This request has exceeded its maximum retry count. It will be dropped. + LL_WARNS("LLMediaDataClient") << "Could not send request " << *request << " for " << mMaxNumRetries << " tries, dropping request." << LL_ENDL; + } + + } +} + +void LLMediaDataClient::serviceHttp() +{ + mHttpRequest->update(0); +} + +// dump the queue +std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::request_queue_t &q) +{ + int i = 0; + LLMediaDataClient::request_queue_t::const_iterator iter = q.begin(); + LLMediaDataClient::request_queue_t::const_iterator end = q.end(); + while (iter != end) + { + s << "\t" << i << "]: " << (*iter)->getID().asString() << "(" << (*iter)->getObject()->getMediaInterest() << ")"; + iter++; + i++; + } + return s; +} + +////////////////////////////////////////////////////////////////////////////////////// +// +// LLMediaDataClient::QueueTimer +// Queue of LLMediaDataClientObject smart pointers to request media for. +// +////////////////////////////////////////////////////////////////////////////////////// + +LLMediaDataClient::QueueTimer::QueueTimer(F32 time, LLMediaDataClient *mdc) +: LLEventTimer(time), mMDC(mdc) +{ + mMDC->setIsRunning(true); +} + +// virtual +bool LLMediaDataClient::QueueTimer::tick() +{ + bool result = true; + + if (!mMDC.isNull()) + { + result = mMDC->processQueueTimer(); + + if(result) + { + // This timer won't fire again. + mMDC->setIsRunning(false); + mMDC = NULL; + } + } + + return result; +} + + +////////////////////////////////////////////////////////////////////////////////////// +// +// LLMediaDataClient::Responder::RetryTimer +// +////////////////////////////////////////////////////////////////////////////////////// + +LLMediaDataClient::RetryTimer::RetryTimer(F32 time, Request::ptr_t request) +: LLEventTimer(time), mRequest(request) +{ + mRequest->startTracking(); +} + +// virtual +bool LLMediaDataClient::RetryTimer::tick() +{ + mRequest->stopTracking(); + + if(mRequest->isDead()) + { + LL_INFOS("LLMediaDataClient") << "RetryTimer fired for dead request: " << *mRequest << ", aborting." << LL_ENDL; + } + else + { + LL_INFOS("LLMediaDataClient") << "RetryTimer fired for: " << *mRequest << ", retrying." << LL_ENDL; + mRequest->reEnqueue(); + } + + // Release the ref to the request. + mRequest.reset(); + + // Don't fire again + return true; +} + + +////////////////////////////////////////////////////////////////////////////////////// +// +// LLMediaDataClient::Request +// +////////////////////////////////////////////////////////////////////////////////////// +/*static*/U32 LLMediaDataClient::Request::sNum = 0; + +LLMediaDataClient::Request::Request(Type in_type, + LLMediaDataClientObject *obj, + LLMediaDataClient *mdc, + S32 face) +: mType(in_type), + mObject(obj), + mNum(++sNum), + mRetryCount(0), + mMDC(mdc), + mScore((F64)0.0), + mFace(face) +{ + mObjectID = mObject->getID(); +} + +const char *LLMediaDataClient::Request::getCapName() const +{ + if(mMDC) + return mMDC->getCapabilityName(); + + return ""; +} + +std::string LLMediaDataClient::Request::getCapability() const +{ + if(mMDC) + { + return getObject()->getCapabilityUrl(getCapName()); + } + + return ""; +} + +const char *LLMediaDataClient::Request::getTypeAsString() const +{ + Type t = getType(); + switch (t) + { + case GET: + return "GET"; + break; + case UPDATE: + return "UPDATE"; + break; + case NAVIGATE: + return "NAVIGATE"; + break; + case ANY: + return "ANY"; + break; + } + return ""; +} + + +void LLMediaDataClient::Request::reEnqueue() +{ + if(mMDC) + { + mMDC->enqueue(shared_from_this()); + } +} + +F32 LLMediaDataClient::Request::getRetryTimerDelay() const +{ + if(mMDC) + return mMDC->mRetryTimerDelay; + + return 0.0f; +} + +U32 LLMediaDataClient::Request::getMaxNumRetries() const +{ + if(mMDC) + return mMDC->mMaxNumRetries; + + return 0; +} + +void LLMediaDataClient::Request::updateScore() +{ + F64 tmp = mObject->getMediaInterest(); + if (tmp != mScore) + { + LL_DEBUGS("LLMediaDataClient") << "Score for " << mObject->getID() << " changed from " << mScore << " to " << tmp << LL_ENDL; + mScore = tmp; + } +} + +void LLMediaDataClient::Request::markDead() +{ + mMDC = NULL; +} + +bool LLMediaDataClient::Request::isDead() +{ + return ((mMDC == NULL) || mObject->isDead()); +} + +void LLMediaDataClient::Request::startTracking() +{ + if(mMDC) + mMDC->trackRequest(shared_from_this()); +} + +void LLMediaDataClient::Request::stopTracking() +{ + if(mMDC) + mMDC->stopTrackingRequest(shared_from_this()); +} + +std::ostream& operator<<(std::ostream &s, const LLMediaDataClient::Request &r) +{ + s << "request: num=" << r.getNum() + << " type=" << r.getTypeAsString() + << " ID=" << r.getID() + << " face=" << r.getFace() + << " #retries=" << r.getRetryCount(); + return s; +} + +//======================================================================== + +LLMediaDataClient::Handler::Handler(const Request::ptr_t &request): + mRequest(request) +{ +} + + +void LLMediaDataClient::Handler::onSuccess(LLCore::HttpResponse * response, const LLSD &content) +{ + mRequest->stopTracking(); + + if (mRequest->isDead()) + { + LL_WARNS("LLMediaDataClient") << "dead request " << *mRequest << LL_ENDL; + return; + } + + LL_DEBUGS("LLMediaDataClientResponse") << *mRequest << LL_ENDL; +} + +void LLMediaDataClient::Handler::onFailure(LLCore::HttpResponse * response, LLCore::HttpStatus status) +{ + mRequest->stopTracking(); + + if (status == LLCore::HttpStatus(HTTP_SERVICE_UNAVAILABLE)) + { + F32 retry_timeout; + + retry_timeout = mRequest->getRetryTimerDelay(); + + mRequest->incRetryCount(); + + if (mRequest->getRetryCount() < mRequest->getMaxNumRetries()) + { + LL_INFOS("LLMediaDataClient") << *mRequest << " got SERVICE_UNAVAILABLE...retrying in " << retry_timeout << " seconds" << LL_ENDL; + + // Start timer (instances are automagically tracked by + // InstanceTracker<> and LLEventTimer) + new RetryTimer(F32(retry_timeout/*secs*/), mRequest); + } + else + { + LL_INFOS("LLMediaDataClient") << *mRequest << " got SERVICE_UNAVAILABLE...retry count " + << mRequest->getRetryCount() << " exceeds " << mRequest->getMaxNumRetries() << ", not retrying" << LL_ENDL; + } + } + else + { + LL_WARNS("LLMediaDataClient") << *mRequest << " HTTP failure " << LL_ENDL; + } +} + + +////////////////////////////////////////////////////////////////////////////////////// +// +// LLObjectMediaDataClient +// Subclass of LLMediaDataClient for the ObjectMedia cap +// +////////////////////////////////////////////////////////////////////////////////////// + +void LLObjectMediaDataClient::fetchMedia(LLMediaDataClientObject *object) +{ + // Create a get request and put it in the queue. + enqueue(Request::ptr_t(new RequestGet(object, this))); +} + +const char *LLObjectMediaDataClient::getCapabilityName() const +{ + return "ObjectMedia"; +} + +LLObjectMediaDataClient::request_queue_t *LLObjectMediaDataClient::getQueue() +{ + return (mCurrentQueueIsTheSortedQueue) ? &mQueue : &mRoundRobinQueue; +} + +void LLObjectMediaDataClient::sortQueue() +{ + if(!mQueue.empty()) + { + // score all elements in the sorted queue. + for(request_queue_t::iterator iter = mQueue.begin(); iter != mQueue.end(); iter++) + { + (*iter)->updateScore(); + } + + // Re-sort the list... + mQueue.sort(compareRequestScores); + + // ...then cull items over the max + U32 size = mQueue.size(); + if (size > mMaxSortedQueueSize) + { + U32 num_to_cull = (size - mMaxSortedQueueSize); + LL_INFOS_ONCE("LLMediaDataClient") << "sorted queue MAXED OUT! Culling " + << num_to_cull << " items" << LL_ENDL; + while (num_to_cull-- > 0) + { + mQueue.back()->markDead(); + mQueue.pop_back(); + } + } + } + +} + +// static +bool LLObjectMediaDataClient::compareRequestScores(const Request::ptr_t &o1, const Request::ptr_t &o2) +{ + if (!o2) return true; + if (!o1) return false; + return ( o1->getScore() > o2->getScore() ); +} + +void LLObjectMediaDataClient::enqueue(Request::ptr_t request) +{ + static LLCachedControl audio_streaming_enabled(gSavedSettings, "AudioStreamingMedia", true); + if (!audio_streaming_enabled) + { + LL_DEBUGS("LLMediaDataClient") << "not queueing request when Media is disabled " << *request << LL_ENDL; + return; + } + + if(request->isDead()) + { + LL_DEBUGS("LLMediaDataClient") << "not queueing dead request " << *request << LL_ENDL; + return; + } + + // Invariants: + // new requests always go into the sorted queue. + // + + bool is_new = request->isNew(); + + if(!is_new && (request->getType() == Request::GET)) + { + // For GET requests that are not new, if a matching request is already in the round robin queue, + // in flight, or being retried, leave it at its current position. + PredicateMatchRequest upred(request->getID(), Request::GET); + request_queue_t::iterator iter = std::find_if(mRoundRobinQueue.begin(), mRoundRobinQueue.end(), upred); + request_set_t::iterator iter2 = std::find_if(mUnQueuedRequests.begin(), mUnQueuedRequests.end(), upred); + + if( (iter != mRoundRobinQueue.end()) || (iter2 != mUnQueuedRequests.end()) ) + { + LL_DEBUGS("LLMediaDataClient") << "ALREADY THERE: NOT Queuing request for " << *request << LL_ENDL; + + return; + } + } + + // TODO: should an UPDATE cause pending GET requests for the same object to be removed from the queue? + // IF the update will cause an object update message to be sent out at some point in the future, it probably should. + + // Remove any existing requests of this type for this object + PredicateMatchRequest upred(request->getID(), request->getType()); + + mark_dead_and_remove_if(mQueue, upred); + mark_dead_and_remove_if(mRoundRobinQueue, upred); + mark_dead_and_remove_if(mUnQueuedRequests, upred); + + if (is_new) + { + LL_DEBUGS("LLMediaDataClient") << "Queuing SORTED request for " << *request << LL_ENDL; + + mQueue.push_back(request); + + LL_DEBUGS("LLMediaDataClientQueue") << "SORTED queue:" << mQueue << LL_ENDL; + } + else + { + if (mRoundRobinQueue.size() > mMaxRoundRobinQueueSize) + { + LL_INFOS_ONCE("LLMediaDataClient") << "RR QUEUE MAXED OUT!!!" << LL_ENDL; + LL_DEBUGS("LLMediaDataClient") << "Not queuing " << *request << LL_ENDL; + return; + } + + LL_DEBUGS("LLMediaDataClient") << "Queuing RR request for " << *request << LL_ENDL; + // Push the request on the pending queue + mRoundRobinQueue.push_back(request); + + LL_DEBUGS("LLMediaDataClientQueue") << "RR queue:" << mRoundRobinQueue << LL_ENDL; + } + // Start the timer if not already running + startQueueTimer(); +} + +bool LLObjectMediaDataClient::canServiceRequest(Request::ptr_t request) +{ + if(mCurrentQueueIsTheSortedQueue) + { + if(!request->getObject()->isInterestingEnough()) + { + LL_DEBUGS("LLMediaDataClient") << "Not fetching " << *request << ": not interesting enough" << LL_ENDL; + return false; + } + } + + return true; +}; + +void LLObjectMediaDataClient::swapCurrentQueue() +{ + // Swap + mCurrentQueueIsTheSortedQueue = !mCurrentQueueIsTheSortedQueue; + // If its empty, swap back + if (getQueue()->empty()) + { + mCurrentQueueIsTheSortedQueue = !mCurrentQueueIsTheSortedQueue; + } +} + +bool LLObjectMediaDataClient::isEmpty() const +{ + return mQueue.empty() && mRoundRobinQueue.empty(); +} + +bool LLObjectMediaDataClient::isInQueue(const LLMediaDataClientObject::ptr_t &object) +{ + // First, call parent impl. + if(LLMediaDataClient::isInQueue(object)) + return true; + + if (std::find_if(mRoundRobinQueue.begin(), mRoundRobinQueue.end(), PredicateMatchRequest(object->getID())) != mRoundRobinQueue.end()) + return true; + + return false; +} + +void LLObjectMediaDataClient::removeFromQueue(const LLMediaDataClientObject::ptr_t &object) +{ + // First, call parent impl. + LLMediaDataClient::removeFromQueue(object); + + mark_dead_and_remove_if(mRoundRobinQueue, PredicateMatchRequest(object->getID())); +} + +bool LLObjectMediaDataClient::processQueueTimer() +{ + if (isDoneProcessing()) + return true; + + LL_DEBUGS("LLMediaDataClient") << "started, SORTED queue size is: " << mQueue.size() + << ", RR queue size is: " << mRoundRobinQueue.size() << LL_ENDL; + LL_DEBUGS("LLMediaDataClientQueue") << " SORTED queue is: " << mQueue << LL_ENDL; + LL_DEBUGS("LLMediaDataClientQueue") << " RR queue is: " << mRoundRobinQueue << LL_ENDL; + +// purgeDeadRequests(); + + sortQueue(); + + LL_DEBUGS("LLMediaDataClientQueue") << "after sort, SORTED queue is: " << mQueue << LL_ENDL; + + serviceQueue(); + serviceHttp(); + + swapCurrentQueue(); + + LL_DEBUGS("LLMediaDataClient") << "finished, SORTED queue size is: " << mQueue.size() + << ", RR queue size is: " << mRoundRobinQueue.size() << LL_ENDL; + LL_DEBUGS("LLMediaDataClientQueue") << " SORTED queue is: " << mQueue << LL_ENDL; + LL_DEBUGS("LLMediaDataClientQueue") << " RR queue is: " << mRoundRobinQueue << LL_ENDL; + + return isDoneProcessing(); +} + +LLObjectMediaDataClient::RequestGet::RequestGet(LLMediaDataClientObject *obj, LLMediaDataClient *mdc): + LLMediaDataClient::Request(LLMediaDataClient::Request::GET, obj, mdc) +{ +} + +LLSD LLObjectMediaDataClient::RequestGet::getPayload() const +{ + LLSD result; + result["verb"] = "GET"; + result[LLTextureEntry::OBJECT_ID_KEY] = mObject->getID(); + + return result; +} + +LLCore::HttpHandler::ptr_t LLObjectMediaDataClient::RequestGet::createHandler() +{ + return LLCore::HttpHandler::ptr_t(new LLObjectMediaDataClient::Handler(shared_from_this())); +} + + +void LLObjectMediaDataClient::updateMedia(LLMediaDataClientObject *object) +{ + // Create an update request and put it in the queue. + enqueue(Request::ptr_t(new RequestUpdate(object, this))); +} + +LLObjectMediaDataClient::RequestUpdate::RequestUpdate(LLMediaDataClientObject *obj, LLMediaDataClient *mdc): + LLMediaDataClient::Request(LLMediaDataClient::Request::UPDATE, obj, mdc) +{ +} + +LLSD LLObjectMediaDataClient::RequestUpdate::getPayload() const +{ + LLSD result; + result["verb"] = "UPDATE"; + result[LLTextureEntry::OBJECT_ID_KEY] = mObject->getID(); + + LLSD object_media_data; + int i = 0; + int end = mObject->getMediaDataCount(); + for ( ; i < end ; ++i) + { + object_media_data.append(mObject->getMediaDataLLSD(i)); + } + + result[LLTextureEntry::OBJECT_MEDIA_DATA_KEY] = object_media_data; + + return result; +} + +LLCore::HttpHandler::ptr_t LLObjectMediaDataClient::RequestUpdate::createHandler() +{ + // This just uses the base class's responder. + return LLCore::HttpHandler::ptr_t(new LLMediaDataClient::Handler(shared_from_this())); +} + +void LLObjectMediaDataClient::Handler::onSuccess(LLCore::HttpResponse * response, const LLSD &content) +{ + LLMediaDataClient::Handler::onSuccess(response, content); + + if (getRequest()->isDead()) + { // warning emitted from base method. + return; + } + + if (!content.isMap()) + { + onFailure(response, LLCore::HttpStatus(HTTP_INTERNAL_ERROR, "Malformed response contents")); + return; + } + + // This responder is only used for GET requests, not UPDATE. + LL_DEBUGS("LLMediaDataClientResponse") << *(getRequest()) << " " << LL_ENDL; + + // Look for an error + if (content.has("error")) + { + const LLSD &error = content["error"]; + LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Error getting media data for object: code=" << + error["code"].asString() << ": " << error["message"].asString() << LL_ENDL; + + // XXX Warn user? + } + else + { + // Check the data + const LLUUID &object_id = content[LLTextureEntry::OBJECT_ID_KEY]; + if (object_id != getRequest()->getObject()->getID()) + { + // NOT good, wrong object id!! + LL_WARNS("LLMediaDataClient") << *(getRequest()) << " DROPPING response with wrong object id (" << object_id << ")" << LL_ENDL; + return; + } + + // Otherwise, update with object media data + getRequest()->getObject()->updateObjectMediaData(content[LLTextureEntry::OBJECT_MEDIA_DATA_KEY], + content[LLTextureEntry::MEDIA_VERSION_KEY]); + } + +} + + +////////////////////////////////////////////////////////////////////////////////////// +// +// LLObjectMediaNavigateClient +// Subclass of LLMediaDataClient for the ObjectMediaNavigate cap +// +////////////////////////////////////////////////////////////////////////////////////// + +const char *LLObjectMediaNavigateClient::getCapabilityName() const +{ + return "ObjectMediaNavigate"; +} + +void LLObjectMediaNavigateClient::enqueue(Request::ptr_t request) +{ + static LLCachedControl audio_streaming_enabled(gSavedSettings, "AudioStreamingMedia", true); + if (!audio_streaming_enabled) + { + LL_DEBUGS("LLMediaDataClient") << "not queueing request when Media is disabled " << *request << LL_ENDL; + return; + } + + if(request->isDead()) + { + LL_DEBUGS("LLMediaDataClient") << "not queuing dead request " << *request << LL_ENDL; + return; + } + + PredicateMatchRequest upred(request); + + // If there's already a matching request in the queue, remove it. + request_queue_t::iterator iter = std::find_if(mQueue.begin(), mQueue.end(), upred); + if(iter != mQueue.end()) + { + LL_DEBUGS("LLMediaDataClient") << "removing matching queued request " << (**iter) << LL_ENDL; + mQueue.erase(iter); + } + else + { + request_set_t::iterator set_iter = std::find_if(mUnQueuedRequests.begin(), mUnQueuedRequests.end(), upred); + if(set_iter != mUnQueuedRequests.end()) + { + LL_DEBUGS("LLMediaDataClient") << "removing matching unqueued request " << (**set_iter) << LL_ENDL; + mUnQueuedRequests.erase(set_iter); + } + } + +#if 0 + // Sadly, this doesn't work. It ends up creating a race condition when the user navigates and then hits the "back" button + // where the navigate-back appears to be spurious and doesn't get broadcast. + if(request->getObject()->isCurrentMediaUrl(request->getFace(), request->getURL())) + { + // This navigate request is trying to send the face to the current URL. Drop it. + LL_DEBUGS("LLMediaDataClient") << "dropping spurious request " << (*request) << LL_ENDL; + } + else +#endif + { + LL_DEBUGS("LLMediaDataClient") << "queuing new request " << (*request) << LL_ENDL; + mQueue.push_back(request); + + // Start the timer if not already running + startQueueTimer(); + } +} + +void LLObjectMediaNavigateClient::navigate(LLMediaDataClientObject *object, U8 texture_index, const std::string &url) +{ + +// LL_INFOS("LLMediaDataClient") << "navigate() initiated: " << ll_print_sd(sd_payload) << LL_ENDL; + + // Create a get request and put it in the queue. + enqueue(Request::ptr_t(new RequestNavigate(object, this, texture_index, url))); +} + +LLObjectMediaNavigateClient::RequestNavigate::RequestNavigate(LLMediaDataClientObject *obj, LLMediaDataClient *mdc, U8 texture_index, const std::string &url): + LLMediaDataClient::Request(LLMediaDataClient::Request::NAVIGATE, obj, mdc, (S32)texture_index), + mURL(url) +{ +} + +LLSD LLObjectMediaNavigateClient::RequestNavigate::getPayload() const +{ + LLSD result; + result[LLTextureEntry::OBJECT_ID_KEY] = getID(); + result[LLMediaEntry::CURRENT_URL_KEY] = mURL; + result[LLTextureEntry::TEXTURE_INDEX_KEY] = (LLSD::Integer)getFace(); + + return result; +} + +LLCore::HttpHandler::ptr_t LLObjectMediaNavigateClient::RequestNavigate::createHandler() +{ + return LLCore::HttpHandler::ptr_t(new LLObjectMediaNavigateClient::Handler(shared_from_this())); +} + +void LLObjectMediaNavigateClient::Handler::onSuccess(LLCore::HttpResponse * response, const LLSD &content) +{ + LLMediaDataClient::Handler::onSuccess(response, content); + + if (getRequest()->isDead()) + { // already warned. + return; + } + + LL_INFOS("LLMediaDataClient") << *(getRequest()) << " NAVIGATE returned" << LL_ENDL; + + if (content.has("error")) + { + const LLSD &error = content["error"]; + int error_code = error["code"]; + + if (ERROR_PERMISSION_DENIED_CODE == error_code) + { + mediaNavigateBounceBack(); + } + else + { + LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Error navigating: code=" << + error["code"].asString() << ": " << error["message"].asString() << LL_ENDL; + } + + // XXX Warn user? + } + else + { + // No action required. + LL_DEBUGS("LLMediaDataClientResponse") << *(getRequest()) << LL_ENDL; + } + +} + +void LLObjectMediaNavigateClient::Handler::onFailure(LLCore::HttpResponse * response, LLCore::HttpStatus status) +{ + LLMediaDataClient::Handler::onFailure(response, status); + + if (getRequest()->isDead()) + { // already warned. + return; + } + + if (status != LLCore::HttpStatus(HTTP_SERVICE_UNAVAILABLE)) + { + mediaNavigateBounceBack(); + } +} + +void LLObjectMediaNavigateClient::Handler::mediaNavigateBounceBack() +{ + LL_WARNS("LLMediaDataClient") << *(getRequest()) << " Error navigating or denied." << LL_ENDL; + const LLSD &payload = getRequest()->getPayload(); + + // bounce the face back + getRequest()->getObject()->mediaNavigateBounceBack((LLSD::Integer)payload[LLTextureEntry::TEXTURE_INDEX_KEY]); +} diff --git a/indra/newview/llmediadataclient.h b/indra/newview/llmediadataclient.h index c996af70e5..ca035e79e0 100644 --- a/indra/newview/llmediadataclient.h +++ b/indra/newview/llmediadataclient.h @@ -1,441 +1,441 @@ -/** - * @file llmediadataclient.h - * @brief class for queueing up requests to the media service - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLMEDIADATACLIENT_H -#define LL_LLMEDIADATACLIENT_H - -#include -#include "llrefcount.h" -#include "llpointer.h" -#include "lleventtimer.h" -#include "llhttpsdhandler.h" -#include "httpcommon.h" -#include "httprequest.h" -#include "httpoptions.h" -#include "httpheaders.h" - -// Link seam for LLVOVolume -class LLMediaDataClientObject : public LLRefCount -{ -public: - // Get the number of media data items - virtual U8 getMediaDataCount() const = 0; - // Get the media data at index, as an LLSD - virtual LLSD getMediaDataLLSD(U8 index) const = 0; - // Return true if the current URL for the face in the media data matches the specified URL. - virtual bool isCurrentMediaUrl(U8 index, const std::string &url) const = 0; - // Get this object's UUID - virtual LLUUID getID() const = 0; - // Navigate back to previous URL - virtual void mediaNavigateBounceBack(U8 index) = 0; - // Does this object have media? - virtual bool hasMedia() const = 0; - // Update the object's media data to the given array - virtual void updateObjectMediaData(LLSD const &media_data_array, const std::string &version_string) = 0; - // Return the total "interest" of the media (on-screen area) - virtual F64 getMediaInterest() const = 0; - // Return the given cap url - virtual std::string getCapabilityUrl(const std::string &name) const = 0; - // Return whether the object has been marked dead - virtual bool isDead() const = 0; - // Returns a media version number for the object - virtual U32 getMediaVersion() const = 0; - // Returns whether the object is "interesting enough" to fetch - virtual bool isInterestingEnough() const = 0; - // Returns whether we've seen this object yet or not - virtual bool isNew() const = 0; - - // smart pointer - typedef LLPointer ptr_t; -}; - - -// This object creates a priority queue for requests. -// Abstracts the Cap URL, the request, and the responder -class LLMediaDataClient : public LLRefCount -{ - friend class PredicateMatchRequest; - -protected: - LOG_CLASS(LLMediaDataClient); -public: - - const static F32 QUEUE_TIMER_DELAY;// = 1.0; // seconds(s) - const static F32 UNAVAILABLE_RETRY_TIMER_DELAY;// = 5.0; // secs - const static U32 MAX_RETRIES;// = 4; - const static U32 MAX_SORTED_QUEUE_SIZE;// = 10000; - const static U32 MAX_ROUND_ROBIN_QUEUE_SIZE;// = 10000; - - // Constructor - LLMediaDataClient(F32 queue_timer_delay = QUEUE_TIMER_DELAY, - F32 retry_timer_delay = UNAVAILABLE_RETRY_TIMER_DELAY, - U32 max_retries = MAX_RETRIES, - U32 max_sorted_queue_size = MAX_SORTED_QUEUE_SIZE, - U32 max_round_robin_queue_size = MAX_ROUND_ROBIN_QUEUE_SIZE); - - F32 getRetryTimerDelay() const { return mRetryTimerDelay; } - - // Returns true iff the queue is empty - virtual bool isEmpty() const; - - // Returns true iff the given object is in the queue - virtual bool isInQueue(const LLMediaDataClientObject::ptr_t &object); - - // Remove the given object from the queue. Returns true iff the given object is removed. - virtual void removeFromQueue(const LLMediaDataClientObject::ptr_t &object); - - // Called only by the Queue timer and tests (potentially) - virtual bool processQueueTimer(); - -protected: - // Destructor - virtual ~LLMediaDataClient(); // use unref - - // Request (pure virtual base class for requests in the queue) - class Request: - public std::enable_shared_from_this - { - public: - typedef std::shared_ptr ptr_t; - - // Subclasses must implement this to build a payload for their request type. - virtual LLSD getPayload() const = 0; - // and must create the correct type of responder. - virtual LLCore::HttpHandler::ptr_t createHandler() = 0; - - virtual std::string getURL() { return ""; } - - enum Type { - GET, - UPDATE, - NAVIGATE, - ANY - }; - - virtual ~Request() - { } - - protected: - // The only way to create one of these is through a subclass. - Request(Type in_type, LLMediaDataClientObject *obj, LLMediaDataClient *mdc, S32 face = -1); - public: - LLMediaDataClientObject *getObject() const { return mObject; } - - U32 getNum() const { return mNum; } - U32 getRetryCount() const { return mRetryCount; } - void incRetryCount() { mRetryCount++; } - Type getType() const { return mType; } - F64 getScore() const { return mScore; } - - // Note: may return empty string! - std::string getCapability() const; - const char *getCapName() const; - const char *getTypeAsString() const; - - // Re-enqueue thyself - void reEnqueue(); - - F32 getRetryTimerDelay() const; - U32 getMaxNumRetries() const; - - bool isObjectValid() const { return mObject.notNull() && (!mObject->isDead()); } - bool isNew() const { return isObjectValid() && mObject->isNew(); } - void updateScore(); - - void markDead(); - bool isDead(); - void startTracking(); - void stopTracking(); - - friend std::ostream& operator<<(std::ostream &s, const Request &q); - - const LLUUID &getID() const { return mObjectID; } - S32 getFace() const { return mFace; } - - bool isMatch (const Request::ptr_t &other, Type match_type = ANY) const - { - return ((match_type == ANY) || (mType == other->mType)) && - (mFace == other->mFace) && - (mObjectID == other->mObjectID); - } - protected: - LLMediaDataClientObject::ptr_t mObject; - private: - Type mType; - // Simple tracking - U32 mNum; - static U32 sNum; - U32 mRetryCount; - F64 mScore; - - LLUUID mObjectID; - S32 mFace; - - // Back pointer to the MDC...not a ref! - LLMediaDataClient *mMDC; - }; - //typedef LLPointer request_ptr_t; - - class Handler : public LLHttpSDHandler - { - LOG_CLASS(Handler); - public: - Handler(const Request::ptr_t &request); - Request::ptr_t getRequest() const { return mRequest; } - - protected: - virtual void onSuccess(LLCore::HttpResponse * response, const LLSD &content); - virtual void onFailure(LLCore::HttpResponse * response, LLCore::HttpStatus status); - - private: - Request::ptr_t mRequest; - }; - - - class RetryTimer : public LLEventTimer - { - public: - RetryTimer(F32 time, Request::ptr_t); - virtual bool tick(); - private: - // back-pointer - Request::ptr_t mRequest; - }; - - -protected: - typedef std::list request_queue_t; - typedef std::set request_set_t; - - // Subclasses must override to return a cap name - virtual const char *getCapabilityName() const = 0; - - // Puts the request into a queue, appropriately handling duplicates, etc. - virtual void enqueue(Request::ptr_t) = 0; - - virtual void serviceQueue(); - virtual void serviceHttp(); - - virtual request_queue_t *getQueue() { return &mQueue; }; - - // Gets the next request, removing it from the queue - virtual Request::ptr_t dequeue(); - - virtual bool canServiceRequest(Request::ptr_t request) { return true; }; - - // Returns a request to the head of the queue (should only be used for requests that came from dequeue - virtual void pushBack(Request::ptr_t request); - - void trackRequest(Request::ptr_t request); - void stopTrackingRequest(Request::ptr_t request); - - bool isDoneProcessing() const; - - request_queue_t mQueue; - - const F32 mQueueTimerDelay; - const F32 mRetryTimerDelay; - const U32 mMaxNumRetries; - const U32 mMaxSortedQueueSize; - const U32 mMaxRoundRobinQueueSize; - - // Set for keeping track of requests that aren't in either queue. This includes: - // Requests that have been sent and are awaiting a response (pointer held by the Responder) - // Requests that are waiting for their retry timers to fire (pointer held by the retry timer) - request_set_t mUnQueuedRequests; - - void startQueueTimer(); - void stopQueueTimer(); - - LLCore::HttpRequest::ptr_t mHttpRequest; - LLCore::HttpHeaders::ptr_t mHttpHeaders; - LLCore::HttpOptions::ptr_t mHttpOpts; - LLCore::HttpRequest::policy_t mHttpPolicy; - -private: - - static F64 getObjectScore(const LLMediaDataClientObject::ptr_t &obj); - - friend std::ostream& operator<<(std::ostream &s, const Request &q); - friend std::ostream& operator<<(std::ostream &s, const request_queue_t &q); - - class QueueTimer : public LLEventTimer - { - public: - QueueTimer(F32 time, LLMediaDataClient *mdc); - virtual bool tick(); - private: - // back-pointer - LLPointer mMDC; - }; - - void setIsRunning(bool val) { mQueueTimerIsRunning = val; } - - bool mQueueTimerIsRunning; - -// template friend typename T::iterator find_matching_request(T &c, const LLMediaDataClient::Request *request, LLMediaDataClient::Request::Type match_type); -// template friend typename T::iterator find_matching_request(T &c, const LLUUID &id, LLMediaDataClient::Request::Type match_type); -// template friend void remove_matching_requests(T &c, const LLUUID &id, LLMediaDataClient::Request::Type match_type); -}; - -// MediaDataClient specific for the ObjectMedia cap -class LLObjectMediaDataClient : public LLMediaDataClient -{ -protected: - LOG_CLASS(LLObjectMediaDataClient); -public: - LLObjectMediaDataClient(F32 queue_timer_delay = QUEUE_TIMER_DELAY, - F32 retry_timer_delay = UNAVAILABLE_RETRY_TIMER_DELAY, - U32 max_retries = MAX_RETRIES, - U32 max_sorted_queue_size = MAX_SORTED_QUEUE_SIZE, - U32 max_round_robin_queue_size = MAX_ROUND_ROBIN_QUEUE_SIZE) - : LLMediaDataClient(queue_timer_delay, retry_timer_delay, max_retries), - mCurrentQueueIsTheSortedQueue(true) - {} - - void fetchMedia(LLMediaDataClientObject *object); - void updateMedia(LLMediaDataClientObject *object); - - class RequestGet: public Request - { - public: - RequestGet(LLMediaDataClientObject *obj, LLMediaDataClient *mdc); - /*virtual*/ LLSD getPayload() const; - /*virtual*/ LLCore::HttpHandler::ptr_t createHandler(); - }; - - class RequestUpdate: public Request - { - public: - RequestUpdate(LLMediaDataClientObject *obj, LLMediaDataClient *mdc); - /*virtual*/ LLSD getPayload() const; - /*virtual*/ LLCore::HttpHandler::ptr_t createHandler(); - }; - - // Returns true iff the queue is empty - virtual bool isEmpty() const; - - // Returns true iff the given object is in the queue - virtual bool isInQueue(const LLMediaDataClientObject::ptr_t &object); - - // Remove the given object from the queue. Returns true iff the given object is removed. - virtual void removeFromQueue(const LLMediaDataClientObject::ptr_t &object); - - virtual bool processQueueTimer(); - - virtual bool canServiceRequest(Request::ptr_t request); - -protected: - // Subclasses must override to return a cap name - virtual const char *getCapabilityName() const; - - virtual request_queue_t *getQueue(); - - // Puts the request into the appropriate queue - virtual void enqueue(Request::ptr_t); - - class Handler: public LLMediaDataClient::Handler - { - LOG_CLASS(Handler); - public: - Handler(const Request::ptr_t &request): - LLMediaDataClient::Handler(request) - {} - - protected: - virtual void onSuccess(LLCore::HttpResponse * response, const LLSD &content); - }; - -private: - // The Get/Update data client needs a second queue to avoid object updates starving load-ins. - void swapCurrentQueue(); - - request_queue_t mRoundRobinQueue; - bool mCurrentQueueIsTheSortedQueue; - - // Comparator for sorting - static bool compareRequestScores(const Request::ptr_t &o1, const Request::ptr_t &o2); - void sortQueue(); -}; - - -// MediaDataClient specific for the ObjectMediaNavigate cap -class LLObjectMediaNavigateClient : public LLMediaDataClient -{ -protected: - LOG_CLASS(LLObjectMediaNavigateClient); -public: - // NOTE: from llmediaservice.h - static const int ERROR_PERMISSION_DENIED_CODE = 8002; - - LLObjectMediaNavigateClient(F32 queue_timer_delay = QUEUE_TIMER_DELAY, - F32 retry_timer_delay = UNAVAILABLE_RETRY_TIMER_DELAY, - U32 max_retries = MAX_RETRIES, - U32 max_sorted_queue_size = MAX_SORTED_QUEUE_SIZE, - U32 max_round_robin_queue_size = MAX_ROUND_ROBIN_QUEUE_SIZE) - : LLMediaDataClient(queue_timer_delay, retry_timer_delay, max_retries) - {} - - void navigate(LLMediaDataClientObject *object, U8 texture_index, const std::string &url); - - // Puts the request into the appropriate queue - virtual void enqueue(Request::ptr_t); - - class RequestNavigate: public Request - { - public: - RequestNavigate(LLMediaDataClientObject *obj, LLMediaDataClient *mdc, U8 texture_index, const std::string &url); - /*virtual*/ LLSD getPayload() const; - /*virtual*/ LLCore::HttpHandler::ptr_t createHandler(); - /*virtual*/ std::string getURL() { return mURL; } - private: - std::string mURL; - }; - -protected: - // Subclasses must override to return a cap name - virtual const char *getCapabilityName() const; - - class Handler : public LLMediaDataClient::Handler - { - LOG_CLASS(Handler); - public: - Handler(const Request::ptr_t &request): - LLMediaDataClient::Handler(request) - {} - - protected: - virtual void onSuccess(LLCore::HttpResponse * response, const LLSD &content); - virtual void onFailure(LLCore::HttpResponse * response, LLCore::HttpStatus status); - - private: - void mediaNavigateBounceBack(); - }; - -}; - - -#endif // LL_LLMEDIADATACLIENT_H +/** + * @file llmediadataclient.h + * @brief class for queueing up requests to the media service + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLMEDIADATACLIENT_H +#define LL_LLMEDIADATACLIENT_H + +#include +#include "llrefcount.h" +#include "llpointer.h" +#include "lleventtimer.h" +#include "llhttpsdhandler.h" +#include "httpcommon.h" +#include "httprequest.h" +#include "httpoptions.h" +#include "httpheaders.h" + +// Link seam for LLVOVolume +class LLMediaDataClientObject : public LLRefCount +{ +public: + // Get the number of media data items + virtual U8 getMediaDataCount() const = 0; + // Get the media data at index, as an LLSD + virtual LLSD getMediaDataLLSD(U8 index) const = 0; + // Return true if the current URL for the face in the media data matches the specified URL. + virtual bool isCurrentMediaUrl(U8 index, const std::string &url) const = 0; + // Get this object's UUID + virtual LLUUID getID() const = 0; + // Navigate back to previous URL + virtual void mediaNavigateBounceBack(U8 index) = 0; + // Does this object have media? + virtual bool hasMedia() const = 0; + // Update the object's media data to the given array + virtual void updateObjectMediaData(LLSD const &media_data_array, const std::string &version_string) = 0; + // Return the total "interest" of the media (on-screen area) + virtual F64 getMediaInterest() const = 0; + // Return the given cap url + virtual std::string getCapabilityUrl(const std::string &name) const = 0; + // Return whether the object has been marked dead + virtual bool isDead() const = 0; + // Returns a media version number for the object + virtual U32 getMediaVersion() const = 0; + // Returns whether the object is "interesting enough" to fetch + virtual bool isInterestingEnough() const = 0; + // Returns whether we've seen this object yet or not + virtual bool isNew() const = 0; + + // smart pointer + typedef LLPointer ptr_t; +}; + + +// This object creates a priority queue for requests. +// Abstracts the Cap URL, the request, and the responder +class LLMediaDataClient : public LLRefCount +{ + friend class PredicateMatchRequest; + +protected: + LOG_CLASS(LLMediaDataClient); +public: + + const static F32 QUEUE_TIMER_DELAY;// = 1.0; // seconds(s) + const static F32 UNAVAILABLE_RETRY_TIMER_DELAY;// = 5.0; // secs + const static U32 MAX_RETRIES;// = 4; + const static U32 MAX_SORTED_QUEUE_SIZE;// = 10000; + const static U32 MAX_ROUND_ROBIN_QUEUE_SIZE;// = 10000; + + // Constructor + LLMediaDataClient(F32 queue_timer_delay = QUEUE_TIMER_DELAY, + F32 retry_timer_delay = UNAVAILABLE_RETRY_TIMER_DELAY, + U32 max_retries = MAX_RETRIES, + U32 max_sorted_queue_size = MAX_SORTED_QUEUE_SIZE, + U32 max_round_robin_queue_size = MAX_ROUND_ROBIN_QUEUE_SIZE); + + F32 getRetryTimerDelay() const { return mRetryTimerDelay; } + + // Returns true iff the queue is empty + virtual bool isEmpty() const; + + // Returns true iff the given object is in the queue + virtual bool isInQueue(const LLMediaDataClientObject::ptr_t &object); + + // Remove the given object from the queue. Returns true iff the given object is removed. + virtual void removeFromQueue(const LLMediaDataClientObject::ptr_t &object); + + // Called only by the Queue timer and tests (potentially) + virtual bool processQueueTimer(); + +protected: + // Destructor + virtual ~LLMediaDataClient(); // use unref + + // Request (pure virtual base class for requests in the queue) + class Request: + public std::enable_shared_from_this + { + public: + typedef std::shared_ptr ptr_t; + + // Subclasses must implement this to build a payload for their request type. + virtual LLSD getPayload() const = 0; + // and must create the correct type of responder. + virtual LLCore::HttpHandler::ptr_t createHandler() = 0; + + virtual std::string getURL() { return ""; } + + enum Type { + GET, + UPDATE, + NAVIGATE, + ANY + }; + + virtual ~Request() + { } + + protected: + // The only way to create one of these is through a subclass. + Request(Type in_type, LLMediaDataClientObject *obj, LLMediaDataClient *mdc, S32 face = -1); + public: + LLMediaDataClientObject *getObject() const { return mObject; } + + U32 getNum() const { return mNum; } + U32 getRetryCount() const { return mRetryCount; } + void incRetryCount() { mRetryCount++; } + Type getType() const { return mType; } + F64 getScore() const { return mScore; } + + // Note: may return empty string! + std::string getCapability() const; + const char *getCapName() const; + const char *getTypeAsString() const; + + // Re-enqueue thyself + void reEnqueue(); + + F32 getRetryTimerDelay() const; + U32 getMaxNumRetries() const; + + bool isObjectValid() const { return mObject.notNull() && (!mObject->isDead()); } + bool isNew() const { return isObjectValid() && mObject->isNew(); } + void updateScore(); + + void markDead(); + bool isDead(); + void startTracking(); + void stopTracking(); + + friend std::ostream& operator<<(std::ostream &s, const Request &q); + + const LLUUID &getID() const { return mObjectID; } + S32 getFace() const { return mFace; } + + bool isMatch (const Request::ptr_t &other, Type match_type = ANY) const + { + return ((match_type == ANY) || (mType == other->mType)) && + (mFace == other->mFace) && + (mObjectID == other->mObjectID); + } + protected: + LLMediaDataClientObject::ptr_t mObject; + private: + Type mType; + // Simple tracking + U32 mNum; + static U32 sNum; + U32 mRetryCount; + F64 mScore; + + LLUUID mObjectID; + S32 mFace; + + // Back pointer to the MDC...not a ref! + LLMediaDataClient *mMDC; + }; + //typedef LLPointer request_ptr_t; + + class Handler : public LLHttpSDHandler + { + LOG_CLASS(Handler); + public: + Handler(const Request::ptr_t &request); + Request::ptr_t getRequest() const { return mRequest; } + + protected: + virtual void onSuccess(LLCore::HttpResponse * response, const LLSD &content); + virtual void onFailure(LLCore::HttpResponse * response, LLCore::HttpStatus status); + + private: + Request::ptr_t mRequest; + }; + + + class RetryTimer : public LLEventTimer + { + public: + RetryTimer(F32 time, Request::ptr_t); + virtual bool tick(); + private: + // back-pointer + Request::ptr_t mRequest; + }; + + +protected: + typedef std::list request_queue_t; + typedef std::set request_set_t; + + // Subclasses must override to return a cap name + virtual const char *getCapabilityName() const = 0; + + // Puts the request into a queue, appropriately handling duplicates, etc. + virtual void enqueue(Request::ptr_t) = 0; + + virtual void serviceQueue(); + virtual void serviceHttp(); + + virtual request_queue_t *getQueue() { return &mQueue; }; + + // Gets the next request, removing it from the queue + virtual Request::ptr_t dequeue(); + + virtual bool canServiceRequest(Request::ptr_t request) { return true; }; + + // Returns a request to the head of the queue (should only be used for requests that came from dequeue + virtual void pushBack(Request::ptr_t request); + + void trackRequest(Request::ptr_t request); + void stopTrackingRequest(Request::ptr_t request); + + bool isDoneProcessing() const; + + request_queue_t mQueue; + + const F32 mQueueTimerDelay; + const F32 mRetryTimerDelay; + const U32 mMaxNumRetries; + const U32 mMaxSortedQueueSize; + const U32 mMaxRoundRobinQueueSize; + + // Set for keeping track of requests that aren't in either queue. This includes: + // Requests that have been sent and are awaiting a response (pointer held by the Responder) + // Requests that are waiting for their retry timers to fire (pointer held by the retry timer) + request_set_t mUnQueuedRequests; + + void startQueueTimer(); + void stopQueueTimer(); + + LLCore::HttpRequest::ptr_t mHttpRequest; + LLCore::HttpHeaders::ptr_t mHttpHeaders; + LLCore::HttpOptions::ptr_t mHttpOpts; + LLCore::HttpRequest::policy_t mHttpPolicy; + +private: + + static F64 getObjectScore(const LLMediaDataClientObject::ptr_t &obj); + + friend std::ostream& operator<<(std::ostream &s, const Request &q); + friend std::ostream& operator<<(std::ostream &s, const request_queue_t &q); + + class QueueTimer : public LLEventTimer + { + public: + QueueTimer(F32 time, LLMediaDataClient *mdc); + virtual bool tick(); + private: + // back-pointer + LLPointer mMDC; + }; + + void setIsRunning(bool val) { mQueueTimerIsRunning = val; } + + bool mQueueTimerIsRunning; + +// template friend typename T::iterator find_matching_request(T &c, const LLMediaDataClient::Request *request, LLMediaDataClient::Request::Type match_type); +// template friend typename T::iterator find_matching_request(T &c, const LLUUID &id, LLMediaDataClient::Request::Type match_type); +// template friend void remove_matching_requests(T &c, const LLUUID &id, LLMediaDataClient::Request::Type match_type); +}; + +// MediaDataClient specific for the ObjectMedia cap +class LLObjectMediaDataClient : public LLMediaDataClient +{ +protected: + LOG_CLASS(LLObjectMediaDataClient); +public: + LLObjectMediaDataClient(F32 queue_timer_delay = QUEUE_TIMER_DELAY, + F32 retry_timer_delay = UNAVAILABLE_RETRY_TIMER_DELAY, + U32 max_retries = MAX_RETRIES, + U32 max_sorted_queue_size = MAX_SORTED_QUEUE_SIZE, + U32 max_round_robin_queue_size = MAX_ROUND_ROBIN_QUEUE_SIZE) + : LLMediaDataClient(queue_timer_delay, retry_timer_delay, max_retries), + mCurrentQueueIsTheSortedQueue(true) + {} + + void fetchMedia(LLMediaDataClientObject *object); + void updateMedia(LLMediaDataClientObject *object); + + class RequestGet: public Request + { + public: + RequestGet(LLMediaDataClientObject *obj, LLMediaDataClient *mdc); + /*virtual*/ LLSD getPayload() const; + /*virtual*/ LLCore::HttpHandler::ptr_t createHandler(); + }; + + class RequestUpdate: public Request + { + public: + RequestUpdate(LLMediaDataClientObject *obj, LLMediaDataClient *mdc); + /*virtual*/ LLSD getPayload() const; + /*virtual*/ LLCore::HttpHandler::ptr_t createHandler(); + }; + + // Returns true iff the queue is empty + virtual bool isEmpty() const; + + // Returns true iff the given object is in the queue + virtual bool isInQueue(const LLMediaDataClientObject::ptr_t &object); + + // Remove the given object from the queue. Returns true iff the given object is removed. + virtual void removeFromQueue(const LLMediaDataClientObject::ptr_t &object); + + virtual bool processQueueTimer(); + + virtual bool canServiceRequest(Request::ptr_t request); + +protected: + // Subclasses must override to return a cap name + virtual const char *getCapabilityName() const; + + virtual request_queue_t *getQueue(); + + // Puts the request into the appropriate queue + virtual void enqueue(Request::ptr_t); + + class Handler: public LLMediaDataClient::Handler + { + LOG_CLASS(Handler); + public: + Handler(const Request::ptr_t &request): + LLMediaDataClient::Handler(request) + {} + + protected: + virtual void onSuccess(LLCore::HttpResponse * response, const LLSD &content); + }; + +private: + // The Get/Update data client needs a second queue to avoid object updates starving load-ins. + void swapCurrentQueue(); + + request_queue_t mRoundRobinQueue; + bool mCurrentQueueIsTheSortedQueue; + + // Comparator for sorting + static bool compareRequestScores(const Request::ptr_t &o1, const Request::ptr_t &o2); + void sortQueue(); +}; + + +// MediaDataClient specific for the ObjectMediaNavigate cap +class LLObjectMediaNavigateClient : public LLMediaDataClient +{ +protected: + LOG_CLASS(LLObjectMediaNavigateClient); +public: + // NOTE: from llmediaservice.h + static const int ERROR_PERMISSION_DENIED_CODE = 8002; + + LLObjectMediaNavigateClient(F32 queue_timer_delay = QUEUE_TIMER_DELAY, + F32 retry_timer_delay = UNAVAILABLE_RETRY_TIMER_DELAY, + U32 max_retries = MAX_RETRIES, + U32 max_sorted_queue_size = MAX_SORTED_QUEUE_SIZE, + U32 max_round_robin_queue_size = MAX_ROUND_ROBIN_QUEUE_SIZE) + : LLMediaDataClient(queue_timer_delay, retry_timer_delay, max_retries) + {} + + void navigate(LLMediaDataClientObject *object, U8 texture_index, const std::string &url); + + // Puts the request into the appropriate queue + virtual void enqueue(Request::ptr_t); + + class RequestNavigate: public Request + { + public: + RequestNavigate(LLMediaDataClientObject *obj, LLMediaDataClient *mdc, U8 texture_index, const std::string &url); + /*virtual*/ LLSD getPayload() const; + /*virtual*/ LLCore::HttpHandler::ptr_t createHandler(); + /*virtual*/ std::string getURL() { return mURL; } + private: + std::string mURL; + }; + +protected: + // Subclasses must override to return a cap name + virtual const char *getCapabilityName() const; + + class Handler : public LLMediaDataClient::Handler + { + LOG_CLASS(Handler); + public: + Handler(const Request::ptr_t &request): + LLMediaDataClient::Handler(request) + {} + + protected: + virtual void onSuccess(LLCore::HttpResponse * response, const LLSD &content); + virtual void onFailure(LLCore::HttpResponse * response, LLCore::HttpStatus status); + + private: + void mediaNavigateBounceBack(); + }; + +}; + + +#endif // LL_LLMEDIADATACLIENT_H diff --git a/indra/newview/llmenuoptionpathfindingrebakenavmesh.cpp b/indra/newview/llmenuoptionpathfindingrebakenavmesh.cpp index 662a8b4feb..54c1b0610e 100644 --- a/indra/newview/llmenuoptionpathfindingrebakenavmesh.cpp +++ b/indra/newview/llmenuoptionpathfindingrebakenavmesh.cpp @@ -1,243 +1,243 @@ -/** -* @file llmenuoptionpathfindingrebakenavmesh.cpp -* @brief Implementation of llmenuoptionpathfindingrebakenavmesh -* @author Prep@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - - -#include "llviewerprecompiledheaders.h" - -#include "llmenuoptionpathfindingrebakenavmesh.h" - -#include -#include - -#include "llagent.h" -#include "llnotificationsutil.h" -#include "llpathfindingmanager.h" -#include "llpathfindingnavmesh.h" -#include "llpathfindingnavmeshstatus.h" -#include "llviewerregion.h" - -LLMenuOptionPathfindingRebakeNavmesh::LLMenuOptionPathfindingRebakeNavmesh() : - mIsInitialized(false), - mCanRebakeRegion(false), - mRebakeNavMeshMode(kRebakeNavMesh_Default), - mNavMeshSlot(), - mRegionCrossingSlot(), - mAgentStateSlot() -{ -} - -LLMenuOptionPathfindingRebakeNavmesh::~LLMenuOptionPathfindingRebakeNavmesh() -{ - if (mIsInitialized) - { - if (mRebakeNavMeshMode == kRebakeNavMesh_RequestSent) - { - LL_WARNS("navmeshRebaking") << "During destruction of the LLMenuOptionPathfindingRebakeNavmesh " - << "singleton, the mode indicates that a request has been sent for which a response has yet " - << "to be received. This could contribute to a crash on exit." << LL_ENDL; - } - - quit(); - } -} - -void LLMenuOptionPathfindingRebakeNavmesh::initialize() -{ - if (!mIsInitialized) - { - mIsInitialized = true; - - setMode(kRebakeNavMesh_Default); - - createNavMeshStatusListenerForCurrentRegion(); - - if ( !mRegionCrossingSlot.connected() ) - { - mRegionCrossingSlot = gAgent.addRegionChangedCallback(boost::bind(&LLMenuOptionPathfindingRebakeNavmesh::handleRegionBoundaryCrossed, this)); - } - - if (!mAgentStateSlot.connected()) - { - mAgentStateSlot = LLPathfindingManager::getInstance()->registerAgentStateListener(boost::bind(&LLMenuOptionPathfindingRebakeNavmesh::handleAgentState, this, _1)); - } - LLPathfindingManager::getInstance()->requestGetAgentState(); - } -} - -void LLMenuOptionPathfindingRebakeNavmesh::quit() -{ - if (mIsInitialized) // Quitting from the login screen leaves this uninitialized - { - if (mNavMeshSlot.connected()) - { - mNavMeshSlot.disconnect(); - } - - if (mRegionCrossingSlot.connected()) - { - mRegionCrossingSlot.disconnect(); - } - - if (mAgentStateSlot.connected()) - { - mAgentStateSlot.disconnect(); - } - - mIsInitialized = false; - } -} - -bool LLMenuOptionPathfindingRebakeNavmesh::canRebakeRegion() const -{ - if (!mIsInitialized) - { - LL_ERRS("navmeshRebaking") << "LLMenuOptionPathfindingRebakeNavmesh class has not been initialized " - << "when the ability to rebake navmesh is being requested." << LL_ENDL; - } - return mCanRebakeRegion; -} - -LLMenuOptionPathfindingRebakeNavmesh::ERebakeNavMeshMode LLMenuOptionPathfindingRebakeNavmesh::getMode() const -{ - if (!mIsInitialized) - { - LL_ERRS("navmeshRebaking") << "LLMenuOptionPathfindingRebakeNavmesh class has not been initialized " - << "when the mode is being requested." << LL_ENDL; - } - return mRebakeNavMeshMode; -} - -void LLMenuOptionPathfindingRebakeNavmesh::sendRequestRebakeNavmesh() -{ - if (!mIsInitialized) - { - LL_ERRS("navmeshRebaking") << "LLMenuOptionPathfindingRebakeNavmesh class has not been initialized " - << "when the request is being made to rebake the navmesh." << LL_ENDL; - } - else - { - if (!canRebakeRegion()) - { - LL_WARNS("navmeshRebaking") << "attempting to rebake navmesh when user does not have permissions " - << "on this region" << LL_ENDL; - } - if (getMode() != kRebakeNavMesh_Available) - { - LL_WARNS("navmeshRebaking") << "attempting to rebake navmesh when mode is not available" - << LL_ENDL; - } - - setMode(kRebakeNavMesh_RequestSent); - LLPathfindingManager::getInstance()->requestRebakeNavMesh(boost::bind(&LLMenuOptionPathfindingRebakeNavmesh::handleRebakeNavMeshResponse, this, _1)); - } -} - -void LLMenuOptionPathfindingRebakeNavmesh::setMode(ERebakeNavMeshMode pRebakeNavMeshMode) -{ - mRebakeNavMeshMode = pRebakeNavMeshMode; -} - -void LLMenuOptionPathfindingRebakeNavmesh::handleAgentState(bool pCanRebakeRegion) -{ - llassert(mIsInitialized); - mCanRebakeRegion = pCanRebakeRegion; -} - -void LLMenuOptionPathfindingRebakeNavmesh::handleRebakeNavMeshResponse(bool pResponseStatus) -{ - llassert(mIsInitialized); - if (mIsInitialized) - { - if (getMode() == kRebakeNavMesh_RequestSent) - { - setMode(pResponseStatus ? kRebakeNavMesh_InProgress : kRebakeNavMesh_Default); - } - - if (!pResponseStatus) - { - LLNotificationsUtil::add("PathfindingCannotRebakeNavmesh"); - } - } -} - -void LLMenuOptionPathfindingRebakeNavmesh::handleNavMeshStatus(const LLPathfindingNavMeshStatus &pNavMeshStatus) -{ - llassert(mIsInitialized); - if (mIsInitialized) - { - ERebakeNavMeshMode rebakeNavMeshMode = kRebakeNavMesh_Default; - if (pNavMeshStatus.isValid()) - { - switch (pNavMeshStatus.getStatus()) - { - case LLPathfindingNavMeshStatus::kPending : - case LLPathfindingNavMeshStatus::kRepending : - rebakeNavMeshMode = kRebakeNavMesh_Available; - break; - case LLPathfindingNavMeshStatus::kBuilding : - rebakeNavMeshMode = kRebakeNavMesh_InProgress; - break; - case LLPathfindingNavMeshStatus::kComplete : - rebakeNavMeshMode = kRebakeNavMesh_NotAvailable; - break; - default : - rebakeNavMeshMode = kRebakeNavMesh_Default; - llassert(0); - break; - } - } - - setMode(rebakeNavMeshMode); - } -} - -void LLMenuOptionPathfindingRebakeNavmesh::handleRegionBoundaryCrossed() -{ - llassert(mIsInitialized); - if (mIsInitialized) - { - createNavMeshStatusListenerForCurrentRegion(); - mCanRebakeRegion = false; - LLPathfindingManager::getInstance()->requestGetAgentState(); - } -} - -void LLMenuOptionPathfindingRebakeNavmesh::createNavMeshStatusListenerForCurrentRegion() -{ - if (mNavMeshSlot.connected()) - { - mNavMeshSlot.disconnect(); - } - - LLViewerRegion *currentRegion = gAgent.getRegion(); - if (currentRegion != NULL) - { - mNavMeshSlot = LLPathfindingManager::getInstance()->registerNavMeshListenerForRegion(currentRegion, boost::bind(&LLMenuOptionPathfindingRebakeNavmesh::handleNavMeshStatus, this, _2)); - LLPathfindingManager::getInstance()->requestGetNavMeshForRegion(currentRegion, true); - } -} - +/** +* @file llmenuoptionpathfindingrebakenavmesh.cpp +* @brief Implementation of llmenuoptionpathfindingrebakenavmesh +* @author Prep@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + + +#include "llviewerprecompiledheaders.h" + +#include "llmenuoptionpathfindingrebakenavmesh.h" + +#include +#include + +#include "llagent.h" +#include "llnotificationsutil.h" +#include "llpathfindingmanager.h" +#include "llpathfindingnavmesh.h" +#include "llpathfindingnavmeshstatus.h" +#include "llviewerregion.h" + +LLMenuOptionPathfindingRebakeNavmesh::LLMenuOptionPathfindingRebakeNavmesh() : + mIsInitialized(false), + mCanRebakeRegion(false), + mRebakeNavMeshMode(kRebakeNavMesh_Default), + mNavMeshSlot(), + mRegionCrossingSlot(), + mAgentStateSlot() +{ +} + +LLMenuOptionPathfindingRebakeNavmesh::~LLMenuOptionPathfindingRebakeNavmesh() +{ + if (mIsInitialized) + { + if (mRebakeNavMeshMode == kRebakeNavMesh_RequestSent) + { + LL_WARNS("navmeshRebaking") << "During destruction of the LLMenuOptionPathfindingRebakeNavmesh " + << "singleton, the mode indicates that a request has been sent for which a response has yet " + << "to be received. This could contribute to a crash on exit." << LL_ENDL; + } + + quit(); + } +} + +void LLMenuOptionPathfindingRebakeNavmesh::initialize() +{ + if (!mIsInitialized) + { + mIsInitialized = true; + + setMode(kRebakeNavMesh_Default); + + createNavMeshStatusListenerForCurrentRegion(); + + if ( !mRegionCrossingSlot.connected() ) + { + mRegionCrossingSlot = gAgent.addRegionChangedCallback(boost::bind(&LLMenuOptionPathfindingRebakeNavmesh::handleRegionBoundaryCrossed, this)); + } + + if (!mAgentStateSlot.connected()) + { + mAgentStateSlot = LLPathfindingManager::getInstance()->registerAgentStateListener(boost::bind(&LLMenuOptionPathfindingRebakeNavmesh::handleAgentState, this, _1)); + } + LLPathfindingManager::getInstance()->requestGetAgentState(); + } +} + +void LLMenuOptionPathfindingRebakeNavmesh::quit() +{ + if (mIsInitialized) // Quitting from the login screen leaves this uninitialized + { + if (mNavMeshSlot.connected()) + { + mNavMeshSlot.disconnect(); + } + + if (mRegionCrossingSlot.connected()) + { + mRegionCrossingSlot.disconnect(); + } + + if (mAgentStateSlot.connected()) + { + mAgentStateSlot.disconnect(); + } + + mIsInitialized = false; + } +} + +bool LLMenuOptionPathfindingRebakeNavmesh::canRebakeRegion() const +{ + if (!mIsInitialized) + { + LL_ERRS("navmeshRebaking") << "LLMenuOptionPathfindingRebakeNavmesh class has not been initialized " + << "when the ability to rebake navmesh is being requested." << LL_ENDL; + } + return mCanRebakeRegion; +} + +LLMenuOptionPathfindingRebakeNavmesh::ERebakeNavMeshMode LLMenuOptionPathfindingRebakeNavmesh::getMode() const +{ + if (!mIsInitialized) + { + LL_ERRS("navmeshRebaking") << "LLMenuOptionPathfindingRebakeNavmesh class has not been initialized " + << "when the mode is being requested." << LL_ENDL; + } + return mRebakeNavMeshMode; +} + +void LLMenuOptionPathfindingRebakeNavmesh::sendRequestRebakeNavmesh() +{ + if (!mIsInitialized) + { + LL_ERRS("navmeshRebaking") << "LLMenuOptionPathfindingRebakeNavmesh class has not been initialized " + << "when the request is being made to rebake the navmesh." << LL_ENDL; + } + else + { + if (!canRebakeRegion()) + { + LL_WARNS("navmeshRebaking") << "attempting to rebake navmesh when user does not have permissions " + << "on this region" << LL_ENDL; + } + if (getMode() != kRebakeNavMesh_Available) + { + LL_WARNS("navmeshRebaking") << "attempting to rebake navmesh when mode is not available" + << LL_ENDL; + } + + setMode(kRebakeNavMesh_RequestSent); + LLPathfindingManager::getInstance()->requestRebakeNavMesh(boost::bind(&LLMenuOptionPathfindingRebakeNavmesh::handleRebakeNavMeshResponse, this, _1)); + } +} + +void LLMenuOptionPathfindingRebakeNavmesh::setMode(ERebakeNavMeshMode pRebakeNavMeshMode) +{ + mRebakeNavMeshMode = pRebakeNavMeshMode; +} + +void LLMenuOptionPathfindingRebakeNavmesh::handleAgentState(bool pCanRebakeRegion) +{ + llassert(mIsInitialized); + mCanRebakeRegion = pCanRebakeRegion; +} + +void LLMenuOptionPathfindingRebakeNavmesh::handleRebakeNavMeshResponse(bool pResponseStatus) +{ + llassert(mIsInitialized); + if (mIsInitialized) + { + if (getMode() == kRebakeNavMesh_RequestSent) + { + setMode(pResponseStatus ? kRebakeNavMesh_InProgress : kRebakeNavMesh_Default); + } + + if (!pResponseStatus) + { + LLNotificationsUtil::add("PathfindingCannotRebakeNavmesh"); + } + } +} + +void LLMenuOptionPathfindingRebakeNavmesh::handleNavMeshStatus(const LLPathfindingNavMeshStatus &pNavMeshStatus) +{ + llassert(mIsInitialized); + if (mIsInitialized) + { + ERebakeNavMeshMode rebakeNavMeshMode = kRebakeNavMesh_Default; + if (pNavMeshStatus.isValid()) + { + switch (pNavMeshStatus.getStatus()) + { + case LLPathfindingNavMeshStatus::kPending : + case LLPathfindingNavMeshStatus::kRepending : + rebakeNavMeshMode = kRebakeNavMesh_Available; + break; + case LLPathfindingNavMeshStatus::kBuilding : + rebakeNavMeshMode = kRebakeNavMesh_InProgress; + break; + case LLPathfindingNavMeshStatus::kComplete : + rebakeNavMeshMode = kRebakeNavMesh_NotAvailable; + break; + default : + rebakeNavMeshMode = kRebakeNavMesh_Default; + llassert(0); + break; + } + } + + setMode(rebakeNavMeshMode); + } +} + +void LLMenuOptionPathfindingRebakeNavmesh::handleRegionBoundaryCrossed() +{ + llassert(mIsInitialized); + if (mIsInitialized) + { + createNavMeshStatusListenerForCurrentRegion(); + mCanRebakeRegion = false; + LLPathfindingManager::getInstance()->requestGetAgentState(); + } +} + +void LLMenuOptionPathfindingRebakeNavmesh::createNavMeshStatusListenerForCurrentRegion() +{ + if (mNavMeshSlot.connected()) + { + mNavMeshSlot.disconnect(); + } + + LLViewerRegion *currentRegion = gAgent.getRegion(); + if (currentRegion != NULL) + { + mNavMeshSlot = LLPathfindingManager::getInstance()->registerNavMeshListenerForRegion(currentRegion, boost::bind(&LLMenuOptionPathfindingRebakeNavmesh::handleNavMeshStatus, this, _2)); + LLPathfindingManager::getInstance()->requestGetNavMeshForRegion(currentRegion, true); + } +} + diff --git a/indra/newview/llmenuoptionpathfindingrebakenavmesh.h b/indra/newview/llmenuoptionpathfindingrebakenavmesh.h index 3a1ae3a870..5a153f762a 100644 --- a/indra/newview/llmenuoptionpathfindingrebakenavmesh.h +++ b/indra/newview/llmenuoptionpathfindingrebakenavmesh.h @@ -1,85 +1,85 @@ -/** -* @file llmenuoptionpathfindingrebakenavmesh.h -* @brief Header file for llmenuoptionpathfindingrebakenavmesh -* @author Prep@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ -#ifndef LL_LLMENUOPTIONPATHFINDINGREBAKENAVMESH_H -#define LL_LLMENUOPTIONPATHFINDINGREBAKENAVMESH_H - -#include - -#include "llpathfindingmanager.h" -#include "llpathfindingnavmesh.h" -#include "llsingleton.h" - -class LLPathfindingNavMeshStatus; - -class LLMenuOptionPathfindingRebakeNavmesh : public LLSingleton -{ - LLSINGLETON(LLMenuOptionPathfindingRebakeNavmesh); - virtual ~LLMenuOptionPathfindingRebakeNavmesh(); - LOG_CLASS(LLMenuOptionPathfindingRebakeNavmesh); - -public: - typedef enum - { - kRebakeNavMesh_Available, - kRebakeNavMesh_RequestSent, - kRebakeNavMesh_InProgress, - kRebakeNavMesh_NotAvailable, - kRebakeNavMesh_Default = kRebakeNavMesh_NotAvailable - } ERebakeNavMeshMode; - - - void initialize(); - void quit(); - - bool canRebakeRegion() const; - ERebakeNavMeshMode getMode() const; - - void sendRequestRebakeNavmesh(); - -protected: - -private: - void setMode(ERebakeNavMeshMode pRebakeNavMeshMode); - - void handleAgentState(bool pCanRebakeRegion); - void handleRebakeNavMeshResponse(bool pResponseStatus); - void handleNavMeshStatus(const LLPathfindingNavMeshStatus &pNavMeshStatus); - void handleRegionBoundaryCrossed(); - - void createNavMeshStatusListenerForCurrentRegion(); - - bool mIsInitialized; - - bool mCanRebakeRegion; - ERebakeNavMeshMode mRebakeNavMeshMode; - - LLPathfindingNavMesh::navmesh_slot_t mNavMeshSlot; - boost::signals2::connection mRegionCrossingSlot; - LLPathfindingManager::agent_state_slot_t mAgentStateSlot; -}; - -#endif // LL_LLMENUOPTIONPATHFINDINGREBAKENAVMESH_H +/** +* @file llmenuoptionpathfindingrebakenavmesh.h +* @brief Header file for llmenuoptionpathfindingrebakenavmesh +* @author Prep@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LL_LLMENUOPTIONPATHFINDINGREBAKENAVMESH_H +#define LL_LLMENUOPTIONPATHFINDINGREBAKENAVMESH_H + +#include + +#include "llpathfindingmanager.h" +#include "llpathfindingnavmesh.h" +#include "llsingleton.h" + +class LLPathfindingNavMeshStatus; + +class LLMenuOptionPathfindingRebakeNavmesh : public LLSingleton +{ + LLSINGLETON(LLMenuOptionPathfindingRebakeNavmesh); + virtual ~LLMenuOptionPathfindingRebakeNavmesh(); + LOG_CLASS(LLMenuOptionPathfindingRebakeNavmesh); + +public: + typedef enum + { + kRebakeNavMesh_Available, + kRebakeNavMesh_RequestSent, + kRebakeNavMesh_InProgress, + kRebakeNavMesh_NotAvailable, + kRebakeNavMesh_Default = kRebakeNavMesh_NotAvailable + } ERebakeNavMeshMode; + + + void initialize(); + void quit(); + + bool canRebakeRegion() const; + ERebakeNavMeshMode getMode() const; + + void sendRequestRebakeNavmesh(); + +protected: + +private: + void setMode(ERebakeNavMeshMode pRebakeNavMeshMode); + + void handleAgentState(bool pCanRebakeRegion); + void handleRebakeNavMeshResponse(bool pResponseStatus); + void handleNavMeshStatus(const LLPathfindingNavMeshStatus &pNavMeshStatus); + void handleRegionBoundaryCrossed(); + + void createNavMeshStatusListenerForCurrentRegion(); + + bool mIsInitialized; + + bool mCanRebakeRegion; + ERebakeNavMeshMode mRebakeNavMeshMode; + + LLPathfindingNavMesh::navmesh_slot_t mNavMeshSlot; + boost::signals2::connection mRegionCrossingSlot; + LLPathfindingManager::agent_state_slot_t mAgentStateSlot; +}; + +#endif // LL_LLMENUOPTIONPATHFINDINGREBAKENAVMESH_H diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 869d1a6ac8..f0fd731fb5 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -1,5531 +1,5531 @@ -/** - * @file llmeshrepository.cpp - * @brief Mesh repository implementation. - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010-2014, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llapr.h" -#include "apr_portable.h" -#include "apr_pools.h" -#include "apr_dso.h" -#include "llhttpconstants.h" -#include "llmeshrepository.h" - -#include "llagent.h" -#include "llappviewer.h" -#include "llbufferstream.h" -#include "llcallbacklist.h" -#include "lldatapacker.h" -#include "lldeadmantimer.h" -#include "llfloatermodelpreview.h" -#include "llfloaterperms.h" -#include "llimagej2c.h" -#include "llhost.h" -#include "llmath.h" -#include "llnotificationsutil.h" -#include "llsd.h" -#include "llsdutil_math.h" -#include "llsdserialize.h" -#include "llthread.h" -#include "llfilesystem.h" -#include "llviewercontrol.h" -#include "llviewerinventory.h" -#include "llviewermenufile.h" -#include "llviewermessage.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llviewerstatsrecorder.h" -#include "llviewertexturelist.h" -#include "llvolume.h" -#include "llvolumemgr.h" -#include "llvovolume.h" -#include "llworld.h" -#include "material_codes.h" -#include "pipeline.h" -#include "llinventorymodel.h" -#include "llfoldertype.h" -#include "llviewerparcelmgr.h" -#include "lluploadfloaterobservers.h" -#include "bufferarray.h" -#include "bufferstream.h" -#include "llfasttimer.h" -#include "llcorehttputil.h" -#include "lltrans.h" -#include "llstatusbar.h" -#include "llinventorypanel.h" -#include "lluploaddialog.h" -#include "llfloaterreg.h" - -#include "boost/iostreams/device/array.hpp" -#include "boost/iostreams/stream.hpp" -#include "boost/lexical_cast.hpp" - -#ifndef LL_WINDOWS -#include "netdb.h" -#endif - - -// Purpose -// -// The purpose of this module is to provide access between the viewer -// and the asset system as regards to mesh objects. -// -// * High-throughput download of mesh assets from servers while -// following best industry practices for network profile. -// * Reliable expensing and upload of new mesh assets. -// * Recovery and retry from errors when appropriate. -// * Decomposition of mesh assets for preview and uploads. -// * And most important: all of the above without exposing the -// main thread to stalls due to deep processing or thread -// locking actions. In particular, the following operations -// on LLMeshRepository are very averse to any stalls: -// * loadMesh -// * search in mMeshHeader (For structural details, see: -// http://wiki.secondlife.com/wiki/Mesh/Mesh_Asset_Format) -// * notifyLoadedMeshes -// * getSkinInfo -// -// Threads -// -// main Main rendering thread, very sensitive to locking and other stalls -// repo Overseeing worker thread associated with the LLMeshRepoThread class -// decom Worker thread for mesh decomposition requests -// core HTTP worker thread: does the work but doesn't intrude here -// uploadN 0-N temporary mesh upload threads (0-1 in practice) -// -// Sequence of Operations -// -// What follows is a description of the retrieval of one LOD for -// a new mesh object. Work is performed by a series of short, quick -// actions distributed over a number of threads. Each is meant -// to proceed without stalling and the whole forms a deep request -// pipeline to achieve throughput. Ellipsis indicates a return -// or break in processing which is resumed elsewhere. -// -// main thread repo thread (run() method) -// -// loadMesh() invoked to request LOD -// append LODRequest to mPendingRequests -// ... -// other mesh requests may be made -// ... -// notifyLoadedMeshes() invoked to stage work -// append HeaderRequest to mHeaderReqQ -// ... -// scan mHeaderReqQ -// issue 4096-byte GET for header -// ... -// onCompleted() invoked for GET -// data copied -// headerReceived() invoked -// LLSD parsed -// mMeshHeader updated -// scan mPendingLOD for LOD request -// push LODRequest to mLODReqQ -// ... -// scan mLODReqQ -// fetchMeshLOD() invoked -// issue Byte-Range GET for LOD -// ... -// onCompleted() invoked for GET -// data copied -// lodReceived() invoked -// unpack data into LLVolume -// append LoadedMesh to mLoadedQ -// ... -// notifyLoadedMeshes() invoked again -// scan mLoadedQ -// notifyMeshLoaded() for LOD -// setMeshAssetLoaded() invoked for system volume -// notifyMeshLoaded() invoked for each interested object -// ... -// -// Mutexes -// -// LLMeshRepository::mMeshMutex -// LLMeshRepoThread::mMutex -// LLMeshRepoThread::mHeaderMutex -// LLMeshRepoThread::mSignal (LLCondition) -// LLPhysicsDecomp::mSignal (LLCondition) -// LLPhysicsDecomp::mMutex -// LLMeshUploadThread::mMutex -// -// Mutex Order Rules -// -// 1. LLMeshRepoThread::mMutex before LLMeshRepoThread::mHeaderMutex -// 2. LLMeshRepository::mMeshMutex before LLMeshRepoThread::mMutex -// (There are more rules, haven't been extracted.) -// -// Data Member Access/Locking -// -// Description of how shared access to static and instance data -// members is performed. Each member is followed by the name of -// the mutex, if any, covering the data and then a list of data -// access models each of which is a triplet of the following form: -// -// {ro, wo, rw}.{main, repo, any}.{mutex, none} -// Type of access: read-only, write-only, read-write. -// Accessing thread or 'any' -// Relevant mutex held during access (several may be held) or 'none' -// -// A careful eye will notice some unsafe operations. Many of these -// have an alibi of some form. Several types of alibi are identified -// and listed here: -// -// [0] No alibi. Probably unsafe. -// [1] Single-writer, self-consistent readers. Old data must -// be tolerated by any reader but data will come true eventually. -// [2] Like [1] but provides a hint about thread state. These -// may be unsafe. -// [3] empty() check outside of lock. Can me made safish when -// done in double-check lock style. But this depends on -// std:: implementation and memory model. -// [4] Appears to be covered by a mutex but doesn't need one. -// [5] Read of a double-checked lock. -// -// So, in addition to documentation, take this as a to-do/review -// list and see if you can improve things. For porters to non-x86 -// architectures, the weaker memory models will make these platforms -// probabilistically more susceptible to hitting race conditions. -// True here and in other multi-thread code such as texture fetching. -// (Strong memory models make weak programmers. Weak memory models -// make strong programmers. Ref: arm, ppc, mips, alpha) -// -// LLMeshRepository: -// -// sBytesReceived none rw.repo.none, ro.main.none [1] -// sMeshRequestCount " -// sHTTPRequestCount " -// sHTTPLargeRequestCount " -// sHTTPRetryCount " -// sHTTPErrorCount " -// sLODPending mMeshMutex [4] rw.main.mMeshMutex -// sLODProcessing Repo::mMutex rw.any.Repo::mMutex -// sCacheBytesRead none rw.repo.none, ro.main.none [1] -// sCacheBytesWritten " -// sCacheReads " -// sCacheWrites " -// mLoadingMeshes mMeshMutex [4] rw.main.none, rw.any.mMeshMutex -// mSkinMap none rw.main.none -// mDecompositionMap none rw.main.none -// mPendingRequests mMeshMutex [4] rw.main.mMeshMutex -// mLoadingSkins mMeshMutex [4] rw.main.mMeshMutex -// mPendingSkinRequests mMeshMutex [4] rw.main.mMeshMutex -// mLoadingDecompositions mMeshMutex [4] rw.main.mMeshMutex -// mPendingDecompositionRequests mMeshMutex [4] rw.main.mMeshMutex -// mLoadingPhysicsShapes mMeshMutex [4] rw.main.mMeshMutex -// mPendingPhysicsShapeRequests mMeshMutex [4] rw.main.mMeshMutex -// mUploads none rw.main.none (upload thread accessing objects) -// mUploadWaitList none rw.main.none (upload thread accessing objects) -// mInventoryQ mMeshMutex [4] rw.main.mMeshMutex, ro.main.none [5] -// mUploadErrorQ mMeshMutex rw.main.mMeshMutex, rw.any.mMeshMutex -// mGetMeshVersion none rw.main.none -// -// LLMeshRepoThread: -// -// sActiveHeaderRequests mMutex rw.any.mMutex, ro.repo.none [1] -// sActiveLODRequests mMutex rw.any.mMutex, ro.repo.none [1] -// sMaxConcurrentRequests mMutex wo.main.none, ro.repo.none, ro.main.mMutex -// mMeshHeader mHeaderMutex rw.repo.mHeaderMutex, ro.main.mHeaderMutex, ro.main.none [0] -// mSkinRequests mMutex rw.repo.mMutex, ro.repo.none [5] -// mSkinInfoQ mMutex rw.repo.mMutex, rw.main.mMutex [5] (was: [0]) -// mDecompositionRequests mMutex rw.repo.mMutex, ro.repo.none [5] -// mPhysicsShapeRequests mMutex rw.repo.mMutex, ro.repo.none [5] -// mDecompositionQ mMutex rw.repo.mMutex, rw.main.mMutex [5] (was: [0]) -// mHeaderReqQ mMutex ro.repo.none [5], rw.repo.mMutex, rw.any.mMutex -// mLODReqQ mMutex ro.repo.none [5], rw.repo.mMutex, rw.any.mMutex -// mUnavailableQ mMutex rw.repo.none [0], ro.main.none [5], rw.main.mMutex -// mLoadedQ mMutex rw.repo.mMutex, ro.main.none [5], rw.main.mMutex -// mPendingLOD mMutex rw.repo.mMutex, rw.any.mMutex -// mGetMeshCapability mMutex rw.main.mMutex, ro.repo.mMutex (was: [0]) -// mGetMesh2Capability mMutex rw.main.mMutex, ro.repo.mMutex (was: [0]) -// mGetMeshVersion mMutex rw.main.mMutex, ro.repo.mMutex -// mHttp* none rw.repo.none -// -// LLMeshUploadThread: -// -// mDiscarded mMutex rw.main.mMutex, ro.uploadN.none [1] -// ... more ... -// -// QA/Development Testing -// -// Debug variable 'MeshUploadFakeErrors' takes a mask of bits that will -// simulate an error on fee query or upload. Defined bits are: -// -// 0x01 Simulate application error on fee check reading -// response body from file "fake_upload_error.xml" -// 0x02 Same as 0x01 but for actual upload attempt. -// 0x04 Simulate a transport problem on fee check with a -// locally-generated 500 status. -// 0x08 As with 0x04 but for the upload operation. -// -// For major changes, see the LL_MESH_FASTTIMER_ENABLE below and -// instructions for looking for frame stalls using fast timers. -// -// *TODO: Work list for followup actions: -// * Review anything marked as unsafe above, verify if there are real issues. -// * See if we can put ::run() into a hard sleep. May not actually perform better -// than the current scheme so be prepared for disappointment. You'll likely -// need to introduce a condition variable class that references a mutex in -// methods rather than derives from mutex which isn't correct. -// * On upload failures, make more information available to the alerting -// dialog. Get the structured information going into the log into a -// tree there. -// * Header parse failures come without much explanation. Elaborate. -// * Work queue for uploads? Any need for this or is the current scheme good -// enough? -// * Move data structures holding mesh data used by main thread into main- -// thread-only access so that no locking is needed. May require duplication -// of some data so that worker thread has a minimal data set to guide -// operations. -// -// -------------------------------------------------------------------------- -// Development/Debug/QA Tools -// -// Enable here or in build environment to get fasttimer data on mesh fetches. -// -// Typically, this is used to perform A/B testing using the -// fasttimer console (shift-ctrl-9). This is done by looking -// for stalls due to lock contention between the main thread -// and the repository and HTTP code. In a release viewer, -// these appear as ping-time or worse spikes in frame time. -// With this instrumentation enabled, a stall will appear -// under the 'Mesh Fetch' timer which will be either top-level -// or under 'Render' time. - -static LLFastTimer::DeclareTimer FTM_MESH_FETCH("Mesh Fetch"); - -// Random failure testing for development/QA. -// -// Set the MESH_*_FAILED macros to either 'false' or to -// an invocation of MESH_RANDOM_NTH_TRUE() with some -// suitable number. In production, all must be false. -// -// Example: -// #define MESH_HTTP_RESPONSE_FAILED MESH_RANDOM_NTH_TRUE(9) - -// 1-in-N calls will test true -#define MESH_RANDOM_NTH_TRUE(_N) ( ll_rand(S32(_N)) == 0 ) - -#define MESH_HTTP_RESPONSE_FAILED false -#define MESH_HEADER_PROCESS_FAILED false -#define MESH_LOD_PROCESS_FAILED false -#define MESH_SKIN_INFO_PROCESS_FAILED false -#define MESH_DECOMP_PROCESS_FAILED false -#define MESH_PHYS_SHAPE_PROCESS_FAILED false - -// -------------------------------------------------------------------------- - - -LLMeshRepository gMeshRepo; - -const S32 MESH_HEADER_SIZE = 4096; // Important: assumption is that headers fit in this space - - -const S32 REQUEST2_HIGH_WATER_MIN = 32; // Limits for GetMesh2 regions -const S32 REQUEST2_HIGH_WATER_MAX = 100; -const S32 REQUEST2_LOW_WATER_MIN = 16; -const S32 REQUEST2_LOW_WATER_MAX = 50; - -const U32 LARGE_MESH_FETCH_THRESHOLD = 1U << 21; // Size at which requests goes to narrow/slow queue -const long SMALL_MESH_XFER_TIMEOUT = 120L; // Seconds to complete xfer, small mesh downloads -const long LARGE_MESH_XFER_TIMEOUT = 600L; // Seconds to complete xfer, large downloads - -const U32 DOWNLOAD_RETRY_LIMIT = 8; -const F32 DOWNLOAD_RETRY_DELAY = 0.5f; // seconds - -// Would normally like to retry on uploads as some -// retryable failures would be recoverable. Unfortunately, -// the mesh service is using 500 (retryable) rather than -// 400/bad request (permanent) for a bad payload and -// retrying that just leads to revocation of the one-shot -// cap which then produces a 404 on retry destroying some -// (occasionally) useful error information. We'll leave -// upload retries to the user as in the past. SH-4667. -const long UPLOAD_RETRY_LIMIT = 0L; - -// Maximum mesh version to support. Three least significant digits are reserved for the minor version, -// with major version changes indicating a format change that is not backwards compatible and should not -// be parsed by viewers that don't specifically support that version. For example, if the integer "1" is -// present, the version is 0.001. A viewer that can parse version 0.001 can also parse versions up to 0.999, -// but not 1.0 (integer 1000). -// See wiki at https://wiki.secondlife.com/wiki/Mesh/Mesh_Asset_Format -const S32 MAX_MESH_VERSION = 999; - -U32 LLMeshRepository::sBytesReceived = 0; -U32 LLMeshRepository::sMeshRequestCount = 0; -U32 LLMeshRepository::sHTTPRequestCount = 0; -U32 LLMeshRepository::sHTTPLargeRequestCount = 0; -U32 LLMeshRepository::sHTTPRetryCount = 0; -U32 LLMeshRepository::sHTTPErrorCount = 0; -U32 LLMeshRepository::sLODProcessing = 0; -U32 LLMeshRepository::sLODPending = 0; - -U32 LLMeshRepository::sCacheBytesRead = 0; -U32 LLMeshRepository::sCacheBytesWritten = 0; -U32 LLMeshRepository::sCacheBytesHeaders = 0; -U32 LLMeshRepository::sCacheBytesSkins = 0; -U32 LLMeshRepository::sCacheBytesDecomps = 0; -U32 LLMeshRepository::sCacheReads = 0; -U32 LLMeshRepository::sCacheWrites = 0; -U32 LLMeshRepository::sMaxLockHoldoffs = 0; - -LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0, false); // true -> gather cpu metrics - -namespace { - // The NoOpDeletor is used when passing certain objects (generally the LLMeshUploadThread) - // in a smart pointer below for passage into the LLCore::Http libararies. - // When the smart pointer is destroyed, no action will be taken since we - // do not in these cases want the object to be destroyed at the end of the call. - // - // *NOTE$: Yes! It is "Deletor" - // http://english.stackexchange.com/questions/4733/what-s-the-rule-for-adding-er-vs-or-when-nouning-a-verb - // "delete" derives from Latin "deletus" - - void NoOpDeletor(LLCore::HttpHandler *) - { /*NoOp*/ } -} - -static S32 dump_num = 0; -std::string make_dump_name(std::string prefix, S32 num) -{ - return prefix + std::to_string(num) + std::string(".xml"); -} -void dump_llsd_to_file(const LLSD& content, std::string filename); -LLSD llsd_from_file(std::string filename); - -const std::string header_lod[] = -{ - "lowest_lod", - "low_lod", - "medium_lod", - "high_lod" -}; -const char * const LOG_MESH = "Mesh"; - -// Static data and functions to measure mesh load -// time metrics for a new region scene. -static unsigned int metrics_teleport_start_count = 0; -boost::signals2::connection metrics_teleport_started_signal; -static void teleport_started(); - -void on_new_single_inventory_upload_complete( - LLAssetType::EType asset_type, - LLInventoryType::EType inventory_type, - const std::string inventory_type_string, - const LLUUID& item_folder_id, - const std::string& item_name, - const std::string& item_description, - const LLSD& server_response, - S32 upload_price); - - -//get the number of bytes resident in memory for given volume -U32 get_volume_memory_size(const LLVolume* volume) -{ - U32 indices = 0; - U32 vertices = 0; - - for (U32 i = 0; i < volume->getNumVolumeFaces(); ++i) - { - const LLVolumeFace& face = volume->getVolumeFace(i); - indices += face.mNumIndices; - vertices += face.mNumVertices; - } - - - return indices*2+vertices*11+sizeof(LLVolume)+sizeof(LLVolumeFace)*volume->getNumVolumeFaces(); -} - -void get_vertex_buffer_from_mesh(LLCDMeshData& mesh, LLModel::PhysicsMesh& res, F32 scale = 1.f) -{ - res.mPositions.clear(); - res.mNormals.clear(); - - const F32* v = mesh.mVertexBase; - - if (mesh.mIndexType == LLCDMeshData::INT_16) - { - U16* idx = (U16*) mesh.mIndexBase; - for (S32 j = 0; j < mesh.mNumTriangles; ++j) - { - F32* mp0 = (F32*) ((U8*)v+idx[0]*mesh.mVertexStrideBytes); - F32* mp1 = (F32*) ((U8*)v+idx[1]*mesh.mVertexStrideBytes); - F32* mp2 = (F32*) ((U8*)v+idx[2]*mesh.mVertexStrideBytes); - - idx = (U16*) (((U8*)idx)+mesh.mIndexStrideBytes); - - LLVector3 v0(mp0); - LLVector3 v1(mp1); - LLVector3 v2(mp2); - - LLVector3 n = (v1-v0)%(v2-v0); - n.normalize(); - - res.mPositions.push_back(v0*scale); - res.mPositions.push_back(v1*scale); - res.mPositions.push_back(v2*scale); - - res.mNormals.push_back(n); - res.mNormals.push_back(n); - res.mNormals.push_back(n); - } - } - else - { - U32* idx = (U32*) mesh.mIndexBase; - for (S32 j = 0; j < mesh.mNumTriangles; ++j) - { - F32* mp0 = (F32*) ((U8*)v+idx[0]*mesh.mVertexStrideBytes); - F32* mp1 = (F32*) ((U8*)v+idx[1]*mesh.mVertexStrideBytes); - F32* mp2 = (F32*) ((U8*)v+idx[2]*mesh.mVertexStrideBytes); - - idx = (U32*) (((U8*)idx)+mesh.mIndexStrideBytes); - - LLVector3 v0(mp0); - LLVector3 v1(mp1); - LLVector3 v2(mp2); - - LLVector3 n = (v1-v0)%(v2-v0); - n.normalize(); - - res.mPositions.push_back(v0*scale); - res.mPositions.push_back(v1*scale); - res.mPositions.push_back(v2*scale); - - res.mNormals.push_back(n); - res.mNormals.push_back(n); - res.mNormals.push_back(n); - } - } -} - -void RequestStats::updateTime() -{ - U32 modifier = 1 << mRetries; // before ++ - mRetries++; - mTimer.reset(); - mTimer.setTimerExpirySec(DOWNLOAD_RETRY_DELAY * (F32)modifier); // up to 32s, 64 total wait -} - -bool RequestStats::canRetry() const -{ - return mRetries < DOWNLOAD_RETRY_LIMIT; -} - -bool RequestStats::isDelayed() const -{ - return mTimer.getStarted() && !mTimer.hasExpired(); -} - -LLViewerFetchedTexture* LLMeshUploadThread::FindViewerTexture(const LLImportMaterial& material) -{ - LLPointer< LLViewerFetchedTexture > * ppTex = static_cast< LLPointer< LLViewerFetchedTexture > * >(material.mOpaqueData); - return ppTex ? (*ppTex).get() : NULL; -} - -volatile S32 LLMeshRepoThread::sActiveHeaderRequests = 0; -volatile S32 LLMeshRepoThread::sActiveLODRequests = 0; -U32 LLMeshRepoThread::sMaxConcurrentRequests = 1; -S32 LLMeshRepoThread::sRequestLowWater = REQUEST2_LOW_WATER_MIN; -S32 LLMeshRepoThread::sRequestHighWater = REQUEST2_HIGH_WATER_MIN; -S32 LLMeshRepoThread::sRequestWaterLevel = 0; - -// Base handler class for all mesh users of llcorehttp. -// This is roughly equivalent to a Responder class in -// traditional LL code. The base is going to perform -// common response/data handling in the inherited -// onCompleted() method. Derived classes, one for each -// type of HTTP action, define processData() and -// processFailure() methods to customize handling and -// error messages. -// -// LLCore::HttpHandler -// LLMeshHandlerBase -// LLMeshHeaderHandler -// LLMeshLODHandler -// LLMeshSkinInfoHandler -// LLMeshDecompositionHandler -// LLMeshPhysicsShapeHandler -// LLMeshUploadThread - -class LLMeshHandlerBase : public LLCore::HttpHandler, - public std::enable_shared_from_this -{ -public: - typedef std::shared_ptr ptr_t; - - LOG_CLASS(LLMeshHandlerBase); - LLMeshHandlerBase(U32 offset, U32 requested_bytes) - : LLCore::HttpHandler(), - mMeshParams(), - mProcessed(false), - mHttpHandle(LLCORE_HTTP_HANDLE_INVALID), - mOffset(offset), - mRequestedBytes(requested_bytes) - {} - - virtual ~LLMeshHandlerBase() - {} - -protected: - LLMeshHandlerBase(const LLMeshHandlerBase &); // Not defined - void operator=(const LLMeshHandlerBase &); // Not defined - -public: - virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); - virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size) = 0; - virtual void processFailure(LLCore::HttpStatus status) = 0; - -public: - LLVolumeParams mMeshParams; - bool mProcessed; - LLCore::HttpHandle mHttpHandle; - U32 mOffset; - U32 mRequestedBytes; -}; - - -// Subclass for header fetches. -// -// Thread: repo -class LLMeshHeaderHandler : public LLMeshHandlerBase -{ -public: - LOG_CLASS(LLMeshHeaderHandler); - LLMeshHeaderHandler(const LLVolumeParams & mesh_params, U32 offset, U32 requested_bytes) - : LLMeshHandlerBase(offset, requested_bytes) - { - mMeshParams = mesh_params; - LLMeshRepoThread::incActiveHeaderRequests(); - } - virtual ~LLMeshHeaderHandler(); - -protected: - LLMeshHeaderHandler(const LLMeshHeaderHandler &); // Not defined - void operator=(const LLMeshHeaderHandler &); // Not defined - -public: - virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size); - virtual void processFailure(LLCore::HttpStatus status); -}; - - -// Subclass for LOD fetches. -// -// Thread: repo -class LLMeshLODHandler : public LLMeshHandlerBase -{ -public: - LOG_CLASS(LLMeshLODHandler); - LLMeshLODHandler(const LLVolumeParams & mesh_params, S32 lod, U32 offset, U32 requested_bytes) - : LLMeshHandlerBase(offset, requested_bytes), - mLOD(lod) - { - mMeshParams = mesh_params; - LLMeshRepoThread::incActiveLODRequests(); - } - virtual ~LLMeshLODHandler(); - -protected: - LLMeshLODHandler(const LLMeshLODHandler &); // Not defined - void operator=(const LLMeshLODHandler &); // Not defined - -public: - virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size); - virtual void processFailure(LLCore::HttpStatus status); - -public: - S32 mLOD; -}; - - -// Subclass for skin info fetches. -// -// Thread: repo -class LLMeshSkinInfoHandler : public LLMeshHandlerBase -{ -public: - LOG_CLASS(LLMeshSkinInfoHandler); - LLMeshSkinInfoHandler(const LLUUID& id, U32 offset, U32 requested_bytes) - : LLMeshHandlerBase(offset, requested_bytes), - mMeshID(id) - {} - virtual ~LLMeshSkinInfoHandler(); - -protected: - LLMeshSkinInfoHandler(const LLMeshSkinInfoHandler &); // Not defined - void operator=(const LLMeshSkinInfoHandler &); // Not defined - -public: - virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size); - virtual void processFailure(LLCore::HttpStatus status); - -public: - LLUUID mMeshID; -}; - - -// Subclass for decomposition fetches. -// -// Thread: repo -class LLMeshDecompositionHandler : public LLMeshHandlerBase -{ -public: - LOG_CLASS(LLMeshDecompositionHandler); - LLMeshDecompositionHandler(const LLUUID& id, U32 offset, U32 requested_bytes) - : LLMeshHandlerBase(offset, requested_bytes), - mMeshID(id) - {} - virtual ~LLMeshDecompositionHandler(); - -protected: - LLMeshDecompositionHandler(const LLMeshDecompositionHandler &); // Not defined - void operator=(const LLMeshDecompositionHandler &); // Not defined - -public: - virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size); - virtual void processFailure(LLCore::HttpStatus status); - -public: - LLUUID mMeshID; -}; - - -// Subclass for physics shape fetches. -// -// Thread: repo -class LLMeshPhysicsShapeHandler : public LLMeshHandlerBase -{ -public: - LOG_CLASS(LLMeshPhysicsShapeHandler); - LLMeshPhysicsShapeHandler(const LLUUID& id, U32 offset, U32 requested_bytes) - : LLMeshHandlerBase(offset, requested_bytes), - mMeshID(id) - {} - virtual ~LLMeshPhysicsShapeHandler(); - -protected: - LLMeshPhysicsShapeHandler(const LLMeshPhysicsShapeHandler &); // Not defined - void operator=(const LLMeshPhysicsShapeHandler &); // Not defined - -public: - virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size); - virtual void processFailure(LLCore::HttpStatus status); - -public: - LLUUID mMeshID; -}; - - -void log_upload_error(LLCore::HttpStatus status, const LLSD& content, - const char * const stage, const std::string & model_name) -{ - // Add notification popup. - LLSD args; - std::string message = content["error"]["message"].asString(); - std::string identifier = content["error"]["identifier"].asString(); - args["MESSAGE"] = message; - args["IDENTIFIER"] = identifier; - args["LABEL"] = model_name; - - // Log details. - LL_WARNS(LOG_MESH) << "Error in stage: " << stage - << ", Reason: " << status.toString() - << " (" << status.toTerseString() << ")" << LL_ENDL; - - std::ostringstream details; - typedef std::set mav_errors_set_t; - mav_errors_set_t mav_errors; - - if (content.has("error")) - { - const LLSD& err = content["error"]; - LL_WARNS(LOG_MESH) << "error: " << err << LL_ENDL; - LL_WARNS(LOG_MESH) << " mesh upload failed, stage '" << stage - << "', error '" << err["error"].asString() - << "', message '" << err["message"].asString() - << "', id '" << err["identifier"].asString() - << "'" << LL_ENDL; - - if (err.has("errors")) - { - details << std::endl << std::endl; - - S32 error_num = 0; - const LLSD& err_list = err["errors"]; - for (LLSD::array_const_iterator it = err_list.beginArray(); - it != err_list.endArray(); - ++it) - { - const LLSD& err_entry = *it; - std::string message = err_entry["message"]; - - if (message.length() > 0) - { - mav_errors.insert(message); - } - - LL_WARNS(LOG_MESH) << " error[" << error_num << "]:" << LL_ENDL; - for (LLSD::map_const_iterator map_it = err_entry.beginMap(); - map_it != err_entry.endMap(); - ++map_it) - { - LL_WARNS(LOG_MESH) << " " << map_it->first << ": " - << map_it->second << LL_ENDL; - } - error_num++; - } - } - } - else - { - LL_WARNS(LOG_MESH) << "Bad response to mesh request, no additional error information available." << LL_ENDL; - } - - mav_errors_set_t::iterator mav_errors_it = mav_errors.begin(); - for (; mav_errors_it != mav_errors.end(); ++mav_errors_it) - { - std::string mav_details = "Mav_Details_" + *mav_errors_it; - details << "Message: '" << *mav_errors_it << "': " << LLTrans::getString(mav_details) << std::endl << std::endl; - } - - std::string details_str = details.str(); - if (details_str.length() > 0) - { - args["DETAILS"] = details_str; - } - - gMeshRepo.uploadError(args); -} - -LLMeshRepoThread::LLMeshRepoThread() -: LLThread("mesh repo"), - mHttpRequest(NULL), - mHttpOptions(), - mHttpLargeOptions(), - mHttpHeaders(), - mHttpPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), - mHttpLargePolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID) -{ - LLAppCoreHttp & app_core_http(LLAppViewer::instance()->getAppCoreHttp()); - - mMutex = new LLMutex(); - mHeaderMutex = new LLMutex(); - mSignal = new LLCondition(); - mHttpRequest = new LLCore::HttpRequest; - mHttpOptions = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); - mHttpOptions->setTransferTimeout(SMALL_MESH_XFER_TIMEOUT); - mHttpOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter")); - mHttpLargeOptions = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); - mHttpLargeOptions->setTransferTimeout(LARGE_MESH_XFER_TIMEOUT); - mHttpLargeOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter")); - mHttpHeaders = LLCore::HttpHeaders::ptr_t(new LLCore::HttpHeaders); - mHttpHeaders->append(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_VND_LL_MESH); - mHttpPolicyClass = app_core_http.getPolicy(LLAppCoreHttp::AP_MESH2); - mHttpLargePolicyClass = app_core_http.getPolicy(LLAppCoreHttp::AP_LARGE_MESH); -} - - -LLMeshRepoThread::~LLMeshRepoThread() -{ - LL_INFOS(LOG_MESH) << "Small GETs issued: " << LLMeshRepository::sHTTPRequestCount - << ", Large GETs issued: " << LLMeshRepository::sHTTPLargeRequestCount - << ", Max Lock Holdoffs: " << LLMeshRepository::sMaxLockHoldoffs - << LL_ENDL; - - mHttpRequestSet.clear(); - mHttpHeaders.reset(); - - while (!mSkinInfoQ.empty()) - { - delete mSkinInfoQ.front(); - mSkinInfoQ.pop_front(); - } - - while (!mDecompositionQ.empty()) - { - delete mDecompositionQ.front(); - mDecompositionQ.pop_front(); - } - - delete mHttpRequest; - mHttpRequest = NULL; - delete mMutex; - mMutex = NULL; - delete mHeaderMutex; - mHeaderMutex = NULL; - delete mSignal; - mSignal = NULL; -} - -void LLMeshRepoThread::run() -{ - LLCDResult res = LLConvexDecomposition::initThread(); - if (res != LLCD_OK && LLConvexDecomposition::isFunctional()) - { - LL_WARNS(LOG_MESH) << "Convex decomposition unable to be loaded. Expect severe problems." << LL_ENDL; - } - - while (!LLApp::isExiting()) - { - // *TODO: Revise sleep/wake strategy and try to move away - // from polling operations in this thread. We can sleep - // this thread hard when: - // * All Http requests are serviced - // * LOD request queue empty - // * Header request queue empty - // * Skin info request queue empty - // * Decomposition request queue empty - // * Physics shape request queue empty - // We wake the thread when any of the above become untrue. - // Will likely need a correctly-implemented condition variable to do this. - // On the other hand, this may actually be an effective and efficient scheme... - - mSignal->wait(); - - if (LLApp::isExiting()) - { - break; - } - - if (! mHttpRequestSet.empty()) - { - // Dispatch all HttpHandler notifications - mHttpRequest->update(0L); - } - sRequestWaterLevel = mHttpRequestSet.size(); // Stats data update - - // NOTE: order of queue processing intentionally favors LOD requests over header requests - // Todo: we are processing mLODReqQ, mHeaderReqQ, mSkinRequests, mDecompositionRequests and mPhysicsShapeRequests - // in relatively similar manners, remake code to simplify/unify the process, - // like processRequests(&requestQ, fetchFunction); which does same thing for each element - - if (!mLODReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) - { - std::list incomplete; - while (!mLODReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) - { - if (!mMutex) - { - break; - } - - mMutex->lock(); - LODRequest req = mLODReqQ.front(); - mLODReqQ.pop(); - LLMeshRepository::sLODProcessing--; - mMutex->unlock(); - if (req.isDelayed()) - { - // failed to load before, wait a bit - incomplete.push_front(req); - } - else if (!fetchMeshLOD(req.mMeshParams, req.mLOD, req.canRetry())) - { - if (req.canRetry()) - { - // failed, resubmit - req.updateTime(); - incomplete.push_front(req); - } - else - { - // too many fails - LLMutexLock lock(mMutex); - mUnavailableQ.push_back(req); - LL_WARNS() << "Failed to load " << req.mMeshParams << " , skip" << LL_ENDL; - } - } - } - - if (!incomplete.empty()) - { - LLMutexLock locker(mMutex); - for (std::list::iterator iter = incomplete.begin(); iter != incomplete.end(); iter++) - { - mLODReqQ.push(*iter); - ++LLMeshRepository::sLODProcessing; - } - } - } - - if (!mHeaderReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) - { - std::list incomplete; - while (!mHeaderReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) - { - if (!mMutex) - { - break; - } - - mMutex->lock(); - HeaderRequest req = mHeaderReqQ.front(); - mHeaderReqQ.pop(); - mMutex->unlock(); - if (req.isDelayed()) - { - // failed to load before, wait a bit - incomplete.push_front(req); - } - else if (!fetchMeshHeader(req.mMeshParams, req.canRetry())) - { - if (req.canRetry()) - { - //failed, resubmit - req.updateTime(); - incomplete.push_front(req); - } - else - { - LL_DEBUGS() << "mHeaderReqQ failed: " << req.mMeshParams << LL_ENDL; - } - } - } - - if (!incomplete.empty()) - { - LLMutexLock locker(mMutex); - for (std::list::iterator iter = incomplete.begin(); iter != incomplete.end(); iter++) - { - mHeaderReqQ.push(*iter); - } - } - } - - // For the final three request lists, similar goal to above but - // slightly different queue structures. Stay off the mutex when - // performing long-duration actions. - - if (mHttpRequestSet.size() < sRequestHighWater - && (!mSkinRequests.empty() - || !mDecompositionRequests.empty() - || !mPhysicsShapeRequests.empty())) - { - // Something to do probably, lock and double-check. We don't want - // to hold the lock long here. That will stall main thread activities - // so we bounce it. - - if (!mSkinRequests.empty()) - { - std::list incomplete; - while (!mSkinRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) - { - - mMutex->lock(); - auto req = mSkinRequests.front(); - mSkinRequests.pop_front(); - mMutex->unlock(); - if (req.isDelayed()) - { - incomplete.emplace_back(req); - } - else if (!fetchMeshSkinInfo(req.mId, req.canRetry())) - { - if (req.canRetry()) - { - req.updateTime(); - incomplete.emplace_back(req); - } - else - { - LLMutexLock locker(mMutex); - mSkinUnavailableQ.push_back(req); - LL_DEBUGS() << "mSkinReqQ failed: " << req.mId << LL_ENDL; - } - } - } - - if (!incomplete.empty()) - { - LLMutexLock locker(mMutex); - for (const auto& req : incomplete) - { - mSkinRequests.push_back(req); - } - } - } - - // holding lock, try next list - // *TODO: For UI/debug-oriented lists, we might drop the fine- - // grained locking as there's a lowered expectation of smoothness - // in these cases. - if (!mDecompositionRequests.empty()) - { - std::set incomplete; - while (!mDecompositionRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) - { - mMutex->lock(); - std::set::iterator iter = mDecompositionRequests.begin(); - UUIDBasedRequest req = *iter; - mDecompositionRequests.erase(iter); - mMutex->unlock(); - if (req.isDelayed()) - { - incomplete.insert(req); - } - else if (!fetchMeshDecomposition(req.mId)) - { - if (req.canRetry()) - { - req.updateTime(); - incomplete.insert(req); - } - else - { - LL_DEBUGS() << "mDecompositionRequests failed: " << req.mId << LL_ENDL; - } - } - } - - if (!incomplete.empty()) - { - LLMutexLock locker(mMutex); - mDecompositionRequests.insert(incomplete.begin(), incomplete.end()); - } - } - - // holding lock, final list - if (!mPhysicsShapeRequests.empty()) - { - std::set incomplete; - while (!mPhysicsShapeRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) - { - mMutex->lock(); - std::set::iterator iter = mPhysicsShapeRequests.begin(); - UUIDBasedRequest req = *iter; - mPhysicsShapeRequests.erase(iter); - mMutex->unlock(); - if (req.isDelayed()) - { - incomplete.insert(req); - } - else if (!fetchMeshPhysicsShape(req.mId)) - { - if (req.canRetry()) - { - req.updateTime(); - incomplete.insert(req); - } - else - { - LL_DEBUGS() << "mPhysicsShapeRequests failed: " << req.mId << LL_ENDL; - } - } - } - - if (!incomplete.empty()) - { - LLMutexLock locker(mMutex); - mPhysicsShapeRequests.insert(incomplete.begin(), incomplete.end()); - } - } - } - - // For dev purposes only. A dynamic change could make this false - // and that shouldn't assert. - // llassert_always(mHttpRequestSet.size() <= sRequestHighWater); - } - - if (mSignal->isLocked()) - { //make sure to let go of the mutex associated with the given signal before shutting down - mSignal->unlock(); - } - - res = LLConvexDecomposition::quitThread(); - if (res != LLCD_OK && LLConvexDecomposition::isFunctional()) - { - LL_WARNS(LOG_MESH) << "Convex decomposition unable to be quit." << LL_ENDL; - } -} - -// Mutex: LLMeshRepoThread::mMutex must be held on entry -void LLMeshRepoThread::loadMeshSkinInfo(const LLUUID& mesh_id) -{ - mSkinRequests.push_back(UUIDBasedRequest(mesh_id)); -} - -// Mutex: LLMeshRepoThread::mMutex must be held on entry -void LLMeshRepoThread::loadMeshDecomposition(const LLUUID& mesh_id) -{ - mDecompositionRequests.insert(UUIDBasedRequest(mesh_id)); -} - -// Mutex: LLMeshRepoThread::mMutex must be held on entry -void LLMeshRepoThread::loadMeshPhysicsShape(const LLUUID& mesh_id) -{ - mPhysicsShapeRequests.insert(UUIDBasedRequest(mesh_id)); -} - -void LLMeshRepoThread::lockAndLoadMeshLOD(const LLVolumeParams& mesh_params, S32 lod) -{ - if (!LLAppViewer::isExiting()) - { - loadMeshLOD(mesh_params, lod); - } -} - - -void LLMeshRepoThread::loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod) -{ //could be called from any thread - const LLUUID& mesh_id = mesh_params.getSculptID(); - LLMutexLock lock(mMutex); - LLMutexLock header_lock(mHeaderMutex); - mesh_header_map::iterator iter = mMeshHeader.find(mesh_id); - if (iter != mMeshHeader.end()) - { //if we have the header, request LOD byte range - - LODRequest req(mesh_params, lod); - { - mLODReqQ.push(req); - LLMeshRepository::sLODProcessing++; - } - } - else - { - HeaderRequest req(mesh_params); - pending_lod_map::iterator pending = mPendingLOD.find(mesh_id); - - if (pending != mPendingLOD.end()) - { //append this lod request to existing header request - pending->second.push_back(lod); - llassert(pending->second.size() <= LLModel::NUM_LODS); - } - else - { //if no header request is pending, fetch header - mHeaderReqQ.push(req); - mPendingLOD[mesh_id].push_back(lod); - } - } -} - -// Mutex: must be holding mMutex when called -void LLMeshRepoThread::setGetMeshCap(const std::string & mesh_cap) -{ - mGetMeshCapability = mesh_cap; -} - - -// Constructs a Cap URL for the mesh. Prefers a GetMesh2 cap -// over a GetMesh cap. -// -// Mutex: acquires mMutex -void LLMeshRepoThread::constructUrl(LLUUID mesh_id, std::string * url) -{ - std::string res_url; - - if (gAgent.getRegion()) - { - { - LLMutexLock lock(mMutex); - res_url = mGetMeshCapability; - } - - if (!res_url.empty()) - { - res_url += "/?mesh_id="; - res_url += mesh_id.asString().c_str(); - } - else - { - LL_WARNS_ONCE(LOG_MESH) << "Current region does not have ViewerAsset capability! Cannot load meshes. Region id: " - << gAgent.getRegion()->getRegionID() << LL_ENDL; - LL_DEBUGS_ONCE(LOG_MESH) << "Cannot load mesh " << mesh_id << " due to missing capability." << LL_ENDL; - } - } - else - { - LL_WARNS_ONCE(LOG_MESH) << "Current region is not loaded so there is no capability to load from! Cannot load meshes." << LL_ENDL; - LL_DEBUGS_ONCE(LOG_MESH) << "Cannot load mesh " << mesh_id << " due to missing capability." << LL_ENDL; - } - - *url = res_url; -} - -// Issue an HTTP GET request with byte range using the right -// policy class. -// -// @return Valid handle or LLCORE_HTTP_HANDLE_INVALID. -// If the latter, actual status is found in -// mHttpStatus member which is valid until the -// next call to this method. -// -// Thread: repo -LLCore::HttpHandle LLMeshRepoThread::getByteRange(const std::string & url, - size_t offset, size_t len, - const LLCore::HttpHandler::ptr_t &handler) -{ - // Also used in lltexturefetch.cpp - static LLCachedControl disable_range_req(gSavedSettings, "HttpRangeRequestsDisable", false); - - LLCore::HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); - - if (len < LARGE_MESH_FETCH_THRESHOLD) - { - handle = mHttpRequest->requestGetByteRange( mHttpPolicyClass, - url, - (disable_range_req ? size_t(0) : offset), - (disable_range_req ? size_t(0) : len), - mHttpOptions, - mHttpHeaders, - handler); - if (LLCORE_HTTP_HANDLE_INVALID != handle) - { - ++LLMeshRepository::sHTTPRequestCount; - } - } - else - { - handle = mHttpRequest->requestGetByteRange(mHttpLargePolicyClass, - url, - (disable_range_req ? size_t(0) : offset), - (disable_range_req ? size_t(0) : len), - mHttpLargeOptions, - mHttpHeaders, - handler); - if (LLCORE_HTTP_HANDLE_INVALID != handle) - { - ++LLMeshRepository::sHTTPLargeRequestCount; - } - } - if (LLCORE_HTTP_HANDLE_INVALID == handle) - { - // Something went wrong, capture the error code for caller. - mHttpStatus = mHttpRequest->getStatus(); - } - return handle; -} - - -bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, bool can_retry) -{ - - if (!mHeaderMutex) - { - return false; - } - - mHeaderMutex->lock(); - - auto header_it = mMeshHeader.find(mesh_id); - if (header_it == mMeshHeader.end()) - { //we have no header info for this mesh, do nothing - mHeaderMutex->unlock(); - return false; - } - - ++LLMeshRepository::sMeshRequestCount; - bool ret = true; - U32 header_size = header_it->second.first; - - if (header_size > 0) - { - const LLMeshHeader& header = header_it->second.second; - - S32 version = header.mVersion; - S32 offset = header_size + header.mSkinOffset; - S32 size = header.mSkinSize; - - mHeaderMutex->unlock(); - - if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) - { - //check cache for mesh skin info - LLFileSystem file(mesh_id, LLAssetType::AT_MESH); - if (file.getSize() >= offset + size) - { - U8* buffer = new(std::nothrow) U8[size]; - if (!buffer) - { - LL_WARNS(LOG_MESH) << "Failed to allocate memory for skin info, size: " << size << LL_ENDL; - return false; - } - LLMeshRepository::sCacheBytesRead += size; - ++LLMeshRepository::sCacheReads; - file.seek(offset); - file.read(buffer, size); - - //make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written) - bool zero = true; - for (S32 i = 0; i < llmin(size, 1024) && zero; ++i) - { - zero = buffer[i] == 0; - } - - if (!zero) - { //attempt to parse - if (skinInfoReceived(mesh_id, buffer, size)) - { - delete[] buffer; - return true; - } - } - - delete[] buffer; - } - - //reading from cache failed for whatever reason, fetch from sim - std::string http_url; - constructUrl(mesh_id, &http_url); - - if (!http_url.empty()) - { - LLMeshHandlerBase::ptr_t handler(new LLMeshSkinInfoHandler(mesh_id, offset, size)); - LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); - if (LLCORE_HTTP_HANDLE_INVALID == handle) - { - LL_WARNS(LOG_MESH) << "HTTP GET request failed for skin info on mesh " << mID - << ". Reason: " << mHttpStatus.toString() - << " (" << mHttpStatus.toTerseString() << ")" - << LL_ENDL; - ret = false; - } - else if(can_retry) - { - handler->mHttpHandle = handle; - mHttpRequestSet.insert(handler); - } - else - { - LLMutexLock locker(mMutex); - mSkinUnavailableQ.emplace_back(mesh_id); - } - } - else - { - LLMutexLock locker(mMutex); - mSkinUnavailableQ.emplace_back(mesh_id); - } - } - else - { - LLMutexLock locker(mMutex); - mSkinUnavailableQ.emplace_back(mesh_id); - } - } - else - { - mHeaderMutex->unlock(); - } - - //early out was not hit, effectively fetched - return ret; -} - -bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) -{ - if (!mHeaderMutex) - { - return false; - } - - mHeaderMutex->lock(); - - auto header_it = mMeshHeader.find(mesh_id); - if (header_it == mMeshHeader.end()) - { //we have no header info for this mesh, do nothing - mHeaderMutex->unlock(); - return false; - } - - ++LLMeshRepository::sMeshRequestCount; - U32 header_size = header_it->second.first; - bool ret = true; - - if (header_size > 0) - { - const auto& header = header_it->second.second; - S32 version = header.mVersion; - S32 offset = header_size + header.mPhysicsConvexOffset; - S32 size = header.mPhysicsConvexSize; - - mHeaderMutex->unlock(); - - if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) - { - //check cache for mesh skin info - LLFileSystem file(mesh_id, LLAssetType::AT_MESH); - if (file.getSize() >= offset+size) - { - U8* buffer = new(std::nothrow) U8[size]; - if (!buffer) - { - LL_WARNS(LOG_MESH) << "Failed to allocate memory for mesh decomposition, size: " << size << LL_ENDL; - return false; - } - LLMeshRepository::sCacheBytesRead += size; - ++LLMeshRepository::sCacheReads; - - file.seek(offset); - file.read(buffer, size); - - //make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written) - bool zero = true; - for (S32 i = 0; i < llmin(size, 1024) && zero; ++i) - { - zero = buffer[i] == 0; - } - - if (!zero) - { //attempt to parse - if (decompositionReceived(mesh_id, buffer, size)) - { - delete[] buffer; - return true; - } - } - - delete[] buffer; - } - - //reading from cache failed for whatever reason, fetch from sim - std::string http_url; - constructUrl(mesh_id, &http_url); - - if (!http_url.empty()) - { - LLMeshHandlerBase::ptr_t handler(new LLMeshDecompositionHandler(mesh_id, offset, size)); - LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); - if (LLCORE_HTTP_HANDLE_INVALID == handle) - { - LL_WARNS(LOG_MESH) << "HTTP GET request failed for decomposition mesh " << mID - << ". Reason: " << mHttpStatus.toString() - << " (" << mHttpStatus.toTerseString() << ")" - << LL_ENDL; - ret = false; - } - else - { - handler->mHttpHandle = handle; - mHttpRequestSet.insert(handler); - } - } - } - } - else - { - mHeaderMutex->unlock(); - } - - //early out was not hit, effectively fetched - return ret; -} - -bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) -{ - if (!mHeaderMutex) - { - return false; - } - - mHeaderMutex->lock(); - - auto header_it = mMeshHeader.find(mesh_id); - if (header_it == mMeshHeader.end()) - { //we have no header info for this mesh, do nothing - mHeaderMutex->unlock(); - return false; - } - - ++LLMeshRepository::sMeshRequestCount; - U32 header_size = header_it->second.first; - bool ret = true; - - if (header_size > 0) - { - const auto& header = header_it->second.second; - S32 version = header.mVersion; - S32 offset = header_size + header.mPhysicsMeshOffset; - S32 size = header.mPhysicsMeshSize; - - mHeaderMutex->unlock(); - - if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) - { - //check cache for mesh physics shape info - LLFileSystem file(mesh_id, LLAssetType::AT_MESH); - if (file.getSize() >= offset+size) - { - LLMeshRepository::sCacheBytesRead += size; - ++LLMeshRepository::sCacheReads; - file.seek(offset); - U8* buffer = new(std::nothrow) U8[size]; - if (!buffer) - { - LL_WARNS(LOG_MESH) << "Failed to allocate memory for physics shape, size: " << size << LL_ENDL; - return false; - } - file.read(buffer, size); - - //make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written) - bool zero = true; - for (S32 i = 0; i < llmin(size, 1024) && zero; ++i) - { - zero = buffer[i] == 0; - } - - if (!zero) - { //attempt to parse - if (physicsShapeReceived(mesh_id, buffer, size) == MESH_OK) - { - delete[] buffer; - return true; - } - } - - delete[] buffer; - } - - //reading from cache failed for whatever reason, fetch from sim - std::string http_url; - constructUrl(mesh_id, &http_url); - - if (!http_url.empty()) - { - LLMeshHandlerBase::ptr_t handler(new LLMeshPhysicsShapeHandler(mesh_id, offset, size)); - LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); - if (LLCORE_HTTP_HANDLE_INVALID == handle) - { - LL_WARNS(LOG_MESH) << "HTTP GET request failed for physics shape on mesh " << mID - << ". Reason: " << mHttpStatus.toString() - << " (" << mHttpStatus.toTerseString() << ")" - << LL_ENDL; - ret = false; - } - else - { - handler->mHttpHandle = handle; - mHttpRequestSet.insert(handler); - } - } - } - else - { //no physics shape whatsoever, report back NULL - physicsShapeReceived(mesh_id, NULL, 0); - } - } - else - { - mHeaderMutex->unlock(); - } - - //early out was not hit, effectively fetched - return ret; -} - -//static -void LLMeshRepoThread::incActiveLODRequests() -{ - LLMutexLock lock(gMeshRepo.mThread->mMutex); - ++LLMeshRepoThread::sActiveLODRequests; -} - -//static -void LLMeshRepoThread::decActiveLODRequests() -{ - LLMutexLock lock(gMeshRepo.mThread->mMutex); - --LLMeshRepoThread::sActiveLODRequests; -} - -//static -void LLMeshRepoThread::incActiveHeaderRequests() -{ - LLMutexLock lock(gMeshRepo.mThread->mMutex); - ++LLMeshRepoThread::sActiveHeaderRequests; -} - -//static -void LLMeshRepoThread::decActiveHeaderRequests() -{ - LLMutexLock lock(gMeshRepo.mThread->mMutex); - --LLMeshRepoThread::sActiveHeaderRequests; -} - -//return false if failed to get header -bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, bool can_retry) -{ - ++LLMeshRepository::sMeshRequestCount; - - { - //look for mesh in asset in cache - LLFileSystem file(mesh_params.getSculptID(), LLAssetType::AT_MESH); - - S32 size = file.getSize(); - - if (size > 0) - { - // *NOTE: if the header size is ever more than 4KB, this will break - U8 buffer[MESH_HEADER_SIZE]; - S32 bytes = llmin(size, MESH_HEADER_SIZE); - LLMeshRepository::sCacheBytesRead += bytes; - ++LLMeshRepository::sCacheReads; - file.read(buffer, bytes); - if (headerReceived(mesh_params, buffer, bytes) == MESH_OK) - { - std::string mid; - mesh_params.getSculptID().toString(mid); - LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh header for ID " << mid << " - was retrieved from the cache." << LL_ENDL; - - // Found mesh in cache - return true; - } - } - } - - //either cache entry doesn't exist or is corrupt, request header from simulator - bool retval = true; - std::string http_url; - constructUrl(mesh_params.getSculptID(), &http_url); - - - if (!http_url.empty()) - { - std::string mid; - mesh_params.getSculptID().toString(mid); - LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh header for ID " << mid << " - was retrieved from the simulator." << LL_ENDL; - - //grab first 4KB if we're going to bother with a fetch. Cache will prevent future fetches if a full mesh fits - //within the first 4KB - //NOTE -- this will break of headers ever exceed 4KB - - LLMeshHandlerBase::ptr_t handler(new LLMeshHeaderHandler(mesh_params, 0, MESH_HEADER_SIZE)); - LLCore::HttpHandle handle = getByteRange(http_url, 0, MESH_HEADER_SIZE, handler); - if (LLCORE_HTTP_HANDLE_INVALID == handle) - { - LL_WARNS(LOG_MESH) << "HTTP GET request failed for mesh header " << mID - << ". Reason: " << mHttpStatus.toString() - << " (" << mHttpStatus.toTerseString() << ")" - << LL_ENDL; - retval = false; - } - else if (can_retry) - { - handler->mHttpHandle = handle; - mHttpRequestSet.insert(handler); - } - } - - return retval; -} - -//return false if failed to get mesh lod. -bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, bool can_retry) -{ - if (!mHeaderMutex) - { - return false; - } - - const LLUUID& mesh_id = mesh_params.getSculptID(); - - mHeaderMutex->lock(); - auto header_it = mMeshHeader.find(mesh_id); - if (header_it == mMeshHeader.end()) - { //we have no header info for this mesh, do nothing - mHeaderMutex->unlock(); - return false; - } - ++LLMeshRepository::sMeshRequestCount; - bool retval = true; - - U32 header_size = header_it->second.first; - if (header_size > 0) - { - const auto& header = header_it->second.second; - S32 version = header.mVersion; - S32 offset = header_size + header.mLodOffset[lod]; - S32 size = header.mLodSize[lod]; - mHeaderMutex->unlock(); - - if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) - { - - //check cache for mesh asset - LLFileSystem file(mesh_id, LLAssetType::AT_MESH); - if (file.getSize() >= offset+size) - { - U8* buffer = new(std::nothrow) U8[size]; - if (!buffer) - { - LL_WARNS(LOG_MESH) << "Can't allocate memory for mesh " << mesh_id << " LOD " << lod << ", size: " << size << LL_ENDL; - // todo: for now it will result in indefinite constant retries, should result in timeout - // or in retry-count and disabling mesh. (but usually viewer is beyond saving at this point) - return false; - } - LLMeshRepository::sCacheBytesRead += size; - ++LLMeshRepository::sCacheReads; - file.seek(offset); - file.read(buffer, size); - - //make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written) - bool zero = true; - for (S32 i = 0; i < llmin(size, 1024) && zero; ++i) - { - zero = buffer[i] == 0; - } - - if (!zero) - { //attempt to parse - if (lodReceived(mesh_params, lod, buffer, size) == MESH_OK) - { - delete[] buffer; - - std::string mid; - mesh_id.toString(mid); - LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh body for ID " << mid << " - was retrieved from the cache." << LL_ENDL; - - return true; - } - } - - delete[] buffer; - } - - //reading from cache failed for whatever reason, fetch from sim - std::string http_url; - constructUrl(mesh_id, &http_url); - - if (!http_url.empty()) - { - std::string mid; - mesh_id.toString(mid); - LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh body for ID " << mid << " - was retrieved from the simulator." << LL_ENDL; - - LLMeshHandlerBase::ptr_t handler(new LLMeshLODHandler(mesh_params, lod, offset, size)); - LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); - if (LLCORE_HTTP_HANDLE_INVALID == handle) - { - LL_WARNS(LOG_MESH) << "HTTP GET request failed for LOD on mesh " << mID - << ". Reason: " << mHttpStatus.toString() - << " (" << mHttpStatus.toTerseString() << ")" - << LL_ENDL; - retval = false; - } - else if (can_retry) - { - handler->mHttpHandle = handle; - mHttpRequestSet.insert(handler); - // *NOTE: Allowing a re-request, not marking as unavailable. Is that correct? - } - else - { - LLMutexLock lock(mMutex); - mUnavailableQ.push_back(LODRequest(mesh_params, lod)); - } - } - else - { - LLMutexLock lock(mMutex); - mUnavailableQ.push_back(LODRequest(mesh_params, lod)); - } - } - else - { - LLMutexLock lock(mMutex); - mUnavailableQ.push_back(LODRequest(mesh_params, lod)); - } - } - else - { - mHeaderMutex->unlock(); - } - - return retval; -} - -EMeshProcessingResult LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size) -{ - const LLUUID mesh_id = mesh_params.getSculptID(); - LLSD header_data; - - LLMeshHeader header; - - llssize header_size = 0; - if (data_size > 0) - { - llssize dsize = data_size; - char* result_ptr = strip_deprecated_header((char*)data, dsize, &header_size); - - data_size = dsize; - - boost::iostreams::stream stream(result_ptr, data_size); - - if (!LLSDSerialize::fromBinary(header_data, stream, data_size)) - { - LL_WARNS(LOG_MESH) << "Mesh header parse error. Not a valid mesh asset! ID: " << mesh_id - << LL_ENDL; - return MESH_PARSE_FAILURE; - } - - if (!header_data.isMap()) - { - LL_WARNS(LOG_MESH) << "Mesh header is invalid for ID: " << mesh_id << LL_ENDL; - return MESH_INVALID; - } - - header.fromLLSD(header_data); - - if (header.mVersion > MAX_MESH_VERSION) - { - LL_INFOS(LOG_MESH) << "Wrong version in header for " << mesh_id << LL_ENDL; - header.m404 = true; - } - // make sure there is at least one lod, function returns -1 and marks as 404 otherwise - else if (LLMeshRepository::getActualMeshLOD(header, 0) >= 0) - { - header_size += stream.tellg(); - } - } - else - { - LL_INFOS(LOG_MESH) << "Non-positive data size. Marking header as non-existent, will not retry. ID: " << mesh_id - << LL_ENDL; - header.m404 = 1; - } - - { - - { - LLMutexLock lock(mHeaderMutex); - mMeshHeader[mesh_id] = { header_size, header }; - LLMeshRepository::sCacheBytesHeaders += header_size; - } - - LLMutexLock lock(mMutex); // make sure only one thread access mPendingLOD at the same time. - - //check for pending requests - pending_lod_map::iterator iter = mPendingLOD.find(mesh_id); - if (iter != mPendingLOD.end()) - { - for (U32 i = 0; i < iter->second.size(); ++i) - { - LODRequest req(mesh_params, iter->second[i]); - mLODReqQ.push(req); - LLMeshRepository::sLODProcessing++; - } - mPendingLOD.erase(iter); - } - } - - return MESH_OK; -} - -EMeshProcessingResult LLMeshRepoThread::lodReceived(const LLVolumeParams& mesh_params, S32 lod, U8* data, S32 data_size) -{ - if (data == NULL || data_size == 0) - { - return MESH_NO_DATA; - } - - LLPointer volume = new LLVolume(mesh_params, LLVolumeLODGroup::getVolumeScaleFromDetail(lod)); - if (volume->unpackVolumeFaces(data, data_size)) - { - if (volume->getNumFaces() > 0) - { - LoadedMesh mesh(volume, mesh_params, lod); - { - LLMutexLock lock(mMutex); - mLoadedQ.push_back(mesh); - // LLPointer is not thread safe, since we added this pointer into - // threaded list, make sure counter gets decreased inside mutex lock - // and won't affect mLoadedQ processing - volume = NULL; - // might be good idea to turn mesh into pointer to avoid making a copy - mesh.mVolume = NULL; - } - return MESH_OK; - } - } - - return MESH_UNKNOWN; -} - -bool LLMeshRepoThread::skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 data_size) -{ - LLSD skin; - - if (data_size > 0) - { - try - { - U32 uzip_result = LLUZipHelper::unzip_llsd(skin, data, data_size); - if (uzip_result != LLUZipHelper::ZR_OK) - { - LL_WARNS(LOG_MESH) << "Mesh skin info parse error. Not a valid mesh asset! ID: " << mesh_id - << " uzip result" << uzip_result - << LL_ENDL; - return false; - } - } - catch (std::bad_alloc&) - { - LL_WARNS(LOG_MESH) << "Out of memory for mesh ID " << mesh_id << " of size: " << data_size << LL_ENDL; - return false; - } - } - - { - LLMeshSkinInfo* info = nullptr; - try - { - info = new LLMeshSkinInfo(mesh_id, skin); - } - catch (const std::bad_alloc& ex) - { - LL_WARNS() << "Failed to allocate skin info with exception: " << ex.what() << LL_ENDL; - return false; - } - - // LL_DEBUGS(LOG_MESH) << "info pelvis offset" << info.mPelvisOffset << LL_ENDL; - { - LLMutexLock lock(mMutex); - mSkinInfoQ.push_back(info); - } - } - - return true; -} - -bool LLMeshRepoThread::decompositionReceived(const LLUUID& mesh_id, U8* data, S32 data_size) -{ - LLSD decomp; - - if (data_size > 0) - { - try - { - U32 uzip_result = LLUZipHelper::unzip_llsd(decomp, data, data_size); - if (uzip_result != LLUZipHelper::ZR_OK) - { - LL_WARNS(LOG_MESH) << "Mesh decomposition parse error. Not a valid mesh asset! ID: " << mesh_id - << " uzip result: " << uzip_result - << LL_ENDL; - return false; - } - } - catch (const std::bad_alloc&) - { - LL_WARNS(LOG_MESH) << "Out of memory for mesh ID " << mesh_id << " of size: " << data_size << LL_ENDL; - return false; - } - } - - { - LLModel::Decomposition* d = new LLModel::Decomposition(decomp); - d->mMeshID = mesh_id; - { - LLMutexLock lock(mMutex); - mDecompositionQ.push_back(d); - } - } - - return true; -} - -EMeshProcessingResult LLMeshRepoThread::physicsShapeReceived(const LLUUID& mesh_id, U8* data, S32 data_size) -{ - LLSD physics_shape; - - LLModel::Decomposition* d = new LLModel::Decomposition(); - d->mMeshID = mesh_id; - - if (data == NULL) - { //no data, no physics shape exists - d->mPhysicsShapeMesh.clear(); - } - else - { - LLVolumeParams volume_params; - volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE); - volume_params.setSculptID(mesh_id, LL_SCULPT_TYPE_MESH); - LLPointer volume = new LLVolume(volume_params,0); - - if (volume->unpackVolumeFaces(data, data_size)) - { - d->mPhysicsShapeMesh.clear(); - - std::vector& pos = d->mPhysicsShapeMesh.mPositions; - std::vector& norm = d->mPhysicsShapeMesh.mNormals; - - for (S32 i = 0; i < volume->getNumVolumeFaces(); ++i) - { - const LLVolumeFace& face = volume->getVolumeFace(i); - - for (S32 i = 0; i < face.mNumIndices; ++i) - { - U16 idx = face.mIndices[i]; - - pos.push_back(LLVector3(face.mPositions[idx].getF32ptr())); - norm.push_back(LLVector3(face.mNormals[idx].getF32ptr())); - } - } - } - } - - { - LLMutexLock lock(mMutex); - mDecompositionQ.push_back(d); - } - return MESH_OK; -} - -LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data, LLVector3& scale, bool upload_textures, - bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, - const std::string & upload_url, bool do_upload, - LLHandle fee_observer, - LLHandle upload_observer) - : LLThread("mesh upload"), - LLCore::HttpHandler(), - mDiscarded(false), - mDoUpload(do_upload), - mWholeModelUploadURL(upload_url), - mFeeObserverHandle(fee_observer), - mUploadObserverHandle(upload_observer) -{ - mInstanceList = data; - mUploadTextures = upload_textures; - mUploadSkin = upload_skin; - mUploadJoints = upload_joints; - mLockScaleIfJointPosition = lock_scale_if_joint_position; - mMutex = new LLMutex(); - mPendingUploads = 0; - mFinished = false; - mOrigin = gAgent.getPositionAgent(); - mHost = gAgent.getRegionHost(); - - mWholeModelFeeCapability = gAgent.getRegionCapability("NewFileAgentInventory"); - - mOrigin += gAgent.getAtAxis() * scale.magVec(); - - mMeshUploadTimeOut = gSavedSettings.getS32("MeshUploadTimeOut"); - - mHttpRequest = new LLCore::HttpRequest; - mHttpOptions = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); - mHttpOptions->setTransferTimeout(mMeshUploadTimeOut); - mHttpOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter")); - mHttpOptions->setRetries(UPLOAD_RETRY_LIMIT); - mHttpHeaders = LLCore::HttpHeaders::ptr_t(new LLCore::HttpHeaders); - mHttpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_LLSD_XML); - mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_UPLOADS); -} - -LLMeshUploadThread::~LLMeshUploadThread() -{ - delete mHttpRequest; - mHttpRequest = NULL; - delete mMutex; - mMutex = NULL; - -} - -LLMeshUploadThread::DecompRequest::DecompRequest(LLModel* mdl, LLModel* base_model, LLMeshUploadThread* thread) -{ - mStage = "single_hull"; - mModel = mdl; - mDecompID = &mdl->mDecompID; - mBaseModel = base_model; - mThread = thread; - - //copy out positions and indices - assignData(mdl) ; - - mThread->mFinalDecomp = this; - mThread->mPhysicsComplete = false; -} - -void LLMeshUploadThread::DecompRequest::completed() -{ - if (mThread->mFinalDecomp == this) - { - mThread->mPhysicsComplete = true; - } - - llassert(mHull.size() == 1); - - mThread->mHullMap[mBaseModel] = mHull[0]; -} - -//called in the main thread. -void LLMeshUploadThread::preStart() -{ - //build map of LLModel refs to instances for callbacks - for (instance_list::iterator iter = mInstanceList.begin(); iter != mInstanceList.end(); ++iter) - { - mInstance[iter->mModel].push_back(*iter); - } -} - -void LLMeshUploadThread::discard() -{ - LLMutexLock lock(mMutex); - mDiscarded = true; -} - -bool LLMeshUploadThread::isDiscarded() const -{ - LLMutexLock lock(mMutex); - return mDiscarded; -} - -void LLMeshUploadThread::run() -{ - if (mDoUpload) - { - doWholeModelUpload(); - } - else - { - requestWholeModelFee(); - } -} - -void dump_llsd_to_file(const LLSD& content, std::string filename) -{ - if (gSavedSettings.getBOOL("MeshUploadLogXML")) - { - llofstream of(filename.c_str()); - LLSDSerialize::toPrettyXML(content,of); - } -} - -LLSD llsd_from_file(std::string filename) -{ - llifstream ifs(filename.c_str()); - LLSD result; - LLSDSerialize::fromXML(result,ifs); - return result; -} - -void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) -{ - LLSD result; - - LLSD res; - result["folder_id"] = gInventory.findUserDefinedCategoryUUIDForType(LLFolderType::FT_OBJECT); - result["texture_folder_id"] = gInventory.findUserDefinedCategoryUUIDForType(LLFolderType::FT_TEXTURE); - result["asset_type"] = "mesh"; - result["inventory_type"] = "object"; - result["description"] = "(No Description)"; - result["next_owner_mask"] = LLSD::Integer(LLFloaterPerms::getNextOwnerPerms("Uploads")); - result["group_mask"] = LLSD::Integer(LLFloaterPerms::getGroupPerms("Uploads")); - result["everyone_mask"] = LLSD::Integer(LLFloaterPerms::getEveryonePerms("Uploads")); - - res["mesh_list"] = LLSD::emptyArray(); - res["texture_list"] = LLSD::emptyArray(); - res["instance_list"] = LLSD::emptyArray(); - S32 mesh_num = 0; - S32 texture_num = 0; - - std::set textures; - std::map texture_index; - - std::map mesh_index; - std::string model_name; - - S32 instance_num = 0; - - for (instance_map::iterator iter = mInstance.begin(); iter != mInstance.end(); ++iter) - { - LLMeshUploadData data; - data.mBaseModel = iter->first; - - if (data.mBaseModel->mSubmodelID) - { - // These are handled below to insure correct parenting order on creation - // due to map walking being based on model address (aka random) - continue; - } - - LLModelInstance& first_instance = *(iter->second.begin()); - for (S32 i = 0; i < 5; i++) - { - data.mModel[i] = first_instance.mLOD[i]; - } - - if (mesh_index.find(data.mBaseModel) == mesh_index.end()) - { - // Have not seen this model before - create a new mesh_list entry for it. - if (model_name.empty()) - { - model_name = data.mBaseModel->getName(); - } - - std::stringstream ostr; - - LLModel::Decomposition& decomp = - data.mModel[LLModel::LOD_PHYSICS].notNull() ? - data.mModel[LLModel::LOD_PHYSICS]->mPhysics : - data.mBaseModel->mPhysics; - - decomp.mBaseHull = mHullMap[data.mBaseModel]; - - LLSD mesh_header = LLModel::writeModel( - ostr, - data.mModel[LLModel::LOD_PHYSICS], - data.mModel[LLModel::LOD_HIGH], - data.mModel[LLModel::LOD_MEDIUM], - data.mModel[LLModel::LOD_LOW], - data.mModel[LLModel::LOD_IMPOSTOR], - decomp, - mUploadSkin, - mUploadJoints, - mLockScaleIfJointPosition, - false, - false, - data.mBaseModel->mSubmodelID); - - data.mAssetData = ostr.str(); - std::string str = ostr.str(); - - res["mesh_list"][mesh_num] = LLSD::Binary(str.begin(),str.end()); - mesh_index[data.mBaseModel] = mesh_num; - mesh_num++; - } - - // For all instances that use this model - for (instance_list::iterator instance_iter = iter->second.begin(); - instance_iter != iter->second.end(); - ++instance_iter) - { - - LLModelInstance& instance = *instance_iter; - - LLSD instance_entry; - - for (S32 i = 0; i < 5; i++) - { - data.mModel[i] = instance.mLOD[i]; - } - - LLVector3 pos, scale; - LLQuaternion rot; - LLMatrix4 transformation = instance.mTransform; - decomposeMeshMatrix(transformation,pos,rot,scale); - instance_entry["position"] = ll_sd_from_vector3(pos); - instance_entry["rotation"] = ll_sd_from_quaternion(rot); - instance_entry["scale"] = ll_sd_from_vector3(scale); - - instance_entry["material"] = LL_MCODE_WOOD; - instance_entry["physics_shape_type"] = data.mModel[LLModel::LOD_PHYSICS].notNull() ? (U8)(LLViewerObject::PHYSICS_SHAPE_PRIM) : (U8)(LLViewerObject::PHYSICS_SHAPE_CONVEX_HULL); - instance_entry["mesh"] = mesh_index[data.mBaseModel]; - instance_entry["mesh_name"] = instance.mLabel; - - instance_entry["face_list"] = LLSD::emptyArray(); - - // We want to be able to allow more than 8 materials... - // - S32 end = llmin((S32)instance.mMaterial.size(), instance.mModel->getNumVolumeFaces()) ; - - for (S32 face_num = 0; face_num < end; face_num++) - { - LLImportMaterial& material = instance.mMaterial[data.mBaseModel->mMaterialList[face_num]]; - LLSD face_entry = LLSD::emptyMap(); - - LLViewerFetchedTexture *texture = NULL; - - if (material.mDiffuseMapFilename.size()) - { - texture = FindViewerTexture(material); - } - - if ((texture != NULL) && - (textures.find(texture) == textures.end())) - { - textures.insert(texture); - } - - std::stringstream texture_str; - if (texture != NULL && include_textures && mUploadTextures) - { - if (texture->hasSavedRawImage()) - { - LLImageDataLock lock(texture->getSavedRawImage()); - - LLPointer upload_file = - LLViewerTextureList::convertToUploadFile(texture->getSavedRawImage()); - - if (!upload_file.isNull() && upload_file->getDataSize()) - { - texture_str.write((const char*) upload_file->getData(), upload_file->getDataSize()); - } - } - } - - if (texture != NULL && - mUploadTextures && - texture_index.find(texture) == texture_index.end()) - { - texture_index[texture] = texture_num; - std::string str = texture_str.str(); - res["texture_list"][texture_num] = LLSD::Binary(str.begin(),str.end()); - texture_num++; - } - - // Subset of TextureEntry fields. - if (texture != NULL && mUploadTextures) - { - face_entry["image"] = texture_index[texture]; - face_entry["scales"] = 1.0; - face_entry["scalet"] = 1.0; - face_entry["offsets"] = 0.0; - face_entry["offsett"] = 0.0; - face_entry["imagerot"] = 0.0; - } - face_entry["diffuse_color"] = ll_sd_from_color4(material.mDiffuseColor); - face_entry["fullbright"] = material.mFullbright; - instance_entry["face_list"][face_num] = face_entry; - } - - res["instance_list"][instance_num] = instance_entry; - instance_num++; - } - } - - for (instance_map::iterator iter = mInstance.begin(); iter != mInstance.end(); ++iter) - { - LLMeshUploadData data; - data.mBaseModel = iter->first; - - if (!data.mBaseModel->mSubmodelID) - { - // These were handled above already... - // - continue; - } - - LLModelInstance& first_instance = *(iter->second.begin()); - for (S32 i = 0; i < 5; i++) - { - data.mModel[i] = first_instance.mLOD[i]; - } - - if (mesh_index.find(data.mBaseModel) == mesh_index.end()) - { - // Have not seen this model before - create a new mesh_list entry for it. - if (model_name.empty()) - { - model_name = data.mBaseModel->getName(); - } - - std::stringstream ostr; - - LLModel::Decomposition& decomp = - data.mModel[LLModel::LOD_PHYSICS].notNull() ? - data.mModel[LLModel::LOD_PHYSICS]->mPhysics : - data.mBaseModel->mPhysics; - - decomp.mBaseHull = mHullMap[data.mBaseModel]; - - LLSD mesh_header = LLModel::writeModel( - ostr, - data.mModel[LLModel::LOD_PHYSICS], - data.mModel[LLModel::LOD_HIGH], - data.mModel[LLModel::LOD_MEDIUM], - data.mModel[LLModel::LOD_LOW], - data.mModel[LLModel::LOD_IMPOSTOR], - decomp, - mUploadSkin, - mUploadJoints, - mLockScaleIfJointPosition, - false, - false, - data.mBaseModel->mSubmodelID); - - data.mAssetData = ostr.str(); - std::string str = ostr.str(); - - res["mesh_list"][mesh_num] = LLSD::Binary(str.begin(),str.end()); - mesh_index[data.mBaseModel] = mesh_num; - mesh_num++; - } - - // For all instances that use this model - for (instance_list::iterator instance_iter = iter->second.begin(); - instance_iter != iter->second.end(); - ++instance_iter) - { - - LLModelInstance& instance = *instance_iter; - - LLSD instance_entry; - - for (S32 i = 0; i < 5; i++) - { - data.mModel[i] = instance.mLOD[i]; - } - - LLVector3 pos, scale; - LLQuaternion rot; - LLMatrix4 transformation = instance.mTransform; - decomposeMeshMatrix(transformation,pos,rot,scale); - instance_entry["position"] = ll_sd_from_vector3(pos); - instance_entry["rotation"] = ll_sd_from_quaternion(rot); - instance_entry["scale"] = ll_sd_from_vector3(scale); - - instance_entry["material"] = LL_MCODE_WOOD; - instance_entry["physics_shape_type"] = (U8)(LLViewerObject::PHYSICS_SHAPE_NONE); - instance_entry["mesh"] = mesh_index[data.mBaseModel]; - - instance_entry["face_list"] = LLSD::emptyArray(); - - // We want to be able to allow more than 8 materials... - // - S32 end = llmin((S32)instance.mMaterial.size(), instance.mModel->getNumVolumeFaces()) ; - - for (S32 face_num = 0; face_num < end; face_num++) - { - LLImportMaterial& material = instance.mMaterial[data.mBaseModel->mMaterialList[face_num]]; - LLSD face_entry = LLSD::emptyMap(); - - LLViewerFetchedTexture *texture = NULL; - - if (material.mDiffuseMapFilename.size()) - { - texture = FindViewerTexture(material); - } - - if ((texture != NULL) && - (textures.find(texture) == textures.end())) - { - textures.insert(texture); - } - - std::stringstream texture_str; - if (texture != NULL && include_textures && mUploadTextures) - { - if (texture->hasSavedRawImage()) - { - LLImageDataLock lock(texture->getSavedRawImage()); - - LLPointer upload_file = - LLViewerTextureList::convertToUploadFile(texture->getSavedRawImage()); - - if (!upload_file.isNull() && upload_file->getDataSize()) - { - texture_str.write((const char*) upload_file->getData(), upload_file->getDataSize()); - } - } - } - - if (texture != NULL && - mUploadTextures && - texture_index.find(texture) == texture_index.end()) - { - texture_index[texture] = texture_num; - std::string str = texture_str.str(); - res["texture_list"][texture_num] = LLSD::Binary(str.begin(),str.end()); - texture_num++; - } - - // Subset of TextureEntry fields. - if (texture != NULL && mUploadTextures) - { - face_entry["image"] = texture_index[texture]; - face_entry["scales"] = 1.0; - face_entry["scalet"] = 1.0; - face_entry["offsets"] = 0.0; - face_entry["offsett"] = 0.0; - face_entry["imagerot"] = 0.0; - } - face_entry["diffuse_color"] = ll_sd_from_color4(material.mDiffuseColor); - face_entry["fullbright"] = material.mFullbright; - instance_entry["face_list"][face_num] = face_entry; - } - - res["instance_list"][instance_num] = instance_entry; - instance_num++; - } - } - - if (model_name.empty()) model_name = "mesh model"; - result["name"] = model_name; - res["metric"] = "MUT_Unspecified"; - result["asset_resources"] = res; - dump_llsd_to_file(result,make_dump_name("whole_model_",dump_num)); - - dest = result; -} - -void LLMeshUploadThread::generateHulls() -{ - bool has_valid_requests = false ; - - for (instance_map::iterator iter = mInstance.begin(); iter != mInstance.end(); ++iter) - { - LLMeshUploadData data; - data.mBaseModel = iter->first; - - LLModelInstance& instance = *(iter->second.begin()); - - for (S32 i = 0; i < 5; i++) - { - data.mModel[i] = instance.mLOD[i]; - } - - //queue up models for hull generation - LLModel* physics = NULL; - - if (data.mModel[LLModel::LOD_PHYSICS].notNull()) - { - physics = data.mModel[LLModel::LOD_PHYSICS]; - } - else if (data.mModel[LLModel::LOD_LOW].notNull()) - { - physics = data.mModel[LLModel::LOD_LOW]; - } - else if (data.mModel[LLModel::LOD_MEDIUM].notNull()) - { - physics = data.mModel[LLModel::LOD_MEDIUM]; - } - else - { - physics = data.mModel[LLModel::LOD_HIGH]; - } - - llassert(physics != NULL); - - DecompRequest* request = new DecompRequest(physics, data.mBaseModel, this); - if(request->isValid()) - { - gMeshRepo.mDecompThread->submitRequest(request); - has_valid_requests = true ; - } - } - - if (has_valid_requests) - { - // *NOTE: Interesting livelock condition on shutdown. If there - // is an upload request in generateHulls() when shutdown starts, - // the main thread isn't available to manage communication between - // the decomposition thread and the upload thread and this loop - // wouldn't complete in turn stalling the main thread. The check - // on isDiscarded() prevents that. - while (! mPhysicsComplete && ! isDiscarded()) - { - apr_sleep(100); - } - } -} - -void LLMeshUploadThread::doWholeModelUpload() -{ - LL_DEBUGS(LOG_MESH) << "Starting model upload. Instances: " << mInstance.size() << LL_ENDL; - - if (mWholeModelUploadURL.empty()) - { - LL_WARNS(LOG_MESH) << "Missing mesh upload capability, unable to upload, fee request failed." - << LL_ENDL; - } - else - { - generateHulls(); - LL_DEBUGS(LOG_MESH) << "Hull generation completed." << LL_ENDL; - - mModelData = LLSD::emptyMap(); - wholeModelToLLSD(mModelData, true); - LLSD body = mModelData["asset_resources"]; - - dump_llsd_to_file(body, make_dump_name("whole_model_body_", dump_num)); - - LLCore::HttpHandle handle = LLCoreHttpUtil::requestPostWithLLSD(mHttpRequest, - mHttpPolicyClass, - mWholeModelUploadURL, - body, - mHttpOptions, - mHttpHeaders, - LLCore::HttpHandler::ptr_t(this, &NoOpDeletor)); - if (LLCORE_HTTP_HANDLE_INVALID == handle) - { - mHttpStatus = mHttpRequest->getStatus(); - - LL_WARNS(LOG_MESH) << "Couldn't issue request for full model upload. Reason: " << mHttpStatus.toString() - << " (" << mHttpStatus.toTerseString() << ")" - << LL_ENDL; - } - else - { - U32 sleep_time(10); - - LL_DEBUGS(LOG_MESH) << "POST request issued." << LL_ENDL; - - mHttpRequest->update(0); - while (! LLApp::isExiting() && ! finished() && ! isDiscarded()) - { - ms_sleep(sleep_time); - sleep_time = llmin(250U, sleep_time + sleep_time); - mHttpRequest->update(0); - } - - if (isDiscarded()) - { - LL_DEBUGS(LOG_MESH) << "Mesh upload operation discarded." << LL_ENDL; - } - else - { - LL_DEBUGS(LOG_MESH) << "Mesh upload operation completed." << LL_ENDL; - } - } - } -} - -void LLMeshUploadThread::requestWholeModelFee() -{ - dump_num++; - - generateHulls(); - - mModelData = LLSD::emptyMap(); - wholeModelToLLSD(mModelData, false); - dump_llsd_to_file(mModelData, make_dump_name("whole_model_fee_request_", dump_num)); - LLCore::HttpHandle handle = LLCoreHttpUtil::requestPostWithLLSD(mHttpRequest, - mHttpPolicyClass, - mWholeModelFeeCapability, - mModelData, - mHttpOptions, - mHttpHeaders, - LLCore::HttpHandler::ptr_t(this, &NoOpDeletor)); - if (LLCORE_HTTP_HANDLE_INVALID == handle) - { - mHttpStatus = mHttpRequest->getStatus(); - - LL_WARNS(LOG_MESH) << "Couldn't issue request for model fee. Reason: " << mHttpStatus.toString() - << " (" << mHttpStatus.toTerseString() << ")" - << LL_ENDL; - } - else - { - U32 sleep_time(10); - - mHttpRequest->update(0); - while (! LLApp::isExiting() && ! finished() && ! isDiscarded()) - { - ms_sleep(sleep_time); - sleep_time = llmin(250U, sleep_time + sleep_time); - mHttpRequest->update(0); - } - if (isDiscarded()) - { - LL_DEBUGS(LOG_MESH) << "Mesh fee query operation discarded." << LL_ENDL; - } - } -} - - -// Does completion duty for both fee queries and actual uploads. -void LLMeshUploadThread::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) -{ - // QA/Devel: 0x2 to enable fake error import on upload, 0x1 on fee check - const S32 fake_error(gSavedSettings.getS32("MeshUploadFakeErrors") & (mDoUpload ? 0xa : 0x5)); - LLCore::HttpStatus status(response->getStatus()); - if (fake_error) - { - status = (fake_error & 0x0c) ? LLCore::HttpStatus(500) : LLCore::HttpStatus(200); - } - std::string reason(status.toString()); - LLSD body; - - mFinished = true; - - if (mDoUpload) - { - // model upload case - LLWholeModelUploadObserver * observer(mUploadObserverHandle.get()); - - if (! status) - { - LL_WARNS(LOG_MESH) << "Upload failed. Reason: " << reason - << " (" << status.toTerseString() << ")" - << LL_ENDL; - - // Build a fake body for the alert generator - body["error"] = LLSD::emptyMap(); - body["error"]["message"] = reason; - body["error"]["identifier"] = "NetworkError"; // from asset-upload/upload_util.py - log_upload_error(status, body, "upload", mModelData["name"].asString()); - - if (observer) - { - doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadFailure, observer)); - } - } - else - { - if (fake_error & 0x2) - { - body = llsd_from_file("fake_upload_error.xml"); - } - else - { - // *TODO: handle error in conversion process - LLCoreHttpUtil::responseToLLSD(response, true, body); - } - dump_llsd_to_file(body, make_dump_name("whole_model_upload_response_", dump_num)); - - if (body["state"].asString() == "complete") - { - // requested "mesh" asset type isn't actually the type - // of the resultant object, fix it up here. - mModelData["asset_type"] = "object"; - gMeshRepo.updateInventory(LLMeshRepository::inventory_data(mModelData, body)); - - if (observer) - { - doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadSuccess, observer)); - } - } - else - { - LL_WARNS(LOG_MESH) << "Upload failed. Not in expected 'complete' state." << LL_ENDL; - log_upload_error(status, body, "upload", mModelData["name"].asString()); - - if (observer) - { - doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadFailure, observer)); - } - } - } - } - else - { - // model fee case - LLWholeModelFeeObserver* observer(mFeeObserverHandle.get()); - mWholeModelUploadURL.clear(); - - if (! status) - { - LL_WARNS(LOG_MESH) << "Fee request failed. Reason: " << reason - << " (" << status.toTerseString() << ")" - << LL_ENDL; - - // Build a fake body for the alert generator - body["error"] = LLSD::emptyMap(); - body["error"]["message"] = reason; - body["error"]["identifier"] = "NetworkError"; // from asset-upload/upload_util.py - log_upload_error(status, body, "fee", mModelData["name"].asString()); - - if (observer) - { - observer->setModelPhysicsFeeErrorStatus(status.toULong(), reason, body["error"]); - } - } - else - { - if (fake_error & 0x1) - { - body = llsd_from_file("fake_upload_error.xml"); - } - else - { - // *TODO: handle error in conversion process - LLCoreHttpUtil::responseToLLSD(response, true, body); - } - dump_llsd_to_file(body, make_dump_name("whole_model_fee_response_", dump_num)); - - if (body["state"].asString() == "upload") - { - mWholeModelUploadURL = body["uploader"].asString(); - - if (observer) - { - body["data"]["upload_price"] = body["upload_price"]; - observer->onModelPhysicsFeeReceived(body["data"], mWholeModelUploadURL); - } - } - else - { - LL_WARNS(LOG_MESH) << "Fee request failed. Not in expected 'upload' state." << LL_ENDL; - log_upload_error(status, body, "fee", mModelData["name"].asString()); - - if (observer) - { - observer->setModelPhysicsFeeErrorStatus(status.toULong(), reason, body["error"]); - } - } - } - } -} - - -void LLMeshRepoThread::notifyLoadedMeshes() -{ - bool update_metrics(false); - - if (!mMutex) - { - return; - } - - if (!mLoadedQ.empty()) - { - std::deque loaded_queue; - - mMutex->lock(); - if (!mLoadedQ.empty()) - { - loaded_queue.swap(mLoadedQ); - mMutex->unlock(); - - update_metrics = true; - - // Process the elements free of the lock - for (const auto& mesh : loaded_queue) - { - if (mesh.mVolume->getNumVolumeFaces() > 0) - { - gMeshRepo.notifyMeshLoaded(mesh.mMeshParams, mesh.mVolume); - } - else - { - gMeshRepo.notifyMeshUnavailable(mesh.mMeshParams, - LLVolumeLODGroup::getVolumeDetailFromScale(mesh.mVolume->getDetail())); - } - } - } - } - - if (!mUnavailableQ.empty()) - { - std::deque unavil_queue; - - mMutex->lock(); - if (!mUnavailableQ.empty()) - { - unavil_queue.swap(mUnavailableQ); - mMutex->unlock(); - - update_metrics = true; - - // Process the elements free of the lock - for (const auto& req : unavil_queue) - { - gMeshRepo.notifyMeshUnavailable(req.mMeshParams, req.mLOD); - } - } - } - - if (!mSkinInfoQ.empty() || !mSkinUnavailableQ.empty() || ! mDecompositionQ.empty()) - { - if (mMutex->trylock()) - { - std::deque skin_info_q; - std::deque skin_info_unavail_q; - std::list decomp_q; - - if (! mSkinInfoQ.empty()) - { - skin_info_q.swap(mSkinInfoQ); - } - - if (! mSkinUnavailableQ.empty()) - { - skin_info_unavail_q.swap(mSkinUnavailableQ); - } - - if (! mDecompositionQ.empty()) - { - decomp_q.swap(mDecompositionQ); - } - - mMutex->unlock(); - - // Process the elements free of the lock - while (! skin_info_q.empty()) - { - gMeshRepo.notifySkinInfoReceived(skin_info_q.front()); - skin_info_q.pop_front(); - } - while (! skin_info_unavail_q.empty()) - { - gMeshRepo.notifySkinInfoUnavailable(skin_info_unavail_q.front().mId); - skin_info_unavail_q.pop_front(); - } - - while (! decomp_q.empty()) - { - gMeshRepo.notifyDecompositionReceived(decomp_q.front()); - decomp_q.pop_front(); - } - } - } - - if (update_metrics) - { - // Ping time-to-load metrics for mesh download operations. - LLMeshRepository::metricsProgress(0); - } - -} - -S32 LLMeshRepoThread::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod) -{ //only ever called from main thread - LLMutexLock lock(mHeaderMutex); - mesh_header_map::iterator iter = mMeshHeader.find(mesh_params.getSculptID()); - - if (iter != mMeshHeader.end()) - { - auto& header = iter->second.second; - - return LLMeshRepository::getActualMeshLOD(header, lod); - } - - return lod; -} - -//static -S32 LLMeshRepository::getActualMeshLOD(LLMeshHeader& header, S32 lod) -{ - lod = llclamp(lod, 0, 3); - - if (header.m404) - { - return -1; - } - - S32 version = header.mVersion; - - if (version > MAX_MESH_VERSION) - { - return -1; - } - - if (header.mLodSize[lod] > 0) - { - return lod; - } - - //search down to find the next available lower lod - for (S32 i = lod-1; i >= 0; --i) - { - if (header.mLodSize[i] > 0) - { - return i; - } - } - - //search up to find then ext available higher lod - for (S32 i = lod+1; i < LLVolumeLODGroup::NUM_LODS; ++i) - { - if (header.mLodSize[i] > 0) - { - return i; - } - } - - //header exists and no good lod found, treat as 404 - header.m404 = true; - - return -1; -} - -// Handle failed or successful requests for mesh assets. -// -// Support for 200 responses was added for several reasons. One, -// a service or cache can ignore range headers and give us a -// 200 with full asset should it elect to. We also support -// a debug flag which disables range requests for those very -// few users that have some sort of problem with their networking -// services. But the 200 response handling is suboptimal: rather -// than cache the whole asset, we just extract the part that would -// have been sent in a 206 and process that. Inefficient but these -// are cases far off the norm. -void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) -{ - mProcessed = true; - - unsigned int retries(0U); - response->getRetries(NULL, &retries); - LLMeshRepository::sHTTPRetryCount += retries; - - LLCore::HttpStatus status(response->getStatus()); - if (! status || MESH_HTTP_RESPONSE_FAILED) - { - processFailure(status); - ++LLMeshRepository::sHTTPErrorCount; - } - else - { - // From texture fetch code and may apply here: - // - // A warning about partial (HTTP 206) data. Some grid services - // do *not* return a 'Content-Range' header in the response to - // Range requests with a 206 status. We're forced to assume - // we get what we asked for in these cases until we can fix - // the services. - // - // May also need to deal with 200 status (full asset returned - // rather than partial) and 416 (request completely unsatisfyable). - // Always been exposed to these but are less likely here where - // speculative loads aren't done. - LLCore::BufferArray * body(response->getBody()); - S32 body_offset(0); - U8 * data(NULL); - S32 data_size(body ? body->size() : 0); - - if (data_size > 0) - { - static const LLCore::HttpStatus par_status(HTTP_PARTIAL_CONTENT); - - unsigned int offset(0), length(0), full_length(0); - - if (par_status == status) - { - // 206 case - response->getRange(&offset, &length, &full_length); - if (! offset && ! length) - { - // This is the case where we receive a 206 status but - // there wasn't a useful Content-Range header in the response. - // This could be because it was badly formatted but is more - // likely due to capabilities services which scrub headers - // from responses. Assume we got what we asked for...` - // length = data_size; - offset = mOffset; - } - } - else - { - // 200 case, typically - offset = 0; - } - - // *DEBUG: To test validation below - // offset += 1; - - // Validate that what we think we received is consistent with - // what we've asked for. I.e. first byte we wanted lies somewhere - // in the response. - if (offset > mOffset - || (offset + data_size) <= mOffset - || (mOffset - offset) >= data_size) - { - // No overlap with requested range. Fail request with - // suitable error. Shouldn't happen unless server/cache/ISP - // is doing something awful. - LL_WARNS(LOG_MESH) << "Mesh response (bytes [" - << offset << ".." << (offset + length - 1) - << "]) didn't overlap with request's origin (bytes [" - << mOffset << ".." << (mOffset + mRequestedBytes - 1) - << "])." << LL_ENDL; - processFailure(LLCore::HttpStatus(LLCore::HttpStatus::LLCORE, LLCore::HE_INV_CONTENT_RANGE_HDR)); - ++LLMeshRepository::sHTTPErrorCount; - goto common_exit; - } - - // *TODO: Try to get rid of data copying and add interfaces - // that support BufferArray directly. Introduce a two-phase - // handler, optional first that takes a body, fallback second - // that requires a temporary allocation and data copy. - body_offset = mOffset - offset; - data = new(std::nothrow) U8[data_size - body_offset]; - if (data) - { - body->read(body_offset, (char *) data, data_size - body_offset); - LLMeshRepository::sBytesReceived += data_size; - } - else - { - LL_WARNS(LOG_MESH) << "Failed to allocate " << data_size - body_offset << " memory for mesh response" << LL_ENDL; - processFailure(LLCore::HttpStatus(LLCore::HttpStatus::LLCORE, LLCore::HE_BAD_ALLOC)); - } - } - - processData(body, body_offset, data, data_size - body_offset); - - delete [] data; - } - - // Release handler -common_exit: - gMeshRepo.mThread->mHttpRequestSet.erase(this->shared_from_this()); -} - - -LLMeshHeaderHandler::~LLMeshHeaderHandler() -{ - if (!LLApp::isExiting()) - { - if (! mProcessed) - { - // something went wrong, retry - LL_WARNS(LOG_MESH) << "Mesh header fetch canceled unexpectedly, retrying." << LL_ENDL; - LLMeshRepoThread::HeaderRequest req(mMeshParams); - LLMutexLock lock(gMeshRepo.mThread->mMutex); - gMeshRepo.mThread->mHeaderReqQ.push(req); - } - LLMeshRepoThread::decActiveHeaderRequests(); - } -} - -void LLMeshHeaderHandler::processFailure(LLCore::HttpStatus status) -{ - LL_WARNS(LOG_MESH) << "Error during mesh header handling. ID: " << mMeshParams.getSculptID() - << ", Reason: " << status.toString() - << " (" << status.toTerseString() << "). Not retrying." - << LL_ENDL; - - // Can't get the header so none of the LODs will be available - LLMutexLock lock(gMeshRepo.mThread->mMutex); - for (int i(0); i < LLVolumeLODGroup::NUM_LODS; ++i) - { - gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, i)); - } -} - -void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, - U8 * data, S32 data_size) -{ - LLUUID mesh_id = mMeshParams.getSculptID(); - bool success = (!MESH_HEADER_PROCESS_FAILED) - && ((data != NULL) == (data_size > 0)); // if we have data but no size or have size but no data, something is wrong; - llassert(success); - EMeshProcessingResult res = MESH_UNKNOWN; - if (success) - { - res = gMeshRepo.mThread->headerReceived(mMeshParams, data, data_size); - success = (res == MESH_OK); - } - if (! success) - { - // *TODO: Get real reason for parse failure here. Might we want to retry? - LL_WARNS(LOG_MESH) << "Unable to parse mesh header. ID: " << mesh_id - << ", Size: " << data_size - << ", Reason: " << res << " Not retrying." - << LL_ENDL; - - // Can't get the header so none of the LODs will be available - LLMutexLock lock(gMeshRepo.mThread->mMutex); - for (int i(0); i < LLVolumeLODGroup::NUM_LODS; ++i) - { - gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, i)); - } - } - else if (data && data_size > 0) - { - // header was successfully retrieved from sim and parsed and is in cache - S32 header_bytes = 0; - LLMeshHeader header; - - gMeshRepo.mThread->mHeaderMutex->lock(); - LLMeshRepoThread::mesh_header_map::iterator iter = gMeshRepo.mThread->mMeshHeader.find(mesh_id); - if (iter != gMeshRepo.mThread->mMeshHeader.end()) - { - header_bytes = (S32)iter->second.first; - header = iter->second.second; - } - - if (header_bytes > 0 - && !header.m404 - && (header.mVersion <= MAX_MESH_VERSION)) - { - std::stringstream str; - - S32 lod_bytes = 0; - - for (U32 i = 0; i < LLModel::LOD_PHYSICS; ++i) - { - // figure out how many bytes we'll need to reserve in the file - lod_bytes = llmax(lod_bytes, header.mLodOffset[i]+header.mLodSize[i]); - } - - // just in case skin info or decomposition is at the end of the file (which it shouldn't be) - lod_bytes = llmax(lod_bytes, header.mSkinOffset+header.mSkinSize); - lod_bytes = llmax(lod_bytes, header.mPhysicsConvexOffset + header.mPhysicsConvexSize); - - // Do not unlock mutex untill we are done with LLSD. - // LLSD is smart and can work like smart pointer, is not thread safe. - gMeshRepo.mThread->mHeaderMutex->unlock(); - - S32 bytes = lod_bytes + header_bytes; - - - // It's possible for the remote asset to have more data than is needed for the local cache - // only allocate as much space in the cache as is needed for the local cache - data_size = llmin(data_size, bytes); - - // Fix asset caching - //LLFileSystem file(mesh_id, LLAssetType::AT_MESH, LLFileSystem::WRITE); - LLFileSystem file(mesh_id, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); - if (file.getMaxSize() >= bytes) - { - LLMeshRepository::sCacheBytesWritten += data_size; - ++LLMeshRepository::sCacheWrites; - - file.write(data, data_size); - - // Fix asset caching - S32 remaining = bytes - file.tell(); - if (remaining > 0) - { - U8* block = new(std::nothrow) U8[remaining]; - if (block) - { - memset(block, 0, remaining); - file.write(block, remaining); - delete[] block; - } - } - // - } - } - else - { - LL_WARNS(LOG_MESH) << "Trying to cache nonexistent mesh, mesh id: " << mesh_id << LL_ENDL; - - gMeshRepo.mThread->mHeaderMutex->unlock(); - - // headerReceived() parsed header, but header's data is invalid so none of the LODs will be available - LLMutexLock lock(gMeshRepo.mThread->mMutex); - for (int i(0); i < LLVolumeLODGroup::NUM_LODS; ++i) - { - gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, i)); - } - } - } -} - -LLMeshLODHandler::~LLMeshLODHandler() -{ - if (! LLApp::isExiting()) - { - if (! mProcessed) - { - LL_WARNS(LOG_MESH) << "Mesh LOD fetch canceled unexpectedly, retrying." << LL_ENDL; - gMeshRepo.mThread->lockAndLoadMeshLOD(mMeshParams, mLOD); - } - LLMeshRepoThread::decActiveLODRequests(); - } -} - -void LLMeshLODHandler::processFailure(LLCore::HttpStatus status) -{ - LL_WARNS(LOG_MESH) << "Error during mesh LOD handling. ID: " << mMeshParams.getSculptID() - << ", Reason: " << status.toString() - << " (" << status.toTerseString() << "). Not retrying." - << LL_ENDL; - - LLMutexLock lock(gMeshRepo.mThread->mMutex); - gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, mLOD)); -} - -void LLMeshLODHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, - U8 * data, S32 data_size) -{ - if ((!MESH_LOD_PROCESS_FAILED) - && ((data != NULL) == (data_size > 0))) // if we have data but no size or have size but no data, something is wrong - { - EMeshProcessingResult result = gMeshRepo.mThread->lodReceived(mMeshParams, mLOD, data, data_size); - if (result == MESH_OK) - { - // good fetch from sim, write to cache - // Fix asset caching - //LLFileSystem file(mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLFileSystem::WRITE); - LLFileSystem file(mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); - - S32 offset = mOffset; - S32 size = mRequestedBytes; - - if (file.getSize() >= offset+size) - { - file.seek(offset); - file.write(data, size); - LLMeshRepository::sCacheBytesWritten += size; - ++LLMeshRepository::sCacheWrites; - } - } - else - { - LL_WARNS(LOG_MESH) << "Error during mesh LOD processing. ID: " << mMeshParams.getSculptID() - << ", Reason: " << result - << " LOD: " << mLOD - << " Data size: " << data_size - << " Not retrying." - << LL_ENDL; - LLMutexLock lock(gMeshRepo.mThread->mMutex); - gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, mLOD)); - } - } - else - { - LL_WARNS(LOG_MESH) << "Error during mesh LOD processing. ID: " << mMeshParams.getSculptID() - << ", Unknown reason. Not retrying." - << " LOD: " << mLOD - << " Data size: " << data_size - << LL_ENDL; - LLMutexLock lock(gMeshRepo.mThread->mMutex); - gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, mLOD)); - } -} - -LLMeshSkinInfoHandler::~LLMeshSkinInfoHandler() -{ - if (!mProcessed) - { - LL_WARNS(LOG_MESH) << "deleting unprocessed request handler (may be ok on exit)" << LL_ENDL; - } -} - -void LLMeshSkinInfoHandler::processFailure(LLCore::HttpStatus status) -{ - LL_WARNS(LOG_MESH) << "Error during mesh skin info handling. ID: " << mMeshID - << ", Reason: " << status.toString() - << " (" << status.toTerseString() << "). Not retrying." - << LL_ENDL; - LLMutexLock lock(gMeshRepo.mThread->mMutex); - gMeshRepo.mThread->mSkinUnavailableQ.emplace_back(mMeshID); -} - -void LLMeshSkinInfoHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, - U8 * data, S32 data_size) -{ - if ((!MESH_SKIN_INFO_PROCESS_FAILED) - && ((data != NULL) == (data_size > 0)) // if we have data but no size or have size but no data, something is wrong - && gMeshRepo.mThread->skinInfoReceived(mMeshID, data, data_size)) - { - // good fetch from sim, write to cache - // Fix asset caching - //LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); - LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); - - S32 offset = mOffset; - S32 size = mRequestedBytes; - - if (file.getSize() >= offset+size) - { - LLMeshRepository::sCacheBytesWritten += size; - ++LLMeshRepository::sCacheWrites; - file.seek(offset); - file.write(data, size); - } - } - else - { - LL_WARNS(LOG_MESH) << "Error during mesh skin info processing. ID: " << mMeshID - << ", Unknown reason. Not retrying." - << LL_ENDL; - LLMutexLock lock(gMeshRepo.mThread->mMutex); - gMeshRepo.mThread->mSkinUnavailableQ.emplace_back(mMeshID); - } -} - -LLMeshDecompositionHandler::~LLMeshDecompositionHandler() -{ - if (!mProcessed) - { - LL_WARNS(LOG_MESH) << "deleting unprocessed request handler (may be ok on exit)" << LL_ENDL; - } -} - -void LLMeshDecompositionHandler::processFailure(LLCore::HttpStatus status) -{ - LL_WARNS(LOG_MESH) << "Error during mesh decomposition handling. ID: " << mMeshID - << ", Reason: " << status.toString() - << " (" << status.toTerseString() << "). Not retrying." - << LL_ENDL; - // *TODO: Mark mesh unavailable on error. For now, simply leave - // request unfulfilled rather than retry forever. -} - -void LLMeshDecompositionHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, - U8 * data, S32 data_size) -{ - if ((!MESH_DECOMP_PROCESS_FAILED) - && ((data != NULL) == (data_size > 0)) // if we have data but no size or have size but no data, something is wrong - && gMeshRepo.mThread->decompositionReceived(mMeshID, data, data_size)) - { - // good fetch from sim, write to cache - // Fix asset caching - //LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); - LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); - - S32 offset = mOffset; - S32 size = mRequestedBytes; - - if (file.getSize() >= offset+size) - { - LLMeshRepository::sCacheBytesWritten += size; - ++LLMeshRepository::sCacheWrites; - file.seek(offset); - file.write(data, size); - } - } - else - { - LL_WARNS(LOG_MESH) << "Error during mesh decomposition processing. ID: " << mMeshID - << ", Unknown reason. Not retrying." - << LL_ENDL; - // *TODO: Mark mesh unavailable on error - } -} - -LLMeshPhysicsShapeHandler::~LLMeshPhysicsShapeHandler() -{ - if (!mProcessed) - { - LL_WARNS(LOG_MESH) << "deleting unprocessed request handler (may be ok on exit)" << LL_ENDL; - } -} - -void LLMeshPhysicsShapeHandler::processFailure(LLCore::HttpStatus status) -{ - LL_WARNS(LOG_MESH) << "Error during mesh physics shape handling. ID: " << mMeshID - << ", Reason: " << status.toString() - << " (" << status.toTerseString() << "). Not retrying." - << LL_ENDL; - // *TODO: Mark mesh unavailable on error -} - -void LLMeshPhysicsShapeHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, - U8 * data, S32 data_size) -{ - if ((!MESH_PHYS_SHAPE_PROCESS_FAILED) - && ((data != NULL) == (data_size > 0)) // if we have data but no size or have size but no data, something is wrong - && gMeshRepo.mThread->physicsShapeReceived(mMeshID, data, data_size) == MESH_OK) - { - // good fetch from sim, write to cache for caching - // Fix asset caching - //LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); - LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); - - S32 offset = mOffset; - S32 size = mRequestedBytes; - - if (file.getSize() >= offset+size) - { - LLMeshRepository::sCacheBytesWritten += size; - ++LLMeshRepository::sCacheWrites; - file.seek(offset); - file.write(data, size); - } - } - else - { - LL_WARNS(LOG_MESH) << "Error during mesh physics shape processing. ID: " << mMeshID - << ", Unknown reason. Not retrying." - << LL_ENDL; - // *TODO: Mark mesh unavailable on error - } -} - -LLMeshRepository::LLMeshRepository() -: mMeshMutex(NULL), - mDecompThread(NULL), - mMeshThreadCount(0), - mThread(NULL) -{ - mSkinInfoCullTimer.resetWithExpiry(10.f); -} - -void LLMeshRepository::init() -{ - mMeshMutex = new LLMutex(); - - LLConvexDecomposition::getInstance()->initSystem(); - - if (!LLConvexDecomposition::isFunctional()) - { - LL_INFOS(LOG_MESH) << "Using STUB for LLConvexDecomposition" << LL_ENDL; - } - - mDecompThread = new LLPhysicsDecomp(); - mDecompThread->start(); - - while (!mDecompThread->mInited) - { //wait for physics decomp thread to init - apr_sleep(100); - } - - metrics_teleport_started_signal = LLViewerMessage::getInstance()->setTeleportStartedCallback(teleport_started); - - mThread = new LLMeshRepoThread(); - mThread->start(); -} - -void LLMeshRepository::shutdown() -{ - LL_INFOS(LOG_MESH) << "Shutting down mesh repository." << LL_ENDL; - llassert(mThread != NULL); - llassert(mThread->mSignal != NULL); - - metrics_teleport_started_signal.disconnect(); - - for (U32 i = 0; i < mUploads.size(); ++i) - { - LL_INFOS(LOG_MESH) << "Discard the pending mesh uploads." << LL_ENDL; - mUploads[i]->discard() ; //discard the uploading requests. - } - - mThread->mSignal->broadcast(); - - while (!mThread->isStopped()) - { - apr_sleep(10); - } - delete mThread; - mThread = NULL; - - for (U32 i = 0; i < mUploads.size(); ++i) - { - LL_INFOS(LOG_MESH) << "Waiting for pending mesh upload " << (i + 1) << "/" << mUploads.size() << LL_ENDL; - while (!mUploads[i]->isStopped()) - { - apr_sleep(10); - } - delete mUploads[i]; - } - - mUploads.clear(); - - delete mMeshMutex; - mMeshMutex = NULL; - - LL_INFOS(LOG_MESH) << "Shutting down decomposition system." << LL_ENDL; - - if (mDecompThread) - { - mDecompThread->shutdown(); - delete mDecompThread; - mDecompThread = NULL; - } - - LLConvexDecomposition::quitSystem(); -} - -//called in the main thread. -S32 LLMeshRepository::update() -{ - // Conditionally log a mesh metrics event - metricsUpdate(); - - if(mUploadWaitList.empty()) - { - return 0 ; - } - - S32 size = mUploadWaitList.size() ; - for (S32 i = 0; i < size; ++i) - { - mUploads.push_back(mUploadWaitList[i]); - mUploadWaitList[i]->preStart() ; - mUploadWaitList[i]->start() ; - } - mUploadWaitList.clear() ; - - return size ; -} - -void LLMeshRepository::unregisterMesh(LLVOVolume* vobj) -{ - for (auto& lod : mLoadingMeshes) - { - for (auto& param : lod) - { - vector_replace_with_last(param.second, vobj); - } - } - - for (auto& skin_pair : mLoadingSkins) - { - vector_replace_with_last(skin_pair.second, vobj); - } -} - -S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_params, S32 detail, S32 last_lod) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; //LL_LL_RECORD_BLOCK_TIME(FTM_MESH_FETCH); - - // Manage time-to-load metrics for mesh download operations. - metricsProgress(1); - - if (detail < 0 || detail >= LLVolumeLODGroup::NUM_LODS) - { - return detail; - } - - { - LLMutexLock lock(mMeshMutex); - //add volume to list of loading meshes - const auto& mesh_id = mesh_params.getSculptID(); - mesh_load_map::iterator iter = mLoadingMeshes[detail].find(mesh_id); - if (iter != mLoadingMeshes[detail].end()) - { //request pending for this mesh, append volume id to list - auto it = std::find(iter->second.begin(), iter->second.end(), vobj); - if (it == iter->second.end()) { - iter->second.push_back(vobj); - } - } - else - { - //first request for this mesh - mLoadingMeshes[detail][mesh_id].push_back(vobj); - mPendingRequests.push_back(LLMeshRepoThread::LODRequest(mesh_params, detail)); - LLMeshRepository::sLODPending++; - } - } - - //do a quick search to see if we can't display something while we wait for this mesh to load - LLVolume* volume = vobj->getVolume(); - - if (volume) - { - LLVolumeParams params = volume->getParams(); - - LLVolumeLODGroup* group = LLPrimitive::getVolumeManager()->getGroup(params); - - if (group) - { - //first, see if last_lod is available (don't transition down to avoid funny popping a la SH-641) - if (last_lod >= 0) - { - LLVolume* lod = group->refLOD(last_lod); - if (lod && lod->isMeshAssetLoaded() && lod->getNumVolumeFaces() > 0) - { - group->derefLOD(lod); - return last_lod; - } - group->derefLOD(lod); - } - - //next, see what the next lowest LOD available might be - for (S32 i = detail-1; i >= 0; --i) - { - LLVolume* lod = group->refLOD(i); - if (lod && lod->isMeshAssetLoaded() && lod->getNumVolumeFaces() > 0) - { - group->derefLOD(lod); - return i; - } - - group->derefLOD(lod); - } - - //no lower LOD is a available, is a higher lod available? - for (S32 i = detail+1; i < LLVolumeLODGroup::NUM_LODS; ++i) - { - LLVolume* lod = group->refLOD(i); - if (lod && lod->isMeshAssetLoaded() && lod->getNumVolumeFaces() > 0) - { - group->derefLOD(lod); - return i; - } - - group->derefLOD(lod); - } - } - } - - return detail; -} - -void LLMeshRepository::notifyLoadedMeshes() -{ //called from main thread - LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; //LL_RECORD_BLOCK_TIME(FTM_MESH_FETCH); - - // GetMesh2 operation with keepalives, etc. With pipelining, - // we'll increase this. See llappcorehttp and llcorehttp for - // discussion on connection strategies. - LLAppCoreHttp & app_core_http(LLAppViewer::instance()->getAppCoreHttp()); - S32 scale(app_core_http.isPipelined(LLAppCoreHttp::AP_MESH2) - ? (2 * LLAppCoreHttp::PIPELINING_DEPTH) - : 5); - - LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("Mesh2MaxConcurrentRequests"); - LLMeshRepoThread::sRequestHighWater = llclamp(scale * S32(LLMeshRepoThread::sMaxConcurrentRequests), - REQUEST2_HIGH_WATER_MIN, - REQUEST2_HIGH_WATER_MAX); - LLMeshRepoThread::sRequestLowWater = llclamp(LLMeshRepoThread::sRequestHighWater / 2, - REQUEST2_LOW_WATER_MIN, - REQUEST2_LOW_WATER_MAX); - - //clean up completed upload threads - for (std::vector::iterator iter = mUploads.begin(); iter != mUploads.end(); ) - { - LLMeshUploadThread* thread = *iter; - - if (thread->isStopped() && thread->finished()) - { - iter = mUploads.erase(iter); - delete thread; - } - else - { - ++iter; - } - } - - //update inventory - if (!mInventoryQ.empty()) - { - LLMutexLock lock(mMeshMutex); - while (!mInventoryQ.empty()) - { - inventory_data& data = mInventoryQ.front(); - - LLAssetType::EType asset_type = LLAssetType::lookup(data.mPostData["asset_type"].asString()); - LLInventoryType::EType inventory_type = LLInventoryType::lookup(data.mPostData["inventory_type"].asString()); - - // Handle addition of texture, if any. - if ( data.mResponse.has("new_texture_folder_id") ) - { - const LLUUID& new_folder_id = data.mResponse["new_texture_folder_id"].asUUID(); - - if ( new_folder_id.notNull() ) - { - LLUUID parent_id = gInventory.findUserDefinedCategoryUUIDForType(LLFolderType::FT_TEXTURE); - - std::string name; - // Check if the server built a different name for the texture folder - if ( data.mResponse.has("new_texture_folder_name") ) - { - name = data.mResponse["new_texture_folder_name"].asString(); - } - else - { - name = data.mPostData["name"].asString(); - } - - // Add the category to the internal representation - LLPointer cat = - new LLViewerInventoryCategory(new_folder_id, parent_id, - LLFolderType::FT_NONE, name, gAgent.getID()); - cat->setVersion(LLViewerInventoryCategory::VERSION_UNKNOWN); - - LLInventoryModel::LLCategoryUpdate update(cat->getParentUUID(), 1); - gInventory.accountForUpdate(update); - gInventory.updateCategory(cat); - } - } - - on_new_single_inventory_upload_complete( - asset_type, - inventory_type, - data.mPostData["asset_type"].asString(), - data.mPostData["folder_id"].asUUID(), - data.mPostData["name"], - data.mPostData["description"], - data.mResponse, - data.mResponse["upload_price"]); - //} - - mInventoryQ.pop(); - } - } - - //call completed callbacks on finished decompositions - mDecompThread->notifyCompleted(); - - if (mSkinInfoCullTimer.checkExpirationAndReset(10.f)) - { - //// Clean up dead skin info - //U64Bytes skinbytes(0); - for (auto iter = mSkinMap.begin(), ender = mSkinMap.end(); iter != ender;) - { - auto copy_iter = iter++; - - //skinbytes += U64Bytes(sizeof(LLMeshSkinInfo)); - //skinbytes += U64Bytes(copy_iter->second->mJointNames.size() * sizeof(std::string)); - //skinbytes += U64Bytes(copy_iter->second->mJointNums.size() * sizeof(S32)); - //skinbytes += U64Bytes(copy_iter->second->mJointNames.size() * sizeof(LLMatrix4a)); - //skinbytes += U64Bytes(copy_iter->second->mJointNames.size() * sizeof(LLMatrix4)); - - if (copy_iter->second->getNumRefs() == 1) - { - mSkinMap.erase(copy_iter); - } - } - //LL_INFOS() << "Skin info cache elements:" << mSkinMap.size() << " Memory: " << U64Kilobytes(skinbytes) << LL_ENDL; - } - - // For major operations, attempt to get the required locks - // without blocking and punt if they're not available. The - // longest run of holdoffs is kept in sMaxLockHoldoffs just - // to collect the data. In testing, I've never seen a value - // greater than 2 (written to log on exit). - { - LLMutexTrylock lock1(mMeshMutex); - LLMutexTrylock lock2(mThread->mMutex); - - static U32 hold_offs(0); - if (! lock1.isLocked() || ! lock2.isLocked()) - { - // If we can't get the locks, skip and pick this up later. - ++hold_offs; - sMaxLockHoldoffs = llmax(sMaxLockHoldoffs, hold_offs); - return; - } - hold_offs = 0; - - if (gAgent.getRegion()) - { - // Update capability urls - static std::string region_name("never name a region this"); - - if (gAgent.getRegion()->getName() != region_name && gAgent.getRegion()->capabilitiesReceived()) - { - region_name = gAgent.getRegion()->getName(); - const std::string mesh_cap(gAgent.getRegion()->getViewerAssetUrl()); - mThread->setGetMeshCap(mesh_cap); - LL_DEBUGS(LOG_MESH) << "Retrieving caps for region '" << region_name - << "', ViewerAsset cap: " << mesh_cap - << LL_ENDL; - } - } - - //popup queued error messages from background threads - while (!mUploadErrorQ.empty()) - { - LLSD substitutions(mUploadErrorQ.front()); - if (substitutions.has("DETAILS")) - { - LLNotificationsUtil::add("MeshUploadErrorDetails", substitutions); - } - else - { - LLNotificationsUtil::add("MeshUploadError", substitutions); - } - mUploadErrorQ.pop(); - } - - S32 active_count = LLMeshRepoThread::sActiveHeaderRequests + LLMeshRepoThread::sActiveLODRequests; - if (active_count < LLMeshRepoThread::sRequestLowWater) - { - S32 push_count = LLMeshRepoThread::sRequestHighWater - active_count; - - if (mPendingRequests.size() > push_count) - { - // More requests than the high-water limit allows so - // sort and forward the most important. - - //calculate "score" for pending requests - - //create score map - std::map score_map; - - for (U32 i = 0; i < LLVolumeLODGroup::NUM_LODS; ++i) - { - for (mesh_load_map::iterator iter = mLoadingMeshes[i].begin(); iter != mLoadingMeshes[i].end(); ++iter) - { - F32 max_score = 0.f; - for (auto obj_iter = iter->second.begin(); obj_iter != iter->second.end(); ++obj_iter) - { - LLVOVolume* object = *obj_iter; - if (object) - { - LLDrawable* drawable = object->mDrawable; - if (drawable) - { - F32 cur_score = drawable->getRadius()/llmax(drawable->mDistanceWRTCamera, 1.f); - max_score = llmax(max_score, cur_score); - } - } - } - - score_map[iter->first] = max_score; - } - } - - //set "score" for pending requests - for (std::vector::iterator iter = mPendingRequests.begin(); iter != mPendingRequests.end(); ++iter) - { - iter->mScore = score_map[iter->mMeshParams.getSculptID()]; - } - - //sort by "score" - std::partial_sort(mPendingRequests.begin(), mPendingRequests.begin() + push_count, - mPendingRequests.end(), LLMeshRepoThread::CompareScoreGreater()); - } - - while (!mPendingRequests.empty() && push_count > 0) - { - LLMeshRepoThread::LODRequest& request = mPendingRequests.front(); - mThread->loadMeshLOD(request.mMeshParams, request.mLOD); - mPendingRequests.erase(mPendingRequests.begin()); - LLMeshRepository::sLODPending--; - push_count--; - } - } - - //send skin info requests - while (!mPendingSkinRequests.empty()) - { - mThread->loadMeshSkinInfo(mPendingSkinRequests.front()); - mPendingSkinRequests.pop(); - } - - //send decomposition requests - while (!mPendingDecompositionRequests.empty()) - { - mThread->loadMeshDecomposition(mPendingDecompositionRequests.front()); - mPendingDecompositionRequests.pop(); - } - - //send physics shapes decomposition requests - while (!mPendingPhysicsShapeRequests.empty()) - { - mThread->loadMeshPhysicsShape(mPendingPhysicsShapeRequests.front()); - mPendingPhysicsShapeRequests.pop(); - } - - mThread->notifyLoadedMeshes(); - } - - mThread->mSignal->signal(); -} - -void LLMeshRepository::notifySkinInfoReceived(LLMeshSkinInfo* info) -{ - mSkinMap[info->mMeshID] = info; // Cache into LLPointer - // Alternative: We can get skin size from header - sCacheBytesSkins += info->sizeBytes(); - - skin_load_map::iterator iter = mLoadingSkins.find(info->mMeshID); - if (iter != mLoadingSkins.end()) - { - for (LLVOVolume* vobj : iter->second) - { - if (vobj) - { - vobj->notifySkinInfoLoaded(info); - } - } - mLoadingSkins.erase(iter); - } -} - -void LLMeshRepository::notifySkinInfoUnavailable(const LLUUID& mesh_id) -{ - skin_load_map::iterator iter = mLoadingSkins.find(mesh_id); - if (iter != mLoadingSkins.end()) - { - for (LLVOVolume* vobj : iter->second) - { - if (vobj) - { - vobj->notifySkinInfoUnavailable(); - } - } - mLoadingSkins.erase(iter); - } -} - -void LLMeshRepository::notifyDecompositionReceived(LLModel::Decomposition* decomp) -{ - decomposition_map::iterator iter = mDecompositionMap.find(decomp->mMeshID); - if (iter == mDecompositionMap.end()) - { //just insert decomp into map - mDecompositionMap[decomp->mMeshID] = decomp; - mLoadingDecompositions.erase(decomp->mMeshID); - sCacheBytesDecomps += decomp->sizeBytes(); - } - else - { //merge decomp with existing entry - sCacheBytesDecomps -= iter->second->sizeBytes(); - iter->second->merge(decomp); - sCacheBytesDecomps += iter->second->sizeBytes(); - - mLoadingDecompositions.erase(decomp->mMeshID); - delete decomp; - } -} - -void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVolume* volume) -{ //called from main thread - S32 detail = LLVolumeLODGroup::getVolumeDetailFromScale(volume->getDetail()); - - //get list of objects waiting to be notified this mesh is loaded - const auto& mesh_id = mesh_params.getSculptID(); - mesh_load_map::iterator obj_iter = mLoadingMeshes[detail].find(mesh_id); - - if (volume && obj_iter != mLoadingMeshes[detail].end()) - { - //make sure target volume is still valid - if (volume->getNumVolumeFaces() <= 0) - { - LL_WARNS(LOG_MESH) << "Mesh loading returned empty volume. ID: " << mesh_id - << LL_ENDL; - } - - { //update system volume - LLVolume* sys_volume = LLPrimitive::getVolumeManager()->refVolume(mesh_params, detail); - if (sys_volume) - { - sys_volume->copyVolumeFaces(volume); - sys_volume->setMeshAssetLoaded(true); - LLPrimitive::getVolumeManager()->unrefVolume(sys_volume); - } - else - { - LL_WARNS(LOG_MESH) << "Couldn't find system volume for mesh " << mesh_id - << LL_ENDL; - } - } - - //notify waiting LLVOVolume instances that their requested mesh is available - for (LLVOVolume* vobj : obj_iter->second) - { - if (vobj) - { - vobj->notifyMeshLoaded(); - } - } - - mLoadingMeshes[detail].erase(obj_iter); - - LLViewerStatsRecorder::instance().meshLoaded(); - } -} - -void LLMeshRepository::notifyMeshUnavailable(const LLVolumeParams& mesh_params, S32 lod) -{ //called from main thread - //get list of objects waiting to be notified this mesh is loaded - const auto& mesh_id = mesh_params.getSculptID(); - mesh_load_map::iterator obj_iter = mLoadingMeshes[lod].find(mesh_id); - if (obj_iter != mLoadingMeshes[lod].end()) - { - F32 detail = LLVolumeLODGroup::getVolumeScaleFromDetail(lod); - - LLVolume* sys_volume = LLPrimitive::getVolumeManager()->refVolume(mesh_params, lod); - if (sys_volume) - { - sys_volume->setMeshAssetUnavaliable(true); - LLPrimitive::getVolumeManager()->unrefVolume(sys_volume); - } - - for (LLVOVolume* vobj : obj_iter->second) - { - if (vobj) - { - LLVolume* obj_volume = vobj->getVolume(); - - if (obj_volume && - obj_volume->getDetail() == detail && - obj_volume->getParams() == mesh_params) - { //should force volume to find most appropriate LOD - vobj->setVolume(obj_volume->getParams(), lod); - } - } - } - - mLoadingMeshes[lod].erase(obj_iter); - } -} - -S32 LLMeshRepository::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod) -{ - return mThread->getActualMeshLOD(mesh_params, lod); -} - -const LLMeshSkinInfo* LLMeshRepository::getSkinInfo(const LLUUID& mesh_id, LLVOVolume* requesting_obj) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - if (mesh_id.notNull()) - { - skin_map::iterator iter = mSkinMap.find(mesh_id); - if (iter != mSkinMap.end()) - { - return iter->second; - } - - //no skin info known about given mesh, try to fetch it - if (requesting_obj != nullptr) - { - LLMutexLock lock(mMeshMutex); - //add volume to list of loading meshes - skin_load_map::iterator iter = mLoadingSkins.find(mesh_id); - if (iter != mLoadingSkins.end()) - { //request pending for this mesh, append volume id to list - auto it = std::find(iter->second.begin(), iter->second.end(), requesting_obj); - if (it == iter->second.end()) { - iter->second.push_back(requesting_obj); - } - } - else - { - //first request for this mesh - mLoadingSkins[mesh_id].push_back(requesting_obj); - mPendingSkinRequests.push(mesh_id); - } - } - } - return nullptr; -} - -void LLMeshRepository::fetchPhysicsShape(const LLUUID& mesh_id) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; //LL_RECORD_BLOCK_TIME(FTM_MESH_FETCH); - - if (mesh_id.notNull()) - { - LLModel::Decomposition* decomp = NULL; - decomposition_map::iterator iter = mDecompositionMap.find(mesh_id); - if (iter != mDecompositionMap.end()) - { - decomp = iter->second; - } - - //decomposition block hasn't been fetched yet - if (!decomp || decomp->mPhysicsShapeMesh.empty()) - { - LLMutexLock lock(mMeshMutex); - //add volume to list of loading meshes - std::set::iterator iter = mLoadingPhysicsShapes.find(mesh_id); - if (iter == mLoadingPhysicsShapes.end()) - { //no request pending for this skin info - // *FIXME: Nothing ever deletes entries, can't be right - mLoadingPhysicsShapes.insert(mesh_id); - mPendingPhysicsShapeRequests.push(mesh_id); - } - } - } -} - -LLModel::Decomposition* LLMeshRepository::getDecomposition(const LLUUID& mesh_id) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; //LL_RECORD_BLOCK_TIME(FTM_MESH_FETCH); - - LLModel::Decomposition* ret = NULL; - - if (mesh_id.notNull()) - { - decomposition_map::iterator iter = mDecompositionMap.find(mesh_id); - if (iter != mDecompositionMap.end()) - { - ret = iter->second; - } - - //decomposition block hasn't been fetched yet - if (!ret || ret->mBaseHullMesh.empty()) - { - LLMutexLock lock(mMeshMutex); - //add volume to list of loading meshes - std::set::iterator iter = mLoadingDecompositions.find(mesh_id); - if (iter == mLoadingDecompositions.end()) - { //no request pending for this skin info - mLoadingDecompositions.insert(mesh_id); - mPendingDecompositionRequests.push(mesh_id); - } - } - } - - return ret; -} - -void LLMeshRepository::buildHull(const LLVolumeParams& params, S32 detail) -{ - LLVolume* volume = LLPrimitive::sVolumeManager->refVolume(params, detail); - - if (!volume->mHullPoints) - { - //all default params - //execute first stage - //set simplify mode to retain - //set retain percentage to zero - //run second stage - } - - LLPrimitive::sVolumeManager->unrefVolume(volume); -} - -bool LLMeshRepository::hasPhysicsShape(const LLUUID& mesh_id) -{ - if (mesh_id.isNull()) - { - return false; - } - - if (mThread->hasPhysicsShapeInHeader(mesh_id)) - { - return true; - } - - LLModel::Decomposition* decomp = getDecomposition(mesh_id); - if (decomp && !decomp->mHull.empty()) - { - return true; - } - - return false; -} - -bool LLMeshRepository::hasSkinInfo(const LLUUID& mesh_id) -{ - if (mesh_id.isNull()) - { - return false; - } - - if (mThread->hasSkinInfoInHeader(mesh_id)) - { - return true; - } - - const LLMeshSkinInfo* skininfo = getSkinInfo(mesh_id); - if (skininfo) - { - return true; - } - - return false; -} - -bool LLMeshRepository::hasHeader(const LLUUID& mesh_id) -{ - if (mesh_id.isNull()) - { - return false; - } - - return mThread->hasHeader(mesh_id); -} - -bool LLMeshRepoThread::hasPhysicsShapeInHeader(const LLUUID& mesh_id) -{ - LLMutexLock lock(mHeaderMutex); - mesh_header_map::iterator iter = mMeshHeader.find(mesh_id); - if (iter != mMeshHeader.end() && iter->second.first > 0) - { - LLMeshHeader &mesh = iter->second.second; - if (mesh.mPhysicsMeshSize > 0) - { - return true; - } - } - - return false; -} - -bool LLMeshRepoThread::hasSkinInfoInHeader(const LLUUID& mesh_id) -{ - LLMutexLock lock(mHeaderMutex); - mesh_header_map::iterator iter = mMeshHeader.find(mesh_id); - if (iter != mMeshHeader.end() && iter->second.first > 0) - { - LLMeshHeader& mesh = iter->second.second; - if (mesh.mSkinOffset >= 0 - && mesh.mSkinSize > 0) - { - return true; - } - } - - return false; -} - -bool LLMeshRepoThread::hasHeader(const LLUUID& mesh_id) -{ - LLMutexLock lock(mHeaderMutex); - mesh_header_map::iterator iter = mMeshHeader.find(mesh_id); - return iter != mMeshHeader.end(); -} - -void LLMeshRepository::uploadModel(std::vector& data, LLVector3& scale, bool upload_textures, - bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, - std::string upload_url, bool do_upload, - LLHandle fee_observer, LLHandle upload_observer) -{ - LLMeshUploadThread* thread = new LLMeshUploadThread(data, scale, upload_textures, - upload_skin, upload_joints, lock_scale_if_joint_position, - upload_url, do_upload, fee_observer, upload_observer); - mUploadWaitList.push_back(thread); -} - -S32 LLMeshRepository::getMeshSize(const LLUUID& mesh_id, S32 lod) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - if (mThread && mesh_id.notNull() && LLPrimitive::NO_LOD != lod) - { - LLMutexLock lock(mThread->mHeaderMutex); - LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id); - if (iter != mThread->mMeshHeader.end() && iter->second.first > 0) - { - const LLMeshHeader& header = iter->second.second; - - if (header.m404) - { - return -1; - } - - S32 size = header.mLodSize[lod]; - return size; - } - - } - - return -1; -} - -void LLMeshUploadThread::decomposeMeshMatrix(LLMatrix4& transformation, - LLVector3& result_pos, - LLQuaternion& result_rot, - LLVector3& result_scale) -{ - // check for reflection - bool reflected = (transformation.determinant() < 0); - - // compute position - LLVector3 position = LLVector3(0, 0, 0) * transformation; - - // compute scale - LLVector3 x_transformed = LLVector3(1, 0, 0) * transformation - position; - LLVector3 y_transformed = LLVector3(0, 1, 0) * transformation - position; - LLVector3 z_transformed = LLVector3(0, 0, 1) * transformation - position; - F32 x_length = x_transformed.normalize(); - F32 y_length = y_transformed.normalize(); - F32 z_length = z_transformed.normalize(); - LLVector3 scale = LLVector3(x_length, y_length, z_length); - - // adjust for "reflected" geometry - LLVector3 x_transformed_reflected = x_transformed; - if (reflected) - { - x_transformed_reflected *= -1.0; - } - - // compute rotation - LLMatrix3 rotation_matrix; - rotation_matrix.setRows(x_transformed_reflected, y_transformed, z_transformed); - LLQuaternion quat_rotation = rotation_matrix.quaternion(); - quat_rotation.normalize(); // the rotation_matrix might not have been orthoginal. make it so here. - LLVector3 euler_rotation; - quat_rotation.getEulerAngles(&euler_rotation.mV[VX], &euler_rotation.mV[VY], &euler_rotation.mV[VZ]); - - result_pos = position + mOrigin; - result_scale = scale; - result_rot = quat_rotation; -} - -void LLMeshRepository::updateInventory(inventory_data data) -{ - LLMutexLock lock(mMeshMutex); - dump_llsd_to_file(data.mPostData,make_dump_name("update_inventory_post_data_",dump_num)); - dump_llsd_to_file(data.mResponse,make_dump_name("update_inventory_response_",dump_num)); - mInventoryQ.push(data); -} - -void LLMeshRepository::uploadError(LLSD& args) -{ - LLMutexLock lock(mMeshMutex); - mUploadErrorQ.push(args); -} - -F32 LLMeshRepository::getEstTrianglesMax(LLUUID mesh_id) -{ - LLMeshCostData costs; - if (getCostData(mesh_id, costs)) - { - return costs.getEstTrisMax(); - } - else - { - return 0.f; - } -} - -F32 LLMeshRepository::getEstTrianglesStreamingCost(LLUUID mesh_id) -{ - LLMeshCostData costs; - if (getCostData(mesh_id, costs)) - { - return costs.getEstTrisForStreamingCost(); - } - else - { - return 0.f; - } -} - -// FIXME replace with calc based on LLMeshCostData -F32 LLMeshRepository::getStreamingCostLegacy(LLUUID mesh_id, F32 radius, S32* bytes, S32* bytes_visible, S32 lod, F32 *unscaled_value) -{ - F32 result = 0.f; - if (mThread && mesh_id.notNull()) - { - LLMutexLock lock(mThread->mHeaderMutex); - LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id); - if (iter != mThread->mMeshHeader.end() && iter->second.first > 0) - { - result = getStreamingCostLegacy(iter->second.second, radius, bytes, bytes_visible, lod, unscaled_value); - } - } - if (result > 0.f) - { - LLMeshCostData data; - if (getCostData(mesh_id, data)) - { - F32 ref_streaming_cost = data.getRadiusBasedStreamingCost(radius); - F32 ref_weighted_tris = data.getRadiusWeightedTris(radius); - if (!is_approx_equal(ref_streaming_cost,result)) - { - LL_WARNS() << mesh_id << "streaming mismatch " << result << " " << ref_streaming_cost << LL_ENDL; - } - if (unscaled_value && !is_approx_equal(ref_weighted_tris,*unscaled_value)) - { - LL_WARNS() << mesh_id << "weighted_tris mismatch " << *unscaled_value << " " << ref_weighted_tris << LL_ENDL; - } - if (bytes && (*bytes != data.getSizeTotal())) - { - LL_WARNS() << mesh_id << "bytes mismatch " << *bytes << " " << data.getSizeTotal() << LL_ENDL; - } - if (bytes_visible && (lod >=0) && (lod < LLVolumeLODGroup::NUM_LODS) && (*bytes_visible != data.getSizeByLOD(lod))) - { - LL_WARNS() << mesh_id << "bytes_visible mismatch " << *bytes_visible << " " << data.getSizeByLOD(lod) << LL_ENDL; - } - } - else - { - LL_WARNS() << "getCostData failed!!!" << LL_ENDL; - } - } - return result; -} - -// FIXME replace with calc based on LLMeshCostData -//static -F32 LLMeshRepository::getStreamingCostLegacy(LLMeshHeader& header, F32 radius, S32* bytes, S32* bytes_visible, S32 lod, F32 *unscaled_value) -{ - if (header.m404 - || header.mLodSize[0] <= 0 - || (header.mVersion > MAX_MESH_VERSION)) - { - return 0.f; - } - - F32 max_distance = 512.f; - - F32 dlowest = llmin(radius/0.03f, max_distance); - F32 dlow = llmin(radius/0.06f, max_distance); - F32 dmid = llmin(radius/0.24f, max_distance); - - static LLCachedControl metadata_discount_ch(gSavedSettings, "MeshMetaDataDiscount", 384); //discount 128 bytes to cover the cost of LLSD tags and compression domain overhead - static LLCachedControl minimum_size_ch(gSavedSettings, "MeshMinimumByteSize", 16); //make sure nothing is "free" - static LLCachedControl bytes_per_triangle_ch(gSavedSettings, "MeshBytesPerTriangle", 16); - - F32 metadata_discount = (F32)metadata_discount_ch; - F32 minimum_size = (F32)minimum_size_ch; - F32 bytes_per_triangle = (F32)bytes_per_triangle_ch; - - S32 bytes_lowest = header.mLodSize[0]; - S32 bytes_low = header.mLodSize[1]; - S32 bytes_mid = header.mLodSize[2]; - S32 bytes_high = header.mLodSize[3]; - - if (bytes_high == 0) - { - return 0.f; - } - - if (bytes_mid == 0) - { - bytes_mid = bytes_high; - } - - if (bytes_low == 0) - { - bytes_low = bytes_mid; - } - - if (bytes_lowest == 0) - { - bytes_lowest = bytes_low; - } - - F32 triangles_lowest = llmax((F32) bytes_lowest-metadata_discount, minimum_size)/bytes_per_triangle; - F32 triangles_low = llmax((F32) bytes_low-metadata_discount, minimum_size)/bytes_per_triangle; - F32 triangles_mid = llmax((F32) bytes_mid-metadata_discount, minimum_size)/bytes_per_triangle; - F32 triangles_high = llmax((F32) bytes_high-metadata_discount, minimum_size)/bytes_per_triangle; - - if (bytes) - { - *bytes = 0; - *bytes += header.mLodSize[0]; - *bytes += header.mLodSize[1]; - *bytes += header.mLodSize[2]; - *bytes += header.mLodSize[3]; - } - - if (bytes_visible) - { - lod = LLMeshRepository::getActualMeshLOD(header, lod); - if (lod >= 0 && lod <= 3) - { - *bytes_visible = header.mLodSize[lod]; - } - } - - F32 max_area = 102944.f; //area of circle that encompasses region (see MAINT-6559) - F32 min_area = 1.f; - - F32 high_area = llmin(F_PI*dmid*dmid, max_area); - F32 mid_area = llmin(F_PI*dlow*dlow, max_area); - F32 low_area = llmin(F_PI*dlowest*dlowest, max_area); - F32 lowest_area = max_area; - - lowest_area -= low_area; - low_area -= mid_area; - mid_area -= high_area; - - high_area = llclamp(high_area, min_area, max_area); - mid_area = llclamp(mid_area, min_area, max_area); - low_area = llclamp(low_area, min_area, max_area); - lowest_area = llclamp(lowest_area, min_area, max_area); - - F32 total_area = high_area + mid_area + low_area + lowest_area; - high_area /= total_area; - mid_area /= total_area; - low_area /= total_area; - lowest_area /= total_area; - - F32 weighted_avg = triangles_high*high_area + - triangles_mid*mid_area + - triangles_low*low_area + - triangles_lowest*lowest_area; - - if (unscaled_value) - { - *unscaled_value = weighted_avg; - } - - return weighted_avg/gSavedSettings.getU32("MeshTriangleBudget")*15000.f; -} - -LLMeshCostData::LLMeshCostData() -{ - std::fill(mSizeByLOD.begin(), mSizeByLOD.end(), 0); - std::fill(mEstTrisByLOD.begin(), mEstTrisByLOD.end(), 0.f); -} - -bool LLMeshCostData::init(const LLMeshHeader& header) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - - std::fill(mSizeByLOD.begin(), mSizeByLOD.end(), 0); - std::fill(mEstTrisByLOD.begin(), mEstTrisByLOD.end(), 0.f); - - S32 bytes_high = header.mLodSize[3]; - S32 bytes_med = header.mLodSize[2]; - if (bytes_med == 0) - { - bytes_med = bytes_high; - } - S32 bytes_low = header.mLodSize[1]; - if (bytes_low == 0) - { - bytes_low = bytes_med; - } - S32 bytes_lowest = header.mLodSize[0]; - if (bytes_lowest == 0) - { - bytes_lowest = bytes_low; - } - mSizeByLOD[0] = bytes_lowest; - mSizeByLOD[1] = bytes_low; - mSizeByLOD[2] = bytes_med; - mSizeByLOD[3] = bytes_high; - - static LLCachedControl metadata_discount(gSavedSettings, "MeshMetaDataDiscount", 384); //discount 128 bytes to cover the cost of LLSD tags and compression domain overhead - static LLCachedControl minimum_size(gSavedSettings, "MeshMinimumByteSize", 16); //make sure nothing is "free" - static LLCachedControl bytes_per_triangle(gSavedSettings, "MeshBytesPerTriangle", 16); - - for (S32 i=0; i=0; i--) - { - // How many tris can we have in this LOD without affecting land impact? - // - normally an LOD should be at most half the size of the previous one. - // - once we reach a floor of ENFORCE_FLOOR, don't require LODs to get any smaller. - allowed_tris = llclamp(allowed_tris/2.0f,ENFORCE_FLOOR,mEstTrisByLOD[i]); - F32 excess_tris = mEstTrisByLOD[i]-allowed_tris; - if (excess_tris>0.f) - { - LL_DEBUGS("StreamingCost") << "excess tris in lod[" << i << "] " << excess_tris << " allowed " << allowed_tris << LL_ENDL; - charged_tris += excess_tris; - } - } - return charged_tris; -} - -F32 LLMeshCostData::getRadiusBasedStreamingCost(F32 radius) -{ - return getRadiusWeightedTris(radius)/gSavedSettings.getU32("MeshTriangleBudget")*15000.f; -} - -F32 LLMeshCostData::getTriangleBasedStreamingCost() -{ - F32 result = ANIMATED_OBJECT_COST_PER_KTRI * 0.001 * getEstTrisForStreamingCost(); - return result; -} - -bool LLMeshRepository::getCostData(LLUUID mesh_id, LLMeshCostData& data) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - data = LLMeshCostData(); - - if (mThread && mesh_id.notNull()) - { - LLMutexLock lock(mThread->mHeaderMutex); - LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id); - if (iter != mThread->mMeshHeader.end() && iter->second.first > 0) - { - LLMeshHeader& header = iter->second.second; - - bool header_invalid = (header.m404 - || header.mLodSize[0] <= 0 - || header.mVersion > MAX_MESH_VERSION); - if (!header_invalid) - { - return getCostData(header, data); - } - - return true; - } - } - return false; -} - -bool LLMeshRepository::getCostData(LLMeshHeader& header, LLMeshCostData& data) -{ - data = LLMeshCostData(); - - if (!data.init(header)) - { - return false; - } - - return true; -} - -LLPhysicsDecomp::LLPhysicsDecomp() -: LLThread("Physics Decomp") -{ - mInited = false; - mQuitting = false; - mDone = false; - - mSignal = new LLCondition(); - mMutex = new LLMutex(); -} - -LLPhysicsDecomp::~LLPhysicsDecomp() -{ - shutdown(); - - delete mSignal; - mSignal = NULL; - delete mMutex; - mMutex = NULL; -} - -void LLPhysicsDecomp::shutdown() -{ - if (mSignal) - { - mQuitting = true; - // There is only one wait(), but just in case 'broadcast' - mSignal->broadcast(); - - while (!isStopped()) - { - apr_sleep(10); - } - } -} - -void LLPhysicsDecomp::submitRequest(LLPhysicsDecomp::Request* request) -{ - LLMutexLock lock(mMutex); - mRequestQ.push(request); - mSignal->signal(); -} - -//static -S32 LLPhysicsDecomp::llcdCallback(const char* status, S32 p1, S32 p2) -{ - if (gMeshRepo.mDecompThread && gMeshRepo.mDecompThread->mCurRequest.notNull()) - { - return gMeshRepo.mDecompThread->mCurRequest->statusCallback(status, p1, p2); - } - - return 1; -} - -void LLPhysicsDecomp::setMeshData(LLCDMeshData& mesh, bool vertex_based) -{ - mesh.mVertexBase = mCurRequest->mPositions[0].mV; - mesh.mVertexStrideBytes = 12; - mesh.mNumVertices = mCurRequest->mPositions.size(); - - if(!vertex_based) - { - mesh.mIndexType = LLCDMeshData::INT_16; - mesh.mIndexBase = &(mCurRequest->mIndices[0]); - mesh.mIndexStrideBytes = 6; - - mesh.mNumTriangles = mCurRequest->mIndices.size()/3; - } - - if ((vertex_based || mesh.mNumTriangles > 0) && mesh.mNumVertices > 2) - { - LLCDResult ret = LLCD_OK; - if (LLConvexDecomposition::getInstance() != NULL) - { - ret = LLConvexDecomposition::getInstance()->setMeshData(&mesh, vertex_based); - } - - if (ret) - { - LL_ERRS(LOG_MESH) << "Convex Decomposition thread valid but could not set mesh data." << LL_ENDL; - } - } -} - -void LLPhysicsDecomp::doDecomposition() -{ - LLCDMeshData mesh; - S32 stage = mStageID[mCurRequest->mStage]; - - if (LLConvexDecomposition::getInstance() == NULL) - { - // stub library. do nothing. - return; - } - - //load data intoLLCD - if (stage == 0) - { - setMeshData(mesh, false); - } - - //build parameter map - std::map param_map; - - static const LLCDParam* params = NULL; - static S32 param_count = 0; - if (!params) - { - param_count = LLConvexDecomposition::getInstance()->getParameters(¶ms); - } - - for (S32 i = 0; i < param_count; ++i) - { - param_map[params[i].mName] = params+i; - } - - U32 ret = LLCD_OK; - //set parameter values - for (decomp_params::iterator iter = mCurRequest->mParams.begin(); iter != mCurRequest->mParams.end(); ++iter) - { - const std::string& name = iter->first; - const LLSD& value = iter->second; - - const LLCDParam* param = param_map[name]; - - if (param == NULL) - { //couldn't find valid parameter - continue; - } - - - if (param->mType == LLCDParam::LLCD_FLOAT) - { - ret = LLConvexDecomposition::getInstance()->setParam(param->mName, (F32) value.asReal()); - } - else if (param->mType == LLCDParam::LLCD_INTEGER || - param->mType == LLCDParam::LLCD_ENUM) - { - ret = LLConvexDecomposition::getInstance()->setParam(param->mName, value.asInteger()); - } - else if (param->mType == LLCDParam::LLCD_BOOLEAN) - { - ret = LLConvexDecomposition::getInstance()->setParam(param->mName, value.asBoolean()); - } - } - - mCurRequest->setStatusMessage("Executing."); - - if (LLConvexDecomposition::getInstance() != NULL) - { - ret = LLConvexDecomposition::getInstance()->executeStage(stage); - } - - if (ret) - { - LL_WARNS(LOG_MESH) << "Convex Decomposition thread valid but could not execute stage " << stage << "." - << LL_ENDL; - LLMutexLock lock(mMutex); - - mCurRequest->mHull.clear(); - mCurRequest->mHullMesh.clear(); - - mCurRequest->setStatusMessage("FAIL"); - - completeCurrent(); - } - else - { - mCurRequest->setStatusMessage("Reading results"); - - S32 num_hulls =0; - if (LLConvexDecomposition::getInstance() != NULL) - { - num_hulls = LLConvexDecomposition::getInstance()->getNumHullsFromStage(stage); - } - - { - LLMutexLock lock(mMutex); - mCurRequest->mHull.clear(); - mCurRequest->mHull.resize(num_hulls); - - mCurRequest->mHullMesh.clear(); - mCurRequest->mHullMesh.resize(num_hulls); - } - - for (S32 i = 0; i < num_hulls; ++i) - { - std::vector p; - LLCDHull hull; - // if LLConvexDecomposition is a stub, num_hulls should have been set to 0 above, and we should not reach this code - LLConvexDecomposition::getInstance()->getHullFromStage(stage, i, &hull); - - const F32* v = hull.mVertexBase; - - for (S32 j = 0; j < hull.mNumVertices; ++j) - { - LLVector3 vert(v[0], v[1], v[2]); - p.push_back(vert); - v = (F32*) (((U8*) v) + hull.mVertexStrideBytes); - } - - LLCDMeshData mesh; - // if LLConvexDecomposition is a stub, num_hulls should have been set to 0 above, and we should not reach this code - LLConvexDecomposition::getInstance()->getMeshFromStage(stage, i, &mesh); - - get_vertex_buffer_from_mesh(mesh, mCurRequest->mHullMesh[i]); - - { - LLMutexLock lock(mMutex); - mCurRequest->mHull[i] = p; - } - } - - { - LLMutexLock lock(mMutex); - mCurRequest->setStatusMessage("FAIL"); - completeCurrent(); - } - } -} - -void LLPhysicsDecomp::completeCurrent() -{ - LLMutexLock lock(mMutex); - mCompletedQ.push(mCurRequest); - mCurRequest = NULL; -} - -void LLPhysicsDecomp::notifyCompleted() -{ - if (!mCompletedQ.empty()) - { - LLMutexLock lock(mMutex); - while (!mCompletedQ.empty()) - { - Request* req = mCompletedQ.front(); - req->completed(); - mCompletedQ.pop(); - } - } -} - - -void make_box(LLPhysicsDecomp::Request * request) -{ - LLVector3 min,max; - min = request->mPositions[0]; - max = min; - - for (U32 i = 0; i < request->mPositions.size(); ++i) - { - update_min_max(min, max, request->mPositions[i]); - } - - request->mHull.clear(); - - LLModel::hull box; - box.push_back(LLVector3(min[0],min[1],min[2])); - box.push_back(LLVector3(max[0],min[1],min[2])); - box.push_back(LLVector3(min[0],max[1],min[2])); - box.push_back(LLVector3(max[0],max[1],min[2])); - box.push_back(LLVector3(min[0],min[1],max[2])); - box.push_back(LLVector3(max[0],min[1],max[2])); - box.push_back(LLVector3(min[0],max[1],max[2])); - box.push_back(LLVector3(max[0],max[1],max[2])); - - request->mHull.push_back(box); -} - - -void LLPhysicsDecomp::doDecompositionSingleHull() -{ - LLConvexDecomposition* decomp = LLConvexDecomposition::getInstance(); - - if (decomp == NULL) - { - //stub. do nothing. - return; - } - - LLCDMeshData mesh; - - setMeshData(mesh, true); - - LLCDResult ret = decomp->buildSingleHull() ; - if (ret) - { - LL_WARNS(LOG_MESH) << "Could not execute decomposition stage when attempting to create single hull." << LL_ENDL; - make_box(mCurRequest); - } - else - { - { - LLMutexLock lock(mMutex); - mCurRequest->mHull.clear(); - mCurRequest->mHull.resize(1); - mCurRequest->mHullMesh.clear(); - } - - std::vector p; - LLCDHull hull; - - // if LLConvexDecomposition is a stub, num_hulls should have been set to 0 above, and we should not reach this code - decomp->getSingleHull(&hull); - - const F32* v = hull.mVertexBase; - - for (S32 j = 0; j < hull.mNumVertices; ++j) - { - LLVector3 vert(v[0], v[1], v[2]); - p.push_back(vert); - v = (F32*) (((U8*) v) + hull.mVertexStrideBytes); - } - - { - LLMutexLock lock(mMutex); - mCurRequest->mHull[0] = p; - } - } - - { - completeCurrent(); - - } -} - - -void LLPhysicsDecomp::run() -{ - LLConvexDecomposition* decomp = LLConvexDecomposition::getInstance(); - if (decomp == NULL) - { - // stub library. Set init to true so the main thread - // doesn't wait for this to finish. - mInited = true; - return; - } - - decomp->initThread(); - mInited = true; - - static const LLCDStageData* stages = NULL; - static S32 num_stages = 0; - - if (!stages) - { - num_stages = decomp->getStages(&stages); - } - - for (S32 i = 0; i < num_stages; i++) - { - mStageID[stages[i].mName] = i; - } - - while (!mQuitting) - { - mSignal->wait(); - while (!mQuitting && !mRequestQ.empty()) - { - { - LLMutexLock lock(mMutex); - mCurRequest = mRequestQ.front(); - mRequestQ.pop(); - } - - S32& id = *(mCurRequest->mDecompID); - if (id == -1) - { - decomp->genDecomposition(id); - } - decomp->bindDecomposition(id); - - if (mCurRequest->mStage == "single_hull") - { - doDecompositionSingleHull(); - } - else - { - doDecomposition(); - } - } - } - - decomp->quitThread(); - - if (mSignal->isLocked()) - { //let go of mSignal's associated mutex - mSignal->unlock(); - } - - mDone = true; -} - -void LLPhysicsDecomp::Request::assignData(LLModel* mdl) -{ - if (!mdl) - { - return ; - } - - U16 index_offset = 0; - U16 tri[3] ; - - mPositions.clear(); - mIndices.clear(); - mBBox[1] = LLVector3(F32_MIN, F32_MIN, F32_MIN) ; - mBBox[0] = LLVector3(F32_MAX, F32_MAX, F32_MAX) ; - - //queue up vertex positions and indices - for (S32 i = 0; i < mdl->getNumVolumeFaces(); ++i) - { - const LLVolumeFace& face = mdl->getVolumeFace(i); - if (mPositions.size() + face.mNumVertices > 65535) - { - continue; - } - - for (U32 j = 0; j < face.mNumVertices; ++j) - { - mPositions.push_back(LLVector3(face.mPositions[j].getF32ptr())); - for(U32 k = 0 ; k < 3 ; k++) - { - mBBox[0].mV[k] = llmin(mBBox[0].mV[k], mPositions[j].mV[k]) ; - mBBox[1].mV[k] = llmax(mBBox[1].mV[k], mPositions[j].mV[k]) ; - } - } - - updateTriangleAreaThreshold() ; - - for (U32 j = 0; j+2 < face.mNumIndices; j += 3) - { - tri[0] = face.mIndices[j] + index_offset ; - tri[1] = face.mIndices[j + 1] + index_offset ; - tri[2] = face.mIndices[j + 2] + index_offset ; - - if(isValidTriangle(tri[0], tri[1], tri[2])) - { - mIndices.push_back(tri[0]); - mIndices.push_back(tri[1]); - mIndices.push_back(tri[2]); - } - } - - index_offset += face.mNumVertices; - } - - return ; -} - -void LLPhysicsDecomp::Request::updateTriangleAreaThreshold() -{ - F32 range = mBBox[1].mV[0] - mBBox[0].mV[0] ; - range = llmin(range, mBBox[1].mV[1] - mBBox[0].mV[1]) ; - range = llmin(range, mBBox[1].mV[2] - mBBox[0].mV[2]) ; - - mTriangleAreaThreshold = llmin(0.0002f, range * 0.000002f) ; -} - -//check if the triangle area is large enough to qualify for a valid triangle -bool LLPhysicsDecomp::Request::isValidTriangle(U16 idx1, U16 idx2, U16 idx3) -{ - LLVector3 a = mPositions[idx2] - mPositions[idx1] ; - LLVector3 b = mPositions[idx3] - mPositions[idx1] ; - F32 c = a * b ; - - return ((a*a) * (b*b) - c * c) > mTriangleAreaThreshold ; -} - -void LLPhysicsDecomp::Request::setStatusMessage(const std::string& msg) -{ - mStatusMessage = msg; -} - -void LLMeshRepository::buildPhysicsMesh(LLModel::Decomposition& decomp) -{ - decomp.mMesh.resize(decomp.mHull.size()); - - for (U32 i = 0; i < decomp.mHull.size(); ++i) - { - LLCDHull hull; - hull.mNumVertices = decomp.mHull[i].size(); - hull.mVertexBase = decomp.mHull[i][0].mV; - hull.mVertexStrideBytes = 12; - - LLCDMeshData mesh; - LLCDResult res = LLCD_OK; - if (LLConvexDecomposition::getInstance() != NULL) - { - res = LLConvexDecomposition::getInstance()->getMeshFromHull(&hull, &mesh); - } - if (res == LLCD_OK) - { - get_vertex_buffer_from_mesh(mesh, decomp.mMesh[i]); - } - } - - if (!decomp.mBaseHull.empty() && decomp.mBaseHullMesh.empty()) - { //get mesh for base hull - LLCDHull hull; - hull.mNumVertices = decomp.mBaseHull.size(); - hull.mVertexBase = decomp.mBaseHull[0].mV; - hull.mVertexStrideBytes = 12; - - LLCDMeshData mesh; - LLCDResult res = LLCD_OK; - if (LLConvexDecomposition::getInstance() != NULL) - { - res = LLConvexDecomposition::getInstance()->getMeshFromHull(&hull, &mesh); - } - if (res == LLCD_OK) - { - get_vertex_buffer_from_mesh(mesh, decomp.mBaseHullMesh); - } - } -} - - -bool LLMeshRepository::meshUploadEnabled() -{ - LLViewerRegion *region = gAgent.getRegion(); - if(gSavedSettings.getBOOL("MeshEnabled") && - region) - { - return region->meshUploadEnabled(); - } - return false; -} - -bool LLMeshRepository::meshRezEnabled() -{ - LLViewerRegion *region = gAgent.getRegion(); - if(gSavedSettings.getBOOL("MeshEnabled") && - region) - { - return region->meshRezEnabled(); - } - return false; -} - -// Threading: main thread only -// static -void LLMeshRepository::metricsStart() -{ - ++metrics_teleport_start_count; - sQuiescentTimer.start(0); -} - -// Threading: main thread only -// static -void LLMeshRepository::metricsStop() -{ - sQuiescentTimer.stop(0); -} - -// Threading: main thread only -// static -void LLMeshRepository::metricsProgress(unsigned int this_count) -{ - static bool first_start(true); - - if (first_start) - { - metricsStart(); - first_start = false; - } - sQuiescentTimer.ringBell(0, this_count); -} - -// Threading: main thread only -// static -void LLMeshRepository::metricsUpdate() -{ - F64 started, stopped; - U64 total_count(U64L(0)), user_cpu(U64L(0)), sys_cpu(U64L(0)); - - if (sQuiescentTimer.isExpired(0, started, stopped, total_count, user_cpu, sys_cpu)) - { - LLSD metrics; - - metrics["reason"] = "Mesh Download Quiescent"; - metrics["scope"] = metrics_teleport_start_count > 1 ? "Teleport" : "Login"; - metrics["start"] = started; - metrics["stop"] = stopped; - metrics["fetches"] = LLSD::Integer(total_count); - metrics["teleports"] = LLSD::Integer(metrics_teleport_start_count); - metrics["user_cpu"] = double(user_cpu) / 1.0e6; - metrics["sys_cpu"] = double(sys_cpu) / 1.0e6; - LL_INFOS(LOG_MESH) << "EventMarker " << metrics << LL_ENDL; - } -} - -// Threading: main thread only -// static -void teleport_started() -{ - LLMeshRepository::metricsStart(); -} - - -void on_new_single_inventory_upload_complete( - LLAssetType::EType asset_type, - LLInventoryType::EType inventory_type, - const std::string inventory_type_string, - const LLUUID& item_folder_id, - const std::string& item_name, - const std::string& item_description, - const LLSD& server_response, - S32 upload_price) -{ - bool success = false; - - if (upload_price > 0) - { - // this upload costed us L$, update our balance - // and display something saying that it cost L$ - LLStatusBar::sendMoneyBalanceRequest(); - - LLSD args; - args["AMOUNT"] = llformat("%d", upload_price); - LLNotificationsUtil::add("UploadPayment", args); - } - - if (item_folder_id.notNull()) - { - U32 everyone_perms = PERM_NONE; - U32 group_perms = PERM_NONE; - U32 next_owner_perms = PERM_ALL; - if (server_response.has("new_next_owner_mask")) - { - // The server provided creation perms so use them. - // Do not assume we got the perms we asked for in - // since the server may not have granted them all. - everyone_perms = server_response["new_everyone_mask"].asInteger(); - group_perms = server_response["new_group_mask"].asInteger(); - next_owner_perms = server_response["new_next_owner_mask"].asInteger(); - } - else - { - // The server doesn't provide creation perms - // so use old assumption-based perms. - if (inventory_type_string != "snapshot") - { - next_owner_perms = PERM_MOVE | PERM_TRANSFER; - } - } - - LLPermissions new_perms; - new_perms.init( - gAgent.getID(), - gAgent.getID(), - LLUUID::null, - LLUUID::null); - - new_perms.initMasks( - PERM_ALL, - PERM_ALL, - everyone_perms, - group_perms, - next_owner_perms); - - U32 inventory_item_flags = 0; - if (server_response.has("inventory_flags")) - { - inventory_item_flags = (U32)server_response["inventory_flags"].asInteger(); - if (inventory_item_flags != 0) - { - LL_INFOS() << "inventory_item_flags " << inventory_item_flags << LL_ENDL; - } - } - S32 creation_date_now = time_corrected(); - LLPointer item = new LLViewerInventoryItem( - server_response["new_inventory_item"].asUUID(), - item_folder_id, - new_perms, - server_response["new_asset"].asUUID(), - asset_type, - inventory_type, - item_name, - item_description, - LLSaleInfo::DEFAULT, - inventory_item_flags, - creation_date_now); - - gInventory.updateItem(item); - gInventory.notifyObservers(); - success = true; - - LLFocusableElement* focus = gFocusMgr.getKeyboardFocus(); - - // Show the preview panel for textures and sounds to let - // user know that the image (or snapshot) arrived intact. - LLInventoryPanel* panel = LLInventoryPanel::getActiveInventoryPanel(false); - if (panel) - { - - panel->setSelection( - server_response["new_inventory_item"].asUUID(), - TAKE_FOCUS_NO); - } - else - { - LLInventoryPanel::openInventoryPanelAndSetSelection(true, server_response["new_inventory_item"].asUUID(), true, false, true); - } - - // restore keyboard focus - gFocusMgr.setKeyboardFocus(focus); - } - else - { - LL_WARNS() << "Can't find a folder to put it in" << LL_ENDL; - } - - // Todo: This is mesh repository code, is following code really needed? - // remove the "Uploading..." message - LLUploadDialog::modalUploadFinished(); - - // Let the Snapshot floater know we have finished uploading a snapshot to inventory. - LLFloater* floater_snapshot = LLFloaterReg::findInstance("snapshot"); - if (asset_type == LLAssetType::AT_TEXTURE && floater_snapshot) - { - floater_snapshot->notify(LLSD().with("set-finished", LLSD().with("ok", success).with("msg", "inventory"))); - } -} +/** + * @file llmeshrepository.cpp + * @brief Mesh repository implementation. + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010-2014, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llapr.h" +#include "apr_portable.h" +#include "apr_pools.h" +#include "apr_dso.h" +#include "llhttpconstants.h" +#include "llmeshrepository.h" + +#include "llagent.h" +#include "llappviewer.h" +#include "llbufferstream.h" +#include "llcallbacklist.h" +#include "lldatapacker.h" +#include "lldeadmantimer.h" +#include "llfloatermodelpreview.h" +#include "llfloaterperms.h" +#include "llimagej2c.h" +#include "llhost.h" +#include "llmath.h" +#include "llnotificationsutil.h" +#include "llsd.h" +#include "llsdutil_math.h" +#include "llsdserialize.h" +#include "llthread.h" +#include "llfilesystem.h" +#include "llviewercontrol.h" +#include "llviewerinventory.h" +#include "llviewermenufile.h" +#include "llviewermessage.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llviewerstatsrecorder.h" +#include "llviewertexturelist.h" +#include "llvolume.h" +#include "llvolumemgr.h" +#include "llvovolume.h" +#include "llworld.h" +#include "material_codes.h" +#include "pipeline.h" +#include "llinventorymodel.h" +#include "llfoldertype.h" +#include "llviewerparcelmgr.h" +#include "lluploadfloaterobservers.h" +#include "bufferarray.h" +#include "bufferstream.h" +#include "llfasttimer.h" +#include "llcorehttputil.h" +#include "lltrans.h" +#include "llstatusbar.h" +#include "llinventorypanel.h" +#include "lluploaddialog.h" +#include "llfloaterreg.h" + +#include "boost/iostreams/device/array.hpp" +#include "boost/iostreams/stream.hpp" +#include "boost/lexical_cast.hpp" + +#ifndef LL_WINDOWS +#include "netdb.h" +#endif + + +// Purpose +// +// The purpose of this module is to provide access between the viewer +// and the asset system as regards to mesh objects. +// +// * High-throughput download of mesh assets from servers while +// following best industry practices for network profile. +// * Reliable expensing and upload of new mesh assets. +// * Recovery and retry from errors when appropriate. +// * Decomposition of mesh assets for preview and uploads. +// * And most important: all of the above without exposing the +// main thread to stalls due to deep processing or thread +// locking actions. In particular, the following operations +// on LLMeshRepository are very averse to any stalls: +// * loadMesh +// * search in mMeshHeader (For structural details, see: +// http://wiki.secondlife.com/wiki/Mesh/Mesh_Asset_Format) +// * notifyLoadedMeshes +// * getSkinInfo +// +// Threads +// +// main Main rendering thread, very sensitive to locking and other stalls +// repo Overseeing worker thread associated with the LLMeshRepoThread class +// decom Worker thread for mesh decomposition requests +// core HTTP worker thread: does the work but doesn't intrude here +// uploadN 0-N temporary mesh upload threads (0-1 in practice) +// +// Sequence of Operations +// +// What follows is a description of the retrieval of one LOD for +// a new mesh object. Work is performed by a series of short, quick +// actions distributed over a number of threads. Each is meant +// to proceed without stalling and the whole forms a deep request +// pipeline to achieve throughput. Ellipsis indicates a return +// or break in processing which is resumed elsewhere. +// +// main thread repo thread (run() method) +// +// loadMesh() invoked to request LOD +// append LODRequest to mPendingRequests +// ... +// other mesh requests may be made +// ... +// notifyLoadedMeshes() invoked to stage work +// append HeaderRequest to mHeaderReqQ +// ... +// scan mHeaderReqQ +// issue 4096-byte GET for header +// ... +// onCompleted() invoked for GET +// data copied +// headerReceived() invoked +// LLSD parsed +// mMeshHeader updated +// scan mPendingLOD for LOD request +// push LODRequest to mLODReqQ +// ... +// scan mLODReqQ +// fetchMeshLOD() invoked +// issue Byte-Range GET for LOD +// ... +// onCompleted() invoked for GET +// data copied +// lodReceived() invoked +// unpack data into LLVolume +// append LoadedMesh to mLoadedQ +// ... +// notifyLoadedMeshes() invoked again +// scan mLoadedQ +// notifyMeshLoaded() for LOD +// setMeshAssetLoaded() invoked for system volume +// notifyMeshLoaded() invoked for each interested object +// ... +// +// Mutexes +// +// LLMeshRepository::mMeshMutex +// LLMeshRepoThread::mMutex +// LLMeshRepoThread::mHeaderMutex +// LLMeshRepoThread::mSignal (LLCondition) +// LLPhysicsDecomp::mSignal (LLCondition) +// LLPhysicsDecomp::mMutex +// LLMeshUploadThread::mMutex +// +// Mutex Order Rules +// +// 1. LLMeshRepoThread::mMutex before LLMeshRepoThread::mHeaderMutex +// 2. LLMeshRepository::mMeshMutex before LLMeshRepoThread::mMutex +// (There are more rules, haven't been extracted.) +// +// Data Member Access/Locking +// +// Description of how shared access to static and instance data +// members is performed. Each member is followed by the name of +// the mutex, if any, covering the data and then a list of data +// access models each of which is a triplet of the following form: +// +// {ro, wo, rw}.{main, repo, any}.{mutex, none} +// Type of access: read-only, write-only, read-write. +// Accessing thread or 'any' +// Relevant mutex held during access (several may be held) or 'none' +// +// A careful eye will notice some unsafe operations. Many of these +// have an alibi of some form. Several types of alibi are identified +// and listed here: +// +// [0] No alibi. Probably unsafe. +// [1] Single-writer, self-consistent readers. Old data must +// be tolerated by any reader but data will come true eventually. +// [2] Like [1] but provides a hint about thread state. These +// may be unsafe. +// [3] empty() check outside of lock. Can me made safish when +// done in double-check lock style. But this depends on +// std:: implementation and memory model. +// [4] Appears to be covered by a mutex but doesn't need one. +// [5] Read of a double-checked lock. +// +// So, in addition to documentation, take this as a to-do/review +// list and see if you can improve things. For porters to non-x86 +// architectures, the weaker memory models will make these platforms +// probabilistically more susceptible to hitting race conditions. +// True here and in other multi-thread code such as texture fetching. +// (Strong memory models make weak programmers. Weak memory models +// make strong programmers. Ref: arm, ppc, mips, alpha) +// +// LLMeshRepository: +// +// sBytesReceived none rw.repo.none, ro.main.none [1] +// sMeshRequestCount " +// sHTTPRequestCount " +// sHTTPLargeRequestCount " +// sHTTPRetryCount " +// sHTTPErrorCount " +// sLODPending mMeshMutex [4] rw.main.mMeshMutex +// sLODProcessing Repo::mMutex rw.any.Repo::mMutex +// sCacheBytesRead none rw.repo.none, ro.main.none [1] +// sCacheBytesWritten " +// sCacheReads " +// sCacheWrites " +// mLoadingMeshes mMeshMutex [4] rw.main.none, rw.any.mMeshMutex +// mSkinMap none rw.main.none +// mDecompositionMap none rw.main.none +// mPendingRequests mMeshMutex [4] rw.main.mMeshMutex +// mLoadingSkins mMeshMutex [4] rw.main.mMeshMutex +// mPendingSkinRequests mMeshMutex [4] rw.main.mMeshMutex +// mLoadingDecompositions mMeshMutex [4] rw.main.mMeshMutex +// mPendingDecompositionRequests mMeshMutex [4] rw.main.mMeshMutex +// mLoadingPhysicsShapes mMeshMutex [4] rw.main.mMeshMutex +// mPendingPhysicsShapeRequests mMeshMutex [4] rw.main.mMeshMutex +// mUploads none rw.main.none (upload thread accessing objects) +// mUploadWaitList none rw.main.none (upload thread accessing objects) +// mInventoryQ mMeshMutex [4] rw.main.mMeshMutex, ro.main.none [5] +// mUploadErrorQ mMeshMutex rw.main.mMeshMutex, rw.any.mMeshMutex +// mGetMeshVersion none rw.main.none +// +// LLMeshRepoThread: +// +// sActiveHeaderRequests mMutex rw.any.mMutex, ro.repo.none [1] +// sActiveLODRequests mMutex rw.any.mMutex, ro.repo.none [1] +// sMaxConcurrentRequests mMutex wo.main.none, ro.repo.none, ro.main.mMutex +// mMeshHeader mHeaderMutex rw.repo.mHeaderMutex, ro.main.mHeaderMutex, ro.main.none [0] +// mSkinRequests mMutex rw.repo.mMutex, ro.repo.none [5] +// mSkinInfoQ mMutex rw.repo.mMutex, rw.main.mMutex [5] (was: [0]) +// mDecompositionRequests mMutex rw.repo.mMutex, ro.repo.none [5] +// mPhysicsShapeRequests mMutex rw.repo.mMutex, ro.repo.none [5] +// mDecompositionQ mMutex rw.repo.mMutex, rw.main.mMutex [5] (was: [0]) +// mHeaderReqQ mMutex ro.repo.none [5], rw.repo.mMutex, rw.any.mMutex +// mLODReqQ mMutex ro.repo.none [5], rw.repo.mMutex, rw.any.mMutex +// mUnavailableQ mMutex rw.repo.none [0], ro.main.none [5], rw.main.mMutex +// mLoadedQ mMutex rw.repo.mMutex, ro.main.none [5], rw.main.mMutex +// mPendingLOD mMutex rw.repo.mMutex, rw.any.mMutex +// mGetMeshCapability mMutex rw.main.mMutex, ro.repo.mMutex (was: [0]) +// mGetMesh2Capability mMutex rw.main.mMutex, ro.repo.mMutex (was: [0]) +// mGetMeshVersion mMutex rw.main.mMutex, ro.repo.mMutex +// mHttp* none rw.repo.none +// +// LLMeshUploadThread: +// +// mDiscarded mMutex rw.main.mMutex, ro.uploadN.none [1] +// ... more ... +// +// QA/Development Testing +// +// Debug variable 'MeshUploadFakeErrors' takes a mask of bits that will +// simulate an error on fee query or upload. Defined bits are: +// +// 0x01 Simulate application error on fee check reading +// response body from file "fake_upload_error.xml" +// 0x02 Same as 0x01 but for actual upload attempt. +// 0x04 Simulate a transport problem on fee check with a +// locally-generated 500 status. +// 0x08 As with 0x04 but for the upload operation. +// +// For major changes, see the LL_MESH_FASTTIMER_ENABLE below and +// instructions for looking for frame stalls using fast timers. +// +// *TODO: Work list for followup actions: +// * Review anything marked as unsafe above, verify if there are real issues. +// * See if we can put ::run() into a hard sleep. May not actually perform better +// than the current scheme so be prepared for disappointment. You'll likely +// need to introduce a condition variable class that references a mutex in +// methods rather than derives from mutex which isn't correct. +// * On upload failures, make more information available to the alerting +// dialog. Get the structured information going into the log into a +// tree there. +// * Header parse failures come without much explanation. Elaborate. +// * Work queue for uploads? Any need for this or is the current scheme good +// enough? +// * Move data structures holding mesh data used by main thread into main- +// thread-only access so that no locking is needed. May require duplication +// of some data so that worker thread has a minimal data set to guide +// operations. +// +// -------------------------------------------------------------------------- +// Development/Debug/QA Tools +// +// Enable here or in build environment to get fasttimer data on mesh fetches. +// +// Typically, this is used to perform A/B testing using the +// fasttimer console (shift-ctrl-9). This is done by looking +// for stalls due to lock contention between the main thread +// and the repository and HTTP code. In a release viewer, +// these appear as ping-time or worse spikes in frame time. +// With this instrumentation enabled, a stall will appear +// under the 'Mesh Fetch' timer which will be either top-level +// or under 'Render' time. + +static LLFastTimer::DeclareTimer FTM_MESH_FETCH("Mesh Fetch"); + +// Random failure testing for development/QA. +// +// Set the MESH_*_FAILED macros to either 'false' or to +// an invocation of MESH_RANDOM_NTH_TRUE() with some +// suitable number. In production, all must be false. +// +// Example: +// #define MESH_HTTP_RESPONSE_FAILED MESH_RANDOM_NTH_TRUE(9) + +// 1-in-N calls will test true +#define MESH_RANDOM_NTH_TRUE(_N) ( ll_rand(S32(_N)) == 0 ) + +#define MESH_HTTP_RESPONSE_FAILED false +#define MESH_HEADER_PROCESS_FAILED false +#define MESH_LOD_PROCESS_FAILED false +#define MESH_SKIN_INFO_PROCESS_FAILED false +#define MESH_DECOMP_PROCESS_FAILED false +#define MESH_PHYS_SHAPE_PROCESS_FAILED false + +// -------------------------------------------------------------------------- + + +LLMeshRepository gMeshRepo; + +const S32 MESH_HEADER_SIZE = 4096; // Important: assumption is that headers fit in this space + + +const S32 REQUEST2_HIGH_WATER_MIN = 32; // Limits for GetMesh2 regions +const S32 REQUEST2_HIGH_WATER_MAX = 100; +const S32 REQUEST2_LOW_WATER_MIN = 16; +const S32 REQUEST2_LOW_WATER_MAX = 50; + +const U32 LARGE_MESH_FETCH_THRESHOLD = 1U << 21; // Size at which requests goes to narrow/slow queue +const long SMALL_MESH_XFER_TIMEOUT = 120L; // Seconds to complete xfer, small mesh downloads +const long LARGE_MESH_XFER_TIMEOUT = 600L; // Seconds to complete xfer, large downloads + +const U32 DOWNLOAD_RETRY_LIMIT = 8; +const F32 DOWNLOAD_RETRY_DELAY = 0.5f; // seconds + +// Would normally like to retry on uploads as some +// retryable failures would be recoverable. Unfortunately, +// the mesh service is using 500 (retryable) rather than +// 400/bad request (permanent) for a bad payload and +// retrying that just leads to revocation of the one-shot +// cap which then produces a 404 on retry destroying some +// (occasionally) useful error information. We'll leave +// upload retries to the user as in the past. SH-4667. +const long UPLOAD_RETRY_LIMIT = 0L; + +// Maximum mesh version to support. Three least significant digits are reserved for the minor version, +// with major version changes indicating a format change that is not backwards compatible and should not +// be parsed by viewers that don't specifically support that version. For example, if the integer "1" is +// present, the version is 0.001. A viewer that can parse version 0.001 can also parse versions up to 0.999, +// but not 1.0 (integer 1000). +// See wiki at https://wiki.secondlife.com/wiki/Mesh/Mesh_Asset_Format +const S32 MAX_MESH_VERSION = 999; + +U32 LLMeshRepository::sBytesReceived = 0; +U32 LLMeshRepository::sMeshRequestCount = 0; +U32 LLMeshRepository::sHTTPRequestCount = 0; +U32 LLMeshRepository::sHTTPLargeRequestCount = 0; +U32 LLMeshRepository::sHTTPRetryCount = 0; +U32 LLMeshRepository::sHTTPErrorCount = 0; +U32 LLMeshRepository::sLODProcessing = 0; +U32 LLMeshRepository::sLODPending = 0; + +U32 LLMeshRepository::sCacheBytesRead = 0; +U32 LLMeshRepository::sCacheBytesWritten = 0; +U32 LLMeshRepository::sCacheBytesHeaders = 0; +U32 LLMeshRepository::sCacheBytesSkins = 0; +U32 LLMeshRepository::sCacheBytesDecomps = 0; +U32 LLMeshRepository::sCacheReads = 0; +U32 LLMeshRepository::sCacheWrites = 0; +U32 LLMeshRepository::sMaxLockHoldoffs = 0; + +LLDeadmanTimer LLMeshRepository::sQuiescentTimer(15.0, false); // true -> gather cpu metrics + +namespace { + // The NoOpDeletor is used when passing certain objects (generally the LLMeshUploadThread) + // in a smart pointer below for passage into the LLCore::Http libararies. + // When the smart pointer is destroyed, no action will be taken since we + // do not in these cases want the object to be destroyed at the end of the call. + // + // *NOTE$: Yes! It is "Deletor" + // http://english.stackexchange.com/questions/4733/what-s-the-rule-for-adding-er-vs-or-when-nouning-a-verb + // "delete" derives from Latin "deletus" + + void NoOpDeletor(LLCore::HttpHandler *) + { /*NoOp*/ } +} + +static S32 dump_num = 0; +std::string make_dump_name(std::string prefix, S32 num) +{ + return prefix + std::to_string(num) + std::string(".xml"); +} +void dump_llsd_to_file(const LLSD& content, std::string filename); +LLSD llsd_from_file(std::string filename); + +const std::string header_lod[] = +{ + "lowest_lod", + "low_lod", + "medium_lod", + "high_lod" +}; +const char * const LOG_MESH = "Mesh"; + +// Static data and functions to measure mesh load +// time metrics for a new region scene. +static unsigned int metrics_teleport_start_count = 0; +boost::signals2::connection metrics_teleport_started_signal; +static void teleport_started(); + +void on_new_single_inventory_upload_complete( + LLAssetType::EType asset_type, + LLInventoryType::EType inventory_type, + const std::string inventory_type_string, + const LLUUID& item_folder_id, + const std::string& item_name, + const std::string& item_description, + const LLSD& server_response, + S32 upload_price); + + +//get the number of bytes resident in memory for given volume +U32 get_volume_memory_size(const LLVolume* volume) +{ + U32 indices = 0; + U32 vertices = 0; + + for (U32 i = 0; i < volume->getNumVolumeFaces(); ++i) + { + const LLVolumeFace& face = volume->getVolumeFace(i); + indices += face.mNumIndices; + vertices += face.mNumVertices; + } + + + return indices*2+vertices*11+sizeof(LLVolume)+sizeof(LLVolumeFace)*volume->getNumVolumeFaces(); +} + +void get_vertex_buffer_from_mesh(LLCDMeshData& mesh, LLModel::PhysicsMesh& res, F32 scale = 1.f) +{ + res.mPositions.clear(); + res.mNormals.clear(); + + const F32* v = mesh.mVertexBase; + + if (mesh.mIndexType == LLCDMeshData::INT_16) + { + U16* idx = (U16*) mesh.mIndexBase; + for (S32 j = 0; j < mesh.mNumTriangles; ++j) + { + F32* mp0 = (F32*) ((U8*)v+idx[0]*mesh.mVertexStrideBytes); + F32* mp1 = (F32*) ((U8*)v+idx[1]*mesh.mVertexStrideBytes); + F32* mp2 = (F32*) ((U8*)v+idx[2]*mesh.mVertexStrideBytes); + + idx = (U16*) (((U8*)idx)+mesh.mIndexStrideBytes); + + LLVector3 v0(mp0); + LLVector3 v1(mp1); + LLVector3 v2(mp2); + + LLVector3 n = (v1-v0)%(v2-v0); + n.normalize(); + + res.mPositions.push_back(v0*scale); + res.mPositions.push_back(v1*scale); + res.mPositions.push_back(v2*scale); + + res.mNormals.push_back(n); + res.mNormals.push_back(n); + res.mNormals.push_back(n); + } + } + else + { + U32* idx = (U32*) mesh.mIndexBase; + for (S32 j = 0; j < mesh.mNumTriangles; ++j) + { + F32* mp0 = (F32*) ((U8*)v+idx[0]*mesh.mVertexStrideBytes); + F32* mp1 = (F32*) ((U8*)v+idx[1]*mesh.mVertexStrideBytes); + F32* mp2 = (F32*) ((U8*)v+idx[2]*mesh.mVertexStrideBytes); + + idx = (U32*) (((U8*)idx)+mesh.mIndexStrideBytes); + + LLVector3 v0(mp0); + LLVector3 v1(mp1); + LLVector3 v2(mp2); + + LLVector3 n = (v1-v0)%(v2-v0); + n.normalize(); + + res.mPositions.push_back(v0*scale); + res.mPositions.push_back(v1*scale); + res.mPositions.push_back(v2*scale); + + res.mNormals.push_back(n); + res.mNormals.push_back(n); + res.mNormals.push_back(n); + } + } +} + +void RequestStats::updateTime() +{ + U32 modifier = 1 << mRetries; // before ++ + mRetries++; + mTimer.reset(); + mTimer.setTimerExpirySec(DOWNLOAD_RETRY_DELAY * (F32)modifier); // up to 32s, 64 total wait +} + +bool RequestStats::canRetry() const +{ + return mRetries < DOWNLOAD_RETRY_LIMIT; +} + +bool RequestStats::isDelayed() const +{ + return mTimer.getStarted() && !mTimer.hasExpired(); +} + +LLViewerFetchedTexture* LLMeshUploadThread::FindViewerTexture(const LLImportMaterial& material) +{ + LLPointer< LLViewerFetchedTexture > * ppTex = static_cast< LLPointer< LLViewerFetchedTexture > * >(material.mOpaqueData); + return ppTex ? (*ppTex).get() : NULL; +} + +volatile S32 LLMeshRepoThread::sActiveHeaderRequests = 0; +volatile S32 LLMeshRepoThread::sActiveLODRequests = 0; +U32 LLMeshRepoThread::sMaxConcurrentRequests = 1; +S32 LLMeshRepoThread::sRequestLowWater = REQUEST2_LOW_WATER_MIN; +S32 LLMeshRepoThread::sRequestHighWater = REQUEST2_HIGH_WATER_MIN; +S32 LLMeshRepoThread::sRequestWaterLevel = 0; + +// Base handler class for all mesh users of llcorehttp. +// This is roughly equivalent to a Responder class in +// traditional LL code. The base is going to perform +// common response/data handling in the inherited +// onCompleted() method. Derived classes, one for each +// type of HTTP action, define processData() and +// processFailure() methods to customize handling and +// error messages. +// +// LLCore::HttpHandler +// LLMeshHandlerBase +// LLMeshHeaderHandler +// LLMeshLODHandler +// LLMeshSkinInfoHandler +// LLMeshDecompositionHandler +// LLMeshPhysicsShapeHandler +// LLMeshUploadThread + +class LLMeshHandlerBase : public LLCore::HttpHandler, + public std::enable_shared_from_this +{ +public: + typedef std::shared_ptr ptr_t; + + LOG_CLASS(LLMeshHandlerBase); + LLMeshHandlerBase(U32 offset, U32 requested_bytes) + : LLCore::HttpHandler(), + mMeshParams(), + mProcessed(false), + mHttpHandle(LLCORE_HTTP_HANDLE_INVALID), + mOffset(offset), + mRequestedBytes(requested_bytes) + {} + + virtual ~LLMeshHandlerBase() + {} + +protected: + LLMeshHandlerBase(const LLMeshHandlerBase &); // Not defined + void operator=(const LLMeshHandlerBase &); // Not defined + +public: + virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); + virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size) = 0; + virtual void processFailure(LLCore::HttpStatus status) = 0; + +public: + LLVolumeParams mMeshParams; + bool mProcessed; + LLCore::HttpHandle mHttpHandle; + U32 mOffset; + U32 mRequestedBytes; +}; + + +// Subclass for header fetches. +// +// Thread: repo +class LLMeshHeaderHandler : public LLMeshHandlerBase +{ +public: + LOG_CLASS(LLMeshHeaderHandler); + LLMeshHeaderHandler(const LLVolumeParams & mesh_params, U32 offset, U32 requested_bytes) + : LLMeshHandlerBase(offset, requested_bytes) + { + mMeshParams = mesh_params; + LLMeshRepoThread::incActiveHeaderRequests(); + } + virtual ~LLMeshHeaderHandler(); + +protected: + LLMeshHeaderHandler(const LLMeshHeaderHandler &); // Not defined + void operator=(const LLMeshHeaderHandler &); // Not defined + +public: + virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size); + virtual void processFailure(LLCore::HttpStatus status); +}; + + +// Subclass for LOD fetches. +// +// Thread: repo +class LLMeshLODHandler : public LLMeshHandlerBase +{ +public: + LOG_CLASS(LLMeshLODHandler); + LLMeshLODHandler(const LLVolumeParams & mesh_params, S32 lod, U32 offset, U32 requested_bytes) + : LLMeshHandlerBase(offset, requested_bytes), + mLOD(lod) + { + mMeshParams = mesh_params; + LLMeshRepoThread::incActiveLODRequests(); + } + virtual ~LLMeshLODHandler(); + +protected: + LLMeshLODHandler(const LLMeshLODHandler &); // Not defined + void operator=(const LLMeshLODHandler &); // Not defined + +public: + virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size); + virtual void processFailure(LLCore::HttpStatus status); + +public: + S32 mLOD; +}; + + +// Subclass for skin info fetches. +// +// Thread: repo +class LLMeshSkinInfoHandler : public LLMeshHandlerBase +{ +public: + LOG_CLASS(LLMeshSkinInfoHandler); + LLMeshSkinInfoHandler(const LLUUID& id, U32 offset, U32 requested_bytes) + : LLMeshHandlerBase(offset, requested_bytes), + mMeshID(id) + {} + virtual ~LLMeshSkinInfoHandler(); + +protected: + LLMeshSkinInfoHandler(const LLMeshSkinInfoHandler &); // Not defined + void operator=(const LLMeshSkinInfoHandler &); // Not defined + +public: + virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size); + virtual void processFailure(LLCore::HttpStatus status); + +public: + LLUUID mMeshID; +}; + + +// Subclass for decomposition fetches. +// +// Thread: repo +class LLMeshDecompositionHandler : public LLMeshHandlerBase +{ +public: + LOG_CLASS(LLMeshDecompositionHandler); + LLMeshDecompositionHandler(const LLUUID& id, U32 offset, U32 requested_bytes) + : LLMeshHandlerBase(offset, requested_bytes), + mMeshID(id) + {} + virtual ~LLMeshDecompositionHandler(); + +protected: + LLMeshDecompositionHandler(const LLMeshDecompositionHandler &); // Not defined + void operator=(const LLMeshDecompositionHandler &); // Not defined + +public: + virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size); + virtual void processFailure(LLCore::HttpStatus status); + +public: + LLUUID mMeshID; +}; + + +// Subclass for physics shape fetches. +// +// Thread: repo +class LLMeshPhysicsShapeHandler : public LLMeshHandlerBase +{ +public: + LOG_CLASS(LLMeshPhysicsShapeHandler); + LLMeshPhysicsShapeHandler(const LLUUID& id, U32 offset, U32 requested_bytes) + : LLMeshHandlerBase(offset, requested_bytes), + mMeshID(id) + {} + virtual ~LLMeshPhysicsShapeHandler(); + +protected: + LLMeshPhysicsShapeHandler(const LLMeshPhysicsShapeHandler &); // Not defined + void operator=(const LLMeshPhysicsShapeHandler &); // Not defined + +public: + virtual void processData(LLCore::BufferArray * body, S32 body_offset, U8 * data, S32 data_size); + virtual void processFailure(LLCore::HttpStatus status); + +public: + LLUUID mMeshID; +}; + + +void log_upload_error(LLCore::HttpStatus status, const LLSD& content, + const char * const stage, const std::string & model_name) +{ + // Add notification popup. + LLSD args; + std::string message = content["error"]["message"].asString(); + std::string identifier = content["error"]["identifier"].asString(); + args["MESSAGE"] = message; + args["IDENTIFIER"] = identifier; + args["LABEL"] = model_name; + + // Log details. + LL_WARNS(LOG_MESH) << "Error in stage: " << stage + << ", Reason: " << status.toString() + << " (" << status.toTerseString() << ")" << LL_ENDL; + + std::ostringstream details; + typedef std::set mav_errors_set_t; + mav_errors_set_t mav_errors; + + if (content.has("error")) + { + const LLSD& err = content["error"]; + LL_WARNS(LOG_MESH) << "error: " << err << LL_ENDL; + LL_WARNS(LOG_MESH) << " mesh upload failed, stage '" << stage + << "', error '" << err["error"].asString() + << "', message '" << err["message"].asString() + << "', id '" << err["identifier"].asString() + << "'" << LL_ENDL; + + if (err.has("errors")) + { + details << std::endl << std::endl; + + S32 error_num = 0; + const LLSD& err_list = err["errors"]; + for (LLSD::array_const_iterator it = err_list.beginArray(); + it != err_list.endArray(); + ++it) + { + const LLSD& err_entry = *it; + std::string message = err_entry["message"]; + + if (message.length() > 0) + { + mav_errors.insert(message); + } + + LL_WARNS(LOG_MESH) << " error[" << error_num << "]:" << LL_ENDL; + for (LLSD::map_const_iterator map_it = err_entry.beginMap(); + map_it != err_entry.endMap(); + ++map_it) + { + LL_WARNS(LOG_MESH) << " " << map_it->first << ": " + << map_it->second << LL_ENDL; + } + error_num++; + } + } + } + else + { + LL_WARNS(LOG_MESH) << "Bad response to mesh request, no additional error information available." << LL_ENDL; + } + + mav_errors_set_t::iterator mav_errors_it = mav_errors.begin(); + for (; mav_errors_it != mav_errors.end(); ++mav_errors_it) + { + std::string mav_details = "Mav_Details_" + *mav_errors_it; + details << "Message: '" << *mav_errors_it << "': " << LLTrans::getString(mav_details) << std::endl << std::endl; + } + + std::string details_str = details.str(); + if (details_str.length() > 0) + { + args["DETAILS"] = details_str; + } + + gMeshRepo.uploadError(args); +} + +LLMeshRepoThread::LLMeshRepoThread() +: LLThread("mesh repo"), + mHttpRequest(NULL), + mHttpOptions(), + mHttpLargeOptions(), + mHttpHeaders(), + mHttpPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), + mHttpLargePolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID) +{ + LLAppCoreHttp & app_core_http(LLAppViewer::instance()->getAppCoreHttp()); + + mMutex = new LLMutex(); + mHeaderMutex = new LLMutex(); + mSignal = new LLCondition(); + mHttpRequest = new LLCore::HttpRequest; + mHttpOptions = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); + mHttpOptions->setTransferTimeout(SMALL_MESH_XFER_TIMEOUT); + mHttpOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter")); + mHttpLargeOptions = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); + mHttpLargeOptions->setTransferTimeout(LARGE_MESH_XFER_TIMEOUT); + mHttpLargeOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter")); + mHttpHeaders = LLCore::HttpHeaders::ptr_t(new LLCore::HttpHeaders); + mHttpHeaders->append(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_VND_LL_MESH); + mHttpPolicyClass = app_core_http.getPolicy(LLAppCoreHttp::AP_MESH2); + mHttpLargePolicyClass = app_core_http.getPolicy(LLAppCoreHttp::AP_LARGE_MESH); +} + + +LLMeshRepoThread::~LLMeshRepoThread() +{ + LL_INFOS(LOG_MESH) << "Small GETs issued: " << LLMeshRepository::sHTTPRequestCount + << ", Large GETs issued: " << LLMeshRepository::sHTTPLargeRequestCount + << ", Max Lock Holdoffs: " << LLMeshRepository::sMaxLockHoldoffs + << LL_ENDL; + + mHttpRequestSet.clear(); + mHttpHeaders.reset(); + + while (!mSkinInfoQ.empty()) + { + delete mSkinInfoQ.front(); + mSkinInfoQ.pop_front(); + } + + while (!mDecompositionQ.empty()) + { + delete mDecompositionQ.front(); + mDecompositionQ.pop_front(); + } + + delete mHttpRequest; + mHttpRequest = NULL; + delete mMutex; + mMutex = NULL; + delete mHeaderMutex; + mHeaderMutex = NULL; + delete mSignal; + mSignal = NULL; +} + +void LLMeshRepoThread::run() +{ + LLCDResult res = LLConvexDecomposition::initThread(); + if (res != LLCD_OK && LLConvexDecomposition::isFunctional()) + { + LL_WARNS(LOG_MESH) << "Convex decomposition unable to be loaded. Expect severe problems." << LL_ENDL; + } + + while (!LLApp::isExiting()) + { + // *TODO: Revise sleep/wake strategy and try to move away + // from polling operations in this thread. We can sleep + // this thread hard when: + // * All Http requests are serviced + // * LOD request queue empty + // * Header request queue empty + // * Skin info request queue empty + // * Decomposition request queue empty + // * Physics shape request queue empty + // We wake the thread when any of the above become untrue. + // Will likely need a correctly-implemented condition variable to do this. + // On the other hand, this may actually be an effective and efficient scheme... + + mSignal->wait(); + + if (LLApp::isExiting()) + { + break; + } + + if (! mHttpRequestSet.empty()) + { + // Dispatch all HttpHandler notifications + mHttpRequest->update(0L); + } + sRequestWaterLevel = mHttpRequestSet.size(); // Stats data update + + // NOTE: order of queue processing intentionally favors LOD requests over header requests + // Todo: we are processing mLODReqQ, mHeaderReqQ, mSkinRequests, mDecompositionRequests and mPhysicsShapeRequests + // in relatively similar manners, remake code to simplify/unify the process, + // like processRequests(&requestQ, fetchFunction); which does same thing for each element + + if (!mLODReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) + { + std::list incomplete; + while (!mLODReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) + { + if (!mMutex) + { + break; + } + + mMutex->lock(); + LODRequest req = mLODReqQ.front(); + mLODReqQ.pop(); + LLMeshRepository::sLODProcessing--; + mMutex->unlock(); + if (req.isDelayed()) + { + // failed to load before, wait a bit + incomplete.push_front(req); + } + else if (!fetchMeshLOD(req.mMeshParams, req.mLOD, req.canRetry())) + { + if (req.canRetry()) + { + // failed, resubmit + req.updateTime(); + incomplete.push_front(req); + } + else + { + // too many fails + LLMutexLock lock(mMutex); + mUnavailableQ.push_back(req); + LL_WARNS() << "Failed to load " << req.mMeshParams << " , skip" << LL_ENDL; + } + } + } + + if (!incomplete.empty()) + { + LLMutexLock locker(mMutex); + for (std::list::iterator iter = incomplete.begin(); iter != incomplete.end(); iter++) + { + mLODReqQ.push(*iter); + ++LLMeshRepository::sLODProcessing; + } + } + } + + if (!mHeaderReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) + { + std::list incomplete; + while (!mHeaderReqQ.empty() && mHttpRequestSet.size() < sRequestHighWater) + { + if (!mMutex) + { + break; + } + + mMutex->lock(); + HeaderRequest req = mHeaderReqQ.front(); + mHeaderReqQ.pop(); + mMutex->unlock(); + if (req.isDelayed()) + { + // failed to load before, wait a bit + incomplete.push_front(req); + } + else if (!fetchMeshHeader(req.mMeshParams, req.canRetry())) + { + if (req.canRetry()) + { + //failed, resubmit + req.updateTime(); + incomplete.push_front(req); + } + else + { + LL_DEBUGS() << "mHeaderReqQ failed: " << req.mMeshParams << LL_ENDL; + } + } + } + + if (!incomplete.empty()) + { + LLMutexLock locker(mMutex); + for (std::list::iterator iter = incomplete.begin(); iter != incomplete.end(); iter++) + { + mHeaderReqQ.push(*iter); + } + } + } + + // For the final three request lists, similar goal to above but + // slightly different queue structures. Stay off the mutex when + // performing long-duration actions. + + if (mHttpRequestSet.size() < sRequestHighWater + && (!mSkinRequests.empty() + || !mDecompositionRequests.empty() + || !mPhysicsShapeRequests.empty())) + { + // Something to do probably, lock and double-check. We don't want + // to hold the lock long here. That will stall main thread activities + // so we bounce it. + + if (!mSkinRequests.empty()) + { + std::list incomplete; + while (!mSkinRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) + { + + mMutex->lock(); + auto req = mSkinRequests.front(); + mSkinRequests.pop_front(); + mMutex->unlock(); + if (req.isDelayed()) + { + incomplete.emplace_back(req); + } + else if (!fetchMeshSkinInfo(req.mId, req.canRetry())) + { + if (req.canRetry()) + { + req.updateTime(); + incomplete.emplace_back(req); + } + else + { + LLMutexLock locker(mMutex); + mSkinUnavailableQ.push_back(req); + LL_DEBUGS() << "mSkinReqQ failed: " << req.mId << LL_ENDL; + } + } + } + + if (!incomplete.empty()) + { + LLMutexLock locker(mMutex); + for (const auto& req : incomplete) + { + mSkinRequests.push_back(req); + } + } + } + + // holding lock, try next list + // *TODO: For UI/debug-oriented lists, we might drop the fine- + // grained locking as there's a lowered expectation of smoothness + // in these cases. + if (!mDecompositionRequests.empty()) + { + std::set incomplete; + while (!mDecompositionRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) + { + mMutex->lock(); + std::set::iterator iter = mDecompositionRequests.begin(); + UUIDBasedRequest req = *iter; + mDecompositionRequests.erase(iter); + mMutex->unlock(); + if (req.isDelayed()) + { + incomplete.insert(req); + } + else if (!fetchMeshDecomposition(req.mId)) + { + if (req.canRetry()) + { + req.updateTime(); + incomplete.insert(req); + } + else + { + LL_DEBUGS() << "mDecompositionRequests failed: " << req.mId << LL_ENDL; + } + } + } + + if (!incomplete.empty()) + { + LLMutexLock locker(mMutex); + mDecompositionRequests.insert(incomplete.begin(), incomplete.end()); + } + } + + // holding lock, final list + if (!mPhysicsShapeRequests.empty()) + { + std::set incomplete; + while (!mPhysicsShapeRequests.empty() && mHttpRequestSet.size() < sRequestHighWater) + { + mMutex->lock(); + std::set::iterator iter = mPhysicsShapeRequests.begin(); + UUIDBasedRequest req = *iter; + mPhysicsShapeRequests.erase(iter); + mMutex->unlock(); + if (req.isDelayed()) + { + incomplete.insert(req); + } + else if (!fetchMeshPhysicsShape(req.mId)) + { + if (req.canRetry()) + { + req.updateTime(); + incomplete.insert(req); + } + else + { + LL_DEBUGS() << "mPhysicsShapeRequests failed: " << req.mId << LL_ENDL; + } + } + } + + if (!incomplete.empty()) + { + LLMutexLock locker(mMutex); + mPhysicsShapeRequests.insert(incomplete.begin(), incomplete.end()); + } + } + } + + // For dev purposes only. A dynamic change could make this false + // and that shouldn't assert. + // llassert_always(mHttpRequestSet.size() <= sRequestHighWater); + } + + if (mSignal->isLocked()) + { //make sure to let go of the mutex associated with the given signal before shutting down + mSignal->unlock(); + } + + res = LLConvexDecomposition::quitThread(); + if (res != LLCD_OK && LLConvexDecomposition::isFunctional()) + { + LL_WARNS(LOG_MESH) << "Convex decomposition unable to be quit." << LL_ENDL; + } +} + +// Mutex: LLMeshRepoThread::mMutex must be held on entry +void LLMeshRepoThread::loadMeshSkinInfo(const LLUUID& mesh_id) +{ + mSkinRequests.push_back(UUIDBasedRequest(mesh_id)); +} + +// Mutex: LLMeshRepoThread::mMutex must be held on entry +void LLMeshRepoThread::loadMeshDecomposition(const LLUUID& mesh_id) +{ + mDecompositionRequests.insert(UUIDBasedRequest(mesh_id)); +} + +// Mutex: LLMeshRepoThread::mMutex must be held on entry +void LLMeshRepoThread::loadMeshPhysicsShape(const LLUUID& mesh_id) +{ + mPhysicsShapeRequests.insert(UUIDBasedRequest(mesh_id)); +} + +void LLMeshRepoThread::lockAndLoadMeshLOD(const LLVolumeParams& mesh_params, S32 lod) +{ + if (!LLAppViewer::isExiting()) + { + loadMeshLOD(mesh_params, lod); + } +} + + +void LLMeshRepoThread::loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod) +{ //could be called from any thread + const LLUUID& mesh_id = mesh_params.getSculptID(); + LLMutexLock lock(mMutex); + LLMutexLock header_lock(mHeaderMutex); + mesh_header_map::iterator iter = mMeshHeader.find(mesh_id); + if (iter != mMeshHeader.end()) + { //if we have the header, request LOD byte range + + LODRequest req(mesh_params, lod); + { + mLODReqQ.push(req); + LLMeshRepository::sLODProcessing++; + } + } + else + { + HeaderRequest req(mesh_params); + pending_lod_map::iterator pending = mPendingLOD.find(mesh_id); + + if (pending != mPendingLOD.end()) + { //append this lod request to existing header request + pending->second.push_back(lod); + llassert(pending->second.size() <= LLModel::NUM_LODS); + } + else + { //if no header request is pending, fetch header + mHeaderReqQ.push(req); + mPendingLOD[mesh_id].push_back(lod); + } + } +} + +// Mutex: must be holding mMutex when called +void LLMeshRepoThread::setGetMeshCap(const std::string & mesh_cap) +{ + mGetMeshCapability = mesh_cap; +} + + +// Constructs a Cap URL for the mesh. Prefers a GetMesh2 cap +// over a GetMesh cap. +// +// Mutex: acquires mMutex +void LLMeshRepoThread::constructUrl(LLUUID mesh_id, std::string * url) +{ + std::string res_url; + + if (gAgent.getRegion()) + { + { + LLMutexLock lock(mMutex); + res_url = mGetMeshCapability; + } + + if (!res_url.empty()) + { + res_url += "/?mesh_id="; + res_url += mesh_id.asString().c_str(); + } + else + { + LL_WARNS_ONCE(LOG_MESH) << "Current region does not have ViewerAsset capability! Cannot load meshes. Region id: " + << gAgent.getRegion()->getRegionID() << LL_ENDL; + LL_DEBUGS_ONCE(LOG_MESH) << "Cannot load mesh " << mesh_id << " due to missing capability." << LL_ENDL; + } + } + else + { + LL_WARNS_ONCE(LOG_MESH) << "Current region is not loaded so there is no capability to load from! Cannot load meshes." << LL_ENDL; + LL_DEBUGS_ONCE(LOG_MESH) << "Cannot load mesh " << mesh_id << " due to missing capability." << LL_ENDL; + } + + *url = res_url; +} + +// Issue an HTTP GET request with byte range using the right +// policy class. +// +// @return Valid handle or LLCORE_HTTP_HANDLE_INVALID. +// If the latter, actual status is found in +// mHttpStatus member which is valid until the +// next call to this method. +// +// Thread: repo +LLCore::HttpHandle LLMeshRepoThread::getByteRange(const std::string & url, + size_t offset, size_t len, + const LLCore::HttpHandler::ptr_t &handler) +{ + // Also used in lltexturefetch.cpp + static LLCachedControl disable_range_req(gSavedSettings, "HttpRangeRequestsDisable", false); + + LLCore::HttpHandle handle(LLCORE_HTTP_HANDLE_INVALID); + + if (len < LARGE_MESH_FETCH_THRESHOLD) + { + handle = mHttpRequest->requestGetByteRange( mHttpPolicyClass, + url, + (disable_range_req ? size_t(0) : offset), + (disable_range_req ? size_t(0) : len), + mHttpOptions, + mHttpHeaders, + handler); + if (LLCORE_HTTP_HANDLE_INVALID != handle) + { + ++LLMeshRepository::sHTTPRequestCount; + } + } + else + { + handle = mHttpRequest->requestGetByteRange(mHttpLargePolicyClass, + url, + (disable_range_req ? size_t(0) : offset), + (disable_range_req ? size_t(0) : len), + mHttpLargeOptions, + mHttpHeaders, + handler); + if (LLCORE_HTTP_HANDLE_INVALID != handle) + { + ++LLMeshRepository::sHTTPLargeRequestCount; + } + } + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + // Something went wrong, capture the error code for caller. + mHttpStatus = mHttpRequest->getStatus(); + } + return handle; +} + + +bool LLMeshRepoThread::fetchMeshSkinInfo(const LLUUID& mesh_id, bool can_retry) +{ + + if (!mHeaderMutex) + { + return false; + } + + mHeaderMutex->lock(); + + auto header_it = mMeshHeader.find(mesh_id); + if (header_it == mMeshHeader.end()) + { //we have no header info for this mesh, do nothing + mHeaderMutex->unlock(); + return false; + } + + ++LLMeshRepository::sMeshRequestCount; + bool ret = true; + U32 header_size = header_it->second.first; + + if (header_size > 0) + { + const LLMeshHeader& header = header_it->second.second; + + S32 version = header.mVersion; + S32 offset = header_size + header.mSkinOffset; + S32 size = header.mSkinSize; + + mHeaderMutex->unlock(); + + if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) + { + //check cache for mesh skin info + LLFileSystem file(mesh_id, LLAssetType::AT_MESH); + if (file.getSize() >= offset + size) + { + U8* buffer = new(std::nothrow) U8[size]; + if (!buffer) + { + LL_WARNS(LOG_MESH) << "Failed to allocate memory for skin info, size: " << size << LL_ENDL; + return false; + } + LLMeshRepository::sCacheBytesRead += size; + ++LLMeshRepository::sCacheReads; + file.seek(offset); + file.read(buffer, size); + + //make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written) + bool zero = true; + for (S32 i = 0; i < llmin(size, 1024) && zero; ++i) + { + zero = buffer[i] == 0; + } + + if (!zero) + { //attempt to parse + if (skinInfoReceived(mesh_id, buffer, size)) + { + delete[] buffer; + return true; + } + } + + delete[] buffer; + } + + //reading from cache failed for whatever reason, fetch from sim + std::string http_url; + constructUrl(mesh_id, &http_url); + + if (!http_url.empty()) + { + LLMeshHandlerBase::ptr_t handler(new LLMeshSkinInfoHandler(mesh_id, offset, size)); + LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + LL_WARNS(LOG_MESH) << "HTTP GET request failed for skin info on mesh " << mID + << ". Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toTerseString() << ")" + << LL_ENDL; + ret = false; + } + else if(can_retry) + { + handler->mHttpHandle = handle; + mHttpRequestSet.insert(handler); + } + else + { + LLMutexLock locker(mMutex); + mSkinUnavailableQ.emplace_back(mesh_id); + } + } + else + { + LLMutexLock locker(mMutex); + mSkinUnavailableQ.emplace_back(mesh_id); + } + } + else + { + LLMutexLock locker(mMutex); + mSkinUnavailableQ.emplace_back(mesh_id); + } + } + else + { + mHeaderMutex->unlock(); + } + + //early out was not hit, effectively fetched + return ret; +} + +bool LLMeshRepoThread::fetchMeshDecomposition(const LLUUID& mesh_id) +{ + if (!mHeaderMutex) + { + return false; + } + + mHeaderMutex->lock(); + + auto header_it = mMeshHeader.find(mesh_id); + if (header_it == mMeshHeader.end()) + { //we have no header info for this mesh, do nothing + mHeaderMutex->unlock(); + return false; + } + + ++LLMeshRepository::sMeshRequestCount; + U32 header_size = header_it->second.first; + bool ret = true; + + if (header_size > 0) + { + const auto& header = header_it->second.second; + S32 version = header.mVersion; + S32 offset = header_size + header.mPhysicsConvexOffset; + S32 size = header.mPhysicsConvexSize; + + mHeaderMutex->unlock(); + + if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) + { + //check cache for mesh skin info + LLFileSystem file(mesh_id, LLAssetType::AT_MESH); + if (file.getSize() >= offset+size) + { + U8* buffer = new(std::nothrow) U8[size]; + if (!buffer) + { + LL_WARNS(LOG_MESH) << "Failed to allocate memory for mesh decomposition, size: " << size << LL_ENDL; + return false; + } + LLMeshRepository::sCacheBytesRead += size; + ++LLMeshRepository::sCacheReads; + + file.seek(offset); + file.read(buffer, size); + + //make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written) + bool zero = true; + for (S32 i = 0; i < llmin(size, 1024) && zero; ++i) + { + zero = buffer[i] == 0; + } + + if (!zero) + { //attempt to parse + if (decompositionReceived(mesh_id, buffer, size)) + { + delete[] buffer; + return true; + } + } + + delete[] buffer; + } + + //reading from cache failed for whatever reason, fetch from sim + std::string http_url; + constructUrl(mesh_id, &http_url); + + if (!http_url.empty()) + { + LLMeshHandlerBase::ptr_t handler(new LLMeshDecompositionHandler(mesh_id, offset, size)); + LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + LL_WARNS(LOG_MESH) << "HTTP GET request failed for decomposition mesh " << mID + << ". Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toTerseString() << ")" + << LL_ENDL; + ret = false; + } + else + { + handler->mHttpHandle = handle; + mHttpRequestSet.insert(handler); + } + } + } + } + else + { + mHeaderMutex->unlock(); + } + + //early out was not hit, effectively fetched + return ret; +} + +bool LLMeshRepoThread::fetchMeshPhysicsShape(const LLUUID& mesh_id) +{ + if (!mHeaderMutex) + { + return false; + } + + mHeaderMutex->lock(); + + auto header_it = mMeshHeader.find(mesh_id); + if (header_it == mMeshHeader.end()) + { //we have no header info for this mesh, do nothing + mHeaderMutex->unlock(); + return false; + } + + ++LLMeshRepository::sMeshRequestCount; + U32 header_size = header_it->second.first; + bool ret = true; + + if (header_size > 0) + { + const auto& header = header_it->second.second; + S32 version = header.mVersion; + S32 offset = header_size + header.mPhysicsMeshOffset; + S32 size = header.mPhysicsMeshSize; + + mHeaderMutex->unlock(); + + if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) + { + //check cache for mesh physics shape info + LLFileSystem file(mesh_id, LLAssetType::AT_MESH); + if (file.getSize() >= offset+size) + { + LLMeshRepository::sCacheBytesRead += size; + ++LLMeshRepository::sCacheReads; + file.seek(offset); + U8* buffer = new(std::nothrow) U8[size]; + if (!buffer) + { + LL_WARNS(LOG_MESH) << "Failed to allocate memory for physics shape, size: " << size << LL_ENDL; + return false; + } + file.read(buffer, size); + + //make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written) + bool zero = true; + for (S32 i = 0; i < llmin(size, 1024) && zero; ++i) + { + zero = buffer[i] == 0; + } + + if (!zero) + { //attempt to parse + if (physicsShapeReceived(mesh_id, buffer, size) == MESH_OK) + { + delete[] buffer; + return true; + } + } + + delete[] buffer; + } + + //reading from cache failed for whatever reason, fetch from sim + std::string http_url; + constructUrl(mesh_id, &http_url); + + if (!http_url.empty()) + { + LLMeshHandlerBase::ptr_t handler(new LLMeshPhysicsShapeHandler(mesh_id, offset, size)); + LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + LL_WARNS(LOG_MESH) << "HTTP GET request failed for physics shape on mesh " << mID + << ". Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toTerseString() << ")" + << LL_ENDL; + ret = false; + } + else + { + handler->mHttpHandle = handle; + mHttpRequestSet.insert(handler); + } + } + } + else + { //no physics shape whatsoever, report back NULL + physicsShapeReceived(mesh_id, NULL, 0); + } + } + else + { + mHeaderMutex->unlock(); + } + + //early out was not hit, effectively fetched + return ret; +} + +//static +void LLMeshRepoThread::incActiveLODRequests() +{ + LLMutexLock lock(gMeshRepo.mThread->mMutex); + ++LLMeshRepoThread::sActiveLODRequests; +} + +//static +void LLMeshRepoThread::decActiveLODRequests() +{ + LLMutexLock lock(gMeshRepo.mThread->mMutex); + --LLMeshRepoThread::sActiveLODRequests; +} + +//static +void LLMeshRepoThread::incActiveHeaderRequests() +{ + LLMutexLock lock(gMeshRepo.mThread->mMutex); + ++LLMeshRepoThread::sActiveHeaderRequests; +} + +//static +void LLMeshRepoThread::decActiveHeaderRequests() +{ + LLMutexLock lock(gMeshRepo.mThread->mMutex); + --LLMeshRepoThread::sActiveHeaderRequests; +} + +//return false if failed to get header +bool LLMeshRepoThread::fetchMeshHeader(const LLVolumeParams& mesh_params, bool can_retry) +{ + ++LLMeshRepository::sMeshRequestCount; + + { + //look for mesh in asset in cache + LLFileSystem file(mesh_params.getSculptID(), LLAssetType::AT_MESH); + + S32 size = file.getSize(); + + if (size > 0) + { + // *NOTE: if the header size is ever more than 4KB, this will break + U8 buffer[MESH_HEADER_SIZE]; + S32 bytes = llmin(size, MESH_HEADER_SIZE); + LLMeshRepository::sCacheBytesRead += bytes; + ++LLMeshRepository::sCacheReads; + file.read(buffer, bytes); + if (headerReceived(mesh_params, buffer, bytes) == MESH_OK) + { + std::string mid; + mesh_params.getSculptID().toString(mid); + LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh header for ID " << mid << " - was retrieved from the cache." << LL_ENDL; + + // Found mesh in cache + return true; + } + } + } + + //either cache entry doesn't exist or is corrupt, request header from simulator + bool retval = true; + std::string http_url; + constructUrl(mesh_params.getSculptID(), &http_url); + + + if (!http_url.empty()) + { + std::string mid; + mesh_params.getSculptID().toString(mid); + LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh header for ID " << mid << " - was retrieved from the simulator." << LL_ENDL; + + //grab first 4KB if we're going to bother with a fetch. Cache will prevent future fetches if a full mesh fits + //within the first 4KB + //NOTE -- this will break of headers ever exceed 4KB + + LLMeshHandlerBase::ptr_t handler(new LLMeshHeaderHandler(mesh_params, 0, MESH_HEADER_SIZE)); + LLCore::HttpHandle handle = getByteRange(http_url, 0, MESH_HEADER_SIZE, handler); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + LL_WARNS(LOG_MESH) << "HTTP GET request failed for mesh header " << mID + << ". Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toTerseString() << ")" + << LL_ENDL; + retval = false; + } + else if (can_retry) + { + handler->mHttpHandle = handle; + mHttpRequestSet.insert(handler); + } + } + + return retval; +} + +//return false if failed to get mesh lod. +bool LLMeshRepoThread::fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, bool can_retry) +{ + if (!mHeaderMutex) + { + return false; + } + + const LLUUID& mesh_id = mesh_params.getSculptID(); + + mHeaderMutex->lock(); + auto header_it = mMeshHeader.find(mesh_id); + if (header_it == mMeshHeader.end()) + { //we have no header info for this mesh, do nothing + mHeaderMutex->unlock(); + return false; + } + ++LLMeshRepository::sMeshRequestCount; + bool retval = true; + + U32 header_size = header_it->second.first; + if (header_size > 0) + { + const auto& header = header_it->second.second; + S32 version = header.mVersion; + S32 offset = header_size + header.mLodOffset[lod]; + S32 size = header.mLodSize[lod]; + mHeaderMutex->unlock(); + + if (version <= MAX_MESH_VERSION && offset >= 0 && size > 0) + { + + //check cache for mesh asset + LLFileSystem file(mesh_id, LLAssetType::AT_MESH); + if (file.getSize() >= offset+size) + { + U8* buffer = new(std::nothrow) U8[size]; + if (!buffer) + { + LL_WARNS(LOG_MESH) << "Can't allocate memory for mesh " << mesh_id << " LOD " << lod << ", size: " << size << LL_ENDL; + // todo: for now it will result in indefinite constant retries, should result in timeout + // or in retry-count and disabling mesh. (but usually viewer is beyond saving at this point) + return false; + } + LLMeshRepository::sCacheBytesRead += size; + ++LLMeshRepository::sCacheReads; + file.seek(offset); + file.read(buffer, size); + + //make sure buffer isn't all 0's by checking the first 1KB (reserved block but not written) + bool zero = true; + for (S32 i = 0; i < llmin(size, 1024) && zero; ++i) + { + zero = buffer[i] == 0; + } + + if (!zero) + { //attempt to parse + if (lodReceived(mesh_params, lod, buffer, size) == MESH_OK) + { + delete[] buffer; + + std::string mid; + mesh_id.toString(mid); + LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh body for ID " << mid << " - was retrieved from the cache." << LL_ENDL; + + return true; + } + } + + delete[] buffer; + } + + //reading from cache failed for whatever reason, fetch from sim + std::string http_url; + constructUrl(mesh_id, &http_url); + + if (!http_url.empty()) + { + std::string mid; + mesh_id.toString(mid); + LL_DEBUGS(LOG_MESH) << "Mesh/Cache: Mesh body for ID " << mid << " - was retrieved from the simulator." << LL_ENDL; + + LLMeshHandlerBase::ptr_t handler(new LLMeshLODHandler(mesh_params, lod, offset, size)); + LLCore::HttpHandle handle = getByteRange(http_url, offset, size, handler); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + LL_WARNS(LOG_MESH) << "HTTP GET request failed for LOD on mesh " << mID + << ". Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toTerseString() << ")" + << LL_ENDL; + retval = false; + } + else if (can_retry) + { + handler->mHttpHandle = handle; + mHttpRequestSet.insert(handler); + // *NOTE: Allowing a re-request, not marking as unavailable. Is that correct? + } + else + { + LLMutexLock lock(mMutex); + mUnavailableQ.push_back(LODRequest(mesh_params, lod)); + } + } + else + { + LLMutexLock lock(mMutex); + mUnavailableQ.push_back(LODRequest(mesh_params, lod)); + } + } + else + { + LLMutexLock lock(mMutex); + mUnavailableQ.push_back(LODRequest(mesh_params, lod)); + } + } + else + { + mHeaderMutex->unlock(); + } + + return retval; +} + +EMeshProcessingResult LLMeshRepoThread::headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size) +{ + const LLUUID mesh_id = mesh_params.getSculptID(); + LLSD header_data; + + LLMeshHeader header; + + llssize header_size = 0; + if (data_size > 0) + { + llssize dsize = data_size; + char* result_ptr = strip_deprecated_header((char*)data, dsize, &header_size); + + data_size = dsize; + + boost::iostreams::stream stream(result_ptr, data_size); + + if (!LLSDSerialize::fromBinary(header_data, stream, data_size)) + { + LL_WARNS(LOG_MESH) << "Mesh header parse error. Not a valid mesh asset! ID: " << mesh_id + << LL_ENDL; + return MESH_PARSE_FAILURE; + } + + if (!header_data.isMap()) + { + LL_WARNS(LOG_MESH) << "Mesh header is invalid for ID: " << mesh_id << LL_ENDL; + return MESH_INVALID; + } + + header.fromLLSD(header_data); + + if (header.mVersion > MAX_MESH_VERSION) + { + LL_INFOS(LOG_MESH) << "Wrong version in header for " << mesh_id << LL_ENDL; + header.m404 = true; + } + // make sure there is at least one lod, function returns -1 and marks as 404 otherwise + else if (LLMeshRepository::getActualMeshLOD(header, 0) >= 0) + { + header_size += stream.tellg(); + } + } + else + { + LL_INFOS(LOG_MESH) << "Non-positive data size. Marking header as non-existent, will not retry. ID: " << mesh_id + << LL_ENDL; + header.m404 = 1; + } + + { + + { + LLMutexLock lock(mHeaderMutex); + mMeshHeader[mesh_id] = { header_size, header }; + LLMeshRepository::sCacheBytesHeaders += header_size; + } + + LLMutexLock lock(mMutex); // make sure only one thread access mPendingLOD at the same time. + + //check for pending requests + pending_lod_map::iterator iter = mPendingLOD.find(mesh_id); + if (iter != mPendingLOD.end()) + { + for (U32 i = 0; i < iter->second.size(); ++i) + { + LODRequest req(mesh_params, iter->second[i]); + mLODReqQ.push(req); + LLMeshRepository::sLODProcessing++; + } + mPendingLOD.erase(iter); + } + } + + return MESH_OK; +} + +EMeshProcessingResult LLMeshRepoThread::lodReceived(const LLVolumeParams& mesh_params, S32 lod, U8* data, S32 data_size) +{ + if (data == NULL || data_size == 0) + { + return MESH_NO_DATA; + } + + LLPointer volume = new LLVolume(mesh_params, LLVolumeLODGroup::getVolumeScaleFromDetail(lod)); + if (volume->unpackVolumeFaces(data, data_size)) + { + if (volume->getNumFaces() > 0) + { + LoadedMesh mesh(volume, mesh_params, lod); + { + LLMutexLock lock(mMutex); + mLoadedQ.push_back(mesh); + // LLPointer is not thread safe, since we added this pointer into + // threaded list, make sure counter gets decreased inside mutex lock + // and won't affect mLoadedQ processing + volume = NULL; + // might be good idea to turn mesh into pointer to avoid making a copy + mesh.mVolume = NULL; + } + return MESH_OK; + } + } + + return MESH_UNKNOWN; +} + +bool LLMeshRepoThread::skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 data_size) +{ + LLSD skin; + + if (data_size > 0) + { + try + { + U32 uzip_result = LLUZipHelper::unzip_llsd(skin, data, data_size); + if (uzip_result != LLUZipHelper::ZR_OK) + { + LL_WARNS(LOG_MESH) << "Mesh skin info parse error. Not a valid mesh asset! ID: " << mesh_id + << " uzip result" << uzip_result + << LL_ENDL; + return false; + } + } + catch (std::bad_alloc&) + { + LL_WARNS(LOG_MESH) << "Out of memory for mesh ID " << mesh_id << " of size: " << data_size << LL_ENDL; + return false; + } + } + + { + LLMeshSkinInfo* info = nullptr; + try + { + info = new LLMeshSkinInfo(mesh_id, skin); + } + catch (const std::bad_alloc& ex) + { + LL_WARNS() << "Failed to allocate skin info with exception: " << ex.what() << LL_ENDL; + return false; + } + + // LL_DEBUGS(LOG_MESH) << "info pelvis offset" << info.mPelvisOffset << LL_ENDL; + { + LLMutexLock lock(mMutex); + mSkinInfoQ.push_back(info); + } + } + + return true; +} + +bool LLMeshRepoThread::decompositionReceived(const LLUUID& mesh_id, U8* data, S32 data_size) +{ + LLSD decomp; + + if (data_size > 0) + { + try + { + U32 uzip_result = LLUZipHelper::unzip_llsd(decomp, data, data_size); + if (uzip_result != LLUZipHelper::ZR_OK) + { + LL_WARNS(LOG_MESH) << "Mesh decomposition parse error. Not a valid mesh asset! ID: " << mesh_id + << " uzip result: " << uzip_result + << LL_ENDL; + return false; + } + } + catch (const std::bad_alloc&) + { + LL_WARNS(LOG_MESH) << "Out of memory for mesh ID " << mesh_id << " of size: " << data_size << LL_ENDL; + return false; + } + } + + { + LLModel::Decomposition* d = new LLModel::Decomposition(decomp); + d->mMeshID = mesh_id; + { + LLMutexLock lock(mMutex); + mDecompositionQ.push_back(d); + } + } + + return true; +} + +EMeshProcessingResult LLMeshRepoThread::physicsShapeReceived(const LLUUID& mesh_id, U8* data, S32 data_size) +{ + LLSD physics_shape; + + LLModel::Decomposition* d = new LLModel::Decomposition(); + d->mMeshID = mesh_id; + + if (data == NULL) + { //no data, no physics shape exists + d->mPhysicsShapeMesh.clear(); + } + else + { + LLVolumeParams volume_params; + volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE); + volume_params.setSculptID(mesh_id, LL_SCULPT_TYPE_MESH); + LLPointer volume = new LLVolume(volume_params,0); + + if (volume->unpackVolumeFaces(data, data_size)) + { + d->mPhysicsShapeMesh.clear(); + + std::vector& pos = d->mPhysicsShapeMesh.mPositions; + std::vector& norm = d->mPhysicsShapeMesh.mNormals; + + for (S32 i = 0; i < volume->getNumVolumeFaces(); ++i) + { + const LLVolumeFace& face = volume->getVolumeFace(i); + + for (S32 i = 0; i < face.mNumIndices; ++i) + { + U16 idx = face.mIndices[i]; + + pos.push_back(LLVector3(face.mPositions[idx].getF32ptr())); + norm.push_back(LLVector3(face.mNormals[idx].getF32ptr())); + } + } + } + } + + { + LLMutexLock lock(mMutex); + mDecompositionQ.push_back(d); + } + return MESH_OK; +} + +LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list& data, LLVector3& scale, bool upload_textures, + bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, + const std::string & upload_url, bool do_upload, + LLHandle fee_observer, + LLHandle upload_observer) + : LLThread("mesh upload"), + LLCore::HttpHandler(), + mDiscarded(false), + mDoUpload(do_upload), + mWholeModelUploadURL(upload_url), + mFeeObserverHandle(fee_observer), + mUploadObserverHandle(upload_observer) +{ + mInstanceList = data; + mUploadTextures = upload_textures; + mUploadSkin = upload_skin; + mUploadJoints = upload_joints; + mLockScaleIfJointPosition = lock_scale_if_joint_position; + mMutex = new LLMutex(); + mPendingUploads = 0; + mFinished = false; + mOrigin = gAgent.getPositionAgent(); + mHost = gAgent.getRegionHost(); + + mWholeModelFeeCapability = gAgent.getRegionCapability("NewFileAgentInventory"); + + mOrigin += gAgent.getAtAxis() * scale.magVec(); + + mMeshUploadTimeOut = gSavedSettings.getS32("MeshUploadTimeOut"); + + mHttpRequest = new LLCore::HttpRequest; + mHttpOptions = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); + mHttpOptions->setTransferTimeout(mMeshUploadTimeOut); + mHttpOptions->setUseRetryAfter(gSavedSettings.getBOOL("MeshUseHttpRetryAfter")); + mHttpOptions->setRetries(UPLOAD_RETRY_LIMIT); + mHttpHeaders = LLCore::HttpHeaders::ptr_t(new LLCore::HttpHeaders); + mHttpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_LLSD_XML); + mHttpPolicyClass = LLAppViewer::instance()->getAppCoreHttp().getPolicy(LLAppCoreHttp::AP_UPLOADS); +} + +LLMeshUploadThread::~LLMeshUploadThread() +{ + delete mHttpRequest; + mHttpRequest = NULL; + delete mMutex; + mMutex = NULL; + +} + +LLMeshUploadThread::DecompRequest::DecompRequest(LLModel* mdl, LLModel* base_model, LLMeshUploadThread* thread) +{ + mStage = "single_hull"; + mModel = mdl; + mDecompID = &mdl->mDecompID; + mBaseModel = base_model; + mThread = thread; + + //copy out positions and indices + assignData(mdl) ; + + mThread->mFinalDecomp = this; + mThread->mPhysicsComplete = false; +} + +void LLMeshUploadThread::DecompRequest::completed() +{ + if (mThread->mFinalDecomp == this) + { + mThread->mPhysicsComplete = true; + } + + llassert(mHull.size() == 1); + + mThread->mHullMap[mBaseModel] = mHull[0]; +} + +//called in the main thread. +void LLMeshUploadThread::preStart() +{ + //build map of LLModel refs to instances for callbacks + for (instance_list::iterator iter = mInstanceList.begin(); iter != mInstanceList.end(); ++iter) + { + mInstance[iter->mModel].push_back(*iter); + } +} + +void LLMeshUploadThread::discard() +{ + LLMutexLock lock(mMutex); + mDiscarded = true; +} + +bool LLMeshUploadThread::isDiscarded() const +{ + LLMutexLock lock(mMutex); + return mDiscarded; +} + +void LLMeshUploadThread::run() +{ + if (mDoUpload) + { + doWholeModelUpload(); + } + else + { + requestWholeModelFee(); + } +} + +void dump_llsd_to_file(const LLSD& content, std::string filename) +{ + if (gSavedSettings.getBOOL("MeshUploadLogXML")) + { + llofstream of(filename.c_str()); + LLSDSerialize::toPrettyXML(content,of); + } +} + +LLSD llsd_from_file(std::string filename) +{ + llifstream ifs(filename.c_str()); + LLSD result; + LLSDSerialize::fromXML(result,ifs); + return result; +} + +void LLMeshUploadThread::wholeModelToLLSD(LLSD& dest, bool include_textures) +{ + LLSD result; + + LLSD res; + result["folder_id"] = gInventory.findUserDefinedCategoryUUIDForType(LLFolderType::FT_OBJECT); + result["texture_folder_id"] = gInventory.findUserDefinedCategoryUUIDForType(LLFolderType::FT_TEXTURE); + result["asset_type"] = "mesh"; + result["inventory_type"] = "object"; + result["description"] = "(No Description)"; + result["next_owner_mask"] = LLSD::Integer(LLFloaterPerms::getNextOwnerPerms("Uploads")); + result["group_mask"] = LLSD::Integer(LLFloaterPerms::getGroupPerms("Uploads")); + result["everyone_mask"] = LLSD::Integer(LLFloaterPerms::getEveryonePerms("Uploads")); + + res["mesh_list"] = LLSD::emptyArray(); + res["texture_list"] = LLSD::emptyArray(); + res["instance_list"] = LLSD::emptyArray(); + S32 mesh_num = 0; + S32 texture_num = 0; + + std::set textures; + std::map texture_index; + + std::map mesh_index; + std::string model_name; + + S32 instance_num = 0; + + for (instance_map::iterator iter = mInstance.begin(); iter != mInstance.end(); ++iter) + { + LLMeshUploadData data; + data.mBaseModel = iter->first; + + if (data.mBaseModel->mSubmodelID) + { + // These are handled below to insure correct parenting order on creation + // due to map walking being based on model address (aka random) + continue; + } + + LLModelInstance& first_instance = *(iter->second.begin()); + for (S32 i = 0; i < 5; i++) + { + data.mModel[i] = first_instance.mLOD[i]; + } + + if (mesh_index.find(data.mBaseModel) == mesh_index.end()) + { + // Have not seen this model before - create a new mesh_list entry for it. + if (model_name.empty()) + { + model_name = data.mBaseModel->getName(); + } + + std::stringstream ostr; + + LLModel::Decomposition& decomp = + data.mModel[LLModel::LOD_PHYSICS].notNull() ? + data.mModel[LLModel::LOD_PHYSICS]->mPhysics : + data.mBaseModel->mPhysics; + + decomp.mBaseHull = mHullMap[data.mBaseModel]; + + LLSD mesh_header = LLModel::writeModel( + ostr, + data.mModel[LLModel::LOD_PHYSICS], + data.mModel[LLModel::LOD_HIGH], + data.mModel[LLModel::LOD_MEDIUM], + data.mModel[LLModel::LOD_LOW], + data.mModel[LLModel::LOD_IMPOSTOR], + decomp, + mUploadSkin, + mUploadJoints, + mLockScaleIfJointPosition, + false, + false, + data.mBaseModel->mSubmodelID); + + data.mAssetData = ostr.str(); + std::string str = ostr.str(); + + res["mesh_list"][mesh_num] = LLSD::Binary(str.begin(),str.end()); + mesh_index[data.mBaseModel] = mesh_num; + mesh_num++; + } + + // For all instances that use this model + for (instance_list::iterator instance_iter = iter->second.begin(); + instance_iter != iter->second.end(); + ++instance_iter) + { + + LLModelInstance& instance = *instance_iter; + + LLSD instance_entry; + + for (S32 i = 0; i < 5; i++) + { + data.mModel[i] = instance.mLOD[i]; + } + + LLVector3 pos, scale; + LLQuaternion rot; + LLMatrix4 transformation = instance.mTransform; + decomposeMeshMatrix(transformation,pos,rot,scale); + instance_entry["position"] = ll_sd_from_vector3(pos); + instance_entry["rotation"] = ll_sd_from_quaternion(rot); + instance_entry["scale"] = ll_sd_from_vector3(scale); + + instance_entry["material"] = LL_MCODE_WOOD; + instance_entry["physics_shape_type"] = data.mModel[LLModel::LOD_PHYSICS].notNull() ? (U8)(LLViewerObject::PHYSICS_SHAPE_PRIM) : (U8)(LLViewerObject::PHYSICS_SHAPE_CONVEX_HULL); + instance_entry["mesh"] = mesh_index[data.mBaseModel]; + instance_entry["mesh_name"] = instance.mLabel; + + instance_entry["face_list"] = LLSD::emptyArray(); + + // We want to be able to allow more than 8 materials... + // + S32 end = llmin((S32)instance.mMaterial.size(), instance.mModel->getNumVolumeFaces()) ; + + for (S32 face_num = 0; face_num < end; face_num++) + { + LLImportMaterial& material = instance.mMaterial[data.mBaseModel->mMaterialList[face_num]]; + LLSD face_entry = LLSD::emptyMap(); + + LLViewerFetchedTexture *texture = NULL; + + if (material.mDiffuseMapFilename.size()) + { + texture = FindViewerTexture(material); + } + + if ((texture != NULL) && + (textures.find(texture) == textures.end())) + { + textures.insert(texture); + } + + std::stringstream texture_str; + if (texture != NULL && include_textures && mUploadTextures) + { + if (texture->hasSavedRawImage()) + { + LLImageDataLock lock(texture->getSavedRawImage()); + + LLPointer upload_file = + LLViewerTextureList::convertToUploadFile(texture->getSavedRawImage()); + + if (!upload_file.isNull() && upload_file->getDataSize()) + { + texture_str.write((const char*) upload_file->getData(), upload_file->getDataSize()); + } + } + } + + if (texture != NULL && + mUploadTextures && + texture_index.find(texture) == texture_index.end()) + { + texture_index[texture] = texture_num; + std::string str = texture_str.str(); + res["texture_list"][texture_num] = LLSD::Binary(str.begin(),str.end()); + texture_num++; + } + + // Subset of TextureEntry fields. + if (texture != NULL && mUploadTextures) + { + face_entry["image"] = texture_index[texture]; + face_entry["scales"] = 1.0; + face_entry["scalet"] = 1.0; + face_entry["offsets"] = 0.0; + face_entry["offsett"] = 0.0; + face_entry["imagerot"] = 0.0; + } + face_entry["diffuse_color"] = ll_sd_from_color4(material.mDiffuseColor); + face_entry["fullbright"] = material.mFullbright; + instance_entry["face_list"][face_num] = face_entry; + } + + res["instance_list"][instance_num] = instance_entry; + instance_num++; + } + } + + for (instance_map::iterator iter = mInstance.begin(); iter != mInstance.end(); ++iter) + { + LLMeshUploadData data; + data.mBaseModel = iter->first; + + if (!data.mBaseModel->mSubmodelID) + { + // These were handled above already... + // + continue; + } + + LLModelInstance& first_instance = *(iter->second.begin()); + for (S32 i = 0; i < 5; i++) + { + data.mModel[i] = first_instance.mLOD[i]; + } + + if (mesh_index.find(data.mBaseModel) == mesh_index.end()) + { + // Have not seen this model before - create a new mesh_list entry for it. + if (model_name.empty()) + { + model_name = data.mBaseModel->getName(); + } + + std::stringstream ostr; + + LLModel::Decomposition& decomp = + data.mModel[LLModel::LOD_PHYSICS].notNull() ? + data.mModel[LLModel::LOD_PHYSICS]->mPhysics : + data.mBaseModel->mPhysics; + + decomp.mBaseHull = mHullMap[data.mBaseModel]; + + LLSD mesh_header = LLModel::writeModel( + ostr, + data.mModel[LLModel::LOD_PHYSICS], + data.mModel[LLModel::LOD_HIGH], + data.mModel[LLModel::LOD_MEDIUM], + data.mModel[LLModel::LOD_LOW], + data.mModel[LLModel::LOD_IMPOSTOR], + decomp, + mUploadSkin, + mUploadJoints, + mLockScaleIfJointPosition, + false, + false, + data.mBaseModel->mSubmodelID); + + data.mAssetData = ostr.str(); + std::string str = ostr.str(); + + res["mesh_list"][mesh_num] = LLSD::Binary(str.begin(),str.end()); + mesh_index[data.mBaseModel] = mesh_num; + mesh_num++; + } + + // For all instances that use this model + for (instance_list::iterator instance_iter = iter->second.begin(); + instance_iter != iter->second.end(); + ++instance_iter) + { + + LLModelInstance& instance = *instance_iter; + + LLSD instance_entry; + + for (S32 i = 0; i < 5; i++) + { + data.mModel[i] = instance.mLOD[i]; + } + + LLVector3 pos, scale; + LLQuaternion rot; + LLMatrix4 transformation = instance.mTransform; + decomposeMeshMatrix(transformation,pos,rot,scale); + instance_entry["position"] = ll_sd_from_vector3(pos); + instance_entry["rotation"] = ll_sd_from_quaternion(rot); + instance_entry["scale"] = ll_sd_from_vector3(scale); + + instance_entry["material"] = LL_MCODE_WOOD; + instance_entry["physics_shape_type"] = (U8)(LLViewerObject::PHYSICS_SHAPE_NONE); + instance_entry["mesh"] = mesh_index[data.mBaseModel]; + + instance_entry["face_list"] = LLSD::emptyArray(); + + // We want to be able to allow more than 8 materials... + // + S32 end = llmin((S32)instance.mMaterial.size(), instance.mModel->getNumVolumeFaces()) ; + + for (S32 face_num = 0; face_num < end; face_num++) + { + LLImportMaterial& material = instance.mMaterial[data.mBaseModel->mMaterialList[face_num]]; + LLSD face_entry = LLSD::emptyMap(); + + LLViewerFetchedTexture *texture = NULL; + + if (material.mDiffuseMapFilename.size()) + { + texture = FindViewerTexture(material); + } + + if ((texture != NULL) && + (textures.find(texture) == textures.end())) + { + textures.insert(texture); + } + + std::stringstream texture_str; + if (texture != NULL && include_textures && mUploadTextures) + { + if (texture->hasSavedRawImage()) + { + LLImageDataLock lock(texture->getSavedRawImage()); + + LLPointer upload_file = + LLViewerTextureList::convertToUploadFile(texture->getSavedRawImage()); + + if (!upload_file.isNull() && upload_file->getDataSize()) + { + texture_str.write((const char*) upload_file->getData(), upload_file->getDataSize()); + } + } + } + + if (texture != NULL && + mUploadTextures && + texture_index.find(texture) == texture_index.end()) + { + texture_index[texture] = texture_num; + std::string str = texture_str.str(); + res["texture_list"][texture_num] = LLSD::Binary(str.begin(),str.end()); + texture_num++; + } + + // Subset of TextureEntry fields. + if (texture != NULL && mUploadTextures) + { + face_entry["image"] = texture_index[texture]; + face_entry["scales"] = 1.0; + face_entry["scalet"] = 1.0; + face_entry["offsets"] = 0.0; + face_entry["offsett"] = 0.0; + face_entry["imagerot"] = 0.0; + } + face_entry["diffuse_color"] = ll_sd_from_color4(material.mDiffuseColor); + face_entry["fullbright"] = material.mFullbright; + instance_entry["face_list"][face_num] = face_entry; + } + + res["instance_list"][instance_num] = instance_entry; + instance_num++; + } + } + + if (model_name.empty()) model_name = "mesh model"; + result["name"] = model_name; + res["metric"] = "MUT_Unspecified"; + result["asset_resources"] = res; + dump_llsd_to_file(result,make_dump_name("whole_model_",dump_num)); + + dest = result; +} + +void LLMeshUploadThread::generateHulls() +{ + bool has_valid_requests = false ; + + for (instance_map::iterator iter = mInstance.begin(); iter != mInstance.end(); ++iter) + { + LLMeshUploadData data; + data.mBaseModel = iter->first; + + LLModelInstance& instance = *(iter->second.begin()); + + for (S32 i = 0; i < 5; i++) + { + data.mModel[i] = instance.mLOD[i]; + } + + //queue up models for hull generation + LLModel* physics = NULL; + + if (data.mModel[LLModel::LOD_PHYSICS].notNull()) + { + physics = data.mModel[LLModel::LOD_PHYSICS]; + } + else if (data.mModel[LLModel::LOD_LOW].notNull()) + { + physics = data.mModel[LLModel::LOD_LOW]; + } + else if (data.mModel[LLModel::LOD_MEDIUM].notNull()) + { + physics = data.mModel[LLModel::LOD_MEDIUM]; + } + else + { + physics = data.mModel[LLModel::LOD_HIGH]; + } + + llassert(physics != NULL); + + DecompRequest* request = new DecompRequest(physics, data.mBaseModel, this); + if(request->isValid()) + { + gMeshRepo.mDecompThread->submitRequest(request); + has_valid_requests = true ; + } + } + + if (has_valid_requests) + { + // *NOTE: Interesting livelock condition on shutdown. If there + // is an upload request in generateHulls() when shutdown starts, + // the main thread isn't available to manage communication between + // the decomposition thread and the upload thread and this loop + // wouldn't complete in turn stalling the main thread. The check + // on isDiscarded() prevents that. + while (! mPhysicsComplete && ! isDiscarded()) + { + apr_sleep(100); + } + } +} + +void LLMeshUploadThread::doWholeModelUpload() +{ + LL_DEBUGS(LOG_MESH) << "Starting model upload. Instances: " << mInstance.size() << LL_ENDL; + + if (mWholeModelUploadURL.empty()) + { + LL_WARNS(LOG_MESH) << "Missing mesh upload capability, unable to upload, fee request failed." + << LL_ENDL; + } + else + { + generateHulls(); + LL_DEBUGS(LOG_MESH) << "Hull generation completed." << LL_ENDL; + + mModelData = LLSD::emptyMap(); + wholeModelToLLSD(mModelData, true); + LLSD body = mModelData["asset_resources"]; + + dump_llsd_to_file(body, make_dump_name("whole_model_body_", dump_num)); + + LLCore::HttpHandle handle = LLCoreHttpUtil::requestPostWithLLSD(mHttpRequest, + mHttpPolicyClass, + mWholeModelUploadURL, + body, + mHttpOptions, + mHttpHeaders, + LLCore::HttpHandler::ptr_t(this, &NoOpDeletor)); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + mHttpStatus = mHttpRequest->getStatus(); + + LL_WARNS(LOG_MESH) << "Couldn't issue request for full model upload. Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toTerseString() << ")" + << LL_ENDL; + } + else + { + U32 sleep_time(10); + + LL_DEBUGS(LOG_MESH) << "POST request issued." << LL_ENDL; + + mHttpRequest->update(0); + while (! LLApp::isExiting() && ! finished() && ! isDiscarded()) + { + ms_sleep(sleep_time); + sleep_time = llmin(250U, sleep_time + sleep_time); + mHttpRequest->update(0); + } + + if (isDiscarded()) + { + LL_DEBUGS(LOG_MESH) << "Mesh upload operation discarded." << LL_ENDL; + } + else + { + LL_DEBUGS(LOG_MESH) << "Mesh upload operation completed." << LL_ENDL; + } + } + } +} + +void LLMeshUploadThread::requestWholeModelFee() +{ + dump_num++; + + generateHulls(); + + mModelData = LLSD::emptyMap(); + wholeModelToLLSD(mModelData, false); + dump_llsd_to_file(mModelData, make_dump_name("whole_model_fee_request_", dump_num)); + LLCore::HttpHandle handle = LLCoreHttpUtil::requestPostWithLLSD(mHttpRequest, + mHttpPolicyClass, + mWholeModelFeeCapability, + mModelData, + mHttpOptions, + mHttpHeaders, + LLCore::HttpHandler::ptr_t(this, &NoOpDeletor)); + if (LLCORE_HTTP_HANDLE_INVALID == handle) + { + mHttpStatus = mHttpRequest->getStatus(); + + LL_WARNS(LOG_MESH) << "Couldn't issue request for model fee. Reason: " << mHttpStatus.toString() + << " (" << mHttpStatus.toTerseString() << ")" + << LL_ENDL; + } + else + { + U32 sleep_time(10); + + mHttpRequest->update(0); + while (! LLApp::isExiting() && ! finished() && ! isDiscarded()) + { + ms_sleep(sleep_time); + sleep_time = llmin(250U, sleep_time + sleep_time); + mHttpRequest->update(0); + } + if (isDiscarded()) + { + LL_DEBUGS(LOG_MESH) << "Mesh fee query operation discarded." << LL_ENDL; + } + } +} + + +// Does completion duty for both fee queries and actual uploads. +void LLMeshUploadThread::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) +{ + // QA/Devel: 0x2 to enable fake error import on upload, 0x1 on fee check + const S32 fake_error(gSavedSettings.getS32("MeshUploadFakeErrors") & (mDoUpload ? 0xa : 0x5)); + LLCore::HttpStatus status(response->getStatus()); + if (fake_error) + { + status = (fake_error & 0x0c) ? LLCore::HttpStatus(500) : LLCore::HttpStatus(200); + } + std::string reason(status.toString()); + LLSD body; + + mFinished = true; + + if (mDoUpload) + { + // model upload case + LLWholeModelUploadObserver * observer(mUploadObserverHandle.get()); + + if (! status) + { + LL_WARNS(LOG_MESH) << "Upload failed. Reason: " << reason + << " (" << status.toTerseString() << ")" + << LL_ENDL; + + // Build a fake body for the alert generator + body["error"] = LLSD::emptyMap(); + body["error"]["message"] = reason; + body["error"]["identifier"] = "NetworkError"; // from asset-upload/upload_util.py + log_upload_error(status, body, "upload", mModelData["name"].asString()); + + if (observer) + { + doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadFailure, observer)); + } + } + else + { + if (fake_error & 0x2) + { + body = llsd_from_file("fake_upload_error.xml"); + } + else + { + // *TODO: handle error in conversion process + LLCoreHttpUtil::responseToLLSD(response, true, body); + } + dump_llsd_to_file(body, make_dump_name("whole_model_upload_response_", dump_num)); + + if (body["state"].asString() == "complete") + { + // requested "mesh" asset type isn't actually the type + // of the resultant object, fix it up here. + mModelData["asset_type"] = "object"; + gMeshRepo.updateInventory(LLMeshRepository::inventory_data(mModelData, body)); + + if (observer) + { + doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadSuccess, observer)); + } + } + else + { + LL_WARNS(LOG_MESH) << "Upload failed. Not in expected 'complete' state." << LL_ENDL; + log_upload_error(status, body, "upload", mModelData["name"].asString()); + + if (observer) + { + doOnIdleOneTime(boost::bind(&LLWholeModelUploadObserver::onModelUploadFailure, observer)); + } + } + } + } + else + { + // model fee case + LLWholeModelFeeObserver* observer(mFeeObserverHandle.get()); + mWholeModelUploadURL.clear(); + + if (! status) + { + LL_WARNS(LOG_MESH) << "Fee request failed. Reason: " << reason + << " (" << status.toTerseString() << ")" + << LL_ENDL; + + // Build a fake body for the alert generator + body["error"] = LLSD::emptyMap(); + body["error"]["message"] = reason; + body["error"]["identifier"] = "NetworkError"; // from asset-upload/upload_util.py + log_upload_error(status, body, "fee", mModelData["name"].asString()); + + if (observer) + { + observer->setModelPhysicsFeeErrorStatus(status.toULong(), reason, body["error"]); + } + } + else + { + if (fake_error & 0x1) + { + body = llsd_from_file("fake_upload_error.xml"); + } + else + { + // *TODO: handle error in conversion process + LLCoreHttpUtil::responseToLLSD(response, true, body); + } + dump_llsd_to_file(body, make_dump_name("whole_model_fee_response_", dump_num)); + + if (body["state"].asString() == "upload") + { + mWholeModelUploadURL = body["uploader"].asString(); + + if (observer) + { + body["data"]["upload_price"] = body["upload_price"]; + observer->onModelPhysicsFeeReceived(body["data"], mWholeModelUploadURL); + } + } + else + { + LL_WARNS(LOG_MESH) << "Fee request failed. Not in expected 'upload' state." << LL_ENDL; + log_upload_error(status, body, "fee", mModelData["name"].asString()); + + if (observer) + { + observer->setModelPhysicsFeeErrorStatus(status.toULong(), reason, body["error"]); + } + } + } + } +} + + +void LLMeshRepoThread::notifyLoadedMeshes() +{ + bool update_metrics(false); + + if (!mMutex) + { + return; + } + + if (!mLoadedQ.empty()) + { + std::deque loaded_queue; + + mMutex->lock(); + if (!mLoadedQ.empty()) + { + loaded_queue.swap(mLoadedQ); + mMutex->unlock(); + + update_metrics = true; + + // Process the elements free of the lock + for (const auto& mesh : loaded_queue) + { + if (mesh.mVolume->getNumVolumeFaces() > 0) + { + gMeshRepo.notifyMeshLoaded(mesh.mMeshParams, mesh.mVolume); + } + else + { + gMeshRepo.notifyMeshUnavailable(mesh.mMeshParams, + LLVolumeLODGroup::getVolumeDetailFromScale(mesh.mVolume->getDetail())); + } + } + } + } + + if (!mUnavailableQ.empty()) + { + std::deque unavil_queue; + + mMutex->lock(); + if (!mUnavailableQ.empty()) + { + unavil_queue.swap(mUnavailableQ); + mMutex->unlock(); + + update_metrics = true; + + // Process the elements free of the lock + for (const auto& req : unavil_queue) + { + gMeshRepo.notifyMeshUnavailable(req.mMeshParams, req.mLOD); + } + } + } + + if (!mSkinInfoQ.empty() || !mSkinUnavailableQ.empty() || ! mDecompositionQ.empty()) + { + if (mMutex->trylock()) + { + std::deque skin_info_q; + std::deque skin_info_unavail_q; + std::list decomp_q; + + if (! mSkinInfoQ.empty()) + { + skin_info_q.swap(mSkinInfoQ); + } + + if (! mSkinUnavailableQ.empty()) + { + skin_info_unavail_q.swap(mSkinUnavailableQ); + } + + if (! mDecompositionQ.empty()) + { + decomp_q.swap(mDecompositionQ); + } + + mMutex->unlock(); + + // Process the elements free of the lock + while (! skin_info_q.empty()) + { + gMeshRepo.notifySkinInfoReceived(skin_info_q.front()); + skin_info_q.pop_front(); + } + while (! skin_info_unavail_q.empty()) + { + gMeshRepo.notifySkinInfoUnavailable(skin_info_unavail_q.front().mId); + skin_info_unavail_q.pop_front(); + } + + while (! decomp_q.empty()) + { + gMeshRepo.notifyDecompositionReceived(decomp_q.front()); + decomp_q.pop_front(); + } + } + } + + if (update_metrics) + { + // Ping time-to-load metrics for mesh download operations. + LLMeshRepository::metricsProgress(0); + } + +} + +S32 LLMeshRepoThread::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod) +{ //only ever called from main thread + LLMutexLock lock(mHeaderMutex); + mesh_header_map::iterator iter = mMeshHeader.find(mesh_params.getSculptID()); + + if (iter != mMeshHeader.end()) + { + auto& header = iter->second.second; + + return LLMeshRepository::getActualMeshLOD(header, lod); + } + + return lod; +} + +//static +S32 LLMeshRepository::getActualMeshLOD(LLMeshHeader& header, S32 lod) +{ + lod = llclamp(lod, 0, 3); + + if (header.m404) + { + return -1; + } + + S32 version = header.mVersion; + + if (version > MAX_MESH_VERSION) + { + return -1; + } + + if (header.mLodSize[lod] > 0) + { + return lod; + } + + //search down to find the next available lower lod + for (S32 i = lod-1; i >= 0; --i) + { + if (header.mLodSize[i] > 0) + { + return i; + } + } + + //search up to find then ext available higher lod + for (S32 i = lod+1; i < LLVolumeLODGroup::NUM_LODS; ++i) + { + if (header.mLodSize[i] > 0) + { + return i; + } + } + + //header exists and no good lod found, treat as 404 + header.m404 = true; + + return -1; +} + +// Handle failed or successful requests for mesh assets. +// +// Support for 200 responses was added for several reasons. One, +// a service or cache can ignore range headers and give us a +// 200 with full asset should it elect to. We also support +// a debug flag which disables range requests for those very +// few users that have some sort of problem with their networking +// services. But the 200 response handling is suboptimal: rather +// than cache the whole asset, we just extract the part that would +// have been sent in a 206 and process that. Inefficient but these +// are cases far off the norm. +void LLMeshHandlerBase::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) +{ + mProcessed = true; + + unsigned int retries(0U); + response->getRetries(NULL, &retries); + LLMeshRepository::sHTTPRetryCount += retries; + + LLCore::HttpStatus status(response->getStatus()); + if (! status || MESH_HTTP_RESPONSE_FAILED) + { + processFailure(status); + ++LLMeshRepository::sHTTPErrorCount; + } + else + { + // From texture fetch code and may apply here: + // + // A warning about partial (HTTP 206) data. Some grid services + // do *not* return a 'Content-Range' header in the response to + // Range requests with a 206 status. We're forced to assume + // we get what we asked for in these cases until we can fix + // the services. + // + // May also need to deal with 200 status (full asset returned + // rather than partial) and 416 (request completely unsatisfyable). + // Always been exposed to these but are less likely here where + // speculative loads aren't done. + LLCore::BufferArray * body(response->getBody()); + S32 body_offset(0); + U8 * data(NULL); + S32 data_size(body ? body->size() : 0); + + if (data_size > 0) + { + static const LLCore::HttpStatus par_status(HTTP_PARTIAL_CONTENT); + + unsigned int offset(0), length(0), full_length(0); + + if (par_status == status) + { + // 206 case + response->getRange(&offset, &length, &full_length); + if (! offset && ! length) + { + // This is the case where we receive a 206 status but + // there wasn't a useful Content-Range header in the response. + // This could be because it was badly formatted but is more + // likely due to capabilities services which scrub headers + // from responses. Assume we got what we asked for...` + // length = data_size; + offset = mOffset; + } + } + else + { + // 200 case, typically + offset = 0; + } + + // *DEBUG: To test validation below + // offset += 1; + + // Validate that what we think we received is consistent with + // what we've asked for. I.e. first byte we wanted lies somewhere + // in the response. + if (offset > mOffset + || (offset + data_size) <= mOffset + || (mOffset - offset) >= data_size) + { + // No overlap with requested range. Fail request with + // suitable error. Shouldn't happen unless server/cache/ISP + // is doing something awful. + LL_WARNS(LOG_MESH) << "Mesh response (bytes [" + << offset << ".." << (offset + length - 1) + << "]) didn't overlap with request's origin (bytes [" + << mOffset << ".." << (mOffset + mRequestedBytes - 1) + << "])." << LL_ENDL; + processFailure(LLCore::HttpStatus(LLCore::HttpStatus::LLCORE, LLCore::HE_INV_CONTENT_RANGE_HDR)); + ++LLMeshRepository::sHTTPErrorCount; + goto common_exit; + } + + // *TODO: Try to get rid of data copying and add interfaces + // that support BufferArray directly. Introduce a two-phase + // handler, optional first that takes a body, fallback second + // that requires a temporary allocation and data copy. + body_offset = mOffset - offset; + data = new(std::nothrow) U8[data_size - body_offset]; + if (data) + { + body->read(body_offset, (char *) data, data_size - body_offset); + LLMeshRepository::sBytesReceived += data_size; + } + else + { + LL_WARNS(LOG_MESH) << "Failed to allocate " << data_size - body_offset << " memory for mesh response" << LL_ENDL; + processFailure(LLCore::HttpStatus(LLCore::HttpStatus::LLCORE, LLCore::HE_BAD_ALLOC)); + } + } + + processData(body, body_offset, data, data_size - body_offset); + + delete [] data; + } + + // Release handler +common_exit: + gMeshRepo.mThread->mHttpRequestSet.erase(this->shared_from_this()); +} + + +LLMeshHeaderHandler::~LLMeshHeaderHandler() +{ + if (!LLApp::isExiting()) + { + if (! mProcessed) + { + // something went wrong, retry + LL_WARNS(LOG_MESH) << "Mesh header fetch canceled unexpectedly, retrying." << LL_ENDL; + LLMeshRepoThread::HeaderRequest req(mMeshParams); + LLMutexLock lock(gMeshRepo.mThread->mMutex); + gMeshRepo.mThread->mHeaderReqQ.push(req); + } + LLMeshRepoThread::decActiveHeaderRequests(); + } +} + +void LLMeshHeaderHandler::processFailure(LLCore::HttpStatus status) +{ + LL_WARNS(LOG_MESH) << "Error during mesh header handling. ID: " << mMeshParams.getSculptID() + << ", Reason: " << status.toString() + << " (" << status.toTerseString() << "). Not retrying." + << LL_ENDL; + + // Can't get the header so none of the LODs will be available + LLMutexLock lock(gMeshRepo.mThread->mMutex); + for (int i(0); i < LLVolumeLODGroup::NUM_LODS; ++i) + { + gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, i)); + } +} + +void LLMeshHeaderHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, + U8 * data, S32 data_size) +{ + LLUUID mesh_id = mMeshParams.getSculptID(); + bool success = (!MESH_HEADER_PROCESS_FAILED) + && ((data != NULL) == (data_size > 0)); // if we have data but no size or have size but no data, something is wrong; + llassert(success); + EMeshProcessingResult res = MESH_UNKNOWN; + if (success) + { + res = gMeshRepo.mThread->headerReceived(mMeshParams, data, data_size); + success = (res == MESH_OK); + } + if (! success) + { + // *TODO: Get real reason for parse failure here. Might we want to retry? + LL_WARNS(LOG_MESH) << "Unable to parse mesh header. ID: " << mesh_id + << ", Size: " << data_size + << ", Reason: " << res << " Not retrying." + << LL_ENDL; + + // Can't get the header so none of the LODs will be available + LLMutexLock lock(gMeshRepo.mThread->mMutex); + for (int i(0); i < LLVolumeLODGroup::NUM_LODS; ++i) + { + gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, i)); + } + } + else if (data && data_size > 0) + { + // header was successfully retrieved from sim and parsed and is in cache + S32 header_bytes = 0; + LLMeshHeader header; + + gMeshRepo.mThread->mHeaderMutex->lock(); + LLMeshRepoThread::mesh_header_map::iterator iter = gMeshRepo.mThread->mMeshHeader.find(mesh_id); + if (iter != gMeshRepo.mThread->mMeshHeader.end()) + { + header_bytes = (S32)iter->second.first; + header = iter->second.second; + } + + if (header_bytes > 0 + && !header.m404 + && (header.mVersion <= MAX_MESH_VERSION)) + { + std::stringstream str; + + S32 lod_bytes = 0; + + for (U32 i = 0; i < LLModel::LOD_PHYSICS; ++i) + { + // figure out how many bytes we'll need to reserve in the file + lod_bytes = llmax(lod_bytes, header.mLodOffset[i]+header.mLodSize[i]); + } + + // just in case skin info or decomposition is at the end of the file (which it shouldn't be) + lod_bytes = llmax(lod_bytes, header.mSkinOffset+header.mSkinSize); + lod_bytes = llmax(lod_bytes, header.mPhysicsConvexOffset + header.mPhysicsConvexSize); + + // Do not unlock mutex untill we are done with LLSD. + // LLSD is smart and can work like smart pointer, is not thread safe. + gMeshRepo.mThread->mHeaderMutex->unlock(); + + S32 bytes = lod_bytes + header_bytes; + + + // It's possible for the remote asset to have more data than is needed for the local cache + // only allocate as much space in the cache as is needed for the local cache + data_size = llmin(data_size, bytes); + + // Fix asset caching + //LLFileSystem file(mesh_id, LLAssetType::AT_MESH, LLFileSystem::WRITE); + LLFileSystem file(mesh_id, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); + if (file.getMaxSize() >= bytes) + { + LLMeshRepository::sCacheBytesWritten += data_size; + ++LLMeshRepository::sCacheWrites; + + file.write(data, data_size); + + // Fix asset caching + S32 remaining = bytes - file.tell(); + if (remaining > 0) + { + U8* block = new(std::nothrow) U8[remaining]; + if (block) + { + memset(block, 0, remaining); + file.write(block, remaining); + delete[] block; + } + } + // + } + } + else + { + LL_WARNS(LOG_MESH) << "Trying to cache nonexistent mesh, mesh id: " << mesh_id << LL_ENDL; + + gMeshRepo.mThread->mHeaderMutex->unlock(); + + // headerReceived() parsed header, but header's data is invalid so none of the LODs will be available + LLMutexLock lock(gMeshRepo.mThread->mMutex); + for (int i(0); i < LLVolumeLODGroup::NUM_LODS; ++i) + { + gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, i)); + } + } + } +} + +LLMeshLODHandler::~LLMeshLODHandler() +{ + if (! LLApp::isExiting()) + { + if (! mProcessed) + { + LL_WARNS(LOG_MESH) << "Mesh LOD fetch canceled unexpectedly, retrying." << LL_ENDL; + gMeshRepo.mThread->lockAndLoadMeshLOD(mMeshParams, mLOD); + } + LLMeshRepoThread::decActiveLODRequests(); + } +} + +void LLMeshLODHandler::processFailure(LLCore::HttpStatus status) +{ + LL_WARNS(LOG_MESH) << "Error during mesh LOD handling. ID: " << mMeshParams.getSculptID() + << ", Reason: " << status.toString() + << " (" << status.toTerseString() << "). Not retrying." + << LL_ENDL; + + LLMutexLock lock(gMeshRepo.mThread->mMutex); + gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, mLOD)); +} + +void LLMeshLODHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, + U8 * data, S32 data_size) +{ + if ((!MESH_LOD_PROCESS_FAILED) + && ((data != NULL) == (data_size > 0))) // if we have data but no size or have size but no data, something is wrong + { + EMeshProcessingResult result = gMeshRepo.mThread->lodReceived(mMeshParams, mLOD, data, data_size); + if (result == MESH_OK) + { + // good fetch from sim, write to cache + // Fix asset caching + //LLFileSystem file(mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLFileSystem::WRITE); + LLFileSystem file(mMeshParams.getSculptID(), LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); + + S32 offset = mOffset; + S32 size = mRequestedBytes; + + if (file.getSize() >= offset+size) + { + file.seek(offset); + file.write(data, size); + LLMeshRepository::sCacheBytesWritten += size; + ++LLMeshRepository::sCacheWrites; + } + } + else + { + LL_WARNS(LOG_MESH) << "Error during mesh LOD processing. ID: " << mMeshParams.getSculptID() + << ", Reason: " << result + << " LOD: " << mLOD + << " Data size: " << data_size + << " Not retrying." + << LL_ENDL; + LLMutexLock lock(gMeshRepo.mThread->mMutex); + gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, mLOD)); + } + } + else + { + LL_WARNS(LOG_MESH) << "Error during mesh LOD processing. ID: " << mMeshParams.getSculptID() + << ", Unknown reason. Not retrying." + << " LOD: " << mLOD + << " Data size: " << data_size + << LL_ENDL; + LLMutexLock lock(gMeshRepo.mThread->mMutex); + gMeshRepo.mThread->mUnavailableQ.push_back(LLMeshRepoThread::LODRequest(mMeshParams, mLOD)); + } +} + +LLMeshSkinInfoHandler::~LLMeshSkinInfoHandler() +{ + if (!mProcessed) + { + LL_WARNS(LOG_MESH) << "deleting unprocessed request handler (may be ok on exit)" << LL_ENDL; + } +} + +void LLMeshSkinInfoHandler::processFailure(LLCore::HttpStatus status) +{ + LL_WARNS(LOG_MESH) << "Error during mesh skin info handling. ID: " << mMeshID + << ", Reason: " << status.toString() + << " (" << status.toTerseString() << "). Not retrying." + << LL_ENDL; + LLMutexLock lock(gMeshRepo.mThread->mMutex); + gMeshRepo.mThread->mSkinUnavailableQ.emplace_back(mMeshID); +} + +void LLMeshSkinInfoHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, + U8 * data, S32 data_size) +{ + if ((!MESH_SKIN_INFO_PROCESS_FAILED) + && ((data != NULL) == (data_size > 0)) // if we have data but no size or have size but no data, something is wrong + && gMeshRepo.mThread->skinInfoReceived(mMeshID, data, data_size)) + { + // good fetch from sim, write to cache + // Fix asset caching + //LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); + LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); + + S32 offset = mOffset; + S32 size = mRequestedBytes; + + if (file.getSize() >= offset+size) + { + LLMeshRepository::sCacheBytesWritten += size; + ++LLMeshRepository::sCacheWrites; + file.seek(offset); + file.write(data, size); + } + } + else + { + LL_WARNS(LOG_MESH) << "Error during mesh skin info processing. ID: " << mMeshID + << ", Unknown reason. Not retrying." + << LL_ENDL; + LLMutexLock lock(gMeshRepo.mThread->mMutex); + gMeshRepo.mThread->mSkinUnavailableQ.emplace_back(mMeshID); + } +} + +LLMeshDecompositionHandler::~LLMeshDecompositionHandler() +{ + if (!mProcessed) + { + LL_WARNS(LOG_MESH) << "deleting unprocessed request handler (may be ok on exit)" << LL_ENDL; + } +} + +void LLMeshDecompositionHandler::processFailure(LLCore::HttpStatus status) +{ + LL_WARNS(LOG_MESH) << "Error during mesh decomposition handling. ID: " << mMeshID + << ", Reason: " << status.toString() + << " (" << status.toTerseString() << "). Not retrying." + << LL_ENDL; + // *TODO: Mark mesh unavailable on error. For now, simply leave + // request unfulfilled rather than retry forever. +} + +void LLMeshDecompositionHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, + U8 * data, S32 data_size) +{ + if ((!MESH_DECOMP_PROCESS_FAILED) + && ((data != NULL) == (data_size > 0)) // if we have data but no size or have size but no data, something is wrong + && gMeshRepo.mThread->decompositionReceived(mMeshID, data, data_size)) + { + // good fetch from sim, write to cache + // Fix asset caching + //LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); + LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); + + S32 offset = mOffset; + S32 size = mRequestedBytes; + + if (file.getSize() >= offset+size) + { + LLMeshRepository::sCacheBytesWritten += size; + ++LLMeshRepository::sCacheWrites; + file.seek(offset); + file.write(data, size); + } + } + else + { + LL_WARNS(LOG_MESH) << "Error during mesh decomposition processing. ID: " << mMeshID + << ", Unknown reason. Not retrying." + << LL_ENDL; + // *TODO: Mark mesh unavailable on error + } +} + +LLMeshPhysicsShapeHandler::~LLMeshPhysicsShapeHandler() +{ + if (!mProcessed) + { + LL_WARNS(LOG_MESH) << "deleting unprocessed request handler (may be ok on exit)" << LL_ENDL; + } +} + +void LLMeshPhysicsShapeHandler::processFailure(LLCore::HttpStatus status) +{ + LL_WARNS(LOG_MESH) << "Error during mesh physics shape handling. ID: " << mMeshID + << ", Reason: " << status.toString() + << " (" << status.toTerseString() << "). Not retrying." + << LL_ENDL; + // *TODO: Mark mesh unavailable on error +} + +void LLMeshPhysicsShapeHandler::processData(LLCore::BufferArray * /* body */, S32 /* body_offset */, + U8 * data, S32 data_size) +{ + if ((!MESH_PHYS_SHAPE_PROCESS_FAILED) + && ((data != NULL) == (data_size > 0)) // if we have data but no size or have size but no data, something is wrong + && gMeshRepo.mThread->physicsShapeReceived(mMeshID, data, data_size) == MESH_OK) + { + // good fetch from sim, write to cache for caching + // Fix asset caching + //LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::WRITE); + LLFileSystem file(mMeshID, LLAssetType::AT_MESH, LLFileSystem::READ_WRITE); + + S32 offset = mOffset; + S32 size = mRequestedBytes; + + if (file.getSize() >= offset+size) + { + LLMeshRepository::sCacheBytesWritten += size; + ++LLMeshRepository::sCacheWrites; + file.seek(offset); + file.write(data, size); + } + } + else + { + LL_WARNS(LOG_MESH) << "Error during mesh physics shape processing. ID: " << mMeshID + << ", Unknown reason. Not retrying." + << LL_ENDL; + // *TODO: Mark mesh unavailable on error + } +} + +LLMeshRepository::LLMeshRepository() +: mMeshMutex(NULL), + mDecompThread(NULL), + mMeshThreadCount(0), + mThread(NULL) +{ + mSkinInfoCullTimer.resetWithExpiry(10.f); +} + +void LLMeshRepository::init() +{ + mMeshMutex = new LLMutex(); + + LLConvexDecomposition::getInstance()->initSystem(); + + if (!LLConvexDecomposition::isFunctional()) + { + LL_INFOS(LOG_MESH) << "Using STUB for LLConvexDecomposition" << LL_ENDL; + } + + mDecompThread = new LLPhysicsDecomp(); + mDecompThread->start(); + + while (!mDecompThread->mInited) + { //wait for physics decomp thread to init + apr_sleep(100); + } + + metrics_teleport_started_signal = LLViewerMessage::getInstance()->setTeleportStartedCallback(teleport_started); + + mThread = new LLMeshRepoThread(); + mThread->start(); +} + +void LLMeshRepository::shutdown() +{ + LL_INFOS(LOG_MESH) << "Shutting down mesh repository." << LL_ENDL; + llassert(mThread != NULL); + llassert(mThread->mSignal != NULL); + + metrics_teleport_started_signal.disconnect(); + + for (U32 i = 0; i < mUploads.size(); ++i) + { + LL_INFOS(LOG_MESH) << "Discard the pending mesh uploads." << LL_ENDL; + mUploads[i]->discard() ; //discard the uploading requests. + } + + mThread->mSignal->broadcast(); + + while (!mThread->isStopped()) + { + apr_sleep(10); + } + delete mThread; + mThread = NULL; + + for (U32 i = 0; i < mUploads.size(); ++i) + { + LL_INFOS(LOG_MESH) << "Waiting for pending mesh upload " << (i + 1) << "/" << mUploads.size() << LL_ENDL; + while (!mUploads[i]->isStopped()) + { + apr_sleep(10); + } + delete mUploads[i]; + } + + mUploads.clear(); + + delete mMeshMutex; + mMeshMutex = NULL; + + LL_INFOS(LOG_MESH) << "Shutting down decomposition system." << LL_ENDL; + + if (mDecompThread) + { + mDecompThread->shutdown(); + delete mDecompThread; + mDecompThread = NULL; + } + + LLConvexDecomposition::quitSystem(); +} + +//called in the main thread. +S32 LLMeshRepository::update() +{ + // Conditionally log a mesh metrics event + metricsUpdate(); + + if(mUploadWaitList.empty()) + { + return 0 ; + } + + S32 size = mUploadWaitList.size() ; + for (S32 i = 0; i < size; ++i) + { + mUploads.push_back(mUploadWaitList[i]); + mUploadWaitList[i]->preStart() ; + mUploadWaitList[i]->start() ; + } + mUploadWaitList.clear() ; + + return size ; +} + +void LLMeshRepository::unregisterMesh(LLVOVolume* vobj) +{ + for (auto& lod : mLoadingMeshes) + { + for (auto& param : lod) + { + vector_replace_with_last(param.second, vobj); + } + } + + for (auto& skin_pair : mLoadingSkins) + { + vector_replace_with_last(skin_pair.second, vobj); + } +} + +S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_params, S32 detail, S32 last_lod) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; //LL_LL_RECORD_BLOCK_TIME(FTM_MESH_FETCH); + + // Manage time-to-load metrics for mesh download operations. + metricsProgress(1); + + if (detail < 0 || detail >= LLVolumeLODGroup::NUM_LODS) + { + return detail; + } + + { + LLMutexLock lock(mMeshMutex); + //add volume to list of loading meshes + const auto& mesh_id = mesh_params.getSculptID(); + mesh_load_map::iterator iter = mLoadingMeshes[detail].find(mesh_id); + if (iter != mLoadingMeshes[detail].end()) + { //request pending for this mesh, append volume id to list + auto it = std::find(iter->second.begin(), iter->second.end(), vobj); + if (it == iter->second.end()) { + iter->second.push_back(vobj); + } + } + else + { + //first request for this mesh + mLoadingMeshes[detail][mesh_id].push_back(vobj); + mPendingRequests.push_back(LLMeshRepoThread::LODRequest(mesh_params, detail)); + LLMeshRepository::sLODPending++; + } + } + + //do a quick search to see if we can't display something while we wait for this mesh to load + LLVolume* volume = vobj->getVolume(); + + if (volume) + { + LLVolumeParams params = volume->getParams(); + + LLVolumeLODGroup* group = LLPrimitive::getVolumeManager()->getGroup(params); + + if (group) + { + //first, see if last_lod is available (don't transition down to avoid funny popping a la SH-641) + if (last_lod >= 0) + { + LLVolume* lod = group->refLOD(last_lod); + if (lod && lod->isMeshAssetLoaded() && lod->getNumVolumeFaces() > 0) + { + group->derefLOD(lod); + return last_lod; + } + group->derefLOD(lod); + } + + //next, see what the next lowest LOD available might be + for (S32 i = detail-1; i >= 0; --i) + { + LLVolume* lod = group->refLOD(i); + if (lod && lod->isMeshAssetLoaded() && lod->getNumVolumeFaces() > 0) + { + group->derefLOD(lod); + return i; + } + + group->derefLOD(lod); + } + + //no lower LOD is a available, is a higher lod available? + for (S32 i = detail+1; i < LLVolumeLODGroup::NUM_LODS; ++i) + { + LLVolume* lod = group->refLOD(i); + if (lod && lod->isMeshAssetLoaded() && lod->getNumVolumeFaces() > 0) + { + group->derefLOD(lod); + return i; + } + + group->derefLOD(lod); + } + } + } + + return detail; +} + +void LLMeshRepository::notifyLoadedMeshes() +{ //called from main thread + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; //LL_RECORD_BLOCK_TIME(FTM_MESH_FETCH); + + // GetMesh2 operation with keepalives, etc. With pipelining, + // we'll increase this. See llappcorehttp and llcorehttp for + // discussion on connection strategies. + LLAppCoreHttp & app_core_http(LLAppViewer::instance()->getAppCoreHttp()); + S32 scale(app_core_http.isPipelined(LLAppCoreHttp::AP_MESH2) + ? (2 * LLAppCoreHttp::PIPELINING_DEPTH) + : 5); + + LLMeshRepoThread::sMaxConcurrentRequests = gSavedSettings.getU32("Mesh2MaxConcurrentRequests"); + LLMeshRepoThread::sRequestHighWater = llclamp(scale * S32(LLMeshRepoThread::sMaxConcurrentRequests), + REQUEST2_HIGH_WATER_MIN, + REQUEST2_HIGH_WATER_MAX); + LLMeshRepoThread::sRequestLowWater = llclamp(LLMeshRepoThread::sRequestHighWater / 2, + REQUEST2_LOW_WATER_MIN, + REQUEST2_LOW_WATER_MAX); + + //clean up completed upload threads + for (std::vector::iterator iter = mUploads.begin(); iter != mUploads.end(); ) + { + LLMeshUploadThread* thread = *iter; + + if (thread->isStopped() && thread->finished()) + { + iter = mUploads.erase(iter); + delete thread; + } + else + { + ++iter; + } + } + + //update inventory + if (!mInventoryQ.empty()) + { + LLMutexLock lock(mMeshMutex); + while (!mInventoryQ.empty()) + { + inventory_data& data = mInventoryQ.front(); + + LLAssetType::EType asset_type = LLAssetType::lookup(data.mPostData["asset_type"].asString()); + LLInventoryType::EType inventory_type = LLInventoryType::lookup(data.mPostData["inventory_type"].asString()); + + // Handle addition of texture, if any. + if ( data.mResponse.has("new_texture_folder_id") ) + { + const LLUUID& new_folder_id = data.mResponse["new_texture_folder_id"].asUUID(); + + if ( new_folder_id.notNull() ) + { + LLUUID parent_id = gInventory.findUserDefinedCategoryUUIDForType(LLFolderType::FT_TEXTURE); + + std::string name; + // Check if the server built a different name for the texture folder + if ( data.mResponse.has("new_texture_folder_name") ) + { + name = data.mResponse["new_texture_folder_name"].asString(); + } + else + { + name = data.mPostData["name"].asString(); + } + + // Add the category to the internal representation + LLPointer cat = + new LLViewerInventoryCategory(new_folder_id, parent_id, + LLFolderType::FT_NONE, name, gAgent.getID()); + cat->setVersion(LLViewerInventoryCategory::VERSION_UNKNOWN); + + LLInventoryModel::LLCategoryUpdate update(cat->getParentUUID(), 1); + gInventory.accountForUpdate(update); + gInventory.updateCategory(cat); + } + } + + on_new_single_inventory_upload_complete( + asset_type, + inventory_type, + data.mPostData["asset_type"].asString(), + data.mPostData["folder_id"].asUUID(), + data.mPostData["name"], + data.mPostData["description"], + data.mResponse, + data.mResponse["upload_price"]); + //} + + mInventoryQ.pop(); + } + } + + //call completed callbacks on finished decompositions + mDecompThread->notifyCompleted(); + + if (mSkinInfoCullTimer.checkExpirationAndReset(10.f)) + { + //// Clean up dead skin info + //U64Bytes skinbytes(0); + for (auto iter = mSkinMap.begin(), ender = mSkinMap.end(); iter != ender;) + { + auto copy_iter = iter++; + + //skinbytes += U64Bytes(sizeof(LLMeshSkinInfo)); + //skinbytes += U64Bytes(copy_iter->second->mJointNames.size() * sizeof(std::string)); + //skinbytes += U64Bytes(copy_iter->second->mJointNums.size() * sizeof(S32)); + //skinbytes += U64Bytes(copy_iter->second->mJointNames.size() * sizeof(LLMatrix4a)); + //skinbytes += U64Bytes(copy_iter->second->mJointNames.size() * sizeof(LLMatrix4)); + + if (copy_iter->second->getNumRefs() == 1) + { + mSkinMap.erase(copy_iter); + } + } + //LL_INFOS() << "Skin info cache elements:" << mSkinMap.size() << " Memory: " << U64Kilobytes(skinbytes) << LL_ENDL; + } + + // For major operations, attempt to get the required locks + // without blocking and punt if they're not available. The + // longest run of holdoffs is kept in sMaxLockHoldoffs just + // to collect the data. In testing, I've never seen a value + // greater than 2 (written to log on exit). + { + LLMutexTrylock lock1(mMeshMutex); + LLMutexTrylock lock2(mThread->mMutex); + + static U32 hold_offs(0); + if (! lock1.isLocked() || ! lock2.isLocked()) + { + // If we can't get the locks, skip and pick this up later. + ++hold_offs; + sMaxLockHoldoffs = llmax(sMaxLockHoldoffs, hold_offs); + return; + } + hold_offs = 0; + + if (gAgent.getRegion()) + { + // Update capability urls + static std::string region_name("never name a region this"); + + if (gAgent.getRegion()->getName() != region_name && gAgent.getRegion()->capabilitiesReceived()) + { + region_name = gAgent.getRegion()->getName(); + const std::string mesh_cap(gAgent.getRegion()->getViewerAssetUrl()); + mThread->setGetMeshCap(mesh_cap); + LL_DEBUGS(LOG_MESH) << "Retrieving caps for region '" << region_name + << "', ViewerAsset cap: " << mesh_cap + << LL_ENDL; + } + } + + //popup queued error messages from background threads + while (!mUploadErrorQ.empty()) + { + LLSD substitutions(mUploadErrorQ.front()); + if (substitutions.has("DETAILS")) + { + LLNotificationsUtil::add("MeshUploadErrorDetails", substitutions); + } + else + { + LLNotificationsUtil::add("MeshUploadError", substitutions); + } + mUploadErrorQ.pop(); + } + + S32 active_count = LLMeshRepoThread::sActiveHeaderRequests + LLMeshRepoThread::sActiveLODRequests; + if (active_count < LLMeshRepoThread::sRequestLowWater) + { + S32 push_count = LLMeshRepoThread::sRequestHighWater - active_count; + + if (mPendingRequests.size() > push_count) + { + // More requests than the high-water limit allows so + // sort and forward the most important. + + //calculate "score" for pending requests + + //create score map + std::map score_map; + + for (U32 i = 0; i < LLVolumeLODGroup::NUM_LODS; ++i) + { + for (mesh_load_map::iterator iter = mLoadingMeshes[i].begin(); iter != mLoadingMeshes[i].end(); ++iter) + { + F32 max_score = 0.f; + for (auto obj_iter = iter->second.begin(); obj_iter != iter->second.end(); ++obj_iter) + { + LLVOVolume* object = *obj_iter; + if (object) + { + LLDrawable* drawable = object->mDrawable; + if (drawable) + { + F32 cur_score = drawable->getRadius()/llmax(drawable->mDistanceWRTCamera, 1.f); + max_score = llmax(max_score, cur_score); + } + } + } + + score_map[iter->first] = max_score; + } + } + + //set "score" for pending requests + for (std::vector::iterator iter = mPendingRequests.begin(); iter != mPendingRequests.end(); ++iter) + { + iter->mScore = score_map[iter->mMeshParams.getSculptID()]; + } + + //sort by "score" + std::partial_sort(mPendingRequests.begin(), mPendingRequests.begin() + push_count, + mPendingRequests.end(), LLMeshRepoThread::CompareScoreGreater()); + } + + while (!mPendingRequests.empty() && push_count > 0) + { + LLMeshRepoThread::LODRequest& request = mPendingRequests.front(); + mThread->loadMeshLOD(request.mMeshParams, request.mLOD); + mPendingRequests.erase(mPendingRequests.begin()); + LLMeshRepository::sLODPending--; + push_count--; + } + } + + //send skin info requests + while (!mPendingSkinRequests.empty()) + { + mThread->loadMeshSkinInfo(mPendingSkinRequests.front()); + mPendingSkinRequests.pop(); + } + + //send decomposition requests + while (!mPendingDecompositionRequests.empty()) + { + mThread->loadMeshDecomposition(mPendingDecompositionRequests.front()); + mPendingDecompositionRequests.pop(); + } + + //send physics shapes decomposition requests + while (!mPendingPhysicsShapeRequests.empty()) + { + mThread->loadMeshPhysicsShape(mPendingPhysicsShapeRequests.front()); + mPendingPhysicsShapeRequests.pop(); + } + + mThread->notifyLoadedMeshes(); + } + + mThread->mSignal->signal(); +} + +void LLMeshRepository::notifySkinInfoReceived(LLMeshSkinInfo* info) +{ + mSkinMap[info->mMeshID] = info; // Cache into LLPointer + // Alternative: We can get skin size from header + sCacheBytesSkins += info->sizeBytes(); + + skin_load_map::iterator iter = mLoadingSkins.find(info->mMeshID); + if (iter != mLoadingSkins.end()) + { + for (LLVOVolume* vobj : iter->second) + { + if (vobj) + { + vobj->notifySkinInfoLoaded(info); + } + } + mLoadingSkins.erase(iter); + } +} + +void LLMeshRepository::notifySkinInfoUnavailable(const LLUUID& mesh_id) +{ + skin_load_map::iterator iter = mLoadingSkins.find(mesh_id); + if (iter != mLoadingSkins.end()) + { + for (LLVOVolume* vobj : iter->second) + { + if (vobj) + { + vobj->notifySkinInfoUnavailable(); + } + } + mLoadingSkins.erase(iter); + } +} + +void LLMeshRepository::notifyDecompositionReceived(LLModel::Decomposition* decomp) +{ + decomposition_map::iterator iter = mDecompositionMap.find(decomp->mMeshID); + if (iter == mDecompositionMap.end()) + { //just insert decomp into map + mDecompositionMap[decomp->mMeshID] = decomp; + mLoadingDecompositions.erase(decomp->mMeshID); + sCacheBytesDecomps += decomp->sizeBytes(); + } + else + { //merge decomp with existing entry + sCacheBytesDecomps -= iter->second->sizeBytes(); + iter->second->merge(decomp); + sCacheBytesDecomps += iter->second->sizeBytes(); + + mLoadingDecompositions.erase(decomp->mMeshID); + delete decomp; + } +} + +void LLMeshRepository::notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVolume* volume) +{ //called from main thread + S32 detail = LLVolumeLODGroup::getVolumeDetailFromScale(volume->getDetail()); + + //get list of objects waiting to be notified this mesh is loaded + const auto& mesh_id = mesh_params.getSculptID(); + mesh_load_map::iterator obj_iter = mLoadingMeshes[detail].find(mesh_id); + + if (volume && obj_iter != mLoadingMeshes[detail].end()) + { + //make sure target volume is still valid + if (volume->getNumVolumeFaces() <= 0) + { + LL_WARNS(LOG_MESH) << "Mesh loading returned empty volume. ID: " << mesh_id + << LL_ENDL; + } + + { //update system volume + LLVolume* sys_volume = LLPrimitive::getVolumeManager()->refVolume(mesh_params, detail); + if (sys_volume) + { + sys_volume->copyVolumeFaces(volume); + sys_volume->setMeshAssetLoaded(true); + LLPrimitive::getVolumeManager()->unrefVolume(sys_volume); + } + else + { + LL_WARNS(LOG_MESH) << "Couldn't find system volume for mesh " << mesh_id + << LL_ENDL; + } + } + + //notify waiting LLVOVolume instances that their requested mesh is available + for (LLVOVolume* vobj : obj_iter->second) + { + if (vobj) + { + vobj->notifyMeshLoaded(); + } + } + + mLoadingMeshes[detail].erase(obj_iter); + + LLViewerStatsRecorder::instance().meshLoaded(); + } +} + +void LLMeshRepository::notifyMeshUnavailable(const LLVolumeParams& mesh_params, S32 lod) +{ //called from main thread + //get list of objects waiting to be notified this mesh is loaded + const auto& mesh_id = mesh_params.getSculptID(); + mesh_load_map::iterator obj_iter = mLoadingMeshes[lod].find(mesh_id); + if (obj_iter != mLoadingMeshes[lod].end()) + { + F32 detail = LLVolumeLODGroup::getVolumeScaleFromDetail(lod); + + LLVolume* sys_volume = LLPrimitive::getVolumeManager()->refVolume(mesh_params, lod); + if (sys_volume) + { + sys_volume->setMeshAssetUnavaliable(true); + LLPrimitive::getVolumeManager()->unrefVolume(sys_volume); + } + + for (LLVOVolume* vobj : obj_iter->second) + { + if (vobj) + { + LLVolume* obj_volume = vobj->getVolume(); + + if (obj_volume && + obj_volume->getDetail() == detail && + obj_volume->getParams() == mesh_params) + { //should force volume to find most appropriate LOD + vobj->setVolume(obj_volume->getParams(), lod); + } + } + } + + mLoadingMeshes[lod].erase(obj_iter); + } +} + +S32 LLMeshRepository::getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod) +{ + return mThread->getActualMeshLOD(mesh_params, lod); +} + +const LLMeshSkinInfo* LLMeshRepository::getSkinInfo(const LLUUID& mesh_id, LLVOVolume* requesting_obj) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + if (mesh_id.notNull()) + { + skin_map::iterator iter = mSkinMap.find(mesh_id); + if (iter != mSkinMap.end()) + { + return iter->second; + } + + //no skin info known about given mesh, try to fetch it + if (requesting_obj != nullptr) + { + LLMutexLock lock(mMeshMutex); + //add volume to list of loading meshes + skin_load_map::iterator iter = mLoadingSkins.find(mesh_id); + if (iter != mLoadingSkins.end()) + { //request pending for this mesh, append volume id to list + auto it = std::find(iter->second.begin(), iter->second.end(), requesting_obj); + if (it == iter->second.end()) { + iter->second.push_back(requesting_obj); + } + } + else + { + //first request for this mesh + mLoadingSkins[mesh_id].push_back(requesting_obj); + mPendingSkinRequests.push(mesh_id); + } + } + } + return nullptr; +} + +void LLMeshRepository::fetchPhysicsShape(const LLUUID& mesh_id) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; //LL_RECORD_BLOCK_TIME(FTM_MESH_FETCH); + + if (mesh_id.notNull()) + { + LLModel::Decomposition* decomp = NULL; + decomposition_map::iterator iter = mDecompositionMap.find(mesh_id); + if (iter != mDecompositionMap.end()) + { + decomp = iter->second; + } + + //decomposition block hasn't been fetched yet + if (!decomp || decomp->mPhysicsShapeMesh.empty()) + { + LLMutexLock lock(mMeshMutex); + //add volume to list of loading meshes + std::set::iterator iter = mLoadingPhysicsShapes.find(mesh_id); + if (iter == mLoadingPhysicsShapes.end()) + { //no request pending for this skin info + // *FIXME: Nothing ever deletes entries, can't be right + mLoadingPhysicsShapes.insert(mesh_id); + mPendingPhysicsShapeRequests.push(mesh_id); + } + } + } +} + +LLModel::Decomposition* LLMeshRepository::getDecomposition(const LLUUID& mesh_id) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; //LL_RECORD_BLOCK_TIME(FTM_MESH_FETCH); + + LLModel::Decomposition* ret = NULL; + + if (mesh_id.notNull()) + { + decomposition_map::iterator iter = mDecompositionMap.find(mesh_id); + if (iter != mDecompositionMap.end()) + { + ret = iter->second; + } + + //decomposition block hasn't been fetched yet + if (!ret || ret->mBaseHullMesh.empty()) + { + LLMutexLock lock(mMeshMutex); + //add volume to list of loading meshes + std::set::iterator iter = mLoadingDecompositions.find(mesh_id); + if (iter == mLoadingDecompositions.end()) + { //no request pending for this skin info + mLoadingDecompositions.insert(mesh_id); + mPendingDecompositionRequests.push(mesh_id); + } + } + } + + return ret; +} + +void LLMeshRepository::buildHull(const LLVolumeParams& params, S32 detail) +{ + LLVolume* volume = LLPrimitive::sVolumeManager->refVolume(params, detail); + + if (!volume->mHullPoints) + { + //all default params + //execute first stage + //set simplify mode to retain + //set retain percentage to zero + //run second stage + } + + LLPrimitive::sVolumeManager->unrefVolume(volume); +} + +bool LLMeshRepository::hasPhysicsShape(const LLUUID& mesh_id) +{ + if (mesh_id.isNull()) + { + return false; + } + + if (mThread->hasPhysicsShapeInHeader(mesh_id)) + { + return true; + } + + LLModel::Decomposition* decomp = getDecomposition(mesh_id); + if (decomp && !decomp->mHull.empty()) + { + return true; + } + + return false; +} + +bool LLMeshRepository::hasSkinInfo(const LLUUID& mesh_id) +{ + if (mesh_id.isNull()) + { + return false; + } + + if (mThread->hasSkinInfoInHeader(mesh_id)) + { + return true; + } + + const LLMeshSkinInfo* skininfo = getSkinInfo(mesh_id); + if (skininfo) + { + return true; + } + + return false; +} + +bool LLMeshRepository::hasHeader(const LLUUID& mesh_id) +{ + if (mesh_id.isNull()) + { + return false; + } + + return mThread->hasHeader(mesh_id); +} + +bool LLMeshRepoThread::hasPhysicsShapeInHeader(const LLUUID& mesh_id) +{ + LLMutexLock lock(mHeaderMutex); + mesh_header_map::iterator iter = mMeshHeader.find(mesh_id); + if (iter != mMeshHeader.end() && iter->second.first > 0) + { + LLMeshHeader &mesh = iter->second.second; + if (mesh.mPhysicsMeshSize > 0) + { + return true; + } + } + + return false; +} + +bool LLMeshRepoThread::hasSkinInfoInHeader(const LLUUID& mesh_id) +{ + LLMutexLock lock(mHeaderMutex); + mesh_header_map::iterator iter = mMeshHeader.find(mesh_id); + if (iter != mMeshHeader.end() && iter->second.first > 0) + { + LLMeshHeader& mesh = iter->second.second; + if (mesh.mSkinOffset >= 0 + && mesh.mSkinSize > 0) + { + return true; + } + } + + return false; +} + +bool LLMeshRepoThread::hasHeader(const LLUUID& mesh_id) +{ + LLMutexLock lock(mHeaderMutex); + mesh_header_map::iterator iter = mMeshHeader.find(mesh_id); + return iter != mMeshHeader.end(); +} + +void LLMeshRepository::uploadModel(std::vector& data, LLVector3& scale, bool upload_textures, + bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, + std::string upload_url, bool do_upload, + LLHandle fee_observer, LLHandle upload_observer) +{ + LLMeshUploadThread* thread = new LLMeshUploadThread(data, scale, upload_textures, + upload_skin, upload_joints, lock_scale_if_joint_position, + upload_url, do_upload, fee_observer, upload_observer); + mUploadWaitList.push_back(thread); +} + +S32 LLMeshRepository::getMeshSize(const LLUUID& mesh_id, S32 lod) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + if (mThread && mesh_id.notNull() && LLPrimitive::NO_LOD != lod) + { + LLMutexLock lock(mThread->mHeaderMutex); + LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id); + if (iter != mThread->mMeshHeader.end() && iter->second.first > 0) + { + const LLMeshHeader& header = iter->second.second; + + if (header.m404) + { + return -1; + } + + S32 size = header.mLodSize[lod]; + return size; + } + + } + + return -1; +} + +void LLMeshUploadThread::decomposeMeshMatrix(LLMatrix4& transformation, + LLVector3& result_pos, + LLQuaternion& result_rot, + LLVector3& result_scale) +{ + // check for reflection + bool reflected = (transformation.determinant() < 0); + + // compute position + LLVector3 position = LLVector3(0, 0, 0) * transformation; + + // compute scale + LLVector3 x_transformed = LLVector3(1, 0, 0) * transformation - position; + LLVector3 y_transformed = LLVector3(0, 1, 0) * transformation - position; + LLVector3 z_transformed = LLVector3(0, 0, 1) * transformation - position; + F32 x_length = x_transformed.normalize(); + F32 y_length = y_transformed.normalize(); + F32 z_length = z_transformed.normalize(); + LLVector3 scale = LLVector3(x_length, y_length, z_length); + + // adjust for "reflected" geometry + LLVector3 x_transformed_reflected = x_transformed; + if (reflected) + { + x_transformed_reflected *= -1.0; + } + + // compute rotation + LLMatrix3 rotation_matrix; + rotation_matrix.setRows(x_transformed_reflected, y_transformed, z_transformed); + LLQuaternion quat_rotation = rotation_matrix.quaternion(); + quat_rotation.normalize(); // the rotation_matrix might not have been orthoginal. make it so here. + LLVector3 euler_rotation; + quat_rotation.getEulerAngles(&euler_rotation.mV[VX], &euler_rotation.mV[VY], &euler_rotation.mV[VZ]); + + result_pos = position + mOrigin; + result_scale = scale; + result_rot = quat_rotation; +} + +void LLMeshRepository::updateInventory(inventory_data data) +{ + LLMutexLock lock(mMeshMutex); + dump_llsd_to_file(data.mPostData,make_dump_name("update_inventory_post_data_",dump_num)); + dump_llsd_to_file(data.mResponse,make_dump_name("update_inventory_response_",dump_num)); + mInventoryQ.push(data); +} + +void LLMeshRepository::uploadError(LLSD& args) +{ + LLMutexLock lock(mMeshMutex); + mUploadErrorQ.push(args); +} + +F32 LLMeshRepository::getEstTrianglesMax(LLUUID mesh_id) +{ + LLMeshCostData costs; + if (getCostData(mesh_id, costs)) + { + return costs.getEstTrisMax(); + } + else + { + return 0.f; + } +} + +F32 LLMeshRepository::getEstTrianglesStreamingCost(LLUUID mesh_id) +{ + LLMeshCostData costs; + if (getCostData(mesh_id, costs)) + { + return costs.getEstTrisForStreamingCost(); + } + else + { + return 0.f; + } +} + +// FIXME replace with calc based on LLMeshCostData +F32 LLMeshRepository::getStreamingCostLegacy(LLUUID mesh_id, F32 radius, S32* bytes, S32* bytes_visible, S32 lod, F32 *unscaled_value) +{ + F32 result = 0.f; + if (mThread && mesh_id.notNull()) + { + LLMutexLock lock(mThread->mHeaderMutex); + LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id); + if (iter != mThread->mMeshHeader.end() && iter->second.first > 0) + { + result = getStreamingCostLegacy(iter->second.second, radius, bytes, bytes_visible, lod, unscaled_value); + } + } + if (result > 0.f) + { + LLMeshCostData data; + if (getCostData(mesh_id, data)) + { + F32 ref_streaming_cost = data.getRadiusBasedStreamingCost(radius); + F32 ref_weighted_tris = data.getRadiusWeightedTris(radius); + if (!is_approx_equal(ref_streaming_cost,result)) + { + LL_WARNS() << mesh_id << "streaming mismatch " << result << " " << ref_streaming_cost << LL_ENDL; + } + if (unscaled_value && !is_approx_equal(ref_weighted_tris,*unscaled_value)) + { + LL_WARNS() << mesh_id << "weighted_tris mismatch " << *unscaled_value << " " << ref_weighted_tris << LL_ENDL; + } + if (bytes && (*bytes != data.getSizeTotal())) + { + LL_WARNS() << mesh_id << "bytes mismatch " << *bytes << " " << data.getSizeTotal() << LL_ENDL; + } + if (bytes_visible && (lod >=0) && (lod < LLVolumeLODGroup::NUM_LODS) && (*bytes_visible != data.getSizeByLOD(lod))) + { + LL_WARNS() << mesh_id << "bytes_visible mismatch " << *bytes_visible << " " << data.getSizeByLOD(lod) << LL_ENDL; + } + } + else + { + LL_WARNS() << "getCostData failed!!!" << LL_ENDL; + } + } + return result; +} + +// FIXME replace with calc based on LLMeshCostData +//static +F32 LLMeshRepository::getStreamingCostLegacy(LLMeshHeader& header, F32 radius, S32* bytes, S32* bytes_visible, S32 lod, F32 *unscaled_value) +{ + if (header.m404 + || header.mLodSize[0] <= 0 + || (header.mVersion > MAX_MESH_VERSION)) + { + return 0.f; + } + + F32 max_distance = 512.f; + + F32 dlowest = llmin(radius/0.03f, max_distance); + F32 dlow = llmin(radius/0.06f, max_distance); + F32 dmid = llmin(radius/0.24f, max_distance); + + static LLCachedControl metadata_discount_ch(gSavedSettings, "MeshMetaDataDiscount", 384); //discount 128 bytes to cover the cost of LLSD tags and compression domain overhead + static LLCachedControl minimum_size_ch(gSavedSettings, "MeshMinimumByteSize", 16); //make sure nothing is "free" + static LLCachedControl bytes_per_triangle_ch(gSavedSettings, "MeshBytesPerTriangle", 16); + + F32 metadata_discount = (F32)metadata_discount_ch; + F32 minimum_size = (F32)minimum_size_ch; + F32 bytes_per_triangle = (F32)bytes_per_triangle_ch; + + S32 bytes_lowest = header.mLodSize[0]; + S32 bytes_low = header.mLodSize[1]; + S32 bytes_mid = header.mLodSize[2]; + S32 bytes_high = header.mLodSize[3]; + + if (bytes_high == 0) + { + return 0.f; + } + + if (bytes_mid == 0) + { + bytes_mid = bytes_high; + } + + if (bytes_low == 0) + { + bytes_low = bytes_mid; + } + + if (bytes_lowest == 0) + { + bytes_lowest = bytes_low; + } + + F32 triangles_lowest = llmax((F32) bytes_lowest-metadata_discount, minimum_size)/bytes_per_triangle; + F32 triangles_low = llmax((F32) bytes_low-metadata_discount, minimum_size)/bytes_per_triangle; + F32 triangles_mid = llmax((F32) bytes_mid-metadata_discount, minimum_size)/bytes_per_triangle; + F32 triangles_high = llmax((F32) bytes_high-metadata_discount, minimum_size)/bytes_per_triangle; + + if (bytes) + { + *bytes = 0; + *bytes += header.mLodSize[0]; + *bytes += header.mLodSize[1]; + *bytes += header.mLodSize[2]; + *bytes += header.mLodSize[3]; + } + + if (bytes_visible) + { + lod = LLMeshRepository::getActualMeshLOD(header, lod); + if (lod >= 0 && lod <= 3) + { + *bytes_visible = header.mLodSize[lod]; + } + } + + F32 max_area = 102944.f; //area of circle that encompasses region (see MAINT-6559) + F32 min_area = 1.f; + + F32 high_area = llmin(F_PI*dmid*dmid, max_area); + F32 mid_area = llmin(F_PI*dlow*dlow, max_area); + F32 low_area = llmin(F_PI*dlowest*dlowest, max_area); + F32 lowest_area = max_area; + + lowest_area -= low_area; + low_area -= mid_area; + mid_area -= high_area; + + high_area = llclamp(high_area, min_area, max_area); + mid_area = llclamp(mid_area, min_area, max_area); + low_area = llclamp(low_area, min_area, max_area); + lowest_area = llclamp(lowest_area, min_area, max_area); + + F32 total_area = high_area + mid_area + low_area + lowest_area; + high_area /= total_area; + mid_area /= total_area; + low_area /= total_area; + lowest_area /= total_area; + + F32 weighted_avg = triangles_high*high_area + + triangles_mid*mid_area + + triangles_low*low_area + + triangles_lowest*lowest_area; + + if (unscaled_value) + { + *unscaled_value = weighted_avg; + } + + return weighted_avg/gSavedSettings.getU32("MeshTriangleBudget")*15000.f; +} + +LLMeshCostData::LLMeshCostData() +{ + std::fill(mSizeByLOD.begin(), mSizeByLOD.end(), 0); + std::fill(mEstTrisByLOD.begin(), mEstTrisByLOD.end(), 0.f); +} + +bool LLMeshCostData::init(const LLMeshHeader& header) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + + std::fill(mSizeByLOD.begin(), mSizeByLOD.end(), 0); + std::fill(mEstTrisByLOD.begin(), mEstTrisByLOD.end(), 0.f); + + S32 bytes_high = header.mLodSize[3]; + S32 bytes_med = header.mLodSize[2]; + if (bytes_med == 0) + { + bytes_med = bytes_high; + } + S32 bytes_low = header.mLodSize[1]; + if (bytes_low == 0) + { + bytes_low = bytes_med; + } + S32 bytes_lowest = header.mLodSize[0]; + if (bytes_lowest == 0) + { + bytes_lowest = bytes_low; + } + mSizeByLOD[0] = bytes_lowest; + mSizeByLOD[1] = bytes_low; + mSizeByLOD[2] = bytes_med; + mSizeByLOD[3] = bytes_high; + + static LLCachedControl metadata_discount(gSavedSettings, "MeshMetaDataDiscount", 384); //discount 128 bytes to cover the cost of LLSD tags and compression domain overhead + static LLCachedControl minimum_size(gSavedSettings, "MeshMinimumByteSize", 16); //make sure nothing is "free" + static LLCachedControl bytes_per_triangle(gSavedSettings, "MeshBytesPerTriangle", 16); + + for (S32 i=0; i=0; i--) + { + // How many tris can we have in this LOD without affecting land impact? + // - normally an LOD should be at most half the size of the previous one. + // - once we reach a floor of ENFORCE_FLOOR, don't require LODs to get any smaller. + allowed_tris = llclamp(allowed_tris/2.0f,ENFORCE_FLOOR,mEstTrisByLOD[i]); + F32 excess_tris = mEstTrisByLOD[i]-allowed_tris; + if (excess_tris>0.f) + { + LL_DEBUGS("StreamingCost") << "excess tris in lod[" << i << "] " << excess_tris << " allowed " << allowed_tris << LL_ENDL; + charged_tris += excess_tris; + } + } + return charged_tris; +} + +F32 LLMeshCostData::getRadiusBasedStreamingCost(F32 radius) +{ + return getRadiusWeightedTris(radius)/gSavedSettings.getU32("MeshTriangleBudget")*15000.f; +} + +F32 LLMeshCostData::getTriangleBasedStreamingCost() +{ + F32 result = ANIMATED_OBJECT_COST_PER_KTRI * 0.001 * getEstTrisForStreamingCost(); + return result; +} + +bool LLMeshRepository::getCostData(LLUUID mesh_id, LLMeshCostData& data) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + data = LLMeshCostData(); + + if (mThread && mesh_id.notNull()) + { + LLMutexLock lock(mThread->mHeaderMutex); + LLMeshRepoThread::mesh_header_map::iterator iter = mThread->mMeshHeader.find(mesh_id); + if (iter != mThread->mMeshHeader.end() && iter->second.first > 0) + { + LLMeshHeader& header = iter->second.second; + + bool header_invalid = (header.m404 + || header.mLodSize[0] <= 0 + || header.mVersion > MAX_MESH_VERSION); + if (!header_invalid) + { + return getCostData(header, data); + } + + return true; + } + } + return false; +} + +bool LLMeshRepository::getCostData(LLMeshHeader& header, LLMeshCostData& data) +{ + data = LLMeshCostData(); + + if (!data.init(header)) + { + return false; + } + + return true; +} + +LLPhysicsDecomp::LLPhysicsDecomp() +: LLThread("Physics Decomp") +{ + mInited = false; + mQuitting = false; + mDone = false; + + mSignal = new LLCondition(); + mMutex = new LLMutex(); +} + +LLPhysicsDecomp::~LLPhysicsDecomp() +{ + shutdown(); + + delete mSignal; + mSignal = NULL; + delete mMutex; + mMutex = NULL; +} + +void LLPhysicsDecomp::shutdown() +{ + if (mSignal) + { + mQuitting = true; + // There is only one wait(), but just in case 'broadcast' + mSignal->broadcast(); + + while (!isStopped()) + { + apr_sleep(10); + } + } +} + +void LLPhysicsDecomp::submitRequest(LLPhysicsDecomp::Request* request) +{ + LLMutexLock lock(mMutex); + mRequestQ.push(request); + mSignal->signal(); +} + +//static +S32 LLPhysicsDecomp::llcdCallback(const char* status, S32 p1, S32 p2) +{ + if (gMeshRepo.mDecompThread && gMeshRepo.mDecompThread->mCurRequest.notNull()) + { + return gMeshRepo.mDecompThread->mCurRequest->statusCallback(status, p1, p2); + } + + return 1; +} + +void LLPhysicsDecomp::setMeshData(LLCDMeshData& mesh, bool vertex_based) +{ + mesh.mVertexBase = mCurRequest->mPositions[0].mV; + mesh.mVertexStrideBytes = 12; + mesh.mNumVertices = mCurRequest->mPositions.size(); + + if(!vertex_based) + { + mesh.mIndexType = LLCDMeshData::INT_16; + mesh.mIndexBase = &(mCurRequest->mIndices[0]); + mesh.mIndexStrideBytes = 6; + + mesh.mNumTriangles = mCurRequest->mIndices.size()/3; + } + + if ((vertex_based || mesh.mNumTriangles > 0) && mesh.mNumVertices > 2) + { + LLCDResult ret = LLCD_OK; + if (LLConvexDecomposition::getInstance() != NULL) + { + ret = LLConvexDecomposition::getInstance()->setMeshData(&mesh, vertex_based); + } + + if (ret) + { + LL_ERRS(LOG_MESH) << "Convex Decomposition thread valid but could not set mesh data." << LL_ENDL; + } + } +} + +void LLPhysicsDecomp::doDecomposition() +{ + LLCDMeshData mesh; + S32 stage = mStageID[mCurRequest->mStage]; + + if (LLConvexDecomposition::getInstance() == NULL) + { + // stub library. do nothing. + return; + } + + //load data intoLLCD + if (stage == 0) + { + setMeshData(mesh, false); + } + + //build parameter map + std::map param_map; + + static const LLCDParam* params = NULL; + static S32 param_count = 0; + if (!params) + { + param_count = LLConvexDecomposition::getInstance()->getParameters(¶ms); + } + + for (S32 i = 0; i < param_count; ++i) + { + param_map[params[i].mName] = params+i; + } + + U32 ret = LLCD_OK; + //set parameter values + for (decomp_params::iterator iter = mCurRequest->mParams.begin(); iter != mCurRequest->mParams.end(); ++iter) + { + const std::string& name = iter->first; + const LLSD& value = iter->second; + + const LLCDParam* param = param_map[name]; + + if (param == NULL) + { //couldn't find valid parameter + continue; + } + + + if (param->mType == LLCDParam::LLCD_FLOAT) + { + ret = LLConvexDecomposition::getInstance()->setParam(param->mName, (F32) value.asReal()); + } + else if (param->mType == LLCDParam::LLCD_INTEGER || + param->mType == LLCDParam::LLCD_ENUM) + { + ret = LLConvexDecomposition::getInstance()->setParam(param->mName, value.asInteger()); + } + else if (param->mType == LLCDParam::LLCD_BOOLEAN) + { + ret = LLConvexDecomposition::getInstance()->setParam(param->mName, value.asBoolean()); + } + } + + mCurRequest->setStatusMessage("Executing."); + + if (LLConvexDecomposition::getInstance() != NULL) + { + ret = LLConvexDecomposition::getInstance()->executeStage(stage); + } + + if (ret) + { + LL_WARNS(LOG_MESH) << "Convex Decomposition thread valid but could not execute stage " << stage << "." + << LL_ENDL; + LLMutexLock lock(mMutex); + + mCurRequest->mHull.clear(); + mCurRequest->mHullMesh.clear(); + + mCurRequest->setStatusMessage("FAIL"); + + completeCurrent(); + } + else + { + mCurRequest->setStatusMessage("Reading results"); + + S32 num_hulls =0; + if (LLConvexDecomposition::getInstance() != NULL) + { + num_hulls = LLConvexDecomposition::getInstance()->getNumHullsFromStage(stage); + } + + { + LLMutexLock lock(mMutex); + mCurRequest->mHull.clear(); + mCurRequest->mHull.resize(num_hulls); + + mCurRequest->mHullMesh.clear(); + mCurRequest->mHullMesh.resize(num_hulls); + } + + for (S32 i = 0; i < num_hulls; ++i) + { + std::vector p; + LLCDHull hull; + // if LLConvexDecomposition is a stub, num_hulls should have been set to 0 above, and we should not reach this code + LLConvexDecomposition::getInstance()->getHullFromStage(stage, i, &hull); + + const F32* v = hull.mVertexBase; + + for (S32 j = 0; j < hull.mNumVertices; ++j) + { + LLVector3 vert(v[0], v[1], v[2]); + p.push_back(vert); + v = (F32*) (((U8*) v) + hull.mVertexStrideBytes); + } + + LLCDMeshData mesh; + // if LLConvexDecomposition is a stub, num_hulls should have been set to 0 above, and we should not reach this code + LLConvexDecomposition::getInstance()->getMeshFromStage(stage, i, &mesh); + + get_vertex_buffer_from_mesh(mesh, mCurRequest->mHullMesh[i]); + + { + LLMutexLock lock(mMutex); + mCurRequest->mHull[i] = p; + } + } + + { + LLMutexLock lock(mMutex); + mCurRequest->setStatusMessage("FAIL"); + completeCurrent(); + } + } +} + +void LLPhysicsDecomp::completeCurrent() +{ + LLMutexLock lock(mMutex); + mCompletedQ.push(mCurRequest); + mCurRequest = NULL; +} + +void LLPhysicsDecomp::notifyCompleted() +{ + if (!mCompletedQ.empty()) + { + LLMutexLock lock(mMutex); + while (!mCompletedQ.empty()) + { + Request* req = mCompletedQ.front(); + req->completed(); + mCompletedQ.pop(); + } + } +} + + +void make_box(LLPhysicsDecomp::Request * request) +{ + LLVector3 min,max; + min = request->mPositions[0]; + max = min; + + for (U32 i = 0; i < request->mPositions.size(); ++i) + { + update_min_max(min, max, request->mPositions[i]); + } + + request->mHull.clear(); + + LLModel::hull box; + box.push_back(LLVector3(min[0],min[1],min[2])); + box.push_back(LLVector3(max[0],min[1],min[2])); + box.push_back(LLVector3(min[0],max[1],min[2])); + box.push_back(LLVector3(max[0],max[1],min[2])); + box.push_back(LLVector3(min[0],min[1],max[2])); + box.push_back(LLVector3(max[0],min[1],max[2])); + box.push_back(LLVector3(min[0],max[1],max[2])); + box.push_back(LLVector3(max[0],max[1],max[2])); + + request->mHull.push_back(box); +} + + +void LLPhysicsDecomp::doDecompositionSingleHull() +{ + LLConvexDecomposition* decomp = LLConvexDecomposition::getInstance(); + + if (decomp == NULL) + { + //stub. do nothing. + return; + } + + LLCDMeshData mesh; + + setMeshData(mesh, true); + + LLCDResult ret = decomp->buildSingleHull() ; + if (ret) + { + LL_WARNS(LOG_MESH) << "Could not execute decomposition stage when attempting to create single hull." << LL_ENDL; + make_box(mCurRequest); + } + else + { + { + LLMutexLock lock(mMutex); + mCurRequest->mHull.clear(); + mCurRequest->mHull.resize(1); + mCurRequest->mHullMesh.clear(); + } + + std::vector p; + LLCDHull hull; + + // if LLConvexDecomposition is a stub, num_hulls should have been set to 0 above, and we should not reach this code + decomp->getSingleHull(&hull); + + const F32* v = hull.mVertexBase; + + for (S32 j = 0; j < hull.mNumVertices; ++j) + { + LLVector3 vert(v[0], v[1], v[2]); + p.push_back(vert); + v = (F32*) (((U8*) v) + hull.mVertexStrideBytes); + } + + { + LLMutexLock lock(mMutex); + mCurRequest->mHull[0] = p; + } + } + + { + completeCurrent(); + + } +} + + +void LLPhysicsDecomp::run() +{ + LLConvexDecomposition* decomp = LLConvexDecomposition::getInstance(); + if (decomp == NULL) + { + // stub library. Set init to true so the main thread + // doesn't wait for this to finish. + mInited = true; + return; + } + + decomp->initThread(); + mInited = true; + + static const LLCDStageData* stages = NULL; + static S32 num_stages = 0; + + if (!stages) + { + num_stages = decomp->getStages(&stages); + } + + for (S32 i = 0; i < num_stages; i++) + { + mStageID[stages[i].mName] = i; + } + + while (!mQuitting) + { + mSignal->wait(); + while (!mQuitting && !mRequestQ.empty()) + { + { + LLMutexLock lock(mMutex); + mCurRequest = mRequestQ.front(); + mRequestQ.pop(); + } + + S32& id = *(mCurRequest->mDecompID); + if (id == -1) + { + decomp->genDecomposition(id); + } + decomp->bindDecomposition(id); + + if (mCurRequest->mStage == "single_hull") + { + doDecompositionSingleHull(); + } + else + { + doDecomposition(); + } + } + } + + decomp->quitThread(); + + if (mSignal->isLocked()) + { //let go of mSignal's associated mutex + mSignal->unlock(); + } + + mDone = true; +} + +void LLPhysicsDecomp::Request::assignData(LLModel* mdl) +{ + if (!mdl) + { + return ; + } + + U16 index_offset = 0; + U16 tri[3] ; + + mPositions.clear(); + mIndices.clear(); + mBBox[1] = LLVector3(F32_MIN, F32_MIN, F32_MIN) ; + mBBox[0] = LLVector3(F32_MAX, F32_MAX, F32_MAX) ; + + //queue up vertex positions and indices + for (S32 i = 0; i < mdl->getNumVolumeFaces(); ++i) + { + const LLVolumeFace& face = mdl->getVolumeFace(i); + if (mPositions.size() + face.mNumVertices > 65535) + { + continue; + } + + for (U32 j = 0; j < face.mNumVertices; ++j) + { + mPositions.push_back(LLVector3(face.mPositions[j].getF32ptr())); + for(U32 k = 0 ; k < 3 ; k++) + { + mBBox[0].mV[k] = llmin(mBBox[0].mV[k], mPositions[j].mV[k]) ; + mBBox[1].mV[k] = llmax(mBBox[1].mV[k], mPositions[j].mV[k]) ; + } + } + + updateTriangleAreaThreshold() ; + + for (U32 j = 0; j+2 < face.mNumIndices; j += 3) + { + tri[0] = face.mIndices[j] + index_offset ; + tri[1] = face.mIndices[j + 1] + index_offset ; + tri[2] = face.mIndices[j + 2] + index_offset ; + + if(isValidTriangle(tri[0], tri[1], tri[2])) + { + mIndices.push_back(tri[0]); + mIndices.push_back(tri[1]); + mIndices.push_back(tri[2]); + } + } + + index_offset += face.mNumVertices; + } + + return ; +} + +void LLPhysicsDecomp::Request::updateTriangleAreaThreshold() +{ + F32 range = mBBox[1].mV[0] - mBBox[0].mV[0] ; + range = llmin(range, mBBox[1].mV[1] - mBBox[0].mV[1]) ; + range = llmin(range, mBBox[1].mV[2] - mBBox[0].mV[2]) ; + + mTriangleAreaThreshold = llmin(0.0002f, range * 0.000002f) ; +} + +//check if the triangle area is large enough to qualify for a valid triangle +bool LLPhysicsDecomp::Request::isValidTriangle(U16 idx1, U16 idx2, U16 idx3) +{ + LLVector3 a = mPositions[idx2] - mPositions[idx1] ; + LLVector3 b = mPositions[idx3] - mPositions[idx1] ; + F32 c = a * b ; + + return ((a*a) * (b*b) - c * c) > mTriangleAreaThreshold ; +} + +void LLPhysicsDecomp::Request::setStatusMessage(const std::string& msg) +{ + mStatusMessage = msg; +} + +void LLMeshRepository::buildPhysicsMesh(LLModel::Decomposition& decomp) +{ + decomp.mMesh.resize(decomp.mHull.size()); + + for (U32 i = 0; i < decomp.mHull.size(); ++i) + { + LLCDHull hull; + hull.mNumVertices = decomp.mHull[i].size(); + hull.mVertexBase = decomp.mHull[i][0].mV; + hull.mVertexStrideBytes = 12; + + LLCDMeshData mesh; + LLCDResult res = LLCD_OK; + if (LLConvexDecomposition::getInstance() != NULL) + { + res = LLConvexDecomposition::getInstance()->getMeshFromHull(&hull, &mesh); + } + if (res == LLCD_OK) + { + get_vertex_buffer_from_mesh(mesh, decomp.mMesh[i]); + } + } + + if (!decomp.mBaseHull.empty() && decomp.mBaseHullMesh.empty()) + { //get mesh for base hull + LLCDHull hull; + hull.mNumVertices = decomp.mBaseHull.size(); + hull.mVertexBase = decomp.mBaseHull[0].mV; + hull.mVertexStrideBytes = 12; + + LLCDMeshData mesh; + LLCDResult res = LLCD_OK; + if (LLConvexDecomposition::getInstance() != NULL) + { + res = LLConvexDecomposition::getInstance()->getMeshFromHull(&hull, &mesh); + } + if (res == LLCD_OK) + { + get_vertex_buffer_from_mesh(mesh, decomp.mBaseHullMesh); + } + } +} + + +bool LLMeshRepository::meshUploadEnabled() +{ + LLViewerRegion *region = gAgent.getRegion(); + if(gSavedSettings.getBOOL("MeshEnabled") && + region) + { + return region->meshUploadEnabled(); + } + return false; +} + +bool LLMeshRepository::meshRezEnabled() +{ + LLViewerRegion *region = gAgent.getRegion(); + if(gSavedSettings.getBOOL("MeshEnabled") && + region) + { + return region->meshRezEnabled(); + } + return false; +} + +// Threading: main thread only +// static +void LLMeshRepository::metricsStart() +{ + ++metrics_teleport_start_count; + sQuiescentTimer.start(0); +} + +// Threading: main thread only +// static +void LLMeshRepository::metricsStop() +{ + sQuiescentTimer.stop(0); +} + +// Threading: main thread only +// static +void LLMeshRepository::metricsProgress(unsigned int this_count) +{ + static bool first_start(true); + + if (first_start) + { + metricsStart(); + first_start = false; + } + sQuiescentTimer.ringBell(0, this_count); +} + +// Threading: main thread only +// static +void LLMeshRepository::metricsUpdate() +{ + F64 started, stopped; + U64 total_count(U64L(0)), user_cpu(U64L(0)), sys_cpu(U64L(0)); + + if (sQuiescentTimer.isExpired(0, started, stopped, total_count, user_cpu, sys_cpu)) + { + LLSD metrics; + + metrics["reason"] = "Mesh Download Quiescent"; + metrics["scope"] = metrics_teleport_start_count > 1 ? "Teleport" : "Login"; + metrics["start"] = started; + metrics["stop"] = stopped; + metrics["fetches"] = LLSD::Integer(total_count); + metrics["teleports"] = LLSD::Integer(metrics_teleport_start_count); + metrics["user_cpu"] = double(user_cpu) / 1.0e6; + metrics["sys_cpu"] = double(sys_cpu) / 1.0e6; + LL_INFOS(LOG_MESH) << "EventMarker " << metrics << LL_ENDL; + } +} + +// Threading: main thread only +// static +void teleport_started() +{ + LLMeshRepository::metricsStart(); +} + + +void on_new_single_inventory_upload_complete( + LLAssetType::EType asset_type, + LLInventoryType::EType inventory_type, + const std::string inventory_type_string, + const LLUUID& item_folder_id, + const std::string& item_name, + const std::string& item_description, + const LLSD& server_response, + S32 upload_price) +{ + bool success = false; + + if (upload_price > 0) + { + // this upload costed us L$, update our balance + // and display something saying that it cost L$ + LLStatusBar::sendMoneyBalanceRequest(); + + LLSD args; + args["AMOUNT"] = llformat("%d", upload_price); + LLNotificationsUtil::add("UploadPayment", args); + } + + if (item_folder_id.notNull()) + { + U32 everyone_perms = PERM_NONE; + U32 group_perms = PERM_NONE; + U32 next_owner_perms = PERM_ALL; + if (server_response.has("new_next_owner_mask")) + { + // The server provided creation perms so use them. + // Do not assume we got the perms we asked for in + // since the server may not have granted them all. + everyone_perms = server_response["new_everyone_mask"].asInteger(); + group_perms = server_response["new_group_mask"].asInteger(); + next_owner_perms = server_response["new_next_owner_mask"].asInteger(); + } + else + { + // The server doesn't provide creation perms + // so use old assumption-based perms. + if (inventory_type_string != "snapshot") + { + next_owner_perms = PERM_MOVE | PERM_TRANSFER; + } + } + + LLPermissions new_perms; + new_perms.init( + gAgent.getID(), + gAgent.getID(), + LLUUID::null, + LLUUID::null); + + new_perms.initMasks( + PERM_ALL, + PERM_ALL, + everyone_perms, + group_perms, + next_owner_perms); + + U32 inventory_item_flags = 0; + if (server_response.has("inventory_flags")) + { + inventory_item_flags = (U32)server_response["inventory_flags"].asInteger(); + if (inventory_item_flags != 0) + { + LL_INFOS() << "inventory_item_flags " << inventory_item_flags << LL_ENDL; + } + } + S32 creation_date_now = time_corrected(); + LLPointer item = new LLViewerInventoryItem( + server_response["new_inventory_item"].asUUID(), + item_folder_id, + new_perms, + server_response["new_asset"].asUUID(), + asset_type, + inventory_type, + item_name, + item_description, + LLSaleInfo::DEFAULT, + inventory_item_flags, + creation_date_now); + + gInventory.updateItem(item); + gInventory.notifyObservers(); + success = true; + + LLFocusableElement* focus = gFocusMgr.getKeyboardFocus(); + + // Show the preview panel for textures and sounds to let + // user know that the image (or snapshot) arrived intact. + LLInventoryPanel* panel = LLInventoryPanel::getActiveInventoryPanel(false); + if (panel) + { + + panel->setSelection( + server_response["new_inventory_item"].asUUID(), + TAKE_FOCUS_NO); + } + else + { + LLInventoryPanel::openInventoryPanelAndSetSelection(true, server_response["new_inventory_item"].asUUID(), true, false, true); + } + + // restore keyboard focus + gFocusMgr.setKeyboardFocus(focus); + } + else + { + LL_WARNS() << "Can't find a folder to put it in" << LL_ENDL; + } + + // Todo: This is mesh repository code, is following code really needed? + // remove the "Uploading..." message + LLUploadDialog::modalUploadFinished(); + + // Let the Snapshot floater know we have finished uploading a snapshot to inventory. + LLFloater* floater_snapshot = LLFloaterReg::findInstance("snapshot"); + if (asset_type == LLAssetType::AT_TEXTURE && floater_snapshot) + { + floater_snapshot->notify(LLSD().with("set-finished", LLSD().with("ok", success).with("msg", "inventory"))); + } +} diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index d62c6eb894..b9430a6165 100644 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -1,746 +1,746 @@ -/** - * @file llmeshrepository.h - * @brief Client-side repository of mesh assets. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010-2013, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_MESH_REPOSITORY_H -#define LL_MESH_REPOSITORY_H - -#include -#include "llassettype.h" -#include "llmodel.h" -#include "lluuid.h" -#include "llviewertexture.h" -#include "llvolume.h" -#include "lldeadmantimer.h" -#include "httpcommon.h" -#include "httprequest.h" -#include "httpoptions.h" -#include "httpheaders.h" -#include "httphandler.h" -#include "llthread.h" - -#define LLCONVEXDECOMPINTER_STATIC 1 - -#include "llconvexdecomposition.h" -#include "lluploadfloaterobservers.h" - -class LLVOVolume; -class LLMutex; -class LLCondition; -class LLMeshRepository; - -typedef enum e_mesh_processing_result_enum -{ - MESH_OK = 0, - MESH_NO_DATA = 1, - MESH_OUT_OF_MEMORY, - MESH_HTTP_REQUEST_FAILED, - MESH_PARSE_FAILURE, - MESH_INVALID, - MESH_UNKNOWN -} EMeshProcessingResult; - -class LLMeshUploadData -{ -public: - LLPointer mBaseModel; - LLPointer mModel[5]; - LLUUID mUUID; - U32 mRetries; - std::string mRSVP; - std::string mAssetData; - LLSD mPostData; - - LLMeshUploadData() - { - mRetries = 0; - } -}; - -class LLTextureUploadData -{ -public: - LLViewerFetchedTexture* mTexture; - LLUUID mUUID; - std::string mRSVP; - std::string mLabel; - U32 mRetries; - std::string mAssetData; - LLSD mPostData; - - LLTextureUploadData() - { - mRetries = 0; - } - - LLTextureUploadData(LLViewerFetchedTexture* texture, std::string& label) - : mTexture(texture), mLabel(label) - { - mRetries = 0; - } -}; - -class LLPhysicsDecomp : public LLThread -{ -public: - - typedef std::map decomp_params; - - class Request : public LLRefCount - { - public: - //input params - S32* mDecompID; - std::string mStage; - std::vector mPositions; - std::vector mIndices; - decomp_params mParams; - - //output state - std::string mStatusMessage; - std::vector mHullMesh; - LLModel::convex_hull_decomposition mHull; - - //status message callback, called from decomposition thread - virtual S32 statusCallback(const char* status, S32 p1, S32 p2) = 0; - - //completed callback, called from the main thread - virtual void completed() = 0; - - virtual void setStatusMessage(const std::string& msg); - - bool isValid() const {return mPositions.size() > 2 && mIndices.size() > 2 ;} - - protected: - //internal use - LLVector3 mBBox[2] ; - F32 mTriangleAreaThreshold ; - - void assignData(LLModel* mdl) ; - void updateTriangleAreaThreshold() ; - bool isValidTriangle(U16 idx1, U16 idx2, U16 idx3) ; - }; - - LLCondition* mSignal; - LLMutex* mMutex; - - bool mInited; - bool mQuitting; - bool mDone; - - LLPhysicsDecomp(); - ~LLPhysicsDecomp(); - - void shutdown(); - - void submitRequest(Request* request); - static S32 llcdCallback(const char*, S32, S32); - void cancel(); - - void setMeshData(LLCDMeshData& mesh, bool vertex_based); - void doDecomposition(); - void doDecompositionSingleHull(); - - virtual void run(); - - void completeCurrent(); - void notifyCompleted(); - - std::map mStageID; - - typedef std::queue > request_queue; - request_queue mRequestQ; - - LLPointer mCurRequest; - - std::queue > mCompletedQ; - -}; - -class RequestStats -{ -public: - RequestStats() : mRetries(0) {}; - - void updateTime(); - bool canRetry() const; - bool isDelayed() const; - U32 getRetries() { return mRetries; } - -private: - U32 mRetries; - LLFrameTimer mTimer; -}; - -class LLMeshHeader -{ -public: - - LLMeshHeader() {} - - explicit LLMeshHeader(const LLSD& header) - { - fromLLSD(header); - } - - void fromLLSD(const LLSD& header) - { - const char* lod[] = - { - "lowest_lod", - "low_lod", - "medium_lod", - "high_lod" - }; - - mVersion = header["version"].asInteger(); - - for (U32 i = 0; i < 4; ++i) - { - mLodOffset[i] = header[lod[i]]["offset"].asInteger(); - mLodSize[i] = header[lod[i]]["size"].asInteger(); - } - - mSkinOffset = header["skin"]["offset"].asInteger(); - mSkinSize = header["skin"]["size"].asInteger(); - - mPhysicsConvexOffset = header["physics_convex"]["offset"].asInteger(); - mPhysicsConvexSize = header["physics_convex"]["size"].asInteger(); - - mPhysicsMeshOffset = header["physics_mesh"]["offset"].asInteger(); - mPhysicsMeshSize = header["physics_mesh"]["size"].asInteger(); - - m404 = header.has("404"); - } - - S32 mVersion = -1; - S32 mSkinOffset = -1; - S32 mSkinSize = -1; - - S32 mPhysicsConvexOffset = -1; - S32 mPhysicsConvexSize = -1; - - S32 mPhysicsMeshOffset = -1; - S32 mPhysicsMeshSize = -1; - - S32 mLodOffset[4] = { -1 }; - S32 mLodSize[4] = { -1 }; - - bool m404 = false; -}; - -class LLMeshRepoThread : public LLThread -{ -public: - - volatile static S32 sActiveHeaderRequests; - volatile static S32 sActiveLODRequests; - static U32 sMaxConcurrentRequests; - static S32 sRequestLowWater; - static S32 sRequestHighWater; - static S32 sRequestWaterLevel; // Stats-use only, may read outside of thread - - LLMutex* mMutex; - LLMutex* mHeaderMutex; - LLCondition* mSignal; - - //map of known mesh headers - typedef boost::unordered_map> mesh_header_map; // pair is header_size and data - mesh_header_map mMeshHeader; - - class HeaderRequest : public RequestStats - { - public: - const LLVolumeParams mMeshParams; - - HeaderRequest(const LLVolumeParams& mesh_params) - : RequestStats(), mMeshParams(mesh_params) - { - } - - bool operator<(const HeaderRequest& rhs) const - { - return mMeshParams < rhs.mMeshParams; - } - }; - - class LODRequest : public RequestStats - { - public: - LLVolumeParams mMeshParams; - S32 mLOD; - F32 mScore; - - LODRequest(const LLVolumeParams& mesh_params, S32 lod) - : RequestStats(), mMeshParams(mesh_params), mLOD(lod), mScore(0.f) - { - } - }; - - struct CompareScoreGreater - { - bool operator()(const LODRequest& lhs, const LODRequest& rhs) - { - return lhs.mScore > rhs.mScore; // greatest = first - } - }; - - class UUIDBasedRequest : public RequestStats - { - public: - LLUUID mId; - - UUIDBasedRequest(const LLUUID& id) - : RequestStats(), mId(id) - { - } - - bool operator<(const UUIDBasedRequest& rhs) const - { - return mId < rhs.mId; - } - }; - - class LoadedMesh - { - public: - LLPointer mVolume; - LLVolumeParams mMeshParams; - S32 mLOD; - - LoadedMesh(LLVolume* volume, const LLVolumeParams& mesh_params, S32 lod) - : mVolume(volume), mMeshParams(mesh_params), mLOD(lod) - { - } - - }; - - //set of requested skin info - std::deque mSkinRequests; - - // list of completed skin info requests - std::deque mSkinInfoQ; - - // list of skin info requests that have failed or are unavailaibe - std::deque mSkinUnavailableQ; - - //set of requested decompositions - std::set mDecompositionRequests; - - //set of requested physics shapes - std::set mPhysicsShapeRequests; - - // list of completed Decomposition info requests - std::list mDecompositionQ; - - //queue of requested headers - std::queue mHeaderReqQ; - - //queue of requested LODs - std::queue mLODReqQ; - - //queue of unavailable LODs (either asset doesn't exist or asset doesn't have desired LOD) - std::deque mUnavailableQ; - - //queue of successfully loaded meshes - std::deque mLoadedQ; - - //map of pending header requests and currently desired LODs - typedef boost::unordered_map > pending_lod_map; - pending_lod_map mPendingLOD; - - // llcorehttp library interface objects. - LLCore::HttpStatus mHttpStatus; - LLCore::HttpRequest * mHttpRequest; - LLCore::HttpOptions::ptr_t mHttpOptions; - LLCore::HttpOptions::ptr_t mHttpLargeOptions; - LLCore::HttpHeaders::ptr_t mHttpHeaders; - LLCore::HttpRequest::policy_t mHttpPolicyClass; - LLCore::HttpRequest::policy_t mHttpLargePolicyClass; - - typedef std::set http_request_set; - http_request_set mHttpRequestSet; // Outstanding HTTP requests - - std::string mGetMeshCapability; - - LLMeshRepoThread(); - ~LLMeshRepoThread(); - - virtual void run(); - - void lockAndLoadMeshLOD(const LLVolumeParams& mesh_params, S32 lod); - void loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod); - - bool fetchMeshHeader(const LLVolumeParams& mesh_params, bool can_retry = true); - bool fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, bool can_retry = true); - EMeshProcessingResult headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size); - EMeshProcessingResult lodReceived(const LLVolumeParams& mesh_params, S32 lod, U8* data, S32 data_size); - bool skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 data_size); - bool decompositionReceived(const LLUUID& mesh_id, U8* data, S32 data_size); - EMeshProcessingResult physicsShapeReceived(const LLUUID& mesh_id, U8* data, S32 data_size); - bool hasPhysicsShapeInHeader(const LLUUID& mesh_id); - bool hasSkinInfoInHeader(const LLUUID& mesh_id); - bool hasHeader(const LLUUID& mesh_id); - - void notifyLoadedMeshes(); - S32 getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod); - - void loadMeshSkinInfo(const LLUUID& mesh_id); - void loadMeshDecomposition(const LLUUID& mesh_id); - void loadMeshPhysicsShape(const LLUUID& mesh_id); - - //send request for skin info, returns true if header info exists - // (should hold onto mesh_id and try again later if header info does not exist) - bool fetchMeshSkinInfo(const LLUUID& mesh_id, bool can_retry = true); - - //send request for decomposition, returns true if header info exists - // (should hold onto mesh_id and try again later if header info does not exist) - bool fetchMeshDecomposition(const LLUUID& mesh_id); - - //send request for PhysicsShape, returns true if header info exists - // (should hold onto mesh_id and try again later if header info does not exist) - bool fetchMeshPhysicsShape(const LLUUID& mesh_id); - - static void incActiveLODRequests(); - static void decActiveLODRequests(); - static void incActiveHeaderRequests(); - static void decActiveHeaderRequests(); - - // Set the caps strings and preferred version for constructing - // mesh fetch URLs. - // - // Mutex: must be holding mMutex when called - void setGetMeshCap(const std::string & get_mesh); - - // Mutex: acquires mMutex - void constructUrl(LLUUID mesh_id, std::string * url); - -private: - // Issue a GET request to a URL with 'Range' header using - // the correct policy class and other attributes. If an invalid - // handle is returned, the request failed and caller must retry - // or dispose of handler. - // - // Threads: Repo thread only - LLCore::HttpHandle getByteRange(const std::string & url, - size_t offset, size_t len, - const LLCore::HttpHandler::ptr_t &handler); -}; - - -// Class whose instances represent a single upload-type request for -// meshes: one fee query or one actual upload attempt. Yes, it creates -// a unique thread for that single request. As it is 1:1, it can also -// trivially serve as the HttpHandler object for request completion -// notifications. - -class LLMeshUploadThread : public LLThread, public LLCore::HttpHandler -{ -private: - S32 mMeshUploadTimeOut ; //maximum time in seconds to execute an uploading request. - -public: - class DecompRequest : public LLPhysicsDecomp::Request - { - public: - LLPointer mModel; - LLPointer mBaseModel; - - LLMeshUploadThread* mThread; - - DecompRequest(LLModel* mdl, LLModel* base_model, LLMeshUploadThread* thread); - - S32 statusCallback(const char* status, S32 p1, S32 p2) { return 1; } - void completed(); - }; - - LLPointer mFinalDecomp; - volatile bool mPhysicsComplete; - - typedef std::map, std::vector > hull_map; - hull_map mHullMap; - - typedef std::vector instance_list; - instance_list mInstanceList; - - typedef std::map, instance_list> instance_map; - instance_map mInstance; - - LLMutex* mMutex; - S32 mPendingUploads; - LLVector3 mOrigin; - bool mFinished; - bool mUploadTextures; - bool mUploadSkin; - bool mUploadJoints; - bool mLockScaleIfJointPosition; - volatile bool mDiscarded; - - LLHost mHost; - std::string mWholeModelFeeCapability; - std::string mWholeModelUploadURL; - - LLMeshUploadThread(instance_list& data, LLVector3& scale, bool upload_textures, - bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, - const std::string & upload_url, bool do_upload = true, - LLHandle fee_observer = (LLHandle()), - LLHandle upload_observer = (LLHandle())); - ~LLMeshUploadThread(); - - bool finished() const { return mFinished; } - virtual void run(); - void preStart(); - void discard() ; - bool isDiscarded() const; - - void generateHulls(); - - void doWholeModelUpload(); - void requestWholeModelFee(); - - void wholeModelToLLSD(LLSD& dest, bool include_textures); - - void decomposeMeshMatrix(LLMatrix4& transformation, - LLVector3& result_pos, - LLQuaternion& result_rot, - LLVector3& result_scale); - - void setFeeObserverHandle(LLHandle observer_handle) { mFeeObserverHandle = observer_handle; } - void setUploadObserverHandle(LLHandle observer_handle) { mUploadObserverHandle = observer_handle; } - - // Inherited from LLCore::HttpHandler - virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); - - LLViewerFetchedTexture* FindViewerTexture(const LLImportMaterial& material); - -private: - LLHandle mFeeObserverHandle; - LLHandle mUploadObserverHandle; - - bool mDoUpload; // if false only model data will be requested, otherwise the model will be uploaded - LLSD mModelData; - - // llcorehttp library interface objects. - LLCore::HttpStatus mHttpStatus; - LLCore::HttpRequest * mHttpRequest; - LLCore::HttpOptions::ptr_t mHttpOptions; - LLCore::HttpHeaders::ptr_t mHttpHeaders; - LLCore::HttpRequest::policy_t mHttpPolicyClass; -}; - -// Params related to streaming cost, render cost, and scene complexity tracking. -class LLMeshCostData -{ -public: - LLMeshCostData(); - - bool init(const LLMeshHeader& header); - - // Size for given LOD - S32 getSizeByLOD(S32 lod); - - // Sum of all LOD sizes. - S32 getSizeTotal(); - - // Estimated triangle counts for the given LOD. - F32 getEstTrisByLOD(S32 lod); - - // Estimated triangle counts for the largest LOD. Typically this - // is also the "high" LOD, but not necessarily. - F32 getEstTrisMax(); - - // Triangle count as computed by original streaming cost - // formula. Triangles in each LOD are weighted based on how - // frequently they will be seen. - // This was called "unscaled_value" in the original getStreamingCost() functions. - F32 getRadiusWeightedTris(F32 radius); - - // Triangle count used by triangle-based cost formula. Based on - // triangles in highest LOD plus potentially partial charges for - // lower LODs depending on complexity. - F32 getEstTrisForStreamingCost(); - - // Streaming cost. This should match the server-side calculation - // for the corresponding volume. - F32 getRadiusBasedStreamingCost(F32 radius); - - // New streaming cost formula, currently only used for animated objects. - F32 getTriangleBasedStreamingCost(); - -private: - // From the "size" field of the mesh header. LOD 0=lowest, 3=highest. - std::array mSizeByLOD; - - // Estimated triangle counts derived from the LOD sizes. LOD 0=lowest, 3=highest. - std::array mEstTrisByLOD; -}; - -class LLMeshRepository -{ -public: - - //metrics - static U32 sBytesReceived; - static U32 sMeshRequestCount; // Total request count, http or cached, all component types - static U32 sHTTPRequestCount; // Http GETs issued (not large) - static U32 sHTTPLargeRequestCount; // Http GETs issued for large requests - static U32 sHTTPRetryCount; // Total request retries whether successful or failed - static U32 sHTTPErrorCount; // Requests ending in error - static U32 sLODPending; - static U32 sLODProcessing; - static U32 sCacheBytesRead; - static U32 sCacheBytesWritten; - static U32 sCacheBytesHeaders; - static U32 sCacheBytesSkins; - static U32 sCacheBytesDecomps; - static U32 sCacheReads; - static U32 sCacheWrites; - static U32 sMaxLockHoldoffs; // Maximum sequential locking failures - - static LLDeadmanTimer sQuiescentTimer; // Time-to-complete-mesh-downloads after significant events - - // Estimated triangle count of the largest LOD - F32 getEstTrianglesMax(LLUUID mesh_id); - F32 getEstTrianglesStreamingCost(LLUUID mesh_id); - F32 getStreamingCostLegacy(LLUUID mesh_id, F32 radius, S32* bytes = NULL, S32* visible_bytes = NULL, S32 detail = -1, F32 *unscaled_value = NULL); - static F32 getStreamingCostLegacy(LLMeshHeader& header, F32 radius, S32* bytes = NULL, S32* visible_bytes = NULL, S32 detail = -1, F32 *unscaled_value = NULL); - bool getCostData(LLUUID mesh_id, LLMeshCostData& data); - bool getCostData(LLMeshHeader& header, LLMeshCostData& data); - - LLMeshRepository(); - - void init(); - void shutdown(); - S32 update(); - - void unregisterMesh(LLVOVolume* volume); - //mesh management functions - S32 loadMesh(LLVOVolume* volume, const LLVolumeParams& mesh_params, S32 detail = 0, S32 last_lod = -1); - - void notifyLoadedMeshes(); - void notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVolume* volume); - void notifyMeshUnavailable(const LLVolumeParams& mesh_params, S32 lod); - void notifySkinInfoReceived(LLMeshSkinInfo* info); - void notifySkinInfoUnavailable(const LLUUID& info); - void notifyDecompositionReceived(LLModel::Decomposition* info); - - S32 getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod); - static S32 getActualMeshLOD(LLMeshHeader& header, S32 lod); - const LLMeshSkinInfo* getSkinInfo(const LLUUID& mesh_id, LLVOVolume* requesting_obj = nullptr); - LLModel::Decomposition* getDecomposition(const LLUUID& mesh_id); - void fetchPhysicsShape(const LLUUID& mesh_id); - bool hasPhysicsShape(const LLUUID& mesh_id); - bool hasSkinInfo(const LLUUID& mesh_id); - bool hasHeader(const LLUUID& mesh_id); - - void buildHull(const LLVolumeParams& params, S32 detail); - void buildPhysicsMesh(LLModel::Decomposition& decomp); - - bool meshUploadEnabled(); - bool meshRezEnabled(); - - void uploadModel(std::vector& data, LLVector3& scale, bool upload_textures, - bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, - std::string upload_url, bool do_upload = true, - LLHandle fee_observer= (LLHandle()), - LLHandle upload_observer = (LLHandle())); - - S32 getMeshSize(const LLUUID& mesh_id, S32 lod); - - // Quiescent timer management, main thread only. - static void metricsStart(); - static void metricsStop(); - static void metricsProgress(unsigned int count); - static void metricsUpdate(); - - typedef boost::unordered_map > mesh_load_map; - mesh_load_map mLoadingMeshes[4]; - - typedef std::unordered_map> skin_map; - skin_map mSkinMap; - - typedef std::map decomposition_map; - decomposition_map mDecompositionMap; - - LLMutex* mMeshMutex; - - std::vector mPendingRequests; - - //list of mesh ids awaiting skin info - typedef boost::unordered_map > skin_load_map; - skin_load_map mLoadingSkins; - - //list of mesh ids that need to send skin info fetch requests - std::queue mPendingSkinRequests; - - //list of mesh ids awaiting decompositions - std::set mLoadingDecompositions; - - //list of mesh ids that need to send decomposition fetch requests - std::queue mPendingDecompositionRequests; - - //list of mesh ids awaiting physics shapes - std::set mLoadingPhysicsShapes; - - //list of mesh ids that need to send physics shape fetch requests - std::queue mPendingPhysicsShapeRequests; - - U32 mMeshThreadCount; - - LLMeshRepoThread* mThread; - std::vector mUploads; - std::vector mUploadWaitList; - - LLPhysicsDecomp* mDecompThread; - - LLFrameTimer mSkinInfoCullTimer; - - class inventory_data - { - public: - LLSD mPostData; - LLSD mResponse; - - inventory_data(const LLSD& data, const LLSD& content) - : mPostData(data), mResponse(content) - { - } - }; - - std::queue mInventoryQ; - - std::queue mUploadErrorQ; - - void uploadError(LLSD& args); - void updateInventory(inventory_data data); -}; - -extern LLMeshRepository gMeshRepo; - -const F32 ANIMATED_OBJECT_BASE_COST = 15.0f; -const F32 ANIMATED_OBJECT_COST_PER_KTRI = 1.5f; - -#endif - +/** + * @file llmeshrepository.h + * @brief Client-side repository of mesh assets. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010-2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_MESH_REPOSITORY_H +#define LL_MESH_REPOSITORY_H + +#include +#include "llassettype.h" +#include "llmodel.h" +#include "lluuid.h" +#include "llviewertexture.h" +#include "llvolume.h" +#include "lldeadmantimer.h" +#include "httpcommon.h" +#include "httprequest.h" +#include "httpoptions.h" +#include "httpheaders.h" +#include "httphandler.h" +#include "llthread.h" + +#define LLCONVEXDECOMPINTER_STATIC 1 + +#include "llconvexdecomposition.h" +#include "lluploadfloaterobservers.h" + +class LLVOVolume; +class LLMutex; +class LLCondition; +class LLMeshRepository; + +typedef enum e_mesh_processing_result_enum +{ + MESH_OK = 0, + MESH_NO_DATA = 1, + MESH_OUT_OF_MEMORY, + MESH_HTTP_REQUEST_FAILED, + MESH_PARSE_FAILURE, + MESH_INVALID, + MESH_UNKNOWN +} EMeshProcessingResult; + +class LLMeshUploadData +{ +public: + LLPointer mBaseModel; + LLPointer mModel[5]; + LLUUID mUUID; + U32 mRetries; + std::string mRSVP; + std::string mAssetData; + LLSD mPostData; + + LLMeshUploadData() + { + mRetries = 0; + } +}; + +class LLTextureUploadData +{ +public: + LLViewerFetchedTexture* mTexture; + LLUUID mUUID; + std::string mRSVP; + std::string mLabel; + U32 mRetries; + std::string mAssetData; + LLSD mPostData; + + LLTextureUploadData() + { + mRetries = 0; + } + + LLTextureUploadData(LLViewerFetchedTexture* texture, std::string& label) + : mTexture(texture), mLabel(label) + { + mRetries = 0; + } +}; + +class LLPhysicsDecomp : public LLThread +{ +public: + + typedef std::map decomp_params; + + class Request : public LLRefCount + { + public: + //input params + S32* mDecompID; + std::string mStage; + std::vector mPositions; + std::vector mIndices; + decomp_params mParams; + + //output state + std::string mStatusMessage; + std::vector mHullMesh; + LLModel::convex_hull_decomposition mHull; + + //status message callback, called from decomposition thread + virtual S32 statusCallback(const char* status, S32 p1, S32 p2) = 0; + + //completed callback, called from the main thread + virtual void completed() = 0; + + virtual void setStatusMessage(const std::string& msg); + + bool isValid() const {return mPositions.size() > 2 && mIndices.size() > 2 ;} + + protected: + //internal use + LLVector3 mBBox[2] ; + F32 mTriangleAreaThreshold ; + + void assignData(LLModel* mdl) ; + void updateTriangleAreaThreshold() ; + bool isValidTriangle(U16 idx1, U16 idx2, U16 idx3) ; + }; + + LLCondition* mSignal; + LLMutex* mMutex; + + bool mInited; + bool mQuitting; + bool mDone; + + LLPhysicsDecomp(); + ~LLPhysicsDecomp(); + + void shutdown(); + + void submitRequest(Request* request); + static S32 llcdCallback(const char*, S32, S32); + void cancel(); + + void setMeshData(LLCDMeshData& mesh, bool vertex_based); + void doDecomposition(); + void doDecompositionSingleHull(); + + virtual void run(); + + void completeCurrent(); + void notifyCompleted(); + + std::map mStageID; + + typedef std::queue > request_queue; + request_queue mRequestQ; + + LLPointer mCurRequest; + + std::queue > mCompletedQ; + +}; + +class RequestStats +{ +public: + RequestStats() : mRetries(0) {}; + + void updateTime(); + bool canRetry() const; + bool isDelayed() const; + U32 getRetries() { return mRetries; } + +private: + U32 mRetries; + LLFrameTimer mTimer; +}; + +class LLMeshHeader +{ +public: + + LLMeshHeader() {} + + explicit LLMeshHeader(const LLSD& header) + { + fromLLSD(header); + } + + void fromLLSD(const LLSD& header) + { + const char* lod[] = + { + "lowest_lod", + "low_lod", + "medium_lod", + "high_lod" + }; + + mVersion = header["version"].asInteger(); + + for (U32 i = 0; i < 4; ++i) + { + mLodOffset[i] = header[lod[i]]["offset"].asInteger(); + mLodSize[i] = header[lod[i]]["size"].asInteger(); + } + + mSkinOffset = header["skin"]["offset"].asInteger(); + mSkinSize = header["skin"]["size"].asInteger(); + + mPhysicsConvexOffset = header["physics_convex"]["offset"].asInteger(); + mPhysicsConvexSize = header["physics_convex"]["size"].asInteger(); + + mPhysicsMeshOffset = header["physics_mesh"]["offset"].asInteger(); + mPhysicsMeshSize = header["physics_mesh"]["size"].asInteger(); + + m404 = header.has("404"); + } + + S32 mVersion = -1; + S32 mSkinOffset = -1; + S32 mSkinSize = -1; + + S32 mPhysicsConvexOffset = -1; + S32 mPhysicsConvexSize = -1; + + S32 mPhysicsMeshOffset = -1; + S32 mPhysicsMeshSize = -1; + + S32 mLodOffset[4] = { -1 }; + S32 mLodSize[4] = { -1 }; + + bool m404 = false; +}; + +class LLMeshRepoThread : public LLThread +{ +public: + + volatile static S32 sActiveHeaderRequests; + volatile static S32 sActiveLODRequests; + static U32 sMaxConcurrentRequests; + static S32 sRequestLowWater; + static S32 sRequestHighWater; + static S32 sRequestWaterLevel; // Stats-use only, may read outside of thread + + LLMutex* mMutex; + LLMutex* mHeaderMutex; + LLCondition* mSignal; + + //map of known mesh headers + typedef boost::unordered_map> mesh_header_map; // pair is header_size and data + mesh_header_map mMeshHeader; + + class HeaderRequest : public RequestStats + { + public: + const LLVolumeParams mMeshParams; + + HeaderRequest(const LLVolumeParams& mesh_params) + : RequestStats(), mMeshParams(mesh_params) + { + } + + bool operator<(const HeaderRequest& rhs) const + { + return mMeshParams < rhs.mMeshParams; + } + }; + + class LODRequest : public RequestStats + { + public: + LLVolumeParams mMeshParams; + S32 mLOD; + F32 mScore; + + LODRequest(const LLVolumeParams& mesh_params, S32 lod) + : RequestStats(), mMeshParams(mesh_params), mLOD(lod), mScore(0.f) + { + } + }; + + struct CompareScoreGreater + { + bool operator()(const LODRequest& lhs, const LODRequest& rhs) + { + return lhs.mScore > rhs.mScore; // greatest = first + } + }; + + class UUIDBasedRequest : public RequestStats + { + public: + LLUUID mId; + + UUIDBasedRequest(const LLUUID& id) + : RequestStats(), mId(id) + { + } + + bool operator<(const UUIDBasedRequest& rhs) const + { + return mId < rhs.mId; + } + }; + + class LoadedMesh + { + public: + LLPointer mVolume; + LLVolumeParams mMeshParams; + S32 mLOD; + + LoadedMesh(LLVolume* volume, const LLVolumeParams& mesh_params, S32 lod) + : mVolume(volume), mMeshParams(mesh_params), mLOD(lod) + { + } + + }; + + //set of requested skin info + std::deque mSkinRequests; + + // list of completed skin info requests + std::deque mSkinInfoQ; + + // list of skin info requests that have failed or are unavailaibe + std::deque mSkinUnavailableQ; + + //set of requested decompositions + std::set mDecompositionRequests; + + //set of requested physics shapes + std::set mPhysicsShapeRequests; + + // list of completed Decomposition info requests + std::list mDecompositionQ; + + //queue of requested headers + std::queue mHeaderReqQ; + + //queue of requested LODs + std::queue mLODReqQ; + + //queue of unavailable LODs (either asset doesn't exist or asset doesn't have desired LOD) + std::deque mUnavailableQ; + + //queue of successfully loaded meshes + std::deque mLoadedQ; + + //map of pending header requests and currently desired LODs + typedef boost::unordered_map > pending_lod_map; + pending_lod_map mPendingLOD; + + // llcorehttp library interface objects. + LLCore::HttpStatus mHttpStatus; + LLCore::HttpRequest * mHttpRequest; + LLCore::HttpOptions::ptr_t mHttpOptions; + LLCore::HttpOptions::ptr_t mHttpLargeOptions; + LLCore::HttpHeaders::ptr_t mHttpHeaders; + LLCore::HttpRequest::policy_t mHttpPolicyClass; + LLCore::HttpRequest::policy_t mHttpLargePolicyClass; + + typedef std::set http_request_set; + http_request_set mHttpRequestSet; // Outstanding HTTP requests + + std::string mGetMeshCapability; + + LLMeshRepoThread(); + ~LLMeshRepoThread(); + + virtual void run(); + + void lockAndLoadMeshLOD(const LLVolumeParams& mesh_params, S32 lod); + void loadMeshLOD(const LLVolumeParams& mesh_params, S32 lod); + + bool fetchMeshHeader(const LLVolumeParams& mesh_params, bool can_retry = true); + bool fetchMeshLOD(const LLVolumeParams& mesh_params, S32 lod, bool can_retry = true); + EMeshProcessingResult headerReceived(const LLVolumeParams& mesh_params, U8* data, S32 data_size); + EMeshProcessingResult lodReceived(const LLVolumeParams& mesh_params, S32 lod, U8* data, S32 data_size); + bool skinInfoReceived(const LLUUID& mesh_id, U8* data, S32 data_size); + bool decompositionReceived(const LLUUID& mesh_id, U8* data, S32 data_size); + EMeshProcessingResult physicsShapeReceived(const LLUUID& mesh_id, U8* data, S32 data_size); + bool hasPhysicsShapeInHeader(const LLUUID& mesh_id); + bool hasSkinInfoInHeader(const LLUUID& mesh_id); + bool hasHeader(const LLUUID& mesh_id); + + void notifyLoadedMeshes(); + S32 getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod); + + void loadMeshSkinInfo(const LLUUID& mesh_id); + void loadMeshDecomposition(const LLUUID& mesh_id); + void loadMeshPhysicsShape(const LLUUID& mesh_id); + + //send request for skin info, returns true if header info exists + // (should hold onto mesh_id and try again later if header info does not exist) + bool fetchMeshSkinInfo(const LLUUID& mesh_id, bool can_retry = true); + + //send request for decomposition, returns true if header info exists + // (should hold onto mesh_id and try again later if header info does not exist) + bool fetchMeshDecomposition(const LLUUID& mesh_id); + + //send request for PhysicsShape, returns true if header info exists + // (should hold onto mesh_id and try again later if header info does not exist) + bool fetchMeshPhysicsShape(const LLUUID& mesh_id); + + static void incActiveLODRequests(); + static void decActiveLODRequests(); + static void incActiveHeaderRequests(); + static void decActiveHeaderRequests(); + + // Set the caps strings and preferred version for constructing + // mesh fetch URLs. + // + // Mutex: must be holding mMutex when called + void setGetMeshCap(const std::string & get_mesh); + + // Mutex: acquires mMutex + void constructUrl(LLUUID mesh_id, std::string * url); + +private: + // Issue a GET request to a URL with 'Range' header using + // the correct policy class and other attributes. If an invalid + // handle is returned, the request failed and caller must retry + // or dispose of handler. + // + // Threads: Repo thread only + LLCore::HttpHandle getByteRange(const std::string & url, + size_t offset, size_t len, + const LLCore::HttpHandler::ptr_t &handler); +}; + + +// Class whose instances represent a single upload-type request for +// meshes: one fee query or one actual upload attempt. Yes, it creates +// a unique thread for that single request. As it is 1:1, it can also +// trivially serve as the HttpHandler object for request completion +// notifications. + +class LLMeshUploadThread : public LLThread, public LLCore::HttpHandler +{ +private: + S32 mMeshUploadTimeOut ; //maximum time in seconds to execute an uploading request. + +public: + class DecompRequest : public LLPhysicsDecomp::Request + { + public: + LLPointer mModel; + LLPointer mBaseModel; + + LLMeshUploadThread* mThread; + + DecompRequest(LLModel* mdl, LLModel* base_model, LLMeshUploadThread* thread); + + S32 statusCallback(const char* status, S32 p1, S32 p2) { return 1; } + void completed(); + }; + + LLPointer mFinalDecomp; + volatile bool mPhysicsComplete; + + typedef std::map, std::vector > hull_map; + hull_map mHullMap; + + typedef std::vector instance_list; + instance_list mInstanceList; + + typedef std::map, instance_list> instance_map; + instance_map mInstance; + + LLMutex* mMutex; + S32 mPendingUploads; + LLVector3 mOrigin; + bool mFinished; + bool mUploadTextures; + bool mUploadSkin; + bool mUploadJoints; + bool mLockScaleIfJointPosition; + volatile bool mDiscarded; + + LLHost mHost; + std::string mWholeModelFeeCapability; + std::string mWholeModelUploadURL; + + LLMeshUploadThread(instance_list& data, LLVector3& scale, bool upload_textures, + bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, + const std::string & upload_url, bool do_upload = true, + LLHandle fee_observer = (LLHandle()), + LLHandle upload_observer = (LLHandle())); + ~LLMeshUploadThread(); + + bool finished() const { return mFinished; } + virtual void run(); + void preStart(); + void discard() ; + bool isDiscarded() const; + + void generateHulls(); + + void doWholeModelUpload(); + void requestWholeModelFee(); + + void wholeModelToLLSD(LLSD& dest, bool include_textures); + + void decomposeMeshMatrix(LLMatrix4& transformation, + LLVector3& result_pos, + LLQuaternion& result_rot, + LLVector3& result_scale); + + void setFeeObserverHandle(LLHandle observer_handle) { mFeeObserverHandle = observer_handle; } + void setUploadObserverHandle(LLHandle observer_handle) { mUploadObserverHandle = observer_handle; } + + // Inherited from LLCore::HttpHandler + virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); + + LLViewerFetchedTexture* FindViewerTexture(const LLImportMaterial& material); + +private: + LLHandle mFeeObserverHandle; + LLHandle mUploadObserverHandle; + + bool mDoUpload; // if false only model data will be requested, otherwise the model will be uploaded + LLSD mModelData; + + // llcorehttp library interface objects. + LLCore::HttpStatus mHttpStatus; + LLCore::HttpRequest * mHttpRequest; + LLCore::HttpOptions::ptr_t mHttpOptions; + LLCore::HttpHeaders::ptr_t mHttpHeaders; + LLCore::HttpRequest::policy_t mHttpPolicyClass; +}; + +// Params related to streaming cost, render cost, and scene complexity tracking. +class LLMeshCostData +{ +public: + LLMeshCostData(); + + bool init(const LLMeshHeader& header); + + // Size for given LOD + S32 getSizeByLOD(S32 lod); + + // Sum of all LOD sizes. + S32 getSizeTotal(); + + // Estimated triangle counts for the given LOD. + F32 getEstTrisByLOD(S32 lod); + + // Estimated triangle counts for the largest LOD. Typically this + // is also the "high" LOD, but not necessarily. + F32 getEstTrisMax(); + + // Triangle count as computed by original streaming cost + // formula. Triangles in each LOD are weighted based on how + // frequently they will be seen. + // This was called "unscaled_value" in the original getStreamingCost() functions. + F32 getRadiusWeightedTris(F32 radius); + + // Triangle count used by triangle-based cost formula. Based on + // triangles in highest LOD plus potentially partial charges for + // lower LODs depending on complexity. + F32 getEstTrisForStreamingCost(); + + // Streaming cost. This should match the server-side calculation + // for the corresponding volume. + F32 getRadiusBasedStreamingCost(F32 radius); + + // New streaming cost formula, currently only used for animated objects. + F32 getTriangleBasedStreamingCost(); + +private: + // From the "size" field of the mesh header. LOD 0=lowest, 3=highest. + std::array mSizeByLOD; + + // Estimated triangle counts derived from the LOD sizes. LOD 0=lowest, 3=highest. + std::array mEstTrisByLOD; +}; + +class LLMeshRepository +{ +public: + + //metrics + static U32 sBytesReceived; + static U32 sMeshRequestCount; // Total request count, http or cached, all component types + static U32 sHTTPRequestCount; // Http GETs issued (not large) + static U32 sHTTPLargeRequestCount; // Http GETs issued for large requests + static U32 sHTTPRetryCount; // Total request retries whether successful or failed + static U32 sHTTPErrorCount; // Requests ending in error + static U32 sLODPending; + static U32 sLODProcessing; + static U32 sCacheBytesRead; + static U32 sCacheBytesWritten; + static U32 sCacheBytesHeaders; + static U32 sCacheBytesSkins; + static U32 sCacheBytesDecomps; + static U32 sCacheReads; + static U32 sCacheWrites; + static U32 sMaxLockHoldoffs; // Maximum sequential locking failures + + static LLDeadmanTimer sQuiescentTimer; // Time-to-complete-mesh-downloads after significant events + + // Estimated triangle count of the largest LOD + F32 getEstTrianglesMax(LLUUID mesh_id); + F32 getEstTrianglesStreamingCost(LLUUID mesh_id); + F32 getStreamingCostLegacy(LLUUID mesh_id, F32 radius, S32* bytes = NULL, S32* visible_bytes = NULL, S32 detail = -1, F32 *unscaled_value = NULL); + static F32 getStreamingCostLegacy(LLMeshHeader& header, F32 radius, S32* bytes = NULL, S32* visible_bytes = NULL, S32 detail = -1, F32 *unscaled_value = NULL); + bool getCostData(LLUUID mesh_id, LLMeshCostData& data); + bool getCostData(LLMeshHeader& header, LLMeshCostData& data); + + LLMeshRepository(); + + void init(); + void shutdown(); + S32 update(); + + void unregisterMesh(LLVOVolume* volume); + //mesh management functions + S32 loadMesh(LLVOVolume* volume, const LLVolumeParams& mesh_params, S32 detail = 0, S32 last_lod = -1); + + void notifyLoadedMeshes(); + void notifyMeshLoaded(const LLVolumeParams& mesh_params, LLVolume* volume); + void notifyMeshUnavailable(const LLVolumeParams& mesh_params, S32 lod); + void notifySkinInfoReceived(LLMeshSkinInfo* info); + void notifySkinInfoUnavailable(const LLUUID& info); + void notifyDecompositionReceived(LLModel::Decomposition* info); + + S32 getActualMeshLOD(const LLVolumeParams& mesh_params, S32 lod); + static S32 getActualMeshLOD(LLMeshHeader& header, S32 lod); + const LLMeshSkinInfo* getSkinInfo(const LLUUID& mesh_id, LLVOVolume* requesting_obj = nullptr); + LLModel::Decomposition* getDecomposition(const LLUUID& mesh_id); + void fetchPhysicsShape(const LLUUID& mesh_id); + bool hasPhysicsShape(const LLUUID& mesh_id); + bool hasSkinInfo(const LLUUID& mesh_id); + bool hasHeader(const LLUUID& mesh_id); + + void buildHull(const LLVolumeParams& params, S32 detail); + void buildPhysicsMesh(LLModel::Decomposition& decomp); + + bool meshUploadEnabled(); + bool meshRezEnabled(); + + void uploadModel(std::vector& data, LLVector3& scale, bool upload_textures, + bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, + std::string upload_url, bool do_upload = true, + LLHandle fee_observer= (LLHandle()), + LLHandle upload_observer = (LLHandle())); + + S32 getMeshSize(const LLUUID& mesh_id, S32 lod); + + // Quiescent timer management, main thread only. + static void metricsStart(); + static void metricsStop(); + static void metricsProgress(unsigned int count); + static void metricsUpdate(); + + typedef boost::unordered_map > mesh_load_map; + mesh_load_map mLoadingMeshes[4]; + + typedef std::unordered_map> skin_map; + skin_map mSkinMap; + + typedef std::map decomposition_map; + decomposition_map mDecompositionMap; + + LLMutex* mMeshMutex; + + std::vector mPendingRequests; + + //list of mesh ids awaiting skin info + typedef boost::unordered_map > skin_load_map; + skin_load_map mLoadingSkins; + + //list of mesh ids that need to send skin info fetch requests + std::queue mPendingSkinRequests; + + //list of mesh ids awaiting decompositions + std::set mLoadingDecompositions; + + //list of mesh ids that need to send decomposition fetch requests + std::queue mPendingDecompositionRequests; + + //list of mesh ids awaiting physics shapes + std::set mLoadingPhysicsShapes; + + //list of mesh ids that need to send physics shape fetch requests + std::queue mPendingPhysicsShapeRequests; + + U32 mMeshThreadCount; + + LLMeshRepoThread* mThread; + std::vector mUploads; + std::vector mUploadWaitList; + + LLPhysicsDecomp* mDecompThread; + + LLFrameTimer mSkinInfoCullTimer; + + class inventory_data + { + public: + LLSD mPostData; + LLSD mResponse; + + inventory_data(const LLSD& data, const LLSD& content) + : mPostData(data), mResponse(content) + { + } + }; + + std::queue mInventoryQ; + + std::queue mUploadErrorQ; + + void uploadError(LLSD& args); + void updateInventory(inventory_data data); +}; + +extern LLMeshRepository gMeshRepo; + +const F32 ANIMATED_OBJECT_BASE_COST = 15.0f; +const F32 ANIMATED_OBJECT_COST_PER_KTRI = 1.5f; + +#endif + diff --git a/indra/newview/llmimetypes.cpp b/indra/newview/llmimetypes.cpp index 6c60d18c97..ee10b38a9d 100644 --- a/indra/newview/llmimetypes.cpp +++ b/indra/newview/llmimetypes.cpp @@ -1,302 +1,302 @@ -/** - * @file llmimetypes.cpp - * @brief Translates a MIME type like "video/quicktime" into a - * localizable user-friendly string like "QuickTime Movie" - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llmimetypes.h" -#include "lltrans.h" -#include "llxmlnode.h" - -#include "lluictrlfactory.h" - -LLMIMETypes::mime_info_map_t LLMIMETypes::sMap; -LLMIMETypes::mime_widget_set_map_t LLMIMETypes::sWidgetMap; - -std::string sDefaultLabel; - // Returned when we don't know what to do with the mime type -std::string sDefaultWidgetType; - // Returned when we don't know what widget set to use -std::string sDefaultImpl; - // Returned when we don't know what impl to use -std::string sXMLFilename; - // Squirrel away XML filename so we know how to reset -std::string DEFAULT_MIME_TYPE = "none/none"; - -///////////////////////////////////////////////////////////////////////////// - -// static -bool LLMIMETypes::parseMIMETypes(const std::string& xml_filename) -{ - LLXMLNodePtr root; - bool success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root); - if ( ! success || root.isNull() || ! root->hasName( "mimetypes" ) ) - { - LL_WARNS() << "Unable to read MIME type file: " - << xml_filename << LL_ENDL; - return false; - } - - for (LLXMLNode* node = root->getFirstChild(); - node != NULL; - node = node->getNextSibling()) - { - if (node->hasName("defaultlabel")) - { - sDefaultLabel = node->getTextContents(); - } - else if (node->hasName("defaultwidget")) - { - sDefaultWidgetType = node->getTextContents(); - } - else if (node->hasName("defaultimpl")) - { - sDefaultImpl = node->getTextContents(); - } - else if (node->hasName("mimetype") || node->hasName("scheme")) - { - std::string mime_type; - node->getAttributeString("name", mime_type); - LLMIMEInfo info; - for (LLXMLNode* child = node->getFirstChild(); - child != NULL; - child = child->getNextSibling()) - { - if (child->hasName("label")) - { - info.mLabel = child->getTextContents(); - } - else if (child->hasName("widgettype")) - { - info.mWidgetType = child->getTextContents(); - } - else if (child->hasName("impl")) - { - info.mImpl = child->getTextContents(); - } - } - sMap[mime_type] = info; - } - else if (node->hasName("widgetset")) - { - std::string set_name; - node->getAttributeString("name", set_name); - LLMIMEWidgetSet info; - for (LLXMLNode* child = node->getFirstChild(); - child != NULL; - child = child->getNextSibling()) - { - if (child->hasName("label")) - { - info.mLabel = child->getTextContents(); - } - if (child->hasName("icon")) - { - info.mIcon = child->getTextContents(); - } - if (child->hasName("default_type")) - { - info.mDefaultMimeType = child->getTextContents(); - } - if (child->hasName("tooltip")) - { - info.mToolTip = child->getTextContents(); - } - if (child->hasName("playtip")) - { - info.mPlayTip = child->getTextContents(); - } - if (child->hasName("allow_resize")) - { - bool allow_resize = false; - child->getBoolValue( 1, &allow_resize ); - info.mAllowResize = allow_resize; - } - if (child->hasName("allow_looping")) - { - bool allow_looping = false; - child->getBoolValue( 1, &allow_looping ); - info.mAllowLooping = allow_looping; - } - } - sWidgetMap[set_name] = info; - } - } - - sXMLFilename = xml_filename; - return true; -} - -// static -std::string LLMIMETypes::translate(const std::string& mime_type) -{ - mime_info_map_t::const_iterator it = sMap.find(mime_type); - if (it != sMap.end()) - { - return it->second.mLabel; - } - else - { - return sDefaultLabel; - } -} - -// static -std::string LLMIMETypes::widgetType(const std::string& mime_type) -{ - mime_info_map_t::const_iterator it = sMap.find(mime_type); - if (it != sMap.end()) - { - return it->second.mWidgetType; - } - else - { - return sDefaultWidgetType; - } -} - -// static -std::string LLMIMETypes::implType(const std::string& mime_type) -{ - mime_info_map_t::const_iterator it = sMap.find(mime_type); - if (it != sMap.end()) - { - return it->second.mImpl; - } - else - { - return sDefaultImpl; - } -} - -// static -std::string LLMIMETypes::findIcon(const std::string& mime_type) -{ - std::string icon = ""; - std::string widget_type = LLMIMETypes::widgetType(mime_type); - mime_widget_set_map_t::iterator it = sWidgetMap.find(widget_type); - if(it != sWidgetMap.end()) - { - icon = it->second.mIcon; - } - return icon; -} - -// static -std::string LLMIMETypes::findDefaultMimeType(const std::string& widget_type) -{ - std::string mime_type = getDefaultMimeType(); - mime_widget_set_map_t::iterator it = sWidgetMap.find(widget_type); - if(it != sWidgetMap.end()) - { - mime_type = it->second.mDefaultMimeType; - } - return mime_type; -} - -// static -const std::string& LLMIMETypes::getDefaultMimeType() -{ - return DEFAULT_MIME_TYPE; -} - -const std::string& LLMIMETypes::getDefaultMimeTypeTranslation() -{ - static std::string mime_type = LLTrans::getString("DefaultMimeType"); - return mime_type; -} - -// static -std::string LLMIMETypes::findToolTip(const std::string& mime_type) -{ - std::string tool_tip = ""; - std::string widget_type = LLMIMETypes::widgetType(mime_type); - mime_widget_set_map_t::iterator it = sWidgetMap.find(widget_type); - if(it != sWidgetMap.end()) - { - tool_tip = it->second.mToolTip; - } - return tool_tip; -} - -// static -std::string LLMIMETypes::findPlayTip(const std::string& mime_type) -{ - std::string play_tip = ""; - std::string widget_type = LLMIMETypes::widgetType(mime_type); - mime_widget_set_map_t::iterator it = sWidgetMap.find(widget_type); - if(it != sWidgetMap.end()) - { - play_tip = it->second.mPlayTip; - } - return play_tip; -} - -// static -bool LLMIMETypes::findAllowResize(const std::string& mime_type) -{ - bool allow_resize = false; - std::string widget_type = LLMIMETypes::widgetType(mime_type); - mime_widget_set_map_t::iterator it = sWidgetMap.find(widget_type); - if(it != sWidgetMap.end()) - { - allow_resize = it->second.mAllowResize; - } - return allow_resize; -} - -// static -bool LLMIMETypes::findAllowLooping(const std::string& mime_type) -{ - bool allow_looping = false; - std::string widget_type = LLMIMETypes::widgetType(mime_type); - mime_widget_set_map_t::iterator it = sWidgetMap.find(widget_type); - if(it != sWidgetMap.end()) - { - allow_looping = it->second.mAllowLooping; - } - return allow_looping; -} - -// static -bool LLMIMETypes::isTypeHandled(const std::string& mime_type) -{ - mime_info_map_t::const_iterator it = sMap.find(mime_type); - if (it != sMap.end()) - { - return true; - } - return false; -} - -// static -void LLMIMETypes::reload(void*) -{ - sMap.clear(); - sWidgetMap.clear(); - (void)LLMIMETypes::parseMIMETypes(sXMLFilename); -} - +/** + * @file llmimetypes.cpp + * @brief Translates a MIME type like "video/quicktime" into a + * localizable user-friendly string like "QuickTime Movie" + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llmimetypes.h" +#include "lltrans.h" +#include "llxmlnode.h" + +#include "lluictrlfactory.h" + +LLMIMETypes::mime_info_map_t LLMIMETypes::sMap; +LLMIMETypes::mime_widget_set_map_t LLMIMETypes::sWidgetMap; + +std::string sDefaultLabel; + // Returned when we don't know what to do with the mime type +std::string sDefaultWidgetType; + // Returned when we don't know what widget set to use +std::string sDefaultImpl; + // Returned when we don't know what impl to use +std::string sXMLFilename; + // Squirrel away XML filename so we know how to reset +std::string DEFAULT_MIME_TYPE = "none/none"; + +///////////////////////////////////////////////////////////////////////////// + +// static +bool LLMIMETypes::parseMIMETypes(const std::string& xml_filename) +{ + LLXMLNodePtr root; + bool success = LLUICtrlFactory::getLayeredXMLNode(xml_filename, root); + if ( ! success || root.isNull() || ! root->hasName( "mimetypes" ) ) + { + LL_WARNS() << "Unable to read MIME type file: " + << xml_filename << LL_ENDL; + return false; + } + + for (LLXMLNode* node = root->getFirstChild(); + node != NULL; + node = node->getNextSibling()) + { + if (node->hasName("defaultlabel")) + { + sDefaultLabel = node->getTextContents(); + } + else if (node->hasName("defaultwidget")) + { + sDefaultWidgetType = node->getTextContents(); + } + else if (node->hasName("defaultimpl")) + { + sDefaultImpl = node->getTextContents(); + } + else if (node->hasName("mimetype") || node->hasName("scheme")) + { + std::string mime_type; + node->getAttributeString("name", mime_type); + LLMIMEInfo info; + for (LLXMLNode* child = node->getFirstChild(); + child != NULL; + child = child->getNextSibling()) + { + if (child->hasName("label")) + { + info.mLabel = child->getTextContents(); + } + else if (child->hasName("widgettype")) + { + info.mWidgetType = child->getTextContents(); + } + else if (child->hasName("impl")) + { + info.mImpl = child->getTextContents(); + } + } + sMap[mime_type] = info; + } + else if (node->hasName("widgetset")) + { + std::string set_name; + node->getAttributeString("name", set_name); + LLMIMEWidgetSet info; + for (LLXMLNode* child = node->getFirstChild(); + child != NULL; + child = child->getNextSibling()) + { + if (child->hasName("label")) + { + info.mLabel = child->getTextContents(); + } + if (child->hasName("icon")) + { + info.mIcon = child->getTextContents(); + } + if (child->hasName("default_type")) + { + info.mDefaultMimeType = child->getTextContents(); + } + if (child->hasName("tooltip")) + { + info.mToolTip = child->getTextContents(); + } + if (child->hasName("playtip")) + { + info.mPlayTip = child->getTextContents(); + } + if (child->hasName("allow_resize")) + { + bool allow_resize = false; + child->getBoolValue( 1, &allow_resize ); + info.mAllowResize = allow_resize; + } + if (child->hasName("allow_looping")) + { + bool allow_looping = false; + child->getBoolValue( 1, &allow_looping ); + info.mAllowLooping = allow_looping; + } + } + sWidgetMap[set_name] = info; + } + } + + sXMLFilename = xml_filename; + return true; +} + +// static +std::string LLMIMETypes::translate(const std::string& mime_type) +{ + mime_info_map_t::const_iterator it = sMap.find(mime_type); + if (it != sMap.end()) + { + return it->second.mLabel; + } + else + { + return sDefaultLabel; + } +} + +// static +std::string LLMIMETypes::widgetType(const std::string& mime_type) +{ + mime_info_map_t::const_iterator it = sMap.find(mime_type); + if (it != sMap.end()) + { + return it->second.mWidgetType; + } + else + { + return sDefaultWidgetType; + } +} + +// static +std::string LLMIMETypes::implType(const std::string& mime_type) +{ + mime_info_map_t::const_iterator it = sMap.find(mime_type); + if (it != sMap.end()) + { + return it->second.mImpl; + } + else + { + return sDefaultImpl; + } +} + +// static +std::string LLMIMETypes::findIcon(const std::string& mime_type) +{ + std::string icon = ""; + std::string widget_type = LLMIMETypes::widgetType(mime_type); + mime_widget_set_map_t::iterator it = sWidgetMap.find(widget_type); + if(it != sWidgetMap.end()) + { + icon = it->second.mIcon; + } + return icon; +} + +// static +std::string LLMIMETypes::findDefaultMimeType(const std::string& widget_type) +{ + std::string mime_type = getDefaultMimeType(); + mime_widget_set_map_t::iterator it = sWidgetMap.find(widget_type); + if(it != sWidgetMap.end()) + { + mime_type = it->second.mDefaultMimeType; + } + return mime_type; +} + +// static +const std::string& LLMIMETypes::getDefaultMimeType() +{ + return DEFAULT_MIME_TYPE; +} + +const std::string& LLMIMETypes::getDefaultMimeTypeTranslation() +{ + static std::string mime_type = LLTrans::getString("DefaultMimeType"); + return mime_type; +} + +// static +std::string LLMIMETypes::findToolTip(const std::string& mime_type) +{ + std::string tool_tip = ""; + std::string widget_type = LLMIMETypes::widgetType(mime_type); + mime_widget_set_map_t::iterator it = sWidgetMap.find(widget_type); + if(it != sWidgetMap.end()) + { + tool_tip = it->second.mToolTip; + } + return tool_tip; +} + +// static +std::string LLMIMETypes::findPlayTip(const std::string& mime_type) +{ + std::string play_tip = ""; + std::string widget_type = LLMIMETypes::widgetType(mime_type); + mime_widget_set_map_t::iterator it = sWidgetMap.find(widget_type); + if(it != sWidgetMap.end()) + { + play_tip = it->second.mPlayTip; + } + return play_tip; +} + +// static +bool LLMIMETypes::findAllowResize(const std::string& mime_type) +{ + bool allow_resize = false; + std::string widget_type = LLMIMETypes::widgetType(mime_type); + mime_widget_set_map_t::iterator it = sWidgetMap.find(widget_type); + if(it != sWidgetMap.end()) + { + allow_resize = it->second.mAllowResize; + } + return allow_resize; +} + +// static +bool LLMIMETypes::findAllowLooping(const std::string& mime_type) +{ + bool allow_looping = false; + std::string widget_type = LLMIMETypes::widgetType(mime_type); + mime_widget_set_map_t::iterator it = sWidgetMap.find(widget_type); + if(it != sWidgetMap.end()) + { + allow_looping = it->second.mAllowLooping; + } + return allow_looping; +} + +// static +bool LLMIMETypes::isTypeHandled(const std::string& mime_type) +{ + mime_info_map_t::const_iterator it = sMap.find(mime_type); + if (it != sMap.end()) + { + return true; + } + return false; +} + +// static +void LLMIMETypes::reload(void*) +{ + sMap.clear(); + sWidgetMap.clear(); + (void)LLMIMETypes::parseMIMETypes(sXMLFilename); +} + diff --git a/indra/newview/llmimetypes.h b/indra/newview/llmimetypes.h index cb66c2fbd8..4c30eed98e 100644 --- a/indra/newview/llmimetypes.h +++ b/indra/newview/llmimetypes.h @@ -1,123 +1,123 @@ -/** - * @file llmimetypes.h - * @brief Translates a MIME type like "video/quicktime" into a - * localizable user-friendly string like "QuickTime Movie" - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLMIMETYPES_H -#define LLMIMETYPES_H - -#include -#include - -class LLMIMETypes -{ -public: - static bool parseMIMETypes(const std::string& xml_file_path); - // Loads the MIME string definition XML file, usually - // from the application skins directory - - static std::string translate(const std::string& mime_type); - // Returns "QuickTime Movie" from "video/quicktime" - - static std::string widgetType(const std::string& mime_type); - // Type of control widgets for this MIME type - // Returns "movie" from "video/quicktime" - - static std::string implType(const std::string& mime_type); - // Type of Impl to use for decoding media. - - static std::string findIcon(const std::string& mime_type); - // Icon from control widget type for this MIME type - - static std::string findToolTip(const std::string& mime_type); - // Tool tip from control widget type for this MIME type - - static std::string findPlayTip(const std::string& mime_type); - // Play button tool tip from control widget type for this MIME type - - static std::string findDefaultMimeType(const std::string& widget_type); - // Canonical mime type associated with this widget set - - static const std::string& getDefaultMimeType(); - - static const std::string& getDefaultMimeTypeTranslation(); - - static bool findAllowResize(const std::string& mime_type); - // accessor for flag to enable/disable media size edit fields - - static bool findAllowLooping(const std::string& mime_type); - // accessor for flag to enable/disable media looping checkbox - - static bool isTypeHandled(const std::string& mime_type); - // determines if the specific mime type is handled by the media system - - static void reload(void*); - // re-loads the MIME types file from the file path last passed into parseMIMETypes - -public: - struct LLMIMEInfo - { - std::string mLabel; - // friendly label like "QuickTime Movie" - - std::string mWidgetType; - // "web" means use web media UI widgets - - std::string mImpl; - // which impl to use with this mime type - }; - struct LLMIMEWidgetSet - { - std::string mLabel; - // friendly label like "QuickTime Movie" - - std::string mIcon; - // Name of icon asset to display in toolbar - - std::string mDefaultMimeType; - // Mime type string to use in absence of a specific one - - std::string mToolTip; - // custom tool tip for this mime type - - std::string mPlayTip; - // custom tool tip to display for Play button - - bool mAllowResize; - // enable/disable media size edit fields - - bool mAllowLooping; - // enable/disable media looping checkbox - }; - typedef std::map< std::string, LLMIMEInfo > mime_info_map_t; - typedef std::map< std::string, LLMIMEWidgetSet > mime_widget_set_map_t; - - // Public so users can iterate over it - static mime_info_map_t sMap; - static mime_widget_set_map_t sWidgetMap; -private: -}; - -#endif +/** + * @file llmimetypes.h + * @brief Translates a MIME type like "video/quicktime" into a + * localizable user-friendly string like "QuickTime Movie" + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLMIMETYPES_H +#define LLMIMETYPES_H + +#include +#include + +class LLMIMETypes +{ +public: + static bool parseMIMETypes(const std::string& xml_file_path); + // Loads the MIME string definition XML file, usually + // from the application skins directory + + static std::string translate(const std::string& mime_type); + // Returns "QuickTime Movie" from "video/quicktime" + + static std::string widgetType(const std::string& mime_type); + // Type of control widgets for this MIME type + // Returns "movie" from "video/quicktime" + + static std::string implType(const std::string& mime_type); + // Type of Impl to use for decoding media. + + static std::string findIcon(const std::string& mime_type); + // Icon from control widget type for this MIME type + + static std::string findToolTip(const std::string& mime_type); + // Tool tip from control widget type for this MIME type + + static std::string findPlayTip(const std::string& mime_type); + // Play button tool tip from control widget type for this MIME type + + static std::string findDefaultMimeType(const std::string& widget_type); + // Canonical mime type associated with this widget set + + static const std::string& getDefaultMimeType(); + + static const std::string& getDefaultMimeTypeTranslation(); + + static bool findAllowResize(const std::string& mime_type); + // accessor for flag to enable/disable media size edit fields + + static bool findAllowLooping(const std::string& mime_type); + // accessor for flag to enable/disable media looping checkbox + + static bool isTypeHandled(const std::string& mime_type); + // determines if the specific mime type is handled by the media system + + static void reload(void*); + // re-loads the MIME types file from the file path last passed into parseMIMETypes + +public: + struct LLMIMEInfo + { + std::string mLabel; + // friendly label like "QuickTime Movie" + + std::string mWidgetType; + // "web" means use web media UI widgets + + std::string mImpl; + // which impl to use with this mime type + }; + struct LLMIMEWidgetSet + { + std::string mLabel; + // friendly label like "QuickTime Movie" + + std::string mIcon; + // Name of icon asset to display in toolbar + + std::string mDefaultMimeType; + // Mime type string to use in absence of a specific one + + std::string mToolTip; + // custom tool tip for this mime type + + std::string mPlayTip; + // custom tool tip to display for Play button + + bool mAllowResize; + // enable/disable media size edit fields + + bool mAllowLooping; + // enable/disable media looping checkbox + }; + typedef std::map< std::string, LLMIMEInfo > mime_info_map_t; + typedef std::map< std::string, LLMIMEWidgetSet > mime_widget_set_map_t; + + // Public so users can iterate over it + static mime_info_map_t sMap; + static mime_widget_set_map_t sWidgetMap; +private: +}; + +#endif diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 93fbab996e..c73cffab5a 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -1,4007 +1,4007 @@ -/** - * @file llmodelpreview.cpp - * @brief LLModelPreview class implementation - * - * $LicenseInfo:firstyear=2020&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2020, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llmodelpreview.h" - -#include "llmodelloader.h" -#include "lldaeloader.h" -#include "llgltfloader.h" -#include "llfloatermodelpreview.h" - -#include "llagent.h" -#include "llanimationstates.h" -#include "llcallbacklist.h" -#include "lldatapacker.h" -#include "lldrawable.h" -#include "llface.h" -#include "lliconctrl.h" -#include "llmatrix4a.h" -#include "llmeshrepository.h" -#include "llmeshoptimizer.h" -#include "llrender.h" -#include "llsdutil_math.h" -#include "llskinningutil.h" -#include "llstring.h" -#include "llsdserialize.h" -#include "lltoolmgr.h" -#include "llui.h" -#include "llvector4a.h" -#include "llviewercamera.h" -#include "llviewercontrol.h" -#include "llviewerobjectlist.h" -#include "llviewernetwork.h" -#include "llviewershadermgr.h" -#include "llviewertexteditor.h" -#include "llviewertexturelist.h" -#include "llvoavatar.h" -#include "pipeline.h" - -// ui controls (from floater) -#include "llbutton.h" -#include "llcombobox.h" -#include "llspinctrl.h" -#include "lltabcontainer.h" -#include "lltextbox.h" - -#include - -#include - -bool LLModelPreview::sIgnoreLoadedCallback = false; - -// Extra configurability, to be exposed later in xml (LLModelPreview probably -// should become UI control at some point or get split into preview control) -static const LLColor4 PREVIEW_CANVAS_COL(0.169f, 0.169f, 0.169f, 1.f); -static const LLColor4 PREVIEW_EDGE_COL(0.4f, 0.4f, 0.4f, 1.0); -static const LLColor4 PREVIEW_BASE_COL(1.f, 1.f, 1.f, 1.f); -static const LLColor3 PREVIEW_BRIGHTNESS(0.9f, 0.9f, 0.9f); -static const F32 PREVIEW_EDGE_WIDTH(1.f); -static const LLColor4 PREVIEW_PSYH_EDGE_COL(0.f, 0.25f, 0.5f, 0.25f); -static const LLColor4 PREVIEW_PSYH_FILL_COL(0.f, 0.5f, 1.0f, 0.5f); -static const F32 PREVIEW_PSYH_EDGE_WIDTH(1.f); -static const LLColor4 PREVIEW_DEG_EDGE_COL(1.f, 0.f, 0.f, 1.f); -static const LLColor4 PREVIEW_DEG_FILL_COL(1.f, 0.f, 0.f, 0.5f); -static const F32 PREVIEW_DEG_EDGE_WIDTH(3.f); -static const F32 PREVIEW_DEG_POINT_SIZE(8.f); -static const F32 PREVIEW_ZOOM_LIMIT(10.f); -static const std::string DEFAULT_PHYSICS_MESH_NAME = "default_physics_shape"; - -const F32 SKIN_WEIGHT_CAMERA_DISTANCE = 16.f; - -LLViewerFetchedTexture* bindMaterialDiffuseTexture(const LLImportMaterial& material) -{ - LLViewerFetchedTexture *texture = LLViewerTextureManager::getFetchedTexture(material.getDiffuseMap(), FTT_DEFAULT, true, LLGLTexture::BOOST_PREVIEW); - - if (texture) - { - if (texture->getDiscardLevel() > -1) - { - gGL.getTexUnit(0)->bind(texture, true); - return texture; - } - } - - return NULL; -} - -std::string stripSuffix(std::string name) -{ - if ((name.find("_LOD") != -1) || (name.find("_PHYS") != -1)) - { - return name.substr(0, name.rfind('_')); - } - return name; -} - -std::string getLodSuffix(S32 lod) -{ - std::string suffix; - switch (lod) - { - case LLModel::LOD_IMPOSTOR: suffix = "_LOD0"; break; - case LLModel::LOD_LOW: suffix = "_LOD1"; break; - case LLModel::LOD_MEDIUM: suffix = "_LOD2"; break; - case LLModel::LOD_PHYSICS: suffix = "_PHYS"; break; - case LLModel::LOD_HIGH: break; - } - return suffix; -} - -void FindModel(LLModelLoader::scene& scene, const std::string& name_to_match, LLModel*& baseModelOut, LLMatrix4& matOut) -{ - LLModelLoader::scene::iterator base_iter = scene.begin(); - bool found = false; - while (!found && (base_iter != scene.end())) - { - matOut = base_iter->first; - - LLModelLoader::model_instance_list::iterator base_instance_iter = base_iter->second.begin(); - while (!found && (base_instance_iter != base_iter->second.end())) - { - LLModelInstance& base_instance = *base_instance_iter++; - LLModel* base_model = base_instance.mModel; - - if (base_model && (base_model->mLabel == name_to_match)) - { - baseModelOut = base_model; - return; - } - } - base_iter++; - } -} - -//----------------------------------------------------------------------------- -// LLModelPreview -//----------------------------------------------------------------------------- - -LLModelPreview::LLModelPreview(S32 width, S32 height, LLFloater* fmp) - : LLViewerDynamicTexture(width, height, 3, ORDER_MIDDLE, false), LLMutex() - , mLodsQuery() - , mLodsWithParsingError() - , mPelvisZOffset(0.0f) - , mLegacyRigFlags(U32_MAX) - , mRigValidJointUpload(false) - , mPhysicsSearchLOD(LLModel::LOD_PHYSICS) - , mResetJoints(false) - , mModelNoErrors(true) - , mLastJointUpdate(false) - , mFirstSkinUpdate(true) - , mHasDegenerate(false) - , mImporterDebug(LLCachedControl(gSavedSettings, "ImporterDebug", false)) -{ - mNeedsUpdate = true; - mCameraDistance = 0.f; - mCameraYaw = 0.f; - mCameraPitch = 0.f; - mCameraZoom = 1.f; - mTextureName = 0; - mPreviewLOD = 0; - mModelLoader = NULL; - mMaxTriangleLimit = 0; - mDirty = false; - mGenLOD = false; - mLoading = false; - mLookUpLodFiles = false; - mLoadState = LLModelLoader::STARTING; - mGroup = 0; - mLODFrozen = false; - - for (U32 i = 0; i < LLModel::NUM_LODS; ++i) - { - mRequestedTriangleCount[i] = 0; - mRequestedCreaseAngle[i] = -1.f; - mRequestedLoDMode[i] = 0; - mRequestedErrorThreshold[i] = 0.f; - } - - mViewOption["show_textures"] = false; - - mFMP = fmp; - - mHasPivot = false; - mModelPivot = LLVector3(0.0f, 0.0f, 0.0f); - - createPreviewAvatar(); -} - -LLModelPreview::~LLModelPreview() -{ - if (mModelLoader) - { - mModelLoader->shutdown(); - } - - if (mPreviewAvatar) - { - mPreviewAvatar->markDead(); - mPreviewAvatar = NULL; - } - - mUploadData.clear(); - mTextureSet.clear(); - - for (U32 i = 0; i < LLModel::NUM_LODS; i++) - { - clearModel(i); - } - mBaseModel.clear(); - mBaseScene.clear(); -} - -void LLModelPreview::updateDimentionsAndOffsets() -{ - assert_main_thread(); - - rebuildUploadData(); - - std::set accounted; - - mPelvisZOffset = mFMP ? mFMP->childGetValue("pelvis_offset").asReal() : 3.0f; - - if (mFMP && mFMP->childGetValue("upload_joints").asBoolean()) - { - // FIXME if preview avatar ever gets reused, this fake mesh ID stuff will fail. - // see also call to addAttachmentPosOverride. - LLUUID fake_mesh_id; - fake_mesh_id.generate(); - getPreviewAvatar()->addPelvisFixup(mPelvisZOffset, fake_mesh_id); - } - - for (U32 i = 0; i < mUploadData.size(); ++i) - { - LLModelInstance& instance = mUploadData[i]; - - if (accounted.find(instance.mModel) == accounted.end()) - { - accounted.insert(instance.mModel); - - // update instance skin info for each lods pelvisZoffset - for (int j = 0; jmSkinInfo.mPelvisOffset = mPelvisZOffset; - } - } - } - } - - F32 scale = mFMP ? mFMP->childGetValue("import_scale").asReal()*2.f : 2.f; - - mDetailsSignal((F32)(mPreviewScale[0] * scale), (F32)(mPreviewScale[1] * scale), (F32)(mPreviewScale[2] * scale)); - - updateStatusMessages(); -} - -void LLModelPreview::rebuildUploadData() -{ - assert_main_thread(); - - mDefaultPhysicsShapeP = NULL; - mUploadData.clear(); - mTextureSet.clear(); - - //fill uploaddata instance vectors from scene data - - std::string requested_name = mFMP->getChild("description_form")->getValue().asString(); - - LLSpinCtrl* scale_spinner = mFMP->getChild("import_scale"); - - F32 scale = scale_spinner->getValue().asReal(); - - LLMatrix4 scale_mat; - scale_mat.initScale(LLVector3(scale, scale, scale)); - - F32 max_scale = 0.f; - - bool legacyMatching = gSavedSettings.getBOOL("ImporterLegacyMatching"); - U32 load_state = 0; - - for (LLModelLoader::scene::iterator iter = mBaseScene.begin(); iter != mBaseScene.end(); ++iter) - { //for each transform in scene - LLMatrix4 mat = iter->first; - - // compute position - LLVector3 position = LLVector3(0, 0, 0) * mat; - - // compute scale - LLVector3 x_transformed = LLVector3(1, 0, 0) * mat - position; - LLVector3 y_transformed = LLVector3(0, 1, 0) * mat - position; - LLVector3 z_transformed = LLVector3(0, 0, 1) * mat - position; - F32 x_length = x_transformed.normalize(); - F32 y_length = y_transformed.normalize(); - F32 z_length = z_transformed.normalize(); - - max_scale = llmax(llmax(llmax(max_scale, x_length), y_length), z_length); - - mat *= scale_mat; - - for (LLModelLoader::model_instance_list::iterator model_iter = iter->second.begin(); model_iter != iter->second.end();) - { // for each instance with said transform applied - LLModelInstance instance = *model_iter++; - - LLModel* base_model = instance.mModel; - - if (base_model && !requested_name.empty()) - { - base_model->mRequestedLabel = requested_name; - } - - for (int i = LLModel::NUM_LODS - 1; i >= LLModel::LOD_IMPOSTOR; i--) - { - LLModel* lod_model = NULL; - if (!legacyMatching) - { - // Fill LOD slots by finding matching meshes by label with name extensions - // in the appropriate scene for each LOD. This fixes all kinds of issues - // where the indexed method below fails in spectacular fashion. - // If you don't take the time to name your LOD and PHYS meshes - // with the name of their corresponding mesh in the HIGH LOD, - // then the indexed method will be attempted below. - - LLMatrix4 transform; - - std::string name_to_match = instance.mLabel; - llassert(!name_to_match.empty()); - - int extensionLOD; - if (i != LLModel::LOD_PHYSICS || mModel[LLModel::LOD_PHYSICS].empty()) - { - extensionLOD = i; - } - else - { - //Physics can be inherited from other LODs or loaded, so we need to adjust what extension we are searching for - extensionLOD = mPhysicsSearchLOD; - } - - std::string toAdd = getLodSuffix(extensionLOD); - - if (name_to_match.find(toAdd) == -1) - { - name_to_match += toAdd; - } - - FindModel(mScene[i], name_to_match, lod_model, transform); - - if (!lod_model && i != LLModel::LOD_PHYSICS) - { - if (mImporterDebug) - { - std::ostringstream out; - out << "Search of" << name_to_match; - out << " in LOD" << i; - out << " list failed. Searching for alternative among LOD lists."; - LL_INFOS() << out.str() << LL_ENDL; - LLFloaterModelPreview::addStringToLog(out, false); - } - - int searchLOD = (i > LLModel::LOD_HIGH) ? LLModel::LOD_HIGH : i; - while ((searchLOD <= LLModel::LOD_HIGH) && !lod_model) - { - std::string name_to_match = instance.mLabel; - llassert(!name_to_match.empty()); - - std::string toAdd = getLodSuffix(searchLOD); - - if (name_to_match.find(toAdd) == -1) - { - name_to_match += toAdd; - } - - // See if we can find an appropriately named model in LOD 'searchLOD' - // - FindModel(mScene[searchLOD], name_to_match, lod_model, transform); - searchLOD++; - } - } - } - else - { - // Use old method of index-based association - U32 idx = 0; - for (idx = 0; idx < mBaseModel.size(); ++idx) - { - // find reference instance for this model - if (mBaseModel[idx] == base_model) - { - if (mImporterDebug) - { - std::ostringstream out; - out << "Attempting to use model index " << idx; - out << " for LOD" << i; - out << " of " << instance.mLabel; - LL_INFOS() << out.str() << LL_ENDL; - LLFloaterModelPreview::addStringToLog(out, false); - } - break; - } - } - - // If the model list for the current LOD includes that index... - // - if (mModel[i].size() > idx) - { - // Assign that index from the model list for our LOD as the LOD model for this instance - // - lod_model = mModel[i][idx]; - if (mImporterDebug) - { - std::ostringstream out; - out << "Indexed match of model index " << idx << " at LOD " << i << " to model named " << lod_model->mLabel; - LL_INFOS() << out.str() << LL_ENDL; - LLFloaterModelPreview::addStringToLog(out, false); - } - } - else if (mImporterDebug) - { - std::ostringstream out; - out << "List of models does not include index " << idx; - LL_INFOS() << out.str() << LL_ENDL; - LLFloaterModelPreview::addStringToLog(out, false); - } - } - if (mWarnOfUnmatchedPhyicsMeshes && !lod_model && (i == LLModel::LOD_PHYSICS)) - { - // Despite the various strategies above, if we don't now have a physics model, we're going to end up with decomposition. - // That's ok, but might not what they wanted. Use default_physics_shape if found. - std::ostringstream out; - out << "No physics model specified for " << instance.mLabel; - if (mDefaultPhysicsShapeP.notNull()) - { - out << " - using: " << DEFAULT_PHYSICS_MESH_NAME; - lod_model = mDefaultPhysicsShapeP; - } - LL_WARNS() << out.str() << LL_ENDL; - LLFloaterModelPreview::addStringToLog(out, mDefaultPhysicsShapeP.isNull()); // Flash log tab if no default. - } - - if (lod_model) - { - if (mImporterDebug) - { - std::ostringstream out; - if (i == LLModel::LOD_PHYSICS) - { - out << "Assigning collision for " << instance.mLabel << " to match " << lod_model->mLabel; - } - else - { - out << "Assigning LOD" << i << " for " << instance.mLabel << " to found match " << lod_model->mLabel; - } - LL_INFOS() << out.str() << LL_ENDL; - LLFloaterModelPreview::addStringToLog(out, false); - } - instance.mLOD[i] = lod_model; - } - else - { - if (i < LLModel::LOD_HIGH && !lodsReady()) - { - // assign a placeholder from previous LOD until lod generation is complete. - // Note: we might need to assign it regardless of conditions like named search does, to prevent crashes. - instance.mLOD[i] = instance.mLOD[i + 1]; - } - if (mImporterDebug) - { - std::ostringstream out; - out << "List of models does not include " << instance.mLabel; - LL_INFOS() << out.str() << LL_ENDL; - LLFloaterModelPreview::addStringToLog(out, false); - } - } - } - - LLModel* high_lod_model = instance.mLOD[LLModel::LOD_HIGH]; - if (!high_lod_model) - { - LLFloaterModelPreview::addStringToLog("Model " + instance.mLabel + " has no High Lod (LOD3).", true); - load_state = LLModelLoader::ERROR_MATERIALS; - mFMP->childDisable("calculate_btn"); - } - else - { - for (U32 i = 0; i < LLModel::NUM_LODS - 1; i++) - { - int refFaceCnt = 0; - int modelFaceCnt = 0; - llassert(instance.mLOD[i]); - if (instance.mLOD[i] && !instance.mLOD[i]->matchMaterialOrder(high_lod_model, refFaceCnt, modelFaceCnt)) - { - LLFloaterModelPreview::addStringToLog("Model " + instance.mLabel + " has mismatching materials between lods." , true); - load_state = LLModelLoader::ERROR_MATERIALS; - mFMP->childDisable("calculate_btn"); - } - } - LLFloaterModelPreview* fmp = (LLFloaterModelPreview*)mFMP; - bool upload_skinweights = fmp && fmp->childGetValue("upload_skin").asBoolean(); - if (upload_skinweights && high_lod_model->mSkinInfo.mJointNames.size() > 0) - { - LLQuaternion bind_rot = LLSkinningUtil::getUnscaledQuaternion(LLMatrix4(high_lod_model->mSkinInfo.mBindShapeMatrix)); - LLQuaternion identity; - if (!bind_rot.isEqualEps(identity, 0.01)) - { - // Bind shape matrix is not in standard X-forward orientation. - // Might be good idea to only show this once. It can be spammy. - std::ostringstream out; - out << "non-identity bind shape rot. mat is "; - out << high_lod_model->mSkinInfo.mBindShapeMatrix; - out << " bind_rot "; - out << bind_rot; - LL_WARNS() << out.str() << LL_ENDL; - - LLFloaterModelPreview::addStringToLog(out, getLoadState() != LLModelLoader::WARNING_BIND_SHAPE_ORIENTATION); - load_state = LLModelLoader::WARNING_BIND_SHAPE_ORIENTATION; - } - } - } - instance.mTransform = mat; - mUploadData.push_back(instance); - } - } - - for (U32 lod = 0; lod < LLModel::NUM_LODS - 1; lod++) - { - // Search for models that are not included into upload data - // If we found any, that means something we loaded is not a sub-model. - for (U32 model_ind = 0; model_ind < mModel[lod].size(); ++model_ind) - { - bool found_model = false; - for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter) - { - LLModelInstance& instance = *iter; - if (instance.mLOD[lod] == mModel[lod][model_ind]) - { - found_model = true; - break; - } - } - if (!found_model && mModel[lod][model_ind] && !mModel[lod][model_ind]->mSubmodelID) - { - if (mImporterDebug) - { - std::ostringstream out; - out << "Model " << mModel[lod][model_ind]->mLabel << " was not used - mismatching lod models."; - LL_INFOS() << out.str() << LL_ENDL; - LLFloaterModelPreview::addStringToLog(out, true); - } - load_state = LLModelLoader::ERROR_MATERIALS; - mFMP->childDisable("calculate_btn"); - } - } - } - - // Update state for notifications - if (load_state > 0) - { - // encountered issues - setLoadState(load_state); - } - else if (getLoadState() == LLModelLoader::ERROR_MATERIALS - || getLoadState() == LLModelLoader::WARNING_BIND_SHAPE_ORIENTATION) - { - // This is only valid for these two error types because they are - // only used inside rebuildUploadData() and updateStatusMessages() - // updateStatusMessages() is called after rebuildUploadData() - setLoadState(LLModelLoader::DONE); - } - - F32 max_import_scale = (DEFAULT_MAX_PRIM_SCALE - 0.1f) / max_scale; - - F32 max_axis = llmax(mPreviewScale.mV[0], mPreviewScale.mV[1]); - max_axis = llmax(max_axis, mPreviewScale.mV[2]); - max_axis *= 2.f; - - //clamp scale so that total imported model bounding box is smaller than 240m on a side - max_import_scale = llmin(max_import_scale, 240.f / max_axis); - - scale_spinner->setMaxValue(max_import_scale); - - if (max_import_scale < scale) - { - scale_spinner->setValue(max_import_scale); - } - -} - -void LLModelPreview::saveUploadData(bool save_skinweights, bool save_joint_positions, bool lock_scale_if_joint_position) -{ - if (!mLODFile[LLModel::LOD_HIGH].empty()) - { - std::string filename = mLODFile[LLModel::LOD_HIGH]; - std::string slm_filename; - - if (LLModelLoader::getSLMFilename(filename, slm_filename)) - { - saveUploadData(slm_filename, save_skinweights, save_joint_positions, lock_scale_if_joint_position); - } - } -} - -void LLModelPreview::saveUploadData(const std::string& filename, - bool save_skinweights, bool save_joint_positions, bool lock_scale_if_joint_position) -{ - - std::set > meshes; - std::map mesh_binary; - - LLModel::hull empty_hull; - - LLSD data; - - data["version"] = SLM_SUPPORTED_VERSION; - if (!mBaseModel.empty()) - { - data["name"] = mBaseModel[0]->getName(); - } - - S32 mesh_id = 0; - - //build list of unique models and initialize local id - for (U32 i = 0; i < mUploadData.size(); ++i) - { - LLModelInstance& instance = mUploadData[i]; - - if (meshes.find(instance.mModel) == meshes.end()) - { - instance.mModel->mLocalID = mesh_id++; - meshes.insert(instance.mModel); - - std::stringstream str; - LLModel::Decomposition& decomp = - instance.mLOD[LLModel::LOD_PHYSICS].notNull() ? - instance.mLOD[LLModel::LOD_PHYSICS]->mPhysics : - instance.mModel->mPhysics; - - LLModel::writeModel(str, - instance.mLOD[LLModel::LOD_PHYSICS], - instance.mLOD[LLModel::LOD_HIGH], - instance.mLOD[LLModel::LOD_MEDIUM], - instance.mLOD[LLModel::LOD_LOW], - instance.mLOD[LLModel::LOD_IMPOSTOR], - decomp, - save_skinweights, - save_joint_positions, - lock_scale_if_joint_position, - false, true, instance.mModel->mSubmodelID); - - data["mesh"][instance.mModel->mLocalID] = str.str(); - } - - data["instance"][i] = instance.asLLSD(); - } - - llofstream out(filename.c_str(), std::ios_base::out | std::ios_base::binary); - LLSDSerialize::toBinary(data, out); - out.flush(); - out.close(); -} - -void LLModelPreview::clearModel(S32 lod) -{ - if (lod < 0 || lod > LLModel::LOD_PHYSICS) - { - return; - } - - mVertexBuffer[lod].clear(); - mModel[lod].clear(); - mScene[lod].clear(); -} - -void LLModelPreview::getJointAliases(JointMap& joint_map) -{ - // Get all standard skeleton joints from the preview avatar. - LLVOAvatar *av = getPreviewAvatar(); - - //Joint names and aliases come from avatar_skeleton.xml - - joint_map = av->getJointAliases(); - - std::vector cv_names, attach_names; - av->getSortedJointNames(1, cv_names); - av->getSortedJointNames(2, attach_names); - for (std::vector::iterator it = cv_names.begin(); it != cv_names.end(); ++it) - { - joint_map[*it] = *it; - } - for (std::vector::iterator it = attach_names.begin(); it != attach_names.end(); ++it) - { - joint_map[*it] = *it; - } -} - -void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable_slm) -{ - assert_main_thread(); - - LLMutexLock lock(this); - - if (lod < LLModel::LOD_IMPOSTOR || lod > LLModel::NUM_LODS - 1) - { - std::ostringstream out; - out << "Invalid level of detail: "; - out << lod; - LL_WARNS() << out.str() << LL_ENDL; - LLFloaterModelPreview::addStringToLog(out, true); - assert(lod >= LLModel::LOD_IMPOSTOR && lod < LLModel::NUM_LODS); - return; - } - - // This triggers if you bring up the file picker and then hit CANCEL. - // Just use the previous model (if any) and ignore that you brought up - // the file picker. - - if (filename.empty()) - { - if (mBaseModel.empty()) - { - // this is the initial file picking. Close the whole floater - // if we don't have a base model to show for high LOD. - mFMP->closeFloater(false); - } - mLoading = false; - return; - } - - if (mModelLoader) - { - LL_WARNS() << "Incompleted model load operation pending." << LL_ENDL; - return; - } - - mLODFile[lod] = filename; - - std::map joint_alias_map; - getJointAliases(joint_alias_map); - - // three possible file extensions, .dae .gltf .glb - // check for .dae and if not then assume one of the .gl?? - std::string filename_lc(filename); - LLStringUtil::toLower(filename_lc); - if (std::string::npos != filename_lc.rfind(".dae")) - { - mModelLoader = new LLDAELoader( - filename, - lod, - &LLModelPreview::loadedCallback, - &LLModelPreview::lookupJointByName, - &LLModelPreview::loadTextures, - &LLModelPreview::stateChangedCallback, - this, - mJointTransformMap, - mJointsFromNode, - joint_alias_map, - LLSkinningUtil::getMaxJointCount(), - gSavedSettings.getU32("ImporterModelLimit"), - gSavedSettings.getBOOL("ImporterPreprocessDAE")); - } - else - { - mModelLoader = new LLGLTFLoader( - filename, - lod, - &LLModelPreview::loadedCallback, - &LLModelPreview::lookupJointByName, - &LLModelPreview::loadTextures, - &LLModelPreview::stateChangedCallback, - this, - mJointTransformMap, - mJointsFromNode, - joint_alias_map, - LLSkinningUtil::getMaxJointCount(), - gSavedSettings.getU32("ImporterModelLimit")); - } - - if (force_disable_slm) - { - mModelLoader->mTrySLM = false; - } - else - { - // For MAINT-6647, we have set force_disable_slm to true, - // which means this code path will never be taken. Trying to - // re-use SLM files has never worked properly; in particular, - // it tends to force the UI into strange checkbox options - // which cannot be altered. - - //only try to load from slm if viewer is configured to do so and this is the - //initial model load (not an LoD or physics shape) - mModelLoader->mTrySLM = gSavedSettings.getBOOL("MeshImportUseSLM") && mUploadData.empty(); - } - mModelLoader->start(); - - mFMP->childSetTextArg("status", "[STATUS]", mFMP->getString("status_reading_file")); - - setPreviewLOD(lod); - - if (getLoadState() >= LLModelLoader::ERROR_PARSING) - { - mFMP->childDisable("ok_btn"); - mFMP->childDisable("calculate_btn"); - } - - if (lod == mPreviewLOD) - { - mFMP->childSetValue("lod_file_" + lod_name[lod], mLODFile[lod]); - } - else if (lod == LLModel::LOD_PHYSICS) - { - mFMP->childSetValue("physics_file", mLODFile[lod]); - } - - mFMP->openFloater(); -} - -void LLModelPreview::setPhysicsFromLOD(S32 lod) -{ - assert_main_thread(); - - if (lod >= 0 && lod <= 3) - { - mPhysicsSearchLOD = lod; - mModel[LLModel::LOD_PHYSICS] = mModel[lod]; - mScene[LLModel::LOD_PHYSICS] = mScene[lod]; - mLODFile[LLModel::LOD_PHYSICS].clear(); - mFMP->childSetValue("physics_file", mLODFile[LLModel::LOD_PHYSICS]); - mVertexBuffer[LLModel::LOD_PHYSICS].clear(); - rebuildUploadData(); - refresh(); - updateStatusMessages(); - } -} - -void LLModelPreview::clearIncompatible(S32 lod) -{ - //Don't discard models if specified model is the physic rep - if (lod == LLModel::LOD_PHYSICS) - { - return; - } - - // at this point we don't care about sub-models, - // different amount of sub-models means face count mismatch, not incompatibility - U32 lod_size = countRootModels(mModel[lod]); - bool replaced_base_model = (lod == LLModel::LOD_HIGH); - for (U32 i = 0; i <= LLModel::LOD_HIGH; i++) - { - // Clear out any entries that aren't compatible with this model - if (i != lod) - { - if (countRootModels(mModel[i]) != lod_size) - { - mModel[i].clear(); - mScene[i].clear(); - mVertexBuffer[i].clear(); - - if (i == LLModel::LOD_HIGH) - { - mBaseModel = mModel[lod]; - mBaseScene = mScene[lod]; - mVertexBuffer[5].clear(); - replaced_base_model = true; - } - } - } - } - - if (replaced_base_model && !mGenLOD) - { - // In case base was replaced, we might need to restart generation - - // Check if already started - bool subscribe_for_generation = mLodsQuery.empty(); - - // Remove previously scheduled work - mLodsQuery.clear(); - - LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance; - if (!fmp) - return; - - // Schedule new work - for (S32 i = LLModel::LOD_HIGH; i >= 0; --i) - { - if (mModel[i].empty()) - { - // Base model was replaced, regenerate this lod if applicable - LLComboBox* lod_combo = mFMP->findChild("lod_source_" + lod_name[i]); - if (!lod_combo) return; - - S32 lod_mode = lod_combo->getCurrentIndex(); - if (lod_mode != LOD_FROM_FILE) - { - mLodsQuery.push_back(i); - } - } - } - - // Subscribe if we have pending work and not subscribed yet - if (!mLodsQuery.empty() && subscribe_for_generation) - { - doOnIdleRepeating(lodQueryCallback); - } - } -} - -void LLModelPreview::loadModelCallback(S32 loaded_lod) -{ - assert_main_thread(); - - LLMutexLock lock(this); - if (!mModelLoader) - { - mLoading = false; - return; - } - if (getLoadState() >= LLModelLoader::ERROR_PARSING) - { - mLoading = false; - mModelLoader = NULL; - mLodsWithParsingError.push_back(loaded_lod); - return; - } - - mLodsWithParsingError.erase(std::remove(mLodsWithParsingError.begin(), mLodsWithParsingError.end(), loaded_lod), mLodsWithParsingError.end()); - if (mLodsWithParsingError.empty()) - { - mFMP->childEnable("calculate_btn"); - } - - // Copy determinations about rig so UI will reflect them - // - setRigValidForJointPositionUpload(mModelLoader->isRigValidForJointPositionUpload()); - setLegacyRigFlags(mModelLoader->getLegacyRigFlags()); - - mModelLoader->loadTextures(); - - if (loaded_lod == -1) - { //populate all LoDs from model loader scene - mBaseModel.clear(); - mBaseScene.clear(); - - bool skin_weights = false; - bool joint_overrides = false; - bool lock_scale_if_joint_position = false; - - for (S32 lod = 0; lod < LLModel::NUM_LODS; ++lod) - { //for each LoD - - //clear scene and model info - mScene[lod].clear(); - mModel[lod].clear(); - mVertexBuffer[lod].clear(); - - if (mModelLoader->mScene.begin()->second[0].mLOD[lod].notNull()) - { //if this LoD exists in the loaded scene - - //copy scene to current LoD - mScene[lod] = mModelLoader->mScene; - - //touch up copied scene to look like current LoD - for (LLModelLoader::scene::iterator iter = mScene[lod].begin(); iter != mScene[lod].end(); ++iter) - { - LLModelLoader::model_instance_list& list = iter->second; - - for (LLModelLoader::model_instance_list::iterator list_iter = list.begin(); list_iter != list.end(); ++list_iter) - { - //override displayed model with current LoD - list_iter->mModel = list_iter->mLOD[lod]; - - if (!list_iter->mModel) - { - continue; - } - - //add current model to current LoD's model list (LLModel::mLocalID makes a good vector index) - S32 idx = list_iter->mModel->mLocalID; - - if (mModel[lod].size() <= idx) - { //stretch model list to fit model at given index - mModel[lod].resize(idx + 1); - } - - mModel[lod][idx] = list_iter->mModel; - if (!list_iter->mModel->mSkinWeights.empty()) - { - skin_weights = true; - - if (!list_iter->mModel->mSkinInfo.mAlternateBindMatrix.empty()) - { - joint_overrides = true; - } - if (list_iter->mModel->mSkinInfo.mLockScaleIfJointPosition) - { - lock_scale_if_joint_position = true; - } - } - } - } - } - } - - if (mFMP) - { - LLFloaterModelPreview* fmp = (LLFloaterModelPreview*)mFMP; - - if (skin_weights) - { //enable uploading/previewing of skin weights if present in .slm file - fmp->enableViewOption("show_skin_weight"); - mViewOption["show_skin_weight"] = true; - fmp->childSetValue("upload_skin", true); - } - - if (joint_overrides) - { - fmp->enableViewOption("show_joint_overrides"); - mViewOption["show_joint_overrides"] = true; - fmp->enableViewOption("show_joint_positions"); - mViewOption["show_joint_positions"] = true; - fmp->childSetValue("upload_joints", true); - } - else - { - fmp->clearAvatarTab(); - } - - if (lock_scale_if_joint_position) - { - fmp->enableViewOption("lock_scale_if_joint_position"); - mViewOption["lock_scale_if_joint_position"] = true; - fmp->childSetValue("lock_scale_if_joint_position", true); - } - } - - //copy high lod to base scene for LoD generation - mBaseScene = mScene[LLModel::LOD_HIGH]; - mBaseModel = mModel[LLModel::LOD_HIGH]; - - mDirty = true; - resetPreviewTarget(); - } - else - { //only replace given LoD - mModel[loaded_lod] = mModelLoader->mModelList; - mScene[loaded_lod] = mModelLoader->mScene; - - // Duplicate the model if it is an internal bounding box model - if (loaded_lod == LLModel::LOD_PHYSICS && - mBaseModel.size() > 1 && // This makes sense for multiple models only - mModelLoader->mModelList.size() == 1 && // Just on the off-chance - mModelLoader->mScene.size() == 1 && // Just on the off-chance - std::filesystem::path(mModelLoader->mFilename).filename() == "cube.dae") - { - // Create a copy of the just loaded model for each model in mBaseModel - const LLModel* origin = mModelLoader->mModelList.front(); - const LLModelInstance& mi = mModelLoader->mScene.begin()->second.front(); - for (U32 i = 1; i < mBaseModel.size(); ++i) - { - LLPointer copy(new LLModel(origin->getParams(), origin->getDetail())); - copy->mLabel = origin->mLabel; - copy->copyVolumeFaces(origin); - copy->mPosition = origin->mPosition; - copy->mMaterialList = origin->mMaterialList; - mModel[loaded_lod].push_back(copy); - mScene[loaded_lod][mi.mTransform].push_back(LLModelInstance(copy, copy->mLabel, mi.mTransform, mi.mMaterial)); - } - } - - mVertexBuffer[loaded_lod].clear(); - - setPreviewLOD(loaded_lod); - - if (loaded_lod == LLModel::LOD_HIGH) - { //save a copy of the highest LOD for automatic LOD manipulation - if (mBaseModel.empty()) - { //first time we've loaded a model, auto-gen LoD - mGenLOD = true; - } - - mBaseModel = mModel[loaded_lod]; - - mBaseScene = mScene[loaded_lod]; - mVertexBuffer[5].clear(); - } - else - { - if (loaded_lod == LLModel::LOD_PHYSICS) - { // Explicitly loading physics. See if there is a default mesh. - LLMatrix4 ignored_transform; // Each mesh that uses this will supply their own. - LLModel* out_model = nullptr; - FindModel(mScene[loaded_lod], DEFAULT_PHYSICS_MESH_NAME + getLodSuffix(loaded_lod), out_model, ignored_transform); - mDefaultPhysicsShapeP = out_model; - mWarnOfUnmatchedPhyicsMeshes = true; - } - bool legacyMatching = gSavedSettings.getBOOL("ImporterLegacyMatching"); - if (!legacyMatching) - { - if (!mBaseModel.empty()) - { - bool name_based = false; - bool has_submodels = false; - for (U32 idx = 0; idx < mBaseModel.size(); ++idx) - { - if (mBaseModel[idx]->mSubmodelID) - { // don't do index-based renaming when the base model has submodels - has_submodels = true; - if (mImporterDebug) - { - std::ostringstream out; - out << "High LOD has submodels"; - LL_INFOS() << out.str() << LL_ENDL; - LLFloaterModelPreview::addStringToLog(out, false); - } - break; - } - } - - for (U32 idx = 0; idx < mModel[loaded_lod].size(); ++idx) - { - std::string loaded_name = stripSuffix(mModel[loaded_lod][idx]->mLabel); - - LLModel* found_model = NULL; - LLMatrix4 transform; - FindModel(mBaseScene, loaded_name, found_model, transform); - if (found_model) - { // don't rename correctly named models (even if they are placed in a wrong order) - name_based = true; - } - - if (mModel[loaded_lod][idx]->mSubmodelID) - { // don't rename the models when loaded LOD model has submodels - has_submodels = true; - } - } - - if (mImporterDebug) - { - std::ostringstream out; - out << "Loaded LOD " << loaded_lod << ": correct names" << (name_based ? "" : "NOT ") << "found; submodels " << (has_submodels ? "" : "NOT ") << "found"; - LL_INFOS() << out.str() << LL_ENDL; - LLFloaterModelPreview::addStringToLog(out, false); - } - - if (!name_based && !has_submodels) - { // replace the name of the model loaded for any non-HIGH LOD to match the others (MAINT-5601) - // this actually works like "ImporterLegacyMatching" for this particular LOD - for (U32 idx = 0; idx < mModel[loaded_lod].size() && idx < mBaseModel.size(); ++idx) - { - std::string name = mBaseModel[idx]->mLabel; - std::string loaded_name = stripSuffix(mModel[loaded_lod][idx]->mLabel); - - if (loaded_name != name) - { - name += getLodSuffix(loaded_lod); - - if (mImporterDebug) - { - std::ostringstream out; - out << "Loded model name " << mModel[loaded_lod][idx]->mLabel; - out << " for LOD " << loaded_lod; - out << " doesn't match the base model. Renaming to " << name; - LL_WARNS() << out.str() << LL_ENDL; - LLFloaterModelPreview::addStringToLog(out, false); - } - mModel[loaded_lod][idx]->mLabel = name; - // Rename the correspondent instance as well - [&]() - { - for (auto& p : mScene[loaded_lod]) - for (auto& i : p.second) - if (i.mModel == mModel[loaded_lod][idx]) - { - i.mLabel = name; - return; - } - }(); - } - } - } - } - } - } - - clearIncompatible(loaded_lod); - - mDirty = true; - - if (loaded_lod == LLModel::LOD_HIGH) - { - resetPreviewTarget(); - } - } - - mLoading = false; - if (mFMP) - { - if (!mBaseModel.empty()) - { - const std::string& model_name = mBaseModel[0]->getName(); - LLLineEditor* description_form = mFMP->getChild("description_form"); - if (description_form->getText().empty()) - { - description_form->setText(model_name); - } - // Add info to log that loading is complete (purpose: separator between loading and other logs) - LLSD args; - args["MODEL_NAME"] = model_name; // Teoretically shouldn't be empty, but might be better idea to add filename here - LLFloaterModelPreview::addStringToLog("ModelLoaded", args, false, loaded_lod); - } - } - refresh(); - - mModelLoadedSignal(); - - mModelLoader = NULL; -} - -void LLModelPreview::resetPreviewTarget() -{ - if (mModelLoader) - { - mPreviewTarget = (mModelLoader->mExtents[0] + mModelLoader->mExtents[1]) * 0.5f; - mPreviewScale = (mModelLoader->mExtents[1] - mModelLoader->mExtents[0]) * 0.5f; - } - - setPreviewTarget(mPreviewScale.magVec()*10.f); -} - -void LLModelPreview::generateNormals() -{ - assert_main_thread(); - - S32 which_lod = mPreviewLOD; - - if (which_lod > 4 || which_lod < 0 || - mModel[which_lod].empty()) - { - return; - } - - F32 angle_cutoff = mFMP->childGetValue("crease_angle").asReal(); - - mRequestedCreaseAngle[which_lod] = angle_cutoff; - - angle_cutoff *= DEG_TO_RAD; - - if (which_lod == 3 && !mBaseModel.empty()) - { - if (mBaseModelFacesCopy.empty()) - { - mBaseModelFacesCopy.reserve(mBaseModel.size()); - for (LLModelLoader::model_list::iterator it = mBaseModel.begin(), itE = mBaseModel.end(); it != itE; ++it) - { - v_LLVolumeFace_t faces; - (*it)->copyFacesTo(faces); - mBaseModelFacesCopy.push_back(faces); - } - } - - for (LLModelLoader::model_list::iterator it = mBaseModel.begin(), itE = mBaseModel.end(); it != itE; ++it) - { - (*it)->generateNormals(angle_cutoff); - } - - mVertexBuffer[5].clear(); - } - - bool perform_copy = mModelFacesCopy[which_lod].empty(); - if (perform_copy) { - mModelFacesCopy[which_lod].reserve(mModel[which_lod].size()); - } - - for (LLModelLoader::model_list::iterator it = mModel[which_lod].begin(), itE = mModel[which_lod].end(); it != itE; ++it) - { - if (perform_copy) - { - v_LLVolumeFace_t faces; - (*it)->copyFacesTo(faces); - mModelFacesCopy[which_lod].push_back(faces); - } - - (*it)->generateNormals(angle_cutoff); - } - - mVertexBuffer[which_lod].clear(); - refresh(); - updateStatusMessages(); -} - -void LLModelPreview::restoreNormals() -{ - S32 which_lod = mPreviewLOD; - - if (which_lod > 4 || which_lod < 0 || - mModel[which_lod].empty()) - { - return; - } - - if (!mBaseModelFacesCopy.empty()) - { - llassert(mBaseModelFacesCopy.size() == mBaseModel.size()); - - vv_LLVolumeFace_t::const_iterator itF = mBaseModelFacesCopy.begin(); - for (LLModelLoader::model_list::iterator it = mBaseModel.begin(), itE = mBaseModel.end(); it != itE; ++it, ++itF) - { - (*it)->copyFacesFrom((*itF)); - } - - mBaseModelFacesCopy.clear(); - } - - if (!mModelFacesCopy[which_lod].empty()) - { - vv_LLVolumeFace_t::const_iterator itF = mModelFacesCopy[which_lod].begin(); - for (LLModelLoader::model_list::iterator it = mModel[which_lod].begin(), itE = mModel[which_lod].end(); it != itE; ++it, ++itF) - { - (*it)->copyFacesFrom((*itF)); - } - - mModelFacesCopy[which_lod].clear(); - } - - mVertexBuffer[which_lod].clear(); - refresh(); - updateStatusMessages(); -} - -// Runs per object, but likely it is a better way to run per model+submodels -// returns a ratio of base model indices to resulting indices -// returns -1 in case of failure -F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *target_model, F32 indices_decimator, F32 error_threshold, eSimplificationMode simplification_mode) -{ - // I. Weld faces together - // Figure out buffer size - S32 size_indices = 0; - S32 size_vertices = 0; - - for (U32 face_idx = 0; face_idx < base_model->getNumVolumeFaces(); ++face_idx) - { - const LLVolumeFace &face = base_model->getVolumeFace(face_idx); - size_indices += face.mNumIndices; - size_vertices += face.mNumVertices; - } - - if (size_indices < 3) - { - return -1; - } - - // Allocate buffers, note that we are using U32 buffer instead of U16 - U32* combined_indices = (U32*)ll_aligned_malloc_32(size_indices * sizeof(U32)); - U32* output_indices = (U32*)ll_aligned_malloc_32(size_indices * sizeof(U32)); - - // extra space for normals and text coords - S32 tc_bytes_size = ((size_vertices * sizeof(LLVector2)) + 0xF) & ~0xF; - LLVector4a* combined_positions = (LLVector4a*)ll_aligned_malloc<64>(sizeof(LLVector4a) * 3 * size_vertices + tc_bytes_size); - LLVector4a* combined_normals = combined_positions + size_vertices; - LLVector2* combined_tex_coords = (LLVector2*)(combined_normals + size_vertices); - - // copy indices and vertices into new buffers - S32 combined_positions_shift = 0; - S32 indices_idx_shift = 0; - S32 combined_indices_shift = 0; - for (U32 face_idx = 0; face_idx < base_model->getNumVolumeFaces(); ++face_idx) - { - const LLVolumeFace &face = base_model->getVolumeFace(face_idx); - - // Vertices - S32 copy_bytes = face.mNumVertices * sizeof(LLVector4a); - LLVector4a::memcpyNonAliased16((F32*)(combined_positions + combined_positions_shift), (F32*)face.mPositions, copy_bytes); - - // Normals - LLVector4a::memcpyNonAliased16((F32*)(combined_normals + combined_positions_shift), (F32*)face.mNormals, copy_bytes); - - // Tex coords - copy_bytes = face.mNumVertices * sizeof(LLVector2); - memcpy((void*)(combined_tex_coords + combined_positions_shift), (void*)face.mTexCoords, copy_bytes); - - combined_positions_shift += face.mNumVertices; - - // Indices - // Sadly can't do dumb memcpy for indices, need to adjust each value - for (S32 i = 0; i < face.mNumIndices; ++i) - { - U16 idx = face.mIndices[i]; - - combined_indices[combined_indices_shift] = idx + indices_idx_shift; - combined_indices_shift++; - } - indices_idx_shift += face.mNumVertices; - } - - // II. Generate a shadow buffer if nessesary. - // Welds together vertices if possible - - U32* shadow_indices = NULL; - // if MESH_OPTIMIZER_FULL, just leave as is, since generateShadowIndexBufferU32 - // won't do anything new, model was remaped on a per face basis. - // Similar for MESH_OPTIMIZER_NO_TOPOLOGY, it's pointless - // since 'simplifySloppy' ignores all topology, including normals and uvs. - // Note: simplifySloppy can affect UVs significantly. - if (simplification_mode == MESH_OPTIMIZER_NO_NORMALS) - { - // strip normals, reflections should restore relatively correctly - shadow_indices = (U32*)ll_aligned_malloc_32(size_indices * sizeof(U32)); - LLMeshOptimizer::generateShadowIndexBufferU32(shadow_indices, combined_indices, size_indices, combined_positions, NULL, combined_tex_coords, size_vertices); - } - if (simplification_mode == MESH_OPTIMIZER_NO_UVS) - { - // strip uvs, can heavily affect textures - shadow_indices = (U32*)ll_aligned_malloc_32(size_indices * sizeof(U32)); - LLMeshOptimizer::generateShadowIndexBufferU32(shadow_indices, combined_indices, size_indices, combined_positions, NULL, NULL, size_vertices); - } - - U32* source_indices = NULL; - if (shadow_indices) - { - source_indices = shadow_indices; - } - else - { - source_indices = combined_indices; - } - - // III. Simplify - S32 target_indices = 0; - F32 result_error = 0; // how far from original the model is, 1 == 100% - S32 size_new_indices = 0; - - if (indices_decimator > 0) - { - target_indices = llclamp(llfloor(size_indices / indices_decimator), 3, (S32)size_indices); // leave at least one triangle - } - else // indices_decimator can be zero for error_threshold based calculations - { - target_indices = 3; - } - - size_new_indices = LLMeshOptimizer::simplifyU32( - output_indices, - source_indices, - size_indices, - combined_positions, - size_vertices, - LLVertexBuffer::sTypeSize[LLVertexBuffer::TYPE_VERTEX], - target_indices, - error_threshold, - simplification_mode == MESH_OPTIMIZER_NO_TOPOLOGY, - &result_error); - - if (result_error < 0) - { - LL_WARNS() << "Negative result error from meshoptimizer for model " << target_model->mLabel - << " target Indices: " << target_indices - << " new Indices: " << size_new_indices - << " original count: " << size_indices << LL_ENDL; - } - - // free unused buffers - ll_aligned_free_32(combined_indices); - ll_aligned_free_32(shadow_indices); - combined_indices = NULL; - shadow_indices = NULL; - - if (size_new_indices < 3) - { - // Model should have at least one visible triangle - ll_aligned_free<64>(combined_positions); - ll_aligned_free_32(output_indices); - - return -1; - } - - // IV. Repack back into individual faces - - LLVector4a* buffer_positions = (LLVector4a*)ll_aligned_malloc<64>(sizeof(LLVector4a) * 3 * size_vertices + tc_bytes_size); - LLVector4a* buffer_normals = buffer_positions + size_vertices; - LLVector2* buffer_tex_coords = (LLVector2*)(buffer_normals + size_vertices); - S32 buffer_idx_size = (size_indices * sizeof(U16) + 0xF) & ~0xF; - U16* buffer_indices = (U16*)ll_aligned_malloc_16(buffer_idx_size); - S32* old_to_new_positions_map = new S32[size_vertices]; - - S32 buf_positions_copied = 0; - S32 buf_indices_copied = 0; - indices_idx_shift = 0; - S32 valid_faces = 0; - - // Crude method to copy indices back into face - for (U32 face_idx = 0; face_idx < base_model->getNumVolumeFaces(); ++face_idx) - { - const LLVolumeFace &face = base_model->getVolumeFace(face_idx); - - // reset data for new run - buf_positions_copied = 0; - buf_indices_copied = 0; - bool copy_triangle = false; - S32 range = indices_idx_shift + face.mNumVertices; - - for (S32 i = 0; i < size_vertices; i++) - { - old_to_new_positions_map[i] = -1; - } - - // Copy relevant indices and vertices - for (S32 i = 0; i < size_new_indices; ++i) - { - U32 idx = output_indices[i]; - - if ((i % 3) == 0) - { - copy_triangle = idx >= indices_idx_shift && idx < range; - } - - if (copy_triangle) - { - if (old_to_new_positions_map[idx] == -1) - { - // New position, need to copy it - // Validate size - if (buf_positions_copied >= U16_MAX) - { - // Normally this shouldn't happen since the whole point is to reduce amount of vertices - // but it might happen if user tries to run optimization with too large triangle or error value - // so fallback to 'per face' mode or verify requested limits and copy base model as is. - LL_WARNS() << "Over triangle limit. Failed to optimize in 'per object' mode, falling back to per face variant for" - << " model " << target_model->mLabel - << " target Indices: " << target_indices - << " new Indices: " << size_new_indices - << " original count: " << size_indices - << " error treshold: " << error_threshold - << LL_ENDL; - - // U16 vertices overflow shouldn't happen, but just in case - size_new_indices = 0; - valid_faces = 0; - for (U32 face_idx = 0; face_idx < base_model->getNumVolumeFaces(); ++face_idx) - { - genMeshOptimizerPerFace(base_model, target_model, face_idx, indices_decimator, error_threshold, simplification_mode); - const LLVolumeFace &face = target_model->getVolumeFace(face_idx); - size_new_indices += face.mNumIndices; - if (face.mNumIndices >= 3) - { - valid_faces++; - } - } - if (valid_faces) - { - return (F32)size_indices / (F32)size_new_indices; - } - else - { - return -1; - } - } - - // Copy vertice, normals, tcs - buffer_positions[buf_positions_copied] = combined_positions[idx]; - buffer_normals[buf_positions_copied] = combined_normals[idx]; - buffer_tex_coords[buf_positions_copied] = combined_tex_coords[idx]; - - old_to_new_positions_map[idx] = buf_positions_copied; - - buffer_indices[buf_indices_copied] = (U16)buf_positions_copied; - buf_positions_copied++; - } - else - { - // existing position - buffer_indices[buf_indices_copied] = (U16)old_to_new_positions_map[idx]; - } - buf_indices_copied++; - } - } - - if (buf_positions_copied >= U16_MAX) - { - break; - } - - LLVolumeFace &new_face = target_model->getVolumeFace(face_idx); - //new_face = face; //temp - - if (buf_indices_copied < 3) - { - // face was optimized away - new_face.resizeIndices(3); - new_face.resizeVertices(1); - memset(new_face.mIndices, 0, sizeof(U16) * 3); - new_face.mPositions[0].clear(); // set first vertice to 0 - new_face.mNormals[0].clear(); - new_face.mTexCoords[0].setZero(); - } - else - { - new_face.resizeIndices(buf_indices_copied); - new_face.resizeVertices(buf_positions_copied); - new_face.allocateTangents(buf_positions_copied); - S32 idx_size = (buf_indices_copied * sizeof(U16) + 0xF) & ~0xF; - LLVector4a::memcpyNonAliased16((F32*)new_face.mIndices, (F32*)buffer_indices, idx_size); - - LLVector4a::memcpyNonAliased16((F32*)new_face.mPositions, (F32*)buffer_positions, buf_positions_copied * sizeof(LLVector4a)); - LLVector4a::memcpyNonAliased16((F32*)new_face.mNormals, (F32*)buffer_normals, buf_positions_copied * sizeof(LLVector4a)); - - U32 tex_size = (buf_positions_copied * sizeof(LLVector2) + 0xF)&~0xF; - LLVector4a::memcpyNonAliased16((F32*)new_face.mTexCoords, (F32*)buffer_tex_coords, tex_size); - - valid_faces++; - } - - indices_idx_shift += face.mNumVertices; - } - - delete[]old_to_new_positions_map; - ll_aligned_free<64>(combined_positions); - ll_aligned_free<64>(buffer_positions); - ll_aligned_free_32(output_indices); - ll_aligned_free_16(buffer_indices); - - if (size_new_indices < 3 || valid_faces == 0) - { - // Model should have at least one visible triangle - return -1; - } - - return (F32)size_indices / (F32)size_new_indices; -} - -F32 LLModelPreview::genMeshOptimizerPerFace(LLModel *base_model, LLModel *target_model, U32 face_idx, F32 indices_decimator, F32 error_threshold, eSimplificationMode simplification_mode) -{ - const LLVolumeFace &face = base_model->getVolumeFace(face_idx); - S32 size_indices = face.mNumIndices; - if (size_indices < 3) - { - return -1; - } - - S32 size = (size_indices * sizeof(U16) + 0xF) & ~0xF; - U16* output_indices = (U16*)ll_aligned_malloc_16(size); - - U16* shadow_indices = NULL; - // if MESH_OPTIMIZER_FULL, just leave as is, since generateShadowIndexBufferU32 - // won't do anything new, model was remaped on a per face basis. - // Similar for MESH_OPTIMIZER_NO_TOPOLOGY, it's pointless - // since 'simplifySloppy' ignores all topology, including normals and uvs. - if (simplification_mode == MESH_OPTIMIZER_NO_NORMALS) - { - U16* shadow_indices = (U16*)ll_aligned_malloc_16(size); - LLMeshOptimizer::generateShadowIndexBufferU16(shadow_indices, face.mIndices, size_indices, face.mPositions, NULL, face.mTexCoords, face.mNumVertices); - } - if (simplification_mode == MESH_OPTIMIZER_NO_UVS) - { - U16* shadow_indices = (U16*)ll_aligned_malloc_16(size); - LLMeshOptimizer::generateShadowIndexBufferU16(shadow_indices, face.mIndices, size_indices, face.mPositions, NULL, NULL, face.mNumVertices); - } - // Don't run ShadowIndexBuffer for MESH_OPTIMIZER_NO_TOPOLOGY, it's pointless - - U16* source_indices = NULL; - if (shadow_indices) - { - source_indices = shadow_indices; - } - else - { - source_indices = face.mIndices; - } - - S32 target_indices = 0; - F32 result_error = 0; // how far from original the model is, 1 == 100% - S32 size_new_indices = 0; - - if (indices_decimator > 0) - { - target_indices = llclamp(llfloor(size_indices / indices_decimator), 3, (S32)size_indices); // leave at least one triangle - } - else - { - target_indices = 3; - } - - size_new_indices = LLMeshOptimizer::simplify( - output_indices, - source_indices, - size_indices, - face.mPositions, - face.mNumVertices, - LLVertexBuffer::sTypeSize[LLVertexBuffer::TYPE_VERTEX], - target_indices, - error_threshold, - simplification_mode == MESH_OPTIMIZER_NO_TOPOLOGY, - &result_error); - - if (result_error < 0) - { - LL_WARNS() << "Negative result error from meshoptimizer for face " << face_idx - << " of model " << target_model->mLabel - << " target Indices: " << target_indices - << " new Indices: " << size_new_indices - << " original count: " << size_indices - << " error treshold: " << error_threshold - << LL_ENDL; - } - - LLVolumeFace &new_face = target_model->getVolumeFace(face_idx); - - // Copy old values - new_face = face; - - if (size_new_indices < 3) - { - if (simplification_mode != MESH_OPTIMIZER_NO_TOPOLOGY) - { - // meshopt_optimizeSloppy() can optimize triangles away even if target_indices is > 2, - // but optimize() isn't supposed to - LL_INFOS() << "No indices generated by meshoptimizer for face " << face_idx - << " of model " << target_model->mLabel - << " target Indices: " << target_indices - << " original count: " << size_indices - << " error treshold: " << error_threshold - << LL_ENDL; - } - - // Face got optimized away - // Generate empty triangle - new_face.resizeIndices(3); - new_face.resizeVertices(1); - memset(new_face.mIndices, 0, sizeof(U16) * 3); - new_face.mPositions[0].clear(); // set first vertice to 0 - new_face.mNormals[0].clear(); - new_face.mTexCoords[0].setZero(); - } - else - { - // Assign new values - new_face.resizeIndices(size_new_indices); // will wipe out mIndices, so new_face can't substitute output - S32 idx_size = (size_new_indices * sizeof(U16) + 0xF) & ~0xF; - LLVector4a::memcpyNonAliased16((F32*)new_face.mIndices, (F32*)output_indices, idx_size); - - // Clear unused values - new_face.optimize(); - } - - ll_aligned_free_16(output_indices); - ll_aligned_free_16(shadow_indices); - - if (size_new_indices < 3) - { - // At least one triangle is needed - return -1; - } - - return (F32)size_indices / (F32)size_new_indices; -} - -void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 decimation, bool enforce_tri_limit) -{ - LL_INFOS() << "Generating lod " << which_lod << " using meshoptimizer" << LL_ENDL; - // Allow LoD from -1 to LLModel::LOD_PHYSICS - if (which_lod < -1 || which_lod > LLModel::NUM_LODS - 1) - { - std::ostringstream out; - out << "Invalid level of detail: " << which_lod; - LL_WARNS() << out.str() << LL_ENDL; - LLFloaterModelPreview::addStringToLog(out, false); - llassert(which_lod >= -1 && which_lod < LLModel::NUM_LODS); - return; - } - - if (mBaseModel.empty()) - { - return; - } - - //get the triangle count for all base models - S32 base_triangle_count = 0; - for (S32 i = 0; i < mBaseModel.size(); ++i) - { - base_triangle_count += mBaseModel[i]->getNumTriangles(); - } - - // Urgh... - // TODO: add interface to mFMP to get error treshold or let mFMP write one into LLModelPreview - // We should not be accesing views from other class! - U32 lod_mode = LIMIT_TRIANGLES; - F32 indices_decimator = 0; - F32 triangle_limit = 0; - F32 lod_error_threshold = 1; //100% - - // If requesting a single lod - if (which_lod > -1 && which_lod < NUM_LOD) - { - LLCtrlSelectionInterface* iface = mFMP->childGetSelectionInterface("lod_mode_" + lod_name[which_lod]); - if (iface) - { - lod_mode = iface->getFirstSelectedIndex(); - } - - if (lod_mode == LIMIT_TRIANGLES) - { - if (!enforce_tri_limit) - { - triangle_limit = base_triangle_count; - // reset to default value for this lod - F32 pw = pow((F32)decimation, (F32)(LLModel::LOD_HIGH - which_lod)); - - triangle_limit /= pw; //indices_ratio can be 1/pw - } - else - { - - // UI spacifies limit for all models of single lod - triangle_limit = mFMP->childGetValue("lod_triangle_limit_" + lod_name[which_lod]).asInteger(); - - } - // meshoptimizer doesn't use triangle limit, it uses indices limit, so convert it to aproximate ratio - // triangle_limit can be 0. - indices_decimator = (F32)base_triangle_count / llmax(triangle_limit, 1.f); - } - else - { - // UI shows 0 to 100%, but meshoptimizer works with 0 to 1 - lod_error_threshold = mFMP->childGetValue("lod_error_threshold_" + lod_name[which_lod]).asReal() / 100.f; - } - } - else - { - // we are genrating all lods and each lod will get own indices_decimator - indices_decimator = 1; - triangle_limit = base_triangle_count; - } - - mMaxTriangleLimit = base_triangle_count; - - // Build models - - S32 start = LLModel::LOD_HIGH; - S32 end = 0; - - if (which_lod != -1) - { - start = which_lod; - end = which_lod; - } - - for (S32 lod = start; lod >= end; --lod) - { - if (which_lod == -1) - { - // we are genrating all lods and each lod gets own indices_ratio - if (lod < start) - { - indices_decimator *= decimation; - triangle_limit /= decimation; - } - } - - mRequestedTriangleCount[lod] = triangle_limit; - mRequestedErrorThreshold[lod] = lod_error_threshold * 100; - mRequestedLoDMode[lod] = lod_mode; - - mModel[lod].clear(); - mModel[lod].resize(mBaseModel.size()); - mVertexBuffer[lod].clear(); - - - for (U32 mdl_idx = 0; mdl_idx < mBaseModel.size(); ++mdl_idx) - { - LLModel* base = mBaseModel[mdl_idx]; - - LLVolumeParams volume_params; - volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE); - mModel[lod][mdl_idx] = new LLModel(volume_params, 0.f); - - std::string name = base->mLabel + getLodSuffix(lod); - - mModel[lod][mdl_idx]->mLabel = name; - mModel[lod][mdl_idx]->mSubmodelID = base->mSubmodelID; - mModel[lod][mdl_idx]->setNumVolumeFaces(base->getNumVolumeFaces()); - - LLModel* target_model = mModel[lod][mdl_idx]; - - // carry over normalized transform into simplified model - for (int i = 0; i < base->getNumVolumeFaces(); ++i) - { - LLVolumeFace& src = base->getVolumeFace(i); - LLVolumeFace& dst = target_model->getVolumeFace(i); - dst.mNormalizedScale = src.mNormalizedScale; - } - - S32 model_meshopt_mode = meshopt_mode; - - // Ideally this should run not per model, - // but combine all submodels with origin model as well - if (model_meshopt_mode == MESH_OPTIMIZER_PRECISE) - { - // Run meshoptimizer for each face - for (U32 face_idx = 0; face_idx < base->getNumVolumeFaces(); ++face_idx) - { - F32 res = genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_FULL); - if (res < 0) - { - // Mesh optimizer failed and returned an invalid model - const LLVolumeFace &face = base->getVolumeFace(face_idx); - LLVolumeFace &new_face = target_model->getVolumeFace(face_idx); - new_face = face; - } - } - } - - if (model_meshopt_mode == MESH_OPTIMIZER_SLOPPY) - { - // Run meshoptimizer for each face - for (U32 face_idx = 0; face_idx < base->getNumVolumeFaces(); ++face_idx) - { - if (genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_NO_TOPOLOGY) < 0) - { - // Sloppy failed and returned an invalid model - genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_FULL); - } - } - } - - if (model_meshopt_mode == MESH_OPTIMIZER_AUTO) - { - // Remove progressively more data if we can't reach the target. - F32 allowed_ratio_drift = 1.8f; - F32 precise_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_FULL); - - if (precise_ratio < 0 || (precise_ratio * allowed_ratio_drift < indices_decimator)) - { - precise_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_NO_NORMALS); - } - - if (precise_ratio < 0 || (precise_ratio * allowed_ratio_drift < indices_decimator)) - { - precise_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_NO_UVS); - } - - if (precise_ratio < 0 || (precise_ratio * allowed_ratio_drift < indices_decimator)) - { - // Try sloppy variant if normal one failed to simplify model enough. - // Sloppy variant can fail entirely and has issues with precision, - // so code needs to do multiple attempts with different decimators. - // Todo: this is a bit of a mess, needs to be refined and improved - - F32 last_working_decimator = 0.f; - F32 last_working_ratio = F32_MAX; - - F32 sloppy_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_NO_TOPOLOGY); - - if (sloppy_ratio > 0) - { - // Would be better to do a copy of target_model here, but if - // we need to use sloppy decimation, model should be cheap - // and fast to generate and it won't affect end result - last_working_decimator = indices_decimator; - last_working_ratio = sloppy_ratio; - } - - // Sloppy has a tendecy to error into lower side, so a request for 100 - // triangles turns into ~70, so check for significant difference from target decimation - F32 sloppy_ratio_drift = 1.4f; - if (lod_mode == LIMIT_TRIANGLES - && (sloppy_ratio > indices_decimator * sloppy_ratio_drift || sloppy_ratio < 0)) - { - // Apply a correction to compensate. - - // (indices_decimator / res_ratio) by itself is likely to overshoot to a differend - // side due to overal lack of precision, and we don't need an ideal result, which - // likely does not exist, just a better one, so a partial correction is enough. - F32 sloppy_decimator = indices_decimator * (indices_decimator / sloppy_ratio + 1) / 2; - sloppy_ratio = genMeshOptimizerPerModel(base, target_model, sloppy_decimator, lod_error_threshold, MESH_OPTIMIZER_NO_TOPOLOGY); - } - - if (last_working_decimator > 0 && sloppy_ratio < last_working_ratio) - { - // Compensation didn't work, return back to previous decimator - sloppy_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_NO_TOPOLOGY); - } - - if (sloppy_ratio < 0) - { - // Sloppy method didn't work, try with smaller decimation values - { - // Find a decimator that does work - F32 sloppy_decimation_step = sqrt((F32)decimation); // example: 27->15->9->5->3 - F32 sloppy_decimator = indices_decimator / sloppy_decimation_step; - U64Microseconds end_time = LLTimer::getTotalTime() + U64Seconds(5); - - while (sloppy_ratio < 0 - && sloppy_decimator > precise_ratio - && sloppy_decimator > 1 // precise_ratio isn't supposed to be below 1, but check just in case - && end_time > LLTimer::getTotalTime()) - { - sloppy_ratio = genMeshOptimizerPerModel(base, target_model, sloppy_decimator, lod_error_threshold, MESH_OPTIMIZER_NO_TOPOLOGY); - sloppy_decimator = sloppy_decimator / sloppy_decimation_step; - } - } - } - - if (sloppy_ratio < 0 || sloppy_ratio < precise_ratio) - { - // Sloppy variant failed to generate triangles or is worse. - // Can happen with models that are too simple as is. - - if (precise_ratio < 0) - { - // Precise method failed as well, just copy face over - target_model->copyVolumeFaces(base); - precise_ratio = 1.f; - } - else - { - // Fallback to normal method - precise_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_FULL); - } - - LL_INFOS() << "Model " << target_model->getName() - << " lod " << which_lod - << " resulting ratio " << precise_ratio - << " simplified using per model method." << LL_ENDL; - } - else - { - LL_INFOS() << "Model " << target_model->getName() - << " lod " << which_lod - << " resulting ratio " << sloppy_ratio - << " sloppily simplified using per model method." << LL_ENDL; - } - } - else - { - LL_INFOS() << "Model " << target_model->getName() - << " lod " << which_lod - << " resulting ratio " << precise_ratio - << " simplified using per model method." << LL_ENDL; - } - } - - //blind copy skin weights and just take closest skin weight to point on - //decimated mesh for now (auto-generating LODs with skin weights is still a bit - //of an open problem). - target_model->mPosition = base->mPosition; - target_model->mSkinWeights = base->mSkinWeights; - target_model->mSkinInfo = base->mSkinInfo; - - //copy material list - target_model->mMaterialList = base->mMaterialList; - - if (!validate_model(target_model)) - { - LL_ERRS() << "Invalid model generated when creating LODs" << LL_ENDL; - } - } - - //rebuild scene based on mBaseScene - mScene[lod].clear(); - mScene[lod] = mBaseScene; - - for (U32 i = 0; i < mBaseModel.size(); ++i) - { - LLModel* mdl = mBaseModel[i]; - LLModel* target = mModel[lod][i]; - if (target) - { - for (LLModelLoader::scene::iterator iter = mScene[lod].begin(); iter != mScene[lod].end(); ++iter) - { - for (U32 j = 0; j < iter->second.size(); ++j) - { - if (iter->second[j].mModel == mdl) - { - iter->second[j].mModel = target; - } - } - } - } - } - } -} - -void LLModelPreview::updateStatusMessages() -{ - // bit mask values for physics errors. used to prevent overwrite of single line status - // TODO: use this to provied multiline status - enum PhysicsError - { - NONE = 0, - NOHAVOK = 1, - DEGENERATE = 2, - TOOMANYHULLS = 4, - TOOMANYVERTSINHULL = 8 - }; - - assert_main_thread(); - - U32 has_physics_error{ PhysicsError::NONE }; // physics error bitmap - //triangle/vertex/submesh count for each mesh asset for each lod - std::vector tris[LLModel::NUM_LODS]; - std::vector verts[LLModel::NUM_LODS]; - std::vector submeshes[LLModel::NUM_LODS]; - - //total triangle/vertex/submesh count for each lod - S32 total_tris[LLModel::NUM_LODS]; - S32 total_verts[LLModel::NUM_LODS]; - S32 total_submeshes[LLModel::NUM_LODS]; - - for (U32 i = 0; i < LLModel::NUM_LODS - 1; i++) - { - total_tris[i] = 0; - total_verts[i] = 0; - total_submeshes[i] = 0; - } - - for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter) - { - LLModelInstance& instance = *iter; - - LLModel* model_high_lod = instance.mLOD[LLModel::LOD_HIGH]; - if (!model_high_lod) - { - setLoadState(LLModelLoader::ERROR_MATERIALS); - mFMP->childDisable("calculate_btn"); - continue; - } - - for (U32 i = 0; i < LLModel::NUM_LODS - 1; i++) - { - LLModel* lod_model = instance.mLOD[i]; - if (!lod_model) - { - setLoadState(LLModelLoader::ERROR_MATERIALS); - mFMP->childDisable("calculate_btn"); - } - else - { - //for each model in the lod - S32 cur_tris = 0; - S32 cur_verts = 0; - S32 cur_submeshes = lod_model->getNumVolumeFaces(); - - for (S32 j = 0; j < cur_submeshes; ++j) - { //for each submesh (face), add triangles and vertices to current total - const LLVolumeFace& face = lod_model->getVolumeFace(j); - cur_tris += face.mNumIndices / 3; - cur_verts += face.mNumVertices; - } - - std::string instance_name = instance.mLabel; - - if (mImporterDebug) - { - // Useful for debugging generalized complaints below about total submeshes which don't have enough - // context to address exactly what needs to be fixed to move towards compliance with the rules. - // - std::ostringstream out; - out << "Instance " << lod_model->mLabel << " LOD " << i << " Verts: " << cur_verts; - LL_INFOS() << out.str() << LL_ENDL; - LLFloaterModelPreview::addStringToLog(out, false); - - out.str(""); - out << "Instance " << lod_model->mLabel << " LOD " << i << " Tris: " << cur_tris; - LL_INFOS() << out.str() << LL_ENDL; - LLFloaterModelPreview::addStringToLog(out, false); - - out.str(""); - out << "Instance " << lod_model->mLabel << " LOD " << i << " Faces: " << cur_submeshes; - LL_INFOS() << out.str() << LL_ENDL; - LLFloaterModelPreview::addStringToLog(out, false); - - out.str(""); - LLModel::material_list::iterator mat_iter = lod_model->mMaterialList.begin(); - while (mat_iter != lod_model->mMaterialList.end()) - { - out << "Instance " << lod_model->mLabel << " LOD " << i << " Material " << *(mat_iter); - LL_INFOS() << out.str() << LL_ENDL; - LLFloaterModelPreview::addStringToLog(out, false); - out.str(""); - mat_iter++; - } - } - - //add this model to the lod total - total_tris[i] += cur_tris; - total_verts[i] += cur_verts; - total_submeshes[i] += cur_submeshes; - - //store this model's counts to asset data - tris[i].push_back(cur_tris); - verts[i].push_back(cur_verts); - submeshes[i].push_back(cur_submeshes); - } - } - } - - if (mMaxTriangleLimit == 0) - { - mMaxTriangleLimit = total_tris[LLModel::LOD_HIGH]; - } - - mHasDegenerate = false; - {//check for degenerate triangles in physics mesh - U32 lod = LLModel::LOD_PHYSICS; - const LLVector4a scale(0.5f); - for (U32 i = 0; i < mModel[lod].size() && !mHasDegenerate; ++i) - { //for each model in the lod - if (mModel[lod][i] && mModel[lod][i]->mPhysics.mHull.empty()) - { //no decomp exists - S32 cur_submeshes = mModel[lod][i]->getNumVolumeFaces(); - for (S32 j = 0; j < cur_submeshes && !mHasDegenerate; ++j) - { //for each submesh (face), add triangles and vertices to current total - LLVolumeFace& face = mModel[lod][i]->getVolumeFace(j); - for (S32 k = 0; (k < face.mNumIndices) && !mHasDegenerate;) - { - U16 index_a = face.mIndices[k + 0]; - U16 index_b = face.mIndices[k + 1]; - U16 index_c = face.mIndices[k + 2]; - - if (index_c == 0 && index_b == 0 && index_a == 0) // test in reverse as 3rd index is less likely to be 0 in a normal case - { - LL_DEBUGS("MeshValidation") << "Empty placeholder triangle (3 identical index 0 verts) ignored" << LL_ENDL; - } - else - { - LLVector4a v1; v1.setMul(face.mPositions[index_a], scale); - LLVector4a v2; v2.setMul(face.mPositions[index_b], scale); - LLVector4a v3; v3.setMul(face.mPositions[index_c], scale); - if (ll_is_degenerate(v1, v2, v3)) - { - mHasDegenerate = true; - } - } - k += 3; - } - } - } - } - } - - // flag degenerates here rather than deferring to a MAV error later - mFMP->childSetVisible("physics_status_message_text", mHasDegenerate); //display or clear - auto degenerateIcon = mFMP->getChild("physics_status_message_icon"); - degenerateIcon->setVisible(mHasDegenerate); - if (mHasDegenerate) - { - has_physics_error |= PhysicsError::DEGENERATE; - mFMP->childSetValue("physics_status_message_text", mFMP->getString("phys_status_degenerate_triangles")); - LLUIImagePtr img = LLUI::getUIImage("ModelImport_Status_Error"); - degenerateIcon->setImage(img); - } - - mFMP->childSetTextArg("submeshes_info", "[SUBMESHES]", llformat("%d", total_submeshes[LLModel::LOD_HIGH])); - - std::string mesh_status_na = mFMP->getString("mesh_status_na"); - - S32 upload_status[LLModel::LOD_HIGH + 1]; - - mModelNoErrors = true; - - const U32 lod_high = LLModel::LOD_HIGH; - U32 high_submodel_count = mModel[lod_high].size() - countRootModels(mModel[lod_high]); - - for (S32 lod = 0; lod <= lod_high; ++lod) - { - upload_status[lod] = 0; - - std::string message = "mesh_status_good"; - - if (total_tris[lod] > 0) - { - mFMP->childSetValue(lod_triangles_name[lod], llformat("%d", total_tris[lod])); - mFMP->childSetValue(lod_vertices_name[lod], llformat("%d", total_verts[lod])); - } - else - { - if (lod == lod_high) - { - upload_status[lod] = 2; - message = "mesh_status_missing_lod"; - } - else - { - for (S32 i = lod - 1; i >= 0; --i) - { - if (total_tris[i] > 0) - { - upload_status[lod] = 2; - message = "mesh_status_missing_lod"; - } - } - } - - mFMP->childSetValue(lod_triangles_name[lod], mesh_status_na); - mFMP->childSetValue(lod_vertices_name[lod], mesh_status_na); - } - - if (lod != lod_high) - { - if (total_submeshes[lod] && total_submeshes[lod] != total_submeshes[lod_high]) - { //number of submeshes is different - message = "mesh_status_submesh_mismatch"; - upload_status[lod] = 2; - } - else if (mModel[lod].size() - countRootModels(mModel[lod]) != high_submodel_count) - {//number of submodels is different, not all faces are matched correctly. - message = "mesh_status_submesh_mismatch"; - upload_status[lod] = 2; - // Note: Submodels in instance were loaded from higher LOD and as result face count - // returns same value and total_submeshes[lod] is identical to high_lod one. - } - else if (!tris[lod].empty() && tris[lod].size() != tris[lod_high].size()) - { //number of meshes is different - message = "mesh_status_mesh_mismatch"; - upload_status[lod] = 2; - } - else if (!verts[lod].empty()) - { - S32 sum_verts_higher_lod = 0; - S32 sum_verts_this_lod = 0; - for (U32 i = 0; i < verts[lod].size(); ++i) - { - sum_verts_higher_lod += ((i < verts[lod + 1].size()) ? verts[lod + 1][i] : 0); - sum_verts_this_lod += verts[lod][i]; - } - - if ((sum_verts_higher_lod > 0) && - (sum_verts_this_lod > sum_verts_higher_lod)) - { - //too many vertices in this lod - message = "mesh_status_too_many_vertices"; - upload_status[lod] = 1; - } - } - } - - LLIconCtrl* icon = mFMP->getChild(lod_icon_name[lod]); - LLUIImagePtr img = LLUI::getUIImage(lod_status_image[upload_status[lod]]); - icon->setVisible(true); - icon->setImage(img); - - if (upload_status[lod] >= 2) - { - mModelNoErrors = false; - } - - if (lod == mPreviewLOD) - { - mFMP->childSetValue("lod_status_message_text", mFMP->getString(message)); - icon = mFMP->getChild("lod_status_message_icon"); - icon->setImage(img); - } - - updateLodControls(lod); - } - - - //warn if hulls have more than 256 points in them - bool physExceededVertexLimit = false; - for (U32 i = 0; mModelNoErrors && i < mModel[LLModel::LOD_PHYSICS].size(); ++i) - { - LLModel* mdl = mModel[LLModel::LOD_PHYSICS][i]; - - if (mdl) - { - for (U32 j = 0; j < mdl->mPhysics.mHull.size(); ++j) - { - if (mdl->mPhysics.mHull[j].size() > 256) - { - physExceededVertexLimit = true; - LL_INFOS() << "Physical model " << mdl->mLabel << " exceeds vertex per hull limitations." << LL_ENDL; - break; - } - } - } - } - - if (physExceededVertexLimit) - { - has_physics_error |= PhysicsError::TOOMANYVERTSINHULL; - } - - if (!(has_physics_error & PhysicsError::DEGENERATE)){ // only update this field (incluides clearing it) if it is not already in use. - mFMP->childSetVisible("physics_status_message_text", physExceededVertexLimit); - LLIconCtrl* physStatusIcon = mFMP->getChild("physics_status_message_icon"); - physStatusIcon->setVisible(physExceededVertexLimit); - if (physExceededVertexLimit) - { - mFMP->childSetValue("physics_status_message_text", mFMP->getString("phys_status_vertex_limit_exceeded")); - LLUIImagePtr img = LLUI::getUIImage("ModelImport_Status_Warning"); - physStatusIcon->setImage(img); - } - } - - if (getLoadState() >= LLModelLoader::ERROR_PARSING) - { - mModelNoErrors = false; - LL_INFOS() << "Loader returned errors, model can't be uploaded" << LL_ENDL; - } - - bool uploadingSkin = mFMP->childGetValue("upload_skin").asBoolean(); - bool uploadingJointPositions = mFMP->childGetValue("upload_joints").asBoolean(); - - if (uploadingSkin) - { - if (uploadingJointPositions && !isRigValidForJointPositionUpload()) - { - mModelNoErrors = false; - LL_INFOS() << "Invalid rig, there might be issues with uploading Joint positions" << LL_ENDL; - } - } - - if (mModelNoErrors && mModelLoader) - { - if (!mModelLoader->areTexturesReady() && mFMP->childGetValue("upload_textures").asBoolean()) - { - // Some textures are still loading, prevent upload until they are done - mModelNoErrors = false; - } - } - - if (!mModelNoErrors || mHasDegenerate) - { - mFMP->childDisable("ok_btn"); - mFMP->childDisable("calculate_btn"); - } - else - { - mFMP->childEnable("ok_btn"); - mFMP->childEnable("calculate_btn"); - } - - if (mModelNoErrors && mLodsWithParsingError.empty()) - { - mFMP->childEnable("calculate_btn"); - } - else - { - mFMP->childDisable("calculate_btn"); - } - - //add up physics triangles etc - S32 phys_tris = 0; - S32 phys_hulls = 0; - S32 phys_points = 0; - - //get the triangle count for the whole scene - for (LLModelLoader::scene::iterator iter = mScene[LLModel::LOD_PHYSICS].begin(), endIter = mScene[LLModel::LOD_PHYSICS].end(); iter != endIter; ++iter) - { - for (LLModelLoader::model_instance_list::iterator instance = iter->second.begin(), end_instance = iter->second.end(); instance != end_instance; ++instance) - { - LLModel* model = instance->mModel; - if (model) - { - S32 cur_submeshes = model->getNumVolumeFaces(); - - LLModel::convex_hull_decomposition& decomp = model->mPhysics.mHull; - - if (!decomp.empty()) - { - phys_hulls += decomp.size(); - for (U32 i = 0; i < decomp.size(); ++i) - { - phys_points += decomp[i].size(); - } - } - else - { //choose physics shape OR decomposition, can't use both - for (S32 j = 0; j < cur_submeshes; ++j) - { //for each submesh (face), add triangles and vertices to current total - const LLVolumeFace& face = model->getVolumeFace(j); - phys_tris += face.mNumIndices / 3; - } - } - } - } - } - - if (phys_tris > 0) - { - mFMP->childSetTextArg("physics_triangles", "[TRIANGLES]", llformat("%d", phys_tris)); - } - else - { - mFMP->childSetTextArg("physics_triangles", "[TRIANGLES]", mesh_status_na); - } - - if (phys_hulls > 0) - { - mFMP->childSetTextArg("physics_hulls", "[HULLS]", llformat("%d", phys_hulls)); - mFMP->childSetTextArg("physics_points", "[POINTS]", llformat("%d", phys_points)); - } - else - { - mFMP->childSetTextArg("physics_hulls", "[HULLS]", mesh_status_na); - mFMP->childSetTextArg("physics_points", "[POINTS]", mesh_status_na); - } - - LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance; - if (fmp) - { - if (phys_tris > 0 || phys_hulls > 0) - { - if (!fmp->isViewOptionEnabled("show_physics")) - { - fmp->enableViewOption("show_physics"); - mViewOption["show_physics"] = true; - fmp->childSetValue("show_physics", true); - } - } - else - { - fmp->disableViewOption("show_physics"); - mViewOption["show_physics"] = false; - fmp->childSetValue("show_physics", false); - - } - - //bool use_hull = fmp->childGetValue("physics_use_hull").asBoolean(); - - //fmp->childSetEnabled("physics_optimize", !use_hull); - - bool enable = (phys_tris > 0 || phys_hulls > 0) && fmp->mCurRequest.empty(); - //enable = enable && !use_hull && fmp->childGetValue("physics_optimize").asBoolean(); - - //enable/disable "analysis" UI - LLPanel* panel = fmp->getChild("physics analysis"); - LLView* child = panel->getFirstChild(); - while (child) - { - child->setEnabled(enable); - child = panel->findNextSibling(child); - } - - enable = phys_hulls > 0 && fmp->mCurRequest.empty(); - //enable/disable "simplification" UI - panel = fmp->getChild("physics simplification"); - child = panel->getFirstChild(); - while (child) - { - child->setEnabled(enable); - child = panel->findNextSibling(child); - } - - if (fmp->mCurRequest.empty()) - { - fmp->childSetVisible("Simplify", true); - fmp->childSetVisible("simplify_cancel", false); - fmp->childSetVisible("Decompose", true); - fmp->childSetVisible("decompose_cancel", false); - - if (phys_hulls > 0) - { - fmp->childEnable("Simplify"); - } - - if (phys_tris || phys_hulls > 0) - { - fmp->childEnable("Decompose"); - } - } - else - { - fmp->childEnable("simplify_cancel"); - fmp->childEnable("decompose_cancel"); - } - } - - - LLCtrlSelectionInterface* iface = fmp->childGetSelectionInterface("physics_lod_combo"); - S32 which_mode = 0; - S32 file_mode = 1; - if (iface) - { - which_mode = iface->getFirstSelectedIndex(); - file_mode = iface->getItemCount() - 1; - } - - if (which_mode == file_mode) - { - mFMP->childEnable("physics_file"); - mFMP->childEnable("physics_browse"); - } - else - { - mFMP->childDisable("physics_file"); - mFMP->childDisable("physics_browse"); - } - - LLSpinCtrl* crease = mFMP->getChild("crease_angle"); - - if (mRequestedCreaseAngle[mPreviewLOD] == -1.f) - { - mFMP->childSetColor("crease_label", LLColor4::grey); - crease->forceSetValue(75.f); - } - else - { - mFMP->childSetColor("crease_label", LLColor4::white); - crease->forceSetValue(mRequestedCreaseAngle[mPreviewLOD]); - } - - mModelUpdatedSignal(true); - -} - -void LLModelPreview::updateLodControls(S32 lod) -{ - if (lod < LLModel::LOD_IMPOSTOR || lod > LLModel::LOD_HIGH) - { - std::ostringstream out; - out << "Invalid level of detail: " << lod; - LL_WARNS() << out.str() << LL_ENDL; - LLFloaterModelPreview::addStringToLog(out, false); - assert(lod >= LLModel::LOD_IMPOSTOR && lod <= LLModel::LOD_HIGH); - return; - } - - const char* lod_controls[] = - { - "lod_mode_", - "lod_triangle_limit_", - "lod_error_threshold_" - }; - const U32 num_lod_controls = sizeof(lod_controls) / sizeof(char*); - - const char* file_controls[] = - { - "lod_browse_", - "lod_file_", - }; - const U32 num_file_controls = sizeof(file_controls) / sizeof(char*); - - LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance; - if (!fmp) return; - - LLComboBox* lod_combo = mFMP->findChild("lod_source_" + lod_name[lod]); - if (!lod_combo) return; - - S32 lod_mode = lod_combo->getCurrentIndex(); - if (lod_mode == LOD_FROM_FILE) // LoD from file - { - fmp->mLODMode[lod] = LOD_FROM_FILE; - for (U32 i = 0; i < num_file_controls; ++i) - { - mFMP->childSetVisible(file_controls[i] + lod_name[lod], true); - } - - for (U32 i = 0; i < num_lod_controls; ++i) - { - mFMP->childSetVisible(lod_controls[i] + lod_name[lod], false); - } - } - else if (lod_mode == USE_LOD_ABOVE) // use LoD above - { - fmp->mLODMode[lod] = USE_LOD_ABOVE; - for (U32 i = 0; i < num_file_controls; ++i) - { - mFMP->childSetVisible(file_controls[i] + lod_name[lod], false); - } - - for (U32 i = 0; i < num_lod_controls; ++i) - { - mFMP->childSetVisible(lod_controls[i] + lod_name[lod], false); - } - - if (lod < LLModel::LOD_HIGH) - { - mModel[lod] = mModel[lod + 1]; - mScene[lod] = mScene[lod + 1]; - mVertexBuffer[lod].clear(); - - // Also update lower LoD - if (lod > LLModel::LOD_IMPOSTOR) - { - updateLodControls(lod - 1); - } - } - } - else // auto generate, the default case for all LoDs except High - { - fmp->mLODMode[lod] = MESH_OPTIMIZER_AUTO; - - //don't actually regenerate lod when refreshing UI - mLODFrozen = true; - - for (U32 i = 0; i < num_file_controls; ++i) - { - mFMP->getChildView(file_controls[i] + lod_name[lod])->setVisible(false); - } - - for (U32 i = 0; i < num_lod_controls; ++i) - { - mFMP->getChildView(lod_controls[i] + lod_name[lod])->setVisible(true); - } - - - LLSpinCtrl* threshold = mFMP->getChild("lod_error_threshold_" + lod_name[lod]); - LLSpinCtrl* limit = mFMP->getChild("lod_triangle_limit_" + lod_name[lod]); - - limit->setMaxValue(mMaxTriangleLimit); - limit->forceSetValue(mRequestedTriangleCount[lod]); - - threshold->forceSetValue(mRequestedErrorThreshold[lod]); - - mFMP->getChild("lod_mode_" + lod_name[lod])->selectNthItem(mRequestedLoDMode[lod]); - - if (mRequestedLoDMode[lod] == 0) - { - limit->setVisible(true); - threshold->setVisible(false); - - limit->setMaxValue(mMaxTriangleLimit); - limit->setIncrement(llmax((U32)1, mMaxTriangleLimit / 32)); - } - else - { - limit->setVisible(false); - threshold->setVisible(true); - } - - mLODFrozen = false; - } -} - -void LLModelPreview::setPreviewTarget(F32 distance) -{ - mCameraDistance = distance; - mCameraZoom = 1.f; - mCameraPitch = 0.f; - mCameraYaw = 0.f; - mCameraOffset.clearVec(); -} - -void LLModelPreview::clearBuffers() -{ - for (U32 i = 0; i < 6; i++) - { - mVertexBuffer[i].clear(); - } -} - -void LLModelPreview::genBuffers(S32 lod, bool include_skin_weights) -{ - LLModelLoader::model_list* model = NULL; - - if (lod < 0 || lod > 4) - { - model = &mBaseModel; - lod = 5; - } - else - { - model = &(mModel[lod]); - } - - if (!mVertexBuffer[lod].empty()) - { - mVertexBuffer[lod].clear(); - } - - mVertexBuffer[lod].clear(); - - LLModelLoader::model_list::iterator base_iter = mBaseModel.begin(); - - for (LLModelLoader::model_list::iterator iter = model->begin(); iter != model->end(); ++iter) - { - LLModel* mdl = *iter; - if (!mdl) - { - continue; - } - - base_iter++; - - bool skinned = include_skin_weights && !mdl->mSkinWeights.empty(); - - LLMatrix4a mat_normal; - if (skinned) - { - glh::matrix4f m((F32*)mdl->mSkinInfo.mBindShapeMatrix.getF32ptr()); - m = m.inverse().transpose(); - mat_normal.loadu(m.m); - } - - S32 num_faces = mdl->getNumVolumeFaces(); - for (S32 i = 0; i < num_faces; ++i) - { - const LLVolumeFace &vf = mdl->getVolumeFace(i); - U32 num_vertices = vf.mNumVertices; - U32 num_indices = vf.mNumIndices; - - if (!num_vertices || !num_indices) - { - continue; - } - - LLVertexBuffer* vb = NULL; - - - - U32 mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0; - - if (skinned) - { - mask |= LLVertexBuffer::MAP_WEIGHT4; - } - - vb = new LLVertexBuffer(mask); - - if (!vb->allocateBuffer(num_vertices, num_indices)) - { - // We are likely to crash due this failure, if this happens, find a way to gracefully stop preview - std::ostringstream out; - out << "Failed to allocate Vertex Buffer for model preview "; - out << num_vertices << " vertices and "; - out << num_indices << " indices"; - LL_WARNS() << out.str() << LL_ENDL; - LLFloaterModelPreview::addStringToLog(out, true); - } - - LLStrider vertex_strider; - LLStrider normal_strider; - LLStrider tc_strider; - LLStrider index_strider; - LLStrider weights_strider; - - vb->getVertexStrider(vertex_strider); - vb->getIndexStrider(index_strider); - - if (skinned) - { - vb->getWeight4Strider(weights_strider); - } - - LLVector4a::memcpyNonAliased16((F32*)vertex_strider.get(), (F32*)vf.mPositions, num_vertices * 4 * sizeof(F32)); - - if (skinned) - { - for (U32 i = 0; i < num_vertices; ++i) - { - LLVector4a* v = (LLVector4a*)vertex_strider.get(); - mdl->mSkinInfo.mBindShapeMatrix.affineTransform(*v, *v); - vertex_strider++; - } - } - if (vf.mTexCoords) - { - vb->getTexCoord0Strider(tc_strider); - S32 tex_size = (num_vertices * 2 * sizeof(F32) + 0xF) & ~0xF; - LLVector4a::memcpyNonAliased16((F32*)tc_strider.get(), (F32*)vf.mTexCoords, tex_size); - } - - if (vf.mNormals) - { - vb->getNormalStrider(normal_strider); - - if (skinned) - { - F32* normals = (F32*)normal_strider.get(); - LLVector4a* src = vf.mNormals; - LLVector4a* end = src + num_vertices; - - while (src < end) - { - LLVector4a normal; - mat_normal.rotate(*src++, normal); - normal.store4a(normals); - normals += 4; - } - } - else - { - LLVector4a::memcpyNonAliased16((F32*)normal_strider.get(), (F32*)vf.mNormals, num_vertices * 4 * sizeof(F32)); - } - } - - if (skinned) - { - for (U32 i = 0; i < num_vertices; i++) - { - //find closest weight to vf.mVertices[i].mPosition - LLVector3 pos(vf.mPositions[i].getF32ptr()); - - const LLModel::weight_list& weight_list = mdl->getJointInfluences(pos); - llassert(weight_list.size()>0 && weight_list.size() <= 4); // LLModel::loadModel() should guarantee this - - LLVector4 w(0, 0, 0, 0); - - for (U32 i = 0; i < weight_list.size(); ++i) - { - F32 wght = llclamp(weight_list[i].mWeight, 0.001f, 0.999f); - F32 joint = (F32)weight_list[i].mJointIdx; - w.mV[i] = joint + wght; - llassert(w.mV[i] - (S32)w.mV[i]>0.0f); // because weights are non-zero, and range of wt values - //should not cause floating point precision issues. - } - - *(weights_strider++) = w; - } - } - - // build indices - for (U32 i = 0; i < num_indices; i++) - { - *(index_strider++) = vf.mIndices[i]; - } - - vb->unmapBuffer(); - - mVertexBuffer[lod][mdl].push_back(vb); - } - } -} - -void LLModelPreview::update() -{ - if (mGenLOD) - { - bool subscribe_for_generation = mLodsQuery.empty(); - mGenLOD = false; - mDirty = true; - mLodsQuery.clear(); - - for (S32 lod = LLModel::LOD_HIGH; lod >= 0; --lod) - { - // adding all lods into query for generation - mLodsQuery.push_back(lod); - } - - if (subscribe_for_generation) - { - doOnIdleRepeating(lodQueryCallback); - } - } - - if (mDirty && mLodsQuery.empty()) - { - mDirty = false; - updateDimentionsAndOffsets(); - refresh(); - } -} - -//----------------------------------------------------------------------------- -// createPreviewAvatar -//----------------------------------------------------------------------------- -void LLModelPreview::createPreviewAvatar(void) -{ - mPreviewAvatar = (LLVOAvatar*)gObjectList.createObjectViewer(LL_PCODE_LEGACY_AVATAR, gAgent.getRegion(), LLViewerObject::CO_FLAG_UI_AVATAR); - if (mPreviewAvatar) - { - mPreviewAvatar->createDrawable(&gPipeline); - mPreviewAvatar->mSpecialRenderMode = 1; - mPreviewAvatar->startMotion(ANIM_AGENT_STAND); - mPreviewAvatar->hideSkirt(); - } - else - { - LL_INFOS() << "Failed to create preview avatar for upload model window" << LL_ENDL; - } -} - -//static -U32 LLModelPreview::countRootModels(LLModelLoader::model_list models) -{ - U32 root_models = 0; - model_list::iterator model_iter = models.begin(); - while (model_iter != models.end()) - { - LLModel* mdl = *model_iter; - if (mdl && mdl->mSubmodelID == 0) - { - root_models++; - } - model_iter++; - } - return root_models; -} - -void LLModelPreview::loadedCallback( - LLModelLoader::scene& scene, - LLModelLoader::model_list& model_list, - S32 lod, - void* opaque) -{ - LLModelPreview* pPreview = static_cast< LLModelPreview* >(opaque); - if (pPreview && !LLModelPreview::sIgnoreLoadedCallback) - { - // Load loader's warnings into floater's log tab - const LLSD out = pPreview->mModelLoader->logOut(); - LLSD::array_const_iterator iter_out = out.beginArray(); - LLSD::array_const_iterator end_out = out.endArray(); - for (; iter_out != end_out; ++iter_out) - { - if (iter_out->has("Message")) - { - LLFloaterModelPreview::addStringToLog(iter_out->get("Message"), *iter_out, true, pPreview->mModelLoader->mLod); - } - } - pPreview->mModelLoader->clearLog(); - pPreview->loadModelCallback(lod); // removes mModelLoader in some cases - if (pPreview->mLookUpLodFiles && (lod != LLModel::LOD_HIGH)) - { - pPreview->lookupLODModelFiles(lod); - } - - const LLVOAvatar* avatarp = pPreview->getPreviewAvatar(); - if (avatarp) { // set up ground plane for possible rendering - const LLVector3 root_pos = avatarp->mRoot->getPosition(); - const LLVector4a* ext = avatarp->mDrawable->getSpatialExtents(); - const LLVector4a min = ext[0], max = ext[1]; - const F32 center = (max[2] - min[2]) * 0.5f; - const F32 ground = root_pos[2] - center; - auto plane = pPreview->mGroundPlane; - plane[0] = {min[0], min[1], ground}; - plane[1] = {max[0], min[1], ground}; - plane[2] = {max[0], max[1], ground}; - plane[3] = {min[0], max[1], ground}; - } - } - -} - -void LLModelPreview::lookupLODModelFiles(S32 lod) -{ - if (lod == LLModel::LOD_PHYSICS) - { - mLookUpLodFiles = false; - return; - } - S32 next_lod = (lod - 1 >= LLModel::LOD_IMPOSTOR) ? lod - 1 : LLModel::LOD_PHYSICS; - - std::string lod_filename = mLODFile[LLModel::LOD_HIGH]; - std::string ext = ".dae"; - std::string lod_filename_lower(lod_filename); - LLStringUtil::toLower(lod_filename_lower); - std::string::size_type i = lod_filename_lower.rfind(ext); - if (i != std::string::npos) - { - lod_filename.replace(i, lod_filename.size() - ext.size(), getLodSuffix(next_lod) + ext); - } - if (gDirUtilp->fileExists(lod_filename)) - { - LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance; - if (fmp) - { - fmp->setCtrlLoadFromFile(next_lod); - } - loadModel(lod_filename, next_lod); - } - else - { - lookupLODModelFiles(next_lod); - } -} - -void LLModelPreview::stateChangedCallback(U32 state, void* opaque) -{ - LLModelPreview* pPreview = static_cast< LLModelPreview* >(opaque); - if (pPreview) - { - pPreview->setLoadState(state); - } -} - -LLJoint* LLModelPreview::lookupJointByName(const std::string& str, void* opaque) -{ - LLModelPreview* pPreview = static_cast< LLModelPreview* >(opaque); - if (pPreview) - { - return pPreview->getPreviewAvatar()->getJoint(str); - } - return NULL; -} - -U32 LLModelPreview::loadTextures(LLImportMaterial& material, void* opaque) -{ - (void)opaque; - - if (material.mDiffuseMapFilename.size()) - { - material.mOpaqueData = new LLPointer< LLViewerFetchedTexture >; - LLPointer< LLViewerFetchedTexture >& tex = (*reinterpret_cast< LLPointer< LLViewerFetchedTexture > * >(material.mOpaqueData)); - - tex = LLViewerTextureManager::getFetchedTextureFromUrl("file://" + LLURI::unescape(material.mDiffuseMapFilename), FTT_LOCAL_FILE, true, LLGLTexture::BOOST_PREVIEW); - tex->setLoadedCallback(LLModelPreview::textureLoadedCallback, 0, true, false, opaque, NULL, false); - tex->forceToSaveRawImage(0, F32_MAX); - material.setDiffuseMap(tex->getID()); // record tex ID - return 1; - } - - material.mOpaqueData = NULL; - return 0; -} - -void LLModelPreview::addEmptyFace(LLModel* pTarget) -{ - U32 type_mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0; - - LLPointer buff = new LLVertexBuffer(type_mask); - - buff->allocateBuffer(1, 3); - memset((U8*)buff->getMappedData(), 0, buff->getSize()); - memset((U8*)buff->getMappedIndices(), 0, buff->getIndicesSize()); - - buff->validateRange(0, buff->getNumVerts() - 1, buff->getNumIndices(), 0); - - LLStrider pos; - LLStrider norm; - LLStrider tc; - LLStrider index; - - buff->getVertexStrider(pos); - - if (type_mask & LLVertexBuffer::MAP_NORMAL) - { - buff->getNormalStrider(norm); - } - if (type_mask & LLVertexBuffer::MAP_TEXCOORD0) - { - buff->getTexCoord0Strider(tc); - } - - buff->getIndexStrider(index); - - //resize face array - int faceCnt = pTarget->getNumVolumeFaces(); - pTarget->setNumVolumeFaces(faceCnt + 1); - pTarget->setVolumeFaceData(faceCnt + 1, pos, norm, tc, index, buff->getNumVerts(), buff->getNumIndices()); - -} - -//----------------------------------------------------------------------------- -// render() -//----------------------------------------------------------------------------- -// Todo: we shouldn't be setting all those UI elements on render. -// Note: Render happens each frame with skinned avatars -bool LLModelPreview::render() -{ - assert_main_thread(); - - LLMutexLock lock(this); - mNeedsUpdate = false; - - bool edges = mViewOption["show_edges"]; - bool joint_overrides = mViewOption["show_joint_overrides"]; - bool joint_positions = mViewOption["show_joint_positions"]; - bool skin_weight = mViewOption["show_skin_weight"]; - bool textures = mViewOption["show_textures"]; - bool physics = mViewOption["show_physics"]; - - S32 width = getWidth(); - S32 height = getHeight(); - - LLGLSUIDefault def; - LLGLDisable no_blend(GL_BLEND); - LLGLEnable cull(GL_CULL_FACE); - LLGLDepthTest depth(GL_FALSE); // SL-12781 disable z-buffer to render background color - - { - gUIProgram.bind(); - - //clear background to grey - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.pushMatrix(); - gGL.loadIdentity(); - gGL.ortho(0.0f, width, 0.0f, height, -1.0f, 1.0f); - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - gGL.loadIdentity(); - - gGL.color4fv(PREVIEW_CANVAS_COL.mV); - gl_rect_2d_simple(width, height); - - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.popMatrix(); - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.popMatrix(); - gUIProgram.unbind(); - } - - LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance; - - bool has_skin_weights = false; - bool upload_skin = mFMP->childGetValue("upload_skin").asBoolean(); - bool upload_joints = mFMP->childGetValue("upload_joints").asBoolean(); - - if (upload_joints != mLastJointUpdate) - { - mLastJointUpdate = upload_joints; - if (fmp) - { - fmp->clearAvatarTab(); - } - } - - for (LLModelLoader::scene::iterator iter = mScene[mPreviewLOD].begin(); iter != mScene[mPreviewLOD].end(); ++iter) - { - for (LLModelLoader::model_instance_list::iterator model_iter = iter->second.begin(); model_iter != iter->second.end(); ++model_iter) - { - LLModelInstance& instance = *model_iter; - LLModel* model = instance.mModel; - model->mPelvisOffset = mPelvisZOffset; - if (!model->mSkinWeights.empty()) - { - has_skin_weights = true; - } - } - } - - if (has_skin_weights && lodsReady()) - { //model has skin weights, enable view options for skin weights and joint positions - U32 flags = getLegacyRigFlags(); - if (fmp) - { - if (flags == LEGACY_RIG_OK) - { - if (mFirstSkinUpdate) - { - // auto enable weight upload if weights are present - // (note: all these UI updates need to be somewhere that is not render) - fmp->childSetValue("upload_skin", true); - mFirstSkinUpdate = false; - upload_skin = true; - skin_weight = true; - mViewOption["show_skin_weight"] = true; - } - - fmp->enableViewOption("show_skin_weight"); - fmp->setViewOptionEnabled("show_joint_overrides", skin_weight); - fmp->setViewOptionEnabled("show_joint_positions", skin_weight); - mFMP->childEnable("upload_skin"); - mFMP->childSetValue("show_skin_weight", skin_weight); - - } - else if ((flags & LEGACY_RIG_FLAG_TOO_MANY_JOINTS) > 0) - { - mFMP->childSetVisible("skin_too_many_joints", true); - } - else if ((flags & LEGACY_RIG_FLAG_UNKNOWN_JOINT) > 0) - { - mFMP->childSetVisible("skin_unknown_joint", true); - } - } - } - else - { - mFMP->childDisable("upload_skin"); - if (fmp) - { - mViewOption["show_skin_weight"] = false; - fmp->disableViewOption("show_skin_weight"); - fmp->disableViewOption("show_joint_overrides"); - fmp->disableViewOption("show_joint_positions"); - - skin_weight = false; - mFMP->childSetValue("show_skin_weight", false); - fmp->setViewOptionEnabled("show_skin_weight", skin_weight); - } - } - - if (upload_skin && !has_skin_weights) - { //can't upload skin weights if model has no skin weights - mFMP->childSetValue("upload_skin", false); - upload_skin = false; - } - - if (!upload_skin && upload_joints) - { //can't upload joints if not uploading skin weights - mFMP->childSetValue("upload_joints", false); - upload_joints = false; - } - - if (fmp) - { - if (upload_skin) - { - // will populate list of joints - fmp->updateAvatarTab(upload_joints); - } - else - { - fmp->clearAvatarTab(); - } - } - - if (upload_skin && upload_joints) - { - mFMP->childEnable("lock_scale_if_joint_position"); - } - else - { - mFMP->childDisable("lock_scale_if_joint_position"); - mFMP->childSetValue("lock_scale_if_joint_position", false); - } - - //Only enable joint offsets if it passed the earlier critiquing - if (isRigValidForJointPositionUpload()) - { - mFMP->childSetEnabled("upload_joints", upload_skin); - } - - F32 explode = mFMP->childGetValue("physics_explode").asReal(); - - LLGLDepthTest gls_depth(GL_TRUE); // SL-12781 re-enable z-buffer for 3D model preview - - LLRect preview_rect; - - preview_rect = mFMP->getChildView("preview_panel")->getRect(); - - F32 aspect = (F32)preview_rect.getWidth() / preview_rect.getHeight(); - - LLViewerCamera::getInstance()->setAspect(aspect); - - LLViewerCamera::getInstance()->setView(LLViewerCamera::getInstance()->getDefaultFOV() / mCameraZoom); - - LLVector3 offset = mCameraOffset; - LLVector3 target_pos = mPreviewTarget + offset; - - F32 z_near = 0.001f; - F32 z_far = mCameraDistance*10.0f + mPreviewScale.magVec() + mCameraOffset.magVec(); - - if (skin_weight) - { - target_pos = getPreviewAvatar()->getPositionAgent() + offset; - z_near = 0.01f; - z_far = 1024.f; - - //render avatar previews every frame - refresh(); - } - - gObjectPreviewProgram.bind(skin_weight); - - gGL.loadIdentity(); - gPipeline.enableLightsPreview(); - - LLQuaternion camera_rot = LLQuaternion(mCameraPitch, LLVector3::y_axis) * - LLQuaternion(mCameraYaw, LLVector3::z_axis); - - LLQuaternion av_rot = camera_rot; - F32 camera_distance = skin_weight ? SKIN_WEIGHT_CAMERA_DISTANCE : mCameraDistance; - LLViewerCamera::getInstance()->setOriginAndLookAt( - target_pos + ((LLVector3(camera_distance, 0.f, 0.f) + offset) * av_rot), // camera - LLVector3::z_axis, // up - target_pos); // point of interest - - - z_near = llclamp(z_far * 0.001f, 0.001f, 0.1f); - - LLViewerCamera::getInstance()->setPerspective(false, mOrigin.mX, mOrigin.mY, width, height, false, z_near, z_far); - - stop_glerror(); - - gGL.pushMatrix(); - gGL.color4fv(PREVIEW_EDGE_COL.mV); - - if (!mBaseModel.empty() && mVertexBuffer[5].empty()) - { - genBuffers(-1, skin_weight); - //genBuffers(3); - } - - if (!mModel[mPreviewLOD].empty()) - { - mFMP->childEnable("reset_btn"); - - bool regen = mVertexBuffer[mPreviewLOD].empty(); - if (!regen) - { - const std::vector >& vb_vec = mVertexBuffer[mPreviewLOD].begin()->second; - if (!vb_vec.empty()) - { - const LLVertexBuffer* buff = vb_vec[0]; - regen = buff->hasDataType(LLVertexBuffer::TYPE_WEIGHT4) != skin_weight; - } - else - { - LL_INFOS() << "Vertex Buffer[" << mPreviewLOD << "]" << " is EMPTY!!!" << LL_ENDL; - regen = true; - } - } - - if (regen) - { - genBuffers(mPreviewLOD, skin_weight); - } - - if (physics && mVertexBuffer[LLModel::LOD_PHYSICS].empty()) - { - genBuffers(LLModel::LOD_PHYSICS, false); - } - - if (!skin_weight) - { - for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter) - { - LLModelInstance& instance = *iter; - - LLModel* model = instance.mLOD[mPreviewLOD]; - - if (!model) - { - continue; - } - - gGL.pushMatrix(); - - LLMatrix4 mat = instance.mTransform; - - gGL.multMatrix((GLfloat*)mat.mMatrix); - - U32 num_models = mVertexBuffer[mPreviewLOD][model].size(); - for (U32 i = 0; i < num_models; ++i) - { - LLVertexBuffer* buffer = mVertexBuffer[mPreviewLOD][model][i]; - - buffer->setBuffer(); - - if (textures) - { - int materialCnt = instance.mModel->mMaterialList.size(); - if (i < materialCnt) - { - const std::string& binding = instance.mModel->mMaterialList[i]; - const LLImportMaterial& material = instance.mMaterial[binding]; - - gGL.diffuseColor4fv(material.mDiffuseColor.mV); - - // Find the tex for this material, bind it, and add it to our set - // - LLViewerFetchedTexture* tex = bindMaterialDiffuseTexture(material); - if (tex) - { - mTextureSet.insert(tex); - } - } - } - else - { - gGL.diffuseColor4fv(PREVIEW_BASE_COL.mV); - } - - buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts() - 1, buffer->getNumIndices(), 0); - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.diffuseColor4fv(PREVIEW_EDGE_COL.mV); - if (edges) - { - glLineWidth(PREVIEW_EDGE_WIDTH); - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts() - 1, buffer->getNumIndices(), 0); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - glLineWidth(1.f); - } - buffer->unmapBuffer(); - } - gGL.popMatrix(); - } - - if (physics) - { - glClear(GL_DEPTH_BUFFER_BIT); - - for (U32 pass = 0; pass < 2; pass++) - { - if (pass == 0) - { //depth only pass - gGL.setColorMask(false, false); - } - else - { - gGL.setColorMask(true, true); - } - - //enable alpha blending on second pass but not first pass - LLGLState blend(GL_BLEND, pass); - - gGL.blendFunc(LLRender::BF_SOURCE_ALPHA, LLRender::BF_ONE_MINUS_SOURCE_ALPHA); - - for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter) - { - LLModelInstance& instance = *iter; - - LLModel* model = instance.mLOD[LLModel::LOD_PHYSICS]; - - if (!model) - { - continue; - } - - gGL.pushMatrix(); - LLMatrix4 mat = instance.mTransform; - - gGL.multMatrix((GLfloat*)mat.mMatrix); - - - bool render_mesh = true; - LLPhysicsDecomp* decomp = gMeshRepo.mDecompThread; - if (decomp) - { - LLMutexLock(decomp->mMutex); - - LLModel::Decomposition& physics = model->mPhysics; - - if (!physics.mHull.empty()) - { - render_mesh = false; - - if (physics.mMesh.empty()) - { //build vertex buffer for physics mesh - gMeshRepo.buildPhysicsMesh(physics); - } - - if (!physics.mMesh.empty()) - { //render hull instead of mesh - // SL-16993 physics.mMesh[i].mNormals were being used to light the exploded - // analyzed physics shape but the drawArrays() interface changed - // causing normal data <0,0,0> to be passed to the shader. - // The Phyics Preview shader uses plain vertex coloring so the physics hull is full lit. - // We could also use interface/ui shaders. - gObjectPreviewProgram.unbind(); - gPhysicsPreviewProgram.bind(); - - for (U32 i = 0; i < physics.mMesh.size(); ++i) - { - if (explode > 0.f) - { - gGL.pushMatrix(); - - LLVector3 offset = model->mHullCenter[i] - model->mCenterOfHullCenters; - offset *= explode; - - gGL.translatef(offset.mV[0], offset.mV[1], offset.mV[2]); - } - - static std::vector hull_colors; - - if (i + 1 >= hull_colors.size()) - { - hull_colors.push_back(LLColor4U(rand() % 128 + 127, rand() % 128 + 127, rand() % 128 + 127, 128)); - } - - gGL.diffuseColor4ubv(hull_colors[i].mV); - LLVertexBuffer::drawArrays(LLRender::TRIANGLES, physics.mMesh[i].mPositions); - - if (explode > 0.f) - { - gGL.popMatrix(); - } - } - - gPhysicsPreviewProgram.unbind(); - gObjectPreviewProgram.bind(); - } - } - } - - if (render_mesh) - { - U32 num_models = mVertexBuffer[LLModel::LOD_PHYSICS][model].size(); - if (pass > 0){ - for (U32 i = 0; i < num_models; ++i) - { - LLVertexBuffer* buffer = mVertexBuffer[LLModel::LOD_PHYSICS][model][i]; - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.diffuseColor4fv(PREVIEW_PSYH_FILL_COL.mV); - - buffer->setBuffer(); - buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts() - 1, buffer->getNumIndices(), 0); - - gGL.diffuseColor4fv(PREVIEW_PSYH_EDGE_COL.mV); - glLineWidth(PREVIEW_PSYH_EDGE_WIDTH); - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts() - 1, buffer->getNumIndices(), 0); - - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - glLineWidth(1.f); - - buffer->unmapBuffer(); - } - } - } - gGL.popMatrix(); - } - - // only do this if mDegenerate was set in the preceding mesh checks [Check this if the ordering ever breaks] - if (mHasDegenerate) - { - glLineWidth(PREVIEW_DEG_EDGE_WIDTH); - glPointSize(PREVIEW_DEG_POINT_SIZE); - gPipeline.enableLightsFullbright(); - //show degenerate triangles - LLGLDepthTest depth(GL_TRUE, GL_TRUE, GL_ALWAYS); - LLGLDisable cull(GL_CULL_FACE); - gGL.diffuseColor4f(1.f, 0.f, 0.f, 1.f); - const LLVector4a scale(0.5f); - - for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter) - { - LLModelInstance& instance = *iter; - - LLModel* model = instance.mLOD[LLModel::LOD_PHYSICS]; - - if (!model) - { - continue; - } - - gGL.pushMatrix(); - LLMatrix4 mat = instance.mTransform; - - gGL.multMatrix((GLfloat*)mat.mMatrix); - - - LLPhysicsDecomp* decomp = gMeshRepo.mDecompThread; - if (decomp) - { - LLMutexLock(decomp->mMutex); - - LLModel::Decomposition& physics = model->mPhysics; - - if (physics.mHull.empty()) - { - U32 num_models = mVertexBuffer[LLModel::LOD_PHYSICS][model].size(); - for (U32 v = 0; v < num_models; ++v) - { - LLVertexBuffer* buffer = mVertexBuffer[LLModel::LOD_PHYSICS][model][v]; - - buffer->setBuffer(); - - LLStrider pos_strider; - buffer->getVertexStrider(pos_strider, 0); - LLVector4a* pos = (LLVector4a*)pos_strider.get(); - - LLStrider idx; - buffer->getIndexStrider(idx, 0); - - for (U32 i = 0; i < buffer->getNumIndices(); i += 3) - { - LLVector4a v1; v1.setMul(pos[*idx++], scale); - LLVector4a v2; v2.setMul(pos[*idx++], scale); - LLVector4a v3; v3.setMul(pos[*idx++], scale); - - if (ll_is_degenerate(v1, v2, v3)) - { - buffer->draw(LLRender::LINE_LOOP, 3, i); - buffer->draw(LLRender::POINTS, 3, i); - } - } - - buffer->unmapBuffer(); - } - } - } - - gGL.popMatrix(); - } - glLineWidth(1.f); - glPointSize(1.f); - gPipeline.enableLightsPreview(); - gGL.setSceneBlendType(LLRender::BT_ALPHA); - } - } - } - } - else - { - target_pos = getPreviewAvatar()->getPositionAgent(); - getPreviewAvatar()->clearAttachmentOverrides(); // removes pelvis fixup - LLUUID fake_mesh_id; - fake_mesh_id.generate(); - getPreviewAvatar()->addPelvisFixup(mPelvisZOffset, fake_mesh_id); - bool pelvis_recalc = false; - - LLViewerCamera::getInstance()->setOriginAndLookAt( - target_pos + ((LLVector3(camera_distance, 0.f, 0.f) + offset) * av_rot), // camera - LLVector3::z_axis, // up - target_pos); // point of interest - - for (LLModelLoader::scene::iterator iter = mScene[mPreviewLOD].begin(); iter != mScene[mPreviewLOD].end(); ++iter) - { - for (LLModelLoader::model_instance_list::iterator model_iter = iter->second.begin(); model_iter != iter->second.end(); ++model_iter) - { - LLModelInstance& instance = *model_iter; - LLModel* model = instance.mModel; - - if (!model->mSkinWeights.empty()) - { - const LLMeshSkinInfo *skin = &model->mSkinInfo; - LLSkinningUtil::initJointNums(&model->mSkinInfo, getPreviewAvatar());// inits skin->mJointNums if nessesary - U32 joint_count = LLSkinningUtil::getMeshJointCount(skin); - U32 bind_count = skin->mAlternateBindMatrix.size(); - - if (joint_overrides - && bind_count > 0 - && joint_count == bind_count) - { - // mesh_id is used to determine which mesh gets to - // set the joint offset, in the event of a conflict. Since - // we don't know the mesh id yet, we can't guarantee that - // joint offsets will be applied with the same priority as - // in the uploaded model. If the file contains multiple - // meshes with conflicting joint offsets, preview may be - // incorrect. - LLUUID fake_mesh_id; - fake_mesh_id.generate(); - for (U32 j = 0; j < joint_count; ++j) - { - LLJoint *joint = getPreviewAvatar()->getJoint(skin->mJointNums[j]); - if (joint) - { - const LLVector3& jointPos = LLVector3(skin->mAlternateBindMatrix[j].getTranslation()); - if (joint->aboveJointPosThreshold(jointPos)) - { - bool override_changed; - joint->addAttachmentPosOverride(jointPos, fake_mesh_id, "model", override_changed); - - if (override_changed) - { - //If joint is a pelvis then handle old/new pelvis to foot values - if (joint->getName() == "mPelvis")// or skin->mJointNames[j] - { - pelvis_recalc = true; - } - } - if (skin->mLockScaleIfJointPosition) - { - // Note that unlike positions, there's no threshold check here, - // just a lock at the default value. - joint->addAttachmentScaleOverride(joint->getDefaultScale(), fake_mesh_id, "model"); - } - } - } - } - } - - for (U32 i = 0, e = mVertexBuffer[mPreviewLOD][model].size(); i < e; ++i) - { - LLVertexBuffer* buffer = mVertexBuffer[mPreviewLOD][model][i]; - - model->mSkinInfo.updateHash(); - LLRenderPass::uploadMatrixPalette(mPreviewAvatar, &model->mSkinInfo); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - if (textures) - { - int materialCnt = instance.mModel->mMaterialList.size(); - if (i < materialCnt) - { - const std::string& binding = instance.mModel->mMaterialList[i]; - const LLImportMaterial& material = instance.mMaterial[binding]; - - gGL.diffuseColor4fv(material.mDiffuseColor.mV); - - // Find the tex for this material, bind it, and add it to our set - // - LLViewerFetchedTexture* tex = bindMaterialDiffuseTexture(material); - if (tex) - { - mTextureSet.insert(tex); - } - } - } - else - { - gGL.diffuseColor4fv(PREVIEW_BASE_COL.mV); - } - - buffer->setBuffer(); - buffer->draw(LLRender::TRIANGLES, buffer->getNumIndices(), 0); - - if (edges) - { - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.diffuseColor4fv(PREVIEW_EDGE_COL.mV); - glLineWidth(PREVIEW_EDGE_WIDTH); - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - buffer->draw(LLRender::TRIANGLES, buffer->getNumIndices(), 0); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - glLineWidth(1.f); - } - } - } - } - } - - if (joint_positions) - { - LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; - if (shader) - { - gDebugProgram.bind(); - } - getPreviewAvatar()->renderCollisionVolumes(); - if (fmp->mTabContainer->getCurrentPanelIndex() == fmp->mAvatarTabIndex) - { - getPreviewAvatar()->renderBones(fmp->mSelectedJointName); - } - else - { - getPreviewAvatar()->renderBones(); - } - renderGroundPlane(mPelvisZOffset); - if (shader) - { - shader->bind(); - } - } - - if (pelvis_recalc) - { - // size/scale recalculation - getPreviewAvatar()->postPelvisSetRecalc(); - } - } - } - - gObjectPreviewProgram.unbind(); - - gGL.popMatrix(); - - return true; -} - -void LLModelPreview::renderGroundPlane(float z_offset) -{ // Not necesarilly general - beware - but it seems to meet the needs of LLModelPreview::render - - gGL.diffuseColor3f( 1.0f, 0.0f, 1.0f ); - - gGL.begin(LLRender::LINES); - gGL.vertex3fv(mGroundPlane[0].mV); - gGL.vertex3fv(mGroundPlane[1].mV); - - gGL.vertex3fv(mGroundPlane[1].mV); - gGL.vertex3fv(mGroundPlane[2].mV); - - gGL.vertex3fv(mGroundPlane[2].mV); - gGL.vertex3fv(mGroundPlane[3].mV); - - gGL.vertex3fv(mGroundPlane[3].mV); - gGL.vertex3fv(mGroundPlane[0].mV); - - gGL.end(); -} - - -//----------------------------------------------------------------------------- -// refresh() -//----------------------------------------------------------------------------- -void LLModelPreview::refresh() -{ - mNeedsUpdate = true; -} - -//----------------------------------------------------------------------------- -// rotate() -//----------------------------------------------------------------------------- -void LLModelPreview::rotate(F32 yaw_radians, F32 pitch_radians) -{ - mCameraYaw = mCameraYaw + yaw_radians; - - mCameraPitch = llclamp(mCameraPitch + pitch_radians, F_PI_BY_TWO * -0.8f, F_PI_BY_TWO * 0.8f); -} - -//----------------------------------------------------------------------------- -// zoom() -//----------------------------------------------------------------------------- -void LLModelPreview::zoom(F32 zoom_amt) -{ - F32 new_zoom = mCameraZoom + zoom_amt; - // TODO: stop clamping in render - mCameraZoom = llclamp(new_zoom, 1.f, PREVIEW_ZOOM_LIMIT); -} - -void LLModelPreview::pan(F32 right, F32 up) -{ - bool skin_weight = mViewOption["show_skin_weight"]; - F32 camera_distance = skin_weight ? SKIN_WEIGHT_CAMERA_DISTANCE : mCameraDistance; - mCameraOffset.mV[VY] = llclamp(mCameraOffset.mV[VY] + right * camera_distance / mCameraZoom, -1.f, 1.f); - mCameraOffset.mV[VZ] = llclamp(mCameraOffset.mV[VZ] + up * camera_distance / mCameraZoom, -1.f, 1.f); -} - -void LLModelPreview::setPreviewLOD(S32 lod) -{ - lod = llclamp(lod, 0, (S32)LLModel::LOD_HIGH); - - if (lod != mPreviewLOD) - { - mPreviewLOD = lod; - - LLComboBox* combo_box = mFMP->getChild("preview_lod_combo"); - combo_box->setCurrentByIndex((NUM_LOD - 1) - mPreviewLOD); // combo box list of lods is in reverse order - mFMP->childSetValue("lod_file_" + lod_name[mPreviewLOD], mLODFile[mPreviewLOD]); - - LLColor4 highlight_color = LLUIColorTable::instance().getColor("MeshImportTableHighlightColor"); - LLColor4 normal_color = LLUIColorTable::instance().getColor("MeshImportTableNormalColor"); - - for (S32 i = 0; i <= LLModel::LOD_HIGH; ++i) - { - const LLColor4& color = (i == lod) ? highlight_color : normal_color; - - mFMP->childSetColor(lod_status_name[i], color); - mFMP->childSetColor(lod_label_name[i], color); - mFMP->childSetColor(lod_triangles_name[i], color); - mFMP->childSetColor(lod_vertices_name[i], color); - } - - LLFloaterModelPreview* fmp = (LLFloaterModelPreview*)mFMP; - if (fmp) - { - // make preview repopulate tab - fmp->clearAvatarTab(); - } - } - refresh(); - updateStatusMessages(); -} - -//static -void LLModelPreview::textureLoadedCallback( - bool success, - LLViewerFetchedTexture *src_vi, - LLImageRaw* src, - LLImageRaw* src_aux, - S32 discard_level, - bool final, - void* userdata) -{ - LLModelPreview* preview = (LLModelPreview*)userdata; - preview->refresh(); - - if (final && preview->mModelLoader) - { - if (preview->mModelLoader->mNumOfFetchingTextures > 0) - { - preview->mModelLoader->mNumOfFetchingTextures--; - } - } -} - -// static -bool LLModelPreview::lodQueryCallback() -{ - // not the best solution, but model preview belongs to floater - // so it is an easy way to check that preview still exists. - LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance; - if (fmp && fmp->mModelPreview) - { - LLModelPreview* preview = fmp->mModelPreview; - if (preview->mLodsQuery.size() > 0) - { - S32 lod = preview->mLodsQuery.back(); - preview->mLodsQuery.pop_back(); - preview->genMeshOptimizerLODs(lod, MESH_OPTIMIZER_AUTO); - - if (preview->mLookUpLodFiles && (lod == LLModel::LOD_HIGH)) - { - preview->lookupLODModelFiles(LLModel::LOD_HIGH); - } - - // return false to continue cycle - return preview->mLodsQuery.empty(); - } - } - // nothing to process - return true; -} - -void LLModelPreview::onLODMeshOptimizerParamCommit(S32 requested_lod, bool enforce_tri_limit, S32 mode) -{ - if (!mLODFrozen) - { - genMeshOptimizerLODs(requested_lod, mode, 3, enforce_tri_limit); - refresh(); - mDirty = true; - } -} - +/** + * @file llmodelpreview.cpp + * @brief LLModelPreview class implementation + * + * $LicenseInfo:firstyear=2020&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2020, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llmodelpreview.h" + +#include "llmodelloader.h" +#include "lldaeloader.h" +#include "llgltfloader.h" +#include "llfloatermodelpreview.h" + +#include "llagent.h" +#include "llanimationstates.h" +#include "llcallbacklist.h" +#include "lldatapacker.h" +#include "lldrawable.h" +#include "llface.h" +#include "lliconctrl.h" +#include "llmatrix4a.h" +#include "llmeshrepository.h" +#include "llmeshoptimizer.h" +#include "llrender.h" +#include "llsdutil_math.h" +#include "llskinningutil.h" +#include "llstring.h" +#include "llsdserialize.h" +#include "lltoolmgr.h" +#include "llui.h" +#include "llvector4a.h" +#include "llviewercamera.h" +#include "llviewercontrol.h" +#include "llviewerobjectlist.h" +#include "llviewernetwork.h" +#include "llviewershadermgr.h" +#include "llviewertexteditor.h" +#include "llviewertexturelist.h" +#include "llvoavatar.h" +#include "pipeline.h" + +// ui controls (from floater) +#include "llbutton.h" +#include "llcombobox.h" +#include "llspinctrl.h" +#include "lltabcontainer.h" +#include "lltextbox.h" + +#include + +#include + +bool LLModelPreview::sIgnoreLoadedCallback = false; + +// Extra configurability, to be exposed later in xml (LLModelPreview probably +// should become UI control at some point or get split into preview control) +static const LLColor4 PREVIEW_CANVAS_COL(0.169f, 0.169f, 0.169f, 1.f); +static const LLColor4 PREVIEW_EDGE_COL(0.4f, 0.4f, 0.4f, 1.0); +static const LLColor4 PREVIEW_BASE_COL(1.f, 1.f, 1.f, 1.f); +static const LLColor3 PREVIEW_BRIGHTNESS(0.9f, 0.9f, 0.9f); +static const F32 PREVIEW_EDGE_WIDTH(1.f); +static const LLColor4 PREVIEW_PSYH_EDGE_COL(0.f, 0.25f, 0.5f, 0.25f); +static const LLColor4 PREVIEW_PSYH_FILL_COL(0.f, 0.5f, 1.0f, 0.5f); +static const F32 PREVIEW_PSYH_EDGE_WIDTH(1.f); +static const LLColor4 PREVIEW_DEG_EDGE_COL(1.f, 0.f, 0.f, 1.f); +static const LLColor4 PREVIEW_DEG_FILL_COL(1.f, 0.f, 0.f, 0.5f); +static const F32 PREVIEW_DEG_EDGE_WIDTH(3.f); +static const F32 PREVIEW_DEG_POINT_SIZE(8.f); +static const F32 PREVIEW_ZOOM_LIMIT(10.f); +static const std::string DEFAULT_PHYSICS_MESH_NAME = "default_physics_shape"; + +const F32 SKIN_WEIGHT_CAMERA_DISTANCE = 16.f; + +LLViewerFetchedTexture* bindMaterialDiffuseTexture(const LLImportMaterial& material) +{ + LLViewerFetchedTexture *texture = LLViewerTextureManager::getFetchedTexture(material.getDiffuseMap(), FTT_DEFAULT, true, LLGLTexture::BOOST_PREVIEW); + + if (texture) + { + if (texture->getDiscardLevel() > -1) + { + gGL.getTexUnit(0)->bind(texture, true); + return texture; + } + } + + return NULL; +} + +std::string stripSuffix(std::string name) +{ + if ((name.find("_LOD") != -1) || (name.find("_PHYS") != -1)) + { + return name.substr(0, name.rfind('_')); + } + return name; +} + +std::string getLodSuffix(S32 lod) +{ + std::string suffix; + switch (lod) + { + case LLModel::LOD_IMPOSTOR: suffix = "_LOD0"; break; + case LLModel::LOD_LOW: suffix = "_LOD1"; break; + case LLModel::LOD_MEDIUM: suffix = "_LOD2"; break; + case LLModel::LOD_PHYSICS: suffix = "_PHYS"; break; + case LLModel::LOD_HIGH: break; + } + return suffix; +} + +void FindModel(LLModelLoader::scene& scene, const std::string& name_to_match, LLModel*& baseModelOut, LLMatrix4& matOut) +{ + LLModelLoader::scene::iterator base_iter = scene.begin(); + bool found = false; + while (!found && (base_iter != scene.end())) + { + matOut = base_iter->first; + + LLModelLoader::model_instance_list::iterator base_instance_iter = base_iter->second.begin(); + while (!found && (base_instance_iter != base_iter->second.end())) + { + LLModelInstance& base_instance = *base_instance_iter++; + LLModel* base_model = base_instance.mModel; + + if (base_model && (base_model->mLabel == name_to_match)) + { + baseModelOut = base_model; + return; + } + } + base_iter++; + } +} + +//----------------------------------------------------------------------------- +// LLModelPreview +//----------------------------------------------------------------------------- + +LLModelPreview::LLModelPreview(S32 width, S32 height, LLFloater* fmp) + : LLViewerDynamicTexture(width, height, 3, ORDER_MIDDLE, false), LLMutex() + , mLodsQuery() + , mLodsWithParsingError() + , mPelvisZOffset(0.0f) + , mLegacyRigFlags(U32_MAX) + , mRigValidJointUpload(false) + , mPhysicsSearchLOD(LLModel::LOD_PHYSICS) + , mResetJoints(false) + , mModelNoErrors(true) + , mLastJointUpdate(false) + , mFirstSkinUpdate(true) + , mHasDegenerate(false) + , mImporterDebug(LLCachedControl(gSavedSettings, "ImporterDebug", false)) +{ + mNeedsUpdate = true; + mCameraDistance = 0.f; + mCameraYaw = 0.f; + mCameraPitch = 0.f; + mCameraZoom = 1.f; + mTextureName = 0; + mPreviewLOD = 0; + mModelLoader = NULL; + mMaxTriangleLimit = 0; + mDirty = false; + mGenLOD = false; + mLoading = false; + mLookUpLodFiles = false; + mLoadState = LLModelLoader::STARTING; + mGroup = 0; + mLODFrozen = false; + + for (U32 i = 0; i < LLModel::NUM_LODS; ++i) + { + mRequestedTriangleCount[i] = 0; + mRequestedCreaseAngle[i] = -1.f; + mRequestedLoDMode[i] = 0; + mRequestedErrorThreshold[i] = 0.f; + } + + mViewOption["show_textures"] = false; + + mFMP = fmp; + + mHasPivot = false; + mModelPivot = LLVector3(0.0f, 0.0f, 0.0f); + + createPreviewAvatar(); +} + +LLModelPreview::~LLModelPreview() +{ + if (mModelLoader) + { + mModelLoader->shutdown(); + } + + if (mPreviewAvatar) + { + mPreviewAvatar->markDead(); + mPreviewAvatar = NULL; + } + + mUploadData.clear(); + mTextureSet.clear(); + + for (U32 i = 0; i < LLModel::NUM_LODS; i++) + { + clearModel(i); + } + mBaseModel.clear(); + mBaseScene.clear(); +} + +void LLModelPreview::updateDimentionsAndOffsets() +{ + assert_main_thread(); + + rebuildUploadData(); + + std::set accounted; + + mPelvisZOffset = mFMP ? mFMP->childGetValue("pelvis_offset").asReal() : 3.0f; + + if (mFMP && mFMP->childGetValue("upload_joints").asBoolean()) + { + // FIXME if preview avatar ever gets reused, this fake mesh ID stuff will fail. + // see also call to addAttachmentPosOverride. + LLUUID fake_mesh_id; + fake_mesh_id.generate(); + getPreviewAvatar()->addPelvisFixup(mPelvisZOffset, fake_mesh_id); + } + + for (U32 i = 0; i < mUploadData.size(); ++i) + { + LLModelInstance& instance = mUploadData[i]; + + if (accounted.find(instance.mModel) == accounted.end()) + { + accounted.insert(instance.mModel); + + // update instance skin info for each lods pelvisZoffset + for (int j = 0; jmSkinInfo.mPelvisOffset = mPelvisZOffset; + } + } + } + } + + F32 scale = mFMP ? mFMP->childGetValue("import_scale").asReal()*2.f : 2.f; + + mDetailsSignal((F32)(mPreviewScale[0] * scale), (F32)(mPreviewScale[1] * scale), (F32)(mPreviewScale[2] * scale)); + + updateStatusMessages(); +} + +void LLModelPreview::rebuildUploadData() +{ + assert_main_thread(); + + mDefaultPhysicsShapeP = NULL; + mUploadData.clear(); + mTextureSet.clear(); + + //fill uploaddata instance vectors from scene data + + std::string requested_name = mFMP->getChild("description_form")->getValue().asString(); + + LLSpinCtrl* scale_spinner = mFMP->getChild("import_scale"); + + F32 scale = scale_spinner->getValue().asReal(); + + LLMatrix4 scale_mat; + scale_mat.initScale(LLVector3(scale, scale, scale)); + + F32 max_scale = 0.f; + + bool legacyMatching = gSavedSettings.getBOOL("ImporterLegacyMatching"); + U32 load_state = 0; + + for (LLModelLoader::scene::iterator iter = mBaseScene.begin(); iter != mBaseScene.end(); ++iter) + { //for each transform in scene + LLMatrix4 mat = iter->first; + + // compute position + LLVector3 position = LLVector3(0, 0, 0) * mat; + + // compute scale + LLVector3 x_transformed = LLVector3(1, 0, 0) * mat - position; + LLVector3 y_transformed = LLVector3(0, 1, 0) * mat - position; + LLVector3 z_transformed = LLVector3(0, 0, 1) * mat - position; + F32 x_length = x_transformed.normalize(); + F32 y_length = y_transformed.normalize(); + F32 z_length = z_transformed.normalize(); + + max_scale = llmax(llmax(llmax(max_scale, x_length), y_length), z_length); + + mat *= scale_mat; + + for (LLModelLoader::model_instance_list::iterator model_iter = iter->second.begin(); model_iter != iter->second.end();) + { // for each instance with said transform applied + LLModelInstance instance = *model_iter++; + + LLModel* base_model = instance.mModel; + + if (base_model && !requested_name.empty()) + { + base_model->mRequestedLabel = requested_name; + } + + for (int i = LLModel::NUM_LODS - 1; i >= LLModel::LOD_IMPOSTOR; i--) + { + LLModel* lod_model = NULL; + if (!legacyMatching) + { + // Fill LOD slots by finding matching meshes by label with name extensions + // in the appropriate scene for each LOD. This fixes all kinds of issues + // where the indexed method below fails in spectacular fashion. + // If you don't take the time to name your LOD and PHYS meshes + // with the name of their corresponding mesh in the HIGH LOD, + // then the indexed method will be attempted below. + + LLMatrix4 transform; + + std::string name_to_match = instance.mLabel; + llassert(!name_to_match.empty()); + + int extensionLOD; + if (i != LLModel::LOD_PHYSICS || mModel[LLModel::LOD_PHYSICS].empty()) + { + extensionLOD = i; + } + else + { + //Physics can be inherited from other LODs or loaded, so we need to adjust what extension we are searching for + extensionLOD = mPhysicsSearchLOD; + } + + std::string toAdd = getLodSuffix(extensionLOD); + + if (name_to_match.find(toAdd) == -1) + { + name_to_match += toAdd; + } + + FindModel(mScene[i], name_to_match, lod_model, transform); + + if (!lod_model && i != LLModel::LOD_PHYSICS) + { + if (mImporterDebug) + { + std::ostringstream out; + out << "Search of" << name_to_match; + out << " in LOD" << i; + out << " list failed. Searching for alternative among LOD lists."; + LL_INFOS() << out.str() << LL_ENDL; + LLFloaterModelPreview::addStringToLog(out, false); + } + + int searchLOD = (i > LLModel::LOD_HIGH) ? LLModel::LOD_HIGH : i; + while ((searchLOD <= LLModel::LOD_HIGH) && !lod_model) + { + std::string name_to_match = instance.mLabel; + llassert(!name_to_match.empty()); + + std::string toAdd = getLodSuffix(searchLOD); + + if (name_to_match.find(toAdd) == -1) + { + name_to_match += toAdd; + } + + // See if we can find an appropriately named model in LOD 'searchLOD' + // + FindModel(mScene[searchLOD], name_to_match, lod_model, transform); + searchLOD++; + } + } + } + else + { + // Use old method of index-based association + U32 idx = 0; + for (idx = 0; idx < mBaseModel.size(); ++idx) + { + // find reference instance for this model + if (mBaseModel[idx] == base_model) + { + if (mImporterDebug) + { + std::ostringstream out; + out << "Attempting to use model index " << idx; + out << " for LOD" << i; + out << " of " << instance.mLabel; + LL_INFOS() << out.str() << LL_ENDL; + LLFloaterModelPreview::addStringToLog(out, false); + } + break; + } + } + + // If the model list for the current LOD includes that index... + // + if (mModel[i].size() > idx) + { + // Assign that index from the model list for our LOD as the LOD model for this instance + // + lod_model = mModel[i][idx]; + if (mImporterDebug) + { + std::ostringstream out; + out << "Indexed match of model index " << idx << " at LOD " << i << " to model named " << lod_model->mLabel; + LL_INFOS() << out.str() << LL_ENDL; + LLFloaterModelPreview::addStringToLog(out, false); + } + } + else if (mImporterDebug) + { + std::ostringstream out; + out << "List of models does not include index " << idx; + LL_INFOS() << out.str() << LL_ENDL; + LLFloaterModelPreview::addStringToLog(out, false); + } + } + if (mWarnOfUnmatchedPhyicsMeshes && !lod_model && (i == LLModel::LOD_PHYSICS)) + { + // Despite the various strategies above, if we don't now have a physics model, we're going to end up with decomposition. + // That's ok, but might not what they wanted. Use default_physics_shape if found. + std::ostringstream out; + out << "No physics model specified for " << instance.mLabel; + if (mDefaultPhysicsShapeP.notNull()) + { + out << " - using: " << DEFAULT_PHYSICS_MESH_NAME; + lod_model = mDefaultPhysicsShapeP; + } + LL_WARNS() << out.str() << LL_ENDL; + LLFloaterModelPreview::addStringToLog(out, mDefaultPhysicsShapeP.isNull()); // Flash log tab if no default. + } + + if (lod_model) + { + if (mImporterDebug) + { + std::ostringstream out; + if (i == LLModel::LOD_PHYSICS) + { + out << "Assigning collision for " << instance.mLabel << " to match " << lod_model->mLabel; + } + else + { + out << "Assigning LOD" << i << " for " << instance.mLabel << " to found match " << lod_model->mLabel; + } + LL_INFOS() << out.str() << LL_ENDL; + LLFloaterModelPreview::addStringToLog(out, false); + } + instance.mLOD[i] = lod_model; + } + else + { + if (i < LLModel::LOD_HIGH && !lodsReady()) + { + // assign a placeholder from previous LOD until lod generation is complete. + // Note: we might need to assign it regardless of conditions like named search does, to prevent crashes. + instance.mLOD[i] = instance.mLOD[i + 1]; + } + if (mImporterDebug) + { + std::ostringstream out; + out << "List of models does not include " << instance.mLabel; + LL_INFOS() << out.str() << LL_ENDL; + LLFloaterModelPreview::addStringToLog(out, false); + } + } + } + + LLModel* high_lod_model = instance.mLOD[LLModel::LOD_HIGH]; + if (!high_lod_model) + { + LLFloaterModelPreview::addStringToLog("Model " + instance.mLabel + " has no High Lod (LOD3).", true); + load_state = LLModelLoader::ERROR_MATERIALS; + mFMP->childDisable("calculate_btn"); + } + else + { + for (U32 i = 0; i < LLModel::NUM_LODS - 1; i++) + { + int refFaceCnt = 0; + int modelFaceCnt = 0; + llassert(instance.mLOD[i]); + if (instance.mLOD[i] && !instance.mLOD[i]->matchMaterialOrder(high_lod_model, refFaceCnt, modelFaceCnt)) + { + LLFloaterModelPreview::addStringToLog("Model " + instance.mLabel + " has mismatching materials between lods." , true); + load_state = LLModelLoader::ERROR_MATERIALS; + mFMP->childDisable("calculate_btn"); + } + } + LLFloaterModelPreview* fmp = (LLFloaterModelPreview*)mFMP; + bool upload_skinweights = fmp && fmp->childGetValue("upload_skin").asBoolean(); + if (upload_skinweights && high_lod_model->mSkinInfo.mJointNames.size() > 0) + { + LLQuaternion bind_rot = LLSkinningUtil::getUnscaledQuaternion(LLMatrix4(high_lod_model->mSkinInfo.mBindShapeMatrix)); + LLQuaternion identity; + if (!bind_rot.isEqualEps(identity, 0.01)) + { + // Bind shape matrix is not in standard X-forward orientation. + // Might be good idea to only show this once. It can be spammy. + std::ostringstream out; + out << "non-identity bind shape rot. mat is "; + out << high_lod_model->mSkinInfo.mBindShapeMatrix; + out << " bind_rot "; + out << bind_rot; + LL_WARNS() << out.str() << LL_ENDL; + + LLFloaterModelPreview::addStringToLog(out, getLoadState() != LLModelLoader::WARNING_BIND_SHAPE_ORIENTATION); + load_state = LLModelLoader::WARNING_BIND_SHAPE_ORIENTATION; + } + } + } + instance.mTransform = mat; + mUploadData.push_back(instance); + } + } + + for (U32 lod = 0; lod < LLModel::NUM_LODS - 1; lod++) + { + // Search for models that are not included into upload data + // If we found any, that means something we loaded is not a sub-model. + for (U32 model_ind = 0; model_ind < mModel[lod].size(); ++model_ind) + { + bool found_model = false; + for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter) + { + LLModelInstance& instance = *iter; + if (instance.mLOD[lod] == mModel[lod][model_ind]) + { + found_model = true; + break; + } + } + if (!found_model && mModel[lod][model_ind] && !mModel[lod][model_ind]->mSubmodelID) + { + if (mImporterDebug) + { + std::ostringstream out; + out << "Model " << mModel[lod][model_ind]->mLabel << " was not used - mismatching lod models."; + LL_INFOS() << out.str() << LL_ENDL; + LLFloaterModelPreview::addStringToLog(out, true); + } + load_state = LLModelLoader::ERROR_MATERIALS; + mFMP->childDisable("calculate_btn"); + } + } + } + + // Update state for notifications + if (load_state > 0) + { + // encountered issues + setLoadState(load_state); + } + else if (getLoadState() == LLModelLoader::ERROR_MATERIALS + || getLoadState() == LLModelLoader::WARNING_BIND_SHAPE_ORIENTATION) + { + // This is only valid for these two error types because they are + // only used inside rebuildUploadData() and updateStatusMessages() + // updateStatusMessages() is called after rebuildUploadData() + setLoadState(LLModelLoader::DONE); + } + + F32 max_import_scale = (DEFAULT_MAX_PRIM_SCALE - 0.1f) / max_scale; + + F32 max_axis = llmax(mPreviewScale.mV[0], mPreviewScale.mV[1]); + max_axis = llmax(max_axis, mPreviewScale.mV[2]); + max_axis *= 2.f; + + //clamp scale so that total imported model bounding box is smaller than 240m on a side + max_import_scale = llmin(max_import_scale, 240.f / max_axis); + + scale_spinner->setMaxValue(max_import_scale); + + if (max_import_scale < scale) + { + scale_spinner->setValue(max_import_scale); + } + +} + +void LLModelPreview::saveUploadData(bool save_skinweights, bool save_joint_positions, bool lock_scale_if_joint_position) +{ + if (!mLODFile[LLModel::LOD_HIGH].empty()) + { + std::string filename = mLODFile[LLModel::LOD_HIGH]; + std::string slm_filename; + + if (LLModelLoader::getSLMFilename(filename, slm_filename)) + { + saveUploadData(slm_filename, save_skinweights, save_joint_positions, lock_scale_if_joint_position); + } + } +} + +void LLModelPreview::saveUploadData(const std::string& filename, + bool save_skinweights, bool save_joint_positions, bool lock_scale_if_joint_position) +{ + + std::set > meshes; + std::map mesh_binary; + + LLModel::hull empty_hull; + + LLSD data; + + data["version"] = SLM_SUPPORTED_VERSION; + if (!mBaseModel.empty()) + { + data["name"] = mBaseModel[0]->getName(); + } + + S32 mesh_id = 0; + + //build list of unique models and initialize local id + for (U32 i = 0; i < mUploadData.size(); ++i) + { + LLModelInstance& instance = mUploadData[i]; + + if (meshes.find(instance.mModel) == meshes.end()) + { + instance.mModel->mLocalID = mesh_id++; + meshes.insert(instance.mModel); + + std::stringstream str; + LLModel::Decomposition& decomp = + instance.mLOD[LLModel::LOD_PHYSICS].notNull() ? + instance.mLOD[LLModel::LOD_PHYSICS]->mPhysics : + instance.mModel->mPhysics; + + LLModel::writeModel(str, + instance.mLOD[LLModel::LOD_PHYSICS], + instance.mLOD[LLModel::LOD_HIGH], + instance.mLOD[LLModel::LOD_MEDIUM], + instance.mLOD[LLModel::LOD_LOW], + instance.mLOD[LLModel::LOD_IMPOSTOR], + decomp, + save_skinweights, + save_joint_positions, + lock_scale_if_joint_position, + false, true, instance.mModel->mSubmodelID); + + data["mesh"][instance.mModel->mLocalID] = str.str(); + } + + data["instance"][i] = instance.asLLSD(); + } + + llofstream out(filename.c_str(), std::ios_base::out | std::ios_base::binary); + LLSDSerialize::toBinary(data, out); + out.flush(); + out.close(); +} + +void LLModelPreview::clearModel(S32 lod) +{ + if (lod < 0 || lod > LLModel::LOD_PHYSICS) + { + return; + } + + mVertexBuffer[lod].clear(); + mModel[lod].clear(); + mScene[lod].clear(); +} + +void LLModelPreview::getJointAliases(JointMap& joint_map) +{ + // Get all standard skeleton joints from the preview avatar. + LLVOAvatar *av = getPreviewAvatar(); + + //Joint names and aliases come from avatar_skeleton.xml + + joint_map = av->getJointAliases(); + + std::vector cv_names, attach_names; + av->getSortedJointNames(1, cv_names); + av->getSortedJointNames(2, attach_names); + for (std::vector::iterator it = cv_names.begin(); it != cv_names.end(); ++it) + { + joint_map[*it] = *it; + } + for (std::vector::iterator it = attach_names.begin(); it != attach_names.end(); ++it) + { + joint_map[*it] = *it; + } +} + +void LLModelPreview::loadModel(std::string filename, S32 lod, bool force_disable_slm) +{ + assert_main_thread(); + + LLMutexLock lock(this); + + if (lod < LLModel::LOD_IMPOSTOR || lod > LLModel::NUM_LODS - 1) + { + std::ostringstream out; + out << "Invalid level of detail: "; + out << lod; + LL_WARNS() << out.str() << LL_ENDL; + LLFloaterModelPreview::addStringToLog(out, true); + assert(lod >= LLModel::LOD_IMPOSTOR && lod < LLModel::NUM_LODS); + return; + } + + // This triggers if you bring up the file picker and then hit CANCEL. + // Just use the previous model (if any) and ignore that you brought up + // the file picker. + + if (filename.empty()) + { + if (mBaseModel.empty()) + { + // this is the initial file picking. Close the whole floater + // if we don't have a base model to show for high LOD. + mFMP->closeFloater(false); + } + mLoading = false; + return; + } + + if (mModelLoader) + { + LL_WARNS() << "Incompleted model load operation pending." << LL_ENDL; + return; + } + + mLODFile[lod] = filename; + + std::map joint_alias_map; + getJointAliases(joint_alias_map); + + // three possible file extensions, .dae .gltf .glb + // check for .dae and if not then assume one of the .gl?? + std::string filename_lc(filename); + LLStringUtil::toLower(filename_lc); + if (std::string::npos != filename_lc.rfind(".dae")) + { + mModelLoader = new LLDAELoader( + filename, + lod, + &LLModelPreview::loadedCallback, + &LLModelPreview::lookupJointByName, + &LLModelPreview::loadTextures, + &LLModelPreview::stateChangedCallback, + this, + mJointTransformMap, + mJointsFromNode, + joint_alias_map, + LLSkinningUtil::getMaxJointCount(), + gSavedSettings.getU32("ImporterModelLimit"), + gSavedSettings.getBOOL("ImporterPreprocessDAE")); + } + else + { + mModelLoader = new LLGLTFLoader( + filename, + lod, + &LLModelPreview::loadedCallback, + &LLModelPreview::lookupJointByName, + &LLModelPreview::loadTextures, + &LLModelPreview::stateChangedCallback, + this, + mJointTransformMap, + mJointsFromNode, + joint_alias_map, + LLSkinningUtil::getMaxJointCount(), + gSavedSettings.getU32("ImporterModelLimit")); + } + + if (force_disable_slm) + { + mModelLoader->mTrySLM = false; + } + else + { + // For MAINT-6647, we have set force_disable_slm to true, + // which means this code path will never be taken. Trying to + // re-use SLM files has never worked properly; in particular, + // it tends to force the UI into strange checkbox options + // which cannot be altered. + + //only try to load from slm if viewer is configured to do so and this is the + //initial model load (not an LoD or physics shape) + mModelLoader->mTrySLM = gSavedSettings.getBOOL("MeshImportUseSLM") && mUploadData.empty(); + } + mModelLoader->start(); + + mFMP->childSetTextArg("status", "[STATUS]", mFMP->getString("status_reading_file")); + + setPreviewLOD(lod); + + if (getLoadState() >= LLModelLoader::ERROR_PARSING) + { + mFMP->childDisable("ok_btn"); + mFMP->childDisable("calculate_btn"); + } + + if (lod == mPreviewLOD) + { + mFMP->childSetValue("lod_file_" + lod_name[lod], mLODFile[lod]); + } + else if (lod == LLModel::LOD_PHYSICS) + { + mFMP->childSetValue("physics_file", mLODFile[lod]); + } + + mFMP->openFloater(); +} + +void LLModelPreview::setPhysicsFromLOD(S32 lod) +{ + assert_main_thread(); + + if (lod >= 0 && lod <= 3) + { + mPhysicsSearchLOD = lod; + mModel[LLModel::LOD_PHYSICS] = mModel[lod]; + mScene[LLModel::LOD_PHYSICS] = mScene[lod]; + mLODFile[LLModel::LOD_PHYSICS].clear(); + mFMP->childSetValue("physics_file", mLODFile[LLModel::LOD_PHYSICS]); + mVertexBuffer[LLModel::LOD_PHYSICS].clear(); + rebuildUploadData(); + refresh(); + updateStatusMessages(); + } +} + +void LLModelPreview::clearIncompatible(S32 lod) +{ + //Don't discard models if specified model is the physic rep + if (lod == LLModel::LOD_PHYSICS) + { + return; + } + + // at this point we don't care about sub-models, + // different amount of sub-models means face count mismatch, not incompatibility + U32 lod_size = countRootModels(mModel[lod]); + bool replaced_base_model = (lod == LLModel::LOD_HIGH); + for (U32 i = 0; i <= LLModel::LOD_HIGH; i++) + { + // Clear out any entries that aren't compatible with this model + if (i != lod) + { + if (countRootModels(mModel[i]) != lod_size) + { + mModel[i].clear(); + mScene[i].clear(); + mVertexBuffer[i].clear(); + + if (i == LLModel::LOD_HIGH) + { + mBaseModel = mModel[lod]; + mBaseScene = mScene[lod]; + mVertexBuffer[5].clear(); + replaced_base_model = true; + } + } + } + } + + if (replaced_base_model && !mGenLOD) + { + // In case base was replaced, we might need to restart generation + + // Check if already started + bool subscribe_for_generation = mLodsQuery.empty(); + + // Remove previously scheduled work + mLodsQuery.clear(); + + LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance; + if (!fmp) + return; + + // Schedule new work + for (S32 i = LLModel::LOD_HIGH; i >= 0; --i) + { + if (mModel[i].empty()) + { + // Base model was replaced, regenerate this lod if applicable + LLComboBox* lod_combo = mFMP->findChild("lod_source_" + lod_name[i]); + if (!lod_combo) return; + + S32 lod_mode = lod_combo->getCurrentIndex(); + if (lod_mode != LOD_FROM_FILE) + { + mLodsQuery.push_back(i); + } + } + } + + // Subscribe if we have pending work and not subscribed yet + if (!mLodsQuery.empty() && subscribe_for_generation) + { + doOnIdleRepeating(lodQueryCallback); + } + } +} + +void LLModelPreview::loadModelCallback(S32 loaded_lod) +{ + assert_main_thread(); + + LLMutexLock lock(this); + if (!mModelLoader) + { + mLoading = false; + return; + } + if (getLoadState() >= LLModelLoader::ERROR_PARSING) + { + mLoading = false; + mModelLoader = NULL; + mLodsWithParsingError.push_back(loaded_lod); + return; + } + + mLodsWithParsingError.erase(std::remove(mLodsWithParsingError.begin(), mLodsWithParsingError.end(), loaded_lod), mLodsWithParsingError.end()); + if (mLodsWithParsingError.empty()) + { + mFMP->childEnable("calculate_btn"); + } + + // Copy determinations about rig so UI will reflect them + // + setRigValidForJointPositionUpload(mModelLoader->isRigValidForJointPositionUpload()); + setLegacyRigFlags(mModelLoader->getLegacyRigFlags()); + + mModelLoader->loadTextures(); + + if (loaded_lod == -1) + { //populate all LoDs from model loader scene + mBaseModel.clear(); + mBaseScene.clear(); + + bool skin_weights = false; + bool joint_overrides = false; + bool lock_scale_if_joint_position = false; + + for (S32 lod = 0; lod < LLModel::NUM_LODS; ++lod) + { //for each LoD + + //clear scene and model info + mScene[lod].clear(); + mModel[lod].clear(); + mVertexBuffer[lod].clear(); + + if (mModelLoader->mScene.begin()->second[0].mLOD[lod].notNull()) + { //if this LoD exists in the loaded scene + + //copy scene to current LoD + mScene[lod] = mModelLoader->mScene; + + //touch up copied scene to look like current LoD + for (LLModelLoader::scene::iterator iter = mScene[lod].begin(); iter != mScene[lod].end(); ++iter) + { + LLModelLoader::model_instance_list& list = iter->second; + + for (LLModelLoader::model_instance_list::iterator list_iter = list.begin(); list_iter != list.end(); ++list_iter) + { + //override displayed model with current LoD + list_iter->mModel = list_iter->mLOD[lod]; + + if (!list_iter->mModel) + { + continue; + } + + //add current model to current LoD's model list (LLModel::mLocalID makes a good vector index) + S32 idx = list_iter->mModel->mLocalID; + + if (mModel[lod].size() <= idx) + { //stretch model list to fit model at given index + mModel[lod].resize(idx + 1); + } + + mModel[lod][idx] = list_iter->mModel; + if (!list_iter->mModel->mSkinWeights.empty()) + { + skin_weights = true; + + if (!list_iter->mModel->mSkinInfo.mAlternateBindMatrix.empty()) + { + joint_overrides = true; + } + if (list_iter->mModel->mSkinInfo.mLockScaleIfJointPosition) + { + lock_scale_if_joint_position = true; + } + } + } + } + } + } + + if (mFMP) + { + LLFloaterModelPreview* fmp = (LLFloaterModelPreview*)mFMP; + + if (skin_weights) + { //enable uploading/previewing of skin weights if present in .slm file + fmp->enableViewOption("show_skin_weight"); + mViewOption["show_skin_weight"] = true; + fmp->childSetValue("upload_skin", true); + } + + if (joint_overrides) + { + fmp->enableViewOption("show_joint_overrides"); + mViewOption["show_joint_overrides"] = true; + fmp->enableViewOption("show_joint_positions"); + mViewOption["show_joint_positions"] = true; + fmp->childSetValue("upload_joints", true); + } + else + { + fmp->clearAvatarTab(); + } + + if (lock_scale_if_joint_position) + { + fmp->enableViewOption("lock_scale_if_joint_position"); + mViewOption["lock_scale_if_joint_position"] = true; + fmp->childSetValue("lock_scale_if_joint_position", true); + } + } + + //copy high lod to base scene for LoD generation + mBaseScene = mScene[LLModel::LOD_HIGH]; + mBaseModel = mModel[LLModel::LOD_HIGH]; + + mDirty = true; + resetPreviewTarget(); + } + else + { //only replace given LoD + mModel[loaded_lod] = mModelLoader->mModelList; + mScene[loaded_lod] = mModelLoader->mScene; + + // Duplicate the model if it is an internal bounding box model + if (loaded_lod == LLModel::LOD_PHYSICS && + mBaseModel.size() > 1 && // This makes sense for multiple models only + mModelLoader->mModelList.size() == 1 && // Just on the off-chance + mModelLoader->mScene.size() == 1 && // Just on the off-chance + std::filesystem::path(mModelLoader->mFilename).filename() == "cube.dae") + { + // Create a copy of the just loaded model for each model in mBaseModel + const LLModel* origin = mModelLoader->mModelList.front(); + const LLModelInstance& mi = mModelLoader->mScene.begin()->second.front(); + for (U32 i = 1; i < mBaseModel.size(); ++i) + { + LLPointer copy(new LLModel(origin->getParams(), origin->getDetail())); + copy->mLabel = origin->mLabel; + copy->copyVolumeFaces(origin); + copy->mPosition = origin->mPosition; + copy->mMaterialList = origin->mMaterialList; + mModel[loaded_lod].push_back(copy); + mScene[loaded_lod][mi.mTransform].push_back(LLModelInstance(copy, copy->mLabel, mi.mTransform, mi.mMaterial)); + } + } + + mVertexBuffer[loaded_lod].clear(); + + setPreviewLOD(loaded_lod); + + if (loaded_lod == LLModel::LOD_HIGH) + { //save a copy of the highest LOD for automatic LOD manipulation + if (mBaseModel.empty()) + { //first time we've loaded a model, auto-gen LoD + mGenLOD = true; + } + + mBaseModel = mModel[loaded_lod]; + + mBaseScene = mScene[loaded_lod]; + mVertexBuffer[5].clear(); + } + else + { + if (loaded_lod == LLModel::LOD_PHYSICS) + { // Explicitly loading physics. See if there is a default mesh. + LLMatrix4 ignored_transform; // Each mesh that uses this will supply their own. + LLModel* out_model = nullptr; + FindModel(mScene[loaded_lod], DEFAULT_PHYSICS_MESH_NAME + getLodSuffix(loaded_lod), out_model, ignored_transform); + mDefaultPhysicsShapeP = out_model; + mWarnOfUnmatchedPhyicsMeshes = true; + } + bool legacyMatching = gSavedSettings.getBOOL("ImporterLegacyMatching"); + if (!legacyMatching) + { + if (!mBaseModel.empty()) + { + bool name_based = false; + bool has_submodels = false; + for (U32 idx = 0; idx < mBaseModel.size(); ++idx) + { + if (mBaseModel[idx]->mSubmodelID) + { // don't do index-based renaming when the base model has submodels + has_submodels = true; + if (mImporterDebug) + { + std::ostringstream out; + out << "High LOD has submodels"; + LL_INFOS() << out.str() << LL_ENDL; + LLFloaterModelPreview::addStringToLog(out, false); + } + break; + } + } + + for (U32 idx = 0; idx < mModel[loaded_lod].size(); ++idx) + { + std::string loaded_name = stripSuffix(mModel[loaded_lod][idx]->mLabel); + + LLModel* found_model = NULL; + LLMatrix4 transform; + FindModel(mBaseScene, loaded_name, found_model, transform); + if (found_model) + { // don't rename correctly named models (even if they are placed in a wrong order) + name_based = true; + } + + if (mModel[loaded_lod][idx]->mSubmodelID) + { // don't rename the models when loaded LOD model has submodels + has_submodels = true; + } + } + + if (mImporterDebug) + { + std::ostringstream out; + out << "Loaded LOD " << loaded_lod << ": correct names" << (name_based ? "" : "NOT ") << "found; submodels " << (has_submodels ? "" : "NOT ") << "found"; + LL_INFOS() << out.str() << LL_ENDL; + LLFloaterModelPreview::addStringToLog(out, false); + } + + if (!name_based && !has_submodels) + { // replace the name of the model loaded for any non-HIGH LOD to match the others (MAINT-5601) + // this actually works like "ImporterLegacyMatching" for this particular LOD + for (U32 idx = 0; idx < mModel[loaded_lod].size() && idx < mBaseModel.size(); ++idx) + { + std::string name = mBaseModel[idx]->mLabel; + std::string loaded_name = stripSuffix(mModel[loaded_lod][idx]->mLabel); + + if (loaded_name != name) + { + name += getLodSuffix(loaded_lod); + + if (mImporterDebug) + { + std::ostringstream out; + out << "Loded model name " << mModel[loaded_lod][idx]->mLabel; + out << " for LOD " << loaded_lod; + out << " doesn't match the base model. Renaming to " << name; + LL_WARNS() << out.str() << LL_ENDL; + LLFloaterModelPreview::addStringToLog(out, false); + } + mModel[loaded_lod][idx]->mLabel = name; + // Rename the correspondent instance as well + [&]() + { + for (auto& p : mScene[loaded_lod]) + for (auto& i : p.second) + if (i.mModel == mModel[loaded_lod][idx]) + { + i.mLabel = name; + return; + } + }(); + } + } + } + } + } + } + + clearIncompatible(loaded_lod); + + mDirty = true; + + if (loaded_lod == LLModel::LOD_HIGH) + { + resetPreviewTarget(); + } + } + + mLoading = false; + if (mFMP) + { + if (!mBaseModel.empty()) + { + const std::string& model_name = mBaseModel[0]->getName(); + LLLineEditor* description_form = mFMP->getChild("description_form"); + if (description_form->getText().empty()) + { + description_form->setText(model_name); + } + // Add info to log that loading is complete (purpose: separator between loading and other logs) + LLSD args; + args["MODEL_NAME"] = model_name; // Teoretically shouldn't be empty, but might be better idea to add filename here + LLFloaterModelPreview::addStringToLog("ModelLoaded", args, false, loaded_lod); + } + } + refresh(); + + mModelLoadedSignal(); + + mModelLoader = NULL; +} + +void LLModelPreview::resetPreviewTarget() +{ + if (mModelLoader) + { + mPreviewTarget = (mModelLoader->mExtents[0] + mModelLoader->mExtents[1]) * 0.5f; + mPreviewScale = (mModelLoader->mExtents[1] - mModelLoader->mExtents[0]) * 0.5f; + } + + setPreviewTarget(mPreviewScale.magVec()*10.f); +} + +void LLModelPreview::generateNormals() +{ + assert_main_thread(); + + S32 which_lod = mPreviewLOD; + + if (which_lod > 4 || which_lod < 0 || + mModel[which_lod].empty()) + { + return; + } + + F32 angle_cutoff = mFMP->childGetValue("crease_angle").asReal(); + + mRequestedCreaseAngle[which_lod] = angle_cutoff; + + angle_cutoff *= DEG_TO_RAD; + + if (which_lod == 3 && !mBaseModel.empty()) + { + if (mBaseModelFacesCopy.empty()) + { + mBaseModelFacesCopy.reserve(mBaseModel.size()); + for (LLModelLoader::model_list::iterator it = mBaseModel.begin(), itE = mBaseModel.end(); it != itE; ++it) + { + v_LLVolumeFace_t faces; + (*it)->copyFacesTo(faces); + mBaseModelFacesCopy.push_back(faces); + } + } + + for (LLModelLoader::model_list::iterator it = mBaseModel.begin(), itE = mBaseModel.end(); it != itE; ++it) + { + (*it)->generateNormals(angle_cutoff); + } + + mVertexBuffer[5].clear(); + } + + bool perform_copy = mModelFacesCopy[which_lod].empty(); + if (perform_copy) { + mModelFacesCopy[which_lod].reserve(mModel[which_lod].size()); + } + + for (LLModelLoader::model_list::iterator it = mModel[which_lod].begin(), itE = mModel[which_lod].end(); it != itE; ++it) + { + if (perform_copy) + { + v_LLVolumeFace_t faces; + (*it)->copyFacesTo(faces); + mModelFacesCopy[which_lod].push_back(faces); + } + + (*it)->generateNormals(angle_cutoff); + } + + mVertexBuffer[which_lod].clear(); + refresh(); + updateStatusMessages(); +} + +void LLModelPreview::restoreNormals() +{ + S32 which_lod = mPreviewLOD; + + if (which_lod > 4 || which_lod < 0 || + mModel[which_lod].empty()) + { + return; + } + + if (!mBaseModelFacesCopy.empty()) + { + llassert(mBaseModelFacesCopy.size() == mBaseModel.size()); + + vv_LLVolumeFace_t::const_iterator itF = mBaseModelFacesCopy.begin(); + for (LLModelLoader::model_list::iterator it = mBaseModel.begin(), itE = mBaseModel.end(); it != itE; ++it, ++itF) + { + (*it)->copyFacesFrom((*itF)); + } + + mBaseModelFacesCopy.clear(); + } + + if (!mModelFacesCopy[which_lod].empty()) + { + vv_LLVolumeFace_t::const_iterator itF = mModelFacesCopy[which_lod].begin(); + for (LLModelLoader::model_list::iterator it = mModel[which_lod].begin(), itE = mModel[which_lod].end(); it != itE; ++it, ++itF) + { + (*it)->copyFacesFrom((*itF)); + } + + mModelFacesCopy[which_lod].clear(); + } + + mVertexBuffer[which_lod].clear(); + refresh(); + updateStatusMessages(); +} + +// Runs per object, but likely it is a better way to run per model+submodels +// returns a ratio of base model indices to resulting indices +// returns -1 in case of failure +F32 LLModelPreview::genMeshOptimizerPerModel(LLModel *base_model, LLModel *target_model, F32 indices_decimator, F32 error_threshold, eSimplificationMode simplification_mode) +{ + // I. Weld faces together + // Figure out buffer size + S32 size_indices = 0; + S32 size_vertices = 0; + + for (U32 face_idx = 0; face_idx < base_model->getNumVolumeFaces(); ++face_idx) + { + const LLVolumeFace &face = base_model->getVolumeFace(face_idx); + size_indices += face.mNumIndices; + size_vertices += face.mNumVertices; + } + + if (size_indices < 3) + { + return -1; + } + + // Allocate buffers, note that we are using U32 buffer instead of U16 + U32* combined_indices = (U32*)ll_aligned_malloc_32(size_indices * sizeof(U32)); + U32* output_indices = (U32*)ll_aligned_malloc_32(size_indices * sizeof(U32)); + + // extra space for normals and text coords + S32 tc_bytes_size = ((size_vertices * sizeof(LLVector2)) + 0xF) & ~0xF; + LLVector4a* combined_positions = (LLVector4a*)ll_aligned_malloc<64>(sizeof(LLVector4a) * 3 * size_vertices + tc_bytes_size); + LLVector4a* combined_normals = combined_positions + size_vertices; + LLVector2* combined_tex_coords = (LLVector2*)(combined_normals + size_vertices); + + // copy indices and vertices into new buffers + S32 combined_positions_shift = 0; + S32 indices_idx_shift = 0; + S32 combined_indices_shift = 0; + for (U32 face_idx = 0; face_idx < base_model->getNumVolumeFaces(); ++face_idx) + { + const LLVolumeFace &face = base_model->getVolumeFace(face_idx); + + // Vertices + S32 copy_bytes = face.mNumVertices * sizeof(LLVector4a); + LLVector4a::memcpyNonAliased16((F32*)(combined_positions + combined_positions_shift), (F32*)face.mPositions, copy_bytes); + + // Normals + LLVector4a::memcpyNonAliased16((F32*)(combined_normals + combined_positions_shift), (F32*)face.mNormals, copy_bytes); + + // Tex coords + copy_bytes = face.mNumVertices * sizeof(LLVector2); + memcpy((void*)(combined_tex_coords + combined_positions_shift), (void*)face.mTexCoords, copy_bytes); + + combined_positions_shift += face.mNumVertices; + + // Indices + // Sadly can't do dumb memcpy for indices, need to adjust each value + for (S32 i = 0; i < face.mNumIndices; ++i) + { + U16 idx = face.mIndices[i]; + + combined_indices[combined_indices_shift] = idx + indices_idx_shift; + combined_indices_shift++; + } + indices_idx_shift += face.mNumVertices; + } + + // II. Generate a shadow buffer if nessesary. + // Welds together vertices if possible + + U32* shadow_indices = NULL; + // if MESH_OPTIMIZER_FULL, just leave as is, since generateShadowIndexBufferU32 + // won't do anything new, model was remaped on a per face basis. + // Similar for MESH_OPTIMIZER_NO_TOPOLOGY, it's pointless + // since 'simplifySloppy' ignores all topology, including normals and uvs. + // Note: simplifySloppy can affect UVs significantly. + if (simplification_mode == MESH_OPTIMIZER_NO_NORMALS) + { + // strip normals, reflections should restore relatively correctly + shadow_indices = (U32*)ll_aligned_malloc_32(size_indices * sizeof(U32)); + LLMeshOptimizer::generateShadowIndexBufferU32(shadow_indices, combined_indices, size_indices, combined_positions, NULL, combined_tex_coords, size_vertices); + } + if (simplification_mode == MESH_OPTIMIZER_NO_UVS) + { + // strip uvs, can heavily affect textures + shadow_indices = (U32*)ll_aligned_malloc_32(size_indices * sizeof(U32)); + LLMeshOptimizer::generateShadowIndexBufferU32(shadow_indices, combined_indices, size_indices, combined_positions, NULL, NULL, size_vertices); + } + + U32* source_indices = NULL; + if (shadow_indices) + { + source_indices = shadow_indices; + } + else + { + source_indices = combined_indices; + } + + // III. Simplify + S32 target_indices = 0; + F32 result_error = 0; // how far from original the model is, 1 == 100% + S32 size_new_indices = 0; + + if (indices_decimator > 0) + { + target_indices = llclamp(llfloor(size_indices / indices_decimator), 3, (S32)size_indices); // leave at least one triangle + } + else // indices_decimator can be zero for error_threshold based calculations + { + target_indices = 3; + } + + size_new_indices = LLMeshOptimizer::simplifyU32( + output_indices, + source_indices, + size_indices, + combined_positions, + size_vertices, + LLVertexBuffer::sTypeSize[LLVertexBuffer::TYPE_VERTEX], + target_indices, + error_threshold, + simplification_mode == MESH_OPTIMIZER_NO_TOPOLOGY, + &result_error); + + if (result_error < 0) + { + LL_WARNS() << "Negative result error from meshoptimizer for model " << target_model->mLabel + << " target Indices: " << target_indices + << " new Indices: " << size_new_indices + << " original count: " << size_indices << LL_ENDL; + } + + // free unused buffers + ll_aligned_free_32(combined_indices); + ll_aligned_free_32(shadow_indices); + combined_indices = NULL; + shadow_indices = NULL; + + if (size_new_indices < 3) + { + // Model should have at least one visible triangle + ll_aligned_free<64>(combined_positions); + ll_aligned_free_32(output_indices); + + return -1; + } + + // IV. Repack back into individual faces + + LLVector4a* buffer_positions = (LLVector4a*)ll_aligned_malloc<64>(sizeof(LLVector4a) * 3 * size_vertices + tc_bytes_size); + LLVector4a* buffer_normals = buffer_positions + size_vertices; + LLVector2* buffer_tex_coords = (LLVector2*)(buffer_normals + size_vertices); + S32 buffer_idx_size = (size_indices * sizeof(U16) + 0xF) & ~0xF; + U16* buffer_indices = (U16*)ll_aligned_malloc_16(buffer_idx_size); + S32* old_to_new_positions_map = new S32[size_vertices]; + + S32 buf_positions_copied = 0; + S32 buf_indices_copied = 0; + indices_idx_shift = 0; + S32 valid_faces = 0; + + // Crude method to copy indices back into face + for (U32 face_idx = 0; face_idx < base_model->getNumVolumeFaces(); ++face_idx) + { + const LLVolumeFace &face = base_model->getVolumeFace(face_idx); + + // reset data for new run + buf_positions_copied = 0; + buf_indices_copied = 0; + bool copy_triangle = false; + S32 range = indices_idx_shift + face.mNumVertices; + + for (S32 i = 0; i < size_vertices; i++) + { + old_to_new_positions_map[i] = -1; + } + + // Copy relevant indices and vertices + for (S32 i = 0; i < size_new_indices; ++i) + { + U32 idx = output_indices[i]; + + if ((i % 3) == 0) + { + copy_triangle = idx >= indices_idx_shift && idx < range; + } + + if (copy_triangle) + { + if (old_to_new_positions_map[idx] == -1) + { + // New position, need to copy it + // Validate size + if (buf_positions_copied >= U16_MAX) + { + // Normally this shouldn't happen since the whole point is to reduce amount of vertices + // but it might happen if user tries to run optimization with too large triangle or error value + // so fallback to 'per face' mode or verify requested limits and copy base model as is. + LL_WARNS() << "Over triangle limit. Failed to optimize in 'per object' mode, falling back to per face variant for" + << " model " << target_model->mLabel + << " target Indices: " << target_indices + << " new Indices: " << size_new_indices + << " original count: " << size_indices + << " error treshold: " << error_threshold + << LL_ENDL; + + // U16 vertices overflow shouldn't happen, but just in case + size_new_indices = 0; + valid_faces = 0; + for (U32 face_idx = 0; face_idx < base_model->getNumVolumeFaces(); ++face_idx) + { + genMeshOptimizerPerFace(base_model, target_model, face_idx, indices_decimator, error_threshold, simplification_mode); + const LLVolumeFace &face = target_model->getVolumeFace(face_idx); + size_new_indices += face.mNumIndices; + if (face.mNumIndices >= 3) + { + valid_faces++; + } + } + if (valid_faces) + { + return (F32)size_indices / (F32)size_new_indices; + } + else + { + return -1; + } + } + + // Copy vertice, normals, tcs + buffer_positions[buf_positions_copied] = combined_positions[idx]; + buffer_normals[buf_positions_copied] = combined_normals[idx]; + buffer_tex_coords[buf_positions_copied] = combined_tex_coords[idx]; + + old_to_new_positions_map[idx] = buf_positions_copied; + + buffer_indices[buf_indices_copied] = (U16)buf_positions_copied; + buf_positions_copied++; + } + else + { + // existing position + buffer_indices[buf_indices_copied] = (U16)old_to_new_positions_map[idx]; + } + buf_indices_copied++; + } + } + + if (buf_positions_copied >= U16_MAX) + { + break; + } + + LLVolumeFace &new_face = target_model->getVolumeFace(face_idx); + //new_face = face; //temp + + if (buf_indices_copied < 3) + { + // face was optimized away + new_face.resizeIndices(3); + new_face.resizeVertices(1); + memset(new_face.mIndices, 0, sizeof(U16) * 3); + new_face.mPositions[0].clear(); // set first vertice to 0 + new_face.mNormals[0].clear(); + new_face.mTexCoords[0].setZero(); + } + else + { + new_face.resizeIndices(buf_indices_copied); + new_face.resizeVertices(buf_positions_copied); + new_face.allocateTangents(buf_positions_copied); + S32 idx_size = (buf_indices_copied * sizeof(U16) + 0xF) & ~0xF; + LLVector4a::memcpyNonAliased16((F32*)new_face.mIndices, (F32*)buffer_indices, idx_size); + + LLVector4a::memcpyNonAliased16((F32*)new_face.mPositions, (F32*)buffer_positions, buf_positions_copied * sizeof(LLVector4a)); + LLVector4a::memcpyNonAliased16((F32*)new_face.mNormals, (F32*)buffer_normals, buf_positions_copied * sizeof(LLVector4a)); + + U32 tex_size = (buf_positions_copied * sizeof(LLVector2) + 0xF)&~0xF; + LLVector4a::memcpyNonAliased16((F32*)new_face.mTexCoords, (F32*)buffer_tex_coords, tex_size); + + valid_faces++; + } + + indices_idx_shift += face.mNumVertices; + } + + delete[]old_to_new_positions_map; + ll_aligned_free<64>(combined_positions); + ll_aligned_free<64>(buffer_positions); + ll_aligned_free_32(output_indices); + ll_aligned_free_16(buffer_indices); + + if (size_new_indices < 3 || valid_faces == 0) + { + // Model should have at least one visible triangle + return -1; + } + + return (F32)size_indices / (F32)size_new_indices; +} + +F32 LLModelPreview::genMeshOptimizerPerFace(LLModel *base_model, LLModel *target_model, U32 face_idx, F32 indices_decimator, F32 error_threshold, eSimplificationMode simplification_mode) +{ + const LLVolumeFace &face = base_model->getVolumeFace(face_idx); + S32 size_indices = face.mNumIndices; + if (size_indices < 3) + { + return -1; + } + + S32 size = (size_indices * sizeof(U16) + 0xF) & ~0xF; + U16* output_indices = (U16*)ll_aligned_malloc_16(size); + + U16* shadow_indices = NULL; + // if MESH_OPTIMIZER_FULL, just leave as is, since generateShadowIndexBufferU32 + // won't do anything new, model was remaped on a per face basis. + // Similar for MESH_OPTIMIZER_NO_TOPOLOGY, it's pointless + // since 'simplifySloppy' ignores all topology, including normals and uvs. + if (simplification_mode == MESH_OPTIMIZER_NO_NORMALS) + { + U16* shadow_indices = (U16*)ll_aligned_malloc_16(size); + LLMeshOptimizer::generateShadowIndexBufferU16(shadow_indices, face.mIndices, size_indices, face.mPositions, NULL, face.mTexCoords, face.mNumVertices); + } + if (simplification_mode == MESH_OPTIMIZER_NO_UVS) + { + U16* shadow_indices = (U16*)ll_aligned_malloc_16(size); + LLMeshOptimizer::generateShadowIndexBufferU16(shadow_indices, face.mIndices, size_indices, face.mPositions, NULL, NULL, face.mNumVertices); + } + // Don't run ShadowIndexBuffer for MESH_OPTIMIZER_NO_TOPOLOGY, it's pointless + + U16* source_indices = NULL; + if (shadow_indices) + { + source_indices = shadow_indices; + } + else + { + source_indices = face.mIndices; + } + + S32 target_indices = 0; + F32 result_error = 0; // how far from original the model is, 1 == 100% + S32 size_new_indices = 0; + + if (indices_decimator > 0) + { + target_indices = llclamp(llfloor(size_indices / indices_decimator), 3, (S32)size_indices); // leave at least one triangle + } + else + { + target_indices = 3; + } + + size_new_indices = LLMeshOptimizer::simplify( + output_indices, + source_indices, + size_indices, + face.mPositions, + face.mNumVertices, + LLVertexBuffer::sTypeSize[LLVertexBuffer::TYPE_VERTEX], + target_indices, + error_threshold, + simplification_mode == MESH_OPTIMIZER_NO_TOPOLOGY, + &result_error); + + if (result_error < 0) + { + LL_WARNS() << "Negative result error from meshoptimizer for face " << face_idx + << " of model " << target_model->mLabel + << " target Indices: " << target_indices + << " new Indices: " << size_new_indices + << " original count: " << size_indices + << " error treshold: " << error_threshold + << LL_ENDL; + } + + LLVolumeFace &new_face = target_model->getVolumeFace(face_idx); + + // Copy old values + new_face = face; + + if (size_new_indices < 3) + { + if (simplification_mode != MESH_OPTIMIZER_NO_TOPOLOGY) + { + // meshopt_optimizeSloppy() can optimize triangles away even if target_indices is > 2, + // but optimize() isn't supposed to + LL_INFOS() << "No indices generated by meshoptimizer for face " << face_idx + << " of model " << target_model->mLabel + << " target Indices: " << target_indices + << " original count: " << size_indices + << " error treshold: " << error_threshold + << LL_ENDL; + } + + // Face got optimized away + // Generate empty triangle + new_face.resizeIndices(3); + new_face.resizeVertices(1); + memset(new_face.mIndices, 0, sizeof(U16) * 3); + new_face.mPositions[0].clear(); // set first vertice to 0 + new_face.mNormals[0].clear(); + new_face.mTexCoords[0].setZero(); + } + else + { + // Assign new values + new_face.resizeIndices(size_new_indices); // will wipe out mIndices, so new_face can't substitute output + S32 idx_size = (size_new_indices * sizeof(U16) + 0xF) & ~0xF; + LLVector4a::memcpyNonAliased16((F32*)new_face.mIndices, (F32*)output_indices, idx_size); + + // Clear unused values + new_face.optimize(); + } + + ll_aligned_free_16(output_indices); + ll_aligned_free_16(shadow_indices); + + if (size_new_indices < 3) + { + // At least one triangle is needed + return -1; + } + + return (F32)size_indices / (F32)size_new_indices; +} + +void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 decimation, bool enforce_tri_limit) +{ + LL_INFOS() << "Generating lod " << which_lod << " using meshoptimizer" << LL_ENDL; + // Allow LoD from -1 to LLModel::LOD_PHYSICS + if (which_lod < -1 || which_lod > LLModel::NUM_LODS - 1) + { + std::ostringstream out; + out << "Invalid level of detail: " << which_lod; + LL_WARNS() << out.str() << LL_ENDL; + LLFloaterModelPreview::addStringToLog(out, false); + llassert(which_lod >= -1 && which_lod < LLModel::NUM_LODS); + return; + } + + if (mBaseModel.empty()) + { + return; + } + + //get the triangle count for all base models + S32 base_triangle_count = 0; + for (S32 i = 0; i < mBaseModel.size(); ++i) + { + base_triangle_count += mBaseModel[i]->getNumTriangles(); + } + + // Urgh... + // TODO: add interface to mFMP to get error treshold or let mFMP write one into LLModelPreview + // We should not be accesing views from other class! + U32 lod_mode = LIMIT_TRIANGLES; + F32 indices_decimator = 0; + F32 triangle_limit = 0; + F32 lod_error_threshold = 1; //100% + + // If requesting a single lod + if (which_lod > -1 && which_lod < NUM_LOD) + { + LLCtrlSelectionInterface* iface = mFMP->childGetSelectionInterface("lod_mode_" + lod_name[which_lod]); + if (iface) + { + lod_mode = iface->getFirstSelectedIndex(); + } + + if (lod_mode == LIMIT_TRIANGLES) + { + if (!enforce_tri_limit) + { + triangle_limit = base_triangle_count; + // reset to default value for this lod + F32 pw = pow((F32)decimation, (F32)(LLModel::LOD_HIGH - which_lod)); + + triangle_limit /= pw; //indices_ratio can be 1/pw + } + else + { + + // UI spacifies limit for all models of single lod + triangle_limit = mFMP->childGetValue("lod_triangle_limit_" + lod_name[which_lod]).asInteger(); + + } + // meshoptimizer doesn't use triangle limit, it uses indices limit, so convert it to aproximate ratio + // triangle_limit can be 0. + indices_decimator = (F32)base_triangle_count / llmax(triangle_limit, 1.f); + } + else + { + // UI shows 0 to 100%, but meshoptimizer works with 0 to 1 + lod_error_threshold = mFMP->childGetValue("lod_error_threshold_" + lod_name[which_lod]).asReal() / 100.f; + } + } + else + { + // we are genrating all lods and each lod will get own indices_decimator + indices_decimator = 1; + triangle_limit = base_triangle_count; + } + + mMaxTriangleLimit = base_triangle_count; + + // Build models + + S32 start = LLModel::LOD_HIGH; + S32 end = 0; + + if (which_lod != -1) + { + start = which_lod; + end = which_lod; + } + + for (S32 lod = start; lod >= end; --lod) + { + if (which_lod == -1) + { + // we are genrating all lods and each lod gets own indices_ratio + if (lod < start) + { + indices_decimator *= decimation; + triangle_limit /= decimation; + } + } + + mRequestedTriangleCount[lod] = triangle_limit; + mRequestedErrorThreshold[lod] = lod_error_threshold * 100; + mRequestedLoDMode[lod] = lod_mode; + + mModel[lod].clear(); + mModel[lod].resize(mBaseModel.size()); + mVertexBuffer[lod].clear(); + + + for (U32 mdl_idx = 0; mdl_idx < mBaseModel.size(); ++mdl_idx) + { + LLModel* base = mBaseModel[mdl_idx]; + + LLVolumeParams volume_params; + volume_params.setType(LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE); + mModel[lod][mdl_idx] = new LLModel(volume_params, 0.f); + + std::string name = base->mLabel + getLodSuffix(lod); + + mModel[lod][mdl_idx]->mLabel = name; + mModel[lod][mdl_idx]->mSubmodelID = base->mSubmodelID; + mModel[lod][mdl_idx]->setNumVolumeFaces(base->getNumVolumeFaces()); + + LLModel* target_model = mModel[lod][mdl_idx]; + + // carry over normalized transform into simplified model + for (int i = 0; i < base->getNumVolumeFaces(); ++i) + { + LLVolumeFace& src = base->getVolumeFace(i); + LLVolumeFace& dst = target_model->getVolumeFace(i); + dst.mNormalizedScale = src.mNormalizedScale; + } + + S32 model_meshopt_mode = meshopt_mode; + + // Ideally this should run not per model, + // but combine all submodels with origin model as well + if (model_meshopt_mode == MESH_OPTIMIZER_PRECISE) + { + // Run meshoptimizer for each face + for (U32 face_idx = 0; face_idx < base->getNumVolumeFaces(); ++face_idx) + { + F32 res = genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_FULL); + if (res < 0) + { + // Mesh optimizer failed and returned an invalid model + const LLVolumeFace &face = base->getVolumeFace(face_idx); + LLVolumeFace &new_face = target_model->getVolumeFace(face_idx); + new_face = face; + } + } + } + + if (model_meshopt_mode == MESH_OPTIMIZER_SLOPPY) + { + // Run meshoptimizer for each face + for (U32 face_idx = 0; face_idx < base->getNumVolumeFaces(); ++face_idx) + { + if (genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_NO_TOPOLOGY) < 0) + { + // Sloppy failed and returned an invalid model + genMeshOptimizerPerFace(base, target_model, face_idx, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_FULL); + } + } + } + + if (model_meshopt_mode == MESH_OPTIMIZER_AUTO) + { + // Remove progressively more data if we can't reach the target. + F32 allowed_ratio_drift = 1.8f; + F32 precise_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_FULL); + + if (precise_ratio < 0 || (precise_ratio * allowed_ratio_drift < indices_decimator)) + { + precise_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_NO_NORMALS); + } + + if (precise_ratio < 0 || (precise_ratio * allowed_ratio_drift < indices_decimator)) + { + precise_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_NO_UVS); + } + + if (precise_ratio < 0 || (precise_ratio * allowed_ratio_drift < indices_decimator)) + { + // Try sloppy variant if normal one failed to simplify model enough. + // Sloppy variant can fail entirely and has issues with precision, + // so code needs to do multiple attempts with different decimators. + // Todo: this is a bit of a mess, needs to be refined and improved + + F32 last_working_decimator = 0.f; + F32 last_working_ratio = F32_MAX; + + F32 sloppy_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_NO_TOPOLOGY); + + if (sloppy_ratio > 0) + { + // Would be better to do a copy of target_model here, but if + // we need to use sloppy decimation, model should be cheap + // and fast to generate and it won't affect end result + last_working_decimator = indices_decimator; + last_working_ratio = sloppy_ratio; + } + + // Sloppy has a tendecy to error into lower side, so a request for 100 + // triangles turns into ~70, so check for significant difference from target decimation + F32 sloppy_ratio_drift = 1.4f; + if (lod_mode == LIMIT_TRIANGLES + && (sloppy_ratio > indices_decimator * sloppy_ratio_drift || sloppy_ratio < 0)) + { + // Apply a correction to compensate. + + // (indices_decimator / res_ratio) by itself is likely to overshoot to a differend + // side due to overal lack of precision, and we don't need an ideal result, which + // likely does not exist, just a better one, so a partial correction is enough. + F32 sloppy_decimator = indices_decimator * (indices_decimator / sloppy_ratio + 1) / 2; + sloppy_ratio = genMeshOptimizerPerModel(base, target_model, sloppy_decimator, lod_error_threshold, MESH_OPTIMIZER_NO_TOPOLOGY); + } + + if (last_working_decimator > 0 && sloppy_ratio < last_working_ratio) + { + // Compensation didn't work, return back to previous decimator + sloppy_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_NO_TOPOLOGY); + } + + if (sloppy_ratio < 0) + { + // Sloppy method didn't work, try with smaller decimation values + { + // Find a decimator that does work + F32 sloppy_decimation_step = sqrt((F32)decimation); // example: 27->15->9->5->3 + F32 sloppy_decimator = indices_decimator / sloppy_decimation_step; + U64Microseconds end_time = LLTimer::getTotalTime() + U64Seconds(5); + + while (sloppy_ratio < 0 + && sloppy_decimator > precise_ratio + && sloppy_decimator > 1 // precise_ratio isn't supposed to be below 1, but check just in case + && end_time > LLTimer::getTotalTime()) + { + sloppy_ratio = genMeshOptimizerPerModel(base, target_model, sloppy_decimator, lod_error_threshold, MESH_OPTIMIZER_NO_TOPOLOGY); + sloppy_decimator = sloppy_decimator / sloppy_decimation_step; + } + } + } + + if (sloppy_ratio < 0 || sloppy_ratio < precise_ratio) + { + // Sloppy variant failed to generate triangles or is worse. + // Can happen with models that are too simple as is. + + if (precise_ratio < 0) + { + // Precise method failed as well, just copy face over + target_model->copyVolumeFaces(base); + precise_ratio = 1.f; + } + else + { + // Fallback to normal method + precise_ratio = genMeshOptimizerPerModel(base, target_model, indices_decimator, lod_error_threshold, MESH_OPTIMIZER_FULL); + } + + LL_INFOS() << "Model " << target_model->getName() + << " lod " << which_lod + << " resulting ratio " << precise_ratio + << " simplified using per model method." << LL_ENDL; + } + else + { + LL_INFOS() << "Model " << target_model->getName() + << " lod " << which_lod + << " resulting ratio " << sloppy_ratio + << " sloppily simplified using per model method." << LL_ENDL; + } + } + else + { + LL_INFOS() << "Model " << target_model->getName() + << " lod " << which_lod + << " resulting ratio " << precise_ratio + << " simplified using per model method." << LL_ENDL; + } + } + + //blind copy skin weights and just take closest skin weight to point on + //decimated mesh for now (auto-generating LODs with skin weights is still a bit + //of an open problem). + target_model->mPosition = base->mPosition; + target_model->mSkinWeights = base->mSkinWeights; + target_model->mSkinInfo = base->mSkinInfo; + + //copy material list + target_model->mMaterialList = base->mMaterialList; + + if (!validate_model(target_model)) + { + LL_ERRS() << "Invalid model generated when creating LODs" << LL_ENDL; + } + } + + //rebuild scene based on mBaseScene + mScene[lod].clear(); + mScene[lod] = mBaseScene; + + for (U32 i = 0; i < mBaseModel.size(); ++i) + { + LLModel* mdl = mBaseModel[i]; + LLModel* target = mModel[lod][i]; + if (target) + { + for (LLModelLoader::scene::iterator iter = mScene[lod].begin(); iter != mScene[lod].end(); ++iter) + { + for (U32 j = 0; j < iter->second.size(); ++j) + { + if (iter->second[j].mModel == mdl) + { + iter->second[j].mModel = target; + } + } + } + } + } + } +} + +void LLModelPreview::updateStatusMessages() +{ + // bit mask values for physics errors. used to prevent overwrite of single line status + // TODO: use this to provied multiline status + enum PhysicsError + { + NONE = 0, + NOHAVOK = 1, + DEGENERATE = 2, + TOOMANYHULLS = 4, + TOOMANYVERTSINHULL = 8 + }; + + assert_main_thread(); + + U32 has_physics_error{ PhysicsError::NONE }; // physics error bitmap + //triangle/vertex/submesh count for each mesh asset for each lod + std::vector tris[LLModel::NUM_LODS]; + std::vector verts[LLModel::NUM_LODS]; + std::vector submeshes[LLModel::NUM_LODS]; + + //total triangle/vertex/submesh count for each lod + S32 total_tris[LLModel::NUM_LODS]; + S32 total_verts[LLModel::NUM_LODS]; + S32 total_submeshes[LLModel::NUM_LODS]; + + for (U32 i = 0; i < LLModel::NUM_LODS - 1; i++) + { + total_tris[i] = 0; + total_verts[i] = 0; + total_submeshes[i] = 0; + } + + for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter) + { + LLModelInstance& instance = *iter; + + LLModel* model_high_lod = instance.mLOD[LLModel::LOD_HIGH]; + if (!model_high_lod) + { + setLoadState(LLModelLoader::ERROR_MATERIALS); + mFMP->childDisable("calculate_btn"); + continue; + } + + for (U32 i = 0; i < LLModel::NUM_LODS - 1; i++) + { + LLModel* lod_model = instance.mLOD[i]; + if (!lod_model) + { + setLoadState(LLModelLoader::ERROR_MATERIALS); + mFMP->childDisable("calculate_btn"); + } + else + { + //for each model in the lod + S32 cur_tris = 0; + S32 cur_verts = 0; + S32 cur_submeshes = lod_model->getNumVolumeFaces(); + + for (S32 j = 0; j < cur_submeshes; ++j) + { //for each submesh (face), add triangles and vertices to current total + const LLVolumeFace& face = lod_model->getVolumeFace(j); + cur_tris += face.mNumIndices / 3; + cur_verts += face.mNumVertices; + } + + std::string instance_name = instance.mLabel; + + if (mImporterDebug) + { + // Useful for debugging generalized complaints below about total submeshes which don't have enough + // context to address exactly what needs to be fixed to move towards compliance with the rules. + // + std::ostringstream out; + out << "Instance " << lod_model->mLabel << " LOD " << i << " Verts: " << cur_verts; + LL_INFOS() << out.str() << LL_ENDL; + LLFloaterModelPreview::addStringToLog(out, false); + + out.str(""); + out << "Instance " << lod_model->mLabel << " LOD " << i << " Tris: " << cur_tris; + LL_INFOS() << out.str() << LL_ENDL; + LLFloaterModelPreview::addStringToLog(out, false); + + out.str(""); + out << "Instance " << lod_model->mLabel << " LOD " << i << " Faces: " << cur_submeshes; + LL_INFOS() << out.str() << LL_ENDL; + LLFloaterModelPreview::addStringToLog(out, false); + + out.str(""); + LLModel::material_list::iterator mat_iter = lod_model->mMaterialList.begin(); + while (mat_iter != lod_model->mMaterialList.end()) + { + out << "Instance " << lod_model->mLabel << " LOD " << i << " Material " << *(mat_iter); + LL_INFOS() << out.str() << LL_ENDL; + LLFloaterModelPreview::addStringToLog(out, false); + out.str(""); + mat_iter++; + } + } + + //add this model to the lod total + total_tris[i] += cur_tris; + total_verts[i] += cur_verts; + total_submeshes[i] += cur_submeshes; + + //store this model's counts to asset data + tris[i].push_back(cur_tris); + verts[i].push_back(cur_verts); + submeshes[i].push_back(cur_submeshes); + } + } + } + + if (mMaxTriangleLimit == 0) + { + mMaxTriangleLimit = total_tris[LLModel::LOD_HIGH]; + } + + mHasDegenerate = false; + {//check for degenerate triangles in physics mesh + U32 lod = LLModel::LOD_PHYSICS; + const LLVector4a scale(0.5f); + for (U32 i = 0; i < mModel[lod].size() && !mHasDegenerate; ++i) + { //for each model in the lod + if (mModel[lod][i] && mModel[lod][i]->mPhysics.mHull.empty()) + { //no decomp exists + S32 cur_submeshes = mModel[lod][i]->getNumVolumeFaces(); + for (S32 j = 0; j < cur_submeshes && !mHasDegenerate; ++j) + { //for each submesh (face), add triangles and vertices to current total + LLVolumeFace& face = mModel[lod][i]->getVolumeFace(j); + for (S32 k = 0; (k < face.mNumIndices) && !mHasDegenerate;) + { + U16 index_a = face.mIndices[k + 0]; + U16 index_b = face.mIndices[k + 1]; + U16 index_c = face.mIndices[k + 2]; + + if (index_c == 0 && index_b == 0 && index_a == 0) // test in reverse as 3rd index is less likely to be 0 in a normal case + { + LL_DEBUGS("MeshValidation") << "Empty placeholder triangle (3 identical index 0 verts) ignored" << LL_ENDL; + } + else + { + LLVector4a v1; v1.setMul(face.mPositions[index_a], scale); + LLVector4a v2; v2.setMul(face.mPositions[index_b], scale); + LLVector4a v3; v3.setMul(face.mPositions[index_c], scale); + if (ll_is_degenerate(v1, v2, v3)) + { + mHasDegenerate = true; + } + } + k += 3; + } + } + } + } + } + + // flag degenerates here rather than deferring to a MAV error later + mFMP->childSetVisible("physics_status_message_text", mHasDegenerate); //display or clear + auto degenerateIcon = mFMP->getChild("physics_status_message_icon"); + degenerateIcon->setVisible(mHasDegenerate); + if (mHasDegenerate) + { + has_physics_error |= PhysicsError::DEGENERATE; + mFMP->childSetValue("physics_status_message_text", mFMP->getString("phys_status_degenerate_triangles")); + LLUIImagePtr img = LLUI::getUIImage("ModelImport_Status_Error"); + degenerateIcon->setImage(img); + } + + mFMP->childSetTextArg("submeshes_info", "[SUBMESHES]", llformat("%d", total_submeshes[LLModel::LOD_HIGH])); + + std::string mesh_status_na = mFMP->getString("mesh_status_na"); + + S32 upload_status[LLModel::LOD_HIGH + 1]; + + mModelNoErrors = true; + + const U32 lod_high = LLModel::LOD_HIGH; + U32 high_submodel_count = mModel[lod_high].size() - countRootModels(mModel[lod_high]); + + for (S32 lod = 0; lod <= lod_high; ++lod) + { + upload_status[lod] = 0; + + std::string message = "mesh_status_good"; + + if (total_tris[lod] > 0) + { + mFMP->childSetValue(lod_triangles_name[lod], llformat("%d", total_tris[lod])); + mFMP->childSetValue(lod_vertices_name[lod], llformat("%d", total_verts[lod])); + } + else + { + if (lod == lod_high) + { + upload_status[lod] = 2; + message = "mesh_status_missing_lod"; + } + else + { + for (S32 i = lod - 1; i >= 0; --i) + { + if (total_tris[i] > 0) + { + upload_status[lod] = 2; + message = "mesh_status_missing_lod"; + } + } + } + + mFMP->childSetValue(lod_triangles_name[lod], mesh_status_na); + mFMP->childSetValue(lod_vertices_name[lod], mesh_status_na); + } + + if (lod != lod_high) + { + if (total_submeshes[lod] && total_submeshes[lod] != total_submeshes[lod_high]) + { //number of submeshes is different + message = "mesh_status_submesh_mismatch"; + upload_status[lod] = 2; + } + else if (mModel[lod].size() - countRootModels(mModel[lod]) != high_submodel_count) + {//number of submodels is different, not all faces are matched correctly. + message = "mesh_status_submesh_mismatch"; + upload_status[lod] = 2; + // Note: Submodels in instance were loaded from higher LOD and as result face count + // returns same value and total_submeshes[lod] is identical to high_lod one. + } + else if (!tris[lod].empty() && tris[lod].size() != tris[lod_high].size()) + { //number of meshes is different + message = "mesh_status_mesh_mismatch"; + upload_status[lod] = 2; + } + else if (!verts[lod].empty()) + { + S32 sum_verts_higher_lod = 0; + S32 sum_verts_this_lod = 0; + for (U32 i = 0; i < verts[lod].size(); ++i) + { + sum_verts_higher_lod += ((i < verts[lod + 1].size()) ? verts[lod + 1][i] : 0); + sum_verts_this_lod += verts[lod][i]; + } + + if ((sum_verts_higher_lod > 0) && + (sum_verts_this_lod > sum_verts_higher_lod)) + { + //too many vertices in this lod + message = "mesh_status_too_many_vertices"; + upload_status[lod] = 1; + } + } + } + + LLIconCtrl* icon = mFMP->getChild(lod_icon_name[lod]); + LLUIImagePtr img = LLUI::getUIImage(lod_status_image[upload_status[lod]]); + icon->setVisible(true); + icon->setImage(img); + + if (upload_status[lod] >= 2) + { + mModelNoErrors = false; + } + + if (lod == mPreviewLOD) + { + mFMP->childSetValue("lod_status_message_text", mFMP->getString(message)); + icon = mFMP->getChild("lod_status_message_icon"); + icon->setImage(img); + } + + updateLodControls(lod); + } + + + //warn if hulls have more than 256 points in them + bool physExceededVertexLimit = false; + for (U32 i = 0; mModelNoErrors && i < mModel[LLModel::LOD_PHYSICS].size(); ++i) + { + LLModel* mdl = mModel[LLModel::LOD_PHYSICS][i]; + + if (mdl) + { + for (U32 j = 0; j < mdl->mPhysics.mHull.size(); ++j) + { + if (mdl->mPhysics.mHull[j].size() > 256) + { + physExceededVertexLimit = true; + LL_INFOS() << "Physical model " << mdl->mLabel << " exceeds vertex per hull limitations." << LL_ENDL; + break; + } + } + } + } + + if (physExceededVertexLimit) + { + has_physics_error |= PhysicsError::TOOMANYVERTSINHULL; + } + + if (!(has_physics_error & PhysicsError::DEGENERATE)){ // only update this field (incluides clearing it) if it is not already in use. + mFMP->childSetVisible("physics_status_message_text", physExceededVertexLimit); + LLIconCtrl* physStatusIcon = mFMP->getChild("physics_status_message_icon"); + physStatusIcon->setVisible(physExceededVertexLimit); + if (physExceededVertexLimit) + { + mFMP->childSetValue("physics_status_message_text", mFMP->getString("phys_status_vertex_limit_exceeded")); + LLUIImagePtr img = LLUI::getUIImage("ModelImport_Status_Warning"); + physStatusIcon->setImage(img); + } + } + + if (getLoadState() >= LLModelLoader::ERROR_PARSING) + { + mModelNoErrors = false; + LL_INFOS() << "Loader returned errors, model can't be uploaded" << LL_ENDL; + } + + bool uploadingSkin = mFMP->childGetValue("upload_skin").asBoolean(); + bool uploadingJointPositions = mFMP->childGetValue("upload_joints").asBoolean(); + + if (uploadingSkin) + { + if (uploadingJointPositions && !isRigValidForJointPositionUpload()) + { + mModelNoErrors = false; + LL_INFOS() << "Invalid rig, there might be issues with uploading Joint positions" << LL_ENDL; + } + } + + if (mModelNoErrors && mModelLoader) + { + if (!mModelLoader->areTexturesReady() && mFMP->childGetValue("upload_textures").asBoolean()) + { + // Some textures are still loading, prevent upload until they are done + mModelNoErrors = false; + } + } + + if (!mModelNoErrors || mHasDegenerate) + { + mFMP->childDisable("ok_btn"); + mFMP->childDisable("calculate_btn"); + } + else + { + mFMP->childEnable("ok_btn"); + mFMP->childEnable("calculate_btn"); + } + + if (mModelNoErrors && mLodsWithParsingError.empty()) + { + mFMP->childEnable("calculate_btn"); + } + else + { + mFMP->childDisable("calculate_btn"); + } + + //add up physics triangles etc + S32 phys_tris = 0; + S32 phys_hulls = 0; + S32 phys_points = 0; + + //get the triangle count for the whole scene + for (LLModelLoader::scene::iterator iter = mScene[LLModel::LOD_PHYSICS].begin(), endIter = mScene[LLModel::LOD_PHYSICS].end(); iter != endIter; ++iter) + { + for (LLModelLoader::model_instance_list::iterator instance = iter->second.begin(), end_instance = iter->second.end(); instance != end_instance; ++instance) + { + LLModel* model = instance->mModel; + if (model) + { + S32 cur_submeshes = model->getNumVolumeFaces(); + + LLModel::convex_hull_decomposition& decomp = model->mPhysics.mHull; + + if (!decomp.empty()) + { + phys_hulls += decomp.size(); + for (U32 i = 0; i < decomp.size(); ++i) + { + phys_points += decomp[i].size(); + } + } + else + { //choose physics shape OR decomposition, can't use both + for (S32 j = 0; j < cur_submeshes; ++j) + { //for each submesh (face), add triangles and vertices to current total + const LLVolumeFace& face = model->getVolumeFace(j); + phys_tris += face.mNumIndices / 3; + } + } + } + } + } + + if (phys_tris > 0) + { + mFMP->childSetTextArg("physics_triangles", "[TRIANGLES]", llformat("%d", phys_tris)); + } + else + { + mFMP->childSetTextArg("physics_triangles", "[TRIANGLES]", mesh_status_na); + } + + if (phys_hulls > 0) + { + mFMP->childSetTextArg("physics_hulls", "[HULLS]", llformat("%d", phys_hulls)); + mFMP->childSetTextArg("physics_points", "[POINTS]", llformat("%d", phys_points)); + } + else + { + mFMP->childSetTextArg("physics_hulls", "[HULLS]", mesh_status_na); + mFMP->childSetTextArg("physics_points", "[POINTS]", mesh_status_na); + } + + LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance; + if (fmp) + { + if (phys_tris > 0 || phys_hulls > 0) + { + if (!fmp->isViewOptionEnabled("show_physics")) + { + fmp->enableViewOption("show_physics"); + mViewOption["show_physics"] = true; + fmp->childSetValue("show_physics", true); + } + } + else + { + fmp->disableViewOption("show_physics"); + mViewOption["show_physics"] = false; + fmp->childSetValue("show_physics", false); + + } + + //bool use_hull = fmp->childGetValue("physics_use_hull").asBoolean(); + + //fmp->childSetEnabled("physics_optimize", !use_hull); + + bool enable = (phys_tris > 0 || phys_hulls > 0) && fmp->mCurRequest.empty(); + //enable = enable && !use_hull && fmp->childGetValue("physics_optimize").asBoolean(); + + //enable/disable "analysis" UI + LLPanel* panel = fmp->getChild("physics analysis"); + LLView* child = panel->getFirstChild(); + while (child) + { + child->setEnabled(enable); + child = panel->findNextSibling(child); + } + + enable = phys_hulls > 0 && fmp->mCurRequest.empty(); + //enable/disable "simplification" UI + panel = fmp->getChild("physics simplification"); + child = panel->getFirstChild(); + while (child) + { + child->setEnabled(enable); + child = panel->findNextSibling(child); + } + + if (fmp->mCurRequest.empty()) + { + fmp->childSetVisible("Simplify", true); + fmp->childSetVisible("simplify_cancel", false); + fmp->childSetVisible("Decompose", true); + fmp->childSetVisible("decompose_cancel", false); + + if (phys_hulls > 0) + { + fmp->childEnable("Simplify"); + } + + if (phys_tris || phys_hulls > 0) + { + fmp->childEnable("Decompose"); + } + } + else + { + fmp->childEnable("simplify_cancel"); + fmp->childEnable("decompose_cancel"); + } + } + + + LLCtrlSelectionInterface* iface = fmp->childGetSelectionInterface("physics_lod_combo"); + S32 which_mode = 0; + S32 file_mode = 1; + if (iface) + { + which_mode = iface->getFirstSelectedIndex(); + file_mode = iface->getItemCount() - 1; + } + + if (which_mode == file_mode) + { + mFMP->childEnable("physics_file"); + mFMP->childEnable("physics_browse"); + } + else + { + mFMP->childDisable("physics_file"); + mFMP->childDisable("physics_browse"); + } + + LLSpinCtrl* crease = mFMP->getChild("crease_angle"); + + if (mRequestedCreaseAngle[mPreviewLOD] == -1.f) + { + mFMP->childSetColor("crease_label", LLColor4::grey); + crease->forceSetValue(75.f); + } + else + { + mFMP->childSetColor("crease_label", LLColor4::white); + crease->forceSetValue(mRequestedCreaseAngle[mPreviewLOD]); + } + + mModelUpdatedSignal(true); + +} + +void LLModelPreview::updateLodControls(S32 lod) +{ + if (lod < LLModel::LOD_IMPOSTOR || lod > LLModel::LOD_HIGH) + { + std::ostringstream out; + out << "Invalid level of detail: " << lod; + LL_WARNS() << out.str() << LL_ENDL; + LLFloaterModelPreview::addStringToLog(out, false); + assert(lod >= LLModel::LOD_IMPOSTOR && lod <= LLModel::LOD_HIGH); + return; + } + + const char* lod_controls[] = + { + "lod_mode_", + "lod_triangle_limit_", + "lod_error_threshold_" + }; + const U32 num_lod_controls = sizeof(lod_controls) / sizeof(char*); + + const char* file_controls[] = + { + "lod_browse_", + "lod_file_", + }; + const U32 num_file_controls = sizeof(file_controls) / sizeof(char*); + + LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance; + if (!fmp) return; + + LLComboBox* lod_combo = mFMP->findChild("lod_source_" + lod_name[lod]); + if (!lod_combo) return; + + S32 lod_mode = lod_combo->getCurrentIndex(); + if (lod_mode == LOD_FROM_FILE) // LoD from file + { + fmp->mLODMode[lod] = LOD_FROM_FILE; + for (U32 i = 0; i < num_file_controls; ++i) + { + mFMP->childSetVisible(file_controls[i] + lod_name[lod], true); + } + + for (U32 i = 0; i < num_lod_controls; ++i) + { + mFMP->childSetVisible(lod_controls[i] + lod_name[lod], false); + } + } + else if (lod_mode == USE_LOD_ABOVE) // use LoD above + { + fmp->mLODMode[lod] = USE_LOD_ABOVE; + for (U32 i = 0; i < num_file_controls; ++i) + { + mFMP->childSetVisible(file_controls[i] + lod_name[lod], false); + } + + for (U32 i = 0; i < num_lod_controls; ++i) + { + mFMP->childSetVisible(lod_controls[i] + lod_name[lod], false); + } + + if (lod < LLModel::LOD_HIGH) + { + mModel[lod] = mModel[lod + 1]; + mScene[lod] = mScene[lod + 1]; + mVertexBuffer[lod].clear(); + + // Also update lower LoD + if (lod > LLModel::LOD_IMPOSTOR) + { + updateLodControls(lod - 1); + } + } + } + else // auto generate, the default case for all LoDs except High + { + fmp->mLODMode[lod] = MESH_OPTIMIZER_AUTO; + + //don't actually regenerate lod when refreshing UI + mLODFrozen = true; + + for (U32 i = 0; i < num_file_controls; ++i) + { + mFMP->getChildView(file_controls[i] + lod_name[lod])->setVisible(false); + } + + for (U32 i = 0; i < num_lod_controls; ++i) + { + mFMP->getChildView(lod_controls[i] + lod_name[lod])->setVisible(true); + } + + + LLSpinCtrl* threshold = mFMP->getChild("lod_error_threshold_" + lod_name[lod]); + LLSpinCtrl* limit = mFMP->getChild("lod_triangle_limit_" + lod_name[lod]); + + limit->setMaxValue(mMaxTriangleLimit); + limit->forceSetValue(mRequestedTriangleCount[lod]); + + threshold->forceSetValue(mRequestedErrorThreshold[lod]); + + mFMP->getChild("lod_mode_" + lod_name[lod])->selectNthItem(mRequestedLoDMode[lod]); + + if (mRequestedLoDMode[lod] == 0) + { + limit->setVisible(true); + threshold->setVisible(false); + + limit->setMaxValue(mMaxTriangleLimit); + limit->setIncrement(llmax((U32)1, mMaxTriangleLimit / 32)); + } + else + { + limit->setVisible(false); + threshold->setVisible(true); + } + + mLODFrozen = false; + } +} + +void LLModelPreview::setPreviewTarget(F32 distance) +{ + mCameraDistance = distance; + mCameraZoom = 1.f; + mCameraPitch = 0.f; + mCameraYaw = 0.f; + mCameraOffset.clearVec(); +} + +void LLModelPreview::clearBuffers() +{ + for (U32 i = 0; i < 6; i++) + { + mVertexBuffer[i].clear(); + } +} + +void LLModelPreview::genBuffers(S32 lod, bool include_skin_weights) +{ + LLModelLoader::model_list* model = NULL; + + if (lod < 0 || lod > 4) + { + model = &mBaseModel; + lod = 5; + } + else + { + model = &(mModel[lod]); + } + + if (!mVertexBuffer[lod].empty()) + { + mVertexBuffer[lod].clear(); + } + + mVertexBuffer[lod].clear(); + + LLModelLoader::model_list::iterator base_iter = mBaseModel.begin(); + + for (LLModelLoader::model_list::iterator iter = model->begin(); iter != model->end(); ++iter) + { + LLModel* mdl = *iter; + if (!mdl) + { + continue; + } + + base_iter++; + + bool skinned = include_skin_weights && !mdl->mSkinWeights.empty(); + + LLMatrix4a mat_normal; + if (skinned) + { + glh::matrix4f m((F32*)mdl->mSkinInfo.mBindShapeMatrix.getF32ptr()); + m = m.inverse().transpose(); + mat_normal.loadu(m.m); + } + + S32 num_faces = mdl->getNumVolumeFaces(); + for (S32 i = 0; i < num_faces; ++i) + { + const LLVolumeFace &vf = mdl->getVolumeFace(i); + U32 num_vertices = vf.mNumVertices; + U32 num_indices = vf.mNumIndices; + + if (!num_vertices || !num_indices) + { + continue; + } + + LLVertexBuffer* vb = NULL; + + + + U32 mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0; + + if (skinned) + { + mask |= LLVertexBuffer::MAP_WEIGHT4; + } + + vb = new LLVertexBuffer(mask); + + if (!vb->allocateBuffer(num_vertices, num_indices)) + { + // We are likely to crash due this failure, if this happens, find a way to gracefully stop preview + std::ostringstream out; + out << "Failed to allocate Vertex Buffer for model preview "; + out << num_vertices << " vertices and "; + out << num_indices << " indices"; + LL_WARNS() << out.str() << LL_ENDL; + LLFloaterModelPreview::addStringToLog(out, true); + } + + LLStrider vertex_strider; + LLStrider normal_strider; + LLStrider tc_strider; + LLStrider index_strider; + LLStrider weights_strider; + + vb->getVertexStrider(vertex_strider); + vb->getIndexStrider(index_strider); + + if (skinned) + { + vb->getWeight4Strider(weights_strider); + } + + LLVector4a::memcpyNonAliased16((F32*)vertex_strider.get(), (F32*)vf.mPositions, num_vertices * 4 * sizeof(F32)); + + if (skinned) + { + for (U32 i = 0; i < num_vertices; ++i) + { + LLVector4a* v = (LLVector4a*)vertex_strider.get(); + mdl->mSkinInfo.mBindShapeMatrix.affineTransform(*v, *v); + vertex_strider++; + } + } + if (vf.mTexCoords) + { + vb->getTexCoord0Strider(tc_strider); + S32 tex_size = (num_vertices * 2 * sizeof(F32) + 0xF) & ~0xF; + LLVector4a::memcpyNonAliased16((F32*)tc_strider.get(), (F32*)vf.mTexCoords, tex_size); + } + + if (vf.mNormals) + { + vb->getNormalStrider(normal_strider); + + if (skinned) + { + F32* normals = (F32*)normal_strider.get(); + LLVector4a* src = vf.mNormals; + LLVector4a* end = src + num_vertices; + + while (src < end) + { + LLVector4a normal; + mat_normal.rotate(*src++, normal); + normal.store4a(normals); + normals += 4; + } + } + else + { + LLVector4a::memcpyNonAliased16((F32*)normal_strider.get(), (F32*)vf.mNormals, num_vertices * 4 * sizeof(F32)); + } + } + + if (skinned) + { + for (U32 i = 0; i < num_vertices; i++) + { + //find closest weight to vf.mVertices[i].mPosition + LLVector3 pos(vf.mPositions[i].getF32ptr()); + + const LLModel::weight_list& weight_list = mdl->getJointInfluences(pos); + llassert(weight_list.size()>0 && weight_list.size() <= 4); // LLModel::loadModel() should guarantee this + + LLVector4 w(0, 0, 0, 0); + + for (U32 i = 0; i < weight_list.size(); ++i) + { + F32 wght = llclamp(weight_list[i].mWeight, 0.001f, 0.999f); + F32 joint = (F32)weight_list[i].mJointIdx; + w.mV[i] = joint + wght; + llassert(w.mV[i] - (S32)w.mV[i]>0.0f); // because weights are non-zero, and range of wt values + //should not cause floating point precision issues. + } + + *(weights_strider++) = w; + } + } + + // build indices + for (U32 i = 0; i < num_indices; i++) + { + *(index_strider++) = vf.mIndices[i]; + } + + vb->unmapBuffer(); + + mVertexBuffer[lod][mdl].push_back(vb); + } + } +} + +void LLModelPreview::update() +{ + if (mGenLOD) + { + bool subscribe_for_generation = mLodsQuery.empty(); + mGenLOD = false; + mDirty = true; + mLodsQuery.clear(); + + for (S32 lod = LLModel::LOD_HIGH; lod >= 0; --lod) + { + // adding all lods into query for generation + mLodsQuery.push_back(lod); + } + + if (subscribe_for_generation) + { + doOnIdleRepeating(lodQueryCallback); + } + } + + if (mDirty && mLodsQuery.empty()) + { + mDirty = false; + updateDimentionsAndOffsets(); + refresh(); + } +} + +//----------------------------------------------------------------------------- +// createPreviewAvatar +//----------------------------------------------------------------------------- +void LLModelPreview::createPreviewAvatar(void) +{ + mPreviewAvatar = (LLVOAvatar*)gObjectList.createObjectViewer(LL_PCODE_LEGACY_AVATAR, gAgent.getRegion(), LLViewerObject::CO_FLAG_UI_AVATAR); + if (mPreviewAvatar) + { + mPreviewAvatar->createDrawable(&gPipeline); + mPreviewAvatar->mSpecialRenderMode = 1; + mPreviewAvatar->startMotion(ANIM_AGENT_STAND); + mPreviewAvatar->hideSkirt(); + } + else + { + LL_INFOS() << "Failed to create preview avatar for upload model window" << LL_ENDL; + } +} + +//static +U32 LLModelPreview::countRootModels(LLModelLoader::model_list models) +{ + U32 root_models = 0; + model_list::iterator model_iter = models.begin(); + while (model_iter != models.end()) + { + LLModel* mdl = *model_iter; + if (mdl && mdl->mSubmodelID == 0) + { + root_models++; + } + model_iter++; + } + return root_models; +} + +void LLModelPreview::loadedCallback( + LLModelLoader::scene& scene, + LLModelLoader::model_list& model_list, + S32 lod, + void* opaque) +{ + LLModelPreview* pPreview = static_cast< LLModelPreview* >(opaque); + if (pPreview && !LLModelPreview::sIgnoreLoadedCallback) + { + // Load loader's warnings into floater's log tab + const LLSD out = pPreview->mModelLoader->logOut(); + LLSD::array_const_iterator iter_out = out.beginArray(); + LLSD::array_const_iterator end_out = out.endArray(); + for (; iter_out != end_out; ++iter_out) + { + if (iter_out->has("Message")) + { + LLFloaterModelPreview::addStringToLog(iter_out->get("Message"), *iter_out, true, pPreview->mModelLoader->mLod); + } + } + pPreview->mModelLoader->clearLog(); + pPreview->loadModelCallback(lod); // removes mModelLoader in some cases + if (pPreview->mLookUpLodFiles && (lod != LLModel::LOD_HIGH)) + { + pPreview->lookupLODModelFiles(lod); + } + + const LLVOAvatar* avatarp = pPreview->getPreviewAvatar(); + if (avatarp) { // set up ground plane for possible rendering + const LLVector3 root_pos = avatarp->mRoot->getPosition(); + const LLVector4a* ext = avatarp->mDrawable->getSpatialExtents(); + const LLVector4a min = ext[0], max = ext[1]; + const F32 center = (max[2] - min[2]) * 0.5f; + const F32 ground = root_pos[2] - center; + auto plane = pPreview->mGroundPlane; + plane[0] = {min[0], min[1], ground}; + plane[1] = {max[0], min[1], ground}; + plane[2] = {max[0], max[1], ground}; + plane[3] = {min[0], max[1], ground}; + } + } + +} + +void LLModelPreview::lookupLODModelFiles(S32 lod) +{ + if (lod == LLModel::LOD_PHYSICS) + { + mLookUpLodFiles = false; + return; + } + S32 next_lod = (lod - 1 >= LLModel::LOD_IMPOSTOR) ? lod - 1 : LLModel::LOD_PHYSICS; + + std::string lod_filename = mLODFile[LLModel::LOD_HIGH]; + std::string ext = ".dae"; + std::string lod_filename_lower(lod_filename); + LLStringUtil::toLower(lod_filename_lower); + std::string::size_type i = lod_filename_lower.rfind(ext); + if (i != std::string::npos) + { + lod_filename.replace(i, lod_filename.size() - ext.size(), getLodSuffix(next_lod) + ext); + } + if (gDirUtilp->fileExists(lod_filename)) + { + LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance; + if (fmp) + { + fmp->setCtrlLoadFromFile(next_lod); + } + loadModel(lod_filename, next_lod); + } + else + { + lookupLODModelFiles(next_lod); + } +} + +void LLModelPreview::stateChangedCallback(U32 state, void* opaque) +{ + LLModelPreview* pPreview = static_cast< LLModelPreview* >(opaque); + if (pPreview) + { + pPreview->setLoadState(state); + } +} + +LLJoint* LLModelPreview::lookupJointByName(const std::string& str, void* opaque) +{ + LLModelPreview* pPreview = static_cast< LLModelPreview* >(opaque); + if (pPreview) + { + return pPreview->getPreviewAvatar()->getJoint(str); + } + return NULL; +} + +U32 LLModelPreview::loadTextures(LLImportMaterial& material, void* opaque) +{ + (void)opaque; + + if (material.mDiffuseMapFilename.size()) + { + material.mOpaqueData = new LLPointer< LLViewerFetchedTexture >; + LLPointer< LLViewerFetchedTexture >& tex = (*reinterpret_cast< LLPointer< LLViewerFetchedTexture > * >(material.mOpaqueData)); + + tex = LLViewerTextureManager::getFetchedTextureFromUrl("file://" + LLURI::unescape(material.mDiffuseMapFilename), FTT_LOCAL_FILE, true, LLGLTexture::BOOST_PREVIEW); + tex->setLoadedCallback(LLModelPreview::textureLoadedCallback, 0, true, false, opaque, NULL, false); + tex->forceToSaveRawImage(0, F32_MAX); + material.setDiffuseMap(tex->getID()); // record tex ID + return 1; + } + + material.mOpaqueData = NULL; + return 0; +} + +void LLModelPreview::addEmptyFace(LLModel* pTarget) +{ + U32 type_mask = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_TEXCOORD0; + + LLPointer buff = new LLVertexBuffer(type_mask); + + buff->allocateBuffer(1, 3); + memset((U8*)buff->getMappedData(), 0, buff->getSize()); + memset((U8*)buff->getMappedIndices(), 0, buff->getIndicesSize()); + + buff->validateRange(0, buff->getNumVerts() - 1, buff->getNumIndices(), 0); + + LLStrider pos; + LLStrider norm; + LLStrider tc; + LLStrider index; + + buff->getVertexStrider(pos); + + if (type_mask & LLVertexBuffer::MAP_NORMAL) + { + buff->getNormalStrider(norm); + } + if (type_mask & LLVertexBuffer::MAP_TEXCOORD0) + { + buff->getTexCoord0Strider(tc); + } + + buff->getIndexStrider(index); + + //resize face array + int faceCnt = pTarget->getNumVolumeFaces(); + pTarget->setNumVolumeFaces(faceCnt + 1); + pTarget->setVolumeFaceData(faceCnt + 1, pos, norm, tc, index, buff->getNumVerts(), buff->getNumIndices()); + +} + +//----------------------------------------------------------------------------- +// render() +//----------------------------------------------------------------------------- +// Todo: we shouldn't be setting all those UI elements on render. +// Note: Render happens each frame with skinned avatars +bool LLModelPreview::render() +{ + assert_main_thread(); + + LLMutexLock lock(this); + mNeedsUpdate = false; + + bool edges = mViewOption["show_edges"]; + bool joint_overrides = mViewOption["show_joint_overrides"]; + bool joint_positions = mViewOption["show_joint_positions"]; + bool skin_weight = mViewOption["show_skin_weight"]; + bool textures = mViewOption["show_textures"]; + bool physics = mViewOption["show_physics"]; + + S32 width = getWidth(); + S32 height = getHeight(); + + LLGLSUIDefault def; + LLGLDisable no_blend(GL_BLEND); + LLGLEnable cull(GL_CULL_FACE); + LLGLDepthTest depth(GL_FALSE); // SL-12781 disable z-buffer to render background color + + { + gUIProgram.bind(); + + //clear background to grey + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.pushMatrix(); + gGL.loadIdentity(); + gGL.ortho(0.0f, width, 0.0f, height, -1.0f, 1.0f); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + gGL.loadIdentity(); + + gGL.color4fv(PREVIEW_CANVAS_COL.mV); + gl_rect_2d_simple(width, height); + + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.popMatrix(); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.popMatrix(); + gUIProgram.unbind(); + } + + LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance; + + bool has_skin_weights = false; + bool upload_skin = mFMP->childGetValue("upload_skin").asBoolean(); + bool upload_joints = mFMP->childGetValue("upload_joints").asBoolean(); + + if (upload_joints != mLastJointUpdate) + { + mLastJointUpdate = upload_joints; + if (fmp) + { + fmp->clearAvatarTab(); + } + } + + for (LLModelLoader::scene::iterator iter = mScene[mPreviewLOD].begin(); iter != mScene[mPreviewLOD].end(); ++iter) + { + for (LLModelLoader::model_instance_list::iterator model_iter = iter->second.begin(); model_iter != iter->second.end(); ++model_iter) + { + LLModelInstance& instance = *model_iter; + LLModel* model = instance.mModel; + model->mPelvisOffset = mPelvisZOffset; + if (!model->mSkinWeights.empty()) + { + has_skin_weights = true; + } + } + } + + if (has_skin_weights && lodsReady()) + { //model has skin weights, enable view options for skin weights and joint positions + U32 flags = getLegacyRigFlags(); + if (fmp) + { + if (flags == LEGACY_RIG_OK) + { + if (mFirstSkinUpdate) + { + // auto enable weight upload if weights are present + // (note: all these UI updates need to be somewhere that is not render) + fmp->childSetValue("upload_skin", true); + mFirstSkinUpdate = false; + upload_skin = true; + skin_weight = true; + mViewOption["show_skin_weight"] = true; + } + + fmp->enableViewOption("show_skin_weight"); + fmp->setViewOptionEnabled("show_joint_overrides", skin_weight); + fmp->setViewOptionEnabled("show_joint_positions", skin_weight); + mFMP->childEnable("upload_skin"); + mFMP->childSetValue("show_skin_weight", skin_weight); + + } + else if ((flags & LEGACY_RIG_FLAG_TOO_MANY_JOINTS) > 0) + { + mFMP->childSetVisible("skin_too_many_joints", true); + } + else if ((flags & LEGACY_RIG_FLAG_UNKNOWN_JOINT) > 0) + { + mFMP->childSetVisible("skin_unknown_joint", true); + } + } + } + else + { + mFMP->childDisable("upload_skin"); + if (fmp) + { + mViewOption["show_skin_weight"] = false; + fmp->disableViewOption("show_skin_weight"); + fmp->disableViewOption("show_joint_overrides"); + fmp->disableViewOption("show_joint_positions"); + + skin_weight = false; + mFMP->childSetValue("show_skin_weight", false); + fmp->setViewOptionEnabled("show_skin_weight", skin_weight); + } + } + + if (upload_skin && !has_skin_weights) + { //can't upload skin weights if model has no skin weights + mFMP->childSetValue("upload_skin", false); + upload_skin = false; + } + + if (!upload_skin && upload_joints) + { //can't upload joints if not uploading skin weights + mFMP->childSetValue("upload_joints", false); + upload_joints = false; + } + + if (fmp) + { + if (upload_skin) + { + // will populate list of joints + fmp->updateAvatarTab(upload_joints); + } + else + { + fmp->clearAvatarTab(); + } + } + + if (upload_skin && upload_joints) + { + mFMP->childEnable("lock_scale_if_joint_position"); + } + else + { + mFMP->childDisable("lock_scale_if_joint_position"); + mFMP->childSetValue("lock_scale_if_joint_position", false); + } + + //Only enable joint offsets if it passed the earlier critiquing + if (isRigValidForJointPositionUpload()) + { + mFMP->childSetEnabled("upload_joints", upload_skin); + } + + F32 explode = mFMP->childGetValue("physics_explode").asReal(); + + LLGLDepthTest gls_depth(GL_TRUE); // SL-12781 re-enable z-buffer for 3D model preview + + LLRect preview_rect; + + preview_rect = mFMP->getChildView("preview_panel")->getRect(); + + F32 aspect = (F32)preview_rect.getWidth() / preview_rect.getHeight(); + + LLViewerCamera::getInstance()->setAspect(aspect); + + LLViewerCamera::getInstance()->setView(LLViewerCamera::getInstance()->getDefaultFOV() / mCameraZoom); + + LLVector3 offset = mCameraOffset; + LLVector3 target_pos = mPreviewTarget + offset; + + F32 z_near = 0.001f; + F32 z_far = mCameraDistance*10.0f + mPreviewScale.magVec() + mCameraOffset.magVec(); + + if (skin_weight) + { + target_pos = getPreviewAvatar()->getPositionAgent() + offset; + z_near = 0.01f; + z_far = 1024.f; + + //render avatar previews every frame + refresh(); + } + + gObjectPreviewProgram.bind(skin_weight); + + gGL.loadIdentity(); + gPipeline.enableLightsPreview(); + + LLQuaternion camera_rot = LLQuaternion(mCameraPitch, LLVector3::y_axis) * + LLQuaternion(mCameraYaw, LLVector3::z_axis); + + LLQuaternion av_rot = camera_rot; + F32 camera_distance = skin_weight ? SKIN_WEIGHT_CAMERA_DISTANCE : mCameraDistance; + LLViewerCamera::getInstance()->setOriginAndLookAt( + target_pos + ((LLVector3(camera_distance, 0.f, 0.f) + offset) * av_rot), // camera + LLVector3::z_axis, // up + target_pos); // point of interest + + + z_near = llclamp(z_far * 0.001f, 0.001f, 0.1f); + + LLViewerCamera::getInstance()->setPerspective(false, mOrigin.mX, mOrigin.mY, width, height, false, z_near, z_far); + + stop_glerror(); + + gGL.pushMatrix(); + gGL.color4fv(PREVIEW_EDGE_COL.mV); + + if (!mBaseModel.empty() && mVertexBuffer[5].empty()) + { + genBuffers(-1, skin_weight); + //genBuffers(3); + } + + if (!mModel[mPreviewLOD].empty()) + { + mFMP->childEnable("reset_btn"); + + bool regen = mVertexBuffer[mPreviewLOD].empty(); + if (!regen) + { + const std::vector >& vb_vec = mVertexBuffer[mPreviewLOD].begin()->second; + if (!vb_vec.empty()) + { + const LLVertexBuffer* buff = vb_vec[0]; + regen = buff->hasDataType(LLVertexBuffer::TYPE_WEIGHT4) != skin_weight; + } + else + { + LL_INFOS() << "Vertex Buffer[" << mPreviewLOD << "]" << " is EMPTY!!!" << LL_ENDL; + regen = true; + } + } + + if (regen) + { + genBuffers(mPreviewLOD, skin_weight); + } + + if (physics && mVertexBuffer[LLModel::LOD_PHYSICS].empty()) + { + genBuffers(LLModel::LOD_PHYSICS, false); + } + + if (!skin_weight) + { + for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter) + { + LLModelInstance& instance = *iter; + + LLModel* model = instance.mLOD[mPreviewLOD]; + + if (!model) + { + continue; + } + + gGL.pushMatrix(); + + LLMatrix4 mat = instance.mTransform; + + gGL.multMatrix((GLfloat*)mat.mMatrix); + + U32 num_models = mVertexBuffer[mPreviewLOD][model].size(); + for (U32 i = 0; i < num_models; ++i) + { + LLVertexBuffer* buffer = mVertexBuffer[mPreviewLOD][model][i]; + + buffer->setBuffer(); + + if (textures) + { + int materialCnt = instance.mModel->mMaterialList.size(); + if (i < materialCnt) + { + const std::string& binding = instance.mModel->mMaterialList[i]; + const LLImportMaterial& material = instance.mMaterial[binding]; + + gGL.diffuseColor4fv(material.mDiffuseColor.mV); + + // Find the tex for this material, bind it, and add it to our set + // + LLViewerFetchedTexture* tex = bindMaterialDiffuseTexture(material); + if (tex) + { + mTextureSet.insert(tex); + } + } + } + else + { + gGL.diffuseColor4fv(PREVIEW_BASE_COL.mV); + } + + buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts() - 1, buffer->getNumIndices(), 0); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gGL.diffuseColor4fv(PREVIEW_EDGE_COL.mV); + if (edges) + { + glLineWidth(PREVIEW_EDGE_WIDTH); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts() - 1, buffer->getNumIndices(), 0); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + glLineWidth(1.f); + } + buffer->unmapBuffer(); + } + gGL.popMatrix(); + } + + if (physics) + { + glClear(GL_DEPTH_BUFFER_BIT); + + for (U32 pass = 0; pass < 2; pass++) + { + if (pass == 0) + { //depth only pass + gGL.setColorMask(false, false); + } + else + { + gGL.setColorMask(true, true); + } + + //enable alpha blending on second pass but not first pass + LLGLState blend(GL_BLEND, pass); + + gGL.blendFunc(LLRender::BF_SOURCE_ALPHA, LLRender::BF_ONE_MINUS_SOURCE_ALPHA); + + for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter) + { + LLModelInstance& instance = *iter; + + LLModel* model = instance.mLOD[LLModel::LOD_PHYSICS]; + + if (!model) + { + continue; + } + + gGL.pushMatrix(); + LLMatrix4 mat = instance.mTransform; + + gGL.multMatrix((GLfloat*)mat.mMatrix); + + + bool render_mesh = true; + LLPhysicsDecomp* decomp = gMeshRepo.mDecompThread; + if (decomp) + { + LLMutexLock(decomp->mMutex); + + LLModel::Decomposition& physics = model->mPhysics; + + if (!physics.mHull.empty()) + { + render_mesh = false; + + if (physics.mMesh.empty()) + { //build vertex buffer for physics mesh + gMeshRepo.buildPhysicsMesh(physics); + } + + if (!physics.mMesh.empty()) + { //render hull instead of mesh + // SL-16993 physics.mMesh[i].mNormals were being used to light the exploded + // analyzed physics shape but the drawArrays() interface changed + // causing normal data <0,0,0> to be passed to the shader. + // The Phyics Preview shader uses plain vertex coloring so the physics hull is full lit. + // We could also use interface/ui shaders. + gObjectPreviewProgram.unbind(); + gPhysicsPreviewProgram.bind(); + + for (U32 i = 0; i < physics.mMesh.size(); ++i) + { + if (explode > 0.f) + { + gGL.pushMatrix(); + + LLVector3 offset = model->mHullCenter[i] - model->mCenterOfHullCenters; + offset *= explode; + + gGL.translatef(offset.mV[0], offset.mV[1], offset.mV[2]); + } + + static std::vector hull_colors; + + if (i + 1 >= hull_colors.size()) + { + hull_colors.push_back(LLColor4U(rand() % 128 + 127, rand() % 128 + 127, rand() % 128 + 127, 128)); + } + + gGL.diffuseColor4ubv(hull_colors[i].mV); + LLVertexBuffer::drawArrays(LLRender::TRIANGLES, physics.mMesh[i].mPositions); + + if (explode > 0.f) + { + gGL.popMatrix(); + } + } + + gPhysicsPreviewProgram.unbind(); + gObjectPreviewProgram.bind(); + } + } + } + + if (render_mesh) + { + U32 num_models = mVertexBuffer[LLModel::LOD_PHYSICS][model].size(); + if (pass > 0){ + for (U32 i = 0; i < num_models; ++i) + { + LLVertexBuffer* buffer = mVertexBuffer[LLModel::LOD_PHYSICS][model][i]; + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gGL.diffuseColor4fv(PREVIEW_PSYH_FILL_COL.mV); + + buffer->setBuffer(); + buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts() - 1, buffer->getNumIndices(), 0); + + gGL.diffuseColor4fv(PREVIEW_PSYH_EDGE_COL.mV); + glLineWidth(PREVIEW_PSYH_EDGE_WIDTH); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts() - 1, buffer->getNumIndices(), 0); + + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + glLineWidth(1.f); + + buffer->unmapBuffer(); + } + } + } + gGL.popMatrix(); + } + + // only do this if mDegenerate was set in the preceding mesh checks [Check this if the ordering ever breaks] + if (mHasDegenerate) + { + glLineWidth(PREVIEW_DEG_EDGE_WIDTH); + glPointSize(PREVIEW_DEG_POINT_SIZE); + gPipeline.enableLightsFullbright(); + //show degenerate triangles + LLGLDepthTest depth(GL_TRUE, GL_TRUE, GL_ALWAYS); + LLGLDisable cull(GL_CULL_FACE); + gGL.diffuseColor4f(1.f, 0.f, 0.f, 1.f); + const LLVector4a scale(0.5f); + + for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter) + { + LLModelInstance& instance = *iter; + + LLModel* model = instance.mLOD[LLModel::LOD_PHYSICS]; + + if (!model) + { + continue; + } + + gGL.pushMatrix(); + LLMatrix4 mat = instance.mTransform; + + gGL.multMatrix((GLfloat*)mat.mMatrix); + + + LLPhysicsDecomp* decomp = gMeshRepo.mDecompThread; + if (decomp) + { + LLMutexLock(decomp->mMutex); + + LLModel::Decomposition& physics = model->mPhysics; + + if (physics.mHull.empty()) + { + U32 num_models = mVertexBuffer[LLModel::LOD_PHYSICS][model].size(); + for (U32 v = 0; v < num_models; ++v) + { + LLVertexBuffer* buffer = mVertexBuffer[LLModel::LOD_PHYSICS][model][v]; + + buffer->setBuffer(); + + LLStrider pos_strider; + buffer->getVertexStrider(pos_strider, 0); + LLVector4a* pos = (LLVector4a*)pos_strider.get(); + + LLStrider idx; + buffer->getIndexStrider(idx, 0); + + for (U32 i = 0; i < buffer->getNumIndices(); i += 3) + { + LLVector4a v1; v1.setMul(pos[*idx++], scale); + LLVector4a v2; v2.setMul(pos[*idx++], scale); + LLVector4a v3; v3.setMul(pos[*idx++], scale); + + if (ll_is_degenerate(v1, v2, v3)) + { + buffer->draw(LLRender::LINE_LOOP, 3, i); + buffer->draw(LLRender::POINTS, 3, i); + } + } + + buffer->unmapBuffer(); + } + } + } + + gGL.popMatrix(); + } + glLineWidth(1.f); + glPointSize(1.f); + gPipeline.enableLightsPreview(); + gGL.setSceneBlendType(LLRender::BT_ALPHA); + } + } + } + } + else + { + target_pos = getPreviewAvatar()->getPositionAgent(); + getPreviewAvatar()->clearAttachmentOverrides(); // removes pelvis fixup + LLUUID fake_mesh_id; + fake_mesh_id.generate(); + getPreviewAvatar()->addPelvisFixup(mPelvisZOffset, fake_mesh_id); + bool pelvis_recalc = false; + + LLViewerCamera::getInstance()->setOriginAndLookAt( + target_pos + ((LLVector3(camera_distance, 0.f, 0.f) + offset) * av_rot), // camera + LLVector3::z_axis, // up + target_pos); // point of interest + + for (LLModelLoader::scene::iterator iter = mScene[mPreviewLOD].begin(); iter != mScene[mPreviewLOD].end(); ++iter) + { + for (LLModelLoader::model_instance_list::iterator model_iter = iter->second.begin(); model_iter != iter->second.end(); ++model_iter) + { + LLModelInstance& instance = *model_iter; + LLModel* model = instance.mModel; + + if (!model->mSkinWeights.empty()) + { + const LLMeshSkinInfo *skin = &model->mSkinInfo; + LLSkinningUtil::initJointNums(&model->mSkinInfo, getPreviewAvatar());// inits skin->mJointNums if nessesary + U32 joint_count = LLSkinningUtil::getMeshJointCount(skin); + U32 bind_count = skin->mAlternateBindMatrix.size(); + + if (joint_overrides + && bind_count > 0 + && joint_count == bind_count) + { + // mesh_id is used to determine which mesh gets to + // set the joint offset, in the event of a conflict. Since + // we don't know the mesh id yet, we can't guarantee that + // joint offsets will be applied with the same priority as + // in the uploaded model. If the file contains multiple + // meshes with conflicting joint offsets, preview may be + // incorrect. + LLUUID fake_mesh_id; + fake_mesh_id.generate(); + for (U32 j = 0; j < joint_count; ++j) + { + LLJoint *joint = getPreviewAvatar()->getJoint(skin->mJointNums[j]); + if (joint) + { + const LLVector3& jointPos = LLVector3(skin->mAlternateBindMatrix[j].getTranslation()); + if (joint->aboveJointPosThreshold(jointPos)) + { + bool override_changed; + joint->addAttachmentPosOverride(jointPos, fake_mesh_id, "model", override_changed); + + if (override_changed) + { + //If joint is a pelvis then handle old/new pelvis to foot values + if (joint->getName() == "mPelvis")// or skin->mJointNames[j] + { + pelvis_recalc = true; + } + } + if (skin->mLockScaleIfJointPosition) + { + // Note that unlike positions, there's no threshold check here, + // just a lock at the default value. + joint->addAttachmentScaleOverride(joint->getDefaultScale(), fake_mesh_id, "model"); + } + } + } + } + } + + for (U32 i = 0, e = mVertexBuffer[mPreviewLOD][model].size(); i < e; ++i) + { + LLVertexBuffer* buffer = mVertexBuffer[mPreviewLOD][model][i]; + + model->mSkinInfo.updateHash(); + LLRenderPass::uploadMatrixPalette(mPreviewAvatar, &model->mSkinInfo); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + if (textures) + { + int materialCnt = instance.mModel->mMaterialList.size(); + if (i < materialCnt) + { + const std::string& binding = instance.mModel->mMaterialList[i]; + const LLImportMaterial& material = instance.mMaterial[binding]; + + gGL.diffuseColor4fv(material.mDiffuseColor.mV); + + // Find the tex for this material, bind it, and add it to our set + // + LLViewerFetchedTexture* tex = bindMaterialDiffuseTexture(material); + if (tex) + { + mTextureSet.insert(tex); + } + } + } + else + { + gGL.diffuseColor4fv(PREVIEW_BASE_COL.mV); + } + + buffer->setBuffer(); + buffer->draw(LLRender::TRIANGLES, buffer->getNumIndices(), 0); + + if (edges) + { + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gGL.diffuseColor4fv(PREVIEW_EDGE_COL.mV); + glLineWidth(PREVIEW_EDGE_WIDTH); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + buffer->draw(LLRender::TRIANGLES, buffer->getNumIndices(), 0); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + glLineWidth(1.f); + } + } + } + } + } + + if (joint_positions) + { + LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; + if (shader) + { + gDebugProgram.bind(); + } + getPreviewAvatar()->renderCollisionVolumes(); + if (fmp->mTabContainer->getCurrentPanelIndex() == fmp->mAvatarTabIndex) + { + getPreviewAvatar()->renderBones(fmp->mSelectedJointName); + } + else + { + getPreviewAvatar()->renderBones(); + } + renderGroundPlane(mPelvisZOffset); + if (shader) + { + shader->bind(); + } + } + + if (pelvis_recalc) + { + // size/scale recalculation + getPreviewAvatar()->postPelvisSetRecalc(); + } + } + } + + gObjectPreviewProgram.unbind(); + + gGL.popMatrix(); + + return true; +} + +void LLModelPreview::renderGroundPlane(float z_offset) +{ // Not necesarilly general - beware - but it seems to meet the needs of LLModelPreview::render + + gGL.diffuseColor3f( 1.0f, 0.0f, 1.0f ); + + gGL.begin(LLRender::LINES); + gGL.vertex3fv(mGroundPlane[0].mV); + gGL.vertex3fv(mGroundPlane[1].mV); + + gGL.vertex3fv(mGroundPlane[1].mV); + gGL.vertex3fv(mGroundPlane[2].mV); + + gGL.vertex3fv(mGroundPlane[2].mV); + gGL.vertex3fv(mGroundPlane[3].mV); + + gGL.vertex3fv(mGroundPlane[3].mV); + gGL.vertex3fv(mGroundPlane[0].mV); + + gGL.end(); +} + + +//----------------------------------------------------------------------------- +// refresh() +//----------------------------------------------------------------------------- +void LLModelPreview::refresh() +{ + mNeedsUpdate = true; +} + +//----------------------------------------------------------------------------- +// rotate() +//----------------------------------------------------------------------------- +void LLModelPreview::rotate(F32 yaw_radians, F32 pitch_radians) +{ + mCameraYaw = mCameraYaw + yaw_radians; + + mCameraPitch = llclamp(mCameraPitch + pitch_radians, F_PI_BY_TWO * -0.8f, F_PI_BY_TWO * 0.8f); +} + +//----------------------------------------------------------------------------- +// zoom() +//----------------------------------------------------------------------------- +void LLModelPreview::zoom(F32 zoom_amt) +{ + F32 new_zoom = mCameraZoom + zoom_amt; + // TODO: stop clamping in render + mCameraZoom = llclamp(new_zoom, 1.f, PREVIEW_ZOOM_LIMIT); +} + +void LLModelPreview::pan(F32 right, F32 up) +{ + bool skin_weight = mViewOption["show_skin_weight"]; + F32 camera_distance = skin_weight ? SKIN_WEIGHT_CAMERA_DISTANCE : mCameraDistance; + mCameraOffset.mV[VY] = llclamp(mCameraOffset.mV[VY] + right * camera_distance / mCameraZoom, -1.f, 1.f); + mCameraOffset.mV[VZ] = llclamp(mCameraOffset.mV[VZ] + up * camera_distance / mCameraZoom, -1.f, 1.f); +} + +void LLModelPreview::setPreviewLOD(S32 lod) +{ + lod = llclamp(lod, 0, (S32)LLModel::LOD_HIGH); + + if (lod != mPreviewLOD) + { + mPreviewLOD = lod; + + LLComboBox* combo_box = mFMP->getChild("preview_lod_combo"); + combo_box->setCurrentByIndex((NUM_LOD - 1) - mPreviewLOD); // combo box list of lods is in reverse order + mFMP->childSetValue("lod_file_" + lod_name[mPreviewLOD], mLODFile[mPreviewLOD]); + + LLColor4 highlight_color = LLUIColorTable::instance().getColor("MeshImportTableHighlightColor"); + LLColor4 normal_color = LLUIColorTable::instance().getColor("MeshImportTableNormalColor"); + + for (S32 i = 0; i <= LLModel::LOD_HIGH; ++i) + { + const LLColor4& color = (i == lod) ? highlight_color : normal_color; + + mFMP->childSetColor(lod_status_name[i], color); + mFMP->childSetColor(lod_label_name[i], color); + mFMP->childSetColor(lod_triangles_name[i], color); + mFMP->childSetColor(lod_vertices_name[i], color); + } + + LLFloaterModelPreview* fmp = (LLFloaterModelPreview*)mFMP; + if (fmp) + { + // make preview repopulate tab + fmp->clearAvatarTab(); + } + } + refresh(); + updateStatusMessages(); +} + +//static +void LLModelPreview::textureLoadedCallback( + bool success, + LLViewerFetchedTexture *src_vi, + LLImageRaw* src, + LLImageRaw* src_aux, + S32 discard_level, + bool final, + void* userdata) +{ + LLModelPreview* preview = (LLModelPreview*)userdata; + preview->refresh(); + + if (final && preview->mModelLoader) + { + if (preview->mModelLoader->mNumOfFetchingTextures > 0) + { + preview->mModelLoader->mNumOfFetchingTextures--; + } + } +} + +// static +bool LLModelPreview::lodQueryCallback() +{ + // not the best solution, but model preview belongs to floater + // so it is an easy way to check that preview still exists. + LLFloaterModelPreview* fmp = LLFloaterModelPreview::sInstance; + if (fmp && fmp->mModelPreview) + { + LLModelPreview* preview = fmp->mModelPreview; + if (preview->mLodsQuery.size() > 0) + { + S32 lod = preview->mLodsQuery.back(); + preview->mLodsQuery.pop_back(); + preview->genMeshOptimizerLODs(lod, MESH_OPTIMIZER_AUTO); + + if (preview->mLookUpLodFiles && (lod == LLModel::LOD_HIGH)) + { + preview->lookupLODModelFiles(LLModel::LOD_HIGH); + } + + // return false to continue cycle + return preview->mLodsQuery.empty(); + } + } + // nothing to process + return true; +} + +void LLModelPreview::onLODMeshOptimizerParamCommit(S32 requested_lod, bool enforce_tri_limit, S32 mode) +{ + if (!mLODFrozen) + { + genMeshOptimizerLODs(requested_lod, mode, 3, enforce_tri_limit); + refresh(); + mDirty = true; + } +} + diff --git a/indra/newview/llmodelpreview.h b/indra/newview/llmodelpreview.h index f554a35531..c615070105 100644 --- a/indra/newview/llmodelpreview.h +++ b/indra/newview/llmodelpreview.h @@ -1,345 +1,345 @@ -/** - * @file llmodelpreview.h - * @brief LLModelPreview class definition, class - * responsible for model preview inside LLFloaterModelPreview - * - * $LicenseInfo:firstyear=2020&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2020, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLMODELPREVIEW_H -#define LL_LLMODELPREVIEW_H - -#include "lldynamictexture.h" -#include "llfloatermodelpreview.h" -#include "llmeshrepository.h" -#include "llmodelloader.h" //NUM_LOD -#include "llmodel.h" - -class LLJoint; -class LLVOAvatar; -class LLTextBox; -class LLVertexBuffer; -class DAE; -class daeElement; -class domProfile_COMMON; -class domInstance_geometry; -class domNode; -class domTranslate; -class domController; -class domSkin; -class domMesh; - -// const strings needed by classes that use model preivew -static const std::string lod_name[NUM_LOD + 1] = -{ - "lowest", - "low", - "medium", - "high", - "I went off the end of the lod_name array. Me so smart." -}; - -static const std::string lod_triangles_name[NUM_LOD + 1] = -{ - "lowest_triangles", - "low_triangles", - "medium_triangles", - "high_triangles", - "I went off the end of the lod_triangles_name array. Me so smart." -}; - -static const std::string lod_vertices_name[NUM_LOD + 1] = -{ - "lowest_vertices", - "low_vertices", - "medium_vertices", - "high_vertices", - "I went off the end of the lod_vertices_name array. Me so smart." -}; - -static const std::string lod_status_name[NUM_LOD + 1] = -{ - "lowest_status", - "low_status", - "medium_status", - "high_status", - "I went off the end of the lod_status_name array. Me so smart." -}; - -static const std::string lod_icon_name[NUM_LOD + 1] = -{ - "status_icon_lowest", - "status_icon_low", - "status_icon_medium", - "status_icon_high", - "I went off the end of the lod_status_name array. Me so smart." -}; - -static const std::string lod_status_image[NUM_LOD + 1] = -{ - "ModelImport_Status_Good", - "ModelImport_Status_Warning", - "ModelImport_Status_Error", - "I went off the end of the lod_status_image array. Me so smart." -}; - -static const std::string lod_label_name[NUM_LOD + 1] = -{ - "lowest_label", - "low_label", - "medium_label", - "high_label", - "I went off the end of the lod_label_name array. Me so smart." -}; - -class LLModelPreview : public LLViewerDynamicTexture, public LLMutex -{ - LOG_CLASS(LLModelPreview); - - typedef boost::signals2::signal details_signal_t; - typedef boost::signals2::signal model_loaded_signal_t; - typedef boost::signals2::signal model_updated_signal_t; - -public: - - typedef enum - { - LOD_FROM_FILE = 0, - MESH_OPTIMIZER_AUTO, // automatically selects method based on model or face - MESH_OPTIMIZER_PRECISE, // combines faces into a single model, simplifies, then splits back into faces - MESH_OPTIMIZER_SLOPPY, // uses sloppy method, works per face - USE_LOD_ABOVE, - } eLoDMode; - - typedef enum - { - LIMIT_TRIANGLES = 0, - LIMIT_ERROR_TRESHOLD, - } eLoDLimit; - -public: - // Todo: model preview shouldn't need floater dependency, it - // should just expose data to floater, not control flaoter like it does - LLModelPreview(S32 width, S32 height, LLFloater* fmp); - virtual ~LLModelPreview(); - - void resetPreviewTarget(); - void setPreviewTarget(F32 distance); - void setTexture(U32 name) { mTextureName = name; } - - void setPhysicsFromLOD(S32 lod); - bool render(); - void update(); - void genBuffers(S32 lod, bool skinned); - void clearBuffers(); - void refresh(); - void rotate(F32 yaw_radians, F32 pitch_radians); - void zoom(F32 zoom_amt); - void pan(F32 right, F32 up); - virtual bool needsRender() { return mNeedsUpdate; } - void setPreviewLOD(S32 lod); - void clearModel(S32 lod); - void getJointAliases(JointMap& joint_map); - void loadModel(std::string filename, S32 lod, bool force_disable_slm = false); - void loadModelCallback(S32 lod); - bool lodsReady() { return !mGenLOD && mLodsQuery.empty(); } - void queryLODs() { mGenLOD = true; }; - void genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 decimation = 3, bool enforce_tri_limit = false); - void generateNormals(); - void restoreNormals(); - void updateDimentionsAndOffsets(); - void rebuildUploadData(); - void saveUploadData(bool save_skinweights, bool save_joint_positions, bool lock_scale_if_joint_position); - void saveUploadData(const std::string& filename, bool save_skinweights, bool save_joint_positions, bool lock_scale_if_joint_position); - void clearIncompatible(S32 lod); - void updateStatusMessages(); - void updateLodControls(S32 lod); - void onLODMeshOptimizerParamCommit(S32 lod, bool enforce_tri_limit, S32 mode); - void addEmptyFace(LLModel* pTarget); - - const bool getModelPivot(void) const { return mHasPivot; } - void setHasPivot(bool val) { mHasPivot = val; } - void setModelPivot(const LLVector3& pivot) { mModelPivot = pivot; } - - //Is a rig valid so that it can be used as a criteria for allowing for uploading of joint positions - //Accessors for joint position upload friendly rigs - const bool isRigValidForJointPositionUpload(void) const { return mRigValidJointUpload; } - void setRigValidForJointPositionUpload(bool rigValid) { mRigValidJointUpload = rigValid; } - - //Accessors for the legacy rigs - const bool isLegacyRigValid(void) const { return mLegacyRigFlags == 0; } - U32 getLegacyRigFlags() const { return mLegacyRigFlags; } - void setLegacyRigFlags(U32 rigFlags) { mLegacyRigFlags = rigFlags; } - - static void textureLoadedCallback(bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* src_aux, S32 discard_level, bool final, void* userdata); - static bool lodQueryCallback(); - - boost::signals2::connection setDetailsCallback(const details_signal_t::slot_type& cb){ return mDetailsSignal.connect(cb); } - boost::signals2::connection setModelLoadedCallback(const model_loaded_signal_t::slot_type& cb){ return mModelLoadedSignal.connect(cb); } - boost::signals2::connection setModelUpdatedCallback(const model_updated_signal_t::slot_type& cb){ return mModelUpdatedSignal.connect(cb); } - - void setLoadState(U32 state) { mLoadState = state; } - U32 getLoadState() { return mLoadState; } - - static bool sIgnoreLoadedCallback; - std::vector mLodsQuery; - std::vector mLodsWithParsingError; - bool mHasDegenerate; - -protected: - - static void loadedCallback(LLModelLoader::scene& scene, LLModelLoader::model_list& model_list, S32 lod, void* opaque); - static void stateChangedCallback(U32 state, void* opaque); - - static LLJoint* lookupJointByName(const std::string&, void* opaque); - static U32 loadTextures(LLImportMaterial& material, void* opaque); - - void lookupLODModelFiles(S32 lod); - -private: - //Utility function for controller vertex compare - bool verifyCount(int expected, int result); - //Creates the dummy avatar for the preview window - void createPreviewAvatar(void); - //Accessor for the dummy avatar - LLVOAvatar* getPreviewAvatar(void) { return mPreviewAvatar; } - // Count amount of original models, excluding sub-models - static U32 countRootModels(LLModelLoader::model_list models); - LLVector3 mGroundPlane[4]; - void renderGroundPlane(float z_offset = 0.0f); - /// Indicates whether we should warn of high-lod meshes that do not have a corresponding physics mesh. - /// Reset when resetting the modelpreview (i.e., when the uploader dialog is created or reset), and when - /// about to process a physics file. Set to true immediately after the file is loaded (before rebuildUploadData()). - /// - /// (The rules for mapping the correspondence of high-lod meshes to physics meshes are complex. When - /// lod rendering meshes are used, there is never an unmatched mesh. Nor is there a mismatch when - /// the high-lod file and physics file have ony one mesh each. In these cases, this value is moot. - /// When there are multiple meshes in each file, they are matched by name or order, and some meshes - /// are broken up by limitations into multiple objects, and thus there can be mismatches.) - bool mWarnOfUnmatchedPhyicsMeshes{false}; - /// A mesh to use as the default physics shape in only those cases where the physics shape is not otherwise specified. - /// It is set only when the user chooses a physics shape file that contains a mesh with a name that matches DEFAULT_PHYSICS_MESH_NAME. - /// It is reset when such a name is not found, and when resetting the modelpreview. - /// Not read unless mWarnOfUnmatchedPhyicsMeshes is true. - LLPointer mDefaultPhysicsShapeP; - - typedef enum - { - MESH_OPTIMIZER_FULL, - MESH_OPTIMIZER_NO_NORMALS, - MESH_OPTIMIZER_NO_UVS, - MESH_OPTIMIZER_NO_TOPOLOGY, - } eSimplificationMode; - - // Merges faces into single mesh, simplifies using mesh optimizer, - // then splits back into faces. - // Returns reached simplification ratio. -1 in case of a failure. - F32 genMeshOptimizerPerModel(LLModel *base_model, LLModel *target_model, F32 indices_ratio, F32 error_threshold, eSimplificationMode simplification_mode); - // Simplifies specified face using mesh optimizer. - // Returns reached simplification ratio. -1 in case of a failure. - F32 genMeshOptimizerPerFace(LLModel *base_model, LLModel *target_model, U32 face_idx, F32 indices_ratio, F32 error_threshold, eSimplificationMode simplification_mode); - -protected: - friend class LLModelLoader; - friend class LLFloaterModelPreview; - friend class LLFloaterModelPreview::DecompRequest; - friend class LLPhysicsDecomp; - - LLFloater* mFMP; - - bool mNeedsUpdate; - bool mDirty; - bool mGenLOD; - U32 mTextureName; - F32 mCameraDistance; - F32 mCameraYaw; - F32 mCameraPitch; - F32 mCameraZoom; - LLVector3 mCameraOffset; - LLVector3 mPreviewTarget; - LLVector3 mPreviewScale; - S32 mPreviewLOD; - S32 mPhysicsSearchLOD; - std::string mLODFile[LLModel::NUM_LODS]; - bool mLoading; - U32 mLoadState; - bool mResetJoints; - bool mModelNoErrors; - bool mLookUpLodFiles; - - std::map mViewOption; - - // Model generation parameters (must rebuild object if these change) - bool mLODFrozen; - U32 mRequestedLoDMode[LLModel::NUM_LODS]; - S32 mRequestedTriangleCount[LLModel::NUM_LODS]; - F32 mRequestedErrorThreshold[LLModel::NUM_LODS]; - F32 mRequestedCreaseAngle[LLModel::NUM_LODS]; - - LLModelLoader* mModelLoader; - - LLModelLoader::scene mScene[LLModel::NUM_LODS]; - LLModelLoader::scene mBaseScene; - - LLModelLoader::model_list mModel[LLModel::NUM_LODS]; - LLModelLoader::model_list mBaseModel; - - typedef std::vector v_LLVolumeFace_t; - typedef std::vector vv_LLVolumeFace_t; - - vv_LLVolumeFace_t mModelFacesCopy[LLModel::NUM_LODS]; - vv_LLVolumeFace_t mBaseModelFacesCopy; - - U32 mGroup; - - // Amount of triangles in original(base) model - U32 mMaxTriangleLimit; - - LLMeshUploadThread::instance_list mUploadData; - std::set mTextureSet; - - //map of vertex buffers to models (one vertex buffer in vector per face in model - std::map > > mVertexBuffer[LLModel::NUM_LODS + 1]; - - details_signal_t mDetailsSignal; - model_loaded_signal_t mModelLoadedSignal; - model_updated_signal_t mModelUpdatedSignal; - - LLVector3 mModelPivot; - bool mHasPivot; - - float mPelvisZOffset; - - bool mRigValidJointUpload; - U32 mLegacyRigFlags; - - bool mLastJointUpdate; - bool mFirstSkinUpdate; - - JointNameSet mJointsFromNode; - JointTransformMap mJointTransformMap; - - LLPointer mPreviewAvatar; - LLCachedControl mImporterDebug; -}; - -#endif // LL_LLMODELPREVIEW_H +/** + * @file llmodelpreview.h + * @brief LLModelPreview class definition, class + * responsible for model preview inside LLFloaterModelPreview + * + * $LicenseInfo:firstyear=2020&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2020, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLMODELPREVIEW_H +#define LL_LLMODELPREVIEW_H + +#include "lldynamictexture.h" +#include "llfloatermodelpreview.h" +#include "llmeshrepository.h" +#include "llmodelloader.h" //NUM_LOD +#include "llmodel.h" + +class LLJoint; +class LLVOAvatar; +class LLTextBox; +class LLVertexBuffer; +class DAE; +class daeElement; +class domProfile_COMMON; +class domInstance_geometry; +class domNode; +class domTranslate; +class domController; +class domSkin; +class domMesh; + +// const strings needed by classes that use model preivew +static const std::string lod_name[NUM_LOD + 1] = +{ + "lowest", + "low", + "medium", + "high", + "I went off the end of the lod_name array. Me so smart." +}; + +static const std::string lod_triangles_name[NUM_LOD + 1] = +{ + "lowest_triangles", + "low_triangles", + "medium_triangles", + "high_triangles", + "I went off the end of the lod_triangles_name array. Me so smart." +}; + +static const std::string lod_vertices_name[NUM_LOD + 1] = +{ + "lowest_vertices", + "low_vertices", + "medium_vertices", + "high_vertices", + "I went off the end of the lod_vertices_name array. Me so smart." +}; + +static const std::string lod_status_name[NUM_LOD + 1] = +{ + "lowest_status", + "low_status", + "medium_status", + "high_status", + "I went off the end of the lod_status_name array. Me so smart." +}; + +static const std::string lod_icon_name[NUM_LOD + 1] = +{ + "status_icon_lowest", + "status_icon_low", + "status_icon_medium", + "status_icon_high", + "I went off the end of the lod_status_name array. Me so smart." +}; + +static const std::string lod_status_image[NUM_LOD + 1] = +{ + "ModelImport_Status_Good", + "ModelImport_Status_Warning", + "ModelImport_Status_Error", + "I went off the end of the lod_status_image array. Me so smart." +}; + +static const std::string lod_label_name[NUM_LOD + 1] = +{ + "lowest_label", + "low_label", + "medium_label", + "high_label", + "I went off the end of the lod_label_name array. Me so smart." +}; + +class LLModelPreview : public LLViewerDynamicTexture, public LLMutex +{ + LOG_CLASS(LLModelPreview); + + typedef boost::signals2::signal details_signal_t; + typedef boost::signals2::signal model_loaded_signal_t; + typedef boost::signals2::signal model_updated_signal_t; + +public: + + typedef enum + { + LOD_FROM_FILE = 0, + MESH_OPTIMIZER_AUTO, // automatically selects method based on model or face + MESH_OPTIMIZER_PRECISE, // combines faces into a single model, simplifies, then splits back into faces + MESH_OPTIMIZER_SLOPPY, // uses sloppy method, works per face + USE_LOD_ABOVE, + } eLoDMode; + + typedef enum + { + LIMIT_TRIANGLES = 0, + LIMIT_ERROR_TRESHOLD, + } eLoDLimit; + +public: + // Todo: model preview shouldn't need floater dependency, it + // should just expose data to floater, not control flaoter like it does + LLModelPreview(S32 width, S32 height, LLFloater* fmp); + virtual ~LLModelPreview(); + + void resetPreviewTarget(); + void setPreviewTarget(F32 distance); + void setTexture(U32 name) { mTextureName = name; } + + void setPhysicsFromLOD(S32 lod); + bool render(); + void update(); + void genBuffers(S32 lod, bool skinned); + void clearBuffers(); + void refresh(); + void rotate(F32 yaw_radians, F32 pitch_radians); + void zoom(F32 zoom_amt); + void pan(F32 right, F32 up); + virtual bool needsRender() { return mNeedsUpdate; } + void setPreviewLOD(S32 lod); + void clearModel(S32 lod); + void getJointAliases(JointMap& joint_map); + void loadModel(std::string filename, S32 lod, bool force_disable_slm = false); + void loadModelCallback(S32 lod); + bool lodsReady() { return !mGenLOD && mLodsQuery.empty(); } + void queryLODs() { mGenLOD = true; }; + void genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 decimation = 3, bool enforce_tri_limit = false); + void generateNormals(); + void restoreNormals(); + void updateDimentionsAndOffsets(); + void rebuildUploadData(); + void saveUploadData(bool save_skinweights, bool save_joint_positions, bool lock_scale_if_joint_position); + void saveUploadData(const std::string& filename, bool save_skinweights, bool save_joint_positions, bool lock_scale_if_joint_position); + void clearIncompatible(S32 lod); + void updateStatusMessages(); + void updateLodControls(S32 lod); + void onLODMeshOptimizerParamCommit(S32 lod, bool enforce_tri_limit, S32 mode); + void addEmptyFace(LLModel* pTarget); + + const bool getModelPivot(void) const { return mHasPivot; } + void setHasPivot(bool val) { mHasPivot = val; } + void setModelPivot(const LLVector3& pivot) { mModelPivot = pivot; } + + //Is a rig valid so that it can be used as a criteria for allowing for uploading of joint positions + //Accessors for joint position upload friendly rigs + const bool isRigValidForJointPositionUpload(void) const { return mRigValidJointUpload; } + void setRigValidForJointPositionUpload(bool rigValid) { mRigValidJointUpload = rigValid; } + + //Accessors for the legacy rigs + const bool isLegacyRigValid(void) const { return mLegacyRigFlags == 0; } + U32 getLegacyRigFlags() const { return mLegacyRigFlags; } + void setLegacyRigFlags(U32 rigFlags) { mLegacyRigFlags = rigFlags; } + + static void textureLoadedCallback(bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* src_aux, S32 discard_level, bool final, void* userdata); + static bool lodQueryCallback(); + + boost::signals2::connection setDetailsCallback(const details_signal_t::slot_type& cb){ return mDetailsSignal.connect(cb); } + boost::signals2::connection setModelLoadedCallback(const model_loaded_signal_t::slot_type& cb){ return mModelLoadedSignal.connect(cb); } + boost::signals2::connection setModelUpdatedCallback(const model_updated_signal_t::slot_type& cb){ return mModelUpdatedSignal.connect(cb); } + + void setLoadState(U32 state) { mLoadState = state; } + U32 getLoadState() { return mLoadState; } + + static bool sIgnoreLoadedCallback; + std::vector mLodsQuery; + std::vector mLodsWithParsingError; + bool mHasDegenerate; + +protected: + + static void loadedCallback(LLModelLoader::scene& scene, LLModelLoader::model_list& model_list, S32 lod, void* opaque); + static void stateChangedCallback(U32 state, void* opaque); + + static LLJoint* lookupJointByName(const std::string&, void* opaque); + static U32 loadTextures(LLImportMaterial& material, void* opaque); + + void lookupLODModelFiles(S32 lod); + +private: + //Utility function for controller vertex compare + bool verifyCount(int expected, int result); + //Creates the dummy avatar for the preview window + void createPreviewAvatar(void); + //Accessor for the dummy avatar + LLVOAvatar* getPreviewAvatar(void) { return mPreviewAvatar; } + // Count amount of original models, excluding sub-models + static U32 countRootModels(LLModelLoader::model_list models); + LLVector3 mGroundPlane[4]; + void renderGroundPlane(float z_offset = 0.0f); + /// Indicates whether we should warn of high-lod meshes that do not have a corresponding physics mesh. + /// Reset when resetting the modelpreview (i.e., when the uploader dialog is created or reset), and when + /// about to process a physics file. Set to true immediately after the file is loaded (before rebuildUploadData()). + /// + /// (The rules for mapping the correspondence of high-lod meshes to physics meshes are complex. When + /// lod rendering meshes are used, there is never an unmatched mesh. Nor is there a mismatch when + /// the high-lod file and physics file have ony one mesh each. In these cases, this value is moot. + /// When there are multiple meshes in each file, they are matched by name or order, and some meshes + /// are broken up by limitations into multiple objects, and thus there can be mismatches.) + bool mWarnOfUnmatchedPhyicsMeshes{false}; + /// A mesh to use as the default physics shape in only those cases where the physics shape is not otherwise specified. + /// It is set only when the user chooses a physics shape file that contains a mesh with a name that matches DEFAULT_PHYSICS_MESH_NAME. + /// It is reset when such a name is not found, and when resetting the modelpreview. + /// Not read unless mWarnOfUnmatchedPhyicsMeshes is true. + LLPointer mDefaultPhysicsShapeP; + + typedef enum + { + MESH_OPTIMIZER_FULL, + MESH_OPTIMIZER_NO_NORMALS, + MESH_OPTIMIZER_NO_UVS, + MESH_OPTIMIZER_NO_TOPOLOGY, + } eSimplificationMode; + + // Merges faces into single mesh, simplifies using mesh optimizer, + // then splits back into faces. + // Returns reached simplification ratio. -1 in case of a failure. + F32 genMeshOptimizerPerModel(LLModel *base_model, LLModel *target_model, F32 indices_ratio, F32 error_threshold, eSimplificationMode simplification_mode); + // Simplifies specified face using mesh optimizer. + // Returns reached simplification ratio. -1 in case of a failure. + F32 genMeshOptimizerPerFace(LLModel *base_model, LLModel *target_model, U32 face_idx, F32 indices_ratio, F32 error_threshold, eSimplificationMode simplification_mode); + +protected: + friend class LLModelLoader; + friend class LLFloaterModelPreview; + friend class LLFloaterModelPreview::DecompRequest; + friend class LLPhysicsDecomp; + + LLFloater* mFMP; + + bool mNeedsUpdate; + bool mDirty; + bool mGenLOD; + U32 mTextureName; + F32 mCameraDistance; + F32 mCameraYaw; + F32 mCameraPitch; + F32 mCameraZoom; + LLVector3 mCameraOffset; + LLVector3 mPreviewTarget; + LLVector3 mPreviewScale; + S32 mPreviewLOD; + S32 mPhysicsSearchLOD; + std::string mLODFile[LLModel::NUM_LODS]; + bool mLoading; + U32 mLoadState; + bool mResetJoints; + bool mModelNoErrors; + bool mLookUpLodFiles; + + std::map mViewOption; + + // Model generation parameters (must rebuild object if these change) + bool mLODFrozen; + U32 mRequestedLoDMode[LLModel::NUM_LODS]; + S32 mRequestedTriangleCount[LLModel::NUM_LODS]; + F32 mRequestedErrorThreshold[LLModel::NUM_LODS]; + F32 mRequestedCreaseAngle[LLModel::NUM_LODS]; + + LLModelLoader* mModelLoader; + + LLModelLoader::scene mScene[LLModel::NUM_LODS]; + LLModelLoader::scene mBaseScene; + + LLModelLoader::model_list mModel[LLModel::NUM_LODS]; + LLModelLoader::model_list mBaseModel; + + typedef std::vector v_LLVolumeFace_t; + typedef std::vector vv_LLVolumeFace_t; + + vv_LLVolumeFace_t mModelFacesCopy[LLModel::NUM_LODS]; + vv_LLVolumeFace_t mBaseModelFacesCopy; + + U32 mGroup; + + // Amount of triangles in original(base) model + U32 mMaxTriangleLimit; + + LLMeshUploadThread::instance_list mUploadData; + std::set mTextureSet; + + //map of vertex buffers to models (one vertex buffer in vector per face in model + std::map > > mVertexBuffer[LLModel::NUM_LODS + 1]; + + details_signal_t mDetailsSignal; + model_loaded_signal_t mModelLoadedSignal; + model_updated_signal_t mModelUpdatedSignal; + + LLVector3 mModelPivot; + bool mHasPivot; + + float mPelvisZOffset; + + bool mRigValidJointUpload; + U32 mLegacyRigFlags; + + bool mLastJointUpdate; + bool mFirstSkinUpdate; + + JointNameSet mJointsFromNode; + JointTransformMap mJointTransformMap; + + LLPointer mPreviewAvatar; + LLCachedControl mImporterDebug; +}; + +#endif // LL_LLMODELPREVIEW_H diff --git a/indra/newview/llmorphview.cpp b/indra/newview/llmorphview.cpp index f00ebeeb86..a5fc813f2f 100644 --- a/indra/newview/llmorphview.cpp +++ b/indra/newview/llmorphview.cpp @@ -1,166 +1,166 @@ -/** - * @file llmorphview.cpp - * @brief Container for Morph functionality - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llmorphview.h" - -#include "lljoint.h" - -#include "llagent.h" -#include "llagentcamera.h" -#include "lldrawable.h" -#include "lldrawpoolavatar.h" -#include "llface.h" -//#include "llfirstuse.h" -#include "llfloatertools.h" -#include "llresmgr.h" -#include "lltoolmgr.h" -#include "lltoolmorph.h" -#include "llviewercamera.h" -#include "llvoavatarself.h" -#include "llviewerwindow.h" -#include "pipeline.h" - -LLMorphView *gMorphView = NULL; - -constexpr F32 MORPH_NEAR_CLIP = 0.1f; - -//----------------------------------------------------------------------------- -// LLMorphView() -//----------------------------------------------------------------------------- -LLMorphView::LLMorphView(const LLMorphView::Params& p) -: LLView(p), - mCameraTargetJoint( NULL ), - mCameraOffset(-0.5f, 0.05f, 0.07f ), - mCameraTargetOffset(0.f, 0.f, 0.05f ), - mOldCameraNearClip( 0.f ), - mCameraPitch( 0.f ), - mCameraYaw( 0.f ), - mCameraDrivenByKeys( false ) -{} - -//----------------------------------------------------------------------------- -// initialize() -//----------------------------------------------------------------------------- -void LLMorphView::initialize() -{ - mCameraPitch = 0.f; - mCameraYaw = 0.f; - - if (!isAgentAvatarValid() || gAgentAvatarp->isDead()) - { - gAgentCamera.changeCameraToDefault(); - return; - } - - gAgentAvatarp->stopMotion( ANIM_AGENT_BODY_NOISE ); - gAgentAvatarp->mSpecialRenderMode = 3; - - // set up camera for close look at avatar - mOldCameraNearClip = LLViewerCamera::getInstance()->getNear(); - LLViewerCamera::getInstance()->setNear(MORPH_NEAR_CLIP); -} - -//----------------------------------------------------------------------------- -// shutdown() -//----------------------------------------------------------------------------- -void LLMorphView::shutdown() -{ - if (isAgentAvatarValid()) - { - gAgentAvatarp->startMotion( ANIM_AGENT_BODY_NOISE ); - gAgentAvatarp->mSpecialRenderMode = 0; - // reset camera - LLViewerCamera::getInstance()->setNear(mOldCameraNearClip); - } -} - - -//----------------------------------------------------------------------------- -// setVisible() -//----------------------------------------------------------------------------- -void LLMorphView::setVisible(bool visible) -{ - if( visible != getVisible() ) - { - LLView::setVisible(visible); - - if (visible) - { - // TODO: verify some user action has already opened outfit editor? - Nyx - initialize(); - - // First run dialog - //LLFirstUse::useAppearance(); - } - else - { - // TODO: verify some user action has already closed outfit editor ? - Nyx - shutdown(); - } - } -} - -void LLMorphView::updateCamera() -{ - if (!mCameraTargetJoint) - { - setCameraTargetJoint(gAgentAvatarp->getJoint("mHead")); - } - if (!isAgentAvatarValid()) return; - - LLJoint* root_joint = gAgentAvatarp->getRootJoint(); - if( !root_joint ) - { - return; - } - - const LLQuaternion& avatar_rot = root_joint->getWorldRotation(); - - LLVector3d joint_pos = gAgent.getPosGlobalFromAgent(mCameraTargetJoint->getWorldPosition()); - LLVector3d target_pos = joint_pos + mCameraTargetOffset * avatar_rot; - - LLQuaternion camera_rot_yaw(mCameraYaw, LLVector3::z_axis); - LLQuaternion camera_rot_pitch(mCameraPitch, LLVector3::y_axis); - - LLVector3d camera_pos = joint_pos + mCameraOffset * camera_rot_pitch * camera_rot_yaw * avatar_rot; - - gAgentCamera.setCameraPosAndFocusGlobal( camera_pos, target_pos, gAgent.getID() ); -} - -void LLMorphView::setCameraDrivenByKeys(bool b) -{ - if( mCameraDrivenByKeys != b ) - { - if( b ) - { - // Reset to the default camera position specified by mCameraPitch, mCameraYaw, etc. - updateCamera(); - } - mCameraDrivenByKeys = b; - } -} +/** + * @file llmorphview.cpp + * @brief Container for Morph functionality + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llmorphview.h" + +#include "lljoint.h" + +#include "llagent.h" +#include "llagentcamera.h" +#include "lldrawable.h" +#include "lldrawpoolavatar.h" +#include "llface.h" +//#include "llfirstuse.h" +#include "llfloatertools.h" +#include "llresmgr.h" +#include "lltoolmgr.h" +#include "lltoolmorph.h" +#include "llviewercamera.h" +#include "llvoavatarself.h" +#include "llviewerwindow.h" +#include "pipeline.h" + +LLMorphView *gMorphView = NULL; + +constexpr F32 MORPH_NEAR_CLIP = 0.1f; + +//----------------------------------------------------------------------------- +// LLMorphView() +//----------------------------------------------------------------------------- +LLMorphView::LLMorphView(const LLMorphView::Params& p) +: LLView(p), + mCameraTargetJoint( NULL ), + mCameraOffset(-0.5f, 0.05f, 0.07f ), + mCameraTargetOffset(0.f, 0.f, 0.05f ), + mOldCameraNearClip( 0.f ), + mCameraPitch( 0.f ), + mCameraYaw( 0.f ), + mCameraDrivenByKeys( false ) +{} + +//----------------------------------------------------------------------------- +// initialize() +//----------------------------------------------------------------------------- +void LLMorphView::initialize() +{ + mCameraPitch = 0.f; + mCameraYaw = 0.f; + + if (!isAgentAvatarValid() || gAgentAvatarp->isDead()) + { + gAgentCamera.changeCameraToDefault(); + return; + } + + gAgentAvatarp->stopMotion( ANIM_AGENT_BODY_NOISE ); + gAgentAvatarp->mSpecialRenderMode = 3; + + // set up camera for close look at avatar + mOldCameraNearClip = LLViewerCamera::getInstance()->getNear(); + LLViewerCamera::getInstance()->setNear(MORPH_NEAR_CLIP); +} + +//----------------------------------------------------------------------------- +// shutdown() +//----------------------------------------------------------------------------- +void LLMorphView::shutdown() +{ + if (isAgentAvatarValid()) + { + gAgentAvatarp->startMotion( ANIM_AGENT_BODY_NOISE ); + gAgentAvatarp->mSpecialRenderMode = 0; + // reset camera + LLViewerCamera::getInstance()->setNear(mOldCameraNearClip); + } +} + + +//----------------------------------------------------------------------------- +// setVisible() +//----------------------------------------------------------------------------- +void LLMorphView::setVisible(bool visible) +{ + if( visible != getVisible() ) + { + LLView::setVisible(visible); + + if (visible) + { + // TODO: verify some user action has already opened outfit editor? - Nyx + initialize(); + + // First run dialog + //LLFirstUse::useAppearance(); + } + else + { + // TODO: verify some user action has already closed outfit editor ? - Nyx + shutdown(); + } + } +} + +void LLMorphView::updateCamera() +{ + if (!mCameraTargetJoint) + { + setCameraTargetJoint(gAgentAvatarp->getJoint("mHead")); + } + if (!isAgentAvatarValid()) return; + + LLJoint* root_joint = gAgentAvatarp->getRootJoint(); + if( !root_joint ) + { + return; + } + + const LLQuaternion& avatar_rot = root_joint->getWorldRotation(); + + LLVector3d joint_pos = gAgent.getPosGlobalFromAgent(mCameraTargetJoint->getWorldPosition()); + LLVector3d target_pos = joint_pos + mCameraTargetOffset * avatar_rot; + + LLQuaternion camera_rot_yaw(mCameraYaw, LLVector3::z_axis); + LLQuaternion camera_rot_pitch(mCameraPitch, LLVector3::y_axis); + + LLVector3d camera_pos = joint_pos + mCameraOffset * camera_rot_pitch * camera_rot_yaw * avatar_rot; + + gAgentCamera.setCameraPosAndFocusGlobal( camera_pos, target_pos, gAgent.getID() ); +} + +void LLMorphView::setCameraDrivenByKeys(bool b) +{ + if( mCameraDrivenByKeys != b ) + { + if( b ) + { + // Reset to the default camera position specified by mCameraPitch, mCameraYaw, etc. + updateCamera(); + } + mCameraDrivenByKeys = b; + } +} diff --git a/indra/newview/llmorphview.h b/indra/newview/llmorphview.h index 0ac729bff4..87f8c25f65 100644 --- a/indra/newview/llmorphview.h +++ b/indra/newview/llmorphview.h @@ -1,87 +1,87 @@ -/** - * @file llmorphview.h - * @brief Container for character morph controls - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLMORPHVIEW_H -#define LL_LLMORPHVIEW_H - -#include "llview.h" -#include "v3dmath.h" -#include "llframetimer.h" - -class LLJoint; - -class LLMorphView : public LLView -{ -public: - struct Params : public LLInitParam::Block - { - Params() - { - changeDefault(mouse_opaque, false); - changeDefault(follows.flags, FOLLOWS_ALL); - } - }; - LLMorphView(const LLMorphView::Params&); - - void shutdown(); - - // inherited methods - void setVisible(bool visible) override; - - void setCameraTargetJoint(LLJoint *joint) {mCameraTargetJoint = joint;} - LLJoint* getCameraTargetJoint() {return mCameraTargetJoint;} - - void setCameraOffset(const LLVector3d& camera_offset) {mCameraOffset = camera_offset;} - void setCameraTargetOffset(const LLVector3d& camera_target_offset) {mCameraTargetOffset = camera_target_offset;} - - void updateCamera(); - void setCameraDrivenByKeys( bool b ); - -protected: - void initialize(); - - LLJoint* mCameraTargetJoint; - LLVector3d mCameraOffset; - LLVector3d mCameraTargetOffset; - LLVector3d mOldCameraPos; - LLVector3d mOldTargetPos; - F32 mOldCameraNearClip; - LLFrameTimer mCameraMoveTimer; - - // camera rotation - F32 mCameraPitch; - F32 mCameraYaw; - - bool mCameraDrivenByKeys; -}; - -// -// Globals -// - -extern LLMorphView *gMorphView; - -#endif +/** + * @file llmorphview.h + * @brief Container for character morph controls + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLMORPHVIEW_H +#define LL_LLMORPHVIEW_H + +#include "llview.h" +#include "v3dmath.h" +#include "llframetimer.h" + +class LLJoint; + +class LLMorphView : public LLView +{ +public: + struct Params : public LLInitParam::Block + { + Params() + { + changeDefault(mouse_opaque, false); + changeDefault(follows.flags, FOLLOWS_ALL); + } + }; + LLMorphView(const LLMorphView::Params&); + + void shutdown(); + + // inherited methods + void setVisible(bool visible) override; + + void setCameraTargetJoint(LLJoint *joint) {mCameraTargetJoint = joint;} + LLJoint* getCameraTargetJoint() {return mCameraTargetJoint;} + + void setCameraOffset(const LLVector3d& camera_offset) {mCameraOffset = camera_offset;} + void setCameraTargetOffset(const LLVector3d& camera_target_offset) {mCameraTargetOffset = camera_target_offset;} + + void updateCamera(); + void setCameraDrivenByKeys( bool b ); + +protected: + void initialize(); + + LLJoint* mCameraTargetJoint; + LLVector3d mCameraOffset; + LLVector3d mCameraTargetOffset; + LLVector3d mOldCameraPos; + LLVector3d mOldTargetPos; + F32 mOldCameraNearClip; + LLFrameTimer mCameraMoveTimer; + + // camera rotation + F32 mCameraPitch; + F32 mCameraYaw; + + bool mCameraDrivenByKeys; +}; + +// +// Globals +// + +extern LLMorphView *gMorphView; + +#endif diff --git a/indra/newview/llmoveview.cpp b/indra/newview/llmoveview.cpp index 33ee99a9d9..a8ceaffde8 100644 --- a/indra/newview/llmoveview.cpp +++ b/indra/newview/llmoveview.cpp @@ -1,731 +1,731 @@ -/** - * @file llmoveview.cpp - * @brief Container for movement buttons like forward, left, fly - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llmoveview.h" - -// Library includes -#include "indra_constants.h" -#include "llparcel.h" - -// Viewer includes - -#include "llagent.h" -#include "llagentcamera.h" -#include "llvoavatarself.h" // to check gAgentAvatarp->isSitting() -#include "llbutton.h" -#include "llfirstuse.h" -#include "llfloaterreg.h" -#include "llhints.h" -#include "lljoystickbutton.h" -#include "lluictrlfactory.h" -#include "llviewerwindow.h" -#include "llviewercontrol.h" -#include "llselectmgr.h" -#include "lltoolbarview.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" -#include "lltooltip.h" - -// -// Constants -// - -const F32 MOVE_BUTTON_DELAY = 0.0f; -const F32 YAW_NUDGE_RATE = 0.05f; // fraction of normal speed -const F32 NUDGE_TIME = 0.25f; // in seconds - -// -// Member functions -// - -// protected -LLFloaterMove::LLFloaterMove(const LLSD& key) -: LLFloater(key), - mForwardButton(NULL), - mBackwardButton(NULL), - mTurnLeftButton(NULL), - mTurnRightButton(NULL), - mMoveUpButton(NULL), - mMoveDownButton(NULL), - mModeActionsPanel(NULL), - mCurrentMode(MM_WALK) -{ -} - -LLFloaterMove::~LLFloaterMove() -{ - // Ensure LLPanelStandStopFlying panel is not among floater's children. See EXT-8458. - setVisible(false); - - // Otherwise it can be destroyed and static pointer in LLPanelStandStopFlying::getInstance() will become invalid. - // Such situation was possible when LLFloaterReg returns "dead" instance of floater. - // Should not happen after LLFloater::destroy was modified to remove "dead" instances from LLFloaterReg. -} - -// virtual -bool LLFloaterMove::postBuild() -{ - updateTransparency(TT_ACTIVE); // force using active floater transparency (STORM-730) - - // Code that implements floater buttons toggling when user moves via keyboard is located in LLAgent::propagate() - - mForwardButton = getChild("forward btn"); - mForwardButton->setHeldDownDelay(MOVE_BUTTON_DELAY); - - mBackwardButton = getChild("backward btn"); - mBackwardButton->setHeldDownDelay(MOVE_BUTTON_DELAY); - - mSlideLeftButton = getChild("move left btn"); - mSlideLeftButton->setHeldDownDelay(MOVE_BUTTON_DELAY); - - mSlideRightButton = getChild("move right btn"); - mSlideRightButton->setHeldDownDelay(MOVE_BUTTON_DELAY); - - mTurnLeftButton = getChild("turn left btn"); - mTurnLeftButton->setHeldDownDelay(MOVE_BUTTON_DELAY); - mTurnLeftButton->setHeldDownCallback(boost::bind(&LLFloaterMove::turnLeft, this)); - mTurnRightButton = getChild("turn right btn"); - mTurnRightButton->setHeldDownDelay(MOVE_BUTTON_DELAY); - mTurnRightButton->setHeldDownCallback(boost::bind(&LLFloaterMove::turnRight, this)); - - mMoveUpButton = getChild("move up btn"); - mMoveUpButton->setHeldDownDelay(MOVE_BUTTON_DELAY); - mMoveUpButton->setHeldDownCallback(boost::bind(&LLFloaterMove::moveUp, this)); - - mMoveDownButton = getChild("move down btn"); - mMoveDownButton->setHeldDownDelay(MOVE_BUTTON_DELAY); - mMoveDownButton->setHeldDownCallback(boost::bind(&LLFloaterMove::moveDown, this)); - - - mModeActionsPanel = getChild("panel_modes"); - - LLButton* btn; - btn = getChild("mode_walk_btn"); - btn->setCommitCallback(boost::bind(&LLFloaterMove::onWalkButtonClick, this)); - - btn = getChild("mode_run_btn"); - btn->setCommitCallback(boost::bind(&LLFloaterMove::onRunButtonClick, this)); - - btn = getChild("mode_fly_btn"); - btn->setCommitCallback(boost::bind(&LLFloaterMove::onFlyButtonClick, this)); - - initModeTooltips(); - - initModeButtonMap(); - - initMovementMode(); - - gAgent.addParcelChangedCallback(LLFloaterMove::sUpdateFlyingStatus); - - return true; -} - -// *NOTE: we assume that setVisible() is called on floater close. -// virtual -void LLFloaterMove::setVisible(bool visible) -{ - // Do nothing with Stand/Stop Flying panel in excessive calls of this method. - if (getVisible() == visible) - { - LLFloater::setVisible(visible); - return; - } - - if (visible) - { - LLFirstUse::notMoving(false); - // Attach the Stand/Stop Flying panel. - LLPanelStandStopFlying* ssf_panel = LLPanelStandStopFlying::getInstance(); - ssf_panel->reparent(this); - const LLRect& mode_actions_rect = mModeActionsPanel->getRect(); - ssf_panel->setOrigin(mode_actions_rect.mLeft, mode_actions_rect.mBottom); - } - else - { - // Detach the Stand/Stop Flying panel. - LLPanelStandStopFlying::getInstance()->reparent(NULL); - } - - LLFloater::setVisible(visible); -} - -// static -F32 LLFloaterMove::getYawRate( F32 time ) -{ - if( time < NUDGE_TIME ) - { - F32 rate = YAW_NUDGE_RATE + time * (1 - YAW_NUDGE_RATE)/ NUDGE_TIME; - return rate; - } - else - { - return 1.f; - } -} - - -// static -void LLFloaterMove::setFlyingMode(bool fly) -{ - LLFloaterMove* instance = LLFloaterReg::findTypedInstance("moveview"); - if (instance) - { - instance->setFlyingModeImpl(fly); - LLVOAvatarSelf* avatar_object = gAgentAvatarp; - bool is_sitting = avatar_object - && (avatar_object->getRegion() != NULL) - && (!avatar_object->isDead()) - && avatar_object->isSitting(); - instance->showModeButtons(!fly && !is_sitting); - } - if (fly) - { - LLPanelStandStopFlying::setStandStopFlyingMode(LLPanelStandStopFlying::SSFM_STOP_FLYING); - } - else - { - LLPanelStandStopFlying::clearStandStopFlyingMode(LLPanelStandStopFlying::SSFM_STOP_FLYING); - } -} -//static -void LLFloaterMove::setAlwaysRunMode(bool run) -{ - LLFloaterMove* instance = LLFloaterReg::findTypedInstance("moveview"); - if (instance) - { - instance->setAlwaysRunModeImpl(run); - } -} - -void LLFloaterMove::setFlyingModeImpl(bool fly) -{ - updateButtonsWithMovementMode(fly ? MM_FLY : (gAgent.getAlwaysRun() ? MM_RUN : MM_WALK)); -} - -void LLFloaterMove::setAlwaysRunModeImpl(bool run) -{ - if (!gAgent.getFlying()) - { - updateButtonsWithMovementMode(run ? MM_RUN : MM_WALK); - } -} - -//static -void LLFloaterMove::setSittingMode(bool bSitting) -{ - if (bSitting) - { - LLPanelStandStopFlying::setStandStopFlyingMode(LLPanelStandStopFlying::SSFM_STAND); - } - else - { - LLPanelStandStopFlying::clearStandStopFlyingMode(LLPanelStandStopFlying::SSFM_STAND); - - // show "Stop Flying" button if needed. EXT-871 - if (gAgent.getFlying()) - { - LLPanelStandStopFlying::setStandStopFlyingMode(LLPanelStandStopFlying::SSFM_STOP_FLYING); - } - } - enableInstance(); -} - -// protected -void LLFloaterMove::turnLeft() -{ - F32 time = mTurnLeftButton->getHeldDownTime(); - gAgent.moveYaw( getYawRate( time ) ); -} - -// protected -void LLFloaterMove::turnRight() -{ - F32 time = mTurnRightButton->getHeldDownTime(); - gAgent.moveYaw( -getYawRate( time ) ); -} - -// protected -void LLFloaterMove::moveUp() -{ - // Jumps or flys up, depending on fly state - gAgent.moveUp(1); -} - -// protected -void LLFloaterMove::moveDown() -{ - // Crouches or flys down, depending on fly state - gAgent.moveUp(-1); -} - -////////////////////////////////////////////////////////////////////////// -// Private Section: -////////////////////////////////////////////////////////////////////////// - -void LLFloaterMove::onWalkButtonClick() -{ - setMovementMode(MM_WALK); -} -void LLFloaterMove::onRunButtonClick() -{ - setMovementMode(MM_RUN); -} -void LLFloaterMove::onFlyButtonClick() -{ - setMovementMode(MM_FLY); -} - -void LLFloaterMove::setMovementMode(const EMovementMode mode) -{ - mCurrentMode = mode; - - if(MM_FLY == mode) - { - LLAgent::toggleFlying(); - } - else - { - gAgent.setFlying(false); - } - - // attempts to set avatar flying can not set it real flying in some cases. - // For ex. when avatar fell down & is standing up. - // So, no need to continue processing FLY mode. See EXT-1079 - if (MM_FLY == mode && !gAgent.getFlying()) - { - return; - } - - switch (mode) - { - case MM_RUN: - gAgent.setAlwaysRun(); - gAgent.setRunning(); - break; - case MM_WALK: - gAgent.clearAlwaysRun(); - gAgent.clearRunning(); - break; - default: - //do nothing for other modes (MM_FLY) - break; - } - // tell the simulator. - gAgent.sendWalkRun(gAgent.getAlwaysRun()); - - updateButtonsWithMovementMode(mode); - - bool bHideModeButtons = MM_FLY == mode - || (isAgentAvatarValid() && gAgentAvatarp->isSitting()); - - showModeButtons(!bHideModeButtons); - -} - -void LLFloaterMove::updateButtonsWithMovementMode(const EMovementMode newMode) -{ - setModeTooltip(newMode); - setModeButtonToggleState(newMode); - setModeTitle(newMode); -} - -void LLFloaterMove::initModeTooltips() -{ - control_tooltip_map_t walkTipMap; - walkTipMap.insert(std::make_pair(mForwardButton, getString("walk_forward_tooltip"))); - walkTipMap.insert(std::make_pair(mBackwardButton, getString("walk_back_tooltip"))); - walkTipMap.insert(std::make_pair(mSlideLeftButton, getString("walk_left_tooltip"))); - walkTipMap.insert(std::make_pair(mSlideRightButton, getString("walk_right_tooltip"))); - walkTipMap.insert(std::make_pair(mMoveUpButton, getString("jump_tooltip"))); - walkTipMap.insert(std::make_pair(mMoveDownButton, getString("crouch_tooltip"))); - mModeControlTooltipsMap[MM_WALK] = walkTipMap; - - control_tooltip_map_t runTipMap; - runTipMap.insert(std::make_pair(mForwardButton, getString("run_forward_tooltip"))); - runTipMap.insert(std::make_pair(mBackwardButton, getString("run_back_tooltip"))); - runTipMap.insert(std::make_pair(mSlideLeftButton, getString("run_left_tooltip"))); - runTipMap.insert(std::make_pair(mSlideRightButton, getString("run_right_tooltip"))); - runTipMap.insert(std::make_pair(mMoveUpButton, getString("jump_tooltip"))); - runTipMap.insert(std::make_pair(mMoveDownButton, getString("crouch_tooltip"))); - mModeControlTooltipsMap[MM_RUN] = runTipMap; - - control_tooltip_map_t flyTipMap; - flyTipMap.insert(std::make_pair(mForwardButton, getString("fly_forward_tooltip"))); - flyTipMap.insert(std::make_pair(mBackwardButton, getString("fly_back_tooltip"))); - flyTipMap.insert(std::make_pair(mSlideLeftButton, getString("fly_left_tooltip"))); - flyTipMap.insert(std::make_pair(mSlideRightButton, getString("fly_right_tooltip"))); - flyTipMap.insert(std::make_pair(mMoveUpButton, getString("fly_up_tooltip"))); - flyTipMap.insert(std::make_pair(mMoveDownButton, getString("fly_down_tooltip"))); - mModeControlTooltipsMap[MM_FLY] = flyTipMap; - - setModeTooltip(MM_WALK); -} - -void LLFloaterMove::initModeButtonMap() -{ - mModeControlButtonMap[MM_WALK] = getChild("mode_walk_btn"); - mModeControlButtonMap[MM_RUN] = getChild("mode_run_btn"); - mModeControlButtonMap[MM_FLY] = getChild("mode_fly_btn"); -} - -void LLFloaterMove::initMovementMode() -{ - EMovementMode initMovementMode = gAgent.getAlwaysRun() ? MM_RUN : MM_WALK; - if (gAgent.getFlying()) - { - initMovementMode = MM_FLY; - } - - mCurrentMode = initMovementMode; - bool hide_mode_buttons = (MM_FLY == mCurrentMode) || (isAgentAvatarValid() && gAgentAvatarp->isSitting()); - - updateButtonsWithMovementMode(mCurrentMode); - showModeButtons(!hide_mode_buttons); -} - -void LLFloaterMove::setModeTooltip(const EMovementMode mode) -{ - llassert_always(mModeControlTooltipsMap.end() != mModeControlTooltipsMap.find(mode)); - control_tooltip_map_t controlsTipMap = mModeControlTooltipsMap[mode]; - control_tooltip_map_t::const_iterator it = controlsTipMap.begin(); - for (; it != controlsTipMap.end(); ++it) - { - LLView* ctrl = it->first; - std::string tooltip = it->second; - ctrl->setToolTip(tooltip); - } -} - -void LLFloaterMove::setModeTitle(const EMovementMode mode) -{ - std::string title; - switch(mode) - { - case MM_WALK: - title = getString("walk_title"); - break; - case MM_RUN: - title = getString("run_title"); - break; - case MM_FLY: - title = getString("fly_title"); - break; - default: - // title should be provided for all modes - llassert(false); - break; - } - setTitle(title); -} - -//static -void LLFloaterMove::sUpdateFlyingStatus() -{ - LLFloaterMove *floater = LLFloaterReg::findTypedInstance("moveview"); - if (floater) floater->mModeControlButtonMap[MM_FLY]->setEnabled(gAgent.canFly()); - -} - -void LLFloaterMove::showModeButtons(bool bShow) -{ - if (mModeActionsPanel->getVisible() == bShow) - return; - mModeActionsPanel->setVisible(bShow); -} - -//static -void LLFloaterMove::enableInstance() -{ - LLFloaterMove* instance = LLFloaterReg::findTypedInstance("moveview"); - if (instance) - { - if (gAgent.getFlying()) - { - instance->showModeButtons(false); - } - else - { - instance->showModeButtons(isAgentAvatarValid() && !gAgentAvatarp->isSitting()); - } - } -} - -void LLFloaterMove::onOpen(const LLSD& key) -{ - if (gAgent.getFlying()) - { - setFlyingMode(true); - showModeButtons(false); - } - - if (isAgentAvatarValid() && gAgentAvatarp->isSitting()) - { - setSittingMode(true); - showModeButtons(false); - } - - sUpdateFlyingStatus(); -} - -void LLFloaterMove::setModeButtonToggleState(const EMovementMode mode) -{ - llassert_always(mModeControlButtonMap.end() != mModeControlButtonMap.find(mode)); - - mode_control_button_map_t::const_iterator it = mModeControlButtonMap.begin(); - for (; it != mModeControlButtonMap.end(); ++it) - { - it->second->setToggleState(false); - } - - mModeControlButtonMap[mode]->setToggleState(true); -} - - - -/************************************************************************/ -/* LLPanelStandStopFlying */ -/************************************************************************/ -LLPanelStandStopFlying::LLPanelStandStopFlying() : - mStandButton(NULL), - mStopFlyingButton(NULL), - mAttached(false) -{ - // make sure we have the only instance of this class - static bool b = true; - llassert_always(b); - b=false; -} - -// static -LLPanelStandStopFlying* LLPanelStandStopFlying::getInstance() -{ - static LLPanelStandStopFlying* panel = getStandStopFlyingPanel(); - return panel; -} - -//static -void LLPanelStandStopFlying::setStandStopFlyingMode(EStandStopFlyingMode mode) -{ - LLPanelStandStopFlying* panel = getInstance(); - - if (mode == SSFM_STAND) - { - LLFirstUse::sit(); - LLFirstUse::notMoving(false); - } - panel->mStandButton->setVisible(SSFM_STAND == mode); - panel->mStopFlyingButton->setVisible(SSFM_STOP_FLYING == mode); - - //visibility of it should be updated after updating visibility of the buttons - panel->setVisible(true); -} - -//static -void LLPanelStandStopFlying::clearStandStopFlyingMode(EStandStopFlyingMode mode) -{ - LLPanelStandStopFlying* panel = getInstance(); - switch(mode) { - case SSFM_STAND: - panel->mStandButton->setVisible(false); - break; - case SSFM_STOP_FLYING: - panel->mStopFlyingButton->setVisible(false); - break; - default: - LL_ERRS() << "Unexpected EStandStopFlyingMode is passed: " << mode << LL_ENDL; - } - -} - -bool LLPanelStandStopFlying::postBuild() -{ - mStandButton = getChild("stand_btn"); - mStandButton->setCommitCallback(boost::bind(&LLPanelStandStopFlying::onStandButtonClick, this)); - mStandButton->setCommitCallback(boost::bind(&LLFloaterMove::enableInstance)); - mStandButton->setVisible(false); - LLHints::getInstance()->registerHintTarget("stand_btn", mStandButton->getHandle()); - - mStopFlyingButton = getChild("stop_fly_btn"); - //mStopFlyingButton->setCommitCallback(boost::bind(&LLFloaterMove::setFlyingMode, false)); - mStopFlyingButton->setCommitCallback(boost::bind(&LLPanelStandStopFlying::onStopFlyingButtonClick, this)); - mStopFlyingButton->setVisible(false); - - gViewerWindow->setOnWorldViewRectUpdated(boost::bind(&LLPanelStandStopFlying::updatePosition, this)); - - return true; -} - -//virtual -void LLPanelStandStopFlying::setVisible(bool visible) -{ - //we dont need to show the panel if these buttons are not activated - if (gAgentCamera.getCameraMode() == CAMERA_MODE_MOUSELOOK) visible = false; - - if (visible) - { - updatePosition(); - } - - // do not change parent visibility in case panel is attached into Move Floater: EXT-3632, EXT-4646 - if (!mAttached) - { - //change visibility of parent layout_panel to animate in/out. EXT-2504 - if (getParent()) getParent()->setVisible(visible); - } - - // also change own visibility to avoid displaying the panel in mouselook (broken when EXT-2504 was implemented). - // See EXT-4718. - LLPanel::setVisible(visible); -} - -bool LLPanelStandStopFlying::handleToolTip(S32 x, S32 y, MASK mask) -{ - LLToolTipMgr::instance().unblockToolTips(); - - if (mStandButton->getVisible()) - { - LLToolTipMgr::instance().show(mStandButton->getToolTip()); - } - else if (mStopFlyingButton->getVisible()) - { - LLToolTipMgr::instance().show(mStopFlyingButton->getToolTip()); - } - - return LLPanel::handleToolTip(x, y, mask); -} - -void LLPanelStandStopFlying::reparent(LLFloaterMove* move_view) -{ - LLPanel* parent = dynamic_cast(getParent()); - if (!parent) - { - LL_WARNS() << "Stand/stop flying panel parent is unset, already attached?: " << mAttached << ", new parent: " << (move_view == NULL ? "NULL" : "Move Floater") << LL_ENDL; - return; - } - - if (move_view != NULL) - { - llassert(move_view != parent); // sanity check - - // Save our original container. - if (!mOriginalParent.get()) - mOriginalParent = parent->getHandle(); - - // Attach to movement controls. - parent->removeChild(this); - move_view->addChild(this); - // Origin must be set by movement controls. - mAttached = true; - } - else - { - if (!mOriginalParent.get()) - { - LL_WARNS() << "Original parent of the stand / stop flying panel not found" << LL_ENDL; - return; - } - - // Detach from movement controls. - parent->removeChild(this); - mOriginalParent.get()->addChild(this); - // update parent with self visibility (it is changed in setVisible()). EXT-4743 - mOriginalParent.get()->setVisible(getVisible()); - - mAttached = false; - updatePosition(); // don't defer until next draw() to avoid flicker - } -} - -////////////////////////////////////////////////////////////////////////// -// Private Section -////////////////////////////////////////////////////////////////////////// - -//static -LLPanelStandStopFlying* LLPanelStandStopFlying::getStandStopFlyingPanel() -{ - LLPanelStandStopFlying* panel = new LLPanelStandStopFlying(); - panel->buildFromFile("panel_stand_stop_flying.xml"); - - panel->setVisible(false); - //LLUI::getInstance()->getRootView()->addChild(panel); - - LL_INFOS() << "Build LLPanelStandStopFlying panel" << LL_ENDL; - - panel->updatePosition(); - return panel; -} - -void LLPanelStandStopFlying::onStandButtonClick() -{ - LLFirstUse::sit(false); - - LLSelectMgr::getInstance()->deselectAllForStandingUp(); - gAgent.setControlFlags(AGENT_CONTROL_STAND_UP); - - setFocus(false); -} - -void LLPanelStandStopFlying::onStopFlyingButtonClick() -{ - gAgent.setFlying(false); - - setFocus(false); // EXT-482 -} - -/** - * Updates position of the Stand & Stop Flying panel to be center aligned with Move button. - */ -void LLPanelStandStopFlying::updatePosition() -{ - if (mAttached) return; - - S32 bottom_tb_center = 0; - if (LLToolBar* toolbar_bottom = gToolBarView->getToolbar(LLToolBarEnums::TOOLBAR_BOTTOM)) - { - bottom_tb_center = toolbar_bottom->getRect().getCenterX(); - } - - S32 left_tb_width = 0; - if (LLToolBar* toolbar_left = gToolBarView->getToolbar(LLToolBarEnums::TOOLBAR_LEFT)) - { - left_tb_width = toolbar_left->getRect().getWidth(); - } - - if (gToolBarView != NULL && gToolBarView->getToolbar(LLToolBarEnums::TOOLBAR_LEFT)->hasButtons()) - { - S32 x_pos = bottom_tb_center - getRect().getWidth() / 2 - left_tb_width; - setOrigin( x_pos, 0); - } - else - { - S32 x_pos = bottom_tb_center - getRect().getWidth() / 2; - setOrigin( x_pos, 0); - } -} - -// EOF +/** + * @file llmoveview.cpp + * @brief Container for movement buttons like forward, left, fly + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llmoveview.h" + +// Library includes +#include "indra_constants.h" +#include "llparcel.h" + +// Viewer includes + +#include "llagent.h" +#include "llagentcamera.h" +#include "llvoavatarself.h" // to check gAgentAvatarp->isSitting() +#include "llbutton.h" +#include "llfirstuse.h" +#include "llfloaterreg.h" +#include "llhints.h" +#include "lljoystickbutton.h" +#include "lluictrlfactory.h" +#include "llviewerwindow.h" +#include "llviewercontrol.h" +#include "llselectmgr.h" +#include "lltoolbarview.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "lltooltip.h" + +// +// Constants +// + +const F32 MOVE_BUTTON_DELAY = 0.0f; +const F32 YAW_NUDGE_RATE = 0.05f; // fraction of normal speed +const F32 NUDGE_TIME = 0.25f; // in seconds + +// +// Member functions +// + +// protected +LLFloaterMove::LLFloaterMove(const LLSD& key) +: LLFloater(key), + mForwardButton(NULL), + mBackwardButton(NULL), + mTurnLeftButton(NULL), + mTurnRightButton(NULL), + mMoveUpButton(NULL), + mMoveDownButton(NULL), + mModeActionsPanel(NULL), + mCurrentMode(MM_WALK) +{ +} + +LLFloaterMove::~LLFloaterMove() +{ + // Ensure LLPanelStandStopFlying panel is not among floater's children. See EXT-8458. + setVisible(false); + + // Otherwise it can be destroyed and static pointer in LLPanelStandStopFlying::getInstance() will become invalid. + // Such situation was possible when LLFloaterReg returns "dead" instance of floater. + // Should not happen after LLFloater::destroy was modified to remove "dead" instances from LLFloaterReg. +} + +// virtual +bool LLFloaterMove::postBuild() +{ + updateTransparency(TT_ACTIVE); // force using active floater transparency (STORM-730) + + // Code that implements floater buttons toggling when user moves via keyboard is located in LLAgent::propagate() + + mForwardButton = getChild("forward btn"); + mForwardButton->setHeldDownDelay(MOVE_BUTTON_DELAY); + + mBackwardButton = getChild("backward btn"); + mBackwardButton->setHeldDownDelay(MOVE_BUTTON_DELAY); + + mSlideLeftButton = getChild("move left btn"); + mSlideLeftButton->setHeldDownDelay(MOVE_BUTTON_DELAY); + + mSlideRightButton = getChild("move right btn"); + mSlideRightButton->setHeldDownDelay(MOVE_BUTTON_DELAY); + + mTurnLeftButton = getChild("turn left btn"); + mTurnLeftButton->setHeldDownDelay(MOVE_BUTTON_DELAY); + mTurnLeftButton->setHeldDownCallback(boost::bind(&LLFloaterMove::turnLeft, this)); + mTurnRightButton = getChild("turn right btn"); + mTurnRightButton->setHeldDownDelay(MOVE_BUTTON_DELAY); + mTurnRightButton->setHeldDownCallback(boost::bind(&LLFloaterMove::turnRight, this)); + + mMoveUpButton = getChild("move up btn"); + mMoveUpButton->setHeldDownDelay(MOVE_BUTTON_DELAY); + mMoveUpButton->setHeldDownCallback(boost::bind(&LLFloaterMove::moveUp, this)); + + mMoveDownButton = getChild("move down btn"); + mMoveDownButton->setHeldDownDelay(MOVE_BUTTON_DELAY); + mMoveDownButton->setHeldDownCallback(boost::bind(&LLFloaterMove::moveDown, this)); + + + mModeActionsPanel = getChild("panel_modes"); + + LLButton* btn; + btn = getChild("mode_walk_btn"); + btn->setCommitCallback(boost::bind(&LLFloaterMove::onWalkButtonClick, this)); + + btn = getChild("mode_run_btn"); + btn->setCommitCallback(boost::bind(&LLFloaterMove::onRunButtonClick, this)); + + btn = getChild("mode_fly_btn"); + btn->setCommitCallback(boost::bind(&LLFloaterMove::onFlyButtonClick, this)); + + initModeTooltips(); + + initModeButtonMap(); + + initMovementMode(); + + gAgent.addParcelChangedCallback(LLFloaterMove::sUpdateFlyingStatus); + + return true; +} + +// *NOTE: we assume that setVisible() is called on floater close. +// virtual +void LLFloaterMove::setVisible(bool visible) +{ + // Do nothing with Stand/Stop Flying panel in excessive calls of this method. + if (getVisible() == visible) + { + LLFloater::setVisible(visible); + return; + } + + if (visible) + { + LLFirstUse::notMoving(false); + // Attach the Stand/Stop Flying panel. + LLPanelStandStopFlying* ssf_panel = LLPanelStandStopFlying::getInstance(); + ssf_panel->reparent(this); + const LLRect& mode_actions_rect = mModeActionsPanel->getRect(); + ssf_panel->setOrigin(mode_actions_rect.mLeft, mode_actions_rect.mBottom); + } + else + { + // Detach the Stand/Stop Flying panel. + LLPanelStandStopFlying::getInstance()->reparent(NULL); + } + + LLFloater::setVisible(visible); +} + +// static +F32 LLFloaterMove::getYawRate( F32 time ) +{ + if( time < NUDGE_TIME ) + { + F32 rate = YAW_NUDGE_RATE + time * (1 - YAW_NUDGE_RATE)/ NUDGE_TIME; + return rate; + } + else + { + return 1.f; + } +} + + +// static +void LLFloaterMove::setFlyingMode(bool fly) +{ + LLFloaterMove* instance = LLFloaterReg::findTypedInstance("moveview"); + if (instance) + { + instance->setFlyingModeImpl(fly); + LLVOAvatarSelf* avatar_object = gAgentAvatarp; + bool is_sitting = avatar_object + && (avatar_object->getRegion() != NULL) + && (!avatar_object->isDead()) + && avatar_object->isSitting(); + instance->showModeButtons(!fly && !is_sitting); + } + if (fly) + { + LLPanelStandStopFlying::setStandStopFlyingMode(LLPanelStandStopFlying::SSFM_STOP_FLYING); + } + else + { + LLPanelStandStopFlying::clearStandStopFlyingMode(LLPanelStandStopFlying::SSFM_STOP_FLYING); + } +} +//static +void LLFloaterMove::setAlwaysRunMode(bool run) +{ + LLFloaterMove* instance = LLFloaterReg::findTypedInstance("moveview"); + if (instance) + { + instance->setAlwaysRunModeImpl(run); + } +} + +void LLFloaterMove::setFlyingModeImpl(bool fly) +{ + updateButtonsWithMovementMode(fly ? MM_FLY : (gAgent.getAlwaysRun() ? MM_RUN : MM_WALK)); +} + +void LLFloaterMove::setAlwaysRunModeImpl(bool run) +{ + if (!gAgent.getFlying()) + { + updateButtonsWithMovementMode(run ? MM_RUN : MM_WALK); + } +} + +//static +void LLFloaterMove::setSittingMode(bool bSitting) +{ + if (bSitting) + { + LLPanelStandStopFlying::setStandStopFlyingMode(LLPanelStandStopFlying::SSFM_STAND); + } + else + { + LLPanelStandStopFlying::clearStandStopFlyingMode(LLPanelStandStopFlying::SSFM_STAND); + + // show "Stop Flying" button if needed. EXT-871 + if (gAgent.getFlying()) + { + LLPanelStandStopFlying::setStandStopFlyingMode(LLPanelStandStopFlying::SSFM_STOP_FLYING); + } + } + enableInstance(); +} + +// protected +void LLFloaterMove::turnLeft() +{ + F32 time = mTurnLeftButton->getHeldDownTime(); + gAgent.moveYaw( getYawRate( time ) ); +} + +// protected +void LLFloaterMove::turnRight() +{ + F32 time = mTurnRightButton->getHeldDownTime(); + gAgent.moveYaw( -getYawRate( time ) ); +} + +// protected +void LLFloaterMove::moveUp() +{ + // Jumps or flys up, depending on fly state + gAgent.moveUp(1); +} + +// protected +void LLFloaterMove::moveDown() +{ + // Crouches or flys down, depending on fly state + gAgent.moveUp(-1); +} + +////////////////////////////////////////////////////////////////////////// +// Private Section: +////////////////////////////////////////////////////////////////////////// + +void LLFloaterMove::onWalkButtonClick() +{ + setMovementMode(MM_WALK); +} +void LLFloaterMove::onRunButtonClick() +{ + setMovementMode(MM_RUN); +} +void LLFloaterMove::onFlyButtonClick() +{ + setMovementMode(MM_FLY); +} + +void LLFloaterMove::setMovementMode(const EMovementMode mode) +{ + mCurrentMode = mode; + + if(MM_FLY == mode) + { + LLAgent::toggleFlying(); + } + else + { + gAgent.setFlying(false); + } + + // attempts to set avatar flying can not set it real flying in some cases. + // For ex. when avatar fell down & is standing up. + // So, no need to continue processing FLY mode. See EXT-1079 + if (MM_FLY == mode && !gAgent.getFlying()) + { + return; + } + + switch (mode) + { + case MM_RUN: + gAgent.setAlwaysRun(); + gAgent.setRunning(); + break; + case MM_WALK: + gAgent.clearAlwaysRun(); + gAgent.clearRunning(); + break; + default: + //do nothing for other modes (MM_FLY) + break; + } + // tell the simulator. + gAgent.sendWalkRun(gAgent.getAlwaysRun()); + + updateButtonsWithMovementMode(mode); + + bool bHideModeButtons = MM_FLY == mode + || (isAgentAvatarValid() && gAgentAvatarp->isSitting()); + + showModeButtons(!bHideModeButtons); + +} + +void LLFloaterMove::updateButtonsWithMovementMode(const EMovementMode newMode) +{ + setModeTooltip(newMode); + setModeButtonToggleState(newMode); + setModeTitle(newMode); +} + +void LLFloaterMove::initModeTooltips() +{ + control_tooltip_map_t walkTipMap; + walkTipMap.insert(std::make_pair(mForwardButton, getString("walk_forward_tooltip"))); + walkTipMap.insert(std::make_pair(mBackwardButton, getString("walk_back_tooltip"))); + walkTipMap.insert(std::make_pair(mSlideLeftButton, getString("walk_left_tooltip"))); + walkTipMap.insert(std::make_pair(mSlideRightButton, getString("walk_right_tooltip"))); + walkTipMap.insert(std::make_pair(mMoveUpButton, getString("jump_tooltip"))); + walkTipMap.insert(std::make_pair(mMoveDownButton, getString("crouch_tooltip"))); + mModeControlTooltipsMap[MM_WALK] = walkTipMap; + + control_tooltip_map_t runTipMap; + runTipMap.insert(std::make_pair(mForwardButton, getString("run_forward_tooltip"))); + runTipMap.insert(std::make_pair(mBackwardButton, getString("run_back_tooltip"))); + runTipMap.insert(std::make_pair(mSlideLeftButton, getString("run_left_tooltip"))); + runTipMap.insert(std::make_pair(mSlideRightButton, getString("run_right_tooltip"))); + runTipMap.insert(std::make_pair(mMoveUpButton, getString("jump_tooltip"))); + runTipMap.insert(std::make_pair(mMoveDownButton, getString("crouch_tooltip"))); + mModeControlTooltipsMap[MM_RUN] = runTipMap; + + control_tooltip_map_t flyTipMap; + flyTipMap.insert(std::make_pair(mForwardButton, getString("fly_forward_tooltip"))); + flyTipMap.insert(std::make_pair(mBackwardButton, getString("fly_back_tooltip"))); + flyTipMap.insert(std::make_pair(mSlideLeftButton, getString("fly_left_tooltip"))); + flyTipMap.insert(std::make_pair(mSlideRightButton, getString("fly_right_tooltip"))); + flyTipMap.insert(std::make_pair(mMoveUpButton, getString("fly_up_tooltip"))); + flyTipMap.insert(std::make_pair(mMoveDownButton, getString("fly_down_tooltip"))); + mModeControlTooltipsMap[MM_FLY] = flyTipMap; + + setModeTooltip(MM_WALK); +} + +void LLFloaterMove::initModeButtonMap() +{ + mModeControlButtonMap[MM_WALK] = getChild("mode_walk_btn"); + mModeControlButtonMap[MM_RUN] = getChild("mode_run_btn"); + mModeControlButtonMap[MM_FLY] = getChild("mode_fly_btn"); +} + +void LLFloaterMove::initMovementMode() +{ + EMovementMode initMovementMode = gAgent.getAlwaysRun() ? MM_RUN : MM_WALK; + if (gAgent.getFlying()) + { + initMovementMode = MM_FLY; + } + + mCurrentMode = initMovementMode; + bool hide_mode_buttons = (MM_FLY == mCurrentMode) || (isAgentAvatarValid() && gAgentAvatarp->isSitting()); + + updateButtonsWithMovementMode(mCurrentMode); + showModeButtons(!hide_mode_buttons); +} + +void LLFloaterMove::setModeTooltip(const EMovementMode mode) +{ + llassert_always(mModeControlTooltipsMap.end() != mModeControlTooltipsMap.find(mode)); + control_tooltip_map_t controlsTipMap = mModeControlTooltipsMap[mode]; + control_tooltip_map_t::const_iterator it = controlsTipMap.begin(); + for (; it != controlsTipMap.end(); ++it) + { + LLView* ctrl = it->first; + std::string tooltip = it->second; + ctrl->setToolTip(tooltip); + } +} + +void LLFloaterMove::setModeTitle(const EMovementMode mode) +{ + std::string title; + switch(mode) + { + case MM_WALK: + title = getString("walk_title"); + break; + case MM_RUN: + title = getString("run_title"); + break; + case MM_FLY: + title = getString("fly_title"); + break; + default: + // title should be provided for all modes + llassert(false); + break; + } + setTitle(title); +} + +//static +void LLFloaterMove::sUpdateFlyingStatus() +{ + LLFloaterMove *floater = LLFloaterReg::findTypedInstance("moveview"); + if (floater) floater->mModeControlButtonMap[MM_FLY]->setEnabled(gAgent.canFly()); + +} + +void LLFloaterMove::showModeButtons(bool bShow) +{ + if (mModeActionsPanel->getVisible() == bShow) + return; + mModeActionsPanel->setVisible(bShow); +} + +//static +void LLFloaterMove::enableInstance() +{ + LLFloaterMove* instance = LLFloaterReg::findTypedInstance("moveview"); + if (instance) + { + if (gAgent.getFlying()) + { + instance->showModeButtons(false); + } + else + { + instance->showModeButtons(isAgentAvatarValid() && !gAgentAvatarp->isSitting()); + } + } +} + +void LLFloaterMove::onOpen(const LLSD& key) +{ + if (gAgent.getFlying()) + { + setFlyingMode(true); + showModeButtons(false); + } + + if (isAgentAvatarValid() && gAgentAvatarp->isSitting()) + { + setSittingMode(true); + showModeButtons(false); + } + + sUpdateFlyingStatus(); +} + +void LLFloaterMove::setModeButtonToggleState(const EMovementMode mode) +{ + llassert_always(mModeControlButtonMap.end() != mModeControlButtonMap.find(mode)); + + mode_control_button_map_t::const_iterator it = mModeControlButtonMap.begin(); + for (; it != mModeControlButtonMap.end(); ++it) + { + it->second->setToggleState(false); + } + + mModeControlButtonMap[mode]->setToggleState(true); +} + + + +/************************************************************************/ +/* LLPanelStandStopFlying */ +/************************************************************************/ +LLPanelStandStopFlying::LLPanelStandStopFlying() : + mStandButton(NULL), + mStopFlyingButton(NULL), + mAttached(false) +{ + // make sure we have the only instance of this class + static bool b = true; + llassert_always(b); + b=false; +} + +// static +LLPanelStandStopFlying* LLPanelStandStopFlying::getInstance() +{ + static LLPanelStandStopFlying* panel = getStandStopFlyingPanel(); + return panel; +} + +//static +void LLPanelStandStopFlying::setStandStopFlyingMode(EStandStopFlyingMode mode) +{ + LLPanelStandStopFlying* panel = getInstance(); + + if (mode == SSFM_STAND) + { + LLFirstUse::sit(); + LLFirstUse::notMoving(false); + } + panel->mStandButton->setVisible(SSFM_STAND == mode); + panel->mStopFlyingButton->setVisible(SSFM_STOP_FLYING == mode); + + //visibility of it should be updated after updating visibility of the buttons + panel->setVisible(true); +} + +//static +void LLPanelStandStopFlying::clearStandStopFlyingMode(EStandStopFlyingMode mode) +{ + LLPanelStandStopFlying* panel = getInstance(); + switch(mode) { + case SSFM_STAND: + panel->mStandButton->setVisible(false); + break; + case SSFM_STOP_FLYING: + panel->mStopFlyingButton->setVisible(false); + break; + default: + LL_ERRS() << "Unexpected EStandStopFlyingMode is passed: " << mode << LL_ENDL; + } + +} + +bool LLPanelStandStopFlying::postBuild() +{ + mStandButton = getChild("stand_btn"); + mStandButton->setCommitCallback(boost::bind(&LLPanelStandStopFlying::onStandButtonClick, this)); + mStandButton->setCommitCallback(boost::bind(&LLFloaterMove::enableInstance)); + mStandButton->setVisible(false); + LLHints::getInstance()->registerHintTarget("stand_btn", mStandButton->getHandle()); + + mStopFlyingButton = getChild("stop_fly_btn"); + //mStopFlyingButton->setCommitCallback(boost::bind(&LLFloaterMove::setFlyingMode, false)); + mStopFlyingButton->setCommitCallback(boost::bind(&LLPanelStandStopFlying::onStopFlyingButtonClick, this)); + mStopFlyingButton->setVisible(false); + + gViewerWindow->setOnWorldViewRectUpdated(boost::bind(&LLPanelStandStopFlying::updatePosition, this)); + + return true; +} + +//virtual +void LLPanelStandStopFlying::setVisible(bool visible) +{ + //we dont need to show the panel if these buttons are not activated + if (gAgentCamera.getCameraMode() == CAMERA_MODE_MOUSELOOK) visible = false; + + if (visible) + { + updatePosition(); + } + + // do not change parent visibility in case panel is attached into Move Floater: EXT-3632, EXT-4646 + if (!mAttached) + { + //change visibility of parent layout_panel to animate in/out. EXT-2504 + if (getParent()) getParent()->setVisible(visible); + } + + // also change own visibility to avoid displaying the panel in mouselook (broken when EXT-2504 was implemented). + // See EXT-4718. + LLPanel::setVisible(visible); +} + +bool LLPanelStandStopFlying::handleToolTip(S32 x, S32 y, MASK mask) +{ + LLToolTipMgr::instance().unblockToolTips(); + + if (mStandButton->getVisible()) + { + LLToolTipMgr::instance().show(mStandButton->getToolTip()); + } + else if (mStopFlyingButton->getVisible()) + { + LLToolTipMgr::instance().show(mStopFlyingButton->getToolTip()); + } + + return LLPanel::handleToolTip(x, y, mask); +} + +void LLPanelStandStopFlying::reparent(LLFloaterMove* move_view) +{ + LLPanel* parent = dynamic_cast(getParent()); + if (!parent) + { + LL_WARNS() << "Stand/stop flying panel parent is unset, already attached?: " << mAttached << ", new parent: " << (move_view == NULL ? "NULL" : "Move Floater") << LL_ENDL; + return; + } + + if (move_view != NULL) + { + llassert(move_view != parent); // sanity check + + // Save our original container. + if (!mOriginalParent.get()) + mOriginalParent = parent->getHandle(); + + // Attach to movement controls. + parent->removeChild(this); + move_view->addChild(this); + // Origin must be set by movement controls. + mAttached = true; + } + else + { + if (!mOriginalParent.get()) + { + LL_WARNS() << "Original parent of the stand / stop flying panel not found" << LL_ENDL; + return; + } + + // Detach from movement controls. + parent->removeChild(this); + mOriginalParent.get()->addChild(this); + // update parent with self visibility (it is changed in setVisible()). EXT-4743 + mOriginalParent.get()->setVisible(getVisible()); + + mAttached = false; + updatePosition(); // don't defer until next draw() to avoid flicker + } +} + +////////////////////////////////////////////////////////////////////////// +// Private Section +////////////////////////////////////////////////////////////////////////// + +//static +LLPanelStandStopFlying* LLPanelStandStopFlying::getStandStopFlyingPanel() +{ + LLPanelStandStopFlying* panel = new LLPanelStandStopFlying(); + panel->buildFromFile("panel_stand_stop_flying.xml"); + + panel->setVisible(false); + //LLUI::getInstance()->getRootView()->addChild(panel); + + LL_INFOS() << "Build LLPanelStandStopFlying panel" << LL_ENDL; + + panel->updatePosition(); + return panel; +} + +void LLPanelStandStopFlying::onStandButtonClick() +{ + LLFirstUse::sit(false); + + LLSelectMgr::getInstance()->deselectAllForStandingUp(); + gAgent.setControlFlags(AGENT_CONTROL_STAND_UP); + + setFocus(false); +} + +void LLPanelStandStopFlying::onStopFlyingButtonClick() +{ + gAgent.setFlying(false); + + setFocus(false); // EXT-482 +} + +/** + * Updates position of the Stand & Stop Flying panel to be center aligned with Move button. + */ +void LLPanelStandStopFlying::updatePosition() +{ + if (mAttached) return; + + S32 bottom_tb_center = 0; + if (LLToolBar* toolbar_bottom = gToolBarView->getToolbar(LLToolBarEnums::TOOLBAR_BOTTOM)) + { + bottom_tb_center = toolbar_bottom->getRect().getCenterX(); + } + + S32 left_tb_width = 0; + if (LLToolBar* toolbar_left = gToolBarView->getToolbar(LLToolBarEnums::TOOLBAR_LEFT)) + { + left_tb_width = toolbar_left->getRect().getWidth(); + } + + if (gToolBarView != NULL && gToolBarView->getToolbar(LLToolBarEnums::TOOLBAR_LEFT)->hasButtons()) + { + S32 x_pos = bottom_tb_center - getRect().getWidth() / 2 - left_tb_width; + setOrigin( x_pos, 0); + } + else + { + S32 x_pos = bottom_tb_center - getRect().getWidth() / 2; + setOrigin( x_pos, 0); + } +} + +// EOF diff --git a/indra/newview/llmoveview.h b/indra/newview/llmoveview.h index 97c7716deb..3690245e1d 100644 --- a/indra/newview/llmoveview.h +++ b/indra/newview/llmoveview.h @@ -1,185 +1,185 @@ -/** - * @file llmoveview.h - * @brief Container for buttons for walking, turning, flying - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLMOVEVIEW_H -#define LL_LLMOVEVIEW_H - -// Library includes -#include "llfloater.h" - -class LLButton; -class LLJoystickAgentTurn; -class LLJoystickAgentSlide; - -// -// Classes -// -class LLFloaterMove -: public LLFloater -{ - LOG_CLASS(LLFloaterMove); - friend class LLFloaterReg; - -private: - LLFloaterMove(const LLSD& key); - ~LLFloaterMove(); -public: - - /*virtual*/ bool postBuild(); - /*virtual*/ void setVisible(bool visible); - static F32 getYawRate(F32 time); - static void setFlyingMode(bool fly); - void setFlyingModeImpl(bool fly); - static void setAlwaysRunMode(bool run); - void setAlwaysRunModeImpl(bool run); - static void setSittingMode(bool bSitting); - static void enableInstance(); - /*virtual*/ void onOpen(const LLSD& key); - - static void sUpdateFlyingStatus(); - -protected: - void turnLeft(); - void turnRight(); - - void moveUp(); - void moveDown(); - -private: - typedef enum movement_mode_t - { - MM_WALK, - MM_RUN, - MM_FLY - } EMovementMode; - void onWalkButtonClick(); - void onRunButtonClick(); - void onFlyButtonClick(); - void initMovementMode(); - void setMovementMode(const EMovementMode mode); - void initModeTooltips(); - void setModeTooltip(const EMovementMode mode); - void setModeTitle(const EMovementMode mode); - void initModeButtonMap(); - void setModeButtonToggleState(const EMovementMode mode); - void updateButtonsWithMovementMode(const EMovementMode newMode); - void showModeButtons(bool bShow); - -public: - - LLJoystickAgentTurn* mForwardButton; - LLJoystickAgentTurn* mBackwardButton; - LLJoystickAgentSlide* mSlideLeftButton; - LLJoystickAgentSlide* mSlideRightButton; - LLButton* mTurnLeftButton; - LLButton* mTurnRightButton; - LLButton* mMoveUpButton; - LLButton* mMoveDownButton; -private: - LLPanel* mModeActionsPanel; - - typedef std::map control_tooltip_map_t; - typedef std::map mode_control_tooltip_map_t; - mode_control_tooltip_map_t mModeControlTooltipsMap; - - typedef std::map mode_control_button_map_t; - mode_control_button_map_t mModeControlButtonMap; - EMovementMode mCurrentMode; - -}; - - -/** - * This class contains Stand Up and Stop Flying buttons displayed above Move button in bottom tray - */ -class LLPanelStandStopFlying : public LLPanel -{ - LOG_CLASS(LLPanelStandStopFlying); -public: - typedef enum stand_stop_flying_mode_t - { - SSFM_STAND, - SSFM_STOP_FLYING - } EStandStopFlyingMode; - - /** - * Attach or detach the panel to/from the movement controls floater. - * - * Called when the floater gets opened/closed, user sits, stands up or starts/stops flying. - * - * @param move_view The floater to attach to (not always accessible via floater registry). - * If NULL is passed, the panel gets reparented to its original container. - * - * @see mAttached - * @see mOriginalParent - */ - void reparent(LLFloaterMove* move_view); - - static LLPanelStandStopFlying* getInstance(); - static void setStandStopFlyingMode(EStandStopFlyingMode mode); - static void clearStandStopFlyingMode(EStandStopFlyingMode mode); - /*virtual*/ bool postBuild(); - /*virtual*/ void setVisible(bool visible); - - // *HACK: due to hard enough to have this control aligned with "Move" button while resizing - // let update its position in each frame - /*virtual*/ void draw(){updatePosition(); LLPanel::draw();} - /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); - - -protected: - LLPanelStandStopFlying(); - - -private: - static LLPanelStandStopFlying* getStandStopFlyingPanel(); - void onStandButtonClick(); - void onStopFlyingButtonClick(); - void updatePosition(); - - LLButton* mStandButton; - LLButton* mStopFlyingButton; - - /** - * The original parent of the panel. - * - * Makes it possible to move (reparent) the panel to the movement controls floater and back. - * - * @see reparent() - */ - LLHandle mOriginalParent; - - /** - * True if the panel is currently attached to the movement controls floater. - * - * @see reparent() - * @see updatePosition() - */ - bool mAttached; -}; - - -#endif +/** + * @file llmoveview.h + * @brief Container for buttons for walking, turning, flying + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLMOVEVIEW_H +#define LL_LLMOVEVIEW_H + +// Library includes +#include "llfloater.h" + +class LLButton; +class LLJoystickAgentTurn; +class LLJoystickAgentSlide; + +// +// Classes +// +class LLFloaterMove +: public LLFloater +{ + LOG_CLASS(LLFloaterMove); + friend class LLFloaterReg; + +private: + LLFloaterMove(const LLSD& key); + ~LLFloaterMove(); +public: + + /*virtual*/ bool postBuild(); + /*virtual*/ void setVisible(bool visible); + static F32 getYawRate(F32 time); + static void setFlyingMode(bool fly); + void setFlyingModeImpl(bool fly); + static void setAlwaysRunMode(bool run); + void setAlwaysRunModeImpl(bool run); + static void setSittingMode(bool bSitting); + static void enableInstance(); + /*virtual*/ void onOpen(const LLSD& key); + + static void sUpdateFlyingStatus(); + +protected: + void turnLeft(); + void turnRight(); + + void moveUp(); + void moveDown(); + +private: + typedef enum movement_mode_t + { + MM_WALK, + MM_RUN, + MM_FLY + } EMovementMode; + void onWalkButtonClick(); + void onRunButtonClick(); + void onFlyButtonClick(); + void initMovementMode(); + void setMovementMode(const EMovementMode mode); + void initModeTooltips(); + void setModeTooltip(const EMovementMode mode); + void setModeTitle(const EMovementMode mode); + void initModeButtonMap(); + void setModeButtonToggleState(const EMovementMode mode); + void updateButtonsWithMovementMode(const EMovementMode newMode); + void showModeButtons(bool bShow); + +public: + + LLJoystickAgentTurn* mForwardButton; + LLJoystickAgentTurn* mBackwardButton; + LLJoystickAgentSlide* mSlideLeftButton; + LLJoystickAgentSlide* mSlideRightButton; + LLButton* mTurnLeftButton; + LLButton* mTurnRightButton; + LLButton* mMoveUpButton; + LLButton* mMoveDownButton; +private: + LLPanel* mModeActionsPanel; + + typedef std::map control_tooltip_map_t; + typedef std::map mode_control_tooltip_map_t; + mode_control_tooltip_map_t mModeControlTooltipsMap; + + typedef std::map mode_control_button_map_t; + mode_control_button_map_t mModeControlButtonMap; + EMovementMode mCurrentMode; + +}; + + +/** + * This class contains Stand Up and Stop Flying buttons displayed above Move button in bottom tray + */ +class LLPanelStandStopFlying : public LLPanel +{ + LOG_CLASS(LLPanelStandStopFlying); +public: + typedef enum stand_stop_flying_mode_t + { + SSFM_STAND, + SSFM_STOP_FLYING + } EStandStopFlyingMode; + + /** + * Attach or detach the panel to/from the movement controls floater. + * + * Called when the floater gets opened/closed, user sits, stands up or starts/stops flying. + * + * @param move_view The floater to attach to (not always accessible via floater registry). + * If NULL is passed, the panel gets reparented to its original container. + * + * @see mAttached + * @see mOriginalParent + */ + void reparent(LLFloaterMove* move_view); + + static LLPanelStandStopFlying* getInstance(); + static void setStandStopFlyingMode(EStandStopFlyingMode mode); + static void clearStandStopFlyingMode(EStandStopFlyingMode mode); + /*virtual*/ bool postBuild(); + /*virtual*/ void setVisible(bool visible); + + // *HACK: due to hard enough to have this control aligned with "Move" button while resizing + // let update its position in each frame + /*virtual*/ void draw(){updatePosition(); LLPanel::draw();} + /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); + + +protected: + LLPanelStandStopFlying(); + + +private: + static LLPanelStandStopFlying* getStandStopFlyingPanel(); + void onStandButtonClick(); + void onStopFlyingButtonClick(); + void updatePosition(); + + LLButton* mStandButton; + LLButton* mStopFlyingButton; + + /** + * The original parent of the panel. + * + * Makes it possible to move (reparent) the panel to the movement controls floater and back. + * + * @see reparent() + */ + LLHandle mOriginalParent; + + /** + * True if the panel is currently attached to the movement controls floater. + * + * @see reparent() + * @see updatePosition() + */ + bool mAttached; +}; + + +#endif diff --git a/indra/newview/llmutelist.cpp b/indra/newview/llmutelist.cpp index abb7a50b0e..2d51acc063 100644 --- a/indra/newview/llmutelist.cpp +++ b/indra/newview/llmutelist.cpp @@ -1,1012 +1,1012 @@ -/** - * @file llmutelist.cpp - * @author Richard Nelson, James Cook - * @brief Management of list of muted players - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/* - * How should muting work? - * Mute an avatar - * Mute a specific object (accidentally spamming) - * - * right-click avatar, mute - * see list of recent chatters, mute - * type a name to mute? - * - * show in list whether chatter is avatar or object - * - * need fast lookup by id - * need lookup by name, doesn't have to be fast - */ - -#include "llviewerprecompiledheaders.h" - -#include "llmutelist.h" - -#include "pipeline.h" - -#include -#include -#include - -#include "lldispatcher.h" -#include "llxfermanager.h" - -#include "llagent.h" -#include "llavatarnamecache.h" -#include "llviewergenericmessage.h" // for gGenericDispatcher -#include "llworld.h" //for particle system banning -#include "llimview.h" -#include "llnotifications.h" -#include "llviewercontrol.h" -#include "llviewerobjectlist.h" -#include "lltrans.h" - -namespace -{ - // This method is used to return an object to mute given an object id. - // Its used by the LLMute constructor and LLMuteList::isMuted. - LLViewerObject* get_object_to_mute_from_id(LLUUID object_id) - { - LLViewerObject *objectp = gObjectList.findObject(object_id); - if ((objectp) && (!objectp->isAvatar())) - { - LLViewerObject *parentp = (LLViewerObject *)objectp->getParent(); - if (parentp && parentp->getID() != gAgent.getID()) - { - objectp = parentp; - } - } - return objectp; - } -} - -// "emptymutelist" -class LLDispatchEmptyMuteList : public LLDispatchHandler -{ -public: - virtual bool operator()( - const LLDispatcher* dispatcher, - const std::string& key, - const LLUUID& invoice, - const sparam_t& strings) - { - LLMuteList::getInstance()->setLoaded(); - return true; - } -}; - -static LLDispatchEmptyMuteList sDispatchEmptyMuteList; - -//----------------------------------------------------------------------------- -// LLMute() -//----------------------------------------------------------------------------- - -LLMute::LLMute(const LLUUID& id, const std::string& name, EType type, U32 flags) - : mID(id), - mName(name), - mType(type), - mFlags(flags) -{ - // muting is done by root objects only - try to find this objects root - LLViewerObject* mute_object = get_object_to_mute_from_id(id); - if(mute_object && mute_object->getID() != id) - { - mID = mute_object->getID(); - LLNameValue* firstname = mute_object->getNVPair("FirstName"); - LLNameValue* lastname = mute_object->getNVPair("LastName"); - if (firstname && lastname) - { - mName = LLCacheName::buildFullName( - firstname->getString(), lastname->getString()); - } - mType = mute_object->isAvatar() ? AGENT : OBJECT; - } - -} - - -std::string LLMute::getDisplayType() const -{ - switch (mType) - { - case BY_NAME: - default: - return LLTrans::getString("MuteByName"); - break; - case AGENT: - return LLTrans::getString("MuteAgent"); - break; - case OBJECT: - return LLTrans::getString("MuteObject"); - break; - case GROUP: - return LLTrans::getString("MuteGroup"); - break; - case EXTERNAL: - return LLTrans::getString("MuteExternal"); - break; - } -} - -//----------------------------------------------------------------------------- -// LLMuteList() -//----------------------------------------------------------------------------- -LLMuteList::LLMuteList() : - mIsLoaded(false) -{ - gGenericDispatcher.addHandler("emptymutelist", &sDispatchEmptyMuteList); - - // Register our callbacks. We may be constructed before gMessageSystem, so - // use callWhenReady() to register them as soon as gMessageSystem becomes - // available. - // When using bind(), must be explicit about default arguments such as - // that last NULL. - gMessageSystem.callWhenReady(boost::bind(&LLMessageSystem::setHandlerFuncFast, _1, - _PREHASH_MuteListUpdate, processMuteListUpdate, - static_cast(NULL))); - gMessageSystem.callWhenReady(boost::bind(&LLMessageSystem::setHandlerFuncFast, _1, - _PREHASH_UseCachedMuteList, processUseCachedMuteList, - static_cast(NULL))); - - // make sure mute list's instance gets initialized before we start any name requests - LLAvatarNameCache::getInstance()->setAccountNameChangedCallback([this](const LLUUID& id, const LLAvatarName& av_name) - { - // it would be better to just pass LLAvatarName instead of doing unnesssesary copies - // but this way is just more convinient - onAccountNameChanged(id, av_name.getUserName()); - }); -} - -//----------------------------------------------------------------------------- -// ~LLMuteList() -//----------------------------------------------------------------------------- -LLMuteList::~LLMuteList() -{ - -} - -void LLMuteList::cleanupSingleton() -{ - LLAvatarNameCache::getInstance()->setAccountNameChangedCallback(NULL); -} - -bool LLMuteList::isLinden(const std::string& name) -{ - std::string username = boost::replace_all_copy(name, ".", " "); - typedef boost::tokenizer > tokenizer; - boost::char_separator sep(" "); - tokenizer tokens(username, sep); - tokenizer::iterator token_iter = tokens.begin(); - - if (token_iter == tokens.end()) return false; - token_iter++; - if (token_iter == tokens.end()) return false; - - std::string last_name = *token_iter; - LLStringUtil::toLower(last_name); - return last_name == "linden"; -} - -static LLVOAvatar* find_avatar(const LLUUID& id) -{ - LLViewerObject *obj = gObjectList.findObject(id); - while (obj && obj->isAttachment()) - { - obj = (LLViewerObject *)obj->getParent(); - } - - if (obj && obj->isAvatar()) - { - return (LLVOAvatar*)obj; - } - else - { - return NULL; - } -} - -bool LLMuteList::add(const LLMute& mute, U32 flags) -{ - // Can't mute text from Lindens - if ((mute.mType == LLMute::AGENT) - && isLinden(mute.mName) && (flags & LLMute::flagTextChat || flags == 0)) - { - LL_WARNS() << "Trying to mute a Linden; ignored" << LL_ENDL; - LLNotifications::instance().add("MuteLinden", LLSD(), LLSD()); - return false; - } - - // Can't mute self. - if (mute.mType == LLMute::AGENT - && mute.mID == gAgent.getID()) - { - LL_WARNS() << "Trying to self; ignored" << LL_ENDL; - return false; - } - - static LLCachedControl mute_list_limit(gSavedSettings, "MuteListLimit", 1000); - if (getMutes().size() >= mute_list_limit) - { - LL_WARNS() << "Mute limit is reached; ignored" << LL_ENDL; - LLSD args; - args["MUTE_LIMIT"] = mute_list_limit; - LLNotifications::instance().add(LLNotification::Params("MuteLimitReached").substitutions(args)); - return false; - } - - if (mute.mType == LLMute::BY_NAME) - { - // Can't mute empty string by name - if (mute.mName.empty()) - { - LL_WARNS() << "Trying to mute empty string by-name" << LL_ENDL; - return false; - } - - // Null mutes must have uuid null - if (mute.mID.notNull()) - { - LL_WARNS() << "Trying to add by-name mute with non-null id" << LL_ENDL; - return false; - } - - std::pair result = mLegacyMutes.insert(mute.mName); - if (result.second) - { - LL_INFOS() << "Muting by name " << mute.mName << LL_ENDL; - updateAdd(mute); - notifyObservers(); - notifyObserversDetailed(mute); - return true; - } - else - { - LL_INFOS() << "duplicate mute ignored" << LL_ENDL; - // was duplicate - return false; - } - } - else - { - // Need a local (non-const) copy to set up flags properly. - LLMute localmute = mute; - - // If an entry for the same entity is already in the list, remove it, saving flags as necessary. - mute_set_t::iterator it = mMutes.find(localmute); - if (it != mMutes.end()) - { - // This mute is already in the list. Save the existing entry's flags if that's warranted. - localmute.mFlags = it->mFlags; - - mMutes.erase(it); - // Don't need to call notifyObservers() here, since it will happen after the entry has been re-added below. - } - else - { - // There was no entry in the list previously. Fake things up by making it look like the previous entry had all properties unmuted. - localmute.mFlags = LLMute::flagAll; - } - - if(flags) - { - // The user passed some combination of flags. Make sure those flag bits are turned off (i.e. those properties will be muted). - localmute.mFlags &= (~flags); - } - else - { - // The user passed 0. Make sure all flag bits are turned off (i.e. all properties will be muted). - localmute.mFlags = 0; - } - - // (re)add the mute entry. - { - std::pair result = mMutes.insert(localmute); - if (result.second) - { - LL_INFOS() << "Muting " << localmute.mName << " id " << localmute.mID << " flags " << localmute.mFlags << LL_ENDL; - updateAdd(localmute); - notifyObservers(); - notifyObserversDetailed(localmute); - - //mute local lights that are attached to the avatar - LLVOAvatar *avatarp = find_avatar(localmute.mID); - if (avatarp) - { - LLPipeline::removeMutedAVsLights(avatarp); - } - //remove agent's notifications as well - if (localmute.mType == LLMute::AGENT) - { - LLNotifications::instance().cancelByOwner(localmute.mID); - } - return true; - } - } - } - - // If we were going to return success, we'd have done it by now. - return false; -} - -void LLMuteList::updateAdd(const LLMute& mute) -{ - // External mutes are local only, don't send them to the server. - if (mute.mType == LLMute::EXTERNAL) - { - return; - } - - // Update the database - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_UpdateMuteListEntry); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_MuteData); - msg->addUUIDFast(_PREHASH_MuteID, mute.mID); - msg->addStringFast(_PREHASH_MuteName, mute.mName); - msg->addS32("MuteType", mute.mType); - msg->addU32("MuteFlags", mute.mFlags); - gAgent.sendReliableMessage(); - - if (!mIsLoaded) - { - LL_WARNS() << "Added elements to non-initialized block list" << LL_ENDL; - } - mIsLoaded = true; // why is this here? -MG -} - - -bool LLMuteList::remove(const LLMute& mute, U32 flags) -{ - bool found = false; - - // First, remove from main list. - mute_set_t::iterator it = mMutes.find(mute); - if (it != mMutes.end()) - { - LLMute localmute = *it; - bool remove = true; - if(flags) - { - // If the user passed mute flags, we may only want to turn some flags on. - localmute.mFlags |= flags; - - if(localmute.mFlags == LLMute::flagAll) - { - // Every currently available mute property has been masked out. - // Remove the mute entry entirely. - } - else - { - // Only some of the properties are masked out. Update the entry. - remove = false; - } - } - else - { - // The caller didn't pass any flags -- just remove the mute entry entirely. - // set flags to notify observers with (flag being present means that something is allowed) - localmute.mFlags = LLMute::flagAll; - } - - // Always remove the entry from the set -- it will be re-added with new flags if necessary. - mMutes.erase(it); - - if(remove) - { - // The entry was actually removed. Notify the server. - updateRemove(localmute); - LL_INFOS() << "Unmuting " << localmute.mName << " id " << localmute.mID << " flags " << localmute.mFlags << LL_ENDL; - } - else - { - // Flags were updated, the mute entry needs to be retransmitted to the server and re-added to the list. - mMutes.insert(localmute); - updateAdd(localmute); - LL_INFOS() << "Updating mute entry " << localmute.mName << " id " << localmute.mID << " flags " << localmute.mFlags << LL_ENDL; - } - - // Must be after erase. - notifyObservers(); - notifyObserversDetailed(localmute); - } - else - { - // Clean up any legacy mutes - string_set_t::iterator legacy_it = mLegacyMutes.find(mute.mName); - if (legacy_it != mLegacyMutes.end()) - { - // Database representation of legacy mute is UUID null. - LLMute mute(LLUUID::null, *legacy_it, LLMute::BY_NAME); - updateRemove(mute); - mLegacyMutes.erase(legacy_it); - // Must be after erase. - notifyObservers(); - notifyObserversDetailed(mute); - } - } - - return found; -} - - -void LLMuteList::updateRemove(const LLMute& mute) -{ - // External mutes are not sent to the server anyway, no need to remove them. - if (mute.mType == LLMute::EXTERNAL) - { - return; - } - - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_RemoveMuteListEntry); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_MuteData); - msg->addUUIDFast(_PREHASH_MuteID, mute.mID); - msg->addString("MuteName", mute.mName); - gAgent.sendReliableMessage(); -} - -void notify_automute_callback(const LLUUID& agent_id, const LLAvatarName& full_name, LLMuteList::EAutoReason reason) -{ - std::string notif_name; - switch (reason) - { - default: - case LLMuteList::AR_IM: - notif_name = "AutoUnmuteByIM"; - break; - case LLMuteList::AR_INVENTORY: - notif_name = "AutoUnmuteByInventory"; - break; - case LLMuteList::AR_MONEY: - notif_name = "AutoUnmuteByMoney"; - break; - } - - LLSD args; - args["NAME"] = full_name.getUserName(); - - LLNotificationPtr notif_ptr = LLNotifications::instance().add(notif_name, args, LLSD()); - if (notif_ptr) - { - std::string message = notif_ptr->getMessage(); - - if (reason == LLMuteList::AR_IM) - { - LLIMModel::getInstance()->addMessage(agent_id, SYSTEM_FROM, LLUUID::null, message); - } - } -} - - -bool LLMuteList::autoRemove(const LLUUID& agent_id, const EAutoReason reason) -{ - bool removed = false; - - if (isMuted(agent_id)) - { - LLMute automute(agent_id, LLStringUtil::null, LLMute::AGENT); - removed = true; - remove(automute); - - LLAvatarName av_name; - if (LLAvatarNameCache::get(agent_id, &av_name)) - { - // name in cache, call callback directly - notify_automute_callback(agent_id, av_name, reason); - } - else - { - // not in cache, lookup name from cache - LLAvatarNameCache::get(agent_id, - boost::bind(¬ify_automute_callback, _1, _2, reason)); - } - } - - return removed; -} - - -std::vector LLMuteList::getMutes() const -{ - std::vector mutes; - - for (mute_set_t::const_iterator it = mMutes.begin(); - it != mMutes.end(); - ++it) - { - mutes.push_back(*it); - } - - for (string_set_t::const_iterator it = mLegacyMutes.begin(); - it != mLegacyMutes.end(); - ++it) - { - LLMute legacy(LLUUID::null, *it); - mutes.push_back(legacy); - } - - std::sort(mutes.begin(), mutes.end(), compare_by_name()); - return mutes; -} - -//----------------------------------------------------------------------------- -// loadFromFile() -//----------------------------------------------------------------------------- -bool LLMuteList::loadFromFile(const std::string& filename) -{ - if(!filename.size()) - { - LL_WARNS() << "Mute List Filename is Empty!" << LL_ENDL; - return false; - } - - LLFILE* fp = LLFile::fopen(filename, "rb"); /*Flawfinder: ignore*/ - if (!fp) - { - LL_WARNS() << "Couldn't open mute list " << filename << LL_ENDL; - return false; - } - - // *NOTE: Changing the size of these buffers will require changes - // in the scanf below. - char id_buffer[MAX_STRING]; /*Flawfinder: ignore*/ - char name_buffer[MAX_STRING]; /*Flawfinder: ignore*/ - char buffer[MAX_STRING]; /*Flawfinder: ignore*/ - while (!feof(fp) - && fgets(buffer, MAX_STRING, fp)) - { - id_buffer[0] = '\0'; - name_buffer[0] = '\0'; - S32 type = 0; - U32 flags = 0; - sscanf( /* Flawfinder: ignore */ - buffer, " %d %254s %254[^|]| %u\n", &type, id_buffer, name_buffer, - &flags); - LLUUID id = LLUUID(id_buffer); - LLMute mute(id, std::string(name_buffer), (LLMute::EType)type, flags); - if (mute.mID.isNull() - || mute.mType == LLMute::BY_NAME) - { - mLegacyMutes.insert(mute.mName); - } - else - { - mMutes.insert(mute); - } - } - fclose(fp); - setLoaded(); - - // server does not maintain up-to date account names (not display names!) - // in this list, so it falls to viewer. - pending_names_t::iterator iter = mPendingAgentNameUpdates.begin(); - pending_names_t::iterator end = mPendingAgentNameUpdates.end(); - while (iter != end) - { - // this will send updates to server, make sure mIsLoaded is set - onAccountNameChanged(iter->first, iter->second); - iter++; - } - mPendingAgentNameUpdates.clear(); - - return true; -} - -//----------------------------------------------------------------------------- -// saveToFile() -//----------------------------------------------------------------------------- -bool LLMuteList::saveToFile(const std::string& filename) -{ - if(!filename.size()) - { - LL_WARNS() << "Mute List Filename is Empty!" << LL_ENDL; - return false; - } - - LLFILE* fp = LLFile::fopen(filename, "wb"); /*Flawfinder: ignore*/ - if (!fp) - { - LL_WARNS() << "Couldn't open mute list " << filename << LL_ENDL; - return false; - } - // legacy mutes have null uuid - std::string id_string; - LLUUID::null.toString(id_string); - for (string_set_t::iterator it = mLegacyMutes.begin(); - it != mLegacyMutes.end(); - ++it) - { - fprintf(fp, "%d %s %s|\n", (S32)LLMute::BY_NAME, id_string.c_str(), it->c_str()); - } - for (mute_set_t::iterator it = mMutes.begin(); - it != mMutes.end(); - ++it) - { - // Don't save external mutes as they are not sent to the server and probably won't - //be valid next time anyway. - if (it->mType != LLMute::EXTERNAL) - { - it->mID.toString(id_string); - const std::string& name = it->mName; - fprintf(fp, "%d %s %s|%u\n", (S32)it->mType, id_string.c_str(), name.c_str(), it->mFlags); - } - } - fclose(fp); - return true; -} - - -bool LLMuteList::isMuted(const LLUUID& id, const std::string& name, U32 flags) const -{ - // for objects, check for muting on their parent prim - LLViewerObject* mute_object = get_object_to_mute_from_id(id); - LLUUID id_to_check = (mute_object) ? mute_object->getID() : id; - - // don't need name or type for lookup - LLMute mute(id_to_check); - mute_set_t::const_iterator mute_it = mMutes.find(mute); - if (mute_it != mMutes.end()) - { - // If any of the flags the caller passed are set, this item isn't considered muted for this caller. - if(flags & mute_it->mFlags) - { - return false; - } - return true; - } - - // empty names can't be legacy-muted - bool avatar = mute_object && mute_object->isAvatar(); - if (name.empty() || avatar) return false; - - // Look in legacy pile - string_set_t::const_iterator legacy_it = mLegacyMutes.find(name); - return legacy_it != mLegacyMutes.end(); -} - -bool LLMuteList::isMuted(const std::string& username, U32 flags) const -{ - mute_set_t::const_iterator mute_iter = mMutes.begin(); - while(mute_iter != mMutes.end()) - { - // can't convert "leha.test" into "LeHa TesT" so username comparison is more reliable - if (mute_iter->mType == LLMute::AGENT - && LLCacheName::buildUsername(mute_iter->mName) == username) - { - return true; - } - mute_iter++; - } - return false; -} - -//----------------------------------------------------------------------------- -// requestFromServer() -//----------------------------------------------------------------------------- -void LLMuteList::requestFromServer(const LLUUID& agent_id) -{ - std::string agent_id_string; - std::string filename; - agent_id.toString(agent_id_string); - filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,agent_id_string) + ".cached_mute"; - LLCRC crc; - crc.update(filename); - - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_MuteListRequest); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, agent_id); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_MuteData); - msg->addU32Fast(_PREHASH_MuteCRC, crc.getCRC()); - - if (gDisconnected) - { - LL_WARNS() << "Trying to request mute list when disconnected!" << LL_ENDL; - return; - } - if (!gAgent.getRegion()) - { - LL_WARNS() << "No region for agent yet, skipping mute list request!" << LL_ENDL; - return; - } - // Double amount of retries due to this request happening during busy stage - // Ideally this should be turned into a capability - gMessageSystem->sendReliable(gAgent.getRegionHost(), LL_DEFAULT_RELIABLE_RETRIES * 2, true, LL_PING_BASED_TIMEOUT_DUMMY, NULL, NULL); -} - -//----------------------------------------------------------------------------- -// cache() -//----------------------------------------------------------------------------- - -void LLMuteList::cache(const LLUUID& agent_id) -{ - // Write to disk even if empty. - if(mIsLoaded) - { - std::string agent_id_string; - std::string filename; - agent_id.toString(agent_id_string); - filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,agent_id_string) + ".cached_mute"; - saveToFile(filename); - } -} - -//----------------------------------------------------------------------------- -// Static message handlers -//----------------------------------------------------------------------------- - -void LLMuteList::processMuteListUpdate(LLMessageSystem* msg, void**) -{ - LL_INFOS() << "LLMuteList::processMuteListUpdate()" << LL_ENDL; - LLUUID agent_id; - msg->getUUIDFast(_PREHASH_MuteData, _PREHASH_AgentID, agent_id); - if(agent_id != gAgent.getID()) - { - LL_WARNS() << "Got an mute list update for the wrong agent." << LL_ENDL; - return; - } - std::string unclean_filename; - msg->getStringFast(_PREHASH_MuteData, _PREHASH_Filename, unclean_filename); - std::string filename = LLDir::getScrubbedFileName(unclean_filename); - - std::string *local_filename_and_path = new std::string(gDirUtilp->getExpandedFilename( LL_PATH_CACHE, filename )); - gXferManager->requestFile(*local_filename_and_path, - filename, - LL_PATH_CACHE, - msg->getSender(), - true, // make the remote file temporary. - onFileMuteList, - (void**)local_filename_and_path, - LLXferManager::HIGH_PRIORITY); -} - -void LLMuteList::processUseCachedMuteList(LLMessageSystem* msg, void**) -{ - LL_INFOS() << "LLMuteList::processUseCachedMuteList()" << LL_ENDL; - - std::string agent_id_string; - gAgent.getID().toString(agent_id_string); - std::string filename; - filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,agent_id_string) + ".cached_mute"; - LLMuteList::getInstance()->loadFromFile(filename); -} - -void LLMuteList::onFileMuteList(void** user_data, S32 error_code, LLExtStat ext_status) -{ - LL_INFOS() << "LLMuteList::processMuteListFile()" << LL_ENDL; - - std::string* local_filename_and_path = (std::string*)user_data; - if(local_filename_and_path && !local_filename_and_path->empty() && (error_code == 0)) - { - LLMuteList::getInstance()->loadFromFile(*local_filename_and_path); - LLFile::remove(*local_filename_and_path); - } - delete local_filename_and_path; -} - -void LLMuteList::onAccountNameChanged(const LLUUID& id, const std::string& username) -{ - if (mIsLoaded) - { - LLMute mute(id, username, LLMute::AGENT); - mute_set_t::iterator mute_it = mMutes.find(mute); - if (mute_it != mMutes.end() - && mute_it->mName != mute.mName - && mute_it->mType == LLMute::AGENT) // just in case, it is std::set, not map - { - // existing mute, but name changed, copy data - mute.mFlags = mute_it->mFlags; - - // erase old variant - mMutes.erase(mute_it); - - // (re)add the mute entry. - { - std::pair result = mMutes.insert(mute); - if (result.second) - { - LL_INFOS() << "Muting " << mute.mName << " id " << mute.mID << " flags " << mute.mFlags << LL_ENDL; - updateAdd(mute); - // Do not notify observers here, observers do not know or need to handle name changes - // Example: block list considers notifyObserversDetailed as a selection update; - // Various chat/voice statuses care only about id and flags - // Since apropriate update time for account names is considered to be in 'hours' it is - // fine not to update UI (will be fine after restart or couple other changes) - - } - } - } - } - else - { - // Delay update until we load file - // Ex: Buddies list can arrive too early since we prerequest - // names from Buddies list before we load blocklist - mPendingAgentNameUpdates[id] = username; - } -} - -void LLMuteList::addObserver(LLMuteListObserver* observer) -{ - mObservers.insert(observer); -} - -void LLMuteList::removeObserver(LLMuteListObserver* observer) -{ - mObservers.erase(observer); -} - -void LLMuteList::setLoaded() -{ - mIsLoaded = true; - notifyObservers(); -} - -void LLMuteList::notifyObservers() -{ - for (observer_set_t::iterator it = mObservers.begin(); - it != mObservers.end(); - ) - { - LLMuteListObserver* observer = *it; - observer->onChange(); - // In case onChange() deleted an entry. - it = mObservers.upper_bound(observer); - } -} - -void LLMuteList::notifyObserversDetailed(const LLMute& mute) -{ - for (observer_set_t::iterator it = mObservers.begin(); - it != mObservers.end(); - ) - { - LLMuteListObserver* observer = *it; - observer->onChangeDetailed(mute); - // In case onChange() deleted an entry. - it = mObservers.upper_bound(observer); - } -} - -LLRenderMuteList::LLRenderMuteList() -{} - -bool LLRenderMuteList::saveToFile() -{ - std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "render_mute_settings.txt"); - LLFILE* fp = LLFile::fopen(filename, "wb"); - if (!fp) - { - LL_WARNS() << "Couldn't open render mute list file: " << filename << LL_ENDL; - return false; - } - - for (std::map::iterator it = sVisuallyMuteSettingsMap.begin(); it != sVisuallyMuteSettingsMap.end(); ++it) - { - if (it->second != 0) - { - std::string id_string; - it->first.toString(id_string); - fprintf(fp, "%d %s [%d]\n", (S32)it->second, id_string.c_str(), (S32)sVisuallyMuteDateMap[it->first]); - } - } - fclose(fp); - return true; -} - -bool LLRenderMuteList::loadFromFile() -{ - std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "render_mute_settings.txt"); - LLFILE* fp = LLFile::fopen(filename, "rb"); - if (!fp) - { - LL_WARNS() << "Couldn't open render mute list file: " << filename << LL_ENDL; - return false; - } - - char id_buffer[MAX_STRING]; - char buffer[MAX_STRING]; - while (!feof(fp) && fgets(buffer, MAX_STRING, fp)) - { - id_buffer[0] = '\0'; - S32 setting = 0; - S32 time = 0; - sscanf(buffer, " %d %254s [%d]\n", &setting, id_buffer, &time); - sVisuallyMuteSettingsMap[LLUUID(id_buffer)] = setting; - sVisuallyMuteDateMap[LLUUID(id_buffer)] = (time == 0) ? (S32)time_corrected() : time; - } - fclose(fp); - return true; -} - -void LLRenderMuteList::saveVisualMuteSetting(const LLUUID& agent_id, S32 setting) -{ - if(setting == 0) - { - sVisuallyMuteSettingsMap.erase(agent_id); - sVisuallyMuteDateMap.erase(agent_id); - } - else - { - sVisuallyMuteSettingsMap[agent_id] = setting; - if (sVisuallyMuteDateMap.find(agent_id) == sVisuallyMuteDateMap.end()) - { - sVisuallyMuteDateMap[agent_id] = (S32)time_corrected(); - } - } - saveToFile(); - notifyObservers(); -} - -S32 LLRenderMuteList::getSavedVisualMuteSetting(const LLUUID& agent_id) -{ - std::map::iterator iter = sVisuallyMuteSettingsMap.find(agent_id); - if (iter != sVisuallyMuteSettingsMap.end()) - { - return iter->second; - } - - return 0; -} - -S32 LLRenderMuteList::getVisualMuteDate(const LLUUID& agent_id) -{ - std::map::iterator iter = sVisuallyMuteDateMap.find(agent_id); - if (iter != sVisuallyMuteDateMap.end()) - { - return iter->second; - } - - return 0; -} - -void LLRenderMuteList::addObserver(LLMuteListObserver* observer) -{ - mObservers.insert(observer); -} - -void LLRenderMuteList::removeObserver(LLMuteListObserver* observer) -{ - mObservers.erase(observer); -} - -void LLRenderMuteList::notifyObservers() -{ - for (observer_set_t::iterator it = mObservers.begin(); - it != mObservers.end(); - ) - { - LLMuteListObserver* observer = *it; - observer->onChange(); - // In case onChange() deleted an entry. - it = mObservers.upper_bound(observer); - } -} +/** + * @file llmutelist.cpp + * @author Richard Nelson, James Cook + * @brief Management of list of muted players + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/* + * How should muting work? + * Mute an avatar + * Mute a specific object (accidentally spamming) + * + * right-click avatar, mute + * see list of recent chatters, mute + * type a name to mute? + * + * show in list whether chatter is avatar or object + * + * need fast lookup by id + * need lookup by name, doesn't have to be fast + */ + +#include "llviewerprecompiledheaders.h" + +#include "llmutelist.h" + +#include "pipeline.h" + +#include +#include +#include + +#include "lldispatcher.h" +#include "llxfermanager.h" + +#include "llagent.h" +#include "llavatarnamecache.h" +#include "llviewergenericmessage.h" // for gGenericDispatcher +#include "llworld.h" //for particle system banning +#include "llimview.h" +#include "llnotifications.h" +#include "llviewercontrol.h" +#include "llviewerobjectlist.h" +#include "lltrans.h" + +namespace +{ + // This method is used to return an object to mute given an object id. + // Its used by the LLMute constructor and LLMuteList::isMuted. + LLViewerObject* get_object_to_mute_from_id(LLUUID object_id) + { + LLViewerObject *objectp = gObjectList.findObject(object_id); + if ((objectp) && (!objectp->isAvatar())) + { + LLViewerObject *parentp = (LLViewerObject *)objectp->getParent(); + if (parentp && parentp->getID() != gAgent.getID()) + { + objectp = parentp; + } + } + return objectp; + } +} + +// "emptymutelist" +class LLDispatchEmptyMuteList : public LLDispatchHandler +{ +public: + virtual bool operator()( + const LLDispatcher* dispatcher, + const std::string& key, + const LLUUID& invoice, + const sparam_t& strings) + { + LLMuteList::getInstance()->setLoaded(); + return true; + } +}; + +static LLDispatchEmptyMuteList sDispatchEmptyMuteList; + +//----------------------------------------------------------------------------- +// LLMute() +//----------------------------------------------------------------------------- + +LLMute::LLMute(const LLUUID& id, const std::string& name, EType type, U32 flags) + : mID(id), + mName(name), + mType(type), + mFlags(flags) +{ + // muting is done by root objects only - try to find this objects root + LLViewerObject* mute_object = get_object_to_mute_from_id(id); + if(mute_object && mute_object->getID() != id) + { + mID = mute_object->getID(); + LLNameValue* firstname = mute_object->getNVPair("FirstName"); + LLNameValue* lastname = mute_object->getNVPair("LastName"); + if (firstname && lastname) + { + mName = LLCacheName::buildFullName( + firstname->getString(), lastname->getString()); + } + mType = mute_object->isAvatar() ? AGENT : OBJECT; + } + +} + + +std::string LLMute::getDisplayType() const +{ + switch (mType) + { + case BY_NAME: + default: + return LLTrans::getString("MuteByName"); + break; + case AGENT: + return LLTrans::getString("MuteAgent"); + break; + case OBJECT: + return LLTrans::getString("MuteObject"); + break; + case GROUP: + return LLTrans::getString("MuteGroup"); + break; + case EXTERNAL: + return LLTrans::getString("MuteExternal"); + break; + } +} + +//----------------------------------------------------------------------------- +// LLMuteList() +//----------------------------------------------------------------------------- +LLMuteList::LLMuteList() : + mIsLoaded(false) +{ + gGenericDispatcher.addHandler("emptymutelist", &sDispatchEmptyMuteList); + + // Register our callbacks. We may be constructed before gMessageSystem, so + // use callWhenReady() to register them as soon as gMessageSystem becomes + // available. + // When using bind(), must be explicit about default arguments such as + // that last NULL. + gMessageSystem.callWhenReady(boost::bind(&LLMessageSystem::setHandlerFuncFast, _1, + _PREHASH_MuteListUpdate, processMuteListUpdate, + static_cast(NULL))); + gMessageSystem.callWhenReady(boost::bind(&LLMessageSystem::setHandlerFuncFast, _1, + _PREHASH_UseCachedMuteList, processUseCachedMuteList, + static_cast(NULL))); + + // make sure mute list's instance gets initialized before we start any name requests + LLAvatarNameCache::getInstance()->setAccountNameChangedCallback([this](const LLUUID& id, const LLAvatarName& av_name) + { + // it would be better to just pass LLAvatarName instead of doing unnesssesary copies + // but this way is just more convinient + onAccountNameChanged(id, av_name.getUserName()); + }); +} + +//----------------------------------------------------------------------------- +// ~LLMuteList() +//----------------------------------------------------------------------------- +LLMuteList::~LLMuteList() +{ + +} + +void LLMuteList::cleanupSingleton() +{ + LLAvatarNameCache::getInstance()->setAccountNameChangedCallback(NULL); +} + +bool LLMuteList::isLinden(const std::string& name) +{ + std::string username = boost::replace_all_copy(name, ".", " "); + typedef boost::tokenizer > tokenizer; + boost::char_separator sep(" "); + tokenizer tokens(username, sep); + tokenizer::iterator token_iter = tokens.begin(); + + if (token_iter == tokens.end()) return false; + token_iter++; + if (token_iter == tokens.end()) return false; + + std::string last_name = *token_iter; + LLStringUtil::toLower(last_name); + return last_name == "linden"; +} + +static LLVOAvatar* find_avatar(const LLUUID& id) +{ + LLViewerObject *obj = gObjectList.findObject(id); + while (obj && obj->isAttachment()) + { + obj = (LLViewerObject *)obj->getParent(); + } + + if (obj && obj->isAvatar()) + { + return (LLVOAvatar*)obj; + } + else + { + return NULL; + } +} + +bool LLMuteList::add(const LLMute& mute, U32 flags) +{ + // Can't mute text from Lindens + if ((mute.mType == LLMute::AGENT) + && isLinden(mute.mName) && (flags & LLMute::flagTextChat || flags == 0)) + { + LL_WARNS() << "Trying to mute a Linden; ignored" << LL_ENDL; + LLNotifications::instance().add("MuteLinden", LLSD(), LLSD()); + return false; + } + + // Can't mute self. + if (mute.mType == LLMute::AGENT + && mute.mID == gAgent.getID()) + { + LL_WARNS() << "Trying to self; ignored" << LL_ENDL; + return false; + } + + static LLCachedControl mute_list_limit(gSavedSettings, "MuteListLimit", 1000); + if (getMutes().size() >= mute_list_limit) + { + LL_WARNS() << "Mute limit is reached; ignored" << LL_ENDL; + LLSD args; + args["MUTE_LIMIT"] = mute_list_limit; + LLNotifications::instance().add(LLNotification::Params("MuteLimitReached").substitutions(args)); + return false; + } + + if (mute.mType == LLMute::BY_NAME) + { + // Can't mute empty string by name + if (mute.mName.empty()) + { + LL_WARNS() << "Trying to mute empty string by-name" << LL_ENDL; + return false; + } + + // Null mutes must have uuid null + if (mute.mID.notNull()) + { + LL_WARNS() << "Trying to add by-name mute with non-null id" << LL_ENDL; + return false; + } + + std::pair result = mLegacyMutes.insert(mute.mName); + if (result.second) + { + LL_INFOS() << "Muting by name " << mute.mName << LL_ENDL; + updateAdd(mute); + notifyObservers(); + notifyObserversDetailed(mute); + return true; + } + else + { + LL_INFOS() << "duplicate mute ignored" << LL_ENDL; + // was duplicate + return false; + } + } + else + { + // Need a local (non-const) copy to set up flags properly. + LLMute localmute = mute; + + // If an entry for the same entity is already in the list, remove it, saving flags as necessary. + mute_set_t::iterator it = mMutes.find(localmute); + if (it != mMutes.end()) + { + // This mute is already in the list. Save the existing entry's flags if that's warranted. + localmute.mFlags = it->mFlags; + + mMutes.erase(it); + // Don't need to call notifyObservers() here, since it will happen after the entry has been re-added below. + } + else + { + // There was no entry in the list previously. Fake things up by making it look like the previous entry had all properties unmuted. + localmute.mFlags = LLMute::flagAll; + } + + if(flags) + { + // The user passed some combination of flags. Make sure those flag bits are turned off (i.e. those properties will be muted). + localmute.mFlags &= (~flags); + } + else + { + // The user passed 0. Make sure all flag bits are turned off (i.e. all properties will be muted). + localmute.mFlags = 0; + } + + // (re)add the mute entry. + { + std::pair result = mMutes.insert(localmute); + if (result.second) + { + LL_INFOS() << "Muting " << localmute.mName << " id " << localmute.mID << " flags " << localmute.mFlags << LL_ENDL; + updateAdd(localmute); + notifyObservers(); + notifyObserversDetailed(localmute); + + //mute local lights that are attached to the avatar + LLVOAvatar *avatarp = find_avatar(localmute.mID); + if (avatarp) + { + LLPipeline::removeMutedAVsLights(avatarp); + } + //remove agent's notifications as well + if (localmute.mType == LLMute::AGENT) + { + LLNotifications::instance().cancelByOwner(localmute.mID); + } + return true; + } + } + } + + // If we were going to return success, we'd have done it by now. + return false; +} + +void LLMuteList::updateAdd(const LLMute& mute) +{ + // External mutes are local only, don't send them to the server. + if (mute.mType == LLMute::EXTERNAL) + { + return; + } + + // Update the database + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_UpdateMuteListEntry); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_MuteData); + msg->addUUIDFast(_PREHASH_MuteID, mute.mID); + msg->addStringFast(_PREHASH_MuteName, mute.mName); + msg->addS32("MuteType", mute.mType); + msg->addU32("MuteFlags", mute.mFlags); + gAgent.sendReliableMessage(); + + if (!mIsLoaded) + { + LL_WARNS() << "Added elements to non-initialized block list" << LL_ENDL; + } + mIsLoaded = true; // why is this here? -MG +} + + +bool LLMuteList::remove(const LLMute& mute, U32 flags) +{ + bool found = false; + + // First, remove from main list. + mute_set_t::iterator it = mMutes.find(mute); + if (it != mMutes.end()) + { + LLMute localmute = *it; + bool remove = true; + if(flags) + { + // If the user passed mute flags, we may only want to turn some flags on. + localmute.mFlags |= flags; + + if(localmute.mFlags == LLMute::flagAll) + { + // Every currently available mute property has been masked out. + // Remove the mute entry entirely. + } + else + { + // Only some of the properties are masked out. Update the entry. + remove = false; + } + } + else + { + // The caller didn't pass any flags -- just remove the mute entry entirely. + // set flags to notify observers with (flag being present means that something is allowed) + localmute.mFlags = LLMute::flagAll; + } + + // Always remove the entry from the set -- it will be re-added with new flags if necessary. + mMutes.erase(it); + + if(remove) + { + // The entry was actually removed. Notify the server. + updateRemove(localmute); + LL_INFOS() << "Unmuting " << localmute.mName << " id " << localmute.mID << " flags " << localmute.mFlags << LL_ENDL; + } + else + { + // Flags were updated, the mute entry needs to be retransmitted to the server and re-added to the list. + mMutes.insert(localmute); + updateAdd(localmute); + LL_INFOS() << "Updating mute entry " << localmute.mName << " id " << localmute.mID << " flags " << localmute.mFlags << LL_ENDL; + } + + // Must be after erase. + notifyObservers(); + notifyObserversDetailed(localmute); + } + else + { + // Clean up any legacy mutes + string_set_t::iterator legacy_it = mLegacyMutes.find(mute.mName); + if (legacy_it != mLegacyMutes.end()) + { + // Database representation of legacy mute is UUID null. + LLMute mute(LLUUID::null, *legacy_it, LLMute::BY_NAME); + updateRemove(mute); + mLegacyMutes.erase(legacy_it); + // Must be after erase. + notifyObservers(); + notifyObserversDetailed(mute); + } + } + + return found; +} + + +void LLMuteList::updateRemove(const LLMute& mute) +{ + // External mutes are not sent to the server anyway, no need to remove them. + if (mute.mType == LLMute::EXTERNAL) + { + return; + } + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_RemoveMuteListEntry); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_MuteData); + msg->addUUIDFast(_PREHASH_MuteID, mute.mID); + msg->addString("MuteName", mute.mName); + gAgent.sendReliableMessage(); +} + +void notify_automute_callback(const LLUUID& agent_id, const LLAvatarName& full_name, LLMuteList::EAutoReason reason) +{ + std::string notif_name; + switch (reason) + { + default: + case LLMuteList::AR_IM: + notif_name = "AutoUnmuteByIM"; + break; + case LLMuteList::AR_INVENTORY: + notif_name = "AutoUnmuteByInventory"; + break; + case LLMuteList::AR_MONEY: + notif_name = "AutoUnmuteByMoney"; + break; + } + + LLSD args; + args["NAME"] = full_name.getUserName(); + + LLNotificationPtr notif_ptr = LLNotifications::instance().add(notif_name, args, LLSD()); + if (notif_ptr) + { + std::string message = notif_ptr->getMessage(); + + if (reason == LLMuteList::AR_IM) + { + LLIMModel::getInstance()->addMessage(agent_id, SYSTEM_FROM, LLUUID::null, message); + } + } +} + + +bool LLMuteList::autoRemove(const LLUUID& agent_id, const EAutoReason reason) +{ + bool removed = false; + + if (isMuted(agent_id)) + { + LLMute automute(agent_id, LLStringUtil::null, LLMute::AGENT); + removed = true; + remove(automute); + + LLAvatarName av_name; + if (LLAvatarNameCache::get(agent_id, &av_name)) + { + // name in cache, call callback directly + notify_automute_callback(agent_id, av_name, reason); + } + else + { + // not in cache, lookup name from cache + LLAvatarNameCache::get(agent_id, + boost::bind(¬ify_automute_callback, _1, _2, reason)); + } + } + + return removed; +} + + +std::vector LLMuteList::getMutes() const +{ + std::vector mutes; + + for (mute_set_t::const_iterator it = mMutes.begin(); + it != mMutes.end(); + ++it) + { + mutes.push_back(*it); + } + + for (string_set_t::const_iterator it = mLegacyMutes.begin(); + it != mLegacyMutes.end(); + ++it) + { + LLMute legacy(LLUUID::null, *it); + mutes.push_back(legacy); + } + + std::sort(mutes.begin(), mutes.end(), compare_by_name()); + return mutes; +} + +//----------------------------------------------------------------------------- +// loadFromFile() +//----------------------------------------------------------------------------- +bool LLMuteList::loadFromFile(const std::string& filename) +{ + if(!filename.size()) + { + LL_WARNS() << "Mute List Filename is Empty!" << LL_ENDL; + return false; + } + + LLFILE* fp = LLFile::fopen(filename, "rb"); /*Flawfinder: ignore*/ + if (!fp) + { + LL_WARNS() << "Couldn't open mute list " << filename << LL_ENDL; + return false; + } + + // *NOTE: Changing the size of these buffers will require changes + // in the scanf below. + char id_buffer[MAX_STRING]; /*Flawfinder: ignore*/ + char name_buffer[MAX_STRING]; /*Flawfinder: ignore*/ + char buffer[MAX_STRING]; /*Flawfinder: ignore*/ + while (!feof(fp) + && fgets(buffer, MAX_STRING, fp)) + { + id_buffer[0] = '\0'; + name_buffer[0] = '\0'; + S32 type = 0; + U32 flags = 0; + sscanf( /* Flawfinder: ignore */ + buffer, " %d %254s %254[^|]| %u\n", &type, id_buffer, name_buffer, + &flags); + LLUUID id = LLUUID(id_buffer); + LLMute mute(id, std::string(name_buffer), (LLMute::EType)type, flags); + if (mute.mID.isNull() + || mute.mType == LLMute::BY_NAME) + { + mLegacyMutes.insert(mute.mName); + } + else + { + mMutes.insert(mute); + } + } + fclose(fp); + setLoaded(); + + // server does not maintain up-to date account names (not display names!) + // in this list, so it falls to viewer. + pending_names_t::iterator iter = mPendingAgentNameUpdates.begin(); + pending_names_t::iterator end = mPendingAgentNameUpdates.end(); + while (iter != end) + { + // this will send updates to server, make sure mIsLoaded is set + onAccountNameChanged(iter->first, iter->second); + iter++; + } + mPendingAgentNameUpdates.clear(); + + return true; +} + +//----------------------------------------------------------------------------- +// saveToFile() +//----------------------------------------------------------------------------- +bool LLMuteList::saveToFile(const std::string& filename) +{ + if(!filename.size()) + { + LL_WARNS() << "Mute List Filename is Empty!" << LL_ENDL; + return false; + } + + LLFILE* fp = LLFile::fopen(filename, "wb"); /*Flawfinder: ignore*/ + if (!fp) + { + LL_WARNS() << "Couldn't open mute list " << filename << LL_ENDL; + return false; + } + // legacy mutes have null uuid + std::string id_string; + LLUUID::null.toString(id_string); + for (string_set_t::iterator it = mLegacyMutes.begin(); + it != mLegacyMutes.end(); + ++it) + { + fprintf(fp, "%d %s %s|\n", (S32)LLMute::BY_NAME, id_string.c_str(), it->c_str()); + } + for (mute_set_t::iterator it = mMutes.begin(); + it != mMutes.end(); + ++it) + { + // Don't save external mutes as they are not sent to the server and probably won't + //be valid next time anyway. + if (it->mType != LLMute::EXTERNAL) + { + it->mID.toString(id_string); + const std::string& name = it->mName; + fprintf(fp, "%d %s %s|%u\n", (S32)it->mType, id_string.c_str(), name.c_str(), it->mFlags); + } + } + fclose(fp); + return true; +} + + +bool LLMuteList::isMuted(const LLUUID& id, const std::string& name, U32 flags) const +{ + // for objects, check for muting on their parent prim + LLViewerObject* mute_object = get_object_to_mute_from_id(id); + LLUUID id_to_check = (mute_object) ? mute_object->getID() : id; + + // don't need name or type for lookup + LLMute mute(id_to_check); + mute_set_t::const_iterator mute_it = mMutes.find(mute); + if (mute_it != mMutes.end()) + { + // If any of the flags the caller passed are set, this item isn't considered muted for this caller. + if(flags & mute_it->mFlags) + { + return false; + } + return true; + } + + // empty names can't be legacy-muted + bool avatar = mute_object && mute_object->isAvatar(); + if (name.empty() || avatar) return false; + + // Look in legacy pile + string_set_t::const_iterator legacy_it = mLegacyMutes.find(name); + return legacy_it != mLegacyMutes.end(); +} + +bool LLMuteList::isMuted(const std::string& username, U32 flags) const +{ + mute_set_t::const_iterator mute_iter = mMutes.begin(); + while(mute_iter != mMutes.end()) + { + // can't convert "leha.test" into "LeHa TesT" so username comparison is more reliable + if (mute_iter->mType == LLMute::AGENT + && LLCacheName::buildUsername(mute_iter->mName) == username) + { + return true; + } + mute_iter++; + } + return false; +} + +//----------------------------------------------------------------------------- +// requestFromServer() +//----------------------------------------------------------------------------- +void LLMuteList::requestFromServer(const LLUUID& agent_id) +{ + std::string agent_id_string; + std::string filename; + agent_id.toString(agent_id_string); + filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,agent_id_string) + ".cached_mute"; + LLCRC crc; + crc.update(filename); + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_MuteListRequest); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, agent_id); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_MuteData); + msg->addU32Fast(_PREHASH_MuteCRC, crc.getCRC()); + + if (gDisconnected) + { + LL_WARNS() << "Trying to request mute list when disconnected!" << LL_ENDL; + return; + } + if (!gAgent.getRegion()) + { + LL_WARNS() << "No region for agent yet, skipping mute list request!" << LL_ENDL; + return; + } + // Double amount of retries due to this request happening during busy stage + // Ideally this should be turned into a capability + gMessageSystem->sendReliable(gAgent.getRegionHost(), LL_DEFAULT_RELIABLE_RETRIES * 2, true, LL_PING_BASED_TIMEOUT_DUMMY, NULL, NULL); +} + +//----------------------------------------------------------------------------- +// cache() +//----------------------------------------------------------------------------- + +void LLMuteList::cache(const LLUUID& agent_id) +{ + // Write to disk even if empty. + if(mIsLoaded) + { + std::string agent_id_string; + std::string filename; + agent_id.toString(agent_id_string); + filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,agent_id_string) + ".cached_mute"; + saveToFile(filename); + } +} + +//----------------------------------------------------------------------------- +// Static message handlers +//----------------------------------------------------------------------------- + +void LLMuteList::processMuteListUpdate(LLMessageSystem* msg, void**) +{ + LL_INFOS() << "LLMuteList::processMuteListUpdate()" << LL_ENDL; + LLUUID agent_id; + msg->getUUIDFast(_PREHASH_MuteData, _PREHASH_AgentID, agent_id); + if(agent_id != gAgent.getID()) + { + LL_WARNS() << "Got an mute list update for the wrong agent." << LL_ENDL; + return; + } + std::string unclean_filename; + msg->getStringFast(_PREHASH_MuteData, _PREHASH_Filename, unclean_filename); + std::string filename = LLDir::getScrubbedFileName(unclean_filename); + + std::string *local_filename_and_path = new std::string(gDirUtilp->getExpandedFilename( LL_PATH_CACHE, filename )); + gXferManager->requestFile(*local_filename_and_path, + filename, + LL_PATH_CACHE, + msg->getSender(), + true, // make the remote file temporary. + onFileMuteList, + (void**)local_filename_and_path, + LLXferManager::HIGH_PRIORITY); +} + +void LLMuteList::processUseCachedMuteList(LLMessageSystem* msg, void**) +{ + LL_INFOS() << "LLMuteList::processUseCachedMuteList()" << LL_ENDL; + + std::string agent_id_string; + gAgent.getID().toString(agent_id_string); + std::string filename; + filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,agent_id_string) + ".cached_mute"; + LLMuteList::getInstance()->loadFromFile(filename); +} + +void LLMuteList::onFileMuteList(void** user_data, S32 error_code, LLExtStat ext_status) +{ + LL_INFOS() << "LLMuteList::processMuteListFile()" << LL_ENDL; + + std::string* local_filename_and_path = (std::string*)user_data; + if(local_filename_and_path && !local_filename_and_path->empty() && (error_code == 0)) + { + LLMuteList::getInstance()->loadFromFile(*local_filename_and_path); + LLFile::remove(*local_filename_and_path); + } + delete local_filename_and_path; +} + +void LLMuteList::onAccountNameChanged(const LLUUID& id, const std::string& username) +{ + if (mIsLoaded) + { + LLMute mute(id, username, LLMute::AGENT); + mute_set_t::iterator mute_it = mMutes.find(mute); + if (mute_it != mMutes.end() + && mute_it->mName != mute.mName + && mute_it->mType == LLMute::AGENT) // just in case, it is std::set, not map + { + // existing mute, but name changed, copy data + mute.mFlags = mute_it->mFlags; + + // erase old variant + mMutes.erase(mute_it); + + // (re)add the mute entry. + { + std::pair result = mMutes.insert(mute); + if (result.second) + { + LL_INFOS() << "Muting " << mute.mName << " id " << mute.mID << " flags " << mute.mFlags << LL_ENDL; + updateAdd(mute); + // Do not notify observers here, observers do not know or need to handle name changes + // Example: block list considers notifyObserversDetailed as a selection update; + // Various chat/voice statuses care only about id and flags + // Since apropriate update time for account names is considered to be in 'hours' it is + // fine not to update UI (will be fine after restart or couple other changes) + + } + } + } + } + else + { + // Delay update until we load file + // Ex: Buddies list can arrive too early since we prerequest + // names from Buddies list before we load blocklist + mPendingAgentNameUpdates[id] = username; + } +} + +void LLMuteList::addObserver(LLMuteListObserver* observer) +{ + mObservers.insert(observer); +} + +void LLMuteList::removeObserver(LLMuteListObserver* observer) +{ + mObservers.erase(observer); +} + +void LLMuteList::setLoaded() +{ + mIsLoaded = true; + notifyObservers(); +} + +void LLMuteList::notifyObservers() +{ + for (observer_set_t::iterator it = mObservers.begin(); + it != mObservers.end(); + ) + { + LLMuteListObserver* observer = *it; + observer->onChange(); + // In case onChange() deleted an entry. + it = mObservers.upper_bound(observer); + } +} + +void LLMuteList::notifyObserversDetailed(const LLMute& mute) +{ + for (observer_set_t::iterator it = mObservers.begin(); + it != mObservers.end(); + ) + { + LLMuteListObserver* observer = *it; + observer->onChangeDetailed(mute); + // In case onChange() deleted an entry. + it = mObservers.upper_bound(observer); + } +} + +LLRenderMuteList::LLRenderMuteList() +{} + +bool LLRenderMuteList::saveToFile() +{ + std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "render_mute_settings.txt"); + LLFILE* fp = LLFile::fopen(filename, "wb"); + if (!fp) + { + LL_WARNS() << "Couldn't open render mute list file: " << filename << LL_ENDL; + return false; + } + + for (std::map::iterator it = sVisuallyMuteSettingsMap.begin(); it != sVisuallyMuteSettingsMap.end(); ++it) + { + if (it->second != 0) + { + std::string id_string; + it->first.toString(id_string); + fprintf(fp, "%d %s [%d]\n", (S32)it->second, id_string.c_str(), (S32)sVisuallyMuteDateMap[it->first]); + } + } + fclose(fp); + return true; +} + +bool LLRenderMuteList::loadFromFile() +{ + std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "render_mute_settings.txt"); + LLFILE* fp = LLFile::fopen(filename, "rb"); + if (!fp) + { + LL_WARNS() << "Couldn't open render mute list file: " << filename << LL_ENDL; + return false; + } + + char id_buffer[MAX_STRING]; + char buffer[MAX_STRING]; + while (!feof(fp) && fgets(buffer, MAX_STRING, fp)) + { + id_buffer[0] = '\0'; + S32 setting = 0; + S32 time = 0; + sscanf(buffer, " %d %254s [%d]\n", &setting, id_buffer, &time); + sVisuallyMuteSettingsMap[LLUUID(id_buffer)] = setting; + sVisuallyMuteDateMap[LLUUID(id_buffer)] = (time == 0) ? (S32)time_corrected() : time; + } + fclose(fp); + return true; +} + +void LLRenderMuteList::saveVisualMuteSetting(const LLUUID& agent_id, S32 setting) +{ + if(setting == 0) + { + sVisuallyMuteSettingsMap.erase(agent_id); + sVisuallyMuteDateMap.erase(agent_id); + } + else + { + sVisuallyMuteSettingsMap[agent_id] = setting; + if (sVisuallyMuteDateMap.find(agent_id) == sVisuallyMuteDateMap.end()) + { + sVisuallyMuteDateMap[agent_id] = (S32)time_corrected(); + } + } + saveToFile(); + notifyObservers(); +} + +S32 LLRenderMuteList::getSavedVisualMuteSetting(const LLUUID& agent_id) +{ + std::map::iterator iter = sVisuallyMuteSettingsMap.find(agent_id); + if (iter != sVisuallyMuteSettingsMap.end()) + { + return iter->second; + } + + return 0; +} + +S32 LLRenderMuteList::getVisualMuteDate(const LLUUID& agent_id) +{ + std::map::iterator iter = sVisuallyMuteDateMap.find(agent_id); + if (iter != sVisuallyMuteDateMap.end()) + { + return iter->second; + } + + return 0; +} + +void LLRenderMuteList::addObserver(LLMuteListObserver* observer) +{ + mObservers.insert(observer); +} + +void LLRenderMuteList::removeObserver(LLMuteListObserver* observer) +{ + mObservers.erase(observer); +} + +void LLRenderMuteList::notifyObservers() +{ + for (observer_set_t::iterator it = mObservers.begin(); + it != mObservers.end(); + ) + { + LLMuteListObserver* observer = *it; + observer->onChange(); + // In case onChange() deleted an entry. + it = mObservers.upper_bound(observer); + } +} diff --git a/indra/newview/llmutelist.h b/indra/newview/llmutelist.h index 78ea6ee01d..13d579c61f 100644 --- a/indra/newview/llmutelist.h +++ b/indra/newview/llmutelist.h @@ -1,207 +1,207 @@ -/** - * @file llmutelist.h - * @brief Management of list of muted players - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_MUTELIST_H -#define LL_MUTELIST_H - -#include "llstring.h" -#include "lluuid.h" -#include "llextendedstatus.h" - -class LLViewerObject; -class LLMessageSystem; -class LLMuteListObserver; - -// An entry in the mute list. -class LLMute -{ -public: - // Legacy mutes are BY_NAME and have null UUID. - // EXTERNAL mutes are only processed through an external system (e.g. Voice) and not stored. - enum EType { BY_NAME = 0, AGENT = 1, OBJECT = 2, GROUP = 3, EXTERNAL = 4, COUNT = 5 }; - - // Bits in the mute flags. For backwards compatibility (since any mute list entries that were created before the flags existed - // will have a flags field of 0), some of the flags are "inverted". - // Note that it's possible, through flags, to completely disable an entry in the mute list. The code should detect this case - // and remove the mute list entry instead. - enum - { - flagTextChat = 0x00000001, // If set, don't mute user's text chat - flagVoiceChat = 0x00000002, // If set, don't mute user's voice chat - flagParticles = 0x00000004, // If set, don't mute user's particles - flagObjectSounds = 0x00000008, // If set, mute user's object sounds - - flagAll = 0x0000000F // Mask of all currently defined flags - }; - - LLMute(const LLUUID& id, const std::string& name = std::string(), EType type = BY_NAME, U32 flags = 0); - - // Returns localized type name of muted item - std::string getDisplayType() const; - -public: - LLUUID mID; // agent or object id - std::string mName; // agent or object name, does not store last name "Resident" - EType mType; // needed for UI display of existing mutes - U32 mFlags; // flags pertaining to this mute entry -}; - -class LLMuteList : public LLSingleton -{ - LLSINGLETON(LLMuteList); - ~LLMuteList(); - /*virtual*/ void cleanupSingleton() override; -public: - // reasons for auto-unmuting a resident - enum EAutoReason - { - AR_IM = 0, // agent IMed a muted resident - AR_MONEY = 1, // agent paid L$ to a muted resident - AR_INVENTORY = 2, // agent offered inventory to a muted resident - AR_COUNT // enum count - }; - - - void addObserver(LLMuteListObserver* observer); - void removeObserver(LLMuteListObserver* observer); - - // Add either a normal or a BY_NAME mute, for any or all properties. - bool add(const LLMute& mute, U32 flags = 0); - - // Remove both normal and legacy mutes, for any or all properties. - bool remove(const LLMute& mute, U32 flags = 0); - bool autoRemove(const LLUUID& agent_id, const EAutoReason reason); - - // Name is required to test against legacy text-only mutes. - bool isMuted(const LLUUID& id, const std::string& name = LLStringUtil::null, U32 flags = 0) const; - - // Workaround for username-based mute search, a lot of string conversions so use cautiously - // Expects lower case username - bool isMuted(const std::string& username, U32 flags = 0) const; - - // Alternate (convenience) form for places we don't need to pass the name, but do need flags - bool isMuted(const LLUUID& id, U32 flags) const { return isMuted(id, LLStringUtil::null, flags); }; - - static bool isLinden(const std::string& name); - - bool isLoaded() const { return mIsLoaded; } - - std::vector getMutes() const; - - // request the mute list - void requestFromServer(const LLUUID& agent_id); - - // call this method on logout to save everything. - void cache(const LLUUID& agent_id); - -private: - bool loadFromFile(const std::string& filename); - bool saveToFile(const std::string& filename); - - void setLoaded(); - void notifyObservers(); - void notifyObserversDetailed(const LLMute &mute); - - void updateAdd(const LLMute& mute); - void updateRemove(const LLMute& mute); - - // TODO: NULL out mute_id in database - static void processMuteListUpdate(LLMessageSystem* msg, void**); - static void processUseCachedMuteList(LLMessageSystem* msg, void**); - - static void onFileMuteList(void** user_data, S32 code, LLExtStat ext_status); - void onAccountNameChanged(const LLUUID& id, const std::string& username); - -private: - struct compare_by_name - { - bool operator()(const LLMute& a, const LLMute& b) const - { - std::string name1 = a.mName; - std::string name2 = b.mName; - - LLStringUtil::toUpper(name1); - LLStringUtil::toUpper(name2); - - return name1 < name2; - } - }; - struct compare_by_id - { - bool operator()(const LLMute& a, const LLMute& b) const - { - return a.mID < b.mID; - } - }; - typedef std::set mute_set_t; - mute_set_t mMutes; - typedef std::map pending_names_t; - pending_names_t mPendingAgentNameUpdates; - - typedef std::set string_set_t; - string_set_t mLegacyMutes; - - typedef std::set observer_set_t; - observer_set_t mObservers; - - bool mIsLoaded; - - friend class LLDispatchEmptyMuteList; -}; - -class LLMuteListObserver -{ -public: - virtual ~LLMuteListObserver() { } - virtual void onChange() = 0; - virtual void onChangeDetailed(const LLMute& ) { } -}; - -class LLRenderMuteList : public LLSingleton -{ - LLSINGLETON(LLRenderMuteList); -public: - bool loadFromFile(); - bool saveToFile(); - S32 getSavedVisualMuteSetting(const LLUUID& agent_id); - void saveVisualMuteSetting(const LLUUID& agent_id, S32 setting); - - S32 getVisualMuteDate(const LLUUID& agent_id); - - void addObserver(LLMuteListObserver* observer); - void removeObserver(LLMuteListObserver* observer); - - std::map sVisuallyMuteSettingsMap; - std::map sVisuallyMuteDateMap; - -private: - void notifyObservers(); - typedef std::set observer_set_t; - observer_set_t mObservers; -}; - - -#endif //LL_MUTELIST_H +/** + * @file llmutelist.h + * @brief Management of list of muted players + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_MUTELIST_H +#define LL_MUTELIST_H + +#include "llstring.h" +#include "lluuid.h" +#include "llextendedstatus.h" + +class LLViewerObject; +class LLMessageSystem; +class LLMuteListObserver; + +// An entry in the mute list. +class LLMute +{ +public: + // Legacy mutes are BY_NAME and have null UUID. + // EXTERNAL mutes are only processed through an external system (e.g. Voice) and not stored. + enum EType { BY_NAME = 0, AGENT = 1, OBJECT = 2, GROUP = 3, EXTERNAL = 4, COUNT = 5 }; + + // Bits in the mute flags. For backwards compatibility (since any mute list entries that were created before the flags existed + // will have a flags field of 0), some of the flags are "inverted". + // Note that it's possible, through flags, to completely disable an entry in the mute list. The code should detect this case + // and remove the mute list entry instead. + enum + { + flagTextChat = 0x00000001, // If set, don't mute user's text chat + flagVoiceChat = 0x00000002, // If set, don't mute user's voice chat + flagParticles = 0x00000004, // If set, don't mute user's particles + flagObjectSounds = 0x00000008, // If set, mute user's object sounds + + flagAll = 0x0000000F // Mask of all currently defined flags + }; + + LLMute(const LLUUID& id, const std::string& name = std::string(), EType type = BY_NAME, U32 flags = 0); + + // Returns localized type name of muted item + std::string getDisplayType() const; + +public: + LLUUID mID; // agent or object id + std::string mName; // agent or object name, does not store last name "Resident" + EType mType; // needed for UI display of existing mutes + U32 mFlags; // flags pertaining to this mute entry +}; + +class LLMuteList : public LLSingleton +{ + LLSINGLETON(LLMuteList); + ~LLMuteList(); + /*virtual*/ void cleanupSingleton() override; +public: + // reasons for auto-unmuting a resident + enum EAutoReason + { + AR_IM = 0, // agent IMed a muted resident + AR_MONEY = 1, // agent paid L$ to a muted resident + AR_INVENTORY = 2, // agent offered inventory to a muted resident + AR_COUNT // enum count + }; + + + void addObserver(LLMuteListObserver* observer); + void removeObserver(LLMuteListObserver* observer); + + // Add either a normal or a BY_NAME mute, for any or all properties. + bool add(const LLMute& mute, U32 flags = 0); + + // Remove both normal and legacy mutes, for any or all properties. + bool remove(const LLMute& mute, U32 flags = 0); + bool autoRemove(const LLUUID& agent_id, const EAutoReason reason); + + // Name is required to test against legacy text-only mutes. + bool isMuted(const LLUUID& id, const std::string& name = LLStringUtil::null, U32 flags = 0) const; + + // Workaround for username-based mute search, a lot of string conversions so use cautiously + // Expects lower case username + bool isMuted(const std::string& username, U32 flags = 0) const; + + // Alternate (convenience) form for places we don't need to pass the name, but do need flags + bool isMuted(const LLUUID& id, U32 flags) const { return isMuted(id, LLStringUtil::null, flags); }; + + static bool isLinden(const std::string& name); + + bool isLoaded() const { return mIsLoaded; } + + std::vector getMutes() const; + + // request the mute list + void requestFromServer(const LLUUID& agent_id); + + // call this method on logout to save everything. + void cache(const LLUUID& agent_id); + +private: + bool loadFromFile(const std::string& filename); + bool saveToFile(const std::string& filename); + + void setLoaded(); + void notifyObservers(); + void notifyObserversDetailed(const LLMute &mute); + + void updateAdd(const LLMute& mute); + void updateRemove(const LLMute& mute); + + // TODO: NULL out mute_id in database + static void processMuteListUpdate(LLMessageSystem* msg, void**); + static void processUseCachedMuteList(LLMessageSystem* msg, void**); + + static void onFileMuteList(void** user_data, S32 code, LLExtStat ext_status); + void onAccountNameChanged(const LLUUID& id, const std::string& username); + +private: + struct compare_by_name + { + bool operator()(const LLMute& a, const LLMute& b) const + { + std::string name1 = a.mName; + std::string name2 = b.mName; + + LLStringUtil::toUpper(name1); + LLStringUtil::toUpper(name2); + + return name1 < name2; + } + }; + struct compare_by_id + { + bool operator()(const LLMute& a, const LLMute& b) const + { + return a.mID < b.mID; + } + }; + typedef std::set mute_set_t; + mute_set_t mMutes; + typedef std::map pending_names_t; + pending_names_t mPendingAgentNameUpdates; + + typedef std::set string_set_t; + string_set_t mLegacyMutes; + + typedef std::set observer_set_t; + observer_set_t mObservers; + + bool mIsLoaded; + + friend class LLDispatchEmptyMuteList; +}; + +class LLMuteListObserver +{ +public: + virtual ~LLMuteListObserver() { } + virtual void onChange() = 0; + virtual void onChangeDetailed(const LLMute& ) { } +}; + +class LLRenderMuteList : public LLSingleton +{ + LLSINGLETON(LLRenderMuteList); +public: + bool loadFromFile(); + bool saveToFile(); + S32 getSavedVisualMuteSetting(const LLUUID& agent_id); + void saveVisualMuteSetting(const LLUUID& agent_id, S32 setting); + + S32 getVisualMuteDate(const LLUUID& agent_id); + + void addObserver(LLMuteListObserver* observer); + void removeObserver(LLMuteListObserver* observer); + + std::map sVisuallyMuteSettingsMap; + std::map sVisuallyMuteDateMap; + +private: + void notifyObservers(); + typedef std::set observer_set_t; + observer_set_t mObservers; +}; + + +#endif //LL_MUTELIST_H diff --git a/indra/newview/llnamebox.cpp b/indra/newview/llnamebox.cpp index 600cdc86be..0e88fa7461 100644 --- a/indra/newview/llnamebox.cpp +++ b/indra/newview/llnamebox.cpp @@ -1,125 +1,125 @@ -/** - * @file llnamebox.cpp - * @brief A text display widget - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llnamebox.h" - -#include "llerror.h" -#include "llfontgl.h" -#include "llui.h" -#include "llviewercontrol.h" -#include "lluuid.h" - -#include "llcachename.h" -#include "llavatarnamecache.h" - -// statics -std::set LLNameBox::sInstances; - -static LLDefaultChildRegistry::Register r("name_box"); - - -LLNameBox::LLNameBox(const Params& p) -: LLTextBox(p) -{ - mNameID = LLUUID::null; - mLink = p.link; - mParseHTML = mLink; // STORM-215 - mInitialValue = p.initial_value().asString(); - LLNameBox::sInstances.insert(this); - setText(LLStringUtil::null); -} - -LLNameBox::~LLNameBox() -{ - LLNameBox::sInstances.erase(this); -} - -void LLNameBox::setNameID(const LLUUID& name_id, bool is_group) -{ - mNameID = name_id; - - std::string name; - bool got_name = false; - - if (!is_group) - { - LLAvatarName av_name; - got_name = LLAvatarNameCache::get(name_id, &av_name); - name = av_name.getUserName(); - } - else - { - got_name = gCacheName->getGroupName(name_id, name); - } - - // Got the name already? Set it. - // Otherwise it will be set later in refresh(). - if (got_name) - setName(name, is_group); - else - setText(mInitialValue); -} - -void LLNameBox::refresh(const LLUUID& id, const std::string& full_name, bool is_group) -{ - if (id == mNameID) - { - setName(full_name, is_group); - } -} - -void LLNameBox::refreshAll(const LLUUID& id, const std::string& full_name, bool is_group) -{ - std::set::iterator it; - for (it = LLNameBox::sInstances.begin(); - it != LLNameBox::sInstances.end(); - ++it) - { - LLNameBox* box = *it; - box->refresh(id, full_name, is_group); - } -} - -void LLNameBox::setName(const std::string& name, bool is_group) -{ - if (mLink) - { - std::string url; - - if (is_group) - url = "[secondlife:///app/group/" + mNameID.asString() + "/about " + name + "]"; - else - url = "[secondlife:///app/agent/" + mNameID.asString() + "/about " + name + "]"; - - setText(url); - } - else - { - setText(name); - } -} +/** + * @file llnamebox.cpp + * @brief A text display widget + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llnamebox.h" + +#include "llerror.h" +#include "llfontgl.h" +#include "llui.h" +#include "llviewercontrol.h" +#include "lluuid.h" + +#include "llcachename.h" +#include "llavatarnamecache.h" + +// statics +std::set LLNameBox::sInstances; + +static LLDefaultChildRegistry::Register r("name_box"); + + +LLNameBox::LLNameBox(const Params& p) +: LLTextBox(p) +{ + mNameID = LLUUID::null; + mLink = p.link; + mParseHTML = mLink; // STORM-215 + mInitialValue = p.initial_value().asString(); + LLNameBox::sInstances.insert(this); + setText(LLStringUtil::null); +} + +LLNameBox::~LLNameBox() +{ + LLNameBox::sInstances.erase(this); +} + +void LLNameBox::setNameID(const LLUUID& name_id, bool is_group) +{ + mNameID = name_id; + + std::string name; + bool got_name = false; + + if (!is_group) + { + LLAvatarName av_name; + got_name = LLAvatarNameCache::get(name_id, &av_name); + name = av_name.getUserName(); + } + else + { + got_name = gCacheName->getGroupName(name_id, name); + } + + // Got the name already? Set it. + // Otherwise it will be set later in refresh(). + if (got_name) + setName(name, is_group); + else + setText(mInitialValue); +} + +void LLNameBox::refresh(const LLUUID& id, const std::string& full_name, bool is_group) +{ + if (id == mNameID) + { + setName(full_name, is_group); + } +} + +void LLNameBox::refreshAll(const LLUUID& id, const std::string& full_name, bool is_group) +{ + std::set::iterator it; + for (it = LLNameBox::sInstances.begin(); + it != LLNameBox::sInstances.end(); + ++it) + { + LLNameBox* box = *it; + box->refresh(id, full_name, is_group); + } +} + +void LLNameBox::setName(const std::string& name, bool is_group) +{ + if (mLink) + { + std::string url; + + if (is_group) + url = "[secondlife:///app/group/" + mNameID.asString() + "/about " + name + "]"; + else + url = "[secondlife:///app/agent/" + mNameID.asString() + "/about " + name + "]"; + + setText(url); + } + else + { + setText(name); + } +} diff --git a/indra/newview/llnamebox.h b/indra/newview/llnamebox.h index 9407c449f3..336ef8551a 100644 --- a/indra/newview/llnamebox.h +++ b/indra/newview/llnamebox.h @@ -1,76 +1,76 @@ -/** - * @file llnamebox.h - * @brief display and refresh a name from the name cache - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLNAMEBOX_H -#define LL_LLNAMEBOX_H - -#include - -#include "llview.h" -#include "llstring.h" -#include "llfontgl.h" -#include "lltextbox.h" - -class LLNameBox -: public LLTextBox -{ -public: - struct Params : public LLInitParam::Block - { - Optional is_group; - Optional link; - - Params() - : is_group("is_group", false) - , link("link", false) - {} - }; - - virtual ~LLNameBox(); - - void setNameID(const LLUUID& name_id, bool is_group); - - void refresh(const LLUUID& id, const std::string& full_name, bool is_group); - - static void refreshAll(const LLUUID& id, const std::string& full_name, bool is_group); - -protected: - LLNameBox (const Params&); - - friend class LLUICtrlFactory; -private: - void setName(const std::string& name, bool is_group); - - static std::set sInstances; - -private: - LLUUID mNameID; - bool mLink; - std::string mInitialValue; - -}; - -#endif +/** + * @file llnamebox.h + * @brief display and refresh a name from the name cache + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLNAMEBOX_H +#define LL_LLNAMEBOX_H + +#include + +#include "llview.h" +#include "llstring.h" +#include "llfontgl.h" +#include "lltextbox.h" + +class LLNameBox +: public LLTextBox +{ +public: + struct Params : public LLInitParam::Block + { + Optional is_group; + Optional link; + + Params() + : is_group("is_group", false) + , link("link", false) + {} + }; + + virtual ~LLNameBox(); + + void setNameID(const LLUUID& name_id, bool is_group); + + void refresh(const LLUUID& id, const std::string& full_name, bool is_group); + + static void refreshAll(const LLUUID& id, const std::string& full_name, bool is_group); + +protected: + LLNameBox (const Params&); + + friend class LLUICtrlFactory; +private: + void setName(const std::string& name, bool is_group); + + static std::set sInstances; + +private: + LLUUID mNameID; + bool mLink; + std::string mInitialValue; + +}; + +#endif diff --git a/indra/newview/llnameeditor.cpp b/indra/newview/llnameeditor.cpp index 18a022668b..4b5e3bd53e 100644 --- a/indra/newview/llnameeditor.cpp +++ b/indra/newview/llnameeditor.cpp @@ -1,110 +1,110 @@ -/** - * @file llnameeditor.cpp - * @brief Name Editor to refresh a name. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llnameeditor.h" -#include "llcachename.h" -#include "llavatarnamecache.h" - -#include "llfontgl.h" - -#include "lluuid.h" -#include "llrect.h" -#include "llstring.h" -#include "llui.h" - -static LLDefaultChildRegistry::Register r("name_editor"); - -// statics -std::set LLNameEditor::sInstances; - -LLNameEditor::LLNameEditor(const LLNameEditor::Params& p) -: LLLineEditor(p) -{ - LLNameEditor::sInstances.insert(this); - - if(!p.name_id().isNull()) - { - setNameID(p.name_id, p.is_group); - } -} - -LLNameEditor::~LLNameEditor() -{ - LLNameEditor::sInstances.erase(this); -} - -void LLNameEditor::setNameID(const LLUUID& name_id, bool is_group) -{ - mNameID = name_id; - - std::string name; - - if (!is_group) - { - LLAvatarName av_name; - LLAvatarNameCache::get(name_id, &av_name); - name = av_name.getUserName(); - } - else - { - gCacheName->getGroupName(name_id, name); - } - - setText(name); -} - -void LLNameEditor::refresh(const LLUUID& id, const std::string& full_name, bool is_group) -{ - if (id == mNameID) - { - setText(full_name); - } -} - -void LLNameEditor::refreshAll(const LLUUID& id, const std::string& full_name, bool is_group) -{ - std::set::iterator it; - for (it = LLNameEditor::sInstances.begin(); - it != LLNameEditor::sInstances.end(); - ++it) - { - LLNameEditor* box = *it; - box->refresh(id, full_name, is_group); - } -} - -void LLNameEditor::setValue( const LLSD& value ) -{ - setNameID(value.asUUID(), false); -} - -LLSD LLNameEditor::getValue() const -{ - return LLSD(mNameID); -} - +/** + * @file llnameeditor.cpp + * @brief Name Editor to refresh a name. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llnameeditor.h" +#include "llcachename.h" +#include "llavatarnamecache.h" + +#include "llfontgl.h" + +#include "lluuid.h" +#include "llrect.h" +#include "llstring.h" +#include "llui.h" + +static LLDefaultChildRegistry::Register r("name_editor"); + +// statics +std::set LLNameEditor::sInstances; + +LLNameEditor::LLNameEditor(const LLNameEditor::Params& p) +: LLLineEditor(p) +{ + LLNameEditor::sInstances.insert(this); + + if(!p.name_id().isNull()) + { + setNameID(p.name_id, p.is_group); + } +} + +LLNameEditor::~LLNameEditor() +{ + LLNameEditor::sInstances.erase(this); +} + +void LLNameEditor::setNameID(const LLUUID& name_id, bool is_group) +{ + mNameID = name_id; + + std::string name; + + if (!is_group) + { + LLAvatarName av_name; + LLAvatarNameCache::get(name_id, &av_name); + name = av_name.getUserName(); + } + else + { + gCacheName->getGroupName(name_id, name); + } + + setText(name); +} + +void LLNameEditor::refresh(const LLUUID& id, const std::string& full_name, bool is_group) +{ + if (id == mNameID) + { + setText(full_name); + } +} + +void LLNameEditor::refreshAll(const LLUUID& id, const std::string& full_name, bool is_group) +{ + std::set::iterator it; + for (it = LLNameEditor::sInstances.begin(); + it != LLNameEditor::sInstances.end(); + ++it) + { + LLNameEditor* box = *it; + box->refresh(id, full_name, is_group); + } +} + +void LLNameEditor::setValue( const LLSD& value ) +{ + setNameID(value.asUUID(), false); +} + +LLSD LLNameEditor::getValue() const +{ + return LLSD(mNameID); +} + diff --git a/indra/newview/llnameeditor.h b/indra/newview/llnameeditor.h index abaa67ee58..efa2da9fd8 100644 --- a/indra/newview/llnameeditor.h +++ b/indra/newview/llnameeditor.h @@ -1,79 +1,79 @@ -/** - * @file llnameeditor.h - * @brief display and refresh a name from the name cache - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLNAMEEDITOR_H -#define LL_LLNAMEEDITOR_H - -#include - -#include "llview.h" -#include "v4color.h" -#include "llstring.h" -#include "llfontgl.h" -#include "lllineeditor.h" - - -class LLNameEditor -: public LLLineEditor -{ -public: - struct Params : public LLInitParam::Block - { - Optional is_group; - Optional name_id; - - Params() - : is_group("is_group"), - name_id("name_id") - {} - }; - -protected: - LLNameEditor(const Params&); - friend class LLUICtrlFactory; -public: - virtual ~LLNameEditor(); - - void setNameID(const LLUUID& name_id, bool is_group); - - void refresh(const LLUUID& id, const std::string& full_name, bool is_group); - - static void refreshAll(const LLUUID& id, const std::string& full_name, bool is_group); - - - // Take/return agent UUIDs - virtual void setValue( const LLSD& value ); - virtual LLSD getValue() const; - -private: - static std::set sInstances; - -private: - LLUUID mNameID; - -}; - -#endif +/** + * @file llnameeditor.h + * @brief display and refresh a name from the name cache + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLNAMEEDITOR_H +#define LL_LLNAMEEDITOR_H + +#include + +#include "llview.h" +#include "v4color.h" +#include "llstring.h" +#include "llfontgl.h" +#include "lllineeditor.h" + + +class LLNameEditor +: public LLLineEditor +{ +public: + struct Params : public LLInitParam::Block + { + Optional is_group; + Optional name_id; + + Params() + : is_group("is_group"), + name_id("name_id") + {} + }; + +protected: + LLNameEditor(const Params&); + friend class LLUICtrlFactory; +public: + virtual ~LLNameEditor(); + + void setNameID(const LLUUID& name_id, bool is_group); + + void refresh(const LLUUID& id, const std::string& full_name, bool is_group); + + static void refreshAll(const LLUUID& id, const std::string& full_name, bool is_group); + + + // Take/return agent UUIDs + virtual void setValue( const LLSD& value ); + virtual LLSD getValue() const; + +private: + static std::set sInstances; + +private: + LLUUID mNameID; + +}; + +#endif diff --git a/indra/newview/llnamelistctrl.cpp b/indra/newview/llnamelistctrl.cpp index 78114d5842..d7ffcb6e25 100644 --- a/indra/newview/llnamelistctrl.cpp +++ b/indra/newview/llnamelistctrl.cpp @@ -1,622 +1,622 @@ -/** - * @file llnamelistctrl.cpp - * @brief A list of names, automatically refreshed from name cache. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llnamelistctrl.h" - -#include - -#include "llavatarnamecache.h" -#include "llcachename.h" -#include "llfloater.h" -#include "llfloaterreg.h" -#include "llfloatersnapshot.h" // gSnapshotFloaterView -#include "llinventory.h" -#include "llscrolllistitem.h" -#include "llscrolllistcell.h" -#include "llscrolllistcolumn.h" -#include "llsdparam.h" -#include "lltooltip.h" -#include "lltrans.h" - -static LLDefaultChildRegistry::Register r("name_list"); - -static constexpr S32 info_icon_size = 16; - -void LLNameListCtrl::NameTypeNames::declareValues() -{ - declare("INDIVIDUAL", LLNameListCtrl::INDIVIDUAL); - declare("GROUP", LLNameListCtrl::GROUP); - declare("SPECIAL", LLNameListCtrl::SPECIAL); -} - -LLNameListCtrl::Params::Params() -: name_column(""), - allow_calling_card_drop("allow_calling_card_drop", false), - short_names("short_names", false) -{ -} - -LLNameListCtrl::LLNameListCtrl(const LLNameListCtrl::Params& p) -: LLScrollListCtrl(p), - mNameColumnIndex(p.name_column.column_index), - mNameColumn(p.name_column.column_name), - mAllowCallingCardDrop(p.allow_calling_card_drop), - mShortNames(p.short_names), - mPendingLookupsRemaining(0), - mHoverIconName("Info_Small"), - mNameListType(INDIVIDUAL) -{} - -// public -LLScrollListItem* LLNameListCtrl::addNameItem(const LLUUID& agent_id, EAddPosition pos, - bool enabled, const std::string& suffix, const std::string& prefix) -{ - //LL_INFOS() << "LLNameListCtrl::addNameItem " << agent_id << LL_ENDL; - - NameItem item; - item.value = agent_id; - item.enabled = enabled; - item.target = INDIVIDUAL; - - return addNameItemRow(item, pos, suffix, prefix); -} - -// virtual, public -bool LLNameListCtrl::handleDragAndDrop( - S32 x, S32 y, MASK mask, - bool drop, - EDragAndDropType cargo_type, void *cargo_data, - EAcceptance *accept, - std::string& tooltip_msg) -{ - if (!mAllowCallingCardDrop) - { - return false; - } - - bool handled = false; - - if (cargo_type == DAD_CALLINGCARD) - { - if (drop) - { - LLInventoryItem* item = (LLInventoryItem *)cargo_data; - addNameItem(item->getCreatorUUID()); - } - - *accept = ACCEPT_YES_MULTI; - } - else - { - *accept = ACCEPT_NO; - if (tooltip_msg.empty()) - { - if (!getToolTip().empty()) - { - tooltip_msg = getToolTip(); - } - else - { - // backwards compatable English tooltip (should be overridden in xml) - tooltip_msg.assign("Drag a calling card here\nto add a resident."); - } - } - } - - handled = true; - LL_DEBUGS("UserInput") << "dragAndDrop handled by LLNameListCtrl " << getName() << LL_ENDL; - - return handled; -} - -void LLNameListCtrl::showInspector(const LLUUID& avatar_id, bool is_group, bool is_experience) -{ - if (isSpecialType()) - { - mIconClickedSignal(avatar_id); - return; - } - if(is_experience) - { - LLFloaterReg::showInstance("experience_profile", avatar_id, true); - return; - } - - if (is_group) - LLFloaterReg::showInstance("inspect_group", LLSD().with("group_id", avatar_id)); - else - LLFloaterReg::showInstance("inspect_avatar", LLSD().with("avatar_id", avatar_id)); -} - -void LLNameListCtrl::mouseOverHighlightNthItem( S32 target_index ) -{ - S32 cur_index = getHighlightedItemInx(); - if (cur_index != target_index) - { - bool is_mouse_over_name_cell = false; - - S32 mouse_x, mouse_y; - LLUI::getInstance()->getMousePositionLocal(this, &mouse_x, &mouse_y); - - S32 column_index = getColumnIndexFromOffset(mouse_x); - LLScrollListItem* hit_item = hitItem(mouse_x, mouse_y); - if (hit_item && column_index == mNameColumnIndex) - { - // Get the name cell which is currently under the mouse pointer. - LLScrollListCell* hit_cell = hit_item->getColumn(column_index); - if (hit_cell) - { - is_mouse_over_name_cell = getCellRect(cur_index, column_index).pointInRect(mouse_x, mouse_y); - } - } - - // If the tool tip is visible and the mouse is over the currently highlighted item's name cell, - // we should not reset the highlighted item index i.e. set mHighlightedItem = -1 - // and should not increase the width of the text inside the cell because it may - // overlap the tool tip icon. - if (LLToolTipMgr::getInstance()->toolTipVisible() && is_mouse_over_name_cell) - return; - - if(0 <= cur_index && cur_index < (S32)getItemList().size()) - { - LLScrollListItem* item = getItemList()[cur_index]; - if (item) - { - LLScrollListText* cell = dynamic_cast(item->getColumn(mNameColumnIndex)); - if (cell) - cell->setTextWidth(cell->getTextWidth() + info_icon_size); - } - else - { - LL_WARNS() << "highlighted name list item is NULL" << LL_ENDL; - } - } - if(target_index != -1) - { - LLScrollListItem* item = getItemList()[target_index]; - LLScrollListText* cell = dynamic_cast(item->getColumn(mNameColumnIndex)); - if (item) - { - if (cell) - cell->setTextWidth(cell->getTextWidth() - info_icon_size); - } - else - { - LL_WARNS() << "target name item is NULL" << LL_ENDL; - } - } - } - - LLScrollListCtrl::mouseOverHighlightNthItem(target_index); -} - -//virtual -bool LLNameListCtrl::handleToolTip(S32 x, S32 y, MASK mask) -{ - bool handled = false; - S32 column_index = getColumnIndexFromOffset(x); - LLNameListItem* hit_item = dynamic_cast(hitItem(x, y)); - LLFloater* floater = gFloaterView->getParentFloater(this); - - - if (floater - && floater->isFrontmost() - && hit_item - && ((column_index == mNameColumnIndex) || isSpecialType())) - { - // ...this is the column with the avatar name - LLUUID item_id = isSpecialType() ? hit_item->getSpecialID() : hit_item->getUUID(); - if (item_id.notNull()) - { - // ...valid avatar id - - LLScrollListCell* hit_cell = hit_item->getColumn(column_index); - if (hit_cell) - { - S32 row_index = getItemIndex(hit_item); - LLRect cell_rect = getCellRect(row_index, isSpecialType() ? getNumColumns() - 1 : column_index); - // Convert rect local to screen coordinates - LLRect sticky_rect; - localRectToScreen(cell_rect, &sticky_rect); - - // Spawn at right side of cell - LLPointer icon = LLUI::getUIImage(mHoverIconName); - S32 screenX = sticky_rect.mRight - info_icon_size; - S32 screenY = sticky_rect.mTop - (sticky_rect.getHeight() - icon->getHeight()) / 2; - LLCoordGL pos(screenX, screenY); - - LLFloater* snapshot_floatr = gSnapshotFloaterView->getFrontmostClosableFloater(); - if (!snapshot_floatr || !snapshot_floatr->getRect().pointInRect(screenX + icon->getWidth(), screenY)) - { - // Should we show a group or an avatar inspector? - bool is_group = hit_item->isGroup(); - bool is_experience = hit_item->isExperience(); - - LLToolTip::Params params; - params.background_visible(false); - params.click_callback(boost::bind(&LLNameListCtrl::showInspector, this, item_id, is_group, is_experience)); - params.delay_time(0.0f); // spawn instantly on hover - params.image(icon); - params.message(""); - params.padding(0); - params.pos(pos); - params.sticky_rect(sticky_rect); - - LLToolTipMgr::getInstance()->show(params); - handled = true; - } - } - } - } - if (!handled) - { - handled = LLScrollListCtrl::handleToolTip(x, y, mask); - } - return handled; -} - -// virtual -bool LLNameListCtrl::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - LLNameListItem* hit_item = dynamic_cast(hitItem(x, y)); - LLFloater* floater = gFloaterView->getParentFloater(this); - if (floater && floater->isFrontmost() && hit_item) - { - if(hit_item->isGroup()) - { - ContextMenuType prev_menu = getContextMenuType(); - setContextMenu(MENU_GROUP); - bool handled = LLScrollListCtrl::handleRightMouseDown(x, y, mask); - setContextMenu(prev_menu); - return handled; - } - } - return LLScrollListCtrl::handleRightMouseDown(x, y, mask); -} - -// public -void LLNameListCtrl::addGroupNameItem(const LLUUID& group_id, EAddPosition pos, - bool enabled) -{ - NameItem item; - item.value = group_id; - item.enabled = enabled; - item.target = GROUP; - - addNameItemRow(item, pos); -} - -// public -void LLNameListCtrl::addGroupNameItem(LLNameListCtrl::NameItem& item, EAddPosition pos) -{ - item.target = GROUP; - addNameItemRow(item, pos); -} - -LLScrollListItem* LLNameListCtrl::addNameItem(LLNameListCtrl::NameItem& item, EAddPosition pos) -{ - item.target = INDIVIDUAL; - return addNameItemRow(item, pos); -} - -LLScrollListItem* LLNameListCtrl::addElement(const LLSD& element, EAddPosition pos, void* userdata) -{ - LLNameListCtrl::NameItem item_params; - LLParamSDParser parser; - parser.readSD(element, item_params); - item_params.userdata = userdata; - return addNameItemRow(item_params, pos); -} - - -LLScrollListItem* LLNameListCtrl::addNameItemRow( - const LLNameListCtrl::NameItem& name_item, - EAddPosition pos, - const std::string& suffix, - const std::string& prefix) -{ - LLUUID id = name_item.value().asUUID(); - LLNameListItem* item = new LLNameListItem(name_item,name_item.target() == GROUP, name_item.target() == EXPERIENCE); - - if (!item) return NULL; - - LLScrollListCtrl::addRow(item, name_item, pos); - - // use supplied name by default - std::string fullname = name_item.name; - - switch(name_item.target) - { - case GROUP: - if (!gCacheName->getGroupName(id, fullname)) - { - avatar_name_cache_connection_map_t::iterator it = mGroupNameCacheConnections.find(id); - if (it != mGroupNameCacheConnections.end()) - { - if (it->second.connected()) - { - it->second.disconnect(); - } - mGroupNameCacheConnections.erase(it); - } - mGroupNameCacheConnections[id] = gCacheName->getGroup(id, boost::bind(&LLNameListCtrl::onGroupNameCache, this, _1, _2, item->getHandle())); - } - break; - case SPECIAL: - { - item->setSpecialID(name_item.special_id()); - return item; - } - case INDIVIDUAL: - { - LLAvatarName av_name; - if (id.isNull()) - { - fullname = LLTrans::getString("AvatarNameNobody"); - } - else if (LLAvatarNameCache::get(id, &av_name)) - { - if (mShortNames) - fullname = av_name.getDisplayName(true); - else - fullname = av_name.getCompleteName(); - } - else - { - // ...schedule a callback - avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.find(id); - if (it != mAvatarNameCacheConnections.end()) - { - if (it->second.connected()) - { - it->second.disconnect(); - } - mAvatarNameCacheConnections.erase(it); - } - mAvatarNameCacheConnections[id] = LLAvatarNameCache::get(id,boost::bind(&LLNameListCtrl::onAvatarNameCache,this, _1, _2, suffix, prefix, item->getHandle())); - - if(mPendingLookupsRemaining <= 0) - { - // BAKER TODO: - // We might get into a state where mPendingLookupsRemaining might - // go negative. So just reset it right now and figure out if it's - // possible later :) - mPendingLookupsRemaining = 0; - mNameListCompleteSignal(false); - } - mPendingLookupsRemaining++; - } - break; - } - case EXPERIENCE: - // just use supplied name - default: - break; - } - - // Append optional suffix. - if (!suffix.empty()) - { - fullname.append(suffix); - } - - LLScrollListCell* cell = item->getColumn(mNameColumnIndex); - if (cell) - { - cell->setValue(prefix + fullname); - cell->setAltValue(name_item.alt_value()); - } - - dirtyColumns(); - - // this column is resizable - LLScrollListColumn* columnp = getColumn(mNameColumnIndex); - if (columnp && columnp->mHeader) - { - columnp->mHeader->setHasResizableElement(true); - } - - return item; -} - -// public -void LLNameListCtrl::removeNameItem(const LLUUID& agent_id) -{ - // Find the item specified with agent_id. - S32 idx = -1; - for (item_list::iterator it = getItemList().begin(); it != getItemList().end(); it++) - { - LLScrollListItem* item = *it; - LLUUID cur_id = isSpecialType() ? dynamic_cast(item)->getSpecialID() : item->getUUID(); - if (cur_id == agent_id) - { - idx = getItemIndex(item); - break; - } - } - - // Remove it. - if (idx >= 0) - { - selectNthItem(idx); // not sure whether this is needed, taken from previous implementation - deleteSingleItem(idx); - - mPendingLookupsRemaining--; - } -} - -// public -LLScrollListItem* LLNameListCtrl::getNameItemByAgentId(const LLUUID& agent_id) -{ - for (item_list::iterator it = getItemList().begin(); it != getItemList().end(); it++) - { - LLScrollListItem* item = *it; - if (item && item->getUUID() == agent_id) - { - return item; - } - } - return NULL; -} - -void LLNameListCtrl::selectItemBySpecialId(const LLUUID& special_id) -{ - if (special_id.isNull()) - { - return; - } - - for (item_list::iterator it = getItemList().begin(); it != getItemList().end(); it++) - { - LLNameListItem* item = dynamic_cast(*it); - if (item && item->getSpecialID() == special_id) - { - item->setSelected(true); - break; - } - } -} - -LLUUID LLNameListCtrl::getSelectedSpecialId() -{ - LLNameListItem* item = dynamic_cast(getFirstSelected()); - if(item) - { - return item->getSpecialID(); - } - return LLUUID(); -} - -void LLNameListCtrl::onAvatarNameCache(const LLUUID& agent_id, - const LLAvatarName& av_name, - std::string suffix, - std::string prefix, - LLHandle item) -{ - avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.find(agent_id); - if (it != mAvatarNameCacheConnections.end()) - { - if (it->second.connected()) - { - it->second.disconnect(); - } - mAvatarNameCacheConnections.erase(it); - } - - std::string name; - if (mShortNames) - name = av_name.getDisplayName(); - else - name = av_name.getCompleteName(); - - // Append optional suffix. - if (!suffix.empty()) - { - name.append(suffix); - } - - if (!prefix.empty()) - { - name.insert(0, prefix); - } - - LLNameListItem* list_item = item.get(); - if (list_item && list_item->getUUID() == agent_id) - { - LLScrollListCell* cell = list_item->getColumn(mNameColumnIndex); - if (cell) - { - cell->setValue(name); - setNeedsSort(); - } - } - - ////////////////////////////////////////////////////////////////////////// - // BAKER - FIX NameListCtrl - //if (mPendingLookupsRemaining <= 0) - { - // We might get into a state where mPendingLookupsRemaining might - // go negative. So just reset it right now and figure out if it's - // possible later :) - //mPendingLookupsRemaining = 0; - - mNameListCompleteSignal(true); - } - //else - { - // mPendingLookupsRemaining--; - } - ////////////////////////////////////////////////////////////////////////// - - dirtyColumns(); -} - -void LLNameListCtrl::onGroupNameCache(const LLUUID& group_id, const std::string name, LLHandle item) -{ - avatar_name_cache_connection_map_t::iterator it = mGroupNameCacheConnections.find(group_id); - if (it != mGroupNameCacheConnections.end()) - { - if (it->second.connected()) - { - it->second.disconnect(); - } - mGroupNameCacheConnections.erase(it); - } - - LLNameListItem* list_item = item.get(); - if (list_item && list_item->getUUID() == group_id) - { - LLScrollListCell* cell = list_item->getColumn(mNameColumnIndex); - if (cell) - { - cell->setValue(name); - setNeedsSort(); - } - } - - dirtyColumns(); -} - -void LLNameListCtrl::updateColumns(bool force_update) -{ - LLScrollListCtrl::updateColumns(force_update); - - if (!mNameColumn.empty()) - { - LLScrollListColumn* name_column = getColumn(mNameColumn); - if (name_column) - { - mNameColumnIndex = name_column->mIndex; - } - } -} - -void LLNameListCtrl::sortByName(bool ascending) -{ - sortByColumnIndex(mNameColumnIndex,ascending); -} +/** + * @file llnamelistctrl.cpp + * @brief A list of names, automatically refreshed from name cache. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llnamelistctrl.h" + +#include + +#include "llavatarnamecache.h" +#include "llcachename.h" +#include "llfloater.h" +#include "llfloaterreg.h" +#include "llfloatersnapshot.h" // gSnapshotFloaterView +#include "llinventory.h" +#include "llscrolllistitem.h" +#include "llscrolllistcell.h" +#include "llscrolllistcolumn.h" +#include "llsdparam.h" +#include "lltooltip.h" +#include "lltrans.h" + +static LLDefaultChildRegistry::Register r("name_list"); + +static constexpr S32 info_icon_size = 16; + +void LLNameListCtrl::NameTypeNames::declareValues() +{ + declare("INDIVIDUAL", LLNameListCtrl::INDIVIDUAL); + declare("GROUP", LLNameListCtrl::GROUP); + declare("SPECIAL", LLNameListCtrl::SPECIAL); +} + +LLNameListCtrl::Params::Params() +: name_column(""), + allow_calling_card_drop("allow_calling_card_drop", false), + short_names("short_names", false) +{ +} + +LLNameListCtrl::LLNameListCtrl(const LLNameListCtrl::Params& p) +: LLScrollListCtrl(p), + mNameColumnIndex(p.name_column.column_index), + mNameColumn(p.name_column.column_name), + mAllowCallingCardDrop(p.allow_calling_card_drop), + mShortNames(p.short_names), + mPendingLookupsRemaining(0), + mHoverIconName("Info_Small"), + mNameListType(INDIVIDUAL) +{} + +// public +LLScrollListItem* LLNameListCtrl::addNameItem(const LLUUID& agent_id, EAddPosition pos, + bool enabled, const std::string& suffix, const std::string& prefix) +{ + //LL_INFOS() << "LLNameListCtrl::addNameItem " << agent_id << LL_ENDL; + + NameItem item; + item.value = agent_id; + item.enabled = enabled; + item.target = INDIVIDUAL; + + return addNameItemRow(item, pos, suffix, prefix); +} + +// virtual, public +bool LLNameListCtrl::handleDragAndDrop( + S32 x, S32 y, MASK mask, + bool drop, + EDragAndDropType cargo_type, void *cargo_data, + EAcceptance *accept, + std::string& tooltip_msg) +{ + if (!mAllowCallingCardDrop) + { + return false; + } + + bool handled = false; + + if (cargo_type == DAD_CALLINGCARD) + { + if (drop) + { + LLInventoryItem* item = (LLInventoryItem *)cargo_data; + addNameItem(item->getCreatorUUID()); + } + + *accept = ACCEPT_YES_MULTI; + } + else + { + *accept = ACCEPT_NO; + if (tooltip_msg.empty()) + { + if (!getToolTip().empty()) + { + tooltip_msg = getToolTip(); + } + else + { + // backwards compatable English tooltip (should be overridden in xml) + tooltip_msg.assign("Drag a calling card here\nto add a resident."); + } + } + } + + handled = true; + LL_DEBUGS("UserInput") << "dragAndDrop handled by LLNameListCtrl " << getName() << LL_ENDL; + + return handled; +} + +void LLNameListCtrl::showInspector(const LLUUID& avatar_id, bool is_group, bool is_experience) +{ + if (isSpecialType()) + { + mIconClickedSignal(avatar_id); + return; + } + if(is_experience) + { + LLFloaterReg::showInstance("experience_profile", avatar_id, true); + return; + } + + if (is_group) + LLFloaterReg::showInstance("inspect_group", LLSD().with("group_id", avatar_id)); + else + LLFloaterReg::showInstance("inspect_avatar", LLSD().with("avatar_id", avatar_id)); +} + +void LLNameListCtrl::mouseOverHighlightNthItem( S32 target_index ) +{ + S32 cur_index = getHighlightedItemInx(); + if (cur_index != target_index) + { + bool is_mouse_over_name_cell = false; + + S32 mouse_x, mouse_y; + LLUI::getInstance()->getMousePositionLocal(this, &mouse_x, &mouse_y); + + S32 column_index = getColumnIndexFromOffset(mouse_x); + LLScrollListItem* hit_item = hitItem(mouse_x, mouse_y); + if (hit_item && column_index == mNameColumnIndex) + { + // Get the name cell which is currently under the mouse pointer. + LLScrollListCell* hit_cell = hit_item->getColumn(column_index); + if (hit_cell) + { + is_mouse_over_name_cell = getCellRect(cur_index, column_index).pointInRect(mouse_x, mouse_y); + } + } + + // If the tool tip is visible and the mouse is over the currently highlighted item's name cell, + // we should not reset the highlighted item index i.e. set mHighlightedItem = -1 + // and should not increase the width of the text inside the cell because it may + // overlap the tool tip icon. + if (LLToolTipMgr::getInstance()->toolTipVisible() && is_mouse_over_name_cell) + return; + + if(0 <= cur_index && cur_index < (S32)getItemList().size()) + { + LLScrollListItem* item = getItemList()[cur_index]; + if (item) + { + LLScrollListText* cell = dynamic_cast(item->getColumn(mNameColumnIndex)); + if (cell) + cell->setTextWidth(cell->getTextWidth() + info_icon_size); + } + else + { + LL_WARNS() << "highlighted name list item is NULL" << LL_ENDL; + } + } + if(target_index != -1) + { + LLScrollListItem* item = getItemList()[target_index]; + LLScrollListText* cell = dynamic_cast(item->getColumn(mNameColumnIndex)); + if (item) + { + if (cell) + cell->setTextWidth(cell->getTextWidth() - info_icon_size); + } + else + { + LL_WARNS() << "target name item is NULL" << LL_ENDL; + } + } + } + + LLScrollListCtrl::mouseOverHighlightNthItem(target_index); +} + +//virtual +bool LLNameListCtrl::handleToolTip(S32 x, S32 y, MASK mask) +{ + bool handled = false; + S32 column_index = getColumnIndexFromOffset(x); + LLNameListItem* hit_item = dynamic_cast(hitItem(x, y)); + LLFloater* floater = gFloaterView->getParentFloater(this); + + + if (floater + && floater->isFrontmost() + && hit_item + && ((column_index == mNameColumnIndex) || isSpecialType())) + { + // ...this is the column with the avatar name + LLUUID item_id = isSpecialType() ? hit_item->getSpecialID() : hit_item->getUUID(); + if (item_id.notNull()) + { + // ...valid avatar id + + LLScrollListCell* hit_cell = hit_item->getColumn(column_index); + if (hit_cell) + { + S32 row_index = getItemIndex(hit_item); + LLRect cell_rect = getCellRect(row_index, isSpecialType() ? getNumColumns() - 1 : column_index); + // Convert rect local to screen coordinates + LLRect sticky_rect; + localRectToScreen(cell_rect, &sticky_rect); + + // Spawn at right side of cell + LLPointer icon = LLUI::getUIImage(mHoverIconName); + S32 screenX = sticky_rect.mRight - info_icon_size; + S32 screenY = sticky_rect.mTop - (sticky_rect.getHeight() - icon->getHeight()) / 2; + LLCoordGL pos(screenX, screenY); + + LLFloater* snapshot_floatr = gSnapshotFloaterView->getFrontmostClosableFloater(); + if (!snapshot_floatr || !snapshot_floatr->getRect().pointInRect(screenX + icon->getWidth(), screenY)) + { + // Should we show a group or an avatar inspector? + bool is_group = hit_item->isGroup(); + bool is_experience = hit_item->isExperience(); + + LLToolTip::Params params; + params.background_visible(false); + params.click_callback(boost::bind(&LLNameListCtrl::showInspector, this, item_id, is_group, is_experience)); + params.delay_time(0.0f); // spawn instantly on hover + params.image(icon); + params.message(""); + params.padding(0); + params.pos(pos); + params.sticky_rect(sticky_rect); + + LLToolTipMgr::getInstance()->show(params); + handled = true; + } + } + } + } + if (!handled) + { + handled = LLScrollListCtrl::handleToolTip(x, y, mask); + } + return handled; +} + +// virtual +bool LLNameListCtrl::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + LLNameListItem* hit_item = dynamic_cast(hitItem(x, y)); + LLFloater* floater = gFloaterView->getParentFloater(this); + if (floater && floater->isFrontmost() && hit_item) + { + if(hit_item->isGroup()) + { + ContextMenuType prev_menu = getContextMenuType(); + setContextMenu(MENU_GROUP); + bool handled = LLScrollListCtrl::handleRightMouseDown(x, y, mask); + setContextMenu(prev_menu); + return handled; + } + } + return LLScrollListCtrl::handleRightMouseDown(x, y, mask); +} + +// public +void LLNameListCtrl::addGroupNameItem(const LLUUID& group_id, EAddPosition pos, + bool enabled) +{ + NameItem item; + item.value = group_id; + item.enabled = enabled; + item.target = GROUP; + + addNameItemRow(item, pos); +} + +// public +void LLNameListCtrl::addGroupNameItem(LLNameListCtrl::NameItem& item, EAddPosition pos) +{ + item.target = GROUP; + addNameItemRow(item, pos); +} + +LLScrollListItem* LLNameListCtrl::addNameItem(LLNameListCtrl::NameItem& item, EAddPosition pos) +{ + item.target = INDIVIDUAL; + return addNameItemRow(item, pos); +} + +LLScrollListItem* LLNameListCtrl::addElement(const LLSD& element, EAddPosition pos, void* userdata) +{ + LLNameListCtrl::NameItem item_params; + LLParamSDParser parser; + parser.readSD(element, item_params); + item_params.userdata = userdata; + return addNameItemRow(item_params, pos); +} + + +LLScrollListItem* LLNameListCtrl::addNameItemRow( + const LLNameListCtrl::NameItem& name_item, + EAddPosition pos, + const std::string& suffix, + const std::string& prefix) +{ + LLUUID id = name_item.value().asUUID(); + LLNameListItem* item = new LLNameListItem(name_item,name_item.target() == GROUP, name_item.target() == EXPERIENCE); + + if (!item) return NULL; + + LLScrollListCtrl::addRow(item, name_item, pos); + + // use supplied name by default + std::string fullname = name_item.name; + + switch(name_item.target) + { + case GROUP: + if (!gCacheName->getGroupName(id, fullname)) + { + avatar_name_cache_connection_map_t::iterator it = mGroupNameCacheConnections.find(id); + if (it != mGroupNameCacheConnections.end()) + { + if (it->second.connected()) + { + it->second.disconnect(); + } + mGroupNameCacheConnections.erase(it); + } + mGroupNameCacheConnections[id] = gCacheName->getGroup(id, boost::bind(&LLNameListCtrl::onGroupNameCache, this, _1, _2, item->getHandle())); + } + break; + case SPECIAL: + { + item->setSpecialID(name_item.special_id()); + return item; + } + case INDIVIDUAL: + { + LLAvatarName av_name; + if (id.isNull()) + { + fullname = LLTrans::getString("AvatarNameNobody"); + } + else if (LLAvatarNameCache::get(id, &av_name)) + { + if (mShortNames) + fullname = av_name.getDisplayName(true); + else + fullname = av_name.getCompleteName(); + } + else + { + // ...schedule a callback + avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.find(id); + if (it != mAvatarNameCacheConnections.end()) + { + if (it->second.connected()) + { + it->second.disconnect(); + } + mAvatarNameCacheConnections.erase(it); + } + mAvatarNameCacheConnections[id] = LLAvatarNameCache::get(id,boost::bind(&LLNameListCtrl::onAvatarNameCache,this, _1, _2, suffix, prefix, item->getHandle())); + + if(mPendingLookupsRemaining <= 0) + { + // BAKER TODO: + // We might get into a state where mPendingLookupsRemaining might + // go negative. So just reset it right now and figure out if it's + // possible later :) + mPendingLookupsRemaining = 0; + mNameListCompleteSignal(false); + } + mPendingLookupsRemaining++; + } + break; + } + case EXPERIENCE: + // just use supplied name + default: + break; + } + + // Append optional suffix. + if (!suffix.empty()) + { + fullname.append(suffix); + } + + LLScrollListCell* cell = item->getColumn(mNameColumnIndex); + if (cell) + { + cell->setValue(prefix + fullname); + cell->setAltValue(name_item.alt_value()); + } + + dirtyColumns(); + + // this column is resizable + LLScrollListColumn* columnp = getColumn(mNameColumnIndex); + if (columnp && columnp->mHeader) + { + columnp->mHeader->setHasResizableElement(true); + } + + return item; +} + +// public +void LLNameListCtrl::removeNameItem(const LLUUID& agent_id) +{ + // Find the item specified with agent_id. + S32 idx = -1; + for (item_list::iterator it = getItemList().begin(); it != getItemList().end(); it++) + { + LLScrollListItem* item = *it; + LLUUID cur_id = isSpecialType() ? dynamic_cast(item)->getSpecialID() : item->getUUID(); + if (cur_id == agent_id) + { + idx = getItemIndex(item); + break; + } + } + + // Remove it. + if (idx >= 0) + { + selectNthItem(idx); // not sure whether this is needed, taken from previous implementation + deleteSingleItem(idx); + + mPendingLookupsRemaining--; + } +} + +// public +LLScrollListItem* LLNameListCtrl::getNameItemByAgentId(const LLUUID& agent_id) +{ + for (item_list::iterator it = getItemList().begin(); it != getItemList().end(); it++) + { + LLScrollListItem* item = *it; + if (item && item->getUUID() == agent_id) + { + return item; + } + } + return NULL; +} + +void LLNameListCtrl::selectItemBySpecialId(const LLUUID& special_id) +{ + if (special_id.isNull()) + { + return; + } + + for (item_list::iterator it = getItemList().begin(); it != getItemList().end(); it++) + { + LLNameListItem* item = dynamic_cast(*it); + if (item && item->getSpecialID() == special_id) + { + item->setSelected(true); + break; + } + } +} + +LLUUID LLNameListCtrl::getSelectedSpecialId() +{ + LLNameListItem* item = dynamic_cast(getFirstSelected()); + if(item) + { + return item->getSpecialID(); + } + return LLUUID(); +} + +void LLNameListCtrl::onAvatarNameCache(const LLUUID& agent_id, + const LLAvatarName& av_name, + std::string suffix, + std::string prefix, + LLHandle item) +{ + avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.find(agent_id); + if (it != mAvatarNameCacheConnections.end()) + { + if (it->second.connected()) + { + it->second.disconnect(); + } + mAvatarNameCacheConnections.erase(it); + } + + std::string name; + if (mShortNames) + name = av_name.getDisplayName(); + else + name = av_name.getCompleteName(); + + // Append optional suffix. + if (!suffix.empty()) + { + name.append(suffix); + } + + if (!prefix.empty()) + { + name.insert(0, prefix); + } + + LLNameListItem* list_item = item.get(); + if (list_item && list_item->getUUID() == agent_id) + { + LLScrollListCell* cell = list_item->getColumn(mNameColumnIndex); + if (cell) + { + cell->setValue(name); + setNeedsSort(); + } + } + + ////////////////////////////////////////////////////////////////////////// + // BAKER - FIX NameListCtrl + //if (mPendingLookupsRemaining <= 0) + { + // We might get into a state where mPendingLookupsRemaining might + // go negative. So just reset it right now and figure out if it's + // possible later :) + //mPendingLookupsRemaining = 0; + + mNameListCompleteSignal(true); + } + //else + { + // mPendingLookupsRemaining--; + } + ////////////////////////////////////////////////////////////////////////// + + dirtyColumns(); +} + +void LLNameListCtrl::onGroupNameCache(const LLUUID& group_id, const std::string name, LLHandle item) +{ + avatar_name_cache_connection_map_t::iterator it = mGroupNameCacheConnections.find(group_id); + if (it != mGroupNameCacheConnections.end()) + { + if (it->second.connected()) + { + it->second.disconnect(); + } + mGroupNameCacheConnections.erase(it); + } + + LLNameListItem* list_item = item.get(); + if (list_item && list_item->getUUID() == group_id) + { + LLScrollListCell* cell = list_item->getColumn(mNameColumnIndex); + if (cell) + { + cell->setValue(name); + setNeedsSort(); + } + } + + dirtyColumns(); +} + +void LLNameListCtrl::updateColumns(bool force_update) +{ + LLScrollListCtrl::updateColumns(force_update); + + if (!mNameColumn.empty()) + { + LLScrollListColumn* name_column = getColumn(mNameColumn); + if (name_column) + { + mNameColumnIndex = name_column->mIndex; + } + } +} + +void LLNameListCtrl::sortByName(bool ascending) +{ + sortByColumnIndex(mNameColumnIndex,ascending); +} diff --git a/indra/newview/llnamelistctrl.h b/indra/newview/llnamelistctrl.h index 09b9b57afe..dde85f4d29 100644 --- a/indra/newview/llnamelistctrl.h +++ b/indra/newview/llnamelistctrl.h @@ -1,225 +1,225 @@ -/** - * @file llnamelistctrl.h - * @brief A list of names, automatically refreshing from the name cache. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLNAMELISTCTRL_H -#define LL_LLNAMELISTCTRL_H - -#include - -#include "llscrolllistctrl.h" - -class LLAvatarName; - -/** - * LLNameListCtrl item - * - * We don't use LLScrollListItem to be able to override getUUID(), which is needed - * because the name list item value is not simply an UUID but a map (uuid, is_group). - */ -class LLNameListItem : public LLScrollListItem, public LLHandleProvider -{ -public: - bool isGroup() const { return mIsGroup; } - void setIsGroup(bool is_group) { mIsGroup = is_group; } - bool isExperience() const { return mIsExperience; } - void setIsExperience(bool is_experience) { mIsExperience = is_experience; } - void setSpecialID(const LLUUID& special_id) { mSpecialID = special_id; } - const LLUUID& getSpecialID() const { return mSpecialID; } - -protected: - friend class LLNameListCtrl; - - LLNameListItem( const LLScrollListItem::Params& p ) - : LLScrollListItem(p), mIsGroup(false), mIsExperience(false) - { - } - - LLNameListItem( const LLScrollListItem::Params& p, bool is_group ) - : LLScrollListItem(p), mIsGroup(is_group), mIsExperience(false) - { - } - - LLNameListItem( const LLScrollListItem::Params& p, bool is_group, bool is_experience ) - : LLScrollListItem(p), mIsGroup(is_group), mIsExperience(is_experience) - { - } - -private: - bool mIsGroup; - bool mIsExperience; - - LLUUID mSpecialID; -}; - - -class LLNameListCtrl -: public LLScrollListCtrl, public LLInstanceTracker -{ -public: - typedef boost::signals2::signal namelist_complete_signal_t; - - typedef enum e_name_type - { - INDIVIDUAL, - GROUP, - SPECIAL, - EXPERIENCE - } ENameType; - - // provide names for enums - struct NameTypeNames : public LLInitParam::TypeValuesHelper - { - static void declareValues(); - }; - - struct NameItem : public LLInitParam::Block - { - Optional name; - Optional target; - Optional special_id; - - NameItem() - : name("name"), - target("target", INDIVIDUAL), - special_id("special_id", LLUUID()) - {} - }; - - struct NameColumn : public LLInitParam::ChoiceBlock - { - Alternative column_index; - Alternative column_name; - NameColumn() - : column_name("name_column"), - column_index("name_column_index", 0) - {} - }; - - struct Params : public LLInitParam::Block - { - Optional name_column; - Optional allow_calling_card_drop; - Optional short_names; - Params(); - }; - -protected: - LLNameListCtrl(const Params&); - virtual ~LLNameListCtrl() - { - for (avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.begin(); it != mAvatarNameCacheConnections.end(); ++it) - { - if (it->second.connected()) - { - it->second.disconnect(); - } - } - mAvatarNameCacheConnections.clear(); - } - friend class LLUICtrlFactory; -public: - // Add a user to the list by name. It will be added, the name - // requested from the cache, and updated as necessary. - LLScrollListItem* addNameItem(const LLUUID& agent_id, EAddPosition pos = ADD_BOTTOM, - bool enabled = true, const std::string& suffix = LLStringUtil::null, const std::string& prefix = LLStringUtil::null); - LLScrollListItem* addNameItem(NameItem& item, EAddPosition pos = ADD_BOTTOM); - - /*virtual*/ LLScrollListItem* addElement(const LLSD& element, EAddPosition pos = ADD_BOTTOM, void* userdata = NULL); - LLScrollListItem* addNameItemRow(const NameItem& value, EAddPosition pos = ADD_BOTTOM, const std::string& suffix = LLStringUtil::null, - const std::string& prefix = LLStringUtil::null); - - // Add a user to the list by name. It will be added, the name - // requested from the cache, and updated as necessary. - void addGroupNameItem(const LLUUID& group_id, EAddPosition pos = ADD_BOTTOM, - bool enabled = true); - void addGroupNameItem(NameItem& item, EAddPosition pos = ADD_BOTTOM); - - - void removeNameItem(const LLUUID& agent_id); - - LLScrollListItem* getNameItemByAgentId(const LLUUID& agent_id); - - void selectItemBySpecialId(const LLUUID& special_id); - LLUUID getSelectedSpecialId(); - - // LLView interface - /*virtual*/ bool handleDragAndDrop(S32 x, S32 y, MASK mask, - bool drop, EDragAndDropType cargo_type, void *cargo_data, - EAcceptance *accept, - std::string& tooltip_msg); - /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); - - void setAllowCallingCardDrop(bool b) { mAllowCallingCardDrop = b; } - - void sortByName(bool ascending); - - /*virtual*/ void updateColumns(bool force_update); - - /*virtual*/ void mouseOverHighlightNthItem( S32 index ); - - /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); - - bool isSpecialType() { return (mNameListType == SPECIAL); } - - void setNameListType(e_name_type type) { mNameListType = type; } - void setHoverIconName(std::string icon_name) { mHoverIconName = icon_name; } - -private: - void showInspector(const LLUUID& avatar_id, bool is_group, bool is_experience = false); - void onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name, std::string suffix, std::string prefix, LLHandle item); - void onGroupNameCache(const LLUUID& group_id, const std::string name, LLHandle item); - -private: - S32 mNameColumnIndex; - std::string mNameColumn; - bool mAllowCallingCardDrop; - bool mShortNames; // display name only, no SLID - typedef std::map avatar_name_cache_connection_map_t; - avatar_name_cache_connection_map_t mAvatarNameCacheConnections; - avatar_name_cache_connection_map_t mGroupNameCacheConnections; - - S32 mPendingLookupsRemaining; - namelist_complete_signal_t mNameListCompleteSignal; - - std::string mHoverIconName; - e_name_type mNameListType; - - boost::signals2::signal mIconClickedSignal; - -public: - boost::signals2::connection setOnNameListCompleteCallback(boost::function onNameListCompleteCallback) - { - return mNameListCompleteSignal.connect(onNameListCompleteCallback); - } - - boost::signals2::connection setIconClickedCallback(boost::function cb) - { - return mIconClickedSignal.connect(cb); - } -}; - - -#endif +/** + * @file llnamelistctrl.h + * @brief A list of names, automatically refreshing from the name cache. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLNAMELISTCTRL_H +#define LL_LLNAMELISTCTRL_H + +#include + +#include "llscrolllistctrl.h" + +class LLAvatarName; + +/** + * LLNameListCtrl item + * + * We don't use LLScrollListItem to be able to override getUUID(), which is needed + * because the name list item value is not simply an UUID but a map (uuid, is_group). + */ +class LLNameListItem : public LLScrollListItem, public LLHandleProvider +{ +public: + bool isGroup() const { return mIsGroup; } + void setIsGroup(bool is_group) { mIsGroup = is_group; } + bool isExperience() const { return mIsExperience; } + void setIsExperience(bool is_experience) { mIsExperience = is_experience; } + void setSpecialID(const LLUUID& special_id) { mSpecialID = special_id; } + const LLUUID& getSpecialID() const { return mSpecialID; } + +protected: + friend class LLNameListCtrl; + + LLNameListItem( const LLScrollListItem::Params& p ) + : LLScrollListItem(p), mIsGroup(false), mIsExperience(false) + { + } + + LLNameListItem( const LLScrollListItem::Params& p, bool is_group ) + : LLScrollListItem(p), mIsGroup(is_group), mIsExperience(false) + { + } + + LLNameListItem( const LLScrollListItem::Params& p, bool is_group, bool is_experience ) + : LLScrollListItem(p), mIsGroup(is_group), mIsExperience(is_experience) + { + } + +private: + bool mIsGroup; + bool mIsExperience; + + LLUUID mSpecialID; +}; + + +class LLNameListCtrl +: public LLScrollListCtrl, public LLInstanceTracker +{ +public: + typedef boost::signals2::signal namelist_complete_signal_t; + + typedef enum e_name_type + { + INDIVIDUAL, + GROUP, + SPECIAL, + EXPERIENCE + } ENameType; + + // provide names for enums + struct NameTypeNames : public LLInitParam::TypeValuesHelper + { + static void declareValues(); + }; + + struct NameItem : public LLInitParam::Block + { + Optional name; + Optional target; + Optional special_id; + + NameItem() + : name("name"), + target("target", INDIVIDUAL), + special_id("special_id", LLUUID()) + {} + }; + + struct NameColumn : public LLInitParam::ChoiceBlock + { + Alternative column_index; + Alternative column_name; + NameColumn() + : column_name("name_column"), + column_index("name_column_index", 0) + {} + }; + + struct Params : public LLInitParam::Block + { + Optional name_column; + Optional allow_calling_card_drop; + Optional short_names; + Params(); + }; + +protected: + LLNameListCtrl(const Params&); + virtual ~LLNameListCtrl() + { + for (avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.begin(); it != mAvatarNameCacheConnections.end(); ++it) + { + if (it->second.connected()) + { + it->second.disconnect(); + } + } + mAvatarNameCacheConnections.clear(); + } + friend class LLUICtrlFactory; +public: + // Add a user to the list by name. It will be added, the name + // requested from the cache, and updated as necessary. + LLScrollListItem* addNameItem(const LLUUID& agent_id, EAddPosition pos = ADD_BOTTOM, + bool enabled = true, const std::string& suffix = LLStringUtil::null, const std::string& prefix = LLStringUtil::null); + LLScrollListItem* addNameItem(NameItem& item, EAddPosition pos = ADD_BOTTOM); + + /*virtual*/ LLScrollListItem* addElement(const LLSD& element, EAddPosition pos = ADD_BOTTOM, void* userdata = NULL); + LLScrollListItem* addNameItemRow(const NameItem& value, EAddPosition pos = ADD_BOTTOM, const std::string& suffix = LLStringUtil::null, + const std::string& prefix = LLStringUtil::null); + + // Add a user to the list by name. It will be added, the name + // requested from the cache, and updated as necessary. + void addGroupNameItem(const LLUUID& group_id, EAddPosition pos = ADD_BOTTOM, + bool enabled = true); + void addGroupNameItem(NameItem& item, EAddPosition pos = ADD_BOTTOM); + + + void removeNameItem(const LLUUID& agent_id); + + LLScrollListItem* getNameItemByAgentId(const LLUUID& agent_id); + + void selectItemBySpecialId(const LLUUID& special_id); + LLUUID getSelectedSpecialId(); + + // LLView interface + /*virtual*/ bool handleDragAndDrop(S32 x, S32 y, MASK mask, + bool drop, EDragAndDropType cargo_type, void *cargo_data, + EAcceptance *accept, + std::string& tooltip_msg); + /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); + + void setAllowCallingCardDrop(bool b) { mAllowCallingCardDrop = b; } + + void sortByName(bool ascending); + + /*virtual*/ void updateColumns(bool force_update); + + /*virtual*/ void mouseOverHighlightNthItem( S32 index ); + + /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); + + bool isSpecialType() { return (mNameListType == SPECIAL); } + + void setNameListType(e_name_type type) { mNameListType = type; } + void setHoverIconName(std::string icon_name) { mHoverIconName = icon_name; } + +private: + void showInspector(const LLUUID& avatar_id, bool is_group, bool is_experience = false); + void onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name, std::string suffix, std::string prefix, LLHandle item); + void onGroupNameCache(const LLUUID& group_id, const std::string name, LLHandle item); + +private: + S32 mNameColumnIndex; + std::string mNameColumn; + bool mAllowCallingCardDrop; + bool mShortNames; // display name only, no SLID + typedef std::map avatar_name_cache_connection_map_t; + avatar_name_cache_connection_map_t mAvatarNameCacheConnections; + avatar_name_cache_connection_map_t mGroupNameCacheConnections; + + S32 mPendingLookupsRemaining; + namelist_complete_signal_t mNameListCompleteSignal; + + std::string mHoverIconName; + e_name_type mNameListType; + + boost::signals2::signal mIconClickedSignal; + +public: + boost::signals2::connection setOnNameListCompleteCallback(boost::function onNameListCompleteCallback) + { + return mNameListCompleteSignal.connect(onNameListCompleteCallback); + } + + boost::signals2::connection setIconClickedCallback(boost::function cb) + { + return mIconClickedSignal.connect(cb); + } +}; + + +#endif diff --git a/indra/newview/llnavigationbar.cpp b/indra/newview/llnavigationbar.cpp index d5768a9e65..da5bc4b05d 100644 --- a/indra/newview/llnavigationbar.cpp +++ b/indra/newview/llnavigationbar.cpp @@ -1,739 +1,739 @@ -/** - * @file llnavigationbar.cpp - * @brief Navigation bar implementation - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llnavigationbar.h" - -#include "v2math.h" - -#include "llregionhandle.h" - -#include "llfloaterreg.h" -#include "llfocusmgr.h" -#include "lliconctrl.h" -#include "llmenugl.h" - -#include "llagent.h" -#include "llviewerregion.h" -#include "lllandmarkactions.h" -#include "lllocationhistory.h" -#include "lllocationinputctrl.h" -#include "llpaneltopinfobar.h" -#include "llteleporthistory.h" -#include "llresizebar.h" -#include "llsearchcombobox.h" -#include "llslurl.h" -#include "llurlregistry.h" -#include "llurldispatcher.h" -#include "llviewerinventory.h" -#include "llviewermenu.h" -#include "llviewerparcelmgr.h" -#include "llworldmapmessage.h" -#include "llappviewer.h" -#include "llviewercontrol.h" -#include "llweb.h" -#include "llhints.h" - -#include "llfloatersidepanelcontainer.h" -#include "llinventorymodel.h" -#include "lllandmarkactions.h" - -#include "llfavoritesbar.h" -#include "llagentui.h" - -#include - -//-- LLTeleportHistoryMenuItem ----------------------------------------------- - -/** - * Item look varies depending on the type (backward/current/forward). - */ -class LLTeleportHistoryMenuItem : public LLMenuItemCallGL -{ -public: - typedef enum e_item_type - { - TYPE_BACKWARD, - TYPE_CURRENT, - TYPE_FORWARD, - } EType; - - struct Params : public LLInitParam::Block - { - Mandatory item_type; - Optional back_item_font, - current_item_font, - forward_item_font; - Optional back_item_image, - forward_item_image; - Optional image_hpad, - image_vpad; - Params() - : item_type(), - back_item_font("back_item_font"), - current_item_font("current_item_font"), - forward_item_font("forward_item_font"), - back_item_image("back_item_image"), - forward_item_image("forward_item_image"), - image_hpad("image_hpad"), - image_vpad("image_vpad") - {} - }; - - /*virtual*/ void draw(); - /*virtual*/ void onMouseEnter(S32 x, S32 y, MASK mask); - /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask); - -private: - LLTeleportHistoryMenuItem(const Params&); - friend class LLUICtrlFactory; - - static const S32 ICON_WIDTH = 16; - static const S32 ICON_HEIGHT = 16; - - LLIconCtrl* mArrowIcon; -}; - -static LLDefaultChildRegistry::Register r("teleport_history_menu_item"); - - -LLTeleportHistoryMenuItem::LLTeleportHistoryMenuItem(const Params& p) -: LLMenuItemCallGL(p), - mArrowIcon(NULL) -{ - // Set appearance depending on the item type. - if (p.item_type == TYPE_BACKWARD) - { - setFont( p.back_item_font ); - } - else if (p.item_type == TYPE_CURRENT) - { - setFont( p.current_item_font ); - } - else - { - setFont( p.forward_item_font ); - } - - LLIconCtrl::Params icon_params; - icon_params.name("icon"); - LLRect rect(0, ICON_HEIGHT, ICON_WIDTH, 0); - rect.translate( p.image_hpad, p.image_vpad ); - icon_params.rect( rect ); - icon_params.mouse_opaque(false); - icon_params.follows.flags(FOLLOWS_LEFT | FOLLOWS_TOP); - icon_params.visible(false); - - mArrowIcon = LLUICtrlFactory::create (icon_params); - - // no image for the current item - if (p.item_type == TYPE_BACKWARD) - mArrowIcon->setValue( p.back_item_image() ); - else if (p.item_type == TYPE_FORWARD) - mArrowIcon->setValue( p.forward_item_image() ); - - addChild(mArrowIcon); -} - -void LLTeleportHistoryMenuItem::draw() -{ - // Draw menu item itself. - LLMenuItemCallGL::draw(); - - // Draw children if any. *TODO: move this to LLMenuItemGL? - LLUICtrl::draw(); -} - -void LLTeleportHistoryMenuItem::onMouseEnter(S32 x, S32 y, MASK mask) -{ - mArrowIcon->setVisible(true); -} - -void LLTeleportHistoryMenuItem::onMouseLeave(S32 x, S32 y, MASK mask) -{ - mArrowIcon->setVisible(false); -} - -static LLDefaultChildRegistry::Register menu_button("pull_button"); - -LLPullButton::LLPullButton(const LLPullButton::Params& params) : - LLButton(params) -{ - setDirectionFromName(params.direction); -} -boost::signals2::connection LLPullButton::setClickDraggingCallback(const commit_signal_t::slot_type& cb) -{ - return mClickDraggingSignal.connect(cb); -} - -/*virtual*/ -void LLPullButton::onMouseLeave(S32 x, S32 y, MASK mask) -{ - LLButton::onMouseLeave(x, y, mask); - - if (mMouseDownTimer.getStarted()) //an user have done a mouse down, if the timer started. see LLButton::handleMouseDown for details - { - const LLVector2 cursor_direction = LLVector2(F32(x), F32(y)) - mLastMouseDown; - /* For now cursor_direction points to the direction of mouse movement - * Need to decide whether should we fire a signal. - * We fire if angle between mDraggingDirection and cursor_direction is less that 45 degree - * Note: - * 0.5 * F_PI_BY_TWO equals to PI/4 radian that equals to angle of 45 degrees - */ - if (angle_between(mDraggingDirection, cursor_direction) < 0.5 * F_PI_BY_TWO)//call if angle < pi/4 - { - mClickDraggingSignal(this, LLSD()); - } - } - -} - -/*virtual*/ -bool LLPullButton::handleMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = LLButton::handleMouseDown(x, y, mask); - if (handled) - { - //if mouse down was handled by button, - //capture mouse position to calculate the direction of mouse move after mouseLeave event - mLastMouseDown.set(F32(x), F32(y)); - } - return handled; -} - -/*virtual*/ -bool LLPullButton::handleMouseUp(S32 x, S32 y, MASK mask) -{ - // reset data to get ready for next circle - mLastMouseDown.clear(); - return LLButton::handleMouseUp(x, y, mask); -} -/** - * this function is setting up dragging direction vector. - * Last one is just unit vector. It points to direction of mouse drag that we need to handle - */ -void LLPullButton::setDirectionFromName(const std::string& name) -{ - if (name == "left") - { - mDraggingDirection.set(F32(-1), F32(0)); - } - else if (name == "right") - { - mDraggingDirection.set(F32(0), F32(1)); - } - else if (name == "down") - { - mDraggingDirection.set(F32(0), F32(-1)); - } - else if (name == "up") - { - mDraggingDirection.set(F32(0), F32(1)); - } -} - -//-- LNavigationBar ---------------------------------------------------------- - -/* -TODO: -- Load navbar height from saved settings (as it's done for status bar) or think of a better way. -*/ - -LLNavigationBar::LLNavigationBar() -: mTeleportHistoryMenu(NULL), - mBtnBack(NULL), - mBtnForward(NULL), - mBtnHome(NULL), - mCmbLocation(NULL), - mSaveToLocationHistory(false), - mNavigationPanel(NULL), - mFavoritePanel(NULL), - mNavPanWidth(0) -{ - buildFromFile( "panel_navigation_bar.xml"); - - // set a listener function for LoginComplete event - LLAppViewer::instance()->setOnLoginCompletedCallback(boost::bind(&LLNavigationBar::handleLoginComplete, this)); -} - -LLNavigationBar::~LLNavigationBar() -{ - mTeleportFinishConnection.disconnect(); - mTeleportFailedConnection.disconnect(); -} - -bool LLNavigationBar::postBuild() -{ - mBtnBack = getChild("back_btn"); - mBtnForward = getChild("forward_btn"); - mBtnHome = getChild("home_btn"); - mBtnLandmarks = getChild("landmarks_btn"); - - mCmbLocation= getChild("location_combo"); - - mBtnBack->setEnabled(false); - mBtnBack->setClickedCallback(boost::bind(&LLNavigationBar::onBackButtonClicked, this)); - mBtnBack->setHeldDownCallback(boost::bind(&LLNavigationBar::onBackOrForwardButtonHeldDown, this,_1, _2)); - mBtnBack->setClickDraggingCallback(boost::bind(&LLNavigationBar::showTeleportHistoryMenu, this,_1)); - - mBtnForward->setEnabled(false); - mBtnForward->setClickedCallback(boost::bind(&LLNavigationBar::onForwardButtonClicked, this)); - mBtnForward->setHeldDownCallback(boost::bind(&LLNavigationBar::onBackOrForwardButtonHeldDown, this, _1, _2)); - mBtnForward->setClickDraggingCallback(boost::bind(&LLNavigationBar::showTeleportHistoryMenu, this,_1)); - - mBtnHome->setClickedCallback(boost::bind(&LLNavigationBar::onHomeButtonClicked, this)); - - mBtnLandmarks->setClickedCallback(boost::bind(&LLNavigationBar::onLandmarksButtonClicked, this)); - - mCmbLocation->setCommitCallback(boost::bind(&LLNavigationBar::onLocationSelection, this)); - - mTeleportFinishConnection = LLViewerParcelMgr::getInstance()-> - setTeleportFinishedCallback(boost::bind(&LLNavigationBar::onTeleportFinished, this, _1)); - - mTeleportFailedConnection = LLViewerParcelMgr::getInstance()-> - setTeleportFailedCallback(boost::bind(&LLNavigationBar::onTeleportFailed, this)); - - mDefaultNbRect = getRect(); - mDefaultFpRect = getChild("favorite")->getRect(); - - // we'll be notified on teleport history changes - LLTeleportHistory::getInstance()->setHistoryChangedCallback( - boost::bind(&LLNavigationBar::onTeleportHistoryChanged, this)); - - LLHints::getInstance()->registerHintTarget("nav_bar", getHandle()); - - mNavigationPanel = getChild("navigation_layout_panel"); - mFavoritePanel = getChild("favorites_layout_panel"); - mNavigationPanel->getResizeBar()->setResizeListener(boost::bind(&LLNavigationBar::onNavbarResized, this)); - mFavoritePanel->getResizeBar()->setResizeListener(boost::bind(&LLNavigationBar::onNavbarResized, this)); - - return true; -} - -void LLNavigationBar::setVisible(bool visible) -{ - // change visibility of grandparent layout_panel to animate in and out - if (getParent()) - { - //to avoid some mysterious bugs like EXT-3352, at least try to log an incorrect parent to ping about a problem. - if(getParent()->getName() != "nav_bar_container") - { - LL_WARNS("LLNavigationBar")<<"NavigationBar has an unknown name of the parent: "<getName()<< LL_ENDL; - } - getParent()->setVisible(visible); - } -} - -void LLNavigationBar::draw() -{ - if (isBackgroundVisible()) - { - static LLUIColor color_drop_shadow = LLUIColorTable::instance().getColor("ColorDropShadow"); - gl_drop_shadow(0, getRect().getHeight(), getRect().getWidth(), 0, - color_drop_shadow, DROP_SHADOW_FLOATER); - } - - LLPanel::draw(); -} - -bool LLNavigationBar::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = childrenHandleRightMouseDown( x, y, mask) != NULL; - if(!handled && !gMenuHolder->hasVisibleMenu()) - { - show_navbar_context_menu(this,x,y); - handled = true; - } - return handled; -} - -void LLNavigationBar::onBackButtonClicked() -{ - LLTeleportHistory::getInstance()->goBack(); -} - -void LLNavigationBar::onNavbarResized() -{ - S32 new_nav_pan_width = mNavigationPanel->getRect().getWidth(); - if(mNavPanWidth != new_nav_pan_width) - { - S32 new_stack_width = new_nav_pan_width + mFavoritePanel->getRect().getWidth(); - F32 ratio = (F32)new_nav_pan_width / (F32)new_stack_width; - gSavedPerAccountSettings.setF32("NavigationBarRatio", ratio); - mNavPanWidth = new_nav_pan_width; - } -} - -void LLNavigationBar::onBackOrForwardButtonHeldDown(LLUICtrl* ctrl, const LLSD& param) -{ - if (param["count"].asInteger() == 0) - showTeleportHistoryMenu(ctrl); -} - -void LLNavigationBar::onForwardButtonClicked() -{ - LLTeleportHistory::getInstance()->goForward(); -} - -void LLNavigationBar::onHomeButtonClicked() -{ - gAgent.teleportHome(); -} - -void LLNavigationBar::onLandmarksButtonClicked() -{ - LLFloaterReg::toggleInstanceOrBringToFront("places"); - LLFloaterSidePanelContainer::showPanel("places", LLSD().with("type", "open_landmark_tab")); -} - -void LLNavigationBar::onTeleportHistoryMenuItemClicked(const LLSD& userdata) -{ - int idx = userdata.asInteger(); - LLTeleportHistory::getInstance()->goToItem(idx); -} - -// This is called when user presses enter in the location input -// or selects a location from the typed locations dropdown. -void LLNavigationBar::onLocationSelection() -{ - std::string typed_location = mCmbLocation->getSimple(); - LLStringUtil::trim(typed_location); - - // Will not teleport to empty location. - if (typed_location.empty()) - return; - //get selected item from combobox item - LLSD value = mCmbLocation->getSelectedValue(); - if(value.isUndefined() && !mCmbLocation->getTextEntry()->isDirty()) - { - // At this point we know that: there is no selected item in list and text field has NOT been changed - // So there is no sense to try to change the location - return; - } - /* since navbar list support autocompletion it contains several types of item: landmark, teleport hystory item, - * typed by user slurl or region name. Let's find out which type of item the user has selected - * to make decision about adding this location into typed history. see mSaveToLocationHistory - * Note: - * Only TYPED_REGION_SLURL item will be added into LLLocationHistory - */ - - if(value.has("item_type")) - { - - switch(value["item_type"].asInteger()) - { - case LANDMARK: - - if(value.has("AssetUUID")) - { - gAgent.teleportViaLandmark( LLUUID(value["AssetUUID"].asString())); - // user teleported by manually inputting inventory landmark's name - mSaveToLocationHistory = false; - return; - } - else - { - LLInventoryModel::item_array_t landmark_items = - LLLandmarkActions::fetchLandmarksByName(typed_location, - false); - if (!landmark_items.empty()) - { - gAgent.teleportViaLandmark( landmark_items[0]->getAssetUUID()); - mSaveToLocationHistory = true; - return; - } - } - break; - - case TELEPORT_HISTORY: - //in case of teleport item was selected, teleport by position too. - case TYPED_REGION_SLURL: - if(value.has("global_pos")) - { - gAgent.teleportViaLocation(LLVector3d(value["global_pos"])); - return; - } - break; - - default: - break; - } - } - //Let's parse slurl or region name - - std::string region_name; - LLVector3 local_coords(128, 128, 0); - // Is the typed location a SLURL? - LLSLURL slurl = LLSLURL(typed_location); - if (slurl.getType() == LLSLURL::LOCATION) - { - region_name = slurl.getRegion(); - local_coords = slurl.getPosition(); - } - else if(!slurl.isValid()) - { - // we have to do this check after previous, because LLUrlRegistry contains handlers for slurl too - // but we need to know whether typed_location is a simple http url. - if (LLUrlRegistry::instance().isUrl(typed_location)) - { - // display http:// URLs in the media browser, or - // anything else is sent to the search floater - LLWeb::loadURL(typed_location); - return; - } - else - { - // assume that an user has typed the {region name} or possible {region_name, parcel} - region_name = typed_location.substr(0,typed_location.find(',')); - } - } - else - { - // was an app slurl, home, whatever. Bail - return; - } - - // Resolve the region name to its global coordinates. - // If resolution succeeds we'll teleport. - LLWorldMapMessage::url_callback_t cb = boost::bind( - &LLNavigationBar::onRegionNameResponse, this, - typed_location, region_name, local_coords, _1, _2, _3, _4); - mSaveToLocationHistory = true; - LLWorldMapMessage::getInstance()->sendNamedRegionRequest(region_name, cb, std::string("unused"), false); -} - -void LLNavigationBar::onTeleportFailed() -{ - mSaveToLocationHistory = false; -} - -void LLNavigationBar::onTeleportFinished(const LLVector3d& global_agent_pos) -{ - if (!mSaveToLocationHistory) - return; - LLLocationHistory* lh = LLLocationHistory::getInstance(); - - //TODO*: do we need convert slurl into readable format? - std::string location; - /*NOTE: - * We can't use gAgent.getPositionAgent() in case of local teleport to build location. - * At this moment gAgent.getPositionAgent() contains previous coordinates. - * according to EXT-65 agent position is being reseted on each frame. - */ - LLAgentUI::buildLocationString(location, LLAgentUI::LOCATION_FORMAT_NO_MATURITY, - gAgent.getPosAgentFromGlobal(global_agent_pos)); - std::string tooltip (LLSLURL(gAgent.getRegion()->getName(), global_agent_pos).getSLURLString()); - - LLLocationHistoryItem item (location, - global_agent_pos, tooltip,TYPED_REGION_SLURL);// we can add into history only TYPED location - //Touch it, if it is at list already, add new location otherwise - if ( !lh->touchItem(item) ) { - lh->addItem(item); - } - - lh->save(); - - mSaveToLocationHistory = false; - -} - -void LLNavigationBar::onTeleportHistoryChanged() -{ - // Update navigation controls. - LLTeleportHistory* h = LLTeleportHistory::getInstance(); - int cur_item = h->getCurrentItemIndex(); - mBtnBack->setEnabled(cur_item > 0); - mBtnForward->setEnabled(cur_item < ((int)h->getItems().size() - 1)); -} - -void LLNavigationBar::rebuildTeleportHistoryMenu() -{ - // Has the pop-up menu been built? - if (mTeleportHistoryMenu) - { - // Clear it. - mTeleportHistoryMenu->empty(); - } - else - { - // Create it. - LLMenuGL::Params menu_p; - menu_p.name("popup"); - menu_p.can_tear_off(false); - menu_p.visible(false); - menu_p.bg_visible(true); - menu_p.scrollable(true); - mTeleportHistoryMenu = LLUICtrlFactory::create(menu_p); - - addChild(mTeleportHistoryMenu); - } - - // Populate the menu with teleport history items. - LLTeleportHistory* hist = LLTeleportHistory::getInstance(); - const LLTeleportHistory::slurl_list_t& hist_items = hist->getItems(); - int cur_item = hist->getCurrentItemIndex(); - - // Items will be shown in the reverse order, just like in Firefox. - for (int i = (int)hist_items.size()-1; i >= 0; i--) - { - LLTeleportHistoryMenuItem::EType type; - if (i < cur_item) - type = LLTeleportHistoryMenuItem::TYPE_BACKWARD; - else if (i > cur_item) - type = LLTeleportHistoryMenuItem::TYPE_FORWARD; - else - type = LLTeleportHistoryMenuItem::TYPE_CURRENT; - - LLTeleportHistoryMenuItem::Params item_params; - item_params.label = item_params.name = hist_items[i].mTitle; - item_params.item_type = type; - item_params.on_click.function(boost::bind(&LLNavigationBar::onTeleportHistoryMenuItemClicked, this, i)); - LLTeleportHistoryMenuItem* new_itemp = LLUICtrlFactory::create(item_params); - //new_itemp->setFont() - mTeleportHistoryMenu->addChild(new_itemp); - } -} - -void LLNavigationBar::onRegionNameResponse( - std::string typed_location, - std::string region_name, - LLVector3 local_coords, - U64 region_handle, const std::string& url, const LLUUID& snapshot_id, bool teleport) -{ - // Invalid location? - if (region_handle) - { - // Teleport to the location. - LLVector3d region_pos = from_region_handle(region_handle); - LLVector3d global_pos = region_pos + (LLVector3d) local_coords; - - LL_INFOS() << "Teleporting to: " << LLSLURL(region_name, global_pos).getSLURLString() << LL_ENDL; - gAgent.teleportViaLocation(global_pos); - } - else if (gSavedSettings.getBOOL("SearchFromAddressBar")) - { - invokeSearch(typed_location); - } -} - -void LLNavigationBar::showTeleportHistoryMenu(LLUICtrl* btn_ctrl) -{ - // Don't show the popup if teleport history is empty. - if (LLTeleportHistory::getInstance()->isEmpty()) - { - LL_DEBUGS() << "Teleport history is empty, will not show the menu." << LL_ENDL; - return; - } - - rebuildTeleportHistoryMenu(); - - if (mTeleportHistoryMenu == NULL) - return; - - mTeleportHistoryMenu->updateParent(LLMenuGL::sMenuContainer); - const S32 MENU_SPAWN_PAD = -1; - LLMenuGL::showPopup(btn_ctrl, mTeleportHistoryMenu, 0, MENU_SPAWN_PAD); - LLButton* nav_button = dynamic_cast(btn_ctrl); - if(nav_button) - { - if(mHistoryMenuConnection.connected()) - { - LL_WARNS("Navgationbar")<<"mHistoryMenuConnection should be disconnected at this moment."<setMouseUpCallback(boost::bind(&LLNavigationBar::onNavigationButtonHeldUp, this, nav_button)); - // pressed state will be update after mouseUp in onBackOrForwardButtonHeldUp(); - nav_button->setForcePressedState(true); - } - // *HACK pass the mouse capturing to the drop-down menu - // it need to let menu handle mouseup event - gFocusMgr.setMouseCapture(gMenuHolder); -} -/** - * Taking into account the HACK above, this callback-function is responsible for correct handling of mouseUp event in case of holding-down the navigation buttons.. - * We need to process this case separately to update a pressed state of navigation button. - */ -void LLNavigationBar::onNavigationButtonHeldUp(LLButton* nav_button) -{ - if(nav_button) - { - nav_button->setForcePressedState(false); - } - if(gFocusMgr.getMouseCapture() == gMenuHolder) - { - // we had passed mouseCapture in showTeleportHistoryMenu() - // now we MUST release mouseCapture to continue a proper mouseevent workflow. - gFocusMgr.setMouseCapture(NULL); - } - //gMenuHolder is using to display bunch of menus. Disconnect signal to avoid unnecessary calls. - mHistoryMenuConnection.disconnect(); -} - -void LLNavigationBar::handleLoginComplete() -{ - LLTeleportHistory::getInstance()->handleLoginComplete(); - LLPanelTopInfoBar::instance().handleLoginComplete(); - mCmbLocation->handleLoginComplete(); - resizeLayoutPanel(); -} - -void LLNavigationBar::resizeLayoutPanel() -{ - LLRect nav_bar_rect = mNavigationPanel->getRect(); - - S32 nav_panel_width = (nav_bar_rect.getWidth() + mFavoritePanel->getRect().getWidth()) * gSavedPerAccountSettings.getF32("NavigationBarRatio"); - - nav_bar_rect.setLeftTopAndSize(nav_bar_rect.mLeft, nav_bar_rect.mTop, nav_panel_width, nav_bar_rect.getHeight()); - mNavigationPanel->handleReshape(nav_bar_rect,true); -} -void LLNavigationBar::invokeSearch(std::string search_text) -{ - LLFloaterReg::showInstance("search", LLSD().with("category", "standard").with("query", LLSD(search_text))); -} - -void LLNavigationBar::clearHistoryCache() -{ - mCmbLocation->removeall(); - LLLocationHistory* lh = LLLocationHistory::getInstance(); - lh->removeItems(); - lh->save(); - LLTeleportHistory::getInstance()->purgeItems(); -} - -int LLNavigationBar::getDefNavBarHeight() -{ - return mDefaultNbRect.getHeight(); -} -int LLNavigationBar::getDefFavBarHeight() -{ - return mDefaultFpRect.getHeight(); -} - -bool LLNavigationBar::isRebakeNavMeshAvailable() -{ - return mCmbLocation->isNavMeshDirty(); -} +/** + * @file llnavigationbar.cpp + * @brief Navigation bar implementation + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llnavigationbar.h" + +#include "v2math.h" + +#include "llregionhandle.h" + +#include "llfloaterreg.h" +#include "llfocusmgr.h" +#include "lliconctrl.h" +#include "llmenugl.h" + +#include "llagent.h" +#include "llviewerregion.h" +#include "lllandmarkactions.h" +#include "lllocationhistory.h" +#include "lllocationinputctrl.h" +#include "llpaneltopinfobar.h" +#include "llteleporthistory.h" +#include "llresizebar.h" +#include "llsearchcombobox.h" +#include "llslurl.h" +#include "llurlregistry.h" +#include "llurldispatcher.h" +#include "llviewerinventory.h" +#include "llviewermenu.h" +#include "llviewerparcelmgr.h" +#include "llworldmapmessage.h" +#include "llappviewer.h" +#include "llviewercontrol.h" +#include "llweb.h" +#include "llhints.h" + +#include "llfloatersidepanelcontainer.h" +#include "llinventorymodel.h" +#include "lllandmarkactions.h" + +#include "llfavoritesbar.h" +#include "llagentui.h" + +#include + +//-- LLTeleportHistoryMenuItem ----------------------------------------------- + +/** + * Item look varies depending on the type (backward/current/forward). + */ +class LLTeleportHistoryMenuItem : public LLMenuItemCallGL +{ +public: + typedef enum e_item_type + { + TYPE_BACKWARD, + TYPE_CURRENT, + TYPE_FORWARD, + } EType; + + struct Params : public LLInitParam::Block + { + Mandatory item_type; + Optional back_item_font, + current_item_font, + forward_item_font; + Optional back_item_image, + forward_item_image; + Optional image_hpad, + image_vpad; + Params() + : item_type(), + back_item_font("back_item_font"), + current_item_font("current_item_font"), + forward_item_font("forward_item_font"), + back_item_image("back_item_image"), + forward_item_image("forward_item_image"), + image_hpad("image_hpad"), + image_vpad("image_vpad") + {} + }; + + /*virtual*/ void draw(); + /*virtual*/ void onMouseEnter(S32 x, S32 y, MASK mask); + /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask); + +private: + LLTeleportHistoryMenuItem(const Params&); + friend class LLUICtrlFactory; + + static const S32 ICON_WIDTH = 16; + static const S32 ICON_HEIGHT = 16; + + LLIconCtrl* mArrowIcon; +}; + +static LLDefaultChildRegistry::Register r("teleport_history_menu_item"); + + +LLTeleportHistoryMenuItem::LLTeleportHistoryMenuItem(const Params& p) +: LLMenuItemCallGL(p), + mArrowIcon(NULL) +{ + // Set appearance depending on the item type. + if (p.item_type == TYPE_BACKWARD) + { + setFont( p.back_item_font ); + } + else if (p.item_type == TYPE_CURRENT) + { + setFont( p.current_item_font ); + } + else + { + setFont( p.forward_item_font ); + } + + LLIconCtrl::Params icon_params; + icon_params.name("icon"); + LLRect rect(0, ICON_HEIGHT, ICON_WIDTH, 0); + rect.translate( p.image_hpad, p.image_vpad ); + icon_params.rect( rect ); + icon_params.mouse_opaque(false); + icon_params.follows.flags(FOLLOWS_LEFT | FOLLOWS_TOP); + icon_params.visible(false); + + mArrowIcon = LLUICtrlFactory::create (icon_params); + + // no image for the current item + if (p.item_type == TYPE_BACKWARD) + mArrowIcon->setValue( p.back_item_image() ); + else if (p.item_type == TYPE_FORWARD) + mArrowIcon->setValue( p.forward_item_image() ); + + addChild(mArrowIcon); +} + +void LLTeleportHistoryMenuItem::draw() +{ + // Draw menu item itself. + LLMenuItemCallGL::draw(); + + // Draw children if any. *TODO: move this to LLMenuItemGL? + LLUICtrl::draw(); +} + +void LLTeleportHistoryMenuItem::onMouseEnter(S32 x, S32 y, MASK mask) +{ + mArrowIcon->setVisible(true); +} + +void LLTeleportHistoryMenuItem::onMouseLeave(S32 x, S32 y, MASK mask) +{ + mArrowIcon->setVisible(false); +} + +static LLDefaultChildRegistry::Register menu_button("pull_button"); + +LLPullButton::LLPullButton(const LLPullButton::Params& params) : + LLButton(params) +{ + setDirectionFromName(params.direction); +} +boost::signals2::connection LLPullButton::setClickDraggingCallback(const commit_signal_t::slot_type& cb) +{ + return mClickDraggingSignal.connect(cb); +} + +/*virtual*/ +void LLPullButton::onMouseLeave(S32 x, S32 y, MASK mask) +{ + LLButton::onMouseLeave(x, y, mask); + + if (mMouseDownTimer.getStarted()) //an user have done a mouse down, if the timer started. see LLButton::handleMouseDown for details + { + const LLVector2 cursor_direction = LLVector2(F32(x), F32(y)) - mLastMouseDown; + /* For now cursor_direction points to the direction of mouse movement + * Need to decide whether should we fire a signal. + * We fire if angle between mDraggingDirection and cursor_direction is less that 45 degree + * Note: + * 0.5 * F_PI_BY_TWO equals to PI/4 radian that equals to angle of 45 degrees + */ + if (angle_between(mDraggingDirection, cursor_direction) < 0.5 * F_PI_BY_TWO)//call if angle < pi/4 + { + mClickDraggingSignal(this, LLSD()); + } + } + +} + +/*virtual*/ +bool LLPullButton::handleMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = LLButton::handleMouseDown(x, y, mask); + if (handled) + { + //if mouse down was handled by button, + //capture mouse position to calculate the direction of mouse move after mouseLeave event + mLastMouseDown.set(F32(x), F32(y)); + } + return handled; +} + +/*virtual*/ +bool LLPullButton::handleMouseUp(S32 x, S32 y, MASK mask) +{ + // reset data to get ready for next circle + mLastMouseDown.clear(); + return LLButton::handleMouseUp(x, y, mask); +} +/** + * this function is setting up dragging direction vector. + * Last one is just unit vector. It points to direction of mouse drag that we need to handle + */ +void LLPullButton::setDirectionFromName(const std::string& name) +{ + if (name == "left") + { + mDraggingDirection.set(F32(-1), F32(0)); + } + else if (name == "right") + { + mDraggingDirection.set(F32(0), F32(1)); + } + else if (name == "down") + { + mDraggingDirection.set(F32(0), F32(-1)); + } + else if (name == "up") + { + mDraggingDirection.set(F32(0), F32(1)); + } +} + +//-- LNavigationBar ---------------------------------------------------------- + +/* +TODO: +- Load navbar height from saved settings (as it's done for status bar) or think of a better way. +*/ + +LLNavigationBar::LLNavigationBar() +: mTeleportHistoryMenu(NULL), + mBtnBack(NULL), + mBtnForward(NULL), + mBtnHome(NULL), + mCmbLocation(NULL), + mSaveToLocationHistory(false), + mNavigationPanel(NULL), + mFavoritePanel(NULL), + mNavPanWidth(0) +{ + buildFromFile( "panel_navigation_bar.xml"); + + // set a listener function for LoginComplete event + LLAppViewer::instance()->setOnLoginCompletedCallback(boost::bind(&LLNavigationBar::handleLoginComplete, this)); +} + +LLNavigationBar::~LLNavigationBar() +{ + mTeleportFinishConnection.disconnect(); + mTeleportFailedConnection.disconnect(); +} + +bool LLNavigationBar::postBuild() +{ + mBtnBack = getChild("back_btn"); + mBtnForward = getChild("forward_btn"); + mBtnHome = getChild("home_btn"); + mBtnLandmarks = getChild("landmarks_btn"); + + mCmbLocation= getChild("location_combo"); + + mBtnBack->setEnabled(false); + mBtnBack->setClickedCallback(boost::bind(&LLNavigationBar::onBackButtonClicked, this)); + mBtnBack->setHeldDownCallback(boost::bind(&LLNavigationBar::onBackOrForwardButtonHeldDown, this,_1, _2)); + mBtnBack->setClickDraggingCallback(boost::bind(&LLNavigationBar::showTeleportHistoryMenu, this,_1)); + + mBtnForward->setEnabled(false); + mBtnForward->setClickedCallback(boost::bind(&LLNavigationBar::onForwardButtonClicked, this)); + mBtnForward->setHeldDownCallback(boost::bind(&LLNavigationBar::onBackOrForwardButtonHeldDown, this, _1, _2)); + mBtnForward->setClickDraggingCallback(boost::bind(&LLNavigationBar::showTeleportHistoryMenu, this,_1)); + + mBtnHome->setClickedCallback(boost::bind(&LLNavigationBar::onHomeButtonClicked, this)); + + mBtnLandmarks->setClickedCallback(boost::bind(&LLNavigationBar::onLandmarksButtonClicked, this)); + + mCmbLocation->setCommitCallback(boost::bind(&LLNavigationBar::onLocationSelection, this)); + + mTeleportFinishConnection = LLViewerParcelMgr::getInstance()-> + setTeleportFinishedCallback(boost::bind(&LLNavigationBar::onTeleportFinished, this, _1)); + + mTeleportFailedConnection = LLViewerParcelMgr::getInstance()-> + setTeleportFailedCallback(boost::bind(&LLNavigationBar::onTeleportFailed, this)); + + mDefaultNbRect = getRect(); + mDefaultFpRect = getChild("favorite")->getRect(); + + // we'll be notified on teleport history changes + LLTeleportHistory::getInstance()->setHistoryChangedCallback( + boost::bind(&LLNavigationBar::onTeleportHistoryChanged, this)); + + LLHints::getInstance()->registerHintTarget("nav_bar", getHandle()); + + mNavigationPanel = getChild("navigation_layout_panel"); + mFavoritePanel = getChild("favorites_layout_panel"); + mNavigationPanel->getResizeBar()->setResizeListener(boost::bind(&LLNavigationBar::onNavbarResized, this)); + mFavoritePanel->getResizeBar()->setResizeListener(boost::bind(&LLNavigationBar::onNavbarResized, this)); + + return true; +} + +void LLNavigationBar::setVisible(bool visible) +{ + // change visibility of grandparent layout_panel to animate in and out + if (getParent()) + { + //to avoid some mysterious bugs like EXT-3352, at least try to log an incorrect parent to ping about a problem. + if(getParent()->getName() != "nav_bar_container") + { + LL_WARNS("LLNavigationBar")<<"NavigationBar has an unknown name of the parent: "<getName()<< LL_ENDL; + } + getParent()->setVisible(visible); + } +} + +void LLNavigationBar::draw() +{ + if (isBackgroundVisible()) + { + static LLUIColor color_drop_shadow = LLUIColorTable::instance().getColor("ColorDropShadow"); + gl_drop_shadow(0, getRect().getHeight(), getRect().getWidth(), 0, + color_drop_shadow, DROP_SHADOW_FLOATER); + } + + LLPanel::draw(); +} + +bool LLNavigationBar::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = childrenHandleRightMouseDown( x, y, mask) != NULL; + if(!handled && !gMenuHolder->hasVisibleMenu()) + { + show_navbar_context_menu(this,x,y); + handled = true; + } + return handled; +} + +void LLNavigationBar::onBackButtonClicked() +{ + LLTeleportHistory::getInstance()->goBack(); +} + +void LLNavigationBar::onNavbarResized() +{ + S32 new_nav_pan_width = mNavigationPanel->getRect().getWidth(); + if(mNavPanWidth != new_nav_pan_width) + { + S32 new_stack_width = new_nav_pan_width + mFavoritePanel->getRect().getWidth(); + F32 ratio = (F32)new_nav_pan_width / (F32)new_stack_width; + gSavedPerAccountSettings.setF32("NavigationBarRatio", ratio); + mNavPanWidth = new_nav_pan_width; + } +} + +void LLNavigationBar::onBackOrForwardButtonHeldDown(LLUICtrl* ctrl, const LLSD& param) +{ + if (param["count"].asInteger() == 0) + showTeleportHistoryMenu(ctrl); +} + +void LLNavigationBar::onForwardButtonClicked() +{ + LLTeleportHistory::getInstance()->goForward(); +} + +void LLNavigationBar::onHomeButtonClicked() +{ + gAgent.teleportHome(); +} + +void LLNavigationBar::onLandmarksButtonClicked() +{ + LLFloaterReg::toggleInstanceOrBringToFront("places"); + LLFloaterSidePanelContainer::showPanel("places", LLSD().with("type", "open_landmark_tab")); +} + +void LLNavigationBar::onTeleportHistoryMenuItemClicked(const LLSD& userdata) +{ + int idx = userdata.asInteger(); + LLTeleportHistory::getInstance()->goToItem(idx); +} + +// This is called when user presses enter in the location input +// or selects a location from the typed locations dropdown. +void LLNavigationBar::onLocationSelection() +{ + std::string typed_location = mCmbLocation->getSimple(); + LLStringUtil::trim(typed_location); + + // Will not teleport to empty location. + if (typed_location.empty()) + return; + //get selected item from combobox item + LLSD value = mCmbLocation->getSelectedValue(); + if(value.isUndefined() && !mCmbLocation->getTextEntry()->isDirty()) + { + // At this point we know that: there is no selected item in list and text field has NOT been changed + // So there is no sense to try to change the location + return; + } + /* since navbar list support autocompletion it contains several types of item: landmark, teleport hystory item, + * typed by user slurl or region name. Let's find out which type of item the user has selected + * to make decision about adding this location into typed history. see mSaveToLocationHistory + * Note: + * Only TYPED_REGION_SLURL item will be added into LLLocationHistory + */ + + if(value.has("item_type")) + { + + switch(value["item_type"].asInteger()) + { + case LANDMARK: + + if(value.has("AssetUUID")) + { + gAgent.teleportViaLandmark( LLUUID(value["AssetUUID"].asString())); + // user teleported by manually inputting inventory landmark's name + mSaveToLocationHistory = false; + return; + } + else + { + LLInventoryModel::item_array_t landmark_items = + LLLandmarkActions::fetchLandmarksByName(typed_location, + false); + if (!landmark_items.empty()) + { + gAgent.teleportViaLandmark( landmark_items[0]->getAssetUUID()); + mSaveToLocationHistory = true; + return; + } + } + break; + + case TELEPORT_HISTORY: + //in case of teleport item was selected, teleport by position too. + case TYPED_REGION_SLURL: + if(value.has("global_pos")) + { + gAgent.teleportViaLocation(LLVector3d(value["global_pos"])); + return; + } + break; + + default: + break; + } + } + //Let's parse slurl or region name + + std::string region_name; + LLVector3 local_coords(128, 128, 0); + // Is the typed location a SLURL? + LLSLURL slurl = LLSLURL(typed_location); + if (slurl.getType() == LLSLURL::LOCATION) + { + region_name = slurl.getRegion(); + local_coords = slurl.getPosition(); + } + else if(!slurl.isValid()) + { + // we have to do this check after previous, because LLUrlRegistry contains handlers for slurl too + // but we need to know whether typed_location is a simple http url. + if (LLUrlRegistry::instance().isUrl(typed_location)) + { + // display http:// URLs in the media browser, or + // anything else is sent to the search floater + LLWeb::loadURL(typed_location); + return; + } + else + { + // assume that an user has typed the {region name} or possible {region_name, parcel} + region_name = typed_location.substr(0,typed_location.find(',')); + } + } + else + { + // was an app slurl, home, whatever. Bail + return; + } + + // Resolve the region name to its global coordinates. + // If resolution succeeds we'll teleport. + LLWorldMapMessage::url_callback_t cb = boost::bind( + &LLNavigationBar::onRegionNameResponse, this, + typed_location, region_name, local_coords, _1, _2, _3, _4); + mSaveToLocationHistory = true; + LLWorldMapMessage::getInstance()->sendNamedRegionRequest(region_name, cb, std::string("unused"), false); +} + +void LLNavigationBar::onTeleportFailed() +{ + mSaveToLocationHistory = false; +} + +void LLNavigationBar::onTeleportFinished(const LLVector3d& global_agent_pos) +{ + if (!mSaveToLocationHistory) + return; + LLLocationHistory* lh = LLLocationHistory::getInstance(); + + //TODO*: do we need convert slurl into readable format? + std::string location; + /*NOTE: + * We can't use gAgent.getPositionAgent() in case of local teleport to build location. + * At this moment gAgent.getPositionAgent() contains previous coordinates. + * according to EXT-65 agent position is being reseted on each frame. + */ + LLAgentUI::buildLocationString(location, LLAgentUI::LOCATION_FORMAT_NO_MATURITY, + gAgent.getPosAgentFromGlobal(global_agent_pos)); + std::string tooltip (LLSLURL(gAgent.getRegion()->getName(), global_agent_pos).getSLURLString()); + + LLLocationHistoryItem item (location, + global_agent_pos, tooltip,TYPED_REGION_SLURL);// we can add into history only TYPED location + //Touch it, if it is at list already, add new location otherwise + if ( !lh->touchItem(item) ) { + lh->addItem(item); + } + + lh->save(); + + mSaveToLocationHistory = false; + +} + +void LLNavigationBar::onTeleportHistoryChanged() +{ + // Update navigation controls. + LLTeleportHistory* h = LLTeleportHistory::getInstance(); + int cur_item = h->getCurrentItemIndex(); + mBtnBack->setEnabled(cur_item > 0); + mBtnForward->setEnabled(cur_item < ((int)h->getItems().size() - 1)); +} + +void LLNavigationBar::rebuildTeleportHistoryMenu() +{ + // Has the pop-up menu been built? + if (mTeleportHistoryMenu) + { + // Clear it. + mTeleportHistoryMenu->empty(); + } + else + { + // Create it. + LLMenuGL::Params menu_p; + menu_p.name("popup"); + menu_p.can_tear_off(false); + menu_p.visible(false); + menu_p.bg_visible(true); + menu_p.scrollable(true); + mTeleportHistoryMenu = LLUICtrlFactory::create(menu_p); + + addChild(mTeleportHistoryMenu); + } + + // Populate the menu with teleport history items. + LLTeleportHistory* hist = LLTeleportHistory::getInstance(); + const LLTeleportHistory::slurl_list_t& hist_items = hist->getItems(); + int cur_item = hist->getCurrentItemIndex(); + + // Items will be shown in the reverse order, just like in Firefox. + for (int i = (int)hist_items.size()-1; i >= 0; i--) + { + LLTeleportHistoryMenuItem::EType type; + if (i < cur_item) + type = LLTeleportHistoryMenuItem::TYPE_BACKWARD; + else if (i > cur_item) + type = LLTeleportHistoryMenuItem::TYPE_FORWARD; + else + type = LLTeleportHistoryMenuItem::TYPE_CURRENT; + + LLTeleportHistoryMenuItem::Params item_params; + item_params.label = item_params.name = hist_items[i].mTitle; + item_params.item_type = type; + item_params.on_click.function(boost::bind(&LLNavigationBar::onTeleportHistoryMenuItemClicked, this, i)); + LLTeleportHistoryMenuItem* new_itemp = LLUICtrlFactory::create(item_params); + //new_itemp->setFont() + mTeleportHistoryMenu->addChild(new_itemp); + } +} + +void LLNavigationBar::onRegionNameResponse( + std::string typed_location, + std::string region_name, + LLVector3 local_coords, + U64 region_handle, const std::string& url, const LLUUID& snapshot_id, bool teleport) +{ + // Invalid location? + if (region_handle) + { + // Teleport to the location. + LLVector3d region_pos = from_region_handle(region_handle); + LLVector3d global_pos = region_pos + (LLVector3d) local_coords; + + LL_INFOS() << "Teleporting to: " << LLSLURL(region_name, global_pos).getSLURLString() << LL_ENDL; + gAgent.teleportViaLocation(global_pos); + } + else if (gSavedSettings.getBOOL("SearchFromAddressBar")) + { + invokeSearch(typed_location); + } +} + +void LLNavigationBar::showTeleportHistoryMenu(LLUICtrl* btn_ctrl) +{ + // Don't show the popup if teleport history is empty. + if (LLTeleportHistory::getInstance()->isEmpty()) + { + LL_DEBUGS() << "Teleport history is empty, will not show the menu." << LL_ENDL; + return; + } + + rebuildTeleportHistoryMenu(); + + if (mTeleportHistoryMenu == NULL) + return; + + mTeleportHistoryMenu->updateParent(LLMenuGL::sMenuContainer); + const S32 MENU_SPAWN_PAD = -1; + LLMenuGL::showPopup(btn_ctrl, mTeleportHistoryMenu, 0, MENU_SPAWN_PAD); + LLButton* nav_button = dynamic_cast(btn_ctrl); + if(nav_button) + { + if(mHistoryMenuConnection.connected()) + { + LL_WARNS("Navgationbar")<<"mHistoryMenuConnection should be disconnected at this moment."<setMouseUpCallback(boost::bind(&LLNavigationBar::onNavigationButtonHeldUp, this, nav_button)); + // pressed state will be update after mouseUp in onBackOrForwardButtonHeldUp(); + nav_button->setForcePressedState(true); + } + // *HACK pass the mouse capturing to the drop-down menu + // it need to let menu handle mouseup event + gFocusMgr.setMouseCapture(gMenuHolder); +} +/** + * Taking into account the HACK above, this callback-function is responsible for correct handling of mouseUp event in case of holding-down the navigation buttons.. + * We need to process this case separately to update a pressed state of navigation button. + */ +void LLNavigationBar::onNavigationButtonHeldUp(LLButton* nav_button) +{ + if(nav_button) + { + nav_button->setForcePressedState(false); + } + if(gFocusMgr.getMouseCapture() == gMenuHolder) + { + // we had passed mouseCapture in showTeleportHistoryMenu() + // now we MUST release mouseCapture to continue a proper mouseevent workflow. + gFocusMgr.setMouseCapture(NULL); + } + //gMenuHolder is using to display bunch of menus. Disconnect signal to avoid unnecessary calls. + mHistoryMenuConnection.disconnect(); +} + +void LLNavigationBar::handleLoginComplete() +{ + LLTeleportHistory::getInstance()->handleLoginComplete(); + LLPanelTopInfoBar::instance().handleLoginComplete(); + mCmbLocation->handleLoginComplete(); + resizeLayoutPanel(); +} + +void LLNavigationBar::resizeLayoutPanel() +{ + LLRect nav_bar_rect = mNavigationPanel->getRect(); + + S32 nav_panel_width = (nav_bar_rect.getWidth() + mFavoritePanel->getRect().getWidth()) * gSavedPerAccountSettings.getF32("NavigationBarRatio"); + + nav_bar_rect.setLeftTopAndSize(nav_bar_rect.mLeft, nav_bar_rect.mTop, nav_panel_width, nav_bar_rect.getHeight()); + mNavigationPanel->handleReshape(nav_bar_rect,true); +} +void LLNavigationBar::invokeSearch(std::string search_text) +{ + LLFloaterReg::showInstance("search", LLSD().with("category", "standard").with("query", LLSD(search_text))); +} + +void LLNavigationBar::clearHistoryCache() +{ + mCmbLocation->removeall(); + LLLocationHistory* lh = LLLocationHistory::getInstance(); + lh->removeItems(); + lh->save(); + LLTeleportHistory::getInstance()->purgeItems(); +} + +int LLNavigationBar::getDefNavBarHeight() +{ + return mDefaultNbRect.getHeight(); +} +int LLNavigationBar::getDefFavBarHeight() +{ + return mDefaultFpRect.getHeight(); +} + +bool LLNavigationBar::isRebakeNavMeshAvailable() +{ + return mCmbLocation->isNavMeshDirty(); +} diff --git a/indra/newview/llnavigationbar.h b/indra/newview/llnavigationbar.h index b1da552c73..2bb6af24bc 100755 --- a/indra/newview/llnavigationbar.h +++ b/indra/newview/llnavigationbar.h @@ -1,163 +1,163 @@ -/** - * @file llnavigationbar.h - * @brief Navigation bar definition - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLNAVIGATIONBAR_H -#define LL_LLNAVIGATIONBAR_H - -#include "llpanel.h" -#include "llbutton.h" -#include "lllayoutstack.h" -#include "llinitdestroyclass.h" - -class LLLocationInputCtrl; -class LLMenuGL; -class LLSearchEditor; -class LLSearchComboBox; - -/** - * This button is able to handle click-dragging mouse event. - * It has appropriated signal for this event. - * Dragging direction can be set from xml attribute called 'direction' - * - * *TODO: move to llui? - */ - -class LLPullButton: public LLButton -{ - LOG_CLASS(LLPullButton); - -public: - struct Params: public LLInitParam::Block - { - Optional direction; // left, right, down, up - - Params() - : direction("direction", "down") - { - } - }; - - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); - - /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); - - /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask); - - boost::signals2::connection setClickDraggingCallback(const commit_signal_t::slot_type& cb); - -protected: - friend class LLUICtrlFactory; - // convert string name into direction vector - void setDirectionFromName(const std::string& name); - LLPullButton(const LLPullButton::Params& params); - - commit_signal_t mClickDraggingSignal; - LLVector2 mLastMouseDown; - LLVector2 mDraggingDirection; -}; - -/** - * Web browser-like navigation bar. - */ -class LLNavigationBar - : public LLPanel, public LLSingleton, private LLDestroyClass -{ - LLSINGLETON(LLNavigationBar); - virtual ~LLNavigationBar(); - LOG_CLASS(LLNavigationBar); - friend class LLDestroyClass; - -public: - - /*virtual*/ void draw() override; - /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool postBuild() override; - /*virtual*/ void setVisible(bool visible) override; - - void handleLoginComplete(); - void clearHistoryCache(); - - int getDefNavBarHeight(); - int getDefFavBarHeight(); - - bool isRebakeNavMeshAvailable(); - -private: - // the distance between navigation panel and favorites panel in pixels - const static S32 FAVBAR_TOP_PADDING = 10; - - void rebuildTeleportHistoryMenu(); - void showTeleportHistoryMenu(LLUICtrl* btn_ctrl); - void invokeSearch(std::string search_text); - void resizeLayoutPanel(); - // callbacks - void onTeleportHistoryMenuItemClicked(const LLSD& userdata); - void onTeleportHistoryChanged(); - void onBackButtonClicked(); - void onBackOrForwardButtonHeldDown(LLUICtrl* ctrl, const LLSD& param); - void onNavigationButtonHeldUp(LLButton* nav_button); - void onForwardButtonClicked(); - void onHomeButtonClicked(); - void onLandmarksButtonClicked(); - void onLocationSelection(); - void onLocationPrearrange(const LLSD& data); - void onTeleportFinished(const LLVector3d& global_agent_pos); - void onTeleportFailed(); - void onNavbarResized(); - void onRegionNameResponse( - std::string typed_location, - std::string region_name, - LLVector3 local_coords, - U64 region_handle, const std::string& url, - const LLUUID& snapshot_id, bool teleport); - - static void destroyClass() - { - if (LLNavigationBar::instanceExists()) - { - LLNavigationBar::getInstance()->setEnabled(false); - } - } - - S32 mNavPanWidth; - LLMenuGL* mTeleportHistoryMenu; - LLPullButton* mBtnBack; - LLPullButton* mBtnForward; - LLButton* mBtnHome; - LLButton* mBtnLandmarks; - LLLocationInputCtrl* mCmbLocation; - LLRect mDefaultNbRect; - LLRect mDefaultFpRect; - LLLayoutPanel* mNavigationPanel; - LLLayoutPanel* mFavoritePanel; - boost::signals2::connection mTeleportFailedConnection; - boost::signals2::connection mTeleportFinishConnection; - boost::signals2::connection mHistoryMenuConnection; - // if true, save location to location history when teleport finishes - bool mSaveToLocationHistory; -}; - -#endif +/** + * @file llnavigationbar.h + * @brief Navigation bar definition + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLNAVIGATIONBAR_H +#define LL_LLNAVIGATIONBAR_H + +#include "llpanel.h" +#include "llbutton.h" +#include "lllayoutstack.h" +#include "llinitdestroyclass.h" + +class LLLocationInputCtrl; +class LLMenuGL; +class LLSearchEditor; +class LLSearchComboBox; + +/** + * This button is able to handle click-dragging mouse event. + * It has appropriated signal for this event. + * Dragging direction can be set from xml attribute called 'direction' + * + * *TODO: move to llui? + */ + +class LLPullButton: public LLButton +{ + LOG_CLASS(LLPullButton); + +public: + struct Params: public LLInitParam::Block + { + Optional direction; // left, right, down, up + + Params() + : direction("direction", "down") + { + } + }; + + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); + + /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); + + /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask); + + boost::signals2::connection setClickDraggingCallback(const commit_signal_t::slot_type& cb); + +protected: + friend class LLUICtrlFactory; + // convert string name into direction vector + void setDirectionFromName(const std::string& name); + LLPullButton(const LLPullButton::Params& params); + + commit_signal_t mClickDraggingSignal; + LLVector2 mLastMouseDown; + LLVector2 mDraggingDirection; +}; + +/** + * Web browser-like navigation bar. + */ +class LLNavigationBar + : public LLPanel, public LLSingleton, private LLDestroyClass +{ + LLSINGLETON(LLNavigationBar); + virtual ~LLNavigationBar(); + LOG_CLASS(LLNavigationBar); + friend class LLDestroyClass; + +public: + + /*virtual*/ void draw() override; + /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool postBuild() override; + /*virtual*/ void setVisible(bool visible) override; + + void handleLoginComplete(); + void clearHistoryCache(); + + int getDefNavBarHeight(); + int getDefFavBarHeight(); + + bool isRebakeNavMeshAvailable(); + +private: + // the distance between navigation panel and favorites panel in pixels + const static S32 FAVBAR_TOP_PADDING = 10; + + void rebuildTeleportHistoryMenu(); + void showTeleportHistoryMenu(LLUICtrl* btn_ctrl); + void invokeSearch(std::string search_text); + void resizeLayoutPanel(); + // callbacks + void onTeleportHistoryMenuItemClicked(const LLSD& userdata); + void onTeleportHistoryChanged(); + void onBackButtonClicked(); + void onBackOrForwardButtonHeldDown(LLUICtrl* ctrl, const LLSD& param); + void onNavigationButtonHeldUp(LLButton* nav_button); + void onForwardButtonClicked(); + void onHomeButtonClicked(); + void onLandmarksButtonClicked(); + void onLocationSelection(); + void onLocationPrearrange(const LLSD& data); + void onTeleportFinished(const LLVector3d& global_agent_pos); + void onTeleportFailed(); + void onNavbarResized(); + void onRegionNameResponse( + std::string typed_location, + std::string region_name, + LLVector3 local_coords, + U64 region_handle, const std::string& url, + const LLUUID& snapshot_id, bool teleport); + + static void destroyClass() + { + if (LLNavigationBar::instanceExists()) + { + LLNavigationBar::getInstance()->setEnabled(false); + } + } + + S32 mNavPanWidth; + LLMenuGL* mTeleportHistoryMenu; + LLPullButton* mBtnBack; + LLPullButton* mBtnForward; + LLButton* mBtnHome; + LLButton* mBtnLandmarks; + LLLocationInputCtrl* mCmbLocation; + LLRect mDefaultNbRect; + LLRect mDefaultFpRect; + LLLayoutPanel* mNavigationPanel; + LLLayoutPanel* mFavoritePanel; + boost::signals2::connection mTeleportFailedConnection; + boost::signals2::connection mTeleportFinishConnection; + boost::signals2::connection mHistoryMenuConnection; + // if true, save location to location history when teleport finishes + bool mSaveToLocationHistory; +}; + +#endif diff --git a/indra/newview/llnetmap.cpp b/indra/newview/llnetmap.cpp index 6a2299076c..1410232a0f 100644 --- a/indra/newview/llnetmap.cpp +++ b/indra/newview/llnetmap.cpp @@ -1,1246 +1,1246 @@ -/** - * @file llnetmap.cpp - * @author James Cook - * @brief Display of surrounding regions, objects, and agents. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2001-2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llnetmap.h" - -// Library includes (should move below) -#include "indra_constants.h" -#include "llavatarnamecache.h" -#include "llmath.h" -#include "llfloaterreg.h" -#include "llfocusmgr.h" -#include "lllocalcliprect.h" -#include "llrender.h" -#include "llresmgr.h" -#include "llui.h" -#include "lltooltip.h" - -#include "llglheaders.h" - -// Viewer includes -#include "llagent.h" -#include "llagentcamera.h" -#include "llappviewer.h" // for gDisconnected -#include "llcallingcard.h" // LLAvatarTracker -#include "llfloaterland.h" -#include "llfloaterworldmap.h" -#include "llparcel.h" -#include "lltracker.h" -#include "llsurface.h" -#include "llurlmatch.h" -#include "llurlregistry.h" -#include "llviewercamera.h" -#include "llviewercontrol.h" -#include "llviewerparcelmgr.h" -#include "llviewertexture.h" -#include "llviewertexturelist.h" -#include "llviewermenu.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llviewerwindow.h" -#include "llworld.h" -#include "llworldmapview.h" // shared draw code - -static LLDefaultChildRegistry::Register r1("net_map"); - -constexpr F32 LLNetMap::MAP_SCALE_MIN = 32; -constexpr F32 LLNetMap::MAP_SCALE_FAR = 32; -constexpr F32 LLNetMap::MAP_SCALE_MEDIUM = 128; -constexpr F32 LLNetMap::MAP_SCALE_CLOSE = 256; -constexpr F32 LLNetMap::MAP_SCALE_VERY_CLOSE = 1024; -constexpr F32 LLNetMap::MAP_SCALE_MAX = 4096; - -constexpr F32 MAP_SCALE_ZOOM_FACTOR = 1.04f; // Zoom in factor per click of scroll wheel (4%) -constexpr F32 MIN_DOT_RADIUS = 3.5f; -constexpr F32 DOT_SCALE = 0.75f; -constexpr F32 MIN_PICK_SCALE = 2.f; -constexpr S32 MOUSE_DRAG_SLOP = 2; // How far the mouse needs to move before we think it's a drag - -constexpr F64 COARSEUPDATE_MAX_Z = 1020.0f; - -LLNetMap::LLNetMap (const Params & p) -: LLUICtrl (p), - mBackgroundColor (p.bg_color()), - mScale( MAP_SCALE_MEDIUM ), - mPixelsPerMeter( MAP_SCALE_MEDIUM / REGION_WIDTH_METERS ), - mObjectMapTPM(0.f), - mObjectMapPixels(0.f), - mCurPan(0.f, 0.f), - mStartPan(0.f, 0.f), - mPopupWorldPos(0.f, 0.f, 0.f), - mMouseDown(0, 0), - mPanning(false), - mUpdateNow(false), - mObjectImageCenterGlobal( gAgentCamera.getCameraPositionGlobal() ), - mObjectRawImagep(), - mObjectImagep(), - mClosestAgentToCursor(), - mClosestAgentAtLastRightClick(), - mToolTipMsg() -{ - mScale = gSavedSettings.getF32("MiniMapScale"); - if (gAgent.isFirstLogin()) - { - // *HACK: On first run, set this to false for new users, otherwise the - // default is true to maintain consistent experience for existing - // users. - gSavedSettings.setBOOL("MiniMapRotate", false); - } - mPixelsPerMeter = mScale / REGION_WIDTH_METERS; - mDotRadius = llmax(DOT_SCALE * mPixelsPerMeter, MIN_DOT_RADIUS); -} - -LLNetMap::~LLNetMap() -{ - auto menu = static_cast(mPopupMenuHandle.get()); - if (menu) - { - menu->die(); - mPopupMenuHandle.markDead(); - } -} - -bool LLNetMap::postBuild() -{ - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar commitRegistrar; - LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enableRegistrar; - - enableRegistrar.add("Minimap.Zoom.Check", boost::bind(&LLNetMap::isZoomChecked, this, _2)); - commitRegistrar.add("Minimap.Zoom.Set", boost::bind(&LLNetMap::setZoom, this, _2)); - commitRegistrar.add("Minimap.Tracker", boost::bind(&LLNetMap::handleStopTracking, this, _2)); - commitRegistrar.add("Minimap.Center.Activate", boost::bind(&LLNetMap::activateCenterMap, this, _2)); - enableRegistrar.add("Minimap.MapOrientation.Check", boost::bind(&LLNetMap::isMapOrientationChecked, this, _2)); - commitRegistrar.add("Minimap.MapOrientation.Set", boost::bind(&LLNetMap::setMapOrientation, this, _2)); - commitRegistrar.add("Minimap.AboutLand", boost::bind(&LLNetMap::popupShowAboutLand, this, _2)); - - LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile("menu_mini_map.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - mPopupMenuHandle = menu->getHandle(); - menu->setItemEnabled("Re-center map", false); - return true; -} - -void LLNetMap::setScale( F32 scale ) -{ - scale = llclamp(scale, MAP_SCALE_MIN, MAP_SCALE_MAX); - mCurPan *= scale / mScale; - mScale = scale; - - if (mObjectImagep.notNull()) - { - F32 width = (F32)(getRect().getWidth()); - F32 height = (F32)(getRect().getHeight()); - F32 diameter = sqrt(width * width + height * height); - F32 region_widths = diameter / mScale; - F32 meters = region_widths * LLWorld::getInstance()->getRegionWidthInMeters(); - F32 num_pixels = (F32)mObjectImagep->getWidth(); - mObjectMapTPM = num_pixels / meters; - mObjectMapPixels = diameter; - } - - mPixelsPerMeter = mScale / REGION_WIDTH_METERS; - mDotRadius = llmax(DOT_SCALE * mPixelsPerMeter, MIN_DOT_RADIUS); - - gSavedSettings.setF32("MiniMapScale", mScale); - - mUpdateNow = true; -} - - -/////////////////////////////////////////////////////////////////////////////////// - -void LLNetMap::draw() -{ - if (!LLWorld::instanceExists()) - { - return; - } - LL_PROFILE_ZONE_SCOPED; - static LLFrameTimer map_timer; - static LLUIColor map_avatar_color = LLUIColorTable::instance().getColor("MapAvatarColor", LLColor4::white); - static LLUIColor map_avatar_friend_color = LLUIColorTable::instance().getColor("MapAvatarFriendColor", LLColor4::white); - static LLUIColor map_track_color = LLUIColorTable::instance().getColor("MapTrackColor", LLColor4::white); - //static LLUIColor map_track_disabled_color = LLUIColorTable::instance().getColor("MapTrackDisabledColor", LLColor4::white); - static LLUIColor map_frustum_color = LLUIColorTable::instance().getColor("MapFrustumColor", LLColor4::white); - static LLUIColor map_parcel_outline_color = LLUIColorTable::instance().getColor("MapParcelOutlineColor", LLColor4(LLColor3(LLColor4::yellow), 0.5f)); - - if (mObjectImagep.isNull()) - { - createObjectImage(); - } - - static LLUICachedControl auto_center("MiniMapAutoCenter", true); - bool auto_centering = auto_center && !mPanning; - mCentering = mCentering && !mPanning; - - if (auto_centering || mCentering) - { - mCurPan = lerp(mCurPan, LLVector2(0.0f, 0.0f) , LLSmoothInterpolation::getInterpolant(0.1f)); - } - bool centered = abs(mCurPan.mV[VX]) < 0.5f && abs(mCurPan.mV[VY]) < 0.5f; - if (centered) - { - mCurPan.mV[0] = 0.0f; - mCurPan.mV[1] = 0.0f; - mCentering = false; - } - - auto menu = static_cast(mPopupMenuHandle.get()); - if (menu) - { - bool can_recenter_map = !(centered || mCentering || auto_centering); - menu->setItemEnabled("Re-center map", can_recenter_map); - } - updateAboutLandPopupButton(); - - // Prepare a scissor region - F32 rotation = 0; - - gGL.pushMatrix(); - gGL.pushUIMatrix(); - - LLVector3 offset = gGL.getUITranslation(); - LLVector3 scale = gGL.getUIScale(); - - gGL.loadIdentity(); - gGL.loadUIIdentity(); - - gGL.scalef(scale.mV[0], scale.mV[1], scale.mV[2]); - gGL.translatef(offset.mV[0], offset.mV[1], offset.mV[2]); - - { - LLLocalClipRect clip(getLocalRect()); - { - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - gGL.matrixMode(LLRender::MM_MODELVIEW); - - // Draw background rectangle - LLColor4 background_color = mBackgroundColor.get(); - gGL.color4fv( background_color.mV ); - gl_rect_2d(0, getRect().getHeight(), getRect().getWidth(), 0); - } - - // region 0,0 is in the middle - S32 center_sw_left = getRect().getWidth() / 2 + llfloor(mCurPan.mV[VX]); - S32 center_sw_bottom = getRect().getHeight() / 2 + llfloor(mCurPan.mV[VY]); - - gGL.pushMatrix(); - - gGL.translatef( (F32) center_sw_left, (F32) center_sw_bottom, 0.f); - - static LLUICachedControl rotate_map("MiniMapRotate", true); - if( rotate_map ) - { - // rotate subsequent draws to agent rotation - rotation = atan2( LLViewerCamera::getInstance()->getAtAxis().mV[VX], LLViewerCamera::getInstance()->getAtAxis().mV[VY] ); - gGL.rotatef( rotation * RAD_TO_DEG, 0.f, 0.f, 1.f); - } - - // figure out where agent is - const S32 region_width = ll_round(LLWorld::getInstance()->getRegionWidthInMeters()); - const F32 scale_pixels_per_meter = mScale / region_width; - - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) - { - LLViewerRegion* regionp = *iter; - // Find x and y position relative to camera's center. - LLVector3 origin_agent = regionp->getOriginAgent(); - LLVector3 rel_region_pos = origin_agent - gAgentCamera.getCameraPositionAgent(); - F32 relative_x = rel_region_pos.mV[0] * scale_pixels_per_meter; - F32 relative_y = rel_region_pos.mV[1] * scale_pixels_per_meter; - - // background region rectangle - F32 bottom = relative_y; - F32 left = relative_x; - F32 top = bottom + mScale ; - F32 right = left + mScale ; - - if (regionp == gAgent.getRegion()) - { - gGL.color4f(1.f, 1.f, 1.f, 1.f); - } - else - { - gGL.color4f(0.8f, 0.8f, 0.8f, 1.f); - } - - if (!regionp->isAlive()) - { - gGL.color4f(1.f, 0.5f, 0.5f, 1.f); - } - - - - // Draw using texture. - gGL.getTexUnit(0)->bind(regionp->getLand().getSTexture()); - gGL.begin(LLRender::QUADS); - gGL.texCoord2f(0.f, 1.f); - gGL.vertex2f(left, top); - gGL.texCoord2f(0.f, 0.f); - gGL.vertex2f(left, bottom); - gGL.texCoord2f(1.f, 0.f); - gGL.vertex2f(right, bottom); - gGL.texCoord2f(1.f, 1.f); - gGL.vertex2f(right, top); - gGL.end(); - - // Draw water - gGL.flush(); - { - if (regionp->getLand().getWaterTexture()) - { - gGL.getTexUnit(0)->bind(regionp->getLand().getWaterTexture()); - gGL.begin(LLRender::QUADS); - gGL.texCoord2f(0.f, 1.f); - gGL.vertex2f(left, top); - gGL.texCoord2f(0.f, 0.f); - gGL.vertex2f(left, bottom); - gGL.texCoord2f(1.f, 0.f); - gGL.vertex2f(right, bottom); - gGL.texCoord2f(1.f, 1.f); - gGL.vertex2f(right, top); - gGL.end(); - } - } - gGL.flush(); - } - - // Redraw object layer periodically - if (mUpdateNow || (map_timer.getElapsedTimeF32() > 0.5f)) - { - mUpdateNow = false; - - // Locate the centre of the object layer, accounting for panning - LLVector3 new_center = globalPosToView(gAgentCamera.getCameraPositionGlobal()); - new_center.mV[VX] -= mCurPan.mV[VX]; - new_center.mV[VY] -= mCurPan.mV[VY]; - new_center.mV[VZ] = 0.f; - mObjectImageCenterGlobal = viewPosToGlobal(llfloor(new_center.mV[VX]), llfloor(new_center.mV[VY])); - - // Create the base texture. - LLImageDataLock lock(mObjectRawImagep); - U8 *default_texture = mObjectRawImagep->getData(); - memset( default_texture, 0, mObjectImagep->getWidth() * mObjectImagep->getHeight() * mObjectImagep->getComponents() ); - - // Draw objects - gObjectList.renderObjectsForMap(*this); - - mObjectImagep->setSubImage(mObjectRawImagep, 0, 0, mObjectImagep->getWidth(), mObjectImagep->getHeight()); - - map_timer.reset(); - } - - LLVector3 map_center_agent = gAgent.getPosAgentFromGlobal(mObjectImageCenterGlobal); - LLVector3 camera_position = gAgentCamera.getCameraPositionAgent(); - map_center_agent -= camera_position; - map_center_agent.mV[VX] *= scale_pixels_per_meter; - map_center_agent.mV[VY] *= scale_pixels_per_meter; - - gGL.getTexUnit(0)->bind(mObjectImagep); - F32 image_half_width = 0.5f*mObjectMapPixels; - F32 image_half_height = 0.5f*mObjectMapPixels; - - gGL.begin(LLRender::QUADS); - gGL.texCoord2f(0.f, 1.f); - gGL.vertex2f(map_center_agent.mV[VX] - image_half_width, image_half_height + map_center_agent.mV[VY]); - gGL.texCoord2f(0.f, 0.f); - gGL.vertex2f(map_center_agent.mV[VX] - image_half_width, map_center_agent.mV[VY] - image_half_height); - gGL.texCoord2f(1.f, 0.f); - gGL.vertex2f(image_half_width + map_center_agent.mV[VX], map_center_agent.mV[VY] - image_half_height); - gGL.texCoord2f(1.f, 1.f); - gGL.vertex2f(image_half_width + map_center_agent.mV[VX], image_half_height + map_center_agent.mV[VY]); - gGL.end(); - - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) - { - LLViewerRegion* regionp = *iter; - regionp->renderPropertyLinesOnMinimap(scale_pixels_per_meter, map_parcel_outline_color.get().mV); - } - - gGL.popMatrix(); - - // Mouse pointer in local coordinates - S32 local_mouse_x; - S32 local_mouse_y; - //localMouse(&local_mouse_x, &local_mouse_y); - LLUI::getInstance()->getMousePositionLocal(this, &local_mouse_x, &local_mouse_y); - mClosestAgentToCursor.setNull(); - F32 closest_dist_squared = F32_MAX; // value will be overridden in the loop - F32 min_pick_dist_squared = (mDotRadius * MIN_PICK_SCALE) * (mDotRadius * MIN_PICK_SCALE); - - LLVector3 pos_map; - uuid_vec_t avatar_ids; - std::vector positions; - bool unknown_relative_z; - - LLWorld::getInstance()->getAvatars(&avatar_ids, &positions, gAgentCamera.getCameraPositionGlobal()); - - // Draw avatars - for (U32 i = 0; i < avatar_ids.size(); i++) - { - LLUUID uuid = avatar_ids[i]; - // Skip self, we'll draw it later - if (uuid == gAgent.getID()) continue; - - pos_map = globalPosToView(positions[i]); - - bool show_as_friend = (LLAvatarTracker::instance().getBuddyInfo(uuid) != NULL); - - LLColor4 color = show_as_friend ? map_avatar_friend_color : map_avatar_color; - - unknown_relative_z = positions[i].mdV[VZ] >= COARSEUPDATE_MAX_Z && - camera_position.mV[VZ] >= COARSEUPDATE_MAX_Z; - - LLWorldMapView::drawAvatar( - pos_map.mV[VX], pos_map.mV[VY], - color, - pos_map.mV[VZ], mDotRadius, - unknown_relative_z); - - if(uuid.notNull()) - { - bool selected = false; - uuid_vec_t::iterator sel_iter = gmSelected.begin(); - for (; sel_iter != gmSelected.end(); sel_iter++) - { - if(*sel_iter == uuid) - { - selected = true; - break; - } - } - if(selected) - { - if( (pos_map.mV[VX] < 0) || - (pos_map.mV[VY] < 0) || - (pos_map.mV[VX] >= getRect().getWidth()) || - (pos_map.mV[VY] >= getRect().getHeight()) ) - { - S32 x = ll_round( pos_map.mV[VX] ); - S32 y = ll_round( pos_map.mV[VY] ); - LLWorldMapView::drawTrackingCircle( getRect(), x, y, color, 1, 10); - } else - { - LLWorldMapView::drawTrackingDot(pos_map.mV[VX],pos_map.mV[VY],color,0.f); - } - } - } - - F32 dist_to_cursor_squared = dist_vec_squared(LLVector2(pos_map.mV[VX], pos_map.mV[VY]), - LLVector2(local_mouse_x,local_mouse_y)); - if(dist_to_cursor_squared < min_pick_dist_squared && dist_to_cursor_squared < closest_dist_squared) - { - closest_dist_squared = dist_to_cursor_squared; - mClosestAgentToCursor = uuid; - } - } - - // Draw dot for autopilot target - if (gAgent.getAutoPilot()) - { - drawTracking( gAgent.getAutoPilotTargetGlobal(), map_track_color ); - } - else - { - LLTracker::ETrackingStatus tracking_status = LLTracker::getTrackingStatus(); - if ( LLTracker::TRACKING_AVATAR == tracking_status ) - { - drawTracking( LLAvatarTracker::instance().getGlobalPos(), map_track_color ); - } - else if ( LLTracker::TRACKING_LANDMARK == tracking_status - || LLTracker::TRACKING_LOCATION == tracking_status ) - { - drawTracking( LLTracker::getTrackedPositionGlobal(), map_track_color ); - } - } - - // Draw dot for self avatar position - LLVector3d pos_global = gAgent.getPositionGlobal(); - pos_map = globalPosToView(pos_global); - S32 dot_width = ll_round(mDotRadius * 2.f); - LLUIImagePtr you = LLWorldMapView::sAvatarYouLargeImage; - if (you) - { - you->draw(ll_round(pos_map.mV[VX] - mDotRadius), - ll_round(pos_map.mV[VY] - mDotRadius), - dot_width, - dot_width); - - F32 dist_to_cursor_squared = dist_vec_squared(LLVector2(pos_map.mV[VX], pos_map.mV[VY]), - LLVector2(local_mouse_x,local_mouse_y)); - if(dist_to_cursor_squared < min_pick_dist_squared && dist_to_cursor_squared < closest_dist_squared) - { - mClosestAgentToCursor = gAgent.getID(); - } - } - - // Draw frustum - F32 meters_to_pixels = mScale/ LLWorld::getInstance()->getRegionWidthInMeters(); - - F32 horiz_fov = LLViewerCamera::getInstance()->getView() * LLViewerCamera::getInstance()->getAspect(); - F32 far_clip_meters = LLViewerCamera::getInstance()->getFar(); - F32 far_clip_pixels = far_clip_meters * meters_to_pixels; - - F32 ctr_x = (F32)center_sw_left; - F32 ctr_y = (F32)center_sw_bottom; - - const F32 steps_per_circle = 40.0f; - const F32 steps_per_radian = steps_per_circle / F_TWO_PI; - const F32 arc_start = -(horiz_fov / 2.0f) + F_PI_BY_TWO; - const F32 arc_end = (horiz_fov / 2.0f) + F_PI_BY_TWO; - const S32 steps = llmax(1, (S32)((horiz_fov * steps_per_radian) + 0.5f)); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - if( rotate_map ) - { - gGL.pushMatrix(); - gGL.translatef( ctr_x, ctr_y, 0 ); - gl_washer_segment_2d(far_clip_pixels, 0, arc_start, arc_end, steps, map_frustum_color(), map_frustum_color()); - gGL.popMatrix(); - } - else - { - gGL.pushMatrix(); - gGL.translatef( ctr_x, ctr_y, 0 ); - // If we don't rotate the map, we have to rotate the frustum. - gGL.rotatef( atan2( LLViewerCamera::getInstance()->getAtAxis().mV[VX], LLViewerCamera::getInstance()->getAtAxis().mV[VY] ) * RAD_TO_DEG, 0.f, 0.f, -1.f); - gl_washer_segment_2d(far_clip_pixels, 0, arc_start, arc_end, steps, map_frustum_color(), map_frustum_color()); - gGL.popMatrix(); - } - } - - gGL.popMatrix(); - gGL.popUIMatrix(); - - LLUICtrl::draw(); -} - -void LLNetMap::reshape(S32 width, S32 height, bool called_from_parent) -{ - LLUICtrl::reshape(width, height, called_from_parent); - createObjectImage(); -} - -LLVector3 LLNetMap::globalPosToView(const LLVector3d& global_pos) -{ - LLVector3d camera_position = gAgentCamera.getCameraPositionGlobal(); - - LLVector3d relative_pos_global = global_pos - camera_position; - LLVector3 pos_local; - pos_local.setVec(relative_pos_global); // convert to floats from doubles - - pos_local.mV[VX] *= mPixelsPerMeter; - pos_local.mV[VY] *= mPixelsPerMeter; - // leave Z component in meters - - static LLUICachedControl rotate_map("MiniMapRotate", true); - if( rotate_map ) - { - F32 radians = atan2( LLViewerCamera::getInstance()->getAtAxis().mV[VX], LLViewerCamera::getInstance()->getAtAxis().mV[VY] ); - LLQuaternion rot(radians, LLVector3(0.f, 0.f, 1.f)); - pos_local.rotVec( rot ); - } - - pos_local.mV[VX] += getRect().getWidth() / 2 + mCurPan.mV[VX]; - pos_local.mV[VY] += getRect().getHeight() / 2 + mCurPan.mV[VY]; - - return pos_local; -} - -void LLNetMap::drawTracking(const LLVector3d& pos_global, const LLColor4& color, - bool draw_arrow ) -{ - LLVector3 pos_local = globalPosToView(pos_global); - if( (pos_local.mV[VX] < 0) || - (pos_local.mV[VY] < 0) || - (pos_local.mV[VX] >= getRect().getWidth()) || - (pos_local.mV[VY] >= getRect().getHeight()) ) - { - if (draw_arrow) - { - S32 x = ll_round( pos_local.mV[VX] ); - S32 y = ll_round( pos_local.mV[VY] ); - LLWorldMapView::drawTrackingCircle( getRect(), x, y, color, 1, 10 ); - LLWorldMapView::drawTrackingArrow( getRect(), x, y, color ); - } - } - else - { - LLWorldMapView::drawTrackingDot(pos_local.mV[VX], - pos_local.mV[VY], - color, - pos_local.mV[VZ]); - } -} - -bool LLNetMap::isMouseOnPopupMenu() -{ - auto menu = static_cast(mPopupMenuHandle.get()); - if (!menu || !menu->isOpen()) - { - return false; - } - - S32 popup_x; - S32 popup_y; - LLUI::getInstance()->getMousePositionLocal(menu, &popup_x, &popup_y); - // *NOTE: Tolerance is larger than it needs to be because the context menu is offset from the mouse when the menu is opened from certain - // directions. This may be a quirk of LLMenuGL::showPopup. -Cosmic,2022-03-22 - constexpr S32 tolerance = 10; - // Test tolerance from all four corners, as the popup menu can appear from a different direction if there's not enough space. - // Assume the size of the popup menu is much larger than the provided tolerance. - // In practice, this is a [tolerance]px margin around the popup menu. - for (S32 sign_x = -1; sign_x <= 1; sign_x += 2) - { - for (S32 sign_y = -1; sign_y <= 1; sign_y += 2) - { - if (menu->pointInView(popup_x + (sign_x * tolerance), popup_y + (sign_y * tolerance))) - { - return true; - } - } - } - return false; -} - -void LLNetMap::updateAboutLandPopupButton() -{ - auto menu = static_cast(mPopupMenuHandle.get()); - if (!menu || !menu->isOpen()) - { - return; - } - - LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosGlobal(mPopupWorldPos); - if (!region) - { - menu->setItemEnabled("About Land", false); - } - else - { - // Check if the mouse is in the bounds of the popup. If so, it's safe to assume no other hover function will be called, so the hover - // parcel can be used to check if location-sensitive tooltip options are available. - if (isMouseOnPopupMenu()) - { - LLViewerParcelMgr::getInstance()->setHoverParcel(mPopupWorldPos); - LLParcel *hover_parcel = LLViewerParcelMgr::getInstance()->getHoverParcel(); - bool valid_parcel = false; - if (hover_parcel) - { - valid_parcel = hover_parcel->getOwnerID().notNull(); - } - menu->setItemEnabled("About Land", valid_parcel); - } - } -} - -LLVector3d LLNetMap::viewPosToGlobal( S32 x, S32 y ) -{ - x -= ll_round(getRect().getWidth() / 2 + mCurPan.mV[VX]); - y -= ll_round(getRect().getHeight() / 2 + mCurPan.mV[VY]); - - LLVector3 pos_local( (F32)x, (F32)y, 0 ); - - F32 radians = - atan2( LLViewerCamera::getInstance()->getAtAxis().mV[VX], LLViewerCamera::getInstance()->getAtAxis().mV[VY] ); - - static LLUICachedControl rotate_map("MiniMapRotate", true); - if( rotate_map ) - { - LLQuaternion rot(radians, LLVector3(0.f, 0.f, 1.f)); - pos_local.rotVec( rot ); - } - - pos_local *= ( LLWorld::getInstance()->getRegionWidthInMeters() / mScale ); - - LLVector3d pos_global; - pos_global.setVec( pos_local ); - pos_global += gAgentCamera.getCameraPositionGlobal(); - - return pos_global; -} - -bool LLNetMap::handleScrollWheel(S32 x, S32 y, S32 clicks) -{ - // note that clicks are reversed from what you'd think: i.e. > 0 means zoom out, < 0 means zoom in - F32 new_scale = mScale * pow(MAP_SCALE_ZOOM_FACTOR, -clicks); - F32 old_scale = mScale; - - setScale(new_scale); - - static LLUICachedControl auto_center("MiniMapAutoCenter", true); - if (!auto_center) - { - // Adjust pan to center the zoom on the mouse pointer - LLVector2 zoom_offset; - zoom_offset.mV[VX] = x - getRect().getWidth() / 2; - zoom_offset.mV[VY] = y - getRect().getHeight() / 2; - mCurPan -= zoom_offset * mScale / old_scale - zoom_offset; - } - - return true; -} - -bool LLNetMap::handleToolTip(S32 x, S32 y, MASK mask) -{ - if (gDisconnected) - { - return false; - } - - // If the cursor is near an avatar on the minimap, a mini-inspector will be - // shown for the avatar, instead of the normal map tooltip. - if (handleToolTipAgent(mClosestAgentToCursor)) - { - return true; - } - - // The popup menu uses the hover parcel when it is open and the mouse is on - // top of it, with some additional tolerance. Returning early here prevents - // fighting over that hover parcel when getting tooltip info in the - // tolerance region. - if (isMouseOnPopupMenu()) - { - return false; - } - - LLRect sticky_rect; - S32 SLOP = 4; - localPointToScreen(x - SLOP, y - SLOP, &(sticky_rect.mLeft), &(sticky_rect.mBottom)); - sticky_rect.mRight = sticky_rect.mLeft + 2 * SLOP; - sticky_rect.mTop = sticky_rect.mBottom + 2 * SLOP; - - std::string parcel_name_msg; - std::string parcel_sale_price_msg; - std::string parcel_sale_area_msg; - std::string parcel_owner_msg; - std::string region_name_msg; - - LLVector3d posGlobal = viewPosToGlobal(x, y); - LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosGlobal(posGlobal); - if (region) - { - std::string region_name = region->getName(); - if (!region_name.empty()) - { - region_name_msg = mRegionNameMsg; - LLStringUtil::format(region_name_msg, {{"[REGION_NAME]", region_name}}); - } - - // Only show parcel information in the tooltip if property lines are visible. Otherwise, the parcel the tooltip is referring to is - // ambiguous. - if (gSavedSettings.getBOOL("MiniMapShowPropertyLines")) - { - LLViewerParcelMgr::getInstance()->setHoverParcel(posGlobal); - LLParcel *hover_parcel = LLViewerParcelMgr::getInstance()->getHoverParcel(); - if (hover_parcel) - { - std::string parcel_name = hover_parcel->getName(); - if (!parcel_name.empty()) - { - parcel_name_msg = mParcelNameMsg; - LLStringUtil::format(parcel_name_msg, {{"[PARCEL_NAME]", parcel_name}}); - } - - const LLUUID parcel_owner = hover_parcel->getOwnerID(); - std::string parcel_owner_name_url = LLSLURL("agent", parcel_owner, "inspect").getSLURLString(); - static LLUrlMatch parcel_owner_name_url_match; - LLUrlRegistry::getInstance()->findUrl(parcel_owner_name_url, parcel_owner_name_url_match); - if (!parcel_owner_name_url_match.empty()) - { - parcel_owner_msg = mParcelOwnerMsg; - std::string parcel_owner_name = parcel_owner_name_url_match.getLabel(); - LLStringUtil::format(parcel_owner_msg, {{"[PARCEL_OWNER]", parcel_owner_name}}); - } - - if (hover_parcel->getForSale()) - { - const LLUUID auth_buyer_id = hover_parcel->getAuthorizedBuyerID(); - const LLUUID agent_id = gAgent.getID(); - bool show_for_sale = auth_buyer_id.isNull() || auth_buyer_id == agent_id || parcel_owner == agent_id; - if (show_for_sale) - { - S32 price = hover_parcel->getSalePrice(); - S32 area = hover_parcel->getArea(); - F32 cost_per_sqm = 0.0f; - if (area > 0) - { - cost_per_sqm = F32(price) / area; - } - std::string formatted_price = LLResMgr::getInstance()->getMonetaryString(price); - std::string formatted_cost_per_meter = llformat("%.1f", cost_per_sqm); - parcel_sale_price_msg = mParcelSalePriceMsg; - LLStringUtil::format(parcel_sale_price_msg, - {{"[PRICE]", formatted_price}, {"[PRICE_PER_SQM]", formatted_cost_per_meter}}); - std::string formatted_area = llformat("%d", area); - parcel_sale_area_msg = mParcelSaleAreaMsg; - LLStringUtil::format(parcel_sale_area_msg, {{"[AREA]", formatted_area}}); - } - } - } - } - } - - std::string tool_tip_hint_msg; - if (gSavedSettings.getBOOL("DoubleClickTeleport")) - { - tool_tip_hint_msg = mAltToolTipHintMsg; - } - else if (gSavedSettings.getBOOL("DoubleClickShowWorldMap")) - { - tool_tip_hint_msg = mToolTipHintMsg; - } - - LLStringUtil::format_map_t args; - args["[PARCEL_NAME_MSG]"] = parcel_name_msg.empty() ? "" : parcel_name_msg + '\n'; - args["[PARCEL_SALE_PRICE_MSG]"] = parcel_sale_price_msg.empty() ? "" : parcel_sale_price_msg + '\n'; - args["[PARCEL_SALE_AREA_MSG]"] = parcel_sale_area_msg.empty() ? "" : parcel_sale_area_msg + '\n'; - args["[PARCEL_OWNER_MSG]"] = parcel_owner_msg.empty() ? "" : parcel_owner_msg + '\n'; - args["[REGION_NAME_MSG]"] = region_name_msg.empty() ? "" : region_name_msg + '\n'; - args["[TOOL_TIP_HINT_MSG]"] = tool_tip_hint_msg.empty() ? "" : tool_tip_hint_msg + '\n'; - - std::string msg = mToolTipMsg; - LLStringUtil::format(msg, args); - if (msg.back() == '\n') - { - msg.resize(msg.size() - 1); - } - LLToolTipMgr::instance().show(LLToolTip::Params().message(msg).sticky_rect(sticky_rect)); - - return true; -} - -bool LLNetMap::handleToolTipAgent(const LLUUID& avatar_id) -{ - LLAvatarName av_name; - if (avatar_id.isNull() || !LLAvatarNameCache::get(avatar_id, &av_name)) - { - return false; - } - - // only show tooltip if same inspector not already open - LLFloater* existing_inspector = LLFloaterReg::findInstance("inspect_avatar"); - if (!existing_inspector - || !existing_inspector->getVisible() - || existing_inspector->getKey()["avatar_id"].asUUID() != avatar_id) - { - LLInspector::Params p; - p.fillFrom(LLUICtrlFactory::instance().getDefaultParams()); - p.message(av_name.getCompleteName()); - p.image.name("Inspector_I"); - p.click_callback(boost::bind(showAvatarInspector, avatar_id)); - p.visible_time_near(6.f); - p.visible_time_far(3.f); - p.delay_time(0.35f); - p.wrap(false); - - LLToolTipMgr::instance().show(p); - } - return true; -} - -// static -void LLNetMap::showAvatarInspector(const LLUUID& avatar_id) -{ - LLSD params; - params["avatar_id"] = avatar_id; - - if (LLToolTipMgr::instance().toolTipVisible()) - { - LLRect rect = LLToolTipMgr::instance().getToolTipRect(); - params["pos"]["x"] = rect.mLeft; - params["pos"]["y"] = rect.mTop; - } - - LLFloaterReg::showInstance("inspect_avatar", params); -} - -void LLNetMap::renderScaledPointGlobal( const LLVector3d& pos, const LLColor4U &color, F32 radius_meters ) -{ - LLVector3 local_pos; - local_pos.setVec( pos - mObjectImageCenterGlobal ); - - S32 diameter_pixels = ll_round(2 * radius_meters * mObjectMapTPM); - renderPoint( local_pos, color, diameter_pixels ); -} - - -void LLNetMap::renderPoint(const LLVector3 &pos_local, const LLColor4U &color, - S32 diameter, S32 relative_height) -{ - if (diameter <= 0) - { - return; - } - - const S32 image_width = (S32)mObjectImagep->getWidth(); - const S32 image_height = (S32)mObjectImagep->getHeight(); - - S32 x_offset = ll_round(pos_local.mV[VX] * mObjectMapTPM + image_width / 2); - S32 y_offset = ll_round(pos_local.mV[VY] * mObjectMapTPM + image_height / 2); - - if ((x_offset < 0) || (x_offset >= image_width)) - { - return; - } - if ((y_offset < 0) || (y_offset >= image_height)) - { - return; - } - - LLImageDataLock lock(mObjectRawImagep); - U8 *datap = mObjectRawImagep->getData(); - - S32 neg_radius = diameter / 2; - S32 pos_radius = diameter - neg_radius; - S32 x, y; - - if (relative_height > 0) - { - // ...point above agent - S32 px, py; - - // vertical line - px = x_offset; - for (y = -neg_radius; y < pos_radius; y++) - { - py = y_offset + y; - if ((py < 0) || (py >= image_height)) - { - continue; - } - S32 offset = px + py * image_width; - ((U32*)datap)[offset] = color.asRGBA(); - } - - // top line - py = y_offset + pos_radius - 1; - for (x = -neg_radius; x < pos_radius; x++) - { - px = x_offset + x; - if ((px < 0) || (px >= image_width)) - { - continue; - } - S32 offset = px + py * image_width; - ((U32*)datap)[offset] = color.asRGBA(); - } - } - else - { - // ...point level with agent - for (x = -neg_radius; x < pos_radius; x++) - { - S32 p_x = x_offset + x; - if ((p_x < 0) || (p_x >= image_width)) - { - continue; - } - - for (y = -neg_radius; y < pos_radius; y++) - { - S32 p_y = y_offset + y; - if ((p_y < 0) || (p_y >= image_height)) - { - continue; - } - S32 offset = p_x + p_y * image_width; - ((U32*)datap)[offset] = color.asRGBA(); - } - } - } -} - -void LLNetMap::createObjectImage() -{ - // Find the size of the side of a square that surrounds the circle that surrounds getRect(). - // ... which is, the diagonal of the rect. - F32 width = (F32)getRect().getWidth(); - F32 height = (F32)getRect().getHeight(); - S32 square_size = ll_round( sqrt(width*width + height*height) ); - - // Find the least power of two >= the minimum size. - const S32 MIN_SIZE = 64; - const S32 MAX_SIZE = 256; - S32 img_size = MIN_SIZE; - while( (img_size*2 < square_size ) && (img_size < MAX_SIZE) ) - { - img_size <<= 1; - } - - if( mObjectImagep.isNull() || - (mObjectImagep->getWidth() != img_size) || - (mObjectImagep->getHeight() != img_size) ) - { - mObjectRawImagep = new LLImageRaw(img_size, img_size, 4); - U8* data = mObjectRawImagep->getData(); - memset( data, 0, img_size * img_size * 4 ); - mObjectImagep = LLViewerTextureManager::getLocalTexture( mObjectRawImagep.get(), false); - } - setScale(mScale); - mUpdateNow = true; -} - -bool LLNetMap::handleMouseDown(S32 x, S32 y, MASK mask) -{ - // Start panning - gFocusMgr.setMouseCapture(this); - - mStartPan = mCurPan; - mMouseDown.mX = x; - mMouseDown.mY = y; - return true; -} - -bool LLNetMap::handleMouseUp(S32 x, S32 y, MASK mask) -{ - if (abs(mMouseDown.mX - x) < 3 && abs(mMouseDown.mY - y) < 3) - { - handleClick(x, y, mask); - } - - if (hasMouseCapture()) - { - if (mPanning) - { - // restore mouse cursor - S32 local_x, local_y; - local_x = mMouseDown.mX + llfloor(mCurPan.mV[VX] - mStartPan.mV[VX]); - local_y = mMouseDown.mY + llfloor(mCurPan.mV[VY] - mStartPan.mV[VY]); - LLRect clip_rect = getRect(); - clip_rect.stretch(-8); - clip_rect.clipPointToRect(mMouseDown.mX, mMouseDown.mY, local_x, local_y); - LLUI::getInstance()->setMousePositionLocal(this, local_x, local_y); - - // finish the pan - mPanning = false; - - mMouseDown.set(0, 0); - } - gViewerWindow->showCursor(); - gFocusMgr.setMouseCapture(NULL); - return true; - } - - return false; -} - -bool LLNetMap::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - auto menu = static_cast(mPopupMenuHandle.get()); - if (menu) - { - mPopupWorldPos = viewPosToGlobal(x, y); - menu->buildDrawLabels(); - menu->updateParent(LLMenuGL::sMenuContainer); - menu->setItemEnabled("Stop Tracking", LLTracker::isTracking(0)); - LLMenuGL::showPopup(this, menu, x, y); - } - return true; -} - -bool LLNetMap::handleClick(S32 x, S32 y, MASK mask) -{ - // TODO: allow clicking an avatar on minimap to select avatar in the nearby avatar list - // if(mClosestAgentToCursor.notNull()) - // mNearbyList->selectUser(mClosestAgentToCursor); - // Needs a registered observer i guess to accomplish this without using - // globals to tell the mNearbyList in llpeoplepanel to select the user - return true; -} - -bool LLNetMap::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - LLVector3d pos_global = viewPosToGlobal(x, y); - - bool double_click_teleport = gSavedSettings.getBOOL("DoubleClickTeleport"); - bool double_click_show_world_map = gSavedSettings.getBOOL("DoubleClickShowWorldMap"); - - if (double_click_teleport || double_click_show_world_map) - { - // If we're not tracking a beacon already, double-click will set one - if (!LLTracker::isTracking(NULL)) - { - LLFloaterWorldMap* world_map = LLFloaterWorldMap::getInstance(); - if (world_map) - { - world_map->trackLocation(pos_global); - } - } - } - - if (double_click_teleport) - { - // If DoubleClickTeleport is on, double clicking the minimap will teleport there - gAgent.teleportViaLocationLookAt(pos_global); - } - else if (double_click_show_world_map) - { - LLFloaterReg::showInstance("world_map"); - } - return true; -} - -F32 LLNetMap::getScaleForName(std::string scale_name) -{ - if (scale_name == "very close") - { - return LLNetMap::MAP_SCALE_VERY_CLOSE; - } - else if (scale_name == "close") - { - return LLNetMap::MAP_SCALE_CLOSE; - } - else if (scale_name == "medium") - { - return LLNetMap::MAP_SCALE_MEDIUM; - } - else if (scale_name == "far") - { - return LLNetMap::MAP_SCALE_FAR; - } - return 0.0f; -} - -// static -bool LLNetMap::outsideSlop( S32 x, S32 y, S32 start_x, S32 start_y, S32 slop ) -{ - S32 dx = x - start_x; - S32 dy = y - start_y; - - return (dx <= -slop || slop <= dx || dy <= -slop || slop <= dy); -} - -bool LLNetMap::handleHover( S32 x, S32 y, MASK mask ) -{ - if (hasMouseCapture()) - { - if (mPanning || outsideSlop(x, y, mMouseDown.mX, mMouseDown.mY, MOUSE_DRAG_SLOP)) - { - if (!mPanning) - { - // Just started panning. Hide cursor. - mPanning = true; - gViewerWindow->hideCursor(); - } - - LLVector2 delta(static_cast(gViewerWindow->getCurrentMouseDX()), - static_cast(gViewerWindow->getCurrentMouseDY())); - - // Set pan to value at start of drag + offset - mCurPan += delta; - - gViewerWindow->moveCursorToCenter(); - } - } - - if (mask & MASK_SHIFT) - { - // If shift is held, change the cursor to hint that the map can be - // dragged. However, holding shift is not required to drag the map. - gViewerWindow->setCursor( UI_CURSOR_TOOLPAN ); - } - else - { - gViewerWindow->setCursor( UI_CURSOR_CROSS ); - } - - return true; -} - -bool LLNetMap::isZoomChecked(const LLSD &userdata) -{ - std::string level = userdata.asString(); - F32 scale = getScaleForName(level); - return scale == mScale; -} - -void LLNetMap::setZoom(const LLSD &userdata) -{ - std::string level = userdata.asString(); - F32 scale = getScaleForName(level); - if (scale != 0.0f) - { - setScale(scale); - } -} - -void LLNetMap::handleStopTracking (const LLSD& userdata) -{ - auto menu = static_cast(mPopupMenuHandle.get()); - if (menu) - { - menu->setItemEnabled ("Stop Tracking", false); - LLTracker::stopTracking (LLTracker::isTracking(NULL)); - } -} - -void LLNetMap::activateCenterMap(const LLSD &userdata) { mCentering = true; } - -bool LLNetMap::isMapOrientationChecked(const LLSD &userdata) -{ - const std::string command_name = userdata.asString(); - const bool rotate_map = gSavedSettings.getBOOL("MiniMapRotate"); - if (command_name == "north_at_top") - { - return !rotate_map; - } - - if (command_name == "camera_at_top") - { - return rotate_map; - } - - return false; -} - -void LLNetMap::setMapOrientation(const LLSD &userdata) -{ - const std::string command_name = userdata.asString(); - if (command_name == "north_at_top") - { - gSavedSettings.setBOOL("MiniMapRotate", false); - } - else if (command_name == "camera_at_top") - { - gSavedSettings.setBOOL("MiniMapRotate", true); - } -} - -void LLNetMap::popupShowAboutLand(const LLSD &userdata) -{ - // Update parcel selection. It's important to deselect land first so the "About Land" floater doesn't refresh with the old selection. - LLViewerParcelMgr::getInstance()->deselectLand(); - LLParcelSelectionHandle selection = LLViewerParcelMgr::getInstance()->selectParcelAt(mPopupWorldPos); - gMenuHolder->setParcelSelection(selection); - - LLFloaterReg::showInstance("about_land", LLSD(), false); -} +/** + * @file llnetmap.cpp + * @author James Cook + * @brief Display of surrounding regions, objects, and agents. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2001-2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llnetmap.h" + +// Library includes (should move below) +#include "indra_constants.h" +#include "llavatarnamecache.h" +#include "llmath.h" +#include "llfloaterreg.h" +#include "llfocusmgr.h" +#include "lllocalcliprect.h" +#include "llrender.h" +#include "llresmgr.h" +#include "llui.h" +#include "lltooltip.h" + +#include "llglheaders.h" + +// Viewer includes +#include "llagent.h" +#include "llagentcamera.h" +#include "llappviewer.h" // for gDisconnected +#include "llcallingcard.h" // LLAvatarTracker +#include "llfloaterland.h" +#include "llfloaterworldmap.h" +#include "llparcel.h" +#include "lltracker.h" +#include "llsurface.h" +#include "llurlmatch.h" +#include "llurlregistry.h" +#include "llviewercamera.h" +#include "llviewercontrol.h" +#include "llviewerparcelmgr.h" +#include "llviewertexture.h" +#include "llviewertexturelist.h" +#include "llviewermenu.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" +#include "llworld.h" +#include "llworldmapview.h" // shared draw code + +static LLDefaultChildRegistry::Register r1("net_map"); + +constexpr F32 LLNetMap::MAP_SCALE_MIN = 32; +constexpr F32 LLNetMap::MAP_SCALE_FAR = 32; +constexpr F32 LLNetMap::MAP_SCALE_MEDIUM = 128; +constexpr F32 LLNetMap::MAP_SCALE_CLOSE = 256; +constexpr F32 LLNetMap::MAP_SCALE_VERY_CLOSE = 1024; +constexpr F32 LLNetMap::MAP_SCALE_MAX = 4096; + +constexpr F32 MAP_SCALE_ZOOM_FACTOR = 1.04f; // Zoom in factor per click of scroll wheel (4%) +constexpr F32 MIN_DOT_RADIUS = 3.5f; +constexpr F32 DOT_SCALE = 0.75f; +constexpr F32 MIN_PICK_SCALE = 2.f; +constexpr S32 MOUSE_DRAG_SLOP = 2; // How far the mouse needs to move before we think it's a drag + +constexpr F64 COARSEUPDATE_MAX_Z = 1020.0f; + +LLNetMap::LLNetMap (const Params & p) +: LLUICtrl (p), + mBackgroundColor (p.bg_color()), + mScale( MAP_SCALE_MEDIUM ), + mPixelsPerMeter( MAP_SCALE_MEDIUM / REGION_WIDTH_METERS ), + mObjectMapTPM(0.f), + mObjectMapPixels(0.f), + mCurPan(0.f, 0.f), + mStartPan(0.f, 0.f), + mPopupWorldPos(0.f, 0.f, 0.f), + mMouseDown(0, 0), + mPanning(false), + mUpdateNow(false), + mObjectImageCenterGlobal( gAgentCamera.getCameraPositionGlobal() ), + mObjectRawImagep(), + mObjectImagep(), + mClosestAgentToCursor(), + mClosestAgentAtLastRightClick(), + mToolTipMsg() +{ + mScale = gSavedSettings.getF32("MiniMapScale"); + if (gAgent.isFirstLogin()) + { + // *HACK: On first run, set this to false for new users, otherwise the + // default is true to maintain consistent experience for existing + // users. + gSavedSettings.setBOOL("MiniMapRotate", false); + } + mPixelsPerMeter = mScale / REGION_WIDTH_METERS; + mDotRadius = llmax(DOT_SCALE * mPixelsPerMeter, MIN_DOT_RADIUS); +} + +LLNetMap::~LLNetMap() +{ + auto menu = static_cast(mPopupMenuHandle.get()); + if (menu) + { + menu->die(); + mPopupMenuHandle.markDead(); + } +} + +bool LLNetMap::postBuild() +{ + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar commitRegistrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enableRegistrar; + + enableRegistrar.add("Minimap.Zoom.Check", boost::bind(&LLNetMap::isZoomChecked, this, _2)); + commitRegistrar.add("Minimap.Zoom.Set", boost::bind(&LLNetMap::setZoom, this, _2)); + commitRegistrar.add("Minimap.Tracker", boost::bind(&LLNetMap::handleStopTracking, this, _2)); + commitRegistrar.add("Minimap.Center.Activate", boost::bind(&LLNetMap::activateCenterMap, this, _2)); + enableRegistrar.add("Minimap.MapOrientation.Check", boost::bind(&LLNetMap::isMapOrientationChecked, this, _2)); + commitRegistrar.add("Minimap.MapOrientation.Set", boost::bind(&LLNetMap::setMapOrientation, this, _2)); + commitRegistrar.add("Minimap.AboutLand", boost::bind(&LLNetMap::popupShowAboutLand, this, _2)); + + LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile("menu_mini_map.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + mPopupMenuHandle = menu->getHandle(); + menu->setItemEnabled("Re-center map", false); + return true; +} + +void LLNetMap::setScale( F32 scale ) +{ + scale = llclamp(scale, MAP_SCALE_MIN, MAP_SCALE_MAX); + mCurPan *= scale / mScale; + mScale = scale; + + if (mObjectImagep.notNull()) + { + F32 width = (F32)(getRect().getWidth()); + F32 height = (F32)(getRect().getHeight()); + F32 diameter = sqrt(width * width + height * height); + F32 region_widths = diameter / mScale; + F32 meters = region_widths * LLWorld::getInstance()->getRegionWidthInMeters(); + F32 num_pixels = (F32)mObjectImagep->getWidth(); + mObjectMapTPM = num_pixels / meters; + mObjectMapPixels = diameter; + } + + mPixelsPerMeter = mScale / REGION_WIDTH_METERS; + mDotRadius = llmax(DOT_SCALE * mPixelsPerMeter, MIN_DOT_RADIUS); + + gSavedSettings.setF32("MiniMapScale", mScale); + + mUpdateNow = true; +} + + +/////////////////////////////////////////////////////////////////////////////////// + +void LLNetMap::draw() +{ + if (!LLWorld::instanceExists()) + { + return; + } + LL_PROFILE_ZONE_SCOPED; + static LLFrameTimer map_timer; + static LLUIColor map_avatar_color = LLUIColorTable::instance().getColor("MapAvatarColor", LLColor4::white); + static LLUIColor map_avatar_friend_color = LLUIColorTable::instance().getColor("MapAvatarFriendColor", LLColor4::white); + static LLUIColor map_track_color = LLUIColorTable::instance().getColor("MapTrackColor", LLColor4::white); + //static LLUIColor map_track_disabled_color = LLUIColorTable::instance().getColor("MapTrackDisabledColor", LLColor4::white); + static LLUIColor map_frustum_color = LLUIColorTable::instance().getColor("MapFrustumColor", LLColor4::white); + static LLUIColor map_parcel_outline_color = LLUIColorTable::instance().getColor("MapParcelOutlineColor", LLColor4(LLColor3(LLColor4::yellow), 0.5f)); + + if (mObjectImagep.isNull()) + { + createObjectImage(); + } + + static LLUICachedControl auto_center("MiniMapAutoCenter", true); + bool auto_centering = auto_center && !mPanning; + mCentering = mCentering && !mPanning; + + if (auto_centering || mCentering) + { + mCurPan = lerp(mCurPan, LLVector2(0.0f, 0.0f) , LLSmoothInterpolation::getInterpolant(0.1f)); + } + bool centered = abs(mCurPan.mV[VX]) < 0.5f && abs(mCurPan.mV[VY]) < 0.5f; + if (centered) + { + mCurPan.mV[0] = 0.0f; + mCurPan.mV[1] = 0.0f; + mCentering = false; + } + + auto menu = static_cast(mPopupMenuHandle.get()); + if (menu) + { + bool can_recenter_map = !(centered || mCentering || auto_centering); + menu->setItemEnabled("Re-center map", can_recenter_map); + } + updateAboutLandPopupButton(); + + // Prepare a scissor region + F32 rotation = 0; + + gGL.pushMatrix(); + gGL.pushUIMatrix(); + + LLVector3 offset = gGL.getUITranslation(); + LLVector3 scale = gGL.getUIScale(); + + gGL.loadIdentity(); + gGL.loadUIIdentity(); + + gGL.scalef(scale.mV[0], scale.mV[1], scale.mV[2]); + gGL.translatef(offset.mV[0], offset.mV[1], offset.mV[2]); + + { + LLLocalClipRect clip(getLocalRect()); + { + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + + // Draw background rectangle + LLColor4 background_color = mBackgroundColor.get(); + gGL.color4fv( background_color.mV ); + gl_rect_2d(0, getRect().getHeight(), getRect().getWidth(), 0); + } + + // region 0,0 is in the middle + S32 center_sw_left = getRect().getWidth() / 2 + llfloor(mCurPan.mV[VX]); + S32 center_sw_bottom = getRect().getHeight() / 2 + llfloor(mCurPan.mV[VY]); + + gGL.pushMatrix(); + + gGL.translatef( (F32) center_sw_left, (F32) center_sw_bottom, 0.f); + + static LLUICachedControl rotate_map("MiniMapRotate", true); + if( rotate_map ) + { + // rotate subsequent draws to agent rotation + rotation = atan2( LLViewerCamera::getInstance()->getAtAxis().mV[VX], LLViewerCamera::getInstance()->getAtAxis().mV[VY] ); + gGL.rotatef( rotation * RAD_TO_DEG, 0.f, 0.f, 1.f); + } + + // figure out where agent is + const S32 region_width = ll_round(LLWorld::getInstance()->getRegionWidthInMeters()); + const F32 scale_pixels_per_meter = mScale / region_width; + + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* regionp = *iter; + // Find x and y position relative to camera's center. + LLVector3 origin_agent = regionp->getOriginAgent(); + LLVector3 rel_region_pos = origin_agent - gAgentCamera.getCameraPositionAgent(); + F32 relative_x = rel_region_pos.mV[0] * scale_pixels_per_meter; + F32 relative_y = rel_region_pos.mV[1] * scale_pixels_per_meter; + + // background region rectangle + F32 bottom = relative_y; + F32 left = relative_x; + F32 top = bottom + mScale ; + F32 right = left + mScale ; + + if (regionp == gAgent.getRegion()) + { + gGL.color4f(1.f, 1.f, 1.f, 1.f); + } + else + { + gGL.color4f(0.8f, 0.8f, 0.8f, 1.f); + } + + if (!regionp->isAlive()) + { + gGL.color4f(1.f, 0.5f, 0.5f, 1.f); + } + + + + // Draw using texture. + gGL.getTexUnit(0)->bind(regionp->getLand().getSTexture()); + gGL.begin(LLRender::QUADS); + gGL.texCoord2f(0.f, 1.f); + gGL.vertex2f(left, top); + gGL.texCoord2f(0.f, 0.f); + gGL.vertex2f(left, bottom); + gGL.texCoord2f(1.f, 0.f); + gGL.vertex2f(right, bottom); + gGL.texCoord2f(1.f, 1.f); + gGL.vertex2f(right, top); + gGL.end(); + + // Draw water + gGL.flush(); + { + if (regionp->getLand().getWaterTexture()) + { + gGL.getTexUnit(0)->bind(regionp->getLand().getWaterTexture()); + gGL.begin(LLRender::QUADS); + gGL.texCoord2f(0.f, 1.f); + gGL.vertex2f(left, top); + gGL.texCoord2f(0.f, 0.f); + gGL.vertex2f(left, bottom); + gGL.texCoord2f(1.f, 0.f); + gGL.vertex2f(right, bottom); + gGL.texCoord2f(1.f, 1.f); + gGL.vertex2f(right, top); + gGL.end(); + } + } + gGL.flush(); + } + + // Redraw object layer periodically + if (mUpdateNow || (map_timer.getElapsedTimeF32() > 0.5f)) + { + mUpdateNow = false; + + // Locate the centre of the object layer, accounting for panning + LLVector3 new_center = globalPosToView(gAgentCamera.getCameraPositionGlobal()); + new_center.mV[VX] -= mCurPan.mV[VX]; + new_center.mV[VY] -= mCurPan.mV[VY]; + new_center.mV[VZ] = 0.f; + mObjectImageCenterGlobal = viewPosToGlobal(llfloor(new_center.mV[VX]), llfloor(new_center.mV[VY])); + + // Create the base texture. + LLImageDataLock lock(mObjectRawImagep); + U8 *default_texture = mObjectRawImagep->getData(); + memset( default_texture, 0, mObjectImagep->getWidth() * mObjectImagep->getHeight() * mObjectImagep->getComponents() ); + + // Draw objects + gObjectList.renderObjectsForMap(*this); + + mObjectImagep->setSubImage(mObjectRawImagep, 0, 0, mObjectImagep->getWidth(), mObjectImagep->getHeight()); + + map_timer.reset(); + } + + LLVector3 map_center_agent = gAgent.getPosAgentFromGlobal(mObjectImageCenterGlobal); + LLVector3 camera_position = gAgentCamera.getCameraPositionAgent(); + map_center_agent -= camera_position; + map_center_agent.mV[VX] *= scale_pixels_per_meter; + map_center_agent.mV[VY] *= scale_pixels_per_meter; + + gGL.getTexUnit(0)->bind(mObjectImagep); + F32 image_half_width = 0.5f*mObjectMapPixels; + F32 image_half_height = 0.5f*mObjectMapPixels; + + gGL.begin(LLRender::QUADS); + gGL.texCoord2f(0.f, 1.f); + gGL.vertex2f(map_center_agent.mV[VX] - image_half_width, image_half_height + map_center_agent.mV[VY]); + gGL.texCoord2f(0.f, 0.f); + gGL.vertex2f(map_center_agent.mV[VX] - image_half_width, map_center_agent.mV[VY] - image_half_height); + gGL.texCoord2f(1.f, 0.f); + gGL.vertex2f(image_half_width + map_center_agent.mV[VX], map_center_agent.mV[VY] - image_half_height); + gGL.texCoord2f(1.f, 1.f); + gGL.vertex2f(image_half_width + map_center_agent.mV[VX], image_half_height + map_center_agent.mV[VY]); + gGL.end(); + + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* regionp = *iter; + regionp->renderPropertyLinesOnMinimap(scale_pixels_per_meter, map_parcel_outline_color.get().mV); + } + + gGL.popMatrix(); + + // Mouse pointer in local coordinates + S32 local_mouse_x; + S32 local_mouse_y; + //localMouse(&local_mouse_x, &local_mouse_y); + LLUI::getInstance()->getMousePositionLocal(this, &local_mouse_x, &local_mouse_y); + mClosestAgentToCursor.setNull(); + F32 closest_dist_squared = F32_MAX; // value will be overridden in the loop + F32 min_pick_dist_squared = (mDotRadius * MIN_PICK_SCALE) * (mDotRadius * MIN_PICK_SCALE); + + LLVector3 pos_map; + uuid_vec_t avatar_ids; + std::vector positions; + bool unknown_relative_z; + + LLWorld::getInstance()->getAvatars(&avatar_ids, &positions, gAgentCamera.getCameraPositionGlobal()); + + // Draw avatars + for (U32 i = 0; i < avatar_ids.size(); i++) + { + LLUUID uuid = avatar_ids[i]; + // Skip self, we'll draw it later + if (uuid == gAgent.getID()) continue; + + pos_map = globalPosToView(positions[i]); + + bool show_as_friend = (LLAvatarTracker::instance().getBuddyInfo(uuid) != NULL); + + LLColor4 color = show_as_friend ? map_avatar_friend_color : map_avatar_color; + + unknown_relative_z = positions[i].mdV[VZ] >= COARSEUPDATE_MAX_Z && + camera_position.mV[VZ] >= COARSEUPDATE_MAX_Z; + + LLWorldMapView::drawAvatar( + pos_map.mV[VX], pos_map.mV[VY], + color, + pos_map.mV[VZ], mDotRadius, + unknown_relative_z); + + if(uuid.notNull()) + { + bool selected = false; + uuid_vec_t::iterator sel_iter = gmSelected.begin(); + for (; sel_iter != gmSelected.end(); sel_iter++) + { + if(*sel_iter == uuid) + { + selected = true; + break; + } + } + if(selected) + { + if( (pos_map.mV[VX] < 0) || + (pos_map.mV[VY] < 0) || + (pos_map.mV[VX] >= getRect().getWidth()) || + (pos_map.mV[VY] >= getRect().getHeight()) ) + { + S32 x = ll_round( pos_map.mV[VX] ); + S32 y = ll_round( pos_map.mV[VY] ); + LLWorldMapView::drawTrackingCircle( getRect(), x, y, color, 1, 10); + } else + { + LLWorldMapView::drawTrackingDot(pos_map.mV[VX],pos_map.mV[VY],color,0.f); + } + } + } + + F32 dist_to_cursor_squared = dist_vec_squared(LLVector2(pos_map.mV[VX], pos_map.mV[VY]), + LLVector2(local_mouse_x,local_mouse_y)); + if(dist_to_cursor_squared < min_pick_dist_squared && dist_to_cursor_squared < closest_dist_squared) + { + closest_dist_squared = dist_to_cursor_squared; + mClosestAgentToCursor = uuid; + } + } + + // Draw dot for autopilot target + if (gAgent.getAutoPilot()) + { + drawTracking( gAgent.getAutoPilotTargetGlobal(), map_track_color ); + } + else + { + LLTracker::ETrackingStatus tracking_status = LLTracker::getTrackingStatus(); + if ( LLTracker::TRACKING_AVATAR == tracking_status ) + { + drawTracking( LLAvatarTracker::instance().getGlobalPos(), map_track_color ); + } + else if ( LLTracker::TRACKING_LANDMARK == tracking_status + || LLTracker::TRACKING_LOCATION == tracking_status ) + { + drawTracking( LLTracker::getTrackedPositionGlobal(), map_track_color ); + } + } + + // Draw dot for self avatar position + LLVector3d pos_global = gAgent.getPositionGlobal(); + pos_map = globalPosToView(pos_global); + S32 dot_width = ll_round(mDotRadius * 2.f); + LLUIImagePtr you = LLWorldMapView::sAvatarYouLargeImage; + if (you) + { + you->draw(ll_round(pos_map.mV[VX] - mDotRadius), + ll_round(pos_map.mV[VY] - mDotRadius), + dot_width, + dot_width); + + F32 dist_to_cursor_squared = dist_vec_squared(LLVector2(pos_map.mV[VX], pos_map.mV[VY]), + LLVector2(local_mouse_x,local_mouse_y)); + if(dist_to_cursor_squared < min_pick_dist_squared && dist_to_cursor_squared < closest_dist_squared) + { + mClosestAgentToCursor = gAgent.getID(); + } + } + + // Draw frustum + F32 meters_to_pixels = mScale/ LLWorld::getInstance()->getRegionWidthInMeters(); + + F32 horiz_fov = LLViewerCamera::getInstance()->getView() * LLViewerCamera::getInstance()->getAspect(); + F32 far_clip_meters = LLViewerCamera::getInstance()->getFar(); + F32 far_clip_pixels = far_clip_meters * meters_to_pixels; + + F32 ctr_x = (F32)center_sw_left; + F32 ctr_y = (F32)center_sw_bottom; + + const F32 steps_per_circle = 40.0f; + const F32 steps_per_radian = steps_per_circle / F_TWO_PI; + const F32 arc_start = -(horiz_fov / 2.0f) + F_PI_BY_TWO; + const F32 arc_end = (horiz_fov / 2.0f) + F_PI_BY_TWO; + const S32 steps = llmax(1, (S32)((horiz_fov * steps_per_radian) + 0.5f)); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + if( rotate_map ) + { + gGL.pushMatrix(); + gGL.translatef( ctr_x, ctr_y, 0 ); + gl_washer_segment_2d(far_clip_pixels, 0, arc_start, arc_end, steps, map_frustum_color(), map_frustum_color()); + gGL.popMatrix(); + } + else + { + gGL.pushMatrix(); + gGL.translatef( ctr_x, ctr_y, 0 ); + // If we don't rotate the map, we have to rotate the frustum. + gGL.rotatef( atan2( LLViewerCamera::getInstance()->getAtAxis().mV[VX], LLViewerCamera::getInstance()->getAtAxis().mV[VY] ) * RAD_TO_DEG, 0.f, 0.f, -1.f); + gl_washer_segment_2d(far_clip_pixels, 0, arc_start, arc_end, steps, map_frustum_color(), map_frustum_color()); + gGL.popMatrix(); + } + } + + gGL.popMatrix(); + gGL.popUIMatrix(); + + LLUICtrl::draw(); +} + +void LLNetMap::reshape(S32 width, S32 height, bool called_from_parent) +{ + LLUICtrl::reshape(width, height, called_from_parent); + createObjectImage(); +} + +LLVector3 LLNetMap::globalPosToView(const LLVector3d& global_pos) +{ + LLVector3d camera_position = gAgentCamera.getCameraPositionGlobal(); + + LLVector3d relative_pos_global = global_pos - camera_position; + LLVector3 pos_local; + pos_local.setVec(relative_pos_global); // convert to floats from doubles + + pos_local.mV[VX] *= mPixelsPerMeter; + pos_local.mV[VY] *= mPixelsPerMeter; + // leave Z component in meters + + static LLUICachedControl rotate_map("MiniMapRotate", true); + if( rotate_map ) + { + F32 radians = atan2( LLViewerCamera::getInstance()->getAtAxis().mV[VX], LLViewerCamera::getInstance()->getAtAxis().mV[VY] ); + LLQuaternion rot(radians, LLVector3(0.f, 0.f, 1.f)); + pos_local.rotVec( rot ); + } + + pos_local.mV[VX] += getRect().getWidth() / 2 + mCurPan.mV[VX]; + pos_local.mV[VY] += getRect().getHeight() / 2 + mCurPan.mV[VY]; + + return pos_local; +} + +void LLNetMap::drawTracking(const LLVector3d& pos_global, const LLColor4& color, + bool draw_arrow ) +{ + LLVector3 pos_local = globalPosToView(pos_global); + if( (pos_local.mV[VX] < 0) || + (pos_local.mV[VY] < 0) || + (pos_local.mV[VX] >= getRect().getWidth()) || + (pos_local.mV[VY] >= getRect().getHeight()) ) + { + if (draw_arrow) + { + S32 x = ll_round( pos_local.mV[VX] ); + S32 y = ll_round( pos_local.mV[VY] ); + LLWorldMapView::drawTrackingCircle( getRect(), x, y, color, 1, 10 ); + LLWorldMapView::drawTrackingArrow( getRect(), x, y, color ); + } + } + else + { + LLWorldMapView::drawTrackingDot(pos_local.mV[VX], + pos_local.mV[VY], + color, + pos_local.mV[VZ]); + } +} + +bool LLNetMap::isMouseOnPopupMenu() +{ + auto menu = static_cast(mPopupMenuHandle.get()); + if (!menu || !menu->isOpen()) + { + return false; + } + + S32 popup_x; + S32 popup_y; + LLUI::getInstance()->getMousePositionLocal(menu, &popup_x, &popup_y); + // *NOTE: Tolerance is larger than it needs to be because the context menu is offset from the mouse when the menu is opened from certain + // directions. This may be a quirk of LLMenuGL::showPopup. -Cosmic,2022-03-22 + constexpr S32 tolerance = 10; + // Test tolerance from all four corners, as the popup menu can appear from a different direction if there's not enough space. + // Assume the size of the popup menu is much larger than the provided tolerance. + // In practice, this is a [tolerance]px margin around the popup menu. + for (S32 sign_x = -1; sign_x <= 1; sign_x += 2) + { + for (S32 sign_y = -1; sign_y <= 1; sign_y += 2) + { + if (menu->pointInView(popup_x + (sign_x * tolerance), popup_y + (sign_y * tolerance))) + { + return true; + } + } + } + return false; +} + +void LLNetMap::updateAboutLandPopupButton() +{ + auto menu = static_cast(mPopupMenuHandle.get()); + if (!menu || !menu->isOpen()) + { + return; + } + + LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosGlobal(mPopupWorldPos); + if (!region) + { + menu->setItemEnabled("About Land", false); + } + else + { + // Check if the mouse is in the bounds of the popup. If so, it's safe to assume no other hover function will be called, so the hover + // parcel can be used to check if location-sensitive tooltip options are available. + if (isMouseOnPopupMenu()) + { + LLViewerParcelMgr::getInstance()->setHoverParcel(mPopupWorldPos); + LLParcel *hover_parcel = LLViewerParcelMgr::getInstance()->getHoverParcel(); + bool valid_parcel = false; + if (hover_parcel) + { + valid_parcel = hover_parcel->getOwnerID().notNull(); + } + menu->setItemEnabled("About Land", valid_parcel); + } + } +} + +LLVector3d LLNetMap::viewPosToGlobal( S32 x, S32 y ) +{ + x -= ll_round(getRect().getWidth() / 2 + mCurPan.mV[VX]); + y -= ll_round(getRect().getHeight() / 2 + mCurPan.mV[VY]); + + LLVector3 pos_local( (F32)x, (F32)y, 0 ); + + F32 radians = - atan2( LLViewerCamera::getInstance()->getAtAxis().mV[VX], LLViewerCamera::getInstance()->getAtAxis().mV[VY] ); + + static LLUICachedControl rotate_map("MiniMapRotate", true); + if( rotate_map ) + { + LLQuaternion rot(radians, LLVector3(0.f, 0.f, 1.f)); + pos_local.rotVec( rot ); + } + + pos_local *= ( LLWorld::getInstance()->getRegionWidthInMeters() / mScale ); + + LLVector3d pos_global; + pos_global.setVec( pos_local ); + pos_global += gAgentCamera.getCameraPositionGlobal(); + + return pos_global; +} + +bool LLNetMap::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + // note that clicks are reversed from what you'd think: i.e. > 0 means zoom out, < 0 means zoom in + F32 new_scale = mScale * pow(MAP_SCALE_ZOOM_FACTOR, -clicks); + F32 old_scale = mScale; + + setScale(new_scale); + + static LLUICachedControl auto_center("MiniMapAutoCenter", true); + if (!auto_center) + { + // Adjust pan to center the zoom on the mouse pointer + LLVector2 zoom_offset; + zoom_offset.mV[VX] = x - getRect().getWidth() / 2; + zoom_offset.mV[VY] = y - getRect().getHeight() / 2; + mCurPan -= zoom_offset * mScale / old_scale - zoom_offset; + } + + return true; +} + +bool LLNetMap::handleToolTip(S32 x, S32 y, MASK mask) +{ + if (gDisconnected) + { + return false; + } + + // If the cursor is near an avatar on the minimap, a mini-inspector will be + // shown for the avatar, instead of the normal map tooltip. + if (handleToolTipAgent(mClosestAgentToCursor)) + { + return true; + } + + // The popup menu uses the hover parcel when it is open and the mouse is on + // top of it, with some additional tolerance. Returning early here prevents + // fighting over that hover parcel when getting tooltip info in the + // tolerance region. + if (isMouseOnPopupMenu()) + { + return false; + } + + LLRect sticky_rect; + S32 SLOP = 4; + localPointToScreen(x - SLOP, y - SLOP, &(sticky_rect.mLeft), &(sticky_rect.mBottom)); + sticky_rect.mRight = sticky_rect.mLeft + 2 * SLOP; + sticky_rect.mTop = sticky_rect.mBottom + 2 * SLOP; + + std::string parcel_name_msg; + std::string parcel_sale_price_msg; + std::string parcel_sale_area_msg; + std::string parcel_owner_msg; + std::string region_name_msg; + + LLVector3d posGlobal = viewPosToGlobal(x, y); + LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosGlobal(posGlobal); + if (region) + { + std::string region_name = region->getName(); + if (!region_name.empty()) + { + region_name_msg = mRegionNameMsg; + LLStringUtil::format(region_name_msg, {{"[REGION_NAME]", region_name}}); + } + + // Only show parcel information in the tooltip if property lines are visible. Otherwise, the parcel the tooltip is referring to is + // ambiguous. + if (gSavedSettings.getBOOL("MiniMapShowPropertyLines")) + { + LLViewerParcelMgr::getInstance()->setHoverParcel(posGlobal); + LLParcel *hover_parcel = LLViewerParcelMgr::getInstance()->getHoverParcel(); + if (hover_parcel) + { + std::string parcel_name = hover_parcel->getName(); + if (!parcel_name.empty()) + { + parcel_name_msg = mParcelNameMsg; + LLStringUtil::format(parcel_name_msg, {{"[PARCEL_NAME]", parcel_name}}); + } + + const LLUUID parcel_owner = hover_parcel->getOwnerID(); + std::string parcel_owner_name_url = LLSLURL("agent", parcel_owner, "inspect").getSLURLString(); + static LLUrlMatch parcel_owner_name_url_match; + LLUrlRegistry::getInstance()->findUrl(parcel_owner_name_url, parcel_owner_name_url_match); + if (!parcel_owner_name_url_match.empty()) + { + parcel_owner_msg = mParcelOwnerMsg; + std::string parcel_owner_name = parcel_owner_name_url_match.getLabel(); + LLStringUtil::format(parcel_owner_msg, {{"[PARCEL_OWNER]", parcel_owner_name}}); + } + + if (hover_parcel->getForSale()) + { + const LLUUID auth_buyer_id = hover_parcel->getAuthorizedBuyerID(); + const LLUUID agent_id = gAgent.getID(); + bool show_for_sale = auth_buyer_id.isNull() || auth_buyer_id == agent_id || parcel_owner == agent_id; + if (show_for_sale) + { + S32 price = hover_parcel->getSalePrice(); + S32 area = hover_parcel->getArea(); + F32 cost_per_sqm = 0.0f; + if (area > 0) + { + cost_per_sqm = F32(price) / area; + } + std::string formatted_price = LLResMgr::getInstance()->getMonetaryString(price); + std::string formatted_cost_per_meter = llformat("%.1f", cost_per_sqm); + parcel_sale_price_msg = mParcelSalePriceMsg; + LLStringUtil::format(parcel_sale_price_msg, + {{"[PRICE]", formatted_price}, {"[PRICE_PER_SQM]", formatted_cost_per_meter}}); + std::string formatted_area = llformat("%d", area); + parcel_sale_area_msg = mParcelSaleAreaMsg; + LLStringUtil::format(parcel_sale_area_msg, {{"[AREA]", formatted_area}}); + } + } + } + } + } + + std::string tool_tip_hint_msg; + if (gSavedSettings.getBOOL("DoubleClickTeleport")) + { + tool_tip_hint_msg = mAltToolTipHintMsg; + } + else if (gSavedSettings.getBOOL("DoubleClickShowWorldMap")) + { + tool_tip_hint_msg = mToolTipHintMsg; + } + + LLStringUtil::format_map_t args; + args["[PARCEL_NAME_MSG]"] = parcel_name_msg.empty() ? "" : parcel_name_msg + '\n'; + args["[PARCEL_SALE_PRICE_MSG]"] = parcel_sale_price_msg.empty() ? "" : parcel_sale_price_msg + '\n'; + args["[PARCEL_SALE_AREA_MSG]"] = parcel_sale_area_msg.empty() ? "" : parcel_sale_area_msg + '\n'; + args["[PARCEL_OWNER_MSG]"] = parcel_owner_msg.empty() ? "" : parcel_owner_msg + '\n'; + args["[REGION_NAME_MSG]"] = region_name_msg.empty() ? "" : region_name_msg + '\n'; + args["[TOOL_TIP_HINT_MSG]"] = tool_tip_hint_msg.empty() ? "" : tool_tip_hint_msg + '\n'; + + std::string msg = mToolTipMsg; + LLStringUtil::format(msg, args); + if (msg.back() == '\n') + { + msg.resize(msg.size() - 1); + } + LLToolTipMgr::instance().show(LLToolTip::Params().message(msg).sticky_rect(sticky_rect)); + + return true; +} + +bool LLNetMap::handleToolTipAgent(const LLUUID& avatar_id) +{ + LLAvatarName av_name; + if (avatar_id.isNull() || !LLAvatarNameCache::get(avatar_id, &av_name)) + { + return false; + } + + // only show tooltip if same inspector not already open + LLFloater* existing_inspector = LLFloaterReg::findInstance("inspect_avatar"); + if (!existing_inspector + || !existing_inspector->getVisible() + || existing_inspector->getKey()["avatar_id"].asUUID() != avatar_id) + { + LLInspector::Params p; + p.fillFrom(LLUICtrlFactory::instance().getDefaultParams()); + p.message(av_name.getCompleteName()); + p.image.name("Inspector_I"); + p.click_callback(boost::bind(showAvatarInspector, avatar_id)); + p.visible_time_near(6.f); + p.visible_time_far(3.f); + p.delay_time(0.35f); + p.wrap(false); + + LLToolTipMgr::instance().show(p); + } + return true; +} + +// static +void LLNetMap::showAvatarInspector(const LLUUID& avatar_id) +{ + LLSD params; + params["avatar_id"] = avatar_id; + + if (LLToolTipMgr::instance().toolTipVisible()) + { + LLRect rect = LLToolTipMgr::instance().getToolTipRect(); + params["pos"]["x"] = rect.mLeft; + params["pos"]["y"] = rect.mTop; + } + + LLFloaterReg::showInstance("inspect_avatar", params); +} + +void LLNetMap::renderScaledPointGlobal( const LLVector3d& pos, const LLColor4U &color, F32 radius_meters ) +{ + LLVector3 local_pos; + local_pos.setVec( pos - mObjectImageCenterGlobal ); + + S32 diameter_pixels = ll_round(2 * radius_meters * mObjectMapTPM); + renderPoint( local_pos, color, diameter_pixels ); +} + + +void LLNetMap::renderPoint(const LLVector3 &pos_local, const LLColor4U &color, + S32 diameter, S32 relative_height) +{ + if (diameter <= 0) + { + return; + } + + const S32 image_width = (S32)mObjectImagep->getWidth(); + const S32 image_height = (S32)mObjectImagep->getHeight(); + + S32 x_offset = ll_round(pos_local.mV[VX] * mObjectMapTPM + image_width / 2); + S32 y_offset = ll_round(pos_local.mV[VY] * mObjectMapTPM + image_height / 2); + + if ((x_offset < 0) || (x_offset >= image_width)) + { + return; + } + if ((y_offset < 0) || (y_offset >= image_height)) + { + return; + } + + LLImageDataLock lock(mObjectRawImagep); + U8 *datap = mObjectRawImagep->getData(); + + S32 neg_radius = diameter / 2; + S32 pos_radius = diameter - neg_radius; + S32 x, y; + + if (relative_height > 0) + { + // ...point above agent + S32 px, py; + + // vertical line + px = x_offset; + for (y = -neg_radius; y < pos_radius; y++) + { + py = y_offset + y; + if ((py < 0) || (py >= image_height)) + { + continue; + } + S32 offset = px + py * image_width; + ((U32*)datap)[offset] = color.asRGBA(); + } + + // top line + py = y_offset + pos_radius - 1; + for (x = -neg_radius; x < pos_radius; x++) + { + px = x_offset + x; + if ((px < 0) || (px >= image_width)) + { + continue; + } + S32 offset = px + py * image_width; + ((U32*)datap)[offset] = color.asRGBA(); + } + } + else + { + // ...point level with agent + for (x = -neg_radius; x < pos_radius; x++) + { + S32 p_x = x_offset + x; + if ((p_x < 0) || (p_x >= image_width)) + { + continue; + } + + for (y = -neg_radius; y < pos_radius; y++) + { + S32 p_y = y_offset + y; + if ((p_y < 0) || (p_y >= image_height)) + { + continue; + } + S32 offset = p_x + p_y * image_width; + ((U32*)datap)[offset] = color.asRGBA(); + } + } + } +} + +void LLNetMap::createObjectImage() +{ + // Find the size of the side of a square that surrounds the circle that surrounds getRect(). + // ... which is, the diagonal of the rect. + F32 width = (F32)getRect().getWidth(); + F32 height = (F32)getRect().getHeight(); + S32 square_size = ll_round( sqrt(width*width + height*height) ); + + // Find the least power of two >= the minimum size. + const S32 MIN_SIZE = 64; + const S32 MAX_SIZE = 256; + S32 img_size = MIN_SIZE; + while( (img_size*2 < square_size ) && (img_size < MAX_SIZE) ) + { + img_size <<= 1; + } + + if( mObjectImagep.isNull() || + (mObjectImagep->getWidth() != img_size) || + (mObjectImagep->getHeight() != img_size) ) + { + mObjectRawImagep = new LLImageRaw(img_size, img_size, 4); + U8* data = mObjectRawImagep->getData(); + memset( data, 0, img_size * img_size * 4 ); + mObjectImagep = LLViewerTextureManager::getLocalTexture( mObjectRawImagep.get(), false); + } + setScale(mScale); + mUpdateNow = true; +} + +bool LLNetMap::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // Start panning + gFocusMgr.setMouseCapture(this); + + mStartPan = mCurPan; + mMouseDown.mX = x; + mMouseDown.mY = y; + return true; +} + +bool LLNetMap::handleMouseUp(S32 x, S32 y, MASK mask) +{ + if (abs(mMouseDown.mX - x) < 3 && abs(mMouseDown.mY - y) < 3) + { + handleClick(x, y, mask); + } + + if (hasMouseCapture()) + { + if (mPanning) + { + // restore mouse cursor + S32 local_x, local_y; + local_x = mMouseDown.mX + llfloor(mCurPan.mV[VX] - mStartPan.mV[VX]); + local_y = mMouseDown.mY + llfloor(mCurPan.mV[VY] - mStartPan.mV[VY]); + LLRect clip_rect = getRect(); + clip_rect.stretch(-8); + clip_rect.clipPointToRect(mMouseDown.mX, mMouseDown.mY, local_x, local_y); + LLUI::getInstance()->setMousePositionLocal(this, local_x, local_y); + + // finish the pan + mPanning = false; + + mMouseDown.set(0, 0); + } + gViewerWindow->showCursor(); + gFocusMgr.setMouseCapture(NULL); + return true; + } + + return false; +} + +bool LLNetMap::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + auto menu = static_cast(mPopupMenuHandle.get()); + if (menu) + { + mPopupWorldPos = viewPosToGlobal(x, y); + menu->buildDrawLabels(); + menu->updateParent(LLMenuGL::sMenuContainer); + menu->setItemEnabled("Stop Tracking", LLTracker::isTracking(0)); + LLMenuGL::showPopup(this, menu, x, y); + } + return true; +} + +bool LLNetMap::handleClick(S32 x, S32 y, MASK mask) +{ + // TODO: allow clicking an avatar on minimap to select avatar in the nearby avatar list + // if(mClosestAgentToCursor.notNull()) + // mNearbyList->selectUser(mClosestAgentToCursor); + // Needs a registered observer i guess to accomplish this without using + // globals to tell the mNearbyList in llpeoplepanel to select the user + return true; +} + +bool LLNetMap::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + LLVector3d pos_global = viewPosToGlobal(x, y); + + bool double_click_teleport = gSavedSettings.getBOOL("DoubleClickTeleport"); + bool double_click_show_world_map = gSavedSettings.getBOOL("DoubleClickShowWorldMap"); + + if (double_click_teleport || double_click_show_world_map) + { + // If we're not tracking a beacon already, double-click will set one + if (!LLTracker::isTracking(NULL)) + { + LLFloaterWorldMap* world_map = LLFloaterWorldMap::getInstance(); + if (world_map) + { + world_map->trackLocation(pos_global); + } + } + } + + if (double_click_teleport) + { + // If DoubleClickTeleport is on, double clicking the minimap will teleport there + gAgent.teleportViaLocationLookAt(pos_global); + } + else if (double_click_show_world_map) + { + LLFloaterReg::showInstance("world_map"); + } + return true; +} + +F32 LLNetMap::getScaleForName(std::string scale_name) +{ + if (scale_name == "very close") + { + return LLNetMap::MAP_SCALE_VERY_CLOSE; + } + else if (scale_name == "close") + { + return LLNetMap::MAP_SCALE_CLOSE; + } + else if (scale_name == "medium") + { + return LLNetMap::MAP_SCALE_MEDIUM; + } + else if (scale_name == "far") + { + return LLNetMap::MAP_SCALE_FAR; + } + return 0.0f; +} + +// static +bool LLNetMap::outsideSlop( S32 x, S32 y, S32 start_x, S32 start_y, S32 slop ) +{ + S32 dx = x - start_x; + S32 dy = y - start_y; + + return (dx <= -slop || slop <= dx || dy <= -slop || slop <= dy); +} + +bool LLNetMap::handleHover( S32 x, S32 y, MASK mask ) +{ + if (hasMouseCapture()) + { + if (mPanning || outsideSlop(x, y, mMouseDown.mX, mMouseDown.mY, MOUSE_DRAG_SLOP)) + { + if (!mPanning) + { + // Just started panning. Hide cursor. + mPanning = true; + gViewerWindow->hideCursor(); + } + + LLVector2 delta(static_cast(gViewerWindow->getCurrentMouseDX()), + static_cast(gViewerWindow->getCurrentMouseDY())); + + // Set pan to value at start of drag + offset + mCurPan += delta; + + gViewerWindow->moveCursorToCenter(); + } + } + + if (mask & MASK_SHIFT) + { + // If shift is held, change the cursor to hint that the map can be + // dragged. However, holding shift is not required to drag the map. + gViewerWindow->setCursor( UI_CURSOR_TOOLPAN ); + } + else + { + gViewerWindow->setCursor( UI_CURSOR_CROSS ); + } + + return true; +} + +bool LLNetMap::isZoomChecked(const LLSD &userdata) +{ + std::string level = userdata.asString(); + F32 scale = getScaleForName(level); + return scale == mScale; +} + +void LLNetMap::setZoom(const LLSD &userdata) +{ + std::string level = userdata.asString(); + F32 scale = getScaleForName(level); + if (scale != 0.0f) + { + setScale(scale); + } +} + +void LLNetMap::handleStopTracking (const LLSD& userdata) +{ + auto menu = static_cast(mPopupMenuHandle.get()); + if (menu) + { + menu->setItemEnabled ("Stop Tracking", false); + LLTracker::stopTracking (LLTracker::isTracking(NULL)); + } +} + +void LLNetMap::activateCenterMap(const LLSD &userdata) { mCentering = true; } + +bool LLNetMap::isMapOrientationChecked(const LLSD &userdata) +{ + const std::string command_name = userdata.asString(); + const bool rotate_map = gSavedSettings.getBOOL("MiniMapRotate"); + if (command_name == "north_at_top") + { + return !rotate_map; + } + + if (command_name == "camera_at_top") + { + return rotate_map; + } + + return false; +} + +void LLNetMap::setMapOrientation(const LLSD &userdata) +{ + const std::string command_name = userdata.asString(); + if (command_name == "north_at_top") + { + gSavedSettings.setBOOL("MiniMapRotate", false); + } + else if (command_name == "camera_at_top") + { + gSavedSettings.setBOOL("MiniMapRotate", true); + } +} + +void LLNetMap::popupShowAboutLand(const LLSD &userdata) +{ + // Update parcel selection. It's important to deselect land first so the "About Land" floater doesn't refresh with the old selection. + LLViewerParcelMgr::getInstance()->deselectLand(); + LLParcelSelectionHandle selection = LLViewerParcelMgr::getInstance()->selectParcelAt(mPopupWorldPos); + gMenuHolder->setParcelSelection(selection); + + LLFloaterReg::showInstance("about_land", LLSD(), false); +} diff --git a/indra/newview/llnetmap.h b/indra/newview/llnetmap.h index 437390baa4..7bb13c9f2b 100644 --- a/indra/newview/llnetmap.h +++ b/indra/newview/llnetmap.h @@ -1,170 +1,170 @@ -/** - * @file llnetmap.h - * @brief A little map of the world with network information - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLNETMAP_H -#define LL_LLNETMAP_H - -#include "llmath.h" -#include "lluictrl.h" -#include "v3math.h" -#include "v3dmath.h" -#include "v4color.h" -#include "llpointer.h" -#include "llcoord.h" - -class LLColor4U; -class LLImageRaw; -class LLViewerTexture; -class LLFloaterMap; -class LLMenuGL; - -class LLNetMap : public LLUICtrl -{ -public: - struct Params - : public LLInitParam::Block - { - Optional bg_color; - - Params() - : bg_color("bg_color") - {} - }; - -protected: - LLNetMap (const Params & p); - friend class LLUICtrlFactory; - friend class LLFloaterMap; - -public: - virtual ~LLNetMap(); - - static const F32 MAP_SCALE_MIN; - static const F32 MAP_SCALE_FAR; - static const F32 MAP_SCALE_MEDIUM; - static const F32 MAP_SCALE_CLOSE; - static const F32 MAP_SCALE_VERY_CLOSE; - static const F32 MAP_SCALE_MAX; - - /*virtual*/ void draw(); - /*virtual*/ bool handleScrollWheel(S32 x, S32 y, S32 clicks); - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleHover( S32 x, S32 y, MASK mask ); - /*virtual*/ bool handleToolTip( S32 x, S32 y, MASK mask); - /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); - - /*virtual*/ bool postBuild(); - /*virtual*/ bool handleRightMouseDown( S32 x, S32 y, MASK mask ); - /*virtual*/ bool handleClick(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleDoubleClick( S32 x, S32 y, MASK mask ); - - void setScale(F32 scale); - - void setToolTipMsg(const std::string& msg) { mToolTipMsg = msg; } - void setParcelNameMsg(const std::string& msg) { mParcelNameMsg = msg; } - void setParcelSalePriceMsg(const std::string& msg) { mParcelSalePriceMsg = msg; } - void setParcelSaleAreaMsg(const std::string& msg) { mParcelSaleAreaMsg = msg; } - void setParcelOwnerMsg(const std::string& msg) { mParcelOwnerMsg = msg; } - void setRegionNameMsg(const std::string& msg) { mRegionNameMsg = msg; } - void setToolTipHintMsg(const std::string& msg) { mToolTipHintMsg = msg; } - void setAltToolTipHintMsg(const std::string& msg) { mAltToolTipHintMsg = msg; } - - void renderScaledPointGlobal( const LLVector3d& pos, const LLColor4U &color, F32 radius ); - -private: - const LLVector3d& getObjectImageCenterGlobal() { return mObjectImageCenterGlobal; } - void renderPoint(const LLVector3 &pos, const LLColor4U &color, - S32 diameter, S32 relative_height = 0); - - LLVector3 globalPosToView(const LLVector3d& global_pos); - LLVector3d viewPosToGlobal(S32 x,S32 y); - - void drawTracking( const LLVector3d& pos_global, - const LLColor4& color, - bool draw_arrow = true); - bool isMouseOnPopupMenu(); - void updateAboutLandPopupButton(); - bool handleToolTipAgent(const LLUUID& avatar_id); - static void showAvatarInspector(const LLUUID& avatar_id); - - void createObjectImage(); - - F32 getScaleForName(std::string scale_name); - static bool outsideSlop(S32 x, S32 y, S32 start_x, S32 start_y, S32 slop); - -private: - bool mUpdateNow; - - LLUIColor mBackgroundColor; - - F32 mScale; // Size of a region in pixels - F32 mPixelsPerMeter; // world meters to map pixels - F32 mObjectMapTPM; // texels per meter on map - F32 mObjectMapPixels; // Width of object map in pixels - F32 mDotRadius; // Size of avatar markers - - bool mPanning; // map is being dragged - bool mCentering; // map is being re-centered around the agent - LLVector2 mCurPan; - LLVector2 mStartPan; // pan offset at start of drag - LLVector3d mPopupWorldPos; // world position picked under mouse when context menu is opened - LLCoordGL mMouseDown; // pointer position at start of drag - - LLVector3d mObjectImageCenterGlobal; - LLPointer mObjectRawImagep; - LLPointer mObjectImagep; - - LLUUID mClosestAgentToCursor; - LLUUID mClosestAgentAtLastRightClick; - - std::string mToolTipMsg; - std::string mParcelNameMsg; - std::string mParcelSalePriceMsg; - std::string mParcelSaleAreaMsg; - std::string mParcelOwnerMsg; - std::string mRegionNameMsg; - std::string mToolTipHintMsg; - std::string mAltToolTipHintMsg; - -public: - void setSelected(uuid_vec_t uuids) { gmSelected=uuids; }; - -private: - bool isZoomChecked(const LLSD& userdata); - void setZoom(const LLSD& userdata); - void handleStopTracking(const LLSD& userdata); - void activateCenterMap(const LLSD& userdata); - bool isMapOrientationChecked(const LLSD& userdata); - void setMapOrientation(const LLSD& userdata); - void popupShowAboutLand(const LLSD& userdata); - - LLHandle mPopupMenuHandle; - uuid_vec_t gmSelected; -}; - - -#endif +/** + * @file llnetmap.h + * @brief A little map of the world with network information + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLNETMAP_H +#define LL_LLNETMAP_H + +#include "llmath.h" +#include "lluictrl.h" +#include "v3math.h" +#include "v3dmath.h" +#include "v4color.h" +#include "llpointer.h" +#include "llcoord.h" + +class LLColor4U; +class LLImageRaw; +class LLViewerTexture; +class LLFloaterMap; +class LLMenuGL; + +class LLNetMap : public LLUICtrl +{ +public: + struct Params + : public LLInitParam::Block + { + Optional bg_color; + + Params() + : bg_color("bg_color") + {} + }; + +protected: + LLNetMap (const Params & p); + friend class LLUICtrlFactory; + friend class LLFloaterMap; + +public: + virtual ~LLNetMap(); + + static const F32 MAP_SCALE_MIN; + static const F32 MAP_SCALE_FAR; + static const F32 MAP_SCALE_MEDIUM; + static const F32 MAP_SCALE_CLOSE; + static const F32 MAP_SCALE_VERY_CLOSE; + static const F32 MAP_SCALE_MAX; + + /*virtual*/ void draw(); + /*virtual*/ bool handleScrollWheel(S32 x, S32 y, S32 clicks); + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleHover( S32 x, S32 y, MASK mask ); + /*virtual*/ bool handleToolTip( S32 x, S32 y, MASK mask); + /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); + + /*virtual*/ bool postBuild(); + /*virtual*/ bool handleRightMouseDown( S32 x, S32 y, MASK mask ); + /*virtual*/ bool handleClick(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleDoubleClick( S32 x, S32 y, MASK mask ); + + void setScale(F32 scale); + + void setToolTipMsg(const std::string& msg) { mToolTipMsg = msg; } + void setParcelNameMsg(const std::string& msg) { mParcelNameMsg = msg; } + void setParcelSalePriceMsg(const std::string& msg) { mParcelSalePriceMsg = msg; } + void setParcelSaleAreaMsg(const std::string& msg) { mParcelSaleAreaMsg = msg; } + void setParcelOwnerMsg(const std::string& msg) { mParcelOwnerMsg = msg; } + void setRegionNameMsg(const std::string& msg) { mRegionNameMsg = msg; } + void setToolTipHintMsg(const std::string& msg) { mToolTipHintMsg = msg; } + void setAltToolTipHintMsg(const std::string& msg) { mAltToolTipHintMsg = msg; } + + void renderScaledPointGlobal( const LLVector3d& pos, const LLColor4U &color, F32 radius ); + +private: + const LLVector3d& getObjectImageCenterGlobal() { return mObjectImageCenterGlobal; } + void renderPoint(const LLVector3 &pos, const LLColor4U &color, + S32 diameter, S32 relative_height = 0); + + LLVector3 globalPosToView(const LLVector3d& global_pos); + LLVector3d viewPosToGlobal(S32 x,S32 y); + + void drawTracking( const LLVector3d& pos_global, + const LLColor4& color, + bool draw_arrow = true); + bool isMouseOnPopupMenu(); + void updateAboutLandPopupButton(); + bool handleToolTipAgent(const LLUUID& avatar_id); + static void showAvatarInspector(const LLUUID& avatar_id); + + void createObjectImage(); + + F32 getScaleForName(std::string scale_name); + static bool outsideSlop(S32 x, S32 y, S32 start_x, S32 start_y, S32 slop); + +private: + bool mUpdateNow; + + LLUIColor mBackgroundColor; + + F32 mScale; // Size of a region in pixels + F32 mPixelsPerMeter; // world meters to map pixels + F32 mObjectMapTPM; // texels per meter on map + F32 mObjectMapPixels; // Width of object map in pixels + F32 mDotRadius; // Size of avatar markers + + bool mPanning; // map is being dragged + bool mCentering; // map is being re-centered around the agent + LLVector2 mCurPan; + LLVector2 mStartPan; // pan offset at start of drag + LLVector3d mPopupWorldPos; // world position picked under mouse when context menu is opened + LLCoordGL mMouseDown; // pointer position at start of drag + + LLVector3d mObjectImageCenterGlobal; + LLPointer mObjectRawImagep; + LLPointer mObjectImagep; + + LLUUID mClosestAgentToCursor; + LLUUID mClosestAgentAtLastRightClick; + + std::string mToolTipMsg; + std::string mParcelNameMsg; + std::string mParcelSalePriceMsg; + std::string mParcelSaleAreaMsg; + std::string mParcelOwnerMsg; + std::string mRegionNameMsg; + std::string mToolTipHintMsg; + std::string mAltToolTipHintMsg; + +public: + void setSelected(uuid_vec_t uuids) { gmSelected=uuids; }; + +private: + bool isZoomChecked(const LLSD& userdata); + void setZoom(const LLSD& userdata); + void handleStopTracking(const LLSD& userdata); + void activateCenterMap(const LLSD& userdata); + bool isMapOrientationChecked(const LLSD& userdata); + void setMapOrientation(const LLSD& userdata); + void popupShowAboutLand(const LLSD& userdata); + + LLHandle mPopupMenuHandle; + uuid_vec_t gmSelected; +}; + + +#endif diff --git a/indra/newview/llnotificationhandlerutil.cpp b/indra/newview/llnotificationhandlerutil.cpp index ba8e0fbec8..23f1f8fa5a 100644 --- a/indra/newview/llnotificationhandlerutil.cpp +++ b/indra/newview/llnotificationhandlerutil.cpp @@ -1,329 +1,329 @@ -/** - * @file llnotificationofferhandler.cpp - * @brief Provides set of utility methods for notifications processing. - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#include "llviewerprecompiledheaders.h" // must be first include - -#include "llavatarnamecache.h" - -#include "llfloaterreg.h" -#include "llnotifications.h" -#include "llurlaction.h" - -#include "llagent.h" -#include "llfloaterimsession.h" -#include "llimview.h" -#include "llfloaterimnearbychat.h" -#include "llnotificationhandler.h" - -using namespace LLNotificationsUI; - -LLNotificationHandler::LLNotificationHandler(const std::string& name, const std::string& notification_type, const std::string& parentName) -: LLNotificationChannel(name, parentName, LLNotificationFilters::filterBy(&LLNotification::getType, notification_type)) -{} - -LLSystemNotificationHandler::LLSystemNotificationHandler(const std::string& name, const std::string& notification_type) - : LLNotificationHandler(name, notification_type, "System") -{} - -LLCommunicationNotificationHandler::LLCommunicationNotificationHandler(const std::string& name, const std::string& notification_type) - : LLNotificationHandler(name, notification_type, "Communication") -{} - -// static -bool LLHandlerUtil::isIMFloaterOpened(const LLNotificationPtr& notification) -{ - bool res = false; - - LLUUID from_id = notification->getPayload()["from_id"]; - LLUUID session_id = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, from_id); - if (LLFloaterIMSession* im_floater = LLFloaterReg::findTypedInstance("impanel", session_id)) - { - res = im_floater->getVisible(); - } - - return res; -} - -// static -void LLHandlerUtil::logToIM(const EInstantMessage& session_type, - const std::string& session_name, const std::string& from_name, - const std::string& message, const LLUUID& session_owner_id, - const LLUUID& from_id) -{ - std::string from = from_name; - if (from_name.empty()) - { - from = SYSTEM_FROM; - } - - LLUUID session_id = LLIMMgr::computeSessionID(session_type, - session_owner_id); - LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession( - session_id); - if (session == NULL) - { - // replace interactive system message marker with correct from string value - if (INTERACTIVE_SYSTEM_FROM == from_name) - { - from = SYSTEM_FROM; - } - - // Build a new format username or firstname_lastname for legacy names - // to use it for a history log filename. - std::string user_name = LLCacheName::buildUsername(session_name); - LLIMModel::instance().logToFile(user_name, from, from_id, message); - } - else - { - S32 unread = session->mNumUnread; - S32 participant_unread = session->mParticipantUnreadMessageCount; - LLIMModel::instance().addMessageSilently(session_id, from, from_id, - message); - // we shouldn't increment counters when logging, so restore them - session->mNumUnread = unread; - session->mParticipantUnreadMessageCount = participant_unread; - - // update IM floater messages - updateIMFLoaterMesages(session_id); - } -} - -void log_name_callback(const LLAvatarName& av_name, const std::string& from_name, - const std::string& message, const LLUUID& from_id) - -{ - LLHandlerUtil::logToIM(IM_NOTHING_SPECIAL, av_name.getUserName(), from_name, message, - from_id, LLUUID()); -} - -// static -void LLHandlerUtil::logToIMP2P(const LLUUID& from_id, const std::string& message, bool to_file_only) -{ - if (!gCacheName) - { - return; - } - - if (from_id.isNull()) - { - // Normal behavior for system generated messages, don't spam. - // LL_WARNS() << " from_id for notification " << notification->getName() << " is null " << LL_ENDL; - return; - } - - if(to_file_only) - { - LLAvatarNameCache::get(from_id, boost::bind(&log_name_callback, _2, "", message, LLUUID())); - } - else - { - LLAvatarNameCache::get(from_id, boost::bind(&log_name_callback, _2, INTERACTIVE_SYSTEM_FROM, message, from_id)); - } -} - -// static -void LLHandlerUtil::logToIMP2P(const LLNotificationPtr& notification, bool to_file_only) -{ - LLUUID from_id = notification->getPayload()["from_id"]; - logToIMP2P(from_id, notification->getMessage(), to_file_only); -} - -// static -void LLHandlerUtil::logGroupNoticeToIMGroup( - const LLNotificationPtr& notification) -{ - - const LLSD& payload = notification->getPayload(); - LLGroupData groupData; - if (!gAgent.getGroupData(payload["group_id"].asUUID(), groupData)) - { - LL_WARNS() - << "Group notice for unknown group: " - << payload["group_id"].asUUID() << LL_ENDL; - return; - } - - const std::string group_name = groupData.mName; - const std::string sender_name = payload["sender_name"].asString(); - - LLUUID sender_id; - if (payload.has("sender_id")) - { - sender_id = payload["sender_id"].asUUID(); - } - - if (sender_id.notNull()) - { - // Legacy support and fallback method - // if we can't retrieve sender id from group notice system message, try to lookup it from cache - sender_id = LLAvatarNameCache::getInstance()->findIdByName(sender_name); - } - - logToIM(IM_SESSION_GROUP_START, group_name, sender_name, payload["message"], - payload["group_id"], sender_id); -} - -// static -void LLHandlerUtil::logToNearbyChat(const LLNotificationPtr& notification, EChatSourceType type) -{ - LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); - if (nearby_chat) - { - LLChat chat_msg(notification->getMessage()); - chat_msg.mSourceType = type; - chat_msg.mFromName = SYSTEM_FROM; - chat_msg.mFromID = LLUUID::null; - nearby_chat->addMessage(chat_msg); - } -} - -// static -LLUUID LLHandlerUtil::spawnIMSession(const std::string& name, const LLUUID& from_id) -{ - LLUUID session_id = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, from_id); - - LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession( - session_id); - if (session == NULL) - { - session_id = LLIMMgr::instance().addSession(name, IM_NOTHING_SPECIAL, from_id); - } - - return session_id; -} - -// static -std::string LLHandlerUtil::getSubstitutionName(const LLNotificationPtr& notification) -{ - std::string res = notification->getSubstitutions().has("NAME") - ? notification->getSubstitutions()["NAME"] - : notification->getSubstitutions()["[NAME]"]; - if (res.empty()) - { - LLUUID from_id = notification->getPayload()["FROM_ID"]; - - //*TODO all keys everywhere should be made of the same case, there is a mix of keys in lower and upper cases - if (from_id.isNull()) - { - from_id = notification->getPayload()["from_id"]; - } - LLAvatarName av_name; - if(LLAvatarNameCache::get(from_id, &av_name)) - { - res = av_name.getUserName(); - } - else - { - res = ""; - } - } - return res; -} - -// static -std::string LLHandlerUtil::getSubstitutionOriginalName(const LLNotificationPtr& notification) -{ - if(notification->getSubstitutions().has("ORIGINAL_NAME")) - { - std::string name = notification->getSubstitutions()["ORIGINAL_NAME"]; - if(!name.empty()) - { - return name; - } - } - return LLHandlerUtil::getSubstitutionName(notification); -} - -// static -void LLHandlerUtil::addNotifPanelToIM(const LLNotificationPtr& notification) -{ - const std::string name = LLHandlerUtil::getSubstitutionName(notification); - LLUUID from_id = notification->getPayload()["from_id"]; - - LLUUID session_id = spawnIMSession(name, from_id); - // add offer to session - LLIMModel::LLIMSession * session = LLIMModel::getInstance()->findIMSession( - session_id); - llassert_always(session != NULL); - - LLSD offer; - offer["notification_id"] = notification->getID(); - offer["from"] = SYSTEM_FROM; - offer["time"] = LLLogChat::timestamp2LogString(0, false); // Use current time - offer["index"] = (LLSD::Integer)session->mMsgs.size(); - session->mMsgs.push_front(offer); - - - // update IM floater and counters - LLSD arg; - arg["session_id"] = session_id; - arg["num_unread"] = ++(session->mNumUnread); - arg["participant_unread"] = ++(session->mParticipantUnreadMessageCount); - LLIMModel::getInstance()->mNewMsgSignal(arg); -} - -// static -void LLHandlerUtil::updateIMFLoaterMesages(const LLUUID& session_id) -{ - LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); - if (im_floater != NULL && im_floater->getVisible()) - { - im_floater->updateMessages(); - } -} - -// static -void LLHandlerUtil::updateVisibleIMFLoaterMesages(const LLNotificationPtr& notification) -{ - const std::string name = LLHandlerUtil::getSubstitutionName(notification); - LLUUID from_id = notification->getPayload()["from_id"]; - LLUUID session_id = spawnIMSession(name, from_id); - - updateIMFLoaterMesages(session_id); -} - -// static -void LLHandlerUtil::decIMMesageCounter(const LLNotificationPtr& notification) -{ - const std::string name = LLHandlerUtil::getSubstitutionName(notification); - LLUUID from_id = notification->getPayload()["from_id"]; - LLUUID session_id = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, from_id); - - LLIMModel::LLIMSession * session = LLIMModel::getInstance()->findIMSession(session_id); - - if (session) - { - LLSD arg; - arg["session_id"] = session_id; - session->mNumUnread--; - arg["num_unread"] = session->mNumUnread; - session->mParticipantUnreadMessageCount--; - arg["participant_unread"] = session->mParticipantUnreadMessageCount; - LLIMModel::getInstance()->mNewMsgSignal(arg); -} -} - +/** + * @file llnotificationofferhandler.cpp + * @brief Provides set of utility methods for notifications processing. + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#include "llviewerprecompiledheaders.h" // must be first include + +#include "llavatarnamecache.h" + +#include "llfloaterreg.h" +#include "llnotifications.h" +#include "llurlaction.h" + +#include "llagent.h" +#include "llfloaterimsession.h" +#include "llimview.h" +#include "llfloaterimnearbychat.h" +#include "llnotificationhandler.h" + +using namespace LLNotificationsUI; + +LLNotificationHandler::LLNotificationHandler(const std::string& name, const std::string& notification_type, const std::string& parentName) +: LLNotificationChannel(name, parentName, LLNotificationFilters::filterBy(&LLNotification::getType, notification_type)) +{} + +LLSystemNotificationHandler::LLSystemNotificationHandler(const std::string& name, const std::string& notification_type) + : LLNotificationHandler(name, notification_type, "System") +{} + +LLCommunicationNotificationHandler::LLCommunicationNotificationHandler(const std::string& name, const std::string& notification_type) + : LLNotificationHandler(name, notification_type, "Communication") +{} + +// static +bool LLHandlerUtil::isIMFloaterOpened(const LLNotificationPtr& notification) +{ + bool res = false; + + LLUUID from_id = notification->getPayload()["from_id"]; + LLUUID session_id = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, from_id); + if (LLFloaterIMSession* im_floater = LLFloaterReg::findTypedInstance("impanel", session_id)) + { + res = im_floater->getVisible(); + } + + return res; +} + +// static +void LLHandlerUtil::logToIM(const EInstantMessage& session_type, + const std::string& session_name, const std::string& from_name, + const std::string& message, const LLUUID& session_owner_id, + const LLUUID& from_id) +{ + std::string from = from_name; + if (from_name.empty()) + { + from = SYSTEM_FROM; + } + + LLUUID session_id = LLIMMgr::computeSessionID(session_type, + session_owner_id); + LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession( + session_id); + if (session == NULL) + { + // replace interactive system message marker with correct from string value + if (INTERACTIVE_SYSTEM_FROM == from_name) + { + from = SYSTEM_FROM; + } + + // Build a new format username or firstname_lastname for legacy names + // to use it for a history log filename. + std::string user_name = LLCacheName::buildUsername(session_name); + LLIMModel::instance().logToFile(user_name, from, from_id, message); + } + else + { + S32 unread = session->mNumUnread; + S32 participant_unread = session->mParticipantUnreadMessageCount; + LLIMModel::instance().addMessageSilently(session_id, from, from_id, + message); + // we shouldn't increment counters when logging, so restore them + session->mNumUnread = unread; + session->mParticipantUnreadMessageCount = participant_unread; + + // update IM floater messages + updateIMFLoaterMesages(session_id); + } +} + +void log_name_callback(const LLAvatarName& av_name, const std::string& from_name, + const std::string& message, const LLUUID& from_id) + +{ + LLHandlerUtil::logToIM(IM_NOTHING_SPECIAL, av_name.getUserName(), from_name, message, + from_id, LLUUID()); +} + +// static +void LLHandlerUtil::logToIMP2P(const LLUUID& from_id, const std::string& message, bool to_file_only) +{ + if (!gCacheName) + { + return; + } + + if (from_id.isNull()) + { + // Normal behavior for system generated messages, don't spam. + // LL_WARNS() << " from_id for notification " << notification->getName() << " is null " << LL_ENDL; + return; + } + + if(to_file_only) + { + LLAvatarNameCache::get(from_id, boost::bind(&log_name_callback, _2, "", message, LLUUID())); + } + else + { + LLAvatarNameCache::get(from_id, boost::bind(&log_name_callback, _2, INTERACTIVE_SYSTEM_FROM, message, from_id)); + } +} + +// static +void LLHandlerUtil::logToIMP2P(const LLNotificationPtr& notification, bool to_file_only) +{ + LLUUID from_id = notification->getPayload()["from_id"]; + logToIMP2P(from_id, notification->getMessage(), to_file_only); +} + +// static +void LLHandlerUtil::logGroupNoticeToIMGroup( + const LLNotificationPtr& notification) +{ + + const LLSD& payload = notification->getPayload(); + LLGroupData groupData; + if (!gAgent.getGroupData(payload["group_id"].asUUID(), groupData)) + { + LL_WARNS() + << "Group notice for unknown group: " + << payload["group_id"].asUUID() << LL_ENDL; + return; + } + + const std::string group_name = groupData.mName; + const std::string sender_name = payload["sender_name"].asString(); + + LLUUID sender_id; + if (payload.has("sender_id")) + { + sender_id = payload["sender_id"].asUUID(); + } + + if (sender_id.notNull()) + { + // Legacy support and fallback method + // if we can't retrieve sender id from group notice system message, try to lookup it from cache + sender_id = LLAvatarNameCache::getInstance()->findIdByName(sender_name); + } + + logToIM(IM_SESSION_GROUP_START, group_name, sender_name, payload["message"], + payload["group_id"], sender_id); +} + +// static +void LLHandlerUtil::logToNearbyChat(const LLNotificationPtr& notification, EChatSourceType type) +{ + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); + if (nearby_chat) + { + LLChat chat_msg(notification->getMessage()); + chat_msg.mSourceType = type; + chat_msg.mFromName = SYSTEM_FROM; + chat_msg.mFromID = LLUUID::null; + nearby_chat->addMessage(chat_msg); + } +} + +// static +LLUUID LLHandlerUtil::spawnIMSession(const std::string& name, const LLUUID& from_id) +{ + LLUUID session_id = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, from_id); + + LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession( + session_id); + if (session == NULL) + { + session_id = LLIMMgr::instance().addSession(name, IM_NOTHING_SPECIAL, from_id); + } + + return session_id; +} + +// static +std::string LLHandlerUtil::getSubstitutionName(const LLNotificationPtr& notification) +{ + std::string res = notification->getSubstitutions().has("NAME") + ? notification->getSubstitutions()["NAME"] + : notification->getSubstitutions()["[NAME]"]; + if (res.empty()) + { + LLUUID from_id = notification->getPayload()["FROM_ID"]; + + //*TODO all keys everywhere should be made of the same case, there is a mix of keys in lower and upper cases + if (from_id.isNull()) + { + from_id = notification->getPayload()["from_id"]; + } + LLAvatarName av_name; + if(LLAvatarNameCache::get(from_id, &av_name)) + { + res = av_name.getUserName(); + } + else + { + res = ""; + } + } + return res; +} + +// static +std::string LLHandlerUtil::getSubstitutionOriginalName(const LLNotificationPtr& notification) +{ + if(notification->getSubstitutions().has("ORIGINAL_NAME")) + { + std::string name = notification->getSubstitutions()["ORIGINAL_NAME"]; + if(!name.empty()) + { + return name; + } + } + return LLHandlerUtil::getSubstitutionName(notification); +} + +// static +void LLHandlerUtil::addNotifPanelToIM(const LLNotificationPtr& notification) +{ + const std::string name = LLHandlerUtil::getSubstitutionName(notification); + LLUUID from_id = notification->getPayload()["from_id"]; + + LLUUID session_id = spawnIMSession(name, from_id); + // add offer to session + LLIMModel::LLIMSession * session = LLIMModel::getInstance()->findIMSession( + session_id); + llassert_always(session != NULL); + + LLSD offer; + offer["notification_id"] = notification->getID(); + offer["from"] = SYSTEM_FROM; + offer["time"] = LLLogChat::timestamp2LogString(0, false); // Use current time + offer["index"] = (LLSD::Integer)session->mMsgs.size(); + session->mMsgs.push_front(offer); + + + // update IM floater and counters + LLSD arg; + arg["session_id"] = session_id; + arg["num_unread"] = ++(session->mNumUnread); + arg["participant_unread"] = ++(session->mParticipantUnreadMessageCount); + LLIMModel::getInstance()->mNewMsgSignal(arg); +} + +// static +void LLHandlerUtil::updateIMFLoaterMesages(const LLUUID& session_id) +{ + LLFloaterIMSession* im_floater = LLFloaterIMSession::findInstance(session_id); + if (im_floater != NULL && im_floater->getVisible()) + { + im_floater->updateMessages(); + } +} + +// static +void LLHandlerUtil::updateVisibleIMFLoaterMesages(const LLNotificationPtr& notification) +{ + const std::string name = LLHandlerUtil::getSubstitutionName(notification); + LLUUID from_id = notification->getPayload()["from_id"]; + LLUUID session_id = spawnIMSession(name, from_id); + + updateIMFLoaterMesages(session_id); +} + +// static +void LLHandlerUtil::decIMMesageCounter(const LLNotificationPtr& notification) +{ + const std::string name = LLHandlerUtil::getSubstitutionName(notification); + LLUUID from_id = notification->getPayload()["from_id"]; + LLUUID session_id = LLIMMgr::computeSessionID(IM_NOTHING_SPECIAL, from_id); + + LLIMModel::LLIMSession * session = LLIMModel::getInstance()->findIMSession(session_id); + + if (session) + { + LLSD arg; + arg["session_id"] = session_id; + session->mNumUnread--; + arg["num_unread"] = session->mNumUnread; + session->mParticipantUnreadMessageCount--; + arg["participant_unread"] = session->mParticipantUnreadMessageCount; + LLIMModel::getInstance()->mNewMsgSignal(arg); +} +} + diff --git a/indra/newview/llnotificationlistitem.cpp b/indra/newview/llnotificationlistitem.cpp index 798070daf7..5b8b28ebe6 100644 --- a/indra/newview/llnotificationlistitem.cpp +++ b/indra/newview/llnotificationlistitem.cpp @@ -1,619 +1,619 @@ -/** - * @file llnotificationlistitem.cpp - * @brief - * - * $LicenseInfo:firstyear=2015&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2015, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#include "llviewerprecompiledheaders.h" // must be first include - -#include "llnotificationlistitem.h" - -#include "llagent.h" -#include "llgroupactions.h" -#include "llinventoryicon.h" -#include "llwindow.h" -#include "v4color.h" -#include "lltrans.h" -#include "lluicolortable.h" -#include "message.h" -#include "llnotificationsutil.h" -#include - -LLNotificationListItem::LLNotificationListItem(const Params& p) : LLPanel(p), - mParams(p), - mTitleBox(NULL), - mExpandBtn(NULL), - mCondenseBtn(NULL), - mCloseBtn(NULL), - mCondensedViewPanel(NULL), - mExpandedViewPanel(NULL), - mCondensedHeight(0), - mExpandedHeight(0), - mExpandedHeightResize(0), - mExpanded(false) -{ - mNotificationName = p.notification_name; -} - -bool LLNotificationListItem::postBuild() -{ - bool rv = LLPanel::postBuild(); - mTitleBox = getChild("notification_title"); - mTitleBoxExp = getChild("notification_title_exp"); - mNoticeTextExp = getChild("notification_text_exp"); - - mTimeBox = getChild("notification_time"); - mTimeBoxExp = getChild("notification_time_exp"); - mExpandBtn = getChild("expand_btn"); - mCondenseBtn = getChild("condense_btn"); - mCloseBtn = getChild("close_btn"); - mCloseBtnExp = getChild("close_expanded_btn"); - - mTitleBox->setValue(mParams.title); - mTitleBoxExp->setValue(mParams.title); - mNoticeTextExp->setValue(mParams.title); - mNoticeTextExp->setEnabled(false); - mNoticeTextExp->setTextExpandedCallback(boost::bind(&LLNotificationListItem::reshapeNotification, this)); - - mTitleBox->setContentTrusted(false); - mTitleBoxExp->setContentTrusted(false); - mNoticeTextExp->setContentTrusted(false); - - mTimeBox->setValue(buildNotificationDate(mParams.time_stamp)); - mTimeBoxExp->setValue(buildNotificationDate(mParams.time_stamp)); - - mExpandBtn->setClickedCallback(boost::bind(&LLNotificationListItem::onClickExpandBtn,this)); - mCondenseBtn->setClickedCallback(boost::bind(&LLNotificationListItem::onClickCondenseBtn,this)); - - //mCloseBtn and mCloseExpandedBtn share the same callback - mCloseBtn->setClickedCallback(boost::bind(&LLNotificationListItem::onClickCloseBtn,this)); - mCloseBtnExp->setClickedCallback(boost::bind(&LLNotificationListItem::onClickCloseBtn,this)); - - mCondensedViewPanel = getChild("layout_panel_condensed_view"); - mExpandedViewPanel = getChild("layout_panel_expanded_view"); - - std::string expanded_height_str = getString("item_expanded_height"); - std::string condensed_height_str = getString("item_condensed_height"); - - mExpandedHeight = (S32)atoi(expanded_height_str.c_str()); - mCondensedHeight = (S32)atoi(condensed_height_str.c_str()); - - setExpanded(false); - - return rv; -} - -LLNotificationListItem::~LLNotificationListItem() -{ -} - -//static -std::string LLNotificationListItem::buildNotificationDate(const LLDate& time_stamp, ETimeType time_type) -{ - std::string timeStr; - switch(time_type) - { - case Local: - timeStr = "[" + LLTrans::getString("LTimeMthNum") + "]/[" - +LLTrans::getString("LTimeDay")+"]/[" - +LLTrans::getString("LTimeYear")+"] [" - +LLTrans::getString("LTimeHour")+"]:[" - +LLTrans::getString("LTimeMin")+ "]"; - break; - case UTC: - timeStr = "[" + LLTrans::getString("UTCTimeMth") + "]/[" - +LLTrans::getString("UTCTimeDay")+"]/[" - +LLTrans::getString("UTCTimeYr")+"] [" - +LLTrans::getString("UTCTimeHr")+"]:[" - +LLTrans::getString("UTCTimeMin")+"] [" - +LLTrans::getString("UTCTimeTimezone")+"]"; - break; - case SLT: - default: - timeStr = "[" + LLTrans::getString("TimeMonth") + "]/[" - +LLTrans::getString("TimeDay")+"]/[" - +LLTrans::getString("TimeYear")+"] [" - +LLTrans::getString("TimeHour")+"]:[" - +LLTrans::getString("TimeMin")+"] [" - +LLTrans::getString("TimeTimezone")+"]"; - break; - } - LLSD substitution; - substitution["datetime"] = time_stamp; - LLStringUtil::format(timeStr, substitution); - return timeStr; -} - -void LLNotificationListItem::onClickCloseBtn() -{ - mOnItemClose(this); - close(); -} - -bool LLNotificationListItem::handleMouseUp(S32 x, S32 y, MASK mask) -{ - bool res = LLPanel::handleMouseUp(x, y, mask); - mOnItemClick(this); - return res; -} - -void LLNotificationListItem::onMouseEnter(S32 x, S32 y, MASK mask) -{ - mCondensedViewPanel->setTransparentColor(LLUIColorTable::instance().getColor( "ScrollHoveredColor" )); - mExpandedViewPanel->setTransparentColor(LLUIColorTable::instance().getColor( "ScrollHoveredColor" )); -} - -void LLNotificationListItem::onMouseLeave(S32 x, S32 y, MASK mask) -{ - mCondensedViewPanel->setTransparentColor(LLUIColorTable::instance().getColor( "SysWellItemUnselected" )); - mExpandedViewPanel->setTransparentColor(LLUIColorTable::instance().getColor( "SysWellItemUnselected" )); -} - -//static -LLNotificationListItem* LLNotificationListItem::create(const Params& p) -{ - if (LLNotificationListItem::getGroupInviteTypes().count(p.notification_name)) - { - return new LLGroupInviteNotificationListItem(p); - } - else if (LLNotificationListItem::getGroupNoticeTypes().count(p.notification_name)) - { - return new LLGroupNoticeNotificationListItem(p); - } - else if (LLNotificationListItem::getTransactionTypes().count(p.notification_name)) - { - return new LLTransactionNotificationListItem(p); - } - return new LLSystemNotificationListItem(p); -} - -//static -std::set LLNotificationListItem::getGroupInviteTypes() -{ - return LLGroupInviteNotificationListItem::getTypes(); -} - - -std::set LLNotificationListItem::getGroupNoticeTypes() -{ - return LLGroupNoticeNotificationListItem::getTypes(); -} - -//static -std::set LLNotificationListItem::getTransactionTypes() -{ - return LLTransactionNotificationListItem::getTypes(); -} - -void LLNotificationListItem::onClickExpandBtn() -{ - setExpanded(true); -} - -void LLNotificationListItem::onClickCondenseBtn() -{ - setExpanded(false); -} - -void LLNotificationListItem::reshapeNotification() -{ - if(mExpanded) - { - S32 width = this->getRect().getWidth(); - this->reshape(width, mNoticeTextExp->getRect().getHeight() + mExpandedHeight, false); - } -} - -void LLNotificationListItem::setExpanded(bool value) -{ - mCondensedViewPanel->setVisible(!value); - mExpandedViewPanel->setVisible(value); - S32 width = this->getRect().getWidth(); - - if (value) - { - this->reshape(width, mNoticeTextExp->getRect().getHeight() + mExpandedHeight, false); - } - else - { - this->reshape(width, mCondensedHeight, false); - } - mExpanded = value; - -} - -std::set LLGroupInviteNotificationListItem::getTypes() -{ - std::set types; - types.insert("JoinGroup"); - return types; -} - -std::set LLGroupNoticeNotificationListItem::getTypes() -{ - std::set types; - types.insert("GroupNotice"); - return types; -} - -std::set LLTransactionNotificationListItem::getTypes() -{ - std::set types; - types.insert("PaymentReceived"); - types.insert("PaymentSent"); - types.insert("UploadPayment"); - return types; -} - -LLGroupNotificationListItem::LLGroupNotificationListItem(const Params& p) - : LLNotificationListItem(p), - mSenderOrFeeBox(NULL) -{ -} - -LLGroupInviteNotificationListItem::LLGroupInviteNotificationListItem(const Params& p) - : LLGroupNotificationListItem(p) -{ - buildFromFile("panel_notification_list_item.xml"); -} - -bool LLGroupInviteNotificationListItem::postBuild() -{ - bool rv = LLGroupNotificationListItem::postBuild(); - setFee(mParams.fee); - mInviteButtonPanel = getChild("button_panel"); - mInviteButtonPanel->setVisible(true); - mJoinBtn = getChild("join_btn"); - mDeclineBtn = getChild("decline_btn"); - mInfoBtn = getChild("info_btn"); - - //invitation with any non-default group role, doesn't have newline characters at the end unlike simple invitations - std::string invitation_desc = mNoticeTextExp->getValue().asString(); - if (invitation_desc.substr(invitation_desc.size() - 2) != "\n\n") - { - invitation_desc += "\n\n"; - mNoticeTextExp->setValue(invitation_desc); - } - - mJoinBtn->setClickedCallback(boost::bind(&LLGroupInviteNotificationListItem::onClickJoinBtn,this)); - mDeclineBtn->setClickedCallback(boost::bind(&LLGroupInviteNotificationListItem::onClickDeclineBtn,this)); - mInfoBtn->setClickedCallback(boost::bind(&LLGroupInviteNotificationListItem::onClickInfoBtn,this)); - - std::string expanded_height_resize_str = getString("expanded_height_resize_for_attachment"); - mExpandedHeightResize = (S32)atoi(expanded_height_resize_str.c_str()); - - return rv; -} - -void LLGroupInviteNotificationListItem::onClickJoinBtn() -{ - if (!gAgent.canJoinGroups()) - { - LLNotificationsUtil::add("JoinedTooManyGroups"); - return; - } - - send_join_group_response(mParams.group_id, mParams.transaction_id, true, mParams.fee, mParams.use_offline_cap); - - LLNotificationListItem::onClickCloseBtn(); -} - -void LLGroupInviteNotificationListItem::onClickDeclineBtn() -{ - send_join_group_response(mParams.group_id, mParams.transaction_id, false, mParams.fee, mParams.use_offline_cap); - - LLNotificationListItem::onClickCloseBtn(); -} - -void LLGroupInviteNotificationListItem::onClickInfoBtn() -{ - LLGroupActions::show(mParams.group_id); -} - -void LLGroupInviteNotificationListItem::setFee(S32 fee) -{ - LLStringUtil::format_map_t string_args; - string_args["[GROUP_FEE]"] = llformat("%d", fee); - std::string fee_text = getString("group_fee_text", string_args); - mSenderOrFeeBox->setValue(fee_text); - mSenderOrFeeBoxExp->setValue(fee_text); - mSenderOrFeeBox->setVisible(true); - mSenderOrFeeBoxExp->setVisible(true); -} - -LLGroupNoticeNotificationListItem::LLGroupNoticeNotificationListItem(const Params& p) - : LLGroupNotificationListItem(p), - mAttachmentPanel(NULL), - mAttachmentTextBox(NULL), - mAttachmentIcon(NULL), - mAttachmentIconExp(NULL), - mInventoryOffer(NULL) -{ - if (mParams.inventory_offer.isDefined()) - { - mInventoryOffer = new LLOfferInfo(mParams.inventory_offer); - } - - buildFromFile("panel_notification_list_item.xml"); -} - -LLGroupNotificationListItem::~LLGroupNotificationListItem() -{ - LLGroupMgr::getInstance()->removeObserver(this); -} - -bool LLGroupNoticeNotificationListItem::postBuild() -{ - bool rv = LLGroupNotificationListItem::postBuild(); - - mAttachmentTextBox = getChild("attachment_text"); - mAttachmentIcon = getChild("attachment_icon"); - mAttachmentIconExp = getChild("attachment_icon_exp"); - mAttachmentPanel = getChild("attachment_panel"); - mAttachmentPanel->setVisible(false); - - - mTitleBox->setValue(mParams.subject); - mTitleBoxExp->setValue(mParams.subject); - mNoticeTextExp->setValue(mParams.message); - - mTimeBox->setValue(buildNotificationDate(mParams.time_stamp)); - mTimeBoxExp->setValue(buildNotificationDate(mParams.time_stamp)); - //Workaround: in case server timestamp is 0 - we use the time when notification was actually received - if (mParams.time_stamp.isNull()) - { - mTimeBox->setValue(buildNotificationDate(mParams.received_time)); - mTimeBoxExp->setValue(buildNotificationDate(mParams.received_time)); - } - setSender(mParams.sender); - - if (mInventoryOffer != NULL) - { - mAttachmentTextBox->setValue(mInventoryOffer->mDesc); - mAttachmentTextBox->setVisible(true); - mAttachmentIcon->setVisible(true); - - std::string icon_name = LLInventoryIcon::getIconName(mInventoryOffer->mType, - LLInventoryType::IT_TEXTURE); - mAttachmentIconExp->setValue(icon_name); - mAttachmentIconExp->setVisible(true); - - mAttachmentTextBox->setClickedCallback(boost::bind( - &LLGroupNoticeNotificationListItem::onClickAttachment, this)); - - std::string expanded_height_resize_str = getString("expanded_height_resize_for_attachment"); - mExpandedHeightResize = (S32)atoi(expanded_height_resize_str.c_str()); - - mAttachmentPanel->setVisible(true); - } - return rv; -} - -bool LLGroupNotificationListItem::postBuild() -{ - bool rv = LLNotificationListItem::postBuild(); - - mGroupIcon = getChild("group_icon"); - mGroupIconExp = getChild("group_icon_exp"); - mGroupNameBoxExp = getChild("group_name_exp"); - - mGroupIcon->setValue(mParams.group_id); - mGroupIconExp->setValue(mParams.group_id); - - mGroupIcon->setVisible(true); - mGroupIconExp->setVisible(true); - - mGroupId = mParams.group_id; - - mSenderOrFeeBox = getChild("sender_or_fee_box"); - mSenderOrFeeBoxExp = getChild("sender_or_fee_box_exp"); - - LLSD value(mParams.group_id); - setGroupId(value); - - return rv; -} - -void LLGroupNotificationListItem::changed(LLGroupChange gc) -{ - if (GC_PROPERTIES == gc) - { - updateFromCache(); - LLGroupMgr::getInstance()->removeObserver(this); - } -} - -bool LLGroupNotificationListItem::updateFromCache() -{ - LLGroupMgrGroupData* group_data = LLGroupMgr::getInstance()->getGroupData(mGroupId); - if (!group_data) return false; - setGroupName(group_data->mName); - return true; -} - -void LLGroupNotificationListItem::setGroupId(const LLUUID& value) -{ - LLGroupMgr* gm = LLGroupMgr::getInstance(); - if (mGroupId.notNull()) - { - gm->removeObserver(this); - - - mID = mGroupId; - - // Check if cache already contains image_id for that group - if (!updateFromCache()) - { - gm->addObserver(this); - gm->sendGroupPropertiesRequest(mGroupId); - } - } -} - -void LLGroupNotificationListItem::setGroupName(std::string name) -{ - if (!name.empty()) - { - LLStringUtil::format_map_t string_args; - string_args["[GROUP_NAME]"] = llformat("%s", name.c_str()); - std::string group_box_str = getString("group_name_text", string_args); - mGroupNameBoxExp->setValue(group_box_str); - mGroupNameBoxExp->setVisible(true); - } - else - { - mGroupNameBoxExp->setValue(LLStringUtil::null); - mGroupNameBoxExp->setVisible(false); - } -} - -void LLGroupNoticeNotificationListItem::setSender(std::string sender) -{ - if (!sender.empty()) - { - LLStringUtil::format_map_t string_args; - string_args["[SENDER_RESIDENT]"] = llformat("%s", sender.c_str()); - std::string sender_text = getString("sender_resident_text", string_args); - mSenderOrFeeBox->setValue(sender_text); - mSenderOrFeeBoxExp->setValue(sender_text); - mSenderOrFeeBox->setVisible(true); - mSenderOrFeeBoxExp->setVisible(true); - } else { - mSenderOrFeeBox->setValue(LLStringUtil::null); - mSenderOrFeeBoxExp->setValue(LLStringUtil::null); - mSenderOrFeeBox->setVisible(false); - mSenderOrFeeBoxExp->setVisible(false); - } -} -void LLGroupNoticeNotificationListItem::close() -{ - // The group notice dialog may be an inventory offer. - // If it has an inventory save button and that button is still enabled - // Then we need to send the inventory declined message - if (mInventoryOffer != NULL) - { - mInventoryOffer->forceResponse(IOR_DECLINE); - mInventoryOffer = NULL; - } -} - -void LLGroupNoticeNotificationListItem::onClickAttachment() -{ - if (mInventoryOffer != NULL) { - static const LLUIColor textColor = LLUIColorTable::instance().getColor( - "GroupNotifyDimmedTextColor"); - mAttachmentTextBox->setColor(textColor); - mAttachmentIconExp->setEnabled(false); - - //if attachment isn't openable - notify about saving - if (!isAttachmentOpenable(mInventoryOffer->mType)) { - LLNotifications::instance().add("AttachmentSaved", LLSD(), LLSD()); - } - mInventoryOffer->forceResponse(IOR_ACCEPT); - mInventoryOffer = NULL; - } -} - -//static -bool LLGroupNoticeNotificationListItem::isAttachmentOpenable(LLAssetType::EType type) -{ - switch (type) - { - case LLAssetType::AT_LANDMARK: - case LLAssetType::AT_NOTECARD: - case LLAssetType::AT_IMAGE_JPEG: - case LLAssetType::AT_IMAGE_TGA: - case LLAssetType::AT_TEXTURE: - case LLAssetType::AT_TEXTURE_TGA: - return true; - default: - return false; - } -} - -LLTransactionNotificationListItem::LLTransactionNotificationListItem(const Params& p) - : LLNotificationListItem(p), - mAvatarIcon(NULL) -{ - buildFromFile("panel_notification_list_item.xml"); -} - -bool LLTransactionNotificationListItem::postBuild() -{ - bool rv = LLNotificationListItem::postBuild(); - mAvatarIcon = getChild("avatar_icon"); - mAvatarIconExp = getChild("avatar_icon_exp"); - mAvatarIcon->setValue("System_Notification"); - mAvatarIconExp->setValue("System_Notification"); - - mAvatarIcon->setVisible(true); - mAvatarIconExp->setVisible(true); - if((GOVERNOR_LINDEN_ID == mParams.paid_to_id) || - (GOVERNOR_LINDEN_ID == mParams.paid_from_id)) - { - return rv; - } - - if (mParams.notification_name == "PaymentReceived") - { - mAvatarIcon->setValue(mParams.paid_from_id); - mAvatarIconExp->setValue(mParams.paid_from_id); - } - else if (mParams.notification_name == "PaymentSent") - { - mAvatarIcon->setValue(mParams.paid_to_id); - mAvatarIconExp->setValue(mParams.paid_to_id); - } - - return rv; -} - -LLSystemNotificationListItem::LLSystemNotificationListItem(const Params& p) - : LLNotificationListItem(p), - mSystemNotificationIcon(NULL), - mIsCaution(false) -{ - buildFromFile("panel_notification_list_item.xml"); - mIsCaution = p.notification_priority >= NOTIFICATION_PRIORITY_HIGH; - if (mIsCaution) - { - mTitleBox->setColor(LLUIColorTable::instance().getColor("NotifyCautionBoxColor")); - mTitleBoxExp->setColor(LLUIColorTable::instance().getColor("NotifyCautionBoxColor")); - mNoticeTextExp->setReadOnlyColor(LLUIColorTable::instance().getColor("NotifyCautionBoxColor")); - mTimeBox->setColor(LLUIColorTable::instance().getColor("NotifyCautionBoxColor")); - mTimeBoxExp->setColor(LLUIColorTable::instance().getColor("NotifyCautionBoxColor")); - } -} - -bool LLSystemNotificationListItem::postBuild() -{ - bool rv = LLNotificationListItem::postBuild(); - mSystemNotificationIcon = getChild("system_notification_icon"); - mSystemNotificationIconExp = getChild("system_notification_icon_exp"); - if (mSystemNotificationIcon) - mSystemNotificationIcon->setVisible(true); - if (mSystemNotificationIconExp) - mSystemNotificationIconExp->setVisible(true); - return rv; -} +/** + * @file llnotificationlistitem.cpp + * @brief + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2015, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#include "llviewerprecompiledheaders.h" // must be first include + +#include "llnotificationlistitem.h" + +#include "llagent.h" +#include "llgroupactions.h" +#include "llinventoryicon.h" +#include "llwindow.h" +#include "v4color.h" +#include "lltrans.h" +#include "lluicolortable.h" +#include "message.h" +#include "llnotificationsutil.h" +#include + +LLNotificationListItem::LLNotificationListItem(const Params& p) : LLPanel(p), + mParams(p), + mTitleBox(NULL), + mExpandBtn(NULL), + mCondenseBtn(NULL), + mCloseBtn(NULL), + mCondensedViewPanel(NULL), + mExpandedViewPanel(NULL), + mCondensedHeight(0), + mExpandedHeight(0), + mExpandedHeightResize(0), + mExpanded(false) +{ + mNotificationName = p.notification_name; +} + +bool LLNotificationListItem::postBuild() +{ + bool rv = LLPanel::postBuild(); + mTitleBox = getChild("notification_title"); + mTitleBoxExp = getChild("notification_title_exp"); + mNoticeTextExp = getChild("notification_text_exp"); + + mTimeBox = getChild("notification_time"); + mTimeBoxExp = getChild("notification_time_exp"); + mExpandBtn = getChild("expand_btn"); + mCondenseBtn = getChild("condense_btn"); + mCloseBtn = getChild("close_btn"); + mCloseBtnExp = getChild("close_expanded_btn"); + + mTitleBox->setValue(mParams.title); + mTitleBoxExp->setValue(mParams.title); + mNoticeTextExp->setValue(mParams.title); + mNoticeTextExp->setEnabled(false); + mNoticeTextExp->setTextExpandedCallback(boost::bind(&LLNotificationListItem::reshapeNotification, this)); + + mTitleBox->setContentTrusted(false); + mTitleBoxExp->setContentTrusted(false); + mNoticeTextExp->setContentTrusted(false); + + mTimeBox->setValue(buildNotificationDate(mParams.time_stamp)); + mTimeBoxExp->setValue(buildNotificationDate(mParams.time_stamp)); + + mExpandBtn->setClickedCallback(boost::bind(&LLNotificationListItem::onClickExpandBtn,this)); + mCondenseBtn->setClickedCallback(boost::bind(&LLNotificationListItem::onClickCondenseBtn,this)); + + //mCloseBtn and mCloseExpandedBtn share the same callback + mCloseBtn->setClickedCallback(boost::bind(&LLNotificationListItem::onClickCloseBtn,this)); + mCloseBtnExp->setClickedCallback(boost::bind(&LLNotificationListItem::onClickCloseBtn,this)); + + mCondensedViewPanel = getChild("layout_panel_condensed_view"); + mExpandedViewPanel = getChild("layout_panel_expanded_view"); + + std::string expanded_height_str = getString("item_expanded_height"); + std::string condensed_height_str = getString("item_condensed_height"); + + mExpandedHeight = (S32)atoi(expanded_height_str.c_str()); + mCondensedHeight = (S32)atoi(condensed_height_str.c_str()); + + setExpanded(false); + + return rv; +} + +LLNotificationListItem::~LLNotificationListItem() +{ +} + +//static +std::string LLNotificationListItem::buildNotificationDate(const LLDate& time_stamp, ETimeType time_type) +{ + std::string timeStr; + switch(time_type) + { + case Local: + timeStr = "[" + LLTrans::getString("LTimeMthNum") + "]/[" + +LLTrans::getString("LTimeDay")+"]/[" + +LLTrans::getString("LTimeYear")+"] [" + +LLTrans::getString("LTimeHour")+"]:[" + +LLTrans::getString("LTimeMin")+ "]"; + break; + case UTC: + timeStr = "[" + LLTrans::getString("UTCTimeMth") + "]/[" + +LLTrans::getString("UTCTimeDay")+"]/[" + +LLTrans::getString("UTCTimeYr")+"] [" + +LLTrans::getString("UTCTimeHr")+"]:[" + +LLTrans::getString("UTCTimeMin")+"] [" + +LLTrans::getString("UTCTimeTimezone")+"]"; + break; + case SLT: + default: + timeStr = "[" + LLTrans::getString("TimeMonth") + "]/[" + +LLTrans::getString("TimeDay")+"]/[" + +LLTrans::getString("TimeYear")+"] [" + +LLTrans::getString("TimeHour")+"]:[" + +LLTrans::getString("TimeMin")+"] [" + +LLTrans::getString("TimeTimezone")+"]"; + break; + } + LLSD substitution; + substitution["datetime"] = time_stamp; + LLStringUtil::format(timeStr, substitution); + return timeStr; +} + +void LLNotificationListItem::onClickCloseBtn() +{ + mOnItemClose(this); + close(); +} + +bool LLNotificationListItem::handleMouseUp(S32 x, S32 y, MASK mask) +{ + bool res = LLPanel::handleMouseUp(x, y, mask); + mOnItemClick(this); + return res; +} + +void LLNotificationListItem::onMouseEnter(S32 x, S32 y, MASK mask) +{ + mCondensedViewPanel->setTransparentColor(LLUIColorTable::instance().getColor( "ScrollHoveredColor" )); + mExpandedViewPanel->setTransparentColor(LLUIColorTable::instance().getColor( "ScrollHoveredColor" )); +} + +void LLNotificationListItem::onMouseLeave(S32 x, S32 y, MASK mask) +{ + mCondensedViewPanel->setTransparentColor(LLUIColorTable::instance().getColor( "SysWellItemUnselected" )); + mExpandedViewPanel->setTransparentColor(LLUIColorTable::instance().getColor( "SysWellItemUnselected" )); +} + +//static +LLNotificationListItem* LLNotificationListItem::create(const Params& p) +{ + if (LLNotificationListItem::getGroupInviteTypes().count(p.notification_name)) + { + return new LLGroupInviteNotificationListItem(p); + } + else if (LLNotificationListItem::getGroupNoticeTypes().count(p.notification_name)) + { + return new LLGroupNoticeNotificationListItem(p); + } + else if (LLNotificationListItem::getTransactionTypes().count(p.notification_name)) + { + return new LLTransactionNotificationListItem(p); + } + return new LLSystemNotificationListItem(p); +} + +//static +std::set LLNotificationListItem::getGroupInviteTypes() +{ + return LLGroupInviteNotificationListItem::getTypes(); +} + + +std::set LLNotificationListItem::getGroupNoticeTypes() +{ + return LLGroupNoticeNotificationListItem::getTypes(); +} + +//static +std::set LLNotificationListItem::getTransactionTypes() +{ + return LLTransactionNotificationListItem::getTypes(); +} + +void LLNotificationListItem::onClickExpandBtn() +{ + setExpanded(true); +} + +void LLNotificationListItem::onClickCondenseBtn() +{ + setExpanded(false); +} + +void LLNotificationListItem::reshapeNotification() +{ + if(mExpanded) + { + S32 width = this->getRect().getWidth(); + this->reshape(width, mNoticeTextExp->getRect().getHeight() + mExpandedHeight, false); + } +} + +void LLNotificationListItem::setExpanded(bool value) +{ + mCondensedViewPanel->setVisible(!value); + mExpandedViewPanel->setVisible(value); + S32 width = this->getRect().getWidth(); + + if (value) + { + this->reshape(width, mNoticeTextExp->getRect().getHeight() + mExpandedHeight, false); + } + else + { + this->reshape(width, mCondensedHeight, false); + } + mExpanded = value; + +} + +std::set LLGroupInviteNotificationListItem::getTypes() +{ + std::set types; + types.insert("JoinGroup"); + return types; +} + +std::set LLGroupNoticeNotificationListItem::getTypes() +{ + std::set types; + types.insert("GroupNotice"); + return types; +} + +std::set LLTransactionNotificationListItem::getTypes() +{ + std::set types; + types.insert("PaymentReceived"); + types.insert("PaymentSent"); + types.insert("UploadPayment"); + return types; +} + +LLGroupNotificationListItem::LLGroupNotificationListItem(const Params& p) + : LLNotificationListItem(p), + mSenderOrFeeBox(NULL) +{ +} + +LLGroupInviteNotificationListItem::LLGroupInviteNotificationListItem(const Params& p) + : LLGroupNotificationListItem(p) +{ + buildFromFile("panel_notification_list_item.xml"); +} + +bool LLGroupInviteNotificationListItem::postBuild() +{ + bool rv = LLGroupNotificationListItem::postBuild(); + setFee(mParams.fee); + mInviteButtonPanel = getChild("button_panel"); + mInviteButtonPanel->setVisible(true); + mJoinBtn = getChild("join_btn"); + mDeclineBtn = getChild("decline_btn"); + mInfoBtn = getChild("info_btn"); + + //invitation with any non-default group role, doesn't have newline characters at the end unlike simple invitations + std::string invitation_desc = mNoticeTextExp->getValue().asString(); + if (invitation_desc.substr(invitation_desc.size() - 2) != "\n\n") + { + invitation_desc += "\n\n"; + mNoticeTextExp->setValue(invitation_desc); + } + + mJoinBtn->setClickedCallback(boost::bind(&LLGroupInviteNotificationListItem::onClickJoinBtn,this)); + mDeclineBtn->setClickedCallback(boost::bind(&LLGroupInviteNotificationListItem::onClickDeclineBtn,this)); + mInfoBtn->setClickedCallback(boost::bind(&LLGroupInviteNotificationListItem::onClickInfoBtn,this)); + + std::string expanded_height_resize_str = getString("expanded_height_resize_for_attachment"); + mExpandedHeightResize = (S32)atoi(expanded_height_resize_str.c_str()); + + return rv; +} + +void LLGroupInviteNotificationListItem::onClickJoinBtn() +{ + if (!gAgent.canJoinGroups()) + { + LLNotificationsUtil::add("JoinedTooManyGroups"); + return; + } + + send_join_group_response(mParams.group_id, mParams.transaction_id, true, mParams.fee, mParams.use_offline_cap); + + LLNotificationListItem::onClickCloseBtn(); +} + +void LLGroupInviteNotificationListItem::onClickDeclineBtn() +{ + send_join_group_response(mParams.group_id, mParams.transaction_id, false, mParams.fee, mParams.use_offline_cap); + + LLNotificationListItem::onClickCloseBtn(); +} + +void LLGroupInviteNotificationListItem::onClickInfoBtn() +{ + LLGroupActions::show(mParams.group_id); +} + +void LLGroupInviteNotificationListItem::setFee(S32 fee) +{ + LLStringUtil::format_map_t string_args; + string_args["[GROUP_FEE]"] = llformat("%d", fee); + std::string fee_text = getString("group_fee_text", string_args); + mSenderOrFeeBox->setValue(fee_text); + mSenderOrFeeBoxExp->setValue(fee_text); + mSenderOrFeeBox->setVisible(true); + mSenderOrFeeBoxExp->setVisible(true); +} + +LLGroupNoticeNotificationListItem::LLGroupNoticeNotificationListItem(const Params& p) + : LLGroupNotificationListItem(p), + mAttachmentPanel(NULL), + mAttachmentTextBox(NULL), + mAttachmentIcon(NULL), + mAttachmentIconExp(NULL), + mInventoryOffer(NULL) +{ + if (mParams.inventory_offer.isDefined()) + { + mInventoryOffer = new LLOfferInfo(mParams.inventory_offer); + } + + buildFromFile("panel_notification_list_item.xml"); +} + +LLGroupNotificationListItem::~LLGroupNotificationListItem() +{ + LLGroupMgr::getInstance()->removeObserver(this); +} + +bool LLGroupNoticeNotificationListItem::postBuild() +{ + bool rv = LLGroupNotificationListItem::postBuild(); + + mAttachmentTextBox = getChild("attachment_text"); + mAttachmentIcon = getChild("attachment_icon"); + mAttachmentIconExp = getChild("attachment_icon_exp"); + mAttachmentPanel = getChild("attachment_panel"); + mAttachmentPanel->setVisible(false); + + + mTitleBox->setValue(mParams.subject); + mTitleBoxExp->setValue(mParams.subject); + mNoticeTextExp->setValue(mParams.message); + + mTimeBox->setValue(buildNotificationDate(mParams.time_stamp)); + mTimeBoxExp->setValue(buildNotificationDate(mParams.time_stamp)); + //Workaround: in case server timestamp is 0 - we use the time when notification was actually received + if (mParams.time_stamp.isNull()) + { + mTimeBox->setValue(buildNotificationDate(mParams.received_time)); + mTimeBoxExp->setValue(buildNotificationDate(mParams.received_time)); + } + setSender(mParams.sender); + + if (mInventoryOffer != NULL) + { + mAttachmentTextBox->setValue(mInventoryOffer->mDesc); + mAttachmentTextBox->setVisible(true); + mAttachmentIcon->setVisible(true); + + std::string icon_name = LLInventoryIcon::getIconName(mInventoryOffer->mType, + LLInventoryType::IT_TEXTURE); + mAttachmentIconExp->setValue(icon_name); + mAttachmentIconExp->setVisible(true); + + mAttachmentTextBox->setClickedCallback(boost::bind( + &LLGroupNoticeNotificationListItem::onClickAttachment, this)); + + std::string expanded_height_resize_str = getString("expanded_height_resize_for_attachment"); + mExpandedHeightResize = (S32)atoi(expanded_height_resize_str.c_str()); + + mAttachmentPanel->setVisible(true); + } + return rv; +} + +bool LLGroupNotificationListItem::postBuild() +{ + bool rv = LLNotificationListItem::postBuild(); + + mGroupIcon = getChild("group_icon"); + mGroupIconExp = getChild("group_icon_exp"); + mGroupNameBoxExp = getChild("group_name_exp"); + + mGroupIcon->setValue(mParams.group_id); + mGroupIconExp->setValue(mParams.group_id); + + mGroupIcon->setVisible(true); + mGroupIconExp->setVisible(true); + + mGroupId = mParams.group_id; + + mSenderOrFeeBox = getChild("sender_or_fee_box"); + mSenderOrFeeBoxExp = getChild("sender_or_fee_box_exp"); + + LLSD value(mParams.group_id); + setGroupId(value); + + return rv; +} + +void LLGroupNotificationListItem::changed(LLGroupChange gc) +{ + if (GC_PROPERTIES == gc) + { + updateFromCache(); + LLGroupMgr::getInstance()->removeObserver(this); + } +} + +bool LLGroupNotificationListItem::updateFromCache() +{ + LLGroupMgrGroupData* group_data = LLGroupMgr::getInstance()->getGroupData(mGroupId); + if (!group_data) return false; + setGroupName(group_data->mName); + return true; +} + +void LLGroupNotificationListItem::setGroupId(const LLUUID& value) +{ + LLGroupMgr* gm = LLGroupMgr::getInstance(); + if (mGroupId.notNull()) + { + gm->removeObserver(this); + + + mID = mGroupId; + + // Check if cache already contains image_id for that group + if (!updateFromCache()) + { + gm->addObserver(this); + gm->sendGroupPropertiesRequest(mGroupId); + } + } +} + +void LLGroupNotificationListItem::setGroupName(std::string name) +{ + if (!name.empty()) + { + LLStringUtil::format_map_t string_args; + string_args["[GROUP_NAME]"] = llformat("%s", name.c_str()); + std::string group_box_str = getString("group_name_text", string_args); + mGroupNameBoxExp->setValue(group_box_str); + mGroupNameBoxExp->setVisible(true); + } + else + { + mGroupNameBoxExp->setValue(LLStringUtil::null); + mGroupNameBoxExp->setVisible(false); + } +} + +void LLGroupNoticeNotificationListItem::setSender(std::string sender) +{ + if (!sender.empty()) + { + LLStringUtil::format_map_t string_args; + string_args["[SENDER_RESIDENT]"] = llformat("%s", sender.c_str()); + std::string sender_text = getString("sender_resident_text", string_args); + mSenderOrFeeBox->setValue(sender_text); + mSenderOrFeeBoxExp->setValue(sender_text); + mSenderOrFeeBox->setVisible(true); + mSenderOrFeeBoxExp->setVisible(true); + } else { + mSenderOrFeeBox->setValue(LLStringUtil::null); + mSenderOrFeeBoxExp->setValue(LLStringUtil::null); + mSenderOrFeeBox->setVisible(false); + mSenderOrFeeBoxExp->setVisible(false); + } +} +void LLGroupNoticeNotificationListItem::close() +{ + // The group notice dialog may be an inventory offer. + // If it has an inventory save button and that button is still enabled + // Then we need to send the inventory declined message + if (mInventoryOffer != NULL) + { + mInventoryOffer->forceResponse(IOR_DECLINE); + mInventoryOffer = NULL; + } +} + +void LLGroupNoticeNotificationListItem::onClickAttachment() +{ + if (mInventoryOffer != NULL) { + static const LLUIColor textColor = LLUIColorTable::instance().getColor( + "GroupNotifyDimmedTextColor"); + mAttachmentTextBox->setColor(textColor); + mAttachmentIconExp->setEnabled(false); + + //if attachment isn't openable - notify about saving + if (!isAttachmentOpenable(mInventoryOffer->mType)) { + LLNotifications::instance().add("AttachmentSaved", LLSD(), LLSD()); + } + mInventoryOffer->forceResponse(IOR_ACCEPT); + mInventoryOffer = NULL; + } +} + +//static +bool LLGroupNoticeNotificationListItem::isAttachmentOpenable(LLAssetType::EType type) +{ + switch (type) + { + case LLAssetType::AT_LANDMARK: + case LLAssetType::AT_NOTECARD: + case LLAssetType::AT_IMAGE_JPEG: + case LLAssetType::AT_IMAGE_TGA: + case LLAssetType::AT_TEXTURE: + case LLAssetType::AT_TEXTURE_TGA: + return true; + default: + return false; + } +} + +LLTransactionNotificationListItem::LLTransactionNotificationListItem(const Params& p) + : LLNotificationListItem(p), + mAvatarIcon(NULL) +{ + buildFromFile("panel_notification_list_item.xml"); +} + +bool LLTransactionNotificationListItem::postBuild() +{ + bool rv = LLNotificationListItem::postBuild(); + mAvatarIcon = getChild("avatar_icon"); + mAvatarIconExp = getChild("avatar_icon_exp"); + mAvatarIcon->setValue("System_Notification"); + mAvatarIconExp->setValue("System_Notification"); + + mAvatarIcon->setVisible(true); + mAvatarIconExp->setVisible(true); + if((GOVERNOR_LINDEN_ID == mParams.paid_to_id) || + (GOVERNOR_LINDEN_ID == mParams.paid_from_id)) + { + return rv; + } + + if (mParams.notification_name == "PaymentReceived") + { + mAvatarIcon->setValue(mParams.paid_from_id); + mAvatarIconExp->setValue(mParams.paid_from_id); + } + else if (mParams.notification_name == "PaymentSent") + { + mAvatarIcon->setValue(mParams.paid_to_id); + mAvatarIconExp->setValue(mParams.paid_to_id); + } + + return rv; +} + +LLSystemNotificationListItem::LLSystemNotificationListItem(const Params& p) + : LLNotificationListItem(p), + mSystemNotificationIcon(NULL), + mIsCaution(false) +{ + buildFromFile("panel_notification_list_item.xml"); + mIsCaution = p.notification_priority >= NOTIFICATION_PRIORITY_HIGH; + if (mIsCaution) + { + mTitleBox->setColor(LLUIColorTable::instance().getColor("NotifyCautionBoxColor")); + mTitleBoxExp->setColor(LLUIColorTable::instance().getColor("NotifyCautionBoxColor")); + mNoticeTextExp->setReadOnlyColor(LLUIColorTable::instance().getColor("NotifyCautionBoxColor")); + mTimeBox->setColor(LLUIColorTable::instance().getColor("NotifyCautionBoxColor")); + mTimeBoxExp->setColor(LLUIColorTable::instance().getColor("NotifyCautionBoxColor")); + } +} + +bool LLSystemNotificationListItem::postBuild() +{ + bool rv = LLNotificationListItem::postBuild(); + mSystemNotificationIcon = getChild("system_notification_icon"); + mSystemNotificationIconExp = getChild("system_notification_icon_exp"); + if (mSystemNotificationIcon) + mSystemNotificationIcon->setVisible(true); + if (mSystemNotificationIconExp) + mSystemNotificationIconExp->setVisible(true); + return rv; +} diff --git a/indra/newview/llnotificationlistitem.h b/indra/newview/llnotificationlistitem.h index f89be9b374..2ed90e31b2 100644 --- a/indra/newview/llnotificationlistitem.h +++ b/indra/newview/llnotificationlistitem.h @@ -1,251 +1,251 @@ -/** - * @file llnotificationlistitem.h - * @brief - * - * $LicenseInfo:firstyear=2015&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2015, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLNOTIFICATIONLISTITEM_H -#define LL_LLNOTIFICATIONLISTITEM_H - -#include "llpanel.h" -#include "lllayoutstack.h" -#include "lltextbox.h" -#include "llviewertexteditor.h" -#include "llbutton.h" -#include "llgroupiconctrl.h" -#include "llavatariconctrl.h" -#include "llchatentry.h" -#include "llgroupmgr.h" -#include "llviewermessage.h" - -#include - -class LLNotificationListItem : public LLPanel -{ -public: - struct Params : public LLInitParam::Block - { - LLUUID notification_id; - LLUUID transaction_id; - LLUUID group_id; - LLUUID paid_from_id; - LLUUID paid_to_id; - std::string notification_name; - std::string title; - std::string subject; - std::string message; - std::string sender; - S32 fee; - U8 use_offline_cap; - LLDate time_stamp; - LLDate received_time; - LLSD inventory_offer; - e_notification_priority notification_priority; - Params() {}; - }; - - static LLNotificationListItem* create(const Params& p); - - static std::set getTransactionTypes(); - static std::set getGroupInviteTypes(); - static std::set getGroupNoticeTypes(); - - // title - void setTitle( std::string title ); - - // get item's ID - LLUUID getID() { return mParams.notification_id; } - std::string& getTitle() { return mTitle; } - std::string& getNotificationName() { return mNotificationName; } - - // handlers - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - virtual void onMouseEnter(S32 x, S32 y, MASK mask); - virtual void onMouseLeave(S32 x, S32 y, MASK mask); - - //callbacks - typedef boost::function item_callback_t; - typedef boost::signals2::signal item_signal_t; - item_signal_t mOnItemClose; - item_signal_t mOnItemClick; - boost::signals2::connection setOnItemCloseCallback(item_callback_t cb) { return mOnItemClose.connect(cb); } - boost::signals2::connection setOnItemClickCallback(item_callback_t cb) { return mOnItemClick.connect(cb); } - - virtual bool showPopup() { return true; } - void setExpanded(bool value); - virtual bool postBuild(); - void reshapeNotification(); - - typedef enum e_time_type - { - SLT = 1, - Local = 2, - UTC = 3, - }ETimeType; - -protected: - LLNotificationListItem(const Params& p); - virtual ~LLNotificationListItem(); - - static std::string buildNotificationDate(const LLDate& time_stamp, ETimeType time_type = SLT); - void onClickExpandBtn(); - void onClickCondenseBtn(); - void onClickCloseBtn(); - virtual void close() {}; - - Params mParams; - LLTextBox* mTitleBox; - LLTextBox* mTitleBoxExp; - LLChatEntry* mNoticeTextExp; - LLTextBox* mTimeBox; - LLTextBox* mTimeBoxExp; - LLButton* mExpandBtn; - LLButton* mCondenseBtn; - LLButton* mCloseBtn; - LLButton* mCloseBtnExp; - LLPanel* mCondensedViewPanel; - LLPanel* mExpandedViewPanel; - std::string mTitle; - std::string mNotificationName; - S32 mCondensedHeight; - S32 mExpandedHeight; - S32 mExpandedHeightResize; - bool mExpanded; -}; - -class LLGroupNotificationListItem - : public LLNotificationListItem, public LLGroupMgrObserver -{ -public: - virtual ~LLGroupNotificationListItem(); - virtual bool postBuild(); - - void setGroupId(const LLUUID& value); - // LLGroupMgrObserver observer trigger - virtual void changed(LLGroupChange gc); - - friend class LLNotificationListItem; -protected: - LLGroupNotificationListItem(const Params& p); - - LLGroupIconCtrl* mGroupIcon; - LLGroupIconCtrl* mGroupIconExp; - LLUUID mGroupId; - LLTextBox* mSenderOrFeeBox; - LLTextBox* mSenderOrFeeBoxExp; - LLTextBox* mGroupNameBoxExp; - -private: - LLGroupNotificationListItem(const LLGroupNotificationListItem &); - LLGroupNotificationListItem & operator=(LLGroupNotificationListItem &); - - void setGroupName(std::string name); - bool updateFromCache(); -}; - -class LLGroupInviteNotificationListItem - : public LLGroupNotificationListItem -{ -public: - static std::set getTypes(); - virtual bool postBuild(); - - /*virtual*/ bool showPopup() { return false; } - -private: - friend class LLNotificationListItem; - LLGroupInviteNotificationListItem(const Params& p); - LLGroupInviteNotificationListItem(const LLGroupInviteNotificationListItem &); - LLGroupInviteNotificationListItem & operator=(LLGroupInviteNotificationListItem &); - - void setFee(S32 fee); - - void onClickJoinBtn(); - void onClickDeclineBtn(); - void onClickInfoBtn(); - - LLPanel* mInviteButtonPanel; - LLButton* mJoinBtn; - LLButton* mDeclineBtn; - LLButton* mInfoBtn; -}; - -class LLGroupNoticeNotificationListItem - : public LLGroupNotificationListItem -{ -public: - static std::set getTypes(); - virtual bool postBuild(); - - /*virtual*/ bool showPopup() { return false; } - -private: - friend class LLNotificationListItem; - LLGroupNoticeNotificationListItem(const Params& p); - LLGroupNoticeNotificationListItem(const LLGroupNoticeNotificationListItem &); - LLGroupNoticeNotificationListItem & operator=(LLGroupNoticeNotificationListItem &); - - void setSender(std::string sender); - void onClickAttachment(); - /*virtual*/ void close(); - - static bool isAttachmentOpenable(LLAssetType::EType); - - LLPanel* mAttachmentPanel; - LLTextBox* mAttachmentTextBox; - LLIconCtrl* mAttachmentIcon; - LLIconCtrl* mAttachmentIconExp; - LLOfferInfo* mInventoryOffer; -}; - -class LLTransactionNotificationListItem : public LLNotificationListItem -{ -public: - static std::set getTypes(); - virtual bool postBuild(); -private: - friend class LLNotificationListItem; - LLTransactionNotificationListItem(const Params& p); - LLTransactionNotificationListItem(const LLTransactionNotificationListItem &); - LLTransactionNotificationListItem & operator=(LLTransactionNotificationListItem &); - LLAvatarIconCtrl* mAvatarIcon; - LLAvatarIconCtrl* mAvatarIconExp; -}; - -class LLSystemNotificationListItem : public LLNotificationListItem -{ -public: - virtual bool postBuild(); -private: - friend class LLNotificationListItem; - LLSystemNotificationListItem(const Params& p); - LLSystemNotificationListItem(const LLSystemNotificationListItem &); - LLSystemNotificationListItem & operator=(LLSystemNotificationListItem &); - LLIconCtrl* mSystemNotificationIcon; - LLIconCtrl* mSystemNotificationIconExp; - bool mIsCaution; -}; - -#endif // LL_LLNOTIFICATIONLISTITEM_H - - +/** + * @file llnotificationlistitem.h + * @brief + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2015, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLNOTIFICATIONLISTITEM_H +#define LL_LLNOTIFICATIONLISTITEM_H + +#include "llpanel.h" +#include "lllayoutstack.h" +#include "lltextbox.h" +#include "llviewertexteditor.h" +#include "llbutton.h" +#include "llgroupiconctrl.h" +#include "llavatariconctrl.h" +#include "llchatentry.h" +#include "llgroupmgr.h" +#include "llviewermessage.h" + +#include + +class LLNotificationListItem : public LLPanel +{ +public: + struct Params : public LLInitParam::Block + { + LLUUID notification_id; + LLUUID transaction_id; + LLUUID group_id; + LLUUID paid_from_id; + LLUUID paid_to_id; + std::string notification_name; + std::string title; + std::string subject; + std::string message; + std::string sender; + S32 fee; + U8 use_offline_cap; + LLDate time_stamp; + LLDate received_time; + LLSD inventory_offer; + e_notification_priority notification_priority; + Params() {}; + }; + + static LLNotificationListItem* create(const Params& p); + + static std::set getTransactionTypes(); + static std::set getGroupInviteTypes(); + static std::set getGroupNoticeTypes(); + + // title + void setTitle( std::string title ); + + // get item's ID + LLUUID getID() { return mParams.notification_id; } + std::string& getTitle() { return mTitle; } + std::string& getNotificationName() { return mNotificationName; } + + // handlers + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + virtual void onMouseEnter(S32 x, S32 y, MASK mask); + virtual void onMouseLeave(S32 x, S32 y, MASK mask); + + //callbacks + typedef boost::function item_callback_t; + typedef boost::signals2::signal item_signal_t; + item_signal_t mOnItemClose; + item_signal_t mOnItemClick; + boost::signals2::connection setOnItemCloseCallback(item_callback_t cb) { return mOnItemClose.connect(cb); } + boost::signals2::connection setOnItemClickCallback(item_callback_t cb) { return mOnItemClick.connect(cb); } + + virtual bool showPopup() { return true; } + void setExpanded(bool value); + virtual bool postBuild(); + void reshapeNotification(); + + typedef enum e_time_type + { + SLT = 1, + Local = 2, + UTC = 3, + }ETimeType; + +protected: + LLNotificationListItem(const Params& p); + virtual ~LLNotificationListItem(); + + static std::string buildNotificationDate(const LLDate& time_stamp, ETimeType time_type = SLT); + void onClickExpandBtn(); + void onClickCondenseBtn(); + void onClickCloseBtn(); + virtual void close() {}; + + Params mParams; + LLTextBox* mTitleBox; + LLTextBox* mTitleBoxExp; + LLChatEntry* mNoticeTextExp; + LLTextBox* mTimeBox; + LLTextBox* mTimeBoxExp; + LLButton* mExpandBtn; + LLButton* mCondenseBtn; + LLButton* mCloseBtn; + LLButton* mCloseBtnExp; + LLPanel* mCondensedViewPanel; + LLPanel* mExpandedViewPanel; + std::string mTitle; + std::string mNotificationName; + S32 mCondensedHeight; + S32 mExpandedHeight; + S32 mExpandedHeightResize; + bool mExpanded; +}; + +class LLGroupNotificationListItem + : public LLNotificationListItem, public LLGroupMgrObserver +{ +public: + virtual ~LLGroupNotificationListItem(); + virtual bool postBuild(); + + void setGroupId(const LLUUID& value); + // LLGroupMgrObserver observer trigger + virtual void changed(LLGroupChange gc); + + friend class LLNotificationListItem; +protected: + LLGroupNotificationListItem(const Params& p); + + LLGroupIconCtrl* mGroupIcon; + LLGroupIconCtrl* mGroupIconExp; + LLUUID mGroupId; + LLTextBox* mSenderOrFeeBox; + LLTextBox* mSenderOrFeeBoxExp; + LLTextBox* mGroupNameBoxExp; + +private: + LLGroupNotificationListItem(const LLGroupNotificationListItem &); + LLGroupNotificationListItem & operator=(LLGroupNotificationListItem &); + + void setGroupName(std::string name); + bool updateFromCache(); +}; + +class LLGroupInviteNotificationListItem + : public LLGroupNotificationListItem +{ +public: + static std::set getTypes(); + virtual bool postBuild(); + + /*virtual*/ bool showPopup() { return false; } + +private: + friend class LLNotificationListItem; + LLGroupInviteNotificationListItem(const Params& p); + LLGroupInviteNotificationListItem(const LLGroupInviteNotificationListItem &); + LLGroupInviteNotificationListItem & operator=(LLGroupInviteNotificationListItem &); + + void setFee(S32 fee); + + void onClickJoinBtn(); + void onClickDeclineBtn(); + void onClickInfoBtn(); + + LLPanel* mInviteButtonPanel; + LLButton* mJoinBtn; + LLButton* mDeclineBtn; + LLButton* mInfoBtn; +}; + +class LLGroupNoticeNotificationListItem + : public LLGroupNotificationListItem +{ +public: + static std::set getTypes(); + virtual bool postBuild(); + + /*virtual*/ bool showPopup() { return false; } + +private: + friend class LLNotificationListItem; + LLGroupNoticeNotificationListItem(const Params& p); + LLGroupNoticeNotificationListItem(const LLGroupNoticeNotificationListItem &); + LLGroupNoticeNotificationListItem & operator=(LLGroupNoticeNotificationListItem &); + + void setSender(std::string sender); + void onClickAttachment(); + /*virtual*/ void close(); + + static bool isAttachmentOpenable(LLAssetType::EType); + + LLPanel* mAttachmentPanel; + LLTextBox* mAttachmentTextBox; + LLIconCtrl* mAttachmentIcon; + LLIconCtrl* mAttachmentIconExp; + LLOfferInfo* mInventoryOffer; +}; + +class LLTransactionNotificationListItem : public LLNotificationListItem +{ +public: + static std::set getTypes(); + virtual bool postBuild(); +private: + friend class LLNotificationListItem; + LLTransactionNotificationListItem(const Params& p); + LLTransactionNotificationListItem(const LLTransactionNotificationListItem &); + LLTransactionNotificationListItem & operator=(LLTransactionNotificationListItem &); + LLAvatarIconCtrl* mAvatarIcon; + LLAvatarIconCtrl* mAvatarIconExp; +}; + +class LLSystemNotificationListItem : public LLNotificationListItem +{ +public: + virtual bool postBuild(); +private: + friend class LLNotificationListItem; + LLSystemNotificationListItem(const Params& p); + LLSystemNotificationListItem(const LLSystemNotificationListItem &); + LLSystemNotificationListItem & operator=(LLSystemNotificationListItem &); + LLIconCtrl* mSystemNotificationIcon; + LLIconCtrl* mSystemNotificationIconExp; + bool mIsCaution; +}; + +#endif // LL_LLNOTIFICATIONLISTITEM_H + + diff --git a/indra/newview/llnotificationofferhandler.cpp b/indra/newview/llnotificationofferhandler.cpp index 69385347bb..b65da28bda 100644 --- a/indra/newview/llnotificationofferhandler.cpp +++ b/indra/newview/llnotificationofferhandler.cpp @@ -1,206 +1,206 @@ -/** - * @file llnotificationofferhandler.cpp - * @brief Notification Handler Class for Simple Notifications and Notification Tips - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#include "llviewerprecompiledheaders.h" // must be first include - -#include "llnotificationhandler.h" -#include "lltoastnotifypanel.h" -#include "llviewercontrol.h" -#include "llviewerwindow.h" -#include "llnotificationmanager.h" -#include "llnotifications.h" -#include "llscriptfloater.h" -#include "llimview.h" -#include "llnotificationsutil.h" - -#include - -using namespace LLNotificationsUI; - -//-------------------------------------------------------------------------- -LLOfferHandler::LLOfferHandler() -: LLCommunicationNotificationHandler("Offer", "offer") -{ - // Getting a Channel for our notifications - LLScreenChannel* channel = LLChannelManager::getInstance()->createNotificationChannel(); - if(channel) - { - channel->setControlHovering(true); - mChannel = channel->getHandle(); - } -} - -//-------------------------------------------------------------------------- -LLOfferHandler::~LLOfferHandler() -{ -} - -//-------------------------------------------------------------------------- -void LLOfferHandler::initChannel() -{ - S32 channel_right_bound = gViewerWindow->getWorldViewRectScaled().mRight - gSavedSettings.getS32("NotificationChannelRightMargin"); - mChannel.get()->init(channel_right_bound - NOTIFY_BOX_WIDTH, channel_right_bound); -} - -//-------------------------------------------------------------------------- -bool LLOfferHandler::processNotification(const LLNotificationPtr& notification, bool should_log) -{ - if (mChannel.isDead()) - { - return false; - } - - // arrange a channel on a screen - if (!mChannel.get()->getVisible()) - { - initChannel(); - } - - if (notification->getPayload().has("give_inventory_notification") && - !notification->getPayload()["give_inventory_notification"].asBoolean()) - { - // This is an original inventory offer, so add a script floater - LLScriptFloaterManager::instance().onAddNotification(notification->getID()); - } - else - { - bool add_notif_to_im = notification->canLogToIM() && notification->hasFormElements(); - - if (add_notif_to_im) - { - const std::string name = LLHandlerUtil::getSubstitutionName(notification); - - LLUUID from_id = notification->getPayload()["from_id"]; - - if (!notification->isDND()) - { - //Will not play a notification sound for inventory and teleport offer based upon chat preference - bool playSound = (notification->getName() == "UserGiveItem" - && gSavedSettings.getBOOL("PlaySoundInventoryOffer")) - || ((notification->getName() == "TeleportOffered" - || notification->getName() == "TeleportOffered_MaturityExceeded" - || notification->getName() == "TeleportOffered_MaturityBlocked") - && gSavedSettings.getBOOL("PlaySoundTeleportOffer")); - - if (playSound) - { - notification->playSound(); - } - } - - LLHandlerUtil::spawnIMSession(name, from_id); - LLHandlerUtil::addNotifPanelToIM(notification); - - } - - if (!notification->canShowToast()) - { - LLNotificationsUtil::cancel(notification); - } - else if(!notification->canLogToIM() || !LLHandlerUtil::isIMFloaterOpened(notification)) - { - LLToastNotifyPanel* notify_box = new LLToastNotifyPanel(notification); - LLToast::Params p; - p.notif_id = notification->getID(); - p.notification = notification; - p.panel = notify_box; - // we not save offer notifications to the syswell floater that should be added to the IM floater - p.can_be_stored = !add_notif_to_im; - p.force_show = notification->getOfferFromAgent(); - p.can_fade = notification->canFadeToast(); - - LLScreenChannel* channel = dynamic_cast(mChannel.get()); - if(channel) - channel->addToast(p); - - } - - if (notification->canLogToIM()) - { - // log only to file if notif panel can be embedded to IM and IM is opened - bool file_only = add_notif_to_im && LLHandlerUtil::isIMFloaterOpened(notification); - if ((notification->getName() == "TeleportOffered" - || notification->getName() == "TeleportOffered_MaturityExceeded" - || notification->getName() == "TeleportOffered_MaturityBlocked")) - { - boost::regex r("\\s*([^<]*)?\\s*( - )?", - boost::regex::perl|boost::regex::icase); - std::string stripped_msg = boost::regex_replace(notification->getMessage(), r, ""); - LLHandlerUtil::logToIMP2P(notification->getPayload()["from_id"], stripped_msg,file_only); - } - else - { - LLHandlerUtil::logToIMP2P(notification, file_only); - } - } - } - - return false; -} - -/*virtual*/ void LLOfferHandler::onChange(LLNotificationPtr p) -{ - auto panelp = LLToastNotifyPanel::getInstance(p->getID()); - if (panelp) - { - // - // HACK: if we're dealing with a notification embedded in IM, update it - // otherwise remove its toast - // - if (dynamic_cast(panelp.get())) - { - panelp->updateNotification(); - } - else - { - // if notification has changed, hide it - mChannel.get()->removeToastByNotificationID(p->getID()); - } - } -} - - -/*virtual*/ void LLOfferHandler::onDelete(LLNotificationPtr notification) -{ - if( notification->getPayload().has("give_inventory_notification") - && !notification->getPayload()["give_inventory_notification"] ) - { - // Remove original inventory offer script floater - LLScriptFloaterManager::instance().onRemoveNotification(notification->getID()); - } - else - { - if (notification->canLogToIM() - && notification->hasFormElements() - && !LLHandlerUtil::isIMFloaterOpened(notification)) - { - LLHandlerUtil::decIMMesageCounter(notification); - } - mChannel.get()->removeToastByNotificationID(notification->getID()); - } -} - +/** + * @file llnotificationofferhandler.cpp + * @brief Notification Handler Class for Simple Notifications and Notification Tips + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#include "llviewerprecompiledheaders.h" // must be first include + +#include "llnotificationhandler.h" +#include "lltoastnotifypanel.h" +#include "llviewercontrol.h" +#include "llviewerwindow.h" +#include "llnotificationmanager.h" +#include "llnotifications.h" +#include "llscriptfloater.h" +#include "llimview.h" +#include "llnotificationsutil.h" + +#include + +using namespace LLNotificationsUI; + +//-------------------------------------------------------------------------- +LLOfferHandler::LLOfferHandler() +: LLCommunicationNotificationHandler("Offer", "offer") +{ + // Getting a Channel for our notifications + LLScreenChannel* channel = LLChannelManager::getInstance()->createNotificationChannel(); + if(channel) + { + channel->setControlHovering(true); + mChannel = channel->getHandle(); + } +} + +//-------------------------------------------------------------------------- +LLOfferHandler::~LLOfferHandler() +{ +} + +//-------------------------------------------------------------------------- +void LLOfferHandler::initChannel() +{ + S32 channel_right_bound = gViewerWindow->getWorldViewRectScaled().mRight - gSavedSettings.getS32("NotificationChannelRightMargin"); + mChannel.get()->init(channel_right_bound - NOTIFY_BOX_WIDTH, channel_right_bound); +} + +//-------------------------------------------------------------------------- +bool LLOfferHandler::processNotification(const LLNotificationPtr& notification, bool should_log) +{ + if (mChannel.isDead()) + { + return false; + } + + // arrange a channel on a screen + if (!mChannel.get()->getVisible()) + { + initChannel(); + } + + if (notification->getPayload().has("give_inventory_notification") && + !notification->getPayload()["give_inventory_notification"].asBoolean()) + { + // This is an original inventory offer, so add a script floater + LLScriptFloaterManager::instance().onAddNotification(notification->getID()); + } + else + { + bool add_notif_to_im = notification->canLogToIM() && notification->hasFormElements(); + + if (add_notif_to_im) + { + const std::string name = LLHandlerUtil::getSubstitutionName(notification); + + LLUUID from_id = notification->getPayload()["from_id"]; + + if (!notification->isDND()) + { + //Will not play a notification sound for inventory and teleport offer based upon chat preference + bool playSound = (notification->getName() == "UserGiveItem" + && gSavedSettings.getBOOL("PlaySoundInventoryOffer")) + || ((notification->getName() == "TeleportOffered" + || notification->getName() == "TeleportOffered_MaturityExceeded" + || notification->getName() == "TeleportOffered_MaturityBlocked") + && gSavedSettings.getBOOL("PlaySoundTeleportOffer")); + + if (playSound) + { + notification->playSound(); + } + } + + LLHandlerUtil::spawnIMSession(name, from_id); + LLHandlerUtil::addNotifPanelToIM(notification); + + } + + if (!notification->canShowToast()) + { + LLNotificationsUtil::cancel(notification); + } + else if(!notification->canLogToIM() || !LLHandlerUtil::isIMFloaterOpened(notification)) + { + LLToastNotifyPanel* notify_box = new LLToastNotifyPanel(notification); + LLToast::Params p; + p.notif_id = notification->getID(); + p.notification = notification; + p.panel = notify_box; + // we not save offer notifications to the syswell floater that should be added to the IM floater + p.can_be_stored = !add_notif_to_im; + p.force_show = notification->getOfferFromAgent(); + p.can_fade = notification->canFadeToast(); + + LLScreenChannel* channel = dynamic_cast(mChannel.get()); + if(channel) + channel->addToast(p); + + } + + if (notification->canLogToIM()) + { + // log only to file if notif panel can be embedded to IM and IM is opened + bool file_only = add_notif_to_im && LLHandlerUtil::isIMFloaterOpened(notification); + if ((notification->getName() == "TeleportOffered" + || notification->getName() == "TeleportOffered_MaturityExceeded" + || notification->getName() == "TeleportOffered_MaturityBlocked")) + { + boost::regex r("\\s*([^<]*)?\\s*( - )?", + boost::regex::perl|boost::regex::icase); + std::string stripped_msg = boost::regex_replace(notification->getMessage(), r, ""); + LLHandlerUtil::logToIMP2P(notification->getPayload()["from_id"], stripped_msg,file_only); + } + else + { + LLHandlerUtil::logToIMP2P(notification, file_only); + } + } + } + + return false; +} + +/*virtual*/ void LLOfferHandler::onChange(LLNotificationPtr p) +{ + auto panelp = LLToastNotifyPanel::getInstance(p->getID()); + if (panelp) + { + // + // HACK: if we're dealing with a notification embedded in IM, update it + // otherwise remove its toast + // + if (dynamic_cast(panelp.get())) + { + panelp->updateNotification(); + } + else + { + // if notification has changed, hide it + mChannel.get()->removeToastByNotificationID(p->getID()); + } + } +} + + +/*virtual*/ void LLOfferHandler::onDelete(LLNotificationPtr notification) +{ + if( notification->getPayload().has("give_inventory_notification") + && !notification->getPayload()["give_inventory_notification"] ) + { + // Remove original inventory offer script floater + LLScriptFloaterManager::instance().onRemoveNotification(notification->getID()); + } + else + { + if (notification->canLogToIM() + && notification->hasFormElements() + && !LLHandlerUtil::isIMFloaterOpened(notification)) + { + LLHandlerUtil::decIMMesageCounter(notification); + } + mChannel.get()->removeToastByNotificationID(notification->getID()); + } +} + diff --git a/indra/newview/lloutfitgallery.cpp b/indra/newview/lloutfitgallery.cpp index 0da0ae59a4..5d55816f94 100644 --- a/indra/newview/lloutfitgallery.cpp +++ b/indra/newview/lloutfitgallery.cpp @@ -1,1323 +1,1323 @@ -/** - * @file lloutfitgallery.cpp - * @author Pavlo Kryvych - * @brief Visual gallery of agent's outfits for My Appearance side panel - * - * $LicenseInfo:firstyear=2015&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2015, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" // must be first include -#include "lloutfitgallery.h" - -// llcommon -#include "llcommonutils.h" -#include "llfilesystem.h" - -#include "llaccordionctrltab.h" -#include "llappearancemgr.h" -#include "llerror.h" -#include "llfilepicker.h" -#include "llfloaterperms.h" -#include "llfloaterreg.h" -#include "llfloatersimplesnapshot.h" -#include "llimagedimensionsinfo.h" -#include "llinventoryfunctions.h" -#include "llinventorymodel.h" -#include "lllocalbitmaps.h" -#include "llnotificationsutil.h" -#include "llpaneloutfitsinventory.h" -#include "lltabcontainer.h" -#include "lltexturectrl.h" -#include "lltrans.h" -#include "llviewercontrol.h" -#include "llviewermenufile.h" -#include "llviewertexturelist.h" -#include "llwearableitemslist.h" - -static LLPanelInjector t_outfit_gallery("outfit_gallery"); - -#define MAX_OUTFIT_PHOTO_WIDTH 256 -#define MAX_OUTFIT_PHOTO_HEIGHT 256 - -const S32 GALLERY_ITEMS_PER_ROW_MIN = 2; - -LLOutfitGallery::LLOutfitGallery(const LLOutfitGallery::Params& p) - : LLOutfitListBase(), - mOutfitsObserver(NULL), - mScrollPanel(NULL), - mGalleryPanel(NULL), - mLastRowPanel(NULL), - mGalleryCreated(false), - mRowCount(0), - mItemsAddedCount(0), - mOutfitLinkPending(NULL), - mOutfitRenamePending(NULL), - mSnapshotFolderID(NULL), - mRowPanelHeight(p.row_panel_height), - mVerticalGap(p.vertical_gap), - mHorizontalGap(p.horizontal_gap), - mItemWidth(p.item_width), - mItemHeight(p.item_height), - mItemHorizontalGap(p.item_horizontal_gap), - mItemsInRow(p.items_in_row), - mRowPanWidthFactor(p.row_panel_width_factor), - mGalleryWidthFactor(p.gallery_width_factor), - mTextureSelected(NULL) -{ - updateGalleryWidth(); -} - -LLOutfitGallery::Params::Params() - : row_panel_height("row_panel_height", 180), - vertical_gap("vertical_gap", 10), - horizontal_gap("horizontal_gap", 10), - item_width("item_width", 150), - item_height("item_height", 175), - item_horizontal_gap("item_horizontal_gap", 16), - items_in_row("items_in_row", GALLERY_ITEMS_PER_ROW_MIN), - row_panel_width_factor("row_panel_width_factor", 166), - gallery_width_factor("gallery_width_factor", 163) -{ - addSynonym(row_panel_height, "row_height"); -} - -const LLOutfitGallery::Params& LLOutfitGallery::getDefaultParams() -{ - return LLUICtrlFactory::getDefaultParams(); -} - -bool LLOutfitGallery::postBuild() -{ - bool rv = LLOutfitListBase::postBuild(); - mScrollPanel = getChild("gallery_scroll_panel"); - LLPanel::Params params = LLPanel::getDefaultParams(); // Don't parse XML when creating dummy LLPanel - mGalleryPanel = LLUICtrlFactory::create(params); - mMessageTextBox = getChild("no_outfits_txt"); - mOutfitGalleryMenu = new LLOutfitGalleryContextMenu(this); - return rv; -} - -void LLOutfitGallery::onOpen(const LLSD& info) -{ - LLOutfitListBase::onOpen(info); - if (!mGalleryCreated) - { - uuid_vec_t cats; - getCurrentCategories(cats); - int n = cats.size(); - buildGalleryPanel(n); - mScrollPanel->addChild(mGalleryPanel); - for (int i = 0; i < n; i++) - { - addToGallery(mOutfitMap[cats[i]]); - } - reArrangeRows(); - mGalleryCreated = true; - } -} - -void LLOutfitGallery::draw() -{ - LLPanel::draw(); - if (mGalleryCreated) - { - updateRowsIfNeeded(); - } -} - -bool LLOutfitGallery::handleKeyHere(KEY key, MASK mask) -{ - bool handled = false; - switch (key) - { - case KEY_RETURN: - // Open selected items if enter key hit on the inventory panel - if (mask == MASK_NONE && mSelectedOutfitUUID.notNull()) - { - // Or should it wearSelectedOutfit? - getSelectedItem()->openOutfitsContent(); - } - handled = true; - break; - case KEY_DELETE: -#if LL_DARWIN - case KEY_BACKSPACE: -#endif - // Delete selected items if delete or backspace key hit on the inventory panel - // Note: on Mac laptop keyboards, backspace and delete are one and the same - if (mSelectedOutfitUUID.notNull()) - { - onRemoveOutfit(mSelectedOutfitUUID); - } - handled = true; - break; - - case KEY_F2: - LLAppearanceMgr::instance().renameOutfit(mSelectedOutfitUUID); - handled = true; - break; - - case KEY_PAGE_UP: - if (mScrollPanel) - { - mScrollPanel->pageUp(30); - } - handled = true; - break; - - case KEY_PAGE_DOWN: - if (mScrollPanel) - { - mScrollPanel->pageDown(30); - } - handled = true; - break; - - case KEY_HOME: - if (mScrollPanel) - { - mScrollPanel->goToTop(); - } - handled = true; - break; - - case KEY_END: - if (mScrollPanel) - { - mScrollPanel->goToBottom(); - } - handled = true; - break; - - case KEY_LEFT: - moveLeft(); - handled = true; - break; - - case KEY_RIGHT: - moveRight(); - handled = true; - break; - - case KEY_UP: - moveUp(); - handled = true; - break; - - case KEY_DOWN: - moveDown(); - handled = true; - break; - - default: - break; - } - - if (handled) - { - mOutfitGalleryMenu->hide(); - } - - return handled; -} - -void LLOutfitGallery::moveUp() -{ - if (mSelectedOutfitUUID.notNull() && mItemsAddedCount > 1) - { - LLOutfitGalleryItem* item = getSelectedItem(); - if (item) - { - S32 n = mItemIndexMap[item]; - n -= mItemsInRow; - if (n >= 0) - { - item = mIndexToItemMap[n]; - LLUUID item_id = item->getUUID(); - ChangeOutfitSelection(nullptr, item_id); - item->setFocus(true); - - scrollToShowItem(mSelectedOutfitUUID); - } - } - } -} - -void LLOutfitGallery::moveDown() -{ - if (mSelectedOutfitUUID.notNull() && mItemsAddedCount > 1) - { - LLOutfitGalleryItem* item = getSelectedItem(); - if (item) - { - S32 n = mItemIndexMap[item]; - n += mItemsInRow; - if (n < mItemsAddedCount) - { - item = mIndexToItemMap[n]; - LLUUID item_id = item->getUUID(); - ChangeOutfitSelection(nullptr, item_id); - item->setFocus(true); - - scrollToShowItem(mSelectedOutfitUUID); - } - } - } -} - -void LLOutfitGallery::moveLeft() -{ - if (mSelectedOutfitUUID.notNull() && mItemsAddedCount > 1) - { - LLOutfitGalleryItem* item = getSelectedItem(); - if (item) - { - // Might be better to get item from panel - S32 n = mItemIndexMap[item]; - n--; - if (n < 0) - { - n = mItemsAddedCount - 1; - } - item = mIndexToItemMap[n]; - LLUUID item_id = item->getUUID(); - ChangeOutfitSelection(nullptr, item_id); - item->setFocus(true); - - scrollToShowItem(mSelectedOutfitUUID); - } - } -} - -void LLOutfitGallery::moveRight() -{ - if (mSelectedOutfitUUID.notNull() && mItemsAddedCount > 1) - { - LLOutfitGalleryItem* item = getSelectedItem(); - if (item) - { - S32 n = mItemIndexMap[item]; - n++; - if (n == mItemsAddedCount) - { - n = 0; - } - item = mIndexToItemMap[n]; - LLUUID item_id = item->getUUID(); - ChangeOutfitSelection(nullptr, item_id); - item->setFocus(true); - - scrollToShowItem(mSelectedOutfitUUID); - } - } -} - -void LLOutfitGallery::onFocusLost() -{ - LLOutfitListBase::onFocusLost(); - - if (mSelectedOutfitUUID.notNull()) - { - LLOutfitGalleryItem* item = getSelectedItem(); - if (item) - { - item->setSelected(false); - } - } -} - -void LLOutfitGallery::onFocusReceived() -{ - LLOutfitListBase::onFocusReceived(); - - if (mSelectedOutfitUUID.notNull()) - { - LLOutfitGalleryItem* item = getSelectedItem(); - if (item) - { - item->setSelected(true); - } - } -} - -void LLOutfitGallery::onRemoveOutfit(const LLUUID& outfit_cat_id) -{ - LLNotificationsUtil::add("DeleteOutfits", LLSD(), LLSD(), boost::bind(onOutfitsRemovalConfirmation, _1, _2, outfit_cat_id)); -} - -void LLOutfitGallery::onOutfitsRemovalConfirmation(const LLSD& notification, const LLSD& response, const LLUUID& outfit_cat_id) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option != 0) return; // canceled - - if (outfit_cat_id.notNull()) - { - gInventory.removeCategory(outfit_cat_id); - } -} - -void LLOutfitGallery::scrollToShowItem(const LLUUID& item_id) -{ - LLOutfitGalleryItem* item = mOutfitMap[item_id]; - if (item) - { - const LLRect visible_content_rect = mScrollPanel->getVisibleContentRect(); - - LLRect item_rect; - item->localRectToOtherView(item->getLocalRect(), &item_rect, mScrollPanel); - LLRect overlap_rect(item_rect); - overlap_rect.intersectWith(visible_content_rect); - - //Scroll when the selected item is outside the visible area - if (overlap_rect.getHeight() + 5 < item->getRect().getHeight()) - { - LLRect content_rect = mScrollPanel->getContentWindowRect(); - LLRect constraint_rect; - constraint_rect.setOriginAndSize(0, 0, content_rect.getWidth(), content_rect.getHeight()); - - LLRect item_doc_rect; - item->localRectToOtherView(item->getLocalRect(), &item_doc_rect, mGalleryPanel); - - mScrollPanel->scrollToShowRect(item_doc_rect, constraint_rect); - } - } -} - -void LLOutfitGallery::updateRowsIfNeeded() -{ - if(((getRect().getWidth() - mRowPanelWidth) > mItemWidth) && mRowCount > 1) - { - reArrangeRows(1); - } - else if((mRowPanelWidth > (getRect().getWidth() + mItemHorizontalGap)) && mItemsInRow > GALLERY_ITEMS_PER_ROW_MIN) - { - reArrangeRows(-1); - } -} - -bool compareGalleryItem(LLOutfitGalleryItem* item1, LLOutfitGalleryItem* item2) -{ - if(gSavedSettings.getBOOL("OutfitGallerySortByName") || - ((item1->isDefaultImage() && item2->isDefaultImage()) || (!item1->isDefaultImage() && !item2->isDefaultImage()))) - { - std::string name1 = item1->getItemName(); - std::string name2 = item2->getItemName(); - - return (LLStringUtil::compareDict(name1, name2) < 0); - } - else - { - return item2->isDefaultImage(); - } -} - -void LLOutfitGallery::reArrangeRows(S32 row_diff) -{ - std::vector buf_items = mItems; - for (std::vector::const_reverse_iterator it = buf_items.rbegin(); it != buf_items.rend(); ++it) - { - removeFromGalleryLast(*it); - } - for (std::vector::const_reverse_iterator it = mHiddenItems.rbegin(); it != mHiddenItems.rend(); ++it) - { - buf_items.push_back(*it); - } - mHiddenItems.clear(); - - mItemsInRow += row_diff; - updateGalleryWidth(); - std::sort(buf_items.begin(), buf_items.end(), compareGalleryItem); - - std::string cur_filter = getFilterSubString(); - LLStringUtil::toUpper(cur_filter); - - for (std::vector::const_iterator it = buf_items.begin(); it != buf_items.end(); ++it) - { - std::string outfit_name = (*it)->getItemName(); - LLStringUtil::toUpper(outfit_name); - - bool hidden = (std::string::npos == outfit_name.find(cur_filter)); - (*it)->setHidden(hidden); - - addToGallery(*it); - } - - updateMessageVisibility(); -} - -void LLOutfitGallery::updateGalleryWidth() -{ - mRowPanelWidth = mRowPanWidthFactor * mItemsInRow - mItemHorizontalGap; - mGalleryWidth = mGalleryWidthFactor * mItemsInRow - mItemHorizontalGap; -} - -LLPanel* LLOutfitGallery::addLastRow() -{ - mRowCount++; - int row = 0; - int vgap = mVerticalGap * row; - LLPanel* result = buildRowPanel(0, row * mRowPanelHeight + vgap); - mGalleryPanel->addChild(result); - return result; -} - -void LLOutfitGallery::moveRowUp(int row) -{ - moveRow(row, mRowCount - 1 - row + 1); -} - -void LLOutfitGallery::moveRowDown(int row) -{ - moveRow(row, mRowCount - 1 - row - 1); -} - -void LLOutfitGallery::moveRow(int row, int pos) -{ - int vgap = mVerticalGap * pos; - moveRowPanel(mRowPanels[row], 0, pos * mRowPanelHeight + vgap); -} - -void LLOutfitGallery::removeLastRow() -{ - mRowCount--; - mGalleryPanel->removeChild(mLastRowPanel); - mUnusedRowPanels.push_back(mLastRowPanel); - mRowPanels.pop_back(); - if (mRowPanels.size() > 0) - { - // Just removed last row - mLastRowPanel = mRowPanels.back(); - } - else - { - mLastRowPanel = NULL; - } -} - -LLPanel* LLOutfitGallery::addToRow(LLPanel* row_stack, LLOutfitGalleryItem* item, int pos, int hgap) -{ - LLPanel* lpanel = buildItemPanel(pos * mItemWidth + hgap); - lpanel->addChild(item); - row_stack->addChild(lpanel); - mItemPanels.push_back(lpanel); - return lpanel; -} - -void LLOutfitGallery::addToGallery(LLOutfitGalleryItem* item) -{ - if(item->isHidden()) - { - mHiddenItems.push_back(item); - return; - } - mItemIndexMap[item] = mItemsAddedCount; - mIndexToItemMap[mItemsAddedCount] = item; - mItemsAddedCount++; - int n = mItemsAddedCount; - int row_count = (n % mItemsInRow) == 0 ? n / mItemsInRow : n / mItemsInRow + 1; - int n_prev = n - 1; - int row_count_prev = (n_prev % mItemsInRow) == 0 ? n_prev / mItemsInRow : n_prev / mItemsInRow + 1; - - bool add_row = row_count != row_count_prev; - int pos = 0; - if (add_row) - { - for (int i = 0; i < row_count_prev; i++) - { - moveRowUp(i); - } - mLastRowPanel = addLastRow(); - mRowPanels.push_back(mLastRowPanel); - } - pos = (n - 1) % mItemsInRow; - mItems.push_back(item); - addToRow(mLastRowPanel, item, pos, mHorizontalGap * pos); - reshapeGalleryPanel(row_count); -} - - -void LLOutfitGallery::removeFromGalleryLast(LLOutfitGalleryItem* item) -{ - if(item->isHidden()) - { - mHiddenItems.pop_back(); - return; - } - int n_prev = mItemsAddedCount; - int n = mItemsAddedCount - 1; - int row_count = (n % mItemsInRow) == 0 ? n / mItemsInRow : n / mItemsInRow + 1; - int row_count_prev = (n_prev % mItemsInRow) == 0 ? n_prev / mItemsInRow : n_prev / mItemsInRow + 1; - mItemsAddedCount--; - mIndexToItemMap.erase(mItemsAddedCount); - - bool remove_row = row_count != row_count_prev; - removeFromLastRow(mItems[mItemsAddedCount]); - mItems.pop_back(); - if (remove_row) - { - for (int i = 0; i < row_count_prev - 1; i++) - { - moveRowDown(i); - } - removeLastRow(); - } - reshapeGalleryPanel(row_count); -} - - -void LLOutfitGallery::removeFromGalleryMiddle(LLOutfitGalleryItem* item) -{ - if(item->isHidden()) - { - mHiddenItems.erase(std::remove(mHiddenItems.begin(), mHiddenItems.end(), item), mHiddenItems.end()); - return; - } - int n = mItemIndexMap[item]; - mItemIndexMap.erase(item); - mIndexToItemMap.erase(n); - std::vector saved; - for (int i = mItemsAddedCount - 1; i > n; i--) - { - saved.push_back(mItems[i]); - removeFromGalleryLast(mItems[i]); - } - removeFromGalleryLast(mItems[n]); - int saved_count = saved.size(); - for (int i = 0; i < saved_count; i++) - { - addToGallery(saved.back()); - saved.pop_back(); - } -} - -void LLOutfitGallery::removeFromLastRow(LLOutfitGalleryItem* item) -{ - mItemPanels.back()->removeChild(item); - mLastRowPanel->removeChild(mItemPanels.back()); - mUnusedItemPanels.push_back(mItemPanels.back()); - mItemPanels.pop_back(); -} - -LLOutfitGalleryItem* LLOutfitGallery::buildGalleryItem(std::string name, LLUUID outfit_id) -{ - LLOutfitGalleryItem::Params giparams; - LLOutfitGalleryItem* gitem = LLUICtrlFactory::create(giparams); - gitem->reshape(mItemWidth, mItemHeight); - gitem->setVisible(true); - gitem->setFollowsLeft(); - gitem->setFollowsTop(); - gitem->setOutfitName(name); - gitem->setUUID(outfit_id); - gitem->setGallery(this); - return gitem; -} - -LLOutfitGalleryItem* LLOutfitGallery::getSelectedItem() -{ - return mOutfitMap[mSelectedOutfitUUID]; -} - -void LLOutfitGallery::buildGalleryPanel(int row_count) -{ - LLPanel::Params params; - mGalleryPanel = LLUICtrlFactory::create(params); - reshapeGalleryPanel(row_count); -} - -void LLOutfitGallery::reshapeGalleryPanel(int row_count) -{ - int bottom = 0; - int left = 0; - int height = row_count * (mRowPanelHeight + mVerticalGap); - LLRect rect = LLRect(left, bottom + height, left + mGalleryWidth, bottom); - mGalleryPanel->setRect(rect); - mGalleryPanel->reshape(mGalleryWidth, height); - mGalleryPanel->setVisible(true); - mGalleryPanel->setFollowsLeft(); - mGalleryPanel->setFollowsTop(); -} - -LLPanel* LLOutfitGallery::buildItemPanel(int left) -{ - LLPanel::Params lpparams; - int top = 0; - LLPanel* lpanel = NULL; - if(mUnusedItemPanels.empty()) - { - lpanel = LLUICtrlFactory::create(lpparams); - } - else - { - lpanel = mUnusedItemPanels.back(); - mUnusedItemPanels.pop_back(); - } - LLRect rect = LLRect(left, top + mItemHeight, left + mItemWidth + mItemHorizontalGap, top); - lpanel->setRect(rect); - lpanel->reshape(mItemWidth + mItemHorizontalGap, mItemHeight); - lpanel->setVisible(true); - lpanel->setFollowsLeft(); - lpanel->setFollowsTop(); - return lpanel; -} - -LLPanel* LLOutfitGallery::buildRowPanel(int left, int bottom) -{ - LLPanel::Params sparams; - LLPanel* stack = NULL; - if(mUnusedRowPanels.empty()) - { - stack = LLUICtrlFactory::create(sparams); - } - else - { - stack = mUnusedRowPanels.back(); - mUnusedRowPanels.pop_back(); - } - moveRowPanel(stack, left, bottom); - return stack; -} - -void LLOutfitGallery::moveRowPanel(LLPanel* stack, int left, int bottom) -{ - LLRect rect = LLRect(left, bottom + mRowPanelHeight, left + mRowPanelWidth, bottom); - stack->setRect(rect); - stack->reshape(mRowPanelWidth, mRowPanelHeight); - stack->setVisible(true); - stack->setFollowsLeft(); - stack->setFollowsTop(); -} - -LLOutfitGallery::~LLOutfitGallery() -{ - delete mOutfitGalleryMenu; - - if (gInventory.containsObserver(mOutfitsObserver)) - { - gInventory.removeObserver(mOutfitsObserver); - } - delete mOutfitsObserver; - - while (!mUnusedRowPanels.empty()) - { - LLPanel* panelp = mUnusedRowPanels.back(); - mUnusedRowPanels.pop_back(); - panelp->die(); - } - while (!mUnusedItemPanels.empty()) - { - LLPanel* panelp = mUnusedItemPanels.back(); - mUnusedItemPanels.pop_back(); - panelp->die(); - } -} - -// virtual -void LLOutfitGallery::onFilterSubStringChanged(const std::string& new_string, const std::string& old_string) -{ - reArrangeRows(); -} - -void LLOutfitGallery::onHighlightBaseOutfit(LLUUID base_id, LLUUID prev_id) -{ - if (mOutfitMap[base_id]) - { - mOutfitMap[base_id]->setOutfitWorn(true); - } - if (mOutfitMap[prev_id]) - { - mOutfitMap[prev_id]->setOutfitWorn(false); - } -} - -void LLOutfitGallery::onSetSelectedOutfitByUUID(const LLUUID& outfit_uuid) -{ -} - -void LLOutfitGallery::getCurrentCategories(uuid_vec_t& vcur) -{ - for (outfit_map_t::const_iterator iter = mOutfitMap.begin(); - iter != mOutfitMap.end(); - iter++) - { - if ((*iter).second != NULL) - { - vcur.push_back((*iter).first); - } - } -} - -void LLOutfitGallery::updateAddedCategory(LLUUID cat_id) -{ - LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id); - if (!cat) return; - - std::string name = cat->getName(); - LLOutfitGalleryItem* item = buildGalleryItem(name, cat_id); - mOutfitMap.insert(LLOutfitGallery::outfit_map_value_t(cat_id, item)); - item->setRightMouseDownCallback(boost::bind(&LLOutfitListBase::outfitRightClickCallBack, this, - _1, _2, _3, cat_id)); - LLWearableItemsList* list = NULL; - item->setFocusReceivedCallback(boost::bind(&LLOutfitListBase::ChangeOutfitSelection, this, list, cat_id)); - if (mGalleryCreated) - { - addToGallery(item); - } - - LLViewerInventoryCategory* outfit_category = gInventory.getCategory(cat_id); - if (!outfit_category) - return; - - if (mOutfitsObserver == NULL) - { - mOutfitsObserver = new LLInventoryCategoriesObserver(); - gInventory.addObserver(mOutfitsObserver); - } - - // Start observing changes in "My Outfits" category. - mOutfitsObserver->addCategory(cat_id, - boost::bind(&LLOutfitGallery::refreshOutfit, this, cat_id), true); - - outfit_category->fetch(); - refreshOutfit(cat_id); -} - -void LLOutfitGallery::updateRemovedCategory(LLUUID cat_id) -{ - outfit_map_t::iterator outfits_iter = mOutfitMap.find(cat_id); - if (outfits_iter != mOutfitMap.end()) - { - // 0. Remove category from observer. - mOutfitsObserver->removeCategory(cat_id); - - //const LLUUID& outfit_id = outfits_iter->first; - LLOutfitGalleryItem* item = outfits_iter->second; - - // An outfit is removed from the list. Do the following: - // 2. Remove the outfit from selection. - deselectOutfit(cat_id); - - // 3. Remove category UUID to accordion tab mapping. - mOutfitMap.erase(outfits_iter); - - // 4. Remove outfit from gallery. - removeFromGalleryMiddle(item); - - // kill removed item - if (item != NULL) - { - item->die(); - } - } - -} - -void LLOutfitGallery::updateChangedCategoryName(LLViewerInventoryCategory *cat, std::string name) -{ - outfit_map_t::iterator outfit_iter = mOutfitMap.find(cat->getUUID()); - if (outfit_iter != mOutfitMap.end()) - { - // Update name of outfit in gallery - LLOutfitGalleryItem* item = outfit_iter->second; - if (item) - { - item->setOutfitName(name); - } - } -} - -void LLOutfitGallery::onOutfitRightClick(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& cat_id) -{ - if (mOutfitMenu && cat_id.notNull()) - { - uuid_vec_t selected_uuids; - selected_uuids.push_back(cat_id); - mOutfitGalleryMenu->show(ctrl, selected_uuids, x, y); - } -} - -void LLOutfitGallery::onChangeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id) -{ - if (mSelectedOutfitUUID == category_id) - return; - if (mOutfitMap[mSelectedOutfitUUID]) - { - mOutfitMap[mSelectedOutfitUUID]->setSelected(false); - } - if (mOutfitMap[category_id]) - { - mOutfitMap[category_id]->setSelected(true); - } - // mSelectedOutfitUUID will be set in LLOutfitListBase::ChangeOutfitSelection -} - -void LLOutfitGallery::wearSelectedOutfit() -{ - LLAppearanceMgr::instance().replaceCurrentOutfit(getSelectedOutfitUUID()); -} - -bool LLOutfitGallery::hasItemSelected() -{ - return false; -} - -bool LLOutfitGallery::canWearSelected() -{ - return false; -} - -bool LLOutfitGallery::hasDefaultImage(const LLUUID& outfit_cat_id) -{ - if (mOutfitMap[outfit_cat_id]) - { - return mOutfitMap[outfit_cat_id]->isDefaultImage(); - } - return false; -} - -void LLOutfitGallery::updateMessageVisibility() -{ - if (mItems.empty()) - { - mMessageTextBox->setVisible(true); - mScrollPanel->setVisible(false); - std::string message = getString(getFilterSubString().empty() ? "no_outfits_msg" : "no_matched_outfits_msg"); - mMessageTextBox->setValue(message); - } - else - { - mScrollPanel->setVisible(true); - mMessageTextBox->setVisible(false); - } -} - -LLOutfitListGearMenuBase* LLOutfitGallery::createGearMenu() -{ - return new LLOutfitGalleryGearMenu(this); -} - -static LLDefaultChildRegistry::Register r("outfit_gallery_item"); - -LLOutfitGalleryItem::LLOutfitGalleryItem(const Params& p) - : LLPanel(p), - mGallery(nullptr), - mTexturep(nullptr), - mSelected(false), - mWorn(false), - mDefaultImage(true), - mOutfitName(""), - mUUID(LLUUID()) -{ - buildFromFile("panel_outfit_gallery_item.xml"); -} - -LLOutfitGalleryItem::~LLOutfitGalleryItem() -{ - -} - -bool LLOutfitGalleryItem::postBuild() -{ - setDefaultImage(); - - mOutfitNameText = getChild("outfit_name"); - mOutfitWornText = getChild("outfit_worn_text"); - mTextBgPanel = getChild("text_bg_panel"); - setOutfitWorn(false); - mHidden = false; - return true; -} - -void LLOutfitGalleryItem::draw() -{ - LLPanel::draw(); - - // Draw border - LLUIColor border_color = LLUIColorTable::instance().getColor(mSelected ? "OutfitGalleryItemSelected" : "OutfitGalleryItemUnselected", LLColor4::white); - LLRect border = getChildView("preview_outfit")->getRect(); - border.mRight = border.mRight + 1; - gl_rect_2d(border, border_color.get(), false); - - // If the floater is focused, don't apply its alpha to the texture (STORM-677). - const F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); - if (mTexturep) - { - if (mImageUpdatePending && mTexturep->getDiscardLevel() >= 0) - { - mImageUpdatePending = false; - if (mTexturep->getOriginalWidth() > MAX_OUTFIT_PHOTO_WIDTH || mTexturep->getOriginalHeight() > MAX_OUTFIT_PHOTO_HEIGHT) - { - setDefaultImage(); - } - } - else - { - LLRect interior = border; - interior.stretch(-1); - - gl_draw_scaled_image(interior.mLeft - 1, interior.mBottom, interior.getWidth(), interior.getHeight(), mTexturep, UI_VERTEX_COLOR % alpha); - - // Pump the priority - mTexturep->addTextureStats((F32)(interior.getWidth() * interior.getHeight())); - } - } - -} - -void LLOutfitGalleryItem::setOutfitName(std::string name) -{ - mOutfitNameText->setText(name); - mOutfitNameText->setToolTip(name); - mOutfitName = name; -} - -void LLOutfitGalleryItem::setOutfitWorn(bool value) -{ - mWorn = value; - LLStringUtil::format_map_t worn_string_args; - std::string worn_string = getString("worn_string", worn_string_args); - LLUIColor text_color = LLUIColorTable::instance().getColor("White", LLColor4::white); - mOutfitWornText->setReadOnlyColor(text_color.get()); - mOutfitNameText->setReadOnlyColor(text_color.get()); - mOutfitWornText->setFont(value ? LLFontGL::getFontSansSerifBold() : LLFontGL::getFontSansSerifSmall()); - mOutfitNameText->setFont(value ? LLFontGL::getFontSansSerifBold() : LLFontGL::getFontSansSerifSmall()); - mOutfitWornText->setValue(value ? worn_string : ""); - mOutfitNameText->setText(mOutfitName); // refresh LLTextViewModel to pick up font changes -} - -void LLOutfitGalleryItem::setSelected(bool value) -{ - mSelected = value; - mTextBgPanel->setBackgroundVisible(value); - setOutfitWorn(mWorn); -} - -bool LLOutfitGalleryItem::handleMouseDown(S32 x, S32 y, MASK mask) -{ - setFocus(true); - return LLUICtrl::handleMouseDown(x, y, mask); -} - -bool LLOutfitGalleryItem::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - setFocus(true); - return LLUICtrl::handleRightMouseDown(x, y, mask); -} - -bool LLOutfitGalleryItem::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - return openOutfitsContent() || LLPanel::handleDoubleClick(x, y, mask); -} - -bool LLOutfitGalleryItem::handleKeyHere(KEY key, MASK mask) -{ - if (!mGallery) - { - return false; - } - - bool handled = false; - switch (key) - { - case KEY_LEFT: - mGallery->moveLeft(); - handled = true; - break; - - case KEY_RIGHT: - mGallery->moveRight(); - handled = true; - break; - - case KEY_UP: - mGallery->moveUp(); - handled = true; - break; - - case KEY_DOWN: - mGallery->moveDown(); - handled = true; - break; - - default: - break; - } - return handled; -} - -void LLOutfitGalleryItem::onFocusLost() -{ - setSelected(false); - - LLPanel::onFocusLost(); -} - -void LLOutfitGalleryItem::onFocusReceived() -{ - setSelected(true); - - LLPanel::onFocusReceived(); -} - -bool LLOutfitGalleryItem::openOutfitsContent() -{ - LLTabContainer* appearence_tabs = LLPanelOutfitsInventory::findInstance()->getChild("appearance_tabs"); - if (appearence_tabs && mUUID.notNull()) - { - appearence_tabs->selectTabByName("outfitslist_tab"); - LLPanel* panel = appearence_tabs->getCurrentPanel(); - if (panel) - { - LLAccordionCtrl* accordion = panel->getChild("outfits_accordion"); - LLOutfitsList* outfit_list = dynamic_cast(panel); - if (accordion != NULL && outfit_list != NULL) - { - outfit_list->setSelectedOutfitByUUID(mUUID); - LLAccordionCtrlTab* tab = accordion->getSelectedTab(); - if (tab) - { - tab->showAndFocusHeader(); - return true; - } - } - } - } - return false; -} - -bool LLOutfitGalleryItem::setImageAssetId(LLUUID image_asset_id) -{ - LLPointer texture = LLViewerTextureManager::getFetchedTexture(image_asset_id, FTT_DEFAULT, MIPMAP_YES, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); - if (texture && texture->getOriginalWidth() <= MAX_OUTFIT_PHOTO_WIDTH && texture->getOriginalHeight() <= MAX_OUTFIT_PHOTO_HEIGHT) - { - mImageAssetId = image_asset_id; - mTexturep = texture; - getChildView("preview_outfit")->setVisible(false); - mDefaultImage = false; - mImageUpdatePending = (texture->getDiscardLevel() == -1); - return true; - } - return false; -} - -LLUUID LLOutfitGalleryItem::getImageAssetId() -{ - return mImageAssetId; -} - -void LLOutfitGalleryItem::setDefaultImage() -{ - mTexturep = NULL; - mImageAssetId.setNull(); - getChildView("preview_outfit")->setVisible(true); - mDefaultImage = true; - mImageUpdatePending = false; -} - -LLContextMenu* LLOutfitGalleryContextMenu::createMenu() -{ - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; - LLUUID selected_id = mUUIDs.front(); - - registrar.add("Outfit.WearReplace", - boost::bind(&LLAppearanceMgr::replaceCurrentOutfit, &LLAppearanceMgr::instance(), selected_id)); - registrar.add("Outfit.WearAdd", - boost::bind(&LLAppearanceMgr::addCategoryToCurrentOutfit, &LLAppearanceMgr::instance(), selected_id)); - registrar.add("Outfit.TakeOff", - boost::bind(&LLAppearanceMgr::takeOffOutfit, &LLAppearanceMgr::instance(), selected_id)); - registrar.add("Outfit.Edit", boost::bind(editOutfit)); - registrar.add("Outfit.Rename", boost::bind(renameOutfit, selected_id)); - registrar.add("Outfit.Delete", boost::bind(LLOutfitGallery::onRemoveOutfit, selected_id)); - registrar.add("Outfit.Create", boost::bind(&LLOutfitGalleryContextMenu::onCreate, this, _2)); - registrar.add("Outfit.Thumbnail", boost::bind(&LLOutfitGalleryContextMenu::onThumbnail, this, selected_id)); - registrar.add("Outfit.Save", boost::bind(&LLOutfitGalleryContextMenu::onSave, this, selected_id)); - enable_registrar.add("Outfit.OnEnable", boost::bind(&LLOutfitGalleryContextMenu::onEnable, this, _2)); - enable_registrar.add("Outfit.OnVisible", boost::bind(&LLOutfitGalleryContextMenu::onVisible, this, _2)); - - return createFromFile("menu_gallery_outfit_tab.xml"); -} - -void LLOutfitGalleryContextMenu::onCreate(const LLSD& data) -{ - LLWearableType::EType type = LLWearableType::getInstance()->typeNameToType(data.asString()); - if (type == LLWearableType::WT_NONE) - { - LL_WARNS() << "Invalid wearable type" << LL_ENDL; - return; - } - - LLAgentWearables::createWearable(type, true); -} - -bool LLOutfitGalleryContextMenu::onEnable(LLSD::String param) -{ - return LLOutfitContextMenu::onEnable(param); -} - -bool LLOutfitGalleryContextMenu::onVisible(LLSD::String param) -{ - return LLOutfitContextMenu::onVisible(param); -} - -LLOutfitGalleryGearMenu::LLOutfitGalleryGearMenu(LLOutfitListBase* olist) - : LLOutfitListGearMenuBase(olist) -{ -} - -void LLOutfitGalleryGearMenu::onUpdateItemsVisibility() -{ - if (!mMenu) return; - bool have_selection = getSelectedOutfitID().notNull(); - mMenu->setItemVisible("expand", false); - mMenu->setItemVisible("collapse", false); - mMenu->setItemVisible("thumbnail", have_selection); - mMenu->setItemVisible("sepatator3", true); - mMenu->setItemVisible("sort_folders_by_name", true); - LLOutfitListGearMenuBase::onUpdateItemsVisibility(); -} - -void LLOutfitGalleryGearMenu::onChangeSortOrder() -{ - bool sort_by_name = !gSavedSettings.getBOOL("OutfitGallerySortByName"); - gSavedSettings.setBOOL("OutfitGallerySortByName", sort_by_name); - LLOutfitGallery* gallery = dynamic_cast(mOutfitList); - if (gallery) - { - gallery->reArrangeRows(); - } -} - -bool LLOutfitGalleryGearMenu::hasDefaultImage() -{ - LLOutfitGallery* gallery = dynamic_cast(mOutfitList); - LLUUID selected_outfit_id = getSelectedOutfitID(); - if (gallery && selected_outfit_id.notNull()) - { - return gallery->hasDefaultImage(selected_outfit_id); - } - return true; -} - -void LLOutfitGallery::onTextureSelectionChanged(LLInventoryItem* itemp) -{ -} - -void LLOutfitGallery::refreshOutfit(const LLUUID& category_id) -{ - LLViewerInventoryCategory* category = gInventory.getCategory(category_id); - if (category) - { - bool photo_loaded = false; - LLUUID asset_id = category->getThumbnailUUID(); - if (asset_id.isNull()) - { - LLInventoryModel::cat_array_t sub_cat_array; - LLInventoryModel::item_array_t outfit_item_array; - // Collect all sub-categories of a given category. - gInventory.collectDescendents( - category->getUUID(), - sub_cat_array, - outfit_item_array, - LLInventoryModel::EXCLUDE_TRASH); - for (LLViewerInventoryItem* outfit_item : outfit_item_array) - { - LLViewerInventoryItem* linked_item = outfit_item->getLinkedItem(); - LLUUID asset_id, inv_id; - std::string item_name; - if (linked_item != NULL) - { - if (linked_item->getActualType() == LLAssetType::AT_TEXTURE) - { - asset_id = linked_item->getAssetUUID(); - inv_id = linked_item->getUUID(); - item_name = linked_item->getName(); - } - } - else if (outfit_item->getActualType() == LLAssetType::AT_TEXTURE) - { - asset_id = outfit_item->getAssetUUID(); - inv_id = outfit_item->getUUID(); - item_name = outfit_item->getName(); - } - if (category->getThumbnailUUID().notNull()) - { - asset_id = category->getThumbnailUUID(); - } - if (asset_id.notNull()) - { - photo_loaded |= mOutfitMap[category_id]->setImageAssetId(asset_id); - // Rename links - if (!mOutfitRenamePending.isNull() && mOutfitRenamePending.asString() == item_name) - { - LLViewerInventoryCategory *outfit_cat = gInventory.getCategory(mOutfitRenamePending); - LLStringUtil::format_map_t photo_string_args; - photo_string_args["OUTFIT_NAME"] = outfit_cat->getName(); - std::string new_name = getString("outfit_photo_string", photo_string_args); - LLSD updates; - updates["name"] = new_name; - update_inventory_item(inv_id, updates, NULL); - mOutfitRenamePending.setNull(); - LLFloater* appearance_floater = LLFloaterReg::getInstance("appearance"); - if (appearance_floater) - { - appearance_floater->setFocus(true); - } - } - if (item_name == LLAppearanceMgr::sExpectedTextureName) - { - // Images with "appropriate" name take priority - break; - } - } - if (!photo_loaded) - { - mOutfitMap[category_id]->setDefaultImage(); - } - } - } - else - { - mOutfitMap[category_id]->setImageAssetId(asset_id); - } - } - - if (mGalleryCreated && !LLApp::isExiting()) - { - reArrangeRows(); - } -} - -LLUUID LLOutfitGallery::getPhotoAssetId(const LLUUID& outfit_id) -{ - outfit_map_t::iterator outfit_it = mOutfitMap.find(outfit_id); - if (outfit_it != mOutfitMap.end()) - { - return outfit_it->second->getImageAssetId(); - } - return LLUUID(); -} - -LLUUID LLOutfitGallery::getDefaultPhoto() -{ - return LLUUID(); -} - +/** + * @file lloutfitgallery.cpp + * @author Pavlo Kryvych + * @brief Visual gallery of agent's outfits for My Appearance side panel + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2015, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" // must be first include +#include "lloutfitgallery.h" + +// llcommon +#include "llcommonutils.h" +#include "llfilesystem.h" + +#include "llaccordionctrltab.h" +#include "llappearancemgr.h" +#include "llerror.h" +#include "llfilepicker.h" +#include "llfloaterperms.h" +#include "llfloaterreg.h" +#include "llfloatersimplesnapshot.h" +#include "llimagedimensionsinfo.h" +#include "llinventoryfunctions.h" +#include "llinventorymodel.h" +#include "lllocalbitmaps.h" +#include "llnotificationsutil.h" +#include "llpaneloutfitsinventory.h" +#include "lltabcontainer.h" +#include "lltexturectrl.h" +#include "lltrans.h" +#include "llviewercontrol.h" +#include "llviewermenufile.h" +#include "llviewertexturelist.h" +#include "llwearableitemslist.h" + +static LLPanelInjector t_outfit_gallery("outfit_gallery"); + +#define MAX_OUTFIT_PHOTO_WIDTH 256 +#define MAX_OUTFIT_PHOTO_HEIGHT 256 + +const S32 GALLERY_ITEMS_PER_ROW_MIN = 2; + +LLOutfitGallery::LLOutfitGallery(const LLOutfitGallery::Params& p) + : LLOutfitListBase(), + mOutfitsObserver(NULL), + mScrollPanel(NULL), + mGalleryPanel(NULL), + mLastRowPanel(NULL), + mGalleryCreated(false), + mRowCount(0), + mItemsAddedCount(0), + mOutfitLinkPending(NULL), + mOutfitRenamePending(NULL), + mSnapshotFolderID(NULL), + mRowPanelHeight(p.row_panel_height), + mVerticalGap(p.vertical_gap), + mHorizontalGap(p.horizontal_gap), + mItemWidth(p.item_width), + mItemHeight(p.item_height), + mItemHorizontalGap(p.item_horizontal_gap), + mItemsInRow(p.items_in_row), + mRowPanWidthFactor(p.row_panel_width_factor), + mGalleryWidthFactor(p.gallery_width_factor), + mTextureSelected(NULL) +{ + updateGalleryWidth(); +} + +LLOutfitGallery::Params::Params() + : row_panel_height("row_panel_height", 180), + vertical_gap("vertical_gap", 10), + horizontal_gap("horizontal_gap", 10), + item_width("item_width", 150), + item_height("item_height", 175), + item_horizontal_gap("item_horizontal_gap", 16), + items_in_row("items_in_row", GALLERY_ITEMS_PER_ROW_MIN), + row_panel_width_factor("row_panel_width_factor", 166), + gallery_width_factor("gallery_width_factor", 163) +{ + addSynonym(row_panel_height, "row_height"); +} + +const LLOutfitGallery::Params& LLOutfitGallery::getDefaultParams() +{ + return LLUICtrlFactory::getDefaultParams(); +} + +bool LLOutfitGallery::postBuild() +{ + bool rv = LLOutfitListBase::postBuild(); + mScrollPanel = getChild("gallery_scroll_panel"); + LLPanel::Params params = LLPanel::getDefaultParams(); // Don't parse XML when creating dummy LLPanel + mGalleryPanel = LLUICtrlFactory::create(params); + mMessageTextBox = getChild("no_outfits_txt"); + mOutfitGalleryMenu = new LLOutfitGalleryContextMenu(this); + return rv; +} + +void LLOutfitGallery::onOpen(const LLSD& info) +{ + LLOutfitListBase::onOpen(info); + if (!mGalleryCreated) + { + uuid_vec_t cats; + getCurrentCategories(cats); + int n = cats.size(); + buildGalleryPanel(n); + mScrollPanel->addChild(mGalleryPanel); + for (int i = 0; i < n; i++) + { + addToGallery(mOutfitMap[cats[i]]); + } + reArrangeRows(); + mGalleryCreated = true; + } +} + +void LLOutfitGallery::draw() +{ + LLPanel::draw(); + if (mGalleryCreated) + { + updateRowsIfNeeded(); + } +} + +bool LLOutfitGallery::handleKeyHere(KEY key, MASK mask) +{ + bool handled = false; + switch (key) + { + case KEY_RETURN: + // Open selected items if enter key hit on the inventory panel + if (mask == MASK_NONE && mSelectedOutfitUUID.notNull()) + { + // Or should it wearSelectedOutfit? + getSelectedItem()->openOutfitsContent(); + } + handled = true; + break; + case KEY_DELETE: +#if LL_DARWIN + case KEY_BACKSPACE: +#endif + // Delete selected items if delete or backspace key hit on the inventory panel + // Note: on Mac laptop keyboards, backspace and delete are one and the same + if (mSelectedOutfitUUID.notNull()) + { + onRemoveOutfit(mSelectedOutfitUUID); + } + handled = true; + break; + + case KEY_F2: + LLAppearanceMgr::instance().renameOutfit(mSelectedOutfitUUID); + handled = true; + break; + + case KEY_PAGE_UP: + if (mScrollPanel) + { + mScrollPanel->pageUp(30); + } + handled = true; + break; + + case KEY_PAGE_DOWN: + if (mScrollPanel) + { + mScrollPanel->pageDown(30); + } + handled = true; + break; + + case KEY_HOME: + if (mScrollPanel) + { + mScrollPanel->goToTop(); + } + handled = true; + break; + + case KEY_END: + if (mScrollPanel) + { + mScrollPanel->goToBottom(); + } + handled = true; + break; + + case KEY_LEFT: + moveLeft(); + handled = true; + break; + + case KEY_RIGHT: + moveRight(); + handled = true; + break; + + case KEY_UP: + moveUp(); + handled = true; + break; + + case KEY_DOWN: + moveDown(); + handled = true; + break; + + default: + break; + } + + if (handled) + { + mOutfitGalleryMenu->hide(); + } + + return handled; +} + +void LLOutfitGallery::moveUp() +{ + if (mSelectedOutfitUUID.notNull() && mItemsAddedCount > 1) + { + LLOutfitGalleryItem* item = getSelectedItem(); + if (item) + { + S32 n = mItemIndexMap[item]; + n -= mItemsInRow; + if (n >= 0) + { + item = mIndexToItemMap[n]; + LLUUID item_id = item->getUUID(); + ChangeOutfitSelection(nullptr, item_id); + item->setFocus(true); + + scrollToShowItem(mSelectedOutfitUUID); + } + } + } +} + +void LLOutfitGallery::moveDown() +{ + if (mSelectedOutfitUUID.notNull() && mItemsAddedCount > 1) + { + LLOutfitGalleryItem* item = getSelectedItem(); + if (item) + { + S32 n = mItemIndexMap[item]; + n += mItemsInRow; + if (n < mItemsAddedCount) + { + item = mIndexToItemMap[n]; + LLUUID item_id = item->getUUID(); + ChangeOutfitSelection(nullptr, item_id); + item->setFocus(true); + + scrollToShowItem(mSelectedOutfitUUID); + } + } + } +} + +void LLOutfitGallery::moveLeft() +{ + if (mSelectedOutfitUUID.notNull() && mItemsAddedCount > 1) + { + LLOutfitGalleryItem* item = getSelectedItem(); + if (item) + { + // Might be better to get item from panel + S32 n = mItemIndexMap[item]; + n--; + if (n < 0) + { + n = mItemsAddedCount - 1; + } + item = mIndexToItemMap[n]; + LLUUID item_id = item->getUUID(); + ChangeOutfitSelection(nullptr, item_id); + item->setFocus(true); + + scrollToShowItem(mSelectedOutfitUUID); + } + } +} + +void LLOutfitGallery::moveRight() +{ + if (mSelectedOutfitUUID.notNull() && mItemsAddedCount > 1) + { + LLOutfitGalleryItem* item = getSelectedItem(); + if (item) + { + S32 n = mItemIndexMap[item]; + n++; + if (n == mItemsAddedCount) + { + n = 0; + } + item = mIndexToItemMap[n]; + LLUUID item_id = item->getUUID(); + ChangeOutfitSelection(nullptr, item_id); + item->setFocus(true); + + scrollToShowItem(mSelectedOutfitUUID); + } + } +} + +void LLOutfitGallery::onFocusLost() +{ + LLOutfitListBase::onFocusLost(); + + if (mSelectedOutfitUUID.notNull()) + { + LLOutfitGalleryItem* item = getSelectedItem(); + if (item) + { + item->setSelected(false); + } + } +} + +void LLOutfitGallery::onFocusReceived() +{ + LLOutfitListBase::onFocusReceived(); + + if (mSelectedOutfitUUID.notNull()) + { + LLOutfitGalleryItem* item = getSelectedItem(); + if (item) + { + item->setSelected(true); + } + } +} + +void LLOutfitGallery::onRemoveOutfit(const LLUUID& outfit_cat_id) +{ + LLNotificationsUtil::add("DeleteOutfits", LLSD(), LLSD(), boost::bind(onOutfitsRemovalConfirmation, _1, _2, outfit_cat_id)); +} + +void LLOutfitGallery::onOutfitsRemovalConfirmation(const LLSD& notification, const LLSD& response, const LLUUID& outfit_cat_id) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) return; // canceled + + if (outfit_cat_id.notNull()) + { + gInventory.removeCategory(outfit_cat_id); + } +} + +void LLOutfitGallery::scrollToShowItem(const LLUUID& item_id) +{ + LLOutfitGalleryItem* item = mOutfitMap[item_id]; + if (item) + { + const LLRect visible_content_rect = mScrollPanel->getVisibleContentRect(); + + LLRect item_rect; + item->localRectToOtherView(item->getLocalRect(), &item_rect, mScrollPanel); + LLRect overlap_rect(item_rect); + overlap_rect.intersectWith(visible_content_rect); + + //Scroll when the selected item is outside the visible area + if (overlap_rect.getHeight() + 5 < item->getRect().getHeight()) + { + LLRect content_rect = mScrollPanel->getContentWindowRect(); + LLRect constraint_rect; + constraint_rect.setOriginAndSize(0, 0, content_rect.getWidth(), content_rect.getHeight()); + + LLRect item_doc_rect; + item->localRectToOtherView(item->getLocalRect(), &item_doc_rect, mGalleryPanel); + + mScrollPanel->scrollToShowRect(item_doc_rect, constraint_rect); + } + } +} + +void LLOutfitGallery::updateRowsIfNeeded() +{ + if(((getRect().getWidth() - mRowPanelWidth) > mItemWidth) && mRowCount > 1) + { + reArrangeRows(1); + } + else if((mRowPanelWidth > (getRect().getWidth() + mItemHorizontalGap)) && mItemsInRow > GALLERY_ITEMS_PER_ROW_MIN) + { + reArrangeRows(-1); + } +} + +bool compareGalleryItem(LLOutfitGalleryItem* item1, LLOutfitGalleryItem* item2) +{ + if(gSavedSettings.getBOOL("OutfitGallerySortByName") || + ((item1->isDefaultImage() && item2->isDefaultImage()) || (!item1->isDefaultImage() && !item2->isDefaultImage()))) + { + std::string name1 = item1->getItemName(); + std::string name2 = item2->getItemName(); + + return (LLStringUtil::compareDict(name1, name2) < 0); + } + else + { + return item2->isDefaultImage(); + } +} + +void LLOutfitGallery::reArrangeRows(S32 row_diff) +{ + std::vector buf_items = mItems; + for (std::vector::const_reverse_iterator it = buf_items.rbegin(); it != buf_items.rend(); ++it) + { + removeFromGalleryLast(*it); + } + for (std::vector::const_reverse_iterator it = mHiddenItems.rbegin(); it != mHiddenItems.rend(); ++it) + { + buf_items.push_back(*it); + } + mHiddenItems.clear(); + + mItemsInRow += row_diff; + updateGalleryWidth(); + std::sort(buf_items.begin(), buf_items.end(), compareGalleryItem); + + std::string cur_filter = getFilterSubString(); + LLStringUtil::toUpper(cur_filter); + + for (std::vector::const_iterator it = buf_items.begin(); it != buf_items.end(); ++it) + { + std::string outfit_name = (*it)->getItemName(); + LLStringUtil::toUpper(outfit_name); + + bool hidden = (std::string::npos == outfit_name.find(cur_filter)); + (*it)->setHidden(hidden); + + addToGallery(*it); + } + + updateMessageVisibility(); +} + +void LLOutfitGallery::updateGalleryWidth() +{ + mRowPanelWidth = mRowPanWidthFactor * mItemsInRow - mItemHorizontalGap; + mGalleryWidth = mGalleryWidthFactor * mItemsInRow - mItemHorizontalGap; +} + +LLPanel* LLOutfitGallery::addLastRow() +{ + mRowCount++; + int row = 0; + int vgap = mVerticalGap * row; + LLPanel* result = buildRowPanel(0, row * mRowPanelHeight + vgap); + mGalleryPanel->addChild(result); + return result; +} + +void LLOutfitGallery::moveRowUp(int row) +{ + moveRow(row, mRowCount - 1 - row + 1); +} + +void LLOutfitGallery::moveRowDown(int row) +{ + moveRow(row, mRowCount - 1 - row - 1); +} + +void LLOutfitGallery::moveRow(int row, int pos) +{ + int vgap = mVerticalGap * pos; + moveRowPanel(mRowPanels[row], 0, pos * mRowPanelHeight + vgap); +} + +void LLOutfitGallery::removeLastRow() +{ + mRowCount--; + mGalleryPanel->removeChild(mLastRowPanel); + mUnusedRowPanels.push_back(mLastRowPanel); + mRowPanels.pop_back(); + if (mRowPanels.size() > 0) + { + // Just removed last row + mLastRowPanel = mRowPanels.back(); + } + else + { + mLastRowPanel = NULL; + } +} + +LLPanel* LLOutfitGallery::addToRow(LLPanel* row_stack, LLOutfitGalleryItem* item, int pos, int hgap) +{ + LLPanel* lpanel = buildItemPanel(pos * mItemWidth + hgap); + lpanel->addChild(item); + row_stack->addChild(lpanel); + mItemPanels.push_back(lpanel); + return lpanel; +} + +void LLOutfitGallery::addToGallery(LLOutfitGalleryItem* item) +{ + if(item->isHidden()) + { + mHiddenItems.push_back(item); + return; + } + mItemIndexMap[item] = mItemsAddedCount; + mIndexToItemMap[mItemsAddedCount] = item; + mItemsAddedCount++; + int n = mItemsAddedCount; + int row_count = (n % mItemsInRow) == 0 ? n / mItemsInRow : n / mItemsInRow + 1; + int n_prev = n - 1; + int row_count_prev = (n_prev % mItemsInRow) == 0 ? n_prev / mItemsInRow : n_prev / mItemsInRow + 1; + + bool add_row = row_count != row_count_prev; + int pos = 0; + if (add_row) + { + for (int i = 0; i < row_count_prev; i++) + { + moveRowUp(i); + } + mLastRowPanel = addLastRow(); + mRowPanels.push_back(mLastRowPanel); + } + pos = (n - 1) % mItemsInRow; + mItems.push_back(item); + addToRow(mLastRowPanel, item, pos, mHorizontalGap * pos); + reshapeGalleryPanel(row_count); +} + + +void LLOutfitGallery::removeFromGalleryLast(LLOutfitGalleryItem* item) +{ + if(item->isHidden()) + { + mHiddenItems.pop_back(); + return; + } + int n_prev = mItemsAddedCount; + int n = mItemsAddedCount - 1; + int row_count = (n % mItemsInRow) == 0 ? n / mItemsInRow : n / mItemsInRow + 1; + int row_count_prev = (n_prev % mItemsInRow) == 0 ? n_prev / mItemsInRow : n_prev / mItemsInRow + 1; + mItemsAddedCount--; + mIndexToItemMap.erase(mItemsAddedCount); + + bool remove_row = row_count != row_count_prev; + removeFromLastRow(mItems[mItemsAddedCount]); + mItems.pop_back(); + if (remove_row) + { + for (int i = 0; i < row_count_prev - 1; i++) + { + moveRowDown(i); + } + removeLastRow(); + } + reshapeGalleryPanel(row_count); +} + + +void LLOutfitGallery::removeFromGalleryMiddle(LLOutfitGalleryItem* item) +{ + if(item->isHidden()) + { + mHiddenItems.erase(std::remove(mHiddenItems.begin(), mHiddenItems.end(), item), mHiddenItems.end()); + return; + } + int n = mItemIndexMap[item]; + mItemIndexMap.erase(item); + mIndexToItemMap.erase(n); + std::vector saved; + for (int i = mItemsAddedCount - 1; i > n; i--) + { + saved.push_back(mItems[i]); + removeFromGalleryLast(mItems[i]); + } + removeFromGalleryLast(mItems[n]); + int saved_count = saved.size(); + for (int i = 0; i < saved_count; i++) + { + addToGallery(saved.back()); + saved.pop_back(); + } +} + +void LLOutfitGallery::removeFromLastRow(LLOutfitGalleryItem* item) +{ + mItemPanels.back()->removeChild(item); + mLastRowPanel->removeChild(mItemPanels.back()); + mUnusedItemPanels.push_back(mItemPanels.back()); + mItemPanels.pop_back(); +} + +LLOutfitGalleryItem* LLOutfitGallery::buildGalleryItem(std::string name, LLUUID outfit_id) +{ + LLOutfitGalleryItem::Params giparams; + LLOutfitGalleryItem* gitem = LLUICtrlFactory::create(giparams); + gitem->reshape(mItemWidth, mItemHeight); + gitem->setVisible(true); + gitem->setFollowsLeft(); + gitem->setFollowsTop(); + gitem->setOutfitName(name); + gitem->setUUID(outfit_id); + gitem->setGallery(this); + return gitem; +} + +LLOutfitGalleryItem* LLOutfitGallery::getSelectedItem() +{ + return mOutfitMap[mSelectedOutfitUUID]; +} + +void LLOutfitGallery::buildGalleryPanel(int row_count) +{ + LLPanel::Params params; + mGalleryPanel = LLUICtrlFactory::create(params); + reshapeGalleryPanel(row_count); +} + +void LLOutfitGallery::reshapeGalleryPanel(int row_count) +{ + int bottom = 0; + int left = 0; + int height = row_count * (mRowPanelHeight + mVerticalGap); + LLRect rect = LLRect(left, bottom + height, left + mGalleryWidth, bottom); + mGalleryPanel->setRect(rect); + mGalleryPanel->reshape(mGalleryWidth, height); + mGalleryPanel->setVisible(true); + mGalleryPanel->setFollowsLeft(); + mGalleryPanel->setFollowsTop(); +} + +LLPanel* LLOutfitGallery::buildItemPanel(int left) +{ + LLPanel::Params lpparams; + int top = 0; + LLPanel* lpanel = NULL; + if(mUnusedItemPanels.empty()) + { + lpanel = LLUICtrlFactory::create(lpparams); + } + else + { + lpanel = mUnusedItemPanels.back(); + mUnusedItemPanels.pop_back(); + } + LLRect rect = LLRect(left, top + mItemHeight, left + mItemWidth + mItemHorizontalGap, top); + lpanel->setRect(rect); + lpanel->reshape(mItemWidth + mItemHorizontalGap, mItemHeight); + lpanel->setVisible(true); + lpanel->setFollowsLeft(); + lpanel->setFollowsTop(); + return lpanel; +} + +LLPanel* LLOutfitGallery::buildRowPanel(int left, int bottom) +{ + LLPanel::Params sparams; + LLPanel* stack = NULL; + if(mUnusedRowPanels.empty()) + { + stack = LLUICtrlFactory::create(sparams); + } + else + { + stack = mUnusedRowPanels.back(); + mUnusedRowPanels.pop_back(); + } + moveRowPanel(stack, left, bottom); + return stack; +} + +void LLOutfitGallery::moveRowPanel(LLPanel* stack, int left, int bottom) +{ + LLRect rect = LLRect(left, bottom + mRowPanelHeight, left + mRowPanelWidth, bottom); + stack->setRect(rect); + stack->reshape(mRowPanelWidth, mRowPanelHeight); + stack->setVisible(true); + stack->setFollowsLeft(); + stack->setFollowsTop(); +} + +LLOutfitGallery::~LLOutfitGallery() +{ + delete mOutfitGalleryMenu; + + if (gInventory.containsObserver(mOutfitsObserver)) + { + gInventory.removeObserver(mOutfitsObserver); + } + delete mOutfitsObserver; + + while (!mUnusedRowPanels.empty()) + { + LLPanel* panelp = mUnusedRowPanels.back(); + mUnusedRowPanels.pop_back(); + panelp->die(); + } + while (!mUnusedItemPanels.empty()) + { + LLPanel* panelp = mUnusedItemPanels.back(); + mUnusedItemPanels.pop_back(); + panelp->die(); + } +} + +// virtual +void LLOutfitGallery::onFilterSubStringChanged(const std::string& new_string, const std::string& old_string) +{ + reArrangeRows(); +} + +void LLOutfitGallery::onHighlightBaseOutfit(LLUUID base_id, LLUUID prev_id) +{ + if (mOutfitMap[base_id]) + { + mOutfitMap[base_id]->setOutfitWorn(true); + } + if (mOutfitMap[prev_id]) + { + mOutfitMap[prev_id]->setOutfitWorn(false); + } +} + +void LLOutfitGallery::onSetSelectedOutfitByUUID(const LLUUID& outfit_uuid) +{ +} + +void LLOutfitGallery::getCurrentCategories(uuid_vec_t& vcur) +{ + for (outfit_map_t::const_iterator iter = mOutfitMap.begin(); + iter != mOutfitMap.end(); + iter++) + { + if ((*iter).second != NULL) + { + vcur.push_back((*iter).first); + } + } +} + +void LLOutfitGallery::updateAddedCategory(LLUUID cat_id) +{ + LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id); + if (!cat) return; + + std::string name = cat->getName(); + LLOutfitGalleryItem* item = buildGalleryItem(name, cat_id); + mOutfitMap.insert(LLOutfitGallery::outfit_map_value_t(cat_id, item)); + item->setRightMouseDownCallback(boost::bind(&LLOutfitListBase::outfitRightClickCallBack, this, + _1, _2, _3, cat_id)); + LLWearableItemsList* list = NULL; + item->setFocusReceivedCallback(boost::bind(&LLOutfitListBase::ChangeOutfitSelection, this, list, cat_id)); + if (mGalleryCreated) + { + addToGallery(item); + } + + LLViewerInventoryCategory* outfit_category = gInventory.getCategory(cat_id); + if (!outfit_category) + return; + + if (mOutfitsObserver == NULL) + { + mOutfitsObserver = new LLInventoryCategoriesObserver(); + gInventory.addObserver(mOutfitsObserver); + } + + // Start observing changes in "My Outfits" category. + mOutfitsObserver->addCategory(cat_id, + boost::bind(&LLOutfitGallery::refreshOutfit, this, cat_id), true); + + outfit_category->fetch(); + refreshOutfit(cat_id); +} + +void LLOutfitGallery::updateRemovedCategory(LLUUID cat_id) +{ + outfit_map_t::iterator outfits_iter = mOutfitMap.find(cat_id); + if (outfits_iter != mOutfitMap.end()) + { + // 0. Remove category from observer. + mOutfitsObserver->removeCategory(cat_id); + + //const LLUUID& outfit_id = outfits_iter->first; + LLOutfitGalleryItem* item = outfits_iter->second; + + // An outfit is removed from the list. Do the following: + // 2. Remove the outfit from selection. + deselectOutfit(cat_id); + + // 3. Remove category UUID to accordion tab mapping. + mOutfitMap.erase(outfits_iter); + + // 4. Remove outfit from gallery. + removeFromGalleryMiddle(item); + + // kill removed item + if (item != NULL) + { + item->die(); + } + } + +} + +void LLOutfitGallery::updateChangedCategoryName(LLViewerInventoryCategory *cat, std::string name) +{ + outfit_map_t::iterator outfit_iter = mOutfitMap.find(cat->getUUID()); + if (outfit_iter != mOutfitMap.end()) + { + // Update name of outfit in gallery + LLOutfitGalleryItem* item = outfit_iter->second; + if (item) + { + item->setOutfitName(name); + } + } +} + +void LLOutfitGallery::onOutfitRightClick(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& cat_id) +{ + if (mOutfitMenu && cat_id.notNull()) + { + uuid_vec_t selected_uuids; + selected_uuids.push_back(cat_id); + mOutfitGalleryMenu->show(ctrl, selected_uuids, x, y); + } +} + +void LLOutfitGallery::onChangeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id) +{ + if (mSelectedOutfitUUID == category_id) + return; + if (mOutfitMap[mSelectedOutfitUUID]) + { + mOutfitMap[mSelectedOutfitUUID]->setSelected(false); + } + if (mOutfitMap[category_id]) + { + mOutfitMap[category_id]->setSelected(true); + } + // mSelectedOutfitUUID will be set in LLOutfitListBase::ChangeOutfitSelection +} + +void LLOutfitGallery::wearSelectedOutfit() +{ + LLAppearanceMgr::instance().replaceCurrentOutfit(getSelectedOutfitUUID()); +} + +bool LLOutfitGallery::hasItemSelected() +{ + return false; +} + +bool LLOutfitGallery::canWearSelected() +{ + return false; +} + +bool LLOutfitGallery::hasDefaultImage(const LLUUID& outfit_cat_id) +{ + if (mOutfitMap[outfit_cat_id]) + { + return mOutfitMap[outfit_cat_id]->isDefaultImage(); + } + return false; +} + +void LLOutfitGallery::updateMessageVisibility() +{ + if (mItems.empty()) + { + mMessageTextBox->setVisible(true); + mScrollPanel->setVisible(false); + std::string message = getString(getFilterSubString().empty() ? "no_outfits_msg" : "no_matched_outfits_msg"); + mMessageTextBox->setValue(message); + } + else + { + mScrollPanel->setVisible(true); + mMessageTextBox->setVisible(false); + } +} + +LLOutfitListGearMenuBase* LLOutfitGallery::createGearMenu() +{ + return new LLOutfitGalleryGearMenu(this); +} + +static LLDefaultChildRegistry::Register r("outfit_gallery_item"); + +LLOutfitGalleryItem::LLOutfitGalleryItem(const Params& p) + : LLPanel(p), + mGallery(nullptr), + mTexturep(nullptr), + mSelected(false), + mWorn(false), + mDefaultImage(true), + mOutfitName(""), + mUUID(LLUUID()) +{ + buildFromFile("panel_outfit_gallery_item.xml"); +} + +LLOutfitGalleryItem::~LLOutfitGalleryItem() +{ + +} + +bool LLOutfitGalleryItem::postBuild() +{ + setDefaultImage(); + + mOutfitNameText = getChild("outfit_name"); + mOutfitWornText = getChild("outfit_worn_text"); + mTextBgPanel = getChild("text_bg_panel"); + setOutfitWorn(false); + mHidden = false; + return true; +} + +void LLOutfitGalleryItem::draw() +{ + LLPanel::draw(); + + // Draw border + LLUIColor border_color = LLUIColorTable::instance().getColor(mSelected ? "OutfitGalleryItemSelected" : "OutfitGalleryItemUnselected", LLColor4::white); + LLRect border = getChildView("preview_outfit")->getRect(); + border.mRight = border.mRight + 1; + gl_rect_2d(border, border_color.get(), false); + + // If the floater is focused, don't apply its alpha to the texture (STORM-677). + const F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); + if (mTexturep) + { + if (mImageUpdatePending && mTexturep->getDiscardLevel() >= 0) + { + mImageUpdatePending = false; + if (mTexturep->getOriginalWidth() > MAX_OUTFIT_PHOTO_WIDTH || mTexturep->getOriginalHeight() > MAX_OUTFIT_PHOTO_HEIGHT) + { + setDefaultImage(); + } + } + else + { + LLRect interior = border; + interior.stretch(-1); + + gl_draw_scaled_image(interior.mLeft - 1, interior.mBottom, interior.getWidth(), interior.getHeight(), mTexturep, UI_VERTEX_COLOR % alpha); + + // Pump the priority + mTexturep->addTextureStats((F32)(interior.getWidth() * interior.getHeight())); + } + } + +} + +void LLOutfitGalleryItem::setOutfitName(std::string name) +{ + mOutfitNameText->setText(name); + mOutfitNameText->setToolTip(name); + mOutfitName = name; +} + +void LLOutfitGalleryItem::setOutfitWorn(bool value) +{ + mWorn = value; + LLStringUtil::format_map_t worn_string_args; + std::string worn_string = getString("worn_string", worn_string_args); + LLUIColor text_color = LLUIColorTable::instance().getColor("White", LLColor4::white); + mOutfitWornText->setReadOnlyColor(text_color.get()); + mOutfitNameText->setReadOnlyColor(text_color.get()); + mOutfitWornText->setFont(value ? LLFontGL::getFontSansSerifBold() : LLFontGL::getFontSansSerifSmall()); + mOutfitNameText->setFont(value ? LLFontGL::getFontSansSerifBold() : LLFontGL::getFontSansSerifSmall()); + mOutfitWornText->setValue(value ? worn_string : ""); + mOutfitNameText->setText(mOutfitName); // refresh LLTextViewModel to pick up font changes +} + +void LLOutfitGalleryItem::setSelected(bool value) +{ + mSelected = value; + mTextBgPanel->setBackgroundVisible(value); + setOutfitWorn(mWorn); +} + +bool LLOutfitGalleryItem::handleMouseDown(S32 x, S32 y, MASK mask) +{ + setFocus(true); + return LLUICtrl::handleMouseDown(x, y, mask); +} + +bool LLOutfitGalleryItem::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + setFocus(true); + return LLUICtrl::handleRightMouseDown(x, y, mask); +} + +bool LLOutfitGalleryItem::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + return openOutfitsContent() || LLPanel::handleDoubleClick(x, y, mask); +} + +bool LLOutfitGalleryItem::handleKeyHere(KEY key, MASK mask) +{ + if (!mGallery) + { + return false; + } + + bool handled = false; + switch (key) + { + case KEY_LEFT: + mGallery->moveLeft(); + handled = true; + break; + + case KEY_RIGHT: + mGallery->moveRight(); + handled = true; + break; + + case KEY_UP: + mGallery->moveUp(); + handled = true; + break; + + case KEY_DOWN: + mGallery->moveDown(); + handled = true; + break; + + default: + break; + } + return handled; +} + +void LLOutfitGalleryItem::onFocusLost() +{ + setSelected(false); + + LLPanel::onFocusLost(); +} + +void LLOutfitGalleryItem::onFocusReceived() +{ + setSelected(true); + + LLPanel::onFocusReceived(); +} + +bool LLOutfitGalleryItem::openOutfitsContent() +{ + LLTabContainer* appearence_tabs = LLPanelOutfitsInventory::findInstance()->getChild("appearance_tabs"); + if (appearence_tabs && mUUID.notNull()) + { + appearence_tabs->selectTabByName("outfitslist_tab"); + LLPanel* panel = appearence_tabs->getCurrentPanel(); + if (panel) + { + LLAccordionCtrl* accordion = panel->getChild("outfits_accordion"); + LLOutfitsList* outfit_list = dynamic_cast(panel); + if (accordion != NULL && outfit_list != NULL) + { + outfit_list->setSelectedOutfitByUUID(mUUID); + LLAccordionCtrlTab* tab = accordion->getSelectedTab(); + if (tab) + { + tab->showAndFocusHeader(); + return true; + } + } + } + } + return false; +} + +bool LLOutfitGalleryItem::setImageAssetId(LLUUID image_asset_id) +{ + LLPointer texture = LLViewerTextureManager::getFetchedTexture(image_asset_id, FTT_DEFAULT, MIPMAP_YES, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + if (texture && texture->getOriginalWidth() <= MAX_OUTFIT_PHOTO_WIDTH && texture->getOriginalHeight() <= MAX_OUTFIT_PHOTO_HEIGHT) + { + mImageAssetId = image_asset_id; + mTexturep = texture; + getChildView("preview_outfit")->setVisible(false); + mDefaultImage = false; + mImageUpdatePending = (texture->getDiscardLevel() == -1); + return true; + } + return false; +} + +LLUUID LLOutfitGalleryItem::getImageAssetId() +{ + return mImageAssetId; +} + +void LLOutfitGalleryItem::setDefaultImage() +{ + mTexturep = NULL; + mImageAssetId.setNull(); + getChildView("preview_outfit")->setVisible(true); + mDefaultImage = true; + mImageUpdatePending = false; +} + +LLContextMenu* LLOutfitGalleryContextMenu::createMenu() +{ + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + LLUUID selected_id = mUUIDs.front(); + + registrar.add("Outfit.WearReplace", + boost::bind(&LLAppearanceMgr::replaceCurrentOutfit, &LLAppearanceMgr::instance(), selected_id)); + registrar.add("Outfit.WearAdd", + boost::bind(&LLAppearanceMgr::addCategoryToCurrentOutfit, &LLAppearanceMgr::instance(), selected_id)); + registrar.add("Outfit.TakeOff", + boost::bind(&LLAppearanceMgr::takeOffOutfit, &LLAppearanceMgr::instance(), selected_id)); + registrar.add("Outfit.Edit", boost::bind(editOutfit)); + registrar.add("Outfit.Rename", boost::bind(renameOutfit, selected_id)); + registrar.add("Outfit.Delete", boost::bind(LLOutfitGallery::onRemoveOutfit, selected_id)); + registrar.add("Outfit.Create", boost::bind(&LLOutfitGalleryContextMenu::onCreate, this, _2)); + registrar.add("Outfit.Thumbnail", boost::bind(&LLOutfitGalleryContextMenu::onThumbnail, this, selected_id)); + registrar.add("Outfit.Save", boost::bind(&LLOutfitGalleryContextMenu::onSave, this, selected_id)); + enable_registrar.add("Outfit.OnEnable", boost::bind(&LLOutfitGalleryContextMenu::onEnable, this, _2)); + enable_registrar.add("Outfit.OnVisible", boost::bind(&LLOutfitGalleryContextMenu::onVisible, this, _2)); + + return createFromFile("menu_gallery_outfit_tab.xml"); +} + +void LLOutfitGalleryContextMenu::onCreate(const LLSD& data) +{ + LLWearableType::EType type = LLWearableType::getInstance()->typeNameToType(data.asString()); + if (type == LLWearableType::WT_NONE) + { + LL_WARNS() << "Invalid wearable type" << LL_ENDL; + return; + } + + LLAgentWearables::createWearable(type, true); +} + +bool LLOutfitGalleryContextMenu::onEnable(LLSD::String param) +{ + return LLOutfitContextMenu::onEnable(param); +} + +bool LLOutfitGalleryContextMenu::onVisible(LLSD::String param) +{ + return LLOutfitContextMenu::onVisible(param); +} + +LLOutfitGalleryGearMenu::LLOutfitGalleryGearMenu(LLOutfitListBase* olist) + : LLOutfitListGearMenuBase(olist) +{ +} + +void LLOutfitGalleryGearMenu::onUpdateItemsVisibility() +{ + if (!mMenu) return; + bool have_selection = getSelectedOutfitID().notNull(); + mMenu->setItemVisible("expand", false); + mMenu->setItemVisible("collapse", false); + mMenu->setItemVisible("thumbnail", have_selection); + mMenu->setItemVisible("sepatator3", true); + mMenu->setItemVisible("sort_folders_by_name", true); + LLOutfitListGearMenuBase::onUpdateItemsVisibility(); +} + +void LLOutfitGalleryGearMenu::onChangeSortOrder() +{ + bool sort_by_name = !gSavedSettings.getBOOL("OutfitGallerySortByName"); + gSavedSettings.setBOOL("OutfitGallerySortByName", sort_by_name); + LLOutfitGallery* gallery = dynamic_cast(mOutfitList); + if (gallery) + { + gallery->reArrangeRows(); + } +} + +bool LLOutfitGalleryGearMenu::hasDefaultImage() +{ + LLOutfitGallery* gallery = dynamic_cast(mOutfitList); + LLUUID selected_outfit_id = getSelectedOutfitID(); + if (gallery && selected_outfit_id.notNull()) + { + return gallery->hasDefaultImage(selected_outfit_id); + } + return true; +} + +void LLOutfitGallery::onTextureSelectionChanged(LLInventoryItem* itemp) +{ +} + +void LLOutfitGallery::refreshOutfit(const LLUUID& category_id) +{ + LLViewerInventoryCategory* category = gInventory.getCategory(category_id); + if (category) + { + bool photo_loaded = false; + LLUUID asset_id = category->getThumbnailUUID(); + if (asset_id.isNull()) + { + LLInventoryModel::cat_array_t sub_cat_array; + LLInventoryModel::item_array_t outfit_item_array; + // Collect all sub-categories of a given category. + gInventory.collectDescendents( + category->getUUID(), + sub_cat_array, + outfit_item_array, + LLInventoryModel::EXCLUDE_TRASH); + for (LLViewerInventoryItem* outfit_item : outfit_item_array) + { + LLViewerInventoryItem* linked_item = outfit_item->getLinkedItem(); + LLUUID asset_id, inv_id; + std::string item_name; + if (linked_item != NULL) + { + if (linked_item->getActualType() == LLAssetType::AT_TEXTURE) + { + asset_id = linked_item->getAssetUUID(); + inv_id = linked_item->getUUID(); + item_name = linked_item->getName(); + } + } + else if (outfit_item->getActualType() == LLAssetType::AT_TEXTURE) + { + asset_id = outfit_item->getAssetUUID(); + inv_id = outfit_item->getUUID(); + item_name = outfit_item->getName(); + } + if (category->getThumbnailUUID().notNull()) + { + asset_id = category->getThumbnailUUID(); + } + if (asset_id.notNull()) + { + photo_loaded |= mOutfitMap[category_id]->setImageAssetId(asset_id); + // Rename links + if (!mOutfitRenamePending.isNull() && mOutfitRenamePending.asString() == item_name) + { + LLViewerInventoryCategory *outfit_cat = gInventory.getCategory(mOutfitRenamePending); + LLStringUtil::format_map_t photo_string_args; + photo_string_args["OUTFIT_NAME"] = outfit_cat->getName(); + std::string new_name = getString("outfit_photo_string", photo_string_args); + LLSD updates; + updates["name"] = new_name; + update_inventory_item(inv_id, updates, NULL); + mOutfitRenamePending.setNull(); + LLFloater* appearance_floater = LLFloaterReg::getInstance("appearance"); + if (appearance_floater) + { + appearance_floater->setFocus(true); + } + } + if (item_name == LLAppearanceMgr::sExpectedTextureName) + { + // Images with "appropriate" name take priority + break; + } + } + if (!photo_loaded) + { + mOutfitMap[category_id]->setDefaultImage(); + } + } + } + else + { + mOutfitMap[category_id]->setImageAssetId(asset_id); + } + } + + if (mGalleryCreated && !LLApp::isExiting()) + { + reArrangeRows(); + } +} + +LLUUID LLOutfitGallery::getPhotoAssetId(const LLUUID& outfit_id) +{ + outfit_map_t::iterator outfit_it = mOutfitMap.find(outfit_id); + if (outfit_it != mOutfitMap.end()) + { + return outfit_it->second->getImageAssetId(); + } + return LLUUID(); +} + +LLUUID LLOutfitGallery::getDefaultPhoto() +{ + return LLUUID(); +} + diff --git a/indra/newview/lloutfitgallery.h b/indra/newview/lloutfitgallery.h index 85bd0b6ccb..d921a7fe72 100644 --- a/indra/newview/lloutfitgallery.h +++ b/indra/newview/lloutfitgallery.h @@ -1,272 +1,272 @@ -/** - * @file lloutfitgallery.h - * @author Pavlo Kryvych - * @brief Visual gallery of agent's outfits for My Appearance side panel - * - * $LicenseInfo:firstyear=2015&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2015, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLOUTFITGALLERYCTRL_H -#define LL_LLOUTFITGALLERYCTRL_H - -#include "llextendedstatus.h" -#include "lliconctrl.h" -#include "lllayoutstack.h" -#include "lloutfitslist.h" -#include "llpanelappearancetab.h" -#include "llviewertexture.h" - -#include - -class LLOutfitGallery; -class LLOutfitGalleryItem; -class LLOutfitListGearMenuBase; -class LLOutfitGalleryGearMenu; -class LLOutfitGalleryContextMenu; - -class LLOutfitGallery : public LLOutfitListBase -{ -public: - friend class LLOutfitGalleryGearMenu; - friend class LLOutfitGalleryContextMenu; - friend class LLUpdateGalleryOnPhotoLinked; - - struct Params - : public LLInitParam::Block - { - Optional row_panel_height; - Optional row_panel_width_factor; - Optional gallery_width_factor; - Optional vertical_gap; - Optional horizontal_gap; - Optional item_width; - Optional item_height; - Optional item_horizontal_gap; - Optional items_in_row; - - Params(); - }; - - static const LLOutfitGallery::Params& getDefaultParams(); - - LLOutfitGallery(const LLOutfitGallery::Params& params = getDefaultParams()); - virtual ~LLOutfitGallery(); - - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& info); - /*virtual*/ void draw(); - /*virtual*/ bool handleKeyHere(KEY key, MASK mask); - void moveUp(); - void moveDown(); - void moveLeft(); - void moveRight(); - - /*virtual*/ void onFocusLost(); - /*virtual*/ void onFocusReceived(); - - static void onRemoveOutfit(const LLUUID& outfit_cat_id); - static void onOutfitsRemovalConfirmation(const LLSD& notification, const LLSD& response, const LLUUID& outfit_cat_id); - void scrollToShowItem(const LLUUID& item_id); - - void wearSelectedOutfit(); - - - /*virtual*/ void onFilterSubStringChanged(const std::string& new_string, const std::string& old_string); - - /*virtual*/ void getCurrentCategories(uuid_vec_t& vcur); - /*virtual*/ void updateAddedCategory(LLUUID cat_id); - /*virtual*/ void updateRemovedCategory(LLUUID cat_id); - /*virtual*/ void updateChangedCategoryName(LLViewerInventoryCategory *cat, std::string name); - - /*virtual*/ bool hasItemSelected(); - /*virtual*/ bool canWearSelected(); - - /*virtual*/ bool getHasExpandableFolders() { return false; } - - void updateMessageVisibility(); - bool hasDefaultImage(const LLUUID& outfit_cat_id); - - void refreshOutfit(const LLUUID& category_id); - -protected: - /*virtual*/ void onHighlightBaseOutfit(LLUUID base_id, LLUUID prev_id); - /*virtual*/ void onSetSelectedOutfitByUUID(const LLUUID& outfit_uuid); - /*virtual*/ void onOutfitRightClick(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& cat_id); - /*virtual*/ void onChangeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id); - - /*virtual*/ void onCollapseAllFolders() {} - /*virtual*/ void onExpandAllFolders() {} - /*virtual*/ LLOutfitListGearMenuBase* createGearMenu(); - -private: - LLUUID getPhotoAssetId(const LLUUID& outfit_id); - LLUUID getDefaultPhoto(); - void addToGallery(LLOutfitGalleryItem* item); - void removeFromGalleryLast(LLOutfitGalleryItem* item); - void removeFromGalleryMiddle(LLOutfitGalleryItem* item); - LLPanel* addLastRow(); - void removeLastRow(); - void moveRowUp(int row); - void moveRowDown(int row); - void moveRow(int row, int pos); - LLPanel* addToRow(LLPanel* row_stack, LLOutfitGalleryItem* item, int pos, int hgap); - void removeFromLastRow(LLOutfitGalleryItem* item); - void reArrangeRows(S32 row_diff = 0); - void updateRowsIfNeeded(); - void updateGalleryWidth(); - - LLOutfitGalleryItem* buildGalleryItem(std::string name, LLUUID outfit_id); - LLOutfitGalleryItem* getSelectedItem(); - - void onTextureSelectionChanged(LLInventoryItem* itemp); - - void buildGalleryPanel(int row_count); - void reshapeGalleryPanel(int row_count); - LLPanel* buildItemPanel(int left); - LLPanel* buildRowPanel(int left, int bottom); - void moveRowPanel(LLPanel* stack, int left, int bottom); - std::vector mRowPanels; - std::vector mItemPanels; - std::vector mUnusedRowPanels; - std::vector mUnusedItemPanels; - std::vector mItems; - std::vector mHiddenItems; - LLScrollContainer* mScrollPanel; - LLPanel* mGalleryPanel; - LLPanel* mLastRowPanel; - LLUUID mOutfitLinkPending; - LLUUID mOutfitRenamePending; - LLUUID mSnapshotFolderID; - LLTextBox* mMessageTextBox; - bool mGalleryCreated; - int mRowCount; - int mItemsAddedCount; - LLPointer mTextureSelected; - /* Params */ - int mRowPanelHeight; - int mVerticalGap; - int mHorizontalGap; - int mItemWidth; - int mItemHeight; - int mItemHorizontalGap; - int mItemsInRow; - int mRowPanelWidth; - int mGalleryWidth; - int mRowPanWidthFactor; - int mGalleryWidthFactor; - - LLListContextMenu* mOutfitGalleryMenu; - - typedef std::map outfit_map_t; - typedef outfit_map_t::value_type outfit_map_value_t; - outfit_map_t mOutfitMap; - typedef std::map item_num_map_t; - typedef item_num_map_t::value_type item_numb_map_value_t; - item_num_map_t mItemIndexMap; - std::map mIndexToItemMap; - - - LLInventoryCategoriesObserver* mOutfitsObserver; -}; -class LLOutfitGalleryContextMenu : public LLOutfitContextMenu -{ -public: - - friend class LLOutfitGallery; - LLOutfitGalleryContextMenu(LLOutfitListBase* outfit_list) - : LLOutfitContextMenu(outfit_list){} - -protected: - /* virtual */ LLContextMenu* createMenu(); - bool onEnable(LLSD::String param); - bool onVisible(LLSD::String param); - void onCreate(const LLSD& data); -}; - - -class LLOutfitGalleryGearMenu : public LLOutfitListGearMenuBase -{ -public: - friend class LLOutfitGallery; - LLOutfitGalleryGearMenu(LLOutfitListBase* olist); - -protected: - /*virtual*/ void onUpdateItemsVisibility(); -private: - /*virtual*/ void onChangeSortOrder(); - - bool hasDefaultImage(); -}; - -class LLOutfitGalleryItem : public LLPanel -{ -public: - struct Params : public LLInitParam::Block - {}; - - LLOutfitGalleryItem(const Params& p); - virtual ~LLOutfitGalleryItem(); - - /*virtual*/ bool postBuild(); - /*virtual*/ void draw(); - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleKeyHere(KEY key, MASK mask); - /*virtual*/ void onFocusLost(); - /*virtual*/ void onFocusReceived(); - - bool openOutfitsContent(); - - void setGallery(LLOutfitGallery* gallery) { mGallery = gallery; } - void setDefaultImage(); - bool setImageAssetId(LLUUID asset_id); - LLUUID getImageAssetId(); - void setOutfitName(std::string name); - void setOutfitWorn(bool value); - void setSelected(bool value); - void setUUID(const LLUUID &outfit_id) {mUUID = outfit_id;} - LLUUID getUUID() const { return mUUID; } - - std::string getItemName() {return mOutfitName;} - bool isDefaultImage() {return mDefaultImage;} - - bool isHidden() {return mHidden;} - void setHidden(bool hidden) {mHidden = hidden;} - -private: - LLOutfitGallery* mGallery; - LLPointer mTexturep; - LLUUID mUUID; - LLUUID mImageAssetId; - LLTextBox* mOutfitNameText; - LLTextBox* mOutfitWornText; - LLPanel* mTextBgPanel; - bool mSelected; - bool mWorn; - bool mDefaultImage; - bool mImageUpdatePending; - bool mHidden; - std::string mOutfitName; -}; - -#endif // LL_LLOUTFITGALLERYCTRL_H +/** + * @file lloutfitgallery.h + * @author Pavlo Kryvych + * @brief Visual gallery of agent's outfits for My Appearance side panel + * + * $LicenseInfo:firstyear=2015&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2015, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLOUTFITGALLERYCTRL_H +#define LL_LLOUTFITGALLERYCTRL_H + +#include "llextendedstatus.h" +#include "lliconctrl.h" +#include "lllayoutstack.h" +#include "lloutfitslist.h" +#include "llpanelappearancetab.h" +#include "llviewertexture.h" + +#include + +class LLOutfitGallery; +class LLOutfitGalleryItem; +class LLOutfitListGearMenuBase; +class LLOutfitGalleryGearMenu; +class LLOutfitGalleryContextMenu; + +class LLOutfitGallery : public LLOutfitListBase +{ +public: + friend class LLOutfitGalleryGearMenu; + friend class LLOutfitGalleryContextMenu; + friend class LLUpdateGalleryOnPhotoLinked; + + struct Params + : public LLInitParam::Block + { + Optional row_panel_height; + Optional row_panel_width_factor; + Optional gallery_width_factor; + Optional vertical_gap; + Optional horizontal_gap; + Optional item_width; + Optional item_height; + Optional item_horizontal_gap; + Optional items_in_row; + + Params(); + }; + + static const LLOutfitGallery::Params& getDefaultParams(); + + LLOutfitGallery(const LLOutfitGallery::Params& params = getDefaultParams()); + virtual ~LLOutfitGallery(); + + /*virtual*/ bool postBuild(); + /*virtual*/ void onOpen(const LLSD& info); + /*virtual*/ void draw(); + /*virtual*/ bool handleKeyHere(KEY key, MASK mask); + void moveUp(); + void moveDown(); + void moveLeft(); + void moveRight(); + + /*virtual*/ void onFocusLost(); + /*virtual*/ void onFocusReceived(); + + static void onRemoveOutfit(const LLUUID& outfit_cat_id); + static void onOutfitsRemovalConfirmation(const LLSD& notification, const LLSD& response, const LLUUID& outfit_cat_id); + void scrollToShowItem(const LLUUID& item_id); + + void wearSelectedOutfit(); + + + /*virtual*/ void onFilterSubStringChanged(const std::string& new_string, const std::string& old_string); + + /*virtual*/ void getCurrentCategories(uuid_vec_t& vcur); + /*virtual*/ void updateAddedCategory(LLUUID cat_id); + /*virtual*/ void updateRemovedCategory(LLUUID cat_id); + /*virtual*/ void updateChangedCategoryName(LLViewerInventoryCategory *cat, std::string name); + + /*virtual*/ bool hasItemSelected(); + /*virtual*/ bool canWearSelected(); + + /*virtual*/ bool getHasExpandableFolders() { return false; } + + void updateMessageVisibility(); + bool hasDefaultImage(const LLUUID& outfit_cat_id); + + void refreshOutfit(const LLUUID& category_id); + +protected: + /*virtual*/ void onHighlightBaseOutfit(LLUUID base_id, LLUUID prev_id); + /*virtual*/ void onSetSelectedOutfitByUUID(const LLUUID& outfit_uuid); + /*virtual*/ void onOutfitRightClick(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& cat_id); + /*virtual*/ void onChangeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id); + + /*virtual*/ void onCollapseAllFolders() {} + /*virtual*/ void onExpandAllFolders() {} + /*virtual*/ LLOutfitListGearMenuBase* createGearMenu(); + +private: + LLUUID getPhotoAssetId(const LLUUID& outfit_id); + LLUUID getDefaultPhoto(); + void addToGallery(LLOutfitGalleryItem* item); + void removeFromGalleryLast(LLOutfitGalleryItem* item); + void removeFromGalleryMiddle(LLOutfitGalleryItem* item); + LLPanel* addLastRow(); + void removeLastRow(); + void moveRowUp(int row); + void moveRowDown(int row); + void moveRow(int row, int pos); + LLPanel* addToRow(LLPanel* row_stack, LLOutfitGalleryItem* item, int pos, int hgap); + void removeFromLastRow(LLOutfitGalleryItem* item); + void reArrangeRows(S32 row_diff = 0); + void updateRowsIfNeeded(); + void updateGalleryWidth(); + + LLOutfitGalleryItem* buildGalleryItem(std::string name, LLUUID outfit_id); + LLOutfitGalleryItem* getSelectedItem(); + + void onTextureSelectionChanged(LLInventoryItem* itemp); + + void buildGalleryPanel(int row_count); + void reshapeGalleryPanel(int row_count); + LLPanel* buildItemPanel(int left); + LLPanel* buildRowPanel(int left, int bottom); + void moveRowPanel(LLPanel* stack, int left, int bottom); + std::vector mRowPanels; + std::vector mItemPanels; + std::vector mUnusedRowPanels; + std::vector mUnusedItemPanels; + std::vector mItems; + std::vector mHiddenItems; + LLScrollContainer* mScrollPanel; + LLPanel* mGalleryPanel; + LLPanel* mLastRowPanel; + LLUUID mOutfitLinkPending; + LLUUID mOutfitRenamePending; + LLUUID mSnapshotFolderID; + LLTextBox* mMessageTextBox; + bool mGalleryCreated; + int mRowCount; + int mItemsAddedCount; + LLPointer mTextureSelected; + /* Params */ + int mRowPanelHeight; + int mVerticalGap; + int mHorizontalGap; + int mItemWidth; + int mItemHeight; + int mItemHorizontalGap; + int mItemsInRow; + int mRowPanelWidth; + int mGalleryWidth; + int mRowPanWidthFactor; + int mGalleryWidthFactor; + + LLListContextMenu* mOutfitGalleryMenu; + + typedef std::map outfit_map_t; + typedef outfit_map_t::value_type outfit_map_value_t; + outfit_map_t mOutfitMap; + typedef std::map item_num_map_t; + typedef item_num_map_t::value_type item_numb_map_value_t; + item_num_map_t mItemIndexMap; + std::map mIndexToItemMap; + + + LLInventoryCategoriesObserver* mOutfitsObserver; +}; +class LLOutfitGalleryContextMenu : public LLOutfitContextMenu +{ +public: + + friend class LLOutfitGallery; + LLOutfitGalleryContextMenu(LLOutfitListBase* outfit_list) + : LLOutfitContextMenu(outfit_list){} + +protected: + /* virtual */ LLContextMenu* createMenu(); + bool onEnable(LLSD::String param); + bool onVisible(LLSD::String param); + void onCreate(const LLSD& data); +}; + + +class LLOutfitGalleryGearMenu : public LLOutfitListGearMenuBase +{ +public: + friend class LLOutfitGallery; + LLOutfitGalleryGearMenu(LLOutfitListBase* olist); + +protected: + /*virtual*/ void onUpdateItemsVisibility(); +private: + /*virtual*/ void onChangeSortOrder(); + + bool hasDefaultImage(); +}; + +class LLOutfitGalleryItem : public LLPanel +{ +public: + struct Params : public LLInitParam::Block + {}; + + LLOutfitGalleryItem(const Params& p); + virtual ~LLOutfitGalleryItem(); + + /*virtual*/ bool postBuild(); + /*virtual*/ void draw(); + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleKeyHere(KEY key, MASK mask); + /*virtual*/ void onFocusLost(); + /*virtual*/ void onFocusReceived(); + + bool openOutfitsContent(); + + void setGallery(LLOutfitGallery* gallery) { mGallery = gallery; } + void setDefaultImage(); + bool setImageAssetId(LLUUID asset_id); + LLUUID getImageAssetId(); + void setOutfitName(std::string name); + void setOutfitWorn(bool value); + void setSelected(bool value); + void setUUID(const LLUUID &outfit_id) {mUUID = outfit_id;} + LLUUID getUUID() const { return mUUID; } + + std::string getItemName() {return mOutfitName;} + bool isDefaultImage() {return mDefaultImage;} + + bool isHidden() {return mHidden;} + void setHidden(bool hidden) {mHidden = hidden;} + +private: + LLOutfitGallery* mGallery; + LLPointer mTexturep; + LLUUID mUUID; + LLUUID mImageAssetId; + LLTextBox* mOutfitNameText; + LLTextBox* mOutfitWornText; + LLPanel* mTextBgPanel; + bool mSelected; + bool mWorn; + bool mDefaultImage; + bool mImageUpdatePending; + bool mHidden; + std::string mOutfitName; +}; + +#endif // LL_LLOUTFITGALLERYCTRL_H diff --git a/indra/newview/lloutfitslist.cpp b/indra/newview/lloutfitslist.cpp index 665cfba9e6..94b32ceea9 100644 --- a/indra/newview/lloutfitslist.cpp +++ b/indra/newview/lloutfitslist.cpp @@ -1,1369 +1,1369 @@ -/** - * @file lloutfitslist.cpp - * @brief List of agent's outfits for My Appearance side panel. - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lloutfitslist.h" - -// llcommon -#include "llcommonutils.h" - -#include "llaccordionctrl.h" -#include "llaccordionctrltab.h" -#include "llagentwearables.h" -#include "llappearancemgr.h" -#include "llfloaterreg.h" -#include "llfloatersidepanelcontainer.h" -#include "llinspecttexture.h" -#include "llinventoryfunctions.h" -#include "llinventorymodel.h" -#include "llmenubutton.h" -#include "llnotificationsutil.h" -#include "lloutfitobserver.h" -#include "lltoggleablemenu.h" -#include "lltransutil.h" -#include "llviewermenu.h" -#include "llvoavatar.h" -#include "llvoavatarself.h" -#include "llwearableitemslist.h" - -static bool is_tab_header_clicked(LLAccordionCtrlTab* tab, S32 y); - -static const LLOutfitTabNameComparator OUTFIT_TAB_NAME_COMPARATOR; - -/*virtual*/ -bool LLOutfitTabNameComparator::compare(const LLAccordionCtrlTab* tab1, const LLAccordionCtrlTab* tab2) const -{ - std::string name1 = tab1->getTitle(); - std::string name2 = tab2->getTitle(); - - return (LLStringUtil::compareDict(name1, name2) < 0); -} - -struct outfit_accordion_tab_params : public LLInitParam::Block -{ - Mandatory wearable_list; - - outfit_accordion_tab_params() - : wearable_list("wearable_items_list") - {} -}; - -const outfit_accordion_tab_params& get_accordion_tab_params() -{ - static outfit_accordion_tab_params tab_params; - static bool initialized = false; - if (!initialized) - { - initialized = true; - - LLXMLNodePtr xmlNode; - if (LLUICtrlFactory::getLayeredXMLNode("outfit_accordion_tab.xml", xmlNode)) - { - LLXUIParser parser; - parser.readXUI(xmlNode, tab_params, "outfit_accordion_tab.xml"); - } - else - { - LL_WARNS() << "Failed to read xml of Outfit's Accordion Tab from outfit_accordion_tab.xml" << LL_ENDL; - } - } - - return tab_params; -} - - -static LLPanelInjector t_outfits_list("outfits_list"); - -LLOutfitsList::LLOutfitsList() - : LLOutfitListBase() - , mAccordion(NULL) - , mListCommands(NULL) - , mItemSelected(false) -{ -} - -LLOutfitsList::~LLOutfitsList() -{ -} - -bool LLOutfitsList::postBuild() -{ - mAccordion = getChild("outfits_accordion"); - mAccordion->setComparator(&OUTFIT_TAB_NAME_COMPARATOR); - - return LLOutfitListBase::postBuild(); -} - -//virtual -void LLOutfitsList::onOpen(const LLSD& info) -{ - if (!mIsInitialized) - { - // Start observing changes in Current Outfit category. - LLOutfitObserver::instance().addCOFChangedCallback(boost::bind(&LLOutfitsList::onCOFChanged, this)); - } - - LLOutfitListBase::onOpen(info); - - LLAccordionCtrlTab* selected_tab = mAccordion->getSelectedTab(); - if (!selected_tab) return; - - // Pass focus to the selected outfit tab. - selected_tab->showAndFocusHeader(); -} - - -void LLOutfitsList::updateAddedCategory(LLUUID cat_id) -{ - LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id); - if (!cat) return; - - std::string name = cat->getName(); - - outfit_accordion_tab_params tab_params(get_accordion_tab_params()); - tab_params.cat_id = cat_id; - LLOutfitAccordionCtrlTab *tab = LLUICtrlFactory::create(tab_params); - if (!tab) return; - LLWearableItemsList* wearable_list = LLUICtrlFactory::create(tab_params.wearable_list); - wearable_list->setShape(tab->getLocalRect()); - tab->addChild(wearable_list); - - tab->setName(name); - tab->setTitle(name); - - // *TODO: LLUICtrlFactory::defaultBuilder does not use "display_children" from xml. Should be investigated. - tab->setDisplayChildren(false); - mAccordion->addCollapsibleCtrl(tab); - - // Start observing the new outfit category. - LLWearableItemsList* list = tab->getChild("wearable_items_list"); - if (!mCategoriesObserver->addCategory(cat_id, boost::bind(&LLWearableItemsList::updateList, list, cat_id))) - { - // Remove accordion tab if category could not be added to observer. - mAccordion->removeCollapsibleCtrl(tab); - - // kill removed tab - tab->die(); - return; - } - - // Map the new tab with outfit category UUID. - mOutfitsMap.insert(LLOutfitsList::outfits_map_value_t(cat_id, tab)); - - tab->setRightMouseDownCallback(boost::bind(&LLOutfitListBase::outfitRightClickCallBack, this, - _1, _2, _3, cat_id)); - - // Setting tab focus callback to monitor currently selected outfit. - tab->setFocusReceivedCallback(boost::bind(&LLOutfitListBase::ChangeOutfitSelection, this, list, cat_id)); - - // Setting callback to reset items selection inside outfit on accordion collapsing and expanding (EXT-7875) - tab->setDropDownStateChangedCallback(boost::bind(&LLOutfitsList::resetItemSelection, this, list, cat_id)); - - // force showing list items that don't match current filter(EXT-7158) - list->setForceShowingUnmatchedItems(true); - - // Setting list commit callback to monitor currently selected wearable item. - list->setCommitCallback(boost::bind(&LLOutfitsList::onListSelectionChange, this, _1)); - - // Setting list refresh callback to apply filter on list change. - list->setRefreshCompleteCallback(boost::bind(&LLOutfitsList::onRefreshComplete, this, _1)); - - list->setRightMouseDownCallback(boost::bind(&LLOutfitsList::onWearableItemsListRightClick, this, _1, _2, _3)); - - // Fetch the new outfit contents. - cat->fetch(); - - // Refresh the list of outfit items after fetch(). - // Further list updates will be triggered by the category observer. - list->updateList(cat_id); - - // If filter is currently applied we store the initial tab state. - if (!getFilterSubString().empty()) - { - tab->notifyChildren(LLSD().with("action", "store_state")); - - // Setting mForceRefresh flag will make the list refresh its contents - // even if it is not currently visible. This is required to apply the - // filter to the newly added list. - list->setForceRefresh(true); - - list->setFilterSubString(getFilterSubString(), false); - } -} - -void LLOutfitsList::updateRemovedCategory(LLUUID cat_id) -{ - outfits_map_t::iterator outfits_iter = mOutfitsMap.find(cat_id); - if (outfits_iter != mOutfitsMap.end()) - { - const LLUUID& outfit_id = outfits_iter->first; - LLAccordionCtrlTab* tab = outfits_iter->second; - - // An outfit is removed from the list. Do the following: - // 1. Remove outfit category from observer to stop monitoring its changes. - mCategoriesObserver->removeCategory(outfit_id); - - // 2. Remove the outfit from selection. - deselectOutfit(outfit_id); - - // 3. Remove category UUID to accordion tab mapping. - mOutfitsMap.erase(outfits_iter); - - // 4. Remove outfit tab from accordion. - mAccordion->removeCollapsibleCtrl(tab); - - // kill removed tab - if (tab != NULL) - { - tab->die(); - } - } -} - -//virtual -void LLOutfitsList::onHighlightBaseOutfit(LLUUID base_id, LLUUID prev_id) -{ - if (mOutfitsMap[prev_id]) - { - mOutfitsMap[prev_id]->setTitleFontStyle("NORMAL"); - mOutfitsMap[prev_id]->setTitleColor(LLUIColorTable::instance().getColor("AccordionHeaderTextColor")); - } - if (mOutfitsMap[base_id]) - { - mOutfitsMap[base_id]->setTitleFontStyle("BOLD"); - mOutfitsMap[base_id]->setTitleColor(LLUIColorTable::instance().getColor("SelectedOutfitTextColor")); - } -} - -void LLOutfitsList::onListSelectionChange(LLUICtrl* ctrl) -{ - LLWearableItemsList* list = dynamic_cast(ctrl); - if (!list) return; - - LLViewerInventoryItem *item = gInventory.getItem(list->getSelectedUUID()); - if (!item) return; - - ChangeOutfitSelection(list, item->getParentUUID()); -} - -void LLOutfitListBase::performAction(std::string action) -{ - if (mSelectedOutfitUUID.isNull()) return; - - LLViewerInventoryCategory* cat = gInventory.getCategory(mSelectedOutfitUUID); - if (!cat) return; - - if ("replaceoutfit" == action) - { - LLAppearanceMgr::instance().wearInventoryCategory( cat, false, false ); - } - else if ("addtooutfit" == action) - { - LLAppearanceMgr::instance().wearInventoryCategory( cat, false, true ); - } - else if ("rename_outfit" == action) - { - LLAppearanceMgr::instance().renameOutfit(mSelectedOutfitUUID); - } -} - -void LLOutfitsList::onSetSelectedOutfitByUUID(const LLUUID& outfit_uuid) -{ - for (outfits_map_t::iterator iter = mOutfitsMap.begin(); - iter != mOutfitsMap.end(); - ++iter) - { - if (outfit_uuid == iter->first) - { - LLAccordionCtrlTab* tab = iter->second; - if (!tab) continue; - - LLWearableItemsList* list = dynamic_cast(tab->getAccordionView()); - if (!list) continue; - - tab->setFocus(true); - ChangeOutfitSelection(list, outfit_uuid); - - tab->changeOpenClose(false); - } - } -} - -// virtual -bool LLOutfitListBase::isActionEnabled(const LLSD& userdata) -{ - if (mSelectedOutfitUUID.isNull()) return false; - - const std::string command_name = userdata.asString(); - if (command_name == "delete") - { - return !hasItemSelected() && LLAppearanceMgr::instance().getCanRemoveOutfit(mSelectedOutfitUUID); - } - if (command_name == "rename") - { - return get_is_category_renameable(&gInventory, mSelectedOutfitUUID); - } - if (command_name == "save_outfit") - { - bool outfit_locked = LLAppearanceMgr::getInstance()->isOutfitLocked(); - bool outfit_dirty = LLAppearanceMgr::getInstance()->isOutfitDirty(); - // allow save only if outfit isn't locked and is dirty - return !outfit_locked && outfit_dirty; - } - if (command_name == "wear") - { - if (gAgentWearables.isCOFChangeInProgress()) - { - return false; - } - - if (hasItemSelected()) - { - return canWearSelected(); - } - - // outfit selected - return LLAppearanceMgr::instance().getCanReplaceCOF(mSelectedOutfitUUID); - } - if (command_name == "take_off") - { - // Enable "Take Off" if any of selected items can be taken off - // or the selected outfit contains items that can be taken off. - return ( hasItemSelected() && canTakeOffSelected() ) - || ( !hasItemSelected() && LLAppearanceMgr::getCanRemoveFromCOF(mSelectedOutfitUUID) ); - } - - if (command_name == "wear_add") - { - // *TODO: do we ever get here? - return LLAppearanceMgr::getCanAddToCOF(mSelectedOutfitUUID); - } - - return false; -} - -void LLOutfitsList::getSelectedItemsUUIDs(uuid_vec_t& selected_uuids) const -{ - // Collect selected items from all selected lists. - for (wearables_lists_map_t::const_iterator iter = mSelectedListsMap.begin(); - iter != mSelectedListsMap.end(); - ++iter) - { - uuid_vec_t uuids; - (*iter).second->getSelectedUUIDs(uuids); - - S32 prev_size = selected_uuids.size(); - selected_uuids.resize(prev_size + uuids.size()); - std::copy(uuids.begin(), uuids.end(), selected_uuids.begin() + prev_size); - } -} - -void LLOutfitsList::onCollapseAllFolders() -{ - for (outfits_map_t::iterator iter = mOutfitsMap.begin(); - iter != mOutfitsMap.end(); - ++iter) - { - LLAccordionCtrlTab* tab = iter->second; - if(tab && tab->isExpanded()) - { - tab->changeOpenClose(true); - } - } -} - -void LLOutfitsList::onExpandAllFolders() -{ - for (outfits_map_t::iterator iter = mOutfitsMap.begin(); - iter != mOutfitsMap.end(); - ++iter) - { - LLAccordionCtrlTab* tab = iter->second; - if(tab && !tab->isExpanded()) - { - tab->changeOpenClose(false); - } - } -} - -bool LLOutfitsList::hasItemSelected() -{ - return mItemSelected; -} - -////////////////////////////////////////////////////////////////////////// -// Private methods -////////////////////////////////////////////////////////////////////////// - -void LLOutfitsList::updateChangedCategoryName(LLViewerInventoryCategory *cat, std::string name) -{ - outfits_map_t::iterator outfits_iter = mOutfitsMap.find(cat->getUUID()); - if (outfits_iter != mOutfitsMap.end()) - { - // Update tab name with the new category name. - LLAccordionCtrlTab* tab = outfits_iter->second; - if (tab) - { - tab->setName(name); - tab->setTitle(name); - } - } -} - -void LLOutfitsList::resetItemSelection(LLWearableItemsList* list, const LLUUID& category_id) -{ - list->resetSelection(); - mItemSelected = false; - signalSelectionOutfitUUID(category_id); -} - -void LLOutfitsList::onChangeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id) -{ - MASK mask = gKeyboard->currentMask(true); - - // Reset selection in all previously selected tabs except for the current - // if new selection is started. - if (list && !(mask & MASK_CONTROL)) - { - for (wearables_lists_map_t::iterator iter = mSelectedListsMap.begin(); - iter != mSelectedListsMap.end(); - ++iter) - { - LLWearableItemsList* selected_list = (*iter).second; - if (selected_list != list) - { - selected_list->resetSelection(); - } - } - - // Clear current selection. - mSelectedListsMap.clear(); - } - - mItemSelected = list && (list->getSelectedItem() != NULL); - - mSelectedListsMap.insert(wearables_lists_map_value_t(category_id, list)); -} - -void LLOutfitsList::deselectOutfit(const LLUUID& category_id) -{ - // Remove selected lists map entry. - mSelectedListsMap.erase(category_id); - - LLOutfitListBase::deselectOutfit(category_id); -} - -void LLOutfitsList::restoreOutfitSelection(LLAccordionCtrlTab* tab, const LLUUID& category_id) -{ - // Try restoring outfit selection after filtering. - if (mAccordion->getSelectedTab() == tab) - { - signalSelectionOutfitUUID(category_id); - } -} - -void LLOutfitsList::onRefreshComplete(LLUICtrl* ctrl) -{ - if (!ctrl || getFilterSubString().empty()) - return; - - for (outfits_map_t::iterator - iter = mOutfitsMap.begin(), - iter_end = mOutfitsMap.end(); - iter != iter_end; ++iter) - { - LLAccordionCtrlTab* tab = iter->second; - if (!tab) continue; - - LLWearableItemsList* list = dynamic_cast(tab->getAccordionView()); - if (list != ctrl) continue; - - applyFilterToTab(iter->first, tab, getFilterSubString()); - } -} - -// virtual -void LLOutfitsList::onFilterSubStringChanged(const std::string& new_string, const std::string& old_string) -{ - mAccordion->setFilterSubString(new_string); - - outfits_map_t::iterator iter = mOutfitsMap.begin(), iter_end = mOutfitsMap.end(); - while (iter != iter_end) - { - const LLUUID& category_id = iter->first; - LLAccordionCtrlTab* tab = iter++->second; - if (!tab) continue; - - LLWearableItemsList* list = dynamic_cast(tab->getAccordionView()); - if (list) - { - list->setFilterSubString(new_string, tab->getDisplayChildren()); - } - - if (old_string.empty()) - { - // Store accordion tab state when filter is not empty - tab->notifyChildren(LLSD().with("action", "store_state")); - } - - if (!new_string.empty()) - { - applyFilterToTab(category_id, tab, new_string); - } - else - { - tab->setVisible(true); - - // Restore tab title when filter is empty - tab->setTitle(tab->getTitle()); - - // Restore accordion state after all those accodrion tab manipulations - tab->notifyChildren(LLSD().with("action", "restore_state")); - - // Try restoring the tab selection. - restoreOutfitSelection(tab, category_id); - } - } - - mAccordion->arrange(); -} - -void LLOutfitsList::applyFilterToTab( - const LLUUID& category_id, - LLAccordionCtrlTab* tab, - const std::string& filter_substring) -{ - if (!tab) return; - LLWearableItemsList* list = dynamic_cast(tab->getAccordionView()); - if (!list) return; - - std::string title = tab->getTitle(); - LLStringUtil::toUpper(title); - - std::string cur_filter = filter_substring; - LLStringUtil::toUpper(cur_filter); - - tab->setTitle(tab->getTitle(), cur_filter); - - if (std::string::npos == title.find(cur_filter)) - { - // Hide tab if its title doesn't pass filter - // and it has no matched items - tab->setVisible(list->hasMatchedItems()); - - // Remove title highlighting because it might - // have been previously highlighted by less restrictive filter - tab->setTitle(tab->getTitle()); - - // Remove the tab from selection. - deselectOutfit(category_id); - } - else - { - // Try restoring the tab selection. - restoreOutfitSelection(tab, category_id); - } -} - -bool LLOutfitsList::canWearSelected() -{ - if (!isAgentAvatarValid()) - { - return false; - } - - uuid_vec_t selected_items; - getSelectedItemsUUIDs(selected_items); - S32 nonreplacable_objects = 0; - - for (uuid_vec_t::const_iterator it = selected_items.begin(); it != selected_items.end(); ++it) - { - const LLUUID& id = *it; - - // Check whether the item is worn. - if (!get_can_item_be_worn(id)) - { - return false; - } - - const LLViewerInventoryItem* item = gInventory.getItem(id); - if (!item) - { - return false; - } - - if (item->getType() == LLAssetType::AT_OBJECT) - { - nonreplacable_objects++; - } - } - - // All selected items can be worn. But do we have enough space for them? - return nonreplacable_objects == 0 || gAgentAvatarp->canAttachMoreObjects(nonreplacable_objects); -} - -void LLOutfitsList::wearSelectedItems() -{ - uuid_vec_t selected_uuids; - getSelectedItemsUUIDs(selected_uuids); - - if(selected_uuids.empty()) - { - return; - } - - wear_multiple(selected_uuids, false); -} - -void LLOutfitsList::onWearableItemsListRightClick(LLUICtrl* ctrl, S32 x, S32 y) -{ - LLWearableItemsList* list = dynamic_cast(ctrl); - if (!list) return; - - uuid_vec_t selected_uuids; - - getSelectedItemsUUIDs(selected_uuids); - - LLWearableItemsList::ContextMenu::instance().show(list, selected_uuids, x, y); -} - -void LLOutfitsList::onCOFChanged() -{ - LLInventoryModel::cat_array_t cat_array; - LLInventoryModel::item_array_t item_array; - - // Collect current COF items - gInventory.collectDescendents( - LLAppearanceMgr::instance().getCOF(), - cat_array, - item_array, - LLInventoryModel::EXCLUDE_TRASH); - - uuid_vec_t vnew; - uuid_vec_t vadded; - uuid_vec_t vremoved; - - // From gInventory we get the UUIDs of links that are currently in COF. - // These links UUIDs are not the same UUIDs that we have in each wearable items list. - // So we collect base items' UUIDs to find them or links that point to them in wearable - // items lists and update their worn state there. - LLInventoryModel::item_array_t::const_iterator array_iter = item_array.begin(), array_end = item_array.end(); - while (array_iter < array_end) - { - vnew.push_back((*(array_iter++))->getLinkedUUID()); - } - - // We need to update only items that were added or removed from COF. - LLCommonUtils::computeDifference(vnew, mCOFLinkedItems, vadded, vremoved); - - // Store the ids of items currently linked from COF. - mCOFLinkedItems = vnew; - - // Append removed ids to added ids because we should update all of them. - vadded.reserve(vadded.size() + vremoved.size()); - vadded.insert(vadded.end(), vremoved.begin(), vremoved.end()); - vremoved.clear(); - - outfits_map_t::iterator map_iter = mOutfitsMap.begin(), map_end = mOutfitsMap.end(); - while (map_iter != map_end) - { - LLAccordionCtrlTab* tab = (map_iter++)->second; - if (!tab) continue; - - LLWearableItemsList* list = dynamic_cast(tab->getAccordionView()); - if (!list) continue; - - // Every list updates the labels of changed items or - // the links that point to these items. - list->updateChangedItems(vadded); - } -} - -void LLOutfitsList::getCurrentCategories(uuid_vec_t& vcur) -{ - // Creating a vector of currently displayed sub-categories UUIDs. - for (outfits_map_t::const_iterator iter = mOutfitsMap.begin(); - iter != mOutfitsMap.end(); - iter++) - { - vcur.push_back((*iter).first); - } -} - - -void LLOutfitsList::sortOutfits() -{ - mAccordion->sort(); -} - -void LLOutfitsList::onOutfitRightClick(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& cat_id) -{ - LLAccordionCtrlTab* tab = dynamic_cast(ctrl); - if (mOutfitMenu && is_tab_header_clicked(tab, y) && cat_id.notNull()) - { - // Focus tab header to trigger tab selection change. - LLUICtrl* header = tab->findChild("dd_header"); - if (header) - { - header->setFocus(true); - } - - uuid_vec_t selected_uuids; - selected_uuids.push_back(cat_id); - mOutfitMenu->show(ctrl, selected_uuids, x, y); - } -} - -LLOutfitListGearMenuBase* LLOutfitsList::createGearMenu() -{ - return new LLOutfitListGearMenu(this); -} - - -bool is_tab_header_clicked(LLAccordionCtrlTab* tab, S32 y) -{ - if(!tab || !tab->getHeaderVisible()) return false; - - S32 header_bottom = tab->getLocalRect().getHeight() - tab->getHeaderHeight(); - return y >= header_bottom; -} - -LLOutfitListBase::LLOutfitListBase() - : LLPanelAppearanceTab() - , mIsInitialized(false) -{ - mCategoriesObserver = new LLInventoryCategoriesObserver(); - mOutfitMenu = new LLOutfitContextMenu(this); - //mGearMenu = createGearMenu(); -} - -LLOutfitListBase::~LLOutfitListBase() -{ - delete mOutfitMenu; - delete mGearMenu; - - if (gInventory.containsObserver(mCategoriesObserver)) - { - gInventory.removeObserver(mCategoriesObserver); - } - delete mCategoriesObserver; -} - -void LLOutfitListBase::onOpen(const LLSD& info) -{ - if (!mIsInitialized) - { - // *TODO: I'm not sure is this check necessary but it never match while developing. - if (!gInventory.isInventoryUsable()) - return; - - const LLUUID outfits = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); - - // *TODO: I'm not sure is this check necessary but it never match while developing. - LLViewerInventoryCategory* category = gInventory.getCategory(outfits); - if (!category) - return; - - gInventory.addObserver(mCategoriesObserver); - - // Start observing changes in "My Outfits" category. - mCategoriesObserver->addCategory(outfits, - boost::bind(&LLOutfitListBase::observerCallback, this, outfits)); - - //const LLUUID cof = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); - // Start observing changes in Current Outfit category. - //mCategoriesObserver->addCategory(cof, boost::bind(&LLOutfitsList::onCOFChanged, this)); - - LLOutfitObserver::instance().addBOFChangedCallback(boost::bind(&LLOutfitListBase::highlightBaseOutfit, this)); - LLOutfitObserver::instance().addBOFReplacedCallback(boost::bind(&LLOutfitListBase::highlightBaseOutfit, this)); - - // Fetch "My Outfits" contents and refresh the list to display - // initially fetched items. If not all items are fetched now - // the observer will refresh the list as soon as the new items - // arrive. - category->fetch(); - refreshList(outfits); - - mIsInitialized = true; - } -} - -void LLOutfitListBase::observerCallback(const LLUUID& category_id) -{ - const LLInventoryModel::changed_items_t& changed_items = gInventory.getChangedIDs(); - mChangedItems.insert(changed_items.begin(), changed_items.end()); - refreshList(category_id); -} - -void LLOutfitListBase::refreshList(const LLUUID& category_id) -{ - bool wasNull = mRefreshListState.CategoryUUID.isNull(); - mRefreshListState.CategoryUUID.setNull(); - - LLInventoryModel::cat_array_t cat_array; - LLInventoryModel::item_array_t item_array; - - // Collect all sub-categories of a given category. - LLIsType is_category(LLAssetType::AT_CATEGORY); - gInventory.collectDescendentsIf( - category_id, - cat_array, - item_array, - LLInventoryModel::EXCLUDE_TRASH, - is_category); - - // Memorize item names for each UUID - std::map names; - for (const LLPointer& cat : cat_array) - { - names.emplace(std::make_pair(cat->getUUID(), cat->getName())); - } - - // Fill added and removed items vectors. - mRefreshListState.Added.clear(); - mRefreshListState.Removed.clear(); - computeDifference(cat_array, mRefreshListState.Added, mRefreshListState.Removed); - // Sort added items vector by item name. - std::sort(mRefreshListState.Added.begin(), mRefreshListState.Added.end(), - [names](const LLUUID& a, const LLUUID& b) - { - return LLStringUtil::compareDict(names.at(a), names.at(b)) < 0; - }); - // Initialize iterators for added and removed items vectors. - mRefreshListState.AddedIterator = mRefreshListState.Added.begin(); - mRefreshListState.RemovedIterator = mRefreshListState.Removed.begin(); - - LL_INFOS() << "added: " << mRefreshListState.Added.size() << - ", removed: " << mRefreshListState.Removed.size() << - ", changed: " << gInventory.getChangedIDs().size() << - LL_ENDL; - - mRefreshListState.CategoryUUID = category_id; - if (wasNull) - { - gIdleCallbacks.addFunction(onIdle, this); - } -} - -// static -void LLOutfitListBase::onIdle(void* userdata) -{ - LLOutfitListBase* self = (LLOutfitListBase*)userdata; - - self->onIdleRefreshList(); -} - -void LLOutfitListBase::onIdleRefreshList() -{ - if (mRefreshListState.CategoryUUID.isNull()) - return; - - const F64 MAX_TIME = 0.05f; - F64 curent_time = LLTimer::getTotalSeconds(); - const F64 end_time = curent_time + MAX_TIME; - - // Handle added tabs. - while (mRefreshListState.AddedIterator < mRefreshListState.Added.end()) - { - const LLUUID cat_id = (*mRefreshListState.AddedIterator++); - updateAddedCategory(cat_id); - - curent_time = LLTimer::getTotalSeconds(); - if (curent_time >= end_time) - return; - } - mRefreshListState.Added.clear(); - mRefreshListState.AddedIterator = mRefreshListState.Added.end(); - - // Handle removed tabs. - while (mRefreshListState.RemovedIterator < mRefreshListState.Removed.end()) - { - const LLUUID cat_id = (*mRefreshListState.RemovedIterator++); - updateRemovedCategory(cat_id); - - curent_time = LLTimer::getTotalSeconds(); - if (curent_time >= end_time) - return; - } - mRefreshListState.Removed.clear(); - mRefreshListState.RemovedIterator = mRefreshListState.Removed.end(); - - // Get changed items from inventory model and update outfit tabs - // which might have been renamed. - while (!mChangedItems.empty()) - { - std::set::const_iterator items_iter = mChangedItems.begin(); - LLViewerInventoryCategory *cat = gInventory.getCategory(*items_iter); - mChangedItems.erase(items_iter); - - // Links aren't supposed to be allowed here, check only cats - if (cat) - { - std::string name = cat->getName(); - updateChangedCategoryName(cat, name); - } - - curent_time = LLTimer::getTotalSeconds(); - if (curent_time >= end_time) - return; - } - - sortOutfits(); - highlightBaseOutfit(); - - gIdleCallbacks.deleteFunction(onIdle, this); - mRefreshListState.CategoryUUID.setNull(); - - LL_INFOS() << "done" << LL_ENDL; -} - -void LLOutfitListBase::computeDifference( - const LLInventoryModel::cat_array_t& vcats, - uuid_vec_t& vadded, - uuid_vec_t& vremoved) -{ - uuid_vec_t vnew; - // Creating a vector of newly collected sub-categories UUIDs. - for (LLInventoryModel::cat_array_t::const_iterator iter = vcats.begin(); - iter != vcats.end(); - iter++) - { - vnew.push_back((*iter)->getUUID()); - } - - uuid_vec_t vcur; - getCurrentCategories(vcur); - - LLCommonUtils::computeDifference(vnew, vcur, vadded, vremoved); -} - -void LLOutfitListBase::sortOutfits() -{ -} - -void LLOutfitListBase::highlightBaseOutfit() -{ - // id of base outfit - LLUUID base_id = LLAppearanceMgr::getInstance()->getBaseOutfitUUID(); - if (base_id != mHighlightedOutfitUUID) - { - LLUUID prev_id = mHighlightedOutfitUUID; - mHighlightedOutfitUUID = base_id; - onHighlightBaseOutfit(base_id, prev_id); - } -} - -void LLOutfitListBase::removeSelected() -{ - LLNotificationsUtil::add("DeleteOutfits", LLSD(), LLSD(), boost::bind(&LLOutfitListBase::onOutfitsRemovalConfirmation, this, _1, _2)); -} - -void LLOutfitListBase::onOutfitsRemovalConfirmation(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option != 0) return; // canceled - - if (mSelectedOutfitUUID.notNull()) - { - gInventory.removeCategory(mSelectedOutfitUUID); - } -} - -void LLOutfitListBase::setSelectedOutfitByUUID(const LLUUID& outfit_uuid) -{ - onSetSelectedOutfitByUUID(outfit_uuid); -} - -boost::signals2::connection LLOutfitListBase::setSelectionChangeCallback(selection_change_callback_t cb) -{ - return mSelectionChangeSignal.connect(cb); -} - -void LLOutfitListBase::signalSelectionOutfitUUID(const LLUUID& category_id) -{ - mSelectionChangeSignal(category_id); -} - -void LLOutfitListBase::outfitRightClickCallBack(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& cat_id) -{ - onOutfitRightClick(ctrl, x, y, cat_id); -} - -void LLOutfitListBase::ChangeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id) -{ - onChangeOutfitSelection(list, category_id); - mSelectedOutfitUUID = category_id; - signalSelectionOutfitUUID(category_id); -} - -bool LLOutfitListBase::postBuild() -{ - mGearMenu = createGearMenu(); - - LLMenuButton* menu_gear_btn = getChild("options_gear_btn"); - - menu_gear_btn->setMouseDownCallback(boost::bind(&LLOutfitListGearMenuBase::updateItemsVisibility, mGearMenu)); - menu_gear_btn->setMenu(mGearMenu->getMenu()); - return true; -} - -void LLOutfitListBase::collapseAllFolders() -{ - onCollapseAllFolders(); -} - -void LLOutfitListBase::expandAllFolders() -{ - onExpandAllFolders(); -} - -void LLOutfitListBase::deselectOutfit(const LLUUID& category_id) -{ - // Reset selection if the outfit is selected. - if (category_id == mSelectedOutfitUUID) - { - mSelectedOutfitUUID = LLUUID::null; - signalSelectionOutfitUUID(mSelectedOutfitUUID); - } -} - -LLContextMenu* LLOutfitContextMenu::createMenu() -{ - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; - LLUUID selected_id = mUUIDs.front(); - - registrar.add("Outfit.WearReplace", - boost::bind(&LLAppearanceMgr::replaceCurrentOutfit, &LLAppearanceMgr::instance(), selected_id)); - registrar.add("Outfit.WearAdd", - boost::bind(&LLAppearanceMgr::addCategoryToCurrentOutfit, &LLAppearanceMgr::instance(), selected_id)); - registrar.add("Outfit.TakeOff", - boost::bind(&LLAppearanceMgr::takeOffOutfit, &LLAppearanceMgr::instance(), selected_id)); - registrar.add("Outfit.Edit", boost::bind(editOutfit)); - registrar.add("Outfit.Rename", boost::bind(renameOutfit, selected_id)); - registrar.add("Outfit.Delete", boost::bind(&LLOutfitListBase::removeSelected, mOutfitList)); - registrar.add("Outfit.Thumbnail", boost::bind(&LLOutfitContextMenu::onThumbnail, this, selected_id)); - registrar.add("Outfit.Save", boost::bind(&LLOutfitContextMenu::onSave, this, selected_id)); - - enable_registrar.add("Outfit.OnEnable", boost::bind(&LLOutfitContextMenu::onEnable, this, _2)); - enable_registrar.add("Outfit.OnVisible", boost::bind(&LLOutfitContextMenu::onVisible, this, _2)); - - return createFromFile("menu_outfit_tab.xml"); - -} - -bool LLOutfitContextMenu::onEnable(LLSD::String param) -{ - LLUUID outfit_cat_id = mUUIDs.back(); - - if ("rename" == param) - { - return get_is_category_renameable(&gInventory, outfit_cat_id); - } - else if ("wear_replace" == param) - { - return LLAppearanceMgr::instance().getCanReplaceCOF(outfit_cat_id); - } - else if ("wear_add" == param) - { - return LLAppearanceMgr::getCanAddToCOF(outfit_cat_id); - } - else if ("take_off" == param) - { - return LLAppearanceMgr::getCanRemoveFromCOF(outfit_cat_id); - } - - return true; -} - -bool LLOutfitContextMenu::onVisible(LLSD::String param) -{ - LLUUID outfit_cat_id = mUUIDs.back(); - - if ("edit" == param) - { - bool is_worn = LLAppearanceMgr::instance().getBaseOutfitUUID() == outfit_cat_id; - return is_worn; - } - else if ("wear_replace" == param) - { - return true; - } - else if ("delete" == param) - { - return LLAppearanceMgr::instance().getCanRemoveOutfit(outfit_cat_id); - } - - return true; -} - -//static -void LLOutfitContextMenu::editOutfit() -{ - LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "edit_outfit")); -} - -void LLOutfitContextMenu::renameOutfit(const LLUUID& outfit_cat_id) -{ - LLAppearanceMgr::instance().renameOutfit(outfit_cat_id); -} - -void LLOutfitContextMenu::onThumbnail(const LLUUID &outfit_cat_id) -{ - if (outfit_cat_id.notNull()) - { - LLSD data(outfit_cat_id); - LLFloaterReg::showInstance("change_item_thumbnail", data); - } -} - -void LLOutfitContextMenu::onSave(const LLUUID &outfit_cat_id) -{ - if (outfit_cat_id.notNull()) - { - LLNotificationsUtil::add("ConfirmOverwriteOutfit", LLSD(), LLSD(), - [outfit_cat_id](const LLSD ¬if, const LLSD &resp) - { - S32 opt = LLNotificationsUtil::getSelectedOption(notif, resp); - if (opt == 0) - { - LLAppearanceMgr::getInstance()->onOutfitFolderCreated(outfit_cat_id, true); - } - }); - } -} - -LLOutfitListGearMenuBase::LLOutfitListGearMenuBase(LLOutfitListBase* olist) - : mOutfitList(olist), - mMenu(NULL) -{ - llassert_always(mOutfitList); - - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; - - registrar.add("Gear.Wear", boost::bind(&LLOutfitListGearMenuBase::onWear, this)); - registrar.add("Gear.TakeOff", boost::bind(&LLOutfitListGearMenuBase::onTakeOff, this)); - registrar.add("Gear.Rename", boost::bind(&LLOutfitListGearMenuBase::onRename, this)); - registrar.add("Gear.Delete", boost::bind(&LLOutfitListBase::removeSelected, mOutfitList)); - registrar.add("Gear.Create", boost::bind(&LLOutfitListGearMenuBase::onCreate, this, _2)); - registrar.add("Gear.Collapse", boost::bind(&LLOutfitListBase::onCollapseAllFolders, mOutfitList)); - registrar.add("Gear.Expand", boost::bind(&LLOutfitListBase::onExpandAllFolders, mOutfitList)); - - registrar.add("Gear.WearAdd", boost::bind(&LLOutfitListGearMenuBase::onAdd, this)); - registrar.add("Gear.Save", boost::bind(&LLOutfitListGearMenuBase::onSave, this)); - - registrar.add("Gear.Thumbnail", boost::bind(&LLOutfitListGearMenuBase::onThumbnail, this)); - registrar.add("Gear.SortByName", boost::bind(&LLOutfitListGearMenuBase::onChangeSortOrder, this)); - - enable_registrar.add("Gear.OnEnable", boost::bind(&LLOutfitListGearMenuBase::onEnable, this, _2)); - enable_registrar.add("Gear.OnVisible", boost::bind(&LLOutfitListGearMenuBase::onVisible, this, _2)); - - mMenu = LLUICtrlFactory::getInstance()->createFromFile( - "menu_outfit_gear.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - llassert(mMenu); -} - -LLOutfitListGearMenuBase::~LLOutfitListGearMenuBase() -{} - -void LLOutfitListGearMenuBase::updateItemsVisibility() -{ - onUpdateItemsVisibility(); -} - -void LLOutfitListGearMenuBase::onUpdateItemsVisibility() -{ - if (!mMenu) return; - - bool have_selection = getSelectedOutfitID().notNull(); - mMenu->setItemVisible("wear_separator", have_selection); - mMenu->arrangeAndClear(); // update menu height -} - -LLToggleableMenu* LLOutfitListGearMenuBase::getMenu() -{ - return mMenu; -} -const LLUUID& LLOutfitListGearMenuBase::getSelectedOutfitID() -{ - return mOutfitList->getSelectedOutfitUUID(); -} - -LLViewerInventoryCategory* LLOutfitListGearMenuBase::getSelectedOutfit() -{ - const LLUUID& selected_outfit_id = getSelectedOutfitID(); - if (selected_outfit_id.isNull()) - { - return NULL; - } - - LLViewerInventoryCategory* cat = gInventory.getCategory(selected_outfit_id); - return cat; -} - -void LLOutfitListGearMenuBase::onWear() -{ - LLViewerInventoryCategory* selected_outfit = getSelectedOutfit(); - if (selected_outfit) - { - LLAppearanceMgr::instance().wearInventoryCategory( - selected_outfit, /*copy=*/ false, /*append=*/ false); - } -} - -void LLOutfitListGearMenuBase::onAdd() -{ - const LLUUID& selected_id = getSelectedOutfitID(); - - if (selected_id.notNull()) - { - LLAppearanceMgr::getInstance()->addCategoryToCurrentOutfit(selected_id); - } -} - -void LLOutfitListGearMenuBase::onSave() -{ - const LLUUID &selected_id = getSelectedOutfitID(); - LLNotificationsUtil::add("ConfirmOverwriteOutfit", LLSD(), LLSD(), - [selected_id](const LLSD ¬if, const LLSD &resp) - { - S32 opt = LLNotificationsUtil::getSelectedOption(notif, resp); - if (opt == 0) - { - LLAppearanceMgr::getInstance()->onOutfitFolderCreated(selected_id, true); - } - }); -} - -void LLOutfitListGearMenuBase::onTakeOff() -{ - // Take off selected outfit. - const LLUUID& selected_outfit_id = getSelectedOutfitID(); - if (selected_outfit_id.notNull()) - { - LLAppearanceMgr::instance().takeOffOutfit(selected_outfit_id); - } -} - -void LLOutfitListGearMenuBase::onRename() -{ - const LLUUID& selected_outfit_id = getSelectedOutfitID(); - if (selected_outfit_id.notNull()) - { - LLAppearanceMgr::instance().renameOutfit(selected_outfit_id); - } -} - -void LLOutfitListGearMenuBase::onCreate(const LLSD& data) -{ - LLWearableType::EType type = LLWearableType::getInstance()->typeNameToType(data.asString()); - if (type == LLWearableType::WT_NONE) - { - LL_WARNS() << "Invalid wearable type" << LL_ENDL; - return; - } - - LLAgentWearables::createWearable(type, true); -} - -bool LLOutfitListGearMenuBase::onEnable(LLSD::String param) -{ - // Handle the "Wear - Replace Current Outfit" menu option specially - // because LLOutfitList::isActionEnabled() checks whether it's allowed - // to wear selected outfit OR selected items, while we're only - // interested in the outfit (STORM-183). - if ("wear" == param) - { - return LLAppearanceMgr::instance().getCanReplaceCOF(mOutfitList->getSelectedOutfitUUID()); - } - - return mOutfitList->isActionEnabled(param); -} - -bool LLOutfitListGearMenuBase::onVisible(LLSD::String param) -{ - const LLUUID& selected_outfit_id = getSelectedOutfitID(); - if (selected_outfit_id.isNull()) // no selection or invalid outfit selected - { - return false; - } - - return true; -} - -void LLOutfitListGearMenuBase::onThumbnail() -{ - const LLUUID& selected_outfit_id = getSelectedOutfitID(); - LLSD data(selected_outfit_id); - LLFloaterReg::showInstance("change_item_thumbnail", data); -} - -void LLOutfitListGearMenuBase::onChangeSortOrder() -{ - -} - -LLOutfitListGearMenu::LLOutfitListGearMenu(LLOutfitListBase* olist) - : LLOutfitListGearMenuBase(olist) -{} - -LLOutfitListGearMenu::~LLOutfitListGearMenu() -{} - -void LLOutfitListGearMenu::onUpdateItemsVisibility() -{ - if (!mMenu) return; - mMenu->setItemVisible("expand", true); - mMenu->setItemVisible("collapse", true); - mMenu->setItemVisible("thumbnail", getSelectedOutfitID().notNull()); - mMenu->setItemVisible("sepatator3", false); - mMenu->setItemVisible("sort_folders_by_name", false); - LLOutfitListGearMenuBase::onUpdateItemsVisibility(); -} - -bool LLOutfitAccordionCtrlTab::handleToolTip(S32 x, S32 y, MASK mask) -{ - if (y >= getLocalRect().getHeight() - getHeaderHeight()) - { - LLSD params; - params["inv_type"] = LLInventoryType::IT_CATEGORY; - params["thumbnail_id"] = gInventory.getCategory(mFolderID)->getThumbnailUUID(); - params["item_id"] = mFolderID; - - LLToolTipMgr::instance().show(LLToolTip::Params() - .message(getToolTip()) - .sticky_rect(calcScreenRect()) - .delay_time(LLView::getTooltipTimeout()) - .create_callback(boost::bind(&LLInspectTextureUtil::createInventoryToolTip, _1)) - .create_params(params)); - return true; - } - - return LLAccordionCtrlTab::handleToolTip(x, y, mask); -} -// EOF +/** + * @file lloutfitslist.cpp + * @brief List of agent's outfits for My Appearance side panel. + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lloutfitslist.h" + +// llcommon +#include "llcommonutils.h" + +#include "llaccordionctrl.h" +#include "llaccordionctrltab.h" +#include "llagentwearables.h" +#include "llappearancemgr.h" +#include "llfloaterreg.h" +#include "llfloatersidepanelcontainer.h" +#include "llinspecttexture.h" +#include "llinventoryfunctions.h" +#include "llinventorymodel.h" +#include "llmenubutton.h" +#include "llnotificationsutil.h" +#include "lloutfitobserver.h" +#include "lltoggleablemenu.h" +#include "lltransutil.h" +#include "llviewermenu.h" +#include "llvoavatar.h" +#include "llvoavatarself.h" +#include "llwearableitemslist.h" + +static bool is_tab_header_clicked(LLAccordionCtrlTab* tab, S32 y); + +static const LLOutfitTabNameComparator OUTFIT_TAB_NAME_COMPARATOR; + +/*virtual*/ +bool LLOutfitTabNameComparator::compare(const LLAccordionCtrlTab* tab1, const LLAccordionCtrlTab* tab2) const +{ + std::string name1 = tab1->getTitle(); + std::string name2 = tab2->getTitle(); + + return (LLStringUtil::compareDict(name1, name2) < 0); +} + +struct outfit_accordion_tab_params : public LLInitParam::Block +{ + Mandatory wearable_list; + + outfit_accordion_tab_params() + : wearable_list("wearable_items_list") + {} +}; + +const outfit_accordion_tab_params& get_accordion_tab_params() +{ + static outfit_accordion_tab_params tab_params; + static bool initialized = false; + if (!initialized) + { + initialized = true; + + LLXMLNodePtr xmlNode; + if (LLUICtrlFactory::getLayeredXMLNode("outfit_accordion_tab.xml", xmlNode)) + { + LLXUIParser parser; + parser.readXUI(xmlNode, tab_params, "outfit_accordion_tab.xml"); + } + else + { + LL_WARNS() << "Failed to read xml of Outfit's Accordion Tab from outfit_accordion_tab.xml" << LL_ENDL; + } + } + + return tab_params; +} + + +static LLPanelInjector t_outfits_list("outfits_list"); + +LLOutfitsList::LLOutfitsList() + : LLOutfitListBase() + , mAccordion(NULL) + , mListCommands(NULL) + , mItemSelected(false) +{ +} + +LLOutfitsList::~LLOutfitsList() +{ +} + +bool LLOutfitsList::postBuild() +{ + mAccordion = getChild("outfits_accordion"); + mAccordion->setComparator(&OUTFIT_TAB_NAME_COMPARATOR); + + return LLOutfitListBase::postBuild(); +} + +//virtual +void LLOutfitsList::onOpen(const LLSD& info) +{ + if (!mIsInitialized) + { + // Start observing changes in Current Outfit category. + LLOutfitObserver::instance().addCOFChangedCallback(boost::bind(&LLOutfitsList::onCOFChanged, this)); + } + + LLOutfitListBase::onOpen(info); + + LLAccordionCtrlTab* selected_tab = mAccordion->getSelectedTab(); + if (!selected_tab) return; + + // Pass focus to the selected outfit tab. + selected_tab->showAndFocusHeader(); +} + + +void LLOutfitsList::updateAddedCategory(LLUUID cat_id) +{ + LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id); + if (!cat) return; + + std::string name = cat->getName(); + + outfit_accordion_tab_params tab_params(get_accordion_tab_params()); + tab_params.cat_id = cat_id; + LLOutfitAccordionCtrlTab *tab = LLUICtrlFactory::create(tab_params); + if (!tab) return; + LLWearableItemsList* wearable_list = LLUICtrlFactory::create(tab_params.wearable_list); + wearable_list->setShape(tab->getLocalRect()); + tab->addChild(wearable_list); + + tab->setName(name); + tab->setTitle(name); + + // *TODO: LLUICtrlFactory::defaultBuilder does not use "display_children" from xml. Should be investigated. + tab->setDisplayChildren(false); + mAccordion->addCollapsibleCtrl(tab); + + // Start observing the new outfit category. + LLWearableItemsList* list = tab->getChild("wearable_items_list"); + if (!mCategoriesObserver->addCategory(cat_id, boost::bind(&LLWearableItemsList::updateList, list, cat_id))) + { + // Remove accordion tab if category could not be added to observer. + mAccordion->removeCollapsibleCtrl(tab); + + // kill removed tab + tab->die(); + return; + } + + // Map the new tab with outfit category UUID. + mOutfitsMap.insert(LLOutfitsList::outfits_map_value_t(cat_id, tab)); + + tab->setRightMouseDownCallback(boost::bind(&LLOutfitListBase::outfitRightClickCallBack, this, + _1, _2, _3, cat_id)); + + // Setting tab focus callback to monitor currently selected outfit. + tab->setFocusReceivedCallback(boost::bind(&LLOutfitListBase::ChangeOutfitSelection, this, list, cat_id)); + + // Setting callback to reset items selection inside outfit on accordion collapsing and expanding (EXT-7875) + tab->setDropDownStateChangedCallback(boost::bind(&LLOutfitsList::resetItemSelection, this, list, cat_id)); + + // force showing list items that don't match current filter(EXT-7158) + list->setForceShowingUnmatchedItems(true); + + // Setting list commit callback to monitor currently selected wearable item. + list->setCommitCallback(boost::bind(&LLOutfitsList::onListSelectionChange, this, _1)); + + // Setting list refresh callback to apply filter on list change. + list->setRefreshCompleteCallback(boost::bind(&LLOutfitsList::onRefreshComplete, this, _1)); + + list->setRightMouseDownCallback(boost::bind(&LLOutfitsList::onWearableItemsListRightClick, this, _1, _2, _3)); + + // Fetch the new outfit contents. + cat->fetch(); + + // Refresh the list of outfit items after fetch(). + // Further list updates will be triggered by the category observer. + list->updateList(cat_id); + + // If filter is currently applied we store the initial tab state. + if (!getFilterSubString().empty()) + { + tab->notifyChildren(LLSD().with("action", "store_state")); + + // Setting mForceRefresh flag will make the list refresh its contents + // even if it is not currently visible. This is required to apply the + // filter to the newly added list. + list->setForceRefresh(true); + + list->setFilterSubString(getFilterSubString(), false); + } +} + +void LLOutfitsList::updateRemovedCategory(LLUUID cat_id) +{ + outfits_map_t::iterator outfits_iter = mOutfitsMap.find(cat_id); + if (outfits_iter != mOutfitsMap.end()) + { + const LLUUID& outfit_id = outfits_iter->first; + LLAccordionCtrlTab* tab = outfits_iter->second; + + // An outfit is removed from the list. Do the following: + // 1. Remove outfit category from observer to stop monitoring its changes. + mCategoriesObserver->removeCategory(outfit_id); + + // 2. Remove the outfit from selection. + deselectOutfit(outfit_id); + + // 3. Remove category UUID to accordion tab mapping. + mOutfitsMap.erase(outfits_iter); + + // 4. Remove outfit tab from accordion. + mAccordion->removeCollapsibleCtrl(tab); + + // kill removed tab + if (tab != NULL) + { + tab->die(); + } + } +} + +//virtual +void LLOutfitsList::onHighlightBaseOutfit(LLUUID base_id, LLUUID prev_id) +{ + if (mOutfitsMap[prev_id]) + { + mOutfitsMap[prev_id]->setTitleFontStyle("NORMAL"); + mOutfitsMap[prev_id]->setTitleColor(LLUIColorTable::instance().getColor("AccordionHeaderTextColor")); + } + if (mOutfitsMap[base_id]) + { + mOutfitsMap[base_id]->setTitleFontStyle("BOLD"); + mOutfitsMap[base_id]->setTitleColor(LLUIColorTable::instance().getColor("SelectedOutfitTextColor")); + } +} + +void LLOutfitsList::onListSelectionChange(LLUICtrl* ctrl) +{ + LLWearableItemsList* list = dynamic_cast(ctrl); + if (!list) return; + + LLViewerInventoryItem *item = gInventory.getItem(list->getSelectedUUID()); + if (!item) return; + + ChangeOutfitSelection(list, item->getParentUUID()); +} + +void LLOutfitListBase::performAction(std::string action) +{ + if (mSelectedOutfitUUID.isNull()) return; + + LLViewerInventoryCategory* cat = gInventory.getCategory(mSelectedOutfitUUID); + if (!cat) return; + + if ("replaceoutfit" == action) + { + LLAppearanceMgr::instance().wearInventoryCategory( cat, false, false ); + } + else if ("addtooutfit" == action) + { + LLAppearanceMgr::instance().wearInventoryCategory( cat, false, true ); + } + else if ("rename_outfit" == action) + { + LLAppearanceMgr::instance().renameOutfit(mSelectedOutfitUUID); + } +} + +void LLOutfitsList::onSetSelectedOutfitByUUID(const LLUUID& outfit_uuid) +{ + for (outfits_map_t::iterator iter = mOutfitsMap.begin(); + iter != mOutfitsMap.end(); + ++iter) + { + if (outfit_uuid == iter->first) + { + LLAccordionCtrlTab* tab = iter->second; + if (!tab) continue; + + LLWearableItemsList* list = dynamic_cast(tab->getAccordionView()); + if (!list) continue; + + tab->setFocus(true); + ChangeOutfitSelection(list, outfit_uuid); + + tab->changeOpenClose(false); + } + } +} + +// virtual +bool LLOutfitListBase::isActionEnabled(const LLSD& userdata) +{ + if (mSelectedOutfitUUID.isNull()) return false; + + const std::string command_name = userdata.asString(); + if (command_name == "delete") + { + return !hasItemSelected() && LLAppearanceMgr::instance().getCanRemoveOutfit(mSelectedOutfitUUID); + } + if (command_name == "rename") + { + return get_is_category_renameable(&gInventory, mSelectedOutfitUUID); + } + if (command_name == "save_outfit") + { + bool outfit_locked = LLAppearanceMgr::getInstance()->isOutfitLocked(); + bool outfit_dirty = LLAppearanceMgr::getInstance()->isOutfitDirty(); + // allow save only if outfit isn't locked and is dirty + return !outfit_locked && outfit_dirty; + } + if (command_name == "wear") + { + if (gAgentWearables.isCOFChangeInProgress()) + { + return false; + } + + if (hasItemSelected()) + { + return canWearSelected(); + } + + // outfit selected + return LLAppearanceMgr::instance().getCanReplaceCOF(mSelectedOutfitUUID); + } + if (command_name == "take_off") + { + // Enable "Take Off" if any of selected items can be taken off + // or the selected outfit contains items that can be taken off. + return ( hasItemSelected() && canTakeOffSelected() ) + || ( !hasItemSelected() && LLAppearanceMgr::getCanRemoveFromCOF(mSelectedOutfitUUID) ); + } + + if (command_name == "wear_add") + { + // *TODO: do we ever get here? + return LLAppearanceMgr::getCanAddToCOF(mSelectedOutfitUUID); + } + + return false; +} + +void LLOutfitsList::getSelectedItemsUUIDs(uuid_vec_t& selected_uuids) const +{ + // Collect selected items from all selected lists. + for (wearables_lists_map_t::const_iterator iter = mSelectedListsMap.begin(); + iter != mSelectedListsMap.end(); + ++iter) + { + uuid_vec_t uuids; + (*iter).second->getSelectedUUIDs(uuids); + + S32 prev_size = selected_uuids.size(); + selected_uuids.resize(prev_size + uuids.size()); + std::copy(uuids.begin(), uuids.end(), selected_uuids.begin() + prev_size); + } +} + +void LLOutfitsList::onCollapseAllFolders() +{ + for (outfits_map_t::iterator iter = mOutfitsMap.begin(); + iter != mOutfitsMap.end(); + ++iter) + { + LLAccordionCtrlTab* tab = iter->second; + if(tab && tab->isExpanded()) + { + tab->changeOpenClose(true); + } + } +} + +void LLOutfitsList::onExpandAllFolders() +{ + for (outfits_map_t::iterator iter = mOutfitsMap.begin(); + iter != mOutfitsMap.end(); + ++iter) + { + LLAccordionCtrlTab* tab = iter->second; + if(tab && !tab->isExpanded()) + { + tab->changeOpenClose(false); + } + } +} + +bool LLOutfitsList::hasItemSelected() +{ + return mItemSelected; +} + +////////////////////////////////////////////////////////////////////////// +// Private methods +////////////////////////////////////////////////////////////////////////// + +void LLOutfitsList::updateChangedCategoryName(LLViewerInventoryCategory *cat, std::string name) +{ + outfits_map_t::iterator outfits_iter = mOutfitsMap.find(cat->getUUID()); + if (outfits_iter != mOutfitsMap.end()) + { + // Update tab name with the new category name. + LLAccordionCtrlTab* tab = outfits_iter->second; + if (tab) + { + tab->setName(name); + tab->setTitle(name); + } + } +} + +void LLOutfitsList::resetItemSelection(LLWearableItemsList* list, const LLUUID& category_id) +{ + list->resetSelection(); + mItemSelected = false; + signalSelectionOutfitUUID(category_id); +} + +void LLOutfitsList::onChangeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id) +{ + MASK mask = gKeyboard->currentMask(true); + + // Reset selection in all previously selected tabs except for the current + // if new selection is started. + if (list && !(mask & MASK_CONTROL)) + { + for (wearables_lists_map_t::iterator iter = mSelectedListsMap.begin(); + iter != mSelectedListsMap.end(); + ++iter) + { + LLWearableItemsList* selected_list = (*iter).second; + if (selected_list != list) + { + selected_list->resetSelection(); + } + } + + // Clear current selection. + mSelectedListsMap.clear(); + } + + mItemSelected = list && (list->getSelectedItem() != NULL); + + mSelectedListsMap.insert(wearables_lists_map_value_t(category_id, list)); +} + +void LLOutfitsList::deselectOutfit(const LLUUID& category_id) +{ + // Remove selected lists map entry. + mSelectedListsMap.erase(category_id); + + LLOutfitListBase::deselectOutfit(category_id); +} + +void LLOutfitsList::restoreOutfitSelection(LLAccordionCtrlTab* tab, const LLUUID& category_id) +{ + // Try restoring outfit selection after filtering. + if (mAccordion->getSelectedTab() == tab) + { + signalSelectionOutfitUUID(category_id); + } +} + +void LLOutfitsList::onRefreshComplete(LLUICtrl* ctrl) +{ + if (!ctrl || getFilterSubString().empty()) + return; + + for (outfits_map_t::iterator + iter = mOutfitsMap.begin(), + iter_end = mOutfitsMap.end(); + iter != iter_end; ++iter) + { + LLAccordionCtrlTab* tab = iter->second; + if (!tab) continue; + + LLWearableItemsList* list = dynamic_cast(tab->getAccordionView()); + if (list != ctrl) continue; + + applyFilterToTab(iter->first, tab, getFilterSubString()); + } +} + +// virtual +void LLOutfitsList::onFilterSubStringChanged(const std::string& new_string, const std::string& old_string) +{ + mAccordion->setFilterSubString(new_string); + + outfits_map_t::iterator iter = mOutfitsMap.begin(), iter_end = mOutfitsMap.end(); + while (iter != iter_end) + { + const LLUUID& category_id = iter->first; + LLAccordionCtrlTab* tab = iter++->second; + if (!tab) continue; + + LLWearableItemsList* list = dynamic_cast(tab->getAccordionView()); + if (list) + { + list->setFilterSubString(new_string, tab->getDisplayChildren()); + } + + if (old_string.empty()) + { + // Store accordion tab state when filter is not empty + tab->notifyChildren(LLSD().with("action", "store_state")); + } + + if (!new_string.empty()) + { + applyFilterToTab(category_id, tab, new_string); + } + else + { + tab->setVisible(true); + + // Restore tab title when filter is empty + tab->setTitle(tab->getTitle()); + + // Restore accordion state after all those accodrion tab manipulations + tab->notifyChildren(LLSD().with("action", "restore_state")); + + // Try restoring the tab selection. + restoreOutfitSelection(tab, category_id); + } + } + + mAccordion->arrange(); +} + +void LLOutfitsList::applyFilterToTab( + const LLUUID& category_id, + LLAccordionCtrlTab* tab, + const std::string& filter_substring) +{ + if (!tab) return; + LLWearableItemsList* list = dynamic_cast(tab->getAccordionView()); + if (!list) return; + + std::string title = tab->getTitle(); + LLStringUtil::toUpper(title); + + std::string cur_filter = filter_substring; + LLStringUtil::toUpper(cur_filter); + + tab->setTitle(tab->getTitle(), cur_filter); + + if (std::string::npos == title.find(cur_filter)) + { + // Hide tab if its title doesn't pass filter + // and it has no matched items + tab->setVisible(list->hasMatchedItems()); + + // Remove title highlighting because it might + // have been previously highlighted by less restrictive filter + tab->setTitle(tab->getTitle()); + + // Remove the tab from selection. + deselectOutfit(category_id); + } + else + { + // Try restoring the tab selection. + restoreOutfitSelection(tab, category_id); + } +} + +bool LLOutfitsList::canWearSelected() +{ + if (!isAgentAvatarValid()) + { + return false; + } + + uuid_vec_t selected_items; + getSelectedItemsUUIDs(selected_items); + S32 nonreplacable_objects = 0; + + for (uuid_vec_t::const_iterator it = selected_items.begin(); it != selected_items.end(); ++it) + { + const LLUUID& id = *it; + + // Check whether the item is worn. + if (!get_can_item_be_worn(id)) + { + return false; + } + + const LLViewerInventoryItem* item = gInventory.getItem(id); + if (!item) + { + return false; + } + + if (item->getType() == LLAssetType::AT_OBJECT) + { + nonreplacable_objects++; + } + } + + // All selected items can be worn. But do we have enough space for them? + return nonreplacable_objects == 0 || gAgentAvatarp->canAttachMoreObjects(nonreplacable_objects); +} + +void LLOutfitsList::wearSelectedItems() +{ + uuid_vec_t selected_uuids; + getSelectedItemsUUIDs(selected_uuids); + + if(selected_uuids.empty()) + { + return; + } + + wear_multiple(selected_uuids, false); +} + +void LLOutfitsList::onWearableItemsListRightClick(LLUICtrl* ctrl, S32 x, S32 y) +{ + LLWearableItemsList* list = dynamic_cast(ctrl); + if (!list) return; + + uuid_vec_t selected_uuids; + + getSelectedItemsUUIDs(selected_uuids); + + LLWearableItemsList::ContextMenu::instance().show(list, selected_uuids, x, y); +} + +void LLOutfitsList::onCOFChanged() +{ + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + + // Collect current COF items + gInventory.collectDescendents( + LLAppearanceMgr::instance().getCOF(), + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH); + + uuid_vec_t vnew; + uuid_vec_t vadded; + uuid_vec_t vremoved; + + // From gInventory we get the UUIDs of links that are currently in COF. + // These links UUIDs are not the same UUIDs that we have in each wearable items list. + // So we collect base items' UUIDs to find them or links that point to them in wearable + // items lists and update their worn state there. + LLInventoryModel::item_array_t::const_iterator array_iter = item_array.begin(), array_end = item_array.end(); + while (array_iter < array_end) + { + vnew.push_back((*(array_iter++))->getLinkedUUID()); + } + + // We need to update only items that were added or removed from COF. + LLCommonUtils::computeDifference(vnew, mCOFLinkedItems, vadded, vremoved); + + // Store the ids of items currently linked from COF. + mCOFLinkedItems = vnew; + + // Append removed ids to added ids because we should update all of them. + vadded.reserve(vadded.size() + vremoved.size()); + vadded.insert(vadded.end(), vremoved.begin(), vremoved.end()); + vremoved.clear(); + + outfits_map_t::iterator map_iter = mOutfitsMap.begin(), map_end = mOutfitsMap.end(); + while (map_iter != map_end) + { + LLAccordionCtrlTab* tab = (map_iter++)->second; + if (!tab) continue; + + LLWearableItemsList* list = dynamic_cast(tab->getAccordionView()); + if (!list) continue; + + // Every list updates the labels of changed items or + // the links that point to these items. + list->updateChangedItems(vadded); + } +} + +void LLOutfitsList::getCurrentCategories(uuid_vec_t& vcur) +{ + // Creating a vector of currently displayed sub-categories UUIDs. + for (outfits_map_t::const_iterator iter = mOutfitsMap.begin(); + iter != mOutfitsMap.end(); + iter++) + { + vcur.push_back((*iter).first); + } +} + + +void LLOutfitsList::sortOutfits() +{ + mAccordion->sort(); +} + +void LLOutfitsList::onOutfitRightClick(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& cat_id) +{ + LLAccordionCtrlTab* tab = dynamic_cast(ctrl); + if (mOutfitMenu && is_tab_header_clicked(tab, y) && cat_id.notNull()) + { + // Focus tab header to trigger tab selection change. + LLUICtrl* header = tab->findChild("dd_header"); + if (header) + { + header->setFocus(true); + } + + uuid_vec_t selected_uuids; + selected_uuids.push_back(cat_id); + mOutfitMenu->show(ctrl, selected_uuids, x, y); + } +} + +LLOutfitListGearMenuBase* LLOutfitsList::createGearMenu() +{ + return new LLOutfitListGearMenu(this); +} + + +bool is_tab_header_clicked(LLAccordionCtrlTab* tab, S32 y) +{ + if(!tab || !tab->getHeaderVisible()) return false; + + S32 header_bottom = tab->getLocalRect().getHeight() - tab->getHeaderHeight(); + return y >= header_bottom; +} + +LLOutfitListBase::LLOutfitListBase() + : LLPanelAppearanceTab() + , mIsInitialized(false) +{ + mCategoriesObserver = new LLInventoryCategoriesObserver(); + mOutfitMenu = new LLOutfitContextMenu(this); + //mGearMenu = createGearMenu(); +} + +LLOutfitListBase::~LLOutfitListBase() +{ + delete mOutfitMenu; + delete mGearMenu; + + if (gInventory.containsObserver(mCategoriesObserver)) + { + gInventory.removeObserver(mCategoriesObserver); + } + delete mCategoriesObserver; +} + +void LLOutfitListBase::onOpen(const LLSD& info) +{ + if (!mIsInitialized) + { + // *TODO: I'm not sure is this check necessary but it never match while developing. + if (!gInventory.isInventoryUsable()) + return; + + const LLUUID outfits = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + + // *TODO: I'm not sure is this check necessary but it never match while developing. + LLViewerInventoryCategory* category = gInventory.getCategory(outfits); + if (!category) + return; + + gInventory.addObserver(mCategoriesObserver); + + // Start observing changes in "My Outfits" category. + mCategoriesObserver->addCategory(outfits, + boost::bind(&LLOutfitListBase::observerCallback, this, outfits)); + + //const LLUUID cof = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + // Start observing changes in Current Outfit category. + //mCategoriesObserver->addCategory(cof, boost::bind(&LLOutfitsList::onCOFChanged, this)); + + LLOutfitObserver::instance().addBOFChangedCallback(boost::bind(&LLOutfitListBase::highlightBaseOutfit, this)); + LLOutfitObserver::instance().addBOFReplacedCallback(boost::bind(&LLOutfitListBase::highlightBaseOutfit, this)); + + // Fetch "My Outfits" contents and refresh the list to display + // initially fetched items. If not all items are fetched now + // the observer will refresh the list as soon as the new items + // arrive. + category->fetch(); + refreshList(outfits); + + mIsInitialized = true; + } +} + +void LLOutfitListBase::observerCallback(const LLUUID& category_id) +{ + const LLInventoryModel::changed_items_t& changed_items = gInventory.getChangedIDs(); + mChangedItems.insert(changed_items.begin(), changed_items.end()); + refreshList(category_id); +} + +void LLOutfitListBase::refreshList(const LLUUID& category_id) +{ + bool wasNull = mRefreshListState.CategoryUUID.isNull(); + mRefreshListState.CategoryUUID.setNull(); + + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + + // Collect all sub-categories of a given category. + LLIsType is_category(LLAssetType::AT_CATEGORY); + gInventory.collectDescendentsIf( + category_id, + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH, + is_category); + + // Memorize item names for each UUID + std::map names; + for (const LLPointer& cat : cat_array) + { + names.emplace(std::make_pair(cat->getUUID(), cat->getName())); + } + + // Fill added and removed items vectors. + mRefreshListState.Added.clear(); + mRefreshListState.Removed.clear(); + computeDifference(cat_array, mRefreshListState.Added, mRefreshListState.Removed); + // Sort added items vector by item name. + std::sort(mRefreshListState.Added.begin(), mRefreshListState.Added.end(), + [names](const LLUUID& a, const LLUUID& b) + { + return LLStringUtil::compareDict(names.at(a), names.at(b)) < 0; + }); + // Initialize iterators for added and removed items vectors. + mRefreshListState.AddedIterator = mRefreshListState.Added.begin(); + mRefreshListState.RemovedIterator = mRefreshListState.Removed.begin(); + + LL_INFOS() << "added: " << mRefreshListState.Added.size() << + ", removed: " << mRefreshListState.Removed.size() << + ", changed: " << gInventory.getChangedIDs().size() << + LL_ENDL; + + mRefreshListState.CategoryUUID = category_id; + if (wasNull) + { + gIdleCallbacks.addFunction(onIdle, this); + } +} + +// static +void LLOutfitListBase::onIdle(void* userdata) +{ + LLOutfitListBase* self = (LLOutfitListBase*)userdata; + + self->onIdleRefreshList(); +} + +void LLOutfitListBase::onIdleRefreshList() +{ + if (mRefreshListState.CategoryUUID.isNull()) + return; + + const F64 MAX_TIME = 0.05f; + F64 curent_time = LLTimer::getTotalSeconds(); + const F64 end_time = curent_time + MAX_TIME; + + // Handle added tabs. + while (mRefreshListState.AddedIterator < mRefreshListState.Added.end()) + { + const LLUUID cat_id = (*mRefreshListState.AddedIterator++); + updateAddedCategory(cat_id); + + curent_time = LLTimer::getTotalSeconds(); + if (curent_time >= end_time) + return; + } + mRefreshListState.Added.clear(); + mRefreshListState.AddedIterator = mRefreshListState.Added.end(); + + // Handle removed tabs. + while (mRefreshListState.RemovedIterator < mRefreshListState.Removed.end()) + { + const LLUUID cat_id = (*mRefreshListState.RemovedIterator++); + updateRemovedCategory(cat_id); + + curent_time = LLTimer::getTotalSeconds(); + if (curent_time >= end_time) + return; + } + mRefreshListState.Removed.clear(); + mRefreshListState.RemovedIterator = mRefreshListState.Removed.end(); + + // Get changed items from inventory model and update outfit tabs + // which might have been renamed. + while (!mChangedItems.empty()) + { + std::set::const_iterator items_iter = mChangedItems.begin(); + LLViewerInventoryCategory *cat = gInventory.getCategory(*items_iter); + mChangedItems.erase(items_iter); + + // Links aren't supposed to be allowed here, check only cats + if (cat) + { + std::string name = cat->getName(); + updateChangedCategoryName(cat, name); + } + + curent_time = LLTimer::getTotalSeconds(); + if (curent_time >= end_time) + return; + } + + sortOutfits(); + highlightBaseOutfit(); + + gIdleCallbacks.deleteFunction(onIdle, this); + mRefreshListState.CategoryUUID.setNull(); + + LL_INFOS() << "done" << LL_ENDL; +} + +void LLOutfitListBase::computeDifference( + const LLInventoryModel::cat_array_t& vcats, + uuid_vec_t& vadded, + uuid_vec_t& vremoved) +{ + uuid_vec_t vnew; + // Creating a vector of newly collected sub-categories UUIDs. + for (LLInventoryModel::cat_array_t::const_iterator iter = vcats.begin(); + iter != vcats.end(); + iter++) + { + vnew.push_back((*iter)->getUUID()); + } + + uuid_vec_t vcur; + getCurrentCategories(vcur); + + LLCommonUtils::computeDifference(vnew, vcur, vadded, vremoved); +} + +void LLOutfitListBase::sortOutfits() +{ +} + +void LLOutfitListBase::highlightBaseOutfit() +{ + // id of base outfit + LLUUID base_id = LLAppearanceMgr::getInstance()->getBaseOutfitUUID(); + if (base_id != mHighlightedOutfitUUID) + { + LLUUID prev_id = mHighlightedOutfitUUID; + mHighlightedOutfitUUID = base_id; + onHighlightBaseOutfit(base_id, prev_id); + } +} + +void LLOutfitListBase::removeSelected() +{ + LLNotificationsUtil::add("DeleteOutfits", LLSD(), LLSD(), boost::bind(&LLOutfitListBase::onOutfitsRemovalConfirmation, this, _1, _2)); +} + +void LLOutfitListBase::onOutfitsRemovalConfirmation(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) return; // canceled + + if (mSelectedOutfitUUID.notNull()) + { + gInventory.removeCategory(mSelectedOutfitUUID); + } +} + +void LLOutfitListBase::setSelectedOutfitByUUID(const LLUUID& outfit_uuid) +{ + onSetSelectedOutfitByUUID(outfit_uuid); +} + +boost::signals2::connection LLOutfitListBase::setSelectionChangeCallback(selection_change_callback_t cb) +{ + return mSelectionChangeSignal.connect(cb); +} + +void LLOutfitListBase::signalSelectionOutfitUUID(const LLUUID& category_id) +{ + mSelectionChangeSignal(category_id); +} + +void LLOutfitListBase::outfitRightClickCallBack(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& cat_id) +{ + onOutfitRightClick(ctrl, x, y, cat_id); +} + +void LLOutfitListBase::ChangeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id) +{ + onChangeOutfitSelection(list, category_id); + mSelectedOutfitUUID = category_id; + signalSelectionOutfitUUID(category_id); +} + +bool LLOutfitListBase::postBuild() +{ + mGearMenu = createGearMenu(); + + LLMenuButton* menu_gear_btn = getChild("options_gear_btn"); + + menu_gear_btn->setMouseDownCallback(boost::bind(&LLOutfitListGearMenuBase::updateItemsVisibility, mGearMenu)); + menu_gear_btn->setMenu(mGearMenu->getMenu()); + return true; +} + +void LLOutfitListBase::collapseAllFolders() +{ + onCollapseAllFolders(); +} + +void LLOutfitListBase::expandAllFolders() +{ + onExpandAllFolders(); +} + +void LLOutfitListBase::deselectOutfit(const LLUUID& category_id) +{ + // Reset selection if the outfit is selected. + if (category_id == mSelectedOutfitUUID) + { + mSelectedOutfitUUID = LLUUID::null; + signalSelectionOutfitUUID(mSelectedOutfitUUID); + } +} + +LLContextMenu* LLOutfitContextMenu::createMenu() +{ + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + LLUUID selected_id = mUUIDs.front(); + + registrar.add("Outfit.WearReplace", + boost::bind(&LLAppearanceMgr::replaceCurrentOutfit, &LLAppearanceMgr::instance(), selected_id)); + registrar.add("Outfit.WearAdd", + boost::bind(&LLAppearanceMgr::addCategoryToCurrentOutfit, &LLAppearanceMgr::instance(), selected_id)); + registrar.add("Outfit.TakeOff", + boost::bind(&LLAppearanceMgr::takeOffOutfit, &LLAppearanceMgr::instance(), selected_id)); + registrar.add("Outfit.Edit", boost::bind(editOutfit)); + registrar.add("Outfit.Rename", boost::bind(renameOutfit, selected_id)); + registrar.add("Outfit.Delete", boost::bind(&LLOutfitListBase::removeSelected, mOutfitList)); + registrar.add("Outfit.Thumbnail", boost::bind(&LLOutfitContextMenu::onThumbnail, this, selected_id)); + registrar.add("Outfit.Save", boost::bind(&LLOutfitContextMenu::onSave, this, selected_id)); + + enable_registrar.add("Outfit.OnEnable", boost::bind(&LLOutfitContextMenu::onEnable, this, _2)); + enable_registrar.add("Outfit.OnVisible", boost::bind(&LLOutfitContextMenu::onVisible, this, _2)); + + return createFromFile("menu_outfit_tab.xml"); + +} + +bool LLOutfitContextMenu::onEnable(LLSD::String param) +{ + LLUUID outfit_cat_id = mUUIDs.back(); + + if ("rename" == param) + { + return get_is_category_renameable(&gInventory, outfit_cat_id); + } + else if ("wear_replace" == param) + { + return LLAppearanceMgr::instance().getCanReplaceCOF(outfit_cat_id); + } + else if ("wear_add" == param) + { + return LLAppearanceMgr::getCanAddToCOF(outfit_cat_id); + } + else if ("take_off" == param) + { + return LLAppearanceMgr::getCanRemoveFromCOF(outfit_cat_id); + } + + return true; +} + +bool LLOutfitContextMenu::onVisible(LLSD::String param) +{ + LLUUID outfit_cat_id = mUUIDs.back(); + + if ("edit" == param) + { + bool is_worn = LLAppearanceMgr::instance().getBaseOutfitUUID() == outfit_cat_id; + return is_worn; + } + else if ("wear_replace" == param) + { + return true; + } + else if ("delete" == param) + { + return LLAppearanceMgr::instance().getCanRemoveOutfit(outfit_cat_id); + } + + return true; +} + +//static +void LLOutfitContextMenu::editOutfit() +{ + LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "edit_outfit")); +} + +void LLOutfitContextMenu::renameOutfit(const LLUUID& outfit_cat_id) +{ + LLAppearanceMgr::instance().renameOutfit(outfit_cat_id); +} + +void LLOutfitContextMenu::onThumbnail(const LLUUID &outfit_cat_id) +{ + if (outfit_cat_id.notNull()) + { + LLSD data(outfit_cat_id); + LLFloaterReg::showInstance("change_item_thumbnail", data); + } +} + +void LLOutfitContextMenu::onSave(const LLUUID &outfit_cat_id) +{ + if (outfit_cat_id.notNull()) + { + LLNotificationsUtil::add("ConfirmOverwriteOutfit", LLSD(), LLSD(), + [outfit_cat_id](const LLSD ¬if, const LLSD &resp) + { + S32 opt = LLNotificationsUtil::getSelectedOption(notif, resp); + if (opt == 0) + { + LLAppearanceMgr::getInstance()->onOutfitFolderCreated(outfit_cat_id, true); + } + }); + } +} + +LLOutfitListGearMenuBase::LLOutfitListGearMenuBase(LLOutfitListBase* olist) + : mOutfitList(olist), + mMenu(NULL) +{ + llassert_always(mOutfitList); + + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + + registrar.add("Gear.Wear", boost::bind(&LLOutfitListGearMenuBase::onWear, this)); + registrar.add("Gear.TakeOff", boost::bind(&LLOutfitListGearMenuBase::onTakeOff, this)); + registrar.add("Gear.Rename", boost::bind(&LLOutfitListGearMenuBase::onRename, this)); + registrar.add("Gear.Delete", boost::bind(&LLOutfitListBase::removeSelected, mOutfitList)); + registrar.add("Gear.Create", boost::bind(&LLOutfitListGearMenuBase::onCreate, this, _2)); + registrar.add("Gear.Collapse", boost::bind(&LLOutfitListBase::onCollapseAllFolders, mOutfitList)); + registrar.add("Gear.Expand", boost::bind(&LLOutfitListBase::onExpandAllFolders, mOutfitList)); + + registrar.add("Gear.WearAdd", boost::bind(&LLOutfitListGearMenuBase::onAdd, this)); + registrar.add("Gear.Save", boost::bind(&LLOutfitListGearMenuBase::onSave, this)); + + registrar.add("Gear.Thumbnail", boost::bind(&LLOutfitListGearMenuBase::onThumbnail, this)); + registrar.add("Gear.SortByName", boost::bind(&LLOutfitListGearMenuBase::onChangeSortOrder, this)); + + enable_registrar.add("Gear.OnEnable", boost::bind(&LLOutfitListGearMenuBase::onEnable, this, _2)); + enable_registrar.add("Gear.OnVisible", boost::bind(&LLOutfitListGearMenuBase::onVisible, this, _2)); + + mMenu = LLUICtrlFactory::getInstance()->createFromFile( + "menu_outfit_gear.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + llassert(mMenu); +} + +LLOutfitListGearMenuBase::~LLOutfitListGearMenuBase() +{} + +void LLOutfitListGearMenuBase::updateItemsVisibility() +{ + onUpdateItemsVisibility(); +} + +void LLOutfitListGearMenuBase::onUpdateItemsVisibility() +{ + if (!mMenu) return; + + bool have_selection = getSelectedOutfitID().notNull(); + mMenu->setItemVisible("wear_separator", have_selection); + mMenu->arrangeAndClear(); // update menu height +} + +LLToggleableMenu* LLOutfitListGearMenuBase::getMenu() +{ + return mMenu; +} +const LLUUID& LLOutfitListGearMenuBase::getSelectedOutfitID() +{ + return mOutfitList->getSelectedOutfitUUID(); +} + +LLViewerInventoryCategory* LLOutfitListGearMenuBase::getSelectedOutfit() +{ + const LLUUID& selected_outfit_id = getSelectedOutfitID(); + if (selected_outfit_id.isNull()) + { + return NULL; + } + + LLViewerInventoryCategory* cat = gInventory.getCategory(selected_outfit_id); + return cat; +} + +void LLOutfitListGearMenuBase::onWear() +{ + LLViewerInventoryCategory* selected_outfit = getSelectedOutfit(); + if (selected_outfit) + { + LLAppearanceMgr::instance().wearInventoryCategory( + selected_outfit, /*copy=*/ false, /*append=*/ false); + } +} + +void LLOutfitListGearMenuBase::onAdd() +{ + const LLUUID& selected_id = getSelectedOutfitID(); + + if (selected_id.notNull()) + { + LLAppearanceMgr::getInstance()->addCategoryToCurrentOutfit(selected_id); + } +} + +void LLOutfitListGearMenuBase::onSave() +{ + const LLUUID &selected_id = getSelectedOutfitID(); + LLNotificationsUtil::add("ConfirmOverwriteOutfit", LLSD(), LLSD(), + [selected_id](const LLSD ¬if, const LLSD &resp) + { + S32 opt = LLNotificationsUtil::getSelectedOption(notif, resp); + if (opt == 0) + { + LLAppearanceMgr::getInstance()->onOutfitFolderCreated(selected_id, true); + } + }); +} + +void LLOutfitListGearMenuBase::onTakeOff() +{ + // Take off selected outfit. + const LLUUID& selected_outfit_id = getSelectedOutfitID(); + if (selected_outfit_id.notNull()) + { + LLAppearanceMgr::instance().takeOffOutfit(selected_outfit_id); + } +} + +void LLOutfitListGearMenuBase::onRename() +{ + const LLUUID& selected_outfit_id = getSelectedOutfitID(); + if (selected_outfit_id.notNull()) + { + LLAppearanceMgr::instance().renameOutfit(selected_outfit_id); + } +} + +void LLOutfitListGearMenuBase::onCreate(const LLSD& data) +{ + LLWearableType::EType type = LLWearableType::getInstance()->typeNameToType(data.asString()); + if (type == LLWearableType::WT_NONE) + { + LL_WARNS() << "Invalid wearable type" << LL_ENDL; + return; + } + + LLAgentWearables::createWearable(type, true); +} + +bool LLOutfitListGearMenuBase::onEnable(LLSD::String param) +{ + // Handle the "Wear - Replace Current Outfit" menu option specially + // because LLOutfitList::isActionEnabled() checks whether it's allowed + // to wear selected outfit OR selected items, while we're only + // interested in the outfit (STORM-183). + if ("wear" == param) + { + return LLAppearanceMgr::instance().getCanReplaceCOF(mOutfitList->getSelectedOutfitUUID()); + } + + return mOutfitList->isActionEnabled(param); +} + +bool LLOutfitListGearMenuBase::onVisible(LLSD::String param) +{ + const LLUUID& selected_outfit_id = getSelectedOutfitID(); + if (selected_outfit_id.isNull()) // no selection or invalid outfit selected + { + return false; + } + + return true; +} + +void LLOutfitListGearMenuBase::onThumbnail() +{ + const LLUUID& selected_outfit_id = getSelectedOutfitID(); + LLSD data(selected_outfit_id); + LLFloaterReg::showInstance("change_item_thumbnail", data); +} + +void LLOutfitListGearMenuBase::onChangeSortOrder() +{ + +} + +LLOutfitListGearMenu::LLOutfitListGearMenu(LLOutfitListBase* olist) + : LLOutfitListGearMenuBase(olist) +{} + +LLOutfitListGearMenu::~LLOutfitListGearMenu() +{} + +void LLOutfitListGearMenu::onUpdateItemsVisibility() +{ + if (!mMenu) return; + mMenu->setItemVisible("expand", true); + mMenu->setItemVisible("collapse", true); + mMenu->setItemVisible("thumbnail", getSelectedOutfitID().notNull()); + mMenu->setItemVisible("sepatator3", false); + mMenu->setItemVisible("sort_folders_by_name", false); + LLOutfitListGearMenuBase::onUpdateItemsVisibility(); +} + +bool LLOutfitAccordionCtrlTab::handleToolTip(S32 x, S32 y, MASK mask) +{ + if (y >= getLocalRect().getHeight() - getHeaderHeight()) + { + LLSD params; + params["inv_type"] = LLInventoryType::IT_CATEGORY; + params["thumbnail_id"] = gInventory.getCategory(mFolderID)->getThumbnailUUID(); + params["item_id"] = mFolderID; + + LLToolTipMgr::instance().show(LLToolTip::Params() + .message(getToolTip()) + .sticky_rect(calcScreenRect()) + .delay_time(LLView::getTooltipTimeout()) + .create_callback(boost::bind(&LLInspectTextureUtil::createInventoryToolTip, _1)) + .create_params(params)); + return true; + } + + return LLAccordionCtrlTab::handleToolTip(x, y, mask); +} +// EOF diff --git a/indra/newview/lloutfitslist.h b/indra/newview/lloutfitslist.h index de6023eeab..f581b419d9 100644 --- a/indra/newview/lloutfitslist.h +++ b/indra/newview/lloutfitslist.h @@ -1,386 +1,386 @@ -/** - * @file lloutfitslist.h - * @brief List of agent's outfits for My Appearance side panel. - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLOUTFITSLIST_H -#define LL_LLOUTFITSLIST_H - -#include "llaccordionctrl.h" -#include "llpanel.h" - -// newview -#include "llaccordionctrltab.h" -#include "llinventorymodel.h" -#include "lllistcontextmenu.h" -#include "llpanelappearancetab.h" -#include "lltoggleablemenu.h" -#include "llviewermenu.h" - -class LLAccordionCtrlTab; -class LLInventoryCategoriesObserver; -class LLOutfitListGearMenuBase; -class LLWearableItemsList; -class LLListContextMenu; - - -/** - * @class LLOutfitTabNameComparator - * - * Comparator of outfit tabs. - */ -class LLOutfitTabNameComparator : public LLAccordionCtrl::LLTabComparator -{ - LOG_CLASS(LLOutfitTabNameComparator); - -public: - LLOutfitTabNameComparator() {}; - virtual ~LLOutfitTabNameComparator() {}; - - /*virtual*/ bool compare(const LLAccordionCtrlTab* tab1, const LLAccordionCtrlTab* tab2) const; -}; - -class LLOutfitListBase : public LLPanelAppearanceTab -{ -public: - typedef boost::function selection_change_callback_t; - typedef boost::signals2::signal selection_change_signal_t; - - LLOutfitListBase(); - virtual ~LLOutfitListBase(); - - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& info); - - void refreshList(const LLUUID& category_id); - void computeDifference(const LLInventoryModel::cat_array_t& vcats, uuid_vec_t& vadded, uuid_vec_t& vremoved); - // highlights currently worn outfit in list and unhighlights previously worn - void highlightBaseOutfit(); - void ChangeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id); - - - virtual void getCurrentCategories(uuid_vec_t& vcur) = 0; - virtual void updateAddedCategory(LLUUID cat_id) = 0; - virtual void updateRemovedCategory(LLUUID cat_id) = 0; - virtual void updateChangedCategoryName(LLViewerInventoryCategory *cat, std::string name) = 0; - virtual void sortOutfits(); - - void removeSelected(); - void setSelectedOutfitByUUID(const LLUUID& outfit_uuid); - const LLUUID& getSelectedOutfitUUID() const { return mSelectedOutfitUUID; } - boost::signals2::connection setSelectionChangeCallback(selection_change_callback_t cb); - void outfitRightClickCallBack(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& cat_id); - - virtual bool isActionEnabled(const LLSD& userdata); - virtual void performAction(std::string action); - virtual bool hasItemSelected() = 0; - virtual bool canWearSelected() = 0; - - virtual void deselectOutfit(const LLUUID& category_id); - - void signalSelectionOutfitUUID(const LLUUID& category_id); - - void collapseAllFolders(); - virtual void onCollapseAllFolders() = 0; - - void expandAllFolders(); - virtual void onExpandAllFolders() = 0; - - virtual bool getHasExpandableFolders() = 0; - -protected: - void observerCallback(const LLUUID& category_id); - virtual LLOutfitListGearMenuBase* createGearMenu() = 0; - virtual void onHighlightBaseOutfit(LLUUID base_id, LLUUID prev_id) = 0; - virtual void onSetSelectedOutfitByUUID(const LLUUID& outfit_uuid) = 0; - virtual void onOutfitRightClick(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& cat_id) = 0; - void onOutfitsRemovalConfirmation(const LLSD& notification, const LLSD& response); - virtual void onChangeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id) = 0; - - static void onIdle(void* userdata); - void onIdleRefreshList(); - - struct - { - LLUUID CategoryUUID; - uuid_vec_t Added; - uuid_vec_t Removed; - uuid_vec_t::const_iterator AddedIterator; - uuid_vec_t::const_iterator RemovedIterator; - } mRefreshListState; - std::set mChangedItems; - - bool mIsInitialized; - LLInventoryCategoriesObserver* mCategoriesObserver; - LLUUID mSelectedOutfitUUID; - // id of currently highlited outfit - LLUUID mHighlightedOutfitUUID; - selection_change_signal_t mSelectionChangeSignal; - LLListContextMenu* mOutfitMenu; - LLOutfitListGearMenuBase* mGearMenu; -}; - -////////////////////////////////////////////////////////////////////////// - -class LLOutfitContextMenu : public LLListContextMenu -{ -public: - - LLOutfitContextMenu(LLOutfitListBase* outfit_list) - : LLListContextMenu(), - mOutfitList(outfit_list) - {} -protected: - /* virtual */ LLContextMenu* createMenu(); - - bool onEnable(LLSD::String param); - - bool onVisible(LLSD::String param); - - static void editOutfit(); - - static void renameOutfit(const LLUUID& outfit_cat_id); - - void onThumbnail(const LLUUID &outfit_cat_id); - void onSave(const LLUUID &outfit_cat_id); - -private: - LLOutfitListBase* mOutfitList; -}; - -class LLOutfitListGearMenuBase -{ -public: - LLOutfitListGearMenuBase(LLOutfitListBase* olist); - virtual ~LLOutfitListGearMenuBase(); - - void updateItemsVisibility(); - - LLToggleableMenu* getMenu(); - -protected: - virtual void onUpdateItemsVisibility(); - virtual void onThumbnail(); - virtual void onChangeSortOrder(); - - const LLUUID& getSelectedOutfitID(); - - LLOutfitListBase* mOutfitList; - LLToggleableMenu* mMenu; -private: - - LLViewerInventoryCategory* getSelectedOutfit(); - - void onWear(); - void onAdd(); - void onTakeOff(); - void onRename(); - void onSave(); - void onCreate(const LLSD& data); - bool onEnable(LLSD::String param); - bool onVisible(LLSD::String param); -}; - -class LLOutfitListGearMenu : public LLOutfitListGearMenuBase -{ -public: - LLOutfitListGearMenu(LLOutfitListBase* olist); - virtual ~LLOutfitListGearMenu(); - -protected: - /*virtual*/ void onUpdateItemsVisibility(); -}; - -class LLOutfitAccordionCtrlTab : public LLAccordionCtrlTab -{ -public: - struct Params : public LLInitParam::Block - { - Optional cat_id; - Params() : cat_id("cat_id") {} - }; - - virtual bool handleToolTip(S32 x, S32 y, MASK mask); - - protected: - LLOutfitAccordionCtrlTab(const LLOutfitAccordionCtrlTab::Params &p) - : LLAccordionCtrlTab(p), - mFolderID(p.cat_id) - {} - friend class LLUICtrlFactory; - - LLUUID mFolderID; -}; - /** - * @class LLOutfitsList - * - * A list of agents's outfits from "My Outfits" inventory category - * which displays each outfit in an accordion tab with a flat list - * of items inside it. - * - * Starts fetching necessary inventory content on first opening. - */ -class LLOutfitsList : public LLOutfitListBase -{ -public: - - LLOutfitsList(); - virtual ~LLOutfitsList(); - - /*virtual*/ bool postBuild(); - - /*virtual*/ void onOpen(const LLSD& info); - - - //virtual void refreshList(const LLUUID& category_id); - - /*virtual*/ void updateAddedCategory(LLUUID cat_id); - /*virtual*/ void updateRemovedCategory(LLUUID cat_id); - - // highlits currently worn outfit tab text and unhighlights previously worn - /*virtual*/ void onHighlightBaseOutfit(LLUUID base_id, LLUUID prev_id); - - //void performAction(std::string action); - - - /*virtual*/ void onFilterSubStringChanged(const std::string& new_string, const std::string& old_string); - - /*virtual*/ void getSelectedItemsUUIDs(uuid_vec_t& selected_uuids) const; - - // Collects selected items from all selected lists and wears them(if possible- adds, else replaces) - void wearSelectedItems(); - - /** - * Returns true if there is a selection inside currently selected outfit - */ - /*virtual*/ bool hasItemSelected(); - - /** - Collapses all outfit accordions. - */ - /*virtual*/ void onCollapseAllFolders(); - /** - Expands all outfit accordions. - */ - void onExpandAllFolders(); - - /*virtual*/ bool getHasExpandableFolders() { return true; } - -protected: - LLOutfitListGearMenuBase* createGearMenu(); - -private: - - /** - * Wrapper for LLCommonUtils::computeDifference. @see LLCommonUtils::computeDifference - */ - //void computeDifference(const LLInventoryModel::cat_array_t& vcats, uuid_vec_t& vadded, uuid_vec_t& vremoved); - - void getCurrentCategories(uuid_vec_t& vcur); - - /** - * Updates tab displaying outfit identified by category_id. - */ - /*virtual*/ void updateChangedCategoryName(LLViewerInventoryCategory *cat, std::string name); - - /*virtual*/ void sortOutfits(); - - /*virtual*/ void onSetSelectedOutfitByUUID(const LLUUID& outfit_uuid); - - /** - * Resets previous selection and stores newly selected list and outfit id. - */ - /*virtual*/ void onChangeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id); - - /** - *Resets items selection inside outfit - */ - void resetItemSelection(LLWearableItemsList* list, const LLUUID& category_id); - - /** - * Removes the outfit from selection. - */ - /*virtual*/ void deselectOutfit(const LLUUID& category_id); - - /** - * Try restoring selection for a temporary hidden tab. - * - * A tab may be hidden if it doesn't match current filter. - */ - void restoreOutfitSelection(LLAccordionCtrlTab* tab, const LLUUID& category_id); - - /** - * Called upon list refresh event to update tab visibility depending on - * the results of applying filter to the title and list items of the tab. - */ - void onRefreshComplete(LLUICtrl* ctrl); - - /** - * Applies filter to the given tab - * - * @see applyFilter() - */ - void applyFilterToTab(const LLUUID& category_id, LLAccordionCtrlTab* tab, const std::string& filter_substring); - - /** - * Returns true if all selected items can be worn. - */ - bool canWearSelected(); - - void onWearableItemsListRightClick(LLUICtrl* ctrl, S32 x, S32 y); - void onCOFChanged(); - - void onListSelectionChange(LLUICtrl* ctrl); - - /*virtual*/ void onOutfitRightClick(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& cat_id); - - static void onOutfitRename(const LLSD& notification, const LLSD& response); - - //LLInventoryCategoriesObserver* mCategoriesObserver; - - LLAccordionCtrl* mAccordion; - LLPanel* mListCommands; - - typedef std::map wearables_lists_map_t; - typedef wearables_lists_map_t::value_type wearables_lists_map_value_t; - wearables_lists_map_t mSelectedListsMap; - - typedef std::map outfits_map_t; - typedef outfits_map_t::value_type outfits_map_value_t; - outfits_map_t mOutfitsMap; - - // IDs of original items which are worn and linked in COF. - // Used to monitor COF changes for updating items worn state. See EXT-8636. - uuid_vec_t mCOFLinkedItems; - - //LLOutfitListGearMenu* mGearMenu; - - //bool mIsInitialized; - /** - * True if there is a selection inside currently selected outfit - */ - bool mItemSelected; -}; - -#endif //LL_LLOUTFITSLIST_H +/** + * @file lloutfitslist.h + * @brief List of agent's outfits for My Appearance side panel. + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLOUTFITSLIST_H +#define LL_LLOUTFITSLIST_H + +#include "llaccordionctrl.h" +#include "llpanel.h" + +// newview +#include "llaccordionctrltab.h" +#include "llinventorymodel.h" +#include "lllistcontextmenu.h" +#include "llpanelappearancetab.h" +#include "lltoggleablemenu.h" +#include "llviewermenu.h" + +class LLAccordionCtrlTab; +class LLInventoryCategoriesObserver; +class LLOutfitListGearMenuBase; +class LLWearableItemsList; +class LLListContextMenu; + + +/** + * @class LLOutfitTabNameComparator + * + * Comparator of outfit tabs. + */ +class LLOutfitTabNameComparator : public LLAccordionCtrl::LLTabComparator +{ + LOG_CLASS(LLOutfitTabNameComparator); + +public: + LLOutfitTabNameComparator() {}; + virtual ~LLOutfitTabNameComparator() {}; + + /*virtual*/ bool compare(const LLAccordionCtrlTab* tab1, const LLAccordionCtrlTab* tab2) const; +}; + +class LLOutfitListBase : public LLPanelAppearanceTab +{ +public: + typedef boost::function selection_change_callback_t; + typedef boost::signals2::signal selection_change_signal_t; + + LLOutfitListBase(); + virtual ~LLOutfitListBase(); + + /*virtual*/ bool postBuild(); + /*virtual*/ void onOpen(const LLSD& info); + + void refreshList(const LLUUID& category_id); + void computeDifference(const LLInventoryModel::cat_array_t& vcats, uuid_vec_t& vadded, uuid_vec_t& vremoved); + // highlights currently worn outfit in list and unhighlights previously worn + void highlightBaseOutfit(); + void ChangeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id); + + + virtual void getCurrentCategories(uuid_vec_t& vcur) = 0; + virtual void updateAddedCategory(LLUUID cat_id) = 0; + virtual void updateRemovedCategory(LLUUID cat_id) = 0; + virtual void updateChangedCategoryName(LLViewerInventoryCategory *cat, std::string name) = 0; + virtual void sortOutfits(); + + void removeSelected(); + void setSelectedOutfitByUUID(const LLUUID& outfit_uuid); + const LLUUID& getSelectedOutfitUUID() const { return mSelectedOutfitUUID; } + boost::signals2::connection setSelectionChangeCallback(selection_change_callback_t cb); + void outfitRightClickCallBack(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& cat_id); + + virtual bool isActionEnabled(const LLSD& userdata); + virtual void performAction(std::string action); + virtual bool hasItemSelected() = 0; + virtual bool canWearSelected() = 0; + + virtual void deselectOutfit(const LLUUID& category_id); + + void signalSelectionOutfitUUID(const LLUUID& category_id); + + void collapseAllFolders(); + virtual void onCollapseAllFolders() = 0; + + void expandAllFolders(); + virtual void onExpandAllFolders() = 0; + + virtual bool getHasExpandableFolders() = 0; + +protected: + void observerCallback(const LLUUID& category_id); + virtual LLOutfitListGearMenuBase* createGearMenu() = 0; + virtual void onHighlightBaseOutfit(LLUUID base_id, LLUUID prev_id) = 0; + virtual void onSetSelectedOutfitByUUID(const LLUUID& outfit_uuid) = 0; + virtual void onOutfitRightClick(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& cat_id) = 0; + void onOutfitsRemovalConfirmation(const LLSD& notification, const LLSD& response); + virtual void onChangeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id) = 0; + + static void onIdle(void* userdata); + void onIdleRefreshList(); + + struct + { + LLUUID CategoryUUID; + uuid_vec_t Added; + uuid_vec_t Removed; + uuid_vec_t::const_iterator AddedIterator; + uuid_vec_t::const_iterator RemovedIterator; + } mRefreshListState; + std::set mChangedItems; + + bool mIsInitialized; + LLInventoryCategoriesObserver* mCategoriesObserver; + LLUUID mSelectedOutfitUUID; + // id of currently highlited outfit + LLUUID mHighlightedOutfitUUID; + selection_change_signal_t mSelectionChangeSignal; + LLListContextMenu* mOutfitMenu; + LLOutfitListGearMenuBase* mGearMenu; +}; + +////////////////////////////////////////////////////////////////////////// + +class LLOutfitContextMenu : public LLListContextMenu +{ +public: + + LLOutfitContextMenu(LLOutfitListBase* outfit_list) + : LLListContextMenu(), + mOutfitList(outfit_list) + {} +protected: + /* virtual */ LLContextMenu* createMenu(); + + bool onEnable(LLSD::String param); + + bool onVisible(LLSD::String param); + + static void editOutfit(); + + static void renameOutfit(const LLUUID& outfit_cat_id); + + void onThumbnail(const LLUUID &outfit_cat_id); + void onSave(const LLUUID &outfit_cat_id); + +private: + LLOutfitListBase* mOutfitList; +}; + +class LLOutfitListGearMenuBase +{ +public: + LLOutfitListGearMenuBase(LLOutfitListBase* olist); + virtual ~LLOutfitListGearMenuBase(); + + void updateItemsVisibility(); + + LLToggleableMenu* getMenu(); + +protected: + virtual void onUpdateItemsVisibility(); + virtual void onThumbnail(); + virtual void onChangeSortOrder(); + + const LLUUID& getSelectedOutfitID(); + + LLOutfitListBase* mOutfitList; + LLToggleableMenu* mMenu; +private: + + LLViewerInventoryCategory* getSelectedOutfit(); + + void onWear(); + void onAdd(); + void onTakeOff(); + void onRename(); + void onSave(); + void onCreate(const LLSD& data); + bool onEnable(LLSD::String param); + bool onVisible(LLSD::String param); +}; + +class LLOutfitListGearMenu : public LLOutfitListGearMenuBase +{ +public: + LLOutfitListGearMenu(LLOutfitListBase* olist); + virtual ~LLOutfitListGearMenu(); + +protected: + /*virtual*/ void onUpdateItemsVisibility(); +}; + +class LLOutfitAccordionCtrlTab : public LLAccordionCtrlTab +{ +public: + struct Params : public LLInitParam::Block + { + Optional cat_id; + Params() : cat_id("cat_id") {} + }; + + virtual bool handleToolTip(S32 x, S32 y, MASK mask); + + protected: + LLOutfitAccordionCtrlTab(const LLOutfitAccordionCtrlTab::Params &p) + : LLAccordionCtrlTab(p), + mFolderID(p.cat_id) + {} + friend class LLUICtrlFactory; + + LLUUID mFolderID; +}; + /** + * @class LLOutfitsList + * + * A list of agents's outfits from "My Outfits" inventory category + * which displays each outfit in an accordion tab with a flat list + * of items inside it. + * + * Starts fetching necessary inventory content on first opening. + */ +class LLOutfitsList : public LLOutfitListBase +{ +public: + + LLOutfitsList(); + virtual ~LLOutfitsList(); + + /*virtual*/ bool postBuild(); + + /*virtual*/ void onOpen(const LLSD& info); + + + //virtual void refreshList(const LLUUID& category_id); + + /*virtual*/ void updateAddedCategory(LLUUID cat_id); + /*virtual*/ void updateRemovedCategory(LLUUID cat_id); + + // highlits currently worn outfit tab text and unhighlights previously worn + /*virtual*/ void onHighlightBaseOutfit(LLUUID base_id, LLUUID prev_id); + + //void performAction(std::string action); + + + /*virtual*/ void onFilterSubStringChanged(const std::string& new_string, const std::string& old_string); + + /*virtual*/ void getSelectedItemsUUIDs(uuid_vec_t& selected_uuids) const; + + // Collects selected items from all selected lists and wears them(if possible- adds, else replaces) + void wearSelectedItems(); + + /** + * Returns true if there is a selection inside currently selected outfit + */ + /*virtual*/ bool hasItemSelected(); + + /** + Collapses all outfit accordions. + */ + /*virtual*/ void onCollapseAllFolders(); + /** + Expands all outfit accordions. + */ + void onExpandAllFolders(); + + /*virtual*/ bool getHasExpandableFolders() { return true; } + +protected: + LLOutfitListGearMenuBase* createGearMenu(); + +private: + + /** + * Wrapper for LLCommonUtils::computeDifference. @see LLCommonUtils::computeDifference + */ + //void computeDifference(const LLInventoryModel::cat_array_t& vcats, uuid_vec_t& vadded, uuid_vec_t& vremoved); + + void getCurrentCategories(uuid_vec_t& vcur); + + /** + * Updates tab displaying outfit identified by category_id. + */ + /*virtual*/ void updateChangedCategoryName(LLViewerInventoryCategory *cat, std::string name); + + /*virtual*/ void sortOutfits(); + + /*virtual*/ void onSetSelectedOutfitByUUID(const LLUUID& outfit_uuid); + + /** + * Resets previous selection and stores newly selected list and outfit id. + */ + /*virtual*/ void onChangeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id); + + /** + *Resets items selection inside outfit + */ + void resetItemSelection(LLWearableItemsList* list, const LLUUID& category_id); + + /** + * Removes the outfit from selection. + */ + /*virtual*/ void deselectOutfit(const LLUUID& category_id); + + /** + * Try restoring selection for a temporary hidden tab. + * + * A tab may be hidden if it doesn't match current filter. + */ + void restoreOutfitSelection(LLAccordionCtrlTab* tab, const LLUUID& category_id); + + /** + * Called upon list refresh event to update tab visibility depending on + * the results of applying filter to the title and list items of the tab. + */ + void onRefreshComplete(LLUICtrl* ctrl); + + /** + * Applies filter to the given tab + * + * @see applyFilter() + */ + void applyFilterToTab(const LLUUID& category_id, LLAccordionCtrlTab* tab, const std::string& filter_substring); + + /** + * Returns true if all selected items can be worn. + */ + bool canWearSelected(); + + void onWearableItemsListRightClick(LLUICtrl* ctrl, S32 x, S32 y); + void onCOFChanged(); + + void onListSelectionChange(LLUICtrl* ctrl); + + /*virtual*/ void onOutfitRightClick(LLUICtrl* ctrl, S32 x, S32 y, const LLUUID& cat_id); + + static void onOutfitRename(const LLSD& notification, const LLSD& response); + + //LLInventoryCategoriesObserver* mCategoriesObserver; + + LLAccordionCtrl* mAccordion; + LLPanel* mListCommands; + + typedef std::map wearables_lists_map_t; + typedef wearables_lists_map_t::value_type wearables_lists_map_value_t; + wearables_lists_map_t mSelectedListsMap; + + typedef std::map outfits_map_t; + typedef outfits_map_t::value_type outfits_map_value_t; + outfits_map_t mOutfitsMap; + + // IDs of original items which are worn and linked in COF. + // Used to monitor COF changes for updating items worn state. See EXT-8636. + uuid_vec_t mCOFLinkedItems; + + //LLOutfitListGearMenu* mGearMenu; + + //bool mIsInitialized; + /** + * True if there is a selection inside currently selected outfit + */ + bool mItemSelected; +}; + +#endif //LL_LLOUTFITSLIST_H diff --git a/indra/newview/lloutputmonitorctrl.cpp b/indra/newview/lloutputmonitorctrl.cpp index c8b6fd5871..e6421d8a41 100644 --- a/indra/newview/lloutputmonitorctrl.cpp +++ b/indra/newview/lloutputmonitorctrl.cpp @@ -1,363 +1,363 @@ -/** - * @file lloutputmonitorctrl.cpp - * @brief LLOutputMonitorCtrl base class - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "lloutputmonitorctrl.h" - -// library includes -#include "llfloaterreg.h" -#include "llui.h" - -// viewer includes -#include "llvoiceclient.h" -#include "llmutelist.h" -#include "llagent.h" - -// default options set in output_monitor.xml -static LLDefaultChildRegistry::Register r("output_monitor"); - -// The defaults will be initialized in the constructor. -//LLColor4 LLOutputMonitorCtrl::sColorMuted; -//LLColor4 LLOutputMonitorCtrl::sColorOverdriven; -//LLColor4 LLOutputMonitorCtrl::sColorNormal; -LLColor4 LLOutputMonitorCtrl::sColorBound; -//S32 LLOutputMonitorCtrl::sRectsNumber = 0; -//F32 LLOutputMonitorCtrl::sRectWidthRatio = 0.f; -//F32 LLOutputMonitorCtrl::sRectHeightRatio = 0.f; - -LLOutputMonitorCtrl::Params::Params() -: draw_border("draw_border"), - image_mute("image_mute"), - image_off("image_off"), - image_on("image_on"), - image_level_1("image_level_1"), - image_level_2("image_level_2"), - image_level_3("image_level_3"), - auto_update("auto_update"), - speaker_id("speaker_id") -{ -}; - -LLOutputMonitorCtrl::LLOutputMonitorCtrl(const LLOutputMonitorCtrl::Params& p) -: LLView(p), - mPower(0), - mImageMute(p.image_mute), - mImageOff(p.image_off), - mImageOn(p.image_on), - mImageLevel1(p.image_level_1), - mImageLevel2(p.image_level_2), - mImageLevel3(p.image_level_3), - mAutoUpdate(p.auto_update), - mSpeakerId(p.speaker_id), - mIsModeratorMuted(false), - mIsAgentControl(false), - mIndicatorToggled(false), - mShowParticipantsSpeaking(false), - mChannelState(INACTIVE_CHANNEL) -{ - //static LLUIColor output_monitor_muted_color = LLUIColorTable::instance().getColor("OutputMonitorMutedColor", LLColor4::orange); - //static LLUIColor output_monitor_overdriven_color = LLUIColorTable::instance().getColor("OutputMonitorOverdrivenColor", LLColor4::red); - //static LLUIColor output_monitor_normal_color = LLUIColorTable::instance().getColor("OutputMonitorNotmalColor", LLColor4::green); - static LLUIColor output_monitor_bound_color = LLUIColorTable::instance().getColor("OutputMonitorBoundColor", LLColor4::white); - //static LLUICachedControl output_monitor_rects_number("OutputMonitorRectanglesNumber", 20); - //static LLUICachedControl output_monitor_rect_width_ratio("OutputMonitorRectangleWidthRatio", 0.5f); - //static LLUICachedControl output_monitor_rect_height_ratio("OutputMonitorRectangleHeightRatio", 0.8f); - - // IAN BUG compare to existing pattern where these are members - some will change per-widget and need to be anyway - // sent feedback to PE - - // *TODO: it looks suboptimal to load the defaults every time an output monitor is constructed. - //sColorMuted = output_monitor_muted_color; - //sColorOverdriven = output_monitor_overdriven_color; - //sColorNormal = output_monitor_normal_color; - sColorBound = output_monitor_bound_color; - //sRectsNumber = output_monitor_rects_number; - //sRectWidthRatio = output_monitor_rect_width_ratio; - //sRectHeightRatio = output_monitor_rect_height_ratio; - - mBorder = p.draw_border; - - //with checking mute state - setSpeakerId(mSpeakerId); -} - -LLOutputMonitorCtrl::~LLOutputMonitorCtrl() -{ - LLMuteList::getInstance()->removeObserver(this); - LLSpeakingIndicatorManager::unregisterSpeakingIndicator(mSpeakerId, this); -} - -void LLOutputMonitorCtrl::setPower(F32 val) -{ - mPower = llmax(0.f, llmin(1.f, val)); -} - -void LLOutputMonitorCtrl::draw() -{ - // Copied from llmediaremotectrl.cpp - // *TODO: Give the LLOutputMonitorCtrl an agent-id to monitor, then - // call directly into LLVoiceClient::getInstance() to ask if that agent-id is muted, is - // speaking, and what power. This avoids duplicating data, which can get - // out of sync. - const F32 LEVEL_0 = LLVoiceClient::OVERDRIVEN_POWER_LEVEL / 3.f; - const F32 LEVEL_1 = LLVoiceClient::OVERDRIVEN_POWER_LEVEL * 2.f / 3.f; - const F32 LEVEL_2 = LLVoiceClient::OVERDRIVEN_POWER_LEVEL; - - if (getVisible() && mAutoUpdate && !getIsMuted() && mSpeakerId.notNull()) - { - setPower(LLVoiceClient::getInstance()->getCurrentPower(mSpeakerId)); - if(mIsAgentControl) - { - setIsTalking(LLVoiceClient::getInstance()->getUserPTTState()); - } - else - { - setIsTalking(LLVoiceClient::getInstance()->getIsSpeaking(mSpeakerId)); - } - } - - if ((mPower == 0.f && !mIsTalking) && mShowParticipantsSpeaking) - { - std::set participant_uuids; - LLVoiceClient::instance().getParticipantList(participant_uuids); - std::set::const_iterator part_it = participant_uuids.begin(); - - F32 power = 0; - for (; part_it != participant_uuids.end(); ++part_it) - { - power = LLVoiceClient::instance().getCurrentPower(*part_it); - if (power) - { - mPower = power; - break; - } - } - } - - LLPointer icon; - if (getIsMuted()) - { - icon = mImageMute; - } - else if (mPower == 0.f && !mIsTalking) - { - // only show off if PTT is not engaged - icon = mImageOff; - } - else if (mPower < LEVEL_0) - { - // PTT is on, possibly with quiet background noise - icon = mImageOn; - } - else if (mPower < LEVEL_1) - { - icon = mImageLevel1; - } - else if (mPower < LEVEL_2) - { - icon = mImageLevel2; - } - else - { - // overdriven - icon = mImageLevel3; - } - - if (icon) - { - icon->draw(0, 0); - } - - // - // Fill the monitor with a bunch of small rectangles. - // The rectangles will be filled with gradient color, - // beginning with sColorNormal and ending with sColorOverdriven. - // - // *TODO: would using a (partially drawn) pixmap instead be faster? - // - const int monh = getRect().getHeight(); - const int monw = getRect().getWidth(); - //int maxrects = sRectsNumber; - //const int period = llmax(1, monw / maxrects, 0, 0); // "1" - min value for the period - //const int rectw = llmax(1, llfloor(period * sRectWidthRatio), 0, 0); // "1" - min value for the rect's width - //const int recth = llfloor(monh * sRectHeightRatio); - - //if(period == 1 && rectw == 1) //if we have so small control, then "maxrects = monitor's_width - 2*monitor_border's_width - // maxrects = monw-2; - - //const int nrects = mIsMuted ? maxrects : llfloor(mPower * maxrects); // how many rects to draw? - //const int rectbtm = (monh - recth) / 2; - //const int recttop = rectbtm + recth; - // - //LLColor4 rect_color; - // - //for (int i=1, xpos = 0; i <= nrects; i++) - //{ - // // Calculate color to use for the current rectangle. - // if (mIsMuted) - // { - // rect_color = sColorMuted; - // } - // else - // { - // F32 frac = (mPower * i/nrects) / LLVoiceClient::OVERDRIVEN_POWER_LEVEL; - // // Use overdriven color if the power exceeds overdriven level. - // if (frac > 1.0f) - // frac = 1.0f; - // rect_color = lerp(sColorNormal, sColorOverdriven, frac); - // } - - // // Draw rectangle filled with the color. - // gl_rect_2d(xpos, recttop, xpos+rectw, rectbtm, rect_color, true); - // xpos += period; - //} - - // - // Draw bounding box. - // - if(mBorder) - gl_rect_2d(0, monh, monw, 0, sColorBound, false); -} - -// virtual -bool LLOutputMonitorCtrl::handleMouseUp(S32 x, S32 y, MASK mask) -{ - if (mSpeakerId != gAgentID) - { - LLFloaterReg::showInstance("floater_voice_volume", LLSD().with("avatar_id", mSpeakerId)); - } - else if (mShowParticipantsSpeaking) - { - LLFloaterReg::showInstance("chat_voice", LLSD()); - } - - return true; -} - -void LLOutputMonitorCtrl::setIsActiveChannel(bool val) -{ - setChannelState(val ? ACTIVE_CHANNEL : INACTIVE_CHANNEL); -} - -void LLOutputMonitorCtrl::setChannelState(EChannelState state) -{ - mChannelState = state; - if (state == INACTIVE_CHANNEL) - { - // switchIndicator will set it to true when channel becomes active - setVisible(false); - } -} - -void LLOutputMonitorCtrl::setSpeakerId(const LLUUID& speaker_id, const LLUUID& session_id/* = LLUUID::null*/, bool show_other_participants_speaking /* = false */) -{ - if (speaker_id.isNull() && mSpeakerId.notNull()) - { - LLSpeakingIndicatorManager::unregisterSpeakingIndicator(mSpeakerId, this); - switchIndicator(false); - mSpeakerId = speaker_id; - } - - if (speaker_id.isNull() || (speaker_id == mSpeakerId)) - { - return; - } - - if (mSpeakerId.notNull()) - { - // Unregister previous registration to avoid crash. EXT-4782. - LLSpeakingIndicatorManager::unregisterSpeakingIndicator(mSpeakerId, this); - } - - mShowParticipantsSpeaking = show_other_participants_speaking; - mSpeakerId = speaker_id; - LLSpeakingIndicatorManager::registerSpeakingIndicator(mSpeakerId, this, session_id); - - //mute management - if (mAutoUpdate) - { - if (speaker_id == gAgentID) - { - mIsMuted = false; - } - else - { - // check only blocking on voice. EXT-3542 - mIsMuted = LLMuteList::getInstance()->isMuted(mSpeakerId, LLMute::flagVoiceChat); - LLMuteList::getInstance()->addObserver(this); - } - } -} - -void LLOutputMonitorCtrl::onChangeDetailed(const LLMute& mute) -{ - if (mute.mID == mSpeakerId) - { - // Check only blocking on voice. - // Logic goes in reverse, if flag is set, action is allowed - mIsMuted = !(LLMute::flagVoiceChat & mute.mFlags); - } -} - -// virtual -void LLOutputMonitorCtrl::switchIndicator(bool switch_on) -{ - if ((mChannelState != INACTIVE_CHANNEL) && (getVisible() != (bool)switch_on)) - { - setVisible(switch_on); - - //Let parent adjust positioning of icons adjacent to speaker indicator - //(when speaker indicator hidden, adjacent icons move to right and when speaker - //indicator visible, adjacent icons move to the left) - if (getParent() && getParent()->isInVisibleChain()) - { - notifyParentVisibilityChanged(); - //Ignore toggled state in case it was set when parent visibility was hidden - mIndicatorToggled = false; - } - else - { - //Makes sure to only adjust adjacent icons when parent becomes visible - //(!mIndicatorToggled ensures that changes of TFT and FTF are discarded, real state changes are TF or FT) - mIndicatorToggled = !mIndicatorToggled; - } - - } -} - -////////////////////////////////////////////////////////////////////////// -// PRIVATE SECTION -////////////////////////////////////////////////////////////////////////// -void LLOutputMonitorCtrl::notifyParentVisibilityChanged() -{ - LL_DEBUGS("SpeakingIndicator") << "Notify parent that visibility was changed: " << mSpeakerId << ", new_visibility: " << getVisible() << LL_ENDL; - - LLSD params = LLSD().with("visibility_changed", getVisible()); - - notifyParent(params); -} - -// EOF +/** + * @file lloutputmonitorctrl.cpp + * @brief LLOutputMonitorCtrl base class + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "lloutputmonitorctrl.h" + +// library includes +#include "llfloaterreg.h" +#include "llui.h" + +// viewer includes +#include "llvoiceclient.h" +#include "llmutelist.h" +#include "llagent.h" + +// default options set in output_monitor.xml +static LLDefaultChildRegistry::Register r("output_monitor"); + +// The defaults will be initialized in the constructor. +//LLColor4 LLOutputMonitorCtrl::sColorMuted; +//LLColor4 LLOutputMonitorCtrl::sColorOverdriven; +//LLColor4 LLOutputMonitorCtrl::sColorNormal; +LLColor4 LLOutputMonitorCtrl::sColorBound; +//S32 LLOutputMonitorCtrl::sRectsNumber = 0; +//F32 LLOutputMonitorCtrl::sRectWidthRatio = 0.f; +//F32 LLOutputMonitorCtrl::sRectHeightRatio = 0.f; + +LLOutputMonitorCtrl::Params::Params() +: draw_border("draw_border"), + image_mute("image_mute"), + image_off("image_off"), + image_on("image_on"), + image_level_1("image_level_1"), + image_level_2("image_level_2"), + image_level_3("image_level_3"), + auto_update("auto_update"), + speaker_id("speaker_id") +{ +}; + +LLOutputMonitorCtrl::LLOutputMonitorCtrl(const LLOutputMonitorCtrl::Params& p) +: LLView(p), + mPower(0), + mImageMute(p.image_mute), + mImageOff(p.image_off), + mImageOn(p.image_on), + mImageLevel1(p.image_level_1), + mImageLevel2(p.image_level_2), + mImageLevel3(p.image_level_3), + mAutoUpdate(p.auto_update), + mSpeakerId(p.speaker_id), + mIsModeratorMuted(false), + mIsAgentControl(false), + mIndicatorToggled(false), + mShowParticipantsSpeaking(false), + mChannelState(INACTIVE_CHANNEL) +{ + //static LLUIColor output_monitor_muted_color = LLUIColorTable::instance().getColor("OutputMonitorMutedColor", LLColor4::orange); + //static LLUIColor output_monitor_overdriven_color = LLUIColorTable::instance().getColor("OutputMonitorOverdrivenColor", LLColor4::red); + //static LLUIColor output_monitor_normal_color = LLUIColorTable::instance().getColor("OutputMonitorNotmalColor", LLColor4::green); + static LLUIColor output_monitor_bound_color = LLUIColorTable::instance().getColor("OutputMonitorBoundColor", LLColor4::white); + //static LLUICachedControl output_monitor_rects_number("OutputMonitorRectanglesNumber", 20); + //static LLUICachedControl output_monitor_rect_width_ratio("OutputMonitorRectangleWidthRatio", 0.5f); + //static LLUICachedControl output_monitor_rect_height_ratio("OutputMonitorRectangleHeightRatio", 0.8f); + + // IAN BUG compare to existing pattern where these are members - some will change per-widget and need to be anyway + // sent feedback to PE + + // *TODO: it looks suboptimal to load the defaults every time an output monitor is constructed. + //sColorMuted = output_monitor_muted_color; + //sColorOverdriven = output_monitor_overdriven_color; + //sColorNormal = output_monitor_normal_color; + sColorBound = output_monitor_bound_color; + //sRectsNumber = output_monitor_rects_number; + //sRectWidthRatio = output_monitor_rect_width_ratio; + //sRectHeightRatio = output_monitor_rect_height_ratio; + + mBorder = p.draw_border; + + //with checking mute state + setSpeakerId(mSpeakerId); +} + +LLOutputMonitorCtrl::~LLOutputMonitorCtrl() +{ + LLMuteList::getInstance()->removeObserver(this); + LLSpeakingIndicatorManager::unregisterSpeakingIndicator(mSpeakerId, this); +} + +void LLOutputMonitorCtrl::setPower(F32 val) +{ + mPower = llmax(0.f, llmin(1.f, val)); +} + +void LLOutputMonitorCtrl::draw() +{ + // Copied from llmediaremotectrl.cpp + // *TODO: Give the LLOutputMonitorCtrl an agent-id to monitor, then + // call directly into LLVoiceClient::getInstance() to ask if that agent-id is muted, is + // speaking, and what power. This avoids duplicating data, which can get + // out of sync. + const F32 LEVEL_0 = LLVoiceClient::OVERDRIVEN_POWER_LEVEL / 3.f; + const F32 LEVEL_1 = LLVoiceClient::OVERDRIVEN_POWER_LEVEL * 2.f / 3.f; + const F32 LEVEL_2 = LLVoiceClient::OVERDRIVEN_POWER_LEVEL; + + if (getVisible() && mAutoUpdate && !getIsMuted() && mSpeakerId.notNull()) + { + setPower(LLVoiceClient::getInstance()->getCurrentPower(mSpeakerId)); + if(mIsAgentControl) + { + setIsTalking(LLVoiceClient::getInstance()->getUserPTTState()); + } + else + { + setIsTalking(LLVoiceClient::getInstance()->getIsSpeaking(mSpeakerId)); + } + } + + if ((mPower == 0.f && !mIsTalking) && mShowParticipantsSpeaking) + { + std::set participant_uuids; + LLVoiceClient::instance().getParticipantList(participant_uuids); + std::set::const_iterator part_it = participant_uuids.begin(); + + F32 power = 0; + for (; part_it != participant_uuids.end(); ++part_it) + { + power = LLVoiceClient::instance().getCurrentPower(*part_it); + if (power) + { + mPower = power; + break; + } + } + } + + LLPointer icon; + if (getIsMuted()) + { + icon = mImageMute; + } + else if (mPower == 0.f && !mIsTalking) + { + // only show off if PTT is not engaged + icon = mImageOff; + } + else if (mPower < LEVEL_0) + { + // PTT is on, possibly with quiet background noise + icon = mImageOn; + } + else if (mPower < LEVEL_1) + { + icon = mImageLevel1; + } + else if (mPower < LEVEL_2) + { + icon = mImageLevel2; + } + else + { + // overdriven + icon = mImageLevel3; + } + + if (icon) + { + icon->draw(0, 0); + } + + // + // Fill the monitor with a bunch of small rectangles. + // The rectangles will be filled with gradient color, + // beginning with sColorNormal and ending with sColorOverdriven. + // + // *TODO: would using a (partially drawn) pixmap instead be faster? + // + const int monh = getRect().getHeight(); + const int monw = getRect().getWidth(); + //int maxrects = sRectsNumber; + //const int period = llmax(1, monw / maxrects, 0, 0); // "1" - min value for the period + //const int rectw = llmax(1, llfloor(period * sRectWidthRatio), 0, 0); // "1" - min value for the rect's width + //const int recth = llfloor(monh * sRectHeightRatio); + + //if(period == 1 && rectw == 1) //if we have so small control, then "maxrects = monitor's_width - 2*monitor_border's_width + // maxrects = monw-2; + + //const int nrects = mIsMuted ? maxrects : llfloor(mPower * maxrects); // how many rects to draw? + //const int rectbtm = (monh - recth) / 2; + //const int recttop = rectbtm + recth; + // + //LLColor4 rect_color; + // + //for (int i=1, xpos = 0; i <= nrects; i++) + //{ + // // Calculate color to use for the current rectangle. + // if (mIsMuted) + // { + // rect_color = sColorMuted; + // } + // else + // { + // F32 frac = (mPower * i/nrects) / LLVoiceClient::OVERDRIVEN_POWER_LEVEL; + // // Use overdriven color if the power exceeds overdriven level. + // if (frac > 1.0f) + // frac = 1.0f; + // rect_color = lerp(sColorNormal, sColorOverdriven, frac); + // } + + // // Draw rectangle filled with the color. + // gl_rect_2d(xpos, recttop, xpos+rectw, rectbtm, rect_color, true); + // xpos += period; + //} + + // + // Draw bounding box. + // + if(mBorder) + gl_rect_2d(0, monh, monw, 0, sColorBound, false); +} + +// virtual +bool LLOutputMonitorCtrl::handleMouseUp(S32 x, S32 y, MASK mask) +{ + if (mSpeakerId != gAgentID) + { + LLFloaterReg::showInstance("floater_voice_volume", LLSD().with("avatar_id", mSpeakerId)); + } + else if (mShowParticipantsSpeaking) + { + LLFloaterReg::showInstance("chat_voice", LLSD()); + } + + return true; +} + +void LLOutputMonitorCtrl::setIsActiveChannel(bool val) +{ + setChannelState(val ? ACTIVE_CHANNEL : INACTIVE_CHANNEL); +} + +void LLOutputMonitorCtrl::setChannelState(EChannelState state) +{ + mChannelState = state; + if (state == INACTIVE_CHANNEL) + { + // switchIndicator will set it to true when channel becomes active + setVisible(false); + } +} + +void LLOutputMonitorCtrl::setSpeakerId(const LLUUID& speaker_id, const LLUUID& session_id/* = LLUUID::null*/, bool show_other_participants_speaking /* = false */) +{ + if (speaker_id.isNull() && mSpeakerId.notNull()) + { + LLSpeakingIndicatorManager::unregisterSpeakingIndicator(mSpeakerId, this); + switchIndicator(false); + mSpeakerId = speaker_id; + } + + if (speaker_id.isNull() || (speaker_id == mSpeakerId)) + { + return; + } + + if (mSpeakerId.notNull()) + { + // Unregister previous registration to avoid crash. EXT-4782. + LLSpeakingIndicatorManager::unregisterSpeakingIndicator(mSpeakerId, this); + } + + mShowParticipantsSpeaking = show_other_participants_speaking; + mSpeakerId = speaker_id; + LLSpeakingIndicatorManager::registerSpeakingIndicator(mSpeakerId, this, session_id); + + //mute management + if (mAutoUpdate) + { + if (speaker_id == gAgentID) + { + mIsMuted = false; + } + else + { + // check only blocking on voice. EXT-3542 + mIsMuted = LLMuteList::getInstance()->isMuted(mSpeakerId, LLMute::flagVoiceChat); + LLMuteList::getInstance()->addObserver(this); + } + } +} + +void LLOutputMonitorCtrl::onChangeDetailed(const LLMute& mute) +{ + if (mute.mID == mSpeakerId) + { + // Check only blocking on voice. + // Logic goes in reverse, if flag is set, action is allowed + mIsMuted = !(LLMute::flagVoiceChat & mute.mFlags); + } +} + +// virtual +void LLOutputMonitorCtrl::switchIndicator(bool switch_on) +{ + if ((mChannelState != INACTIVE_CHANNEL) && (getVisible() != (bool)switch_on)) + { + setVisible(switch_on); + + //Let parent adjust positioning of icons adjacent to speaker indicator + //(when speaker indicator hidden, adjacent icons move to right and when speaker + //indicator visible, adjacent icons move to the left) + if (getParent() && getParent()->isInVisibleChain()) + { + notifyParentVisibilityChanged(); + //Ignore toggled state in case it was set when parent visibility was hidden + mIndicatorToggled = false; + } + else + { + //Makes sure to only adjust adjacent icons when parent becomes visible + //(!mIndicatorToggled ensures that changes of TFT and FTF are discarded, real state changes are TF or FT) + mIndicatorToggled = !mIndicatorToggled; + } + + } +} + +////////////////////////////////////////////////////////////////////////// +// PRIVATE SECTION +////////////////////////////////////////////////////////////////////////// +void LLOutputMonitorCtrl::notifyParentVisibilityChanged() +{ + LL_DEBUGS("SpeakingIndicator") << "Notify parent that visibility was changed: " << mSpeakerId << ", new_visibility: " << getVisible() << LL_ENDL; + + LLSD params = LLSD().with("visibility_changed", getVisible()); + + notifyParent(params); +} + +// EOF diff --git a/indra/newview/lloutputmonitorctrl.h b/indra/newview/lloutputmonitorctrl.h index d532cdb2b3..f104bab355 100644 --- a/indra/newview/lloutputmonitorctrl.h +++ b/indra/newview/lloutputmonitorctrl.h @@ -1,170 +1,170 @@ -/** - * @file lloutputmonitorctrl.h - * @brief LLOutputMonitorCtrl base class - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLOUTPUTMONITORCTRL_H -#define LL_LLOUTPUTMONITORCTRL_H - -#include "v4color.h" -#include "../llui/llview.h" -#include "llmutelist.h" -#include "llspeakingindicatormanager.h" -//#include "../llui/lluiimage.h" - -class LLTextBox; -class LLUICtrlFactory; - -// -// Classes -// - -class LLOutputMonitorCtrl -: public LLView, public LLSpeakingIndicator, LLMuteListObserver -{ -public: - struct Params : public LLInitParam::Block - { - Optional draw_border; - Mandatory image_mute, - image_off, - image_on, - image_level_1, - image_level_2, - image_level_3; - Optional auto_update; - Optional speaker_id; - - Params(); - }; -protected: - bool mBorder; - LLOutputMonitorCtrl(const Params&); - friend class LLUICtrlFactory; - -public: - virtual ~LLOutputMonitorCtrl(); - - // llview overrides - virtual void draw(); - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - - void setPower(F32 val); - F32 getPower(F32 val) const { return mPower; } - - bool getIsMuted() const { return (mIsMuted || mIsModeratorMuted); } - void setIsModeratorMuted(bool val) { mIsModeratorMuted = val; } - - // For the current user, need to know the PTT state to show - // correct button image. - void setIsAgentControl(bool val) { mIsAgentControl = val; } - void setIsTalking(bool val) { mIsTalking = val; } - - enum EChannelState - { - ACTIVE_CHANNEL, - INACTIVE_CHANNEL, - UNDEFINED_CHANNEL - }; - - // switchIndicator controls visibility, 'active channel' governs if we are allowed to show indicator - void setIsActiveChannel(bool val); - void setChannelState(EChannelState state); - - void setShowParticipantsSpeaking(bool show) { mShowParticipantsSpeaking = show; } - - /** - * Sets avatar UUID to interact with voice channel. - * - * @param speaker_id LLUUID of an avatar whose voice level is displayed. - * @param session_id session UUID for which indicator should be shown only. Passed to LLSpeakingIndicatorManager - * If this parameter is set registered indicator will be shown only in voice channel - * which has the same session id (EXT-5562). - */ - void setSpeakerId(const LLUUID& speaker_id, const LLUUID& session_id = LLUUID::null, bool show_other_participants_speaking = false); - - //called by mute list - virtual void onChange() {}; - virtual void onChangeDetailed(const LLMute& mute); - - /** - * Implementation of LLSpeakingIndicator interface. - * Behavior is implemented via changing visibility. - * - * If instance is in visible chain now (all parents are visible) it changes visibility - * and notify parent about this. - * - * Otherwise it marks an instance as dirty and stores necessary visibility. - * It will be applied in next draw and parent will be notified. - */ - virtual void switchIndicator(bool switch_on); - bool getIndicatorToggled() { return mIndicatorToggled;} - void setIndicatorToggled(bool value) { mIndicatorToggled = value;} - -private: - - /** - * Notifies parent about changed visibility. - * - * Passes LLSD with "visibility_changed" => value. - * For now it is processed by LLAvatarListItem to update (reshape) its children. - * Implemented fo complete EXT-3976 - */ - void notifyParentVisibilityChanged(); - - //static LLColor4 sColorMuted; - //static LLColor4 sColorNormal; - //static LLColor4 sColorOverdriven; - static LLColor4 sColorBound; - //static S32 sRectsNumber; - //static F32 sRectWidthRatio; - //static F32 sRectHeightRatio; - - - - F32 mPower; - bool mIsAgentControl; - bool mIsModeratorMuted; - bool mIsMuted; - bool mIsTalking; - bool mShowParticipantsSpeaking; - LLPointer mImageMute; - LLPointer mImageOff; - LLPointer mImageOn; - LLPointer mImageLevel1; - LLPointer mImageLevel2; - LLPointer mImageLevel3; - - /** whether to deal with LLVoiceClient::getInstance() directly */ - bool mAutoUpdate; - - /** uuid of a speaker being monitored */ - LLUUID mSpeakerId; - - bool mIndicatorToggled; - - EChannelState mChannelState; -}; - -#endif +/** + * @file lloutputmonitorctrl.h + * @brief LLOutputMonitorCtrl base class + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLOUTPUTMONITORCTRL_H +#define LL_LLOUTPUTMONITORCTRL_H + +#include "v4color.h" +#include "../llui/llview.h" +#include "llmutelist.h" +#include "llspeakingindicatormanager.h" +//#include "../llui/lluiimage.h" + +class LLTextBox; +class LLUICtrlFactory; + +// +// Classes +// + +class LLOutputMonitorCtrl +: public LLView, public LLSpeakingIndicator, LLMuteListObserver +{ +public: + struct Params : public LLInitParam::Block + { + Optional draw_border; + Mandatory image_mute, + image_off, + image_on, + image_level_1, + image_level_2, + image_level_3; + Optional auto_update; + Optional speaker_id; + + Params(); + }; +protected: + bool mBorder; + LLOutputMonitorCtrl(const Params&); + friend class LLUICtrlFactory; + +public: + virtual ~LLOutputMonitorCtrl(); + + // llview overrides + virtual void draw(); + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + + void setPower(F32 val); + F32 getPower(F32 val) const { return mPower; } + + bool getIsMuted() const { return (mIsMuted || mIsModeratorMuted); } + void setIsModeratorMuted(bool val) { mIsModeratorMuted = val; } + + // For the current user, need to know the PTT state to show + // correct button image. + void setIsAgentControl(bool val) { mIsAgentControl = val; } + void setIsTalking(bool val) { mIsTalking = val; } + + enum EChannelState + { + ACTIVE_CHANNEL, + INACTIVE_CHANNEL, + UNDEFINED_CHANNEL + }; + + // switchIndicator controls visibility, 'active channel' governs if we are allowed to show indicator + void setIsActiveChannel(bool val); + void setChannelState(EChannelState state); + + void setShowParticipantsSpeaking(bool show) { mShowParticipantsSpeaking = show; } + + /** + * Sets avatar UUID to interact with voice channel. + * + * @param speaker_id LLUUID of an avatar whose voice level is displayed. + * @param session_id session UUID for which indicator should be shown only. Passed to LLSpeakingIndicatorManager + * If this parameter is set registered indicator will be shown only in voice channel + * which has the same session id (EXT-5562). + */ + void setSpeakerId(const LLUUID& speaker_id, const LLUUID& session_id = LLUUID::null, bool show_other_participants_speaking = false); + + //called by mute list + virtual void onChange() {}; + virtual void onChangeDetailed(const LLMute& mute); + + /** + * Implementation of LLSpeakingIndicator interface. + * Behavior is implemented via changing visibility. + * + * If instance is in visible chain now (all parents are visible) it changes visibility + * and notify parent about this. + * + * Otherwise it marks an instance as dirty and stores necessary visibility. + * It will be applied in next draw and parent will be notified. + */ + virtual void switchIndicator(bool switch_on); + bool getIndicatorToggled() { return mIndicatorToggled;} + void setIndicatorToggled(bool value) { mIndicatorToggled = value;} + +private: + + /** + * Notifies parent about changed visibility. + * + * Passes LLSD with "visibility_changed" => value. + * For now it is processed by LLAvatarListItem to update (reshape) its children. + * Implemented fo complete EXT-3976 + */ + void notifyParentVisibilityChanged(); + + //static LLColor4 sColorMuted; + //static LLColor4 sColorNormal; + //static LLColor4 sColorOverdriven; + static LLColor4 sColorBound; + //static S32 sRectsNumber; + //static F32 sRectWidthRatio; + //static F32 sRectHeightRatio; + + + + F32 mPower; + bool mIsAgentControl; + bool mIsModeratorMuted; + bool mIsMuted; + bool mIsTalking; + bool mShowParticipantsSpeaking; + LLPointer mImageMute; + LLPointer mImageOff; + LLPointer mImageOn; + LLPointer mImageLevel1; + LLPointer mImageLevel2; + LLPointer mImageLevel3; + + /** whether to deal with LLVoiceClient::getInstance() directly */ + bool mAutoUpdate; + + /** uuid of a speaker being monitored */ + LLUUID mSpeakerId; + + bool mIndicatorToggled; + + EChannelState mChannelState; +}; + +#endif diff --git a/indra/newview/llpanelavatartag.cpp b/indra/newview/llpanelavatartag.cpp index d0055638c2..124645e3f9 100644 --- a/indra/newview/llpanelavatartag.cpp +++ b/indra/newview/llpanelavatartag.cpp @@ -1,101 +1,101 @@ -/** - * @file llpanelavatartag.cpp - * @brief Avatar tag panel - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelavatartag.h" - -#include "lluictrlfactory.h" -#include "llavatariconctrl.h" -#include "lltextbox.h" - -LLPanelAvatarTag::LLPanelAvatarTag(const LLUUID& key, const std::string im_time) - : LLPanel() - , mAvatarId(LLUUID::null) -// , mFadeTimer() -{ - buildFromFile( "panel_avatar_tag.xml"); - setLeftButtonClickCallback(boost::bind(&LLPanelAvatarTag::onClick, this)); - setAvatarId(key); - setTime(im_time); -} - -LLPanelAvatarTag::~LLPanelAvatarTag() -{ - // Name callbacks will be automatically disconnected since LLPanel is trackable -} - -bool LLPanelAvatarTag::postBuild() -{ - mIcon = getChild("avatar_tag_icon"); - mName = getChild("sender_tag_name"); - mTime = getChild("tag_time"); - return true; -} - -void LLPanelAvatarTag::draw() -{ - -} -void LLPanelAvatarTag::setName(const std::string& name) -{ - if (mName) - mName->setText(name); -} - -void LLPanelAvatarTag::setTime(const std::string& time) -{ - if (mTime) - mTime->setText(time); -} - - -void LLPanelAvatarTag::setAvatarId(const LLUUID& avatar_id) -{ - mAvatarId = avatar_id; - if (mIcon) - { - mIcon->setValue(avatar_id); - } - setName(std::string(mIcon->getFullName())); -} - -boost::signals2::connection LLPanelAvatarTag::setLeftButtonClickCallback( - const commit_callback_t& cb) -{ - return setCommitCallback(cb); -} - -bool LLPanelAvatarTag::handleMouseDown(S32 x, S32 y, MASK mask) -{ - onCommit(); - return true; -} - -void LLPanelAvatarTag::onClick() -{ - // Do the on click stuff. -} +/** + * @file llpanelavatartag.cpp + * @brief Avatar tag panel + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelavatartag.h" + +#include "lluictrlfactory.h" +#include "llavatariconctrl.h" +#include "lltextbox.h" + +LLPanelAvatarTag::LLPanelAvatarTag(const LLUUID& key, const std::string im_time) + : LLPanel() + , mAvatarId(LLUUID::null) +// , mFadeTimer() +{ + buildFromFile( "panel_avatar_tag.xml"); + setLeftButtonClickCallback(boost::bind(&LLPanelAvatarTag::onClick, this)); + setAvatarId(key); + setTime(im_time); +} + +LLPanelAvatarTag::~LLPanelAvatarTag() +{ + // Name callbacks will be automatically disconnected since LLPanel is trackable +} + +bool LLPanelAvatarTag::postBuild() +{ + mIcon = getChild("avatar_tag_icon"); + mName = getChild("sender_tag_name"); + mTime = getChild("tag_time"); + return true; +} + +void LLPanelAvatarTag::draw() +{ + +} +void LLPanelAvatarTag::setName(const std::string& name) +{ + if (mName) + mName->setText(name); +} + +void LLPanelAvatarTag::setTime(const std::string& time) +{ + if (mTime) + mTime->setText(time); +} + + +void LLPanelAvatarTag::setAvatarId(const LLUUID& avatar_id) +{ + mAvatarId = avatar_id; + if (mIcon) + { + mIcon->setValue(avatar_id); + } + setName(std::string(mIcon->getFullName())); +} + +boost::signals2::connection LLPanelAvatarTag::setLeftButtonClickCallback( + const commit_callback_t& cb) +{ + return setCommitCallback(cb); +} + +bool LLPanelAvatarTag::handleMouseDown(S32 x, S32 y, MASK mask) +{ + onCommit(); + return true; +} + +void LLPanelAvatarTag::onClick() +{ + // Do the on click stuff. +} diff --git a/indra/newview/llpanelavatartag.h b/indra/newview/llpanelavatartag.h index 77a5466069..fbc50386a6 100644 --- a/indra/newview/llpanelavatartag.h +++ b/indra/newview/llpanelavatartag.h @@ -1,87 +1,87 @@ -/** - * @file llpanelavatartag.h - * @brief Avatar row panel - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELAVATARTAG_H -#define LL_LLPANELAVATARTAG_H - -#include "llpanel.h" -#include "llavatarpropertiesprocessor.h" - -class LLAvatarIconCtrl; -class LLTextBox; - -/** - * Avatar Tag panel. - * - * Test. - * - * Contains avatar name - * Provide methods for setting avatar id, state, muted status and speech power. - */ -class LLPanelAvatarTag : public LLPanel -{ -public: - LLPanelAvatarTag(const LLUUID& key, const std::string im_time); - virtual ~LLPanelAvatarTag(); - - /** - * Set avatar ID. - * - * After the ID is set, it is possible to track the avatar status and get its name. - */ - void setAvatarId(const LLUUID& avatar_id); - void setTime (const std::string& time); - - const LLUUID& getAvatarId() const { return mAvatarId; } - - /*virtual*/ bool postBuild(); - /*virtual*/ void draw(); - - virtual boost::signals2::connection setLeftButtonClickCallback( - const commit_callback_t& cb); - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - - void onClick(); -private: - void setName(const std::string& name); - - /** - * Called by LLCacheName when the avatar name gets updated. - */ - void nameUpdatedCallback( - const LLUUID& id, - const std::string& first, - const std::string& last, - bool is_group); - - LLAvatarIconCtrl* mIcon; /// status tracking avatar icon - LLTextBox* mName; /// displays avatar name - LLTextBox* mTime; /// displays time - LLUUID mAvatarId; -// LLFrameTimer mFadeTimer; -}; - -#endif +/** + * @file llpanelavatartag.h + * @brief Avatar row panel + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELAVATARTAG_H +#define LL_LLPANELAVATARTAG_H + +#include "llpanel.h" +#include "llavatarpropertiesprocessor.h" + +class LLAvatarIconCtrl; +class LLTextBox; + +/** + * Avatar Tag panel. + * + * Test. + * + * Contains avatar name + * Provide methods for setting avatar id, state, muted status and speech power. + */ +class LLPanelAvatarTag : public LLPanel +{ +public: + LLPanelAvatarTag(const LLUUID& key, const std::string im_time); + virtual ~LLPanelAvatarTag(); + + /** + * Set avatar ID. + * + * After the ID is set, it is possible to track the avatar status and get its name. + */ + void setAvatarId(const LLUUID& avatar_id); + void setTime (const std::string& time); + + const LLUUID& getAvatarId() const { return mAvatarId; } + + /*virtual*/ bool postBuild(); + /*virtual*/ void draw(); + + virtual boost::signals2::connection setLeftButtonClickCallback( + const commit_callback_t& cb); + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + + void onClick(); +private: + void setName(const std::string& name); + + /** + * Called by LLCacheName when the avatar name gets updated. + */ + void nameUpdatedCallback( + const LLUUID& id, + const std::string& first, + const std::string& last, + bool is_group); + + LLAvatarIconCtrl* mIcon; /// status tracking avatar icon + LLTextBox* mName; /// displays avatar name + LLTextBox* mTime; /// displays time + LLUUID mAvatarId; +// LLFrameTimer mFadeTimer; +}; + +#endif diff --git a/indra/newview/llpanelblockedlist.cpp b/indra/newview/llpanelblockedlist.cpp index 6608b8169d..0de22fce25 100644 --- a/indra/newview/llpanelblockedlist.cpp +++ b/indra/newview/llpanelblockedlist.cpp @@ -1,326 +1,326 @@ -/** - * @file llpanelblockedlist.cpp - * @brief Container for blocked Residents & Objects list - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelblockedlist.h" - -// library include -#include "llavatarname.h" -#include "llfiltereditor.h" -#include "llfloater.h" -#include "llfloaterreg.h" -#include "llnotificationsutil.h" -#include "llscrolllistctrl.h" -#include "llmenubutton.h" - -// project include -#include "llavatarlistitem.h" -#include "llblocklist.h" -#include "llblockedlistitem.h" -#include "llfloateravatarpicker.h" -#include "llfloatersidepanelcontainer.h" -#include "llinventorylistitem.h" -#include "llinventorymodel.h" -#include "llsidetraypanelcontainer.h" -#include "llviewercontrol.h" - -static LLPanelInjector t_panel_blocked_list("panel_block_list_sidetray"); - -// -// Constants -// -const std::string BLOCKED_PARAM_NAME = "blocked_to_select"; - -//----------------------------------------------------------------------------- -// LLPanelBlockedList() -//----------------------------------------------------------------------------- - -LLPanelBlockedList::LLPanelBlockedList() -: LLPanel() -{ - mCommitCallbackRegistrar.add("Block.Action", boost::bind(&LLPanelBlockedList::onCustomAction, this, _2)); - mEnableCallbackRegistrar.add("Block.Check", boost::bind(&LLPanelBlockedList::isActionChecked, this, _2)); -} - -void LLPanelBlockedList::removePicker() -{ - if(mPicker.get()) - { - mPicker.get()->closeFloater(); - } -} - -bool LLPanelBlockedList::postBuild() -{ - mBlockedList = getChild("blocked"); - mBlockedList->setCommitOnSelectionChange(true); - this->setVisibleCallback(boost::bind(&LLPanelBlockedList::removePicker, this)); - - switch (gSavedSettings.getU32("BlockPeopleSortOrder")) - { - case E_SORT_BY_NAME: - mBlockedList->sortByName(); - break; - - case E_SORT_BY_TYPE: - mBlockedList->sortByType(); - break; - default: - LL_WARNS() << "Unrecognized sort order for blocked list" << LL_ENDL; - break; - } - - // Use the context menu of the Block list for the Block tab gear menu. - LLToggleableMenu* blocked_gear_menu = mBlockedList->getContextMenu(); - if (blocked_gear_menu) - { - getChild("blocked_gear_btn")->setMenu(blocked_gear_menu, LLMenuButton::MP_BOTTOM_LEFT); - } - - getChild("unblock_btn")->setCommitCallback(boost::bind(&LLPanelBlockedList::unblockItem, this)); - getChild("blocked_filter_input")->setCommitCallback(boost::bind(&LLPanelBlockedList::onFilterEdit, this, _2)); - - return LLPanel::postBuild(); -} - -void LLPanelBlockedList::draw() -{ - updateButtons(); - LLPanel::draw(); -} - -void LLPanelBlockedList::onOpen(const LLSD& key) -{ - if (key.has(BLOCKED_PARAM_NAME) && key[BLOCKED_PARAM_NAME].asUUID().notNull()) - { - selectBlocked(key[BLOCKED_PARAM_NAME].asUUID()); - } -} - -void LLPanelBlockedList::selectBlocked(const LLUUID& mute_id) -{ - mBlockedList->resetSelection(); - mBlockedList->selectItemByUUID(mute_id); -} - -void LLPanelBlockedList::showPanelAndSelect(const LLUUID& idToSelect) -{ - LLFloaterSidePanelContainer::showPanel("people", "panel_people", - LLSD().with("people_panel_tab_name", "blocked_panel").with(BLOCKED_PARAM_NAME, idToSelect)); -} - - -////////////////////////////////////////////////////////////////////////// -// Private Section -////////////////////////////////////////////////////////////////////////// -void LLPanelBlockedList::updateButtons() -{ - bool hasSelected = NULL != mBlockedList->getSelectedItem(); - getChildView("unblock_btn")->setEnabled(hasSelected); - getChildView("blocked_gear_btn")->setEnabled(hasSelected); - - getChild("block_limit")->setTextArg("[COUNT]", llformat("%d", mBlockedList->getMuteListSize())); - getChild("block_limit")->setTextArg("[LIMIT]", llformat("%d", gSavedSettings.getS32("MuteListLimit"))); -} - -void LLPanelBlockedList::unblockItem() -{ - LLBlockedListItem* item = mBlockedList->getBlockedItem(); - if (item) - { - LLMute mute(item->getUUID(), item->getName()); - LLMuteList::instance().remove(mute); - } -} - -void LLPanelBlockedList::onCustomAction(const LLSD& userdata) -{ - const std::string command_name = userdata.asString(); - - if ("block_obj_by_name" == command_name) - { - blockObjectByName(); - } - else if ("block_res_by_name" == command_name) - { - blockResidentByName(); - } - else if ("sort_by_name" == command_name) - { - mBlockedList->sortByName(); - gSavedSettings.setU32("BlockPeopleSortOrder", E_SORT_BY_NAME); - } - else if ("sort_by_type" == command_name) - { - mBlockedList->sortByType(); - gSavedSettings.setU32("BlockPeopleSortOrder", E_SORT_BY_TYPE); - } -} - -bool LLPanelBlockedList::isActionChecked(const LLSD& userdata) -{ - std::string item = userdata.asString(); - U32 sort_order = gSavedSettings.getU32("BlockPeopleSortOrder"); - - if ("sort_by_name" == item) - { - return E_SORT_BY_NAME == sort_order; - } - else if ("sort_by_type" == item) - { - return E_SORT_BY_TYPE == sort_order; - } - - return false; -} - -void LLPanelBlockedList::blockResidentByName() -{ - const bool allow_multiple = false; - const bool close_on_select = true; - - LLView * button = findChild("plus_btn", true); - LLFloater* root_floater = gFloaterView->getParentFloater(this); - LLFloaterAvatarPicker * picker = LLFloaterAvatarPicker::show(boost::bind(&LLPanelBlockedList::callbackBlockPicked, this, _1, _2), - allow_multiple, close_on_select, false, root_floater->getName(), button); - - if (root_floater) - { - root_floater->addDependentFloater(picker); - } - - mPicker = picker->getHandle(); -} - -void LLPanelBlockedList::blockObjectByName() -{ - LLFloaterGetBlockedObjectName::show(&LLPanelBlockedList::callbackBlockByName); -} - -void LLPanelBlockedList::onFilterEdit(const std::string& search_string) -{ - std::string filter = search_string; - LLStringUtil::trimHead(filter); - - mBlockedList->setNameFilter(filter); -} - -void LLPanelBlockedList::callbackBlockPicked(const uuid_vec_t& ids, const std::vector names) -{ - if (names.empty() || ids.empty()) return; - LLMute mute(ids[0], names[0].getUserName(), LLMute::AGENT); - LLMuteList::getInstance()->add(mute); - showPanelAndSelect(mute.mID); -} - -//static -void LLPanelBlockedList::callbackBlockByName(const std::string& text) -{ - if (text.empty()) return; - - LLMute mute(LLUUID::null, text, LLMute::BY_NAME); - bool success = LLMuteList::getInstance()->add(mute); - if (!success) - { - LLNotificationsUtil::add("MuteByNameFailed"); - } -} - -////////////////////////////////////////////////////////////////////////// -// LLFloaterGetBlockedObjectName -////////////////////////////////////////////////////////////////////////// - -// Constructor/Destructor -LLFloaterGetBlockedObjectName::LLFloaterGetBlockedObjectName(const LLSD& key) -: LLFloater(key) -, mGetObjectNameCallback(NULL) -{ -} - -// Destroys the object -LLFloaterGetBlockedObjectName::~LLFloaterGetBlockedObjectName() -{ - gFocusMgr.releaseFocusIfNeeded( this ); -} - -bool LLFloaterGetBlockedObjectName::postBuild() -{ - getChild("OK")-> setCommitCallback(boost::bind(&LLFloaterGetBlockedObjectName::applyBlocking, this)); - getChild("Cancel")-> setCommitCallback(boost::bind(&LLFloaterGetBlockedObjectName::cancelBlocking, this)); - center(); - - return LLFloater::postBuild(); -} - -bool LLFloaterGetBlockedObjectName::handleKeyHere(KEY key, MASK mask) -{ - if (key == KEY_RETURN && mask == MASK_NONE) - { - applyBlocking(); - return true; - } - else if (key == KEY_ESCAPE && mask == MASK_NONE) - { - cancelBlocking(); - return true; - } - - return LLFloater::handleKeyHere(key, mask); -} - -// static -LLFloaterGetBlockedObjectName* LLFloaterGetBlockedObjectName::show(get_object_name_callback_t callback) -{ - LLFloaterGetBlockedObjectName* floater = LLFloaterReg::showTypedInstance("mute_object_by_name"); - - floater->mGetObjectNameCallback = callback; - - // *TODO: mantipov: should LLFloaterGetBlockedObjectName be closed when panel is closed? - // old Floater dependency is not enable in panel - // addDependentFloater(floater); - - return floater; -} - -////////////////////////////////////////////////////////////////////////// -// Private Section -void LLFloaterGetBlockedObjectName::applyBlocking() -{ - if (mGetObjectNameCallback) - { - const std::string& text = getChild("object_name")->getValue().asString(); - mGetObjectNameCallback(text); - } - closeFloater(); -} - -void LLFloaterGetBlockedObjectName::cancelBlocking() -{ - closeFloater(); -} - -//EOF +/** + * @file llpanelblockedlist.cpp + * @brief Container for blocked Residents & Objects list + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelblockedlist.h" + +// library include +#include "llavatarname.h" +#include "llfiltereditor.h" +#include "llfloater.h" +#include "llfloaterreg.h" +#include "llnotificationsutil.h" +#include "llscrolllistctrl.h" +#include "llmenubutton.h" + +// project include +#include "llavatarlistitem.h" +#include "llblocklist.h" +#include "llblockedlistitem.h" +#include "llfloateravatarpicker.h" +#include "llfloatersidepanelcontainer.h" +#include "llinventorylistitem.h" +#include "llinventorymodel.h" +#include "llsidetraypanelcontainer.h" +#include "llviewercontrol.h" + +static LLPanelInjector t_panel_blocked_list("panel_block_list_sidetray"); + +// +// Constants +// +const std::string BLOCKED_PARAM_NAME = "blocked_to_select"; + +//----------------------------------------------------------------------------- +// LLPanelBlockedList() +//----------------------------------------------------------------------------- + +LLPanelBlockedList::LLPanelBlockedList() +: LLPanel() +{ + mCommitCallbackRegistrar.add("Block.Action", boost::bind(&LLPanelBlockedList::onCustomAction, this, _2)); + mEnableCallbackRegistrar.add("Block.Check", boost::bind(&LLPanelBlockedList::isActionChecked, this, _2)); +} + +void LLPanelBlockedList::removePicker() +{ + if(mPicker.get()) + { + mPicker.get()->closeFloater(); + } +} + +bool LLPanelBlockedList::postBuild() +{ + mBlockedList = getChild("blocked"); + mBlockedList->setCommitOnSelectionChange(true); + this->setVisibleCallback(boost::bind(&LLPanelBlockedList::removePicker, this)); + + switch (gSavedSettings.getU32("BlockPeopleSortOrder")) + { + case E_SORT_BY_NAME: + mBlockedList->sortByName(); + break; + + case E_SORT_BY_TYPE: + mBlockedList->sortByType(); + break; + default: + LL_WARNS() << "Unrecognized sort order for blocked list" << LL_ENDL; + break; + } + + // Use the context menu of the Block list for the Block tab gear menu. + LLToggleableMenu* blocked_gear_menu = mBlockedList->getContextMenu(); + if (blocked_gear_menu) + { + getChild("blocked_gear_btn")->setMenu(blocked_gear_menu, LLMenuButton::MP_BOTTOM_LEFT); + } + + getChild("unblock_btn")->setCommitCallback(boost::bind(&LLPanelBlockedList::unblockItem, this)); + getChild("blocked_filter_input")->setCommitCallback(boost::bind(&LLPanelBlockedList::onFilterEdit, this, _2)); + + return LLPanel::postBuild(); +} + +void LLPanelBlockedList::draw() +{ + updateButtons(); + LLPanel::draw(); +} + +void LLPanelBlockedList::onOpen(const LLSD& key) +{ + if (key.has(BLOCKED_PARAM_NAME) && key[BLOCKED_PARAM_NAME].asUUID().notNull()) + { + selectBlocked(key[BLOCKED_PARAM_NAME].asUUID()); + } +} + +void LLPanelBlockedList::selectBlocked(const LLUUID& mute_id) +{ + mBlockedList->resetSelection(); + mBlockedList->selectItemByUUID(mute_id); +} + +void LLPanelBlockedList::showPanelAndSelect(const LLUUID& idToSelect) +{ + LLFloaterSidePanelContainer::showPanel("people", "panel_people", + LLSD().with("people_panel_tab_name", "blocked_panel").with(BLOCKED_PARAM_NAME, idToSelect)); +} + + +////////////////////////////////////////////////////////////////////////// +// Private Section +////////////////////////////////////////////////////////////////////////// +void LLPanelBlockedList::updateButtons() +{ + bool hasSelected = NULL != mBlockedList->getSelectedItem(); + getChildView("unblock_btn")->setEnabled(hasSelected); + getChildView("blocked_gear_btn")->setEnabled(hasSelected); + + getChild("block_limit")->setTextArg("[COUNT]", llformat("%d", mBlockedList->getMuteListSize())); + getChild("block_limit")->setTextArg("[LIMIT]", llformat("%d", gSavedSettings.getS32("MuteListLimit"))); +} + +void LLPanelBlockedList::unblockItem() +{ + LLBlockedListItem* item = mBlockedList->getBlockedItem(); + if (item) + { + LLMute mute(item->getUUID(), item->getName()); + LLMuteList::instance().remove(mute); + } +} + +void LLPanelBlockedList::onCustomAction(const LLSD& userdata) +{ + const std::string command_name = userdata.asString(); + + if ("block_obj_by_name" == command_name) + { + blockObjectByName(); + } + else if ("block_res_by_name" == command_name) + { + blockResidentByName(); + } + else if ("sort_by_name" == command_name) + { + mBlockedList->sortByName(); + gSavedSettings.setU32("BlockPeopleSortOrder", E_SORT_BY_NAME); + } + else if ("sort_by_type" == command_name) + { + mBlockedList->sortByType(); + gSavedSettings.setU32("BlockPeopleSortOrder", E_SORT_BY_TYPE); + } +} + +bool LLPanelBlockedList::isActionChecked(const LLSD& userdata) +{ + std::string item = userdata.asString(); + U32 sort_order = gSavedSettings.getU32("BlockPeopleSortOrder"); + + if ("sort_by_name" == item) + { + return E_SORT_BY_NAME == sort_order; + } + else if ("sort_by_type" == item) + { + return E_SORT_BY_TYPE == sort_order; + } + + return false; +} + +void LLPanelBlockedList::blockResidentByName() +{ + const bool allow_multiple = false; + const bool close_on_select = true; + + LLView * button = findChild("plus_btn", true); + LLFloater* root_floater = gFloaterView->getParentFloater(this); + LLFloaterAvatarPicker * picker = LLFloaterAvatarPicker::show(boost::bind(&LLPanelBlockedList::callbackBlockPicked, this, _1, _2), + allow_multiple, close_on_select, false, root_floater->getName(), button); + + if (root_floater) + { + root_floater->addDependentFloater(picker); + } + + mPicker = picker->getHandle(); +} + +void LLPanelBlockedList::blockObjectByName() +{ + LLFloaterGetBlockedObjectName::show(&LLPanelBlockedList::callbackBlockByName); +} + +void LLPanelBlockedList::onFilterEdit(const std::string& search_string) +{ + std::string filter = search_string; + LLStringUtil::trimHead(filter); + + mBlockedList->setNameFilter(filter); +} + +void LLPanelBlockedList::callbackBlockPicked(const uuid_vec_t& ids, const std::vector names) +{ + if (names.empty() || ids.empty()) return; + LLMute mute(ids[0], names[0].getUserName(), LLMute::AGENT); + LLMuteList::getInstance()->add(mute); + showPanelAndSelect(mute.mID); +} + +//static +void LLPanelBlockedList::callbackBlockByName(const std::string& text) +{ + if (text.empty()) return; + + LLMute mute(LLUUID::null, text, LLMute::BY_NAME); + bool success = LLMuteList::getInstance()->add(mute); + if (!success) + { + LLNotificationsUtil::add("MuteByNameFailed"); + } +} + +////////////////////////////////////////////////////////////////////////// +// LLFloaterGetBlockedObjectName +////////////////////////////////////////////////////////////////////////// + +// Constructor/Destructor +LLFloaterGetBlockedObjectName::LLFloaterGetBlockedObjectName(const LLSD& key) +: LLFloater(key) +, mGetObjectNameCallback(NULL) +{ +} + +// Destroys the object +LLFloaterGetBlockedObjectName::~LLFloaterGetBlockedObjectName() +{ + gFocusMgr.releaseFocusIfNeeded( this ); +} + +bool LLFloaterGetBlockedObjectName::postBuild() +{ + getChild("OK")-> setCommitCallback(boost::bind(&LLFloaterGetBlockedObjectName::applyBlocking, this)); + getChild("Cancel")-> setCommitCallback(boost::bind(&LLFloaterGetBlockedObjectName::cancelBlocking, this)); + center(); + + return LLFloater::postBuild(); +} + +bool LLFloaterGetBlockedObjectName::handleKeyHere(KEY key, MASK mask) +{ + if (key == KEY_RETURN && mask == MASK_NONE) + { + applyBlocking(); + return true; + } + else if (key == KEY_ESCAPE && mask == MASK_NONE) + { + cancelBlocking(); + return true; + } + + return LLFloater::handleKeyHere(key, mask); +} + +// static +LLFloaterGetBlockedObjectName* LLFloaterGetBlockedObjectName::show(get_object_name_callback_t callback) +{ + LLFloaterGetBlockedObjectName* floater = LLFloaterReg::showTypedInstance("mute_object_by_name"); + + floater->mGetObjectNameCallback = callback; + + // *TODO: mantipov: should LLFloaterGetBlockedObjectName be closed when panel is closed? + // old Floater dependency is not enable in panel + // addDependentFloater(floater); + + return floater; +} + +////////////////////////////////////////////////////////////////////////// +// Private Section +void LLFloaterGetBlockedObjectName::applyBlocking() +{ + if (mGetObjectNameCallback) + { + const std::string& text = getChild("object_name")->getValue().asString(); + mGetObjectNameCallback(text); + } + closeFloater(); +} + +void LLFloaterGetBlockedObjectName::cancelBlocking() +{ + closeFloater(); +} + +//EOF diff --git a/indra/newview/llpanelblockedlist.h b/indra/newview/llpanelblockedlist.h index d9d5064086..446f3d4bad 100644 --- a/indra/newview/llpanelblockedlist.h +++ b/indra/newview/llpanelblockedlist.h @@ -1,113 +1,113 @@ -/** - * @file llpanelblockedlist.h - * @brief Container for blocked Residents & Objects list - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELBLOCKEDLIST_H -#define LL_LLPANELBLOCKEDLIST_H - -#include "llpanel.h" -#include "llmutelist.h" -#include "llfloater.h" - -class LLAvatarName; -class LLBlockList; - -class LLPanelBlockedList : public LLPanel -{ -public: - LLPanelBlockedList(); - ~LLPanelBlockedList(){}; - - bool postBuild() override; - void draw() override; - void onOpen(const LLSD& key) override; - - void selectBlocked(const LLUUID& id); - - /** - * Shows current Panel in side tray and select passed blocked item. - * - * @param idToSelect - LLUUID of blocked Resident or Object to be selected. - * If it is LLUUID::null, nothing will be selected. - */ - static void showPanelAndSelect(const LLUUID& idToSelect); - -private: - - typedef enum e_sort_oder{ - E_SORT_BY_NAME = 0, - E_SORT_BY_TYPE = 1, - } ESortOrder; - - void removePicker(); - void updateButtons(); - - // UI callbacks - void unblockItem(); - void blockResidentByName(); - void blockObjectByName(); - void onFilterEdit(const std::string& search_string); - - // List commnads - void onCustomAction(const LLSD& userdata); - bool isActionChecked(const LLSD& userdata); - - void callbackBlockPicked(const uuid_vec_t& ids, const std::vector names); - static void callbackBlockByName(const std::string& text); - -private: - LLBlockList* mBlockedList; - LLHandle mPicker; -}; - -//----------------------------------------------------------------------------- -// LLFloaterGetBlockedObjectName() -//----------------------------------------------------------------------------- -// Class for handling mute object by name floater. -class LLFloaterGetBlockedObjectName : public LLFloater -{ - friend class LLFloaterReg; -public: - typedef boost::function get_object_name_callback_t; - - bool postBuild() override; - - bool handleKeyHere(KEY key, MASK mask) override; - - static LLFloaterGetBlockedObjectName* show(get_object_name_callback_t callback); - -private: - LLFloaterGetBlockedObjectName(const LLSD& key); - virtual ~LLFloaterGetBlockedObjectName(); - - // UI Callbacks - void applyBlocking(); - void cancelBlocking(); - - get_object_name_callback_t mGetObjectNameCallback; -}; - - -#endif // LL_LLPANELBLOCKEDLIST_H +/** + * @file llpanelblockedlist.h + * @brief Container for blocked Residents & Objects list + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELBLOCKEDLIST_H +#define LL_LLPANELBLOCKEDLIST_H + +#include "llpanel.h" +#include "llmutelist.h" +#include "llfloater.h" + +class LLAvatarName; +class LLBlockList; + +class LLPanelBlockedList : public LLPanel +{ +public: + LLPanelBlockedList(); + ~LLPanelBlockedList(){}; + + bool postBuild() override; + void draw() override; + void onOpen(const LLSD& key) override; + + void selectBlocked(const LLUUID& id); + + /** + * Shows current Panel in side tray and select passed blocked item. + * + * @param idToSelect - LLUUID of blocked Resident or Object to be selected. + * If it is LLUUID::null, nothing will be selected. + */ + static void showPanelAndSelect(const LLUUID& idToSelect); + +private: + + typedef enum e_sort_oder{ + E_SORT_BY_NAME = 0, + E_SORT_BY_TYPE = 1, + } ESortOrder; + + void removePicker(); + void updateButtons(); + + // UI callbacks + void unblockItem(); + void blockResidentByName(); + void blockObjectByName(); + void onFilterEdit(const std::string& search_string); + + // List commnads + void onCustomAction(const LLSD& userdata); + bool isActionChecked(const LLSD& userdata); + + void callbackBlockPicked(const uuid_vec_t& ids, const std::vector names); + static void callbackBlockByName(const std::string& text); + +private: + LLBlockList* mBlockedList; + LLHandle mPicker; +}; + +//----------------------------------------------------------------------------- +// LLFloaterGetBlockedObjectName() +//----------------------------------------------------------------------------- +// Class for handling mute object by name floater. +class LLFloaterGetBlockedObjectName : public LLFloater +{ + friend class LLFloaterReg; +public: + typedef boost::function get_object_name_callback_t; + + bool postBuild() override; + + bool handleKeyHere(KEY key, MASK mask) override; + + static LLFloaterGetBlockedObjectName* show(get_object_name_callback_t callback); + +private: + LLFloaterGetBlockedObjectName(const LLSD& key); + virtual ~LLFloaterGetBlockedObjectName(); + + // UI Callbacks + void applyBlocking(); + void cancelBlocking(); + + get_object_name_callback_t mGetObjectNameCallback; +}; + + +#endif // LL_LLPANELBLOCKEDLIST_H diff --git a/indra/newview/llpanelclassified.cpp b/indra/newview/llpanelclassified.cpp index fdbd4258c8..9fe8f39bd6 100644 --- a/indra/newview/llpanelclassified.cpp +++ b/indra/newview/llpanelclassified.cpp @@ -1,569 +1,569 @@ -/** - * @file llpanelclassified.cpp - * @brief LLPanelClassifiedInfo class implementation - * - * $LicenseInfo:firstyear=2021&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2021, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -// Display of a classified used both for the global view in the -// Find directory, and also for each individual user's classified in their -// profile. - -#include "llviewerprecompiledheaders.h" - -#include "llpanelclassified.h" - -#include "lldispatcher.h" -#include "llfloaterreg.h" -#include "llparcel.h" - -#include "llagent.h" -#include "llclassifiedflags.h" -#include "lliconctrl.h" -#include "lltexturectrl.h" -#include "llfloaterworldmap.h" -#include "llviewergenericmessage.h" // send_generic_message -#include "llviewerregion.h" -#include "llscrollcontainer.h" -#include "llcorehttputil.h" - -//static -LLPanelClassifiedInfo::panel_list_t LLPanelClassifiedInfo::sAllPanels; -static LLPanelInjector t_panel_panel_classified_info("panel_classified_info"); - -// "classifiedclickthrough" -// strings[0] = classified_id -// strings[1] = teleport_clicks -// strings[2] = map_clicks -// strings[3] = profile_clicks -class LLDispatchClassifiedClickThrough : public LLDispatchHandler -{ -public: - virtual bool operator()( - const LLDispatcher* dispatcher, - const std::string& key, - const LLUUID& invoice, - const sparam_t& strings) - { - if (strings.size() != 4) return false; - LLUUID classified_id(strings[0]); - S32 teleport_clicks = atoi(strings[1].c_str()); - S32 map_clicks = atoi(strings[2].c_str()); - S32 profile_clicks = atoi(strings[3].c_str()); - - LLPanelClassifiedInfo::setClickThrough( - classified_id, teleport_clicks, map_clicks, profile_clicks, false); - - return true; - } -}; -static LLDispatchClassifiedClickThrough sClassifiedClickThrough; - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -LLPanelClassifiedInfo::LLPanelClassifiedInfo() - : LLPanel() - , mInfoLoaded(false) - , mScrollingPanel(NULL) - , mScrollContainer(NULL) - , mScrollingPanelMinHeight(0) - , mScrollingPanelWidth(0) - , mSnapshotStreched(false) - , mTeleportClicksOld(0) - , mMapClicksOld(0) - , mProfileClicksOld(0) - , mTeleportClicksNew(0) - , mMapClicksNew(0) - , mProfileClicksNew(0) - , mSnapshotCtrl(NULL) -{ - sAllPanels.push_back(this); -} - -LLPanelClassifiedInfo::~LLPanelClassifiedInfo() -{ - sAllPanels.remove(this); - - if (getAvatarId().notNull()) - { - LLAvatarPropertiesProcessor::getInstance()->removeObserver(getAvatarId(), this); - } -} - -bool LLPanelClassifiedInfo::postBuild() -{ - childSetAction("show_on_map_btn", boost::bind(&LLPanelClassifiedInfo::onMapClick, this)); - childSetAction("teleport_btn", boost::bind(&LLPanelClassifiedInfo::onTeleportClick, this)); - - mScrollingPanel = getChild("scroll_content_panel"); - mScrollContainer = getChild("profile_scroll"); - - mScrollingPanelMinHeight = mScrollContainer->getScrolledViewRect().getHeight(); - mScrollingPanelWidth = mScrollingPanel->getRect().getWidth(); - - mSnapshotCtrl = getChild("classified_snapshot"); - mSnapshotRect = getDefaultSnapshotRect(); - - return true; -} - -void LLPanelClassifiedInfo::reshape(S32 width, S32 height, bool called_from_parent /* = true */) -{ - LLPanel::reshape(width, height, called_from_parent); - - if (!mScrollContainer || !mScrollingPanel) - return; - - static LLUICachedControl scrollbar_size ("UIScrollbarSize", 0); - - S32 scroll_height = mScrollContainer->getRect().getHeight(); - if (mScrollingPanelMinHeight >= scroll_height) - { - mScrollingPanel->reshape(mScrollingPanelWidth, mScrollingPanelMinHeight); - } - else - { - mScrollingPanel->reshape(mScrollingPanelWidth + scrollbar_size, scroll_height); - } - - mSnapshotRect = getDefaultSnapshotRect(); - stretchSnapshot(); -} - -void LLPanelClassifiedInfo::onOpen(const LLSD& key) -{ - LLUUID avatar_id = key["classified_creator_id"]; - if(avatar_id.isNull()) - { - return; - } - - if(getAvatarId().notNull()) - { - LLAvatarPropertiesProcessor::getInstance()->removeObserver(getAvatarId(), this); - } - - setAvatarId(avatar_id); - - resetData(); - resetControls(); - scrollToTop(); - - setClassifiedId(key["classified_id"]); - setClassifiedName(key["classified_name"]); - setDescription(key["classified_desc"]); - setSnapshotId(key["classified_snapshot_id"]); - setFromSearch(key["from_search"]); - - LL_INFOS() << "Opening classified [" << getClassifiedName() << "] (" << getClassifiedId() << ")" << LL_ENDL; - - LLAvatarPropertiesProcessor::getInstance()->addObserver(getAvatarId(), this); - LLAvatarPropertiesProcessor::getInstance()->sendClassifiedInfoRequest(getClassifiedId()); - gGenericDispatcher.addHandler("classifiedclickthrough", &sClassifiedClickThrough); - - if (gAgent.getRegion()) - { - // While we're at it let's get the stats from the new table if that - // capability exists. - std::string url = gAgent.getRegion()->getCapability("SearchStatRequest"); - if (!url.empty()) - { - LL_INFOS() << "Classified stat request via capability" << LL_ENDL; - LLSD body; - LLUUID classifiedId = getClassifiedId(); - body["classified_id"] = classifiedId; - LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost(url, body, - boost::bind(&LLPanelClassifiedInfo::handleSearchStatResponse, classifiedId, _1)); - } - } - // Update classified click stats. - // *TODO: Should we do this when opening not from search? - sendClickMessage("profile"); - - setInfoLoaded(false); -} - -/*static*/ -void LLPanelClassifiedInfo::handleSearchStatResponse(LLUUID classifiedId, LLSD result) -{ - S32 teleport = result["teleport_clicks"].asInteger(); - S32 map = result["map_clicks"].asInteger(); - S32 profile = result["profile_clicks"].asInteger(); - S32 search_teleport = result["search_teleport_clicks"].asInteger(); - S32 search_map = result["search_map_clicks"].asInteger(); - S32 search_profile = result["search_profile_clicks"].asInteger(); - - LLPanelClassifiedInfo::setClickThrough(classifiedId, - teleport + search_teleport, - map + search_map, - profile + search_profile, - true); -} - -void LLPanelClassifiedInfo::processProperties(void* data, EAvatarProcessorType type) -{ - if(APT_CLASSIFIED_INFO == type) - { - LLAvatarClassifiedInfo* c_info = static_cast(data); - if(c_info && getClassifiedId() == c_info->classified_id) - { - setClassifiedName(c_info->name); - setDescription(c_info->description); - setSnapshotId(c_info->snapshot_id); - setParcelId(c_info->parcel_id); - setPosGlobal(c_info->pos_global); - setSimName(c_info->sim_name); - - setClassifiedLocation(createLocationText(c_info->parcel_name, c_info->sim_name, c_info->pos_global)); - getChild("category")->setValue(LLClassifiedInfo::sCategories[c_info->category]); - - static std::string mature_str = getString("type_mature"); - static std::string pg_str = getString("type_pg"); - static LLUIString price_str = getString("l$_price"); - static std::string date_fmt = getString("date_fmt"); - - bool mature = is_cf_mature(c_info->flags); - getChild("content_type")->setValue(mature ? mature_str : pg_str); - getChild("content_type_moderate")->setVisible(mature); - getChild("content_type_general")->setVisible(!mature); - - std::string auto_renew_str = is_cf_auto_renew(c_info->flags) ? - getString("auto_renew_on") : getString("auto_renew_off"); - getChild("auto_renew")->setValue(auto_renew_str); - - price_str.setArg("[PRICE]", llformat("%d", c_info->price_for_listing)); - getChild("price_for_listing")->setValue(LLSD(price_str)); - - std::string date_str = date_fmt; - LLStringUtil::format(date_str, LLSD().with("datetime", (S32) c_info->creation_date)); - getChild("creation_date")->setValue(date_str); - - setInfoLoaded(true); - - LLAvatarPropertiesProcessor::getInstance()->removeObserver(getAvatarId(), this); - } - } -} - -void LLPanelClassifiedInfo::resetData() -{ - setClassifiedName(LLStringUtil::null); - setDescription(LLStringUtil::null); - setClassifiedLocation(LLStringUtil::null); - setClassifiedId(LLUUID::null); - setSnapshotId(LLUUID::null); - setPosGlobal(LLVector3d::zero); - setParcelId(LLUUID::null); - setSimName(LLStringUtil::null); - setFromSearch(false); - - // reset click stats - mTeleportClicksOld = 0; - mMapClicksOld = 0; - mProfileClicksOld = 0; - mTeleportClicksNew = 0; - mMapClicksNew = 0; - mProfileClicksNew = 0; - - getChild("category")->setValue(LLStringUtil::null); - getChild("content_type")->setValue(LLStringUtil::null); - getChild("click_through_text")->setValue(LLStringUtil::null); - getChild("price_for_listing")->setValue(LLStringUtil::null); - getChild("auto_renew")->setValue(LLStringUtil::null); - getChild("creation_date")->setValue(LLStringUtil::null); - getChild("click_through_text")->setValue(LLStringUtil::null); - getChild("content_type_moderate")->setVisible(false); - getChild("content_type_general")->setVisible(false); -} - -void LLPanelClassifiedInfo::resetControls() -{ - bool is_self = getAvatarId() == gAgent.getID(); - - getChildView("edit_btn")->setEnabled(is_self); - getChildView("edit_btn")->setVisible( is_self); - getChildView("price_layout_panel")->setVisible( is_self); - getChildView("clickthrough_layout_panel")->setVisible( is_self); -} - -void LLPanelClassifiedInfo::setClassifiedName(const std::string& name) -{ - getChild("classified_name")->setValue(name); -} - -std::string LLPanelClassifiedInfo::getClassifiedName() -{ - return getChild("classified_name")->getValue().asString(); -} - -void LLPanelClassifiedInfo::setDescription(const std::string& desc) -{ - getChild("classified_desc")->setValue(desc); -} - -std::string LLPanelClassifiedInfo::getDescription() -{ - return getChild("classified_desc")->getValue().asString(); -} - -void LLPanelClassifiedInfo::setClassifiedLocation(const std::string& location) -{ - getChild("classified_location")->setValue(location); -} - -std::string LLPanelClassifiedInfo::getClassifiedLocation() -{ - return getChild("classified_location")->getValue().asString(); -} - -void LLPanelClassifiedInfo::setSnapshotId(const LLUUID& id) -{ - mSnapshotCtrl->setValue(id); - mSnapshotStreched = false; -} - -void LLPanelClassifiedInfo::draw() -{ - LLPanel::draw(); - - // Stretch in draw because it takes some time to load a texture, - // going to try to stretch snapshot until texture is loaded - if(!mSnapshotStreched) - { - stretchSnapshot(); - } -} - -LLUUID LLPanelClassifiedInfo::getSnapshotId() -{ - return getChild("classified_snapshot")->getValue().asUUID(); -} - -// static -void LLPanelClassifiedInfo::setClickThrough( - const LLUUID& classified_id, - S32 teleport, - S32 map, - S32 profile, - bool from_new_table) -{ - LL_INFOS() << "Click-through data for classified " << classified_id << " arrived: [" - << teleport << ", " << map << ", " << profile << "] (" - << (from_new_table ? "new" : "old") << ")" << LL_ENDL; - - for (panel_list_t::iterator iter = sAllPanels.begin(); iter != sAllPanels.end(); ++iter) - { - LLPanelClassifiedInfo* self = *iter; - if (self->getClassifiedId() != classified_id) - { - continue; - } - - // *HACK: Skip LLPanelClassifiedEdit instances: they don't display clicks data. - // Those instances should not be in the list at all. - if (typeid(*self) != typeid(LLPanelClassifiedInfo)) - { - continue; - } - - LL_INFOS() << "Updating classified info panel" << LL_ENDL; - - // We need to check to see if the data came from the new stat_table - // or the old classified table. We also need to cache the data from - // the two separate sources so as to display the aggregate totals. - - if (from_new_table) - { - self->mTeleportClicksNew = teleport; - self->mMapClicksNew = map; - self->mProfileClicksNew = profile; - } - else - { - self->mTeleportClicksOld = teleport; - self->mMapClicksOld = map; - self->mProfileClicksOld = profile; - } - - static LLUIString ct_str = self->getString("click_through_text_fmt"); - - ct_str.setArg("[TELEPORT]", llformat("%d", self->mTeleportClicksNew + self->mTeleportClicksOld)); - ct_str.setArg("[MAP]", llformat("%d", self->mMapClicksNew + self->mMapClicksOld)); - ct_str.setArg("[PROFILE]", llformat("%d", self->mProfileClicksNew + self->mProfileClicksOld)); - - self->getChild("click_through_text")->setValue(ct_str.getString()); - // *HACK: remove this when there is enough room for click stats in the info panel - self->getChildView("click_through_text")->setToolTip(ct_str.getString()); - - LL_INFOS() << "teleport: " << llformat("%d", self->mTeleportClicksNew + self->mTeleportClicksOld) - << ", map: " << llformat("%d", self->mMapClicksNew + self->mMapClicksOld) - << ", profile: " << llformat("%d", self->mProfileClicksNew + self->mProfileClicksOld) - << LL_ENDL; - } -} - -// static -std::string LLPanelClassifiedInfo::createLocationText( - const std::string& original_name, - const std::string& sim_name, - const LLVector3d& pos_global) -{ - std::string location_text; - - location_text.append(original_name); - - if (!sim_name.empty()) - { - if (!location_text.empty()) - location_text.append(", "); - location_text.append(sim_name); - } - - if (!location_text.empty()) - location_text.append(" "); - - if (!pos_global.isNull()) - { - S32 region_x = ll_round((F32)pos_global.mdV[VX]) % REGION_WIDTH_UNITS; - S32 region_y = ll_round((F32)pos_global.mdV[VY]) % REGION_WIDTH_UNITS; - S32 region_z = ll_round((F32)pos_global.mdV[VZ]); - location_text.append(llformat(" (%d, %d, %d)", region_x, region_y, region_z)); - } - - return location_text; -} - -void LLPanelClassifiedInfo::stretchSnapshot() -{ - // *NOTE dzaporozhan - // Could be moved to LLTextureCtrl - - LLViewerFetchedTexture* texture = mSnapshotCtrl->getTexture(); - - if(!texture) - { - return; - } - - if(0 == texture->getOriginalWidth() || 0 == texture->getOriginalHeight()) - { - // looks like texture is not loaded yet - return; - } - - LLRect rc = mSnapshotRect; - // *HACK dzaporozhan - // LLTextureCtrl uses BTN_HEIGHT_SMALL as bottom for texture which causes - // drawn texture to be smaller than expected. (see LLTextureCtrl::draw()) - // Lets increase texture height to force texture look as expected. - rc.mBottom -= BTN_HEIGHT_SMALL; - - F32 t_width = texture->getFullWidth(); - F32 t_height = texture->getFullHeight(); - - F32 ratio = llmin( (rc.getWidth() / t_width), (rc.getHeight() / t_height) ); - - t_width *= ratio; - t_height *= ratio; - - rc.setCenterAndSize(rc.getCenterX(), rc.getCenterY(), llfloor(t_width), llfloor(t_height)); - mSnapshotCtrl->setShape(rc); - - mSnapshotStreched = true; -} - -LLRect LLPanelClassifiedInfo::getDefaultSnapshotRect() -{ - // Using scroll container makes getting default rect a hard task - // because rect in postBuild() and in first reshape() is not the same. - // Using snapshot_panel makes it easier to reshape snapshot. - return getChild("snapshot_panel")->getLocalRect(); -} - -void LLPanelClassifiedInfo::scrollToTop() -{ - LLScrollContainer* scrollContainer = findChild("profile_scroll"); - if (scrollContainer) - scrollContainer->goToTop(); -} - -// static -// *TODO: move out of the panel -void LLPanelClassifiedInfo::sendClickMessage( - const std::string& type, - bool from_search, - const LLUUID& classified_id, - const LLUUID& parcel_id, - const LLVector3d& global_pos, - const std::string& sim_name) -{ - if (gAgent.getRegion()) - { - // You're allowed to click on your own ads to reassure yourself - // that the system is working. - LLSD body; - body["type"] = type; - body["from_search"] = from_search; - body["classified_id"] = classified_id; - body["parcel_id"] = parcel_id; - body["dest_pos_global"] = global_pos.getValue(); - body["region_name"] = sim_name; - - std::string url = gAgent.getRegion()->getCapability("SearchStatTracking"); - LL_INFOS() << "Sending click msg via capability (url=" << url << ")" << LL_ENDL; - LL_INFOS() << "body: [" << body << "]" << LL_ENDL; - LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, body, - "SearchStatTracking Click report sent.", "SearchStatTracking Click report NOT sent."); - } -} - -void LLPanelClassifiedInfo::sendClickMessage(const std::string& type) -{ - sendClickMessage( - type, - fromSearch(), - getClassifiedId(), - getParcelId(), - getPosGlobal(), - getSimName()); -} - -void LLPanelClassifiedInfo::onMapClick() -{ - sendClickMessage("map"); - LLFloaterWorldMap::getInstance()->trackLocation(getPosGlobal()); - LLFloaterReg::showInstance("world_map", "center"); -} - -void LLPanelClassifiedInfo::onTeleportClick() -{ - if (!getPosGlobal().isExactlyZero()) - { - sendClickMessage("teleport"); - gAgent.teleportViaLocation(getPosGlobal()); - LLFloaterWorldMap::getInstance()->trackLocation(getPosGlobal()); - } -} - -//EOF +/** + * @file llpanelclassified.cpp + * @brief LLPanelClassifiedInfo class implementation + * + * $LicenseInfo:firstyear=2021&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2021, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Display of a classified used both for the global view in the +// Find directory, and also for each individual user's classified in their +// profile. + +#include "llviewerprecompiledheaders.h" + +#include "llpanelclassified.h" + +#include "lldispatcher.h" +#include "llfloaterreg.h" +#include "llparcel.h" + +#include "llagent.h" +#include "llclassifiedflags.h" +#include "lliconctrl.h" +#include "lltexturectrl.h" +#include "llfloaterworldmap.h" +#include "llviewergenericmessage.h" // send_generic_message +#include "llviewerregion.h" +#include "llscrollcontainer.h" +#include "llcorehttputil.h" + +//static +LLPanelClassifiedInfo::panel_list_t LLPanelClassifiedInfo::sAllPanels; +static LLPanelInjector t_panel_panel_classified_info("panel_classified_info"); + +// "classifiedclickthrough" +// strings[0] = classified_id +// strings[1] = teleport_clicks +// strings[2] = map_clicks +// strings[3] = profile_clicks +class LLDispatchClassifiedClickThrough : public LLDispatchHandler +{ +public: + virtual bool operator()( + const LLDispatcher* dispatcher, + const std::string& key, + const LLUUID& invoice, + const sparam_t& strings) + { + if (strings.size() != 4) return false; + LLUUID classified_id(strings[0]); + S32 teleport_clicks = atoi(strings[1].c_str()); + S32 map_clicks = atoi(strings[2].c_str()); + S32 profile_clicks = atoi(strings[3].c_str()); + + LLPanelClassifiedInfo::setClickThrough( + classified_id, teleport_clicks, map_clicks, profile_clicks, false); + + return true; + } +}; +static LLDispatchClassifiedClickThrough sClassifiedClickThrough; + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +LLPanelClassifiedInfo::LLPanelClassifiedInfo() + : LLPanel() + , mInfoLoaded(false) + , mScrollingPanel(NULL) + , mScrollContainer(NULL) + , mScrollingPanelMinHeight(0) + , mScrollingPanelWidth(0) + , mSnapshotStreched(false) + , mTeleportClicksOld(0) + , mMapClicksOld(0) + , mProfileClicksOld(0) + , mTeleportClicksNew(0) + , mMapClicksNew(0) + , mProfileClicksNew(0) + , mSnapshotCtrl(NULL) +{ + sAllPanels.push_back(this); +} + +LLPanelClassifiedInfo::~LLPanelClassifiedInfo() +{ + sAllPanels.remove(this); + + if (getAvatarId().notNull()) + { + LLAvatarPropertiesProcessor::getInstance()->removeObserver(getAvatarId(), this); + } +} + +bool LLPanelClassifiedInfo::postBuild() +{ + childSetAction("show_on_map_btn", boost::bind(&LLPanelClassifiedInfo::onMapClick, this)); + childSetAction("teleport_btn", boost::bind(&LLPanelClassifiedInfo::onTeleportClick, this)); + + mScrollingPanel = getChild("scroll_content_panel"); + mScrollContainer = getChild("profile_scroll"); + + mScrollingPanelMinHeight = mScrollContainer->getScrolledViewRect().getHeight(); + mScrollingPanelWidth = mScrollingPanel->getRect().getWidth(); + + mSnapshotCtrl = getChild("classified_snapshot"); + mSnapshotRect = getDefaultSnapshotRect(); + + return true; +} + +void LLPanelClassifiedInfo::reshape(S32 width, S32 height, bool called_from_parent /* = true */) +{ + LLPanel::reshape(width, height, called_from_parent); + + if (!mScrollContainer || !mScrollingPanel) + return; + + static LLUICachedControl scrollbar_size ("UIScrollbarSize", 0); + + S32 scroll_height = mScrollContainer->getRect().getHeight(); + if (mScrollingPanelMinHeight >= scroll_height) + { + mScrollingPanel->reshape(mScrollingPanelWidth, mScrollingPanelMinHeight); + } + else + { + mScrollingPanel->reshape(mScrollingPanelWidth + scrollbar_size, scroll_height); + } + + mSnapshotRect = getDefaultSnapshotRect(); + stretchSnapshot(); +} + +void LLPanelClassifiedInfo::onOpen(const LLSD& key) +{ + LLUUID avatar_id = key["classified_creator_id"]; + if(avatar_id.isNull()) + { + return; + } + + if(getAvatarId().notNull()) + { + LLAvatarPropertiesProcessor::getInstance()->removeObserver(getAvatarId(), this); + } + + setAvatarId(avatar_id); + + resetData(); + resetControls(); + scrollToTop(); + + setClassifiedId(key["classified_id"]); + setClassifiedName(key["classified_name"]); + setDescription(key["classified_desc"]); + setSnapshotId(key["classified_snapshot_id"]); + setFromSearch(key["from_search"]); + + LL_INFOS() << "Opening classified [" << getClassifiedName() << "] (" << getClassifiedId() << ")" << LL_ENDL; + + LLAvatarPropertiesProcessor::getInstance()->addObserver(getAvatarId(), this); + LLAvatarPropertiesProcessor::getInstance()->sendClassifiedInfoRequest(getClassifiedId()); + gGenericDispatcher.addHandler("classifiedclickthrough", &sClassifiedClickThrough); + + if (gAgent.getRegion()) + { + // While we're at it let's get the stats from the new table if that + // capability exists. + std::string url = gAgent.getRegion()->getCapability("SearchStatRequest"); + if (!url.empty()) + { + LL_INFOS() << "Classified stat request via capability" << LL_ENDL; + LLSD body; + LLUUID classifiedId = getClassifiedId(); + body["classified_id"] = classifiedId; + LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost(url, body, + boost::bind(&LLPanelClassifiedInfo::handleSearchStatResponse, classifiedId, _1)); + } + } + // Update classified click stats. + // *TODO: Should we do this when opening not from search? + sendClickMessage("profile"); + + setInfoLoaded(false); +} + +/*static*/ +void LLPanelClassifiedInfo::handleSearchStatResponse(LLUUID classifiedId, LLSD result) +{ + S32 teleport = result["teleport_clicks"].asInteger(); + S32 map = result["map_clicks"].asInteger(); + S32 profile = result["profile_clicks"].asInteger(); + S32 search_teleport = result["search_teleport_clicks"].asInteger(); + S32 search_map = result["search_map_clicks"].asInteger(); + S32 search_profile = result["search_profile_clicks"].asInteger(); + + LLPanelClassifiedInfo::setClickThrough(classifiedId, + teleport + search_teleport, + map + search_map, + profile + search_profile, + true); +} + +void LLPanelClassifiedInfo::processProperties(void* data, EAvatarProcessorType type) +{ + if(APT_CLASSIFIED_INFO == type) + { + LLAvatarClassifiedInfo* c_info = static_cast(data); + if(c_info && getClassifiedId() == c_info->classified_id) + { + setClassifiedName(c_info->name); + setDescription(c_info->description); + setSnapshotId(c_info->snapshot_id); + setParcelId(c_info->parcel_id); + setPosGlobal(c_info->pos_global); + setSimName(c_info->sim_name); + + setClassifiedLocation(createLocationText(c_info->parcel_name, c_info->sim_name, c_info->pos_global)); + getChild("category")->setValue(LLClassifiedInfo::sCategories[c_info->category]); + + static std::string mature_str = getString("type_mature"); + static std::string pg_str = getString("type_pg"); + static LLUIString price_str = getString("l$_price"); + static std::string date_fmt = getString("date_fmt"); + + bool mature = is_cf_mature(c_info->flags); + getChild("content_type")->setValue(mature ? mature_str : pg_str); + getChild("content_type_moderate")->setVisible(mature); + getChild("content_type_general")->setVisible(!mature); + + std::string auto_renew_str = is_cf_auto_renew(c_info->flags) ? + getString("auto_renew_on") : getString("auto_renew_off"); + getChild("auto_renew")->setValue(auto_renew_str); + + price_str.setArg("[PRICE]", llformat("%d", c_info->price_for_listing)); + getChild("price_for_listing")->setValue(LLSD(price_str)); + + std::string date_str = date_fmt; + LLStringUtil::format(date_str, LLSD().with("datetime", (S32) c_info->creation_date)); + getChild("creation_date")->setValue(date_str); + + setInfoLoaded(true); + + LLAvatarPropertiesProcessor::getInstance()->removeObserver(getAvatarId(), this); + } + } +} + +void LLPanelClassifiedInfo::resetData() +{ + setClassifiedName(LLStringUtil::null); + setDescription(LLStringUtil::null); + setClassifiedLocation(LLStringUtil::null); + setClassifiedId(LLUUID::null); + setSnapshotId(LLUUID::null); + setPosGlobal(LLVector3d::zero); + setParcelId(LLUUID::null); + setSimName(LLStringUtil::null); + setFromSearch(false); + + // reset click stats + mTeleportClicksOld = 0; + mMapClicksOld = 0; + mProfileClicksOld = 0; + mTeleportClicksNew = 0; + mMapClicksNew = 0; + mProfileClicksNew = 0; + + getChild("category")->setValue(LLStringUtil::null); + getChild("content_type")->setValue(LLStringUtil::null); + getChild("click_through_text")->setValue(LLStringUtil::null); + getChild("price_for_listing")->setValue(LLStringUtil::null); + getChild("auto_renew")->setValue(LLStringUtil::null); + getChild("creation_date")->setValue(LLStringUtil::null); + getChild("click_through_text")->setValue(LLStringUtil::null); + getChild("content_type_moderate")->setVisible(false); + getChild("content_type_general")->setVisible(false); +} + +void LLPanelClassifiedInfo::resetControls() +{ + bool is_self = getAvatarId() == gAgent.getID(); + + getChildView("edit_btn")->setEnabled(is_self); + getChildView("edit_btn")->setVisible( is_self); + getChildView("price_layout_panel")->setVisible( is_self); + getChildView("clickthrough_layout_panel")->setVisible( is_self); +} + +void LLPanelClassifiedInfo::setClassifiedName(const std::string& name) +{ + getChild("classified_name")->setValue(name); +} + +std::string LLPanelClassifiedInfo::getClassifiedName() +{ + return getChild("classified_name")->getValue().asString(); +} + +void LLPanelClassifiedInfo::setDescription(const std::string& desc) +{ + getChild("classified_desc")->setValue(desc); +} + +std::string LLPanelClassifiedInfo::getDescription() +{ + return getChild("classified_desc")->getValue().asString(); +} + +void LLPanelClassifiedInfo::setClassifiedLocation(const std::string& location) +{ + getChild("classified_location")->setValue(location); +} + +std::string LLPanelClassifiedInfo::getClassifiedLocation() +{ + return getChild("classified_location")->getValue().asString(); +} + +void LLPanelClassifiedInfo::setSnapshotId(const LLUUID& id) +{ + mSnapshotCtrl->setValue(id); + mSnapshotStreched = false; +} + +void LLPanelClassifiedInfo::draw() +{ + LLPanel::draw(); + + // Stretch in draw because it takes some time to load a texture, + // going to try to stretch snapshot until texture is loaded + if(!mSnapshotStreched) + { + stretchSnapshot(); + } +} + +LLUUID LLPanelClassifiedInfo::getSnapshotId() +{ + return getChild("classified_snapshot")->getValue().asUUID(); +} + +// static +void LLPanelClassifiedInfo::setClickThrough( + const LLUUID& classified_id, + S32 teleport, + S32 map, + S32 profile, + bool from_new_table) +{ + LL_INFOS() << "Click-through data for classified " << classified_id << " arrived: [" + << teleport << ", " << map << ", " << profile << "] (" + << (from_new_table ? "new" : "old") << ")" << LL_ENDL; + + for (panel_list_t::iterator iter = sAllPanels.begin(); iter != sAllPanels.end(); ++iter) + { + LLPanelClassifiedInfo* self = *iter; + if (self->getClassifiedId() != classified_id) + { + continue; + } + + // *HACK: Skip LLPanelClassifiedEdit instances: they don't display clicks data. + // Those instances should not be in the list at all. + if (typeid(*self) != typeid(LLPanelClassifiedInfo)) + { + continue; + } + + LL_INFOS() << "Updating classified info panel" << LL_ENDL; + + // We need to check to see if the data came from the new stat_table + // or the old classified table. We also need to cache the data from + // the two separate sources so as to display the aggregate totals. + + if (from_new_table) + { + self->mTeleportClicksNew = teleport; + self->mMapClicksNew = map; + self->mProfileClicksNew = profile; + } + else + { + self->mTeleportClicksOld = teleport; + self->mMapClicksOld = map; + self->mProfileClicksOld = profile; + } + + static LLUIString ct_str = self->getString("click_through_text_fmt"); + + ct_str.setArg("[TELEPORT]", llformat("%d", self->mTeleportClicksNew + self->mTeleportClicksOld)); + ct_str.setArg("[MAP]", llformat("%d", self->mMapClicksNew + self->mMapClicksOld)); + ct_str.setArg("[PROFILE]", llformat("%d", self->mProfileClicksNew + self->mProfileClicksOld)); + + self->getChild("click_through_text")->setValue(ct_str.getString()); + // *HACK: remove this when there is enough room for click stats in the info panel + self->getChildView("click_through_text")->setToolTip(ct_str.getString()); + + LL_INFOS() << "teleport: " << llformat("%d", self->mTeleportClicksNew + self->mTeleportClicksOld) + << ", map: " << llformat("%d", self->mMapClicksNew + self->mMapClicksOld) + << ", profile: " << llformat("%d", self->mProfileClicksNew + self->mProfileClicksOld) + << LL_ENDL; + } +} + +// static +std::string LLPanelClassifiedInfo::createLocationText( + const std::string& original_name, + const std::string& sim_name, + const LLVector3d& pos_global) +{ + std::string location_text; + + location_text.append(original_name); + + if (!sim_name.empty()) + { + if (!location_text.empty()) + location_text.append(", "); + location_text.append(sim_name); + } + + if (!location_text.empty()) + location_text.append(" "); + + if (!pos_global.isNull()) + { + S32 region_x = ll_round((F32)pos_global.mdV[VX]) % REGION_WIDTH_UNITS; + S32 region_y = ll_round((F32)pos_global.mdV[VY]) % REGION_WIDTH_UNITS; + S32 region_z = ll_round((F32)pos_global.mdV[VZ]); + location_text.append(llformat(" (%d, %d, %d)", region_x, region_y, region_z)); + } + + return location_text; +} + +void LLPanelClassifiedInfo::stretchSnapshot() +{ + // *NOTE dzaporozhan + // Could be moved to LLTextureCtrl + + LLViewerFetchedTexture* texture = mSnapshotCtrl->getTexture(); + + if(!texture) + { + return; + } + + if(0 == texture->getOriginalWidth() || 0 == texture->getOriginalHeight()) + { + // looks like texture is not loaded yet + return; + } + + LLRect rc = mSnapshotRect; + // *HACK dzaporozhan + // LLTextureCtrl uses BTN_HEIGHT_SMALL as bottom for texture which causes + // drawn texture to be smaller than expected. (see LLTextureCtrl::draw()) + // Lets increase texture height to force texture look as expected. + rc.mBottom -= BTN_HEIGHT_SMALL; + + F32 t_width = texture->getFullWidth(); + F32 t_height = texture->getFullHeight(); + + F32 ratio = llmin( (rc.getWidth() / t_width), (rc.getHeight() / t_height) ); + + t_width *= ratio; + t_height *= ratio; + + rc.setCenterAndSize(rc.getCenterX(), rc.getCenterY(), llfloor(t_width), llfloor(t_height)); + mSnapshotCtrl->setShape(rc); + + mSnapshotStreched = true; +} + +LLRect LLPanelClassifiedInfo::getDefaultSnapshotRect() +{ + // Using scroll container makes getting default rect a hard task + // because rect in postBuild() and in first reshape() is not the same. + // Using snapshot_panel makes it easier to reshape snapshot. + return getChild("snapshot_panel")->getLocalRect(); +} + +void LLPanelClassifiedInfo::scrollToTop() +{ + LLScrollContainer* scrollContainer = findChild("profile_scroll"); + if (scrollContainer) + scrollContainer->goToTop(); +} + +// static +// *TODO: move out of the panel +void LLPanelClassifiedInfo::sendClickMessage( + const std::string& type, + bool from_search, + const LLUUID& classified_id, + const LLUUID& parcel_id, + const LLVector3d& global_pos, + const std::string& sim_name) +{ + if (gAgent.getRegion()) + { + // You're allowed to click on your own ads to reassure yourself + // that the system is working. + LLSD body; + body["type"] = type; + body["from_search"] = from_search; + body["classified_id"] = classified_id; + body["parcel_id"] = parcel_id; + body["dest_pos_global"] = global_pos.getValue(); + body["region_name"] = sim_name; + + std::string url = gAgent.getRegion()->getCapability("SearchStatTracking"); + LL_INFOS() << "Sending click msg via capability (url=" << url << ")" << LL_ENDL; + LL_INFOS() << "body: [" << body << "]" << LL_ENDL; + LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, body, + "SearchStatTracking Click report sent.", "SearchStatTracking Click report NOT sent."); + } +} + +void LLPanelClassifiedInfo::sendClickMessage(const std::string& type) +{ + sendClickMessage( + type, + fromSearch(), + getClassifiedId(), + getParcelId(), + getPosGlobal(), + getSimName()); +} + +void LLPanelClassifiedInfo::onMapClick() +{ + sendClickMessage("map"); + LLFloaterWorldMap::getInstance()->trackLocation(getPosGlobal()); + LLFloaterReg::showInstance("world_map", "center"); +} + +void LLPanelClassifiedInfo::onTeleportClick() +{ + if (!getPosGlobal().isExactlyZero()) + { + sendClickMessage("teleport"); + gAgent.teleportViaLocation(getPosGlobal()); + LLFloaterWorldMap::getInstance()->trackLocation(getPosGlobal()); + } +} + +//EOF diff --git a/indra/newview/llpanelclassified.h b/indra/newview/llpanelclassified.h index 603ce0a82f..266b9d222a 100644 --- a/indra/newview/llpanelclassified.h +++ b/indra/newview/llpanelclassified.h @@ -1,175 +1,175 @@ -/** - * @file llpanelclassified.h - * @brief LLPanelClassifiedInfo class definition - * - * $LicenseInfo:firstyear=2021&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2021, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -// Display of a classified used both for the global view in the -// Find directory, and also for each individual user's classified in their -// profile. -#ifndef LL_LLPANELCLASSIFIED_H -#define LL_LLPANELCLASSIFIED_H - -#include "llavatarpropertiesprocessor.h" -#include "llclassifiedinfo.h" -#include "llfloater.h" -#include "llpanel.h" -#include "llrect.h" - -class LLScrollContainer; -class LLTextureCtrl; - -class LLPanelClassifiedInfo : public LLPanel, public LLAvatarPropertiesObserver -{ - LOG_CLASS(LLPanelClassifiedInfo); -public: - - LLPanelClassifiedInfo(); - virtual ~LLPanelClassifiedInfo(); - - /*virtual*/ void onOpen(const LLSD& key); - - /*virtual*/ bool postBuild(); - - /*virtual*/ void processProperties(void* data, EAvatarProcessorType type); - - void setAvatarId(const LLUUID& avatar_id) { mAvatarId = avatar_id; } - - LLUUID& getAvatarId() { return mAvatarId; } - - void setSnapshotId(const LLUUID& id); - - LLUUID getSnapshotId(); - - void setClassifiedId(const LLUUID& id) { mClassifiedId = id; } - - LLUUID& getClassifiedId() { return mClassifiedId; } - - void setClassifiedName(const std::string& name); - - std::string getClassifiedName(); - - void setDescription(const std::string& desc); - - std::string getDescription(); - - void setClassifiedLocation(const std::string& location); - - std::string getClassifiedLocation(); - - void setPosGlobal(const LLVector3d& pos) { mPosGlobal = pos; } - - LLVector3d& getPosGlobal() { return mPosGlobal; } - - void setParcelId(const LLUUID& id) { mParcelId = id; } - - LLUUID getParcelId() { return mParcelId; } - - void setSimName(const std::string& sim_name) { mSimName = sim_name; } - - std::string getSimName() { return mSimName; } - - void setFromSearch(bool val) { mFromSearch = val; } - - bool fromSearch() { return mFromSearch; } - - bool getInfoLoaded() { return mInfoLoaded; } - - void setInfoLoaded(bool loaded) { mInfoLoaded = loaded; } - - static void setClickThrough( - const LLUUID& classified_id, - S32 teleport, - S32 map, - S32 profile, - bool from_new_table); - - static void sendClickMessage( - const std::string& type, - bool from_search, - const LLUUID& classified_id, - const LLUUID& parcel_id, - const LLVector3d& global_pos, - const std::string& sim_name); - - /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); - - /*virtual*/ void draw(); - -protected: - - virtual void resetData(); - - virtual void resetControls(); - - static std::string createLocationText( - const std::string& original_name, - const std::string& sim_name, - const LLVector3d& pos_global); - - void stretchSnapshot(); - void sendClickMessage(const std::string& type); - - LLRect getDefaultSnapshotRect(); - - void scrollToTop(); - - void onMapClick(); - void onTeleportClick(); - - bool mSnapshotStreched; - LLRect mSnapshotRect; - LLTextureCtrl* mSnapshotCtrl; - -private: - - LLUUID mAvatarId; - LLUUID mClassifiedId; - LLVector3d mPosGlobal; - LLUUID mParcelId; - std::string mSimName; - bool mFromSearch; - bool mInfoLoaded; - - LLScrollContainer* mScrollContainer; - LLPanel* mScrollingPanel; - - S32 mScrollingPanelMinHeight; - S32 mScrollingPanelWidth; - - // Needed for stat tracking - S32 mTeleportClicksOld; - S32 mMapClicksOld; - S32 mProfileClicksOld; - S32 mTeleportClicksNew; - S32 mMapClicksNew; - S32 mProfileClicksNew; - - static void handleSearchStatResponse(LLUUID classifiedId, LLSD result); - - - typedef std::list panel_list_t; - static panel_list_t sAllPanels; -}; - -#endif // LL_LLPANELCLASSIFIED_H +/** + * @file llpanelclassified.h + * @brief LLPanelClassifiedInfo class definition + * + * $LicenseInfo:firstyear=2021&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2021, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Display of a classified used both for the global view in the +// Find directory, and also for each individual user's classified in their +// profile. +#ifndef LL_LLPANELCLASSIFIED_H +#define LL_LLPANELCLASSIFIED_H + +#include "llavatarpropertiesprocessor.h" +#include "llclassifiedinfo.h" +#include "llfloater.h" +#include "llpanel.h" +#include "llrect.h" + +class LLScrollContainer; +class LLTextureCtrl; + +class LLPanelClassifiedInfo : public LLPanel, public LLAvatarPropertiesObserver +{ + LOG_CLASS(LLPanelClassifiedInfo); +public: + + LLPanelClassifiedInfo(); + virtual ~LLPanelClassifiedInfo(); + + /*virtual*/ void onOpen(const LLSD& key); + + /*virtual*/ bool postBuild(); + + /*virtual*/ void processProperties(void* data, EAvatarProcessorType type); + + void setAvatarId(const LLUUID& avatar_id) { mAvatarId = avatar_id; } + + LLUUID& getAvatarId() { return mAvatarId; } + + void setSnapshotId(const LLUUID& id); + + LLUUID getSnapshotId(); + + void setClassifiedId(const LLUUID& id) { mClassifiedId = id; } + + LLUUID& getClassifiedId() { return mClassifiedId; } + + void setClassifiedName(const std::string& name); + + std::string getClassifiedName(); + + void setDescription(const std::string& desc); + + std::string getDescription(); + + void setClassifiedLocation(const std::string& location); + + std::string getClassifiedLocation(); + + void setPosGlobal(const LLVector3d& pos) { mPosGlobal = pos; } + + LLVector3d& getPosGlobal() { return mPosGlobal; } + + void setParcelId(const LLUUID& id) { mParcelId = id; } + + LLUUID getParcelId() { return mParcelId; } + + void setSimName(const std::string& sim_name) { mSimName = sim_name; } + + std::string getSimName() { return mSimName; } + + void setFromSearch(bool val) { mFromSearch = val; } + + bool fromSearch() { return mFromSearch; } + + bool getInfoLoaded() { return mInfoLoaded; } + + void setInfoLoaded(bool loaded) { mInfoLoaded = loaded; } + + static void setClickThrough( + const LLUUID& classified_id, + S32 teleport, + S32 map, + S32 profile, + bool from_new_table); + + static void sendClickMessage( + const std::string& type, + bool from_search, + const LLUUID& classified_id, + const LLUUID& parcel_id, + const LLVector3d& global_pos, + const std::string& sim_name); + + /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); + + /*virtual*/ void draw(); + +protected: + + virtual void resetData(); + + virtual void resetControls(); + + static std::string createLocationText( + const std::string& original_name, + const std::string& sim_name, + const LLVector3d& pos_global); + + void stretchSnapshot(); + void sendClickMessage(const std::string& type); + + LLRect getDefaultSnapshotRect(); + + void scrollToTop(); + + void onMapClick(); + void onTeleportClick(); + + bool mSnapshotStreched; + LLRect mSnapshotRect; + LLTextureCtrl* mSnapshotCtrl; + +private: + + LLUUID mAvatarId; + LLUUID mClassifiedId; + LLVector3d mPosGlobal; + LLUUID mParcelId; + std::string mSimName; + bool mFromSearch; + bool mInfoLoaded; + + LLScrollContainer* mScrollContainer; + LLPanel* mScrollingPanel; + + S32 mScrollingPanelMinHeight; + S32 mScrollingPanelWidth; + + // Needed for stat tracking + S32 mTeleportClicksOld; + S32 mMapClicksOld; + S32 mProfileClicksOld; + S32 mTeleportClicksNew; + S32 mMapClicksNew; + S32 mProfileClicksNew; + + static void handleSearchStatResponse(LLUUID classifiedId, LLSD result); + + + typedef std::list panel_list_t; + static panel_list_t sAllPanels; +}; + +#endif // LL_LLPANELCLASSIFIED_H diff --git a/indra/newview/llpanelcontents.cpp b/indra/newview/llpanelcontents.cpp index 7413a2d93e..7b78ad2934 100644 --- a/indra/newview/llpanelcontents.cpp +++ b/indra/newview/llpanelcontents.cpp @@ -1,208 +1,208 @@ -/** - * @file llpanelcontents.cpp - * @brief Object contents panel in the tools floater. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -// file include -#include "llpanelcontents.h" - -// linden library includes -#include "llerror.h" -#include "llfloaterreg.h" -#include "llfontgl.h" -#include "llinventorydefines.h" -#include "llmaterialtable.h" -#include "llpermissionsflags.h" -#include "llrect.h" -#include "llstring.h" -#include "llui.h" -#include "m3math.h" -#include "material_codes.h" - -// project includes -#include "llagent.h" -#include "llpanelobjectinventory.h" -#include "llpreviewscript.h" -#include "llresmgr.h" -#include "llselectmgr.h" -#include "lltool.h" -#include "lltoolcomp.h" -#include "lltoolmgr.h" -#include "lltrans.h" -#include "llviewerassettype.h" -#include "llviewerinventory.h" -#include "llviewerobject.h" -#include "llviewerregion.h" -#include "llviewerwindow.h" -#include "llworld.h" -#include "llfloaterperms.h" - -// -// Imported globals -// - - -// -// Globals -// -const char* LLPanelContents::TENTATIVE_SUFFIX = "_tentative"; -const char* LLPanelContents::PERMS_OWNER_INTERACT_KEY = "perms_owner_interact"; -const char* LLPanelContents::PERMS_OWNER_CONTROL_KEY = "perms_owner_control"; -const char* LLPanelContents::PERMS_GROUP_INTERACT_KEY = "perms_group_interact"; -const char* LLPanelContents::PERMS_GROUP_CONTROL_KEY = "perms_group_control"; -const char* LLPanelContents::PERMS_ANYONE_INTERACT_KEY = "perms_anyone_interact"; -const char* LLPanelContents::PERMS_ANYONE_CONTROL_KEY = "perms_anyone_control"; - -bool LLPanelContents::postBuild() -{ - setMouseOpaque(false); - - childSetAction("button new script",&LLPanelContents::onClickNewScript, this); - childSetAction("button permissions",&LLPanelContents::onClickPermissions, this); - - mPanelInventoryObject = getChild("contents_inventory"); - - return true; -} - -LLPanelContents::LLPanelContents() - : LLPanel(), - mPanelInventoryObject(NULL) -{ -} - - -LLPanelContents::~LLPanelContents() -{ - // Children all cleaned up by default view destructor. -} - - -void LLPanelContents::getState(LLViewerObject *objectp ) -{ - if( !objectp ) - { - getChildView("button new script")->setEnabled(false); - return; - } - - LLUUID group_id; // used for SL-23488 - LLSelectMgr::getInstance()->selectGetGroup(group_id); // sets group_id as a side effect SL-23488 - - // BUG? Check for all objects being editable? - bool editable = gAgent.isGodlike() - || (objectp->permModify() && !objectp->isPermanentEnforced() - && ( objectp->permYouOwner() || ( !group_id.isNull() && gAgent.isInGroup(group_id) ))); // solves SL-23488 - bool all_volume = LLSelectMgr::getInstance()->selectionAllPCode( LL_PCODE_VOLUME ); - - // Edit script button - ok if object is editable and there's an unambiguous destination for the object. - getChildView("button new script")->setEnabled( - editable && - all_volume && - ((LLSelectMgr::getInstance()->getSelection()->getRootObjectCount() == 1) - || (LLSelectMgr::getInstance()->getSelection()->getObjectCount() == 1))); - - getChildView("button permissions")->setEnabled(!objectp->isPermanentEnforced()); - mPanelInventoryObject->setEnabled(!objectp->isPermanentEnforced()); -} - -void LLPanelContents::refresh() -{ - const bool children_ok = true; - LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getFirstRootObject(children_ok); - - getState(object); - if (mPanelInventoryObject) - { - mPanelInventoryObject->refresh(); - } -} - -void LLPanelContents::clearContents() -{ - if (mPanelInventoryObject) - { - mPanelInventoryObject->clearInventoryTask(); - } -} - - -// -// Static functions -// - -// static -void LLPanelContents::onClickNewScript(void *userdata) -{ - const bool children_ok = true; - LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getFirstRootObject(children_ok); - if(object) - { - LLPermissions perm; - perm.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null); - - // Parameters are base, owner, everyone, group, next - perm.initMasks( - PERM_ALL, - PERM_ALL, - LLFloaterPerms::getEveryonePerms("Scripts"), - LLFloaterPerms::getGroupPerms("Scripts"), - PERM_MOVE | LLFloaterPerms::getNextOwnerPerms("Scripts")); - std::string desc; - LLViewerAssetType::generateDescriptionFor(LLAssetType::AT_LSL_TEXT, desc); - LLPointer new_item = - new LLViewerInventoryItem( - LLUUID::null, - LLUUID::null, - perm, - LLUUID::null, - LLAssetType::AT_LSL_TEXT, - LLInventoryType::IT_LSL, - "New Script", - desc, - LLSaleInfo::DEFAULT, - LLInventoryItemFlags::II_FLAGS_NONE, - time_corrected()); - object->saveScript(new_item, true, true); - - std::string name = new_item->getName(); - - // *NOTE: In order to resolve SL-22177, we needed to create - // the script first, and then you have to click it in - // inventory to edit it. - // *TODO: The script creation should round-trip back to the - // viewer so the viewer can auto-open the script and start - // editing ASAP. - } -} - - -// static -void LLPanelContents::onClickPermissions(void *userdata) -{ - LLPanelContents* self = (LLPanelContents*)userdata; - gFloaterView->getParentFloater(self)->addDependentFloater(LLFloaterReg::showInstance("bulk_perms")); -} +/** + * @file llpanelcontents.cpp + * @brief Object contents panel in the tools floater. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +// file include +#include "llpanelcontents.h" + +// linden library includes +#include "llerror.h" +#include "llfloaterreg.h" +#include "llfontgl.h" +#include "llinventorydefines.h" +#include "llmaterialtable.h" +#include "llpermissionsflags.h" +#include "llrect.h" +#include "llstring.h" +#include "llui.h" +#include "m3math.h" +#include "material_codes.h" + +// project includes +#include "llagent.h" +#include "llpanelobjectinventory.h" +#include "llpreviewscript.h" +#include "llresmgr.h" +#include "llselectmgr.h" +#include "lltool.h" +#include "lltoolcomp.h" +#include "lltoolmgr.h" +#include "lltrans.h" +#include "llviewerassettype.h" +#include "llviewerinventory.h" +#include "llviewerobject.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" +#include "llworld.h" +#include "llfloaterperms.h" + +// +// Imported globals +// + + +// +// Globals +// +const char* LLPanelContents::TENTATIVE_SUFFIX = "_tentative"; +const char* LLPanelContents::PERMS_OWNER_INTERACT_KEY = "perms_owner_interact"; +const char* LLPanelContents::PERMS_OWNER_CONTROL_KEY = "perms_owner_control"; +const char* LLPanelContents::PERMS_GROUP_INTERACT_KEY = "perms_group_interact"; +const char* LLPanelContents::PERMS_GROUP_CONTROL_KEY = "perms_group_control"; +const char* LLPanelContents::PERMS_ANYONE_INTERACT_KEY = "perms_anyone_interact"; +const char* LLPanelContents::PERMS_ANYONE_CONTROL_KEY = "perms_anyone_control"; + +bool LLPanelContents::postBuild() +{ + setMouseOpaque(false); + + childSetAction("button new script",&LLPanelContents::onClickNewScript, this); + childSetAction("button permissions",&LLPanelContents::onClickPermissions, this); + + mPanelInventoryObject = getChild("contents_inventory"); + + return true; +} + +LLPanelContents::LLPanelContents() + : LLPanel(), + mPanelInventoryObject(NULL) +{ +} + + +LLPanelContents::~LLPanelContents() +{ + // Children all cleaned up by default view destructor. +} + + +void LLPanelContents::getState(LLViewerObject *objectp ) +{ + if( !objectp ) + { + getChildView("button new script")->setEnabled(false); + return; + } + + LLUUID group_id; // used for SL-23488 + LLSelectMgr::getInstance()->selectGetGroup(group_id); // sets group_id as a side effect SL-23488 + + // BUG? Check for all objects being editable? + bool editable = gAgent.isGodlike() + || (objectp->permModify() && !objectp->isPermanentEnforced() + && ( objectp->permYouOwner() || ( !group_id.isNull() && gAgent.isInGroup(group_id) ))); // solves SL-23488 + bool all_volume = LLSelectMgr::getInstance()->selectionAllPCode( LL_PCODE_VOLUME ); + + // Edit script button - ok if object is editable and there's an unambiguous destination for the object. + getChildView("button new script")->setEnabled( + editable && + all_volume && + ((LLSelectMgr::getInstance()->getSelection()->getRootObjectCount() == 1) + || (LLSelectMgr::getInstance()->getSelection()->getObjectCount() == 1))); + + getChildView("button permissions")->setEnabled(!objectp->isPermanentEnforced()); + mPanelInventoryObject->setEnabled(!objectp->isPermanentEnforced()); +} + +void LLPanelContents::refresh() +{ + const bool children_ok = true; + LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getFirstRootObject(children_ok); + + getState(object); + if (mPanelInventoryObject) + { + mPanelInventoryObject->refresh(); + } +} + +void LLPanelContents::clearContents() +{ + if (mPanelInventoryObject) + { + mPanelInventoryObject->clearInventoryTask(); + } +} + + +// +// Static functions +// + +// static +void LLPanelContents::onClickNewScript(void *userdata) +{ + const bool children_ok = true; + LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getFirstRootObject(children_ok); + if(object) + { + LLPermissions perm; + perm.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null); + + // Parameters are base, owner, everyone, group, next + perm.initMasks( + PERM_ALL, + PERM_ALL, + LLFloaterPerms::getEveryonePerms("Scripts"), + LLFloaterPerms::getGroupPerms("Scripts"), + PERM_MOVE | LLFloaterPerms::getNextOwnerPerms("Scripts")); + std::string desc; + LLViewerAssetType::generateDescriptionFor(LLAssetType::AT_LSL_TEXT, desc); + LLPointer new_item = + new LLViewerInventoryItem( + LLUUID::null, + LLUUID::null, + perm, + LLUUID::null, + LLAssetType::AT_LSL_TEXT, + LLInventoryType::IT_LSL, + "New Script", + desc, + LLSaleInfo::DEFAULT, + LLInventoryItemFlags::II_FLAGS_NONE, + time_corrected()); + object->saveScript(new_item, true, true); + + std::string name = new_item->getName(); + + // *NOTE: In order to resolve SL-22177, we needed to create + // the script first, and then you have to click it in + // inventory to edit it. + // *TODO: The script creation should round-trip back to the + // viewer so the viewer can auto-open the script and start + // editing ASAP. + } +} + + +// static +void LLPanelContents::onClickPermissions(void *userdata) +{ + LLPanelContents* self = (LLPanelContents*)userdata; + gFloaterView->getParentFloater(self)->addDependentFloater(LLFloaterReg::showInstance("bulk_perms")); +} diff --git a/indra/newview/llpanelcontents.h b/indra/newview/llpanelcontents.h index 5ed3a44a3e..748bb76a82 100644 --- a/indra/newview/llpanelcontents.h +++ b/indra/newview/llpanelcontents.h @@ -1,75 +1,75 @@ -/** - * @file llpanelcontents.h - * @brief Object contents panel in the tools floater. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELCONTENTS_H -#define LL_LLPANELCONTENTS_H - -#include "v3math.h" -#include "llpanel.h" -#include "llinventory.h" -#include "lluuid.h" -#include "llviewerobject.h" -#include "llvoinventorylistener.h" - -class LLButton; -class LLPanelObjectInventory; -class LLViewerObject; -class LLCheckBoxCtrl; -class LLSpinCtrl; - -class LLPanelContents : public LLPanel -{ -public: - virtual bool postBuild(); - LLPanelContents(); - virtual ~LLPanelContents(); - - void refresh(); - void clearContents(); - - - static void onClickNewScript(void*); - static void onClickPermissions(void*); - - // Key suffix for "tentative" fields - static const char* TENTATIVE_SUFFIX; - - // These aren't fields in LLMediaEntry, so we have to define them ourselves for checkbox control - static const char* PERMS_OWNER_INTERACT_KEY; - static const char* PERMS_OWNER_CONTROL_KEY; - static const char* PERMS_GROUP_INTERACT_KEY; - static const char* PERMS_GROUP_CONTROL_KEY; - static const char* PERMS_ANYONE_INTERACT_KEY; - static const char* PERMS_ANYONE_CONTROL_KEY; - -protected: - void getState(LLViewerObject *object); - -public: - LLPanelObjectInventory* mPanelInventoryObject; -}; - -#endif // LL_LLPANELCONTENTS_H +/** + * @file llpanelcontents.h + * @brief Object contents panel in the tools floater. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELCONTENTS_H +#define LL_LLPANELCONTENTS_H + +#include "v3math.h" +#include "llpanel.h" +#include "llinventory.h" +#include "lluuid.h" +#include "llviewerobject.h" +#include "llvoinventorylistener.h" + +class LLButton; +class LLPanelObjectInventory; +class LLViewerObject; +class LLCheckBoxCtrl; +class LLSpinCtrl; + +class LLPanelContents : public LLPanel +{ +public: + virtual bool postBuild(); + LLPanelContents(); + virtual ~LLPanelContents(); + + void refresh(); + void clearContents(); + + + static void onClickNewScript(void*); + static void onClickPermissions(void*); + + // Key suffix for "tentative" fields + static const char* TENTATIVE_SUFFIX; + + // These aren't fields in LLMediaEntry, so we have to define them ourselves for checkbox control + static const char* PERMS_OWNER_INTERACT_KEY; + static const char* PERMS_OWNER_CONTROL_KEY; + static const char* PERMS_GROUP_INTERACT_KEY; + static const char* PERMS_GROUP_CONTROL_KEY; + static const char* PERMS_ANYONE_INTERACT_KEY; + static const char* PERMS_ANYONE_CONTROL_KEY; + +protected: + void getState(LLViewerObject *object); + +public: + LLPanelObjectInventory* mPanelInventoryObject; +}; + +#endif // LL_LLPANELCONTENTS_H diff --git a/indra/newview/llpaneleditsky.h b/indra/newview/llpaneleditsky.h index 34b672f4bb..bab606bc8e 100644 --- a/indra/newview/llpaneleditsky.h +++ b/indra/newview/llpaneleditsky.h @@ -1,175 +1,175 @@ -/** -* @file llpaneleditsky.h -* @brief Panels for sky settings -* -* $LicenseInfo:firstyear=2011&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2011, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#ifndef LLPANEL_EDIT_SKY_H -#define LLPANEL_EDIT_SKY_H - -#include "llpanel.h" -#include "llsettingssky.h" -#include "llfloatereditenvironmentbase.h" - -//========================================================================= -class LLSlider; -class LLColorSwatchCtrl; -class LLTextureCtrl; - -//========================================================================= -class LLPanelSettingsSky : public LLSettingsEditPanel -{ - LOG_CLASS(LLPanelSettingsSky); - -public: - LLPanelSettingsSky(); - - virtual void setSettings(const LLSettingsBase::ptr_t &settings) override { setSky(std::static_pointer_cast(settings)); } - - LLSettingsSky::ptr_t getSky() const { return mSkySettings; } - void setSky(const LLSettingsSky::ptr_t &sky) { mSkySettings = sky; clearIsDirty(); refresh(); } - -protected: - LLSettingsSky::ptr_t mSkySettings; -}; - -class LLPanelSettingsSkyAtmosTab : public LLPanelSettingsSky -{ - LOG_CLASS(LLPanelSettingsSkyAtmosTab); - -public: - LLPanelSettingsSkyAtmosTab(); - - virtual bool postBuild() override; - virtual void setEnabled(bool enabled) override; - -protected: - virtual void refresh() override; - -private: - void onAmbientLightChanged(); - void onBlueHorizonChanged(); - void onBlueDensityChanged(); - void onHazeHorizonChanged(); - void onHazeDensityChanged(); - void onSceneGammaChanged(); - void onDensityMultipChanged(); - void onDistanceMultipChanged(); - void onMaxAltChanged(); - void onMoistureLevelChanged(); - void onDropletRadiusChanged(); - void onIceLevelChanged(); - void onReflectionProbeAmbianceChanged(); - void updateGammaLabel(bool auto_adjust = false); - -}; - -class LLPanelSettingsSkyCloudTab : public LLPanelSettingsSky -{ - LOG_CLASS(LLPanelSettingsSkyCloudTab); - -public: - LLPanelSettingsSkyCloudTab(); - - virtual bool postBuild() override; - void setEnabled(bool enabled) override; - -protected: - virtual void refresh() override; - -private: - void onCloudColorChanged(); - void onCloudCoverageChanged(); - void onCloudScaleChanged(); - void onCloudVarianceChanged(); - void onCloudScrollChanged(); - void onCloudMapChanged(); - void onCloudDensityChanged(); - void onCloudDetailChanged(); -}; - -class LLPanelSettingsSkySunMoonTab : public LLPanelSettingsSky -{ - LOG_CLASS(LLPanelSettingsSkySunMoonTab); - -public: - LLPanelSettingsSkySunMoonTab(); - - virtual bool postBuild() override; - virtual void setEnabled(bool enabled) override; - -protected: - virtual void refresh() override; - -private: - void onSunMoonColorChanged(); - void onGlowChanged(); - void onStarBrightnessChanged(); - void onSunRotationChanged(); - void onSunAzimElevChanged(); - void onSunScaleChanged(); - void onSunImageChanged(); - void onMoonRotationChanged(); - void onMoonAzimElevChanged(); - void onMoonScaleChanged(); - void onMoonBrightnessChanged(); - void onMoonImageChanged(); -}; - -// single subtab of the density settings tab -class LLPanelSettingsSkyDensityTab : public LLPanelSettingsSky -{ - LOG_CLASS(LLPanelSettingsSkyDensityTab); - -public: - LLPanelSettingsSkyDensityTab(); - - virtual bool postBuild() override; - virtual void setEnabled(bool enabled) override; - -protected: - virtual void refresh() override; - - void onRayleighExponentialChanged(); - void onRayleighExponentialScaleChanged(); - void onRayleighLinearChanged(); - void onRayleighConstantChanged(); - void onRayleighMaxAltitudeChanged(); - - void onMieExponentialChanged(); - void onMieExponentialScaleChanged(); - void onMieLinearChanged(); - void onMieConstantChanged(); - void onMieAnisoFactorChanged(); - void onMieMaxAltitudeChanged(); - - void onAbsorptionExponentialChanged(); - void onAbsorptionExponentialScaleChanged(); - void onAbsorptionLinearChanged(); - void onAbsorptionConstantChanged(); - void onAbsorptionMaxAltitudeChanged(); - - // update the settings for our profile type - void updateProfile(); -}; -#endif // LLPANEL_EDIT_SKY_H +/** +* @file llpaneleditsky.h +* @brief Panels for sky settings +* +* $LicenseInfo:firstyear=2011&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2011, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#ifndef LLPANEL_EDIT_SKY_H +#define LLPANEL_EDIT_SKY_H + +#include "llpanel.h" +#include "llsettingssky.h" +#include "llfloatereditenvironmentbase.h" + +//========================================================================= +class LLSlider; +class LLColorSwatchCtrl; +class LLTextureCtrl; + +//========================================================================= +class LLPanelSettingsSky : public LLSettingsEditPanel +{ + LOG_CLASS(LLPanelSettingsSky); + +public: + LLPanelSettingsSky(); + + virtual void setSettings(const LLSettingsBase::ptr_t &settings) override { setSky(std::static_pointer_cast(settings)); } + + LLSettingsSky::ptr_t getSky() const { return mSkySettings; } + void setSky(const LLSettingsSky::ptr_t &sky) { mSkySettings = sky; clearIsDirty(); refresh(); } + +protected: + LLSettingsSky::ptr_t mSkySettings; +}; + +class LLPanelSettingsSkyAtmosTab : public LLPanelSettingsSky +{ + LOG_CLASS(LLPanelSettingsSkyAtmosTab); + +public: + LLPanelSettingsSkyAtmosTab(); + + virtual bool postBuild() override; + virtual void setEnabled(bool enabled) override; + +protected: + virtual void refresh() override; + +private: + void onAmbientLightChanged(); + void onBlueHorizonChanged(); + void onBlueDensityChanged(); + void onHazeHorizonChanged(); + void onHazeDensityChanged(); + void onSceneGammaChanged(); + void onDensityMultipChanged(); + void onDistanceMultipChanged(); + void onMaxAltChanged(); + void onMoistureLevelChanged(); + void onDropletRadiusChanged(); + void onIceLevelChanged(); + void onReflectionProbeAmbianceChanged(); + void updateGammaLabel(bool auto_adjust = false); + +}; + +class LLPanelSettingsSkyCloudTab : public LLPanelSettingsSky +{ + LOG_CLASS(LLPanelSettingsSkyCloudTab); + +public: + LLPanelSettingsSkyCloudTab(); + + virtual bool postBuild() override; + void setEnabled(bool enabled) override; + +protected: + virtual void refresh() override; + +private: + void onCloudColorChanged(); + void onCloudCoverageChanged(); + void onCloudScaleChanged(); + void onCloudVarianceChanged(); + void onCloudScrollChanged(); + void onCloudMapChanged(); + void onCloudDensityChanged(); + void onCloudDetailChanged(); +}; + +class LLPanelSettingsSkySunMoonTab : public LLPanelSettingsSky +{ + LOG_CLASS(LLPanelSettingsSkySunMoonTab); + +public: + LLPanelSettingsSkySunMoonTab(); + + virtual bool postBuild() override; + virtual void setEnabled(bool enabled) override; + +protected: + virtual void refresh() override; + +private: + void onSunMoonColorChanged(); + void onGlowChanged(); + void onStarBrightnessChanged(); + void onSunRotationChanged(); + void onSunAzimElevChanged(); + void onSunScaleChanged(); + void onSunImageChanged(); + void onMoonRotationChanged(); + void onMoonAzimElevChanged(); + void onMoonScaleChanged(); + void onMoonBrightnessChanged(); + void onMoonImageChanged(); +}; + +// single subtab of the density settings tab +class LLPanelSettingsSkyDensityTab : public LLPanelSettingsSky +{ + LOG_CLASS(LLPanelSettingsSkyDensityTab); + +public: + LLPanelSettingsSkyDensityTab(); + + virtual bool postBuild() override; + virtual void setEnabled(bool enabled) override; + +protected: + virtual void refresh() override; + + void onRayleighExponentialChanged(); + void onRayleighExponentialScaleChanged(); + void onRayleighLinearChanged(); + void onRayleighConstantChanged(); + void onRayleighMaxAltitudeChanged(); + + void onMieExponentialChanged(); + void onMieExponentialScaleChanged(); + void onMieLinearChanged(); + void onMieConstantChanged(); + void onMieAnisoFactorChanged(); + void onMieMaxAltitudeChanged(); + + void onAbsorptionExponentialChanged(); + void onAbsorptionExponentialScaleChanged(); + void onAbsorptionLinearChanged(); + void onAbsorptionConstantChanged(); + void onAbsorptionMaxAltitudeChanged(); + + // update the settings for our profile type + void updateProfile(); +}; +#endif // LLPANEL_EDIT_SKY_H diff --git a/indra/newview/llpaneleditwater.h b/indra/newview/llpaneleditwater.h index a3070343ab..69034789c4 100644 --- a/indra/newview/llpaneleditwater.h +++ b/indra/newview/llpaneleditwater.h @@ -1,98 +1,98 @@ -/** -* @file llpaneleditwater.h -* @brief Panels for water settings -* -* $LicenseInfo:firstyear=2011&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2011, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#ifndef LLPANEL_EDIT_WATER_H -#define LLPANEL_EDIT_WATER_H - -#include "llpanel.h" -#include "llsettingswater.h" - -#include "llfloatereditenvironmentbase.h" - -//========================================================================= -class LLSlider; -class LLColorSwatchCtrl; -class LLTextureCtrl; -class LLXYVector; - -//========================================================================= -class LLPanelSettingsWater : public LLSettingsEditPanel -{ - LOG_CLASS(LLPanelSettingsWater); - -public: - LLPanelSettingsWater(); - - virtual void setSettings(const LLSettingsBase::ptr_t &settings) override { setWater(std::static_pointer_cast(settings)); } - - LLSettingsWater::ptr_t getWater() const { return mWaterSettings; } - void setWater(const LLSettingsWater::ptr_t &water) { mWaterSettings = water; clearIsDirty(); refresh(); } - -protected: - LLSettingsWater::ptr_t mWaterSettings; -}; - -// *RIDER* In this case this split is unecessary since there is only a single -// tab page for water settings at this point. However more may be added in the -// future and I want to reinforce the pattern used for sky/atmosphere tabs. -class LLPanelSettingsWaterMainTab : public LLPanelSettingsWater -{ - LOG_CLASS(LLPanelSettingsWaterMainTab); - -public: - LLPanelSettingsWaterMainTab(); - - virtual bool postBuild() override; - virtual void setEnabled(bool enabled) override; - -protected: - virtual void refresh() override; - -private: - - LLColorSwatchCtrl * mClrFogColor; -// LLSlider * mSldFogDensity; -// LLSlider * mSldUnderWaterMod; - LLTextureCtrl * mTxtNormalMap; - - void onFogColorChanged(); - void onFogDensityChanged(); - void onFogUnderWaterChanged(); - void onNormalMapChanged(); - - void onLargeWaveChanged(); - void onSmallWaveChanged(); - - void onNormalScaleChanged(); - void onFresnelScaleChanged(); - void onFresnelOffsetChanged(); - void onScaleAboveChanged(); - void onScaleBelowChanged(); - void onBlurMultipChanged(); -}; - - -#endif // LLPANEL_EDIT_WATER_H +/** +* @file llpaneleditwater.h +* @brief Panels for water settings +* +* $LicenseInfo:firstyear=2011&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2011, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#ifndef LLPANEL_EDIT_WATER_H +#define LLPANEL_EDIT_WATER_H + +#include "llpanel.h" +#include "llsettingswater.h" + +#include "llfloatereditenvironmentbase.h" + +//========================================================================= +class LLSlider; +class LLColorSwatchCtrl; +class LLTextureCtrl; +class LLXYVector; + +//========================================================================= +class LLPanelSettingsWater : public LLSettingsEditPanel +{ + LOG_CLASS(LLPanelSettingsWater); + +public: + LLPanelSettingsWater(); + + virtual void setSettings(const LLSettingsBase::ptr_t &settings) override { setWater(std::static_pointer_cast(settings)); } + + LLSettingsWater::ptr_t getWater() const { return mWaterSettings; } + void setWater(const LLSettingsWater::ptr_t &water) { mWaterSettings = water; clearIsDirty(); refresh(); } + +protected: + LLSettingsWater::ptr_t mWaterSettings; +}; + +// *RIDER* In this case this split is unecessary since there is only a single +// tab page for water settings at this point. However more may be added in the +// future and I want to reinforce the pattern used for sky/atmosphere tabs. +class LLPanelSettingsWaterMainTab : public LLPanelSettingsWater +{ + LOG_CLASS(LLPanelSettingsWaterMainTab); + +public: + LLPanelSettingsWaterMainTab(); + + virtual bool postBuild() override; + virtual void setEnabled(bool enabled) override; + +protected: + virtual void refresh() override; + +private: + + LLColorSwatchCtrl * mClrFogColor; +// LLSlider * mSldFogDensity; +// LLSlider * mSldUnderWaterMod; + LLTextureCtrl * mTxtNormalMap; + + void onFogColorChanged(); + void onFogDensityChanged(); + void onFogUnderWaterChanged(); + void onNormalMapChanged(); + + void onLargeWaveChanged(); + void onSmallWaveChanged(); + + void onNormalScaleChanged(); + void onFresnelScaleChanged(); + void onFresnelOffsetChanged(); + void onScaleAboveChanged(); + void onScaleBelowChanged(); + void onBlurMultipChanged(); +}; + + +#endif // LLPANEL_EDIT_WATER_H diff --git a/indra/newview/llpaneleditwearable.h b/indra/newview/llpaneleditwearable.h index 46df984ede..aa4ac915c7 100644 --- a/indra/newview/llpaneleditwearable.h +++ b/indra/newview/llpaneleditwearable.h @@ -1,180 +1,180 @@ -/** - * @file llpaneleditwearable.h - * @brief A LLPanel dedicated to the editing of wearables. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELEDITWEARABLE_H -#define LL_LLPANELEDITWEARABLE_H - -#include "llpanel.h" -#include "llscrollingpanellist.h" -#include "llmodaldialog.h" -#include "llavatarappearancedefines.h" -#include "llwearabletype.h" - -class LLAccordionCtrl; -class LLCheckBoxCtrl; -class LLViewerWearable; -class LLTextBox; -class LLViewerInventoryItem; -class LLViewerVisualParam; -class LLVisualParamHint; -class LLViewerJointMesh; -class LLAccordionCtrlTab; -class LLJoint; -class LLLineEditor; - -class LLPanelEditWearable : public LLPanel -{ -public: - LLPanelEditWearable( ); - virtual ~LLPanelEditWearable(); - - /*virtual*/ bool postBuild(); - /*virtual*/ bool isDirty() const; // LLUICtrl - /*virtual*/ void draw(); - void onClose(); - - // changes camera angle to default for selected subpart - void changeCamera(U8 subpart); - - LLViewerWearable* getWearable() { return mWearablePtr; } - void setWearable(LLViewerWearable *wearable, bool disable_camera_switch = false); - - void saveChanges(bool force_save_as = false); - void revertChanges(); - - void showDefaultSubpart(); - void onTabExpandedCollapsed(const LLSD& param, U8 index); - - void updateScrollingPanelList(); - - static void onRevertButtonClicked(void* userdata); - static void onBackButtonClicked(void* userdata); - void onCommitSexChange(); - void onSaveAsButtonClicked(); - void saveAsCallback(const LLSD& notification, const LLSD& response); - - virtual void setVisible(bool visible); - -private: - typedef std::map value_map_t; - - void showWearable(LLViewerWearable* wearable, bool show, bool disable_camera_switch = false); - void updateScrollingPanelUI(); - LLPanel* getPanel(LLWearableType::EType type); - void getSortedParams(value_map_t &sorted_params, const std::string &edit_group); - void buildParamList(LLScrollingPanelList *panel_list, value_map_t &sorted_params, LLAccordionCtrlTab *tab, LLJoint* jointp); - // update bottom bar buttons ("Save", "Revert", etc) - void updateVerbs(); - - void onColorSwatchCommit(const LLUICtrl*); - void onTexturePickerCommit(const LLUICtrl*); - void updatePanelPickerControls(LLWearableType::EType type); - void toggleTypeSpecificControls(LLWearableType::EType type); - void updateTypeSpecificControls(LLWearableType::EType type); - - //alpha mask checkboxes - void configureAlphaCheckbox(LLAvatarAppearanceDefines::ETextureIndex te, const std::string& name); - void onInvisibilityCommit(LLCheckBoxCtrl* checkbox_ctrl, LLAvatarAppearanceDefines::ETextureIndex te); - void updateAlphaCheckboxes(); - void initPreviousAlphaTextures(); - void initPreviousAlphaTextureEntry(LLAvatarAppearanceDefines::ETextureIndex te); - - // callback for HeightUnits parameter. - bool changeHeightUnits(const LLSD& new_value); - - // updates current metric and replacement metric label text - void updateMetricLayout(bool new_value); - - // updates avatar height label - void updateAvatarHeightLabel(); - - void onWearablePanelVisibilityChange(const LLSD &in_visible_chain, LLAccordionCtrl* accordion_ctrl); - - void setWearablePanelVisibilityChangeCallback(LLPanel* bodypart_panel); - - // *HACK Remove this when serverside texture baking is available on all regions. - void incrementCofVersionLegacy(); - - // the pointer to the wearable we're editing. NULL means we're not editing a wearable. - LLViewerWearable *mWearablePtr; - LLViewerInventoryItem* mWearableItem; - - // these are constant no matter what wearable we're editing - LLButton *mBtnRevert; - LLButton *mBtnBack; - std::string mBackBtnLabel; - - LLTextBox *mPanelTitle; - LLTextBox *mDescTitle; - LLTextBox *mTxtAvatarHeight; - - - // localized and parameterized strings that used to build avatar_height_label - std::string mMeters; - std::string mFeet; - std::string mHeight; - LLUIString mHeightValue; - LLUIString mReplacementMetricUrl; - - // color for mHeight string - LLUIColor mAvatarHeightLabelColor; - // color for mHeightValue string - LLUIColor mAvatarHeightValueLabelColor; - - // This text editor reference will change each time we edit a new wearable - - // it will be grabbed from the currently visible panel - LLLineEditor *mNameEditor; - - // The following panels will be shown/hidden based on what wearable we're editing - // body parts - LLPanel *mPanelShape; - LLPanel *mPanelSkin; - LLPanel *mPanelEyes; - LLPanel *mPanelHair; - - //clothes - LLPanel *mPanelShirt; - LLPanel *mPanelPants; - LLPanel *mPanelShoes; - LLPanel *mPanelSocks; - LLPanel *mPanelJacket; - LLPanel *mPanelGloves; - LLPanel *mPanelUndershirt; - LLPanel *mPanelUnderpants; - LLPanel *mPanelSkirt; - LLPanel *mPanelAlpha; - LLPanel *mPanelTattoo; - LLPanel *mPanelUniversal; - LLPanel *mPanelPhysics; - - typedef std::map string_texture_index_map_t; - string_texture_index_map_t mAlphaCheckbox2Index; - - typedef std::map s32_uuid_map_t; - s32_uuid_map_t mPreviousAlphaTexture; -}; - -#endif +/** + * @file llpaneleditwearable.h + * @brief A LLPanel dedicated to the editing of wearables. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELEDITWEARABLE_H +#define LL_LLPANELEDITWEARABLE_H + +#include "llpanel.h" +#include "llscrollingpanellist.h" +#include "llmodaldialog.h" +#include "llavatarappearancedefines.h" +#include "llwearabletype.h" + +class LLAccordionCtrl; +class LLCheckBoxCtrl; +class LLViewerWearable; +class LLTextBox; +class LLViewerInventoryItem; +class LLViewerVisualParam; +class LLVisualParamHint; +class LLViewerJointMesh; +class LLAccordionCtrlTab; +class LLJoint; +class LLLineEditor; + +class LLPanelEditWearable : public LLPanel +{ +public: + LLPanelEditWearable( ); + virtual ~LLPanelEditWearable(); + + /*virtual*/ bool postBuild(); + /*virtual*/ bool isDirty() const; // LLUICtrl + /*virtual*/ void draw(); + void onClose(); + + // changes camera angle to default for selected subpart + void changeCamera(U8 subpart); + + LLViewerWearable* getWearable() { return mWearablePtr; } + void setWearable(LLViewerWearable *wearable, bool disable_camera_switch = false); + + void saveChanges(bool force_save_as = false); + void revertChanges(); + + void showDefaultSubpart(); + void onTabExpandedCollapsed(const LLSD& param, U8 index); + + void updateScrollingPanelList(); + + static void onRevertButtonClicked(void* userdata); + static void onBackButtonClicked(void* userdata); + void onCommitSexChange(); + void onSaveAsButtonClicked(); + void saveAsCallback(const LLSD& notification, const LLSD& response); + + virtual void setVisible(bool visible); + +private: + typedef std::map value_map_t; + + void showWearable(LLViewerWearable* wearable, bool show, bool disable_camera_switch = false); + void updateScrollingPanelUI(); + LLPanel* getPanel(LLWearableType::EType type); + void getSortedParams(value_map_t &sorted_params, const std::string &edit_group); + void buildParamList(LLScrollingPanelList *panel_list, value_map_t &sorted_params, LLAccordionCtrlTab *tab, LLJoint* jointp); + // update bottom bar buttons ("Save", "Revert", etc) + void updateVerbs(); + + void onColorSwatchCommit(const LLUICtrl*); + void onTexturePickerCommit(const LLUICtrl*); + void updatePanelPickerControls(LLWearableType::EType type); + void toggleTypeSpecificControls(LLWearableType::EType type); + void updateTypeSpecificControls(LLWearableType::EType type); + + //alpha mask checkboxes + void configureAlphaCheckbox(LLAvatarAppearanceDefines::ETextureIndex te, const std::string& name); + void onInvisibilityCommit(LLCheckBoxCtrl* checkbox_ctrl, LLAvatarAppearanceDefines::ETextureIndex te); + void updateAlphaCheckboxes(); + void initPreviousAlphaTextures(); + void initPreviousAlphaTextureEntry(LLAvatarAppearanceDefines::ETextureIndex te); + + // callback for HeightUnits parameter. + bool changeHeightUnits(const LLSD& new_value); + + // updates current metric and replacement metric label text + void updateMetricLayout(bool new_value); + + // updates avatar height label + void updateAvatarHeightLabel(); + + void onWearablePanelVisibilityChange(const LLSD &in_visible_chain, LLAccordionCtrl* accordion_ctrl); + + void setWearablePanelVisibilityChangeCallback(LLPanel* bodypart_panel); + + // *HACK Remove this when serverside texture baking is available on all regions. + void incrementCofVersionLegacy(); + + // the pointer to the wearable we're editing. NULL means we're not editing a wearable. + LLViewerWearable *mWearablePtr; + LLViewerInventoryItem* mWearableItem; + + // these are constant no matter what wearable we're editing + LLButton *mBtnRevert; + LLButton *mBtnBack; + std::string mBackBtnLabel; + + LLTextBox *mPanelTitle; + LLTextBox *mDescTitle; + LLTextBox *mTxtAvatarHeight; + + + // localized and parameterized strings that used to build avatar_height_label + std::string mMeters; + std::string mFeet; + std::string mHeight; + LLUIString mHeightValue; + LLUIString mReplacementMetricUrl; + + // color for mHeight string + LLUIColor mAvatarHeightLabelColor; + // color for mHeightValue string + LLUIColor mAvatarHeightValueLabelColor; + + // This text editor reference will change each time we edit a new wearable - + // it will be grabbed from the currently visible panel + LLLineEditor *mNameEditor; + + // The following panels will be shown/hidden based on what wearable we're editing + // body parts + LLPanel *mPanelShape; + LLPanel *mPanelSkin; + LLPanel *mPanelEyes; + LLPanel *mPanelHair; + + //clothes + LLPanel *mPanelShirt; + LLPanel *mPanelPants; + LLPanel *mPanelShoes; + LLPanel *mPanelSocks; + LLPanel *mPanelJacket; + LLPanel *mPanelGloves; + LLPanel *mPanelUndershirt; + LLPanel *mPanelUnderpants; + LLPanel *mPanelSkirt; + LLPanel *mPanelAlpha; + LLPanel *mPanelTattoo; + LLPanel *mPanelUniversal; + LLPanel *mPanelPhysics; + + typedef std::map string_texture_index_map_t; + string_texture_index_map_t mAlphaCheckbox2Index; + + typedef std::map s32_uuid_map_t; + s32_uuid_map_t mPreviousAlphaTexture; +}; + +#endif diff --git a/indra/newview/llpanelemojicomplete.cpp b/indra/newview/llpanelemojicomplete.cpp index c794ca44ec..5cbc565a70 100644 --- a/indra/newview/llpanelemojicomplete.cpp +++ b/indra/newview/llpanelemojicomplete.cpp @@ -1,4 +1,4 @@ -/** +/** * @file llpanelemojicomplete.h * @brief Header file for LLPanelEmojiComplete * diff --git a/indra/newview/llpanelenvironment.cpp b/indra/newview/llpanelenvironment.cpp index a58c3929ac..f0cb5d68aa 100644 --- a/indra/newview/llpanelenvironment.cpp +++ b/indra/newview/llpanelenvironment.cpp @@ -1,1178 +1,1178 @@ -/** - * @file llpanelenvironment.cpp - * @brief LLPanelExperiences class implementation - * - * $LicenseInfo:firstyear=2013&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2013, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - - -#include "llpanelprofile.h" -#include "lluictrlfactory.h" -#include "llexperiencecache.h" -#include "llagent.h" -#include "llparcel.h" - -#include "llviewerregion.h" -#include "llpanelenvironment.h" -#include "llslurl.h" -#include "lllayoutstack.h" - -#include "llfloater.h" -#include "llfloaterreg.h" -#include "llfloatereditextdaycycle.h" -#include "llmultisliderctrl.h" -#include "llnotificationsutil.h" -#include "llsettingsvo.h" - -#include "llappviewer.h" -#include "llcallbacklist.h" -#include "llviewerparcelmgr.h" - -#include "llinventorymodel.h" - -//========================================================================= -namespace -{ - const std::string FLOATER_DAY_CYCLE_EDIT("env_edit_extdaycycle"); - const std::string STRING_REGION_ENV("str_region_env"); - const std::string STRING_EMPTY_NAME("str_empty"); - - inline bool ends_with(std::string const & value, std::string const & ending) - { - if (ending.size() > value.size()) - return false; - return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); - } - -} - -//========================================================================= -const std::string LLPanelEnvironmentInfo::BTN_SELECTINV("btn_select_inventory"); -const std::string LLPanelEnvironmentInfo::BTN_EDIT("btn_edit"); -const std::string LLPanelEnvironmentInfo::BTN_USEDEFAULT("btn_usedefault"); -const std::string LLPanelEnvironmentInfo::BTN_RST_ALTITUDES("btn_rst_altitudes"); -const std::string LLPanelEnvironmentInfo::SLD_DAYLENGTH("sld_day_length"); -const std::string LLPanelEnvironmentInfo::SLD_DAYOFFSET("sld_day_offset"); -const std::string LLPanelEnvironmentInfo::SLD_ALTITUDES("sld_altitudes"); -const std::string LLPanelEnvironmentInfo::ICN_GROUND("icon_ground"); -const std::string LLPanelEnvironmentInfo::ICN_WATER("icon_water"); -const std::string LLPanelEnvironmentInfo::CHK_ALLOWOVERRIDE("chk_allow_override"); -const std::string LLPanelEnvironmentInfo::LBL_TIMEOFDAY("lbl_apparent_time"); -const std::string LLPanelEnvironmentInfo::PNL_SETTINGS("pnl_environment_config"); -const std::string LLPanelEnvironmentInfo::PNL_ENVIRONMENT_ALTITUDES("pnl_environment_altitudes"); -const std::string LLPanelEnvironmentInfo::PNL_BUTTONS("pnl_environment_buttons"); -const std::string LLPanelEnvironmentInfo::PNL_DISABLED("pnl_environment_disabled"); -const std::string LLPanelEnvironmentInfo::TXT_DISABLED("txt_environment_disabled"); -const std::string LLPanelEnvironmentInfo::PNL_REGION_MSG("pnl_environment_region_msg"); -const std::string LLPanelEnvironmentInfo::SDT_DROP_TARGET("sdt_drop_target"); - -const std::string LLPanelEnvironmentInfo::STR_LABEL_USEDEFAULT("str_label_use_default"); -const std::string LLPanelEnvironmentInfo::STR_LABEL_USEREGION("str_label_use_region"); -const std::string LLPanelEnvironmentInfo::STR_ALTITUDE_DESCRIPTION("str_altitude_desription"); -const std::string LLPanelEnvironmentInfo::STR_NO_PARCEL("str_no_parcel"); -const std::string LLPanelEnvironmentInfo::STR_CROSS_REGION("str_cross_region"); -const std::string LLPanelEnvironmentInfo::STR_LEGACY("str_legacy"); -const std::string LLPanelEnvironmentInfo::STR_DISALLOWED("str_disallowed"); -const std::string LLPanelEnvironmentInfo::STR_TOO_SMALL("str_too_small"); - -const S32 LLPanelEnvironmentInfo::MINIMUM_PARCEL_SIZE(128); - -const U32 LLPanelEnvironmentInfo::DIRTY_FLAG_DAYCYCLE(0x01 << 0); -const U32 LLPanelEnvironmentInfo::DIRTY_FLAG_DAYLENGTH(0x01 << 1); -const U32 LLPanelEnvironmentInfo::DIRTY_FLAG_DAYOFFSET(0x01 << 2); -const U32 LLPanelEnvironmentInfo::DIRTY_FLAG_ALTITUDES(0x01 << 3); - -const U32 LLPanelEnvironmentInfo::DIRTY_FLAG_MASK( - LLPanelEnvironmentInfo::DIRTY_FLAG_DAYCYCLE | - LLPanelEnvironmentInfo::DIRTY_FLAG_DAYLENGTH | - LLPanelEnvironmentInfo::DIRTY_FLAG_DAYOFFSET | - LLPanelEnvironmentInfo::DIRTY_FLAG_ALTITUDES); - -const U32 ALTITUDE_SLIDER_COUNT = 3; -const F32 ALTITUDE_DEFAULT_HEIGHT_STEP = 1000; -const U32 ALTITUDE_MARKERS_COUNT = 3; -const U32 ALTITUDE_PREFIXERS_COUNT = 5; - -const std::string slider_marker_base = "mark"; - -const std::string alt_sliders[] = { - "sld1", - "sld2", - "sld3", -}; - -const std::string alt_prefixes[] = { - "alt1", - "alt2", - "alt3", - "ground", - "water", -}; - -const std::string alt_panels[] = { - "pnl_alt1", - "pnl_alt2", - "pnl_alt3", - "pnl_ground", - "pnl_water", -}; - -static LLDefaultChildRegistry::Register r("settings_drop_target"); - -//========================================================================= -LLPanelEnvironmentInfo::LLPanelEnvironmentInfo(): - mCurrentEnvironment(), - mDirtyFlag(0), - mEditorLastParcelId(INVALID_PARCEL_ID), - mCrossRegion(false), - mNoSelection(false), - mNoEnvironment(false), - mCurEnvVersion(INVALID_PARCEL_ENVIRONMENT_VERSION), - mSettingsFloater(), - mEditFloater(), - mAllowOverride(true) -{ -} - -LLPanelEnvironmentInfo::~LLPanelEnvironmentInfo() -{ - if (mChangeMonitor.connected()) - mChangeMonitor.disconnect(); - if (mCommitConnection.connected()) - mCommitConnection.disconnect(); - if (mUpdateConnection.connected()) - mUpdateConnection.disconnect(); -} - -bool LLPanelEnvironmentInfo::postBuild() -{ - - getChild(BTN_USEDEFAULT)->setCommitCallback([this](LLUICtrl *, const LLSD &){ onBtnDefault(); }); - getChild(BTN_SELECTINV)->setCommitCallback([this](LLUICtrl *, const LLSD &){ onBtnSelect(); }); - getChild(BTN_EDIT)->setCommitCallback([this](LLUICtrl *, const LLSD &){ onBtnEdit(); }); - getChild(BTN_RST_ALTITUDES)->setCommitCallback([this](LLUICtrl *, const LLSD &){ onBtnRstAltitudes(); }); - - getChild(SLD_DAYLENGTH)->setCommitCallback([this](LLUICtrl *, const LLSD &value) { onSldDayLengthChanged(value.asReal()); }); - getChild(SLD_DAYLENGTH)->setSliderMouseUpCallback([this](LLUICtrl *, const LLSD &) { onDayLenOffsetMouseUp(); }); - getChild(SLD_DAYLENGTH)->setSliderEditorCommitCallback([this](LLUICtrl *, const LLSD &) { onDayLenOffsetMouseUp(); }); - getChild(SLD_DAYOFFSET)->setCommitCallback([this](LLUICtrl *, const LLSD &value) { onSldDayOffsetChanged(value.asReal()); }); - getChild(SLD_DAYOFFSET)->setSliderMouseUpCallback([this](LLUICtrl *, const LLSD &) { onDayLenOffsetMouseUp(); }); - getChild(SLD_DAYOFFSET)->setSliderEditorCommitCallback([this](LLUICtrl *, const LLSD &) { onDayLenOffsetMouseUp(); }); - - getChild(SLD_ALTITUDES)->setCommitCallback([this](LLUICtrl *cntrl, const LLSD &value) { onAltSliderCallback(cntrl, value); }); - getChild(SLD_ALTITUDES)->setSliderMouseUpCallback([this](LLUICtrl *, const LLSD &) { onAltSliderMouseUp(); }); - - mChangeMonitor = LLEnvironment::instance().setEnvironmentChanged([this](LLEnvironment::EnvSelection_t env, S32 version) { onEnvironmentChanged(env, version); }); - - for (U32 idx = 0; idx < ALTITUDE_SLIDER_COUNT; idx++) - { - LLSettingsDropTarget* drop_target = findChild("sdt_" + alt_prefixes[idx]); - if (drop_target) - { - drop_target->setPanel(this, alt_sliders[idx]); - } - // set initial values to prevent [ALTITUDE] from displaying - updateAltLabel(alt_prefixes[idx], idx + 2, idx * 1000); - } - getChild("sdt_" + alt_prefixes[3])->setPanel(this, alt_prefixes[3]); - getChild("sdt_" + alt_prefixes[4])->setPanel(this, alt_prefixes[4]); - - return true; -} - -// virtual -void LLPanelEnvironmentInfo::onOpen(const LLSD& key) -{ - refreshFromSource(); -} - -// virtual -void LLPanelEnvironmentInfo::onVisibilityChange(bool new_visibility) -{ - if (new_visibility) - { - gIdleCallbacks.addFunction(onIdlePlay, this); - } - else - { - commitDayLenOffsetChanges(false); // arrow-key changes - - LLFloaterSettingsPicker *picker = getSettingsPicker(false); - if (picker) - { - picker->closeFloater(); - } - - gIdleCallbacks.deleteFunction(onIdlePlay, this); - LLFloaterEditExtDayCycle *dayeditor = getEditFloater(false); - if (mCommitConnection.connected()) - mCommitConnection.disconnect(); - - if (dayeditor) - { - if (dayeditor->isDirty()) - dayeditor->refresh(); - else - { - dayeditor->closeFloater(); - mEditFloater.markDead(); - } - } - } - -} - -void LLPanelEnvironmentInfo::refresh() -{ - if (gDisconnected) - return; - - if (!setControlsEnabled(canEdit())) - return; - - if (!mCurrentEnvironment) - { - return; - } - - F32Hours daylength(mCurrentEnvironment->mDayLength); - F32Hours dayoffset(mCurrentEnvironment->mDayOffset); - - if (dayoffset.value() > 12.0f) - dayoffset -= F32Hours(24.0); - - getChild(SLD_DAYLENGTH)->setValue(daylength.value()); - getChild(SLD_DAYOFFSET)->setValue(dayoffset.value()); - - udpateApparentTimeOfDay(); - - updateEditFloater(mCurrentEnvironment, canEdit()); - - LLEnvironment::altitude_list_t altitudes = mCurrentEnvironment->mAltitudes; - - if (altitudes.size() > 0) - { - LLMultiSliderCtrl *sld = getChild(SLD_ALTITUDES); - sld->clear(); - - for (S32 idx = 0; idx < ALTITUDE_SLIDER_COUNT; ++idx) - { - // make sure values are in range, server is supposed to validate them, - // but issues happen, try to fix values in such case - F32 altitude = llclamp(altitudes[idx + 1], sld->getMinValue(), sld->getMaxValue()); - bool res = sld->addSlider(altitude, alt_sliders[idx]); - if (!res) - { - LL_WARNS_ONCE("ENVPANEL") << "Failed to validate altitude from server for parcel id" << getParcelId() << LL_ENDL; - // Find a spot to insert altitude. - // Assuming everything alright with slider, we should find new place in 11 steps top (step 25m, no overlap 100m) - F32 alt_step = (altitude > (sld->getMaxValue() / 2)) ? -sld->getIncrement() : sld->getIncrement(); - for (U32 i = 0; i < 30; i++) - { - altitude += alt_step; - if (altitude > sld->getMaxValue()) - { - altitude = sld->getMinValue(); - } - else if (altitude < sld->getMinValue()) - { - altitude = sld->getMaxValue(); - } - res = sld->addSlider(altitude, alt_sliders[idx]); - if (res) break; - } - } - if (res) - { - // slider has some auto correction that might have kicked in - altitude = sld->getSliderValue(alt_sliders[idx]); - } - else - { - // Something is very very wrong - LL_WARNS_ONCE("ENVPANEL") << "Failed to set up altitudes for parcel id " << getParcelId() << LL_ENDL; - } - updateAltLabel(alt_prefixes[idx], idx + 2, altitude); - mAltitudes[alt_sliders[idx]] = AltitudeData(idx + 2, idx, altitude); - } - if (sld->getCurNumSliders() != ALTITUDE_SLIDER_COUNT) - { - LL_WARNS("ENVPANEL") << "Failed to add altitude sliders!" << LL_ENDL; - } - readjustAltLabels(); - sld->resetCurSlider(); - } - - updateAltLabel(alt_prefixes[3], 1, 0); // ground - updateAltLabel(alt_prefixes[4], 0, 0); // water - -} - -void LLPanelEnvironmentInfo::refreshFromEstate() -{ - LLViewerRegion *pRegion = gAgent.getRegion(); - - bool oldAO = mAllowOverride; - mAllowOverride = (isRegion() && LLEstateInfoModel::instance().getAllowEnvironmentOverride()) || pRegion->getAllowEnvironmentOverride(); - if (oldAO != mAllowOverride) - refresh(); -} - -std::string LLPanelEnvironmentInfo::getNameForTrackIndex(S32 index) -{ - std::string invname; - if (!mCurrentEnvironment || index < LLSettingsDay::TRACK_WATER || index >= LLSettingsDay::TRACK_MAX) - { - invname = getString(STRING_EMPTY_NAME); - } - else if (mCurrentEnvironment->mDayCycleName.empty()) - { - invname = mCurrentEnvironment->mNameList[index]; - - if (invname.empty()) - { - if (index <= LLSettingsDay::TRACK_GROUND_LEVEL) - invname = getString(isRegion() ? STRING_EMPTY_NAME : STRING_REGION_ENV); - } - } - else if (!mCurrentEnvironment->mDayCycle->isTrackEmpty(index)) - { - invname = mCurrentEnvironment->mDayCycleName; - } - - - if (invname.empty()) - { - invname = getNameForTrackIndex(index - 1); - if (invname[0] != '(') - invname = "(" + invname + ")"; - } - - return invname; -} - -LLFloaterSettingsPicker * LLPanelEnvironmentInfo::getSettingsPicker(bool create) -{ - LLFloaterSettingsPicker *picker = static_cast(mSettingsFloater.get()); - - // Show the dialog - if (!picker && create) - { - picker = new LLFloaterSettingsPicker(this, - LLUUID::null); - - mSettingsFloater = picker->getHandle(); - - picker->setCommitCallback([this](LLUICtrl *, const LLSD &data){ onPickerCommitted(data["ItemId"].asUUID()); }); - } - - return picker; -} - -LLFloaterEditExtDayCycle * LLPanelEnvironmentInfo::getEditFloater(bool create) -{ - static const S32 FOURHOURS(4 * 60 * 60); - LLFloaterEditExtDayCycle *editor = static_cast(mEditFloater.get()); - - // Show the dialog - if (!editor && create) - { - LLSD params(LLSDMap(LLFloaterEditExtDayCycle::KEY_EDIT_CONTEXT, isRegion() ? LLFloaterEditExtDayCycle::CONTEXT_REGION : LLFloaterEditExtDayCycle::CONTEXT_PARCEL) - (LLFloaterEditExtDayCycle::KEY_DAY_LENGTH, mCurrentEnvironment ? (S32)(mCurrentEnvironment->mDayLength.value()) : FOURHOURS)); - - editor = (LLFloaterEditExtDayCycle *)LLFloaterReg::getInstance(FLOATER_DAY_CYCLE_EDIT, params); - - if (!editor) - return nullptr; - mEditFloater = editor->getHandle(); - } - - if (editor && !mCommitConnection.connected()) - mCommitConnection = editor->setEditCommitSignal([this](LLSettingsDay::ptr_t pday) { onEditCommitted(pday); }); - - return editor; -} - - -void LLPanelEnvironmentInfo::updateEditFloater(const LLEnvironment::EnvironmentInfo::ptr_t &nextenv, bool enable) -{ - LLFloaterEditExtDayCycle *dayeditor(getEditFloater(false)); - - if (!dayeditor || !dayeditor->isInVisibleChain()) - return; - - if (!nextenv || !nextenv->mDayCycle || !enable) - { - if (mCommitConnection.connected()) - mCommitConnection.disconnect(); - - if (dayeditor->isDirty()) - dayeditor->refresh(); - else - dayeditor->closeFloater(); - } - else if (dayeditor->getEditingAssetId() != nextenv->mDayCycle->getAssetId() - || mEditorLastParcelId != nextenv->mParcelId - || mEditorLastRegionId != nextenv->mRegionId) - { - // Ignore dirty - // If parcel selection changed whatever we do except saving to inventory with - // old settings will be invalid. - mEditorLastParcelId = nextenv->mParcelId; - mEditorLastRegionId = nextenv->mRegionId; - - dayeditor->setEditDayCycle(nextenv->mDayCycle); - } -} - -bool LLPanelEnvironmentInfo::setControlsEnabled(bool enabled) -{ - bool is_unavailable(false); - bool is_legacy = (mCurrentEnvironment) ? mCurrentEnvironment->mIsLegacy : true; - bool is_bigenough = isLargeEnough(); - - if (mNoEnvironment || (!LLEnvironment::instance().isExtendedEnvironmentEnabled() && !isRegion())) - { - is_unavailable = true; - getChild(TXT_DISABLED)->setText(getString(STR_LEGACY)); - } - else if (mNoSelection) - { - is_unavailable = true; - getChild(TXT_DISABLED)->setText(getString(STR_NO_PARCEL)); - } - else if (mCrossRegion) - { - is_unavailable = true; - getChild(TXT_DISABLED)->setText(getString(STR_CROSS_REGION)); - } - else if (!isRegion() && !mAllowOverride) - { - is_unavailable = true; - getChild(TXT_DISABLED)->setText(getString(STR_DISALLOWED)); - } - else if (!is_bigenough) - { - is_unavailable = true; - getChild(TXT_DISABLED)->setText(getString(STR_TOO_SMALL)); - } - - if (is_unavailable) - { - getChild(PNL_SETTINGS)->setVisible(false); - getChild(PNL_BUTTONS)->setVisible(false); - getChild(PNL_DISABLED)->setVisible(true); - getChild(PNL_ENVIRONMENT_ALTITUDES)->setVisible(false); - getChild(PNL_REGION_MSG)->setVisible(false); - updateEditFloater(mCurrentEnvironment, false); - - return false; - } - getChild(PNL_SETTINGS)->setVisible(true); - getChild(PNL_BUTTONS)->setVisible(true); - getChild(PNL_DISABLED)->setVisible(false); - getChild(PNL_REGION_MSG)->setVisible(isRegion()); - - getChild(PNL_ENVIRONMENT_ALTITUDES)->setVisible(LLEnvironment::instance().isExtendedEnvironmentEnabled()); - getChild(BTN_RST_ALTITUDES)->setVisible(isRegion()); - - bool can_enable = enabled && !is_legacy && mCurrentEnvironment && (mCurEnvVersion != INVALID_PARCEL_ENVIRONMENT_VERSION); - getChild(BTN_SELECTINV)->setEnabled(can_enable); - getChild(BTN_USEDEFAULT)->setEnabled(can_enable); - getChild(BTN_EDIT)->setEnabled(can_enable); - getChild(SLD_DAYLENGTH)->setEnabled(can_enable); - getChild(SLD_DAYOFFSET)->setEnabled(can_enable); - getChild(SLD_ALTITUDES)->setEnabled(can_enable && isRegion()); - getChild(ICN_GROUND)->setColor((can_enable && isRegion()) ? LLColor4::white : LLColor4::grey % 0.8f); - getChild(ICN_WATER)->setColor((can_enable && isRegion()) ? LLColor4::white : LLColor4::grey % 0.8f); - getChild(BTN_RST_ALTITUDES)->setEnabled(can_enable && isRegion()); - getChild(PNL_ENVIRONMENT_ALTITUDES)->setEnabled(can_enable); - getChild(CHK_ALLOWOVERRIDE)->setEnabled(can_enable && isRegion()); - - for (U32 idx = 0; idx < ALTITUDE_MARKERS_COUNT; idx++) - { - LLUICtrl* marker = findChild(slider_marker_base + llformat("%u", idx)); - if (marker) - { - static LLColor4 marker_color(0.75f, 0.75f, 0.75f, 1.f); - marker->setColor((can_enable && isRegion()) ? marker_color : marker_color % 0.3f); - } - } - - for (U32 idx = 0; idx < ALTITUDE_PREFIXERS_COUNT; idx++) - { - LLSettingsDropTarget* drop_target = findChild("sdt_" + alt_prefixes[idx]); - if (drop_target) - { - drop_target->setDndEnabled(can_enable); - } - } - - return true; -} - -void LLPanelEnvironmentInfo::setDirtyFlag(U32 flag) -{ - mDirtyFlag |= flag; -} - -void LLPanelEnvironmentInfo::clearDirtyFlag(U32 flag) -{ - mDirtyFlag &= ~flag; -} - -void LLPanelEnvironmentInfo::updateAltLabel(const std::string &alt_prefix, U32 sky_index, F32 alt_value) -{ - LLMultiSliderCtrl *sld = findChild(SLD_ALTITUDES); - if (!sld) - { - LL_WARNS() << "Failed to find slider " << SLD_ALTITUDES << LL_ENDL; - return; - } - LLRect sld_rect = sld->getRect(); - S32 sld_range = sld_rect.getHeight(); - S32 sld_bottom = sld_rect.mBottom; - S32 sld_offset = sld_rect.getWidth(); // Roughly identical to thumb's width in slider. - S32 pos = (sld_range - sld_offset) * ((alt_value - 100) / (4000 - 100)); - - // get related views - LLTextBox* text = findChild("txt_" + alt_prefix); - LLLineEditor *field = findChild("edt_invname_" + alt_prefix); - LLView *alt_panel = findChild("pnl_" + alt_prefix); - - if (text && (sky_index > 1)) - { - // update text - std::ostringstream convert; - convert << alt_value; - text->setTextArg("[ALTITUDE]", convert.str()); - convert.str(""); - convert.clear(); - convert << sky_index; - text->setTextArg("[INDEX]", convert.str()); - } - - if (field) - { - field->setText(getNameForTrackIndex(sky_index)); - } - - if (alt_panel && (sky_index > 1)) - { - // move containing panel - LLRect rect = alt_panel->getRect(); - S32 height = rect.getHeight(); - rect.mBottom = sld_bottom + (sld_offset / 2 + 1) + pos - (height / 2); - rect.mTop = rect.mBottom + height; - alt_panel->setRect(rect); - } - -} - -void LLPanelEnvironmentInfo::readjustAltLabels() -{ - // Re-adjust all labels - // Very simple "adjust after the fact" method - // Note: labels can be in any order - - LLMultiSliderCtrl *sld = findChild(SLD_ALTITUDES); - if (!sld) return; - - LLView* view_midle = NULL; - U32 midle_ind = 0; - S32 shift_up = 0; - S32 shift_down = 0; - LLRect sld_rect = sld->getRect(); - - // Find the middle one - for (U32 i = 0; i < ALTITUDE_SLIDER_COUNT; i++) - { - LLView* cmp_view = findChild(alt_panels[i], true); - if (!cmp_view) return; - LLRect cmp_rect = cmp_view->getRect(); - S32 pos = 0; - shift_up = 0; - shift_down = 0; - - for (U32 j = 0; j < ALTITUDE_SLIDER_COUNT; j++) - { - if (i != j) - { - LLView* intr_view = findChild(alt_panels[j], true); - if (!intr_view) return; - LLRect intr_rect = intr_view->getRect(); - if (cmp_rect.mBottom >= intr_rect.mBottom) - { - pos++; - } - if (intr_rect.mBottom <= cmp_rect.mTop && intr_rect.mBottom >= cmp_rect.mBottom) - { - shift_up = cmp_rect.mTop - intr_rect.mBottom; - } - else if (intr_rect.mTop >= cmp_rect.mBottom && intr_rect.mBottom <= cmp_rect.mBottom) - { - shift_down = cmp_rect.mBottom - intr_rect.mTop; - } - } - } - if (pos == 1) // middle - { - view_midle = cmp_view; - midle_ind = i; - break; - } - } - - // Account for edges - LLRect midle_rect = view_midle->getRect(); - F32 factor = 0.5f; - S32 edge_zone_height = midle_rect.getHeight() * 1.5f; - - if (midle_rect.mBottom - sld_rect.mBottom < edge_zone_height) - { - factor = 1 - ((midle_rect.mBottom - sld_rect.mBottom) / (edge_zone_height * 2)); - } - else if (sld_rect.mTop - midle_rect.mTop < edge_zone_height ) - { - factor = ((sld_rect.mTop - midle_rect.mTop) / (edge_zone_height * 2)); - } - - S32 shift_middle = (S32)(((F32)shift_down * factor) + ((F32)shift_up * (1.f - factor))); - shift_down = shift_down - shift_middle; - shift_up = shift_up - shift_middle; - - // fix crossings - for (U32 i = 0; i < ALTITUDE_SLIDER_COUNT; i++) - { - if (i != midle_ind) - { - LLView* trn_view = findChild(alt_panels[i], true); - LLRect trn_rect = trn_view->getRect(); - - if (trn_rect.mBottom <= midle_rect.mTop && trn_rect.mBottom >= midle_rect.mBottom) - { - // Approximate shift - trn_rect.translate(0, shift_up); - trn_view->setRect(trn_rect); - } - else if (trn_rect.mTop >= midle_rect.mBottom && trn_rect.mBottom <= midle_rect.mBottom) - { - // Approximate shift - trn_rect.translate(0, shift_down); - trn_view->setRect(trn_rect); - } - } - } - - if (shift_middle != 0) - { - midle_rect.translate(0, -shift_middle); //reversed relative to others - view_midle->setRect(midle_rect); - } -} - -void LLPanelEnvironmentInfo::onSldDayLengthChanged(F32 value) -{ - if (mCurrentEnvironment) - { - F32Hours daylength(value); - - mCurrentEnvironment->mDayLength = daylength; - setDirtyFlag(DIRTY_FLAG_DAYLENGTH); - - udpateApparentTimeOfDay(); - } -} - -void LLPanelEnvironmentInfo::onSldDayOffsetChanged(F32 value) -{ - if (mCurrentEnvironment) - { - F32Hours dayoffset(value); - - if (dayoffset.value() <= 0.0f) - dayoffset += F32Hours(24.0); - - mCurrentEnvironment->mDayOffset = dayoffset; - setDirtyFlag(DIRTY_FLAG_DAYOFFSET); - - udpateApparentTimeOfDay(); - } -} - -void LLPanelEnvironmentInfo::onDayLenOffsetMouseUp() -{ - commitDayLenOffsetChanges(true); -} - -void LLPanelEnvironmentInfo::commitDayLenOffsetChanges(bool need_callback) -{ - if (mCurrentEnvironment && (getDirtyFlag() & (DIRTY_FLAG_DAYLENGTH | DIRTY_FLAG_DAYOFFSET))) - { - clearDirtyFlag(DIRTY_FLAG_DAYOFFSET); - clearDirtyFlag(DIRTY_FLAG_DAYLENGTH); - - LLHandle that_h = getHandle(); - - if (need_callback) - { - LLEnvironment::instance().updateParcel(getParcelId(), - LLSettingsDay::ptr_t(), - mCurrentEnvironment->mDayLength.value(), - mCurrentEnvironment->mDayOffset.value(), - LLEnvironment::altitudes_vect_t(), - [that_h](S32 parcel_id, LLEnvironment::EnvironmentInfo::ptr_t envifo) { _onEnvironmentReceived(that_h, parcel_id, envifo); }); - } - else - { - LLEnvironment::instance().updateParcel(getParcelId(), - LLSettingsDay::ptr_t(), - mCurrentEnvironment->mDayLength.value(), - mCurrentEnvironment->mDayOffset.value(), - LLEnvironment::altitudes_vect_t()); - } - - } -} - -void LLPanelEnvironmentInfo::onAltSliderCallback(LLUICtrl *cntrl, const LLSD &data) -{ - LLMultiSliderCtrl *sld = (LLMultiSliderCtrl *)cntrl; - std::string sld_name = sld->getCurSlider(); - - if (sld_name.empty()) return; - - F32 sld_value = sld->getCurSliderValue(); - - mAltitudes[sld_name].mAltitude = sld_value; - - // update all labels since we could have jumped multiple and we will need to readjust - // (or sort by altitude, too little elements, so I didn't bother with efficiency) - altitudes_data_t::iterator end = mAltitudes.end(); - altitudes_data_t::iterator iter = mAltitudes.begin(); - altitudes_data_t::iterator iter2; - U32 new_index; - while (iter != end) - { - iter2 = mAltitudes.begin(); - new_index = 2; - while (iter2 != end) - { - if (iter->second.mAltitude > iter2->second.mAltitude) - { - new_index++; - } - iter2++; - } - iter->second.mTrackIndex = new_index; - - updateAltLabel(alt_prefixes[iter->second.mLabelIndex], iter->second.mTrackIndex, iter->second.mAltitude); - iter++; - } - - readjustAltLabels(); - setDirtyFlag(DIRTY_FLAG_ALTITUDES); -} - -void LLPanelEnvironmentInfo::onAltSliderMouseUp() -{ - if (isRegion() && (getDirtyFlag() & DIRTY_FLAG_ALTITUDES)) - { - clearDirtyFlag(DIRTY_FLAG_ALTITUDES); - clearDirtyFlag(DIRTY_FLAG_DAYLENGTH); - clearDirtyFlag(DIRTY_FLAG_DAYOFFSET); - - LLHandle that_h = getHandle(); - LLEnvironment::altitudes_vect_t alts; - - for (auto alt : mAltitudes) - { - alts.push_back(alt.second.mAltitude); - } - setControlsEnabled(false); - LLEnvironment::instance().updateParcel(getParcelId(), - LLSettingsDay::ptr_t(), - mCurrentEnvironment ? mCurrentEnvironment->mDayLength.value() : -1, - mCurrentEnvironment ? mCurrentEnvironment->mDayOffset.value() : -1, - alts); - } -} - -void LLPanelEnvironmentInfo::onBtnDefault() -{ - LLHandle that_h = getHandle(); - S32 parcel_id = getParcelId(); - LLNotificationsUtil::add("SettingsConfirmReset", LLSD(), LLSD(), - [that_h, parcel_id](const LLSD¬if, const LLSD&resp) - { - S32 opt = LLNotificationsUtil::getSelectedOption(notif, resp); - if (opt == 0) - { - LLEnvironment::instance().resetParcel(parcel_id, - [that_h](S32 parcel_id, LLEnvironment::EnvironmentInfo::ptr_t envifo) { _onEnvironmentReceived(that_h, parcel_id, envifo); }); - } - }); -} - -void LLPanelEnvironmentInfo::onBtnEdit() -{ - static const S32 FOURHOURS(4 * 60 * 60); - - LLFloaterEditExtDayCycle *dayeditor = getEditFloater(); - - LLSD params(LLSDMap(LLFloaterEditExtDayCycle::KEY_EDIT_CONTEXT, isRegion() ? LLFloaterEditExtDayCycle::VALUE_CONTEXT_REGION : LLFloaterEditExtDayCycle::VALUE_CONTEXT_PARCEL) - (LLFloaterEditExtDayCycle::KEY_DAY_LENGTH, mCurrentEnvironment ? (S32)(mCurrentEnvironment->mDayLength.value()) : FOURHOURS) - (LLFloaterEditExtDayCycle::KEY_CANMOD, LLSD::Boolean(true))); - - dayeditor->openFloater(params); - if (mCurrentEnvironment && mCurrentEnvironment->mDayCycle) - { - dayeditor->setEditDayCycle(mCurrentEnvironment->mDayCycle); - if (!ends_with(mCurrentEnvironment->mDayCycle->getName(), "(customized)")) - { - dayeditor->setEditName(mCurrentEnvironment->mDayCycle->getName() + "(customized)"); - } - } - else - dayeditor->setEditDefaultDayCycle(); -} - -void LLPanelEnvironmentInfo::onBtnSelect() -{ - LLFloaterSettingsPicker *picker = getSettingsPicker(); - if (picker) - { - LLUUID item_id; - if (mCurrentEnvironment && mCurrentEnvironment->mDayCycle) - { - item_id = LLFloaterSettingsPicker::findItemID(mCurrentEnvironment->mDayCycle->getAssetId(), false, false); - } - picker->setSettingsFilter(LLSettingsType::ST_NONE); - picker->setSettingsItemId(item_id); - picker->openFloater(); - picker->setFocus(true); - } -} - -void LLPanelEnvironmentInfo::onBtnRstAltitudes() -{ - if (isRegion()) - { - LLHandle that_h = getHandle(); - LLEnvironment::altitudes_vect_t alts; - - clearDirtyFlag(DIRTY_FLAG_ALTITUDES); - clearDirtyFlag(DIRTY_FLAG_DAYLENGTH); - clearDirtyFlag(DIRTY_FLAG_DAYOFFSET); - - for (S32 idx = 1; idx <= ALTITUDE_SLIDER_COUNT; ++idx) - { - F32 new_height = idx * ALTITUDE_DEFAULT_HEIGHT_STEP; - alts.push_back(new_height); - } - - LLEnvironment::instance().updateParcel(getParcelId(), - LLSettingsDay::ptr_t(), - mCurrentEnvironment ? mCurrentEnvironment->mDayLength.value() : -1, - mCurrentEnvironment ? mCurrentEnvironment->mDayOffset.value() : -1, - alts, - [that_h](S32 parcel_id, LLEnvironment::EnvironmentInfo::ptr_t envifo) { _onEnvironmentReceived(that_h, parcel_id, envifo); }); - } -} - -void LLPanelEnvironmentInfo::udpateApparentTimeOfDay() -{ - static const F32 SECONDSINDAY(24.0 * 60.0 * 60.0); - - if ((!mCurrentEnvironment) || (mCurrentEnvironment->mDayLength.value() < 1.0) || (mCurrentEnvironment->mDayOffset.value() < 1.0)) - { - getChild(LBL_TIMEOFDAY)->setVisible(false); - return; - } - getChild(LBL_TIMEOFDAY)->setVisible(true); - - S32Seconds now(LLDate::now().secondsSinceEpoch()); - - now += mCurrentEnvironment->mDayOffset; - - F32 perc = (F32)(now.value() % mCurrentEnvironment->mDayLength.value()) / (F32)(mCurrentEnvironment->mDayLength.value()); - - S32Seconds secondofday((S32)(perc * SECONDSINDAY)); - S32Hours hourofday(secondofday); - S32Seconds secondofhour(secondofday - hourofday); - S32Minutes minutesofhour(secondofhour); - bool am_pm(hourofday.value() >= 12); - - if (hourofday.value() < 1) - hourofday = S32Hours(12); - if (hourofday.value() > 12) - hourofday -= S32Hours(12); - - std::string lblminute(((minutesofhour.value() < 10) ? "0" : "") + LLSD(minutesofhour.value()).asString()); - - - getChild(LBL_TIMEOFDAY)->setTextArg("[HH]", LLSD(hourofday.value()).asString()); - getChild(LBL_TIMEOFDAY)->setTextArg("[MM]", lblminute); - getChild(LBL_TIMEOFDAY)->setTextArg("[AP]", std::string(am_pm ? "PM" : "AM")); - getChild(LBL_TIMEOFDAY)->setTextArg("[PRC]", LLSD((S32)(100 * perc)).asString()); - -} - -void LLPanelEnvironmentInfo::onIdlePlay(void *data) -{ - ((LLPanelEnvironmentInfo *)data)->udpateApparentTimeOfDay(); -} - - -void LLPanelEnvironmentInfo::onPickerCommitted(LLUUID item_id, std::string source) -{ - if (source == alt_prefixes[4]) - { - onPickerCommitted(item_id, 0); - } - else if (source == alt_prefixes[3]) - { - onPickerCommitted(item_id, 1); - } - else - { - onPickerCommitted(item_id, mAltitudes[source].mTrackIndex); - } -} - -void LLPanelEnvironmentInfo::onPickerCommitted(LLUUID item_id, S32 track_num) -{ - LLInventoryItem *itemp = gInventory.getItem(item_id); - if (itemp) - { - LLHandle that_h = getHandle(); - clearDirtyFlag(DIRTY_FLAG_DAYLENGTH); - clearDirtyFlag(DIRTY_FLAG_DAYOFFSET); - - U32 flags(0); - - if (itemp) - { - if (!itemp->getPermissions().allowOperationBy(PERM_MODIFY, gAgent.getID())) - flags |= LLSettingsBase::FLAG_NOMOD; - if (!itemp->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID())) - flags |= LLSettingsBase::FLAG_NOTRANS; - } - - LLEnvironment::instance().updateParcel(getParcelId(), - itemp->getAssetUUID(), - itemp->getName(), - track_num, - mCurrentEnvironment ? mCurrentEnvironment->mDayLength.value() : -1, - mCurrentEnvironment ? mCurrentEnvironment->mDayOffset.value() : -1, - flags, - LLEnvironment::altitudes_vect_t(), - [that_h](S32 parcel_id, LLEnvironment::EnvironmentInfo::ptr_t envifo) { _onEnvironmentReceived(that_h, parcel_id, envifo); }); - } -} - -void LLPanelEnvironmentInfo::onEditCommitted(LLSettingsDay::ptr_t newday) -{ - LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_EDIT); - LLEnvironment::instance().updateEnvironment(); - if (!newday) - { - LL_WARNS("ENVPANEL") << "Editor committed an empty day. Do nothing." << LL_ENDL; - return; - } - if (!mCurrentEnvironment) - { - // Attempting to save mid update? - LL_WARNS("ENVPANEL") << "Failed to apply changes from editor! Dirty state: " << mDirtyFlag << " env version: " << mCurEnvVersion << LL_ENDL; - return; - } - size_t newhash(newday->getHash()); - size_t oldhash((mCurrentEnvironment->mDayCycle) ? mCurrentEnvironment->mDayCycle->getHash() : 0); - - if (newhash != oldhash) - { - LLHandle that_h = getHandle(); - clearDirtyFlag(DIRTY_FLAG_DAYLENGTH); - clearDirtyFlag(DIRTY_FLAG_DAYOFFSET); - - LLEnvironment::instance().updateParcel(getParcelId(), - newday, - mCurrentEnvironment ? mCurrentEnvironment->mDayLength.value() : -1, - mCurrentEnvironment ? mCurrentEnvironment->mDayOffset.value() : -1, - LLEnvironment::altitudes_vect_t(), - [that_h](S32 parcel_id, LLEnvironment::EnvironmentInfo::ptr_t envifo) { _onEnvironmentReceived(that_h, parcel_id, envifo); }); - } -} - -void LLPanelEnvironmentInfo::onEnvironmentChanged(LLEnvironment::EnvSelection_t env, S32 new_version) -{ - if (new_version < INVALID_PARCEL_ENVIRONMENT_VERSION) - { - // cleanups and local changes, we are only interested in changes sent by server - return; - } - - LL_DEBUGS("ENVPANEL") << "Received environment update " << mCurEnvVersion << " " << new_version << LL_ENDL; - - // Environment comes from different sources, from environment update callbacks, - // from hovers (causes callbacks on version change) and from personal requests - // filter out duplicates and out of order packets by checking parcel environment version. - - if (isRegion()) - { - // Note: region uses same init versions as parcel - if (env == LLEnvironment::ENV_REGION - // version should be always growing, UNSET_PARCEL_ENVIRONMENT_VERSION is backup case - && (mCurEnvVersion < new_version || mCurEnvVersion <= UNSET_PARCEL_ENVIRONMENT_VERSION)) - { - if (new_version >= UNSET_PARCEL_ENVIRONMENT_VERSION) - { - // 'pending state' to prevent re-request on following onEnvironmentChanged if there will be any - mCurEnvVersion = new_version; - } - mCurrentEnvironment.reset(); - refreshFromSource(); - } - } - else if ((env == LLEnvironment::ENV_PARCEL) - && (getParcelId() == LLViewerParcelMgr::instance().getAgentParcelId())) - { - LLParcel *parcel = getParcel(); - if (parcel) - { - // first for parcel own settings, second is for case when parcel uses region settings - if (mCurEnvVersion < new_version - || (mCurEnvVersion != new_version && new_version == UNSET_PARCEL_ENVIRONMENT_VERSION)) - { - // 'pending state' to prevent re-request on following onEnvironmentChanged if there will be any - mCurEnvVersion = new_version; - mCurrentEnvironment.reset(); - - refreshFromSource(); - } - else if (mCurrentEnvironment) - { - // update controls - refresh(); - } - } - } -} - - -void LLPanelEnvironmentInfo::onPickerAssetDownloaded(LLSettingsBase::ptr_t settings) -{ - LLSettingsVODay::buildFromOtherSetting(settings, [this](LLSettingsDay::ptr_t pday) - { - if (pday) - { - mCurrentEnvironment->mDayCycle = pday; - setDirtyFlag(DIRTY_FLAG_DAYCYCLE); - } - refresh(); - }); -} - -void LLPanelEnvironmentInfo::onEnvironmentReceived(S32 parcel_id, LLEnvironment::EnvironmentInfo::ptr_t envifo) -{ - if (parcel_id != getParcelId()) - { - LL_WARNS("ENVPANEL") << "Have environment for parcel " << parcel_id << " expecting " << getParcelId() << ". Discarding." << LL_ENDL; - return; - } - mCurrentEnvironment = envifo; - clearDirtyFlag(DIRTY_FLAG_MASK); - if (mCurrentEnvironment->mEnvVersion > INVALID_PARCEL_ENVIRONMENT_VERSION) - { - // Server provided version, use it - mCurEnvVersion = mCurrentEnvironment->mEnvVersion; - LL_DEBUGS("ENVPANEL") << " Setting environment version: " << mCurEnvVersion << " for parcel id: " << parcel_id << LL_ENDL; - } - // Backup: Version was not provided for some reason - else - { - LL_WARNS("ENVPANEL") << " Environment version was not provided for " << parcel_id << ", old env version: " << mCurEnvVersion << LL_ENDL; - } - - refreshFromEstate(); - refresh(); - - // todo: we have envifo and parcel env version, should we just setEnvironment() and parcel's property to prevent dupplicate requests? -} - -void LLPanelEnvironmentInfo::_onEnvironmentReceived(LLHandle that_h, S32 parcel_id, LLEnvironment::EnvironmentInfo::ptr_t envifo) -{ - LLPanelEnvironmentInfo *that = (LLPanelEnvironmentInfo *)that_h.get(); - if (!that) - return; - that->onEnvironmentReceived(parcel_id, envifo); -} - -LLSettingsDropTarget::LLSettingsDropTarget(const LLSettingsDropTarget::Params& p) - : LLView(p), mEnvironmentInfoPanel(NULL), mDndEnabled(false) -{} - -bool LLSettingsDropTarget::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - bool handled = false; - - if (getParent() && mDndEnabled) - { - handled = true; - - switch (cargo_type) - { - case DAD_SETTINGS: - { - LLViewerInventoryItem* inv_item = (LLViewerInventoryItem*)cargo_data; - if (inv_item && mEnvironmentInfoPanel) - { - LLUUID item_id = inv_item->getUUID(); - if (gInventory.getItem(item_id)) - { - *accept = ACCEPT_YES_COPY_SINGLE; - if (drop) - { - // might be better to use name of the element - mEnvironmentInfoPanel->onPickerCommitted(item_id, mTrack); - } - } - } - else - { - *accept = ACCEPT_NO; - } - break; - } - default: - *accept = ACCEPT_NO; - break; - } - } - return handled; -} +/** + * @file llpanelenvironment.cpp + * @brief LLPanelExperiences class implementation + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + + +#include "llpanelprofile.h" +#include "lluictrlfactory.h" +#include "llexperiencecache.h" +#include "llagent.h" +#include "llparcel.h" + +#include "llviewerregion.h" +#include "llpanelenvironment.h" +#include "llslurl.h" +#include "lllayoutstack.h" + +#include "llfloater.h" +#include "llfloaterreg.h" +#include "llfloatereditextdaycycle.h" +#include "llmultisliderctrl.h" +#include "llnotificationsutil.h" +#include "llsettingsvo.h" + +#include "llappviewer.h" +#include "llcallbacklist.h" +#include "llviewerparcelmgr.h" + +#include "llinventorymodel.h" + +//========================================================================= +namespace +{ + const std::string FLOATER_DAY_CYCLE_EDIT("env_edit_extdaycycle"); + const std::string STRING_REGION_ENV("str_region_env"); + const std::string STRING_EMPTY_NAME("str_empty"); + + inline bool ends_with(std::string const & value, std::string const & ending) + { + if (ending.size() > value.size()) + return false; + return std::equal(ending.rbegin(), ending.rend(), value.rbegin()); + } + +} + +//========================================================================= +const std::string LLPanelEnvironmentInfo::BTN_SELECTINV("btn_select_inventory"); +const std::string LLPanelEnvironmentInfo::BTN_EDIT("btn_edit"); +const std::string LLPanelEnvironmentInfo::BTN_USEDEFAULT("btn_usedefault"); +const std::string LLPanelEnvironmentInfo::BTN_RST_ALTITUDES("btn_rst_altitudes"); +const std::string LLPanelEnvironmentInfo::SLD_DAYLENGTH("sld_day_length"); +const std::string LLPanelEnvironmentInfo::SLD_DAYOFFSET("sld_day_offset"); +const std::string LLPanelEnvironmentInfo::SLD_ALTITUDES("sld_altitudes"); +const std::string LLPanelEnvironmentInfo::ICN_GROUND("icon_ground"); +const std::string LLPanelEnvironmentInfo::ICN_WATER("icon_water"); +const std::string LLPanelEnvironmentInfo::CHK_ALLOWOVERRIDE("chk_allow_override"); +const std::string LLPanelEnvironmentInfo::LBL_TIMEOFDAY("lbl_apparent_time"); +const std::string LLPanelEnvironmentInfo::PNL_SETTINGS("pnl_environment_config"); +const std::string LLPanelEnvironmentInfo::PNL_ENVIRONMENT_ALTITUDES("pnl_environment_altitudes"); +const std::string LLPanelEnvironmentInfo::PNL_BUTTONS("pnl_environment_buttons"); +const std::string LLPanelEnvironmentInfo::PNL_DISABLED("pnl_environment_disabled"); +const std::string LLPanelEnvironmentInfo::TXT_DISABLED("txt_environment_disabled"); +const std::string LLPanelEnvironmentInfo::PNL_REGION_MSG("pnl_environment_region_msg"); +const std::string LLPanelEnvironmentInfo::SDT_DROP_TARGET("sdt_drop_target"); + +const std::string LLPanelEnvironmentInfo::STR_LABEL_USEDEFAULT("str_label_use_default"); +const std::string LLPanelEnvironmentInfo::STR_LABEL_USEREGION("str_label_use_region"); +const std::string LLPanelEnvironmentInfo::STR_ALTITUDE_DESCRIPTION("str_altitude_desription"); +const std::string LLPanelEnvironmentInfo::STR_NO_PARCEL("str_no_parcel"); +const std::string LLPanelEnvironmentInfo::STR_CROSS_REGION("str_cross_region"); +const std::string LLPanelEnvironmentInfo::STR_LEGACY("str_legacy"); +const std::string LLPanelEnvironmentInfo::STR_DISALLOWED("str_disallowed"); +const std::string LLPanelEnvironmentInfo::STR_TOO_SMALL("str_too_small"); + +const S32 LLPanelEnvironmentInfo::MINIMUM_PARCEL_SIZE(128); + +const U32 LLPanelEnvironmentInfo::DIRTY_FLAG_DAYCYCLE(0x01 << 0); +const U32 LLPanelEnvironmentInfo::DIRTY_FLAG_DAYLENGTH(0x01 << 1); +const U32 LLPanelEnvironmentInfo::DIRTY_FLAG_DAYOFFSET(0x01 << 2); +const U32 LLPanelEnvironmentInfo::DIRTY_FLAG_ALTITUDES(0x01 << 3); + +const U32 LLPanelEnvironmentInfo::DIRTY_FLAG_MASK( + LLPanelEnvironmentInfo::DIRTY_FLAG_DAYCYCLE | + LLPanelEnvironmentInfo::DIRTY_FLAG_DAYLENGTH | + LLPanelEnvironmentInfo::DIRTY_FLAG_DAYOFFSET | + LLPanelEnvironmentInfo::DIRTY_FLAG_ALTITUDES); + +const U32 ALTITUDE_SLIDER_COUNT = 3; +const F32 ALTITUDE_DEFAULT_HEIGHT_STEP = 1000; +const U32 ALTITUDE_MARKERS_COUNT = 3; +const U32 ALTITUDE_PREFIXERS_COUNT = 5; + +const std::string slider_marker_base = "mark"; + +const std::string alt_sliders[] = { + "sld1", + "sld2", + "sld3", +}; + +const std::string alt_prefixes[] = { + "alt1", + "alt2", + "alt3", + "ground", + "water", +}; + +const std::string alt_panels[] = { + "pnl_alt1", + "pnl_alt2", + "pnl_alt3", + "pnl_ground", + "pnl_water", +}; + +static LLDefaultChildRegistry::Register r("settings_drop_target"); + +//========================================================================= +LLPanelEnvironmentInfo::LLPanelEnvironmentInfo(): + mCurrentEnvironment(), + mDirtyFlag(0), + mEditorLastParcelId(INVALID_PARCEL_ID), + mCrossRegion(false), + mNoSelection(false), + mNoEnvironment(false), + mCurEnvVersion(INVALID_PARCEL_ENVIRONMENT_VERSION), + mSettingsFloater(), + mEditFloater(), + mAllowOverride(true) +{ +} + +LLPanelEnvironmentInfo::~LLPanelEnvironmentInfo() +{ + if (mChangeMonitor.connected()) + mChangeMonitor.disconnect(); + if (mCommitConnection.connected()) + mCommitConnection.disconnect(); + if (mUpdateConnection.connected()) + mUpdateConnection.disconnect(); +} + +bool LLPanelEnvironmentInfo::postBuild() +{ + + getChild(BTN_USEDEFAULT)->setCommitCallback([this](LLUICtrl *, const LLSD &){ onBtnDefault(); }); + getChild(BTN_SELECTINV)->setCommitCallback([this](LLUICtrl *, const LLSD &){ onBtnSelect(); }); + getChild(BTN_EDIT)->setCommitCallback([this](LLUICtrl *, const LLSD &){ onBtnEdit(); }); + getChild(BTN_RST_ALTITUDES)->setCommitCallback([this](LLUICtrl *, const LLSD &){ onBtnRstAltitudes(); }); + + getChild(SLD_DAYLENGTH)->setCommitCallback([this](LLUICtrl *, const LLSD &value) { onSldDayLengthChanged(value.asReal()); }); + getChild(SLD_DAYLENGTH)->setSliderMouseUpCallback([this](LLUICtrl *, const LLSD &) { onDayLenOffsetMouseUp(); }); + getChild(SLD_DAYLENGTH)->setSliderEditorCommitCallback([this](LLUICtrl *, const LLSD &) { onDayLenOffsetMouseUp(); }); + getChild(SLD_DAYOFFSET)->setCommitCallback([this](LLUICtrl *, const LLSD &value) { onSldDayOffsetChanged(value.asReal()); }); + getChild(SLD_DAYOFFSET)->setSliderMouseUpCallback([this](LLUICtrl *, const LLSD &) { onDayLenOffsetMouseUp(); }); + getChild(SLD_DAYOFFSET)->setSliderEditorCommitCallback([this](LLUICtrl *, const LLSD &) { onDayLenOffsetMouseUp(); }); + + getChild(SLD_ALTITUDES)->setCommitCallback([this](LLUICtrl *cntrl, const LLSD &value) { onAltSliderCallback(cntrl, value); }); + getChild(SLD_ALTITUDES)->setSliderMouseUpCallback([this](LLUICtrl *, const LLSD &) { onAltSliderMouseUp(); }); + + mChangeMonitor = LLEnvironment::instance().setEnvironmentChanged([this](LLEnvironment::EnvSelection_t env, S32 version) { onEnvironmentChanged(env, version); }); + + for (U32 idx = 0; idx < ALTITUDE_SLIDER_COUNT; idx++) + { + LLSettingsDropTarget* drop_target = findChild("sdt_" + alt_prefixes[idx]); + if (drop_target) + { + drop_target->setPanel(this, alt_sliders[idx]); + } + // set initial values to prevent [ALTITUDE] from displaying + updateAltLabel(alt_prefixes[idx], idx + 2, idx * 1000); + } + getChild("sdt_" + alt_prefixes[3])->setPanel(this, alt_prefixes[3]); + getChild("sdt_" + alt_prefixes[4])->setPanel(this, alt_prefixes[4]); + + return true; +} + +// virtual +void LLPanelEnvironmentInfo::onOpen(const LLSD& key) +{ + refreshFromSource(); +} + +// virtual +void LLPanelEnvironmentInfo::onVisibilityChange(bool new_visibility) +{ + if (new_visibility) + { + gIdleCallbacks.addFunction(onIdlePlay, this); + } + else + { + commitDayLenOffsetChanges(false); // arrow-key changes + + LLFloaterSettingsPicker *picker = getSettingsPicker(false); + if (picker) + { + picker->closeFloater(); + } + + gIdleCallbacks.deleteFunction(onIdlePlay, this); + LLFloaterEditExtDayCycle *dayeditor = getEditFloater(false); + if (mCommitConnection.connected()) + mCommitConnection.disconnect(); + + if (dayeditor) + { + if (dayeditor->isDirty()) + dayeditor->refresh(); + else + { + dayeditor->closeFloater(); + mEditFloater.markDead(); + } + } + } + +} + +void LLPanelEnvironmentInfo::refresh() +{ + if (gDisconnected) + return; + + if (!setControlsEnabled(canEdit())) + return; + + if (!mCurrentEnvironment) + { + return; + } + + F32Hours daylength(mCurrentEnvironment->mDayLength); + F32Hours dayoffset(mCurrentEnvironment->mDayOffset); + + if (dayoffset.value() > 12.0f) + dayoffset -= F32Hours(24.0); + + getChild(SLD_DAYLENGTH)->setValue(daylength.value()); + getChild(SLD_DAYOFFSET)->setValue(dayoffset.value()); + + udpateApparentTimeOfDay(); + + updateEditFloater(mCurrentEnvironment, canEdit()); + + LLEnvironment::altitude_list_t altitudes = mCurrentEnvironment->mAltitudes; + + if (altitudes.size() > 0) + { + LLMultiSliderCtrl *sld = getChild(SLD_ALTITUDES); + sld->clear(); + + for (S32 idx = 0; idx < ALTITUDE_SLIDER_COUNT; ++idx) + { + // make sure values are in range, server is supposed to validate them, + // but issues happen, try to fix values in such case + F32 altitude = llclamp(altitudes[idx + 1], sld->getMinValue(), sld->getMaxValue()); + bool res = sld->addSlider(altitude, alt_sliders[idx]); + if (!res) + { + LL_WARNS_ONCE("ENVPANEL") << "Failed to validate altitude from server for parcel id" << getParcelId() << LL_ENDL; + // Find a spot to insert altitude. + // Assuming everything alright with slider, we should find new place in 11 steps top (step 25m, no overlap 100m) + F32 alt_step = (altitude > (sld->getMaxValue() / 2)) ? -sld->getIncrement() : sld->getIncrement(); + for (U32 i = 0; i < 30; i++) + { + altitude += alt_step; + if (altitude > sld->getMaxValue()) + { + altitude = sld->getMinValue(); + } + else if (altitude < sld->getMinValue()) + { + altitude = sld->getMaxValue(); + } + res = sld->addSlider(altitude, alt_sliders[idx]); + if (res) break; + } + } + if (res) + { + // slider has some auto correction that might have kicked in + altitude = sld->getSliderValue(alt_sliders[idx]); + } + else + { + // Something is very very wrong + LL_WARNS_ONCE("ENVPANEL") << "Failed to set up altitudes for parcel id " << getParcelId() << LL_ENDL; + } + updateAltLabel(alt_prefixes[idx], idx + 2, altitude); + mAltitudes[alt_sliders[idx]] = AltitudeData(idx + 2, idx, altitude); + } + if (sld->getCurNumSliders() != ALTITUDE_SLIDER_COUNT) + { + LL_WARNS("ENVPANEL") << "Failed to add altitude sliders!" << LL_ENDL; + } + readjustAltLabels(); + sld->resetCurSlider(); + } + + updateAltLabel(alt_prefixes[3], 1, 0); // ground + updateAltLabel(alt_prefixes[4], 0, 0); // water + +} + +void LLPanelEnvironmentInfo::refreshFromEstate() +{ + LLViewerRegion *pRegion = gAgent.getRegion(); + + bool oldAO = mAllowOverride; + mAllowOverride = (isRegion() && LLEstateInfoModel::instance().getAllowEnvironmentOverride()) || pRegion->getAllowEnvironmentOverride(); + if (oldAO != mAllowOverride) + refresh(); +} + +std::string LLPanelEnvironmentInfo::getNameForTrackIndex(S32 index) +{ + std::string invname; + if (!mCurrentEnvironment || index < LLSettingsDay::TRACK_WATER || index >= LLSettingsDay::TRACK_MAX) + { + invname = getString(STRING_EMPTY_NAME); + } + else if (mCurrentEnvironment->mDayCycleName.empty()) + { + invname = mCurrentEnvironment->mNameList[index]; + + if (invname.empty()) + { + if (index <= LLSettingsDay::TRACK_GROUND_LEVEL) + invname = getString(isRegion() ? STRING_EMPTY_NAME : STRING_REGION_ENV); + } + } + else if (!mCurrentEnvironment->mDayCycle->isTrackEmpty(index)) + { + invname = mCurrentEnvironment->mDayCycleName; + } + + + if (invname.empty()) + { + invname = getNameForTrackIndex(index - 1); + if (invname[0] != '(') + invname = "(" + invname + ")"; + } + + return invname; +} + +LLFloaterSettingsPicker * LLPanelEnvironmentInfo::getSettingsPicker(bool create) +{ + LLFloaterSettingsPicker *picker = static_cast(mSettingsFloater.get()); + + // Show the dialog + if (!picker && create) + { + picker = new LLFloaterSettingsPicker(this, + LLUUID::null); + + mSettingsFloater = picker->getHandle(); + + picker->setCommitCallback([this](LLUICtrl *, const LLSD &data){ onPickerCommitted(data["ItemId"].asUUID()); }); + } + + return picker; +} + +LLFloaterEditExtDayCycle * LLPanelEnvironmentInfo::getEditFloater(bool create) +{ + static const S32 FOURHOURS(4 * 60 * 60); + LLFloaterEditExtDayCycle *editor = static_cast(mEditFloater.get()); + + // Show the dialog + if (!editor && create) + { + LLSD params(LLSDMap(LLFloaterEditExtDayCycle::KEY_EDIT_CONTEXT, isRegion() ? LLFloaterEditExtDayCycle::CONTEXT_REGION : LLFloaterEditExtDayCycle::CONTEXT_PARCEL) + (LLFloaterEditExtDayCycle::KEY_DAY_LENGTH, mCurrentEnvironment ? (S32)(mCurrentEnvironment->mDayLength.value()) : FOURHOURS)); + + editor = (LLFloaterEditExtDayCycle *)LLFloaterReg::getInstance(FLOATER_DAY_CYCLE_EDIT, params); + + if (!editor) + return nullptr; + mEditFloater = editor->getHandle(); + } + + if (editor && !mCommitConnection.connected()) + mCommitConnection = editor->setEditCommitSignal([this](LLSettingsDay::ptr_t pday) { onEditCommitted(pday); }); + + return editor; +} + + +void LLPanelEnvironmentInfo::updateEditFloater(const LLEnvironment::EnvironmentInfo::ptr_t &nextenv, bool enable) +{ + LLFloaterEditExtDayCycle *dayeditor(getEditFloater(false)); + + if (!dayeditor || !dayeditor->isInVisibleChain()) + return; + + if (!nextenv || !nextenv->mDayCycle || !enable) + { + if (mCommitConnection.connected()) + mCommitConnection.disconnect(); + + if (dayeditor->isDirty()) + dayeditor->refresh(); + else + dayeditor->closeFloater(); + } + else if (dayeditor->getEditingAssetId() != nextenv->mDayCycle->getAssetId() + || mEditorLastParcelId != nextenv->mParcelId + || mEditorLastRegionId != nextenv->mRegionId) + { + // Ignore dirty + // If parcel selection changed whatever we do except saving to inventory with + // old settings will be invalid. + mEditorLastParcelId = nextenv->mParcelId; + mEditorLastRegionId = nextenv->mRegionId; + + dayeditor->setEditDayCycle(nextenv->mDayCycle); + } +} + +bool LLPanelEnvironmentInfo::setControlsEnabled(bool enabled) +{ + bool is_unavailable(false); + bool is_legacy = (mCurrentEnvironment) ? mCurrentEnvironment->mIsLegacy : true; + bool is_bigenough = isLargeEnough(); + + if (mNoEnvironment || (!LLEnvironment::instance().isExtendedEnvironmentEnabled() && !isRegion())) + { + is_unavailable = true; + getChild(TXT_DISABLED)->setText(getString(STR_LEGACY)); + } + else if (mNoSelection) + { + is_unavailable = true; + getChild(TXT_DISABLED)->setText(getString(STR_NO_PARCEL)); + } + else if (mCrossRegion) + { + is_unavailable = true; + getChild(TXT_DISABLED)->setText(getString(STR_CROSS_REGION)); + } + else if (!isRegion() && !mAllowOverride) + { + is_unavailable = true; + getChild(TXT_DISABLED)->setText(getString(STR_DISALLOWED)); + } + else if (!is_bigenough) + { + is_unavailable = true; + getChild(TXT_DISABLED)->setText(getString(STR_TOO_SMALL)); + } + + if (is_unavailable) + { + getChild(PNL_SETTINGS)->setVisible(false); + getChild(PNL_BUTTONS)->setVisible(false); + getChild(PNL_DISABLED)->setVisible(true); + getChild(PNL_ENVIRONMENT_ALTITUDES)->setVisible(false); + getChild(PNL_REGION_MSG)->setVisible(false); + updateEditFloater(mCurrentEnvironment, false); + + return false; + } + getChild(PNL_SETTINGS)->setVisible(true); + getChild(PNL_BUTTONS)->setVisible(true); + getChild(PNL_DISABLED)->setVisible(false); + getChild(PNL_REGION_MSG)->setVisible(isRegion()); + + getChild(PNL_ENVIRONMENT_ALTITUDES)->setVisible(LLEnvironment::instance().isExtendedEnvironmentEnabled()); + getChild(BTN_RST_ALTITUDES)->setVisible(isRegion()); + + bool can_enable = enabled && !is_legacy && mCurrentEnvironment && (mCurEnvVersion != INVALID_PARCEL_ENVIRONMENT_VERSION); + getChild(BTN_SELECTINV)->setEnabled(can_enable); + getChild(BTN_USEDEFAULT)->setEnabled(can_enable); + getChild(BTN_EDIT)->setEnabled(can_enable); + getChild(SLD_DAYLENGTH)->setEnabled(can_enable); + getChild(SLD_DAYOFFSET)->setEnabled(can_enable); + getChild(SLD_ALTITUDES)->setEnabled(can_enable && isRegion()); + getChild(ICN_GROUND)->setColor((can_enable && isRegion()) ? LLColor4::white : LLColor4::grey % 0.8f); + getChild(ICN_WATER)->setColor((can_enable && isRegion()) ? LLColor4::white : LLColor4::grey % 0.8f); + getChild(BTN_RST_ALTITUDES)->setEnabled(can_enable && isRegion()); + getChild(PNL_ENVIRONMENT_ALTITUDES)->setEnabled(can_enable); + getChild(CHK_ALLOWOVERRIDE)->setEnabled(can_enable && isRegion()); + + for (U32 idx = 0; idx < ALTITUDE_MARKERS_COUNT; idx++) + { + LLUICtrl* marker = findChild(slider_marker_base + llformat("%u", idx)); + if (marker) + { + static LLColor4 marker_color(0.75f, 0.75f, 0.75f, 1.f); + marker->setColor((can_enable && isRegion()) ? marker_color : marker_color % 0.3f); + } + } + + for (U32 idx = 0; idx < ALTITUDE_PREFIXERS_COUNT; idx++) + { + LLSettingsDropTarget* drop_target = findChild("sdt_" + alt_prefixes[idx]); + if (drop_target) + { + drop_target->setDndEnabled(can_enable); + } + } + + return true; +} + +void LLPanelEnvironmentInfo::setDirtyFlag(U32 flag) +{ + mDirtyFlag |= flag; +} + +void LLPanelEnvironmentInfo::clearDirtyFlag(U32 flag) +{ + mDirtyFlag &= ~flag; +} + +void LLPanelEnvironmentInfo::updateAltLabel(const std::string &alt_prefix, U32 sky_index, F32 alt_value) +{ + LLMultiSliderCtrl *sld = findChild(SLD_ALTITUDES); + if (!sld) + { + LL_WARNS() << "Failed to find slider " << SLD_ALTITUDES << LL_ENDL; + return; + } + LLRect sld_rect = sld->getRect(); + S32 sld_range = sld_rect.getHeight(); + S32 sld_bottom = sld_rect.mBottom; + S32 sld_offset = sld_rect.getWidth(); // Roughly identical to thumb's width in slider. + S32 pos = (sld_range - sld_offset) * ((alt_value - 100) / (4000 - 100)); + + // get related views + LLTextBox* text = findChild("txt_" + alt_prefix); + LLLineEditor *field = findChild("edt_invname_" + alt_prefix); + LLView *alt_panel = findChild("pnl_" + alt_prefix); + + if (text && (sky_index > 1)) + { + // update text + std::ostringstream convert; + convert << alt_value; + text->setTextArg("[ALTITUDE]", convert.str()); + convert.str(""); + convert.clear(); + convert << sky_index; + text->setTextArg("[INDEX]", convert.str()); + } + + if (field) + { + field->setText(getNameForTrackIndex(sky_index)); + } + + if (alt_panel && (sky_index > 1)) + { + // move containing panel + LLRect rect = alt_panel->getRect(); + S32 height = rect.getHeight(); + rect.mBottom = sld_bottom + (sld_offset / 2 + 1) + pos - (height / 2); + rect.mTop = rect.mBottom + height; + alt_panel->setRect(rect); + } + +} + +void LLPanelEnvironmentInfo::readjustAltLabels() +{ + // Re-adjust all labels + // Very simple "adjust after the fact" method + // Note: labels can be in any order + + LLMultiSliderCtrl *sld = findChild(SLD_ALTITUDES); + if (!sld) return; + + LLView* view_midle = NULL; + U32 midle_ind = 0; + S32 shift_up = 0; + S32 shift_down = 0; + LLRect sld_rect = sld->getRect(); + + // Find the middle one + for (U32 i = 0; i < ALTITUDE_SLIDER_COUNT; i++) + { + LLView* cmp_view = findChild(alt_panels[i], true); + if (!cmp_view) return; + LLRect cmp_rect = cmp_view->getRect(); + S32 pos = 0; + shift_up = 0; + shift_down = 0; + + for (U32 j = 0; j < ALTITUDE_SLIDER_COUNT; j++) + { + if (i != j) + { + LLView* intr_view = findChild(alt_panels[j], true); + if (!intr_view) return; + LLRect intr_rect = intr_view->getRect(); + if (cmp_rect.mBottom >= intr_rect.mBottom) + { + pos++; + } + if (intr_rect.mBottom <= cmp_rect.mTop && intr_rect.mBottom >= cmp_rect.mBottom) + { + shift_up = cmp_rect.mTop - intr_rect.mBottom; + } + else if (intr_rect.mTop >= cmp_rect.mBottom && intr_rect.mBottom <= cmp_rect.mBottom) + { + shift_down = cmp_rect.mBottom - intr_rect.mTop; + } + } + } + if (pos == 1) // middle + { + view_midle = cmp_view; + midle_ind = i; + break; + } + } + + // Account for edges + LLRect midle_rect = view_midle->getRect(); + F32 factor = 0.5f; + S32 edge_zone_height = midle_rect.getHeight() * 1.5f; + + if (midle_rect.mBottom - sld_rect.mBottom < edge_zone_height) + { + factor = 1 - ((midle_rect.mBottom - sld_rect.mBottom) / (edge_zone_height * 2)); + } + else if (sld_rect.mTop - midle_rect.mTop < edge_zone_height ) + { + factor = ((sld_rect.mTop - midle_rect.mTop) / (edge_zone_height * 2)); + } + + S32 shift_middle = (S32)(((F32)shift_down * factor) + ((F32)shift_up * (1.f - factor))); + shift_down = shift_down - shift_middle; + shift_up = shift_up - shift_middle; + + // fix crossings + for (U32 i = 0; i < ALTITUDE_SLIDER_COUNT; i++) + { + if (i != midle_ind) + { + LLView* trn_view = findChild(alt_panels[i], true); + LLRect trn_rect = trn_view->getRect(); + + if (trn_rect.mBottom <= midle_rect.mTop && trn_rect.mBottom >= midle_rect.mBottom) + { + // Approximate shift + trn_rect.translate(0, shift_up); + trn_view->setRect(trn_rect); + } + else if (trn_rect.mTop >= midle_rect.mBottom && trn_rect.mBottom <= midle_rect.mBottom) + { + // Approximate shift + trn_rect.translate(0, shift_down); + trn_view->setRect(trn_rect); + } + } + } + + if (shift_middle != 0) + { + midle_rect.translate(0, -shift_middle); //reversed relative to others + view_midle->setRect(midle_rect); + } +} + +void LLPanelEnvironmentInfo::onSldDayLengthChanged(F32 value) +{ + if (mCurrentEnvironment) + { + F32Hours daylength(value); + + mCurrentEnvironment->mDayLength = daylength; + setDirtyFlag(DIRTY_FLAG_DAYLENGTH); + + udpateApparentTimeOfDay(); + } +} + +void LLPanelEnvironmentInfo::onSldDayOffsetChanged(F32 value) +{ + if (mCurrentEnvironment) + { + F32Hours dayoffset(value); + + if (dayoffset.value() <= 0.0f) + dayoffset += F32Hours(24.0); + + mCurrentEnvironment->mDayOffset = dayoffset; + setDirtyFlag(DIRTY_FLAG_DAYOFFSET); + + udpateApparentTimeOfDay(); + } +} + +void LLPanelEnvironmentInfo::onDayLenOffsetMouseUp() +{ + commitDayLenOffsetChanges(true); +} + +void LLPanelEnvironmentInfo::commitDayLenOffsetChanges(bool need_callback) +{ + if (mCurrentEnvironment && (getDirtyFlag() & (DIRTY_FLAG_DAYLENGTH | DIRTY_FLAG_DAYOFFSET))) + { + clearDirtyFlag(DIRTY_FLAG_DAYOFFSET); + clearDirtyFlag(DIRTY_FLAG_DAYLENGTH); + + LLHandle that_h = getHandle(); + + if (need_callback) + { + LLEnvironment::instance().updateParcel(getParcelId(), + LLSettingsDay::ptr_t(), + mCurrentEnvironment->mDayLength.value(), + mCurrentEnvironment->mDayOffset.value(), + LLEnvironment::altitudes_vect_t(), + [that_h](S32 parcel_id, LLEnvironment::EnvironmentInfo::ptr_t envifo) { _onEnvironmentReceived(that_h, parcel_id, envifo); }); + } + else + { + LLEnvironment::instance().updateParcel(getParcelId(), + LLSettingsDay::ptr_t(), + mCurrentEnvironment->mDayLength.value(), + mCurrentEnvironment->mDayOffset.value(), + LLEnvironment::altitudes_vect_t()); + } + + } +} + +void LLPanelEnvironmentInfo::onAltSliderCallback(LLUICtrl *cntrl, const LLSD &data) +{ + LLMultiSliderCtrl *sld = (LLMultiSliderCtrl *)cntrl; + std::string sld_name = sld->getCurSlider(); + + if (sld_name.empty()) return; + + F32 sld_value = sld->getCurSliderValue(); + + mAltitudes[sld_name].mAltitude = sld_value; + + // update all labels since we could have jumped multiple and we will need to readjust + // (or sort by altitude, too little elements, so I didn't bother with efficiency) + altitudes_data_t::iterator end = mAltitudes.end(); + altitudes_data_t::iterator iter = mAltitudes.begin(); + altitudes_data_t::iterator iter2; + U32 new_index; + while (iter != end) + { + iter2 = mAltitudes.begin(); + new_index = 2; + while (iter2 != end) + { + if (iter->second.mAltitude > iter2->second.mAltitude) + { + new_index++; + } + iter2++; + } + iter->second.mTrackIndex = new_index; + + updateAltLabel(alt_prefixes[iter->second.mLabelIndex], iter->second.mTrackIndex, iter->second.mAltitude); + iter++; + } + + readjustAltLabels(); + setDirtyFlag(DIRTY_FLAG_ALTITUDES); +} + +void LLPanelEnvironmentInfo::onAltSliderMouseUp() +{ + if (isRegion() && (getDirtyFlag() & DIRTY_FLAG_ALTITUDES)) + { + clearDirtyFlag(DIRTY_FLAG_ALTITUDES); + clearDirtyFlag(DIRTY_FLAG_DAYLENGTH); + clearDirtyFlag(DIRTY_FLAG_DAYOFFSET); + + LLHandle that_h = getHandle(); + LLEnvironment::altitudes_vect_t alts; + + for (auto alt : mAltitudes) + { + alts.push_back(alt.second.mAltitude); + } + setControlsEnabled(false); + LLEnvironment::instance().updateParcel(getParcelId(), + LLSettingsDay::ptr_t(), + mCurrentEnvironment ? mCurrentEnvironment->mDayLength.value() : -1, + mCurrentEnvironment ? mCurrentEnvironment->mDayOffset.value() : -1, + alts); + } +} + +void LLPanelEnvironmentInfo::onBtnDefault() +{ + LLHandle that_h = getHandle(); + S32 parcel_id = getParcelId(); + LLNotificationsUtil::add("SettingsConfirmReset", LLSD(), LLSD(), + [that_h, parcel_id](const LLSD¬if, const LLSD&resp) + { + S32 opt = LLNotificationsUtil::getSelectedOption(notif, resp); + if (opt == 0) + { + LLEnvironment::instance().resetParcel(parcel_id, + [that_h](S32 parcel_id, LLEnvironment::EnvironmentInfo::ptr_t envifo) { _onEnvironmentReceived(that_h, parcel_id, envifo); }); + } + }); +} + +void LLPanelEnvironmentInfo::onBtnEdit() +{ + static const S32 FOURHOURS(4 * 60 * 60); + + LLFloaterEditExtDayCycle *dayeditor = getEditFloater(); + + LLSD params(LLSDMap(LLFloaterEditExtDayCycle::KEY_EDIT_CONTEXT, isRegion() ? LLFloaterEditExtDayCycle::VALUE_CONTEXT_REGION : LLFloaterEditExtDayCycle::VALUE_CONTEXT_PARCEL) + (LLFloaterEditExtDayCycle::KEY_DAY_LENGTH, mCurrentEnvironment ? (S32)(mCurrentEnvironment->mDayLength.value()) : FOURHOURS) + (LLFloaterEditExtDayCycle::KEY_CANMOD, LLSD::Boolean(true))); + + dayeditor->openFloater(params); + if (mCurrentEnvironment && mCurrentEnvironment->mDayCycle) + { + dayeditor->setEditDayCycle(mCurrentEnvironment->mDayCycle); + if (!ends_with(mCurrentEnvironment->mDayCycle->getName(), "(customized)")) + { + dayeditor->setEditName(mCurrentEnvironment->mDayCycle->getName() + "(customized)"); + } + } + else + dayeditor->setEditDefaultDayCycle(); +} + +void LLPanelEnvironmentInfo::onBtnSelect() +{ + LLFloaterSettingsPicker *picker = getSettingsPicker(); + if (picker) + { + LLUUID item_id; + if (mCurrentEnvironment && mCurrentEnvironment->mDayCycle) + { + item_id = LLFloaterSettingsPicker::findItemID(mCurrentEnvironment->mDayCycle->getAssetId(), false, false); + } + picker->setSettingsFilter(LLSettingsType::ST_NONE); + picker->setSettingsItemId(item_id); + picker->openFloater(); + picker->setFocus(true); + } +} + +void LLPanelEnvironmentInfo::onBtnRstAltitudes() +{ + if (isRegion()) + { + LLHandle that_h = getHandle(); + LLEnvironment::altitudes_vect_t alts; + + clearDirtyFlag(DIRTY_FLAG_ALTITUDES); + clearDirtyFlag(DIRTY_FLAG_DAYLENGTH); + clearDirtyFlag(DIRTY_FLAG_DAYOFFSET); + + for (S32 idx = 1; idx <= ALTITUDE_SLIDER_COUNT; ++idx) + { + F32 new_height = idx * ALTITUDE_DEFAULT_HEIGHT_STEP; + alts.push_back(new_height); + } + + LLEnvironment::instance().updateParcel(getParcelId(), + LLSettingsDay::ptr_t(), + mCurrentEnvironment ? mCurrentEnvironment->mDayLength.value() : -1, + mCurrentEnvironment ? mCurrentEnvironment->mDayOffset.value() : -1, + alts, + [that_h](S32 parcel_id, LLEnvironment::EnvironmentInfo::ptr_t envifo) { _onEnvironmentReceived(that_h, parcel_id, envifo); }); + } +} + +void LLPanelEnvironmentInfo::udpateApparentTimeOfDay() +{ + static const F32 SECONDSINDAY(24.0 * 60.0 * 60.0); + + if ((!mCurrentEnvironment) || (mCurrentEnvironment->mDayLength.value() < 1.0) || (mCurrentEnvironment->mDayOffset.value() < 1.0)) + { + getChild(LBL_TIMEOFDAY)->setVisible(false); + return; + } + getChild(LBL_TIMEOFDAY)->setVisible(true); + + S32Seconds now(LLDate::now().secondsSinceEpoch()); + + now += mCurrentEnvironment->mDayOffset; + + F32 perc = (F32)(now.value() % mCurrentEnvironment->mDayLength.value()) / (F32)(mCurrentEnvironment->mDayLength.value()); + + S32Seconds secondofday((S32)(perc * SECONDSINDAY)); + S32Hours hourofday(secondofday); + S32Seconds secondofhour(secondofday - hourofday); + S32Minutes minutesofhour(secondofhour); + bool am_pm(hourofday.value() >= 12); + + if (hourofday.value() < 1) + hourofday = S32Hours(12); + if (hourofday.value() > 12) + hourofday -= S32Hours(12); + + std::string lblminute(((minutesofhour.value() < 10) ? "0" : "") + LLSD(minutesofhour.value()).asString()); + + + getChild(LBL_TIMEOFDAY)->setTextArg("[HH]", LLSD(hourofday.value()).asString()); + getChild(LBL_TIMEOFDAY)->setTextArg("[MM]", lblminute); + getChild(LBL_TIMEOFDAY)->setTextArg("[AP]", std::string(am_pm ? "PM" : "AM")); + getChild(LBL_TIMEOFDAY)->setTextArg("[PRC]", LLSD((S32)(100 * perc)).asString()); + +} + +void LLPanelEnvironmentInfo::onIdlePlay(void *data) +{ + ((LLPanelEnvironmentInfo *)data)->udpateApparentTimeOfDay(); +} + + +void LLPanelEnvironmentInfo::onPickerCommitted(LLUUID item_id, std::string source) +{ + if (source == alt_prefixes[4]) + { + onPickerCommitted(item_id, 0); + } + else if (source == alt_prefixes[3]) + { + onPickerCommitted(item_id, 1); + } + else + { + onPickerCommitted(item_id, mAltitudes[source].mTrackIndex); + } +} + +void LLPanelEnvironmentInfo::onPickerCommitted(LLUUID item_id, S32 track_num) +{ + LLInventoryItem *itemp = gInventory.getItem(item_id); + if (itemp) + { + LLHandle that_h = getHandle(); + clearDirtyFlag(DIRTY_FLAG_DAYLENGTH); + clearDirtyFlag(DIRTY_FLAG_DAYOFFSET); + + U32 flags(0); + + if (itemp) + { + if (!itemp->getPermissions().allowOperationBy(PERM_MODIFY, gAgent.getID())) + flags |= LLSettingsBase::FLAG_NOMOD; + if (!itemp->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID())) + flags |= LLSettingsBase::FLAG_NOTRANS; + } + + LLEnvironment::instance().updateParcel(getParcelId(), + itemp->getAssetUUID(), + itemp->getName(), + track_num, + mCurrentEnvironment ? mCurrentEnvironment->mDayLength.value() : -1, + mCurrentEnvironment ? mCurrentEnvironment->mDayOffset.value() : -1, + flags, + LLEnvironment::altitudes_vect_t(), + [that_h](S32 parcel_id, LLEnvironment::EnvironmentInfo::ptr_t envifo) { _onEnvironmentReceived(that_h, parcel_id, envifo); }); + } +} + +void LLPanelEnvironmentInfo::onEditCommitted(LLSettingsDay::ptr_t newday) +{ + LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_EDIT); + LLEnvironment::instance().updateEnvironment(); + if (!newday) + { + LL_WARNS("ENVPANEL") << "Editor committed an empty day. Do nothing." << LL_ENDL; + return; + } + if (!mCurrentEnvironment) + { + // Attempting to save mid update? + LL_WARNS("ENVPANEL") << "Failed to apply changes from editor! Dirty state: " << mDirtyFlag << " env version: " << mCurEnvVersion << LL_ENDL; + return; + } + size_t newhash(newday->getHash()); + size_t oldhash((mCurrentEnvironment->mDayCycle) ? mCurrentEnvironment->mDayCycle->getHash() : 0); + + if (newhash != oldhash) + { + LLHandle that_h = getHandle(); + clearDirtyFlag(DIRTY_FLAG_DAYLENGTH); + clearDirtyFlag(DIRTY_FLAG_DAYOFFSET); + + LLEnvironment::instance().updateParcel(getParcelId(), + newday, + mCurrentEnvironment ? mCurrentEnvironment->mDayLength.value() : -1, + mCurrentEnvironment ? mCurrentEnvironment->mDayOffset.value() : -1, + LLEnvironment::altitudes_vect_t(), + [that_h](S32 parcel_id, LLEnvironment::EnvironmentInfo::ptr_t envifo) { _onEnvironmentReceived(that_h, parcel_id, envifo); }); + } +} + +void LLPanelEnvironmentInfo::onEnvironmentChanged(LLEnvironment::EnvSelection_t env, S32 new_version) +{ + if (new_version < INVALID_PARCEL_ENVIRONMENT_VERSION) + { + // cleanups and local changes, we are only interested in changes sent by server + return; + } + + LL_DEBUGS("ENVPANEL") << "Received environment update " << mCurEnvVersion << " " << new_version << LL_ENDL; + + // Environment comes from different sources, from environment update callbacks, + // from hovers (causes callbacks on version change) and from personal requests + // filter out duplicates and out of order packets by checking parcel environment version. + + if (isRegion()) + { + // Note: region uses same init versions as parcel + if (env == LLEnvironment::ENV_REGION + // version should be always growing, UNSET_PARCEL_ENVIRONMENT_VERSION is backup case + && (mCurEnvVersion < new_version || mCurEnvVersion <= UNSET_PARCEL_ENVIRONMENT_VERSION)) + { + if (new_version >= UNSET_PARCEL_ENVIRONMENT_VERSION) + { + // 'pending state' to prevent re-request on following onEnvironmentChanged if there will be any + mCurEnvVersion = new_version; + } + mCurrentEnvironment.reset(); + refreshFromSource(); + } + } + else if ((env == LLEnvironment::ENV_PARCEL) + && (getParcelId() == LLViewerParcelMgr::instance().getAgentParcelId())) + { + LLParcel *parcel = getParcel(); + if (parcel) + { + // first for parcel own settings, second is for case when parcel uses region settings + if (mCurEnvVersion < new_version + || (mCurEnvVersion != new_version && new_version == UNSET_PARCEL_ENVIRONMENT_VERSION)) + { + // 'pending state' to prevent re-request on following onEnvironmentChanged if there will be any + mCurEnvVersion = new_version; + mCurrentEnvironment.reset(); + + refreshFromSource(); + } + else if (mCurrentEnvironment) + { + // update controls + refresh(); + } + } + } +} + + +void LLPanelEnvironmentInfo::onPickerAssetDownloaded(LLSettingsBase::ptr_t settings) +{ + LLSettingsVODay::buildFromOtherSetting(settings, [this](LLSettingsDay::ptr_t pday) + { + if (pday) + { + mCurrentEnvironment->mDayCycle = pday; + setDirtyFlag(DIRTY_FLAG_DAYCYCLE); + } + refresh(); + }); +} + +void LLPanelEnvironmentInfo::onEnvironmentReceived(S32 parcel_id, LLEnvironment::EnvironmentInfo::ptr_t envifo) +{ + if (parcel_id != getParcelId()) + { + LL_WARNS("ENVPANEL") << "Have environment for parcel " << parcel_id << " expecting " << getParcelId() << ". Discarding." << LL_ENDL; + return; + } + mCurrentEnvironment = envifo; + clearDirtyFlag(DIRTY_FLAG_MASK); + if (mCurrentEnvironment->mEnvVersion > INVALID_PARCEL_ENVIRONMENT_VERSION) + { + // Server provided version, use it + mCurEnvVersion = mCurrentEnvironment->mEnvVersion; + LL_DEBUGS("ENVPANEL") << " Setting environment version: " << mCurEnvVersion << " for parcel id: " << parcel_id << LL_ENDL; + } + // Backup: Version was not provided for some reason + else + { + LL_WARNS("ENVPANEL") << " Environment version was not provided for " << parcel_id << ", old env version: " << mCurEnvVersion << LL_ENDL; + } + + refreshFromEstate(); + refresh(); + + // todo: we have envifo and parcel env version, should we just setEnvironment() and parcel's property to prevent dupplicate requests? +} + +void LLPanelEnvironmentInfo::_onEnvironmentReceived(LLHandle that_h, S32 parcel_id, LLEnvironment::EnvironmentInfo::ptr_t envifo) +{ + LLPanelEnvironmentInfo *that = (LLPanelEnvironmentInfo *)that_h.get(); + if (!that) + return; + that->onEnvironmentReceived(parcel_id, envifo); +} + +LLSettingsDropTarget::LLSettingsDropTarget(const LLSettingsDropTarget::Params& p) + : LLView(p), mEnvironmentInfoPanel(NULL), mDndEnabled(false) +{} + +bool LLSettingsDropTarget::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + bool handled = false; + + if (getParent() && mDndEnabled) + { + handled = true; + + switch (cargo_type) + { + case DAD_SETTINGS: + { + LLViewerInventoryItem* inv_item = (LLViewerInventoryItem*)cargo_data; + if (inv_item && mEnvironmentInfoPanel) + { + LLUUID item_id = inv_item->getUUID(); + if (gInventory.getItem(item_id)) + { + *accept = ACCEPT_YES_COPY_SINGLE; + if (drop) + { + // might be better to use name of the element + mEnvironmentInfoPanel->onPickerCommitted(item_id, mTrack); + } + } + } + else + { + *accept = ACCEPT_NO; + } + break; + } + default: + *accept = ACCEPT_NO; + break; + } + } + return handled; +} diff --git a/indra/newview/llpanelexperiencelisteditor.cpp b/indra/newview/llpanelexperiencelisteditor.cpp index 2de6840c30..01c8e88370 100644 --- a/indra/newview/llpanelexperiencelisteditor.cpp +++ b/indra/newview/llpanelexperiencelisteditor.cpp @@ -1,270 +1,270 @@ -/** - * @file llpanelexperiencelisteditor.cpp - * @brief Editor for building a list of experiences - * - * $LicenseInfo:firstyear=2014&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelexperiencelisteditor.h" - -#include "llbutton.h" -#include "llexperiencecache.h" -#include "llfloaterexperiencepicker.h" -#include "llfloaterreg.h" -#include "llhandle.h" -#include "llnamelistctrl.h" -#include "llscrolllistctrl.h" -#include "llviewerregion.h" -#include "llagent.h" -#include "lltextbox.h" -#include "lltrans.h" -#include "llsdutil.h" - - -static LLPanelInjector t_panel_experience_list_editor("panel_experience_list_editor"); - - -LLPanelExperienceListEditor::LLPanelExperienceListEditor() - :mItems(NULL) - ,mProfile(NULL) - ,mRemove(NULL) - ,mReadonly(false) - ,mMaxExperienceIDs(0) -{ -} - -bool LLPanelExperienceListEditor::postBuild() -{ - mItems = getChild("experience_list"); - mAdd = getChild("btn_add"); - mRemove = getChild("btn_remove"); - mProfile = getChild("btn_profile"); - - childSetAction("btn_add", boost::bind(&LLPanelExperienceListEditor::onAdd, this)); - childSetAction("btn_remove", boost::bind(&LLPanelExperienceListEditor::onRemove, this)); - childSetAction("btn_profile", boost::bind(&LLPanelExperienceListEditor::onProfile, this)); - - mItems->setCommitCallback(boost::bind(&LLPanelExperienceListEditor::checkButtonsEnabled, this)); - - checkButtonsEnabled(); - return true; -} - -const uuid_list_t& LLPanelExperienceListEditor::getExperienceIds() const -{ - return mExperienceIds; -} - -void LLPanelExperienceListEditor::addExperienceIds( const uuid_vec_t& experience_ids ) -{ - // the commented out code in this function is handled by the callback and no longer necessary! - - //mExperienceIds.insert(experience_ids.begin(), experience_ids.end()); - //onItems(); - if(!mAddedCallback.empty()) - { - for(uuid_vec_t::const_iterator it = experience_ids.begin(); it != experience_ids.end(); ++it) - { - mAddedCallback(*it); - } - } -} - - -void LLPanelExperienceListEditor::setExperienceIds( const LLSD& experience_ids ) -{ - mExperienceIds.clear(); - for (LLSD uuid : llsd::inArray(experience_ids)) - { - // Using insert(range) doesn't work here because the conversion from - // LLSD to LLUUID is ambiguous: have to specify asUUID() for each entry. - mExperienceIds.insert(uuid.asUUID()); - } - onItems(); -} - -void LLPanelExperienceListEditor::addExperience( const LLUUID& id ) -{ - mExperienceIds.insert(id); - onItems(); -} -void LLPanelExperienceListEditor::onAdd() -{ - if(!mPicker.isDead()) - { - mPicker.markDead(); - } - - mKey.generateNewID(); - - LLFloaterExperiencePicker* picker=LLFloaterExperiencePicker::show(boost::bind(&LLPanelExperienceListEditor::addExperienceIds, this, _1), mKey, false, true, mFilters, mAdd); - mPicker = picker->getDerivedHandle(); -} - - -void LLPanelExperienceListEditor::onRemove() -{ - // the commented out code in this function is handled by the callback and no longer necessary! - - std::vector items= mItems->getAllSelected(); - std::vector::iterator it = items.begin(); - for(/**/; it != items.end(); ++it) - { - if((*it) != NULL) - { - //mExperienceIds.erase((*it)->getValue()); - mRemovedCallback((*it)->getValue()); - } - } - mItems->selectFirstItem(); - checkButtonsEnabled(); - //onItems(); -} - -void LLPanelExperienceListEditor::onProfile() -{ - LLScrollListItem* item = mItems->getFirstSelected(); - if(item) - { - LLFloaterReg::showInstance("experience_profile", item->getUUID(), true); - } -} - -void LLPanelExperienceListEditor::checkButtonsEnabled() -{ - mAdd->setEnabled(!mReadonly); - int selected = mItems->getNumSelected(); - - bool remove_enabled = !mReadonly && selected>0; - if(remove_enabled && mSticky) - { - std::vector items= mItems->getAllSelected(); - std::vector::iterator it = items.begin(); - for(/**/; it != items.end() && remove_enabled; ++it) - { - if((*it) != NULL) - { - remove_enabled = !mSticky((*it)->getValue()); - } - } - - - } - mRemove->setEnabled(remove_enabled); - mProfile->setEnabled(selected==1); -} - -void LLPanelExperienceListEditor::onItems() -{ - mItems->deleteAllItems(); - - LLSD item; - uuid_list_t::iterator it = mExperienceIds.begin(); - for(/**/; it != mExperienceIds.end(); ++it) - { - const LLUUID& experience = *it; - item["id"]=experience; - item["target"] = LLNameListCtrl::EXPERIENCE; - LLSD& columns = item["columns"]; - columns[0]["column"] = "experience_name"; - columns[0]["value"] = getString("loading"); - mItems->addElement(item); - - LLExperienceCache::instance().get(experience, boost::bind(&LLPanelExperienceListEditor::experienceDetailsCallback, - getDerivedHandle(), _1)); - } - - - if(mItems->getItemCount() == 0) - { - mItems->setCommentText(getString("no_results")); - } - - - checkButtonsEnabled(); -} - -void LLPanelExperienceListEditor::experienceDetailsCallback( LLHandle panel, const LLSD& experience ) -{ - if(!panel.isDead()) - { - panel.get()->onExperienceDetails(experience); - } -} - -void LLPanelExperienceListEditor::onExperienceDetails( const LLSD& experience ) -{ - LLScrollListItem* item = mItems->getItem(experience[LLExperienceCache::EXPERIENCE_ID]); - if(!item) - return; - - std::string experience_name_string = experience[LLExperienceCache::NAME].asString(); - if (experience_name_string.empty()) - { - experience_name_string = LLTrans::getString("ExperienceNameUntitled"); - } - - item->getColumn(0)->setValue(experience_name_string); -} - -LLPanelExperienceListEditor::~LLPanelExperienceListEditor() -{ - if(!mPicker.isDead()) - { - mPicker.get()->closeFloater(); - } -} - -void LLPanelExperienceListEditor::loading() -{ - mItems->clear(); - mItems->setCommentText( getString("loading")); -} - -void LLPanelExperienceListEditor::setReadonly( bool val ) -{ - mReadonly = val; - checkButtonsEnabled(); -} - -void LLPanelExperienceListEditor::refreshExperienceCounter() -{ - if(mMaxExperienceIDs > 0) - { - LLStringUtil::format_map_t args; - args["[EXPERIENCES]"] = llformat("%d", mItems->getItemCount()); - args["[MAXEXPERIENCES]"] = llformat("%d", mMaxExperienceIDs); - getChild("text_count")->setText(LLTrans::getString("ExperiencesCounter", args)); - } -} - -boost::signals2::connection LLPanelExperienceListEditor::setAddedCallback( list_changed_signal_t::slot_type cb ) -{ - return mAddedCallback.connect(cb); -} - -boost::signals2::connection LLPanelExperienceListEditor::setRemovedCallback( list_changed_signal_t::slot_type cb ) -{ - return mRemovedCallback.connect(cb); -} +/** + * @file llpanelexperiencelisteditor.cpp + * @brief Editor for building a list of experiences + * + * $LicenseInfo:firstyear=2014&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelexperiencelisteditor.h" + +#include "llbutton.h" +#include "llexperiencecache.h" +#include "llfloaterexperiencepicker.h" +#include "llfloaterreg.h" +#include "llhandle.h" +#include "llnamelistctrl.h" +#include "llscrolllistctrl.h" +#include "llviewerregion.h" +#include "llagent.h" +#include "lltextbox.h" +#include "lltrans.h" +#include "llsdutil.h" + + +static LLPanelInjector t_panel_experience_list_editor("panel_experience_list_editor"); + + +LLPanelExperienceListEditor::LLPanelExperienceListEditor() + :mItems(NULL) + ,mProfile(NULL) + ,mRemove(NULL) + ,mReadonly(false) + ,mMaxExperienceIDs(0) +{ +} + +bool LLPanelExperienceListEditor::postBuild() +{ + mItems = getChild("experience_list"); + mAdd = getChild("btn_add"); + mRemove = getChild("btn_remove"); + mProfile = getChild("btn_profile"); + + childSetAction("btn_add", boost::bind(&LLPanelExperienceListEditor::onAdd, this)); + childSetAction("btn_remove", boost::bind(&LLPanelExperienceListEditor::onRemove, this)); + childSetAction("btn_profile", boost::bind(&LLPanelExperienceListEditor::onProfile, this)); + + mItems->setCommitCallback(boost::bind(&LLPanelExperienceListEditor::checkButtonsEnabled, this)); + + checkButtonsEnabled(); + return true; +} + +const uuid_list_t& LLPanelExperienceListEditor::getExperienceIds() const +{ + return mExperienceIds; +} + +void LLPanelExperienceListEditor::addExperienceIds( const uuid_vec_t& experience_ids ) +{ + // the commented out code in this function is handled by the callback and no longer necessary! + + //mExperienceIds.insert(experience_ids.begin(), experience_ids.end()); + //onItems(); + if(!mAddedCallback.empty()) + { + for(uuid_vec_t::const_iterator it = experience_ids.begin(); it != experience_ids.end(); ++it) + { + mAddedCallback(*it); + } + } +} + + +void LLPanelExperienceListEditor::setExperienceIds( const LLSD& experience_ids ) +{ + mExperienceIds.clear(); + for (LLSD uuid : llsd::inArray(experience_ids)) + { + // Using insert(range) doesn't work here because the conversion from + // LLSD to LLUUID is ambiguous: have to specify asUUID() for each entry. + mExperienceIds.insert(uuid.asUUID()); + } + onItems(); +} + +void LLPanelExperienceListEditor::addExperience( const LLUUID& id ) +{ + mExperienceIds.insert(id); + onItems(); +} +void LLPanelExperienceListEditor::onAdd() +{ + if(!mPicker.isDead()) + { + mPicker.markDead(); + } + + mKey.generateNewID(); + + LLFloaterExperiencePicker* picker=LLFloaterExperiencePicker::show(boost::bind(&LLPanelExperienceListEditor::addExperienceIds, this, _1), mKey, false, true, mFilters, mAdd); + mPicker = picker->getDerivedHandle(); +} + + +void LLPanelExperienceListEditor::onRemove() +{ + // the commented out code in this function is handled by the callback and no longer necessary! + + std::vector items= mItems->getAllSelected(); + std::vector::iterator it = items.begin(); + for(/**/; it != items.end(); ++it) + { + if((*it) != NULL) + { + //mExperienceIds.erase((*it)->getValue()); + mRemovedCallback((*it)->getValue()); + } + } + mItems->selectFirstItem(); + checkButtonsEnabled(); + //onItems(); +} + +void LLPanelExperienceListEditor::onProfile() +{ + LLScrollListItem* item = mItems->getFirstSelected(); + if(item) + { + LLFloaterReg::showInstance("experience_profile", item->getUUID(), true); + } +} + +void LLPanelExperienceListEditor::checkButtonsEnabled() +{ + mAdd->setEnabled(!mReadonly); + int selected = mItems->getNumSelected(); + + bool remove_enabled = !mReadonly && selected>0; + if(remove_enabled && mSticky) + { + std::vector items= mItems->getAllSelected(); + std::vector::iterator it = items.begin(); + for(/**/; it != items.end() && remove_enabled; ++it) + { + if((*it) != NULL) + { + remove_enabled = !mSticky((*it)->getValue()); + } + } + + + } + mRemove->setEnabled(remove_enabled); + mProfile->setEnabled(selected==1); +} + +void LLPanelExperienceListEditor::onItems() +{ + mItems->deleteAllItems(); + + LLSD item; + uuid_list_t::iterator it = mExperienceIds.begin(); + for(/**/; it != mExperienceIds.end(); ++it) + { + const LLUUID& experience = *it; + item["id"]=experience; + item["target"] = LLNameListCtrl::EXPERIENCE; + LLSD& columns = item["columns"]; + columns[0]["column"] = "experience_name"; + columns[0]["value"] = getString("loading"); + mItems->addElement(item); + + LLExperienceCache::instance().get(experience, boost::bind(&LLPanelExperienceListEditor::experienceDetailsCallback, + getDerivedHandle(), _1)); + } + + + if(mItems->getItemCount() == 0) + { + mItems->setCommentText(getString("no_results")); + } + + + checkButtonsEnabled(); +} + +void LLPanelExperienceListEditor::experienceDetailsCallback( LLHandle panel, const LLSD& experience ) +{ + if(!panel.isDead()) + { + panel.get()->onExperienceDetails(experience); + } +} + +void LLPanelExperienceListEditor::onExperienceDetails( const LLSD& experience ) +{ + LLScrollListItem* item = mItems->getItem(experience[LLExperienceCache::EXPERIENCE_ID]); + if(!item) + return; + + std::string experience_name_string = experience[LLExperienceCache::NAME].asString(); + if (experience_name_string.empty()) + { + experience_name_string = LLTrans::getString("ExperienceNameUntitled"); + } + + item->getColumn(0)->setValue(experience_name_string); +} + +LLPanelExperienceListEditor::~LLPanelExperienceListEditor() +{ + if(!mPicker.isDead()) + { + mPicker.get()->closeFloater(); + } +} + +void LLPanelExperienceListEditor::loading() +{ + mItems->clear(); + mItems->setCommentText( getString("loading")); +} + +void LLPanelExperienceListEditor::setReadonly( bool val ) +{ + mReadonly = val; + checkButtonsEnabled(); +} + +void LLPanelExperienceListEditor::refreshExperienceCounter() +{ + if(mMaxExperienceIDs > 0) + { + LLStringUtil::format_map_t args; + args["[EXPERIENCES]"] = llformat("%d", mItems->getItemCount()); + args["[MAXEXPERIENCES]"] = llformat("%d", mMaxExperienceIDs); + getChild("text_count")->setText(LLTrans::getString("ExperiencesCounter", args)); + } +} + +boost::signals2::connection LLPanelExperienceListEditor::setAddedCallback( list_changed_signal_t::slot_type cb ) +{ + return mAddedCallback.connect(cb); +} + +boost::signals2::connection LLPanelExperienceListEditor::setRemovedCallback( list_changed_signal_t::slot_type cb ) +{ + return mRemovedCallback.connect(cb); +} diff --git a/indra/newview/llpanelexperiencelisteditor.h b/indra/newview/llpanelexperiencelisteditor.h index 558aecb202..7ff1ddac5a 100644 --- a/indra/newview/llpanelexperiencelisteditor.h +++ b/indra/newview/llpanelexperiencelisteditor.h @@ -1,101 +1,101 @@ -/** -* @file llpanelexperiencelisteditor.cpp -* @brief Editor for building a list of experiences -* -* $LicenseInfo:firstyear=2014&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELEXPERIENCELISTEDITOR_H -#define LL_LLPANELEXPERIENCELISTEDITOR_H - -#include "llpanel.h" -#include "lluuid.h" -#include - -class LLNameListCtrl; -class LLScrollListCtrl; -class LLButton; -class LLFloaterExperiencePicker; - -class LLPanelExperienceListEditor : public LLPanel -{ -public: - - typedef boost::signals2::signal list_changed_signal_t; - // filter function for experiences, return true if the experience should be hidden. - typedef boost::function experience_function; - typedef std::vector filter_list; - typedef LLHandle PickerHandle; - LLPanelExperienceListEditor(); - ~LLPanelExperienceListEditor(); - bool postBuild(); - - void loading(); - - const uuid_list_t& getExperienceIds()const; - void setExperienceIds(const LLSD& experience_ids); - void addExperienceIds(const uuid_vec_t& experience_ids); - - void addExperience(const LLUUID& id); - - boost::signals2::connection setAddedCallback(list_changed_signal_t::slot_type cb ); - boost::signals2::connection setRemovedCallback(list_changed_signal_t::slot_type cb ); - - bool getReadonly() const { return mReadonly; } - void setReadonly(bool val); - - void refreshExperienceCounter(); - - void addFilter(experience_function func){mFilters.push_back(func);} - void setStickyFunction(experience_function func){mSticky = func;} - U32 getMaxExperienceIDs() const { return mMaxExperienceIDs; } - void setMaxExperienceIDs(U32 val) { mMaxExperienceIDs = val; } -private: - - void onItems(); - void onRemove(); - void onAdd(); - void onProfile(); - - void checkButtonsEnabled(); - static void experienceDetailsCallback( LLHandle panel, const LLSD& experience ); - void onExperienceDetails( const LLSD& experience ); - void processResponse( const LLSD& content ); - uuid_list_t mExperienceIds; - - - LLNameListCtrl* mItems; - filter_list mFilters; - LLButton* mAdd; - LLButton* mRemove; - LLButton* mProfile; - PickerHandle mPicker; - list_changed_signal_t mAddedCallback; - list_changed_signal_t mRemovedCallback; - LLUUID mKey; - bool mReadonly; - experience_function mSticky; - U32 mMaxExperienceIDs; - -}; - -#endif //LL_LLPANELEXPERIENCELISTEDITOR_H +/** +* @file llpanelexperiencelisteditor.cpp +* @brief Editor for building a list of experiences +* +* $LicenseInfo:firstyear=2014&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELEXPERIENCELISTEDITOR_H +#define LL_LLPANELEXPERIENCELISTEDITOR_H + +#include "llpanel.h" +#include "lluuid.h" +#include + +class LLNameListCtrl; +class LLScrollListCtrl; +class LLButton; +class LLFloaterExperiencePicker; + +class LLPanelExperienceListEditor : public LLPanel +{ +public: + + typedef boost::signals2::signal list_changed_signal_t; + // filter function for experiences, return true if the experience should be hidden. + typedef boost::function experience_function; + typedef std::vector filter_list; + typedef LLHandle PickerHandle; + LLPanelExperienceListEditor(); + ~LLPanelExperienceListEditor(); + bool postBuild(); + + void loading(); + + const uuid_list_t& getExperienceIds()const; + void setExperienceIds(const LLSD& experience_ids); + void addExperienceIds(const uuid_vec_t& experience_ids); + + void addExperience(const LLUUID& id); + + boost::signals2::connection setAddedCallback(list_changed_signal_t::slot_type cb ); + boost::signals2::connection setRemovedCallback(list_changed_signal_t::slot_type cb ); + + bool getReadonly() const { return mReadonly; } + void setReadonly(bool val); + + void refreshExperienceCounter(); + + void addFilter(experience_function func){mFilters.push_back(func);} + void setStickyFunction(experience_function func){mSticky = func;} + U32 getMaxExperienceIDs() const { return mMaxExperienceIDs; } + void setMaxExperienceIDs(U32 val) { mMaxExperienceIDs = val; } +private: + + void onItems(); + void onRemove(); + void onAdd(); + void onProfile(); + + void checkButtonsEnabled(); + static void experienceDetailsCallback( LLHandle panel, const LLSD& experience ); + void onExperienceDetails( const LLSD& experience ); + void processResponse( const LLSD& content ); + uuid_list_t mExperienceIds; + + + LLNameListCtrl* mItems; + filter_list mFilters; + LLButton* mAdd; + LLButton* mRemove; + LLButton* mProfile; + PickerHandle mPicker; + list_changed_signal_t mAddedCallback; + list_changed_signal_t mRemovedCallback; + LLUUID mKey; + bool mReadonly; + experience_function mSticky; + U32 mMaxExperienceIDs; + +}; + +#endif //LL_LLPANELEXPERIENCELISTEDITOR_H diff --git a/indra/newview/llpanelexperiencelog.cpp b/indra/newview/llpanelexperiencelog.cpp index 06c7045ab7..176da130bb 100644 --- a/indra/newview/llpanelexperiencelog.cpp +++ b/indra/newview/llpanelexperiencelog.cpp @@ -1,267 +1,267 @@ -/** - * @file llpanelexperiencelog.cpp - * @brief llpanelexperiencelog - * - * $LicenseInfo:firstyear=2014&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2014, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#include "llviewerprecompiledheaders.h" -#include "llpanelexperiencelog.h" - -#include "llexperiencelog.h" -#include "llexperiencecache.h" -#include "llbutton.h" -#include "llscrolllistctrl.h" -#include "llcombobox.h" -#include "llspinctrl.h" -#include "llcheckboxctrl.h" -#include "llfloaterreg.h" -#include "llfloaterreporter.h" -#include "llinventoryfunctions.h" - - -#define BTN_PROFILE_XP "btn_profile_xp" -#define BTN_REPORT_XP "btn_report_xp" - -static LLPanelInjector register_experiences_panel("experience_log"); - - -LLPanelExperienceLog::LLPanelExperienceLog( ) - : mEventList(NULL) - , mPageSize(25) - , mCurrentPage(0) -{ - buildFromFile("panel_experience_log.xml"); -} - -bool LLPanelExperienceLog::postBuild() -{ - LLExperienceLog* log = LLExperienceLog::getInstance(); - mEventList = getChild("experience_log_list"); - mEventList->setCommitCallback(boost::bind(&LLPanelExperienceLog::onSelectionChanged, this)); - mEventList->setDoubleClickCallback( boost::bind(&LLPanelExperienceLog::onProfileExperience, this)); - - getChild("btn_clear")->setCommitCallback(boost::bind(&LLExperienceLog::clear, log)); - getChild("btn_clear")->setCommitCallback(boost::bind(&LLPanelExperienceLog::refresh, this)); - - getChild(BTN_PROFILE_XP)->setCommitCallback(boost::bind(&LLPanelExperienceLog::onProfileExperience, this)); - getChild(BTN_REPORT_XP )->setCommitCallback(boost::bind(&LLPanelExperienceLog::onReportExperience, this)); - getChild("btn_notify" )->setCommitCallback(boost::bind(&LLPanelExperienceLog::onNotify, this)); - getChild("btn_next" )->setCommitCallback(boost::bind(&LLPanelExperienceLog::onNext, this)); - getChild("btn_prev" )->setCommitCallback(boost::bind(&LLPanelExperienceLog::onPrev, this)); - - LLCheckBoxCtrl* check = getChild("notify_all"); - check->set(log->getNotifyNewEvent()); - check->setCommitCallback(boost::bind(&LLPanelExperienceLog::notifyChanged, this)); - - - LLSpinCtrl* spin = getChild("logsizespinner"); - spin->set(log->getMaxDays()); - spin->setCommitCallback(boost::bind(&LLPanelExperienceLog::logSizeChanged, this)); - - mPageSize = log->getPageSize(); - refresh(); - mNewEvent = LLExperienceLog::instance().addUpdateSignal(boost::bind(&LLPanelExperienceLog::refresh, this)); - return true; -} - -LLPanelExperienceLog* LLPanelExperienceLog::create() -{ - return new LLPanelExperienceLog(); -} - -void LLPanelExperienceLog::refresh() -{ - S32 selected = mEventList->getFirstSelectedIndex(); - mEventList->deleteAllItems(); - const LLSD events = LLExperienceLog::instance().getEvents(); - - if(events.size() == 0) - { - mEventList->setCommentText(getString("no_events")); - return; - } - - setAllChildrenEnabled(false); - - LLSD item; - bool waiting = false; - LLUUID waiting_id; - - int itemsToSkip = mPageSize*mCurrentPage; - int items = 0; - bool moreItems = false; - LLSD events_to_save = events; - if (events.isMap() && events.size() != 0) - { - LLSD::map_const_iterator day = events.endMap(); - do - { - --day; - const LLSD& dayArray = day->second; - - std::string date = day->first; - if(!LLExperienceLog::instance().isNotExpired(date)) - { - events_to_save.erase(day->first); - continue; - } - int size = dayArray.size(); - if(itemsToSkip > size) - { - itemsToSkip -= size; - continue; - } - if(items >= mPageSize && size > 0) - { - moreItems = true; - break; - } - for(int i = dayArray.size() - itemsToSkip - 1; i >= 0; i--) - { - if(items >= mPageSize) - { - moreItems = true; - break; - } - const LLSD event = dayArray[i]; - LLUUID id = event[LLExperienceCache::EXPERIENCE_ID].asUUID(); - const LLSD& experience = LLExperienceCache::instance().get(id); - if(experience.isUndefined()){ - waiting = true; - waiting_id = id; - } - if(!waiting) - { - item["id"] = event; - - LLSD& columns = item["columns"]; - columns[0]["column"] = "time"; - columns[0]["value"] = day->first+event["Time"].asString(); - columns[1]["column"] = "event"; - columns[1]["value"] = LLExperienceLog::getPermissionString(event, "ExperiencePermissionShort"); - columns[2]["column"] = "experience_name"; - columns[2]["value"] = experience[LLExperienceCache::NAME].asString(); - columns[3]["column"] = "object_name"; - columns[3]["value"] = event["ObjectName"].asString(); - mEventList->addElement(item); - } - ++items; - } - } while (day != events.beginMap()); - } - LLExperienceLog::getInstance()->setEventsToSave(events_to_save); - if(waiting) - { - mEventList->deleteAllItems(); - mEventList->setCommentText(getString("loading")); - LLExperienceCache::instance().get(waiting_id, boost::bind(&LLPanelExperienceLog::refresh, this)); - } - else - { - setAllChildrenEnabled(true); - - mEventList->setEnabled(true); - getChild("btn_next")->setEnabled(moreItems); - getChild("btn_prev")->setEnabled(mCurrentPage>0); - getChild("btn_clear")->setEnabled(mEventList->getItemCount()>0); - if(selected<0) - { - selected = 0; - } - mEventList->selectNthItem(selected); - onSelectionChanged(); - } -} - -void LLPanelExperienceLog::onProfileExperience() -{ - LLSD event = getSelectedEvent(); - if(event.isDefined()) - { - LLFloaterReg::showInstance("experience_profile", event[LLExperienceCache::EXPERIENCE_ID].asUUID(), true); - } -} - -void LLPanelExperienceLog::onReportExperience() -{ - LLSD event = getSelectedEvent(); - if(event.isDefined()) - { - LLFloaterReporter::showFromExperience(event[LLExperienceCache::EXPERIENCE_ID].asUUID()); - } -} - -void LLPanelExperienceLog::onNotify() -{ - LLSD event = getSelectedEvent(); - if(event.isDefined()) - { - LLExperienceLog::instance().notify(event); - } -} - -void LLPanelExperienceLog::onNext() -{ - mCurrentPage++; - refresh(); -} - -void LLPanelExperienceLog::onPrev() -{ - if(mCurrentPage>0) - { - mCurrentPage--; - refresh(); - } -} - -void LLPanelExperienceLog::notifyChanged() -{ - LLExperienceLog::instance().setNotifyNewEvent(getChild("notify_all")->get()); -} - -void LLPanelExperienceLog::logSizeChanged() -{ - int value = (int)(getChild("logsizespinner")->get()); - LLExperienceLog::instance().setMaxDays(value); - refresh(); -} - -void LLPanelExperienceLog::onSelectionChanged() -{ - bool enabled = (1 == mEventList->getNumSelected()); - getChild(BTN_REPORT_XP)->setEnabled(enabled); - getChild(BTN_PROFILE_XP)->setEnabled(enabled); - getChild("btn_notify")->setEnabled(enabled); -} - -LLSD LLPanelExperienceLog::getSelectedEvent() -{ - LLScrollListItem* item = mEventList->getFirstSelected(); - if(item) - { - return item->getValue(); - } - return LLSD(); -} +/** + * @file llpanelexperiencelog.cpp + * @brief llpanelexperiencelog + * + * $LicenseInfo:firstyear=2014&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#include "llviewerprecompiledheaders.h" +#include "llpanelexperiencelog.h" + +#include "llexperiencelog.h" +#include "llexperiencecache.h" +#include "llbutton.h" +#include "llscrolllistctrl.h" +#include "llcombobox.h" +#include "llspinctrl.h" +#include "llcheckboxctrl.h" +#include "llfloaterreg.h" +#include "llfloaterreporter.h" +#include "llinventoryfunctions.h" + + +#define BTN_PROFILE_XP "btn_profile_xp" +#define BTN_REPORT_XP "btn_report_xp" + +static LLPanelInjector register_experiences_panel("experience_log"); + + +LLPanelExperienceLog::LLPanelExperienceLog( ) + : mEventList(NULL) + , mPageSize(25) + , mCurrentPage(0) +{ + buildFromFile("panel_experience_log.xml"); +} + +bool LLPanelExperienceLog::postBuild() +{ + LLExperienceLog* log = LLExperienceLog::getInstance(); + mEventList = getChild("experience_log_list"); + mEventList->setCommitCallback(boost::bind(&LLPanelExperienceLog::onSelectionChanged, this)); + mEventList->setDoubleClickCallback( boost::bind(&LLPanelExperienceLog::onProfileExperience, this)); + + getChild("btn_clear")->setCommitCallback(boost::bind(&LLExperienceLog::clear, log)); + getChild("btn_clear")->setCommitCallback(boost::bind(&LLPanelExperienceLog::refresh, this)); + + getChild(BTN_PROFILE_XP)->setCommitCallback(boost::bind(&LLPanelExperienceLog::onProfileExperience, this)); + getChild(BTN_REPORT_XP )->setCommitCallback(boost::bind(&LLPanelExperienceLog::onReportExperience, this)); + getChild("btn_notify" )->setCommitCallback(boost::bind(&LLPanelExperienceLog::onNotify, this)); + getChild("btn_next" )->setCommitCallback(boost::bind(&LLPanelExperienceLog::onNext, this)); + getChild("btn_prev" )->setCommitCallback(boost::bind(&LLPanelExperienceLog::onPrev, this)); + + LLCheckBoxCtrl* check = getChild("notify_all"); + check->set(log->getNotifyNewEvent()); + check->setCommitCallback(boost::bind(&LLPanelExperienceLog::notifyChanged, this)); + + + LLSpinCtrl* spin = getChild("logsizespinner"); + spin->set(log->getMaxDays()); + spin->setCommitCallback(boost::bind(&LLPanelExperienceLog::logSizeChanged, this)); + + mPageSize = log->getPageSize(); + refresh(); + mNewEvent = LLExperienceLog::instance().addUpdateSignal(boost::bind(&LLPanelExperienceLog::refresh, this)); + return true; +} + +LLPanelExperienceLog* LLPanelExperienceLog::create() +{ + return new LLPanelExperienceLog(); +} + +void LLPanelExperienceLog::refresh() +{ + S32 selected = mEventList->getFirstSelectedIndex(); + mEventList->deleteAllItems(); + const LLSD events = LLExperienceLog::instance().getEvents(); + + if(events.size() == 0) + { + mEventList->setCommentText(getString("no_events")); + return; + } + + setAllChildrenEnabled(false); + + LLSD item; + bool waiting = false; + LLUUID waiting_id; + + int itemsToSkip = mPageSize*mCurrentPage; + int items = 0; + bool moreItems = false; + LLSD events_to_save = events; + if (events.isMap() && events.size() != 0) + { + LLSD::map_const_iterator day = events.endMap(); + do + { + --day; + const LLSD& dayArray = day->second; + + std::string date = day->first; + if(!LLExperienceLog::instance().isNotExpired(date)) + { + events_to_save.erase(day->first); + continue; + } + int size = dayArray.size(); + if(itemsToSkip > size) + { + itemsToSkip -= size; + continue; + } + if(items >= mPageSize && size > 0) + { + moreItems = true; + break; + } + for(int i = dayArray.size() - itemsToSkip - 1; i >= 0; i--) + { + if(items >= mPageSize) + { + moreItems = true; + break; + } + const LLSD event = dayArray[i]; + LLUUID id = event[LLExperienceCache::EXPERIENCE_ID].asUUID(); + const LLSD& experience = LLExperienceCache::instance().get(id); + if(experience.isUndefined()){ + waiting = true; + waiting_id = id; + } + if(!waiting) + { + item["id"] = event; + + LLSD& columns = item["columns"]; + columns[0]["column"] = "time"; + columns[0]["value"] = day->first+event["Time"].asString(); + columns[1]["column"] = "event"; + columns[1]["value"] = LLExperienceLog::getPermissionString(event, "ExperiencePermissionShort"); + columns[2]["column"] = "experience_name"; + columns[2]["value"] = experience[LLExperienceCache::NAME].asString(); + columns[3]["column"] = "object_name"; + columns[3]["value"] = event["ObjectName"].asString(); + mEventList->addElement(item); + } + ++items; + } + } while (day != events.beginMap()); + } + LLExperienceLog::getInstance()->setEventsToSave(events_to_save); + if(waiting) + { + mEventList->deleteAllItems(); + mEventList->setCommentText(getString("loading")); + LLExperienceCache::instance().get(waiting_id, boost::bind(&LLPanelExperienceLog::refresh, this)); + } + else + { + setAllChildrenEnabled(true); + + mEventList->setEnabled(true); + getChild("btn_next")->setEnabled(moreItems); + getChild("btn_prev")->setEnabled(mCurrentPage>0); + getChild("btn_clear")->setEnabled(mEventList->getItemCount()>0); + if(selected<0) + { + selected = 0; + } + mEventList->selectNthItem(selected); + onSelectionChanged(); + } +} + +void LLPanelExperienceLog::onProfileExperience() +{ + LLSD event = getSelectedEvent(); + if(event.isDefined()) + { + LLFloaterReg::showInstance("experience_profile", event[LLExperienceCache::EXPERIENCE_ID].asUUID(), true); + } +} + +void LLPanelExperienceLog::onReportExperience() +{ + LLSD event = getSelectedEvent(); + if(event.isDefined()) + { + LLFloaterReporter::showFromExperience(event[LLExperienceCache::EXPERIENCE_ID].asUUID()); + } +} + +void LLPanelExperienceLog::onNotify() +{ + LLSD event = getSelectedEvent(); + if(event.isDefined()) + { + LLExperienceLog::instance().notify(event); + } +} + +void LLPanelExperienceLog::onNext() +{ + mCurrentPage++; + refresh(); +} + +void LLPanelExperienceLog::onPrev() +{ + if(mCurrentPage>0) + { + mCurrentPage--; + refresh(); + } +} + +void LLPanelExperienceLog::notifyChanged() +{ + LLExperienceLog::instance().setNotifyNewEvent(getChild("notify_all")->get()); +} + +void LLPanelExperienceLog::logSizeChanged() +{ + int value = (int)(getChild("logsizespinner")->get()); + LLExperienceLog::instance().setMaxDays(value); + refresh(); +} + +void LLPanelExperienceLog::onSelectionChanged() +{ + bool enabled = (1 == mEventList->getNumSelected()); + getChild(BTN_REPORT_XP)->setEnabled(enabled); + getChild(BTN_PROFILE_XP)->setEnabled(enabled); + getChild("btn_notify")->setEnabled(enabled); +} + +LLSD LLPanelExperienceLog::getSelectedEvent() +{ + LLScrollListItem* item = mEventList->getFirstSelected(); + if(item) + { + return item->getValue(); + } + return LLSD(); +} diff --git a/indra/newview/llpanelexperiencelog.h b/indra/newview/llpanelexperiencelog.h index 8a6f802f80..fb84e0475a 100644 --- a/indra/newview/llpanelexperiencelog.h +++ b/indra/newview/llpanelexperiencelog.h @@ -1,64 +1,64 @@ -/** - * @file llpanelexperiencelog.h - * @brief llpanelexperiencelog and related class definitions - * - * $LicenseInfo:firstyear=2014&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2014, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#ifndef LL_LLPANELEXPERIENCELOG_H -#define LL_LLPANELEXPERIENCELOG_H - -#include "llpanel.h" -class LLScrollListCtrl; - -class LLPanelExperienceLog - : public LLPanel -{ -public: - - LLPanelExperienceLog(); - - static LLPanelExperienceLog* create(); - - /*virtual*/ bool postBuild(); - - void refresh(); -protected: - void logSizeChanged(); - void notifyChanged(); - void onNext(); - void onNotify(); - void onPrev(); - void onProfileExperience(); - void onReportExperience(); - void onSelectionChanged(); - - LLSD getSelectedEvent(); -private: - LLScrollListCtrl* mEventList; - U32 mPageSize; - U32 mCurrentPage; - boost::signals2::scoped_connection mNewEvent; -}; - -#endif // LL_LLPANELEXPERIENCELOG_H +/** + * @file llpanelexperiencelog.h + * @brief llpanelexperiencelog and related class definitions + * + * $LicenseInfo:firstyear=2014&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#ifndef LL_LLPANELEXPERIENCELOG_H +#define LL_LLPANELEXPERIENCELOG_H + +#include "llpanel.h" +class LLScrollListCtrl; + +class LLPanelExperienceLog + : public LLPanel +{ +public: + + LLPanelExperienceLog(); + + static LLPanelExperienceLog* create(); + + /*virtual*/ bool postBuild(); + + void refresh(); +protected: + void logSizeChanged(); + void notifyChanged(); + void onNext(); + void onNotify(); + void onPrev(); + void onProfileExperience(); + void onReportExperience(); + void onSelectionChanged(); + + LLSD getSelectedEvent(); +private: + LLScrollListCtrl* mEventList; + U32 mPageSize; + U32 mCurrentPage; + boost::signals2::scoped_connection mNewEvent; +}; + +#endif // LL_LLPANELEXPERIENCELOG_H diff --git a/indra/newview/llpanelexperiencepicker.cpp b/indra/newview/llpanelexperiencepicker.cpp index 0142523ef6..5a176b8b92 100644 --- a/indra/newview/llpanelexperiencepicker.cpp +++ b/indra/newview/llpanelexperiencepicker.cpp @@ -1,449 +1,449 @@ -/** -* @file llpanelexperiencepicker.cpp -* @brief Implementation of llpanelexperiencepicker -* @author dolphin@lindenlab.com -* -* $LicenseInfo:firstyear=2014&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2014, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelexperiencepicker.h" - - -#include "lllineeditor.h" -#include "llfloaterreg.h" -#include "llscrolllistctrl.h" -#include "llviewerregion.h" -#include "llagent.h" -#include "llexperiencecache.h" -#include "llslurl.h" -#include "llavatarnamecache.h" -#include "llcombobox.h" -#include "llviewercontrol.h" -#include "llfloater.h" -#include "llregex.h" -#include "lltrans.h" - -#define BTN_FIND "find" -#define BTN_OK "ok_btn" -#define BTN_CANCEL "cancel_btn" -#define BTN_PROFILE "profile_btn" -#define BTN_LEFT "left_btn" -#define BTN_RIGHT "right_btn" -#define TEXT_EDIT "edit" -#define TEXT_MATURITY "maturity" -#define LIST_RESULTS "search_results" -#define PANEL_SEARCH "search_panel" - -const static std::string columnSpace = " "; - -static LLPanelInjector t_panel_status("llpanelexperiencepicker"); - -LLPanelExperiencePicker::LLPanelExperiencePicker() - :LLPanel() -{ - buildFromFile("panel_experience_search.xml"); - setDefaultFilters(); -} - -LLPanelExperiencePicker::~LLPanelExperiencePicker() -{ -} - -bool LLPanelExperiencePicker::postBuild() -{ - getChild(TEXT_EDIT)->setKeystrokeCallback( boost::bind(&LLPanelExperiencePicker::editKeystroke, this, _1, _2),NULL); - - childSetAction(BTN_FIND, boost::bind(&LLPanelExperiencePicker::onBtnFind, this)); - getChildView(BTN_FIND)->setEnabled(true); - - LLScrollListCtrl* searchresults = getChild(LIST_RESULTS); - searchresults->setDoubleClickCallback( boost::bind(&LLPanelExperiencePicker::onBtnSelect, this)); - searchresults->setCommitCallback(boost::bind(&LLPanelExperiencePicker::onList, this)); - getChildView(LIST_RESULTS)->setEnabled(false); - getChild(LIST_RESULTS)->setCommentText(getString("no_results")); - - childSetAction(BTN_OK, boost::bind(&LLPanelExperiencePicker::onBtnSelect, this)); - getChildView(BTN_OK)->setEnabled(false); - childSetAction(BTN_CANCEL, boost::bind(&LLPanelExperiencePicker::onBtnClose, this)); - childSetAction(BTN_PROFILE, boost::bind(&LLPanelExperiencePicker::onBtnProfile, this)); - getChildView(BTN_PROFILE)->setEnabled(false); - - getChild(TEXT_MATURITY)->setCurrentByIndex(gSavedPerAccountSettings.getU32("ExperienceSearchMaturity")); - getChild(TEXT_MATURITY)->setCommitCallback(boost::bind(&LLPanelExperiencePicker::onMaturity, this)); - getChild(TEXT_EDIT)->setFocus(true); - - childSetAction(BTN_LEFT, boost::bind(&LLPanelExperiencePicker::onPage, this, -1)); - childSetAction(BTN_RIGHT, boost::bind(&LLPanelExperiencePicker::onPage, this, 1)); - - LLPanel* search_panel = getChild(PANEL_SEARCH); - if (search_panel) - { - // Start searching when Return is pressed in the line editor. - search_panel->setDefaultBtn(BTN_FIND); - } - return true; -} - -void LLPanelExperiencePicker::editKeystroke( class LLLineEditor* caller, void* user_data ) -{ - getChildView(BTN_FIND)->setEnabled(true); -} - -void LLPanelExperiencePicker::onBtnFind() -{ - mCurrentPage=1; - boost::cmatch what; - std::string text = getChild(TEXT_EDIT)->getValue().asString(); - const boost::regex expression("secondlife:///app/experience/[\\da-f-]+/profile"); - if (ll_regex_match(text.c_str(), what, expression)) - { - LLURI uri(text); - LLSD path_array = uri.pathArray(); - if (path_array.size() == 4) - { - std::string exp_id = path_array.get(2).asString(); - LLUUID experience_id(exp_id); - if (!experience_id.isNull()) - { - const LLSD& experience_details = LLExperienceCache::instance().get(experience_id); - if(!experience_details.isUndefined()) - { - std::string experience_name_string = experience_details[LLExperienceCache::NAME].asString(); - if(!experience_name_string.empty()) - { - getChild(TEXT_EDIT)->setValue(experience_name_string); - } - } - else - { - getChild(LIST_RESULTS)->deleteAllItems(); - getChild(LIST_RESULTS)->setCommentText(getString("searching")); - - getChildView(BTN_OK)->setEnabled(false); - getChildView(BTN_PROFILE)->setEnabled(false); - - getChildView(BTN_RIGHT)->setEnabled(false); - getChildView(BTN_LEFT)->setEnabled(false); - LLExperienceCache::instance().get(experience_id, boost::bind(&LLPanelExperiencePicker::onBtnFind, this)); - return; - } - } - } - } - - - find(); -} - -void LLPanelExperiencePicker::onList() -{ - bool enabled = isSelectButtonEnabled(); - getChildView(BTN_OK)->setEnabled(enabled); - - enabled = enabled && getChild(LIST_RESULTS)->getNumSelected() == 1; - getChildView(BTN_PROFILE)->setEnabled(enabled); -} - -void LLPanelExperiencePicker::find() -{ - std::string text = getChild(TEXT_EDIT)->getValue().asString(); - mQueryID.generate(); - - LLExperienceCache::instance().findExperienceByName(text, mCurrentPage, - boost::bind(&LLPanelExperiencePicker::findResults, getDerivedHandle(), mQueryID, _1)); - - getChild(LIST_RESULTS)->deleteAllItems(); - getChild(LIST_RESULTS)->setCommentText(getString("searching")); - - getChildView(BTN_OK)->setEnabled(false); - getChildView(BTN_PROFILE)->setEnabled(false); - - getChildView(BTN_RIGHT)->setEnabled(false); - getChildView(BTN_LEFT)->setEnabled(false); -} - -/*static*/ -void LLPanelExperiencePicker::findResults(LLHandle hparent, LLUUID queryId, LLSD foundResult) -{ - if (hparent.isDead()) - return; - - LLPanelExperiencePicker* panel = hparent.get(); - if (panel) - { - panel->processResponse(queryId, foundResult); - } -} - -bool LLPanelExperiencePicker::isSelectButtonEnabled() -{ - LLScrollListCtrl* list=getChild(LIST_RESULTS); - return list->getFirstSelectedIndex() >=0; -} - -void LLPanelExperiencePicker::getSelectedExperienceIds( const LLScrollListCtrl* results, uuid_vec_t &experience_ids ) -{ - std::vector items = results->getAllSelected(); - for(std::vector::iterator it = items.begin(); it != items.end(); ++it) - { - LLScrollListItem* item = *it; - if (item->getUUID().notNull()) - { - experience_ids.push_back(item->getUUID()); - } - } -} - -void LLPanelExperiencePicker::setAllowMultiple( bool allow_multiple ) -{ - getChild(LIST_RESULTS)->setAllowMultipleSelection(allow_multiple); -} - - -void name_callback(const LLHandle& floater, const LLUUID& experience_id, const LLUUID& agent_id, const LLAvatarName& av_name) -{ - if(floater.isDead()) - return; - LLPanelExperiencePicker* picker = floater.get(); - LLScrollListCtrl* search_results = picker->getChild(LIST_RESULTS); - - LLScrollListItem* item = search_results->getItem(experience_id); - if(!item) - return; - - item->getColumn(2)->setValue(columnSpace+av_name.getDisplayName()); - -} - -void LLPanelExperiencePicker::processResponse( const LLUUID& query_id, const LLSD& content ) -{ - if(query_id != mQueryID) - { - return; - } - - mResponse = content; - - getChildView(BTN_RIGHT)->setEnabled(content.has("next_page_url")); - getChildView(BTN_LEFT)->setEnabled(content.has("previous_page_url")); - - filterContent(); - -} - -void LLPanelExperiencePicker::onBtnSelect() -{ - if(!isSelectButtonEnabled()) - { - return; - } - - if(mSelectionCallback) - { - const LLScrollListCtrl* results = getChild(LIST_RESULTS); - uuid_vec_t experience_ids; - - getSelectedExperienceIds(results, experience_ids); - mSelectionCallback(experience_ids); - getChild(LIST_RESULTS)->deselectAllItems(true); - if(mCloseOnSelect) - { - mCloseOnSelect = false; - onBtnClose(); - } - } - else - { - onBtnProfile(); - } -} - -void LLPanelExperiencePicker::onBtnClose() -{ - LLFloater* floater = getParentByType(); - if (floater) - { - floater->closeFloater(); - } -} - -void LLPanelExperiencePicker::onBtnProfile() -{ - LLScrollListItem* item = getChild(LIST_RESULTS)->getFirstSelected(); - if(item) - { - LLFloaterReg::showInstance("experience_profile", item->getUUID(), true); - } -} - -std::string LLPanelExperiencePicker::getMaturityString(int maturity) -{ - if(maturity <= SIM_ACCESS_PG) - { - return getString("maturity_icon_general"); - } - else if(maturity <= SIM_ACCESS_MATURE) - { - return getString("maturity_icon_moderate"); - } - return getString("maturity_icon_adult"); -} - -void LLPanelExperiencePicker::filterContent() -{ - LLScrollListCtrl* search_results = getChild(LIST_RESULTS); - - const LLSD& experiences=mResponse["experience_keys"]; - - search_results->deleteAllItems(); - - LLSD item; - LLSD::array_const_iterator it = experiences.beginArray(); - for ( ; it != experiences.endArray(); ++it) - { - const LLSD& experience = *it; - - if(isExperienceHidden(experience)) - continue; - - std::string experience_name_string = experience[LLExperienceCache::NAME].asString(); - if (experience_name_string.empty()) - { - experience_name_string = LLTrans::getString("ExperienceNameUntitled"); - } - - item["id"]=experience[LLExperienceCache::EXPERIENCE_ID]; - LLSD& columns = item["columns"]; - columns[0]["column"] = "maturity"; - columns[0]["value"] = getMaturityString(experience[LLExperienceCache::MATURITY].asInteger()); - columns[0]["type"]="icon"; - columns[0]["halign"]="right"; - columns[1]["column"] = "experience_name"; - columns[1]["value"] = columnSpace+experience_name_string; - columns[2]["column"] = "owner"; - columns[2]["value"] = columnSpace+getString("loading"); - search_results->addElement(item); - LLAvatarNameCache::get(experience[LLExperienceCache::AGENT_ID], boost::bind(name_callback, getDerivedHandle(), experience[LLExperienceCache::EXPERIENCE_ID], _1, _2)); - } - - if (search_results->isEmpty()) - { - LLStringUtil::format_map_t map; - std::string search_text = getChild(TEXT_EDIT)->getValue().asString(); - map["[TEXT]"] = search_text; - if (search_text.empty()) - { - getChild(LIST_RESULTS)->setCommentText(getString("no_results")); - } - else - { - getChild(LIST_RESULTS)->setCommentText(getString("not_found", map)); - } - search_results->setEnabled(false); - getChildView(BTN_OK)->setEnabled(false); - getChildView(BTN_PROFILE)->setEnabled(false); - } - else - { - getChildView(BTN_OK)->setEnabled(true); - search_results->setEnabled(true); - search_results->sortByColumnIndex(1, true); - std::string text = getChild(TEXT_EDIT)->getValue().asString(); - if (!search_results->selectItemByLabel(text, true, 1)) - { - search_results->selectFirstItem(); - } - onList(); - search_results->setFocus(true); - } -} - -void LLPanelExperiencePicker::onMaturity() -{ - gSavedPerAccountSettings.setU32("ExperienceSearchMaturity", getChild(TEXT_MATURITY)->getCurrentIndex()); - if(mResponse.has("experience_keys") && mResponse["experience_keys"].beginArray() != mResponse["experience_keys"].endArray()) - { - filterContent(); - } -} - -bool LLPanelExperiencePicker::isExperienceHidden( const LLSD& experience) const -{ - bool hide=false; - filter_list::const_iterator it = mFilters.begin(); - for(/**/;it != mFilters.end(); ++it) - { - if((*it)(experience)){ - return true; - } - } - - return hide; -} - -bool LLPanelExperiencePicker::FilterOverRating( const LLSD& experience ) -{ - int maturity = getChild(TEXT_MATURITY)->getSelectedValue().asInteger(); - return experience[LLExperienceCache::MATURITY].asInteger() > maturity; -} - -bool LLPanelExperiencePicker::FilterWithProperty( const LLSD& experience, S32 prop) -{ - return (experience[LLExperienceCache::PROPERTIES].asInteger() & prop) != 0; -} - -bool LLPanelExperiencePicker::FilterWithoutProperties( const LLSD& experience, S32 prop) -{ - return ((experience[LLExperienceCache::PROPERTIES].asInteger() & prop) == prop); -} - -bool LLPanelExperiencePicker::FilterWithoutProperty( const LLSD& experience, S32 prop ) -{ - return (experience[LLExperienceCache::PROPERTIES].asInteger() & prop) == 0; -} - -void LLPanelExperiencePicker::setDefaultFilters() -{ - mFilters.clear(); - addFilter(boost::bind(&LLPanelExperiencePicker::FilterOverRating, this, _1)); -} - -bool LLPanelExperiencePicker::FilterMatching( const LLSD& experience, const LLUUID& id ) -{ - if(experience.isUUID()) - { - return experience.asUUID() == id; - } - return experience[LLExperienceCache::EXPERIENCE_ID].asUUID() == id; -} - -void LLPanelExperiencePicker::onPage( S32 direction ) -{ - mCurrentPage += direction; - if(mCurrentPage < 1) - { - mCurrentPage = 1; - } - find(); -} +/** +* @file llpanelexperiencepicker.cpp +* @brief Implementation of llpanelexperiencepicker +* @author dolphin@lindenlab.com +* +* $LicenseInfo:firstyear=2014&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2014, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelexperiencepicker.h" + + +#include "lllineeditor.h" +#include "llfloaterreg.h" +#include "llscrolllistctrl.h" +#include "llviewerregion.h" +#include "llagent.h" +#include "llexperiencecache.h" +#include "llslurl.h" +#include "llavatarnamecache.h" +#include "llcombobox.h" +#include "llviewercontrol.h" +#include "llfloater.h" +#include "llregex.h" +#include "lltrans.h" + +#define BTN_FIND "find" +#define BTN_OK "ok_btn" +#define BTN_CANCEL "cancel_btn" +#define BTN_PROFILE "profile_btn" +#define BTN_LEFT "left_btn" +#define BTN_RIGHT "right_btn" +#define TEXT_EDIT "edit" +#define TEXT_MATURITY "maturity" +#define LIST_RESULTS "search_results" +#define PANEL_SEARCH "search_panel" + +const static std::string columnSpace = " "; + +static LLPanelInjector t_panel_status("llpanelexperiencepicker"); + +LLPanelExperiencePicker::LLPanelExperiencePicker() + :LLPanel() +{ + buildFromFile("panel_experience_search.xml"); + setDefaultFilters(); +} + +LLPanelExperiencePicker::~LLPanelExperiencePicker() +{ +} + +bool LLPanelExperiencePicker::postBuild() +{ + getChild(TEXT_EDIT)->setKeystrokeCallback( boost::bind(&LLPanelExperiencePicker::editKeystroke, this, _1, _2),NULL); + + childSetAction(BTN_FIND, boost::bind(&LLPanelExperiencePicker::onBtnFind, this)); + getChildView(BTN_FIND)->setEnabled(true); + + LLScrollListCtrl* searchresults = getChild(LIST_RESULTS); + searchresults->setDoubleClickCallback( boost::bind(&LLPanelExperiencePicker::onBtnSelect, this)); + searchresults->setCommitCallback(boost::bind(&LLPanelExperiencePicker::onList, this)); + getChildView(LIST_RESULTS)->setEnabled(false); + getChild(LIST_RESULTS)->setCommentText(getString("no_results")); + + childSetAction(BTN_OK, boost::bind(&LLPanelExperiencePicker::onBtnSelect, this)); + getChildView(BTN_OK)->setEnabled(false); + childSetAction(BTN_CANCEL, boost::bind(&LLPanelExperiencePicker::onBtnClose, this)); + childSetAction(BTN_PROFILE, boost::bind(&LLPanelExperiencePicker::onBtnProfile, this)); + getChildView(BTN_PROFILE)->setEnabled(false); + + getChild(TEXT_MATURITY)->setCurrentByIndex(gSavedPerAccountSettings.getU32("ExperienceSearchMaturity")); + getChild(TEXT_MATURITY)->setCommitCallback(boost::bind(&LLPanelExperiencePicker::onMaturity, this)); + getChild(TEXT_EDIT)->setFocus(true); + + childSetAction(BTN_LEFT, boost::bind(&LLPanelExperiencePicker::onPage, this, -1)); + childSetAction(BTN_RIGHT, boost::bind(&LLPanelExperiencePicker::onPage, this, 1)); + + LLPanel* search_panel = getChild(PANEL_SEARCH); + if (search_panel) + { + // Start searching when Return is pressed in the line editor. + search_panel->setDefaultBtn(BTN_FIND); + } + return true; +} + +void LLPanelExperiencePicker::editKeystroke( class LLLineEditor* caller, void* user_data ) +{ + getChildView(BTN_FIND)->setEnabled(true); +} + +void LLPanelExperiencePicker::onBtnFind() +{ + mCurrentPage=1; + boost::cmatch what; + std::string text = getChild(TEXT_EDIT)->getValue().asString(); + const boost::regex expression("secondlife:///app/experience/[\\da-f-]+/profile"); + if (ll_regex_match(text.c_str(), what, expression)) + { + LLURI uri(text); + LLSD path_array = uri.pathArray(); + if (path_array.size() == 4) + { + std::string exp_id = path_array.get(2).asString(); + LLUUID experience_id(exp_id); + if (!experience_id.isNull()) + { + const LLSD& experience_details = LLExperienceCache::instance().get(experience_id); + if(!experience_details.isUndefined()) + { + std::string experience_name_string = experience_details[LLExperienceCache::NAME].asString(); + if(!experience_name_string.empty()) + { + getChild(TEXT_EDIT)->setValue(experience_name_string); + } + } + else + { + getChild(LIST_RESULTS)->deleteAllItems(); + getChild(LIST_RESULTS)->setCommentText(getString("searching")); + + getChildView(BTN_OK)->setEnabled(false); + getChildView(BTN_PROFILE)->setEnabled(false); + + getChildView(BTN_RIGHT)->setEnabled(false); + getChildView(BTN_LEFT)->setEnabled(false); + LLExperienceCache::instance().get(experience_id, boost::bind(&LLPanelExperiencePicker::onBtnFind, this)); + return; + } + } + } + } + + + find(); +} + +void LLPanelExperiencePicker::onList() +{ + bool enabled = isSelectButtonEnabled(); + getChildView(BTN_OK)->setEnabled(enabled); + + enabled = enabled && getChild(LIST_RESULTS)->getNumSelected() == 1; + getChildView(BTN_PROFILE)->setEnabled(enabled); +} + +void LLPanelExperiencePicker::find() +{ + std::string text = getChild(TEXT_EDIT)->getValue().asString(); + mQueryID.generate(); + + LLExperienceCache::instance().findExperienceByName(text, mCurrentPage, + boost::bind(&LLPanelExperiencePicker::findResults, getDerivedHandle(), mQueryID, _1)); + + getChild(LIST_RESULTS)->deleteAllItems(); + getChild(LIST_RESULTS)->setCommentText(getString("searching")); + + getChildView(BTN_OK)->setEnabled(false); + getChildView(BTN_PROFILE)->setEnabled(false); + + getChildView(BTN_RIGHT)->setEnabled(false); + getChildView(BTN_LEFT)->setEnabled(false); +} + +/*static*/ +void LLPanelExperiencePicker::findResults(LLHandle hparent, LLUUID queryId, LLSD foundResult) +{ + if (hparent.isDead()) + return; + + LLPanelExperiencePicker* panel = hparent.get(); + if (panel) + { + panel->processResponse(queryId, foundResult); + } +} + +bool LLPanelExperiencePicker::isSelectButtonEnabled() +{ + LLScrollListCtrl* list=getChild(LIST_RESULTS); + return list->getFirstSelectedIndex() >=0; +} + +void LLPanelExperiencePicker::getSelectedExperienceIds( const LLScrollListCtrl* results, uuid_vec_t &experience_ids ) +{ + std::vector items = results->getAllSelected(); + for(std::vector::iterator it = items.begin(); it != items.end(); ++it) + { + LLScrollListItem* item = *it; + if (item->getUUID().notNull()) + { + experience_ids.push_back(item->getUUID()); + } + } +} + +void LLPanelExperiencePicker::setAllowMultiple( bool allow_multiple ) +{ + getChild(LIST_RESULTS)->setAllowMultipleSelection(allow_multiple); +} + + +void name_callback(const LLHandle& floater, const LLUUID& experience_id, const LLUUID& agent_id, const LLAvatarName& av_name) +{ + if(floater.isDead()) + return; + LLPanelExperiencePicker* picker = floater.get(); + LLScrollListCtrl* search_results = picker->getChild(LIST_RESULTS); + + LLScrollListItem* item = search_results->getItem(experience_id); + if(!item) + return; + + item->getColumn(2)->setValue(columnSpace+av_name.getDisplayName()); + +} + +void LLPanelExperiencePicker::processResponse( const LLUUID& query_id, const LLSD& content ) +{ + if(query_id != mQueryID) + { + return; + } + + mResponse = content; + + getChildView(BTN_RIGHT)->setEnabled(content.has("next_page_url")); + getChildView(BTN_LEFT)->setEnabled(content.has("previous_page_url")); + + filterContent(); + +} + +void LLPanelExperiencePicker::onBtnSelect() +{ + if(!isSelectButtonEnabled()) + { + return; + } + + if(mSelectionCallback) + { + const LLScrollListCtrl* results = getChild(LIST_RESULTS); + uuid_vec_t experience_ids; + + getSelectedExperienceIds(results, experience_ids); + mSelectionCallback(experience_ids); + getChild(LIST_RESULTS)->deselectAllItems(true); + if(mCloseOnSelect) + { + mCloseOnSelect = false; + onBtnClose(); + } + } + else + { + onBtnProfile(); + } +} + +void LLPanelExperiencePicker::onBtnClose() +{ + LLFloater* floater = getParentByType(); + if (floater) + { + floater->closeFloater(); + } +} + +void LLPanelExperiencePicker::onBtnProfile() +{ + LLScrollListItem* item = getChild(LIST_RESULTS)->getFirstSelected(); + if(item) + { + LLFloaterReg::showInstance("experience_profile", item->getUUID(), true); + } +} + +std::string LLPanelExperiencePicker::getMaturityString(int maturity) +{ + if(maturity <= SIM_ACCESS_PG) + { + return getString("maturity_icon_general"); + } + else if(maturity <= SIM_ACCESS_MATURE) + { + return getString("maturity_icon_moderate"); + } + return getString("maturity_icon_adult"); +} + +void LLPanelExperiencePicker::filterContent() +{ + LLScrollListCtrl* search_results = getChild(LIST_RESULTS); + + const LLSD& experiences=mResponse["experience_keys"]; + + search_results->deleteAllItems(); + + LLSD item; + LLSD::array_const_iterator it = experiences.beginArray(); + for ( ; it != experiences.endArray(); ++it) + { + const LLSD& experience = *it; + + if(isExperienceHidden(experience)) + continue; + + std::string experience_name_string = experience[LLExperienceCache::NAME].asString(); + if (experience_name_string.empty()) + { + experience_name_string = LLTrans::getString("ExperienceNameUntitled"); + } + + item["id"]=experience[LLExperienceCache::EXPERIENCE_ID]; + LLSD& columns = item["columns"]; + columns[0]["column"] = "maturity"; + columns[0]["value"] = getMaturityString(experience[LLExperienceCache::MATURITY].asInteger()); + columns[0]["type"]="icon"; + columns[0]["halign"]="right"; + columns[1]["column"] = "experience_name"; + columns[1]["value"] = columnSpace+experience_name_string; + columns[2]["column"] = "owner"; + columns[2]["value"] = columnSpace+getString("loading"); + search_results->addElement(item); + LLAvatarNameCache::get(experience[LLExperienceCache::AGENT_ID], boost::bind(name_callback, getDerivedHandle(), experience[LLExperienceCache::EXPERIENCE_ID], _1, _2)); + } + + if (search_results->isEmpty()) + { + LLStringUtil::format_map_t map; + std::string search_text = getChild(TEXT_EDIT)->getValue().asString(); + map["[TEXT]"] = search_text; + if (search_text.empty()) + { + getChild(LIST_RESULTS)->setCommentText(getString("no_results")); + } + else + { + getChild(LIST_RESULTS)->setCommentText(getString("not_found", map)); + } + search_results->setEnabled(false); + getChildView(BTN_OK)->setEnabled(false); + getChildView(BTN_PROFILE)->setEnabled(false); + } + else + { + getChildView(BTN_OK)->setEnabled(true); + search_results->setEnabled(true); + search_results->sortByColumnIndex(1, true); + std::string text = getChild(TEXT_EDIT)->getValue().asString(); + if (!search_results->selectItemByLabel(text, true, 1)) + { + search_results->selectFirstItem(); + } + onList(); + search_results->setFocus(true); + } +} + +void LLPanelExperiencePicker::onMaturity() +{ + gSavedPerAccountSettings.setU32("ExperienceSearchMaturity", getChild(TEXT_MATURITY)->getCurrentIndex()); + if(mResponse.has("experience_keys") && mResponse["experience_keys"].beginArray() != mResponse["experience_keys"].endArray()) + { + filterContent(); + } +} + +bool LLPanelExperiencePicker::isExperienceHidden( const LLSD& experience) const +{ + bool hide=false; + filter_list::const_iterator it = mFilters.begin(); + for(/**/;it != mFilters.end(); ++it) + { + if((*it)(experience)){ + return true; + } + } + + return hide; +} + +bool LLPanelExperiencePicker::FilterOverRating( const LLSD& experience ) +{ + int maturity = getChild(TEXT_MATURITY)->getSelectedValue().asInteger(); + return experience[LLExperienceCache::MATURITY].asInteger() > maturity; +} + +bool LLPanelExperiencePicker::FilterWithProperty( const LLSD& experience, S32 prop) +{ + return (experience[LLExperienceCache::PROPERTIES].asInteger() & prop) != 0; +} + +bool LLPanelExperiencePicker::FilterWithoutProperties( const LLSD& experience, S32 prop) +{ + return ((experience[LLExperienceCache::PROPERTIES].asInteger() & prop) == prop); +} + +bool LLPanelExperiencePicker::FilterWithoutProperty( const LLSD& experience, S32 prop ) +{ + return (experience[LLExperienceCache::PROPERTIES].asInteger() & prop) == 0; +} + +void LLPanelExperiencePicker::setDefaultFilters() +{ + mFilters.clear(); + addFilter(boost::bind(&LLPanelExperiencePicker::FilterOverRating, this, _1)); +} + +bool LLPanelExperiencePicker::FilterMatching( const LLSD& experience, const LLUUID& id ) +{ + if(experience.isUUID()) + { + return experience.asUUID() == id; + } + return experience[LLExperienceCache::EXPERIENCE_ID].asUUID() == id; +} + +void LLPanelExperiencePicker::onPage( S32 direction ) +{ + mCurrentPage += direction; + if(mCurrentPage < 1) + { + mCurrentPage = 1; + } + find(); +} diff --git a/indra/newview/llpanelexperiencepicker.h b/indra/newview/llpanelexperiencepicker.h index f327abd9b6..72c0b1b74d 100644 --- a/indra/newview/llpanelexperiencepicker.h +++ b/indra/newview/llpanelexperiencepicker.h @@ -1,96 +1,96 @@ -/** -* @file llpanelexperiencepicker.h -* @brief Header file for llpanelexperiencepicker -* @author dolphin@lindenlab.com -* -* $LicenseInfo:firstyear=2014&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2014, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ -#ifndef LL_LLPANELEXPERIENCEPICKER_H -#define LL_LLPANELEXPERIENCEPICKER_H - -#include "llpanel.h" - -class LLScrollListCtrl; -class LLLineEditor; - - -class LLPanelExperiencePicker : public LLPanel -{ -public: - friend class LLExperienceSearchResponder; - friend class LLFloaterExperiencePicker; - - typedef boost::function select_callback_t; - // filter function for experiences, return true if the experience should be hidden. - typedef boost::function filter_function; - typedef std::vector filter_list; - - LLPanelExperiencePicker(); - virtual ~LLPanelExperiencePicker(); - - bool postBuild(); - - void addFilter(filter_function func){mFilters.push_back(func);} - template - void addFilters(IT begin, IT end){mFilters.insert(mFilters.end(), begin, end);} - void setDefaultFilters(); - - static bool FilterWithProperty(const LLSD& experience, S32 prop); - static bool FilterWithoutProperties(const LLSD& experience, S32 prop); - static bool FilterWithoutProperty(const LLSD& experience, S32 prop); - static bool FilterMatching(const LLSD& experience, const LLUUID& id); - bool FilterOverRating(const LLSD& experience); - -private: - void editKeystroke(LLLineEditor* caller, void* user_data); - - void onBtnFind(); - void onBtnSelect(); - void onBtnClose(); - void onBtnProfile(); - void onList(); - void onMaturity(); - void onPage(S32 direction); - - void getSelectedExperienceIds( const LLScrollListCtrl* results, uuid_vec_t &experience_ids ); - void setAllowMultiple(bool allow_multiple); - - void find(); - static void findResults(LLHandle hparent, LLUUID queryId, LLSD foundResult); - - bool isSelectButtonEnabled(); - void processResponse( const LLUUID& query_id, const LLSD& content ); - - void filterContent(); - bool isExperienceHidden(const LLSD& experience) const ; - std::string getMaturityString(int maturity); - - - select_callback_t mSelectionCallback; - filter_list mFilters; - LLUUID mQueryID; - LLSD mResponse; - bool mCloseOnSelect; - S32 mCurrentPage; -}; - -#endif // LL_LLPANELEXPERIENCEPICKER_H +/** +* @file llpanelexperiencepicker.h +* @brief Header file for llpanelexperiencepicker +* @author dolphin@lindenlab.com +* +* $LicenseInfo:firstyear=2014&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2014, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LL_LLPANELEXPERIENCEPICKER_H +#define LL_LLPANELEXPERIENCEPICKER_H + +#include "llpanel.h" + +class LLScrollListCtrl; +class LLLineEditor; + + +class LLPanelExperiencePicker : public LLPanel +{ +public: + friend class LLExperienceSearchResponder; + friend class LLFloaterExperiencePicker; + + typedef boost::function select_callback_t; + // filter function for experiences, return true if the experience should be hidden. + typedef boost::function filter_function; + typedef std::vector filter_list; + + LLPanelExperiencePicker(); + virtual ~LLPanelExperiencePicker(); + + bool postBuild(); + + void addFilter(filter_function func){mFilters.push_back(func);} + template + void addFilters(IT begin, IT end){mFilters.insert(mFilters.end(), begin, end);} + void setDefaultFilters(); + + static bool FilterWithProperty(const LLSD& experience, S32 prop); + static bool FilterWithoutProperties(const LLSD& experience, S32 prop); + static bool FilterWithoutProperty(const LLSD& experience, S32 prop); + static bool FilterMatching(const LLSD& experience, const LLUUID& id); + bool FilterOverRating(const LLSD& experience); + +private: + void editKeystroke(LLLineEditor* caller, void* user_data); + + void onBtnFind(); + void onBtnSelect(); + void onBtnClose(); + void onBtnProfile(); + void onList(); + void onMaturity(); + void onPage(S32 direction); + + void getSelectedExperienceIds( const LLScrollListCtrl* results, uuid_vec_t &experience_ids ); + void setAllowMultiple(bool allow_multiple); + + void find(); + static void findResults(LLHandle hparent, LLUUID queryId, LLSD foundResult); + + bool isSelectButtonEnabled(); + void processResponse( const LLUUID& query_id, const LLSD& content ); + + void filterContent(); + bool isExperienceHidden(const LLSD& experience) const ; + std::string getMaturityString(int maturity); + + + select_callback_t mSelectionCallback; + filter_list mFilters; + LLUUID mQueryID; + LLSD mResponse; + bool mCloseOnSelect; + S32 mCurrentPage; +}; + +#endif // LL_LLPANELEXPERIENCEPICKER_H diff --git a/indra/newview/llpanelexperiences.cpp b/indra/newview/llpanelexperiences.cpp index 59bce94028..6cdeefdbad 100644 --- a/indra/newview/llpanelexperiences.cpp +++ b/indra/newview/llpanelexperiences.cpp @@ -1,239 +1,239 @@ -/** - * @file llpanelexperiences.cpp - * @brief LLPanelExperiences class implementation - * - * $LicenseInfo:firstyear=2013&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2013, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#include "llviewerprecompiledheaders.h" - - -#include "llpanelprofile.h" -#include "lluictrlfactory.h" -#include "llexperiencecache.h" -#include "llagent.h" - -#include "llpanelexperiences.h" -#include "llslurl.h" -#include "lllayoutstack.h" - - - -static LLPanelInjector register_experiences_panel("experiences_panel"); - - -//comparators -static const LLExperienceItemComparator NAME_COMPARATOR; - -LLPanelExperiences::LLPanelExperiences( ) - : mExperiencesList(NULL) -{ - buildFromFile("panel_experiences.xml"); -} - -bool LLPanelExperiences::postBuild( void ) -{ - mExperiencesList = getChild("experiences_list"); - if (hasString("loading_experiences")) - { - mExperiencesList->setNoItemsCommentText(getString("loading_experiences")); - } - else if (hasString("no_experiences")) - { - mExperiencesList->setNoItemsCommentText(getString("no_experiences")); - } - mExperiencesList->setComparator(&NAME_COMPARATOR); - - return true; -} - - - -LLExperienceItem* LLPanelExperiences::getSelectedExperienceItem() -{ - LLPanel* selected_item = mExperiencesList->getSelectedItem(); - if (!selected_item) return NULL; - - return dynamic_cast(selected_item); -} - -void LLPanelExperiences::setExperienceList( const LLSD& experiences ) -{ - if (hasString("no_experiences")) - { - mExperiencesList->setNoItemsCommentText(getString("no_experiences")); - } - mExperiencesList->clear(); - - LLSD::array_const_iterator it = experiences.beginArray(); - for( /**/ ; it != experiences.endArray(); ++it) - { - LLUUID public_key = it->asUUID(); - LLExperienceItem* item = new LLExperienceItem(); - - item->init(public_key); - mExperiencesList->addItem(item, public_key); - - const LLSD& experience_details = LLExperienceCache::instance().get(public_key); - if (experience_details.isUndefined()) - { - LLExperienceCache::instance().get(public_key, boost::bind(&LLPanelExperiences::sortExperiencesList, this)); - } - } - - sortExperiencesList(); -} - -void LLPanelExperiences::sortExperiencesList() -{ - mExperiencesList->sort(); -} - -void LLPanelExperiences::getExperienceIdsList(std::vector& result) -{ - std::vector ids; - mExperiencesList->getValues(ids); - for (LLSD::array_const_iterator it = ids.begin(); it != ids.end(); ++it) - { - result.push_back(it->asUUID()); - } -} - -LLPanelExperiences* LLPanelExperiences::create(const std::string& name) -{ - LLPanelExperiences* panel= new LLPanelExperiences(); - panel->setName(name); - return panel; -} - -void LLPanelExperiences::removeExperiences( const LLSD& ids ) -{ - LLSD::array_const_iterator it = ids.beginArray(); - for( /**/ ; it != ids.endArray(); ++it) - { - removeExperience(it->asUUID()); - } -} - -void LLPanelExperiences::removeExperience( const LLUUID& id ) -{ - mExperiencesList->removeItemByUUID(id); -} - -void LLPanelExperiences::addExperience( const LLUUID& id ) -{ - if(!mExperiencesList->getItemByValue(id)) - { - LLExperienceItem* item = new LLExperienceItem(); - - item->init(id); - mExperiencesList->addItem(item, id); - mExperiencesList->sort(); - } -} - -void LLPanelExperiences::setButtonAction(const std::string& label, const commit_signal_t::slot_type& cb ) -{ - if(label.empty()) - { - getChild("button_panel")->setVisible(false); - } - else - { - getChild("button_panel")->setVisible(true); - LLButton* child = getChild("btn_action"); - child->setCommitCallback(cb); - child->setLabel(getString(label)); - } -} - -void LLPanelExperiences::enableButton( bool enable ) -{ - getChild("btn_action")->setEnabled(enable); -} - - -LLExperienceItem::LLExperienceItem() - : mName(NULL) -{ - buildFromFile("panel_experience_list_item.xml"); -} - -void LLExperienceItem::init( const LLUUID& id) -{ - mName = getChild("experience_name"); - mName->setValue(LLSLURL("experience", id, "profile").getSLURLString()); -} - -LLExperienceItem::~LLExperienceItem() -{ - -} - -std::string LLExperienceItem::getExperienceName() const -{ - if (mName) - { - return mName->getValue(); - } - - return ""; -} - -void LLPanelSearchExperiences::doSearch() -{ - -} - -LLPanelSearchExperiences* LLPanelSearchExperiences::create( const std::string& name ) -{ - LLPanelSearchExperiences* panel= new LLPanelSearchExperiences(); - panel->getChild("results")->addChild(LLPanelExperiences::create(name)); - return panel; -} - -bool LLPanelSearchExperiences::postBuild( void ) -{ - childSetAction("search_button", boost::bind(&LLPanelSearchExperiences::doSearch, this)); - return true; -} - -bool LLExperienceItemComparator::compare(const LLPanel* item1, const LLPanel* item2) const -{ - const LLExperienceItem* experience_item1 = dynamic_cast(item1); - const LLExperienceItem* experience_item2 = dynamic_cast(item2); - - if (!experience_item1 || !experience_item2) - { - LL_ERRS() << "item1 and item2 cannot be null" << LL_ENDL; - return true; - } - - std::string name1 = experience_item1->getExperienceName(); - std::string name2 = experience_item2->getExperienceName(); - - LLStringUtil::toUpper(name1); - LLStringUtil::toUpper(name2); - - return name1 < name2; -} +/** + * @file llpanelexperiences.cpp + * @brief LLPanelExperiences class implementation + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#include "llviewerprecompiledheaders.h" + + +#include "llpanelprofile.h" +#include "lluictrlfactory.h" +#include "llexperiencecache.h" +#include "llagent.h" + +#include "llpanelexperiences.h" +#include "llslurl.h" +#include "lllayoutstack.h" + + + +static LLPanelInjector register_experiences_panel("experiences_panel"); + + +//comparators +static const LLExperienceItemComparator NAME_COMPARATOR; + +LLPanelExperiences::LLPanelExperiences( ) + : mExperiencesList(NULL) +{ + buildFromFile("panel_experiences.xml"); +} + +bool LLPanelExperiences::postBuild( void ) +{ + mExperiencesList = getChild("experiences_list"); + if (hasString("loading_experiences")) + { + mExperiencesList->setNoItemsCommentText(getString("loading_experiences")); + } + else if (hasString("no_experiences")) + { + mExperiencesList->setNoItemsCommentText(getString("no_experiences")); + } + mExperiencesList->setComparator(&NAME_COMPARATOR); + + return true; +} + + + +LLExperienceItem* LLPanelExperiences::getSelectedExperienceItem() +{ + LLPanel* selected_item = mExperiencesList->getSelectedItem(); + if (!selected_item) return NULL; + + return dynamic_cast(selected_item); +} + +void LLPanelExperiences::setExperienceList( const LLSD& experiences ) +{ + if (hasString("no_experiences")) + { + mExperiencesList->setNoItemsCommentText(getString("no_experiences")); + } + mExperiencesList->clear(); + + LLSD::array_const_iterator it = experiences.beginArray(); + for( /**/ ; it != experiences.endArray(); ++it) + { + LLUUID public_key = it->asUUID(); + LLExperienceItem* item = new LLExperienceItem(); + + item->init(public_key); + mExperiencesList->addItem(item, public_key); + + const LLSD& experience_details = LLExperienceCache::instance().get(public_key); + if (experience_details.isUndefined()) + { + LLExperienceCache::instance().get(public_key, boost::bind(&LLPanelExperiences::sortExperiencesList, this)); + } + } + + sortExperiencesList(); +} + +void LLPanelExperiences::sortExperiencesList() +{ + mExperiencesList->sort(); +} + +void LLPanelExperiences::getExperienceIdsList(std::vector& result) +{ + std::vector ids; + mExperiencesList->getValues(ids); + for (LLSD::array_const_iterator it = ids.begin(); it != ids.end(); ++it) + { + result.push_back(it->asUUID()); + } +} + +LLPanelExperiences* LLPanelExperiences::create(const std::string& name) +{ + LLPanelExperiences* panel= new LLPanelExperiences(); + panel->setName(name); + return panel; +} + +void LLPanelExperiences::removeExperiences( const LLSD& ids ) +{ + LLSD::array_const_iterator it = ids.beginArray(); + for( /**/ ; it != ids.endArray(); ++it) + { + removeExperience(it->asUUID()); + } +} + +void LLPanelExperiences::removeExperience( const LLUUID& id ) +{ + mExperiencesList->removeItemByUUID(id); +} + +void LLPanelExperiences::addExperience( const LLUUID& id ) +{ + if(!mExperiencesList->getItemByValue(id)) + { + LLExperienceItem* item = new LLExperienceItem(); + + item->init(id); + mExperiencesList->addItem(item, id); + mExperiencesList->sort(); + } +} + +void LLPanelExperiences::setButtonAction(const std::string& label, const commit_signal_t::slot_type& cb ) +{ + if(label.empty()) + { + getChild("button_panel")->setVisible(false); + } + else + { + getChild("button_panel")->setVisible(true); + LLButton* child = getChild("btn_action"); + child->setCommitCallback(cb); + child->setLabel(getString(label)); + } +} + +void LLPanelExperiences::enableButton( bool enable ) +{ + getChild("btn_action")->setEnabled(enable); +} + + +LLExperienceItem::LLExperienceItem() + : mName(NULL) +{ + buildFromFile("panel_experience_list_item.xml"); +} + +void LLExperienceItem::init( const LLUUID& id) +{ + mName = getChild("experience_name"); + mName->setValue(LLSLURL("experience", id, "profile").getSLURLString()); +} + +LLExperienceItem::~LLExperienceItem() +{ + +} + +std::string LLExperienceItem::getExperienceName() const +{ + if (mName) + { + return mName->getValue(); + } + + return ""; +} + +void LLPanelSearchExperiences::doSearch() +{ + +} + +LLPanelSearchExperiences* LLPanelSearchExperiences::create( const std::string& name ) +{ + LLPanelSearchExperiences* panel= new LLPanelSearchExperiences(); + panel->getChild("results")->addChild(LLPanelExperiences::create(name)); + return panel; +} + +bool LLPanelSearchExperiences::postBuild( void ) +{ + childSetAction("search_button", boost::bind(&LLPanelSearchExperiences::doSearch, this)); + return true; +} + +bool LLExperienceItemComparator::compare(const LLPanel* item1, const LLPanel* item2) const +{ + const LLExperienceItem* experience_item1 = dynamic_cast(item1); + const LLExperienceItem* experience_item2 = dynamic_cast(item2); + + if (!experience_item1 || !experience_item2) + { + LL_ERRS() << "item1 and item2 cannot be null" << LL_ENDL; + return true; + } + + std::string name1 = experience_item1->getExperienceName(); + std::string name2 = experience_item2->getExperienceName(); + + LLStringUtil::toUpper(name1); + LLStringUtil::toUpper(name2); + + return name1 < name2; +} diff --git a/indra/newview/llpanelexperiences.h b/indra/newview/llpanelexperiences.h index f41fe0675e..9c70593a5c 100644 --- a/indra/newview/llpanelexperiences.h +++ b/indra/newview/llpanelexperiences.h @@ -1,98 +1,98 @@ -/** - * @file llpanelexperiences.h - * @brief LLPanelExperiences class definition - * - * $LicenseInfo:firstyear=2013&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2013, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELEXPERIENCES_H -#define LL_LLPANELEXPERIENCES_H - -#include "llaccordionctrltab.h" -#include "llflatlistview.h" - -class LLExperienceItem; -class LLPanelProfile; - - -class LLPanelSearchExperiences - : public LLPanel -{ -public: - LLPanelSearchExperiences(){} - static LLPanelSearchExperiences* create(const std::string& name); - /*virtual*/ bool postBuild(void); - - void doSearch(); -}; - -class LLPanelExperiences - : public LLPanel -{ -public: - LLPanelExperiences(); - - static LLPanelExperiences* create(const std::string& name); - - /*virtual*/ bool postBuild(void); - - void setExperienceList(const LLSD& experiences); - void getExperienceIdsList(std::vector& result); - - void sortExperiencesList(); - - LLExperienceItem* getSelectedExperienceItem(); - void removeExperiences( const LLSD& ids ); - void removeExperience( const LLUUID& id); - void addExperience( const LLUUID& id); - void setButtonAction(const std::string& label, const commit_signal_t::slot_type& cb); - void enableButton(bool enable); -protected: - -private: - LLFlatListView* mExperiencesList; -}; - -class LLExperienceItemComparator : public LLFlatListView::ItemComparator -{ - LOG_CLASS(LLExperienceItemComparator); - -public: - LLExperienceItemComparator() {}; - virtual ~LLExperienceItemComparator() {}; - - virtual bool compare(const LLPanel* item1, const LLPanel* item2) const; -}; - -class LLExperienceItem - : public LLPanel -{ -public: - LLExperienceItem(); - ~LLExperienceItem(); - - void init(const LLUUID& experience_id); - std::string getExperienceName() const; -protected: - LLUICtrl* mName; -}; -#endif // LL_LLPANELEXPERIENCES_H +/** + * @file llpanelexperiences.h + * @brief LLPanelExperiences class definition + * + * $LicenseInfo:firstyear=2013&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELEXPERIENCES_H +#define LL_LLPANELEXPERIENCES_H + +#include "llaccordionctrltab.h" +#include "llflatlistview.h" + +class LLExperienceItem; +class LLPanelProfile; + + +class LLPanelSearchExperiences + : public LLPanel +{ +public: + LLPanelSearchExperiences(){} + static LLPanelSearchExperiences* create(const std::string& name); + /*virtual*/ bool postBuild(void); + + void doSearch(); +}; + +class LLPanelExperiences + : public LLPanel +{ +public: + LLPanelExperiences(); + + static LLPanelExperiences* create(const std::string& name); + + /*virtual*/ bool postBuild(void); + + void setExperienceList(const LLSD& experiences); + void getExperienceIdsList(std::vector& result); + + void sortExperiencesList(); + + LLExperienceItem* getSelectedExperienceItem(); + void removeExperiences( const LLSD& ids ); + void removeExperience( const LLUUID& id); + void addExperience( const LLUUID& id); + void setButtonAction(const std::string& label, const commit_signal_t::slot_type& cb); + void enableButton(bool enable); +protected: + +private: + LLFlatListView* mExperiencesList; +}; + +class LLExperienceItemComparator : public LLFlatListView::ItemComparator +{ + LOG_CLASS(LLExperienceItemComparator); + +public: + LLExperienceItemComparator() {}; + virtual ~LLExperienceItemComparator() {}; + + virtual bool compare(const LLPanel* item1, const LLPanel* item2) const; +}; + +class LLExperienceItem + : public LLPanel +{ +public: + LLExperienceItem(); + ~LLExperienceItem(); + + void init(const LLUUID& experience_id); + std::string getExperienceName() const; +protected: + LLUICtrl* mName; +}; +#endif // LL_LLPANELEXPERIENCES_H diff --git a/indra/newview/llpanelface.cpp b/indra/newview/llpanelface.cpp index 4d5fed9e70..8d6cdb4d8d 100644 --- a/indra/newview/llpanelface.cpp +++ b/indra/newview/llpanelface.cpp @@ -1,5498 +1,5498 @@ -/** - * @file llpanelface.cpp - * @brief Panel in the tools floater for editing face textures, colors, etc. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -// file include -#include "llpanelface.h" - -// library includes -#include "llcalc.h" -#include "llerror.h" -#include "llrect.h" -#include "llstring.h" -#include "llfontgl.h" - -// project includes -#include "llagent.h" -#include "llagentdata.h" -#include "llbutton.h" -#include "llcheckboxctrl.h" -#include "llcolorswatch.h" -#include "llcombobox.h" -#include "lldrawpoolbump.h" -#include "llface.h" -#include "llgltfmateriallist.h" -#include "llinventoryfunctions.h" -#include "llinventorymodel.h" // gInventory -#include "llinventorymodelbackgroundfetch.h" -#include "llfloatermediasettings.h" -#include "llfloaterreg.h" -#include "llfloatertools.h" -#include "lllineeditor.h" -#include "llmaterialmgr.h" -#include "llmaterialeditor.h" -#include "llmediactrl.h" -#include "llmediaentry.h" -#include "llmenubutton.h" -#include "llnotificationsutil.h" -#include "llpanelcontents.h" -#include "llradiogroup.h" -#include "llresmgr.h" -#include "llselectmgr.h" -#include "llspinctrl.h" -#include "lltextbox.h" -#include "lltexturectrl.h" -#include "lltextureentry.h" -#include "lltooldraganddrop.h" -#include "lltoolface.h" -#include "lltoolmgr.h" -#include "lltrans.h" -#include "llui.h" -#include "llviewercontrol.h" -#include "llviewermedia.h" -#include "llviewerobject.h" -#include "llviewerregion.h" -#include "llviewerstats.h" -#include "llvovolume.h" -#include "llvoinventorylistener.h" -#include "lluictrlfactory.h" -#include "llpluginclassmedia.h" -#include "llviewertexturelist.h"// Update sel manager as to which channel we're editing so it can reflect the correct overlay UI - - - -#include "llagent.h" -#include "llfilesystem.h" -#include "llviewerassetupload.h" -#include "llviewermenufile.h" -#include "llsd.h" -#include "llsdutil.h" -#include "llsdserialize.h" -#include "llinventorymodel.h" - -using namespace std::literals; - -LLPanelFace::Selection LLPanelFace::sMaterialOverrideSelection; - -// -// Constant definitions for comboboxes -// Must match the commbobox definitions in panel_tools_texture.xml -// -const S32 MATMEDIA_MATERIAL = 0; // Material -const S32 MATMEDIA_PBR = 1; // PBR -const S32 MATMEDIA_MEDIA = 2; // Media -const S32 MATTYPE_DIFFUSE = 0; // Diffuse material texture -const S32 MATTYPE_NORMAL = 1; // Normal map -const S32 MATTYPE_SPECULAR = 2; // Specular map -const S32 ALPHAMODE_MASK = 2; // Alpha masking mode -const S32 BUMPY_TEXTURE = 18; // use supplied normal map -const S32 SHINY_TEXTURE = 4; // use supplied specular map -const S32 PBRTYPE_RENDER_MATERIAL_ID = 0; // Render Material ID -const S32 PBRTYPE_BASE_COLOR = 1; // PBR Base Color -const S32 PBRTYPE_METALLIC_ROUGHNESS = 2; // PBR Metallic -const S32 PBRTYPE_EMISSIVE = 3; // PBR Emissive -const S32 PBRTYPE_NORMAL = 4; // PBR Normal - -LLGLTFMaterial::TextureInfo texture_info_from_pbrtype(S32 pbr_type) -{ - switch (pbr_type) - { - case PBRTYPE_BASE_COLOR: - return LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR; - break; - case PBRTYPE_NORMAL: - return LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL; - break; - case PBRTYPE_METALLIC_ROUGHNESS: - return LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS; - break; - case PBRTYPE_EMISSIVE: - return LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE; - break; - default: - return LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; - break; - } -} - -void LLPanelFace::updateSelectedGLTFMaterials(std::function func) -{ - struct LLSelectedTEGLTFMaterialFunctor : public LLSelectedTEFunctor - { - LLSelectedTEGLTFMaterialFunctor(std::function func) : mFunc(func) {} - virtual ~LLSelectedTEGLTFMaterialFunctor() {}; - bool apply(LLViewerObject* object, S32 face) override - { - LLGLTFMaterial new_override; - const LLTextureEntry* tep = object->getTE(face); - if (tep->getGLTFMaterialOverride()) - { - new_override = *tep->getGLTFMaterialOverride(); - } - mFunc(&new_override); - LLGLTFMaterialList::queueModify(object, face, &new_override); - - return true; - } - - std::function mFunc; - } select_func(func); - - LLSelectMgr::getInstance()->getSelection()->applyToTEs(&select_func); -} - -template -void readSelectedGLTFMaterial(std::function func, T& value, bool& identical, bool has_tolerance, T tolerance) -{ - struct LLSelectedTEGetGLTFMaterialFunctor : public LLSelectedTEGetFunctor - { - LLSelectedTEGetGLTFMaterialFunctor(std::function func) : mFunc(func) {} - virtual ~LLSelectedTEGetGLTFMaterialFunctor() {}; - T get(LLViewerObject* object, S32 face) override - { - const LLTextureEntry* tep = object->getTE(face); - const LLGLTFMaterial* render_material = tep->getGLTFRenderMaterial(); - - return mFunc(render_material); - } - - std::function mFunc; - } select_func(func); - identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue(&select_func, value, has_tolerance, tolerance); -} - -BOOST_STATIC_ASSERT(MATTYPE_DIFFUSE == LLRender::DIFFUSE_MAP && MATTYPE_NORMAL == LLRender::NORMAL_MAP && MATTYPE_SPECULAR == LLRender::SPECULAR_MAP); - -// -// "Use texture" label for normal/specular type comboboxes -// Filled in at initialization from translated strings -// -std::string USE_TEXTURE; - -LLRender::eTexIndex LLPanelFace::getTextureChannelToEdit() -{ - LLRender::eTexIndex channel_to_edit = LLRender::DIFFUSE_MAP; - if (mComboMatMedia) - { - U32 matmedia_selection = mComboMatMedia->getCurrentIndex(); - if (matmedia_selection == MATMEDIA_MATERIAL) - { - LLRadioGroup* radio_mat_type = getChild("radio_material_type"); - channel_to_edit = (LLRender::eTexIndex)radio_mat_type->getSelectedIndex(); - } - if (matmedia_selection == MATMEDIA_PBR) - { - LLRadioGroup* radio_mat_type = getChild("radio_pbr_type"); - channel_to_edit = (LLRender::eTexIndex)radio_mat_type->getSelectedIndex(); - } - } - - channel_to_edit = (channel_to_edit == LLRender::NORMAL_MAP) ? (getCurrentNormalMap().isNull() ? LLRender::DIFFUSE_MAP : channel_to_edit) : channel_to_edit; - channel_to_edit = (channel_to_edit == LLRender::SPECULAR_MAP) ? (getCurrentSpecularMap().isNull() ? LLRender::DIFFUSE_MAP : channel_to_edit) : channel_to_edit; - return channel_to_edit; -} - -LLRender::eTexIndex LLPanelFace::getTextureDropChannel() -{ - if (mComboMatMedia && mComboMatMedia->getCurrentIndex() == MATMEDIA_MATERIAL) - { - LLRadioGroup* radio_mat_type = getChild("radio_material_type"); - return LLRender::eTexIndex(radio_mat_type->getSelectedIndex()); - } - - return LLRender::eTexIndex(MATTYPE_DIFFUSE); -} - -LLGLTFMaterial::TextureInfo LLPanelFace::getPBRDropChannel() -{ - if (mComboMatMedia && mComboMatMedia->getCurrentIndex() == MATMEDIA_PBR) - { - LLRadioGroup* radio_pbr_type = getChild("radio_pbr_type"); - return texture_info_from_pbrtype(radio_pbr_type->getSelectedIndex()); - } - - return texture_info_from_pbrtype(PBRTYPE_BASE_COLOR); -} - -// Things the UI provides... -// -LLUUID LLPanelFace::getCurrentNormalMap() { return getChild("bumpytexture control")->getImageAssetID(); } -LLUUID LLPanelFace::getCurrentSpecularMap() { return getChild("shinytexture control")->getImageAssetID(); } -U32 LLPanelFace::getCurrentShininess() { return getChild("combobox shininess")->getCurrentIndex(); } -U32 LLPanelFace::getCurrentBumpiness() { return getChild("combobox bumpiness")->getCurrentIndex(); } -U8 LLPanelFace::getCurrentDiffuseAlphaMode() { return (U8)getChild("combobox alphamode")->getCurrentIndex(); } -U8 LLPanelFace::getCurrentAlphaMaskCutoff() { return (U8)getChild("maskcutoff")->getValue().asInteger(); } -U8 LLPanelFace::getCurrentEnvIntensity() { return (U8)getChild("environment")->getValue().asInteger(); } -U8 LLPanelFace::getCurrentGlossiness() { return (U8)getChild("glossiness")->getValue().asInteger(); } -F32 LLPanelFace::getCurrentBumpyRot() { return getChild("bumpyRot")->getValue().asReal(); } -F32 LLPanelFace::getCurrentBumpyScaleU() { return getChild("bumpyScaleU")->getValue().asReal(); } -F32 LLPanelFace::getCurrentBumpyScaleV() { return getChild("bumpyScaleV")->getValue().asReal(); } -F32 LLPanelFace::getCurrentBumpyOffsetU() { return getChild("bumpyOffsetU")->getValue().asReal(); } -F32 LLPanelFace::getCurrentBumpyOffsetV() { return getChild("bumpyOffsetV")->getValue().asReal(); } -F32 LLPanelFace::getCurrentShinyRot() { return getChild("shinyRot")->getValue().asReal(); } -F32 LLPanelFace::getCurrentShinyScaleU() { return getChild("shinyScaleU")->getValue().asReal(); } -F32 LLPanelFace::getCurrentShinyScaleV() { return getChild("shinyScaleV")->getValue().asReal(); } -F32 LLPanelFace::getCurrentShinyOffsetU() { return getChild("shinyOffsetU")->getValue().asReal(); } -F32 LLPanelFace::getCurrentShinyOffsetV() { return getChild("shinyOffsetV")->getValue().asReal(); } - -// -// Methods -// - -bool LLPanelFace::postBuild() -{ - childSetCommitCallback("combobox shininess",&LLPanelFace::onCommitShiny,this); - childSetCommitCallback("combobox bumpiness",&LLPanelFace::onCommitBump,this); - childSetCommitCallback("combobox alphamode",&LLPanelFace::onCommitAlphaMode,this); - childSetCommitCallback("TexScaleU",&LLPanelFace::onCommitTextureScaleX, this); - childSetCommitCallback("TexScaleV",&LLPanelFace::onCommitTextureScaleY, this); - childSetCommitCallback("TexRot",&LLPanelFace::onCommitTextureRot, this); - childSetCommitCallback("rptctrl",&LLPanelFace::onCommitRepeatsPerMeter, this); - childSetCommitCallback("checkbox planar align",&LLPanelFace::onCommitPlanarAlign, this); - childSetCommitCallback("TexOffsetU",LLPanelFace::onCommitTextureOffsetX, this); - childSetCommitCallback("TexOffsetV",LLPanelFace::onCommitTextureOffsetY, this); - - childSetCommitCallback("bumpyScaleU",&LLPanelFace::onCommitMaterialBumpyScaleX, this); - childSetCommitCallback("bumpyScaleV",&LLPanelFace::onCommitMaterialBumpyScaleY, this); - childSetCommitCallback("bumpyRot",&LLPanelFace::onCommitMaterialBumpyRot, this); - childSetCommitCallback("bumpyOffsetU",&LLPanelFace::onCommitMaterialBumpyOffsetX, this); - childSetCommitCallback("bumpyOffsetV",&LLPanelFace::onCommitMaterialBumpyOffsetY, this); - childSetCommitCallback("shinyScaleU",&LLPanelFace::onCommitMaterialShinyScaleX, this); - childSetCommitCallback("shinyScaleV",&LLPanelFace::onCommitMaterialShinyScaleY, this); - childSetCommitCallback("shinyRot",&LLPanelFace::onCommitMaterialShinyRot, this); - childSetCommitCallback("shinyOffsetU",&LLPanelFace::onCommitMaterialShinyOffsetX, this); - childSetCommitCallback("shinyOffsetV",&LLPanelFace::onCommitMaterialShinyOffsetY, this); - childSetCommitCallback("glossiness",&LLPanelFace::onCommitMaterialGloss, this); - childSetCommitCallback("environment",&LLPanelFace::onCommitMaterialEnv, this); - childSetCommitCallback("maskcutoff",&LLPanelFace::onCommitMaterialMaskCutoff, this); - childSetCommitCallback("add_media", &LLPanelFace::onClickBtnAddMedia, this); - childSetCommitCallback("delete_media", &LLPanelFace::onClickBtnDeleteMedia, this); - - getChild("gltfTextureScaleU")->setCommitCallback(boost::bind(&LLPanelFace::onCommitGLTFTextureScaleU, this, _1), nullptr); - getChild("gltfTextureScaleV")->setCommitCallback(boost::bind(&LLPanelFace::onCommitGLTFTextureScaleV, this, _1), nullptr); - getChild("gltfTextureRotation")->setCommitCallback(boost::bind(&LLPanelFace::onCommitGLTFRotation, this, _1), nullptr); - getChild("gltfTextureOffsetU")->setCommitCallback(boost::bind(&LLPanelFace::onCommitGLTFTextureOffsetU, this, _1), nullptr); - getChild("gltfTextureOffsetV")->setCommitCallback(boost::bind(&LLPanelFace::onCommitGLTFTextureOffsetV, this, _1), nullptr); - - LLGLTFMaterialList::addSelectionUpdateCallback(&LLPanelFace::onMaterialOverrideReceived); - sMaterialOverrideSelection.connect(); - - childSetAction("button align",&LLPanelFace::onClickAutoFix,this); - childSetAction("button align textures", &LLPanelFace::onAlignTexture, this); - childSetAction("pbr_from_inventory", &LLPanelFace::onClickBtnLoadInvPBR, this); - childSetAction("edit_selected_pbr", &LLPanelFace::onClickBtnEditPBR, this); - childSetAction("save_selected_pbr", &LLPanelFace::onClickBtnSavePBR, this); - - LLTextureCtrl* mTextureCtrl; - LLTextureCtrl* mShinyTextureCtrl; - LLTextureCtrl* mBumpyTextureCtrl; - LLColorSwatchCtrl* mColorSwatch; - LLColorSwatchCtrl* mShinyColorSwatch; - - LLComboBox* mComboTexGen; - - LLCheckBoxCtrl *mCheckFullbright; - - LLTextBox* mLabelColorTransp; - LLSpinCtrl* mCtrlColorTransp; // transparency = 1 - alpha - - LLSpinCtrl* mCtrlGlow; - - setMouseOpaque(false); - - LLTextureCtrl* pbr_ctrl = findChild("pbr_control"); - if (pbr_ctrl) - { - pbr_ctrl->setDefaultImageAssetID(LLUUID::null); - pbr_ctrl->setBlankImageAssetID(LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID); - pbr_ctrl->setCommitCallback(boost::bind(&LLPanelFace::onCommitPbr, this, _2)); - pbr_ctrl->setOnCancelCallback(boost::bind(&LLPanelFace::onCancelPbr, this, _2)); - pbr_ctrl->setOnSelectCallback(boost::bind(&LLPanelFace::onSelectPbr, this, _2)); - pbr_ctrl->setDragCallback(boost::bind(&LLPanelFace::onDragPbr, this, _2)); - pbr_ctrl->setOnTextureSelectedCallback(boost::bind(&LLPanelFace::onPbrSelectionChanged, this, _1)); - pbr_ctrl->setOnCloseCallback(boost::bind(&LLPanelFace::onCloseTexturePicker, this, _2)); - - pbr_ctrl->setFollowsTop(); - pbr_ctrl->setFollowsLeft(); - pbr_ctrl->setImmediateFilterPermMask(PERM_NONE); - pbr_ctrl->setDnDFilterPermMask(PERM_COPY | PERM_TRANSFER); - pbr_ctrl->setBakeTextureEnabled(false); - pbr_ctrl->setInventoryPickType(PICK_MATERIAL); - } - - mTextureCtrl = getChild("texture control"); - if(mTextureCtrl) - { - mTextureCtrl->setDefaultImageAssetID(DEFAULT_OBJECT_TEXTURE); - mTextureCtrl->setCommitCallback( boost::bind(&LLPanelFace::onCommitTexture, this, _2) ); - mTextureCtrl->setOnCancelCallback( boost::bind(&LLPanelFace::onCancelTexture, this, _2) ); - mTextureCtrl->setOnSelectCallback( boost::bind(&LLPanelFace::onSelectTexture, this, _2) ); - mTextureCtrl->setDragCallback(boost::bind(&LLPanelFace::onDragTexture, this, _2)); - mTextureCtrl->setOnTextureSelectedCallback(boost::bind(&LLPanelFace::onTextureSelectionChanged, this, _1)); - mTextureCtrl->setOnCloseCallback( boost::bind(&LLPanelFace::onCloseTexturePicker, this, _2) ); - - mTextureCtrl->setFollowsTop(); - mTextureCtrl->setFollowsLeft(); - mTextureCtrl->setImmediateFilterPermMask(PERM_NONE); - mTextureCtrl->setDnDFilterPermMask(PERM_COPY | PERM_TRANSFER); - } - - mShinyTextureCtrl = getChild("shinytexture control"); - if(mShinyTextureCtrl) - { - mShinyTextureCtrl->setDefaultImageAssetID(DEFAULT_OBJECT_SPECULAR); - mShinyTextureCtrl->setCommitCallback( boost::bind(&LLPanelFace::onCommitSpecularTexture, this, _2) ); - mShinyTextureCtrl->setOnCancelCallback( boost::bind(&LLPanelFace::onCancelSpecularTexture, this, _2) ); - mShinyTextureCtrl->setOnSelectCallback( boost::bind(&LLPanelFace::onSelectSpecularTexture, this, _2) ); - mShinyTextureCtrl->setOnCloseCallback( boost::bind(&LLPanelFace::onCloseTexturePicker, this, _2) ); - - mShinyTextureCtrl->setDragCallback(boost::bind(&LLPanelFace::onDragTexture, this, _2)); - mShinyTextureCtrl->setOnTextureSelectedCallback(boost::bind(&LLPanelFace::onTextureSelectionChanged, this, _1)); - mShinyTextureCtrl->setFollowsTop(); - mShinyTextureCtrl->setFollowsLeft(); - mShinyTextureCtrl->setImmediateFilterPermMask(PERM_NONE); - mShinyTextureCtrl->setDnDFilterPermMask(PERM_COPY | PERM_TRANSFER); - } - - mBumpyTextureCtrl = getChild("bumpytexture control"); - if(mBumpyTextureCtrl) - { - mBumpyTextureCtrl->setDefaultImageAssetID(DEFAULT_OBJECT_NORMAL); - mBumpyTextureCtrl->setBlankImageAssetID(BLANK_OBJECT_NORMAL); - mBumpyTextureCtrl->setCommitCallback( boost::bind(&LLPanelFace::onCommitNormalTexture, this, _2) ); - mBumpyTextureCtrl->setOnCancelCallback( boost::bind(&LLPanelFace::onCancelNormalTexture, this, _2) ); - mBumpyTextureCtrl->setOnSelectCallback( boost::bind(&LLPanelFace::onSelectNormalTexture, this, _2) ); - mBumpyTextureCtrl->setOnCloseCallback( boost::bind(&LLPanelFace::onCloseTexturePicker, this, _2) ); - - mBumpyTextureCtrl->setDragCallback(boost::bind(&LLPanelFace::onDragTexture, this, _2)); - mBumpyTextureCtrl->setOnTextureSelectedCallback(boost::bind(&LLPanelFace::onTextureSelectionChanged, this, _1)); - mBumpyTextureCtrl->setFollowsTop(); - mBumpyTextureCtrl->setFollowsLeft(); - mBumpyTextureCtrl->setImmediateFilterPermMask(PERM_NONE); - mBumpyTextureCtrl->setDnDFilterPermMask(PERM_COPY | PERM_TRANSFER); - } - - mColorSwatch = getChild("colorswatch"); - if(mColorSwatch) - { - mColorSwatch->setCommitCallback(boost::bind(&LLPanelFace::onCommitColor, this, _2)); - mColorSwatch->setOnCancelCallback(boost::bind(&LLPanelFace::onCancelColor, this, _2)); - mColorSwatch->setOnSelectCallback(boost::bind(&LLPanelFace::onSelectColor, this, _2)); - mColorSwatch->setFollowsTop(); - mColorSwatch->setFollowsLeft(); - mColorSwatch->setCanApplyImmediately(true); - } - - mShinyColorSwatch = getChild("shinycolorswatch"); - if(mShinyColorSwatch) - { - mShinyColorSwatch->setCommitCallback(boost::bind(&LLPanelFace::onCommitShinyColor, this, _2)); - mShinyColorSwatch->setOnCancelCallback(boost::bind(&LLPanelFace::onCancelShinyColor, this, _2)); - mShinyColorSwatch->setOnSelectCallback(boost::bind(&LLPanelFace::onSelectShinyColor, this, _2)); - mShinyColorSwatch->setFollowsTop(); - mShinyColorSwatch->setFollowsLeft(); - mShinyColorSwatch->setCanApplyImmediately(true); - } - - mLabelColorTransp = getChild("color trans"); - if(mLabelColorTransp) - { - mLabelColorTransp->setFollowsTop(); - mLabelColorTransp->setFollowsLeft(); - } - - mCtrlColorTransp = getChild("ColorTrans"); - if(mCtrlColorTransp) - { - mCtrlColorTransp->setCommitCallback(boost::bind(&LLPanelFace::onCommitAlpha, this, _2)); - mCtrlColorTransp->setPrecision(0); - mCtrlColorTransp->setFollowsTop(); - mCtrlColorTransp->setFollowsLeft(); - } - - mCheckFullbright = getChild("checkbox fullbright"); - if (mCheckFullbright) - { - mCheckFullbright->setCommitCallback(LLPanelFace::onCommitFullbright, this); - } - - mComboTexGen = getChild("combobox texgen"); - if(mComboTexGen) - { - mComboTexGen->setCommitCallback(LLPanelFace::onCommitTexGen, this); - mComboTexGen->setFollows(FOLLOWS_LEFT | FOLLOWS_TOP); - } - - mComboMatMedia = getChild("combobox matmedia"); - if(mComboMatMedia) - { - mComboMatMedia->setCommitCallback(LLPanelFace::onCommitMaterialsMedia,this); - mComboMatMedia->selectNthItem(MATMEDIA_MATERIAL); - } - - LLRadioGroup* radio_mat_type = findChild("radio_material_type"); - if(radio_mat_type) - { - radio_mat_type->setCommitCallback(LLPanelFace::onCommitMaterialType, this); - radio_mat_type->selectNthItem(MATTYPE_DIFFUSE); - } - - LLRadioGroup* radio_pbr_type = findChild("radio_pbr_type"); - if (radio_pbr_type) - { - radio_pbr_type->setCommitCallback(LLPanelFace::onCommitPbrType, this); - radio_pbr_type->selectNthItem(PBRTYPE_RENDER_MATERIAL_ID); - } - - mCtrlGlow = getChild("glow"); - if(mCtrlGlow) - { - mCtrlGlow->setCommitCallback(LLPanelFace::onCommitGlow, this); - } - - mMenuClipboardColor = getChild("clipboard_color_params_btn"); - mMenuClipboardTexture = getChild("clipboard_texture_params_btn"); - - mTitleMedia = getChild("title_media"); - mTitleMediaText = getChild("media_info"); - - clearCtrls(); - - return true; -} - -LLPanelFace::LLPanelFace() -: LLPanel(), - mIsAlpha(false), - mComboMatMedia(NULL), - mTitleMedia(NULL), - mTitleMediaText(NULL), - mNeedMediaTitle(true) -{ - USE_TEXTURE = LLTrans::getString("use_texture"); - mCommitCallbackRegistrar.add("PanelFace.menuDoToSelected", boost::bind(&LLPanelFace::menuDoToSelected, this, _2)); - mEnableCallbackRegistrar.add("PanelFace.menuEnable", boost::bind(&LLPanelFace::menuEnableItem, this, _2)); -} - -LLPanelFace::~LLPanelFace() -{ - unloadMedia(); -} - -void LLPanelFace::onVisibilityChange(bool new_visibility) -{ - if (new_visibility) - { - gAgent.showLatestFeatureNotification("gltf"); - } - LLPanel::onVisibilityChange(new_visibility); -} - -void LLPanelFace::draw() -{ - updateCopyTexButton(); - - // grab media name/title and update the UI widget - // Todo: move it, it's preferable not to update - // labels inside draw - updateMediaTitle(); - - LLPanel::draw(); - - if (sMaterialOverrideSelection.update()) - { - setMaterialOverridesFromSelection(); - LLMaterialEditor::updateLive(); - } -} - -void LLPanelFace::sendTexture() -{ - LLTextureCtrl* mTextureCtrl = getChild("texture control"); - if(!mTextureCtrl) return; - if( !mTextureCtrl->getTentative() ) - { - // we grab the item id first, because we want to do a - // permissions check in the selection manager. ARGH! - LLUUID id = mTextureCtrl->getImageItemID(); - if(id.isNull()) - { - id = mTextureCtrl->getImageAssetID(); - } - if (!LLSelectMgr::getInstance()->selectionSetImage(id)) - { - // need to refresh value in texture ctrl - refresh(); - } - } -} - -void LLPanelFace::sendBump(U32 bumpiness) -{ - LLTextureCtrl* bumpytexture_ctrl = getChild("bumpytexture control"); - if (bumpiness < BUMPY_TEXTURE) -{ - LL_DEBUGS("Materials") << "clearing bumptexture control" << LL_ENDL; - bumpytexture_ctrl->clear(); - bumpytexture_ctrl->setImageAssetID(LLUUID()); - } - - updateBumpyControls(bumpiness == BUMPY_TEXTURE, true); - - LLUUID current_normal_map = bumpytexture_ctrl->getImageAssetID(); - - U8 bump = (U8) bumpiness & TEM_BUMP_MASK; - - // Clear legacy bump to None when using an actual normal map - // - if (!current_normal_map.isNull()) - bump = 0; - - // Set the normal map or reset it to null as appropriate - // - LLSelectedTEMaterial::setNormalID(this, current_normal_map); - - LLSelectMgr::getInstance()->selectionSetBumpmap( bump, bumpytexture_ctrl->getImageItemID() ); -} - -void LLPanelFace::sendTexGen() -{ - LLComboBox* mComboTexGen = getChild("combobox texgen"); - if(!mComboTexGen)return; - U8 tex_gen = (U8) mComboTexGen->getCurrentIndex() << TEM_TEX_GEN_SHIFT; - LLSelectMgr::getInstance()->selectionSetTexGen( tex_gen ); -} - -void LLPanelFace::sendShiny(U32 shininess) -{ - LLTextureCtrl* texture_ctrl = getChild("shinytexture control"); - - if (shininess < SHINY_TEXTURE) -{ - texture_ctrl->clear(); - texture_ctrl->setImageAssetID(LLUUID()); - } - - LLUUID specmap = getCurrentSpecularMap(); - - U8 shiny = (U8) shininess & TEM_SHINY_MASK; - if (!specmap.isNull()) - shiny = 0; - - LLSelectedTEMaterial::setSpecularID(this, specmap); - - LLSelectMgr::getInstance()->selectionSetShiny( shiny, texture_ctrl->getImageItemID() ); - - updateShinyControls(!specmap.isNull(), true); - -} - -void LLPanelFace::sendFullbright() -{ - LLCheckBoxCtrl* mCheckFullbright = getChild("checkbox fullbright"); - if(!mCheckFullbright)return; - U8 fullbright = mCheckFullbright->get() ? TEM_FULLBRIGHT_MASK : 0; - LLSelectMgr::getInstance()->selectionSetFullbright( fullbright ); -} - -void LLPanelFace::sendColor() -{ - - LLColorSwatchCtrl* mColorSwatch = getChild("colorswatch"); - if(!mColorSwatch)return; - LLColor4 color = mColorSwatch->get(); - - LLSelectMgr::getInstance()->selectionSetColorOnly( color ); -} - -void LLPanelFace::sendAlpha() -{ - LLSpinCtrl* mCtrlColorTransp = getChild("ColorTrans"); - if(!mCtrlColorTransp)return; - F32 alpha = (100.f - mCtrlColorTransp->get()) / 100.f; - - LLSelectMgr::getInstance()->selectionSetAlphaOnly( alpha ); -} - - -void LLPanelFace::sendGlow() -{ - LLSpinCtrl* mCtrlGlow = getChild("glow"); - llassert(mCtrlGlow); - if (mCtrlGlow) - { - F32 glow = mCtrlGlow->get(); - LLSelectMgr::getInstance()->selectionSetGlow( glow ); - } -} - -struct LLPanelFaceSetTEFunctor : public LLSelectedTEFunctor -{ - LLPanelFaceSetTEFunctor(LLPanelFace* panel) : mPanel(panel) {} - virtual bool apply(LLViewerObject* object, S32 te) - { - bool valid; - F32 value; - std::string prefix; - - // Effectively the same as MATMEDIA_PBR sans using different radio, - // separate for the sake of clarity - LLRadioGroup * radio_mat_type = mPanel->getChild("radio_material_type"); - switch (radio_mat_type->getSelectedIndex()) - { - case MATTYPE_DIFFUSE: - prefix = "Tex"; - break; - case MATTYPE_NORMAL: - prefix = "bumpy"; - break; - case MATTYPE_SPECULAR: - prefix = "shiny"; - break; - } - - LLSpinCtrl * ctrlTexScaleS = mPanel->getChild(prefix + "ScaleU"); - LLSpinCtrl * ctrlTexScaleT = mPanel->getChild(prefix + "ScaleV"); - LLSpinCtrl * ctrlTexOffsetS = mPanel->getChild(prefix + "OffsetU"); - LLSpinCtrl * ctrlTexOffsetT = mPanel->getChild(prefix + "OffsetV"); - LLSpinCtrl * ctrlTexRotation = mPanel->getChild(prefix + "Rot"); - - LLComboBox* comboTexGen = mPanel->getChild("combobox texgen"); - LLCheckBoxCtrl* cb_planar_align = mPanel->getChild("checkbox planar align"); - bool align_planar = (cb_planar_align && cb_planar_align->get()); - - llassert(comboTexGen); - llassert(object); - - if (ctrlTexScaleS) - { - valid = !ctrlTexScaleS->getTentative(); // || !checkFlipScaleS->getTentative(); - if (valid || align_planar) - { - value = ctrlTexScaleS->get(); - if (comboTexGen && - comboTexGen->getCurrentIndex() == 1) - { - value *= 0.5f; - } - object->setTEScaleS( te, value ); - - if (align_planar) - { - LLPanelFace::LLSelectedTEMaterial::setNormalRepeatX(mPanel, value, te, object->getID()); - LLPanelFace::LLSelectedTEMaterial::setSpecularRepeatX(mPanel, value, te, object->getID()); - } - } - } - - if (ctrlTexScaleT) - { - valid = !ctrlTexScaleT->getTentative(); // || !checkFlipScaleT->getTentative(); - if (valid || align_planar) - { - value = ctrlTexScaleT->get(); - //if( checkFlipScaleT->get() ) - //{ - // value = -value; - //} - if (comboTexGen && - comboTexGen->getCurrentIndex() == 1) - { - value *= 0.5f; - } - object->setTEScaleT( te, value ); - - if (align_planar) - { - LLPanelFace::LLSelectedTEMaterial::setNormalRepeatY(mPanel, value, te, object->getID()); - LLPanelFace::LLSelectedTEMaterial::setSpecularRepeatY(mPanel, value, te, object->getID()); - } - } - } - - if (ctrlTexOffsetS) - { - valid = !ctrlTexOffsetS->getTentative(); - if (valid || align_planar) - { - value = ctrlTexOffsetS->get(); - object->setTEOffsetS( te, value ); - - if (align_planar) - { - LLPanelFace::LLSelectedTEMaterial::setNormalOffsetX(mPanel, value, te, object->getID()); - LLPanelFace::LLSelectedTEMaterial::setSpecularOffsetX(mPanel, value, te, object->getID()); - } - } - } - - if (ctrlTexOffsetT) - { - valid = !ctrlTexOffsetT->getTentative(); - if (valid || align_planar) - { - value = ctrlTexOffsetT->get(); - object->setTEOffsetT( te, value ); - - if (align_planar) - { - LLPanelFace::LLSelectedTEMaterial::setNormalOffsetY(mPanel, value, te, object->getID()); - LLPanelFace::LLSelectedTEMaterial::setSpecularOffsetY(mPanel, value, te, object->getID()); - } - } - } - - if (ctrlTexRotation) - { - valid = !ctrlTexRotation->getTentative(); - if (valid || align_planar) - { - value = ctrlTexRotation->get() * DEG_TO_RAD; - object->setTERotation( te, value ); - - if (align_planar) - { - LLPanelFace::LLSelectedTEMaterial::setNormalRotation(mPanel, value, te, object->getID()); - LLPanelFace::LLSelectedTEMaterial::setSpecularRotation(mPanel, value, te, object->getID()); - } - } - } - return true; - } -private: - LLPanelFace* mPanel; -}; - -// Functor that aligns a face to mCenterFace -struct LLPanelFaceSetAlignedTEFunctor : public LLSelectedTEFunctor -{ - LLPanelFaceSetAlignedTEFunctor(LLPanelFace* panel, LLFace* center_face) : - mPanel(panel), - mCenterFace(center_face) {} - - virtual bool apply(LLViewerObject* object, S32 te) - { - LLFace* facep = object->mDrawable->getFace(te); - if (!facep) - { - return true; - } - - if (facep->getViewerObject()->getVolume()->getNumVolumeFaces() <= te) - { - return true; - } - - bool set_aligned = true; - if (facep == mCenterFace) - { - set_aligned = false; - } - if (set_aligned) - { - LLVector2 uv_offset, uv_scale; - F32 uv_rot; - set_aligned = facep->calcAlignedPlanarTE(mCenterFace, &uv_offset, &uv_scale, &uv_rot); - if (set_aligned) - { - object->setTEOffset(te, uv_offset.mV[VX], uv_offset.mV[VY]); - object->setTEScale(te, uv_scale.mV[VX], uv_scale.mV[VY]); - object->setTERotation(te, uv_rot); - - LLPanelFace::LLSelectedTEMaterial::setNormalRotation(mPanel, uv_rot, te, object->getID()); - LLPanelFace::LLSelectedTEMaterial::setSpecularRotation(mPanel, uv_rot, te, object->getID()); - - LLPanelFace::LLSelectedTEMaterial::setNormalOffsetX(mPanel, uv_offset.mV[VX], te, object->getID()); - LLPanelFace::LLSelectedTEMaterial::setNormalOffsetY(mPanel, uv_offset.mV[VY], te, object->getID()); - LLPanelFace::LLSelectedTEMaterial::setNormalRepeatX(mPanel, uv_scale.mV[VX], te, object->getID()); - LLPanelFace::LLSelectedTEMaterial::setNormalRepeatY(mPanel, uv_scale.mV[VY], te, object->getID()); - - LLPanelFace::LLSelectedTEMaterial::setSpecularOffsetX(mPanel, uv_offset.mV[VX], te, object->getID()); - LLPanelFace::LLSelectedTEMaterial::setSpecularOffsetY(mPanel, uv_offset.mV[VY], te, object->getID()); - LLPanelFace::LLSelectedTEMaterial::setSpecularRepeatX(mPanel, uv_scale.mV[VX], te, object->getID()); - LLPanelFace::LLSelectedTEMaterial::setSpecularRepeatY(mPanel, uv_scale.mV[VY], te, object->getID()); - } - } - if (!set_aligned) - { - LLPanelFaceSetTEFunctor setfunc(mPanel); - setfunc.apply(object, te); - } - return true; - } -private: - LLPanelFace* mPanel; - LLFace* mCenterFace; -}; - -struct LLPanelFaceSetAlignedConcreteTEFunctor : public LLSelectedTEFunctor -{ - LLPanelFaceSetAlignedConcreteTEFunctor(LLPanelFace* panel, LLFace* center_face, LLRender::eTexIndex map) : - mPanel(panel), - mChefFace(center_face), - mMap(map) - {} - - virtual bool apply(LLViewerObject* object, S32 te) - { - LLFace* facep = object->mDrawable->getFace(te); - if (!facep) - { - return true; - } - - if (facep->getViewerObject()->getVolume()->getNumVolumeFaces() <= te) - { - return true; - } - - if (mChefFace != facep) - { - LLVector2 uv_offset, uv_scale; - F32 uv_rot; - if (facep->calcAlignedPlanarTE(mChefFace, &uv_offset, &uv_scale, &uv_rot, mMap)) - { - switch (mMap) - { - case LLRender::DIFFUSE_MAP: - object->setTEOffset(te, uv_offset.mV[VX], uv_offset.mV[VY]); - object->setTEScale(te, uv_scale.mV[VX], uv_scale.mV[VY]); - object->setTERotation(te, uv_rot); - break; - case LLRender::NORMAL_MAP: - LLPanelFace::LLSelectedTEMaterial::setNormalRotation(mPanel, uv_rot, te, object->getID()); - LLPanelFace::LLSelectedTEMaterial::setNormalOffsetX(mPanel, uv_offset.mV[VX], te, object->getID()); - LLPanelFace::LLSelectedTEMaterial::setNormalOffsetY(mPanel, uv_offset.mV[VY], te, object->getID()); - LLPanelFace::LLSelectedTEMaterial::setNormalRepeatX(mPanel, uv_scale.mV[VX], te, object->getID()); - LLPanelFace::LLSelectedTEMaterial::setNormalRepeatY(mPanel, uv_scale.mV[VY], te, object->getID()); - break; - case LLRender::SPECULAR_MAP: - LLPanelFace::LLSelectedTEMaterial::setSpecularRotation(mPanel, uv_rot, te, object->getID()); - LLPanelFace::LLSelectedTEMaterial::setSpecularOffsetX(mPanel, uv_offset.mV[VX], te, object->getID()); - LLPanelFace::LLSelectedTEMaterial::setSpecularOffsetY(mPanel, uv_offset.mV[VY], te, object->getID()); - LLPanelFace::LLSelectedTEMaterial::setSpecularRepeatX(mPanel, uv_scale.mV[VX], te, object->getID()); - LLPanelFace::LLSelectedTEMaterial::setSpecularRepeatY(mPanel, uv_scale.mV[VY], te, object->getID()); - break; - default: /*make compiler happy*/ - break; - } - } - } - - return true; - } -private: - LLPanelFace* mPanel; - LLFace* mChefFace; - LLRender::eTexIndex mMap; -}; - -// Functor that tests if a face is aligned to mCenterFace -struct LLPanelFaceGetIsAlignedTEFunctor : public LLSelectedTEFunctor -{ - LLPanelFaceGetIsAlignedTEFunctor(LLFace* center_face) : - mCenterFace(center_face) {} - - virtual bool apply(LLViewerObject* object, S32 te) - { - LLFace* facep = object->mDrawable->getFace(te); - if (!facep) - { - return false; - } - - if (facep->getViewerObject()->getVolume()->getNumVolumeFaces() <= te) - { //volume face does not exist, can't be aligned - return false; - } - - if (facep == mCenterFace) - { - return true; - } - - LLVector2 aligned_st_offset, aligned_st_scale; - F32 aligned_st_rot; - if ( facep->calcAlignedPlanarTE(mCenterFace, &aligned_st_offset, &aligned_st_scale, &aligned_st_rot) ) - { - const LLTextureEntry* tep = facep->getTextureEntry(); - LLVector2 st_offset, st_scale; - tep->getOffset(&st_offset.mV[VX], &st_offset.mV[VY]); - tep->getScale(&st_scale.mV[VX], &st_scale.mV[VY]); - F32 st_rot = tep->getRotation(); - - bool eq_offset_x = is_approx_equal_fraction(st_offset.mV[VX], aligned_st_offset.mV[VX], 12); - bool eq_offset_y = is_approx_equal_fraction(st_offset.mV[VY], aligned_st_offset.mV[VY], 12); - bool eq_scale_x = is_approx_equal_fraction(st_scale.mV[VX], aligned_st_scale.mV[VX], 12); - bool eq_scale_y = is_approx_equal_fraction(st_scale.mV[VY], aligned_st_scale.mV[VY], 12); - bool eq_rot = is_approx_equal_fraction(st_rot, aligned_st_rot, 6); - - // needs a fuzzy comparison, because of fp errors - if (eq_offset_x && - eq_offset_y && - eq_scale_x && - eq_scale_y && - eq_rot) - { - return true; - } - } - return false; - } -private: - LLFace* mCenterFace; -}; - -struct LLPanelFaceSendFunctor : public LLSelectedObjectFunctor -{ - virtual bool apply(LLViewerObject* object) - { - object->sendTEUpdate(); - return true; - } -}; - -void LLPanelFace::sendTextureInfo() -{ - if ((bool)childGetValue("checkbox planar align").asBoolean()) - { - LLFace* last_face = NULL; - bool identical_face =false; - LLSelectedTE::getFace(last_face, identical_face); - LLPanelFaceSetAlignedTEFunctor setfunc(this, last_face); - LLSelectMgr::getInstance()->getSelection()->applyToTEs(&setfunc); - } - else - { - LLPanelFaceSetTEFunctor setfunc(this); - LLSelectMgr::getInstance()->getSelection()->applyToTEs(&setfunc); - } - - LLPanelFaceSendFunctor sendfunc; - LLSelectMgr::getInstance()->getSelection()->applyToObjects(&sendfunc); -} - -void LLPanelFace::alignTestureLayer() -{ - LLFace* last_face = NULL; - bool identical_face = false; - LLSelectedTE::getFace(last_face, identical_face); - - LLRadioGroup * radio_mat_type = getChild("radio_material_type"); - LLPanelFaceSetAlignedConcreteTEFunctor setfunc(this, last_face, static_cast(radio_mat_type->getSelectedIndex())); - LLSelectMgr::getInstance()->getSelection()->applyToTEs(&setfunc); -} - -void LLPanelFace::getState() -{ - updateUI(); -} - -void LLPanelFace::updateUI(bool force_set_values /*false*/) -{ //set state of UI to match state of texture entry(ies) (calls setEnabled, setValue, etc, but NOT setVisible) - LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode(); - LLViewerObject* objectp = node ? node->getObject() : NULL; - - if (objectp - && objectp->getPCode() == LL_PCODE_VOLUME - && objectp->permModify()) - { - bool editable = objectp->permModify() && !objectp->isPermanentEnforced(); - bool attachment = objectp->isAttachment(); - - bool has_pbr_material; - bool has_faces_without_pbr; - updateUIGLTF(objectp, has_pbr_material, has_faces_without_pbr, force_set_values); - - const bool has_material = !has_pbr_material; - - // only turn on auto-adjust button if there is a media renderer and the media is loaded - childSetEnabled("button align", editable); - - if (mComboMatMedia->getCurrentIndex() < MATMEDIA_MATERIAL) - { - // When selecting an object with a pbr and UI combo is not set, - // set to pbr option, otherwise to a texture (material) - if (has_pbr_material) - { - mComboMatMedia->selectNthItem(MATMEDIA_PBR); - } - else - { - mComboMatMedia->selectNthItem(MATMEDIA_MATERIAL); - } - } - - // *NOTE: The "identical" variable is currently only used to decide if - // the texgen control should be tentative - this is not used by GLTF - // materials. -Cosmic;2022-11-09 - bool identical = true; // true because it is anded below - bool identical_diffuse = false; - bool identical_norm = false; - bool identical_spec = false; - - LLTextureCtrl *texture_ctrl = getChild("texture control"); - LLTextureCtrl *shinytexture_ctrl = getChild("shinytexture control"); - LLTextureCtrl *bumpytexture_ctrl = getChild("bumpytexture control"); - - LLUUID id; - LLUUID normmap_id; - LLUUID specmap_id; - - LLSelectedTE::getTexId(id, identical_diffuse); - LLSelectedTEMaterial::getNormalID(normmap_id, identical_norm); - LLSelectedTEMaterial::getSpecularID(specmap_id, identical_spec); - - static S32 selected_te = -1; - static LLUUID prev_obj_id; - if ((LLToolFace::getInstance() == LLToolMgr::getInstance()->getCurrentTool()) && - !LLSelectMgr::getInstance()->getSelection()->isMultipleTESelected()) - { - S32 new_selection = -1; // Don't use getLastSelectedTE, it could have been deselected - S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); - for (S32 te = 0; te < num_tes; ++te) - { - if (node->isTESelected(te)) - { - new_selection = te; - break; - } - } - - if ((new_selection != selected_te) - || (prev_obj_id != objectp->getID())) - { - bool te_has_media = objectp->getTE(new_selection) && objectp->getTE(new_selection)->hasMedia(); - bool te_has_pbr = objectp->getRenderMaterialID(new_selection).notNull(); - - if (te_has_pbr && !((mComboMatMedia->getCurrentIndex() == MATMEDIA_MEDIA) && te_has_media)) - { - mComboMatMedia->selectNthItem(MATMEDIA_PBR); - } - else if (te_has_media) - { - mComboMatMedia->selectNthItem(MATMEDIA_MEDIA); - } - else if (id.notNull() || normmap_id.notNull() || specmap_id.notNull()) - { - mComboMatMedia->selectNthItem(MATMEDIA_MATERIAL); - } - selected_te = new_selection; - prev_obj_id = objectp->getID(); - } - } - else - { - if (prev_obj_id != objectp->getID()) - { - if (has_pbr_material && (mComboMatMedia->getCurrentIndex() == MATMEDIA_MATERIAL)) - { - mComboMatMedia->selectNthItem(MATMEDIA_PBR); - } - else if (!has_pbr_material && (mComboMatMedia->getCurrentIndex() == MATMEDIA_PBR)) - { - mComboMatMedia->selectNthItem(MATMEDIA_MATERIAL); - } - prev_obj_id = objectp->getID(); - } - } - mComboMatMedia->setEnabled(editable); - - LLRadioGroup* radio_mat_type = getChild("radio_material_type"); - if (radio_mat_type->getSelectedIndex() < MATTYPE_DIFFUSE) - { - radio_mat_type->selectNthItem(MATTYPE_DIFFUSE); - } - radio_mat_type->setEnabled(editable); - - LLRadioGroup* radio_pbr_type = getChild("radio_pbr_type"); - if (radio_pbr_type->getSelectedIndex() < PBRTYPE_RENDER_MATERIAL_ID) - { - radio_pbr_type->selectNthItem(PBRTYPE_RENDER_MATERIAL_ID); - } - radio_pbr_type->setEnabled(editable); - const bool pbr_selected = mComboMatMedia->getCurrentIndex() == MATMEDIA_PBR; - const bool texture_info_selected = pbr_selected && radio_pbr_type->getSelectedIndex() != PBRTYPE_RENDER_MATERIAL_ID; - - getChildView("checkbox_sync_settings")->setEnabled(editable); - childSetValue("checkbox_sync_settings", gSavedSettings.getBOOL("SyncMaterialSettings")); - - updateVisibility(objectp); - - // Color swatch - { - getChildView("color label")->setEnabled(editable); - } - LLColorSwatchCtrl* color_swatch = findChild("colorswatch"); - - LLColor4 color = LLColor4::white; - bool identical_color = false; - - if (color_swatch) - { - LLSelectedTE::getColor(color, identical_color); - LLColor4 prev_color = color_swatch->get(); - - color_swatch->setOriginal(color); - color_swatch->set(color, force_set_values || (prev_color != color) || !editable); - - color_swatch->setValid(editable && !has_pbr_material); - color_swatch->setEnabled( editable && !has_pbr_material); - color_swatch->setCanApplyImmediately( editable && !has_pbr_material); - } - - // Color transparency - getChildView("color trans")->setEnabled(editable); - - F32 transparency = (1.f - color.mV[VALPHA]) * 100.f; - getChild("ColorTrans")->setValue(editable ? transparency : 0); - getChildView("ColorTrans")->setEnabled(editable && has_material); - - U8 shiny = 0; - bool identical_shiny = false; - - // Shiny - LLSelectedTE::getShiny(shiny, identical_shiny); - identical = identical && identical_shiny; - - shiny = specmap_id.isNull() ? shiny : SHINY_TEXTURE; - - LLCtrlSelectionInterface* combobox_shininess = childGetSelectionInterface("combobox shininess"); - if (combobox_shininess) - { - combobox_shininess->selectNthItem((S32)shiny); - } - - getChildView("label shininess")->setEnabled(editable); - getChildView("combobox shininess")->setEnabled(editable); - - getChildView("label glossiness")->setEnabled(editable); - getChildView("glossiness")->setEnabled(editable); - - getChildView("label environment")->setEnabled(editable); - getChildView("environment")->setEnabled(editable); - getChildView("label shinycolor")->setEnabled(editable); - - getChild("combobox shininess")->setTentative(!identical_spec); - getChild("glossiness")->setTentative(!identical_spec); - getChild("environment")->setTentative(!identical_spec); - getChild("shinycolorswatch")->setTentative(!identical_spec); - - LLColorSwatchCtrl* mShinyColorSwatch = getChild("shinycolorswatch"); - if (mShinyColorSwatch) - { - mShinyColorSwatch->setValid(editable); - mShinyColorSwatch->setEnabled( editable ); - mShinyColorSwatch->setCanApplyImmediately( editable ); - } - - U8 bumpy = 0; - // Bumpy - { - bool identical_bumpy = false; - LLSelectedTE::getBumpmap(bumpy,identical_bumpy); - - LLUUID norm_map_id = getCurrentNormalMap(); - LLCtrlSelectionInterface* combobox_bumpiness = childGetSelectionInterface("combobox bumpiness"); - - bumpy = norm_map_id.isNull() ? bumpy : BUMPY_TEXTURE; - - if (combobox_bumpiness) - { - combobox_bumpiness->selectNthItem((S32)bumpy); - } - else - { - LL_WARNS() << "failed childGetSelectionInterface for 'combobox bumpiness'" << LL_ENDL; - } - - getChildView("combobox bumpiness")->setEnabled(editable); - getChild("combobox bumpiness")->setTentative(!identical_bumpy); - getChildView("label bumpiness")->setEnabled(editable); - } - - // Texture - { - mIsAlpha = false; - LLGLenum image_format = GL_RGB; - bool identical_image_format = false; - LLSelectedTE::getImageFormat(image_format, identical_image_format); - - mIsAlpha = false; - switch (image_format) - { - case GL_RGBA: - case GL_ALPHA: - { - mIsAlpha = true; - } - break; - - case GL_RGB: break; - default: - { - LL_WARNS() << "Unexpected tex format in LLPanelFace...resorting to no alpha" << LL_ENDL; - } - break; - } - - if (LLViewerMedia::getInstance()->textureHasMedia(id)) - { - getChildView("button align")->setEnabled(editable); - } - - // Diffuse Alpha Mode - - // Init to the default that is appropriate for the alpha content of the asset - // - U8 alpha_mode = mIsAlpha ? LLMaterial::DIFFUSE_ALPHA_MODE_BLEND : LLMaterial::DIFFUSE_ALPHA_MODE_NONE; - - bool identical_alpha_mode = false; - - // See if that's been overridden by a material setting for same... - // - LLSelectedTEMaterial::getCurrentDiffuseAlphaMode(alpha_mode, identical_alpha_mode, mIsAlpha); - - LLCtrlSelectionInterface* combobox_alphamode = childGetSelectionInterface("combobox alphamode"); - if (combobox_alphamode) - { - //it is invalid to have any alpha mode other than blend if transparency is greater than zero ... - // Want masking? Want emissive? Tough! You get BLEND! - alpha_mode = (transparency > 0.f) ? LLMaterial::DIFFUSE_ALPHA_MODE_BLEND : alpha_mode; - - // ... unless there is no alpha channel in the texture, in which case alpha mode MUST be none - alpha_mode = mIsAlpha ? alpha_mode : LLMaterial::DIFFUSE_ALPHA_MODE_NONE; - - combobox_alphamode->selectNthItem(alpha_mode); - } - else - { - LL_WARNS() << "failed childGetSelectionInterface for 'combobox alphamode'" << LL_ENDL; - } - - updateAlphaControls(); - - if (texture_ctrl) - { - if (identical_diffuse) - { - texture_ctrl->setTentative(false); - texture_ctrl->setEnabled(editable && !has_pbr_material); - texture_ctrl->setImageAssetID(id); - getChildView("combobox alphamode")->setEnabled(editable && mIsAlpha && transparency <= 0.f && !has_pbr_material); - getChildView("label alphamode")->setEnabled(editable && mIsAlpha && !has_pbr_material); - getChildView("maskcutoff")->setEnabled(editable && mIsAlpha && !has_pbr_material); - getChildView("label maskcutoff")->setEnabled(editable && mIsAlpha && !has_pbr_material); - - texture_ctrl->setBakeTextureEnabled(true); - } - else if (id.isNull()) - { - // None selected - texture_ctrl->setTentative(false); - texture_ctrl->setEnabled(false); - texture_ctrl->setImageAssetID(LLUUID::null); - getChildView("combobox alphamode")->setEnabled(false); - getChildView("label alphamode")->setEnabled(false); - getChildView("maskcutoff")->setEnabled(false); - getChildView("label maskcutoff")->setEnabled(false); - - texture_ctrl->setBakeTextureEnabled(false); - } - else - { - // Tentative: multiple selected with different textures - texture_ctrl->setTentative(true); - texture_ctrl->setEnabled(editable && !has_pbr_material); - texture_ctrl->setImageAssetID(id); - getChildView("combobox alphamode")->setEnabled(editable && mIsAlpha && transparency <= 0.f && !has_pbr_material); - getChildView("label alphamode")->setEnabled(editable && mIsAlpha && !has_pbr_material); - getChildView("maskcutoff")->setEnabled(editable && mIsAlpha && !has_pbr_material); - getChildView("label maskcutoff")->setEnabled(editable && mIsAlpha && !has_pbr_material); - - texture_ctrl->setBakeTextureEnabled(true); - } - - if (attachment) - { - // attachments are in world and in inventory, - // server doesn't support changing permissions - // in such case - texture_ctrl->setImmediateFilterPermMask(PERM_COPY | PERM_TRANSFER); - } - else - { - texture_ctrl->setImmediateFilterPermMask(PERM_NONE); - } - } - - if (shinytexture_ctrl) - { - shinytexture_ctrl->setTentative( !identical_spec ); - shinytexture_ctrl->setEnabled( editable && !has_pbr_material); - shinytexture_ctrl->setImageAssetID( specmap_id ); - - if (attachment) - { - shinytexture_ctrl->setImmediateFilterPermMask(PERM_COPY | PERM_TRANSFER); - } - else - { - shinytexture_ctrl->setImmediateFilterPermMask(PERM_NONE); - } - } - - if (bumpytexture_ctrl) - { - bumpytexture_ctrl->setTentative( !identical_norm ); - bumpytexture_ctrl->setEnabled( editable && !has_pbr_material); - bumpytexture_ctrl->setImageAssetID( normmap_id ); - - if (attachment) - { - bumpytexture_ctrl->setImmediateFilterPermMask(PERM_COPY | PERM_TRANSFER); - } - else - { - bumpytexture_ctrl->setImmediateFilterPermMask(PERM_NONE); - } - } - } - - // planar align - bool align_planar = false; - bool identical_planar_aligned = false; - { - LLCheckBoxCtrl* cb_planar_align = getChild("checkbox planar align"); - align_planar = (cb_planar_align && cb_planar_align->get()); - - bool enabled = (editable && isIdenticalPlanarTexgen() && !texture_info_selected); - childSetValue("checkbox planar align", align_planar && enabled); - childSetVisible("checkbox planar align", enabled); - childSetEnabled("checkbox planar align", enabled); - childSetEnabled("button align textures", enabled && LLSelectMgr::getInstance()->getSelection()->getObjectCount() > 1); - - if (align_planar && enabled) - { - LLFace* last_face = NULL; - bool identical_face = false; - LLSelectedTE::getFace(last_face, identical_face); - - LLPanelFaceGetIsAlignedTEFunctor get_is_aligend_func(last_face); - // this will determine if the texture param controls are tentative: - identical_planar_aligned = LLSelectMgr::getInstance()->getSelection()->applyToTEs(&get_is_aligend_func); - } - } - - // Needs to be public and before tex scale settings below to properly reflect - // behavior when in planar vs default texgen modes in the - // NORSPEC-84 et al - // - LLTextureEntry::e_texgen selected_texgen = LLTextureEntry::TEX_GEN_DEFAULT; - bool identical_texgen = true; - bool identical_planar_texgen = false; - - { - LLSelectedTE::getTexGen(selected_texgen, identical_texgen); - identical_planar_texgen = (identical_texgen && (selected_texgen == LLTextureEntry::TEX_GEN_PLANAR)); - } - - // Texture scale - { - bool identical_diff_scale_s = false; - bool identical_spec_scale_s = false; - bool identical_norm_scale_s = false; - - identical = align_planar ? identical_planar_aligned : identical; - - F32 diff_scale_s = 1.f; - F32 spec_scale_s = 1.f; - F32 norm_scale_s = 1.f; - - LLSelectedTE::getScaleS(diff_scale_s, identical_diff_scale_s); - LLSelectedTEMaterial::getSpecularRepeatX(spec_scale_s, identical_spec_scale_s); - LLSelectedTEMaterial::getNormalRepeatX(norm_scale_s, identical_norm_scale_s); - - diff_scale_s = editable ? diff_scale_s : 1.0f; - diff_scale_s *= identical_planar_texgen ? 2.0f : 1.0f; - - norm_scale_s = editable ? norm_scale_s : 1.0f; - norm_scale_s *= identical_planar_texgen ? 2.0f : 1.0f; - - spec_scale_s = editable ? spec_scale_s : 1.0f; - spec_scale_s *= identical_planar_texgen ? 2.0f : 1.0f; - - getChild("TexScaleU")->setValue(diff_scale_s); - getChild("shinyScaleU")->setValue(spec_scale_s); - getChild("bumpyScaleU")->setValue(norm_scale_s); - - getChildView("TexScaleU")->setEnabled(editable && has_material); - getChildView("shinyScaleU")->setEnabled(editable && has_material && specmap_id.notNull()); - getChildView("bumpyScaleU")->setEnabled(editable && has_material && normmap_id.notNull()); - - bool diff_scale_tentative = !(identical && identical_diff_scale_s); - bool norm_scale_tentative = !(identical && identical_norm_scale_s); - bool spec_scale_tentative = !(identical && identical_spec_scale_s); - - getChild("TexScaleU")->setTentative( LLSD(diff_scale_tentative)); - getChild("shinyScaleU")->setTentative(LLSD(spec_scale_tentative)); - getChild("bumpyScaleU")->setTentative(LLSD(norm_scale_tentative)); - } - - { - bool identical_diff_scale_t = false; - bool identical_spec_scale_t = false; - bool identical_norm_scale_t = false; - - F32 diff_scale_t = 1.f; - F32 spec_scale_t = 1.f; - F32 norm_scale_t = 1.f; - - LLSelectedTE::getScaleT(diff_scale_t, identical_diff_scale_t); - LLSelectedTEMaterial::getSpecularRepeatY(spec_scale_t, identical_spec_scale_t); - LLSelectedTEMaterial::getNormalRepeatY(norm_scale_t, identical_norm_scale_t); - - diff_scale_t = editable ? diff_scale_t : 1.0f; - diff_scale_t *= identical_planar_texgen ? 2.0f : 1.0f; - - norm_scale_t = editable ? norm_scale_t : 1.0f; - norm_scale_t *= identical_planar_texgen ? 2.0f : 1.0f; - - spec_scale_t = editable ? spec_scale_t : 1.0f; - spec_scale_t *= identical_planar_texgen ? 2.0f : 1.0f; - - bool diff_scale_tentative = !identical_diff_scale_t; - bool norm_scale_tentative = !identical_norm_scale_t; - bool spec_scale_tentative = !identical_spec_scale_t; - - getChildView("TexScaleV")->setEnabled(editable && has_material); - getChildView("shinyScaleV")->setEnabled(editable && has_material && specmap_id.notNull()); - getChildView("bumpyScaleV")->setEnabled(editable && has_material && normmap_id.notNull()); - - if (force_set_values) - { - getChild("TexScaleV")->forceSetValue(diff_scale_t); - } - else - { - getChild("TexScaleV")->setValue(diff_scale_t); - } - getChild("shinyScaleV")->setValue(norm_scale_t); - getChild("bumpyScaleV")->setValue(spec_scale_t); - - getChild("TexScaleV")->setTentative(LLSD(diff_scale_tentative)); - getChild("shinyScaleV")->setTentative(LLSD(norm_scale_tentative)); - getChild("bumpyScaleV")->setTentative(LLSD(spec_scale_tentative)); - } - - // Texture offset - { - bool identical_diff_offset_s = false; - bool identical_norm_offset_s = false; - bool identical_spec_offset_s = false; - - F32 diff_offset_s = 0.0f; - F32 norm_offset_s = 0.0f; - F32 spec_offset_s = 0.0f; - - LLSelectedTE::getOffsetS(diff_offset_s, identical_diff_offset_s); - LLSelectedTEMaterial::getNormalOffsetX(norm_offset_s, identical_norm_offset_s); - LLSelectedTEMaterial::getSpecularOffsetX(spec_offset_s, identical_spec_offset_s); - - bool diff_offset_u_tentative = !(align_planar ? identical_planar_aligned : identical_diff_offset_s); - bool norm_offset_u_tentative = !(align_planar ? identical_planar_aligned : identical_norm_offset_s); - bool spec_offset_u_tentative = !(align_planar ? identical_planar_aligned : identical_spec_offset_s); - - getChild("TexOffsetU")->setValue( editable ? diff_offset_s : 0.0f); - getChild("bumpyOffsetU")->setValue(editable ? norm_offset_s : 0.0f); - getChild("shinyOffsetU")->setValue(editable ? spec_offset_s : 0.0f); - - getChild("TexOffsetU")->setTentative(LLSD(diff_offset_u_tentative)); - getChild("shinyOffsetU")->setTentative(LLSD(norm_offset_u_tentative)); - getChild("bumpyOffsetU")->setTentative(LLSD(spec_offset_u_tentative)); - - getChildView("TexOffsetU")->setEnabled(editable && has_material); - getChildView("shinyOffsetU")->setEnabled(editable && has_material && specmap_id.notNull()); - getChildView("bumpyOffsetU")->setEnabled(editable && has_material && normmap_id.notNull()); - } - - { - bool identical_diff_offset_t = false; - bool identical_norm_offset_t = false; - bool identical_spec_offset_t = false; - - F32 diff_offset_t = 0.0f; - F32 norm_offset_t = 0.0f; - F32 spec_offset_t = 0.0f; - - LLSelectedTE::getOffsetT(diff_offset_t, identical_diff_offset_t); - LLSelectedTEMaterial::getNormalOffsetY(norm_offset_t, identical_norm_offset_t); - LLSelectedTEMaterial::getSpecularOffsetY(spec_offset_t, identical_spec_offset_t); - - bool diff_offset_v_tentative = !(align_planar ? identical_planar_aligned : identical_diff_offset_t); - bool norm_offset_v_tentative = !(align_planar ? identical_planar_aligned : identical_norm_offset_t); - bool spec_offset_v_tentative = !(align_planar ? identical_planar_aligned : identical_spec_offset_t); - - getChild("TexOffsetV")->setValue( editable ? diff_offset_t : 0.0f); - getChild("bumpyOffsetV")->setValue(editable ? norm_offset_t : 0.0f); - getChild("shinyOffsetV")->setValue(editable ? spec_offset_t : 0.0f); - - getChild("TexOffsetV")->setTentative(LLSD(diff_offset_v_tentative)); - getChild("shinyOffsetV")->setTentative(LLSD(norm_offset_v_tentative)); - getChild("bumpyOffsetV")->setTentative(LLSD(spec_offset_v_tentative)); - - getChildView("TexOffsetV")->setEnabled(editable && has_material); - getChildView("shinyOffsetV")->setEnabled(editable && has_material && specmap_id.notNull()); - getChildView("bumpyOffsetV")->setEnabled(editable && has_material && normmap_id.notNull()); - } - - // Texture rotation - { - bool identical_diff_rotation = false; - bool identical_norm_rotation = false; - bool identical_spec_rotation = false; - - F32 diff_rotation = 0.f; - F32 norm_rotation = 0.f; - F32 spec_rotation = 0.f; - - LLSelectedTE::getRotation(diff_rotation,identical_diff_rotation); - LLSelectedTEMaterial::getSpecularRotation(spec_rotation,identical_spec_rotation); - LLSelectedTEMaterial::getNormalRotation(norm_rotation,identical_norm_rotation); - - bool diff_rot_tentative = !(align_planar ? identical_planar_aligned : identical_diff_rotation); - bool norm_rot_tentative = !(align_planar ? identical_planar_aligned : identical_norm_rotation); - bool spec_rot_tentative = !(align_planar ? identical_planar_aligned : identical_spec_rotation); - - F32 diff_rot_deg = diff_rotation * RAD_TO_DEG; - F32 norm_rot_deg = norm_rotation * RAD_TO_DEG; - F32 spec_rot_deg = spec_rotation * RAD_TO_DEG; - - getChildView("TexRot")->setEnabled(editable && has_material); - getChildView("shinyRot")->setEnabled(editable && has_material && specmap_id.notNull()); - getChildView("bumpyRot")->setEnabled(editable && has_material && normmap_id.notNull()); - - getChild("TexRot")->setTentative(diff_rot_tentative); - getChild("shinyRot")->setTentative(LLSD(norm_rot_tentative)); - getChild("bumpyRot")->setTentative(LLSD(spec_rot_tentative)); - - getChild("TexRot")->setValue( editable ? diff_rot_deg : 0.0f); - getChild("shinyRot")->setValue(editable ? spec_rot_deg : 0.0f); - getChild("bumpyRot")->setValue(editable ? norm_rot_deg : 0.0f); - } - - { - F32 glow = 0.f; - bool identical_glow = false; - LLSelectedTE::getGlow(glow,identical_glow); - getChild("glow")->setValue(glow); - getChild("glow")->setTentative(!identical_glow); - getChildView("glow")->setEnabled(editable); - getChildView("glow label")->setEnabled(editable); - } - - { - LLCtrlSelectionInterface* combobox_texgen = childGetSelectionInterface("combobox texgen"); - if (combobox_texgen) - { - // Maps from enum to combobox entry index - combobox_texgen->selectNthItem(((S32)selected_texgen) >> 1); - } - else - { - LL_WARNS() << "failed childGetSelectionInterface for 'combobox texgen'" << LL_ENDL; - } - - getChildView("combobox texgen")->setEnabled(editable); - getChild("combobox texgen")->setTentative(!identical); - getChildView("tex gen")->setEnabled(editable); - } - - { - U8 fullbright_flag = 0; - bool identical_fullbright = false; - - LLSelectedTE::getFullbright(fullbright_flag,identical_fullbright); - - getChild("checkbox fullbright")->setValue((S32)(fullbright_flag != 0)); - getChildView("checkbox fullbright")->setEnabled(editable && !has_pbr_material); - getChild("checkbox fullbright")->setTentative(!identical_fullbright); - mComboMatMedia->setEnabledByValue("Materials", !has_pbr_material); - } - - // Repeats per meter - { - F32 repeats_diff = 1.f; - F32 repeats_norm = 1.f; - F32 repeats_spec = 1.f; - - bool identical_diff_repeats = false; - bool identical_norm_repeats = false; - bool identical_spec_repeats = false; - - LLSelectedTE::getMaxDiffuseRepeats(repeats_diff, identical_diff_repeats); - LLSelectedTEMaterial::getMaxNormalRepeats(repeats_norm, identical_norm_repeats); - LLSelectedTEMaterial::getMaxSpecularRepeats(repeats_spec, identical_spec_repeats); - - LLComboBox* mComboTexGen = getChild("combobox texgen"); - if (mComboTexGen) - { - S32 index = mComboTexGen ? mComboTexGen->getCurrentIndex() : 0; - bool enabled = editable && (index != 1); - bool identical_repeats = true; - S32 material_selection = mComboMatMedia->getCurrentIndex(); - F32 repeats = 1.0f; - - U32 material_type = MATTYPE_DIFFUSE; - if (material_selection == MATMEDIA_MATERIAL) - { - material_type = radio_mat_type->getSelectedIndex(); - } - else if (material_selection == MATMEDIA_PBR) - { - enabled = editable && has_pbr_material; - material_type = radio_pbr_type->getSelectedIndex(); - } - - switch (material_type) - { - default: - case MATTYPE_DIFFUSE: - { - if (material_selection != MATMEDIA_PBR) - { - enabled = editable && !id.isNull(); - } - identical_repeats = identical_diff_repeats; - repeats = repeats_diff; - } - break; - - case MATTYPE_SPECULAR: - { - if (material_selection != MATMEDIA_PBR) - { - enabled = (editable && ((shiny == SHINY_TEXTURE) && !specmap_id.isNull())); - } - identical_repeats = identical_spec_repeats; - repeats = repeats_spec; - } - break; - - case MATTYPE_NORMAL: - { - if (material_selection != MATMEDIA_PBR) - { - enabled = (editable && ((bumpy == BUMPY_TEXTURE) && !normmap_id.isNull())); - } - identical_repeats = identical_norm_repeats; - repeats = repeats_norm; - } - break; - } - - bool repeats_tentative = !identical_repeats; - - LLSpinCtrl* rpt_ctrl = getChild("rptctrl"); - if (force_set_values) - { - //onCommit, previosly edited element updates related ones - rpt_ctrl->forceSetValue(editable ? repeats : 1.0f); - } - else - { - rpt_ctrl->setValue(editable ? repeats : 1.0f); - } - rpt_ctrl->setTentative(LLSD(repeats_tentative)); - rpt_ctrl->setEnabled(has_material && !identical_planar_texgen && enabled); - } - } - - // Materials - { - LLMaterialPtr material; - LLSelectedTEMaterial::getCurrent(material, identical); - - if (material && editable) - { - LL_DEBUGS("Materials") << material->asLLSD() << LL_ENDL; - - // Alpha - LLCtrlSelectionInterface* combobox_alphamode = - childGetSelectionInterface("combobox alphamode"); - if (combobox_alphamode) - { - U32 alpha_mode = material->getDiffuseAlphaMode(); - - if (transparency > 0.f) - { //it is invalid to have any alpha mode other than blend if transparency is greater than zero ... - alpha_mode = LLMaterial::DIFFUSE_ALPHA_MODE_BLEND; - } - - if (!mIsAlpha) - { // ... unless there is no alpha channel in the texture, in which case alpha mode MUST ebe none - alpha_mode = LLMaterial::DIFFUSE_ALPHA_MODE_NONE; - } - - combobox_alphamode->selectNthItem(alpha_mode); - } - else - { - LL_WARNS() << "failed childGetSelectionInterface for 'combobox alphamode'" << LL_ENDL; - } - getChild("maskcutoff")->setValue(material->getAlphaMaskCutoff()); - updateAlphaControls(); - - identical_planar_texgen = isIdenticalPlanarTexgen(); - - // Shiny (specular) - F32 offset_x, offset_y, repeat_x, repeat_y, rot; - LLTextureCtrl* texture_ctrl = getChild("shinytexture control"); - texture_ctrl->setImageAssetID(material->getSpecularID()); - - if (!material->getSpecularID().isNull() && (shiny == SHINY_TEXTURE)) - { - material->getSpecularOffset(offset_x,offset_y); - material->getSpecularRepeat(repeat_x,repeat_y); - - if (identical_planar_texgen) - { - repeat_x *= 2.0f; - repeat_y *= 2.0f; - } - - rot = material->getSpecularRotation(); - getChild("shinyScaleU")->setValue(repeat_x); - getChild("shinyScaleV")->setValue(repeat_y); - getChild("shinyRot")->setValue(rot*RAD_TO_DEG); - getChild("shinyOffsetU")->setValue(offset_x); - getChild("shinyOffsetV")->setValue(offset_y); - getChild("glossiness")->setValue(material->getSpecularLightExponent()); - getChild("environment")->setValue(material->getEnvironmentIntensity()); - - updateShinyControls(!material->getSpecularID().isNull(), true); - } - - // Assert desired colorswatch color to match material AFTER updateShinyControls - // to avoid getting overwritten with the default on some UI state changes. - // - if (!material->getSpecularID().isNull()) - { - LLColorSwatchCtrl* shiny_swatch = getChild("shinycolorswatch"); - LLColor4 new_color = material->getSpecularLightColor(); - LLColor4 old_color = shiny_swatch->get(); - - shiny_swatch->setOriginal(new_color); - shiny_swatch->set(new_color, force_set_values || old_color != new_color || !editable); - } - - // Bumpy (normal) - texture_ctrl = getChild("bumpytexture control"); - texture_ctrl->setImageAssetID(material->getNormalID()); - - if (!material->getNormalID().isNull()) - { - material->getNormalOffset(offset_x,offset_y); - material->getNormalRepeat(repeat_x,repeat_y); - - if (identical_planar_texgen) - { - repeat_x *= 2.0f; - repeat_y *= 2.0f; - } - - rot = material->getNormalRotation(); - getChild("bumpyScaleU")->setValue(repeat_x); - getChild("bumpyScaleV")->setValue(repeat_y); - getChild("bumpyRot")->setValue(rot*RAD_TO_DEG); - getChild("bumpyOffsetU")->setValue(offset_x); - getChild("bumpyOffsetV")->setValue(offset_y); - - updateBumpyControls(!material->getNormalID().isNull(), true); - } - } - } - S32 selected_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount(); - bool single_volume = (selected_count == 1); - mMenuClipboardColor->setEnabled(editable && single_volume); - - // Set variable values for numeric expressions - LLCalc* calcp = LLCalc::getInstance(); - calcp->setVar(LLCalc::TEX_U_SCALE, childGetValue("TexScaleU").asReal()); - calcp->setVar(LLCalc::TEX_V_SCALE, childGetValue("TexScaleV").asReal()); - calcp->setVar(LLCalc::TEX_U_OFFSET, childGetValue("TexOffsetU").asReal()); - calcp->setVar(LLCalc::TEX_V_OFFSET, childGetValue("TexOffsetV").asReal()); - calcp->setVar(LLCalc::TEX_ROTATION, childGetValue("TexRot").asReal()); - calcp->setVar(LLCalc::TEX_TRANSPARENCY, childGetValue("ColorTrans").asReal()); - calcp->setVar(LLCalc::TEX_GLOW, childGetValue("glow").asReal()); - } - else - { - // Disable all UICtrls - clearCtrls(); - - // Disable non-UICtrls - LLTextureCtrl* pbr_ctrl = findChild("pbr_control"); - if (pbr_ctrl) - { - pbr_ctrl->setImageAssetID(LLUUID::null); - pbr_ctrl->setEnabled(false); - } - LLTextureCtrl* texture_ctrl = getChild("texture control"); - if (texture_ctrl) - { - texture_ctrl->setImageAssetID( LLUUID::null ); - texture_ctrl->setEnabled( false ); // this is a LLUICtrl, but we don't want it to have keyboard focus so we add it as a child, not a ctrl. -// texture_ctrl->setValid(false); - } - LLColorSwatchCtrl* mColorSwatch = getChild("colorswatch"); - if (mColorSwatch) - { - mColorSwatch->setEnabled( false ); - mColorSwatch->setFallbackImage(LLUI::getUIImage("locked_image.j2c") ); - mColorSwatch->setValid(false); - } - LLRadioGroup* radio_mat_type = getChild("radio_material_type"); - if (radio_mat_type) - { - radio_mat_type->setSelectedIndex(0); - } - getChildView("color trans")->setEnabled(false); - getChildView("rptctrl")->setEnabled(false); - getChildView("tex gen")->setEnabled(false); - getChildView("label shininess")->setEnabled(false); - getChildView("label bumpiness")->setEnabled(false); - getChildView("button align")->setEnabled(false); - getChildView("pbr_from_inventory")->setEnabled(false); - getChildView("edit_selected_pbr")->setEnabled(false); - getChildView("save_selected_pbr")->setEnabled(false); - - updateVisibility(); - - // Set variable values for numeric expressions - LLCalc* calcp = LLCalc::getInstance(); - calcp->clearVar(LLCalc::TEX_U_SCALE); - calcp->clearVar(LLCalc::TEX_V_SCALE); - calcp->clearVar(LLCalc::TEX_U_OFFSET); - calcp->clearVar(LLCalc::TEX_V_OFFSET); - calcp->clearVar(LLCalc::TEX_ROTATION); - calcp->clearVar(LLCalc::TEX_TRANSPARENCY); - calcp->clearVar(LLCalc::TEX_GLOW); - } -} - -// One-off listener that updates the build floater UI when the agent inventory adds or removes an item -class PBRPickerAgentListener : public LLInventoryObserver -{ -protected: - bool mChangePending = true; -public: - PBRPickerAgentListener() : LLInventoryObserver() - { - gInventory.addObserver(this); - } - - const bool isListening() - { - return mChangePending; - } - - void changed(U32 mask) override - { - if (!(mask & (ADD | REMOVE))) - { - return; - } - - if (gFloaterTools) - { - gFloaterTools->dirty(); - } - gInventory.removeObserver(this); - mChangePending = false; - } - - ~PBRPickerAgentListener() override - { - gInventory.removeObserver(this); - mChangePending = false; - } -}; - -// One-off listener that updates the build floater UI when the prim inventory updates -class PBRPickerObjectListener : public LLVOInventoryListener -{ -protected: - LLViewerObject* mObjectp; - bool mChangePending = true; -public: - - PBRPickerObjectListener(LLViewerObject* object) - : mObjectp(object) - { - registerVOInventoryListener(mObjectp, nullptr); - } - - const bool isListeningFor(const LLViewerObject* objectp) const - { - return mChangePending && (objectp == mObjectp); - } - - void inventoryChanged(LLViewerObject* object, - LLInventoryObject::object_list_t* inventory, - S32 serial_num, - void* user_data) override - { - if (gFloaterTools) - { - gFloaterTools->dirty(); - } - removeVOInventoryListener(); - mChangePending = false; - } - - ~PBRPickerObjectListener() - { - removeVOInventoryListener(); - mChangePending = false; - } -}; - -void LLPanelFace::updateUIGLTF(LLViewerObject* objectp, bool& has_pbr_material, bool& has_faces_without_pbr, bool force_set_values) -{ - has_pbr_material = false; - - bool has_pbr_capabilities = LLMaterialEditor::capabilitiesAvailable(); - bool identical_pbr = true; - const bool settable = has_pbr_capabilities && objectp->permModify() && !objectp->isPermanentEnforced(); - const bool editable = LLMaterialEditor::canModifyObjectsMaterial(); - const bool saveable = LLMaterialEditor::canSaveObjectsMaterial(); - - // pbr material - LLTextureCtrl* pbr_ctrl = findChild("pbr_control"); - LLUUID pbr_id; - if (pbr_ctrl) - { - LLSelectedTE::getPbrMaterialId(pbr_id, identical_pbr, has_pbr_material, has_faces_without_pbr); - - pbr_ctrl->setTentative(!identical_pbr); - pbr_ctrl->setEnabled(settable); - pbr_ctrl->setImageAssetID(pbr_id); - - if (objectp->isAttachment()) - { - pbr_ctrl->setFilterPermissionMasks(PERM_COPY | PERM_TRANSFER | PERM_MODIFY); - } - else - { - pbr_ctrl->setImmediateFilterPermMask(PERM_NONE); - } - } - - getChildView("pbr_from_inventory")->setEnabled(settable); - getChildView("edit_selected_pbr")->setEnabled(editable && !has_faces_without_pbr); - getChildView("save_selected_pbr")->setEnabled(saveable && identical_pbr); - if (objectp->isInventoryPending()) - { - // Reuse the same listener when possible - if (!mVOInventoryListener || !mVOInventoryListener->isListeningFor(objectp)) - { - mVOInventoryListener = std::make_unique(objectp); - } - } - else - { - mVOInventoryListener = nullptr; - } - if (!identical_pbr || pbr_id.isNull() || pbr_id == LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID) - { - mAgentInventoryListener = nullptr; - } - else - { - if (!mAgentInventoryListener || !mAgentInventoryListener->isListening()) - { - mAgentInventoryListener = std::make_unique(); - } - } - - const bool show_pbr = mComboMatMedia->getCurrentIndex() == MATMEDIA_PBR && mComboMatMedia->getEnabled(); - if (show_pbr) - { - const bool new_state = has_pbr_capabilities && has_pbr_material && !has_faces_without_pbr; - - LLUICtrl* gltfCtrlTextureScaleU = getChild("gltfTextureScaleU"); - LLUICtrl* gltfCtrlTextureScaleV = getChild("gltfTextureScaleV"); - LLUICtrl* gltfCtrlTextureRotation = getChild("gltfTextureRotation"); - LLUICtrl* gltfCtrlTextureOffsetU = getChild("gltfTextureOffsetU"); - LLUICtrl* gltfCtrlTextureOffsetV = getChild("gltfTextureOffsetV"); - - gltfCtrlTextureScaleU->setEnabled(new_state); - gltfCtrlTextureScaleV->setEnabled(new_state); - gltfCtrlTextureRotation->setEnabled(new_state); - gltfCtrlTextureOffsetU->setEnabled(new_state); - gltfCtrlTextureOffsetV->setEnabled(new_state); - - // Control values will be set once per frame in - // setMaterialOverridesFromSelection - sMaterialOverrideSelection.setDirty(); - } -} - -void LLPanelFace::updateVisibilityGLTF(LLViewerObject* objectp /*= nullptr */) -{ - const bool show_pbr = mComboMatMedia->getCurrentIndex() == MATMEDIA_PBR && mComboMatMedia->getEnabled(); - const bool inventory_pending = objectp && objectp->isInventoryPending(); - - LLRadioGroup* radio_pbr_type = findChild("radio_pbr_type"); - radio_pbr_type->setVisible(show_pbr); - - const U32 pbr_type = radio_pbr_type->getSelectedIndex(); - const bool show_pbr_render_material_id = show_pbr && (pbr_type == PBRTYPE_RENDER_MATERIAL_ID); - - getChildView("pbr_control")->setVisible(show_pbr_render_material_id); - - getChildView("pbr_from_inventory")->setVisible(show_pbr_render_material_id); - getChildView("edit_selected_pbr")->setVisible(show_pbr_render_material_id && !inventory_pending); - getChildView("save_selected_pbr")->setVisible(show_pbr_render_material_id && !inventory_pending); - getChildView("material_permissions_loading_label")->setVisible(show_pbr_render_material_id && inventory_pending); - - getChildView("gltfTextureScaleU")->setVisible(show_pbr); - getChildView("gltfTextureScaleV")->setVisible(show_pbr); - getChildView("gltfTextureRotation")->setVisible(show_pbr); - getChildView("gltfTextureOffsetU")->setVisible(show_pbr); - getChildView("gltfTextureOffsetV")->setVisible(show_pbr); -} - -void LLPanelFace::updateCopyTexButton() -{ - LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); - mMenuClipboardTexture->setEnabled(objectp && objectp->getPCode() == LL_PCODE_VOLUME && objectp->permModify() - && !objectp->isPermanentEnforced() && !objectp->isInventoryPending() - && (LLSelectMgr::getInstance()->getSelection()->getObjectCount() == 1) - && LLMaterialEditor::canClipboardObjectsMaterial()); - std::string tooltip = (objectp && objectp->isInventoryPending()) ? LLTrans::getString("LoadingContents") : getString("paste_options"); - mMenuClipboardTexture->setToolTip(tooltip); -} - -void LLPanelFace::refresh() -{ - LL_DEBUGS("Materials") << LL_ENDL; - getState(); -} - -void LLPanelFace::refreshMedia() -{ - LLObjectSelectionHandle selected_objects = LLSelectMgr::getInstance()->getSelection(); - LLViewerObject* first_object = selected_objects->getFirstObject(); - - if (!(first_object - && first_object->getPCode() == LL_PCODE_VOLUME - && first_object->permModify() - )) - { - getChildView("add_media")->setEnabled(false); - mTitleMediaText->clear(); - clearMediaSettings(); - return; - } - - std::string url = first_object->getRegion()->getCapability("ObjectMedia"); - bool has_media_capability = (!url.empty()); - - if (!has_media_capability) - { - getChildView("add_media")->setEnabled(false); - LL_WARNS("LLFloaterToolsMedia") << "Media not enabled (no capability) in this region!" << LL_ENDL; - clearMediaSettings(); - return; - } - - bool is_nonpermanent_enforced = (LLSelectMgr::getInstance()->getSelection()->getFirstRootNode() - && LLSelectMgr::getInstance()->selectGetRootsNonPermanentEnforced()) - || LLSelectMgr::getInstance()->selectGetNonPermanentEnforced(); - bool editable = is_nonpermanent_enforced && (first_object->permModify() || selectedMediaEditable()); - - // Check modify permissions and whether any selected objects are in - // the process of being fetched. If they are, then we're not editable - if (editable) - { - LLObjectSelection::iterator iter = selected_objects->begin(); - LLObjectSelection::iterator end = selected_objects->end(); - for (; iter != end; ++iter) - { - LLSelectNode* node = *iter; - LLVOVolume* object = dynamic_cast(node->getObject()); - if (NULL != object) - { - if (!object->permModify()) - { - LL_INFOS("LLFloaterToolsMedia") - << "Selection not editable due to lack of modify permissions on object id " - << object->getID() << LL_ENDL; - - editable = false; - break; - } - } - } - } - - // Media settings - bool bool_has_media = false; - struct media_functor : public LLSelectedTEGetFunctor - { - bool get(LLViewerObject* object, S32 face) - { - LLTextureEntry *te = object->getTE(face); - if (te) - { - return te->hasMedia(); - } - return false; - } - } func; - - - // check if all faces have media(or, all dont have media) - LLFloaterMediaSettings::getInstance()->mIdenticalHasMediaInfo = selected_objects->getSelectedTEValue(&func, bool_has_media); - - const LLMediaEntry default_media_data; - - struct functor_getter_media_data : public LLSelectedTEGetFunctor< LLMediaEntry> - { - functor_getter_media_data(const LLMediaEntry& entry) : mMediaEntry(entry) {} - - LLMediaEntry get(LLViewerObject* object, S32 face) - { - if (object) - if (object->getTE(face)) - if (object->getTE(face)->getMediaData()) - return *(object->getTE(face)->getMediaData()); - return mMediaEntry; - }; - - const LLMediaEntry& mMediaEntry; - - } func_media_data(default_media_data); - - LLMediaEntry media_data_get; - LLFloaterMediaSettings::getInstance()->mMultipleMedia = !(selected_objects->getSelectedTEValue(&func_media_data, media_data_get)); - - std::string multi_media_info_str = LLTrans::getString("Multiple Media"); - std::string media_title = ""; - // update UI depending on whether "object" (prim or face) has media - // and whether or not you are allowed to edit it. - - getChildView("add_media")->setEnabled(editable); - // IF all the faces have media (or all dont have media) - if (LLFloaterMediaSettings::getInstance()->mIdenticalHasMediaInfo) - { - // TODO: get media title and set it. - mTitleMediaText->clear(); - // if identical is set, all faces are same (whether all empty or has the same media) - if (!(LLFloaterMediaSettings::getInstance()->mMultipleMedia)) - { - // Media data is valid - if (media_data_get != default_media_data) - { - // initial media title is the media URL (until we get the name) - media_title = media_data_get.getHomeURL(); - } - // else all faces might be empty. - } - else // there' re Different Medias' been set on on the faces. - { - media_title = multi_media_info_str; - } - - getChildView("delete_media")->setEnabled(bool_has_media && editable); - // TODO: display a list of all media on the face - use 'identical' flag - } - else // not all face has media but at least one does. - { - // seleted faces have not identical value - LLFloaterMediaSettings::getInstance()->mMultipleValidMedia = selected_objects->isMultipleTEValue(&func_media_data, default_media_data); - - if (LLFloaterMediaSettings::getInstance()->mMultipleValidMedia) - { - media_title = multi_media_info_str; - } - else - { - // Media data is valid - if (media_data_get != default_media_data) - { - // initial media title is the media URL (until we get the name) - media_title = media_data_get.getHomeURL(); - } - } - - getChildView("delete_media")->setEnabled(true); - } - - U32 materials_media = mComboMatMedia->getCurrentIndex(); - if (materials_media == MATMEDIA_MEDIA) - { - // currently displaying media info, navigateTo and update title - navigateToTitleMedia(media_title); - } - else - { - // Media can be heavy, don't keep it around - // MAC specific: MAC doesn't support setVolume(0) so if not - // unloaded, it might keep playing audio until user closes editor - unloadMedia(); - mNeedMediaTitle = false; - } - - mTitleMediaText->setText(media_title); - - // load values for media settings - updateMediaSettings(); - - LLFloaterMediaSettings::initValues(mMediaSettings, editable); -} - -void LLPanelFace::unloadMedia() -{ - // destroy media source used to grab media title - if (mTitleMedia) - mTitleMedia->unloadMediaSource(); -} - -// static -void LLPanelFace::onMaterialOverrideReceived(const LLUUID& object_id, S32 side) -{ - sMaterialOverrideSelection.onSelectedObjectUpdated(object_id, side); -} - -////////////////////////////////////////////////////////////////////////////// -// -void LLPanelFace::navigateToTitleMedia( const std::string url ) -{ - std::string multi_media_info_str = LLTrans::getString("Multiple Media"); - if (url.empty() || multi_media_info_str == url) - { - // nothing to show - mNeedMediaTitle = false; - } - else if (mTitleMedia) - { - LLPluginClassMedia* media_plugin = mTitleMedia->getMediaPlugin(); - // check if url changed or if we need a new media source - if (mTitleMedia->getCurrentNavUrl() != url || media_plugin == NULL) - { - mTitleMedia->navigateTo( url ); - - LLViewerMediaImpl* impl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mTitleMedia->getTextureID()); - if (impl) - { - // if it's a page with a movie, we don't want to hear it - impl->setVolume(0); - }; - } - - // flag that we need to update the title (even if no request were made) - mNeedMediaTitle = true; - } -} - -bool LLPanelFace::selectedMediaEditable() -{ - U32 owner_mask_on; - U32 owner_mask_off; - U32 valid_owner_perms = LLSelectMgr::getInstance()->selectGetPerm(PERM_OWNER, - &owner_mask_on, &owner_mask_off); - U32 group_mask_on; - U32 group_mask_off; - U32 valid_group_perms = LLSelectMgr::getInstance()->selectGetPerm(PERM_GROUP, - &group_mask_on, &group_mask_off); - U32 everyone_mask_on; - U32 everyone_mask_off; - S32 valid_everyone_perms = LLSelectMgr::getInstance()->selectGetPerm(PERM_EVERYONE, - &everyone_mask_on, &everyone_mask_off); - - bool selected_Media_editable = false; - - // if perms we got back are valid - if (valid_owner_perms && - valid_group_perms && - valid_everyone_perms) - { - - if ((owner_mask_on & PERM_MODIFY) || - (group_mask_on & PERM_MODIFY) || - (everyone_mask_on & PERM_MODIFY)) - { - selected_Media_editable = true; - } - else - // user is NOT allowed to press the RESET button - { - selected_Media_editable = false; - }; - }; - - return selected_Media_editable; -} - -void LLPanelFace::clearMediaSettings() -{ - LLFloaterMediaSettings::clearValues(false); -} - -void LLPanelFace::updateMediaSettings() -{ - bool identical(false); - std::string base_key(""); - std::string value_str(""); - int value_int = 0; - bool value_bool = false; - LLObjectSelectionHandle selected_objects = LLSelectMgr::getInstance()->getSelection(); - // TODO: (CP) refactor this using something clever or boost or both !! - - const LLMediaEntry default_media_data; - - // controls - U8 value_u8 = default_media_data.getControls(); - struct functor_getter_controls : public LLSelectedTEGetFunctor< U8 > - { - functor_getter_controls(const LLMediaEntry &entry) : mMediaEntry(entry) {} - - U8 get(LLViewerObject* object, S32 face) - { - if (object) - if (object->getTE(face)) - if (object->getTE(face)->getMediaData()) - return object->getTE(face)->getMediaData()->getControls(); - return mMediaEntry.getControls(); - }; - - const LLMediaEntry &mMediaEntry; - - } func_controls(default_media_data); - identical = selected_objects->getSelectedTEValue(&func_controls, value_u8); - base_key = std::string(LLMediaEntry::CONTROLS_KEY); - mMediaSettings[base_key] = value_u8; - mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; - - // First click (formerly left click) - value_bool = default_media_data.getFirstClickInteract(); - struct functor_getter_first_click : public LLSelectedTEGetFunctor< bool > - { - functor_getter_first_click(const LLMediaEntry& entry) : mMediaEntry(entry) {} - - bool get(LLViewerObject* object, S32 face) - { - if (object) - if (object->getTE(face)) - if (object->getTE(face)->getMediaData()) - return object->getTE(face)->getMediaData()->getFirstClickInteract(); - return mMediaEntry.getFirstClickInteract(); - }; - - const LLMediaEntry &mMediaEntry; - - } func_first_click(default_media_data); - identical = selected_objects->getSelectedTEValue(&func_first_click, value_bool); - base_key = std::string(LLMediaEntry::FIRST_CLICK_INTERACT_KEY); - mMediaSettings[base_key] = value_bool; - mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; - - // Home URL - value_str = default_media_data.getHomeURL(); - struct functor_getter_home_url : public LLSelectedTEGetFunctor< std::string > - { - functor_getter_home_url(const LLMediaEntry& entry) : mMediaEntry(entry) {} - - std::string get(LLViewerObject* object, S32 face) - { - if (object) - if (object->getTE(face)) - if (object->getTE(face)->getMediaData()) - return object->getTE(face)->getMediaData()->getHomeURL(); - return mMediaEntry.getHomeURL(); - }; - - const LLMediaEntry &mMediaEntry; - - } func_home_url(default_media_data); - identical = selected_objects->getSelectedTEValue(&func_home_url, value_str); - base_key = std::string(LLMediaEntry::HOME_URL_KEY); - mMediaSettings[base_key] = value_str; - mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; - - // Current URL - value_str = default_media_data.getCurrentURL(); - struct functor_getter_current_url : public LLSelectedTEGetFunctor< std::string > - { - functor_getter_current_url(const LLMediaEntry& entry) : mMediaEntry(entry) {} - - std::string get(LLViewerObject* object, S32 face) - { - if (object) - if (object->getTE(face)) - if (object->getTE(face)->getMediaData()) - return object->getTE(face)->getMediaData()->getCurrentURL(); - return mMediaEntry.getCurrentURL(); - }; - - const LLMediaEntry &mMediaEntry; - - } func_current_url(default_media_data); - identical = selected_objects->getSelectedTEValue(&func_current_url, value_str); - base_key = std::string(LLMediaEntry::CURRENT_URL_KEY); - mMediaSettings[base_key] = value_str; - mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; - - // Auto zoom - value_bool = default_media_data.getAutoZoom(); - struct functor_getter_auto_zoom : public LLSelectedTEGetFunctor< bool > - { - - functor_getter_auto_zoom(const LLMediaEntry& entry) : mMediaEntry(entry) {} - - bool get(LLViewerObject* object, S32 face) - { - if (object) - if (object->getTE(face)) - if (object->getTE(face)->getMediaData()) - return object->getTE(face)->getMediaData()->getAutoZoom(); - return mMediaEntry.getAutoZoom(); - }; - - const LLMediaEntry &mMediaEntry; - - } func_auto_zoom(default_media_data); - identical = selected_objects->getSelectedTEValue(&func_auto_zoom, value_bool); - base_key = std::string(LLMediaEntry::AUTO_ZOOM_KEY); - mMediaSettings[base_key] = value_bool; - mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; - - // Auto play - //value_bool = default_media_data.getAutoPlay(); - // set default to auto play true -- angela EXT-5172 - value_bool = true; - struct functor_getter_auto_play : public LLSelectedTEGetFunctor< bool > - { - functor_getter_auto_play(const LLMediaEntry& entry) : mMediaEntry(entry) {} - - bool get(LLViewerObject* object, S32 face) - { - if (object) - if (object->getTE(face)) - if (object->getTE(face)->getMediaData()) - return object->getTE(face)->getMediaData()->getAutoPlay(); - //return mMediaEntry.getAutoPlay(); set default to auto play true -- angela EXT-5172 - return true; - }; - - const LLMediaEntry &mMediaEntry; - - } func_auto_play(default_media_data); - identical = selected_objects->getSelectedTEValue(&func_auto_play, value_bool); - base_key = std::string(LLMediaEntry::AUTO_PLAY_KEY); - mMediaSettings[base_key] = value_bool; - mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; - - - // Auto scale - // set default to auto scale true -- angela EXT-5172 - //value_bool = default_media_data.getAutoScale(); - value_bool = true; - struct functor_getter_auto_scale : public LLSelectedTEGetFunctor< bool > - { - functor_getter_auto_scale(const LLMediaEntry& entry) : mMediaEntry(entry) {} - - bool get(LLViewerObject* object, S32 face) - { - if (object) - if (object->getTE(face)) - if (object->getTE(face)->getMediaData()) - return object->getTE(face)->getMediaData()->getAutoScale(); - // return mMediaEntry.getAutoScale(); set default to auto scale true -- angela EXT-5172 - return true; - }; - - const LLMediaEntry &mMediaEntry; - - } func_auto_scale(default_media_data); - identical = selected_objects->getSelectedTEValue(&func_auto_scale, value_bool); - base_key = std::string(LLMediaEntry::AUTO_SCALE_KEY); - mMediaSettings[base_key] = value_bool; - mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; - - // Auto loop - value_bool = default_media_data.getAutoLoop(); - struct functor_getter_auto_loop : public LLSelectedTEGetFunctor< bool > - { - functor_getter_auto_loop(const LLMediaEntry& entry) : mMediaEntry(entry) {} - - bool get(LLViewerObject* object, S32 face) - { - if (object) - if (object->getTE(face)) - if (object->getTE(face)->getMediaData()) - return object->getTE(face)->getMediaData()->getAutoLoop(); - return mMediaEntry.getAutoLoop(); - }; - - const LLMediaEntry &mMediaEntry; - - } func_auto_loop(default_media_data); - identical = selected_objects->getSelectedTEValue(&func_auto_loop, value_bool); - base_key = std::string(LLMediaEntry::AUTO_LOOP_KEY); - mMediaSettings[base_key] = value_bool; - mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; - - // width pixels (if not auto scaled) - value_int = default_media_data.getWidthPixels(); - struct functor_getter_width_pixels : public LLSelectedTEGetFunctor< int > - { - functor_getter_width_pixels(const LLMediaEntry& entry) : mMediaEntry(entry) {} - - int get(LLViewerObject* object, S32 face) - { - if (object) - if (object->getTE(face)) - if (object->getTE(face)->getMediaData()) - return object->getTE(face)->getMediaData()->getWidthPixels(); - return mMediaEntry.getWidthPixels(); - }; - - const LLMediaEntry &mMediaEntry; - - } func_width_pixels(default_media_data); - identical = selected_objects->getSelectedTEValue(&func_width_pixels, value_int); - base_key = std::string(LLMediaEntry::WIDTH_PIXELS_KEY); - mMediaSettings[base_key] = value_int; - mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; - - // height pixels (if not auto scaled) - value_int = default_media_data.getHeightPixels(); - struct functor_getter_height_pixels : public LLSelectedTEGetFunctor< int > - { - functor_getter_height_pixels(const LLMediaEntry& entry) : mMediaEntry(entry) {} - - int get(LLViewerObject* object, S32 face) - { - if (object) - if (object->getTE(face)) - if (object->getTE(face)->getMediaData()) - return object->getTE(face)->getMediaData()->getHeightPixels(); - return mMediaEntry.getHeightPixels(); - }; - - const LLMediaEntry &mMediaEntry; - - } func_height_pixels(default_media_data); - identical = selected_objects->getSelectedTEValue(&func_height_pixels, value_int); - base_key = std::string(LLMediaEntry::HEIGHT_PIXELS_KEY); - mMediaSettings[base_key] = value_int; - mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; - - // Enable Alt image - value_bool = default_media_data.getAltImageEnable(); - struct functor_getter_enable_alt_image : public LLSelectedTEGetFunctor< bool > - { - functor_getter_enable_alt_image(const LLMediaEntry& entry) : mMediaEntry(entry) {} - - bool get(LLViewerObject* object, S32 face) - { - if (object) - if (object->getTE(face)) - if (object->getTE(face)->getMediaData()) - return object->getTE(face)->getMediaData()->getAltImageEnable(); - return mMediaEntry.getAltImageEnable(); - }; - - const LLMediaEntry &mMediaEntry; - - } func_enable_alt_image(default_media_data); - identical = selected_objects->getSelectedTEValue(&func_enable_alt_image, value_bool); - base_key = std::string(LLMediaEntry::ALT_IMAGE_ENABLE_KEY); - mMediaSettings[base_key] = value_bool; - mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; - - // Perms - owner interact - value_bool = 0 != (default_media_data.getPermsInteract() & LLMediaEntry::PERM_OWNER); - struct functor_getter_perms_owner_interact : public LLSelectedTEGetFunctor< bool > - { - functor_getter_perms_owner_interact(const LLMediaEntry& entry) : mMediaEntry(entry) {} - - bool get(LLViewerObject* object, S32 face) - { - if (object) - if (object->getTE(face)) - if (object->getTE(face)->getMediaData()) - return (0 != (object->getTE(face)->getMediaData()->getPermsInteract() & LLMediaEntry::PERM_OWNER)); - return 0 != (mMediaEntry.getPermsInteract() & LLMediaEntry::PERM_OWNER); - }; - - const LLMediaEntry &mMediaEntry; - - } func_perms_owner_interact(default_media_data); - identical = selected_objects->getSelectedTEValue(&func_perms_owner_interact, value_bool); - base_key = std::string(LLPanelContents::PERMS_OWNER_INTERACT_KEY); - mMediaSettings[base_key] = value_bool; - mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; - - // Perms - owner control - value_bool = 0 != (default_media_data.getPermsControl() & LLMediaEntry::PERM_OWNER); - struct functor_getter_perms_owner_control : public LLSelectedTEGetFunctor< bool > - { - functor_getter_perms_owner_control(const LLMediaEntry& entry) : mMediaEntry(entry) {} - - bool get(LLViewerObject* object, S32 face) - { - if (object) - if (object->getTE(face)) - if (object->getTE(face)->getMediaData()) - return (0 != (object->getTE(face)->getMediaData()->getPermsControl() & LLMediaEntry::PERM_OWNER)); - return 0 != (mMediaEntry.getPermsControl() & LLMediaEntry::PERM_OWNER); - }; - - const LLMediaEntry &mMediaEntry; - - } func_perms_owner_control(default_media_data); - identical = selected_objects->getSelectedTEValue(&func_perms_owner_control, value_bool); - base_key = std::string(LLPanelContents::PERMS_OWNER_CONTROL_KEY); - mMediaSettings[base_key] = value_bool; - mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; - - // Perms - group interact - value_bool = 0 != (default_media_data.getPermsInteract() & LLMediaEntry::PERM_GROUP); - struct functor_getter_perms_group_interact : public LLSelectedTEGetFunctor< bool > - { - functor_getter_perms_group_interact(const LLMediaEntry& entry) : mMediaEntry(entry) {} - - bool get(LLViewerObject* object, S32 face) - { - if (object) - if (object->getTE(face)) - if (object->getTE(face)->getMediaData()) - return (0 != (object->getTE(face)->getMediaData()->getPermsInteract() & LLMediaEntry::PERM_GROUP)); - return 0 != (mMediaEntry.getPermsInteract() & LLMediaEntry::PERM_GROUP); - }; - - const LLMediaEntry &mMediaEntry; - - } func_perms_group_interact(default_media_data); - identical = selected_objects->getSelectedTEValue(&func_perms_group_interact, value_bool); - base_key = std::string(LLPanelContents::PERMS_GROUP_INTERACT_KEY); - mMediaSettings[base_key] = value_bool; - mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; - - // Perms - group control - value_bool = 0 != (default_media_data.getPermsControl() & LLMediaEntry::PERM_GROUP); - struct functor_getter_perms_group_control : public LLSelectedTEGetFunctor< bool > - { - functor_getter_perms_group_control(const LLMediaEntry& entry) : mMediaEntry(entry) {} - - bool get(LLViewerObject* object, S32 face) - { - if (object) - if (object->getTE(face)) - if (object->getTE(face)->getMediaData()) - return (0 != (object->getTE(face)->getMediaData()->getPermsControl() & LLMediaEntry::PERM_GROUP)); - return 0 != (mMediaEntry.getPermsControl() & LLMediaEntry::PERM_GROUP); - }; - - const LLMediaEntry &mMediaEntry; - - } func_perms_group_control(default_media_data); - identical = selected_objects->getSelectedTEValue(&func_perms_group_control, value_bool); - base_key = std::string(LLPanelContents::PERMS_GROUP_CONTROL_KEY); - mMediaSettings[base_key] = value_bool; - mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; - - // Perms - anyone interact - value_bool = 0 != (default_media_data.getPermsInteract() & LLMediaEntry::PERM_ANYONE); - struct functor_getter_perms_anyone_interact : public LLSelectedTEGetFunctor< bool > - { - functor_getter_perms_anyone_interact(const LLMediaEntry& entry) : mMediaEntry(entry) {} - - bool get(LLViewerObject* object, S32 face) - { - if (object) - if (object->getTE(face)) - if (object->getTE(face)->getMediaData()) - return (0 != (object->getTE(face)->getMediaData()->getPermsInteract() & LLMediaEntry::PERM_ANYONE)); - return 0 != (mMediaEntry.getPermsInteract() & LLMediaEntry::PERM_ANYONE); - }; - - const LLMediaEntry &mMediaEntry; - - } func_perms_anyone_interact(default_media_data); - identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue(&func_perms_anyone_interact, value_bool); - base_key = std::string(LLPanelContents::PERMS_ANYONE_INTERACT_KEY); - mMediaSettings[base_key] = value_bool; - mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; - - // Perms - anyone control - value_bool = 0 != (default_media_data.getPermsControl() & LLMediaEntry::PERM_ANYONE); - struct functor_getter_perms_anyone_control : public LLSelectedTEGetFunctor< bool > - { - functor_getter_perms_anyone_control(const LLMediaEntry& entry) : mMediaEntry(entry) {} - - bool get(LLViewerObject* object, S32 face) - { - if (object) - if (object->getTE(face)) - if (object->getTE(face)->getMediaData()) - return (0 != (object->getTE(face)->getMediaData()->getPermsControl() & LLMediaEntry::PERM_ANYONE)); - return 0 != (mMediaEntry.getPermsControl() & LLMediaEntry::PERM_ANYONE); - }; - - const LLMediaEntry &mMediaEntry; - - } func_perms_anyone_control(default_media_data); - identical = selected_objects->getSelectedTEValue(&func_perms_anyone_control, value_bool); - base_key = std::string(LLPanelContents::PERMS_ANYONE_CONTROL_KEY); - mMediaSettings[base_key] = value_bool; - mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; - - // security - whitelist enable - value_bool = default_media_data.getWhiteListEnable(); - struct functor_getter_whitelist_enable : public LLSelectedTEGetFunctor< bool > - { - functor_getter_whitelist_enable(const LLMediaEntry& entry) : mMediaEntry(entry) {} - - bool get(LLViewerObject* object, S32 face) - { - if (object) - if (object->getTE(face)) - if (object->getTE(face)->getMediaData()) - return object->getTE(face)->getMediaData()->getWhiteListEnable(); - return mMediaEntry.getWhiteListEnable(); - }; - - const LLMediaEntry &mMediaEntry; - - } func_whitelist_enable(default_media_data); - identical = selected_objects->getSelectedTEValue(&func_whitelist_enable, value_bool); - base_key = std::string(LLMediaEntry::WHITELIST_ENABLE_KEY); - mMediaSettings[base_key] = value_bool; - mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; - - // security - whitelist URLs - std::vector value_vector_str = default_media_data.getWhiteList(); - struct functor_getter_whitelist_urls : public LLSelectedTEGetFunctor< std::vector > - { - functor_getter_whitelist_urls(const LLMediaEntry& entry) : mMediaEntry(entry) {} - - std::vector get(LLViewerObject* object, S32 face) - { - if (object) - if (object->getTE(face)) - if (object->getTE(face)->getMediaData()) - return object->getTE(face)->getMediaData()->getWhiteList(); - return mMediaEntry.getWhiteList(); - }; - - const LLMediaEntry &mMediaEntry; - - } func_whitelist_urls(default_media_data); - identical = selected_objects->getSelectedTEValue(&func_whitelist_urls, value_vector_str); - base_key = std::string(LLMediaEntry::WHITELIST_KEY); - mMediaSettings[base_key].clear(); - std::vector< std::string >::iterator iter = value_vector_str.begin(); - while (iter != value_vector_str.end()) - { - std::string white_list_url = *iter; - mMediaSettings[base_key].append(white_list_url); - ++iter; - }; - - mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; -} - -void LLPanelFace::updateMediaTitle() -{ - // only get the media name if we need it - if (!mNeedMediaTitle) - return; - - // get plugin impl - LLPluginClassMedia* media_plugin = mTitleMedia->getMediaPlugin(); - if (media_plugin && mTitleMedia->getCurrentNavUrl() == media_plugin->getNavigateURI()) - { - // get the media name (asynchronous - must call repeatedly) - std::string media_title = media_plugin->getMediaName(); - - // only replace the title if what we get contains something - if (!media_title.empty()) - { - // update the UI widget - if (mTitleMediaText) - { - mTitleMediaText->setText(media_title); - - // stop looking for a title when we get one - mNeedMediaTitle = false; - }; - }; - }; -} - -// -// Static functions -// - -// static -F32 LLPanelFace::valueGlow(LLViewerObject* object, S32 face) -{ - return (F32)(object->getTE(face)->getGlow()); -} - - -void LLPanelFace::onCommitColor(const LLSD& data) -{ - sendColor(); -} - -void LLPanelFace::onCommitShinyColor(const LLSD& data) -{ - LLSelectedTEMaterial::setSpecularLightColor(this, getChild("shinycolorswatch")->get()); -} - -void LLPanelFace::onCommitAlpha(const LLSD& data) -{ - sendAlpha(); -} - -void LLPanelFace::onCancelColor(const LLSD& data) -{ - LLSelectMgr::getInstance()->selectionRevertColors(); -} - -void LLPanelFace::onCancelShinyColor(const LLSD& data) -{ - LLSelectMgr::getInstance()->selectionRevertShinyColors(); -} - -void LLPanelFace::onSelectColor(const LLSD& data) -{ - LLSelectMgr::getInstance()->saveSelectedObjectColors(); - sendColor(); -} - -void LLPanelFace::onSelectShinyColor(const LLSD& data) -{ - LLSelectedTEMaterial::setSpecularLightColor(this, getChild("shinycolorswatch")->get()); - LLSelectMgr::getInstance()->saveSelectedShinyColors(); -} - -// static -void LLPanelFace::onCommitMaterialsMedia(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - // Force to default states to side-step problems with menu contents - // and generally reflecting old state when switching tabs or objects - // - self->updateShinyControls(false,true); - self->updateBumpyControls(false,true); - self->updateUI(); - self->refreshMedia(); -} - -void LLPanelFace::updateVisibility(LLViewerObject* objectp /* = nullptr */) -{ - LLRadioGroup* radio_mat_type = findChild("radio_material_type"); - LLRadioGroup* radio_pbr_type = findChild("radio_pbr_type"); - LLComboBox* combo_shininess = findChild("combobox shininess"); - LLComboBox* combo_bumpiness = findChild("combobox bumpiness"); - if (!radio_mat_type || !radio_pbr_type || !mComboMatMedia || !combo_shininess || !combo_bumpiness) - { - LL_WARNS("Materials") << "Combo box not found...exiting." << LL_ENDL; - return; - } - U32 materials_media = mComboMatMedia->getCurrentIndex(); - U32 material_type = radio_mat_type->getSelectedIndex(); - bool show_media = (materials_media == MATMEDIA_MEDIA) && mComboMatMedia->getEnabled(); - bool show_material = materials_media == MATMEDIA_MATERIAL; - bool show_texture = (show_media || (show_material && (material_type == MATTYPE_DIFFUSE) && mComboMatMedia->getEnabled())); - bool show_bumpiness = show_material && (material_type == MATTYPE_NORMAL) && mComboMatMedia->getEnabled(); - bool show_shininess = show_material && (material_type == MATTYPE_SPECULAR) && mComboMatMedia->getEnabled(); - const bool show_pbr = mComboMatMedia->getCurrentIndex() == MATMEDIA_PBR && mComboMatMedia->getEnabled(); - const U32 pbr_type = findChild("radio_pbr_type")->getSelectedIndex(); - const LLGLTFMaterial::TextureInfo texture_info = texture_info_from_pbrtype(pbr_type); - const bool show_pbr_asset = show_pbr && texture_info == LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; - - radio_mat_type->setVisible(show_material); - - // Shared material controls - getChildView("checkbox_sync_settings")->setVisible(show_material || show_media); - getChildView("tex gen")->setVisible(show_material || show_media || show_pbr_asset); - getChildView("combobox texgen")->setVisible(show_material || show_media || show_pbr_asset); - getChildView("button align textures")->setVisible(show_material || show_media); - - // Media controls - mTitleMediaText->setVisible(show_media); - getChildView("add_media")->setVisible(show_media); - getChildView("delete_media")->setVisible(show_media); - getChildView("button align")->setVisible(show_media); - - // Diffuse texture controls - getChildView("texture control")->setVisible(show_texture && show_material); - getChildView("label alphamode")->setVisible(show_texture && show_material); - getChildView("combobox alphamode")->setVisible(show_texture && show_material); - getChildView("label maskcutoff")->setVisible(false); - getChildView("maskcutoff")->setVisible(false); - if (show_texture && show_material) - { - updateAlphaControls(); - } - // texture scale and position controls - getChildView("TexScaleU")->setVisible(show_texture); - getChildView("TexScaleV")->setVisible(show_texture); - getChildView("TexRot")->setVisible(show_texture); - getChildView("TexOffsetU")->setVisible(show_texture); - getChildView("TexOffsetV")->setVisible(show_texture); - - // Specular map controls - getChildView("shinytexture control")->setVisible(show_shininess); - getChildView("combobox shininess")->setVisible(show_shininess); - getChildView("label shininess")->setVisible(show_shininess); - getChildView("label glossiness")->setVisible(false); - getChildView("glossiness")->setVisible(false); - getChildView("label environment")->setVisible(false); - getChildView("environment")->setVisible(false); - getChildView("label shinycolor")->setVisible(false); - getChildView("shinycolorswatch")->setVisible(false); - if (show_shininess) - { - updateShinyControls(); - } - getChildView("shinyScaleU")->setVisible(show_shininess); - getChildView("shinyScaleV")->setVisible(show_shininess); - getChildView("shinyRot")->setVisible(show_shininess); - getChildView("shinyOffsetU")->setVisible(show_shininess); - getChildView("shinyOffsetV")->setVisible(show_shininess); - - // Normal map controls - if (show_bumpiness) - { - updateBumpyControls(); - } - getChildView("bumpytexture control")->setVisible(show_bumpiness); - getChildView("combobox bumpiness")->setVisible(show_bumpiness); - getChildView("label bumpiness")->setVisible(show_bumpiness); - getChildView("bumpyScaleU")->setVisible(show_bumpiness); - getChildView("bumpyScaleV")->setVisible(show_bumpiness); - getChildView("bumpyRot")->setVisible(show_bumpiness); - getChildView("bumpyOffsetU")->setVisible(show_bumpiness); - getChildView("bumpyOffsetV")->setVisible(show_bumpiness); - - getChild("rptctrl")->setVisible(show_material || show_media); - - // PBR controls - updateVisibilityGLTF(objectp); -} - -// static -void LLPanelFace::onCommitMaterialType(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - // Force to default states to side-step problems with menu contents - // and generally reflecting old state when switching tabs or objects - // - self->updateShinyControls(false,true); - self->updateBumpyControls(false,true); - self->updateUI(); -} - -// static -void LLPanelFace::onCommitPbrType(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*)userdata; - // Force to default states to side-step problems with menu contents - // and generally reflecting old state when switching tabs or objects - // - self->updateUI(); -} - -// static -void LLPanelFace::onCommitBump(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - - LLComboBox* mComboBumpiness = self->getChild("combobox bumpiness"); - if(!mComboBumpiness) - return; - - U32 bumpiness = mComboBumpiness->getCurrentIndex(); - - self->sendBump(bumpiness); -} - -// static -void LLPanelFace::onCommitTexGen(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - self->sendTexGen(); -} - -// static -void LLPanelFace::updateShinyControls(bool is_setting_texture, bool mess_with_shiny_combobox) -{ - LLTextureCtrl* texture_ctrl = getChild("shinytexture control"); - LLUUID shiny_texture_ID = texture_ctrl->getImageAssetID(); - LL_DEBUGS("Materials") << "Shiny texture selected: " << shiny_texture_ID << LL_ENDL; - LLComboBox* comboShiny = getChild("combobox shininess"); - - if(mess_with_shiny_combobox) - { - if (!comboShiny) - { - return; - } - if (!shiny_texture_ID.isNull() && is_setting_texture) - { - if (!comboShiny->itemExists(USE_TEXTURE)) - { - comboShiny->add(USE_TEXTURE); - } - comboShiny->setSimple(USE_TEXTURE); - } - else - { - if (comboShiny->itemExists(USE_TEXTURE)) - { - comboShiny->remove(SHINY_TEXTURE); - comboShiny->selectFirstItem(); - } - } - } - else - { - if (shiny_texture_ID.isNull() && comboShiny && comboShiny->itemExists(USE_TEXTURE)) - { - comboShiny->remove(SHINY_TEXTURE); - comboShiny->selectFirstItem(); - } - } - - - LLRadioGroup* radio_mat_type = getChild("radio_material_type"); - U32 materials_media = mComboMatMedia->getCurrentIndex(); - U32 material_type = radio_mat_type->getSelectedIndex(); - bool show_material = (materials_media == MATMEDIA_MATERIAL); - bool show_shininess = show_material && (material_type == MATTYPE_SPECULAR) && mComboMatMedia->getEnabled(); - U32 shiny_value = comboShiny->getCurrentIndex(); - bool show_shinyctrls = (shiny_value == SHINY_TEXTURE) && show_shininess; // Use texture - getChildView("label glossiness")->setVisible(show_shinyctrls); - getChildView("glossiness")->setVisible(show_shinyctrls); - getChildView("label environment")->setVisible(show_shinyctrls); - getChildView("environment")->setVisible(show_shinyctrls); - getChildView("label shinycolor")->setVisible(show_shinyctrls); - getChildView("shinycolorswatch")->setVisible(show_shinyctrls); -} - -// static -void LLPanelFace::updateBumpyControls(bool is_setting_texture, bool mess_with_combobox) -{ - LLTextureCtrl* texture_ctrl = getChild("bumpytexture control"); - LLUUID bumpy_texture_ID = texture_ctrl->getImageAssetID(); - LL_DEBUGS("Materials") << "texture: " << bumpy_texture_ID << (mess_with_combobox ? "" : " do not") << " update combobox" << LL_ENDL; - LLComboBox* comboBumpy = getChild("combobox bumpiness"); - if (!comboBumpy) - { - return; - } - - if (mess_with_combobox) - { - LLTextureCtrl* texture_ctrl = getChild("bumpytexture control"); - LLUUID bumpy_texture_ID = texture_ctrl->getImageAssetID(); - LL_DEBUGS("Materials") << "texture: " << bumpy_texture_ID << (mess_with_combobox ? "" : " do not") << " update combobox" << LL_ENDL; - - if (!bumpy_texture_ID.isNull() && is_setting_texture) - { - if (!comboBumpy->itemExists(USE_TEXTURE)) - { - comboBumpy->add(USE_TEXTURE); - } - comboBumpy->setSimple(USE_TEXTURE); - } - else - { - if (comboBumpy->itemExists(USE_TEXTURE)) - { - comboBumpy->remove(BUMPY_TEXTURE); - comboBumpy->selectFirstItem(); - } - } - } -} - -// static -void LLPanelFace::onCommitShiny(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - - - LLComboBox* mComboShininess = self->getChild("combobox shininess"); - if(!mComboShininess) - return; - - U32 shininess = mComboShininess->getCurrentIndex(); - - self->sendShiny(shininess); -} - -// static -void LLPanelFace::updateAlphaControls() -{ - LLComboBox* comboAlphaMode = getChild("combobox alphamode"); - if (!comboAlphaMode) - { - return; - } - U32 alpha_value = comboAlphaMode->getCurrentIndex(); - bool show_alphactrls = (alpha_value == ALPHAMODE_MASK); // Alpha masking - - U32 mat_media = MATMEDIA_MATERIAL; - if (mComboMatMedia) - { - mat_media = mComboMatMedia->getCurrentIndex(); - } - - U32 mat_type = MATTYPE_DIFFUSE; - LLRadioGroup* radio_mat_type = getChild("radio_material_type"); - if(radio_mat_type) - { - mat_type = radio_mat_type->getSelectedIndex(); - } - - show_alphactrls = show_alphactrls && (mat_media == MATMEDIA_MATERIAL); - show_alphactrls = show_alphactrls && (mat_type == MATTYPE_DIFFUSE); - - getChildView("label maskcutoff")->setVisible(show_alphactrls); - getChildView("maskcutoff")->setVisible(show_alphactrls); -} - -// static -void LLPanelFace::onCommitAlphaMode(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - self->updateAlphaControls(); - LLSelectedTEMaterial::setDiffuseAlphaMode(self,self->getCurrentDiffuseAlphaMode()); -} - -// static -void LLPanelFace::onCommitFullbright(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - self->sendFullbright(); -} - -// static -void LLPanelFace::onCommitGlow(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - self->sendGlow(); -} - -// static -bool LLPanelFace::onDragPbr(LLUICtrl*, LLInventoryItem* item) -{ - bool accept = true; - for (LLObjectSelection::root_iterator iter = LLSelectMgr::getInstance()->getSelection()->root_begin(); - iter != LLSelectMgr::getInstance()->getSelection()->root_end(); iter++) - { - LLSelectNode* node = *iter; - LLViewerObject* obj = node->getObject(); - if (!LLToolDragAndDrop::isInventoryDropAcceptable(obj, item)) - { - accept = false; - break; - } - } - return accept; -} - -void LLPanelFace::onCommitPbr(const LLSD& data) -{ - LLTextureCtrl* pbr_ctrl = findChild("pbr_control"); - if (!pbr_ctrl) return; - if (!pbr_ctrl->getTentative()) - { - // we grab the item id first, because we want to do a - // permissions check in the selection manager. ARGH! - LLUUID id = pbr_ctrl->getImageItemID(); - if (id.isNull()) - { - id = pbr_ctrl->getImageAssetID(); - } - if (!LLSelectMgr::getInstance()->selectionSetGLTFMaterial(id)) - { - // If failed to set material, refresh pbr_ctrl's value - refresh(); - } - } -} - -void LLPanelFace::onCancelPbr(const LLSD& data) -{ - LLSelectMgr::getInstance()->selectionRevertGLTFMaterials(); -} - -void LLPanelFace::onSelectPbr(const LLSD& data) -{ - LLSelectMgr::getInstance()->saveSelectedObjectTextures(); - - LLTextureCtrl* pbr_ctrl = findChild("pbr_control"); - if (!pbr_ctrl) return; - if (!pbr_ctrl->getTentative()) - { - // we grab the item id first, because we want to do a - // permissions check in the selection manager. ARGH! - LLUUID id = pbr_ctrl->getImageItemID(); - if (id.isNull()) - { - id = pbr_ctrl->getImageAssetID(); - } - if (!LLSelectMgr::getInstance()->selectionSetGLTFMaterial(id)) - { - refresh(); - } - } -} - -// static -bool LLPanelFace::onDragTexture(LLUICtrl*, LLInventoryItem* item) -{ - bool accept = true; - for (LLObjectSelection::root_iterator iter = LLSelectMgr::getInstance()->getSelection()->root_begin(); - iter != LLSelectMgr::getInstance()->getSelection()->root_end(); iter++) - { - LLSelectNode* node = *iter; - LLViewerObject* obj = node->getObject(); - if (!LLToolDragAndDrop::isInventoryDropAcceptable(obj, item)) - { - accept = false; - break; - } - } - return accept; -} - -void LLPanelFace::onCommitTexture( const LLSD& data ) -{ - add(LLStatViewer::EDIT_TEXTURE, 1); - sendTexture(); -} - -void LLPanelFace::onCancelTexture(const LLSD& data) -{ - LLSelectMgr::getInstance()->selectionRevertTextures(); -} - -void LLPanelFace::onSelectTexture(const LLSD& data) -{ - LLSelectMgr::getInstance()->saveSelectedObjectTextures(); - sendTexture(); - - LLGLenum image_format; - bool identical_image_format = false; - LLSelectedTE::getImageFormat(image_format, identical_image_format); - - LLCtrlSelectionInterface* combobox_alphamode = - childGetSelectionInterface("combobox alphamode"); - - U32 alpha_mode = LLMaterial::DIFFUSE_ALPHA_MODE_NONE; - if (combobox_alphamode) - { - switch (image_format) - { - case GL_RGBA: - case GL_ALPHA: - { - alpha_mode = LLMaterial::DIFFUSE_ALPHA_MODE_BLEND; - } - break; - - case GL_RGB: break; - default: - { - LL_WARNS() << "Unexpected tex format in LLPanelFace...resorting to no alpha" << LL_ENDL; - } - break; - } - - combobox_alphamode->selectNthItem(alpha_mode); - } - LLSelectedTEMaterial::setDiffuseAlphaMode(this, getCurrentDiffuseAlphaMode()); -} - -void LLPanelFace::onCloseTexturePicker(const LLSD& data) -{ - LL_DEBUGS("Materials") << data << LL_ENDL; - updateUI(); -} - -void LLPanelFace::onCommitSpecularTexture( const LLSD& data ) -{ - LL_DEBUGS("Materials") << data << LL_ENDL; - sendShiny(SHINY_TEXTURE); -} - -void LLPanelFace::onCommitNormalTexture( const LLSD& data ) -{ - LL_DEBUGS("Materials") << data << LL_ENDL; - LLUUID nmap_id = getCurrentNormalMap(); - sendBump(nmap_id.isNull() ? 0 : BUMPY_TEXTURE); -} - -void LLPanelFace::onCancelSpecularTexture(const LLSD& data) -{ - U8 shiny = 0; - bool identical_shiny = false; - LLSelectedTE::getShiny(shiny, identical_shiny); - LLUUID spec_map_id = getChild("shinytexture control")->getImageAssetID(); - shiny = spec_map_id.isNull() ? shiny : SHINY_TEXTURE; - sendShiny(shiny); -} - -void LLPanelFace::onCancelNormalTexture(const LLSD& data) -{ - U8 bumpy = 0; - bool identical_bumpy = false; - LLSelectedTE::getBumpmap(bumpy, identical_bumpy); - LLUUID spec_map_id = getChild("bumpytexture control")->getImageAssetID(); - bumpy = spec_map_id.isNull() ? bumpy : BUMPY_TEXTURE; - sendBump(bumpy); -} - -void LLPanelFace::onSelectSpecularTexture(const LLSD& data) -{ - LL_DEBUGS("Materials") << data << LL_ENDL; - sendShiny(SHINY_TEXTURE); -} - -void LLPanelFace::onSelectNormalTexture(const LLSD& data) -{ - LL_DEBUGS("Materials") << data << LL_ENDL; - LLUUID nmap_id = getCurrentNormalMap(); - sendBump(nmap_id.isNull() ? 0 : BUMPY_TEXTURE); -} - -////////////////////////////////////////////////////////////////////////////// -// called when a user wants to edit existing media settings on a prim or prim face -// TODO: test if there is media on the item and only allow editing if present -void LLPanelFace::onClickBtnEditMedia(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*)userdata; - self->refreshMedia(); - LLFloaterReg::showInstance("media_settings"); -} - -////////////////////////////////////////////////////////////////////////////// -// called when a user wants to delete media from a prim or prim face -void LLPanelFace::onClickBtnDeleteMedia(LLUICtrl* ctrl, void* userdata) -{ - LLNotificationsUtil::add("DeleteMedia", LLSD(), LLSD(), deleteMediaConfirm); -} - -////////////////////////////////////////////////////////////////////////////// -// called when a user wants to add media to a prim or prim face -void LLPanelFace::onClickBtnAddMedia(LLUICtrl* ctrl, void* userdata) -{ - // check if multiple faces are selected - if (LLSelectMgr::getInstance()->getSelection()->isMultipleTESelected()) - { - LLPanelFace* self = (LLPanelFace*)userdata; - self->refreshMedia(); - LLNotificationsUtil::add("MultipleFacesSelected", LLSD(), LLSD(), multipleFacesSelectedConfirm); - } - else - { - onClickBtnEditMedia(ctrl, userdata); - } -} - -// static -bool LLPanelFace::deleteMediaConfirm(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - switch (option) - { - case 0: // "Yes" - LLSelectMgr::getInstance()->selectionSetMedia(0, LLSD()); - if (LLFloaterReg::instanceVisible("media_settings")) - { - LLFloaterReg::hideInstance("media_settings"); - } - break; - - case 1: // "No" - default: - break; - } - return false; -} - -// static -bool LLPanelFace::multipleFacesSelectedConfirm(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - switch (option) - { - case 0: // "Yes" - LLFloaterReg::showInstance("media_settings"); - break; - case 1: // "No" - default: - break; - } - return false; -} - -//static -void LLPanelFace::syncOffsetX(LLPanelFace* self, F32 offsetU) -{ - LLSelectedTEMaterial::setNormalOffsetX(self,offsetU); - LLSelectedTEMaterial::setSpecularOffsetX(self,offsetU); - self->getChild("TexOffsetU")->forceSetValue(offsetU); - self->sendTextureInfo(); -} - -//static -void LLPanelFace::syncOffsetY(LLPanelFace* self, F32 offsetV) -{ - LLSelectedTEMaterial::setNormalOffsetY(self,offsetV); - LLSelectedTEMaterial::setSpecularOffsetY(self,offsetV); - self->getChild("TexOffsetV")->forceSetValue(offsetV); - self->sendTextureInfo(); -} - -//static -void LLPanelFace::onCommitMaterialBumpyOffsetX(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - llassert_always(self); - - if (gSavedSettings.getBOOL("SyncMaterialSettings")) - { - syncOffsetX(self,self->getCurrentBumpyOffsetU()); - } - else - { - LLSelectedTEMaterial::setNormalOffsetX(self,self->getCurrentBumpyOffsetU()); - } - -} - -//static -void LLPanelFace::onCommitMaterialBumpyOffsetY(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - llassert_always(self); - - if (gSavedSettings.getBOOL("SyncMaterialSettings")) - { - syncOffsetY(self,self->getCurrentBumpyOffsetV()); - } - else - { - LLSelectedTEMaterial::setNormalOffsetY(self,self->getCurrentBumpyOffsetV()); - } -} - -//static -void LLPanelFace::onCommitMaterialShinyOffsetX(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - llassert_always(self); - - if (gSavedSettings.getBOOL("SyncMaterialSettings")) - { - syncOffsetX(self, self->getCurrentShinyOffsetU()); - } - else - { - LLSelectedTEMaterial::setSpecularOffsetX(self,self->getCurrentShinyOffsetU()); - } -} - -//static -void LLPanelFace::onCommitMaterialShinyOffsetY(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - llassert_always(self); - - if (gSavedSettings.getBOOL("SyncMaterialSettings")) - { - syncOffsetY(self,self->getCurrentShinyOffsetV()); - } - else - { - LLSelectedTEMaterial::setSpecularOffsetY(self,self->getCurrentShinyOffsetV()); - } -} - -//static -void LLPanelFace::syncRepeatX(LLPanelFace* self, F32 scaleU) -{ - LLSelectedTEMaterial::setNormalRepeatX(self,scaleU); - LLSelectedTEMaterial::setSpecularRepeatX(self,scaleU); - self->sendTextureInfo(); -} - -//static -void LLPanelFace::syncRepeatY(LLPanelFace* self, F32 scaleV) -{ - LLSelectedTEMaterial::setNormalRepeatY(self,scaleV); - LLSelectedTEMaterial::setSpecularRepeatY(self,scaleV); - self->sendTextureInfo(); -} - -//static -void LLPanelFace::onCommitMaterialBumpyScaleX(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - llassert_always(self); - F32 bumpy_scale_u = self->getCurrentBumpyScaleU(); - if (self->isIdenticalPlanarTexgen()) - { - bumpy_scale_u *= 0.5f; - } - - if (gSavedSettings.getBOOL("SyncMaterialSettings")) - { - self->getChild("TexScaleU")->forceSetValue(self->getCurrentBumpyScaleU()); - syncRepeatX(self, bumpy_scale_u); - } - else - { - LLSelectedTEMaterial::setNormalRepeatX(self,bumpy_scale_u); - } -} - -//static -void LLPanelFace::onCommitMaterialBumpyScaleY(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - llassert_always(self); - F32 bumpy_scale_v = self->getCurrentBumpyScaleV(); - if (self->isIdenticalPlanarTexgen()) - { - bumpy_scale_v *= 0.5f; - } - - - if (gSavedSettings.getBOOL("SyncMaterialSettings")) - { - self->getChild("TexScaleV")->forceSetValue(self->getCurrentBumpyScaleV()); - syncRepeatY(self, bumpy_scale_v); - } - else - { - LLSelectedTEMaterial::setNormalRepeatY(self,bumpy_scale_v); - } -} - -//static -void LLPanelFace::onCommitMaterialShinyScaleX(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - llassert_always(self); - F32 shiny_scale_u = self->getCurrentShinyScaleU(); - if (self->isIdenticalPlanarTexgen()) - { - shiny_scale_u *= 0.5f; - } - - if (gSavedSettings.getBOOL("SyncMaterialSettings")) - { - self->getChild("TexScaleU")->forceSetValue(self->getCurrentShinyScaleU()); - syncRepeatX(self, shiny_scale_u); - } - else - { - LLSelectedTEMaterial::setSpecularRepeatX(self,shiny_scale_u); - } -} - -//static -void LLPanelFace::onCommitMaterialShinyScaleY(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - llassert_always(self); - F32 shiny_scale_v = self->getCurrentShinyScaleV(); - if (self->isIdenticalPlanarTexgen()) - { - shiny_scale_v *= 0.5f; - } - - if (gSavedSettings.getBOOL("SyncMaterialSettings")) - { - self->getChild("TexScaleV")->forceSetValue(self->getCurrentShinyScaleV()); - syncRepeatY(self, shiny_scale_v); - } - else - { - LLSelectedTEMaterial::setSpecularRepeatY(self,shiny_scale_v); - } -} - -//static -void LLPanelFace::syncMaterialRot(LLPanelFace* self, F32 rot, int te) -{ - LLSelectedTEMaterial::setNormalRotation(self,rot * DEG_TO_RAD, te); - LLSelectedTEMaterial::setSpecularRotation(self,rot * DEG_TO_RAD, te); - self->sendTextureInfo(); -} - -//static -void LLPanelFace::onCommitMaterialBumpyRot(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - llassert_always(self); - - if (gSavedSettings.getBOOL("SyncMaterialSettings")) - { - self->getChild("TexRot")->forceSetValue(self->getCurrentBumpyRot()); - syncMaterialRot(self, self->getCurrentBumpyRot()); - } - else - { - if ((bool)self->childGetValue("checkbox planar align").asBoolean()) - { - LLFace* last_face = NULL; - bool identical_face = false; - LLSelectedTE::getFace(last_face, identical_face); - LLPanelFaceSetAlignedTEFunctor setfunc(self, last_face); - LLSelectMgr::getInstance()->getSelection()->applyToTEs(&setfunc); - } - else - { - LLSelectedTEMaterial::setNormalRotation(self, self->getCurrentBumpyRot() * DEG_TO_RAD); - } - } -} - -//static -void LLPanelFace::onCommitMaterialShinyRot(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - llassert_always(self); - - if (gSavedSettings.getBOOL("SyncMaterialSettings")) - { - self->getChild("TexRot")->forceSetValue(self->getCurrentShinyRot()); - syncMaterialRot(self, self->getCurrentShinyRot()); - } - else - { - if ((bool)self->childGetValue("checkbox planar align").asBoolean()) - { - LLFace* last_face = NULL; - bool identical_face = false; - LLSelectedTE::getFace(last_face, identical_face); - LLPanelFaceSetAlignedTEFunctor setfunc(self, last_face); - LLSelectMgr::getInstance()->getSelection()->applyToTEs(&setfunc); - } - else - { - LLSelectedTEMaterial::setSpecularRotation(self, self->getCurrentShinyRot() * DEG_TO_RAD); - } - } -} - -//static -void LLPanelFace::onCommitMaterialGloss(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - llassert_always(self); - LLSelectedTEMaterial::setSpecularLightExponent(self,self->getCurrentGlossiness()); -} - -//static -void LLPanelFace::onCommitMaterialEnv(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - llassert_always(self); - LLSelectedTEMaterial::setEnvironmentIntensity(self,self->getCurrentEnvIntensity()); -} - -//static -void LLPanelFace::onCommitMaterialMaskCutoff(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - LLSelectedTEMaterial::setAlphaMaskCutoff(self,self->getCurrentAlphaMaskCutoff()); -} - -// static -void LLPanelFace::onCommitTextureInfo( LLUICtrl* ctrl, void* userdata ) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - self->sendTextureInfo(); - // vertical scale and repeats per meter depends on each other, so force set on changes - self->updateUI(true); -} - -// static -void LLPanelFace::onCommitTextureScaleX( LLUICtrl* ctrl, void* userdata ) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - if (gSavedSettings.getBOOL("SyncMaterialSettings")) - { - F32 bumpy_scale_u = self->getChild("TexScaleU")->getValue().asReal(); - if (self->isIdenticalPlanarTexgen()) - { - bumpy_scale_u *= 0.5f; - } - syncRepeatX(self, bumpy_scale_u); - } - else - { - self->sendTextureInfo(); - } - self->updateUI(true); -} - -// static -void LLPanelFace::onCommitTextureScaleY( LLUICtrl* ctrl, void* userdata ) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - if (gSavedSettings.getBOOL("SyncMaterialSettings")) - { - F32 bumpy_scale_v = self->getChild("TexScaleV")->getValue().asReal(); - if (self->isIdenticalPlanarTexgen()) - { - bumpy_scale_v *= 0.5f; - } - syncRepeatY(self, bumpy_scale_v); - } - else - { - self->sendTextureInfo(); - } - self->updateUI(true); -} - -// static -void LLPanelFace::onCommitTextureRot( LLUICtrl* ctrl, void* userdata ) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - - if (gSavedSettings.getBOOL("SyncMaterialSettings")) - { - syncMaterialRot(self, self->getChild("TexRot")->getValue().asReal()); - } - else - { - self->sendTextureInfo(); - } - self->updateUI(true); -} - -// static -void LLPanelFace::onCommitTextureOffsetX( LLUICtrl* ctrl, void* userdata ) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - if (gSavedSettings.getBOOL("SyncMaterialSettings")) - { - syncOffsetX(self, self->getChild("TexOffsetU")->getValue().asReal()); - } - else - { - self->sendTextureInfo(); - } - self->updateUI(true); -} - -// static -void LLPanelFace::onCommitTextureOffsetY( LLUICtrl* ctrl, void* userdata ) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - if (gSavedSettings.getBOOL("SyncMaterialSettings")) - { - syncOffsetY(self, self->getChild("TexOffsetV")->getValue().asReal()); - } - else - { - self->sendTextureInfo(); - } - self->updateUI(true); -} - -// Commit the number of repeats per meter -// static -void LLPanelFace::onCommitRepeatsPerMeter(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - - LLUICtrl* repeats_ctrl = self->getChild("rptctrl"); - - U32 materials_media = self->mComboMatMedia->getCurrentIndex(); - U32 material_type = 0; - if (materials_media == MATMEDIA_PBR) - { - LLRadioGroup* radio_mat_type = self->getChild("radio_pbr_type"); - material_type = radio_mat_type->getSelectedIndex(); - } - if (materials_media == MATMEDIA_MATERIAL) - { - LLRadioGroup* radio_mat_type = self->getChild("radio_material_type"); - material_type = radio_mat_type->getSelectedIndex(); - } - - F32 repeats_per_meter = repeats_ctrl->getValue().asReal(); - - F32 obj_scale_s = 1.0f; - F32 obj_scale_t = 1.0f; - - bool identical_scale_s = false; - bool identical_scale_t = false; - - LLSelectedTE::getObjectScaleS(obj_scale_s, identical_scale_s); - LLSelectedTE::getObjectScaleS(obj_scale_t, identical_scale_t); - - LLUICtrl* bumpy_scale_u = self->getChild("bumpyScaleU"); - LLUICtrl* bumpy_scale_v = self->getChild("bumpyScaleV"); - LLUICtrl* shiny_scale_u = self->getChild("shinyScaleU"); - LLUICtrl* shiny_scale_v = self->getChild("shinyScaleV"); - - if (gSavedSettings.getBOOL("SyncMaterialSettings")) - { - LLSelectMgr::getInstance()->selectionTexScaleAutofit( repeats_per_meter ); - - bumpy_scale_u->setValue(obj_scale_s * repeats_per_meter); - bumpy_scale_v->setValue(obj_scale_t * repeats_per_meter); - - LLSelectedTEMaterial::setNormalRepeatX(self,obj_scale_s * repeats_per_meter); - LLSelectedTEMaterial::setNormalRepeatY(self,obj_scale_t * repeats_per_meter); - - shiny_scale_u->setValue(obj_scale_s * repeats_per_meter); - shiny_scale_v->setValue(obj_scale_t * repeats_per_meter); - - LLSelectedTEMaterial::setSpecularRepeatX(self,obj_scale_s * repeats_per_meter); - LLSelectedTEMaterial::setSpecularRepeatY(self,obj_scale_t * repeats_per_meter); - } - else - { - switch (material_type) - { - case MATTYPE_DIFFUSE: - { - LLSelectMgr::getInstance()->selectionTexScaleAutofit( repeats_per_meter ); - } - break; - - case MATTYPE_NORMAL: - { - bumpy_scale_u->setValue(obj_scale_s * repeats_per_meter); - bumpy_scale_v->setValue(obj_scale_t * repeats_per_meter); - - LLSelectedTEMaterial::setNormalRepeatX(self,obj_scale_s * repeats_per_meter); - LLSelectedTEMaterial::setNormalRepeatY(self,obj_scale_t * repeats_per_meter); - } - break; - - case MATTYPE_SPECULAR: - { - shiny_scale_u->setValue(obj_scale_s * repeats_per_meter); - shiny_scale_v->setValue(obj_scale_t * repeats_per_meter); - - LLSelectedTEMaterial::setSpecularRepeatX(self,obj_scale_s * repeats_per_meter); - LLSelectedTEMaterial::setSpecularRepeatY(self,obj_scale_t * repeats_per_meter); - } - break; - - default: - llassert(false); - break; - } - } - // vertical scale and repeats per meter depends on each other, so force set on changes - self->updateUI(true); -} - -struct LLPanelFaceSetMediaFunctor : public LLSelectedTEFunctor -{ - virtual bool apply(LLViewerObject* object, S32 te) - { - viewer_media_t pMediaImpl; - - const LLTextureEntry* tep = object->getTE(te); - const LLMediaEntry* mep = tep->hasMedia() ? tep->getMediaData() : NULL; - if ( mep ) - { - pMediaImpl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mep->getMediaID()); - } - - if ( pMediaImpl.isNull()) - { - // If we didn't find face media for this face, check whether this face is showing parcel media. - pMediaImpl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(tep->getID()); - } - - if ( pMediaImpl.notNull()) - { - LLPluginClassMedia *media = pMediaImpl->getMediaPlugin(); - if(media) - { - S32 media_width = media->getWidth(); - S32 media_height = media->getHeight(); - S32 texture_width = media->getTextureWidth(); - S32 texture_height = media->getTextureHeight(); - F32 scale_s = (F32)media_width / (F32)texture_width; - F32 scale_t = (F32)media_height / (F32)texture_height; - - // set scale and adjust offset - object->setTEScaleS( te, scale_s ); - object->setTEScaleT( te, scale_t ); // don't need to flip Y anymore since QT does this for us now. - object->setTEOffsetS( te, -( 1.0f - scale_s ) / 2.0f ); - object->setTEOffsetT( te, -( 1.0f - scale_t ) / 2.0f ); - } - } - return true; - }; -}; - -void LLPanelFace::onClickAutoFix(void* userdata) -{ - LLPanelFaceSetMediaFunctor setfunc; - LLSelectMgr::getInstance()->getSelection()->applyToTEs(&setfunc); - - LLPanelFaceSendFunctor sendfunc; - LLSelectMgr::getInstance()->getSelection()->applyToObjects(&sendfunc); -} - -void LLPanelFace::onAlignTexture(void* userdata) -{ - LLPanelFace* self = (LLPanelFace*)userdata; - self->alignTestureLayer(); -} - -void LLPanelFace::onClickBtnLoadInvPBR(void* userdata) -{ - // Shouldn't this be "save to inventory?" - LLPanelFace* self = (LLPanelFace*)userdata; - LLTextureCtrl* pbr_ctrl = self->findChild("pbr_control"); - pbr_ctrl->showPicker(true); -} - -void LLPanelFace::onClickBtnEditPBR(void* userdata) -{ - LLMaterialEditor::loadLive(); -} - -void LLPanelFace::onClickBtnSavePBR(void* userdata) -{ - LLMaterialEditor::saveObjectsMaterialAs(); -} - -enum EPasteMode -{ - PASTE_COLOR, - PASTE_TEXTURE -}; - -struct LLPanelFacePasteTexFunctor : public LLSelectedTEFunctor -{ - LLPanelFacePasteTexFunctor(LLPanelFace* panel, EPasteMode mode) : - mPanelFace(panel), mMode(mode) {} - - virtual bool apply(LLViewerObject* objectp, S32 te) - { - switch (mMode) - { - case PASTE_COLOR: - mPanelFace->onPasteColor(objectp, te); - break; - case PASTE_TEXTURE: - mPanelFace->onPasteTexture(objectp, te); - break; - } - return true; - } -private: - LLPanelFace *mPanelFace; - EPasteMode mMode; -}; - -struct LLPanelFaceUpdateFunctor : public LLSelectedObjectFunctor -{ - LLPanelFaceUpdateFunctor(bool update_media) - : mUpdateMedia(update_media) - {} - - virtual bool apply(LLViewerObject* object) - { - object->sendTEUpdate(); - - if (mUpdateMedia) - { - LLVOVolume *vo = dynamic_cast(object); - if (vo && vo->hasMedia()) - { - vo->sendMediaDataUpdate(); - } - } - return true; - } -private: - bool mUpdateMedia; -}; - -struct LLPanelFaceNavigateHomeFunctor : public LLSelectedTEFunctor -{ - virtual bool apply(LLViewerObject* objectp, S32 te) - { - if (objectp && objectp->getTE(te)) - { - LLTextureEntry* tep = objectp->getTE(te); - const LLMediaEntry *media_data = tep->getMediaData(); - if (media_data) - { - if (media_data->getCurrentURL().empty() && media_data->getAutoPlay()) - { - viewer_media_t media_impl = - LLViewerMedia::getInstance()->getMediaImplFromTextureID(tep->getMediaData()->getMediaID()); - if (media_impl) - { - media_impl->navigateHome(); - } - } - } - } - return true; - } -}; - -void LLPanelFace::onCopyColor() -{ - LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); - LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode(); - S32 selected_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount(); - if (!objectp || !node - || objectp->getPCode() != LL_PCODE_VOLUME - || !objectp->permModify() - || objectp->isPermanentEnforced() - || selected_count > 1) - { - return; - } - - if (mClipboardParams.has("color")) - { - mClipboardParams["color"].clear(); - } - else - { - mClipboardParams["color"] = LLSD::emptyArray(); - } - - std::map asset_item_map; - - // a way to resolve situations where source and target have different amount of faces - S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); - mClipboardParams["color_all_tes"] = (num_tes != 1) || (LLToolFace::getInstance() == LLToolMgr::getInstance()->getCurrentTool()); - for (S32 te = 0; te < num_tes; ++te) - { - if (node->isTESelected(te)) - { - LLTextureEntry* tep = objectp->getTE(te); - if (tep) - { - LLSD te_data; - - // asLLSD() includes media - te_data["te"] = tep->asLLSD(); // Note: includes a lot more than just color/alpha/glow - - mClipboardParams["color"].append(te_data); - } - } - } -} - -void LLPanelFace::onPasteColor() -{ - if (!mClipboardParams.has("color")) - { - return; - } - - LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); - LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode(); - S32 selected_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount(); - if (!objectp || !node - || objectp->getPCode() != LL_PCODE_VOLUME - || !objectp->permModify() - || objectp->isPermanentEnforced() - || selected_count > 1) - { - // not supposed to happen - LL_WARNS() << "Failed to paste color due to missing or wrong selection" << LL_ENDL; - return; - } - - bool face_selection_mode = LLToolFace::getInstance() == LLToolMgr::getInstance()->getCurrentTool(); - LLSD &clipboard = mClipboardParams["color"]; // array - S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); - S32 compare_tes = num_tes; - - if (face_selection_mode) - { - compare_tes = 0; - for (S32 te = 0; te < num_tes; ++te) - { - if (node->isTESelected(te)) - { - compare_tes++; - } - } - } - - // we can copy if single face was copied in edit face mode or if face count matches - if (!((clipboard.size() == 1) && mClipboardParams["color_all_tes"].asBoolean()) - && compare_tes != clipboard.size()) - { - LLSD notif_args; - if (face_selection_mode) - { - static std::string reason = getString("paste_error_face_selection_mismatch"); - notif_args["REASON"] = reason; - } - else - { - static std::string reason = getString("paste_error_object_face_count_mismatch"); - notif_args["REASON"] = reason; - } - LLNotificationsUtil::add("FacePasteFailed", notif_args); - return; - } - - LLObjectSelectionHandle selected_objects = LLSelectMgr::getInstance()->getSelection(); - - LLPanelFacePasteTexFunctor paste_func(this, PASTE_COLOR); - selected_objects->applyToTEs(&paste_func); - - LLPanelFaceUpdateFunctor sendfunc(false); - selected_objects->applyToObjects(&sendfunc); -} - -void LLPanelFace::onPasteColor(LLViewerObject* objectp, S32 te) -{ - LLSD te_data; - LLSD &clipboard = mClipboardParams["color"]; // array - if ((clipboard.size() == 1) && mClipboardParams["color_all_tes"].asBoolean()) - { - te_data = *(clipboard.beginArray()); - } - else if (clipboard[te]) - { - te_data = clipboard[te]; - } - else - { - return; - } - - LLTextureEntry* tep = objectp->getTE(te); - if (tep) - { - if (te_data.has("te")) - { - // Color / Alpha - if (te_data["te"].has("colors")) - { - LLColor4 color = tep->getColor(); - - LLColor4 clip_color; - clip_color.setValue(te_data["te"]["colors"]); - - // Color - color.mV[VRED] = clip_color.mV[VRED]; - color.mV[VGREEN] = clip_color.mV[VGREEN]; - color.mV[VBLUE] = clip_color.mV[VBLUE]; - - // Alpha - color.mV[VALPHA] = clip_color.mV[VALPHA]; - - objectp->setTEColor(te, color); - } - - // Color/fullbright - if (te_data["te"].has("fullbright")) - { - objectp->setTEFullbright(te, te_data["te"]["fullbright"].asInteger()); - } - - // Glow - if (te_data["te"].has("glow")) - { - objectp->setTEGlow(te, (F32)te_data["te"]["glow"].asReal()); - } - } - } -} - -void LLPanelFace::onCopyTexture() -{ - LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); - LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode(); - S32 selected_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount(); - if (!objectp || !node - || objectp->getPCode() != LL_PCODE_VOLUME - || !objectp->permModify() - || objectp->isPermanentEnforced() - || selected_count > 1 - || !LLMaterialEditor::canClipboardObjectsMaterial()) - { - return; - } - - if (mClipboardParams.has("texture")) - { - mClipboardParams["texture"].clear(); - } - else - { - mClipboardParams["texture"] = LLSD::emptyArray(); - } - - std::map asset_item_map; - - // a way to resolve situations where source and target have different amount of faces - S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); - mClipboardParams["texture_all_tes"] = (num_tes != 1) || (LLToolFace::getInstance() == LLToolMgr::getInstance()->getCurrentTool()); - for (S32 te = 0; te < num_tes; ++te) - { - if (node->isTESelected(te)) - { - LLTextureEntry* tep = objectp->getTE(te); - if (tep) - { - LLSD te_data; - - // asLLSD() includes media - te_data["te"] = tep->asLLSD(); - te_data["te"]["shiny"] = tep->getShiny(); - te_data["te"]["bumpmap"] = tep->getBumpmap(); - te_data["te"]["bumpshiny"] = tep->getBumpShiny(); - te_data["te"]["bumpfullbright"] = tep->getBumpShinyFullbright(); - te_data["te"]["texgen"] = tep->getTexGen(); - te_data["te"]["pbr"] = objectp->getRenderMaterialID(te); - if (tep->getGLTFMaterialOverride() != nullptr) - { - te_data["te"]["pbr_override"] = tep->getGLTFMaterialOverride()->asJSON(); - } - - if (te_data["te"].has("imageid")) - { - LLUUID item_id; - LLUUID id = te_data["te"]["imageid"].asUUID(); - bool from_library = get_is_predefined_texture(id); - bool full_perm = from_library; - - if (!full_perm - && objectp->permCopy() - && objectp->permTransfer() - && objectp->permModify()) - { - // If agent created this object and nothing is limiting permissions, mark as full perm - // If agent was granted permission to edit objects owned and created by somebody else, mark full perm - // This check is not perfect since we can't figure out whom textures belong to so this ended up restrictive - std::string creator_app_link; - LLUUID creator_id; - LLSelectMgr::getInstance()->selectGetCreator(creator_id, creator_app_link); - full_perm = objectp->mOwnerID == creator_id; - } - - if (id.notNull() && !full_perm) - { - std::map::iterator iter = asset_item_map.find(id); - if (iter != asset_item_map.end()) - { - item_id = iter->second; - } - else - { - // What this does is simply searches inventory for item with same asset id, - // as result it is Hightly unreliable, leaves little control to user, borderline hack - // but there are little options to preserve permissions - multiple inventory - // items might reference same asset and inventory search is expensive. - bool no_transfer = false; - if (objectp->getInventoryItemByAsset(id)) - { - no_transfer = !objectp->getInventoryItemByAsset(id)->getIsFullPerm(); - } - item_id = get_copy_free_item_by_asset_id(id, no_transfer); - // record value to avoid repeating inventory search when possible - asset_item_map[id] = item_id; - } - } - - if (item_id.notNull() && gInventory.isObjectDescendentOf(item_id, gInventory.getLibraryRootFolderID())) - { - full_perm = true; - from_library = true; - } - - { - te_data["te"]["itemfullperm"] = full_perm; - te_data["te"]["fromlibrary"] = from_library; - - // If full permission object, texture is free to copy, - // but otherwise we need to check inventory and extract permissions - // - // Normally we care only about restrictions for current user and objects - // don't inherit any 'next owner' permissions from texture, so there is - // no need to record item id if full_perm==true - if (!full_perm && !from_library && item_id.notNull()) - { - LLViewerInventoryItem* itemp = gInventory.getItem(item_id); - if (itemp) - { - LLPermissions item_permissions = itemp->getPermissions(); - if (item_permissions.allowOperationBy(PERM_COPY, - gAgent.getID(), - gAgent.getGroupID())) - { - te_data["te"]["imageitemid"] = item_id; - te_data["te"]["itemfullperm"] = itemp->getIsFullPerm(); - if (!itemp->isFinished()) - { - // needed for dropTextureAllFaces - LLInventoryModelBackgroundFetch::instance().start(item_id, false); - } - } - } - } - } - } - - LLMaterialPtr material_ptr = tep->getMaterialParams(); - if (!material_ptr.isNull()) - { - LLSD mat_data; - - mat_data["NormMap"] = material_ptr->getNormalID(); - mat_data["SpecMap"] = material_ptr->getSpecularID(); - - mat_data["NormRepX"] = material_ptr->getNormalRepeatX(); - mat_data["NormRepY"] = material_ptr->getNormalRepeatY(); - mat_data["NormOffX"] = material_ptr->getNormalOffsetX(); - mat_data["NormOffY"] = material_ptr->getNormalOffsetY(); - mat_data["NormRot"] = material_ptr->getNormalRotation(); - - mat_data["SpecRepX"] = material_ptr->getSpecularRepeatX(); - mat_data["SpecRepY"] = material_ptr->getSpecularRepeatY(); - mat_data["SpecOffX"] = material_ptr->getSpecularOffsetX(); - mat_data["SpecOffY"] = material_ptr->getSpecularOffsetY(); - mat_data["SpecRot"] = material_ptr->getSpecularRotation(); - - mat_data["SpecColor"] = material_ptr->getSpecularLightColor().getValue(); - mat_data["SpecExp"] = material_ptr->getSpecularLightExponent(); - mat_data["EnvIntensity"] = material_ptr->getEnvironmentIntensity(); - mat_data["AlphaMaskCutoff"] = material_ptr->getAlphaMaskCutoff(); - mat_data["DiffuseAlphaMode"] = material_ptr->getDiffuseAlphaMode(); - - // Replace no-copy textures, destination texture will get used instead if available - if (mat_data.has("NormMap")) - { - LLUUID id = mat_data["NormMap"].asUUID(); - if (id.notNull() && !get_can_copy_texture(id)) - { - mat_data["NormMap"] = DEFAULT_OBJECT_TEXTURE; - mat_data["NormMapNoCopy"] = true; - } - - } - if (mat_data.has("SpecMap")) - { - LLUUID id = mat_data["SpecMap"].asUUID(); - if (id.notNull() && !get_can_copy_texture(id)) - { - mat_data["SpecMap"] = DEFAULT_OBJECT_TEXTURE; - mat_data["SpecMapNoCopy"] = true; - } - - } - - te_data["material"] = mat_data; - } - - mClipboardParams["texture"].append(te_data); - } - } - } -} - -void LLPanelFace::onPasteTexture() -{ - if (!mClipboardParams.has("texture")) - { - return; - } - - LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); - LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode(); - S32 selected_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount(); - if (!objectp || !node - || objectp->getPCode() != LL_PCODE_VOLUME - || !objectp->permModify() - || objectp->isPermanentEnforced() - || selected_count > 1 - || !LLMaterialEditor::canClipboardObjectsMaterial()) - { - // not supposed to happen - LL_WARNS() << "Failed to paste texture due to missing or wrong selection" << LL_ENDL; - return; - } - - bool face_selection_mode = LLToolFace::getInstance() == LLToolMgr::getInstance()->getCurrentTool(); - LLSD &clipboard = mClipboardParams["texture"]; // array - S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); - S32 compare_tes = num_tes; - - if (face_selection_mode) - { - compare_tes = 0; - for (S32 te = 0; te < num_tes; ++te) - { - if (node->isTESelected(te)) - { - compare_tes++; - } - } - } - - // we can copy if single face was copied in edit face mode or if face count matches - if (!((clipboard.size() == 1) && mClipboardParams["texture_all_tes"].asBoolean()) - && compare_tes != clipboard.size()) - { - LLSD notif_args; - if (face_selection_mode) - { - static std::string reason = getString("paste_error_face_selection_mismatch"); - notif_args["REASON"] = reason; - } - else - { - static std::string reason = getString("paste_error_object_face_count_mismatch"); - notif_args["REASON"] = reason; - } - LLNotificationsUtil::add("FacePasteFailed", notif_args); - return; - } - - bool full_perm_object = true; - LLSD::array_const_iterator iter = clipboard.beginArray(); - LLSD::array_const_iterator end = clipboard.endArray(); - for (; iter != end; ++iter) - { - const LLSD& te_data = *iter; - if (te_data.has("te") && te_data["te"].has("imageid")) - { - bool full_perm = te_data["te"].has("itemfullperm") && te_data["te"]["itemfullperm"].asBoolean(); - full_perm_object &= full_perm; - if (!full_perm) - { - if (te_data["te"].has("imageitemid")) - { - LLUUID item_id = te_data["te"]["imageitemid"].asUUID(); - if (item_id.notNull()) - { - LLViewerInventoryItem* itemp = gInventory.getItem(item_id); - if (!itemp) - { - // image might be in object's inventory, but it can be not up to date - LLSD notif_args; - static std::string reason = getString("paste_error_inventory_not_found"); - notif_args["REASON"] = reason; - LLNotificationsUtil::add("FacePasteFailed", notif_args); - return; - } - } - } - else - { - // Item was not found on 'copy' stage - // Since this happened at copy, might be better to either show this - // at copy stage or to drop clipboard here - LLSD notif_args; - static std::string reason = getString("paste_error_inventory_not_found"); - notif_args["REASON"] = reason; - LLNotificationsUtil::add("FacePasteFailed", notif_args); - return; - } - } - } - } - - if (!full_perm_object) - { - LLNotificationsUtil::add("FacePasteTexturePermissions"); - } - - LLObjectSelectionHandle selected_objects = LLSelectMgr::getInstance()->getSelection(); - - LLPanelFacePasteTexFunctor paste_func(this, PASTE_TEXTURE); - selected_objects->applyToTEs(&paste_func); - - LLPanelFaceUpdateFunctor sendfunc(true); - selected_objects->applyToObjects(&sendfunc); - - LLGLTFMaterialList::flushUpdates(); - - LLPanelFaceNavigateHomeFunctor navigate_home_func; - selected_objects->applyToTEs(&navigate_home_func); -} - -void LLPanelFace::onPasteTexture(LLViewerObject* objectp, S32 te) -{ - LLSD te_data; - LLSD &clipboard = mClipboardParams["texture"]; // array - if ((clipboard.size() == 1) && mClipboardParams["texture_all_tes"].asBoolean()) - { - te_data = *(clipboard.beginArray()); - } - else if (clipboard[te]) - { - te_data = clipboard[te]; - } - else - { - return; - } - - LLTextureEntry* tep = objectp->getTE(te); - if (tep) - { - if (te_data.has("te")) - { - // Texture - bool full_perm = te_data["te"].has("itemfullperm") && te_data["te"]["itemfullperm"].asBoolean(); - bool from_library = te_data["te"].has("fromlibrary") && te_data["te"]["fromlibrary"].asBoolean(); - if (te_data["te"].has("imageid")) - { - const LLUUID& imageid = te_data["te"]["imageid"].asUUID(); //texture or asset id - LLViewerInventoryItem* itemp_res = NULL; - - if (te_data["te"].has("imageitemid")) - { - LLUUID item_id = te_data["te"]["imageitemid"].asUUID(); - if (item_id.notNull()) - { - LLViewerInventoryItem* itemp = gInventory.getItem(item_id); - if (itemp && itemp->isFinished()) - { - // dropTextureAllFaces will fail if incomplete - itemp_res = itemp; - } - else - { - // Theoretically shouldn't happend, but if it does happen, we - // might need to add a notification to user that paste will fail - // since inventory isn't fully loaded - LL_WARNS() << "Item " << item_id << " is incomplete, paste might fail silently." << LL_ENDL; - } - } - } - // for case when item got removed from inventory after we pressed 'copy' - // or texture got pasted into previous object - if (!itemp_res && !full_perm) - { - // Due to checks for imageitemid in LLPanelFace::onPasteTexture() this should no longer be reachable. - LL_INFOS() << "Item " << te_data["te"]["imageitemid"].asUUID() << " no longer in inventory." << LL_ENDL; - // Todo: fix this, we are often searching same texture multiple times (equal to number of faces) - // Perhaps just mPanelFace->onPasteTexture(objectp, te, &asset_to_item_id_map); ? Not pretty, but will work - LLViewerInventoryCategory::cat_array_t cats; - LLViewerInventoryItem::item_array_t items; - LLAssetIDMatches asset_id_matches(imageid); - gInventory.collectDescendentsIf(LLUUID::null, - cats, - items, - LLInventoryModel::INCLUDE_TRASH, - asset_id_matches); - - // Extremely unreliable and perfomance unfriendly. - // But we need this to check permissions and it is how texture control finds items - for (S32 i = 0; i < items.size(); i++) - { - LLViewerInventoryItem* itemp = items[i]; - if (itemp && itemp->isFinished()) - { - // dropTextureAllFaces will fail if incomplete - LLPermissions item_permissions = itemp->getPermissions(); - if (item_permissions.allowOperationBy(PERM_COPY, - gAgent.getID(), - gAgent.getGroupID())) - { - itemp_res = itemp; - break; // first match - } - } - } - } - - if (itemp_res) - { - if (te == -1) // all faces - { - LLToolDragAndDrop::dropTextureAllFaces(objectp, - itemp_res, - from_library ? LLToolDragAndDrop::SOURCE_LIBRARY : LLToolDragAndDrop::SOURCE_AGENT, - LLUUID::null, - false); - } - else // one face - { - LLToolDragAndDrop::dropTextureOneFace(objectp, - te, - itemp_res, - from_library ? LLToolDragAndDrop::SOURCE_LIBRARY : LLToolDragAndDrop::SOURCE_AGENT, - LLUUID::null, - false, - 0); - } - } - // not an inventory item or no complete items - else if (full_perm) - { - // Either library, local or existed as fullperm when user made a copy - LLViewerTexture* image = LLViewerTextureManager::getFetchedTexture(imageid, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); - objectp->setTEImage(U8(te), image); - } - } - - if (te_data["te"].has("bumpmap")) - { - objectp->setTEBumpmap(te, (U8)te_data["te"]["bumpmap"].asInteger()); - } - if (te_data["te"].has("bumpshiny")) - { - objectp->setTEBumpShiny(te, (U8)te_data["te"]["bumpshiny"].asInteger()); - } - if (te_data["te"].has("bumpfullbright")) - { - objectp->setTEBumpShinyFullbright(te, (U8)te_data["te"]["bumpfullbright"].asInteger()); - } - if (te_data["te"].has("texgen")) - { - objectp->setTETexGen(te, (U8)te_data["te"]["texgen"].asInteger()); - } - - // PBR/GLTF - if (te_data["te"].has("pbr")) - { - objectp->setRenderMaterialID(te, te_data["te"]["pbr"].asUUID(), false /*managing our own update*/); - tep->setGLTFRenderMaterial(nullptr); - tep->setGLTFMaterialOverride(nullptr); - - LLSD override_data; - override_data["object_id"] = objectp->getID(); - override_data["side"] = te; - if (te_data["te"].has("pbr_override")) - { - override_data["gltf_json"] = te_data["te"]["pbr_override"]; - } - else - { - override_data["gltf_json"] = ""; - } - - override_data["asset_id"] = te_data["te"]["pbr"].asUUID(); - - LLGLTFMaterialList::queueUpdate(override_data); - } - else - { - objectp->setRenderMaterialID(te, LLUUID::null, false /*send in bulk later*/ ); - tep->setGLTFRenderMaterial(nullptr); - tep->setGLTFMaterialOverride(nullptr); - - // blank out most override data on the server - LLGLTFMaterialList::queueApply(objectp, te, LLUUID::null); - } - - // Texture map - if (te_data["te"].has("scales") && te_data["te"].has("scalet")) - { - objectp->setTEScale(te, (F32)te_data["te"]["scales"].asReal(), (F32)te_data["te"]["scalet"].asReal()); - } - if (te_data["te"].has("offsets") && te_data["te"].has("offsett")) - { - objectp->setTEOffset(te, (F32)te_data["te"]["offsets"].asReal(), (F32)te_data["te"]["offsett"].asReal()); - } - if (te_data["te"].has("imagerot")) - { - objectp->setTERotation(te, (F32)te_data["te"]["imagerot"].asReal()); - } - - // Media - if (te_data["te"].has("media_flags")) - { - U8 media_flags = te_data["te"]["media_flags"].asInteger(); - objectp->setTEMediaFlags(te, media_flags); - LLVOVolume *vo = dynamic_cast(objectp); - if (vo && te_data["te"].has(LLTextureEntry::TEXTURE_MEDIA_DATA_KEY)) - { - vo->syncMediaData(te, te_data["te"][LLTextureEntry::TEXTURE_MEDIA_DATA_KEY], true/*merge*/, true/*ignore_agent*/); - } - } - else - { - // Keep media flags on destination unchanged - } - } - - if (te_data.has("material")) - { - LLUUID object_id = objectp->getID(); - - // Normal - // Replace placeholders with target's - if (te_data["material"].has("NormMapNoCopy")) - { - LLMaterialPtr material = tep->getMaterialParams(); - if (material.notNull()) - { - LLUUID id = material->getNormalID(); - if (id.notNull()) - { - te_data["material"]["NormMap"] = id; - } - } - } - LLSelectedTEMaterial::setNormalID(this, te_data["material"]["NormMap"].asUUID(), te, object_id); - LLSelectedTEMaterial::setNormalRepeatX(this, (F32)te_data["material"]["NormRepX"].asReal(), te, object_id); - LLSelectedTEMaterial::setNormalRepeatY(this, (F32)te_data["material"]["NormRepY"].asReal(), te, object_id); - LLSelectedTEMaterial::setNormalOffsetX(this, (F32)te_data["material"]["NormOffX"].asReal(), te, object_id); - LLSelectedTEMaterial::setNormalOffsetY(this, (F32)te_data["material"]["NormOffY"].asReal(), te, object_id); - LLSelectedTEMaterial::setNormalRotation(this, (F32)te_data["material"]["NormRot"].asReal(), te, object_id); - - // Specular - // Replace placeholders with target's - if (te_data["material"].has("SpecMapNoCopy")) - { - LLMaterialPtr material = tep->getMaterialParams(); - if (material.notNull()) - { - LLUUID id = material->getSpecularID(); - if (id.notNull()) - { - te_data["material"]["SpecMap"] = id; - } - } - } - LLSelectedTEMaterial::setSpecularID(this, te_data["material"]["SpecMap"].asUUID(), te, object_id); - LLSelectedTEMaterial::setSpecularRepeatX(this, (F32)te_data["material"]["SpecRepX"].asReal(), te, object_id); - LLSelectedTEMaterial::setSpecularRepeatY(this, (F32)te_data["material"]["SpecRepY"].asReal(), te, object_id); - LLSelectedTEMaterial::setSpecularOffsetX(this, (F32)te_data["material"]["SpecOffX"].asReal(), te, object_id); - LLSelectedTEMaterial::setSpecularOffsetY(this, (F32)te_data["material"]["SpecOffY"].asReal(), te, object_id); - LLSelectedTEMaterial::setSpecularRotation(this, (F32)te_data["material"]["SpecRot"].asReal(), te, object_id); - LLColor4U spec_color(te_data["material"]["SpecColor"]); - LLSelectedTEMaterial::setSpecularLightColor(this, spec_color, te); - LLSelectedTEMaterial::setSpecularLightExponent(this, (U8)te_data["material"]["SpecExp"].asInteger(), te, object_id); - LLSelectedTEMaterial::setEnvironmentIntensity(this, (U8)te_data["material"]["EnvIntensity"].asInteger(), te, object_id); - LLSelectedTEMaterial::setDiffuseAlphaMode(this, (U8)te_data["material"]["DiffuseAlphaMode"].asInteger(), te, object_id); - LLSelectedTEMaterial::setAlphaMaskCutoff(this, (U8)te_data["material"]["AlphaMaskCutoff"].asInteger(), te, object_id); - if (te_data.has("te") && te_data["te"].has("shiny")) - { - objectp->setTEShiny(te, (U8)te_data["te"]["shiny"].asInteger()); - } - } - } -} - -void LLPanelFace::menuDoToSelected(const LLSD& userdata) -{ - std::string command = userdata.asString(); - - // paste - if (command == "color_paste") - { - onPasteColor(); - } - else if (command == "texture_paste") - { - onPasteTexture(); - } - // copy - else if (command == "color_copy") - { - onCopyColor(); - } - else if (command == "texture_copy") - { - onCopyTexture(); - } -} - -bool LLPanelFace::menuEnableItem(const LLSD& userdata) -{ - std::string command = userdata.asString(); - - // paste options - if (command == "color_paste") - { - return mClipboardParams.has("color"); - } - else if (command == "texture_paste") - { - return mClipboardParams.has("texture"); - } - return false; -} - - -// static -void LLPanelFace::onCommitPlanarAlign(LLUICtrl* ctrl, void* userdata) -{ - LLPanelFace* self = (LLPanelFace*) userdata; - self->getState(); - self->sendTextureInfo(); -} - -void LLPanelFace::updateGLTFTextureTransform(float value, U32 pbr_type, std::function edit) -{ - U32 texture_info_start; - U32 texture_info_end; - const LLGLTFMaterial::TextureInfo texture_info = texture_info_from_pbrtype(pbr_type); - if (texture_info == LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT) - { - texture_info_start = 0; - texture_info_end = LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; - } - else - { - texture_info_start = texture_info_from_pbrtype(pbr_type); - texture_info_end = texture_info_start + 1; - } - updateSelectedGLTFMaterials([&](LLGLTFMaterial* new_override) - { - for (U32 ti = texture_info_start; ti < texture_info_end; ++ti) - { - LLGLTFMaterial::TextureTransform& new_transform = new_override->mTextureTransform[(LLGLTFMaterial::TextureInfo)ti]; - edit(&new_transform); - } - }); -} - -void LLPanelFace::setMaterialOverridesFromSelection() -{ - const U32 pbr_type = findChild("radio_pbr_type")->getSelectedIndex(); - const LLGLTFMaterial::TextureInfo texture_info = texture_info_from_pbrtype(pbr_type); - U32 texture_info_start; - U32 texture_info_end; - if (texture_info == LLGLTFMaterial::TextureInfo::GLTF_TEXTURE_INFO_COUNT) - { - texture_info_start = 0; - texture_info_end = LLGLTFMaterial::TextureInfo::GLTF_TEXTURE_INFO_COUNT; - } - else - { - texture_info_start = texture_info; - texture_info_end = texture_info + 1; - } - - bool read_transform = true; - LLGLTFMaterial::TextureTransform transform; - bool scale_u_same = true; - bool scale_v_same = true; - bool rotation_same = true; - bool offset_u_same = true; - bool offset_v_same = true; - - for (U32 i = texture_info_start; i < texture_info_end; ++i) - { - LLGLTFMaterial::TextureTransform this_transform; - bool this_scale_u_same = true; - bool this_scale_v_same = true; - bool this_rotation_same = true; - bool this_offset_u_same = true; - bool this_offset_v_same = true; - - readSelectedGLTFMaterial([&](const LLGLTFMaterial* mat) - { - return mat ? mat->mTextureTransform[i].mScale[VX] : 0.f; - }, this_transform.mScale[VX], this_scale_u_same, true, 1e-3f); - readSelectedGLTFMaterial([&](const LLGLTFMaterial* mat) - { - return mat ? mat->mTextureTransform[i].mScale[VY] : 0.f; - }, this_transform.mScale[VY], this_scale_v_same, true, 1e-3f); - readSelectedGLTFMaterial([&](const LLGLTFMaterial* mat) - { - return mat ? mat->mTextureTransform[i].mRotation : 0.f; - }, this_transform.mRotation, this_rotation_same, true, 1e-3f); - readSelectedGLTFMaterial([&](const LLGLTFMaterial* mat) - { - return mat ? mat->mTextureTransform[i].mOffset[VX] : 0.f; - }, this_transform.mOffset[VX], this_offset_u_same, true, 1e-3f); - readSelectedGLTFMaterial([&](const LLGLTFMaterial* mat) - { - return mat ? mat->mTextureTransform[i].mOffset[VY] : 0.f; - }, this_transform.mOffset[VY], this_offset_v_same, true, 1e-3f); - - scale_u_same = scale_u_same && this_scale_u_same; - scale_v_same = scale_v_same && this_scale_v_same; - rotation_same = rotation_same && this_rotation_same; - offset_u_same = offset_u_same && this_offset_u_same; - offset_v_same = offset_v_same && this_offset_v_same; - - if (read_transform) - { - read_transform = false; - transform = this_transform; - } - else - { - scale_u_same = scale_u_same && (this_transform.mScale[VX] == transform.mScale[VX]); - scale_v_same = scale_v_same && (this_transform.mScale[VY] == transform.mScale[VY]); - rotation_same = rotation_same && (this_transform.mRotation == transform.mRotation); - offset_u_same = offset_u_same && (this_transform.mOffset[VX] == transform.mOffset[VX]); - offset_v_same = offset_v_same && (this_transform.mOffset[VY] == transform.mOffset[VY]); - } - } - - LLUICtrl* gltfCtrlTextureScaleU = getChild("gltfTextureScaleU"); - LLUICtrl* gltfCtrlTextureScaleV = getChild("gltfTextureScaleV"); - LLUICtrl* gltfCtrlTextureRotation = getChild("gltfTextureRotation"); - LLUICtrl* gltfCtrlTextureOffsetU = getChild("gltfTextureOffsetU"); - LLUICtrl* gltfCtrlTextureOffsetV = getChild("gltfTextureOffsetV"); - - gltfCtrlTextureScaleU->setValue(transform.mScale[VX]); - gltfCtrlTextureScaleV->setValue(transform.mScale[VY]); - gltfCtrlTextureRotation->setValue(transform.mRotation * RAD_TO_DEG); - gltfCtrlTextureOffsetU->setValue(transform.mOffset[VX]); - gltfCtrlTextureOffsetV->setValue(transform.mOffset[VY]); - - gltfCtrlTextureScaleU->setTentative(!scale_u_same); - gltfCtrlTextureScaleV->setTentative(!scale_v_same); - gltfCtrlTextureRotation->setTentative(!rotation_same); - gltfCtrlTextureOffsetU->setTentative(!offset_u_same); - gltfCtrlTextureOffsetV->setTentative(!offset_v_same); -} - -void LLPanelFace::Selection::connect() -{ - if (!mSelectConnection.connected()) - { - mSelectConnection = LLSelectMgr::instance().mUpdateSignal.connect(boost::bind(&LLPanelFace::Selection::onSelectionChanged, this)); - } -} - -bool LLPanelFace::Selection::update() -{ - const bool changed = mChanged || compareSelection(); - mChanged = false; - return changed; -} - -void LLPanelFace::Selection::onSelectedObjectUpdated(const LLUUID& object_id, S32 side) -{ - if (object_id == mSelectedObjectID) - { - if (side == mLastSelectedSide) - { - mChanged = true; - } - else if (mLastSelectedSide == -1) // if last selected face was deselected - { - LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode(); - if (node && node->isTESelected(side)) - { - mChanged = true; - } - } - } -} - -bool LLPanelFace::Selection::compareSelection() -{ - if (!mNeedsSelectionCheck) - { - return false; - } - mNeedsSelectionCheck = false; - - const S32 old_object_count = mSelectedObjectCount; - const S32 old_te_count = mSelectedTECount; - const LLUUID old_object_id = mSelectedObjectID; - const S32 old_side = mLastSelectedSide; - - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - LLSelectNode* node = selection->getFirstNode(); - if (node) - { - LLViewerObject* object = node->getObject(); - mSelectedObjectCount = selection->getObjectCount(); - mSelectedTECount = selection->getTECount(); - mSelectedObjectID = object->getID(); - mLastSelectedSide = node->getLastSelectedTE(); - } - else - { - mSelectedObjectCount = 0; - mSelectedTECount = 0; - mSelectedObjectID = LLUUID::null; - mLastSelectedSide = -1; - } - - const bool selection_changed = - old_object_count != mSelectedObjectCount - || old_te_count != mSelectedTECount - || old_object_id != mSelectedObjectID - || old_side != mLastSelectedSide; - mChanged = mChanged || selection_changed; - return selection_changed; -} - -void LLPanelFace::onCommitGLTFTextureScaleU(LLUICtrl* ctrl) -{ - const float value = ctrl->getValue().asReal(); - const U32 pbr_type = findChild("radio_pbr_type")->getSelectedIndex(); - updateGLTFTextureTransform(value, pbr_type, [&](LLGLTFMaterial::TextureTransform* new_transform) - { - new_transform->mScale.mV[VX] = value; - }); -} - -void LLPanelFace::onCommitGLTFTextureScaleV(LLUICtrl* ctrl) -{ - const float value = ctrl->getValue().asReal(); - const U32 pbr_type = findChild("radio_pbr_type")->getSelectedIndex(); - updateGLTFTextureTransform(value, pbr_type, [&](LLGLTFMaterial::TextureTransform* new_transform) - { - new_transform->mScale.mV[VY] = value; - }); -} - -void LLPanelFace::onCommitGLTFRotation(LLUICtrl* ctrl) -{ - const float value = ctrl->getValue().asReal() * DEG_TO_RAD; - const U32 pbr_type = findChild("radio_pbr_type")->getSelectedIndex(); - updateGLTFTextureTransform(value, pbr_type, [&](LLGLTFMaterial::TextureTransform* new_transform) - { - new_transform->mRotation = value; - }); -} - -void LLPanelFace::onCommitGLTFTextureOffsetU(LLUICtrl* ctrl) -{ - const float value = ctrl->getValue().asReal(); - const U32 pbr_type = findChild("radio_pbr_type")->getSelectedIndex(); - updateGLTFTextureTransform(value, pbr_type, [&](LLGLTFMaterial::TextureTransform* new_transform) - { - new_transform->mOffset.mV[VX] = value; - }); -} - -void LLPanelFace::onCommitGLTFTextureOffsetV(LLUICtrl* ctrl) -{ - const float value = ctrl->getValue().asReal(); - const U32 pbr_type = findChild("radio_pbr_type")->getSelectedIndex(); - updateGLTFTextureTransform(value, pbr_type, [&](LLGLTFMaterial::TextureTransform* new_transform) - { - new_transform->mOffset.mV[VY] = value; - }); -} - -void LLPanelFace::onTextureSelectionChanged(LLInventoryItem* itemp) -{ - LL_DEBUGS("Materials") << "item asset " << itemp->getAssetUUID() << LL_ENDL; - LLRadioGroup* radio_mat_type = findChild("radio_material_type"); - if(!radio_mat_type) - { - return; - } - U32 mattype = radio_mat_type->getSelectedIndex(); - std::string which_control="texture control"; - switch (mattype) - { - case MATTYPE_SPECULAR: - which_control = "shinytexture control"; - break; - case MATTYPE_NORMAL: - which_control = "bumpytexture control"; - break; - // no default needed - } - LL_DEBUGS("Materials") << "control " << which_control << LL_ENDL; - LLTextureCtrl* texture_ctrl = getChild(which_control); - if (texture_ctrl) - { - LLUUID obj_owner_id; - std::string obj_owner_name; - LLSelectMgr::instance().selectGetOwner(obj_owner_id, obj_owner_name); - - LLSaleInfo sale_info; - LLSelectMgr::instance().selectGetSaleInfo(sale_info); - - bool can_copy = itemp->getPermissions().allowCopyBy(gAgentID); // do we have perm to copy this texture? - bool can_transfer = itemp->getPermissions().allowOperationBy(PERM_TRANSFER, gAgentID); // do we have perm to transfer this texture? - bool is_object_owner = gAgentID == obj_owner_id; // does object for which we are going to apply texture belong to the agent? - bool not_for_sale = !sale_info.isForSale(); // is object for which we are going to apply texture not for sale? - - if (can_copy && can_transfer) - { - texture_ctrl->setCanApply(true, true); - return; - } - - // if texture has (no-transfer) attribute it can be applied only for object which we own and is not for sale - texture_ctrl->setCanApply(false, can_transfer ? true : is_object_owner && not_for_sale); - - if (gSavedSettings.getBOOL("TextureLivePreview")) - { - LLNotificationsUtil::add("LivePreviewUnavailable"); - } - } -} - -void LLPanelFace::onPbrSelectionChanged(LLInventoryItem* itemp) -{ - LLTextureCtrl* pbr_ctrl = findChild("pbr_control"); - if (pbr_ctrl) - { - LLUUID obj_owner_id; - std::string obj_owner_name; - LLSelectMgr::instance().selectGetOwner(obj_owner_id, obj_owner_name); - - LLSaleInfo sale_info; - LLSelectMgr::instance().selectGetSaleInfo(sale_info); - - bool can_copy = itemp->getPermissions().allowCopyBy(gAgentID); // do we have perm to copy this material? - bool can_transfer = itemp->getPermissions().allowOperationBy(PERM_TRANSFER, gAgentID); // do we have perm to transfer this material? - bool can_modify = itemp->getPermissions().allowOperationBy(PERM_MODIFY, gAgentID); // do we have perm to transfer this material? - bool is_object_owner = gAgentID == obj_owner_id; // does object for which we are going to apply material belong to the agent? - bool not_for_sale = !sale_info.isForSale(); // is object for which we are going to apply material not for sale? - bool from_library = ALEXANDRIA_LINDEN_ID == itemp->getPermissions().getOwner(); - - if ((can_copy && can_transfer && can_modify) || from_library) - { - pbr_ctrl->setCanApply(true, true); - return; - } - - // if material has (no-transfer) attribute it can be applied only for object which we own and is not for sale - pbr_ctrl->setCanApply(false, can_transfer ? true : is_object_owner && not_for_sale); - - if (gSavedSettings.getBOOL("TextureLivePreview")) - { - LLNotificationsUtil::add("LivePreviewUnavailablePBR"); - } - } -} - -bool LLPanelFace::isIdenticalPlanarTexgen() -{ - LLTextureEntry::e_texgen selected_texgen = LLTextureEntry::TEX_GEN_DEFAULT; - bool identical_texgen = false; - LLSelectedTE::getTexGen(selected_texgen, identical_texgen); - return (identical_texgen && (selected_texgen == LLTextureEntry::TEX_GEN_PLANAR)); -} - -void LLPanelFace::LLSelectedTE::getFace(LLFace*& face_to_return, bool& identical_face) -{ - struct LLSelectedTEGetFace : public LLSelectedTEGetFunctor - { - LLFace* get(LLViewerObject* object, S32 te) - { - return (object->mDrawable) ? object->mDrawable->getFace(te): NULL; - } - } get_te_face_func; - identical_face = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue(&get_te_face_func, face_to_return, false, (LLFace*)nullptr); -} - -void LLPanelFace::LLSelectedTE::getImageFormat(LLGLenum& image_format_to_return, bool& identical_face) -{ - LLGLenum image_format; - struct LLSelectedTEGetImageFormat : public LLSelectedTEGetFunctor - { - LLGLenum get(LLViewerObject* object, S32 te_index) - { - LLViewerTexture* image = object->getTEImage(te_index); - return image ? image->getPrimaryFormat() : GL_RGB; - } - } get_glenum; - identical_face = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue(&get_glenum, image_format); - image_format_to_return = image_format; -} - -void LLPanelFace::LLSelectedTE::getTexId(LLUUID& id, bool& identical) -{ - struct LLSelectedTEGetTexId : public LLSelectedTEGetFunctor - { - LLUUID get(LLViewerObject* object, S32 te_index) - { - LLTextureEntry *te = object->getTE(te_index); - if (te) - { - if ((te->getID() == IMG_USE_BAKED_EYES) || (te->getID() == IMG_USE_BAKED_HAIR) || (te->getID() == IMG_USE_BAKED_HEAD) || (te->getID() == IMG_USE_BAKED_LOWER) || (te->getID() == IMG_USE_BAKED_SKIRT) || (te->getID() == IMG_USE_BAKED_UPPER) - || (te->getID() == IMG_USE_BAKED_LEFTARM) || (te->getID() == IMG_USE_BAKED_LEFTLEG) || (te->getID() == IMG_USE_BAKED_AUX1) || (te->getID() == IMG_USE_BAKED_AUX2) || (te->getID() == IMG_USE_BAKED_AUX3)) - { - return te->getID(); - } - } - - LLUUID id; - LLViewerTexture* image = object->getTEImage(te_index); - if (image) - { - id = image->getID(); - } - - if (!id.isNull() && LLViewerMedia::getInstance()->textureHasMedia(id)) - { - if (te) - { - LLViewerTexture* tex = te->getID().notNull() ? gTextureList.findImage(te->getID(), TEX_LIST_STANDARD) : NULL; - if(!tex) - { - tex = LLViewerFetchedTexture::sDefaultImagep; - } - if (tex) - { - id = tex->getID(); - } - } - } - return id; - } - } func; - identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &func, id ); -} - -void LLPanelFace::LLSelectedTE::getPbrMaterialId(LLUUID& id, bool& identical, bool& has_faces_with_pbr, bool& has_faces_without_pbr) -{ - struct LLSelectedTEGetmatId : public LLSelectedTEFunctor - { - LLSelectedTEGetmatId() - : mHasFacesWithoutPBR(false) - , mHasFacesWithPBR(false) - , mIdenticalId(true) - , mIdenticalOverride(true) - , mInitialized(false) - , mMaterialOverride(LLGLTFMaterial::sDefault) - { - } - bool apply(LLViewerObject* object, S32 te_index) override - { - LLUUID pbr_id = object->getRenderMaterialID(te_index); - if (pbr_id.isNull()) - { - mHasFacesWithoutPBR = true; - } - else - { - mHasFacesWithPBR = true; - } - if (mInitialized) - { - if (mPBRId != pbr_id) - { - mIdenticalId = false; - } - - LLGLTFMaterial* te_override = object->getTE(te_index)->getGLTFMaterialOverride(); - if (te_override) - { - LLGLTFMaterial override = *te_override; - override.sanitizeAssetMaterial(); - mIdenticalOverride &= (override == mMaterialOverride); - } - else - { - mIdenticalOverride &= (mMaterialOverride == LLGLTFMaterial::sDefault); - } - } - else - { - mInitialized = true; - mPBRId = pbr_id; - LLGLTFMaterial* override = object->getTE(te_index)->getGLTFMaterialOverride(); - if (override) - { - mMaterialOverride = *override; - mMaterialOverride.sanitizeAssetMaterial(); - } - } - return true; - } - bool mHasFacesWithoutPBR; - bool mHasFacesWithPBR; - bool mIdenticalId; - bool mIdenticalOverride; - bool mInitialized; - LLGLTFMaterial mMaterialOverride; - LLUUID mPBRId; - } func; - LLSelectMgr::getInstance()->getSelection()->applyToTEs(&func); - id = func.mPBRId; - identical = func.mIdenticalId && func.mIdenticalOverride; - has_faces_with_pbr = func.mHasFacesWithPBR; - has_faces_without_pbr = func.mHasFacesWithoutPBR; -} - -void LLPanelFace::LLSelectedTEMaterial::getCurrent(LLMaterialPtr& material_ptr, bool& identical_material) -{ - struct MaterialFunctor : public LLSelectedTEGetFunctor - { - LLMaterialPtr get(LLViewerObject* object, S32 te_index) - { - return object->getTE(te_index)->getMaterialParams(); - } - } func; - identical_material = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &func, material_ptr); -} - -void LLPanelFace::LLSelectedTEMaterial::getMaxSpecularRepeats(F32& repeats, bool& identical) -{ - struct LLSelectedTEGetMaxSpecRepeats : public LLSelectedTEGetFunctor - { - F32 get(LLViewerObject* object, S32 face) - { - LLMaterial* mat = object->getTE(face)->getMaterialParams().get(); - U32 s_axis = VX; - U32 t_axis = VY; - F32 repeats_s = 1.0f; - F32 repeats_t = 1.0f; - if (mat) - { - mat->getSpecularRepeat(repeats_s, repeats_t); - repeats_s /= object->getScale().mV[s_axis]; - repeats_t /= object->getScale().mV[t_axis]; - } - return llmax(repeats_s, repeats_t); - } - - } max_spec_repeats_func; - identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &max_spec_repeats_func, repeats); -} - -void LLPanelFace::LLSelectedTEMaterial::getMaxNormalRepeats(F32& repeats, bool& identical) -{ - struct LLSelectedTEGetMaxNormRepeats : public LLSelectedTEGetFunctor - { - F32 get(LLViewerObject* object, S32 face) - { - LLMaterial* mat = object->getTE(face)->getMaterialParams().get(); - U32 s_axis = VX; - U32 t_axis = VY; - F32 repeats_s = 1.0f; - F32 repeats_t = 1.0f; - if (mat) - { - mat->getNormalRepeat(repeats_s, repeats_t); - repeats_s /= object->getScale().mV[s_axis]; - repeats_t /= object->getScale().mV[t_axis]; - } - return llmax(repeats_s, repeats_t); - } - - } max_norm_repeats_func; - identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &max_norm_repeats_func, repeats); -} - -void LLPanelFace::LLSelectedTEMaterial::getCurrentDiffuseAlphaMode(U8& diffuse_alpha_mode, bool& identical, bool diffuse_texture_has_alpha) -{ - struct LLSelectedTEGetDiffuseAlphaMode : public LLSelectedTEGetFunctor - { - LLSelectedTEGetDiffuseAlphaMode() : _isAlpha(false) {} - LLSelectedTEGetDiffuseAlphaMode(bool diffuse_texture_has_alpha) : _isAlpha(diffuse_texture_has_alpha) {} - virtual ~LLSelectedTEGetDiffuseAlphaMode() {} - - U8 get(LLViewerObject* object, S32 face) - { - U8 diffuse_mode = _isAlpha ? LLMaterial::DIFFUSE_ALPHA_MODE_BLEND : LLMaterial::DIFFUSE_ALPHA_MODE_NONE; - - LLTextureEntry* tep = object->getTE(face); - if (tep) - { - LLMaterial* mat = tep->getMaterialParams().get(); - if (mat) - { - diffuse_mode = mat->getDiffuseAlphaMode(); - } - } - - return diffuse_mode; - } - bool _isAlpha; // whether or not the diffuse texture selected contains alpha information - } get_diff_mode(diffuse_texture_has_alpha); - identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &get_diff_mode, diffuse_alpha_mode); -} - -void LLPanelFace::LLSelectedTE::getObjectScaleS(F32& scale_s, bool& identical) -{ - struct LLSelectedTEGetObjectScaleS : public LLSelectedTEGetFunctor - { - F32 get(LLViewerObject* object, S32 face) - { - U32 s_axis = VX; - U32 t_axis = VY; - LLPrimitive::getTESTAxes(face, &s_axis, &t_axis); - return object->getScale().mV[s_axis]; - } - - } scale_s_func; - identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &scale_s_func, scale_s ); -} - -void LLPanelFace::LLSelectedTE::getObjectScaleT(F32& scale_t, bool& identical) -{ - struct LLSelectedTEGetObjectScaleS : public LLSelectedTEGetFunctor - { - F32 get(LLViewerObject* object, S32 face) - { - U32 s_axis = VX; - U32 t_axis = VY; - LLPrimitive::getTESTAxes(face, &s_axis, &t_axis); - return object->getScale().mV[t_axis]; - } - - } scale_t_func; - identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &scale_t_func, scale_t ); -} - -void LLPanelFace::LLSelectedTE::getMaxDiffuseRepeats(F32& repeats, bool& identical) -{ - struct LLSelectedTEGetMaxDiffuseRepeats : public LLSelectedTEGetFunctor - { - F32 get(LLViewerObject* object, S32 face) - { - U32 s_axis = VX; - U32 t_axis = VY; - LLPrimitive::getTESTAxes(face, &s_axis, &t_axis); - F32 repeats_s = object->getTE(face)->mScaleS / object->getScale().mV[s_axis]; - F32 repeats_t = object->getTE(face)->mScaleT / object->getScale().mV[t_axis]; - return llmax(repeats_s, repeats_t); - } - - } max_diff_repeats_func; - identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &max_diff_repeats_func, repeats ); -} - +/** + * @file llpanelface.cpp + * @brief Panel in the tools floater for editing face textures, colors, etc. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +// file include +#include "llpanelface.h" + +// library includes +#include "llcalc.h" +#include "llerror.h" +#include "llrect.h" +#include "llstring.h" +#include "llfontgl.h" + +// project includes +#include "llagent.h" +#include "llagentdata.h" +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "llcolorswatch.h" +#include "llcombobox.h" +#include "lldrawpoolbump.h" +#include "llface.h" +#include "llgltfmateriallist.h" +#include "llinventoryfunctions.h" +#include "llinventorymodel.h" // gInventory +#include "llinventorymodelbackgroundfetch.h" +#include "llfloatermediasettings.h" +#include "llfloaterreg.h" +#include "llfloatertools.h" +#include "lllineeditor.h" +#include "llmaterialmgr.h" +#include "llmaterialeditor.h" +#include "llmediactrl.h" +#include "llmediaentry.h" +#include "llmenubutton.h" +#include "llnotificationsutil.h" +#include "llpanelcontents.h" +#include "llradiogroup.h" +#include "llresmgr.h" +#include "llselectmgr.h" +#include "llspinctrl.h" +#include "lltextbox.h" +#include "lltexturectrl.h" +#include "lltextureentry.h" +#include "lltooldraganddrop.h" +#include "lltoolface.h" +#include "lltoolmgr.h" +#include "lltrans.h" +#include "llui.h" +#include "llviewercontrol.h" +#include "llviewermedia.h" +#include "llviewerobject.h" +#include "llviewerregion.h" +#include "llviewerstats.h" +#include "llvovolume.h" +#include "llvoinventorylistener.h" +#include "lluictrlfactory.h" +#include "llpluginclassmedia.h" +#include "llviewertexturelist.h"// Update sel manager as to which channel we're editing so it can reflect the correct overlay UI + + + +#include "llagent.h" +#include "llfilesystem.h" +#include "llviewerassetupload.h" +#include "llviewermenufile.h" +#include "llsd.h" +#include "llsdutil.h" +#include "llsdserialize.h" +#include "llinventorymodel.h" + +using namespace std::literals; + +LLPanelFace::Selection LLPanelFace::sMaterialOverrideSelection; + +// +// Constant definitions for comboboxes +// Must match the commbobox definitions in panel_tools_texture.xml +// +const S32 MATMEDIA_MATERIAL = 0; // Material +const S32 MATMEDIA_PBR = 1; // PBR +const S32 MATMEDIA_MEDIA = 2; // Media +const S32 MATTYPE_DIFFUSE = 0; // Diffuse material texture +const S32 MATTYPE_NORMAL = 1; // Normal map +const S32 MATTYPE_SPECULAR = 2; // Specular map +const S32 ALPHAMODE_MASK = 2; // Alpha masking mode +const S32 BUMPY_TEXTURE = 18; // use supplied normal map +const S32 SHINY_TEXTURE = 4; // use supplied specular map +const S32 PBRTYPE_RENDER_MATERIAL_ID = 0; // Render Material ID +const S32 PBRTYPE_BASE_COLOR = 1; // PBR Base Color +const S32 PBRTYPE_METALLIC_ROUGHNESS = 2; // PBR Metallic +const S32 PBRTYPE_EMISSIVE = 3; // PBR Emissive +const S32 PBRTYPE_NORMAL = 4; // PBR Normal + +LLGLTFMaterial::TextureInfo texture_info_from_pbrtype(S32 pbr_type) +{ + switch (pbr_type) + { + case PBRTYPE_BASE_COLOR: + return LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR; + break; + case PBRTYPE_NORMAL: + return LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL; + break; + case PBRTYPE_METALLIC_ROUGHNESS: + return LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS; + break; + case PBRTYPE_EMISSIVE: + return LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE; + break; + default: + return LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; + break; + } +} + +void LLPanelFace::updateSelectedGLTFMaterials(std::function func) +{ + struct LLSelectedTEGLTFMaterialFunctor : public LLSelectedTEFunctor + { + LLSelectedTEGLTFMaterialFunctor(std::function func) : mFunc(func) {} + virtual ~LLSelectedTEGLTFMaterialFunctor() {}; + bool apply(LLViewerObject* object, S32 face) override + { + LLGLTFMaterial new_override; + const LLTextureEntry* tep = object->getTE(face); + if (tep->getGLTFMaterialOverride()) + { + new_override = *tep->getGLTFMaterialOverride(); + } + mFunc(&new_override); + LLGLTFMaterialList::queueModify(object, face, &new_override); + + return true; + } + + std::function mFunc; + } select_func(func); + + LLSelectMgr::getInstance()->getSelection()->applyToTEs(&select_func); +} + +template +void readSelectedGLTFMaterial(std::function func, T& value, bool& identical, bool has_tolerance, T tolerance) +{ + struct LLSelectedTEGetGLTFMaterialFunctor : public LLSelectedTEGetFunctor + { + LLSelectedTEGetGLTFMaterialFunctor(std::function func) : mFunc(func) {} + virtual ~LLSelectedTEGetGLTFMaterialFunctor() {}; + T get(LLViewerObject* object, S32 face) override + { + const LLTextureEntry* tep = object->getTE(face); + const LLGLTFMaterial* render_material = tep->getGLTFRenderMaterial(); + + return mFunc(render_material); + } + + std::function mFunc; + } select_func(func); + identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue(&select_func, value, has_tolerance, tolerance); +} + +BOOST_STATIC_ASSERT(MATTYPE_DIFFUSE == LLRender::DIFFUSE_MAP && MATTYPE_NORMAL == LLRender::NORMAL_MAP && MATTYPE_SPECULAR == LLRender::SPECULAR_MAP); + +// +// "Use texture" label for normal/specular type comboboxes +// Filled in at initialization from translated strings +// +std::string USE_TEXTURE; + +LLRender::eTexIndex LLPanelFace::getTextureChannelToEdit() +{ + LLRender::eTexIndex channel_to_edit = LLRender::DIFFUSE_MAP; + if (mComboMatMedia) + { + U32 matmedia_selection = mComboMatMedia->getCurrentIndex(); + if (matmedia_selection == MATMEDIA_MATERIAL) + { + LLRadioGroup* radio_mat_type = getChild("radio_material_type"); + channel_to_edit = (LLRender::eTexIndex)radio_mat_type->getSelectedIndex(); + } + if (matmedia_selection == MATMEDIA_PBR) + { + LLRadioGroup* radio_mat_type = getChild("radio_pbr_type"); + channel_to_edit = (LLRender::eTexIndex)radio_mat_type->getSelectedIndex(); + } + } + + channel_to_edit = (channel_to_edit == LLRender::NORMAL_MAP) ? (getCurrentNormalMap().isNull() ? LLRender::DIFFUSE_MAP : channel_to_edit) : channel_to_edit; + channel_to_edit = (channel_to_edit == LLRender::SPECULAR_MAP) ? (getCurrentSpecularMap().isNull() ? LLRender::DIFFUSE_MAP : channel_to_edit) : channel_to_edit; + return channel_to_edit; +} + +LLRender::eTexIndex LLPanelFace::getTextureDropChannel() +{ + if (mComboMatMedia && mComboMatMedia->getCurrentIndex() == MATMEDIA_MATERIAL) + { + LLRadioGroup* radio_mat_type = getChild("radio_material_type"); + return LLRender::eTexIndex(radio_mat_type->getSelectedIndex()); + } + + return LLRender::eTexIndex(MATTYPE_DIFFUSE); +} + +LLGLTFMaterial::TextureInfo LLPanelFace::getPBRDropChannel() +{ + if (mComboMatMedia && mComboMatMedia->getCurrentIndex() == MATMEDIA_PBR) + { + LLRadioGroup* radio_pbr_type = getChild("radio_pbr_type"); + return texture_info_from_pbrtype(radio_pbr_type->getSelectedIndex()); + } + + return texture_info_from_pbrtype(PBRTYPE_BASE_COLOR); +} + +// Things the UI provides... +// +LLUUID LLPanelFace::getCurrentNormalMap() { return getChild("bumpytexture control")->getImageAssetID(); } +LLUUID LLPanelFace::getCurrentSpecularMap() { return getChild("shinytexture control")->getImageAssetID(); } +U32 LLPanelFace::getCurrentShininess() { return getChild("combobox shininess")->getCurrentIndex(); } +U32 LLPanelFace::getCurrentBumpiness() { return getChild("combobox bumpiness")->getCurrentIndex(); } +U8 LLPanelFace::getCurrentDiffuseAlphaMode() { return (U8)getChild("combobox alphamode")->getCurrentIndex(); } +U8 LLPanelFace::getCurrentAlphaMaskCutoff() { return (U8)getChild("maskcutoff")->getValue().asInteger(); } +U8 LLPanelFace::getCurrentEnvIntensity() { return (U8)getChild("environment")->getValue().asInteger(); } +U8 LLPanelFace::getCurrentGlossiness() { return (U8)getChild("glossiness")->getValue().asInteger(); } +F32 LLPanelFace::getCurrentBumpyRot() { return getChild("bumpyRot")->getValue().asReal(); } +F32 LLPanelFace::getCurrentBumpyScaleU() { return getChild("bumpyScaleU")->getValue().asReal(); } +F32 LLPanelFace::getCurrentBumpyScaleV() { return getChild("bumpyScaleV")->getValue().asReal(); } +F32 LLPanelFace::getCurrentBumpyOffsetU() { return getChild("bumpyOffsetU")->getValue().asReal(); } +F32 LLPanelFace::getCurrentBumpyOffsetV() { return getChild("bumpyOffsetV")->getValue().asReal(); } +F32 LLPanelFace::getCurrentShinyRot() { return getChild("shinyRot")->getValue().asReal(); } +F32 LLPanelFace::getCurrentShinyScaleU() { return getChild("shinyScaleU")->getValue().asReal(); } +F32 LLPanelFace::getCurrentShinyScaleV() { return getChild("shinyScaleV")->getValue().asReal(); } +F32 LLPanelFace::getCurrentShinyOffsetU() { return getChild("shinyOffsetU")->getValue().asReal(); } +F32 LLPanelFace::getCurrentShinyOffsetV() { return getChild("shinyOffsetV")->getValue().asReal(); } + +// +// Methods +// + +bool LLPanelFace::postBuild() +{ + childSetCommitCallback("combobox shininess",&LLPanelFace::onCommitShiny,this); + childSetCommitCallback("combobox bumpiness",&LLPanelFace::onCommitBump,this); + childSetCommitCallback("combobox alphamode",&LLPanelFace::onCommitAlphaMode,this); + childSetCommitCallback("TexScaleU",&LLPanelFace::onCommitTextureScaleX, this); + childSetCommitCallback("TexScaleV",&LLPanelFace::onCommitTextureScaleY, this); + childSetCommitCallback("TexRot",&LLPanelFace::onCommitTextureRot, this); + childSetCommitCallback("rptctrl",&LLPanelFace::onCommitRepeatsPerMeter, this); + childSetCommitCallback("checkbox planar align",&LLPanelFace::onCommitPlanarAlign, this); + childSetCommitCallback("TexOffsetU",LLPanelFace::onCommitTextureOffsetX, this); + childSetCommitCallback("TexOffsetV",LLPanelFace::onCommitTextureOffsetY, this); + + childSetCommitCallback("bumpyScaleU",&LLPanelFace::onCommitMaterialBumpyScaleX, this); + childSetCommitCallback("bumpyScaleV",&LLPanelFace::onCommitMaterialBumpyScaleY, this); + childSetCommitCallback("bumpyRot",&LLPanelFace::onCommitMaterialBumpyRot, this); + childSetCommitCallback("bumpyOffsetU",&LLPanelFace::onCommitMaterialBumpyOffsetX, this); + childSetCommitCallback("bumpyOffsetV",&LLPanelFace::onCommitMaterialBumpyOffsetY, this); + childSetCommitCallback("shinyScaleU",&LLPanelFace::onCommitMaterialShinyScaleX, this); + childSetCommitCallback("shinyScaleV",&LLPanelFace::onCommitMaterialShinyScaleY, this); + childSetCommitCallback("shinyRot",&LLPanelFace::onCommitMaterialShinyRot, this); + childSetCommitCallback("shinyOffsetU",&LLPanelFace::onCommitMaterialShinyOffsetX, this); + childSetCommitCallback("shinyOffsetV",&LLPanelFace::onCommitMaterialShinyOffsetY, this); + childSetCommitCallback("glossiness",&LLPanelFace::onCommitMaterialGloss, this); + childSetCommitCallback("environment",&LLPanelFace::onCommitMaterialEnv, this); + childSetCommitCallback("maskcutoff",&LLPanelFace::onCommitMaterialMaskCutoff, this); + childSetCommitCallback("add_media", &LLPanelFace::onClickBtnAddMedia, this); + childSetCommitCallback("delete_media", &LLPanelFace::onClickBtnDeleteMedia, this); + + getChild("gltfTextureScaleU")->setCommitCallback(boost::bind(&LLPanelFace::onCommitGLTFTextureScaleU, this, _1), nullptr); + getChild("gltfTextureScaleV")->setCommitCallback(boost::bind(&LLPanelFace::onCommitGLTFTextureScaleV, this, _1), nullptr); + getChild("gltfTextureRotation")->setCommitCallback(boost::bind(&LLPanelFace::onCommitGLTFRotation, this, _1), nullptr); + getChild("gltfTextureOffsetU")->setCommitCallback(boost::bind(&LLPanelFace::onCommitGLTFTextureOffsetU, this, _1), nullptr); + getChild("gltfTextureOffsetV")->setCommitCallback(boost::bind(&LLPanelFace::onCommitGLTFTextureOffsetV, this, _1), nullptr); + + LLGLTFMaterialList::addSelectionUpdateCallback(&LLPanelFace::onMaterialOverrideReceived); + sMaterialOverrideSelection.connect(); + + childSetAction("button align",&LLPanelFace::onClickAutoFix,this); + childSetAction("button align textures", &LLPanelFace::onAlignTexture, this); + childSetAction("pbr_from_inventory", &LLPanelFace::onClickBtnLoadInvPBR, this); + childSetAction("edit_selected_pbr", &LLPanelFace::onClickBtnEditPBR, this); + childSetAction("save_selected_pbr", &LLPanelFace::onClickBtnSavePBR, this); + + LLTextureCtrl* mTextureCtrl; + LLTextureCtrl* mShinyTextureCtrl; + LLTextureCtrl* mBumpyTextureCtrl; + LLColorSwatchCtrl* mColorSwatch; + LLColorSwatchCtrl* mShinyColorSwatch; + + LLComboBox* mComboTexGen; + + LLCheckBoxCtrl *mCheckFullbright; + + LLTextBox* mLabelColorTransp; + LLSpinCtrl* mCtrlColorTransp; // transparency = 1 - alpha + + LLSpinCtrl* mCtrlGlow; + + setMouseOpaque(false); + + LLTextureCtrl* pbr_ctrl = findChild("pbr_control"); + if (pbr_ctrl) + { + pbr_ctrl->setDefaultImageAssetID(LLUUID::null); + pbr_ctrl->setBlankImageAssetID(LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID); + pbr_ctrl->setCommitCallback(boost::bind(&LLPanelFace::onCommitPbr, this, _2)); + pbr_ctrl->setOnCancelCallback(boost::bind(&LLPanelFace::onCancelPbr, this, _2)); + pbr_ctrl->setOnSelectCallback(boost::bind(&LLPanelFace::onSelectPbr, this, _2)); + pbr_ctrl->setDragCallback(boost::bind(&LLPanelFace::onDragPbr, this, _2)); + pbr_ctrl->setOnTextureSelectedCallback(boost::bind(&LLPanelFace::onPbrSelectionChanged, this, _1)); + pbr_ctrl->setOnCloseCallback(boost::bind(&LLPanelFace::onCloseTexturePicker, this, _2)); + + pbr_ctrl->setFollowsTop(); + pbr_ctrl->setFollowsLeft(); + pbr_ctrl->setImmediateFilterPermMask(PERM_NONE); + pbr_ctrl->setDnDFilterPermMask(PERM_COPY | PERM_TRANSFER); + pbr_ctrl->setBakeTextureEnabled(false); + pbr_ctrl->setInventoryPickType(PICK_MATERIAL); + } + + mTextureCtrl = getChild("texture control"); + if(mTextureCtrl) + { + mTextureCtrl->setDefaultImageAssetID(DEFAULT_OBJECT_TEXTURE); + mTextureCtrl->setCommitCallback( boost::bind(&LLPanelFace::onCommitTexture, this, _2) ); + mTextureCtrl->setOnCancelCallback( boost::bind(&LLPanelFace::onCancelTexture, this, _2) ); + mTextureCtrl->setOnSelectCallback( boost::bind(&LLPanelFace::onSelectTexture, this, _2) ); + mTextureCtrl->setDragCallback(boost::bind(&LLPanelFace::onDragTexture, this, _2)); + mTextureCtrl->setOnTextureSelectedCallback(boost::bind(&LLPanelFace::onTextureSelectionChanged, this, _1)); + mTextureCtrl->setOnCloseCallback( boost::bind(&LLPanelFace::onCloseTexturePicker, this, _2) ); + + mTextureCtrl->setFollowsTop(); + mTextureCtrl->setFollowsLeft(); + mTextureCtrl->setImmediateFilterPermMask(PERM_NONE); + mTextureCtrl->setDnDFilterPermMask(PERM_COPY | PERM_TRANSFER); + } + + mShinyTextureCtrl = getChild("shinytexture control"); + if(mShinyTextureCtrl) + { + mShinyTextureCtrl->setDefaultImageAssetID(DEFAULT_OBJECT_SPECULAR); + mShinyTextureCtrl->setCommitCallback( boost::bind(&LLPanelFace::onCommitSpecularTexture, this, _2) ); + mShinyTextureCtrl->setOnCancelCallback( boost::bind(&LLPanelFace::onCancelSpecularTexture, this, _2) ); + mShinyTextureCtrl->setOnSelectCallback( boost::bind(&LLPanelFace::onSelectSpecularTexture, this, _2) ); + mShinyTextureCtrl->setOnCloseCallback( boost::bind(&LLPanelFace::onCloseTexturePicker, this, _2) ); + + mShinyTextureCtrl->setDragCallback(boost::bind(&LLPanelFace::onDragTexture, this, _2)); + mShinyTextureCtrl->setOnTextureSelectedCallback(boost::bind(&LLPanelFace::onTextureSelectionChanged, this, _1)); + mShinyTextureCtrl->setFollowsTop(); + mShinyTextureCtrl->setFollowsLeft(); + mShinyTextureCtrl->setImmediateFilterPermMask(PERM_NONE); + mShinyTextureCtrl->setDnDFilterPermMask(PERM_COPY | PERM_TRANSFER); + } + + mBumpyTextureCtrl = getChild("bumpytexture control"); + if(mBumpyTextureCtrl) + { + mBumpyTextureCtrl->setDefaultImageAssetID(DEFAULT_OBJECT_NORMAL); + mBumpyTextureCtrl->setBlankImageAssetID(BLANK_OBJECT_NORMAL); + mBumpyTextureCtrl->setCommitCallback( boost::bind(&LLPanelFace::onCommitNormalTexture, this, _2) ); + mBumpyTextureCtrl->setOnCancelCallback( boost::bind(&LLPanelFace::onCancelNormalTexture, this, _2) ); + mBumpyTextureCtrl->setOnSelectCallback( boost::bind(&LLPanelFace::onSelectNormalTexture, this, _2) ); + mBumpyTextureCtrl->setOnCloseCallback( boost::bind(&LLPanelFace::onCloseTexturePicker, this, _2) ); + + mBumpyTextureCtrl->setDragCallback(boost::bind(&LLPanelFace::onDragTexture, this, _2)); + mBumpyTextureCtrl->setOnTextureSelectedCallback(boost::bind(&LLPanelFace::onTextureSelectionChanged, this, _1)); + mBumpyTextureCtrl->setFollowsTop(); + mBumpyTextureCtrl->setFollowsLeft(); + mBumpyTextureCtrl->setImmediateFilterPermMask(PERM_NONE); + mBumpyTextureCtrl->setDnDFilterPermMask(PERM_COPY | PERM_TRANSFER); + } + + mColorSwatch = getChild("colorswatch"); + if(mColorSwatch) + { + mColorSwatch->setCommitCallback(boost::bind(&LLPanelFace::onCommitColor, this, _2)); + mColorSwatch->setOnCancelCallback(boost::bind(&LLPanelFace::onCancelColor, this, _2)); + mColorSwatch->setOnSelectCallback(boost::bind(&LLPanelFace::onSelectColor, this, _2)); + mColorSwatch->setFollowsTop(); + mColorSwatch->setFollowsLeft(); + mColorSwatch->setCanApplyImmediately(true); + } + + mShinyColorSwatch = getChild("shinycolorswatch"); + if(mShinyColorSwatch) + { + mShinyColorSwatch->setCommitCallback(boost::bind(&LLPanelFace::onCommitShinyColor, this, _2)); + mShinyColorSwatch->setOnCancelCallback(boost::bind(&LLPanelFace::onCancelShinyColor, this, _2)); + mShinyColorSwatch->setOnSelectCallback(boost::bind(&LLPanelFace::onSelectShinyColor, this, _2)); + mShinyColorSwatch->setFollowsTop(); + mShinyColorSwatch->setFollowsLeft(); + mShinyColorSwatch->setCanApplyImmediately(true); + } + + mLabelColorTransp = getChild("color trans"); + if(mLabelColorTransp) + { + mLabelColorTransp->setFollowsTop(); + mLabelColorTransp->setFollowsLeft(); + } + + mCtrlColorTransp = getChild("ColorTrans"); + if(mCtrlColorTransp) + { + mCtrlColorTransp->setCommitCallback(boost::bind(&LLPanelFace::onCommitAlpha, this, _2)); + mCtrlColorTransp->setPrecision(0); + mCtrlColorTransp->setFollowsTop(); + mCtrlColorTransp->setFollowsLeft(); + } + + mCheckFullbright = getChild("checkbox fullbright"); + if (mCheckFullbright) + { + mCheckFullbright->setCommitCallback(LLPanelFace::onCommitFullbright, this); + } + + mComboTexGen = getChild("combobox texgen"); + if(mComboTexGen) + { + mComboTexGen->setCommitCallback(LLPanelFace::onCommitTexGen, this); + mComboTexGen->setFollows(FOLLOWS_LEFT | FOLLOWS_TOP); + } + + mComboMatMedia = getChild("combobox matmedia"); + if(mComboMatMedia) + { + mComboMatMedia->setCommitCallback(LLPanelFace::onCommitMaterialsMedia,this); + mComboMatMedia->selectNthItem(MATMEDIA_MATERIAL); + } + + LLRadioGroup* radio_mat_type = findChild("radio_material_type"); + if(radio_mat_type) + { + radio_mat_type->setCommitCallback(LLPanelFace::onCommitMaterialType, this); + radio_mat_type->selectNthItem(MATTYPE_DIFFUSE); + } + + LLRadioGroup* radio_pbr_type = findChild("radio_pbr_type"); + if (radio_pbr_type) + { + radio_pbr_type->setCommitCallback(LLPanelFace::onCommitPbrType, this); + radio_pbr_type->selectNthItem(PBRTYPE_RENDER_MATERIAL_ID); + } + + mCtrlGlow = getChild("glow"); + if(mCtrlGlow) + { + mCtrlGlow->setCommitCallback(LLPanelFace::onCommitGlow, this); + } + + mMenuClipboardColor = getChild("clipboard_color_params_btn"); + mMenuClipboardTexture = getChild("clipboard_texture_params_btn"); + + mTitleMedia = getChild("title_media"); + mTitleMediaText = getChild("media_info"); + + clearCtrls(); + + return true; +} + +LLPanelFace::LLPanelFace() +: LLPanel(), + mIsAlpha(false), + mComboMatMedia(NULL), + mTitleMedia(NULL), + mTitleMediaText(NULL), + mNeedMediaTitle(true) +{ + USE_TEXTURE = LLTrans::getString("use_texture"); + mCommitCallbackRegistrar.add("PanelFace.menuDoToSelected", boost::bind(&LLPanelFace::menuDoToSelected, this, _2)); + mEnableCallbackRegistrar.add("PanelFace.menuEnable", boost::bind(&LLPanelFace::menuEnableItem, this, _2)); +} + +LLPanelFace::~LLPanelFace() +{ + unloadMedia(); +} + +void LLPanelFace::onVisibilityChange(bool new_visibility) +{ + if (new_visibility) + { + gAgent.showLatestFeatureNotification("gltf"); + } + LLPanel::onVisibilityChange(new_visibility); +} + +void LLPanelFace::draw() +{ + updateCopyTexButton(); + + // grab media name/title and update the UI widget + // Todo: move it, it's preferable not to update + // labels inside draw + updateMediaTitle(); + + LLPanel::draw(); + + if (sMaterialOverrideSelection.update()) + { + setMaterialOverridesFromSelection(); + LLMaterialEditor::updateLive(); + } +} + +void LLPanelFace::sendTexture() +{ + LLTextureCtrl* mTextureCtrl = getChild("texture control"); + if(!mTextureCtrl) return; + if( !mTextureCtrl->getTentative() ) + { + // we grab the item id first, because we want to do a + // permissions check in the selection manager. ARGH! + LLUUID id = mTextureCtrl->getImageItemID(); + if(id.isNull()) + { + id = mTextureCtrl->getImageAssetID(); + } + if (!LLSelectMgr::getInstance()->selectionSetImage(id)) + { + // need to refresh value in texture ctrl + refresh(); + } + } +} + +void LLPanelFace::sendBump(U32 bumpiness) +{ + LLTextureCtrl* bumpytexture_ctrl = getChild("bumpytexture control"); + if (bumpiness < BUMPY_TEXTURE) +{ + LL_DEBUGS("Materials") << "clearing bumptexture control" << LL_ENDL; + bumpytexture_ctrl->clear(); + bumpytexture_ctrl->setImageAssetID(LLUUID()); + } + + updateBumpyControls(bumpiness == BUMPY_TEXTURE, true); + + LLUUID current_normal_map = bumpytexture_ctrl->getImageAssetID(); + + U8 bump = (U8) bumpiness & TEM_BUMP_MASK; + + // Clear legacy bump to None when using an actual normal map + // + if (!current_normal_map.isNull()) + bump = 0; + + // Set the normal map or reset it to null as appropriate + // + LLSelectedTEMaterial::setNormalID(this, current_normal_map); + + LLSelectMgr::getInstance()->selectionSetBumpmap( bump, bumpytexture_ctrl->getImageItemID() ); +} + +void LLPanelFace::sendTexGen() +{ + LLComboBox* mComboTexGen = getChild("combobox texgen"); + if(!mComboTexGen)return; + U8 tex_gen = (U8) mComboTexGen->getCurrentIndex() << TEM_TEX_GEN_SHIFT; + LLSelectMgr::getInstance()->selectionSetTexGen( tex_gen ); +} + +void LLPanelFace::sendShiny(U32 shininess) +{ + LLTextureCtrl* texture_ctrl = getChild("shinytexture control"); + + if (shininess < SHINY_TEXTURE) +{ + texture_ctrl->clear(); + texture_ctrl->setImageAssetID(LLUUID()); + } + + LLUUID specmap = getCurrentSpecularMap(); + + U8 shiny = (U8) shininess & TEM_SHINY_MASK; + if (!specmap.isNull()) + shiny = 0; + + LLSelectedTEMaterial::setSpecularID(this, specmap); + + LLSelectMgr::getInstance()->selectionSetShiny( shiny, texture_ctrl->getImageItemID() ); + + updateShinyControls(!specmap.isNull(), true); + +} + +void LLPanelFace::sendFullbright() +{ + LLCheckBoxCtrl* mCheckFullbright = getChild("checkbox fullbright"); + if(!mCheckFullbright)return; + U8 fullbright = mCheckFullbright->get() ? TEM_FULLBRIGHT_MASK : 0; + LLSelectMgr::getInstance()->selectionSetFullbright( fullbright ); +} + +void LLPanelFace::sendColor() +{ + + LLColorSwatchCtrl* mColorSwatch = getChild("colorswatch"); + if(!mColorSwatch)return; + LLColor4 color = mColorSwatch->get(); + + LLSelectMgr::getInstance()->selectionSetColorOnly( color ); +} + +void LLPanelFace::sendAlpha() +{ + LLSpinCtrl* mCtrlColorTransp = getChild("ColorTrans"); + if(!mCtrlColorTransp)return; + F32 alpha = (100.f - mCtrlColorTransp->get()) / 100.f; + + LLSelectMgr::getInstance()->selectionSetAlphaOnly( alpha ); +} + + +void LLPanelFace::sendGlow() +{ + LLSpinCtrl* mCtrlGlow = getChild("glow"); + llassert(mCtrlGlow); + if (mCtrlGlow) + { + F32 glow = mCtrlGlow->get(); + LLSelectMgr::getInstance()->selectionSetGlow( glow ); + } +} + +struct LLPanelFaceSetTEFunctor : public LLSelectedTEFunctor +{ + LLPanelFaceSetTEFunctor(LLPanelFace* panel) : mPanel(panel) {} + virtual bool apply(LLViewerObject* object, S32 te) + { + bool valid; + F32 value; + std::string prefix; + + // Effectively the same as MATMEDIA_PBR sans using different radio, + // separate for the sake of clarity + LLRadioGroup * radio_mat_type = mPanel->getChild("radio_material_type"); + switch (radio_mat_type->getSelectedIndex()) + { + case MATTYPE_DIFFUSE: + prefix = "Tex"; + break; + case MATTYPE_NORMAL: + prefix = "bumpy"; + break; + case MATTYPE_SPECULAR: + prefix = "shiny"; + break; + } + + LLSpinCtrl * ctrlTexScaleS = mPanel->getChild(prefix + "ScaleU"); + LLSpinCtrl * ctrlTexScaleT = mPanel->getChild(prefix + "ScaleV"); + LLSpinCtrl * ctrlTexOffsetS = mPanel->getChild(prefix + "OffsetU"); + LLSpinCtrl * ctrlTexOffsetT = mPanel->getChild(prefix + "OffsetV"); + LLSpinCtrl * ctrlTexRotation = mPanel->getChild(prefix + "Rot"); + + LLComboBox* comboTexGen = mPanel->getChild("combobox texgen"); + LLCheckBoxCtrl* cb_planar_align = mPanel->getChild("checkbox planar align"); + bool align_planar = (cb_planar_align && cb_planar_align->get()); + + llassert(comboTexGen); + llassert(object); + + if (ctrlTexScaleS) + { + valid = !ctrlTexScaleS->getTentative(); // || !checkFlipScaleS->getTentative(); + if (valid || align_planar) + { + value = ctrlTexScaleS->get(); + if (comboTexGen && + comboTexGen->getCurrentIndex() == 1) + { + value *= 0.5f; + } + object->setTEScaleS( te, value ); + + if (align_planar) + { + LLPanelFace::LLSelectedTEMaterial::setNormalRepeatX(mPanel, value, te, object->getID()); + LLPanelFace::LLSelectedTEMaterial::setSpecularRepeatX(mPanel, value, te, object->getID()); + } + } + } + + if (ctrlTexScaleT) + { + valid = !ctrlTexScaleT->getTentative(); // || !checkFlipScaleT->getTentative(); + if (valid || align_planar) + { + value = ctrlTexScaleT->get(); + //if( checkFlipScaleT->get() ) + //{ + // value = -value; + //} + if (comboTexGen && + comboTexGen->getCurrentIndex() == 1) + { + value *= 0.5f; + } + object->setTEScaleT( te, value ); + + if (align_planar) + { + LLPanelFace::LLSelectedTEMaterial::setNormalRepeatY(mPanel, value, te, object->getID()); + LLPanelFace::LLSelectedTEMaterial::setSpecularRepeatY(mPanel, value, te, object->getID()); + } + } + } + + if (ctrlTexOffsetS) + { + valid = !ctrlTexOffsetS->getTentative(); + if (valid || align_planar) + { + value = ctrlTexOffsetS->get(); + object->setTEOffsetS( te, value ); + + if (align_planar) + { + LLPanelFace::LLSelectedTEMaterial::setNormalOffsetX(mPanel, value, te, object->getID()); + LLPanelFace::LLSelectedTEMaterial::setSpecularOffsetX(mPanel, value, te, object->getID()); + } + } + } + + if (ctrlTexOffsetT) + { + valid = !ctrlTexOffsetT->getTentative(); + if (valid || align_planar) + { + value = ctrlTexOffsetT->get(); + object->setTEOffsetT( te, value ); + + if (align_planar) + { + LLPanelFace::LLSelectedTEMaterial::setNormalOffsetY(mPanel, value, te, object->getID()); + LLPanelFace::LLSelectedTEMaterial::setSpecularOffsetY(mPanel, value, te, object->getID()); + } + } + } + + if (ctrlTexRotation) + { + valid = !ctrlTexRotation->getTentative(); + if (valid || align_planar) + { + value = ctrlTexRotation->get() * DEG_TO_RAD; + object->setTERotation( te, value ); + + if (align_planar) + { + LLPanelFace::LLSelectedTEMaterial::setNormalRotation(mPanel, value, te, object->getID()); + LLPanelFace::LLSelectedTEMaterial::setSpecularRotation(mPanel, value, te, object->getID()); + } + } + } + return true; + } +private: + LLPanelFace* mPanel; +}; + +// Functor that aligns a face to mCenterFace +struct LLPanelFaceSetAlignedTEFunctor : public LLSelectedTEFunctor +{ + LLPanelFaceSetAlignedTEFunctor(LLPanelFace* panel, LLFace* center_face) : + mPanel(panel), + mCenterFace(center_face) {} + + virtual bool apply(LLViewerObject* object, S32 te) + { + LLFace* facep = object->mDrawable->getFace(te); + if (!facep) + { + return true; + } + + if (facep->getViewerObject()->getVolume()->getNumVolumeFaces() <= te) + { + return true; + } + + bool set_aligned = true; + if (facep == mCenterFace) + { + set_aligned = false; + } + if (set_aligned) + { + LLVector2 uv_offset, uv_scale; + F32 uv_rot; + set_aligned = facep->calcAlignedPlanarTE(mCenterFace, &uv_offset, &uv_scale, &uv_rot); + if (set_aligned) + { + object->setTEOffset(te, uv_offset.mV[VX], uv_offset.mV[VY]); + object->setTEScale(te, uv_scale.mV[VX], uv_scale.mV[VY]); + object->setTERotation(te, uv_rot); + + LLPanelFace::LLSelectedTEMaterial::setNormalRotation(mPanel, uv_rot, te, object->getID()); + LLPanelFace::LLSelectedTEMaterial::setSpecularRotation(mPanel, uv_rot, te, object->getID()); + + LLPanelFace::LLSelectedTEMaterial::setNormalOffsetX(mPanel, uv_offset.mV[VX], te, object->getID()); + LLPanelFace::LLSelectedTEMaterial::setNormalOffsetY(mPanel, uv_offset.mV[VY], te, object->getID()); + LLPanelFace::LLSelectedTEMaterial::setNormalRepeatX(mPanel, uv_scale.mV[VX], te, object->getID()); + LLPanelFace::LLSelectedTEMaterial::setNormalRepeatY(mPanel, uv_scale.mV[VY], te, object->getID()); + + LLPanelFace::LLSelectedTEMaterial::setSpecularOffsetX(mPanel, uv_offset.mV[VX], te, object->getID()); + LLPanelFace::LLSelectedTEMaterial::setSpecularOffsetY(mPanel, uv_offset.mV[VY], te, object->getID()); + LLPanelFace::LLSelectedTEMaterial::setSpecularRepeatX(mPanel, uv_scale.mV[VX], te, object->getID()); + LLPanelFace::LLSelectedTEMaterial::setSpecularRepeatY(mPanel, uv_scale.mV[VY], te, object->getID()); + } + } + if (!set_aligned) + { + LLPanelFaceSetTEFunctor setfunc(mPanel); + setfunc.apply(object, te); + } + return true; + } +private: + LLPanelFace* mPanel; + LLFace* mCenterFace; +}; + +struct LLPanelFaceSetAlignedConcreteTEFunctor : public LLSelectedTEFunctor +{ + LLPanelFaceSetAlignedConcreteTEFunctor(LLPanelFace* panel, LLFace* center_face, LLRender::eTexIndex map) : + mPanel(panel), + mChefFace(center_face), + mMap(map) + {} + + virtual bool apply(LLViewerObject* object, S32 te) + { + LLFace* facep = object->mDrawable->getFace(te); + if (!facep) + { + return true; + } + + if (facep->getViewerObject()->getVolume()->getNumVolumeFaces() <= te) + { + return true; + } + + if (mChefFace != facep) + { + LLVector2 uv_offset, uv_scale; + F32 uv_rot; + if (facep->calcAlignedPlanarTE(mChefFace, &uv_offset, &uv_scale, &uv_rot, mMap)) + { + switch (mMap) + { + case LLRender::DIFFUSE_MAP: + object->setTEOffset(te, uv_offset.mV[VX], uv_offset.mV[VY]); + object->setTEScale(te, uv_scale.mV[VX], uv_scale.mV[VY]); + object->setTERotation(te, uv_rot); + break; + case LLRender::NORMAL_MAP: + LLPanelFace::LLSelectedTEMaterial::setNormalRotation(mPanel, uv_rot, te, object->getID()); + LLPanelFace::LLSelectedTEMaterial::setNormalOffsetX(mPanel, uv_offset.mV[VX], te, object->getID()); + LLPanelFace::LLSelectedTEMaterial::setNormalOffsetY(mPanel, uv_offset.mV[VY], te, object->getID()); + LLPanelFace::LLSelectedTEMaterial::setNormalRepeatX(mPanel, uv_scale.mV[VX], te, object->getID()); + LLPanelFace::LLSelectedTEMaterial::setNormalRepeatY(mPanel, uv_scale.mV[VY], te, object->getID()); + break; + case LLRender::SPECULAR_MAP: + LLPanelFace::LLSelectedTEMaterial::setSpecularRotation(mPanel, uv_rot, te, object->getID()); + LLPanelFace::LLSelectedTEMaterial::setSpecularOffsetX(mPanel, uv_offset.mV[VX], te, object->getID()); + LLPanelFace::LLSelectedTEMaterial::setSpecularOffsetY(mPanel, uv_offset.mV[VY], te, object->getID()); + LLPanelFace::LLSelectedTEMaterial::setSpecularRepeatX(mPanel, uv_scale.mV[VX], te, object->getID()); + LLPanelFace::LLSelectedTEMaterial::setSpecularRepeatY(mPanel, uv_scale.mV[VY], te, object->getID()); + break; + default: /*make compiler happy*/ + break; + } + } + } + + return true; + } +private: + LLPanelFace* mPanel; + LLFace* mChefFace; + LLRender::eTexIndex mMap; +}; + +// Functor that tests if a face is aligned to mCenterFace +struct LLPanelFaceGetIsAlignedTEFunctor : public LLSelectedTEFunctor +{ + LLPanelFaceGetIsAlignedTEFunctor(LLFace* center_face) : + mCenterFace(center_face) {} + + virtual bool apply(LLViewerObject* object, S32 te) + { + LLFace* facep = object->mDrawable->getFace(te); + if (!facep) + { + return false; + } + + if (facep->getViewerObject()->getVolume()->getNumVolumeFaces() <= te) + { //volume face does not exist, can't be aligned + return false; + } + + if (facep == mCenterFace) + { + return true; + } + + LLVector2 aligned_st_offset, aligned_st_scale; + F32 aligned_st_rot; + if ( facep->calcAlignedPlanarTE(mCenterFace, &aligned_st_offset, &aligned_st_scale, &aligned_st_rot) ) + { + const LLTextureEntry* tep = facep->getTextureEntry(); + LLVector2 st_offset, st_scale; + tep->getOffset(&st_offset.mV[VX], &st_offset.mV[VY]); + tep->getScale(&st_scale.mV[VX], &st_scale.mV[VY]); + F32 st_rot = tep->getRotation(); + + bool eq_offset_x = is_approx_equal_fraction(st_offset.mV[VX], aligned_st_offset.mV[VX], 12); + bool eq_offset_y = is_approx_equal_fraction(st_offset.mV[VY], aligned_st_offset.mV[VY], 12); + bool eq_scale_x = is_approx_equal_fraction(st_scale.mV[VX], aligned_st_scale.mV[VX], 12); + bool eq_scale_y = is_approx_equal_fraction(st_scale.mV[VY], aligned_st_scale.mV[VY], 12); + bool eq_rot = is_approx_equal_fraction(st_rot, aligned_st_rot, 6); + + // needs a fuzzy comparison, because of fp errors + if (eq_offset_x && + eq_offset_y && + eq_scale_x && + eq_scale_y && + eq_rot) + { + return true; + } + } + return false; + } +private: + LLFace* mCenterFace; +}; + +struct LLPanelFaceSendFunctor : public LLSelectedObjectFunctor +{ + virtual bool apply(LLViewerObject* object) + { + object->sendTEUpdate(); + return true; + } +}; + +void LLPanelFace::sendTextureInfo() +{ + if ((bool)childGetValue("checkbox planar align").asBoolean()) + { + LLFace* last_face = NULL; + bool identical_face =false; + LLSelectedTE::getFace(last_face, identical_face); + LLPanelFaceSetAlignedTEFunctor setfunc(this, last_face); + LLSelectMgr::getInstance()->getSelection()->applyToTEs(&setfunc); + } + else + { + LLPanelFaceSetTEFunctor setfunc(this); + LLSelectMgr::getInstance()->getSelection()->applyToTEs(&setfunc); + } + + LLPanelFaceSendFunctor sendfunc; + LLSelectMgr::getInstance()->getSelection()->applyToObjects(&sendfunc); +} + +void LLPanelFace::alignTestureLayer() +{ + LLFace* last_face = NULL; + bool identical_face = false; + LLSelectedTE::getFace(last_face, identical_face); + + LLRadioGroup * radio_mat_type = getChild("radio_material_type"); + LLPanelFaceSetAlignedConcreteTEFunctor setfunc(this, last_face, static_cast(radio_mat_type->getSelectedIndex())); + LLSelectMgr::getInstance()->getSelection()->applyToTEs(&setfunc); +} + +void LLPanelFace::getState() +{ + updateUI(); +} + +void LLPanelFace::updateUI(bool force_set_values /*false*/) +{ //set state of UI to match state of texture entry(ies) (calls setEnabled, setValue, etc, but NOT setVisible) + LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode(); + LLViewerObject* objectp = node ? node->getObject() : NULL; + + if (objectp + && objectp->getPCode() == LL_PCODE_VOLUME + && objectp->permModify()) + { + bool editable = objectp->permModify() && !objectp->isPermanentEnforced(); + bool attachment = objectp->isAttachment(); + + bool has_pbr_material; + bool has_faces_without_pbr; + updateUIGLTF(objectp, has_pbr_material, has_faces_without_pbr, force_set_values); + + const bool has_material = !has_pbr_material; + + // only turn on auto-adjust button if there is a media renderer and the media is loaded + childSetEnabled("button align", editable); + + if (mComboMatMedia->getCurrentIndex() < MATMEDIA_MATERIAL) + { + // When selecting an object with a pbr and UI combo is not set, + // set to pbr option, otherwise to a texture (material) + if (has_pbr_material) + { + mComboMatMedia->selectNthItem(MATMEDIA_PBR); + } + else + { + mComboMatMedia->selectNthItem(MATMEDIA_MATERIAL); + } + } + + // *NOTE: The "identical" variable is currently only used to decide if + // the texgen control should be tentative - this is not used by GLTF + // materials. -Cosmic;2022-11-09 + bool identical = true; // true because it is anded below + bool identical_diffuse = false; + bool identical_norm = false; + bool identical_spec = false; + + LLTextureCtrl *texture_ctrl = getChild("texture control"); + LLTextureCtrl *shinytexture_ctrl = getChild("shinytexture control"); + LLTextureCtrl *bumpytexture_ctrl = getChild("bumpytexture control"); + + LLUUID id; + LLUUID normmap_id; + LLUUID specmap_id; + + LLSelectedTE::getTexId(id, identical_diffuse); + LLSelectedTEMaterial::getNormalID(normmap_id, identical_norm); + LLSelectedTEMaterial::getSpecularID(specmap_id, identical_spec); + + static S32 selected_te = -1; + static LLUUID prev_obj_id; + if ((LLToolFace::getInstance() == LLToolMgr::getInstance()->getCurrentTool()) && + !LLSelectMgr::getInstance()->getSelection()->isMultipleTESelected()) + { + S32 new_selection = -1; // Don't use getLastSelectedTE, it could have been deselected + S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); + for (S32 te = 0; te < num_tes; ++te) + { + if (node->isTESelected(te)) + { + new_selection = te; + break; + } + } + + if ((new_selection != selected_te) + || (prev_obj_id != objectp->getID())) + { + bool te_has_media = objectp->getTE(new_selection) && objectp->getTE(new_selection)->hasMedia(); + bool te_has_pbr = objectp->getRenderMaterialID(new_selection).notNull(); + + if (te_has_pbr && !((mComboMatMedia->getCurrentIndex() == MATMEDIA_MEDIA) && te_has_media)) + { + mComboMatMedia->selectNthItem(MATMEDIA_PBR); + } + else if (te_has_media) + { + mComboMatMedia->selectNthItem(MATMEDIA_MEDIA); + } + else if (id.notNull() || normmap_id.notNull() || specmap_id.notNull()) + { + mComboMatMedia->selectNthItem(MATMEDIA_MATERIAL); + } + selected_te = new_selection; + prev_obj_id = objectp->getID(); + } + } + else + { + if (prev_obj_id != objectp->getID()) + { + if (has_pbr_material && (mComboMatMedia->getCurrentIndex() == MATMEDIA_MATERIAL)) + { + mComboMatMedia->selectNthItem(MATMEDIA_PBR); + } + else if (!has_pbr_material && (mComboMatMedia->getCurrentIndex() == MATMEDIA_PBR)) + { + mComboMatMedia->selectNthItem(MATMEDIA_MATERIAL); + } + prev_obj_id = objectp->getID(); + } + } + mComboMatMedia->setEnabled(editable); + + LLRadioGroup* radio_mat_type = getChild("radio_material_type"); + if (radio_mat_type->getSelectedIndex() < MATTYPE_DIFFUSE) + { + radio_mat_type->selectNthItem(MATTYPE_DIFFUSE); + } + radio_mat_type->setEnabled(editable); + + LLRadioGroup* radio_pbr_type = getChild("radio_pbr_type"); + if (radio_pbr_type->getSelectedIndex() < PBRTYPE_RENDER_MATERIAL_ID) + { + radio_pbr_type->selectNthItem(PBRTYPE_RENDER_MATERIAL_ID); + } + radio_pbr_type->setEnabled(editable); + const bool pbr_selected = mComboMatMedia->getCurrentIndex() == MATMEDIA_PBR; + const bool texture_info_selected = pbr_selected && radio_pbr_type->getSelectedIndex() != PBRTYPE_RENDER_MATERIAL_ID; + + getChildView("checkbox_sync_settings")->setEnabled(editable); + childSetValue("checkbox_sync_settings", gSavedSettings.getBOOL("SyncMaterialSettings")); + + updateVisibility(objectp); + + // Color swatch + { + getChildView("color label")->setEnabled(editable); + } + LLColorSwatchCtrl* color_swatch = findChild("colorswatch"); + + LLColor4 color = LLColor4::white; + bool identical_color = false; + + if (color_swatch) + { + LLSelectedTE::getColor(color, identical_color); + LLColor4 prev_color = color_swatch->get(); + + color_swatch->setOriginal(color); + color_swatch->set(color, force_set_values || (prev_color != color) || !editable); + + color_swatch->setValid(editable && !has_pbr_material); + color_swatch->setEnabled( editable && !has_pbr_material); + color_swatch->setCanApplyImmediately( editable && !has_pbr_material); + } + + // Color transparency + getChildView("color trans")->setEnabled(editable); + + F32 transparency = (1.f - color.mV[VALPHA]) * 100.f; + getChild("ColorTrans")->setValue(editable ? transparency : 0); + getChildView("ColorTrans")->setEnabled(editable && has_material); + + U8 shiny = 0; + bool identical_shiny = false; + + // Shiny + LLSelectedTE::getShiny(shiny, identical_shiny); + identical = identical && identical_shiny; + + shiny = specmap_id.isNull() ? shiny : SHINY_TEXTURE; + + LLCtrlSelectionInterface* combobox_shininess = childGetSelectionInterface("combobox shininess"); + if (combobox_shininess) + { + combobox_shininess->selectNthItem((S32)shiny); + } + + getChildView("label shininess")->setEnabled(editable); + getChildView("combobox shininess")->setEnabled(editable); + + getChildView("label glossiness")->setEnabled(editable); + getChildView("glossiness")->setEnabled(editable); + + getChildView("label environment")->setEnabled(editable); + getChildView("environment")->setEnabled(editable); + getChildView("label shinycolor")->setEnabled(editable); + + getChild("combobox shininess")->setTentative(!identical_spec); + getChild("glossiness")->setTentative(!identical_spec); + getChild("environment")->setTentative(!identical_spec); + getChild("shinycolorswatch")->setTentative(!identical_spec); + + LLColorSwatchCtrl* mShinyColorSwatch = getChild("shinycolorswatch"); + if (mShinyColorSwatch) + { + mShinyColorSwatch->setValid(editable); + mShinyColorSwatch->setEnabled( editable ); + mShinyColorSwatch->setCanApplyImmediately( editable ); + } + + U8 bumpy = 0; + // Bumpy + { + bool identical_bumpy = false; + LLSelectedTE::getBumpmap(bumpy,identical_bumpy); + + LLUUID norm_map_id = getCurrentNormalMap(); + LLCtrlSelectionInterface* combobox_bumpiness = childGetSelectionInterface("combobox bumpiness"); + + bumpy = norm_map_id.isNull() ? bumpy : BUMPY_TEXTURE; + + if (combobox_bumpiness) + { + combobox_bumpiness->selectNthItem((S32)bumpy); + } + else + { + LL_WARNS() << "failed childGetSelectionInterface for 'combobox bumpiness'" << LL_ENDL; + } + + getChildView("combobox bumpiness")->setEnabled(editable); + getChild("combobox bumpiness")->setTentative(!identical_bumpy); + getChildView("label bumpiness")->setEnabled(editable); + } + + // Texture + { + mIsAlpha = false; + LLGLenum image_format = GL_RGB; + bool identical_image_format = false; + LLSelectedTE::getImageFormat(image_format, identical_image_format); + + mIsAlpha = false; + switch (image_format) + { + case GL_RGBA: + case GL_ALPHA: + { + mIsAlpha = true; + } + break; + + case GL_RGB: break; + default: + { + LL_WARNS() << "Unexpected tex format in LLPanelFace...resorting to no alpha" << LL_ENDL; + } + break; + } + + if (LLViewerMedia::getInstance()->textureHasMedia(id)) + { + getChildView("button align")->setEnabled(editable); + } + + // Diffuse Alpha Mode + + // Init to the default that is appropriate for the alpha content of the asset + // + U8 alpha_mode = mIsAlpha ? LLMaterial::DIFFUSE_ALPHA_MODE_BLEND : LLMaterial::DIFFUSE_ALPHA_MODE_NONE; + + bool identical_alpha_mode = false; + + // See if that's been overridden by a material setting for same... + // + LLSelectedTEMaterial::getCurrentDiffuseAlphaMode(alpha_mode, identical_alpha_mode, mIsAlpha); + + LLCtrlSelectionInterface* combobox_alphamode = childGetSelectionInterface("combobox alphamode"); + if (combobox_alphamode) + { + //it is invalid to have any alpha mode other than blend if transparency is greater than zero ... + // Want masking? Want emissive? Tough! You get BLEND! + alpha_mode = (transparency > 0.f) ? LLMaterial::DIFFUSE_ALPHA_MODE_BLEND : alpha_mode; + + // ... unless there is no alpha channel in the texture, in which case alpha mode MUST be none + alpha_mode = mIsAlpha ? alpha_mode : LLMaterial::DIFFUSE_ALPHA_MODE_NONE; + + combobox_alphamode->selectNthItem(alpha_mode); + } + else + { + LL_WARNS() << "failed childGetSelectionInterface for 'combobox alphamode'" << LL_ENDL; + } + + updateAlphaControls(); + + if (texture_ctrl) + { + if (identical_diffuse) + { + texture_ctrl->setTentative(false); + texture_ctrl->setEnabled(editable && !has_pbr_material); + texture_ctrl->setImageAssetID(id); + getChildView("combobox alphamode")->setEnabled(editable && mIsAlpha && transparency <= 0.f && !has_pbr_material); + getChildView("label alphamode")->setEnabled(editable && mIsAlpha && !has_pbr_material); + getChildView("maskcutoff")->setEnabled(editable && mIsAlpha && !has_pbr_material); + getChildView("label maskcutoff")->setEnabled(editable && mIsAlpha && !has_pbr_material); + + texture_ctrl->setBakeTextureEnabled(true); + } + else if (id.isNull()) + { + // None selected + texture_ctrl->setTentative(false); + texture_ctrl->setEnabled(false); + texture_ctrl->setImageAssetID(LLUUID::null); + getChildView("combobox alphamode")->setEnabled(false); + getChildView("label alphamode")->setEnabled(false); + getChildView("maskcutoff")->setEnabled(false); + getChildView("label maskcutoff")->setEnabled(false); + + texture_ctrl->setBakeTextureEnabled(false); + } + else + { + // Tentative: multiple selected with different textures + texture_ctrl->setTentative(true); + texture_ctrl->setEnabled(editable && !has_pbr_material); + texture_ctrl->setImageAssetID(id); + getChildView("combobox alphamode")->setEnabled(editable && mIsAlpha && transparency <= 0.f && !has_pbr_material); + getChildView("label alphamode")->setEnabled(editable && mIsAlpha && !has_pbr_material); + getChildView("maskcutoff")->setEnabled(editable && mIsAlpha && !has_pbr_material); + getChildView("label maskcutoff")->setEnabled(editable && mIsAlpha && !has_pbr_material); + + texture_ctrl->setBakeTextureEnabled(true); + } + + if (attachment) + { + // attachments are in world and in inventory, + // server doesn't support changing permissions + // in such case + texture_ctrl->setImmediateFilterPermMask(PERM_COPY | PERM_TRANSFER); + } + else + { + texture_ctrl->setImmediateFilterPermMask(PERM_NONE); + } + } + + if (shinytexture_ctrl) + { + shinytexture_ctrl->setTentative( !identical_spec ); + shinytexture_ctrl->setEnabled( editable && !has_pbr_material); + shinytexture_ctrl->setImageAssetID( specmap_id ); + + if (attachment) + { + shinytexture_ctrl->setImmediateFilterPermMask(PERM_COPY | PERM_TRANSFER); + } + else + { + shinytexture_ctrl->setImmediateFilterPermMask(PERM_NONE); + } + } + + if (bumpytexture_ctrl) + { + bumpytexture_ctrl->setTentative( !identical_norm ); + bumpytexture_ctrl->setEnabled( editable && !has_pbr_material); + bumpytexture_ctrl->setImageAssetID( normmap_id ); + + if (attachment) + { + bumpytexture_ctrl->setImmediateFilterPermMask(PERM_COPY | PERM_TRANSFER); + } + else + { + bumpytexture_ctrl->setImmediateFilterPermMask(PERM_NONE); + } + } + } + + // planar align + bool align_planar = false; + bool identical_planar_aligned = false; + { + LLCheckBoxCtrl* cb_planar_align = getChild("checkbox planar align"); + align_planar = (cb_planar_align && cb_planar_align->get()); + + bool enabled = (editable && isIdenticalPlanarTexgen() && !texture_info_selected); + childSetValue("checkbox planar align", align_planar && enabled); + childSetVisible("checkbox planar align", enabled); + childSetEnabled("checkbox planar align", enabled); + childSetEnabled("button align textures", enabled && LLSelectMgr::getInstance()->getSelection()->getObjectCount() > 1); + + if (align_planar && enabled) + { + LLFace* last_face = NULL; + bool identical_face = false; + LLSelectedTE::getFace(last_face, identical_face); + + LLPanelFaceGetIsAlignedTEFunctor get_is_aligend_func(last_face); + // this will determine if the texture param controls are tentative: + identical_planar_aligned = LLSelectMgr::getInstance()->getSelection()->applyToTEs(&get_is_aligend_func); + } + } + + // Needs to be public and before tex scale settings below to properly reflect + // behavior when in planar vs default texgen modes in the + // NORSPEC-84 et al + // + LLTextureEntry::e_texgen selected_texgen = LLTextureEntry::TEX_GEN_DEFAULT; + bool identical_texgen = true; + bool identical_planar_texgen = false; + + { + LLSelectedTE::getTexGen(selected_texgen, identical_texgen); + identical_planar_texgen = (identical_texgen && (selected_texgen == LLTextureEntry::TEX_GEN_PLANAR)); + } + + // Texture scale + { + bool identical_diff_scale_s = false; + bool identical_spec_scale_s = false; + bool identical_norm_scale_s = false; + + identical = align_planar ? identical_planar_aligned : identical; + + F32 diff_scale_s = 1.f; + F32 spec_scale_s = 1.f; + F32 norm_scale_s = 1.f; + + LLSelectedTE::getScaleS(diff_scale_s, identical_diff_scale_s); + LLSelectedTEMaterial::getSpecularRepeatX(spec_scale_s, identical_spec_scale_s); + LLSelectedTEMaterial::getNormalRepeatX(norm_scale_s, identical_norm_scale_s); + + diff_scale_s = editable ? diff_scale_s : 1.0f; + diff_scale_s *= identical_planar_texgen ? 2.0f : 1.0f; + + norm_scale_s = editable ? norm_scale_s : 1.0f; + norm_scale_s *= identical_planar_texgen ? 2.0f : 1.0f; + + spec_scale_s = editable ? spec_scale_s : 1.0f; + spec_scale_s *= identical_planar_texgen ? 2.0f : 1.0f; + + getChild("TexScaleU")->setValue(diff_scale_s); + getChild("shinyScaleU")->setValue(spec_scale_s); + getChild("bumpyScaleU")->setValue(norm_scale_s); + + getChildView("TexScaleU")->setEnabled(editable && has_material); + getChildView("shinyScaleU")->setEnabled(editable && has_material && specmap_id.notNull()); + getChildView("bumpyScaleU")->setEnabled(editable && has_material && normmap_id.notNull()); + + bool diff_scale_tentative = !(identical && identical_diff_scale_s); + bool norm_scale_tentative = !(identical && identical_norm_scale_s); + bool spec_scale_tentative = !(identical && identical_spec_scale_s); + + getChild("TexScaleU")->setTentative( LLSD(diff_scale_tentative)); + getChild("shinyScaleU")->setTentative(LLSD(spec_scale_tentative)); + getChild("bumpyScaleU")->setTentative(LLSD(norm_scale_tentative)); + } + + { + bool identical_diff_scale_t = false; + bool identical_spec_scale_t = false; + bool identical_norm_scale_t = false; + + F32 diff_scale_t = 1.f; + F32 spec_scale_t = 1.f; + F32 norm_scale_t = 1.f; + + LLSelectedTE::getScaleT(diff_scale_t, identical_diff_scale_t); + LLSelectedTEMaterial::getSpecularRepeatY(spec_scale_t, identical_spec_scale_t); + LLSelectedTEMaterial::getNormalRepeatY(norm_scale_t, identical_norm_scale_t); + + diff_scale_t = editable ? diff_scale_t : 1.0f; + diff_scale_t *= identical_planar_texgen ? 2.0f : 1.0f; + + norm_scale_t = editable ? norm_scale_t : 1.0f; + norm_scale_t *= identical_planar_texgen ? 2.0f : 1.0f; + + spec_scale_t = editable ? spec_scale_t : 1.0f; + spec_scale_t *= identical_planar_texgen ? 2.0f : 1.0f; + + bool diff_scale_tentative = !identical_diff_scale_t; + bool norm_scale_tentative = !identical_norm_scale_t; + bool spec_scale_tentative = !identical_spec_scale_t; + + getChildView("TexScaleV")->setEnabled(editable && has_material); + getChildView("shinyScaleV")->setEnabled(editable && has_material && specmap_id.notNull()); + getChildView("bumpyScaleV")->setEnabled(editable && has_material && normmap_id.notNull()); + + if (force_set_values) + { + getChild("TexScaleV")->forceSetValue(diff_scale_t); + } + else + { + getChild("TexScaleV")->setValue(diff_scale_t); + } + getChild("shinyScaleV")->setValue(norm_scale_t); + getChild("bumpyScaleV")->setValue(spec_scale_t); + + getChild("TexScaleV")->setTentative(LLSD(diff_scale_tentative)); + getChild("shinyScaleV")->setTentative(LLSD(norm_scale_tentative)); + getChild("bumpyScaleV")->setTentative(LLSD(spec_scale_tentative)); + } + + // Texture offset + { + bool identical_diff_offset_s = false; + bool identical_norm_offset_s = false; + bool identical_spec_offset_s = false; + + F32 diff_offset_s = 0.0f; + F32 norm_offset_s = 0.0f; + F32 spec_offset_s = 0.0f; + + LLSelectedTE::getOffsetS(diff_offset_s, identical_diff_offset_s); + LLSelectedTEMaterial::getNormalOffsetX(norm_offset_s, identical_norm_offset_s); + LLSelectedTEMaterial::getSpecularOffsetX(spec_offset_s, identical_spec_offset_s); + + bool diff_offset_u_tentative = !(align_planar ? identical_planar_aligned : identical_diff_offset_s); + bool norm_offset_u_tentative = !(align_planar ? identical_planar_aligned : identical_norm_offset_s); + bool spec_offset_u_tentative = !(align_planar ? identical_planar_aligned : identical_spec_offset_s); + + getChild("TexOffsetU")->setValue( editable ? diff_offset_s : 0.0f); + getChild("bumpyOffsetU")->setValue(editable ? norm_offset_s : 0.0f); + getChild("shinyOffsetU")->setValue(editable ? spec_offset_s : 0.0f); + + getChild("TexOffsetU")->setTentative(LLSD(diff_offset_u_tentative)); + getChild("shinyOffsetU")->setTentative(LLSD(norm_offset_u_tentative)); + getChild("bumpyOffsetU")->setTentative(LLSD(spec_offset_u_tentative)); + + getChildView("TexOffsetU")->setEnabled(editable && has_material); + getChildView("shinyOffsetU")->setEnabled(editable && has_material && specmap_id.notNull()); + getChildView("bumpyOffsetU")->setEnabled(editable && has_material && normmap_id.notNull()); + } + + { + bool identical_diff_offset_t = false; + bool identical_norm_offset_t = false; + bool identical_spec_offset_t = false; + + F32 diff_offset_t = 0.0f; + F32 norm_offset_t = 0.0f; + F32 spec_offset_t = 0.0f; + + LLSelectedTE::getOffsetT(diff_offset_t, identical_diff_offset_t); + LLSelectedTEMaterial::getNormalOffsetY(norm_offset_t, identical_norm_offset_t); + LLSelectedTEMaterial::getSpecularOffsetY(spec_offset_t, identical_spec_offset_t); + + bool diff_offset_v_tentative = !(align_planar ? identical_planar_aligned : identical_diff_offset_t); + bool norm_offset_v_tentative = !(align_planar ? identical_planar_aligned : identical_norm_offset_t); + bool spec_offset_v_tentative = !(align_planar ? identical_planar_aligned : identical_spec_offset_t); + + getChild("TexOffsetV")->setValue( editable ? diff_offset_t : 0.0f); + getChild("bumpyOffsetV")->setValue(editable ? norm_offset_t : 0.0f); + getChild("shinyOffsetV")->setValue(editable ? spec_offset_t : 0.0f); + + getChild("TexOffsetV")->setTentative(LLSD(diff_offset_v_tentative)); + getChild("shinyOffsetV")->setTentative(LLSD(norm_offset_v_tentative)); + getChild("bumpyOffsetV")->setTentative(LLSD(spec_offset_v_tentative)); + + getChildView("TexOffsetV")->setEnabled(editable && has_material); + getChildView("shinyOffsetV")->setEnabled(editable && has_material && specmap_id.notNull()); + getChildView("bumpyOffsetV")->setEnabled(editable && has_material && normmap_id.notNull()); + } + + // Texture rotation + { + bool identical_diff_rotation = false; + bool identical_norm_rotation = false; + bool identical_spec_rotation = false; + + F32 diff_rotation = 0.f; + F32 norm_rotation = 0.f; + F32 spec_rotation = 0.f; + + LLSelectedTE::getRotation(diff_rotation,identical_diff_rotation); + LLSelectedTEMaterial::getSpecularRotation(spec_rotation,identical_spec_rotation); + LLSelectedTEMaterial::getNormalRotation(norm_rotation,identical_norm_rotation); + + bool diff_rot_tentative = !(align_planar ? identical_planar_aligned : identical_diff_rotation); + bool norm_rot_tentative = !(align_planar ? identical_planar_aligned : identical_norm_rotation); + bool spec_rot_tentative = !(align_planar ? identical_planar_aligned : identical_spec_rotation); + + F32 diff_rot_deg = diff_rotation * RAD_TO_DEG; + F32 norm_rot_deg = norm_rotation * RAD_TO_DEG; + F32 spec_rot_deg = spec_rotation * RAD_TO_DEG; + + getChildView("TexRot")->setEnabled(editable && has_material); + getChildView("shinyRot")->setEnabled(editable && has_material && specmap_id.notNull()); + getChildView("bumpyRot")->setEnabled(editable && has_material && normmap_id.notNull()); + + getChild("TexRot")->setTentative(diff_rot_tentative); + getChild("shinyRot")->setTentative(LLSD(norm_rot_tentative)); + getChild("bumpyRot")->setTentative(LLSD(spec_rot_tentative)); + + getChild("TexRot")->setValue( editable ? diff_rot_deg : 0.0f); + getChild("shinyRot")->setValue(editable ? spec_rot_deg : 0.0f); + getChild("bumpyRot")->setValue(editable ? norm_rot_deg : 0.0f); + } + + { + F32 glow = 0.f; + bool identical_glow = false; + LLSelectedTE::getGlow(glow,identical_glow); + getChild("glow")->setValue(glow); + getChild("glow")->setTentative(!identical_glow); + getChildView("glow")->setEnabled(editable); + getChildView("glow label")->setEnabled(editable); + } + + { + LLCtrlSelectionInterface* combobox_texgen = childGetSelectionInterface("combobox texgen"); + if (combobox_texgen) + { + // Maps from enum to combobox entry index + combobox_texgen->selectNthItem(((S32)selected_texgen) >> 1); + } + else + { + LL_WARNS() << "failed childGetSelectionInterface for 'combobox texgen'" << LL_ENDL; + } + + getChildView("combobox texgen")->setEnabled(editable); + getChild("combobox texgen")->setTentative(!identical); + getChildView("tex gen")->setEnabled(editable); + } + + { + U8 fullbright_flag = 0; + bool identical_fullbright = false; + + LLSelectedTE::getFullbright(fullbright_flag,identical_fullbright); + + getChild("checkbox fullbright")->setValue((S32)(fullbright_flag != 0)); + getChildView("checkbox fullbright")->setEnabled(editable && !has_pbr_material); + getChild("checkbox fullbright")->setTentative(!identical_fullbright); + mComboMatMedia->setEnabledByValue("Materials", !has_pbr_material); + } + + // Repeats per meter + { + F32 repeats_diff = 1.f; + F32 repeats_norm = 1.f; + F32 repeats_spec = 1.f; + + bool identical_diff_repeats = false; + bool identical_norm_repeats = false; + bool identical_spec_repeats = false; + + LLSelectedTE::getMaxDiffuseRepeats(repeats_diff, identical_diff_repeats); + LLSelectedTEMaterial::getMaxNormalRepeats(repeats_norm, identical_norm_repeats); + LLSelectedTEMaterial::getMaxSpecularRepeats(repeats_spec, identical_spec_repeats); + + LLComboBox* mComboTexGen = getChild("combobox texgen"); + if (mComboTexGen) + { + S32 index = mComboTexGen ? mComboTexGen->getCurrentIndex() : 0; + bool enabled = editable && (index != 1); + bool identical_repeats = true; + S32 material_selection = mComboMatMedia->getCurrentIndex(); + F32 repeats = 1.0f; + + U32 material_type = MATTYPE_DIFFUSE; + if (material_selection == MATMEDIA_MATERIAL) + { + material_type = radio_mat_type->getSelectedIndex(); + } + else if (material_selection == MATMEDIA_PBR) + { + enabled = editable && has_pbr_material; + material_type = radio_pbr_type->getSelectedIndex(); + } + + switch (material_type) + { + default: + case MATTYPE_DIFFUSE: + { + if (material_selection != MATMEDIA_PBR) + { + enabled = editable && !id.isNull(); + } + identical_repeats = identical_diff_repeats; + repeats = repeats_diff; + } + break; + + case MATTYPE_SPECULAR: + { + if (material_selection != MATMEDIA_PBR) + { + enabled = (editable && ((shiny == SHINY_TEXTURE) && !specmap_id.isNull())); + } + identical_repeats = identical_spec_repeats; + repeats = repeats_spec; + } + break; + + case MATTYPE_NORMAL: + { + if (material_selection != MATMEDIA_PBR) + { + enabled = (editable && ((bumpy == BUMPY_TEXTURE) && !normmap_id.isNull())); + } + identical_repeats = identical_norm_repeats; + repeats = repeats_norm; + } + break; + } + + bool repeats_tentative = !identical_repeats; + + LLSpinCtrl* rpt_ctrl = getChild("rptctrl"); + if (force_set_values) + { + //onCommit, previosly edited element updates related ones + rpt_ctrl->forceSetValue(editable ? repeats : 1.0f); + } + else + { + rpt_ctrl->setValue(editable ? repeats : 1.0f); + } + rpt_ctrl->setTentative(LLSD(repeats_tentative)); + rpt_ctrl->setEnabled(has_material && !identical_planar_texgen && enabled); + } + } + + // Materials + { + LLMaterialPtr material; + LLSelectedTEMaterial::getCurrent(material, identical); + + if (material && editable) + { + LL_DEBUGS("Materials") << material->asLLSD() << LL_ENDL; + + // Alpha + LLCtrlSelectionInterface* combobox_alphamode = + childGetSelectionInterface("combobox alphamode"); + if (combobox_alphamode) + { + U32 alpha_mode = material->getDiffuseAlphaMode(); + + if (transparency > 0.f) + { //it is invalid to have any alpha mode other than blend if transparency is greater than zero ... + alpha_mode = LLMaterial::DIFFUSE_ALPHA_MODE_BLEND; + } + + if (!mIsAlpha) + { // ... unless there is no alpha channel in the texture, in which case alpha mode MUST ebe none + alpha_mode = LLMaterial::DIFFUSE_ALPHA_MODE_NONE; + } + + combobox_alphamode->selectNthItem(alpha_mode); + } + else + { + LL_WARNS() << "failed childGetSelectionInterface for 'combobox alphamode'" << LL_ENDL; + } + getChild("maskcutoff")->setValue(material->getAlphaMaskCutoff()); + updateAlphaControls(); + + identical_planar_texgen = isIdenticalPlanarTexgen(); + + // Shiny (specular) + F32 offset_x, offset_y, repeat_x, repeat_y, rot; + LLTextureCtrl* texture_ctrl = getChild("shinytexture control"); + texture_ctrl->setImageAssetID(material->getSpecularID()); + + if (!material->getSpecularID().isNull() && (shiny == SHINY_TEXTURE)) + { + material->getSpecularOffset(offset_x,offset_y); + material->getSpecularRepeat(repeat_x,repeat_y); + + if (identical_planar_texgen) + { + repeat_x *= 2.0f; + repeat_y *= 2.0f; + } + + rot = material->getSpecularRotation(); + getChild("shinyScaleU")->setValue(repeat_x); + getChild("shinyScaleV")->setValue(repeat_y); + getChild("shinyRot")->setValue(rot*RAD_TO_DEG); + getChild("shinyOffsetU")->setValue(offset_x); + getChild("shinyOffsetV")->setValue(offset_y); + getChild("glossiness")->setValue(material->getSpecularLightExponent()); + getChild("environment")->setValue(material->getEnvironmentIntensity()); + + updateShinyControls(!material->getSpecularID().isNull(), true); + } + + // Assert desired colorswatch color to match material AFTER updateShinyControls + // to avoid getting overwritten with the default on some UI state changes. + // + if (!material->getSpecularID().isNull()) + { + LLColorSwatchCtrl* shiny_swatch = getChild("shinycolorswatch"); + LLColor4 new_color = material->getSpecularLightColor(); + LLColor4 old_color = shiny_swatch->get(); + + shiny_swatch->setOriginal(new_color); + shiny_swatch->set(new_color, force_set_values || old_color != new_color || !editable); + } + + // Bumpy (normal) + texture_ctrl = getChild("bumpytexture control"); + texture_ctrl->setImageAssetID(material->getNormalID()); + + if (!material->getNormalID().isNull()) + { + material->getNormalOffset(offset_x,offset_y); + material->getNormalRepeat(repeat_x,repeat_y); + + if (identical_planar_texgen) + { + repeat_x *= 2.0f; + repeat_y *= 2.0f; + } + + rot = material->getNormalRotation(); + getChild("bumpyScaleU")->setValue(repeat_x); + getChild("bumpyScaleV")->setValue(repeat_y); + getChild("bumpyRot")->setValue(rot*RAD_TO_DEG); + getChild("bumpyOffsetU")->setValue(offset_x); + getChild("bumpyOffsetV")->setValue(offset_y); + + updateBumpyControls(!material->getNormalID().isNull(), true); + } + } + } + S32 selected_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount(); + bool single_volume = (selected_count == 1); + mMenuClipboardColor->setEnabled(editable && single_volume); + + // Set variable values for numeric expressions + LLCalc* calcp = LLCalc::getInstance(); + calcp->setVar(LLCalc::TEX_U_SCALE, childGetValue("TexScaleU").asReal()); + calcp->setVar(LLCalc::TEX_V_SCALE, childGetValue("TexScaleV").asReal()); + calcp->setVar(LLCalc::TEX_U_OFFSET, childGetValue("TexOffsetU").asReal()); + calcp->setVar(LLCalc::TEX_V_OFFSET, childGetValue("TexOffsetV").asReal()); + calcp->setVar(LLCalc::TEX_ROTATION, childGetValue("TexRot").asReal()); + calcp->setVar(LLCalc::TEX_TRANSPARENCY, childGetValue("ColorTrans").asReal()); + calcp->setVar(LLCalc::TEX_GLOW, childGetValue("glow").asReal()); + } + else + { + // Disable all UICtrls + clearCtrls(); + + // Disable non-UICtrls + LLTextureCtrl* pbr_ctrl = findChild("pbr_control"); + if (pbr_ctrl) + { + pbr_ctrl->setImageAssetID(LLUUID::null); + pbr_ctrl->setEnabled(false); + } + LLTextureCtrl* texture_ctrl = getChild("texture control"); + if (texture_ctrl) + { + texture_ctrl->setImageAssetID( LLUUID::null ); + texture_ctrl->setEnabled( false ); // this is a LLUICtrl, but we don't want it to have keyboard focus so we add it as a child, not a ctrl. +// texture_ctrl->setValid(false); + } + LLColorSwatchCtrl* mColorSwatch = getChild("colorswatch"); + if (mColorSwatch) + { + mColorSwatch->setEnabled( false ); + mColorSwatch->setFallbackImage(LLUI::getUIImage("locked_image.j2c") ); + mColorSwatch->setValid(false); + } + LLRadioGroup* radio_mat_type = getChild("radio_material_type"); + if (radio_mat_type) + { + radio_mat_type->setSelectedIndex(0); + } + getChildView("color trans")->setEnabled(false); + getChildView("rptctrl")->setEnabled(false); + getChildView("tex gen")->setEnabled(false); + getChildView("label shininess")->setEnabled(false); + getChildView("label bumpiness")->setEnabled(false); + getChildView("button align")->setEnabled(false); + getChildView("pbr_from_inventory")->setEnabled(false); + getChildView("edit_selected_pbr")->setEnabled(false); + getChildView("save_selected_pbr")->setEnabled(false); + + updateVisibility(); + + // Set variable values for numeric expressions + LLCalc* calcp = LLCalc::getInstance(); + calcp->clearVar(LLCalc::TEX_U_SCALE); + calcp->clearVar(LLCalc::TEX_V_SCALE); + calcp->clearVar(LLCalc::TEX_U_OFFSET); + calcp->clearVar(LLCalc::TEX_V_OFFSET); + calcp->clearVar(LLCalc::TEX_ROTATION); + calcp->clearVar(LLCalc::TEX_TRANSPARENCY); + calcp->clearVar(LLCalc::TEX_GLOW); + } +} + +// One-off listener that updates the build floater UI when the agent inventory adds or removes an item +class PBRPickerAgentListener : public LLInventoryObserver +{ +protected: + bool mChangePending = true; +public: + PBRPickerAgentListener() : LLInventoryObserver() + { + gInventory.addObserver(this); + } + + const bool isListening() + { + return mChangePending; + } + + void changed(U32 mask) override + { + if (!(mask & (ADD | REMOVE))) + { + return; + } + + if (gFloaterTools) + { + gFloaterTools->dirty(); + } + gInventory.removeObserver(this); + mChangePending = false; + } + + ~PBRPickerAgentListener() override + { + gInventory.removeObserver(this); + mChangePending = false; + } +}; + +// One-off listener that updates the build floater UI when the prim inventory updates +class PBRPickerObjectListener : public LLVOInventoryListener +{ +protected: + LLViewerObject* mObjectp; + bool mChangePending = true; +public: + + PBRPickerObjectListener(LLViewerObject* object) + : mObjectp(object) + { + registerVOInventoryListener(mObjectp, nullptr); + } + + const bool isListeningFor(const LLViewerObject* objectp) const + { + return mChangePending && (objectp == mObjectp); + } + + void inventoryChanged(LLViewerObject* object, + LLInventoryObject::object_list_t* inventory, + S32 serial_num, + void* user_data) override + { + if (gFloaterTools) + { + gFloaterTools->dirty(); + } + removeVOInventoryListener(); + mChangePending = false; + } + + ~PBRPickerObjectListener() + { + removeVOInventoryListener(); + mChangePending = false; + } +}; + +void LLPanelFace::updateUIGLTF(LLViewerObject* objectp, bool& has_pbr_material, bool& has_faces_without_pbr, bool force_set_values) +{ + has_pbr_material = false; + + bool has_pbr_capabilities = LLMaterialEditor::capabilitiesAvailable(); + bool identical_pbr = true; + const bool settable = has_pbr_capabilities && objectp->permModify() && !objectp->isPermanentEnforced(); + const bool editable = LLMaterialEditor::canModifyObjectsMaterial(); + const bool saveable = LLMaterialEditor::canSaveObjectsMaterial(); + + // pbr material + LLTextureCtrl* pbr_ctrl = findChild("pbr_control"); + LLUUID pbr_id; + if (pbr_ctrl) + { + LLSelectedTE::getPbrMaterialId(pbr_id, identical_pbr, has_pbr_material, has_faces_without_pbr); + + pbr_ctrl->setTentative(!identical_pbr); + pbr_ctrl->setEnabled(settable); + pbr_ctrl->setImageAssetID(pbr_id); + + if (objectp->isAttachment()) + { + pbr_ctrl->setFilterPermissionMasks(PERM_COPY | PERM_TRANSFER | PERM_MODIFY); + } + else + { + pbr_ctrl->setImmediateFilterPermMask(PERM_NONE); + } + } + + getChildView("pbr_from_inventory")->setEnabled(settable); + getChildView("edit_selected_pbr")->setEnabled(editable && !has_faces_without_pbr); + getChildView("save_selected_pbr")->setEnabled(saveable && identical_pbr); + if (objectp->isInventoryPending()) + { + // Reuse the same listener when possible + if (!mVOInventoryListener || !mVOInventoryListener->isListeningFor(objectp)) + { + mVOInventoryListener = std::make_unique(objectp); + } + } + else + { + mVOInventoryListener = nullptr; + } + if (!identical_pbr || pbr_id.isNull() || pbr_id == LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID) + { + mAgentInventoryListener = nullptr; + } + else + { + if (!mAgentInventoryListener || !mAgentInventoryListener->isListening()) + { + mAgentInventoryListener = std::make_unique(); + } + } + + const bool show_pbr = mComboMatMedia->getCurrentIndex() == MATMEDIA_PBR && mComboMatMedia->getEnabled(); + if (show_pbr) + { + const bool new_state = has_pbr_capabilities && has_pbr_material && !has_faces_without_pbr; + + LLUICtrl* gltfCtrlTextureScaleU = getChild("gltfTextureScaleU"); + LLUICtrl* gltfCtrlTextureScaleV = getChild("gltfTextureScaleV"); + LLUICtrl* gltfCtrlTextureRotation = getChild("gltfTextureRotation"); + LLUICtrl* gltfCtrlTextureOffsetU = getChild("gltfTextureOffsetU"); + LLUICtrl* gltfCtrlTextureOffsetV = getChild("gltfTextureOffsetV"); + + gltfCtrlTextureScaleU->setEnabled(new_state); + gltfCtrlTextureScaleV->setEnabled(new_state); + gltfCtrlTextureRotation->setEnabled(new_state); + gltfCtrlTextureOffsetU->setEnabled(new_state); + gltfCtrlTextureOffsetV->setEnabled(new_state); + + // Control values will be set once per frame in + // setMaterialOverridesFromSelection + sMaterialOverrideSelection.setDirty(); + } +} + +void LLPanelFace::updateVisibilityGLTF(LLViewerObject* objectp /*= nullptr */) +{ + const bool show_pbr = mComboMatMedia->getCurrentIndex() == MATMEDIA_PBR && mComboMatMedia->getEnabled(); + const bool inventory_pending = objectp && objectp->isInventoryPending(); + + LLRadioGroup* radio_pbr_type = findChild("radio_pbr_type"); + radio_pbr_type->setVisible(show_pbr); + + const U32 pbr_type = radio_pbr_type->getSelectedIndex(); + const bool show_pbr_render_material_id = show_pbr && (pbr_type == PBRTYPE_RENDER_MATERIAL_ID); + + getChildView("pbr_control")->setVisible(show_pbr_render_material_id); + + getChildView("pbr_from_inventory")->setVisible(show_pbr_render_material_id); + getChildView("edit_selected_pbr")->setVisible(show_pbr_render_material_id && !inventory_pending); + getChildView("save_selected_pbr")->setVisible(show_pbr_render_material_id && !inventory_pending); + getChildView("material_permissions_loading_label")->setVisible(show_pbr_render_material_id && inventory_pending); + + getChildView("gltfTextureScaleU")->setVisible(show_pbr); + getChildView("gltfTextureScaleV")->setVisible(show_pbr); + getChildView("gltfTextureRotation")->setVisible(show_pbr); + getChildView("gltfTextureOffsetU")->setVisible(show_pbr); + getChildView("gltfTextureOffsetV")->setVisible(show_pbr); +} + +void LLPanelFace::updateCopyTexButton() +{ + LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); + mMenuClipboardTexture->setEnabled(objectp && objectp->getPCode() == LL_PCODE_VOLUME && objectp->permModify() + && !objectp->isPermanentEnforced() && !objectp->isInventoryPending() + && (LLSelectMgr::getInstance()->getSelection()->getObjectCount() == 1) + && LLMaterialEditor::canClipboardObjectsMaterial()); + std::string tooltip = (objectp && objectp->isInventoryPending()) ? LLTrans::getString("LoadingContents") : getString("paste_options"); + mMenuClipboardTexture->setToolTip(tooltip); +} + +void LLPanelFace::refresh() +{ + LL_DEBUGS("Materials") << LL_ENDL; + getState(); +} + +void LLPanelFace::refreshMedia() +{ + LLObjectSelectionHandle selected_objects = LLSelectMgr::getInstance()->getSelection(); + LLViewerObject* first_object = selected_objects->getFirstObject(); + + if (!(first_object + && first_object->getPCode() == LL_PCODE_VOLUME + && first_object->permModify() + )) + { + getChildView("add_media")->setEnabled(false); + mTitleMediaText->clear(); + clearMediaSettings(); + return; + } + + std::string url = first_object->getRegion()->getCapability("ObjectMedia"); + bool has_media_capability = (!url.empty()); + + if (!has_media_capability) + { + getChildView("add_media")->setEnabled(false); + LL_WARNS("LLFloaterToolsMedia") << "Media not enabled (no capability) in this region!" << LL_ENDL; + clearMediaSettings(); + return; + } + + bool is_nonpermanent_enforced = (LLSelectMgr::getInstance()->getSelection()->getFirstRootNode() + && LLSelectMgr::getInstance()->selectGetRootsNonPermanentEnforced()) + || LLSelectMgr::getInstance()->selectGetNonPermanentEnforced(); + bool editable = is_nonpermanent_enforced && (first_object->permModify() || selectedMediaEditable()); + + // Check modify permissions and whether any selected objects are in + // the process of being fetched. If they are, then we're not editable + if (editable) + { + LLObjectSelection::iterator iter = selected_objects->begin(); + LLObjectSelection::iterator end = selected_objects->end(); + for (; iter != end; ++iter) + { + LLSelectNode* node = *iter; + LLVOVolume* object = dynamic_cast(node->getObject()); + if (NULL != object) + { + if (!object->permModify()) + { + LL_INFOS("LLFloaterToolsMedia") + << "Selection not editable due to lack of modify permissions on object id " + << object->getID() << LL_ENDL; + + editable = false; + break; + } + } + } + } + + // Media settings + bool bool_has_media = false; + struct media_functor : public LLSelectedTEGetFunctor + { + bool get(LLViewerObject* object, S32 face) + { + LLTextureEntry *te = object->getTE(face); + if (te) + { + return te->hasMedia(); + } + return false; + } + } func; + + + // check if all faces have media(or, all dont have media) + LLFloaterMediaSettings::getInstance()->mIdenticalHasMediaInfo = selected_objects->getSelectedTEValue(&func, bool_has_media); + + const LLMediaEntry default_media_data; + + struct functor_getter_media_data : public LLSelectedTEGetFunctor< LLMediaEntry> + { + functor_getter_media_data(const LLMediaEntry& entry) : mMediaEntry(entry) {} + + LLMediaEntry get(LLViewerObject* object, S32 face) + { + if (object) + if (object->getTE(face)) + if (object->getTE(face)->getMediaData()) + return *(object->getTE(face)->getMediaData()); + return mMediaEntry; + }; + + const LLMediaEntry& mMediaEntry; + + } func_media_data(default_media_data); + + LLMediaEntry media_data_get; + LLFloaterMediaSettings::getInstance()->mMultipleMedia = !(selected_objects->getSelectedTEValue(&func_media_data, media_data_get)); + + std::string multi_media_info_str = LLTrans::getString("Multiple Media"); + std::string media_title = ""; + // update UI depending on whether "object" (prim or face) has media + // and whether or not you are allowed to edit it. + + getChildView("add_media")->setEnabled(editable); + // IF all the faces have media (or all dont have media) + if (LLFloaterMediaSettings::getInstance()->mIdenticalHasMediaInfo) + { + // TODO: get media title and set it. + mTitleMediaText->clear(); + // if identical is set, all faces are same (whether all empty or has the same media) + if (!(LLFloaterMediaSettings::getInstance()->mMultipleMedia)) + { + // Media data is valid + if (media_data_get != default_media_data) + { + // initial media title is the media URL (until we get the name) + media_title = media_data_get.getHomeURL(); + } + // else all faces might be empty. + } + else // there' re Different Medias' been set on on the faces. + { + media_title = multi_media_info_str; + } + + getChildView("delete_media")->setEnabled(bool_has_media && editable); + // TODO: display a list of all media on the face - use 'identical' flag + } + else // not all face has media but at least one does. + { + // seleted faces have not identical value + LLFloaterMediaSettings::getInstance()->mMultipleValidMedia = selected_objects->isMultipleTEValue(&func_media_data, default_media_data); + + if (LLFloaterMediaSettings::getInstance()->mMultipleValidMedia) + { + media_title = multi_media_info_str; + } + else + { + // Media data is valid + if (media_data_get != default_media_data) + { + // initial media title is the media URL (until we get the name) + media_title = media_data_get.getHomeURL(); + } + } + + getChildView("delete_media")->setEnabled(true); + } + + U32 materials_media = mComboMatMedia->getCurrentIndex(); + if (materials_media == MATMEDIA_MEDIA) + { + // currently displaying media info, navigateTo and update title + navigateToTitleMedia(media_title); + } + else + { + // Media can be heavy, don't keep it around + // MAC specific: MAC doesn't support setVolume(0) so if not + // unloaded, it might keep playing audio until user closes editor + unloadMedia(); + mNeedMediaTitle = false; + } + + mTitleMediaText->setText(media_title); + + // load values for media settings + updateMediaSettings(); + + LLFloaterMediaSettings::initValues(mMediaSettings, editable); +} + +void LLPanelFace::unloadMedia() +{ + // destroy media source used to grab media title + if (mTitleMedia) + mTitleMedia->unloadMediaSource(); +} + +// static +void LLPanelFace::onMaterialOverrideReceived(const LLUUID& object_id, S32 side) +{ + sMaterialOverrideSelection.onSelectedObjectUpdated(object_id, side); +} + +////////////////////////////////////////////////////////////////////////////// +// +void LLPanelFace::navigateToTitleMedia( const std::string url ) +{ + std::string multi_media_info_str = LLTrans::getString("Multiple Media"); + if (url.empty() || multi_media_info_str == url) + { + // nothing to show + mNeedMediaTitle = false; + } + else if (mTitleMedia) + { + LLPluginClassMedia* media_plugin = mTitleMedia->getMediaPlugin(); + // check if url changed or if we need a new media source + if (mTitleMedia->getCurrentNavUrl() != url || media_plugin == NULL) + { + mTitleMedia->navigateTo( url ); + + LLViewerMediaImpl* impl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mTitleMedia->getTextureID()); + if (impl) + { + // if it's a page with a movie, we don't want to hear it + impl->setVolume(0); + }; + } + + // flag that we need to update the title (even if no request were made) + mNeedMediaTitle = true; + } +} + +bool LLPanelFace::selectedMediaEditable() +{ + U32 owner_mask_on; + U32 owner_mask_off; + U32 valid_owner_perms = LLSelectMgr::getInstance()->selectGetPerm(PERM_OWNER, + &owner_mask_on, &owner_mask_off); + U32 group_mask_on; + U32 group_mask_off; + U32 valid_group_perms = LLSelectMgr::getInstance()->selectGetPerm(PERM_GROUP, + &group_mask_on, &group_mask_off); + U32 everyone_mask_on; + U32 everyone_mask_off; + S32 valid_everyone_perms = LLSelectMgr::getInstance()->selectGetPerm(PERM_EVERYONE, + &everyone_mask_on, &everyone_mask_off); + + bool selected_Media_editable = false; + + // if perms we got back are valid + if (valid_owner_perms && + valid_group_perms && + valid_everyone_perms) + { + + if ((owner_mask_on & PERM_MODIFY) || + (group_mask_on & PERM_MODIFY) || + (everyone_mask_on & PERM_MODIFY)) + { + selected_Media_editable = true; + } + else + // user is NOT allowed to press the RESET button + { + selected_Media_editable = false; + }; + }; + + return selected_Media_editable; +} + +void LLPanelFace::clearMediaSettings() +{ + LLFloaterMediaSettings::clearValues(false); +} + +void LLPanelFace::updateMediaSettings() +{ + bool identical(false); + std::string base_key(""); + std::string value_str(""); + int value_int = 0; + bool value_bool = false; + LLObjectSelectionHandle selected_objects = LLSelectMgr::getInstance()->getSelection(); + // TODO: (CP) refactor this using something clever or boost or both !! + + const LLMediaEntry default_media_data; + + // controls + U8 value_u8 = default_media_data.getControls(); + struct functor_getter_controls : public LLSelectedTEGetFunctor< U8 > + { + functor_getter_controls(const LLMediaEntry &entry) : mMediaEntry(entry) {} + + U8 get(LLViewerObject* object, S32 face) + { + if (object) + if (object->getTE(face)) + if (object->getTE(face)->getMediaData()) + return object->getTE(face)->getMediaData()->getControls(); + return mMediaEntry.getControls(); + }; + + const LLMediaEntry &mMediaEntry; + + } func_controls(default_media_data); + identical = selected_objects->getSelectedTEValue(&func_controls, value_u8); + base_key = std::string(LLMediaEntry::CONTROLS_KEY); + mMediaSettings[base_key] = value_u8; + mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; + + // First click (formerly left click) + value_bool = default_media_data.getFirstClickInteract(); + struct functor_getter_first_click : public LLSelectedTEGetFunctor< bool > + { + functor_getter_first_click(const LLMediaEntry& entry) : mMediaEntry(entry) {} + + bool get(LLViewerObject* object, S32 face) + { + if (object) + if (object->getTE(face)) + if (object->getTE(face)->getMediaData()) + return object->getTE(face)->getMediaData()->getFirstClickInteract(); + return mMediaEntry.getFirstClickInteract(); + }; + + const LLMediaEntry &mMediaEntry; + + } func_first_click(default_media_data); + identical = selected_objects->getSelectedTEValue(&func_first_click, value_bool); + base_key = std::string(LLMediaEntry::FIRST_CLICK_INTERACT_KEY); + mMediaSettings[base_key] = value_bool; + mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; + + // Home URL + value_str = default_media_data.getHomeURL(); + struct functor_getter_home_url : public LLSelectedTEGetFunctor< std::string > + { + functor_getter_home_url(const LLMediaEntry& entry) : mMediaEntry(entry) {} + + std::string get(LLViewerObject* object, S32 face) + { + if (object) + if (object->getTE(face)) + if (object->getTE(face)->getMediaData()) + return object->getTE(face)->getMediaData()->getHomeURL(); + return mMediaEntry.getHomeURL(); + }; + + const LLMediaEntry &mMediaEntry; + + } func_home_url(default_media_data); + identical = selected_objects->getSelectedTEValue(&func_home_url, value_str); + base_key = std::string(LLMediaEntry::HOME_URL_KEY); + mMediaSettings[base_key] = value_str; + mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; + + // Current URL + value_str = default_media_data.getCurrentURL(); + struct functor_getter_current_url : public LLSelectedTEGetFunctor< std::string > + { + functor_getter_current_url(const LLMediaEntry& entry) : mMediaEntry(entry) {} + + std::string get(LLViewerObject* object, S32 face) + { + if (object) + if (object->getTE(face)) + if (object->getTE(face)->getMediaData()) + return object->getTE(face)->getMediaData()->getCurrentURL(); + return mMediaEntry.getCurrentURL(); + }; + + const LLMediaEntry &mMediaEntry; + + } func_current_url(default_media_data); + identical = selected_objects->getSelectedTEValue(&func_current_url, value_str); + base_key = std::string(LLMediaEntry::CURRENT_URL_KEY); + mMediaSettings[base_key] = value_str; + mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; + + // Auto zoom + value_bool = default_media_data.getAutoZoom(); + struct functor_getter_auto_zoom : public LLSelectedTEGetFunctor< bool > + { + + functor_getter_auto_zoom(const LLMediaEntry& entry) : mMediaEntry(entry) {} + + bool get(LLViewerObject* object, S32 face) + { + if (object) + if (object->getTE(face)) + if (object->getTE(face)->getMediaData()) + return object->getTE(face)->getMediaData()->getAutoZoom(); + return mMediaEntry.getAutoZoom(); + }; + + const LLMediaEntry &mMediaEntry; + + } func_auto_zoom(default_media_data); + identical = selected_objects->getSelectedTEValue(&func_auto_zoom, value_bool); + base_key = std::string(LLMediaEntry::AUTO_ZOOM_KEY); + mMediaSettings[base_key] = value_bool; + mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; + + // Auto play + //value_bool = default_media_data.getAutoPlay(); + // set default to auto play true -- angela EXT-5172 + value_bool = true; + struct functor_getter_auto_play : public LLSelectedTEGetFunctor< bool > + { + functor_getter_auto_play(const LLMediaEntry& entry) : mMediaEntry(entry) {} + + bool get(LLViewerObject* object, S32 face) + { + if (object) + if (object->getTE(face)) + if (object->getTE(face)->getMediaData()) + return object->getTE(face)->getMediaData()->getAutoPlay(); + //return mMediaEntry.getAutoPlay(); set default to auto play true -- angela EXT-5172 + return true; + }; + + const LLMediaEntry &mMediaEntry; + + } func_auto_play(default_media_data); + identical = selected_objects->getSelectedTEValue(&func_auto_play, value_bool); + base_key = std::string(LLMediaEntry::AUTO_PLAY_KEY); + mMediaSettings[base_key] = value_bool; + mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; + + + // Auto scale + // set default to auto scale true -- angela EXT-5172 + //value_bool = default_media_data.getAutoScale(); + value_bool = true; + struct functor_getter_auto_scale : public LLSelectedTEGetFunctor< bool > + { + functor_getter_auto_scale(const LLMediaEntry& entry) : mMediaEntry(entry) {} + + bool get(LLViewerObject* object, S32 face) + { + if (object) + if (object->getTE(face)) + if (object->getTE(face)->getMediaData()) + return object->getTE(face)->getMediaData()->getAutoScale(); + // return mMediaEntry.getAutoScale(); set default to auto scale true -- angela EXT-5172 + return true; + }; + + const LLMediaEntry &mMediaEntry; + + } func_auto_scale(default_media_data); + identical = selected_objects->getSelectedTEValue(&func_auto_scale, value_bool); + base_key = std::string(LLMediaEntry::AUTO_SCALE_KEY); + mMediaSettings[base_key] = value_bool; + mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; + + // Auto loop + value_bool = default_media_data.getAutoLoop(); + struct functor_getter_auto_loop : public LLSelectedTEGetFunctor< bool > + { + functor_getter_auto_loop(const LLMediaEntry& entry) : mMediaEntry(entry) {} + + bool get(LLViewerObject* object, S32 face) + { + if (object) + if (object->getTE(face)) + if (object->getTE(face)->getMediaData()) + return object->getTE(face)->getMediaData()->getAutoLoop(); + return mMediaEntry.getAutoLoop(); + }; + + const LLMediaEntry &mMediaEntry; + + } func_auto_loop(default_media_data); + identical = selected_objects->getSelectedTEValue(&func_auto_loop, value_bool); + base_key = std::string(LLMediaEntry::AUTO_LOOP_KEY); + mMediaSettings[base_key] = value_bool; + mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; + + // width pixels (if not auto scaled) + value_int = default_media_data.getWidthPixels(); + struct functor_getter_width_pixels : public LLSelectedTEGetFunctor< int > + { + functor_getter_width_pixels(const LLMediaEntry& entry) : mMediaEntry(entry) {} + + int get(LLViewerObject* object, S32 face) + { + if (object) + if (object->getTE(face)) + if (object->getTE(face)->getMediaData()) + return object->getTE(face)->getMediaData()->getWidthPixels(); + return mMediaEntry.getWidthPixels(); + }; + + const LLMediaEntry &mMediaEntry; + + } func_width_pixels(default_media_data); + identical = selected_objects->getSelectedTEValue(&func_width_pixels, value_int); + base_key = std::string(LLMediaEntry::WIDTH_PIXELS_KEY); + mMediaSettings[base_key] = value_int; + mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; + + // height pixels (if not auto scaled) + value_int = default_media_data.getHeightPixels(); + struct functor_getter_height_pixels : public LLSelectedTEGetFunctor< int > + { + functor_getter_height_pixels(const LLMediaEntry& entry) : mMediaEntry(entry) {} + + int get(LLViewerObject* object, S32 face) + { + if (object) + if (object->getTE(face)) + if (object->getTE(face)->getMediaData()) + return object->getTE(face)->getMediaData()->getHeightPixels(); + return mMediaEntry.getHeightPixels(); + }; + + const LLMediaEntry &mMediaEntry; + + } func_height_pixels(default_media_data); + identical = selected_objects->getSelectedTEValue(&func_height_pixels, value_int); + base_key = std::string(LLMediaEntry::HEIGHT_PIXELS_KEY); + mMediaSettings[base_key] = value_int; + mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; + + // Enable Alt image + value_bool = default_media_data.getAltImageEnable(); + struct functor_getter_enable_alt_image : public LLSelectedTEGetFunctor< bool > + { + functor_getter_enable_alt_image(const LLMediaEntry& entry) : mMediaEntry(entry) {} + + bool get(LLViewerObject* object, S32 face) + { + if (object) + if (object->getTE(face)) + if (object->getTE(face)->getMediaData()) + return object->getTE(face)->getMediaData()->getAltImageEnable(); + return mMediaEntry.getAltImageEnable(); + }; + + const LLMediaEntry &mMediaEntry; + + } func_enable_alt_image(default_media_data); + identical = selected_objects->getSelectedTEValue(&func_enable_alt_image, value_bool); + base_key = std::string(LLMediaEntry::ALT_IMAGE_ENABLE_KEY); + mMediaSettings[base_key] = value_bool; + mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; + + // Perms - owner interact + value_bool = 0 != (default_media_data.getPermsInteract() & LLMediaEntry::PERM_OWNER); + struct functor_getter_perms_owner_interact : public LLSelectedTEGetFunctor< bool > + { + functor_getter_perms_owner_interact(const LLMediaEntry& entry) : mMediaEntry(entry) {} + + bool get(LLViewerObject* object, S32 face) + { + if (object) + if (object->getTE(face)) + if (object->getTE(face)->getMediaData()) + return (0 != (object->getTE(face)->getMediaData()->getPermsInteract() & LLMediaEntry::PERM_OWNER)); + return 0 != (mMediaEntry.getPermsInteract() & LLMediaEntry::PERM_OWNER); + }; + + const LLMediaEntry &mMediaEntry; + + } func_perms_owner_interact(default_media_data); + identical = selected_objects->getSelectedTEValue(&func_perms_owner_interact, value_bool); + base_key = std::string(LLPanelContents::PERMS_OWNER_INTERACT_KEY); + mMediaSettings[base_key] = value_bool; + mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; + + // Perms - owner control + value_bool = 0 != (default_media_data.getPermsControl() & LLMediaEntry::PERM_OWNER); + struct functor_getter_perms_owner_control : public LLSelectedTEGetFunctor< bool > + { + functor_getter_perms_owner_control(const LLMediaEntry& entry) : mMediaEntry(entry) {} + + bool get(LLViewerObject* object, S32 face) + { + if (object) + if (object->getTE(face)) + if (object->getTE(face)->getMediaData()) + return (0 != (object->getTE(face)->getMediaData()->getPermsControl() & LLMediaEntry::PERM_OWNER)); + return 0 != (mMediaEntry.getPermsControl() & LLMediaEntry::PERM_OWNER); + }; + + const LLMediaEntry &mMediaEntry; + + } func_perms_owner_control(default_media_data); + identical = selected_objects->getSelectedTEValue(&func_perms_owner_control, value_bool); + base_key = std::string(LLPanelContents::PERMS_OWNER_CONTROL_KEY); + mMediaSettings[base_key] = value_bool; + mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; + + // Perms - group interact + value_bool = 0 != (default_media_data.getPermsInteract() & LLMediaEntry::PERM_GROUP); + struct functor_getter_perms_group_interact : public LLSelectedTEGetFunctor< bool > + { + functor_getter_perms_group_interact(const LLMediaEntry& entry) : mMediaEntry(entry) {} + + bool get(LLViewerObject* object, S32 face) + { + if (object) + if (object->getTE(face)) + if (object->getTE(face)->getMediaData()) + return (0 != (object->getTE(face)->getMediaData()->getPermsInteract() & LLMediaEntry::PERM_GROUP)); + return 0 != (mMediaEntry.getPermsInteract() & LLMediaEntry::PERM_GROUP); + }; + + const LLMediaEntry &mMediaEntry; + + } func_perms_group_interact(default_media_data); + identical = selected_objects->getSelectedTEValue(&func_perms_group_interact, value_bool); + base_key = std::string(LLPanelContents::PERMS_GROUP_INTERACT_KEY); + mMediaSettings[base_key] = value_bool; + mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; + + // Perms - group control + value_bool = 0 != (default_media_data.getPermsControl() & LLMediaEntry::PERM_GROUP); + struct functor_getter_perms_group_control : public LLSelectedTEGetFunctor< bool > + { + functor_getter_perms_group_control(const LLMediaEntry& entry) : mMediaEntry(entry) {} + + bool get(LLViewerObject* object, S32 face) + { + if (object) + if (object->getTE(face)) + if (object->getTE(face)->getMediaData()) + return (0 != (object->getTE(face)->getMediaData()->getPermsControl() & LLMediaEntry::PERM_GROUP)); + return 0 != (mMediaEntry.getPermsControl() & LLMediaEntry::PERM_GROUP); + }; + + const LLMediaEntry &mMediaEntry; + + } func_perms_group_control(default_media_data); + identical = selected_objects->getSelectedTEValue(&func_perms_group_control, value_bool); + base_key = std::string(LLPanelContents::PERMS_GROUP_CONTROL_KEY); + mMediaSettings[base_key] = value_bool; + mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; + + // Perms - anyone interact + value_bool = 0 != (default_media_data.getPermsInteract() & LLMediaEntry::PERM_ANYONE); + struct functor_getter_perms_anyone_interact : public LLSelectedTEGetFunctor< bool > + { + functor_getter_perms_anyone_interact(const LLMediaEntry& entry) : mMediaEntry(entry) {} + + bool get(LLViewerObject* object, S32 face) + { + if (object) + if (object->getTE(face)) + if (object->getTE(face)->getMediaData()) + return (0 != (object->getTE(face)->getMediaData()->getPermsInteract() & LLMediaEntry::PERM_ANYONE)); + return 0 != (mMediaEntry.getPermsInteract() & LLMediaEntry::PERM_ANYONE); + }; + + const LLMediaEntry &mMediaEntry; + + } func_perms_anyone_interact(default_media_data); + identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue(&func_perms_anyone_interact, value_bool); + base_key = std::string(LLPanelContents::PERMS_ANYONE_INTERACT_KEY); + mMediaSettings[base_key] = value_bool; + mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; + + // Perms - anyone control + value_bool = 0 != (default_media_data.getPermsControl() & LLMediaEntry::PERM_ANYONE); + struct functor_getter_perms_anyone_control : public LLSelectedTEGetFunctor< bool > + { + functor_getter_perms_anyone_control(const LLMediaEntry& entry) : mMediaEntry(entry) {} + + bool get(LLViewerObject* object, S32 face) + { + if (object) + if (object->getTE(face)) + if (object->getTE(face)->getMediaData()) + return (0 != (object->getTE(face)->getMediaData()->getPermsControl() & LLMediaEntry::PERM_ANYONE)); + return 0 != (mMediaEntry.getPermsControl() & LLMediaEntry::PERM_ANYONE); + }; + + const LLMediaEntry &mMediaEntry; + + } func_perms_anyone_control(default_media_data); + identical = selected_objects->getSelectedTEValue(&func_perms_anyone_control, value_bool); + base_key = std::string(LLPanelContents::PERMS_ANYONE_CONTROL_KEY); + mMediaSettings[base_key] = value_bool; + mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; + + // security - whitelist enable + value_bool = default_media_data.getWhiteListEnable(); + struct functor_getter_whitelist_enable : public LLSelectedTEGetFunctor< bool > + { + functor_getter_whitelist_enable(const LLMediaEntry& entry) : mMediaEntry(entry) {} + + bool get(LLViewerObject* object, S32 face) + { + if (object) + if (object->getTE(face)) + if (object->getTE(face)->getMediaData()) + return object->getTE(face)->getMediaData()->getWhiteListEnable(); + return mMediaEntry.getWhiteListEnable(); + }; + + const LLMediaEntry &mMediaEntry; + + } func_whitelist_enable(default_media_data); + identical = selected_objects->getSelectedTEValue(&func_whitelist_enable, value_bool); + base_key = std::string(LLMediaEntry::WHITELIST_ENABLE_KEY); + mMediaSettings[base_key] = value_bool; + mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; + + // security - whitelist URLs + std::vector value_vector_str = default_media_data.getWhiteList(); + struct functor_getter_whitelist_urls : public LLSelectedTEGetFunctor< std::vector > + { + functor_getter_whitelist_urls(const LLMediaEntry& entry) : mMediaEntry(entry) {} + + std::vector get(LLViewerObject* object, S32 face) + { + if (object) + if (object->getTE(face)) + if (object->getTE(face)->getMediaData()) + return object->getTE(face)->getMediaData()->getWhiteList(); + return mMediaEntry.getWhiteList(); + }; + + const LLMediaEntry &mMediaEntry; + + } func_whitelist_urls(default_media_data); + identical = selected_objects->getSelectedTEValue(&func_whitelist_urls, value_vector_str); + base_key = std::string(LLMediaEntry::WHITELIST_KEY); + mMediaSettings[base_key].clear(); + std::vector< std::string >::iterator iter = value_vector_str.begin(); + while (iter != value_vector_str.end()) + { + std::string white_list_url = *iter; + mMediaSettings[base_key].append(white_list_url); + ++iter; + }; + + mMediaSettings[base_key + std::string(LLPanelContents::TENTATIVE_SUFFIX)] = !identical; +} + +void LLPanelFace::updateMediaTitle() +{ + // only get the media name if we need it + if (!mNeedMediaTitle) + return; + + // get plugin impl + LLPluginClassMedia* media_plugin = mTitleMedia->getMediaPlugin(); + if (media_plugin && mTitleMedia->getCurrentNavUrl() == media_plugin->getNavigateURI()) + { + // get the media name (asynchronous - must call repeatedly) + std::string media_title = media_plugin->getMediaName(); + + // only replace the title if what we get contains something + if (!media_title.empty()) + { + // update the UI widget + if (mTitleMediaText) + { + mTitleMediaText->setText(media_title); + + // stop looking for a title when we get one + mNeedMediaTitle = false; + }; + }; + }; +} + +// +// Static functions +// + +// static +F32 LLPanelFace::valueGlow(LLViewerObject* object, S32 face) +{ + return (F32)(object->getTE(face)->getGlow()); +} + + +void LLPanelFace::onCommitColor(const LLSD& data) +{ + sendColor(); +} + +void LLPanelFace::onCommitShinyColor(const LLSD& data) +{ + LLSelectedTEMaterial::setSpecularLightColor(this, getChild("shinycolorswatch")->get()); +} + +void LLPanelFace::onCommitAlpha(const LLSD& data) +{ + sendAlpha(); +} + +void LLPanelFace::onCancelColor(const LLSD& data) +{ + LLSelectMgr::getInstance()->selectionRevertColors(); +} + +void LLPanelFace::onCancelShinyColor(const LLSD& data) +{ + LLSelectMgr::getInstance()->selectionRevertShinyColors(); +} + +void LLPanelFace::onSelectColor(const LLSD& data) +{ + LLSelectMgr::getInstance()->saveSelectedObjectColors(); + sendColor(); +} + +void LLPanelFace::onSelectShinyColor(const LLSD& data) +{ + LLSelectedTEMaterial::setSpecularLightColor(this, getChild("shinycolorswatch")->get()); + LLSelectMgr::getInstance()->saveSelectedShinyColors(); +} + +// static +void LLPanelFace::onCommitMaterialsMedia(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + // Force to default states to side-step problems with menu contents + // and generally reflecting old state when switching tabs or objects + // + self->updateShinyControls(false,true); + self->updateBumpyControls(false,true); + self->updateUI(); + self->refreshMedia(); +} + +void LLPanelFace::updateVisibility(LLViewerObject* objectp /* = nullptr */) +{ + LLRadioGroup* radio_mat_type = findChild("radio_material_type"); + LLRadioGroup* radio_pbr_type = findChild("radio_pbr_type"); + LLComboBox* combo_shininess = findChild("combobox shininess"); + LLComboBox* combo_bumpiness = findChild("combobox bumpiness"); + if (!radio_mat_type || !radio_pbr_type || !mComboMatMedia || !combo_shininess || !combo_bumpiness) + { + LL_WARNS("Materials") << "Combo box not found...exiting." << LL_ENDL; + return; + } + U32 materials_media = mComboMatMedia->getCurrentIndex(); + U32 material_type = radio_mat_type->getSelectedIndex(); + bool show_media = (materials_media == MATMEDIA_MEDIA) && mComboMatMedia->getEnabled(); + bool show_material = materials_media == MATMEDIA_MATERIAL; + bool show_texture = (show_media || (show_material && (material_type == MATTYPE_DIFFUSE) && mComboMatMedia->getEnabled())); + bool show_bumpiness = show_material && (material_type == MATTYPE_NORMAL) && mComboMatMedia->getEnabled(); + bool show_shininess = show_material && (material_type == MATTYPE_SPECULAR) && mComboMatMedia->getEnabled(); + const bool show_pbr = mComboMatMedia->getCurrentIndex() == MATMEDIA_PBR && mComboMatMedia->getEnabled(); + const U32 pbr_type = findChild("radio_pbr_type")->getSelectedIndex(); + const LLGLTFMaterial::TextureInfo texture_info = texture_info_from_pbrtype(pbr_type); + const bool show_pbr_asset = show_pbr && texture_info == LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; + + radio_mat_type->setVisible(show_material); + + // Shared material controls + getChildView("checkbox_sync_settings")->setVisible(show_material || show_media); + getChildView("tex gen")->setVisible(show_material || show_media || show_pbr_asset); + getChildView("combobox texgen")->setVisible(show_material || show_media || show_pbr_asset); + getChildView("button align textures")->setVisible(show_material || show_media); + + // Media controls + mTitleMediaText->setVisible(show_media); + getChildView("add_media")->setVisible(show_media); + getChildView("delete_media")->setVisible(show_media); + getChildView("button align")->setVisible(show_media); + + // Diffuse texture controls + getChildView("texture control")->setVisible(show_texture && show_material); + getChildView("label alphamode")->setVisible(show_texture && show_material); + getChildView("combobox alphamode")->setVisible(show_texture && show_material); + getChildView("label maskcutoff")->setVisible(false); + getChildView("maskcutoff")->setVisible(false); + if (show_texture && show_material) + { + updateAlphaControls(); + } + // texture scale and position controls + getChildView("TexScaleU")->setVisible(show_texture); + getChildView("TexScaleV")->setVisible(show_texture); + getChildView("TexRot")->setVisible(show_texture); + getChildView("TexOffsetU")->setVisible(show_texture); + getChildView("TexOffsetV")->setVisible(show_texture); + + // Specular map controls + getChildView("shinytexture control")->setVisible(show_shininess); + getChildView("combobox shininess")->setVisible(show_shininess); + getChildView("label shininess")->setVisible(show_shininess); + getChildView("label glossiness")->setVisible(false); + getChildView("glossiness")->setVisible(false); + getChildView("label environment")->setVisible(false); + getChildView("environment")->setVisible(false); + getChildView("label shinycolor")->setVisible(false); + getChildView("shinycolorswatch")->setVisible(false); + if (show_shininess) + { + updateShinyControls(); + } + getChildView("shinyScaleU")->setVisible(show_shininess); + getChildView("shinyScaleV")->setVisible(show_shininess); + getChildView("shinyRot")->setVisible(show_shininess); + getChildView("shinyOffsetU")->setVisible(show_shininess); + getChildView("shinyOffsetV")->setVisible(show_shininess); + + // Normal map controls + if (show_bumpiness) + { + updateBumpyControls(); + } + getChildView("bumpytexture control")->setVisible(show_bumpiness); + getChildView("combobox bumpiness")->setVisible(show_bumpiness); + getChildView("label bumpiness")->setVisible(show_bumpiness); + getChildView("bumpyScaleU")->setVisible(show_bumpiness); + getChildView("bumpyScaleV")->setVisible(show_bumpiness); + getChildView("bumpyRot")->setVisible(show_bumpiness); + getChildView("bumpyOffsetU")->setVisible(show_bumpiness); + getChildView("bumpyOffsetV")->setVisible(show_bumpiness); + + getChild("rptctrl")->setVisible(show_material || show_media); + + // PBR controls + updateVisibilityGLTF(objectp); +} + +// static +void LLPanelFace::onCommitMaterialType(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + // Force to default states to side-step problems with menu contents + // and generally reflecting old state when switching tabs or objects + // + self->updateShinyControls(false,true); + self->updateBumpyControls(false,true); + self->updateUI(); +} + +// static +void LLPanelFace::onCommitPbrType(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*)userdata; + // Force to default states to side-step problems with menu contents + // and generally reflecting old state when switching tabs or objects + // + self->updateUI(); +} + +// static +void LLPanelFace::onCommitBump(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + + LLComboBox* mComboBumpiness = self->getChild("combobox bumpiness"); + if(!mComboBumpiness) + return; + + U32 bumpiness = mComboBumpiness->getCurrentIndex(); + + self->sendBump(bumpiness); +} + +// static +void LLPanelFace::onCommitTexGen(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + self->sendTexGen(); +} + +// static +void LLPanelFace::updateShinyControls(bool is_setting_texture, bool mess_with_shiny_combobox) +{ + LLTextureCtrl* texture_ctrl = getChild("shinytexture control"); + LLUUID shiny_texture_ID = texture_ctrl->getImageAssetID(); + LL_DEBUGS("Materials") << "Shiny texture selected: " << shiny_texture_ID << LL_ENDL; + LLComboBox* comboShiny = getChild("combobox shininess"); + + if(mess_with_shiny_combobox) + { + if (!comboShiny) + { + return; + } + if (!shiny_texture_ID.isNull() && is_setting_texture) + { + if (!comboShiny->itemExists(USE_TEXTURE)) + { + comboShiny->add(USE_TEXTURE); + } + comboShiny->setSimple(USE_TEXTURE); + } + else + { + if (comboShiny->itemExists(USE_TEXTURE)) + { + comboShiny->remove(SHINY_TEXTURE); + comboShiny->selectFirstItem(); + } + } + } + else + { + if (shiny_texture_ID.isNull() && comboShiny && comboShiny->itemExists(USE_TEXTURE)) + { + comboShiny->remove(SHINY_TEXTURE); + comboShiny->selectFirstItem(); + } + } + + + LLRadioGroup* radio_mat_type = getChild("radio_material_type"); + U32 materials_media = mComboMatMedia->getCurrentIndex(); + U32 material_type = radio_mat_type->getSelectedIndex(); + bool show_material = (materials_media == MATMEDIA_MATERIAL); + bool show_shininess = show_material && (material_type == MATTYPE_SPECULAR) && mComboMatMedia->getEnabled(); + U32 shiny_value = comboShiny->getCurrentIndex(); + bool show_shinyctrls = (shiny_value == SHINY_TEXTURE) && show_shininess; // Use texture + getChildView("label glossiness")->setVisible(show_shinyctrls); + getChildView("glossiness")->setVisible(show_shinyctrls); + getChildView("label environment")->setVisible(show_shinyctrls); + getChildView("environment")->setVisible(show_shinyctrls); + getChildView("label shinycolor")->setVisible(show_shinyctrls); + getChildView("shinycolorswatch")->setVisible(show_shinyctrls); +} + +// static +void LLPanelFace::updateBumpyControls(bool is_setting_texture, bool mess_with_combobox) +{ + LLTextureCtrl* texture_ctrl = getChild("bumpytexture control"); + LLUUID bumpy_texture_ID = texture_ctrl->getImageAssetID(); + LL_DEBUGS("Materials") << "texture: " << bumpy_texture_ID << (mess_with_combobox ? "" : " do not") << " update combobox" << LL_ENDL; + LLComboBox* comboBumpy = getChild("combobox bumpiness"); + if (!comboBumpy) + { + return; + } + + if (mess_with_combobox) + { + LLTextureCtrl* texture_ctrl = getChild("bumpytexture control"); + LLUUID bumpy_texture_ID = texture_ctrl->getImageAssetID(); + LL_DEBUGS("Materials") << "texture: " << bumpy_texture_ID << (mess_with_combobox ? "" : " do not") << " update combobox" << LL_ENDL; + + if (!bumpy_texture_ID.isNull() && is_setting_texture) + { + if (!comboBumpy->itemExists(USE_TEXTURE)) + { + comboBumpy->add(USE_TEXTURE); + } + comboBumpy->setSimple(USE_TEXTURE); + } + else + { + if (comboBumpy->itemExists(USE_TEXTURE)) + { + comboBumpy->remove(BUMPY_TEXTURE); + comboBumpy->selectFirstItem(); + } + } + } +} + +// static +void LLPanelFace::onCommitShiny(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + + + LLComboBox* mComboShininess = self->getChild("combobox shininess"); + if(!mComboShininess) + return; + + U32 shininess = mComboShininess->getCurrentIndex(); + + self->sendShiny(shininess); +} + +// static +void LLPanelFace::updateAlphaControls() +{ + LLComboBox* comboAlphaMode = getChild("combobox alphamode"); + if (!comboAlphaMode) + { + return; + } + U32 alpha_value = comboAlphaMode->getCurrentIndex(); + bool show_alphactrls = (alpha_value == ALPHAMODE_MASK); // Alpha masking + + U32 mat_media = MATMEDIA_MATERIAL; + if (mComboMatMedia) + { + mat_media = mComboMatMedia->getCurrentIndex(); + } + + U32 mat_type = MATTYPE_DIFFUSE; + LLRadioGroup* radio_mat_type = getChild("radio_material_type"); + if(radio_mat_type) + { + mat_type = radio_mat_type->getSelectedIndex(); + } + + show_alphactrls = show_alphactrls && (mat_media == MATMEDIA_MATERIAL); + show_alphactrls = show_alphactrls && (mat_type == MATTYPE_DIFFUSE); + + getChildView("label maskcutoff")->setVisible(show_alphactrls); + getChildView("maskcutoff")->setVisible(show_alphactrls); +} + +// static +void LLPanelFace::onCommitAlphaMode(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + self->updateAlphaControls(); + LLSelectedTEMaterial::setDiffuseAlphaMode(self,self->getCurrentDiffuseAlphaMode()); +} + +// static +void LLPanelFace::onCommitFullbright(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + self->sendFullbright(); +} + +// static +void LLPanelFace::onCommitGlow(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + self->sendGlow(); +} + +// static +bool LLPanelFace::onDragPbr(LLUICtrl*, LLInventoryItem* item) +{ + bool accept = true; + for (LLObjectSelection::root_iterator iter = LLSelectMgr::getInstance()->getSelection()->root_begin(); + iter != LLSelectMgr::getInstance()->getSelection()->root_end(); iter++) + { + LLSelectNode* node = *iter; + LLViewerObject* obj = node->getObject(); + if (!LLToolDragAndDrop::isInventoryDropAcceptable(obj, item)) + { + accept = false; + break; + } + } + return accept; +} + +void LLPanelFace::onCommitPbr(const LLSD& data) +{ + LLTextureCtrl* pbr_ctrl = findChild("pbr_control"); + if (!pbr_ctrl) return; + if (!pbr_ctrl->getTentative()) + { + // we grab the item id first, because we want to do a + // permissions check in the selection manager. ARGH! + LLUUID id = pbr_ctrl->getImageItemID(); + if (id.isNull()) + { + id = pbr_ctrl->getImageAssetID(); + } + if (!LLSelectMgr::getInstance()->selectionSetGLTFMaterial(id)) + { + // If failed to set material, refresh pbr_ctrl's value + refresh(); + } + } +} + +void LLPanelFace::onCancelPbr(const LLSD& data) +{ + LLSelectMgr::getInstance()->selectionRevertGLTFMaterials(); +} + +void LLPanelFace::onSelectPbr(const LLSD& data) +{ + LLSelectMgr::getInstance()->saveSelectedObjectTextures(); + + LLTextureCtrl* pbr_ctrl = findChild("pbr_control"); + if (!pbr_ctrl) return; + if (!pbr_ctrl->getTentative()) + { + // we grab the item id first, because we want to do a + // permissions check in the selection manager. ARGH! + LLUUID id = pbr_ctrl->getImageItemID(); + if (id.isNull()) + { + id = pbr_ctrl->getImageAssetID(); + } + if (!LLSelectMgr::getInstance()->selectionSetGLTFMaterial(id)) + { + refresh(); + } + } +} + +// static +bool LLPanelFace::onDragTexture(LLUICtrl*, LLInventoryItem* item) +{ + bool accept = true; + for (LLObjectSelection::root_iterator iter = LLSelectMgr::getInstance()->getSelection()->root_begin(); + iter != LLSelectMgr::getInstance()->getSelection()->root_end(); iter++) + { + LLSelectNode* node = *iter; + LLViewerObject* obj = node->getObject(); + if (!LLToolDragAndDrop::isInventoryDropAcceptable(obj, item)) + { + accept = false; + break; + } + } + return accept; +} + +void LLPanelFace::onCommitTexture( const LLSD& data ) +{ + add(LLStatViewer::EDIT_TEXTURE, 1); + sendTexture(); +} + +void LLPanelFace::onCancelTexture(const LLSD& data) +{ + LLSelectMgr::getInstance()->selectionRevertTextures(); +} + +void LLPanelFace::onSelectTexture(const LLSD& data) +{ + LLSelectMgr::getInstance()->saveSelectedObjectTextures(); + sendTexture(); + + LLGLenum image_format; + bool identical_image_format = false; + LLSelectedTE::getImageFormat(image_format, identical_image_format); + + LLCtrlSelectionInterface* combobox_alphamode = + childGetSelectionInterface("combobox alphamode"); + + U32 alpha_mode = LLMaterial::DIFFUSE_ALPHA_MODE_NONE; + if (combobox_alphamode) + { + switch (image_format) + { + case GL_RGBA: + case GL_ALPHA: + { + alpha_mode = LLMaterial::DIFFUSE_ALPHA_MODE_BLEND; + } + break; + + case GL_RGB: break; + default: + { + LL_WARNS() << "Unexpected tex format in LLPanelFace...resorting to no alpha" << LL_ENDL; + } + break; + } + + combobox_alphamode->selectNthItem(alpha_mode); + } + LLSelectedTEMaterial::setDiffuseAlphaMode(this, getCurrentDiffuseAlphaMode()); +} + +void LLPanelFace::onCloseTexturePicker(const LLSD& data) +{ + LL_DEBUGS("Materials") << data << LL_ENDL; + updateUI(); +} + +void LLPanelFace::onCommitSpecularTexture( const LLSD& data ) +{ + LL_DEBUGS("Materials") << data << LL_ENDL; + sendShiny(SHINY_TEXTURE); +} + +void LLPanelFace::onCommitNormalTexture( const LLSD& data ) +{ + LL_DEBUGS("Materials") << data << LL_ENDL; + LLUUID nmap_id = getCurrentNormalMap(); + sendBump(nmap_id.isNull() ? 0 : BUMPY_TEXTURE); +} + +void LLPanelFace::onCancelSpecularTexture(const LLSD& data) +{ + U8 shiny = 0; + bool identical_shiny = false; + LLSelectedTE::getShiny(shiny, identical_shiny); + LLUUID spec_map_id = getChild("shinytexture control")->getImageAssetID(); + shiny = spec_map_id.isNull() ? shiny : SHINY_TEXTURE; + sendShiny(shiny); +} + +void LLPanelFace::onCancelNormalTexture(const LLSD& data) +{ + U8 bumpy = 0; + bool identical_bumpy = false; + LLSelectedTE::getBumpmap(bumpy, identical_bumpy); + LLUUID spec_map_id = getChild("bumpytexture control")->getImageAssetID(); + bumpy = spec_map_id.isNull() ? bumpy : BUMPY_TEXTURE; + sendBump(bumpy); +} + +void LLPanelFace::onSelectSpecularTexture(const LLSD& data) +{ + LL_DEBUGS("Materials") << data << LL_ENDL; + sendShiny(SHINY_TEXTURE); +} + +void LLPanelFace::onSelectNormalTexture(const LLSD& data) +{ + LL_DEBUGS("Materials") << data << LL_ENDL; + LLUUID nmap_id = getCurrentNormalMap(); + sendBump(nmap_id.isNull() ? 0 : BUMPY_TEXTURE); +} + +////////////////////////////////////////////////////////////////////////////// +// called when a user wants to edit existing media settings on a prim or prim face +// TODO: test if there is media on the item and only allow editing if present +void LLPanelFace::onClickBtnEditMedia(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*)userdata; + self->refreshMedia(); + LLFloaterReg::showInstance("media_settings"); +} + +////////////////////////////////////////////////////////////////////////////// +// called when a user wants to delete media from a prim or prim face +void LLPanelFace::onClickBtnDeleteMedia(LLUICtrl* ctrl, void* userdata) +{ + LLNotificationsUtil::add("DeleteMedia", LLSD(), LLSD(), deleteMediaConfirm); +} + +////////////////////////////////////////////////////////////////////////////// +// called when a user wants to add media to a prim or prim face +void LLPanelFace::onClickBtnAddMedia(LLUICtrl* ctrl, void* userdata) +{ + // check if multiple faces are selected + if (LLSelectMgr::getInstance()->getSelection()->isMultipleTESelected()) + { + LLPanelFace* self = (LLPanelFace*)userdata; + self->refreshMedia(); + LLNotificationsUtil::add("MultipleFacesSelected", LLSD(), LLSD(), multipleFacesSelectedConfirm); + } + else + { + onClickBtnEditMedia(ctrl, userdata); + } +} + +// static +bool LLPanelFace::deleteMediaConfirm(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + switch (option) + { + case 0: // "Yes" + LLSelectMgr::getInstance()->selectionSetMedia(0, LLSD()); + if (LLFloaterReg::instanceVisible("media_settings")) + { + LLFloaterReg::hideInstance("media_settings"); + } + break; + + case 1: // "No" + default: + break; + } + return false; +} + +// static +bool LLPanelFace::multipleFacesSelectedConfirm(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + switch (option) + { + case 0: // "Yes" + LLFloaterReg::showInstance("media_settings"); + break; + case 1: // "No" + default: + break; + } + return false; +} + +//static +void LLPanelFace::syncOffsetX(LLPanelFace* self, F32 offsetU) +{ + LLSelectedTEMaterial::setNormalOffsetX(self,offsetU); + LLSelectedTEMaterial::setSpecularOffsetX(self,offsetU); + self->getChild("TexOffsetU")->forceSetValue(offsetU); + self->sendTextureInfo(); +} + +//static +void LLPanelFace::syncOffsetY(LLPanelFace* self, F32 offsetV) +{ + LLSelectedTEMaterial::setNormalOffsetY(self,offsetV); + LLSelectedTEMaterial::setSpecularOffsetY(self,offsetV); + self->getChild("TexOffsetV")->forceSetValue(offsetV); + self->sendTextureInfo(); +} + +//static +void LLPanelFace::onCommitMaterialBumpyOffsetX(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + llassert_always(self); + + if (gSavedSettings.getBOOL("SyncMaterialSettings")) + { + syncOffsetX(self,self->getCurrentBumpyOffsetU()); + } + else + { + LLSelectedTEMaterial::setNormalOffsetX(self,self->getCurrentBumpyOffsetU()); + } + +} + +//static +void LLPanelFace::onCommitMaterialBumpyOffsetY(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + llassert_always(self); + + if (gSavedSettings.getBOOL("SyncMaterialSettings")) + { + syncOffsetY(self,self->getCurrentBumpyOffsetV()); + } + else + { + LLSelectedTEMaterial::setNormalOffsetY(self,self->getCurrentBumpyOffsetV()); + } +} + +//static +void LLPanelFace::onCommitMaterialShinyOffsetX(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + llassert_always(self); + + if (gSavedSettings.getBOOL("SyncMaterialSettings")) + { + syncOffsetX(self, self->getCurrentShinyOffsetU()); + } + else + { + LLSelectedTEMaterial::setSpecularOffsetX(self,self->getCurrentShinyOffsetU()); + } +} + +//static +void LLPanelFace::onCommitMaterialShinyOffsetY(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + llassert_always(self); + + if (gSavedSettings.getBOOL("SyncMaterialSettings")) + { + syncOffsetY(self,self->getCurrentShinyOffsetV()); + } + else + { + LLSelectedTEMaterial::setSpecularOffsetY(self,self->getCurrentShinyOffsetV()); + } +} + +//static +void LLPanelFace::syncRepeatX(LLPanelFace* self, F32 scaleU) +{ + LLSelectedTEMaterial::setNormalRepeatX(self,scaleU); + LLSelectedTEMaterial::setSpecularRepeatX(self,scaleU); + self->sendTextureInfo(); +} + +//static +void LLPanelFace::syncRepeatY(LLPanelFace* self, F32 scaleV) +{ + LLSelectedTEMaterial::setNormalRepeatY(self,scaleV); + LLSelectedTEMaterial::setSpecularRepeatY(self,scaleV); + self->sendTextureInfo(); +} + +//static +void LLPanelFace::onCommitMaterialBumpyScaleX(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + llassert_always(self); + F32 bumpy_scale_u = self->getCurrentBumpyScaleU(); + if (self->isIdenticalPlanarTexgen()) + { + bumpy_scale_u *= 0.5f; + } + + if (gSavedSettings.getBOOL("SyncMaterialSettings")) + { + self->getChild("TexScaleU")->forceSetValue(self->getCurrentBumpyScaleU()); + syncRepeatX(self, bumpy_scale_u); + } + else + { + LLSelectedTEMaterial::setNormalRepeatX(self,bumpy_scale_u); + } +} + +//static +void LLPanelFace::onCommitMaterialBumpyScaleY(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + llassert_always(self); + F32 bumpy_scale_v = self->getCurrentBumpyScaleV(); + if (self->isIdenticalPlanarTexgen()) + { + bumpy_scale_v *= 0.5f; + } + + + if (gSavedSettings.getBOOL("SyncMaterialSettings")) + { + self->getChild("TexScaleV")->forceSetValue(self->getCurrentBumpyScaleV()); + syncRepeatY(self, bumpy_scale_v); + } + else + { + LLSelectedTEMaterial::setNormalRepeatY(self,bumpy_scale_v); + } +} + +//static +void LLPanelFace::onCommitMaterialShinyScaleX(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + llassert_always(self); + F32 shiny_scale_u = self->getCurrentShinyScaleU(); + if (self->isIdenticalPlanarTexgen()) + { + shiny_scale_u *= 0.5f; + } + + if (gSavedSettings.getBOOL("SyncMaterialSettings")) + { + self->getChild("TexScaleU")->forceSetValue(self->getCurrentShinyScaleU()); + syncRepeatX(self, shiny_scale_u); + } + else + { + LLSelectedTEMaterial::setSpecularRepeatX(self,shiny_scale_u); + } +} + +//static +void LLPanelFace::onCommitMaterialShinyScaleY(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + llassert_always(self); + F32 shiny_scale_v = self->getCurrentShinyScaleV(); + if (self->isIdenticalPlanarTexgen()) + { + shiny_scale_v *= 0.5f; + } + + if (gSavedSettings.getBOOL("SyncMaterialSettings")) + { + self->getChild("TexScaleV")->forceSetValue(self->getCurrentShinyScaleV()); + syncRepeatY(self, shiny_scale_v); + } + else + { + LLSelectedTEMaterial::setSpecularRepeatY(self,shiny_scale_v); + } +} + +//static +void LLPanelFace::syncMaterialRot(LLPanelFace* self, F32 rot, int te) +{ + LLSelectedTEMaterial::setNormalRotation(self,rot * DEG_TO_RAD, te); + LLSelectedTEMaterial::setSpecularRotation(self,rot * DEG_TO_RAD, te); + self->sendTextureInfo(); +} + +//static +void LLPanelFace::onCommitMaterialBumpyRot(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + llassert_always(self); + + if (gSavedSettings.getBOOL("SyncMaterialSettings")) + { + self->getChild("TexRot")->forceSetValue(self->getCurrentBumpyRot()); + syncMaterialRot(self, self->getCurrentBumpyRot()); + } + else + { + if ((bool)self->childGetValue("checkbox planar align").asBoolean()) + { + LLFace* last_face = NULL; + bool identical_face = false; + LLSelectedTE::getFace(last_face, identical_face); + LLPanelFaceSetAlignedTEFunctor setfunc(self, last_face); + LLSelectMgr::getInstance()->getSelection()->applyToTEs(&setfunc); + } + else + { + LLSelectedTEMaterial::setNormalRotation(self, self->getCurrentBumpyRot() * DEG_TO_RAD); + } + } +} + +//static +void LLPanelFace::onCommitMaterialShinyRot(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + llassert_always(self); + + if (gSavedSettings.getBOOL("SyncMaterialSettings")) + { + self->getChild("TexRot")->forceSetValue(self->getCurrentShinyRot()); + syncMaterialRot(self, self->getCurrentShinyRot()); + } + else + { + if ((bool)self->childGetValue("checkbox planar align").asBoolean()) + { + LLFace* last_face = NULL; + bool identical_face = false; + LLSelectedTE::getFace(last_face, identical_face); + LLPanelFaceSetAlignedTEFunctor setfunc(self, last_face); + LLSelectMgr::getInstance()->getSelection()->applyToTEs(&setfunc); + } + else + { + LLSelectedTEMaterial::setSpecularRotation(self, self->getCurrentShinyRot() * DEG_TO_RAD); + } + } +} + +//static +void LLPanelFace::onCommitMaterialGloss(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + llassert_always(self); + LLSelectedTEMaterial::setSpecularLightExponent(self,self->getCurrentGlossiness()); +} + +//static +void LLPanelFace::onCommitMaterialEnv(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + llassert_always(self); + LLSelectedTEMaterial::setEnvironmentIntensity(self,self->getCurrentEnvIntensity()); +} + +//static +void LLPanelFace::onCommitMaterialMaskCutoff(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + LLSelectedTEMaterial::setAlphaMaskCutoff(self,self->getCurrentAlphaMaskCutoff()); +} + +// static +void LLPanelFace::onCommitTextureInfo( LLUICtrl* ctrl, void* userdata ) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + self->sendTextureInfo(); + // vertical scale and repeats per meter depends on each other, so force set on changes + self->updateUI(true); +} + +// static +void LLPanelFace::onCommitTextureScaleX( LLUICtrl* ctrl, void* userdata ) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + if (gSavedSettings.getBOOL("SyncMaterialSettings")) + { + F32 bumpy_scale_u = self->getChild("TexScaleU")->getValue().asReal(); + if (self->isIdenticalPlanarTexgen()) + { + bumpy_scale_u *= 0.5f; + } + syncRepeatX(self, bumpy_scale_u); + } + else + { + self->sendTextureInfo(); + } + self->updateUI(true); +} + +// static +void LLPanelFace::onCommitTextureScaleY( LLUICtrl* ctrl, void* userdata ) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + if (gSavedSettings.getBOOL("SyncMaterialSettings")) + { + F32 bumpy_scale_v = self->getChild("TexScaleV")->getValue().asReal(); + if (self->isIdenticalPlanarTexgen()) + { + bumpy_scale_v *= 0.5f; + } + syncRepeatY(self, bumpy_scale_v); + } + else + { + self->sendTextureInfo(); + } + self->updateUI(true); +} + +// static +void LLPanelFace::onCommitTextureRot( LLUICtrl* ctrl, void* userdata ) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + + if (gSavedSettings.getBOOL("SyncMaterialSettings")) + { + syncMaterialRot(self, self->getChild("TexRot")->getValue().asReal()); + } + else + { + self->sendTextureInfo(); + } + self->updateUI(true); +} + +// static +void LLPanelFace::onCommitTextureOffsetX( LLUICtrl* ctrl, void* userdata ) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + if (gSavedSettings.getBOOL("SyncMaterialSettings")) + { + syncOffsetX(self, self->getChild("TexOffsetU")->getValue().asReal()); + } + else + { + self->sendTextureInfo(); + } + self->updateUI(true); +} + +// static +void LLPanelFace::onCommitTextureOffsetY( LLUICtrl* ctrl, void* userdata ) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + if (gSavedSettings.getBOOL("SyncMaterialSettings")) + { + syncOffsetY(self, self->getChild("TexOffsetV")->getValue().asReal()); + } + else + { + self->sendTextureInfo(); + } + self->updateUI(true); +} + +// Commit the number of repeats per meter +// static +void LLPanelFace::onCommitRepeatsPerMeter(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + + LLUICtrl* repeats_ctrl = self->getChild("rptctrl"); + + U32 materials_media = self->mComboMatMedia->getCurrentIndex(); + U32 material_type = 0; + if (materials_media == MATMEDIA_PBR) + { + LLRadioGroup* radio_mat_type = self->getChild("radio_pbr_type"); + material_type = radio_mat_type->getSelectedIndex(); + } + if (materials_media == MATMEDIA_MATERIAL) + { + LLRadioGroup* radio_mat_type = self->getChild("radio_material_type"); + material_type = radio_mat_type->getSelectedIndex(); + } + + F32 repeats_per_meter = repeats_ctrl->getValue().asReal(); + + F32 obj_scale_s = 1.0f; + F32 obj_scale_t = 1.0f; + + bool identical_scale_s = false; + bool identical_scale_t = false; + + LLSelectedTE::getObjectScaleS(obj_scale_s, identical_scale_s); + LLSelectedTE::getObjectScaleS(obj_scale_t, identical_scale_t); + + LLUICtrl* bumpy_scale_u = self->getChild("bumpyScaleU"); + LLUICtrl* bumpy_scale_v = self->getChild("bumpyScaleV"); + LLUICtrl* shiny_scale_u = self->getChild("shinyScaleU"); + LLUICtrl* shiny_scale_v = self->getChild("shinyScaleV"); + + if (gSavedSettings.getBOOL("SyncMaterialSettings")) + { + LLSelectMgr::getInstance()->selectionTexScaleAutofit( repeats_per_meter ); + + bumpy_scale_u->setValue(obj_scale_s * repeats_per_meter); + bumpy_scale_v->setValue(obj_scale_t * repeats_per_meter); + + LLSelectedTEMaterial::setNormalRepeatX(self,obj_scale_s * repeats_per_meter); + LLSelectedTEMaterial::setNormalRepeatY(self,obj_scale_t * repeats_per_meter); + + shiny_scale_u->setValue(obj_scale_s * repeats_per_meter); + shiny_scale_v->setValue(obj_scale_t * repeats_per_meter); + + LLSelectedTEMaterial::setSpecularRepeatX(self,obj_scale_s * repeats_per_meter); + LLSelectedTEMaterial::setSpecularRepeatY(self,obj_scale_t * repeats_per_meter); + } + else + { + switch (material_type) + { + case MATTYPE_DIFFUSE: + { + LLSelectMgr::getInstance()->selectionTexScaleAutofit( repeats_per_meter ); + } + break; + + case MATTYPE_NORMAL: + { + bumpy_scale_u->setValue(obj_scale_s * repeats_per_meter); + bumpy_scale_v->setValue(obj_scale_t * repeats_per_meter); + + LLSelectedTEMaterial::setNormalRepeatX(self,obj_scale_s * repeats_per_meter); + LLSelectedTEMaterial::setNormalRepeatY(self,obj_scale_t * repeats_per_meter); + } + break; + + case MATTYPE_SPECULAR: + { + shiny_scale_u->setValue(obj_scale_s * repeats_per_meter); + shiny_scale_v->setValue(obj_scale_t * repeats_per_meter); + + LLSelectedTEMaterial::setSpecularRepeatX(self,obj_scale_s * repeats_per_meter); + LLSelectedTEMaterial::setSpecularRepeatY(self,obj_scale_t * repeats_per_meter); + } + break; + + default: + llassert(false); + break; + } + } + // vertical scale and repeats per meter depends on each other, so force set on changes + self->updateUI(true); +} + +struct LLPanelFaceSetMediaFunctor : public LLSelectedTEFunctor +{ + virtual bool apply(LLViewerObject* object, S32 te) + { + viewer_media_t pMediaImpl; + + const LLTextureEntry* tep = object->getTE(te); + const LLMediaEntry* mep = tep->hasMedia() ? tep->getMediaData() : NULL; + if ( mep ) + { + pMediaImpl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mep->getMediaID()); + } + + if ( pMediaImpl.isNull()) + { + // If we didn't find face media for this face, check whether this face is showing parcel media. + pMediaImpl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(tep->getID()); + } + + if ( pMediaImpl.notNull()) + { + LLPluginClassMedia *media = pMediaImpl->getMediaPlugin(); + if(media) + { + S32 media_width = media->getWidth(); + S32 media_height = media->getHeight(); + S32 texture_width = media->getTextureWidth(); + S32 texture_height = media->getTextureHeight(); + F32 scale_s = (F32)media_width / (F32)texture_width; + F32 scale_t = (F32)media_height / (F32)texture_height; + + // set scale and adjust offset + object->setTEScaleS( te, scale_s ); + object->setTEScaleT( te, scale_t ); // don't need to flip Y anymore since QT does this for us now. + object->setTEOffsetS( te, -( 1.0f - scale_s ) / 2.0f ); + object->setTEOffsetT( te, -( 1.0f - scale_t ) / 2.0f ); + } + } + return true; + }; +}; + +void LLPanelFace::onClickAutoFix(void* userdata) +{ + LLPanelFaceSetMediaFunctor setfunc; + LLSelectMgr::getInstance()->getSelection()->applyToTEs(&setfunc); + + LLPanelFaceSendFunctor sendfunc; + LLSelectMgr::getInstance()->getSelection()->applyToObjects(&sendfunc); +} + +void LLPanelFace::onAlignTexture(void* userdata) +{ + LLPanelFace* self = (LLPanelFace*)userdata; + self->alignTestureLayer(); +} + +void LLPanelFace::onClickBtnLoadInvPBR(void* userdata) +{ + // Shouldn't this be "save to inventory?" + LLPanelFace* self = (LLPanelFace*)userdata; + LLTextureCtrl* pbr_ctrl = self->findChild("pbr_control"); + pbr_ctrl->showPicker(true); +} + +void LLPanelFace::onClickBtnEditPBR(void* userdata) +{ + LLMaterialEditor::loadLive(); +} + +void LLPanelFace::onClickBtnSavePBR(void* userdata) +{ + LLMaterialEditor::saveObjectsMaterialAs(); +} + +enum EPasteMode +{ + PASTE_COLOR, + PASTE_TEXTURE +}; + +struct LLPanelFacePasteTexFunctor : public LLSelectedTEFunctor +{ + LLPanelFacePasteTexFunctor(LLPanelFace* panel, EPasteMode mode) : + mPanelFace(panel), mMode(mode) {} + + virtual bool apply(LLViewerObject* objectp, S32 te) + { + switch (mMode) + { + case PASTE_COLOR: + mPanelFace->onPasteColor(objectp, te); + break; + case PASTE_TEXTURE: + mPanelFace->onPasteTexture(objectp, te); + break; + } + return true; + } +private: + LLPanelFace *mPanelFace; + EPasteMode mMode; +}; + +struct LLPanelFaceUpdateFunctor : public LLSelectedObjectFunctor +{ + LLPanelFaceUpdateFunctor(bool update_media) + : mUpdateMedia(update_media) + {} + + virtual bool apply(LLViewerObject* object) + { + object->sendTEUpdate(); + + if (mUpdateMedia) + { + LLVOVolume *vo = dynamic_cast(object); + if (vo && vo->hasMedia()) + { + vo->sendMediaDataUpdate(); + } + } + return true; + } +private: + bool mUpdateMedia; +}; + +struct LLPanelFaceNavigateHomeFunctor : public LLSelectedTEFunctor +{ + virtual bool apply(LLViewerObject* objectp, S32 te) + { + if (objectp && objectp->getTE(te)) + { + LLTextureEntry* tep = objectp->getTE(te); + const LLMediaEntry *media_data = tep->getMediaData(); + if (media_data) + { + if (media_data->getCurrentURL().empty() && media_data->getAutoPlay()) + { + viewer_media_t media_impl = + LLViewerMedia::getInstance()->getMediaImplFromTextureID(tep->getMediaData()->getMediaID()); + if (media_impl) + { + media_impl->navigateHome(); + } + } + } + } + return true; + } +}; + +void LLPanelFace::onCopyColor() +{ + LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); + LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode(); + S32 selected_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount(); + if (!objectp || !node + || objectp->getPCode() != LL_PCODE_VOLUME + || !objectp->permModify() + || objectp->isPermanentEnforced() + || selected_count > 1) + { + return; + } + + if (mClipboardParams.has("color")) + { + mClipboardParams["color"].clear(); + } + else + { + mClipboardParams["color"] = LLSD::emptyArray(); + } + + std::map asset_item_map; + + // a way to resolve situations where source and target have different amount of faces + S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); + mClipboardParams["color_all_tes"] = (num_tes != 1) || (LLToolFace::getInstance() == LLToolMgr::getInstance()->getCurrentTool()); + for (S32 te = 0; te < num_tes; ++te) + { + if (node->isTESelected(te)) + { + LLTextureEntry* tep = objectp->getTE(te); + if (tep) + { + LLSD te_data; + + // asLLSD() includes media + te_data["te"] = tep->asLLSD(); // Note: includes a lot more than just color/alpha/glow + + mClipboardParams["color"].append(te_data); + } + } + } +} + +void LLPanelFace::onPasteColor() +{ + if (!mClipboardParams.has("color")) + { + return; + } + + LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); + LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode(); + S32 selected_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount(); + if (!objectp || !node + || objectp->getPCode() != LL_PCODE_VOLUME + || !objectp->permModify() + || objectp->isPermanentEnforced() + || selected_count > 1) + { + // not supposed to happen + LL_WARNS() << "Failed to paste color due to missing or wrong selection" << LL_ENDL; + return; + } + + bool face_selection_mode = LLToolFace::getInstance() == LLToolMgr::getInstance()->getCurrentTool(); + LLSD &clipboard = mClipboardParams["color"]; // array + S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); + S32 compare_tes = num_tes; + + if (face_selection_mode) + { + compare_tes = 0; + for (S32 te = 0; te < num_tes; ++te) + { + if (node->isTESelected(te)) + { + compare_tes++; + } + } + } + + // we can copy if single face was copied in edit face mode or if face count matches + if (!((clipboard.size() == 1) && mClipboardParams["color_all_tes"].asBoolean()) + && compare_tes != clipboard.size()) + { + LLSD notif_args; + if (face_selection_mode) + { + static std::string reason = getString("paste_error_face_selection_mismatch"); + notif_args["REASON"] = reason; + } + else + { + static std::string reason = getString("paste_error_object_face_count_mismatch"); + notif_args["REASON"] = reason; + } + LLNotificationsUtil::add("FacePasteFailed", notif_args); + return; + } + + LLObjectSelectionHandle selected_objects = LLSelectMgr::getInstance()->getSelection(); + + LLPanelFacePasteTexFunctor paste_func(this, PASTE_COLOR); + selected_objects->applyToTEs(&paste_func); + + LLPanelFaceUpdateFunctor sendfunc(false); + selected_objects->applyToObjects(&sendfunc); +} + +void LLPanelFace::onPasteColor(LLViewerObject* objectp, S32 te) +{ + LLSD te_data; + LLSD &clipboard = mClipboardParams["color"]; // array + if ((clipboard.size() == 1) && mClipboardParams["color_all_tes"].asBoolean()) + { + te_data = *(clipboard.beginArray()); + } + else if (clipboard[te]) + { + te_data = clipboard[te]; + } + else + { + return; + } + + LLTextureEntry* tep = objectp->getTE(te); + if (tep) + { + if (te_data.has("te")) + { + // Color / Alpha + if (te_data["te"].has("colors")) + { + LLColor4 color = tep->getColor(); + + LLColor4 clip_color; + clip_color.setValue(te_data["te"]["colors"]); + + // Color + color.mV[VRED] = clip_color.mV[VRED]; + color.mV[VGREEN] = clip_color.mV[VGREEN]; + color.mV[VBLUE] = clip_color.mV[VBLUE]; + + // Alpha + color.mV[VALPHA] = clip_color.mV[VALPHA]; + + objectp->setTEColor(te, color); + } + + // Color/fullbright + if (te_data["te"].has("fullbright")) + { + objectp->setTEFullbright(te, te_data["te"]["fullbright"].asInteger()); + } + + // Glow + if (te_data["te"].has("glow")) + { + objectp->setTEGlow(te, (F32)te_data["te"]["glow"].asReal()); + } + } + } +} + +void LLPanelFace::onCopyTexture() +{ + LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); + LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode(); + S32 selected_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount(); + if (!objectp || !node + || objectp->getPCode() != LL_PCODE_VOLUME + || !objectp->permModify() + || objectp->isPermanentEnforced() + || selected_count > 1 + || !LLMaterialEditor::canClipboardObjectsMaterial()) + { + return; + } + + if (mClipboardParams.has("texture")) + { + mClipboardParams["texture"].clear(); + } + else + { + mClipboardParams["texture"] = LLSD::emptyArray(); + } + + std::map asset_item_map; + + // a way to resolve situations where source and target have different amount of faces + S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); + mClipboardParams["texture_all_tes"] = (num_tes != 1) || (LLToolFace::getInstance() == LLToolMgr::getInstance()->getCurrentTool()); + for (S32 te = 0; te < num_tes; ++te) + { + if (node->isTESelected(te)) + { + LLTextureEntry* tep = objectp->getTE(te); + if (tep) + { + LLSD te_data; + + // asLLSD() includes media + te_data["te"] = tep->asLLSD(); + te_data["te"]["shiny"] = tep->getShiny(); + te_data["te"]["bumpmap"] = tep->getBumpmap(); + te_data["te"]["bumpshiny"] = tep->getBumpShiny(); + te_data["te"]["bumpfullbright"] = tep->getBumpShinyFullbright(); + te_data["te"]["texgen"] = tep->getTexGen(); + te_data["te"]["pbr"] = objectp->getRenderMaterialID(te); + if (tep->getGLTFMaterialOverride() != nullptr) + { + te_data["te"]["pbr_override"] = tep->getGLTFMaterialOverride()->asJSON(); + } + + if (te_data["te"].has("imageid")) + { + LLUUID item_id; + LLUUID id = te_data["te"]["imageid"].asUUID(); + bool from_library = get_is_predefined_texture(id); + bool full_perm = from_library; + + if (!full_perm + && objectp->permCopy() + && objectp->permTransfer() + && objectp->permModify()) + { + // If agent created this object and nothing is limiting permissions, mark as full perm + // If agent was granted permission to edit objects owned and created by somebody else, mark full perm + // This check is not perfect since we can't figure out whom textures belong to so this ended up restrictive + std::string creator_app_link; + LLUUID creator_id; + LLSelectMgr::getInstance()->selectGetCreator(creator_id, creator_app_link); + full_perm = objectp->mOwnerID == creator_id; + } + + if (id.notNull() && !full_perm) + { + std::map::iterator iter = asset_item_map.find(id); + if (iter != asset_item_map.end()) + { + item_id = iter->second; + } + else + { + // What this does is simply searches inventory for item with same asset id, + // as result it is Hightly unreliable, leaves little control to user, borderline hack + // but there are little options to preserve permissions - multiple inventory + // items might reference same asset and inventory search is expensive. + bool no_transfer = false; + if (objectp->getInventoryItemByAsset(id)) + { + no_transfer = !objectp->getInventoryItemByAsset(id)->getIsFullPerm(); + } + item_id = get_copy_free_item_by_asset_id(id, no_transfer); + // record value to avoid repeating inventory search when possible + asset_item_map[id] = item_id; + } + } + + if (item_id.notNull() && gInventory.isObjectDescendentOf(item_id, gInventory.getLibraryRootFolderID())) + { + full_perm = true; + from_library = true; + } + + { + te_data["te"]["itemfullperm"] = full_perm; + te_data["te"]["fromlibrary"] = from_library; + + // If full permission object, texture is free to copy, + // but otherwise we need to check inventory and extract permissions + // + // Normally we care only about restrictions for current user and objects + // don't inherit any 'next owner' permissions from texture, so there is + // no need to record item id if full_perm==true + if (!full_perm && !from_library && item_id.notNull()) + { + LLViewerInventoryItem* itemp = gInventory.getItem(item_id); + if (itemp) + { + LLPermissions item_permissions = itemp->getPermissions(); + if (item_permissions.allowOperationBy(PERM_COPY, + gAgent.getID(), + gAgent.getGroupID())) + { + te_data["te"]["imageitemid"] = item_id; + te_data["te"]["itemfullperm"] = itemp->getIsFullPerm(); + if (!itemp->isFinished()) + { + // needed for dropTextureAllFaces + LLInventoryModelBackgroundFetch::instance().start(item_id, false); + } + } + } + } + } + } + + LLMaterialPtr material_ptr = tep->getMaterialParams(); + if (!material_ptr.isNull()) + { + LLSD mat_data; + + mat_data["NormMap"] = material_ptr->getNormalID(); + mat_data["SpecMap"] = material_ptr->getSpecularID(); + + mat_data["NormRepX"] = material_ptr->getNormalRepeatX(); + mat_data["NormRepY"] = material_ptr->getNormalRepeatY(); + mat_data["NormOffX"] = material_ptr->getNormalOffsetX(); + mat_data["NormOffY"] = material_ptr->getNormalOffsetY(); + mat_data["NormRot"] = material_ptr->getNormalRotation(); + + mat_data["SpecRepX"] = material_ptr->getSpecularRepeatX(); + mat_data["SpecRepY"] = material_ptr->getSpecularRepeatY(); + mat_data["SpecOffX"] = material_ptr->getSpecularOffsetX(); + mat_data["SpecOffY"] = material_ptr->getSpecularOffsetY(); + mat_data["SpecRot"] = material_ptr->getSpecularRotation(); + + mat_data["SpecColor"] = material_ptr->getSpecularLightColor().getValue(); + mat_data["SpecExp"] = material_ptr->getSpecularLightExponent(); + mat_data["EnvIntensity"] = material_ptr->getEnvironmentIntensity(); + mat_data["AlphaMaskCutoff"] = material_ptr->getAlphaMaskCutoff(); + mat_data["DiffuseAlphaMode"] = material_ptr->getDiffuseAlphaMode(); + + // Replace no-copy textures, destination texture will get used instead if available + if (mat_data.has("NormMap")) + { + LLUUID id = mat_data["NormMap"].asUUID(); + if (id.notNull() && !get_can_copy_texture(id)) + { + mat_data["NormMap"] = DEFAULT_OBJECT_TEXTURE; + mat_data["NormMapNoCopy"] = true; + } + + } + if (mat_data.has("SpecMap")) + { + LLUUID id = mat_data["SpecMap"].asUUID(); + if (id.notNull() && !get_can_copy_texture(id)) + { + mat_data["SpecMap"] = DEFAULT_OBJECT_TEXTURE; + mat_data["SpecMapNoCopy"] = true; + } + + } + + te_data["material"] = mat_data; + } + + mClipboardParams["texture"].append(te_data); + } + } + } +} + +void LLPanelFace::onPasteTexture() +{ + if (!mClipboardParams.has("texture")) + { + return; + } + + LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); + LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode(); + S32 selected_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount(); + if (!objectp || !node + || objectp->getPCode() != LL_PCODE_VOLUME + || !objectp->permModify() + || objectp->isPermanentEnforced() + || selected_count > 1 + || !LLMaterialEditor::canClipboardObjectsMaterial()) + { + // not supposed to happen + LL_WARNS() << "Failed to paste texture due to missing or wrong selection" << LL_ENDL; + return; + } + + bool face_selection_mode = LLToolFace::getInstance() == LLToolMgr::getInstance()->getCurrentTool(); + LLSD &clipboard = mClipboardParams["texture"]; // array + S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); + S32 compare_tes = num_tes; + + if (face_selection_mode) + { + compare_tes = 0; + for (S32 te = 0; te < num_tes; ++te) + { + if (node->isTESelected(te)) + { + compare_tes++; + } + } + } + + // we can copy if single face was copied in edit face mode or if face count matches + if (!((clipboard.size() == 1) && mClipboardParams["texture_all_tes"].asBoolean()) + && compare_tes != clipboard.size()) + { + LLSD notif_args; + if (face_selection_mode) + { + static std::string reason = getString("paste_error_face_selection_mismatch"); + notif_args["REASON"] = reason; + } + else + { + static std::string reason = getString("paste_error_object_face_count_mismatch"); + notif_args["REASON"] = reason; + } + LLNotificationsUtil::add("FacePasteFailed", notif_args); + return; + } + + bool full_perm_object = true; + LLSD::array_const_iterator iter = clipboard.beginArray(); + LLSD::array_const_iterator end = clipboard.endArray(); + for (; iter != end; ++iter) + { + const LLSD& te_data = *iter; + if (te_data.has("te") && te_data["te"].has("imageid")) + { + bool full_perm = te_data["te"].has("itemfullperm") && te_data["te"]["itemfullperm"].asBoolean(); + full_perm_object &= full_perm; + if (!full_perm) + { + if (te_data["te"].has("imageitemid")) + { + LLUUID item_id = te_data["te"]["imageitemid"].asUUID(); + if (item_id.notNull()) + { + LLViewerInventoryItem* itemp = gInventory.getItem(item_id); + if (!itemp) + { + // image might be in object's inventory, but it can be not up to date + LLSD notif_args; + static std::string reason = getString("paste_error_inventory_not_found"); + notif_args["REASON"] = reason; + LLNotificationsUtil::add("FacePasteFailed", notif_args); + return; + } + } + } + else + { + // Item was not found on 'copy' stage + // Since this happened at copy, might be better to either show this + // at copy stage or to drop clipboard here + LLSD notif_args; + static std::string reason = getString("paste_error_inventory_not_found"); + notif_args["REASON"] = reason; + LLNotificationsUtil::add("FacePasteFailed", notif_args); + return; + } + } + } + } + + if (!full_perm_object) + { + LLNotificationsUtil::add("FacePasteTexturePermissions"); + } + + LLObjectSelectionHandle selected_objects = LLSelectMgr::getInstance()->getSelection(); + + LLPanelFacePasteTexFunctor paste_func(this, PASTE_TEXTURE); + selected_objects->applyToTEs(&paste_func); + + LLPanelFaceUpdateFunctor sendfunc(true); + selected_objects->applyToObjects(&sendfunc); + + LLGLTFMaterialList::flushUpdates(); + + LLPanelFaceNavigateHomeFunctor navigate_home_func; + selected_objects->applyToTEs(&navigate_home_func); +} + +void LLPanelFace::onPasteTexture(LLViewerObject* objectp, S32 te) +{ + LLSD te_data; + LLSD &clipboard = mClipboardParams["texture"]; // array + if ((clipboard.size() == 1) && mClipboardParams["texture_all_tes"].asBoolean()) + { + te_data = *(clipboard.beginArray()); + } + else if (clipboard[te]) + { + te_data = clipboard[te]; + } + else + { + return; + } + + LLTextureEntry* tep = objectp->getTE(te); + if (tep) + { + if (te_data.has("te")) + { + // Texture + bool full_perm = te_data["te"].has("itemfullperm") && te_data["te"]["itemfullperm"].asBoolean(); + bool from_library = te_data["te"].has("fromlibrary") && te_data["te"]["fromlibrary"].asBoolean(); + if (te_data["te"].has("imageid")) + { + const LLUUID& imageid = te_data["te"]["imageid"].asUUID(); //texture or asset id + LLViewerInventoryItem* itemp_res = NULL; + + if (te_data["te"].has("imageitemid")) + { + LLUUID item_id = te_data["te"]["imageitemid"].asUUID(); + if (item_id.notNull()) + { + LLViewerInventoryItem* itemp = gInventory.getItem(item_id); + if (itemp && itemp->isFinished()) + { + // dropTextureAllFaces will fail if incomplete + itemp_res = itemp; + } + else + { + // Theoretically shouldn't happend, but if it does happen, we + // might need to add a notification to user that paste will fail + // since inventory isn't fully loaded + LL_WARNS() << "Item " << item_id << " is incomplete, paste might fail silently." << LL_ENDL; + } + } + } + // for case when item got removed from inventory after we pressed 'copy' + // or texture got pasted into previous object + if (!itemp_res && !full_perm) + { + // Due to checks for imageitemid in LLPanelFace::onPasteTexture() this should no longer be reachable. + LL_INFOS() << "Item " << te_data["te"]["imageitemid"].asUUID() << " no longer in inventory." << LL_ENDL; + // Todo: fix this, we are often searching same texture multiple times (equal to number of faces) + // Perhaps just mPanelFace->onPasteTexture(objectp, te, &asset_to_item_id_map); ? Not pretty, but will work + LLViewerInventoryCategory::cat_array_t cats; + LLViewerInventoryItem::item_array_t items; + LLAssetIDMatches asset_id_matches(imageid); + gInventory.collectDescendentsIf(LLUUID::null, + cats, + items, + LLInventoryModel::INCLUDE_TRASH, + asset_id_matches); + + // Extremely unreliable and perfomance unfriendly. + // But we need this to check permissions and it is how texture control finds items + for (S32 i = 0; i < items.size(); i++) + { + LLViewerInventoryItem* itemp = items[i]; + if (itemp && itemp->isFinished()) + { + // dropTextureAllFaces will fail if incomplete + LLPermissions item_permissions = itemp->getPermissions(); + if (item_permissions.allowOperationBy(PERM_COPY, + gAgent.getID(), + gAgent.getGroupID())) + { + itemp_res = itemp; + break; // first match + } + } + } + } + + if (itemp_res) + { + if (te == -1) // all faces + { + LLToolDragAndDrop::dropTextureAllFaces(objectp, + itemp_res, + from_library ? LLToolDragAndDrop::SOURCE_LIBRARY : LLToolDragAndDrop::SOURCE_AGENT, + LLUUID::null, + false); + } + else // one face + { + LLToolDragAndDrop::dropTextureOneFace(objectp, + te, + itemp_res, + from_library ? LLToolDragAndDrop::SOURCE_LIBRARY : LLToolDragAndDrop::SOURCE_AGENT, + LLUUID::null, + false, + 0); + } + } + // not an inventory item or no complete items + else if (full_perm) + { + // Either library, local or existed as fullperm when user made a copy + LLViewerTexture* image = LLViewerTextureManager::getFetchedTexture(imageid, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + objectp->setTEImage(U8(te), image); + } + } + + if (te_data["te"].has("bumpmap")) + { + objectp->setTEBumpmap(te, (U8)te_data["te"]["bumpmap"].asInteger()); + } + if (te_data["te"].has("bumpshiny")) + { + objectp->setTEBumpShiny(te, (U8)te_data["te"]["bumpshiny"].asInteger()); + } + if (te_data["te"].has("bumpfullbright")) + { + objectp->setTEBumpShinyFullbright(te, (U8)te_data["te"]["bumpfullbright"].asInteger()); + } + if (te_data["te"].has("texgen")) + { + objectp->setTETexGen(te, (U8)te_data["te"]["texgen"].asInteger()); + } + + // PBR/GLTF + if (te_data["te"].has("pbr")) + { + objectp->setRenderMaterialID(te, te_data["te"]["pbr"].asUUID(), false /*managing our own update*/); + tep->setGLTFRenderMaterial(nullptr); + tep->setGLTFMaterialOverride(nullptr); + + LLSD override_data; + override_data["object_id"] = objectp->getID(); + override_data["side"] = te; + if (te_data["te"].has("pbr_override")) + { + override_data["gltf_json"] = te_data["te"]["pbr_override"]; + } + else + { + override_data["gltf_json"] = ""; + } + + override_data["asset_id"] = te_data["te"]["pbr"].asUUID(); + + LLGLTFMaterialList::queueUpdate(override_data); + } + else + { + objectp->setRenderMaterialID(te, LLUUID::null, false /*send in bulk later*/ ); + tep->setGLTFRenderMaterial(nullptr); + tep->setGLTFMaterialOverride(nullptr); + + // blank out most override data on the server + LLGLTFMaterialList::queueApply(objectp, te, LLUUID::null); + } + + // Texture map + if (te_data["te"].has("scales") && te_data["te"].has("scalet")) + { + objectp->setTEScale(te, (F32)te_data["te"]["scales"].asReal(), (F32)te_data["te"]["scalet"].asReal()); + } + if (te_data["te"].has("offsets") && te_data["te"].has("offsett")) + { + objectp->setTEOffset(te, (F32)te_data["te"]["offsets"].asReal(), (F32)te_data["te"]["offsett"].asReal()); + } + if (te_data["te"].has("imagerot")) + { + objectp->setTERotation(te, (F32)te_data["te"]["imagerot"].asReal()); + } + + // Media + if (te_data["te"].has("media_flags")) + { + U8 media_flags = te_data["te"]["media_flags"].asInteger(); + objectp->setTEMediaFlags(te, media_flags); + LLVOVolume *vo = dynamic_cast(objectp); + if (vo && te_data["te"].has(LLTextureEntry::TEXTURE_MEDIA_DATA_KEY)) + { + vo->syncMediaData(te, te_data["te"][LLTextureEntry::TEXTURE_MEDIA_DATA_KEY], true/*merge*/, true/*ignore_agent*/); + } + } + else + { + // Keep media flags on destination unchanged + } + } + + if (te_data.has("material")) + { + LLUUID object_id = objectp->getID(); + + // Normal + // Replace placeholders with target's + if (te_data["material"].has("NormMapNoCopy")) + { + LLMaterialPtr material = tep->getMaterialParams(); + if (material.notNull()) + { + LLUUID id = material->getNormalID(); + if (id.notNull()) + { + te_data["material"]["NormMap"] = id; + } + } + } + LLSelectedTEMaterial::setNormalID(this, te_data["material"]["NormMap"].asUUID(), te, object_id); + LLSelectedTEMaterial::setNormalRepeatX(this, (F32)te_data["material"]["NormRepX"].asReal(), te, object_id); + LLSelectedTEMaterial::setNormalRepeatY(this, (F32)te_data["material"]["NormRepY"].asReal(), te, object_id); + LLSelectedTEMaterial::setNormalOffsetX(this, (F32)te_data["material"]["NormOffX"].asReal(), te, object_id); + LLSelectedTEMaterial::setNormalOffsetY(this, (F32)te_data["material"]["NormOffY"].asReal(), te, object_id); + LLSelectedTEMaterial::setNormalRotation(this, (F32)te_data["material"]["NormRot"].asReal(), te, object_id); + + // Specular + // Replace placeholders with target's + if (te_data["material"].has("SpecMapNoCopy")) + { + LLMaterialPtr material = tep->getMaterialParams(); + if (material.notNull()) + { + LLUUID id = material->getSpecularID(); + if (id.notNull()) + { + te_data["material"]["SpecMap"] = id; + } + } + } + LLSelectedTEMaterial::setSpecularID(this, te_data["material"]["SpecMap"].asUUID(), te, object_id); + LLSelectedTEMaterial::setSpecularRepeatX(this, (F32)te_data["material"]["SpecRepX"].asReal(), te, object_id); + LLSelectedTEMaterial::setSpecularRepeatY(this, (F32)te_data["material"]["SpecRepY"].asReal(), te, object_id); + LLSelectedTEMaterial::setSpecularOffsetX(this, (F32)te_data["material"]["SpecOffX"].asReal(), te, object_id); + LLSelectedTEMaterial::setSpecularOffsetY(this, (F32)te_data["material"]["SpecOffY"].asReal(), te, object_id); + LLSelectedTEMaterial::setSpecularRotation(this, (F32)te_data["material"]["SpecRot"].asReal(), te, object_id); + LLColor4U spec_color(te_data["material"]["SpecColor"]); + LLSelectedTEMaterial::setSpecularLightColor(this, spec_color, te); + LLSelectedTEMaterial::setSpecularLightExponent(this, (U8)te_data["material"]["SpecExp"].asInteger(), te, object_id); + LLSelectedTEMaterial::setEnvironmentIntensity(this, (U8)te_data["material"]["EnvIntensity"].asInteger(), te, object_id); + LLSelectedTEMaterial::setDiffuseAlphaMode(this, (U8)te_data["material"]["DiffuseAlphaMode"].asInteger(), te, object_id); + LLSelectedTEMaterial::setAlphaMaskCutoff(this, (U8)te_data["material"]["AlphaMaskCutoff"].asInteger(), te, object_id); + if (te_data.has("te") && te_data["te"].has("shiny")) + { + objectp->setTEShiny(te, (U8)te_data["te"]["shiny"].asInteger()); + } + } + } +} + +void LLPanelFace::menuDoToSelected(const LLSD& userdata) +{ + std::string command = userdata.asString(); + + // paste + if (command == "color_paste") + { + onPasteColor(); + } + else if (command == "texture_paste") + { + onPasteTexture(); + } + // copy + else if (command == "color_copy") + { + onCopyColor(); + } + else if (command == "texture_copy") + { + onCopyTexture(); + } +} + +bool LLPanelFace::menuEnableItem(const LLSD& userdata) +{ + std::string command = userdata.asString(); + + // paste options + if (command == "color_paste") + { + return mClipboardParams.has("color"); + } + else if (command == "texture_paste") + { + return mClipboardParams.has("texture"); + } + return false; +} + + +// static +void LLPanelFace::onCommitPlanarAlign(LLUICtrl* ctrl, void* userdata) +{ + LLPanelFace* self = (LLPanelFace*) userdata; + self->getState(); + self->sendTextureInfo(); +} + +void LLPanelFace::updateGLTFTextureTransform(float value, U32 pbr_type, std::function edit) +{ + U32 texture_info_start; + U32 texture_info_end; + const LLGLTFMaterial::TextureInfo texture_info = texture_info_from_pbrtype(pbr_type); + if (texture_info == LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT) + { + texture_info_start = 0; + texture_info_end = LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; + } + else + { + texture_info_start = texture_info_from_pbrtype(pbr_type); + texture_info_end = texture_info_start + 1; + } + updateSelectedGLTFMaterials([&](LLGLTFMaterial* new_override) + { + for (U32 ti = texture_info_start; ti < texture_info_end; ++ti) + { + LLGLTFMaterial::TextureTransform& new_transform = new_override->mTextureTransform[(LLGLTFMaterial::TextureInfo)ti]; + edit(&new_transform); + } + }); +} + +void LLPanelFace::setMaterialOverridesFromSelection() +{ + const U32 pbr_type = findChild("radio_pbr_type")->getSelectedIndex(); + const LLGLTFMaterial::TextureInfo texture_info = texture_info_from_pbrtype(pbr_type); + U32 texture_info_start; + U32 texture_info_end; + if (texture_info == LLGLTFMaterial::TextureInfo::GLTF_TEXTURE_INFO_COUNT) + { + texture_info_start = 0; + texture_info_end = LLGLTFMaterial::TextureInfo::GLTF_TEXTURE_INFO_COUNT; + } + else + { + texture_info_start = texture_info; + texture_info_end = texture_info + 1; + } + + bool read_transform = true; + LLGLTFMaterial::TextureTransform transform; + bool scale_u_same = true; + bool scale_v_same = true; + bool rotation_same = true; + bool offset_u_same = true; + bool offset_v_same = true; + + for (U32 i = texture_info_start; i < texture_info_end; ++i) + { + LLGLTFMaterial::TextureTransform this_transform; + bool this_scale_u_same = true; + bool this_scale_v_same = true; + bool this_rotation_same = true; + bool this_offset_u_same = true; + bool this_offset_v_same = true; + + readSelectedGLTFMaterial([&](const LLGLTFMaterial* mat) + { + return mat ? mat->mTextureTransform[i].mScale[VX] : 0.f; + }, this_transform.mScale[VX], this_scale_u_same, true, 1e-3f); + readSelectedGLTFMaterial([&](const LLGLTFMaterial* mat) + { + return mat ? mat->mTextureTransform[i].mScale[VY] : 0.f; + }, this_transform.mScale[VY], this_scale_v_same, true, 1e-3f); + readSelectedGLTFMaterial([&](const LLGLTFMaterial* mat) + { + return mat ? mat->mTextureTransform[i].mRotation : 0.f; + }, this_transform.mRotation, this_rotation_same, true, 1e-3f); + readSelectedGLTFMaterial([&](const LLGLTFMaterial* mat) + { + return mat ? mat->mTextureTransform[i].mOffset[VX] : 0.f; + }, this_transform.mOffset[VX], this_offset_u_same, true, 1e-3f); + readSelectedGLTFMaterial([&](const LLGLTFMaterial* mat) + { + return mat ? mat->mTextureTransform[i].mOffset[VY] : 0.f; + }, this_transform.mOffset[VY], this_offset_v_same, true, 1e-3f); + + scale_u_same = scale_u_same && this_scale_u_same; + scale_v_same = scale_v_same && this_scale_v_same; + rotation_same = rotation_same && this_rotation_same; + offset_u_same = offset_u_same && this_offset_u_same; + offset_v_same = offset_v_same && this_offset_v_same; + + if (read_transform) + { + read_transform = false; + transform = this_transform; + } + else + { + scale_u_same = scale_u_same && (this_transform.mScale[VX] == transform.mScale[VX]); + scale_v_same = scale_v_same && (this_transform.mScale[VY] == transform.mScale[VY]); + rotation_same = rotation_same && (this_transform.mRotation == transform.mRotation); + offset_u_same = offset_u_same && (this_transform.mOffset[VX] == transform.mOffset[VX]); + offset_v_same = offset_v_same && (this_transform.mOffset[VY] == transform.mOffset[VY]); + } + } + + LLUICtrl* gltfCtrlTextureScaleU = getChild("gltfTextureScaleU"); + LLUICtrl* gltfCtrlTextureScaleV = getChild("gltfTextureScaleV"); + LLUICtrl* gltfCtrlTextureRotation = getChild("gltfTextureRotation"); + LLUICtrl* gltfCtrlTextureOffsetU = getChild("gltfTextureOffsetU"); + LLUICtrl* gltfCtrlTextureOffsetV = getChild("gltfTextureOffsetV"); + + gltfCtrlTextureScaleU->setValue(transform.mScale[VX]); + gltfCtrlTextureScaleV->setValue(transform.mScale[VY]); + gltfCtrlTextureRotation->setValue(transform.mRotation * RAD_TO_DEG); + gltfCtrlTextureOffsetU->setValue(transform.mOffset[VX]); + gltfCtrlTextureOffsetV->setValue(transform.mOffset[VY]); + + gltfCtrlTextureScaleU->setTentative(!scale_u_same); + gltfCtrlTextureScaleV->setTentative(!scale_v_same); + gltfCtrlTextureRotation->setTentative(!rotation_same); + gltfCtrlTextureOffsetU->setTentative(!offset_u_same); + gltfCtrlTextureOffsetV->setTentative(!offset_v_same); +} + +void LLPanelFace::Selection::connect() +{ + if (!mSelectConnection.connected()) + { + mSelectConnection = LLSelectMgr::instance().mUpdateSignal.connect(boost::bind(&LLPanelFace::Selection::onSelectionChanged, this)); + } +} + +bool LLPanelFace::Selection::update() +{ + const bool changed = mChanged || compareSelection(); + mChanged = false; + return changed; +} + +void LLPanelFace::Selection::onSelectedObjectUpdated(const LLUUID& object_id, S32 side) +{ + if (object_id == mSelectedObjectID) + { + if (side == mLastSelectedSide) + { + mChanged = true; + } + else if (mLastSelectedSide == -1) // if last selected face was deselected + { + LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode(); + if (node && node->isTESelected(side)) + { + mChanged = true; + } + } + } +} + +bool LLPanelFace::Selection::compareSelection() +{ + if (!mNeedsSelectionCheck) + { + return false; + } + mNeedsSelectionCheck = false; + + const S32 old_object_count = mSelectedObjectCount; + const S32 old_te_count = mSelectedTECount; + const LLUUID old_object_id = mSelectedObjectID; + const S32 old_side = mLastSelectedSide; + + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + LLSelectNode* node = selection->getFirstNode(); + if (node) + { + LLViewerObject* object = node->getObject(); + mSelectedObjectCount = selection->getObjectCount(); + mSelectedTECount = selection->getTECount(); + mSelectedObjectID = object->getID(); + mLastSelectedSide = node->getLastSelectedTE(); + } + else + { + mSelectedObjectCount = 0; + mSelectedTECount = 0; + mSelectedObjectID = LLUUID::null; + mLastSelectedSide = -1; + } + + const bool selection_changed = + old_object_count != mSelectedObjectCount + || old_te_count != mSelectedTECount + || old_object_id != mSelectedObjectID + || old_side != mLastSelectedSide; + mChanged = mChanged || selection_changed; + return selection_changed; +} + +void LLPanelFace::onCommitGLTFTextureScaleU(LLUICtrl* ctrl) +{ + const float value = ctrl->getValue().asReal(); + const U32 pbr_type = findChild("radio_pbr_type")->getSelectedIndex(); + updateGLTFTextureTransform(value, pbr_type, [&](LLGLTFMaterial::TextureTransform* new_transform) + { + new_transform->mScale.mV[VX] = value; + }); +} + +void LLPanelFace::onCommitGLTFTextureScaleV(LLUICtrl* ctrl) +{ + const float value = ctrl->getValue().asReal(); + const U32 pbr_type = findChild("radio_pbr_type")->getSelectedIndex(); + updateGLTFTextureTransform(value, pbr_type, [&](LLGLTFMaterial::TextureTransform* new_transform) + { + new_transform->mScale.mV[VY] = value; + }); +} + +void LLPanelFace::onCommitGLTFRotation(LLUICtrl* ctrl) +{ + const float value = ctrl->getValue().asReal() * DEG_TO_RAD; + const U32 pbr_type = findChild("radio_pbr_type")->getSelectedIndex(); + updateGLTFTextureTransform(value, pbr_type, [&](LLGLTFMaterial::TextureTransform* new_transform) + { + new_transform->mRotation = value; + }); +} + +void LLPanelFace::onCommitGLTFTextureOffsetU(LLUICtrl* ctrl) +{ + const float value = ctrl->getValue().asReal(); + const U32 pbr_type = findChild("radio_pbr_type")->getSelectedIndex(); + updateGLTFTextureTransform(value, pbr_type, [&](LLGLTFMaterial::TextureTransform* new_transform) + { + new_transform->mOffset.mV[VX] = value; + }); +} + +void LLPanelFace::onCommitGLTFTextureOffsetV(LLUICtrl* ctrl) +{ + const float value = ctrl->getValue().asReal(); + const U32 pbr_type = findChild("radio_pbr_type")->getSelectedIndex(); + updateGLTFTextureTransform(value, pbr_type, [&](LLGLTFMaterial::TextureTransform* new_transform) + { + new_transform->mOffset.mV[VY] = value; + }); +} + +void LLPanelFace::onTextureSelectionChanged(LLInventoryItem* itemp) +{ + LL_DEBUGS("Materials") << "item asset " << itemp->getAssetUUID() << LL_ENDL; + LLRadioGroup* radio_mat_type = findChild("radio_material_type"); + if(!radio_mat_type) + { + return; + } + U32 mattype = radio_mat_type->getSelectedIndex(); + std::string which_control="texture control"; + switch (mattype) + { + case MATTYPE_SPECULAR: + which_control = "shinytexture control"; + break; + case MATTYPE_NORMAL: + which_control = "bumpytexture control"; + break; + // no default needed + } + LL_DEBUGS("Materials") << "control " << which_control << LL_ENDL; + LLTextureCtrl* texture_ctrl = getChild(which_control); + if (texture_ctrl) + { + LLUUID obj_owner_id; + std::string obj_owner_name; + LLSelectMgr::instance().selectGetOwner(obj_owner_id, obj_owner_name); + + LLSaleInfo sale_info; + LLSelectMgr::instance().selectGetSaleInfo(sale_info); + + bool can_copy = itemp->getPermissions().allowCopyBy(gAgentID); // do we have perm to copy this texture? + bool can_transfer = itemp->getPermissions().allowOperationBy(PERM_TRANSFER, gAgentID); // do we have perm to transfer this texture? + bool is_object_owner = gAgentID == obj_owner_id; // does object for which we are going to apply texture belong to the agent? + bool not_for_sale = !sale_info.isForSale(); // is object for which we are going to apply texture not for sale? + + if (can_copy && can_transfer) + { + texture_ctrl->setCanApply(true, true); + return; + } + + // if texture has (no-transfer) attribute it can be applied only for object which we own and is not for sale + texture_ctrl->setCanApply(false, can_transfer ? true : is_object_owner && not_for_sale); + + if (gSavedSettings.getBOOL("TextureLivePreview")) + { + LLNotificationsUtil::add("LivePreviewUnavailable"); + } + } +} + +void LLPanelFace::onPbrSelectionChanged(LLInventoryItem* itemp) +{ + LLTextureCtrl* pbr_ctrl = findChild("pbr_control"); + if (pbr_ctrl) + { + LLUUID obj_owner_id; + std::string obj_owner_name; + LLSelectMgr::instance().selectGetOwner(obj_owner_id, obj_owner_name); + + LLSaleInfo sale_info; + LLSelectMgr::instance().selectGetSaleInfo(sale_info); + + bool can_copy = itemp->getPermissions().allowCopyBy(gAgentID); // do we have perm to copy this material? + bool can_transfer = itemp->getPermissions().allowOperationBy(PERM_TRANSFER, gAgentID); // do we have perm to transfer this material? + bool can_modify = itemp->getPermissions().allowOperationBy(PERM_MODIFY, gAgentID); // do we have perm to transfer this material? + bool is_object_owner = gAgentID == obj_owner_id; // does object for which we are going to apply material belong to the agent? + bool not_for_sale = !sale_info.isForSale(); // is object for which we are going to apply material not for sale? + bool from_library = ALEXANDRIA_LINDEN_ID == itemp->getPermissions().getOwner(); + + if ((can_copy && can_transfer && can_modify) || from_library) + { + pbr_ctrl->setCanApply(true, true); + return; + } + + // if material has (no-transfer) attribute it can be applied only for object which we own and is not for sale + pbr_ctrl->setCanApply(false, can_transfer ? true : is_object_owner && not_for_sale); + + if (gSavedSettings.getBOOL("TextureLivePreview")) + { + LLNotificationsUtil::add("LivePreviewUnavailablePBR"); + } + } +} + +bool LLPanelFace::isIdenticalPlanarTexgen() +{ + LLTextureEntry::e_texgen selected_texgen = LLTextureEntry::TEX_GEN_DEFAULT; + bool identical_texgen = false; + LLSelectedTE::getTexGen(selected_texgen, identical_texgen); + return (identical_texgen && (selected_texgen == LLTextureEntry::TEX_GEN_PLANAR)); +} + +void LLPanelFace::LLSelectedTE::getFace(LLFace*& face_to_return, bool& identical_face) +{ + struct LLSelectedTEGetFace : public LLSelectedTEGetFunctor + { + LLFace* get(LLViewerObject* object, S32 te) + { + return (object->mDrawable) ? object->mDrawable->getFace(te): NULL; + } + } get_te_face_func; + identical_face = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue(&get_te_face_func, face_to_return, false, (LLFace*)nullptr); +} + +void LLPanelFace::LLSelectedTE::getImageFormat(LLGLenum& image_format_to_return, bool& identical_face) +{ + LLGLenum image_format; + struct LLSelectedTEGetImageFormat : public LLSelectedTEGetFunctor + { + LLGLenum get(LLViewerObject* object, S32 te_index) + { + LLViewerTexture* image = object->getTEImage(te_index); + return image ? image->getPrimaryFormat() : GL_RGB; + } + } get_glenum; + identical_face = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue(&get_glenum, image_format); + image_format_to_return = image_format; +} + +void LLPanelFace::LLSelectedTE::getTexId(LLUUID& id, bool& identical) +{ + struct LLSelectedTEGetTexId : public LLSelectedTEGetFunctor + { + LLUUID get(LLViewerObject* object, S32 te_index) + { + LLTextureEntry *te = object->getTE(te_index); + if (te) + { + if ((te->getID() == IMG_USE_BAKED_EYES) || (te->getID() == IMG_USE_BAKED_HAIR) || (te->getID() == IMG_USE_BAKED_HEAD) || (te->getID() == IMG_USE_BAKED_LOWER) || (te->getID() == IMG_USE_BAKED_SKIRT) || (te->getID() == IMG_USE_BAKED_UPPER) + || (te->getID() == IMG_USE_BAKED_LEFTARM) || (te->getID() == IMG_USE_BAKED_LEFTLEG) || (te->getID() == IMG_USE_BAKED_AUX1) || (te->getID() == IMG_USE_BAKED_AUX2) || (te->getID() == IMG_USE_BAKED_AUX3)) + { + return te->getID(); + } + } + + LLUUID id; + LLViewerTexture* image = object->getTEImage(te_index); + if (image) + { + id = image->getID(); + } + + if (!id.isNull() && LLViewerMedia::getInstance()->textureHasMedia(id)) + { + if (te) + { + LLViewerTexture* tex = te->getID().notNull() ? gTextureList.findImage(te->getID(), TEX_LIST_STANDARD) : NULL; + if(!tex) + { + tex = LLViewerFetchedTexture::sDefaultImagep; + } + if (tex) + { + id = tex->getID(); + } + } + } + return id; + } + } func; + identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &func, id ); +} + +void LLPanelFace::LLSelectedTE::getPbrMaterialId(LLUUID& id, bool& identical, bool& has_faces_with_pbr, bool& has_faces_without_pbr) +{ + struct LLSelectedTEGetmatId : public LLSelectedTEFunctor + { + LLSelectedTEGetmatId() + : mHasFacesWithoutPBR(false) + , mHasFacesWithPBR(false) + , mIdenticalId(true) + , mIdenticalOverride(true) + , mInitialized(false) + , mMaterialOverride(LLGLTFMaterial::sDefault) + { + } + bool apply(LLViewerObject* object, S32 te_index) override + { + LLUUID pbr_id = object->getRenderMaterialID(te_index); + if (pbr_id.isNull()) + { + mHasFacesWithoutPBR = true; + } + else + { + mHasFacesWithPBR = true; + } + if (mInitialized) + { + if (mPBRId != pbr_id) + { + mIdenticalId = false; + } + + LLGLTFMaterial* te_override = object->getTE(te_index)->getGLTFMaterialOverride(); + if (te_override) + { + LLGLTFMaterial override = *te_override; + override.sanitizeAssetMaterial(); + mIdenticalOverride &= (override == mMaterialOverride); + } + else + { + mIdenticalOverride &= (mMaterialOverride == LLGLTFMaterial::sDefault); + } + } + else + { + mInitialized = true; + mPBRId = pbr_id; + LLGLTFMaterial* override = object->getTE(te_index)->getGLTFMaterialOverride(); + if (override) + { + mMaterialOverride = *override; + mMaterialOverride.sanitizeAssetMaterial(); + } + } + return true; + } + bool mHasFacesWithoutPBR; + bool mHasFacesWithPBR; + bool mIdenticalId; + bool mIdenticalOverride; + bool mInitialized; + LLGLTFMaterial mMaterialOverride; + LLUUID mPBRId; + } func; + LLSelectMgr::getInstance()->getSelection()->applyToTEs(&func); + id = func.mPBRId; + identical = func.mIdenticalId && func.mIdenticalOverride; + has_faces_with_pbr = func.mHasFacesWithPBR; + has_faces_without_pbr = func.mHasFacesWithoutPBR; +} + +void LLPanelFace::LLSelectedTEMaterial::getCurrent(LLMaterialPtr& material_ptr, bool& identical_material) +{ + struct MaterialFunctor : public LLSelectedTEGetFunctor + { + LLMaterialPtr get(LLViewerObject* object, S32 te_index) + { + return object->getTE(te_index)->getMaterialParams(); + } + } func; + identical_material = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &func, material_ptr); +} + +void LLPanelFace::LLSelectedTEMaterial::getMaxSpecularRepeats(F32& repeats, bool& identical) +{ + struct LLSelectedTEGetMaxSpecRepeats : public LLSelectedTEGetFunctor + { + F32 get(LLViewerObject* object, S32 face) + { + LLMaterial* mat = object->getTE(face)->getMaterialParams().get(); + U32 s_axis = VX; + U32 t_axis = VY; + F32 repeats_s = 1.0f; + F32 repeats_t = 1.0f; + if (mat) + { + mat->getSpecularRepeat(repeats_s, repeats_t); + repeats_s /= object->getScale().mV[s_axis]; + repeats_t /= object->getScale().mV[t_axis]; + } + return llmax(repeats_s, repeats_t); + } + + } max_spec_repeats_func; + identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &max_spec_repeats_func, repeats); +} + +void LLPanelFace::LLSelectedTEMaterial::getMaxNormalRepeats(F32& repeats, bool& identical) +{ + struct LLSelectedTEGetMaxNormRepeats : public LLSelectedTEGetFunctor + { + F32 get(LLViewerObject* object, S32 face) + { + LLMaterial* mat = object->getTE(face)->getMaterialParams().get(); + U32 s_axis = VX; + U32 t_axis = VY; + F32 repeats_s = 1.0f; + F32 repeats_t = 1.0f; + if (mat) + { + mat->getNormalRepeat(repeats_s, repeats_t); + repeats_s /= object->getScale().mV[s_axis]; + repeats_t /= object->getScale().mV[t_axis]; + } + return llmax(repeats_s, repeats_t); + } + + } max_norm_repeats_func; + identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &max_norm_repeats_func, repeats); +} + +void LLPanelFace::LLSelectedTEMaterial::getCurrentDiffuseAlphaMode(U8& diffuse_alpha_mode, bool& identical, bool diffuse_texture_has_alpha) +{ + struct LLSelectedTEGetDiffuseAlphaMode : public LLSelectedTEGetFunctor + { + LLSelectedTEGetDiffuseAlphaMode() : _isAlpha(false) {} + LLSelectedTEGetDiffuseAlphaMode(bool diffuse_texture_has_alpha) : _isAlpha(diffuse_texture_has_alpha) {} + virtual ~LLSelectedTEGetDiffuseAlphaMode() {} + + U8 get(LLViewerObject* object, S32 face) + { + U8 diffuse_mode = _isAlpha ? LLMaterial::DIFFUSE_ALPHA_MODE_BLEND : LLMaterial::DIFFUSE_ALPHA_MODE_NONE; + + LLTextureEntry* tep = object->getTE(face); + if (tep) + { + LLMaterial* mat = tep->getMaterialParams().get(); + if (mat) + { + diffuse_mode = mat->getDiffuseAlphaMode(); + } + } + + return diffuse_mode; + } + bool _isAlpha; // whether or not the diffuse texture selected contains alpha information + } get_diff_mode(diffuse_texture_has_alpha); + identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &get_diff_mode, diffuse_alpha_mode); +} + +void LLPanelFace::LLSelectedTE::getObjectScaleS(F32& scale_s, bool& identical) +{ + struct LLSelectedTEGetObjectScaleS : public LLSelectedTEGetFunctor + { + F32 get(LLViewerObject* object, S32 face) + { + U32 s_axis = VX; + U32 t_axis = VY; + LLPrimitive::getTESTAxes(face, &s_axis, &t_axis); + return object->getScale().mV[s_axis]; + } + + } scale_s_func; + identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &scale_s_func, scale_s ); +} + +void LLPanelFace::LLSelectedTE::getObjectScaleT(F32& scale_t, bool& identical) +{ + struct LLSelectedTEGetObjectScaleS : public LLSelectedTEGetFunctor + { + F32 get(LLViewerObject* object, S32 face) + { + U32 s_axis = VX; + U32 t_axis = VY; + LLPrimitive::getTESTAxes(face, &s_axis, &t_axis); + return object->getScale().mV[t_axis]; + } + + } scale_t_func; + identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &scale_t_func, scale_t ); +} + +void LLPanelFace::LLSelectedTE::getMaxDiffuseRepeats(F32& repeats, bool& identical) +{ + struct LLSelectedTEGetMaxDiffuseRepeats : public LLSelectedTEGetFunctor + { + F32 get(LLViewerObject* object, S32 face) + { + U32 s_axis = VX; + U32 t_axis = VY; + LLPrimitive::getTESTAxes(face, &s_axis, &t_axis); + F32 repeats_s = object->getTE(face)->mScaleS / object->getScale().mV[s_axis]; + F32 repeats_t = object->getTE(face)->mScaleT / object->getScale().mV[t_axis]; + return llmax(repeats_s, repeats_t); + } + + } max_diff_repeats_func; + identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &max_diff_repeats_func, repeats ); +} + diff --git a/indra/newview/llpanelface.h b/indra/newview/llpanelface.h index 15663470a8..7736a68f3c 100644 --- a/indra/newview/llpanelface.h +++ b/indra/newview/llpanelface.h @@ -1,624 +1,624 @@ -/** - * @file llpanelface.h - * @brief Panel in the tools floater for editing face textures, colors, etc. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELFACE_H -#define LL_LLPANELFACE_H - -#include "v4color.h" -#include "llpanel.h" -#include "llgltfmaterial.h" -#include "llmaterial.h" -#include "llmaterialmgr.h" -#include "lltextureentry.h" -#include "llselectmgr.h" - -#include - -class LLButton; -class LLCheckBoxCtrl; -class LLColorSwatchCtrl; -class LLComboBox; -class LLInventoryItem; -class LLLineEditor; -class LLSpinCtrl; -class LLTextBox; -class LLTextureCtrl; -class LLUICtrl; -class LLViewerObject; -class LLFloater; -class LLMaterialID; -class LLMediaCtrl; -class LLMenuButton; - -class PBRPickerAgentListener; -class PBRPickerObjectListener; - -// Represents an edit for use in replicating the op across one or more materials in the selection set. -// -// The apply function optionally performs the edit which it implements -// as a functor taking Data that calls member func MaterialFunc taking SetValueType -// on an instance of the LLMaterial class. -// -// boost who? -// -template< - typename DataType, - typename SetValueType, - void (LLMaterial::*MaterialEditFunc)(SetValueType data) > -class LLMaterialEditFunctor -{ -public: - LLMaterialEditFunctor(const DataType& data) : _data(data) {} - virtual ~LLMaterialEditFunctor() {} - virtual void apply(LLMaterialPtr& material) { (material->*(MaterialEditFunc))(_data); } - DataType _data; -}; - -template< - typename DataType, - DataType (LLMaterial::*MaterialGetFunc)() > -class LLMaterialGetFunctor -{ -public: - LLMaterialGetFunctor() {} - virtual DataType get(LLMaterialPtr& material) { return (material->*(MaterialGetFunc)); } -}; - -template< - typename DataType, - DataType (LLTextureEntry::*TEGetFunc)() > -class LLTEGetFunctor -{ -public: - LLTEGetFunctor() {} - virtual DataType get(LLTextureEntry* entry) { return (entry*(TEGetFunc)); } -}; - -class LLPanelFace : public LLPanel -{ -public: - virtual bool postBuild(); - LLPanelFace(); - virtual ~LLPanelFace(); - - void refresh(); - void refreshMedia(); - void unloadMedia(); - - static void onMaterialOverrideReceived(const LLUUID& object_id, S32 side); - - /*virtual*/ void onVisibilityChange(bool new_visibility); - /*virtual*/ void draw(); - - LLMaterialPtr createDefaultMaterial(LLMaterialPtr current_material) - { - LLMaterialPtr new_material(!current_material.isNull() ? new LLMaterial(current_material->asLLSD()) : new LLMaterial()); - llassert_always(new_material); - - // Preserve old diffuse alpha mode or assert correct default blend mode as appropriate for the alpha channel content of the diffuse texture - // - new_material->setDiffuseAlphaMode(current_material.isNull() ? (isAlpha() ? LLMaterial::DIFFUSE_ALPHA_MODE_BLEND : LLMaterial::DIFFUSE_ALPHA_MODE_NONE) : current_material->getDiffuseAlphaMode()); - return new_material; - } - - LLRender::eTexIndex getTextureChannelToEdit(); - LLRender::eTexIndex getTextureDropChannel(); - LLGLTFMaterial::TextureInfo getPBRDropChannel(); - -protected: - void navigateToTitleMedia(const std::string url); - bool selectedMediaEditable(); - void clearMediaSettings(); - void updateMediaSettings(); - void updateMediaTitle(); - - void getState(); - - void sendTexture(); // applies and sends texture - void sendTextureInfo(); // applies and sends texture scale, offset, etc. - void sendColor(); // applies and sends color - void sendAlpha(); // applies and sends transparency - void sendBump(U32 bumpiness); // applies and sends bump map - void sendTexGen(); // applies and sends bump map - void sendShiny(U32 shininess); // applies and sends shininess - void sendFullbright(); // applies and sends full bright - void sendGlow(); - void alignTestureLayer(); - - void updateCopyTexButton(); - - void onCommitPbr(const LLSD& data); - void onCancelPbr(const LLSD& data); - void onSelectPbr(const LLSD& data); - static bool onDragPbr(LLUICtrl* ctrl, LLInventoryItem* item); - - // this function is to return true if the drag should succeed. - static bool onDragTexture(LLUICtrl* ctrl, LLInventoryItem* item); - - void onCommitTexture(const LLSD& data); - void onCancelTexture(const LLSD& data); - void onSelectTexture(const LLSD& data); - void onCommitSpecularTexture(const LLSD& data); - void onCancelSpecularTexture(const LLSD& data); - void onSelectSpecularTexture(const LLSD& data); - void onCommitNormalTexture(const LLSD& data); - void onCancelNormalTexture(const LLSD& data); - void onSelectNormalTexture(const LLSD& data); - void onCommitColor(const LLSD& data); - void onCommitShinyColor(const LLSD& data); - void onCommitAlpha(const LLSD& data); - void onCancelColor(const LLSD& data); - void onCancelShinyColor(const LLSD& data); - void onSelectColor(const LLSD& data); - void onSelectShinyColor(const LLSD& data); - - void onCloseTexturePicker(const LLSD& data); - - static bool deleteMediaConfirm(const LLSD& notification, const LLSD& response); - static bool multipleFacesSelectedConfirm(const LLSD& notification, const LLSD& response); - - // Make UI reflect state of currently selected material (refresh) - // and UI mode (e.g. editing normal map v diffuse map) - // - // @param force_set_values forces spinners to set value even if they are focused - void updateUI(bool force_set_values = false); - - // Convenience func to determine if all faces in selection have - // identical planar texgen settings during edits - // - bool isIdenticalPlanarTexgen(); - - // Callback funcs for individual controls - // - static void onCommitTextureInfo(LLUICtrl* ctrl, void* userdata); - static void onCommitTextureScaleX(LLUICtrl* ctrl, void* userdata); - static void onCommitTextureScaleY(LLUICtrl* ctrl, void* userdata); - static void onCommitTextureRot(LLUICtrl* ctrl, void* userdata); - static void onCommitTextureOffsetX(LLUICtrl* ctrl, void* userdata); - static void onCommitTextureOffsetY(LLUICtrl* ctrl, void* userdata); - - static void onCommitMaterialBumpyScaleX( LLUICtrl* ctrl, void* userdata); - static void onCommitMaterialBumpyScaleY( LLUICtrl* ctrl, void* userdata); - static void onCommitMaterialBumpyRot( LLUICtrl* ctrl, void* userdata); - static void onCommitMaterialBumpyOffsetX( LLUICtrl* ctrl, void* userdata); - static void onCommitMaterialBumpyOffsetY( LLUICtrl* ctrl, void* userdata); - - static void syncRepeatX(LLPanelFace* self, F32 scaleU); - static void syncRepeatY(LLPanelFace* self, F32 scaleV); - static void syncOffsetX(LLPanelFace* self, F32 offsetU); - static void syncOffsetY(LLPanelFace* self, F32 offsetV); - static void syncMaterialRot(LLPanelFace* self, F32 rot, int te = -1); - - static void onCommitMaterialShinyScaleX( LLUICtrl* ctrl, void* userdata); - static void onCommitMaterialShinyScaleY( LLUICtrl* ctrl, void* userdata); - static void onCommitMaterialShinyRot( LLUICtrl* ctrl, void* userdata); - static void onCommitMaterialShinyOffsetX( LLUICtrl* ctrl, void* userdata); - static void onCommitMaterialShinyOffsetY( LLUICtrl* ctrl, void* userdata); - - static void onCommitMaterialGloss( LLUICtrl* ctrl, void* userdata); - static void onCommitMaterialEnv( LLUICtrl* ctrl, void* userdata); - static void onCommitMaterialMaskCutoff( LLUICtrl* ctrl, void* userdata); - static void onCommitMaterialID( LLUICtrl* ctrl, void* userdata); - - static void onCommitMaterialsMedia( LLUICtrl* ctrl, void* userdata); - static void onCommitMaterialType( LLUICtrl* ctrl, void* userdata); - static void onCommitPbrType(LLUICtrl* ctrl, void* userdata); - static void onClickBtnEditMedia(LLUICtrl* ctrl, void* userdata); - static void onClickBtnDeleteMedia(LLUICtrl* ctrl, void* userdata); - static void onClickBtnAddMedia(LLUICtrl* ctrl, void* userdata); - static void onCommitBump( LLUICtrl* ctrl, void* userdata); - static void onCommitTexGen( LLUICtrl* ctrl, void* userdata); - static void onCommitShiny( LLUICtrl* ctrl, void* userdata); - static void onCommitAlphaMode( LLUICtrl* ctrl, void* userdata); - static void onCommitFullbright( LLUICtrl* ctrl, void* userdata); - static void onCommitGlow( LLUICtrl* ctrl, void *userdata); - static void onCommitPlanarAlign( LLUICtrl* ctrl, void* userdata); - static void onCommitRepeatsPerMeter( LLUICtrl* ctrl, void* userinfo); - - void onCommitGLTFTextureScaleU(LLUICtrl* ctrl); - void onCommitGLTFTextureScaleV(LLUICtrl* ctrl); - void onCommitGLTFRotation(LLUICtrl* ctrl); - void onCommitGLTFTextureOffsetU(LLUICtrl* ctrl); - void onCommitGLTFTextureOffsetV(LLUICtrl* ctrl); - - static void onClickAutoFix(void*); - static void onAlignTexture(void*); - static void onClickBtnLoadInvPBR(void* userdata); - static void onClickBtnEditPBR(void* userdata); - static void onClickBtnSavePBR(void* userdata); - -public: // needs to be accessible to selection manager - void onCopyColor(); // records all selected faces - void onPasteColor(); // to specific face - void onPasteColor(LLViewerObject* objectp, S32 te); // to specific face - void onCopyTexture(); - void onPasteTexture(); - void onPasteTexture(LLViewerObject* objectp, S32 te); - -protected: - void menuDoToSelected(const LLSD& userdata); - bool menuEnableItem(const LLSD& userdata); - - static F32 valueGlow(LLViewerObject* object, S32 face); - - - -private: - bool isAlpha() { return mIsAlpha; } - - // Convenience funcs to keep the visual flack to a minimum - // - LLUUID getCurrentNormalMap(); - LLUUID getCurrentSpecularMap(); - U32 getCurrentShininess(); - U32 getCurrentBumpiness(); - U8 getCurrentDiffuseAlphaMode(); - U8 getCurrentAlphaMaskCutoff(); - U8 getCurrentEnvIntensity(); - U8 getCurrentGlossiness(); - F32 getCurrentBumpyRot(); - F32 getCurrentBumpyScaleU(); - F32 getCurrentBumpyScaleV(); - F32 getCurrentBumpyOffsetU(); - F32 getCurrentBumpyOffsetV(); - F32 getCurrentShinyRot(); - F32 getCurrentShinyScaleU(); - F32 getCurrentShinyScaleV(); - F32 getCurrentShinyOffsetU(); - F32 getCurrentShinyOffsetV(); - - LLComboBox *mComboMatMedia; - LLMediaCtrl *mTitleMedia; - LLTextBox *mTitleMediaText; - - // Update visibility of controls to match current UI mode - // (e.g. materials vs media editing) - // - // Do NOT call updateUI from within this function. - // - void updateVisibility(LLViewerObject* objectp = nullptr); - - // Hey look everyone, a type-safe alternative to copy and paste! :) - // - - // Update material parameters by applying 'edit_func' to selected TEs - // - template< - typename DataType, - typename SetValueType, - void (LLMaterial::*MaterialEditFunc)(SetValueType data) > - static void edit(LLPanelFace* p, DataType data, int te = -1, const LLUUID &only_for_object_id = LLUUID()) - { - LLMaterialEditFunctor< DataType, SetValueType, MaterialEditFunc > edit(data); - struct LLSelectedTEEditMaterial : public LLSelectedTEMaterialFunctor - { - LLSelectedTEEditMaterial(LLPanelFace* panel, LLMaterialEditFunctor< DataType, SetValueType, MaterialEditFunc >* editp, const LLUUID &only_for_object_id) : _panel(panel), _edit(editp), _only_for_object_id(only_for_object_id) {} - virtual ~LLSelectedTEEditMaterial() {}; - virtual LLMaterialPtr apply(LLViewerObject* object, S32 face, LLTextureEntry* tep, LLMaterialPtr& current_material) - { - if (_edit && (_only_for_object_id.isNull() || _only_for_object_id == object->getID())) - { - LLMaterialPtr new_material = _panel->createDefaultMaterial(current_material); - llassert_always(new_material); - - // Determine correct alpha mode for current diffuse texture - // (i.e. does it have an alpha channel that makes alpha mode useful) - // - // _panel->isAlpha() "lies" when one face has alpha and the rest do not (NORSPEC-329) - // need to get per-face answer to this question for sane alpha mode retention on updates. - // - bool is_alpha_face = object->isImageAlphaBlended(face); - - // need to keep this original answer for valid comparisons in logic below - // - U8 original_default_alpha_mode = is_alpha_face ? LLMaterial::DIFFUSE_ALPHA_MODE_BLEND : LLMaterial::DIFFUSE_ALPHA_MODE_NONE; - - U8 default_alpha_mode = original_default_alpha_mode; - - if (!current_material.isNull()) - { - default_alpha_mode = current_material->getDiffuseAlphaMode(); - } - - // Insure we don't inherit the default of blend by accident... - // this will be stomped by a legit request to change the alpha mode by the apply() below - // - new_material->setDiffuseAlphaMode(default_alpha_mode); - - // Do "It"! - // - _edit->apply(new_material); - - U32 new_alpha_mode = new_material->getDiffuseAlphaMode(); - LLUUID new_normal_map_id = new_material->getNormalID(); - LLUUID new_spec_map_id = new_material->getSpecularID(); - - if ((new_alpha_mode == LLMaterial::DIFFUSE_ALPHA_MODE_BLEND) && !is_alpha_face) - { - new_alpha_mode = LLMaterial::DIFFUSE_ALPHA_MODE_NONE; - new_material->setDiffuseAlphaMode(LLMaterial::DIFFUSE_ALPHA_MODE_NONE); - } - - bool is_default_blend_mode = (new_alpha_mode == original_default_alpha_mode); - bool is_need_material = !is_default_blend_mode || !new_normal_map_id.isNull() || !new_spec_map_id.isNull(); - - if (!is_need_material) - { - LL_DEBUGS("Materials") << "Removing material from object " << object->getID() << " face " << face << LL_ENDL; - LLMaterialMgr::getInstance()->remove(object->getID(),face); - new_material = NULL; - } - else - { - LL_DEBUGS("Materials") << "Putting material on object " << object->getID() << " face " << face << ", material: " << new_material->asLLSD() << LL_ENDL; - LLMaterialMgr::getInstance()->put(object->getID(),face,*new_material); - } - - object->setTEMaterialParams(face, new_material); - return new_material; - } - return NULL; - } - LLMaterialEditFunctor< DataType, SetValueType, MaterialEditFunc >* _edit; - LLPanelFace *_panel; - const LLUUID & _only_for_object_id; - } editor(p, &edit, only_for_object_id); - LLSelectMgr::getInstance()->selectionSetMaterialParams(&editor, te); - } - - template< - typename DataType, - typename ReturnType, - ReturnType (LLMaterial::* const MaterialGetFunc)() const > - static void getTEMaterialValue(DataType& data_to_return, bool& identical,DataType default_value, bool has_tolerance = false, DataType tolerance = DataType()) - { - DataType data_value; - struct GetTEMaterialVal : public LLSelectedTEGetFunctor - { - GetTEMaterialVal(DataType default_value) : _default(default_value) {} - virtual ~GetTEMaterialVal() {} - - DataType get(LLViewerObject* object, S32 face) - { - DataType ret = _default; - LLMaterialPtr material_ptr; - LLTextureEntry* tep = object ? object->getTE(face) : NULL; - if (tep) - { - material_ptr = tep->getMaterialParams(); - if (!material_ptr.isNull()) - { - ret = (material_ptr->*(MaterialGetFunc))(); - } - } - return ret; - } - DataType _default; - } GetFunc(default_value); - identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &GetFunc, data_value, has_tolerance, tolerance); - data_to_return = data_value; - } - - template< - typename DataType, - typename ReturnType, // some kids just have to different... - ReturnType (LLTextureEntry::* const TEGetFunc)() const > - static void getTEValue(DataType& data_to_return, bool& identical, DataType default_value, bool has_tolerance = false, DataType tolerance = DataType()) - { - DataType data_value; - struct GetTEVal : public LLSelectedTEGetFunctor - { - GetTEVal(DataType default_value) : _default(default_value) {} - virtual ~GetTEVal() {} - - DataType get(LLViewerObject* object, S32 face) { - LLTextureEntry* tep = object ? object->getTE(face) : NULL; - return tep ? ((tep->*(TEGetFunc))()) : _default; - } - DataType _default; - } GetTEValFunc(default_value); - identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &GetTEValFunc, data_value, has_tolerance, tolerance ); - data_to_return = data_value; - } - - // Update vis and enabling of specific subsets of controls based on material params - // (e.g. hide the spec controls if no spec texture is applied) - // - void updateShinyControls(bool is_setting_texture = false, bool mess_with_combobox = false); - void updateBumpyControls(bool is_setting_texture = false, bool mess_with_combobox = false); - void updateAlphaControls(); - - /* - * Checks whether the selected texture from the LLFloaterTexturePicker can be applied to the currently selected object. - * If agent selects texture which is not allowed to be applied for the currently selected object, - * all controls of the floater texture picker which allow to apply the texture will be disabled. - */ - void onTextureSelectionChanged(LLInventoryItem* itemp); - void onPbrSelectionChanged(LLInventoryItem* itemp); - - void updateUIGLTF(LLViewerObject* objectp, bool& has_pbr_material, bool& has_faces_without_pbr, bool force_set_values); - void updateVisibilityGLTF(LLViewerObject* objectp = nullptr); - - void updateSelectedGLTFMaterials(std::function func); - void updateGLTFTextureTransform(float value, U32 pbr_type, std::function edit); - - void setMaterialOverridesFromSelection(); - - LLMenuButton* mMenuClipboardColor; - LLMenuButton* mMenuClipboardTexture; - - bool mIsAlpha; - - LLSD mClipboardParams; - - LLSD mMediaSettings; - bool mNeedMediaTitle; - - class Selection - { - public: - void connect(); - - // Returns true if the selected objects or sides have changed since - // this was last called, and no object update is pending - bool update(); - - // Prevents update() returning true until the provided object is - // updated. Necessary to prevent controls updating when the mouse is - // held down. - void setDirty() { mChanged = true; }; - - // Callbacks - void onSelectionChanged() { mNeedsSelectionCheck = true; } - void onSelectedObjectUpdated(const LLUUID &object_id, S32 side); - - protected: - bool compareSelection(); - - bool mChanged = false; - - boost::signals2::scoped_connection mSelectConnection; - bool mNeedsSelectionCheck = true; - S32 mSelectedObjectCount = 0; - S32 mSelectedTECount = 0; - LLUUID mSelectedObjectID; - S32 mLastSelectedSide = -1; - }; - - static Selection sMaterialOverrideSelection; - - std::unique_ptr mAgentInventoryListener; - std::unique_ptr mVOInventoryListener; - -public: - #if defined(DEF_GET_MAT_STATE) - #undef DEF_GET_MAT_STATE - #endif - - #if defined(DEF_GET_TE_STATE) - #undef DEF_GET_TE_STATE - #endif - - #if defined(DEF_EDIT_MAT_STATE) - DEF_EDIT_MAT_STATE - #endif - - // Accessors for selected TE material state - // - #define DEF_GET_MAT_STATE(DataType,ReturnType,MaterialMemberFunc,DefaultValue,HasTolerance,Tolerance) \ - static void MaterialMemberFunc(DataType& data, bool& identical, bool has_tolerance = HasTolerance, DataType tolerance = Tolerance) \ - { \ - getTEMaterialValue< DataType, ReturnType, &LLMaterial::MaterialMemberFunc >(data, identical, DefaultValue, has_tolerance, tolerance); \ - } - - // Mutators for selected TE material - // - #define DEF_EDIT_MAT_STATE(DataType,ReturnType,MaterialMemberFunc) \ - static void MaterialMemberFunc(LLPanelFace* p, DataType data, int te = -1, const LLUUID &only_for_object_id = LLUUID()) \ - { \ - edit< DataType, ReturnType, &LLMaterial::MaterialMemberFunc >(p, data, te, only_for_object_id); \ - } - - // Accessors for selected TE state proper (legacy settings etc) - // - #define DEF_GET_TE_STATE(DataType,ReturnType,TexEntryMemberFunc,DefaultValue,HasTolerance,Tolerance) \ - static void TexEntryMemberFunc(DataType& data, bool& identical, bool has_tolerance = HasTolerance, DataType tolerance = Tolerance) \ - { \ - getTEValue< DataType, ReturnType, &LLTextureEntry::TexEntryMemberFunc >(data, identical, DefaultValue, has_tolerance, tolerance); \ - } - - class LLSelectedTEMaterial - { - public: - static void getCurrent(LLMaterialPtr& material_ptr, bool& identical_material); - static void getMaxSpecularRepeats(F32& repeats, bool& identical); - static void getMaxNormalRepeats(F32& repeats, bool& identical); - static void getCurrentDiffuseAlphaMode(U8& diffuse_alpha_mode, bool& identical, bool diffuse_texture_has_alpha); - - DEF_GET_MAT_STATE(LLUUID,const LLUUID&,getNormalID,LLUUID::null, false, LLUUID::null) - DEF_GET_MAT_STATE(LLUUID,const LLUUID&,getSpecularID,LLUUID::null, false, LLUUID::null) - DEF_GET_MAT_STATE(F32,F32,getSpecularRepeatX,1.0f, true, 0.001f) - DEF_GET_MAT_STATE(F32,F32,getSpecularRepeatY,1.0f, true, 0.001f) - DEF_GET_MAT_STATE(F32,F32,getSpecularOffsetX,0.0f, true, 0.001f) - DEF_GET_MAT_STATE(F32,F32,getSpecularOffsetY,0.0f, true, 0.001f) - DEF_GET_MAT_STATE(F32,F32,getSpecularRotation,0.0f, true, 0.001f) - - DEF_GET_MAT_STATE(F32,F32,getNormalRepeatX,1.0f, true, 0.001f) - DEF_GET_MAT_STATE(F32,F32,getNormalRepeatY,1.0f, true, 0.001f) - DEF_GET_MAT_STATE(F32,F32,getNormalOffsetX,0.0f, true, 0.001f) - DEF_GET_MAT_STATE(F32,F32,getNormalOffsetY,0.0f, true, 0.001f) - DEF_GET_MAT_STATE(F32,F32,getNormalRotation,0.0f, true, 0.001f) - - DEF_EDIT_MAT_STATE(U8,U8,setDiffuseAlphaMode); - DEF_EDIT_MAT_STATE(U8,U8,setAlphaMaskCutoff); - - DEF_EDIT_MAT_STATE(F32,F32,setNormalOffsetX); - DEF_EDIT_MAT_STATE(F32,F32,setNormalOffsetY); - DEF_EDIT_MAT_STATE(F32,F32,setNormalRepeatX); - DEF_EDIT_MAT_STATE(F32,F32,setNormalRepeatY); - DEF_EDIT_MAT_STATE(F32,F32,setNormalRotation); - - DEF_EDIT_MAT_STATE(F32,F32,setSpecularOffsetX); - DEF_EDIT_MAT_STATE(F32,F32,setSpecularOffsetY); - DEF_EDIT_MAT_STATE(F32,F32,setSpecularRepeatX); - DEF_EDIT_MAT_STATE(F32,F32,setSpecularRepeatY); - DEF_EDIT_MAT_STATE(F32,F32,setSpecularRotation); - - DEF_EDIT_MAT_STATE(U8,U8,setEnvironmentIntensity); - DEF_EDIT_MAT_STATE(U8,U8,setSpecularLightExponent); - - DEF_EDIT_MAT_STATE(LLUUID,const LLUUID&,setNormalID); - DEF_EDIT_MAT_STATE(LLUUID,const LLUUID&,setSpecularID); - DEF_EDIT_MAT_STATE(LLColor4U, const LLColor4U&,setSpecularLightColor); - }; - - class LLSelectedTE - { - public: - static void getFace(class LLFace*& face_to_return, bool& identical_face); - static void getImageFormat(LLGLenum& image_format_to_return, bool& identical_face); - static void getTexId(LLUUID& id, bool& identical); - static void getPbrMaterialId(LLUUID& id, bool& identical, bool& has_pbr, bool& has_faces_without_pbr); - static void getObjectScaleS(F32& scale_s, bool& identical); - static void getObjectScaleT(F32& scale_t, bool& identical); - static void getMaxDiffuseRepeats(F32& repeats, bool& identical); - - DEF_GET_TE_STATE(U8,U8,getBumpmap,0, false, 0) - DEF_GET_TE_STATE(U8,U8,getShiny,0, false, 0) - DEF_GET_TE_STATE(U8,U8,getFullbright,0, false, 0) - DEF_GET_TE_STATE(F32,F32,getRotation,0.0f, true, 0.001f) - DEF_GET_TE_STATE(F32,F32,getOffsetS,0.0f, true, 0.001f) - DEF_GET_TE_STATE(F32,F32,getOffsetT,0.0f, true, 0.001f) - DEF_GET_TE_STATE(F32,F32,getScaleS,1.0f, true, 0.001f) - DEF_GET_TE_STATE(F32,F32,getScaleT,1.0f, true, 0.001f) - DEF_GET_TE_STATE(F32,F32,getGlow,0.0f, true, 0.001f) - DEF_GET_TE_STATE(LLTextureEntry::e_texgen,LLTextureEntry::e_texgen,getTexGen,LLTextureEntry::TEX_GEN_DEFAULT, false, LLTextureEntry::TEX_GEN_DEFAULT) - DEF_GET_TE_STATE(LLColor4,const LLColor4&,getColor,LLColor4::white, false, LLColor4::black); - }; -}; - -#endif - +/** + * @file llpanelface.h + * @brief Panel in the tools floater for editing face textures, colors, etc. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELFACE_H +#define LL_LLPANELFACE_H + +#include "v4color.h" +#include "llpanel.h" +#include "llgltfmaterial.h" +#include "llmaterial.h" +#include "llmaterialmgr.h" +#include "lltextureentry.h" +#include "llselectmgr.h" + +#include + +class LLButton; +class LLCheckBoxCtrl; +class LLColorSwatchCtrl; +class LLComboBox; +class LLInventoryItem; +class LLLineEditor; +class LLSpinCtrl; +class LLTextBox; +class LLTextureCtrl; +class LLUICtrl; +class LLViewerObject; +class LLFloater; +class LLMaterialID; +class LLMediaCtrl; +class LLMenuButton; + +class PBRPickerAgentListener; +class PBRPickerObjectListener; + +// Represents an edit for use in replicating the op across one or more materials in the selection set. +// +// The apply function optionally performs the edit which it implements +// as a functor taking Data that calls member func MaterialFunc taking SetValueType +// on an instance of the LLMaterial class. +// +// boost who? +// +template< + typename DataType, + typename SetValueType, + void (LLMaterial::*MaterialEditFunc)(SetValueType data) > +class LLMaterialEditFunctor +{ +public: + LLMaterialEditFunctor(const DataType& data) : _data(data) {} + virtual ~LLMaterialEditFunctor() {} + virtual void apply(LLMaterialPtr& material) { (material->*(MaterialEditFunc))(_data); } + DataType _data; +}; + +template< + typename DataType, + DataType (LLMaterial::*MaterialGetFunc)() > +class LLMaterialGetFunctor +{ +public: + LLMaterialGetFunctor() {} + virtual DataType get(LLMaterialPtr& material) { return (material->*(MaterialGetFunc)); } +}; + +template< + typename DataType, + DataType (LLTextureEntry::*TEGetFunc)() > +class LLTEGetFunctor +{ +public: + LLTEGetFunctor() {} + virtual DataType get(LLTextureEntry* entry) { return (entry*(TEGetFunc)); } +}; + +class LLPanelFace : public LLPanel +{ +public: + virtual bool postBuild(); + LLPanelFace(); + virtual ~LLPanelFace(); + + void refresh(); + void refreshMedia(); + void unloadMedia(); + + static void onMaterialOverrideReceived(const LLUUID& object_id, S32 side); + + /*virtual*/ void onVisibilityChange(bool new_visibility); + /*virtual*/ void draw(); + + LLMaterialPtr createDefaultMaterial(LLMaterialPtr current_material) + { + LLMaterialPtr new_material(!current_material.isNull() ? new LLMaterial(current_material->asLLSD()) : new LLMaterial()); + llassert_always(new_material); + + // Preserve old diffuse alpha mode or assert correct default blend mode as appropriate for the alpha channel content of the diffuse texture + // + new_material->setDiffuseAlphaMode(current_material.isNull() ? (isAlpha() ? LLMaterial::DIFFUSE_ALPHA_MODE_BLEND : LLMaterial::DIFFUSE_ALPHA_MODE_NONE) : current_material->getDiffuseAlphaMode()); + return new_material; + } + + LLRender::eTexIndex getTextureChannelToEdit(); + LLRender::eTexIndex getTextureDropChannel(); + LLGLTFMaterial::TextureInfo getPBRDropChannel(); + +protected: + void navigateToTitleMedia(const std::string url); + bool selectedMediaEditable(); + void clearMediaSettings(); + void updateMediaSettings(); + void updateMediaTitle(); + + void getState(); + + void sendTexture(); // applies and sends texture + void sendTextureInfo(); // applies and sends texture scale, offset, etc. + void sendColor(); // applies and sends color + void sendAlpha(); // applies and sends transparency + void sendBump(U32 bumpiness); // applies and sends bump map + void sendTexGen(); // applies and sends bump map + void sendShiny(U32 shininess); // applies and sends shininess + void sendFullbright(); // applies and sends full bright + void sendGlow(); + void alignTestureLayer(); + + void updateCopyTexButton(); + + void onCommitPbr(const LLSD& data); + void onCancelPbr(const LLSD& data); + void onSelectPbr(const LLSD& data); + static bool onDragPbr(LLUICtrl* ctrl, LLInventoryItem* item); + + // this function is to return true if the drag should succeed. + static bool onDragTexture(LLUICtrl* ctrl, LLInventoryItem* item); + + void onCommitTexture(const LLSD& data); + void onCancelTexture(const LLSD& data); + void onSelectTexture(const LLSD& data); + void onCommitSpecularTexture(const LLSD& data); + void onCancelSpecularTexture(const LLSD& data); + void onSelectSpecularTexture(const LLSD& data); + void onCommitNormalTexture(const LLSD& data); + void onCancelNormalTexture(const LLSD& data); + void onSelectNormalTexture(const LLSD& data); + void onCommitColor(const LLSD& data); + void onCommitShinyColor(const LLSD& data); + void onCommitAlpha(const LLSD& data); + void onCancelColor(const LLSD& data); + void onCancelShinyColor(const LLSD& data); + void onSelectColor(const LLSD& data); + void onSelectShinyColor(const LLSD& data); + + void onCloseTexturePicker(const LLSD& data); + + static bool deleteMediaConfirm(const LLSD& notification, const LLSD& response); + static bool multipleFacesSelectedConfirm(const LLSD& notification, const LLSD& response); + + // Make UI reflect state of currently selected material (refresh) + // and UI mode (e.g. editing normal map v diffuse map) + // + // @param force_set_values forces spinners to set value even if they are focused + void updateUI(bool force_set_values = false); + + // Convenience func to determine if all faces in selection have + // identical planar texgen settings during edits + // + bool isIdenticalPlanarTexgen(); + + // Callback funcs for individual controls + // + static void onCommitTextureInfo(LLUICtrl* ctrl, void* userdata); + static void onCommitTextureScaleX(LLUICtrl* ctrl, void* userdata); + static void onCommitTextureScaleY(LLUICtrl* ctrl, void* userdata); + static void onCommitTextureRot(LLUICtrl* ctrl, void* userdata); + static void onCommitTextureOffsetX(LLUICtrl* ctrl, void* userdata); + static void onCommitTextureOffsetY(LLUICtrl* ctrl, void* userdata); + + static void onCommitMaterialBumpyScaleX( LLUICtrl* ctrl, void* userdata); + static void onCommitMaterialBumpyScaleY( LLUICtrl* ctrl, void* userdata); + static void onCommitMaterialBumpyRot( LLUICtrl* ctrl, void* userdata); + static void onCommitMaterialBumpyOffsetX( LLUICtrl* ctrl, void* userdata); + static void onCommitMaterialBumpyOffsetY( LLUICtrl* ctrl, void* userdata); + + static void syncRepeatX(LLPanelFace* self, F32 scaleU); + static void syncRepeatY(LLPanelFace* self, F32 scaleV); + static void syncOffsetX(LLPanelFace* self, F32 offsetU); + static void syncOffsetY(LLPanelFace* self, F32 offsetV); + static void syncMaterialRot(LLPanelFace* self, F32 rot, int te = -1); + + static void onCommitMaterialShinyScaleX( LLUICtrl* ctrl, void* userdata); + static void onCommitMaterialShinyScaleY( LLUICtrl* ctrl, void* userdata); + static void onCommitMaterialShinyRot( LLUICtrl* ctrl, void* userdata); + static void onCommitMaterialShinyOffsetX( LLUICtrl* ctrl, void* userdata); + static void onCommitMaterialShinyOffsetY( LLUICtrl* ctrl, void* userdata); + + static void onCommitMaterialGloss( LLUICtrl* ctrl, void* userdata); + static void onCommitMaterialEnv( LLUICtrl* ctrl, void* userdata); + static void onCommitMaterialMaskCutoff( LLUICtrl* ctrl, void* userdata); + static void onCommitMaterialID( LLUICtrl* ctrl, void* userdata); + + static void onCommitMaterialsMedia( LLUICtrl* ctrl, void* userdata); + static void onCommitMaterialType( LLUICtrl* ctrl, void* userdata); + static void onCommitPbrType(LLUICtrl* ctrl, void* userdata); + static void onClickBtnEditMedia(LLUICtrl* ctrl, void* userdata); + static void onClickBtnDeleteMedia(LLUICtrl* ctrl, void* userdata); + static void onClickBtnAddMedia(LLUICtrl* ctrl, void* userdata); + static void onCommitBump( LLUICtrl* ctrl, void* userdata); + static void onCommitTexGen( LLUICtrl* ctrl, void* userdata); + static void onCommitShiny( LLUICtrl* ctrl, void* userdata); + static void onCommitAlphaMode( LLUICtrl* ctrl, void* userdata); + static void onCommitFullbright( LLUICtrl* ctrl, void* userdata); + static void onCommitGlow( LLUICtrl* ctrl, void *userdata); + static void onCommitPlanarAlign( LLUICtrl* ctrl, void* userdata); + static void onCommitRepeatsPerMeter( LLUICtrl* ctrl, void* userinfo); + + void onCommitGLTFTextureScaleU(LLUICtrl* ctrl); + void onCommitGLTFTextureScaleV(LLUICtrl* ctrl); + void onCommitGLTFRotation(LLUICtrl* ctrl); + void onCommitGLTFTextureOffsetU(LLUICtrl* ctrl); + void onCommitGLTFTextureOffsetV(LLUICtrl* ctrl); + + static void onClickAutoFix(void*); + static void onAlignTexture(void*); + static void onClickBtnLoadInvPBR(void* userdata); + static void onClickBtnEditPBR(void* userdata); + static void onClickBtnSavePBR(void* userdata); + +public: // needs to be accessible to selection manager + void onCopyColor(); // records all selected faces + void onPasteColor(); // to specific face + void onPasteColor(LLViewerObject* objectp, S32 te); // to specific face + void onCopyTexture(); + void onPasteTexture(); + void onPasteTexture(LLViewerObject* objectp, S32 te); + +protected: + void menuDoToSelected(const LLSD& userdata); + bool menuEnableItem(const LLSD& userdata); + + static F32 valueGlow(LLViewerObject* object, S32 face); + + + +private: + bool isAlpha() { return mIsAlpha; } + + // Convenience funcs to keep the visual flack to a minimum + // + LLUUID getCurrentNormalMap(); + LLUUID getCurrentSpecularMap(); + U32 getCurrentShininess(); + U32 getCurrentBumpiness(); + U8 getCurrentDiffuseAlphaMode(); + U8 getCurrentAlphaMaskCutoff(); + U8 getCurrentEnvIntensity(); + U8 getCurrentGlossiness(); + F32 getCurrentBumpyRot(); + F32 getCurrentBumpyScaleU(); + F32 getCurrentBumpyScaleV(); + F32 getCurrentBumpyOffsetU(); + F32 getCurrentBumpyOffsetV(); + F32 getCurrentShinyRot(); + F32 getCurrentShinyScaleU(); + F32 getCurrentShinyScaleV(); + F32 getCurrentShinyOffsetU(); + F32 getCurrentShinyOffsetV(); + + LLComboBox *mComboMatMedia; + LLMediaCtrl *mTitleMedia; + LLTextBox *mTitleMediaText; + + // Update visibility of controls to match current UI mode + // (e.g. materials vs media editing) + // + // Do NOT call updateUI from within this function. + // + void updateVisibility(LLViewerObject* objectp = nullptr); + + // Hey look everyone, a type-safe alternative to copy and paste! :) + // + + // Update material parameters by applying 'edit_func' to selected TEs + // + template< + typename DataType, + typename SetValueType, + void (LLMaterial::*MaterialEditFunc)(SetValueType data) > + static void edit(LLPanelFace* p, DataType data, int te = -1, const LLUUID &only_for_object_id = LLUUID()) + { + LLMaterialEditFunctor< DataType, SetValueType, MaterialEditFunc > edit(data); + struct LLSelectedTEEditMaterial : public LLSelectedTEMaterialFunctor + { + LLSelectedTEEditMaterial(LLPanelFace* panel, LLMaterialEditFunctor< DataType, SetValueType, MaterialEditFunc >* editp, const LLUUID &only_for_object_id) : _panel(panel), _edit(editp), _only_for_object_id(only_for_object_id) {} + virtual ~LLSelectedTEEditMaterial() {}; + virtual LLMaterialPtr apply(LLViewerObject* object, S32 face, LLTextureEntry* tep, LLMaterialPtr& current_material) + { + if (_edit && (_only_for_object_id.isNull() || _only_for_object_id == object->getID())) + { + LLMaterialPtr new_material = _panel->createDefaultMaterial(current_material); + llassert_always(new_material); + + // Determine correct alpha mode for current diffuse texture + // (i.e. does it have an alpha channel that makes alpha mode useful) + // + // _panel->isAlpha() "lies" when one face has alpha and the rest do not (NORSPEC-329) + // need to get per-face answer to this question for sane alpha mode retention on updates. + // + bool is_alpha_face = object->isImageAlphaBlended(face); + + // need to keep this original answer for valid comparisons in logic below + // + U8 original_default_alpha_mode = is_alpha_face ? LLMaterial::DIFFUSE_ALPHA_MODE_BLEND : LLMaterial::DIFFUSE_ALPHA_MODE_NONE; + + U8 default_alpha_mode = original_default_alpha_mode; + + if (!current_material.isNull()) + { + default_alpha_mode = current_material->getDiffuseAlphaMode(); + } + + // Insure we don't inherit the default of blend by accident... + // this will be stomped by a legit request to change the alpha mode by the apply() below + // + new_material->setDiffuseAlphaMode(default_alpha_mode); + + // Do "It"! + // + _edit->apply(new_material); + + U32 new_alpha_mode = new_material->getDiffuseAlphaMode(); + LLUUID new_normal_map_id = new_material->getNormalID(); + LLUUID new_spec_map_id = new_material->getSpecularID(); + + if ((new_alpha_mode == LLMaterial::DIFFUSE_ALPHA_MODE_BLEND) && !is_alpha_face) + { + new_alpha_mode = LLMaterial::DIFFUSE_ALPHA_MODE_NONE; + new_material->setDiffuseAlphaMode(LLMaterial::DIFFUSE_ALPHA_MODE_NONE); + } + + bool is_default_blend_mode = (new_alpha_mode == original_default_alpha_mode); + bool is_need_material = !is_default_blend_mode || !new_normal_map_id.isNull() || !new_spec_map_id.isNull(); + + if (!is_need_material) + { + LL_DEBUGS("Materials") << "Removing material from object " << object->getID() << " face " << face << LL_ENDL; + LLMaterialMgr::getInstance()->remove(object->getID(),face); + new_material = NULL; + } + else + { + LL_DEBUGS("Materials") << "Putting material on object " << object->getID() << " face " << face << ", material: " << new_material->asLLSD() << LL_ENDL; + LLMaterialMgr::getInstance()->put(object->getID(),face,*new_material); + } + + object->setTEMaterialParams(face, new_material); + return new_material; + } + return NULL; + } + LLMaterialEditFunctor< DataType, SetValueType, MaterialEditFunc >* _edit; + LLPanelFace *_panel; + const LLUUID & _only_for_object_id; + } editor(p, &edit, only_for_object_id); + LLSelectMgr::getInstance()->selectionSetMaterialParams(&editor, te); + } + + template< + typename DataType, + typename ReturnType, + ReturnType (LLMaterial::* const MaterialGetFunc)() const > + static void getTEMaterialValue(DataType& data_to_return, bool& identical,DataType default_value, bool has_tolerance = false, DataType tolerance = DataType()) + { + DataType data_value; + struct GetTEMaterialVal : public LLSelectedTEGetFunctor + { + GetTEMaterialVal(DataType default_value) : _default(default_value) {} + virtual ~GetTEMaterialVal() {} + + DataType get(LLViewerObject* object, S32 face) + { + DataType ret = _default; + LLMaterialPtr material_ptr; + LLTextureEntry* tep = object ? object->getTE(face) : NULL; + if (tep) + { + material_ptr = tep->getMaterialParams(); + if (!material_ptr.isNull()) + { + ret = (material_ptr->*(MaterialGetFunc))(); + } + } + return ret; + } + DataType _default; + } GetFunc(default_value); + identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &GetFunc, data_value, has_tolerance, tolerance); + data_to_return = data_value; + } + + template< + typename DataType, + typename ReturnType, // some kids just have to different... + ReturnType (LLTextureEntry::* const TEGetFunc)() const > + static void getTEValue(DataType& data_to_return, bool& identical, DataType default_value, bool has_tolerance = false, DataType tolerance = DataType()) + { + DataType data_value; + struct GetTEVal : public LLSelectedTEGetFunctor + { + GetTEVal(DataType default_value) : _default(default_value) {} + virtual ~GetTEVal() {} + + DataType get(LLViewerObject* object, S32 face) { + LLTextureEntry* tep = object ? object->getTE(face) : NULL; + return tep ? ((tep->*(TEGetFunc))()) : _default; + } + DataType _default; + } GetTEValFunc(default_value); + identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &GetTEValFunc, data_value, has_tolerance, tolerance ); + data_to_return = data_value; + } + + // Update vis and enabling of specific subsets of controls based on material params + // (e.g. hide the spec controls if no spec texture is applied) + // + void updateShinyControls(bool is_setting_texture = false, bool mess_with_combobox = false); + void updateBumpyControls(bool is_setting_texture = false, bool mess_with_combobox = false); + void updateAlphaControls(); + + /* + * Checks whether the selected texture from the LLFloaterTexturePicker can be applied to the currently selected object. + * If agent selects texture which is not allowed to be applied for the currently selected object, + * all controls of the floater texture picker which allow to apply the texture will be disabled. + */ + void onTextureSelectionChanged(LLInventoryItem* itemp); + void onPbrSelectionChanged(LLInventoryItem* itemp); + + void updateUIGLTF(LLViewerObject* objectp, bool& has_pbr_material, bool& has_faces_without_pbr, bool force_set_values); + void updateVisibilityGLTF(LLViewerObject* objectp = nullptr); + + void updateSelectedGLTFMaterials(std::function func); + void updateGLTFTextureTransform(float value, U32 pbr_type, std::function edit); + + void setMaterialOverridesFromSelection(); + + LLMenuButton* mMenuClipboardColor; + LLMenuButton* mMenuClipboardTexture; + + bool mIsAlpha; + + LLSD mClipboardParams; + + LLSD mMediaSettings; + bool mNeedMediaTitle; + + class Selection + { + public: + void connect(); + + // Returns true if the selected objects or sides have changed since + // this was last called, and no object update is pending + bool update(); + + // Prevents update() returning true until the provided object is + // updated. Necessary to prevent controls updating when the mouse is + // held down. + void setDirty() { mChanged = true; }; + + // Callbacks + void onSelectionChanged() { mNeedsSelectionCheck = true; } + void onSelectedObjectUpdated(const LLUUID &object_id, S32 side); + + protected: + bool compareSelection(); + + bool mChanged = false; + + boost::signals2::scoped_connection mSelectConnection; + bool mNeedsSelectionCheck = true; + S32 mSelectedObjectCount = 0; + S32 mSelectedTECount = 0; + LLUUID mSelectedObjectID; + S32 mLastSelectedSide = -1; + }; + + static Selection sMaterialOverrideSelection; + + std::unique_ptr mAgentInventoryListener; + std::unique_ptr mVOInventoryListener; + +public: + #if defined(DEF_GET_MAT_STATE) + #undef DEF_GET_MAT_STATE + #endif + + #if defined(DEF_GET_TE_STATE) + #undef DEF_GET_TE_STATE + #endif + + #if defined(DEF_EDIT_MAT_STATE) + DEF_EDIT_MAT_STATE + #endif + + // Accessors for selected TE material state + // + #define DEF_GET_MAT_STATE(DataType,ReturnType,MaterialMemberFunc,DefaultValue,HasTolerance,Tolerance) \ + static void MaterialMemberFunc(DataType& data, bool& identical, bool has_tolerance = HasTolerance, DataType tolerance = Tolerance) \ + { \ + getTEMaterialValue< DataType, ReturnType, &LLMaterial::MaterialMemberFunc >(data, identical, DefaultValue, has_tolerance, tolerance); \ + } + + // Mutators for selected TE material + // + #define DEF_EDIT_MAT_STATE(DataType,ReturnType,MaterialMemberFunc) \ + static void MaterialMemberFunc(LLPanelFace* p, DataType data, int te = -1, const LLUUID &only_for_object_id = LLUUID()) \ + { \ + edit< DataType, ReturnType, &LLMaterial::MaterialMemberFunc >(p, data, te, only_for_object_id); \ + } + + // Accessors for selected TE state proper (legacy settings etc) + // + #define DEF_GET_TE_STATE(DataType,ReturnType,TexEntryMemberFunc,DefaultValue,HasTolerance,Tolerance) \ + static void TexEntryMemberFunc(DataType& data, bool& identical, bool has_tolerance = HasTolerance, DataType tolerance = Tolerance) \ + { \ + getTEValue< DataType, ReturnType, &LLTextureEntry::TexEntryMemberFunc >(data, identical, DefaultValue, has_tolerance, tolerance); \ + } + + class LLSelectedTEMaterial + { + public: + static void getCurrent(LLMaterialPtr& material_ptr, bool& identical_material); + static void getMaxSpecularRepeats(F32& repeats, bool& identical); + static void getMaxNormalRepeats(F32& repeats, bool& identical); + static void getCurrentDiffuseAlphaMode(U8& diffuse_alpha_mode, bool& identical, bool diffuse_texture_has_alpha); + + DEF_GET_MAT_STATE(LLUUID,const LLUUID&,getNormalID,LLUUID::null, false, LLUUID::null) + DEF_GET_MAT_STATE(LLUUID,const LLUUID&,getSpecularID,LLUUID::null, false, LLUUID::null) + DEF_GET_MAT_STATE(F32,F32,getSpecularRepeatX,1.0f, true, 0.001f) + DEF_GET_MAT_STATE(F32,F32,getSpecularRepeatY,1.0f, true, 0.001f) + DEF_GET_MAT_STATE(F32,F32,getSpecularOffsetX,0.0f, true, 0.001f) + DEF_GET_MAT_STATE(F32,F32,getSpecularOffsetY,0.0f, true, 0.001f) + DEF_GET_MAT_STATE(F32,F32,getSpecularRotation,0.0f, true, 0.001f) + + DEF_GET_MAT_STATE(F32,F32,getNormalRepeatX,1.0f, true, 0.001f) + DEF_GET_MAT_STATE(F32,F32,getNormalRepeatY,1.0f, true, 0.001f) + DEF_GET_MAT_STATE(F32,F32,getNormalOffsetX,0.0f, true, 0.001f) + DEF_GET_MAT_STATE(F32,F32,getNormalOffsetY,0.0f, true, 0.001f) + DEF_GET_MAT_STATE(F32,F32,getNormalRotation,0.0f, true, 0.001f) + + DEF_EDIT_MAT_STATE(U8,U8,setDiffuseAlphaMode); + DEF_EDIT_MAT_STATE(U8,U8,setAlphaMaskCutoff); + + DEF_EDIT_MAT_STATE(F32,F32,setNormalOffsetX); + DEF_EDIT_MAT_STATE(F32,F32,setNormalOffsetY); + DEF_EDIT_MAT_STATE(F32,F32,setNormalRepeatX); + DEF_EDIT_MAT_STATE(F32,F32,setNormalRepeatY); + DEF_EDIT_MAT_STATE(F32,F32,setNormalRotation); + + DEF_EDIT_MAT_STATE(F32,F32,setSpecularOffsetX); + DEF_EDIT_MAT_STATE(F32,F32,setSpecularOffsetY); + DEF_EDIT_MAT_STATE(F32,F32,setSpecularRepeatX); + DEF_EDIT_MAT_STATE(F32,F32,setSpecularRepeatY); + DEF_EDIT_MAT_STATE(F32,F32,setSpecularRotation); + + DEF_EDIT_MAT_STATE(U8,U8,setEnvironmentIntensity); + DEF_EDIT_MAT_STATE(U8,U8,setSpecularLightExponent); + + DEF_EDIT_MAT_STATE(LLUUID,const LLUUID&,setNormalID); + DEF_EDIT_MAT_STATE(LLUUID,const LLUUID&,setSpecularID); + DEF_EDIT_MAT_STATE(LLColor4U, const LLColor4U&,setSpecularLightColor); + }; + + class LLSelectedTE + { + public: + static void getFace(class LLFace*& face_to_return, bool& identical_face); + static void getImageFormat(LLGLenum& image_format_to_return, bool& identical_face); + static void getTexId(LLUUID& id, bool& identical); + static void getPbrMaterialId(LLUUID& id, bool& identical, bool& has_pbr, bool& has_faces_without_pbr); + static void getObjectScaleS(F32& scale_s, bool& identical); + static void getObjectScaleT(F32& scale_t, bool& identical); + static void getMaxDiffuseRepeats(F32& repeats, bool& identical); + + DEF_GET_TE_STATE(U8,U8,getBumpmap,0, false, 0) + DEF_GET_TE_STATE(U8,U8,getShiny,0, false, 0) + DEF_GET_TE_STATE(U8,U8,getFullbright,0, false, 0) + DEF_GET_TE_STATE(F32,F32,getRotation,0.0f, true, 0.001f) + DEF_GET_TE_STATE(F32,F32,getOffsetS,0.0f, true, 0.001f) + DEF_GET_TE_STATE(F32,F32,getOffsetT,0.0f, true, 0.001f) + DEF_GET_TE_STATE(F32,F32,getScaleS,1.0f, true, 0.001f) + DEF_GET_TE_STATE(F32,F32,getScaleT,1.0f, true, 0.001f) + DEF_GET_TE_STATE(F32,F32,getGlow,0.0f, true, 0.001f) + DEF_GET_TE_STATE(LLTextureEntry::e_texgen,LLTextureEntry::e_texgen,getTexGen,LLTextureEntry::TEX_GEN_DEFAULT, false, LLTextureEntry::TEX_GEN_DEFAULT) + DEF_GET_TE_STATE(LLColor4,const LLColor4&,getColor,LLColor4::white, false, LLColor4::black); + }; +}; + +#endif + diff --git a/indra/newview/llpanelgroup.cpp b/indra/newview/llpanelgroup.cpp index 5c5adee804..db9f1078fe 100644 --- a/indra/newview/llpanelgroup.cpp +++ b/indra/newview/llpanelgroup.cpp @@ -1,626 +1,626 @@ -/** - * @file llpanelgroup.cpp - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelgroup.h" - -// Library includes -#include "llbutton.h" -#include "llfloatersidepanelcontainer.h" -#include "lltabcontainer.h" -#include "lltextbox.h" -#include "lluictrlfactory.h" - -// Viewer includes -#include "llviewermessage.h" -#include "llviewerwindow.h" -#include "llappviewer.h" -#include "llnotificationsutil.h" -#include "llfloaterreg.h" -#include "llfloater.h" -#include "llgroupactions.h" - -#include "llagent.h" - -#include "llsidetraypanelcontainer.h" - -#include "llpanelgroupnotices.h" -#include "llpanelgroupgeneral.h" -#include "llpanelgrouproles.h" - -#include "llaccordionctrltab.h" -#include "llaccordionctrl.h" - -#include "lltrans.h" - -static LLPanelInjector t_panel_group("panel_group_info_sidetray"); - - - -LLPanelGroupTab::LLPanelGroupTab() - : LLPanel(), - mAllowEdit(true), - mHasModal(false) -{ - mGroupID = LLUUID::null; -} - -LLPanelGroupTab::~LLPanelGroupTab() -{ -} - -bool LLPanelGroupTab::isVisibleByAgent(LLAgent* agentp) -{ - //default to being visible - return true; -} - -bool LLPanelGroupTab::postBuild() -{ - return true; -} - -LLPanelGroup::LLPanelGroup() -: LLPanel(), - LLGroupMgrObserver( LLUUID() ), - mSkipRefresh(false), - mButtonJoin(NULL) -{ - // Set up the factory callbacks. - // Roles sub tabs - LLGroupMgr::getInstance()->addObserver(this); -} - - -LLPanelGroup::~LLPanelGroup() -{ - LLGroupMgr::getInstance()->removeObserver(this); - if(LLVoiceClient::instanceExists()) - { - LLVoiceClient::getInstance()->removeObserver(this); - } -} - -void LLPanelGroup::onOpen(const LLSD& key) -{ - if(!key.has("group_id")) - return; - - LLUUID group_id = key["group_id"]; - if(!key.has("action")) - { - setGroupID(group_id); - getChild("groups_accordion")->expandDefaultTab(); - return; - } - - std::string str_action = key["action"]; - - if(str_action == "refresh") - { - if(mID == group_id || group_id == LLUUID::null) - refreshData(); - } - else if(str_action == "close") - { - onBackBtnClick(); - } - else if(str_action == "refresh_notices") - { - LLPanelGroupNotices* panel_notices = findChild("group_notices_tab_panel"); - if(panel_notices) - panel_notices->refreshNotices(); - } - if (str_action == "show_notices") - { - setGroupID(group_id); - - LLAccordionCtrl *tab_ctrl = getChild("groups_accordion"); - tab_ctrl->collapseAllTabs(); - getChild("group_notices_tab")->setDisplayChildren(true); - tab_ctrl->arrange(); - } - -} - -bool LLPanelGroup::postBuild() -{ - mDefaultNeedsApplyMesg = getString("default_needs_apply_text"); - mWantApplyMesg = getString("want_apply_text"); - - LLButton* button; - - button = getChild("btn_apply"); - button->setClickedCallback(onBtnApply, this); - button->setVisible(true); - button->setEnabled(false); - - button = getChild("btn_call"); - button->setClickedCallback(onBtnGroupCallClicked, this); - - button = getChild("btn_chat"); - button->setClickedCallback(onBtnGroupChatClicked, this); - - button = getChild("btn_refresh"); - button->setClickedCallback(onBtnRefresh, this); - - childSetCommitCallback("back",boost::bind(&LLPanelGroup::onBackBtnClick,this),NULL); - - LLPanelGroupTab* panel_general = findChild("group_general_tab_panel"); - LLPanelGroupTab* panel_roles = findChild("group_roles_tab_panel"); - LLPanelGroupTab* panel_notices = findChild("group_notices_tab_panel"); - LLPanelGroupTab* panel_land = findChild("group_land_tab_panel"); - LLPanelGroupTab* panel_experiences = findChild("group_experiences_tab_panel"); - - if(panel_general) mTabs.push_back(panel_general); - if(panel_roles) mTabs.push_back(panel_roles); - if(panel_notices) mTabs.push_back(panel_notices); - if(panel_land) mTabs.push_back(panel_land); - if(panel_experiences) mTabs.push_back(panel_experiences); - - if(panel_general) - { - panel_general->setupCtrls(this); - button = panel_general->getChild("btn_join"); - button->setVisible(false); - button->setEnabled(true); - - mButtonJoin = button; - mButtonJoin->setCommitCallback(boost::bind(&LLPanelGroup::onBtnJoin,this)); - - mJoinText = panel_general->getChild("join_cost_text"); - } - - LLVoiceClient::getInstance()->addObserver(this); - - return true; -} - -void LLPanelGroup::reposButton(const std::string& name) -{ - LLButton* button = findChild(name); - if(!button) - return; - LLRect btn_rect = button->getRect(); - btn_rect.setLeftTopAndSize( btn_rect.mLeft, btn_rect.getHeight() + 2, btn_rect.getWidth(), btn_rect.getHeight()); - button->setRect(btn_rect); -} - -void LLPanelGroup::reposButtons() -{ - LLButton* button_refresh = findChild("btn_refresh"); - LLButton* button_cancel = findChild("btn_cancel"); - - if(button_refresh && button_cancel && button_refresh->getVisible() && button_cancel->getVisible()) - { - LLRect btn_refresh_rect = button_refresh->getRect(); - LLRect btn_cancel_rect = button_cancel->getRect(); - btn_refresh_rect.setLeftTopAndSize( btn_cancel_rect.mLeft + btn_cancel_rect.getWidth() + 2, - btn_refresh_rect.getHeight() + 2, btn_refresh_rect.getWidth(), btn_refresh_rect.getHeight()); - button_refresh->setRect(btn_refresh_rect); - } - - reposButton("btn_apply"); - reposButton("btn_refresh"); - reposButton("btn_cancel"); - reposButton("btn_chat"); - reposButton("btn_call"); -} - -void LLPanelGroup::reshape(S32 width, S32 height, bool called_from_parent ) -{ - LLPanel::reshape(width, height, called_from_parent ); - - reposButtons(); -} - -void LLPanelGroup::onBackBtnClick() -{ - LLSideTrayPanelContainer* parent = dynamic_cast(getParent()); - if(parent) - { - parent->openPreviousPanel(); - } -} - -void LLPanelGroup::onBtnRefresh(void* user_data) -{ - LLPanelGroup* self = static_cast(user_data); - self->refreshData(); -} - -void LLPanelGroup::onBtnApply(void* user_data) -{ - LLPanelGroup* self = static_cast(user_data); - self->apply(); - self->refreshData(); -} - -void LLPanelGroup::onBtnGroupCallClicked(void* user_data) -{ - LLPanelGroup* self = static_cast(user_data); - self->callGroup(); -} - -void LLPanelGroup::onBtnGroupChatClicked(void* user_data) -{ - LLPanelGroup* self = static_cast(user_data); - self->chatGroup(); -} - -void LLPanelGroup::onBtnJoin() -{ - if (LLGroupActions::isInGroup(mID)) - { - LLGroupActions::leave(mID); - } - else - { - LL_DEBUGS() << "joining group: " << mID << LL_ENDL; - LLGroupActions::join(mID); -} -} - -void LLPanelGroup::changed(LLGroupChange gc) -{ - for(std::vector::iterator it = mTabs.begin();it!=mTabs.end();++it) - (*it)->update(gc); - update(gc); -} - -// virtual -void LLPanelGroup::onChange(EStatusType status, const std::string &channelURI, bool proximal) -{ - if(status == STATUS_JOINING || status == STATUS_LEFT_CHANNEL) - { - return; - } - - childSetEnabled("btn_call", LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking()); -} - -void LLPanelGroup::notifyObservers() -{ - changed(GC_ALL); -} - -void LLPanelGroup::update(LLGroupChange gc) -{ - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mID); - if(gdatap) - { - std::string group_name = gdatap->mName.empty() ? LLTrans::getString("LoadingData") : gdatap->mName; - LLUICtrl* group_name_ctrl = getChild("group_name"); - group_name_ctrl->setValue(group_name); - group_name_ctrl->setToolTip(group_name); - - LLGroupData agent_gdatap; - bool is_member = gAgent.getGroupData(mID,agent_gdatap) || gAgent.isGodlikeWithoutAdminMenuFakery(); - bool join_btn_visible = is_member || gdatap->mOpenEnrollment; - - mButtonJoin->setVisible(join_btn_visible); - mJoinText->setVisible(join_btn_visible); - - if (is_member) - { - mJoinText->setValue(getString("group_member")); - mButtonJoin->setLabel(getString("leave_txt")); - } - else if(join_btn_visible) - { - LLStringUtil::format_map_t string_args; - std::string fee_buff; - if(gdatap->mMembershipFee) - { - string_args["[AMOUNT]"] = llformat("%d", gdatap->mMembershipFee); - fee_buff = getString("group_join_btn", string_args); - - } - else - { - fee_buff = getString("group_join_free", string_args); - } - mJoinText->setValue(fee_buff); - mButtonJoin->setLabel(getString("join_txt")); - } - } -} - -void LLPanelGroup::setGroupID(const LLUUID& group_id) -{ - std::string str_group_id; - group_id.toString(str_group_id); - - bool is_same_id = group_id == mID; - - LLGroupMgr::getInstance()->removeObserver(this); - mID = group_id; - LLGroupMgr::getInstance()->addObserver(this); - - for(std::vector::iterator it = mTabs.begin();it!=mTabs.end();++it) - (*it)->setGroupID(group_id); - - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mID); - if(gdatap) - { - std::string group_name = gdatap->mName.empty() ? LLTrans::getString("LoadingData") : gdatap->mName; - LLUICtrl* group_name_ctrl = getChild("group_name"); - group_name_ctrl->setValue(group_name); - group_name_ctrl->setToolTip(group_name); - } - - LLButton* button_apply = findChild("btn_apply"); - LLButton* button_refresh = findChild("btn_refresh"); - - LLButton* button_cancel = findChild("btn_cancel"); - LLButton* button_call = findChild("btn_call"); - LLButton* button_chat = findChild("btn_chat"); - - - bool is_null_group_id = group_id == LLUUID::null; - if(button_apply) - button_apply->setVisible(!is_null_group_id); - if(button_refresh) - button_refresh->setVisible(!is_null_group_id); - - if(button_cancel) - button_cancel->setVisible(!is_null_group_id); - - if(button_call) - button_call->setVisible(!is_null_group_id); - if(button_chat) - button_chat->setVisible(!is_null_group_id); - - getChild("prepend_founded_by")->setVisible(!is_null_group_id); - - LLAccordionCtrl* tab_ctrl = getChild("groups_accordion"); - tab_ctrl->reset(); - - LLAccordionCtrlTab* tab_general = getChild("group_general_tab"); - LLAccordionCtrlTab* tab_roles = getChild("group_roles_tab"); - LLAccordionCtrlTab* tab_notices = getChild("group_notices_tab"); - LLAccordionCtrlTab* tab_land = getChild("group_land_tab"); - LLAccordionCtrlTab* tab_experiences = getChild("group_experiences_tab"); - - if(mButtonJoin) - mButtonJoin->setVisible(false); - - - if(is_null_group_id)//creating new group - { - if(!tab_general->getDisplayChildren()) - tab_general->changeOpenClose(tab_general->getDisplayChildren()); - - if(tab_roles->getDisplayChildren()) - tab_roles->changeOpenClose(tab_roles->getDisplayChildren()); - if(tab_notices->getDisplayChildren()) - tab_notices->changeOpenClose(tab_notices->getDisplayChildren()); - if(tab_land->getDisplayChildren()) - tab_land->changeOpenClose(tab_land->getDisplayChildren()); - if(tab_experiences->getDisplayChildren()) - tab_experiences->changeOpenClose(tab_land->getDisplayChildren()); - - tab_roles->setVisible(false); - tab_notices->setVisible(false); - tab_land->setVisible(false); - tab_experiences->setVisible(false); - - getChild("group_name")->setVisible(false); - getChild("group_name_editor")->setVisible(true); - - if(button_call) - button_call->setVisible(false); - if(button_chat) - button_chat->setVisible(false); - } - else - { - if(!is_same_id) - { - if(!tab_general->getDisplayChildren()) - tab_general->changeOpenClose(tab_general->getDisplayChildren()); - if(tab_roles->getDisplayChildren()) - tab_roles->changeOpenClose(tab_roles->getDisplayChildren()); - if(tab_notices->getDisplayChildren()) - tab_notices->changeOpenClose(tab_notices->getDisplayChildren()); - if(tab_land->getDisplayChildren()) - tab_land->changeOpenClose(tab_land->getDisplayChildren()); - if(tab_experiences->getDisplayChildren()) - tab_experiences->changeOpenClose(tab_land->getDisplayChildren()); - } - - LLGroupData agent_gdatap; - bool is_member = gAgent.getGroupData(mID,agent_gdatap) || gAgent.isGodlikeWithoutAdminMenuFakery(); - - tab_roles->setVisible(is_member); - tab_notices->setVisible(is_member); - tab_land->setVisible(is_member); - tab_experiences->setVisible(is_member); - - getChild("group_name")->setVisible(true); - getChild("group_name_editor")->setVisible(false); - - if(button_apply) - button_apply->setVisible(is_member); - if(button_call) - button_call->setVisible(is_member); - if(button_chat) - button_chat->setVisible(is_member); - } - - tab_ctrl->arrange(); - - reposButtons(); - update(GC_ALL);//show/hide "join" button if data is already ready -} - -bool LLPanelGroup::apply(LLPanelGroupTab* tab) -{ - if(!tab) - return false; - - std::string mesg; - if ( !tab->needsApply(mesg) ) - return true; - - std::string apply_mesg; - if(tab->apply( apply_mesg ) ) - { - //we skip refreshing group after ew manually apply changes since its very annoying - //for those who are editing group - - LLPanelGroupRoles * roles_tab = dynamic_cast(tab); - if (roles_tab) - { - LLGroupMgr* gmgrp = LLGroupMgr::getInstance(); - LLGroupMgrGroupData* gdatap = gmgrp->getGroupData(roles_tab->getGroupID()); - - // allow refresh only for one specific case: - // there is only one member in group and it is not owner - // it's a wrong situation and need refresh panels from server - if (gdatap && gdatap->isSingleMemberNotOwner()) - { - return true; - } - } - - mSkipRefresh = true; - return true; - } - - if ( !apply_mesg.empty() ) - { - LLSD args; - args["MESSAGE"] = apply_mesg; - LLNotificationsUtil::add("GenericAlert", args); - } - return false; -} - -bool LLPanelGroup::apply() -{ - return apply(findChild("group_general_tab_panel")) - && apply(findChild("group_roles_tab_panel")) - && apply(findChild("group_notices_tab_panel")) - && apply(findChild("group_land_tab_panel")) - && apply(findChild("group_experiences_tab_panel")) - ; -} - - -// virtual -void LLPanelGroup::draw() -{ - LLPanel::draw(); - - if (mRefreshTimer.hasExpired()) - { - mRefreshTimer.stop(); - childEnable("btn_refresh"); - childEnable("groups_accordion"); - } - - LLButton* button_apply = findChild("btn_apply"); - - if(button_apply && button_apply->getVisible()) - { - bool enable = false; - std::string mesg; - for(std::vector::iterator it = mTabs.begin();it!=mTabs.end();++it) - enable = enable || (*it)->needsApply(mesg); - - childSetEnabled("btn_apply", enable); - } -} - -void LLPanelGroup::refreshData() -{ - if(mSkipRefresh) - { - mSkipRefresh = false; - return; - } - LLGroupMgr::getInstance()->clearGroupData(getID()); - - setGroupID(getID()); - - // 5 second timeout - childDisable("btn_refresh"); - childDisable("groups_accordion"); - - mRefreshTimer.start(); - mRefreshTimer.setTimerExpirySec(5); -} - -void LLPanelGroup::callGroup() -{ - LLGroupActions::startCall(getID()); -} - -void LLPanelGroup::chatGroup() -{ - LLGroupActions::startIM(getID()); -} - -void LLPanelGroup::showNotice(const std::string& subject, - const std::string& message, - const bool& has_inventory, - const std::string& inventory_name, - LLOfferInfo* inventory_offer) -{ - LLPanelGroupNotices* panel_notices = findChild("group_notices_tab_panel"); - if(!panel_notices) - { - // We need to clean up that inventory offer. - if (inventory_offer) - { - inventory_offer->forceResponse(IOR_DECLINE); - } - return; - } - panel_notices->showNotice(subject,message,has_inventory,inventory_name,inventory_offer); -} - -//static - -void LLPanelGroup::showNotice(const std::string& subject, - const std::string& message, - const LLUUID& group_id, - const bool& has_inventory, - const std::string& inventory_name, - LLOfferInfo* inventory_offer) -{ - LLPanelGroup* panel = LLFloaterSidePanelContainer::getPanel("people", "panel_group_info_sidetray"); - if(!panel) - return; - - if(panel->getID() != group_id)//???? only for current group_id or switch panels? FIXME - return; - panel->showNotice(subject,message,has_inventory,inventory_name,inventory_offer); - -} - - +/** + * @file llpanelgroup.cpp + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelgroup.h" + +// Library includes +#include "llbutton.h" +#include "llfloatersidepanelcontainer.h" +#include "lltabcontainer.h" +#include "lltextbox.h" +#include "lluictrlfactory.h" + +// Viewer includes +#include "llviewermessage.h" +#include "llviewerwindow.h" +#include "llappviewer.h" +#include "llnotificationsutil.h" +#include "llfloaterreg.h" +#include "llfloater.h" +#include "llgroupactions.h" + +#include "llagent.h" + +#include "llsidetraypanelcontainer.h" + +#include "llpanelgroupnotices.h" +#include "llpanelgroupgeneral.h" +#include "llpanelgrouproles.h" + +#include "llaccordionctrltab.h" +#include "llaccordionctrl.h" + +#include "lltrans.h" + +static LLPanelInjector t_panel_group("panel_group_info_sidetray"); + + + +LLPanelGroupTab::LLPanelGroupTab() + : LLPanel(), + mAllowEdit(true), + mHasModal(false) +{ + mGroupID = LLUUID::null; +} + +LLPanelGroupTab::~LLPanelGroupTab() +{ +} + +bool LLPanelGroupTab::isVisibleByAgent(LLAgent* agentp) +{ + //default to being visible + return true; +} + +bool LLPanelGroupTab::postBuild() +{ + return true; +} + +LLPanelGroup::LLPanelGroup() +: LLPanel(), + LLGroupMgrObserver( LLUUID() ), + mSkipRefresh(false), + mButtonJoin(NULL) +{ + // Set up the factory callbacks. + // Roles sub tabs + LLGroupMgr::getInstance()->addObserver(this); +} + + +LLPanelGroup::~LLPanelGroup() +{ + LLGroupMgr::getInstance()->removeObserver(this); + if(LLVoiceClient::instanceExists()) + { + LLVoiceClient::getInstance()->removeObserver(this); + } +} + +void LLPanelGroup::onOpen(const LLSD& key) +{ + if(!key.has("group_id")) + return; + + LLUUID group_id = key["group_id"]; + if(!key.has("action")) + { + setGroupID(group_id); + getChild("groups_accordion")->expandDefaultTab(); + return; + } + + std::string str_action = key["action"]; + + if(str_action == "refresh") + { + if(mID == group_id || group_id == LLUUID::null) + refreshData(); + } + else if(str_action == "close") + { + onBackBtnClick(); + } + else if(str_action == "refresh_notices") + { + LLPanelGroupNotices* panel_notices = findChild("group_notices_tab_panel"); + if(panel_notices) + panel_notices->refreshNotices(); + } + if (str_action == "show_notices") + { + setGroupID(group_id); + + LLAccordionCtrl *tab_ctrl = getChild("groups_accordion"); + tab_ctrl->collapseAllTabs(); + getChild("group_notices_tab")->setDisplayChildren(true); + tab_ctrl->arrange(); + } + +} + +bool LLPanelGroup::postBuild() +{ + mDefaultNeedsApplyMesg = getString("default_needs_apply_text"); + mWantApplyMesg = getString("want_apply_text"); + + LLButton* button; + + button = getChild("btn_apply"); + button->setClickedCallback(onBtnApply, this); + button->setVisible(true); + button->setEnabled(false); + + button = getChild("btn_call"); + button->setClickedCallback(onBtnGroupCallClicked, this); + + button = getChild("btn_chat"); + button->setClickedCallback(onBtnGroupChatClicked, this); + + button = getChild("btn_refresh"); + button->setClickedCallback(onBtnRefresh, this); + + childSetCommitCallback("back",boost::bind(&LLPanelGroup::onBackBtnClick,this),NULL); + + LLPanelGroupTab* panel_general = findChild("group_general_tab_panel"); + LLPanelGroupTab* panel_roles = findChild("group_roles_tab_panel"); + LLPanelGroupTab* panel_notices = findChild("group_notices_tab_panel"); + LLPanelGroupTab* panel_land = findChild("group_land_tab_panel"); + LLPanelGroupTab* panel_experiences = findChild("group_experiences_tab_panel"); + + if(panel_general) mTabs.push_back(panel_general); + if(panel_roles) mTabs.push_back(panel_roles); + if(panel_notices) mTabs.push_back(panel_notices); + if(panel_land) mTabs.push_back(panel_land); + if(panel_experiences) mTabs.push_back(panel_experiences); + + if(panel_general) + { + panel_general->setupCtrls(this); + button = panel_general->getChild("btn_join"); + button->setVisible(false); + button->setEnabled(true); + + mButtonJoin = button; + mButtonJoin->setCommitCallback(boost::bind(&LLPanelGroup::onBtnJoin,this)); + + mJoinText = panel_general->getChild("join_cost_text"); + } + + LLVoiceClient::getInstance()->addObserver(this); + + return true; +} + +void LLPanelGroup::reposButton(const std::string& name) +{ + LLButton* button = findChild(name); + if(!button) + return; + LLRect btn_rect = button->getRect(); + btn_rect.setLeftTopAndSize( btn_rect.mLeft, btn_rect.getHeight() + 2, btn_rect.getWidth(), btn_rect.getHeight()); + button->setRect(btn_rect); +} + +void LLPanelGroup::reposButtons() +{ + LLButton* button_refresh = findChild("btn_refresh"); + LLButton* button_cancel = findChild("btn_cancel"); + + if(button_refresh && button_cancel && button_refresh->getVisible() && button_cancel->getVisible()) + { + LLRect btn_refresh_rect = button_refresh->getRect(); + LLRect btn_cancel_rect = button_cancel->getRect(); + btn_refresh_rect.setLeftTopAndSize( btn_cancel_rect.mLeft + btn_cancel_rect.getWidth() + 2, + btn_refresh_rect.getHeight() + 2, btn_refresh_rect.getWidth(), btn_refresh_rect.getHeight()); + button_refresh->setRect(btn_refresh_rect); + } + + reposButton("btn_apply"); + reposButton("btn_refresh"); + reposButton("btn_cancel"); + reposButton("btn_chat"); + reposButton("btn_call"); +} + +void LLPanelGroup::reshape(S32 width, S32 height, bool called_from_parent ) +{ + LLPanel::reshape(width, height, called_from_parent ); + + reposButtons(); +} + +void LLPanelGroup::onBackBtnClick() +{ + LLSideTrayPanelContainer* parent = dynamic_cast(getParent()); + if(parent) + { + parent->openPreviousPanel(); + } +} + +void LLPanelGroup::onBtnRefresh(void* user_data) +{ + LLPanelGroup* self = static_cast(user_data); + self->refreshData(); +} + +void LLPanelGroup::onBtnApply(void* user_data) +{ + LLPanelGroup* self = static_cast(user_data); + self->apply(); + self->refreshData(); +} + +void LLPanelGroup::onBtnGroupCallClicked(void* user_data) +{ + LLPanelGroup* self = static_cast(user_data); + self->callGroup(); +} + +void LLPanelGroup::onBtnGroupChatClicked(void* user_data) +{ + LLPanelGroup* self = static_cast(user_data); + self->chatGroup(); +} + +void LLPanelGroup::onBtnJoin() +{ + if (LLGroupActions::isInGroup(mID)) + { + LLGroupActions::leave(mID); + } + else + { + LL_DEBUGS() << "joining group: " << mID << LL_ENDL; + LLGroupActions::join(mID); +} +} + +void LLPanelGroup::changed(LLGroupChange gc) +{ + for(std::vector::iterator it = mTabs.begin();it!=mTabs.end();++it) + (*it)->update(gc); + update(gc); +} + +// virtual +void LLPanelGroup::onChange(EStatusType status, const std::string &channelURI, bool proximal) +{ + if(status == STATUS_JOINING || status == STATUS_LEFT_CHANNEL) + { + return; + } + + childSetEnabled("btn_call", LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking()); +} + +void LLPanelGroup::notifyObservers() +{ + changed(GC_ALL); +} + +void LLPanelGroup::update(LLGroupChange gc) +{ + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mID); + if(gdatap) + { + std::string group_name = gdatap->mName.empty() ? LLTrans::getString("LoadingData") : gdatap->mName; + LLUICtrl* group_name_ctrl = getChild("group_name"); + group_name_ctrl->setValue(group_name); + group_name_ctrl->setToolTip(group_name); + + LLGroupData agent_gdatap; + bool is_member = gAgent.getGroupData(mID,agent_gdatap) || gAgent.isGodlikeWithoutAdminMenuFakery(); + bool join_btn_visible = is_member || gdatap->mOpenEnrollment; + + mButtonJoin->setVisible(join_btn_visible); + mJoinText->setVisible(join_btn_visible); + + if (is_member) + { + mJoinText->setValue(getString("group_member")); + mButtonJoin->setLabel(getString("leave_txt")); + } + else if(join_btn_visible) + { + LLStringUtil::format_map_t string_args; + std::string fee_buff; + if(gdatap->mMembershipFee) + { + string_args["[AMOUNT]"] = llformat("%d", gdatap->mMembershipFee); + fee_buff = getString("group_join_btn", string_args); + + } + else + { + fee_buff = getString("group_join_free", string_args); + } + mJoinText->setValue(fee_buff); + mButtonJoin->setLabel(getString("join_txt")); + } + } +} + +void LLPanelGroup::setGroupID(const LLUUID& group_id) +{ + std::string str_group_id; + group_id.toString(str_group_id); + + bool is_same_id = group_id == mID; + + LLGroupMgr::getInstance()->removeObserver(this); + mID = group_id; + LLGroupMgr::getInstance()->addObserver(this); + + for(std::vector::iterator it = mTabs.begin();it!=mTabs.end();++it) + (*it)->setGroupID(group_id); + + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mID); + if(gdatap) + { + std::string group_name = gdatap->mName.empty() ? LLTrans::getString("LoadingData") : gdatap->mName; + LLUICtrl* group_name_ctrl = getChild("group_name"); + group_name_ctrl->setValue(group_name); + group_name_ctrl->setToolTip(group_name); + } + + LLButton* button_apply = findChild("btn_apply"); + LLButton* button_refresh = findChild("btn_refresh"); + + LLButton* button_cancel = findChild("btn_cancel"); + LLButton* button_call = findChild("btn_call"); + LLButton* button_chat = findChild("btn_chat"); + + + bool is_null_group_id = group_id == LLUUID::null; + if(button_apply) + button_apply->setVisible(!is_null_group_id); + if(button_refresh) + button_refresh->setVisible(!is_null_group_id); + + if(button_cancel) + button_cancel->setVisible(!is_null_group_id); + + if(button_call) + button_call->setVisible(!is_null_group_id); + if(button_chat) + button_chat->setVisible(!is_null_group_id); + + getChild("prepend_founded_by")->setVisible(!is_null_group_id); + + LLAccordionCtrl* tab_ctrl = getChild("groups_accordion"); + tab_ctrl->reset(); + + LLAccordionCtrlTab* tab_general = getChild("group_general_tab"); + LLAccordionCtrlTab* tab_roles = getChild("group_roles_tab"); + LLAccordionCtrlTab* tab_notices = getChild("group_notices_tab"); + LLAccordionCtrlTab* tab_land = getChild("group_land_tab"); + LLAccordionCtrlTab* tab_experiences = getChild("group_experiences_tab"); + + if(mButtonJoin) + mButtonJoin->setVisible(false); + + + if(is_null_group_id)//creating new group + { + if(!tab_general->getDisplayChildren()) + tab_general->changeOpenClose(tab_general->getDisplayChildren()); + + if(tab_roles->getDisplayChildren()) + tab_roles->changeOpenClose(tab_roles->getDisplayChildren()); + if(tab_notices->getDisplayChildren()) + tab_notices->changeOpenClose(tab_notices->getDisplayChildren()); + if(tab_land->getDisplayChildren()) + tab_land->changeOpenClose(tab_land->getDisplayChildren()); + if(tab_experiences->getDisplayChildren()) + tab_experiences->changeOpenClose(tab_land->getDisplayChildren()); + + tab_roles->setVisible(false); + tab_notices->setVisible(false); + tab_land->setVisible(false); + tab_experiences->setVisible(false); + + getChild("group_name")->setVisible(false); + getChild("group_name_editor")->setVisible(true); + + if(button_call) + button_call->setVisible(false); + if(button_chat) + button_chat->setVisible(false); + } + else + { + if(!is_same_id) + { + if(!tab_general->getDisplayChildren()) + tab_general->changeOpenClose(tab_general->getDisplayChildren()); + if(tab_roles->getDisplayChildren()) + tab_roles->changeOpenClose(tab_roles->getDisplayChildren()); + if(tab_notices->getDisplayChildren()) + tab_notices->changeOpenClose(tab_notices->getDisplayChildren()); + if(tab_land->getDisplayChildren()) + tab_land->changeOpenClose(tab_land->getDisplayChildren()); + if(tab_experiences->getDisplayChildren()) + tab_experiences->changeOpenClose(tab_land->getDisplayChildren()); + } + + LLGroupData agent_gdatap; + bool is_member = gAgent.getGroupData(mID,agent_gdatap) || gAgent.isGodlikeWithoutAdminMenuFakery(); + + tab_roles->setVisible(is_member); + tab_notices->setVisible(is_member); + tab_land->setVisible(is_member); + tab_experiences->setVisible(is_member); + + getChild("group_name")->setVisible(true); + getChild("group_name_editor")->setVisible(false); + + if(button_apply) + button_apply->setVisible(is_member); + if(button_call) + button_call->setVisible(is_member); + if(button_chat) + button_chat->setVisible(is_member); + } + + tab_ctrl->arrange(); + + reposButtons(); + update(GC_ALL);//show/hide "join" button if data is already ready +} + +bool LLPanelGroup::apply(LLPanelGroupTab* tab) +{ + if(!tab) + return false; + + std::string mesg; + if ( !tab->needsApply(mesg) ) + return true; + + std::string apply_mesg; + if(tab->apply( apply_mesg ) ) + { + //we skip refreshing group after ew manually apply changes since its very annoying + //for those who are editing group + + LLPanelGroupRoles * roles_tab = dynamic_cast(tab); + if (roles_tab) + { + LLGroupMgr* gmgrp = LLGroupMgr::getInstance(); + LLGroupMgrGroupData* gdatap = gmgrp->getGroupData(roles_tab->getGroupID()); + + // allow refresh only for one specific case: + // there is only one member in group and it is not owner + // it's a wrong situation and need refresh panels from server + if (gdatap && gdatap->isSingleMemberNotOwner()) + { + return true; + } + } + + mSkipRefresh = true; + return true; + } + + if ( !apply_mesg.empty() ) + { + LLSD args; + args["MESSAGE"] = apply_mesg; + LLNotificationsUtil::add("GenericAlert", args); + } + return false; +} + +bool LLPanelGroup::apply() +{ + return apply(findChild("group_general_tab_panel")) + && apply(findChild("group_roles_tab_panel")) + && apply(findChild("group_notices_tab_panel")) + && apply(findChild("group_land_tab_panel")) + && apply(findChild("group_experiences_tab_panel")) + ; +} + + +// virtual +void LLPanelGroup::draw() +{ + LLPanel::draw(); + + if (mRefreshTimer.hasExpired()) + { + mRefreshTimer.stop(); + childEnable("btn_refresh"); + childEnable("groups_accordion"); + } + + LLButton* button_apply = findChild("btn_apply"); + + if(button_apply && button_apply->getVisible()) + { + bool enable = false; + std::string mesg; + for(std::vector::iterator it = mTabs.begin();it!=mTabs.end();++it) + enable = enable || (*it)->needsApply(mesg); + + childSetEnabled("btn_apply", enable); + } +} + +void LLPanelGroup::refreshData() +{ + if(mSkipRefresh) + { + mSkipRefresh = false; + return; + } + LLGroupMgr::getInstance()->clearGroupData(getID()); + + setGroupID(getID()); + + // 5 second timeout + childDisable("btn_refresh"); + childDisable("groups_accordion"); + + mRefreshTimer.start(); + mRefreshTimer.setTimerExpirySec(5); +} + +void LLPanelGroup::callGroup() +{ + LLGroupActions::startCall(getID()); +} + +void LLPanelGroup::chatGroup() +{ + LLGroupActions::startIM(getID()); +} + +void LLPanelGroup::showNotice(const std::string& subject, + const std::string& message, + const bool& has_inventory, + const std::string& inventory_name, + LLOfferInfo* inventory_offer) +{ + LLPanelGroupNotices* panel_notices = findChild("group_notices_tab_panel"); + if(!panel_notices) + { + // We need to clean up that inventory offer. + if (inventory_offer) + { + inventory_offer->forceResponse(IOR_DECLINE); + } + return; + } + panel_notices->showNotice(subject,message,has_inventory,inventory_name,inventory_offer); +} + +//static + +void LLPanelGroup::showNotice(const std::string& subject, + const std::string& message, + const LLUUID& group_id, + const bool& has_inventory, + const std::string& inventory_name, + LLOfferInfo* inventory_offer) +{ + LLPanelGroup* panel = LLFloaterSidePanelContainer::getPanel("people", "panel_group_info_sidetray"); + if(!panel) + return; + + if(panel->getID() != group_id)//???? only for current group_id or switch panels? FIXME + return; + panel->showNotice(subject,message,has_inventory,inventory_name,inventory_offer); + +} + + diff --git a/indra/newview/llpanelgroup.h b/indra/newview/llpanelgroup.h index 3873228cbd..f85408698a 100644 --- a/indra/newview/llpanelgroup.h +++ b/indra/newview/llpanelgroup.h @@ -1,171 +1,171 @@ -/** - * @file llpanelgroup.h - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELGROUP_H -#define LL_LLPANELGROUP_H - -#include "llgroupmgr.h" -#include "llpanel.h" -#include "lltimer.h" -#include "llvoiceclient.h" - -class LLOfferInfo; - -const F32 UPDATE_MEMBERS_SECONDS_PER_FRAME = 0.005; // 5ms - -// Forward declares -class LLPanelGroupTab; -class LLTabContainer; -class LLAgent; - - -class LLPanelGroup : public LLPanel, - public LLGroupMgrObserver, - public LLVoiceClientStatusObserver -{ -public: - LLPanelGroup(); - virtual ~LLPanelGroup(); - - virtual bool postBuild(); - - void setGroupID(const LLUUID& group_id); - - void draw(); - - void onOpen(const LLSD& key); - - // Group manager observer trigger. - virtual void changed(LLGroupChange gc); - - // Implements LLVoiceClientStatusObserver::onChange() to enable the call - // button when voice is available - /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal); - - void showNotice(const std::string& subject, - const std::string& message, - const bool& has_inventory, - const std::string& inventory_name, - LLOfferInfo* inventory_offer); - - void notifyObservers(); - - bool apply(); - void refreshData(); - void callGroup(); - void chatGroup(); - - virtual void reshape(S32 width, S32 height, bool called_from_parent = true); - - static void showNotice(const std::string& subject, - const std::string& message, - const LLUUID& group_id, - const bool& has_inventory, - const std::string& inventory_name, - LLOfferInfo* inventory_offer); - - -protected: - virtual void update(LLGroupChange gc); - - void onBackBtnClick(); - void onBtnJoin(); - - static void onBtnApply(void*); - static void onBtnRefresh(void*); - static void onBtnGroupCallClicked(void*); - static void onBtnGroupChatClicked(void*); - - void reposButton(const std::string& name); - void reposButtons(); - - -protected: - bool apply(LLPanelGroupTab* tab); - - LLTimer mRefreshTimer; - - bool mSkipRefresh; - - std::string mDefaultNeedsApplyMesg; - std::string mWantApplyMesg; - - std::vector mTabs; - - LLButton* mButtonJoin; - LLUICtrl* mJoinText; -}; - -class LLPanelGroupTab : public LLPanel -{ -public: - LLPanelGroupTab(); - virtual ~LLPanelGroupTab(); - - // Triggered when the tab becomes active. - virtual void activate() { } - - // Triggered when the tab becomes inactive. - virtual void deactivate() { } - - // Asks if something needs to be applied. - // If returning true, this function should modify the message to the user. - virtual bool needsApply(std::string& mesg) { return false; } - - // Asks if there is currently a modal dialog being shown. - virtual bool hasModal() { return mHasModal; } - - // Request to apply current data. - // If returning fail, this function should modify the message to the user. - virtual bool apply(std::string& mesg) { return true; } - - // Request a cancel of changes - virtual void cancel() { } - - // Triggered when group information changes in the group manager. - virtual void update(LLGroupChange gc) { } - - // This just connects the help button callback. - virtual bool postBuild(); - - virtual bool isVisibleByAgent(LLAgent* agentp); - - virtual void setGroupID(const LLUUID& id) {mGroupID = id;}; - - void notifyObservers() {}; - - const LLUUID& getGroupID() const { return mGroupID;} - - virtual void setupCtrls (LLPanel* parent) {}; - - virtual void onFilterChanged() { } - -protected: - LLUUID mGroupID; - bool mAllowEdit; - bool mHasModal; -}; - -#endif // LL_LLPANELGROUP_H +/** + * @file llpanelgroup.h + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELGROUP_H +#define LL_LLPANELGROUP_H + +#include "llgroupmgr.h" +#include "llpanel.h" +#include "lltimer.h" +#include "llvoiceclient.h" + +class LLOfferInfo; + +const F32 UPDATE_MEMBERS_SECONDS_PER_FRAME = 0.005; // 5ms + +// Forward declares +class LLPanelGroupTab; +class LLTabContainer; +class LLAgent; + + +class LLPanelGroup : public LLPanel, + public LLGroupMgrObserver, + public LLVoiceClientStatusObserver +{ +public: + LLPanelGroup(); + virtual ~LLPanelGroup(); + + virtual bool postBuild(); + + void setGroupID(const LLUUID& group_id); + + void draw(); + + void onOpen(const LLSD& key); + + // Group manager observer trigger. + virtual void changed(LLGroupChange gc); + + // Implements LLVoiceClientStatusObserver::onChange() to enable the call + // button when voice is available + /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal); + + void showNotice(const std::string& subject, + const std::string& message, + const bool& has_inventory, + const std::string& inventory_name, + LLOfferInfo* inventory_offer); + + void notifyObservers(); + + bool apply(); + void refreshData(); + void callGroup(); + void chatGroup(); + + virtual void reshape(S32 width, S32 height, bool called_from_parent = true); + + static void showNotice(const std::string& subject, + const std::string& message, + const LLUUID& group_id, + const bool& has_inventory, + const std::string& inventory_name, + LLOfferInfo* inventory_offer); + + +protected: + virtual void update(LLGroupChange gc); + + void onBackBtnClick(); + void onBtnJoin(); + + static void onBtnApply(void*); + static void onBtnRefresh(void*); + static void onBtnGroupCallClicked(void*); + static void onBtnGroupChatClicked(void*); + + void reposButton(const std::string& name); + void reposButtons(); + + +protected: + bool apply(LLPanelGroupTab* tab); + + LLTimer mRefreshTimer; + + bool mSkipRefresh; + + std::string mDefaultNeedsApplyMesg; + std::string mWantApplyMesg; + + std::vector mTabs; + + LLButton* mButtonJoin; + LLUICtrl* mJoinText; +}; + +class LLPanelGroupTab : public LLPanel +{ +public: + LLPanelGroupTab(); + virtual ~LLPanelGroupTab(); + + // Triggered when the tab becomes active. + virtual void activate() { } + + // Triggered when the tab becomes inactive. + virtual void deactivate() { } + + // Asks if something needs to be applied. + // If returning true, this function should modify the message to the user. + virtual bool needsApply(std::string& mesg) { return false; } + + // Asks if there is currently a modal dialog being shown. + virtual bool hasModal() { return mHasModal; } + + // Request to apply current data. + // If returning fail, this function should modify the message to the user. + virtual bool apply(std::string& mesg) { return true; } + + // Request a cancel of changes + virtual void cancel() { } + + // Triggered when group information changes in the group manager. + virtual void update(LLGroupChange gc) { } + + // This just connects the help button callback. + virtual bool postBuild(); + + virtual bool isVisibleByAgent(LLAgent* agentp); + + virtual void setGroupID(const LLUUID& id) {mGroupID = id;}; + + void notifyObservers() {}; + + const LLUUID& getGroupID() const { return mGroupID;} + + virtual void setupCtrls (LLPanel* parent) {}; + + virtual void onFilterChanged() { } + +protected: + LLUUID mGroupID; + bool mAllowEdit; + bool mHasModal; +}; + +#endif // LL_LLPANELGROUP_H diff --git a/indra/newview/llpanelgroupbulk.cpp b/indra/newview/llpanelgroupbulk.cpp index 53fade99c2..f54ec45a3c 100644 --- a/indra/newview/llpanelgroupbulk.cpp +++ b/indra/newview/llpanelgroupbulk.cpp @@ -1,422 +1,422 @@ -/** -* @file llpanelgroupbulk.cpp -* @brief Implementation of llpanelgroupbulk -* @author Baker@lindenlab.com -* -* $LicenseInfo:firstyear=2013&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2013, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelgroupbulk.h" -#include "llpanelgroupbulkimpl.h" - -#include "llagent.h" -#include "llavatarnamecache.h" -#include "llfloateravatarpicker.h" -#include "llbutton.h" -#include "llcallingcard.h" -#include "llcombobox.h" -#include "llgroupactions.h" -#include "llgroupmgr.h" -#include "llnamelistctrl.h" -#include "llnotificationsutil.h" -#include "llscrolllistitem.h" -#include "llspinctrl.h" -#include "lltextbox.h" -#include "llviewerobject.h" -#include "llviewerobjectlist.h" -#include "lluictrlfactory.h" -#include "llviewerwindow.h" - - -////////////////////////////////////////////////////////////////////////// -// Implementation of llpanelgroupbulkimpl.h functions -////////////////////////////////////////////////////////////////////////// -LLPanelGroupBulkImpl::LLPanelGroupBulkImpl(const LLUUID& group_id) : - mGroupID(group_id), - mBulkAgentList(NULL), - mOKButton(NULL), - mRemoveButton(NULL), - mGroupName(NULL), - mLoadingText(), - mTooManySelected(), - mCloseCallback(NULL), - mCloseCallbackUserData(NULL), - mAvatarNameCacheConnection(), - mRoleNames(NULL), - mOwnerWarning(), - mAlreadyInGroup(), - mConfirmedOwnerInvite(false), - mListFullNotificationSent(false) -{} - -LLPanelGroupBulkImpl::~LLPanelGroupBulkImpl() -{ - if(mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } -} - -void LLPanelGroupBulkImpl::callbackClickAdd(void* userdata) -{ - LLPanelGroupBulk* panelp = (LLPanelGroupBulk*)userdata; - - if(panelp) - { - //Right now this is hard coded with some knowledge that it is part - //of a floater since the avatar picker needs to be added as a dependent - //floater to the parent floater. - //Soon the avatar picker will be embedded into this panel - //instead of being it's own separate floater. But that is next week. - //This will do for now. -jwolk May 10, 2006 - LLView* button = panelp->findChild("add_button"); - LLFloater* root_floater = gFloaterView->getParentFloater(panelp); - LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show( - boost::bind(callbackAddUsers, _1, panelp->mImplementation), true, false, false, root_floater->getName(), button); - if(picker) - { - root_floater->addDependentFloater(picker); - LLGroupMgr::getInstance()->sendCapGroupMembersRequest(panelp->mImplementation->mGroupID); - } - } -} - -void LLPanelGroupBulkImpl::callbackClickRemove(void* userdata) -{ - LLPanelGroupBulkImpl* selfp = (LLPanelGroupBulkImpl*)userdata; - if (selfp) - selfp->handleRemove(); -} - -void LLPanelGroupBulkImpl::callbackClickCancel(void* userdata) -{ - LLPanelGroupBulkImpl* selfp = (LLPanelGroupBulkImpl*)userdata; - if(selfp) - (*(selfp->mCloseCallback))(selfp->mCloseCallbackUserData); -} - -void LLPanelGroupBulkImpl::callbackSelect(LLUICtrl* ctrl, void* userdata) -{ - LLPanelGroupBulkImpl* selfp = (LLPanelGroupBulkImpl*)userdata; - if (selfp) - selfp->handleSelection(); -} - -void LLPanelGroupBulkImpl::callbackAddUsers(const uuid_vec_t& agent_ids, void* user_data) -{ - std::vector names; - for (S32 i = 0; i < (S32)agent_ids.size(); i++) - { - LLAvatarName av_name; - if (LLAvatarNameCache::get(agent_ids[i], &av_name)) - { - onAvatarNameCache(agent_ids[i], av_name, user_data); - } - else - { - LLPanelGroupBulkImpl* selfp = (LLPanelGroupBulkImpl*) user_data; - if (selfp) - { - if (selfp->mAvatarNameCacheConnection.connected()) - { - selfp->mAvatarNameCacheConnection.disconnect(); - } - // *TODO : Add a callback per avatar name being fetched. - selfp->mAvatarNameCacheConnection = LLAvatarNameCache::get(agent_ids[i],boost::bind(onAvatarNameCache, _1, _2, user_data)); - } - } - } -} - -void LLPanelGroupBulkImpl::onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name, void* user_data) -{ - LLPanelGroupBulkImpl* selfp = (LLPanelGroupBulkImpl*) user_data; - - if (selfp) - { - if (selfp->mAvatarNameCacheConnection.connected()) - { - selfp->mAvatarNameCacheConnection.disconnect(); - } - std::vector names; - uuid_vec_t agent_ids; - agent_ids.push_back(agent_id); - names.push_back(av_name.getCompleteName()); - - selfp->addUsers(names, agent_ids); - } -} - -void LLPanelGroupBulkImpl::handleRemove() -{ - std::vector selection = mBulkAgentList->getAllSelected(); - if (selection.empty()) - return; - - std::vector::iterator iter; - for(iter = selection.begin(); iter != selection.end(); ++iter) - { - mInviteeIDs.erase((*iter)->getUUID()); - } - - mBulkAgentList->deleteSelectedItems(); - mRemoveButton->setEnabled(false); - - if( mOKButton && mOKButton->getEnabled() && - mBulkAgentList->isEmpty()) - { - mOKButton->setEnabled(false); - } -} - -void LLPanelGroupBulkImpl::handleSelection() -{ - std::vector selection = mBulkAgentList->getAllSelected(); - if (selection.empty()) - mRemoveButton->setEnabled(false); - else - mRemoveButton->setEnabled(true); -} - -void LLPanelGroupBulkImpl::addUsers(const std::vector& names, const uuid_vec_t& agent_ids) -{ - std::string name; - LLUUID id; - - if(mListFullNotificationSent) - { - return; - } - - if( !mListFullNotificationSent && - (names.size() + mInviteeIDs.size() > MAX_GROUP_INVITES)) - { - mListFullNotificationSent = true; - - // Fail! Show a warning and don't add any names. - LLSD msg; - msg["MESSAGE"] = mTooManySelected; - LLNotificationsUtil::add("GenericAlert", msg); - return; - } - - for (S32 i = 0; i < (S32)names.size(); ++i) - { - name = names[i]; - id = agent_ids[i]; - - if(mInviteeIDs.find(id) != mInviteeIDs.end()) - { - continue; - } - - //add the name to the names list - LLSD row; - row["id"] = id; - row["columns"][0]["value"] = name; - - mBulkAgentList->addElement(row); - mInviteeIDs.insert(id); - - // We've successfully added someone to the list. - if(mOKButton && !mOKButton->getEnabled()) - mOKButton->setEnabled(true); - } -} - -void LLPanelGroupBulkImpl::setGroupName(std::string name) -{ - if(mGroupName) - mGroupName->setText(name); -} - - -LLPanelGroupBulk::LLPanelGroupBulk(const LLUUID& group_id) : - LLPanel(), - mImplementation(new LLPanelGroupBulkImpl(group_id)), - mPendingGroupPropertiesUpdate(false), - mPendingRoleDataUpdate(false), - mPendingMemberDataUpdate(false) -{} - -LLPanelGroupBulk::~LLPanelGroupBulk() -{ - delete mImplementation; -} - -void LLPanelGroupBulk::clear() -{ - mImplementation->mInviteeIDs.clear(); - - if(mImplementation->mBulkAgentList) - mImplementation->mBulkAgentList->deleteAllItems(); - - if(mImplementation->mOKButton) - mImplementation->mOKButton->setEnabled(false); -} - -void LLPanelGroupBulk::update() -{ - updateGroupName(); - updateGroupData(); -} - -void LLPanelGroupBulk::draw() -{ - LLPanel::draw(); - update(); -} - -void LLPanelGroupBulk::updateGroupName() -{ - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mImplementation->mGroupID); - - if( gdatap && - gdatap->isGroupPropertiesDataComplete()) - { - // Only do work if the current group name differs - if(mImplementation->mGroupName->getText().compare(gdatap->mName) != 0) - mImplementation->setGroupName(gdatap->mName); - } - else - { - mImplementation->setGroupName(mImplementation->mLoadingText); - } -} - -void LLPanelGroupBulk::updateGroupData() -{ - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mImplementation->mGroupID); - if(gdatap && gdatap->isGroupPropertiesDataComplete()) - { - mPendingGroupPropertiesUpdate = false; - } - else - { - if(!mPendingGroupPropertiesUpdate) - { - mPendingGroupPropertiesUpdate = true; - LLGroupMgr::getInstance()->sendGroupPropertiesRequest(mImplementation->mGroupID); - } - } - - if(gdatap && gdatap->isRoleDataComplete()) - { - mPendingRoleDataUpdate = false; - } - else - { - if(!mPendingRoleDataUpdate) - { - mPendingRoleDataUpdate = true; - LLGroupMgr::getInstance()->sendGroupRoleDataRequest(mImplementation->mGroupID); - } - } - - if(gdatap && gdatap->isMemberDataComplete()) - { - mPendingMemberDataUpdate = false; - } - else - { - if(!mPendingMemberDataUpdate) - { - mPendingMemberDataUpdate = true; - LLGroupMgr::getInstance()->sendCapGroupMembersRequest(mImplementation->mGroupID); - } - } -} - -void LLPanelGroupBulk::addUserCallback(const LLUUID& id, const LLAvatarName& av_name) -{ - std::vector names; - uuid_vec_t agent_ids; - agent_ids.push_back(id); - names.push_back(av_name.getAccountName()); - - mImplementation->addUsers(names, agent_ids); -} - -void LLPanelGroupBulk::setCloseCallback(void (*close_callback)(void*), void* data) -{ - mImplementation->mCloseCallback = close_callback; - mImplementation->mCloseCallbackUserData = data; -} - -void LLPanelGroupBulk::addUsers(uuid_vec_t& agent_ids) -{ - std::vector names; - for (S32 i = 0; i < (S32)agent_ids.size(); i++) - { - std::string fullname; - LLUUID agent_id = agent_ids[i]; - LLViewerObject* dest = gObjectList.findObject(agent_id); - if(dest && dest->isAvatar()) - { - LLNameValue* nvfirst = dest->getNVPair("FirstName"); - LLNameValue* nvlast = dest->getNVPair("LastName"); - if(nvfirst && nvlast) - { - fullname = LLCacheName::buildFullName( - nvfirst->getString(), nvlast->getString()); - - } - if (!fullname.empty()) - { - names.push_back(fullname); - } - else - { - LL_WARNS() << "llPanelGroupBulk: Selected avatar has no name: " << dest->getID() << LL_ENDL; - names.push_back("(Unknown)"); - } - } - else - { - //looks like user try to invite offline friend - //for offline avatar_id gObjectList.findObject() will return null - //so we need to do this additional search in avatar tracker, see EXT-4732 - if (LLAvatarTracker::instance().isBuddy(agent_id)) - { - LLAvatarName av_name; - if (!LLAvatarNameCache::get(agent_id, &av_name)) - { - // actually it should happen, just in case - LLAvatarNameCache::get(LLUUID(agent_id), boost::bind(&LLPanelGroupBulk::addUserCallback, this, _1, _2)); - // for this special case! - //when there is no cached name we should remove resident from agent_ids list to avoid breaking of sequence - // removed id will be added in callback - agent_ids.erase(agent_ids.begin() + i); - } - else - { - names.push_back(av_name.getAccountName()); - } - } - } - } - mImplementation->mListFullNotificationSent = false; - mImplementation->addUsers(names, agent_ids); -} - +/** +* @file llpanelgroupbulk.cpp +* @brief Implementation of llpanelgroupbulk +* @author Baker@lindenlab.com +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2013, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelgroupbulk.h" +#include "llpanelgroupbulkimpl.h" + +#include "llagent.h" +#include "llavatarnamecache.h" +#include "llfloateravatarpicker.h" +#include "llbutton.h" +#include "llcallingcard.h" +#include "llcombobox.h" +#include "llgroupactions.h" +#include "llgroupmgr.h" +#include "llnamelistctrl.h" +#include "llnotificationsutil.h" +#include "llscrolllistitem.h" +#include "llspinctrl.h" +#include "lltextbox.h" +#include "llviewerobject.h" +#include "llviewerobjectlist.h" +#include "lluictrlfactory.h" +#include "llviewerwindow.h" + + +////////////////////////////////////////////////////////////////////////// +// Implementation of llpanelgroupbulkimpl.h functions +////////////////////////////////////////////////////////////////////////// +LLPanelGroupBulkImpl::LLPanelGroupBulkImpl(const LLUUID& group_id) : + mGroupID(group_id), + mBulkAgentList(NULL), + mOKButton(NULL), + mRemoveButton(NULL), + mGroupName(NULL), + mLoadingText(), + mTooManySelected(), + mCloseCallback(NULL), + mCloseCallbackUserData(NULL), + mAvatarNameCacheConnection(), + mRoleNames(NULL), + mOwnerWarning(), + mAlreadyInGroup(), + mConfirmedOwnerInvite(false), + mListFullNotificationSent(false) +{} + +LLPanelGroupBulkImpl::~LLPanelGroupBulkImpl() +{ + if(mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } +} + +void LLPanelGroupBulkImpl::callbackClickAdd(void* userdata) +{ + LLPanelGroupBulk* panelp = (LLPanelGroupBulk*)userdata; + + if(panelp) + { + //Right now this is hard coded with some knowledge that it is part + //of a floater since the avatar picker needs to be added as a dependent + //floater to the parent floater. + //Soon the avatar picker will be embedded into this panel + //instead of being it's own separate floater. But that is next week. + //This will do for now. -jwolk May 10, 2006 + LLView* button = panelp->findChild("add_button"); + LLFloater* root_floater = gFloaterView->getParentFloater(panelp); + LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show( + boost::bind(callbackAddUsers, _1, panelp->mImplementation), true, false, false, root_floater->getName(), button); + if(picker) + { + root_floater->addDependentFloater(picker); + LLGroupMgr::getInstance()->sendCapGroupMembersRequest(panelp->mImplementation->mGroupID); + } + } +} + +void LLPanelGroupBulkImpl::callbackClickRemove(void* userdata) +{ + LLPanelGroupBulkImpl* selfp = (LLPanelGroupBulkImpl*)userdata; + if (selfp) + selfp->handleRemove(); +} + +void LLPanelGroupBulkImpl::callbackClickCancel(void* userdata) +{ + LLPanelGroupBulkImpl* selfp = (LLPanelGroupBulkImpl*)userdata; + if(selfp) + (*(selfp->mCloseCallback))(selfp->mCloseCallbackUserData); +} + +void LLPanelGroupBulkImpl::callbackSelect(LLUICtrl* ctrl, void* userdata) +{ + LLPanelGroupBulkImpl* selfp = (LLPanelGroupBulkImpl*)userdata; + if (selfp) + selfp->handleSelection(); +} + +void LLPanelGroupBulkImpl::callbackAddUsers(const uuid_vec_t& agent_ids, void* user_data) +{ + std::vector names; + for (S32 i = 0; i < (S32)agent_ids.size(); i++) + { + LLAvatarName av_name; + if (LLAvatarNameCache::get(agent_ids[i], &av_name)) + { + onAvatarNameCache(agent_ids[i], av_name, user_data); + } + else + { + LLPanelGroupBulkImpl* selfp = (LLPanelGroupBulkImpl*) user_data; + if (selfp) + { + if (selfp->mAvatarNameCacheConnection.connected()) + { + selfp->mAvatarNameCacheConnection.disconnect(); + } + // *TODO : Add a callback per avatar name being fetched. + selfp->mAvatarNameCacheConnection = LLAvatarNameCache::get(agent_ids[i],boost::bind(onAvatarNameCache, _1, _2, user_data)); + } + } + } +} + +void LLPanelGroupBulkImpl::onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name, void* user_data) +{ + LLPanelGroupBulkImpl* selfp = (LLPanelGroupBulkImpl*) user_data; + + if (selfp) + { + if (selfp->mAvatarNameCacheConnection.connected()) + { + selfp->mAvatarNameCacheConnection.disconnect(); + } + std::vector names; + uuid_vec_t agent_ids; + agent_ids.push_back(agent_id); + names.push_back(av_name.getCompleteName()); + + selfp->addUsers(names, agent_ids); + } +} + +void LLPanelGroupBulkImpl::handleRemove() +{ + std::vector selection = mBulkAgentList->getAllSelected(); + if (selection.empty()) + return; + + std::vector::iterator iter; + for(iter = selection.begin(); iter != selection.end(); ++iter) + { + mInviteeIDs.erase((*iter)->getUUID()); + } + + mBulkAgentList->deleteSelectedItems(); + mRemoveButton->setEnabled(false); + + if( mOKButton && mOKButton->getEnabled() && + mBulkAgentList->isEmpty()) + { + mOKButton->setEnabled(false); + } +} + +void LLPanelGroupBulkImpl::handleSelection() +{ + std::vector selection = mBulkAgentList->getAllSelected(); + if (selection.empty()) + mRemoveButton->setEnabled(false); + else + mRemoveButton->setEnabled(true); +} + +void LLPanelGroupBulkImpl::addUsers(const std::vector& names, const uuid_vec_t& agent_ids) +{ + std::string name; + LLUUID id; + + if(mListFullNotificationSent) + { + return; + } + + if( !mListFullNotificationSent && + (names.size() + mInviteeIDs.size() > MAX_GROUP_INVITES)) + { + mListFullNotificationSent = true; + + // Fail! Show a warning and don't add any names. + LLSD msg; + msg["MESSAGE"] = mTooManySelected; + LLNotificationsUtil::add("GenericAlert", msg); + return; + } + + for (S32 i = 0; i < (S32)names.size(); ++i) + { + name = names[i]; + id = agent_ids[i]; + + if(mInviteeIDs.find(id) != mInviteeIDs.end()) + { + continue; + } + + //add the name to the names list + LLSD row; + row["id"] = id; + row["columns"][0]["value"] = name; + + mBulkAgentList->addElement(row); + mInviteeIDs.insert(id); + + // We've successfully added someone to the list. + if(mOKButton && !mOKButton->getEnabled()) + mOKButton->setEnabled(true); + } +} + +void LLPanelGroupBulkImpl::setGroupName(std::string name) +{ + if(mGroupName) + mGroupName->setText(name); +} + + +LLPanelGroupBulk::LLPanelGroupBulk(const LLUUID& group_id) : + LLPanel(), + mImplementation(new LLPanelGroupBulkImpl(group_id)), + mPendingGroupPropertiesUpdate(false), + mPendingRoleDataUpdate(false), + mPendingMemberDataUpdate(false) +{} + +LLPanelGroupBulk::~LLPanelGroupBulk() +{ + delete mImplementation; +} + +void LLPanelGroupBulk::clear() +{ + mImplementation->mInviteeIDs.clear(); + + if(mImplementation->mBulkAgentList) + mImplementation->mBulkAgentList->deleteAllItems(); + + if(mImplementation->mOKButton) + mImplementation->mOKButton->setEnabled(false); +} + +void LLPanelGroupBulk::update() +{ + updateGroupName(); + updateGroupData(); +} + +void LLPanelGroupBulk::draw() +{ + LLPanel::draw(); + update(); +} + +void LLPanelGroupBulk::updateGroupName() +{ + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mImplementation->mGroupID); + + if( gdatap && + gdatap->isGroupPropertiesDataComplete()) + { + // Only do work if the current group name differs + if(mImplementation->mGroupName->getText().compare(gdatap->mName) != 0) + mImplementation->setGroupName(gdatap->mName); + } + else + { + mImplementation->setGroupName(mImplementation->mLoadingText); + } +} + +void LLPanelGroupBulk::updateGroupData() +{ + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mImplementation->mGroupID); + if(gdatap && gdatap->isGroupPropertiesDataComplete()) + { + mPendingGroupPropertiesUpdate = false; + } + else + { + if(!mPendingGroupPropertiesUpdate) + { + mPendingGroupPropertiesUpdate = true; + LLGroupMgr::getInstance()->sendGroupPropertiesRequest(mImplementation->mGroupID); + } + } + + if(gdatap && gdatap->isRoleDataComplete()) + { + mPendingRoleDataUpdate = false; + } + else + { + if(!mPendingRoleDataUpdate) + { + mPendingRoleDataUpdate = true; + LLGroupMgr::getInstance()->sendGroupRoleDataRequest(mImplementation->mGroupID); + } + } + + if(gdatap && gdatap->isMemberDataComplete()) + { + mPendingMemberDataUpdate = false; + } + else + { + if(!mPendingMemberDataUpdate) + { + mPendingMemberDataUpdate = true; + LLGroupMgr::getInstance()->sendCapGroupMembersRequest(mImplementation->mGroupID); + } + } +} + +void LLPanelGroupBulk::addUserCallback(const LLUUID& id, const LLAvatarName& av_name) +{ + std::vector names; + uuid_vec_t agent_ids; + agent_ids.push_back(id); + names.push_back(av_name.getAccountName()); + + mImplementation->addUsers(names, agent_ids); +} + +void LLPanelGroupBulk::setCloseCallback(void (*close_callback)(void*), void* data) +{ + mImplementation->mCloseCallback = close_callback; + mImplementation->mCloseCallbackUserData = data; +} + +void LLPanelGroupBulk::addUsers(uuid_vec_t& agent_ids) +{ + std::vector names; + for (S32 i = 0; i < (S32)agent_ids.size(); i++) + { + std::string fullname; + LLUUID agent_id = agent_ids[i]; + LLViewerObject* dest = gObjectList.findObject(agent_id); + if(dest && dest->isAvatar()) + { + LLNameValue* nvfirst = dest->getNVPair("FirstName"); + LLNameValue* nvlast = dest->getNVPair("LastName"); + if(nvfirst && nvlast) + { + fullname = LLCacheName::buildFullName( + nvfirst->getString(), nvlast->getString()); + + } + if (!fullname.empty()) + { + names.push_back(fullname); + } + else + { + LL_WARNS() << "llPanelGroupBulk: Selected avatar has no name: " << dest->getID() << LL_ENDL; + names.push_back("(Unknown)"); + } + } + else + { + //looks like user try to invite offline friend + //for offline avatar_id gObjectList.findObject() will return null + //so we need to do this additional search in avatar tracker, see EXT-4732 + if (LLAvatarTracker::instance().isBuddy(agent_id)) + { + LLAvatarName av_name; + if (!LLAvatarNameCache::get(agent_id, &av_name)) + { + // actually it should happen, just in case + LLAvatarNameCache::get(LLUUID(agent_id), boost::bind(&LLPanelGroupBulk::addUserCallback, this, _1, _2)); + // for this special case! + //when there is no cached name we should remove resident from agent_ids list to avoid breaking of sequence + // removed id will be added in callback + agent_ids.erase(agent_ids.begin() + i); + } + else + { + names.push_back(av_name.getAccountName()); + } + } + } + } + mImplementation->mListFullNotificationSent = false; + mImplementation->addUsers(names, agent_ids); +} + diff --git a/indra/newview/llpanelgroupbulkban.cpp b/indra/newview/llpanelgroupbulkban.cpp index 4972de7f11..3c764887a6 100644 --- a/indra/newview/llpanelgroupbulkban.cpp +++ b/indra/newview/llpanelgroupbulkban.cpp @@ -1,256 +1,256 @@ -/** -* @file llpanelgroupbulkban.cpp -* -* $LicenseInfo:firstyear=2013&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2013, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelgroupbulkban.h" -#include "llpanelgroupbulk.h" -#include "llpanelgroupbulkimpl.h" - -#include "llagent.h" -#include "llavatarnamecache.h" -#include "llavataractions.h" -#include "llfloateravatarpicker.h" -#include "llbutton.h" -#include "llcallingcard.h" -#include "llcombobox.h" -#include "llgroupactions.h" -#include "llgroupmgr.h" -#include "llnamelistctrl.h" -#include "llnotificationsutil.h" -#include "llscrolllistitem.h" -#include "llslurl.h" -#include "llspinctrl.h" -#include "lltextbox.h" -#include "llviewerobject.h" -#include "llviewerobjectlist.h" -#include "lluictrlfactory.h" -#include "llviewerwindow.h" - -LLPanelGroupBulkBan::LLPanelGroupBulkBan(const LLUUID& group_id) : LLPanelGroupBulk(group_id) -{ - // Pass on construction of this panel to the control factory. - buildFromFile( "panel_group_bulk_ban.xml"); -} - -bool LLPanelGroupBulkBan::postBuild() -{ - constexpr bool recurse = true; - - mImplementation->mLoadingText = getString("loading"); - mImplementation->mGroupName = getChild("group_name_text", recurse); - mImplementation->mBulkAgentList = getChild("banned_agent_list", recurse); - if ( mImplementation->mBulkAgentList ) - { - mImplementation->mBulkAgentList->setCommitOnSelectionChange(true); - mImplementation->mBulkAgentList->setCommitCallback(LLPanelGroupBulkImpl::callbackSelect, mImplementation); - } - - LLButton* button = getChild("add_button", recurse); - if ( button ) - { - // default to opening avatarpicker automatically - // (*impl::callbackClickAdd)((void*)this); - button->setClickedCallback(LLPanelGroupBulkImpl::callbackClickAdd, this); - } - - mImplementation->mRemoveButton = - getChild("remove_button", recurse); - if ( mImplementation->mRemoveButton ) - { - mImplementation->mRemoveButton->setClickedCallback(LLPanelGroupBulkImpl::callbackClickRemove, mImplementation); - mImplementation->mRemoveButton->setEnabled(false); - } - - mImplementation->mOKButton = - getChild("ban_button", recurse); - if ( mImplementation->mOKButton ) - { - mImplementation->mOKButton->setClickedCallback(LLPanelGroupBulkBan::callbackClickSubmit, this); - mImplementation->mOKButton->setEnabled(false); - } - - button = getChild("cancel_button", recurse); - if ( button ) - { - button->setClickedCallback(LLPanelGroupBulkImpl::callbackClickCancel, mImplementation); - } - - mImplementation->mTooManySelected = getString("ban_selection_too_large"); - mImplementation->mBanNotPermitted = getString("ban_not_permitted"); - mImplementation->mBanLimitFail = getString("ban_limit_fail"); - mImplementation->mCannotBanYourself = getString("cant_ban_yourself"); - - update(); - return true; -} - -// TODO: Refactor the shitty callback functions with void* -- just use boost::bind to call submit() instead. -void LLPanelGroupBulkBan::callbackClickSubmit(void* userdata) -{ - LLPanelGroupBulkBan* selfp = (LLPanelGroupBulkBan*)userdata; - - if(selfp) - selfp->submit(); -} - - -void LLPanelGroupBulkBan::submit() -{ - if (!gAgent.hasPowerInGroup(mImplementation->mGroupID, GP_GROUP_BAN_ACCESS)) - { - // Fail! Agent no longer have ban rights. Permissions could have changed after button was pressed. - LLSD msg; - msg["MESSAGE"] = mImplementation->mBanNotPermitted; - LLNotificationsUtil::add("GenericAlert", msg); - (*(mImplementation->mCloseCallback))(mImplementation->mCloseCallbackUserData); - return; - } - LLGroupMgrGroupData * group_datap = LLGroupMgr::getInstance()->getGroupData(mImplementation->mGroupID); - if (group_datap && group_datap->mBanList.size() >= GB_MAX_BANNED_AGENTS) - { - // Fail! Size limit exceeded. List could have updated after button was pressed. - LLSD msg; - msg["MESSAGE"] = mImplementation->mBanLimitFail; - LLNotificationsUtil::add("GenericAlert", msg); - (*(mImplementation->mCloseCallback))(mImplementation->mCloseCallbackUserData); - return; - } - std::vector banned_agent_list; - std::vector agents = mImplementation->mBulkAgentList->getAllData(); - std::vector::iterator iter = agents.begin(); - for(;iter != agents.end(); ++iter) - { - LLScrollListItem* agent = *iter; - banned_agent_list.push_back(agent->getUUID()); - } - - const S32 MAX_BANS_PER_REQUEST = 100; // Max bans per request. 100 to match server cap. - if (banned_agent_list.size() > MAX_BANS_PER_REQUEST) - { - // Fail! - LLSD msg; - msg["MESSAGE"] = mImplementation->mTooManySelected; - LLNotificationsUtil::add("GenericAlert", msg); - (*(mImplementation->mCloseCallback))(mImplementation->mCloseCallbackUserData); - return; - } - - // remove already banned users and yourself from request. - std::vector banned_avatar_names; - std::vector out_of_limit_names; - bool banning_self = false; - std::vector::iterator conflict = std::find(banned_agent_list.begin(), banned_agent_list.end(), gAgent.getID()); - if (conflict != banned_agent_list.end()) - { - banned_agent_list.erase(conflict); - banning_self = true; - } - if (group_datap) - { - for (const auto& [group_ban_agent_id, group_ban_data] : group_datap->mBanList) - { - std::vector::iterator conflict = std::find(banned_agent_list.begin(), banned_agent_list.end(), group_ban_agent_id); - if (conflict != banned_agent_list.end()) - { - LLAvatarName av_name; - LLAvatarNameCache::get(group_ban_agent_id, &av_name); - banned_avatar_names.emplace_back(av_name); - - banned_agent_list.erase(conflict); - if (banned_agent_list.empty()) - { - break; - } - } - } - // this check should always be the last one before we send the request. - // Otherwise we have a possibility of cutting more then we need to. - if (banned_agent_list.size() > GB_MAX_BANNED_AGENTS - group_datap->mBanList.size()) - { - std::vector::iterator exeedes_limit = banned_agent_list.begin() + GB_MAX_BANNED_AGENTS - group_datap->mBanList.size(); - for (std::vector::iterator itor = exeedes_limit ; - itor != banned_agent_list.end(); ++itor) - { - LLAvatarName av_name; - LLAvatarNameCache::get(*itor, &av_name); - out_of_limit_names.push_back(av_name); - } - banned_agent_list.erase(exeedes_limit,banned_agent_list.end()); - } - } - - // sending request and ejecting members - if (banned_agent_list.size() != 0) - { - LLGroupMgr::getInstance()->sendGroupBanRequest(LLGroupMgr::REQUEST_POST, mImplementation->mGroupID, LLGroupMgr::BAN_CREATE | LLGroupMgr::BAN_UPDATE, banned_agent_list); - LLGroupMgr::getInstance()->sendGroupMemberEjects(mImplementation->mGroupID, banned_agent_list); - } - - // building notification - if (banned_avatar_names.size() > 0 || banning_self || out_of_limit_names.size() > 0) - { - std::string reasons; - if(banned_avatar_names.size() > 0) - { - reasons = "\n " + buildResidentsArgument(banned_avatar_names, "residents_already_banned"); - } - - if(banning_self) - { - reasons += "\n " + mImplementation->mCannotBanYourself; - } - - if(out_of_limit_names.size() > 0) - { - reasons += "\n " + buildResidentsArgument(out_of_limit_names, "ban_limit_reached"); - } - - LLStringUtil::format_map_t msg_args; - msg_args["[REASONS]"] = reasons; - LLSD msg; - if (banned_agent_list.size() == 0) - { - msg["MESSAGE"] = getString("ban_failed", msg_args); - } - else - { - msg["MESSAGE"] = getString("partial_ban", msg_args); - } - LLNotificationsUtil::add("GenericAlert", msg); - } - - //then close - (*(mImplementation->mCloseCallback))(mImplementation->mCloseCallbackUserData); -} - -std::string LLPanelGroupBulkBan::buildResidentsArgument(std::vector avatar_names, const std::string &format) -{ - std::string names_string; - LLAvatarActions::buildResidentsString(avatar_names, names_string); - LLStringUtil::format_map_t args; - args["[RESIDENTS]"] = names_string; - return getString(format, args); -} +/** +* @file llpanelgroupbulkban.cpp +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2013, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelgroupbulkban.h" +#include "llpanelgroupbulk.h" +#include "llpanelgroupbulkimpl.h" + +#include "llagent.h" +#include "llavatarnamecache.h" +#include "llavataractions.h" +#include "llfloateravatarpicker.h" +#include "llbutton.h" +#include "llcallingcard.h" +#include "llcombobox.h" +#include "llgroupactions.h" +#include "llgroupmgr.h" +#include "llnamelistctrl.h" +#include "llnotificationsutil.h" +#include "llscrolllistitem.h" +#include "llslurl.h" +#include "llspinctrl.h" +#include "lltextbox.h" +#include "llviewerobject.h" +#include "llviewerobjectlist.h" +#include "lluictrlfactory.h" +#include "llviewerwindow.h" + +LLPanelGroupBulkBan::LLPanelGroupBulkBan(const LLUUID& group_id) : LLPanelGroupBulk(group_id) +{ + // Pass on construction of this panel to the control factory. + buildFromFile( "panel_group_bulk_ban.xml"); +} + +bool LLPanelGroupBulkBan::postBuild() +{ + constexpr bool recurse = true; + + mImplementation->mLoadingText = getString("loading"); + mImplementation->mGroupName = getChild("group_name_text", recurse); + mImplementation->mBulkAgentList = getChild("banned_agent_list", recurse); + if ( mImplementation->mBulkAgentList ) + { + mImplementation->mBulkAgentList->setCommitOnSelectionChange(true); + mImplementation->mBulkAgentList->setCommitCallback(LLPanelGroupBulkImpl::callbackSelect, mImplementation); + } + + LLButton* button = getChild("add_button", recurse); + if ( button ) + { + // default to opening avatarpicker automatically + // (*impl::callbackClickAdd)((void*)this); + button->setClickedCallback(LLPanelGroupBulkImpl::callbackClickAdd, this); + } + + mImplementation->mRemoveButton = + getChild("remove_button", recurse); + if ( mImplementation->mRemoveButton ) + { + mImplementation->mRemoveButton->setClickedCallback(LLPanelGroupBulkImpl::callbackClickRemove, mImplementation); + mImplementation->mRemoveButton->setEnabled(false); + } + + mImplementation->mOKButton = + getChild("ban_button", recurse); + if ( mImplementation->mOKButton ) + { + mImplementation->mOKButton->setClickedCallback(LLPanelGroupBulkBan::callbackClickSubmit, this); + mImplementation->mOKButton->setEnabled(false); + } + + button = getChild("cancel_button", recurse); + if ( button ) + { + button->setClickedCallback(LLPanelGroupBulkImpl::callbackClickCancel, mImplementation); + } + + mImplementation->mTooManySelected = getString("ban_selection_too_large"); + mImplementation->mBanNotPermitted = getString("ban_not_permitted"); + mImplementation->mBanLimitFail = getString("ban_limit_fail"); + mImplementation->mCannotBanYourself = getString("cant_ban_yourself"); + + update(); + return true; +} + +// TODO: Refactor the shitty callback functions with void* -- just use boost::bind to call submit() instead. +void LLPanelGroupBulkBan::callbackClickSubmit(void* userdata) +{ + LLPanelGroupBulkBan* selfp = (LLPanelGroupBulkBan*)userdata; + + if(selfp) + selfp->submit(); +} + + +void LLPanelGroupBulkBan::submit() +{ + if (!gAgent.hasPowerInGroup(mImplementation->mGroupID, GP_GROUP_BAN_ACCESS)) + { + // Fail! Agent no longer have ban rights. Permissions could have changed after button was pressed. + LLSD msg; + msg["MESSAGE"] = mImplementation->mBanNotPermitted; + LLNotificationsUtil::add("GenericAlert", msg); + (*(mImplementation->mCloseCallback))(mImplementation->mCloseCallbackUserData); + return; + } + LLGroupMgrGroupData * group_datap = LLGroupMgr::getInstance()->getGroupData(mImplementation->mGroupID); + if (group_datap && group_datap->mBanList.size() >= GB_MAX_BANNED_AGENTS) + { + // Fail! Size limit exceeded. List could have updated after button was pressed. + LLSD msg; + msg["MESSAGE"] = mImplementation->mBanLimitFail; + LLNotificationsUtil::add("GenericAlert", msg); + (*(mImplementation->mCloseCallback))(mImplementation->mCloseCallbackUserData); + return; + } + std::vector banned_agent_list; + std::vector agents = mImplementation->mBulkAgentList->getAllData(); + std::vector::iterator iter = agents.begin(); + for(;iter != agents.end(); ++iter) + { + LLScrollListItem* agent = *iter; + banned_agent_list.push_back(agent->getUUID()); + } + + const S32 MAX_BANS_PER_REQUEST = 100; // Max bans per request. 100 to match server cap. + if (banned_agent_list.size() > MAX_BANS_PER_REQUEST) + { + // Fail! + LLSD msg; + msg["MESSAGE"] = mImplementation->mTooManySelected; + LLNotificationsUtil::add("GenericAlert", msg); + (*(mImplementation->mCloseCallback))(mImplementation->mCloseCallbackUserData); + return; + } + + // remove already banned users and yourself from request. + std::vector banned_avatar_names; + std::vector out_of_limit_names; + bool banning_self = false; + std::vector::iterator conflict = std::find(banned_agent_list.begin(), banned_agent_list.end(), gAgent.getID()); + if (conflict != banned_agent_list.end()) + { + banned_agent_list.erase(conflict); + banning_self = true; + } + if (group_datap) + { + for (const auto& [group_ban_agent_id, group_ban_data] : group_datap->mBanList) + { + std::vector::iterator conflict = std::find(banned_agent_list.begin(), banned_agent_list.end(), group_ban_agent_id); + if (conflict != banned_agent_list.end()) + { + LLAvatarName av_name; + LLAvatarNameCache::get(group_ban_agent_id, &av_name); + banned_avatar_names.emplace_back(av_name); + + banned_agent_list.erase(conflict); + if (banned_agent_list.empty()) + { + break; + } + } + } + // this check should always be the last one before we send the request. + // Otherwise we have a possibility of cutting more then we need to. + if (banned_agent_list.size() > GB_MAX_BANNED_AGENTS - group_datap->mBanList.size()) + { + std::vector::iterator exeedes_limit = banned_agent_list.begin() + GB_MAX_BANNED_AGENTS - group_datap->mBanList.size(); + for (std::vector::iterator itor = exeedes_limit ; + itor != banned_agent_list.end(); ++itor) + { + LLAvatarName av_name; + LLAvatarNameCache::get(*itor, &av_name); + out_of_limit_names.push_back(av_name); + } + banned_agent_list.erase(exeedes_limit,banned_agent_list.end()); + } + } + + // sending request and ejecting members + if (banned_agent_list.size() != 0) + { + LLGroupMgr::getInstance()->sendGroupBanRequest(LLGroupMgr::REQUEST_POST, mImplementation->mGroupID, LLGroupMgr::BAN_CREATE | LLGroupMgr::BAN_UPDATE, banned_agent_list); + LLGroupMgr::getInstance()->sendGroupMemberEjects(mImplementation->mGroupID, banned_agent_list); + } + + // building notification + if (banned_avatar_names.size() > 0 || banning_self || out_of_limit_names.size() > 0) + { + std::string reasons; + if(banned_avatar_names.size() > 0) + { + reasons = "\n " + buildResidentsArgument(banned_avatar_names, "residents_already_banned"); + } + + if(banning_self) + { + reasons += "\n " + mImplementation->mCannotBanYourself; + } + + if(out_of_limit_names.size() > 0) + { + reasons += "\n " + buildResidentsArgument(out_of_limit_names, "ban_limit_reached"); + } + + LLStringUtil::format_map_t msg_args; + msg_args["[REASONS]"] = reasons; + LLSD msg; + if (banned_agent_list.size() == 0) + { + msg["MESSAGE"] = getString("ban_failed", msg_args); + } + else + { + msg["MESSAGE"] = getString("partial_ban", msg_args); + } + LLNotificationsUtil::add("GenericAlert", msg); + } + + //then close + (*(mImplementation->mCloseCallback))(mImplementation->mCloseCallbackUserData); +} + +std::string LLPanelGroupBulkBan::buildResidentsArgument(std::vector avatar_names, const std::string &format) +{ + std::string names_string; + LLAvatarActions::buildResidentsString(avatar_names, names_string); + LLStringUtil::format_map_t args; + args["[RESIDENTS]"] = names_string; + return getString(format, args); +} diff --git a/indra/newview/llpanelgroupbulkban.h b/indra/newview/llpanelgroupbulkban.h index 324f606133..db4af1ccd3 100644 --- a/indra/newview/llpanelgroupbulkban.h +++ b/indra/newview/llpanelgroupbulkban.h @@ -1,49 +1,49 @@ -/** -* @file llpanelgroupbulkban.h -* -* $LicenseInfo:firstyear=2013&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2013, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#ifndef LL_LLPANELGROUPBULKBAN_H -#define LL_LLPANELGROUPBULKBAN_H - -#include "llpanel.h" -#include "lluuid.h" -#include "llpanelgroupbulk.h" - -class LLAvatarName; - -class LLPanelGroupBulkBan : public LLPanelGroupBulk -{ -public: - LLPanelGroupBulkBan(const LLUUID& group_id); - ~LLPanelGroupBulkBan() {} - - virtual bool postBuild(); - - static void callbackClickSubmit(void* userdata); - virtual void submit(); -private: - std::string buildResidentsArgument(std::vector avatar_names, const std::string &format); -}; - -#endif // LL_LLPANELGROUPBULKBAN_H +/** +* @file llpanelgroupbulkban.h +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2013, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#ifndef LL_LLPANELGROUPBULKBAN_H +#define LL_LLPANELGROUPBULKBAN_H + +#include "llpanel.h" +#include "lluuid.h" +#include "llpanelgroupbulk.h" + +class LLAvatarName; + +class LLPanelGroupBulkBan : public LLPanelGroupBulk +{ +public: + LLPanelGroupBulkBan(const LLUUID& group_id); + ~LLPanelGroupBulkBan() {} + + virtual bool postBuild(); + + static void callbackClickSubmit(void* userdata); + virtual void submit(); +private: + std::string buildResidentsArgument(std::vector avatar_names, const std::string &format); +}; + +#endif // LL_LLPANELGROUPBULKBAN_H diff --git a/indra/newview/llpanelgroupexperiences.cpp b/indra/newview/llpanelgroupexperiences.cpp index 2f06794e65..99c40984a5 100644 --- a/indra/newview/llpanelgroupexperiences.cpp +++ b/indra/newview/llpanelgroupexperiences.cpp @@ -1,124 +1,124 @@ -/** - * @file llpanelgroupexperiences.cpp - * @brief List of experiences owned by a group. - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelgroupexperiences.h" - -#include "lluictrlfactory.h" -#include "roles_constants.h" -#include "llappviewer.h" -#include "llagent.h" -#include "llviewerregion.h" -#include "llflatlistview.h" -#include "llpanelexperiences.h" -#include "llsd.h" -#include "llexperiencecache.h" - -static LLPanelInjector t_panel_group_experiences("panel_group_experiences"); - - -LLPanelGroupExperiences::LLPanelGroupExperiences() -: LLPanelGroupTab(), mExperiencesList(NULL) -{ -} - -LLPanelGroupExperiences::~LLPanelGroupExperiences() -{ -} - -bool LLPanelGroupExperiences::postBuild() -{ - mExperiencesList = getChild("experiences_list"); - if (hasString("loading_experiences")) - { - mExperiencesList->setNoItemsCommentText(getString("loading_experiences")); - } - else if (hasString("no_experiences")) - { - mExperiencesList->setNoItemsCommentText(getString("no_experiences")); - } - - return LLPanelGroupTab::postBuild(); -} - -void LLPanelGroupExperiences::activate() -{ - if ((getGroupID() == LLUUID::null) || gDisconnected) - { - return; - } - - LLExperienceCache::instance().getGroupExperiences(getGroupID(), - boost::bind(&LLPanelGroupExperiences::groupExperiencesResults, getDerivedHandle(), _1)); -} - -void LLPanelGroupExperiences::setGroupID(const LLUUID& id) -{ - LLPanelGroupTab::setGroupID(id); - - if(id == LLUUID::null) - { - return; - } - - activate(); -} - -void LLPanelGroupExperiences::setExperienceList(const LLSD& experiences) -{ - if (hasString("no_experiences")) - { - mExperiencesList->setNoItemsCommentText(getString("no_experiences")); - } - mExperiencesList->clear(); - - LLSD::array_const_iterator it = experiences.beginArray(); - for ( /**/ ; it != experiences.endArray(); ++it) - { - LLUUID public_key = it->asUUID(); - LLExperienceItem* item = new LLExperienceItem(); - - item->init(public_key); - mExperiencesList->addItem(item, public_key); - } -} - -/*static*/ -void LLPanelGroupExperiences::groupExperiencesResults(LLHandle handle, const LLSD &experiences) -{ - if (handle.isDead()) - { - return; - } - - LLPanelGroupExperiences* panel = handle.get(); - if (panel) - { - panel->setExperienceList(experiences); - } - -} +/** + * @file llpanelgroupexperiences.cpp + * @brief List of experiences owned by a group. + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelgroupexperiences.h" + +#include "lluictrlfactory.h" +#include "roles_constants.h" +#include "llappviewer.h" +#include "llagent.h" +#include "llviewerregion.h" +#include "llflatlistview.h" +#include "llpanelexperiences.h" +#include "llsd.h" +#include "llexperiencecache.h" + +static LLPanelInjector t_panel_group_experiences("panel_group_experiences"); + + +LLPanelGroupExperiences::LLPanelGroupExperiences() +: LLPanelGroupTab(), mExperiencesList(NULL) +{ +} + +LLPanelGroupExperiences::~LLPanelGroupExperiences() +{ +} + +bool LLPanelGroupExperiences::postBuild() +{ + mExperiencesList = getChild("experiences_list"); + if (hasString("loading_experiences")) + { + mExperiencesList->setNoItemsCommentText(getString("loading_experiences")); + } + else if (hasString("no_experiences")) + { + mExperiencesList->setNoItemsCommentText(getString("no_experiences")); + } + + return LLPanelGroupTab::postBuild(); +} + +void LLPanelGroupExperiences::activate() +{ + if ((getGroupID() == LLUUID::null) || gDisconnected) + { + return; + } + + LLExperienceCache::instance().getGroupExperiences(getGroupID(), + boost::bind(&LLPanelGroupExperiences::groupExperiencesResults, getDerivedHandle(), _1)); +} + +void LLPanelGroupExperiences::setGroupID(const LLUUID& id) +{ + LLPanelGroupTab::setGroupID(id); + + if(id == LLUUID::null) + { + return; + } + + activate(); +} + +void LLPanelGroupExperiences::setExperienceList(const LLSD& experiences) +{ + if (hasString("no_experiences")) + { + mExperiencesList->setNoItemsCommentText(getString("no_experiences")); + } + mExperiencesList->clear(); + + LLSD::array_const_iterator it = experiences.beginArray(); + for ( /**/ ; it != experiences.endArray(); ++it) + { + LLUUID public_key = it->asUUID(); + LLExperienceItem* item = new LLExperienceItem(); + + item->init(public_key); + mExperiencesList->addItem(item, public_key); + } +} + +/*static*/ +void LLPanelGroupExperiences::groupExperiencesResults(LLHandle handle, const LLSD &experiences) +{ + if (handle.isDead()) + { + return; + } + + LLPanelGroupExperiences* panel = handle.get(); + if (panel) + { + panel->setExperienceList(experiences); + } + +} diff --git a/indra/newview/llpanelgroupexperiences.h b/indra/newview/llpanelgroupexperiences.h index 6417879d25..77c3750174 100644 --- a/indra/newview/llpanelgroupexperiences.h +++ b/indra/newview/llpanelgroupexperiences.h @@ -1,56 +1,56 @@ -/** - * @file llpanelgroupexperiences.h - * @brief List of experiences owned by a group. - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELGROUPEXPERIENCES_H -#define LL_LLPANELGROUPEXPERIENCES_H - -#include "llpanelgroup.h" - -class LLFlatListView; - -class LLPanelGroupExperiences : public LLPanelGroupTab -{ -public: - LLPanelGroupExperiences(); - virtual ~LLPanelGroupExperiences(); - - // LLPanelGroupTab - virtual void activate(); - - virtual bool postBuild(); - - virtual void setGroupID(const LLUUID& id); - - void setExperienceList(const LLSD& experiences); - -protected: - LLFlatListView* mExperiencesList; - -private: - static void groupExperiencesResults(LLHandle, const LLSD &); -}; - -#endif +/** + * @file llpanelgroupexperiences.h + * @brief List of experiences owned by a group. + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELGROUPEXPERIENCES_H +#define LL_LLPANELGROUPEXPERIENCES_H + +#include "llpanelgroup.h" + +class LLFlatListView; + +class LLPanelGroupExperiences : public LLPanelGroupTab +{ +public: + LLPanelGroupExperiences(); + virtual ~LLPanelGroupExperiences(); + + // LLPanelGroupTab + virtual void activate(); + + virtual bool postBuild(); + + virtual void setGroupID(const LLUUID& id); + + void setExperienceList(const LLSD& experiences); + +protected: + LLFlatListView* mExperiencesList; + +private: + static void groupExperiencesResults(LLHandle, const LLSD &); +}; + +#endif diff --git a/indra/newview/llpanelgroupgeneral.cpp b/indra/newview/llpanelgroupgeneral.cpp index e4635bdc81..ca429ae2f8 100644 --- a/indra/newview/llpanelgroupgeneral.cpp +++ b/indra/newview/llpanelgroupgeneral.cpp @@ -1,746 +1,746 @@ -/** - * @file llpanelgroupgeneral.cpp - * @brief General information about a group. - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelgroupgeneral.h" - -#include "llavatarnamecache.h" -#include "llagent.h" -#include "llagentbenefits.h" -#include "llsdparam.h" -#include "lluictrlfactory.h" -#include "roles_constants.h" - -// UI elements -#include "llbutton.h" -#include "llcheckboxctrl.h" -#include "llcombobox.h" -#include "lldbstrings.h" -#include "llavataractions.h" -#include "llgroupactions.h" -#include "lllineeditor.h" -#include "llnamelistctrl.h" -#include "llnotificationsutil.h" -#include "llscrolllistitem.h" -#include "llspinctrl.h" -#include "llslurl.h" -#include "lltextbox.h" -#include "lltexteditor.h" -#include "lltexturectrl.h" -#include "lltrans.h" -#include "llviewerwindow.h" - -static LLPanelInjector t_panel_group_general("panel_group_general"); - -// consts -const S32 MATURE_CONTENT = 1; -const S32 NON_MATURE_CONTENT = 2; -const S32 DECLINE_TO_STATE = 0; - - -LLPanelGroupGeneral::LLPanelGroupGeneral() -: LLPanelGroupTab(), - mChanged(false), - mFirstUse(true), - mGroupNameEditor(NULL), - mFounderName(NULL), - mInsignia(NULL), - mEditCharter(NULL), - mCtrlShowInGroupList(NULL), - mComboMature(NULL), - mCtrlOpenEnrollment(NULL), - mCtrlEnrollmentFee(NULL), - mSpinEnrollmentFee(NULL), - mCtrlReceiveNotices(NULL), - mCtrlListGroup(NULL), - mActiveTitleLabel(NULL), - mComboActiveTitle(NULL) -{ - -} - -LLPanelGroupGeneral::~LLPanelGroupGeneral() -{ -} - -bool LLPanelGroupGeneral::postBuild() -{ - constexpr bool recurse = true; - - mEditCharter = getChild("charter", recurse); - if(mEditCharter) - { - mEditCharter->setCommitCallback(onCommitAny, this); - mEditCharter->setFocusReceivedCallback(boost::bind(onFocusEdit, _1, this)); - mEditCharter->setFocusChangedCallback(boost::bind(onFocusEdit, _1, this)); - mEditCharter->setContentTrusted(false); - } - - // Options - mCtrlShowInGroupList = getChild("show_in_group_list", recurse); - if (mCtrlShowInGroupList) - { - mCtrlShowInGroupList->setCommitCallback(onCommitAny, this); - } - - mComboMature = getChild("group_mature_check", recurse); - if(mComboMature) - { - mComboMature->setCurrentByIndex(0); - mComboMature->setCommitCallback(onCommitAny, this); - if (gAgent.isTeen()) - { - // Teens don't get to set mature flag. JC - mComboMature->setVisible(false); - mComboMature->setCurrentByIndex(NON_MATURE_CONTENT); - } - } - mCtrlOpenEnrollment = getChild("open_enrollement", recurse); - if (mCtrlOpenEnrollment) - { - mCtrlOpenEnrollment->setCommitCallback(onCommitAny, this); - } - - mCtrlEnrollmentFee = getChild("check_enrollment_fee", recurse); - if (mCtrlEnrollmentFee) - { - mCtrlEnrollmentFee->setCommitCallback(onCommitEnrollment, this); - } - - mSpinEnrollmentFee = getChild("spin_enrollment_fee", recurse); - if (mSpinEnrollmentFee) - { - mSpinEnrollmentFee->setCommitCallback(onCommitAny, this); - mSpinEnrollmentFee->setPrecision(0); - mSpinEnrollmentFee->resetDirty(); - } - - bool accept_notices = false; - bool list_in_profile = false; - LLGroupData data; - if(gAgent.getGroupData(mGroupID,data)) - { - accept_notices = data.mAcceptNotices; - list_in_profile = data.mListInProfile; - } - mCtrlReceiveNotices = getChild("receive_notices", recurse); - if (mCtrlReceiveNotices) - { - mCtrlReceiveNotices->setCommitCallback(onCommitUserOnly, this); - mCtrlReceiveNotices->set(accept_notices); - mCtrlReceiveNotices->setEnabled(data.mID.notNull()); - } - - mCtrlListGroup = getChild("list_groups_in_profile", recurse); - if (mCtrlListGroup) - { - mCtrlListGroup->setCommitCallback(onCommitUserOnly, this); - mCtrlListGroup->set(list_in_profile); - mCtrlListGroup->setEnabled(data.mID.notNull()); - mCtrlListGroup->resetDirty(); - } - - mActiveTitleLabel = getChild("active_title_label", recurse); - - mComboActiveTitle = getChild("active_title", recurse); - if (mComboActiveTitle) - { - mComboActiveTitle->setCommitCallback(onCommitAny, this); - } - - mIncompleteMemberDataStr = getString("incomplete_member_data_str"); - - // If the group_id is null, then we are creating a new group - if (mGroupID.isNull()) - { - mEditCharter->setEnabled(true); - - mCtrlShowInGroupList->setEnabled(true); - mComboMature->setEnabled(true); - mCtrlOpenEnrollment->setEnabled(true); - mCtrlEnrollmentFee->setEnabled(true); - mSpinEnrollmentFee->setEnabled(true); - - } - - return LLPanelGroupTab::postBuild(); -} - -void LLPanelGroupGeneral::setupCtrls(LLPanel* panel_group) -{ - mInsignia = getChild("insignia"); - if (mInsignia) - { - mInsignia->setCommitCallback(onCommitAny, this); - mInsignia->setAllowLocalTexture(false); - } - mFounderName = getChild("founder_name"); - - - mGroupNameEditor = panel_group->getChild("group_name_editor"); - mGroupNameEditor->setPrevalidate( LLTextValidate::validateASCIINoLeadingSpace ); - - -} - -// static -void LLPanelGroupGeneral::onFocusEdit(LLFocusableElement* ctrl, void* data) -{ - LLPanelGroupGeneral* self = (LLPanelGroupGeneral*)data; - self->updateChanged(); - self->notifyObservers(); -} - -// static -void LLPanelGroupGeneral::onCommitAny(LLUICtrl* ctrl, void* data) -{ - LLPanelGroupGeneral* self = (LLPanelGroupGeneral*)data; - self->updateChanged(); - self->notifyObservers(); -} - -// static -void LLPanelGroupGeneral::onCommitUserOnly(LLUICtrl* ctrl, void* data) -{ - LLPanelGroupGeneral* self = (LLPanelGroupGeneral*)data; - self->mChanged = true; - self->notifyObservers(); -} - - -// static -void LLPanelGroupGeneral::onCommitEnrollment(LLUICtrl* ctrl, void* data) -{ - onCommitAny(ctrl, data); - - LLPanelGroupGeneral* self = (LLPanelGroupGeneral*)data; - // Make sure both enrollment related widgets are there. - if (!self->mCtrlEnrollmentFee || !self->mSpinEnrollmentFee) - { - return; - } - - // Make sure the agent can change enrollment info. - if (!gAgent.hasPowerInGroup(self->mGroupID,GP_MEMBER_OPTIONS) - || !self->mAllowEdit) - { - return; - } - - if (self->mCtrlEnrollmentFee->get()) - { - self->mSpinEnrollmentFee->setEnabled(true); - } - else - { - self->mSpinEnrollmentFee->setEnabled(false); - self->mSpinEnrollmentFee->set(0); - } -} - -// static -void LLPanelGroupGeneral::onClickInfo(void *userdata) -{ - LLPanelGroupGeneral *self = (LLPanelGroupGeneral *)userdata; - - if ( !self ) return; - - LL_DEBUGS() << "open group info: " << self->mGroupID << LL_ENDL; - - LLGroupActions::show(self->mGroupID); - -} - -bool LLPanelGroupGeneral::needsApply(std::string& mesg) -{ - updateChanged(); - mesg = getString("group_info_unchanged"); - return mChanged || mGroupID.isNull(); -} - -void LLPanelGroupGeneral::activate() -{ - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - if (mGroupID.notNull() - && (!gdatap || mFirstUse)) - { - LLGroupMgr::getInstance()->sendGroupTitlesRequest(mGroupID); - LLGroupMgr::getInstance()->sendGroupPropertiesRequest(mGroupID); - - mFirstUse = false; - } - mChanged = false; - - update(GC_ALL); -} - -void LLPanelGroupGeneral::draw() -{ - LLPanelGroupTab::draw(); -} - -bool LLPanelGroupGeneral::apply(std::string& mesg) -{ - if (mGroupID.isNull()) - { - return false; - } - - if (!mGroupID.isNull() && mAllowEdit && mComboActiveTitle && mComboActiveTitle->isDirty()) - { - LLGroupMgr::getInstance()->sendGroupTitleUpdate(mGroupID,mComboActiveTitle->getCurrentID()); - update(GC_TITLES); - mComboActiveTitle->resetDirty(); - } - - bool has_power_in_group = gAgent.hasPowerInGroup(mGroupID,GP_GROUP_CHANGE_IDENTITY); - - if (has_power_in_group) - { - LL_INFOS() << "LLPanelGroupGeneral::apply" << LL_ENDL; - - // Check to make sure mature has been set - if(mComboMature && - mComboMature->getCurrentIndex() == DECLINE_TO_STATE) - { - LLNotificationsUtil::add("SetGroupMature", LLSD(), LLSD(), - boost::bind(&LLPanelGroupGeneral::confirmMatureApply, this, _1, _2)); - return false; - } - - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - if (!gdatap) - { - mesg = LLTrans::getString("NoGroupDataFound"); - mesg.append(mGroupID.asString()); - return false; - } - bool can_change_ident = false; - bool can_change_member_opts = false; - can_change_ident = gAgent.hasPowerInGroup(mGroupID,GP_GROUP_CHANGE_IDENTITY); - can_change_member_opts = gAgent.hasPowerInGroup(mGroupID,GP_MEMBER_OPTIONS); - - if (can_change_ident) - { - if (mEditCharter) gdatap->mCharter = mEditCharter->getText(); - if (mInsignia) gdatap->mInsigniaID = mInsignia->getImageAssetID(); - if (mComboMature) - { - if (!gAgent.isTeen()) - { - gdatap->mMaturePublish = - mComboMature->getCurrentIndex() == MATURE_CONTENT; - } - else - { - gdatap->mMaturePublish = false; - } - } - if (mCtrlShowInGroupList) gdatap->mShowInList = mCtrlShowInGroupList->get(); - } - - if (can_change_member_opts) - { - if (mCtrlOpenEnrollment) gdatap->mOpenEnrollment = mCtrlOpenEnrollment->get(); - if (mCtrlEnrollmentFee && mSpinEnrollmentFee) - { - gdatap->mMembershipFee = (mCtrlEnrollmentFee->get()) ? - (S32) mSpinEnrollmentFee->get() : 0; - // Set to the used value, and reset initial value used for isdirty check - mSpinEnrollmentFee->set( (F32)gdatap->mMembershipFee ); - } - } - - if (can_change_ident || can_change_member_opts) - { - LLGroupMgr::getInstance()->sendUpdateGroupInfo(mGroupID); - } - } - - bool receive_notices = false; - bool list_in_profile = false; - if (mCtrlReceiveNotices) - receive_notices = mCtrlReceiveNotices->get(); - if (mCtrlListGroup) - list_in_profile = mCtrlListGroup->get(); - - gAgent.setUserGroupFlags(mGroupID, receive_notices, list_in_profile); - - resetDirty(); - - mChanged = false; - - return true; -} - -void LLPanelGroupGeneral::cancel() -{ - mChanged = false; - - //cancel out all of the click changes to, although since we are - //shifting tabs or closing the floater, this need not be done...yet - notifyObservers(); -} - -// invoked from callbackConfirmMature -bool LLPanelGroupGeneral::confirmMatureApply(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - // 0 == Yes - // 1 == No - // 2 == Cancel - switch(option) - { - case 0: - mComboMature->setCurrentByIndex(MATURE_CONTENT); - break; - case 1: - mComboMature->setCurrentByIndex(NON_MATURE_CONTENT); - break; - default: - return false; - } - - // If we got here it means they set a valid value - std::string mesg = ""; - bool ret = apply(mesg); - if ( !mesg.empty() ) - { - LLSD args; - args["MESSAGE"] = mesg; - LLNotificationsUtil::add("GenericAlert", args); - } - - return ret; -} - -// virtual -void LLPanelGroupGeneral::update(LLGroupChange gc) -{ - if (mGroupID.isNull()) return; - - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - - if (!gdatap) return; - - LLGroupData agent_gdatap; - bool is_member = false; - if (gAgent.getGroupData(mGroupID,agent_gdatap)) is_member = true; - - if (mComboActiveTitle) - { - mComboActiveTitle->setVisible(is_member); - mComboActiveTitle->setEnabled(mAllowEdit); - - if ( mActiveTitleLabel) mActiveTitleLabel->setVisible(is_member); - - if (is_member) - { - LLUUID current_title_role; - - mComboActiveTitle->clear(); - mComboActiveTitle->removeall(); - bool has_selected_title = false; - - if (1 == gdatap->mTitles.size()) - { - // Only the everyone title. Don't bother letting them try changing this. - mComboActiveTitle->setEnabled(false); - } - else - { - mComboActiveTitle->setEnabled(true); - } - - std::vector::const_iterator citer = gdatap->mTitles.begin(); - std::vector::const_iterator end = gdatap->mTitles.end(); - - for ( ; citer != end; ++citer) - { - mComboActiveTitle->add(citer->mTitle,citer->mRoleID, (citer->mSelected ? ADD_TOP : ADD_BOTTOM)); - if (citer->mSelected) - { - mComboActiveTitle->setCurrentByID(citer->mRoleID); - has_selected_title = true; - } - } - - if (!has_selected_title) - { - mComboActiveTitle->setCurrentByID(LLUUID::null); - } - } - - } - - // After role member data was changed in Roles->Members - // need to update role titles. See STORM-918. - if (gc == GC_ROLE_MEMBER_DATA) - LLGroupMgr::getInstance()->sendGroupTitlesRequest(mGroupID); - - // If this was just a titles update, we are done. - if (gc == GC_TITLES) return; - - bool can_change_ident = false; - bool can_change_member_opts = false; - can_change_ident = gAgent.hasPowerInGroup(mGroupID,GP_GROUP_CHANGE_IDENTITY); - can_change_member_opts = gAgent.hasPowerInGroup(mGroupID,GP_MEMBER_OPTIONS); - - if (mCtrlShowInGroupList) - { - mCtrlShowInGroupList->set(gdatap->mShowInList); - mCtrlShowInGroupList->setEnabled(mAllowEdit && can_change_ident); - } - if (mComboMature) - { - if(gdatap->mMaturePublish) - { - mComboMature->setCurrentByIndex(MATURE_CONTENT); - } - else - { - mComboMature->setCurrentByIndex(NON_MATURE_CONTENT); - } - mComboMature->setEnabled(mAllowEdit && can_change_ident); - mComboMature->setVisible( !gAgent.isTeen() ); - } - if (mCtrlOpenEnrollment) - { - mCtrlOpenEnrollment->set(gdatap->mOpenEnrollment); - mCtrlOpenEnrollment->setEnabled(mAllowEdit && can_change_member_opts); - } - if (mCtrlEnrollmentFee) - { - mCtrlEnrollmentFee->set(gdatap->mMembershipFee > 0); - mCtrlEnrollmentFee->setEnabled(mAllowEdit && can_change_member_opts); - } - - if (mSpinEnrollmentFee) - { - S32 fee = gdatap->mMembershipFee; - mSpinEnrollmentFee->set((F32)fee); - mSpinEnrollmentFee->setEnabled( mAllowEdit && - (fee > 0) && - can_change_member_opts); - } - if (mCtrlReceiveNotices) - { - mCtrlReceiveNotices->setVisible(is_member); - if (is_member) - { - mCtrlReceiveNotices->setEnabled(mAllowEdit); - } - } - - - if (mInsignia) mInsignia->setEnabled(mAllowEdit && can_change_ident); - if (mEditCharter) mEditCharter->setEnabled(mAllowEdit && can_change_ident); - - if (mGroupNameEditor) mGroupNameEditor->setVisible(false); - if (mFounderName) mFounderName->setText(LLSLURL("agent", gdatap->mFounderID, "inspect").getSLURLString()); - if (mInsignia) - { - if (gdatap->mInsigniaID.notNull()) - { - mInsignia->setImageAssetID(gdatap->mInsigniaID); - } - else - { - mInsignia->setImageAssetName(mInsignia->getDefaultImageName()); - } - } - - if (mEditCharter) - { - mEditCharter->setParseURLs(!mAllowEdit || !can_change_ident); - mEditCharter->setText(gdatap->mCharter); - } - - resetDirty(); -} - -void LLPanelGroupGeneral::updateChanged() -{ - // List all the controls we want to check for changes... - LLUICtrl *check_list[] = - { - mGroupNameEditor, - mFounderName, - mInsignia, - mEditCharter, - mCtrlShowInGroupList, - mComboMature, - mCtrlOpenEnrollment, - mCtrlEnrollmentFee, - mSpinEnrollmentFee, - mCtrlReceiveNotices, - mCtrlListGroup, - mActiveTitleLabel, - mComboActiveTitle - }; - - mChanged = false; - - for( size_t i=0; iisDirty() ) - { - mChanged = true; - break; - } - } -} - -void LLPanelGroupGeneral::reset() -{ - mFounderName->setVisible(false); - - - mCtrlReceiveNotices->set(false); - - - mCtrlListGroup->set(true); - - mCtrlReceiveNotices->setEnabled(false); - mCtrlReceiveNotices->setVisible(true); - - mCtrlListGroup->setEnabled(false); - - mGroupNameEditor->setEnabled(true); - mEditCharter->setEnabled(true); - - mCtrlShowInGroupList->setEnabled(false); - mComboMature->setEnabled(true); - - mCtrlOpenEnrollment->setEnabled(true); - - mCtrlEnrollmentFee->setEnabled(true); - - mSpinEnrollmentFee->setEnabled(true); - mSpinEnrollmentFee->set((F32)0); - - mGroupNameEditor->setVisible(true); - - mComboActiveTitle->setVisible(false); - - mInsignia->setImageAssetID(LLUUID::null); - - mInsignia->setEnabled(true); - - mInsignia->setImageAssetName(mInsignia->getDefaultImageName()); - - { - std::string empty_str = ""; - mEditCharter->setText(empty_str); - mGroupNameEditor->setText(empty_str); - } - - { - mComboMature->setEnabled(true); - mComboMature->setVisible( !gAgent.isTeen() ); - mComboMature->selectFirstItem(); - } - - - resetDirty(); -} - -void LLPanelGroupGeneral::resetDirty() -{ - // List all the controls we want to check for changes... - LLUICtrl *check_list[] = - { - mGroupNameEditor, - mFounderName, - mInsignia, - mEditCharter, - mCtrlShowInGroupList, - mComboMature, - mCtrlOpenEnrollment, - mCtrlEnrollmentFee, - mSpinEnrollmentFee, - mCtrlReceiveNotices, - mCtrlListGroup, - mActiveTitleLabel, - mComboActiveTitle - }; - - for( size_t i=0; iresetDirty() ; - } - - -} - -void LLPanelGroupGeneral::setGroupID(const LLUUID& id) -{ - LLPanelGroupTab::setGroupID(id); - - if(id == LLUUID::null) - { - reset(); - return; - } - - bool accept_notices = false; - bool list_in_profile = false; - LLGroupData data; - if(gAgent.getGroupData(mGroupID,data)) - { - accept_notices = data.mAcceptNotices; - list_in_profile = data.mListInProfile; - } - mCtrlReceiveNotices = getChild("receive_notices"); - if (mCtrlReceiveNotices) - { - mCtrlReceiveNotices->set(accept_notices); - mCtrlReceiveNotices->setEnabled(data.mID.notNull()); - } - - mCtrlListGroup = getChild("list_groups_in_profile"); - if (mCtrlListGroup) - { - mCtrlListGroup->set(list_in_profile); - mCtrlListGroup->setEnabled(data.mID.notNull()); - } - - mCtrlShowInGroupList->setEnabled(data.mID.notNull()); - - mActiveTitleLabel = getChild("active_title_label"); - - mComboActiveTitle = getChild("active_title"); - - mFounderName->setVisible(true); - - mInsignia->setImageAssetID(LLUUID::null); - - resetDirty(); - - activate(); -} +/** + * @file llpanelgroupgeneral.cpp + * @brief General information about a group. + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelgroupgeneral.h" + +#include "llavatarnamecache.h" +#include "llagent.h" +#include "llagentbenefits.h" +#include "llsdparam.h" +#include "lluictrlfactory.h" +#include "roles_constants.h" + +// UI elements +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "lldbstrings.h" +#include "llavataractions.h" +#include "llgroupactions.h" +#include "lllineeditor.h" +#include "llnamelistctrl.h" +#include "llnotificationsutil.h" +#include "llscrolllistitem.h" +#include "llspinctrl.h" +#include "llslurl.h" +#include "lltextbox.h" +#include "lltexteditor.h" +#include "lltexturectrl.h" +#include "lltrans.h" +#include "llviewerwindow.h" + +static LLPanelInjector t_panel_group_general("panel_group_general"); + +// consts +const S32 MATURE_CONTENT = 1; +const S32 NON_MATURE_CONTENT = 2; +const S32 DECLINE_TO_STATE = 0; + + +LLPanelGroupGeneral::LLPanelGroupGeneral() +: LLPanelGroupTab(), + mChanged(false), + mFirstUse(true), + mGroupNameEditor(NULL), + mFounderName(NULL), + mInsignia(NULL), + mEditCharter(NULL), + mCtrlShowInGroupList(NULL), + mComboMature(NULL), + mCtrlOpenEnrollment(NULL), + mCtrlEnrollmentFee(NULL), + mSpinEnrollmentFee(NULL), + mCtrlReceiveNotices(NULL), + mCtrlListGroup(NULL), + mActiveTitleLabel(NULL), + mComboActiveTitle(NULL) +{ + +} + +LLPanelGroupGeneral::~LLPanelGroupGeneral() +{ +} + +bool LLPanelGroupGeneral::postBuild() +{ + constexpr bool recurse = true; + + mEditCharter = getChild("charter", recurse); + if(mEditCharter) + { + mEditCharter->setCommitCallback(onCommitAny, this); + mEditCharter->setFocusReceivedCallback(boost::bind(onFocusEdit, _1, this)); + mEditCharter->setFocusChangedCallback(boost::bind(onFocusEdit, _1, this)); + mEditCharter->setContentTrusted(false); + } + + // Options + mCtrlShowInGroupList = getChild("show_in_group_list", recurse); + if (mCtrlShowInGroupList) + { + mCtrlShowInGroupList->setCommitCallback(onCommitAny, this); + } + + mComboMature = getChild("group_mature_check", recurse); + if(mComboMature) + { + mComboMature->setCurrentByIndex(0); + mComboMature->setCommitCallback(onCommitAny, this); + if (gAgent.isTeen()) + { + // Teens don't get to set mature flag. JC + mComboMature->setVisible(false); + mComboMature->setCurrentByIndex(NON_MATURE_CONTENT); + } + } + mCtrlOpenEnrollment = getChild("open_enrollement", recurse); + if (mCtrlOpenEnrollment) + { + mCtrlOpenEnrollment->setCommitCallback(onCommitAny, this); + } + + mCtrlEnrollmentFee = getChild("check_enrollment_fee", recurse); + if (mCtrlEnrollmentFee) + { + mCtrlEnrollmentFee->setCommitCallback(onCommitEnrollment, this); + } + + mSpinEnrollmentFee = getChild("spin_enrollment_fee", recurse); + if (mSpinEnrollmentFee) + { + mSpinEnrollmentFee->setCommitCallback(onCommitAny, this); + mSpinEnrollmentFee->setPrecision(0); + mSpinEnrollmentFee->resetDirty(); + } + + bool accept_notices = false; + bool list_in_profile = false; + LLGroupData data; + if(gAgent.getGroupData(mGroupID,data)) + { + accept_notices = data.mAcceptNotices; + list_in_profile = data.mListInProfile; + } + mCtrlReceiveNotices = getChild("receive_notices", recurse); + if (mCtrlReceiveNotices) + { + mCtrlReceiveNotices->setCommitCallback(onCommitUserOnly, this); + mCtrlReceiveNotices->set(accept_notices); + mCtrlReceiveNotices->setEnabled(data.mID.notNull()); + } + + mCtrlListGroup = getChild("list_groups_in_profile", recurse); + if (mCtrlListGroup) + { + mCtrlListGroup->setCommitCallback(onCommitUserOnly, this); + mCtrlListGroup->set(list_in_profile); + mCtrlListGroup->setEnabled(data.mID.notNull()); + mCtrlListGroup->resetDirty(); + } + + mActiveTitleLabel = getChild("active_title_label", recurse); + + mComboActiveTitle = getChild("active_title", recurse); + if (mComboActiveTitle) + { + mComboActiveTitle->setCommitCallback(onCommitAny, this); + } + + mIncompleteMemberDataStr = getString("incomplete_member_data_str"); + + // If the group_id is null, then we are creating a new group + if (mGroupID.isNull()) + { + mEditCharter->setEnabled(true); + + mCtrlShowInGroupList->setEnabled(true); + mComboMature->setEnabled(true); + mCtrlOpenEnrollment->setEnabled(true); + mCtrlEnrollmentFee->setEnabled(true); + mSpinEnrollmentFee->setEnabled(true); + + } + + return LLPanelGroupTab::postBuild(); +} + +void LLPanelGroupGeneral::setupCtrls(LLPanel* panel_group) +{ + mInsignia = getChild("insignia"); + if (mInsignia) + { + mInsignia->setCommitCallback(onCommitAny, this); + mInsignia->setAllowLocalTexture(false); + } + mFounderName = getChild("founder_name"); + + + mGroupNameEditor = panel_group->getChild("group_name_editor"); + mGroupNameEditor->setPrevalidate( LLTextValidate::validateASCIINoLeadingSpace ); + + +} + +// static +void LLPanelGroupGeneral::onFocusEdit(LLFocusableElement* ctrl, void* data) +{ + LLPanelGroupGeneral* self = (LLPanelGroupGeneral*)data; + self->updateChanged(); + self->notifyObservers(); +} + +// static +void LLPanelGroupGeneral::onCommitAny(LLUICtrl* ctrl, void* data) +{ + LLPanelGroupGeneral* self = (LLPanelGroupGeneral*)data; + self->updateChanged(); + self->notifyObservers(); +} + +// static +void LLPanelGroupGeneral::onCommitUserOnly(LLUICtrl* ctrl, void* data) +{ + LLPanelGroupGeneral* self = (LLPanelGroupGeneral*)data; + self->mChanged = true; + self->notifyObservers(); +} + + +// static +void LLPanelGroupGeneral::onCommitEnrollment(LLUICtrl* ctrl, void* data) +{ + onCommitAny(ctrl, data); + + LLPanelGroupGeneral* self = (LLPanelGroupGeneral*)data; + // Make sure both enrollment related widgets are there. + if (!self->mCtrlEnrollmentFee || !self->mSpinEnrollmentFee) + { + return; + } + + // Make sure the agent can change enrollment info. + if (!gAgent.hasPowerInGroup(self->mGroupID,GP_MEMBER_OPTIONS) + || !self->mAllowEdit) + { + return; + } + + if (self->mCtrlEnrollmentFee->get()) + { + self->mSpinEnrollmentFee->setEnabled(true); + } + else + { + self->mSpinEnrollmentFee->setEnabled(false); + self->mSpinEnrollmentFee->set(0); + } +} + +// static +void LLPanelGroupGeneral::onClickInfo(void *userdata) +{ + LLPanelGroupGeneral *self = (LLPanelGroupGeneral *)userdata; + + if ( !self ) return; + + LL_DEBUGS() << "open group info: " << self->mGroupID << LL_ENDL; + + LLGroupActions::show(self->mGroupID); + +} + +bool LLPanelGroupGeneral::needsApply(std::string& mesg) +{ + updateChanged(); + mesg = getString("group_info_unchanged"); + return mChanged || mGroupID.isNull(); +} + +void LLPanelGroupGeneral::activate() +{ + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + if (mGroupID.notNull() + && (!gdatap || mFirstUse)) + { + LLGroupMgr::getInstance()->sendGroupTitlesRequest(mGroupID); + LLGroupMgr::getInstance()->sendGroupPropertiesRequest(mGroupID); + + mFirstUse = false; + } + mChanged = false; + + update(GC_ALL); +} + +void LLPanelGroupGeneral::draw() +{ + LLPanelGroupTab::draw(); +} + +bool LLPanelGroupGeneral::apply(std::string& mesg) +{ + if (mGroupID.isNull()) + { + return false; + } + + if (!mGroupID.isNull() && mAllowEdit && mComboActiveTitle && mComboActiveTitle->isDirty()) + { + LLGroupMgr::getInstance()->sendGroupTitleUpdate(mGroupID,mComboActiveTitle->getCurrentID()); + update(GC_TITLES); + mComboActiveTitle->resetDirty(); + } + + bool has_power_in_group = gAgent.hasPowerInGroup(mGroupID,GP_GROUP_CHANGE_IDENTITY); + + if (has_power_in_group) + { + LL_INFOS() << "LLPanelGroupGeneral::apply" << LL_ENDL; + + // Check to make sure mature has been set + if(mComboMature && + mComboMature->getCurrentIndex() == DECLINE_TO_STATE) + { + LLNotificationsUtil::add("SetGroupMature", LLSD(), LLSD(), + boost::bind(&LLPanelGroupGeneral::confirmMatureApply, this, _1, _2)); + return false; + } + + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + if (!gdatap) + { + mesg = LLTrans::getString("NoGroupDataFound"); + mesg.append(mGroupID.asString()); + return false; + } + bool can_change_ident = false; + bool can_change_member_opts = false; + can_change_ident = gAgent.hasPowerInGroup(mGroupID,GP_GROUP_CHANGE_IDENTITY); + can_change_member_opts = gAgent.hasPowerInGroup(mGroupID,GP_MEMBER_OPTIONS); + + if (can_change_ident) + { + if (mEditCharter) gdatap->mCharter = mEditCharter->getText(); + if (mInsignia) gdatap->mInsigniaID = mInsignia->getImageAssetID(); + if (mComboMature) + { + if (!gAgent.isTeen()) + { + gdatap->mMaturePublish = + mComboMature->getCurrentIndex() == MATURE_CONTENT; + } + else + { + gdatap->mMaturePublish = false; + } + } + if (mCtrlShowInGroupList) gdatap->mShowInList = mCtrlShowInGroupList->get(); + } + + if (can_change_member_opts) + { + if (mCtrlOpenEnrollment) gdatap->mOpenEnrollment = mCtrlOpenEnrollment->get(); + if (mCtrlEnrollmentFee && mSpinEnrollmentFee) + { + gdatap->mMembershipFee = (mCtrlEnrollmentFee->get()) ? + (S32) mSpinEnrollmentFee->get() : 0; + // Set to the used value, and reset initial value used for isdirty check + mSpinEnrollmentFee->set( (F32)gdatap->mMembershipFee ); + } + } + + if (can_change_ident || can_change_member_opts) + { + LLGroupMgr::getInstance()->sendUpdateGroupInfo(mGroupID); + } + } + + bool receive_notices = false; + bool list_in_profile = false; + if (mCtrlReceiveNotices) + receive_notices = mCtrlReceiveNotices->get(); + if (mCtrlListGroup) + list_in_profile = mCtrlListGroup->get(); + + gAgent.setUserGroupFlags(mGroupID, receive_notices, list_in_profile); + + resetDirty(); + + mChanged = false; + + return true; +} + +void LLPanelGroupGeneral::cancel() +{ + mChanged = false; + + //cancel out all of the click changes to, although since we are + //shifting tabs or closing the floater, this need not be done...yet + notifyObservers(); +} + +// invoked from callbackConfirmMature +bool LLPanelGroupGeneral::confirmMatureApply(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + // 0 == Yes + // 1 == No + // 2 == Cancel + switch(option) + { + case 0: + mComboMature->setCurrentByIndex(MATURE_CONTENT); + break; + case 1: + mComboMature->setCurrentByIndex(NON_MATURE_CONTENT); + break; + default: + return false; + } + + // If we got here it means they set a valid value + std::string mesg = ""; + bool ret = apply(mesg); + if ( !mesg.empty() ) + { + LLSD args; + args["MESSAGE"] = mesg; + LLNotificationsUtil::add("GenericAlert", args); + } + + return ret; +} + +// virtual +void LLPanelGroupGeneral::update(LLGroupChange gc) +{ + if (mGroupID.isNull()) return; + + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + + if (!gdatap) return; + + LLGroupData agent_gdatap; + bool is_member = false; + if (gAgent.getGroupData(mGroupID,agent_gdatap)) is_member = true; + + if (mComboActiveTitle) + { + mComboActiveTitle->setVisible(is_member); + mComboActiveTitle->setEnabled(mAllowEdit); + + if ( mActiveTitleLabel) mActiveTitleLabel->setVisible(is_member); + + if (is_member) + { + LLUUID current_title_role; + + mComboActiveTitle->clear(); + mComboActiveTitle->removeall(); + bool has_selected_title = false; + + if (1 == gdatap->mTitles.size()) + { + // Only the everyone title. Don't bother letting them try changing this. + mComboActiveTitle->setEnabled(false); + } + else + { + mComboActiveTitle->setEnabled(true); + } + + std::vector::const_iterator citer = gdatap->mTitles.begin(); + std::vector::const_iterator end = gdatap->mTitles.end(); + + for ( ; citer != end; ++citer) + { + mComboActiveTitle->add(citer->mTitle,citer->mRoleID, (citer->mSelected ? ADD_TOP : ADD_BOTTOM)); + if (citer->mSelected) + { + mComboActiveTitle->setCurrentByID(citer->mRoleID); + has_selected_title = true; + } + } + + if (!has_selected_title) + { + mComboActiveTitle->setCurrentByID(LLUUID::null); + } + } + + } + + // After role member data was changed in Roles->Members + // need to update role titles. See STORM-918. + if (gc == GC_ROLE_MEMBER_DATA) + LLGroupMgr::getInstance()->sendGroupTitlesRequest(mGroupID); + + // If this was just a titles update, we are done. + if (gc == GC_TITLES) return; + + bool can_change_ident = false; + bool can_change_member_opts = false; + can_change_ident = gAgent.hasPowerInGroup(mGroupID,GP_GROUP_CHANGE_IDENTITY); + can_change_member_opts = gAgent.hasPowerInGroup(mGroupID,GP_MEMBER_OPTIONS); + + if (mCtrlShowInGroupList) + { + mCtrlShowInGroupList->set(gdatap->mShowInList); + mCtrlShowInGroupList->setEnabled(mAllowEdit && can_change_ident); + } + if (mComboMature) + { + if(gdatap->mMaturePublish) + { + mComboMature->setCurrentByIndex(MATURE_CONTENT); + } + else + { + mComboMature->setCurrentByIndex(NON_MATURE_CONTENT); + } + mComboMature->setEnabled(mAllowEdit && can_change_ident); + mComboMature->setVisible( !gAgent.isTeen() ); + } + if (mCtrlOpenEnrollment) + { + mCtrlOpenEnrollment->set(gdatap->mOpenEnrollment); + mCtrlOpenEnrollment->setEnabled(mAllowEdit && can_change_member_opts); + } + if (mCtrlEnrollmentFee) + { + mCtrlEnrollmentFee->set(gdatap->mMembershipFee > 0); + mCtrlEnrollmentFee->setEnabled(mAllowEdit && can_change_member_opts); + } + + if (mSpinEnrollmentFee) + { + S32 fee = gdatap->mMembershipFee; + mSpinEnrollmentFee->set((F32)fee); + mSpinEnrollmentFee->setEnabled( mAllowEdit && + (fee > 0) && + can_change_member_opts); + } + if (mCtrlReceiveNotices) + { + mCtrlReceiveNotices->setVisible(is_member); + if (is_member) + { + mCtrlReceiveNotices->setEnabled(mAllowEdit); + } + } + + + if (mInsignia) mInsignia->setEnabled(mAllowEdit && can_change_ident); + if (mEditCharter) mEditCharter->setEnabled(mAllowEdit && can_change_ident); + + if (mGroupNameEditor) mGroupNameEditor->setVisible(false); + if (mFounderName) mFounderName->setText(LLSLURL("agent", gdatap->mFounderID, "inspect").getSLURLString()); + if (mInsignia) + { + if (gdatap->mInsigniaID.notNull()) + { + mInsignia->setImageAssetID(gdatap->mInsigniaID); + } + else + { + mInsignia->setImageAssetName(mInsignia->getDefaultImageName()); + } + } + + if (mEditCharter) + { + mEditCharter->setParseURLs(!mAllowEdit || !can_change_ident); + mEditCharter->setText(gdatap->mCharter); + } + + resetDirty(); +} + +void LLPanelGroupGeneral::updateChanged() +{ + // List all the controls we want to check for changes... + LLUICtrl *check_list[] = + { + mGroupNameEditor, + mFounderName, + mInsignia, + mEditCharter, + mCtrlShowInGroupList, + mComboMature, + mCtrlOpenEnrollment, + mCtrlEnrollmentFee, + mSpinEnrollmentFee, + mCtrlReceiveNotices, + mCtrlListGroup, + mActiveTitleLabel, + mComboActiveTitle + }; + + mChanged = false; + + for( size_t i=0; iisDirty() ) + { + mChanged = true; + break; + } + } +} + +void LLPanelGroupGeneral::reset() +{ + mFounderName->setVisible(false); + + + mCtrlReceiveNotices->set(false); + + + mCtrlListGroup->set(true); + + mCtrlReceiveNotices->setEnabled(false); + mCtrlReceiveNotices->setVisible(true); + + mCtrlListGroup->setEnabled(false); + + mGroupNameEditor->setEnabled(true); + mEditCharter->setEnabled(true); + + mCtrlShowInGroupList->setEnabled(false); + mComboMature->setEnabled(true); + + mCtrlOpenEnrollment->setEnabled(true); + + mCtrlEnrollmentFee->setEnabled(true); + + mSpinEnrollmentFee->setEnabled(true); + mSpinEnrollmentFee->set((F32)0); + + mGroupNameEditor->setVisible(true); + + mComboActiveTitle->setVisible(false); + + mInsignia->setImageAssetID(LLUUID::null); + + mInsignia->setEnabled(true); + + mInsignia->setImageAssetName(mInsignia->getDefaultImageName()); + + { + std::string empty_str = ""; + mEditCharter->setText(empty_str); + mGroupNameEditor->setText(empty_str); + } + + { + mComboMature->setEnabled(true); + mComboMature->setVisible( !gAgent.isTeen() ); + mComboMature->selectFirstItem(); + } + + + resetDirty(); +} + +void LLPanelGroupGeneral::resetDirty() +{ + // List all the controls we want to check for changes... + LLUICtrl *check_list[] = + { + mGroupNameEditor, + mFounderName, + mInsignia, + mEditCharter, + mCtrlShowInGroupList, + mComboMature, + mCtrlOpenEnrollment, + mCtrlEnrollmentFee, + mSpinEnrollmentFee, + mCtrlReceiveNotices, + mCtrlListGroup, + mActiveTitleLabel, + mComboActiveTitle + }; + + for( size_t i=0; iresetDirty() ; + } + + +} + +void LLPanelGroupGeneral::setGroupID(const LLUUID& id) +{ + LLPanelGroupTab::setGroupID(id); + + if(id == LLUUID::null) + { + reset(); + return; + } + + bool accept_notices = false; + bool list_in_profile = false; + LLGroupData data; + if(gAgent.getGroupData(mGroupID,data)) + { + accept_notices = data.mAcceptNotices; + list_in_profile = data.mListInProfile; + } + mCtrlReceiveNotices = getChild("receive_notices"); + if (mCtrlReceiveNotices) + { + mCtrlReceiveNotices->set(accept_notices); + mCtrlReceiveNotices->setEnabled(data.mID.notNull()); + } + + mCtrlListGroup = getChild("list_groups_in_profile"); + if (mCtrlListGroup) + { + mCtrlListGroup->set(list_in_profile); + mCtrlListGroup->setEnabled(data.mID.notNull()); + } + + mCtrlShowInGroupList->setEnabled(data.mID.notNull()); + + mActiveTitleLabel = getChild("active_title_label"); + + mComboActiveTitle = getChild("active_title"); + + mFounderName->setVisible(true); + + mInsignia->setImageAssetID(LLUUID::null); + + resetDirty(); + + activate(); +} diff --git a/indra/newview/llpanelgroupgeneral.h b/indra/newview/llpanelgroupgeneral.h index 58f8f5eb18..e5d766dc40 100644 --- a/indra/newview/llpanelgroupgeneral.h +++ b/indra/newview/llpanelgroupgeneral.h @@ -1,103 +1,103 @@ -/** - * @file llpanelgroupgeneral.h - * @brief General information about a group. - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELGROUPGENERAL_H -#define LL_LLPANELGROUPGENERAL_H - -#include "llpanelgroup.h" - -class LLLineEditor; -class LLTextBox; -class LLTextureCtrl; -class LLTextEditor; -class LLButton; -class LLNameListCtrl; -class LLCheckBoxCtrl; -class LLComboBox; -class LLSpinCtrl; -class LLAvatarName; - -class LLPanelGroupGeneral : public LLPanelGroupTab -{ -public: - LLPanelGroupGeneral(); - virtual ~LLPanelGroupGeneral(); - - // LLPanelGroupTab - virtual void activate(); - virtual bool needsApply(std::string& mesg); - virtual bool apply(std::string& mesg); - virtual void cancel(); - - virtual void update(LLGroupChange gc); - - virtual bool postBuild(); - - virtual void draw(); - - virtual void setGroupID(const LLUUID& id); - - virtual void setupCtrls (LLPanel* parent); -private: - void reset(); - - void resetDirty(); - - static void onFocusEdit(LLFocusableElement* ctrl, void* data); - static void onCommitAny(LLUICtrl* ctrl, void* data); - static void onCommitUserOnly(LLUICtrl* ctrl, void* data); - static void onCommitEnrollment(LLUICtrl* ctrl, void* data); - static void onClickInfo(void* userdata); - static void onReceiveNotices(LLUICtrl* ctrl, void* data); - - static bool joinDlgCB(const LLSD& notification, const LLSD& response); - - void updateChanged(); - bool confirmMatureApply(const LLSD& notification, const LLSD& response); - - bool mChanged; - bool mFirstUse; - std::string mIncompleteMemberDataStr; - - // Group information (include any updates in updateChanged) - LLLineEditor *mGroupNameEditor; - LLTextBox *mFounderName; - LLTextureCtrl *mInsignia; - LLTextEditor *mEditCharter; - - // Options (include any updates in updateChanged) - LLCheckBoxCtrl *mCtrlShowInGroupList; - LLCheckBoxCtrl *mCtrlOpenEnrollment; - LLCheckBoxCtrl *mCtrlEnrollmentFee; - LLSpinCtrl *mSpinEnrollmentFee; - LLCheckBoxCtrl *mCtrlReceiveNotices; - LLCheckBoxCtrl *mCtrlListGroup; - LLTextBox *mActiveTitleLabel; - LLComboBox *mComboActiveTitle; - LLComboBox *mComboMature; -}; - -#endif +/** + * @file llpanelgroupgeneral.h + * @brief General information about a group. + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELGROUPGENERAL_H +#define LL_LLPANELGROUPGENERAL_H + +#include "llpanelgroup.h" + +class LLLineEditor; +class LLTextBox; +class LLTextureCtrl; +class LLTextEditor; +class LLButton; +class LLNameListCtrl; +class LLCheckBoxCtrl; +class LLComboBox; +class LLSpinCtrl; +class LLAvatarName; + +class LLPanelGroupGeneral : public LLPanelGroupTab +{ +public: + LLPanelGroupGeneral(); + virtual ~LLPanelGroupGeneral(); + + // LLPanelGroupTab + virtual void activate(); + virtual bool needsApply(std::string& mesg); + virtual bool apply(std::string& mesg); + virtual void cancel(); + + virtual void update(LLGroupChange gc); + + virtual bool postBuild(); + + virtual void draw(); + + virtual void setGroupID(const LLUUID& id); + + virtual void setupCtrls (LLPanel* parent); +private: + void reset(); + + void resetDirty(); + + static void onFocusEdit(LLFocusableElement* ctrl, void* data); + static void onCommitAny(LLUICtrl* ctrl, void* data); + static void onCommitUserOnly(LLUICtrl* ctrl, void* data); + static void onCommitEnrollment(LLUICtrl* ctrl, void* data); + static void onClickInfo(void* userdata); + static void onReceiveNotices(LLUICtrl* ctrl, void* data); + + static bool joinDlgCB(const LLSD& notification, const LLSD& response); + + void updateChanged(); + bool confirmMatureApply(const LLSD& notification, const LLSD& response); + + bool mChanged; + bool mFirstUse; + std::string mIncompleteMemberDataStr; + + // Group information (include any updates in updateChanged) + LLLineEditor *mGroupNameEditor; + LLTextBox *mFounderName; + LLTextureCtrl *mInsignia; + LLTextEditor *mEditCharter; + + // Options (include any updates in updateChanged) + LLCheckBoxCtrl *mCtrlShowInGroupList; + LLCheckBoxCtrl *mCtrlOpenEnrollment; + LLCheckBoxCtrl *mCtrlEnrollmentFee; + LLSpinCtrl *mSpinEnrollmentFee; + LLCheckBoxCtrl *mCtrlReceiveNotices; + LLCheckBoxCtrl *mCtrlListGroup; + LLTextBox *mActiveTitleLabel; + LLComboBox *mComboActiveTitle; + LLComboBox *mComboMature; +}; + +#endif diff --git a/indra/newview/llpanelgroupinvite.cpp b/indra/newview/llpanelgroupinvite.cpp index b169bfb623..8a6876f8fc 100644 --- a/indra/newview/llpanelgroupinvite.cpp +++ b/indra/newview/llpanelgroupinvite.cpp @@ -1,700 +1,700 @@ -/** - * @file llpanelgroupinvite.cpp - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelgroupinvite.h" - -#include "llagent.h" -#include "llavatarnamecache.h" -#include "llfloateravatarpicker.h" -#include "llbutton.h" -#include "llcallingcard.h" -#include "llcombobox.h" -#include "llgroupactions.h" -#include "llgroupmgr.h" -#include "llnamelistctrl.h" -#include "llnotificationsutil.h" -#include "llscrolllistitem.h" -#include "llspinctrl.h" -#include "lltextbox.h" -#include "llviewerobject.h" -#include "llviewerobjectlist.h" -#include "lluictrlfactory.h" -#include "llviewerwindow.h" - -class LLPanelGroupInvite::impl -{ -public: - impl(const LLUUID& group_id); - ~impl(); - - void addUsers(const std::vector& names, - const uuid_vec_t& agent_ids); - void submitInvitations(); - void addRoleNames(LLGroupMgrGroupData* gdatap); - void handleRemove(); - void handleSelection(); - - static void callbackClickCancel(void* userdata); - static void callbackClickOK(void* userdata); - static void callbackClickAdd(void* userdata); - static void callbackClickRemove(void* userdata); - static void callbackSelect(LLUICtrl* ctrl, void* userdata); - static void callbackAddUsers(const uuid_vec_t& agent_ids, - void* user_data); - - static void onAvatarNameCache(const LLUUID& agent_id, - const LLAvatarName& av_name, - void* user_data); - - bool inviteOwnerCallback(const LLSD& notification, const LLSD& response); - -public: - LLUUID mGroupID; - - std::string mLoadingText; - LLNameListCtrl *mInvitees; - LLComboBox *mRoleNames; - LLButton *mOKButton; - LLButton *mRemoveButton; - LLTextBox *mGroupName; - std::string mOwnerWarning; - std::string mAlreadyInGroup; - std::string mTooManySelected; - bool mConfirmedOwnerInvite; - std::set mInviteeIDs; - - void (*mCloseCallback)(void* data); - - void* mCloseCallbackUserData; - - boost::signals2::connection mAvatarNameCacheConnection; -}; - - -LLPanelGroupInvite::impl::impl(const LLUUID& group_id): - mGroupID( group_id ), - mLoadingText (), - mInvitees ( NULL ), - mRoleNames( NULL ), - mOKButton ( NULL ), - mRemoveButton( NULL ), - mGroupName( NULL ), - mConfirmedOwnerInvite( false ), - mCloseCallback( NULL ), - mCloseCallbackUserData( NULL ), - mAvatarNameCacheConnection() -{ -} - -LLPanelGroupInvite::impl::~impl() -{ - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } -} - -const S32 MAX_GROUP_INVITES = 100; // Max invites per request. 100 to match server cap. - -void LLPanelGroupInvite::impl::addUsers(const std::vector& names, - const uuid_vec_t& agent_ids) -{ - std::string name; - LLUUID id; - - if (names.size() + mInviteeIDs.size() > MAX_GROUP_INVITES) - { - // Fail! Show a warning and don't add any names. - LLSD msg; - msg["MESSAGE"] = mTooManySelected; - LLNotificationsUtil::add("GenericAlert", msg); - return; - } - - for (S32 i = 0; i < (S32)names.size(); i++) - { - name = names[i]; - id = agent_ids[i]; - - // Make sure this agent isn't already in the list. - if (mInviteeIDs.find(id) != mInviteeIDs.end()) - { - continue; - } - - //add the name to the names list - LLSD row; - row["id"] = id; - row["columns"][0]["value"] = name; - - mInvitees->addElement(row); - mInviteeIDs.insert(id); - } -} - -void LLPanelGroupInvite::impl::submitInvitations() -{ - std::map role_member_pairs; - - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - - // Default to everyone role. - LLUUID role_id = LLUUID::null; - - if (mRoleNames) - { - role_id = mRoleNames->getCurrentID(); - - // owner role: display confirmation and wait for callback - if ((role_id == gdatap->mOwnerRole) && (!mConfirmedOwnerInvite)) - { - LLSD args; - args["MESSAGE"] = mOwnerWarning; - LLNotificationsUtil::add("GenericAlertYesCancel", args, LLSD(), boost::bind(&LLPanelGroupInvite::impl::inviteOwnerCallback, this, _1, _2)); - return; // we'll be called again if user confirms - } - } - - bool already_in_group = false; - //loop over the users - std::vector items = mInvitees->getAllData(); - for (std::vector::iterator iter = items.begin(); - iter != items.end(); ++iter) - { - LLScrollListItem* item = *iter; - if(LLGroupActions::isAvatarMemberOfGroup(mGroupID, item->getUUID())) - { - already_in_group = true; - continue; - } - role_member_pairs[item->getUUID()] = role_id; - } - - if (role_member_pairs.size() > MAX_GROUP_INVITES) - { - // Fail! - LLSD msg; - msg["MESSAGE"] = mTooManySelected; - LLNotificationsUtil::add("GenericAlert", msg); - (*mCloseCallback)(mCloseCallbackUserData); - return; - } - - LLGroupMgr::getInstance()->sendGroupMemberInvites(mGroupID, role_member_pairs); - - if(already_in_group) - { - LLSD msg; - msg["MESSAGE"] = mAlreadyInGroup; - LLNotificationsUtil::add("GenericAlert", msg); - } - - //then close - (*mCloseCallback)(mCloseCallbackUserData); -} - -bool LLPanelGroupInvite::impl::inviteOwnerCallback(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - - switch(option) - { - case 0: - // user confirmed that they really want a new group owner - mConfirmedOwnerInvite = true; - submitInvitations(); - break; - case 1: - // fall through - default: - break; - } - return false; -} - - - -void LLPanelGroupInvite::impl::addRoleNames(LLGroupMgrGroupData* gdatap) -{ - LLGroupMgrGroupData::member_list_t::iterator agent_iter = - gdatap->mMembers.find(gAgent.getID()); - - //loop over the agent's roles in the group - //then add those roles to the list of roles that the agent - //can invite people to be. - //if the user is the owner then we add - //all of the roles in the group, - //else if they have the add to roles power - //we add every role but owner, - //else if they have the limited add to roles power - //we add every role the user is in, - //else we just add to everyone - bool is_owner = false; - bool can_assign_any = gAgent.hasPowerInGroup(mGroupID, - GP_ROLE_ASSIGN_MEMBER); - bool can_assign_limited = gAgent.hasPowerInGroup(mGroupID, - GP_ROLE_ASSIGN_MEMBER_LIMITED); - LLGroupMemberData* member_data = NULL; - //get the member data for the agent if it exists - if (agent_iter != gdatap->mMembers.end()) - { - member_data = (*agent_iter).second; - if (member_data && mRoleNames) - { - is_owner = member_data->isOwner(); - }//end if member data is not null - }//end if agent is in the group - - - - LLGroupMgrGroupData::role_list_t::iterator rit = gdatap->mRoles.begin(); - LLGroupMgrGroupData::role_list_t::iterator end = gdatap->mRoles.end(); - - //populate the role list: - for ( ; rit != end; ++rit) - { - LLUUID role_id = (*rit).first; - LLRoleData rd; - if ( gdatap->getRoleData(role_id,rd) ) - { - // Owners can add any role. - if ( is_owner - // Even 'can_assign_any' can't add owner role. - || (can_assign_any && role_id != gdatap->mOwnerRole) - // Add all roles user is in - || (can_assign_limited && member_data && member_data->isInRole(role_id)) - // Everyone role. - || role_id == LLUUID::null ) - { - mRoleNames->add(rd.mRoleName, - role_id, - ADD_BOTTOM); - } - } - } -} - -//static -void LLPanelGroupInvite::impl::callbackClickAdd(void* userdata) -{ - LLPanelGroupInvite* panelp = (LLPanelGroupInvite*) userdata; - - if ( panelp ) - { - //Right now this is hard coded with some knowledge that it is part - //of a floater since the avatar picker needs to be added as a dependent - //floater to the parent floater. - //Soon the avatar picker will be embedded into this panel - //instead of being it's own separate floater. But that is next week. - //This will do for now. -jwolk May 10, 2006 - LLView * button = panelp->findChild("add_button"); - LLFloater * root_floater = gFloaterView->getParentFloater(panelp); - LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show( - boost::bind(impl::callbackAddUsers, _1, panelp->mImplementation), true, false, false, root_floater->getName(), button); - if (picker) - { - root_floater->addDependentFloater(picker); - } - } -} - -//static -void LLPanelGroupInvite::impl::callbackClickRemove(void* userdata) -{ - impl* selfp = (impl*) userdata; - - if ( selfp ) selfp->handleRemove(); -} - -void LLPanelGroupInvite::impl::handleRemove() -{ - // Check if there is anything selected. - std::vector selection = - mInvitees->getAllSelected(); - if (selection.empty()) return; - - std::vector::iterator iter; - for(iter = selection.begin(); iter != selection.end(); ++iter) - { - mInviteeIDs.erase( (*iter)->getUUID() ); - } - - // Remove all selected invitees. - mInvitees->deleteSelectedItems(); - mRemoveButton->setEnabled(false); -} - -// static -void LLPanelGroupInvite::impl::callbackSelect( - LLUICtrl* ctrl, void* userdata) -{ - impl* selfp = (impl*) userdata; - if ( selfp ) selfp->handleSelection(); -} - -void LLPanelGroupInvite::impl::handleSelection() -{ - // Check if there is anything selected. - std::vector selection = - mInvitees->getAllSelected(); - if (selection.empty()) - { - mRemoveButton->setEnabled(false); - } - else - { - mRemoveButton->setEnabled(true); - } -} - -void LLPanelGroupInvite::impl::callbackClickCancel(void* userdata) -{ - impl* selfp = (impl*) userdata; - - if ( selfp ) - { - (*(selfp->mCloseCallback))(selfp->mCloseCallbackUserData); - } -} - -void LLPanelGroupInvite::impl::callbackClickOK(void* userdata) -{ - impl* selfp = (impl*) userdata; - - if ( selfp ) selfp->submitInvitations(); -} - - - -//static -void LLPanelGroupInvite::impl::callbackAddUsers(const uuid_vec_t& agent_ids, void* user_data) -{ - std::vector names; - for (S32 i = 0; i < (S32)agent_ids.size(); i++) - { - LLAvatarName av_name; - if (LLAvatarNameCache::get(agent_ids[i], &av_name)) - { - LLPanelGroupInvite::impl::onAvatarNameCache(agent_ids[i], av_name, user_data); - } - else - { - impl* selfp = (impl*) user_data; - if (selfp) - { - if (selfp->mAvatarNameCacheConnection.connected()) - { - selfp->mAvatarNameCacheConnection.disconnect(); - } - // *TODO : Add a callback per avatar name being fetched. - selfp->mAvatarNameCacheConnection = LLAvatarNameCache::get(agent_ids[i],boost::bind(&LLPanelGroupInvite::impl::onAvatarNameCache, _1, _2, user_data)); - } - } - } - -} - -void LLPanelGroupInvite::impl::onAvatarNameCache(const LLUUID& agent_id, - const LLAvatarName& av_name, - void* user_data) -{ - impl* selfp = (impl*) user_data; - - if (selfp) - { - if (selfp->mAvatarNameCacheConnection.connected()) - { - selfp->mAvatarNameCacheConnection.disconnect(); - } - std::vector names; - uuid_vec_t agent_ids; - agent_ids.push_back(agent_id); - names.push_back(av_name.getCompleteName()); - - selfp->addUsers(names, agent_ids); - } -} - - -LLPanelGroupInvite::LLPanelGroupInvite(const LLUUID& group_id) - : LLPanel(), - mImplementation(new impl(group_id)), - mPendingUpdate(false) -{ - // Pass on construction of this panel to the control factory. - buildFromFile( "panel_group_invite.xml"); -} - -LLPanelGroupInvite::~LLPanelGroupInvite() -{ - delete mImplementation; -} - -void LLPanelGroupInvite::setCloseCallback(void (*close_callback)(void*), - void* data) -{ - mImplementation->mCloseCallback = close_callback; - mImplementation->mCloseCallbackUserData = data; -} - -void LLPanelGroupInvite::clear() -{ - mStoreSelected = LLUUID::null; - mImplementation->mInvitees->deleteAllItems(); - mImplementation->mRoleNames->clear(); - mImplementation->mRoleNames->removeall(); - mImplementation->mOKButton->setEnabled(false); - mImplementation->mInviteeIDs.clear(); -} - -void LLPanelGroupInvite::addUsers(uuid_vec_t& agent_ids) -{ - std::vector names; - for (S32 i = 0; i < (S32)agent_ids.size(); i++) - { - std::string fullname; - LLUUID agent_id = agent_ids[i]; - LLViewerObject* dest = gObjectList.findObject(agent_id); - if(dest && dest->isAvatar()) - { - LLNameValue* nvfirst = dest->getNVPair("FirstName"); - LLNameValue* nvlast = dest->getNVPair("LastName"); - if(nvfirst && nvlast) - { - fullname = LLCacheName::buildFullName( - nvfirst->getString(), nvlast->getString()); - - } - if (!fullname.empty()) - { - names.push_back(fullname); - } - else - { - LL_WARNS() << "llPanelGroupInvite: Selected avatar has no name: " << dest->getID() << LL_ENDL; - names.push_back("(Unknown)"); - } - } - else - { - //looks like user try to invite offline avatar (or the avatar from the other region) - //for offline avatar_id gObjectList.findObject() will return null - //so we need to do this additional search in avatar tracker, see EXT-4732 - LLAvatarName av_name; - if (!LLAvatarNameCache::get(agent_id, &av_name)) - { - // actually it should happen, just in case - //LLAvatarNameCache::get(LLUUID(agent_id), boost::bind(&LLPanelGroupInvite::addUserCallback, this, _1, _2)); - // for this special case! - //when there is no cached name we should remove resident from agent_ids list to avoid breaking of sequence - // removed id will be added in callback - agent_ids.erase(agent_ids.begin() + i); - } - else - { - names.push_back(av_name.getAccountName()); - } - } - } - mImplementation->addUsers(names, agent_ids); -} - -void LLPanelGroupInvite::addUserCallback(const LLUUID& id, const LLAvatarName& av_name) -{ - std::vector names; - uuid_vec_t agent_ids; - agent_ids.push_back(id); - names.push_back(av_name.getAccountName()); - - mImplementation->addUsers(names, agent_ids); -} - -void LLPanelGroupInvite::draw() -{ - LLPanel::draw(); - if (mPendingUpdate) - { - updateLists(); - } -} - -void LLPanelGroupInvite::update() -{ - mPendingUpdate = false; - if (mImplementation->mGroupName) - { - mImplementation->mGroupName->setText(mImplementation->mLoadingText); - } - if ( mImplementation->mRoleNames ) - { - mStoreSelected = mImplementation->mRoleNames->getCurrentID(); - mImplementation->mRoleNames->clear(); - mImplementation->mRoleNames->removeall(); - mImplementation->mRoleNames->add(mImplementation->mLoadingText, LLUUID::null, ADD_BOTTOM); - mImplementation->mRoleNames->setCurrentByID(LLUUID::null); - } - - updateLists(); -} - -void LLPanelGroupInvite::updateLists() -{ - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mImplementation->mGroupID); - bool waiting = false; - - if (gdatap) - { - if (gdatap->isGroupPropertiesDataComplete()) - { - if (mImplementation->mGroupName) - { - mImplementation->mGroupName->setText(gdatap->mName); - } - } - else - { - waiting = true; - } - if (gdatap->isRoleDataComplete() && gdatap->isMemberDataComplete() - && (gdatap->isRoleMemberDataComplete() || !gdatap->mMembers.size())) // MAINT-5270: large groups receives an empty members list without some powers, so RoleMemberData wouldn't be complete for them - { - if ( mImplementation->mRoleNames ) - { - mImplementation->mRoleNames->clear(); - mImplementation->mRoleNames->removeall(); - - //add the role names and select the everybody role by default - mImplementation->addRoleNames(gdatap); - mImplementation->mRoleNames->setCurrentByID(mStoreSelected); - } - } - else - { - waiting = true; - } - } - else - { - waiting = true; - } - - if (waiting) - { - if (!mPendingUpdate) - { - // Note: this will partially fail if some requests are already in progress - LLGroupMgr::getInstance()->sendGroupPropertiesRequest(mImplementation->mGroupID); - LLGroupMgr::getInstance()->sendGroupRoleDataRequest(mImplementation->mGroupID); - LLGroupMgr::getInstance()->sendGroupRoleMembersRequest(mImplementation->mGroupID); - LLGroupMgr::getInstance()->sendCapGroupMembersRequest(mImplementation->mGroupID); - } - else if (gdatap) - { - // restart requests that were interrupted/dropped/failed to start - if (!gdatap->isRoleDataPending() && !gdatap->isRoleDataComplete()) - { - LLGroupMgr::getInstance()->sendGroupRoleDataRequest(mImplementation->mGroupID); - } - if (!gdatap->isRoleMemberDataPending() && !gdatap->isRoleMemberDataComplete()) - { - LLGroupMgr::getInstance()->sendGroupRoleMembersRequest(mImplementation->mGroupID); - } - // sendCapGroupMembersRequest has a per frame send limitation that could have - // interrupted previous request - if (!gdatap->isMemberDataPending() && !gdatap->isMemberDataComplete()) - { - LLGroupMgr::getInstance()->sendCapGroupMembersRequest(mImplementation->mGroupID); - } - } - mPendingUpdate = true; - } - else - { - mPendingUpdate = false; - if (mImplementation->mOKButton && mImplementation->mRoleNames->getItemCount()) - { - mImplementation->mOKButton->setEnabled(true); - } - } -} - -bool LLPanelGroupInvite::postBuild() -{ - constexpr bool recurse = true; - - mImplementation->mLoadingText = getString("loading"); - mImplementation->mRoleNames = getChild("role_name", - recurse); - mImplementation->mGroupName = getChild("group_name_text", recurse); - mImplementation->mInvitees = - getChild("invitee_list", recurse); - if ( mImplementation->mInvitees ) - { - mImplementation->mInvitees->setCommitOnSelectionChange(true); - mImplementation->mInvitees->setCommitCallback(impl::callbackSelect, mImplementation); - } - - LLButton* button = getChild("add_button", recurse); - if ( button ) - { - // default to opening avatarpicker automatically - // (*impl::callbackClickAdd)((void*)this); - button->setClickedCallback(impl::callbackClickAdd, this); - } - - mImplementation->mRemoveButton = - getChild("remove_button", recurse); - if ( mImplementation->mRemoveButton ) - { - mImplementation->mRemoveButton->setClickedCallback(impl::callbackClickRemove, mImplementation); - mImplementation->mRemoveButton->setEnabled(false); - } - - mImplementation->mOKButton = - getChild("invite_button", recurse); - if ( mImplementation->mOKButton ) - { - mImplementation->mOKButton->setClickedCallback(impl::callbackClickOK, mImplementation); - mImplementation->mOKButton->setEnabled(false); - } - - button = getChild("cancel_button", recurse); - if ( button ) - { - button->setClickedCallback(impl::callbackClickCancel, mImplementation); - } - - mImplementation->mOwnerWarning = getString("confirm_invite_owner_str"); - mImplementation->mAlreadyInGroup = getString("already_in_group"); - mImplementation->mTooManySelected = getString("invite_selection_too_large"); - - update(); - - return (mImplementation->mRoleNames && - mImplementation->mInvitees && - mImplementation->mRemoveButton); -} +/** + * @file llpanelgroupinvite.cpp + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelgroupinvite.h" + +#include "llagent.h" +#include "llavatarnamecache.h" +#include "llfloateravatarpicker.h" +#include "llbutton.h" +#include "llcallingcard.h" +#include "llcombobox.h" +#include "llgroupactions.h" +#include "llgroupmgr.h" +#include "llnamelistctrl.h" +#include "llnotificationsutil.h" +#include "llscrolllistitem.h" +#include "llspinctrl.h" +#include "lltextbox.h" +#include "llviewerobject.h" +#include "llviewerobjectlist.h" +#include "lluictrlfactory.h" +#include "llviewerwindow.h" + +class LLPanelGroupInvite::impl +{ +public: + impl(const LLUUID& group_id); + ~impl(); + + void addUsers(const std::vector& names, + const uuid_vec_t& agent_ids); + void submitInvitations(); + void addRoleNames(LLGroupMgrGroupData* gdatap); + void handleRemove(); + void handleSelection(); + + static void callbackClickCancel(void* userdata); + static void callbackClickOK(void* userdata); + static void callbackClickAdd(void* userdata); + static void callbackClickRemove(void* userdata); + static void callbackSelect(LLUICtrl* ctrl, void* userdata); + static void callbackAddUsers(const uuid_vec_t& agent_ids, + void* user_data); + + static void onAvatarNameCache(const LLUUID& agent_id, + const LLAvatarName& av_name, + void* user_data); + + bool inviteOwnerCallback(const LLSD& notification, const LLSD& response); + +public: + LLUUID mGroupID; + + std::string mLoadingText; + LLNameListCtrl *mInvitees; + LLComboBox *mRoleNames; + LLButton *mOKButton; + LLButton *mRemoveButton; + LLTextBox *mGroupName; + std::string mOwnerWarning; + std::string mAlreadyInGroup; + std::string mTooManySelected; + bool mConfirmedOwnerInvite; + std::set mInviteeIDs; + + void (*mCloseCallback)(void* data); + + void* mCloseCallbackUserData; + + boost::signals2::connection mAvatarNameCacheConnection; +}; + + +LLPanelGroupInvite::impl::impl(const LLUUID& group_id): + mGroupID( group_id ), + mLoadingText (), + mInvitees ( NULL ), + mRoleNames( NULL ), + mOKButton ( NULL ), + mRemoveButton( NULL ), + mGroupName( NULL ), + mConfirmedOwnerInvite( false ), + mCloseCallback( NULL ), + mCloseCallbackUserData( NULL ), + mAvatarNameCacheConnection() +{ +} + +LLPanelGroupInvite::impl::~impl() +{ + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } +} + +const S32 MAX_GROUP_INVITES = 100; // Max invites per request. 100 to match server cap. + +void LLPanelGroupInvite::impl::addUsers(const std::vector& names, + const uuid_vec_t& agent_ids) +{ + std::string name; + LLUUID id; + + if (names.size() + mInviteeIDs.size() > MAX_GROUP_INVITES) + { + // Fail! Show a warning and don't add any names. + LLSD msg; + msg["MESSAGE"] = mTooManySelected; + LLNotificationsUtil::add("GenericAlert", msg); + return; + } + + for (S32 i = 0; i < (S32)names.size(); i++) + { + name = names[i]; + id = agent_ids[i]; + + // Make sure this agent isn't already in the list. + if (mInviteeIDs.find(id) != mInviteeIDs.end()) + { + continue; + } + + //add the name to the names list + LLSD row; + row["id"] = id; + row["columns"][0]["value"] = name; + + mInvitees->addElement(row); + mInviteeIDs.insert(id); + } +} + +void LLPanelGroupInvite::impl::submitInvitations() +{ + std::map role_member_pairs; + + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + + // Default to everyone role. + LLUUID role_id = LLUUID::null; + + if (mRoleNames) + { + role_id = mRoleNames->getCurrentID(); + + // owner role: display confirmation and wait for callback + if ((role_id == gdatap->mOwnerRole) && (!mConfirmedOwnerInvite)) + { + LLSD args; + args["MESSAGE"] = mOwnerWarning; + LLNotificationsUtil::add("GenericAlertYesCancel", args, LLSD(), boost::bind(&LLPanelGroupInvite::impl::inviteOwnerCallback, this, _1, _2)); + return; // we'll be called again if user confirms + } + } + + bool already_in_group = false; + //loop over the users + std::vector items = mInvitees->getAllData(); + for (std::vector::iterator iter = items.begin(); + iter != items.end(); ++iter) + { + LLScrollListItem* item = *iter; + if(LLGroupActions::isAvatarMemberOfGroup(mGroupID, item->getUUID())) + { + already_in_group = true; + continue; + } + role_member_pairs[item->getUUID()] = role_id; + } + + if (role_member_pairs.size() > MAX_GROUP_INVITES) + { + // Fail! + LLSD msg; + msg["MESSAGE"] = mTooManySelected; + LLNotificationsUtil::add("GenericAlert", msg); + (*mCloseCallback)(mCloseCallbackUserData); + return; + } + + LLGroupMgr::getInstance()->sendGroupMemberInvites(mGroupID, role_member_pairs); + + if(already_in_group) + { + LLSD msg; + msg["MESSAGE"] = mAlreadyInGroup; + LLNotificationsUtil::add("GenericAlert", msg); + } + + //then close + (*mCloseCallback)(mCloseCallbackUserData); +} + +bool LLPanelGroupInvite::impl::inviteOwnerCallback(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + + switch(option) + { + case 0: + // user confirmed that they really want a new group owner + mConfirmedOwnerInvite = true; + submitInvitations(); + break; + case 1: + // fall through + default: + break; + } + return false; +} + + + +void LLPanelGroupInvite::impl::addRoleNames(LLGroupMgrGroupData* gdatap) +{ + LLGroupMgrGroupData::member_list_t::iterator agent_iter = + gdatap->mMembers.find(gAgent.getID()); + + //loop over the agent's roles in the group + //then add those roles to the list of roles that the agent + //can invite people to be. + //if the user is the owner then we add + //all of the roles in the group, + //else if they have the add to roles power + //we add every role but owner, + //else if they have the limited add to roles power + //we add every role the user is in, + //else we just add to everyone + bool is_owner = false; + bool can_assign_any = gAgent.hasPowerInGroup(mGroupID, + GP_ROLE_ASSIGN_MEMBER); + bool can_assign_limited = gAgent.hasPowerInGroup(mGroupID, + GP_ROLE_ASSIGN_MEMBER_LIMITED); + LLGroupMemberData* member_data = NULL; + //get the member data for the agent if it exists + if (agent_iter != gdatap->mMembers.end()) + { + member_data = (*agent_iter).second; + if (member_data && mRoleNames) + { + is_owner = member_data->isOwner(); + }//end if member data is not null + }//end if agent is in the group + + + + LLGroupMgrGroupData::role_list_t::iterator rit = gdatap->mRoles.begin(); + LLGroupMgrGroupData::role_list_t::iterator end = gdatap->mRoles.end(); + + //populate the role list: + for ( ; rit != end; ++rit) + { + LLUUID role_id = (*rit).first; + LLRoleData rd; + if ( gdatap->getRoleData(role_id,rd) ) + { + // Owners can add any role. + if ( is_owner + // Even 'can_assign_any' can't add owner role. + || (can_assign_any && role_id != gdatap->mOwnerRole) + // Add all roles user is in + || (can_assign_limited && member_data && member_data->isInRole(role_id)) + // Everyone role. + || role_id == LLUUID::null ) + { + mRoleNames->add(rd.mRoleName, + role_id, + ADD_BOTTOM); + } + } + } +} + +//static +void LLPanelGroupInvite::impl::callbackClickAdd(void* userdata) +{ + LLPanelGroupInvite* panelp = (LLPanelGroupInvite*) userdata; + + if ( panelp ) + { + //Right now this is hard coded with some knowledge that it is part + //of a floater since the avatar picker needs to be added as a dependent + //floater to the parent floater. + //Soon the avatar picker will be embedded into this panel + //instead of being it's own separate floater. But that is next week. + //This will do for now. -jwolk May 10, 2006 + LLView * button = panelp->findChild("add_button"); + LLFloater * root_floater = gFloaterView->getParentFloater(panelp); + LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show( + boost::bind(impl::callbackAddUsers, _1, panelp->mImplementation), true, false, false, root_floater->getName(), button); + if (picker) + { + root_floater->addDependentFloater(picker); + } + } +} + +//static +void LLPanelGroupInvite::impl::callbackClickRemove(void* userdata) +{ + impl* selfp = (impl*) userdata; + + if ( selfp ) selfp->handleRemove(); +} + +void LLPanelGroupInvite::impl::handleRemove() +{ + // Check if there is anything selected. + std::vector selection = + mInvitees->getAllSelected(); + if (selection.empty()) return; + + std::vector::iterator iter; + for(iter = selection.begin(); iter != selection.end(); ++iter) + { + mInviteeIDs.erase( (*iter)->getUUID() ); + } + + // Remove all selected invitees. + mInvitees->deleteSelectedItems(); + mRemoveButton->setEnabled(false); +} + +// static +void LLPanelGroupInvite::impl::callbackSelect( + LLUICtrl* ctrl, void* userdata) +{ + impl* selfp = (impl*) userdata; + if ( selfp ) selfp->handleSelection(); +} + +void LLPanelGroupInvite::impl::handleSelection() +{ + // Check if there is anything selected. + std::vector selection = + mInvitees->getAllSelected(); + if (selection.empty()) + { + mRemoveButton->setEnabled(false); + } + else + { + mRemoveButton->setEnabled(true); + } +} + +void LLPanelGroupInvite::impl::callbackClickCancel(void* userdata) +{ + impl* selfp = (impl*) userdata; + + if ( selfp ) + { + (*(selfp->mCloseCallback))(selfp->mCloseCallbackUserData); + } +} + +void LLPanelGroupInvite::impl::callbackClickOK(void* userdata) +{ + impl* selfp = (impl*) userdata; + + if ( selfp ) selfp->submitInvitations(); +} + + + +//static +void LLPanelGroupInvite::impl::callbackAddUsers(const uuid_vec_t& agent_ids, void* user_data) +{ + std::vector names; + for (S32 i = 0; i < (S32)agent_ids.size(); i++) + { + LLAvatarName av_name; + if (LLAvatarNameCache::get(agent_ids[i], &av_name)) + { + LLPanelGroupInvite::impl::onAvatarNameCache(agent_ids[i], av_name, user_data); + } + else + { + impl* selfp = (impl*) user_data; + if (selfp) + { + if (selfp->mAvatarNameCacheConnection.connected()) + { + selfp->mAvatarNameCacheConnection.disconnect(); + } + // *TODO : Add a callback per avatar name being fetched. + selfp->mAvatarNameCacheConnection = LLAvatarNameCache::get(agent_ids[i],boost::bind(&LLPanelGroupInvite::impl::onAvatarNameCache, _1, _2, user_data)); + } + } + } + +} + +void LLPanelGroupInvite::impl::onAvatarNameCache(const LLUUID& agent_id, + const LLAvatarName& av_name, + void* user_data) +{ + impl* selfp = (impl*) user_data; + + if (selfp) + { + if (selfp->mAvatarNameCacheConnection.connected()) + { + selfp->mAvatarNameCacheConnection.disconnect(); + } + std::vector names; + uuid_vec_t agent_ids; + agent_ids.push_back(agent_id); + names.push_back(av_name.getCompleteName()); + + selfp->addUsers(names, agent_ids); + } +} + + +LLPanelGroupInvite::LLPanelGroupInvite(const LLUUID& group_id) + : LLPanel(), + mImplementation(new impl(group_id)), + mPendingUpdate(false) +{ + // Pass on construction of this panel to the control factory. + buildFromFile( "panel_group_invite.xml"); +} + +LLPanelGroupInvite::~LLPanelGroupInvite() +{ + delete mImplementation; +} + +void LLPanelGroupInvite::setCloseCallback(void (*close_callback)(void*), + void* data) +{ + mImplementation->mCloseCallback = close_callback; + mImplementation->mCloseCallbackUserData = data; +} + +void LLPanelGroupInvite::clear() +{ + mStoreSelected = LLUUID::null; + mImplementation->mInvitees->deleteAllItems(); + mImplementation->mRoleNames->clear(); + mImplementation->mRoleNames->removeall(); + mImplementation->mOKButton->setEnabled(false); + mImplementation->mInviteeIDs.clear(); +} + +void LLPanelGroupInvite::addUsers(uuid_vec_t& agent_ids) +{ + std::vector names; + for (S32 i = 0; i < (S32)agent_ids.size(); i++) + { + std::string fullname; + LLUUID agent_id = agent_ids[i]; + LLViewerObject* dest = gObjectList.findObject(agent_id); + if(dest && dest->isAvatar()) + { + LLNameValue* nvfirst = dest->getNVPair("FirstName"); + LLNameValue* nvlast = dest->getNVPair("LastName"); + if(nvfirst && nvlast) + { + fullname = LLCacheName::buildFullName( + nvfirst->getString(), nvlast->getString()); + + } + if (!fullname.empty()) + { + names.push_back(fullname); + } + else + { + LL_WARNS() << "llPanelGroupInvite: Selected avatar has no name: " << dest->getID() << LL_ENDL; + names.push_back("(Unknown)"); + } + } + else + { + //looks like user try to invite offline avatar (or the avatar from the other region) + //for offline avatar_id gObjectList.findObject() will return null + //so we need to do this additional search in avatar tracker, see EXT-4732 + LLAvatarName av_name; + if (!LLAvatarNameCache::get(agent_id, &av_name)) + { + // actually it should happen, just in case + //LLAvatarNameCache::get(LLUUID(agent_id), boost::bind(&LLPanelGroupInvite::addUserCallback, this, _1, _2)); + // for this special case! + //when there is no cached name we should remove resident from agent_ids list to avoid breaking of sequence + // removed id will be added in callback + agent_ids.erase(agent_ids.begin() + i); + } + else + { + names.push_back(av_name.getAccountName()); + } + } + } + mImplementation->addUsers(names, agent_ids); +} + +void LLPanelGroupInvite::addUserCallback(const LLUUID& id, const LLAvatarName& av_name) +{ + std::vector names; + uuid_vec_t agent_ids; + agent_ids.push_back(id); + names.push_back(av_name.getAccountName()); + + mImplementation->addUsers(names, agent_ids); +} + +void LLPanelGroupInvite::draw() +{ + LLPanel::draw(); + if (mPendingUpdate) + { + updateLists(); + } +} + +void LLPanelGroupInvite::update() +{ + mPendingUpdate = false; + if (mImplementation->mGroupName) + { + mImplementation->mGroupName->setText(mImplementation->mLoadingText); + } + if ( mImplementation->mRoleNames ) + { + mStoreSelected = mImplementation->mRoleNames->getCurrentID(); + mImplementation->mRoleNames->clear(); + mImplementation->mRoleNames->removeall(); + mImplementation->mRoleNames->add(mImplementation->mLoadingText, LLUUID::null, ADD_BOTTOM); + mImplementation->mRoleNames->setCurrentByID(LLUUID::null); + } + + updateLists(); +} + +void LLPanelGroupInvite::updateLists() +{ + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mImplementation->mGroupID); + bool waiting = false; + + if (gdatap) + { + if (gdatap->isGroupPropertiesDataComplete()) + { + if (mImplementation->mGroupName) + { + mImplementation->mGroupName->setText(gdatap->mName); + } + } + else + { + waiting = true; + } + if (gdatap->isRoleDataComplete() && gdatap->isMemberDataComplete() + && (gdatap->isRoleMemberDataComplete() || !gdatap->mMembers.size())) // MAINT-5270: large groups receives an empty members list without some powers, so RoleMemberData wouldn't be complete for them + { + if ( mImplementation->mRoleNames ) + { + mImplementation->mRoleNames->clear(); + mImplementation->mRoleNames->removeall(); + + //add the role names and select the everybody role by default + mImplementation->addRoleNames(gdatap); + mImplementation->mRoleNames->setCurrentByID(mStoreSelected); + } + } + else + { + waiting = true; + } + } + else + { + waiting = true; + } + + if (waiting) + { + if (!mPendingUpdate) + { + // Note: this will partially fail if some requests are already in progress + LLGroupMgr::getInstance()->sendGroupPropertiesRequest(mImplementation->mGroupID); + LLGroupMgr::getInstance()->sendGroupRoleDataRequest(mImplementation->mGroupID); + LLGroupMgr::getInstance()->sendGroupRoleMembersRequest(mImplementation->mGroupID); + LLGroupMgr::getInstance()->sendCapGroupMembersRequest(mImplementation->mGroupID); + } + else if (gdatap) + { + // restart requests that were interrupted/dropped/failed to start + if (!gdatap->isRoleDataPending() && !gdatap->isRoleDataComplete()) + { + LLGroupMgr::getInstance()->sendGroupRoleDataRequest(mImplementation->mGroupID); + } + if (!gdatap->isRoleMemberDataPending() && !gdatap->isRoleMemberDataComplete()) + { + LLGroupMgr::getInstance()->sendGroupRoleMembersRequest(mImplementation->mGroupID); + } + // sendCapGroupMembersRequest has a per frame send limitation that could have + // interrupted previous request + if (!gdatap->isMemberDataPending() && !gdatap->isMemberDataComplete()) + { + LLGroupMgr::getInstance()->sendCapGroupMembersRequest(mImplementation->mGroupID); + } + } + mPendingUpdate = true; + } + else + { + mPendingUpdate = false; + if (mImplementation->mOKButton && mImplementation->mRoleNames->getItemCount()) + { + mImplementation->mOKButton->setEnabled(true); + } + } +} + +bool LLPanelGroupInvite::postBuild() +{ + constexpr bool recurse = true; + + mImplementation->mLoadingText = getString("loading"); + mImplementation->mRoleNames = getChild("role_name", + recurse); + mImplementation->mGroupName = getChild("group_name_text", recurse); + mImplementation->mInvitees = + getChild("invitee_list", recurse); + if ( mImplementation->mInvitees ) + { + mImplementation->mInvitees->setCommitOnSelectionChange(true); + mImplementation->mInvitees->setCommitCallback(impl::callbackSelect, mImplementation); + } + + LLButton* button = getChild("add_button", recurse); + if ( button ) + { + // default to opening avatarpicker automatically + // (*impl::callbackClickAdd)((void*)this); + button->setClickedCallback(impl::callbackClickAdd, this); + } + + mImplementation->mRemoveButton = + getChild("remove_button", recurse); + if ( mImplementation->mRemoveButton ) + { + mImplementation->mRemoveButton->setClickedCallback(impl::callbackClickRemove, mImplementation); + mImplementation->mRemoveButton->setEnabled(false); + } + + mImplementation->mOKButton = + getChild("invite_button", recurse); + if ( mImplementation->mOKButton ) + { + mImplementation->mOKButton->setClickedCallback(impl::callbackClickOK, mImplementation); + mImplementation->mOKButton->setEnabled(false); + } + + button = getChild("cancel_button", recurse); + if ( button ) + { + button->setClickedCallback(impl::callbackClickCancel, mImplementation); + } + + mImplementation->mOwnerWarning = getString("confirm_invite_owner_str"); + mImplementation->mAlreadyInGroup = getString("already_in_group"); + mImplementation->mTooManySelected = getString("invite_selection_too_large"); + + update(); + + return (mImplementation->mRoleNames && + mImplementation->mInvitees && + mImplementation->mRemoveButton); +} diff --git a/indra/newview/llpanelgroupinvite.h b/indra/newview/llpanelgroupinvite.h index aa50366ff7..08b7996246 100644 --- a/indra/newview/llpanelgroupinvite.h +++ b/indra/newview/llpanelgroupinvite.h @@ -1,62 +1,62 @@ -/** - * @file llpanelgroupinvite.h - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELGROUPINVITE_H -#define LL_LLPANELGROUPINVITE_H - -#include "llpanel.h" -#include "lluuid.h" - -class LLAvatarName; - -class LLPanelGroupInvite -: public LLPanel -{ -public: - LLPanelGroupInvite(const LLUUID& group_id); - ~LLPanelGroupInvite(); - - void addUsers(uuid_vec_t& agent_ids); - /** - * this callback is being used to add a user whose fullname isn't been loaded before invoking of addUsers(). - */ - void addUserCallback(const LLUUID& id, const LLAvatarName& av_name); - void clear() override; - void update(); - - void setCloseCallback(void (*close_callback)(void*), void* data); - - void draw() override; - bool postBuild() override; -protected: - class impl; - impl* mImplementation; - - bool mPendingUpdate; - LLUUID mStoreSelected; - void updateLists(); -}; - -#endif +/** + * @file llpanelgroupinvite.h + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELGROUPINVITE_H +#define LL_LLPANELGROUPINVITE_H + +#include "llpanel.h" +#include "lluuid.h" + +class LLAvatarName; + +class LLPanelGroupInvite +: public LLPanel +{ +public: + LLPanelGroupInvite(const LLUUID& group_id); + ~LLPanelGroupInvite(); + + void addUsers(uuid_vec_t& agent_ids); + /** + * this callback is being used to add a user whose fullname isn't been loaded before invoking of addUsers(). + */ + void addUserCallback(const LLUUID& id, const LLAvatarName& av_name); + void clear() override; + void update(); + + void setCloseCallback(void (*close_callback)(void*), void* data); + + void draw() override; + bool postBuild() override; +protected: + class impl; + impl* mImplementation; + + bool mPendingUpdate; + LLUUID mStoreSelected; + void updateLists(); +}; + +#endif diff --git a/indra/newview/llpanelgrouplandmoney.cpp b/indra/newview/llpanelgrouplandmoney.cpp index 3f4368e022..987782836b 100644 --- a/indra/newview/llpanelgrouplandmoney.cpp +++ b/indra/newview/llpanelgrouplandmoney.cpp @@ -1,1652 +1,1652 @@ -/** - * @file llpanelgrouplandmoney.cpp - * @brief Panel for group land and L$. - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelgrouplandmoney.h" - -#include "lluiconstants.h" -#include "roles_constants.h" - -#include "llparcel.h" -#include "llqueryflags.h" - -#include "llagent.h" -#include "lldateutil.h" -#include "lliconctrl.h" -#include "llfloaterreg.h" -#include "lllineeditor.h" -#include "llproductinforequest.h" -#include "llscrolllistctrl.h" -#include "llscrolllistitem.h" -#include "llscrolllistcell.h" -#include "lltextbox.h" -#include "lltabcontainer.h" -#include "lltexteditor.h" -#include "lltrans.h" -#include "lltransactiontypes.h" -#include "lltrans.h" -#include "lluictrlfactory.h" - -#include "llstatusbar.h" -#include "llfloaterworldmap.h" -#include "llviewermessage.h" - -static LLPanelInjector t_panel_group_money("panel_group_land_money"); - - - -//////////////////////////////////////////////////////////////////////////// -//************************************************* -//** LLGroupMoneyTabEventHandler::impl Functions ** -//************************************************* - -class LLGroupMoneyTabEventHandlerImpl -{ -public: - LLGroupMoneyTabEventHandlerImpl(LLButton* earlier_buttonp, - LLButton* later_buttonp, - LLTextEditor* text_editorp, - LLPanel* tabpanelp, - const std::string& loading_text, - S32 interval_length_days, - S32 max_interval_days); - ~LLGroupMoneyTabEventHandlerImpl(); - - bool getCanClickLater(); - bool getCanClickEarlier(); - - void updateButtons(); - - void setGroupID(const LLUUID& group_id) { mGroupID = group_id; } ; - const LLUUID& getGroupID() const { return mGroupID;} - - -//member variables -public: - LLUUID mPanelID; - LLUUID mGroupID; - - LLPanel* mTabPanelp; - - int mIntervalLength; - int mMaxInterval; - int mCurrentInterval; - - LLTextEditor* mTextEditorp; - LLButton* mEarlierButtonp; - LLButton* mLaterButtonp; - - std::string mLoadingText; -}; - - -class LLGroupMoneyTabEventHandler -{ -public: - LLGroupMoneyTabEventHandler(LLButton* earlier_button, - LLButton* later_button, - LLTextEditor* text_editor, - LLTabContainer* tab_containerp, - LLPanel* panelp, - const std::string& loading_text, - S32 interval_length_days, - S32 max_interval_days); - virtual ~LLGroupMoneyTabEventHandler(); - - virtual void requestData(LLMessageSystem* msg); - virtual void processReply(LLMessageSystem* msg, void** data); - - virtual void onClickEarlier(); - virtual void onClickLater(); - virtual void onClickTab(); - - void setGroupID(const LLUUID& group_id) { if(mImplementationp) mImplementationp->setGroupID(group_id); } ; - - static void clickEarlierCallback(void* data); - static void clickLaterCallback(void* data); - - - - static std::map sInstanceIDs; - static std::map sTabsToHandlers; -protected: - LLGroupMoneyTabEventHandlerImpl* mImplementationp; -}; - -class LLGroupMoneyDetailsTabEventHandler : public LLGroupMoneyTabEventHandler -{ -public: - LLGroupMoneyDetailsTabEventHandler(LLButton* earlier_buttonp, - LLButton* later_buttonp, - LLTextEditor* text_editorp, - LLTabContainer* tab_containerp, - LLPanel* panelp, - const std::string& loading_text - ); - virtual ~LLGroupMoneyDetailsTabEventHandler(); - - virtual void requestData(LLMessageSystem* msg); - virtual void processReply(LLMessageSystem* msg, void** data); -}; - - -class LLGroupMoneySalesTabEventHandler : public LLGroupMoneyTabEventHandler -{ -public: - LLGroupMoneySalesTabEventHandler(LLButton* earlier_buttonp, - LLButton* later_buttonp, - LLTextEditor* text_editorp, - LLTabContainer* tab_containerp, - LLPanel* panelp, - const std::string& loading_text - ); - virtual ~LLGroupMoneySalesTabEventHandler(); - - virtual void requestData(LLMessageSystem* msg); - virtual void processReply(LLMessageSystem* msg, void** data); -}; - -class LLGroupMoneyPlanningTabEventHandler : public LLGroupMoneyTabEventHandler -{ -public: - LLGroupMoneyPlanningTabEventHandler(LLTextEditor* text_editor, - LLTabContainer* tab_containerp, - LLPanel* panelp, - const std::string& loading_text - ); - virtual ~LLGroupMoneyPlanningTabEventHandler(); - - virtual void requestData(LLMessageSystem* msg); - virtual void processReply(LLMessageSystem* msg, void** data); -}; - -//////////////////////////////////////////////////////////////////////////// - -class LLPanelGroupLandMoney::impl -{ -public: - impl(LLPanelGroupLandMoney& panel); //constructor - virtual ~impl(); - - void requestGroupLandInfo(); - - int getStoredContribution(); - void setYourContributionTextField(int contrib); - void setYourMaxContributionTextBox(int max); - - virtual void onMapButton(); - virtual bool applyContribution(); - virtual void processGroupLand(LLMessageSystem* msg); - - static void mapCallback(void* data); - static void contributionCommitCallback(LLUICtrl* ctrl, void* userdata); - static void contributionKeystrokeCallback(LLLineEditor* caller, void* userdata); - -//member variables -public: - LLPanelGroupLandMoney& mPanel; - - LLTextBox* mGroupOverLimitTextp; - LLIconCtrl* mGroupOverLimitIconp; - - LLLineEditor* mYourContributionEditorp; - - LLButton* mMapButtonp; - - LLGroupMoneyTabEventHandler* mMoneyDetailsTabEHp; - LLGroupMoneyTabEventHandler* mMoneyPlanningTabEHp; - LLGroupMoneyTabEventHandler* mMoneySalesTabEHp; - - LLScrollListCtrl* mGroupParcelsp; - - LLUUID mTransID; - - bool mBeenActivated; - bool mNeedsSendGroupLandRequest; - bool mNeedsApply; - - std::string mCantViewParcelsText; - std::string mCantViewAccountsText; - std::string mEmptyParcelsText; -}; - -//******************************************* -//** LLPanelGroupLandMoney::impl Functions ** -//******************************************* -LLPanelGroupLandMoney::impl::impl(LLPanelGroupLandMoney& panel) - : mPanel(panel) -{ - mTransID = LLUUID::null; - - mBeenActivated = false; - mNeedsSendGroupLandRequest = true; - mNeedsApply = false; - - mYourContributionEditorp = NULL; - mMapButtonp = NULL; - mGroupParcelsp = NULL; - mGroupOverLimitTextp = NULL; - mGroupOverLimitIconp = NULL; - - mMoneySalesTabEHp = NULL; - mMoneyPlanningTabEHp = NULL; - mMoneyDetailsTabEHp = NULL; -} - -LLPanelGroupLandMoney::impl::~impl() -{ - if ( mMoneySalesTabEHp ) delete mMoneySalesTabEHp; - if ( mMoneyDetailsTabEHp ) delete mMoneyDetailsTabEHp; - if ( mMoneyPlanningTabEHp ) delete mMoneyPlanningTabEHp; -} - -void LLPanelGroupLandMoney::impl::requestGroupLandInfo() -{ - U32 query_flags = DFQ_GROUP_OWNED; - - mTransID.generate(); - mGroupParcelsp->deleteAllItems(); - - send_places_query(mPanel.mGroupID, mTransID, "", query_flags, LLParcel::C_ANY, ""); -} - -void LLPanelGroupLandMoney::impl::onMapButton() -{ - LLScrollListItem* itemp; - itemp = mGroupParcelsp->getFirstSelected(); - if (!itemp) return; - - const LLScrollListCell* cellp; - cellp = itemp->getColumn(itemp->getNumColumns() - 1); // hidden column is last - - F32 global_x = 0.f; - F32 global_y = 0.f; - sscanf(cellp->getValue().asString().c_str(), "%f %f", &global_x, &global_y); - - // Hack: Use the agent's z-height - F64 global_z = gAgent.getPositionGlobal().mdV[VZ]; - - LLVector3d pos_global(global_x, global_y, global_z); - LLFloaterWorldMap* worldmap_instance = LLFloaterWorldMap::getInstance(); - if(worldmap_instance) - { - worldmap_instance->trackLocation(pos_global); - LLFloaterReg::showInstance("world_map", "center"); - } -} - -bool LLPanelGroupLandMoney::impl::applyContribution() -{ - // calculate max donation, which is sum of available and current. - S32 your_contribution = 0; - S32 sqm_avail; - - your_contribution = getStoredContribution(); - sqm_avail = your_contribution; - - if(gStatusBar) - { - sqm_avail += gStatusBar->getSquareMetersLeft(); - } - - // get new contribution and compare to available - S32 new_contribution = atoi(mYourContributionEditorp->getText().c_str()); - - if( new_contribution != your_contribution && - new_contribution >= 0 && - new_contribution <= sqm_avail ) - { - // update group info and server - if(!gAgent.setGroupContribution(mPanel.mGroupID, new_contribution)) - { - // should never happen... - LL_WARNS() << "Unable to set contribution." << LL_ENDL; - return false; - } - } - else - { - //TODO: throw up some error message here and return? right now we just - //fail silently and force the previous value -jwolk - new_contribution = your_contribution; - } - - //set your contribution - setYourContributionTextField(new_contribution); - - return true; -} - -// Retrieves the land contribution for this agent that is currently -// stored in the database, NOT what is currently entered in the text field -int LLPanelGroupLandMoney::impl::getStoredContribution() -{ - LLGroupData group_data; - - group_data.mContribution = 0; - gAgent.getGroupData(mPanel.mGroupID, group_data); - - return group_data.mContribution; -} - -// Fills in the text field with the contribution, contrib -void LLPanelGroupLandMoney::impl::setYourContributionTextField(int contrib) -{ - std::string buffer = llformat("%d", contrib); - - if ( mYourContributionEditorp ) - { - mYourContributionEditorp->setText(buffer); - } -} - -void LLPanelGroupLandMoney::impl::setYourMaxContributionTextBox(int max) -{ - mPanel.getChild("your_contribution_max_value")->setTextArg("[AMOUNT]", llformat("%d", max)); -} - -//static -void LLPanelGroupLandMoney::impl::mapCallback(void* data) -{ - LLPanelGroupLandMoney::impl* selfp = (LLPanelGroupLandMoney::impl*) data; - - if ( selfp ) selfp->onMapButton(); -} - -void LLPanelGroupLandMoney::impl::contributionCommitCallback(LLUICtrl* ctrl, - void* userdata) -{ - LLPanelGroupLandMoney* tabp = (LLPanelGroupLandMoney*) userdata; - LLLineEditor* editorp = (LLLineEditor*) ctrl; - - if ( tabp && editorp ) - { - impl* self = tabp->mImplementationp; - int your_contribution = 0; - int new_contribution = 0; - - new_contribution= atoi(editorp->getText().c_str()); - your_contribution = self->getStoredContribution(); - - //reset their junk data to be "good" data to us - self->setYourContributionTextField(new_contribution); - - //check to see if they're contribution text has changed - self->mNeedsApply = new_contribution != your_contribution; - tabp->notifyObservers(); - } -} - -void LLPanelGroupLandMoney::impl::contributionKeystrokeCallback(LLLineEditor* caller, - void* userdata) -{ - impl::contributionCommitCallback(caller, userdata); -} - -//static -void LLPanelGroupLandMoney::impl::processGroupLand(LLMessageSystem* msg) -{ - S32 count = msg->getNumberOfBlocks("QueryData"); - if(count > 0) - { - S32 first_block = 0; - - LLUUID owner_id; - LLUUID trans_id; - - msg->getUUID("QueryData", "OwnerID", owner_id, 0); - msg->getUUID("TransactionData", "TransactionID", trans_id); - - if(owner_id.isNull()) - { - // special block which has total contribution - ++first_block; - - S32 committed = 0; - S32 billable_area = 0; - - if(count == 1) - { - msg->getS32("QueryData", "BillableArea", committed, 0); - } - else - { - for(S32 i = first_block; i < count; ++i) - { - msg->getS32("QueryData", "BillableArea", billable_area, i); - committed+=billable_area; - } - } - - S32 total_contribution; - msg->getS32("QueryData", "ActualArea", total_contribution, 0); - mPanel.getChild("total_contributed_land_value")->setTextArg("[AREA]", llformat("%d", total_contribution)); - - mPanel.getChild("total_land_in_use_value")->setTextArg("[AREA]", llformat("%d", committed)); - S32 available = total_contribution - committed; - mPanel.getChild("land_available_value")->setTextArg("[AREA]", llformat("%d", available)); - - - if ( mGroupOverLimitTextp && mGroupOverLimitIconp ) - - { - mGroupOverLimitIconp->setVisible(available < 0); - mGroupOverLimitTextp->setVisible(available < 0); - } - - } - - if ( trans_id != mTransID ) return; - - // This power was removed to make group roles simpler - //if ( !gAgent.hasPowerInGroup(mGroupID, GP_LAND_VIEW_OWNED) ) return; - if (!gAgent.isInGroup(mPanel.mGroupID)) return; - - mGroupParcelsp->setCommentText(mEmptyParcelsText); - - std::string name; - std::string desc; - S32 actual_area; - S32 billable_area; - U8 flags; - F32 global_x; - F32 global_y; - std::string sim_name; - std::string land_sku; - std::string land_type; - - for(S32 i = first_block; i < count; ++i) - { - msg->getUUID("QueryData", "OwnerID", owner_id, i); - msg->getString("QueryData", "Name", name, i); - msg->getString("QueryData", "Desc", desc, i); - msg->getS32("QueryData", "ActualArea", actual_area, i); - msg->getS32("QueryData", "BillableArea", billable_area, i); - msg->getU8("QueryData", "Flags", flags, i); - msg->getF32("QueryData", "GlobalX", global_x, i); - msg->getF32("QueryData", "GlobalY", global_y, i); - msg->getString("QueryData", "SimName", sim_name, i); - - if ( msg->getSizeFast(_PREHASH_QueryData, i, _PREHASH_ProductSKU) > 0 ) - { - msg->getStringFast( _PREHASH_QueryData, _PREHASH_ProductSKU, land_sku, i); - LL_INFOS() << "Land sku: " << land_sku << LL_ENDL; - land_type = LLProductInfoRequestManager::instance().getDescriptionForSku(land_sku); - } - else - { - land_sku.clear(); - land_type = LLTrans::getString("land_type_unknown"); - } - - S32 region_x = ll_round(global_x) % REGION_WIDTH_UNITS; - S32 region_y = ll_round(global_y) % REGION_WIDTH_UNITS; - std::string location = sim_name + llformat(" (%d, %d)", region_x, region_y); - std::string area; - - - if(billable_area == actual_area) - { - area = llformat("%d", billable_area); - } - else - { - area = llformat("%d / %d", billable_area, actual_area); - } - - std::string hidden; - hidden = llformat("%f %f", global_x, global_y); - - LLSD row; - - row["columns"][0]["column"] = "name"; - row["columns"][0]["value"] = name; - row["columns"][0]["font"] = "SANSSERIF_SMALL"; - - row["columns"][1]["column"] = "location"; - row["columns"][1]["value"] = location; - row["columns"][1]["font"] = "SANSSERIF_SMALL"; - - row["columns"][2]["column"] = "area"; - row["columns"][2]["value"] = area; - row["columns"][2]["font"] = "SANSSERIF_SMALL"; - - row["columns"][3]["column"] = "type"; - row["columns"][3]["value"] = land_type; - row["columns"][3]["font"] = "SANSSERIF_SMALL"; - - // hidden is always last column - row["columns"][4]["column"] = "hidden"; - row["columns"][4]["value"] = hidden; - - mGroupParcelsp->addElement(row); - } - } -} - -//************************************* -//** LLPanelGroupLandMoney Functions ** -//************************************* - - -//static -std::map LLPanelGroupLandMoney::sGroupIDs; - -LLPanelGroupLandMoney::LLPanelGroupLandMoney() : - LLPanelGroupTab() -{ - //FIXME - add setGroupID(); - mImplementationp = new impl(*this); - - //problem what if someone has both the group floater open and the finder - //open to the same group? Some maps that map group ids to panels - //will then only be working for the last panel for a given group id :( - - //FIXME - add to setGroupID() - //LLPanelGroupLandMoney::sGroupIDs.insert(group_id, this); -} - -LLPanelGroupLandMoney::~LLPanelGroupLandMoney() -{ - delete mImplementationp; - LLPanelGroupLandMoney::sGroupIDs.erase(mGroupID); -} - -void LLPanelGroupLandMoney::activate() -{ - if ( !mImplementationp->mBeenActivated ) - { - //select the first tab - LLTabContainer* tabp = getChild("group_money_tab_container"); - - if ( tabp ) - { - tabp->selectFirstTab(); - mImplementationp->mBeenActivated = true; - } - - //fill in the max contribution - - //This calculation is unfortunately based on - //the status bar's concept of how much land the user has - //which can change dynamically if the user buys new land, gives - //more land to a group, etc. - //A race condition can occur if we want to update the UI's - //concept of the user's max contribution before the status - //bar has been updated from a change in the user's group contribution. - - //Since the max contribution should not change solely on changing - //a user's group contribution, (it would only change through - //purchasing of new land) this code is placed here - //and only updated once to prevent the race condition - //at the price of having stale data. - //We need to have the status bar have observers - //or find better way of distributing up to date land data. - jwolk - S32 max_avail = mImplementationp->getStoredContribution(); - if(gStatusBar) - { - max_avail += gStatusBar->getSquareMetersLeft(); - } - mImplementationp->setYourMaxContributionTextBox(max_avail); - } - - mImplementationp->mMapButtonp->setEnabled(false); - update(GC_ALL); -} - -void LLPanelGroupLandMoney::update(LLGroupChange gc) -{ - if (gc != GC_ALL) return; //Don't update if it's the wrong panel! - - LLTabContainer* tabp = getChild("group_money_tab_container"); - - if ( tabp ) - { - LLPanel* panelp; - LLGroupMoneyTabEventHandler* eh; - - panelp = tabp->getCurrentPanel(); - - //now pull the event handler associated with that L$ tab - if ( panelp ) - { - eh = get_if_there(LLGroupMoneyTabEventHandler::sTabsToHandlers, - panelp, - (LLGroupMoneyTabEventHandler*)NULL); - if ( eh ) eh->onClickTab(); - } - } - - mImplementationp->requestGroupLandInfo(); - mImplementationp->setYourContributionTextField(mImplementationp->getStoredContribution()); -} - -bool LLPanelGroupLandMoney::needsApply(std::string& mesg) -{ - return mImplementationp->mNeedsApply; -} - -bool LLPanelGroupLandMoney::apply(std::string& mesg) -{ - if (!mImplementationp->applyContribution() ) - { - mesg = getString("land_contrib_error"); - return false; - } - - mImplementationp->mNeedsApply = false; - notifyObservers(); - - return true; -} - -void LLPanelGroupLandMoney::cancel() -{ - //set the contribution back to the "stored value" - mImplementationp->setYourContributionTextField(mImplementationp->getStoredContribution()); - - mImplementationp->mNeedsApply = false; - notifyObservers(); -} - - -bool LLPanelGroupLandMoney::postBuild() -{ - /* This power was removed to make group roles simpler - bool has_parcel_view = gAgent.hasPowerInGroup(mGroupID, - GP_LAND_VIEW_OWNED); - bool has_accounting_view = gAgent.hasPowerInGroup(mGroupID, - GP_ACCOUNTING_VIEW); - */ - - bool can_view = gAgent.isInGroup(mGroupID); - - mImplementationp->mGroupOverLimitIconp = - getChild("group_over_limit_icon"); - mImplementationp->mGroupOverLimitTextp = - getChild("group_over_limit_text"); - - mImplementationp->mYourContributionEditorp - = getChild("your_contribution_line_editor"); - if ( mImplementationp->mYourContributionEditorp ) - { - LLLineEditor* editor = mImplementationp->mYourContributionEditorp; - - editor->setCommitCallback(mImplementationp->contributionCommitCallback, this); - editor->setKeystrokeCallback(mImplementationp->contributionKeystrokeCallback, this); - } - - mImplementationp->mMapButtonp = getChild("map_button"); - - mImplementationp->mGroupParcelsp = - getChild("group_parcel_list"); - - if ( mImplementationp->mGroupParcelsp ) - { - mImplementationp->mGroupParcelsp->setCommitCallback(boost::bind(&LLPanelGroupLandMoney::onLandSelectionChanged, this)); - mImplementationp->mGroupParcelsp->setCommitOnSelectionChange(true); - } - - mImplementationp->mCantViewParcelsText = getString("cant_view_group_land_text"); - mImplementationp->mCantViewAccountsText = getString("cant_view_group_accounting_text"); - mImplementationp->mEmptyParcelsText = getString("epmty_view_group_land_text"); - - if ( mImplementationp->mMapButtonp ) - { - mImplementationp->mMapButtonp->setClickedCallback(LLPanelGroupLandMoney::impl::mapCallback, mImplementationp); - } - - if ( mImplementationp->mGroupOverLimitTextp ) - { - mImplementationp->mGroupOverLimitTextp->setVisible(false); - } - - if ( mImplementationp->mGroupOverLimitIconp ) - { - mImplementationp->mGroupOverLimitIconp->setVisible(false); - } - - if ( !can_view ) - { - if ( mImplementationp->mGroupParcelsp ) - { - mImplementationp->mGroupParcelsp->setCommentText( - mImplementationp->mCantViewParcelsText); - mImplementationp->mGroupParcelsp->setEnabled(false); - } - } - - - - LLButton* earlierp, *laterp; - LLTextEditor* textp; - LLPanel* panelp; - - LLTabContainer* tabcp = getChild("group_money_tab_container"); - - if ( !can_view ) - { - if ( tabcp ) - { - S32 i; - S32 tab_count = tabcp->getTabCount(); - - for (i = tab_count - 1; i >=0; --i) - { - tabcp->enableTabButton(i, false); - } - } - } - - std::string loading_text = getString("loading_txt"); - - //pull out the widgets for the L$ details tab - earlierp = getChild("earlier_details_button", true); - laterp = getChild("later_details_button", true); - textp = getChild("group_money_details_text", true); - panelp = getChild("group_money_details_tab", true); - - if ( !can_view ) - { - textp->setText(mImplementationp->mCantViewAccountsText); - } - else - { - mImplementationp->mMoneyDetailsTabEHp = - new LLGroupMoneyDetailsTabEventHandler(earlierp, - laterp, - textp, - tabcp, - panelp, - loading_text); - } - - textp = getChild("group_money_planning_text", true); - panelp = getChild("group_money_planning_tab", true); - - if ( !can_view ) - { - textp->setText(mImplementationp->mCantViewAccountsText); - } - else - { - //Temporally disabled for DEV-11287. - mImplementationp->mMoneyPlanningTabEHp = - new LLGroupMoneyPlanningTabEventHandler(textp, - tabcp, - panelp, - loading_text); - } - - //pull out the widgets for the L$ sales tab - earlierp = getChild("earlier_sales_button", true); - laterp = getChild("later_sales_button", true); - textp = getChild("group_money_sales_text", true); - panelp = getChild("group_money_sales_tab", true); - - if ( !can_view ) - { - textp->setText(mImplementationp->mCantViewAccountsText); - } - else - { - mImplementationp->mMoneySalesTabEHp = - new LLGroupMoneySalesTabEventHandler(earlierp, - laterp, - textp, - tabcp, - panelp, - loading_text); - } - - return LLPanelGroupTab::postBuild(); -} - -void LLPanelGroupLandMoney::onLandSelectionChanged() -{ - mImplementationp->mMapButtonp->setEnabled( mImplementationp->mGroupParcelsp->getItemCount() > 0 ); -} - -bool LLPanelGroupLandMoney::isVisibleByAgent(LLAgent* agentp) -{ - return mAllowEdit && agentp->isInGroup(mGroupID); -} - -void LLPanelGroupLandMoney::processPlacesReply(LLMessageSystem* msg, void**) -{ - LLUUID group_id; - msg->getUUID("AgentData", "QueryID", group_id); - - group_id_map_t::iterator found_it = sGroupIDs.find(group_id); - if(found_it == sGroupIDs.end()) - { - LL_INFOS() << "Group Panel Land L$ " << group_id << " no longer in existence." - << LL_ENDL; - return; - } - - found_it->second->mImplementationp->processGroupLand(msg); -} - - -LLGroupMoneyTabEventHandlerImpl::LLGroupMoneyTabEventHandlerImpl(LLButton* earlier_buttonp, - LLButton* later_buttonp, - LLTextEditor* text_editorp, - LLPanel* tabpanelp, - const std::string& loading_text, - S32 interval_length_days, - S32 max_interval_days) -{ - mPanelID.generate(); - - mIntervalLength = interval_length_days; - mMaxInterval = max_interval_days; - mCurrentInterval = 0; - - mTextEditorp = text_editorp; - mEarlierButtonp = earlier_buttonp; - mLaterButtonp = later_buttonp; - mTabPanelp = tabpanelp; - - mLoadingText = loading_text; -} - -LLGroupMoneyTabEventHandlerImpl::~LLGroupMoneyTabEventHandlerImpl() -{ -} - -bool LLGroupMoneyTabEventHandlerImpl::getCanClickEarlier() -{ - return (mCurrentInterval < mMaxInterval); -} - -bool LLGroupMoneyTabEventHandlerImpl::getCanClickLater() -{ - return ( mCurrentInterval > 0 ); -} - -void LLGroupMoneyTabEventHandlerImpl::updateButtons() -{ - if ( mEarlierButtonp ) - { - mEarlierButtonp->setEnabled(getCanClickEarlier()); - } - if ( mLaterButtonp ) - { - mLaterButtonp->setEnabled(getCanClickLater()); - } -} - -//******************************************* -//** LLGroupMoneyTabEventHandler Functions ** -//******************************************* - -std::map LLGroupMoneyTabEventHandler::sInstanceIDs; -std::map LLGroupMoneyTabEventHandler::sTabsToHandlers; - -LLGroupMoneyTabEventHandler::LLGroupMoneyTabEventHandler(LLButton* earlier_buttonp, - LLButton* later_buttonp, - LLTextEditor* text_editorp, - LLTabContainer* tab_containerp, - LLPanel* panelp, - const std::string& loading_text, - S32 interval_length_days, - S32 max_interval_days) -{ - mImplementationp = new LLGroupMoneyTabEventHandlerImpl(earlier_buttonp, - later_buttonp, - text_editorp, - panelp, - loading_text, - interval_length_days, - max_interval_days); - - if ( earlier_buttonp ) - { - earlier_buttonp->setClickedCallback(clickEarlierCallback, this); - } - - if ( later_buttonp ) - { - later_buttonp->setClickedCallback(clickLaterCallback, this); - } - - mImplementationp->updateButtons(); - - if ( tab_containerp && panelp ) - { - tab_containerp->setCommitCallback(boost::bind(&LLGroupMoneyTabEventHandler::onClickTab, this)); - } - - sInstanceIDs.insert(std::make_pair(mImplementationp->mPanelID, this)); - sTabsToHandlers[panelp] = this; -} - -LLGroupMoneyTabEventHandler::~LLGroupMoneyTabEventHandler() -{ - sInstanceIDs.erase(mImplementationp->mPanelID); - sTabsToHandlers.erase(mImplementationp->mTabPanelp); - - delete mImplementationp; -} - - -void LLGroupMoneyTabEventHandler::onClickTab() -{ - requestData(gMessageSystem); -} - -void LLGroupMoneyTabEventHandler::requestData(LLMessageSystem* msg) -{ - //do nothing -} - -void LLGroupMoneyTabEventHandler::processReply(LLMessageSystem* msg, void** data) -{ - //do nothing -} - -void LLGroupMoneyTabEventHandler::onClickEarlier() -{ - if ( mImplementationp->mTextEditorp) - { - mImplementationp->mTextEditorp->setText(mImplementationp->mLoadingText); - } - mImplementationp->mCurrentInterval++; - - mImplementationp->updateButtons(); - - requestData(gMessageSystem); -} - -void LLGroupMoneyTabEventHandler::onClickLater() -{ - if ( mImplementationp->mTextEditorp ) - { - mImplementationp->mTextEditorp->setText(mImplementationp->mLoadingText); - } - mImplementationp->mCurrentInterval--; - - mImplementationp->updateButtons(); - - requestData(gMessageSystem); -} - -//static -void LLGroupMoneyTabEventHandler::clickEarlierCallback(void* data) -{ - LLGroupMoneyTabEventHandler* selfp = (LLGroupMoneyTabEventHandler*) data; - - if ( selfp ) selfp->onClickEarlier(); -} - -//static -void LLGroupMoneyTabEventHandler::clickLaterCallback(void* data) -{ - LLGroupMoneyTabEventHandler* selfp = (LLGroupMoneyTabEventHandler*) data; - if ( selfp ) selfp->onClickLater(); -} - -//************************************************** -//** LLGroupMoneyDetailsTabEventHandler Functions ** -//************************************************** - -LLGroupMoneyDetailsTabEventHandler::LLGroupMoneyDetailsTabEventHandler(LLButton* earlier_buttonp, - LLButton* later_buttonp, - LLTextEditor* text_editorp, - LLTabContainer* tab_containerp, - LLPanel* panelp, - const std::string& loading_text) - : LLGroupMoneyTabEventHandler(earlier_buttonp, - later_buttonp, - text_editorp, - tab_containerp, - panelp, - loading_text, - SUMMARY_INTERVAL, - SUMMARY_MAX) -{ -} - -LLGroupMoneyDetailsTabEventHandler::~LLGroupMoneyDetailsTabEventHandler() -{ -} - -void LLGroupMoneyDetailsTabEventHandler::requestData(LLMessageSystem* msg) -{ - msg->newMessageFast(_PREHASH_GroupAccountDetailsRequest); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); - msg->addUUIDFast(_PREHASH_GroupID, mImplementationp->getGroupID() ); - msg->nextBlockFast(_PREHASH_MoneyData); - msg->addUUIDFast(_PREHASH_RequestID, mImplementationp->mPanelID ); - msg->addS32Fast(_PREHASH_IntervalDays, mImplementationp->mIntervalLength ); - msg->addS32Fast(_PREHASH_CurrentInterval, mImplementationp->mCurrentInterval); - - gAgent.sendReliableMessage(); - - if ( mImplementationp->mTextEditorp ) - { - mImplementationp->mTextEditorp->setText(mImplementationp->mLoadingText); - } - - LLGroupMoneyTabEventHandler::requestData(msg); -} - -void LLGroupMoneyDetailsTabEventHandler::processReply(LLMessageSystem* msg, - void** data) -{ - LLUUID group_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_GroupID, group_id ); - if (mImplementationp->getGroupID() != group_id) - { - LL_WARNS() << "Group Account details not for this group!" << LL_ENDL; - return; - } - - std::string start_date; - S32 interval_days; - S32 current_interval; - - msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_IntervalDays, interval_days ); - msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_CurrentInterval, current_interval ); - msg->getStringFast(_PREHASH_MoneyData, _PREHASH_StartDate, start_date); - - std::string time_str = LLTrans::getString("GroupMoneyStartDate"); - LLSD substitution; - - // We don't do time zone corrections of the calculated number of seconds - // because we don't have a full time stamp, only a date. - substitution["datetime"] = LLDateUtil::secondsSinceEpochFromString("%Y-%m-%d", start_date); - LLStringUtil::format (time_str, substitution); - - if ( interval_days != mImplementationp->mIntervalLength || - current_interval != mImplementationp->mCurrentInterval ) - { - LL_INFOS() << "Out of date details packet " << interval_days << " " - << current_interval << LL_ENDL; - return; - } - - std::string text = time_str; - text.append("\n\n"); - - S32 total_amount = 0; - S32 transactions = msg->getNumberOfBlocksFast(_PREHASH_HistoryData); - for(S32 i = 0; i < transactions; i++) - { - S32 amount = 0; - std::string desc; - - msg->getStringFast(_PREHASH_HistoryData, _PREHASH_Description, desc, i ); - msg->getS32Fast(_PREHASH_HistoryData, _PREHASH_Amount, amount, i); - - if (amount != 0) - { - text.append(llformat("%-24s %6d\n", desc.c_str(), amount)); - } - else - { - // skip it - } - - total_amount += amount; - } - - text.append(1, '\n'); - - text.append(llformat("%-24s %6d\n", LLTrans::getString("GroupMoneyTotal").c_str(), total_amount)); - - if ( mImplementationp->mTextEditorp ) - { - mImplementationp->mTextEditorp->setText(text); - } -} - -//static -void LLPanelGroupLandMoney::processGroupAccountDetailsReply(LLMessageSystem* msg, - void** data) -{ - LLUUID agent_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); - if (gAgent.getID() != agent_id) - { - LL_WARNS() << "Got group L$ history reply for another agent!" << LL_ENDL; - return; - } - - LLUUID request_id; - msg->getUUIDFast(_PREHASH_MoneyData, _PREHASH_RequestID, request_id ); - LLGroupMoneyTabEventHandler* selfp = get_ptr_in_map(LLGroupMoneyTabEventHandler::sInstanceIDs, request_id); - if (!selfp) - { - LL_WARNS() << "GroupAccountDetails received for non-existent group panel." << LL_ENDL; - return; - } - - selfp->processReply(msg, data); -} - -//************************************************ -//** LLGroupMoneySalesTabEventHandler Functions ** -//************************************************ - -LLGroupMoneySalesTabEventHandler::LLGroupMoneySalesTabEventHandler(LLButton* earlier_buttonp, - LLButton* later_buttonp, - LLTextEditor* text_editorp, - LLTabContainer* tab_containerp, - LLPanel* panelp, - const std::string& loading_text) - : LLGroupMoneyTabEventHandler(earlier_buttonp, - later_buttonp, - text_editorp, - tab_containerp, - panelp, - loading_text, - SUMMARY_INTERVAL, - SUMMARY_MAX) -{ -} - -LLGroupMoneySalesTabEventHandler::~LLGroupMoneySalesTabEventHandler() -{ -} - -void LLGroupMoneySalesTabEventHandler::requestData(LLMessageSystem* msg) -{ - msg->newMessageFast(_PREHASH_GroupAccountTransactionsRequest); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); - msg->addUUIDFast(_PREHASH_GroupID, mImplementationp->getGroupID() ); - msg->nextBlockFast(_PREHASH_MoneyData); - msg->addUUIDFast(_PREHASH_RequestID, mImplementationp->mPanelID ); - msg->addS32Fast(_PREHASH_IntervalDays, mImplementationp->mIntervalLength ); - msg->addS32Fast(_PREHASH_CurrentInterval, mImplementationp->mCurrentInterval); - - gAgent.sendReliableMessage(); - - if ( mImplementationp->mTextEditorp ) - { - mImplementationp->mTextEditorp->setText(mImplementationp->mLoadingText); - } - - LLGroupMoneyTabEventHandler::requestData(msg); -} - -void LLGroupMoneySalesTabEventHandler::processReply(LLMessageSystem* msg, - void** data) -{ - LLUUID group_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_GroupID, group_id ); - if (mImplementationp->getGroupID() != group_id) - { - LL_WARNS() << "Group Account Transactions not for this group!" << LL_ENDL; - return; - } - - std::string text = mImplementationp->mTextEditorp->getText(); - - std::string start_date; - S32 interval_days; - S32 current_interval; - - msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_IntervalDays, interval_days ); - msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_CurrentInterval, current_interval ); - msg->getStringFast(_PREHASH_MoneyData, _PREHASH_StartDate, start_date); - - if (interval_days != mImplementationp->mIntervalLength || - current_interval != mImplementationp->mCurrentInterval) - { - LL_INFOS() << "Out of date details packet " << interval_days << " " - << current_interval << LL_ENDL; - return; - } - - // If this is the first packet, clear the text, don't append. - // Start with the date. - if (text == mImplementationp->mLoadingText) - { - std::string time_str = LLTrans::getString("GroupMoneyStartDate"); - LLSD substitution; - - // We don't do time zone corrections of the calculated number of seconds - // because we don't have a full time stamp, only a date. - substitution["datetime"] = LLDateUtil::secondsSinceEpochFromString("%Y-%m-%d", start_date); - LLStringUtil::format (time_str, substitution); - - text = time_str + "\n\n"; - } - - S32 transactions = msg->getNumberOfBlocksFast(_PREHASH_HistoryData); - if (transactions == 0) - { - text.append(LLTrans::getString("none_text")); - } - else - { - for(S32 i = 0; i < transactions; i++) - { - std::string time; - S32 type = 0; - S32 amount = 0; - std::string user; - std::string item; - - msg->getStringFast(_PREHASH_HistoryData, _PREHASH_Time, time, i); - msg->getStringFast(_PREHASH_HistoryData, _PREHASH_User, user, i ); - msg->getS32Fast(_PREHASH_HistoryData, _PREHASH_Type, type, i); - msg->getStringFast(_PREHASH_HistoryData, _PREHASH_Item, item, i ); - msg->getS32Fast(_PREHASH_HistoryData, _PREHASH_Amount, amount, i); - - if (amount != 0) - { - std::string verb; - - switch(type) - { - case TRANS_OBJECT_SALE: - verb = LLTrans::getString("GroupMoneyBought").c_str(); - break; - case TRANS_GIFT: - verb = LLTrans::getString("GroupMoneyPaidYou").c_str(); - break; - case TRANS_PAY_OBJECT: - verb = LLTrans::getString("GroupMoneyPaidInto").c_str(); - break; - case TRANS_LAND_PASS_SALE: - verb = LLTrans::getString("GroupMoneyBoughtPassTo").c_str(); - break; - case TRANS_EVENT_FEE: - verb = LLTrans::getString("GroupMoneyPaidFeeForEvent").c_str(); - break; - case TRANS_EVENT_PRIZE: - verb = LLTrans::getString("GroupMoneyPaidPrizeForEvent").c_str(); - break; - default: - verb = ""; - break; - } - - std::string line = llformat("%s %6d - %s %s %s\n", time.c_str(), amount, user.c_str(), verb.c_str(), item.c_str()); - text.append(line); - } - } - } - - if ( mImplementationp->mTextEditorp) - { - mImplementationp->mTextEditorp->setText(text); - } -} - -//static -void LLPanelGroupLandMoney::processGroupAccountTransactionsReply(LLMessageSystem* msg, - void** data) -{ - LLUUID agent_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); - if (gAgent.getID() != agent_id) - { - LL_WARNS() << "Got group L$ history reply for another agent!" << LL_ENDL; - return; - } - - LLUUID request_id; - msg->getUUIDFast(_PREHASH_MoneyData, _PREHASH_RequestID, request_id ); - - LLGroupMoneyTabEventHandler* self; - - self = get_ptr_in_map(LLGroupMoneyTabEventHandler::sInstanceIDs, request_id); - if (!self) - { - LL_WARNS() << "GroupAccountTransactions recieved for non-existent group panel." << LL_ENDL; - return; - } - - self->processReply(msg, data); -} - -//*************************************************** -//** LLGroupMoneyPlanningTabEventHandler Functions ** -//*************************************************** - -LLGroupMoneyPlanningTabEventHandler::LLGroupMoneyPlanningTabEventHandler(LLTextEditor* text_editorp, - LLTabContainer* tab_containerp, - LLPanel* panelp, - const std::string& loading_text) - : LLGroupMoneyTabEventHandler(NULL, - NULL, - text_editorp, - tab_containerp, - panelp, - loading_text, - SUMMARY_INTERVAL, - SUMMARY_MAX) -{ -} - -LLGroupMoneyPlanningTabEventHandler::~LLGroupMoneyPlanningTabEventHandler() -{ -} - -void LLGroupMoneyPlanningTabEventHandler::requestData(LLMessageSystem* msg) -{ - msg->newMessageFast(_PREHASH_GroupAccountSummaryRequest); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); - msg->addUUIDFast(_PREHASH_GroupID, mImplementationp->getGroupID() ); - msg->nextBlockFast(_PREHASH_MoneyData); - msg->addUUIDFast(_PREHASH_RequestID, mImplementationp->mPanelID ); - msg->addS32Fast(_PREHASH_IntervalDays, mImplementationp->mIntervalLength); - msg->addS32Fast(_PREHASH_CurrentInterval, 0); //planning has 0 interval - - gAgent.sendReliableMessage(); - - if ( mImplementationp->mTextEditorp ) - { - mImplementationp->mTextEditorp->setText(mImplementationp->mLoadingText); - } - - LLGroupMoneyTabEventHandler::requestData(msg); -} - -void LLGroupMoneyPlanningTabEventHandler::processReply(LLMessageSystem* msg, - void** data) -{ - LLUUID group_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_GroupID, group_id ); - if (mImplementationp->getGroupID() != group_id) - { - LL_WARNS() << "Group Account Summary received not for this group!" << LL_ENDL; - return; - } - - std::string text; - - std::string start_date; - std::string last_stipend_date; - std::string next_stipend_date; - S32 interval_days; - S32 current_interval; - S32 balance; - S32 total_credits; - S32 total_debits; - S32 cur_object_tax; - S32 cur_light_tax; - S32 cur_land_tax; - S32 cur_group_tax; - S32 cur_parcel_dir_fee; - S32 proj_object_tax; - S32 proj_light_tax; - S32 proj_land_tax; - S32 proj_group_tax; - S32 proj_parcel_dir_fee; - S32 non_exempt_members; - - msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_IntervalDays, interval_days ); - msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_CurrentInterval, current_interval ); - msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_Balance, balance ); - msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_TotalCredits, total_credits ); - msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_TotalDebits, total_debits ); - msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_ObjectTaxCurrent, cur_object_tax ); - msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_LightTaxCurrent, cur_light_tax ); - msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_LandTaxCurrent, cur_land_tax ); - msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_GroupTaxCurrent, cur_group_tax ); - msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_ParcelDirFeeCurrent, cur_parcel_dir_fee ); - msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_ObjectTaxEstimate, proj_object_tax ); - msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_LightTaxEstimate, proj_light_tax ); - msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_LandTaxEstimate, proj_land_tax ); - msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_GroupTaxEstimate, proj_group_tax ); - msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_ParcelDirFeeEstimate, proj_parcel_dir_fee ); - msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_NonExemptMembers, non_exempt_members ); - - msg->getStringFast(_PREHASH_MoneyData, _PREHASH_StartDate, start_date); - msg->getStringFast(_PREHASH_MoneyData, _PREHASH_LastTaxDate, last_stipend_date); - msg->getStringFast(_PREHASH_MoneyData, _PREHASH_TaxDate, next_stipend_date); - - - if (interval_days != mImplementationp->mIntervalLength || - current_interval != mImplementationp->mCurrentInterval) - { - LL_INFOS() << "Out of date summary packet " << interval_days << " " - << current_interval << LL_ENDL; - return; - } - - text.append(LLTrans::getString("SummaryForTheWeek")); - - std::string date_format_str = LLTrans::getString("GroupPlanningDate"); - std::string time_str = date_format_str; - LLSD substitution; - // We don't do time zone corrections of the calculated number of seconds - // because we don't have a full time stamp, only a date. - substitution["datetime"] = LLDateUtil::secondsSinceEpochFromString("%Y-%m-%d", start_date); - LLStringUtil::format (time_str, substitution); - - text.append(time_str); - text.append(". "); - - if (current_interval == 0) - { - text.append(LLTrans::getString("NextStipendDay")); - - time_str = date_format_str; - substitution["datetime"] = LLDateUtil::secondsSinceEpochFromString("%Y-%m-%d", next_stipend_date); - LLStringUtil::format (time_str, substitution); - - text.append(time_str); - text.append(".\n\n"); - text.append(llformat("%-23sL$%6d\n", LLTrans::getString("GroupMoneyBalance").c_str(), balance )); - text.append(1, '\n'); - } - - // [DEV-29503] Hide the individual info since - // non_exempt_member here is a wrong choice to calculate individual shares. -// text.append( LLTrans::getString("GroupIndividualShare")); -// text.append(llformat( "%-24s %6d %6d \n", LLTrans::getString("GroupMoneyCredits").c_str(), total_credits, (S32)floor((F32)total_credits/(F32)non_exempt_members))); -// text.append(llformat( "%-24s %6d %6d \n", LLTrans::getString("GroupMoneyDebits").c_str(), total_debits, (S32)floor((F32)total_debits/(F32)non_exempt_members))); -// text.append(llformat( "%-24s %6d %6d \n", LLTrans::getString("GroupMoneyTotal").c_str(), total_credits + total_debits, (S32)floor((F32)(total_credits + total_debits)/(F32)non_exempt_members))); - - text.append(llformat( "%s\n", LLTrans::getString("GroupColumn").c_str())); - text.append(llformat( "%-24s %6d\n", LLTrans::getString("GroupMoneyCredits").c_str(), total_credits)); - text.append(llformat( "%-24s %6d\n", LLTrans::getString("GroupMoneyDebits").c_str(), total_debits)); - text.append(llformat( "%-24s %6d\n", LLTrans::getString("GroupMoneyTotal").c_str(), total_credits + total_debits)); - - if ( mImplementationp->mTextEditorp ) - { - mImplementationp->mTextEditorp->setText(text); - } -} - -//static -void LLPanelGroupLandMoney::processGroupAccountSummaryReply(LLMessageSystem* msg, - void** data) -{ - LLUUID agent_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); - if (gAgent.getID() != agent_id) - { - LL_WARNS() << "Got group L$ history reply for another agent!" << LL_ENDL; - return; - } - - LLUUID request_id; - msg->getUUIDFast(_PREHASH_MoneyData, _PREHASH_RequestID, request_id ); - - LLGroupMoneyTabEventHandler* self; - - self = get_ptr_in_map(LLGroupMoneyTabEventHandler::sInstanceIDs, request_id); - if (!self) - { - LL_WARNS() << "GroupAccountSummary recieved for non-existent group L$ planning tab." << LL_ENDL; - return; - } - - self->processReply(msg, data); -} - -void LLPanelGroupLandMoney::setGroupID(const LLUUID& id) -{ - LLPanelGroupLandMoney::sGroupIDs.erase(mGroupID); - LLPanelGroupTab::setGroupID(id); - LLPanelGroupLandMoney::sGroupIDs.insert(std::make_pair(mGroupID, this)); - - - bool can_view = gAgent.isInGroup(mGroupID); - - mImplementationp->mGroupOverLimitIconp = - getChild("group_over_limit_icon"); - mImplementationp->mGroupOverLimitTextp = - getChild("group_over_limit_text"); - - mImplementationp->mYourContributionEditorp - = getChild("your_contribution_line_editor"); - if ( mImplementationp->mYourContributionEditorp ) - { - LLLineEditor* editor = mImplementationp->mYourContributionEditorp; - - editor->setCommitCallback(mImplementationp->contributionCommitCallback, this); - editor->setKeystrokeCallback(mImplementationp->contributionKeystrokeCallback, this); - } - - mImplementationp->mMapButtonp = getChild("map_button"); - - mImplementationp->mGroupParcelsp = - getChild("group_parcel_list"); - - if ( mImplementationp->mGroupParcelsp ) - { - mImplementationp->mGroupParcelsp->setCommitCallback(boost::bind(&LLPanelGroupLandMoney::onLandSelectionChanged, this)); - mImplementationp->mGroupParcelsp->setCommitOnSelectionChange(true); - } - - mImplementationp->mCantViewParcelsText = getString("cant_view_group_land_text"); - mImplementationp->mCantViewAccountsText = getString("cant_view_group_accounting_text"); - - if ( mImplementationp->mMapButtonp ) - { - mImplementationp->mMapButtonp->setClickedCallback(LLPanelGroupLandMoney::impl::mapCallback, mImplementationp); - } - - if ( mImplementationp->mGroupOverLimitTextp ) - { - mImplementationp->mGroupOverLimitTextp->setVisible(false); - } - - if ( mImplementationp->mGroupOverLimitIconp ) - { - mImplementationp->mGroupOverLimitIconp->setVisible(false); - } - - if ( mImplementationp->mGroupParcelsp ) - { - mImplementationp->mGroupParcelsp->setEnabled(can_view); - } - - if ( !can_view && mImplementationp->mGroupParcelsp ) - { - mImplementationp->mGroupParcelsp->setEnabled(false); - } - - - LLButton* earlierp, *laterp; - LLTextEditor* textp; - LLPanel* panelp; - - LLTabContainer* tabcp = getChild("group_money_tab_container"); - - if ( tabcp ) - { - S32 i; - S32 tab_count = tabcp->getTabCount(); - - for (i = tab_count - 1; i >=0; --i) - { - tabcp->enableTabButton(i, can_view ); - } - } - - std::string loading_text = getString("loading_txt"); - - //pull out the widgets for the L$ details tab - earlierp = getChild("earlier_details_button", true); - laterp = getChild("later_details_button", true); - textp = getChild("group_money_details_text", true); - panelp = getChild("group_money_details_tab", true); - - if ( !can_view ) - { - textp->setText(mImplementationp->mCantViewAccountsText); - } - else - { - if(mImplementationp->mMoneyDetailsTabEHp == 0) - mImplementationp->mMoneyDetailsTabEHp = new LLGroupMoneyDetailsTabEventHandler(earlierp,laterp,textp,tabcp,panelp,loading_text); - mImplementationp->mMoneyDetailsTabEHp->setGroupID(mGroupID); - } - - textp = getChild("group_money_planning_text", true); - - - if ( !can_view ) - { - textp->setText(mImplementationp->mCantViewAccountsText); - } - else - { - panelp = getChild("group_money_planning_tab", true); - if(mImplementationp->mMoneyPlanningTabEHp == 0) - mImplementationp->mMoneyPlanningTabEHp = new LLGroupMoneyPlanningTabEventHandler(textp,tabcp,panelp,loading_text); - mImplementationp->mMoneyPlanningTabEHp->setGroupID(mGroupID); - } - - //pull out the widgets for the L$ sales tab - textp = getChild("group_money_sales_text", true); - - - if ( !can_view ) - { - textp->setText(mImplementationp->mCantViewAccountsText); - } - else - { - earlierp = getChild("earlier_sales_button", true); - laterp = getChild("later_sales_button", true); - panelp = getChild("group_money_sales_tab", true); - if(mImplementationp->mMoneySalesTabEHp == NULL) - mImplementationp->mMoneySalesTabEHp = new LLGroupMoneySalesTabEventHandler(earlierp,laterp,textp,tabcp,panelp,loading_text); - mImplementationp->mMoneySalesTabEHp->setGroupID(mGroupID); - } - - mImplementationp->mBeenActivated = false; - - activate(); -} - +/** + * @file llpanelgrouplandmoney.cpp + * @brief Panel for group land and L$. + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelgrouplandmoney.h" + +#include "lluiconstants.h" +#include "roles_constants.h" + +#include "llparcel.h" +#include "llqueryflags.h" + +#include "llagent.h" +#include "lldateutil.h" +#include "lliconctrl.h" +#include "llfloaterreg.h" +#include "lllineeditor.h" +#include "llproductinforequest.h" +#include "llscrolllistctrl.h" +#include "llscrolllistitem.h" +#include "llscrolllistcell.h" +#include "lltextbox.h" +#include "lltabcontainer.h" +#include "lltexteditor.h" +#include "lltrans.h" +#include "lltransactiontypes.h" +#include "lltrans.h" +#include "lluictrlfactory.h" + +#include "llstatusbar.h" +#include "llfloaterworldmap.h" +#include "llviewermessage.h" + +static LLPanelInjector t_panel_group_money("panel_group_land_money"); + + + +//////////////////////////////////////////////////////////////////////////// +//************************************************* +//** LLGroupMoneyTabEventHandler::impl Functions ** +//************************************************* + +class LLGroupMoneyTabEventHandlerImpl +{ +public: + LLGroupMoneyTabEventHandlerImpl(LLButton* earlier_buttonp, + LLButton* later_buttonp, + LLTextEditor* text_editorp, + LLPanel* tabpanelp, + const std::string& loading_text, + S32 interval_length_days, + S32 max_interval_days); + ~LLGroupMoneyTabEventHandlerImpl(); + + bool getCanClickLater(); + bool getCanClickEarlier(); + + void updateButtons(); + + void setGroupID(const LLUUID& group_id) { mGroupID = group_id; } ; + const LLUUID& getGroupID() const { return mGroupID;} + + +//member variables +public: + LLUUID mPanelID; + LLUUID mGroupID; + + LLPanel* mTabPanelp; + + int mIntervalLength; + int mMaxInterval; + int mCurrentInterval; + + LLTextEditor* mTextEditorp; + LLButton* mEarlierButtonp; + LLButton* mLaterButtonp; + + std::string mLoadingText; +}; + + +class LLGroupMoneyTabEventHandler +{ +public: + LLGroupMoneyTabEventHandler(LLButton* earlier_button, + LLButton* later_button, + LLTextEditor* text_editor, + LLTabContainer* tab_containerp, + LLPanel* panelp, + const std::string& loading_text, + S32 interval_length_days, + S32 max_interval_days); + virtual ~LLGroupMoneyTabEventHandler(); + + virtual void requestData(LLMessageSystem* msg); + virtual void processReply(LLMessageSystem* msg, void** data); + + virtual void onClickEarlier(); + virtual void onClickLater(); + virtual void onClickTab(); + + void setGroupID(const LLUUID& group_id) { if(mImplementationp) mImplementationp->setGroupID(group_id); } ; + + static void clickEarlierCallback(void* data); + static void clickLaterCallback(void* data); + + + + static std::map sInstanceIDs; + static std::map sTabsToHandlers; +protected: + LLGroupMoneyTabEventHandlerImpl* mImplementationp; +}; + +class LLGroupMoneyDetailsTabEventHandler : public LLGroupMoneyTabEventHandler +{ +public: + LLGroupMoneyDetailsTabEventHandler(LLButton* earlier_buttonp, + LLButton* later_buttonp, + LLTextEditor* text_editorp, + LLTabContainer* tab_containerp, + LLPanel* panelp, + const std::string& loading_text + ); + virtual ~LLGroupMoneyDetailsTabEventHandler(); + + virtual void requestData(LLMessageSystem* msg); + virtual void processReply(LLMessageSystem* msg, void** data); +}; + + +class LLGroupMoneySalesTabEventHandler : public LLGroupMoneyTabEventHandler +{ +public: + LLGroupMoneySalesTabEventHandler(LLButton* earlier_buttonp, + LLButton* later_buttonp, + LLTextEditor* text_editorp, + LLTabContainer* tab_containerp, + LLPanel* panelp, + const std::string& loading_text + ); + virtual ~LLGroupMoneySalesTabEventHandler(); + + virtual void requestData(LLMessageSystem* msg); + virtual void processReply(LLMessageSystem* msg, void** data); +}; + +class LLGroupMoneyPlanningTabEventHandler : public LLGroupMoneyTabEventHandler +{ +public: + LLGroupMoneyPlanningTabEventHandler(LLTextEditor* text_editor, + LLTabContainer* tab_containerp, + LLPanel* panelp, + const std::string& loading_text + ); + virtual ~LLGroupMoneyPlanningTabEventHandler(); + + virtual void requestData(LLMessageSystem* msg); + virtual void processReply(LLMessageSystem* msg, void** data); +}; + +//////////////////////////////////////////////////////////////////////////// + +class LLPanelGroupLandMoney::impl +{ +public: + impl(LLPanelGroupLandMoney& panel); //constructor + virtual ~impl(); + + void requestGroupLandInfo(); + + int getStoredContribution(); + void setYourContributionTextField(int contrib); + void setYourMaxContributionTextBox(int max); + + virtual void onMapButton(); + virtual bool applyContribution(); + virtual void processGroupLand(LLMessageSystem* msg); + + static void mapCallback(void* data); + static void contributionCommitCallback(LLUICtrl* ctrl, void* userdata); + static void contributionKeystrokeCallback(LLLineEditor* caller, void* userdata); + +//member variables +public: + LLPanelGroupLandMoney& mPanel; + + LLTextBox* mGroupOverLimitTextp; + LLIconCtrl* mGroupOverLimitIconp; + + LLLineEditor* mYourContributionEditorp; + + LLButton* mMapButtonp; + + LLGroupMoneyTabEventHandler* mMoneyDetailsTabEHp; + LLGroupMoneyTabEventHandler* mMoneyPlanningTabEHp; + LLGroupMoneyTabEventHandler* mMoneySalesTabEHp; + + LLScrollListCtrl* mGroupParcelsp; + + LLUUID mTransID; + + bool mBeenActivated; + bool mNeedsSendGroupLandRequest; + bool mNeedsApply; + + std::string mCantViewParcelsText; + std::string mCantViewAccountsText; + std::string mEmptyParcelsText; +}; + +//******************************************* +//** LLPanelGroupLandMoney::impl Functions ** +//******************************************* +LLPanelGroupLandMoney::impl::impl(LLPanelGroupLandMoney& panel) + : mPanel(panel) +{ + mTransID = LLUUID::null; + + mBeenActivated = false; + mNeedsSendGroupLandRequest = true; + mNeedsApply = false; + + mYourContributionEditorp = NULL; + mMapButtonp = NULL; + mGroupParcelsp = NULL; + mGroupOverLimitTextp = NULL; + mGroupOverLimitIconp = NULL; + + mMoneySalesTabEHp = NULL; + mMoneyPlanningTabEHp = NULL; + mMoneyDetailsTabEHp = NULL; +} + +LLPanelGroupLandMoney::impl::~impl() +{ + if ( mMoneySalesTabEHp ) delete mMoneySalesTabEHp; + if ( mMoneyDetailsTabEHp ) delete mMoneyDetailsTabEHp; + if ( mMoneyPlanningTabEHp ) delete mMoneyPlanningTabEHp; +} + +void LLPanelGroupLandMoney::impl::requestGroupLandInfo() +{ + U32 query_flags = DFQ_GROUP_OWNED; + + mTransID.generate(); + mGroupParcelsp->deleteAllItems(); + + send_places_query(mPanel.mGroupID, mTransID, "", query_flags, LLParcel::C_ANY, ""); +} + +void LLPanelGroupLandMoney::impl::onMapButton() +{ + LLScrollListItem* itemp; + itemp = mGroupParcelsp->getFirstSelected(); + if (!itemp) return; + + const LLScrollListCell* cellp; + cellp = itemp->getColumn(itemp->getNumColumns() - 1); // hidden column is last + + F32 global_x = 0.f; + F32 global_y = 0.f; + sscanf(cellp->getValue().asString().c_str(), "%f %f", &global_x, &global_y); + + // Hack: Use the agent's z-height + F64 global_z = gAgent.getPositionGlobal().mdV[VZ]; + + LLVector3d pos_global(global_x, global_y, global_z); + LLFloaterWorldMap* worldmap_instance = LLFloaterWorldMap::getInstance(); + if(worldmap_instance) + { + worldmap_instance->trackLocation(pos_global); + LLFloaterReg::showInstance("world_map", "center"); + } +} + +bool LLPanelGroupLandMoney::impl::applyContribution() +{ + // calculate max donation, which is sum of available and current. + S32 your_contribution = 0; + S32 sqm_avail; + + your_contribution = getStoredContribution(); + sqm_avail = your_contribution; + + if(gStatusBar) + { + sqm_avail += gStatusBar->getSquareMetersLeft(); + } + + // get new contribution and compare to available + S32 new_contribution = atoi(mYourContributionEditorp->getText().c_str()); + + if( new_contribution != your_contribution && + new_contribution >= 0 && + new_contribution <= sqm_avail ) + { + // update group info and server + if(!gAgent.setGroupContribution(mPanel.mGroupID, new_contribution)) + { + // should never happen... + LL_WARNS() << "Unable to set contribution." << LL_ENDL; + return false; + } + } + else + { + //TODO: throw up some error message here and return? right now we just + //fail silently and force the previous value -jwolk + new_contribution = your_contribution; + } + + //set your contribution + setYourContributionTextField(new_contribution); + + return true; +} + +// Retrieves the land contribution for this agent that is currently +// stored in the database, NOT what is currently entered in the text field +int LLPanelGroupLandMoney::impl::getStoredContribution() +{ + LLGroupData group_data; + + group_data.mContribution = 0; + gAgent.getGroupData(mPanel.mGroupID, group_data); + + return group_data.mContribution; +} + +// Fills in the text field with the contribution, contrib +void LLPanelGroupLandMoney::impl::setYourContributionTextField(int contrib) +{ + std::string buffer = llformat("%d", contrib); + + if ( mYourContributionEditorp ) + { + mYourContributionEditorp->setText(buffer); + } +} + +void LLPanelGroupLandMoney::impl::setYourMaxContributionTextBox(int max) +{ + mPanel.getChild("your_contribution_max_value")->setTextArg("[AMOUNT]", llformat("%d", max)); +} + +//static +void LLPanelGroupLandMoney::impl::mapCallback(void* data) +{ + LLPanelGroupLandMoney::impl* selfp = (LLPanelGroupLandMoney::impl*) data; + + if ( selfp ) selfp->onMapButton(); +} + +void LLPanelGroupLandMoney::impl::contributionCommitCallback(LLUICtrl* ctrl, + void* userdata) +{ + LLPanelGroupLandMoney* tabp = (LLPanelGroupLandMoney*) userdata; + LLLineEditor* editorp = (LLLineEditor*) ctrl; + + if ( tabp && editorp ) + { + impl* self = tabp->mImplementationp; + int your_contribution = 0; + int new_contribution = 0; + + new_contribution= atoi(editorp->getText().c_str()); + your_contribution = self->getStoredContribution(); + + //reset their junk data to be "good" data to us + self->setYourContributionTextField(new_contribution); + + //check to see if they're contribution text has changed + self->mNeedsApply = new_contribution != your_contribution; + tabp->notifyObservers(); + } +} + +void LLPanelGroupLandMoney::impl::contributionKeystrokeCallback(LLLineEditor* caller, + void* userdata) +{ + impl::contributionCommitCallback(caller, userdata); +} + +//static +void LLPanelGroupLandMoney::impl::processGroupLand(LLMessageSystem* msg) +{ + S32 count = msg->getNumberOfBlocks("QueryData"); + if(count > 0) + { + S32 first_block = 0; + + LLUUID owner_id; + LLUUID trans_id; + + msg->getUUID("QueryData", "OwnerID", owner_id, 0); + msg->getUUID("TransactionData", "TransactionID", trans_id); + + if(owner_id.isNull()) + { + // special block which has total contribution + ++first_block; + + S32 committed = 0; + S32 billable_area = 0; + + if(count == 1) + { + msg->getS32("QueryData", "BillableArea", committed, 0); + } + else + { + for(S32 i = first_block; i < count; ++i) + { + msg->getS32("QueryData", "BillableArea", billable_area, i); + committed+=billable_area; + } + } + + S32 total_contribution; + msg->getS32("QueryData", "ActualArea", total_contribution, 0); + mPanel.getChild("total_contributed_land_value")->setTextArg("[AREA]", llformat("%d", total_contribution)); + + mPanel.getChild("total_land_in_use_value")->setTextArg("[AREA]", llformat("%d", committed)); + S32 available = total_contribution - committed; + mPanel.getChild("land_available_value")->setTextArg("[AREA]", llformat("%d", available)); + + + if ( mGroupOverLimitTextp && mGroupOverLimitIconp ) + + { + mGroupOverLimitIconp->setVisible(available < 0); + mGroupOverLimitTextp->setVisible(available < 0); + } + + } + + if ( trans_id != mTransID ) return; + + // This power was removed to make group roles simpler + //if ( !gAgent.hasPowerInGroup(mGroupID, GP_LAND_VIEW_OWNED) ) return; + if (!gAgent.isInGroup(mPanel.mGroupID)) return; + + mGroupParcelsp->setCommentText(mEmptyParcelsText); + + std::string name; + std::string desc; + S32 actual_area; + S32 billable_area; + U8 flags; + F32 global_x; + F32 global_y; + std::string sim_name; + std::string land_sku; + std::string land_type; + + for(S32 i = first_block; i < count; ++i) + { + msg->getUUID("QueryData", "OwnerID", owner_id, i); + msg->getString("QueryData", "Name", name, i); + msg->getString("QueryData", "Desc", desc, i); + msg->getS32("QueryData", "ActualArea", actual_area, i); + msg->getS32("QueryData", "BillableArea", billable_area, i); + msg->getU8("QueryData", "Flags", flags, i); + msg->getF32("QueryData", "GlobalX", global_x, i); + msg->getF32("QueryData", "GlobalY", global_y, i); + msg->getString("QueryData", "SimName", sim_name, i); + + if ( msg->getSizeFast(_PREHASH_QueryData, i, _PREHASH_ProductSKU) > 0 ) + { + msg->getStringFast( _PREHASH_QueryData, _PREHASH_ProductSKU, land_sku, i); + LL_INFOS() << "Land sku: " << land_sku << LL_ENDL; + land_type = LLProductInfoRequestManager::instance().getDescriptionForSku(land_sku); + } + else + { + land_sku.clear(); + land_type = LLTrans::getString("land_type_unknown"); + } + + S32 region_x = ll_round(global_x) % REGION_WIDTH_UNITS; + S32 region_y = ll_round(global_y) % REGION_WIDTH_UNITS; + std::string location = sim_name + llformat(" (%d, %d)", region_x, region_y); + std::string area; + + + if(billable_area == actual_area) + { + area = llformat("%d", billable_area); + } + else + { + area = llformat("%d / %d", billable_area, actual_area); + } + + std::string hidden; + hidden = llformat("%f %f", global_x, global_y); + + LLSD row; + + row["columns"][0]["column"] = "name"; + row["columns"][0]["value"] = name; + row["columns"][0]["font"] = "SANSSERIF_SMALL"; + + row["columns"][1]["column"] = "location"; + row["columns"][1]["value"] = location; + row["columns"][1]["font"] = "SANSSERIF_SMALL"; + + row["columns"][2]["column"] = "area"; + row["columns"][2]["value"] = area; + row["columns"][2]["font"] = "SANSSERIF_SMALL"; + + row["columns"][3]["column"] = "type"; + row["columns"][3]["value"] = land_type; + row["columns"][3]["font"] = "SANSSERIF_SMALL"; + + // hidden is always last column + row["columns"][4]["column"] = "hidden"; + row["columns"][4]["value"] = hidden; + + mGroupParcelsp->addElement(row); + } + } +} + +//************************************* +//** LLPanelGroupLandMoney Functions ** +//************************************* + + +//static +std::map LLPanelGroupLandMoney::sGroupIDs; + +LLPanelGroupLandMoney::LLPanelGroupLandMoney() : + LLPanelGroupTab() +{ + //FIXME - add setGroupID(); + mImplementationp = new impl(*this); + + //problem what if someone has both the group floater open and the finder + //open to the same group? Some maps that map group ids to panels + //will then only be working for the last panel for a given group id :( + + //FIXME - add to setGroupID() + //LLPanelGroupLandMoney::sGroupIDs.insert(group_id, this); +} + +LLPanelGroupLandMoney::~LLPanelGroupLandMoney() +{ + delete mImplementationp; + LLPanelGroupLandMoney::sGroupIDs.erase(mGroupID); +} + +void LLPanelGroupLandMoney::activate() +{ + if ( !mImplementationp->mBeenActivated ) + { + //select the first tab + LLTabContainer* tabp = getChild("group_money_tab_container"); + + if ( tabp ) + { + tabp->selectFirstTab(); + mImplementationp->mBeenActivated = true; + } + + //fill in the max contribution + + //This calculation is unfortunately based on + //the status bar's concept of how much land the user has + //which can change dynamically if the user buys new land, gives + //more land to a group, etc. + //A race condition can occur if we want to update the UI's + //concept of the user's max contribution before the status + //bar has been updated from a change in the user's group contribution. + + //Since the max contribution should not change solely on changing + //a user's group contribution, (it would only change through + //purchasing of new land) this code is placed here + //and only updated once to prevent the race condition + //at the price of having stale data. + //We need to have the status bar have observers + //or find better way of distributing up to date land data. - jwolk + S32 max_avail = mImplementationp->getStoredContribution(); + if(gStatusBar) + { + max_avail += gStatusBar->getSquareMetersLeft(); + } + mImplementationp->setYourMaxContributionTextBox(max_avail); + } + + mImplementationp->mMapButtonp->setEnabled(false); + update(GC_ALL); +} + +void LLPanelGroupLandMoney::update(LLGroupChange gc) +{ + if (gc != GC_ALL) return; //Don't update if it's the wrong panel! + + LLTabContainer* tabp = getChild("group_money_tab_container"); + + if ( tabp ) + { + LLPanel* panelp; + LLGroupMoneyTabEventHandler* eh; + + panelp = tabp->getCurrentPanel(); + + //now pull the event handler associated with that L$ tab + if ( panelp ) + { + eh = get_if_there(LLGroupMoneyTabEventHandler::sTabsToHandlers, + panelp, + (LLGroupMoneyTabEventHandler*)NULL); + if ( eh ) eh->onClickTab(); + } + } + + mImplementationp->requestGroupLandInfo(); + mImplementationp->setYourContributionTextField(mImplementationp->getStoredContribution()); +} + +bool LLPanelGroupLandMoney::needsApply(std::string& mesg) +{ + return mImplementationp->mNeedsApply; +} + +bool LLPanelGroupLandMoney::apply(std::string& mesg) +{ + if (!mImplementationp->applyContribution() ) + { + mesg = getString("land_contrib_error"); + return false; + } + + mImplementationp->mNeedsApply = false; + notifyObservers(); + + return true; +} + +void LLPanelGroupLandMoney::cancel() +{ + //set the contribution back to the "stored value" + mImplementationp->setYourContributionTextField(mImplementationp->getStoredContribution()); + + mImplementationp->mNeedsApply = false; + notifyObservers(); +} + + +bool LLPanelGroupLandMoney::postBuild() +{ + /* This power was removed to make group roles simpler + bool has_parcel_view = gAgent.hasPowerInGroup(mGroupID, + GP_LAND_VIEW_OWNED); + bool has_accounting_view = gAgent.hasPowerInGroup(mGroupID, + GP_ACCOUNTING_VIEW); + */ + + bool can_view = gAgent.isInGroup(mGroupID); + + mImplementationp->mGroupOverLimitIconp = + getChild("group_over_limit_icon"); + mImplementationp->mGroupOverLimitTextp = + getChild("group_over_limit_text"); + + mImplementationp->mYourContributionEditorp + = getChild("your_contribution_line_editor"); + if ( mImplementationp->mYourContributionEditorp ) + { + LLLineEditor* editor = mImplementationp->mYourContributionEditorp; + + editor->setCommitCallback(mImplementationp->contributionCommitCallback, this); + editor->setKeystrokeCallback(mImplementationp->contributionKeystrokeCallback, this); + } + + mImplementationp->mMapButtonp = getChild("map_button"); + + mImplementationp->mGroupParcelsp = + getChild("group_parcel_list"); + + if ( mImplementationp->mGroupParcelsp ) + { + mImplementationp->mGroupParcelsp->setCommitCallback(boost::bind(&LLPanelGroupLandMoney::onLandSelectionChanged, this)); + mImplementationp->mGroupParcelsp->setCommitOnSelectionChange(true); + } + + mImplementationp->mCantViewParcelsText = getString("cant_view_group_land_text"); + mImplementationp->mCantViewAccountsText = getString("cant_view_group_accounting_text"); + mImplementationp->mEmptyParcelsText = getString("epmty_view_group_land_text"); + + if ( mImplementationp->mMapButtonp ) + { + mImplementationp->mMapButtonp->setClickedCallback(LLPanelGroupLandMoney::impl::mapCallback, mImplementationp); + } + + if ( mImplementationp->mGroupOverLimitTextp ) + { + mImplementationp->mGroupOverLimitTextp->setVisible(false); + } + + if ( mImplementationp->mGroupOverLimitIconp ) + { + mImplementationp->mGroupOverLimitIconp->setVisible(false); + } + + if ( !can_view ) + { + if ( mImplementationp->mGroupParcelsp ) + { + mImplementationp->mGroupParcelsp->setCommentText( + mImplementationp->mCantViewParcelsText); + mImplementationp->mGroupParcelsp->setEnabled(false); + } + } + + + + LLButton* earlierp, *laterp; + LLTextEditor* textp; + LLPanel* panelp; + + LLTabContainer* tabcp = getChild("group_money_tab_container"); + + if ( !can_view ) + { + if ( tabcp ) + { + S32 i; + S32 tab_count = tabcp->getTabCount(); + + for (i = tab_count - 1; i >=0; --i) + { + tabcp->enableTabButton(i, false); + } + } + } + + std::string loading_text = getString("loading_txt"); + + //pull out the widgets for the L$ details tab + earlierp = getChild("earlier_details_button", true); + laterp = getChild("later_details_button", true); + textp = getChild("group_money_details_text", true); + panelp = getChild("group_money_details_tab", true); + + if ( !can_view ) + { + textp->setText(mImplementationp->mCantViewAccountsText); + } + else + { + mImplementationp->mMoneyDetailsTabEHp = + new LLGroupMoneyDetailsTabEventHandler(earlierp, + laterp, + textp, + tabcp, + panelp, + loading_text); + } + + textp = getChild("group_money_planning_text", true); + panelp = getChild("group_money_planning_tab", true); + + if ( !can_view ) + { + textp->setText(mImplementationp->mCantViewAccountsText); + } + else + { + //Temporally disabled for DEV-11287. + mImplementationp->mMoneyPlanningTabEHp = + new LLGroupMoneyPlanningTabEventHandler(textp, + tabcp, + panelp, + loading_text); + } + + //pull out the widgets for the L$ sales tab + earlierp = getChild("earlier_sales_button", true); + laterp = getChild("later_sales_button", true); + textp = getChild("group_money_sales_text", true); + panelp = getChild("group_money_sales_tab", true); + + if ( !can_view ) + { + textp->setText(mImplementationp->mCantViewAccountsText); + } + else + { + mImplementationp->mMoneySalesTabEHp = + new LLGroupMoneySalesTabEventHandler(earlierp, + laterp, + textp, + tabcp, + panelp, + loading_text); + } + + return LLPanelGroupTab::postBuild(); +} + +void LLPanelGroupLandMoney::onLandSelectionChanged() +{ + mImplementationp->mMapButtonp->setEnabled( mImplementationp->mGroupParcelsp->getItemCount() > 0 ); +} + +bool LLPanelGroupLandMoney::isVisibleByAgent(LLAgent* agentp) +{ + return mAllowEdit && agentp->isInGroup(mGroupID); +} + +void LLPanelGroupLandMoney::processPlacesReply(LLMessageSystem* msg, void**) +{ + LLUUID group_id; + msg->getUUID("AgentData", "QueryID", group_id); + + group_id_map_t::iterator found_it = sGroupIDs.find(group_id); + if(found_it == sGroupIDs.end()) + { + LL_INFOS() << "Group Panel Land L$ " << group_id << " no longer in existence." + << LL_ENDL; + return; + } + + found_it->second->mImplementationp->processGroupLand(msg); +} + + +LLGroupMoneyTabEventHandlerImpl::LLGroupMoneyTabEventHandlerImpl(LLButton* earlier_buttonp, + LLButton* later_buttonp, + LLTextEditor* text_editorp, + LLPanel* tabpanelp, + const std::string& loading_text, + S32 interval_length_days, + S32 max_interval_days) +{ + mPanelID.generate(); + + mIntervalLength = interval_length_days; + mMaxInterval = max_interval_days; + mCurrentInterval = 0; + + mTextEditorp = text_editorp; + mEarlierButtonp = earlier_buttonp; + mLaterButtonp = later_buttonp; + mTabPanelp = tabpanelp; + + mLoadingText = loading_text; +} + +LLGroupMoneyTabEventHandlerImpl::~LLGroupMoneyTabEventHandlerImpl() +{ +} + +bool LLGroupMoneyTabEventHandlerImpl::getCanClickEarlier() +{ + return (mCurrentInterval < mMaxInterval); +} + +bool LLGroupMoneyTabEventHandlerImpl::getCanClickLater() +{ + return ( mCurrentInterval > 0 ); +} + +void LLGroupMoneyTabEventHandlerImpl::updateButtons() +{ + if ( mEarlierButtonp ) + { + mEarlierButtonp->setEnabled(getCanClickEarlier()); + } + if ( mLaterButtonp ) + { + mLaterButtonp->setEnabled(getCanClickLater()); + } +} + +//******************************************* +//** LLGroupMoneyTabEventHandler Functions ** +//******************************************* + +std::map LLGroupMoneyTabEventHandler::sInstanceIDs; +std::map LLGroupMoneyTabEventHandler::sTabsToHandlers; + +LLGroupMoneyTabEventHandler::LLGroupMoneyTabEventHandler(LLButton* earlier_buttonp, + LLButton* later_buttonp, + LLTextEditor* text_editorp, + LLTabContainer* tab_containerp, + LLPanel* panelp, + const std::string& loading_text, + S32 interval_length_days, + S32 max_interval_days) +{ + mImplementationp = new LLGroupMoneyTabEventHandlerImpl(earlier_buttonp, + later_buttonp, + text_editorp, + panelp, + loading_text, + interval_length_days, + max_interval_days); + + if ( earlier_buttonp ) + { + earlier_buttonp->setClickedCallback(clickEarlierCallback, this); + } + + if ( later_buttonp ) + { + later_buttonp->setClickedCallback(clickLaterCallback, this); + } + + mImplementationp->updateButtons(); + + if ( tab_containerp && panelp ) + { + tab_containerp->setCommitCallback(boost::bind(&LLGroupMoneyTabEventHandler::onClickTab, this)); + } + + sInstanceIDs.insert(std::make_pair(mImplementationp->mPanelID, this)); + sTabsToHandlers[panelp] = this; +} + +LLGroupMoneyTabEventHandler::~LLGroupMoneyTabEventHandler() +{ + sInstanceIDs.erase(mImplementationp->mPanelID); + sTabsToHandlers.erase(mImplementationp->mTabPanelp); + + delete mImplementationp; +} + + +void LLGroupMoneyTabEventHandler::onClickTab() +{ + requestData(gMessageSystem); +} + +void LLGroupMoneyTabEventHandler::requestData(LLMessageSystem* msg) +{ + //do nothing +} + +void LLGroupMoneyTabEventHandler::processReply(LLMessageSystem* msg, void** data) +{ + //do nothing +} + +void LLGroupMoneyTabEventHandler::onClickEarlier() +{ + if ( mImplementationp->mTextEditorp) + { + mImplementationp->mTextEditorp->setText(mImplementationp->mLoadingText); + } + mImplementationp->mCurrentInterval++; + + mImplementationp->updateButtons(); + + requestData(gMessageSystem); +} + +void LLGroupMoneyTabEventHandler::onClickLater() +{ + if ( mImplementationp->mTextEditorp ) + { + mImplementationp->mTextEditorp->setText(mImplementationp->mLoadingText); + } + mImplementationp->mCurrentInterval--; + + mImplementationp->updateButtons(); + + requestData(gMessageSystem); +} + +//static +void LLGroupMoneyTabEventHandler::clickEarlierCallback(void* data) +{ + LLGroupMoneyTabEventHandler* selfp = (LLGroupMoneyTabEventHandler*) data; + + if ( selfp ) selfp->onClickEarlier(); +} + +//static +void LLGroupMoneyTabEventHandler::clickLaterCallback(void* data) +{ + LLGroupMoneyTabEventHandler* selfp = (LLGroupMoneyTabEventHandler*) data; + if ( selfp ) selfp->onClickLater(); +} + +//************************************************** +//** LLGroupMoneyDetailsTabEventHandler Functions ** +//************************************************** + +LLGroupMoneyDetailsTabEventHandler::LLGroupMoneyDetailsTabEventHandler(LLButton* earlier_buttonp, + LLButton* later_buttonp, + LLTextEditor* text_editorp, + LLTabContainer* tab_containerp, + LLPanel* panelp, + const std::string& loading_text) + : LLGroupMoneyTabEventHandler(earlier_buttonp, + later_buttonp, + text_editorp, + tab_containerp, + panelp, + loading_text, + SUMMARY_INTERVAL, + SUMMARY_MAX) +{ +} + +LLGroupMoneyDetailsTabEventHandler::~LLGroupMoneyDetailsTabEventHandler() +{ +} + +void LLGroupMoneyDetailsTabEventHandler::requestData(LLMessageSystem* msg) +{ + msg->newMessageFast(_PREHASH_GroupAccountDetailsRequest); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); + msg->addUUIDFast(_PREHASH_GroupID, mImplementationp->getGroupID() ); + msg->nextBlockFast(_PREHASH_MoneyData); + msg->addUUIDFast(_PREHASH_RequestID, mImplementationp->mPanelID ); + msg->addS32Fast(_PREHASH_IntervalDays, mImplementationp->mIntervalLength ); + msg->addS32Fast(_PREHASH_CurrentInterval, mImplementationp->mCurrentInterval); + + gAgent.sendReliableMessage(); + + if ( mImplementationp->mTextEditorp ) + { + mImplementationp->mTextEditorp->setText(mImplementationp->mLoadingText); + } + + LLGroupMoneyTabEventHandler::requestData(msg); +} + +void LLGroupMoneyDetailsTabEventHandler::processReply(LLMessageSystem* msg, + void** data) +{ + LLUUID group_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_GroupID, group_id ); + if (mImplementationp->getGroupID() != group_id) + { + LL_WARNS() << "Group Account details not for this group!" << LL_ENDL; + return; + } + + std::string start_date; + S32 interval_days; + S32 current_interval; + + msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_IntervalDays, interval_days ); + msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_CurrentInterval, current_interval ); + msg->getStringFast(_PREHASH_MoneyData, _PREHASH_StartDate, start_date); + + std::string time_str = LLTrans::getString("GroupMoneyStartDate"); + LLSD substitution; + + // We don't do time zone corrections of the calculated number of seconds + // because we don't have a full time stamp, only a date. + substitution["datetime"] = LLDateUtil::secondsSinceEpochFromString("%Y-%m-%d", start_date); + LLStringUtil::format (time_str, substitution); + + if ( interval_days != mImplementationp->mIntervalLength || + current_interval != mImplementationp->mCurrentInterval ) + { + LL_INFOS() << "Out of date details packet " << interval_days << " " + << current_interval << LL_ENDL; + return; + } + + std::string text = time_str; + text.append("\n\n"); + + S32 total_amount = 0; + S32 transactions = msg->getNumberOfBlocksFast(_PREHASH_HistoryData); + for(S32 i = 0; i < transactions; i++) + { + S32 amount = 0; + std::string desc; + + msg->getStringFast(_PREHASH_HistoryData, _PREHASH_Description, desc, i ); + msg->getS32Fast(_PREHASH_HistoryData, _PREHASH_Amount, amount, i); + + if (amount != 0) + { + text.append(llformat("%-24s %6d\n", desc.c_str(), amount)); + } + else + { + // skip it + } + + total_amount += amount; + } + + text.append(1, '\n'); + + text.append(llformat("%-24s %6d\n", LLTrans::getString("GroupMoneyTotal").c_str(), total_amount)); + + if ( mImplementationp->mTextEditorp ) + { + mImplementationp->mTextEditorp->setText(text); + } +} + +//static +void LLPanelGroupLandMoney::processGroupAccountDetailsReply(LLMessageSystem* msg, + void** data) +{ + LLUUID agent_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); + if (gAgent.getID() != agent_id) + { + LL_WARNS() << "Got group L$ history reply for another agent!" << LL_ENDL; + return; + } + + LLUUID request_id; + msg->getUUIDFast(_PREHASH_MoneyData, _PREHASH_RequestID, request_id ); + LLGroupMoneyTabEventHandler* selfp = get_ptr_in_map(LLGroupMoneyTabEventHandler::sInstanceIDs, request_id); + if (!selfp) + { + LL_WARNS() << "GroupAccountDetails received for non-existent group panel." << LL_ENDL; + return; + } + + selfp->processReply(msg, data); +} + +//************************************************ +//** LLGroupMoneySalesTabEventHandler Functions ** +//************************************************ + +LLGroupMoneySalesTabEventHandler::LLGroupMoneySalesTabEventHandler(LLButton* earlier_buttonp, + LLButton* later_buttonp, + LLTextEditor* text_editorp, + LLTabContainer* tab_containerp, + LLPanel* panelp, + const std::string& loading_text) + : LLGroupMoneyTabEventHandler(earlier_buttonp, + later_buttonp, + text_editorp, + tab_containerp, + panelp, + loading_text, + SUMMARY_INTERVAL, + SUMMARY_MAX) +{ +} + +LLGroupMoneySalesTabEventHandler::~LLGroupMoneySalesTabEventHandler() +{ +} + +void LLGroupMoneySalesTabEventHandler::requestData(LLMessageSystem* msg) +{ + msg->newMessageFast(_PREHASH_GroupAccountTransactionsRequest); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); + msg->addUUIDFast(_PREHASH_GroupID, mImplementationp->getGroupID() ); + msg->nextBlockFast(_PREHASH_MoneyData); + msg->addUUIDFast(_PREHASH_RequestID, mImplementationp->mPanelID ); + msg->addS32Fast(_PREHASH_IntervalDays, mImplementationp->mIntervalLength ); + msg->addS32Fast(_PREHASH_CurrentInterval, mImplementationp->mCurrentInterval); + + gAgent.sendReliableMessage(); + + if ( mImplementationp->mTextEditorp ) + { + mImplementationp->mTextEditorp->setText(mImplementationp->mLoadingText); + } + + LLGroupMoneyTabEventHandler::requestData(msg); +} + +void LLGroupMoneySalesTabEventHandler::processReply(LLMessageSystem* msg, + void** data) +{ + LLUUID group_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_GroupID, group_id ); + if (mImplementationp->getGroupID() != group_id) + { + LL_WARNS() << "Group Account Transactions not for this group!" << LL_ENDL; + return; + } + + std::string text = mImplementationp->mTextEditorp->getText(); + + std::string start_date; + S32 interval_days; + S32 current_interval; + + msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_IntervalDays, interval_days ); + msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_CurrentInterval, current_interval ); + msg->getStringFast(_PREHASH_MoneyData, _PREHASH_StartDate, start_date); + + if (interval_days != mImplementationp->mIntervalLength || + current_interval != mImplementationp->mCurrentInterval) + { + LL_INFOS() << "Out of date details packet " << interval_days << " " + << current_interval << LL_ENDL; + return; + } + + // If this is the first packet, clear the text, don't append. + // Start with the date. + if (text == mImplementationp->mLoadingText) + { + std::string time_str = LLTrans::getString("GroupMoneyStartDate"); + LLSD substitution; + + // We don't do time zone corrections of the calculated number of seconds + // because we don't have a full time stamp, only a date. + substitution["datetime"] = LLDateUtil::secondsSinceEpochFromString("%Y-%m-%d", start_date); + LLStringUtil::format (time_str, substitution); + + text = time_str + "\n\n"; + } + + S32 transactions = msg->getNumberOfBlocksFast(_PREHASH_HistoryData); + if (transactions == 0) + { + text.append(LLTrans::getString("none_text")); + } + else + { + for(S32 i = 0; i < transactions; i++) + { + std::string time; + S32 type = 0; + S32 amount = 0; + std::string user; + std::string item; + + msg->getStringFast(_PREHASH_HistoryData, _PREHASH_Time, time, i); + msg->getStringFast(_PREHASH_HistoryData, _PREHASH_User, user, i ); + msg->getS32Fast(_PREHASH_HistoryData, _PREHASH_Type, type, i); + msg->getStringFast(_PREHASH_HistoryData, _PREHASH_Item, item, i ); + msg->getS32Fast(_PREHASH_HistoryData, _PREHASH_Amount, amount, i); + + if (amount != 0) + { + std::string verb; + + switch(type) + { + case TRANS_OBJECT_SALE: + verb = LLTrans::getString("GroupMoneyBought").c_str(); + break; + case TRANS_GIFT: + verb = LLTrans::getString("GroupMoneyPaidYou").c_str(); + break; + case TRANS_PAY_OBJECT: + verb = LLTrans::getString("GroupMoneyPaidInto").c_str(); + break; + case TRANS_LAND_PASS_SALE: + verb = LLTrans::getString("GroupMoneyBoughtPassTo").c_str(); + break; + case TRANS_EVENT_FEE: + verb = LLTrans::getString("GroupMoneyPaidFeeForEvent").c_str(); + break; + case TRANS_EVENT_PRIZE: + verb = LLTrans::getString("GroupMoneyPaidPrizeForEvent").c_str(); + break; + default: + verb = ""; + break; + } + + std::string line = llformat("%s %6d - %s %s %s\n", time.c_str(), amount, user.c_str(), verb.c_str(), item.c_str()); + text.append(line); + } + } + } + + if ( mImplementationp->mTextEditorp) + { + mImplementationp->mTextEditorp->setText(text); + } +} + +//static +void LLPanelGroupLandMoney::processGroupAccountTransactionsReply(LLMessageSystem* msg, + void** data) +{ + LLUUID agent_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); + if (gAgent.getID() != agent_id) + { + LL_WARNS() << "Got group L$ history reply for another agent!" << LL_ENDL; + return; + } + + LLUUID request_id; + msg->getUUIDFast(_PREHASH_MoneyData, _PREHASH_RequestID, request_id ); + + LLGroupMoneyTabEventHandler* self; + + self = get_ptr_in_map(LLGroupMoneyTabEventHandler::sInstanceIDs, request_id); + if (!self) + { + LL_WARNS() << "GroupAccountTransactions recieved for non-existent group panel." << LL_ENDL; + return; + } + + self->processReply(msg, data); +} + +//*************************************************** +//** LLGroupMoneyPlanningTabEventHandler Functions ** +//*************************************************** + +LLGroupMoneyPlanningTabEventHandler::LLGroupMoneyPlanningTabEventHandler(LLTextEditor* text_editorp, + LLTabContainer* tab_containerp, + LLPanel* panelp, + const std::string& loading_text) + : LLGroupMoneyTabEventHandler(NULL, + NULL, + text_editorp, + tab_containerp, + panelp, + loading_text, + SUMMARY_INTERVAL, + SUMMARY_MAX) +{ +} + +LLGroupMoneyPlanningTabEventHandler::~LLGroupMoneyPlanningTabEventHandler() +{ +} + +void LLGroupMoneyPlanningTabEventHandler::requestData(LLMessageSystem* msg) +{ + msg->newMessageFast(_PREHASH_GroupAccountSummaryRequest); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); + msg->addUUIDFast(_PREHASH_GroupID, mImplementationp->getGroupID() ); + msg->nextBlockFast(_PREHASH_MoneyData); + msg->addUUIDFast(_PREHASH_RequestID, mImplementationp->mPanelID ); + msg->addS32Fast(_PREHASH_IntervalDays, mImplementationp->mIntervalLength); + msg->addS32Fast(_PREHASH_CurrentInterval, 0); //planning has 0 interval + + gAgent.sendReliableMessage(); + + if ( mImplementationp->mTextEditorp ) + { + mImplementationp->mTextEditorp->setText(mImplementationp->mLoadingText); + } + + LLGroupMoneyTabEventHandler::requestData(msg); +} + +void LLGroupMoneyPlanningTabEventHandler::processReply(LLMessageSystem* msg, + void** data) +{ + LLUUID group_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_GroupID, group_id ); + if (mImplementationp->getGroupID() != group_id) + { + LL_WARNS() << "Group Account Summary received not for this group!" << LL_ENDL; + return; + } + + std::string text; + + std::string start_date; + std::string last_stipend_date; + std::string next_stipend_date; + S32 interval_days; + S32 current_interval; + S32 balance; + S32 total_credits; + S32 total_debits; + S32 cur_object_tax; + S32 cur_light_tax; + S32 cur_land_tax; + S32 cur_group_tax; + S32 cur_parcel_dir_fee; + S32 proj_object_tax; + S32 proj_light_tax; + S32 proj_land_tax; + S32 proj_group_tax; + S32 proj_parcel_dir_fee; + S32 non_exempt_members; + + msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_IntervalDays, interval_days ); + msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_CurrentInterval, current_interval ); + msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_Balance, balance ); + msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_TotalCredits, total_credits ); + msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_TotalDebits, total_debits ); + msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_ObjectTaxCurrent, cur_object_tax ); + msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_LightTaxCurrent, cur_light_tax ); + msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_LandTaxCurrent, cur_land_tax ); + msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_GroupTaxCurrent, cur_group_tax ); + msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_ParcelDirFeeCurrent, cur_parcel_dir_fee ); + msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_ObjectTaxEstimate, proj_object_tax ); + msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_LightTaxEstimate, proj_light_tax ); + msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_LandTaxEstimate, proj_land_tax ); + msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_GroupTaxEstimate, proj_group_tax ); + msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_ParcelDirFeeEstimate, proj_parcel_dir_fee ); + msg->getS32Fast(_PREHASH_MoneyData, _PREHASH_NonExemptMembers, non_exempt_members ); + + msg->getStringFast(_PREHASH_MoneyData, _PREHASH_StartDate, start_date); + msg->getStringFast(_PREHASH_MoneyData, _PREHASH_LastTaxDate, last_stipend_date); + msg->getStringFast(_PREHASH_MoneyData, _PREHASH_TaxDate, next_stipend_date); + + + if (interval_days != mImplementationp->mIntervalLength || + current_interval != mImplementationp->mCurrentInterval) + { + LL_INFOS() << "Out of date summary packet " << interval_days << " " + << current_interval << LL_ENDL; + return; + } + + text.append(LLTrans::getString("SummaryForTheWeek")); + + std::string date_format_str = LLTrans::getString("GroupPlanningDate"); + std::string time_str = date_format_str; + LLSD substitution; + // We don't do time zone corrections of the calculated number of seconds + // because we don't have a full time stamp, only a date. + substitution["datetime"] = LLDateUtil::secondsSinceEpochFromString("%Y-%m-%d", start_date); + LLStringUtil::format (time_str, substitution); + + text.append(time_str); + text.append(". "); + + if (current_interval == 0) + { + text.append(LLTrans::getString("NextStipendDay")); + + time_str = date_format_str; + substitution["datetime"] = LLDateUtil::secondsSinceEpochFromString("%Y-%m-%d", next_stipend_date); + LLStringUtil::format (time_str, substitution); + + text.append(time_str); + text.append(".\n\n"); + text.append(llformat("%-23sL$%6d\n", LLTrans::getString("GroupMoneyBalance").c_str(), balance )); + text.append(1, '\n'); + } + + // [DEV-29503] Hide the individual info since + // non_exempt_member here is a wrong choice to calculate individual shares. +// text.append( LLTrans::getString("GroupIndividualShare")); +// text.append(llformat( "%-24s %6d %6d \n", LLTrans::getString("GroupMoneyCredits").c_str(), total_credits, (S32)floor((F32)total_credits/(F32)non_exempt_members))); +// text.append(llformat( "%-24s %6d %6d \n", LLTrans::getString("GroupMoneyDebits").c_str(), total_debits, (S32)floor((F32)total_debits/(F32)non_exempt_members))); +// text.append(llformat( "%-24s %6d %6d \n", LLTrans::getString("GroupMoneyTotal").c_str(), total_credits + total_debits, (S32)floor((F32)(total_credits + total_debits)/(F32)non_exempt_members))); + + text.append(llformat( "%s\n", LLTrans::getString("GroupColumn").c_str())); + text.append(llformat( "%-24s %6d\n", LLTrans::getString("GroupMoneyCredits").c_str(), total_credits)); + text.append(llformat( "%-24s %6d\n", LLTrans::getString("GroupMoneyDebits").c_str(), total_debits)); + text.append(llformat( "%-24s %6d\n", LLTrans::getString("GroupMoneyTotal").c_str(), total_credits + total_debits)); + + if ( mImplementationp->mTextEditorp ) + { + mImplementationp->mTextEditorp->setText(text); + } +} + +//static +void LLPanelGroupLandMoney::processGroupAccountSummaryReply(LLMessageSystem* msg, + void** data) +{ + LLUUID agent_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id ); + if (gAgent.getID() != agent_id) + { + LL_WARNS() << "Got group L$ history reply for another agent!" << LL_ENDL; + return; + } + + LLUUID request_id; + msg->getUUIDFast(_PREHASH_MoneyData, _PREHASH_RequestID, request_id ); + + LLGroupMoneyTabEventHandler* self; + + self = get_ptr_in_map(LLGroupMoneyTabEventHandler::sInstanceIDs, request_id); + if (!self) + { + LL_WARNS() << "GroupAccountSummary recieved for non-existent group L$ planning tab." << LL_ENDL; + return; + } + + self->processReply(msg, data); +} + +void LLPanelGroupLandMoney::setGroupID(const LLUUID& id) +{ + LLPanelGroupLandMoney::sGroupIDs.erase(mGroupID); + LLPanelGroupTab::setGroupID(id); + LLPanelGroupLandMoney::sGroupIDs.insert(std::make_pair(mGroupID, this)); + + + bool can_view = gAgent.isInGroup(mGroupID); + + mImplementationp->mGroupOverLimitIconp = + getChild("group_over_limit_icon"); + mImplementationp->mGroupOverLimitTextp = + getChild("group_over_limit_text"); + + mImplementationp->mYourContributionEditorp + = getChild("your_contribution_line_editor"); + if ( mImplementationp->mYourContributionEditorp ) + { + LLLineEditor* editor = mImplementationp->mYourContributionEditorp; + + editor->setCommitCallback(mImplementationp->contributionCommitCallback, this); + editor->setKeystrokeCallback(mImplementationp->contributionKeystrokeCallback, this); + } + + mImplementationp->mMapButtonp = getChild("map_button"); + + mImplementationp->mGroupParcelsp = + getChild("group_parcel_list"); + + if ( mImplementationp->mGroupParcelsp ) + { + mImplementationp->mGroupParcelsp->setCommitCallback(boost::bind(&LLPanelGroupLandMoney::onLandSelectionChanged, this)); + mImplementationp->mGroupParcelsp->setCommitOnSelectionChange(true); + } + + mImplementationp->mCantViewParcelsText = getString("cant_view_group_land_text"); + mImplementationp->mCantViewAccountsText = getString("cant_view_group_accounting_text"); + + if ( mImplementationp->mMapButtonp ) + { + mImplementationp->mMapButtonp->setClickedCallback(LLPanelGroupLandMoney::impl::mapCallback, mImplementationp); + } + + if ( mImplementationp->mGroupOverLimitTextp ) + { + mImplementationp->mGroupOverLimitTextp->setVisible(false); + } + + if ( mImplementationp->mGroupOverLimitIconp ) + { + mImplementationp->mGroupOverLimitIconp->setVisible(false); + } + + if ( mImplementationp->mGroupParcelsp ) + { + mImplementationp->mGroupParcelsp->setEnabled(can_view); + } + + if ( !can_view && mImplementationp->mGroupParcelsp ) + { + mImplementationp->mGroupParcelsp->setEnabled(false); + } + + + LLButton* earlierp, *laterp; + LLTextEditor* textp; + LLPanel* panelp; + + LLTabContainer* tabcp = getChild("group_money_tab_container"); + + if ( tabcp ) + { + S32 i; + S32 tab_count = tabcp->getTabCount(); + + for (i = tab_count - 1; i >=0; --i) + { + tabcp->enableTabButton(i, can_view ); + } + } + + std::string loading_text = getString("loading_txt"); + + //pull out the widgets for the L$ details tab + earlierp = getChild("earlier_details_button", true); + laterp = getChild("later_details_button", true); + textp = getChild("group_money_details_text", true); + panelp = getChild("group_money_details_tab", true); + + if ( !can_view ) + { + textp->setText(mImplementationp->mCantViewAccountsText); + } + else + { + if(mImplementationp->mMoneyDetailsTabEHp == 0) + mImplementationp->mMoneyDetailsTabEHp = new LLGroupMoneyDetailsTabEventHandler(earlierp,laterp,textp,tabcp,panelp,loading_text); + mImplementationp->mMoneyDetailsTabEHp->setGroupID(mGroupID); + } + + textp = getChild("group_money_planning_text", true); + + + if ( !can_view ) + { + textp->setText(mImplementationp->mCantViewAccountsText); + } + else + { + panelp = getChild("group_money_planning_tab", true); + if(mImplementationp->mMoneyPlanningTabEHp == 0) + mImplementationp->mMoneyPlanningTabEHp = new LLGroupMoneyPlanningTabEventHandler(textp,tabcp,panelp,loading_text); + mImplementationp->mMoneyPlanningTabEHp->setGroupID(mGroupID); + } + + //pull out the widgets for the L$ sales tab + textp = getChild("group_money_sales_text", true); + + + if ( !can_view ) + { + textp->setText(mImplementationp->mCantViewAccountsText); + } + else + { + earlierp = getChild("earlier_sales_button", true); + laterp = getChild("later_sales_button", true); + panelp = getChild("group_money_sales_tab", true); + if(mImplementationp->mMoneySalesTabEHp == NULL) + mImplementationp->mMoneySalesTabEHp = new LLGroupMoneySalesTabEventHandler(earlierp,laterp,textp,tabcp,panelp,loading_text); + mImplementationp->mMoneySalesTabEHp->setGroupID(mGroupID); + } + + mImplementationp->mBeenActivated = false; + + activate(); +} + diff --git a/indra/newview/llpanelgrouplandmoney.h b/indra/newview/llpanelgrouplandmoney.h index d526602962..d619e1be0a 100644 --- a/indra/newview/llpanelgrouplandmoney.h +++ b/indra/newview/llpanelgrouplandmoney.h @@ -1,66 +1,66 @@ -/** - * @file llpanelgrouplandmoney.h - * @brief Panel for group land and L$. - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_PANEL_GROUP_LAND_MONEY_H -#define LL_PANEL_GROUP_LAND_MONEY_H - -#include "llpanelgroup.h" -#include -#include "lluuid.h" - -class LLPanelGroupLandMoney : public LLPanelGroupTab -{ -public: - LLPanelGroupLandMoney(); - virtual ~LLPanelGroupLandMoney(); - virtual bool postBuild(); - virtual bool isVisibleByAgent(LLAgent* agentp); - - virtual void activate(); - virtual bool needsApply(std::string& mesg); - virtual bool apply(std::string& mesg); - virtual void cancel(); - virtual void update(LLGroupChange gc); - - static void processPlacesReply(LLMessageSystem* msg, void**); - - typedef std::map group_id_map_t; - static group_id_map_t sGroupIDs; - - static void processGroupAccountDetailsReply(LLMessageSystem* msg, void** data); - static void processGroupAccountTransactionsReply(LLMessageSystem* msg, void** data); - static void processGroupAccountSummaryReply(LLMessageSystem* msg, void** data); - - virtual void setGroupID(const LLUUID& id); - - virtual void onLandSelectionChanged(); - -protected: - class impl; - impl* mImplementationp; -}; - -#endif // LL_PANEL_GROUP_LAND_MONEY_H +/** + * @file llpanelgrouplandmoney.h + * @brief Panel for group land and L$. + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_PANEL_GROUP_LAND_MONEY_H +#define LL_PANEL_GROUP_LAND_MONEY_H + +#include "llpanelgroup.h" +#include +#include "lluuid.h" + +class LLPanelGroupLandMoney : public LLPanelGroupTab +{ +public: + LLPanelGroupLandMoney(); + virtual ~LLPanelGroupLandMoney(); + virtual bool postBuild(); + virtual bool isVisibleByAgent(LLAgent* agentp); + + virtual void activate(); + virtual bool needsApply(std::string& mesg); + virtual bool apply(std::string& mesg); + virtual void cancel(); + virtual void update(LLGroupChange gc); + + static void processPlacesReply(LLMessageSystem* msg, void**); + + typedef std::map group_id_map_t; + static group_id_map_t sGroupIDs; + + static void processGroupAccountDetailsReply(LLMessageSystem* msg, void** data); + static void processGroupAccountTransactionsReply(LLMessageSystem* msg, void** data); + static void processGroupAccountSummaryReply(LLMessageSystem* msg, void** data); + + virtual void setGroupID(const LLUUID& id); + + virtual void onLandSelectionChanged(); + +protected: + class impl; + impl* mImplementationp; +}; + +#endif // LL_PANEL_GROUP_LAND_MONEY_H diff --git a/indra/newview/llpanelgroupnotices.cpp b/indra/newview/llpanelgroupnotices.cpp index e5c9acad8e..483c6876ed 100644 --- a/indra/newview/llpanelgroupnotices.cpp +++ b/indra/newview/llpanelgroupnotices.cpp @@ -1,680 +1,680 @@ -/** - * @file llpanelgroupnotices.cpp - * @brief A panel to display group notices. - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelgroupnotices.h" - -#include "llview.h" - -#include "llavatarnamecache.h" -#include "llinventory.h" -#include "llviewerinventory.h" -#include "llinventorydefines.h" -#include "llinventoryfunctions.h" -#include "llinventoryicon.h" -#include "llinventorymodel.h" -#include "llagent.h" -#include "llagentui.h" - -#include "lllineeditor.h" -#include "lltexteditor.h" -#include "llbutton.h" -#include "lliconctrl.h" -#include "llcheckboxctrl.h" -#include "llscrolllistctrl.h" -#include "llscrolllistitem.h" -#include "lltextbox.h" -#include "lltrans.h" - -#include "roles_constants.h" -#include "llviewerwindow.h" -#include "llviewermessage.h" -#include "llnotificationsutil.h" -#include "llgiveinventory.h" - -static LLPanelInjector t_panel_group_notices("panel_group_notices"); - - -///////////////////////// -// LLPanelGroupNotices // -///////////////////////// -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLDropTarget -// -// This handy class is a simple way to drop something on another -// view. It handles drop events, always setting itself to the size of -// its parent. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLGroupDropTarget : public LLView -{ -public: - struct Params : public LLInitParam::Block - { - // *NOTE: These parameters logically Mandatory, but are not - // specified in XML files, hence Optional - Optional panel; - Optional group_id; - Params() - : panel("panel"), - group_id("group_id") - { - changeDefault(mouse_opaque, false); - changeDefault(follows.flags, FOLLOWS_ALL); - } - }; - LLGroupDropTarget(const Params&); - ~LLGroupDropTarget() {}; - - // - // LLView functionality - virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg); - void setPanel (LLPanelGroupNotices* panel) {mGroupNoticesPanel = panel;}; - void setGroup (LLUUID group) {mGroupID = group;}; - -protected: - LLPanelGroupNotices* mGroupNoticesPanel; - LLUUID mGroupID; -}; - -static LLDefaultChildRegistry::Register r("group_drop_target"); - -LLGroupDropTarget::LLGroupDropTarget(const LLGroupDropTarget::Params& p) -: LLView(p), - mGroupNoticesPanel(p.panel), - mGroupID(p.group_id) -{} - -bool LLGroupDropTarget::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - bool handled = false; - - if (!gAgent.hasPowerInGroup(mGroupID,GP_NOTICES_SEND)) - { - *accept = ACCEPT_NO; - return true; - } - - if(getParent()) - { - // check if inside - //LLRect parent_rect = mParentView->getRect(); - //getRect().set(0, parent_rect.getHeight(), parent_rect.getWidth(), 0); - handled = true; - - // check the type - switch(cargo_type) - { - case DAD_TEXTURE: - case DAD_SOUND: - case DAD_LANDMARK: - case DAD_SCRIPT: - case DAD_OBJECT: - case DAD_NOTECARD: - case DAD_CLOTHING: - case DAD_BODYPART: - case DAD_ANIMATION: - case DAD_GESTURE: - case DAD_CALLINGCARD: - case DAD_MESH: - case DAD_SETTINGS: - case DAD_MATERIAL: - { - LLViewerInventoryItem* inv_item = (LLViewerInventoryItem*)cargo_data; - if(gInventory.getItem(inv_item->getUUID()) - && LLGiveInventory::isInventoryGroupGiveAcceptable(inv_item)) - { - // *TODO: get multiple object transfers working - *accept = ACCEPT_YES_COPY_SINGLE; - if(drop) - { - mGroupNoticesPanel->setItem(inv_item); - } - } - else - { - // It's not in the user's inventory (it's probably - // in an object's contents), so disallow dragging - // it here. You can't give something you don't - // yet have. - *accept = ACCEPT_NO; - } - break; - } - case DAD_CATEGORY: - default: - *accept = ACCEPT_NO; - break; - } - } - return handled; -} - -//----------------------------------------------------------------------------- -// LLPanelGroupNotices -//----------------------------------------------------------------------------- -std::string build_notice_date(const U32& the_time) -{ - // ISO 8601 date format - - time_t t = (time_t)the_time; - - if (!t) - { - time(&t); - } - - std::string dateStr = "["+ LLTrans::getString("LTimeYear") + "]/[" - + LLTrans::getString("LTimeMthNum") + "]/[" - + LLTrans::getString("LTimeDay") + "] [" - + LLTrans::getString("LTimeHour") + "]:[" - + LLTrans::getString("LTimeMin") + "]:[" - + LLTrans::getString("LTimeSec") + "]"; - LLSD substitution; - substitution["datetime"] = (S32) t; - LLStringUtil::format (dateStr, substitution); - return dateStr; -} - -LLPanelGroupNotices::LLPanelGroupNotices() : - LLPanelGroupTab(), - mInventoryItem(NULL), - mInventoryOffer(NULL) -{ - - -} - -LLPanelGroupNotices::~LLPanelGroupNotices() -{ - sInstances.erase(mGroupID); - - if (mInventoryOffer) - { - // Cancel the inventory offer. - mInventoryOffer->forceResponse(IOR_DECLINE); - - mInventoryOffer = NULL; - } -} - - -bool LLPanelGroupNotices::isVisibleByAgent(LLAgent* agentp) -{ - return mAllowEdit && - agentp->hasPowerInGroup(mGroupID, GP_NOTICES_SEND | GP_NOTICES_RECEIVE); -} - -bool LLPanelGroupNotices::postBuild() -{ - constexpr bool recurse = true; - - mNoticesList = getChild("notice_list",recurse); - mNoticesList->setCommitOnSelectionChange(true); - mNoticesList->setCommitCallback(onSelectNotice, this); - mNoticesList->sortByColumn("date", false); - - mBtnNewMessage = getChild("create_new_notice",recurse); - mBtnNewMessage->setClickedCallback(onClickNewMessage, this); - mBtnNewMessage->setEnabled(gAgent.hasPowerInGroup(mGroupID, GP_NOTICES_SEND)); - - mBtnGetPastNotices = getChild("refresh_notices",recurse); - mBtnGetPastNotices->setClickedCallback(onClickRefreshNotices, this); - - // Create - mCreateSubject = getChild("create_subject",recurse); - mCreateMessage = getChild("create_message",recurse); - - mCreateInventoryName = getChild("create_inventory_name",recurse); - mCreateInventoryName->setTabStop(false); - mCreateInventoryName->setEnabled(false); - - mCreateInventoryIcon = getChild("create_inv_icon",recurse); - mCreateInventoryIcon->setVisible(false); - - mBtnSendMessage = getChild("send_notice",recurse); - mBtnSendMessage->setClickedCallback(onClickSendMessage, this); - - mBtnRemoveAttachment = getChild("remove_attachment",recurse); - mBtnRemoveAttachment->setClickedCallback(onClickRemoveAttachment, this); - mBtnRemoveAttachment->setEnabled(false); - - // View - mViewSubject = getChild("view_subject",recurse); - mViewMessage = getChild("view_message",recurse); - - mViewInventoryName = getChild("view_inventory_name",recurse); - mViewInventoryName->setTabStop(false); - mViewInventoryName->setEnabled(false); - - mViewInventoryIcon = getChild("view_inv_icon",recurse); - mViewInventoryIcon->setVisible(false); - - mBtnOpenAttachment = getChild("open_attachment",recurse); - mBtnOpenAttachment->setClickedCallback(onClickOpenAttachment, this); - - mNoNoticesStr = getString("no_notices_text"); - - mPanelCreateNotice = getChild("panel_create_new_notice",recurse); - mPanelViewNotice = getChild("panel_view_past_notice",recurse); - - LLGroupDropTarget* target = getChild ("drop_target"); - target->setPanel (this); - target->setGroup (mGroupID); - - arrangeNoticeView(VIEW_PAST_NOTICE); - - return LLPanelGroupTab::postBuild(); -} - -void LLPanelGroupNotices::activate() -{ - if (mNoticesList) - { - mNoticesList->deleteAllItems(); - mKnownNoticeIds.clear(); - } - - mPrevSelectedNotice = LLUUID(); - - bool can_send = gAgent.hasPowerInGroup(mGroupID,GP_NOTICES_SEND); - bool can_receive = gAgent.hasPowerInGroup(mGroupID,GP_NOTICES_RECEIVE); - - mPanelViewNotice->setEnabled(can_receive); - mPanelCreateNotice->setEnabled(can_send); - - // Always disabled to stop direct editing of attachment names - mCreateInventoryName->setEnabled(false); - mViewInventoryName->setEnabled(false); - - // If we can receive notices, grab them right away. - if (can_receive) - { - onClickRefreshNotices(this); - } -} - -void LLPanelGroupNotices::setItem(LLPointer inv_item) -{ - mInventoryItem = inv_item; - - bool item_is_multi = false; - if ( inv_item->getFlags() & LLInventoryItemFlags::II_FLAGS_OBJECT_HAS_MULTIPLE_ITEMS ) - { - item_is_multi = true; - }; - - std::string icon_name = LLInventoryIcon::getIconName(inv_item->getType(), - inv_item->getInventoryType(), - inv_item->getFlags(), - item_is_multi ); - - mCreateInventoryIcon->setValue(icon_name); - mCreateInventoryIcon->setVisible(true); - - std::stringstream ss; - ss << " " << mInventoryItem->getName(); - - mCreateInventoryName->setText(ss.str()); - mBtnRemoveAttachment->setEnabled(true); -} - -void LLPanelGroupNotices::onClickRemoveAttachment(void* data) -{ - LLPanelGroupNotices* self = (LLPanelGroupNotices*)data; - self->mInventoryItem = NULL; - self->mCreateInventoryName->clear(); - self->mCreateInventoryIcon->setVisible(false); - self->mBtnRemoveAttachment->setEnabled(false); -} - -//static -void LLPanelGroupNotices::onClickOpenAttachment(void* data) -{ - LLPanelGroupNotices* self = (LLPanelGroupNotices*)data; - - self->mInventoryOffer->forceResponse(IOR_ACCEPT); - self->mInventoryOffer = NULL; - self->mBtnOpenAttachment->setEnabled(false); -} - -void LLPanelGroupNotices::onClickSendMessage(void* data) -{ - LLPanelGroupNotices* self = (LLPanelGroupNotices*)data; - - if (self->mCreateSubject->getText().empty()) - { - // Must supply a subject - LLNotificationsUtil::add("MustSpecifyGroupNoticeSubject"); - return; - } - send_group_notice( - self->mGroupID, - self->mCreateSubject->getText(), - self->mCreateMessage->getText(), - self->mInventoryItem); - - - //instantly add new notice. actual notice will be added after ferreshNotices call - LLUUID id = LLUUID::generateNewID(); - std::string subj = self->mCreateSubject->getText(); - std::string name ; - LLAgentUI::buildFullname(name); - U32 timestamp = 0; - - LLSD row; - row["id"] = id; - - row["columns"][0]["column"] = "icon"; - - row["columns"][1]["column"] = "subject"; - row["columns"][1]["value"] = subj; - - row["columns"][2]["column"] = "from"; - row["columns"][2]["value"] = name; - - row["columns"][3]["column"] = "date"; - row["columns"][3]["value"] = build_notice_date(timestamp); - - row["columns"][4]["column"] = "sort"; - row["columns"][4]["value"] = llformat( "%u", timestamp); - - self->mNoticesList->addElement(row, ADD_BOTTOM); - self->mKnownNoticeIds.insert(id); - - self->mCreateMessage->clear(); - self->mCreateSubject->clear(); - onClickRemoveAttachment(data); - - self->arrangeNoticeView(VIEW_PAST_NOTICE); -} - -//static -void LLPanelGroupNotices::onClickNewMessage(void* data) -{ - LLPanelGroupNotices* self = (LLPanelGroupNotices*)data; - - self->arrangeNoticeView(CREATE_NEW_NOTICE); - - if (self->mInventoryOffer) - { - self->mInventoryOffer->forceResponse(IOR_DECLINE); - self->mInventoryOffer = NULL; - } - - self->mCreateSubject->clear(); - self->mCreateMessage->clear(); - if (self->mInventoryItem) onClickRemoveAttachment(self); - self->mNoticesList->deselectAllItems(true); // true == don't commit on chnage -} - -void LLPanelGroupNotices::refreshNotices() -{ - onClickRefreshNotices(this); -} - -void LLPanelGroupNotices::clearNoticeList() -{ - mPrevSelectedNotice = mNoticesList->getStringUUIDSelectedItem(); - mNoticesList->deleteAllItems(); - mKnownNoticeIds.clear(); -} - -void LLPanelGroupNotices::onClickRefreshNotices(void* data) -{ - LL_DEBUGS() << "LLPanelGroupNotices::onClickGetPastNotices" << LL_ENDL; - LLPanelGroupNotices* self = (LLPanelGroupNotices*)data; - - self->clearNoticeList(); - - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("GroupNoticesListRequest"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID",gAgent.getID()); - msg->addUUID("SessionID",gAgent.getSessionID()); - msg->nextBlock("Data"); - msg->addUUID("GroupID",self->mGroupID); - gAgent.sendReliableMessage(); -} - -//static -std::map LLPanelGroupNotices::sInstances; - -// static -void LLPanelGroupNotices::processGroupNoticesListReply(LLMessageSystem* msg, void** data) -{ - LLUUID group_id; - msg->getUUID("AgentData", "GroupID", group_id); - - std::map::iterator it = sInstances.find(group_id); - if (it == sInstances.end()) - { - LL_INFOS() << "Group Panel Notices " << group_id << " no longer in existence." - << LL_ENDL; - return; - } - - LLPanelGroupNotices* selfp = it->second; - if(!selfp) - { - LL_INFOS() << "Group Panel Notices " << group_id << " no longer in existence." - << LL_ENDL; - return; - } - - selfp->processNotices(msg); -} - -void LLPanelGroupNotices::processNotices(LLMessageSystem* msg) -{ - LLUUID id; - std::string subj; - std::string name; - U32 timestamp; - bool has_attachment; - U8 asset_type; - - S32 i=0; - S32 count = msg->getNumberOfBlocks("Data"); - - mNoticesList->setEnabled(true); - - //save sort state and set unsorted state to prevent unnecessary - //sorting while adding notices - bool save_sort = mNoticesList->isSorted(); - mNoticesList->setNeedsSort(false); - - for (;igetUUID("Data","NoticeID",id,i); - if (1 == count && id.isNull()) - { - // Only one entry, the dummy entry. - mNoticesList->setCommentText(mNoNoticesStr); - mNoticesList->setEnabled(false); - return; - } - - // Due to some network delays we can receive notice list more than once... - // So add only unique notices - if (mKnownNoticeIds.find(id) != mKnownNoticeIds.end()) - { - // If items with this ID already in the list - skip it - continue; - } - - msg->getString("Data","Subject",subj,i); - msg->getString("Data","FromName",name,i); - msg->getBOOL("Data","HasAttachment",has_attachment,i); - msg->getU8("Data","AssetType",asset_type,i); - msg->getU32("Data","Timestamp",timestamp,i); - - // we only have the legacy name here, convert it to a username - name = LLCacheName::buildUsername(name); - - LLSD row; - row["id"] = id; - row["columns"][0]["column"] = "icon"; - if (has_attachment) - { - std::string icon_name = LLInventoryIcon::getIconName( - (LLAssetType::EType)asset_type, - LLInventoryType::IT_NONE); - row["columns"][0]["type"] = "icon"; - row["columns"][0]["value"] = icon_name; - } - - row["columns"][1]["column"] = "subject"; - row["columns"][1]["value"] = subj; - - row["columns"][2]["column"] = "from"; - row["columns"][2]["value"] = name; - - row["columns"][3]["column"] = "date"; - row["columns"][3]["value"] = build_notice_date(timestamp); - - row["columns"][4]["column"] = "sort"; - row["columns"][4]["value"] = llformat( "%u", timestamp); - - mNoticesList->addElement(row, ADD_BOTTOM); - mKnownNoticeIds.insert(id); - } - - mNoticesList->setNeedsSort(save_sort); - mNoticesList->updateSort(); - if (mPanelViewNotice->getVisible()) - { - if (!mNoticesList->selectByID(mPrevSelectedNotice)) - { - mNoticesList->selectFirstItem(); - } - } -} - -void LLPanelGroupNotices::onSelectNotice(LLUICtrl* ctrl, void* data) -{ - LLPanelGroupNotices* self = (LLPanelGroupNotices*)data; - - if(!self) return; - LLScrollListItem* item = self->mNoticesList->getFirstSelected(); - if (!item) return; - - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("GroupNoticeRequest"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID",gAgent.getID()); - msg->addUUID("SessionID",gAgent.getSessionID()); - msg->nextBlock("Data"); - msg->addUUID("GroupNoticeID",item->getUUID()); - gAgent.sendReliableMessage(); - - LL_DEBUGS() << "Item " << item->getUUID() << " selected." << LL_ENDL; -} - -void LLPanelGroupNotices::showNotice(const std::string& subject, - const std::string& message, - const bool& has_inventory, - const std::string& inventory_name, - LLOfferInfo* inventory_offer) -{ - arrangeNoticeView(VIEW_PAST_NOTICE); - - if(mViewSubject) mViewSubject->setText(subject); - if(mViewMessage) mViewMessage->setText(message); - - if (mInventoryOffer) - { - // Cancel the inventory offer for the previously viewed notice - mInventoryOffer->forceResponse(IOR_DECLINE); - mInventoryOffer = NULL; - } - - if (inventory_offer) - { - mInventoryOffer = inventory_offer; - - std::string icon_name = LLInventoryIcon::getIconName(mInventoryOffer->mType, - LLInventoryType::IT_TEXTURE); - - mViewInventoryIcon->setValue(icon_name); - mViewInventoryIcon->setVisible(true); - - std::stringstream ss; - ss << " " << inventory_name; - - mViewInventoryName->setText(ss.str()); - mBtnOpenAttachment->setEnabled(true); - } - else - { - mViewInventoryName->clear(); - mViewInventoryIcon->setVisible(false); - mBtnOpenAttachment->setEnabled(false); - } -} - -void LLPanelGroupNotices::arrangeNoticeView(ENoticeView view_type) -{ - if (CREATE_NEW_NOTICE == view_type) - { - mPanelCreateNotice->setVisible(true); - mPanelViewNotice->setVisible(false); - } - else - { - mPanelCreateNotice->setVisible(false); - mPanelViewNotice->setVisible(true); - mBtnOpenAttachment->setEnabled(false); - } -} -void LLPanelGroupNotices::setGroupID(const LLUUID& id) -{ - sInstances.erase(mGroupID); - LLPanelGroupTab::setGroupID(id); - sInstances[mGroupID] = this; - - mBtnNewMessage->setEnabled(gAgent.hasPowerInGroup(mGroupID, GP_NOTICES_SEND)); - - LLGroupDropTarget* target = getChild ("drop_target"); - target->setPanel (this); - target->setGroup (mGroupID); - - if(mViewMessage) - mViewMessage->clear(); - - if(mViewInventoryName) - mViewInventoryName->clear(); - - activate(); -} +/** + * @file llpanelgroupnotices.cpp + * @brief A panel to display group notices. + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelgroupnotices.h" + +#include "llview.h" + +#include "llavatarnamecache.h" +#include "llinventory.h" +#include "llviewerinventory.h" +#include "llinventorydefines.h" +#include "llinventoryfunctions.h" +#include "llinventoryicon.h" +#include "llinventorymodel.h" +#include "llagent.h" +#include "llagentui.h" + +#include "lllineeditor.h" +#include "lltexteditor.h" +#include "llbutton.h" +#include "lliconctrl.h" +#include "llcheckboxctrl.h" +#include "llscrolllistctrl.h" +#include "llscrolllistitem.h" +#include "lltextbox.h" +#include "lltrans.h" + +#include "roles_constants.h" +#include "llviewerwindow.h" +#include "llviewermessage.h" +#include "llnotificationsutil.h" +#include "llgiveinventory.h" + +static LLPanelInjector t_panel_group_notices("panel_group_notices"); + + +///////////////////////// +// LLPanelGroupNotices // +///////////////////////// +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLDropTarget +// +// This handy class is a simple way to drop something on another +// view. It handles drop events, always setting itself to the size of +// its parent. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLGroupDropTarget : public LLView +{ +public: + struct Params : public LLInitParam::Block + { + // *NOTE: These parameters logically Mandatory, but are not + // specified in XML files, hence Optional + Optional panel; + Optional group_id; + Params() + : panel("panel"), + group_id("group_id") + { + changeDefault(mouse_opaque, false); + changeDefault(follows.flags, FOLLOWS_ALL); + } + }; + LLGroupDropTarget(const Params&); + ~LLGroupDropTarget() {}; + + // + // LLView functionality + virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + void setPanel (LLPanelGroupNotices* panel) {mGroupNoticesPanel = panel;}; + void setGroup (LLUUID group) {mGroupID = group;}; + +protected: + LLPanelGroupNotices* mGroupNoticesPanel; + LLUUID mGroupID; +}; + +static LLDefaultChildRegistry::Register r("group_drop_target"); + +LLGroupDropTarget::LLGroupDropTarget(const LLGroupDropTarget::Params& p) +: LLView(p), + mGroupNoticesPanel(p.panel), + mGroupID(p.group_id) +{} + +bool LLGroupDropTarget::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + bool handled = false; + + if (!gAgent.hasPowerInGroup(mGroupID,GP_NOTICES_SEND)) + { + *accept = ACCEPT_NO; + return true; + } + + if(getParent()) + { + // check if inside + //LLRect parent_rect = mParentView->getRect(); + //getRect().set(0, parent_rect.getHeight(), parent_rect.getWidth(), 0); + handled = true; + + // check the type + switch(cargo_type) + { + case DAD_TEXTURE: + case DAD_SOUND: + case DAD_LANDMARK: + case DAD_SCRIPT: + case DAD_OBJECT: + case DAD_NOTECARD: + case DAD_CLOTHING: + case DAD_BODYPART: + case DAD_ANIMATION: + case DAD_GESTURE: + case DAD_CALLINGCARD: + case DAD_MESH: + case DAD_SETTINGS: + case DAD_MATERIAL: + { + LLViewerInventoryItem* inv_item = (LLViewerInventoryItem*)cargo_data; + if(gInventory.getItem(inv_item->getUUID()) + && LLGiveInventory::isInventoryGroupGiveAcceptable(inv_item)) + { + // *TODO: get multiple object transfers working + *accept = ACCEPT_YES_COPY_SINGLE; + if(drop) + { + mGroupNoticesPanel->setItem(inv_item); + } + } + else + { + // It's not in the user's inventory (it's probably + // in an object's contents), so disallow dragging + // it here. You can't give something you don't + // yet have. + *accept = ACCEPT_NO; + } + break; + } + case DAD_CATEGORY: + default: + *accept = ACCEPT_NO; + break; + } + } + return handled; +} + +//----------------------------------------------------------------------------- +// LLPanelGroupNotices +//----------------------------------------------------------------------------- +std::string build_notice_date(const U32& the_time) +{ + // ISO 8601 date format + + time_t t = (time_t)the_time; + + if (!t) + { + time(&t); + } + + std::string dateStr = "["+ LLTrans::getString("LTimeYear") + "]/[" + + LLTrans::getString("LTimeMthNum") + "]/[" + + LLTrans::getString("LTimeDay") + "] [" + + LLTrans::getString("LTimeHour") + "]:[" + + LLTrans::getString("LTimeMin") + "]:[" + + LLTrans::getString("LTimeSec") + "]"; + LLSD substitution; + substitution["datetime"] = (S32) t; + LLStringUtil::format (dateStr, substitution); + return dateStr; +} + +LLPanelGroupNotices::LLPanelGroupNotices() : + LLPanelGroupTab(), + mInventoryItem(NULL), + mInventoryOffer(NULL) +{ + + +} + +LLPanelGroupNotices::~LLPanelGroupNotices() +{ + sInstances.erase(mGroupID); + + if (mInventoryOffer) + { + // Cancel the inventory offer. + mInventoryOffer->forceResponse(IOR_DECLINE); + + mInventoryOffer = NULL; + } +} + + +bool LLPanelGroupNotices::isVisibleByAgent(LLAgent* agentp) +{ + return mAllowEdit && + agentp->hasPowerInGroup(mGroupID, GP_NOTICES_SEND | GP_NOTICES_RECEIVE); +} + +bool LLPanelGroupNotices::postBuild() +{ + constexpr bool recurse = true; + + mNoticesList = getChild("notice_list",recurse); + mNoticesList->setCommitOnSelectionChange(true); + mNoticesList->setCommitCallback(onSelectNotice, this); + mNoticesList->sortByColumn("date", false); + + mBtnNewMessage = getChild("create_new_notice",recurse); + mBtnNewMessage->setClickedCallback(onClickNewMessage, this); + mBtnNewMessage->setEnabled(gAgent.hasPowerInGroup(mGroupID, GP_NOTICES_SEND)); + + mBtnGetPastNotices = getChild("refresh_notices",recurse); + mBtnGetPastNotices->setClickedCallback(onClickRefreshNotices, this); + + // Create + mCreateSubject = getChild("create_subject",recurse); + mCreateMessage = getChild("create_message",recurse); + + mCreateInventoryName = getChild("create_inventory_name",recurse); + mCreateInventoryName->setTabStop(false); + mCreateInventoryName->setEnabled(false); + + mCreateInventoryIcon = getChild("create_inv_icon",recurse); + mCreateInventoryIcon->setVisible(false); + + mBtnSendMessage = getChild("send_notice",recurse); + mBtnSendMessage->setClickedCallback(onClickSendMessage, this); + + mBtnRemoveAttachment = getChild("remove_attachment",recurse); + mBtnRemoveAttachment->setClickedCallback(onClickRemoveAttachment, this); + mBtnRemoveAttachment->setEnabled(false); + + // View + mViewSubject = getChild("view_subject",recurse); + mViewMessage = getChild("view_message",recurse); + + mViewInventoryName = getChild("view_inventory_name",recurse); + mViewInventoryName->setTabStop(false); + mViewInventoryName->setEnabled(false); + + mViewInventoryIcon = getChild("view_inv_icon",recurse); + mViewInventoryIcon->setVisible(false); + + mBtnOpenAttachment = getChild("open_attachment",recurse); + mBtnOpenAttachment->setClickedCallback(onClickOpenAttachment, this); + + mNoNoticesStr = getString("no_notices_text"); + + mPanelCreateNotice = getChild("panel_create_new_notice",recurse); + mPanelViewNotice = getChild("panel_view_past_notice",recurse); + + LLGroupDropTarget* target = getChild ("drop_target"); + target->setPanel (this); + target->setGroup (mGroupID); + + arrangeNoticeView(VIEW_PAST_NOTICE); + + return LLPanelGroupTab::postBuild(); +} + +void LLPanelGroupNotices::activate() +{ + if (mNoticesList) + { + mNoticesList->deleteAllItems(); + mKnownNoticeIds.clear(); + } + + mPrevSelectedNotice = LLUUID(); + + bool can_send = gAgent.hasPowerInGroup(mGroupID,GP_NOTICES_SEND); + bool can_receive = gAgent.hasPowerInGroup(mGroupID,GP_NOTICES_RECEIVE); + + mPanelViewNotice->setEnabled(can_receive); + mPanelCreateNotice->setEnabled(can_send); + + // Always disabled to stop direct editing of attachment names + mCreateInventoryName->setEnabled(false); + mViewInventoryName->setEnabled(false); + + // If we can receive notices, grab them right away. + if (can_receive) + { + onClickRefreshNotices(this); + } +} + +void LLPanelGroupNotices::setItem(LLPointer inv_item) +{ + mInventoryItem = inv_item; + + bool item_is_multi = false; + if ( inv_item->getFlags() & LLInventoryItemFlags::II_FLAGS_OBJECT_HAS_MULTIPLE_ITEMS ) + { + item_is_multi = true; + }; + + std::string icon_name = LLInventoryIcon::getIconName(inv_item->getType(), + inv_item->getInventoryType(), + inv_item->getFlags(), + item_is_multi ); + + mCreateInventoryIcon->setValue(icon_name); + mCreateInventoryIcon->setVisible(true); + + std::stringstream ss; + ss << " " << mInventoryItem->getName(); + + mCreateInventoryName->setText(ss.str()); + mBtnRemoveAttachment->setEnabled(true); +} + +void LLPanelGroupNotices::onClickRemoveAttachment(void* data) +{ + LLPanelGroupNotices* self = (LLPanelGroupNotices*)data; + self->mInventoryItem = NULL; + self->mCreateInventoryName->clear(); + self->mCreateInventoryIcon->setVisible(false); + self->mBtnRemoveAttachment->setEnabled(false); +} + +//static +void LLPanelGroupNotices::onClickOpenAttachment(void* data) +{ + LLPanelGroupNotices* self = (LLPanelGroupNotices*)data; + + self->mInventoryOffer->forceResponse(IOR_ACCEPT); + self->mInventoryOffer = NULL; + self->mBtnOpenAttachment->setEnabled(false); +} + +void LLPanelGroupNotices::onClickSendMessage(void* data) +{ + LLPanelGroupNotices* self = (LLPanelGroupNotices*)data; + + if (self->mCreateSubject->getText().empty()) + { + // Must supply a subject + LLNotificationsUtil::add("MustSpecifyGroupNoticeSubject"); + return; + } + send_group_notice( + self->mGroupID, + self->mCreateSubject->getText(), + self->mCreateMessage->getText(), + self->mInventoryItem); + + + //instantly add new notice. actual notice will be added after ferreshNotices call + LLUUID id = LLUUID::generateNewID(); + std::string subj = self->mCreateSubject->getText(); + std::string name ; + LLAgentUI::buildFullname(name); + U32 timestamp = 0; + + LLSD row; + row["id"] = id; + + row["columns"][0]["column"] = "icon"; + + row["columns"][1]["column"] = "subject"; + row["columns"][1]["value"] = subj; + + row["columns"][2]["column"] = "from"; + row["columns"][2]["value"] = name; + + row["columns"][3]["column"] = "date"; + row["columns"][3]["value"] = build_notice_date(timestamp); + + row["columns"][4]["column"] = "sort"; + row["columns"][4]["value"] = llformat( "%u", timestamp); + + self->mNoticesList->addElement(row, ADD_BOTTOM); + self->mKnownNoticeIds.insert(id); + + self->mCreateMessage->clear(); + self->mCreateSubject->clear(); + onClickRemoveAttachment(data); + + self->arrangeNoticeView(VIEW_PAST_NOTICE); +} + +//static +void LLPanelGroupNotices::onClickNewMessage(void* data) +{ + LLPanelGroupNotices* self = (LLPanelGroupNotices*)data; + + self->arrangeNoticeView(CREATE_NEW_NOTICE); + + if (self->mInventoryOffer) + { + self->mInventoryOffer->forceResponse(IOR_DECLINE); + self->mInventoryOffer = NULL; + } + + self->mCreateSubject->clear(); + self->mCreateMessage->clear(); + if (self->mInventoryItem) onClickRemoveAttachment(self); + self->mNoticesList->deselectAllItems(true); // true == don't commit on chnage +} + +void LLPanelGroupNotices::refreshNotices() +{ + onClickRefreshNotices(this); +} + +void LLPanelGroupNotices::clearNoticeList() +{ + mPrevSelectedNotice = mNoticesList->getStringUUIDSelectedItem(); + mNoticesList->deleteAllItems(); + mKnownNoticeIds.clear(); +} + +void LLPanelGroupNotices::onClickRefreshNotices(void* data) +{ + LL_DEBUGS() << "LLPanelGroupNotices::onClickGetPastNotices" << LL_ENDL; + LLPanelGroupNotices* self = (LLPanelGroupNotices*)data; + + self->clearNoticeList(); + + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("GroupNoticesListRequest"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID",gAgent.getID()); + msg->addUUID("SessionID",gAgent.getSessionID()); + msg->nextBlock("Data"); + msg->addUUID("GroupID",self->mGroupID); + gAgent.sendReliableMessage(); +} + +//static +std::map LLPanelGroupNotices::sInstances; + +// static +void LLPanelGroupNotices::processGroupNoticesListReply(LLMessageSystem* msg, void** data) +{ + LLUUID group_id; + msg->getUUID("AgentData", "GroupID", group_id); + + std::map::iterator it = sInstances.find(group_id); + if (it == sInstances.end()) + { + LL_INFOS() << "Group Panel Notices " << group_id << " no longer in existence." + << LL_ENDL; + return; + } + + LLPanelGroupNotices* selfp = it->second; + if(!selfp) + { + LL_INFOS() << "Group Panel Notices " << group_id << " no longer in existence." + << LL_ENDL; + return; + } + + selfp->processNotices(msg); +} + +void LLPanelGroupNotices::processNotices(LLMessageSystem* msg) +{ + LLUUID id; + std::string subj; + std::string name; + U32 timestamp; + bool has_attachment; + U8 asset_type; + + S32 i=0; + S32 count = msg->getNumberOfBlocks("Data"); + + mNoticesList->setEnabled(true); + + //save sort state and set unsorted state to prevent unnecessary + //sorting while adding notices + bool save_sort = mNoticesList->isSorted(); + mNoticesList->setNeedsSort(false); + + for (;igetUUID("Data","NoticeID",id,i); + if (1 == count && id.isNull()) + { + // Only one entry, the dummy entry. + mNoticesList->setCommentText(mNoNoticesStr); + mNoticesList->setEnabled(false); + return; + } + + // Due to some network delays we can receive notice list more than once... + // So add only unique notices + if (mKnownNoticeIds.find(id) != mKnownNoticeIds.end()) + { + // If items with this ID already in the list - skip it + continue; + } + + msg->getString("Data","Subject",subj,i); + msg->getString("Data","FromName",name,i); + msg->getBOOL("Data","HasAttachment",has_attachment,i); + msg->getU8("Data","AssetType",asset_type,i); + msg->getU32("Data","Timestamp",timestamp,i); + + // we only have the legacy name here, convert it to a username + name = LLCacheName::buildUsername(name); + + LLSD row; + row["id"] = id; + row["columns"][0]["column"] = "icon"; + if (has_attachment) + { + std::string icon_name = LLInventoryIcon::getIconName( + (LLAssetType::EType)asset_type, + LLInventoryType::IT_NONE); + row["columns"][0]["type"] = "icon"; + row["columns"][0]["value"] = icon_name; + } + + row["columns"][1]["column"] = "subject"; + row["columns"][1]["value"] = subj; + + row["columns"][2]["column"] = "from"; + row["columns"][2]["value"] = name; + + row["columns"][3]["column"] = "date"; + row["columns"][3]["value"] = build_notice_date(timestamp); + + row["columns"][4]["column"] = "sort"; + row["columns"][4]["value"] = llformat( "%u", timestamp); + + mNoticesList->addElement(row, ADD_BOTTOM); + mKnownNoticeIds.insert(id); + } + + mNoticesList->setNeedsSort(save_sort); + mNoticesList->updateSort(); + if (mPanelViewNotice->getVisible()) + { + if (!mNoticesList->selectByID(mPrevSelectedNotice)) + { + mNoticesList->selectFirstItem(); + } + } +} + +void LLPanelGroupNotices::onSelectNotice(LLUICtrl* ctrl, void* data) +{ + LLPanelGroupNotices* self = (LLPanelGroupNotices*)data; + + if(!self) return; + LLScrollListItem* item = self->mNoticesList->getFirstSelected(); + if (!item) return; + + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("GroupNoticeRequest"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID",gAgent.getID()); + msg->addUUID("SessionID",gAgent.getSessionID()); + msg->nextBlock("Data"); + msg->addUUID("GroupNoticeID",item->getUUID()); + gAgent.sendReliableMessage(); + + LL_DEBUGS() << "Item " << item->getUUID() << " selected." << LL_ENDL; +} + +void LLPanelGroupNotices::showNotice(const std::string& subject, + const std::string& message, + const bool& has_inventory, + const std::string& inventory_name, + LLOfferInfo* inventory_offer) +{ + arrangeNoticeView(VIEW_PAST_NOTICE); + + if(mViewSubject) mViewSubject->setText(subject); + if(mViewMessage) mViewMessage->setText(message); + + if (mInventoryOffer) + { + // Cancel the inventory offer for the previously viewed notice + mInventoryOffer->forceResponse(IOR_DECLINE); + mInventoryOffer = NULL; + } + + if (inventory_offer) + { + mInventoryOffer = inventory_offer; + + std::string icon_name = LLInventoryIcon::getIconName(mInventoryOffer->mType, + LLInventoryType::IT_TEXTURE); + + mViewInventoryIcon->setValue(icon_name); + mViewInventoryIcon->setVisible(true); + + std::stringstream ss; + ss << " " << inventory_name; + + mViewInventoryName->setText(ss.str()); + mBtnOpenAttachment->setEnabled(true); + } + else + { + mViewInventoryName->clear(); + mViewInventoryIcon->setVisible(false); + mBtnOpenAttachment->setEnabled(false); + } +} + +void LLPanelGroupNotices::arrangeNoticeView(ENoticeView view_type) +{ + if (CREATE_NEW_NOTICE == view_type) + { + mPanelCreateNotice->setVisible(true); + mPanelViewNotice->setVisible(false); + } + else + { + mPanelCreateNotice->setVisible(false); + mPanelViewNotice->setVisible(true); + mBtnOpenAttachment->setEnabled(false); + } +} +void LLPanelGroupNotices::setGroupID(const LLUUID& id) +{ + sInstances.erase(mGroupID); + LLPanelGroupTab::setGroupID(id); + sInstances[mGroupID] = this; + + mBtnNewMessage->setEnabled(gAgent.hasPowerInGroup(mGroupID, GP_NOTICES_SEND)); + + LLGroupDropTarget* target = getChild ("drop_target"); + target->setPanel (this); + target->setGroup (mGroupID); + + if(mViewMessage) + mViewMessage->clear(); + + if(mViewInventoryName) + mViewInventoryName->clear(); + + activate(); +} diff --git a/indra/newview/llpanelgroupnotices.h b/indra/newview/llpanelgroupnotices.h index 890c643946..6cb04c0593 100644 --- a/indra/newview/llpanelgroupnotices.h +++ b/indra/newview/llpanelgroupnotices.h @@ -1,124 +1,124 @@ -/** - * @file llpanelgroupnotices.h - * @brief A panel to display group notices. - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELGROUPNOTICES_H -#define LL_LLPANELGROUPNOTICES_H - -#include "llpanelgroup.h" -#include "llpointer.h" -#include "llinventory.h" - -class LLLineEditor; -class LLTextEditor; -class LLButton; -class LLIconCtrl; -class LLCheckBoxCtrl; -class LLScrollListCtrl; - -class LLPanelGroupNotices : public LLPanelGroupTab -{ -public: - LLPanelGroupNotices(); - virtual ~LLPanelGroupNotices(); - - // LLPanelGroupTab - virtual void activate(); - //virtual bool needsApply(std::string& mesg); - //virtual bool apply(std::string& mesg); - //virtual void update(); - - virtual bool postBuild(); - virtual bool isVisibleByAgent(LLAgent* agentp); - - void setItem(LLPointer inv_item); - - static void processGroupNoticesListReply(LLMessageSystem* msg, void** data); - - void showNotice(const std::string& subject, - const std::string& message, - const bool& has_inventory, - const std::string& inventory_name, - LLOfferInfo* inventory_offer); - - void refreshNotices(); - - void clearNoticeList(); - - virtual void setGroupID(const LLUUID& id); - -private: - static void onClickRemoveAttachment(void* data); - static void onClickOpenAttachment(void* data); - static void onClickSendMessage(void* data); - static void onClickNewMessage(void* data); - static void onClickRefreshNotices(void* data); - - void processNotices(LLMessageSystem* msg); - static void onSelectNotice(LLUICtrl* ctrl, void* data); - - enum ENoticeView - { - VIEW_PAST_NOTICE, - CREATE_NEW_NOTICE - }; - - void arrangeNoticeView(ENoticeView view_type); - - LLPointer mInventoryItem; - - LLLineEditor *mCreateSubject; - LLLineEditor *mCreateInventoryName; - LLTextEditor *mCreateMessage; - - LLLineEditor *mViewSubject; - LLLineEditor *mViewInventoryName; - LLTextEditor *mViewMessage; - - LLButton *mBtnSendMessage; - LLButton *mBtnNewMessage; - LLButton *mBtnRemoveAttachment; - LLButton *mBtnOpenAttachment; - LLButton *mBtnGetPastNotices; - - LLPanel *mPanelCreateNotice; - LLPanel *mPanelViewNotice; - - LLIconCtrl *mCreateInventoryIcon; - LLIconCtrl *mViewInventoryIcon; - - LLScrollListCtrl *mNoticesList; - std::set mKnownNoticeIds; // Dupplicate avoidance, to avoid searching and inserting dupplciates into mNoticesList - - std::string mNoNoticesStr; - - LLOfferInfo* mInventoryOffer; - - LLUUID mPrevSelectedNotice; - - static std::map sInstances; -}; - -#endif +/** + * @file llpanelgroupnotices.h + * @brief A panel to display group notices. + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELGROUPNOTICES_H +#define LL_LLPANELGROUPNOTICES_H + +#include "llpanelgroup.h" +#include "llpointer.h" +#include "llinventory.h" + +class LLLineEditor; +class LLTextEditor; +class LLButton; +class LLIconCtrl; +class LLCheckBoxCtrl; +class LLScrollListCtrl; + +class LLPanelGroupNotices : public LLPanelGroupTab +{ +public: + LLPanelGroupNotices(); + virtual ~LLPanelGroupNotices(); + + // LLPanelGroupTab + virtual void activate(); + //virtual bool needsApply(std::string& mesg); + //virtual bool apply(std::string& mesg); + //virtual void update(); + + virtual bool postBuild(); + virtual bool isVisibleByAgent(LLAgent* agentp); + + void setItem(LLPointer inv_item); + + static void processGroupNoticesListReply(LLMessageSystem* msg, void** data); + + void showNotice(const std::string& subject, + const std::string& message, + const bool& has_inventory, + const std::string& inventory_name, + LLOfferInfo* inventory_offer); + + void refreshNotices(); + + void clearNoticeList(); + + virtual void setGroupID(const LLUUID& id); + +private: + static void onClickRemoveAttachment(void* data); + static void onClickOpenAttachment(void* data); + static void onClickSendMessage(void* data); + static void onClickNewMessage(void* data); + static void onClickRefreshNotices(void* data); + + void processNotices(LLMessageSystem* msg); + static void onSelectNotice(LLUICtrl* ctrl, void* data); + + enum ENoticeView + { + VIEW_PAST_NOTICE, + CREATE_NEW_NOTICE + }; + + void arrangeNoticeView(ENoticeView view_type); + + LLPointer mInventoryItem; + + LLLineEditor *mCreateSubject; + LLLineEditor *mCreateInventoryName; + LLTextEditor *mCreateMessage; + + LLLineEditor *mViewSubject; + LLLineEditor *mViewInventoryName; + LLTextEditor *mViewMessage; + + LLButton *mBtnSendMessage; + LLButton *mBtnNewMessage; + LLButton *mBtnRemoveAttachment; + LLButton *mBtnOpenAttachment; + LLButton *mBtnGetPastNotices; + + LLPanel *mPanelCreateNotice; + LLPanel *mPanelViewNotice; + + LLIconCtrl *mCreateInventoryIcon; + LLIconCtrl *mViewInventoryIcon; + + LLScrollListCtrl *mNoticesList; + std::set mKnownNoticeIds; // Dupplicate avoidance, to avoid searching and inserting dupplciates into mNoticesList + + std::string mNoNoticesStr; + + LLOfferInfo* mInventoryOffer; + + LLUUID mPrevSelectedNotice; + + static std::map sInstances; +}; + +#endif diff --git a/indra/newview/llpanelgrouproles.cpp b/indra/newview/llpanelgrouproles.cpp index 087bd41cd4..cb6d44f1e7 100644 --- a/indra/newview/llpanelgrouproles.cpp +++ b/indra/newview/llpanelgrouproles.cpp @@ -1,3327 +1,3327 @@ -/** - * @file llpanelgrouproles.cpp - * @brief Panel for roles information about a particular group. - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llcheckboxctrl.h" - -#include "llagent.h" -#include "llavatarnamecache.h" -#include "llbutton.h" -#include "llfiltereditor.h" -#include "llfloatergroupbulkban.h" -#include "llfloatergroupinvite.h" -#include "llavataractions.h" -#include "lliconctrl.h" -#include "lllineeditor.h" -#include "llnamelistctrl.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "llpanelgrouproles.h" -#include "llscrolllistctrl.h" -#include "llscrolllistitem.h" -#include "llscrolllistcell.h" -#include "llslurl.h" -#include "lltabcontainer.h" -#include "lltextbox.h" -#include "lltexteditor.h" -#include "lltrans.h" -#include "llviewertexturelist.h" -#include "llviewerwindow.h" -#include "llfocusmgr.h" -#include "llviewercontrol.h" - -#include "roles_constants.h" - -static LLPanelInjector t_panel_group_roles("panel_group_roles"); - -bool agentCanRemoveFromRole(const LLUUID& group_id, - const LLUUID& role_id) -{ - return gAgent.hasPowerInGroup(group_id, GP_ROLE_REMOVE_MEMBER); -} - -bool agentCanAddToRole(const LLUUID& group_id, - const LLUUID& role_id) -{ - if (gAgent.isGodlike()) - return true; - - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_id); - if (!gdatap) - { - LL_WARNS() << "agentCanAddToRole " - << "-- No group data!" << LL_ENDL; - return false; - } - - //make sure the agent is in the group - LLGroupMgrGroupData::member_list_t::iterator mi = gdatap->mMembers.find(gAgent.getID()); - if (mi == gdatap->mMembers.end()) - { - return false; - } - - LLGroupMemberData* member_data = (*mi).second; - - // Owners can add to any role. - if ( member_data->isInRole(gdatap->mOwnerRole) ) - { - return true; - } - - // 'Limited assign members' can add to roles the user is in. - if ( gAgent.hasPowerInGroup(group_id, GP_ROLE_ASSIGN_MEMBER_LIMITED) && - member_data->isInRole(role_id) ) - { - return true; - } - - // 'assign members' can add to non-owner roles. - if ( gAgent.hasPowerInGroup(group_id, GP_ROLE_ASSIGN_MEMBER) && - role_id != gdatap->mOwnerRole ) - { - return true; - } - - return false; -} - - -// LLPanelGroupRoles ///////////////////////////////////////////////////// - -// static -LLPanelGroupRoles::LLPanelGroupRoles() -: LLPanelGroupTab(), - mCurrentTab(NULL), - mRequestedTab( NULL ), - mSubTabContainer( NULL ), - mFirstUse( true ) -{ -} - -LLPanelGroupRoles::~LLPanelGroupRoles() -{ -} - -bool LLPanelGroupRoles::postBuild() -{ - LL_DEBUGS() << "LLPanelGroupRoles::postBuild()" << LL_ENDL; - - mSubTabContainer = getChild("roles_tab_container"); - - if (!mSubTabContainer) return false; - - // Hook up each sub-tabs callback and widgets. - for (S32 i = 0; i < mSubTabContainer->getTabCount(); ++i) - { - LLPanel* panel = mSubTabContainer->getPanelByIndex(i); - LLPanelGroupSubTab* subtabp = dynamic_cast(panel); - if (!subtabp) - { - LL_WARNS() << "Invalid subtab panel: " << panel->getName() << LL_ENDL; - return false; - } - - // Hand the subtab a pointer to this LLPanelGroupRoles, so that it can - // look around for the widgets it is interested in. - if (!subtabp->postBuildSubTab(this)) - return false; - - //subtabp->addObserver(this); - } - // Add click callbacks to tab switching. - mSubTabContainer->setValidateBeforeCommit(boost::bind(&LLPanelGroupRoles::handleSubTabSwitch, this, _1)); - - // Set the current tab to whatever is currently being shown. - mCurrentTab = (LLPanelGroupTab*) mSubTabContainer->getCurrentPanel(); - if (!mCurrentTab) - { - // Need to select a tab. - mSubTabContainer->selectFirstTab(); - mCurrentTab = (LLPanelGroupTab*) mSubTabContainer->getCurrentPanel(); - } - - if (!mCurrentTab) return false; - - // Act as though this tab was just activated. - mCurrentTab->activate(); - - // Read apply text from the xml file. - mDefaultNeedsApplyMesg = getString("default_needs_apply_text"); - mWantApplyMesg = getString("want_apply_text"); - - return LLPanelGroupTab::postBuild(); -} - -bool LLPanelGroupRoles::isVisibleByAgent(LLAgent* agentp) -{ - /* This power was removed to make group roles simpler - return agentp->hasPowerInGroup(mGroupID, - GP_ROLE_CREATE | - GP_ROLE_DELETE | - GP_ROLE_PROPERTIES | - GP_ROLE_VIEW | - GP_ROLE_ASSIGN_MEMBER | - GP_ROLE_REMOVE_MEMBER | - GP_ROLE_CHANGE_ACTIONS | - GP_MEMBER_INVITE | - GP_MEMBER_EJECT | - GP_MEMBER_OPTIONS ); - */ - return mAllowEdit && agentp->isInGroup(mGroupID); - -} - -bool LLPanelGroupRoles::handleSubTabSwitch(const LLSD& data) -{ - std::string panel_name = data.asString(); - - if(mRequestedTab != NULL)//we already have tab change request - { - return false; - } - - mRequestedTab = static_cast(mSubTabContainer->getPanelByName(panel_name)); - - std::string mesg; - if (mCurrentTab && mCurrentTab->needsApply(mesg)) - { - // If no message was provided, give a generic one. - if (mesg.empty()) - { - mesg = mDefaultNeedsApplyMesg; - } - // Create a notify box, telling the user about the unapplied tab. - LLSD args; - args["NEEDS_APPLY_MESSAGE"] = mesg; - args["WANT_APPLY_MESSAGE"] = mWantApplyMesg; - LLNotificationsUtil::add("PanelGroupApply", args, LLSD(), - boost::bind(&LLPanelGroupRoles::handleNotifyCallback, this, _1, _2)); - mHasModal = true; - - // Returning false will block a close action from finishing until - // we get a response back from the user. - return false; - } - - transitionToTab(); - return true; -} - -void LLPanelGroupRoles::transitionToTab() -{ - // Tell the current panel that it is being deactivated. - if (mCurrentTab) - { - mCurrentTab->deactivate(); - } - - // Tell the new panel that it is being activated. - if (mRequestedTab) - { - // This is now the current tab; - mCurrentTab = mRequestedTab; - mCurrentTab->activate(); - mRequestedTab = 0; - } -} - -bool LLPanelGroupRoles::handleNotifyCallback(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - mHasModal = false; - LLPanelGroupTab* transition_tab = mRequestedTab; - switch (option) - { - case 0: // "Apply Changes" - { - // Try to apply changes, and switch to the requested tab. - std::string apply_mesg; - if ( !apply( apply_mesg ) ) - { - // There was a problem doing the apply. - if ( !apply_mesg.empty() ) - { - mHasModal = true; - LLSD args; - args["MESSAGE"] = apply_mesg; - LLNotificationsUtil::add("GenericAlert", args, LLSD(), boost::bind(&LLPanelGroupRoles::onModalClose, this, _1, _2)); - } - // Skip switching tabs. - break; - } - transitionToTab(); - mSubTabContainer->selectTabPanel( transition_tab ); - - break; - } - case 1: // "Ignore Changes" - // Switch to the requested panel without applying changes - cancel(); - transitionToTab(); - mSubTabContainer->selectTabPanel( transition_tab ); - break; - case 2: // "Cancel" - default: - mRequestedTab = NULL; - // Do nothing. The user is canceling the action. - break; - } - return false; -} - -bool LLPanelGroupRoles::onModalClose(const LLSD& notification, const LLSD& response) -{ - mHasModal = false; - return false; -} - -bool LLPanelGroupRoles::apply(std::string& mesg) -{ - // Pass this along to the currently visible sub tab. - if (!mSubTabContainer) return false; - - LLPanelGroupTab* panelp = (LLPanelGroupTab*) mSubTabContainer->getCurrentPanel(); - if (!panelp) return false; - - // Ignore the needs apply message. - std::string ignore_mesg; - if ( !panelp->needsApply(ignore_mesg) ) - { - // We don't need to apply anything. - // We're done. - return true; - } - - // Try to do the actual apply. - return panelp->apply(mesg); -} - -void LLPanelGroupRoles::cancel() -{ - // Pass this along to the currently visible sub tab. - if (!mSubTabContainer) return; - - LLPanelGroupTab* panelp = (LLPanelGroupTab*) mSubTabContainer->getCurrentPanel(); - if (!panelp) return; - - panelp->cancel(); -} - -void LLPanelGroupRoles::update(LLGroupChange gc) -{ - if (mGroupID.isNull()) return; - - LLPanelGroupTab* panelp = (LLPanelGroupTab*) mSubTabContainer->getCurrentPanel(); - if (panelp) - { - panelp->update(gc); - } - else - { - LL_WARNS() << "LLPanelGroupRoles::update() -- No subtab to update!" << LL_ENDL; - } - -} - -void LLPanelGroupRoles::activate() -{ - if (!gAgent.isInGroup(mGroupID)) return; - - // Start requesting member and role data if needed. - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - - if (!gdatap || !gdatap->isRoleDataComplete() ) - { - // Mildly hackish - clear all pending changes - cancel(); - - LLGroupMgr::getInstance()->sendGroupRoleDataRequest(mGroupID); - } - - // Need this to get base group member powers - if (!gdatap || !gdatap->isGroupPropertiesDataComplete() ) - { - LLGroupMgr::getInstance()->sendGroupPropertiesRequest(mGroupID); - } - - mFirstUse = false; - - LLPanelGroupTab* panelp = (LLPanelGroupTab*) mSubTabContainer->getCurrentPanel(); - if (panelp) panelp->activate(); -} - -void LLPanelGroupRoles::deactivate() -{ - LLPanelGroupTab* panelp = (LLPanelGroupTab*) mSubTabContainer->getCurrentPanel(); - if (panelp) panelp->deactivate(); -} - -bool LLPanelGroupRoles::needsApply(std::string& mesg) -{ - LLPanelGroupTab* panelp = (LLPanelGroupTab*) mSubTabContainer->getCurrentPanel(); - if (!panelp) return false; - - return panelp->needsApply(mesg); -} - -bool LLPanelGroupRoles::hasModal() -{ - if (mHasModal) return true; - - LLPanelGroupTab* panelp = (LLPanelGroupTab*) mSubTabContainer->getCurrentPanel(); - if (!panelp) return false; - - return panelp->hasModal(); -} - -void LLPanelGroupRoles::setGroupID(const LLUUID& id) -{ - LLPanelGroupTab::setGroupID(id); - - LLPanelGroupMembersSubTab* group_members_tab = findChild("members_sub_tab"); - LLPanelGroupRolesSubTab* group_roles_tab = findChild("roles_sub_tab"); - LLPanelGroupActionsSubTab* group_actions_tab = findChild("actions_sub_tab"); - LLPanelGroupBanListSubTab* group_ban_tab = findChild("banlist_sub_tab"); - - if(group_members_tab) group_members_tab->setGroupID(id); - if(group_roles_tab) group_roles_tab->setGroupID(id); - if(group_actions_tab) group_actions_tab->setGroupID(id); - if(group_ban_tab) group_ban_tab->setGroupID(id); - - LLButton* button = getChild("member_invite"); - if ( button ) - button->setEnabled(gAgent.hasPowerInGroup(mGroupID, GP_MEMBER_INVITE)); - - if(mSubTabContainer) - mSubTabContainer->selectTab(1); - group_roles_tab->mFirstOpen = true; - activate(); -} - - -// LLPanelGroupSubTab //////////////////////////////////////////////////// -LLPanelGroupSubTab::LLPanelGroupSubTab() -: LLPanelGroupTab(), - mHeader(NULL), - mFooter(NULL), - mActivated(false), - mHasGroupBanPower(false), - mSearchEditor(NULL) -{ -} - -LLPanelGroupSubTab::~LLPanelGroupSubTab() -{ - mSearchCommitConnection.disconnect(); -} - -bool LLPanelGroupSubTab::postBuildSubTab(LLView* root) -{ - // Get icons for later use. - mActionIcons.clear(); - - if (hasString("power_folder_icon")) - { - mActionIcons["folder"] = getString("power_folder_icon"); - } - - if (hasString("power_all_have_icon")) - { - mActionIcons["full"] = getString("power_all_have_icon"); - } - - if (hasString("power_partial_icon")) - { - mActionIcons["partial"] = getString("power_partial_icon"); - } - return true; -} - -bool LLPanelGroupSubTab::postBuild() -{ - // Hook up the search widgets. - constexpr bool recurse = true; - - mSearchEditor = findChild("filter_input", recurse); - if (mSearchEditor) // SubTab doesn't implement this, only some of derived classes - { - // panel - mSearchCommitConnection = mSearchEditor->setCommitCallback(boost::bind(&LLPanelGroupSubTab::setSearchFilter, this, _2)); - } - - return LLPanelGroupTab::postBuild(); -} - -void LLPanelGroupSubTab::setGroupID(const LLUUID& id) -{ - LLPanelGroupTab::setGroupID(id); - if(mSearchEditor) - { - mSearchEditor->clear(); - setSearchFilter(""); - } - - mActivated = false; -} - -void LLPanelGroupSubTab::setSearchFilter(const std::string& filter) -{ - if(mSearchFilter == filter) - return; - mSearchFilter = filter; - LLStringUtil::toLower(mSearchFilter); - update(GC_ALL); - onFilterChanged(); -} - -void LLPanelGroupSubTab::activate() -{ - setOthersVisible(true); -} - -void LLPanelGroupSubTab::deactivate() -{ - setOthersVisible(false); -} - -void LLPanelGroupSubTab::setOthersVisible(bool b) -{ - if (mHeader) - { - mHeader->setVisible( b ); - } - - if (mFooter) - { - mFooter->setVisible( b ); - } -} - -bool LLPanelGroupSubTab::matchesActionSearchFilter(std::string action) -{ - // If the search filter is empty, everything passes. - if (mSearchFilter.empty()) return true; - - LLStringUtil::toLower(action); - std::string::size_type match = action.find(mSearchFilter); - - if (std::string::npos == match) - { - // not found - return false; - } - else - { - return true; - } -} - -void LLPanelGroupSubTab::buildActionsList(LLScrollListCtrl* ctrl, - U64 allowed_by_some, - U64 allowed_by_all, - LLUICtrl::commit_callback_t commit_callback, - bool show_all, - bool filter, - bool is_owner_role) -{ - if (LLGroupMgr::getInstance()->mRoleActionSets.empty()) - { - LL_WARNS() << "Can't build action list - no actions found." << LL_ENDL; - return; - } - - mHasGroupBanPower = false; - - std::vector::iterator ras_it = LLGroupMgr::getInstance()->mRoleActionSets.begin(); - std::vector::iterator ras_end = LLGroupMgr::getInstance()->mRoleActionSets.end(); - for ( ; ras_it != ras_end; ++ras_it) - { - buildActionCategory(ctrl, - allowed_by_some, - allowed_by_all, - (*ras_it), - commit_callback, - show_all, - filter, - is_owner_role); - } -} - -void LLPanelGroupSubTab::buildActionCategory(LLScrollListCtrl* ctrl, - U64 allowed_by_some, - U64 allowed_by_all, - LLRoleActionSet* action_set, - LLUICtrl::commit_callback_t commit_callback, - bool show_all, - bool filter, - bool is_owner_role) -{ - LL_DEBUGS() << "Building role list for: " << action_set->mActionSetData->mName << LL_ENDL; - // See if the allow mask matches anything in this category. - if (show_all || (allowed_by_some & action_set->mActionSetData->mPowerBit)) - { - // List all the actions in this category that at least some members have. - LLSD row; - - row["columns"][0]["column"] = "icon"; - row["columns"][0]["type"] = "icon"; - - icon_map_t::iterator iter = mActionIcons.find("folder"); - if (iter != mActionIcons.end()) - { - row["columns"][0]["value"] = (*iter).second; - } - - row["columns"][1]["column"] = "action"; - row["columns"][1]["type"] = "text"; - row["columns"][1]["value"] = LLTrans::getString(action_set->mActionSetData->mName); - row["columns"][1]["font"]["name"] = "SANSSERIF_SMALL"; - - - LLScrollListItem* title_row = ctrl->addElement(row, ADD_BOTTOM, action_set->mActionSetData); - - LLScrollListText* name_textp = dynamic_cast(title_row->getColumn(2)); //?? I have no idea fix getColumn(1) return column spacer... - if (name_textp) - name_textp->setFontStyle(LLFontGL::BOLD); - - bool category_matches_filter = (filter) ? matchesActionSearchFilter(action_set->mActionSetData->mName) : true; - - std::vector::iterator ra_it = action_set->mActions.begin(); - std::vector::iterator ra_end = action_set->mActions.end(); - - bool items_match_filter = false; - bool can_change_actions = (!is_owner_role && gAgent.hasPowerInGroup(mGroupID, GP_ROLE_CHANGE_ACTIONS)); - - for ( ; ra_it != ra_end; ++ra_it) - { - // See if anyone has these action. - if (!show_all && !(allowed_by_some & (*ra_it)->mPowerBit)) - { - continue; - } - - // See if we are filtering out these actions - // If we aren't using filters, category_matches_filter will be true. - if (!category_matches_filter - && !matchesActionSearchFilter((*ra_it)->mDescription)) - { - continue; - } - - items_match_filter = true; - - // See if everyone has these actions. - bool show_full_strength = false; - if ( (allowed_by_some & (*ra_it)->mPowerBit) == (allowed_by_all & (*ra_it)->mPowerBit) ) - { - show_full_strength = true; - } - - LLSD row; - - S32 column_index = 0; - row["columns"][column_index]["column"] = "icon"; - ++column_index; - - - S32 check_box_index = -1; - if (commit_callback) - { - row["columns"][column_index]["column"] = "checkbox"; - row["columns"][column_index]["type"] = "checkbox"; - check_box_index = column_index; - ++column_index; - } - else - { - if (show_full_strength) - { - icon_map_t::iterator iter = mActionIcons.find("full"); - if (iter != mActionIcons.end()) - { - row["columns"][column_index]["column"] = "checkbox"; - row["columns"][column_index]["type"] = "icon"; - row["columns"][column_index]["value"] = (*iter).second; - ++column_index; - } - } - else - { - icon_map_t::iterator iter = mActionIcons.find("partial"); - if (iter != mActionIcons.end()) - { - row["columns"][column_index]["column"] = "checkbox"; - row["columns"][column_index]["type"] = "icon"; - row["columns"][column_index]["value"] = (*iter).second; - ++column_index; - } - row["enabled"] = false; - } - } - - row["columns"][column_index]["column"] = "action"; - row["columns"][column_index]["value"] = (*ra_it)->mDescription; - row["columns"][column_index]["font"] = "SANSSERIF_SMALL"; - - if(mHasGroupBanPower) - { - // The ban ability is being set. Prevent these abilities from being manipulated - if((*ra_it)->mPowerBit == GP_MEMBER_EJECT) - { - row["enabled"] = false; - } - else if((*ra_it)->mPowerBit == GP_ROLE_REMOVE_MEMBER) - { - row["enabled"] = false; - } - } - else - { - // The ban ability is not set. Allow these abilities to be manipulated - if((*ra_it)->mPowerBit == GP_MEMBER_EJECT) - { - row["enabled"] = true; - } - else if((*ra_it)->mPowerBit == GP_ROLE_REMOVE_MEMBER) - { - row["enabled"] = true; - } - } - - LLScrollListItem* item = ctrl->addElement(row, ADD_BOTTOM, (*ra_it)); - - if (-1 != check_box_index) - { - // Extract the checkbox that was created. - LLScrollListCheck* check_cell = (LLScrollListCheck*) item->getColumn(check_box_index); - LLCheckBoxCtrl* check = check_cell->getCheckBox(); - check->setEnabled(can_change_actions); - check->setCommitCallback(commit_callback); - check->setToolTip( check->getLabel() ); - - if (show_all) - { - check->setTentative(false); - if (allowed_by_some & (*ra_it)->mPowerBit) - { - check->set(true); - } - else - { - check->set(false); - } - } - else - { - check->set(true); - if (show_full_strength) - { - check->setTentative(false); - } - else - { - check->setTentative(true); - } - } - - // Regardless of whether or not this ability is allowed by all or some, we want to prevent - // the group managers from accidentally disabling either of the two additional abilities - // tied with GP_GROUP_BAN_ACCESS. - if( (allowed_by_all & GP_GROUP_BAN_ACCESS) == GP_GROUP_BAN_ACCESS || - (allowed_by_some & GP_GROUP_BAN_ACCESS) == GP_GROUP_BAN_ACCESS) - { - mHasGroupBanPower = true; - } - } - } - - if (!items_match_filter) - { - S32 title_index = ctrl->getItemIndex(title_row); - ctrl->deleteSingleItem(title_index); - } - } -} - -void LLPanelGroupSubTab::setFooterEnabled(bool enable) -{ - if (mFooter) - { - mFooter->setAllChildrenEnabled(enable); - } -} - - -// LLPanelGroupMembersSubTab ///////////////////////////////////////////// -static LLPanelInjector t_panel_group_members_subtab("panel_group_members_subtab"); - -LLPanelGroupMembersSubTab::LLPanelGroupMembersSubTab() -: LLPanelGroupSubTab(), - mMembersList(NULL), - mAssignedRolesList(NULL), - mAllowedActionsList(NULL), - mChanged(false), - mPendingMemberUpdate(false), - mHasMatch(false), - mNumOwnerAdditions(0) -{ -} - -LLPanelGroupMembersSubTab::~LLPanelGroupMembersSubTab() -{ - for (avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.begin(); it != mAvatarNameCacheConnections.end(); ++it) - { - if (it->second.connected()) - { - it->second.disconnect(); - } - } - mAvatarNameCacheConnections.clear(); - if (mMembersList) - { - gSavedSettings.setString("GroupMembersSortOrder", mMembersList->getSortColumnName()); - } -} - -bool LLPanelGroupMembersSubTab::postBuildSubTab(LLView* root) -{ - LLPanelGroupSubTab::postBuildSubTab(root); - - // Upcast parent so we can ask it for sibling controls. - LLPanelGroupRoles* parent = (LLPanelGroupRoles*) root; - - // Look recursively from the parent to find all our widgets. - bool recurse = true; - mHeader = parent->findChild("members_header", recurse); - mFooter = parent->findChild("members_footer", recurse); - - mMembersList = parent->getChild("member_list", recurse); - mAssignedRolesList = parent->getChild("member_assigned_roles", recurse); - mAllowedActionsList = parent->getChild("member_allowed_actions", recurse); - mActionDescription = parent->getChild("member_action_description", recurse); - - if (!mMembersList || !mAssignedRolesList || !mAllowedActionsList || !mActionDescription) return false; - - mAllowedActionsList->setCommitOnSelectionChange(true); - mAllowedActionsList->setCommitCallback(boost::bind(&LLPanelGroupMembersSubTab::updateActionDescription, this)); - - // We want to be notified whenever a member is selected. - mMembersList->setCommitOnSelectionChange(true); - mMembersList->setCommitCallback(onMemberSelect, this); - // Show the member's profile on double click. - mMembersList->setDoubleClickCallback(onMemberDoubleClick, this); - mMembersList->setContextMenu(LLScrollListCtrl::MENU_AVATAR); - mMembersList->setIsFriendCallback(LLAvatarActions::isFriend); - - LLSD row; - row["columns"][0]["column"] = "name"; - row["columns"][1]["column"] = "donated"; - row["columns"][2]["column"] = "online"; - mMembersList->addElement(row); - std::string order_by = gSavedSettings.getString("GroupMembersSortOrder"); - if(!order_by.empty()) - { - mMembersList->sortByColumn(order_by, true); - } - - LLButton* button = parent->getChild("member_invite", recurse); - if ( button ) - { - button->setClickedCallback(onInviteMember, this); - button->setEnabled(gAgent.hasPowerInGroup(mGroupID, GP_MEMBER_INVITE)); - } - - mEjectBtn = parent->getChild("member_eject", recurse); - if ( mEjectBtn ) - { - mEjectBtn->setClickedCallback(onEjectMembers, this); - mEjectBtn->setEnabled(false); - } - - mBanBtn = parent->getChild("member_ban", recurse); - if(mBanBtn) - { - mBanBtn->setClickedCallback(onBanMember, this); - mBanBtn->setEnabled(false); - } - - return true; -} - -void LLPanelGroupMembersSubTab::setGroupID(const LLUUID& id) -{ - //clear members list - if(mMembersList) mMembersList->deleteAllItems(); - if(mAssignedRolesList) mAssignedRolesList->deleteAllItems(); - if(mAllowedActionsList) mAllowedActionsList->deleteAllItems(); - - LLPanelGroupSubTab::setGroupID(id); -} - -// static -void LLPanelGroupMembersSubTab::onMemberSelect(LLUICtrl* ctrl, void* user_data) -{ - LLPanelGroupMembersSubTab* self = static_cast(user_data); - self->handleMemberSelect(); -} - -void LLPanelGroupMembersSubTab::handleMemberSelect() -{ - LL_DEBUGS() << "LLPanelGroupMembersSubTab::handleMemberSelect" << LL_ENDL; - - mAssignedRolesList->deleteAllItems(); - mAllowedActionsList->deleteAllItems(); - mActionDescription->clear(); - - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - if (!gdatap) - { - LL_WARNS() << "LLPanelGroupMembersSubTab::handleMemberSelect() " - << "-- No group data!" << LL_ENDL; - return; - } - - // Check if there is anything selected. - std::vector selection = mMembersList->getAllSelected(); - if (selection.empty()) return; - - // Build a vector of all selected members, and gather allowed actions. - uuid_vec_t selected_members; - U64 allowed_by_all = GP_ALL_POWERS; //0xFFFFffffFFFFffffLL; - U64 allowed_by_some = 0; - - std::vector::iterator itor; - for (itor = selection.begin(); - itor != selection.end(); ++itor) - { - LLUUID member_id = (*itor)->getUUID(); - - selected_members.push_back( member_id ); - // Get this member's power mask including any unsaved changes - - U64 powers = getAgentPowersBasedOnRoleChanges( member_id ); - - allowed_by_all &= powers; - allowed_by_some |= powers; - } - std::sort(selected_members.begin(), selected_members.end()); - - ////////////////////////////////// - // Build the allowed actions list. - ////////////////////////////////// - buildActionsList(mAllowedActionsList, - allowed_by_some, - allowed_by_all, - NULL, - false, - false, - false); - - ////////////////////////////////// - // Build the assigned roles list. - ////////////////////////////////// - // Add each role to the assigned roles list. - LLGroupMgrGroupData::role_list_t::iterator iter = gdatap->mRoles.begin(); - LLGroupMgrGroupData::role_list_t::iterator end = gdatap->mRoles.end(); - - bool can_ban_members = gAgent.hasPowerInGroup(mGroupID, GP_GROUP_BAN_ACCESS); - bool can_eject_members = gAgent.hasPowerInGroup(mGroupID, GP_MEMBER_EJECT); - bool member_is_owner = false; - - for( ; iter != end; ++iter) - { - // Count how many selected users are in this role. - const LLUUID& role_id = iter->first; - LLGroupRoleData* group_role_data = iter->second; - - if (group_role_data) - { - const bool needs_sort = false; - S32 count = group_role_data->getMembersInRole( - selected_members, needs_sort); - //check if the user has permissions to assign/remove - //members to/from the role (but the ability to add/remove - //should only be based on the "saved" changes to the role - //not in the temp/meta data. -jwolk - bool cb_enable = ( (count > 0) ? - agentCanRemoveFromRole(mGroupID, role_id) : - agentCanAddToRole(mGroupID, role_id) ); - - - // Owner role has special enabling permissions for removal. - if (cb_enable && (count > 0) && role_id == gdatap->mOwnerRole) - { - // Check if any owners besides this agent are selected. - uuid_vec_t::const_iterator member_iter; - uuid_vec_t::const_iterator member_end = - selected_members.end(); - for (member_iter = selected_members.begin(); - member_iter != member_end; - ++member_iter) - { - // Don't count the agent. - if ((*member_iter) == gAgent.getID()) continue; - - // Look up the member data. - LLGroupMgrGroupData::member_list_t::iterator mi = - gdatap->mMembers.find((*member_iter)); - if (mi == gdatap->mMembers.end()) continue; - LLGroupMemberData* member_data = (*mi).second; - // Is the member an owner? - if ( member_data && member_data->isInRole(gdatap->mOwnerRole) ) - { - // Can't remove other owners. - cb_enable = false; - can_ban_members = false; - break; - } - } - } - - //now see if there are any role changes for the selected - //members and remember to include them - uuid_vec_t::iterator sel_mem_iter = selected_members.begin(); - for (; sel_mem_iter != selected_members.end(); sel_mem_iter++) - { - LLRoleMemberChangeType type; - if ( getRoleChangeType(*sel_mem_iter, role_id, type) ) - { - if ( type == RMC_ADD ) count++; - else if ( type == RMC_REMOVE ) count--; - } - } - - // If anyone selected is in any role besides 'Everyone' then they can't be ejected. - if (role_id.notNull() && (count > 0)) - { - can_eject_members = false; - if (role_id == gdatap->mOwnerRole) - { - member_is_owner = true; - } - } - - LLRoleData rd; - if (gdatap->getRoleData(role_id,rd)) - { - std::ostringstream label; - label << rd.mRoleName; - // Don't bother showing a count, if there is only 0 or 1. - if (count > 1) - { - label << ": " << count ; - } - - LLSD row; - row["id"] = role_id; - - row["columns"][0]["column"] = "checkbox"; - row["columns"][0]["type"] = "checkbox"; - - row["columns"][1]["column"] = "role"; - row["columns"][1]["value"] = label.str(); - - if (row["id"].asUUID().isNull()) - { - // This is the everyone role, you can't take people out of the everyone role! - row["enabled"] = false; - } - - LLScrollListItem* item = mAssignedRolesList->addElement(row); - - // Extract the checkbox that was created. - LLScrollListCheck* check_cell = (LLScrollListCheck*) item->getColumn(0); - LLCheckBoxCtrl* check = check_cell->getCheckBox(); - check->setCommitCallback(onRoleCheck, this); - check->set( count > 0 ); - check->setTentative( - (0 != count) - && (selected_members.size() != - (uuid_vec_t::size_type)count)); - - //NOTE: as of right now a user can break the group - //by removing himself from a role if he is the - //last owner. We should check for this special case - // -jwolk - check->setEnabled(cb_enable); - item->setEnabled(cb_enable); - } - } - else - { - // This could happen if changes are not synced right on sub-panel change. - LL_WARNS() << "No group role data for " << iter->second << LL_ENDL; - } - } - mAssignedRolesList->setEnabled(true); - - if (gAgent.isGodlike()) - { - can_eject_members = true; - // can_ban_members = true; - } - - if (!can_eject_members && !member_is_owner) - { - // Maybe we can eject them because we are an owner... - LLGroupMgrGroupData::member_list_t::iterator mi = gdatap->mMembers.find(gAgent.getID()); - if (mi != gdatap->mMembers.end()) - { - LLGroupMemberData* member_data = (*mi).second; - - if ( member_data && member_data->isInRole(gdatap->mOwnerRole) ) - { - can_eject_members = true; - //can_ban_members = true; - } - } - - } - - // ... or we can eject them because we have all the requisite powers... - if( gAgent.hasPowerInGroup(mGroupID, GP_ROLE_REMOVE_MEMBER) && - !member_is_owner) - { - if( gAgent.hasPowerInGroup(mGroupID, GP_MEMBER_EJECT)) - { - can_eject_members = true; - } - - if( gAgent.hasPowerInGroup(mGroupID, GP_GROUP_BAN_ACCESS)) - { - can_ban_members = true; - } - } - - - uuid_vec_t::const_iterator member_iter = selected_members.begin(); - uuid_vec_t::const_iterator member_end = selected_members.end(); - for ( ; member_iter != member_end; ++member_iter) - { - // Don't count the agent. - if ((*member_iter) == gAgent.getID()) - { - can_eject_members = false; - can_ban_members = false; - } - } - - mBanBtn->setEnabled(can_ban_members); - mEjectBtn->setEnabled(can_eject_members); -} - -// static -void LLPanelGroupMembersSubTab::onMemberDoubleClick(void* user_data) -{ - LLPanelGroupMembersSubTab* self = static_cast(user_data); - self->handleMemberDoubleClick(); -} - -//static -void LLPanelGroupMembersSubTab::onInviteMember(void *userdata) -{ - LLPanelGroupMembersSubTab* selfp = (LLPanelGroupMembersSubTab*) userdata; - - if ( selfp ) - { - selfp->handleInviteMember(); - } -} - -void LLPanelGroupMembersSubTab::handleInviteMember() -{ - LLFloaterGroupInvite::showForGroup(mGroupID, NULL, false); -} - -void LLPanelGroupMembersSubTab::onEjectMembers(void *userdata) -{ - LLPanelGroupMembersSubTab* selfp = (LLPanelGroupMembersSubTab*) userdata; - - if ( selfp ) - { - selfp->confirmEjectMembers(); - } -} - -void LLPanelGroupMembersSubTab::confirmEjectMembers() -{ - std::vector selection = mMembersList->getAllSelected(); - if (selection.empty()) return; - - S32 selection_count = selection.size(); - if (selection_count == 1) - { - LLSD args; - LLAvatarName av_name; - LLAvatarNameCache::get(mMembersList->getValue(), &av_name); - args["AVATAR_NAME"] = av_name.getUserName(); - LLSD payload; - LLNotificationsUtil::add("EjectGroupMemberWarning", - args, - payload, - boost::bind(&LLPanelGroupMembersSubTab::handleEjectCallback, this, _1, _2)); - } - else - { - LLSD args; - args["COUNT"] = llformat("%d", selection_count); - LLSD payload; - LLNotificationsUtil::add("EjectGroupMembersWarning", - args, - payload, - boost::bind(&LLPanelGroupMembersSubTab::handleEjectCallback, this, _1, _2)); - } -} - -void LLPanelGroupMembersSubTab::handleEjectMembers() -{ - //send down an eject message - uuid_vec_t selected_members; - - std::vector selection = mMembersList->getAllSelected(); - if (selection.empty()) return; - - std::vector::iterator itor; - for (itor = selection.begin() ; - itor != selection.end(); ++itor) - { - LLUUID member_id = (*itor)->getUUID(); - selected_members.push_back( member_id ); - } - - mMembersList->deleteSelectedItems(); - - sendEjectNotifications(mGroupID, selected_members); - - LLGroupMgr::getInstance()->sendGroupMemberEjects(mGroupID, selected_members); -} - -bool LLPanelGroupMembersSubTab::handleEjectCallback(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (0 == option) // Eject button - { - handleEjectMembers(); - } - return false; -} - -void LLPanelGroupMembersSubTab::sendEjectNotifications(const LLUUID& group_id, const uuid_vec_t& selected_members) -{ - LLGroupMgrGroupData* group_data = LLGroupMgr::getInstance()->getGroupData(group_id); - - if (group_data) - { - for (uuid_vec_t::const_iterator i = selected_members.begin(); i != selected_members.end(); ++i) - { - LLSD args; - args["AVATAR_NAME"] = LLSLURL("agent", *i, "completename").getSLURLString(); - args["GROUP_NAME"] = group_data->mName; - - LLNotifications::instance().add(LLNotification::Params("EjectAvatarFromGroup").substitutions(args)); - } - } -} - -void LLPanelGroupMembersSubTab::handleRoleCheck(const LLUUID& role_id, - LLRoleMemberChangeType type) -{ - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - if (!gdatap) return; - - //add that the user is requesting to change the roles for selected - //members - U64 powers_all_have = GP_ALL_POWERS; - U64 powers_some_have = 0; - - bool is_owner_role = ( gdatap->mOwnerRole == role_id ); - LLUUID member_id; - - std::vector selection = mMembersList->getAllSelected(); - if (selection.empty()) - { - return; - } - - for (std::vector::iterator itor = selection.begin() ; - itor != selection.end(); ++itor) - { - member_id = (*itor)->getUUID(); - - //see if we requested a change for this member before - if ( mMemberRoleChangeData.find(member_id) == mMemberRoleChangeData.end() ) - { - mMemberRoleChangeData[member_id] = new role_change_data_map_t; - } - role_change_data_map_t* role_change_datap = mMemberRoleChangeData[member_id]; - - //now check to see if the selected group member - //had changed his association with the selected role before - - role_change_data_map_t::iterator role = role_change_datap->find(role_id); - if ( role != role_change_datap->end() ) - { - //see if the new change type cancels out the previous change - if (role->second != type) - { - role_change_datap->erase(role_id); - if ( is_owner_role ) mNumOwnerAdditions--; - } - //else do nothing - - if ( role_change_datap->empty() ) - { - //the current member now has no role changes - //so erase the role change and erase the member's entry - delete role_change_datap; - role_change_datap = NULL; - - mMemberRoleChangeData.erase(member_id); - } - } - else - { - //a previously unchanged role is being changed - (*role_change_datap)[role_id] = type; - if ( is_owner_role && type == RMC_ADD ) mNumOwnerAdditions++; - } - - //we need to calculate what powers the selected members - //have (including the role changes we're making) - //so that we can rebuild the action list - U64 new_powers = getAgentPowersBasedOnRoleChanges(member_id); - - powers_all_have &= new_powers; - powers_some_have |= new_powers; - } - - - mChanged = !mMemberRoleChangeData.empty(); - notifyObservers(); - - //alrighty now we need to update the actions list - //to reflect the changes - mAllowedActionsList->deleteAllItems(); - buildActionsList(mAllowedActionsList, - powers_some_have, - powers_all_have, - NULL, - false, - false, - false); -} - -// static -void LLPanelGroupMembersSubTab::onRoleCheck(LLUICtrl* ctrl, void* user_data) -{ - LLPanelGroupMembersSubTab* self = static_cast(user_data); - LLCheckBoxCtrl* check_box = static_cast(ctrl); - if (!check_box || !self) return; - - LLScrollListItem* first_selected = - self->mAssignedRolesList->getFirstSelected(); - if (first_selected) - { - LLUUID role_id = first_selected->getUUID(); - LLRoleMemberChangeType change_type = (check_box->get() ? - RMC_ADD : - RMC_REMOVE); - - self->handleRoleCheck(role_id, change_type); - } -} - -void LLPanelGroupMembersSubTab::handleMemberDoubleClick() -{ - LLScrollListItem* selected = mMembersList->getFirstSelected(); - if (selected) - { - LLUUID member_id = selected->getUUID(); - LLAvatarActions::showProfile( member_id ); - } -} - -void LLPanelGroupMembersSubTab::activate() -{ - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - - LLPanelGroupSubTab::activate(); - if(!mActivated) - { - if (!gdatap || !gdatap->isMemberDataComplete()) - { - LLGroupMgr::getInstance()->sendCapGroupMembersRequest(mGroupID); - } - - if (!gdatap || !gdatap->isRoleMemberDataComplete()) - { - LLGroupMgr::getInstance()->sendGroupRoleMembersRequest(mGroupID); - } - - update(GC_ALL); - mActivated = true; - } - else - { - // Members can be removed outside of this tab, checking changes - if (!gdatap || (gdatap->isMemberDataComplete() && gdatap->mMembers.size() != mMembersList->getItemCount())) - { - update(GC_MEMBER_DATA); - } - } - mActionDescription->clear(); -} - -void LLPanelGroupMembersSubTab::deactivate() -{ - LLPanelGroupSubTab::deactivate(); -} - -bool LLPanelGroupMembersSubTab::needsApply(std::string& mesg) -{ - return mChanged; -} - -void LLPanelGroupMembersSubTab::cancel() -{ - if ( mChanged ) - { - std::for_each(mMemberRoleChangeData.begin(), - mMemberRoleChangeData.end(), - DeletePairedPointer()); - mMemberRoleChangeData.clear(); - - mChanged = false; - notifyObservers(); - } -} - -bool LLPanelGroupMembersSubTab::apply(std::string& mesg) -{ - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - if (!gdatap) - { - LL_WARNS() << "Unable to get group data for group " << mGroupID << LL_ENDL; - - mesg.assign("Unable to save member data. Try again later."); - return false; - } - - if (mChanged) - { - //figure out if we are somehow adding an owner or not and alert - //the user...possibly make it ignorable - if ( mNumOwnerAdditions > 0 ) - { - LLRoleData rd; - LLSD args; - - if ( gdatap->getRoleData(gdatap->mOwnerRole, rd) ) - { - mHasModal = true; - args["ROLE_NAME"] = rd.mRoleName; - LLNotificationsUtil::add("AddGroupOwnerWarning", - args, - LLSD(), - boost::bind(&LLPanelGroupMembersSubTab::addOwnerCB, this, _1, _2)); - } - else - { - LL_WARNS() << "Unable to get role information for the owner role in group " << mGroupID << LL_ENDL; - - mesg.assign("Unable to retried specific group information. Try again later"); - return false; - } - - } - else - { - applyMemberChanges(); - } - } - - return true; -} - -bool LLPanelGroupMembersSubTab::addOwnerCB(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - mHasModal = false; - - if (0 == option) - { - // User clicked "Yes" - applyMemberChanges(); - } - return false; -} - -void LLPanelGroupMembersSubTab::applyMemberChanges() -{ - //sucks to do a find again here, but it is in constant time, so, could - //be worse - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - if (!gdatap) - { - LL_WARNS() << "Unable to get group data for group " << mGroupID << LL_ENDL; - return; - } - - //we need to add all of the changed roles data - //for each member whose role changed - for (member_role_changes_map_t::iterator member = mMemberRoleChangeData.begin(); - member != mMemberRoleChangeData.end(); ++member) - { - for (role_change_data_map_t::iterator role = member->second->begin(); - role != member->second->end(); ++role) - { - gdatap->changeRoleMember(role->first, //role_id - member->first, //member_id - role->second); //add/remove - } - - member->second->clear(); - delete member->second; - } - mMemberRoleChangeData.clear(); - - LLGroupMgr::getInstance()->sendGroupRoleMemberChanges(mGroupID); - //force a UI update - handleMemberSelect(); - - mChanged = false; - mNumOwnerAdditions = 0; - notifyObservers(); -} - -bool LLPanelGroupMembersSubTab::matchesSearchFilter(const std::string& fullname) -{ - // If the search filter is empty, everything passes. - if (mSearchFilter.empty()) return true; - - // Create a full name, and compare it to the search filter. - std::string fullname_lc(fullname); - LLStringUtil::toLower(fullname_lc); - - std::string::size_type match = fullname_lc.find(mSearchFilter); - - if (std::string::npos == match) - { - // not found - return false; - } - else - { - return true; - } -} - -U64 LLPanelGroupMembersSubTab::getAgentPowersBasedOnRoleChanges(const LLUUID& agent_id) -{ - //we loop over all of the changes - //if we are adding a role, then we simply add the role's powers - //if we are removing a role, we store that role id away - //and then we have to build the powers up bases on the roles the agent - //is in - - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - if (!gdatap) - { - LL_WARNS() << "LLPanelGroupMembersSubTab::getAgentPowersBasedOnRoleChanges() -- No group data!" << LL_ENDL; - return GP_NO_POWERS; - } - - LLGroupMgrGroupData::member_list_t::iterator iter = gdatap->mMembers.find(agent_id); - if ( iter == gdatap->mMembers.end() ) - { - LL_WARNS() << "LLPanelGroupMembersSubTab::getAgentPowersBasedOnRoleChanges() -- No member data for member with UUID " << agent_id << LL_ENDL; - return GP_NO_POWERS; - } - - LLGroupMemberData* member_data = (*iter).second; - if (!member_data) - { - LL_WARNS() << "LLPanelGroupMembersSubTab::getAgentPowersBasedOnRoleChanges() -- Null member data for member with UUID " << agent_id << LL_ENDL; - return GP_NO_POWERS; - } - - //see if there are unsaved role changes for this agent - role_change_data_map_t* role_change_datap = NULL; - member_role_changes_map_t::iterator member = mMemberRoleChangeData.find(agent_id); - if ( member != mMemberRoleChangeData.end() ) - { - //this member has unsaved role changes - //so grab them - role_change_datap = (*member).second; - } - - U64 new_powers = GP_NO_POWERS; - - if ( role_change_datap ) - { - uuid_vec_t roles_to_be_removed; - - for (role_change_data_map_t::iterator role = role_change_datap->begin(); - role != role_change_datap->end(); ++ role) - { - if ( role->second == RMC_ADD ) - { - new_powers |= gdatap->getRolePowers(role->first); - } - else - { - roles_to_be_removed.push_back(role->first); - } - } - - //loop over the member's current roles, summing up - //the powers (not including the role we are removing) - for (LLGroupMemberData::role_list_t::iterator current_role = member_data->roleBegin(); - current_role != member_data->roleEnd(); ++current_role) - { - bool role_in_remove_list = - (std::find(roles_to_be_removed.begin(), - roles_to_be_removed.end(), - current_role->second->getID()) != - roles_to_be_removed.end()); - - if ( !role_in_remove_list ) - { - new_powers |= - current_role->second->getRoleData().mRolePowers; - } - } - } - else - { - //there are no changes for this member - //the member's powers are just the ones stored in the group - //manager - new_powers = member_data->getAgentPowers(); - } - - return new_powers; -} - -//If there is no change, returns false be sure to verify -//that there is a role change before attempting to get it or else -//the data will make no sense. Stores the role change type -bool LLPanelGroupMembersSubTab::getRoleChangeType(const LLUUID& member_id, - const LLUUID& role_id, - LLRoleMemberChangeType& type) -{ - member_role_changes_map_t::iterator member_changes_iter = mMemberRoleChangeData.find(member_id); - if ( member_changes_iter != mMemberRoleChangeData.end() ) - { - role_change_data_map_t::iterator role_changes_iter = member_changes_iter->second->find(role_id); - if ( role_changes_iter != member_changes_iter->second->end() ) - { - type = role_changes_iter->second; - return true; - } - } - - return false; -} - -void LLPanelGroupMembersSubTab::draw() -{ - LLPanelGroupSubTab::draw(); - - if (mPendingMemberUpdate) - { - updateMembers(); - } -} - -void LLPanelGroupMembersSubTab::update(LLGroupChange gc) -{ - if (mGroupID.isNull()) return; - - if ( GC_TITLES == gc || GC_PROPERTIES == gc ) - { - // Don't care about title or general group properties updates. - return; - } - - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - if (!gdatap) - { - LL_WARNS() << "LLPanelGroupMembersSubTab::update() -- No group data!" << LL_ENDL; - return; - } - - // Wait for both all data to be retrieved before displaying anything. - if ( gdatap->isMemberDataComplete() - && gdatap->isRoleDataComplete() - && gdatap->isRoleMemberDataComplete()) - { - mMemberProgress = gdatap->mMembers.begin(); - mPendingMemberUpdate = true; - mHasMatch = false; - } - else - { - // Build a string with info on retrieval progress. - std::ostringstream retrieved; - - if ( gdatap->isRoleDataComplete() && gdatap->isMemberDataComplete() && !gdatap->mMembers.size() ) - { - // MAINT-5237 - retrieved << "Member list not available."; - } - else if ( !gdatap->isMemberDataComplete() ) - { - // Still busy retreiving member list. - retrieved << "Retrieving member list (" << gdatap->mMembers.size() - << " / " << gdatap->mMemberCount << ")..."; - } - else if( !gdatap->isRoleDataComplete() ) - { - // Still busy retreiving role list. - retrieved << "Retrieving role list (" << gdatap->mRoles.size() - << " / " << gdatap->mRoleCount << ")..."; - } - else // (!gdatap->isRoleMemberDataComplete()) - { - // Still busy retreiving role/member mappings. - retrieved << "Retrieving role member mappings..."; - } - mMembersList->setEnabled(false); - mMembersList->setCommentText(retrieved.str()); - } -} - -void LLPanelGroupMembersSubTab::addMemberToList(LLGroupMemberData* data) -{ - if (!data) return; - LLUIString donated = getString("donation_area"); - donated.setArg("[AREA]", llformat("%d", data->getContribution())); - - LLNameListCtrl::NameItem item_params; - item_params.value = data->getID(); - - item_params.columns.add().column("name").font.name("SANSSERIF_SMALL").style("NORMAL"); - - item_params.columns.add().column("donated").value(donated.getString()) - .font.name("SANSSERIF_SMALL").style("NORMAL"); - - item_params.columns.add().column("online").value(data->getOnlineStatus()) - .font.name("SANSSERIF_SMALL").style("NORMAL"); - - item_params.columns.add().column("title").value(data->getTitle()).font.name("SANSSERIF_SMALL").style("NORMAL");; - - mMembersList->addNameItemRow(item_params); - - mHasMatch = true; -} - -void LLPanelGroupMembersSubTab::onNameCache(const LLUUID& update_id, LLGroupMemberData* member, const LLAvatarName& av_name, const LLUUID& av_id) -{ - avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.find(av_id); - if (it != mAvatarNameCacheConnections.end()) - { - if (it->second.connected()) - { - it->second.disconnect(); - } - mAvatarNameCacheConnections.erase(it); - } - - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - if (!gdatap - || gdatap->getMemberVersion() != update_id - || !member) - { - return; - } - - // trying to avoid unnecessary hash lookups - if (matchesSearchFilter(av_name.getAccountName())) - { - addMemberToList(member); - if(!mMembersList->getEnabled()) - { - mMembersList->setEnabled(true); - } - } - -} - -void LLPanelGroupMembersSubTab::updateMembers() -{ - mPendingMemberUpdate = false; - - // Rebuild the members list. - - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - if (!gdatap) - { - LL_WARNS() << "LLPanelGroupMembersSubTab::updateMembers() -- No group data!" << LL_ENDL; - return; - } - - // Make sure all data is still complete. Incomplete data - // may occur if we refresh. - if ( !gdatap->isMemberDataComplete() - || !gdatap->isRoleDataComplete() - || !gdatap->isRoleMemberDataComplete()) - { - return; - } - - //cleanup list only for first iteration - if(mMemberProgress == gdatap->mMembers.begin()) - { - mMembersList->deleteAllItems(); - } - - LLGroupMgrGroupData::member_list_t::iterator end = gdatap->mMembers.end(); - - LLTimer update_time; - update_time.setTimerExpirySec(UPDATE_MEMBERS_SECONDS_PER_FRAME); - - for( ; mMemberProgress != end && !update_time.hasExpired(); ++mMemberProgress) - { - if (!mMemberProgress->second) - continue; - - // Do filtering on name if it is already in the cache. - LLAvatarName av_name; - if (LLAvatarNameCache::get(mMemberProgress->first, &av_name)) - { - if (matchesSearchFilter(av_name.getAccountName())) - { - addMemberToList(mMemberProgress->second); - } - } - else - { - // If name is not cached, onNameCache() should be called when it is cached and add this member to list. - avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.find(mMemberProgress->first); - if (it != mAvatarNameCacheConnections.end()) - { - if (it->second.connected()) - { - it->second.disconnect(); - } - mAvatarNameCacheConnections.erase(it); - } - mAvatarNameCacheConnections[mMemberProgress->first] = LLAvatarNameCache::get(mMemberProgress->first, boost::bind(&LLPanelGroupMembersSubTab::onNameCache, this, gdatap->getMemberVersion(), mMemberProgress->second, _2, _1)); - } - } - - if (mMemberProgress == end) - { - if (mHasMatch) - { - mMembersList->setEnabled(true); - } - else if (gdatap->mMembers.size()) - { - mMembersList->setEnabled(false); - mMembersList->setCommentText(std::string("No match.")); - } - } - else - { - mPendingMemberUpdate = true; - } - - // This should clear the other two lists, since nothing is selected. - handleMemberSelect(); -} - -void LLPanelGroupMembersSubTab::onBanMember(void* user_data) -{ - LLPanelGroupMembersSubTab* self = static_cast(user_data); - self->confirmBanMembers(); -} - -void LLPanelGroupMembersSubTab::confirmBanMembers() -{ - std::vector selection = mMembersList->getAllSelected(); - if (selection.empty()) return; - - S32 selection_count = selection.size(); - if (selection_count == 1) - { - LLSD args; - LLAvatarName av_name; - LLAvatarNameCache::get(mMembersList->getValue(), &av_name); - args["AVATAR_NAME"] = av_name.getUserName(); - LLSD payload; - LLNotificationsUtil::add("BanGroupMemberWarning", - args, - payload, - boost::bind(&LLPanelGroupMembersSubTab::handleBanCallback, this, _1, _2)); - } - else - { - LLSD args; - args["COUNT"] = llformat("%d", selection_count); - LLSD payload; - LLNotificationsUtil::add("BanGroupMembersWarning", - args, - payload, - boost::bind(&LLPanelGroupMembersSubTab::handleBanCallback, this, _1, _2)); - } -} - -bool LLPanelGroupMembersSubTab::handleBanCallback(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (0 == option) // Eject button - { - handleBanMember(); - } - return false; -} - -void LLPanelGroupMembersSubTab::updateActionDescription() -{ - mActionDescription->setText(std::string()); - LLScrollListItem* action_item = mAllowedActionsList->getFirstSelected(); - if (!action_item || !mAllowedActionsList->getCanSelect()) - { - return; - } - - LLRoleAction* rap = (LLRoleAction*)action_item->getUserdata(); - if (rap) - { - std::string desc = rap->mLongDescription.empty() ? rap->mDescription : rap->mLongDescription; - mActionDescription->setText(desc); - } -} - -void LLPanelGroupMembersSubTab::handleBanMember() -{ - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - if(!gdatap) - { - LL_WARNS("Groups") << "Unable to get group data for group " << mGroupID << LL_ENDL; - return; - } - - std::vector selection = mMembersList->getAllSelected(); - if(selection.empty()) - { - return; - } - - uuid_vec_t ban_ids; - std::vector::iterator itor; - for(itor = selection.begin(); itor != selection.end(); ++itor) - { - LLUUID ban_id = (*itor)->getUUID(); - ban_ids.push_back(ban_id); - - LLGroupBanData ban_data; - gdatap->createBanEntry(ban_id, ban_data); - } - - LLGroupMgr::getInstance()->sendGroupBanRequest(LLGroupMgr::REQUEST_POST, mGroupID, LLGroupMgr::BAN_CREATE, ban_ids); - handleEjectMembers(); -} - - -// LLPanelGroupRolesSubTab /////////////////////////////////////////////// -static LLPanelInjector t_panel_group_roles_subtab("panel_group_roles_subtab"); - -LLPanelGroupRolesSubTab::LLPanelGroupRolesSubTab() - : LLPanelGroupSubTab(), - mRolesList(NULL), - mAssignedMembersList(NULL), - mAllowedActionsList(NULL), - mRoleName(NULL), - mRoleTitle(NULL), - mRoleDescription(NULL), - mMemberVisibleCheck(NULL), - mDeleteRoleButton(NULL), - mCopyRoleButton(NULL), - mCreateRoleButton(NULL), - mFirstOpen(true), - mHasRoleChange(false) -{ -} - -LLPanelGroupRolesSubTab::~LLPanelGroupRolesSubTab() -{ -} - -bool LLPanelGroupRolesSubTab::postBuildSubTab(LLView* root) -{ - LLPanelGroupSubTab::postBuildSubTab(root); - - // Upcast parent so we can ask it for sibling controls. - LLPanelGroupRoles* parent = (LLPanelGroupRoles*) root; - - // Look recursively from the parent to find all our widgets. - bool recurse = true; - mHeader = parent->findChild("roles_header", recurse); - mFooter = parent->findChild("roles_footer", recurse); - - - mRolesList = parent->getChild("role_list", recurse); - mAssignedMembersList = parent->getChild("role_assigned_members", recurse); - mAllowedActionsList = parent->getChild("role_allowed_actions", recurse); - mActionDescription = parent->getChild("role_action_description", recurse); - - mRoleName = parent->getChild("role_name", recurse); - mRoleTitle = parent->getChild("role_title", recurse); - mRoleDescription = parent->getChild("role_description", recurse); - - mMemberVisibleCheck = parent->getChild("role_visible_in_list", recurse); - - if (!mRolesList || !mAssignedMembersList || !mAllowedActionsList || !mActionDescription - || !mRoleName || !mRoleTitle || !mRoleDescription || !mMemberVisibleCheck) - { - LL_WARNS() << "ARG! element not found." << LL_ENDL; - return false; - } - - mRemoveEveryoneTxt = getString("cant_delete_role"); - - mCreateRoleButton = - parent->getChild("role_create", recurse); - if ( mCreateRoleButton ) - { - mCreateRoleButton->setClickedCallback(onCreateRole, this); - mCreateRoleButton->setEnabled(false); - } - - mCopyRoleButton = - parent->getChild("role_copy", recurse); - if ( mCopyRoleButton ) - { - mCopyRoleButton->setClickedCallback(onCopyRole, this); - mCopyRoleButton->setEnabled(false); - } - - mDeleteRoleButton = - parent->getChild("role_delete", recurse); - if ( mDeleteRoleButton ) - { - mDeleteRoleButton->setClickedCallback(onDeleteRole, this); - mDeleteRoleButton->setEnabled(false); - } - - mRolesList->setCommitOnSelectionChange(true); - mRolesList->setCommitCallback(onRoleSelect, this); - - mAssignedMembersList->setContextMenu(LLScrollListCtrl::MENU_AVATAR); - - mMemberVisibleCheck->setCommitCallback(onMemberVisibilityChange, this); - - mAllowedActionsList->setCommitOnSelectionChange(true); - mAllowedActionsList->setCommitCallback(boost::bind(&LLPanelGroupRolesSubTab::updateActionDescription, this)); - - mRoleName->setCommitOnFocusLost(true); - mRoleName->setKeystrokeCallback(onPropertiesKey, this); - - mRoleTitle->setCommitOnFocusLost(true); - mRoleTitle->setKeystrokeCallback(onPropertiesKey, this); - - mRoleDescription->setCommitOnFocusLost(true); - mRoleDescription->setKeystrokeCallback(boost::bind(&LLPanelGroupRolesSubTab::onDescriptionKeyStroke, this, _1)); - - setFooterEnabled(false); - - return true; -} - -void LLPanelGroupRolesSubTab::activate() -{ - LLPanelGroupSubTab::activate(); - - mActionDescription->clear(); - mRolesList->deselectAllItems(); - mAssignedMembersList->deleteAllItems(); - mAllowedActionsList->deleteAllItems(); - mRoleName->clear(); - mRoleDescription->clear(); - mRoleTitle->clear(); - - setFooterEnabled(false); - - mHasRoleChange = false; - update(GC_ALL); -} - -void LLPanelGroupRolesSubTab::deactivate() -{ - LL_DEBUGS() << "LLPanelGroupRolesSubTab::deactivate()" << LL_ENDL; - - LLPanelGroupSubTab::deactivate(); - mFirstOpen = false; -} - -bool LLPanelGroupRolesSubTab::needsApply(std::string& mesg) -{ - LL_DEBUGS() << "LLPanelGroupRolesSubTab::needsApply()" << LL_ENDL; - - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - if(!gdatap) - { - LL_WARNS() << "Unable to get group data for group " << mGroupID << LL_ENDL; - return false; - } - - - return (mHasRoleChange // Text changed in current role - || (gdatap && gdatap->pendingRoleChanges())); // Pending role changes in the group -} - -bool LLPanelGroupRolesSubTab::apply(std::string& mesg) -{ - LL_DEBUGS() << "LLPanelGroupRolesSubTab::apply()" << LL_ENDL; - - saveRoleChanges(true); - mFirstOpen = false; - LLGroupMgr::getInstance()->sendGroupRoleChanges(mGroupID); - - notifyObservers(); - - return true; -} - -void LLPanelGroupRolesSubTab::cancel() -{ - mHasRoleChange = false; - LLGroupMgr::getInstance()->cancelGroupRoleChanges(mGroupID); - - notifyObservers(); -} - -LLSD LLPanelGroupRolesSubTab::createRoleItem(const LLUUID& role_id, - std::string name, - std::string title, - S32 members) -{ - LLSD row; - row["id"] = role_id; - - row["columns"][0]["column"] = "name"; - row["columns"][0]["value"] = name; - - row["columns"][1]["column"] = "title"; - row["columns"][1]["value"] = title; - - row["columns"][2]["column"] = "members"; - row["columns"][2]["value"] = members; - - return row; -} - -bool LLPanelGroupRolesSubTab::matchesSearchFilter(std::string rolename, std::string roletitle) -{ - // If the search filter is empty, everything passes. - if (mSearchFilter.empty()) return true; - - LLStringUtil::toLower(rolename); - LLStringUtil::toLower(roletitle); - std::string::size_type match_name = rolename.find(mSearchFilter); - std::string::size_type match_title = roletitle.find(mSearchFilter); - - if ( (std::string::npos == match_name) - && (std::string::npos == match_title)) - { - // not found - return false; - } - else - { - return true; - } -} - -void LLPanelGroupRolesSubTab::update(LLGroupChange gc) -{ - LL_DEBUGS() << "LLPanelGroupRolesSubTab::update()" << LL_ENDL; - - if (mGroupID.isNull()) return; - - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - - if (!gdatap || !gdatap->isRoleDataComplete()) - { - LLGroupMgr::getInstance()->sendGroupRoleDataRequest(mGroupID); - } - else - { - bool had_selection = false; - LLUUID last_selected; - if (mRolesList->getFirstSelected()) - { - last_selected = mRolesList->getFirstSelected()->getUUID(); - had_selection = true; - } - mRolesList->deleteAllItems(); - - LLScrollListItem* item = NULL; - - LLGroupMgrGroupData::role_list_t::iterator rit = gdatap->mRoles.begin(); - LLGroupMgrGroupData::role_list_t::iterator end = gdatap->mRoles.end(); - - for ( ; rit != end; ++rit) - { - LLRoleData rd; - if (gdatap->getRoleData((*rit).first,rd)) - { - if (matchesSearchFilter(rd.mRoleName, rd.mRoleTitle)) - { - // If this is the everyone role, then EVERYONE is in it. - S32 members_in_role = (*rit).first.isNull() ? gdatap->mMembers.size() : (*rit).second->getTotalMembersInRole(); - LLSD row = createRoleItem((*rit).first,rd.mRoleName, rd.mRoleTitle, members_in_role); - item = mRolesList->addElement(row, ((*rit).first.isNull()) ? ADD_TOP : ADD_BOTTOM, this); - if (had_selection && ((*rit).first == last_selected)) - { - item->setSelected(true); - } - } - } - else - { - LL_WARNS() << "LLPanelGroupRolesSubTab::update() No role data for role " << (*rit).first << LL_ENDL; - } - } - - mRolesList->sortByColumn(std::string("name"), true); - - if ( (gdatap->mRoles.size() < (U32)MAX_ROLES) - && gAgent.hasPowerInGroup(mGroupID, GP_ROLE_CREATE) ) - { - mCreateRoleButton->setEnabled(true); - } - else - { - mCreateRoleButton->setEnabled(false); - } - - if (had_selection) - { - handleRoleSelect(); - } - else - { - mAssignedMembersList->deleteAllItems(); - mAllowedActionsList->deleteAllItems(); - mRoleName->clear(); - mRoleDescription->clear(); - mRoleTitle->clear(); - setFooterEnabled(false); - mDeleteRoleButton->setEnabled(false); - mCopyRoleButton->setEnabled(false); - } - } - - if ((GC_ROLE_MEMBER_DATA == gc || GC_MEMBER_DATA == gc) - && gdatap - && gdatap->isMemberDataComplete() - && gdatap->isRoleMemberDataComplete()) - { - buildMembersList(); - } -} - -// static -void LLPanelGroupRolesSubTab::onRoleSelect(LLUICtrl* ctrl, void* user_data) -{ - LLPanelGroupRolesSubTab* self = static_cast(user_data); - if (!self) - return; - - self->handleRoleSelect(); -} - -void LLPanelGroupRolesSubTab::handleRoleSelect() -{ - bool can_delete = true; - LL_DEBUGS() << "LLPanelGroupRolesSubTab::handleRoleSelect()" << LL_ENDL; - - mAssignedMembersList->deleteAllItems(); - mAllowedActionsList->deleteAllItems(); - - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - if (!gdatap) - { - LL_WARNS() << "LLPanelGroupRolesSubTab::handleRoleSelect() " - << "-- No group data!" << LL_ENDL; - return; - } - - saveRoleChanges(false); - - // Check if there is anything selected. - LLScrollListItem* item = mRolesList->getFirstSelected(); - if (!item) - { - setFooterEnabled(false); - return; - } - - setFooterEnabled(true); - - LLRoleData rd; - if (gdatap->getRoleData(item->getUUID(),rd)) - { - bool is_owner_role = ( gdatap->mOwnerRole == item->getUUID() ); - mRoleName->setText(rd.mRoleName); - mRoleTitle->setText(rd.mRoleTitle); - mRoleDescription->setText(rd.mRoleDescription); - - mAllowedActionsList->setEnabled(gAgent.hasPowerInGroup(mGroupID, - GP_ROLE_CHANGE_ACTIONS)); - buildActionsList(mAllowedActionsList, - rd.mRolePowers, - 0LL, - boost::bind(&LLPanelGroupRolesSubTab::handleActionCheck, this, _1, false), - true, - false, - is_owner_role); - - - mMemberVisibleCheck->set((rd.mRolePowers & GP_MEMBER_VISIBLE_IN_DIR) == GP_MEMBER_VISIBLE_IN_DIR); - mRoleName->setEnabled(!is_owner_role && - gAgent.hasPowerInGroup(mGroupID, GP_ROLE_PROPERTIES)); - mRoleTitle->setEnabled(gAgent.hasPowerInGroup(mGroupID, GP_ROLE_PROPERTIES)); - mRoleDescription->setEnabled(gAgent.hasPowerInGroup(mGroupID, GP_ROLE_PROPERTIES)); - - if ( is_owner_role ) - { - // you can't delete the owner role - can_delete = false; - // ... or hide members with this role - mMemberVisibleCheck->setEnabled(false); - } - else - { - mMemberVisibleCheck->setEnabled(gAgent.hasPowerInGroup(mGroupID, GP_ROLE_PROPERTIES)); - } - - if (item->getUUID().isNull()) - { - // Everyone role, can't edit description or name or delete - mRoleDescription->setEnabled(false); - mRoleName->setEnabled(false); - can_delete = false; - } - } - else - { - mRolesList->deselectAllItems(); - mAssignedMembersList->deleteAllItems(); - mAllowedActionsList->deleteAllItems(); - mRoleName->clear(); - mRoleDescription->clear(); - mRoleTitle->clear(); - setFooterEnabled(false); - - can_delete = false; - } - mSelectedRole = item->getUUID(); - buildMembersList(); - - mCopyRoleButton->setEnabled(gAgent.hasPowerInGroup(mGroupID, GP_ROLE_CREATE)); - can_delete = can_delete && gAgent.hasPowerInGroup(mGroupID, - GP_ROLE_DELETE); - mDeleteRoleButton->setEnabled(can_delete); -} - -void LLPanelGroupRolesSubTab::buildMembersList() -{ - mAssignedMembersList->deleteAllItems(); - - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - if (!gdatap) - { - LL_WARNS() << "LLPanelGroupRolesSubTab::handleRoleSelect() " - << "-- No group data!" << LL_ENDL; - return; - } - - // Check if there is anything selected. - LLScrollListItem* item = mRolesList->getFirstSelected(); - if (!item) return; - - if (item->getUUID().isNull()) - { - // Special cased 'Everyone' role - LLGroupMgrGroupData::member_list_t::iterator mit = gdatap->mMembers.begin(); - LLGroupMgrGroupData::member_list_t::iterator end = gdatap->mMembers.end(); - for ( ; mit != end; ++mit) - { - mAssignedMembersList->addNameItem((*mit).first); - } - } - else - { - LLGroupMgrGroupData::role_list_t::iterator rit = gdatap->mRoles.find(item->getUUID()); - if (rit != gdatap->mRoles.end()) - { - LLGroupRoleData* rdatap = (*rit).second; - if (rdatap) - { - uuid_vec_t::const_iterator mit = rdatap->getMembersBegin(); - uuid_vec_t::const_iterator end = rdatap->getMembersEnd(); - for ( ; mit != end; ++mit) - { - mAssignedMembersList->addNameItem((*mit)); - } - } - } - } -} - -struct ActionCBData -{ - LLPanelGroupRolesSubTab* mSelf; - LLCheckBoxCtrl* mCheck; -}; - -void LLPanelGroupRolesSubTab::handleActionCheck(LLUICtrl* ctrl, bool force) -{ - LLCheckBoxCtrl* check = dynamic_cast(ctrl); - if (!check) - return; - - LL_DEBUGS() << "LLPanelGroupRolesSubTab::handleActionSelect()" << LL_ENDL; - - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - if (!gdatap) - { - LL_WARNS() << "LLPanelGroupRolesSubTab::handleRoleSelect() " - << "-- No group data!" << LL_ENDL; - return; - } - - LLScrollListItem* action_item = mAllowedActionsList->getFirstSelected(); - if (!action_item) - { - return; - } - - LLScrollListItem* role_item = mRolesList->getFirstSelected(); - if (!role_item) - { - return; - } - LLUUID role_id = role_item->getUUID(); - - LLRoleAction* rap = (LLRoleAction*)action_item->getUserdata(); - U64 power = rap->mPowerBit; - - bool isEnablingAbility = check->get(); - LLRoleData rd; - LLSD args; - - if (isEnablingAbility && - !force && - ((GP_ROLE_ASSIGN_MEMBER == power) || (GP_ROLE_CHANGE_ACTIONS == power) )) - { - // Uncheck the item, for now. It will be - // checked if they click 'Yes', below. - check->set(false); - - LLRoleData rd; - LLSD args; - - if ( gdatap->getRoleData(role_id, rd) ) - { - args["ACTION_NAME"] = rap->mDescription; - args["ROLE_NAME"] = rd.mRoleName; - mHasModal = true; - std::string warning = "AssignDangerousActionWarning"; - if (GP_ROLE_CHANGE_ACTIONS == power) - { - warning = "AssignDangerousAbilityWarning"; - } - LLNotificationsUtil::add(warning, args, LLSD(), boost::bind(&LLPanelGroupRolesSubTab::addActionCB, this, _1, _2, check)); - } - else - { - LL_WARNS() << "Unable to look up role information for role id: " - << role_id << LL_ENDL; - } - } - - if(GP_GROUP_BAN_ACCESS == power) - { - std::string warning = isEnablingAbility ? "AssignBanAbilityWarning" : "RemoveBanAbilityWarning"; - - ////////////////////////////////////////////////////////////////////////// - // Get role data for both GP_ROLE_REMOVE_MEMBER and GP_MEMBER_EJECT - // Add description and role name to LLSD - // Pop up dialog saying "Yo, you also granted these other abilities when you did this!" - if ( gdatap->getRoleData(role_id, rd) ) - { - args["ACTION_NAME"] = rap->mDescription; - args["ROLE_NAME"] = rd.mRoleName; - mHasModal = true; - - std::vector all_data = mAllowedActionsList->getAllData(); - std::vector::iterator ad_it = all_data.begin(); - std::vector::iterator ad_end = all_data.end(); - LLRoleAction* adp; - for( ; ad_it != ad_end; ++ad_it) - { - adp = (LLRoleAction*)(*ad_it)->getUserdata(); - if(adp->mPowerBit == GP_MEMBER_EJECT) - { - args["ACTION_NAME_2"] = adp->mDescription; - } - else if(adp->mPowerBit == GP_ROLE_REMOVE_MEMBER) - { - args["ACTION_NAME_3"] = adp->mDescription; - } - } - - LLNotificationsUtil::add(warning, args); - } - else - { - LL_WARNS() << "Unable to look up role information for role id: " - << role_id << LL_ENDL; - } - - ////////////////////////////////////////////////////////////////////////// - - U64 current_role_powers = gdatap->getRolePowers(role_id); - - if(isEnablingAbility) - { - power |= (GP_ROLE_REMOVE_MEMBER | GP_MEMBER_EJECT); - current_role_powers |= power; - } - else - { - current_role_powers &= ~GP_GROUP_BAN_ACCESS; - } - - mAllowedActionsList->deleteAllItems(); - buildActionsList( mAllowedActionsList, - current_role_powers, - current_role_powers, - boost::bind(&LLPanelGroupRolesSubTab::handleActionCheck, this, _1, false), - true, - false, - false); - - } - - ////////////////////////////////////////////////////////////////////////// - // Adding non-specific ability to role - ////////////////////////////////////////////////////////////////////////// - if(isEnablingAbility) - { - gdatap->addRolePower(role_id, power); - } - else - { - gdatap->removeRolePower(role_id,power); - } - - mHasRoleChange = true; - notifyObservers(); - -} - -bool LLPanelGroupRolesSubTab::addActionCB(const LLSD& notification, const LLSD& response, LLCheckBoxCtrl* check) -{ - if (!check) return false; - - mHasModal = false; - - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (0 == option) - { - // User clicked "Yes" - check->set(true); - const bool force_add = true; - handleActionCheck(check, force_add); - } - return false; -} - -// static -void LLPanelGroupRolesSubTab::onPropertiesKey(LLLineEditor* ctrl, void* user_data) -{ - LLPanelGroupRolesSubTab* self = static_cast(user_data); - if (!self) return; - - self->mHasRoleChange = true; - self->notifyObservers(); -} - -void LLPanelGroupRolesSubTab::onDescriptionKeyStroke(LLTextEditor* caller) -{ - mHasRoleChange = true; - notifyObservers(); -} - -// static -void LLPanelGroupRolesSubTab::onDescriptionCommit(LLUICtrl* ctrl, void* user_data) -{ - LLPanelGroupRolesSubTab* self = static_cast(user_data); - if (!self) return; - - self->mHasRoleChange = true; - self->notifyObservers(); -} - -// static -void LLPanelGroupRolesSubTab::onMemberVisibilityChange(LLUICtrl* ctrl, void* user_data) -{ - LLPanelGroupRolesSubTab* self = static_cast(user_data); - LLCheckBoxCtrl* check = static_cast(ctrl); - if (!check || !self) return; - - self->handleMemberVisibilityChange(check->get()); -} - -void LLPanelGroupRolesSubTab::handleMemberVisibilityChange(bool value) -{ - LL_DEBUGS() << "LLPanelGroupRolesSubTab::handleMemberVisibilityChange()" << LL_ENDL; - - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - if (!gdatap) - { - LL_WARNS() << "LLPanelGroupRolesSubTab::handleRoleSelect() " - << "-- No group data!" << LL_ENDL; - return; - } - - LLScrollListItem* role_item = mRolesList->getFirstSelected(); - if (!role_item) - { - return; - } - - if (value) - { - gdatap->addRolePower(role_item->getUUID(),GP_MEMBER_VISIBLE_IN_DIR); - } - else - { - gdatap->removeRolePower(role_item->getUUID(),GP_MEMBER_VISIBLE_IN_DIR); - } -} - -// static -void LLPanelGroupRolesSubTab::onCreateRole(void* user_data) -{ - LLPanelGroupRolesSubTab* self = static_cast(user_data); - if (!self) return; - - self->handleCreateRole(); -} - -void LLPanelGroupRolesSubTab::handleCreateRole() -{ - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - - if (!gdatap) return; - - LLUUID new_role_id; - new_role_id.generate(); - - LLRoleData rd; - rd.mRoleName = "New Role"; - gdatap->createRole(new_role_id,rd); - - mRolesList->deselectAllItems(true); - LLSD row; - row["id"] = new_role_id; - row["columns"][0]["column"] = "name"; - row["columns"][0]["value"] = rd.mRoleName; - mRolesList->addElement(row, ADD_BOTTOM, this); - mRolesList->selectByID(new_role_id); - - // put focus on name field and select its contents - if(mRoleName) - { - mRoleName->setFocus(true); - mRoleName->onTabInto(); - gFocusMgr.triggerFocusFlash(); - } - - notifyObservers(); -} - -// static -void LLPanelGroupRolesSubTab::onCopyRole(void* user_data) -{ - LLPanelGroupRolesSubTab* self = static_cast(user_data); - if (!self) return; - - self->handleCopyRole(); -} - -void LLPanelGroupRolesSubTab::handleCopyRole() -{ - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - - if (!gdatap) return; - - LLScrollListItem* role_item = mRolesList->getFirstSelected(); - if (!role_item || role_item->getUUID().isNull()) - { - return; - } - - LLRoleData rd; - if (!gdatap->getRoleData(role_item->getUUID(), rd)) - { - return; - } - - LLUUID new_role_id; - new_role_id.generate(); - rd.mRoleName += "(Copy)"; - gdatap->createRole(new_role_id,rd); - - mRolesList->deselectAllItems(true); - LLSD row; - row["id"] = new_role_id; - row["columns"][0]["column"] = "name"; - row["columns"][0]["value"] = rd.mRoleName; - mRolesList->addElement(row, ADD_BOTTOM, this); - mRolesList->selectByID(new_role_id); - - // put focus on name field and select its contents - if(mRoleName) - { - mRoleName->setFocus(true); - mRoleName->onTabInto(); - gFocusMgr.triggerFocusFlash(); - } - - notifyObservers(); -} - -// static -void LLPanelGroupRolesSubTab::onDeleteRole(void* user_data) -{ - LLPanelGroupRolesSubTab* self = static_cast(user_data); - if (!self) return; - - self->handleDeleteRole(); -} - -void LLPanelGroupRolesSubTab::handleDeleteRole() -{ - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - - if (!gdatap) return; - - LLScrollListItem* role_item = mRolesList->getFirstSelected(); - if (!role_item) - { - return; - } - - if (role_item->getUUID().isNull() || role_item->getUUID() == gdatap->mOwnerRole) - { - LLSD args; - args["MESSAGE"] = mRemoveEveryoneTxt; - LLNotificationsUtil::add("GenericAlert", args); - return; - } - - gdatap->deleteRole(role_item->getUUID()); - mRolesList->deleteSingleItem(mRolesList->getFirstSelectedIndex()); - mRolesList->selectFirstItem(); - - notifyObservers(); -} - -void LLPanelGroupRolesSubTab::saveRoleChanges(bool select_saved_role) -{ - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - - if (!gdatap) return; - - if (mHasRoleChange) - { - LLRoleData rd; - if (!gdatap->getRoleData(mSelectedRole,rd)) return; - - rd.mRoleName = mRoleName->getText(); - rd.mRoleDescription = mRoleDescription->getText(); - rd.mRoleTitle = mRoleTitle->getText(); - - S32 role_members_count = 0; - if (mSelectedRole.isNull()) - { - role_members_count = gdatap->mMemberCount; - } - else if(LLGroupRoleData* grd = get_ptr_in_map(gdatap->mRoles, mSelectedRole)) - { - role_members_count = grd->getTotalMembersInRole(); - } - - gdatap->setRoleData(mSelectedRole,rd); - - mRolesList->deleteSingleItem(mRolesList->getItemIndex(mSelectedRole)); - - LLSD row = createRoleItem(mSelectedRole,rd.mRoleName,rd.mRoleTitle,role_members_count); - LLScrollListItem* item = mRolesList->addElement(row, ADD_BOTTOM, this); - item->setSelected(select_saved_role); - - mHasRoleChange = false; - } -} - -void LLPanelGroupRolesSubTab::updateActionDescription() -{ - mActionDescription->setText(std::string()); - LLScrollListItem* action_item = mAllowedActionsList->getFirstSelected(); - if (!action_item || !mAllowedActionsList->getCanSelect()) - { - return; - } - - LLRoleAction* rap = (LLRoleAction*)action_item->getUserdata(); - if (rap) - { - std::string desc = rap->mLongDescription.empty() ? rap->mDescription : rap->mLongDescription; - mActionDescription->setText(desc); - } -} - -void LLPanelGroupRolesSubTab::setGroupID(const LLUUID& id) -{ - if(mRolesList) mRolesList->deleteAllItems(); - if(mAssignedMembersList) mAssignedMembersList->deleteAllItems(); - if(mAllowedActionsList) mAllowedActionsList->deleteAllItems(); - - if(mRoleName) mRoleName->clear(); - if(mRoleDescription) mRoleDescription->clear(); - if(mRoleTitle) mRoleTitle->clear(); - - mHasRoleChange = false; - - setFooterEnabled(false); - - LLPanelGroupSubTab::setGroupID(id); -} - - -// LLPanelGroupActionsSubTab ///////////////////////////////////////////// -static LLPanelInjector t_panel_group_actions_subtab("panel_group_actions_subtab"); - -LLPanelGroupActionsSubTab::LLPanelGroupActionsSubTab() -: LLPanelGroupSubTab() -{ -} - -LLPanelGroupActionsSubTab::~LLPanelGroupActionsSubTab() -{ -} - -bool LLPanelGroupActionsSubTab::postBuildSubTab(LLView* root) -{ - LLPanelGroupSubTab::postBuildSubTab(root); - - // Upcast parent so we can ask it for sibling controls. - LLPanelGroupRoles* parent = (LLPanelGroupRoles*) root; - - // Look recursively from the parent to find all our widgets. - bool recurse = true; - mHeader = parent->findChild("actions_header", recurse); - mFooter = parent->findChild("actions_footer", recurse); - - mActionDescription = parent->getChild("action_description", recurse); - - mActionList = parent->getChild("action_list",recurse); - mActionRoles = parent->getChild("action_roles",recurse); - mActionMembers = parent->getChild("action_members",recurse); - - if (!mActionList || !mActionDescription || !mActionRoles || !mActionMembers) return false; - - mActionList->setCommitOnSelectionChange(true); - mActionList->setCommitCallback(boost::bind(&LLPanelGroupActionsSubTab::handleActionSelect, this)); - mActionList->setContextMenu(LLScrollListCtrl::MENU_AVATAR); - - update(GC_ALL); - - return true; -} - -void LLPanelGroupActionsSubTab::activate() -{ - LLPanelGroupSubTab::activate(); - - update(GC_ALL); - mActionDescription->clear(); - mActionList->deselectAllItems(); - mActionList->deleteAllItems(); - buildActionsList(mActionList, - GP_ALL_POWERS, - GP_ALL_POWERS, - NULL, - false, - true, - false); -} - -void LLPanelGroupActionsSubTab::deactivate() -{ - LL_DEBUGS() << "LLPanelGroupActionsSubTab::deactivate()" << LL_ENDL; - - LLPanelGroupSubTab::deactivate(); -} - -bool LLPanelGroupActionsSubTab::needsApply(std::string& mesg) -{ - LL_DEBUGS() << "LLPanelGroupActionsSubTab::needsApply()" << LL_ENDL; - - return false; -} - -bool LLPanelGroupActionsSubTab::apply(std::string& mesg) -{ - LL_DEBUGS() << "LLPanelGroupActionsSubTab::apply()" << LL_ENDL; - return true; -} - -void LLPanelGroupActionsSubTab::update(LLGroupChange gc) -{ - LL_DEBUGS() << "LLPanelGroupActionsSubTab::update()" << LL_ENDL; - - if (mGroupID.isNull()) return; - - mActionMembers->deleteAllItems(); - mActionRoles->deleteAllItems(); - - if(mActionList->hasSelectedItem()) - { - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - if (gdatap && gdatap->isMemberDataComplete() && gdatap->isRoleDataComplete()) - { - handleActionSelect(); - } - } -} - -void LLPanelGroupActionsSubTab::onFilterChanged() -{ - mActionDescription->clear(); - mActionList->deselectAllItems(); - mActionList->deleteAllItems(); - buildActionsList(mActionList, - GP_ALL_POWERS, - GP_ALL_POWERS, - NULL, - false, - true, - false); -} - -void LLPanelGroupActionsSubTab::handleActionSelect() -{ - mActionMembers->deleteAllItems(); - mActionRoles->deleteAllItems(); - - U64 power_mask = GP_NO_POWERS; - std::vector selection = - mActionList->getAllSelected(); - if (selection.empty()) return; - - LLRoleAction* rap; - - std::vector::iterator itor; - for (itor = selection.begin() ; - itor != selection.end(); ++itor) - { - rap = (LLRoleAction*)( (*itor)->getUserdata() ); - power_mask |= rap->mPowerBit; - } - - if (selection.size() == 1) - { - LLScrollListItem* item = selection[0]; - rap = (LLRoleAction*)(item->getUserdata()); - - if (rap->mLongDescription.empty()) - { - mActionDescription->setText(rap->mDescription); - } - else - { - mActionDescription->setText(rap->mLongDescription); - } - } - else - { - mActionDescription->clear(); - } - - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - - if (!gdatap) return; - - if (gdatap->isMemberDataComplete()) - { - LLGroupMgrGroupData::member_list_t::iterator it = gdatap->mMembers.begin(); - LLGroupMgrGroupData::member_list_t::iterator end = gdatap->mMembers.end(); - LLGroupMemberData* gmd; - - for ( ; it != end; ++it) - { - gmd = (*it).second; - if (!gmd) continue; - if ((gmd->getAgentPowers() & power_mask) == power_mask) - { - mActionMembers->addNameItem(gmd->getID()); - } - } - } - else - { - LLGroupMgr::getInstance()->sendCapGroupMembersRequest(mGroupID); - } - - if (gdatap->isRoleDataComplete()) - { - LLGroupMgrGroupData::role_list_t::iterator it = gdatap->mRoles.begin(); - LLGroupMgrGroupData::role_list_t::iterator end = gdatap->mRoles.end(); - LLGroupRoleData* rmd; - - for ( ; it != end; ++it) - { - rmd = (*it).second; - if (!rmd) continue; - if ((rmd->getRoleData().mRolePowers & power_mask) == power_mask) - { - mActionRoles->addSimpleElement(rmd->getRoleData().mRoleName); - } - } - } - else - { - LLGroupMgr::getInstance()->sendGroupRoleDataRequest(mGroupID); - } -} - -void LLPanelGroupActionsSubTab::setGroupID(const LLUUID& id) -{ - if(mActionList) mActionList->deleteAllItems(); - if(mActionRoles) mActionRoles->deleteAllItems(); - if(mActionMembers) mActionMembers->deleteAllItems(); - - if(mActionDescription) mActionDescription->clear(); - - LLPanelGroupSubTab::setGroupID(id); -} - - -// LLPanelGroupBanListSubTab ///////////////////////////////////////////// -static LLPanelInjector t_panel_group_ban_subtab("panel_group_banlist_subtab"); - -LLPanelGroupBanListSubTab::LLPanelGroupBanListSubTab() - : LLPanelGroupSubTab(), - mBanList(NULL), - mCreateBanButton(NULL), - mDeleteBanButton(NULL) -{} - -bool LLPanelGroupBanListSubTab::postBuildSubTab(LLView* root) -{ - LLPanelGroupSubTab::postBuildSubTab(root); - - // Upcast parent so we can ask it for sibling controls. - LLPanelGroupRoles* parent = (LLPanelGroupRoles*)root; - - // Look recursively from the parent to find all our widgets. - bool recurse = true; - - mHeader = parent->findChild("banlist_header", recurse); - mFooter = parent->findChild("banlist_footer", recurse); - - mBanList = parent->getChild("ban_list", recurse); - - mCreateBanButton = parent->getChild("ban_create", recurse); - mDeleteBanButton = parent->getChild("ban_delete", recurse); - mRefreshBanListButton = parent->getChild("ban_refresh", recurse); - mBanCountText = parent->getChild("ban_count", recurse); - - if(!mBanList || !mCreateBanButton || !mDeleteBanButton || !mRefreshBanListButton || !mBanCountText) - return false; - - mBanList->setCommitOnSelectionChange(true); - mBanList->setCommitCallback(onBanEntrySelect, this); - - mCreateBanButton->setClickedCallback(onCreateBanEntry, this); - mCreateBanButton->setEnabled(false); - - mDeleteBanButton->setClickedCallback(onDeleteBanEntry, this); - mDeleteBanButton->setEnabled(false); - - mRefreshBanListButton->setClickedCallback(onRefreshBanList, this); - mRefreshBanListButton->setEnabled(false); - - setBanCount(0); - - mBanList->setOnNameListCompleteCallback(boost::bind(&LLPanelGroupBanListSubTab::onBanListCompleted, this, _1)); - - populateBanList(); - - setFooterEnabled(false); - return true; -} - -void LLPanelGroupBanListSubTab::activate() -{ - LLPanelGroupSubTab::activate(); - - mBanList->deselectAllItems(); - mDeleteBanButton->setEnabled(false); - - LLGroupMgrGroupData * group_datap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - if (group_datap) - { - mCreateBanButton->setEnabled(gAgent.hasPowerInGroup(mGroupID, GP_GROUP_BAN_ACCESS) && - group_datap->mBanList.size() < GB_MAX_BANNED_AGENTS); - setBanCount(group_datap->mBanList.size()); - } - else - { - mCreateBanButton->setEnabled(false); - setBanCount(0); - } - - // BAKER: Should I really request everytime activate() is called? - // Perhaps I should only do it on a force refresh, or if an action on the list happens... - // Because it's not going to live-update the list anyway... You'd have to refresh if you - // wanted to see someone else's additions anyway... - // - LLGroupMgr::getInstance()->sendGroupBanRequest(LLGroupMgr::REQUEST_GET, mGroupID); - - setFooterEnabled(false); - update(GC_ALL); -} - -void LLPanelGroupBanListSubTab::update(LLGroupChange gc) -{ - populateBanList(); -} - -void LLPanelGroupBanListSubTab::draw() -{ - LLPanelGroupSubTab::draw(); - - // BAKER: Might be good to put it here instead of update, maybe.. See how often draw gets hit. - //if( - // populateBanList(); -} - -void LLPanelGroupBanListSubTab::onBanEntrySelect(LLUICtrl* ctrl, void* user_data) -{ - LLPanelGroupBanListSubTab* self = static_cast(user_data); - if (!self) - return; - - self->handleBanEntrySelect(); -} - -void LLPanelGroupBanListSubTab::handleBanEntrySelect() -{ - if (gAgent.hasPowerInGroup(mGroupID, GP_GROUP_BAN_ACCESS)) - { - mDeleteBanButton->setEnabled(true); - } -} - -void LLPanelGroupBanListSubTab::onCreateBanEntry(void* user_data) -{ - LLPanelGroupBanListSubTab* self = static_cast(user_data); - if (!self) - return; - - self->handleCreateBanEntry(); -} - -void LLPanelGroupBanListSubTab::handleCreateBanEntry() -{ - LLFloaterGroupBulkBan::showForGroup(mGroupID); - //populateBanList(); -} - -void LLPanelGroupBanListSubTab::onDeleteBanEntry(void* user_data) -{ - LLPanelGroupBanListSubTab* self = static_cast(user_data); - if (!self) - return; - - self->handleDeleteBanEntry(); -} - -void LLPanelGroupBanListSubTab::handleDeleteBanEntry() -{ - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - if(!gdatap) - { - LL_WARNS("Groups") << "Unable to get group data for group " << mGroupID << LL_ENDL; - return; - } - - std::vector selection = mBanList->getAllSelected(); - if(selection.empty()) - { - return; - } - - bool can_ban_members = false; - if (gAgent.isGodlike() || - gAgent.hasPowerInGroup(mGroupID, GP_GROUP_BAN_ACCESS)) - { - can_ban_members = true; - } - - // Owners can ban anyone in the group. - LLGroupMgrGroupData::member_list_t::iterator mi = gdatap->mMembers.find(gAgent.getID()); - if (mi != gdatap->mMembers.end()) - { - LLGroupMemberData* member_data = (*mi).second; - if ( member_data && member_data->isInRole(gdatap->mOwnerRole) ) - { - can_ban_members = true; - } - } - - if(!can_ban_members) - return; - - std::vector ban_ids; - std::vector::iterator itor; - for(itor = selection.begin(); itor != selection.end(); ++itor) - { - LLUUID ban_id = (*itor)->getUUID(); - ban_ids.push_back(ban_id); - - gdatap->removeBanEntry(ban_id); - mBanList->removeNameItem(ban_id); - - // Removing an item removes the selection, we shouldn't be able to click - // the button anymore until we reselect another entry. - mDeleteBanButton->setEnabled(false); - } - - // update ban-count related elements - mCreateBanButton->setEnabled(true); - setBanCount(gdatap->mBanList.size()); - - LLGroupMgr::getInstance()->sendGroupBanRequest(LLGroupMgr::REQUEST_POST, mGroupID, LLGroupMgr::BAN_DELETE, ban_ids); -} - -void LLPanelGroupBanListSubTab::onRefreshBanList(void* user_data) -{ - LLPanelGroupBanListSubTab* self = static_cast(user_data); - if (!self) - return; - - self->handleRefreshBanList(); -} - -void LLPanelGroupBanListSubTab::handleRefreshBanList() -{ - mRefreshBanListButton->setEnabled(false); - LLGroupMgr::getInstance()->sendGroupBanRequest(LLGroupMgr::REQUEST_GET, mGroupID); -} - -void LLPanelGroupBanListSubTab::onBanListCompleted(bool isComplete) -{ - if(isComplete) - { - mRefreshBanListButton->setEnabled(true); - populateBanList(); - } -} - -void LLPanelGroupBanListSubTab::setBanCount(U32 ban_count) -{ - LLStringUtil::format_map_t args; - args["[COUNT]"] = llformat("%d", ban_count); - args["[LIMIT]"] = llformat("%d", GB_MAX_BANNED_AGENTS); - mBanCountText->setText(getString("ban_count_template", args)); -} - -void LLPanelGroupBanListSubTab::populateBanList() -{ - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); - if(!gdatap) - { - LL_WARNS("Groups") << "Unable to get group data for group " << mGroupID << LL_ENDL; - return; - } - - mBanList->deleteAllItems(); - std::map::const_iterator entry = gdatap->mBanList.begin(); - for(; entry != gdatap->mBanList.end(); entry++) - { - LLNameListCtrl::NameItem ban_entry; - ban_entry.value = entry->first; - LLGroupBanData bd = entry->second; - - ban_entry.columns.add().column("name").font.name("SANSSERIF_SMALL").style("NORMAL"); - - // Baker TODO: MAINT- - // Check out utc_to_pacific_time() - - std::string ban_date_str = bd.mBanDate.toHTTPDateString("%Y/%m/%d"); -// time_t utc_time; -// utc_time = time_corrected(); -// LLSD substitution; -// substitution["datetime"] = (S32) utc_time; -// LLStringUtil::format (ban_date_str, substitution); - - //LL_INFOS("BAKER") << "[BAKER] BAN_DATE: " << bd.mBanDate.toHTTPDateString("%Y/%m/%d") << LL_ENDL; - //LL_INFOS("BAKER") << "[BAKER] BAN_DATE_MODIFIED: " << ban_date_str << LL_ENDL; - - //ban_entry.columns.add().column("ban_date").value(ban_date_str.font.name("SANSSERIF_SMALL").style("NORMAL"); - ban_entry.columns.add().column("ban_date").value(bd.mBanDate.toHTTPDateString("%Y/%m/%d")).font.name("SANSSERIF_SMALL").style("NORMAL"); - - mBanList->addNameItemRow(ban_entry); - } - - mRefreshBanListButton->setEnabled(true); - mCreateBanButton->setEnabled(gAgent.hasPowerInGroup(mGroupID, GP_GROUP_BAN_ACCESS) && - gdatap->mBanList.size() < GB_MAX_BANNED_AGENTS); - setBanCount(gdatap->mBanList.size()); -} - -void LLPanelGroupBanListSubTab::setGroupID(const LLUUID& id) -{ - if(mBanList) - mBanList->deleteAllItems(); - - setFooterEnabled(false); - LLPanelGroupSubTab::setGroupID(id); -} +/** + * @file llpanelgrouproles.cpp + * @brief Panel for roles information about a particular group. + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llcheckboxctrl.h" + +#include "llagent.h" +#include "llavatarnamecache.h" +#include "llbutton.h" +#include "llfiltereditor.h" +#include "llfloatergroupbulkban.h" +#include "llfloatergroupinvite.h" +#include "llavataractions.h" +#include "lliconctrl.h" +#include "lllineeditor.h" +#include "llnamelistctrl.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "llpanelgrouproles.h" +#include "llscrolllistctrl.h" +#include "llscrolllistitem.h" +#include "llscrolllistcell.h" +#include "llslurl.h" +#include "lltabcontainer.h" +#include "lltextbox.h" +#include "lltexteditor.h" +#include "lltrans.h" +#include "llviewertexturelist.h" +#include "llviewerwindow.h" +#include "llfocusmgr.h" +#include "llviewercontrol.h" + +#include "roles_constants.h" + +static LLPanelInjector t_panel_group_roles("panel_group_roles"); + +bool agentCanRemoveFromRole(const LLUUID& group_id, + const LLUUID& role_id) +{ + return gAgent.hasPowerInGroup(group_id, GP_ROLE_REMOVE_MEMBER); +} + +bool agentCanAddToRole(const LLUUID& group_id, + const LLUUID& role_id) +{ + if (gAgent.isGodlike()) + return true; + + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_id); + if (!gdatap) + { + LL_WARNS() << "agentCanAddToRole " + << "-- No group data!" << LL_ENDL; + return false; + } + + //make sure the agent is in the group + LLGroupMgrGroupData::member_list_t::iterator mi = gdatap->mMembers.find(gAgent.getID()); + if (mi == gdatap->mMembers.end()) + { + return false; + } + + LLGroupMemberData* member_data = (*mi).second; + + // Owners can add to any role. + if ( member_data->isInRole(gdatap->mOwnerRole) ) + { + return true; + } + + // 'Limited assign members' can add to roles the user is in. + if ( gAgent.hasPowerInGroup(group_id, GP_ROLE_ASSIGN_MEMBER_LIMITED) && + member_data->isInRole(role_id) ) + { + return true; + } + + // 'assign members' can add to non-owner roles. + if ( gAgent.hasPowerInGroup(group_id, GP_ROLE_ASSIGN_MEMBER) && + role_id != gdatap->mOwnerRole ) + { + return true; + } + + return false; +} + + +// LLPanelGroupRoles ///////////////////////////////////////////////////// + +// static +LLPanelGroupRoles::LLPanelGroupRoles() +: LLPanelGroupTab(), + mCurrentTab(NULL), + mRequestedTab( NULL ), + mSubTabContainer( NULL ), + mFirstUse( true ) +{ +} + +LLPanelGroupRoles::~LLPanelGroupRoles() +{ +} + +bool LLPanelGroupRoles::postBuild() +{ + LL_DEBUGS() << "LLPanelGroupRoles::postBuild()" << LL_ENDL; + + mSubTabContainer = getChild("roles_tab_container"); + + if (!mSubTabContainer) return false; + + // Hook up each sub-tabs callback and widgets. + for (S32 i = 0; i < mSubTabContainer->getTabCount(); ++i) + { + LLPanel* panel = mSubTabContainer->getPanelByIndex(i); + LLPanelGroupSubTab* subtabp = dynamic_cast(panel); + if (!subtabp) + { + LL_WARNS() << "Invalid subtab panel: " << panel->getName() << LL_ENDL; + return false; + } + + // Hand the subtab a pointer to this LLPanelGroupRoles, so that it can + // look around for the widgets it is interested in. + if (!subtabp->postBuildSubTab(this)) + return false; + + //subtabp->addObserver(this); + } + // Add click callbacks to tab switching. + mSubTabContainer->setValidateBeforeCommit(boost::bind(&LLPanelGroupRoles::handleSubTabSwitch, this, _1)); + + // Set the current tab to whatever is currently being shown. + mCurrentTab = (LLPanelGroupTab*) mSubTabContainer->getCurrentPanel(); + if (!mCurrentTab) + { + // Need to select a tab. + mSubTabContainer->selectFirstTab(); + mCurrentTab = (LLPanelGroupTab*) mSubTabContainer->getCurrentPanel(); + } + + if (!mCurrentTab) return false; + + // Act as though this tab was just activated. + mCurrentTab->activate(); + + // Read apply text from the xml file. + mDefaultNeedsApplyMesg = getString("default_needs_apply_text"); + mWantApplyMesg = getString("want_apply_text"); + + return LLPanelGroupTab::postBuild(); +} + +bool LLPanelGroupRoles::isVisibleByAgent(LLAgent* agentp) +{ + /* This power was removed to make group roles simpler + return agentp->hasPowerInGroup(mGroupID, + GP_ROLE_CREATE | + GP_ROLE_DELETE | + GP_ROLE_PROPERTIES | + GP_ROLE_VIEW | + GP_ROLE_ASSIGN_MEMBER | + GP_ROLE_REMOVE_MEMBER | + GP_ROLE_CHANGE_ACTIONS | + GP_MEMBER_INVITE | + GP_MEMBER_EJECT | + GP_MEMBER_OPTIONS ); + */ + return mAllowEdit && agentp->isInGroup(mGroupID); + +} + +bool LLPanelGroupRoles::handleSubTabSwitch(const LLSD& data) +{ + std::string panel_name = data.asString(); + + if(mRequestedTab != NULL)//we already have tab change request + { + return false; + } + + mRequestedTab = static_cast(mSubTabContainer->getPanelByName(panel_name)); + + std::string mesg; + if (mCurrentTab && mCurrentTab->needsApply(mesg)) + { + // If no message was provided, give a generic one. + if (mesg.empty()) + { + mesg = mDefaultNeedsApplyMesg; + } + // Create a notify box, telling the user about the unapplied tab. + LLSD args; + args["NEEDS_APPLY_MESSAGE"] = mesg; + args["WANT_APPLY_MESSAGE"] = mWantApplyMesg; + LLNotificationsUtil::add("PanelGroupApply", args, LLSD(), + boost::bind(&LLPanelGroupRoles::handleNotifyCallback, this, _1, _2)); + mHasModal = true; + + // Returning false will block a close action from finishing until + // we get a response back from the user. + return false; + } + + transitionToTab(); + return true; +} + +void LLPanelGroupRoles::transitionToTab() +{ + // Tell the current panel that it is being deactivated. + if (mCurrentTab) + { + mCurrentTab->deactivate(); + } + + // Tell the new panel that it is being activated. + if (mRequestedTab) + { + // This is now the current tab; + mCurrentTab = mRequestedTab; + mCurrentTab->activate(); + mRequestedTab = 0; + } +} + +bool LLPanelGroupRoles::handleNotifyCallback(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + mHasModal = false; + LLPanelGroupTab* transition_tab = mRequestedTab; + switch (option) + { + case 0: // "Apply Changes" + { + // Try to apply changes, and switch to the requested tab. + std::string apply_mesg; + if ( !apply( apply_mesg ) ) + { + // There was a problem doing the apply. + if ( !apply_mesg.empty() ) + { + mHasModal = true; + LLSD args; + args["MESSAGE"] = apply_mesg; + LLNotificationsUtil::add("GenericAlert", args, LLSD(), boost::bind(&LLPanelGroupRoles::onModalClose, this, _1, _2)); + } + // Skip switching tabs. + break; + } + transitionToTab(); + mSubTabContainer->selectTabPanel( transition_tab ); + + break; + } + case 1: // "Ignore Changes" + // Switch to the requested panel without applying changes + cancel(); + transitionToTab(); + mSubTabContainer->selectTabPanel( transition_tab ); + break; + case 2: // "Cancel" + default: + mRequestedTab = NULL; + // Do nothing. The user is canceling the action. + break; + } + return false; +} + +bool LLPanelGroupRoles::onModalClose(const LLSD& notification, const LLSD& response) +{ + mHasModal = false; + return false; +} + +bool LLPanelGroupRoles::apply(std::string& mesg) +{ + // Pass this along to the currently visible sub tab. + if (!mSubTabContainer) return false; + + LLPanelGroupTab* panelp = (LLPanelGroupTab*) mSubTabContainer->getCurrentPanel(); + if (!panelp) return false; + + // Ignore the needs apply message. + std::string ignore_mesg; + if ( !panelp->needsApply(ignore_mesg) ) + { + // We don't need to apply anything. + // We're done. + return true; + } + + // Try to do the actual apply. + return panelp->apply(mesg); +} + +void LLPanelGroupRoles::cancel() +{ + // Pass this along to the currently visible sub tab. + if (!mSubTabContainer) return; + + LLPanelGroupTab* panelp = (LLPanelGroupTab*) mSubTabContainer->getCurrentPanel(); + if (!panelp) return; + + panelp->cancel(); +} + +void LLPanelGroupRoles::update(LLGroupChange gc) +{ + if (mGroupID.isNull()) return; + + LLPanelGroupTab* panelp = (LLPanelGroupTab*) mSubTabContainer->getCurrentPanel(); + if (panelp) + { + panelp->update(gc); + } + else + { + LL_WARNS() << "LLPanelGroupRoles::update() -- No subtab to update!" << LL_ENDL; + } + +} + +void LLPanelGroupRoles::activate() +{ + if (!gAgent.isInGroup(mGroupID)) return; + + // Start requesting member and role data if needed. + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + + if (!gdatap || !gdatap->isRoleDataComplete() ) + { + // Mildly hackish - clear all pending changes + cancel(); + + LLGroupMgr::getInstance()->sendGroupRoleDataRequest(mGroupID); + } + + // Need this to get base group member powers + if (!gdatap || !gdatap->isGroupPropertiesDataComplete() ) + { + LLGroupMgr::getInstance()->sendGroupPropertiesRequest(mGroupID); + } + + mFirstUse = false; + + LLPanelGroupTab* panelp = (LLPanelGroupTab*) mSubTabContainer->getCurrentPanel(); + if (panelp) panelp->activate(); +} + +void LLPanelGroupRoles::deactivate() +{ + LLPanelGroupTab* panelp = (LLPanelGroupTab*) mSubTabContainer->getCurrentPanel(); + if (panelp) panelp->deactivate(); +} + +bool LLPanelGroupRoles::needsApply(std::string& mesg) +{ + LLPanelGroupTab* panelp = (LLPanelGroupTab*) mSubTabContainer->getCurrentPanel(); + if (!panelp) return false; + + return panelp->needsApply(mesg); +} + +bool LLPanelGroupRoles::hasModal() +{ + if (mHasModal) return true; + + LLPanelGroupTab* panelp = (LLPanelGroupTab*) mSubTabContainer->getCurrentPanel(); + if (!panelp) return false; + + return panelp->hasModal(); +} + +void LLPanelGroupRoles::setGroupID(const LLUUID& id) +{ + LLPanelGroupTab::setGroupID(id); + + LLPanelGroupMembersSubTab* group_members_tab = findChild("members_sub_tab"); + LLPanelGroupRolesSubTab* group_roles_tab = findChild("roles_sub_tab"); + LLPanelGroupActionsSubTab* group_actions_tab = findChild("actions_sub_tab"); + LLPanelGroupBanListSubTab* group_ban_tab = findChild("banlist_sub_tab"); + + if(group_members_tab) group_members_tab->setGroupID(id); + if(group_roles_tab) group_roles_tab->setGroupID(id); + if(group_actions_tab) group_actions_tab->setGroupID(id); + if(group_ban_tab) group_ban_tab->setGroupID(id); + + LLButton* button = getChild("member_invite"); + if ( button ) + button->setEnabled(gAgent.hasPowerInGroup(mGroupID, GP_MEMBER_INVITE)); + + if(mSubTabContainer) + mSubTabContainer->selectTab(1); + group_roles_tab->mFirstOpen = true; + activate(); +} + + +// LLPanelGroupSubTab //////////////////////////////////////////////////// +LLPanelGroupSubTab::LLPanelGroupSubTab() +: LLPanelGroupTab(), + mHeader(NULL), + mFooter(NULL), + mActivated(false), + mHasGroupBanPower(false), + mSearchEditor(NULL) +{ +} + +LLPanelGroupSubTab::~LLPanelGroupSubTab() +{ + mSearchCommitConnection.disconnect(); +} + +bool LLPanelGroupSubTab::postBuildSubTab(LLView* root) +{ + // Get icons for later use. + mActionIcons.clear(); + + if (hasString("power_folder_icon")) + { + mActionIcons["folder"] = getString("power_folder_icon"); + } + + if (hasString("power_all_have_icon")) + { + mActionIcons["full"] = getString("power_all_have_icon"); + } + + if (hasString("power_partial_icon")) + { + mActionIcons["partial"] = getString("power_partial_icon"); + } + return true; +} + +bool LLPanelGroupSubTab::postBuild() +{ + // Hook up the search widgets. + constexpr bool recurse = true; + + mSearchEditor = findChild("filter_input", recurse); + if (mSearchEditor) // SubTab doesn't implement this, only some of derived classes + { + // panel + mSearchCommitConnection = mSearchEditor->setCommitCallback(boost::bind(&LLPanelGroupSubTab::setSearchFilter, this, _2)); + } + + return LLPanelGroupTab::postBuild(); +} + +void LLPanelGroupSubTab::setGroupID(const LLUUID& id) +{ + LLPanelGroupTab::setGroupID(id); + if(mSearchEditor) + { + mSearchEditor->clear(); + setSearchFilter(""); + } + + mActivated = false; +} + +void LLPanelGroupSubTab::setSearchFilter(const std::string& filter) +{ + if(mSearchFilter == filter) + return; + mSearchFilter = filter; + LLStringUtil::toLower(mSearchFilter); + update(GC_ALL); + onFilterChanged(); +} + +void LLPanelGroupSubTab::activate() +{ + setOthersVisible(true); +} + +void LLPanelGroupSubTab::deactivate() +{ + setOthersVisible(false); +} + +void LLPanelGroupSubTab::setOthersVisible(bool b) +{ + if (mHeader) + { + mHeader->setVisible( b ); + } + + if (mFooter) + { + mFooter->setVisible( b ); + } +} + +bool LLPanelGroupSubTab::matchesActionSearchFilter(std::string action) +{ + // If the search filter is empty, everything passes. + if (mSearchFilter.empty()) return true; + + LLStringUtil::toLower(action); + std::string::size_type match = action.find(mSearchFilter); + + if (std::string::npos == match) + { + // not found + return false; + } + else + { + return true; + } +} + +void LLPanelGroupSubTab::buildActionsList(LLScrollListCtrl* ctrl, + U64 allowed_by_some, + U64 allowed_by_all, + LLUICtrl::commit_callback_t commit_callback, + bool show_all, + bool filter, + bool is_owner_role) +{ + if (LLGroupMgr::getInstance()->mRoleActionSets.empty()) + { + LL_WARNS() << "Can't build action list - no actions found." << LL_ENDL; + return; + } + + mHasGroupBanPower = false; + + std::vector::iterator ras_it = LLGroupMgr::getInstance()->mRoleActionSets.begin(); + std::vector::iterator ras_end = LLGroupMgr::getInstance()->mRoleActionSets.end(); + for ( ; ras_it != ras_end; ++ras_it) + { + buildActionCategory(ctrl, + allowed_by_some, + allowed_by_all, + (*ras_it), + commit_callback, + show_all, + filter, + is_owner_role); + } +} + +void LLPanelGroupSubTab::buildActionCategory(LLScrollListCtrl* ctrl, + U64 allowed_by_some, + U64 allowed_by_all, + LLRoleActionSet* action_set, + LLUICtrl::commit_callback_t commit_callback, + bool show_all, + bool filter, + bool is_owner_role) +{ + LL_DEBUGS() << "Building role list for: " << action_set->mActionSetData->mName << LL_ENDL; + // See if the allow mask matches anything in this category. + if (show_all || (allowed_by_some & action_set->mActionSetData->mPowerBit)) + { + // List all the actions in this category that at least some members have. + LLSD row; + + row["columns"][0]["column"] = "icon"; + row["columns"][0]["type"] = "icon"; + + icon_map_t::iterator iter = mActionIcons.find("folder"); + if (iter != mActionIcons.end()) + { + row["columns"][0]["value"] = (*iter).second; + } + + row["columns"][1]["column"] = "action"; + row["columns"][1]["type"] = "text"; + row["columns"][1]["value"] = LLTrans::getString(action_set->mActionSetData->mName); + row["columns"][1]["font"]["name"] = "SANSSERIF_SMALL"; + + + LLScrollListItem* title_row = ctrl->addElement(row, ADD_BOTTOM, action_set->mActionSetData); + + LLScrollListText* name_textp = dynamic_cast(title_row->getColumn(2)); //?? I have no idea fix getColumn(1) return column spacer... + if (name_textp) + name_textp->setFontStyle(LLFontGL::BOLD); + + bool category_matches_filter = (filter) ? matchesActionSearchFilter(action_set->mActionSetData->mName) : true; + + std::vector::iterator ra_it = action_set->mActions.begin(); + std::vector::iterator ra_end = action_set->mActions.end(); + + bool items_match_filter = false; + bool can_change_actions = (!is_owner_role && gAgent.hasPowerInGroup(mGroupID, GP_ROLE_CHANGE_ACTIONS)); + + for ( ; ra_it != ra_end; ++ra_it) + { + // See if anyone has these action. + if (!show_all && !(allowed_by_some & (*ra_it)->mPowerBit)) + { + continue; + } + + // See if we are filtering out these actions + // If we aren't using filters, category_matches_filter will be true. + if (!category_matches_filter + && !matchesActionSearchFilter((*ra_it)->mDescription)) + { + continue; + } + + items_match_filter = true; + + // See if everyone has these actions. + bool show_full_strength = false; + if ( (allowed_by_some & (*ra_it)->mPowerBit) == (allowed_by_all & (*ra_it)->mPowerBit) ) + { + show_full_strength = true; + } + + LLSD row; + + S32 column_index = 0; + row["columns"][column_index]["column"] = "icon"; + ++column_index; + + + S32 check_box_index = -1; + if (commit_callback) + { + row["columns"][column_index]["column"] = "checkbox"; + row["columns"][column_index]["type"] = "checkbox"; + check_box_index = column_index; + ++column_index; + } + else + { + if (show_full_strength) + { + icon_map_t::iterator iter = mActionIcons.find("full"); + if (iter != mActionIcons.end()) + { + row["columns"][column_index]["column"] = "checkbox"; + row["columns"][column_index]["type"] = "icon"; + row["columns"][column_index]["value"] = (*iter).second; + ++column_index; + } + } + else + { + icon_map_t::iterator iter = mActionIcons.find("partial"); + if (iter != mActionIcons.end()) + { + row["columns"][column_index]["column"] = "checkbox"; + row["columns"][column_index]["type"] = "icon"; + row["columns"][column_index]["value"] = (*iter).second; + ++column_index; + } + row["enabled"] = false; + } + } + + row["columns"][column_index]["column"] = "action"; + row["columns"][column_index]["value"] = (*ra_it)->mDescription; + row["columns"][column_index]["font"] = "SANSSERIF_SMALL"; + + if(mHasGroupBanPower) + { + // The ban ability is being set. Prevent these abilities from being manipulated + if((*ra_it)->mPowerBit == GP_MEMBER_EJECT) + { + row["enabled"] = false; + } + else if((*ra_it)->mPowerBit == GP_ROLE_REMOVE_MEMBER) + { + row["enabled"] = false; + } + } + else + { + // The ban ability is not set. Allow these abilities to be manipulated + if((*ra_it)->mPowerBit == GP_MEMBER_EJECT) + { + row["enabled"] = true; + } + else if((*ra_it)->mPowerBit == GP_ROLE_REMOVE_MEMBER) + { + row["enabled"] = true; + } + } + + LLScrollListItem* item = ctrl->addElement(row, ADD_BOTTOM, (*ra_it)); + + if (-1 != check_box_index) + { + // Extract the checkbox that was created. + LLScrollListCheck* check_cell = (LLScrollListCheck*) item->getColumn(check_box_index); + LLCheckBoxCtrl* check = check_cell->getCheckBox(); + check->setEnabled(can_change_actions); + check->setCommitCallback(commit_callback); + check->setToolTip( check->getLabel() ); + + if (show_all) + { + check->setTentative(false); + if (allowed_by_some & (*ra_it)->mPowerBit) + { + check->set(true); + } + else + { + check->set(false); + } + } + else + { + check->set(true); + if (show_full_strength) + { + check->setTentative(false); + } + else + { + check->setTentative(true); + } + } + + // Regardless of whether or not this ability is allowed by all or some, we want to prevent + // the group managers from accidentally disabling either of the two additional abilities + // tied with GP_GROUP_BAN_ACCESS. + if( (allowed_by_all & GP_GROUP_BAN_ACCESS) == GP_GROUP_BAN_ACCESS || + (allowed_by_some & GP_GROUP_BAN_ACCESS) == GP_GROUP_BAN_ACCESS) + { + mHasGroupBanPower = true; + } + } + } + + if (!items_match_filter) + { + S32 title_index = ctrl->getItemIndex(title_row); + ctrl->deleteSingleItem(title_index); + } + } +} + +void LLPanelGroupSubTab::setFooterEnabled(bool enable) +{ + if (mFooter) + { + mFooter->setAllChildrenEnabled(enable); + } +} + + +// LLPanelGroupMembersSubTab ///////////////////////////////////////////// +static LLPanelInjector t_panel_group_members_subtab("panel_group_members_subtab"); + +LLPanelGroupMembersSubTab::LLPanelGroupMembersSubTab() +: LLPanelGroupSubTab(), + mMembersList(NULL), + mAssignedRolesList(NULL), + mAllowedActionsList(NULL), + mChanged(false), + mPendingMemberUpdate(false), + mHasMatch(false), + mNumOwnerAdditions(0) +{ +} + +LLPanelGroupMembersSubTab::~LLPanelGroupMembersSubTab() +{ + for (avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.begin(); it != mAvatarNameCacheConnections.end(); ++it) + { + if (it->second.connected()) + { + it->second.disconnect(); + } + } + mAvatarNameCacheConnections.clear(); + if (mMembersList) + { + gSavedSettings.setString("GroupMembersSortOrder", mMembersList->getSortColumnName()); + } +} + +bool LLPanelGroupMembersSubTab::postBuildSubTab(LLView* root) +{ + LLPanelGroupSubTab::postBuildSubTab(root); + + // Upcast parent so we can ask it for sibling controls. + LLPanelGroupRoles* parent = (LLPanelGroupRoles*) root; + + // Look recursively from the parent to find all our widgets. + bool recurse = true; + mHeader = parent->findChild("members_header", recurse); + mFooter = parent->findChild("members_footer", recurse); + + mMembersList = parent->getChild("member_list", recurse); + mAssignedRolesList = parent->getChild("member_assigned_roles", recurse); + mAllowedActionsList = parent->getChild("member_allowed_actions", recurse); + mActionDescription = parent->getChild("member_action_description", recurse); + + if (!mMembersList || !mAssignedRolesList || !mAllowedActionsList || !mActionDescription) return false; + + mAllowedActionsList->setCommitOnSelectionChange(true); + mAllowedActionsList->setCommitCallback(boost::bind(&LLPanelGroupMembersSubTab::updateActionDescription, this)); + + // We want to be notified whenever a member is selected. + mMembersList->setCommitOnSelectionChange(true); + mMembersList->setCommitCallback(onMemberSelect, this); + // Show the member's profile on double click. + mMembersList->setDoubleClickCallback(onMemberDoubleClick, this); + mMembersList->setContextMenu(LLScrollListCtrl::MENU_AVATAR); + mMembersList->setIsFriendCallback(LLAvatarActions::isFriend); + + LLSD row; + row["columns"][0]["column"] = "name"; + row["columns"][1]["column"] = "donated"; + row["columns"][2]["column"] = "online"; + mMembersList->addElement(row); + std::string order_by = gSavedSettings.getString("GroupMembersSortOrder"); + if(!order_by.empty()) + { + mMembersList->sortByColumn(order_by, true); + } + + LLButton* button = parent->getChild("member_invite", recurse); + if ( button ) + { + button->setClickedCallback(onInviteMember, this); + button->setEnabled(gAgent.hasPowerInGroup(mGroupID, GP_MEMBER_INVITE)); + } + + mEjectBtn = parent->getChild("member_eject", recurse); + if ( mEjectBtn ) + { + mEjectBtn->setClickedCallback(onEjectMembers, this); + mEjectBtn->setEnabled(false); + } + + mBanBtn = parent->getChild("member_ban", recurse); + if(mBanBtn) + { + mBanBtn->setClickedCallback(onBanMember, this); + mBanBtn->setEnabled(false); + } + + return true; +} + +void LLPanelGroupMembersSubTab::setGroupID(const LLUUID& id) +{ + //clear members list + if(mMembersList) mMembersList->deleteAllItems(); + if(mAssignedRolesList) mAssignedRolesList->deleteAllItems(); + if(mAllowedActionsList) mAllowedActionsList->deleteAllItems(); + + LLPanelGroupSubTab::setGroupID(id); +} + +// static +void LLPanelGroupMembersSubTab::onMemberSelect(LLUICtrl* ctrl, void* user_data) +{ + LLPanelGroupMembersSubTab* self = static_cast(user_data); + self->handleMemberSelect(); +} + +void LLPanelGroupMembersSubTab::handleMemberSelect() +{ + LL_DEBUGS() << "LLPanelGroupMembersSubTab::handleMemberSelect" << LL_ENDL; + + mAssignedRolesList->deleteAllItems(); + mAllowedActionsList->deleteAllItems(); + mActionDescription->clear(); + + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + if (!gdatap) + { + LL_WARNS() << "LLPanelGroupMembersSubTab::handleMemberSelect() " + << "-- No group data!" << LL_ENDL; + return; + } + + // Check if there is anything selected. + std::vector selection = mMembersList->getAllSelected(); + if (selection.empty()) return; + + // Build a vector of all selected members, and gather allowed actions. + uuid_vec_t selected_members; + U64 allowed_by_all = GP_ALL_POWERS; //0xFFFFffffFFFFffffLL; + U64 allowed_by_some = 0; + + std::vector::iterator itor; + for (itor = selection.begin(); + itor != selection.end(); ++itor) + { + LLUUID member_id = (*itor)->getUUID(); + + selected_members.push_back( member_id ); + // Get this member's power mask including any unsaved changes + + U64 powers = getAgentPowersBasedOnRoleChanges( member_id ); + + allowed_by_all &= powers; + allowed_by_some |= powers; + } + std::sort(selected_members.begin(), selected_members.end()); + + ////////////////////////////////// + // Build the allowed actions list. + ////////////////////////////////// + buildActionsList(mAllowedActionsList, + allowed_by_some, + allowed_by_all, + NULL, + false, + false, + false); + + ////////////////////////////////// + // Build the assigned roles list. + ////////////////////////////////// + // Add each role to the assigned roles list. + LLGroupMgrGroupData::role_list_t::iterator iter = gdatap->mRoles.begin(); + LLGroupMgrGroupData::role_list_t::iterator end = gdatap->mRoles.end(); + + bool can_ban_members = gAgent.hasPowerInGroup(mGroupID, GP_GROUP_BAN_ACCESS); + bool can_eject_members = gAgent.hasPowerInGroup(mGroupID, GP_MEMBER_EJECT); + bool member_is_owner = false; + + for( ; iter != end; ++iter) + { + // Count how many selected users are in this role. + const LLUUID& role_id = iter->first; + LLGroupRoleData* group_role_data = iter->second; + + if (group_role_data) + { + const bool needs_sort = false; + S32 count = group_role_data->getMembersInRole( + selected_members, needs_sort); + //check if the user has permissions to assign/remove + //members to/from the role (but the ability to add/remove + //should only be based on the "saved" changes to the role + //not in the temp/meta data. -jwolk + bool cb_enable = ( (count > 0) ? + agentCanRemoveFromRole(mGroupID, role_id) : + agentCanAddToRole(mGroupID, role_id) ); + + + // Owner role has special enabling permissions for removal. + if (cb_enable && (count > 0) && role_id == gdatap->mOwnerRole) + { + // Check if any owners besides this agent are selected. + uuid_vec_t::const_iterator member_iter; + uuid_vec_t::const_iterator member_end = + selected_members.end(); + for (member_iter = selected_members.begin(); + member_iter != member_end; + ++member_iter) + { + // Don't count the agent. + if ((*member_iter) == gAgent.getID()) continue; + + // Look up the member data. + LLGroupMgrGroupData::member_list_t::iterator mi = + gdatap->mMembers.find((*member_iter)); + if (mi == gdatap->mMembers.end()) continue; + LLGroupMemberData* member_data = (*mi).second; + // Is the member an owner? + if ( member_data && member_data->isInRole(gdatap->mOwnerRole) ) + { + // Can't remove other owners. + cb_enable = false; + can_ban_members = false; + break; + } + } + } + + //now see if there are any role changes for the selected + //members and remember to include them + uuid_vec_t::iterator sel_mem_iter = selected_members.begin(); + for (; sel_mem_iter != selected_members.end(); sel_mem_iter++) + { + LLRoleMemberChangeType type; + if ( getRoleChangeType(*sel_mem_iter, role_id, type) ) + { + if ( type == RMC_ADD ) count++; + else if ( type == RMC_REMOVE ) count--; + } + } + + // If anyone selected is in any role besides 'Everyone' then they can't be ejected. + if (role_id.notNull() && (count > 0)) + { + can_eject_members = false; + if (role_id == gdatap->mOwnerRole) + { + member_is_owner = true; + } + } + + LLRoleData rd; + if (gdatap->getRoleData(role_id,rd)) + { + std::ostringstream label; + label << rd.mRoleName; + // Don't bother showing a count, if there is only 0 or 1. + if (count > 1) + { + label << ": " << count ; + } + + LLSD row; + row["id"] = role_id; + + row["columns"][0]["column"] = "checkbox"; + row["columns"][0]["type"] = "checkbox"; + + row["columns"][1]["column"] = "role"; + row["columns"][1]["value"] = label.str(); + + if (row["id"].asUUID().isNull()) + { + // This is the everyone role, you can't take people out of the everyone role! + row["enabled"] = false; + } + + LLScrollListItem* item = mAssignedRolesList->addElement(row); + + // Extract the checkbox that was created. + LLScrollListCheck* check_cell = (LLScrollListCheck*) item->getColumn(0); + LLCheckBoxCtrl* check = check_cell->getCheckBox(); + check->setCommitCallback(onRoleCheck, this); + check->set( count > 0 ); + check->setTentative( + (0 != count) + && (selected_members.size() != + (uuid_vec_t::size_type)count)); + + //NOTE: as of right now a user can break the group + //by removing himself from a role if he is the + //last owner. We should check for this special case + // -jwolk + check->setEnabled(cb_enable); + item->setEnabled(cb_enable); + } + } + else + { + // This could happen if changes are not synced right on sub-panel change. + LL_WARNS() << "No group role data for " << iter->second << LL_ENDL; + } + } + mAssignedRolesList->setEnabled(true); + + if (gAgent.isGodlike()) + { + can_eject_members = true; + // can_ban_members = true; + } + + if (!can_eject_members && !member_is_owner) + { + // Maybe we can eject them because we are an owner... + LLGroupMgrGroupData::member_list_t::iterator mi = gdatap->mMembers.find(gAgent.getID()); + if (mi != gdatap->mMembers.end()) + { + LLGroupMemberData* member_data = (*mi).second; + + if ( member_data && member_data->isInRole(gdatap->mOwnerRole) ) + { + can_eject_members = true; + //can_ban_members = true; + } + } + + } + + // ... or we can eject them because we have all the requisite powers... + if( gAgent.hasPowerInGroup(mGroupID, GP_ROLE_REMOVE_MEMBER) && + !member_is_owner) + { + if( gAgent.hasPowerInGroup(mGroupID, GP_MEMBER_EJECT)) + { + can_eject_members = true; + } + + if( gAgent.hasPowerInGroup(mGroupID, GP_GROUP_BAN_ACCESS)) + { + can_ban_members = true; + } + } + + + uuid_vec_t::const_iterator member_iter = selected_members.begin(); + uuid_vec_t::const_iterator member_end = selected_members.end(); + for ( ; member_iter != member_end; ++member_iter) + { + // Don't count the agent. + if ((*member_iter) == gAgent.getID()) + { + can_eject_members = false; + can_ban_members = false; + } + } + + mBanBtn->setEnabled(can_ban_members); + mEjectBtn->setEnabled(can_eject_members); +} + +// static +void LLPanelGroupMembersSubTab::onMemberDoubleClick(void* user_data) +{ + LLPanelGroupMembersSubTab* self = static_cast(user_data); + self->handleMemberDoubleClick(); +} + +//static +void LLPanelGroupMembersSubTab::onInviteMember(void *userdata) +{ + LLPanelGroupMembersSubTab* selfp = (LLPanelGroupMembersSubTab*) userdata; + + if ( selfp ) + { + selfp->handleInviteMember(); + } +} + +void LLPanelGroupMembersSubTab::handleInviteMember() +{ + LLFloaterGroupInvite::showForGroup(mGroupID, NULL, false); +} + +void LLPanelGroupMembersSubTab::onEjectMembers(void *userdata) +{ + LLPanelGroupMembersSubTab* selfp = (LLPanelGroupMembersSubTab*) userdata; + + if ( selfp ) + { + selfp->confirmEjectMembers(); + } +} + +void LLPanelGroupMembersSubTab::confirmEjectMembers() +{ + std::vector selection = mMembersList->getAllSelected(); + if (selection.empty()) return; + + S32 selection_count = selection.size(); + if (selection_count == 1) + { + LLSD args; + LLAvatarName av_name; + LLAvatarNameCache::get(mMembersList->getValue(), &av_name); + args["AVATAR_NAME"] = av_name.getUserName(); + LLSD payload; + LLNotificationsUtil::add("EjectGroupMemberWarning", + args, + payload, + boost::bind(&LLPanelGroupMembersSubTab::handleEjectCallback, this, _1, _2)); + } + else + { + LLSD args; + args["COUNT"] = llformat("%d", selection_count); + LLSD payload; + LLNotificationsUtil::add("EjectGroupMembersWarning", + args, + payload, + boost::bind(&LLPanelGroupMembersSubTab::handleEjectCallback, this, _1, _2)); + } +} + +void LLPanelGroupMembersSubTab::handleEjectMembers() +{ + //send down an eject message + uuid_vec_t selected_members; + + std::vector selection = mMembersList->getAllSelected(); + if (selection.empty()) return; + + std::vector::iterator itor; + for (itor = selection.begin() ; + itor != selection.end(); ++itor) + { + LLUUID member_id = (*itor)->getUUID(); + selected_members.push_back( member_id ); + } + + mMembersList->deleteSelectedItems(); + + sendEjectNotifications(mGroupID, selected_members); + + LLGroupMgr::getInstance()->sendGroupMemberEjects(mGroupID, selected_members); +} + +bool LLPanelGroupMembersSubTab::handleEjectCallback(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (0 == option) // Eject button + { + handleEjectMembers(); + } + return false; +} + +void LLPanelGroupMembersSubTab::sendEjectNotifications(const LLUUID& group_id, const uuid_vec_t& selected_members) +{ + LLGroupMgrGroupData* group_data = LLGroupMgr::getInstance()->getGroupData(group_id); + + if (group_data) + { + for (uuid_vec_t::const_iterator i = selected_members.begin(); i != selected_members.end(); ++i) + { + LLSD args; + args["AVATAR_NAME"] = LLSLURL("agent", *i, "completename").getSLURLString(); + args["GROUP_NAME"] = group_data->mName; + + LLNotifications::instance().add(LLNotification::Params("EjectAvatarFromGroup").substitutions(args)); + } + } +} + +void LLPanelGroupMembersSubTab::handleRoleCheck(const LLUUID& role_id, + LLRoleMemberChangeType type) +{ + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + if (!gdatap) return; + + //add that the user is requesting to change the roles for selected + //members + U64 powers_all_have = GP_ALL_POWERS; + U64 powers_some_have = 0; + + bool is_owner_role = ( gdatap->mOwnerRole == role_id ); + LLUUID member_id; + + std::vector selection = mMembersList->getAllSelected(); + if (selection.empty()) + { + return; + } + + for (std::vector::iterator itor = selection.begin() ; + itor != selection.end(); ++itor) + { + member_id = (*itor)->getUUID(); + + //see if we requested a change for this member before + if ( mMemberRoleChangeData.find(member_id) == mMemberRoleChangeData.end() ) + { + mMemberRoleChangeData[member_id] = new role_change_data_map_t; + } + role_change_data_map_t* role_change_datap = mMemberRoleChangeData[member_id]; + + //now check to see if the selected group member + //had changed his association with the selected role before + + role_change_data_map_t::iterator role = role_change_datap->find(role_id); + if ( role != role_change_datap->end() ) + { + //see if the new change type cancels out the previous change + if (role->second != type) + { + role_change_datap->erase(role_id); + if ( is_owner_role ) mNumOwnerAdditions--; + } + //else do nothing + + if ( role_change_datap->empty() ) + { + //the current member now has no role changes + //so erase the role change and erase the member's entry + delete role_change_datap; + role_change_datap = NULL; + + mMemberRoleChangeData.erase(member_id); + } + } + else + { + //a previously unchanged role is being changed + (*role_change_datap)[role_id] = type; + if ( is_owner_role && type == RMC_ADD ) mNumOwnerAdditions++; + } + + //we need to calculate what powers the selected members + //have (including the role changes we're making) + //so that we can rebuild the action list + U64 new_powers = getAgentPowersBasedOnRoleChanges(member_id); + + powers_all_have &= new_powers; + powers_some_have |= new_powers; + } + + + mChanged = !mMemberRoleChangeData.empty(); + notifyObservers(); + + //alrighty now we need to update the actions list + //to reflect the changes + mAllowedActionsList->deleteAllItems(); + buildActionsList(mAllowedActionsList, + powers_some_have, + powers_all_have, + NULL, + false, + false, + false); +} + +// static +void LLPanelGroupMembersSubTab::onRoleCheck(LLUICtrl* ctrl, void* user_data) +{ + LLPanelGroupMembersSubTab* self = static_cast(user_data); + LLCheckBoxCtrl* check_box = static_cast(ctrl); + if (!check_box || !self) return; + + LLScrollListItem* first_selected = + self->mAssignedRolesList->getFirstSelected(); + if (first_selected) + { + LLUUID role_id = first_selected->getUUID(); + LLRoleMemberChangeType change_type = (check_box->get() ? + RMC_ADD : + RMC_REMOVE); + + self->handleRoleCheck(role_id, change_type); + } +} + +void LLPanelGroupMembersSubTab::handleMemberDoubleClick() +{ + LLScrollListItem* selected = mMembersList->getFirstSelected(); + if (selected) + { + LLUUID member_id = selected->getUUID(); + LLAvatarActions::showProfile( member_id ); + } +} + +void LLPanelGroupMembersSubTab::activate() +{ + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + + LLPanelGroupSubTab::activate(); + if(!mActivated) + { + if (!gdatap || !gdatap->isMemberDataComplete()) + { + LLGroupMgr::getInstance()->sendCapGroupMembersRequest(mGroupID); + } + + if (!gdatap || !gdatap->isRoleMemberDataComplete()) + { + LLGroupMgr::getInstance()->sendGroupRoleMembersRequest(mGroupID); + } + + update(GC_ALL); + mActivated = true; + } + else + { + // Members can be removed outside of this tab, checking changes + if (!gdatap || (gdatap->isMemberDataComplete() && gdatap->mMembers.size() != mMembersList->getItemCount())) + { + update(GC_MEMBER_DATA); + } + } + mActionDescription->clear(); +} + +void LLPanelGroupMembersSubTab::deactivate() +{ + LLPanelGroupSubTab::deactivate(); +} + +bool LLPanelGroupMembersSubTab::needsApply(std::string& mesg) +{ + return mChanged; +} + +void LLPanelGroupMembersSubTab::cancel() +{ + if ( mChanged ) + { + std::for_each(mMemberRoleChangeData.begin(), + mMemberRoleChangeData.end(), + DeletePairedPointer()); + mMemberRoleChangeData.clear(); + + mChanged = false; + notifyObservers(); + } +} + +bool LLPanelGroupMembersSubTab::apply(std::string& mesg) +{ + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + if (!gdatap) + { + LL_WARNS() << "Unable to get group data for group " << mGroupID << LL_ENDL; + + mesg.assign("Unable to save member data. Try again later."); + return false; + } + + if (mChanged) + { + //figure out if we are somehow adding an owner or not and alert + //the user...possibly make it ignorable + if ( mNumOwnerAdditions > 0 ) + { + LLRoleData rd; + LLSD args; + + if ( gdatap->getRoleData(gdatap->mOwnerRole, rd) ) + { + mHasModal = true; + args["ROLE_NAME"] = rd.mRoleName; + LLNotificationsUtil::add("AddGroupOwnerWarning", + args, + LLSD(), + boost::bind(&LLPanelGroupMembersSubTab::addOwnerCB, this, _1, _2)); + } + else + { + LL_WARNS() << "Unable to get role information for the owner role in group " << mGroupID << LL_ENDL; + + mesg.assign("Unable to retried specific group information. Try again later"); + return false; + } + + } + else + { + applyMemberChanges(); + } + } + + return true; +} + +bool LLPanelGroupMembersSubTab::addOwnerCB(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + mHasModal = false; + + if (0 == option) + { + // User clicked "Yes" + applyMemberChanges(); + } + return false; +} + +void LLPanelGroupMembersSubTab::applyMemberChanges() +{ + //sucks to do a find again here, but it is in constant time, so, could + //be worse + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + if (!gdatap) + { + LL_WARNS() << "Unable to get group data for group " << mGroupID << LL_ENDL; + return; + } + + //we need to add all of the changed roles data + //for each member whose role changed + for (member_role_changes_map_t::iterator member = mMemberRoleChangeData.begin(); + member != mMemberRoleChangeData.end(); ++member) + { + for (role_change_data_map_t::iterator role = member->second->begin(); + role != member->second->end(); ++role) + { + gdatap->changeRoleMember(role->first, //role_id + member->first, //member_id + role->second); //add/remove + } + + member->second->clear(); + delete member->second; + } + mMemberRoleChangeData.clear(); + + LLGroupMgr::getInstance()->sendGroupRoleMemberChanges(mGroupID); + //force a UI update + handleMemberSelect(); + + mChanged = false; + mNumOwnerAdditions = 0; + notifyObservers(); +} + +bool LLPanelGroupMembersSubTab::matchesSearchFilter(const std::string& fullname) +{ + // If the search filter is empty, everything passes. + if (mSearchFilter.empty()) return true; + + // Create a full name, and compare it to the search filter. + std::string fullname_lc(fullname); + LLStringUtil::toLower(fullname_lc); + + std::string::size_type match = fullname_lc.find(mSearchFilter); + + if (std::string::npos == match) + { + // not found + return false; + } + else + { + return true; + } +} + +U64 LLPanelGroupMembersSubTab::getAgentPowersBasedOnRoleChanges(const LLUUID& agent_id) +{ + //we loop over all of the changes + //if we are adding a role, then we simply add the role's powers + //if we are removing a role, we store that role id away + //and then we have to build the powers up bases on the roles the agent + //is in + + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + if (!gdatap) + { + LL_WARNS() << "LLPanelGroupMembersSubTab::getAgentPowersBasedOnRoleChanges() -- No group data!" << LL_ENDL; + return GP_NO_POWERS; + } + + LLGroupMgrGroupData::member_list_t::iterator iter = gdatap->mMembers.find(agent_id); + if ( iter == gdatap->mMembers.end() ) + { + LL_WARNS() << "LLPanelGroupMembersSubTab::getAgentPowersBasedOnRoleChanges() -- No member data for member with UUID " << agent_id << LL_ENDL; + return GP_NO_POWERS; + } + + LLGroupMemberData* member_data = (*iter).second; + if (!member_data) + { + LL_WARNS() << "LLPanelGroupMembersSubTab::getAgentPowersBasedOnRoleChanges() -- Null member data for member with UUID " << agent_id << LL_ENDL; + return GP_NO_POWERS; + } + + //see if there are unsaved role changes for this agent + role_change_data_map_t* role_change_datap = NULL; + member_role_changes_map_t::iterator member = mMemberRoleChangeData.find(agent_id); + if ( member != mMemberRoleChangeData.end() ) + { + //this member has unsaved role changes + //so grab them + role_change_datap = (*member).second; + } + + U64 new_powers = GP_NO_POWERS; + + if ( role_change_datap ) + { + uuid_vec_t roles_to_be_removed; + + for (role_change_data_map_t::iterator role = role_change_datap->begin(); + role != role_change_datap->end(); ++ role) + { + if ( role->second == RMC_ADD ) + { + new_powers |= gdatap->getRolePowers(role->first); + } + else + { + roles_to_be_removed.push_back(role->first); + } + } + + //loop over the member's current roles, summing up + //the powers (not including the role we are removing) + for (LLGroupMemberData::role_list_t::iterator current_role = member_data->roleBegin(); + current_role != member_data->roleEnd(); ++current_role) + { + bool role_in_remove_list = + (std::find(roles_to_be_removed.begin(), + roles_to_be_removed.end(), + current_role->second->getID()) != + roles_to_be_removed.end()); + + if ( !role_in_remove_list ) + { + new_powers |= + current_role->second->getRoleData().mRolePowers; + } + } + } + else + { + //there are no changes for this member + //the member's powers are just the ones stored in the group + //manager + new_powers = member_data->getAgentPowers(); + } + + return new_powers; +} + +//If there is no change, returns false be sure to verify +//that there is a role change before attempting to get it or else +//the data will make no sense. Stores the role change type +bool LLPanelGroupMembersSubTab::getRoleChangeType(const LLUUID& member_id, + const LLUUID& role_id, + LLRoleMemberChangeType& type) +{ + member_role_changes_map_t::iterator member_changes_iter = mMemberRoleChangeData.find(member_id); + if ( member_changes_iter != mMemberRoleChangeData.end() ) + { + role_change_data_map_t::iterator role_changes_iter = member_changes_iter->second->find(role_id); + if ( role_changes_iter != member_changes_iter->second->end() ) + { + type = role_changes_iter->second; + return true; + } + } + + return false; +} + +void LLPanelGroupMembersSubTab::draw() +{ + LLPanelGroupSubTab::draw(); + + if (mPendingMemberUpdate) + { + updateMembers(); + } +} + +void LLPanelGroupMembersSubTab::update(LLGroupChange gc) +{ + if (mGroupID.isNull()) return; + + if ( GC_TITLES == gc || GC_PROPERTIES == gc ) + { + // Don't care about title or general group properties updates. + return; + } + + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + if (!gdatap) + { + LL_WARNS() << "LLPanelGroupMembersSubTab::update() -- No group data!" << LL_ENDL; + return; + } + + // Wait for both all data to be retrieved before displaying anything. + if ( gdatap->isMemberDataComplete() + && gdatap->isRoleDataComplete() + && gdatap->isRoleMemberDataComplete()) + { + mMemberProgress = gdatap->mMembers.begin(); + mPendingMemberUpdate = true; + mHasMatch = false; + } + else + { + // Build a string with info on retrieval progress. + std::ostringstream retrieved; + + if ( gdatap->isRoleDataComplete() && gdatap->isMemberDataComplete() && !gdatap->mMembers.size() ) + { + // MAINT-5237 + retrieved << "Member list not available."; + } + else if ( !gdatap->isMemberDataComplete() ) + { + // Still busy retreiving member list. + retrieved << "Retrieving member list (" << gdatap->mMembers.size() + << " / " << gdatap->mMemberCount << ")..."; + } + else if( !gdatap->isRoleDataComplete() ) + { + // Still busy retreiving role list. + retrieved << "Retrieving role list (" << gdatap->mRoles.size() + << " / " << gdatap->mRoleCount << ")..."; + } + else // (!gdatap->isRoleMemberDataComplete()) + { + // Still busy retreiving role/member mappings. + retrieved << "Retrieving role member mappings..."; + } + mMembersList->setEnabled(false); + mMembersList->setCommentText(retrieved.str()); + } +} + +void LLPanelGroupMembersSubTab::addMemberToList(LLGroupMemberData* data) +{ + if (!data) return; + LLUIString donated = getString("donation_area"); + donated.setArg("[AREA]", llformat("%d", data->getContribution())); + + LLNameListCtrl::NameItem item_params; + item_params.value = data->getID(); + + item_params.columns.add().column("name").font.name("SANSSERIF_SMALL").style("NORMAL"); + + item_params.columns.add().column("donated").value(donated.getString()) + .font.name("SANSSERIF_SMALL").style("NORMAL"); + + item_params.columns.add().column("online").value(data->getOnlineStatus()) + .font.name("SANSSERIF_SMALL").style("NORMAL"); + + item_params.columns.add().column("title").value(data->getTitle()).font.name("SANSSERIF_SMALL").style("NORMAL");; + + mMembersList->addNameItemRow(item_params); + + mHasMatch = true; +} + +void LLPanelGroupMembersSubTab::onNameCache(const LLUUID& update_id, LLGroupMemberData* member, const LLAvatarName& av_name, const LLUUID& av_id) +{ + avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.find(av_id); + if (it != mAvatarNameCacheConnections.end()) + { + if (it->second.connected()) + { + it->second.disconnect(); + } + mAvatarNameCacheConnections.erase(it); + } + + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + if (!gdatap + || gdatap->getMemberVersion() != update_id + || !member) + { + return; + } + + // trying to avoid unnecessary hash lookups + if (matchesSearchFilter(av_name.getAccountName())) + { + addMemberToList(member); + if(!mMembersList->getEnabled()) + { + mMembersList->setEnabled(true); + } + } + +} + +void LLPanelGroupMembersSubTab::updateMembers() +{ + mPendingMemberUpdate = false; + + // Rebuild the members list. + + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + if (!gdatap) + { + LL_WARNS() << "LLPanelGroupMembersSubTab::updateMembers() -- No group data!" << LL_ENDL; + return; + } + + // Make sure all data is still complete. Incomplete data + // may occur if we refresh. + if ( !gdatap->isMemberDataComplete() + || !gdatap->isRoleDataComplete() + || !gdatap->isRoleMemberDataComplete()) + { + return; + } + + //cleanup list only for first iteration + if(mMemberProgress == gdatap->mMembers.begin()) + { + mMembersList->deleteAllItems(); + } + + LLGroupMgrGroupData::member_list_t::iterator end = gdatap->mMembers.end(); + + LLTimer update_time; + update_time.setTimerExpirySec(UPDATE_MEMBERS_SECONDS_PER_FRAME); + + for( ; mMemberProgress != end && !update_time.hasExpired(); ++mMemberProgress) + { + if (!mMemberProgress->second) + continue; + + // Do filtering on name if it is already in the cache. + LLAvatarName av_name; + if (LLAvatarNameCache::get(mMemberProgress->first, &av_name)) + { + if (matchesSearchFilter(av_name.getAccountName())) + { + addMemberToList(mMemberProgress->second); + } + } + else + { + // If name is not cached, onNameCache() should be called when it is cached and add this member to list. + avatar_name_cache_connection_map_t::iterator it = mAvatarNameCacheConnections.find(mMemberProgress->first); + if (it != mAvatarNameCacheConnections.end()) + { + if (it->second.connected()) + { + it->second.disconnect(); + } + mAvatarNameCacheConnections.erase(it); + } + mAvatarNameCacheConnections[mMemberProgress->first] = LLAvatarNameCache::get(mMemberProgress->first, boost::bind(&LLPanelGroupMembersSubTab::onNameCache, this, gdatap->getMemberVersion(), mMemberProgress->second, _2, _1)); + } + } + + if (mMemberProgress == end) + { + if (mHasMatch) + { + mMembersList->setEnabled(true); + } + else if (gdatap->mMembers.size()) + { + mMembersList->setEnabled(false); + mMembersList->setCommentText(std::string("No match.")); + } + } + else + { + mPendingMemberUpdate = true; + } + + // This should clear the other two lists, since nothing is selected. + handleMemberSelect(); +} + +void LLPanelGroupMembersSubTab::onBanMember(void* user_data) +{ + LLPanelGroupMembersSubTab* self = static_cast(user_data); + self->confirmBanMembers(); +} + +void LLPanelGroupMembersSubTab::confirmBanMembers() +{ + std::vector selection = mMembersList->getAllSelected(); + if (selection.empty()) return; + + S32 selection_count = selection.size(); + if (selection_count == 1) + { + LLSD args; + LLAvatarName av_name; + LLAvatarNameCache::get(mMembersList->getValue(), &av_name); + args["AVATAR_NAME"] = av_name.getUserName(); + LLSD payload; + LLNotificationsUtil::add("BanGroupMemberWarning", + args, + payload, + boost::bind(&LLPanelGroupMembersSubTab::handleBanCallback, this, _1, _2)); + } + else + { + LLSD args; + args["COUNT"] = llformat("%d", selection_count); + LLSD payload; + LLNotificationsUtil::add("BanGroupMembersWarning", + args, + payload, + boost::bind(&LLPanelGroupMembersSubTab::handleBanCallback, this, _1, _2)); + } +} + +bool LLPanelGroupMembersSubTab::handleBanCallback(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (0 == option) // Eject button + { + handleBanMember(); + } + return false; +} + +void LLPanelGroupMembersSubTab::updateActionDescription() +{ + mActionDescription->setText(std::string()); + LLScrollListItem* action_item = mAllowedActionsList->getFirstSelected(); + if (!action_item || !mAllowedActionsList->getCanSelect()) + { + return; + } + + LLRoleAction* rap = (LLRoleAction*)action_item->getUserdata(); + if (rap) + { + std::string desc = rap->mLongDescription.empty() ? rap->mDescription : rap->mLongDescription; + mActionDescription->setText(desc); + } +} + +void LLPanelGroupMembersSubTab::handleBanMember() +{ + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + if(!gdatap) + { + LL_WARNS("Groups") << "Unable to get group data for group " << mGroupID << LL_ENDL; + return; + } + + std::vector selection = mMembersList->getAllSelected(); + if(selection.empty()) + { + return; + } + + uuid_vec_t ban_ids; + std::vector::iterator itor; + for(itor = selection.begin(); itor != selection.end(); ++itor) + { + LLUUID ban_id = (*itor)->getUUID(); + ban_ids.push_back(ban_id); + + LLGroupBanData ban_data; + gdatap->createBanEntry(ban_id, ban_data); + } + + LLGroupMgr::getInstance()->sendGroupBanRequest(LLGroupMgr::REQUEST_POST, mGroupID, LLGroupMgr::BAN_CREATE, ban_ids); + handleEjectMembers(); +} + + +// LLPanelGroupRolesSubTab /////////////////////////////////////////////// +static LLPanelInjector t_panel_group_roles_subtab("panel_group_roles_subtab"); + +LLPanelGroupRolesSubTab::LLPanelGroupRolesSubTab() + : LLPanelGroupSubTab(), + mRolesList(NULL), + mAssignedMembersList(NULL), + mAllowedActionsList(NULL), + mRoleName(NULL), + mRoleTitle(NULL), + mRoleDescription(NULL), + mMemberVisibleCheck(NULL), + mDeleteRoleButton(NULL), + mCopyRoleButton(NULL), + mCreateRoleButton(NULL), + mFirstOpen(true), + mHasRoleChange(false) +{ +} + +LLPanelGroupRolesSubTab::~LLPanelGroupRolesSubTab() +{ +} + +bool LLPanelGroupRolesSubTab::postBuildSubTab(LLView* root) +{ + LLPanelGroupSubTab::postBuildSubTab(root); + + // Upcast parent so we can ask it for sibling controls. + LLPanelGroupRoles* parent = (LLPanelGroupRoles*) root; + + // Look recursively from the parent to find all our widgets. + bool recurse = true; + mHeader = parent->findChild("roles_header", recurse); + mFooter = parent->findChild("roles_footer", recurse); + + + mRolesList = parent->getChild("role_list", recurse); + mAssignedMembersList = parent->getChild("role_assigned_members", recurse); + mAllowedActionsList = parent->getChild("role_allowed_actions", recurse); + mActionDescription = parent->getChild("role_action_description", recurse); + + mRoleName = parent->getChild("role_name", recurse); + mRoleTitle = parent->getChild("role_title", recurse); + mRoleDescription = parent->getChild("role_description", recurse); + + mMemberVisibleCheck = parent->getChild("role_visible_in_list", recurse); + + if (!mRolesList || !mAssignedMembersList || !mAllowedActionsList || !mActionDescription + || !mRoleName || !mRoleTitle || !mRoleDescription || !mMemberVisibleCheck) + { + LL_WARNS() << "ARG! element not found." << LL_ENDL; + return false; + } + + mRemoveEveryoneTxt = getString("cant_delete_role"); + + mCreateRoleButton = + parent->getChild("role_create", recurse); + if ( mCreateRoleButton ) + { + mCreateRoleButton->setClickedCallback(onCreateRole, this); + mCreateRoleButton->setEnabled(false); + } + + mCopyRoleButton = + parent->getChild("role_copy", recurse); + if ( mCopyRoleButton ) + { + mCopyRoleButton->setClickedCallback(onCopyRole, this); + mCopyRoleButton->setEnabled(false); + } + + mDeleteRoleButton = + parent->getChild("role_delete", recurse); + if ( mDeleteRoleButton ) + { + mDeleteRoleButton->setClickedCallback(onDeleteRole, this); + mDeleteRoleButton->setEnabled(false); + } + + mRolesList->setCommitOnSelectionChange(true); + mRolesList->setCommitCallback(onRoleSelect, this); + + mAssignedMembersList->setContextMenu(LLScrollListCtrl::MENU_AVATAR); + + mMemberVisibleCheck->setCommitCallback(onMemberVisibilityChange, this); + + mAllowedActionsList->setCommitOnSelectionChange(true); + mAllowedActionsList->setCommitCallback(boost::bind(&LLPanelGroupRolesSubTab::updateActionDescription, this)); + + mRoleName->setCommitOnFocusLost(true); + mRoleName->setKeystrokeCallback(onPropertiesKey, this); + + mRoleTitle->setCommitOnFocusLost(true); + mRoleTitle->setKeystrokeCallback(onPropertiesKey, this); + + mRoleDescription->setCommitOnFocusLost(true); + mRoleDescription->setKeystrokeCallback(boost::bind(&LLPanelGroupRolesSubTab::onDescriptionKeyStroke, this, _1)); + + setFooterEnabled(false); + + return true; +} + +void LLPanelGroupRolesSubTab::activate() +{ + LLPanelGroupSubTab::activate(); + + mActionDescription->clear(); + mRolesList->deselectAllItems(); + mAssignedMembersList->deleteAllItems(); + mAllowedActionsList->deleteAllItems(); + mRoleName->clear(); + mRoleDescription->clear(); + mRoleTitle->clear(); + + setFooterEnabled(false); + + mHasRoleChange = false; + update(GC_ALL); +} + +void LLPanelGroupRolesSubTab::deactivate() +{ + LL_DEBUGS() << "LLPanelGroupRolesSubTab::deactivate()" << LL_ENDL; + + LLPanelGroupSubTab::deactivate(); + mFirstOpen = false; +} + +bool LLPanelGroupRolesSubTab::needsApply(std::string& mesg) +{ + LL_DEBUGS() << "LLPanelGroupRolesSubTab::needsApply()" << LL_ENDL; + + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + if(!gdatap) + { + LL_WARNS() << "Unable to get group data for group " << mGroupID << LL_ENDL; + return false; + } + + + return (mHasRoleChange // Text changed in current role + || (gdatap && gdatap->pendingRoleChanges())); // Pending role changes in the group +} + +bool LLPanelGroupRolesSubTab::apply(std::string& mesg) +{ + LL_DEBUGS() << "LLPanelGroupRolesSubTab::apply()" << LL_ENDL; + + saveRoleChanges(true); + mFirstOpen = false; + LLGroupMgr::getInstance()->sendGroupRoleChanges(mGroupID); + + notifyObservers(); + + return true; +} + +void LLPanelGroupRolesSubTab::cancel() +{ + mHasRoleChange = false; + LLGroupMgr::getInstance()->cancelGroupRoleChanges(mGroupID); + + notifyObservers(); +} + +LLSD LLPanelGroupRolesSubTab::createRoleItem(const LLUUID& role_id, + std::string name, + std::string title, + S32 members) +{ + LLSD row; + row["id"] = role_id; + + row["columns"][0]["column"] = "name"; + row["columns"][0]["value"] = name; + + row["columns"][1]["column"] = "title"; + row["columns"][1]["value"] = title; + + row["columns"][2]["column"] = "members"; + row["columns"][2]["value"] = members; + + return row; +} + +bool LLPanelGroupRolesSubTab::matchesSearchFilter(std::string rolename, std::string roletitle) +{ + // If the search filter is empty, everything passes. + if (mSearchFilter.empty()) return true; + + LLStringUtil::toLower(rolename); + LLStringUtil::toLower(roletitle); + std::string::size_type match_name = rolename.find(mSearchFilter); + std::string::size_type match_title = roletitle.find(mSearchFilter); + + if ( (std::string::npos == match_name) + && (std::string::npos == match_title)) + { + // not found + return false; + } + else + { + return true; + } +} + +void LLPanelGroupRolesSubTab::update(LLGroupChange gc) +{ + LL_DEBUGS() << "LLPanelGroupRolesSubTab::update()" << LL_ENDL; + + if (mGroupID.isNull()) return; + + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + + if (!gdatap || !gdatap->isRoleDataComplete()) + { + LLGroupMgr::getInstance()->sendGroupRoleDataRequest(mGroupID); + } + else + { + bool had_selection = false; + LLUUID last_selected; + if (mRolesList->getFirstSelected()) + { + last_selected = mRolesList->getFirstSelected()->getUUID(); + had_selection = true; + } + mRolesList->deleteAllItems(); + + LLScrollListItem* item = NULL; + + LLGroupMgrGroupData::role_list_t::iterator rit = gdatap->mRoles.begin(); + LLGroupMgrGroupData::role_list_t::iterator end = gdatap->mRoles.end(); + + for ( ; rit != end; ++rit) + { + LLRoleData rd; + if (gdatap->getRoleData((*rit).first,rd)) + { + if (matchesSearchFilter(rd.mRoleName, rd.mRoleTitle)) + { + // If this is the everyone role, then EVERYONE is in it. + S32 members_in_role = (*rit).first.isNull() ? gdatap->mMembers.size() : (*rit).second->getTotalMembersInRole(); + LLSD row = createRoleItem((*rit).first,rd.mRoleName, rd.mRoleTitle, members_in_role); + item = mRolesList->addElement(row, ((*rit).first.isNull()) ? ADD_TOP : ADD_BOTTOM, this); + if (had_selection && ((*rit).first == last_selected)) + { + item->setSelected(true); + } + } + } + else + { + LL_WARNS() << "LLPanelGroupRolesSubTab::update() No role data for role " << (*rit).first << LL_ENDL; + } + } + + mRolesList->sortByColumn(std::string("name"), true); + + if ( (gdatap->mRoles.size() < (U32)MAX_ROLES) + && gAgent.hasPowerInGroup(mGroupID, GP_ROLE_CREATE) ) + { + mCreateRoleButton->setEnabled(true); + } + else + { + mCreateRoleButton->setEnabled(false); + } + + if (had_selection) + { + handleRoleSelect(); + } + else + { + mAssignedMembersList->deleteAllItems(); + mAllowedActionsList->deleteAllItems(); + mRoleName->clear(); + mRoleDescription->clear(); + mRoleTitle->clear(); + setFooterEnabled(false); + mDeleteRoleButton->setEnabled(false); + mCopyRoleButton->setEnabled(false); + } + } + + if ((GC_ROLE_MEMBER_DATA == gc || GC_MEMBER_DATA == gc) + && gdatap + && gdatap->isMemberDataComplete() + && gdatap->isRoleMemberDataComplete()) + { + buildMembersList(); + } +} + +// static +void LLPanelGroupRolesSubTab::onRoleSelect(LLUICtrl* ctrl, void* user_data) +{ + LLPanelGroupRolesSubTab* self = static_cast(user_data); + if (!self) + return; + + self->handleRoleSelect(); +} + +void LLPanelGroupRolesSubTab::handleRoleSelect() +{ + bool can_delete = true; + LL_DEBUGS() << "LLPanelGroupRolesSubTab::handleRoleSelect()" << LL_ENDL; + + mAssignedMembersList->deleteAllItems(); + mAllowedActionsList->deleteAllItems(); + + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + if (!gdatap) + { + LL_WARNS() << "LLPanelGroupRolesSubTab::handleRoleSelect() " + << "-- No group data!" << LL_ENDL; + return; + } + + saveRoleChanges(false); + + // Check if there is anything selected. + LLScrollListItem* item = mRolesList->getFirstSelected(); + if (!item) + { + setFooterEnabled(false); + return; + } + + setFooterEnabled(true); + + LLRoleData rd; + if (gdatap->getRoleData(item->getUUID(),rd)) + { + bool is_owner_role = ( gdatap->mOwnerRole == item->getUUID() ); + mRoleName->setText(rd.mRoleName); + mRoleTitle->setText(rd.mRoleTitle); + mRoleDescription->setText(rd.mRoleDescription); + + mAllowedActionsList->setEnabled(gAgent.hasPowerInGroup(mGroupID, + GP_ROLE_CHANGE_ACTIONS)); + buildActionsList(mAllowedActionsList, + rd.mRolePowers, + 0LL, + boost::bind(&LLPanelGroupRolesSubTab::handleActionCheck, this, _1, false), + true, + false, + is_owner_role); + + + mMemberVisibleCheck->set((rd.mRolePowers & GP_MEMBER_VISIBLE_IN_DIR) == GP_MEMBER_VISIBLE_IN_DIR); + mRoleName->setEnabled(!is_owner_role && + gAgent.hasPowerInGroup(mGroupID, GP_ROLE_PROPERTIES)); + mRoleTitle->setEnabled(gAgent.hasPowerInGroup(mGroupID, GP_ROLE_PROPERTIES)); + mRoleDescription->setEnabled(gAgent.hasPowerInGroup(mGroupID, GP_ROLE_PROPERTIES)); + + if ( is_owner_role ) + { + // you can't delete the owner role + can_delete = false; + // ... or hide members with this role + mMemberVisibleCheck->setEnabled(false); + } + else + { + mMemberVisibleCheck->setEnabled(gAgent.hasPowerInGroup(mGroupID, GP_ROLE_PROPERTIES)); + } + + if (item->getUUID().isNull()) + { + // Everyone role, can't edit description or name or delete + mRoleDescription->setEnabled(false); + mRoleName->setEnabled(false); + can_delete = false; + } + } + else + { + mRolesList->deselectAllItems(); + mAssignedMembersList->deleteAllItems(); + mAllowedActionsList->deleteAllItems(); + mRoleName->clear(); + mRoleDescription->clear(); + mRoleTitle->clear(); + setFooterEnabled(false); + + can_delete = false; + } + mSelectedRole = item->getUUID(); + buildMembersList(); + + mCopyRoleButton->setEnabled(gAgent.hasPowerInGroup(mGroupID, GP_ROLE_CREATE)); + can_delete = can_delete && gAgent.hasPowerInGroup(mGroupID, + GP_ROLE_DELETE); + mDeleteRoleButton->setEnabled(can_delete); +} + +void LLPanelGroupRolesSubTab::buildMembersList() +{ + mAssignedMembersList->deleteAllItems(); + + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + if (!gdatap) + { + LL_WARNS() << "LLPanelGroupRolesSubTab::handleRoleSelect() " + << "-- No group data!" << LL_ENDL; + return; + } + + // Check if there is anything selected. + LLScrollListItem* item = mRolesList->getFirstSelected(); + if (!item) return; + + if (item->getUUID().isNull()) + { + // Special cased 'Everyone' role + LLGroupMgrGroupData::member_list_t::iterator mit = gdatap->mMembers.begin(); + LLGroupMgrGroupData::member_list_t::iterator end = gdatap->mMembers.end(); + for ( ; mit != end; ++mit) + { + mAssignedMembersList->addNameItem((*mit).first); + } + } + else + { + LLGroupMgrGroupData::role_list_t::iterator rit = gdatap->mRoles.find(item->getUUID()); + if (rit != gdatap->mRoles.end()) + { + LLGroupRoleData* rdatap = (*rit).second; + if (rdatap) + { + uuid_vec_t::const_iterator mit = rdatap->getMembersBegin(); + uuid_vec_t::const_iterator end = rdatap->getMembersEnd(); + for ( ; mit != end; ++mit) + { + mAssignedMembersList->addNameItem((*mit)); + } + } + } + } +} + +struct ActionCBData +{ + LLPanelGroupRolesSubTab* mSelf; + LLCheckBoxCtrl* mCheck; +}; + +void LLPanelGroupRolesSubTab::handleActionCheck(LLUICtrl* ctrl, bool force) +{ + LLCheckBoxCtrl* check = dynamic_cast(ctrl); + if (!check) + return; + + LL_DEBUGS() << "LLPanelGroupRolesSubTab::handleActionSelect()" << LL_ENDL; + + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + if (!gdatap) + { + LL_WARNS() << "LLPanelGroupRolesSubTab::handleRoleSelect() " + << "-- No group data!" << LL_ENDL; + return; + } + + LLScrollListItem* action_item = mAllowedActionsList->getFirstSelected(); + if (!action_item) + { + return; + } + + LLScrollListItem* role_item = mRolesList->getFirstSelected(); + if (!role_item) + { + return; + } + LLUUID role_id = role_item->getUUID(); + + LLRoleAction* rap = (LLRoleAction*)action_item->getUserdata(); + U64 power = rap->mPowerBit; + + bool isEnablingAbility = check->get(); + LLRoleData rd; + LLSD args; + + if (isEnablingAbility && + !force && + ((GP_ROLE_ASSIGN_MEMBER == power) || (GP_ROLE_CHANGE_ACTIONS == power) )) + { + // Uncheck the item, for now. It will be + // checked if they click 'Yes', below. + check->set(false); + + LLRoleData rd; + LLSD args; + + if ( gdatap->getRoleData(role_id, rd) ) + { + args["ACTION_NAME"] = rap->mDescription; + args["ROLE_NAME"] = rd.mRoleName; + mHasModal = true; + std::string warning = "AssignDangerousActionWarning"; + if (GP_ROLE_CHANGE_ACTIONS == power) + { + warning = "AssignDangerousAbilityWarning"; + } + LLNotificationsUtil::add(warning, args, LLSD(), boost::bind(&LLPanelGroupRolesSubTab::addActionCB, this, _1, _2, check)); + } + else + { + LL_WARNS() << "Unable to look up role information for role id: " + << role_id << LL_ENDL; + } + } + + if(GP_GROUP_BAN_ACCESS == power) + { + std::string warning = isEnablingAbility ? "AssignBanAbilityWarning" : "RemoveBanAbilityWarning"; + + ////////////////////////////////////////////////////////////////////////// + // Get role data for both GP_ROLE_REMOVE_MEMBER and GP_MEMBER_EJECT + // Add description and role name to LLSD + // Pop up dialog saying "Yo, you also granted these other abilities when you did this!" + if ( gdatap->getRoleData(role_id, rd) ) + { + args["ACTION_NAME"] = rap->mDescription; + args["ROLE_NAME"] = rd.mRoleName; + mHasModal = true; + + std::vector all_data = mAllowedActionsList->getAllData(); + std::vector::iterator ad_it = all_data.begin(); + std::vector::iterator ad_end = all_data.end(); + LLRoleAction* adp; + for( ; ad_it != ad_end; ++ad_it) + { + adp = (LLRoleAction*)(*ad_it)->getUserdata(); + if(adp->mPowerBit == GP_MEMBER_EJECT) + { + args["ACTION_NAME_2"] = adp->mDescription; + } + else if(adp->mPowerBit == GP_ROLE_REMOVE_MEMBER) + { + args["ACTION_NAME_3"] = adp->mDescription; + } + } + + LLNotificationsUtil::add(warning, args); + } + else + { + LL_WARNS() << "Unable to look up role information for role id: " + << role_id << LL_ENDL; + } + + ////////////////////////////////////////////////////////////////////////// + + U64 current_role_powers = gdatap->getRolePowers(role_id); + + if(isEnablingAbility) + { + power |= (GP_ROLE_REMOVE_MEMBER | GP_MEMBER_EJECT); + current_role_powers |= power; + } + else + { + current_role_powers &= ~GP_GROUP_BAN_ACCESS; + } + + mAllowedActionsList->deleteAllItems(); + buildActionsList( mAllowedActionsList, + current_role_powers, + current_role_powers, + boost::bind(&LLPanelGroupRolesSubTab::handleActionCheck, this, _1, false), + true, + false, + false); + + } + + ////////////////////////////////////////////////////////////////////////// + // Adding non-specific ability to role + ////////////////////////////////////////////////////////////////////////// + if(isEnablingAbility) + { + gdatap->addRolePower(role_id, power); + } + else + { + gdatap->removeRolePower(role_id,power); + } + + mHasRoleChange = true; + notifyObservers(); + +} + +bool LLPanelGroupRolesSubTab::addActionCB(const LLSD& notification, const LLSD& response, LLCheckBoxCtrl* check) +{ + if (!check) return false; + + mHasModal = false; + + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (0 == option) + { + // User clicked "Yes" + check->set(true); + const bool force_add = true; + handleActionCheck(check, force_add); + } + return false; +} + +// static +void LLPanelGroupRolesSubTab::onPropertiesKey(LLLineEditor* ctrl, void* user_data) +{ + LLPanelGroupRolesSubTab* self = static_cast(user_data); + if (!self) return; + + self->mHasRoleChange = true; + self->notifyObservers(); +} + +void LLPanelGroupRolesSubTab::onDescriptionKeyStroke(LLTextEditor* caller) +{ + mHasRoleChange = true; + notifyObservers(); +} + +// static +void LLPanelGroupRolesSubTab::onDescriptionCommit(LLUICtrl* ctrl, void* user_data) +{ + LLPanelGroupRolesSubTab* self = static_cast(user_data); + if (!self) return; + + self->mHasRoleChange = true; + self->notifyObservers(); +} + +// static +void LLPanelGroupRolesSubTab::onMemberVisibilityChange(LLUICtrl* ctrl, void* user_data) +{ + LLPanelGroupRolesSubTab* self = static_cast(user_data); + LLCheckBoxCtrl* check = static_cast(ctrl); + if (!check || !self) return; + + self->handleMemberVisibilityChange(check->get()); +} + +void LLPanelGroupRolesSubTab::handleMemberVisibilityChange(bool value) +{ + LL_DEBUGS() << "LLPanelGroupRolesSubTab::handleMemberVisibilityChange()" << LL_ENDL; + + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + if (!gdatap) + { + LL_WARNS() << "LLPanelGroupRolesSubTab::handleRoleSelect() " + << "-- No group data!" << LL_ENDL; + return; + } + + LLScrollListItem* role_item = mRolesList->getFirstSelected(); + if (!role_item) + { + return; + } + + if (value) + { + gdatap->addRolePower(role_item->getUUID(),GP_MEMBER_VISIBLE_IN_DIR); + } + else + { + gdatap->removeRolePower(role_item->getUUID(),GP_MEMBER_VISIBLE_IN_DIR); + } +} + +// static +void LLPanelGroupRolesSubTab::onCreateRole(void* user_data) +{ + LLPanelGroupRolesSubTab* self = static_cast(user_data); + if (!self) return; + + self->handleCreateRole(); +} + +void LLPanelGroupRolesSubTab::handleCreateRole() +{ + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + + if (!gdatap) return; + + LLUUID new_role_id; + new_role_id.generate(); + + LLRoleData rd; + rd.mRoleName = "New Role"; + gdatap->createRole(new_role_id,rd); + + mRolesList->deselectAllItems(true); + LLSD row; + row["id"] = new_role_id; + row["columns"][0]["column"] = "name"; + row["columns"][0]["value"] = rd.mRoleName; + mRolesList->addElement(row, ADD_BOTTOM, this); + mRolesList->selectByID(new_role_id); + + // put focus on name field and select its contents + if(mRoleName) + { + mRoleName->setFocus(true); + mRoleName->onTabInto(); + gFocusMgr.triggerFocusFlash(); + } + + notifyObservers(); +} + +// static +void LLPanelGroupRolesSubTab::onCopyRole(void* user_data) +{ + LLPanelGroupRolesSubTab* self = static_cast(user_data); + if (!self) return; + + self->handleCopyRole(); +} + +void LLPanelGroupRolesSubTab::handleCopyRole() +{ + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + + if (!gdatap) return; + + LLScrollListItem* role_item = mRolesList->getFirstSelected(); + if (!role_item || role_item->getUUID().isNull()) + { + return; + } + + LLRoleData rd; + if (!gdatap->getRoleData(role_item->getUUID(), rd)) + { + return; + } + + LLUUID new_role_id; + new_role_id.generate(); + rd.mRoleName += "(Copy)"; + gdatap->createRole(new_role_id,rd); + + mRolesList->deselectAllItems(true); + LLSD row; + row["id"] = new_role_id; + row["columns"][0]["column"] = "name"; + row["columns"][0]["value"] = rd.mRoleName; + mRolesList->addElement(row, ADD_BOTTOM, this); + mRolesList->selectByID(new_role_id); + + // put focus on name field and select its contents + if(mRoleName) + { + mRoleName->setFocus(true); + mRoleName->onTabInto(); + gFocusMgr.triggerFocusFlash(); + } + + notifyObservers(); +} + +// static +void LLPanelGroupRolesSubTab::onDeleteRole(void* user_data) +{ + LLPanelGroupRolesSubTab* self = static_cast(user_data); + if (!self) return; + + self->handleDeleteRole(); +} + +void LLPanelGroupRolesSubTab::handleDeleteRole() +{ + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + + if (!gdatap) return; + + LLScrollListItem* role_item = mRolesList->getFirstSelected(); + if (!role_item) + { + return; + } + + if (role_item->getUUID().isNull() || role_item->getUUID() == gdatap->mOwnerRole) + { + LLSD args; + args["MESSAGE"] = mRemoveEveryoneTxt; + LLNotificationsUtil::add("GenericAlert", args); + return; + } + + gdatap->deleteRole(role_item->getUUID()); + mRolesList->deleteSingleItem(mRolesList->getFirstSelectedIndex()); + mRolesList->selectFirstItem(); + + notifyObservers(); +} + +void LLPanelGroupRolesSubTab::saveRoleChanges(bool select_saved_role) +{ + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + + if (!gdatap) return; + + if (mHasRoleChange) + { + LLRoleData rd; + if (!gdatap->getRoleData(mSelectedRole,rd)) return; + + rd.mRoleName = mRoleName->getText(); + rd.mRoleDescription = mRoleDescription->getText(); + rd.mRoleTitle = mRoleTitle->getText(); + + S32 role_members_count = 0; + if (mSelectedRole.isNull()) + { + role_members_count = gdatap->mMemberCount; + } + else if(LLGroupRoleData* grd = get_ptr_in_map(gdatap->mRoles, mSelectedRole)) + { + role_members_count = grd->getTotalMembersInRole(); + } + + gdatap->setRoleData(mSelectedRole,rd); + + mRolesList->deleteSingleItem(mRolesList->getItemIndex(mSelectedRole)); + + LLSD row = createRoleItem(mSelectedRole,rd.mRoleName,rd.mRoleTitle,role_members_count); + LLScrollListItem* item = mRolesList->addElement(row, ADD_BOTTOM, this); + item->setSelected(select_saved_role); + + mHasRoleChange = false; + } +} + +void LLPanelGroupRolesSubTab::updateActionDescription() +{ + mActionDescription->setText(std::string()); + LLScrollListItem* action_item = mAllowedActionsList->getFirstSelected(); + if (!action_item || !mAllowedActionsList->getCanSelect()) + { + return; + } + + LLRoleAction* rap = (LLRoleAction*)action_item->getUserdata(); + if (rap) + { + std::string desc = rap->mLongDescription.empty() ? rap->mDescription : rap->mLongDescription; + mActionDescription->setText(desc); + } +} + +void LLPanelGroupRolesSubTab::setGroupID(const LLUUID& id) +{ + if(mRolesList) mRolesList->deleteAllItems(); + if(mAssignedMembersList) mAssignedMembersList->deleteAllItems(); + if(mAllowedActionsList) mAllowedActionsList->deleteAllItems(); + + if(mRoleName) mRoleName->clear(); + if(mRoleDescription) mRoleDescription->clear(); + if(mRoleTitle) mRoleTitle->clear(); + + mHasRoleChange = false; + + setFooterEnabled(false); + + LLPanelGroupSubTab::setGroupID(id); +} + + +// LLPanelGroupActionsSubTab ///////////////////////////////////////////// +static LLPanelInjector t_panel_group_actions_subtab("panel_group_actions_subtab"); + +LLPanelGroupActionsSubTab::LLPanelGroupActionsSubTab() +: LLPanelGroupSubTab() +{ +} + +LLPanelGroupActionsSubTab::~LLPanelGroupActionsSubTab() +{ +} + +bool LLPanelGroupActionsSubTab::postBuildSubTab(LLView* root) +{ + LLPanelGroupSubTab::postBuildSubTab(root); + + // Upcast parent so we can ask it for sibling controls. + LLPanelGroupRoles* parent = (LLPanelGroupRoles*) root; + + // Look recursively from the parent to find all our widgets. + bool recurse = true; + mHeader = parent->findChild("actions_header", recurse); + mFooter = parent->findChild("actions_footer", recurse); + + mActionDescription = parent->getChild("action_description", recurse); + + mActionList = parent->getChild("action_list",recurse); + mActionRoles = parent->getChild("action_roles",recurse); + mActionMembers = parent->getChild("action_members",recurse); + + if (!mActionList || !mActionDescription || !mActionRoles || !mActionMembers) return false; + + mActionList->setCommitOnSelectionChange(true); + mActionList->setCommitCallback(boost::bind(&LLPanelGroupActionsSubTab::handleActionSelect, this)); + mActionList->setContextMenu(LLScrollListCtrl::MENU_AVATAR); + + update(GC_ALL); + + return true; +} + +void LLPanelGroupActionsSubTab::activate() +{ + LLPanelGroupSubTab::activate(); + + update(GC_ALL); + mActionDescription->clear(); + mActionList->deselectAllItems(); + mActionList->deleteAllItems(); + buildActionsList(mActionList, + GP_ALL_POWERS, + GP_ALL_POWERS, + NULL, + false, + true, + false); +} + +void LLPanelGroupActionsSubTab::deactivate() +{ + LL_DEBUGS() << "LLPanelGroupActionsSubTab::deactivate()" << LL_ENDL; + + LLPanelGroupSubTab::deactivate(); +} + +bool LLPanelGroupActionsSubTab::needsApply(std::string& mesg) +{ + LL_DEBUGS() << "LLPanelGroupActionsSubTab::needsApply()" << LL_ENDL; + + return false; +} + +bool LLPanelGroupActionsSubTab::apply(std::string& mesg) +{ + LL_DEBUGS() << "LLPanelGroupActionsSubTab::apply()" << LL_ENDL; + return true; +} + +void LLPanelGroupActionsSubTab::update(LLGroupChange gc) +{ + LL_DEBUGS() << "LLPanelGroupActionsSubTab::update()" << LL_ENDL; + + if (mGroupID.isNull()) return; + + mActionMembers->deleteAllItems(); + mActionRoles->deleteAllItems(); + + if(mActionList->hasSelectedItem()) + { + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + if (gdatap && gdatap->isMemberDataComplete() && gdatap->isRoleDataComplete()) + { + handleActionSelect(); + } + } +} + +void LLPanelGroupActionsSubTab::onFilterChanged() +{ + mActionDescription->clear(); + mActionList->deselectAllItems(); + mActionList->deleteAllItems(); + buildActionsList(mActionList, + GP_ALL_POWERS, + GP_ALL_POWERS, + NULL, + false, + true, + false); +} + +void LLPanelGroupActionsSubTab::handleActionSelect() +{ + mActionMembers->deleteAllItems(); + mActionRoles->deleteAllItems(); + + U64 power_mask = GP_NO_POWERS; + std::vector selection = + mActionList->getAllSelected(); + if (selection.empty()) return; + + LLRoleAction* rap; + + std::vector::iterator itor; + for (itor = selection.begin() ; + itor != selection.end(); ++itor) + { + rap = (LLRoleAction*)( (*itor)->getUserdata() ); + power_mask |= rap->mPowerBit; + } + + if (selection.size() == 1) + { + LLScrollListItem* item = selection[0]; + rap = (LLRoleAction*)(item->getUserdata()); + + if (rap->mLongDescription.empty()) + { + mActionDescription->setText(rap->mDescription); + } + else + { + mActionDescription->setText(rap->mLongDescription); + } + } + else + { + mActionDescription->clear(); + } + + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + + if (!gdatap) return; + + if (gdatap->isMemberDataComplete()) + { + LLGroupMgrGroupData::member_list_t::iterator it = gdatap->mMembers.begin(); + LLGroupMgrGroupData::member_list_t::iterator end = gdatap->mMembers.end(); + LLGroupMemberData* gmd; + + for ( ; it != end; ++it) + { + gmd = (*it).second; + if (!gmd) continue; + if ((gmd->getAgentPowers() & power_mask) == power_mask) + { + mActionMembers->addNameItem(gmd->getID()); + } + } + } + else + { + LLGroupMgr::getInstance()->sendCapGroupMembersRequest(mGroupID); + } + + if (gdatap->isRoleDataComplete()) + { + LLGroupMgrGroupData::role_list_t::iterator it = gdatap->mRoles.begin(); + LLGroupMgrGroupData::role_list_t::iterator end = gdatap->mRoles.end(); + LLGroupRoleData* rmd; + + for ( ; it != end; ++it) + { + rmd = (*it).second; + if (!rmd) continue; + if ((rmd->getRoleData().mRolePowers & power_mask) == power_mask) + { + mActionRoles->addSimpleElement(rmd->getRoleData().mRoleName); + } + } + } + else + { + LLGroupMgr::getInstance()->sendGroupRoleDataRequest(mGroupID); + } +} + +void LLPanelGroupActionsSubTab::setGroupID(const LLUUID& id) +{ + if(mActionList) mActionList->deleteAllItems(); + if(mActionRoles) mActionRoles->deleteAllItems(); + if(mActionMembers) mActionMembers->deleteAllItems(); + + if(mActionDescription) mActionDescription->clear(); + + LLPanelGroupSubTab::setGroupID(id); +} + + +// LLPanelGroupBanListSubTab ///////////////////////////////////////////// +static LLPanelInjector t_panel_group_ban_subtab("panel_group_banlist_subtab"); + +LLPanelGroupBanListSubTab::LLPanelGroupBanListSubTab() + : LLPanelGroupSubTab(), + mBanList(NULL), + mCreateBanButton(NULL), + mDeleteBanButton(NULL) +{} + +bool LLPanelGroupBanListSubTab::postBuildSubTab(LLView* root) +{ + LLPanelGroupSubTab::postBuildSubTab(root); + + // Upcast parent so we can ask it for sibling controls. + LLPanelGroupRoles* parent = (LLPanelGroupRoles*)root; + + // Look recursively from the parent to find all our widgets. + bool recurse = true; + + mHeader = parent->findChild("banlist_header", recurse); + mFooter = parent->findChild("banlist_footer", recurse); + + mBanList = parent->getChild("ban_list", recurse); + + mCreateBanButton = parent->getChild("ban_create", recurse); + mDeleteBanButton = parent->getChild("ban_delete", recurse); + mRefreshBanListButton = parent->getChild("ban_refresh", recurse); + mBanCountText = parent->getChild("ban_count", recurse); + + if(!mBanList || !mCreateBanButton || !mDeleteBanButton || !mRefreshBanListButton || !mBanCountText) + return false; + + mBanList->setCommitOnSelectionChange(true); + mBanList->setCommitCallback(onBanEntrySelect, this); + + mCreateBanButton->setClickedCallback(onCreateBanEntry, this); + mCreateBanButton->setEnabled(false); + + mDeleteBanButton->setClickedCallback(onDeleteBanEntry, this); + mDeleteBanButton->setEnabled(false); + + mRefreshBanListButton->setClickedCallback(onRefreshBanList, this); + mRefreshBanListButton->setEnabled(false); + + setBanCount(0); + + mBanList->setOnNameListCompleteCallback(boost::bind(&LLPanelGroupBanListSubTab::onBanListCompleted, this, _1)); + + populateBanList(); + + setFooterEnabled(false); + return true; +} + +void LLPanelGroupBanListSubTab::activate() +{ + LLPanelGroupSubTab::activate(); + + mBanList->deselectAllItems(); + mDeleteBanButton->setEnabled(false); + + LLGroupMgrGroupData * group_datap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + if (group_datap) + { + mCreateBanButton->setEnabled(gAgent.hasPowerInGroup(mGroupID, GP_GROUP_BAN_ACCESS) && + group_datap->mBanList.size() < GB_MAX_BANNED_AGENTS); + setBanCount(group_datap->mBanList.size()); + } + else + { + mCreateBanButton->setEnabled(false); + setBanCount(0); + } + + // BAKER: Should I really request everytime activate() is called? + // Perhaps I should only do it on a force refresh, or if an action on the list happens... + // Because it's not going to live-update the list anyway... You'd have to refresh if you + // wanted to see someone else's additions anyway... + // + LLGroupMgr::getInstance()->sendGroupBanRequest(LLGroupMgr::REQUEST_GET, mGroupID); + + setFooterEnabled(false); + update(GC_ALL); +} + +void LLPanelGroupBanListSubTab::update(LLGroupChange gc) +{ + populateBanList(); +} + +void LLPanelGroupBanListSubTab::draw() +{ + LLPanelGroupSubTab::draw(); + + // BAKER: Might be good to put it here instead of update, maybe.. See how often draw gets hit. + //if( + // populateBanList(); +} + +void LLPanelGroupBanListSubTab::onBanEntrySelect(LLUICtrl* ctrl, void* user_data) +{ + LLPanelGroupBanListSubTab* self = static_cast(user_data); + if (!self) + return; + + self->handleBanEntrySelect(); +} + +void LLPanelGroupBanListSubTab::handleBanEntrySelect() +{ + if (gAgent.hasPowerInGroup(mGroupID, GP_GROUP_BAN_ACCESS)) + { + mDeleteBanButton->setEnabled(true); + } +} + +void LLPanelGroupBanListSubTab::onCreateBanEntry(void* user_data) +{ + LLPanelGroupBanListSubTab* self = static_cast(user_data); + if (!self) + return; + + self->handleCreateBanEntry(); +} + +void LLPanelGroupBanListSubTab::handleCreateBanEntry() +{ + LLFloaterGroupBulkBan::showForGroup(mGroupID); + //populateBanList(); +} + +void LLPanelGroupBanListSubTab::onDeleteBanEntry(void* user_data) +{ + LLPanelGroupBanListSubTab* self = static_cast(user_data); + if (!self) + return; + + self->handleDeleteBanEntry(); +} + +void LLPanelGroupBanListSubTab::handleDeleteBanEntry() +{ + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + if(!gdatap) + { + LL_WARNS("Groups") << "Unable to get group data for group " << mGroupID << LL_ENDL; + return; + } + + std::vector selection = mBanList->getAllSelected(); + if(selection.empty()) + { + return; + } + + bool can_ban_members = false; + if (gAgent.isGodlike() || + gAgent.hasPowerInGroup(mGroupID, GP_GROUP_BAN_ACCESS)) + { + can_ban_members = true; + } + + // Owners can ban anyone in the group. + LLGroupMgrGroupData::member_list_t::iterator mi = gdatap->mMembers.find(gAgent.getID()); + if (mi != gdatap->mMembers.end()) + { + LLGroupMemberData* member_data = (*mi).second; + if ( member_data && member_data->isInRole(gdatap->mOwnerRole) ) + { + can_ban_members = true; + } + } + + if(!can_ban_members) + return; + + std::vector ban_ids; + std::vector::iterator itor; + for(itor = selection.begin(); itor != selection.end(); ++itor) + { + LLUUID ban_id = (*itor)->getUUID(); + ban_ids.push_back(ban_id); + + gdatap->removeBanEntry(ban_id); + mBanList->removeNameItem(ban_id); + + // Removing an item removes the selection, we shouldn't be able to click + // the button anymore until we reselect another entry. + mDeleteBanButton->setEnabled(false); + } + + // update ban-count related elements + mCreateBanButton->setEnabled(true); + setBanCount(gdatap->mBanList.size()); + + LLGroupMgr::getInstance()->sendGroupBanRequest(LLGroupMgr::REQUEST_POST, mGroupID, LLGroupMgr::BAN_DELETE, ban_ids); +} + +void LLPanelGroupBanListSubTab::onRefreshBanList(void* user_data) +{ + LLPanelGroupBanListSubTab* self = static_cast(user_data); + if (!self) + return; + + self->handleRefreshBanList(); +} + +void LLPanelGroupBanListSubTab::handleRefreshBanList() +{ + mRefreshBanListButton->setEnabled(false); + LLGroupMgr::getInstance()->sendGroupBanRequest(LLGroupMgr::REQUEST_GET, mGroupID); +} + +void LLPanelGroupBanListSubTab::onBanListCompleted(bool isComplete) +{ + if(isComplete) + { + mRefreshBanListButton->setEnabled(true); + populateBanList(); + } +} + +void LLPanelGroupBanListSubTab::setBanCount(U32 ban_count) +{ + LLStringUtil::format_map_t args; + args["[COUNT]"] = llformat("%d", ban_count); + args["[LIMIT]"] = llformat("%d", GB_MAX_BANNED_AGENTS); + mBanCountText->setText(getString("ban_count_template", args)); +} + +void LLPanelGroupBanListSubTab::populateBanList() +{ + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(mGroupID); + if(!gdatap) + { + LL_WARNS("Groups") << "Unable to get group data for group " << mGroupID << LL_ENDL; + return; + } + + mBanList->deleteAllItems(); + std::map::const_iterator entry = gdatap->mBanList.begin(); + for(; entry != gdatap->mBanList.end(); entry++) + { + LLNameListCtrl::NameItem ban_entry; + ban_entry.value = entry->first; + LLGroupBanData bd = entry->second; + + ban_entry.columns.add().column("name").font.name("SANSSERIF_SMALL").style("NORMAL"); + + // Baker TODO: MAINT- + // Check out utc_to_pacific_time() + + std::string ban_date_str = bd.mBanDate.toHTTPDateString("%Y/%m/%d"); +// time_t utc_time; +// utc_time = time_corrected(); +// LLSD substitution; +// substitution["datetime"] = (S32) utc_time; +// LLStringUtil::format (ban_date_str, substitution); + + //LL_INFOS("BAKER") << "[BAKER] BAN_DATE: " << bd.mBanDate.toHTTPDateString("%Y/%m/%d") << LL_ENDL; + //LL_INFOS("BAKER") << "[BAKER] BAN_DATE_MODIFIED: " << ban_date_str << LL_ENDL; + + //ban_entry.columns.add().column("ban_date").value(ban_date_str.font.name("SANSSERIF_SMALL").style("NORMAL"); + ban_entry.columns.add().column("ban_date").value(bd.mBanDate.toHTTPDateString("%Y/%m/%d")).font.name("SANSSERIF_SMALL").style("NORMAL"); + + mBanList->addNameItemRow(ban_entry); + } + + mRefreshBanListButton->setEnabled(true); + mCreateBanButton->setEnabled(gAgent.hasPowerInGroup(mGroupID, GP_GROUP_BAN_ACCESS) && + gdatap->mBanList.size() < GB_MAX_BANNED_AGENTS); + setBanCount(gdatap->mBanList.size()); +} + +void LLPanelGroupBanListSubTab::setGroupID(const LLUUID& id) +{ + if(mBanList) + mBanList->deleteAllItems(); + + setFooterEnabled(false); + LLPanelGroupSubTab::setGroupID(id); +} diff --git a/indra/newview/llpanelgrouproles.h b/indra/newview/llpanelgrouproles.h index cd995c874e..e320efa1c7 100644 --- a/indra/newview/llpanelgrouproles.h +++ b/indra/newview/llpanelgrouproles.h @@ -1,381 +1,381 @@ -/** - * @file llpanelgrouproles.h - * @brief Panel for roles information about a particular group. - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELGROUPROLES_H -#define LL_LLPANELGROUPROLES_H - -#include "llpanelgroup.h" - -class LLFilterEditor; -class LLNameListCtrl; -class LLPanelGroupSubTab; -class LLPanelGroupMembersSubTab; -class LLPanelGroupRolesSubTab; -class LLPanelGroupActionsSubTab; -class LLScrollListCtrl; -class LLScrollListItem; -class LLTextEditor; - -typedef std::map icon_map_t; - - -class LLPanelGroupRoles : public LLPanelGroupTab -{ -public: - LLPanelGroupRoles(); - virtual ~LLPanelGroupRoles(); - - // Allow sub tabs to ask for sibling controls. - friend class LLPanelGroupMembersSubTab; - friend class LLPanelGroupRolesSubTab; - friend class LLPanelGroupActionsSubTab; - - virtual bool postBuild(); - virtual bool isVisibleByAgent(LLAgent* agentp); - - - bool handleSubTabSwitch(const LLSD& data); - - // Checks if the current tab needs to be applied, and tries to switch to the requested tab. - bool attemptTransition(); - - // Switches to the requested tab (will close() if requested is NULL) - void transitionToTab(); - - // Used by attemptTransition to query the user's response to a tab that needs to apply. - bool handleNotifyCallback(const LLSD& notification, const LLSD& response); - bool onModalClose(const LLSD& notification, const LLSD& response); - - // Most of these messages are just passed on to the current sub-tab. - virtual void activate(); - virtual void deactivate(); - virtual bool needsApply(std::string& mesg); - virtual bool hasModal(); - virtual bool apply(std::string& mesg); - virtual void cancel(); - virtual void update(LLGroupChange gc); - - virtual void setGroupID(const LLUUID& id); - -protected: - LLPanelGroupTab* mCurrentTab; - LLPanelGroupTab* mRequestedTab; - LLTabContainer* mSubTabContainer; - bool mFirstUse; - - std::string mDefaultNeedsApplyMesg; - std::string mWantApplyMesg; -}; - - -class LLPanelGroupSubTab : public LLPanelGroupTab -{ -public: - LLPanelGroupSubTab(); - virtual ~LLPanelGroupSubTab(); - - virtual bool postBuild(); - - // This allows sub-tabs to collect child widgets from a higher level in the view hierarchy. - virtual bool postBuildSubTab(LLView* root); - - virtual void setSearchFilter( const std::string& filter ); - - virtual void activate(); - virtual void deactivate(); - - // Helper functions - bool matchesActionSearchFilter(std::string action); - - - void setFooterEnabled(bool enable); - - virtual void setGroupID(const LLUUID& id); -protected: - void buildActionsList(LLScrollListCtrl* ctrl, - U64 allowed_by_some, - U64 allowed_by_all, - LLUICtrl::commit_callback_t commit_callback, - bool show_all, - bool filter, - bool is_owner_role); - void buildActionCategory(LLScrollListCtrl* ctrl, - U64 allowed_by_some, - U64 allowed_by_all, - LLRoleActionSet* action_set, - LLUICtrl::commit_callback_t commit_callback, - bool show_all, - bool filter, - bool is_owner_role); - -protected: - LLPanel* mHeader; // Might not be present in xui of derived class (NULL) - LLPanel* mFooter; - - LLFilterEditor* mSearchEditor; - boost::signals2::connection mSearchCommitConnection; - - std::string mSearchFilter; - - icon_map_t mActionIcons; - - bool mActivated; - - bool mHasGroupBanPower; // Used to communicate between action sets due to the dependency between - // GP_GROUP_BAN_ACCESS and GP_EJECT_MEMBER and GP_ROLE_REMOVE_MEMBER - - void setOthersVisible(bool b); -}; - - -class LLPanelGroupMembersSubTab : public LLPanelGroupSubTab -{ -public: - LLPanelGroupMembersSubTab(); - virtual ~LLPanelGroupMembersSubTab(); - - virtual bool postBuildSubTab(LLView* root); - - static void onMemberSelect(LLUICtrl*, void*); - void handleMemberSelect(); - - static void onMemberDoubleClick(void*); - void handleMemberDoubleClick(); - - static void onInviteMember(void*); - void handleInviteMember(); - - static void onEjectMembers(void*); - void handleEjectMembers(); - void sendEjectNotifications(const LLUUID& group_id, const uuid_vec_t& selected_members); - bool handleEjectCallback(const LLSD& notification, const LLSD& response); - void confirmEjectMembers(); - - static void onRoleCheck(LLUICtrl* check, void* user_data); - void handleRoleCheck(const LLUUID& role_id, - LLRoleMemberChangeType type); - - static void onBanMember(void* user_data); - void handleBanMember(); - bool handleBanCallback(const LLSD& notification, const LLSD& response); - void confirmBanMembers(); - - void updateActionDescription(); - - void applyMemberChanges(); - bool addOwnerCB(const LLSD& notification, const LLSD& response); - - virtual void activate(); - virtual void deactivate(); - virtual void cancel(); - virtual bool needsApply(std::string& mesg); - virtual bool apply(std::string& mesg); - virtual void update(LLGroupChange gc); - void updateMembers(); - - virtual void draw(); - - virtual void setGroupID(const LLUUID& id); - - void addMemberToList(LLGroupMemberData* data); - void onNameCache(const LLUUID& update_id, LLGroupMemberData* member, const LLAvatarName& av_name, const LLUUID& av_id); - -protected: - typedef std::map role_change_data_map_t; - typedef std::map member_role_changes_map_t; - - bool matchesSearchFilter(const std::string& fullname); - - U64 getAgentPowersBasedOnRoleChanges(const LLUUID& agent_id); - bool getRoleChangeType(const LLUUID& member_id, - const LLUUID& role_id, - LLRoleMemberChangeType& type); - - LLNameListCtrl* mMembersList; - LLScrollListCtrl* mAssignedRolesList; - LLScrollListCtrl* mAllowedActionsList; - LLButton* mEjectBtn; - LLButton* mBanBtn; - - bool mChanged; - bool mPendingMemberUpdate; - bool mHasMatch; - - LLTextEditor* mActionDescription; - - member_role_changes_map_t mMemberRoleChangeData; - U32 mNumOwnerAdditions; - - LLGroupMgrGroupData::member_list_t::iterator mMemberProgress; - typedef std::map avatar_name_cache_connection_map_t; - avatar_name_cache_connection_map_t mAvatarNameCacheConnections; -}; - - -class LLPanelGroupRolesSubTab : public LLPanelGroupSubTab -{ -public: - LLPanelGroupRolesSubTab(); - virtual ~LLPanelGroupRolesSubTab(); - - virtual bool postBuildSubTab(LLView* root); - - virtual void activate(); - virtual void deactivate(); - virtual bool needsApply(std::string& mesg); - virtual bool apply(std::string& mesg); - virtual void cancel(); - bool matchesSearchFilter(std::string rolename, std::string roletitle); - virtual void update(LLGroupChange gc); - - static void onRoleSelect(LLUICtrl*, void*); - void handleRoleSelect(); - void buildMembersList(); - - static void onActionCheck(LLUICtrl*, void*); - bool addActionCB(const LLSD& notification, const LLSD& response, LLCheckBoxCtrl* check); - - static void onPropertiesKey(LLLineEditor*, void*); - - void onDescriptionKeyStroke(LLTextEditor* caller); - - static void onDescriptionCommit(LLUICtrl*, void*); - - static void onMemberVisibilityChange(LLUICtrl*, void*); - void handleMemberVisibilityChange(bool value); - - static void onCreateRole(void*); - void handleCreateRole(); - - static void onCopyRole(void*); - void handleCopyRole(); - - static void onDeleteRole(void*); - void handleDeleteRole(); - - void updateActionDescription(); - - void saveRoleChanges(bool select_saved_role); - - virtual void setGroupID(const LLUUID& id); - - bool mFirstOpen; - -protected: - void handleActionCheck(LLUICtrl* ctrl, bool force); - LLSD createRoleItem(const LLUUID& role_id, std::string name, std::string title, S32 members); - - LLScrollListCtrl* mRolesList; - LLNameListCtrl* mAssignedMembersList; - LLScrollListCtrl* mAllowedActionsList; - LLTextEditor* mActionDescription; - - LLLineEditor* mRoleName; - LLLineEditor* mRoleTitle; - LLTextEditor* mRoleDescription; - - LLCheckBoxCtrl* mMemberVisibleCheck; - LLButton* mDeleteRoleButton; - LLButton* mCreateRoleButton; - LLButton* mCopyRoleButton; - - LLUUID mSelectedRole; - bool mHasRoleChange; - std::string mRemoveEveryoneTxt; -}; - - -class LLPanelGroupActionsSubTab : public LLPanelGroupSubTab -{ -public: - LLPanelGroupActionsSubTab(); - virtual ~LLPanelGroupActionsSubTab(); - - virtual bool postBuildSubTab(LLView* root); - - - virtual void activate(); - virtual void deactivate(); - virtual bool needsApply(std::string& mesg); - virtual bool apply(std::string& mesg); - virtual void update(LLGroupChange gc); - virtual void onFilterChanged(); - - void handleActionSelect(); - - virtual void setGroupID(const LLUUID& id); -protected: - LLScrollListCtrl* mActionList; - LLScrollListCtrl* mActionRoles; - LLNameListCtrl* mActionMembers; - - LLTextEditor* mActionDescription; -}; - - -class LLPanelGroupBanListSubTab : public LLPanelGroupSubTab -{ -public: - LLPanelGroupBanListSubTab(); - virtual ~LLPanelGroupBanListSubTab() {} - - virtual bool postBuildSubTab(LLView* root); - - virtual void activate(); - virtual void update(LLGroupChange gc); - virtual void draw(); - - static void onBanEntrySelect(LLUICtrl* ctrl, void* user_data); - void handleBanEntrySelect(); - - static void onCreateBanEntry(void* user_data); - void handleCreateBanEntry(); - - static void onDeleteBanEntry(void* user_data); - void handleDeleteBanEntry(); - - static void onRefreshBanList(void* user_data); - void handleRefreshBanList(); - - void onBanListCompleted(bool isComplete); - -protected: - void setBanCount(U32 ban_count); - void populateBanList(); - -public: - virtual void setGroupID(const LLUUID& id); - -protected: - LLNameListCtrl* mBanList; - LLButton* mCreateBanButton; - LLButton* mDeleteBanButton; - LLButton* mRefreshBanListButton; - LLTextBase* mBanCountText; - -}; - -#endif // LL_LLPANELGROUPROLES_H +/** + * @file llpanelgrouproles.h + * @brief Panel for roles information about a particular group. + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELGROUPROLES_H +#define LL_LLPANELGROUPROLES_H + +#include "llpanelgroup.h" + +class LLFilterEditor; +class LLNameListCtrl; +class LLPanelGroupSubTab; +class LLPanelGroupMembersSubTab; +class LLPanelGroupRolesSubTab; +class LLPanelGroupActionsSubTab; +class LLScrollListCtrl; +class LLScrollListItem; +class LLTextEditor; + +typedef std::map icon_map_t; + + +class LLPanelGroupRoles : public LLPanelGroupTab +{ +public: + LLPanelGroupRoles(); + virtual ~LLPanelGroupRoles(); + + // Allow sub tabs to ask for sibling controls. + friend class LLPanelGroupMembersSubTab; + friend class LLPanelGroupRolesSubTab; + friend class LLPanelGroupActionsSubTab; + + virtual bool postBuild(); + virtual bool isVisibleByAgent(LLAgent* agentp); + + + bool handleSubTabSwitch(const LLSD& data); + + // Checks if the current tab needs to be applied, and tries to switch to the requested tab. + bool attemptTransition(); + + // Switches to the requested tab (will close() if requested is NULL) + void transitionToTab(); + + // Used by attemptTransition to query the user's response to a tab that needs to apply. + bool handleNotifyCallback(const LLSD& notification, const LLSD& response); + bool onModalClose(const LLSD& notification, const LLSD& response); + + // Most of these messages are just passed on to the current sub-tab. + virtual void activate(); + virtual void deactivate(); + virtual bool needsApply(std::string& mesg); + virtual bool hasModal(); + virtual bool apply(std::string& mesg); + virtual void cancel(); + virtual void update(LLGroupChange gc); + + virtual void setGroupID(const LLUUID& id); + +protected: + LLPanelGroupTab* mCurrentTab; + LLPanelGroupTab* mRequestedTab; + LLTabContainer* mSubTabContainer; + bool mFirstUse; + + std::string mDefaultNeedsApplyMesg; + std::string mWantApplyMesg; +}; + + +class LLPanelGroupSubTab : public LLPanelGroupTab +{ +public: + LLPanelGroupSubTab(); + virtual ~LLPanelGroupSubTab(); + + virtual bool postBuild(); + + // This allows sub-tabs to collect child widgets from a higher level in the view hierarchy. + virtual bool postBuildSubTab(LLView* root); + + virtual void setSearchFilter( const std::string& filter ); + + virtual void activate(); + virtual void deactivate(); + + // Helper functions + bool matchesActionSearchFilter(std::string action); + + + void setFooterEnabled(bool enable); + + virtual void setGroupID(const LLUUID& id); +protected: + void buildActionsList(LLScrollListCtrl* ctrl, + U64 allowed_by_some, + U64 allowed_by_all, + LLUICtrl::commit_callback_t commit_callback, + bool show_all, + bool filter, + bool is_owner_role); + void buildActionCategory(LLScrollListCtrl* ctrl, + U64 allowed_by_some, + U64 allowed_by_all, + LLRoleActionSet* action_set, + LLUICtrl::commit_callback_t commit_callback, + bool show_all, + bool filter, + bool is_owner_role); + +protected: + LLPanel* mHeader; // Might not be present in xui of derived class (NULL) + LLPanel* mFooter; + + LLFilterEditor* mSearchEditor; + boost::signals2::connection mSearchCommitConnection; + + std::string mSearchFilter; + + icon_map_t mActionIcons; + + bool mActivated; + + bool mHasGroupBanPower; // Used to communicate between action sets due to the dependency between + // GP_GROUP_BAN_ACCESS and GP_EJECT_MEMBER and GP_ROLE_REMOVE_MEMBER + + void setOthersVisible(bool b); +}; + + +class LLPanelGroupMembersSubTab : public LLPanelGroupSubTab +{ +public: + LLPanelGroupMembersSubTab(); + virtual ~LLPanelGroupMembersSubTab(); + + virtual bool postBuildSubTab(LLView* root); + + static void onMemberSelect(LLUICtrl*, void*); + void handleMemberSelect(); + + static void onMemberDoubleClick(void*); + void handleMemberDoubleClick(); + + static void onInviteMember(void*); + void handleInviteMember(); + + static void onEjectMembers(void*); + void handleEjectMembers(); + void sendEjectNotifications(const LLUUID& group_id, const uuid_vec_t& selected_members); + bool handleEjectCallback(const LLSD& notification, const LLSD& response); + void confirmEjectMembers(); + + static void onRoleCheck(LLUICtrl* check, void* user_data); + void handleRoleCheck(const LLUUID& role_id, + LLRoleMemberChangeType type); + + static void onBanMember(void* user_data); + void handleBanMember(); + bool handleBanCallback(const LLSD& notification, const LLSD& response); + void confirmBanMembers(); + + void updateActionDescription(); + + void applyMemberChanges(); + bool addOwnerCB(const LLSD& notification, const LLSD& response); + + virtual void activate(); + virtual void deactivate(); + virtual void cancel(); + virtual bool needsApply(std::string& mesg); + virtual bool apply(std::string& mesg); + virtual void update(LLGroupChange gc); + void updateMembers(); + + virtual void draw(); + + virtual void setGroupID(const LLUUID& id); + + void addMemberToList(LLGroupMemberData* data); + void onNameCache(const LLUUID& update_id, LLGroupMemberData* member, const LLAvatarName& av_name, const LLUUID& av_id); + +protected: + typedef std::map role_change_data_map_t; + typedef std::map member_role_changes_map_t; + + bool matchesSearchFilter(const std::string& fullname); + + U64 getAgentPowersBasedOnRoleChanges(const LLUUID& agent_id); + bool getRoleChangeType(const LLUUID& member_id, + const LLUUID& role_id, + LLRoleMemberChangeType& type); + + LLNameListCtrl* mMembersList; + LLScrollListCtrl* mAssignedRolesList; + LLScrollListCtrl* mAllowedActionsList; + LLButton* mEjectBtn; + LLButton* mBanBtn; + + bool mChanged; + bool mPendingMemberUpdate; + bool mHasMatch; + + LLTextEditor* mActionDescription; + + member_role_changes_map_t mMemberRoleChangeData; + U32 mNumOwnerAdditions; + + LLGroupMgrGroupData::member_list_t::iterator mMemberProgress; + typedef std::map avatar_name_cache_connection_map_t; + avatar_name_cache_connection_map_t mAvatarNameCacheConnections; +}; + + +class LLPanelGroupRolesSubTab : public LLPanelGroupSubTab +{ +public: + LLPanelGroupRolesSubTab(); + virtual ~LLPanelGroupRolesSubTab(); + + virtual bool postBuildSubTab(LLView* root); + + virtual void activate(); + virtual void deactivate(); + virtual bool needsApply(std::string& mesg); + virtual bool apply(std::string& mesg); + virtual void cancel(); + bool matchesSearchFilter(std::string rolename, std::string roletitle); + virtual void update(LLGroupChange gc); + + static void onRoleSelect(LLUICtrl*, void*); + void handleRoleSelect(); + void buildMembersList(); + + static void onActionCheck(LLUICtrl*, void*); + bool addActionCB(const LLSD& notification, const LLSD& response, LLCheckBoxCtrl* check); + + static void onPropertiesKey(LLLineEditor*, void*); + + void onDescriptionKeyStroke(LLTextEditor* caller); + + static void onDescriptionCommit(LLUICtrl*, void*); + + static void onMemberVisibilityChange(LLUICtrl*, void*); + void handleMemberVisibilityChange(bool value); + + static void onCreateRole(void*); + void handleCreateRole(); + + static void onCopyRole(void*); + void handleCopyRole(); + + static void onDeleteRole(void*); + void handleDeleteRole(); + + void updateActionDescription(); + + void saveRoleChanges(bool select_saved_role); + + virtual void setGroupID(const LLUUID& id); + + bool mFirstOpen; + +protected: + void handleActionCheck(LLUICtrl* ctrl, bool force); + LLSD createRoleItem(const LLUUID& role_id, std::string name, std::string title, S32 members); + + LLScrollListCtrl* mRolesList; + LLNameListCtrl* mAssignedMembersList; + LLScrollListCtrl* mAllowedActionsList; + LLTextEditor* mActionDescription; + + LLLineEditor* mRoleName; + LLLineEditor* mRoleTitle; + LLTextEditor* mRoleDescription; + + LLCheckBoxCtrl* mMemberVisibleCheck; + LLButton* mDeleteRoleButton; + LLButton* mCreateRoleButton; + LLButton* mCopyRoleButton; + + LLUUID mSelectedRole; + bool mHasRoleChange; + std::string mRemoveEveryoneTxt; +}; + + +class LLPanelGroupActionsSubTab : public LLPanelGroupSubTab +{ +public: + LLPanelGroupActionsSubTab(); + virtual ~LLPanelGroupActionsSubTab(); + + virtual bool postBuildSubTab(LLView* root); + + + virtual void activate(); + virtual void deactivate(); + virtual bool needsApply(std::string& mesg); + virtual bool apply(std::string& mesg); + virtual void update(LLGroupChange gc); + virtual void onFilterChanged(); + + void handleActionSelect(); + + virtual void setGroupID(const LLUUID& id); +protected: + LLScrollListCtrl* mActionList; + LLScrollListCtrl* mActionRoles; + LLNameListCtrl* mActionMembers; + + LLTextEditor* mActionDescription; +}; + + +class LLPanelGroupBanListSubTab : public LLPanelGroupSubTab +{ +public: + LLPanelGroupBanListSubTab(); + virtual ~LLPanelGroupBanListSubTab() {} + + virtual bool postBuildSubTab(LLView* root); + + virtual void activate(); + virtual void update(LLGroupChange gc); + virtual void draw(); + + static void onBanEntrySelect(LLUICtrl* ctrl, void* user_data); + void handleBanEntrySelect(); + + static void onCreateBanEntry(void* user_data); + void handleCreateBanEntry(); + + static void onDeleteBanEntry(void* user_data); + void handleDeleteBanEntry(); + + static void onRefreshBanList(void* user_data); + void handleRefreshBanList(); + + void onBanListCompleted(bool isComplete); + +protected: + void setBanCount(U32 ban_count); + void populateBanList(); + +public: + virtual void setGroupID(const LLUUID& id); + +protected: + LLNameListCtrl* mBanList; + LLButton* mCreateBanButton; + LLButton* mDeleteBanButton; + LLButton* mRefreshBanListButton; + LLTextBase* mBanCountText; + +}; + +#endif // LL_LLPANELGROUPROLES_H diff --git a/indra/newview/llpanelhome.cpp b/indra/newview/llpanelhome.cpp index b701fefe7a..a17d101539 100644 --- a/indra/newview/llpanelhome.cpp +++ b/indra/newview/llpanelhome.cpp @@ -1,72 +1,72 @@ -/** -* @file llpanelhome.cpp -* @author Martin Reddy -* @brief The Home side tray panel -* -* $LicenseInfo:firstyear=2009&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2010, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#include "llviewerprecompiledheaders.h" -#include "llpanelhome.h" - -#include "llmediactrl.h" -#include "llviewerhome.h" - -static LLPanelInjector t_home("panel_sidetray_home"); - -LLPanelHome::LLPanelHome() : - LLPanel(), - LLViewerMediaObserver(), - mBrowser(NULL), - mFirstView(true) -{ -} - -void LLPanelHome::onOpen(const LLSD& key) -{ - // display the home page the first time we open the panel - // *NOTE: this seems to happen during login. Can we avoid that? - if (mFirstView && mBrowser) - { - mBrowser->navigateHome(); - } - mFirstView = false; -} - -bool LLPanelHome::postBuild() -{ - mBrowser = getChild("browser"); - if (mBrowser) - { - // read the URL to display from settings.xml - std::string url = LLViewerHome::getHomeURL(); - - mBrowser->addObserver(this); - mBrowser->setHomePageUrl(url); - } - - return true; -} - -void LLPanelHome::handleMediaEvent(LLPluginClassMedia *self, EMediaEvent event) -{ -} +/** +* @file llpanelhome.cpp +* @author Martin Reddy +* @brief The Home side tray panel +* +* $LicenseInfo:firstyear=2009&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2010, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" +#include "llpanelhome.h" + +#include "llmediactrl.h" +#include "llviewerhome.h" + +static LLPanelInjector t_home("panel_sidetray_home"); + +LLPanelHome::LLPanelHome() : + LLPanel(), + LLViewerMediaObserver(), + mBrowser(NULL), + mFirstView(true) +{ +} + +void LLPanelHome::onOpen(const LLSD& key) +{ + // display the home page the first time we open the panel + // *NOTE: this seems to happen during login. Can we avoid that? + if (mFirstView && mBrowser) + { + mBrowser->navigateHome(); + } + mFirstView = false; +} + +bool LLPanelHome::postBuild() +{ + mBrowser = getChild("browser"); + if (mBrowser) + { + // read the URL to display from settings.xml + std::string url = LLViewerHome::getHomeURL(); + + mBrowser->addObserver(this); + mBrowser->setHomePageUrl(url); + } + + return true; +} + +void LLPanelHome::handleMediaEvent(LLPluginClassMedia *self, EMediaEvent event) +{ +} diff --git a/indra/newview/llpanelhome.h b/indra/newview/llpanelhome.h index 1b593e53f5..e4687d1765 100644 --- a/indra/newview/llpanelhome.h +++ b/indra/newview/llpanelhome.h @@ -1,58 +1,58 @@ -/** -* @file llpanelhome.h -* @author Martin Reddy -* @brief The Home side tray panel -* -* $LicenseInfo:firstyear=2009&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2010, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#ifndef LL_LLPANELHOME_H -#define LL_LLPANELHOME_H - -#include "llpanel.h" -#include "llsd.h" -#include "llviewermediaobserver.h" - -class LLMediaCtrl; - -/** - * Base class for web-based Home side tray - */ -class LLPanelHome : - public LLPanel, - public LLViewerMediaObserver -{ -public: - LLPanelHome(); - - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); - -private: - // inherited from LLViewerMediaObserver - /*virtual*/ void handleMediaEvent(LLPluginClassMedia *self, EMediaEvent event); - - LLMediaCtrl *mBrowser; - bool mFirstView; -}; - -#endif //LL_LLPANELHOME_H +/** +* @file llpanelhome.h +* @author Martin Reddy +* @brief The Home side tray panel +* +* $LicenseInfo:firstyear=2009&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2010, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#ifndef LL_LLPANELHOME_H +#define LL_LLPANELHOME_H + +#include "llpanel.h" +#include "llsd.h" +#include "llviewermediaobserver.h" + +class LLMediaCtrl; + +/** + * Base class for web-based Home side tray + */ +class LLPanelHome : + public LLPanel, + public LLViewerMediaObserver +{ +public: + LLPanelHome(); + + /*virtual*/ bool postBuild(); + /*virtual*/ void onOpen(const LLSD& key); + +private: + // inherited from LLViewerMediaObserver + /*virtual*/ void handleMediaEvent(LLPluginClassMedia *self, EMediaEvent event); + + LLMediaCtrl *mBrowser; + bool mFirstView; +}; + +#endif //LL_LLPANELHOME_H diff --git a/indra/newview/llpanelland.cpp b/indra/newview/llpanelland.cpp index a76505f2de..3e22374294 100644 --- a/indra/newview/llpanelland.cpp +++ b/indra/newview/llpanelland.cpp @@ -1,259 +1,259 @@ -/** - * @file llpanelland.cpp - * @brief Land information in the tool floater, NOT the "About Land" floater - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelland.h" - -#include "llparcel.h" - -#include "llagent.h" -#include "llbutton.h" -#include "llcheckboxctrl.h" -#include "llfloaterland.h" -#include "llfloaterreg.h" -#include "lltextbox.h" -#include "llviewercontrol.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" -#include "llviewerwindow.h" -#include "roles_constants.h" - -#include "lluictrlfactory.h" - -LLPanelLandSelectObserver* LLPanelLandInfo::sObserver = NULL; -LLPanelLandInfo* LLPanelLandInfo::sInstance = NULL; - -class LLPanelLandSelectObserver : public LLParcelObserver -{ -public: - LLPanelLandSelectObserver() {} - virtual ~LLPanelLandSelectObserver() {} - virtual void changed() { LLPanelLandInfo::refreshAll(); } -}; - - -bool LLPanelLandInfo::postBuild() -{ - childSetAction("button buy land",boost::bind(onClickClaim)); - childSetAction("button abandon land", boost::bind(onClickRelease)); - childSetAction("button subdivide land", boost::bind(onClickDivide)); - childSetAction("button join land", boost::bind(onClickJoin)); - childSetAction("button about land", boost::bind(onClickAbout)); - - mCheckShowOwners = getChild("checkbox show owners"); - getChild("checkbox show owners")->setValue(gSavedSettings.getBOOL("ShowParcelOwners")); - - return true; -} -// -// Methods -// -LLPanelLandInfo::LLPanelLandInfo() -: LLPanel(), - mCheckShowOwners(NULL) -{ - if (!sInstance) - { - sInstance = this; - } - if (!sObserver) - { - sObserver = new LLPanelLandSelectObserver(); - LLViewerParcelMgr::getInstance()->addObserver( sObserver ); - } - -} - - -// virtual -LLPanelLandInfo::~LLPanelLandInfo() -{ - LLViewerParcelMgr::getInstance()->removeObserver( sObserver ); - delete sObserver; - sObserver = NULL; - - sInstance = NULL; -} - - -// static -void LLPanelLandInfo::refreshAll() -{ - if (sInstance) - { - sInstance->refresh(); - } -} - - -// public -void LLPanelLandInfo::refresh() -{ - LLParcel *parcel = LLViewerParcelMgr::getInstance()->getParcelSelection()->getParcel(); - LLViewerRegion *regionp = LLViewerParcelMgr::getInstance()->getSelectionRegion(); - - if (!parcel || !regionp) - { - // nothing selected, disable panel - getChildView("label_area_price")->setVisible(false); - getChildView("label_area")->setVisible(false); - - //mTextPrice->setText(LLStringUtil::null); - getChild("textbox price")->setValue(LLStringUtil::null); - - getChildView("button buy land")->setEnabled(false); - getChildView("button abandon land")->setEnabled(false); - getChildView("button subdivide land")->setEnabled(false); - getChildView("button join land")->setEnabled(false); - getChildView("button about land")->setEnabled(false); - } - else - { - // something selected, hooray! - const LLUUID& owner_id = parcel->getOwnerID(); - const LLUUID& auth_buyer_id = parcel->getAuthorizedBuyerID(); - - bool is_public = parcel->isPublic(); - bool is_for_sale = parcel->getForSale() - && ((parcel->getSalePrice() > 0) || (auth_buyer_id.notNull())); - bool can_buy = (is_for_sale - && (owner_id != gAgent.getID()) - && ((gAgent.getID() == auth_buyer_id) - || (auth_buyer_id.isNull()))); - - if (is_public && !LLViewerParcelMgr::getInstance()->getParcelSelection()->getMultipleOwners()) - { - getChildView("button buy land")->setEnabled(true); - } - else - { - getChildView("button buy land")->setEnabled(can_buy); - } - - bool owner_release = LLViewerParcelMgr::isParcelOwnedByAgent(parcel, GP_LAND_RELEASE); - bool owner_divide = LLViewerParcelMgr::isParcelOwnedByAgent(parcel, GP_LAND_DIVIDE_JOIN); - - bool manager_releaseable = ( gAgent.canManageEstate() - && (parcel->getOwnerID() == regionp->getOwner()) ); - - bool manager_divideable = ( gAgent.canManageEstate() - && ((parcel->getOwnerID() == regionp->getOwner()) || owner_divide) ); - - getChildView("button abandon land")->setEnabled(owner_release || manager_releaseable || gAgent.isGodlike()); - - // only mainland sims are subdividable by owner - if (regionp->getRegionFlag(REGION_FLAGS_ALLOW_PARCEL_CHANGES)) - { - getChildView("button subdivide land")->setEnabled(owner_divide || manager_divideable || gAgent.isGodlike()); - } - else - { - getChildView("button subdivide land")->setEnabled(manager_divideable || gAgent.isGodlike()); - } - - // To join land, must have something selected, - // not just a single unit of land, - // you must own part of it, - // and it must not be a whole parcel. - if (LLViewerParcelMgr::getInstance()->getSelectedArea() > PARCEL_UNIT_AREA - //&& LLViewerParcelMgr::getInstance()->getSelfCount() > 1 - && !LLViewerParcelMgr::getInstance()->getParcelSelection()->getWholeParcelSelected()) - { - getChildView("button join land")->setEnabled(true); - } - else - { - LL_DEBUGS() << "Invalid selection for joining land" << LL_ENDL; - getChildView("button join land")->setEnabled(false); - } - - getChildView("button about land")->setEnabled(true); - - // show pricing information - S32 area; - S32 claim_price; - S32 rent_price; - bool for_sale; - F32 dwell; - LLViewerParcelMgr::getInstance()->getDisplayInfo(&area, - &claim_price, - &rent_price, - &for_sale, - &dwell); - if(is_public || (is_for_sale && LLViewerParcelMgr::getInstance()->getParcelSelection()->getWholeParcelSelected())) - { - getChild("label_area_price")->setTextArg("[PRICE]", llformat("%d",claim_price)); - getChild("label_area_price")->setTextArg("[AREA]", llformat("%d",area)); - getChildView("label_area_price")->setVisible(true); - getChildView("label_area")->setVisible(false); - } - else - { - getChildView("label_area_price")->setVisible(false); - getChild("label_area")->setTextArg("[AREA]", llformat("%d",area)); - getChildView("label_area")->setVisible(true); - } - } -} - - -//static -void LLPanelLandInfo::onClickClaim() -{ - LLViewerParcelMgr::getInstance()->startBuyLand(); -} - - -//static -void LLPanelLandInfo::onClickRelease() -{ - LLViewerParcelMgr::getInstance()->startReleaseLand(); -} - -// static -void LLPanelLandInfo::onClickDivide() -{ - LLViewerParcelMgr::getInstance()->startDivideLand(); -} - -// static -void LLPanelLandInfo::onClickJoin() -{ - LLViewerParcelMgr::getInstance()->startJoinLand(); -} - -//static -void LLPanelLandInfo::onClickAbout() -{ - // Promote the rectangle selection to a parcel selection - if (!LLViewerParcelMgr::getInstance()->getParcelSelection()->getWholeParcelSelected()) - { - LLViewerParcelMgr::getInstance()->selectParcelInRectangle(); - } - - LLFloaterReg::showInstance("about_land"); -} +/** + * @file llpanelland.cpp + * @brief Land information in the tool floater, NOT the "About Land" floater + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelland.h" + +#include "llparcel.h" + +#include "llagent.h" +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "llfloaterland.h" +#include "llfloaterreg.h" +#include "lltextbox.h" +#include "llviewercontrol.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" +#include "roles_constants.h" + +#include "lluictrlfactory.h" + +LLPanelLandSelectObserver* LLPanelLandInfo::sObserver = NULL; +LLPanelLandInfo* LLPanelLandInfo::sInstance = NULL; + +class LLPanelLandSelectObserver : public LLParcelObserver +{ +public: + LLPanelLandSelectObserver() {} + virtual ~LLPanelLandSelectObserver() {} + virtual void changed() { LLPanelLandInfo::refreshAll(); } +}; + + +bool LLPanelLandInfo::postBuild() +{ + childSetAction("button buy land",boost::bind(onClickClaim)); + childSetAction("button abandon land", boost::bind(onClickRelease)); + childSetAction("button subdivide land", boost::bind(onClickDivide)); + childSetAction("button join land", boost::bind(onClickJoin)); + childSetAction("button about land", boost::bind(onClickAbout)); + + mCheckShowOwners = getChild("checkbox show owners"); + getChild("checkbox show owners")->setValue(gSavedSettings.getBOOL("ShowParcelOwners")); + + return true; +} +// +// Methods +// +LLPanelLandInfo::LLPanelLandInfo() +: LLPanel(), + mCheckShowOwners(NULL) +{ + if (!sInstance) + { + sInstance = this; + } + if (!sObserver) + { + sObserver = new LLPanelLandSelectObserver(); + LLViewerParcelMgr::getInstance()->addObserver( sObserver ); + } + +} + + +// virtual +LLPanelLandInfo::~LLPanelLandInfo() +{ + LLViewerParcelMgr::getInstance()->removeObserver( sObserver ); + delete sObserver; + sObserver = NULL; + + sInstance = NULL; +} + + +// static +void LLPanelLandInfo::refreshAll() +{ + if (sInstance) + { + sInstance->refresh(); + } +} + + +// public +void LLPanelLandInfo::refresh() +{ + LLParcel *parcel = LLViewerParcelMgr::getInstance()->getParcelSelection()->getParcel(); + LLViewerRegion *regionp = LLViewerParcelMgr::getInstance()->getSelectionRegion(); + + if (!parcel || !regionp) + { + // nothing selected, disable panel + getChildView("label_area_price")->setVisible(false); + getChildView("label_area")->setVisible(false); + + //mTextPrice->setText(LLStringUtil::null); + getChild("textbox price")->setValue(LLStringUtil::null); + + getChildView("button buy land")->setEnabled(false); + getChildView("button abandon land")->setEnabled(false); + getChildView("button subdivide land")->setEnabled(false); + getChildView("button join land")->setEnabled(false); + getChildView("button about land")->setEnabled(false); + } + else + { + // something selected, hooray! + const LLUUID& owner_id = parcel->getOwnerID(); + const LLUUID& auth_buyer_id = parcel->getAuthorizedBuyerID(); + + bool is_public = parcel->isPublic(); + bool is_for_sale = parcel->getForSale() + && ((parcel->getSalePrice() > 0) || (auth_buyer_id.notNull())); + bool can_buy = (is_for_sale + && (owner_id != gAgent.getID()) + && ((gAgent.getID() == auth_buyer_id) + || (auth_buyer_id.isNull()))); + + if (is_public && !LLViewerParcelMgr::getInstance()->getParcelSelection()->getMultipleOwners()) + { + getChildView("button buy land")->setEnabled(true); + } + else + { + getChildView("button buy land")->setEnabled(can_buy); + } + + bool owner_release = LLViewerParcelMgr::isParcelOwnedByAgent(parcel, GP_LAND_RELEASE); + bool owner_divide = LLViewerParcelMgr::isParcelOwnedByAgent(parcel, GP_LAND_DIVIDE_JOIN); + + bool manager_releaseable = ( gAgent.canManageEstate() + && (parcel->getOwnerID() == regionp->getOwner()) ); + + bool manager_divideable = ( gAgent.canManageEstate() + && ((parcel->getOwnerID() == regionp->getOwner()) || owner_divide) ); + + getChildView("button abandon land")->setEnabled(owner_release || manager_releaseable || gAgent.isGodlike()); + + // only mainland sims are subdividable by owner + if (regionp->getRegionFlag(REGION_FLAGS_ALLOW_PARCEL_CHANGES)) + { + getChildView("button subdivide land")->setEnabled(owner_divide || manager_divideable || gAgent.isGodlike()); + } + else + { + getChildView("button subdivide land")->setEnabled(manager_divideable || gAgent.isGodlike()); + } + + // To join land, must have something selected, + // not just a single unit of land, + // you must own part of it, + // and it must not be a whole parcel. + if (LLViewerParcelMgr::getInstance()->getSelectedArea() > PARCEL_UNIT_AREA + //&& LLViewerParcelMgr::getInstance()->getSelfCount() > 1 + && !LLViewerParcelMgr::getInstance()->getParcelSelection()->getWholeParcelSelected()) + { + getChildView("button join land")->setEnabled(true); + } + else + { + LL_DEBUGS() << "Invalid selection for joining land" << LL_ENDL; + getChildView("button join land")->setEnabled(false); + } + + getChildView("button about land")->setEnabled(true); + + // show pricing information + S32 area; + S32 claim_price; + S32 rent_price; + bool for_sale; + F32 dwell; + LLViewerParcelMgr::getInstance()->getDisplayInfo(&area, + &claim_price, + &rent_price, + &for_sale, + &dwell); + if(is_public || (is_for_sale && LLViewerParcelMgr::getInstance()->getParcelSelection()->getWholeParcelSelected())) + { + getChild("label_area_price")->setTextArg("[PRICE]", llformat("%d",claim_price)); + getChild("label_area_price")->setTextArg("[AREA]", llformat("%d",area)); + getChildView("label_area_price")->setVisible(true); + getChildView("label_area")->setVisible(false); + } + else + { + getChildView("label_area_price")->setVisible(false); + getChild("label_area")->setTextArg("[AREA]", llformat("%d",area)); + getChildView("label_area")->setVisible(true); + } + } +} + + +//static +void LLPanelLandInfo::onClickClaim() +{ + LLViewerParcelMgr::getInstance()->startBuyLand(); +} + + +//static +void LLPanelLandInfo::onClickRelease() +{ + LLViewerParcelMgr::getInstance()->startReleaseLand(); +} + +// static +void LLPanelLandInfo::onClickDivide() +{ + LLViewerParcelMgr::getInstance()->startDivideLand(); +} + +// static +void LLPanelLandInfo::onClickJoin() +{ + LLViewerParcelMgr::getInstance()->startJoinLand(); +} + +//static +void LLPanelLandInfo::onClickAbout() +{ + // Promote the rectangle selection to a parcel selection + if (!LLViewerParcelMgr::getInstance()->getParcelSelection()->getWholeParcelSelected()) + { + LLViewerParcelMgr::getInstance()->selectParcelInRectangle(); + } + + LLFloaterReg::showInstance("about_land"); +} diff --git a/indra/newview/llpanelland.h b/indra/newview/llpanelland.h index cd0f0b9441..7d0c6936bd 100644 --- a/indra/newview/llpanelland.h +++ b/indra/newview/llpanelland.h @@ -1,65 +1,65 @@ -/** - * @file llpanelland.h - * @brief Land information in the tool floater, NOT the "About Land" floater - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELLAND_H -#define LL_LLPANELLAND_H - -#include "llpanel.h" - -class LLTextBox; -class LLCheckBoxCtrl; -class LLButton; -class LLSpinCtrl; -class LLLineEditor; -class LLPanelLandSelectObserver; - -class LLPanelLandInfo -: public LLPanel -{ -public: - LLPanelLandInfo(); - virtual ~LLPanelLandInfo(); - - void refresh() override; - static void refreshAll(); - - LLCheckBoxCtrl *mCheckShowOwners; - -protected: - static void onClickClaim(); - static void onClickRelease(); - static void onClickDivide(); - static void onClickJoin(); - static void onClickAbout(); - -protected: - bool postBuild() override; - - static LLPanelLandSelectObserver* sObserver; - static LLPanelLandInfo* sInstance; -}; - -#endif +/** + * @file llpanelland.h + * @brief Land information in the tool floater, NOT the "About Land" floater + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELLAND_H +#define LL_LLPANELLAND_H + +#include "llpanel.h" + +class LLTextBox; +class LLCheckBoxCtrl; +class LLButton; +class LLSpinCtrl; +class LLLineEditor; +class LLPanelLandSelectObserver; + +class LLPanelLandInfo +: public LLPanel +{ +public: + LLPanelLandInfo(); + virtual ~LLPanelLandInfo(); + + void refresh() override; + static void refreshAll(); + + LLCheckBoxCtrl *mCheckShowOwners; + +protected: + static void onClickClaim(); + static void onClickRelease(); + static void onClickDivide(); + static void onClickJoin(); + static void onClickAbout(); + +protected: + bool postBuild() override; + + static LLPanelLandSelectObserver* sObserver; + static LLPanelLandInfo* sInstance; +}; + +#endif diff --git a/indra/newview/llpanellandaudio.cpp b/indra/newview/llpanellandaudio.cpp index fb982ae88e..143fa5602a 100644 --- a/indra/newview/llpanellandaudio.cpp +++ b/indra/newview/llpanellandaudio.cpp @@ -1,212 +1,212 @@ -/** - * @file llpanellandaudio.cpp - * @brief Allows configuration of "media" for a land parcel, - * for example movies, web pages, and audio. - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanellandaudio.h" - -// viewer includes -#include "llmimetypes.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" -#include "lluictrlfactory.h" - -// library includes -#include "llcheckboxctrl.h" -#include "llcombobox.h" -#include "llfloaterurlentry.h" -#include "llfocusmgr.h" -#include "lllineeditor.h" -#include "llparcel.h" -#include "lltextbox.h" -#include "llradiogroup.h" -#include "llspinctrl.h" -#include "llsdutil.h" -#include "lltexturectrl.h" -#include "roles_constants.h" -#include "llscrolllistctrl.h" - -// Values for the parcel voice settings radio group -enum -{ - kRadioVoiceChatEstate = 0, - kRadioVoiceChatPrivate = 1, - kRadioVoiceChatDisable = 2 -}; - -//--------------------------------------------------------------------------- -// LLPanelLandAudio -//--------------------------------------------------------------------------- - -LLPanelLandAudio::LLPanelLandAudio(LLParcelSelectionHandle& parcel) -: LLPanel(/*std::string("land_media_panel")*/), mParcel(parcel) -{ -} - - -// virtual -LLPanelLandAudio::~LLPanelLandAudio() -{ -} - - -bool LLPanelLandAudio::postBuild() -{ - mCheckSoundLocal = getChild("check sound local"); - childSetCommitCallback("check sound local", onCommitAny, this); - - mCheckParcelEnableVoice = getChild("parcel_enable_voice_channel"); - childSetCommitCallback("parcel_enable_voice_channel", onCommitAny, this); - - // This one is always disabled so no need for a commit callback - mCheckEstateDisabledVoice = getChild("parcel_enable_voice_channel_is_estate_disabled"); - - mCheckParcelVoiceLocal = getChild("parcel_enable_voice_channel_local"); - childSetCommitCallback("parcel_enable_voice_channel_local", onCommitAny, this); - - mMusicURLEdit = getChild("music_url"); - childSetCommitCallback("music_url", onCommitAny, this); - - mCheckAVSoundAny = getChild("all av sound check"); - childSetCommitCallback("all av sound check", onCommitAny, this); - - mCheckAVSoundGroup = getChild("group av sound check"); - childSetCommitCallback("group av sound check", onCommitAny, this); - - mCheckObscureMOAP = getChild("obscure_moap"); - childSetCommitCallback("obscure_moap", onCommitAny, this); - - return true; -} - - -// public -void LLPanelLandAudio::refresh() -{ - LLParcel *parcel = mParcel->getParcel(); - - if (!parcel) - { - clearCtrls(); - } - else - { - // something selected, hooray! - - // Display options - bool can_change_media = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_CHANGE_MEDIA); - - mCheckSoundLocal->set( parcel->getSoundLocal() ); - mCheckSoundLocal->setEnabled( can_change_media ); - - bool allow_voice = parcel->getParcelFlagAllowVoice(); - - LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); - if (region && region->isVoiceEnabled()) - { - mCheckEstateDisabledVoice->setVisible(false); - - mCheckParcelEnableVoice->setVisible(true); - mCheckParcelEnableVoice->setEnabled( can_change_media ); - mCheckParcelEnableVoice->set(allow_voice); - - mCheckParcelVoiceLocal->setEnabled( can_change_media && allow_voice ); - } - else - { - // Voice disabled at estate level, overrides parcel settings - // Replace the parcel voice checkbox with a disabled one - // labelled with an explanatory message - mCheckEstateDisabledVoice->setVisible(true); - - mCheckParcelEnableVoice->setVisible(false); - mCheckParcelEnableVoice->setEnabled(false); - mCheckParcelVoiceLocal->setEnabled(false); - } - - mCheckParcelEnableVoice->set(allow_voice); - mCheckParcelVoiceLocal->set(!parcel->getParcelFlagUseEstateVoiceChannel()); - - mMusicURLEdit->setText(parcel->getMusicURL()); - mMusicURLEdit->setEnabled( can_change_media ); - - bool can_change_av_sounds = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_OPTIONS) && parcel->getHaveNewParcelLimitData(); - mCheckAVSoundAny->set(parcel->getAllowAnyAVSounds()); - mCheckAVSoundAny->setEnabled(can_change_av_sounds); - - mCheckAVSoundGroup->set(parcel->getAllowGroupAVSounds() || parcel->getAllowAnyAVSounds()); // On if "Everyone" is on - mCheckAVSoundGroup->setEnabled(can_change_av_sounds && !parcel->getAllowAnyAVSounds()); // Enabled if "Everyone" is off - - mCheckObscureMOAP->set(parcel->getObscureMOAP()); - mCheckObscureMOAP->setEnabled(can_change_media); - } -} -// static -void LLPanelLandAudio::onCommitAny(LLUICtrl*, void *userdata) -{ - LLPanelLandAudio *self = (LLPanelLandAudio *)userdata; - - LLParcel* parcel = self->mParcel->getParcel(); - if (!parcel) - { - return; - } - - // Extract data from UI - bool sound_local = self->mCheckSoundLocal->get(); - std::string music_url = self->mMusicURLEdit->getText(); - - bool voice_enabled = self->mCheckParcelEnableVoice->get(); - bool voice_estate_chan = !self->mCheckParcelVoiceLocal->get(); - - bool any_av_sound = self->mCheckAVSoundAny->get(); - bool group_av_sound = true; // If set to "Everyone" then group is checked as well - if (!any_av_sound) - { // If "Everyone" is off, use the value from the checkbox - group_av_sound = self->mCheckAVSoundGroup->get(); - } - - bool obscure_moap = self->mCheckObscureMOAP->get(); - - // Remove leading/trailing whitespace (common when copying/pasting) - LLStringUtil::trim(music_url); - - // Push data into current parcel - parcel->setParcelFlag(PF_ALLOW_VOICE_CHAT, voice_enabled); - parcel->setParcelFlag(PF_USE_ESTATE_VOICE_CHAN, voice_estate_chan); - parcel->setParcelFlag(PF_SOUND_LOCAL, sound_local); - parcel->setMusicURL(music_url); - parcel->setAllowAnyAVSounds(any_av_sound); - parcel->setAllowGroupAVSounds(group_av_sound); - parcel->setObscureMOAP(obscure_moap); - - // Send current parcel data upstream to server - LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate( parcel ); - - // Might have changed properties, so let's redraw! - self->refresh(); -} +/** + * @file llpanellandaudio.cpp + * @brief Allows configuration of "media" for a land parcel, + * for example movies, web pages, and audio. + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanellandaudio.h" + +// viewer includes +#include "llmimetypes.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "lluictrlfactory.h" + +// library includes +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "llfloaterurlentry.h" +#include "llfocusmgr.h" +#include "lllineeditor.h" +#include "llparcel.h" +#include "lltextbox.h" +#include "llradiogroup.h" +#include "llspinctrl.h" +#include "llsdutil.h" +#include "lltexturectrl.h" +#include "roles_constants.h" +#include "llscrolllistctrl.h" + +// Values for the parcel voice settings radio group +enum +{ + kRadioVoiceChatEstate = 0, + kRadioVoiceChatPrivate = 1, + kRadioVoiceChatDisable = 2 +}; + +//--------------------------------------------------------------------------- +// LLPanelLandAudio +//--------------------------------------------------------------------------- + +LLPanelLandAudio::LLPanelLandAudio(LLParcelSelectionHandle& parcel) +: LLPanel(/*std::string("land_media_panel")*/), mParcel(parcel) +{ +} + + +// virtual +LLPanelLandAudio::~LLPanelLandAudio() +{ +} + + +bool LLPanelLandAudio::postBuild() +{ + mCheckSoundLocal = getChild("check sound local"); + childSetCommitCallback("check sound local", onCommitAny, this); + + mCheckParcelEnableVoice = getChild("parcel_enable_voice_channel"); + childSetCommitCallback("parcel_enable_voice_channel", onCommitAny, this); + + // This one is always disabled so no need for a commit callback + mCheckEstateDisabledVoice = getChild("parcel_enable_voice_channel_is_estate_disabled"); + + mCheckParcelVoiceLocal = getChild("parcel_enable_voice_channel_local"); + childSetCommitCallback("parcel_enable_voice_channel_local", onCommitAny, this); + + mMusicURLEdit = getChild("music_url"); + childSetCommitCallback("music_url", onCommitAny, this); + + mCheckAVSoundAny = getChild("all av sound check"); + childSetCommitCallback("all av sound check", onCommitAny, this); + + mCheckAVSoundGroup = getChild("group av sound check"); + childSetCommitCallback("group av sound check", onCommitAny, this); + + mCheckObscureMOAP = getChild("obscure_moap"); + childSetCommitCallback("obscure_moap", onCommitAny, this); + + return true; +} + + +// public +void LLPanelLandAudio::refresh() +{ + LLParcel *parcel = mParcel->getParcel(); + + if (!parcel) + { + clearCtrls(); + } + else + { + // something selected, hooray! + + // Display options + bool can_change_media = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_CHANGE_MEDIA); + + mCheckSoundLocal->set( parcel->getSoundLocal() ); + mCheckSoundLocal->setEnabled( can_change_media ); + + bool allow_voice = parcel->getParcelFlagAllowVoice(); + + LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); + if (region && region->isVoiceEnabled()) + { + mCheckEstateDisabledVoice->setVisible(false); + + mCheckParcelEnableVoice->setVisible(true); + mCheckParcelEnableVoice->setEnabled( can_change_media ); + mCheckParcelEnableVoice->set(allow_voice); + + mCheckParcelVoiceLocal->setEnabled( can_change_media && allow_voice ); + } + else + { + // Voice disabled at estate level, overrides parcel settings + // Replace the parcel voice checkbox with a disabled one + // labelled with an explanatory message + mCheckEstateDisabledVoice->setVisible(true); + + mCheckParcelEnableVoice->setVisible(false); + mCheckParcelEnableVoice->setEnabled(false); + mCheckParcelVoiceLocal->setEnabled(false); + } + + mCheckParcelEnableVoice->set(allow_voice); + mCheckParcelVoiceLocal->set(!parcel->getParcelFlagUseEstateVoiceChannel()); + + mMusicURLEdit->setText(parcel->getMusicURL()); + mMusicURLEdit->setEnabled( can_change_media ); + + bool can_change_av_sounds = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_OPTIONS) && parcel->getHaveNewParcelLimitData(); + mCheckAVSoundAny->set(parcel->getAllowAnyAVSounds()); + mCheckAVSoundAny->setEnabled(can_change_av_sounds); + + mCheckAVSoundGroup->set(parcel->getAllowGroupAVSounds() || parcel->getAllowAnyAVSounds()); // On if "Everyone" is on + mCheckAVSoundGroup->setEnabled(can_change_av_sounds && !parcel->getAllowAnyAVSounds()); // Enabled if "Everyone" is off + + mCheckObscureMOAP->set(parcel->getObscureMOAP()); + mCheckObscureMOAP->setEnabled(can_change_media); + } +} +// static +void LLPanelLandAudio::onCommitAny(LLUICtrl*, void *userdata) +{ + LLPanelLandAudio *self = (LLPanelLandAudio *)userdata; + + LLParcel* parcel = self->mParcel->getParcel(); + if (!parcel) + { + return; + } + + // Extract data from UI + bool sound_local = self->mCheckSoundLocal->get(); + std::string music_url = self->mMusicURLEdit->getText(); + + bool voice_enabled = self->mCheckParcelEnableVoice->get(); + bool voice_estate_chan = !self->mCheckParcelVoiceLocal->get(); + + bool any_av_sound = self->mCheckAVSoundAny->get(); + bool group_av_sound = true; // If set to "Everyone" then group is checked as well + if (!any_av_sound) + { // If "Everyone" is off, use the value from the checkbox + group_av_sound = self->mCheckAVSoundGroup->get(); + } + + bool obscure_moap = self->mCheckObscureMOAP->get(); + + // Remove leading/trailing whitespace (common when copying/pasting) + LLStringUtil::trim(music_url); + + // Push data into current parcel + parcel->setParcelFlag(PF_ALLOW_VOICE_CHAT, voice_enabled); + parcel->setParcelFlag(PF_USE_ESTATE_VOICE_CHAN, voice_estate_chan); + parcel->setParcelFlag(PF_SOUND_LOCAL, sound_local); + parcel->setMusicURL(music_url); + parcel->setAllowAnyAVSounds(any_av_sound); + parcel->setAllowGroupAVSounds(group_av_sound); + parcel->setObscureMOAP(obscure_moap); + + // Send current parcel data upstream to server + LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate( parcel ); + + // Might have changed properties, so let's redraw! + self->refresh(); +} diff --git a/indra/newview/llpanellandaudio.h b/indra/newview/llpanellandaudio.h index f84777fd50..9fdcea0f59 100644 --- a/indra/newview/llpanellandaudio.h +++ b/indra/newview/llpanellandaudio.h @@ -1,61 +1,61 @@ -/** - * @file llpanellandaudio.h - * @brief Allows configuration of "audio" for a land parcel. - * - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLPANELLANDAUDIO_H -#define LLPANELLANDAUDIO_H - -#include "lllineeditor.h" -#include "llpanel.h" -#include "llparcelselection.h" -#include "lluifwd.h" // widget pointer types - -class LLPanelLandAudio - : public LLPanel -{ -public: - LLPanelLandAudio(LLSafeHandle& parcelp); - /*virtual*/ ~LLPanelLandAudio(); - /*virtual*/ bool postBuild(); - void refresh(); - -private: - static void onCommitAny(LLUICtrl* ctrl, void *userdata); - -private: - LLCheckBoxCtrl* mCheckSoundLocal; - LLCheckBoxCtrl* mCheckParcelEnableVoice; - LLCheckBoxCtrl* mCheckEstateDisabledVoice; - LLCheckBoxCtrl* mCheckParcelVoiceLocal; - LLLineEditor* mMusicURLEdit; - LLCheckBoxCtrl* mCheckAVSoundAny; - LLCheckBoxCtrl* mCheckAVSoundGroup; - LLCheckBoxCtrl* mCheckObscureMOAP; - - LLSafeHandle& mParcel; -}; - -#endif +/** + * @file llpanellandaudio.h + * @brief Allows configuration of "audio" for a land parcel. + * + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLPANELLANDAUDIO_H +#define LLPANELLANDAUDIO_H + +#include "lllineeditor.h" +#include "llpanel.h" +#include "llparcelselection.h" +#include "lluifwd.h" // widget pointer types + +class LLPanelLandAudio + : public LLPanel +{ +public: + LLPanelLandAudio(LLSafeHandle& parcelp); + /*virtual*/ ~LLPanelLandAudio(); + /*virtual*/ bool postBuild(); + void refresh(); + +private: + static void onCommitAny(LLUICtrl* ctrl, void *userdata); + +private: + LLCheckBoxCtrl* mCheckSoundLocal; + LLCheckBoxCtrl* mCheckParcelEnableVoice; + LLCheckBoxCtrl* mCheckEstateDisabledVoice; + LLCheckBoxCtrl* mCheckParcelVoiceLocal; + LLLineEditor* mMusicURLEdit; + LLCheckBoxCtrl* mCheckAVSoundAny; + LLCheckBoxCtrl* mCheckAVSoundGroup; + LLCheckBoxCtrl* mCheckObscureMOAP; + + LLSafeHandle& mParcel; +}; + +#endif diff --git a/indra/newview/llpanellandmarkinfo.cpp b/indra/newview/llpanellandmarkinfo.cpp index e04d8913a0..41373cd7f5 100644 --- a/indra/newview/llpanellandmarkinfo.cpp +++ b/indra/newview/llpanellandmarkinfo.cpp @@ -1,552 +1,552 @@ -/** - * @file llpanellandmarkinfo.cpp - * @brief Displays landmark info in Side Tray. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanellandmarkinfo.h" - -#include "llcombobox.h" -#include "lliconctrl.h" -#include "llinventoryfunctions.h" -#include "lllineeditor.h" -#include "lltextbox.h" -#include "lltexteditor.h" -#include "lltrans.h" - -#include "llagent.h" -#include "llagentui.h" -#include "lllandmarkactions.h" -#include "llparcel.h" -#include "llslurl.h" -#include "llviewerinventory.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" - -//---------------------------------------------------------------------------- -// Aux types and methods -//---------------------------------------------------------------------------- - -typedef std::pair folder_pair_t; - -static bool cmp_folders(const folder_pair_t& left, const folder_pair_t& right); - -static LLPanelInjector t_landmark_info("panel_landmark_info"); - -// Statics for textures filenames -static std::string icon_pg; -static std::string icon_m; -static std::string icon_r; - -LLPanelLandmarkInfo::LLPanelLandmarkInfo() -: LLPanelPlaceInfo() -{} - -// virtual -LLPanelLandmarkInfo::~LLPanelLandmarkInfo() -{} - -// virtual -bool LLPanelLandmarkInfo::postBuild() -{ - LLPanelPlaceInfo::postBuild(); - - mOwner = getChild("owner"); - mCreator = getChild("creator"); - mCreated = getChild("created"); - - mLandmarkTitle = getChild("title_value"); - mLandmarkTitleEditor = getChild("title_editor"); - mNotesEditor = getChild("notes_editor"); - mFolderCombo = getChild("folder_combo"); - - icon_pg = getString("icon_PG"); - icon_m = getString("icon_M"); - icon_r = getString("icon_R"); - - return true; -} - -// virtual -void LLPanelLandmarkInfo::resetLocation() -{ - LLPanelPlaceInfo::resetLocation(); - - std::string loading = LLTrans::getString("LoadingData"); - mCreator->setText(loading); - mOwner->setText(loading); - mCreated->setText(loading); - mLandmarkTitle->setText(LLStringUtil::null); - mLandmarkTitleEditor->setText(LLStringUtil::null); - mNotesEditor->setText(LLStringUtil::null); -} - -// virtual -void LLPanelLandmarkInfo::setInfoType(EInfoType type) -{ - LLUUID dest_folder; - setInfoType(type, dest_folder); -} - -// Sets CREATE_LANDMARK infotype and creates landmark at desired folder -void LLPanelLandmarkInfo::setInfoAndCreateLandmark(const LLUUID& folder_id) -{ - setInfoType(CREATE_LANDMARK, folder_id); -} - -void LLPanelLandmarkInfo::setInfoType(EInfoType type, const LLUUID &folder_id) -{ - LLPanel* landmark_info_panel = getChild("landmark_info_panel"); - - bool is_info_type_create_landmark = type == CREATE_LANDMARK; - - landmark_info_panel->setVisible(type == LANDMARK); - - getChild("folder_label")->setVisible(is_info_type_create_landmark); - getChild("edit_btn")->setVisible(!is_info_type_create_landmark); - mFolderCombo->setVisible(is_info_type_create_landmark); - - switch(type) - { - case CREATE_LANDMARK: - { - mCurrentTitle = getString("title_create_landmark"); - - mLandmarkTitle->setVisible(false); - mLandmarkTitleEditor->setVisible(true); - mNotesEditor->setEnabled(true); - - LLViewerParcelMgr* parcel_mgr = LLViewerParcelMgr::getInstance(); - LLParcel* parcel = parcel_mgr->getAgentParcel(); - std::string name = parcel->getName(); - LLVector3 agent_pos = gAgent.getPositionAgent(); - - if (name.empty()) - { - S32 region_x = ll_round(agent_pos.mV[VX]); - S32 region_y = ll_round(agent_pos.mV[VY]); - S32 region_z = ll_round(agent_pos.mV[VZ]); - - std::string region_name; - LLViewerRegion* region = parcel_mgr->getSelectionRegion(); - if (region) - { - region_name = region->getName(); - } - else - { - std::string desc; - LLAgentUI::buildLocationString(desc, LLAgentUI::LOCATION_FORMAT_NORMAL, agent_pos); - region_name = desc; - } - - mLandmarkTitleEditor->setText(llformat("%s (%d, %d, %d)", - region_name.c_str(), region_x, region_y, region_z)); - } - else - { - mLandmarkTitleEditor->setText(name); - } - - LLUUID owner_id = parcel->getOwnerID(); - if (owner_id.notNull()) - { - if (parcel->getIsGroupOwned()) - { - std::string owner_name = LLSLURL("group", parcel->getGroupID(), "inspect").getSLURLString(); - mParcelOwner->setText(owner_name); - } - else - { - std::string owner_name = LLSLURL("agent", owner_id, "inspect").getSLURLString(); - mParcelOwner->setText(owner_name); - } - } - else - { - mParcelOwner->setText(getString("public")); - } - - // Moved landmark creation here from LLPanelLandmarkInfo::processParcelInfo() - // because we use only agent's current coordinates instead of waiting for - // remote parcel request to complete. - if (!LLLandmarkActions::landmarkAlreadyExists()) - { - createLandmark(folder_id); - } - } - break; - - case LANDMARK: - default: - mCurrentTitle = getString("title_landmark"); - - mLandmarkTitle->setVisible(true); - mLandmarkTitleEditor->setVisible(false); - mNotesEditor->setEnabled(false); - break; - } - - populateFoldersList(); - - // Prevent the floater from losing focus (if the sidepanel is undocked). - setFocus(true); - - LLPanelPlaceInfo::setInfoType(type); -} - -// virtual -void LLPanelLandmarkInfo::processParcelInfo(const LLParcelData& parcel_data) -{ - LLPanelPlaceInfo::processParcelInfo(parcel_data); - - // HACK: Flag 0x2 == adult region, - // Flag 0x1 == mature region, otherwise assume PG - if (parcel_data.flags & 0x2) - { - mMaturityRatingIcon->setValue(icon_r); - mMaturityRatingText->setText(LLViewerRegion::accessToString(SIM_ACCESS_ADULT)); - } - else if (parcel_data.flags & 0x1) - { - mMaturityRatingIcon->setValue(icon_m); - mMaturityRatingText->setText(LLViewerRegion::accessToString(SIM_ACCESS_MATURE)); - } - else - { - mMaturityRatingIcon->setValue(icon_pg); - mMaturityRatingText->setText(LLViewerRegion::accessToString(SIM_ACCESS_PG)); - } - - if (parcel_data.owner_id.notNull()) - { - if (parcel_data.flags & 0x4) // depends onto DRTSIM-453 - { - std::string owner_name = LLSLURL("group", parcel_data.owner_id, "inspect").getSLURLString(); - mParcelOwner->setText(owner_name); - } - else - { - std::string owner_name = LLSLURL("agent", parcel_data.owner_id, "inspect").getSLURLString(); - mParcelOwner->setText(owner_name); - } - } - else - { - mParcelOwner->setText(getString("public")); - } - - LLSD info; - info["update_verbs"] = true; - info["global_x"] = parcel_data.global_x; - info["global_y"] = parcel_data.global_y; - info["global_z"] = parcel_data.global_z; - notifyParent(info); -} - -void LLPanelLandmarkInfo::displayItemInfo(const LLInventoryItem* pItem) -{ - if (!pItem) - return; - - if(!gCacheName) - return; - - const LLPermissions& perm = pItem->getPermissions(); - - ////////////////// - // CREATOR NAME // - ////////////////// - if (pItem->getCreatorUUID().notNull()) - { - // IDEVO - LLUUID creator_id = pItem->getCreatorUUID(); - std::string name = - LLSLURL("agent", creator_id, "inspect").getSLURLString(); - mCreator->setText(name); - } - else - { - mCreator->setText(getString("unknown")); - } - - //////////////// - // OWNER NAME // - //////////////// - if(perm.isOwned()) - { - std::string name; - if (perm.isGroupOwned()) - { - LLUUID group_id = perm.getGroup(); - name = LLSLURL("group", group_id, "inspect").getSLURLString(); - } - else - { - LLUUID owner_id = perm.getOwner(); - name = LLSLURL("agent", owner_id, "inspect").getSLURLString(); - } - mOwner->setText(name); - } - else - { - std::string public_str = getString("public"); - mOwner->setText(public_str); - } - - ////////////////// - // ACQUIRE DATE // - ////////////////// - time_t time_utc = pItem->getCreationDate(); - if (0 == time_utc) - { - mCreated->setText(getString("unknown")); - } - else - { - std::string timeStr = getString("acquired_date"); - LLSD substitution; - substitution["datetime"] = (S32) time_utc; - LLStringUtil::format (timeStr, substitution); - mCreated->setText(timeStr); - } - - mLandmarkTitle->setText(pItem->getName()); - mLandmarkTitleEditor->setText(pItem->getName()); - mNotesEditor->setText(pItem->getDescription()); -} - -void LLPanelLandmarkInfo::toggleLandmarkEditMode(bool enabled) -{ - // If switching to edit mode while creating landmark - // the "Create Landmark" title remains. - if (enabled && mInfoType != CREATE_LANDMARK) - { - mTitle->setText(getString("title_edit_landmark")); - } - else - { - mTitle->setText(mCurrentTitle); - - mLandmarkTitle->setText(mLandmarkTitleEditor->getText()); - } - - if (mNotesEditor->getReadOnly() == enabled) - { - mLandmarkTitle->setVisible(!enabled); - mLandmarkTitleEditor->setVisible(enabled); - mNotesEditor->setReadOnly(!enabled); - mFolderCombo->setVisible(enabled); - getChild("folder_label")->setVisible(enabled); - getChild("edit_btn")->setVisible(!enabled); - - // HACK: To change the text color in a text editor - // when it was enabled/disabled we set the text once again. - mNotesEditor->setText(mNotesEditor->getText()); - } - - // Prevent the floater from losing focus (if the sidepanel is undocked). - setFocus(true); -} - -void LLPanelLandmarkInfo::setCanEdit(bool enabled) -{ - getChild("edit_btn")->setEnabled(enabled); -} - -const std::string& LLPanelLandmarkInfo::getLandmarkTitle() const -{ - return mLandmarkTitleEditor->getText(); -} - -const std::string LLPanelLandmarkInfo::getLandmarkNotes() const -{ - return mNotesEditor->getText(); -} - -const LLUUID LLPanelLandmarkInfo::getLandmarkFolder() const -{ - return mFolderCombo->getValue().asUUID(); -} - -bool LLPanelLandmarkInfo::setLandmarkFolder(const LLUUID& id) -{ - return mFolderCombo->setCurrentByID(id); -} - -void LLPanelLandmarkInfo::createLandmark(const LLUUID& folder_id) -{ - std::string name = mLandmarkTitleEditor->getText(); - std::string desc = mNotesEditor->getText(); - - LLStringUtil::trim(name); - LLStringUtil::trim(desc); - - // If typed name is empty use the parcel name instead. - if (name.empty()) - { - name = mParcelName->getText(); - - // If no parcel exists use the region name instead. - if (name.empty()) - { - name = mRegionTitle; - } - } - - LLStringUtil::replaceChar(desc, '\n', ' '); - - // If no folder chosen use the "Landmarks" folder. - LLLandmarkActions::createLandmarkHere(name, desc, - folder_id.notNull() ? folder_id : gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK)); -} - -// static -std::string LLPanelLandmarkInfo::getFullFolderName(const LLViewerInventoryCategory* cat) -{ - std::string name; - LLUUID parent_id; - - llassert(cat); - if (cat) - { - name = cat->getName(); - parent_id = cat->getParentUUID(); - bool is_under_root_category = parent_id == gInventory.getRootFolderID(); - - // we don't want "My Inventory" to appear in the name - while ((parent_id = cat->getParentUUID()).notNull()) - { - cat = gInventory.getCategory(parent_id); - llassert(cat); - if (cat) - { - if (is_under_root_category || cat->getParentUUID() == gInventory.getRootFolderID()) - { - std::string localized_name; - - // Looking for translation only for protected type categories - // to avoid warnings about non existent string in strings.xml. - bool is_protected_type = LLFolderType::lookupIsProtectedType(cat->getPreferredType()); - - if (is_under_root_category) - { - // translate category name, if it's right below the root - bool is_found = is_protected_type && LLTrans::findString(localized_name, "InvFolder " + name); - name = is_found ? localized_name : name; - } - else - { - bool is_found = is_protected_type && LLTrans::findString(localized_name, "InvFolder " + cat->getName()); - - // add translated category name to folder's full name - name = (is_found ? localized_name : cat->getName()) + "/" + name; - } - - break; - } - else - { - name = cat->getName() + "/" + name; - } - } - } - } - - return name; -} - -void LLPanelLandmarkInfo::populateFoldersList() -{ - // Collect all folders that can contain landmarks. - LLInventoryModel::cat_array_t cats; - collectLandmarkFolders(cats); - - mFolderCombo->removeall(); - - // Put the "Landmarks" folder first in list. - LLUUID landmarks_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK); - const LLViewerInventoryCategory* lmcat = gInventory.getCategory(landmarks_id); - if (!lmcat) - { - LL_WARNS() << "Cannot find the landmarks folder" << LL_ENDL; - } - else - { - std::string cat_full_name = getFullFolderName(lmcat); - mFolderCombo->add(cat_full_name, lmcat->getUUID()); - } - - typedef std::vector folder_vec_t; - folder_vec_t folders; - // Sort the folders by their full name. - for (S32 i = 0; i < cats.size(); i++) - { - const LLViewerInventoryCategory* cat = cats.at(i); - std::string cat_full_name = getFullFolderName(cat); - folders.push_back(folder_pair_t(cat->getUUID(), cat_full_name)); - } - sort(folders.begin(), folders.end(), cmp_folders); - - // Finally, populate the combobox. - for (folder_vec_t::const_iterator it = folders.begin(); it != folders.end(); it++) - mFolderCombo->add(it->second, LLSD(it->first)); -} - -static bool cmp_folders(const folder_pair_t& left, const folder_pair_t& right) -{ - return left.second < right.second; -} - -void LLPanelLandmarkInfo::collectLandmarkFolders(LLInventoryModel::cat_array_t& cats) -{ - LLUUID landmarks_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK); - - // Add descendent folders of the "Landmarks" category. - LLInventoryModel::item_array_t items; // unused - LLIsType is_category(LLAssetType::AT_CATEGORY); - gInventory.collectDescendentsIf( - landmarks_id, - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - is_category); -} - -/* virtual */ void LLUpdateLandmarkParent::fire(const LLUUID& inv_item_id) -{ - LLInventoryModel::update_list_t update; - LLInventoryModel::LLCategoryUpdate old_folder(mItem->getParentUUID(), -1); - update.push_back(old_folder); - LLInventoryModel::LLCategoryUpdate new_folder(mNewParentId, 1); - update.push_back(new_folder); - gInventory.accountForUpdate(update); - - mItem->setParent(mNewParentId); - mItem->updateParentOnServer(false); - - gInventory.updateItem(mItem); - gInventory.notifyObservers(); -} +/** + * @file llpanellandmarkinfo.cpp + * @brief Displays landmark info in Side Tray. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanellandmarkinfo.h" + +#include "llcombobox.h" +#include "lliconctrl.h" +#include "llinventoryfunctions.h" +#include "lllineeditor.h" +#include "lltextbox.h" +#include "lltexteditor.h" +#include "lltrans.h" + +#include "llagent.h" +#include "llagentui.h" +#include "lllandmarkactions.h" +#include "llparcel.h" +#include "llslurl.h" +#include "llviewerinventory.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" + +//---------------------------------------------------------------------------- +// Aux types and methods +//---------------------------------------------------------------------------- + +typedef std::pair folder_pair_t; + +static bool cmp_folders(const folder_pair_t& left, const folder_pair_t& right); + +static LLPanelInjector t_landmark_info("panel_landmark_info"); + +// Statics for textures filenames +static std::string icon_pg; +static std::string icon_m; +static std::string icon_r; + +LLPanelLandmarkInfo::LLPanelLandmarkInfo() +: LLPanelPlaceInfo() +{} + +// virtual +LLPanelLandmarkInfo::~LLPanelLandmarkInfo() +{} + +// virtual +bool LLPanelLandmarkInfo::postBuild() +{ + LLPanelPlaceInfo::postBuild(); + + mOwner = getChild("owner"); + mCreator = getChild("creator"); + mCreated = getChild("created"); + + mLandmarkTitle = getChild("title_value"); + mLandmarkTitleEditor = getChild("title_editor"); + mNotesEditor = getChild("notes_editor"); + mFolderCombo = getChild("folder_combo"); + + icon_pg = getString("icon_PG"); + icon_m = getString("icon_M"); + icon_r = getString("icon_R"); + + return true; +} + +// virtual +void LLPanelLandmarkInfo::resetLocation() +{ + LLPanelPlaceInfo::resetLocation(); + + std::string loading = LLTrans::getString("LoadingData"); + mCreator->setText(loading); + mOwner->setText(loading); + mCreated->setText(loading); + mLandmarkTitle->setText(LLStringUtil::null); + mLandmarkTitleEditor->setText(LLStringUtil::null); + mNotesEditor->setText(LLStringUtil::null); +} + +// virtual +void LLPanelLandmarkInfo::setInfoType(EInfoType type) +{ + LLUUID dest_folder; + setInfoType(type, dest_folder); +} + +// Sets CREATE_LANDMARK infotype and creates landmark at desired folder +void LLPanelLandmarkInfo::setInfoAndCreateLandmark(const LLUUID& folder_id) +{ + setInfoType(CREATE_LANDMARK, folder_id); +} + +void LLPanelLandmarkInfo::setInfoType(EInfoType type, const LLUUID &folder_id) +{ + LLPanel* landmark_info_panel = getChild("landmark_info_panel"); + + bool is_info_type_create_landmark = type == CREATE_LANDMARK; + + landmark_info_panel->setVisible(type == LANDMARK); + + getChild("folder_label")->setVisible(is_info_type_create_landmark); + getChild("edit_btn")->setVisible(!is_info_type_create_landmark); + mFolderCombo->setVisible(is_info_type_create_landmark); + + switch(type) + { + case CREATE_LANDMARK: + { + mCurrentTitle = getString("title_create_landmark"); + + mLandmarkTitle->setVisible(false); + mLandmarkTitleEditor->setVisible(true); + mNotesEditor->setEnabled(true); + + LLViewerParcelMgr* parcel_mgr = LLViewerParcelMgr::getInstance(); + LLParcel* parcel = parcel_mgr->getAgentParcel(); + std::string name = parcel->getName(); + LLVector3 agent_pos = gAgent.getPositionAgent(); + + if (name.empty()) + { + S32 region_x = ll_round(agent_pos.mV[VX]); + S32 region_y = ll_round(agent_pos.mV[VY]); + S32 region_z = ll_round(agent_pos.mV[VZ]); + + std::string region_name; + LLViewerRegion* region = parcel_mgr->getSelectionRegion(); + if (region) + { + region_name = region->getName(); + } + else + { + std::string desc; + LLAgentUI::buildLocationString(desc, LLAgentUI::LOCATION_FORMAT_NORMAL, agent_pos); + region_name = desc; + } + + mLandmarkTitleEditor->setText(llformat("%s (%d, %d, %d)", + region_name.c_str(), region_x, region_y, region_z)); + } + else + { + mLandmarkTitleEditor->setText(name); + } + + LLUUID owner_id = parcel->getOwnerID(); + if (owner_id.notNull()) + { + if (parcel->getIsGroupOwned()) + { + std::string owner_name = LLSLURL("group", parcel->getGroupID(), "inspect").getSLURLString(); + mParcelOwner->setText(owner_name); + } + else + { + std::string owner_name = LLSLURL("agent", owner_id, "inspect").getSLURLString(); + mParcelOwner->setText(owner_name); + } + } + else + { + mParcelOwner->setText(getString("public")); + } + + // Moved landmark creation here from LLPanelLandmarkInfo::processParcelInfo() + // because we use only agent's current coordinates instead of waiting for + // remote parcel request to complete. + if (!LLLandmarkActions::landmarkAlreadyExists()) + { + createLandmark(folder_id); + } + } + break; + + case LANDMARK: + default: + mCurrentTitle = getString("title_landmark"); + + mLandmarkTitle->setVisible(true); + mLandmarkTitleEditor->setVisible(false); + mNotesEditor->setEnabled(false); + break; + } + + populateFoldersList(); + + // Prevent the floater from losing focus (if the sidepanel is undocked). + setFocus(true); + + LLPanelPlaceInfo::setInfoType(type); +} + +// virtual +void LLPanelLandmarkInfo::processParcelInfo(const LLParcelData& parcel_data) +{ + LLPanelPlaceInfo::processParcelInfo(parcel_data); + + // HACK: Flag 0x2 == adult region, + // Flag 0x1 == mature region, otherwise assume PG + if (parcel_data.flags & 0x2) + { + mMaturityRatingIcon->setValue(icon_r); + mMaturityRatingText->setText(LLViewerRegion::accessToString(SIM_ACCESS_ADULT)); + } + else if (parcel_data.flags & 0x1) + { + mMaturityRatingIcon->setValue(icon_m); + mMaturityRatingText->setText(LLViewerRegion::accessToString(SIM_ACCESS_MATURE)); + } + else + { + mMaturityRatingIcon->setValue(icon_pg); + mMaturityRatingText->setText(LLViewerRegion::accessToString(SIM_ACCESS_PG)); + } + + if (parcel_data.owner_id.notNull()) + { + if (parcel_data.flags & 0x4) // depends onto DRTSIM-453 + { + std::string owner_name = LLSLURL("group", parcel_data.owner_id, "inspect").getSLURLString(); + mParcelOwner->setText(owner_name); + } + else + { + std::string owner_name = LLSLURL("agent", parcel_data.owner_id, "inspect").getSLURLString(); + mParcelOwner->setText(owner_name); + } + } + else + { + mParcelOwner->setText(getString("public")); + } + + LLSD info; + info["update_verbs"] = true; + info["global_x"] = parcel_data.global_x; + info["global_y"] = parcel_data.global_y; + info["global_z"] = parcel_data.global_z; + notifyParent(info); +} + +void LLPanelLandmarkInfo::displayItemInfo(const LLInventoryItem* pItem) +{ + if (!pItem) + return; + + if(!gCacheName) + return; + + const LLPermissions& perm = pItem->getPermissions(); + + ////////////////// + // CREATOR NAME // + ////////////////// + if (pItem->getCreatorUUID().notNull()) + { + // IDEVO + LLUUID creator_id = pItem->getCreatorUUID(); + std::string name = + LLSLURL("agent", creator_id, "inspect").getSLURLString(); + mCreator->setText(name); + } + else + { + mCreator->setText(getString("unknown")); + } + + //////////////// + // OWNER NAME // + //////////////// + if(perm.isOwned()) + { + std::string name; + if (perm.isGroupOwned()) + { + LLUUID group_id = perm.getGroup(); + name = LLSLURL("group", group_id, "inspect").getSLURLString(); + } + else + { + LLUUID owner_id = perm.getOwner(); + name = LLSLURL("agent", owner_id, "inspect").getSLURLString(); + } + mOwner->setText(name); + } + else + { + std::string public_str = getString("public"); + mOwner->setText(public_str); + } + + ////////////////// + // ACQUIRE DATE // + ////////////////// + time_t time_utc = pItem->getCreationDate(); + if (0 == time_utc) + { + mCreated->setText(getString("unknown")); + } + else + { + std::string timeStr = getString("acquired_date"); + LLSD substitution; + substitution["datetime"] = (S32) time_utc; + LLStringUtil::format (timeStr, substitution); + mCreated->setText(timeStr); + } + + mLandmarkTitle->setText(pItem->getName()); + mLandmarkTitleEditor->setText(pItem->getName()); + mNotesEditor->setText(pItem->getDescription()); +} + +void LLPanelLandmarkInfo::toggleLandmarkEditMode(bool enabled) +{ + // If switching to edit mode while creating landmark + // the "Create Landmark" title remains. + if (enabled && mInfoType != CREATE_LANDMARK) + { + mTitle->setText(getString("title_edit_landmark")); + } + else + { + mTitle->setText(mCurrentTitle); + + mLandmarkTitle->setText(mLandmarkTitleEditor->getText()); + } + + if (mNotesEditor->getReadOnly() == enabled) + { + mLandmarkTitle->setVisible(!enabled); + mLandmarkTitleEditor->setVisible(enabled); + mNotesEditor->setReadOnly(!enabled); + mFolderCombo->setVisible(enabled); + getChild("folder_label")->setVisible(enabled); + getChild("edit_btn")->setVisible(!enabled); + + // HACK: To change the text color in a text editor + // when it was enabled/disabled we set the text once again. + mNotesEditor->setText(mNotesEditor->getText()); + } + + // Prevent the floater from losing focus (if the sidepanel is undocked). + setFocus(true); +} + +void LLPanelLandmarkInfo::setCanEdit(bool enabled) +{ + getChild("edit_btn")->setEnabled(enabled); +} + +const std::string& LLPanelLandmarkInfo::getLandmarkTitle() const +{ + return mLandmarkTitleEditor->getText(); +} + +const std::string LLPanelLandmarkInfo::getLandmarkNotes() const +{ + return mNotesEditor->getText(); +} + +const LLUUID LLPanelLandmarkInfo::getLandmarkFolder() const +{ + return mFolderCombo->getValue().asUUID(); +} + +bool LLPanelLandmarkInfo::setLandmarkFolder(const LLUUID& id) +{ + return mFolderCombo->setCurrentByID(id); +} + +void LLPanelLandmarkInfo::createLandmark(const LLUUID& folder_id) +{ + std::string name = mLandmarkTitleEditor->getText(); + std::string desc = mNotesEditor->getText(); + + LLStringUtil::trim(name); + LLStringUtil::trim(desc); + + // If typed name is empty use the parcel name instead. + if (name.empty()) + { + name = mParcelName->getText(); + + // If no parcel exists use the region name instead. + if (name.empty()) + { + name = mRegionTitle; + } + } + + LLStringUtil::replaceChar(desc, '\n', ' '); + + // If no folder chosen use the "Landmarks" folder. + LLLandmarkActions::createLandmarkHere(name, desc, + folder_id.notNull() ? folder_id : gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK)); +} + +// static +std::string LLPanelLandmarkInfo::getFullFolderName(const LLViewerInventoryCategory* cat) +{ + std::string name; + LLUUID parent_id; + + llassert(cat); + if (cat) + { + name = cat->getName(); + parent_id = cat->getParentUUID(); + bool is_under_root_category = parent_id == gInventory.getRootFolderID(); + + // we don't want "My Inventory" to appear in the name + while ((parent_id = cat->getParentUUID()).notNull()) + { + cat = gInventory.getCategory(parent_id); + llassert(cat); + if (cat) + { + if (is_under_root_category || cat->getParentUUID() == gInventory.getRootFolderID()) + { + std::string localized_name; + + // Looking for translation only for protected type categories + // to avoid warnings about non existent string in strings.xml. + bool is_protected_type = LLFolderType::lookupIsProtectedType(cat->getPreferredType()); + + if (is_under_root_category) + { + // translate category name, if it's right below the root + bool is_found = is_protected_type && LLTrans::findString(localized_name, "InvFolder " + name); + name = is_found ? localized_name : name; + } + else + { + bool is_found = is_protected_type && LLTrans::findString(localized_name, "InvFolder " + cat->getName()); + + // add translated category name to folder's full name + name = (is_found ? localized_name : cat->getName()) + "/" + name; + } + + break; + } + else + { + name = cat->getName() + "/" + name; + } + } + } + } + + return name; +} + +void LLPanelLandmarkInfo::populateFoldersList() +{ + // Collect all folders that can contain landmarks. + LLInventoryModel::cat_array_t cats; + collectLandmarkFolders(cats); + + mFolderCombo->removeall(); + + // Put the "Landmarks" folder first in list. + LLUUID landmarks_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK); + const LLViewerInventoryCategory* lmcat = gInventory.getCategory(landmarks_id); + if (!lmcat) + { + LL_WARNS() << "Cannot find the landmarks folder" << LL_ENDL; + } + else + { + std::string cat_full_name = getFullFolderName(lmcat); + mFolderCombo->add(cat_full_name, lmcat->getUUID()); + } + + typedef std::vector folder_vec_t; + folder_vec_t folders; + // Sort the folders by their full name. + for (S32 i = 0; i < cats.size(); i++) + { + const LLViewerInventoryCategory* cat = cats.at(i); + std::string cat_full_name = getFullFolderName(cat); + folders.push_back(folder_pair_t(cat->getUUID(), cat_full_name)); + } + sort(folders.begin(), folders.end(), cmp_folders); + + // Finally, populate the combobox. + for (folder_vec_t::const_iterator it = folders.begin(); it != folders.end(); it++) + mFolderCombo->add(it->second, LLSD(it->first)); +} + +static bool cmp_folders(const folder_pair_t& left, const folder_pair_t& right) +{ + return left.second < right.second; +} + +void LLPanelLandmarkInfo::collectLandmarkFolders(LLInventoryModel::cat_array_t& cats) +{ + LLUUID landmarks_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK); + + // Add descendent folders of the "Landmarks" category. + LLInventoryModel::item_array_t items; // unused + LLIsType is_category(LLAssetType::AT_CATEGORY); + gInventory.collectDescendentsIf( + landmarks_id, + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + is_category); +} + +/* virtual */ void LLUpdateLandmarkParent::fire(const LLUUID& inv_item_id) +{ + LLInventoryModel::update_list_t update; + LLInventoryModel::LLCategoryUpdate old_folder(mItem->getParentUUID(), -1); + update.push_back(old_folder); + LLInventoryModel::LLCategoryUpdate new_folder(mNewParentId, 1); + update.push_back(new_folder); + gInventory.accountForUpdate(update); + + mItem->setParent(mNewParentId); + mItem->updateParentOnServer(false); + + gInventory.updateItem(mItem); + gInventory.notifyObservers(); +} diff --git a/indra/newview/llpanellandmarkinfo.h b/indra/newview/llpanellandmarkinfo.h index 5d169f3f16..af47af06ac 100644 --- a/indra/newview/llpanellandmarkinfo.h +++ b/indra/newview/llpanellandmarkinfo.h @@ -1,105 +1,105 @@ -/** - * @file llpanellandmarkinfo.h - * @brief Displays landmark info in Side Tray. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELLANDMARKINFO_H -#define LL_LLPANELLANDMARKINFO_H - -#include "llpanelplaceinfo.h" -#include "llinventorymodel.h" - -class LLComboBox; -class LLLineEditor; -class LLTextEditor; - -class LLPanelLandmarkInfo : public LLPanelPlaceInfo -{ -public: - LLPanelLandmarkInfo(); - /*virtual*/ ~LLPanelLandmarkInfo(); - - /*virtual*/ bool postBuild(); - - /*virtual*/ void resetLocation(); - - // If landmark doesn't exists, will create it at default folder - /*virtual*/ void setInfoType(EInfoType type); - - // Sets CREATE_LANDMARK infotype and creates landmark at desired folder - void setInfoAndCreateLandmark(const LLUUID& folder_id); - - /*virtual*/ void processParcelInfo(const LLParcelData& parcel_data); - - // Displays landmark owner, creator and creation date info. - void displayItemInfo(const LLInventoryItem* pItem); - - void toggleLandmarkEditMode(bool enabled); - void setCanEdit(bool enabled); - - const std::string& getLandmarkTitle() const; - const std::string getLandmarkNotes() const; - const LLUUID getLandmarkFolder() const; - - // Select current landmark folder in combobox. - bool setLandmarkFolder(const LLUUID& id); - - typedef std::vector > cat_array_t; - static std::string getFullFolderName(const LLViewerInventoryCategory* cat); - static void collectLandmarkFolders(LLInventoryModel::cat_array_t& cats); - -private: - // Create a landmark for the current location - // in a folder specified by folder_id. - // Expects title and description to be initialized - void createLandmark(const LLUUID& folder_id); - - // If landmark doesn't exists, will create it at specified folder - void setInfoType(EInfoType type, const LLUUID &folder_id); - - void populateFoldersList(); - - LLTextBox* mOwner; - LLTextBox* mCreator; - LLTextBox* mCreated; - LLLineEditor* mLandmarkTitle; - LLLineEditor* mLandmarkTitleEditor; - LLTextEditor* mNotesEditor; - LLComboBox* mFolderCombo; -}; - -class LLUpdateLandmarkParent : public LLInventoryCallback -{ -public: - LLUpdateLandmarkParent(LLPointer item, LLUUID new_parent) : - mItem(item), - mNewParentId(new_parent) - {}; - /* virtual */ void fire(const LLUUID& inv_item_id); - -private: - LLPointer mItem; - LLUUID mNewParentId; -}; -#endif // LL_LLPANELLANDMARKINFO_H +/** + * @file llpanellandmarkinfo.h + * @brief Displays landmark info in Side Tray. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELLANDMARKINFO_H +#define LL_LLPANELLANDMARKINFO_H + +#include "llpanelplaceinfo.h" +#include "llinventorymodel.h" + +class LLComboBox; +class LLLineEditor; +class LLTextEditor; + +class LLPanelLandmarkInfo : public LLPanelPlaceInfo +{ +public: + LLPanelLandmarkInfo(); + /*virtual*/ ~LLPanelLandmarkInfo(); + + /*virtual*/ bool postBuild(); + + /*virtual*/ void resetLocation(); + + // If landmark doesn't exists, will create it at default folder + /*virtual*/ void setInfoType(EInfoType type); + + // Sets CREATE_LANDMARK infotype and creates landmark at desired folder + void setInfoAndCreateLandmark(const LLUUID& folder_id); + + /*virtual*/ void processParcelInfo(const LLParcelData& parcel_data); + + // Displays landmark owner, creator and creation date info. + void displayItemInfo(const LLInventoryItem* pItem); + + void toggleLandmarkEditMode(bool enabled); + void setCanEdit(bool enabled); + + const std::string& getLandmarkTitle() const; + const std::string getLandmarkNotes() const; + const LLUUID getLandmarkFolder() const; + + // Select current landmark folder in combobox. + bool setLandmarkFolder(const LLUUID& id); + + typedef std::vector > cat_array_t; + static std::string getFullFolderName(const LLViewerInventoryCategory* cat); + static void collectLandmarkFolders(LLInventoryModel::cat_array_t& cats); + +private: + // Create a landmark for the current location + // in a folder specified by folder_id. + // Expects title and description to be initialized + void createLandmark(const LLUUID& folder_id); + + // If landmark doesn't exists, will create it at specified folder + void setInfoType(EInfoType type, const LLUUID &folder_id); + + void populateFoldersList(); + + LLTextBox* mOwner; + LLTextBox* mCreator; + LLTextBox* mCreated; + LLLineEditor* mLandmarkTitle; + LLLineEditor* mLandmarkTitleEditor; + LLTextEditor* mNotesEditor; + LLComboBox* mFolderCombo; +}; + +class LLUpdateLandmarkParent : public LLInventoryCallback +{ +public: + LLUpdateLandmarkParent(LLPointer item, LLUUID new_parent) : + mItem(item), + mNewParentId(new_parent) + {}; + /* virtual */ void fire(const LLUUID& inv_item_id); + +private: + LLPointer mItem; + LLUUID mNewParentId; +}; +#endif // LL_LLPANELLANDMARKINFO_H diff --git a/indra/newview/llpanellandmarks.cpp b/indra/newview/llpanellandmarks.cpp index 8826c2eec0..99133d1fb3 100644 --- a/indra/newview/llpanellandmarks.cpp +++ b/indra/newview/llpanellandmarks.cpp @@ -1,1248 +1,1248 @@ -/** - * @file llpanellandmarks.cpp - * @brief Landmarks tab for Side Bar "Places" panel - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanellandmarks.h" - -#include "llbutton.h" -#include "llfloaterprofile.h" -#include "llfloaterreg.h" -#include "llnotificationsutil.h" -#include "llsdutil.h" -#include "llsdutil_math.h" -#include "llregionhandle.h" - -#include "llaccordionctrl.h" -#include "llagent.h" -#include "llagentpicksinfo.h" -#include "llagentui.h" -#include "llavataractions.h" -#include "llcallbacklist.h" -#include "llfloatersidepanelcontainer.h" -#include "llfloaterworldmap.h" -#include "llfolderviewitem.h" -#include "llinventorymodelbackgroundfetch.h" -#include "llinventorypanel.h" -#include "llinventoryfunctions.h" -#include "lllandmarkactions.h" -#include "llmenubutton.h" -#include "llplacesinventorybridge.h" -#include "llplacesinventorypanel.h" -#include "llplacesfolderview.h" -#include "lltoggleablemenu.h" -#include "llviewermenu.h" -#include "llviewerregion.h" - -// Not yet implemented; need to remove buildPanel() from constructor when we switch -//static LLRegisterPanelClassWrapper t_landmarks("panel_landmarks"); - -// helper functions -static void filter_list(LLPlacesInventoryPanel* inventory_list, const std::string& string); -static void collapse_all_folders(LLFolderView* root_folder); -static void expand_all_folders(LLFolderView* root_folder); -static bool has_expanded_folders(LLFolderView* root_folder); -static bool has_collapsed_folders(LLFolderView* root_folder); -static void toggle_restore_menu(LLMenuGL* menu, bool visible, bool enabled); - -/** - * Functor counting expanded and collapsed folders in folder view tree to know - * when to enable or disable "Expand all folders" and "Collapse all folders" commands. - */ -class LLCheckFolderState : public LLFolderViewFunctor -{ -public: - LLCheckFolderState() - : mCollapsedFolders(0), - mExpandedFolders(0) - {} - virtual ~LLCheckFolderState() {} - virtual void doFolder(LLFolderViewFolder* folder); - virtual void doItem(LLFolderViewItem* item) {} - S32 getCollapsedFolders() { return mCollapsedFolders; } - S32 getExpandedFolders() { return mExpandedFolders; } - -private: - S32 mCollapsedFolders; - S32 mExpandedFolders; -}; - -// virtual -void LLCheckFolderState::doFolder(LLFolderViewFolder* folder) -{ - // Counting only folders that pass the filter. - // The listener check allow us to avoid counting the folder view - // object itself because it has no listener assigned. - if (folder->getViewModelItem()->descendantsPassedFilter()) - { - if (folder->isOpen()) - { - ++mExpandedFolders; - } - else - { - ++mCollapsedFolders; - } - } -} - -// Functor searching and opening a folder specified by UUID -// in a folder view tree. -class LLOpenFolderByID : public LLFolderViewFunctor -{ -public: - LLOpenFolderByID(const LLUUID& folder_id) - : mFolderID(folder_id) - , mIsFolderOpen(false) - {} - virtual ~LLOpenFolderByID() {} - /*virtual*/ void doFolder(LLFolderViewFolder* folder); - /*virtual*/ void doItem(LLFolderViewItem* item) {} - - bool isFolderOpen() { return mIsFolderOpen; } - -private: - bool mIsFolderOpen; - LLUUID mFolderID; -}; - -// virtual -void LLOpenFolderByID::doFolder(LLFolderViewFolder* folder) -{ - if (folder->getViewModelItem() && static_cast(folder->getViewModelItem())->getUUID() == mFolderID) - { - if (!folder->isOpen()) - { - folder->setOpen(true); - mIsFolderOpen = true; - } - } -} - -LLLandmarksPanel::LLLandmarksPanel() - : LLPanelPlacesTab() - , mLandmarksInventoryPanel(NULL) - , mCurrentSelectedList(NULL) - , mGearFolderMenu(NULL) - , mGearLandmarkMenu(NULL) - , mSortingMenu(NULL) - , mAddMenu(NULL) - , isLandmarksPanel(true) -{ - buildFromFile("panel_landmarks.xml"); -} - -LLLandmarksPanel::LLLandmarksPanel(bool is_landmark_panel) - : LLPanelPlacesTab() - , mLandmarksInventoryPanel(NULL) - , mCurrentSelectedList(NULL) - , mGearFolderMenu(NULL) - , mGearLandmarkMenu(NULL) - , mSortingMenu(NULL) - , mAddMenu(NULL) - , isLandmarksPanel(is_landmark_panel) -{ - if (is_landmark_panel) - { - buildFromFile("panel_landmarks.xml"); - } -} - -LLLandmarksPanel::~LLLandmarksPanel() -{ -} - -bool LLLandmarksPanel::postBuild() -{ - if (!gInventory.isInventoryUsable()) - return false; - - // mast be called before any other initXXX methods to init Gear menu - initListCommandsHandlers(); - initLandmarksInventoryPanel(); - - return true; -} - -// virtual -void LLLandmarksPanel::onSearchEdit(const std::string& string) -{ - filter_list(mCurrentSelectedList, string); - - if (sFilterSubString != string) - sFilterSubString = string; -} - -// virtual -void LLLandmarksPanel::onShowOnMap() -{ - if (NULL == mCurrentSelectedList) - { - LL_WARNS() << "There are no selected list. No actions are performed." << LL_ENDL; - return; - } - - doActionOnCurSelectedLandmark(boost::bind(&LLLandmarksPanel::doShowOnMap, this, _1)); -} - -//virtual -void LLLandmarksPanel::onShowProfile() -{ - LLFolderViewModelItemInventory* cur_item = getCurSelectedViewModelItem(); - - if(!cur_item) - return; - - cur_item->performAction(mCurrentSelectedList->getModel(),"about"); -} - -// virtual -void LLLandmarksPanel::onTeleport() -{ - LLFolderViewModelItemInventory* view_model_item = getCurSelectedViewModelItem(); - if (view_model_item && view_model_item->getInventoryType() == LLInventoryType::IT_LANDMARK) - { - view_model_item->openItem(); - } -} - -/*virtual*/ -void LLLandmarksPanel::onRemoveSelected() -{ - onClipboardAction("delete"); -} - -// virtual -bool LLLandmarksPanel::isSingleItemSelected() -{ - bool result = false; - - if (mCurrentSelectedList != NULL) - { - LLFolderView* root_view = mCurrentSelectedList->getRootFolder(); - - if (root_view->getSelectedCount() == 1) - { - result = isLandmarkSelected(); - } - } - - return result; -} - -// virtual -LLToggleableMenu* LLLandmarksPanel::getSelectionMenu() -{ - LLToggleableMenu* menu = mGearFolderMenu; - - if (mCurrentSelectedList) - { - LLFolderViewModelItemInventory* listenerp = getCurSelectedViewModelItem(); - if (!listenerp) - return menu; - - if (listenerp->getInventoryType() == LLInventoryType::IT_LANDMARK) - { - menu = mGearLandmarkMenu; - } - } - return menu; -} - -// virtual -LLToggleableMenu* LLLandmarksPanel::getSortingMenu() -{ - return mSortingMenu; -} - -// virtual -LLToggleableMenu* LLLandmarksPanel::getCreateMenu() -{ - return mAddMenu; -} - -void LLLandmarksPanel::updateVerbs() -{ - if (sRemoveBtn) - { - sRemoveBtn->setEnabled(isActionEnabled("delete") && (isFolderSelected() || isLandmarkSelected())); - } -} - -void LLLandmarksPanel::setItemSelected(const LLUUID& obj_id, bool take_keyboard_focus) -{ - if (!mCurrentSelectedList) - return; - - LLFolderView* root = mCurrentSelectedList->getRootFolder(); - LLFolderViewItem* item = mCurrentSelectedList->getItemByID(obj_id); - if (!item) - return; - root->setSelection(item, false, take_keyboard_focus); - root->scrollToShowSelection(); -} - -////////////////////////////////////////////////////////////////////////// -// PROTECTED METHODS -////////////////////////////////////////////////////////////////////////// - -bool LLLandmarksPanel::isLandmarkSelected() const -{ - LLFolderViewModelItemInventory* current_item = getCurSelectedViewModelItem(); - return current_item && (current_item->getInventoryType() == LLInventoryType::IT_LANDMARK); -} - -bool LLLandmarksPanel::isFolderSelected() const -{ - LLFolderViewModelItemInventory* current_item = getCurSelectedViewModelItem(); - return current_item && (current_item->getInventoryType() == LLInventoryType::IT_CATEGORY); -} - -void LLLandmarksPanel::doActionOnCurSelectedLandmark(LLLandmarkList::loaded_callback_t cb) -{ - LLFolderViewModelItemInventory* cur_item = getCurSelectedViewModelItem(); - if(cur_item && cur_item->getInventoryType() == LLInventoryType::IT_LANDMARK) - { - LLLandmark* landmark = LLLandmarkActions::getLandmark(cur_item->getUUID(), cb); - if (landmark) - { - cb(landmark); - } - } -} - -LLFolderViewItem* LLLandmarksPanel::getCurSelectedItem() const -{ - return mCurrentSelectedList ? mCurrentSelectedList->getRootFolder()->getCurSelectedItem() : NULL; -} - -LLFolderViewModelItemInventory* LLLandmarksPanel::getCurSelectedViewModelItem() const -{ - LLFolderViewItem* cur_item = getCurSelectedItem(); - if (cur_item) - { - return static_cast(cur_item->getViewModelItem()); - } - return NULL; -} - - -void LLLandmarksPanel::updateSortOrder(LLInventoryPanel* panel, bool byDate) -{ - if(!panel) return; - - U32 order = panel->getSortOrder(); - if (byDate) - { - panel->setSortOrder( order | LLInventoryFilter::SO_DATE ); - } - else - { - panel->setSortOrder( order & ~LLInventoryFilter::SO_DATE ); - } -} - -void LLLandmarksPanel::resetSelection() -{ -} - -// virtual -void LLLandmarksPanel::processParcelInfo(const LLParcelData& parcel_data) -{ - //this function will be called after user will try to create a pick for selected landmark. - // We have to make request to sever to get parcel_id and snaption_id. - if(mCreatePickItemId.notNull()) - { - LLInventoryItem* inv_item = gInventory.getItem(mCreatePickItemId); - - if (inv_item && inv_item->getInventoryType() == LLInventoryType::IT_LANDMARK) - { - // we are processing response for doCreatePick, landmark should be already loaded - LLLandmark* landmark = LLLandmarkActions::getLandmark(inv_item->getUUID()); - if (landmark) - { - doProcessParcelInfo(landmark, inv_item, parcel_data); - } - } - mCreatePickItemId.setNull(); - } -} - -// virtual -void LLLandmarksPanel::setParcelID(const LLUUID& parcel_id) -{ - if (!parcel_id.isNull()) - { - LLRemoteParcelInfoProcessor::getInstance()->addObserver(parcel_id, this); - LLRemoteParcelInfoProcessor::getInstance()->sendParcelInfoRequest(parcel_id); - } -} - -// virtual -void LLLandmarksPanel::setErrorStatus(S32 status, const std::string& reason) -{ - LL_WARNS() << "Can't handle remote parcel request."<< " Http Status: "<< status << ". Reason : "<< reason<("landmarks_list"); - - initLandmarksPanel(mLandmarksInventoryPanel); - - mLandmarksInventoryPanel->setShowFolderState(LLInventoryFilter::SHOW_ALL_FOLDERS); - - // subscribe to have auto-rename functionality while creating New Folder - mLandmarksInventoryPanel->setSelectCallback(boost::bind(&LLInventoryPanel::onSelectionChange, mLandmarksInventoryPanel, _1, _2)); - - mCurrentSelectedList = mLandmarksInventoryPanel; -} - -void LLLandmarksPanel::initLandmarksPanel(LLPlacesInventoryPanel* inventory_list) -{ - inventory_list->getFilter().setEmptyLookupMessage("PlacesNoMatchingItems"); - inventory_list->setFilterTypes(0x1 << LLInventoryType::IT_LANDMARK); - inventory_list->setSelectCallback(boost::bind(&LLLandmarksPanel::updateVerbs, this)); - - inventory_list->setShowFolderState(LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); - bool sorting_order = gSavedSettings.getBOOL("LandmarksSortedByDate"); - updateSortOrder(inventory_list, sorting_order); - - LLPlacesFolderView* root_folder = dynamic_cast(inventory_list->getRootFolder()); - if (root_folder) - { - if (mGearFolderMenu) - { - root_folder->setupMenuHandle(LLInventoryType::IT_CATEGORY, mGearFolderMenu->getHandle()); - } - if (mGearLandmarkMenu) - { - root_folder->setupMenuHandle(LLInventoryType::IT_LANDMARK, mGearLandmarkMenu->getHandle()); - } - - root_folder->setParentLandmarksPanel(this); - } - - inventory_list->saveFolderState(); -} - - -// List Commands Handlers -void LLLandmarksPanel::initListCommandsHandlers() -{ - mCommitCallbackRegistrar.add("Places.LandmarksGear.Add.Action", boost::bind(&LLLandmarksPanel::onAddAction, this, _2)); - mCommitCallbackRegistrar.add("Places.LandmarksGear.CopyPaste.Action", boost::bind(&LLLandmarksPanel::onClipboardAction, this, _2)); - mCommitCallbackRegistrar.add("Places.LandmarksGear.Custom.Action", boost::bind(&LLLandmarksPanel::onCustomAction, this, _2)); - mCommitCallbackRegistrar.add("Places.LandmarksGear.Folding.Action", boost::bind(&LLLandmarksPanel::onFoldingAction, this, _2)); - mEnableCallbackRegistrar.add("Places.LandmarksGear.Check", boost::bind(&LLLandmarksPanel::isActionChecked, this, _2)); - mEnableCallbackRegistrar.add("Places.LandmarksGear.Enable", boost::bind(&LLLandmarksPanel::isActionEnabled, this, _2)); - mGearLandmarkMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_places_gear_landmark.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - mGearFolderMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_places_gear_folder.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - mSortingMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_places_gear_sorting.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - mAddMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_place_add_button.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - - if (mGearLandmarkMenu) - { - mGearLandmarkMenu->setVisibilityChangeCallback(boost::bind(&LLLandmarksPanel::onMenuVisibilityChange, this, _1, _2)); - // show menus even if all items are disabled - mGearLandmarkMenu->setAlwaysShowMenu(true); - } // Else corrupted files? - - if (mGearFolderMenu) - { - mGearFolderMenu->setVisibilityChangeCallback(boost::bind(&LLLandmarksPanel::onMenuVisibilityChange, this, _1, _2)); - mGearFolderMenu->setAlwaysShowMenu(true); - } - - if (mAddMenu) - { - mAddMenu->setAlwaysShowMenu(true); - } -} - -void LLLandmarksPanel::updateMenuVisibility(LLUICtrl* menu) -{ - onMenuVisibilityChange(menu, LLSD().with("visibility", true)); -} - -void LLLandmarksPanel::onTrashButtonClick() const -{ - onClipboardAction("delete"); -} - -void LLLandmarksPanel::onAddAction(const LLSD& userdata) const -{ - LLFolderViewModelItemInventory* view_model = getCurSelectedViewModelItem(); - LLFolderViewItem* item = getCurSelectedItem(); - - std::string command_name = userdata.asString(); - if("add_landmark" == command_name - || "add_landmark_root" == command_name) - { - LLViewerInventoryItem* landmark = LLLandmarkActions::findLandmarkForAgentPos(); - if(landmark) - { - LLNotificationsUtil::add("LandmarkAlreadyExists"); - } - else - { - LLSD args; - args["type"] = "create_landmark"; - if ("add_landmark" == command_name - && view_model->getInventoryType() == LLInventoryType::IT_CATEGORY) - { - args["dest_folder"] = view_model->getUUID(); - } - if ("add_landmark_root" == command_name - && mCurrentSelectedList == mLandmarksInventoryPanel) - { - args["dest_folder"] = mLandmarksInventoryPanel->getRootFolderID(); - } - // else will end up in favorites - LLFloaterReg::showInstance("add_landmark", args); - } - } - else if ("category" == command_name) - { - if (item && mCurrentSelectedList == mLandmarksInventoryPanel) - { - LLFolderViewModelItem* folder_bridge = NULL; - - if (view_model->getInventoryType() - == LLInventoryType::IT_LANDMARK) - { - // for a landmark get parent folder bridge - folder_bridge = item->getParentFolder()->getViewModelItem(); - } - else if (view_model->getInventoryType() - == LLInventoryType::IT_CATEGORY) - { - // for a folder get its own bridge - folder_bridge = view_model; - } - - menu_create_inventory_item(mCurrentSelectedList, - dynamic_cast (folder_bridge), LLSD( - "category"), gInventory.findCategoryUUIDForType( - LLFolderType::FT_LANDMARK)); - } - else - { - //in case My Landmarks tab is completely empty (thus cannot be determined as being selected) - menu_create_inventory_item(mLandmarksInventoryPanel, NULL, LLSD("category"), - gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK)); - } - } - else if ("category_root" == command_name) - { - //in case My Landmarks tab is completely empty (thus cannot be determined as being selected) - menu_create_inventory_item(mLandmarksInventoryPanel, NULL, LLSD("category"), - gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK)); - } -} - -void LLLandmarksPanel::onClipboardAction(const LLSD& userdata) const -{ - if(!mCurrentSelectedList) - return; - std::string command_name = userdata.asString(); - if("copy_slurl" == command_name) - { - LLFolderViewModelItemInventory* cur_item = getCurSelectedViewModelItem(); - if(cur_item) - LLLandmarkActions::copySLURLtoClipboard(cur_item->getUUID()); - } - else if ( "paste" == command_name) - { - mCurrentSelectedList->getRootFolder()->paste(); - } - else if ( "cut" == command_name) - { - mCurrentSelectedList->getRootFolder()->cut(); - } - else - { - mCurrentSelectedList->doToSelected(command_name); - } -} - -void LLLandmarksPanel::onFoldingAction(const LLSD& userdata) -{ - std::string command_name = userdata.asString(); - - if ("expand_all" == command_name) - { - expand_all_folders(mCurrentSelectedList->getRootFolder()); - } - else if ("collapse_all" == command_name) - { - collapse_all_folders(mCurrentSelectedList->getRootFolder()); - } - else if ("sort_by_date" == command_name) - { - bool sorting_order = gSavedSettings.getBOOL("LandmarksSortedByDate"); - sorting_order=!sorting_order; - gSavedSettings.setBOOL("LandmarksSortedByDate",sorting_order); - updateSortOrder(mLandmarksInventoryPanel, sorting_order); - } - else - { - if(mCurrentSelectedList) - { - mCurrentSelectedList->doToSelected(userdata); - } - } -} - -bool LLLandmarksPanel::isActionChecked(const LLSD& userdata) const -{ - const std::string command_name = userdata.asString(); - - if ( "sort_by_date" == command_name) - { - bool sorting_order = gSavedSettings.getBOOL("LandmarksSortedByDate"); - return sorting_order; - } - - return false; -} - -bool LLLandmarksPanel::isActionEnabled(const LLSD& userdata) const -{ - std::string command_name = userdata.asString(); - - LLFolderView* root_folder_view = mCurrentSelectedList - ? mCurrentSelectedList->getRootFolder() - : NULL; - - bool is_single_selection = root_folder_view && root_folder_view->getSelectedCount() == 1; - - if ("collapse_all" == command_name) - { - return has_expanded_folders(mCurrentSelectedList->getRootFolder()); - } - else if ("expand_all" == command_name) - { - return has_collapsed_folders(mCurrentSelectedList->getRootFolder()); - } - else if ("sort_by_date" == command_name) - { - // disable "sort_by_date" for Favorites tab because - // it has its own items order. EXT-1758 - if (!isLandmarksPanel) - { - return false; - } - } - else if ( "paste" == command_name - || "cut" == command_name - || "copy" == command_name - || "delete" == command_name - || "collapse" == command_name - || "expand" == command_name - ) - { - if (!root_folder_view) return false; - - std::set selected_uuids = root_folder_view->getSelectionList(); - - if (selected_uuids.empty()) - { - return false; - } - - // Allow to execute the command only if it can be applied to all selected items. - for (std::set::const_iterator iter = selected_uuids.begin(); iter != selected_uuids.end(); ++iter) - { - LLFolderViewItem* item = *iter; - - if (!item) return false; - - if (!canItemBeModified(command_name, item)) return false; - } - - return true; - } - else if ( "teleport" == command_name - || "more_info" == command_name - || "show_on_map" == command_name - || "copy_slurl" == command_name - || "rename" == command_name - ) - { - // disable some commands for multi-selection. EXT-1757 - if (!is_single_selection) - { - return false; - } - - if ("show_on_map" == command_name) - { - LLFolderViewModelItemInventory* cur_item = getCurSelectedViewModelItem(); - if (!cur_item) return false; - - LLViewerInventoryItem* inv_item = dynamic_cast(cur_item->getInventoryObject()); - if (!inv_item) return false; - - LLUUID asset_uuid = inv_item->getAssetUUID(); - if (asset_uuid.isNull()) return false; - - // Disable "Show on Map" if landmark loading is in progress. - return !gLandmarkList.isAssetInLoadedCallbackMap(asset_uuid); - } - else if ("rename" == command_name) - { - LLFolderViewItem* selected_item = getCurSelectedItem(); - if (!selected_item) return false; - - return canItemBeModified(command_name, selected_item); - } - - return true; - } - if ("category_root" == command_name || "category" == command_name) - { - // we can add folder only in Landmarks tab - return isLandmarksPanel; - } - else if("create_pick" == command_name) - { - if (mCurrentSelectedList) - { - std::set selection = mCurrentSelectedList->getRootFolder()->getSelectionList(); - if (!selection.empty()) - { - return ( 1 == selection.size() && !LLAgentPicksInfo::getInstance()->isPickLimitReached() ); - } - } - return false; - } - else if ("add_landmark" == command_name) - { - if (!is_single_selection) - { - return false; - } - - LLFolderViewModelItemInventory* view_model = getCurSelectedViewModelItem(); - if (!view_model || view_model->getInventoryType() != LLInventoryType::IT_CATEGORY) - { - return false; - } - LLViewerInventoryItem* landmark = LLLandmarkActions::findLandmarkForAgentPos(); - if (landmark) - { - //already exists - return false; - } - return true; - } - else if ("add_landmark_root" == command_name) - { - LLViewerInventoryItem* landmark = LLLandmarkActions::findLandmarkForAgentPos(); - if (landmark) - { - //already exists - return false; - } - return true; - } - else if ("share" == command_name) - { - if (!mCurrentSelectedList) - { - return false; - } - if (!LLAvatarActions::canShareSelectedItems(mCurrentSelectedList)) - { - return false; - } - return true; - } - else if (command_name == "move_to_landmarks" || command_name == "move_to_favorites") - { - LLFolderViewModelItemInventory* cur_item_model = getCurSelectedViewModelItem(); - if (cur_item_model) - { - LLFolderType::EType folder_type = command_name == "move_to_landmarks" ? LLFolderType::FT_FAVORITE : LLFolderType::FT_LANDMARK; - if (!gInventory.isObjectDescendentOf(cur_item_model->getUUID(), gInventory.findCategoryUUIDForType(folder_type))) - { - return false; - } - - if (root_folder_view) - { - std::set selected_uuids = root_folder_view->getSelectionList(); - for (std::set::const_iterator iter = selected_uuids.begin(); iter != selected_uuids.end(); ++iter) - { - LLFolderViewItem* item = *iter; - if (!item) return false; - - cur_item_model = static_cast(item->getViewModelItem()); - if (!cur_item_model || cur_item_model->getInventoryType() != LLInventoryType::IT_LANDMARK) - { - return false; - } - } - return true; - } - } - return false; - } - else - { - LL_WARNS() << "Unprocessed command has come: " << command_name << LL_ENDL; - } - - return true; -} - -void LLLandmarksPanel::onCustomAction(const LLSD& userdata) -{ - std::string command_name = userdata.asString(); - if("more_info" == command_name) - { - onShowProfile(); - } - else if ("teleport" == command_name) - { - onTeleport(); - } - else if ("show_on_map" == command_name) - { - onShowOnMap(); - } - else if ("create_pick" == command_name) - { - LLFolderViewModelItemInventory* cur_item = getCurSelectedViewModelItem(); - if (cur_item) - { - doActionOnCurSelectedLandmark(boost::bind(&LLLandmarksPanel::doCreatePick, this, _1, cur_item->getUUID())); - } - } - else if ("share" == command_name && mCurrentSelectedList) - { - LLAvatarActions::shareWithAvatars(mCurrentSelectedList); - } - else if ("restore" == command_name && mCurrentSelectedList) - { - mCurrentSelectedList->doToSelected(userdata); - } - else if (command_name == "move_to_landmarks" || command_name == "move_to_favorites") - { - LLFolderView* root_folder_view = mCurrentSelectedList ? mCurrentSelectedList->getRootFolder() : NULL; - if (root_folder_view) - { - LLFolderType::EType folder_type = command_name == "move_to_landmarks" ? LLFolderType::FT_LANDMARK : LLFolderType::FT_FAVORITE; - std::set selected_uuids = root_folder_view->getSelectionList(); - for (std::set::const_iterator iter = selected_uuids.begin(); iter != selected_uuids.end(); ++iter) - { - LLFolderViewItem* item = *iter; - if (item) - { - LLFolderViewModelItemInventory* item_model = static_cast(item->getViewModelItem()); - if (item_model) - { - change_item_parent(item_model->getUUID(), gInventory.findCategoryUUIDForType(folder_type)); - } - } - } - } - - } -} - -void LLLandmarksPanel::onMenuVisibilityChange(LLUICtrl* ctrl, const LLSD& param) -{ - bool new_visibility = param["visibility"].asBoolean(); - - // We don't have to update items visibility if the menu is hiding. - if (!new_visibility) return; - - bool are_any_items_in_trash = false; - bool are_all_items_in_trash = true; - - LLFolderView* root_folder_view = mCurrentSelectedList ? mCurrentSelectedList->getRootFolder() : NULL; - if(root_folder_view) - { - const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - - std::set selected_items = root_folder_view->getSelectionList(); - - // Iterate through selected items to find out if any of these items are in Trash - // or all the items are in Trash category. - for (std::set::const_iterator iter = selected_items.begin(); iter != selected_items.end(); ++iter) - { - LLFolderViewItem* item = *iter; - - // If no item is found it might be a folder id. - if (!item) continue; - - LLFolderViewModelItemInventory* listenerp = static_cast(item->getViewModelItem()); - if(!listenerp) continue; - - // Trash category itself should not be included because it can't be - // actually restored from trash. - are_all_items_in_trash &= listenerp->isItemInTrash() && listenerp->getUUID() != trash_id; - - // If there are any selected items in Trash including the Trash category itself - // we show "Restore Item" in context menu and hide other irrelevant items. - are_any_items_in_trash |= listenerp->isItemInTrash(); - } - } - - // Display "Restore Item" menu entry if at least one of the selected items - // is in Trash or the Trash category itself is among selected items. - // Hide other menu entries in this case. - // Enable this menu entry only if all selected items are in the Trash category. - toggle_restore_menu((LLMenuGL*)ctrl, are_any_items_in_trash, are_all_items_in_trash); -} - -/* -Processes such actions: cut/rename/delete/paste actions - -Rules: - 1. We can't perform any action in Library - 2. For Landmarks we can: - - cut/rename/delete in any other accordions - - paste - only in Favorites, Landmarks accordions - 3. For Folders we can: perform any action in Landmarks accordion, except Received folder - 4. We can paste folders from Clipboard (processed by LLFolderView::canPaste()) - 5. Check LLFolderView/Inventory Bridges rules - */ -bool LLLandmarksPanel::canItemBeModified(const std::string& command_name, LLFolderViewItem* item) const -{ - // validate own rules first - - if (!item) return false; - - bool can_be_modified = false; - - // landmarks can be modified in any other accordion... - if (static_cast(item->getViewModelItem())->getInventoryType() == LLInventoryType::IT_LANDMARK) - { - can_be_modified = true; - } - else - { - // ...folders only in the Landmarks accordion... - can_be_modified = isLandmarksPanel; - } - - // then ask LLFolderView permissions - - LLFolderView* root_folder = mCurrentSelectedList->getRootFolder(); - - if ("copy" == command_name) - { - // we shouldn't be able to copy folders from My Inventory Panel - return can_be_modified && root_folder->canCopy(); - } - else if ("collapse" == command_name) - { - return item->isOpen(); - } - else if ("expand" == command_name) - { - return !item->isOpen(); - } - - if (can_be_modified) - { - LLFolderViewModelItemInventory* listenerp = static_cast(item->getViewModelItem()); - - if ("cut" == command_name) - { - can_be_modified = root_folder->canCut(); - } - else if ("rename" == command_name) - { - can_be_modified = listenerp ? listenerp->isItemRenameable() : false; - } - else if ("delete" == command_name) - { - can_be_modified = listenerp ? listenerp->isItemRemovable() && !listenerp->isItemInTrash() : false; - } - else if("paste" == command_name) - { - can_be_modified = root_folder->canPaste(); - } - else - { - LL_WARNS() << "Unprocessed command has come: " << command_name << LL_ENDL; - } - } - - return can_be_modified; -} - -bool LLLandmarksPanel::handleDragAndDropToTrash(bool drop, EDragAndDropType cargo_type, void* cargo_data , EAcceptance* accept) -{ - *accept = ACCEPT_NO; - - switch (cargo_type) - { - - case DAD_LANDMARK: - case DAD_CATEGORY: - { - bool is_enabled = isActionEnabled("delete"); - - if (is_enabled) *accept = ACCEPT_YES_MULTI; - - if (is_enabled && drop) - { - // don't call onClipboardAction("delete") - // this lead to removing (N * 2 - 1) items if drag N>1 items into trash. EXT-6757 - // So, let remove items one by one. - LLInventoryItem* item = static_cast(cargo_data); - if (item) - { - LLFolderViewItem* fv_item = mCurrentSelectedList - ? mCurrentSelectedList->getItemByID(item->getUUID()) - : NULL; - - if (fv_item) - { - // is Item Removable checked inside of remove() - fv_item->remove(); - } - } - } - } - break; - default: - break; - } - - updateVerbs(); - return true; -} - -void LLLandmarksPanel::doShowOnMap(LLLandmark* landmark) -{ - LLVector3d landmark_global_pos; - if (!landmark->getGlobalPos(landmark_global_pos)) - return; - - LLFloaterWorldMap* worldmap_instance = LLFloaterWorldMap::getInstance(); - if (!landmark_global_pos.isExactlyZero() && worldmap_instance) - { - worldmap_instance->trackLocation(landmark_global_pos); - LLFloaterReg::showInstance("world_map", "center"); - } - - if (mGearLandmarkMenu) - { - mGearLandmarkMenu->setItemEnabled("show_on_map", true); - } -} - -void LLLandmarksPanel::doProcessParcelInfo(LLLandmark* landmark, - LLInventoryItem* inv_item, - const LLParcelData& parcel_data) -{ - LLVector3d landmark_global_pos; - landmark->getGlobalPos(landmark_global_pos); - - LLPickData data; - data.pos_global = landmark_global_pos; - data.name = inv_item->getName(); - data.desc = inv_item->getDescription(); - data.snapshot_id = parcel_data.snapshot_id; - data.parcel_id = parcel_data.parcel_id; - - LLFloaterProfile* profile_floater = dynamic_cast(LLFloaterReg::showInstance("profile", LLSD().with("id", gAgentID))); - if (profile_floater) - { - profile_floater->createPick(data); - } -} - -void LLLandmarksPanel::doCreatePick(LLLandmark* landmark, const LLUUID &item_id) -{ - LLViewerRegion* region = gAgent.getRegion(); - if (!region) return; - - mCreatePickItemId = item_id; - - LLGlobalVec pos_global; - LLUUID region_id; - landmark->getGlobalPos(pos_global); - landmark->getRegionID(region_id); - LLVector3 region_pos((F32)fmod(pos_global.mdV[VX], (F64)REGION_WIDTH_METERS), - (F32)fmod(pos_global.mdV[VY], (F64)REGION_WIDTH_METERS), - (F32)pos_global.mdV[VZ]); - - LLSD body; - std::string url = region->getCapability("RemoteParcelRequest"); - if (!url.empty()) - { - LLRemoteParcelInfoProcessor::getInstance()->requestRegionParcelInfo(url, - region_id, region_pos, pos_global, getObserverHandle()); - } - else - { - LL_WARNS() << "Can't create pick for landmark for region" << region_id - << ". Region: " << region->getName() - << " does not support RemoteParcelRequest" << LL_ENDL; - } -} - -////////////////////////////////////////////////////////////////////////// -// HELPER FUNCTIONS -////////////////////////////////////////////////////////////////////////// -static void filter_list(LLPlacesInventoryPanel* inventory_list, const std::string& string) -{ - // When search is cleared, restore the old folder state. - if (!inventory_list->getFilterSubString().empty() && string == "") - { - inventory_list->setFilterSubString(LLStringUtil::null); - // Re-open folders that were open before - inventory_list->restoreFolderState(); - } - - if (inventory_list->getFilterSubString().empty() && string.empty()) - { - // current filter and new filter empty, do nothing - return; - } - - // save current folder open state if no filter currently applied - if (inventory_list->getFilterSubString().empty()) - { - inventory_list->saveFolderState(); - } - - // Set new filter string - inventory_list->setFilterSubString(string); -} - -static void collapse_all_folders(LLFolderView* root_folder) -{ - if (!root_folder) - return; - - root_folder->setOpenArrangeRecursively(false, LLFolderViewFolder::RECURSE_DOWN); - root_folder->arrangeAll(); -} - -static void expand_all_folders(LLFolderView* root_folder) -{ - if (!root_folder) - return; - - root_folder->setOpenArrangeRecursively(true, LLFolderViewFolder::RECURSE_DOWN); - root_folder->arrangeAll(); -} - -static bool has_expanded_folders(LLFolderView* root_folder) -{ - LLCheckFolderState checker; - root_folder->applyFunctorRecursively(checker); - - // We assume that the root folder is always expanded so we enable "collapse_all" - // command when we have at least one more expanded folder. - if (checker.getExpandedFolders() < 2) - { - return false; - } - - return true; -} - -static bool has_collapsed_folders(LLFolderView* root_folder) -{ - LLCheckFolderState checker; - root_folder->applyFunctorRecursively(checker); - - if (checker.getCollapsedFolders() < 1) - { - return false; - } - - return true; -} - -// Displays "Restore Item" context menu entry while hiding -// all other entries or vice versa. -// Sets "Restore Item" enabled state. -void toggle_restore_menu(LLMenuGL *menu, bool visible, bool enabled) -{ - if (!menu) return; - - const LLView::child_list_t *list = menu->getChildList(); - for (LLView::child_list_t::const_iterator itor = list->begin(); - itor != list->end(); - ++itor) - { - LLView *menu_item = (*itor); - std::string name = menu_item->getName(); - - if ("restore_item" == name) - { - menu_item->setVisible(visible); - menu_item->setEnabled(enabled); - } - else - { - menu_item->setVisible(!visible); - } - } -} - -LLFavoritesPanel::LLFavoritesPanel() - : LLLandmarksPanel(false) -{ - buildFromFile("panel_favorites.xml"); -} - -bool LLFavoritesPanel::postBuild() -{ - if (!gInventory.isInventoryUsable()) - return false; - - // mast be called before any other initXXX methods to init Gear menu - LLLandmarksPanel::initListCommandsHandlers(); - - initFavoritesInventoryPanel(); - - return true; -} - -void LLFavoritesPanel::initFavoritesInventoryPanel() -{ - mCurrentSelectedList = getChild("favorites_list"); - - LLLandmarksPanel::initLandmarksPanel(mCurrentSelectedList); - mCurrentSelectedList->getFilter().setEmptyLookupMessage("FavoritesNoMatchingItems"); -} -// EOF +/** + * @file llpanellandmarks.cpp + * @brief Landmarks tab for Side Bar "Places" panel + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanellandmarks.h" + +#include "llbutton.h" +#include "llfloaterprofile.h" +#include "llfloaterreg.h" +#include "llnotificationsutil.h" +#include "llsdutil.h" +#include "llsdutil_math.h" +#include "llregionhandle.h" + +#include "llaccordionctrl.h" +#include "llagent.h" +#include "llagentpicksinfo.h" +#include "llagentui.h" +#include "llavataractions.h" +#include "llcallbacklist.h" +#include "llfloatersidepanelcontainer.h" +#include "llfloaterworldmap.h" +#include "llfolderviewitem.h" +#include "llinventorymodelbackgroundfetch.h" +#include "llinventorypanel.h" +#include "llinventoryfunctions.h" +#include "lllandmarkactions.h" +#include "llmenubutton.h" +#include "llplacesinventorybridge.h" +#include "llplacesinventorypanel.h" +#include "llplacesfolderview.h" +#include "lltoggleablemenu.h" +#include "llviewermenu.h" +#include "llviewerregion.h" + +// Not yet implemented; need to remove buildPanel() from constructor when we switch +//static LLRegisterPanelClassWrapper t_landmarks("panel_landmarks"); + +// helper functions +static void filter_list(LLPlacesInventoryPanel* inventory_list, const std::string& string); +static void collapse_all_folders(LLFolderView* root_folder); +static void expand_all_folders(LLFolderView* root_folder); +static bool has_expanded_folders(LLFolderView* root_folder); +static bool has_collapsed_folders(LLFolderView* root_folder); +static void toggle_restore_menu(LLMenuGL* menu, bool visible, bool enabled); + +/** + * Functor counting expanded and collapsed folders in folder view tree to know + * when to enable or disable "Expand all folders" and "Collapse all folders" commands. + */ +class LLCheckFolderState : public LLFolderViewFunctor +{ +public: + LLCheckFolderState() + : mCollapsedFolders(0), + mExpandedFolders(0) + {} + virtual ~LLCheckFolderState() {} + virtual void doFolder(LLFolderViewFolder* folder); + virtual void doItem(LLFolderViewItem* item) {} + S32 getCollapsedFolders() { return mCollapsedFolders; } + S32 getExpandedFolders() { return mExpandedFolders; } + +private: + S32 mCollapsedFolders; + S32 mExpandedFolders; +}; + +// virtual +void LLCheckFolderState::doFolder(LLFolderViewFolder* folder) +{ + // Counting only folders that pass the filter. + // The listener check allow us to avoid counting the folder view + // object itself because it has no listener assigned. + if (folder->getViewModelItem()->descendantsPassedFilter()) + { + if (folder->isOpen()) + { + ++mExpandedFolders; + } + else + { + ++mCollapsedFolders; + } + } +} + +// Functor searching and opening a folder specified by UUID +// in a folder view tree. +class LLOpenFolderByID : public LLFolderViewFunctor +{ +public: + LLOpenFolderByID(const LLUUID& folder_id) + : mFolderID(folder_id) + , mIsFolderOpen(false) + {} + virtual ~LLOpenFolderByID() {} + /*virtual*/ void doFolder(LLFolderViewFolder* folder); + /*virtual*/ void doItem(LLFolderViewItem* item) {} + + bool isFolderOpen() { return mIsFolderOpen; } + +private: + bool mIsFolderOpen; + LLUUID mFolderID; +}; + +// virtual +void LLOpenFolderByID::doFolder(LLFolderViewFolder* folder) +{ + if (folder->getViewModelItem() && static_cast(folder->getViewModelItem())->getUUID() == mFolderID) + { + if (!folder->isOpen()) + { + folder->setOpen(true); + mIsFolderOpen = true; + } + } +} + +LLLandmarksPanel::LLLandmarksPanel() + : LLPanelPlacesTab() + , mLandmarksInventoryPanel(NULL) + , mCurrentSelectedList(NULL) + , mGearFolderMenu(NULL) + , mGearLandmarkMenu(NULL) + , mSortingMenu(NULL) + , mAddMenu(NULL) + , isLandmarksPanel(true) +{ + buildFromFile("panel_landmarks.xml"); +} + +LLLandmarksPanel::LLLandmarksPanel(bool is_landmark_panel) + : LLPanelPlacesTab() + , mLandmarksInventoryPanel(NULL) + , mCurrentSelectedList(NULL) + , mGearFolderMenu(NULL) + , mGearLandmarkMenu(NULL) + , mSortingMenu(NULL) + , mAddMenu(NULL) + , isLandmarksPanel(is_landmark_panel) +{ + if (is_landmark_panel) + { + buildFromFile("panel_landmarks.xml"); + } +} + +LLLandmarksPanel::~LLLandmarksPanel() +{ +} + +bool LLLandmarksPanel::postBuild() +{ + if (!gInventory.isInventoryUsable()) + return false; + + // mast be called before any other initXXX methods to init Gear menu + initListCommandsHandlers(); + initLandmarksInventoryPanel(); + + return true; +} + +// virtual +void LLLandmarksPanel::onSearchEdit(const std::string& string) +{ + filter_list(mCurrentSelectedList, string); + + if (sFilterSubString != string) + sFilterSubString = string; +} + +// virtual +void LLLandmarksPanel::onShowOnMap() +{ + if (NULL == mCurrentSelectedList) + { + LL_WARNS() << "There are no selected list. No actions are performed." << LL_ENDL; + return; + } + + doActionOnCurSelectedLandmark(boost::bind(&LLLandmarksPanel::doShowOnMap, this, _1)); +} + +//virtual +void LLLandmarksPanel::onShowProfile() +{ + LLFolderViewModelItemInventory* cur_item = getCurSelectedViewModelItem(); + + if(!cur_item) + return; + + cur_item->performAction(mCurrentSelectedList->getModel(),"about"); +} + +// virtual +void LLLandmarksPanel::onTeleport() +{ + LLFolderViewModelItemInventory* view_model_item = getCurSelectedViewModelItem(); + if (view_model_item && view_model_item->getInventoryType() == LLInventoryType::IT_LANDMARK) + { + view_model_item->openItem(); + } +} + +/*virtual*/ +void LLLandmarksPanel::onRemoveSelected() +{ + onClipboardAction("delete"); +} + +// virtual +bool LLLandmarksPanel::isSingleItemSelected() +{ + bool result = false; + + if (mCurrentSelectedList != NULL) + { + LLFolderView* root_view = mCurrentSelectedList->getRootFolder(); + + if (root_view->getSelectedCount() == 1) + { + result = isLandmarkSelected(); + } + } + + return result; +} + +// virtual +LLToggleableMenu* LLLandmarksPanel::getSelectionMenu() +{ + LLToggleableMenu* menu = mGearFolderMenu; + + if (mCurrentSelectedList) + { + LLFolderViewModelItemInventory* listenerp = getCurSelectedViewModelItem(); + if (!listenerp) + return menu; + + if (listenerp->getInventoryType() == LLInventoryType::IT_LANDMARK) + { + menu = mGearLandmarkMenu; + } + } + return menu; +} + +// virtual +LLToggleableMenu* LLLandmarksPanel::getSortingMenu() +{ + return mSortingMenu; +} + +// virtual +LLToggleableMenu* LLLandmarksPanel::getCreateMenu() +{ + return mAddMenu; +} + +void LLLandmarksPanel::updateVerbs() +{ + if (sRemoveBtn) + { + sRemoveBtn->setEnabled(isActionEnabled("delete") && (isFolderSelected() || isLandmarkSelected())); + } +} + +void LLLandmarksPanel::setItemSelected(const LLUUID& obj_id, bool take_keyboard_focus) +{ + if (!mCurrentSelectedList) + return; + + LLFolderView* root = mCurrentSelectedList->getRootFolder(); + LLFolderViewItem* item = mCurrentSelectedList->getItemByID(obj_id); + if (!item) + return; + root->setSelection(item, false, take_keyboard_focus); + root->scrollToShowSelection(); +} + +////////////////////////////////////////////////////////////////////////// +// PROTECTED METHODS +////////////////////////////////////////////////////////////////////////// + +bool LLLandmarksPanel::isLandmarkSelected() const +{ + LLFolderViewModelItemInventory* current_item = getCurSelectedViewModelItem(); + return current_item && (current_item->getInventoryType() == LLInventoryType::IT_LANDMARK); +} + +bool LLLandmarksPanel::isFolderSelected() const +{ + LLFolderViewModelItemInventory* current_item = getCurSelectedViewModelItem(); + return current_item && (current_item->getInventoryType() == LLInventoryType::IT_CATEGORY); +} + +void LLLandmarksPanel::doActionOnCurSelectedLandmark(LLLandmarkList::loaded_callback_t cb) +{ + LLFolderViewModelItemInventory* cur_item = getCurSelectedViewModelItem(); + if(cur_item && cur_item->getInventoryType() == LLInventoryType::IT_LANDMARK) + { + LLLandmark* landmark = LLLandmarkActions::getLandmark(cur_item->getUUID(), cb); + if (landmark) + { + cb(landmark); + } + } +} + +LLFolderViewItem* LLLandmarksPanel::getCurSelectedItem() const +{ + return mCurrentSelectedList ? mCurrentSelectedList->getRootFolder()->getCurSelectedItem() : NULL; +} + +LLFolderViewModelItemInventory* LLLandmarksPanel::getCurSelectedViewModelItem() const +{ + LLFolderViewItem* cur_item = getCurSelectedItem(); + if (cur_item) + { + return static_cast(cur_item->getViewModelItem()); + } + return NULL; +} + + +void LLLandmarksPanel::updateSortOrder(LLInventoryPanel* panel, bool byDate) +{ + if(!panel) return; + + U32 order = panel->getSortOrder(); + if (byDate) + { + panel->setSortOrder( order | LLInventoryFilter::SO_DATE ); + } + else + { + panel->setSortOrder( order & ~LLInventoryFilter::SO_DATE ); + } +} + +void LLLandmarksPanel::resetSelection() +{ +} + +// virtual +void LLLandmarksPanel::processParcelInfo(const LLParcelData& parcel_data) +{ + //this function will be called after user will try to create a pick for selected landmark. + // We have to make request to sever to get parcel_id and snaption_id. + if(mCreatePickItemId.notNull()) + { + LLInventoryItem* inv_item = gInventory.getItem(mCreatePickItemId); + + if (inv_item && inv_item->getInventoryType() == LLInventoryType::IT_LANDMARK) + { + // we are processing response for doCreatePick, landmark should be already loaded + LLLandmark* landmark = LLLandmarkActions::getLandmark(inv_item->getUUID()); + if (landmark) + { + doProcessParcelInfo(landmark, inv_item, parcel_data); + } + } + mCreatePickItemId.setNull(); + } +} + +// virtual +void LLLandmarksPanel::setParcelID(const LLUUID& parcel_id) +{ + if (!parcel_id.isNull()) + { + LLRemoteParcelInfoProcessor::getInstance()->addObserver(parcel_id, this); + LLRemoteParcelInfoProcessor::getInstance()->sendParcelInfoRequest(parcel_id); + } +} + +// virtual +void LLLandmarksPanel::setErrorStatus(S32 status, const std::string& reason) +{ + LL_WARNS() << "Can't handle remote parcel request."<< " Http Status: "<< status << ". Reason : "<< reason<("landmarks_list"); + + initLandmarksPanel(mLandmarksInventoryPanel); + + mLandmarksInventoryPanel->setShowFolderState(LLInventoryFilter::SHOW_ALL_FOLDERS); + + // subscribe to have auto-rename functionality while creating New Folder + mLandmarksInventoryPanel->setSelectCallback(boost::bind(&LLInventoryPanel::onSelectionChange, mLandmarksInventoryPanel, _1, _2)); + + mCurrentSelectedList = mLandmarksInventoryPanel; +} + +void LLLandmarksPanel::initLandmarksPanel(LLPlacesInventoryPanel* inventory_list) +{ + inventory_list->getFilter().setEmptyLookupMessage("PlacesNoMatchingItems"); + inventory_list->setFilterTypes(0x1 << LLInventoryType::IT_LANDMARK); + inventory_list->setSelectCallback(boost::bind(&LLLandmarksPanel::updateVerbs, this)); + + inventory_list->setShowFolderState(LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); + bool sorting_order = gSavedSettings.getBOOL("LandmarksSortedByDate"); + updateSortOrder(inventory_list, sorting_order); + + LLPlacesFolderView* root_folder = dynamic_cast(inventory_list->getRootFolder()); + if (root_folder) + { + if (mGearFolderMenu) + { + root_folder->setupMenuHandle(LLInventoryType::IT_CATEGORY, mGearFolderMenu->getHandle()); + } + if (mGearLandmarkMenu) + { + root_folder->setupMenuHandle(LLInventoryType::IT_LANDMARK, mGearLandmarkMenu->getHandle()); + } + + root_folder->setParentLandmarksPanel(this); + } + + inventory_list->saveFolderState(); +} + + +// List Commands Handlers +void LLLandmarksPanel::initListCommandsHandlers() +{ + mCommitCallbackRegistrar.add("Places.LandmarksGear.Add.Action", boost::bind(&LLLandmarksPanel::onAddAction, this, _2)); + mCommitCallbackRegistrar.add("Places.LandmarksGear.CopyPaste.Action", boost::bind(&LLLandmarksPanel::onClipboardAction, this, _2)); + mCommitCallbackRegistrar.add("Places.LandmarksGear.Custom.Action", boost::bind(&LLLandmarksPanel::onCustomAction, this, _2)); + mCommitCallbackRegistrar.add("Places.LandmarksGear.Folding.Action", boost::bind(&LLLandmarksPanel::onFoldingAction, this, _2)); + mEnableCallbackRegistrar.add("Places.LandmarksGear.Check", boost::bind(&LLLandmarksPanel::isActionChecked, this, _2)); + mEnableCallbackRegistrar.add("Places.LandmarksGear.Enable", boost::bind(&LLLandmarksPanel::isActionEnabled, this, _2)); + mGearLandmarkMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_places_gear_landmark.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + mGearFolderMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_places_gear_folder.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + mSortingMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_places_gear_sorting.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + mAddMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_place_add_button.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + + if (mGearLandmarkMenu) + { + mGearLandmarkMenu->setVisibilityChangeCallback(boost::bind(&LLLandmarksPanel::onMenuVisibilityChange, this, _1, _2)); + // show menus even if all items are disabled + mGearLandmarkMenu->setAlwaysShowMenu(true); + } // Else corrupted files? + + if (mGearFolderMenu) + { + mGearFolderMenu->setVisibilityChangeCallback(boost::bind(&LLLandmarksPanel::onMenuVisibilityChange, this, _1, _2)); + mGearFolderMenu->setAlwaysShowMenu(true); + } + + if (mAddMenu) + { + mAddMenu->setAlwaysShowMenu(true); + } +} + +void LLLandmarksPanel::updateMenuVisibility(LLUICtrl* menu) +{ + onMenuVisibilityChange(menu, LLSD().with("visibility", true)); +} + +void LLLandmarksPanel::onTrashButtonClick() const +{ + onClipboardAction("delete"); +} + +void LLLandmarksPanel::onAddAction(const LLSD& userdata) const +{ + LLFolderViewModelItemInventory* view_model = getCurSelectedViewModelItem(); + LLFolderViewItem* item = getCurSelectedItem(); + + std::string command_name = userdata.asString(); + if("add_landmark" == command_name + || "add_landmark_root" == command_name) + { + LLViewerInventoryItem* landmark = LLLandmarkActions::findLandmarkForAgentPos(); + if(landmark) + { + LLNotificationsUtil::add("LandmarkAlreadyExists"); + } + else + { + LLSD args; + args["type"] = "create_landmark"; + if ("add_landmark" == command_name + && view_model->getInventoryType() == LLInventoryType::IT_CATEGORY) + { + args["dest_folder"] = view_model->getUUID(); + } + if ("add_landmark_root" == command_name + && mCurrentSelectedList == mLandmarksInventoryPanel) + { + args["dest_folder"] = mLandmarksInventoryPanel->getRootFolderID(); + } + // else will end up in favorites + LLFloaterReg::showInstance("add_landmark", args); + } + } + else if ("category" == command_name) + { + if (item && mCurrentSelectedList == mLandmarksInventoryPanel) + { + LLFolderViewModelItem* folder_bridge = NULL; + + if (view_model->getInventoryType() + == LLInventoryType::IT_LANDMARK) + { + // for a landmark get parent folder bridge + folder_bridge = item->getParentFolder()->getViewModelItem(); + } + else if (view_model->getInventoryType() + == LLInventoryType::IT_CATEGORY) + { + // for a folder get its own bridge + folder_bridge = view_model; + } + + menu_create_inventory_item(mCurrentSelectedList, + dynamic_cast (folder_bridge), LLSD( + "category"), gInventory.findCategoryUUIDForType( + LLFolderType::FT_LANDMARK)); + } + else + { + //in case My Landmarks tab is completely empty (thus cannot be determined as being selected) + menu_create_inventory_item(mLandmarksInventoryPanel, NULL, LLSD("category"), + gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK)); + } + } + else if ("category_root" == command_name) + { + //in case My Landmarks tab is completely empty (thus cannot be determined as being selected) + menu_create_inventory_item(mLandmarksInventoryPanel, NULL, LLSD("category"), + gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK)); + } +} + +void LLLandmarksPanel::onClipboardAction(const LLSD& userdata) const +{ + if(!mCurrentSelectedList) + return; + std::string command_name = userdata.asString(); + if("copy_slurl" == command_name) + { + LLFolderViewModelItemInventory* cur_item = getCurSelectedViewModelItem(); + if(cur_item) + LLLandmarkActions::copySLURLtoClipboard(cur_item->getUUID()); + } + else if ( "paste" == command_name) + { + mCurrentSelectedList->getRootFolder()->paste(); + } + else if ( "cut" == command_name) + { + mCurrentSelectedList->getRootFolder()->cut(); + } + else + { + mCurrentSelectedList->doToSelected(command_name); + } +} + +void LLLandmarksPanel::onFoldingAction(const LLSD& userdata) +{ + std::string command_name = userdata.asString(); + + if ("expand_all" == command_name) + { + expand_all_folders(mCurrentSelectedList->getRootFolder()); + } + else if ("collapse_all" == command_name) + { + collapse_all_folders(mCurrentSelectedList->getRootFolder()); + } + else if ("sort_by_date" == command_name) + { + bool sorting_order = gSavedSettings.getBOOL("LandmarksSortedByDate"); + sorting_order=!sorting_order; + gSavedSettings.setBOOL("LandmarksSortedByDate",sorting_order); + updateSortOrder(mLandmarksInventoryPanel, sorting_order); + } + else + { + if(mCurrentSelectedList) + { + mCurrentSelectedList->doToSelected(userdata); + } + } +} + +bool LLLandmarksPanel::isActionChecked(const LLSD& userdata) const +{ + const std::string command_name = userdata.asString(); + + if ( "sort_by_date" == command_name) + { + bool sorting_order = gSavedSettings.getBOOL("LandmarksSortedByDate"); + return sorting_order; + } + + return false; +} + +bool LLLandmarksPanel::isActionEnabled(const LLSD& userdata) const +{ + std::string command_name = userdata.asString(); + + LLFolderView* root_folder_view = mCurrentSelectedList + ? mCurrentSelectedList->getRootFolder() + : NULL; + + bool is_single_selection = root_folder_view && root_folder_view->getSelectedCount() == 1; + + if ("collapse_all" == command_name) + { + return has_expanded_folders(mCurrentSelectedList->getRootFolder()); + } + else if ("expand_all" == command_name) + { + return has_collapsed_folders(mCurrentSelectedList->getRootFolder()); + } + else if ("sort_by_date" == command_name) + { + // disable "sort_by_date" for Favorites tab because + // it has its own items order. EXT-1758 + if (!isLandmarksPanel) + { + return false; + } + } + else if ( "paste" == command_name + || "cut" == command_name + || "copy" == command_name + || "delete" == command_name + || "collapse" == command_name + || "expand" == command_name + ) + { + if (!root_folder_view) return false; + + std::set selected_uuids = root_folder_view->getSelectionList(); + + if (selected_uuids.empty()) + { + return false; + } + + // Allow to execute the command only if it can be applied to all selected items. + for (std::set::const_iterator iter = selected_uuids.begin(); iter != selected_uuids.end(); ++iter) + { + LLFolderViewItem* item = *iter; + + if (!item) return false; + + if (!canItemBeModified(command_name, item)) return false; + } + + return true; + } + else if ( "teleport" == command_name + || "more_info" == command_name + || "show_on_map" == command_name + || "copy_slurl" == command_name + || "rename" == command_name + ) + { + // disable some commands for multi-selection. EXT-1757 + if (!is_single_selection) + { + return false; + } + + if ("show_on_map" == command_name) + { + LLFolderViewModelItemInventory* cur_item = getCurSelectedViewModelItem(); + if (!cur_item) return false; + + LLViewerInventoryItem* inv_item = dynamic_cast(cur_item->getInventoryObject()); + if (!inv_item) return false; + + LLUUID asset_uuid = inv_item->getAssetUUID(); + if (asset_uuid.isNull()) return false; + + // Disable "Show on Map" if landmark loading is in progress. + return !gLandmarkList.isAssetInLoadedCallbackMap(asset_uuid); + } + else if ("rename" == command_name) + { + LLFolderViewItem* selected_item = getCurSelectedItem(); + if (!selected_item) return false; + + return canItemBeModified(command_name, selected_item); + } + + return true; + } + if ("category_root" == command_name || "category" == command_name) + { + // we can add folder only in Landmarks tab + return isLandmarksPanel; + } + else if("create_pick" == command_name) + { + if (mCurrentSelectedList) + { + std::set selection = mCurrentSelectedList->getRootFolder()->getSelectionList(); + if (!selection.empty()) + { + return ( 1 == selection.size() && !LLAgentPicksInfo::getInstance()->isPickLimitReached() ); + } + } + return false; + } + else if ("add_landmark" == command_name) + { + if (!is_single_selection) + { + return false; + } + + LLFolderViewModelItemInventory* view_model = getCurSelectedViewModelItem(); + if (!view_model || view_model->getInventoryType() != LLInventoryType::IT_CATEGORY) + { + return false; + } + LLViewerInventoryItem* landmark = LLLandmarkActions::findLandmarkForAgentPos(); + if (landmark) + { + //already exists + return false; + } + return true; + } + else if ("add_landmark_root" == command_name) + { + LLViewerInventoryItem* landmark = LLLandmarkActions::findLandmarkForAgentPos(); + if (landmark) + { + //already exists + return false; + } + return true; + } + else if ("share" == command_name) + { + if (!mCurrentSelectedList) + { + return false; + } + if (!LLAvatarActions::canShareSelectedItems(mCurrentSelectedList)) + { + return false; + } + return true; + } + else if (command_name == "move_to_landmarks" || command_name == "move_to_favorites") + { + LLFolderViewModelItemInventory* cur_item_model = getCurSelectedViewModelItem(); + if (cur_item_model) + { + LLFolderType::EType folder_type = command_name == "move_to_landmarks" ? LLFolderType::FT_FAVORITE : LLFolderType::FT_LANDMARK; + if (!gInventory.isObjectDescendentOf(cur_item_model->getUUID(), gInventory.findCategoryUUIDForType(folder_type))) + { + return false; + } + + if (root_folder_view) + { + std::set selected_uuids = root_folder_view->getSelectionList(); + for (std::set::const_iterator iter = selected_uuids.begin(); iter != selected_uuids.end(); ++iter) + { + LLFolderViewItem* item = *iter; + if (!item) return false; + + cur_item_model = static_cast(item->getViewModelItem()); + if (!cur_item_model || cur_item_model->getInventoryType() != LLInventoryType::IT_LANDMARK) + { + return false; + } + } + return true; + } + } + return false; + } + else + { + LL_WARNS() << "Unprocessed command has come: " << command_name << LL_ENDL; + } + + return true; +} + +void LLLandmarksPanel::onCustomAction(const LLSD& userdata) +{ + std::string command_name = userdata.asString(); + if("more_info" == command_name) + { + onShowProfile(); + } + else if ("teleport" == command_name) + { + onTeleport(); + } + else if ("show_on_map" == command_name) + { + onShowOnMap(); + } + else if ("create_pick" == command_name) + { + LLFolderViewModelItemInventory* cur_item = getCurSelectedViewModelItem(); + if (cur_item) + { + doActionOnCurSelectedLandmark(boost::bind(&LLLandmarksPanel::doCreatePick, this, _1, cur_item->getUUID())); + } + } + else if ("share" == command_name && mCurrentSelectedList) + { + LLAvatarActions::shareWithAvatars(mCurrentSelectedList); + } + else if ("restore" == command_name && mCurrentSelectedList) + { + mCurrentSelectedList->doToSelected(userdata); + } + else if (command_name == "move_to_landmarks" || command_name == "move_to_favorites") + { + LLFolderView* root_folder_view = mCurrentSelectedList ? mCurrentSelectedList->getRootFolder() : NULL; + if (root_folder_view) + { + LLFolderType::EType folder_type = command_name == "move_to_landmarks" ? LLFolderType::FT_LANDMARK : LLFolderType::FT_FAVORITE; + std::set selected_uuids = root_folder_view->getSelectionList(); + for (std::set::const_iterator iter = selected_uuids.begin(); iter != selected_uuids.end(); ++iter) + { + LLFolderViewItem* item = *iter; + if (item) + { + LLFolderViewModelItemInventory* item_model = static_cast(item->getViewModelItem()); + if (item_model) + { + change_item_parent(item_model->getUUID(), gInventory.findCategoryUUIDForType(folder_type)); + } + } + } + } + + } +} + +void LLLandmarksPanel::onMenuVisibilityChange(LLUICtrl* ctrl, const LLSD& param) +{ + bool new_visibility = param["visibility"].asBoolean(); + + // We don't have to update items visibility if the menu is hiding. + if (!new_visibility) return; + + bool are_any_items_in_trash = false; + bool are_all_items_in_trash = true; + + LLFolderView* root_folder_view = mCurrentSelectedList ? mCurrentSelectedList->getRootFolder() : NULL; + if(root_folder_view) + { + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + + std::set selected_items = root_folder_view->getSelectionList(); + + // Iterate through selected items to find out if any of these items are in Trash + // or all the items are in Trash category. + for (std::set::const_iterator iter = selected_items.begin(); iter != selected_items.end(); ++iter) + { + LLFolderViewItem* item = *iter; + + // If no item is found it might be a folder id. + if (!item) continue; + + LLFolderViewModelItemInventory* listenerp = static_cast(item->getViewModelItem()); + if(!listenerp) continue; + + // Trash category itself should not be included because it can't be + // actually restored from trash. + are_all_items_in_trash &= listenerp->isItemInTrash() && listenerp->getUUID() != trash_id; + + // If there are any selected items in Trash including the Trash category itself + // we show "Restore Item" in context menu and hide other irrelevant items. + are_any_items_in_trash |= listenerp->isItemInTrash(); + } + } + + // Display "Restore Item" menu entry if at least one of the selected items + // is in Trash or the Trash category itself is among selected items. + // Hide other menu entries in this case. + // Enable this menu entry only if all selected items are in the Trash category. + toggle_restore_menu((LLMenuGL*)ctrl, are_any_items_in_trash, are_all_items_in_trash); +} + +/* +Processes such actions: cut/rename/delete/paste actions + +Rules: + 1. We can't perform any action in Library + 2. For Landmarks we can: + - cut/rename/delete in any other accordions + - paste - only in Favorites, Landmarks accordions + 3. For Folders we can: perform any action in Landmarks accordion, except Received folder + 4. We can paste folders from Clipboard (processed by LLFolderView::canPaste()) + 5. Check LLFolderView/Inventory Bridges rules + */ +bool LLLandmarksPanel::canItemBeModified(const std::string& command_name, LLFolderViewItem* item) const +{ + // validate own rules first + + if (!item) return false; + + bool can_be_modified = false; + + // landmarks can be modified in any other accordion... + if (static_cast(item->getViewModelItem())->getInventoryType() == LLInventoryType::IT_LANDMARK) + { + can_be_modified = true; + } + else + { + // ...folders only in the Landmarks accordion... + can_be_modified = isLandmarksPanel; + } + + // then ask LLFolderView permissions + + LLFolderView* root_folder = mCurrentSelectedList->getRootFolder(); + + if ("copy" == command_name) + { + // we shouldn't be able to copy folders from My Inventory Panel + return can_be_modified && root_folder->canCopy(); + } + else if ("collapse" == command_name) + { + return item->isOpen(); + } + else if ("expand" == command_name) + { + return !item->isOpen(); + } + + if (can_be_modified) + { + LLFolderViewModelItemInventory* listenerp = static_cast(item->getViewModelItem()); + + if ("cut" == command_name) + { + can_be_modified = root_folder->canCut(); + } + else if ("rename" == command_name) + { + can_be_modified = listenerp ? listenerp->isItemRenameable() : false; + } + else if ("delete" == command_name) + { + can_be_modified = listenerp ? listenerp->isItemRemovable() && !listenerp->isItemInTrash() : false; + } + else if("paste" == command_name) + { + can_be_modified = root_folder->canPaste(); + } + else + { + LL_WARNS() << "Unprocessed command has come: " << command_name << LL_ENDL; + } + } + + return can_be_modified; +} + +bool LLLandmarksPanel::handleDragAndDropToTrash(bool drop, EDragAndDropType cargo_type, void* cargo_data , EAcceptance* accept) +{ + *accept = ACCEPT_NO; + + switch (cargo_type) + { + + case DAD_LANDMARK: + case DAD_CATEGORY: + { + bool is_enabled = isActionEnabled("delete"); + + if (is_enabled) *accept = ACCEPT_YES_MULTI; + + if (is_enabled && drop) + { + // don't call onClipboardAction("delete") + // this lead to removing (N * 2 - 1) items if drag N>1 items into trash. EXT-6757 + // So, let remove items one by one. + LLInventoryItem* item = static_cast(cargo_data); + if (item) + { + LLFolderViewItem* fv_item = mCurrentSelectedList + ? mCurrentSelectedList->getItemByID(item->getUUID()) + : NULL; + + if (fv_item) + { + // is Item Removable checked inside of remove() + fv_item->remove(); + } + } + } + } + break; + default: + break; + } + + updateVerbs(); + return true; +} + +void LLLandmarksPanel::doShowOnMap(LLLandmark* landmark) +{ + LLVector3d landmark_global_pos; + if (!landmark->getGlobalPos(landmark_global_pos)) + return; + + LLFloaterWorldMap* worldmap_instance = LLFloaterWorldMap::getInstance(); + if (!landmark_global_pos.isExactlyZero() && worldmap_instance) + { + worldmap_instance->trackLocation(landmark_global_pos); + LLFloaterReg::showInstance("world_map", "center"); + } + + if (mGearLandmarkMenu) + { + mGearLandmarkMenu->setItemEnabled("show_on_map", true); + } +} + +void LLLandmarksPanel::doProcessParcelInfo(LLLandmark* landmark, + LLInventoryItem* inv_item, + const LLParcelData& parcel_data) +{ + LLVector3d landmark_global_pos; + landmark->getGlobalPos(landmark_global_pos); + + LLPickData data; + data.pos_global = landmark_global_pos; + data.name = inv_item->getName(); + data.desc = inv_item->getDescription(); + data.snapshot_id = parcel_data.snapshot_id; + data.parcel_id = parcel_data.parcel_id; + + LLFloaterProfile* profile_floater = dynamic_cast(LLFloaterReg::showInstance("profile", LLSD().with("id", gAgentID))); + if (profile_floater) + { + profile_floater->createPick(data); + } +} + +void LLLandmarksPanel::doCreatePick(LLLandmark* landmark, const LLUUID &item_id) +{ + LLViewerRegion* region = gAgent.getRegion(); + if (!region) return; + + mCreatePickItemId = item_id; + + LLGlobalVec pos_global; + LLUUID region_id; + landmark->getGlobalPos(pos_global); + landmark->getRegionID(region_id); + LLVector3 region_pos((F32)fmod(pos_global.mdV[VX], (F64)REGION_WIDTH_METERS), + (F32)fmod(pos_global.mdV[VY], (F64)REGION_WIDTH_METERS), + (F32)pos_global.mdV[VZ]); + + LLSD body; + std::string url = region->getCapability("RemoteParcelRequest"); + if (!url.empty()) + { + LLRemoteParcelInfoProcessor::getInstance()->requestRegionParcelInfo(url, + region_id, region_pos, pos_global, getObserverHandle()); + } + else + { + LL_WARNS() << "Can't create pick for landmark for region" << region_id + << ". Region: " << region->getName() + << " does not support RemoteParcelRequest" << LL_ENDL; + } +} + +////////////////////////////////////////////////////////////////////////// +// HELPER FUNCTIONS +////////////////////////////////////////////////////////////////////////// +static void filter_list(LLPlacesInventoryPanel* inventory_list, const std::string& string) +{ + // When search is cleared, restore the old folder state. + if (!inventory_list->getFilterSubString().empty() && string == "") + { + inventory_list->setFilterSubString(LLStringUtil::null); + // Re-open folders that were open before + inventory_list->restoreFolderState(); + } + + if (inventory_list->getFilterSubString().empty() && string.empty()) + { + // current filter and new filter empty, do nothing + return; + } + + // save current folder open state if no filter currently applied + if (inventory_list->getFilterSubString().empty()) + { + inventory_list->saveFolderState(); + } + + // Set new filter string + inventory_list->setFilterSubString(string); +} + +static void collapse_all_folders(LLFolderView* root_folder) +{ + if (!root_folder) + return; + + root_folder->setOpenArrangeRecursively(false, LLFolderViewFolder::RECURSE_DOWN); + root_folder->arrangeAll(); +} + +static void expand_all_folders(LLFolderView* root_folder) +{ + if (!root_folder) + return; + + root_folder->setOpenArrangeRecursively(true, LLFolderViewFolder::RECURSE_DOWN); + root_folder->arrangeAll(); +} + +static bool has_expanded_folders(LLFolderView* root_folder) +{ + LLCheckFolderState checker; + root_folder->applyFunctorRecursively(checker); + + // We assume that the root folder is always expanded so we enable "collapse_all" + // command when we have at least one more expanded folder. + if (checker.getExpandedFolders() < 2) + { + return false; + } + + return true; +} + +static bool has_collapsed_folders(LLFolderView* root_folder) +{ + LLCheckFolderState checker; + root_folder->applyFunctorRecursively(checker); + + if (checker.getCollapsedFolders() < 1) + { + return false; + } + + return true; +} + +// Displays "Restore Item" context menu entry while hiding +// all other entries or vice versa. +// Sets "Restore Item" enabled state. +void toggle_restore_menu(LLMenuGL *menu, bool visible, bool enabled) +{ + if (!menu) return; + + const LLView::child_list_t *list = menu->getChildList(); + for (LLView::child_list_t::const_iterator itor = list->begin(); + itor != list->end(); + ++itor) + { + LLView *menu_item = (*itor); + std::string name = menu_item->getName(); + + if ("restore_item" == name) + { + menu_item->setVisible(visible); + menu_item->setEnabled(enabled); + } + else + { + menu_item->setVisible(!visible); + } + } +} + +LLFavoritesPanel::LLFavoritesPanel() + : LLLandmarksPanel(false) +{ + buildFromFile("panel_favorites.xml"); +} + +bool LLFavoritesPanel::postBuild() +{ + if (!gInventory.isInventoryUsable()) + return false; + + // mast be called before any other initXXX methods to init Gear menu + LLLandmarksPanel::initListCommandsHandlers(); + + initFavoritesInventoryPanel(); + + return true; +} + +void LLFavoritesPanel::initFavoritesInventoryPanel() +{ + mCurrentSelectedList = getChild("favorites_list"); + + LLLandmarksPanel::initLandmarksPanel(mCurrentSelectedList); + mCurrentSelectedList->getFilter().setEmptyLookupMessage("FavoritesNoMatchingItems"); +} +// EOF diff --git a/indra/newview/llpanellandmarks.h b/indra/newview/llpanellandmarks.h index 9a17cf8ab1..30e9188044 100644 --- a/indra/newview/llpanellandmarks.h +++ b/indra/newview/llpanellandmarks.h @@ -1,169 +1,169 @@ -/** - * @file llpanellandmarks.h - * @brief Landmarks tab for Side Bar "Places" panel - * class definition - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELLANDMARKS_H -#define LL_LLPANELLANDMARKS_H - -#include "lllandmark.h" - -// newview -#include "llinventorymodel.h" -#include "lllandmarklist.h" -#include "llpanelplacestab.h" -#include "llremoteparcelrequest.h" - -class LLAccordionCtrlTab; -class LLFolderViewItem; -class LLMenuButton; -class LLMenuGL; -class LLToggleableMenu; -class LLInventoryPanel; -class LLPlacesInventoryPanel; -class LLFolderViewModelItemInventory; - -class LLLandmarksPanel : public LLPanelPlacesTab, LLRemoteParcelInfoObserver -{ -public: - LLLandmarksPanel(); - LLLandmarksPanel(bool is_landmark_panel); - virtual ~LLLandmarksPanel(); - - bool postBuild() override; - void onSearchEdit(const std::string& string) override; - void onShowOnMap() override; - void onShowProfile() override; - void onTeleport() override; - void onRemoveSelected() override; - void updateVerbs() override; - bool isSingleItemSelected() override; - - LLToggleableMenu* getSelectionMenu() override; - LLToggleableMenu* getSortingMenu() override; - LLToggleableMenu* getCreateMenu() override; - - /** - * Processes drag-n-drop of the Landmarks and folders into trash button. - */ - bool handleDragAndDropToTrash(bool drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept) override; - - void setCurrentSelectedList(LLPlacesInventoryPanel* inventory_list) - { - mCurrentSelectedList = inventory_list; - } - - /** - * Selects item with "obj_id" in one of accordion tabs. - */ - void setItemSelected(const LLUUID& obj_id, bool take_keyboard_focus); - - void updateMenuVisibility(LLUICtrl* menu); - - void doCreatePick(LLLandmark* landmark, const LLUUID &item_id ); - - void resetSelection(); - -protected: - /** - * @return true - if current selected panel is not null and selected item is a landmark - */ - bool isLandmarkSelected() const; - bool isFolderSelected() const; - void doActionOnCurSelectedLandmark(LLLandmarkList::loaded_callback_t cb); - LLFolderViewItem* getCurSelectedItem() const; - LLFolderViewModelItemInventory* getCurSelectedViewModelItem() const; - - void updateSortOrder(LLInventoryPanel* panel, bool byDate); - - //LLRemoteParcelInfoObserver interface - void processParcelInfo(const LLParcelData& parcel_data) override; - void setParcelID(const LLUUID& parcel_id) override; - void setErrorStatus(S32 status, const std::string& reason) override; - - // List Commands Handlers - void initListCommandsHandlers(); - void initLandmarksPanel(LLPlacesInventoryPanel* inventory_list); - - LLPlacesInventoryPanel* mCurrentSelectedList; - -private: - void initLandmarksInventoryPanel(); - - void onTrashButtonClick() const; - void onAddAction(const LLSD& command_name) const; - void onClipboardAction(const LLSD& command_name) const; - void onFoldingAction(const LLSD& command_name); - bool isActionChecked(const LLSD& userdata) const; - bool isActionEnabled(const LLSD& command_name) const; - void onCustomAction(const LLSD& command_name); - - /** - * Updates context menu depending on the selected items location. - * - * For items in Trash category the menu includes the "Restore Item" - * context menu entry. - */ - void onMenuVisibilityChange(LLUICtrl* ctrl, const LLSD& param); - - /** - * Determines if an item can be modified via context/gear menu. - * - * It validates Places Landmarks rules first. And then LLFolderView permissions. - * For now it checks cut/rename/delete/paste actions. - */ - bool canItemBeModified(const std::string& command_name, LLFolderViewItem* item) const; - - /** - * Landmark actions callbacks. Fire when a landmark is loaded from the list. - */ - void doShowOnMap(LLLandmark* landmark); - void doProcessParcelInfo(LLLandmark* landmark, - LLInventoryItem* inv_item, - const LLParcelData& parcel_data); - -private: - LLPlacesInventoryPanel* mLandmarksInventoryPanel; - LLToggleableMenu* mGearLandmarkMenu; - LLToggleableMenu* mGearFolderMenu; - LLToggleableMenu* mSortingMenu; - LLToggleableMenu* mAddMenu; - - bool isLandmarksPanel; - - LLUUID mCreatePickItemId; // item we requested a pick for -}; - - -class LLFavoritesPanel : public LLLandmarksPanel -{ -public: - LLFavoritesPanel(); - - bool postBuild() override; - void initFavoritesInventoryPanel(); -}; - -#endif //LL_LLPANELLANDMARKS_H +/** + * @file llpanellandmarks.h + * @brief Landmarks tab for Side Bar "Places" panel + * class definition + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELLANDMARKS_H +#define LL_LLPANELLANDMARKS_H + +#include "lllandmark.h" + +// newview +#include "llinventorymodel.h" +#include "lllandmarklist.h" +#include "llpanelplacestab.h" +#include "llremoteparcelrequest.h" + +class LLAccordionCtrlTab; +class LLFolderViewItem; +class LLMenuButton; +class LLMenuGL; +class LLToggleableMenu; +class LLInventoryPanel; +class LLPlacesInventoryPanel; +class LLFolderViewModelItemInventory; + +class LLLandmarksPanel : public LLPanelPlacesTab, LLRemoteParcelInfoObserver +{ +public: + LLLandmarksPanel(); + LLLandmarksPanel(bool is_landmark_panel); + virtual ~LLLandmarksPanel(); + + bool postBuild() override; + void onSearchEdit(const std::string& string) override; + void onShowOnMap() override; + void onShowProfile() override; + void onTeleport() override; + void onRemoveSelected() override; + void updateVerbs() override; + bool isSingleItemSelected() override; + + LLToggleableMenu* getSelectionMenu() override; + LLToggleableMenu* getSortingMenu() override; + LLToggleableMenu* getCreateMenu() override; + + /** + * Processes drag-n-drop of the Landmarks and folders into trash button. + */ + bool handleDragAndDropToTrash(bool drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept) override; + + void setCurrentSelectedList(LLPlacesInventoryPanel* inventory_list) + { + mCurrentSelectedList = inventory_list; + } + + /** + * Selects item with "obj_id" in one of accordion tabs. + */ + void setItemSelected(const LLUUID& obj_id, bool take_keyboard_focus); + + void updateMenuVisibility(LLUICtrl* menu); + + void doCreatePick(LLLandmark* landmark, const LLUUID &item_id ); + + void resetSelection(); + +protected: + /** + * @return true - if current selected panel is not null and selected item is a landmark + */ + bool isLandmarkSelected() const; + bool isFolderSelected() const; + void doActionOnCurSelectedLandmark(LLLandmarkList::loaded_callback_t cb); + LLFolderViewItem* getCurSelectedItem() const; + LLFolderViewModelItemInventory* getCurSelectedViewModelItem() const; + + void updateSortOrder(LLInventoryPanel* panel, bool byDate); + + //LLRemoteParcelInfoObserver interface + void processParcelInfo(const LLParcelData& parcel_data) override; + void setParcelID(const LLUUID& parcel_id) override; + void setErrorStatus(S32 status, const std::string& reason) override; + + // List Commands Handlers + void initListCommandsHandlers(); + void initLandmarksPanel(LLPlacesInventoryPanel* inventory_list); + + LLPlacesInventoryPanel* mCurrentSelectedList; + +private: + void initLandmarksInventoryPanel(); + + void onTrashButtonClick() const; + void onAddAction(const LLSD& command_name) const; + void onClipboardAction(const LLSD& command_name) const; + void onFoldingAction(const LLSD& command_name); + bool isActionChecked(const LLSD& userdata) const; + bool isActionEnabled(const LLSD& command_name) const; + void onCustomAction(const LLSD& command_name); + + /** + * Updates context menu depending on the selected items location. + * + * For items in Trash category the menu includes the "Restore Item" + * context menu entry. + */ + void onMenuVisibilityChange(LLUICtrl* ctrl, const LLSD& param); + + /** + * Determines if an item can be modified via context/gear menu. + * + * It validates Places Landmarks rules first. And then LLFolderView permissions. + * For now it checks cut/rename/delete/paste actions. + */ + bool canItemBeModified(const std::string& command_name, LLFolderViewItem* item) const; + + /** + * Landmark actions callbacks. Fire when a landmark is loaded from the list. + */ + void doShowOnMap(LLLandmark* landmark); + void doProcessParcelInfo(LLLandmark* landmark, + LLInventoryItem* inv_item, + const LLParcelData& parcel_data); + +private: + LLPlacesInventoryPanel* mLandmarksInventoryPanel; + LLToggleableMenu* mGearLandmarkMenu; + LLToggleableMenu* mGearFolderMenu; + LLToggleableMenu* mSortingMenu; + LLToggleableMenu* mAddMenu; + + bool isLandmarksPanel; + + LLUUID mCreatePickItemId; // item we requested a pick for +}; + + +class LLFavoritesPanel : public LLLandmarksPanel +{ +public: + LLFavoritesPanel(); + + bool postBuild() override; + void initFavoritesInventoryPanel(); +}; + +#endif //LL_LLPANELLANDMARKS_H diff --git a/indra/newview/llpanellandmedia.cpp b/indra/newview/llpanellandmedia.cpp index 6a3968252c..294bd4021d 100644 --- a/indra/newview/llpanellandmedia.cpp +++ b/indra/newview/llpanellandmedia.cpp @@ -1,329 +1,329 @@ -/** - * @file llpanellandmedia.cpp - * @brief Allows configuration of "media" for a land parcel, - * for example movies, web pages, and audio. - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanellandmedia.h" - -// viewer includes -#include "llmimetypes.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" -#include "llviewermedia.h" -#include "llviewerparcelmedia.h" -#include "lluictrlfactory.h" - -// library includes -#include "llcheckboxctrl.h" -#include "llcombobox.h" -#include "llfloaterurlentry.h" -#include "llfocusmgr.h" -#include "lllineeditor.h" -#include "llparcel.h" -#include "lltextbox.h" -#include "llradiogroup.h" -#include "llspinctrl.h" -#include "llsdutil.h" -#include "lltexturectrl.h" -#include "roles_constants.h" -#include "llscrolllistctrl.h" - -//--------------------------------------------------------------------------- -// LLPanelLandMedia -//--------------------------------------------------------------------------- - -LLPanelLandMedia::LLPanelLandMedia(LLParcelSelectionHandle& parcel) -: LLPanel(), - mParcel(parcel), - mMediaURLEdit(NULL), - mMediaDescEdit(NULL), - mMediaTypeCombo(NULL), - mSetURLButton(NULL), - mMediaHeightCtrl(NULL), - mMediaWidthCtrl(NULL), - mMediaSizeCtrlLabel(NULL), - mMediaTextureCtrl(NULL), - mMediaAutoScaleCheck(NULL), - mMediaLoopCheck(NULL) -{ -} - - -// virtual -LLPanelLandMedia::~LLPanelLandMedia() -{ -} - -bool LLPanelLandMedia::postBuild() -{ - - mMediaTextureCtrl = getChild("media texture"); - mMediaTextureCtrl->setCommitCallback( onCommitAny, this ); - mMediaTextureCtrl->setAllowNoTexture ( true ); - mMediaTextureCtrl->setImmediateFilterPermMask(PERM_COPY | PERM_TRANSFER); - mMediaTextureCtrl->setDnDFilterPermMask(PERM_COPY | PERM_TRANSFER); - - mMediaAutoScaleCheck = getChild("media_auto_scale"); - childSetCommitCallback("media_auto_scale", onCommitAny, this); - - mMediaLoopCheck = getChild("media_loop"); - childSetCommitCallback("media_loop", onCommitAny, this ); - - mMediaURLEdit = getChild("media_url"); - childSetCommitCallback("media_url", onCommitAny, this ); - - mMediaDescEdit = getChild("url_description"); - childSetCommitCallback("url_description", onCommitAny, this); - - mMediaTypeCombo = getChild("media type"); - childSetCommitCallback("media type", onCommitType, this); - populateMIMECombo(); - - mMediaWidthCtrl = getChild("media_size_width"); - childSetCommitCallback("media_size_width", onCommitAny, this); - mMediaHeightCtrl = getChild("media_size_height"); - childSetCommitCallback("media_size_height", onCommitAny, this); - mMediaSizeCtrlLabel = getChild("media_size"); - - mSetURLButton = getChild("set_media_url"); - childSetAction("set_media_url", onSetBtn, this); - - return true; -} - - -// public -void LLPanelLandMedia::refresh() -{ - LLParcel *parcel = mParcel->getParcel(); - - if (!parcel) - { - clearCtrls(); - } - else - { - // something selected, hooray! - - // Display options - bool can_change_media = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_CHANGE_MEDIA); - - mMediaURLEdit->setText(parcel->getMediaURL()); - mMediaURLEdit->setEnabled( false ); - - getChild("current_url")->setValue(parcel->getMediaCurrentURL()); - - mMediaDescEdit->setText(parcel->getMediaDesc()); - mMediaDescEdit->setEnabled( can_change_media ); - - std::string mime_type = parcel->getMediaType(); - if (mime_type.empty() || mime_type == LLMIMETypes::getDefaultMimeType()) - { - mime_type = LLMIMETypes::getDefaultMimeTypeTranslation(); - } - setMediaType(mime_type); - mMediaTypeCombo->setEnabled( can_change_media ); - getChild("mime_type")->setValue(mime_type); - - mMediaAutoScaleCheck->set( static_cast(parcel->getMediaAutoScale()) ); - mMediaAutoScaleCheck->setEnabled ( can_change_media ); - - // Special code to disable looping checkbox for HTML MIME type - // (DEV-10042 -- Parcel Media: "Loop Media" should be disabled for static media types) - bool allow_looping = LLMIMETypes::findAllowLooping( mime_type ); - if ( allow_looping ) - mMediaLoopCheck->set( static_cast(parcel->getMediaLoop()) ); - else - mMediaLoopCheck->set( false ); - mMediaLoopCheck->setEnabled ( can_change_media && allow_looping ); - - // disallow media size change for mime types that don't allow it - bool allow_resize = LLMIMETypes::findAllowResize( mime_type ); - if ( allow_resize ) - mMediaWidthCtrl->setValue( parcel->getMediaWidth() ); - else - mMediaWidthCtrl->setValue( 0 ); - mMediaWidthCtrl->setEnabled ( can_change_media && allow_resize ); - - if ( allow_resize ) - mMediaHeightCtrl->setValue( parcel->getMediaHeight() ); - else - mMediaHeightCtrl->setValue( 0 ); - mMediaHeightCtrl->setEnabled ( can_change_media && allow_resize ); - - // enable/disable for text label for completeness - mMediaSizeCtrlLabel->setEnabled( can_change_media && allow_resize ); - - mMediaTextureCtrl->setImageAssetID ( parcel->getMediaID() ); - mMediaTextureCtrl->setEnabled( can_change_media ); - - mSetURLButton->setEnabled( can_change_media ); - - } -} - -void LLPanelLandMedia::populateMIMECombo() -{ - std::string default_mime_type = LLMIMETypes::getDefaultMimeType(); - std::string default_label; - LLMIMETypes::mime_widget_set_map_t::const_iterator it; - for (it = LLMIMETypes::sWidgetMap.begin(); it != LLMIMETypes::sWidgetMap.end(); ++it) - { - const std::string& mime_type = it->first; - const LLMIMETypes::LLMIMEWidgetSet& info = it->second; - if (info.mDefaultMimeType == default_mime_type) - { - // Add this label at the end to make UI look cleaner - default_label = info.mLabel; - } - else - { - mMediaTypeCombo->add(info.mLabel, mime_type); - } - } - - mMediaTypeCombo->add( default_label, default_mime_type, ADD_BOTTOM ); -} - -void LLPanelLandMedia::setMediaType(const std::string& mime_type) -{ - LLParcel *parcel = mParcel->getParcel(); - if(parcel) - parcel->setMediaType(mime_type); - - std::string media_key = LLMIMETypes::widgetType(mime_type); - mMediaTypeCombo->setValue(media_key); - - std::string mime_str = mime_type; - if(LLMIMETypes::getDefaultMimeType() == mime_type) - { - // Instead of showing predefined "none/none" we are going to show something - // localizable - "none" for example (see EXT-6542) - mime_str = LLMIMETypes::getDefaultMimeTypeTranslation(); - } - getChild("mime_type")->setValue(mime_str); -} - -void LLPanelLandMedia::setMediaURL(const std::string& media_url) -{ - mMediaURLEdit->setText(media_url); - LLParcel *parcel = mParcel->getParcel(); - if(parcel) - parcel->setMediaCurrentURL(media_url); - // LLViewerMedia::navigateHome(); - - - mMediaURLEdit->onCommit(); - // LLViewerParcelMedia::sendMediaNavigateMessage(media_url); - getChild("current_url")->setValue(media_url); -} -std::string LLPanelLandMedia::getMediaURL() -{ - return mMediaURLEdit->getText(); -} - -// static -void LLPanelLandMedia::onCommitType(LLUICtrl *ctrl, void *userdata) -{ - LLPanelLandMedia *self = (LLPanelLandMedia *)userdata; - std::string current_type = LLMIMETypes::widgetType(self->getChild("mime_type")->getValue().asString()); - std::string new_type = self->mMediaTypeCombo->getValue(); - if(current_type != new_type) - { - self->getChild("mime_type")->setValue(LLMIMETypes::findDefaultMimeType(new_type)); - } - onCommitAny(ctrl, userdata); - -} - -// static -void LLPanelLandMedia::onCommitAny(LLUICtrl*, void *userdata) -{ - LLPanelLandMedia *self = (LLPanelLandMedia *)userdata; - - LLParcel* parcel = self->mParcel->getParcel(); - if (!parcel) - { - return; - } - - // Extract data from UI - std::string media_url = self->mMediaURLEdit->getText(); - std::string media_desc = self->mMediaDescEdit->getText(); - std::string mime_type = self->getChild("mime_type")->getValue().asString(); - U8 media_auto_scale = static_cast(self->mMediaAutoScaleCheck->get()); - U8 media_loop = static_cast(self->mMediaLoopCheck->get()); - S32 media_width = (S32)self->mMediaWidthCtrl->get(); - S32 media_height = (S32)self->mMediaHeightCtrl->get(); - LLUUID media_id = self->mMediaTextureCtrl->getImageAssetID(); - - - self->getChild("mime_type")->setValue(mime_type); - - // Remove leading/trailing whitespace (common when copying/pasting) - LLStringUtil::trim(media_url); - - // Push data into current parcel - parcel->setMediaURL(media_url); - parcel->setMediaType(mime_type); - parcel->setMediaDesc(media_desc); - parcel->setMediaWidth(media_width); - parcel->setMediaHeight(media_height); - parcel->setMediaID(media_id); - parcel->setMediaAutoScale ( media_auto_scale ); - parcel->setMediaLoop ( media_loop ); - - // Send current parcel data upstream to server - LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate( parcel ); - - // Might have changed properties, so let's redraw! - self->refresh(); -} -// static -void LLPanelLandMedia::onSetBtn(void *userdata) -{ - LLPanelLandMedia *self = (LLPanelLandMedia *)userdata; - self->mURLEntryFloater = LLFloaterURLEntry::show( self->getHandle(), self->getMediaURL() ); - LLFloater* parent_floater = gFloaterView->getParentFloater(self); - if (parent_floater) - { - parent_floater->addDependentFloater(self->mURLEntryFloater.get()); - } -} - -// static -void LLPanelLandMedia::onResetBtn(void *userdata) -{ - LLPanelLandMedia *self = (LLPanelLandMedia *)userdata; - LLParcel* parcel = self->mParcel->getParcel(); - // LLViewerMedia::navigateHome(); - self->refresh(); - self->getChild("current_url")->setValue(parcel->getMediaURL()); - // LLViewerParcelMedia::sendMediaNavigateMessage(parcel->getMediaURL()); - -} - +/** + * @file llpanellandmedia.cpp + * @brief Allows configuration of "media" for a land parcel, + * for example movies, web pages, and audio. + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanellandmedia.h" + +// viewer includes +#include "llmimetypes.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "llviewermedia.h" +#include "llviewerparcelmedia.h" +#include "lluictrlfactory.h" + +// library includes +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "llfloaterurlentry.h" +#include "llfocusmgr.h" +#include "lllineeditor.h" +#include "llparcel.h" +#include "lltextbox.h" +#include "llradiogroup.h" +#include "llspinctrl.h" +#include "llsdutil.h" +#include "lltexturectrl.h" +#include "roles_constants.h" +#include "llscrolllistctrl.h" + +//--------------------------------------------------------------------------- +// LLPanelLandMedia +//--------------------------------------------------------------------------- + +LLPanelLandMedia::LLPanelLandMedia(LLParcelSelectionHandle& parcel) +: LLPanel(), + mParcel(parcel), + mMediaURLEdit(NULL), + mMediaDescEdit(NULL), + mMediaTypeCombo(NULL), + mSetURLButton(NULL), + mMediaHeightCtrl(NULL), + mMediaWidthCtrl(NULL), + mMediaSizeCtrlLabel(NULL), + mMediaTextureCtrl(NULL), + mMediaAutoScaleCheck(NULL), + mMediaLoopCheck(NULL) +{ +} + + +// virtual +LLPanelLandMedia::~LLPanelLandMedia() +{ +} + +bool LLPanelLandMedia::postBuild() +{ + + mMediaTextureCtrl = getChild("media texture"); + mMediaTextureCtrl->setCommitCallback( onCommitAny, this ); + mMediaTextureCtrl->setAllowNoTexture ( true ); + mMediaTextureCtrl->setImmediateFilterPermMask(PERM_COPY | PERM_TRANSFER); + mMediaTextureCtrl->setDnDFilterPermMask(PERM_COPY | PERM_TRANSFER); + + mMediaAutoScaleCheck = getChild("media_auto_scale"); + childSetCommitCallback("media_auto_scale", onCommitAny, this); + + mMediaLoopCheck = getChild("media_loop"); + childSetCommitCallback("media_loop", onCommitAny, this ); + + mMediaURLEdit = getChild("media_url"); + childSetCommitCallback("media_url", onCommitAny, this ); + + mMediaDescEdit = getChild("url_description"); + childSetCommitCallback("url_description", onCommitAny, this); + + mMediaTypeCombo = getChild("media type"); + childSetCommitCallback("media type", onCommitType, this); + populateMIMECombo(); + + mMediaWidthCtrl = getChild("media_size_width"); + childSetCommitCallback("media_size_width", onCommitAny, this); + mMediaHeightCtrl = getChild("media_size_height"); + childSetCommitCallback("media_size_height", onCommitAny, this); + mMediaSizeCtrlLabel = getChild("media_size"); + + mSetURLButton = getChild("set_media_url"); + childSetAction("set_media_url", onSetBtn, this); + + return true; +} + + +// public +void LLPanelLandMedia::refresh() +{ + LLParcel *parcel = mParcel->getParcel(); + + if (!parcel) + { + clearCtrls(); + } + else + { + // something selected, hooray! + + // Display options + bool can_change_media = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_CHANGE_MEDIA); + + mMediaURLEdit->setText(parcel->getMediaURL()); + mMediaURLEdit->setEnabled( false ); + + getChild("current_url")->setValue(parcel->getMediaCurrentURL()); + + mMediaDescEdit->setText(parcel->getMediaDesc()); + mMediaDescEdit->setEnabled( can_change_media ); + + std::string mime_type = parcel->getMediaType(); + if (mime_type.empty() || mime_type == LLMIMETypes::getDefaultMimeType()) + { + mime_type = LLMIMETypes::getDefaultMimeTypeTranslation(); + } + setMediaType(mime_type); + mMediaTypeCombo->setEnabled( can_change_media ); + getChild("mime_type")->setValue(mime_type); + + mMediaAutoScaleCheck->set( static_cast(parcel->getMediaAutoScale()) ); + mMediaAutoScaleCheck->setEnabled ( can_change_media ); + + // Special code to disable looping checkbox for HTML MIME type + // (DEV-10042 -- Parcel Media: "Loop Media" should be disabled for static media types) + bool allow_looping = LLMIMETypes::findAllowLooping( mime_type ); + if ( allow_looping ) + mMediaLoopCheck->set( static_cast(parcel->getMediaLoop()) ); + else + mMediaLoopCheck->set( false ); + mMediaLoopCheck->setEnabled ( can_change_media && allow_looping ); + + // disallow media size change for mime types that don't allow it + bool allow_resize = LLMIMETypes::findAllowResize( mime_type ); + if ( allow_resize ) + mMediaWidthCtrl->setValue( parcel->getMediaWidth() ); + else + mMediaWidthCtrl->setValue( 0 ); + mMediaWidthCtrl->setEnabled ( can_change_media && allow_resize ); + + if ( allow_resize ) + mMediaHeightCtrl->setValue( parcel->getMediaHeight() ); + else + mMediaHeightCtrl->setValue( 0 ); + mMediaHeightCtrl->setEnabled ( can_change_media && allow_resize ); + + // enable/disable for text label for completeness + mMediaSizeCtrlLabel->setEnabled( can_change_media && allow_resize ); + + mMediaTextureCtrl->setImageAssetID ( parcel->getMediaID() ); + mMediaTextureCtrl->setEnabled( can_change_media ); + + mSetURLButton->setEnabled( can_change_media ); + + } +} + +void LLPanelLandMedia::populateMIMECombo() +{ + std::string default_mime_type = LLMIMETypes::getDefaultMimeType(); + std::string default_label; + LLMIMETypes::mime_widget_set_map_t::const_iterator it; + for (it = LLMIMETypes::sWidgetMap.begin(); it != LLMIMETypes::sWidgetMap.end(); ++it) + { + const std::string& mime_type = it->first; + const LLMIMETypes::LLMIMEWidgetSet& info = it->second; + if (info.mDefaultMimeType == default_mime_type) + { + // Add this label at the end to make UI look cleaner + default_label = info.mLabel; + } + else + { + mMediaTypeCombo->add(info.mLabel, mime_type); + } + } + + mMediaTypeCombo->add( default_label, default_mime_type, ADD_BOTTOM ); +} + +void LLPanelLandMedia::setMediaType(const std::string& mime_type) +{ + LLParcel *parcel = mParcel->getParcel(); + if(parcel) + parcel->setMediaType(mime_type); + + std::string media_key = LLMIMETypes::widgetType(mime_type); + mMediaTypeCombo->setValue(media_key); + + std::string mime_str = mime_type; + if(LLMIMETypes::getDefaultMimeType() == mime_type) + { + // Instead of showing predefined "none/none" we are going to show something + // localizable - "none" for example (see EXT-6542) + mime_str = LLMIMETypes::getDefaultMimeTypeTranslation(); + } + getChild("mime_type")->setValue(mime_str); +} + +void LLPanelLandMedia::setMediaURL(const std::string& media_url) +{ + mMediaURLEdit->setText(media_url); + LLParcel *parcel = mParcel->getParcel(); + if(parcel) + parcel->setMediaCurrentURL(media_url); + // LLViewerMedia::navigateHome(); + + + mMediaURLEdit->onCommit(); + // LLViewerParcelMedia::sendMediaNavigateMessage(media_url); + getChild("current_url")->setValue(media_url); +} +std::string LLPanelLandMedia::getMediaURL() +{ + return mMediaURLEdit->getText(); +} + +// static +void LLPanelLandMedia::onCommitType(LLUICtrl *ctrl, void *userdata) +{ + LLPanelLandMedia *self = (LLPanelLandMedia *)userdata; + std::string current_type = LLMIMETypes::widgetType(self->getChild("mime_type")->getValue().asString()); + std::string new_type = self->mMediaTypeCombo->getValue(); + if(current_type != new_type) + { + self->getChild("mime_type")->setValue(LLMIMETypes::findDefaultMimeType(new_type)); + } + onCommitAny(ctrl, userdata); + +} + +// static +void LLPanelLandMedia::onCommitAny(LLUICtrl*, void *userdata) +{ + LLPanelLandMedia *self = (LLPanelLandMedia *)userdata; + + LLParcel* parcel = self->mParcel->getParcel(); + if (!parcel) + { + return; + } + + // Extract data from UI + std::string media_url = self->mMediaURLEdit->getText(); + std::string media_desc = self->mMediaDescEdit->getText(); + std::string mime_type = self->getChild("mime_type")->getValue().asString(); + U8 media_auto_scale = static_cast(self->mMediaAutoScaleCheck->get()); + U8 media_loop = static_cast(self->mMediaLoopCheck->get()); + S32 media_width = (S32)self->mMediaWidthCtrl->get(); + S32 media_height = (S32)self->mMediaHeightCtrl->get(); + LLUUID media_id = self->mMediaTextureCtrl->getImageAssetID(); + + + self->getChild("mime_type")->setValue(mime_type); + + // Remove leading/trailing whitespace (common when copying/pasting) + LLStringUtil::trim(media_url); + + // Push data into current parcel + parcel->setMediaURL(media_url); + parcel->setMediaType(mime_type); + parcel->setMediaDesc(media_desc); + parcel->setMediaWidth(media_width); + parcel->setMediaHeight(media_height); + parcel->setMediaID(media_id); + parcel->setMediaAutoScale ( media_auto_scale ); + parcel->setMediaLoop ( media_loop ); + + // Send current parcel data upstream to server + LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate( parcel ); + + // Might have changed properties, so let's redraw! + self->refresh(); +} +// static +void LLPanelLandMedia::onSetBtn(void *userdata) +{ + LLPanelLandMedia *self = (LLPanelLandMedia *)userdata; + self->mURLEntryFloater = LLFloaterURLEntry::show( self->getHandle(), self->getMediaURL() ); + LLFloater* parent_floater = gFloaterView->getParentFloater(self); + if (parent_floater) + { + parent_floater->addDependentFloater(self->mURLEntryFloater.get()); + } +} + +// static +void LLPanelLandMedia::onResetBtn(void *userdata) +{ + LLPanelLandMedia *self = (LLPanelLandMedia *)userdata; + LLParcel* parcel = self->mParcel->getParcel(); + // LLViewerMedia::navigateHome(); + self->refresh(); + self->getChild("current_url")->setValue(parcel->getMediaURL()); + // LLViewerParcelMedia::sendMediaNavigateMessage(parcel->getMediaURL()); + +} + diff --git a/indra/newview/llpanellandmedia.h b/indra/newview/llpanellandmedia.h index feefd407e1..161784f047 100644 --- a/indra/newview/llpanellandmedia.h +++ b/indra/newview/llpanellandmedia.h @@ -1,73 +1,73 @@ -/** - * @file llpanellandmedia.h - * @brief Allows configuration of "media" for a land parcel, - * for example movies, web pages, and audio. - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLPANELLANDMEDIA_H -#define LLPANELLANDMEDIA_H - -#include "lllineeditor.h" -#include "llpanel.h" -#include "llparcelselection.h" -#include "lluifwd.h" // widget pointer types - -class LLPanelLandMedia -: public LLPanel -{ -public: - LLPanelLandMedia(LLSafeHandle& parcelp); - /*virtual*/ ~LLPanelLandMedia(); - /*virtual*/ bool postBuild(); - void refresh(); - void setMediaType(const std::string& media_type); - void setMediaURL(const std::string& media_type); - std::string getMediaURL(); - -private: - void populateMIMECombo(); - static void onCommitAny(LLUICtrl* ctrl, void *userdata); - static void onCommitType(LLUICtrl* ctrl, void *userdata); - static void onSetBtn(void* userdata); - static void onResetBtn(void* userdata); - -private: - LLLineEditor* mMediaURLEdit; - LLLineEditor* mMediaDescEdit; - LLComboBox* mMediaTypeCombo; - LLButton* mSetURLButton; - LLSpinCtrl* mMediaHeightCtrl; - LLSpinCtrl* mMediaWidthCtrl; - LLTextBox* mMediaSizeCtrlLabel; - LLTextureCtrl* mMediaTextureCtrl; - LLCheckBoxCtrl* mMediaAutoScaleCheck; - LLCheckBoxCtrl* mMediaLoopCheck; - LLHandle mURLEntryFloater; - - - - LLSafeHandle& mParcel; -}; - -#endif +/** + * @file llpanellandmedia.h + * @brief Allows configuration of "media" for a land parcel, + * for example movies, web pages, and audio. + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLPANELLANDMEDIA_H +#define LLPANELLANDMEDIA_H + +#include "lllineeditor.h" +#include "llpanel.h" +#include "llparcelselection.h" +#include "lluifwd.h" // widget pointer types + +class LLPanelLandMedia +: public LLPanel +{ +public: + LLPanelLandMedia(LLSafeHandle& parcelp); + /*virtual*/ ~LLPanelLandMedia(); + /*virtual*/ bool postBuild(); + void refresh(); + void setMediaType(const std::string& media_type); + void setMediaURL(const std::string& media_type); + std::string getMediaURL(); + +private: + void populateMIMECombo(); + static void onCommitAny(LLUICtrl* ctrl, void *userdata); + static void onCommitType(LLUICtrl* ctrl, void *userdata); + static void onSetBtn(void* userdata); + static void onResetBtn(void* userdata); + +private: + LLLineEditor* mMediaURLEdit; + LLLineEditor* mMediaDescEdit; + LLComboBox* mMediaTypeCombo; + LLButton* mSetURLButton; + LLSpinCtrl* mMediaHeightCtrl; + LLSpinCtrl* mMediaWidthCtrl; + LLTextBox* mMediaSizeCtrlLabel; + LLTextureCtrl* mMediaTextureCtrl; + LLCheckBoxCtrl* mMediaAutoScaleCheck; + LLCheckBoxCtrl* mMediaLoopCheck; + LLHandle mURLEntryFloater; + + + + LLSafeHandle& mParcel; +}; + +#endif diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp index b879871ea2..aa90c1960e 100644 --- a/indra/newview/llpanellogin.cpp +++ b/indra/newview/llpanellogin.cpp @@ -1,1369 +1,1369 @@ -/** - * @file llpanellogin.cpp - * @brief Login dialog and logo display - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanellogin.h" -#include "lllayoutstack.h" - -#include "indra_constants.h" // for key and mask constants -#include "llfloaterreg.h" -#include "llfontgl.h" -#include "llmd5.h" -#include "v4color.h" - -#include "llappviewer.h" -#include "llbutton.h" -#include "llcheckboxctrl.h" -#include "llcommandhandler.h" // for secondlife:///app/login/ -#include "llcombobox.h" -#include "llviewercontrol.h" -#include "llfocusmgr.h" -#include "lllineeditor.h" -#include "llnotificationsutil.h" -#include "llsecapi.h" -#include "llstartup.h" -#include "lltextbox.h" -#include "llui.h" -#include "lluiconstants.h" -#include "llslurl.h" -#include "llversioninfo.h" -#include "llviewerhelp.h" -#include "llviewertexturelist.h" -#include "llviewermenu.h" // for handle_preferences() -#include "llviewernetwork.h" -#include "llviewerwindow.h" // to link into child list -#include "lluictrlfactory.h" -#include "llweb.h" -#include "llmediactrl.h" -#include "llrootview.h" - -#include "llfloatertos.h" -#include "lltrans.h" -#include "llglheaders.h" -#include "llpanelloginlistener.h" -#include "stringize.h" - -#if LL_WINDOWS -#pragma warning(disable: 4355) // 'this' used in initializer list -#endif // LL_WINDOWS - -#include "llsdserialize.h" - -LLPanelLogin *LLPanelLogin::sInstance = NULL; -bool LLPanelLogin::sCapslockDidNotification = false; -bool LLPanelLogin::sCredentialSet = false; - -// Helper functions - -LLPointer load_user_credentials(std::string &user_key) -{ - if (gSecAPIHandler->hasCredentialMap("login_list", LLGridManager::getInstance()->getGrid())) - { - // user_key should be of "name Resident" format - return gSecAPIHandler->loadFromCredentialMap("login_list", LLGridManager::getInstance()->getGrid(), user_key); - } - else - { - // legacy (or legacy^2, since it also tries to load from settings) - return gSecAPIHandler->loadCredential(LLGridManager::getInstance()->getGrid()); - } -} - -class LLLoginLocationAutoHandler : public LLCommandHandler -{ -public: - // don't allow from external browsers - LLLoginLocationAutoHandler() : LLCommandHandler("location_login", UNTRUSTED_BLOCK) { } - bool handle(const LLSD& tokens, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) - { - if (LLStartUp::getStartupState() < STATE_LOGIN_CLEANUP) - { - if ( tokens.size() == 0 || tokens.size() > 4 ) - return false; - - // unescape is important - uris with spaces are escaped in this code path - // (e.g. space -> %20) and the code to log into a region doesn't support that. - const std::string region = LLURI::unescape( tokens[0].asString() ); - - // just region name as payload - if ( tokens.size() == 1 ) - { - // region name only - slurl will end up as center of region - LLSLURL slurl(region); - LLPanelLogin::autologinToLocation(slurl); - } - else - // region name and x coord as payload - if ( tokens.size() == 2 ) - { - // invalid to only specify region and x coordinate - // slurl code will revert to same as region only, so do this anyway - LLSLURL slurl(region); - LLPanelLogin::autologinToLocation(slurl); - } - else - // region name and x/y coord as payload - if ( tokens.size() == 3 ) - { - // region and x/y specified - default z to 0 - F32 xpos; - std::istringstream codec(tokens[1].asString()); - codec >> xpos; - - F32 ypos; - codec.clear(); - codec.str(tokens[2].asString()); - codec >> ypos; - - const LLVector3 location(xpos, ypos, 0.0f); - LLSLURL slurl(region, location); - - LLPanelLogin::autologinToLocation(slurl); - } - else - // region name and x/y/z coord as payload - if ( tokens.size() == 4 ) - { - // region and x/y/z specified - ok - F32 xpos; - std::istringstream codec(tokens[1].asString()); - codec >> xpos; - - F32 ypos; - codec.clear(); - codec.str(tokens[2].asString()); - codec >> ypos; - - F32 zpos; - codec.clear(); - codec.str(tokens[3].asString()); - codec >> zpos; - - const LLVector3 location(xpos, ypos, zpos); - LLSLURL slurl(region, location); - - LLPanelLogin::autologinToLocation(slurl); - }; - } - return true; - } -}; -LLLoginLocationAutoHandler gLoginLocationAutoHandler; - -//--------------------------------------------------------------------------- -// Public methods -//--------------------------------------------------------------------------- -LLPanelLogin::LLPanelLogin(const LLRect &rect, - void (*callback)(S32 option, void* user_data), - void *cb_data) -: LLPanel(), - mCallback(callback), - mCallbackData(cb_data), - mListener(new LLPanelLoginListener(this)), - mFirstLoginThisInstall(gSavedSettings.getBOOL("FirstLoginThisInstall")), - mUsernameLength(0), - mPasswordLength(0), - mLocationLength(0), - mShowFavorites(false) -{ - setBackgroundVisible(false); - setBackgroundOpaque(true); - - mPasswordModified = false; - - sInstance = this; - - LLView* login_holder = gViewerWindow->getLoginPanelHolder(); - if (login_holder) - { - login_holder->addChild(this); - } - - if (mFirstLoginThisInstall) - { - buildFromFile( "panel_login_first.xml"); - } - else - { - buildFromFile( "panel_login.xml"); - } - - reshape(rect.getWidth(), rect.getHeight()); - - LLLineEditor* password_edit(getChild("password_edit")); - password_edit->setKeystrokeCallback(onPassKey, this); - // STEAM-14: When user presses Enter with this field in focus, initiate login - password_edit->setCommitCallback(boost::bind(&LLPanelLogin::onClickConnect, false)); - - // change z sort of clickable text to be behind buttons - sendChildToBack(getChildView("forgot_password_text")); - sendChildToBack(getChildView("sign_up_text")); - - std::string current_grid = LLGridManager::getInstance()->getGrid(); - if (!mFirstLoginThisInstall) - { - LLComboBox* favorites_combo = getChild("start_location_combo"); - updateLocationSelectorsVisibility(); // separate so that it can be called from preferences - favorites_combo->setReturnCallback(boost::bind(&LLPanelLogin::onClickConnect, false)); - favorites_combo->setFocusLostCallback(boost::bind(&LLPanelLogin::onLocationSLURL, this)); - - LLComboBox* server_choice_combo = getChild("server_combo"); - server_choice_combo->setCommitCallback(boost::bind(&LLPanelLogin::onSelectServer, this)); - - // Load all of the grids, sorted, and then add a bar and the current grid at the top - server_choice_combo->removeall(); - - std::map known_grids = LLGridManager::getInstance()->getKnownGrids(); - for (std::map::iterator grid_choice = known_grids.begin(); - grid_choice != known_grids.end(); - grid_choice++) - { - if (!grid_choice->first.empty() && current_grid != grid_choice->first) - { - LL_DEBUGS("AppInit") << "adding " << grid_choice->first << LL_ENDL; - server_choice_combo->add(grid_choice->second, grid_choice->first); - } - } - server_choice_combo->sortByName(); - - LL_DEBUGS("AppInit") << "adding current " << current_grid << LL_ENDL; - server_choice_combo->add(LLGridManager::getInstance()->getGridLabel(), - current_grid, - ADD_TOP); - server_choice_combo->selectFirstItem(); - } - - LLSLURL start_slurl(LLStartUp::getStartSLURL()); - // The StartSLURL might have been set either by an explicit command-line - // argument (CmdLineLoginLocation) or by default. - // current_grid might have been set either by an explicit command-line - // argument (CmdLineGridChoice) or by default. - // If the grid specified by StartSLURL is the same as current_grid, the - // distinction is moot. - // If we have an explicit command-line SLURL, use that. - // If we DON'T have an explicit command-line SLURL but we DO have an - // explicit command-line grid, which is different from the default SLURL's - // -- do NOT override the explicit command-line grid with the grid from - // the default SLURL! - bool force_grid{ start_slurl.getGrid() != current_grid && - gSavedSettings.getString("CmdLineLoginLocation").empty() && - ! gSavedSettings.getString("CmdLineGridChoice").empty() }; - if ( !start_slurl.isSpatial() ) // has a start been established by the command line or NextLoginLocation ? - { - // no, so get the preference setting - std::string defaultStartLocation = gSavedSettings.getString("LoginLocation"); - LL_INFOS("AppInit")<<"default LoginLocation '"<("connect_btn"); - setDefaultBtn(def_btn); - - std::string channel = LLVersionInfo::instance().getChannel(); - std::string version = stringize(LLVersionInfo::instance().getShortVersion(), " (", - LLVersionInfo::instance().getBuild(), ')'); - - LLTextBox* forgot_password_text = getChild("forgot_password_text"); - forgot_password_text->setClickedCallback(onClickForgotPassword, NULL); - - LLTextBox* sign_up_text = getChild("sign_up_text"); - sign_up_text->setClickedCallback(onClickSignUp, NULL); - - // get the web browser control - LLMediaCtrl* web_browser = getChild("login_html"); - web_browser->addObserver(this); - - loadLoginPage(); - - LLComboBox* username_combo(getChild("username_combo")); - username_combo->setTextChangedCallback(boost::bind(&LLPanelLogin::onUserNameTextEnty, this)); - // STEAM-14: When user presses Enter with this field in focus, initiate login - username_combo->setCommitCallback(boost::bind(&LLPanelLogin::onUserListCommit, this)); - username_combo->setReturnCallback(boost::bind(&LLPanelLogin::onClickConnect, this)); - username_combo->setKeystrokeOnEsc(true); - - - LLCheckBoxCtrl* remember_name = getChild("remember_name"); - remember_name->setCommitCallback(boost::bind(&LLPanelLogin::onRememberUserCheck, this)); - getChild("remember_password")->setCommitCallback(boost::bind(&LLPanelLogin::onRememberPasswordCheck, this)); -} - -void LLPanelLogin::addFavoritesToStartLocation() -{ - if (mFirstLoginThisInstall) - { - // first login panel has no favorites, just update name length and buttons - std::string user_defined_name = getChild("username_combo")->getSimple(); - mUsernameLength = user_defined_name.length(); - updateLoginButtons(); - return; - } - - // Clear the combo. - LLComboBox* combo = getChild("start_location_combo"); - if (!combo) return; - int num_items = combo->getItemCount(); - for (int i = num_items - 1; i > 1; i--) - { - combo->remove(i); - } - - // Load favorites into the combo. - std::string user_defined_name = getChild("username_combo")->getSimple(); - LLStringUtil::trim(user_defined_name); - LLStringUtil::toLower(user_defined_name); - std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "stored_favorites_" + LLGridManager::getInstance()->getGrid() + ".xml"); - std::string old_filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "stored_favorites.xml"); - mUsernameLength = user_defined_name.length(); - updateLoginButtons(); - - std::string::size_type index = user_defined_name.find_first_of(" ._"); - if (index != std::string::npos) - { - std::string username = user_defined_name.substr(0, index); - std::string lastname = user_defined_name.substr(index+1); - if (lastname == "resident") - { - user_defined_name = username; - } - else - { - user_defined_name = username + " " + lastname; - } - } - - LLSD fav_llsd; - llifstream file; - file.open(filename.c_str()); - if (!file.is_open()) - { - file.open(old_filename.c_str()); - if (!file.is_open()) return; - } - LLSDSerialize::fromXML(fav_llsd, file); - - for (LLSD::map_const_iterator iter = fav_llsd.beginMap(); - iter != fav_llsd.endMap(); ++iter) - { - // The account name in stored_favorites.xml has Resident last name even if user has - // a single word account name, so it can be compared case-insensitive with the - // user defined "firstname lastname". - S32 res = LLStringUtil::compareInsensitive(user_defined_name, iter->first); - if (res != 0) - { - LL_DEBUGS() << "Skipping favorites for " << iter->first << LL_ENDL; - continue; - } - - combo->addSeparator(); - LL_DEBUGS() << "Loading favorites for " << iter->first << LL_ENDL; - LLSD user_llsd = iter->second; - bool update_password_setting = true; - for (LLSD::array_const_iterator iter1 = user_llsd.beginArray(); - iter1 != user_llsd.endArray(); ++iter1) - { - if ((*iter1).has("save_password")) - { - bool save_password = (*iter1)["save_password"].asBoolean(); - gSavedSettings.setBOOL("RememberPassword", save_password); - if (!save_password) - { - getChild("connect_btn")->setEnabled(false); - } - update_password_setting = false; - } - - std::string label = (*iter1)["name"].asString(); - std::string value = (*iter1)["slurl"].asString(); - if(label != "" && value != "") - { - mShowFavorites = true; - combo->add(label, value); - if ( LLStartUp::getStartSLURL().getSLURLString() == value) - { - combo->selectByValue(value); - } - } - } - if (update_password_setting) - { - gSavedSettings.setBOOL("UpdateRememberPasswordSetting", true); - } - break; - } - if (combo->getValue().asString().empty()) - { - combo->selectFirstItem(); - // Value 'home' or 'last' should have been taken from NextLoginLocation - // but NextLoginLocation was not set, so init it from combo explicitly - onLocationSLURL(); - } -} - -LLPanelLogin::~LLPanelLogin() -{ - LLPanelLogin::sInstance = NULL; - - // Controls having keyboard focus by default - // must reset it on destroy. (EXT-2748) - gFocusMgr.setDefaultKeyboardFocus(NULL); -} - -// virtual -void LLPanelLogin::setFocus(bool b) -{ - if(b != hasFocus()) - { - if(b) - { - giveFocus(); - } - else - { - LLPanel::setFocus(b); - } - } -} - -// static -void LLPanelLogin::giveFocus() -{ - if( sInstance ) - { - // Grab focus and move cursor to first blank input field - std::string username = sInstance->getChild("username_combo")->getValue().asString(); - std::string pass = sInstance->getChild("password_edit")->getValue().asString(); - - bool have_username = !username.empty(); - bool have_pass = !pass.empty(); - - LLLineEditor* edit = NULL; - LLComboBox* combo = NULL; - if (have_username && !have_pass) - { - // User saved his name but not his password. Move - // focus to password field. - edit = sInstance->getChild("password_edit"); - } - else - { - // User doesn't have a name, so start there. - combo = sInstance->getChild("username_combo"); - } - - if (edit) - { - edit->setFocus(true); - edit->selectAll(); - } - else if (combo) - { - combo->setFocus(true); - } - } -} - -// static -void LLPanelLogin::show(const LLRect &rect, - void (*callback)(S32 option, void* user_data), - void* callback_data) -{ - if (!LLPanelLogin::sInstance) - { - new LLPanelLogin(rect, callback, callback_data); - } - - if( !gFocusMgr.getKeyboardFocus() ) - { - // Grab focus and move cursor to first enabled control - sInstance->setFocus(true); - } - - // Make sure that focus always goes here (and use the latest sInstance that was just created) - gFocusMgr.setDefaultKeyboardFocus(sInstance); -} - -//static -void LLPanelLogin::reshapePanel() -{ - if (sInstance) - { - LLRect rect = sInstance->getRect(); - sInstance->reshape(rect.getWidth(), rect.getHeight()); - } -} - -//static -void LLPanelLogin::populateFields(LLPointer credential, bool remember_user, bool remember_psswrd) -{ - if (!sInstance) - { - LL_WARNS() << "Attempted fillFields with no login view shown" << LL_ENDL; - return; - } - - sInstance->getChild("remember_name")->setValue(remember_user); - LLUICtrl* remember_password = sInstance->getChild("remember_password"); - remember_password->setValue(remember_user && remember_psswrd); - remember_password->setEnabled(remember_user); - sInstance->populateUserList(credential); -} - -//static -void LLPanelLogin::resetFields() -{ - if (!sInstance) - { - // class not existing at this point might happen since this - // function is used to reset list in case of changes by external sources - return; - } - if (sInstance->mFirstLoginThisInstall) - { - // no list to populate - LL_WARNS() << "Shouldn't happen, user should have no ability to modify list on first install" << LL_ENDL; - } - else - { - LLPointer cred = gSecAPIHandler->loadCredential(LLGridManager::getInstance()->getGrid()); - sInstance->populateUserList(cred); - } -} - -// static -void LLPanelLogin::setFields(LLPointer credential) -{ - if (!sInstance) - { - LL_WARNS() << "Attempted fillFields with no login view shown" << LL_ENDL; - return; - } - sCredentialSet = true; - LL_INFOS("Credentials") << "Setting login fields to " << *credential << LL_ENDL; - - LLSD identifier = credential.notNull() ? credential->getIdentifier() : LLSD(); - - if(identifier.has("type") && (std::string)identifier["type"] == "agent") - { - // not nessesary for panel_login.xml, needed for panel_login_first.xml - std::string firstname = identifier["first_name"].asString(); - std::string lastname = identifier["last_name"].asString(); - std::string login_id = firstname; - if (!lastname.empty() && lastname != "Resident" && lastname != "resident") - { - // support traditional First Last name SLURLs - login_id += " "; - login_id += lastname; - } - sInstance->getChild("username_combo")->setLabel(login_id); - sInstance->mUsernameLength = login_id.length(); - } - else if(identifier.has("type") && (std::string)identifier["type"] == "account") - { - std::string login_id = identifier["account_name"].asString(); - sInstance->getChild("username_combo")->setLabel(login_id); - sInstance->mUsernameLength = login_id.length(); - } - else - { - sInstance->getChild("username_combo")->setLabel(std::string()); - sInstance->mUsernameLength = 0; - } - - sInstance->addFavoritesToStartLocation(); - // if the password exists in the credential, set the password field with - // a filler to get some stars - LLSD authenticator = credential.notNull() ? credential->getAuthenticator() : LLSD(); - LL_INFOS("Credentials") << "Setting authenticator field " << authenticator["type"].asString() << LL_ENDL; - if(authenticator.isMap() && - authenticator.has("secret") && - (authenticator["secret"].asString().size() > 0)) - { - - // This is a MD5 hex digest of a password. - // We don't actually use the password input field, - // fill it with MAX_PASSWORD characters so we get a - // nice row of asterisks. - const std::string filler("123456789!123456"); - sInstance->getChild("password_edit")->setValue(filler); - sInstance->mPasswordLength = filler.length(); - sInstance->updateLoginButtons(); - } - else - { - sInstance->getChild("password_edit")->setValue(std::string()); - sInstance->mPasswordLength = 0; - } -} - -// static -void LLPanelLogin::getFields(LLPointer& credential, - bool& remember_user, - bool& remember_psswrd) -{ - if (!sInstance) - { - LL_WARNS() << "Attempted getFields with no login view shown" << LL_ENDL; - return; - } - - LLSD identifier = LLSD::emptyMap(); - LLSD authenticator = LLSD::emptyMap(); - - std::string username = sInstance->getChild("username_combo")->getSimple(); - std::string password = sInstance->getChild("password_edit")->getValue().asString(); - LLStringUtil::trim(username); - - LL_INFOS("Credentials", "Authentication") << "retrieving username:" << username << LL_ENDL; - // determine if the username is a first/last form or not. - size_t separator_index = username.find_first_of(' '); - { - // Be lenient in terms of what separators we allow for two-word names - // and allow legacy users to login with firstname.lastname - separator_index = username.find_first_of(" ._"); - std::string first = username.substr(0, separator_index); - std::string last; - if (separator_index != username.npos) - { - last = username.substr(separator_index+1, username.npos); - LLStringUtil::trim(last); - } - else - { - // ...on Linden grids, single username users as considered to have - // last name "Resident" - // *TODO: Make login.cgi support "account_name" like above - last = "Resident"; - } - - if (last.find_first_of(' ') == last.npos) - { - LL_INFOS("Credentials", "Authentication") << "agent: " << username << LL_ENDL; - // traditional firstname / lastname - identifier["type"] = CRED_IDENTIFIER_TYPE_AGENT; - identifier["first_name"] = first; - identifier["last_name"] = last; - - if (LLPanelLogin::sInstance->mPasswordModified) - { - authenticator = LLSD::emptyMap(); - authenticator["type"] = CRED_AUTHENTICATOR_TYPE_HASH; - authenticator["algorithm"] = "md5"; - LLMD5 pass((const U8 *)password.c_str()); - char md5pass[33]; /* Flawfinder: ignore */ - pass.hex_digest(md5pass); - authenticator["secret"] = md5pass; - } - else - { - std::string key = first + "_" + last; - LLStringUtil::toLower(key); - credential = load_user_credentials(key); - if (credential.notNull()) - { - authenticator = credential->getAuthenticator(); - } - } - } - } - credential = gSecAPIHandler->createCredential(LLGridManager::getInstance()->getGrid(), identifier, authenticator); - - remember_psswrd = sInstance->getChild("remember_password")->getValue(); - remember_user = sInstance->getChild("remember_name")->getValue(); -} - - -// static -bool LLPanelLogin::areCredentialFieldsDirty() -{ - if (!sInstance) - { - LL_WARNS() << "Attempted getServer with no login view shown" << LL_ENDL; - } - else - { - LLComboBox* combo = sInstance->getChild("username_combo"); - if (combo && combo->getCurrentIndex() == -1 && !combo->getValue().asString().empty()) - { - return true; - } - LLLineEditor* ctrl = sInstance->getChild("password_edit"); - if(ctrl && ctrl->isDirty()) - { - return true; - } - } - return false; -} - - -// static -void LLPanelLogin::updateLocationSelectorsVisibility() -{ - if (sInstance) - { - bool show_server = gSavedSettings.getBOOL("ForceShowGrid"); - LLComboBox* server_combo = sInstance->getChild("server_combo"); - if ( server_combo ) - { - server_combo->setVisible(show_server); - } - } -} - -// static - called from LLStartUp::setStartSLURL -void LLPanelLogin::onUpdateStartSLURL(const LLSLURL& new_start_slurl) -{ - if (!sInstance) return; - - LL_DEBUGS("AppInit")<getChild("start_location_combo"); - /* - * Determine whether or not the new_start_slurl modifies the grid. - * - * Note that some forms that could be in the slurl are grid-agnostic., - * such as "home". Other forms, such as - * https://grid.example.com/region/Party%20Town/20/30/5 - * specify a particular grid; in those cases we want to change the grid - * and the grid selector to match the new value. - */ - enum LLSLURL::SLURL_TYPE new_slurl_type = new_start_slurl.getType(); - switch ( new_slurl_type ) - { - case LLSLURL::LOCATION: - { - std::string slurl_grid = LLGridManager::getInstance()->getGrid(new_start_slurl.getGrid()); - if ( ! slurl_grid.empty() ) // is that a valid grid? - { - if ( slurl_grid != LLGridManager::getInstance()->getGrid() ) // new grid? - { - // the slurl changes the grid, so update everything to match - LLGridManager::getInstance()->setGridChoice(slurl_grid); - - // update the grid selector to match the slurl - LLComboBox* server_combo = sInstance->getChild("server_combo"); - std::string server_label(LLGridManager::getInstance()->getGridLabel(slurl_grid)); - server_combo->setSimple(server_label); - - updateServer(); // to change the links and splash screen - } - if ( new_start_slurl.getLocationString().length() ) - { - - location_combo->setLabel(new_start_slurl.getLocationString()); - sInstance->mLocationLength = new_start_slurl.getLocationString().length(); - sInstance->updateLoginButtons(); - } - } - else - { - // the grid specified by the slurl is not known - LLNotificationsUtil::add("InvalidLocationSLURL"); - LL_WARNS("AppInit")<<"invalid LoginLocation:"<setTextEntry(LLStringUtil::null); - } - } - break; - - case LLSLURL::HOME_LOCATION: - //location_combo->setCurrentByIndex(0); // home location - break; - - default: - LL_WARNS("AppInit")<<"invalid login slurl, using home"<setCurrentByIndex(0); // home location - break; - } -} - -void LLPanelLogin::setLocation(const LLSLURL& slurl) -{ - LL_DEBUGS("AppInit")<<"setting Location "<onClickConnect(unused_parameter); - } -} - - -// static -void LLPanelLogin::closePanel() -{ - if (sInstance) - { - if (LLPanelLogin::sInstance->getParent()) - { - LLPanelLogin::sInstance->getParent()->removeChild(LLPanelLogin::sInstance); - } - - delete sInstance; - sInstance = NULL; - } -} - -// static -void LLPanelLogin::setAlwaysRefresh(bool refresh) -{ - if (sInstance && LLStartUp::getStartupState() < STATE_LOGIN_CLEANUP) - { - LLMediaCtrl* web_browser = sInstance->getChild("login_html"); - - if (web_browser) - { - web_browser->setAlwaysRefresh(refresh); - } - } -} - - - -void LLPanelLogin::loadLoginPage() -{ - if (!sInstance) return; - - LLURI login_page = LLURI(LLGridManager::getInstance()->getLoginPage()); - LLSD params(login_page.queryMap()); - - LL_DEBUGS("AppInit") << "login_page: " << login_page << LL_ENDL; - - // allow users (testers really) to specify a different login content URL - std::string force_login_url = gSavedSettings.getString("ForceLoginURL"); - if ( force_login_url.length() > 0 ) - { - login_page = LLURI(force_login_url); - } - - // Language - params["lang"] = LLUI::getLanguage(); - - // First Login? - if (gSavedSettings.getBOOL("FirstLoginThisInstall")) - { - params["firstlogin"] = "true"; // not bool: server expects string true - } - - // Channel and Version - params["version"] = stringize(LLVersionInfo::instance().getShortVersion(), " (", - LLVersionInfo::instance().getBuild(), ')'); - params["channel"] = LLVersionInfo::instance().getChannel(); - - // Grid - params["grid"] = LLGridManager::getInstance()->getGridId(); - - // add OS info - params["os"] = LLOSInfo::instance().getOSStringSimple(); - - // sourceid - params["sourceid"] = gSavedSettings.getString("sourceid"); - - // login page (web) content version - params["login_content_version"] = gSavedSettings.getString("LoginContentVersion"); - - // Make an LLURI with this augmented info - std::string url = login_page.scheme().empty()? login_page.authority() : login_page.scheme() + "://" + login_page.authority(); - LLURI login_uri(LLURI::buildHTTP(url, - login_page.path(), - params)); - - gViewerWindow->setMenuBackgroundColor(false, !LLGridManager::getInstance()->isInProductionGrid()); - - LLMediaCtrl* web_browser = sInstance->getChild("login_html"); - if (web_browser->getCurrentNavUrl() != login_uri.asString()) - { - LL_DEBUGS("AppInit") << "loading: " << login_uri << LL_ENDL; - web_browser->navigateTo( login_uri.asString(), "text/html" ); - } -} - -void LLPanelLogin::handleMediaEvent(LLPluginClassMedia* /*self*/, EMediaEvent event) -{ -} - -//--------------------------------------------------------------------------- -// Protected methods -//--------------------------------------------------------------------------- -// static -void LLPanelLogin::onClickConnect(bool commit_fields) -{ - if (sInstance && sInstance->mCallback) - { - if (commit_fields) - { - // JC - Make sure the fields all get committed. - sInstance->setFocus(false); - } - - LLComboBox* combo = sInstance->getChild("server_combo"); - LLSD combo_val = combo->getSelectedValue(); - - // the grid definitions may come from a user-supplied grids.xml, so they may not be good - LL_DEBUGS("AppInit")<<"grid "<setGridChoice(combo_val.asString()); - } - catch (LLInvalidGridName ex) - { - LLSD args; - args["GRID"] = ex.name(); - LLNotificationsUtil::add("InvalidGrid", args); - return; - } - - // The start location SLURL has already been sent to LLStartUp::setStartSLURL - - std::string username = sInstance->getChild("username_combo")->getValue().asString(); - std::string password = sInstance->getChild("password_edit")->getValue().asString(); - - if(username.empty()) - { - // user must type in something into the username field - LLNotificationsUtil::add("MustHaveAccountToLogIn"); - } - else if(password.empty()) - { - LLNotificationsUtil::add("MustEnterPasswordToLogIn"); - } - else - { - sCredentialSet = false; - LLPointer cred; - bool remember_1, remember_2; - getFields(cred, remember_1, remember_2); - std::string identifier_type; - cred->identifierType(identifier_type); - LLSD allowed_credential_types; - LLGridManager::getInstance()->getLoginIdentifierTypes(allowed_credential_types); - - // check the typed in credential type against the credential types expected by the server. - for(LLSD::array_iterator i = allowed_credential_types.beginArray(); - i != allowed_credential_types.endArray(); - i++) - { - - if(i->asString() == identifier_type) - { - // yay correct credential type - sInstance->mCallback(0, sInstance->mCallbackData); - return; - } - } - - // Right now, maingrid is the only thing that is picky about - // credential format, as it doesn't yet allow account (single username) - // format creds. - Rox. James, we wanna fix the message when we change - // this. - LLNotificationsUtil::add("InvalidCredentialFormat"); - } - } -} - -// static -void LLPanelLogin::onClickVersion(void*) -{ - LLFloaterReg::showInstance("sl_about"); -} - -//static -void LLPanelLogin::onClickForgotPassword(void*) -{ - if (sInstance ) - { - LLWeb::loadURLExternal(sInstance->getString( "forgot_password_url" )); - } -} - -//static -void LLPanelLogin::onClickSignUp(void*) -{ - if (sInstance) - { - LLWeb::loadURLExternal(sInstance->getString("sign_up_url")); - } -} - -// static -void LLPanelLogin::onUserNameTextEnty(void*) -{ - sInstance->mPasswordModified = true; - sInstance->getChild("password_edit")->setValue(std::string()); - sInstance->mPasswordLength = 0; - sInstance->addFavoritesToStartLocation(); //will call updateLoginButtons() -} - -// static -void LLPanelLogin::onUserListCommit(void*) -{ - if (sInstance) - { - LLComboBox* username_combo(sInstance->getChild("username_combo")); - static S32 ind = -1; - if (ind != username_combo->getCurrentIndex()) - { - std::string user_key = username_combo->getSelectedValue(); - LLPointer cred = gSecAPIHandler->loadFromCredentialMap("login_list", LLGridManager::getInstance()->getGrid(), user_key); - setFields(cred); - sInstance->mPasswordModified = false; - } - else - { - std::string pass = sInstance->getChild("password_edit")->getValue().asString(); - if (pass.empty()) - { - sInstance->giveFocus(); - } - else - { - onClickConnect(); - } - } - } -} - -// static -void LLPanelLogin::onRememberUserCheck(void*) -{ - if (sInstance) - { - LLCheckBoxCtrl* remember_name(sInstance->getChild("remember_name")); - LLCheckBoxCtrl* remember_psswrd(sInstance->getChild("remember_password")); - LLComboBox* user_combo(sInstance->getChild("username_combo")); - - bool remember = remember_name->getValue().asBoolean(); - if (!sInstance->mFirstLoginThisInstall - && user_combo->getCurrentIndex() != -1 - && !remember) - { - remember = true; - remember_name->setValue(true); - LLNotificationsUtil::add("LoginCantRemoveUsername"); - } - if (!remember) - { - remember_psswrd->setValue(false); - } - remember_psswrd->setEnabled(remember); - } -} - -void LLPanelLogin::onRememberPasswordCheck(void*) -{ - if (sInstance) - { - gSavedSettings.setBOOL("UpdateRememberPasswordSetting", true); - - LLPointer cred; - bool remember_user, remember_password; - getFields(cred, remember_user, remember_password); - - std::string grid(LLGridManager::getInstance()->getGridId()); - std::string user_id(cred->userID()); - } -} - -// static -void LLPanelLogin::onPassKey(LLLineEditor* caller, void* user_data) -{ - LLPanelLogin *self = (LLPanelLogin *)user_data; - self->mPasswordModified = true; - if (gKeyboard->getKeyDown(KEY_CAPSLOCK) && !sCapslockDidNotification) - { - // *TODO: use another way to notify user about enabled caps lock, see EXT-6858 - sCapslockDidNotification = true; - } - - LLLineEditor* password_edit(self->getChild("password_edit")); - self->mPasswordLength = password_edit->getText().length(); - self->updateLoginButtons(); -} - - -void LLPanelLogin::updateServer() -{ - if (sInstance) - { - try - { - // if they've selected another grid, we should load the credentials - // for that grid and set them to the UI. But if there were any modifications to - // fields, modifications should carry over. - // Not sure if it should carry over password but it worked like this before login changes - // Example: you started typing in and found that your are under wrong grid, - // you switch yet don't lose anything - if (sInstance->areCredentialFieldsDirty()) - { - // save modified creds - LLComboBox* user_combo = sInstance->getChild("username_combo"); - LLLineEditor* pswd_edit = sInstance->getChild("password_edit"); - std::string username = user_combo->getSimple(); - LLStringUtil::trim(username); - std::string password = pswd_edit->getValue().asString(); - - // populate dropbox and setFields - // Note: following call is related to initializeLoginInfo() - LLPointer credential = gSecAPIHandler->loadCredential(LLGridManager::getInstance()->getGrid()); - sInstance->populateUserList(credential); - - // restore creds - user_combo->setTextEntry(username); - pswd_edit->setValue(password); - sInstance->mUsernameLength = username.length(); - sInstance->mPasswordLength = password.length(); - } - else - { - // populate dropbox and setFields - // Note: following call is related to initializeLoginInfo() - LLPointer credential = gSecAPIHandler->loadCredential(LLGridManager::getInstance()->getGrid()); - sInstance->populateUserList(credential); - } - - // update the login panel links - bool system_grid = LLGridManager::getInstance()->isSystemGrid(); - - // Want to vanish not only create_new_account_btn, but also the - // title text over it, so turn on/off the whole layout_panel element. - sInstance->getChild("links")->setVisible(system_grid); - sInstance->getChildView("forgot_password_text")->setVisible(system_grid); - - // grid changed so show new splash screen (possibly) - loadLoginPage(); - } - catch (LLInvalidGridName ex) - { - LL_WARNS("AppInit")<<"server '"<("connect_btn"); - - login_btn->setEnabled(mUsernameLength != 0 && mPasswordLength != 0); - - if (!mFirstLoginThisInstall) - { - LLComboBox* user_combo = getChild("username_combo"); - LLCheckBoxCtrl* remember_name = getChild("remember_name"); - if (user_combo->getCurrentIndex() != -1) - { - remember_name->setValue(true); - LLCheckBoxCtrl* remember_pass = getChild("remember_password"); - remember_pass->setEnabled(true); - } // Note: might be good idea to do "else remember_name->setValue(mRememberedState)" but it might behave 'weird' to user - } -} - -void LLPanelLogin::populateUserList(LLPointer credential) -{ - LLComboBox* user_combo = getChild("username_combo"); - user_combo->removeall(); - user_combo->clear(); - user_combo->setValue(std::string()); - getChild("password_edit")->setValue(std::string()); - mUsernameLength = 0; - mPasswordLength = 0; - - if (gSecAPIHandler->hasCredentialMap("login_list", LLGridManager::getInstance()->getGrid())) - { - LLSecAPIHandler::credential_map_t credencials; - gSecAPIHandler->loadCredentialMap("login_list", LLGridManager::getInstance()->getGrid(), credencials); - - LLSecAPIHandler::credential_map_t::iterator cr_iter = credencials.begin(); - LLSecAPIHandler::credential_map_t::iterator cr_end = credencials.end(); - while (cr_iter != cr_end) - { - if (cr_iter->second.notNull()) // basic safety in case of future changes - { - // cr_iter->first == user_id , to be able to be find it in case we select it - user_combo->add(LLPanelLogin::getUserName(cr_iter->second), cr_iter->first, ADD_BOTTOM, true); - } - cr_iter++; - } - - if (credential.isNull() || !user_combo->setSelectedByValue(LLSD(credential->userID()), true)) - { - // selection failed, fields will be mepty - updateLoginButtons(); - } - else - { - setFields(credential); - } - } - else - { - if (credential.notNull()) - { - const LLSD &ident = credential->getIdentifier(); - if (ident.isMap() && ident.has("type")) - { - // this llsd might hold invalid credencial (failed login), so - // do not add to the list, just set field. - setFields(credential); - } - else - { - updateLoginButtons(); - } - } - else - { - updateLoginButtons(); - } - } -} - - -void LLPanelLogin::onSelectServer() -{ - // The user twiddled with the grid choice ui. - // apply the selection to the grid setting. - LLComboBox* server_combo = getChild("server_combo"); - LLSD server_combo_val = server_combo->getSelectedValue(); - LL_INFOS("AppInit") << "grid "<setGridChoice(server_combo_val.asString()); - addFavoritesToStartLocation(); - - /* - * Determine whether or not the value in the start_location_combo makes sense - * with the new grid value. - * - * Note that some forms that could be in the location combo are grid-agnostic, - * such as "MyRegion/128/128/0". There could be regions with that name on any - * number of grids, so leave them alone. Other forms, such as - * https://grid.example.com/region/Party%20Town/20/30/5 specify a particular - * grid; in those cases we want to clear the location. - */ - LLComboBox* location_combo = getChild("start_location_combo"); - S32 index = location_combo->getCurrentIndex(); - switch (index) - { - case 0: // last location - LLStartUp::setStartSLURL(LLSLURL(LLSLURL::SIM_LOCATION_LAST)); - break; - case 1: // home location - LLStartUp::setStartSLURL(LLSLURL(LLSLURL::SIM_LOCATION_HOME)); - break; - - default: - { - std::string location = location_combo->getValue().asString(); - LLSLURL slurl(location); // generata a slurl from the location combo contents - if (location.empty() - || (slurl.getType() == LLSLURL::LOCATION - && slurl.getGrid() != LLGridManager::getInstance()->getGrid()) - ) - { - // the grid specified by the location is not this one, so clear the combo - location_combo->setCurrentByIndex(0); // last location on the new grid - onLocationSLURL(); - } - } - break; - } - - updateServer(); -} - -void LLPanelLogin::onLocationSLURL() -{ - LLComboBox* location_combo = getChild("start_location_combo"); - std::string location = location_combo->getValue().asString(); - LL_DEBUGS("AppInit")< &cred) -{ - if (cred.isNull()) - { - return "unknown"; - } - const LLSD &ident = cred->getIdentifier(); - - if (!ident.isMap()) - { - return "unknown"; - } - else if ((std::string)ident["type"] == "agent") - { - std::string second_name = ident["last_name"]; - if (second_name == "resident" || second_name == "Resident") - { - return (std::string)ident["first_name"]; - } - return (std::string)ident["first_name"] + " " + (std::string)ident["last_name"]; - } - else if ((std::string)ident["type"] == "account") - { - return LLCacheName::cleanFullName((std::string)ident["account_name"]); - } - - return "unknown"; -} - +/** + * @file llpanellogin.cpp + * @brief Login dialog and logo display + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanellogin.h" +#include "lllayoutstack.h" + +#include "indra_constants.h" // for key and mask constants +#include "llfloaterreg.h" +#include "llfontgl.h" +#include "llmd5.h" +#include "v4color.h" + +#include "llappviewer.h" +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "llcommandhandler.h" // for secondlife:///app/login/ +#include "llcombobox.h" +#include "llviewercontrol.h" +#include "llfocusmgr.h" +#include "lllineeditor.h" +#include "llnotificationsutil.h" +#include "llsecapi.h" +#include "llstartup.h" +#include "lltextbox.h" +#include "llui.h" +#include "lluiconstants.h" +#include "llslurl.h" +#include "llversioninfo.h" +#include "llviewerhelp.h" +#include "llviewertexturelist.h" +#include "llviewermenu.h" // for handle_preferences() +#include "llviewernetwork.h" +#include "llviewerwindow.h" // to link into child list +#include "lluictrlfactory.h" +#include "llweb.h" +#include "llmediactrl.h" +#include "llrootview.h" + +#include "llfloatertos.h" +#include "lltrans.h" +#include "llglheaders.h" +#include "llpanelloginlistener.h" +#include "stringize.h" + +#if LL_WINDOWS +#pragma warning(disable: 4355) // 'this' used in initializer list +#endif // LL_WINDOWS + +#include "llsdserialize.h" + +LLPanelLogin *LLPanelLogin::sInstance = NULL; +bool LLPanelLogin::sCapslockDidNotification = false; +bool LLPanelLogin::sCredentialSet = false; + +// Helper functions + +LLPointer load_user_credentials(std::string &user_key) +{ + if (gSecAPIHandler->hasCredentialMap("login_list", LLGridManager::getInstance()->getGrid())) + { + // user_key should be of "name Resident" format + return gSecAPIHandler->loadFromCredentialMap("login_list", LLGridManager::getInstance()->getGrid(), user_key); + } + else + { + // legacy (or legacy^2, since it also tries to load from settings) + return gSecAPIHandler->loadCredential(LLGridManager::getInstance()->getGrid()); + } +} + +class LLLoginLocationAutoHandler : public LLCommandHandler +{ +public: + // don't allow from external browsers + LLLoginLocationAutoHandler() : LLCommandHandler("location_login", UNTRUSTED_BLOCK) { } + bool handle(const LLSD& tokens, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) + { + if (LLStartUp::getStartupState() < STATE_LOGIN_CLEANUP) + { + if ( tokens.size() == 0 || tokens.size() > 4 ) + return false; + + // unescape is important - uris with spaces are escaped in this code path + // (e.g. space -> %20) and the code to log into a region doesn't support that. + const std::string region = LLURI::unescape( tokens[0].asString() ); + + // just region name as payload + if ( tokens.size() == 1 ) + { + // region name only - slurl will end up as center of region + LLSLURL slurl(region); + LLPanelLogin::autologinToLocation(slurl); + } + else + // region name and x coord as payload + if ( tokens.size() == 2 ) + { + // invalid to only specify region and x coordinate + // slurl code will revert to same as region only, so do this anyway + LLSLURL slurl(region); + LLPanelLogin::autologinToLocation(slurl); + } + else + // region name and x/y coord as payload + if ( tokens.size() == 3 ) + { + // region and x/y specified - default z to 0 + F32 xpos; + std::istringstream codec(tokens[1].asString()); + codec >> xpos; + + F32 ypos; + codec.clear(); + codec.str(tokens[2].asString()); + codec >> ypos; + + const LLVector3 location(xpos, ypos, 0.0f); + LLSLURL slurl(region, location); + + LLPanelLogin::autologinToLocation(slurl); + } + else + // region name and x/y/z coord as payload + if ( tokens.size() == 4 ) + { + // region and x/y/z specified - ok + F32 xpos; + std::istringstream codec(tokens[1].asString()); + codec >> xpos; + + F32 ypos; + codec.clear(); + codec.str(tokens[2].asString()); + codec >> ypos; + + F32 zpos; + codec.clear(); + codec.str(tokens[3].asString()); + codec >> zpos; + + const LLVector3 location(xpos, ypos, zpos); + LLSLURL slurl(region, location); + + LLPanelLogin::autologinToLocation(slurl); + }; + } + return true; + } +}; +LLLoginLocationAutoHandler gLoginLocationAutoHandler; + +//--------------------------------------------------------------------------- +// Public methods +//--------------------------------------------------------------------------- +LLPanelLogin::LLPanelLogin(const LLRect &rect, + void (*callback)(S32 option, void* user_data), + void *cb_data) +: LLPanel(), + mCallback(callback), + mCallbackData(cb_data), + mListener(new LLPanelLoginListener(this)), + mFirstLoginThisInstall(gSavedSettings.getBOOL("FirstLoginThisInstall")), + mUsernameLength(0), + mPasswordLength(0), + mLocationLength(0), + mShowFavorites(false) +{ + setBackgroundVisible(false); + setBackgroundOpaque(true); + + mPasswordModified = false; + + sInstance = this; + + LLView* login_holder = gViewerWindow->getLoginPanelHolder(); + if (login_holder) + { + login_holder->addChild(this); + } + + if (mFirstLoginThisInstall) + { + buildFromFile( "panel_login_first.xml"); + } + else + { + buildFromFile( "panel_login.xml"); + } + + reshape(rect.getWidth(), rect.getHeight()); + + LLLineEditor* password_edit(getChild("password_edit")); + password_edit->setKeystrokeCallback(onPassKey, this); + // STEAM-14: When user presses Enter with this field in focus, initiate login + password_edit->setCommitCallback(boost::bind(&LLPanelLogin::onClickConnect, false)); + + // change z sort of clickable text to be behind buttons + sendChildToBack(getChildView("forgot_password_text")); + sendChildToBack(getChildView("sign_up_text")); + + std::string current_grid = LLGridManager::getInstance()->getGrid(); + if (!mFirstLoginThisInstall) + { + LLComboBox* favorites_combo = getChild("start_location_combo"); + updateLocationSelectorsVisibility(); // separate so that it can be called from preferences + favorites_combo->setReturnCallback(boost::bind(&LLPanelLogin::onClickConnect, false)); + favorites_combo->setFocusLostCallback(boost::bind(&LLPanelLogin::onLocationSLURL, this)); + + LLComboBox* server_choice_combo = getChild("server_combo"); + server_choice_combo->setCommitCallback(boost::bind(&LLPanelLogin::onSelectServer, this)); + + // Load all of the grids, sorted, and then add a bar and the current grid at the top + server_choice_combo->removeall(); + + std::map known_grids = LLGridManager::getInstance()->getKnownGrids(); + for (std::map::iterator grid_choice = known_grids.begin(); + grid_choice != known_grids.end(); + grid_choice++) + { + if (!grid_choice->first.empty() && current_grid != grid_choice->first) + { + LL_DEBUGS("AppInit") << "adding " << grid_choice->first << LL_ENDL; + server_choice_combo->add(grid_choice->second, grid_choice->first); + } + } + server_choice_combo->sortByName(); + + LL_DEBUGS("AppInit") << "adding current " << current_grid << LL_ENDL; + server_choice_combo->add(LLGridManager::getInstance()->getGridLabel(), + current_grid, + ADD_TOP); + server_choice_combo->selectFirstItem(); + } + + LLSLURL start_slurl(LLStartUp::getStartSLURL()); + // The StartSLURL might have been set either by an explicit command-line + // argument (CmdLineLoginLocation) or by default. + // current_grid might have been set either by an explicit command-line + // argument (CmdLineGridChoice) or by default. + // If the grid specified by StartSLURL is the same as current_grid, the + // distinction is moot. + // If we have an explicit command-line SLURL, use that. + // If we DON'T have an explicit command-line SLURL but we DO have an + // explicit command-line grid, which is different from the default SLURL's + // -- do NOT override the explicit command-line grid with the grid from + // the default SLURL! + bool force_grid{ start_slurl.getGrid() != current_grid && + gSavedSettings.getString("CmdLineLoginLocation").empty() && + ! gSavedSettings.getString("CmdLineGridChoice").empty() }; + if ( !start_slurl.isSpatial() ) // has a start been established by the command line or NextLoginLocation ? + { + // no, so get the preference setting + std::string defaultStartLocation = gSavedSettings.getString("LoginLocation"); + LL_INFOS("AppInit")<<"default LoginLocation '"<("connect_btn"); + setDefaultBtn(def_btn); + + std::string channel = LLVersionInfo::instance().getChannel(); + std::string version = stringize(LLVersionInfo::instance().getShortVersion(), " (", + LLVersionInfo::instance().getBuild(), ')'); + + LLTextBox* forgot_password_text = getChild("forgot_password_text"); + forgot_password_text->setClickedCallback(onClickForgotPassword, NULL); + + LLTextBox* sign_up_text = getChild("sign_up_text"); + sign_up_text->setClickedCallback(onClickSignUp, NULL); + + // get the web browser control + LLMediaCtrl* web_browser = getChild("login_html"); + web_browser->addObserver(this); + + loadLoginPage(); + + LLComboBox* username_combo(getChild("username_combo")); + username_combo->setTextChangedCallback(boost::bind(&LLPanelLogin::onUserNameTextEnty, this)); + // STEAM-14: When user presses Enter with this field in focus, initiate login + username_combo->setCommitCallback(boost::bind(&LLPanelLogin::onUserListCommit, this)); + username_combo->setReturnCallback(boost::bind(&LLPanelLogin::onClickConnect, this)); + username_combo->setKeystrokeOnEsc(true); + + + LLCheckBoxCtrl* remember_name = getChild("remember_name"); + remember_name->setCommitCallback(boost::bind(&LLPanelLogin::onRememberUserCheck, this)); + getChild("remember_password")->setCommitCallback(boost::bind(&LLPanelLogin::onRememberPasswordCheck, this)); +} + +void LLPanelLogin::addFavoritesToStartLocation() +{ + if (mFirstLoginThisInstall) + { + // first login panel has no favorites, just update name length and buttons + std::string user_defined_name = getChild("username_combo")->getSimple(); + mUsernameLength = user_defined_name.length(); + updateLoginButtons(); + return; + } + + // Clear the combo. + LLComboBox* combo = getChild("start_location_combo"); + if (!combo) return; + int num_items = combo->getItemCount(); + for (int i = num_items - 1; i > 1; i--) + { + combo->remove(i); + } + + // Load favorites into the combo. + std::string user_defined_name = getChild("username_combo")->getSimple(); + LLStringUtil::trim(user_defined_name); + LLStringUtil::toLower(user_defined_name); + std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "stored_favorites_" + LLGridManager::getInstance()->getGrid() + ".xml"); + std::string old_filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "stored_favorites.xml"); + mUsernameLength = user_defined_name.length(); + updateLoginButtons(); + + std::string::size_type index = user_defined_name.find_first_of(" ._"); + if (index != std::string::npos) + { + std::string username = user_defined_name.substr(0, index); + std::string lastname = user_defined_name.substr(index+1); + if (lastname == "resident") + { + user_defined_name = username; + } + else + { + user_defined_name = username + " " + lastname; + } + } + + LLSD fav_llsd; + llifstream file; + file.open(filename.c_str()); + if (!file.is_open()) + { + file.open(old_filename.c_str()); + if (!file.is_open()) return; + } + LLSDSerialize::fromXML(fav_llsd, file); + + for (LLSD::map_const_iterator iter = fav_llsd.beginMap(); + iter != fav_llsd.endMap(); ++iter) + { + // The account name in stored_favorites.xml has Resident last name even if user has + // a single word account name, so it can be compared case-insensitive with the + // user defined "firstname lastname". + S32 res = LLStringUtil::compareInsensitive(user_defined_name, iter->first); + if (res != 0) + { + LL_DEBUGS() << "Skipping favorites for " << iter->first << LL_ENDL; + continue; + } + + combo->addSeparator(); + LL_DEBUGS() << "Loading favorites for " << iter->first << LL_ENDL; + LLSD user_llsd = iter->second; + bool update_password_setting = true; + for (LLSD::array_const_iterator iter1 = user_llsd.beginArray(); + iter1 != user_llsd.endArray(); ++iter1) + { + if ((*iter1).has("save_password")) + { + bool save_password = (*iter1)["save_password"].asBoolean(); + gSavedSettings.setBOOL("RememberPassword", save_password); + if (!save_password) + { + getChild("connect_btn")->setEnabled(false); + } + update_password_setting = false; + } + + std::string label = (*iter1)["name"].asString(); + std::string value = (*iter1)["slurl"].asString(); + if(label != "" && value != "") + { + mShowFavorites = true; + combo->add(label, value); + if ( LLStartUp::getStartSLURL().getSLURLString() == value) + { + combo->selectByValue(value); + } + } + } + if (update_password_setting) + { + gSavedSettings.setBOOL("UpdateRememberPasswordSetting", true); + } + break; + } + if (combo->getValue().asString().empty()) + { + combo->selectFirstItem(); + // Value 'home' or 'last' should have been taken from NextLoginLocation + // but NextLoginLocation was not set, so init it from combo explicitly + onLocationSLURL(); + } +} + +LLPanelLogin::~LLPanelLogin() +{ + LLPanelLogin::sInstance = NULL; + + // Controls having keyboard focus by default + // must reset it on destroy. (EXT-2748) + gFocusMgr.setDefaultKeyboardFocus(NULL); +} + +// virtual +void LLPanelLogin::setFocus(bool b) +{ + if(b != hasFocus()) + { + if(b) + { + giveFocus(); + } + else + { + LLPanel::setFocus(b); + } + } +} + +// static +void LLPanelLogin::giveFocus() +{ + if( sInstance ) + { + // Grab focus and move cursor to first blank input field + std::string username = sInstance->getChild("username_combo")->getValue().asString(); + std::string pass = sInstance->getChild("password_edit")->getValue().asString(); + + bool have_username = !username.empty(); + bool have_pass = !pass.empty(); + + LLLineEditor* edit = NULL; + LLComboBox* combo = NULL; + if (have_username && !have_pass) + { + // User saved his name but not his password. Move + // focus to password field. + edit = sInstance->getChild("password_edit"); + } + else + { + // User doesn't have a name, so start there. + combo = sInstance->getChild("username_combo"); + } + + if (edit) + { + edit->setFocus(true); + edit->selectAll(); + } + else if (combo) + { + combo->setFocus(true); + } + } +} + +// static +void LLPanelLogin::show(const LLRect &rect, + void (*callback)(S32 option, void* user_data), + void* callback_data) +{ + if (!LLPanelLogin::sInstance) + { + new LLPanelLogin(rect, callback, callback_data); + } + + if( !gFocusMgr.getKeyboardFocus() ) + { + // Grab focus and move cursor to first enabled control + sInstance->setFocus(true); + } + + // Make sure that focus always goes here (and use the latest sInstance that was just created) + gFocusMgr.setDefaultKeyboardFocus(sInstance); +} + +//static +void LLPanelLogin::reshapePanel() +{ + if (sInstance) + { + LLRect rect = sInstance->getRect(); + sInstance->reshape(rect.getWidth(), rect.getHeight()); + } +} + +//static +void LLPanelLogin::populateFields(LLPointer credential, bool remember_user, bool remember_psswrd) +{ + if (!sInstance) + { + LL_WARNS() << "Attempted fillFields with no login view shown" << LL_ENDL; + return; + } + + sInstance->getChild("remember_name")->setValue(remember_user); + LLUICtrl* remember_password = sInstance->getChild("remember_password"); + remember_password->setValue(remember_user && remember_psswrd); + remember_password->setEnabled(remember_user); + sInstance->populateUserList(credential); +} + +//static +void LLPanelLogin::resetFields() +{ + if (!sInstance) + { + // class not existing at this point might happen since this + // function is used to reset list in case of changes by external sources + return; + } + if (sInstance->mFirstLoginThisInstall) + { + // no list to populate + LL_WARNS() << "Shouldn't happen, user should have no ability to modify list on first install" << LL_ENDL; + } + else + { + LLPointer cred = gSecAPIHandler->loadCredential(LLGridManager::getInstance()->getGrid()); + sInstance->populateUserList(cred); + } +} + +// static +void LLPanelLogin::setFields(LLPointer credential) +{ + if (!sInstance) + { + LL_WARNS() << "Attempted fillFields with no login view shown" << LL_ENDL; + return; + } + sCredentialSet = true; + LL_INFOS("Credentials") << "Setting login fields to " << *credential << LL_ENDL; + + LLSD identifier = credential.notNull() ? credential->getIdentifier() : LLSD(); + + if(identifier.has("type") && (std::string)identifier["type"] == "agent") + { + // not nessesary for panel_login.xml, needed for panel_login_first.xml + std::string firstname = identifier["first_name"].asString(); + std::string lastname = identifier["last_name"].asString(); + std::string login_id = firstname; + if (!lastname.empty() && lastname != "Resident" && lastname != "resident") + { + // support traditional First Last name SLURLs + login_id += " "; + login_id += lastname; + } + sInstance->getChild("username_combo")->setLabel(login_id); + sInstance->mUsernameLength = login_id.length(); + } + else if(identifier.has("type") && (std::string)identifier["type"] == "account") + { + std::string login_id = identifier["account_name"].asString(); + sInstance->getChild("username_combo")->setLabel(login_id); + sInstance->mUsernameLength = login_id.length(); + } + else + { + sInstance->getChild("username_combo")->setLabel(std::string()); + sInstance->mUsernameLength = 0; + } + + sInstance->addFavoritesToStartLocation(); + // if the password exists in the credential, set the password field with + // a filler to get some stars + LLSD authenticator = credential.notNull() ? credential->getAuthenticator() : LLSD(); + LL_INFOS("Credentials") << "Setting authenticator field " << authenticator["type"].asString() << LL_ENDL; + if(authenticator.isMap() && + authenticator.has("secret") && + (authenticator["secret"].asString().size() > 0)) + { + + // This is a MD5 hex digest of a password. + // We don't actually use the password input field, + // fill it with MAX_PASSWORD characters so we get a + // nice row of asterisks. + const std::string filler("123456789!123456"); + sInstance->getChild("password_edit")->setValue(filler); + sInstance->mPasswordLength = filler.length(); + sInstance->updateLoginButtons(); + } + else + { + sInstance->getChild("password_edit")->setValue(std::string()); + sInstance->mPasswordLength = 0; + } +} + +// static +void LLPanelLogin::getFields(LLPointer& credential, + bool& remember_user, + bool& remember_psswrd) +{ + if (!sInstance) + { + LL_WARNS() << "Attempted getFields with no login view shown" << LL_ENDL; + return; + } + + LLSD identifier = LLSD::emptyMap(); + LLSD authenticator = LLSD::emptyMap(); + + std::string username = sInstance->getChild("username_combo")->getSimple(); + std::string password = sInstance->getChild("password_edit")->getValue().asString(); + LLStringUtil::trim(username); + + LL_INFOS("Credentials", "Authentication") << "retrieving username:" << username << LL_ENDL; + // determine if the username is a first/last form or not. + size_t separator_index = username.find_first_of(' '); + { + // Be lenient in terms of what separators we allow for two-word names + // and allow legacy users to login with firstname.lastname + separator_index = username.find_first_of(" ._"); + std::string first = username.substr(0, separator_index); + std::string last; + if (separator_index != username.npos) + { + last = username.substr(separator_index+1, username.npos); + LLStringUtil::trim(last); + } + else + { + // ...on Linden grids, single username users as considered to have + // last name "Resident" + // *TODO: Make login.cgi support "account_name" like above + last = "Resident"; + } + + if (last.find_first_of(' ') == last.npos) + { + LL_INFOS("Credentials", "Authentication") << "agent: " << username << LL_ENDL; + // traditional firstname / lastname + identifier["type"] = CRED_IDENTIFIER_TYPE_AGENT; + identifier["first_name"] = first; + identifier["last_name"] = last; + + if (LLPanelLogin::sInstance->mPasswordModified) + { + authenticator = LLSD::emptyMap(); + authenticator["type"] = CRED_AUTHENTICATOR_TYPE_HASH; + authenticator["algorithm"] = "md5"; + LLMD5 pass((const U8 *)password.c_str()); + char md5pass[33]; /* Flawfinder: ignore */ + pass.hex_digest(md5pass); + authenticator["secret"] = md5pass; + } + else + { + std::string key = first + "_" + last; + LLStringUtil::toLower(key); + credential = load_user_credentials(key); + if (credential.notNull()) + { + authenticator = credential->getAuthenticator(); + } + } + } + } + credential = gSecAPIHandler->createCredential(LLGridManager::getInstance()->getGrid(), identifier, authenticator); + + remember_psswrd = sInstance->getChild("remember_password")->getValue(); + remember_user = sInstance->getChild("remember_name")->getValue(); +} + + +// static +bool LLPanelLogin::areCredentialFieldsDirty() +{ + if (!sInstance) + { + LL_WARNS() << "Attempted getServer with no login view shown" << LL_ENDL; + } + else + { + LLComboBox* combo = sInstance->getChild("username_combo"); + if (combo && combo->getCurrentIndex() == -1 && !combo->getValue().asString().empty()) + { + return true; + } + LLLineEditor* ctrl = sInstance->getChild("password_edit"); + if(ctrl && ctrl->isDirty()) + { + return true; + } + } + return false; +} + + +// static +void LLPanelLogin::updateLocationSelectorsVisibility() +{ + if (sInstance) + { + bool show_server = gSavedSettings.getBOOL("ForceShowGrid"); + LLComboBox* server_combo = sInstance->getChild("server_combo"); + if ( server_combo ) + { + server_combo->setVisible(show_server); + } + } +} + +// static - called from LLStartUp::setStartSLURL +void LLPanelLogin::onUpdateStartSLURL(const LLSLURL& new_start_slurl) +{ + if (!sInstance) return; + + LL_DEBUGS("AppInit")<getChild("start_location_combo"); + /* + * Determine whether or not the new_start_slurl modifies the grid. + * + * Note that some forms that could be in the slurl are grid-agnostic., + * such as "home". Other forms, such as + * https://grid.example.com/region/Party%20Town/20/30/5 + * specify a particular grid; in those cases we want to change the grid + * and the grid selector to match the new value. + */ + enum LLSLURL::SLURL_TYPE new_slurl_type = new_start_slurl.getType(); + switch ( new_slurl_type ) + { + case LLSLURL::LOCATION: + { + std::string slurl_grid = LLGridManager::getInstance()->getGrid(new_start_slurl.getGrid()); + if ( ! slurl_grid.empty() ) // is that a valid grid? + { + if ( slurl_grid != LLGridManager::getInstance()->getGrid() ) // new grid? + { + // the slurl changes the grid, so update everything to match + LLGridManager::getInstance()->setGridChoice(slurl_grid); + + // update the grid selector to match the slurl + LLComboBox* server_combo = sInstance->getChild("server_combo"); + std::string server_label(LLGridManager::getInstance()->getGridLabel(slurl_grid)); + server_combo->setSimple(server_label); + + updateServer(); // to change the links and splash screen + } + if ( new_start_slurl.getLocationString().length() ) + { + + location_combo->setLabel(new_start_slurl.getLocationString()); + sInstance->mLocationLength = new_start_slurl.getLocationString().length(); + sInstance->updateLoginButtons(); + } + } + else + { + // the grid specified by the slurl is not known + LLNotificationsUtil::add("InvalidLocationSLURL"); + LL_WARNS("AppInit")<<"invalid LoginLocation:"<setTextEntry(LLStringUtil::null); + } + } + break; + + case LLSLURL::HOME_LOCATION: + //location_combo->setCurrentByIndex(0); // home location + break; + + default: + LL_WARNS("AppInit")<<"invalid login slurl, using home"<setCurrentByIndex(0); // home location + break; + } +} + +void LLPanelLogin::setLocation(const LLSLURL& slurl) +{ + LL_DEBUGS("AppInit")<<"setting Location "<onClickConnect(unused_parameter); + } +} + + +// static +void LLPanelLogin::closePanel() +{ + if (sInstance) + { + if (LLPanelLogin::sInstance->getParent()) + { + LLPanelLogin::sInstance->getParent()->removeChild(LLPanelLogin::sInstance); + } + + delete sInstance; + sInstance = NULL; + } +} + +// static +void LLPanelLogin::setAlwaysRefresh(bool refresh) +{ + if (sInstance && LLStartUp::getStartupState() < STATE_LOGIN_CLEANUP) + { + LLMediaCtrl* web_browser = sInstance->getChild("login_html"); + + if (web_browser) + { + web_browser->setAlwaysRefresh(refresh); + } + } +} + + + +void LLPanelLogin::loadLoginPage() +{ + if (!sInstance) return; + + LLURI login_page = LLURI(LLGridManager::getInstance()->getLoginPage()); + LLSD params(login_page.queryMap()); + + LL_DEBUGS("AppInit") << "login_page: " << login_page << LL_ENDL; + + // allow users (testers really) to specify a different login content URL + std::string force_login_url = gSavedSettings.getString("ForceLoginURL"); + if ( force_login_url.length() > 0 ) + { + login_page = LLURI(force_login_url); + } + + // Language + params["lang"] = LLUI::getLanguage(); + + // First Login? + if (gSavedSettings.getBOOL("FirstLoginThisInstall")) + { + params["firstlogin"] = "true"; // not bool: server expects string true + } + + // Channel and Version + params["version"] = stringize(LLVersionInfo::instance().getShortVersion(), " (", + LLVersionInfo::instance().getBuild(), ')'); + params["channel"] = LLVersionInfo::instance().getChannel(); + + // Grid + params["grid"] = LLGridManager::getInstance()->getGridId(); + + // add OS info + params["os"] = LLOSInfo::instance().getOSStringSimple(); + + // sourceid + params["sourceid"] = gSavedSettings.getString("sourceid"); + + // login page (web) content version + params["login_content_version"] = gSavedSettings.getString("LoginContentVersion"); + + // Make an LLURI with this augmented info + std::string url = login_page.scheme().empty()? login_page.authority() : login_page.scheme() + "://" + login_page.authority(); + LLURI login_uri(LLURI::buildHTTP(url, + login_page.path(), + params)); + + gViewerWindow->setMenuBackgroundColor(false, !LLGridManager::getInstance()->isInProductionGrid()); + + LLMediaCtrl* web_browser = sInstance->getChild("login_html"); + if (web_browser->getCurrentNavUrl() != login_uri.asString()) + { + LL_DEBUGS("AppInit") << "loading: " << login_uri << LL_ENDL; + web_browser->navigateTo( login_uri.asString(), "text/html" ); + } +} + +void LLPanelLogin::handleMediaEvent(LLPluginClassMedia* /*self*/, EMediaEvent event) +{ +} + +//--------------------------------------------------------------------------- +// Protected methods +//--------------------------------------------------------------------------- +// static +void LLPanelLogin::onClickConnect(bool commit_fields) +{ + if (sInstance && sInstance->mCallback) + { + if (commit_fields) + { + // JC - Make sure the fields all get committed. + sInstance->setFocus(false); + } + + LLComboBox* combo = sInstance->getChild("server_combo"); + LLSD combo_val = combo->getSelectedValue(); + + // the grid definitions may come from a user-supplied grids.xml, so they may not be good + LL_DEBUGS("AppInit")<<"grid "<setGridChoice(combo_val.asString()); + } + catch (LLInvalidGridName ex) + { + LLSD args; + args["GRID"] = ex.name(); + LLNotificationsUtil::add("InvalidGrid", args); + return; + } + + // The start location SLURL has already been sent to LLStartUp::setStartSLURL + + std::string username = sInstance->getChild("username_combo")->getValue().asString(); + std::string password = sInstance->getChild("password_edit")->getValue().asString(); + + if(username.empty()) + { + // user must type in something into the username field + LLNotificationsUtil::add("MustHaveAccountToLogIn"); + } + else if(password.empty()) + { + LLNotificationsUtil::add("MustEnterPasswordToLogIn"); + } + else + { + sCredentialSet = false; + LLPointer cred; + bool remember_1, remember_2; + getFields(cred, remember_1, remember_2); + std::string identifier_type; + cred->identifierType(identifier_type); + LLSD allowed_credential_types; + LLGridManager::getInstance()->getLoginIdentifierTypes(allowed_credential_types); + + // check the typed in credential type against the credential types expected by the server. + for(LLSD::array_iterator i = allowed_credential_types.beginArray(); + i != allowed_credential_types.endArray(); + i++) + { + + if(i->asString() == identifier_type) + { + // yay correct credential type + sInstance->mCallback(0, sInstance->mCallbackData); + return; + } + } + + // Right now, maingrid is the only thing that is picky about + // credential format, as it doesn't yet allow account (single username) + // format creds. - Rox. James, we wanna fix the message when we change + // this. + LLNotificationsUtil::add("InvalidCredentialFormat"); + } + } +} + +// static +void LLPanelLogin::onClickVersion(void*) +{ + LLFloaterReg::showInstance("sl_about"); +} + +//static +void LLPanelLogin::onClickForgotPassword(void*) +{ + if (sInstance ) + { + LLWeb::loadURLExternal(sInstance->getString( "forgot_password_url" )); + } +} + +//static +void LLPanelLogin::onClickSignUp(void*) +{ + if (sInstance) + { + LLWeb::loadURLExternal(sInstance->getString("sign_up_url")); + } +} + +// static +void LLPanelLogin::onUserNameTextEnty(void*) +{ + sInstance->mPasswordModified = true; + sInstance->getChild("password_edit")->setValue(std::string()); + sInstance->mPasswordLength = 0; + sInstance->addFavoritesToStartLocation(); //will call updateLoginButtons() +} + +// static +void LLPanelLogin::onUserListCommit(void*) +{ + if (sInstance) + { + LLComboBox* username_combo(sInstance->getChild("username_combo")); + static S32 ind = -1; + if (ind != username_combo->getCurrentIndex()) + { + std::string user_key = username_combo->getSelectedValue(); + LLPointer cred = gSecAPIHandler->loadFromCredentialMap("login_list", LLGridManager::getInstance()->getGrid(), user_key); + setFields(cred); + sInstance->mPasswordModified = false; + } + else + { + std::string pass = sInstance->getChild("password_edit")->getValue().asString(); + if (pass.empty()) + { + sInstance->giveFocus(); + } + else + { + onClickConnect(); + } + } + } +} + +// static +void LLPanelLogin::onRememberUserCheck(void*) +{ + if (sInstance) + { + LLCheckBoxCtrl* remember_name(sInstance->getChild("remember_name")); + LLCheckBoxCtrl* remember_psswrd(sInstance->getChild("remember_password")); + LLComboBox* user_combo(sInstance->getChild("username_combo")); + + bool remember = remember_name->getValue().asBoolean(); + if (!sInstance->mFirstLoginThisInstall + && user_combo->getCurrentIndex() != -1 + && !remember) + { + remember = true; + remember_name->setValue(true); + LLNotificationsUtil::add("LoginCantRemoveUsername"); + } + if (!remember) + { + remember_psswrd->setValue(false); + } + remember_psswrd->setEnabled(remember); + } +} + +void LLPanelLogin::onRememberPasswordCheck(void*) +{ + if (sInstance) + { + gSavedSettings.setBOOL("UpdateRememberPasswordSetting", true); + + LLPointer cred; + bool remember_user, remember_password; + getFields(cred, remember_user, remember_password); + + std::string grid(LLGridManager::getInstance()->getGridId()); + std::string user_id(cred->userID()); + } +} + +// static +void LLPanelLogin::onPassKey(LLLineEditor* caller, void* user_data) +{ + LLPanelLogin *self = (LLPanelLogin *)user_data; + self->mPasswordModified = true; + if (gKeyboard->getKeyDown(KEY_CAPSLOCK) && !sCapslockDidNotification) + { + // *TODO: use another way to notify user about enabled caps lock, see EXT-6858 + sCapslockDidNotification = true; + } + + LLLineEditor* password_edit(self->getChild("password_edit")); + self->mPasswordLength = password_edit->getText().length(); + self->updateLoginButtons(); +} + + +void LLPanelLogin::updateServer() +{ + if (sInstance) + { + try + { + // if they've selected another grid, we should load the credentials + // for that grid and set them to the UI. But if there were any modifications to + // fields, modifications should carry over. + // Not sure if it should carry over password but it worked like this before login changes + // Example: you started typing in and found that your are under wrong grid, + // you switch yet don't lose anything + if (sInstance->areCredentialFieldsDirty()) + { + // save modified creds + LLComboBox* user_combo = sInstance->getChild("username_combo"); + LLLineEditor* pswd_edit = sInstance->getChild("password_edit"); + std::string username = user_combo->getSimple(); + LLStringUtil::trim(username); + std::string password = pswd_edit->getValue().asString(); + + // populate dropbox and setFields + // Note: following call is related to initializeLoginInfo() + LLPointer credential = gSecAPIHandler->loadCredential(LLGridManager::getInstance()->getGrid()); + sInstance->populateUserList(credential); + + // restore creds + user_combo->setTextEntry(username); + pswd_edit->setValue(password); + sInstance->mUsernameLength = username.length(); + sInstance->mPasswordLength = password.length(); + } + else + { + // populate dropbox and setFields + // Note: following call is related to initializeLoginInfo() + LLPointer credential = gSecAPIHandler->loadCredential(LLGridManager::getInstance()->getGrid()); + sInstance->populateUserList(credential); + } + + // update the login panel links + bool system_grid = LLGridManager::getInstance()->isSystemGrid(); + + // Want to vanish not only create_new_account_btn, but also the + // title text over it, so turn on/off the whole layout_panel element. + sInstance->getChild("links")->setVisible(system_grid); + sInstance->getChildView("forgot_password_text")->setVisible(system_grid); + + // grid changed so show new splash screen (possibly) + loadLoginPage(); + } + catch (LLInvalidGridName ex) + { + LL_WARNS("AppInit")<<"server '"<("connect_btn"); + + login_btn->setEnabled(mUsernameLength != 0 && mPasswordLength != 0); + + if (!mFirstLoginThisInstall) + { + LLComboBox* user_combo = getChild("username_combo"); + LLCheckBoxCtrl* remember_name = getChild("remember_name"); + if (user_combo->getCurrentIndex() != -1) + { + remember_name->setValue(true); + LLCheckBoxCtrl* remember_pass = getChild("remember_password"); + remember_pass->setEnabled(true); + } // Note: might be good idea to do "else remember_name->setValue(mRememberedState)" but it might behave 'weird' to user + } +} + +void LLPanelLogin::populateUserList(LLPointer credential) +{ + LLComboBox* user_combo = getChild("username_combo"); + user_combo->removeall(); + user_combo->clear(); + user_combo->setValue(std::string()); + getChild("password_edit")->setValue(std::string()); + mUsernameLength = 0; + mPasswordLength = 0; + + if (gSecAPIHandler->hasCredentialMap("login_list", LLGridManager::getInstance()->getGrid())) + { + LLSecAPIHandler::credential_map_t credencials; + gSecAPIHandler->loadCredentialMap("login_list", LLGridManager::getInstance()->getGrid(), credencials); + + LLSecAPIHandler::credential_map_t::iterator cr_iter = credencials.begin(); + LLSecAPIHandler::credential_map_t::iterator cr_end = credencials.end(); + while (cr_iter != cr_end) + { + if (cr_iter->second.notNull()) // basic safety in case of future changes + { + // cr_iter->first == user_id , to be able to be find it in case we select it + user_combo->add(LLPanelLogin::getUserName(cr_iter->second), cr_iter->first, ADD_BOTTOM, true); + } + cr_iter++; + } + + if (credential.isNull() || !user_combo->setSelectedByValue(LLSD(credential->userID()), true)) + { + // selection failed, fields will be mepty + updateLoginButtons(); + } + else + { + setFields(credential); + } + } + else + { + if (credential.notNull()) + { + const LLSD &ident = credential->getIdentifier(); + if (ident.isMap() && ident.has("type")) + { + // this llsd might hold invalid credencial (failed login), so + // do not add to the list, just set field. + setFields(credential); + } + else + { + updateLoginButtons(); + } + } + else + { + updateLoginButtons(); + } + } +} + + +void LLPanelLogin::onSelectServer() +{ + // The user twiddled with the grid choice ui. + // apply the selection to the grid setting. + LLComboBox* server_combo = getChild("server_combo"); + LLSD server_combo_val = server_combo->getSelectedValue(); + LL_INFOS("AppInit") << "grid "<setGridChoice(server_combo_val.asString()); + addFavoritesToStartLocation(); + + /* + * Determine whether or not the value in the start_location_combo makes sense + * with the new grid value. + * + * Note that some forms that could be in the location combo are grid-agnostic, + * such as "MyRegion/128/128/0". There could be regions with that name on any + * number of grids, so leave them alone. Other forms, such as + * https://grid.example.com/region/Party%20Town/20/30/5 specify a particular + * grid; in those cases we want to clear the location. + */ + LLComboBox* location_combo = getChild("start_location_combo"); + S32 index = location_combo->getCurrentIndex(); + switch (index) + { + case 0: // last location + LLStartUp::setStartSLURL(LLSLURL(LLSLURL::SIM_LOCATION_LAST)); + break; + case 1: // home location + LLStartUp::setStartSLURL(LLSLURL(LLSLURL::SIM_LOCATION_HOME)); + break; + + default: + { + std::string location = location_combo->getValue().asString(); + LLSLURL slurl(location); // generata a slurl from the location combo contents + if (location.empty() + || (slurl.getType() == LLSLURL::LOCATION + && slurl.getGrid() != LLGridManager::getInstance()->getGrid()) + ) + { + // the grid specified by the location is not this one, so clear the combo + location_combo->setCurrentByIndex(0); // last location on the new grid + onLocationSLURL(); + } + } + break; + } + + updateServer(); +} + +void LLPanelLogin::onLocationSLURL() +{ + LLComboBox* location_combo = getChild("start_location_combo"); + std::string location = location_combo->getValue().asString(); + LL_DEBUGS("AppInit")< &cred) +{ + if (cred.isNull()) + { + return "unknown"; + } + const LLSD &ident = cred->getIdentifier(); + + if (!ident.isMap()) + { + return "unknown"; + } + else if ((std::string)ident["type"] == "agent") + { + std::string second_name = ident["last_name"]; + if (second_name == "resident" || second_name == "Resident") + { + return (std::string)ident["first_name"]; + } + return (std::string)ident["first_name"] + " " + (std::string)ident["last_name"]; + } + else if ((std::string)ident["type"] == "account") + { + return LLCacheName::cleanFullName((std::string)ident["account_name"]); + } + + return "unknown"; +} + diff --git a/indra/newview/llpanellogin.h b/indra/newview/llpanellogin.h index b3dc79b538..00fd17badf 100644 --- a/indra/newview/llpanellogin.h +++ b/indra/newview/llpanellogin.h @@ -1,132 +1,132 @@ -/** - * @file llpanellogin.h - * @brief Login username entry fields. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELLOGIN_H -#define LL_LLPANELLOGIN_H - -#include "llpanel.h" -#include "llpointer.h" // LLPointer<> -#include "llmediactrl.h" // LLMediaCtrlObserver -#include - -class LLLineEditor; -class LLUIImage; -class LLPanelLoginListener; -class LLSLURL; -class LLCredential; - -class LLPanelLogin: - public LLPanel, - public LLViewerMediaObserver -{ - LOG_CLASS(LLPanelLogin); -public: - LLPanelLogin(const LLRect &rect, - void (*callback)(S32 option, void* user_data), - void *callback_data); - ~LLPanelLogin(); - - virtual void setFocus( bool b ); - - static void show(const LLRect &rect, - void (*callback)(S32 option, void* user_data), - void* callback_data); - static void reshapePanel(); - - static void populateFields(LLPointer credential, bool remember_user, bool remember_psswrd); - static void resetFields(); - static void getFields(LLPointer& credential, bool& remember_user, bool& remember_psswrd); - - static bool isCredentialSet() { return sCredentialSet; } - - static bool areCredentialFieldsDirty(); - static void setLocation(const LLSLURL& slurl); - static void autologinToLocation(const LLSLURL& slurl); - - /// Call when preferences that control visibility may have changed - static void updateLocationSelectorsVisibility(); - - static void closePanel(); - - static void loadLoginPage(); - static void giveFocus(); - static void setAlwaysRefresh(bool refresh); - - // inherited from LLViewerMediaObserver - /*virtual*/ void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event); - static void updateServer(); // update the combo box, change the login page to the new server, clear the combo - - /// to be called from LLStartUp::setStartSLURL - static void onUpdateStartSLURL(const LLSLURL& new_start_slurl); - - // called from prefs when initializing panel - static bool getShowFavorites(); - - // extract name from cred in a format apropriate for username field - static std::string getUserName(LLPointer &cred); - -private: - friend class LLPanelLoginListener; - void addFavoritesToStartLocation(); - void onSelectServer(); - void onLocationSLURL(); - - static void setFields(LLPointer credential); - - static void onClickConnect(bool commit_fields = true); - static void onClickVersion(void*); - static void onClickForgotPassword(void*); - static void onClickSignUp(void*); - static void onUserNameTextEnty(void*); - static void onUserListCommit(void*); - static void onRememberUserCheck(void*); - static void onRememberPasswordCheck(void*); - static void onPassKey(LLLineEditor* caller, void* user_data); - -private: - std::unique_ptr mListener; - - void updateLoginButtons(); - void populateUserList(LLPointer credential); - - void (*mCallback)(S32 option, void *userdata); - void* mCallbackData; - - bool mPasswordModified; - bool mShowFavorites; - - static LLPanelLogin* sInstance; - static bool sCapslockDidNotification; - bool mFirstLoginThisInstall; - - static bool sCredentialSet; - - unsigned int mUsernameLength; - unsigned int mPasswordLength; - unsigned int mLocationLength; -}; - -#endif +/** + * @file llpanellogin.h + * @brief Login username entry fields. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELLOGIN_H +#define LL_LLPANELLOGIN_H + +#include "llpanel.h" +#include "llpointer.h" // LLPointer<> +#include "llmediactrl.h" // LLMediaCtrlObserver +#include + +class LLLineEditor; +class LLUIImage; +class LLPanelLoginListener; +class LLSLURL; +class LLCredential; + +class LLPanelLogin: + public LLPanel, + public LLViewerMediaObserver +{ + LOG_CLASS(LLPanelLogin); +public: + LLPanelLogin(const LLRect &rect, + void (*callback)(S32 option, void* user_data), + void *callback_data); + ~LLPanelLogin(); + + virtual void setFocus( bool b ); + + static void show(const LLRect &rect, + void (*callback)(S32 option, void* user_data), + void* callback_data); + static void reshapePanel(); + + static void populateFields(LLPointer credential, bool remember_user, bool remember_psswrd); + static void resetFields(); + static void getFields(LLPointer& credential, bool& remember_user, bool& remember_psswrd); + + static bool isCredentialSet() { return sCredentialSet; } + + static bool areCredentialFieldsDirty(); + static void setLocation(const LLSLURL& slurl); + static void autologinToLocation(const LLSLURL& slurl); + + /// Call when preferences that control visibility may have changed + static void updateLocationSelectorsVisibility(); + + static void closePanel(); + + static void loadLoginPage(); + static void giveFocus(); + static void setAlwaysRefresh(bool refresh); + + // inherited from LLViewerMediaObserver + /*virtual*/ void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event); + static void updateServer(); // update the combo box, change the login page to the new server, clear the combo + + /// to be called from LLStartUp::setStartSLURL + static void onUpdateStartSLURL(const LLSLURL& new_start_slurl); + + // called from prefs when initializing panel + static bool getShowFavorites(); + + // extract name from cred in a format apropriate for username field + static std::string getUserName(LLPointer &cred); + +private: + friend class LLPanelLoginListener; + void addFavoritesToStartLocation(); + void onSelectServer(); + void onLocationSLURL(); + + static void setFields(LLPointer credential); + + static void onClickConnect(bool commit_fields = true); + static void onClickVersion(void*); + static void onClickForgotPassword(void*); + static void onClickSignUp(void*); + static void onUserNameTextEnty(void*); + static void onUserListCommit(void*); + static void onRememberUserCheck(void*); + static void onRememberPasswordCheck(void*); + static void onPassKey(LLLineEditor* caller, void* user_data); + +private: + std::unique_ptr mListener; + + void updateLoginButtons(); + void populateUserList(LLPointer credential); + + void (*mCallback)(S32 option, void *userdata); + void* mCallbackData; + + bool mPasswordModified; + bool mShowFavorites; + + static LLPanelLogin* sInstance; + static bool sCapslockDidNotification; + bool mFirstLoginThisInstall; + + static bool sCredentialSet; + + unsigned int mUsernameLength; + unsigned int mPasswordLength; + unsigned int mLocationLength; +}; + +#endif diff --git a/indra/newview/llpanelmaininventory.cpp b/indra/newview/llpanelmaininventory.cpp index 0d84ac0fa4..a34502cabb 100644 --- a/indra/newview/llpanelmaininventory.cpp +++ b/indra/newview/llpanelmaininventory.cpp @@ -1,2657 +1,2657 @@ -/** - * @file llpanelmaininventory.cpp - * @brief Implementation of llpanelmaininventory. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llpanelmaininventory.h" - -#include "llagent.h" -#include "llagentbenefits.h" -#include "llagentcamera.h" -#include "llavataractions.h" -#include "llcheckboxctrl.h" -#include "llcombobox.h" -#include "lldndbutton.h" -#include "llfilepicker.h" -#include "llinventorybridge.h" -#include "llinventoryfunctions.h" -#include "llinventorygallery.h" -#include "llinventorymodelbackgroundfetch.h" -#include "llfiltereditor.h" -#include "llfloatersidepanelcontainer.h" -#include "llfloaterreg.h" -#include "llmenubutton.h" -#include "lloutfitobserver.h" -#include "llpanelmarketplaceinbox.h" -#include "llpreviewtexture.h" -#include "llresmgr.h" -#include "llscrollcontainer.h" -#include "llsdserialize.h" -#include "llsdparam.h" -#include "llspinctrl.h" -#include "lltoggleablemenu.h" -#include "lltooldraganddrop.h" -#include "lltrans.h" -#include "llviewermenu.h" -#include "llviewertexturelist.h" -#include "llviewerinventory.h" -#include "llsidepanelinventory.h" -#include "llfolderview.h" -#include "llradiogroup.h" -#include "llenvironment.h" -#include "llweb.h" - -const std::string FILTERS_FILENAME("filters.xml"); - -const std::string ALL_ITEMS("All Items"); -const std::string RECENT_ITEMS("Recent Items"); -const std::string WORN_ITEMS("Worn Items"); - -static LLPanelInjector t_inventory("panel_main_inventory"); - -///---------------------------------------------------------------------------- -/// LLFloaterInventoryFinder -///---------------------------------------------------------------------------- - -class LLFloaterInventoryFinder : public LLFloater -{ -public: - LLFloaterInventoryFinder( LLPanelMainInventory* inventory_view); - virtual void draw(); - /*virtual*/ bool postBuild(); - void changeFilter(LLInventoryFilter* filter); - void updateElementsFromFilter(); - bool getCheckShowEmpty(); - bool getCheckSinceLogoff(); - U32 getDateSearchDirection(); - - void onCreatorSelfFilterCommit(); - void onCreatorOtherFilterCommit(); - - static void onTimeAgo(LLUICtrl*, void *); - static void onCloseBtn(void* user_data); - static void selectAllTypes(void* user_data); - static void selectNoTypes(void* user_data); -private: - LLPanelMainInventory* mPanelMainInventory; - LLSpinCtrl* mSpinSinceDays; - LLSpinCtrl* mSpinSinceHours; - LLCheckBoxCtrl* mCreatorSelf; - LLCheckBoxCtrl* mCreatorOthers; - LLInventoryFilter* mFilter; -}; - -///---------------------------------------------------------------------------- -/// LLPanelMainInventory -///---------------------------------------------------------------------------- - -LLPanelMainInventory::LLPanelMainInventory(const LLPanel::Params& p) - : LLPanel(p), - mActivePanel(NULL), - mWornItemsPanel(NULL), - mSavedFolderState(NULL), - mFilterText(""), - mMenuGearDefault(NULL), - mMenuVisibility(NULL), - mMenuAddHandle(), - mNeedUploadCost(true), - mMenuViewDefault(NULL), - mSingleFolderMode(false), - mForceShowInvLayout(false), - mViewMode(MODE_COMBINATION), - mListViewRootUpdatedConnection(), - mGalleryRootUpdatedConnection() -{ - // Menu Callbacks (non contex menus) - mCommitCallbackRegistrar.add("Inventory.DoToSelected", boost::bind(&LLPanelMainInventory::doToSelected, this, _2)); - mCommitCallbackRegistrar.add("Inventory.CloseAllFolders", boost::bind(&LLPanelMainInventory::closeAllFolders, this)); - mCommitCallbackRegistrar.add("Inventory.EmptyTrash", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyTrash", LLFolderType::FT_TRASH)); - mCommitCallbackRegistrar.add("Inventory.EmptyLostAndFound", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyLostAndFound", LLFolderType::FT_LOST_AND_FOUND)); - mCommitCallbackRegistrar.add("Inventory.DoCreate", boost::bind(&LLPanelMainInventory::doCreate, this, _2)); - mCommitCallbackRegistrar.add("Inventory.ShowFilters", boost::bind(&LLPanelMainInventory::toggleFindOptions, this)); - mCommitCallbackRegistrar.add("Inventory.ResetFilters", boost::bind(&LLPanelMainInventory::resetFilters, this)); - mCommitCallbackRegistrar.add("Inventory.SetSortBy", boost::bind(&LLPanelMainInventory::setSortBy, this, _2)); - - mEnableCallbackRegistrar.add("Inventory.EnvironmentEnabled", [](LLUICtrl *, const LLSD &) { return LLPanelMainInventory::hasSettingsInventory(); }); - mEnableCallbackRegistrar.add("Inventory.MaterialsEnabled", [](LLUICtrl *, const LLSD &) { return LLPanelMainInventory::hasMaterialsInventory(); }); - - - mSavedFolderState = new LLSaveFolderState(); - mSavedFolderState->setApply(false); -} - -bool LLPanelMainInventory::postBuild() -{ - gInventory.addObserver(this); - - mFilterTabs = getChild("inventory filter tabs"); - mFilterTabs->setCommitCallback(boost::bind(&LLPanelMainInventory::onFilterSelected, this)); - - mCounterCtrl = getChild("ItemcountText"); - - //panel->getFilter().markDefault(); - - // Set up the default inv. panel/filter settings. - mActivePanel = getChild(ALL_ITEMS); - if (mActivePanel) - { - // "All Items" is the previous only view, so it gets the InventorySortOrder - mActivePanel->setSortOrder(gSavedSettings.getU32(LLInventoryPanel::DEFAULT_SORT_ORDER)); - mActivePanel->getFilter().markDefault(); - mActivePanel->getRootFolder()->applyFunctorRecursively(*mSavedFolderState); - mActivePanel->setSelectCallback(boost::bind(&LLPanelMainInventory::onSelectionChange, this, mActivePanel, _1, _2)); - mResortActivePanel = true; - } - LLInventoryPanel* recent_items_panel = getChild(RECENT_ITEMS); - if (recent_items_panel) - { - // assign default values until we will be sure that we have setting to restore - recent_items_panel->setSinceLogoff(true); - recent_items_panel->setSortOrder(LLInventoryFilter::SO_DATE); - recent_items_panel->setShowFolderState(LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); - LLInventoryFilter& recent_filter = recent_items_panel->getFilter(); - recent_filter.setFilterObjectTypes(recent_filter.getFilterObjectTypes() & ~(0x1 << LLInventoryType::IT_CATEGORY)); - recent_filter.setEmptyLookupMessage("InventoryNoMatchingRecentItems"); - recent_filter.markDefault(); - recent_items_panel->setSelectCallback(boost::bind(&LLPanelMainInventory::onSelectionChange, this, recent_items_panel, _1, _2)); - } - - mWornItemsPanel = getChild(WORN_ITEMS); - if (mWornItemsPanel) - { - U32 filter_types = 0x0; - filter_types |= 0x1 << LLInventoryType::IT_WEARABLE; - filter_types |= 0x1 << LLInventoryType::IT_ATTACHMENT; - filter_types |= 0x1 << LLInventoryType::IT_OBJECT; - mWornItemsPanel->setFilterTypes(filter_types); - mWornItemsPanel->setFilterWorn(); - mWornItemsPanel->setShowFolderState(LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); - mWornItemsPanel->setFilterLinks(LLInventoryFilter::FILTERLINK_EXCLUDE_LINKS); - LLInventoryFilter& worn_filter = mWornItemsPanel->getFilter(); - worn_filter.setFilterCategoryTypes(worn_filter.getFilterCategoryTypes() | (1ULL << LLFolderType::FT_INBOX)); - worn_filter.markDefault(); - mWornItemsPanel->setSelectCallback(boost::bind(&LLPanelMainInventory::onSelectionChange, this, mWornItemsPanel, _1, _2)); - } - mSearchTypeCombo = getChild("search_type"); - if(mSearchTypeCombo) - { - mSearchTypeCombo->setCommitCallback(boost::bind(&LLPanelMainInventory::onSelectSearchType, this)); - } - // Now load the stored settings from disk, if available. - std::string filterSaveName(gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, FILTERS_FILENAME)); - LL_INFOS("Inventory") << "LLPanelMainInventory::init: reading from " << filterSaveName << LL_ENDL; - llifstream file(filterSaveName.c_str()); - LLSD savedFilterState; - if (file.is_open()) - { - LLSDSerialize::fromXML(savedFilterState, file); - file.close(); - - // Load the persistent "Recent Items" settings. - // Note that the "All Items" settings do not persist. - if(recent_items_panel) - { - if(savedFilterState.has(recent_items_panel->getFilter().getName())) - { - LLSD recent_items = savedFilterState.get( - recent_items_panel->getFilter().getName()); - LLInventoryFilter::Params p; - LLParamSDParser parser; - parser.readSD(recent_items, p); - recent_items_panel->getFilter().fromParams(p); - recent_items_panel->setSortOrder(gSavedSettings.getU32(LLInventoryPanel::RECENTITEMS_SORT_ORDER)); - } - } - if(mActivePanel) - { - if(savedFilterState.has(mActivePanel->getFilter().getName())) - { - LLSD items = savedFilterState.get(mActivePanel->getFilter().getName()); - LLInventoryFilter::Params p; - LLParamSDParser parser; - parser.readSD(items, p); - mActivePanel->getFilter().setSearchVisibilityTypes(p); - } - } - - } - - mFilterEditor = getChild("inventory search editor"); - if (mFilterEditor) - { - mFilterEditor->setCommitCallback(boost::bind(&LLPanelMainInventory::onFilterEdit, this, _2)); - } - - mGearMenuButton = getChild("options_gear_btn"); - mVisibilityMenuButton = getChild("options_visibility_btn"); - mViewMenuButton = getChild("view_btn"); - - mBackBtn = getChild("back_btn"); - mForwardBtn = getChild("forward_btn"); - mUpBtn = getChild("up_btn"); - mViewModeBtn = getChild("view_mode_btn"); - mNavigationBtnsPanel = getChild("nav_buttons"); - - mDefaultViewPanel = getChild("default_inventory_panel"); - mCombinationViewPanel = getChild("combination_view_inventory"); - mCombinationGalleryLayoutPanel = getChild("comb_gallery_layout"); - mCombinationListLayoutPanel = getChild("comb_inventory_layout"); - mCombinationLayoutStack = getChild("combination_view_stack"); - - mCombinationInventoryPanel = getChild("comb_single_folder_inv"); - LLInventoryFilter& comb_inv_filter = mCombinationInventoryPanel->getFilter(); - comb_inv_filter.setFilterThumbnails(LLInventoryFilter::FILTER_EXCLUDE_THUMBNAILS); - comb_inv_filter.markDefault(); - mCombinationInventoryPanel->setSelectCallback(boost::bind(&LLPanelMainInventory::onCombinationInventorySelectionChanged, this, _1, _2)); - mListViewRootUpdatedConnection = mCombinationInventoryPanel->setRootChangedCallback(boost::bind(&LLPanelMainInventory::onCombinationRootChanged, this, false)); - - mCombinationGalleryPanel = getChild("comb_gallery_view_inv"); - mCombinationGalleryPanel->setSortOrder(mCombinationInventoryPanel->getSortOrder()); - LLInventoryFilter& comb_gallery_filter = mCombinationGalleryPanel->getFilter(); - comb_gallery_filter.setFilterThumbnails(LLInventoryFilter::FILTER_ONLY_THUMBNAILS); - comb_gallery_filter.markDefault(); - mGalleryRootUpdatedConnection = mCombinationGalleryPanel->setRootChangedCallback(boost::bind(&LLPanelMainInventory::onCombinationRootChanged, this, true)); - mCombinationGalleryPanel->setSelectionChangeCallback(boost::bind(&LLPanelMainInventory::onCombinationGallerySelectionChanged, this, _1)); - - initListCommandsHandlers(); - - const std::string texture_upload_cost_str = std::to_string(LLAgentBenefitsMgr::current().getTextureUploadCost()); - const std::string sound_upload_cost_str = std::to_string(LLAgentBenefitsMgr::current().getSoundUploadCost()); - const std::string animation_upload_cost_str = std::to_string(LLAgentBenefitsMgr::current().getAnimationUploadCost()); - - LLMenuGL* menu = (LLMenuGL*)mMenuAddHandle.get(); - if (menu) - { - menu->getChild("Upload Image")->setLabelArg("[COST]", texture_upload_cost_str); - menu->getChild("Upload Sound")->setLabelArg("[COST]", sound_upload_cost_str); - menu->getChild("Upload Animation")->setLabelArg("[COST]", animation_upload_cost_str); - } - - // Trigger callback for focus received so we can deselect items in inbox/outbox - LLFocusableElement::setFocusReceivedCallback(boost::bind(&LLPanelMainInventory::onFocusReceived, this)); - - return true; -} - -// Destroys the object -LLPanelMainInventory::~LLPanelMainInventory( void ) -{ - // Save the filters state. - // Some params types cannot be saved this way - // for example, LLParamSDParser doesn't know about U64, - // so some FilterOps params should be revised. - LLSD filterRoot; - LLInventoryPanel* all_items_panel = getChild(ALL_ITEMS); - if (all_items_panel) - { - LLSD filterState; - LLInventoryPanel::InventoryState p; - all_items_panel->getFilter().toParams(p.filter); - all_items_panel->getRootViewModel().getSorter().toParams(p.sort); - if (p.validateBlock(false)) - { - LLParamSDParser().writeSD(filterState, p); - filterRoot[all_items_panel->getName()] = filterState; - } - } - - LLInventoryPanel* panel = findChild(RECENT_ITEMS); - if (panel) - { - LLSD filterState; - LLInventoryPanel::InventoryState p; - panel->getFilter().toParams(p.filter); - panel->getRootViewModel().getSorter().toParams(p.sort); - if (p.validateBlock(false)) - { - LLParamSDParser().writeSD(filterState, p); - filterRoot[panel->getName()] = filterState; - } - } - - std::string filterSaveName(gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, FILTERS_FILENAME)); - llofstream filtersFile(filterSaveName.c_str()); - if(!LLSDSerialize::toPrettyXML(filterRoot, filtersFile)) - { - LL_WARNS() << "Could not write to filters save file " << filterSaveName << LL_ENDL; - } - else - { - filtersFile.close(); - } - - gInventory.removeObserver(this); - delete mSavedFolderState; - - auto menu = mMenuAddHandle.get(); - if(menu) - { - menu->die(); - mMenuAddHandle.markDead(); - } - - if (mListViewRootUpdatedConnection.connected()) - { - mListViewRootUpdatedConnection.disconnect(); - } - if (mGalleryRootUpdatedConnection.connected()) - { - mGalleryRootUpdatedConnection.disconnect(); - } -} - -LLInventoryPanel* LLPanelMainInventory::getAllItemsPanel() -{ - return getChild(ALL_ITEMS); -} - -void LLPanelMainInventory::selectAllItemsPanel() -{ - mFilterTabs->selectFirstTab(); -} - -bool LLPanelMainInventory::isRecentItemsPanelSelected() -{ - return (RECENT_ITEMS == getActivePanel()->getName()); -} - -void LLPanelMainInventory::startSearch() -{ - // this forces focus to line editor portion of search editor - if (mFilterEditor) - { - mFilterEditor->focusFirstItem(true); - } -} - -bool LLPanelMainInventory::handleKeyHere(KEY key, MASK mask) -{ - LLFolderView* root_folder = mActivePanel ? mActivePanel->getRootFolder() : NULL; - if (root_folder) - { - // first check for user accepting current search results - if (mFilterEditor - && mFilterEditor->hasFocus() - && (key == KEY_RETURN - || key == KEY_DOWN) - && mask == MASK_NONE) - { - // move focus to inventory proper - mActivePanel->setFocus(true); - root_folder->scrollToShowSelection(); - return true; - } - - if (mActivePanel->hasFocus() && key == KEY_UP) - { - startSearch(); - } - if(mSingleFolderMode && key == KEY_LEFT) - { - onBackFolderClicked(); - } - } - - return LLPanel::handleKeyHere(key, mask); - -} - -//---------------------------------------------------------------------------- -// menu callbacks - -void LLPanelMainInventory::doToSelected(const LLSD& userdata) -{ - getPanel()->doToSelected(userdata); -} - -void LLPanelMainInventory::closeAllFolders() -{ - getPanel()->getRootFolder()->closeAllFolders(); -} - -S32 get_instance_num() -{ - static S32 instance_num = 0; - instance_num = (instance_num + 1) % S32_MAX; - - return instance_num; -} - -LLFloaterSidePanelContainer* LLPanelMainInventory::newWindow() -{ - S32 instance_num = get_instance_num(); - - if (!gAgentCamera.cameraMouselook()) - { - LLFloaterSidePanelContainer* floater = LLFloaterReg::showTypedInstance("inventory", LLSD(instance_num)); - LLSidepanelInventory* sidepanel_inventory = floater->findChild("main_panel"); - sidepanel_inventory->initInventoryViews(); - return floater; - } - return NULL; -} - -//static -void LLPanelMainInventory::newFolderWindow(LLUUID folder_id, LLUUID item_to_select) -{ - LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("inventory"); - for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); iter != inst_list.end();) - { - LLFloaterSidePanelContainer* inventory_container = dynamic_cast(*iter++); - if (inventory_container) - { - LLSidepanelInventory* sidepanel_inventory = dynamic_cast(inventory_container->findChild("main_panel", true)); - if (sidepanel_inventory) - { - LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); - if (main_inventory && main_inventory->isSingleFolderMode() - && (main_inventory->getCurrentSFVRoot() == folder_id)) - { - main_inventory->setFocus(true); - if(item_to_select.notNull()) - { - main_inventory->setGallerySelection(item_to_select); - } - return; - } - } - } - } - - S32 instance_num = get_instance_num(); - - LLFloaterSidePanelContainer* inventory_container = LLFloaterReg::showTypedInstance("inventory", LLSD(instance_num)); - if(inventory_container) - { - LLSidepanelInventory* sidepanel_inventory = dynamic_cast(inventory_container->findChild("main_panel", true)); - if (sidepanel_inventory) - { - LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); - if (main_inventory) - { - main_inventory->initSingleFolderRoot(folder_id); - main_inventory->toggleViewMode(); - if(folder_id.notNull()) - { - if(item_to_select.notNull()) - { - main_inventory->setGallerySelection(item_to_select, true); - } - } - } - } - } -} - -void LLPanelMainInventory::doCreate(const LLSD& userdata) -{ - reset_inventory_filter(); - if(mSingleFolderMode) - { - if(isListViewMode() || isCombinationViewMode()) - { - LLFolderViewItem* current_folder = getActivePanel()->getRootFolder(); - if (current_folder) - { - if(isCombinationViewMode()) - { - mForceShowInvLayout = true; - } - - LLHandle handle = getHandle(); - std::function callback_created = [handle](const LLUUID& new_id) - { - gInventory.notifyObservers(); // not really needed, should have been already done - LLPanelMainInventory* panel = (LLPanelMainInventory*)handle.get(); - if (new_id.notNull() && panel) - { - // might need to refresh visibility, delay rename - panel->mCombInvUUIDNeedsRename = new_id; - - if (panel->isCombinationViewMode()) - { - panel->mForceShowInvLayout = true; - } - - LL_DEBUGS("Inventory") << "Done creating inventory: " << new_id << LL_ENDL; - } - }; - menu_create_inventory_item(NULL, getCurrentSFVRoot(), userdata, LLUUID::null, callback_created); - } - } - else - { - LLHandle handle = getHandle(); - std::function callback_created = [handle](const LLUUID &new_id) - { - gInventory.notifyObservers(); // not really needed, should have been already done - if (new_id.notNull()) - { - LLPanelMainInventory* panel = (LLPanelMainInventory*)handle.get(); - if (panel) - { - panel->setGallerySelection(new_id); - LL_DEBUGS("Inventory") << "Done creating inventory: " << new_id << LL_ENDL; - } - } - }; - menu_create_inventory_item(NULL, getCurrentSFVRoot(), userdata, LLUUID::null, callback_created); - } - } - else - { - menu_create_inventory_item(getPanel(), NULL, userdata); - } -} - -void LLPanelMainInventory::resetFilters() -{ - LLFloaterInventoryFinder *finder = getFinder(); - getCurrentFilter().resetDefault(); - if (finder) - { - finder->updateElementsFromFilter(); - } - - setFilterTextFromFilter(); -} - -void LLPanelMainInventory::resetAllItemsFilters() -{ - LLFloaterInventoryFinder *finder = getFinder(); - getAllItemsPanel()->getFilter().resetDefault(); - if (finder) - { - finder->updateElementsFromFilter(); - } - - setFilterTextFromFilter(); -} - -void LLPanelMainInventory::findLinks(const LLUUID& item_id, const std::string& item_name) -{ - mFilterSubString = item_name; - - LLInventoryFilter &filter = mActivePanel->getFilter(); - filter.setFindAllLinksMode(item_name, item_id); - - mFilterEditor->setText(item_name); - mFilterEditor->setFocus(true); -} - -void LLPanelMainInventory::setSortBy(const LLSD& userdata) -{ - U32 sort_order_mask = getActivePanel()->getSortOrder(); - std::string sort_type = userdata.asString(); - if (sort_type == "name") - { - sort_order_mask &= ~LLInventoryFilter::SO_DATE; - } - else if (sort_type == "date") - { - sort_order_mask |= LLInventoryFilter::SO_DATE; - } - else if (sort_type == "foldersalwaysbyname") - { - if ( sort_order_mask & LLInventoryFilter::SO_FOLDERS_BY_NAME ) - { - sort_order_mask &= ~LLInventoryFilter::SO_FOLDERS_BY_NAME; - } - else - { - sort_order_mask |= LLInventoryFilter::SO_FOLDERS_BY_NAME; - } - } - else if (sort_type == "systemfolderstotop") - { - if ( sort_order_mask & LLInventoryFilter::SO_SYSTEM_FOLDERS_TO_TOP ) - { - sort_order_mask &= ~LLInventoryFilter::SO_SYSTEM_FOLDERS_TO_TOP; - } - else - { - sort_order_mask |= LLInventoryFilter::SO_SYSTEM_FOLDERS_TO_TOP; - } - } - if(mSingleFolderMode && !isListViewMode()) - { - mCombinationGalleryPanel->setSortOrder(sort_order_mask, true); - } - - getActivePanel()->setSortOrder(sort_order_mask); - if (isRecentItemsPanelSelected()) - { - gSavedSettings.setU32("RecentItemsSortOrder", sort_order_mask); - } - else - { - gSavedSettings.setU32("InventorySortOrder", sort_order_mask); - } -} - -void LLPanelMainInventory::onSelectSearchType() -{ - std::string new_type = mSearchTypeCombo->getValue(); - if (new_type == "search_by_name") - { - setSearchType(LLInventoryFilter::SEARCHTYPE_NAME); - } - if (new_type == "search_by_creator") - { - setSearchType(LLInventoryFilter::SEARCHTYPE_CREATOR); - } - if (new_type == "search_by_description") - { - setSearchType(LLInventoryFilter::SEARCHTYPE_DESCRIPTION); - } - if (new_type == "search_by_UUID") - { - setSearchType(LLInventoryFilter::SEARCHTYPE_UUID); - } -} - -void LLPanelMainInventory::setSearchType(LLInventoryFilter::ESearchType type) -{ - if(mSingleFolderMode && isGalleryViewMode()) - { - mCombinationGalleryPanel->setSearchType(type); - } - if(mSingleFolderMode && isCombinationViewMode()) - { - mCombinationInventoryPanel->setSearchType(type); - mCombinationGalleryPanel->setSearchType(type); - } - else - { - getActivePanel()->setSearchType(type); - } -} - -void LLPanelMainInventory::updateSearchTypeCombo() -{ - LLInventoryFilter::ESearchType search_type(LLInventoryFilter::SEARCHTYPE_NAME); - - if(mSingleFolderMode && isGalleryViewMode()) - { - search_type = mCombinationGalleryPanel->getSearchType(); - } - else if(mSingleFolderMode && isCombinationViewMode()) - { - search_type = mCombinationGalleryPanel->getSearchType(); - } - else - { - search_type = getActivePanel()->getSearchType(); - } - - switch(search_type) - { - case LLInventoryFilter::SEARCHTYPE_CREATOR: - mSearchTypeCombo->setValue("search_by_creator"); - break; - case LLInventoryFilter::SEARCHTYPE_DESCRIPTION: - mSearchTypeCombo->setValue("search_by_description"); - break; - case LLInventoryFilter::SEARCHTYPE_UUID: - mSearchTypeCombo->setValue("search_by_UUID"); - break; - case LLInventoryFilter::SEARCHTYPE_NAME: - default: - mSearchTypeCombo->setValue("search_by_name"); - break; - } -} - -// static -bool LLPanelMainInventory::filtersVisible(void* user_data) -{ - LLPanelMainInventory* self = (LLPanelMainInventory*)user_data; - if(!self) return false; - - return self->getFinder() != NULL; -} - -void LLPanelMainInventory::onClearSearch() -{ - bool initially_active = false; - LLFloater *finder = getFinder(); - if (mActivePanel && (getActivePanel() != mWornItemsPanel)) - { - initially_active = mActivePanel->getFilter().isNotDefault(); - setFilterSubString(LLStringUtil::null); - mActivePanel->setFilterTypes(0xffffffffffffffffULL); - mActivePanel->setFilterLinks(LLInventoryFilter::FILTERLINK_INCLUDE_LINKS); - } - - if (finder) - { - LLFloaterInventoryFinder::selectAllTypes(finder); - } - - // re-open folders that were initially open in case filter was active - if (mActivePanel && (mFilterSubString.size() || initially_active) && !mSingleFolderMode) - { - mSavedFolderState->setApply(true); - mActivePanel->getRootFolder()->applyFunctorRecursively(*mSavedFolderState); - LLOpenFoldersWithSelection opener; - mActivePanel->getRootFolder()->applyFunctorRecursively(opener); - mActivePanel->getRootFolder()->scrollToShowSelection(); - } - mFilterSubString = ""; - - LLSidepanelInventory * sidepanel_inventory = getParentSidepanelInventory(); - if (sidepanel_inventory) - { - LLPanelMarketplaceInbox* inbox_panel = sidepanel_inventory->getChild("marketplace_inbox"); - if (inbox_panel) - { - inbox_panel->onClearSearch(); - } - } -} - -void LLPanelMainInventory::onFilterEdit(const std::string& search_string ) -{ - if(mSingleFolderMode && isGalleryViewMode()) - { - mFilterSubString = search_string; - mCombinationGalleryPanel->setFilterSubString(mFilterSubString); - return; - } - if(mSingleFolderMode && isCombinationViewMode()) - { - mCombinationGalleryPanel->setFilterSubString(search_string); - } - - if (search_string == "") - { - onClearSearch(); - } - - if (!mActivePanel) - { - return; - } - - if (!LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) - { - llassert(false); // this should have been done on startup - LLInventoryModelBackgroundFetch::instance().start(); - } - - mFilterSubString = search_string; - if (mActivePanel->getFilterSubString().empty() && mFilterSubString.empty()) - { - // current filter and new filter empty, do nothing - return; - } - - // save current folder open state if no filter currently applied - if (!mActivePanel->getFilter().isNotDefault()) - { - mSavedFolderState->setApply(false); - mActivePanel->getRootFolder()->applyFunctorRecursively(*mSavedFolderState); - } - - // set new filter string - setFilterSubString(mFilterSubString); - - LLSidepanelInventory * sidepanel_inventory = getParentSidepanelInventory(); - if (sidepanel_inventory) - { - LLPanelMarketplaceInbox* inbox_panel = sidepanel_inventory->getChild("marketplace_inbox"); - if (inbox_panel) - { - inbox_panel->onFilterEdit(search_string); - } - } -} - - - //static - bool LLPanelMainInventory::incrementalFind(LLFolderViewItem* first_item, const char *find_text, bool backward) - { - LLPanelMainInventory* active_view = NULL; - - LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("inventory"); - for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); iter != inst_list.end(); ++iter) - { - LLPanelMainInventory* iv = dynamic_cast(*iter); - if (iv) - { - if (gFocusMgr.childHasKeyboardFocus(iv)) - { - active_view = iv; - break; - } - } - } - - if (!active_view) - { - return false; - } - - std::string search_string(find_text); - - if (search_string.empty()) - { - return false; - } - - if (active_view->getPanel() && - active_view->getPanel()->getRootFolder()->search(first_item, search_string, backward)) - { - return true; - } - - return false; - } - -void LLPanelMainInventory::onFilterSelected() -{ - // Find my index - setActivePanel(); - - if (!mActivePanel) - { - return; - } - - if (getActivePanel() == mWornItemsPanel) - { - mActivePanel->openAllFolders(); - } - updateSearchTypeCombo(); - setFilterSubString(mFilterSubString); - LLInventoryFilter& filter = getCurrentFilter(); - LLFloaterInventoryFinder *finder = getFinder(); - if (finder) - { - finder->changeFilter(&filter); - if (mSingleFolderMode) - { - finder->setTitle(getLocalizedRootName()); - } - } - if (filter.isActive() && !LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) - { - llassert(false); // this should have been done on startup - LLInventoryModelBackgroundFetch::instance().start(); - } - setFilterTextFromFilter(); -} - -const std::string LLPanelMainInventory::getFilterSubString() -{ - return mActivePanel->getFilterSubString(); -} - -void LLPanelMainInventory::setFilterSubString(const std::string& string) -{ - mActivePanel->setFilterSubString(string); -} - -bool LLPanelMainInventory::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - // Check to see if we are auto scrolling from the last frame - LLInventoryPanel* panel = (LLInventoryPanel*)this->getActivePanel(); - bool needsToScroll = panel->getScrollableContainer()->canAutoScroll(x, y); - if(mFilterTabs) - { - if(needsToScroll) - { - mFilterTabs->startDragAndDropDelayTimer(); - } - } - - bool handled = LLPanel::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); - - return handled; -} - -// virtual -void LLPanelMainInventory::changed(U32) -{ - updateItemcountText(); -} - -void LLPanelMainInventory::setFocusFilterEditor() -{ - if(mFilterEditor) - { - mFilterEditor->setFocus(true); - } -} - -// virtual -void LLPanelMainInventory::draw() -{ - if (mActivePanel && mFilterEditor) - { - mFilterEditor->setText(mFilterSubString); - } - if (mActivePanel && mResortActivePanel) - { - // EXP-756: Force resorting of the list the first time we draw the list: - // In the case of date sorting, we don't have enough information at initialization time - // to correctly sort the folders. Later manual resort doesn't do anything as the order value is - // set correctly. The workaround is to reset the order to alphabetical (or anything) then to the correct order. - U32 order = mActivePanel->getSortOrder(); - mActivePanel->setSortOrder(LLInventoryFilter::SO_NAME); - mActivePanel->setSortOrder(order); - mResortActivePanel = false; - } - LLPanel::draw(); - updateItemcountText(); - updateCombinationVisibility(); -} - -void LLPanelMainInventory::updateItemcountText() -{ - if(mItemCount != gInventory.getItemCount()) - { - mItemCount = gInventory.getItemCount(); - mItemCountString = ""; - LLLocale locale(LLLocale::USER_LOCALE); - LLResMgr::getInstance()->getIntegerString(mItemCountString, mItemCount); - } - - if(mCategoryCount != gInventory.getCategoryCount()) - { - mCategoryCount = gInventory.getCategoryCount(); - mCategoryCountString = ""; - LLLocale locale(LLLocale::USER_LOCALE); - LLResMgr::getInstance()->getIntegerString(mCategoryCountString, mCategoryCount); - } - - LLStringUtil::format_map_t string_args; - string_args["[ITEM_COUNT]"] = mItemCountString; - string_args["[CATEGORY_COUNT]"] = mCategoryCountString; - string_args["[FILTER]"] = getFilterText(); - - std::string text = ""; - - if (LLInventoryModelBackgroundFetch::instance().folderFetchActive()) - { - text = getString("ItemcountFetching", string_args); - } - else if (LLInventoryModelBackgroundFetch::instance().isEverythingFetched()) - { - text = getString("ItemcountCompleted", string_args); - } - else - { - text = getString("ItemcountUnknown", string_args); - } - - if (mSingleFolderMode) - { - LLInventoryModel::cat_array_t *cats; - LLInventoryModel::item_array_t *items; - - gInventory.getDirectDescendentsOf(getCurrentSFVRoot(), cats, items); - - if (items && cats) - { - string_args["[ITEM_COUNT]"] = llformat("%d", items->size()); - string_args["[CATEGORY_COUNT]"] = llformat("%d", cats->size()); - text = getString("ItemcountCompleted", string_args); - } - } - - mCounterCtrl->setValue(text); - mCounterCtrl->setToolTip(text); -} - -void LLPanelMainInventory::onFocusReceived() -{ - LLSidepanelInventory *sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); - if (!sidepanel_inventory) - { - LL_WARNS() << "Could not find Inventory Panel in My Inventory floater" << LL_ENDL; - return; - } - - sidepanel_inventory->clearSelections(false, true); -} - -void LLPanelMainInventory::setFilterTextFromFilter() -{ - mFilterText = getCurrentFilter().getFilterText(); -} - -void LLPanelMainInventory::toggleFindOptions() -{ - LLFloater *floater = getFinder(); - if (!floater) - { - LLFloaterInventoryFinder * finder = new LLFloaterInventoryFinder(this); - mFinderHandle = finder->getHandle(); - finder->openFloater(); - - LLFloater* parent_floater = gFloaterView->getParentFloater(this); - if (parent_floater) - parent_floater->addDependentFloater(mFinderHandle); - - if (!LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) - { - llassert(false); // this should have been done on startup - LLInventoryModelBackgroundFetch::instance().start(); - } - - if (mSingleFolderMode) - { - finder->setTitle(getLocalizedRootName()); - } - } - else - { - floater->closeFloater(); - } -} - -void LLPanelMainInventory::setSelectCallback(const LLFolderView::signal_t::slot_type& cb) -{ - getChild(ALL_ITEMS)->setSelectCallback(cb); - getChild(RECENT_ITEMS)->setSelectCallback(cb); -} - -void LLPanelMainInventory::onSelectionChange(LLInventoryPanel *panel, const std::deque& items, bool user_action) -{ - updateListCommands(); - panel->onSelectionChange(items, user_action); -} - -///---------------------------------------------------------------------------- -/// LLFloaterInventoryFinder -///---------------------------------------------------------------------------- - -LLFloaterInventoryFinder* LLPanelMainInventory::getFinder() -{ - return (LLFloaterInventoryFinder*)mFinderHandle.get(); -} - - -LLFloaterInventoryFinder::LLFloaterInventoryFinder(LLPanelMainInventory* inventory_view) : - LLFloater(LLSD()), - mPanelMainInventory(inventory_view), - mFilter(&inventory_view->getPanel()->getFilter()) -{ - buildFromFile("floater_inventory_view_finder.xml"); - updateElementsFromFilter(); -} - -bool LLFloaterInventoryFinder::postBuild() -{ - const LLRect& viewrect = mPanelMainInventory->getRect(); - setRect(LLRect(viewrect.mLeft - getRect().getWidth(), viewrect.mTop, viewrect.mLeft, viewrect.mTop - getRect().getHeight())); - - childSetAction("All", selectAllTypes, this); - childSetAction("None", selectNoTypes, this); - - mSpinSinceHours = getChild("spin_hours_ago"); - childSetCommitCallback("spin_hours_ago", onTimeAgo, this); - - mSpinSinceDays = getChild("spin_days_ago"); - childSetCommitCallback("spin_days_ago", onTimeAgo, this); - - mCreatorSelf = getChild("check_created_by_me"); - mCreatorOthers = getChild("check_created_by_others"); - mCreatorSelf->setCommitCallback(boost::bind(&LLFloaterInventoryFinder::onCreatorSelfFilterCommit, this)); - mCreatorOthers->setCommitCallback(boost::bind(&LLFloaterInventoryFinder::onCreatorOtherFilterCommit, this)); - - childSetAction("Close", onCloseBtn, this); - - updateElementsFromFilter(); - return true; -} -void LLFloaterInventoryFinder::onTimeAgo(LLUICtrl *ctrl, void *user_data) -{ - LLFloaterInventoryFinder *self = (LLFloaterInventoryFinder *)user_data; - if (!self) return; - - if ( self->mSpinSinceDays->get() || self->mSpinSinceHours->get() ) - { - self->getChild("check_since_logoff")->setValue(false); - - U32 days = (U32)self->mSpinSinceDays->get(); - U32 hours = (U32)self->mSpinSinceHours->get(); - if (hours >= 24) - { - // Try to handle both cases of spinner clicking and text input in a sensible fashion as best as possible. - // There is no way to tell if someone has clicked the spinner to get to 24 or input 24 manually, so in - // this case add to days. Any value > 24 means they have input the hours manually, so do not add to the - // current day value. - if (24 == hours) // Got to 24 via spinner clicking or text input of 24 - { - days = days + hours / 24; - } - else // Text input, so do not add to days - { - days = hours / 24; - } - hours = (U32)hours % 24; - self->mSpinSinceHours->setFocus(false); - self->mSpinSinceDays->setFocus(false); - self->mSpinSinceDays->set((F32)days); - self->mSpinSinceHours->set((F32)hours); - self->mSpinSinceHours->setFocus(true); - } - } -} - -void LLFloaterInventoryFinder::changeFilter(LLInventoryFilter* filter) -{ - mFilter = filter; - updateElementsFromFilter(); -} - -void LLFloaterInventoryFinder::updateElementsFromFilter() -{ - if (!mFilter) - return; - - // Get data needed for filter display - U32 filter_types = mFilter->getFilterObjectTypes(); - LLInventoryFilter::EFolderShow show_folders = mFilter->getShowFolderState(); - U32 hours = mFilter->getHoursAgo(); - U32 date_search_direction = mFilter->getDateSearchDirection(); - - LLInventoryFilter::EFilterCreatorType filter_creator = mFilter->getFilterCreatorType(); - bool show_created_by_me = ((filter_creator == LLInventoryFilter::FILTERCREATOR_ALL) || (filter_creator == LLInventoryFilter::FILTERCREATOR_SELF)); - bool show_created_by_others = ((filter_creator == LLInventoryFilter::FILTERCREATOR_ALL) || (filter_creator == LLInventoryFilter::FILTERCREATOR_OTHERS)); - - // update the ui elements - setTitle(mFilter->getName()); - - getChild("check_animation")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_ANIMATION)); - - getChild("check_calling_card")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_CALLINGCARD)); - getChild("check_clothing")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_WEARABLE)); - getChild("check_gesture")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_GESTURE)); - getChild("check_landmark")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_LANDMARK)); - getChild("check_material")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_MATERIAL)); - getChild("check_notecard")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_NOTECARD)); - getChild("check_object")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_OBJECT)); - getChild("check_script")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_LSL)); - getChild("check_sound")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_SOUND)); - getChild("check_texture")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_TEXTURE)); - getChild("check_snapshot")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_SNAPSHOT)); - getChild("check_settings")->setValue((S32)(filter_types & 0x1 << LLInventoryType::IT_SETTINGS)); - getChild("check_show_empty")->setValue(show_folders == LLInventoryFilter::SHOW_ALL_FOLDERS); - - getChild("check_created_by_me")->setValue(show_created_by_me); - getChild("check_created_by_others")->setValue(show_created_by_others); - - getChild("check_since_logoff")->setValue(mFilter->isSinceLogoff()); - mSpinSinceHours->set((F32)(hours % 24)); - mSpinSinceDays->set((F32)(hours / 24)); - getChild("date_search_direction")->setSelectedIndex(date_search_direction); -} - -void LLFloaterInventoryFinder::draw() -{ - U64 filter = 0xffffffffffffffffULL; - bool filtered_by_all_types = true; - - if (!getChild("check_animation")->getValue()) - { - filter &= ~(0x1 << LLInventoryType::IT_ANIMATION); - filtered_by_all_types = false; - } - - - if (!getChild("check_calling_card")->getValue()) - { - filter &= ~(0x1 << LLInventoryType::IT_CALLINGCARD); - filtered_by_all_types = false; - } - - if (!getChild("check_clothing")->getValue()) - { - filter &= ~(0x1 << LLInventoryType::IT_WEARABLE); - filtered_by_all_types = false; - } - - if (!getChild("check_gesture")->getValue()) - { - filter &= ~(0x1 << LLInventoryType::IT_GESTURE); - filtered_by_all_types = false; - } - - if (!getChild("check_landmark")->getValue()) - - - { - filter &= ~(0x1 << LLInventoryType::IT_LANDMARK); - filtered_by_all_types = false; - } - - if (!getChild("check_material")->getValue()) - { - filter &= ~(0x1 << LLInventoryType::IT_MATERIAL); - filtered_by_all_types = false; - } - - if (!getChild("check_notecard")->getValue()) - { - filter &= ~(0x1 << LLInventoryType::IT_NOTECARD); - filtered_by_all_types = false; - } - - if (!getChild("check_object")->getValue()) - { - filter &= ~(0x1 << LLInventoryType::IT_OBJECT); - filter &= ~(0x1 << LLInventoryType::IT_ATTACHMENT); - filtered_by_all_types = false; - } - - if (!getChild("check_script")->getValue()) - { - filter &= ~(0x1 << LLInventoryType::IT_LSL); - filtered_by_all_types = false; - } - - if (!getChild("check_sound")->getValue()) - { - filter &= ~(0x1 << LLInventoryType::IT_SOUND); - filtered_by_all_types = false; - } - - if (!getChild("check_texture")->getValue()) - { - filter &= ~(0x1 << LLInventoryType::IT_TEXTURE); - filtered_by_all_types = false; - } - - if (!getChild("check_snapshot")->getValue()) - { - filter &= ~(0x1 << LLInventoryType::IT_SNAPSHOT); - filtered_by_all_types = false; - } - - if (!getChild("check_settings")->getValue()) - { - filter &= ~(0x1 << LLInventoryType::IT_SETTINGS); - filtered_by_all_types = false; - } - - if (!filtered_by_all_types || (mPanelMainInventory->getPanel()->getFilter().getFilterTypes() & LLInventoryFilter::FILTERTYPE_DATE)) - { - // don't include folders in filter, unless I've selected everything or filtering by date - filter &= ~(0x1 << LLInventoryType::IT_CATEGORY); - } - - - bool is_sf_mode = mPanelMainInventory->isSingleFolderMode(); - if(is_sf_mode && mPanelMainInventory->isGalleryViewMode()) - { - mPanelMainInventory->mCombinationGalleryPanel->getFilter().setShowFolderState(getCheckShowEmpty() ? - LLInventoryFilter::SHOW_ALL_FOLDERS : LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); - mPanelMainInventory->mCombinationGalleryPanel->getFilter().setFilterObjectTypes(filter); - } - else - { - if(is_sf_mode && mPanelMainInventory->isCombinationViewMode()) - { - mPanelMainInventory->mCombinationGalleryPanel->getFilter().setShowFolderState(getCheckShowEmpty() ? - LLInventoryFilter::SHOW_ALL_FOLDERS : LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); - mPanelMainInventory->mCombinationGalleryPanel->getFilter().setFilterObjectTypes(filter); - } - // update the panel, panel will update the filter - mPanelMainInventory->getPanel()->setShowFolderState(getCheckShowEmpty() ? - LLInventoryFilter::SHOW_ALL_FOLDERS : LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); - mPanelMainInventory->getPanel()->setFilterTypes(filter); - } - - if (getCheckSinceLogoff()) - { - mSpinSinceDays->set(0); - mSpinSinceHours->set(0); - } - U32 days = (U32)mSpinSinceDays->get(); - U32 hours = (U32)mSpinSinceHours->get(); - if (hours >= 24) - { - days = hours / 24; - hours = (U32)hours % 24; - // A UI element that has focus will not display a new value set to it - mSpinSinceHours->setFocus(false); - mSpinSinceDays->setFocus(false); - mSpinSinceDays->set((F32)days); - mSpinSinceHours->set((F32)hours); - mSpinSinceHours->setFocus(true); - } - hours += days * 24; - - - mPanelMainInventory->setFilterTextFromFilter(); - if(is_sf_mode && mPanelMainInventory->isGalleryViewMode()) - { - mPanelMainInventory->mCombinationGalleryPanel->getFilter().setHoursAgo(hours); - mPanelMainInventory->mCombinationGalleryPanel->getFilter().setDateRangeLastLogoff(getCheckSinceLogoff()); - mPanelMainInventory->mCombinationGalleryPanel->getFilter().setDateSearchDirection(getDateSearchDirection()); - } - else - { - if(is_sf_mode && mPanelMainInventory->isCombinationViewMode()) - { - mPanelMainInventory->mCombinationGalleryPanel->getFilter().setHoursAgo(hours); - mPanelMainInventory->mCombinationGalleryPanel->getFilter().setDateRangeLastLogoff(getCheckSinceLogoff()); - mPanelMainInventory->mCombinationGalleryPanel->getFilter().setDateSearchDirection(getDateSearchDirection()); - } - mPanelMainInventory->getPanel()->setHoursAgo(hours); - mPanelMainInventory->getPanel()->setSinceLogoff(getCheckSinceLogoff()); - mPanelMainInventory->getPanel()->setDateSearchDirection(getDateSearchDirection()); - } - - LLPanel::draw(); -} - -void LLFloaterInventoryFinder::onCreatorSelfFilterCommit() -{ - bool show_creator_self = mCreatorSelf->getValue(); - bool show_creator_other = mCreatorOthers->getValue(); - - if(show_creator_self && show_creator_other) - { - mPanelMainInventory->getCurrentFilter().setFilterCreator(LLInventoryFilter::FILTERCREATOR_ALL); - } - else if(show_creator_self) - { - mPanelMainInventory->getCurrentFilter().setFilterCreator(LLInventoryFilter::FILTERCREATOR_SELF); - } - else if(!show_creator_self || !show_creator_other) - { - mPanelMainInventory->getCurrentFilter().setFilterCreator(LLInventoryFilter::FILTERCREATOR_OTHERS); - mCreatorOthers->set(true); - } -} - -void LLFloaterInventoryFinder::onCreatorOtherFilterCommit() -{ - bool show_creator_self = mCreatorSelf->getValue(); - bool show_creator_other = mCreatorOthers->getValue(); - - if(show_creator_self && show_creator_other) - { - mPanelMainInventory->getCurrentFilter().setFilterCreator(LLInventoryFilter::FILTERCREATOR_ALL); - } - else if(show_creator_other) - { - mPanelMainInventory->getCurrentFilter().setFilterCreator(LLInventoryFilter::FILTERCREATOR_OTHERS); - } - else if(!show_creator_other || !show_creator_self) - { - mPanelMainInventory->getCurrentFilter().setFilterCreator(LLInventoryFilter::FILTERCREATOR_SELF); - mCreatorSelf->set(true); - } -} - -bool LLFloaterInventoryFinder::getCheckShowEmpty() -{ - return getChild("check_show_empty")->getValue(); -} - -bool LLFloaterInventoryFinder::getCheckSinceLogoff() -{ - return getChild("check_since_logoff")->getValue(); -} - -U32 LLFloaterInventoryFinder::getDateSearchDirection() -{ - return getChild("date_search_direction")->getSelectedIndex(); -} - -void LLFloaterInventoryFinder::onCloseBtn(void* user_data) -{ - LLFloaterInventoryFinder* finderp = (LLFloaterInventoryFinder*)user_data; - finderp->closeFloater(); -} - -// static -void LLFloaterInventoryFinder::selectAllTypes(void* user_data) -{ - LLFloaterInventoryFinder* self = (LLFloaterInventoryFinder*)user_data; - if(!self) return; - - self->getChild("check_animation")->setValue(true); - self->getChild("check_calling_card")->setValue(true); - self->getChild("check_clothing")->setValue(true); - self->getChild("check_gesture")->setValue(true); - self->getChild("check_landmark")->setValue(true); - self->getChild("check_material")->setValue(true); - self->getChild("check_notecard")->setValue(true); - self->getChild("check_object")->setValue(true); - self->getChild("check_script")->setValue(true); - self->getChild("check_sound")->setValue(true); - self->getChild("check_texture")->setValue(true); - self->getChild("check_snapshot")->setValue(true); - self->getChild("check_settings")->setValue(true); -} - -//static -void LLFloaterInventoryFinder::selectNoTypes(void* user_data) -{ - LLFloaterInventoryFinder* self = (LLFloaterInventoryFinder*)user_data; - if(!self) return; - - self->getChild("check_animation")->setValue(false); - self->getChild("check_calling_card")->setValue(false); - self->getChild("check_clothing")->setValue(false); - self->getChild("check_gesture")->setValue(false); - self->getChild("check_landmark")->setValue(false); - self->getChild("check_material")->setValue(false); - self->getChild("check_notecard")->setValue(false); - self->getChild("check_object")->setValue(false); - self->getChild("check_script")->setValue(false); - self->getChild("check_sound")->setValue(false); - self->getChild("check_texture")->setValue(false); - self->getChild("check_snapshot")->setValue(false); - self->getChild("check_settings")->setValue(false); -} - -////////////////////////////////////////////////////////////////////////////////// -// List Commands // - -void LLPanelMainInventory::initListCommandsHandlers() -{ - childSetAction("add_btn", boost::bind(&LLPanelMainInventory::onAddButtonClick, this)); - childSetAction("view_mode_btn", boost::bind(&LLPanelMainInventory::onViewModeClick, this)); - childSetAction("up_btn", boost::bind(&LLPanelMainInventory::onUpFolderClicked, this)); - childSetAction("back_btn", boost::bind(&LLPanelMainInventory::onBackFolderClicked, this)); - childSetAction("forward_btn", boost::bind(&LLPanelMainInventory::onForwardFolderClicked, this)); - - mCommitCallbackRegistrar.add("Inventory.GearDefault.Custom.Action", boost::bind(&LLPanelMainInventory::onCustomAction, this, _2)); - mEnableCallbackRegistrar.add("Inventory.GearDefault.Check", boost::bind(&LLPanelMainInventory::isActionChecked, this, _2)); - mEnableCallbackRegistrar.add("Inventory.GearDefault.Enable", boost::bind(&LLPanelMainInventory::isActionEnabled, this, _2)); - mEnableCallbackRegistrar.add("Inventory.GearDefault.Visible", boost::bind(&LLPanelMainInventory::isActionVisible, this, _2)); - mMenuGearDefault = LLUICtrlFactory::getInstance()->createFromFile("menu_inventory_gear_default.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - mGearMenuButton->setMenu(mMenuGearDefault, LLMenuButton::MP_BOTTOM_LEFT, true); - mMenuViewDefault = LLUICtrlFactory::getInstance()->createFromFile("menu_inventory_view_default.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - mViewMenuButton->setMenu(mMenuViewDefault, LLMenuButton::MP_BOTTOM_LEFT, true); - LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile("menu_inventory_add.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - mMenuAddHandle = menu->getHandle(); - - mMenuVisibility = LLUICtrlFactory::getInstance()->createFromFile("menu_inventory_search_visibility.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - mVisibilityMenuButton->setMenu(mMenuVisibility, LLMenuButton::MP_BOTTOM_LEFT, true); - - // Update the trash button when selected item(s) get worn or taken off. - LLOutfitObserver::instance().addCOFChangedCallback(boost::bind(&LLPanelMainInventory::updateListCommands, this)); -} - -void LLPanelMainInventory::updateListCommands() -{ -} - -void LLPanelMainInventory::onAddButtonClick() -{ -// Gray out the "New Folder" option when the Recent tab is active as new folders will not be displayed -// unless "Always show folders" is checked in the filter options. - - LLMenuGL* menu = (LLMenuGL*)mMenuAddHandle.get(); - if (menu) - { - disableAddIfNeeded(); - - setUploadCostIfNeeded(); - - showActionMenu(menu,"add_btn"); - } -} - -void LLPanelMainInventory::setActivePanel() -{ - // Todo: should cover gallery mode in some way - if(mSingleFolderMode && isListViewMode()) - { - mActivePanel = getChild("comb_single_folder_inv"); - } - else if(mSingleFolderMode && isCombinationViewMode()) - { - mActivePanel = getChild("comb_single_folder_inv"); - } - else - { - mActivePanel = (LLInventoryPanel*)getChild("inventory filter tabs")->getCurrentPanel(); - } - mViewModeBtn->setEnabled(mSingleFolderMode || (getAllItemsPanel() == getActivePanel())); -} - -void LLPanelMainInventory::initSingleFolderRoot(const LLUUID& start_folder_id) -{ - mCombinationInventoryPanel->initFolderRoot(start_folder_id); -} - -void LLPanelMainInventory::initInventoryViews() -{ - LLInventoryPanel* all_item = getChild(ALL_ITEMS); - all_item->initializeViewBuilding(); - LLInventoryPanel* recent_item = getChild(RECENT_ITEMS); - recent_item->initializeViewBuilding(); - LLInventoryPanel* worn_item = getChild(WORN_ITEMS); - worn_item->initializeViewBuilding(); -} - -void LLPanelMainInventory::toggleViewMode() -{ - if(mSingleFolderMode && isCombinationViewMode()) - { - mCombinationInventoryPanel->getRootFolder()->setForceArrange(false); - } - - mSingleFolderMode = !mSingleFolderMode; - mReshapeInvLayout = true; - - if (mCombinationGalleryPanel->getRootFolder().isNull()) - { - mCombinationGalleryPanel->setRootFolder(mCombinationInventoryPanel->getSingleFolderRoot()); - mCombinationGalleryPanel->updateRootFolder(); - } - - updatePanelVisibility(); - setActivePanel(); - updateTitle(); - onFilterSelected(); - - LLSidepanelInventory* sidepanel_inventory = getParentSidepanelInventory(); - if (sidepanel_inventory) - { - if(mSingleFolderMode) - { - sidepanel_inventory->hideInbox(); - } - else - { - sidepanel_inventory->toggleInbox(); - } - } -} - -void LLPanelMainInventory::onViewModeClick() -{ - LLUUID selected_folder; - LLUUID new_root_folder; - if(mSingleFolderMode) - { - selected_folder = getCurrentSFVRoot(); - } - else - { - LLFolderView* root = getActivePanel()->getRootFolder(); - std::set selection_set = root->getSelectionList(); - if (selection_set.size() == 1) - { - LLFolderViewItem* current_item = *selection_set.begin(); - if (current_item) - { - const LLUUID& id = static_cast(current_item->getViewModelItem())->getUUID(); - if(gInventory.getCategory(id) != NULL) - { - new_root_folder = id; - } - else - { - const LLViewerInventoryItem* selected_item = gInventory.getItem(id); - if (selected_item && selected_item->getParentUUID().notNull()) - { - new_root_folder = selected_item->getParentUUID(); - selected_folder = id; - } - } - } - } - mCombinationInventoryPanel->initFolderRoot(new_root_folder); - } - - toggleViewMode(); - - if (mSingleFolderMode && new_root_folder.notNull()) - { - setSingleFolderViewRoot(new_root_folder, true); - if(selected_folder.notNull() && isListViewMode()) - { - getActivePanel()->setSelection(selected_folder, TAKE_FOCUS_YES); - } - } - else - { - if(selected_folder.notNull()) - { - selectAllItemsPanel(); - getActivePanel()->setSelection(selected_folder, TAKE_FOCUS_YES); - } - } -} - -void LLPanelMainInventory::onUpFolderClicked() -{ - const LLViewerInventoryCategory* cat = gInventory.getCategory(getCurrentSFVRoot()); - if (cat) - { - if (cat->getParentUUID().notNull()) - { - if(isListViewMode()) - { - mCombinationInventoryPanel->changeFolderRoot(cat->getParentUUID()); - } - if(isGalleryViewMode()) - { - mCombinationGalleryPanel->setRootFolder(cat->getParentUUID()); - } - if(isCombinationViewMode()) - { - mCombinationInventoryPanel->changeFolderRoot(cat->getParentUUID()); - } - } - } -} - -void LLPanelMainInventory::onBackFolderClicked() -{ - if(isListViewMode()) - { - mCombinationInventoryPanel->onBackwardFolder(); - } - if(isGalleryViewMode()) - { - mCombinationGalleryPanel->onBackwardFolder(); - } - if(isCombinationViewMode()) - { - mCombinationInventoryPanel->onBackwardFolder(); - } -} - -void LLPanelMainInventory::onForwardFolderClicked() -{ - if(isListViewMode()) - { - mCombinationInventoryPanel->onForwardFolder(); - } - if(isGalleryViewMode()) - { - mCombinationGalleryPanel->onForwardFolder(); - } - if(isCombinationViewMode()) - { - mCombinationInventoryPanel->onForwardFolder(); - } -} - -void LLPanelMainInventory::setSingleFolderViewRoot(const LLUUID& folder_id, bool clear_nav_history) -{ - if(isListViewMode()) - { - mCombinationInventoryPanel->changeFolderRoot(folder_id); - if(clear_nav_history) - { - mCombinationInventoryPanel->clearNavigationHistory(); - } - } - else if(isGalleryViewMode()) - { - mCombinationGalleryPanel->setRootFolder(folder_id); - if(clear_nav_history) - { - mCombinationGalleryPanel->clearNavigationHistory(); - } - } - else if(isCombinationViewMode()) - { - mCombinationInventoryPanel->changeFolderRoot(folder_id); - } - updateNavButtons(); -} - -LLUUID LLPanelMainInventory::getSingleFolderViewRoot() -{ - return mCombinationInventoryPanel->getSingleFolderRoot(); -} - -void LLPanelMainInventory::showActionMenu(LLMenuGL* menu, std::string spawning_view_name) -{ - if (menu) - { - menu->buildDrawLabels(); - menu->updateParent(LLMenuGL::sMenuContainer); - LLView* spawning_view = getChild (spawning_view_name); - S32 menu_x, menu_y; - //show menu in co-ordinates of panel - spawning_view->localPointToOtherView(0, 0, &menu_x, &menu_y, this); - LLMenuGL::showPopup(this, menu, menu_x, menu_y); - } -} - -void LLPanelMainInventory::onClipboardAction(const LLSD& userdata) -{ - std::string command_name = userdata.asString(); - getActivePanel()->doToSelected(command_name); -} - -void LLPanelMainInventory::saveTexture(const LLSD& userdata) -{ - LLUUID item_id; - if(mSingleFolderMode && isGalleryViewMode()) - { - item_id = mCombinationGalleryPanel->getFirstSelectedItemID(); - if (item_id.isNull()) return; - } - else - { - LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); - if (!current_item) - { - return; - } - item_id = static_cast(current_item->getViewModelItem())->getUUID(); - } - - LLPreviewTexture* preview_texture = LLFloaterReg::showTypedInstance("preview_texture", LLSD(item_id), TAKE_FOCUS_YES); - if (preview_texture) - { - preview_texture->openToSave(); - } -} - -void LLPanelMainInventory::onCustomAction(const LLSD& userdata) -{ - if (!isActionEnabled(userdata)) - return; - - const std::string command_name = userdata.asString(); - - if (command_name == "new_window") - { - newWindow(); - } - if (command_name == "sort_by_name") - { - const LLSD arg = "name"; - setSortBy(arg); - } - if (command_name == "sort_by_recent") - { - const LLSD arg = "date"; - setSortBy(arg); - } - if (command_name == "sort_folders_by_name") - { - const LLSD arg = "foldersalwaysbyname"; - setSortBy(arg); - } - if (command_name == "sort_system_folders_to_top") - { - const LLSD arg = "systemfolderstotop"; - setSortBy(arg); - } - if (command_name == "show_filters") - { - toggleFindOptions(); - } - if (command_name == "reset_filters") - { - resetFilters(); - } - if (command_name == "close_folders") - { - closeAllFolders(); - } - if (command_name == "empty_trash") - { - const std::string notification = "ConfirmEmptyTrash"; - gInventory.emptyFolderType(notification, LLFolderType::FT_TRASH); - } - if (command_name == "empty_lostnfound") - { - const std::string notification = "ConfirmEmptyLostAndFound"; - gInventory.emptyFolderType(notification, LLFolderType::FT_LOST_AND_FOUND); - } - if (command_name == "save_texture") - { - saveTexture(userdata); - } - // This doesn't currently work, since the viewer can't change an assetID an item. - if (command_name == "regenerate_link") - { - LLInventoryPanel *active_panel = getActivePanel(); - LLFolderViewItem* current_item = active_panel->getRootFolder()->getCurSelectedItem(); - if (!current_item) - { - return; - } - const LLUUID item_id = static_cast(current_item->getViewModelItem())->getUUID(); - LLViewerInventoryItem *item = gInventory.getItem(item_id); - if (item) - { - item->regenerateLink(); - } - active_panel->setSelection(item_id, TAKE_FOCUS_NO); - } - if (command_name == "find_original") - { - if(mSingleFolderMode && isGalleryViewMode()) - { - LLInventoryObject *obj = gInventory.getObject(mCombinationGalleryPanel->getFirstSelectedItemID()); - if (obj && obj->getIsLinkType()) - { - show_item_original(obj->getUUID()); - } - } - else - { - LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); - if (!current_item) - { - return; - } - static_cast(current_item->getViewModelItem())->performAction(getActivePanel()->getModel(), "goto"); - } - } - - if (command_name == "find_links") - { - if(mSingleFolderMode && isGalleryViewMode()) - { - LLFloaterSidePanelContainer* inventory_container = newWindow(); - if (inventory_container) - { - LLSidepanelInventory* sidepanel_inventory = dynamic_cast(inventory_container->findChild("main_panel", true)); - if (sidepanel_inventory) - { - LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); - if (main_inventory) - { - LLInventoryObject *obj = gInventory.getObject(mCombinationGalleryPanel->getFirstSelectedItemID()); - if (obj) - { - main_inventory->findLinks(obj->getUUID(), obj->getName()); - } - } - } - } - } - else - { - LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); - if (!current_item) - { - return; - } - const LLUUID& item_id = static_cast(current_item->getViewModelItem())->getUUID(); - const std::string &item_name = current_item->getViewModelItem()->getName(); - findLinks(item_id, item_name); - } - } - - if (command_name == "replace_links") - { - LLSD params; - if(mSingleFolderMode && isGalleryViewMode()) - { - params = LLSD(mCombinationGalleryPanel->getFirstSelectedItemID()); - } - else - { - LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); - if (current_item) - { - LLInvFVBridge* bridge = (LLInvFVBridge*)current_item->getViewModelItem(); - - if (bridge) - { - LLInventoryObject* obj = bridge->getInventoryObject(); - if (obj && obj->getType() != LLAssetType::AT_CATEGORY && obj->getActualType() != LLAssetType::AT_LINK_FOLDER) - { - params = LLSD(obj->getUUID()); - } - } - } - } - LLFloaterReg::showInstance("linkreplace", params); - } - - if (command_name == "close_inv_windows") - { - LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("inventory"); - for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); iter != inst_list.end();) - { - LLFloaterSidePanelContainer* iv = dynamic_cast(*iter++); - if (iv) - { - iv->closeFloater(); - } - } - LLFloaterReg::hideInstance("inventory_settings"); - } - - if (command_name == "toggle_search_outfits") - { - getCurrentFilter().toggleSearchVisibilityOutfits(); - } - - if (command_name == "toggle_search_trash") - { - getCurrentFilter().toggleSearchVisibilityTrash(); - } - - if (command_name == "toggle_search_library") - { - getCurrentFilter().toggleSearchVisibilityLibrary(); - } - - if (command_name == "include_links") - { - getCurrentFilter().toggleSearchVisibilityLinks(); - } - - if (command_name == "share") - { - if(mSingleFolderMode && isGalleryViewMode()) - { - std::set uuids{ mCombinationGalleryPanel->getFirstSelectedItemID()}; - LLAvatarActions::shareWithAvatars(uuids, gFloaterView->getParentFloater(this)); - } - else - { - LLAvatarActions::shareWithAvatars(this); - } - } - if (command_name == "shop") - { - LLWeb::loadURL(gSavedSettings.getString("MarketplaceURL")); - } - if (command_name == "list_view") - { - setViewMode(MODE_LIST); - } - if (command_name == "gallery_view") - { - setViewMode(MODE_GALLERY); - } - if (command_name == "combination_view") - { - setViewMode(MODE_COMBINATION); - } -} - -void LLPanelMainInventory::onVisibilityChange( bool new_visibility ) -{ - if(!new_visibility) - { - LLMenuGL* menu = (LLMenuGL*)mMenuAddHandle.get(); - if (menu) - { - menu->setVisible(false); - } - getActivePanel()->getRootFolder()->finishRenamingItem(); - } -} - -bool LLPanelMainInventory::isSaveTextureEnabled(const LLSD& userdata) -{ - LLViewerInventoryItem *inv_item = NULL; - if(mSingleFolderMode && isGalleryViewMode()) - { - inv_item = gInventory.getItem(mCombinationGalleryPanel->getFirstSelectedItemID()); - } - else - { - LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); - if (current_item) - { - inv_item = dynamic_cast(static_cast(current_item->getViewModelItem())->getInventoryObject()); - } - } - if(inv_item) - { - bool can_save = inv_item->checkPermissionsSet(PERM_ITEM_UNRESTRICTED); - LLInventoryType::EType curr_type = inv_item->getInventoryType(); - return can_save && (curr_type == LLInventoryType::IT_TEXTURE || curr_type == LLInventoryType::IT_SNAPSHOT); - } - - return false; -} - -bool LLPanelMainInventory::isActionEnabled(const LLSD& userdata) -{ - const std::string command_name = userdata.asString(); - if (command_name == "not_empty") - { - bool status = false; - LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); - if (current_item) - { - const LLUUID& item_id = static_cast(current_item->getViewModelItem())->getUUID(); - LLInventoryModel::cat_array_t* cat_array; - LLInventoryModel::item_array_t* item_array; - gInventory.getDirectDescendentsOf(item_id, cat_array, item_array); - status = (0 == cat_array->size() && 0 == item_array->size()); - } - return status; - } - if (command_name == "delete") - { - return getActivePanel()->isSelectionRemovable(); - } - if (command_name == "save_texture") - { - return isSaveTextureEnabled(userdata); - } - if (command_name == "find_original") - { - LLUUID item_id; - if(mSingleFolderMode && isGalleryViewMode()) - { - item_id = mCombinationGalleryPanel->getFirstSelectedItemID(); - } - else{ - LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); - if (!current_item) return false; - item_id = static_cast(current_item->getViewModelItem())->getUUID(); - } - const LLViewerInventoryItem *item = gInventory.getItem(item_id); - if (item && item->getIsLinkType() && !item->getIsBrokenLink()) - { - return true; - } - return false; - } - - if (command_name == "find_links") - { - LLUUID item_id; - if(mSingleFolderMode && isGalleryViewMode()) - { - item_id = mCombinationGalleryPanel->getFirstSelectedItemID(); - } - else{ - LLFolderView* root = getActivePanel()->getRootFolder(); - std::set selection_set = root->getSelectionList(); - if (selection_set.size() != 1) return false; - LLFolderViewItem* current_item = root->getCurSelectedItem(); - if (!current_item) return false; - item_id = static_cast(current_item->getViewModelItem())->getUUID(); - } - const LLInventoryObject *obj = gInventory.getObject(item_id); - if (obj && !obj->getIsLinkType() && LLAssetType::lookupCanLink(obj->getType())) - { - return true; - } - return false; - } - // This doesn't currently work, since the viewer can't change an assetID an item. - if (command_name == "regenerate_link") - { - LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); - if (!current_item) return false; - const LLUUID& item_id = static_cast(current_item->getViewModelItem())->getUUID(); - const LLViewerInventoryItem *item = gInventory.getItem(item_id); - if (item && item->getIsBrokenLink()) - { - return true; - } - return false; - } - - if (command_name == "share") - { - if(mSingleFolderMode && isGalleryViewMode()) - { - return can_share_item(mCombinationGalleryPanel->getFirstSelectedItemID()); - } - else{ - LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); - if (!current_item) return false; - LLSidepanelInventory* parent = LLFloaterSidePanelContainer::getPanel("inventory"); - return parent ? parent->canShare() : false; - } - } - if (command_name == "empty_trash") - { - const LLUUID &trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - LLInventoryModel::EHasChildren children = gInventory.categoryHasChildren(trash_id); - return children != LLInventoryModel::CHILDREN_NO && gInventory.isCategoryComplete(trash_id); - } - if (command_name == "empty_lostnfound") - { - const LLUUID &trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); - LLInventoryModel::EHasChildren children = gInventory.categoryHasChildren(trash_id); - return children != LLInventoryModel::CHILDREN_NO && gInventory.isCategoryComplete(trash_id); - } - - return true; -} - -bool LLPanelMainInventory::isActionVisible(const LLSD& userdata) -{ - const std::string param_str = userdata.asString(); - if (param_str == "single_folder_view") - { - return mSingleFolderMode; - } - if (param_str == "multi_folder_view") - { - return !mSingleFolderMode; - } - - return true; -} - -bool LLPanelMainInventory::isActionChecked(const LLSD& userdata) -{ - U32 sort_order_mask = (mSingleFolderMode && isGalleryViewMode()) ? mCombinationGalleryPanel->getSortOrder() : getActivePanel()->getSortOrder(); - const std::string command_name = userdata.asString(); - if (command_name == "sort_by_name") - { - return ~sort_order_mask & LLInventoryFilter::SO_DATE; - } - - if (command_name == "sort_by_recent") - { - return sort_order_mask & LLInventoryFilter::SO_DATE; - } - - if (command_name == "sort_folders_by_name") - { - return sort_order_mask & LLInventoryFilter::SO_FOLDERS_BY_NAME; - } - - if (command_name == "sort_system_folders_to_top") - { - return sort_order_mask & LLInventoryFilter::SO_SYSTEM_FOLDERS_TO_TOP; - } - - if (command_name == "toggle_search_outfits") - { - return (getCurrentFilter().getSearchVisibilityTypes() & LLInventoryFilter::VISIBILITY_OUTFITS) != 0; - } - - if (command_name == "toggle_search_trash") - { - return (getCurrentFilter().getSearchVisibilityTypes() & LLInventoryFilter::VISIBILITY_TRASH) != 0; - } - - if (command_name == "toggle_search_library") - { - return (getCurrentFilter().getSearchVisibilityTypes() & LLInventoryFilter::VISIBILITY_LIBRARY) != 0; - } - - if (command_name == "include_links") - { - return (getCurrentFilter().getSearchVisibilityTypes() & LLInventoryFilter::VISIBILITY_LINKS) != 0; - } - - if (command_name == "list_view") - { - return isListViewMode(); - } - if (command_name == "gallery_view") - { - return isGalleryViewMode(); - } - if (command_name == "combination_view") - { - return isCombinationViewMode(); - } - - return false; -} - -void LLPanelMainInventory::setUploadCostIfNeeded() -{ - LLMenuGL* menu = (LLMenuGL*)mMenuAddHandle.get(); - if(mNeedUploadCost && menu) - { - const std::string texture_upload_cost_str = std::to_string(LLAgentBenefitsMgr::current().getTextureUploadCost()); - const std::string sound_upload_cost_str = std::to_string(LLAgentBenefitsMgr::current().getSoundUploadCost()); - const std::string animation_upload_cost_str = std::to_string(LLAgentBenefitsMgr::current().getAnimationUploadCost()); - - menu->getChild("Upload Image")->setLabelArg("[COST]", texture_upload_cost_str); - menu->getChild("Upload Sound")->setLabelArg("[COST]", sound_upload_cost_str); - menu->getChild("Upload Animation")->setLabelArg("[COST]", animation_upload_cost_str); - } -} - -bool is_add_allowed(LLUUID folder_id) -{ - if(!gInventory.isObjectDescendentOf(folder_id, gInventory.getRootFolderID())) - { - return false; - } - - std::vector not_allowed_types; - not_allowed_types.push_back(LLFolderType::FT_LOST_AND_FOUND); - not_allowed_types.push_back(LLFolderType::FT_FAVORITE); - not_allowed_types.push_back(LLFolderType::FT_MARKETPLACE_LISTINGS); - not_allowed_types.push_back(LLFolderType::FT_TRASH); - not_allowed_types.push_back(LLFolderType::FT_CURRENT_OUTFIT); - not_allowed_types.push_back(LLFolderType::FT_INBOX); - - for (std::vector::const_iterator it = not_allowed_types.begin(); - it != not_allowed_types.end(); ++it) - { - if(gInventory.isObjectDescendentOf(folder_id, gInventory.findCategoryUUIDForType(*it))) - { - return false; - } - } - - LLViewerInventoryCategory* cat = gInventory.getCategory(folder_id); - if (cat && (cat->getPreferredType() == LLFolderType::FT_OUTFIT)) - { - return false; - } - return true; -} - -void LLPanelMainInventory::disableAddIfNeeded() -{ - LLMenuGL* menu = (LLMenuGL*)mMenuAddHandle.get(); - if (menu) - { - bool enable = !mSingleFolderMode || is_add_allowed(getCurrentSFVRoot()); - - menu->getChild("New Folder")->setEnabled(enable && !isRecentItemsPanelSelected()); - menu->getChild("New Script")->setEnabled(enable); - menu->getChild("New Note")->setEnabled(enable); - menu->getChild("New Gesture")->setEnabled(enable); - menu->setItemEnabled("New Clothes", enable); - menu->setItemEnabled("New Body Parts", enable); - menu->setItemEnabled("New Settings", enable); - } -} - -bool LLPanelMainInventory::hasSettingsInventory() -{ - return LLEnvironment::instance().isInventoryEnabled(); -} - -bool LLPanelMainInventory::hasMaterialsInventory() -{ - std::string agent_url = gAgent.getRegionCapability("UpdateMaterialAgentInventory"); - std::string task_url = gAgent.getRegionCapability("UpdateMaterialTaskInventory"); - - return (!agent_url.empty() && !task_url.empty()); -} - -void LLPanelMainInventory::updateTitle() -{ - LLFloater* inventory_floater = gFloaterView->getParentFloater(this); - if(inventory_floater) - { - if(mSingleFolderMode) - { - inventory_floater->setTitle(getLocalizedRootName()); - LLFloaterInventoryFinder *finder = getFinder(); - if (finder) - { - finder->setTitle(getLocalizedRootName()); - } - } - else - { - inventory_floater->setTitle(getString("inventory_title")); - } - } - updateNavButtons(); -} - -void LLPanelMainInventory::onCombinationRootChanged(bool gallery_clicked) -{ - if(gallery_clicked) - { - mCombinationInventoryPanel->changeFolderRoot(mCombinationGalleryPanel->getRootFolder()); - } - else - { - mCombinationGalleryPanel->setRootFolder(mCombinationInventoryPanel->getSingleFolderRoot()); - } - mForceShowInvLayout = false; - updateTitle(); - mReshapeInvLayout = true; -} - -void LLPanelMainInventory::onCombinationGallerySelectionChanged(const LLUUID& category_id) -{ -} - -void LLPanelMainInventory::onCombinationInventorySelectionChanged(const std::deque& items, bool user_action) -{ - onSelectionChange(mCombinationInventoryPanel, items, user_action); -} - -void LLPanelMainInventory::updatePanelVisibility() -{ - mDefaultViewPanel->setVisible(!mSingleFolderMode); - mCombinationViewPanel->setVisible(mSingleFolderMode); - mNavigationBtnsPanel->setVisible(mSingleFolderMode); - mViewModeBtn->setImageOverlay(mSingleFolderMode ? getString("default_mode_btn") : getString("single_folder_mode_btn")); - mViewModeBtn->setEnabled(mSingleFolderMode || (getAllItemsPanel() == getActivePanel())); - if (mSingleFolderMode) - { - if (isCombinationViewMode()) - { - LLInventoryFilter& comb_inv_filter = mCombinationInventoryPanel->getFilter(); - comb_inv_filter.setFilterThumbnails(LLInventoryFilter::FILTER_EXCLUDE_THUMBNAILS); - comb_inv_filter.markDefault(); - - LLInventoryFilter& comb_gallery_filter = mCombinationGalleryPanel->getFilter(); - comb_gallery_filter.setFilterThumbnails(LLInventoryFilter::FILTER_ONLY_THUMBNAILS); - comb_gallery_filter.markDefault(); - - // visibility will be controled by updateCombinationVisibility() - mCombinationGalleryLayoutPanel->setVisible(true); - mCombinationGalleryPanel->setVisible(true); - mCombinationListLayoutPanel->setVisible(true); - } - else - { - LLInventoryFilter& comb_inv_filter = mCombinationInventoryPanel->getFilter(); - comb_inv_filter.setFilterThumbnails(LLInventoryFilter::FILTER_INCLUDE_THUMBNAILS); - comb_inv_filter.markDefault(); - - LLInventoryFilter& comb_gallery_filter = mCombinationGalleryPanel->getFilter(); - comb_gallery_filter.setFilterThumbnails(LLInventoryFilter::FILTER_INCLUDE_THUMBNAILS); - comb_gallery_filter.markDefault(); - - mCombinationLayoutStack->setPanelSpacing(0); - mCombinationGalleryLayoutPanel->setVisible(mSingleFolderMode && isGalleryViewMode()); - mCombinationGalleryPanel->setVisible(mSingleFolderMode && isGalleryViewMode()); // to prevent or process updates - mCombinationListLayoutPanel->setVisible(mSingleFolderMode && isListViewMode()); - } - } - else - { - mCombinationGalleryLayoutPanel->setVisible(false); - mCombinationGalleryPanel->setVisible(false); // to prevent updates - mCombinationListLayoutPanel->setVisible(false); - } -} - -void LLPanelMainInventory::updateCombinationVisibility() -{ - if(mSingleFolderMode && isCombinationViewMode()) - { - bool is_gallery_empty = !mCombinationGalleryPanel->hasVisibleItems(); - bool show_inv_pane = mCombinationInventoryPanel->hasVisibleItems() || is_gallery_empty || mForceShowInvLayout; - - const S32 DRAG_HANDLE_PADDING = 12; // for drag handle to not overlap gallery when both inventories are visible - mCombinationLayoutStack->setPanelSpacing(show_inv_pane ? DRAG_HANDLE_PADDING : 0); - - mCombinationGalleryLayoutPanel->setVisible(!is_gallery_empty); - mCombinationListLayoutPanel->setVisible(show_inv_pane); - mCombinationInventoryPanel->getRootFolder()->setForceArrange(!show_inv_pane); - if(mCombinationInventoryPanel->hasVisibleItems()) - { - mForceShowInvLayout = false; - } - if(is_gallery_empty) - { - mCombinationGalleryPanel->handleModifiedFilter(); - } - - getActivePanel()->getRootFolder(); - - if (mReshapeInvLayout - && show_inv_pane - && (mCombinationGalleryPanel->hasVisibleItems() || mCombinationGalleryPanel->areViewsInitialized()) - && mCombinationInventoryPanel->areViewsInitialized()) - { - mReshapeInvLayout = false; - - // force drop previous shape (because panel doesn't decrease shape properly) - LLRect list_latout = mCombinationListLayoutPanel->getRect(); - list_latout.mTop = list_latout.mBottom; // min height is at 100, so it should snap to be bigger - mCombinationListLayoutPanel->setShape(list_latout, false); - - LLRect inv_inner_rect = mCombinationInventoryPanel->getScrollableContainer()->getScrolledViewRect(); - S32 inv_height = inv_inner_rect.getHeight() - + (mCombinationInventoryPanel->getScrollableContainer()->getBorderWidth() * 2) - + mCombinationInventoryPanel->getScrollableContainer()->getSize(); - LLRect inner_galery_rect = mCombinationGalleryPanel->getScrollableContainer()->getScrolledViewRect(); - S32 gallery_height = inner_galery_rect.getHeight() - + (mCombinationGalleryPanel->getScrollableContainer()->getBorderWidth() * 2) - + mCombinationGalleryPanel->getScrollableContainer()->getSize(); - LLRect layout_rect = mCombinationViewPanel->getRect(); - - // by default make it take 1/3 of the panel - S32 list_default_height = layout_rect.getHeight() / 3; - // Don't set height from gallery_default_height - needs to account for a resizer in such case - S32 gallery_default_height = layout_rect.getHeight() - list_default_height; - - if (inv_height > list_default_height - && gallery_height < gallery_default_height) - { - LLRect gallery_latout = mCombinationGalleryLayoutPanel->getRect(); - gallery_latout.mTop = gallery_latout.mBottom + gallery_height; - mCombinationGalleryLayoutPanel->setShape(gallery_latout, true /*tell stack to account for new shape*/); - } - else if (inv_height < list_default_height - && gallery_height > gallery_default_height) - { - LLRect list_latout = mCombinationListLayoutPanel->getRect(); - list_latout.mTop = list_latout.mBottom + inv_height; - mCombinationListLayoutPanel->setShape(list_latout, true /*tell stack to account for new shape*/); - } - else - { - LLRect list_latout = mCombinationListLayoutPanel->getRect(); - list_latout.mTop = list_latout.mBottom + list_default_height; - mCombinationListLayoutPanel->setShape(list_latout, true /*tell stack to account for new shape*/); - } - } - } - - if (mSingleFolderMode - && !isGalleryViewMode() - && mCombInvUUIDNeedsRename.notNull() - && mCombinationInventoryPanel->areViewsInitialized()) - { - mCombinationInventoryPanel->setSelectionByID(mCombInvUUIDNeedsRename, true); - mCombinationInventoryPanel->getRootFolder()->scrollToShowSelection(); - mCombinationInventoryPanel->getRootFolder()->setNeedsAutoRename(true); - mCombInvUUIDNeedsRename.setNull(); - } -} - -void LLPanelMainInventory::updateNavButtons() -{ - if(isListViewMode()) - { - mBackBtn->setEnabled(mCombinationInventoryPanel->isBackwardAvailable()); - mForwardBtn->setEnabled(mCombinationInventoryPanel->isForwardAvailable()); - } - if(isGalleryViewMode()) - { - mBackBtn->setEnabled(mCombinationGalleryPanel->isBackwardAvailable()); - mForwardBtn->setEnabled(mCombinationGalleryPanel->isForwardAvailable()); - } - if(isCombinationViewMode()) - { - mBackBtn->setEnabled(mCombinationInventoryPanel->isBackwardAvailable()); - mForwardBtn->setEnabled(mCombinationInventoryPanel->isForwardAvailable()); - } - - const LLViewerInventoryCategory* cat = gInventory.getCategory(getCurrentSFVRoot()); - bool up_enabled = (cat && cat->getParentUUID().notNull()); - mUpBtn->setEnabled(up_enabled); -} - -LLSidepanelInventory* LLPanelMainInventory::getParentSidepanelInventory() -{ - LLFloaterSidePanelContainer* inventory_container = dynamic_cast(gFloaterView->getParentFloater(this)); - if(inventory_container) - { - return dynamic_cast(inventory_container->findChild("main_panel", true)); - } - return NULL; -} - -void LLPanelMainInventory::setViewMode(EViewModeType mode) -{ - if(mode != mViewMode) - { - std::list forward_history; - std::list backward_history; - U32 sort_order = 0; - switch(mViewMode) - { - case MODE_LIST: - forward_history = mCombinationInventoryPanel->getNavForwardList(); - backward_history = mCombinationInventoryPanel->getNavBackwardList(); - sort_order = mCombinationInventoryPanel->getSortOrder(); - break; - case MODE_GALLERY: - forward_history = mCombinationGalleryPanel->getNavForwardList(); - backward_history = mCombinationGalleryPanel->getNavBackwardList(); - sort_order = mCombinationGalleryPanel->getSortOrder(); - break; - case MODE_COMBINATION: - forward_history = mCombinationInventoryPanel->getNavForwardList(); - backward_history = mCombinationInventoryPanel->getNavBackwardList(); - mCombinationInventoryPanel->getRootFolder()->setForceArrange(false); - sort_order = mCombinationInventoryPanel->getSortOrder(); - break; - } - - LLUUID cur_root = getCurrentSFVRoot(); - mViewMode = mode; - - updatePanelVisibility(); - - if(isListViewMode()) - { - mCombinationInventoryPanel->changeFolderRoot(cur_root); - mCombinationInventoryPanel->setNavForwardList(forward_history); - mCombinationInventoryPanel->setNavBackwardList(backward_history); - mCombinationInventoryPanel->setSortOrder(sort_order); - } - if(isGalleryViewMode()) - { - mCombinationGalleryPanel->setRootFolder(cur_root); - mCombinationGalleryPanel->setNavForwardList(forward_history); - mCombinationGalleryPanel->setNavBackwardList(backward_history); - mCombinationGalleryPanel->setSortOrder(sort_order, true); - } - if(isCombinationViewMode()) - { - mCombinationInventoryPanel->changeFolderRoot(cur_root); - mCombinationGalleryPanel->setRootFolder(cur_root); - mCombinationInventoryPanel->setNavForwardList(forward_history); - mCombinationInventoryPanel->setNavBackwardList(backward_history); - mCombinationGalleryPanel->setNavForwardList(forward_history); - mCombinationGalleryPanel->setNavBackwardList(backward_history); - mCombinationInventoryPanel->setSortOrder(sort_order); - mCombinationGalleryPanel->setSortOrder(sort_order, true); - } - - updateNavButtons(); - - onFilterSelected(); - if((isListViewMode() && (mActivePanel->getFilterSubString() != mFilterSubString)) || - (isGalleryViewMode() && (mCombinationGalleryPanel->getFilterSubString() != mFilterSubString))) - { - onFilterEdit(mFilterSubString); - } - } -} - -std::string LLPanelMainInventory::getLocalizedRootName() -{ - return mSingleFolderMode ? get_localized_folder_name(getCurrentSFVRoot()) : ""; -} - -LLUUID LLPanelMainInventory::getCurrentSFVRoot() -{ - if(isListViewMode()) - { - return mCombinationInventoryPanel->getSingleFolderRoot(); - } - if(isGalleryViewMode()) - { - return mCombinationGalleryPanel->getRootFolder(); - } - if(isCombinationViewMode()) - { - return mCombinationInventoryPanel->getSingleFolderRoot(); - } - return LLUUID::null; -} - -LLInventoryFilter& LLPanelMainInventory::getCurrentFilter() -{ - if(mSingleFolderMode && isGalleryViewMode()) - { - return mCombinationGalleryPanel->getFilter(); - } - else - { - return mActivePanel->getFilter(); - } -} - -void LLPanelMainInventory::setGallerySelection(const LLUUID& item_id, bool new_window) -{ - if(mSingleFolderMode && isGalleryViewMode()) - { - mCombinationGalleryPanel->changeItemSelection(item_id, true); - } - else if(mSingleFolderMode && isCombinationViewMode()) - { - if(mCombinationGalleryPanel->getFilter().checkAgainstFilterThumbnails(item_id)) - { - mCombinationGalleryPanel->changeItemSelection(item_id, false); - scrollToGallerySelection(); - } - else - { - mCombinationInventoryPanel->setSelection(item_id, true); - scrollToInvPanelSelection(); - } - } - else if (mSingleFolderMode && isListViewMode()) - { - mCombinationInventoryPanel->setSelection(item_id, true); - } -} - -void LLPanelMainInventory::scrollToGallerySelection() -{ - mCombinationGalleryPanel->scrollToShowItem(mCombinationGalleryPanel->getFirstSelectedItemID()); -} - -void LLPanelMainInventory::scrollToInvPanelSelection() -{ - mCombinationInventoryPanel->getRootFolder()->scrollToShowSelection(); -} - -// List Commands // -//////////////////////////////////////////////////////////////////////////////// +/** + * @file llpanelmaininventory.cpp + * @brief Implementation of llpanelmaininventory. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llpanelmaininventory.h" + +#include "llagent.h" +#include "llagentbenefits.h" +#include "llagentcamera.h" +#include "llavataractions.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "lldndbutton.h" +#include "llfilepicker.h" +#include "llinventorybridge.h" +#include "llinventoryfunctions.h" +#include "llinventorygallery.h" +#include "llinventorymodelbackgroundfetch.h" +#include "llfiltereditor.h" +#include "llfloatersidepanelcontainer.h" +#include "llfloaterreg.h" +#include "llmenubutton.h" +#include "lloutfitobserver.h" +#include "llpanelmarketplaceinbox.h" +#include "llpreviewtexture.h" +#include "llresmgr.h" +#include "llscrollcontainer.h" +#include "llsdserialize.h" +#include "llsdparam.h" +#include "llspinctrl.h" +#include "lltoggleablemenu.h" +#include "lltooldraganddrop.h" +#include "lltrans.h" +#include "llviewermenu.h" +#include "llviewertexturelist.h" +#include "llviewerinventory.h" +#include "llsidepanelinventory.h" +#include "llfolderview.h" +#include "llradiogroup.h" +#include "llenvironment.h" +#include "llweb.h" + +const std::string FILTERS_FILENAME("filters.xml"); + +const std::string ALL_ITEMS("All Items"); +const std::string RECENT_ITEMS("Recent Items"); +const std::string WORN_ITEMS("Worn Items"); + +static LLPanelInjector t_inventory("panel_main_inventory"); + +///---------------------------------------------------------------------------- +/// LLFloaterInventoryFinder +///---------------------------------------------------------------------------- + +class LLFloaterInventoryFinder : public LLFloater +{ +public: + LLFloaterInventoryFinder( LLPanelMainInventory* inventory_view); + virtual void draw(); + /*virtual*/ bool postBuild(); + void changeFilter(LLInventoryFilter* filter); + void updateElementsFromFilter(); + bool getCheckShowEmpty(); + bool getCheckSinceLogoff(); + U32 getDateSearchDirection(); + + void onCreatorSelfFilterCommit(); + void onCreatorOtherFilterCommit(); + + static void onTimeAgo(LLUICtrl*, void *); + static void onCloseBtn(void* user_data); + static void selectAllTypes(void* user_data); + static void selectNoTypes(void* user_data); +private: + LLPanelMainInventory* mPanelMainInventory; + LLSpinCtrl* mSpinSinceDays; + LLSpinCtrl* mSpinSinceHours; + LLCheckBoxCtrl* mCreatorSelf; + LLCheckBoxCtrl* mCreatorOthers; + LLInventoryFilter* mFilter; +}; + +///---------------------------------------------------------------------------- +/// LLPanelMainInventory +///---------------------------------------------------------------------------- + +LLPanelMainInventory::LLPanelMainInventory(const LLPanel::Params& p) + : LLPanel(p), + mActivePanel(NULL), + mWornItemsPanel(NULL), + mSavedFolderState(NULL), + mFilterText(""), + mMenuGearDefault(NULL), + mMenuVisibility(NULL), + mMenuAddHandle(), + mNeedUploadCost(true), + mMenuViewDefault(NULL), + mSingleFolderMode(false), + mForceShowInvLayout(false), + mViewMode(MODE_COMBINATION), + mListViewRootUpdatedConnection(), + mGalleryRootUpdatedConnection() +{ + // Menu Callbacks (non contex menus) + mCommitCallbackRegistrar.add("Inventory.DoToSelected", boost::bind(&LLPanelMainInventory::doToSelected, this, _2)); + mCommitCallbackRegistrar.add("Inventory.CloseAllFolders", boost::bind(&LLPanelMainInventory::closeAllFolders, this)); + mCommitCallbackRegistrar.add("Inventory.EmptyTrash", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyTrash", LLFolderType::FT_TRASH)); + mCommitCallbackRegistrar.add("Inventory.EmptyLostAndFound", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyLostAndFound", LLFolderType::FT_LOST_AND_FOUND)); + mCommitCallbackRegistrar.add("Inventory.DoCreate", boost::bind(&LLPanelMainInventory::doCreate, this, _2)); + mCommitCallbackRegistrar.add("Inventory.ShowFilters", boost::bind(&LLPanelMainInventory::toggleFindOptions, this)); + mCommitCallbackRegistrar.add("Inventory.ResetFilters", boost::bind(&LLPanelMainInventory::resetFilters, this)); + mCommitCallbackRegistrar.add("Inventory.SetSortBy", boost::bind(&LLPanelMainInventory::setSortBy, this, _2)); + + mEnableCallbackRegistrar.add("Inventory.EnvironmentEnabled", [](LLUICtrl *, const LLSD &) { return LLPanelMainInventory::hasSettingsInventory(); }); + mEnableCallbackRegistrar.add("Inventory.MaterialsEnabled", [](LLUICtrl *, const LLSD &) { return LLPanelMainInventory::hasMaterialsInventory(); }); + + + mSavedFolderState = new LLSaveFolderState(); + mSavedFolderState->setApply(false); +} + +bool LLPanelMainInventory::postBuild() +{ + gInventory.addObserver(this); + + mFilterTabs = getChild("inventory filter tabs"); + mFilterTabs->setCommitCallback(boost::bind(&LLPanelMainInventory::onFilterSelected, this)); + + mCounterCtrl = getChild("ItemcountText"); + + //panel->getFilter().markDefault(); + + // Set up the default inv. panel/filter settings. + mActivePanel = getChild(ALL_ITEMS); + if (mActivePanel) + { + // "All Items" is the previous only view, so it gets the InventorySortOrder + mActivePanel->setSortOrder(gSavedSettings.getU32(LLInventoryPanel::DEFAULT_SORT_ORDER)); + mActivePanel->getFilter().markDefault(); + mActivePanel->getRootFolder()->applyFunctorRecursively(*mSavedFolderState); + mActivePanel->setSelectCallback(boost::bind(&LLPanelMainInventory::onSelectionChange, this, mActivePanel, _1, _2)); + mResortActivePanel = true; + } + LLInventoryPanel* recent_items_panel = getChild(RECENT_ITEMS); + if (recent_items_panel) + { + // assign default values until we will be sure that we have setting to restore + recent_items_panel->setSinceLogoff(true); + recent_items_panel->setSortOrder(LLInventoryFilter::SO_DATE); + recent_items_panel->setShowFolderState(LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); + LLInventoryFilter& recent_filter = recent_items_panel->getFilter(); + recent_filter.setFilterObjectTypes(recent_filter.getFilterObjectTypes() & ~(0x1 << LLInventoryType::IT_CATEGORY)); + recent_filter.setEmptyLookupMessage("InventoryNoMatchingRecentItems"); + recent_filter.markDefault(); + recent_items_panel->setSelectCallback(boost::bind(&LLPanelMainInventory::onSelectionChange, this, recent_items_panel, _1, _2)); + } + + mWornItemsPanel = getChild(WORN_ITEMS); + if (mWornItemsPanel) + { + U32 filter_types = 0x0; + filter_types |= 0x1 << LLInventoryType::IT_WEARABLE; + filter_types |= 0x1 << LLInventoryType::IT_ATTACHMENT; + filter_types |= 0x1 << LLInventoryType::IT_OBJECT; + mWornItemsPanel->setFilterTypes(filter_types); + mWornItemsPanel->setFilterWorn(); + mWornItemsPanel->setShowFolderState(LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); + mWornItemsPanel->setFilterLinks(LLInventoryFilter::FILTERLINK_EXCLUDE_LINKS); + LLInventoryFilter& worn_filter = mWornItemsPanel->getFilter(); + worn_filter.setFilterCategoryTypes(worn_filter.getFilterCategoryTypes() | (1ULL << LLFolderType::FT_INBOX)); + worn_filter.markDefault(); + mWornItemsPanel->setSelectCallback(boost::bind(&LLPanelMainInventory::onSelectionChange, this, mWornItemsPanel, _1, _2)); + } + mSearchTypeCombo = getChild("search_type"); + if(mSearchTypeCombo) + { + mSearchTypeCombo->setCommitCallback(boost::bind(&LLPanelMainInventory::onSelectSearchType, this)); + } + // Now load the stored settings from disk, if available. + std::string filterSaveName(gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, FILTERS_FILENAME)); + LL_INFOS("Inventory") << "LLPanelMainInventory::init: reading from " << filterSaveName << LL_ENDL; + llifstream file(filterSaveName.c_str()); + LLSD savedFilterState; + if (file.is_open()) + { + LLSDSerialize::fromXML(savedFilterState, file); + file.close(); + + // Load the persistent "Recent Items" settings. + // Note that the "All Items" settings do not persist. + if(recent_items_panel) + { + if(savedFilterState.has(recent_items_panel->getFilter().getName())) + { + LLSD recent_items = savedFilterState.get( + recent_items_panel->getFilter().getName()); + LLInventoryFilter::Params p; + LLParamSDParser parser; + parser.readSD(recent_items, p); + recent_items_panel->getFilter().fromParams(p); + recent_items_panel->setSortOrder(gSavedSettings.getU32(LLInventoryPanel::RECENTITEMS_SORT_ORDER)); + } + } + if(mActivePanel) + { + if(savedFilterState.has(mActivePanel->getFilter().getName())) + { + LLSD items = savedFilterState.get(mActivePanel->getFilter().getName()); + LLInventoryFilter::Params p; + LLParamSDParser parser; + parser.readSD(items, p); + mActivePanel->getFilter().setSearchVisibilityTypes(p); + } + } + + } + + mFilterEditor = getChild("inventory search editor"); + if (mFilterEditor) + { + mFilterEditor->setCommitCallback(boost::bind(&LLPanelMainInventory::onFilterEdit, this, _2)); + } + + mGearMenuButton = getChild("options_gear_btn"); + mVisibilityMenuButton = getChild("options_visibility_btn"); + mViewMenuButton = getChild("view_btn"); + + mBackBtn = getChild("back_btn"); + mForwardBtn = getChild("forward_btn"); + mUpBtn = getChild("up_btn"); + mViewModeBtn = getChild("view_mode_btn"); + mNavigationBtnsPanel = getChild("nav_buttons"); + + mDefaultViewPanel = getChild("default_inventory_panel"); + mCombinationViewPanel = getChild("combination_view_inventory"); + mCombinationGalleryLayoutPanel = getChild("comb_gallery_layout"); + mCombinationListLayoutPanel = getChild("comb_inventory_layout"); + mCombinationLayoutStack = getChild("combination_view_stack"); + + mCombinationInventoryPanel = getChild("comb_single_folder_inv"); + LLInventoryFilter& comb_inv_filter = mCombinationInventoryPanel->getFilter(); + comb_inv_filter.setFilterThumbnails(LLInventoryFilter::FILTER_EXCLUDE_THUMBNAILS); + comb_inv_filter.markDefault(); + mCombinationInventoryPanel->setSelectCallback(boost::bind(&LLPanelMainInventory::onCombinationInventorySelectionChanged, this, _1, _2)); + mListViewRootUpdatedConnection = mCombinationInventoryPanel->setRootChangedCallback(boost::bind(&LLPanelMainInventory::onCombinationRootChanged, this, false)); + + mCombinationGalleryPanel = getChild("comb_gallery_view_inv"); + mCombinationGalleryPanel->setSortOrder(mCombinationInventoryPanel->getSortOrder()); + LLInventoryFilter& comb_gallery_filter = mCombinationGalleryPanel->getFilter(); + comb_gallery_filter.setFilterThumbnails(LLInventoryFilter::FILTER_ONLY_THUMBNAILS); + comb_gallery_filter.markDefault(); + mGalleryRootUpdatedConnection = mCombinationGalleryPanel->setRootChangedCallback(boost::bind(&LLPanelMainInventory::onCombinationRootChanged, this, true)); + mCombinationGalleryPanel->setSelectionChangeCallback(boost::bind(&LLPanelMainInventory::onCombinationGallerySelectionChanged, this, _1)); + + initListCommandsHandlers(); + + const std::string texture_upload_cost_str = std::to_string(LLAgentBenefitsMgr::current().getTextureUploadCost()); + const std::string sound_upload_cost_str = std::to_string(LLAgentBenefitsMgr::current().getSoundUploadCost()); + const std::string animation_upload_cost_str = std::to_string(LLAgentBenefitsMgr::current().getAnimationUploadCost()); + + LLMenuGL* menu = (LLMenuGL*)mMenuAddHandle.get(); + if (menu) + { + menu->getChild("Upload Image")->setLabelArg("[COST]", texture_upload_cost_str); + menu->getChild("Upload Sound")->setLabelArg("[COST]", sound_upload_cost_str); + menu->getChild("Upload Animation")->setLabelArg("[COST]", animation_upload_cost_str); + } + + // Trigger callback for focus received so we can deselect items in inbox/outbox + LLFocusableElement::setFocusReceivedCallback(boost::bind(&LLPanelMainInventory::onFocusReceived, this)); + + return true; +} + +// Destroys the object +LLPanelMainInventory::~LLPanelMainInventory( void ) +{ + // Save the filters state. + // Some params types cannot be saved this way + // for example, LLParamSDParser doesn't know about U64, + // so some FilterOps params should be revised. + LLSD filterRoot; + LLInventoryPanel* all_items_panel = getChild(ALL_ITEMS); + if (all_items_panel) + { + LLSD filterState; + LLInventoryPanel::InventoryState p; + all_items_panel->getFilter().toParams(p.filter); + all_items_panel->getRootViewModel().getSorter().toParams(p.sort); + if (p.validateBlock(false)) + { + LLParamSDParser().writeSD(filterState, p); + filterRoot[all_items_panel->getName()] = filterState; + } + } + + LLInventoryPanel* panel = findChild(RECENT_ITEMS); + if (panel) + { + LLSD filterState; + LLInventoryPanel::InventoryState p; + panel->getFilter().toParams(p.filter); + panel->getRootViewModel().getSorter().toParams(p.sort); + if (p.validateBlock(false)) + { + LLParamSDParser().writeSD(filterState, p); + filterRoot[panel->getName()] = filterState; + } + } + + std::string filterSaveName(gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, FILTERS_FILENAME)); + llofstream filtersFile(filterSaveName.c_str()); + if(!LLSDSerialize::toPrettyXML(filterRoot, filtersFile)) + { + LL_WARNS() << "Could not write to filters save file " << filterSaveName << LL_ENDL; + } + else + { + filtersFile.close(); + } + + gInventory.removeObserver(this); + delete mSavedFolderState; + + auto menu = mMenuAddHandle.get(); + if(menu) + { + menu->die(); + mMenuAddHandle.markDead(); + } + + if (mListViewRootUpdatedConnection.connected()) + { + mListViewRootUpdatedConnection.disconnect(); + } + if (mGalleryRootUpdatedConnection.connected()) + { + mGalleryRootUpdatedConnection.disconnect(); + } +} + +LLInventoryPanel* LLPanelMainInventory::getAllItemsPanel() +{ + return getChild(ALL_ITEMS); +} + +void LLPanelMainInventory::selectAllItemsPanel() +{ + mFilterTabs->selectFirstTab(); +} + +bool LLPanelMainInventory::isRecentItemsPanelSelected() +{ + return (RECENT_ITEMS == getActivePanel()->getName()); +} + +void LLPanelMainInventory::startSearch() +{ + // this forces focus to line editor portion of search editor + if (mFilterEditor) + { + mFilterEditor->focusFirstItem(true); + } +} + +bool LLPanelMainInventory::handleKeyHere(KEY key, MASK mask) +{ + LLFolderView* root_folder = mActivePanel ? mActivePanel->getRootFolder() : NULL; + if (root_folder) + { + // first check for user accepting current search results + if (mFilterEditor + && mFilterEditor->hasFocus() + && (key == KEY_RETURN + || key == KEY_DOWN) + && mask == MASK_NONE) + { + // move focus to inventory proper + mActivePanel->setFocus(true); + root_folder->scrollToShowSelection(); + return true; + } + + if (mActivePanel->hasFocus() && key == KEY_UP) + { + startSearch(); + } + if(mSingleFolderMode && key == KEY_LEFT) + { + onBackFolderClicked(); + } + } + + return LLPanel::handleKeyHere(key, mask); + +} + +//---------------------------------------------------------------------------- +// menu callbacks + +void LLPanelMainInventory::doToSelected(const LLSD& userdata) +{ + getPanel()->doToSelected(userdata); +} + +void LLPanelMainInventory::closeAllFolders() +{ + getPanel()->getRootFolder()->closeAllFolders(); +} + +S32 get_instance_num() +{ + static S32 instance_num = 0; + instance_num = (instance_num + 1) % S32_MAX; + + return instance_num; +} + +LLFloaterSidePanelContainer* LLPanelMainInventory::newWindow() +{ + S32 instance_num = get_instance_num(); + + if (!gAgentCamera.cameraMouselook()) + { + LLFloaterSidePanelContainer* floater = LLFloaterReg::showTypedInstance("inventory", LLSD(instance_num)); + LLSidepanelInventory* sidepanel_inventory = floater->findChild("main_panel"); + sidepanel_inventory->initInventoryViews(); + return floater; + } + return NULL; +} + +//static +void LLPanelMainInventory::newFolderWindow(LLUUID folder_id, LLUUID item_to_select) +{ + LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("inventory"); + for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); iter != inst_list.end();) + { + LLFloaterSidePanelContainer* inventory_container = dynamic_cast(*iter++); + if (inventory_container) + { + LLSidepanelInventory* sidepanel_inventory = dynamic_cast(inventory_container->findChild("main_panel", true)); + if (sidepanel_inventory) + { + LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); + if (main_inventory && main_inventory->isSingleFolderMode() + && (main_inventory->getCurrentSFVRoot() == folder_id)) + { + main_inventory->setFocus(true); + if(item_to_select.notNull()) + { + main_inventory->setGallerySelection(item_to_select); + } + return; + } + } + } + } + + S32 instance_num = get_instance_num(); + + LLFloaterSidePanelContainer* inventory_container = LLFloaterReg::showTypedInstance("inventory", LLSD(instance_num)); + if(inventory_container) + { + LLSidepanelInventory* sidepanel_inventory = dynamic_cast(inventory_container->findChild("main_panel", true)); + if (sidepanel_inventory) + { + LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); + if (main_inventory) + { + main_inventory->initSingleFolderRoot(folder_id); + main_inventory->toggleViewMode(); + if(folder_id.notNull()) + { + if(item_to_select.notNull()) + { + main_inventory->setGallerySelection(item_to_select, true); + } + } + } + } + } +} + +void LLPanelMainInventory::doCreate(const LLSD& userdata) +{ + reset_inventory_filter(); + if(mSingleFolderMode) + { + if(isListViewMode() || isCombinationViewMode()) + { + LLFolderViewItem* current_folder = getActivePanel()->getRootFolder(); + if (current_folder) + { + if(isCombinationViewMode()) + { + mForceShowInvLayout = true; + } + + LLHandle handle = getHandle(); + std::function callback_created = [handle](const LLUUID& new_id) + { + gInventory.notifyObservers(); // not really needed, should have been already done + LLPanelMainInventory* panel = (LLPanelMainInventory*)handle.get(); + if (new_id.notNull() && panel) + { + // might need to refresh visibility, delay rename + panel->mCombInvUUIDNeedsRename = new_id; + + if (panel->isCombinationViewMode()) + { + panel->mForceShowInvLayout = true; + } + + LL_DEBUGS("Inventory") << "Done creating inventory: " << new_id << LL_ENDL; + } + }; + menu_create_inventory_item(NULL, getCurrentSFVRoot(), userdata, LLUUID::null, callback_created); + } + } + else + { + LLHandle handle = getHandle(); + std::function callback_created = [handle](const LLUUID &new_id) + { + gInventory.notifyObservers(); // not really needed, should have been already done + if (new_id.notNull()) + { + LLPanelMainInventory* panel = (LLPanelMainInventory*)handle.get(); + if (panel) + { + panel->setGallerySelection(new_id); + LL_DEBUGS("Inventory") << "Done creating inventory: " << new_id << LL_ENDL; + } + } + }; + menu_create_inventory_item(NULL, getCurrentSFVRoot(), userdata, LLUUID::null, callback_created); + } + } + else + { + menu_create_inventory_item(getPanel(), NULL, userdata); + } +} + +void LLPanelMainInventory::resetFilters() +{ + LLFloaterInventoryFinder *finder = getFinder(); + getCurrentFilter().resetDefault(); + if (finder) + { + finder->updateElementsFromFilter(); + } + + setFilterTextFromFilter(); +} + +void LLPanelMainInventory::resetAllItemsFilters() +{ + LLFloaterInventoryFinder *finder = getFinder(); + getAllItemsPanel()->getFilter().resetDefault(); + if (finder) + { + finder->updateElementsFromFilter(); + } + + setFilterTextFromFilter(); +} + +void LLPanelMainInventory::findLinks(const LLUUID& item_id, const std::string& item_name) +{ + mFilterSubString = item_name; + + LLInventoryFilter &filter = mActivePanel->getFilter(); + filter.setFindAllLinksMode(item_name, item_id); + + mFilterEditor->setText(item_name); + mFilterEditor->setFocus(true); +} + +void LLPanelMainInventory::setSortBy(const LLSD& userdata) +{ + U32 sort_order_mask = getActivePanel()->getSortOrder(); + std::string sort_type = userdata.asString(); + if (sort_type == "name") + { + sort_order_mask &= ~LLInventoryFilter::SO_DATE; + } + else if (sort_type == "date") + { + sort_order_mask |= LLInventoryFilter::SO_DATE; + } + else if (sort_type == "foldersalwaysbyname") + { + if ( sort_order_mask & LLInventoryFilter::SO_FOLDERS_BY_NAME ) + { + sort_order_mask &= ~LLInventoryFilter::SO_FOLDERS_BY_NAME; + } + else + { + sort_order_mask |= LLInventoryFilter::SO_FOLDERS_BY_NAME; + } + } + else if (sort_type == "systemfolderstotop") + { + if ( sort_order_mask & LLInventoryFilter::SO_SYSTEM_FOLDERS_TO_TOP ) + { + sort_order_mask &= ~LLInventoryFilter::SO_SYSTEM_FOLDERS_TO_TOP; + } + else + { + sort_order_mask |= LLInventoryFilter::SO_SYSTEM_FOLDERS_TO_TOP; + } + } + if(mSingleFolderMode && !isListViewMode()) + { + mCombinationGalleryPanel->setSortOrder(sort_order_mask, true); + } + + getActivePanel()->setSortOrder(sort_order_mask); + if (isRecentItemsPanelSelected()) + { + gSavedSettings.setU32("RecentItemsSortOrder", sort_order_mask); + } + else + { + gSavedSettings.setU32("InventorySortOrder", sort_order_mask); + } +} + +void LLPanelMainInventory::onSelectSearchType() +{ + std::string new_type = mSearchTypeCombo->getValue(); + if (new_type == "search_by_name") + { + setSearchType(LLInventoryFilter::SEARCHTYPE_NAME); + } + if (new_type == "search_by_creator") + { + setSearchType(LLInventoryFilter::SEARCHTYPE_CREATOR); + } + if (new_type == "search_by_description") + { + setSearchType(LLInventoryFilter::SEARCHTYPE_DESCRIPTION); + } + if (new_type == "search_by_UUID") + { + setSearchType(LLInventoryFilter::SEARCHTYPE_UUID); + } +} + +void LLPanelMainInventory::setSearchType(LLInventoryFilter::ESearchType type) +{ + if(mSingleFolderMode && isGalleryViewMode()) + { + mCombinationGalleryPanel->setSearchType(type); + } + if(mSingleFolderMode && isCombinationViewMode()) + { + mCombinationInventoryPanel->setSearchType(type); + mCombinationGalleryPanel->setSearchType(type); + } + else + { + getActivePanel()->setSearchType(type); + } +} + +void LLPanelMainInventory::updateSearchTypeCombo() +{ + LLInventoryFilter::ESearchType search_type(LLInventoryFilter::SEARCHTYPE_NAME); + + if(mSingleFolderMode && isGalleryViewMode()) + { + search_type = mCombinationGalleryPanel->getSearchType(); + } + else if(mSingleFolderMode && isCombinationViewMode()) + { + search_type = mCombinationGalleryPanel->getSearchType(); + } + else + { + search_type = getActivePanel()->getSearchType(); + } + + switch(search_type) + { + case LLInventoryFilter::SEARCHTYPE_CREATOR: + mSearchTypeCombo->setValue("search_by_creator"); + break; + case LLInventoryFilter::SEARCHTYPE_DESCRIPTION: + mSearchTypeCombo->setValue("search_by_description"); + break; + case LLInventoryFilter::SEARCHTYPE_UUID: + mSearchTypeCombo->setValue("search_by_UUID"); + break; + case LLInventoryFilter::SEARCHTYPE_NAME: + default: + mSearchTypeCombo->setValue("search_by_name"); + break; + } +} + +// static +bool LLPanelMainInventory::filtersVisible(void* user_data) +{ + LLPanelMainInventory* self = (LLPanelMainInventory*)user_data; + if(!self) return false; + + return self->getFinder() != NULL; +} + +void LLPanelMainInventory::onClearSearch() +{ + bool initially_active = false; + LLFloater *finder = getFinder(); + if (mActivePanel && (getActivePanel() != mWornItemsPanel)) + { + initially_active = mActivePanel->getFilter().isNotDefault(); + setFilterSubString(LLStringUtil::null); + mActivePanel->setFilterTypes(0xffffffffffffffffULL); + mActivePanel->setFilterLinks(LLInventoryFilter::FILTERLINK_INCLUDE_LINKS); + } + + if (finder) + { + LLFloaterInventoryFinder::selectAllTypes(finder); + } + + // re-open folders that were initially open in case filter was active + if (mActivePanel && (mFilterSubString.size() || initially_active) && !mSingleFolderMode) + { + mSavedFolderState->setApply(true); + mActivePanel->getRootFolder()->applyFunctorRecursively(*mSavedFolderState); + LLOpenFoldersWithSelection opener; + mActivePanel->getRootFolder()->applyFunctorRecursively(opener); + mActivePanel->getRootFolder()->scrollToShowSelection(); + } + mFilterSubString = ""; + + LLSidepanelInventory * sidepanel_inventory = getParentSidepanelInventory(); + if (sidepanel_inventory) + { + LLPanelMarketplaceInbox* inbox_panel = sidepanel_inventory->getChild("marketplace_inbox"); + if (inbox_panel) + { + inbox_panel->onClearSearch(); + } + } +} + +void LLPanelMainInventory::onFilterEdit(const std::string& search_string ) +{ + if(mSingleFolderMode && isGalleryViewMode()) + { + mFilterSubString = search_string; + mCombinationGalleryPanel->setFilterSubString(mFilterSubString); + return; + } + if(mSingleFolderMode && isCombinationViewMode()) + { + mCombinationGalleryPanel->setFilterSubString(search_string); + } + + if (search_string == "") + { + onClearSearch(); + } + + if (!mActivePanel) + { + return; + } + + if (!LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) + { + llassert(false); // this should have been done on startup + LLInventoryModelBackgroundFetch::instance().start(); + } + + mFilterSubString = search_string; + if (mActivePanel->getFilterSubString().empty() && mFilterSubString.empty()) + { + // current filter and new filter empty, do nothing + return; + } + + // save current folder open state if no filter currently applied + if (!mActivePanel->getFilter().isNotDefault()) + { + mSavedFolderState->setApply(false); + mActivePanel->getRootFolder()->applyFunctorRecursively(*mSavedFolderState); + } + + // set new filter string + setFilterSubString(mFilterSubString); + + LLSidepanelInventory * sidepanel_inventory = getParentSidepanelInventory(); + if (sidepanel_inventory) + { + LLPanelMarketplaceInbox* inbox_panel = sidepanel_inventory->getChild("marketplace_inbox"); + if (inbox_panel) + { + inbox_panel->onFilterEdit(search_string); + } + } +} + + + //static + bool LLPanelMainInventory::incrementalFind(LLFolderViewItem* first_item, const char *find_text, bool backward) + { + LLPanelMainInventory* active_view = NULL; + + LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("inventory"); + for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); iter != inst_list.end(); ++iter) + { + LLPanelMainInventory* iv = dynamic_cast(*iter); + if (iv) + { + if (gFocusMgr.childHasKeyboardFocus(iv)) + { + active_view = iv; + break; + } + } + } + + if (!active_view) + { + return false; + } + + std::string search_string(find_text); + + if (search_string.empty()) + { + return false; + } + + if (active_view->getPanel() && + active_view->getPanel()->getRootFolder()->search(first_item, search_string, backward)) + { + return true; + } + + return false; + } + +void LLPanelMainInventory::onFilterSelected() +{ + // Find my index + setActivePanel(); + + if (!mActivePanel) + { + return; + } + + if (getActivePanel() == mWornItemsPanel) + { + mActivePanel->openAllFolders(); + } + updateSearchTypeCombo(); + setFilterSubString(mFilterSubString); + LLInventoryFilter& filter = getCurrentFilter(); + LLFloaterInventoryFinder *finder = getFinder(); + if (finder) + { + finder->changeFilter(&filter); + if (mSingleFolderMode) + { + finder->setTitle(getLocalizedRootName()); + } + } + if (filter.isActive() && !LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) + { + llassert(false); // this should have been done on startup + LLInventoryModelBackgroundFetch::instance().start(); + } + setFilterTextFromFilter(); +} + +const std::string LLPanelMainInventory::getFilterSubString() +{ + return mActivePanel->getFilterSubString(); +} + +void LLPanelMainInventory::setFilterSubString(const std::string& string) +{ + mActivePanel->setFilterSubString(string); +} + +bool LLPanelMainInventory::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + // Check to see if we are auto scrolling from the last frame + LLInventoryPanel* panel = (LLInventoryPanel*)this->getActivePanel(); + bool needsToScroll = panel->getScrollableContainer()->canAutoScroll(x, y); + if(mFilterTabs) + { + if(needsToScroll) + { + mFilterTabs->startDragAndDropDelayTimer(); + } + } + + bool handled = LLPanel::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); + + return handled; +} + +// virtual +void LLPanelMainInventory::changed(U32) +{ + updateItemcountText(); +} + +void LLPanelMainInventory::setFocusFilterEditor() +{ + if(mFilterEditor) + { + mFilterEditor->setFocus(true); + } +} + +// virtual +void LLPanelMainInventory::draw() +{ + if (mActivePanel && mFilterEditor) + { + mFilterEditor->setText(mFilterSubString); + } + if (mActivePanel && mResortActivePanel) + { + // EXP-756: Force resorting of the list the first time we draw the list: + // In the case of date sorting, we don't have enough information at initialization time + // to correctly sort the folders. Later manual resort doesn't do anything as the order value is + // set correctly. The workaround is to reset the order to alphabetical (or anything) then to the correct order. + U32 order = mActivePanel->getSortOrder(); + mActivePanel->setSortOrder(LLInventoryFilter::SO_NAME); + mActivePanel->setSortOrder(order); + mResortActivePanel = false; + } + LLPanel::draw(); + updateItemcountText(); + updateCombinationVisibility(); +} + +void LLPanelMainInventory::updateItemcountText() +{ + if(mItemCount != gInventory.getItemCount()) + { + mItemCount = gInventory.getItemCount(); + mItemCountString = ""; + LLLocale locale(LLLocale::USER_LOCALE); + LLResMgr::getInstance()->getIntegerString(mItemCountString, mItemCount); + } + + if(mCategoryCount != gInventory.getCategoryCount()) + { + mCategoryCount = gInventory.getCategoryCount(); + mCategoryCountString = ""; + LLLocale locale(LLLocale::USER_LOCALE); + LLResMgr::getInstance()->getIntegerString(mCategoryCountString, mCategoryCount); + } + + LLStringUtil::format_map_t string_args; + string_args["[ITEM_COUNT]"] = mItemCountString; + string_args["[CATEGORY_COUNT]"] = mCategoryCountString; + string_args["[FILTER]"] = getFilterText(); + + std::string text = ""; + + if (LLInventoryModelBackgroundFetch::instance().folderFetchActive()) + { + text = getString("ItemcountFetching", string_args); + } + else if (LLInventoryModelBackgroundFetch::instance().isEverythingFetched()) + { + text = getString("ItemcountCompleted", string_args); + } + else + { + text = getString("ItemcountUnknown", string_args); + } + + if (mSingleFolderMode) + { + LLInventoryModel::cat_array_t *cats; + LLInventoryModel::item_array_t *items; + + gInventory.getDirectDescendentsOf(getCurrentSFVRoot(), cats, items); + + if (items && cats) + { + string_args["[ITEM_COUNT]"] = llformat("%d", items->size()); + string_args["[CATEGORY_COUNT]"] = llformat("%d", cats->size()); + text = getString("ItemcountCompleted", string_args); + } + } + + mCounterCtrl->setValue(text); + mCounterCtrl->setToolTip(text); +} + +void LLPanelMainInventory::onFocusReceived() +{ + LLSidepanelInventory *sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); + if (!sidepanel_inventory) + { + LL_WARNS() << "Could not find Inventory Panel in My Inventory floater" << LL_ENDL; + return; + } + + sidepanel_inventory->clearSelections(false, true); +} + +void LLPanelMainInventory::setFilterTextFromFilter() +{ + mFilterText = getCurrentFilter().getFilterText(); +} + +void LLPanelMainInventory::toggleFindOptions() +{ + LLFloater *floater = getFinder(); + if (!floater) + { + LLFloaterInventoryFinder * finder = new LLFloaterInventoryFinder(this); + mFinderHandle = finder->getHandle(); + finder->openFloater(); + + LLFloater* parent_floater = gFloaterView->getParentFloater(this); + if (parent_floater) + parent_floater->addDependentFloater(mFinderHandle); + + if (!LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) + { + llassert(false); // this should have been done on startup + LLInventoryModelBackgroundFetch::instance().start(); + } + + if (mSingleFolderMode) + { + finder->setTitle(getLocalizedRootName()); + } + } + else + { + floater->closeFloater(); + } +} + +void LLPanelMainInventory::setSelectCallback(const LLFolderView::signal_t::slot_type& cb) +{ + getChild(ALL_ITEMS)->setSelectCallback(cb); + getChild(RECENT_ITEMS)->setSelectCallback(cb); +} + +void LLPanelMainInventory::onSelectionChange(LLInventoryPanel *panel, const std::deque& items, bool user_action) +{ + updateListCommands(); + panel->onSelectionChange(items, user_action); +} + +///---------------------------------------------------------------------------- +/// LLFloaterInventoryFinder +///---------------------------------------------------------------------------- + +LLFloaterInventoryFinder* LLPanelMainInventory::getFinder() +{ + return (LLFloaterInventoryFinder*)mFinderHandle.get(); +} + + +LLFloaterInventoryFinder::LLFloaterInventoryFinder(LLPanelMainInventory* inventory_view) : + LLFloater(LLSD()), + mPanelMainInventory(inventory_view), + mFilter(&inventory_view->getPanel()->getFilter()) +{ + buildFromFile("floater_inventory_view_finder.xml"); + updateElementsFromFilter(); +} + +bool LLFloaterInventoryFinder::postBuild() +{ + const LLRect& viewrect = mPanelMainInventory->getRect(); + setRect(LLRect(viewrect.mLeft - getRect().getWidth(), viewrect.mTop, viewrect.mLeft, viewrect.mTop - getRect().getHeight())); + + childSetAction("All", selectAllTypes, this); + childSetAction("None", selectNoTypes, this); + + mSpinSinceHours = getChild("spin_hours_ago"); + childSetCommitCallback("spin_hours_ago", onTimeAgo, this); + + mSpinSinceDays = getChild("spin_days_ago"); + childSetCommitCallback("spin_days_ago", onTimeAgo, this); + + mCreatorSelf = getChild("check_created_by_me"); + mCreatorOthers = getChild("check_created_by_others"); + mCreatorSelf->setCommitCallback(boost::bind(&LLFloaterInventoryFinder::onCreatorSelfFilterCommit, this)); + mCreatorOthers->setCommitCallback(boost::bind(&LLFloaterInventoryFinder::onCreatorOtherFilterCommit, this)); + + childSetAction("Close", onCloseBtn, this); + + updateElementsFromFilter(); + return true; +} +void LLFloaterInventoryFinder::onTimeAgo(LLUICtrl *ctrl, void *user_data) +{ + LLFloaterInventoryFinder *self = (LLFloaterInventoryFinder *)user_data; + if (!self) return; + + if ( self->mSpinSinceDays->get() || self->mSpinSinceHours->get() ) + { + self->getChild("check_since_logoff")->setValue(false); + + U32 days = (U32)self->mSpinSinceDays->get(); + U32 hours = (U32)self->mSpinSinceHours->get(); + if (hours >= 24) + { + // Try to handle both cases of spinner clicking and text input in a sensible fashion as best as possible. + // There is no way to tell if someone has clicked the spinner to get to 24 or input 24 manually, so in + // this case add to days. Any value > 24 means they have input the hours manually, so do not add to the + // current day value. + if (24 == hours) // Got to 24 via spinner clicking or text input of 24 + { + days = days + hours / 24; + } + else // Text input, so do not add to days + { + days = hours / 24; + } + hours = (U32)hours % 24; + self->mSpinSinceHours->setFocus(false); + self->mSpinSinceDays->setFocus(false); + self->mSpinSinceDays->set((F32)days); + self->mSpinSinceHours->set((F32)hours); + self->mSpinSinceHours->setFocus(true); + } + } +} + +void LLFloaterInventoryFinder::changeFilter(LLInventoryFilter* filter) +{ + mFilter = filter; + updateElementsFromFilter(); +} + +void LLFloaterInventoryFinder::updateElementsFromFilter() +{ + if (!mFilter) + return; + + // Get data needed for filter display + U32 filter_types = mFilter->getFilterObjectTypes(); + LLInventoryFilter::EFolderShow show_folders = mFilter->getShowFolderState(); + U32 hours = mFilter->getHoursAgo(); + U32 date_search_direction = mFilter->getDateSearchDirection(); + + LLInventoryFilter::EFilterCreatorType filter_creator = mFilter->getFilterCreatorType(); + bool show_created_by_me = ((filter_creator == LLInventoryFilter::FILTERCREATOR_ALL) || (filter_creator == LLInventoryFilter::FILTERCREATOR_SELF)); + bool show_created_by_others = ((filter_creator == LLInventoryFilter::FILTERCREATOR_ALL) || (filter_creator == LLInventoryFilter::FILTERCREATOR_OTHERS)); + + // update the ui elements + setTitle(mFilter->getName()); + + getChild("check_animation")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_ANIMATION)); + + getChild("check_calling_card")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_CALLINGCARD)); + getChild("check_clothing")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_WEARABLE)); + getChild("check_gesture")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_GESTURE)); + getChild("check_landmark")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_LANDMARK)); + getChild("check_material")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_MATERIAL)); + getChild("check_notecard")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_NOTECARD)); + getChild("check_object")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_OBJECT)); + getChild("check_script")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_LSL)); + getChild("check_sound")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_SOUND)); + getChild("check_texture")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_TEXTURE)); + getChild("check_snapshot")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_SNAPSHOT)); + getChild("check_settings")->setValue((S32)(filter_types & 0x1 << LLInventoryType::IT_SETTINGS)); + getChild("check_show_empty")->setValue(show_folders == LLInventoryFilter::SHOW_ALL_FOLDERS); + + getChild("check_created_by_me")->setValue(show_created_by_me); + getChild("check_created_by_others")->setValue(show_created_by_others); + + getChild("check_since_logoff")->setValue(mFilter->isSinceLogoff()); + mSpinSinceHours->set((F32)(hours % 24)); + mSpinSinceDays->set((F32)(hours / 24)); + getChild("date_search_direction")->setSelectedIndex(date_search_direction); +} + +void LLFloaterInventoryFinder::draw() +{ + U64 filter = 0xffffffffffffffffULL; + bool filtered_by_all_types = true; + + if (!getChild("check_animation")->getValue()) + { + filter &= ~(0x1 << LLInventoryType::IT_ANIMATION); + filtered_by_all_types = false; + } + + + if (!getChild("check_calling_card")->getValue()) + { + filter &= ~(0x1 << LLInventoryType::IT_CALLINGCARD); + filtered_by_all_types = false; + } + + if (!getChild("check_clothing")->getValue()) + { + filter &= ~(0x1 << LLInventoryType::IT_WEARABLE); + filtered_by_all_types = false; + } + + if (!getChild("check_gesture")->getValue()) + { + filter &= ~(0x1 << LLInventoryType::IT_GESTURE); + filtered_by_all_types = false; + } + + if (!getChild("check_landmark")->getValue()) + + + { + filter &= ~(0x1 << LLInventoryType::IT_LANDMARK); + filtered_by_all_types = false; + } + + if (!getChild("check_material")->getValue()) + { + filter &= ~(0x1 << LLInventoryType::IT_MATERIAL); + filtered_by_all_types = false; + } + + if (!getChild("check_notecard")->getValue()) + { + filter &= ~(0x1 << LLInventoryType::IT_NOTECARD); + filtered_by_all_types = false; + } + + if (!getChild("check_object")->getValue()) + { + filter &= ~(0x1 << LLInventoryType::IT_OBJECT); + filter &= ~(0x1 << LLInventoryType::IT_ATTACHMENT); + filtered_by_all_types = false; + } + + if (!getChild("check_script")->getValue()) + { + filter &= ~(0x1 << LLInventoryType::IT_LSL); + filtered_by_all_types = false; + } + + if (!getChild("check_sound")->getValue()) + { + filter &= ~(0x1 << LLInventoryType::IT_SOUND); + filtered_by_all_types = false; + } + + if (!getChild("check_texture")->getValue()) + { + filter &= ~(0x1 << LLInventoryType::IT_TEXTURE); + filtered_by_all_types = false; + } + + if (!getChild("check_snapshot")->getValue()) + { + filter &= ~(0x1 << LLInventoryType::IT_SNAPSHOT); + filtered_by_all_types = false; + } + + if (!getChild("check_settings")->getValue()) + { + filter &= ~(0x1 << LLInventoryType::IT_SETTINGS); + filtered_by_all_types = false; + } + + if (!filtered_by_all_types || (mPanelMainInventory->getPanel()->getFilter().getFilterTypes() & LLInventoryFilter::FILTERTYPE_DATE)) + { + // don't include folders in filter, unless I've selected everything or filtering by date + filter &= ~(0x1 << LLInventoryType::IT_CATEGORY); + } + + + bool is_sf_mode = mPanelMainInventory->isSingleFolderMode(); + if(is_sf_mode && mPanelMainInventory->isGalleryViewMode()) + { + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setShowFolderState(getCheckShowEmpty() ? + LLInventoryFilter::SHOW_ALL_FOLDERS : LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setFilterObjectTypes(filter); + } + else + { + if(is_sf_mode && mPanelMainInventory->isCombinationViewMode()) + { + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setShowFolderState(getCheckShowEmpty() ? + LLInventoryFilter::SHOW_ALL_FOLDERS : LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setFilterObjectTypes(filter); + } + // update the panel, panel will update the filter + mPanelMainInventory->getPanel()->setShowFolderState(getCheckShowEmpty() ? + LLInventoryFilter::SHOW_ALL_FOLDERS : LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); + mPanelMainInventory->getPanel()->setFilterTypes(filter); + } + + if (getCheckSinceLogoff()) + { + mSpinSinceDays->set(0); + mSpinSinceHours->set(0); + } + U32 days = (U32)mSpinSinceDays->get(); + U32 hours = (U32)mSpinSinceHours->get(); + if (hours >= 24) + { + days = hours / 24; + hours = (U32)hours % 24; + // A UI element that has focus will not display a new value set to it + mSpinSinceHours->setFocus(false); + mSpinSinceDays->setFocus(false); + mSpinSinceDays->set((F32)days); + mSpinSinceHours->set((F32)hours); + mSpinSinceHours->setFocus(true); + } + hours += days * 24; + + + mPanelMainInventory->setFilterTextFromFilter(); + if(is_sf_mode && mPanelMainInventory->isGalleryViewMode()) + { + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setHoursAgo(hours); + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setDateRangeLastLogoff(getCheckSinceLogoff()); + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setDateSearchDirection(getDateSearchDirection()); + } + else + { + if(is_sf_mode && mPanelMainInventory->isCombinationViewMode()) + { + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setHoursAgo(hours); + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setDateRangeLastLogoff(getCheckSinceLogoff()); + mPanelMainInventory->mCombinationGalleryPanel->getFilter().setDateSearchDirection(getDateSearchDirection()); + } + mPanelMainInventory->getPanel()->setHoursAgo(hours); + mPanelMainInventory->getPanel()->setSinceLogoff(getCheckSinceLogoff()); + mPanelMainInventory->getPanel()->setDateSearchDirection(getDateSearchDirection()); + } + + LLPanel::draw(); +} + +void LLFloaterInventoryFinder::onCreatorSelfFilterCommit() +{ + bool show_creator_self = mCreatorSelf->getValue(); + bool show_creator_other = mCreatorOthers->getValue(); + + if(show_creator_self && show_creator_other) + { + mPanelMainInventory->getCurrentFilter().setFilterCreator(LLInventoryFilter::FILTERCREATOR_ALL); + } + else if(show_creator_self) + { + mPanelMainInventory->getCurrentFilter().setFilterCreator(LLInventoryFilter::FILTERCREATOR_SELF); + } + else if(!show_creator_self || !show_creator_other) + { + mPanelMainInventory->getCurrentFilter().setFilterCreator(LLInventoryFilter::FILTERCREATOR_OTHERS); + mCreatorOthers->set(true); + } +} + +void LLFloaterInventoryFinder::onCreatorOtherFilterCommit() +{ + bool show_creator_self = mCreatorSelf->getValue(); + bool show_creator_other = mCreatorOthers->getValue(); + + if(show_creator_self && show_creator_other) + { + mPanelMainInventory->getCurrentFilter().setFilterCreator(LLInventoryFilter::FILTERCREATOR_ALL); + } + else if(show_creator_other) + { + mPanelMainInventory->getCurrentFilter().setFilterCreator(LLInventoryFilter::FILTERCREATOR_OTHERS); + } + else if(!show_creator_other || !show_creator_self) + { + mPanelMainInventory->getCurrentFilter().setFilterCreator(LLInventoryFilter::FILTERCREATOR_SELF); + mCreatorSelf->set(true); + } +} + +bool LLFloaterInventoryFinder::getCheckShowEmpty() +{ + return getChild("check_show_empty")->getValue(); +} + +bool LLFloaterInventoryFinder::getCheckSinceLogoff() +{ + return getChild("check_since_logoff")->getValue(); +} + +U32 LLFloaterInventoryFinder::getDateSearchDirection() +{ + return getChild("date_search_direction")->getSelectedIndex(); +} + +void LLFloaterInventoryFinder::onCloseBtn(void* user_data) +{ + LLFloaterInventoryFinder* finderp = (LLFloaterInventoryFinder*)user_data; + finderp->closeFloater(); +} + +// static +void LLFloaterInventoryFinder::selectAllTypes(void* user_data) +{ + LLFloaterInventoryFinder* self = (LLFloaterInventoryFinder*)user_data; + if(!self) return; + + self->getChild("check_animation")->setValue(true); + self->getChild("check_calling_card")->setValue(true); + self->getChild("check_clothing")->setValue(true); + self->getChild("check_gesture")->setValue(true); + self->getChild("check_landmark")->setValue(true); + self->getChild("check_material")->setValue(true); + self->getChild("check_notecard")->setValue(true); + self->getChild("check_object")->setValue(true); + self->getChild("check_script")->setValue(true); + self->getChild("check_sound")->setValue(true); + self->getChild("check_texture")->setValue(true); + self->getChild("check_snapshot")->setValue(true); + self->getChild("check_settings")->setValue(true); +} + +//static +void LLFloaterInventoryFinder::selectNoTypes(void* user_data) +{ + LLFloaterInventoryFinder* self = (LLFloaterInventoryFinder*)user_data; + if(!self) return; + + self->getChild("check_animation")->setValue(false); + self->getChild("check_calling_card")->setValue(false); + self->getChild("check_clothing")->setValue(false); + self->getChild("check_gesture")->setValue(false); + self->getChild("check_landmark")->setValue(false); + self->getChild("check_material")->setValue(false); + self->getChild("check_notecard")->setValue(false); + self->getChild("check_object")->setValue(false); + self->getChild("check_script")->setValue(false); + self->getChild("check_sound")->setValue(false); + self->getChild("check_texture")->setValue(false); + self->getChild("check_snapshot")->setValue(false); + self->getChild("check_settings")->setValue(false); +} + +////////////////////////////////////////////////////////////////////////////////// +// List Commands // + +void LLPanelMainInventory::initListCommandsHandlers() +{ + childSetAction("add_btn", boost::bind(&LLPanelMainInventory::onAddButtonClick, this)); + childSetAction("view_mode_btn", boost::bind(&LLPanelMainInventory::onViewModeClick, this)); + childSetAction("up_btn", boost::bind(&LLPanelMainInventory::onUpFolderClicked, this)); + childSetAction("back_btn", boost::bind(&LLPanelMainInventory::onBackFolderClicked, this)); + childSetAction("forward_btn", boost::bind(&LLPanelMainInventory::onForwardFolderClicked, this)); + + mCommitCallbackRegistrar.add("Inventory.GearDefault.Custom.Action", boost::bind(&LLPanelMainInventory::onCustomAction, this, _2)); + mEnableCallbackRegistrar.add("Inventory.GearDefault.Check", boost::bind(&LLPanelMainInventory::isActionChecked, this, _2)); + mEnableCallbackRegistrar.add("Inventory.GearDefault.Enable", boost::bind(&LLPanelMainInventory::isActionEnabled, this, _2)); + mEnableCallbackRegistrar.add("Inventory.GearDefault.Visible", boost::bind(&LLPanelMainInventory::isActionVisible, this, _2)); + mMenuGearDefault = LLUICtrlFactory::getInstance()->createFromFile("menu_inventory_gear_default.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + mGearMenuButton->setMenu(mMenuGearDefault, LLMenuButton::MP_BOTTOM_LEFT, true); + mMenuViewDefault = LLUICtrlFactory::getInstance()->createFromFile("menu_inventory_view_default.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + mViewMenuButton->setMenu(mMenuViewDefault, LLMenuButton::MP_BOTTOM_LEFT, true); + LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile("menu_inventory_add.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + mMenuAddHandle = menu->getHandle(); + + mMenuVisibility = LLUICtrlFactory::getInstance()->createFromFile("menu_inventory_search_visibility.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + mVisibilityMenuButton->setMenu(mMenuVisibility, LLMenuButton::MP_BOTTOM_LEFT, true); + + // Update the trash button when selected item(s) get worn or taken off. + LLOutfitObserver::instance().addCOFChangedCallback(boost::bind(&LLPanelMainInventory::updateListCommands, this)); +} + +void LLPanelMainInventory::updateListCommands() +{ +} + +void LLPanelMainInventory::onAddButtonClick() +{ +// Gray out the "New Folder" option when the Recent tab is active as new folders will not be displayed +// unless "Always show folders" is checked in the filter options. + + LLMenuGL* menu = (LLMenuGL*)mMenuAddHandle.get(); + if (menu) + { + disableAddIfNeeded(); + + setUploadCostIfNeeded(); + + showActionMenu(menu,"add_btn"); + } +} + +void LLPanelMainInventory::setActivePanel() +{ + // Todo: should cover gallery mode in some way + if(mSingleFolderMode && isListViewMode()) + { + mActivePanel = getChild("comb_single_folder_inv"); + } + else if(mSingleFolderMode && isCombinationViewMode()) + { + mActivePanel = getChild("comb_single_folder_inv"); + } + else + { + mActivePanel = (LLInventoryPanel*)getChild("inventory filter tabs")->getCurrentPanel(); + } + mViewModeBtn->setEnabled(mSingleFolderMode || (getAllItemsPanel() == getActivePanel())); +} + +void LLPanelMainInventory::initSingleFolderRoot(const LLUUID& start_folder_id) +{ + mCombinationInventoryPanel->initFolderRoot(start_folder_id); +} + +void LLPanelMainInventory::initInventoryViews() +{ + LLInventoryPanel* all_item = getChild(ALL_ITEMS); + all_item->initializeViewBuilding(); + LLInventoryPanel* recent_item = getChild(RECENT_ITEMS); + recent_item->initializeViewBuilding(); + LLInventoryPanel* worn_item = getChild(WORN_ITEMS); + worn_item->initializeViewBuilding(); +} + +void LLPanelMainInventory::toggleViewMode() +{ + if(mSingleFolderMode && isCombinationViewMode()) + { + mCombinationInventoryPanel->getRootFolder()->setForceArrange(false); + } + + mSingleFolderMode = !mSingleFolderMode; + mReshapeInvLayout = true; + + if (mCombinationGalleryPanel->getRootFolder().isNull()) + { + mCombinationGalleryPanel->setRootFolder(mCombinationInventoryPanel->getSingleFolderRoot()); + mCombinationGalleryPanel->updateRootFolder(); + } + + updatePanelVisibility(); + setActivePanel(); + updateTitle(); + onFilterSelected(); + + LLSidepanelInventory* sidepanel_inventory = getParentSidepanelInventory(); + if (sidepanel_inventory) + { + if(mSingleFolderMode) + { + sidepanel_inventory->hideInbox(); + } + else + { + sidepanel_inventory->toggleInbox(); + } + } +} + +void LLPanelMainInventory::onViewModeClick() +{ + LLUUID selected_folder; + LLUUID new_root_folder; + if(mSingleFolderMode) + { + selected_folder = getCurrentSFVRoot(); + } + else + { + LLFolderView* root = getActivePanel()->getRootFolder(); + std::set selection_set = root->getSelectionList(); + if (selection_set.size() == 1) + { + LLFolderViewItem* current_item = *selection_set.begin(); + if (current_item) + { + const LLUUID& id = static_cast(current_item->getViewModelItem())->getUUID(); + if(gInventory.getCategory(id) != NULL) + { + new_root_folder = id; + } + else + { + const LLViewerInventoryItem* selected_item = gInventory.getItem(id); + if (selected_item && selected_item->getParentUUID().notNull()) + { + new_root_folder = selected_item->getParentUUID(); + selected_folder = id; + } + } + } + } + mCombinationInventoryPanel->initFolderRoot(new_root_folder); + } + + toggleViewMode(); + + if (mSingleFolderMode && new_root_folder.notNull()) + { + setSingleFolderViewRoot(new_root_folder, true); + if(selected_folder.notNull() && isListViewMode()) + { + getActivePanel()->setSelection(selected_folder, TAKE_FOCUS_YES); + } + } + else + { + if(selected_folder.notNull()) + { + selectAllItemsPanel(); + getActivePanel()->setSelection(selected_folder, TAKE_FOCUS_YES); + } + } +} + +void LLPanelMainInventory::onUpFolderClicked() +{ + const LLViewerInventoryCategory* cat = gInventory.getCategory(getCurrentSFVRoot()); + if (cat) + { + if (cat->getParentUUID().notNull()) + { + if(isListViewMode()) + { + mCombinationInventoryPanel->changeFolderRoot(cat->getParentUUID()); + } + if(isGalleryViewMode()) + { + mCombinationGalleryPanel->setRootFolder(cat->getParentUUID()); + } + if(isCombinationViewMode()) + { + mCombinationInventoryPanel->changeFolderRoot(cat->getParentUUID()); + } + } + } +} + +void LLPanelMainInventory::onBackFolderClicked() +{ + if(isListViewMode()) + { + mCombinationInventoryPanel->onBackwardFolder(); + } + if(isGalleryViewMode()) + { + mCombinationGalleryPanel->onBackwardFolder(); + } + if(isCombinationViewMode()) + { + mCombinationInventoryPanel->onBackwardFolder(); + } +} + +void LLPanelMainInventory::onForwardFolderClicked() +{ + if(isListViewMode()) + { + mCombinationInventoryPanel->onForwardFolder(); + } + if(isGalleryViewMode()) + { + mCombinationGalleryPanel->onForwardFolder(); + } + if(isCombinationViewMode()) + { + mCombinationInventoryPanel->onForwardFolder(); + } +} + +void LLPanelMainInventory::setSingleFolderViewRoot(const LLUUID& folder_id, bool clear_nav_history) +{ + if(isListViewMode()) + { + mCombinationInventoryPanel->changeFolderRoot(folder_id); + if(clear_nav_history) + { + mCombinationInventoryPanel->clearNavigationHistory(); + } + } + else if(isGalleryViewMode()) + { + mCombinationGalleryPanel->setRootFolder(folder_id); + if(clear_nav_history) + { + mCombinationGalleryPanel->clearNavigationHistory(); + } + } + else if(isCombinationViewMode()) + { + mCombinationInventoryPanel->changeFolderRoot(folder_id); + } + updateNavButtons(); +} + +LLUUID LLPanelMainInventory::getSingleFolderViewRoot() +{ + return mCombinationInventoryPanel->getSingleFolderRoot(); +} + +void LLPanelMainInventory::showActionMenu(LLMenuGL* menu, std::string spawning_view_name) +{ + if (menu) + { + menu->buildDrawLabels(); + menu->updateParent(LLMenuGL::sMenuContainer); + LLView* spawning_view = getChild (spawning_view_name); + S32 menu_x, menu_y; + //show menu in co-ordinates of panel + spawning_view->localPointToOtherView(0, 0, &menu_x, &menu_y, this); + LLMenuGL::showPopup(this, menu, menu_x, menu_y); + } +} + +void LLPanelMainInventory::onClipboardAction(const LLSD& userdata) +{ + std::string command_name = userdata.asString(); + getActivePanel()->doToSelected(command_name); +} + +void LLPanelMainInventory::saveTexture(const LLSD& userdata) +{ + LLUUID item_id; + if(mSingleFolderMode && isGalleryViewMode()) + { + item_id = mCombinationGalleryPanel->getFirstSelectedItemID(); + if (item_id.isNull()) return; + } + else + { + LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); + if (!current_item) + { + return; + } + item_id = static_cast(current_item->getViewModelItem())->getUUID(); + } + + LLPreviewTexture* preview_texture = LLFloaterReg::showTypedInstance("preview_texture", LLSD(item_id), TAKE_FOCUS_YES); + if (preview_texture) + { + preview_texture->openToSave(); + } +} + +void LLPanelMainInventory::onCustomAction(const LLSD& userdata) +{ + if (!isActionEnabled(userdata)) + return; + + const std::string command_name = userdata.asString(); + + if (command_name == "new_window") + { + newWindow(); + } + if (command_name == "sort_by_name") + { + const LLSD arg = "name"; + setSortBy(arg); + } + if (command_name == "sort_by_recent") + { + const LLSD arg = "date"; + setSortBy(arg); + } + if (command_name == "sort_folders_by_name") + { + const LLSD arg = "foldersalwaysbyname"; + setSortBy(arg); + } + if (command_name == "sort_system_folders_to_top") + { + const LLSD arg = "systemfolderstotop"; + setSortBy(arg); + } + if (command_name == "show_filters") + { + toggleFindOptions(); + } + if (command_name == "reset_filters") + { + resetFilters(); + } + if (command_name == "close_folders") + { + closeAllFolders(); + } + if (command_name == "empty_trash") + { + const std::string notification = "ConfirmEmptyTrash"; + gInventory.emptyFolderType(notification, LLFolderType::FT_TRASH); + } + if (command_name == "empty_lostnfound") + { + const std::string notification = "ConfirmEmptyLostAndFound"; + gInventory.emptyFolderType(notification, LLFolderType::FT_LOST_AND_FOUND); + } + if (command_name == "save_texture") + { + saveTexture(userdata); + } + // This doesn't currently work, since the viewer can't change an assetID an item. + if (command_name == "regenerate_link") + { + LLInventoryPanel *active_panel = getActivePanel(); + LLFolderViewItem* current_item = active_panel->getRootFolder()->getCurSelectedItem(); + if (!current_item) + { + return; + } + const LLUUID item_id = static_cast(current_item->getViewModelItem())->getUUID(); + LLViewerInventoryItem *item = gInventory.getItem(item_id); + if (item) + { + item->regenerateLink(); + } + active_panel->setSelection(item_id, TAKE_FOCUS_NO); + } + if (command_name == "find_original") + { + if(mSingleFolderMode && isGalleryViewMode()) + { + LLInventoryObject *obj = gInventory.getObject(mCombinationGalleryPanel->getFirstSelectedItemID()); + if (obj && obj->getIsLinkType()) + { + show_item_original(obj->getUUID()); + } + } + else + { + LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); + if (!current_item) + { + return; + } + static_cast(current_item->getViewModelItem())->performAction(getActivePanel()->getModel(), "goto"); + } + } + + if (command_name == "find_links") + { + if(mSingleFolderMode && isGalleryViewMode()) + { + LLFloaterSidePanelContainer* inventory_container = newWindow(); + if (inventory_container) + { + LLSidepanelInventory* sidepanel_inventory = dynamic_cast(inventory_container->findChild("main_panel", true)); + if (sidepanel_inventory) + { + LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); + if (main_inventory) + { + LLInventoryObject *obj = gInventory.getObject(mCombinationGalleryPanel->getFirstSelectedItemID()); + if (obj) + { + main_inventory->findLinks(obj->getUUID(), obj->getName()); + } + } + } + } + } + else + { + LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); + if (!current_item) + { + return; + } + const LLUUID& item_id = static_cast(current_item->getViewModelItem())->getUUID(); + const std::string &item_name = current_item->getViewModelItem()->getName(); + findLinks(item_id, item_name); + } + } + + if (command_name == "replace_links") + { + LLSD params; + if(mSingleFolderMode && isGalleryViewMode()) + { + params = LLSD(mCombinationGalleryPanel->getFirstSelectedItemID()); + } + else + { + LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); + if (current_item) + { + LLInvFVBridge* bridge = (LLInvFVBridge*)current_item->getViewModelItem(); + + if (bridge) + { + LLInventoryObject* obj = bridge->getInventoryObject(); + if (obj && obj->getType() != LLAssetType::AT_CATEGORY && obj->getActualType() != LLAssetType::AT_LINK_FOLDER) + { + params = LLSD(obj->getUUID()); + } + } + } + } + LLFloaterReg::showInstance("linkreplace", params); + } + + if (command_name == "close_inv_windows") + { + LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("inventory"); + for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); iter != inst_list.end();) + { + LLFloaterSidePanelContainer* iv = dynamic_cast(*iter++); + if (iv) + { + iv->closeFloater(); + } + } + LLFloaterReg::hideInstance("inventory_settings"); + } + + if (command_name == "toggle_search_outfits") + { + getCurrentFilter().toggleSearchVisibilityOutfits(); + } + + if (command_name == "toggle_search_trash") + { + getCurrentFilter().toggleSearchVisibilityTrash(); + } + + if (command_name == "toggle_search_library") + { + getCurrentFilter().toggleSearchVisibilityLibrary(); + } + + if (command_name == "include_links") + { + getCurrentFilter().toggleSearchVisibilityLinks(); + } + + if (command_name == "share") + { + if(mSingleFolderMode && isGalleryViewMode()) + { + std::set uuids{ mCombinationGalleryPanel->getFirstSelectedItemID()}; + LLAvatarActions::shareWithAvatars(uuids, gFloaterView->getParentFloater(this)); + } + else + { + LLAvatarActions::shareWithAvatars(this); + } + } + if (command_name == "shop") + { + LLWeb::loadURL(gSavedSettings.getString("MarketplaceURL")); + } + if (command_name == "list_view") + { + setViewMode(MODE_LIST); + } + if (command_name == "gallery_view") + { + setViewMode(MODE_GALLERY); + } + if (command_name == "combination_view") + { + setViewMode(MODE_COMBINATION); + } +} + +void LLPanelMainInventory::onVisibilityChange( bool new_visibility ) +{ + if(!new_visibility) + { + LLMenuGL* menu = (LLMenuGL*)mMenuAddHandle.get(); + if (menu) + { + menu->setVisible(false); + } + getActivePanel()->getRootFolder()->finishRenamingItem(); + } +} + +bool LLPanelMainInventory::isSaveTextureEnabled(const LLSD& userdata) +{ + LLViewerInventoryItem *inv_item = NULL; + if(mSingleFolderMode && isGalleryViewMode()) + { + inv_item = gInventory.getItem(mCombinationGalleryPanel->getFirstSelectedItemID()); + } + else + { + LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); + if (current_item) + { + inv_item = dynamic_cast(static_cast(current_item->getViewModelItem())->getInventoryObject()); + } + } + if(inv_item) + { + bool can_save = inv_item->checkPermissionsSet(PERM_ITEM_UNRESTRICTED); + LLInventoryType::EType curr_type = inv_item->getInventoryType(); + return can_save && (curr_type == LLInventoryType::IT_TEXTURE || curr_type == LLInventoryType::IT_SNAPSHOT); + } + + return false; +} + +bool LLPanelMainInventory::isActionEnabled(const LLSD& userdata) +{ + const std::string command_name = userdata.asString(); + if (command_name == "not_empty") + { + bool status = false; + LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); + if (current_item) + { + const LLUUID& item_id = static_cast(current_item->getViewModelItem())->getUUID(); + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(item_id, cat_array, item_array); + status = (0 == cat_array->size() && 0 == item_array->size()); + } + return status; + } + if (command_name == "delete") + { + return getActivePanel()->isSelectionRemovable(); + } + if (command_name == "save_texture") + { + return isSaveTextureEnabled(userdata); + } + if (command_name == "find_original") + { + LLUUID item_id; + if(mSingleFolderMode && isGalleryViewMode()) + { + item_id = mCombinationGalleryPanel->getFirstSelectedItemID(); + } + else{ + LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); + if (!current_item) return false; + item_id = static_cast(current_item->getViewModelItem())->getUUID(); + } + const LLViewerInventoryItem *item = gInventory.getItem(item_id); + if (item && item->getIsLinkType() && !item->getIsBrokenLink()) + { + return true; + } + return false; + } + + if (command_name == "find_links") + { + LLUUID item_id; + if(mSingleFolderMode && isGalleryViewMode()) + { + item_id = mCombinationGalleryPanel->getFirstSelectedItemID(); + } + else{ + LLFolderView* root = getActivePanel()->getRootFolder(); + std::set selection_set = root->getSelectionList(); + if (selection_set.size() != 1) return false; + LLFolderViewItem* current_item = root->getCurSelectedItem(); + if (!current_item) return false; + item_id = static_cast(current_item->getViewModelItem())->getUUID(); + } + const LLInventoryObject *obj = gInventory.getObject(item_id); + if (obj && !obj->getIsLinkType() && LLAssetType::lookupCanLink(obj->getType())) + { + return true; + } + return false; + } + // This doesn't currently work, since the viewer can't change an assetID an item. + if (command_name == "regenerate_link") + { + LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); + if (!current_item) return false; + const LLUUID& item_id = static_cast(current_item->getViewModelItem())->getUUID(); + const LLViewerInventoryItem *item = gInventory.getItem(item_id); + if (item && item->getIsBrokenLink()) + { + return true; + } + return false; + } + + if (command_name == "share") + { + if(mSingleFolderMode && isGalleryViewMode()) + { + return can_share_item(mCombinationGalleryPanel->getFirstSelectedItemID()); + } + else{ + LLFolderViewItem* current_item = getActivePanel()->getRootFolder()->getCurSelectedItem(); + if (!current_item) return false; + LLSidepanelInventory* parent = LLFloaterSidePanelContainer::getPanel("inventory"); + return parent ? parent->canShare() : false; + } + } + if (command_name == "empty_trash") + { + const LLUUID &trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + LLInventoryModel::EHasChildren children = gInventory.categoryHasChildren(trash_id); + return children != LLInventoryModel::CHILDREN_NO && gInventory.isCategoryComplete(trash_id); + } + if (command_name == "empty_lostnfound") + { + const LLUUID &trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_LOST_AND_FOUND); + LLInventoryModel::EHasChildren children = gInventory.categoryHasChildren(trash_id); + return children != LLInventoryModel::CHILDREN_NO && gInventory.isCategoryComplete(trash_id); + } + + return true; +} + +bool LLPanelMainInventory::isActionVisible(const LLSD& userdata) +{ + const std::string param_str = userdata.asString(); + if (param_str == "single_folder_view") + { + return mSingleFolderMode; + } + if (param_str == "multi_folder_view") + { + return !mSingleFolderMode; + } + + return true; +} + +bool LLPanelMainInventory::isActionChecked(const LLSD& userdata) +{ + U32 sort_order_mask = (mSingleFolderMode && isGalleryViewMode()) ? mCombinationGalleryPanel->getSortOrder() : getActivePanel()->getSortOrder(); + const std::string command_name = userdata.asString(); + if (command_name == "sort_by_name") + { + return ~sort_order_mask & LLInventoryFilter::SO_DATE; + } + + if (command_name == "sort_by_recent") + { + return sort_order_mask & LLInventoryFilter::SO_DATE; + } + + if (command_name == "sort_folders_by_name") + { + return sort_order_mask & LLInventoryFilter::SO_FOLDERS_BY_NAME; + } + + if (command_name == "sort_system_folders_to_top") + { + return sort_order_mask & LLInventoryFilter::SO_SYSTEM_FOLDERS_TO_TOP; + } + + if (command_name == "toggle_search_outfits") + { + return (getCurrentFilter().getSearchVisibilityTypes() & LLInventoryFilter::VISIBILITY_OUTFITS) != 0; + } + + if (command_name == "toggle_search_trash") + { + return (getCurrentFilter().getSearchVisibilityTypes() & LLInventoryFilter::VISIBILITY_TRASH) != 0; + } + + if (command_name == "toggle_search_library") + { + return (getCurrentFilter().getSearchVisibilityTypes() & LLInventoryFilter::VISIBILITY_LIBRARY) != 0; + } + + if (command_name == "include_links") + { + return (getCurrentFilter().getSearchVisibilityTypes() & LLInventoryFilter::VISIBILITY_LINKS) != 0; + } + + if (command_name == "list_view") + { + return isListViewMode(); + } + if (command_name == "gallery_view") + { + return isGalleryViewMode(); + } + if (command_name == "combination_view") + { + return isCombinationViewMode(); + } + + return false; +} + +void LLPanelMainInventory::setUploadCostIfNeeded() +{ + LLMenuGL* menu = (LLMenuGL*)mMenuAddHandle.get(); + if(mNeedUploadCost && menu) + { + const std::string texture_upload_cost_str = std::to_string(LLAgentBenefitsMgr::current().getTextureUploadCost()); + const std::string sound_upload_cost_str = std::to_string(LLAgentBenefitsMgr::current().getSoundUploadCost()); + const std::string animation_upload_cost_str = std::to_string(LLAgentBenefitsMgr::current().getAnimationUploadCost()); + + menu->getChild("Upload Image")->setLabelArg("[COST]", texture_upload_cost_str); + menu->getChild("Upload Sound")->setLabelArg("[COST]", sound_upload_cost_str); + menu->getChild("Upload Animation")->setLabelArg("[COST]", animation_upload_cost_str); + } +} + +bool is_add_allowed(LLUUID folder_id) +{ + if(!gInventory.isObjectDescendentOf(folder_id, gInventory.getRootFolderID())) + { + return false; + } + + std::vector not_allowed_types; + not_allowed_types.push_back(LLFolderType::FT_LOST_AND_FOUND); + not_allowed_types.push_back(LLFolderType::FT_FAVORITE); + not_allowed_types.push_back(LLFolderType::FT_MARKETPLACE_LISTINGS); + not_allowed_types.push_back(LLFolderType::FT_TRASH); + not_allowed_types.push_back(LLFolderType::FT_CURRENT_OUTFIT); + not_allowed_types.push_back(LLFolderType::FT_INBOX); + + for (std::vector::const_iterator it = not_allowed_types.begin(); + it != not_allowed_types.end(); ++it) + { + if(gInventory.isObjectDescendentOf(folder_id, gInventory.findCategoryUUIDForType(*it))) + { + return false; + } + } + + LLViewerInventoryCategory* cat = gInventory.getCategory(folder_id); + if (cat && (cat->getPreferredType() == LLFolderType::FT_OUTFIT)) + { + return false; + } + return true; +} + +void LLPanelMainInventory::disableAddIfNeeded() +{ + LLMenuGL* menu = (LLMenuGL*)mMenuAddHandle.get(); + if (menu) + { + bool enable = !mSingleFolderMode || is_add_allowed(getCurrentSFVRoot()); + + menu->getChild("New Folder")->setEnabled(enable && !isRecentItemsPanelSelected()); + menu->getChild("New Script")->setEnabled(enable); + menu->getChild("New Note")->setEnabled(enable); + menu->getChild("New Gesture")->setEnabled(enable); + menu->setItemEnabled("New Clothes", enable); + menu->setItemEnabled("New Body Parts", enable); + menu->setItemEnabled("New Settings", enable); + } +} + +bool LLPanelMainInventory::hasSettingsInventory() +{ + return LLEnvironment::instance().isInventoryEnabled(); +} + +bool LLPanelMainInventory::hasMaterialsInventory() +{ + std::string agent_url = gAgent.getRegionCapability("UpdateMaterialAgentInventory"); + std::string task_url = gAgent.getRegionCapability("UpdateMaterialTaskInventory"); + + return (!agent_url.empty() && !task_url.empty()); +} + +void LLPanelMainInventory::updateTitle() +{ + LLFloater* inventory_floater = gFloaterView->getParentFloater(this); + if(inventory_floater) + { + if(mSingleFolderMode) + { + inventory_floater->setTitle(getLocalizedRootName()); + LLFloaterInventoryFinder *finder = getFinder(); + if (finder) + { + finder->setTitle(getLocalizedRootName()); + } + } + else + { + inventory_floater->setTitle(getString("inventory_title")); + } + } + updateNavButtons(); +} + +void LLPanelMainInventory::onCombinationRootChanged(bool gallery_clicked) +{ + if(gallery_clicked) + { + mCombinationInventoryPanel->changeFolderRoot(mCombinationGalleryPanel->getRootFolder()); + } + else + { + mCombinationGalleryPanel->setRootFolder(mCombinationInventoryPanel->getSingleFolderRoot()); + } + mForceShowInvLayout = false; + updateTitle(); + mReshapeInvLayout = true; +} + +void LLPanelMainInventory::onCombinationGallerySelectionChanged(const LLUUID& category_id) +{ +} + +void LLPanelMainInventory::onCombinationInventorySelectionChanged(const std::deque& items, bool user_action) +{ + onSelectionChange(mCombinationInventoryPanel, items, user_action); +} + +void LLPanelMainInventory::updatePanelVisibility() +{ + mDefaultViewPanel->setVisible(!mSingleFolderMode); + mCombinationViewPanel->setVisible(mSingleFolderMode); + mNavigationBtnsPanel->setVisible(mSingleFolderMode); + mViewModeBtn->setImageOverlay(mSingleFolderMode ? getString("default_mode_btn") : getString("single_folder_mode_btn")); + mViewModeBtn->setEnabled(mSingleFolderMode || (getAllItemsPanel() == getActivePanel())); + if (mSingleFolderMode) + { + if (isCombinationViewMode()) + { + LLInventoryFilter& comb_inv_filter = mCombinationInventoryPanel->getFilter(); + comb_inv_filter.setFilterThumbnails(LLInventoryFilter::FILTER_EXCLUDE_THUMBNAILS); + comb_inv_filter.markDefault(); + + LLInventoryFilter& comb_gallery_filter = mCombinationGalleryPanel->getFilter(); + comb_gallery_filter.setFilterThumbnails(LLInventoryFilter::FILTER_ONLY_THUMBNAILS); + comb_gallery_filter.markDefault(); + + // visibility will be controled by updateCombinationVisibility() + mCombinationGalleryLayoutPanel->setVisible(true); + mCombinationGalleryPanel->setVisible(true); + mCombinationListLayoutPanel->setVisible(true); + } + else + { + LLInventoryFilter& comb_inv_filter = mCombinationInventoryPanel->getFilter(); + comb_inv_filter.setFilterThumbnails(LLInventoryFilter::FILTER_INCLUDE_THUMBNAILS); + comb_inv_filter.markDefault(); + + LLInventoryFilter& comb_gallery_filter = mCombinationGalleryPanel->getFilter(); + comb_gallery_filter.setFilterThumbnails(LLInventoryFilter::FILTER_INCLUDE_THUMBNAILS); + comb_gallery_filter.markDefault(); + + mCombinationLayoutStack->setPanelSpacing(0); + mCombinationGalleryLayoutPanel->setVisible(mSingleFolderMode && isGalleryViewMode()); + mCombinationGalleryPanel->setVisible(mSingleFolderMode && isGalleryViewMode()); // to prevent or process updates + mCombinationListLayoutPanel->setVisible(mSingleFolderMode && isListViewMode()); + } + } + else + { + mCombinationGalleryLayoutPanel->setVisible(false); + mCombinationGalleryPanel->setVisible(false); // to prevent updates + mCombinationListLayoutPanel->setVisible(false); + } +} + +void LLPanelMainInventory::updateCombinationVisibility() +{ + if(mSingleFolderMode && isCombinationViewMode()) + { + bool is_gallery_empty = !mCombinationGalleryPanel->hasVisibleItems(); + bool show_inv_pane = mCombinationInventoryPanel->hasVisibleItems() || is_gallery_empty || mForceShowInvLayout; + + const S32 DRAG_HANDLE_PADDING = 12; // for drag handle to not overlap gallery when both inventories are visible + mCombinationLayoutStack->setPanelSpacing(show_inv_pane ? DRAG_HANDLE_PADDING : 0); + + mCombinationGalleryLayoutPanel->setVisible(!is_gallery_empty); + mCombinationListLayoutPanel->setVisible(show_inv_pane); + mCombinationInventoryPanel->getRootFolder()->setForceArrange(!show_inv_pane); + if(mCombinationInventoryPanel->hasVisibleItems()) + { + mForceShowInvLayout = false; + } + if(is_gallery_empty) + { + mCombinationGalleryPanel->handleModifiedFilter(); + } + + getActivePanel()->getRootFolder(); + + if (mReshapeInvLayout + && show_inv_pane + && (mCombinationGalleryPanel->hasVisibleItems() || mCombinationGalleryPanel->areViewsInitialized()) + && mCombinationInventoryPanel->areViewsInitialized()) + { + mReshapeInvLayout = false; + + // force drop previous shape (because panel doesn't decrease shape properly) + LLRect list_latout = mCombinationListLayoutPanel->getRect(); + list_latout.mTop = list_latout.mBottom; // min height is at 100, so it should snap to be bigger + mCombinationListLayoutPanel->setShape(list_latout, false); + + LLRect inv_inner_rect = mCombinationInventoryPanel->getScrollableContainer()->getScrolledViewRect(); + S32 inv_height = inv_inner_rect.getHeight() + + (mCombinationInventoryPanel->getScrollableContainer()->getBorderWidth() * 2) + + mCombinationInventoryPanel->getScrollableContainer()->getSize(); + LLRect inner_galery_rect = mCombinationGalleryPanel->getScrollableContainer()->getScrolledViewRect(); + S32 gallery_height = inner_galery_rect.getHeight() + + (mCombinationGalleryPanel->getScrollableContainer()->getBorderWidth() * 2) + + mCombinationGalleryPanel->getScrollableContainer()->getSize(); + LLRect layout_rect = mCombinationViewPanel->getRect(); + + // by default make it take 1/3 of the panel + S32 list_default_height = layout_rect.getHeight() / 3; + // Don't set height from gallery_default_height - needs to account for a resizer in such case + S32 gallery_default_height = layout_rect.getHeight() - list_default_height; + + if (inv_height > list_default_height + && gallery_height < gallery_default_height) + { + LLRect gallery_latout = mCombinationGalleryLayoutPanel->getRect(); + gallery_latout.mTop = gallery_latout.mBottom + gallery_height; + mCombinationGalleryLayoutPanel->setShape(gallery_latout, true /*tell stack to account for new shape*/); + } + else if (inv_height < list_default_height + && gallery_height > gallery_default_height) + { + LLRect list_latout = mCombinationListLayoutPanel->getRect(); + list_latout.mTop = list_latout.mBottom + inv_height; + mCombinationListLayoutPanel->setShape(list_latout, true /*tell stack to account for new shape*/); + } + else + { + LLRect list_latout = mCombinationListLayoutPanel->getRect(); + list_latout.mTop = list_latout.mBottom + list_default_height; + mCombinationListLayoutPanel->setShape(list_latout, true /*tell stack to account for new shape*/); + } + } + } + + if (mSingleFolderMode + && !isGalleryViewMode() + && mCombInvUUIDNeedsRename.notNull() + && mCombinationInventoryPanel->areViewsInitialized()) + { + mCombinationInventoryPanel->setSelectionByID(mCombInvUUIDNeedsRename, true); + mCombinationInventoryPanel->getRootFolder()->scrollToShowSelection(); + mCombinationInventoryPanel->getRootFolder()->setNeedsAutoRename(true); + mCombInvUUIDNeedsRename.setNull(); + } +} + +void LLPanelMainInventory::updateNavButtons() +{ + if(isListViewMode()) + { + mBackBtn->setEnabled(mCombinationInventoryPanel->isBackwardAvailable()); + mForwardBtn->setEnabled(mCombinationInventoryPanel->isForwardAvailable()); + } + if(isGalleryViewMode()) + { + mBackBtn->setEnabled(mCombinationGalleryPanel->isBackwardAvailable()); + mForwardBtn->setEnabled(mCombinationGalleryPanel->isForwardAvailable()); + } + if(isCombinationViewMode()) + { + mBackBtn->setEnabled(mCombinationInventoryPanel->isBackwardAvailable()); + mForwardBtn->setEnabled(mCombinationInventoryPanel->isForwardAvailable()); + } + + const LLViewerInventoryCategory* cat = gInventory.getCategory(getCurrentSFVRoot()); + bool up_enabled = (cat && cat->getParentUUID().notNull()); + mUpBtn->setEnabled(up_enabled); +} + +LLSidepanelInventory* LLPanelMainInventory::getParentSidepanelInventory() +{ + LLFloaterSidePanelContainer* inventory_container = dynamic_cast(gFloaterView->getParentFloater(this)); + if(inventory_container) + { + return dynamic_cast(inventory_container->findChild("main_panel", true)); + } + return NULL; +} + +void LLPanelMainInventory::setViewMode(EViewModeType mode) +{ + if(mode != mViewMode) + { + std::list forward_history; + std::list backward_history; + U32 sort_order = 0; + switch(mViewMode) + { + case MODE_LIST: + forward_history = mCombinationInventoryPanel->getNavForwardList(); + backward_history = mCombinationInventoryPanel->getNavBackwardList(); + sort_order = mCombinationInventoryPanel->getSortOrder(); + break; + case MODE_GALLERY: + forward_history = mCombinationGalleryPanel->getNavForwardList(); + backward_history = mCombinationGalleryPanel->getNavBackwardList(); + sort_order = mCombinationGalleryPanel->getSortOrder(); + break; + case MODE_COMBINATION: + forward_history = mCombinationInventoryPanel->getNavForwardList(); + backward_history = mCombinationInventoryPanel->getNavBackwardList(); + mCombinationInventoryPanel->getRootFolder()->setForceArrange(false); + sort_order = mCombinationInventoryPanel->getSortOrder(); + break; + } + + LLUUID cur_root = getCurrentSFVRoot(); + mViewMode = mode; + + updatePanelVisibility(); + + if(isListViewMode()) + { + mCombinationInventoryPanel->changeFolderRoot(cur_root); + mCombinationInventoryPanel->setNavForwardList(forward_history); + mCombinationInventoryPanel->setNavBackwardList(backward_history); + mCombinationInventoryPanel->setSortOrder(sort_order); + } + if(isGalleryViewMode()) + { + mCombinationGalleryPanel->setRootFolder(cur_root); + mCombinationGalleryPanel->setNavForwardList(forward_history); + mCombinationGalleryPanel->setNavBackwardList(backward_history); + mCombinationGalleryPanel->setSortOrder(sort_order, true); + } + if(isCombinationViewMode()) + { + mCombinationInventoryPanel->changeFolderRoot(cur_root); + mCombinationGalleryPanel->setRootFolder(cur_root); + mCombinationInventoryPanel->setNavForwardList(forward_history); + mCombinationInventoryPanel->setNavBackwardList(backward_history); + mCombinationGalleryPanel->setNavForwardList(forward_history); + mCombinationGalleryPanel->setNavBackwardList(backward_history); + mCombinationInventoryPanel->setSortOrder(sort_order); + mCombinationGalleryPanel->setSortOrder(sort_order, true); + } + + updateNavButtons(); + + onFilterSelected(); + if((isListViewMode() && (mActivePanel->getFilterSubString() != mFilterSubString)) || + (isGalleryViewMode() && (mCombinationGalleryPanel->getFilterSubString() != mFilterSubString))) + { + onFilterEdit(mFilterSubString); + } + } +} + +std::string LLPanelMainInventory::getLocalizedRootName() +{ + return mSingleFolderMode ? get_localized_folder_name(getCurrentSFVRoot()) : ""; +} + +LLUUID LLPanelMainInventory::getCurrentSFVRoot() +{ + if(isListViewMode()) + { + return mCombinationInventoryPanel->getSingleFolderRoot(); + } + if(isGalleryViewMode()) + { + return mCombinationGalleryPanel->getRootFolder(); + } + if(isCombinationViewMode()) + { + return mCombinationInventoryPanel->getSingleFolderRoot(); + } + return LLUUID::null; +} + +LLInventoryFilter& LLPanelMainInventory::getCurrentFilter() +{ + if(mSingleFolderMode && isGalleryViewMode()) + { + return mCombinationGalleryPanel->getFilter(); + } + else + { + return mActivePanel->getFilter(); + } +} + +void LLPanelMainInventory::setGallerySelection(const LLUUID& item_id, bool new_window) +{ + if(mSingleFolderMode && isGalleryViewMode()) + { + mCombinationGalleryPanel->changeItemSelection(item_id, true); + } + else if(mSingleFolderMode && isCombinationViewMode()) + { + if(mCombinationGalleryPanel->getFilter().checkAgainstFilterThumbnails(item_id)) + { + mCombinationGalleryPanel->changeItemSelection(item_id, false); + scrollToGallerySelection(); + } + else + { + mCombinationInventoryPanel->setSelection(item_id, true); + scrollToInvPanelSelection(); + } + } + else if (mSingleFolderMode && isListViewMode()) + { + mCombinationInventoryPanel->setSelection(item_id, true); + } +} + +void LLPanelMainInventory::scrollToGallerySelection() +{ + mCombinationGalleryPanel->scrollToShowItem(mCombinationGalleryPanel->getFirstSelectedItemID()); +} + +void LLPanelMainInventory::scrollToInvPanelSelection() +{ + mCombinationInventoryPanel->getRootFolder()->scrollToShowSelection(); +} + +// List Commands // +//////////////////////////////////////////////////////////////////////////////// diff --git a/indra/newview/llpanelmaininventory.h b/indra/newview/llpanelmaininventory.h index f1e4476bb0..cad2501645 100644 --- a/indra/newview/llpanelmaininventory.h +++ b/indra/newview/llpanelmaininventory.h @@ -1,265 +1,265 @@ -/** - * @file llpanelmaininventory.h - * @brief llpanelmaininventory.h - * class definition - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELMAININVENTORY_H -#define LL_LLPANELMAININVENTORY_H - -#include "llpanel.h" -#include "llinventoryobserver.h" -#include "llinventorypanel.h" -#include "lldndbutton.h" - -#include "llfolderview.h" - -class LLComboBox; -class LLFolderViewItem; -class LLInventoryPanel; -class LLInventoryGallery; -class LLSaveFolderState; -class LLFilterEditor; -class LLTabContainer; -class LLFloaterInventoryFinder; -class LLMenuButton; -class LLMenuGL; -class LLSidepanelInventory; -class LLToggleableMenu; -class LLFloater; -class LLFloaterSidePanelContainer; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLPanelMainInventory -// -// This is a panel used to view and control an agent's inventory, -// including all the fixin's (e.g. AllItems/RecentItems tabs, filter floaters). -// -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLPanelMainInventory : public LLPanel, LLInventoryObserver -{ -public: - friend class LLFloaterInventoryFinder; - - LLPanelMainInventory(const LLPanel::Params& p = getDefaultParams()); - ~LLPanelMainInventory(); - - bool postBuild(); - - enum EViewModeType - { - MODE_LIST, - MODE_GALLERY, - MODE_COMBINATION - }; - - virtual bool handleKeyHere(KEY key, MASK mask); - - // Inherited functionality - /*virtual*/ bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg); - /*virtual*/ void changed(U32); - /*virtual*/ void draw(); - /*virtual*/ void onVisibilityChange ( bool new_visibility ); - - LLInventoryPanel* getPanel() { return mActivePanel; } - LLInventoryPanel* getActivePanel() { return mActivePanel; } - LLInventoryPanel* getAllItemsPanel(); - void selectAllItemsPanel(); - const LLInventoryPanel* getActivePanel() const { return mActivePanel; } - void setActivePanel(); - - bool isRecentItemsPanelSelected(); - - const std::string& getFilterText() const { return mFilterText; } - - void setSelectCallback(const LLFolderView::signal_t::slot_type& cb); - - void onFilterEdit(const std::string& search_string ); - - void setFocusFilterEditor(); - - static LLFloaterSidePanelContainer* newWindow(); - static void newFolderWindow(LLUUID folder_id = LLUUID(), LLUUID item_to_select = LLUUID()); - - void toggleFindOptions(); - - void resetFilters(); - void resetAllItemsFilters(); - - void findLinks(const LLUUID& item_id, const std::string& item_name); - - void onViewModeClick(); - void toggleViewMode(); - void initSingleFolderRoot(const LLUUID& start_folder_id = LLUUID::null); - void initInventoryViews(); - void onUpFolderClicked(); - void onBackFolderClicked(); - void onForwardFolderClicked(); - void setSingleFolderViewRoot(const LLUUID& folder_id, bool clear_nav_history = true); - void setGallerySelection(const LLUUID& item_id, bool new_window = false); - LLUUID getSingleFolderViewRoot(); - bool isSingleFolderMode() { return mSingleFolderMode; } - - void scrollToGallerySelection(); - void scrollToInvPanelSelection(); - - void setViewMode(EViewModeType mode); - bool isListViewMode() { return (mViewMode == MODE_LIST); } - bool isGalleryViewMode() { return (mViewMode == MODE_GALLERY); } - bool isCombinationViewMode() { return (mViewMode == MODE_COMBINATION); } - LLUUID getCurrentSFVRoot(); - std::string getLocalizedRootName(); - - LLInventoryFilter& getCurrentFilter(); - -protected: - // - // Misc functions - // - void setFilterTextFromFilter(); - void startSearch(); - - void onSelectionChange(LLInventoryPanel *panel, const std::deque& items, bool user_action); - - static bool filtersVisible(void* user_data); - void onClearSearch(); - static void onFoldersByName(void *user_data); - static bool checkFoldersByName(void *user_data); - - static bool incrementalFind(LLFolderViewItem* first_item, const char *find_text, bool backward); - void onFilterSelected(); - - const std::string getFilterSubString(); - void setFilterSubString(const std::string& string); - - // menu callbacks - void doToSelected(const LLSD& userdata); - void closeAllFolders(); - void doCreate(const LLSD& userdata); - void setSortBy(const LLSD& userdata); - void saveTexture(const LLSD& userdata); - bool isSaveTextureEnabled(const LLSD& userdata); - void updateItemcountText(); - - void updatePanelVisibility(); - void updateCombinationVisibility(); - - void onFocusReceived(); - void onSelectSearchType(); - void updateSearchTypeCombo(); - void setSearchType(LLInventoryFilter::ESearchType type); - - LLSidepanelInventory* getParentSidepanelInventory(); - -private: - LLFloaterInventoryFinder* getFinder(); - - LLFilterEditor* mFilterEditor; - LLTabContainer* mFilterTabs; - LLUICtrl* mCounterCtrl; - LLHandle mFinderHandle; - LLInventoryPanel* mActivePanel; - LLInventoryPanel* mWornItemsPanel; - bool mResortActivePanel; - LLSaveFolderState* mSavedFolderState; - std::string mFilterText; - std::string mFilterSubString; - S32 mItemCount; - std::string mItemCountString; - S32 mCategoryCount; - std::string mCategoryCountString; - LLComboBox* mSearchTypeCombo; - - LLButton* mBackBtn; - LLButton* mForwardBtn; - LLButton* mUpBtn; - LLButton* mViewModeBtn; - LLLayoutPanel* mNavigationBtnsPanel; - - LLPanel* mDefaultViewPanel; - LLPanel* mCombinationViewPanel; - - bool mSingleFolderMode; - EViewModeType mViewMode; - - LLInventorySingleFolderPanel* mCombinationInventoryPanel; - LLInventoryGallery* mCombinationGalleryPanel; - LLPanel* mCombinationGalleryLayoutPanel; - LLLayoutPanel* mCombinationListLayoutPanel; - LLLayoutStack* mCombinationLayoutStack; - - boost::signals2::connection mListViewRootUpdatedConnection; - boost::signals2::connection mGalleryRootUpdatedConnection; - - ////////////////////////////////////////////////////////////////////////////////// - // List Commands // -protected: - void initListCommandsHandlers(); - void updateListCommands(); - void onAddButtonClick(); - void showActionMenu(LLMenuGL* menu, std::string spawning_view_name); - void onClipboardAction(const LLSD& userdata); - bool isActionEnabled(const LLSD& command_name); - bool isActionChecked(const LLSD& userdata); - void onCustomAction(const LLSD& command_name); - bool isActionVisible(const LLSD& userdata); - static bool hasSettingsInventory(); - static bool hasMaterialsInventory(); - void updateTitle(); - void updateNavButtons(); - - void onCombinationRootChanged(bool gallery_clicked); - void onCombinationGallerySelectionChanged(const LLUUID& category_id); - void onCombinationInventorySelectionChanged(const std::deque& items, bool user_action); - /** - * Set upload cost in "Upload" sub menu. - */ - void setUploadCostIfNeeded(); - void disableAddIfNeeded(); -private: - LLToggleableMenu* mMenuGearDefault; - LLToggleableMenu* mMenuViewDefault; - LLToggleableMenu* mMenuVisibility; - LLMenuButton* mGearMenuButton; - LLMenuButton* mViewMenuButton; - LLMenuButton* mVisibilityMenuButton; - LLHandle mMenuAddHandle; - - bool mNeedUploadCost; - - bool mForceShowInvLayout; - bool mReshapeInvLayout; - LLUUID mCombInvUUIDNeedsRename; - // List Commands // - //////////////////////////////////////////////////////////////////////////////// -}; - -#endif // LL_LLPANELMAININVENTORY_H - - - +/** + * @file llpanelmaininventory.h + * @brief llpanelmaininventory.h + * class definition + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELMAININVENTORY_H +#define LL_LLPANELMAININVENTORY_H + +#include "llpanel.h" +#include "llinventoryobserver.h" +#include "llinventorypanel.h" +#include "lldndbutton.h" + +#include "llfolderview.h" + +class LLComboBox; +class LLFolderViewItem; +class LLInventoryPanel; +class LLInventoryGallery; +class LLSaveFolderState; +class LLFilterEditor; +class LLTabContainer; +class LLFloaterInventoryFinder; +class LLMenuButton; +class LLMenuGL; +class LLSidepanelInventory; +class LLToggleableMenu; +class LLFloater; +class LLFloaterSidePanelContainer; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLPanelMainInventory +// +// This is a panel used to view and control an agent's inventory, +// including all the fixin's (e.g. AllItems/RecentItems tabs, filter floaters). +// +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLPanelMainInventory : public LLPanel, LLInventoryObserver +{ +public: + friend class LLFloaterInventoryFinder; + + LLPanelMainInventory(const LLPanel::Params& p = getDefaultParams()); + ~LLPanelMainInventory(); + + bool postBuild(); + + enum EViewModeType + { + MODE_LIST, + MODE_GALLERY, + MODE_COMBINATION + }; + + virtual bool handleKeyHere(KEY key, MASK mask); + + // Inherited functionality + /*virtual*/ bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + /*virtual*/ void changed(U32); + /*virtual*/ void draw(); + /*virtual*/ void onVisibilityChange ( bool new_visibility ); + + LLInventoryPanel* getPanel() { return mActivePanel; } + LLInventoryPanel* getActivePanel() { return mActivePanel; } + LLInventoryPanel* getAllItemsPanel(); + void selectAllItemsPanel(); + const LLInventoryPanel* getActivePanel() const { return mActivePanel; } + void setActivePanel(); + + bool isRecentItemsPanelSelected(); + + const std::string& getFilterText() const { return mFilterText; } + + void setSelectCallback(const LLFolderView::signal_t::slot_type& cb); + + void onFilterEdit(const std::string& search_string ); + + void setFocusFilterEditor(); + + static LLFloaterSidePanelContainer* newWindow(); + static void newFolderWindow(LLUUID folder_id = LLUUID(), LLUUID item_to_select = LLUUID()); + + void toggleFindOptions(); + + void resetFilters(); + void resetAllItemsFilters(); + + void findLinks(const LLUUID& item_id, const std::string& item_name); + + void onViewModeClick(); + void toggleViewMode(); + void initSingleFolderRoot(const LLUUID& start_folder_id = LLUUID::null); + void initInventoryViews(); + void onUpFolderClicked(); + void onBackFolderClicked(); + void onForwardFolderClicked(); + void setSingleFolderViewRoot(const LLUUID& folder_id, bool clear_nav_history = true); + void setGallerySelection(const LLUUID& item_id, bool new_window = false); + LLUUID getSingleFolderViewRoot(); + bool isSingleFolderMode() { return mSingleFolderMode; } + + void scrollToGallerySelection(); + void scrollToInvPanelSelection(); + + void setViewMode(EViewModeType mode); + bool isListViewMode() { return (mViewMode == MODE_LIST); } + bool isGalleryViewMode() { return (mViewMode == MODE_GALLERY); } + bool isCombinationViewMode() { return (mViewMode == MODE_COMBINATION); } + LLUUID getCurrentSFVRoot(); + std::string getLocalizedRootName(); + + LLInventoryFilter& getCurrentFilter(); + +protected: + // + // Misc functions + // + void setFilterTextFromFilter(); + void startSearch(); + + void onSelectionChange(LLInventoryPanel *panel, const std::deque& items, bool user_action); + + static bool filtersVisible(void* user_data); + void onClearSearch(); + static void onFoldersByName(void *user_data); + static bool checkFoldersByName(void *user_data); + + static bool incrementalFind(LLFolderViewItem* first_item, const char *find_text, bool backward); + void onFilterSelected(); + + const std::string getFilterSubString(); + void setFilterSubString(const std::string& string); + + // menu callbacks + void doToSelected(const LLSD& userdata); + void closeAllFolders(); + void doCreate(const LLSD& userdata); + void setSortBy(const LLSD& userdata); + void saveTexture(const LLSD& userdata); + bool isSaveTextureEnabled(const LLSD& userdata); + void updateItemcountText(); + + void updatePanelVisibility(); + void updateCombinationVisibility(); + + void onFocusReceived(); + void onSelectSearchType(); + void updateSearchTypeCombo(); + void setSearchType(LLInventoryFilter::ESearchType type); + + LLSidepanelInventory* getParentSidepanelInventory(); + +private: + LLFloaterInventoryFinder* getFinder(); + + LLFilterEditor* mFilterEditor; + LLTabContainer* mFilterTabs; + LLUICtrl* mCounterCtrl; + LLHandle mFinderHandle; + LLInventoryPanel* mActivePanel; + LLInventoryPanel* mWornItemsPanel; + bool mResortActivePanel; + LLSaveFolderState* mSavedFolderState; + std::string mFilterText; + std::string mFilterSubString; + S32 mItemCount; + std::string mItemCountString; + S32 mCategoryCount; + std::string mCategoryCountString; + LLComboBox* mSearchTypeCombo; + + LLButton* mBackBtn; + LLButton* mForwardBtn; + LLButton* mUpBtn; + LLButton* mViewModeBtn; + LLLayoutPanel* mNavigationBtnsPanel; + + LLPanel* mDefaultViewPanel; + LLPanel* mCombinationViewPanel; + + bool mSingleFolderMode; + EViewModeType mViewMode; + + LLInventorySingleFolderPanel* mCombinationInventoryPanel; + LLInventoryGallery* mCombinationGalleryPanel; + LLPanel* mCombinationGalleryLayoutPanel; + LLLayoutPanel* mCombinationListLayoutPanel; + LLLayoutStack* mCombinationLayoutStack; + + boost::signals2::connection mListViewRootUpdatedConnection; + boost::signals2::connection mGalleryRootUpdatedConnection; + + ////////////////////////////////////////////////////////////////////////////////// + // List Commands // +protected: + void initListCommandsHandlers(); + void updateListCommands(); + void onAddButtonClick(); + void showActionMenu(LLMenuGL* menu, std::string spawning_view_name); + void onClipboardAction(const LLSD& userdata); + bool isActionEnabled(const LLSD& command_name); + bool isActionChecked(const LLSD& userdata); + void onCustomAction(const LLSD& command_name); + bool isActionVisible(const LLSD& userdata); + static bool hasSettingsInventory(); + static bool hasMaterialsInventory(); + void updateTitle(); + void updateNavButtons(); + + void onCombinationRootChanged(bool gallery_clicked); + void onCombinationGallerySelectionChanged(const LLUUID& category_id); + void onCombinationInventorySelectionChanged(const std::deque& items, bool user_action); + /** + * Set upload cost in "Upload" sub menu. + */ + void setUploadCostIfNeeded(); + void disableAddIfNeeded(); +private: + LLToggleableMenu* mMenuGearDefault; + LLToggleableMenu* mMenuViewDefault; + LLToggleableMenu* mMenuVisibility; + LLMenuButton* mGearMenuButton; + LLMenuButton* mViewMenuButton; + LLMenuButton* mVisibilityMenuButton; + LLHandle mMenuAddHandle; + + bool mNeedUploadCost; + + bool mForceShowInvLayout; + bool mReshapeInvLayout; + LLUUID mCombInvUUIDNeedsRename; + // List Commands // + //////////////////////////////////////////////////////////////////////////////// +}; + +#endif // LL_LLPANELMAININVENTORY_H + + + diff --git a/indra/newview/llpanelmarketplaceinbox.cpp b/indra/newview/llpanelmarketplaceinbox.cpp index 57c4efeb87..b6ec403b87 100644 --- a/indra/newview/llpanelmarketplaceinbox.cpp +++ b/indra/newview/llpanelmarketplaceinbox.cpp @@ -1,281 +1,281 @@ -/** - * @file llpanelmarketplaceinbox.cpp - * @brief Panel for marketplace inbox - * -* $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelmarketplaceinbox.h" -#include "llpanelmarketplaceinboxinventory.h" - -#include "llappviewer.h" -#include "llbutton.h" -#include "llinventorypanel.h" -#include "llfloatersidepanelcontainer.h" -#include "llfolderview.h" -#include "llsidepanelinventory.h" -#include "llviewercontrol.h" - - -static LLPanelInjector t_panel_marketplace_inbox("panel_marketplace_inbox"); - -const LLPanelMarketplaceInbox::Params& LLPanelMarketplaceInbox::getDefaultParams() -{ - return LLUICtrlFactory::getDefaultParams(); -} - -// protected -LLPanelMarketplaceInbox::LLPanelMarketplaceInbox(const Params& p) - : LLPanel(p) - , mFreshCountCtrl(NULL) - , mInboxButton(NULL) - , mInventoryPanel(NULL) - , mSavedFolderState(NULL) -{ - mSavedFolderState = new LLSaveFolderState(); - mSavedFolderState->setApply(false); -} - -LLPanelMarketplaceInbox::~LLPanelMarketplaceInbox() -{ - delete mSavedFolderState; -} - -// virtual -bool LLPanelMarketplaceInbox::postBuild() -{ - LLFocusableElement::setFocusReceivedCallback(boost::bind(&LLPanelMarketplaceInbox::onFocusReceived, this)); - - mFreshCountCtrl = getChild("inbox_fresh_new_count"); - mInboxButton = getChild("inbox_btn"); - - return true; -} - -void LLPanelMarketplaceInbox::onSelectionChange() -{ -} - - -LLInventoryPanel * LLPanelMarketplaceInbox::setupInventoryPanel() -{ - LLView * inbox_inventory_placeholder = getChild("inbox_inventory_placeholder"); - LLView * inbox_inventory_parent = inbox_inventory_placeholder->getParent(); - - mInventoryPanel = - LLUICtrlFactory::createFromFile("panel_inbox_inventory.xml", - inbox_inventory_parent, - LLInventoryPanel::child_registry_t::instance()); - - llassert(mInventoryPanel); - - // Reshape the inventory to the proper size - LLRect inventory_placeholder_rect = inbox_inventory_placeholder->getRect(); - mInventoryPanel->setShape(inventory_placeholder_rect); - - // Set the sort order newest to oldest - mInventoryPanel->getFolderViewModel()->setSorter(LLInventoryFilter::SO_DATE); - mInventoryPanel->getFilter().markDefault(); - mInventoryPanel->getRootFolder()->applyFunctorRecursively(*mSavedFolderState); - - // Set selection callback for proper update of inventory status buttons - mInventoryPanel->setSelectCallback(boost::bind(&LLPanelMarketplaceInbox::onSelectionChange, this)); - - // Set up the note to display when the inbox is empty - mInventoryPanel->getFilter().setEmptyLookupMessage("InventoryInboxNoItems"); - - // Hide the placeholder text - inbox_inventory_placeholder->setVisible(false); - - return mInventoryPanel; -} - -void LLPanelMarketplaceInbox::onFocusReceived() -{ - LLSidepanelInventory *sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); - if (sidepanel_inventory) - { - sidepanel_inventory->clearSelections(true, false); - } - - gSavedPerAccountSettings.setU32("LastInventoryInboxActivity", time_corrected()); -} - -bool LLPanelMarketplaceInbox::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, void *cargo_data, EAcceptance *accept, std::string& tooltip_msg) -{ - *accept = ACCEPT_NO; - return true; -} - -U32 LLPanelMarketplaceInbox::getFreshItemCount() const -{ - - // - // NOTE: When turning this on, be sure to test the no inbox/outbox case because this code probably - // will return "2" for the Inventory and LIBRARY top-levels when that happens. - // - - U32 fresh_item_count = 0; - - if (mInventoryPanel) - { - LLFolderViewFolder * inbox_folder = mInventoryPanel->getRootFolder(); - - if (inbox_folder) - { - LLFolderViewFolder::folders_t::const_iterator folders_it = inbox_folder->getFoldersBegin(); - LLFolderViewFolder::folders_t::const_iterator folders_end = inbox_folder->getFoldersEnd(); - - for (; folders_it != folders_end; ++folders_it) - { - const LLFolderViewFolder * folder_view = *folders_it; - const LLInboxFolderViewFolder * inbox_folder_view = dynamic_cast(folder_view); - - if (inbox_folder_view && inbox_folder_view->isFresh()) - { - fresh_item_count++; - } - } - - LLFolderViewFolder::items_t::const_iterator items_it = inbox_folder->getItemsBegin(); - LLFolderViewFolder::items_t::const_iterator items_end = inbox_folder->getItemsEnd(); - - for (; items_it != items_end; ++items_it) - { - const LLFolderViewItem * item_view = *items_it; - const LLInboxFolderViewItem * inbox_item_view = dynamic_cast(item_view); - - if (inbox_item_view && inbox_item_view->isFresh()) - { - fresh_item_count++; - } - } - } - } - - return fresh_item_count; -} - -U32 LLPanelMarketplaceInbox::getTotalItemCount() const -{ - U32 item_count = 0; - - if (mInventoryPanel) - { - const LLFolderViewFolder * inbox_folder = mInventoryPanel->getRootFolder(); - - if (inbox_folder) - { - item_count += inbox_folder->getFoldersCount(); - item_count += inbox_folder->getItemsCount(); - } - } - - return item_count; -} - -void LLPanelMarketplaceInbox::onClearSearch() -{ - if (mInventoryPanel) - { - mInventoryPanel->setFilterSubString(LLStringUtil::null); - mSavedFolderState->setApply(true); - mInventoryPanel->getRootFolder()->applyFunctorRecursively(*mSavedFolderState); - LLOpenFoldersWithSelection opener; - mInventoryPanel->getRootFolder()->applyFunctorRecursively(opener); - mInventoryPanel->getRootFolder()->scrollToShowSelection(); - } -} - -void LLPanelMarketplaceInbox::onFilterEdit(const std::string& search_string) -{ - if (mInventoryPanel) - { - - if (search_string == "") - { - onClearSearch(); - } - - if (!mInventoryPanel->getFilter().isNotDefault()) - { - mSavedFolderState->setApply(false); - mInventoryPanel->getRootFolder()->applyFunctorRecursively(*mSavedFolderState); - } - mInventoryPanel->setFilterSubString(search_string); - } -} - -std::string LLPanelMarketplaceInbox::getBadgeString() const -{ - std::string item_count_str(""); - - LLPanel *inventory_panel = LLFloaterSidePanelContainer::getPanel("inventory"); - - // If the inbox is visible, and the side panel is collapsed or expanded and not the inventory panel - if (getParent()->getVisible() && inventory_panel && !inventory_panel->isInVisibleChain()) - { - U32 item_count = getFreshItemCount(); - - if (item_count) - { - item_count_str = llformat("%d", item_count); - } - } - - return item_count_str; -} - -void LLPanelMarketplaceInbox::draw() -{ - U32 item_count = getTotalItemCount(); - - llassert(mFreshCountCtrl != NULL); - - if (item_count > 0) - { - std::string item_count_str = llformat("%d", item_count); - - LLStringUtil::format_map_t args; - args["[NUM]"] = item_count_str; - mInboxButton->setLabel(getString("InboxLabelWithArg", args)); - - // set green text to fresh item count - U32 fresh_item_count = getFreshItemCount(); - mFreshCountCtrl->setVisible((fresh_item_count > 0)); - - if (fresh_item_count > 0) - { - mFreshCountCtrl->setTextArg("[NUM]", llformat("%d", fresh_item_count)); - } - } - else - { - mInboxButton->setLabel(getString("InboxLabelNoArg")); - - mFreshCountCtrl->setVisible(false); - } - - LLPanel::draw(); -} +/** + * @file llpanelmarketplaceinbox.cpp + * @brief Panel for marketplace inbox + * +* $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelmarketplaceinbox.h" +#include "llpanelmarketplaceinboxinventory.h" + +#include "llappviewer.h" +#include "llbutton.h" +#include "llinventorypanel.h" +#include "llfloatersidepanelcontainer.h" +#include "llfolderview.h" +#include "llsidepanelinventory.h" +#include "llviewercontrol.h" + + +static LLPanelInjector t_panel_marketplace_inbox("panel_marketplace_inbox"); + +const LLPanelMarketplaceInbox::Params& LLPanelMarketplaceInbox::getDefaultParams() +{ + return LLUICtrlFactory::getDefaultParams(); +} + +// protected +LLPanelMarketplaceInbox::LLPanelMarketplaceInbox(const Params& p) + : LLPanel(p) + , mFreshCountCtrl(NULL) + , mInboxButton(NULL) + , mInventoryPanel(NULL) + , mSavedFolderState(NULL) +{ + mSavedFolderState = new LLSaveFolderState(); + mSavedFolderState->setApply(false); +} + +LLPanelMarketplaceInbox::~LLPanelMarketplaceInbox() +{ + delete mSavedFolderState; +} + +// virtual +bool LLPanelMarketplaceInbox::postBuild() +{ + LLFocusableElement::setFocusReceivedCallback(boost::bind(&LLPanelMarketplaceInbox::onFocusReceived, this)); + + mFreshCountCtrl = getChild("inbox_fresh_new_count"); + mInboxButton = getChild("inbox_btn"); + + return true; +} + +void LLPanelMarketplaceInbox::onSelectionChange() +{ +} + + +LLInventoryPanel * LLPanelMarketplaceInbox::setupInventoryPanel() +{ + LLView * inbox_inventory_placeholder = getChild("inbox_inventory_placeholder"); + LLView * inbox_inventory_parent = inbox_inventory_placeholder->getParent(); + + mInventoryPanel = + LLUICtrlFactory::createFromFile("panel_inbox_inventory.xml", + inbox_inventory_parent, + LLInventoryPanel::child_registry_t::instance()); + + llassert(mInventoryPanel); + + // Reshape the inventory to the proper size + LLRect inventory_placeholder_rect = inbox_inventory_placeholder->getRect(); + mInventoryPanel->setShape(inventory_placeholder_rect); + + // Set the sort order newest to oldest + mInventoryPanel->getFolderViewModel()->setSorter(LLInventoryFilter::SO_DATE); + mInventoryPanel->getFilter().markDefault(); + mInventoryPanel->getRootFolder()->applyFunctorRecursively(*mSavedFolderState); + + // Set selection callback for proper update of inventory status buttons + mInventoryPanel->setSelectCallback(boost::bind(&LLPanelMarketplaceInbox::onSelectionChange, this)); + + // Set up the note to display when the inbox is empty + mInventoryPanel->getFilter().setEmptyLookupMessage("InventoryInboxNoItems"); + + // Hide the placeholder text + inbox_inventory_placeholder->setVisible(false); + + return mInventoryPanel; +} + +void LLPanelMarketplaceInbox::onFocusReceived() +{ + LLSidepanelInventory *sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); + if (sidepanel_inventory) + { + sidepanel_inventory->clearSelections(true, false); + } + + gSavedPerAccountSettings.setU32("LastInventoryInboxActivity", time_corrected()); +} + +bool LLPanelMarketplaceInbox::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, void *cargo_data, EAcceptance *accept, std::string& tooltip_msg) +{ + *accept = ACCEPT_NO; + return true; +} + +U32 LLPanelMarketplaceInbox::getFreshItemCount() const +{ + + // + // NOTE: When turning this on, be sure to test the no inbox/outbox case because this code probably + // will return "2" for the Inventory and LIBRARY top-levels when that happens. + // + + U32 fresh_item_count = 0; + + if (mInventoryPanel) + { + LLFolderViewFolder * inbox_folder = mInventoryPanel->getRootFolder(); + + if (inbox_folder) + { + LLFolderViewFolder::folders_t::const_iterator folders_it = inbox_folder->getFoldersBegin(); + LLFolderViewFolder::folders_t::const_iterator folders_end = inbox_folder->getFoldersEnd(); + + for (; folders_it != folders_end; ++folders_it) + { + const LLFolderViewFolder * folder_view = *folders_it; + const LLInboxFolderViewFolder * inbox_folder_view = dynamic_cast(folder_view); + + if (inbox_folder_view && inbox_folder_view->isFresh()) + { + fresh_item_count++; + } + } + + LLFolderViewFolder::items_t::const_iterator items_it = inbox_folder->getItemsBegin(); + LLFolderViewFolder::items_t::const_iterator items_end = inbox_folder->getItemsEnd(); + + for (; items_it != items_end; ++items_it) + { + const LLFolderViewItem * item_view = *items_it; + const LLInboxFolderViewItem * inbox_item_view = dynamic_cast(item_view); + + if (inbox_item_view && inbox_item_view->isFresh()) + { + fresh_item_count++; + } + } + } + } + + return fresh_item_count; +} + +U32 LLPanelMarketplaceInbox::getTotalItemCount() const +{ + U32 item_count = 0; + + if (mInventoryPanel) + { + const LLFolderViewFolder * inbox_folder = mInventoryPanel->getRootFolder(); + + if (inbox_folder) + { + item_count += inbox_folder->getFoldersCount(); + item_count += inbox_folder->getItemsCount(); + } + } + + return item_count; +} + +void LLPanelMarketplaceInbox::onClearSearch() +{ + if (mInventoryPanel) + { + mInventoryPanel->setFilterSubString(LLStringUtil::null); + mSavedFolderState->setApply(true); + mInventoryPanel->getRootFolder()->applyFunctorRecursively(*mSavedFolderState); + LLOpenFoldersWithSelection opener; + mInventoryPanel->getRootFolder()->applyFunctorRecursively(opener); + mInventoryPanel->getRootFolder()->scrollToShowSelection(); + } +} + +void LLPanelMarketplaceInbox::onFilterEdit(const std::string& search_string) +{ + if (mInventoryPanel) + { + + if (search_string == "") + { + onClearSearch(); + } + + if (!mInventoryPanel->getFilter().isNotDefault()) + { + mSavedFolderState->setApply(false); + mInventoryPanel->getRootFolder()->applyFunctorRecursively(*mSavedFolderState); + } + mInventoryPanel->setFilterSubString(search_string); + } +} + +std::string LLPanelMarketplaceInbox::getBadgeString() const +{ + std::string item_count_str(""); + + LLPanel *inventory_panel = LLFloaterSidePanelContainer::getPanel("inventory"); + + // If the inbox is visible, and the side panel is collapsed or expanded and not the inventory panel + if (getParent()->getVisible() && inventory_panel && !inventory_panel->isInVisibleChain()) + { + U32 item_count = getFreshItemCount(); + + if (item_count) + { + item_count_str = llformat("%d", item_count); + } + } + + return item_count_str; +} + +void LLPanelMarketplaceInbox::draw() +{ + U32 item_count = getTotalItemCount(); + + llassert(mFreshCountCtrl != NULL); + + if (item_count > 0) + { + std::string item_count_str = llformat("%d", item_count); + + LLStringUtil::format_map_t args; + args["[NUM]"] = item_count_str; + mInboxButton->setLabel(getString("InboxLabelWithArg", args)); + + // set green text to fresh item count + U32 fresh_item_count = getFreshItemCount(); + mFreshCountCtrl->setVisible((fresh_item_count > 0)); + + if (fresh_item_count > 0) + { + mFreshCountCtrl->setTextArg("[NUM]", llformat("%d", fresh_item_count)); + } + } + else + { + mInboxButton->setLabel(getString("InboxLabelNoArg")); + + mFreshCountCtrl->setVisible(false); + } + + LLPanel::draw(); +} diff --git a/indra/newview/llpanelmarketplaceinbox.h b/indra/newview/llpanelmarketplaceinbox.h index cc0d792d0f..e711bb5e5c 100644 --- a/indra/newview/llpanelmarketplaceinbox.h +++ b/indra/newview/llpanelmarketplaceinbox.h @@ -1,81 +1,81 @@ -/** - * @file llpanelmarketplaceinbox.h - * @brief Panel for marketplace inbox - * -* $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELMARKETPLACEINBOX_H -#define LL_LLPANELMARKETPLACEINBOX_H - -#include "llpanel.h" -#include "llfolderview.h" -class LLButton; -class LLInventoryPanel; -class LLUICtrl; - -class LLPanelMarketplaceInbox : public LLPanel -{ -public: - - struct Params : public LLInitParam::Block - {}; - - LOG_CLASS(LLPanelMarketplaceInbox); - - // RN: for some reason you can't just use LLUICtrlFactory::getDefaultParams as a default argument in VC8 - static const LLPanelMarketplaceInbox::Params& getDefaultParams(); - - LLPanelMarketplaceInbox(const Params& p = getDefaultParams()); - ~LLPanelMarketplaceInbox(); - - bool postBuild() override; - - bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, void *cargo_data, EAcceptance *accept, std::string& tooltip_msg) override; - - void draw() override; - - LLInventoryPanel * setupInventoryPanel(); - - void onClearSearch(); - void onFilterEdit(const std::string& search_string); - - U32 getFreshItemCount() const; - U32 getTotalItemCount() const; - - std::string getBadgeString() const; - -private: - - void onSelectionChange(); - - void onFocusReceived() override; - -private: - LLUICtrl * mFreshCountCtrl; - LLButton * mInboxButton; - LLInventoryPanel * mInventoryPanel; - LLSaveFolderState* mSavedFolderState; -}; - - -#endif //LL_LLPANELMARKETPLACEINBOX_H +/** + * @file llpanelmarketplaceinbox.h + * @brief Panel for marketplace inbox + * +* $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELMARKETPLACEINBOX_H +#define LL_LLPANELMARKETPLACEINBOX_H + +#include "llpanel.h" +#include "llfolderview.h" +class LLButton; +class LLInventoryPanel; +class LLUICtrl; + +class LLPanelMarketplaceInbox : public LLPanel +{ +public: + + struct Params : public LLInitParam::Block + {}; + + LOG_CLASS(LLPanelMarketplaceInbox); + + // RN: for some reason you can't just use LLUICtrlFactory::getDefaultParams as a default argument in VC8 + static const LLPanelMarketplaceInbox::Params& getDefaultParams(); + + LLPanelMarketplaceInbox(const Params& p = getDefaultParams()); + ~LLPanelMarketplaceInbox(); + + bool postBuild() override; + + bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, void *cargo_data, EAcceptance *accept, std::string& tooltip_msg) override; + + void draw() override; + + LLInventoryPanel * setupInventoryPanel(); + + void onClearSearch(); + void onFilterEdit(const std::string& search_string); + + U32 getFreshItemCount() const; + U32 getTotalItemCount() const; + + std::string getBadgeString() const; + +private: + + void onSelectionChange(); + + void onFocusReceived() override; + +private: + LLUICtrl * mFreshCountCtrl; + LLButton * mInboxButton; + LLInventoryPanel * mInventoryPanel; + LLSaveFolderState* mSavedFolderState; +}; + + +#endif //LL_LLPANELMARKETPLACEINBOX_H diff --git a/indra/newview/llpanelmarketplaceinboxinventory.cpp b/indra/newview/llpanelmarketplaceinboxinventory.cpp index 153121d4e8..526462b940 100644 --- a/indra/newview/llpanelmarketplaceinboxinventory.cpp +++ b/indra/newview/llpanelmarketplaceinboxinventory.cpp @@ -1,375 +1,375 @@ -/** - * @file llpanelmarketplaceinboxinventory.cpp - * @brief LLInboxInventoryPanel class definition - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelmarketplaceinboxinventory.h" - -#include "llfolderview.h" -#include "llfolderviewitem.h" -#include "llfolderviewmodel.h" -#include "llinventorybridge.h" -#include "llinventoryfunctions.h" -#include "llpanellandmarks.h" -#include "llplacesinventorybridge.h" -#include "llviewerfoldertype.h" -#include "llsdserialize.h" - - -#define DEBUGGING_FRESHNESS 0 - -const LLColor4U DEFAULT_WHITE(255, 255, 255); - -const std::string NEW_INBOX_FILENAME("inbox_new_items.xml"); - -// -// statics -// - -static LLDefaultChildRegistry::Register r1("inbox_inventory_panel"); -static LLDefaultChildRegistry::Register r2("inbox_folder_view_folder"); -static LLDefaultChildRegistry::Register r3("inbox_folder_view_item"); - - -// -// LLInboxInventoryPanel Implementation -// - -LLInboxInventoryPanel::LLInboxInventoryPanel(const LLInboxInventoryPanel::Params& p) -: LLInventoryPanel(p) -{ - LLInboxNewItemsStorage::getInstance()->load(); - LLInboxNewItemsStorage::getInstance()->addInboxPanel(this); -} - -LLInboxInventoryPanel::~LLInboxInventoryPanel() -{ - LLInboxNewItemsStorage::getInstance()->removeInboxPanel(this); -} - -void LLInboxInventoryPanel::initFromParams(const LLInventoryPanel::Params& params) -{ - LLInventoryPanel::initFromParams(params); - getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() | (1ULL << LLFolderType::FT_INBOX)); -} - -LLFolderViewFolder * LLInboxInventoryPanel::createFolderViewFolder(LLInvFVBridge * bridge, bool allow_drop) -{ - LLUIColor item_color = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE); - - LLInboxFolderViewFolder::Params params; - - params.name = bridge->getDisplayName(); - params.root = mFolderRoot.get(); - params.listener = bridge; - params.tool_tip = params.name; - params.font_color = item_color; - params.font_highlight_color = item_color; - params.allow_drop = allow_drop; - - return LLUICtrlFactory::create(params); -} - -LLFolderViewItem * LLInboxInventoryPanel::createFolderViewItem(LLInvFVBridge * bridge) -{ - LLUIColor item_color = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE); - - LLInboxFolderViewItem::Params params; - - params.name = bridge->getDisplayName(); - params.creation_date = bridge->getCreationDate(); - params.root = mFolderRoot.get(); - params.listener = bridge; - params.rect = LLRect (0, 0, 0, 0); - params.tool_tip = params.name; - params.font_color = item_color; - params.font_highlight_color = item_color; - - return LLUICtrlFactory::create(params); -} - -void LLInboxInventoryPanel::onRemoveItemFreshness(const LLUUID& item_id) -{ - LLInboxFolderViewFolder* inbox_folder_view = dynamic_cast(getFolderByID(item_id)); - if(inbox_folder_view) - { - inbox_folder_view->setFresh(false); - } - - LLInboxFolderViewItem* inbox_item_view = dynamic_cast(getItemByID(item_id)); - if(inbox_item_view) - { - inbox_item_view->setFresh(false); - } -} - -// -// LLInboxFolderViewFolder Implementation -// - -LLInboxFolderViewFolder::LLInboxFolderViewFolder(const Params& p) -: LLFolderViewFolder(p), - LLBadgeOwner(getHandle()), - mFresh(false) -{ - initBadgeParams(p.new_badge()); -} - -void LLInboxFolderViewFolder::addItem(LLFolderViewItem* item) -{ - LLFolderViewFolder::addItem(item); - - if(item) - { - LLInvFVBridge* itemBridge = static_cast(item->getViewModelItem()); - LLFolderBridge * bridge = static_cast(getViewModelItem()); - bridge->updateHierarchyCreationDate(itemBridge->getCreationDate()); - } - - // Compute freshness if our parent is the root folder for the inbox - if ((mParentFolder == mRoot) && !mFresh) - { - computeFreshness(); - } -} - -// virtual -void LLInboxFolderViewFolder::draw() -{ - if (!hasBadgeHolderParent()) - { - addBadgeToParentHolder(); - setDrawBadgeAtTop(true); - } - - setBadgeVisibility(mFresh); - - LLFolderViewFolder::draw(); - - if (mFresh) - { - reshapeBadge(getRect()); - } - -} - -bool LLInboxFolderViewFolder::handleMouseDown( S32 x, S32 y, MASK mask ) -{ - deFreshify(); - return LLFolderViewFolder::handleMouseDown(x, y, mask); -} - -bool LLInboxFolderViewFolder::handleDoubleClick( S32 x, S32 y, MASK mask ) -{ - deFreshify(); - return LLFolderViewFolder::handleDoubleClick(x, y, mask); -} - -void LLInboxFolderViewFolder::selectItem() -{ - deFreshify(); - LLFolderViewFolder::selectItem(); -} - -void LLInboxFolderViewFolder::computeFreshness() -{ - LLFolderViewModelItemInventory* view_model = static_cast(getViewModelItem()); - const U32 last_expansion_utc = gSavedPerAccountSettings.getU32("LastInventoryInboxActivity"); - - if (last_expansion_utc > 0) - { - mFresh = (view_model->getCreationDate() > last_expansion_utc) || LLInboxNewItemsStorage::getInstance()->isItemFresh(view_model->getUUID()); - -#if DEBUGGING_FRESHNESS - if (mFresh) - { - LL_INFOS() << "Item is fresh! -- creation " << mCreationDate << ", saved_freshness_date " << last_expansion_utc << LL_ENDL; - } -#endif - } - else - { - mFresh = true; - } - - if (mFresh) - { - LLInboxNewItemsStorage::getInstance()->addFreshItem(view_model->getUUID()); - } -} - -void LLInboxFolderViewFolder::deFreshify() -{ - mFresh = false; - - gSavedPerAccountSettings.setU32("LastInventoryInboxActivity", time_corrected()); - LLInboxNewItemsStorage::getInstance()->removeItem(static_cast(getViewModelItem())->getUUID()); -} - -// -// LLInboxFolderViewItem Implementation -// - -LLInboxFolderViewItem::LLInboxFolderViewItem(const Params& p) - : LLFolderViewItem(p) - , LLBadgeOwner(getHandle()) - , mFresh(false) -{ - initBadgeParams(p.new_badge()); -} - -void LLInboxFolderViewItem::addToFolder(LLFolderViewFolder* folder) -{ - LLFolderViewItem::addToFolder(folder); - - // Compute freshness if our parent is the root folder for the inbox - if (mParentFolder == mRoot) - { - computeFreshness(); - } -} - -bool LLInboxFolderViewItem::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - deFreshify(); - - return LLFolderViewItem::handleDoubleClick(x, y, mask); -} - -// virtual -void LLInboxFolderViewItem::draw() -{ - if (!hasBadgeHolderParent()) - { - addBadgeToParentHolder(); - } - - setBadgeVisibility(mFresh); - - LLFolderViewItem::draw(); -} - -void LLInboxFolderViewItem::selectItem() -{ - deFreshify(); - - LLFolderViewItem::selectItem(); -} - -void LLInboxFolderViewItem::computeFreshness() -{ - const U32 last_expansion_utc = gSavedPerAccountSettings.getU32("LastInventoryInboxActivity"); - - if (last_expansion_utc > 0) - { - mFresh = (static_cast(getViewModelItem())->getCreationDate() > last_expansion_utc); - -#if DEBUGGING_FRESHNESS - if (mFresh) - { - LL_INFOS() << "Item is fresh! -- creation " << mCreationDate << ", saved_freshness_date " << last_expansion_utc << LL_ENDL; - } -#endif - } - else - { - mFresh = true; - } -} - -void LLInboxFolderViewItem::deFreshify() -{ - mFresh = false; - - gSavedPerAccountSettings.setU32("LastInventoryInboxActivity", time_corrected()); -} - -LLInboxNewItemsStorage::LLInboxNewItemsStorage() -{ -} - -// static -void LLInboxNewItemsStorage::destroyClass() -{ - LLInboxNewItemsStorage::getInstance()->saveNewItemsIds(); -} - -void LLInboxNewItemsStorage::saveNewItemsIds() -{ - std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, NEW_INBOX_FILENAME); - if (!filename.empty()) - { - LLSD uuids_data; - for (std::set::const_iterator it = mNewItemsIDs.begin(); it != mNewItemsIDs.end(); it++) - { - uuids_data.append((*it)); - } - - llofstream file; - file.open(filename.c_str()); - if ( file.is_open() ) - { - LLSDSerialize::toPrettyXML(uuids_data, file); - file.close(); - } - } -} - -void LLInboxNewItemsStorage::load() -{ - std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, NEW_INBOX_FILENAME); - if (!filename.empty()) - { - llifstream in_file; - in_file.open(filename.c_str()); - - LLSD uuids_data; - if (in_file.is_open()) - { - LLSDSerialize::fromXML(uuids_data, in_file); - in_file.close(); - for (LLSD::array_iterator i = uuids_data.beginArray(); i != uuids_data.endArray(); ++i) - { - mNewItemsIDs.insert((*i).asUUID()); - } - } - } -} - -void LLInboxNewItemsStorage::removeItem(const LLUUID& id) -{ - mNewItemsIDs.erase(id); - - //notify inbox panels - for (auto inbox : mInboxPanels) - { - if(inbox) - { - inbox->onRemoveItemFreshness(id); - } - } -} -// eof +/** + * @file llpanelmarketplaceinboxinventory.cpp + * @brief LLInboxInventoryPanel class definition + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelmarketplaceinboxinventory.h" + +#include "llfolderview.h" +#include "llfolderviewitem.h" +#include "llfolderviewmodel.h" +#include "llinventorybridge.h" +#include "llinventoryfunctions.h" +#include "llpanellandmarks.h" +#include "llplacesinventorybridge.h" +#include "llviewerfoldertype.h" +#include "llsdserialize.h" + + +#define DEBUGGING_FRESHNESS 0 + +const LLColor4U DEFAULT_WHITE(255, 255, 255); + +const std::string NEW_INBOX_FILENAME("inbox_new_items.xml"); + +// +// statics +// + +static LLDefaultChildRegistry::Register r1("inbox_inventory_panel"); +static LLDefaultChildRegistry::Register r2("inbox_folder_view_folder"); +static LLDefaultChildRegistry::Register r3("inbox_folder_view_item"); + + +// +// LLInboxInventoryPanel Implementation +// + +LLInboxInventoryPanel::LLInboxInventoryPanel(const LLInboxInventoryPanel::Params& p) +: LLInventoryPanel(p) +{ + LLInboxNewItemsStorage::getInstance()->load(); + LLInboxNewItemsStorage::getInstance()->addInboxPanel(this); +} + +LLInboxInventoryPanel::~LLInboxInventoryPanel() +{ + LLInboxNewItemsStorage::getInstance()->removeInboxPanel(this); +} + +void LLInboxInventoryPanel::initFromParams(const LLInventoryPanel::Params& params) +{ + LLInventoryPanel::initFromParams(params); + getFilter().setFilterCategoryTypes(getFilter().getFilterCategoryTypes() | (1ULL << LLFolderType::FT_INBOX)); +} + +LLFolderViewFolder * LLInboxInventoryPanel::createFolderViewFolder(LLInvFVBridge * bridge, bool allow_drop) +{ + LLUIColor item_color = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE); + + LLInboxFolderViewFolder::Params params; + + params.name = bridge->getDisplayName(); + params.root = mFolderRoot.get(); + params.listener = bridge; + params.tool_tip = params.name; + params.font_color = item_color; + params.font_highlight_color = item_color; + params.allow_drop = allow_drop; + + return LLUICtrlFactory::create(params); +} + +LLFolderViewItem * LLInboxInventoryPanel::createFolderViewItem(LLInvFVBridge * bridge) +{ + LLUIColor item_color = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE); + + LLInboxFolderViewItem::Params params; + + params.name = bridge->getDisplayName(); + params.creation_date = bridge->getCreationDate(); + params.root = mFolderRoot.get(); + params.listener = bridge; + params.rect = LLRect (0, 0, 0, 0); + params.tool_tip = params.name; + params.font_color = item_color; + params.font_highlight_color = item_color; + + return LLUICtrlFactory::create(params); +} + +void LLInboxInventoryPanel::onRemoveItemFreshness(const LLUUID& item_id) +{ + LLInboxFolderViewFolder* inbox_folder_view = dynamic_cast(getFolderByID(item_id)); + if(inbox_folder_view) + { + inbox_folder_view->setFresh(false); + } + + LLInboxFolderViewItem* inbox_item_view = dynamic_cast(getItemByID(item_id)); + if(inbox_item_view) + { + inbox_item_view->setFresh(false); + } +} + +// +// LLInboxFolderViewFolder Implementation +// + +LLInboxFolderViewFolder::LLInboxFolderViewFolder(const Params& p) +: LLFolderViewFolder(p), + LLBadgeOwner(getHandle()), + mFresh(false) +{ + initBadgeParams(p.new_badge()); +} + +void LLInboxFolderViewFolder::addItem(LLFolderViewItem* item) +{ + LLFolderViewFolder::addItem(item); + + if(item) + { + LLInvFVBridge* itemBridge = static_cast(item->getViewModelItem()); + LLFolderBridge * bridge = static_cast(getViewModelItem()); + bridge->updateHierarchyCreationDate(itemBridge->getCreationDate()); + } + + // Compute freshness if our parent is the root folder for the inbox + if ((mParentFolder == mRoot) && !mFresh) + { + computeFreshness(); + } +} + +// virtual +void LLInboxFolderViewFolder::draw() +{ + if (!hasBadgeHolderParent()) + { + addBadgeToParentHolder(); + setDrawBadgeAtTop(true); + } + + setBadgeVisibility(mFresh); + + LLFolderViewFolder::draw(); + + if (mFresh) + { + reshapeBadge(getRect()); + } + +} + +bool LLInboxFolderViewFolder::handleMouseDown( S32 x, S32 y, MASK mask ) +{ + deFreshify(); + return LLFolderViewFolder::handleMouseDown(x, y, mask); +} + +bool LLInboxFolderViewFolder::handleDoubleClick( S32 x, S32 y, MASK mask ) +{ + deFreshify(); + return LLFolderViewFolder::handleDoubleClick(x, y, mask); +} + +void LLInboxFolderViewFolder::selectItem() +{ + deFreshify(); + LLFolderViewFolder::selectItem(); +} + +void LLInboxFolderViewFolder::computeFreshness() +{ + LLFolderViewModelItemInventory* view_model = static_cast(getViewModelItem()); + const U32 last_expansion_utc = gSavedPerAccountSettings.getU32("LastInventoryInboxActivity"); + + if (last_expansion_utc > 0) + { + mFresh = (view_model->getCreationDate() > last_expansion_utc) || LLInboxNewItemsStorage::getInstance()->isItemFresh(view_model->getUUID()); + +#if DEBUGGING_FRESHNESS + if (mFresh) + { + LL_INFOS() << "Item is fresh! -- creation " << mCreationDate << ", saved_freshness_date " << last_expansion_utc << LL_ENDL; + } +#endif + } + else + { + mFresh = true; + } + + if (mFresh) + { + LLInboxNewItemsStorage::getInstance()->addFreshItem(view_model->getUUID()); + } +} + +void LLInboxFolderViewFolder::deFreshify() +{ + mFresh = false; + + gSavedPerAccountSettings.setU32("LastInventoryInboxActivity", time_corrected()); + LLInboxNewItemsStorage::getInstance()->removeItem(static_cast(getViewModelItem())->getUUID()); +} + +// +// LLInboxFolderViewItem Implementation +// + +LLInboxFolderViewItem::LLInboxFolderViewItem(const Params& p) + : LLFolderViewItem(p) + , LLBadgeOwner(getHandle()) + , mFresh(false) +{ + initBadgeParams(p.new_badge()); +} + +void LLInboxFolderViewItem::addToFolder(LLFolderViewFolder* folder) +{ + LLFolderViewItem::addToFolder(folder); + + // Compute freshness if our parent is the root folder for the inbox + if (mParentFolder == mRoot) + { + computeFreshness(); + } +} + +bool LLInboxFolderViewItem::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + deFreshify(); + + return LLFolderViewItem::handleDoubleClick(x, y, mask); +} + +// virtual +void LLInboxFolderViewItem::draw() +{ + if (!hasBadgeHolderParent()) + { + addBadgeToParentHolder(); + } + + setBadgeVisibility(mFresh); + + LLFolderViewItem::draw(); +} + +void LLInboxFolderViewItem::selectItem() +{ + deFreshify(); + + LLFolderViewItem::selectItem(); +} + +void LLInboxFolderViewItem::computeFreshness() +{ + const U32 last_expansion_utc = gSavedPerAccountSettings.getU32("LastInventoryInboxActivity"); + + if (last_expansion_utc > 0) + { + mFresh = (static_cast(getViewModelItem())->getCreationDate() > last_expansion_utc); + +#if DEBUGGING_FRESHNESS + if (mFresh) + { + LL_INFOS() << "Item is fresh! -- creation " << mCreationDate << ", saved_freshness_date " << last_expansion_utc << LL_ENDL; + } +#endif + } + else + { + mFresh = true; + } +} + +void LLInboxFolderViewItem::deFreshify() +{ + mFresh = false; + + gSavedPerAccountSettings.setU32("LastInventoryInboxActivity", time_corrected()); +} + +LLInboxNewItemsStorage::LLInboxNewItemsStorage() +{ +} + +// static +void LLInboxNewItemsStorage::destroyClass() +{ + LLInboxNewItemsStorage::getInstance()->saveNewItemsIds(); +} + +void LLInboxNewItemsStorage::saveNewItemsIds() +{ + std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, NEW_INBOX_FILENAME); + if (!filename.empty()) + { + LLSD uuids_data; + for (std::set::const_iterator it = mNewItemsIDs.begin(); it != mNewItemsIDs.end(); it++) + { + uuids_data.append((*it)); + } + + llofstream file; + file.open(filename.c_str()); + if ( file.is_open() ) + { + LLSDSerialize::toPrettyXML(uuids_data, file); + file.close(); + } + } +} + +void LLInboxNewItemsStorage::load() +{ + std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, NEW_INBOX_FILENAME); + if (!filename.empty()) + { + llifstream in_file; + in_file.open(filename.c_str()); + + LLSD uuids_data; + if (in_file.is_open()) + { + LLSDSerialize::fromXML(uuids_data, in_file); + in_file.close(); + for (LLSD::array_iterator i = uuids_data.beginArray(); i != uuids_data.endArray(); ++i) + { + mNewItemsIDs.insert((*i).asUUID()); + } + } + } +} + +void LLInboxNewItemsStorage::removeItem(const LLUUID& id) +{ + mNewItemsIDs.erase(id); + + //notify inbox panels + for (auto inbox : mInboxPanels) + { + if(inbox) + { + inbox->onRemoveItemFreshness(id); + } + } +} +// eof diff --git a/indra/newview/llpanelmarketplaceinboxinventory.h b/indra/newview/llpanelmarketplaceinboxinventory.h index 64179487c5..0516846138 100644 --- a/indra/newview/llpanelmarketplaceinboxinventory.h +++ b/indra/newview/llpanelmarketplaceinboxinventory.h @@ -1,144 +1,144 @@ -/** - * @file llpanelmarketplaceinboxinventory.h - * @brief LLInboxInventoryPanel class declaration - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_INBOXINVENTORYPANEL_H -#define LL_INBOXINVENTORYPANEL_H - - -#include "llbadgeowner.h" -#include "llinventorypanel.h" -#include "llfolderviewitem.h" - - - - - -class LLInboxInventoryPanel : public LLInventoryPanel -{ -public: - struct Params : public LLInitParam::Block - {}; - - LLInboxInventoryPanel(const Params& p); - ~LLInboxInventoryPanel(); - - // virtual - void initFromParams(const LLInventoryPanel::Params&); - LLFolderViewFolder* createFolderViewFolder(LLInvFVBridge * bridge, bool allow_drop); - LLFolderViewItem * createFolderViewItem(LLInvFVBridge * bridge); - - void onRemoveItemFreshness(const LLUUID& item_id); -}; - - -class LLInboxFolderViewFolder : public LLFolderViewFolder, public LLBadgeOwner -{ -public: - struct Params : public LLInitParam::Block - { - Optional new_badge; - - Params() - : new_badge("new_badge") - {} - }; - - LLInboxFolderViewFolder(const Params& p); - - void addItem(LLFolderViewItem* item); - void draw(); - - bool handleMouseDown(S32 x, S32 y, MASK mask); - bool handleDoubleClick(S32 x, S32 y, MASK mask); - void selectItem(); - - void computeFreshness(); - void deFreshify(); - - bool isFresh() const { return mFresh; } - void setFresh(bool is_fresh) { mFresh = is_fresh; } - -protected: - bool mFresh; -}; - - -class LLInboxFolderViewItem : public LLFolderViewItem, public LLBadgeOwner -{ -public: - struct Params : public LLInitParam::Block - { - Optional new_badge; - - Params() - : new_badge("new_badge") - {} - }; - - LLInboxFolderViewItem(const Params& p); - - void addToFolder(LLFolderViewFolder* folder); - bool handleDoubleClick(S32 x, S32 y, MASK mask); - - void draw(); - - void selectItem(); - - void computeFreshness(); - void deFreshify(); - - bool isFresh() const { return mFresh; } - void setFresh(bool is_fresh) { mFresh = is_fresh; } - -protected: - bool mFresh; -}; - -class LLInboxNewItemsStorage : public LLSingleton - , public LLDestroyClass -{ - LLSINGLETON(LLInboxNewItemsStorage); - LOG_CLASS(LLInboxNewItemsStorage); -public: - static void destroyClass(); - void saveNewItemsIds(); - - void load(); - - void addFreshItem(const LLUUID& id) { mNewItemsIDs.insert(id); } - void removeItem(const LLUUID& id); - bool isItemFresh(const LLUUID& id) { return (mNewItemsIDs.find(id) != mNewItemsIDs.end()); } - - void addInboxPanel(LLInboxInventoryPanel* inbox) { mInboxPanels.insert(inbox); } - void removeInboxPanel(LLInboxInventoryPanel* inbox) { mInboxPanels.erase(inbox); } - -private: - std::set mNewItemsIDs; - - std::set mInboxPanels; -}; - -#endif //LL_INBOXINVENTORYPANEL_H +/** + * @file llpanelmarketplaceinboxinventory.h + * @brief LLInboxInventoryPanel class declaration + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_INBOXINVENTORYPANEL_H +#define LL_INBOXINVENTORYPANEL_H + + +#include "llbadgeowner.h" +#include "llinventorypanel.h" +#include "llfolderviewitem.h" + + + + + +class LLInboxInventoryPanel : public LLInventoryPanel +{ +public: + struct Params : public LLInitParam::Block + {}; + + LLInboxInventoryPanel(const Params& p); + ~LLInboxInventoryPanel(); + + // virtual + void initFromParams(const LLInventoryPanel::Params&); + LLFolderViewFolder* createFolderViewFolder(LLInvFVBridge * bridge, bool allow_drop); + LLFolderViewItem * createFolderViewItem(LLInvFVBridge * bridge); + + void onRemoveItemFreshness(const LLUUID& item_id); +}; + + +class LLInboxFolderViewFolder : public LLFolderViewFolder, public LLBadgeOwner +{ +public: + struct Params : public LLInitParam::Block + { + Optional new_badge; + + Params() + : new_badge("new_badge") + {} + }; + + LLInboxFolderViewFolder(const Params& p); + + void addItem(LLFolderViewItem* item); + void draw(); + + bool handleMouseDown(S32 x, S32 y, MASK mask); + bool handleDoubleClick(S32 x, S32 y, MASK mask); + void selectItem(); + + void computeFreshness(); + void deFreshify(); + + bool isFresh() const { return mFresh; } + void setFresh(bool is_fresh) { mFresh = is_fresh; } + +protected: + bool mFresh; +}; + + +class LLInboxFolderViewItem : public LLFolderViewItem, public LLBadgeOwner +{ +public: + struct Params : public LLInitParam::Block + { + Optional new_badge; + + Params() + : new_badge("new_badge") + {} + }; + + LLInboxFolderViewItem(const Params& p); + + void addToFolder(LLFolderViewFolder* folder); + bool handleDoubleClick(S32 x, S32 y, MASK mask); + + void draw(); + + void selectItem(); + + void computeFreshness(); + void deFreshify(); + + bool isFresh() const { return mFresh; } + void setFresh(bool is_fresh) { mFresh = is_fresh; } + +protected: + bool mFresh; +}; + +class LLInboxNewItemsStorage : public LLSingleton + , public LLDestroyClass +{ + LLSINGLETON(LLInboxNewItemsStorage); + LOG_CLASS(LLInboxNewItemsStorage); +public: + static void destroyClass(); + void saveNewItemsIds(); + + void load(); + + void addFreshItem(const LLUUID& id) { mNewItemsIDs.insert(id); } + void removeItem(const LLUUID& id); + bool isItemFresh(const LLUUID& id) { return (mNewItemsIDs.find(id) != mNewItemsIDs.end()); } + + void addInboxPanel(LLInboxInventoryPanel* inbox) { mInboxPanels.insert(inbox); } + void removeInboxPanel(LLInboxInventoryPanel* inbox) { mInboxPanels.erase(inbox); } + +private: + std::set mNewItemsIDs; + + std::set mInboxPanels; +}; + +#endif //LL_INBOXINVENTORYPANEL_H diff --git a/indra/newview/llpanelmediasettingsgeneral.cpp b/indra/newview/llpanelmediasettingsgeneral.cpp index 85c173a418..380c2827ac 100644 --- a/indra/newview/llpanelmediasettingsgeneral.cpp +++ b/indra/newview/llpanelmediasettingsgeneral.cpp @@ -1,540 +1,540 @@ -/** - * @file llpanelmediasettingsgeneral.cpp - * @brief LLPanelMediaSettingsGeneral class implementation - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelmediasettingsgeneral.h" - -// library includes -#include "llcombobox.h" -#include "llcheckboxctrl.h" -#include "llnotificationsutil.h" -#include "llspinctrl.h" -#include "lluictrlfactory.h" - -// project includes -#include "llagent.h" -#include "llviewerwindow.h" -#include "llviewermedia.h" -#include "llvovolume.h" -#include "llsdutil.h" -#include "llselectmgr.h" -#include "llbutton.h" -#include "lltexturectrl.h" -#include "llurl.h" -#include "llwindow.h" -#include "llmediaentry.h" -#include "llmediactrl.h" -#include "llpanelcontents.h" -#include "llpermissions.h" -#include "llpluginclassmedia.h" -#include "llfloatermediasettings.h" -#include "llfloatertools.h" -#include "lltrans.h" -#include "lltextbox.h" -#include "llpanelmediasettingssecurity.h" - -const char *CHECKERBOARD_DATA_URL = "data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%22100%%22 height=%22100%%22 %3E%3Cdefs%3E%3Cpattern id=%22checker%22 patternUnits=%22userSpaceOnUse%22 x=%220%22 y=%220%22 width=%22128%22 height=%22128%22 viewBox=%220 0 128 128%22 %3E%3Crect x=%220%22 y=%220%22 width=%2264%22 height=%2264%22 fill=%22#ddddff%22 /%3E%3Crect x=%2264%22 y=%2264%22 width=%2264%22 height=%2264%22 fill=%22#ddddff%22 /%3E%3C/pattern%3E%3C/defs%3E%3Crect x=%220%22 y=%220%22 width=%22100%%22 height=%22100%%22 fill=%22url(#checker)%22 /%3E%3C/svg%3E"; - -//////////////////////////////////////////////////////////////////////////////// -// -LLPanelMediaSettingsGeneral::LLPanelMediaSettingsGeneral() : - mAutoLoop( NULL ), - mFirstClick( NULL ), - mAutoZoom( NULL ), - mAutoPlay( NULL ), - mAutoScale( NULL ), - mWidthPixels( NULL ), - mHeightPixels( NULL ), - mHomeURL( NULL ), - mCurrentURL( NULL ), - mParent( NULL ), - mMediaEditable(false) -{ - // build dialog from XML - buildFromFile( "panel_media_settings_general.xml"); -} - -//////////////////////////////////////////////////////////////////////////////// -// -bool LLPanelMediaSettingsGeneral::postBuild() -{ - // connect member vars with UI widgets - mAutoLoop = getChild< LLCheckBoxCtrl >( LLMediaEntry::AUTO_LOOP_KEY ); - mAutoPlay = getChild< LLCheckBoxCtrl >( LLMediaEntry::AUTO_PLAY_KEY ); - mAutoScale = getChild< LLCheckBoxCtrl >( LLMediaEntry::AUTO_SCALE_KEY ); - mAutoZoom = getChild< LLCheckBoxCtrl >( LLMediaEntry::AUTO_ZOOM_KEY ); - mCurrentURL = getChild< LLTextBox >( LLMediaEntry::CURRENT_URL_KEY ); - mFirstClick = getChild< LLCheckBoxCtrl >( LLMediaEntry::FIRST_CLICK_INTERACT_KEY ); - mHeightPixels = getChild< LLSpinCtrl >( LLMediaEntry::HEIGHT_PIXELS_KEY ); - mHomeURL = getChild< LLLineEditor >( LLMediaEntry::HOME_URL_KEY ); - mWidthPixels = getChild< LLSpinCtrl >( LLMediaEntry::WIDTH_PIXELS_KEY ); - mPreviewMedia = getChild("preview_media"); - mFailWhiteListText = getChild( "home_fails_whitelist_label" ); - - // watch commit action for HOME URL - childSetCommitCallback( LLMediaEntry::HOME_URL_KEY, onCommitHomeURL, this); - childSetCommitCallback( "current_url_reset_btn",onBtnResetCurrentUrl, this); - - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -// virtual -LLPanelMediaSettingsGeneral::~LLPanelMediaSettingsGeneral() -{ -} - -//////////////////////////////////////////////////////////////////////////////// -// static -void LLPanelMediaSettingsGeneral::draw() -{ - // housekeeping - LLPanel::draw(); - - // TODO: we need to call this repeatedly until the floater panels are fully - // created but once we have a valid answer, we should stop looking here - the - // commit callback will handle it - checkHomeUrlPassesWhitelist(); - - // enable/disable pixel values image entry based on auto scale checkbox - if (!mAutoScale->getValue().asBoolean()) - { - getChildView( LLMediaEntry::WIDTH_PIXELS_KEY )->setEnabled( true ); - getChildView( LLMediaEntry::HEIGHT_PIXELS_KEY )->setEnabled( true ); - } - else - { - getChildView( LLMediaEntry::WIDTH_PIXELS_KEY )->setEnabled( false ); - getChildView( LLMediaEntry::HEIGHT_PIXELS_KEY )->setEnabled( false ); - }; - - // enable/disable UI based on type of media - bool reset_button_is_active = true; - if (mPreviewMedia) - { - if (LLPluginClassMedia* media_plugin = mPreviewMedia->getMediaPlugin()) - { - // turn off volume (if we can) for preview. Note: this really only - // works for QuickTime movies right now - no way to control the - // volume of a flash app embedded in a page for example - media_plugin->setVolume( 0 ); - - // some controls are only appropriate for time or browser type plugins - // so we selectively enable/disable them - need to do it in draw - // because the information from plugins arrives assynchronously - if (media_plugin->pluginSupportsMediaTime()) - { - getChildView( LLMediaEntry::CURRENT_URL_KEY )->setEnabled( false ); - reset_button_is_active = false; - getChildView("current_url_label")->setEnabled(false ); - getChildView( LLMediaEntry::AUTO_LOOP_KEY )->setEnabled( true ); - } - else - { - getChildView( LLMediaEntry::CURRENT_URL_KEY )->setEnabled( true ); - reset_button_is_active = true; - getChildView("current_url_label")->setEnabled(true ); - getChildView( LLMediaEntry::AUTO_LOOP_KEY )->setEnabled( false ); - }; - }; - }; - - // current URL can change over time, update it here - updateCurrentUrl(); - - LLPermissions perm; - bool user_can_press_reset = mMediaEditable; - - // several places modify this widget so we must collect states in one place - if ( reset_button_is_active ) - { - // user has perms to press reset button and it is active - if ( user_can_press_reset ) - { - getChildView("current_url_reset_btn")->setEnabled(true ); - } - // user does not has perms to press reset button and it is active - else - { - getChildView("current_url_reset_btn")->setEnabled(false ); - }; - } - else - // reset button is inactive so we just slam it to off - other states don't matter - { - getChildView("current_url_reset_btn")->setEnabled(false ); - }; -} - -//////////////////////////////////////////////////////////////////////////////// -// static -void LLPanelMediaSettingsGeneral::clearValues( void* userdata, bool editable, bool update_preview) -{ - LLPanelMediaSettingsGeneral *self =(LLPanelMediaSettingsGeneral *)userdata; - self->mAutoLoop->clear(); - self->mAutoPlay->clear(); - self->mAutoScale->clear(); - self->mAutoZoom ->clear(); - self->mCurrentURL->clear(); - self->mFirstClick->clear(); - self->mHeightPixels->clear(); - self->mHomeURL->clear(); - self->mWidthPixels->clear(); - self->mAutoLoop ->setEnabled(editable); - self->mAutoPlay ->setEnabled(editable); - self->mAutoScale ->setEnabled(editable); - self->mAutoZoom ->setEnabled(editable); - self->mCurrentURL ->setEnabled(editable); - self->mFirstClick ->setEnabled(editable); - self->mHeightPixels ->setEnabled(editable); - self->mHomeURL ->setEnabled(editable); - self->mWidthPixels ->setEnabled(editable); - if (update_preview) - { - self->updateMediaPreview(); - } -} - -// static -bool LLPanelMediaSettingsGeneral::isMultiple() -{ - // IF all the faces have media (or all dont have media) - if ( LLFloaterMediaSettings::getInstance()->mIdenticalHasMediaInfo ) - { - if(LLFloaterMediaSettings::getInstance()->mMultipleMedia) - { - return true; - } - - } - else - { - if(LLFloaterMediaSettings::getInstance()->mMultipleValidMedia) - { - return true; - } - } - return false; -} - -//////////////////////////////////////////////////////////////////////////////// -// static -void LLPanelMediaSettingsGeneral::initValues( void* userdata, const LLSD& _media_settings, bool editable) -{ - LLPanelMediaSettingsGeneral *self =(LLPanelMediaSettingsGeneral *)userdata; - self->mMediaEditable = editable; - - LLSD media_settings = _media_settings; - - if ( LLPanelMediaSettingsGeneral::isMultiple() ) - { - // *HACK: "edit" the incoming media_settings - media_settings[LLMediaEntry::CURRENT_URL_KEY] = LLTrans::getString("Multiple Media"); - media_settings[LLMediaEntry::HOME_URL_KEY] = LLTrans::getString("Multiple Media"); - } - - std::string base_key( "" ); - std::string tentative_key( "" ); - - struct - { - std::string key_name; - LLUICtrl* ctrl_ptr; - std::string ctrl_type; - - } data_set [] = - { - { LLMediaEntry::AUTO_LOOP_KEY, self->mAutoLoop, "LLCheckBoxCtrl" }, - { LLMediaEntry::AUTO_PLAY_KEY, self->mAutoPlay, "LLCheckBoxCtrl" }, - { LLMediaEntry::AUTO_SCALE_KEY, self->mAutoScale, "LLCheckBoxCtrl" }, - { LLMediaEntry::AUTO_ZOOM_KEY, self->mAutoZoom, "LLCheckBoxCtrl" }, - { LLMediaEntry::CURRENT_URL_KEY, self->mCurrentURL, "LLTextBox" }, - { LLMediaEntry::HEIGHT_PIXELS_KEY, self->mHeightPixels, "LLSpinCtrl" }, - { LLMediaEntry::HOME_URL_KEY, self->mHomeURL, "LLLineEditor" }, - { LLMediaEntry::FIRST_CLICK_INTERACT_KEY, self->mFirstClick, "LLCheckBoxCtrl" }, - { LLMediaEntry::WIDTH_PIXELS_KEY, self->mWidthPixels, "LLSpinCtrl" }, - { "", NULL , "" } - }; - - for( int i = 0; data_set[ i ].key_name.length() > 0; ++i ) - { - base_key = std::string( data_set[ i ].key_name ); - tentative_key = base_key + std::string( LLPanelContents::TENTATIVE_SUFFIX ); - // TODO: CP - I bet there is a better way to do this using Boost - if ( media_settings[ base_key ].isDefined() ) - { - if ( data_set[ i ].ctrl_type == "LLLineEditor" ) - { - static_cast< LLLineEditor* >( data_set[ i ].ctrl_ptr )-> - setText( media_settings[ base_key ].asString() ); - } - else - if ( data_set[ i ].ctrl_type == "LLCheckBoxCtrl" ) - static_cast< LLCheckBoxCtrl* >( data_set[ i ].ctrl_ptr )-> - setValue( media_settings[ base_key ].asBoolean() ); - else - if ( data_set[ i ].ctrl_type == "LLComboBox" ) - static_cast< LLComboBox* >( data_set[ i ].ctrl_ptr )-> - setCurrentByIndex( media_settings[ base_key ].asInteger() ); - else - if ( data_set[ i ].ctrl_type == "LLSpinCtrl" ) - static_cast< LLSpinCtrl* >( data_set[ i ].ctrl_ptr )-> - setValue( media_settings[ base_key ].asInteger() ); - - data_set[ i ].ctrl_ptr->setEnabled(self->mMediaEditable); - data_set[ i ].ctrl_ptr->setTentative( media_settings[ tentative_key ].asBoolean() ); - }; - }; -} - -//////////////////////////////////////////////////////////////////////////////// -// Helper to set media control to media URL as required -void LLPanelMediaSettingsGeneral::updateMediaPreview() -{ - if(LLTrans::getString("Multiple Media") == mHomeURL->getValue().asString()) - { - return; - } - if ( mHomeURL->getValue().asString().length() > 0 ) - { - if(mPreviewMedia->getCurrentNavUrl() != mHomeURL->getValue().asString()) - { - mPreviewMedia->navigateTo( mHomeURL->getValue().asString() ); - } - } - else - // new home URL will be empty if media is deleted so display a - // "preview goes here" data url page - { - if(mPreviewMedia->getCurrentNavUrl() != CHECKERBOARD_DATA_URL) - { - mPreviewMedia->navigateTo( CHECKERBOARD_DATA_URL ); - } - }; -} - -//////////////////////////////////////////////////////////////////////////////// - -// virtual -void LLPanelMediaSettingsGeneral::onClose(bool app_quitting) -{ - if(mPreviewMedia) - { - mPreviewMedia->unloadMediaSource(); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLPanelMediaSettingsGeneral::checkHomeUrlPassesWhitelist() -{ - // parent floater has not constructed the security panel yet - if ( mParent->getPanelSecurity() == 0 ) - return; - - std::string home_url = getHomeUrl(); - if ( home_url.empty() || mParent->getPanelSecurity()->urlPassesWhiteList( home_url ) ) - { - // Home URL is empty or passes the white list so hide the warning message - mFailWhiteListText->setVisible( false ); - } - else - { - // Home URL does not pass the white list so show the warning message - mFailWhiteListText->setVisible( true ); - }; -} - -//////////////////////////////////////////////////////////////////////////////// -// static -void LLPanelMediaSettingsGeneral::onCommitHomeURL( LLUICtrl* ctrl, void *userdata ) -{ - LLPanelMediaSettingsGeneral* self =(LLPanelMediaSettingsGeneral *)userdata; - - // check home url passes whitelist and display warning if not - self->checkHomeUrlPassesWhitelist(); - - self->updateMediaPreview(); -} - -//////////////////////////////////////////////////////////////////////////////// -// static -void LLPanelMediaSettingsGeneral::onBtnResetCurrentUrl(LLUICtrl* ctrl, void *userdata) -{ - LLPanelMediaSettingsGeneral* self =(LLPanelMediaSettingsGeneral *)userdata; - self->navigateHomeSelectedFace(false); -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLPanelMediaSettingsGeneral::preApply() -{ - // Make sure the home URL entry is committed - mHomeURL->onCommit(); -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLPanelMediaSettingsGeneral::getValues( LLSD &fill_me_in, bool include_tentative ) -{ - if (include_tentative || !mAutoLoop->getTentative()) fill_me_in[LLMediaEntry::AUTO_LOOP_KEY] = (LLSD::Boolean)mAutoLoop->getValue(); - if (include_tentative || !mAutoPlay->getTentative()) fill_me_in[LLMediaEntry::AUTO_PLAY_KEY] = (LLSD::Boolean)mAutoPlay->getValue(); - if (include_tentative || !mAutoScale->getTentative()) fill_me_in[LLMediaEntry::AUTO_SCALE_KEY] = (LLSD::Boolean)mAutoScale->getValue(); - if (include_tentative || !mAutoZoom->getTentative()) fill_me_in[LLMediaEntry::AUTO_ZOOM_KEY] = (LLSD::Boolean)mAutoZoom->getValue(); - //Don't fill in current URL: this is only supposed to get changed via navigate - // if (include_tentative || !mCurrentURL->getTentative()) fill_me_in[LLMediaEntry::CURRENT_URL_KEY] = mCurrentURL->getValue(); - if (include_tentative || !mHeightPixels->getTentative()) fill_me_in[LLMediaEntry::HEIGHT_PIXELS_KEY] = (LLSD::Integer)mHeightPixels->getValue(); - // Don't fill in the home URL if it is the special "Multiple Media" string! - if ((include_tentative || !mHomeURL->getTentative()) - && LLTrans::getString("Multiple Media") != mHomeURL->getValue()) - fill_me_in[LLMediaEntry::HOME_URL_KEY] = (LLSD::String)mHomeURL->getValue(); - if (include_tentative || !mFirstClick->getTentative()) fill_me_in[LLMediaEntry::FIRST_CLICK_INTERACT_KEY] = (LLSD::Boolean)mFirstClick->getValue(); - if (include_tentative || !mWidthPixels->getTentative()) fill_me_in[LLMediaEntry::WIDTH_PIXELS_KEY] = (LLSD::Integer)mWidthPixels->getValue(); -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLPanelMediaSettingsGeneral::postApply() -{ - // Make sure to navigate to the home URL if the current URL is empty and - // autoplay is on - navigateHomeSelectedFace(true); -} - - -//////////////////////////////////////////////////////////////////////////////// -// -void LLPanelMediaSettingsGeneral::setParent( LLFloaterMediaSettings* parent ) -{ - mParent = parent; -}; - -//////////////////////////////////////////////////////////////////////////////// -// -bool LLPanelMediaSettingsGeneral::navigateHomeSelectedFace(bool only_if_current_is_empty) -{ - struct functor_navigate_media : public LLSelectedTEGetFunctor< bool> - { - functor_navigate_media(bool flag) : only_if_current_is_empty(flag) {} - bool get( LLViewerObject* object, S32 face ) - { - if ( object && object->getTE(face) && object->permModify() ) - { - const LLMediaEntry *media_data = object->getTE(face)->getMediaData(); - if ( media_data ) - { - if (!only_if_current_is_empty || (media_data->getCurrentURL().empty() && media_data->getAutoPlay())) - { - viewer_media_t media_impl = - LLViewerMedia::getInstance()->getMediaImplFromTextureID(object->getTE(face)->getMediaData()->getMediaID()); - if (media_impl) - { - media_impl->setPriority(LLPluginClassMedia::PRIORITY_NORMAL); - media_impl->navigateHome(); - - if (!only_if_current_is_empty) - { - LLSD media_data; - media_data[LLMediaEntry::CURRENT_URL_KEY] = std::string(); - object->getTE(face)->mergeIntoMediaData(media_data); - } - return true; - } - } - } - } - return false; - }; - bool only_if_current_is_empty; - - } functor_navigate_media(only_if_current_is_empty); - - bool all_face_media_navigated = false; - LLObjectSelectionHandle selected_objects =LLSelectMgr::getInstance()->getSelection(); - selected_objects->getSelectedTEValue( &functor_navigate_media, all_face_media_navigated ); - - if (all_face_media_navigated) - { - struct functor_sync_to_server : public LLSelectedObjectFunctor - { - virtual bool apply(LLViewerObject* object) - { - LLVOVolume *volume = dynamic_cast(object); - if (volume) - { - volume->sendMediaDataUpdate(); - } - return true; - } - } sendfunc; - selected_objects->applyToObjects(&sendfunc); - } - - // Note: we don't update the 'current URL' field until the media data itself changes - - return all_face_media_navigated; -} - -//////////////////////////////////////////////////////////////////////////////// -// -const std::string LLPanelMediaSettingsGeneral::getHomeUrl() -{ - return mHomeURL->getValue().asString(); -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLPanelMediaSettingsGeneral::updateCurrentUrl() -{ - // Get the current URL from the selection - const LLMediaEntry default_media_data; - std::string value_str = default_media_data.getCurrentURL(); - struct functor_getter_current_url : public LLSelectedTEGetFunctor< std::string > - { - functor_getter_current_url(const LLMediaEntry& entry): mMediaEntry(entry) {} - - std::string get( LLViewerObject* object, S32 face ) - { - if ( object ) - if ( object->getTE(face) ) - if ( object->getTE(face)->getMediaData() ) - return object->getTE(face)->getMediaData()->getCurrentURL(); - return mMediaEntry.getCurrentURL(); - }; - - const LLMediaEntry & mMediaEntry; - - } func_current_url(default_media_data); - bool identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &func_current_url, value_str ); - mCurrentURL->setText(value_str); - mCurrentURL->setTentative(identical); - - if ( LLPanelMediaSettingsGeneral::isMultiple() ) - { - mCurrentURL->setText(LLTrans::getString("Multiple Media")); - } -} +/** + * @file llpanelmediasettingsgeneral.cpp + * @brief LLPanelMediaSettingsGeneral class implementation + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelmediasettingsgeneral.h" + +// library includes +#include "llcombobox.h" +#include "llcheckboxctrl.h" +#include "llnotificationsutil.h" +#include "llspinctrl.h" +#include "lluictrlfactory.h" + +// project includes +#include "llagent.h" +#include "llviewerwindow.h" +#include "llviewermedia.h" +#include "llvovolume.h" +#include "llsdutil.h" +#include "llselectmgr.h" +#include "llbutton.h" +#include "lltexturectrl.h" +#include "llurl.h" +#include "llwindow.h" +#include "llmediaentry.h" +#include "llmediactrl.h" +#include "llpanelcontents.h" +#include "llpermissions.h" +#include "llpluginclassmedia.h" +#include "llfloatermediasettings.h" +#include "llfloatertools.h" +#include "lltrans.h" +#include "lltextbox.h" +#include "llpanelmediasettingssecurity.h" + +const char *CHECKERBOARD_DATA_URL = "data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 width=%22100%%22 height=%22100%%22 %3E%3Cdefs%3E%3Cpattern id=%22checker%22 patternUnits=%22userSpaceOnUse%22 x=%220%22 y=%220%22 width=%22128%22 height=%22128%22 viewBox=%220 0 128 128%22 %3E%3Crect x=%220%22 y=%220%22 width=%2264%22 height=%2264%22 fill=%22#ddddff%22 /%3E%3Crect x=%2264%22 y=%2264%22 width=%2264%22 height=%2264%22 fill=%22#ddddff%22 /%3E%3C/pattern%3E%3C/defs%3E%3Crect x=%220%22 y=%220%22 width=%22100%%22 height=%22100%%22 fill=%22url(#checker)%22 /%3E%3C/svg%3E"; + +//////////////////////////////////////////////////////////////////////////////// +// +LLPanelMediaSettingsGeneral::LLPanelMediaSettingsGeneral() : + mAutoLoop( NULL ), + mFirstClick( NULL ), + mAutoZoom( NULL ), + mAutoPlay( NULL ), + mAutoScale( NULL ), + mWidthPixels( NULL ), + mHeightPixels( NULL ), + mHomeURL( NULL ), + mCurrentURL( NULL ), + mParent( NULL ), + mMediaEditable(false) +{ + // build dialog from XML + buildFromFile( "panel_media_settings_general.xml"); +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLPanelMediaSettingsGeneral::postBuild() +{ + // connect member vars with UI widgets + mAutoLoop = getChild< LLCheckBoxCtrl >( LLMediaEntry::AUTO_LOOP_KEY ); + mAutoPlay = getChild< LLCheckBoxCtrl >( LLMediaEntry::AUTO_PLAY_KEY ); + mAutoScale = getChild< LLCheckBoxCtrl >( LLMediaEntry::AUTO_SCALE_KEY ); + mAutoZoom = getChild< LLCheckBoxCtrl >( LLMediaEntry::AUTO_ZOOM_KEY ); + mCurrentURL = getChild< LLTextBox >( LLMediaEntry::CURRENT_URL_KEY ); + mFirstClick = getChild< LLCheckBoxCtrl >( LLMediaEntry::FIRST_CLICK_INTERACT_KEY ); + mHeightPixels = getChild< LLSpinCtrl >( LLMediaEntry::HEIGHT_PIXELS_KEY ); + mHomeURL = getChild< LLLineEditor >( LLMediaEntry::HOME_URL_KEY ); + mWidthPixels = getChild< LLSpinCtrl >( LLMediaEntry::WIDTH_PIXELS_KEY ); + mPreviewMedia = getChild("preview_media"); + mFailWhiteListText = getChild( "home_fails_whitelist_label" ); + + // watch commit action for HOME URL + childSetCommitCallback( LLMediaEntry::HOME_URL_KEY, onCommitHomeURL, this); + childSetCommitCallback( "current_url_reset_btn",onBtnResetCurrentUrl, this); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// virtual +LLPanelMediaSettingsGeneral::~LLPanelMediaSettingsGeneral() +{ +} + +//////////////////////////////////////////////////////////////////////////////// +// static +void LLPanelMediaSettingsGeneral::draw() +{ + // housekeeping + LLPanel::draw(); + + // TODO: we need to call this repeatedly until the floater panels are fully + // created but once we have a valid answer, we should stop looking here - the + // commit callback will handle it + checkHomeUrlPassesWhitelist(); + + // enable/disable pixel values image entry based on auto scale checkbox + if (!mAutoScale->getValue().asBoolean()) + { + getChildView( LLMediaEntry::WIDTH_PIXELS_KEY )->setEnabled( true ); + getChildView( LLMediaEntry::HEIGHT_PIXELS_KEY )->setEnabled( true ); + } + else + { + getChildView( LLMediaEntry::WIDTH_PIXELS_KEY )->setEnabled( false ); + getChildView( LLMediaEntry::HEIGHT_PIXELS_KEY )->setEnabled( false ); + }; + + // enable/disable UI based on type of media + bool reset_button_is_active = true; + if (mPreviewMedia) + { + if (LLPluginClassMedia* media_plugin = mPreviewMedia->getMediaPlugin()) + { + // turn off volume (if we can) for preview. Note: this really only + // works for QuickTime movies right now - no way to control the + // volume of a flash app embedded in a page for example + media_plugin->setVolume( 0 ); + + // some controls are only appropriate for time or browser type plugins + // so we selectively enable/disable them - need to do it in draw + // because the information from plugins arrives assynchronously + if (media_plugin->pluginSupportsMediaTime()) + { + getChildView( LLMediaEntry::CURRENT_URL_KEY )->setEnabled( false ); + reset_button_is_active = false; + getChildView("current_url_label")->setEnabled(false ); + getChildView( LLMediaEntry::AUTO_LOOP_KEY )->setEnabled( true ); + } + else + { + getChildView( LLMediaEntry::CURRENT_URL_KEY )->setEnabled( true ); + reset_button_is_active = true; + getChildView("current_url_label")->setEnabled(true ); + getChildView( LLMediaEntry::AUTO_LOOP_KEY )->setEnabled( false ); + }; + }; + }; + + // current URL can change over time, update it here + updateCurrentUrl(); + + LLPermissions perm; + bool user_can_press_reset = mMediaEditable; + + // several places modify this widget so we must collect states in one place + if ( reset_button_is_active ) + { + // user has perms to press reset button and it is active + if ( user_can_press_reset ) + { + getChildView("current_url_reset_btn")->setEnabled(true ); + } + // user does not has perms to press reset button and it is active + else + { + getChildView("current_url_reset_btn")->setEnabled(false ); + }; + } + else + // reset button is inactive so we just slam it to off - other states don't matter + { + getChildView("current_url_reset_btn")->setEnabled(false ); + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// static +void LLPanelMediaSettingsGeneral::clearValues( void* userdata, bool editable, bool update_preview) +{ + LLPanelMediaSettingsGeneral *self =(LLPanelMediaSettingsGeneral *)userdata; + self->mAutoLoop->clear(); + self->mAutoPlay->clear(); + self->mAutoScale->clear(); + self->mAutoZoom ->clear(); + self->mCurrentURL->clear(); + self->mFirstClick->clear(); + self->mHeightPixels->clear(); + self->mHomeURL->clear(); + self->mWidthPixels->clear(); + self->mAutoLoop ->setEnabled(editable); + self->mAutoPlay ->setEnabled(editable); + self->mAutoScale ->setEnabled(editable); + self->mAutoZoom ->setEnabled(editable); + self->mCurrentURL ->setEnabled(editable); + self->mFirstClick ->setEnabled(editable); + self->mHeightPixels ->setEnabled(editable); + self->mHomeURL ->setEnabled(editable); + self->mWidthPixels ->setEnabled(editable); + if (update_preview) + { + self->updateMediaPreview(); + } +} + +// static +bool LLPanelMediaSettingsGeneral::isMultiple() +{ + // IF all the faces have media (or all dont have media) + if ( LLFloaterMediaSettings::getInstance()->mIdenticalHasMediaInfo ) + { + if(LLFloaterMediaSettings::getInstance()->mMultipleMedia) + { + return true; + } + + } + else + { + if(LLFloaterMediaSettings::getInstance()->mMultipleValidMedia) + { + return true; + } + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// static +void LLPanelMediaSettingsGeneral::initValues( void* userdata, const LLSD& _media_settings, bool editable) +{ + LLPanelMediaSettingsGeneral *self =(LLPanelMediaSettingsGeneral *)userdata; + self->mMediaEditable = editable; + + LLSD media_settings = _media_settings; + + if ( LLPanelMediaSettingsGeneral::isMultiple() ) + { + // *HACK: "edit" the incoming media_settings + media_settings[LLMediaEntry::CURRENT_URL_KEY] = LLTrans::getString("Multiple Media"); + media_settings[LLMediaEntry::HOME_URL_KEY] = LLTrans::getString("Multiple Media"); + } + + std::string base_key( "" ); + std::string tentative_key( "" ); + + struct + { + std::string key_name; + LLUICtrl* ctrl_ptr; + std::string ctrl_type; + + } data_set [] = + { + { LLMediaEntry::AUTO_LOOP_KEY, self->mAutoLoop, "LLCheckBoxCtrl" }, + { LLMediaEntry::AUTO_PLAY_KEY, self->mAutoPlay, "LLCheckBoxCtrl" }, + { LLMediaEntry::AUTO_SCALE_KEY, self->mAutoScale, "LLCheckBoxCtrl" }, + { LLMediaEntry::AUTO_ZOOM_KEY, self->mAutoZoom, "LLCheckBoxCtrl" }, + { LLMediaEntry::CURRENT_URL_KEY, self->mCurrentURL, "LLTextBox" }, + { LLMediaEntry::HEIGHT_PIXELS_KEY, self->mHeightPixels, "LLSpinCtrl" }, + { LLMediaEntry::HOME_URL_KEY, self->mHomeURL, "LLLineEditor" }, + { LLMediaEntry::FIRST_CLICK_INTERACT_KEY, self->mFirstClick, "LLCheckBoxCtrl" }, + { LLMediaEntry::WIDTH_PIXELS_KEY, self->mWidthPixels, "LLSpinCtrl" }, + { "", NULL , "" } + }; + + for( int i = 0; data_set[ i ].key_name.length() > 0; ++i ) + { + base_key = std::string( data_set[ i ].key_name ); + tentative_key = base_key + std::string( LLPanelContents::TENTATIVE_SUFFIX ); + // TODO: CP - I bet there is a better way to do this using Boost + if ( media_settings[ base_key ].isDefined() ) + { + if ( data_set[ i ].ctrl_type == "LLLineEditor" ) + { + static_cast< LLLineEditor* >( data_set[ i ].ctrl_ptr )-> + setText( media_settings[ base_key ].asString() ); + } + else + if ( data_set[ i ].ctrl_type == "LLCheckBoxCtrl" ) + static_cast< LLCheckBoxCtrl* >( data_set[ i ].ctrl_ptr )-> + setValue( media_settings[ base_key ].asBoolean() ); + else + if ( data_set[ i ].ctrl_type == "LLComboBox" ) + static_cast< LLComboBox* >( data_set[ i ].ctrl_ptr )-> + setCurrentByIndex( media_settings[ base_key ].asInteger() ); + else + if ( data_set[ i ].ctrl_type == "LLSpinCtrl" ) + static_cast< LLSpinCtrl* >( data_set[ i ].ctrl_ptr )-> + setValue( media_settings[ base_key ].asInteger() ); + + data_set[ i ].ctrl_ptr->setEnabled(self->mMediaEditable); + data_set[ i ].ctrl_ptr->setTentative( media_settings[ tentative_key ].asBoolean() ); + }; + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// Helper to set media control to media URL as required +void LLPanelMediaSettingsGeneral::updateMediaPreview() +{ + if(LLTrans::getString("Multiple Media") == mHomeURL->getValue().asString()) + { + return; + } + if ( mHomeURL->getValue().asString().length() > 0 ) + { + if(mPreviewMedia->getCurrentNavUrl() != mHomeURL->getValue().asString()) + { + mPreviewMedia->navigateTo( mHomeURL->getValue().asString() ); + } + } + else + // new home URL will be empty if media is deleted so display a + // "preview goes here" data url page + { + if(mPreviewMedia->getCurrentNavUrl() != CHECKERBOARD_DATA_URL) + { + mPreviewMedia->navigateTo( CHECKERBOARD_DATA_URL ); + } + }; +} + +//////////////////////////////////////////////////////////////////////////////// + +// virtual +void LLPanelMediaSettingsGeneral::onClose(bool app_quitting) +{ + if(mPreviewMedia) + { + mPreviewMedia->unloadMediaSource(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLPanelMediaSettingsGeneral::checkHomeUrlPassesWhitelist() +{ + // parent floater has not constructed the security panel yet + if ( mParent->getPanelSecurity() == 0 ) + return; + + std::string home_url = getHomeUrl(); + if ( home_url.empty() || mParent->getPanelSecurity()->urlPassesWhiteList( home_url ) ) + { + // Home URL is empty or passes the white list so hide the warning message + mFailWhiteListText->setVisible( false ); + } + else + { + // Home URL does not pass the white list so show the warning message + mFailWhiteListText->setVisible( true ); + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// static +void LLPanelMediaSettingsGeneral::onCommitHomeURL( LLUICtrl* ctrl, void *userdata ) +{ + LLPanelMediaSettingsGeneral* self =(LLPanelMediaSettingsGeneral *)userdata; + + // check home url passes whitelist and display warning if not + self->checkHomeUrlPassesWhitelist(); + + self->updateMediaPreview(); +} + +//////////////////////////////////////////////////////////////////////////////// +// static +void LLPanelMediaSettingsGeneral::onBtnResetCurrentUrl(LLUICtrl* ctrl, void *userdata) +{ + LLPanelMediaSettingsGeneral* self =(LLPanelMediaSettingsGeneral *)userdata; + self->navigateHomeSelectedFace(false); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLPanelMediaSettingsGeneral::preApply() +{ + // Make sure the home URL entry is committed + mHomeURL->onCommit(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLPanelMediaSettingsGeneral::getValues( LLSD &fill_me_in, bool include_tentative ) +{ + if (include_tentative || !mAutoLoop->getTentative()) fill_me_in[LLMediaEntry::AUTO_LOOP_KEY] = (LLSD::Boolean)mAutoLoop->getValue(); + if (include_tentative || !mAutoPlay->getTentative()) fill_me_in[LLMediaEntry::AUTO_PLAY_KEY] = (LLSD::Boolean)mAutoPlay->getValue(); + if (include_tentative || !mAutoScale->getTentative()) fill_me_in[LLMediaEntry::AUTO_SCALE_KEY] = (LLSD::Boolean)mAutoScale->getValue(); + if (include_tentative || !mAutoZoom->getTentative()) fill_me_in[LLMediaEntry::AUTO_ZOOM_KEY] = (LLSD::Boolean)mAutoZoom->getValue(); + //Don't fill in current URL: this is only supposed to get changed via navigate + // if (include_tentative || !mCurrentURL->getTentative()) fill_me_in[LLMediaEntry::CURRENT_URL_KEY] = mCurrentURL->getValue(); + if (include_tentative || !mHeightPixels->getTentative()) fill_me_in[LLMediaEntry::HEIGHT_PIXELS_KEY] = (LLSD::Integer)mHeightPixels->getValue(); + // Don't fill in the home URL if it is the special "Multiple Media" string! + if ((include_tentative || !mHomeURL->getTentative()) + && LLTrans::getString("Multiple Media") != mHomeURL->getValue()) + fill_me_in[LLMediaEntry::HOME_URL_KEY] = (LLSD::String)mHomeURL->getValue(); + if (include_tentative || !mFirstClick->getTentative()) fill_me_in[LLMediaEntry::FIRST_CLICK_INTERACT_KEY] = (LLSD::Boolean)mFirstClick->getValue(); + if (include_tentative || !mWidthPixels->getTentative()) fill_me_in[LLMediaEntry::WIDTH_PIXELS_KEY] = (LLSD::Integer)mWidthPixels->getValue(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLPanelMediaSettingsGeneral::postApply() +{ + // Make sure to navigate to the home URL if the current URL is empty and + // autoplay is on + navigateHomeSelectedFace(true); +} + + +//////////////////////////////////////////////////////////////////////////////// +// +void LLPanelMediaSettingsGeneral::setParent( LLFloaterMediaSettings* parent ) +{ + mParent = parent; +}; + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLPanelMediaSettingsGeneral::navigateHomeSelectedFace(bool only_if_current_is_empty) +{ + struct functor_navigate_media : public LLSelectedTEGetFunctor< bool> + { + functor_navigate_media(bool flag) : only_if_current_is_empty(flag) {} + bool get( LLViewerObject* object, S32 face ) + { + if ( object && object->getTE(face) && object->permModify() ) + { + const LLMediaEntry *media_data = object->getTE(face)->getMediaData(); + if ( media_data ) + { + if (!only_if_current_is_empty || (media_data->getCurrentURL().empty() && media_data->getAutoPlay())) + { + viewer_media_t media_impl = + LLViewerMedia::getInstance()->getMediaImplFromTextureID(object->getTE(face)->getMediaData()->getMediaID()); + if (media_impl) + { + media_impl->setPriority(LLPluginClassMedia::PRIORITY_NORMAL); + media_impl->navigateHome(); + + if (!only_if_current_is_empty) + { + LLSD media_data; + media_data[LLMediaEntry::CURRENT_URL_KEY] = std::string(); + object->getTE(face)->mergeIntoMediaData(media_data); + } + return true; + } + } + } + } + return false; + }; + bool only_if_current_is_empty; + + } functor_navigate_media(only_if_current_is_empty); + + bool all_face_media_navigated = false; + LLObjectSelectionHandle selected_objects =LLSelectMgr::getInstance()->getSelection(); + selected_objects->getSelectedTEValue( &functor_navigate_media, all_face_media_navigated ); + + if (all_face_media_navigated) + { + struct functor_sync_to_server : public LLSelectedObjectFunctor + { + virtual bool apply(LLViewerObject* object) + { + LLVOVolume *volume = dynamic_cast(object); + if (volume) + { + volume->sendMediaDataUpdate(); + } + return true; + } + } sendfunc; + selected_objects->applyToObjects(&sendfunc); + } + + // Note: we don't update the 'current URL' field until the media data itself changes + + return all_face_media_navigated; +} + +//////////////////////////////////////////////////////////////////////////////// +// +const std::string LLPanelMediaSettingsGeneral::getHomeUrl() +{ + return mHomeURL->getValue().asString(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLPanelMediaSettingsGeneral::updateCurrentUrl() +{ + // Get the current URL from the selection + const LLMediaEntry default_media_data; + std::string value_str = default_media_data.getCurrentURL(); + struct functor_getter_current_url : public LLSelectedTEGetFunctor< std::string > + { + functor_getter_current_url(const LLMediaEntry& entry): mMediaEntry(entry) {} + + std::string get( LLViewerObject* object, S32 face ) + { + if ( object ) + if ( object->getTE(face) ) + if ( object->getTE(face)->getMediaData() ) + return object->getTE(face)->getMediaData()->getCurrentURL(); + return mMediaEntry.getCurrentURL(); + }; + + const LLMediaEntry & mMediaEntry; + + } func_current_url(default_media_data); + bool identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &func_current_url, value_str ); + mCurrentURL->setText(value_str); + mCurrentURL->setTentative(identical); + + if ( LLPanelMediaSettingsGeneral::isMultiple() ) + { + mCurrentURL->setText(LLTrans::getString("Multiple Media")); + } +} diff --git a/indra/newview/llpanelmediasettingsgeneral.h b/indra/newview/llpanelmediasettingsgeneral.h index b954798a68..9fe97b9121 100644 --- a/indra/newview/llpanelmediasettingsgeneral.h +++ b/indra/newview/llpanelmediasettingsgeneral.h @@ -1,100 +1,100 @@ -/** - * @file llpanelmediasettingsgeneral.h - * @brief LLPanelMediaSettingsGeneral class definition - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELMEDIAMEDIASETTINGSGENERAL_H -#define LL_LLPANELMEDIAMEDIASETTINGSGENERAL_H - -#include "llpanel.h" - -class LLButton; -class LLCheckBoxCtrl; -class LLLineEditor; -class LLSpinCtrl; -class LLTextureCtrl; -class LLMediaCtrl; -class LLTextBox; -class LLFloaterMediaSettings; - -class LLPanelMediaSettingsGeneral : public LLPanel -{ -public: - LLPanelMediaSettingsGeneral(); - ~LLPanelMediaSettingsGeneral(); - - // XXX TODO: put these into a common parent class? - // Hook that the floater calls before applying changes from the panel - void preApply(); - // Function that asks the panel to fill in values associated with the panel - // 'include_tentative' means fill in tentative values as well, otherwise do not - void getValues(LLSD &fill_me_in, bool include_tentative = true); - // Hook that the floater calls after applying changes to the panel - void postApply(); - - bool postBuild(); - /*virtual*/ void draw(); - /*virtual*/ void onClose(bool app_quitting); - - void setParent( LLFloaterMediaSettings* parent ); - static void initValues( void* userdata, const LLSD& media_settings ,bool editable); - static void clearValues( void* userdata, bool editable, bool update_preview = true); - - // Navigates the current selected face to the Home URL. - // If 'only_if_current_is_empty' is "true", it only performs - // the operation if: 1) the current URL is empty, and 2) auto play is true. - bool navigateHomeSelectedFace(bool only_if_current_is_empty); - - void updateMediaPreview(); - - const std::string getHomeUrl(); - -protected: - LLFloaterMediaSettings* mParent; - bool mMediaEditable; - -private: - void updateCurrentUrl(); - - static void onBtnResetCurrentUrl(LLUICtrl* ctrl, void *userdata); - static void onCommitHomeURL(LLUICtrl* ctrl, void *userdata ); - - static bool isMultiple(); - - void checkHomeUrlPassesWhitelist(); - - LLCheckBoxCtrl* mAutoLoop; - LLCheckBoxCtrl* mFirstClick; - LLCheckBoxCtrl* mAutoZoom; - LLCheckBoxCtrl* mAutoPlay; - LLCheckBoxCtrl* mAutoScale; - LLSpinCtrl* mWidthPixels; - LLSpinCtrl* mHeightPixels; - LLLineEditor* mHomeURL; - LLTextBox* mCurrentURL; - LLMediaCtrl* mPreviewMedia; - LLTextBox* mFailWhiteListText; -}; - -#endif // LL_LLPANELMEDIAMEDIASETTINGSGENERAL_H +/** + * @file llpanelmediasettingsgeneral.h + * @brief LLPanelMediaSettingsGeneral class definition + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELMEDIAMEDIASETTINGSGENERAL_H +#define LL_LLPANELMEDIAMEDIASETTINGSGENERAL_H + +#include "llpanel.h" + +class LLButton; +class LLCheckBoxCtrl; +class LLLineEditor; +class LLSpinCtrl; +class LLTextureCtrl; +class LLMediaCtrl; +class LLTextBox; +class LLFloaterMediaSettings; + +class LLPanelMediaSettingsGeneral : public LLPanel +{ +public: + LLPanelMediaSettingsGeneral(); + ~LLPanelMediaSettingsGeneral(); + + // XXX TODO: put these into a common parent class? + // Hook that the floater calls before applying changes from the panel + void preApply(); + // Function that asks the panel to fill in values associated with the panel + // 'include_tentative' means fill in tentative values as well, otherwise do not + void getValues(LLSD &fill_me_in, bool include_tentative = true); + // Hook that the floater calls after applying changes to the panel + void postApply(); + + bool postBuild(); + /*virtual*/ void draw(); + /*virtual*/ void onClose(bool app_quitting); + + void setParent( LLFloaterMediaSettings* parent ); + static void initValues( void* userdata, const LLSD& media_settings ,bool editable); + static void clearValues( void* userdata, bool editable, bool update_preview = true); + + // Navigates the current selected face to the Home URL. + // If 'only_if_current_is_empty' is "true", it only performs + // the operation if: 1) the current URL is empty, and 2) auto play is true. + bool navigateHomeSelectedFace(bool only_if_current_is_empty); + + void updateMediaPreview(); + + const std::string getHomeUrl(); + +protected: + LLFloaterMediaSettings* mParent; + bool mMediaEditable; + +private: + void updateCurrentUrl(); + + static void onBtnResetCurrentUrl(LLUICtrl* ctrl, void *userdata); + static void onCommitHomeURL(LLUICtrl* ctrl, void *userdata ); + + static bool isMultiple(); + + void checkHomeUrlPassesWhitelist(); + + LLCheckBoxCtrl* mAutoLoop; + LLCheckBoxCtrl* mFirstClick; + LLCheckBoxCtrl* mAutoZoom; + LLCheckBoxCtrl* mAutoPlay; + LLCheckBoxCtrl* mAutoScale; + LLSpinCtrl* mWidthPixels; + LLSpinCtrl* mHeightPixels; + LLLineEditor* mHomeURL; + LLTextBox* mCurrentURL; + LLMediaCtrl* mPreviewMedia; + LLTextBox* mFailWhiteListText; +}; + +#endif // LL_LLPANELMEDIAMEDIASETTINGSGENERAL_H diff --git a/indra/newview/llpanelmediasettingspermissions.cpp b/indra/newview/llpanelmediasettingspermissions.cpp index a5bcf35a44..868d492083 100644 --- a/indra/newview/llpanelmediasettingspermissions.cpp +++ b/indra/newview/llpanelmediasettingspermissions.cpp @@ -1,284 +1,284 @@ -/** - * @file llpanelmediasettingspermissions.cpp - * @brief LLPanelMediaSettingsPermissions class implementation - * - * note that "permissions" tab is really "Controls" tab - refs to 'perms' and - * 'permissions' not changed to 'controls' since we don't want to change - * shared files in server code and keeping everything the same seemed best. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelmediasettingspermissions.h" -#include "llpanelcontents.h" -#include "llcombobox.h" -#include "llcheckboxctrl.h" -#include "llspinctrl.h" -#include "llurlhistory.h" -#include "lluictrlfactory.h" -#include "llwindow.h" -#include "llviewerwindow.h" -#include "llsdutil.h" -#include "llselectmgr.h" -#include "llmediaentry.h" -#include "llnamebox.h" -#include "lltrans.h" -#include "llfloatermediasettings.h" - -//////////////////////////////////////////////////////////////////////////////// -// -LLPanelMediaSettingsPermissions::LLPanelMediaSettingsPermissions() : - mControls( NULL ), - mPermsOwnerInteract( 0 ), - mPermsOwnerControl( 0 ), - mPermsGroupName( 0 ), - mPermsGroupInteract( 0 ), - mPermsGroupControl( 0 ), - mPermsWorldInteract( 0 ), - mPermsWorldControl( 0 ) -{ - // build dialog from XML - buildFromFile( "panel_media_settings_permissions.xml"); -} - -//////////////////////////////////////////////////////////////////////////////// -// -bool LLPanelMediaSettingsPermissions::postBuild() -{ - // connect member vars with UI widgets - mControls = getChild< LLComboBox >( LLMediaEntry::CONTROLS_KEY ); - mPermsOwnerInteract = getChild< LLCheckBoxCtrl >( LLPanelContents::PERMS_OWNER_INTERACT_KEY ); - mPermsOwnerControl = getChild< LLCheckBoxCtrl >( LLPanelContents::PERMS_OWNER_CONTROL_KEY ); - mPermsGroupInteract = getChild< LLCheckBoxCtrl >( LLPanelContents::PERMS_GROUP_INTERACT_KEY ); - mPermsGroupControl = getChild< LLCheckBoxCtrl >( LLPanelContents::PERMS_GROUP_CONTROL_KEY ); - mPermsWorldInteract = getChild< LLCheckBoxCtrl >( LLPanelContents::PERMS_ANYONE_INTERACT_KEY ); - mPermsWorldControl = getChild< LLCheckBoxCtrl >( LLPanelContents::PERMS_ANYONE_CONTROL_KEY ); - - mPermsGroupName = getChild< LLNameBox >( "perms_group_name" ); - - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -// virtual -LLPanelMediaSettingsPermissions::~LLPanelMediaSettingsPermissions() -{ -} - -//////////////////////////////////////////////////////////////////////////////// -// virtual -void LLPanelMediaSettingsPermissions::draw() -{ - // housekeeping - LLPanel::draw(); - - getChild("perms_group_name")->setValue(LLStringUtil::null); - LLUUID group_id; - bool groups_identical = LLSelectMgr::getInstance()->selectGetGroup(group_id); - if (groups_identical) - { - if(mPermsGroupName) - { - mPermsGroupName->setNameID(group_id, true); - } - } - else - { - if(mPermsGroupName) - { - mPermsGroupName->setNameID(LLUUID::null, true); - mPermsGroupName->refresh(LLUUID::null, std::string(), true); - } - } -} - -//////////////////////////////////////////////////////////////////////////////// -// static -void LLPanelMediaSettingsPermissions::clearValues( void* userdata, bool editable) -{ - LLPanelMediaSettingsPermissions *self =(LLPanelMediaSettingsPermissions *)userdata; - - self->mControls->clear(); - self->mPermsOwnerInteract->clear(); - self->mPermsOwnerControl->clear(); - self->mPermsGroupInteract->clear(); - self->mPermsGroupControl->clear(); - self->mPermsWorldInteract->clear(); - self->mPermsWorldControl->clear(); - - self->mControls->setEnabled(editable); - self->mPermsOwnerInteract->setEnabled(editable); - self->mPermsOwnerControl->setEnabled(editable); - self->mPermsGroupInteract->setEnabled(editable); - self->mPermsGroupControl->setEnabled(editable); - self->mPermsWorldInteract->setEnabled(editable); - self->mPermsWorldControl->setEnabled(editable); - - self->getChild< LLTextBox >("controls_label")->setEnabled(editable); - self->getChild< LLTextBox >("owner_label")->setEnabled(editable); - self->getChild< LLTextBox >("group_label")->setEnabled(editable); - self->getChild< LLNameBox >("perms_group_name")->setEnabled(editable); - self->getChild< LLTextBox >("anyone_label")->setEnabled(editable); -} - -//////////////////////////////////////////////////////////////////////////////// -// static -void LLPanelMediaSettingsPermissions::initValues( void* userdata, const LLSD& media_settings , bool editable) -{ - LLPanelMediaSettingsPermissions *self =(LLPanelMediaSettingsPermissions *)userdata; - std::string base_key( "" ); - std::string tentative_key( "" ); - - struct - { - std::string key_name; - LLUICtrl* ctrl_ptr; - std::string ctrl_type; - - } data_set [] = - { - { LLMediaEntry::CONTROLS_KEY, self->mControls, "LLComboBox" }, - { LLPanelContents::PERMS_OWNER_INTERACT_KEY, self->mPermsOwnerInteract, "LLCheckBoxCtrl" }, - { LLPanelContents::PERMS_OWNER_CONTROL_KEY, self->mPermsOwnerControl, "LLCheckBoxCtrl" }, - { LLPanelContents::PERMS_GROUP_INTERACT_KEY, self->mPermsGroupInteract, "LLCheckBoxCtrl" }, - { LLPanelContents::PERMS_GROUP_CONTROL_KEY, self->mPermsGroupControl, "LLCheckBoxCtrl" }, - { LLPanelContents::PERMS_ANYONE_INTERACT_KEY, self->mPermsWorldInteract, "LLCheckBoxCtrl" }, - { LLPanelContents::PERMS_ANYONE_CONTROL_KEY, self->mPermsWorldControl, "LLCheckBoxCtrl" }, - { "", NULL , "" } - }; - - for( int i = 0; data_set[ i ].key_name.length() > 0; ++i ) - { - base_key = std::string( data_set[ i ].key_name ); - tentative_key = base_key + std::string( LLPanelContents::TENTATIVE_SUFFIX ); - - // TODO: CP - I bet there is a better way to do this using Boost - if ( media_settings[ base_key ].isDefined() ) - { - if ( data_set[ i ].ctrl_type == "LLCheckBoxCtrl" ) - { - // Most recent change to the "sense" of these checkboxes - // means the value in the checkbox matches that on the server - static_cast< LLCheckBoxCtrl* >( data_set[ i ].ctrl_ptr )-> - setValue( media_settings[ base_key ].asBoolean() ); - } - else - if ( data_set[ i ].ctrl_type == "LLComboBox" ) - static_cast< LLComboBox* >( data_set[ i ].ctrl_ptr )-> - setCurrentByIndex( media_settings[ base_key ].asInteger() ); - - data_set[ i ].ctrl_ptr->setEnabled(editable); - data_set[ i ].ctrl_ptr->setTentative( media_settings[ tentative_key ].asBoolean() ); - }; - }; - - // *NOTE: If any of a particular flavor is tentative, we have to disable - // them all because of an architectural issue: namely that we represent - // these as a bit field, and we can't selectively apply only one bit to all selected - // faces if they don't match. Also see the *NOTE below. - if ( self->mPermsOwnerInteract->getTentative() || - self->mPermsGroupInteract->getTentative() || - self->mPermsWorldInteract->getTentative()) - { - self->mPermsOwnerInteract->setEnabled(false); - self->mPermsGroupInteract->setEnabled(false); - self->mPermsWorldInteract->setEnabled(false); - } - if ( self->mPermsOwnerControl->getTentative() || - self->mPermsGroupControl->getTentative() || - self->mPermsWorldControl->getTentative()) - { - self->mPermsOwnerControl->setEnabled(false); - self->mPermsGroupControl->setEnabled(false); - self->mPermsWorldControl->setEnabled(false); - } - - self->getChild< LLTextBox >("controls_label")->setEnabled(editable); - self->getChild< LLTextBox >("owner_label")->setEnabled(editable); - self->getChild< LLTextBox >("group_label")->setEnabled(editable); - self->getChild< LLNameBox >("perms_group_name")->setEnabled(editable); - self->getChild< LLTextBox >("anyone_label")->setEnabled(editable); -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLPanelMediaSettingsPermissions::preApply() -{ - // no-op -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLPanelMediaSettingsPermissions::getValues( LLSD &fill_me_in, bool include_tentative ) -{ - // moved over from the 'General settings' tab - if (include_tentative || !mControls->getTentative()) fill_me_in[LLMediaEntry::CONTROLS_KEY] = (LLSD::Integer)mControls->getCurrentIndex(); - - // *NOTE: For some reason, gcc does not like these symbol references in the - // expressions below (inside the static_casts). I have NO idea why :(. - // For some reason, assigning them to const temp vars here fixes the link - // error. Bizarre. - const U8 none = LLMediaEntry::PERM_NONE; - const U8 owner = LLMediaEntry::PERM_OWNER; - const U8 group = LLMediaEntry::PERM_GROUP; - const U8 anyone = LLMediaEntry::PERM_ANYONE; - const LLSD::Integer control = static_cast( - (mPermsOwnerControl->getValue() ? owner : none ) | - (mPermsGroupControl->getValue() ? group: none ) | - (mPermsWorldControl->getValue() ? anyone : none )); - const LLSD::Integer interact = static_cast( - (mPermsOwnerInteract->getValue() ? owner: none ) | - (mPermsGroupInteract->getValue() ? group : none ) | - (mPermsWorldInteract->getValue() ? anyone : none )); - - // *TODO: This will fill in the values of all permissions values, even if - // one or more is tentative. This is not quite the user expectation...what - // it should do is only change the bit that was made "untentative", but in - // a multiple-selection situation, this isn't possible given the architecture - // for how settings are applied. - if (include_tentative || - !mPermsOwnerControl->getTentative() || - !mPermsGroupControl->getTentative() || - !mPermsWorldControl->getTentative()) - { - fill_me_in[LLMediaEntry::PERMS_CONTROL_KEY] = control; - } - if (include_tentative || - !mPermsOwnerInteract->getTentative() || - !mPermsGroupInteract->getTentative() || - !mPermsWorldInteract->getTentative()) - { - fill_me_in[LLMediaEntry::PERMS_INTERACT_KEY] = interact; - } -} - - -//////////////////////////////////////////////////////////////////////////////// -// -void LLPanelMediaSettingsPermissions::postApply() -{ - // no-op -} - - +/** + * @file llpanelmediasettingspermissions.cpp + * @brief LLPanelMediaSettingsPermissions class implementation + * + * note that "permissions" tab is really "Controls" tab - refs to 'perms' and + * 'permissions' not changed to 'controls' since we don't want to change + * shared files in server code and keeping everything the same seemed best. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelmediasettingspermissions.h" +#include "llpanelcontents.h" +#include "llcombobox.h" +#include "llcheckboxctrl.h" +#include "llspinctrl.h" +#include "llurlhistory.h" +#include "lluictrlfactory.h" +#include "llwindow.h" +#include "llviewerwindow.h" +#include "llsdutil.h" +#include "llselectmgr.h" +#include "llmediaentry.h" +#include "llnamebox.h" +#include "lltrans.h" +#include "llfloatermediasettings.h" + +//////////////////////////////////////////////////////////////////////////////// +// +LLPanelMediaSettingsPermissions::LLPanelMediaSettingsPermissions() : + mControls( NULL ), + mPermsOwnerInteract( 0 ), + mPermsOwnerControl( 0 ), + mPermsGroupName( 0 ), + mPermsGroupInteract( 0 ), + mPermsGroupControl( 0 ), + mPermsWorldInteract( 0 ), + mPermsWorldControl( 0 ) +{ + // build dialog from XML + buildFromFile( "panel_media_settings_permissions.xml"); +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLPanelMediaSettingsPermissions::postBuild() +{ + // connect member vars with UI widgets + mControls = getChild< LLComboBox >( LLMediaEntry::CONTROLS_KEY ); + mPermsOwnerInteract = getChild< LLCheckBoxCtrl >( LLPanelContents::PERMS_OWNER_INTERACT_KEY ); + mPermsOwnerControl = getChild< LLCheckBoxCtrl >( LLPanelContents::PERMS_OWNER_CONTROL_KEY ); + mPermsGroupInteract = getChild< LLCheckBoxCtrl >( LLPanelContents::PERMS_GROUP_INTERACT_KEY ); + mPermsGroupControl = getChild< LLCheckBoxCtrl >( LLPanelContents::PERMS_GROUP_CONTROL_KEY ); + mPermsWorldInteract = getChild< LLCheckBoxCtrl >( LLPanelContents::PERMS_ANYONE_INTERACT_KEY ); + mPermsWorldControl = getChild< LLCheckBoxCtrl >( LLPanelContents::PERMS_ANYONE_CONTROL_KEY ); + + mPermsGroupName = getChild< LLNameBox >( "perms_group_name" ); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// virtual +LLPanelMediaSettingsPermissions::~LLPanelMediaSettingsPermissions() +{ +} + +//////////////////////////////////////////////////////////////////////////////// +// virtual +void LLPanelMediaSettingsPermissions::draw() +{ + // housekeeping + LLPanel::draw(); + + getChild("perms_group_name")->setValue(LLStringUtil::null); + LLUUID group_id; + bool groups_identical = LLSelectMgr::getInstance()->selectGetGroup(group_id); + if (groups_identical) + { + if(mPermsGroupName) + { + mPermsGroupName->setNameID(group_id, true); + } + } + else + { + if(mPermsGroupName) + { + mPermsGroupName->setNameID(LLUUID::null, true); + mPermsGroupName->refresh(LLUUID::null, std::string(), true); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// static +void LLPanelMediaSettingsPermissions::clearValues( void* userdata, bool editable) +{ + LLPanelMediaSettingsPermissions *self =(LLPanelMediaSettingsPermissions *)userdata; + + self->mControls->clear(); + self->mPermsOwnerInteract->clear(); + self->mPermsOwnerControl->clear(); + self->mPermsGroupInteract->clear(); + self->mPermsGroupControl->clear(); + self->mPermsWorldInteract->clear(); + self->mPermsWorldControl->clear(); + + self->mControls->setEnabled(editable); + self->mPermsOwnerInteract->setEnabled(editable); + self->mPermsOwnerControl->setEnabled(editable); + self->mPermsGroupInteract->setEnabled(editable); + self->mPermsGroupControl->setEnabled(editable); + self->mPermsWorldInteract->setEnabled(editable); + self->mPermsWorldControl->setEnabled(editable); + + self->getChild< LLTextBox >("controls_label")->setEnabled(editable); + self->getChild< LLTextBox >("owner_label")->setEnabled(editable); + self->getChild< LLTextBox >("group_label")->setEnabled(editable); + self->getChild< LLNameBox >("perms_group_name")->setEnabled(editable); + self->getChild< LLTextBox >("anyone_label")->setEnabled(editable); +} + +//////////////////////////////////////////////////////////////////////////////// +// static +void LLPanelMediaSettingsPermissions::initValues( void* userdata, const LLSD& media_settings , bool editable) +{ + LLPanelMediaSettingsPermissions *self =(LLPanelMediaSettingsPermissions *)userdata; + std::string base_key( "" ); + std::string tentative_key( "" ); + + struct + { + std::string key_name; + LLUICtrl* ctrl_ptr; + std::string ctrl_type; + + } data_set [] = + { + { LLMediaEntry::CONTROLS_KEY, self->mControls, "LLComboBox" }, + { LLPanelContents::PERMS_OWNER_INTERACT_KEY, self->mPermsOwnerInteract, "LLCheckBoxCtrl" }, + { LLPanelContents::PERMS_OWNER_CONTROL_KEY, self->mPermsOwnerControl, "LLCheckBoxCtrl" }, + { LLPanelContents::PERMS_GROUP_INTERACT_KEY, self->mPermsGroupInteract, "LLCheckBoxCtrl" }, + { LLPanelContents::PERMS_GROUP_CONTROL_KEY, self->mPermsGroupControl, "LLCheckBoxCtrl" }, + { LLPanelContents::PERMS_ANYONE_INTERACT_KEY, self->mPermsWorldInteract, "LLCheckBoxCtrl" }, + { LLPanelContents::PERMS_ANYONE_CONTROL_KEY, self->mPermsWorldControl, "LLCheckBoxCtrl" }, + { "", NULL , "" } + }; + + for( int i = 0; data_set[ i ].key_name.length() > 0; ++i ) + { + base_key = std::string( data_set[ i ].key_name ); + tentative_key = base_key + std::string( LLPanelContents::TENTATIVE_SUFFIX ); + + // TODO: CP - I bet there is a better way to do this using Boost + if ( media_settings[ base_key ].isDefined() ) + { + if ( data_set[ i ].ctrl_type == "LLCheckBoxCtrl" ) + { + // Most recent change to the "sense" of these checkboxes + // means the value in the checkbox matches that on the server + static_cast< LLCheckBoxCtrl* >( data_set[ i ].ctrl_ptr )-> + setValue( media_settings[ base_key ].asBoolean() ); + } + else + if ( data_set[ i ].ctrl_type == "LLComboBox" ) + static_cast< LLComboBox* >( data_set[ i ].ctrl_ptr )-> + setCurrentByIndex( media_settings[ base_key ].asInteger() ); + + data_set[ i ].ctrl_ptr->setEnabled(editable); + data_set[ i ].ctrl_ptr->setTentative( media_settings[ tentative_key ].asBoolean() ); + }; + }; + + // *NOTE: If any of a particular flavor is tentative, we have to disable + // them all because of an architectural issue: namely that we represent + // these as a bit field, and we can't selectively apply only one bit to all selected + // faces if they don't match. Also see the *NOTE below. + if ( self->mPermsOwnerInteract->getTentative() || + self->mPermsGroupInteract->getTentative() || + self->mPermsWorldInteract->getTentative()) + { + self->mPermsOwnerInteract->setEnabled(false); + self->mPermsGroupInteract->setEnabled(false); + self->mPermsWorldInteract->setEnabled(false); + } + if ( self->mPermsOwnerControl->getTentative() || + self->mPermsGroupControl->getTentative() || + self->mPermsWorldControl->getTentative()) + { + self->mPermsOwnerControl->setEnabled(false); + self->mPermsGroupControl->setEnabled(false); + self->mPermsWorldControl->setEnabled(false); + } + + self->getChild< LLTextBox >("controls_label")->setEnabled(editable); + self->getChild< LLTextBox >("owner_label")->setEnabled(editable); + self->getChild< LLTextBox >("group_label")->setEnabled(editable); + self->getChild< LLNameBox >("perms_group_name")->setEnabled(editable); + self->getChild< LLTextBox >("anyone_label")->setEnabled(editable); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLPanelMediaSettingsPermissions::preApply() +{ + // no-op +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLPanelMediaSettingsPermissions::getValues( LLSD &fill_me_in, bool include_tentative ) +{ + // moved over from the 'General settings' tab + if (include_tentative || !mControls->getTentative()) fill_me_in[LLMediaEntry::CONTROLS_KEY] = (LLSD::Integer)mControls->getCurrentIndex(); + + // *NOTE: For some reason, gcc does not like these symbol references in the + // expressions below (inside the static_casts). I have NO idea why :(. + // For some reason, assigning them to const temp vars here fixes the link + // error. Bizarre. + const U8 none = LLMediaEntry::PERM_NONE; + const U8 owner = LLMediaEntry::PERM_OWNER; + const U8 group = LLMediaEntry::PERM_GROUP; + const U8 anyone = LLMediaEntry::PERM_ANYONE; + const LLSD::Integer control = static_cast( + (mPermsOwnerControl->getValue() ? owner : none ) | + (mPermsGroupControl->getValue() ? group: none ) | + (mPermsWorldControl->getValue() ? anyone : none )); + const LLSD::Integer interact = static_cast( + (mPermsOwnerInteract->getValue() ? owner: none ) | + (mPermsGroupInteract->getValue() ? group : none ) | + (mPermsWorldInteract->getValue() ? anyone : none )); + + // *TODO: This will fill in the values of all permissions values, even if + // one or more is tentative. This is not quite the user expectation...what + // it should do is only change the bit that was made "untentative", but in + // a multiple-selection situation, this isn't possible given the architecture + // for how settings are applied. + if (include_tentative || + !mPermsOwnerControl->getTentative() || + !mPermsGroupControl->getTentative() || + !mPermsWorldControl->getTentative()) + { + fill_me_in[LLMediaEntry::PERMS_CONTROL_KEY] = control; + } + if (include_tentative || + !mPermsOwnerInteract->getTentative() || + !mPermsGroupInteract->getTentative() || + !mPermsWorldInteract->getTentative()) + { + fill_me_in[LLMediaEntry::PERMS_INTERACT_KEY] = interact; + } +} + + +//////////////////////////////////////////////////////////////////////////////// +// +void LLPanelMediaSettingsPermissions::postApply() +{ + // no-op +} + + diff --git a/indra/newview/llpanelmediasettingspermissions.h b/indra/newview/llpanelmediasettingspermissions.h index 85e21e0ada..3d920b2c39 100644 --- a/indra/newview/llpanelmediasettingspermissions.h +++ b/indra/newview/llpanelmediasettingspermissions.h @@ -1,73 +1,73 @@ -/** - * @file llpanelmediasettingspermissions.h - * @brief LLPanelMediaSettingsPermissions class definition - * - * note that "permissions" tab is really "Controls" tab - refs to 'perms' and - * 'permissions' not changed to 'controls' since we don't want to change - * shared files in server code and keeping everything the same seemed best. - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELMEDIAMEDIASETTINGSPERMISSIONS_H -#define LL_LLPANELMEDIAMEDIASETTINGSPERMISSIONS_H - -#include "llpanel.h" -#include "lluuid.h" - -class LLComboBox; -class LLCheckBoxCtrl; -class LLNameBox; - -class LLPanelMediaSettingsPermissions : public LLPanel -{ -public: - LLPanelMediaSettingsPermissions(); - ~LLPanelMediaSettingsPermissions(); - - bool postBuild(); - virtual void draw(); - - // XXX TODO: put these into a common parent class? - // Hook that the floater calls before applying changes from the panel - void preApply(); - // Function that asks the panel to fill in values associated with the panel - // 'include_tentative' means fill in tentative values as well, otherwise do not - void getValues(LLSD &fill_me_in, bool include_tentative = true); - // Hook that the floater calls after applying changes to the panel - void postApply(); - - static void initValues( void* userdata, const LLSD& media_settings, bool editable ); - static void clearValues( void* userdata, bool editable); - -private: - LLComboBox* mControls; - LLCheckBoxCtrl* mPermsOwnerInteract; - LLCheckBoxCtrl* mPermsOwnerControl; - LLNameBox* mPermsGroupName; - LLCheckBoxCtrl* mPermsGroupInteract; - LLCheckBoxCtrl* mPermsGroupControl; - LLCheckBoxCtrl* mPermsWorldInteract; - LLCheckBoxCtrl* mPermsWorldControl; -}; - -#endif // LL_LLPANELMEDIAMEDIASETTINGSPERMISSIONS_H +/** + * @file llpanelmediasettingspermissions.h + * @brief LLPanelMediaSettingsPermissions class definition + * + * note that "permissions" tab is really "Controls" tab - refs to 'perms' and + * 'permissions' not changed to 'controls' since we don't want to change + * shared files in server code and keeping everything the same seemed best. + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELMEDIAMEDIASETTINGSPERMISSIONS_H +#define LL_LLPANELMEDIAMEDIASETTINGSPERMISSIONS_H + +#include "llpanel.h" +#include "lluuid.h" + +class LLComboBox; +class LLCheckBoxCtrl; +class LLNameBox; + +class LLPanelMediaSettingsPermissions : public LLPanel +{ +public: + LLPanelMediaSettingsPermissions(); + ~LLPanelMediaSettingsPermissions(); + + bool postBuild(); + virtual void draw(); + + // XXX TODO: put these into a common parent class? + // Hook that the floater calls before applying changes from the panel + void preApply(); + // Function that asks the panel to fill in values associated with the panel + // 'include_tentative' means fill in tentative values as well, otherwise do not + void getValues(LLSD &fill_me_in, bool include_tentative = true); + // Hook that the floater calls after applying changes to the panel + void postApply(); + + static void initValues( void* userdata, const LLSD& media_settings, bool editable ); + static void clearValues( void* userdata, bool editable); + +private: + LLComboBox* mControls; + LLCheckBoxCtrl* mPermsOwnerInteract; + LLCheckBoxCtrl* mPermsOwnerControl; + LLNameBox* mPermsGroupName; + LLCheckBoxCtrl* mPermsGroupInteract; + LLCheckBoxCtrl* mPermsGroupControl; + LLCheckBoxCtrl* mPermsWorldInteract; + LLCheckBoxCtrl* mPermsWorldControl; +}; + +#endif // LL_LLPANELMEDIAMEDIASETTINGSPERMISSIONS_H diff --git a/indra/newview/llpanelmediasettingssecurity.cpp b/indra/newview/llpanelmediasettingssecurity.cpp index 82ee9c8fb2..6e4e9f426d 100644 --- a/indra/newview/llpanelmediasettingssecurity.cpp +++ b/indra/newview/llpanelmediasettingssecurity.cpp @@ -1,356 +1,356 @@ -/** - * @file llpanelmediasettingssecurity.cpp - * @brief LLPanelMediaSettingsSecurity class implementation - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelmediasettingssecurity.h" - -#include "llfloaterreg.h" -#include "llpanelcontents.h" -#include "llcheckboxctrl.h" -#include "llnotificationsutil.h" -#include "llscrolllistctrl.h" -#include "llscrolllistitem.h" -#include "lluictrlfactory.h" -#include "llwindow.h" -#include "llviewerwindow.h" -#include "llsdutil.h" -#include "llselectmgr.h" -#include "llmediaentry.h" -#include "lltextbox.h" -#include "llfloaterwhitelistentry.h" -#include "llfloatermediasettings.h" - -//////////////////////////////////////////////////////////////////////////////// -// -LLPanelMediaSettingsSecurity::LLPanelMediaSettingsSecurity() : - mParent( NULL ) -{ - mCommitCallbackRegistrar.add("Media.whitelistAdd", boost::bind(&LLPanelMediaSettingsSecurity::onBtnAdd, this)); - mCommitCallbackRegistrar.add("Media.whitelistDelete", boost::bind(&LLPanelMediaSettingsSecurity::onBtnDel, this)); - - // build dialog from XML - buildFromFile( "panel_media_settings_security.xml"); -} - -//////////////////////////////////////////////////////////////////////////////// -// -bool LLPanelMediaSettingsSecurity::postBuild() -{ - mEnableWhiteList = getChild< LLCheckBoxCtrl >( LLMediaEntry::WHITELIST_ENABLE_KEY ); - mWhiteListList = getChild< LLScrollListCtrl >( LLMediaEntry::WHITELIST_KEY ); - mHomeUrlFailsWhiteListText = getChild( "home_url_fails_whitelist" ); - - setDefaultBtn("whitelist_add"); - - return true; -} - -//////////////////////////////////////////////////////////////////////////////// -// virtual -LLPanelMediaSettingsSecurity::~LLPanelMediaSettingsSecurity() -{ -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLPanelMediaSettingsSecurity::draw() -{ - // housekeeping - LLPanel::draw(); -} - -//////////////////////////////////////////////////////////////////////////////// -// static -void LLPanelMediaSettingsSecurity::initValues( void* userdata, const LLSD& media_settings , bool editable) -{ - LLPanelMediaSettingsSecurity *self =(LLPanelMediaSettingsSecurity *)userdata; - std::string base_key( "" ); - std::string tentative_key( "" ); - - struct - { - std::string key_name; - LLUICtrl* ctrl_ptr; - std::string ctrl_type; - - } data_set [] = - { - { LLMediaEntry::WHITELIST_ENABLE_KEY, self->mEnableWhiteList, "LLCheckBoxCtrl" }, - { LLMediaEntry::WHITELIST_KEY, self->mWhiteListList, "LLScrollListCtrl" }, - { "", NULL , "" } - }; - - for( int i = 0; data_set[ i ].key_name.length() > 0; ++i ) - { - base_key = std::string( data_set[ i ].key_name ); - tentative_key = base_key + std::string( LLPanelContents::TENTATIVE_SUFFIX ); - - bool enabled_overridden = false; - - // TODO: CP - I bet there is a better way to do this using Boost - if ( media_settings[ base_key ].isDefined() ) - { - if ( data_set[ i ].ctrl_type == "LLCheckBoxCtrl" ) - { - static_cast< LLCheckBoxCtrl* >( data_set[ i ].ctrl_ptr )-> - setValue( media_settings[ base_key ].asBoolean() ); - } - else - if ( data_set[ i ].ctrl_type == "LLScrollListCtrl" ) - { - // get control - LLScrollListCtrl* list = static_cast< LLScrollListCtrl* >( data_set[ i ].ctrl_ptr ); - list->deleteAllItems(); - - // points to list of white list URLs - LLSD url_list = media_settings[ base_key ]; - - // better be the whitelist - llassert(data_set[ i ].ctrl_ptr == self->mWhiteListList); - - // If tentative, don't add entries - if (media_settings[ tentative_key ].asBoolean()) - { - self->mWhiteListList->setEnabled(false); - enabled_overridden = true; - } - else { - // iterate over them and add to scroll list - LLSD::array_iterator iter = url_list.beginArray(); - while( iter != url_list.endArray() ) - { - std::string entry = *iter; - self->addWhiteListEntry( entry ); - ++iter; - } - } - }; - if ( ! enabled_overridden) data_set[ i ].ctrl_ptr->setEnabled(editable); - data_set[ i ].ctrl_ptr->setTentative( media_settings[ tentative_key ].asBoolean() ); - }; - }; - - // initial update - hides/shows status messages etc. - self->updateWhitelistEnableStatus(); -} - -//////////////////////////////////////////////////////////////////////////////// -// static -void LLPanelMediaSettingsSecurity::clearValues( void* userdata , bool editable) -{ - LLPanelMediaSettingsSecurity *self =(LLPanelMediaSettingsSecurity *)userdata; - self->mEnableWhiteList->clear(); - self->mWhiteListList->deleteAllItems(); - self->mEnableWhiteList->setEnabled(editable); - self->mWhiteListList->setEnabled(editable); -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLPanelMediaSettingsSecurity::preApply() -{ - // no-op -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLPanelMediaSettingsSecurity::getValues( LLSD &fill_me_in, bool include_tentative ) -{ - if (include_tentative || !mEnableWhiteList->getTentative()) - fill_me_in[LLMediaEntry::WHITELIST_ENABLE_KEY] = (LLSD::Boolean)mEnableWhiteList->getValue(); - - if (include_tentative || !mWhiteListList->getTentative()) - { - // iterate over white list and extract items - std::vector< LLScrollListItem* > whitelist_items = mWhiteListList->getAllData(); - std::vector< LLScrollListItem* >::iterator iter = whitelist_items.begin(); - - // *NOTE: need actually set the key to be an emptyArray(), or the merge - // we do with this LLSD will think there's nothing to change. - fill_me_in[LLMediaEntry::WHITELIST_KEY] = LLSD::emptyArray(); - while( iter != whitelist_items.end() ) - { - LLScrollListCell* cell = (*iter)->getColumn( ENTRY_COLUMN ); - std::string whitelist_url = cell->getValue().asString(); - - fill_me_in[ LLMediaEntry::WHITELIST_KEY ].append( whitelist_url ); - ++iter; - }; - } -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLPanelMediaSettingsSecurity::postApply() -{ - // no-op -} - -/////////////////////////////////////////////////////////////////////////////// -// Try to make a valid URL if a fragment ( -// white list list box widget and build a list to test against. Can also -const std::string LLPanelMediaSettingsSecurity::makeValidUrl( const std::string& src_url ) -{ - // use LLURI to determine if we have a valid scheme - LLURI candidate_url( src_url ); - if ( candidate_url.scheme().empty() ) - { - // build a URL comprised of default scheme and the original fragment - const std::string default_scheme( "http://" ); - return default_scheme + src_url; - }; - - // we *could* test the "default scheme" + "original fragment" URL again - // using LLURI to see if it's valid but I think the outcome is the same - // in either case - our only option is to return the original URL - - // we *think* the original url passed in was valid - return src_url; -} - -/////////////////////////////////////////////////////////////////////////////// -// wrapper for testing a URL against the whitelist. We grab entries from -// white list list box widget and build a list to test against. -bool LLPanelMediaSettingsSecurity::urlPassesWhiteList( const std::string& test_url ) -{ - // If the whitlelist list is tentative, it means we have multiple settings. - // In that case, we have no choice but to return true - if ( mWhiteListList->getTentative() ) return true; - - // the checkUrlAgainstWhitelist(..) function works on a vector - // of strings for the white list entries - in this panel, the white list - // is stored in the widgets themselves so we need to build something compatible. - std::vector< std::string > whitelist_strings; - whitelist_strings.clear(); // may not be required - I forget what the spec says. - - // step through whitelist widget entries and grab them as strings - std::vector< LLScrollListItem* > whitelist_items = mWhiteListList->getAllData(); - std::vector< LLScrollListItem* >::iterator iter = whitelist_items.begin(); - while( iter != whitelist_items.end() ) - { - LLScrollListCell* cell = (*iter)->getColumn( ENTRY_COLUMN ); - std::string whitelist_url = cell->getValue().asString(); - - whitelist_strings.push_back( whitelist_url ); - - ++iter; - }; - - // possible the URL is just a fragment so we validize it - const std::string valid_url = makeValidUrl( test_url ); - - // indicate if the URL passes whitelist - return LLMediaEntry::checkUrlAgainstWhitelist( valid_url, whitelist_strings ); -} - -/////////////////////////////////////////////////////////////////////////////// -// -void LLPanelMediaSettingsSecurity::updateWhitelistEnableStatus() -{ - // get the value for home URL and make it a valid URL - const std::string valid_url = makeValidUrl( mParent->getHomeUrl() ); - - // now check to see if the home url passes the whitelist in its entirity - if ( urlPassesWhiteList( valid_url ) ) - { - mEnableWhiteList->setEnabled( true ); - mHomeUrlFailsWhiteListText->setVisible( false ); - } - else - { - mEnableWhiteList->set( false ); - mEnableWhiteList->setEnabled( false ); - mHomeUrlFailsWhiteListText->setVisible( true ); - }; -} - -/////////////////////////////////////////////////////////////////////////////// -// Add an entry to the whitelist scrollbox and indicate if the current -// home URL passes this entry or not using an icon -void LLPanelMediaSettingsSecurity::addWhiteListEntry( const std::string& entry ) -{ - // grab the home url - std::string home_url( "" ); - if ( mParent ) - home_url = mParent->getHomeUrl(); - - // try to make a valid URL based on what the user entered - missing scheme for example - const std::string valid_url = makeValidUrl( home_url ); - - // check the home url against this single whitelist entry - std::vector< std::string > whitelist_entries; - whitelist_entries.push_back( entry ); - bool home_url_passes_entry = LLMediaEntry::checkUrlAgainstWhitelist( valid_url, whitelist_entries ); - - // build an icon cell based on whether or not the home url pases it or not - LLSD row; - if ( home_url_passes_entry || home_url.empty() ) - { - row[ "columns" ][ ICON_COLUMN ][ "type" ] = "icon"; - row[ "columns" ][ ICON_COLUMN ][ "value" ] = ""; - row[ "columns" ][ ICON_COLUMN ][ "width" ] = 20; - } - else - { - row[ "columns" ][ ICON_COLUMN ][ "type" ] = "icon"; - row[ "columns" ][ ICON_COLUMN ][ "value" ] = "Parcel_Exp_Color"; - row[ "columns" ][ ICON_COLUMN ][ "width" ] = 20; - }; - - // always add in the entry itself - row[ "columns" ][ ENTRY_COLUMN ][ "type" ] = "text"; - row[ "columns" ][ ENTRY_COLUMN ][ "value" ] = entry; - - // add to the white list scroll box - mWhiteListList->addElement( row ); -}; - -/////////////////////////////////////////////////////////////////////////////// -// static -void LLPanelMediaSettingsSecurity::onBtnAdd( void* userdata ) -{ - LLFloaterReg::showInstance("whitelist_entry"); -} - -/////////////////////////////////////////////////////////////////////////////// -// static -void LLPanelMediaSettingsSecurity::onBtnDel( void* userdata ) -{ - LLPanelMediaSettingsSecurity *self =(LLPanelMediaSettingsSecurity *)userdata; - - self->mWhiteListList->deleteSelectedItems(); - - // contents of whitelist changed so recheck it against home url - self->updateWhitelistEnableStatus(); -} - -//////////////////////////////////////////////////////////////////////////////// -// -void LLPanelMediaSettingsSecurity::setParent( LLFloaterMediaSettings* parent ) -{ - mParent = parent; -}; +/** + * @file llpanelmediasettingssecurity.cpp + * @brief LLPanelMediaSettingsSecurity class implementation + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelmediasettingssecurity.h" + +#include "llfloaterreg.h" +#include "llpanelcontents.h" +#include "llcheckboxctrl.h" +#include "llnotificationsutil.h" +#include "llscrolllistctrl.h" +#include "llscrolllistitem.h" +#include "lluictrlfactory.h" +#include "llwindow.h" +#include "llviewerwindow.h" +#include "llsdutil.h" +#include "llselectmgr.h" +#include "llmediaentry.h" +#include "lltextbox.h" +#include "llfloaterwhitelistentry.h" +#include "llfloatermediasettings.h" + +//////////////////////////////////////////////////////////////////////////////// +// +LLPanelMediaSettingsSecurity::LLPanelMediaSettingsSecurity() : + mParent( NULL ) +{ + mCommitCallbackRegistrar.add("Media.whitelistAdd", boost::bind(&LLPanelMediaSettingsSecurity::onBtnAdd, this)); + mCommitCallbackRegistrar.add("Media.whitelistDelete", boost::bind(&LLPanelMediaSettingsSecurity::onBtnDel, this)); + + // build dialog from XML + buildFromFile( "panel_media_settings_security.xml"); +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLPanelMediaSettingsSecurity::postBuild() +{ + mEnableWhiteList = getChild< LLCheckBoxCtrl >( LLMediaEntry::WHITELIST_ENABLE_KEY ); + mWhiteListList = getChild< LLScrollListCtrl >( LLMediaEntry::WHITELIST_KEY ); + mHomeUrlFailsWhiteListText = getChild( "home_url_fails_whitelist" ); + + setDefaultBtn("whitelist_add"); + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// virtual +LLPanelMediaSettingsSecurity::~LLPanelMediaSettingsSecurity() +{ +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLPanelMediaSettingsSecurity::draw() +{ + // housekeeping + LLPanel::draw(); +} + +//////////////////////////////////////////////////////////////////////////////// +// static +void LLPanelMediaSettingsSecurity::initValues( void* userdata, const LLSD& media_settings , bool editable) +{ + LLPanelMediaSettingsSecurity *self =(LLPanelMediaSettingsSecurity *)userdata; + std::string base_key( "" ); + std::string tentative_key( "" ); + + struct + { + std::string key_name; + LLUICtrl* ctrl_ptr; + std::string ctrl_type; + + } data_set [] = + { + { LLMediaEntry::WHITELIST_ENABLE_KEY, self->mEnableWhiteList, "LLCheckBoxCtrl" }, + { LLMediaEntry::WHITELIST_KEY, self->mWhiteListList, "LLScrollListCtrl" }, + { "", NULL , "" } + }; + + for( int i = 0; data_set[ i ].key_name.length() > 0; ++i ) + { + base_key = std::string( data_set[ i ].key_name ); + tentative_key = base_key + std::string( LLPanelContents::TENTATIVE_SUFFIX ); + + bool enabled_overridden = false; + + // TODO: CP - I bet there is a better way to do this using Boost + if ( media_settings[ base_key ].isDefined() ) + { + if ( data_set[ i ].ctrl_type == "LLCheckBoxCtrl" ) + { + static_cast< LLCheckBoxCtrl* >( data_set[ i ].ctrl_ptr )-> + setValue( media_settings[ base_key ].asBoolean() ); + } + else + if ( data_set[ i ].ctrl_type == "LLScrollListCtrl" ) + { + // get control + LLScrollListCtrl* list = static_cast< LLScrollListCtrl* >( data_set[ i ].ctrl_ptr ); + list->deleteAllItems(); + + // points to list of white list URLs + LLSD url_list = media_settings[ base_key ]; + + // better be the whitelist + llassert(data_set[ i ].ctrl_ptr == self->mWhiteListList); + + // If tentative, don't add entries + if (media_settings[ tentative_key ].asBoolean()) + { + self->mWhiteListList->setEnabled(false); + enabled_overridden = true; + } + else { + // iterate over them and add to scroll list + LLSD::array_iterator iter = url_list.beginArray(); + while( iter != url_list.endArray() ) + { + std::string entry = *iter; + self->addWhiteListEntry( entry ); + ++iter; + } + } + }; + if ( ! enabled_overridden) data_set[ i ].ctrl_ptr->setEnabled(editable); + data_set[ i ].ctrl_ptr->setTentative( media_settings[ tentative_key ].asBoolean() ); + }; + }; + + // initial update - hides/shows status messages etc. + self->updateWhitelistEnableStatus(); +} + +//////////////////////////////////////////////////////////////////////////////// +// static +void LLPanelMediaSettingsSecurity::clearValues( void* userdata , bool editable) +{ + LLPanelMediaSettingsSecurity *self =(LLPanelMediaSettingsSecurity *)userdata; + self->mEnableWhiteList->clear(); + self->mWhiteListList->deleteAllItems(); + self->mEnableWhiteList->setEnabled(editable); + self->mWhiteListList->setEnabled(editable); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLPanelMediaSettingsSecurity::preApply() +{ + // no-op +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLPanelMediaSettingsSecurity::getValues( LLSD &fill_me_in, bool include_tentative ) +{ + if (include_tentative || !mEnableWhiteList->getTentative()) + fill_me_in[LLMediaEntry::WHITELIST_ENABLE_KEY] = (LLSD::Boolean)mEnableWhiteList->getValue(); + + if (include_tentative || !mWhiteListList->getTentative()) + { + // iterate over white list and extract items + std::vector< LLScrollListItem* > whitelist_items = mWhiteListList->getAllData(); + std::vector< LLScrollListItem* >::iterator iter = whitelist_items.begin(); + + // *NOTE: need actually set the key to be an emptyArray(), or the merge + // we do with this LLSD will think there's nothing to change. + fill_me_in[LLMediaEntry::WHITELIST_KEY] = LLSD::emptyArray(); + while( iter != whitelist_items.end() ) + { + LLScrollListCell* cell = (*iter)->getColumn( ENTRY_COLUMN ); + std::string whitelist_url = cell->getValue().asString(); + + fill_me_in[ LLMediaEntry::WHITELIST_KEY ].append( whitelist_url ); + ++iter; + }; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLPanelMediaSettingsSecurity::postApply() +{ + // no-op +} + +/////////////////////////////////////////////////////////////////////////////// +// Try to make a valid URL if a fragment ( +// white list list box widget and build a list to test against. Can also +const std::string LLPanelMediaSettingsSecurity::makeValidUrl( const std::string& src_url ) +{ + // use LLURI to determine if we have a valid scheme + LLURI candidate_url( src_url ); + if ( candidate_url.scheme().empty() ) + { + // build a URL comprised of default scheme and the original fragment + const std::string default_scheme( "http://" ); + return default_scheme + src_url; + }; + + // we *could* test the "default scheme" + "original fragment" URL again + // using LLURI to see if it's valid but I think the outcome is the same + // in either case - our only option is to return the original URL + + // we *think* the original url passed in was valid + return src_url; +} + +/////////////////////////////////////////////////////////////////////////////// +// wrapper for testing a URL against the whitelist. We grab entries from +// white list list box widget and build a list to test against. +bool LLPanelMediaSettingsSecurity::urlPassesWhiteList( const std::string& test_url ) +{ + // If the whitlelist list is tentative, it means we have multiple settings. + // In that case, we have no choice but to return true + if ( mWhiteListList->getTentative() ) return true; + + // the checkUrlAgainstWhitelist(..) function works on a vector + // of strings for the white list entries - in this panel, the white list + // is stored in the widgets themselves so we need to build something compatible. + std::vector< std::string > whitelist_strings; + whitelist_strings.clear(); // may not be required - I forget what the spec says. + + // step through whitelist widget entries and grab them as strings + std::vector< LLScrollListItem* > whitelist_items = mWhiteListList->getAllData(); + std::vector< LLScrollListItem* >::iterator iter = whitelist_items.begin(); + while( iter != whitelist_items.end() ) + { + LLScrollListCell* cell = (*iter)->getColumn( ENTRY_COLUMN ); + std::string whitelist_url = cell->getValue().asString(); + + whitelist_strings.push_back( whitelist_url ); + + ++iter; + }; + + // possible the URL is just a fragment so we validize it + const std::string valid_url = makeValidUrl( test_url ); + + // indicate if the URL passes whitelist + return LLMediaEntry::checkUrlAgainstWhitelist( valid_url, whitelist_strings ); +} + +/////////////////////////////////////////////////////////////////////////////// +// +void LLPanelMediaSettingsSecurity::updateWhitelistEnableStatus() +{ + // get the value for home URL and make it a valid URL + const std::string valid_url = makeValidUrl( mParent->getHomeUrl() ); + + // now check to see if the home url passes the whitelist in its entirity + if ( urlPassesWhiteList( valid_url ) ) + { + mEnableWhiteList->setEnabled( true ); + mHomeUrlFailsWhiteListText->setVisible( false ); + } + else + { + mEnableWhiteList->set( false ); + mEnableWhiteList->setEnabled( false ); + mHomeUrlFailsWhiteListText->setVisible( true ); + }; +} + +/////////////////////////////////////////////////////////////////////////////// +// Add an entry to the whitelist scrollbox and indicate if the current +// home URL passes this entry or not using an icon +void LLPanelMediaSettingsSecurity::addWhiteListEntry( const std::string& entry ) +{ + // grab the home url + std::string home_url( "" ); + if ( mParent ) + home_url = mParent->getHomeUrl(); + + // try to make a valid URL based on what the user entered - missing scheme for example + const std::string valid_url = makeValidUrl( home_url ); + + // check the home url against this single whitelist entry + std::vector< std::string > whitelist_entries; + whitelist_entries.push_back( entry ); + bool home_url_passes_entry = LLMediaEntry::checkUrlAgainstWhitelist( valid_url, whitelist_entries ); + + // build an icon cell based on whether or not the home url pases it or not + LLSD row; + if ( home_url_passes_entry || home_url.empty() ) + { + row[ "columns" ][ ICON_COLUMN ][ "type" ] = "icon"; + row[ "columns" ][ ICON_COLUMN ][ "value" ] = ""; + row[ "columns" ][ ICON_COLUMN ][ "width" ] = 20; + } + else + { + row[ "columns" ][ ICON_COLUMN ][ "type" ] = "icon"; + row[ "columns" ][ ICON_COLUMN ][ "value" ] = "Parcel_Exp_Color"; + row[ "columns" ][ ICON_COLUMN ][ "width" ] = 20; + }; + + // always add in the entry itself + row[ "columns" ][ ENTRY_COLUMN ][ "type" ] = "text"; + row[ "columns" ][ ENTRY_COLUMN ][ "value" ] = entry; + + // add to the white list scroll box + mWhiteListList->addElement( row ); +}; + +/////////////////////////////////////////////////////////////////////////////// +// static +void LLPanelMediaSettingsSecurity::onBtnAdd( void* userdata ) +{ + LLFloaterReg::showInstance("whitelist_entry"); +} + +/////////////////////////////////////////////////////////////////////////////// +// static +void LLPanelMediaSettingsSecurity::onBtnDel( void* userdata ) +{ + LLPanelMediaSettingsSecurity *self =(LLPanelMediaSettingsSecurity *)userdata; + + self->mWhiteListList->deleteSelectedItems(); + + // contents of whitelist changed so recheck it against home url + self->updateWhitelistEnableStatus(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLPanelMediaSettingsSecurity::setParent( LLFloaterMediaSettings* parent ) +{ + mParent = parent; +}; diff --git a/indra/newview/llpanelmediasettingssecurity.h b/indra/newview/llpanelmediasettingssecurity.h index ce55120b62..23c68a8050 100644 --- a/indra/newview/llpanelmediasettingssecurity.h +++ b/indra/newview/llpanelmediasettingssecurity.h @@ -1,82 +1,82 @@ -/** - * @file llpanelmediasettingssecurity.h - * @brief LLPanelMediaSettingsSecurity class definition - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELMEDIAMEDIASETTINGSSECURITY_H -#define LL_LLPANELMEDIAMEDIASETTINGSSECURITY_H - -#include "llpanel.h" - -class LLCheckBoxCtrl; -class LLScrollListCtrl; -class LLTextBox; -class LLFloaterMediaSettings; - -class LLPanelMediaSettingsSecurity : public LLPanel -{ -public: - LLPanelMediaSettingsSecurity(); - ~LLPanelMediaSettingsSecurity(); - - bool postBuild(); - virtual void draw(); - - // XXX TODO: put these into a common parent class? - // Hook that the floater calls before applying changes from the panel - void preApply(); - // Function that asks the panel to fill in values associated with the panel - // 'include_tentative' means fill in tentative values as well, otherwise do not - void getValues(LLSD &fill_me_in, bool include_tentative = true); - // Hook that the floater calls after applying changes to the panel - void postApply(); - - static void initValues( void* userdata, const LLSD& media_settings, bool editable); - static void clearValues( void* userdata, bool editable); - void addWhiteListEntry( const std::string& url ); - void setParent( LLFloaterMediaSettings* parent ); - bool urlPassesWhiteList( const std::string& test_url ); - const std::string makeValidUrl( const std::string& src_url ); - - void updateWhitelistEnableStatus(); - -protected: - LLFloaterMediaSettings* mParent; - -private: - enum ColumnIndex - { - ICON_COLUMN = 0, - ENTRY_COLUMN = 1, - }; - - LLCheckBoxCtrl* mEnableWhiteList; - LLScrollListCtrl* mWhiteListList; - LLTextBox* mHomeUrlFailsWhiteListText; - - static void onBtnAdd(void*); - static void onBtnDel(void*); -}; - -#endif // LL_LLPANELMEDIAMEDIASETTINGSSECURITY_H +/** + * @file llpanelmediasettingssecurity.h + * @brief LLPanelMediaSettingsSecurity class definition + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELMEDIAMEDIASETTINGSSECURITY_H +#define LL_LLPANELMEDIAMEDIASETTINGSSECURITY_H + +#include "llpanel.h" + +class LLCheckBoxCtrl; +class LLScrollListCtrl; +class LLTextBox; +class LLFloaterMediaSettings; + +class LLPanelMediaSettingsSecurity : public LLPanel +{ +public: + LLPanelMediaSettingsSecurity(); + ~LLPanelMediaSettingsSecurity(); + + bool postBuild(); + virtual void draw(); + + // XXX TODO: put these into a common parent class? + // Hook that the floater calls before applying changes from the panel + void preApply(); + // Function that asks the panel to fill in values associated with the panel + // 'include_tentative' means fill in tentative values as well, otherwise do not + void getValues(LLSD &fill_me_in, bool include_tentative = true); + // Hook that the floater calls after applying changes to the panel + void postApply(); + + static void initValues( void* userdata, const LLSD& media_settings, bool editable); + static void clearValues( void* userdata, bool editable); + void addWhiteListEntry( const std::string& url ); + void setParent( LLFloaterMediaSettings* parent ); + bool urlPassesWhiteList( const std::string& test_url ); + const std::string makeValidUrl( const std::string& src_url ); + + void updateWhitelistEnableStatus(); + +protected: + LLFloaterMediaSettings* mParent; + +private: + enum ColumnIndex + { + ICON_COLUMN = 0, + ENTRY_COLUMN = 1, + }; + + LLCheckBoxCtrl* mEnableWhiteList; + LLScrollListCtrl* mWhiteListList; + LLTextBox* mHomeUrlFailsWhiteListText; + + static void onBtnAdd(void*); + static void onBtnDel(void*); +}; + +#endif // LL_LLPANELMEDIAMEDIASETTINGSSECURITY_H diff --git a/indra/newview/llpanelnearbymedia.cpp b/indra/newview/llpanelnearbymedia.cpp index 22ca26e500..aa60c5cf6c 100644 --- a/indra/newview/llpanelnearbymedia.cpp +++ b/indra/newview/llpanelnearbymedia.cpp @@ -1,1330 +1,1330 @@ -/** - * @file llpanelnearbymedia.cpp - * @brief Management interface for muting and controlling nearby media - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelnearbymedia.h" - -#include "llaudioengine.h" -#include "llbase64.h" -#include "llcheckboxctrl.h" -#include "llclipboard.h" -#include "llcombobox.h" -#include "llresizebar.h" -#include "llresizehandle.h" -#include "llscrolllistctrl.h" -#include "llscrolllistitem.h" -#include "llscrolllistcell.h" -#include "llslider.h" -#include "llsliderctrl.h" -#include "llagent.h" -#include "llagentui.h" -#include "llbutton.h" -#include "lltextbox.h" -#include "llviewermedia.h" -#include "llviewerparcelaskplay.h" -#include "llviewerparcelmedia.h" -#include "llviewerregion.h" -#include "llviewermediafocus.h" -#include "llviewerparcelmgr.h" -#include "llparcel.h" -#include "llpluginclassmedia.h" -#include "llvovolume.h" -#include "llstatusbar.h" -#include "llsdutil.h" -#include "lltoggleablemenu.h" -#include "llvieweraudio.h" -#include "llviewermenu.h" - -#include "llfloaterreg.h" -#include "llfloaterpreference.h" // for the gear icon -#include "lltabcontainer.h" - -#include - -extern LLControlGroup gSavedSettings; - -static const LLUUID PARCEL_MEDIA_LIST_ITEM_UUID = LLUUID("CAB5920F-E484-4233-8621-384CF373A321"); -static const LLUUID PARCEL_AUDIO_LIST_ITEM_UUID = LLUUID("DF4B020D-8A24-4B95-AB5D-CA970D694822"); - -// -// LLPanelNearByMedia -// - - -LLPanelNearByMedia::LLPanelNearByMedia() -: mMediaList(NULL), - mEnableAllCtrl(NULL), - mDebugInfoVisible(false), - mParcelMediaItem(NULL), - mParcelAudioItem(NULL), - mMoreLessBtn(NULL) -{ - // This is just an initial value, mParcelAudioAutoStart does not affect ParcelMediaAutoPlayEnable - mParcelAudioAutoStart = gSavedSettings.getS32("ParcelMediaAutoPlayEnable") != 0 - && gSavedSettings.getBOOL("MediaTentativeAutoPlay"); - - gSavedSettings.getControl("ParcelMediaAutoPlayEnable")->getSignal()->connect(boost::bind(&LLPanelNearByMedia::handleMediaAutoPlayChanged, this, _2)); - - mCommitCallbackRegistrar.add("MediaListCtrl.EnableAll", boost::bind(&LLPanelNearByMedia::onClickEnableAll, this)); - mCommitCallbackRegistrar.add("MediaListCtrl.DisableAll", boost::bind(&LLPanelNearByMedia::onClickDisableAll, this)); - mCommitCallbackRegistrar.add("MediaListCtrl.GoMediaPrefs", boost::bind(&LLPanelNearByMedia::onAdvancedButtonClick, this)); - mCommitCallbackRegistrar.add("MediaListCtrl.MoreLess", boost::bind(&LLPanelNearByMedia::onMoreLess, this)); - mCommitCallbackRegistrar.add("SelectedMediaCtrl.Stop", boost::bind(&LLPanelNearByMedia::onClickSelectedMediaStop, this)); - mCommitCallbackRegistrar.add("SelectedMediaCtrl.Play", boost::bind(&LLPanelNearByMedia::onClickSelectedMediaPlay, this)); - mCommitCallbackRegistrar.add("SelectedMediaCtrl.Pause", boost::bind(&LLPanelNearByMedia::onClickSelectedMediaPause, this)); - mCommitCallbackRegistrar.add("SelectedMediaCtrl.Mute", boost::bind(&LLPanelNearByMedia::onClickSelectedMediaMute, this)); - mCommitCallbackRegistrar.add("SelectedMediaCtrl.Volume", boost::bind(&LLPanelNearByMedia::onCommitSelectedMediaVolume, this)); - mCommitCallbackRegistrar.add("SelectedMediaCtrl.Zoom", boost::bind(&LLPanelNearByMedia::onClickSelectedMediaZoom, this)); - mCommitCallbackRegistrar.add("SelectedMediaCtrl.Unzoom", boost::bind(&LLPanelNearByMedia::onClickSelectedMediaUnzoom, this)); - - // Context menu handler. - mCommitCallbackRegistrar.add("SelectedMediaCtrl.Action", - [this](LLUICtrl* ctrl, const LLSD& data) - { - onMenuAction(data); - }); - mEnableCallbackRegistrar.add("SelectedMediaCtrl.Visible", - [this](LLUICtrl* ctrl, const LLSD& data) - { - return onMenuVisible(data); - }); - - buildFromFile( "panel_nearby_media.xml"); -} - -LLPanelNearByMedia::~LLPanelNearByMedia() -{ -} - -bool LLPanelNearByMedia::postBuild() -{ - LLPanelPulldown::postBuild(); - - const S32 RESIZE_BAR_THICKNESS = 6; - LLResizeBar::Params p; - p.rect = LLRect(0, RESIZE_BAR_THICKNESS, getRect().getWidth(), 0); - p.name = "resizebar_bottom"; - p.min_size = getRect().getHeight(); - p.side = LLResizeBar::BOTTOM; - p.resizing_view = this; - addChild( LLUICtrlFactory::create(p) ); - - p.rect = LLRect( 0, getRect().getHeight(), RESIZE_BAR_THICKNESS, 0); - p.name = "resizebar_left"; - p.min_size = getRect().getWidth(); - p.side = LLResizeBar::LEFT; - addChild( LLUICtrlFactory::create(p) ); - - LLResizeHandle::Params resize_handle_p; - resize_handle_p.rect = LLRect( 0, RESIZE_HANDLE_HEIGHT, RESIZE_HANDLE_WIDTH, 0 ); - resize_handle_p.mouse_opaque(false); - resize_handle_p.min_width(getRect().getWidth()); - resize_handle_p.min_height(getRect().getHeight()); - resize_handle_p.corner(LLResizeHandle::LEFT_BOTTOM); - addChild(LLUICtrlFactory::create(resize_handle_p)); - - mNearbyMediaPanel = getChild("nearby_media_panel"); - mMediaList = getChild("media_list"); - mEnableAllCtrl = getChild("all_nearby_media_enable_btn"); - mDisableAllCtrl = getChild("all_nearby_media_disable_btn"); - mShowCtrl = getChild("show_combo"); - - // Dynamic (selection-dependent) controls - mStopCtrl = getChild("stop"); - mPlayCtrl = getChild("play"); - mPauseCtrl = getChild("pause"); - mMuteCtrl = getChild("mute"); - mVolumeSliderCtrl = getChild("volume_slider_ctrl"); - mZoomCtrl = getChild("zoom"); - mUnzoomCtrl = getChild("unzoom"); - mVolumeSlider = getChild("volume_slider"); - mMuteBtn = getChild("mute_btn"); - mMoreLessBtn = getChild("more_btn"); - - mEmptyNameString = getString("empty_item_text"); - mParcelMediaName = getString("parcel_media_name"); - mParcelAudioName = getString("parcel_audio_name"); - mPlayingString = getString("playing_suffix"); - - mMediaList->setDoubleClickCallback(onZoomMedia, this); - mMediaList->sortByColumnIndex(PROXIMITY_COLUMN, true); - mMediaList->sortByColumnIndex(VISIBILITY_COLUMN, false); - - refreshList(); - updateControls(); - updateColumns(); - - LLView* minimized_controls = getChildView("minimized_controls"); - mMoreRect = getRect(); - mLessRect = getRect(); - mLessRect.mBottom = minimized_controls->getRect().mBottom; - - mMoreLessBtn->setVisible(false); - onMoreLess(); - - mContextMenu = LLUICtrlFactory::getInstance()->createFromFile( - "menu_nearby_media.xml", - gMenuHolder, - LLViewerMenuHolderGL::child_registry_t::instance()); - - return true; -} - -void LLPanelNearByMedia::handleMediaAutoPlayChanged(const LLSD& newvalue) -{ - // update mParcelAudioAutoStartMode if "ParcelMediaAutoPlayEnable" changes - S32 value = gSavedSettings.getS32("ParcelMediaAutoPlayEnable"); - mParcelAudioAutoStart = value != 0 - && gSavedSettings.getBOOL("MediaTentativeAutoPlay"); - - LLViewerParcelAskPlay *inst = LLViewerParcelAskPlay::getInstance(); - if (value == 2 && !inst->hasData()) - { - // Init if nessesary - inst->loadSettings(); - } - inst->cancelNotification(); -} - -/*virtual*/ -void LLPanelNearByMedia::reshape(S32 width, S32 height, bool called_from_parent) -{ - LLPanelPulldown::reshape(width, height, called_from_parent); - - if (mMoreLessBtn && mMoreLessBtn->getValue().asBoolean()) - { - mMoreRect = getRect(); - } - -} - -/*virtual*/ -void LLPanelNearByMedia::draw() -{ - // keep bottom of panel on screen - LLRect screen_rect = calcScreenRect(); - if (screen_rect.mBottom < 0) - { - LLRect new_rect = getRect(); - new_rect.mBottom += 0 - screen_rect.mBottom; - setShape(new_rect); - } - - refreshList(); - updateControls(); - - LLPanelPulldown::draw(); -} - -/*virtual*/ -bool LLPanelNearByMedia::handleHover(S32 x, S32 y, MASK mask) -{ - LLPanelPulldown::handleHover(x, y, mask); - - // If we are hovering over this panel, make sure to clear any hovered media - // ID. Note that the more general solution would be to clear this ID when - // the mouse leaves the in-scene view, but that proved to be problematic. - // See EXT-5517 - LLViewerMediaFocus::getInstance()->clearHover(); - - // Always handle - return true; -} - -bool LLPanelNearByMedia::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - S32 x_list, y_list; - localPointToOtherView(x, y, &x_list, &y_list, mMediaList); - if (mMoreLessBtn->getToggleState() - && mMediaList->pointInView(x_list, y_list)) - { - LLScrollListItem* hit_item = mMediaList->hitItem(x_list, y_list); - bool selected = hit_item && hit_item->getSelected(); - if (!selected) - { - selected = mMediaList->selectItemAt(x_list, y_list, mask); - } - - if (selected && mContextMenu) - { - mContextMenu->buildDrawLabels(); - mContextMenu->updateParent(LLMenuGL::sMenuContainer); - LLMenuGL::showPopup(this, mContextMenu, x, y); - return true; - } - } - - return LLPanelPulldown::handleRightMouseDown(x, y, mask); -} - - -void LLPanelNearByMedia::onVisibilityChange(bool new_visibility) -{ - if (!new_visibility && mContextMenu->getVisible()) - { - gMenuHolder->hideMenus(); - } - LLPanelPulldown::onVisibilityChange(new_visibility); -} - -bool LLPanelNearByMedia::getParcelAudioAutoStart() -{ - return mParcelAudioAutoStart; -} - -LLScrollListItem* LLPanelNearByMedia::addListItem(const LLUUID &id) -{ - if (NULL == mMediaList) return NULL; - - // Just set up the columns -- the values will be filled in by updateListItem(). - - LLSD row; - row["id"] = id; - - LLSD &columns = row["columns"]; - - columns[CHECKBOX_COLUMN]["column"] = "media_checkbox_ctrl"; - columns[CHECKBOX_COLUMN]["type"] = "checkbox"; - //if(mDebugInfoVisible) - { - columns[PROXIMITY_COLUMN]["column"] = "media_proximity"; - columns[PROXIMITY_COLUMN]["value"] = ""; - columns[VISIBILITY_COLUMN]["column"] = "media_visibility"; - columns[VISIBILITY_COLUMN]["value"] = ""; - columns[CLASS_COLUMN]["column"] = "media_class"; - columns[CLASS_COLUMN]["type"] = "text"; - columns[CLASS_COLUMN]["value"] = ""; - } - columns[NAME_COLUMN]["column"] = "media_name"; - columns[NAME_COLUMN]["type"] = "text"; - columns[NAME_COLUMN]["value"] = ""; - //if(mDebugInfoVisible) - { - columns[DEBUG_COLUMN]["column"] = "media_debug"; - columns[DEBUG_COLUMN]["type"] = "text"; - columns[DEBUG_COLUMN]["value"] = ""; - } - - LLScrollListItem* new_item = mMediaList->addElement(row); - if (NULL != new_item) - { - LLScrollListCheck* scroll_list_check = dynamic_cast(new_item->getColumn(CHECKBOX_COLUMN)); - if (scroll_list_check) - { - LLCheckBoxCtrl *check = scroll_list_check->getCheckBox(); - check->setCommitCallback(boost::bind(&LLPanelNearByMedia::onCheckItem, this, _1, id)); - } - } - return new_item; -} - -void LLPanelNearByMedia::updateListItem(LLScrollListItem* item, LLViewerMediaImpl* impl) -{ - std::string item_name; - std::string item_tooltip; - std::string debug_str; - LLPanelNearByMedia::MediaClass media_class = MEDIA_CLASS_ALL; - - getNameAndUrlHelper(impl, item_name, item_tooltip, mEmptyNameString); - // Focused - if (impl->hasFocus()) - { - media_class = MEDIA_CLASS_FOCUSED; - } - // Is attached to another avatar? - else if (impl->isAttachedToAnotherAvatar()) - { - media_class = MEDIA_CLASS_ON_OTHERS; - } - // Outside agent parcel - else if (!impl->isInAgentParcel()) - { - media_class = MEDIA_CLASS_OUTSIDE_PARCEL; - } - else { - // inside parcel - media_class = MEDIA_CLASS_WITHIN_PARCEL; - } - - if(mDebugInfoVisible) - { - debug_str += llformat("%g/", (float)impl->getInterest()); - - // proximity distance is actually distance squared -- display it as straight distance. - debug_str += llformat("%g/", (F32) sqrt(impl->getProximityDistance())); - - // s += llformat("%g/", (float)impl->getCPUUsage()); - // s += llformat("%g/", (float)impl->getApproximateTextureInterest()); - debug_str += llformat("%g/", (float)(NULL == impl->getSomeObject()) ? 0.0 : impl->getSomeObject()->getPixelArea()); - - debug_str += LLPluginClassMedia::priorityToString(impl->getPriority()); - - if(impl->hasMedia()) - { - debug_str += '@'; - } - else if(impl->isPlayable()) - { - debug_str += '+'; - } - else if(impl->isForcedUnloaded()) - { - debug_str += '!'; - } - } - - updateListItem(item, - item_name, - item_tooltip, - impl->getProximity(), - impl->isMediaDisabled(), - impl->hasMedia(), - impl->isMediaTimeBased() && impl->isMediaPlaying(), - media_class, - debug_str); -} - -void LLPanelNearByMedia::updateListItem(LLScrollListItem* item, - const std::string &item_name, - const std::string &item_tooltip, - S32 proximity, - bool is_disabled, - bool has_media, - bool is_time_based_and_playing, - LLPanelNearByMedia::MediaClass media_class, - const std::string &debug_str) -{ - LLScrollListCell* cell = item->getColumn(PROXIMITY_COLUMN); - if(cell) - { - // since we are forced to sort by text, encode sort order as string - std::string proximity_string = STRINGIZE(proximity); - std::string old_proximity_string = cell->getValue().asString(); - if(proximity_string != old_proximity_string) - { - cell->setValue(proximity_string); - mMediaList->setNeedsSort(true); - } - } - - cell = item->getColumn(CHECKBOX_COLUMN); - if(cell) - { - cell->setValue(!is_disabled); - } - - cell = item->getColumn(VISIBILITY_COLUMN); - if(cell) - { - S32 old_visibility = cell->getValue(); - // *HACK ALERT: force ordering of Media before Audio before the rest of the list - S32 new_visibility = - item->getUUID() == PARCEL_MEDIA_LIST_ITEM_UUID ? 3 - : item->getUUID() == PARCEL_AUDIO_LIST_ITEM_UUID ? 2 - : (has_media) ? 1 - : ((is_disabled) ? 0 - : -1); - cell->setValue(STRINGIZE(new_visibility)); - if (new_visibility != old_visibility) - { - mMediaList->setNeedsSort(true); - } - } - - cell = item->getColumn(NAME_COLUMN); - if(cell) - { - std::string name = item_name; - std::string old_name = cell->getValue().asString(); - if (has_media) - { - name += " " + mPlayingString; - } - if (name != old_name) - { - cell->setValue(name); - } - cell->setToolTip(item_tooltip); - - // *TODO: Make these font styles/colors configurable via XUI - U8 font_style = LLFontGL::NORMAL; - LLColor4 cell_color = LLColor4::white; - - // Only colorize by class in debug - if (mDebugInfoVisible) - { - switch (media_class) { - case MEDIA_CLASS_FOCUSED: - cell_color = LLColor4::yellow; - break; - case MEDIA_CLASS_ON_OTHERS: - cell_color = LLColor4::red; - break; - case MEDIA_CLASS_OUTSIDE_PARCEL: - cell_color = LLColor4::orange; - break; - case MEDIA_CLASS_WITHIN_PARCEL: - default: - break; - } - } - if (is_disabled) - { - if (mDebugInfoVisible) - { - font_style |= LLFontGL::ITALIC; - cell_color = LLColor4::black; - } - else { - // Dim it if it is disabled - cell_color.setAlpha(0.25); - } - } - // Dim it if it isn't "showing" - else if (!has_media) - { - cell_color.setAlpha(0.25); - } - // Bold it if it is time-based media and it is playing - else if (is_time_based_and_playing) - { - if (mDebugInfoVisible) font_style |= LLFontGL::BOLD; - } - cell->setColor(cell_color); - LLScrollListText *text_cell = dynamic_cast (cell); - if (text_cell) - { - text_cell->setFontStyle(font_style); - } - } - - cell = item->getColumn(CLASS_COLUMN); - if(cell) - { - // TODO: clean this up! - cell->setValue(STRINGIZE(media_class)); - } - - if(mDebugInfoVisible) - { - cell = item->getColumn(DEBUG_COLUMN); - if(cell) - { - cell->setValue(debug_str); - } - } -} - -void LLPanelNearByMedia::removeListItem(const LLUUID &id) -{ - if (NULL == mMediaList) return; - - mMediaList->deleteSingleItem(mMediaList->getItemIndex(id)); - mMediaList->updateLayout(); -} - -void LLPanelNearByMedia::refreshParcelItems() -{ - // - // First add/remove the "fake" items Parcel Media and Parcel Audio. - // These items will have special UUIDs - // PARCEL_MEDIA_LIST_ITEM_UUID - // PARCEL_AUDIO_LIST_ITEM_UUID - // - // Get the filter choice. - const LLSD &choice_llsd = mShowCtrl->getSelectedValue(); - MediaClass choice = (MediaClass)choice_llsd.asInteger(); - // Only show "special parcel items" if "All" or "Within" filter - // (and if media is "enabled") - bool should_include = (choice == MEDIA_CLASS_ALL || choice == MEDIA_CLASS_WITHIN_PARCEL); - LLViewerMedia* media_inst = LLViewerMedia::getInstance(); - - // First Parcel Media: add or remove it as necessary - if (gSavedSettings.getBOOL("AudioStreamingMedia") && should_include && media_inst->hasParcelMedia()) - { - // Yes, there is parcel media. - if (NULL == mParcelMediaItem) - { - mParcelMediaItem = addListItem(PARCEL_MEDIA_LIST_ITEM_UUID); - mMediaList->setNeedsSort(true); - } - } - else { - if (NULL != mParcelMediaItem) { - removeListItem(PARCEL_MEDIA_LIST_ITEM_UUID); - mParcelMediaItem = NULL; - mMediaList->setNeedsSort(true); - } - } - - // ... then update it - if (NULL != mParcelMediaItem) - { - std::string name, url, tooltip; - getNameAndUrlHelper(LLViewerParcelMedia::getInstance()->getParcelMedia(), name, url, ""); - if (name.empty() || name == url) - { - tooltip = url; - } - else - { - tooltip = name + " : " + url; - } - LLViewerMediaImpl *impl = LLViewerParcelMedia::getInstance()->getParcelMedia(); - updateListItem(mParcelMediaItem, - mParcelMediaName, - tooltip, - -2, // Proximity closer than anything else, before Parcel Audio - impl == NULL || impl->isMediaDisabled(), - impl != NULL && !LLViewerParcelMedia::getInstance()->getURL().empty(), - impl != NULL && impl->isMediaTimeBased() && impl->isMediaPlaying(), - MEDIA_CLASS_ALL, - "parcel media"); - } - - // Next Parcel Audio: add or remove it as necessary (don't show if disabled in prefs) - if (should_include && media_inst->hasParcelAudio() && gSavedSettings.getBOOL("AudioStreamingMusic")) - { - // Yes, there is parcel audio. - if (NULL == mParcelAudioItem) - { - mParcelAudioItem = addListItem(PARCEL_AUDIO_LIST_ITEM_UUID); - mMediaList->setNeedsSort(true); - } - } - else { - if (NULL != mParcelAudioItem) { - removeListItem(PARCEL_AUDIO_LIST_ITEM_UUID); - mParcelAudioItem = NULL; - mMediaList->setNeedsSort(true); - } - } - - // ... then update it - if (NULL != mParcelAudioItem) - { - bool is_playing = media_inst->isParcelAudioPlaying(); - - std::string url; - url = media_inst->getParcelAudioURL(); - - updateListItem(mParcelAudioItem, - mParcelAudioName, - url, - -1, // Proximity after Parcel Media, but closer than anything else - (!is_playing), - is_playing, - is_playing, - MEDIA_CLASS_ALL, - "parcel audio"); - } -} - -void LLPanelNearByMedia::refreshList() -{ - bool all_items_deleted = false; - - if(!mMediaList) - { - // None of this makes any sense if the media list isn't there. - return; - } - - // Check whether the debug column has been shown/hidden. - bool debug_info_visible = gSavedSettings.getBOOL("MediaPerformanceManagerDebug"); - if(debug_info_visible != mDebugInfoVisible) - { - mDebugInfoVisible = debug_info_visible; - - // Clear all items so the list gets regenerated. - mMediaList->deleteAllItems(); - mParcelAudioItem = NULL; - mParcelMediaItem = NULL; - all_items_deleted = true; - - updateColumns(); - } - - refreshParcelItems(); - - // Get the canonical list from LLViewerMedia - LLViewerMedia* media_inst = LLViewerMedia::getInstance(); - LLViewerMedia::impl_list impls = media_inst->getPriorityList(); - LLViewerMedia::impl_list::iterator priority_iter; - - U32 disabled_count = 0; - - // iterate over the impl list, creating rows as necessary. - for(priority_iter = impls.begin(); priority_iter != impls.end(); priority_iter++) - { - LLViewerMediaImpl *impl = *priority_iter; - - // If we just emptied out the list, every flag needs to be reset. - if(all_items_deleted) - { - impl->setInNearbyMediaList(false); - } - - if (!impl->isParcelMedia()) - { - LLUUID media_id = impl->getMediaTextureID(); - S32 proximity = impl->getProximity(); - // This is expensive (i.e. a linear search) -- don't use it here. We now use mInNearbyMediaList instead. - //S32 index = mMediaList->getItemIndex(media_id); - if (proximity < 0 || !shouldShow(impl)) - { - if (impl->getInNearbyMediaList()) - { - // There's a row for this impl -- remove it. - removeListItem(media_id); - impl->setInNearbyMediaList(false); - } - } - else - { - if (!impl->getInNearbyMediaList()) - { - // We don't have a row for this impl -- add one. - addListItem(media_id); - impl->setInNearbyMediaList(true); - } - } - // Update counts - if (impl->isMediaDisabled()) - { - disabled_count++; - } - } - } - mDisableAllCtrl->setEnabled((gSavedSettings.getBOOL("AudioStreamingMusic") || - gSavedSettings.getBOOL("AudioStreamingMedia")) && - (media_inst->isAnyMediaShowing() || - media_inst->isParcelMediaPlaying() || - media_inst->isParcelAudioPlaying())); - - mEnableAllCtrl->setEnabled( (gSavedSettings.getBOOL("AudioStreamingMusic") || - gSavedSettings.getBOOL("AudioStreamingMedia")) && - (disabled_count > 0 || - // parcel media (if we have it, and it isn't playing, enable "start") - (media_inst->hasParcelMedia() && ! media_inst->isParcelMediaPlaying()) || - // parcel audio (if we have it, and it isn't playing, enable "start") - (media_inst->hasParcelAudio() && ! media_inst->isParcelAudioPlaying()))); - - // Iterate over the rows in the control, updating ones whose impl exists, and deleting ones whose impl has gone away. - std::vector items = mMediaList->getAllData(); - - for (std::vector::iterator item_it = items.begin(); - item_it != items.end(); - ++item_it) - { - LLScrollListItem* item = (*item_it); - LLUUID row_id = item->getUUID(); - - if (row_id != PARCEL_MEDIA_LIST_ITEM_UUID && - row_id != PARCEL_AUDIO_LIST_ITEM_UUID) - { - LLViewerMediaImpl* impl = media_inst->getMediaImplFromTextureID(row_id); - if(impl) - { - updateListItem(item, impl); - } - else - { - // This item's impl has been deleted -- remove the row. - // Removing the row won't throw off our iteration, since we have a local copy of the array. - // We just need to make sure we don't access this item after the delete. - removeListItem(row_id); - } - } - } - - // Set the selection to whatever media impl the media focus/hover is on. - // This is an experiment, and can be removed by ifdefing out these 4 lines. - LLUUID media_target = LLViewerMediaFocus::getInstance()->getControlsMediaID(); - if(media_target.notNull()) - { - mMediaList->selectByID(media_target); - } -} - -void LLPanelNearByMedia::updateColumns() -{ - if (!mDebugInfoVisible) - { - if (mMediaList->getColumn(CHECKBOX_COLUMN)) mMediaList->getColumn(VISIBILITY_COLUMN)->setWidth(-1); - if (mMediaList->getColumn(VISIBILITY_COLUMN)) mMediaList->getColumn(VISIBILITY_COLUMN)->setWidth(-1); - if (mMediaList->getColumn(PROXIMITY_COLUMN)) mMediaList->getColumn(PROXIMITY_COLUMN)->setWidth(-1); - if (mMediaList->getColumn(CLASS_COLUMN)) mMediaList->getColumn(CLASS_COLUMN)->setWidth(-1); - if (mMediaList->getColumn(DEBUG_COLUMN)) mMediaList->getColumn(DEBUG_COLUMN)->setWidth(-1); - } - else { - if (mMediaList->getColumn(CHECKBOX_COLUMN)) mMediaList->getColumn(VISIBILITY_COLUMN)->setWidth(20); - if (mMediaList->getColumn(VISIBILITY_COLUMN)) mMediaList->getColumn(VISIBILITY_COLUMN)->setWidth(20); - if (mMediaList->getColumn(PROXIMITY_COLUMN)) mMediaList->getColumn(PROXIMITY_COLUMN)->setWidth(30); - if (mMediaList->getColumn(CLASS_COLUMN)) mMediaList->getColumn(CLASS_COLUMN)->setWidth(20); - if (mMediaList->getColumn(DEBUG_COLUMN)) mMediaList->getColumn(DEBUG_COLUMN)->setWidth(200); - } -} - -void LLPanelNearByMedia::onClickEnableAll() -{ - LLViewerMedia::getInstance()->setAllMediaEnabled(true); -} - -void LLPanelNearByMedia::onClickDisableAll() -{ - LLViewerMedia::getInstance()->setAllMediaEnabled(false); -} - -void LLPanelNearByMedia::onClickEnableParcelMedia() -{ - if ( ! LLViewerMedia::getInstance()->isParcelMediaPlaying() ) - { - LLViewerParcelMedia::getInstance()->play(LLViewerParcelMgr::getInstance()->getAgentParcel()); - } -} - -void LLPanelNearByMedia::onClickDisableParcelMedia() -{ - // This actually unloads the impl, as opposed to "stop"ping the media - LLViewerParcelMedia::getInstance()->stop(); -} - -void LLPanelNearByMedia::onCheckItem(LLUICtrl* ctrl, const LLUUID &row_id) -{ - LLCheckBoxCtrl* check = static_cast(ctrl); - - setDisabled(row_id, ! check->getValue()); -} - -bool LLPanelNearByMedia::setDisabled(const LLUUID &row_id, bool disabled) -{ - if (row_id == PARCEL_AUDIO_LIST_ITEM_UUID) - { - if (disabled) - { - onClickParcelAudioStop(); - } - else - { - onClickParcelAudioPlay(); - } - return true; - } - else if (row_id == PARCEL_MEDIA_LIST_ITEM_UUID) - { - if (disabled) - { - onClickDisableParcelMedia(); - } - else - { - onClickEnableParcelMedia(); - } - return true; - } - else { - LLViewerMediaImpl* impl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(row_id); - if(impl) - { - impl->setDisabled(disabled, true); - return true; - } - } - return false; -} - -//static -void LLPanelNearByMedia::onZoomMedia(void* user_data) -{ - LLPanelNearByMedia* panelp = (LLPanelNearByMedia*)user_data; - LLUUID media_id = panelp->mMediaList->getValue().asUUID(); - - LLViewerMediaFocus::getInstance()->focusZoomOnMedia(media_id); -} - -void LLPanelNearByMedia::onClickParcelMediaPlay() -{ - LLViewerParcelMedia::getInstance()->play(LLViewerParcelMgr::getInstance()->getAgentParcel()); -} - -void LLPanelNearByMedia::onClickParcelMediaStop() -{ - if (LLViewerParcelMedia::getInstance()->getParcelMedia()) - { - // This stops the media playing, as opposed to unloading it like - // LLViewerParcelMedia::stop() does - LLViewerParcelMedia::getInstance()->getParcelMedia()->stop(); - } -} - -void LLPanelNearByMedia::onClickParcelMediaPause() -{ - LLViewerParcelMedia::getInstance()->pause(); -} - -void LLPanelNearByMedia::onClickParcelAudioPlay() -{ - // User *explicitly* started the internet stream, so keep the stream - // playing and updated as they cross to other parcels etc. - mParcelAudioAutoStart = true; - if (!gAudiop) - { - LL_WARNS("AudioEngine") << "LLAudioEngine instance doesn't exist!" << LL_ENDL; - return; - } - - if (LLAudioEngine::AUDIO_PAUSED == gAudiop->isInternetStreamPlaying()) - { - // 'false' means unpause - gAudiop->pauseInternetStream(false); - } - else - { - LLViewerAudio::getInstance()->startInternetStreamWithAutoFade(LLViewerMedia::getInstance()->getParcelAudioURL()); - } -} - -void LLPanelNearByMedia::onClickParcelAudioStop() -{ - // User *explicitly* stopped the internet stream, so don't - // re-start audio when i.e. they move to another parcel, until - // they explicitly start it again. - mParcelAudioAutoStart = false; - if (!gAudiop) - { - LL_WARNS("AudioEngine") << "LLAudioEngine instance doesn't exist!" << LL_ENDL; - return; - } - - LLViewerAudio::getInstance()->stopInternetStreamWithAutoFade(); -} - -void LLPanelNearByMedia::onClickParcelAudioPause() -{ - if (!gAudiop) - { - LL_WARNS("AudioEngine") << "LLAudioEngine instance doesn't exist!" << LL_ENDL; - return; - } - - // 'true' means pause - gAudiop->pauseInternetStream(true); -} - -bool LLPanelNearByMedia::shouldShow(LLViewerMediaImpl* impl) -{ - const LLSD &choice_llsd = mShowCtrl->getSelectedValue(); - MediaClass choice = (MediaClass)choice_llsd.asInteger(); - - switch (choice) - { - case MEDIA_CLASS_ALL: - return true; - break; - case MEDIA_CLASS_WITHIN_PARCEL: - return impl->isInAgentParcel(); - break; - case MEDIA_CLASS_OUTSIDE_PARCEL: - return ! impl->isInAgentParcel(); - break; - case MEDIA_CLASS_ON_OTHERS: - return impl->isAttachedToAnotherAvatar(); - break; - default: - break; - } - return true; -} - -void LLPanelNearByMedia::onAdvancedButtonClick() -{ - // bring up the prefs floater - LLFloaterPreference* prefsfloater = dynamic_cast(LLFloaterReg::showInstance("preferences")); - if (prefsfloater) - { - // grab the 'audio' panel from the preferences floater and - // bring it the front! - LLTabContainer* tabcontainer = prefsfloater->getChild("pref core"); - LLPanel* audiopanel = prefsfloater->getChild("audio"); - if (tabcontainer && audiopanel) - { - tabcontainer->selectTabPanel(audiopanel); - } - } -} - -void LLPanelNearByMedia::onMoreLess() -{ - bool is_more = mMoreLessBtn->getToggleState(); - mNearbyMediaPanel->setVisible(is_more); - - // enable resizing when expanded - getChildView("resizebar_bottom")->setEnabled(is_more); - - LLRect new_rect = is_more ? mMoreRect : mLessRect; - new_rect.translate(getRect().mRight - new_rect.mRight, getRect().mTop - new_rect.mTop); - - setShape(new_rect); - - mMoreLessBtn->setVisible(true); -} - -void LLPanelNearByMedia::updateControls() -{ - LLUUID selected_media_id = mMediaList->getValue().asUUID(); - LLViewerMedia* media_inst = LLViewerMedia::getInstance(); - - if (selected_media_id == PARCEL_AUDIO_LIST_ITEM_UUID) - { - if (!media_inst->getInstance()->hasParcelAudio() || !gSavedSettings.getBOOL("AudioStreamingMusic")) - { - // disable controls if audio streaming music is disabled from preference - showDisabledControls(); - } - else { - showTimeBasedControls(media_inst->isParcelAudioPlaying(), - false, // include_zoom - false, // is_zoomed - gSavedSettings.getBOOL("MuteMusic"), - gSavedSettings.getF32("AudioLevelMusic") ); - } - } - else if (selected_media_id == PARCEL_MEDIA_LIST_ITEM_UUID) - { - if (!media_inst->hasParcelMedia() || !gSavedSettings.getBOOL("AudioStreamingMedia")) - { - // disable controls if audio streaming media is disabled from preference - showDisabledControls(); - } - else { - LLViewerMediaImpl* impl = LLViewerParcelMedia::getInstance()->getParcelMedia(); - if (NULL == impl) - { - // Just means it hasn't started yet - showBasicControls(false, false, false, false, 0); - } - else if (impl->isMediaTimeBased()) - { - showTimeBasedControls(impl->isMediaPlaying(), - false, // include_zoom - false, // is_zoomed - impl->getVolume() == 0.0, - impl->getVolume() ); - } - else { - // non-time-based parcel media - showBasicControls(media_inst->isParcelMediaPlaying(), - false, - false, - impl->getVolume() == 0.0, - impl->getVolume()); - } - } - } - else { - LLViewerMediaImpl* impl = media_inst->getMediaImplFromTextureID(selected_media_id); - - if (NULL == impl || !gSavedSettings.getBOOL("AudioStreamingMedia")) - { - showDisabledControls(); - } - else { - if (impl->isMediaTimeBased()) - { - showTimeBasedControls(impl->isMediaPlaying(), - ! impl->isParcelMedia(), // include_zoom - LLViewerMediaFocus::getInstance()->isZoomed(), - impl->getVolume() == 0.0, - impl->getVolume()); - } - else { - showBasicControls(!impl->isMediaDisabled(), - ! impl->isParcelMedia(), // include_zoom - LLViewerMediaFocus::getInstance()->isZoomedOnMedia(impl->getMediaTextureID()), - impl->getVolume() == 0.0, - impl->getVolume()); - } - } - } -} - -void LLPanelNearByMedia::showBasicControls(bool playing, bool include_zoom, bool is_zoomed, bool muted, F32 volume) -{ - mStopCtrl->setVisible(playing); - mPlayCtrl->setVisible(!playing); - mPauseCtrl->setVisible(false); - mVolumeSliderCtrl->setVisible(true); - mMuteCtrl->setVisible(true); - mMuteBtn->setValue(muted); - mVolumeSlider->setValue(volume); - mZoomCtrl->setVisible(include_zoom && !is_zoomed); - mUnzoomCtrl->setVisible(include_zoom && is_zoomed); - mStopCtrl->setEnabled(true); - mZoomCtrl->setEnabled(true); -} - -void LLPanelNearByMedia::showTimeBasedControls(bool playing, bool include_zoom, bool is_zoomed, bool muted, F32 volume) -{ - mStopCtrl->setVisible(true); - mPlayCtrl->setVisible(!playing); - mPauseCtrl->setVisible(playing); - mMuteCtrl->setVisible(true); - mVolumeSliderCtrl->setVisible(true); - mZoomCtrl->setVisible(include_zoom); - mZoomCtrl->setVisible(include_zoom && !is_zoomed); - mUnzoomCtrl->setVisible(include_zoom && is_zoomed); - mStopCtrl->setEnabled(true); - mZoomCtrl->setEnabled(true); - mMuteBtn->setValue(muted); - mVolumeSlider->setValue(volume); -} - -void LLPanelNearByMedia::showDisabledControls() -{ - mStopCtrl->setVisible(true); - mPlayCtrl->setVisible(false); - mPauseCtrl->setVisible(false); - mMuteCtrl->setVisible(false); - mVolumeSliderCtrl->setVisible(false); - mZoomCtrl->setVisible(true); - mUnzoomCtrl->setVisible(false); - mStopCtrl->setEnabled(false); - mZoomCtrl->setEnabled(false); -} - -void LLPanelNearByMedia::onClickSelectedMediaStop() -{ - setDisabled(mMediaList->getValue().asUUID(), true); -} - -void LLPanelNearByMedia::onClickSelectedMediaPlay() -{ - LLUUID selected_media_id = mMediaList->getValue().asUUID(); - - // First enable it - setDisabled(selected_media_id, false); - - // Special code to make play "unpause" if time-based and playing - if (selected_media_id != PARCEL_AUDIO_LIST_ITEM_UUID) - { - LLViewerMediaImpl *impl = (selected_media_id == PARCEL_MEDIA_LIST_ITEM_UUID) ? - ((LLViewerMediaImpl*)LLViewerParcelMedia::getInstance()->getParcelMedia()) : LLViewerMedia::getInstance()->getMediaImplFromTextureID(selected_media_id); - if (NULL != impl) - { - if (impl->isMediaTimeBased() && impl->isMediaPaused()) - { - // Aha! It's really time-based media that's paused, so unpause - impl->play(); - return; - } - else if (impl->isParcelMedia()) - { - LLViewerParcelMedia::getInstance()->play(LLViewerParcelMgr::getInstance()->getAgentParcel()); - } - } - } -} - -void LLPanelNearByMedia::onClickSelectedMediaPause() -{ - LLUUID selected_media_id = mMediaList->getValue().asUUID(); - if (selected_media_id == PARCEL_AUDIO_LIST_ITEM_UUID) - { - onClickParcelAudioPause(); - } - else if (selected_media_id == PARCEL_MEDIA_LIST_ITEM_UUID) - { - onClickParcelMediaPause(); - } - else { - LLViewerMediaImpl* impl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(selected_media_id); - if (NULL != impl && impl->isMediaTimeBased() && impl->isMediaPlaying()) - { - impl->pause(); - } - } -} - -void LLPanelNearByMedia::onClickSelectedMediaMute() -{ - LLUUID selected_media_id = mMediaList->getValue().asUUID(); - if (selected_media_id == PARCEL_AUDIO_LIST_ITEM_UUID) - { - gSavedSettings.setBOOL("MuteMusic", mMuteBtn->getValue()); - } - else { - LLViewerMediaImpl* impl = (selected_media_id == PARCEL_MEDIA_LIST_ITEM_UUID) ? - ((LLViewerMediaImpl*)LLViewerParcelMedia::getInstance()->getParcelMedia()) : LLViewerMedia::getInstance()->getMediaImplFromTextureID(selected_media_id); - if (NULL != impl) - { - F32 volume = impl->getVolume(); - if(volume > 0.0) - { - impl->setMute(true); - } - else if (mVolumeSlider->getValueF32() == 0.0) - { - impl->setMute(false); - mVolumeSlider->setValue(impl->getVolume()); - } - else - { - impl->setVolume(mVolumeSlider->getValueF32()); - } - } - } -} - -void LLPanelNearByMedia::onCommitSelectedMediaVolume() -{ - LLUUID selected_media_id = mMediaList->getValue().asUUID(); - if (selected_media_id == PARCEL_AUDIO_LIST_ITEM_UUID) - { - F32 vol = mVolumeSlider->getValueF32(); - gSavedSettings.setF32("AudioLevelMusic", vol); - } - else { - LLViewerMediaImpl* impl = (selected_media_id == PARCEL_MEDIA_LIST_ITEM_UUID) ? - ((LLViewerMediaImpl*)LLViewerParcelMedia::getInstance()->getParcelMedia()) : LLViewerMedia::getInstance()->getMediaImplFromTextureID(selected_media_id); - if (NULL != impl) - { - impl->setVolume(mVolumeSlider->getValueF32()); - } - } -} - -void LLPanelNearByMedia::onClickSelectedMediaZoom() -{ - LLUUID selected_media_id = mMediaList->getValue().asUUID(); - if (selected_media_id == PARCEL_AUDIO_LIST_ITEM_UUID || selected_media_id == PARCEL_MEDIA_LIST_ITEM_UUID) - return; - LLViewerMediaFocus::getInstance()->focusZoomOnMedia(selected_media_id); -} - -void LLPanelNearByMedia::onClickSelectedMediaUnzoom() -{ - LLViewerMediaFocus::getInstance()->unZoom(); -} - -void LLPanelNearByMedia::onMenuAction(const LLSD& userdata) -{ - const std::string command_name = userdata.asString(); - if ("copy_url" == command_name) - { - LLClipboard::instance().reset(); - std::string url = getSelectedUrl(); - - if (!url.empty()) - { - LLClipboard::instance().copyToClipboard(utf8str_to_wstring(url), 0, url.size()); - } - } - else if ("copy_data" == command_name) - { - LLClipboard::instance().reset(); - std::string url = getSelectedUrl(); - static const std::string encoding_specifier = "base64,"; - size_t pos = url.find(encoding_specifier); - if (pos != std::string::npos) - { - pos += encoding_specifier.size(); - std::string res = LLBase64::decodeAsString(url.substr(pos)); - LLClipboard::instance().copyToClipboard(utf8str_to_wstring(res), 0, res.size()); - } - else - { - url = LLURI::unescape(url); - LLClipboard::instance().copyToClipboard(utf8str_to_wstring(url), 0, url.size()); - } - } -} - -bool LLPanelNearByMedia::onMenuVisible(const LLSD& userdata) -{ - const std::string command_name = userdata.asString(); - if ("copy_data" == command_name) - { - std::string url = getSelectedUrl(); - if (url.rfind("data:", 0) == 0) - { - // might be a a good idea to permit text/html only - return true; - } - } - return false; -} - -// static -void LLPanelNearByMedia::getNameAndUrlHelper(LLViewerMediaImpl* impl, std::string& name, std::string & url, const std::string &defaultName) -{ - if (NULL == impl) return; - - name = impl->getName(); - url = impl->getCurrentMediaURL(); // This is the URL the media impl actually has loaded - if (url.empty()) - { - url = impl->getMediaEntryURL(); // This is the current URL from the media data - } - if (url.empty()) - { - url = impl->getHomeURL(); // This is the home URL from the media data - } - if (name.empty()) - { - name = url; - } - if (name.empty()) - { - name = defaultName; - } -} - -std::string LLPanelNearByMedia::getSelectedUrl() -{ - std::string url; - LLUUID selected_media_id = mMediaList->getValue().asUUID(); - if (selected_media_id == PARCEL_AUDIO_LIST_ITEM_UUID) - { - url = LLViewerMedia::getInstance()->getParcelAudioURL(); - } - else if (selected_media_id == PARCEL_MEDIA_LIST_ITEM_UUID) - { - url = LLViewerParcelMedia::getInstance()->getURL(); - } - else - { - LLViewerMediaImpl* impl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(selected_media_id); - if (NULL != impl) - { - std::string name; - getNameAndUrlHelper(impl, name, url, mEmptyNameString); - } - } - return url; -} - +/** + * @file llpanelnearbymedia.cpp + * @brief Management interface for muting and controlling nearby media + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelnearbymedia.h" + +#include "llaudioengine.h" +#include "llbase64.h" +#include "llcheckboxctrl.h" +#include "llclipboard.h" +#include "llcombobox.h" +#include "llresizebar.h" +#include "llresizehandle.h" +#include "llscrolllistctrl.h" +#include "llscrolllistitem.h" +#include "llscrolllistcell.h" +#include "llslider.h" +#include "llsliderctrl.h" +#include "llagent.h" +#include "llagentui.h" +#include "llbutton.h" +#include "lltextbox.h" +#include "llviewermedia.h" +#include "llviewerparcelaskplay.h" +#include "llviewerparcelmedia.h" +#include "llviewerregion.h" +#include "llviewermediafocus.h" +#include "llviewerparcelmgr.h" +#include "llparcel.h" +#include "llpluginclassmedia.h" +#include "llvovolume.h" +#include "llstatusbar.h" +#include "llsdutil.h" +#include "lltoggleablemenu.h" +#include "llvieweraudio.h" +#include "llviewermenu.h" + +#include "llfloaterreg.h" +#include "llfloaterpreference.h" // for the gear icon +#include "lltabcontainer.h" + +#include + +extern LLControlGroup gSavedSettings; + +static const LLUUID PARCEL_MEDIA_LIST_ITEM_UUID = LLUUID("CAB5920F-E484-4233-8621-384CF373A321"); +static const LLUUID PARCEL_AUDIO_LIST_ITEM_UUID = LLUUID("DF4B020D-8A24-4B95-AB5D-CA970D694822"); + +// +// LLPanelNearByMedia +// + + +LLPanelNearByMedia::LLPanelNearByMedia() +: mMediaList(NULL), + mEnableAllCtrl(NULL), + mDebugInfoVisible(false), + mParcelMediaItem(NULL), + mParcelAudioItem(NULL), + mMoreLessBtn(NULL) +{ + // This is just an initial value, mParcelAudioAutoStart does not affect ParcelMediaAutoPlayEnable + mParcelAudioAutoStart = gSavedSettings.getS32("ParcelMediaAutoPlayEnable") != 0 + && gSavedSettings.getBOOL("MediaTentativeAutoPlay"); + + gSavedSettings.getControl("ParcelMediaAutoPlayEnable")->getSignal()->connect(boost::bind(&LLPanelNearByMedia::handleMediaAutoPlayChanged, this, _2)); + + mCommitCallbackRegistrar.add("MediaListCtrl.EnableAll", boost::bind(&LLPanelNearByMedia::onClickEnableAll, this)); + mCommitCallbackRegistrar.add("MediaListCtrl.DisableAll", boost::bind(&LLPanelNearByMedia::onClickDisableAll, this)); + mCommitCallbackRegistrar.add("MediaListCtrl.GoMediaPrefs", boost::bind(&LLPanelNearByMedia::onAdvancedButtonClick, this)); + mCommitCallbackRegistrar.add("MediaListCtrl.MoreLess", boost::bind(&LLPanelNearByMedia::onMoreLess, this)); + mCommitCallbackRegistrar.add("SelectedMediaCtrl.Stop", boost::bind(&LLPanelNearByMedia::onClickSelectedMediaStop, this)); + mCommitCallbackRegistrar.add("SelectedMediaCtrl.Play", boost::bind(&LLPanelNearByMedia::onClickSelectedMediaPlay, this)); + mCommitCallbackRegistrar.add("SelectedMediaCtrl.Pause", boost::bind(&LLPanelNearByMedia::onClickSelectedMediaPause, this)); + mCommitCallbackRegistrar.add("SelectedMediaCtrl.Mute", boost::bind(&LLPanelNearByMedia::onClickSelectedMediaMute, this)); + mCommitCallbackRegistrar.add("SelectedMediaCtrl.Volume", boost::bind(&LLPanelNearByMedia::onCommitSelectedMediaVolume, this)); + mCommitCallbackRegistrar.add("SelectedMediaCtrl.Zoom", boost::bind(&LLPanelNearByMedia::onClickSelectedMediaZoom, this)); + mCommitCallbackRegistrar.add("SelectedMediaCtrl.Unzoom", boost::bind(&LLPanelNearByMedia::onClickSelectedMediaUnzoom, this)); + + // Context menu handler. + mCommitCallbackRegistrar.add("SelectedMediaCtrl.Action", + [this](LLUICtrl* ctrl, const LLSD& data) + { + onMenuAction(data); + }); + mEnableCallbackRegistrar.add("SelectedMediaCtrl.Visible", + [this](LLUICtrl* ctrl, const LLSD& data) + { + return onMenuVisible(data); + }); + + buildFromFile( "panel_nearby_media.xml"); +} + +LLPanelNearByMedia::~LLPanelNearByMedia() +{ +} + +bool LLPanelNearByMedia::postBuild() +{ + LLPanelPulldown::postBuild(); + + const S32 RESIZE_BAR_THICKNESS = 6; + LLResizeBar::Params p; + p.rect = LLRect(0, RESIZE_BAR_THICKNESS, getRect().getWidth(), 0); + p.name = "resizebar_bottom"; + p.min_size = getRect().getHeight(); + p.side = LLResizeBar::BOTTOM; + p.resizing_view = this; + addChild( LLUICtrlFactory::create(p) ); + + p.rect = LLRect( 0, getRect().getHeight(), RESIZE_BAR_THICKNESS, 0); + p.name = "resizebar_left"; + p.min_size = getRect().getWidth(); + p.side = LLResizeBar::LEFT; + addChild( LLUICtrlFactory::create(p) ); + + LLResizeHandle::Params resize_handle_p; + resize_handle_p.rect = LLRect( 0, RESIZE_HANDLE_HEIGHT, RESIZE_HANDLE_WIDTH, 0 ); + resize_handle_p.mouse_opaque(false); + resize_handle_p.min_width(getRect().getWidth()); + resize_handle_p.min_height(getRect().getHeight()); + resize_handle_p.corner(LLResizeHandle::LEFT_BOTTOM); + addChild(LLUICtrlFactory::create(resize_handle_p)); + + mNearbyMediaPanel = getChild("nearby_media_panel"); + mMediaList = getChild("media_list"); + mEnableAllCtrl = getChild("all_nearby_media_enable_btn"); + mDisableAllCtrl = getChild("all_nearby_media_disable_btn"); + mShowCtrl = getChild("show_combo"); + + // Dynamic (selection-dependent) controls + mStopCtrl = getChild("stop"); + mPlayCtrl = getChild("play"); + mPauseCtrl = getChild("pause"); + mMuteCtrl = getChild("mute"); + mVolumeSliderCtrl = getChild("volume_slider_ctrl"); + mZoomCtrl = getChild("zoom"); + mUnzoomCtrl = getChild("unzoom"); + mVolumeSlider = getChild("volume_slider"); + mMuteBtn = getChild("mute_btn"); + mMoreLessBtn = getChild("more_btn"); + + mEmptyNameString = getString("empty_item_text"); + mParcelMediaName = getString("parcel_media_name"); + mParcelAudioName = getString("parcel_audio_name"); + mPlayingString = getString("playing_suffix"); + + mMediaList->setDoubleClickCallback(onZoomMedia, this); + mMediaList->sortByColumnIndex(PROXIMITY_COLUMN, true); + mMediaList->sortByColumnIndex(VISIBILITY_COLUMN, false); + + refreshList(); + updateControls(); + updateColumns(); + + LLView* minimized_controls = getChildView("minimized_controls"); + mMoreRect = getRect(); + mLessRect = getRect(); + mLessRect.mBottom = minimized_controls->getRect().mBottom; + + mMoreLessBtn->setVisible(false); + onMoreLess(); + + mContextMenu = LLUICtrlFactory::getInstance()->createFromFile( + "menu_nearby_media.xml", + gMenuHolder, + LLViewerMenuHolderGL::child_registry_t::instance()); + + return true; +} + +void LLPanelNearByMedia::handleMediaAutoPlayChanged(const LLSD& newvalue) +{ + // update mParcelAudioAutoStartMode if "ParcelMediaAutoPlayEnable" changes + S32 value = gSavedSettings.getS32("ParcelMediaAutoPlayEnable"); + mParcelAudioAutoStart = value != 0 + && gSavedSettings.getBOOL("MediaTentativeAutoPlay"); + + LLViewerParcelAskPlay *inst = LLViewerParcelAskPlay::getInstance(); + if (value == 2 && !inst->hasData()) + { + // Init if nessesary + inst->loadSettings(); + } + inst->cancelNotification(); +} + +/*virtual*/ +void LLPanelNearByMedia::reshape(S32 width, S32 height, bool called_from_parent) +{ + LLPanelPulldown::reshape(width, height, called_from_parent); + + if (mMoreLessBtn && mMoreLessBtn->getValue().asBoolean()) + { + mMoreRect = getRect(); + } + +} + +/*virtual*/ +void LLPanelNearByMedia::draw() +{ + // keep bottom of panel on screen + LLRect screen_rect = calcScreenRect(); + if (screen_rect.mBottom < 0) + { + LLRect new_rect = getRect(); + new_rect.mBottom += 0 - screen_rect.mBottom; + setShape(new_rect); + } + + refreshList(); + updateControls(); + + LLPanelPulldown::draw(); +} + +/*virtual*/ +bool LLPanelNearByMedia::handleHover(S32 x, S32 y, MASK mask) +{ + LLPanelPulldown::handleHover(x, y, mask); + + // If we are hovering over this panel, make sure to clear any hovered media + // ID. Note that the more general solution would be to clear this ID when + // the mouse leaves the in-scene view, but that proved to be problematic. + // See EXT-5517 + LLViewerMediaFocus::getInstance()->clearHover(); + + // Always handle + return true; +} + +bool LLPanelNearByMedia::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + S32 x_list, y_list; + localPointToOtherView(x, y, &x_list, &y_list, mMediaList); + if (mMoreLessBtn->getToggleState() + && mMediaList->pointInView(x_list, y_list)) + { + LLScrollListItem* hit_item = mMediaList->hitItem(x_list, y_list); + bool selected = hit_item && hit_item->getSelected(); + if (!selected) + { + selected = mMediaList->selectItemAt(x_list, y_list, mask); + } + + if (selected && mContextMenu) + { + mContextMenu->buildDrawLabels(); + mContextMenu->updateParent(LLMenuGL::sMenuContainer); + LLMenuGL::showPopup(this, mContextMenu, x, y); + return true; + } + } + + return LLPanelPulldown::handleRightMouseDown(x, y, mask); +} + + +void LLPanelNearByMedia::onVisibilityChange(bool new_visibility) +{ + if (!new_visibility && mContextMenu->getVisible()) + { + gMenuHolder->hideMenus(); + } + LLPanelPulldown::onVisibilityChange(new_visibility); +} + +bool LLPanelNearByMedia::getParcelAudioAutoStart() +{ + return mParcelAudioAutoStart; +} + +LLScrollListItem* LLPanelNearByMedia::addListItem(const LLUUID &id) +{ + if (NULL == mMediaList) return NULL; + + // Just set up the columns -- the values will be filled in by updateListItem(). + + LLSD row; + row["id"] = id; + + LLSD &columns = row["columns"]; + + columns[CHECKBOX_COLUMN]["column"] = "media_checkbox_ctrl"; + columns[CHECKBOX_COLUMN]["type"] = "checkbox"; + //if(mDebugInfoVisible) + { + columns[PROXIMITY_COLUMN]["column"] = "media_proximity"; + columns[PROXIMITY_COLUMN]["value"] = ""; + columns[VISIBILITY_COLUMN]["column"] = "media_visibility"; + columns[VISIBILITY_COLUMN]["value"] = ""; + columns[CLASS_COLUMN]["column"] = "media_class"; + columns[CLASS_COLUMN]["type"] = "text"; + columns[CLASS_COLUMN]["value"] = ""; + } + columns[NAME_COLUMN]["column"] = "media_name"; + columns[NAME_COLUMN]["type"] = "text"; + columns[NAME_COLUMN]["value"] = ""; + //if(mDebugInfoVisible) + { + columns[DEBUG_COLUMN]["column"] = "media_debug"; + columns[DEBUG_COLUMN]["type"] = "text"; + columns[DEBUG_COLUMN]["value"] = ""; + } + + LLScrollListItem* new_item = mMediaList->addElement(row); + if (NULL != new_item) + { + LLScrollListCheck* scroll_list_check = dynamic_cast(new_item->getColumn(CHECKBOX_COLUMN)); + if (scroll_list_check) + { + LLCheckBoxCtrl *check = scroll_list_check->getCheckBox(); + check->setCommitCallback(boost::bind(&LLPanelNearByMedia::onCheckItem, this, _1, id)); + } + } + return new_item; +} + +void LLPanelNearByMedia::updateListItem(LLScrollListItem* item, LLViewerMediaImpl* impl) +{ + std::string item_name; + std::string item_tooltip; + std::string debug_str; + LLPanelNearByMedia::MediaClass media_class = MEDIA_CLASS_ALL; + + getNameAndUrlHelper(impl, item_name, item_tooltip, mEmptyNameString); + // Focused + if (impl->hasFocus()) + { + media_class = MEDIA_CLASS_FOCUSED; + } + // Is attached to another avatar? + else if (impl->isAttachedToAnotherAvatar()) + { + media_class = MEDIA_CLASS_ON_OTHERS; + } + // Outside agent parcel + else if (!impl->isInAgentParcel()) + { + media_class = MEDIA_CLASS_OUTSIDE_PARCEL; + } + else { + // inside parcel + media_class = MEDIA_CLASS_WITHIN_PARCEL; + } + + if(mDebugInfoVisible) + { + debug_str += llformat("%g/", (float)impl->getInterest()); + + // proximity distance is actually distance squared -- display it as straight distance. + debug_str += llformat("%g/", (F32) sqrt(impl->getProximityDistance())); + + // s += llformat("%g/", (float)impl->getCPUUsage()); + // s += llformat("%g/", (float)impl->getApproximateTextureInterest()); + debug_str += llformat("%g/", (float)(NULL == impl->getSomeObject()) ? 0.0 : impl->getSomeObject()->getPixelArea()); + + debug_str += LLPluginClassMedia::priorityToString(impl->getPriority()); + + if(impl->hasMedia()) + { + debug_str += '@'; + } + else if(impl->isPlayable()) + { + debug_str += '+'; + } + else if(impl->isForcedUnloaded()) + { + debug_str += '!'; + } + } + + updateListItem(item, + item_name, + item_tooltip, + impl->getProximity(), + impl->isMediaDisabled(), + impl->hasMedia(), + impl->isMediaTimeBased() && impl->isMediaPlaying(), + media_class, + debug_str); +} + +void LLPanelNearByMedia::updateListItem(LLScrollListItem* item, + const std::string &item_name, + const std::string &item_tooltip, + S32 proximity, + bool is_disabled, + bool has_media, + bool is_time_based_and_playing, + LLPanelNearByMedia::MediaClass media_class, + const std::string &debug_str) +{ + LLScrollListCell* cell = item->getColumn(PROXIMITY_COLUMN); + if(cell) + { + // since we are forced to sort by text, encode sort order as string + std::string proximity_string = STRINGIZE(proximity); + std::string old_proximity_string = cell->getValue().asString(); + if(proximity_string != old_proximity_string) + { + cell->setValue(proximity_string); + mMediaList->setNeedsSort(true); + } + } + + cell = item->getColumn(CHECKBOX_COLUMN); + if(cell) + { + cell->setValue(!is_disabled); + } + + cell = item->getColumn(VISIBILITY_COLUMN); + if(cell) + { + S32 old_visibility = cell->getValue(); + // *HACK ALERT: force ordering of Media before Audio before the rest of the list + S32 new_visibility = + item->getUUID() == PARCEL_MEDIA_LIST_ITEM_UUID ? 3 + : item->getUUID() == PARCEL_AUDIO_LIST_ITEM_UUID ? 2 + : (has_media) ? 1 + : ((is_disabled) ? 0 + : -1); + cell->setValue(STRINGIZE(new_visibility)); + if (new_visibility != old_visibility) + { + mMediaList->setNeedsSort(true); + } + } + + cell = item->getColumn(NAME_COLUMN); + if(cell) + { + std::string name = item_name; + std::string old_name = cell->getValue().asString(); + if (has_media) + { + name += " " + mPlayingString; + } + if (name != old_name) + { + cell->setValue(name); + } + cell->setToolTip(item_tooltip); + + // *TODO: Make these font styles/colors configurable via XUI + U8 font_style = LLFontGL::NORMAL; + LLColor4 cell_color = LLColor4::white; + + // Only colorize by class in debug + if (mDebugInfoVisible) + { + switch (media_class) { + case MEDIA_CLASS_FOCUSED: + cell_color = LLColor4::yellow; + break; + case MEDIA_CLASS_ON_OTHERS: + cell_color = LLColor4::red; + break; + case MEDIA_CLASS_OUTSIDE_PARCEL: + cell_color = LLColor4::orange; + break; + case MEDIA_CLASS_WITHIN_PARCEL: + default: + break; + } + } + if (is_disabled) + { + if (mDebugInfoVisible) + { + font_style |= LLFontGL::ITALIC; + cell_color = LLColor4::black; + } + else { + // Dim it if it is disabled + cell_color.setAlpha(0.25); + } + } + // Dim it if it isn't "showing" + else if (!has_media) + { + cell_color.setAlpha(0.25); + } + // Bold it if it is time-based media and it is playing + else if (is_time_based_and_playing) + { + if (mDebugInfoVisible) font_style |= LLFontGL::BOLD; + } + cell->setColor(cell_color); + LLScrollListText *text_cell = dynamic_cast (cell); + if (text_cell) + { + text_cell->setFontStyle(font_style); + } + } + + cell = item->getColumn(CLASS_COLUMN); + if(cell) + { + // TODO: clean this up! + cell->setValue(STRINGIZE(media_class)); + } + + if(mDebugInfoVisible) + { + cell = item->getColumn(DEBUG_COLUMN); + if(cell) + { + cell->setValue(debug_str); + } + } +} + +void LLPanelNearByMedia::removeListItem(const LLUUID &id) +{ + if (NULL == mMediaList) return; + + mMediaList->deleteSingleItem(mMediaList->getItemIndex(id)); + mMediaList->updateLayout(); +} + +void LLPanelNearByMedia::refreshParcelItems() +{ + // + // First add/remove the "fake" items Parcel Media and Parcel Audio. + // These items will have special UUIDs + // PARCEL_MEDIA_LIST_ITEM_UUID + // PARCEL_AUDIO_LIST_ITEM_UUID + // + // Get the filter choice. + const LLSD &choice_llsd = mShowCtrl->getSelectedValue(); + MediaClass choice = (MediaClass)choice_llsd.asInteger(); + // Only show "special parcel items" if "All" or "Within" filter + // (and if media is "enabled") + bool should_include = (choice == MEDIA_CLASS_ALL || choice == MEDIA_CLASS_WITHIN_PARCEL); + LLViewerMedia* media_inst = LLViewerMedia::getInstance(); + + // First Parcel Media: add or remove it as necessary + if (gSavedSettings.getBOOL("AudioStreamingMedia") && should_include && media_inst->hasParcelMedia()) + { + // Yes, there is parcel media. + if (NULL == mParcelMediaItem) + { + mParcelMediaItem = addListItem(PARCEL_MEDIA_LIST_ITEM_UUID); + mMediaList->setNeedsSort(true); + } + } + else { + if (NULL != mParcelMediaItem) { + removeListItem(PARCEL_MEDIA_LIST_ITEM_UUID); + mParcelMediaItem = NULL; + mMediaList->setNeedsSort(true); + } + } + + // ... then update it + if (NULL != mParcelMediaItem) + { + std::string name, url, tooltip; + getNameAndUrlHelper(LLViewerParcelMedia::getInstance()->getParcelMedia(), name, url, ""); + if (name.empty() || name == url) + { + tooltip = url; + } + else + { + tooltip = name + " : " + url; + } + LLViewerMediaImpl *impl = LLViewerParcelMedia::getInstance()->getParcelMedia(); + updateListItem(mParcelMediaItem, + mParcelMediaName, + tooltip, + -2, // Proximity closer than anything else, before Parcel Audio + impl == NULL || impl->isMediaDisabled(), + impl != NULL && !LLViewerParcelMedia::getInstance()->getURL().empty(), + impl != NULL && impl->isMediaTimeBased() && impl->isMediaPlaying(), + MEDIA_CLASS_ALL, + "parcel media"); + } + + // Next Parcel Audio: add or remove it as necessary (don't show if disabled in prefs) + if (should_include && media_inst->hasParcelAudio() && gSavedSettings.getBOOL("AudioStreamingMusic")) + { + // Yes, there is parcel audio. + if (NULL == mParcelAudioItem) + { + mParcelAudioItem = addListItem(PARCEL_AUDIO_LIST_ITEM_UUID); + mMediaList->setNeedsSort(true); + } + } + else { + if (NULL != mParcelAudioItem) { + removeListItem(PARCEL_AUDIO_LIST_ITEM_UUID); + mParcelAudioItem = NULL; + mMediaList->setNeedsSort(true); + } + } + + // ... then update it + if (NULL != mParcelAudioItem) + { + bool is_playing = media_inst->isParcelAudioPlaying(); + + std::string url; + url = media_inst->getParcelAudioURL(); + + updateListItem(mParcelAudioItem, + mParcelAudioName, + url, + -1, // Proximity after Parcel Media, but closer than anything else + (!is_playing), + is_playing, + is_playing, + MEDIA_CLASS_ALL, + "parcel audio"); + } +} + +void LLPanelNearByMedia::refreshList() +{ + bool all_items_deleted = false; + + if(!mMediaList) + { + // None of this makes any sense if the media list isn't there. + return; + } + + // Check whether the debug column has been shown/hidden. + bool debug_info_visible = gSavedSettings.getBOOL("MediaPerformanceManagerDebug"); + if(debug_info_visible != mDebugInfoVisible) + { + mDebugInfoVisible = debug_info_visible; + + // Clear all items so the list gets regenerated. + mMediaList->deleteAllItems(); + mParcelAudioItem = NULL; + mParcelMediaItem = NULL; + all_items_deleted = true; + + updateColumns(); + } + + refreshParcelItems(); + + // Get the canonical list from LLViewerMedia + LLViewerMedia* media_inst = LLViewerMedia::getInstance(); + LLViewerMedia::impl_list impls = media_inst->getPriorityList(); + LLViewerMedia::impl_list::iterator priority_iter; + + U32 disabled_count = 0; + + // iterate over the impl list, creating rows as necessary. + for(priority_iter = impls.begin(); priority_iter != impls.end(); priority_iter++) + { + LLViewerMediaImpl *impl = *priority_iter; + + // If we just emptied out the list, every flag needs to be reset. + if(all_items_deleted) + { + impl->setInNearbyMediaList(false); + } + + if (!impl->isParcelMedia()) + { + LLUUID media_id = impl->getMediaTextureID(); + S32 proximity = impl->getProximity(); + // This is expensive (i.e. a linear search) -- don't use it here. We now use mInNearbyMediaList instead. + //S32 index = mMediaList->getItemIndex(media_id); + if (proximity < 0 || !shouldShow(impl)) + { + if (impl->getInNearbyMediaList()) + { + // There's a row for this impl -- remove it. + removeListItem(media_id); + impl->setInNearbyMediaList(false); + } + } + else + { + if (!impl->getInNearbyMediaList()) + { + // We don't have a row for this impl -- add one. + addListItem(media_id); + impl->setInNearbyMediaList(true); + } + } + // Update counts + if (impl->isMediaDisabled()) + { + disabled_count++; + } + } + } + mDisableAllCtrl->setEnabled((gSavedSettings.getBOOL("AudioStreamingMusic") || + gSavedSettings.getBOOL("AudioStreamingMedia")) && + (media_inst->isAnyMediaShowing() || + media_inst->isParcelMediaPlaying() || + media_inst->isParcelAudioPlaying())); + + mEnableAllCtrl->setEnabled( (gSavedSettings.getBOOL("AudioStreamingMusic") || + gSavedSettings.getBOOL("AudioStreamingMedia")) && + (disabled_count > 0 || + // parcel media (if we have it, and it isn't playing, enable "start") + (media_inst->hasParcelMedia() && ! media_inst->isParcelMediaPlaying()) || + // parcel audio (if we have it, and it isn't playing, enable "start") + (media_inst->hasParcelAudio() && ! media_inst->isParcelAudioPlaying()))); + + // Iterate over the rows in the control, updating ones whose impl exists, and deleting ones whose impl has gone away. + std::vector items = mMediaList->getAllData(); + + for (std::vector::iterator item_it = items.begin(); + item_it != items.end(); + ++item_it) + { + LLScrollListItem* item = (*item_it); + LLUUID row_id = item->getUUID(); + + if (row_id != PARCEL_MEDIA_LIST_ITEM_UUID && + row_id != PARCEL_AUDIO_LIST_ITEM_UUID) + { + LLViewerMediaImpl* impl = media_inst->getMediaImplFromTextureID(row_id); + if(impl) + { + updateListItem(item, impl); + } + else + { + // This item's impl has been deleted -- remove the row. + // Removing the row won't throw off our iteration, since we have a local copy of the array. + // We just need to make sure we don't access this item after the delete. + removeListItem(row_id); + } + } + } + + // Set the selection to whatever media impl the media focus/hover is on. + // This is an experiment, and can be removed by ifdefing out these 4 lines. + LLUUID media_target = LLViewerMediaFocus::getInstance()->getControlsMediaID(); + if(media_target.notNull()) + { + mMediaList->selectByID(media_target); + } +} + +void LLPanelNearByMedia::updateColumns() +{ + if (!mDebugInfoVisible) + { + if (mMediaList->getColumn(CHECKBOX_COLUMN)) mMediaList->getColumn(VISIBILITY_COLUMN)->setWidth(-1); + if (mMediaList->getColumn(VISIBILITY_COLUMN)) mMediaList->getColumn(VISIBILITY_COLUMN)->setWidth(-1); + if (mMediaList->getColumn(PROXIMITY_COLUMN)) mMediaList->getColumn(PROXIMITY_COLUMN)->setWidth(-1); + if (mMediaList->getColumn(CLASS_COLUMN)) mMediaList->getColumn(CLASS_COLUMN)->setWidth(-1); + if (mMediaList->getColumn(DEBUG_COLUMN)) mMediaList->getColumn(DEBUG_COLUMN)->setWidth(-1); + } + else { + if (mMediaList->getColumn(CHECKBOX_COLUMN)) mMediaList->getColumn(VISIBILITY_COLUMN)->setWidth(20); + if (mMediaList->getColumn(VISIBILITY_COLUMN)) mMediaList->getColumn(VISIBILITY_COLUMN)->setWidth(20); + if (mMediaList->getColumn(PROXIMITY_COLUMN)) mMediaList->getColumn(PROXIMITY_COLUMN)->setWidth(30); + if (mMediaList->getColumn(CLASS_COLUMN)) mMediaList->getColumn(CLASS_COLUMN)->setWidth(20); + if (mMediaList->getColumn(DEBUG_COLUMN)) mMediaList->getColumn(DEBUG_COLUMN)->setWidth(200); + } +} + +void LLPanelNearByMedia::onClickEnableAll() +{ + LLViewerMedia::getInstance()->setAllMediaEnabled(true); +} + +void LLPanelNearByMedia::onClickDisableAll() +{ + LLViewerMedia::getInstance()->setAllMediaEnabled(false); +} + +void LLPanelNearByMedia::onClickEnableParcelMedia() +{ + if ( ! LLViewerMedia::getInstance()->isParcelMediaPlaying() ) + { + LLViewerParcelMedia::getInstance()->play(LLViewerParcelMgr::getInstance()->getAgentParcel()); + } +} + +void LLPanelNearByMedia::onClickDisableParcelMedia() +{ + // This actually unloads the impl, as opposed to "stop"ping the media + LLViewerParcelMedia::getInstance()->stop(); +} + +void LLPanelNearByMedia::onCheckItem(LLUICtrl* ctrl, const LLUUID &row_id) +{ + LLCheckBoxCtrl* check = static_cast(ctrl); + + setDisabled(row_id, ! check->getValue()); +} + +bool LLPanelNearByMedia::setDisabled(const LLUUID &row_id, bool disabled) +{ + if (row_id == PARCEL_AUDIO_LIST_ITEM_UUID) + { + if (disabled) + { + onClickParcelAudioStop(); + } + else + { + onClickParcelAudioPlay(); + } + return true; + } + else if (row_id == PARCEL_MEDIA_LIST_ITEM_UUID) + { + if (disabled) + { + onClickDisableParcelMedia(); + } + else + { + onClickEnableParcelMedia(); + } + return true; + } + else { + LLViewerMediaImpl* impl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(row_id); + if(impl) + { + impl->setDisabled(disabled, true); + return true; + } + } + return false; +} + +//static +void LLPanelNearByMedia::onZoomMedia(void* user_data) +{ + LLPanelNearByMedia* panelp = (LLPanelNearByMedia*)user_data; + LLUUID media_id = panelp->mMediaList->getValue().asUUID(); + + LLViewerMediaFocus::getInstance()->focusZoomOnMedia(media_id); +} + +void LLPanelNearByMedia::onClickParcelMediaPlay() +{ + LLViewerParcelMedia::getInstance()->play(LLViewerParcelMgr::getInstance()->getAgentParcel()); +} + +void LLPanelNearByMedia::onClickParcelMediaStop() +{ + if (LLViewerParcelMedia::getInstance()->getParcelMedia()) + { + // This stops the media playing, as opposed to unloading it like + // LLViewerParcelMedia::stop() does + LLViewerParcelMedia::getInstance()->getParcelMedia()->stop(); + } +} + +void LLPanelNearByMedia::onClickParcelMediaPause() +{ + LLViewerParcelMedia::getInstance()->pause(); +} + +void LLPanelNearByMedia::onClickParcelAudioPlay() +{ + // User *explicitly* started the internet stream, so keep the stream + // playing and updated as they cross to other parcels etc. + mParcelAudioAutoStart = true; + if (!gAudiop) + { + LL_WARNS("AudioEngine") << "LLAudioEngine instance doesn't exist!" << LL_ENDL; + return; + } + + if (LLAudioEngine::AUDIO_PAUSED == gAudiop->isInternetStreamPlaying()) + { + // 'false' means unpause + gAudiop->pauseInternetStream(false); + } + else + { + LLViewerAudio::getInstance()->startInternetStreamWithAutoFade(LLViewerMedia::getInstance()->getParcelAudioURL()); + } +} + +void LLPanelNearByMedia::onClickParcelAudioStop() +{ + // User *explicitly* stopped the internet stream, so don't + // re-start audio when i.e. they move to another parcel, until + // they explicitly start it again. + mParcelAudioAutoStart = false; + if (!gAudiop) + { + LL_WARNS("AudioEngine") << "LLAudioEngine instance doesn't exist!" << LL_ENDL; + return; + } + + LLViewerAudio::getInstance()->stopInternetStreamWithAutoFade(); +} + +void LLPanelNearByMedia::onClickParcelAudioPause() +{ + if (!gAudiop) + { + LL_WARNS("AudioEngine") << "LLAudioEngine instance doesn't exist!" << LL_ENDL; + return; + } + + // 'true' means pause + gAudiop->pauseInternetStream(true); +} + +bool LLPanelNearByMedia::shouldShow(LLViewerMediaImpl* impl) +{ + const LLSD &choice_llsd = mShowCtrl->getSelectedValue(); + MediaClass choice = (MediaClass)choice_llsd.asInteger(); + + switch (choice) + { + case MEDIA_CLASS_ALL: + return true; + break; + case MEDIA_CLASS_WITHIN_PARCEL: + return impl->isInAgentParcel(); + break; + case MEDIA_CLASS_OUTSIDE_PARCEL: + return ! impl->isInAgentParcel(); + break; + case MEDIA_CLASS_ON_OTHERS: + return impl->isAttachedToAnotherAvatar(); + break; + default: + break; + } + return true; +} + +void LLPanelNearByMedia::onAdvancedButtonClick() +{ + // bring up the prefs floater + LLFloaterPreference* prefsfloater = dynamic_cast(LLFloaterReg::showInstance("preferences")); + if (prefsfloater) + { + // grab the 'audio' panel from the preferences floater and + // bring it the front! + LLTabContainer* tabcontainer = prefsfloater->getChild("pref core"); + LLPanel* audiopanel = prefsfloater->getChild("audio"); + if (tabcontainer && audiopanel) + { + tabcontainer->selectTabPanel(audiopanel); + } + } +} + +void LLPanelNearByMedia::onMoreLess() +{ + bool is_more = mMoreLessBtn->getToggleState(); + mNearbyMediaPanel->setVisible(is_more); + + // enable resizing when expanded + getChildView("resizebar_bottom")->setEnabled(is_more); + + LLRect new_rect = is_more ? mMoreRect : mLessRect; + new_rect.translate(getRect().mRight - new_rect.mRight, getRect().mTop - new_rect.mTop); + + setShape(new_rect); + + mMoreLessBtn->setVisible(true); +} + +void LLPanelNearByMedia::updateControls() +{ + LLUUID selected_media_id = mMediaList->getValue().asUUID(); + LLViewerMedia* media_inst = LLViewerMedia::getInstance(); + + if (selected_media_id == PARCEL_AUDIO_LIST_ITEM_UUID) + { + if (!media_inst->getInstance()->hasParcelAudio() || !gSavedSettings.getBOOL("AudioStreamingMusic")) + { + // disable controls if audio streaming music is disabled from preference + showDisabledControls(); + } + else { + showTimeBasedControls(media_inst->isParcelAudioPlaying(), + false, // include_zoom + false, // is_zoomed + gSavedSettings.getBOOL("MuteMusic"), + gSavedSettings.getF32("AudioLevelMusic") ); + } + } + else if (selected_media_id == PARCEL_MEDIA_LIST_ITEM_UUID) + { + if (!media_inst->hasParcelMedia() || !gSavedSettings.getBOOL("AudioStreamingMedia")) + { + // disable controls if audio streaming media is disabled from preference + showDisabledControls(); + } + else { + LLViewerMediaImpl* impl = LLViewerParcelMedia::getInstance()->getParcelMedia(); + if (NULL == impl) + { + // Just means it hasn't started yet + showBasicControls(false, false, false, false, 0); + } + else if (impl->isMediaTimeBased()) + { + showTimeBasedControls(impl->isMediaPlaying(), + false, // include_zoom + false, // is_zoomed + impl->getVolume() == 0.0, + impl->getVolume() ); + } + else { + // non-time-based parcel media + showBasicControls(media_inst->isParcelMediaPlaying(), + false, + false, + impl->getVolume() == 0.0, + impl->getVolume()); + } + } + } + else { + LLViewerMediaImpl* impl = media_inst->getMediaImplFromTextureID(selected_media_id); + + if (NULL == impl || !gSavedSettings.getBOOL("AudioStreamingMedia")) + { + showDisabledControls(); + } + else { + if (impl->isMediaTimeBased()) + { + showTimeBasedControls(impl->isMediaPlaying(), + ! impl->isParcelMedia(), // include_zoom + LLViewerMediaFocus::getInstance()->isZoomed(), + impl->getVolume() == 0.0, + impl->getVolume()); + } + else { + showBasicControls(!impl->isMediaDisabled(), + ! impl->isParcelMedia(), // include_zoom + LLViewerMediaFocus::getInstance()->isZoomedOnMedia(impl->getMediaTextureID()), + impl->getVolume() == 0.0, + impl->getVolume()); + } + } + } +} + +void LLPanelNearByMedia::showBasicControls(bool playing, bool include_zoom, bool is_zoomed, bool muted, F32 volume) +{ + mStopCtrl->setVisible(playing); + mPlayCtrl->setVisible(!playing); + mPauseCtrl->setVisible(false); + mVolumeSliderCtrl->setVisible(true); + mMuteCtrl->setVisible(true); + mMuteBtn->setValue(muted); + mVolumeSlider->setValue(volume); + mZoomCtrl->setVisible(include_zoom && !is_zoomed); + mUnzoomCtrl->setVisible(include_zoom && is_zoomed); + mStopCtrl->setEnabled(true); + mZoomCtrl->setEnabled(true); +} + +void LLPanelNearByMedia::showTimeBasedControls(bool playing, bool include_zoom, bool is_zoomed, bool muted, F32 volume) +{ + mStopCtrl->setVisible(true); + mPlayCtrl->setVisible(!playing); + mPauseCtrl->setVisible(playing); + mMuteCtrl->setVisible(true); + mVolumeSliderCtrl->setVisible(true); + mZoomCtrl->setVisible(include_zoom); + mZoomCtrl->setVisible(include_zoom && !is_zoomed); + mUnzoomCtrl->setVisible(include_zoom && is_zoomed); + mStopCtrl->setEnabled(true); + mZoomCtrl->setEnabled(true); + mMuteBtn->setValue(muted); + mVolumeSlider->setValue(volume); +} + +void LLPanelNearByMedia::showDisabledControls() +{ + mStopCtrl->setVisible(true); + mPlayCtrl->setVisible(false); + mPauseCtrl->setVisible(false); + mMuteCtrl->setVisible(false); + mVolumeSliderCtrl->setVisible(false); + mZoomCtrl->setVisible(true); + mUnzoomCtrl->setVisible(false); + mStopCtrl->setEnabled(false); + mZoomCtrl->setEnabled(false); +} + +void LLPanelNearByMedia::onClickSelectedMediaStop() +{ + setDisabled(mMediaList->getValue().asUUID(), true); +} + +void LLPanelNearByMedia::onClickSelectedMediaPlay() +{ + LLUUID selected_media_id = mMediaList->getValue().asUUID(); + + // First enable it + setDisabled(selected_media_id, false); + + // Special code to make play "unpause" if time-based and playing + if (selected_media_id != PARCEL_AUDIO_LIST_ITEM_UUID) + { + LLViewerMediaImpl *impl = (selected_media_id == PARCEL_MEDIA_LIST_ITEM_UUID) ? + ((LLViewerMediaImpl*)LLViewerParcelMedia::getInstance()->getParcelMedia()) : LLViewerMedia::getInstance()->getMediaImplFromTextureID(selected_media_id); + if (NULL != impl) + { + if (impl->isMediaTimeBased() && impl->isMediaPaused()) + { + // Aha! It's really time-based media that's paused, so unpause + impl->play(); + return; + } + else if (impl->isParcelMedia()) + { + LLViewerParcelMedia::getInstance()->play(LLViewerParcelMgr::getInstance()->getAgentParcel()); + } + } + } +} + +void LLPanelNearByMedia::onClickSelectedMediaPause() +{ + LLUUID selected_media_id = mMediaList->getValue().asUUID(); + if (selected_media_id == PARCEL_AUDIO_LIST_ITEM_UUID) + { + onClickParcelAudioPause(); + } + else if (selected_media_id == PARCEL_MEDIA_LIST_ITEM_UUID) + { + onClickParcelMediaPause(); + } + else { + LLViewerMediaImpl* impl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(selected_media_id); + if (NULL != impl && impl->isMediaTimeBased() && impl->isMediaPlaying()) + { + impl->pause(); + } + } +} + +void LLPanelNearByMedia::onClickSelectedMediaMute() +{ + LLUUID selected_media_id = mMediaList->getValue().asUUID(); + if (selected_media_id == PARCEL_AUDIO_LIST_ITEM_UUID) + { + gSavedSettings.setBOOL("MuteMusic", mMuteBtn->getValue()); + } + else { + LLViewerMediaImpl* impl = (selected_media_id == PARCEL_MEDIA_LIST_ITEM_UUID) ? + ((LLViewerMediaImpl*)LLViewerParcelMedia::getInstance()->getParcelMedia()) : LLViewerMedia::getInstance()->getMediaImplFromTextureID(selected_media_id); + if (NULL != impl) + { + F32 volume = impl->getVolume(); + if(volume > 0.0) + { + impl->setMute(true); + } + else if (mVolumeSlider->getValueF32() == 0.0) + { + impl->setMute(false); + mVolumeSlider->setValue(impl->getVolume()); + } + else + { + impl->setVolume(mVolumeSlider->getValueF32()); + } + } + } +} + +void LLPanelNearByMedia::onCommitSelectedMediaVolume() +{ + LLUUID selected_media_id = mMediaList->getValue().asUUID(); + if (selected_media_id == PARCEL_AUDIO_LIST_ITEM_UUID) + { + F32 vol = mVolumeSlider->getValueF32(); + gSavedSettings.setF32("AudioLevelMusic", vol); + } + else { + LLViewerMediaImpl* impl = (selected_media_id == PARCEL_MEDIA_LIST_ITEM_UUID) ? + ((LLViewerMediaImpl*)LLViewerParcelMedia::getInstance()->getParcelMedia()) : LLViewerMedia::getInstance()->getMediaImplFromTextureID(selected_media_id); + if (NULL != impl) + { + impl->setVolume(mVolumeSlider->getValueF32()); + } + } +} + +void LLPanelNearByMedia::onClickSelectedMediaZoom() +{ + LLUUID selected_media_id = mMediaList->getValue().asUUID(); + if (selected_media_id == PARCEL_AUDIO_LIST_ITEM_UUID || selected_media_id == PARCEL_MEDIA_LIST_ITEM_UUID) + return; + LLViewerMediaFocus::getInstance()->focusZoomOnMedia(selected_media_id); +} + +void LLPanelNearByMedia::onClickSelectedMediaUnzoom() +{ + LLViewerMediaFocus::getInstance()->unZoom(); +} + +void LLPanelNearByMedia::onMenuAction(const LLSD& userdata) +{ + const std::string command_name = userdata.asString(); + if ("copy_url" == command_name) + { + LLClipboard::instance().reset(); + std::string url = getSelectedUrl(); + + if (!url.empty()) + { + LLClipboard::instance().copyToClipboard(utf8str_to_wstring(url), 0, url.size()); + } + } + else if ("copy_data" == command_name) + { + LLClipboard::instance().reset(); + std::string url = getSelectedUrl(); + static const std::string encoding_specifier = "base64,"; + size_t pos = url.find(encoding_specifier); + if (pos != std::string::npos) + { + pos += encoding_specifier.size(); + std::string res = LLBase64::decodeAsString(url.substr(pos)); + LLClipboard::instance().copyToClipboard(utf8str_to_wstring(res), 0, res.size()); + } + else + { + url = LLURI::unescape(url); + LLClipboard::instance().copyToClipboard(utf8str_to_wstring(url), 0, url.size()); + } + } +} + +bool LLPanelNearByMedia::onMenuVisible(const LLSD& userdata) +{ + const std::string command_name = userdata.asString(); + if ("copy_data" == command_name) + { + std::string url = getSelectedUrl(); + if (url.rfind("data:", 0) == 0) + { + // might be a a good idea to permit text/html only + return true; + } + } + return false; +} + +// static +void LLPanelNearByMedia::getNameAndUrlHelper(LLViewerMediaImpl* impl, std::string& name, std::string & url, const std::string &defaultName) +{ + if (NULL == impl) return; + + name = impl->getName(); + url = impl->getCurrentMediaURL(); // This is the URL the media impl actually has loaded + if (url.empty()) + { + url = impl->getMediaEntryURL(); // This is the current URL from the media data + } + if (url.empty()) + { + url = impl->getHomeURL(); // This is the home URL from the media data + } + if (name.empty()) + { + name = url; + } + if (name.empty()) + { + name = defaultName; + } +} + +std::string LLPanelNearByMedia::getSelectedUrl() +{ + std::string url; + LLUUID selected_media_id = mMediaList->getValue().asUUID(); + if (selected_media_id == PARCEL_AUDIO_LIST_ITEM_UUID) + { + url = LLViewerMedia::getInstance()->getParcelAudioURL(); + } + else if (selected_media_id == PARCEL_MEDIA_LIST_ITEM_UUID) + { + url = LLViewerParcelMedia::getInstance()->getURL(); + } + else + { + LLViewerMediaImpl* impl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(selected_media_id); + if (NULL != impl) + { + std::string name; + getNameAndUrlHelper(impl, name, url, mEmptyNameString); + } + } + return url; +} + diff --git a/indra/newview/llpanelnearbymedia.h b/indra/newview/llpanelnearbymedia.h index e4ce91703c..3154812745 100644 --- a/indra/newview/llpanelnearbymedia.h +++ b/indra/newview/llpanelnearbymedia.h @@ -1,181 +1,181 @@ -/** - * @file llpanelnearbymedia.h - * @brief Management interface for muting and controlling nearby media - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELNEARBYMEDIA_H -#define LL_LLPANELNEARBYMEDIA_H - -#include "llpanelpulldown.h" - -class LLPanelNearbyMedia; -class LLButton; -class LLScrollListCtrl; -class LLSlider; -class LLSliderCtrl; -class LLCheckBoxCtrl; -class LLTextBox; -class LLToggleableMenu; -class LLComboBox; -class LLViewerMediaImpl; - -class LLPanelNearByMedia : public LLPanelPulldown -{ -public: - - bool postBuild() override; - void draw() override; - void reshape(S32 width, S32 height, bool called_from_parent) override; - bool handleHover(S32 x, S32 y, MASK mask) override; - bool handleRightMouseDown(S32 x, S32 y, MASK mask) override; - void onVisibilityChange(bool new_visibility) override; - - // this is part of the nearby media *dialog* so we can track whether - // the user *implicitly* wants audio on or off via their *explicit* - // interaction with our buttons. - bool getParcelAudioAutoStart(); - - // callback for when the auto play media preference changes - // to update mParcelAudioAutoStart - void handleMediaAutoPlayChanged(const LLSD& newvalue); - - LLPanelNearByMedia(); - virtual ~LLPanelNearByMedia(); - -private: - - enum ColumnIndex { - CHECKBOX_COLUMN = 0, - PROXIMITY_COLUMN = 1, - VISIBILITY_COLUMN = 2, - CLASS_COLUMN = 3, - NAME_COLUMN = 4, - DEBUG_COLUMN = 5 - }; - - // Media "class" enumeration - enum MediaClass { - MEDIA_CLASS_ALL = 0, - MEDIA_CLASS_FOCUSED = 1, - MEDIA_CLASS_WITHIN_PARCEL = 2, - MEDIA_CLASS_OUTSIDE_PARCEL = 3, - MEDIA_CLASS_ON_OTHERS = 4 - }; - - // Add/remove an LLViewerMediaImpl to/from the list - LLScrollListItem* addListItem(const LLUUID &id); - void updateListItem(LLScrollListItem* item, LLViewerMediaImpl* impl); - void updateListItem(LLScrollListItem* item, - const std::string &item_name, - const std::string &item_tooltip, - S32 proximity, - bool is_disabled, - bool has_media, - bool is_time_based_and_playing, - MediaClass media_class, - const std::string &debug_str); - void removeListItem(const LLUUID &id); - - // Refresh the list in the UI - void refreshList(); - - void refreshParcelItems(); - - // UI Callbacks - void onClickEnableAll(); - void onClickDisableAll(); - void onClickEnableParcelMedia(); - void onClickDisableParcelMedia(); - void onClickParcelMediaPlay(); - void onClickParcelMediaStop(); - void onClickParcelMediaPause(); - void onClickParcelAudioPlay(); - void onClickParcelAudioStop(); - void onClickParcelAudioPause(); - void onAdvancedButtonClick(); - void onMoreLess(); - - void onCheckItem(LLUICtrl* ctrl, const LLUUID &row_id); - - static void onZoomMedia(void* user_data); - -private: - bool setDisabled(const LLUUID &id, bool disabled); - - static void getNameAndUrlHelper(LLViewerMediaImpl* impl, std::string& name, std::string & url, const std::string &defaultName); - std::string getSelectedUrl(); - - void updateColumns(); - - bool shouldShow(LLViewerMediaImpl* impl); - - void showBasicControls(bool playing, bool include_zoom, bool is_zoomed, bool muted, F32 volume); - void showTimeBasedControls(bool playing, bool include_zoom, bool is_zoomed, bool muted, F32 volume); - void showDisabledControls(); - void updateControls(); - - void onClickSelectedMediaStop(); - void onClickSelectedMediaPlay(); - void onClickSelectedMediaPause(); - void onClickSelectedMediaMute(); - void onCommitSelectedMediaVolume(); - void onClickSelectedMediaZoom(); - void onClickSelectedMediaUnzoom(); - void onMenuAction(const LLSD& userdata); - bool onMenuVisible(const LLSD& userdata); - - LLUICtrl* mNearbyMediaPanel; - LLScrollListCtrl* mMediaList; - LLUICtrl* mEnableAllCtrl; - LLUICtrl* mDisableAllCtrl; - LLComboBox* mShowCtrl; - - // Dynamic (selection-dependent) controls - LLUICtrl* mStopCtrl; - LLUICtrl* mPlayCtrl; - LLUICtrl* mPauseCtrl; - LLUICtrl* mMuteCtrl; - LLUICtrl* mVolumeSliderCtrl; - LLUICtrl* mZoomCtrl; - LLUICtrl* mUnzoomCtrl; - LLSlider* mVolumeSlider; - LLButton* mMuteBtn; - LLButton* mMoreLessBtn; - - bool mDebugInfoVisible; - bool mParcelAudioAutoStart; - std::string mEmptyNameString; - std::string mPlayingString; - std::string mParcelMediaName; - std::string mParcelAudioName; - - LLRect mMoreRect; - LLRect mLessRect; - LLScrollListItem* mParcelMediaItem; - LLScrollListItem* mParcelAudioItem; - LLToggleableMenu* mContextMenu; -}; - - -#endif // LL_LLPANELNEARBYMEDIA_H +/** + * @file llpanelnearbymedia.h + * @brief Management interface for muting and controlling nearby media + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELNEARBYMEDIA_H +#define LL_LLPANELNEARBYMEDIA_H + +#include "llpanelpulldown.h" + +class LLPanelNearbyMedia; +class LLButton; +class LLScrollListCtrl; +class LLSlider; +class LLSliderCtrl; +class LLCheckBoxCtrl; +class LLTextBox; +class LLToggleableMenu; +class LLComboBox; +class LLViewerMediaImpl; + +class LLPanelNearByMedia : public LLPanelPulldown +{ +public: + + bool postBuild() override; + void draw() override; + void reshape(S32 width, S32 height, bool called_from_parent) override; + bool handleHover(S32 x, S32 y, MASK mask) override; + bool handleRightMouseDown(S32 x, S32 y, MASK mask) override; + void onVisibilityChange(bool new_visibility) override; + + // this is part of the nearby media *dialog* so we can track whether + // the user *implicitly* wants audio on or off via their *explicit* + // interaction with our buttons. + bool getParcelAudioAutoStart(); + + // callback for when the auto play media preference changes + // to update mParcelAudioAutoStart + void handleMediaAutoPlayChanged(const LLSD& newvalue); + + LLPanelNearByMedia(); + virtual ~LLPanelNearByMedia(); + +private: + + enum ColumnIndex { + CHECKBOX_COLUMN = 0, + PROXIMITY_COLUMN = 1, + VISIBILITY_COLUMN = 2, + CLASS_COLUMN = 3, + NAME_COLUMN = 4, + DEBUG_COLUMN = 5 + }; + + // Media "class" enumeration + enum MediaClass { + MEDIA_CLASS_ALL = 0, + MEDIA_CLASS_FOCUSED = 1, + MEDIA_CLASS_WITHIN_PARCEL = 2, + MEDIA_CLASS_OUTSIDE_PARCEL = 3, + MEDIA_CLASS_ON_OTHERS = 4 + }; + + // Add/remove an LLViewerMediaImpl to/from the list + LLScrollListItem* addListItem(const LLUUID &id); + void updateListItem(LLScrollListItem* item, LLViewerMediaImpl* impl); + void updateListItem(LLScrollListItem* item, + const std::string &item_name, + const std::string &item_tooltip, + S32 proximity, + bool is_disabled, + bool has_media, + bool is_time_based_and_playing, + MediaClass media_class, + const std::string &debug_str); + void removeListItem(const LLUUID &id); + + // Refresh the list in the UI + void refreshList(); + + void refreshParcelItems(); + + // UI Callbacks + void onClickEnableAll(); + void onClickDisableAll(); + void onClickEnableParcelMedia(); + void onClickDisableParcelMedia(); + void onClickParcelMediaPlay(); + void onClickParcelMediaStop(); + void onClickParcelMediaPause(); + void onClickParcelAudioPlay(); + void onClickParcelAudioStop(); + void onClickParcelAudioPause(); + void onAdvancedButtonClick(); + void onMoreLess(); + + void onCheckItem(LLUICtrl* ctrl, const LLUUID &row_id); + + static void onZoomMedia(void* user_data); + +private: + bool setDisabled(const LLUUID &id, bool disabled); + + static void getNameAndUrlHelper(LLViewerMediaImpl* impl, std::string& name, std::string & url, const std::string &defaultName); + std::string getSelectedUrl(); + + void updateColumns(); + + bool shouldShow(LLViewerMediaImpl* impl); + + void showBasicControls(bool playing, bool include_zoom, bool is_zoomed, bool muted, F32 volume); + void showTimeBasedControls(bool playing, bool include_zoom, bool is_zoomed, bool muted, F32 volume); + void showDisabledControls(); + void updateControls(); + + void onClickSelectedMediaStop(); + void onClickSelectedMediaPlay(); + void onClickSelectedMediaPause(); + void onClickSelectedMediaMute(); + void onCommitSelectedMediaVolume(); + void onClickSelectedMediaZoom(); + void onClickSelectedMediaUnzoom(); + void onMenuAction(const LLSD& userdata); + bool onMenuVisible(const LLSD& userdata); + + LLUICtrl* mNearbyMediaPanel; + LLScrollListCtrl* mMediaList; + LLUICtrl* mEnableAllCtrl; + LLUICtrl* mDisableAllCtrl; + LLComboBox* mShowCtrl; + + // Dynamic (selection-dependent) controls + LLUICtrl* mStopCtrl; + LLUICtrl* mPlayCtrl; + LLUICtrl* mPauseCtrl; + LLUICtrl* mMuteCtrl; + LLUICtrl* mVolumeSliderCtrl; + LLUICtrl* mZoomCtrl; + LLUICtrl* mUnzoomCtrl; + LLSlider* mVolumeSlider; + LLButton* mMuteBtn; + LLButton* mMoreLessBtn; + + bool mDebugInfoVisible; + bool mParcelAudioAutoStart; + std::string mEmptyNameString; + std::string mPlayingString; + std::string mParcelMediaName; + std::string mParcelAudioName; + + LLRect mMoreRect; + LLRect mLessRect; + LLScrollListItem* mParcelMediaItem; + LLScrollListItem* mParcelAudioItem; + LLToggleableMenu* mContextMenu; +}; + + +#endif // LL_LLPANELNEARBYMEDIA_H diff --git a/indra/newview/llpanelobject.cpp b/indra/newview/llpanelobject.cpp index 1dcb7c414c..0a3a2e753a 100644 --- a/indra/newview/llpanelobject.cpp +++ b/indra/newview/llpanelobject.cpp @@ -1,2333 +1,2333 @@ -/** - * @file llpanelobject.cpp - * @brief Object editing (position, scale, etc.) in the tools floater - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -// file include -#include "llpanelobject.h" - -// linden library includes -#include "llerror.h" -#include "llfontgl.h" -#include "llpermissionsflags.h" -#include "llstring.h" -#include "llvolume.h" -#include "m3math.h" - -// project includes -#include "llagent.h" -#include "llbutton.h" -#include "llcalc.h" -#include "llcheckboxctrl.h" -#include "llcolorswatch.h" -#include "llcombobox.h" -#include "llfocusmgr.h" -#include "llmanipscale.h" -#include "llmenubutton.h" -#include "llpreviewscript.h" -#include "llresmgr.h" -#include "llselectmgr.h" -#include "llspinctrl.h" -#include "lltexturectrl.h" -#include "lltextbox.h" -#include "lltool.h" -#include "lltoolcomp.h" -#include "lltoolmgr.h" -#include "llui.h" -#include "llviewerobject.h" -#include "llviewerregion.h" -#include "llviewerwindow.h" -#include "llvovolume.h" -#include "llworld.h" -#include "pipeline.h" -#include "llviewercontrol.h" -#include "lluictrlfactory.h" -//#include "llfirstuse.h" - -#include "lldrawpool.h" - -// -// Constants -// -enum { - MI_BOX, - MI_CYLINDER, - MI_PRISM, - MI_SPHERE, - MI_TORUS, - MI_TUBE, - MI_RING, - MI_SCULPT, - MI_NONE, - MI_VOLUME_COUNT -}; - -enum { - MI_HOLE_SAME, - MI_HOLE_CIRCLE, - MI_HOLE_SQUARE, - MI_HOLE_TRIANGLE, - MI_HOLE_COUNT -}; - -const F32 MAX_ATTACHMENT_DIST = 3.5f; // meters - -//static const std::string LEGACY_FULLBRIGHT_DESC =LLTrans::getString("Fullbright"); - -bool LLPanelObject::postBuild() -{ - setMouseOpaque(false); - - //-------------------------------------------------------- - // Top - //-------------------------------------------------------- - - // Lock checkbox - mCheckLock = getChild("checkbox locked"); - childSetCommitCallback("checkbox locked",onCommitLock,this); - - // Physical checkbox - mCheckPhysics = getChild("Physical Checkbox Ctrl"); - childSetCommitCallback("Physical Checkbox Ctrl",onCommitPhysics,this); - - // Temporary checkbox - mCheckTemporary = getChild("Temporary Checkbox Ctrl"); - childSetCommitCallback("Temporary Checkbox Ctrl",onCommitTemporary,this); - - // Phantom checkbox - mCheckPhantom = getChild("Phantom Checkbox Ctrl"); - childSetCommitCallback("Phantom Checkbox Ctrl",onCommitPhantom,this); - - // Position - mMenuClipboardPos = getChild("clipboard_pos_btn"); - mLabelPosition = getChild("label position"); - mCtrlPosX = getChild("Pos X"); - childSetCommitCallback("Pos X",onCommitPosition,this); - mCtrlPosY = getChild("Pos Y"); - childSetCommitCallback("Pos Y",onCommitPosition,this); - mCtrlPosZ = getChild("Pos Z"); - childSetCommitCallback("Pos Z",onCommitPosition,this); - - // Scale - mMenuClipboardSize = getChild("clipboard_size_btn"); - mLabelSize = getChild("label size"); - mCtrlScaleX = getChild("Scale X"); - childSetCommitCallback("Scale X",onCommitScale,this); - - // Scale Y - mCtrlScaleY = getChild("Scale Y"); - childSetCommitCallback("Scale Y",onCommitScale,this); - - // Scale Z - mCtrlScaleZ = getChild("Scale Z"); - childSetCommitCallback("Scale Z",onCommitScale,this); - - // Rotation - mMenuClipboardRot = getChild("clipboard_rot_btn"); - mLabelRotation = getChild("label rotation"); - mCtrlRotX = getChild("Rot X"); - childSetCommitCallback("Rot X",onCommitRotation,this); - mCtrlRotY = getChild("Rot Y"); - childSetCommitCallback("Rot Y",onCommitRotation,this); - mCtrlRotZ = getChild("Rot Z"); - childSetCommitCallback("Rot Z",onCommitRotation,this); - - //-------------------------------------------------------- - - // Base Type - mComboBaseType = getChild("comboBaseType"); - childSetCommitCallback("comboBaseType",onCommitParametric,this); - - mMenuClipboardParams = getChild("clipboard_obj_params_btn"); - - // Cut - mLabelCut = getChild("text cut"); - mSpinCutBegin = getChild("cut begin"); - childSetCommitCallback("cut begin",onCommitParametric,this); - mSpinCutBegin->setValidateBeforeCommit( precommitValidate ); - mSpinCutEnd = getChild("cut end"); - childSetCommitCallback("cut end",onCommitParametric,this); - mSpinCutEnd->setValidateBeforeCommit( &precommitValidate ); - - // Hollow / Skew - mLabelHollow = getChild("text hollow"); - mLabelSkew = getChild("text skew"); - mSpinHollow = getChild("Scale 1"); - childSetCommitCallback("Scale 1",onCommitParametric,this); - mSpinHollow->setValidateBeforeCommit( &precommitValidate ); - mSpinSkew = getChild("Skew"); - childSetCommitCallback("Skew",onCommitParametric,this); - mSpinSkew->setValidateBeforeCommit( &precommitValidate ); - mLabelHoleType = getChild("Hollow Shape"); - - // Hole Type - mComboHoleType = getChild("hole"); - childSetCommitCallback("hole",onCommitParametric,this); - - // Twist - mLabelTwist = getChild("text twist"); - mSpinTwistBegin = getChild("Twist Begin"); - childSetCommitCallback("Twist Begin",onCommitParametric,this); - mSpinTwistBegin->setValidateBeforeCommit( precommitValidate ); - mSpinTwist = getChild("Twist End"); - childSetCommitCallback("Twist End",onCommitParametric,this); - mSpinTwist->setValidateBeforeCommit( &precommitValidate ); - - // Scale - mSpinScaleX = getChild("Taper Scale X"); - childSetCommitCallback("Taper Scale X",onCommitParametric,this); - mSpinScaleX->setValidateBeforeCommit( &precommitValidate ); - mSpinScaleY = getChild("Taper Scale Y"); - childSetCommitCallback("Taper Scale Y",onCommitParametric,this); - mSpinScaleY->setValidateBeforeCommit( &precommitValidate ); - - // Shear - mLabelShear = getChild("text topshear"); - mSpinShearX = getChild("Shear X"); - childSetCommitCallback("Shear X",onCommitParametric,this); - mSpinShearX->setValidateBeforeCommit( &precommitValidate ); - mSpinShearY = getChild("Shear Y"); - childSetCommitCallback("Shear Y",onCommitParametric,this); - mSpinShearY->setValidateBeforeCommit( &precommitValidate ); - - // Path / Profile - mCtrlPathBegin = getChild("Path Limit Begin"); - childSetCommitCallback("Path Limit Begin",onCommitParametric,this); - mCtrlPathBegin->setValidateBeforeCommit( &precommitValidate ); - mCtrlPathEnd = getChild("Path Limit End"); - childSetCommitCallback("Path Limit End",onCommitParametric,this); - mCtrlPathEnd->setValidateBeforeCommit( &precommitValidate ); - - // Taper - mLabelTaper = getChild("text taper2"); - mSpinTaperX = getChild("Taper X"); - childSetCommitCallback("Taper X",onCommitParametric,this); - mSpinTaperX->setValidateBeforeCommit( precommitValidate ); - mSpinTaperY = getChild("Taper Y"); - childSetCommitCallback("Taper Y",onCommitParametric,this); - mSpinTaperY->setValidateBeforeCommit( precommitValidate ); - - // Radius Offset / Revolutions - mLabelRadiusOffset = getChild("text radius delta"); - mLabelRevolutions = getChild("text revolutions"); - mSpinRadiusOffset = getChild("Radius Offset"); - childSetCommitCallback("Radius Offset",onCommitParametric,this); - mSpinRadiusOffset->setValidateBeforeCommit( &precommitValidate ); - mSpinRevolutions = getChild("Revolutions"); - childSetCommitCallback("Revolutions",onCommitParametric,this); - mSpinRevolutions->setValidateBeforeCommit( &precommitValidate ); - - // Sculpt - mCtrlSculptTexture = getChild("sculpt texture control"); - if (mCtrlSculptTexture) - { - mCtrlSculptTexture->setDefaultImageAssetID(SCULPT_DEFAULT_TEXTURE); - mCtrlSculptTexture->setCommitCallback( boost::bind(&LLPanelObject::onCommitSculpt, this, _2 )); - mCtrlSculptTexture->setOnCancelCallback( boost::bind(&LLPanelObject::onCancelSculpt, this, _2 )); - mCtrlSculptTexture->setOnSelectCallback( boost::bind(&LLPanelObject::onSelectSculpt, this, _2 )); - mCtrlSculptTexture->setDropCallback( boost::bind(&LLPanelObject::onDropSculpt, this, _2 )); - // Don't allow (no copy) or (no transfer) textures to be selected during immediate mode - mCtrlSculptTexture->setImmediateFilterPermMask(PERM_COPY | PERM_TRANSFER); - mCtrlSculptTexture->setDnDFilterPermMask(PERM_COPY | PERM_TRANSFER); - LLAggregatePermissions texture_perms; - if (LLSelectMgr::getInstance()->selectGetAggregateTexturePermissions(texture_perms)) - { - bool can_copy = - texture_perms.getValue(PERM_COPY) == LLAggregatePermissions::AP_EMPTY || - texture_perms.getValue(PERM_COPY) == LLAggregatePermissions::AP_ALL; - bool can_transfer = - texture_perms.getValue(PERM_TRANSFER) == LLAggregatePermissions::AP_EMPTY || - texture_perms.getValue(PERM_TRANSFER) == LLAggregatePermissions::AP_ALL; - mCtrlSculptTexture->setCanApplyImmediately(can_copy && can_transfer); - } - else - { - mCtrlSculptTexture->setCanApplyImmediately(false); - } - } - - mLabelSculptType = getChild("label sculpt type"); - mCtrlSculptType = getChild("sculpt type control"); - childSetCommitCallback("sculpt type control", onCommitSculptType, this); - mCtrlSculptMirror = getChild("sculpt mirror control"); - childSetCommitCallback("sculpt mirror control", onCommitSculptType, this); - mCtrlSculptInvert = getChild("sculpt invert control"); - childSetCommitCallback("sculpt invert control", onCommitSculptType, this); - - // Start with everyone disabled - clearCtrls(); - - return true; -} - -LLPanelObject::LLPanelObject() -: LLPanel(), - mIsPhysical(false), - mIsTemporary(false), - mIsPhantom(false), - mSelectedType(MI_BOX), - mSculptTextureRevert(LLUUID::null), - mSculptTypeRevert(0), - mHasClipboardPos(false), - mHasClipboardSize(false), - mHasClipboardRot(false), - mSizeChanged(false) -{ - mCommitCallbackRegistrar.add("PanelObject.menuDoToSelected", boost::bind(&LLPanelObject::menuDoToSelected, this, _2)); - mEnableCallbackRegistrar.add("PanelObject.menuEnable", boost::bind(&LLPanelObject::menuEnableItem, this, _2)); -} - - -LLPanelObject::~LLPanelObject() -{ - // Children all cleaned up by default view destructor. -} - -void LLPanelObject::getState( ) -{ - LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getFirstRootObject(); - LLViewerObject* root_objectp = objectp; - if(!objectp) - { - objectp = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); - // *FIX: shouldn't we just keep the child? - if (objectp) - { - LLViewerObject* parentp = objectp->getRootEdit(); - - if (parentp) - { - root_objectp = parentp; - } - else - { - root_objectp = objectp; - } - } - } - - LLCalc* calcp = LLCalc::getInstance(); - - LLVOVolume *volobjp = NULL; - if ( objectp && (objectp->getPCode() == LL_PCODE_VOLUME)) - { - volobjp = (LLVOVolume *)objectp; - } - - if( !objectp ) - { - //forfeit focus - if (gFocusMgr.childHasKeyboardFocus(this)) - { - gFocusMgr.setKeyboardFocus(NULL); - } - - // Disable all text input fields - clearCtrls(); - calcp->clearAllVariables(); - return; - } - - S32 selected_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount(); - bool single_volume = (LLSelectMgr::getInstance()->selectionAllPCode( LL_PCODE_VOLUME )) - && (selected_count == 1); - - bool enable_move; - bool enable_modify; - - LLSelectMgr::getInstance()->selectGetEditMoveLinksetPermissions(enable_move, enable_modify); - - bool enable_scale = enable_modify; - bool enable_rotate = enable_move; // already accounts for a case of children, which needs permModify() as well - - LLVector3 vec; - if (enable_move) - { - vec = objectp->getPositionEdit(); - mCtrlPosX->set( vec.mV[VX] ); - mCtrlPosY->set( vec.mV[VY] ); - mCtrlPosZ->set( vec.mV[VZ] ); - calcp->setVar(LLCalc::X_POS, vec.mV[VX]); - calcp->setVar(LLCalc::Y_POS, vec.mV[VY]); - calcp->setVar(LLCalc::Z_POS, vec.mV[VZ]); - } - else - { - mCtrlPosX->clear(); - mCtrlPosY->clear(); - mCtrlPosZ->clear(); - calcp->clearVar(LLCalc::X_POS); - calcp->clearVar(LLCalc::Y_POS); - calcp->clearVar(LLCalc::Z_POS); - } - - mMenuClipboardPos->setEnabled(enable_move); - mLabelPosition->setEnabled( enable_move ); - mCtrlPosX->setEnabled(enable_move); - mCtrlPosY->setEnabled(enable_move); - mCtrlPosZ->setEnabled(enable_move); - - if (enable_scale) - { - vec = objectp->getScale(); - mCtrlScaleX->set( vec.mV[VX] ); - mCtrlScaleY->set( vec.mV[VY] ); - mCtrlScaleZ->set( vec.mV[VZ] ); - calcp->setVar(LLCalc::X_SCALE, vec.mV[VX]); - calcp->setVar(LLCalc::Y_SCALE, vec.mV[VY]); - calcp->setVar(LLCalc::Z_SCALE, vec.mV[VZ]); - } - else - { - mCtrlScaleX->clear(); - mCtrlScaleY->clear(); - mCtrlScaleZ->clear(); - calcp->setVar(LLCalc::X_SCALE, 0.f); - calcp->setVar(LLCalc::Y_SCALE, 0.f); - calcp->setVar(LLCalc::Z_SCALE, 0.f); - } - - mMenuClipboardSize->setEnabled(enable_scale); - mLabelSize->setEnabled( enable_scale ); - mCtrlScaleX->setEnabled( enable_scale ); - mCtrlScaleY->setEnabled( enable_scale ); - mCtrlScaleZ->setEnabled( enable_scale ); - - LLQuaternion object_rot = objectp->getRotationEdit(); - object_rot.getEulerAngles(&(mCurEulerDegrees.mV[VX]), &(mCurEulerDegrees.mV[VY]), &(mCurEulerDegrees.mV[VZ])); - mCurEulerDegrees *= RAD_TO_DEG; - mCurEulerDegrees.mV[VX] = fmod(ll_round(mCurEulerDegrees.mV[VX], OBJECT_ROTATION_PRECISION) + 360.f, 360.f); - mCurEulerDegrees.mV[VY] = fmod(ll_round(mCurEulerDegrees.mV[VY], OBJECT_ROTATION_PRECISION) + 360.f, 360.f); - mCurEulerDegrees.mV[VZ] = fmod(ll_round(mCurEulerDegrees.mV[VZ], OBJECT_ROTATION_PRECISION) + 360.f, 360.f); - - if (enable_rotate) - { - mCtrlRotX->set( mCurEulerDegrees.mV[VX] ); - mCtrlRotY->set( mCurEulerDegrees.mV[VY] ); - mCtrlRotZ->set( mCurEulerDegrees.mV[VZ] ); - calcp->setVar(LLCalc::X_ROT, mCurEulerDegrees.mV[VX]); - calcp->setVar(LLCalc::Y_ROT, mCurEulerDegrees.mV[VY]); - calcp->setVar(LLCalc::Z_ROT, mCurEulerDegrees.mV[VZ]); - } - else - { - mCtrlRotX->clear(); - mCtrlRotY->clear(); - mCtrlRotZ->clear(); - calcp->clearVar(LLCalc::X_ROT); - calcp->clearVar(LLCalc::Y_ROT); - calcp->clearVar(LLCalc::Z_ROT); - } - - mMenuClipboardRot->setEnabled(enable_rotate); - mLabelRotation->setEnabled( enable_rotate ); - mCtrlRotX->setEnabled( enable_rotate ); - mCtrlRotY->setEnabled( enable_rotate ); - mCtrlRotZ->setEnabled( enable_rotate ); - - LLUUID owner_id; - std::string owner_name; - LLSelectMgr::getInstance()->selectGetOwner(owner_id, owner_name); - - // BUG? Check for all objects being editable? - S32 roots_selected = LLSelectMgr::getInstance()->getSelection()->getRootObjectCount(); - bool editable = root_objectp->permModify(); - - bool is_flexible = volobjp && volobjp->isFlexible(); - bool is_permanent = root_objectp->flagObjectPermanent(); - bool is_permanent_enforced = root_objectp->isPermanentEnforced(); - bool is_character = root_objectp->flagCharacter(); - llassert(!is_permanent || !is_character); // should never have a permanent object that is also a character - - // Lock checkbox - only modifiable if you own the object. - bool self_owned = (gAgent.getID() == owner_id); - mCheckLock->setEnabled( roots_selected > 0 && self_owned && !is_permanent_enforced); - - // More lock and debit checkbox - get the values - bool valid; - U32 owner_mask_on; - U32 owner_mask_off; - valid = LLSelectMgr::getInstance()->selectGetPerm(PERM_OWNER, &owner_mask_on, &owner_mask_off); - - if(valid) - { - if(owner_mask_on & PERM_MOVE) - { - // owner can move, so not locked - mCheckLock->set(false); - mCheckLock->setTentative(false); - } - else if(owner_mask_off & PERM_MOVE) - { - // owner can't move, so locked - mCheckLock->set(true); - mCheckLock->setTentative(false); - } - else - { - // some locked, some not locked - mCheckLock->set(false); - mCheckLock->setTentative(true); - } - } - - // Physics checkbox - mIsPhysical = root_objectp->flagUsePhysics(); - llassert(!is_permanent || !mIsPhysical); // should never have a permanent object that is also physical - - mCheckPhysics->set( mIsPhysical ); - mCheckPhysics->setEnabled( roots_selected>0 - && (editable || gAgent.isGodlike()) - && !is_flexible && !is_permanent); - - mIsTemporary = root_objectp->flagTemporaryOnRez(); - llassert(!is_permanent || !mIsTemporary); // should never has a permanent object that is also temporary - - mCheckTemporary->set( mIsTemporary ); - mCheckTemporary->setEnabled( roots_selected>0 && editable && !is_permanent); - - mIsPhantom = root_objectp->flagPhantom(); - bool is_volume_detect = root_objectp->flagVolumeDetect(); - llassert(!is_character || !mIsPhantom); // should never have a character that is also a phantom - mCheckPhantom->set( mIsPhantom ); - mCheckPhantom->setEnabled( roots_selected>0 && editable && !is_flexible && !is_permanent_enforced && !is_character && !is_volume_detect); - - //---------------------------------------------------------------------------- - - S32 selected_item = MI_BOX; - S32 selected_hole = MI_HOLE_SAME; - bool enabled = false; - bool hole_enabled = false; - F32 scale_x=1.f, scale_y=1.f; - bool isMesh = false; - - if( !objectp || !objectp->getVolume() || !editable || !single_volume) - { - // Clear out all geometry fields. - mComboBaseType->clear(); - mSpinHollow->clear(); - mSpinCutBegin->clear(); - mSpinCutEnd->clear(); - mCtrlPathBegin->clear(); - mCtrlPathEnd->clear(); - mSpinScaleX->clear(); - mSpinScaleY->clear(); - mSpinTwist->clear(); - mSpinTwistBegin->clear(); - mComboHoleType->clear(); - mSpinShearX->clear(); - mSpinShearY->clear(); - mSpinTaperX->clear(); - mSpinTaperY->clear(); - mSpinRadiusOffset->clear(); - mSpinRevolutions->clear(); - mSpinSkew->clear(); - - mSelectedType = MI_NONE; - } - else - { - // Only allowed to change these parameters for objects - // that you have permissions on AND are not attachments. - enabled = root_objectp->permModify() && !root_objectp->isPermanentEnforced(); - - // Volume type - const LLVolumeParams &volume_params = objectp->getVolume()->getParams(); - U8 path = volume_params.getPathParams().getCurveType(); - U8 profile_and_hole = volume_params.getProfileParams().getCurveType(); - U8 profile = profile_and_hole & LL_PCODE_PROFILE_MASK; - U8 hole = profile_and_hole & LL_PCODE_HOLE_MASK; - - // Scale goes first so we can differentiate between a sphere and a torus, - // which have the same profile and path types. - - // Scale - scale_x = volume_params.getRatioX(); - scale_y = volume_params.getRatioY(); - - bool linear_path = (path == LL_PCODE_PATH_LINE) || (path == LL_PCODE_PATH_FLEXIBLE); - if ( linear_path && profile == LL_PCODE_PROFILE_CIRCLE ) - { - selected_item = MI_CYLINDER; - } - else if ( linear_path && profile == LL_PCODE_PROFILE_SQUARE ) - { - selected_item = MI_BOX; - } - else if ( linear_path && profile == LL_PCODE_PROFILE_ISOTRI ) - { - selected_item = MI_PRISM; - } - else if ( linear_path && profile == LL_PCODE_PROFILE_EQUALTRI ) - { - selected_item = MI_PRISM; - } - else if ( linear_path && profile == LL_PCODE_PROFILE_RIGHTTRI ) - { - selected_item = MI_PRISM; - } - else if (path == LL_PCODE_PATH_FLEXIBLE) // shouldn't happen - { - selected_item = MI_CYLINDER; // reasonable default - } - else if ( path == LL_PCODE_PATH_CIRCLE && profile == LL_PCODE_PROFILE_CIRCLE && scale_y > 0.75f) - { - selected_item = MI_SPHERE; - } - else if ( path == LL_PCODE_PATH_CIRCLE && profile == LL_PCODE_PROFILE_CIRCLE && scale_y <= 0.75f) - { - selected_item = MI_TORUS; - } - else if ( path == LL_PCODE_PATH_CIRCLE && profile == LL_PCODE_PROFILE_CIRCLE_HALF) - { - selected_item = MI_SPHERE; - } - else if ( path == LL_PCODE_PATH_CIRCLE2 && profile == LL_PCODE_PROFILE_CIRCLE ) - { - // Spirals aren't supported. Make it into a sphere. JC - selected_item = MI_SPHERE; - } - else if ( path == LL_PCODE_PATH_CIRCLE && profile == LL_PCODE_PROFILE_EQUALTRI ) - { - selected_item = MI_RING; - } - else if ( path == LL_PCODE_PATH_CIRCLE && profile == LL_PCODE_PROFILE_SQUARE && scale_y <= 0.75f) - { - selected_item = MI_TUBE; - } - else - { - LL_INFOS("FloaterTools") << "Unknown path " << (S32) path << " profile " << (S32) profile << " in getState" << LL_ENDL; - selected_item = MI_BOX; - } - - - if (objectp->getParameterEntryInUse(LLNetworkData::PARAMS_SCULPT)) - { - selected_item = MI_SCULPT; - //LLFirstUse::useSculptedPrim(); - } - - - mComboBaseType ->setCurrentByIndex( selected_item ); - mSelectedType = selected_item; - - // Grab S path - F32 begin_s = volume_params.getBeginS(); - F32 end_s = volume_params.getEndS(); - - // Compute cut and advanced cut from S and T - F32 begin_t = volume_params.getBeginT(); - F32 end_t = volume_params.getEndT(); - - // Hollowness - F32 hollow = 100.f * volume_params.getHollow(); - mSpinHollow->set( hollow ); - calcp->setVar(LLCalc::HOLLOW, hollow); - // All hollow objects allow a shape to be selected. - if (hollow > 0.f) - { - switch (hole) - { - case LL_PCODE_HOLE_CIRCLE: - selected_hole = MI_HOLE_CIRCLE; - break; - case LL_PCODE_HOLE_SQUARE: - selected_hole = MI_HOLE_SQUARE; - break; - case LL_PCODE_HOLE_TRIANGLE: - selected_hole = MI_HOLE_TRIANGLE; - break; - case LL_PCODE_HOLE_SAME: - default: - selected_hole = MI_HOLE_SAME; - break; - } - mComboHoleType->setCurrentByIndex( selected_hole ); - hole_enabled = enabled; - } - else - { - mComboHoleType->setCurrentByIndex( MI_HOLE_SAME ); - hole_enabled = false; - } - - // Cut interpretation varies based on base object type - F32 cut_begin, cut_end, adv_cut_begin, adv_cut_end; - - if ( selected_item == MI_SPHERE || selected_item == MI_TORUS || - selected_item == MI_TUBE || selected_item == MI_RING ) - { - cut_begin = begin_t; - cut_end = end_t; - adv_cut_begin = begin_s; - adv_cut_end = end_s; - } - else - { - cut_begin = begin_s; - cut_end = end_s; - adv_cut_begin = begin_t; - adv_cut_end = end_t; - } - - mSpinCutBegin ->set( cut_begin ); - mSpinCutEnd ->set( cut_end ); - mCtrlPathBegin ->set( adv_cut_begin ); - mCtrlPathEnd ->set( adv_cut_end ); - calcp->setVar(LLCalc::CUT_BEGIN, cut_begin); - calcp->setVar(LLCalc::CUT_END, cut_end); - calcp->setVar(LLCalc::PATH_BEGIN, adv_cut_begin); - calcp->setVar(LLCalc::PATH_END, adv_cut_end); - - // Twist - F32 twist = volume_params.getTwist(); - F32 twist_begin = volume_params.getTwistBegin(); - // Check the path type for conversion. - if (path == LL_PCODE_PATH_LINE || path == LL_PCODE_PATH_FLEXIBLE) - { - twist *= OBJECT_TWIST_LINEAR_MAX; - twist_begin *= OBJECT_TWIST_LINEAR_MAX; - } - else - { - twist *= OBJECT_TWIST_MAX; - twist_begin *= OBJECT_TWIST_MAX; - } - - mSpinTwist ->set( twist ); - mSpinTwistBegin ->set( twist_begin ); - calcp->setVar(LLCalc::TWIST_END, twist); - calcp->setVar(LLCalc::TWIST_BEGIN, twist_begin); - - // Shear - F32 shear_x = volume_params.getShearX(); - F32 shear_y = volume_params.getShearY(); - mSpinShearX->set( shear_x ); - mSpinShearY->set( shear_y ); - calcp->setVar(LLCalc::X_SHEAR, shear_x); - calcp->setVar(LLCalc::Y_SHEAR, shear_y); - - // Taper - F32 taper_x = volume_params.getTaperX(); - F32 taper_y = volume_params.getTaperY(); - mSpinTaperX->set( taper_x ); - mSpinTaperY->set( taper_y ); - calcp->setVar(LLCalc::X_TAPER, taper_x); - calcp->setVar(LLCalc::Y_TAPER, taper_y); - - // Radius offset. - F32 radius_offset = volume_params.getRadiusOffset(); - // Limit radius offset, based on taper and hole size y. - F32 radius_mag = fabs(radius_offset); - F32 hole_y_mag = fabs(scale_y); - F32 taper_y_mag = fabs(taper_y); - // Check to see if the taper effects us. - if ( (radius_offset > 0.f && taper_y < 0.f) || - (radius_offset < 0.f && taper_y > 0.f) ) - { - // The taper does not help increase the radius offset range. - taper_y_mag = 0.f; - } - F32 max_radius_mag = 1.f - hole_y_mag * (1.f - taper_y_mag) / (1.f - hole_y_mag); - // Enforce the maximum magnitude. - if (radius_mag > max_radius_mag) - { - // Check radius offset sign. - if (radius_offset < 0.f) - { - radius_offset = -max_radius_mag; - } - else - { - radius_offset = max_radius_mag; - } - } - mSpinRadiusOffset->set( radius_offset); - calcp->setVar(LLCalc::RADIUS_OFFSET, radius_offset); - - // Revolutions - F32 revolutions = volume_params.getRevolutions(); - mSpinRevolutions->set( revolutions ); - calcp->setVar(LLCalc::REVOLUTIONS, revolutions); - - // Skew - F32 skew = volume_params.getSkew(); - // Limit skew, based on revolutions hole size x. - F32 skew_mag= fabs(skew); - F32 min_skew_mag = 1.0f - 1.0f / (revolutions * scale_x + 1.0f); - // Discontinuity; A revolution of 1 allows skews below 0.5. - if ( fabs(revolutions - 1.0f) < 0.001) - min_skew_mag = 0.0f; - - // Clip skew. - if (skew_mag < min_skew_mag) - { - // Check skew sign. - if (skew < 0.0f) - { - skew = -min_skew_mag; - } - else - { - skew = min_skew_mag; - } - } - mSpinSkew->set( skew ); - calcp->setVar(LLCalc::SKEW, skew); - } - - // Compute control visibility, label names, and twist range. - // Start with defaults. - bool cut_visible = true; - bool hollow_visible = true; - bool top_size_x_visible = true; - bool top_size_y_visible = true; - bool top_shear_x_visible = true; - bool top_shear_y_visible = true; - bool twist_visible = true; - bool advanced_cut_visible = false; - bool taper_visible = false; - bool skew_visible = false; - bool radius_offset_visible = false; - bool revolutions_visible = false; - bool sculpt_texture_visible = false; - F32 twist_min = OBJECT_TWIST_LINEAR_MIN; - F32 twist_max = OBJECT_TWIST_LINEAR_MAX; - F32 twist_inc = OBJECT_TWIST_LINEAR_INC; - - bool advanced_is_dimple = false; - bool advanced_is_slice = false; - bool size_is_hole = false; - - // Tune based on overall volume type - switch (selected_item) - { - case MI_SPHERE: - top_size_x_visible = false; - top_size_y_visible = false; - top_shear_x_visible = false; - top_shear_y_visible = false; - //twist_visible = false; - advanced_cut_visible = true; - advanced_is_dimple = true; - twist_min = OBJECT_TWIST_MIN; - twist_max = OBJECT_TWIST_MAX; - twist_inc = OBJECT_TWIST_INC; - break; - - case MI_TORUS: - case MI_TUBE: - case MI_RING: - //top_size_x_visible = false; - //top_size_y_visible = false; - size_is_hole = true; - skew_visible = true; - advanced_cut_visible = true; - taper_visible = true; - radius_offset_visible = true; - revolutions_visible = true; - twist_min = OBJECT_TWIST_MIN; - twist_max = OBJECT_TWIST_MAX; - twist_inc = OBJECT_TWIST_INC; - - break; - - case MI_SCULPT: - cut_visible = false; - hollow_visible = false; - twist_visible = false; - top_size_x_visible = false; - top_size_y_visible = false; - top_shear_x_visible = false; - top_shear_y_visible = false; - skew_visible = false; - advanced_cut_visible = false; - taper_visible = false; - radius_offset_visible = false; - revolutions_visible = false; - sculpt_texture_visible = true; - - break; - - case MI_BOX: - advanced_cut_visible = true; - advanced_is_slice = true; - break; - - case MI_CYLINDER: - advanced_cut_visible = true; - advanced_is_slice = true; - break; - - case MI_PRISM: - advanced_cut_visible = true; - advanced_is_slice = true; - break; - - default: - break; - } - - // Check if we need to change top size/hole size params. - switch (selected_item) - { - case MI_SPHERE: - case MI_TORUS: - case MI_TUBE: - case MI_RING: - mSpinScaleX->set( scale_x ); - mSpinScaleY->set( scale_y ); - calcp->setVar(LLCalc::X_HOLE, scale_x); - calcp->setVar(LLCalc::Y_HOLE, scale_y); - mSpinScaleX->setMinValue(OBJECT_MIN_HOLE_SIZE); - mSpinScaleX->setMaxValue(OBJECT_MAX_HOLE_SIZE_X); - mSpinScaleY->setMinValue(OBJECT_MIN_HOLE_SIZE); - mSpinScaleY->setMaxValue(OBJECT_MAX_HOLE_SIZE_Y); - break; - default: - if (editable && single_volume) - { - mSpinScaleX->set( 1.f - scale_x ); - mSpinScaleY->set( 1.f - scale_y ); - mSpinScaleX->setMinValue(-1.f); - mSpinScaleX->setMaxValue(1.f); - mSpinScaleY->setMinValue(-1.f); - mSpinScaleY->setMaxValue(1.f); - - // Torus' Hole Size is Box/Cyl/Prism's Taper - calcp->setVar(LLCalc::X_TAPER, 1.f - scale_x); - calcp->setVar(LLCalc::Y_TAPER, 1.f - scale_y); - - // Box/Cyl/Prism have no hole size - calcp->setVar(LLCalc::X_HOLE, 0.f); - calcp->setVar(LLCalc::Y_HOLE, 0.f); - } - break; - } - - // Check if we need to limit the hollow based on the hole type. - if ( selected_hole == MI_HOLE_SQUARE && - ( selected_item == MI_CYLINDER || selected_item == MI_TORUS || - selected_item == MI_PRISM || selected_item == MI_RING || - selected_item == MI_SPHERE ) ) - { - mSpinHollow->setMinValue(0.f); - mSpinHollow->setMaxValue(70.f); - } - else - { - mSpinHollow->setMinValue(0.f); - mSpinHollow->setMaxValue(95.f); - } - - // Update field enablement - mComboBaseType ->setEnabled( enabled ); - mMenuClipboardParams->setEnabled(enabled); - - mLabelCut ->setEnabled( enabled ); - mSpinCutBegin ->setEnabled( enabled ); - mSpinCutEnd ->setEnabled( enabled ); - - mLabelHollow ->setEnabled( enabled ); - mSpinHollow ->setEnabled( enabled ); - mLabelHoleType ->setEnabled( hole_enabled ); - mComboHoleType ->setEnabled( hole_enabled ); - - mLabelTwist ->setEnabled( enabled ); - mSpinTwist ->setEnabled( enabled ); - mSpinTwistBegin ->setEnabled( enabled ); - - mLabelSkew ->setEnabled( enabled ); - mSpinSkew ->setEnabled( enabled ); - - getChildView("scale_hole")->setVisible( false); - getChildView("scale_taper")->setVisible( false); - if (top_size_x_visible || top_size_y_visible) - { - if (size_is_hole) - { - getChildView("scale_hole")->setVisible( true); - getChildView("scale_hole")->setEnabled(enabled); - } - else - { - getChildView("scale_taper")->setVisible( true); - getChildView("scale_taper")->setEnabled(enabled); - } - } - - mSpinScaleX ->setEnabled( enabled ); - mSpinScaleY ->setEnabled( enabled ); - - mLabelShear ->setEnabled( enabled ); - mSpinShearX ->setEnabled( enabled ); - mSpinShearY ->setEnabled( enabled ); - - getChildView("advanced_cut")->setVisible( false); - getChildView("advanced_dimple")->setVisible( false); - getChildView("advanced_slice")->setVisible( false); - - if (advanced_cut_visible) - { - if (advanced_is_dimple) - { - getChildView("advanced_dimple")->setVisible( true); - getChildView("advanced_dimple")->setEnabled(enabled); - } - - else if (advanced_is_slice) - { - getChildView("advanced_slice")->setVisible( true); - getChildView("advanced_slice")->setEnabled(enabled); - } - else - { - getChildView("advanced_cut")->setVisible( true); - getChildView("advanced_cut")->setEnabled(enabled); - } - } - - mCtrlPathBegin ->setEnabled( enabled ); - mCtrlPathEnd ->setEnabled( enabled ); - - mLabelTaper ->setEnabled( enabled ); - mSpinTaperX ->setEnabled( enabled ); - mSpinTaperY ->setEnabled( enabled ); - - mLabelRadiusOffset->setEnabled( enabled ); - mSpinRadiusOffset ->setEnabled( enabled ); - - mLabelRevolutions->setEnabled( enabled ); - mSpinRevolutions ->setEnabled( enabled ); - - // Update field visibility - mLabelCut ->setVisible( cut_visible ); - mSpinCutBegin ->setVisible( cut_visible ); - mSpinCutEnd ->setVisible( cut_visible ); - - mLabelHollow ->setVisible( hollow_visible ); - mSpinHollow ->setVisible( hollow_visible ); - mLabelHoleType ->setVisible( hollow_visible ); - mComboHoleType ->setVisible( hollow_visible ); - - mLabelTwist ->setVisible( twist_visible ); - mSpinTwist ->setVisible( twist_visible ); - mSpinTwistBegin ->setVisible( twist_visible ); - mSpinTwist ->setMinValue( twist_min ); - mSpinTwist ->setMaxValue( twist_max ); - mSpinTwist ->setIncrement( twist_inc ); - mSpinTwistBegin ->setMinValue( twist_min ); - mSpinTwistBegin ->setMaxValue( twist_max ); - mSpinTwistBegin ->setIncrement( twist_inc ); - - mSpinScaleX ->setVisible( top_size_x_visible ); - mSpinScaleY ->setVisible( top_size_y_visible ); - - mLabelSkew ->setVisible( skew_visible ); - mSpinSkew ->setVisible( skew_visible ); - - mLabelShear ->setVisible( top_shear_x_visible || top_shear_y_visible ); - mSpinShearX ->setVisible( top_shear_x_visible ); - mSpinShearY ->setVisible( top_shear_y_visible ); - - mCtrlPathBegin ->setVisible( advanced_cut_visible ); - mCtrlPathEnd ->setVisible( advanced_cut_visible ); - - mLabelTaper ->setVisible( taper_visible ); - mSpinTaperX ->setVisible( taper_visible ); - mSpinTaperY ->setVisible( taper_visible ); - - mLabelRadiusOffset->setVisible( radius_offset_visible ); - mSpinRadiusOffset ->setVisible( radius_offset_visible ); - - mLabelRevolutions->setVisible( revolutions_visible ); - mSpinRevolutions ->setVisible( revolutions_visible ); - - mCtrlSculptTexture->setVisible(sculpt_texture_visible); - mLabelSculptType->setVisible(sculpt_texture_visible); - mCtrlSculptType->setVisible(sculpt_texture_visible); - - - // sculpt texture - if (selected_item == MI_SCULPT) - { - - - LLUUID id; - LLSculptParams *sculpt_params = (LLSculptParams *)objectp->getParameterEntry(LLNetworkData::PARAMS_SCULPT); - - - if (sculpt_params) // if we have a legal sculpt param block for this object: - { - if (mObject != objectp) // we've just selected a new object, so save for undo - { - mSculptTextureRevert = sculpt_params->getSculptTexture(); - mSculptTypeRevert = sculpt_params->getSculptType(); - } - - U8 sculpt_type = sculpt_params->getSculptType(); - U8 sculpt_stitching = sculpt_type & LL_SCULPT_TYPE_MASK; - bool sculpt_invert = sculpt_type & LL_SCULPT_FLAG_INVERT; - bool sculpt_mirror = sculpt_type & LL_SCULPT_FLAG_MIRROR; - isMesh = (sculpt_stitching == LL_SCULPT_TYPE_MESH); - - LLTextureCtrl* mTextureCtrl = getChild("sculpt texture control"); - if(mTextureCtrl) - { - mTextureCtrl->setTentative(false); - mTextureCtrl->setEnabled(editable && !isMesh); - if (editable) - mTextureCtrl->setImageAssetID(sculpt_params->getSculptTexture()); - else - mTextureCtrl->setImageAssetID(LLUUID::null); - } - - mComboBaseType->setEnabled(!isMesh); - mMenuClipboardParams->setEnabled(!isMesh); - - if (mCtrlSculptType) - { - if (sculpt_stitching == LL_SCULPT_TYPE_NONE) - { - // since 'None' is no longer an option in the combo box - // use 'Plane' as an equivalent sculpt type - mCtrlSculptType->setSelectedByValue(LLSD(LL_SCULPT_TYPE_PLANE), true); - } - else - { - mCtrlSculptType->setSelectedByValue(LLSD(sculpt_stitching), true); - } - mCtrlSculptType->setEnabled(editable && !isMesh); - } - - if (mCtrlSculptMirror) - { - mCtrlSculptMirror->set(sculpt_mirror); - mCtrlSculptMirror->setEnabled(editable && !isMesh); - } - - if (mCtrlSculptInvert) - { - mCtrlSculptInvert->set(sculpt_invert); - mCtrlSculptInvert->setEnabled(editable); - } - - if (mLabelSculptType) - { - mLabelSculptType->setEnabled(true); - } - - } - } - else - { - mSculptTextureRevert = LLUUID::null; - } - - mCtrlSculptMirror->setVisible(sculpt_texture_visible && !isMesh); - mCtrlSculptInvert->setVisible(sculpt_texture_visible && !isMesh); - - //---------------------------------------------------------------------------- - - mObject = objectp; - mRootObject = root_objectp; -} - -// static -bool LLPanelObject::precommitValidate( const LLSD& data ) -{ - // TODO: Richard will fill this in later. - return true; // false means that validation failed and new value should not be commited. -} - -void LLPanelObject::sendIsPhysical() -{ - bool value = mCheckPhysics->get(); - if( mIsPhysical != value ) - { - LLSelectMgr::getInstance()->selectionUpdatePhysics(value); - mIsPhysical = value; - - LL_INFOS("FloaterTools") << "update physics sent" << LL_ENDL; - } - else - { - LL_INFOS("FloaterTools") << "update physics not changed" << LL_ENDL; - } -} - -void LLPanelObject::sendIsTemporary() -{ - bool value = mCheckTemporary->get(); - if( mIsTemporary != value ) - { - LLSelectMgr::getInstance()->selectionUpdateTemporary(value); - mIsTemporary = value; - - LL_INFOS("FloaterTools") << "update temporary sent" << LL_ENDL; - } - else - { - LL_INFOS("FloaterTools") << "update temporary not changed" << LL_ENDL; - } -} - - -void LLPanelObject::sendIsPhantom() -{ - bool value = mCheckPhantom->get(); - if( mIsPhantom != value ) - { - LLSelectMgr::getInstance()->selectionUpdatePhantom(value); - mIsPhantom = value; - - LL_INFOS("FloaterTools") << "update phantom sent" << LL_ENDL; - } - else - { - LL_INFOS("FloaterTools") << "update phantom not changed" << LL_ENDL; - } -} - -// static -void LLPanelObject::onCommitParametric( LLUICtrl* ctrl, void* userdata ) -{ - LLPanelObject* self = (LLPanelObject*) userdata; - - if (self->mObject.isNull()) - { - return; - } - - if (self->mObject->getPCode() != LL_PCODE_VOLUME) - { - // Don't allow modification of non-volume objects. - return; - } - - LLVolume *volume = self->mObject->getVolume(); - if (!volume) - { - return; - } - - LLVolumeParams volume_params; - self->getVolumeParams(volume_params); - - - - // set sculpting - S32 selected_type = self->mComboBaseType->getCurrentIndex(); - - if (selected_type == MI_SCULPT) - { - self->mObject->setParameterEntryInUse(LLNetworkData::PARAMS_SCULPT, true, true); - LLSculptParams *sculpt_params = (LLSculptParams *)self->mObject->getParameterEntry(LLNetworkData::PARAMS_SCULPT); - if (sculpt_params) - volume_params.setSculptID(sculpt_params->getSculptTexture(), sculpt_params->getSculptType()); - } - else - { - LLSculptParams *sculpt_params = (LLSculptParams *)self->mObject->getParameterEntry(LLNetworkData::PARAMS_SCULPT); - if (sculpt_params) - self->mObject->setParameterEntryInUse(LLNetworkData::PARAMS_SCULPT, false, true); - } - - // Update the volume, if necessary. - self->mObject->updateVolume(volume_params); - - - // This was added to make sure thate when changes are made, the UI - // adjusts to present valid options. - // *FIX: only some changes, ie, hollow or primitive type changes, - // require a refresh. - self->refresh(); - -} - -void LLPanelObject::getVolumeParams(LLVolumeParams& volume_params) -{ - // Figure out what type of volume to make - S32 was_selected_type = mSelectedType; - S32 selected_type = mComboBaseType->getCurrentIndex(); - U8 profile; - U8 path; - switch ( selected_type ) - { - case MI_CYLINDER: - profile = LL_PCODE_PROFILE_CIRCLE; - path = LL_PCODE_PATH_LINE; - break; - - case MI_BOX: - profile = LL_PCODE_PROFILE_SQUARE; - path = LL_PCODE_PATH_LINE; - break; - - case MI_PRISM: - profile = LL_PCODE_PROFILE_EQUALTRI; - path = LL_PCODE_PATH_LINE; - break; - - case MI_SPHERE: - profile = LL_PCODE_PROFILE_CIRCLE_HALF; - path = LL_PCODE_PATH_CIRCLE; - break; - - case MI_TORUS: - profile = LL_PCODE_PROFILE_CIRCLE; - path = LL_PCODE_PATH_CIRCLE; - break; - - case MI_TUBE: - profile = LL_PCODE_PROFILE_SQUARE; - path = LL_PCODE_PATH_CIRCLE; - break; - - case MI_RING: - profile = LL_PCODE_PROFILE_EQUALTRI; - path = LL_PCODE_PATH_CIRCLE; - break; - - case MI_SCULPT: - profile = LL_PCODE_PROFILE_CIRCLE; - path = LL_PCODE_PATH_CIRCLE; - break; - - default: - LL_WARNS("FloaterTools") << "Unknown base type " << selected_type - << " in getVolumeParams()" << LL_ENDL; - // assume a box - selected_type = MI_BOX; - profile = LL_PCODE_PROFILE_SQUARE; - path = LL_PCODE_PATH_LINE; - break; - } - - - if (path == LL_PCODE_PATH_LINE) - { - LLVOVolume *volobjp = (LLVOVolume *)(LLViewerObject*)(mObject); - if (volobjp->isFlexible()) - { - path = LL_PCODE_PATH_FLEXIBLE; - } - } - - S32 selected_hole = mComboHoleType->getCurrentIndex(); - U8 hole; - switch (selected_hole) - { - case MI_HOLE_CIRCLE: - hole = LL_PCODE_HOLE_CIRCLE; - break; - case MI_HOLE_SQUARE: - hole = LL_PCODE_HOLE_SQUARE; - break; - case MI_HOLE_TRIANGLE: - hole = LL_PCODE_HOLE_TRIANGLE; - break; - case MI_HOLE_SAME: - default: - hole = LL_PCODE_HOLE_SAME; - break; - } - - volume_params.setType(profile | hole, path); - mSelectedType = selected_type; - - // Compute cut start/end - F32 cut_begin = mSpinCutBegin->get(); - F32 cut_end = mSpinCutEnd->get(); - - // Make sure at least OBJECT_CUT_INC of the object survives - if (cut_begin > cut_end - OBJECT_MIN_CUT_INC) - { - cut_begin = cut_end - OBJECT_MIN_CUT_INC; - mSpinCutBegin->set(cut_begin); - } - - F32 adv_cut_begin = mCtrlPathBegin->get(); - F32 adv_cut_end = mCtrlPathEnd->get(); - - // Make sure at least OBJECT_CUT_INC of the object survives - if (adv_cut_begin > adv_cut_end - OBJECT_MIN_CUT_INC) - { - adv_cut_begin = adv_cut_end - OBJECT_MIN_CUT_INC; - mCtrlPathBegin->set(adv_cut_begin); - } - - F32 begin_s, end_s; - F32 begin_t, end_t; - - if (selected_type == MI_SPHERE || selected_type == MI_TORUS || - selected_type == MI_TUBE || selected_type == MI_RING) - { - begin_s = adv_cut_begin; - end_s = adv_cut_end; - - begin_t = cut_begin; - end_t = cut_end; - } - else - { - begin_s = cut_begin; - end_s = cut_end; - - begin_t = adv_cut_begin; - end_t = adv_cut_end; - } - - volume_params.setBeginAndEndS(begin_s, end_s); - volume_params.setBeginAndEndT(begin_t, end_t); - - // Hollowness - F32 hollow = mSpinHollow->get() / 100.f; - - if ( selected_hole == MI_HOLE_SQUARE && - ( selected_type == MI_CYLINDER || selected_type == MI_TORUS || - selected_type == MI_PRISM || selected_type == MI_RING || - selected_type == MI_SPHERE ) ) - { - if (hollow > 0.7f) hollow = 0.7f; - } - - volume_params.setHollow( hollow ); - - // Twist Begin,End - F32 twist_begin = mSpinTwistBegin->get(); - F32 twist = mSpinTwist->get(); - // Check the path type for twist conversion. - if (path == LL_PCODE_PATH_LINE || path == LL_PCODE_PATH_FLEXIBLE) - { - twist_begin /= OBJECT_TWIST_LINEAR_MAX; - twist /= OBJECT_TWIST_LINEAR_MAX; - } - else - { - twist_begin /= OBJECT_TWIST_MAX; - twist /= OBJECT_TWIST_MAX; - } - - volume_params.setTwistBegin(twist_begin); - volume_params.setTwist(twist); - - // Scale X,Y - F32 scale_x = mSpinScaleX->get(); - F32 scale_y = mSpinScaleY->get(); - if ( was_selected_type == MI_BOX || was_selected_type == MI_CYLINDER || was_selected_type == MI_PRISM) - { - scale_x = 1.f - scale_x; - scale_y = 1.f - scale_y; - } - - // Skew - F32 skew = mSpinSkew->get(); - - // Taper X,Y - F32 taper_x = mSpinTaperX->get(); - F32 taper_y = mSpinTaperY->get(); - - // Radius offset - F32 radius_offset = mSpinRadiusOffset->get(); - - // Revolutions - F32 revolutions = mSpinRevolutions->get(); - - if ( selected_type == MI_SPHERE ) - { - // Snap values to valid sphere parameters. - scale_x = 1.0f; - scale_y = 1.0f; - skew = 0.0f; - taper_x = 0.0f; - taper_y = 0.0f; - radius_offset = 0.0f; - revolutions = 1.0f; - } - else if ( selected_type == MI_TORUS || selected_type == MI_TUBE || - selected_type == MI_RING ) - { - scale_x = llclamp( - scale_x, - OBJECT_MIN_HOLE_SIZE, - OBJECT_MAX_HOLE_SIZE_X); - scale_y = llclamp( - scale_y, - OBJECT_MIN_HOLE_SIZE, - OBJECT_MAX_HOLE_SIZE_Y); - - // Limit radius offset, based on taper and hole size y. - F32 radius_mag = fabs(radius_offset); - F32 hole_y_mag = fabs(scale_y); - F32 taper_y_mag = fabs(taper_y); - // Check to see if the taper effects us. - if ( (radius_offset > 0.f && taper_y < 0.f) || - (radius_offset < 0.f && taper_y > 0.f) ) - { - // The taper does not help increase the radius offset range. - taper_y_mag = 0.f; - } - F32 max_radius_mag = 1.f - hole_y_mag * (1.f - taper_y_mag) / (1.f - hole_y_mag); - // Enforce the maximum magnitude. - if (radius_mag > max_radius_mag) - { - // Check radius offset sign. - if (radius_offset < 0.f) - { - radius_offset = -max_radius_mag; - } - else - { - radius_offset = max_radius_mag; - } - } - - // Check the skew value against the revolutions. - F32 skew_mag= fabs(skew); - F32 min_skew_mag = 1.0f - 1.0f / (revolutions * scale_x + 1.0f); - // Discontinuity; A revolution of 1 allows skews below 0.5. - if ( fabs(revolutions - 1.0f) < 0.001) - min_skew_mag = 0.0f; - - // Clip skew. - if (skew_mag < min_skew_mag) - { - // Check skew sign. - if (skew < 0.0f) - { - skew = -min_skew_mag; - } - else - { - skew = min_skew_mag; - } - } - } - - volume_params.setRatio( scale_x, scale_y ); - volume_params.setSkew(skew); - volume_params.setTaper( taper_x, taper_y ); - volume_params.setRadiusOffset(radius_offset); - volume_params.setRevolutions(revolutions); - - // Shear X,Y - F32 shear_x = mSpinShearX->get(); - F32 shear_y = mSpinShearY->get(); - volume_params.setShear( shear_x, shear_y ); - - if (selected_type == MI_SCULPT) - { - volume_params.setSculptID(LLUUID::null, 0); - volume_params.setBeginAndEndT (0, 1); - volume_params.setBeginAndEndS (0, 1); - volume_params.setHollow (0); - volume_params.setTwistBegin (0); - volume_params.setTwistEnd (0); - volume_params.setRatio (1, 0.5); - volume_params.setShear (0, 0); - volume_params.setTaper (0, 0); - volume_params.setRevolutions (1); - volume_params.setRadiusOffset (0); - volume_params.setSkew (0); - } - -} - -// BUG: Make work with multiple objects -void LLPanelObject::sendRotation(bool btn_down) -{ - if (mObject.isNull()) return; - - LLVector3 new_rot(mCtrlRotX->get(), mCtrlRotY->get(), mCtrlRotZ->get()); - new_rot.mV[VX] = ll_round(new_rot.mV[VX], OBJECT_ROTATION_PRECISION); - new_rot.mV[VY] = ll_round(new_rot.mV[VY], OBJECT_ROTATION_PRECISION); - new_rot.mV[VZ] = ll_round(new_rot.mV[VZ], OBJECT_ROTATION_PRECISION); - - // Note: must compare before conversion to radians - LLVector3 delta = new_rot - mCurEulerDegrees; - - if (delta.magVec() >= 0.0005f) - { - mCurEulerDegrees = new_rot; - new_rot *= DEG_TO_RAD; - - LLQuaternion rotation; - rotation.setQuat(new_rot.mV[VX], new_rot.mV[VY], new_rot.mV[VZ]); - - if (mRootObject != mObject) - { - rotation = rotation * ~mRootObject->getRotationRegion(); - } - - // To include avatars into movements and rotation - // If false, all children are selected anyway - move avatar - // If true, not all children are selected - save positions - bool individual_selection = gSavedSettings.getBOOL("EditLinkedParts"); - std::vector& child_positions = mObject->mUnselectedChildrenPositions ; - std::vector child_rotations; - if (mObject->isRootEdit() && individual_selection) - { - mObject->saveUnselectedChildrenRotation(child_rotations) ; - mObject->saveUnselectedChildrenPosition(child_positions) ; - } - - mObject->setRotation(rotation); - LLManip::rebuild(mObject) ; - - // for individually selected roots, we need to counterrotate all the children - if (mObject->isRootEdit() && individual_selection) - { - mObject->resetChildrenRotationAndPosition(child_rotations, child_positions) ; - } - - if(!btn_down) - { - child_positions.clear() ; - LLSelectMgr::getInstance()->sendMultipleUpdate(UPD_ROTATION | UPD_POSITION); - } - } -} - - -// BUG: Make work with multiple objects -void LLPanelObject::sendScale(bool btn_down) -{ - if (mObject.isNull()) return; - - LLVector3 newscale(mCtrlScaleX->get(), mCtrlScaleY->get(), mCtrlScaleZ->get()); - - LLVector3 delta = newscale - mObject->getScale(); - if (delta.magVec() >= 0.0005f || (mSizeChanged && !btn_down)) - { - // scale changed by more than 1/2 millimeter - mSizeChanged = btn_down; - - // check to see if we aren't scaling the textures - // (in which case the tex coord's need to be recomputed) - bool dont_stretch_textures = !LLManipScale::getStretchTextures(); - if (dont_stretch_textures) - { - LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_SCALE); - } - - mObject->setScale(newscale, true); - - if(!btn_down) - { - LLSelectMgr::getInstance()->sendMultipleUpdate(UPD_SCALE | UPD_POSITION); - } - - LLSelectMgr::getInstance()->adjustTexturesByScale(true, !dont_stretch_textures); -// LL_INFOS() << "scale sent" << LL_ENDL; - } - else - { -// LL_INFOS() << "scale not changed" << LL_ENDL; - } -} - - -void LLPanelObject::sendPosition(bool btn_down) -{ - if (mObject.isNull()) return; - - LLVector3 newpos(mCtrlPosX->get(), mCtrlPosY->get(), mCtrlPosZ->get()); - LLViewerRegion* regionp = mObject->getRegion(); - - if (!regionp) return; - - if (!mObject->isAttachment()) - { - // Clamp the Z height - const F32 height = newpos.mV[VZ]; - const F32 min_height = LLWorld::getInstance()->getMinAllowedZ(mObject, mObject->getPositionGlobal()); - const F32 max_height = LLWorld::getInstance()->getRegionMaxHeight(); - - if ( height < min_height) - { - newpos.mV[VZ] = min_height; - mCtrlPosZ->set( min_height ); - } - else if ( height > max_height ) - { - newpos.mV[VZ] = max_height; - mCtrlPosZ->set( max_height ); - } - - // Grass is always drawn on the ground, so clamp its position to the ground - if (mObject->getPCode() == LL_PCODE_LEGACY_GRASS) - { - mCtrlPosZ->set(LLWorld::getInstance()->resolveLandHeightAgent(newpos) + 1.f); - } - } - else - { - if (newpos.length() > MAX_ATTACHMENT_DIST) - { - newpos.clampLength(MAX_ATTACHMENT_DIST); - mCtrlPosX->set(newpos.mV[VX]); - mCtrlPosY->set(newpos.mV[VY]); - mCtrlPosZ->set(newpos.mV[VZ]); - } - } - - // Make sure new position is in a valid region, so the object - // won't get dumped by the simulator. - LLVector3d new_pos_global = regionp->getPosGlobalFromRegion(newpos); - bool is_valid_pos = true; - if (mObject->isAttachment()) - { - LLVector3 delta_pos = mObject->getPositionEdit() - newpos; - LLVector3d attachment_pos = regionp->getPosGlobalFromRegion(mObject->getPositionRegion() + delta_pos); - is_valid_pos = LLWorld::getInstance()->positionRegionValidGlobal(attachment_pos); - } - else - { - is_valid_pos = LLWorld::getInstance()->positionRegionValidGlobal(new_pos_global); - } - - if (is_valid_pos) - { - // send only if the position is changed, that is, the delta vector is not zero - LLVector3d old_pos_global = mObject->getPositionGlobal(); - LLVector3d delta = new_pos_global - old_pos_global; - // moved more than 1/2 millimeter - if (delta.magVec() >= 0.0005f) - { - if (mRootObject != mObject) - { - newpos = newpos - mRootObject->getPositionRegion(); - newpos = newpos * ~mRootObject->getRotationRegion(); - mObject->setPositionParent(newpos); - } - else - { - mObject->setPositionEdit(newpos); - } - - LLManip::rebuild(mObject) ; - - // for individually selected roots, we need to counter-translate all unselected children - if (mObject->isRootEdit()) - { - // only offset by parent's translation - mObject->resetChildrenPosition(LLVector3(-delta), true, true) ; - } - - if(!btn_down) - { - LLSelectMgr::getInstance()->sendMultipleUpdate(UPD_POSITION); - } - - LLSelectMgr::getInstance()->updateSelectionCenter(); - } - } - else - { - // move failed, so we update the UI with the correct values - LLVector3 vec = mRootObject->getPositionRegion(); - mCtrlPosX->set(vec.mV[VX]); - mCtrlPosY->set(vec.mV[VY]); - mCtrlPosZ->set(vec.mV[VZ]); - } -} - -void LLPanelObject::sendSculpt() -{ - if (mObject.isNull()) - return; - - LLSculptParams sculpt_params; - LLUUID sculpt_id = LLUUID::null; - - if (mCtrlSculptTexture) - { - sculpt_id = mCtrlSculptTexture->getImageAssetID(); - } - - U8 sculpt_type = 0; - - if (mCtrlSculptType) - { - sculpt_type |= mCtrlSculptType->getValue().asInteger(); - } - - bool enabled = sculpt_type != LL_SCULPT_TYPE_MESH; - - if (mCtrlSculptMirror) - { - mCtrlSculptMirror->setEnabled(enabled); - } - - if (mCtrlSculptInvert) - { - mCtrlSculptInvert->setEnabled(enabled); - } - - if ((mCtrlSculptMirror) && (mCtrlSculptMirror->get())) - { - sculpt_type |= LL_SCULPT_FLAG_MIRROR; - } - - if ((mCtrlSculptInvert) && (mCtrlSculptInvert->get())) - { - sculpt_type |= LL_SCULPT_FLAG_INVERT; - } - - sculpt_params.setSculptTexture(sculpt_id, sculpt_type); - mObject->setParameterEntry(LLNetworkData::PARAMS_SCULPT, sculpt_params, true); -} - -void LLPanelObject::refresh() -{ - getState(); - if (mObject.notNull() && mObject->isDead()) - { - mObject = NULL; - } - - if (mRootObject.notNull() && mRootObject->isDead()) - { - mRootObject = NULL; - } - - F32 max_scale = get_default_max_prim_scale(LLPickInfo::isFlora(mObject)); - - getChild("Scale X")->setMaxValue(max_scale); - getChild("Scale Y")->setMaxValue(max_scale); - getChild("Scale Z")->setMaxValue(max_scale); -} - - -void LLPanelObject::draw() -{ - const LLColor4 white( 1.0f, 1.0f, 1.0f, 1); - const LLColor4 red( 1.0f, 0.25f, 0.f, 1); - const LLColor4 green( 0.f, 1.0f, 0.f, 1); - const LLColor4 blue( 0.f, 0.5f, 1.0f, 1); - - // Tune the colors of the labels - LLTool* tool = LLToolMgr::getInstance()->getCurrentTool(); - - if (tool == LLToolCompTranslate::getInstance()) - { - mCtrlPosX ->setLabelColor(red); - mCtrlPosY ->setLabelColor(green); - mCtrlPosZ ->setLabelColor(blue); - - mCtrlScaleX ->setLabelColor(white); - mCtrlScaleY ->setLabelColor(white); - mCtrlScaleZ ->setLabelColor(white); - - mCtrlRotX ->setLabelColor(white); - mCtrlRotY ->setLabelColor(white); - mCtrlRotZ ->setLabelColor(white); - } - else if ( tool == LLToolCompScale::getInstance() ) - { - mCtrlPosX ->setLabelColor(white); - mCtrlPosY ->setLabelColor(white); - mCtrlPosZ ->setLabelColor(white); - - mCtrlScaleX ->setLabelColor(red); - mCtrlScaleY ->setLabelColor(green); - mCtrlScaleZ ->setLabelColor(blue); - - mCtrlRotX ->setLabelColor(white); - mCtrlRotY ->setLabelColor(white); - mCtrlRotZ ->setLabelColor(white); - } - else if ( tool == LLToolCompRotate::getInstance() ) - { - mCtrlPosX ->setLabelColor(white); - mCtrlPosY ->setLabelColor(white); - mCtrlPosZ ->setLabelColor(white); - - mCtrlScaleX ->setLabelColor(white); - mCtrlScaleY ->setLabelColor(white); - mCtrlScaleZ ->setLabelColor(white); - - mCtrlRotX ->setLabelColor(red); - mCtrlRotY ->setLabelColor(green); - mCtrlRotZ ->setLabelColor(blue); - } - else - { - mCtrlPosX ->setLabelColor(white); - mCtrlPosY ->setLabelColor(white); - mCtrlPosZ ->setLabelColor(white); - - mCtrlScaleX ->setLabelColor(white); - mCtrlScaleY ->setLabelColor(white); - mCtrlScaleZ ->setLabelColor(white); - - mCtrlRotX ->setLabelColor(white); - mCtrlRotY ->setLabelColor(white); - mCtrlRotZ ->setLabelColor(white); - } - - LLPanel::draw(); -} - -// virtual -void LLPanelObject::clearCtrls() -{ - LLPanel::clearCtrls(); - - mCheckLock ->set(false); - mCheckLock ->setEnabled( false ); - mCheckPhysics ->set(false); - mCheckPhysics ->setEnabled( false ); - mCheckTemporary ->set(false); - mCheckTemporary ->setEnabled( false ); - mCheckPhantom ->set(false); - mCheckPhantom ->setEnabled( false ); - - // Disable text labels - mLabelPosition ->setEnabled( false ); - mLabelSize ->setEnabled( false ); - mLabelRotation ->setEnabled( false ); - mLabelCut ->setEnabled( false ); - mLabelHollow ->setEnabled( false ); - mLabelHoleType ->setEnabled( false ); - mLabelTwist ->setEnabled( false ); - mLabelSkew ->setEnabled( false ); - mLabelShear ->setEnabled( false ); - mLabelTaper ->setEnabled( false ); - mLabelRadiusOffset->setEnabled( false ); - mLabelRevolutions->setEnabled( false ); - - getChildView("scale_hole")->setEnabled(false); - getChildView("scale_taper")->setEnabled(false); - getChildView("advanced_cut")->setEnabled(false); - getChildView("advanced_dimple")->setEnabled(false); - getChildView("advanced_slice")->setVisible( false); -} - -// -// Static functions -// - -// static -void LLPanelObject::onCommitLock(LLUICtrl *ctrl, void *data) -{ - // Checkbox will have toggled itself - LLPanelObject *self = (LLPanelObject *)data; - - if(self->mRootObject.isNull()) return; - - bool new_state = self->mCheckLock->get(); - - LLSelectMgr::getInstance()->selectionSetObjectPermissions(PERM_OWNER, !new_state, PERM_MOVE | PERM_MODIFY); -} - -// static -void LLPanelObject::onCommitPosition( LLUICtrl* ctrl, void* userdata ) -{ - LLPanelObject* self = (LLPanelObject*) userdata; - bool btn_down = ((LLSpinCtrl*)ctrl)->isMouseHeldDown() ; - self->sendPosition(btn_down); -} - -// static -void LLPanelObject::onCommitScale( LLUICtrl* ctrl, void* userdata ) -{ - LLPanelObject* self = (LLPanelObject*) userdata; - bool btn_down = ((LLSpinCtrl*)ctrl)->isMouseHeldDown() ; - self->sendScale(btn_down); -} - -// static -void LLPanelObject::onCommitRotation( LLUICtrl* ctrl, void* userdata ) -{ - LLPanelObject* self = (LLPanelObject*) userdata; - bool btn_down = ((LLSpinCtrl*)ctrl)->isMouseHeldDown() ; - self->sendRotation(btn_down); -} - -// static -void LLPanelObject::onCommitPhysics( LLUICtrl* ctrl, void* userdata ) -{ - LLPanelObject* self = (LLPanelObject*) userdata; - self->sendIsPhysical(); -} - -// static -void LLPanelObject::onCommitTemporary( LLUICtrl* ctrl, void* userdata ) -{ - LLPanelObject* self = (LLPanelObject*) userdata; - self->sendIsTemporary(); -} - -// static -void LLPanelObject::onCommitPhantom( LLUICtrl* ctrl, void* userdata ) -{ - LLPanelObject* self = (LLPanelObject*) userdata; - self->sendIsPhantom(); -} - -void LLPanelObject::onSelectSculpt(const LLSD& data) -{ - LLTextureCtrl* mTextureCtrl = getChild("sculpt texture control"); - - if (mTextureCtrl) - { - mSculptTextureRevert = mTextureCtrl->getImageAssetID(); - } - - sendSculpt(); -} - - -void LLPanelObject::onCommitSculpt( const LLSD& data ) -{ - sendSculpt(); -} - -bool LLPanelObject::onDropSculpt(LLInventoryItem* item) -{ - LLTextureCtrl* mTextureCtrl = getChild("sculpt texture control"); - - if (mTextureCtrl) - { - LLUUID asset = item->getAssetUUID(); - - mTextureCtrl->setImageAssetID(asset); - mSculptTextureRevert = asset; - } - - return true; -} - - -void LLPanelObject::onCancelSculpt(const LLSD& data) -{ - LLTextureCtrl* mTextureCtrl = getChild("sculpt texture control"); - if(!mTextureCtrl) - return; - - if(mSculptTextureRevert == LLUUID::null) - { - mSculptTextureRevert = SCULPT_DEFAULT_TEXTURE; - } - mTextureCtrl->setImageAssetID(mSculptTextureRevert); - - sendSculpt(); -} - -// static -void LLPanelObject::onCommitSculptType(LLUICtrl *ctrl, void* userdata) -{ - LLPanelObject* self = (LLPanelObject*) userdata; - - self->sendSculpt(); -} - -void LLPanelObject::menuDoToSelected(const LLSD& userdata) -{ - std::string command = userdata.asString(); - - // paste - if (command == "psr_paste") - { - onPastePos(); - onPasteSize(); - onPasteRot(); - } - else if (command == "pos_paste") - { - onPastePos(); - } - else if (command == "size_paste") - { - onPasteSize(); - } - else if (command == "rot_paste") - { - onPasteRot(); - } - else if (command == "params_paste") - { - onPasteParams(); - } - // copy - else if (command == "psr_copy") - { - onCopyPos(); - onCopySize(); - onCopyRot(); - } - else if (command == "pos_copy") - { - onCopyPos(); - } - else if (command == "size_copy") - { - onCopySize(); - } - else if (command == "rot_copy") - { - onCopyRot(); - } - else if (command == "params_copy") - { - onCopyParams(); - } -} - -bool LLPanelObject::menuEnableItem(const LLSD& userdata) -{ - std::string command = userdata.asString(); - - // paste options - if (command == "psr_paste") - { - S32 selected_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount(); - bool single_volume = (LLSelectMgr::getInstance()->selectionAllPCode(LL_PCODE_VOLUME)) - && (selected_count == 1); - - if (!single_volume) - { - return false; - } - - bool enable_move; - bool enable_modify; - - LLSelectMgr::getInstance()->selectGetEditMoveLinksetPermissions(enable_move, enable_modify); - - return enable_move && enable_modify && mHasClipboardPos && mHasClipboardSize && mHasClipboardRot; - } - else if (command == "pos_paste") - { - // assumes that menu won't be active if there is no move permission - return mHasClipboardPos; - } - else if (command == "size_paste") - { - return mHasClipboardSize; - } - else if (command == "rot_paste") - { - return mHasClipboardRot; - } - else if (command == "params_paste") - { - return mClipboardParams.isMap() && (mClipboardParams.size() != 0); - } - // copy options - else if (command == "psr_copy") - { - S32 selected_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount(); - bool single_volume = (LLSelectMgr::getInstance()->selectionAllPCode(LL_PCODE_VOLUME)) - && (selected_count == 1); - - if (!single_volume) - { - return false; - } - - bool enable_move; - bool enable_modify; - - LLSelectMgr::getInstance()->selectGetEditMoveLinksetPermissions(enable_move, enable_modify); - - // since we forbid seeing values we also should forbid copying them - return enable_move && enable_modify; - } - return false; -} - -void LLPanelObject::onCopyPos() -{ - mClipboardPos = LLVector3(mCtrlPosX->get(), mCtrlPosY->get(), mCtrlPosZ->get()); - - std::string stringVec = llformat("<%g, %g, %g>", mClipboardPos.mV[VX], mClipboardPos.mV[VY], mClipboardPos.mV[VZ]); - LLView::getWindow()->copyTextToClipboard(utf8str_to_wstring(stringVec)); - - mHasClipboardPos = true; -} - -void LLPanelObject::onCopySize() -{ - mClipboardSize = LLVector3(mCtrlScaleX->get(), mCtrlScaleY->get(), mCtrlScaleZ->get()); - - std::string stringVec = llformat("<%g, %g, %g>", mClipboardSize.mV[VX], mClipboardSize.mV[VY], mClipboardSize.mV[VZ]); - LLView::getWindow()->copyTextToClipboard(utf8str_to_wstring(stringVec)); - - mHasClipboardSize = true; -} - -void LLPanelObject::onCopyRot() -{ - mClipboardRot = LLVector3(mCtrlRotX->get(), mCtrlRotY->get(), mCtrlRotZ->get()); - - std::string stringVec = llformat("<%g, %g, %g>", mClipboardRot.mV[VX], mClipboardRot.mV[VY], mClipboardRot.mV[VZ]); - LLView::getWindow()->copyTextToClipboard(utf8str_to_wstring(stringVec)); - - mHasClipboardRot = true; -} - -void LLPanelObject::onPastePos() -{ - if (!mHasClipboardPos) return; - if (mObject.isNull()) return; - - LLViewerRegion* regionp = mObject->getRegion(); - if (!regionp) return; - - - // Clamp pos on non-attachments, just keep the prims within the region - if (!mObject->isAttachment()) - { - F32 max_width = regionp->getWidth(); // meters - mClipboardPos.mV[VX] = llclamp(mClipboardPos.mV[VX], 0.f, max_width); - mClipboardPos.mV[VY] = llclamp(mClipboardPos.mV[VY], 0.f, max_width); - //height will get properly clamped by sendPosition - } - else - { - mClipboardPos.clampLength(MAX_ATTACHMENT_DIST); - } - - mCtrlPosX->set( mClipboardPos.mV[VX] ); - mCtrlPosY->set( mClipboardPos.mV[VY] ); - mCtrlPosZ->set( mClipboardPos.mV[VZ] ); - - sendPosition(false); -} - -void LLPanelObject::onPasteSize() -{ - if (!mHasClipboardSize) return; - - mClipboardSize.mV[VX] = llclamp(mClipboardSize.mV[VX], MIN_PRIM_SCALE, DEFAULT_MAX_PRIM_SCALE); - mClipboardSize.mV[VY] = llclamp(mClipboardSize.mV[VY], MIN_PRIM_SCALE, DEFAULT_MAX_PRIM_SCALE); - mClipboardSize.mV[VZ] = llclamp(mClipboardSize.mV[VZ], MIN_PRIM_SCALE, DEFAULT_MAX_PRIM_SCALE); - - mCtrlScaleX->set(mClipboardSize.mV[VX]); - mCtrlScaleY->set(mClipboardSize.mV[VY]); - mCtrlScaleZ->set(mClipboardSize.mV[VZ]); - - sendScale(false); -} - -void LLPanelObject::onPasteRot() -{ - if (!mHasClipboardRot) return; - - mCtrlRotX->set(mClipboardRot.mV[VX]); - mCtrlRotY->set(mClipboardRot.mV[VY]); - mCtrlRotZ->set(mClipboardRot.mV[VZ]); - - sendRotation(false); -} - -void LLPanelObject::onCopyParams() -{ - LLViewerObject* objectp = mObject; - if (!objectp || objectp->isMesh()) - { - return; - } - - mClipboardParams.clear(); - - // Parametrics - LLVolumeParams params; - getVolumeParams(params); - mClipboardParams["volume_params"] = params.asLLSD(); - - // Sculpted Prim - if (objectp->getParameterEntryInUse(LLNetworkData::PARAMS_SCULPT)) - { - LLSculptParams *sculpt_params = (LLSculptParams *)objectp->getParameterEntry(LLNetworkData::PARAMS_SCULPT); - - LLUUID texture_id = sculpt_params->getSculptTexture(); - if (get_can_copy_texture(texture_id)) - { - LL_DEBUGS("FloaterTools") << "Recording texture" << LL_ENDL; - mClipboardParams["sculpt"]["id"] = texture_id; - } - else - { - mClipboardParams["sculpt"]["id"] = SCULPT_DEFAULT_TEXTURE; - } - - mClipboardParams["sculpt"]["type"] = sculpt_params->getSculptType(); - } -} - -void LLPanelObject::onPasteParams() -{ - LLViewerObject* objectp = mObject; - if (!objectp) - { - return; - } - - // Sculpted Prim - if (mClipboardParams.has("sculpt")) - { - LLSculptParams sculpt_params; - LLUUID sculpt_id = mClipboardParams["sculpt"]["id"].asUUID(); - U8 sculpt_type = (U8)mClipboardParams["sculpt"]["type"].asInteger(); - sculpt_params.setSculptTexture(sculpt_id, sculpt_type); - objectp->setParameterEntry(LLNetworkData::PARAMS_SCULPT, sculpt_params, true); - } - else - { - LLSculptParams *sculpt_params = (LLSculptParams *)objectp->getParameterEntry(LLNetworkData::PARAMS_SCULPT); - if (sculpt_params) - { - objectp->setParameterEntryInUse(LLNetworkData::PARAMS_SCULPT, false, true); - } - } - - // volume params - // make sure updateVolume() won't affect flexible - if (mClipboardParams.has("volume_params")) - { - LLVolumeParams params; - params.fromLLSD(mClipboardParams["volume_params"]); - LLVOVolume *volobjp = (LLVOVolume *)objectp; - if (volobjp->isFlexible()) - { - if (params.getPathParams().getCurveType() == LL_PCODE_PATH_LINE) - { - params.getPathParams().setCurveType(LL_PCODE_PATH_FLEXIBLE); - } - } - else if (params.getPathParams().getCurveType() == LL_PCODE_PATH_FLEXIBLE) - { - params.getPathParams().setCurveType(LL_PCODE_PATH_LINE); - } - - objectp->updateVolume(params); - } -} +/** + * @file llpanelobject.cpp + * @brief Object editing (position, scale, etc.) in the tools floater + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +// file include +#include "llpanelobject.h" + +// linden library includes +#include "llerror.h" +#include "llfontgl.h" +#include "llpermissionsflags.h" +#include "llstring.h" +#include "llvolume.h" +#include "m3math.h" + +// project includes +#include "llagent.h" +#include "llbutton.h" +#include "llcalc.h" +#include "llcheckboxctrl.h" +#include "llcolorswatch.h" +#include "llcombobox.h" +#include "llfocusmgr.h" +#include "llmanipscale.h" +#include "llmenubutton.h" +#include "llpreviewscript.h" +#include "llresmgr.h" +#include "llselectmgr.h" +#include "llspinctrl.h" +#include "lltexturectrl.h" +#include "lltextbox.h" +#include "lltool.h" +#include "lltoolcomp.h" +#include "lltoolmgr.h" +#include "llui.h" +#include "llviewerobject.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" +#include "llvovolume.h" +#include "llworld.h" +#include "pipeline.h" +#include "llviewercontrol.h" +#include "lluictrlfactory.h" +//#include "llfirstuse.h" + +#include "lldrawpool.h" + +// +// Constants +// +enum { + MI_BOX, + MI_CYLINDER, + MI_PRISM, + MI_SPHERE, + MI_TORUS, + MI_TUBE, + MI_RING, + MI_SCULPT, + MI_NONE, + MI_VOLUME_COUNT +}; + +enum { + MI_HOLE_SAME, + MI_HOLE_CIRCLE, + MI_HOLE_SQUARE, + MI_HOLE_TRIANGLE, + MI_HOLE_COUNT +}; + +const F32 MAX_ATTACHMENT_DIST = 3.5f; // meters + +//static const std::string LEGACY_FULLBRIGHT_DESC =LLTrans::getString("Fullbright"); + +bool LLPanelObject::postBuild() +{ + setMouseOpaque(false); + + //-------------------------------------------------------- + // Top + //-------------------------------------------------------- + + // Lock checkbox + mCheckLock = getChild("checkbox locked"); + childSetCommitCallback("checkbox locked",onCommitLock,this); + + // Physical checkbox + mCheckPhysics = getChild("Physical Checkbox Ctrl"); + childSetCommitCallback("Physical Checkbox Ctrl",onCommitPhysics,this); + + // Temporary checkbox + mCheckTemporary = getChild("Temporary Checkbox Ctrl"); + childSetCommitCallback("Temporary Checkbox Ctrl",onCommitTemporary,this); + + // Phantom checkbox + mCheckPhantom = getChild("Phantom Checkbox Ctrl"); + childSetCommitCallback("Phantom Checkbox Ctrl",onCommitPhantom,this); + + // Position + mMenuClipboardPos = getChild("clipboard_pos_btn"); + mLabelPosition = getChild("label position"); + mCtrlPosX = getChild("Pos X"); + childSetCommitCallback("Pos X",onCommitPosition,this); + mCtrlPosY = getChild("Pos Y"); + childSetCommitCallback("Pos Y",onCommitPosition,this); + mCtrlPosZ = getChild("Pos Z"); + childSetCommitCallback("Pos Z",onCommitPosition,this); + + // Scale + mMenuClipboardSize = getChild("clipboard_size_btn"); + mLabelSize = getChild("label size"); + mCtrlScaleX = getChild("Scale X"); + childSetCommitCallback("Scale X",onCommitScale,this); + + // Scale Y + mCtrlScaleY = getChild("Scale Y"); + childSetCommitCallback("Scale Y",onCommitScale,this); + + // Scale Z + mCtrlScaleZ = getChild("Scale Z"); + childSetCommitCallback("Scale Z",onCommitScale,this); + + // Rotation + mMenuClipboardRot = getChild("clipboard_rot_btn"); + mLabelRotation = getChild("label rotation"); + mCtrlRotX = getChild("Rot X"); + childSetCommitCallback("Rot X",onCommitRotation,this); + mCtrlRotY = getChild("Rot Y"); + childSetCommitCallback("Rot Y",onCommitRotation,this); + mCtrlRotZ = getChild("Rot Z"); + childSetCommitCallback("Rot Z",onCommitRotation,this); + + //-------------------------------------------------------- + + // Base Type + mComboBaseType = getChild("comboBaseType"); + childSetCommitCallback("comboBaseType",onCommitParametric,this); + + mMenuClipboardParams = getChild("clipboard_obj_params_btn"); + + // Cut + mLabelCut = getChild("text cut"); + mSpinCutBegin = getChild("cut begin"); + childSetCommitCallback("cut begin",onCommitParametric,this); + mSpinCutBegin->setValidateBeforeCommit( precommitValidate ); + mSpinCutEnd = getChild("cut end"); + childSetCommitCallback("cut end",onCommitParametric,this); + mSpinCutEnd->setValidateBeforeCommit( &precommitValidate ); + + // Hollow / Skew + mLabelHollow = getChild("text hollow"); + mLabelSkew = getChild("text skew"); + mSpinHollow = getChild("Scale 1"); + childSetCommitCallback("Scale 1",onCommitParametric,this); + mSpinHollow->setValidateBeforeCommit( &precommitValidate ); + mSpinSkew = getChild("Skew"); + childSetCommitCallback("Skew",onCommitParametric,this); + mSpinSkew->setValidateBeforeCommit( &precommitValidate ); + mLabelHoleType = getChild("Hollow Shape"); + + // Hole Type + mComboHoleType = getChild("hole"); + childSetCommitCallback("hole",onCommitParametric,this); + + // Twist + mLabelTwist = getChild("text twist"); + mSpinTwistBegin = getChild("Twist Begin"); + childSetCommitCallback("Twist Begin",onCommitParametric,this); + mSpinTwistBegin->setValidateBeforeCommit( precommitValidate ); + mSpinTwist = getChild("Twist End"); + childSetCommitCallback("Twist End",onCommitParametric,this); + mSpinTwist->setValidateBeforeCommit( &precommitValidate ); + + // Scale + mSpinScaleX = getChild("Taper Scale X"); + childSetCommitCallback("Taper Scale X",onCommitParametric,this); + mSpinScaleX->setValidateBeforeCommit( &precommitValidate ); + mSpinScaleY = getChild("Taper Scale Y"); + childSetCommitCallback("Taper Scale Y",onCommitParametric,this); + mSpinScaleY->setValidateBeforeCommit( &precommitValidate ); + + // Shear + mLabelShear = getChild("text topshear"); + mSpinShearX = getChild("Shear X"); + childSetCommitCallback("Shear X",onCommitParametric,this); + mSpinShearX->setValidateBeforeCommit( &precommitValidate ); + mSpinShearY = getChild("Shear Y"); + childSetCommitCallback("Shear Y",onCommitParametric,this); + mSpinShearY->setValidateBeforeCommit( &precommitValidate ); + + // Path / Profile + mCtrlPathBegin = getChild("Path Limit Begin"); + childSetCommitCallback("Path Limit Begin",onCommitParametric,this); + mCtrlPathBegin->setValidateBeforeCommit( &precommitValidate ); + mCtrlPathEnd = getChild("Path Limit End"); + childSetCommitCallback("Path Limit End",onCommitParametric,this); + mCtrlPathEnd->setValidateBeforeCommit( &precommitValidate ); + + // Taper + mLabelTaper = getChild("text taper2"); + mSpinTaperX = getChild("Taper X"); + childSetCommitCallback("Taper X",onCommitParametric,this); + mSpinTaperX->setValidateBeforeCommit( precommitValidate ); + mSpinTaperY = getChild("Taper Y"); + childSetCommitCallback("Taper Y",onCommitParametric,this); + mSpinTaperY->setValidateBeforeCommit( precommitValidate ); + + // Radius Offset / Revolutions + mLabelRadiusOffset = getChild("text radius delta"); + mLabelRevolutions = getChild("text revolutions"); + mSpinRadiusOffset = getChild("Radius Offset"); + childSetCommitCallback("Radius Offset",onCommitParametric,this); + mSpinRadiusOffset->setValidateBeforeCommit( &precommitValidate ); + mSpinRevolutions = getChild("Revolutions"); + childSetCommitCallback("Revolutions",onCommitParametric,this); + mSpinRevolutions->setValidateBeforeCommit( &precommitValidate ); + + // Sculpt + mCtrlSculptTexture = getChild("sculpt texture control"); + if (mCtrlSculptTexture) + { + mCtrlSculptTexture->setDefaultImageAssetID(SCULPT_DEFAULT_TEXTURE); + mCtrlSculptTexture->setCommitCallback( boost::bind(&LLPanelObject::onCommitSculpt, this, _2 )); + mCtrlSculptTexture->setOnCancelCallback( boost::bind(&LLPanelObject::onCancelSculpt, this, _2 )); + mCtrlSculptTexture->setOnSelectCallback( boost::bind(&LLPanelObject::onSelectSculpt, this, _2 )); + mCtrlSculptTexture->setDropCallback( boost::bind(&LLPanelObject::onDropSculpt, this, _2 )); + // Don't allow (no copy) or (no transfer) textures to be selected during immediate mode + mCtrlSculptTexture->setImmediateFilterPermMask(PERM_COPY | PERM_TRANSFER); + mCtrlSculptTexture->setDnDFilterPermMask(PERM_COPY | PERM_TRANSFER); + LLAggregatePermissions texture_perms; + if (LLSelectMgr::getInstance()->selectGetAggregateTexturePermissions(texture_perms)) + { + bool can_copy = + texture_perms.getValue(PERM_COPY) == LLAggregatePermissions::AP_EMPTY || + texture_perms.getValue(PERM_COPY) == LLAggregatePermissions::AP_ALL; + bool can_transfer = + texture_perms.getValue(PERM_TRANSFER) == LLAggregatePermissions::AP_EMPTY || + texture_perms.getValue(PERM_TRANSFER) == LLAggregatePermissions::AP_ALL; + mCtrlSculptTexture->setCanApplyImmediately(can_copy && can_transfer); + } + else + { + mCtrlSculptTexture->setCanApplyImmediately(false); + } + } + + mLabelSculptType = getChild("label sculpt type"); + mCtrlSculptType = getChild("sculpt type control"); + childSetCommitCallback("sculpt type control", onCommitSculptType, this); + mCtrlSculptMirror = getChild("sculpt mirror control"); + childSetCommitCallback("sculpt mirror control", onCommitSculptType, this); + mCtrlSculptInvert = getChild("sculpt invert control"); + childSetCommitCallback("sculpt invert control", onCommitSculptType, this); + + // Start with everyone disabled + clearCtrls(); + + return true; +} + +LLPanelObject::LLPanelObject() +: LLPanel(), + mIsPhysical(false), + mIsTemporary(false), + mIsPhantom(false), + mSelectedType(MI_BOX), + mSculptTextureRevert(LLUUID::null), + mSculptTypeRevert(0), + mHasClipboardPos(false), + mHasClipboardSize(false), + mHasClipboardRot(false), + mSizeChanged(false) +{ + mCommitCallbackRegistrar.add("PanelObject.menuDoToSelected", boost::bind(&LLPanelObject::menuDoToSelected, this, _2)); + mEnableCallbackRegistrar.add("PanelObject.menuEnable", boost::bind(&LLPanelObject::menuEnableItem, this, _2)); +} + + +LLPanelObject::~LLPanelObject() +{ + // Children all cleaned up by default view destructor. +} + +void LLPanelObject::getState( ) +{ + LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getFirstRootObject(); + LLViewerObject* root_objectp = objectp; + if(!objectp) + { + objectp = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); + // *FIX: shouldn't we just keep the child? + if (objectp) + { + LLViewerObject* parentp = objectp->getRootEdit(); + + if (parentp) + { + root_objectp = parentp; + } + else + { + root_objectp = objectp; + } + } + } + + LLCalc* calcp = LLCalc::getInstance(); + + LLVOVolume *volobjp = NULL; + if ( objectp && (objectp->getPCode() == LL_PCODE_VOLUME)) + { + volobjp = (LLVOVolume *)objectp; + } + + if( !objectp ) + { + //forfeit focus + if (gFocusMgr.childHasKeyboardFocus(this)) + { + gFocusMgr.setKeyboardFocus(NULL); + } + + // Disable all text input fields + clearCtrls(); + calcp->clearAllVariables(); + return; + } + + S32 selected_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount(); + bool single_volume = (LLSelectMgr::getInstance()->selectionAllPCode( LL_PCODE_VOLUME )) + && (selected_count == 1); + + bool enable_move; + bool enable_modify; + + LLSelectMgr::getInstance()->selectGetEditMoveLinksetPermissions(enable_move, enable_modify); + + bool enable_scale = enable_modify; + bool enable_rotate = enable_move; // already accounts for a case of children, which needs permModify() as well + + LLVector3 vec; + if (enable_move) + { + vec = objectp->getPositionEdit(); + mCtrlPosX->set( vec.mV[VX] ); + mCtrlPosY->set( vec.mV[VY] ); + mCtrlPosZ->set( vec.mV[VZ] ); + calcp->setVar(LLCalc::X_POS, vec.mV[VX]); + calcp->setVar(LLCalc::Y_POS, vec.mV[VY]); + calcp->setVar(LLCalc::Z_POS, vec.mV[VZ]); + } + else + { + mCtrlPosX->clear(); + mCtrlPosY->clear(); + mCtrlPosZ->clear(); + calcp->clearVar(LLCalc::X_POS); + calcp->clearVar(LLCalc::Y_POS); + calcp->clearVar(LLCalc::Z_POS); + } + + mMenuClipboardPos->setEnabled(enable_move); + mLabelPosition->setEnabled( enable_move ); + mCtrlPosX->setEnabled(enable_move); + mCtrlPosY->setEnabled(enable_move); + mCtrlPosZ->setEnabled(enable_move); + + if (enable_scale) + { + vec = objectp->getScale(); + mCtrlScaleX->set( vec.mV[VX] ); + mCtrlScaleY->set( vec.mV[VY] ); + mCtrlScaleZ->set( vec.mV[VZ] ); + calcp->setVar(LLCalc::X_SCALE, vec.mV[VX]); + calcp->setVar(LLCalc::Y_SCALE, vec.mV[VY]); + calcp->setVar(LLCalc::Z_SCALE, vec.mV[VZ]); + } + else + { + mCtrlScaleX->clear(); + mCtrlScaleY->clear(); + mCtrlScaleZ->clear(); + calcp->setVar(LLCalc::X_SCALE, 0.f); + calcp->setVar(LLCalc::Y_SCALE, 0.f); + calcp->setVar(LLCalc::Z_SCALE, 0.f); + } + + mMenuClipboardSize->setEnabled(enable_scale); + mLabelSize->setEnabled( enable_scale ); + mCtrlScaleX->setEnabled( enable_scale ); + mCtrlScaleY->setEnabled( enable_scale ); + mCtrlScaleZ->setEnabled( enable_scale ); + + LLQuaternion object_rot = objectp->getRotationEdit(); + object_rot.getEulerAngles(&(mCurEulerDegrees.mV[VX]), &(mCurEulerDegrees.mV[VY]), &(mCurEulerDegrees.mV[VZ])); + mCurEulerDegrees *= RAD_TO_DEG; + mCurEulerDegrees.mV[VX] = fmod(ll_round(mCurEulerDegrees.mV[VX], OBJECT_ROTATION_PRECISION) + 360.f, 360.f); + mCurEulerDegrees.mV[VY] = fmod(ll_round(mCurEulerDegrees.mV[VY], OBJECT_ROTATION_PRECISION) + 360.f, 360.f); + mCurEulerDegrees.mV[VZ] = fmod(ll_round(mCurEulerDegrees.mV[VZ], OBJECT_ROTATION_PRECISION) + 360.f, 360.f); + + if (enable_rotate) + { + mCtrlRotX->set( mCurEulerDegrees.mV[VX] ); + mCtrlRotY->set( mCurEulerDegrees.mV[VY] ); + mCtrlRotZ->set( mCurEulerDegrees.mV[VZ] ); + calcp->setVar(LLCalc::X_ROT, mCurEulerDegrees.mV[VX]); + calcp->setVar(LLCalc::Y_ROT, mCurEulerDegrees.mV[VY]); + calcp->setVar(LLCalc::Z_ROT, mCurEulerDegrees.mV[VZ]); + } + else + { + mCtrlRotX->clear(); + mCtrlRotY->clear(); + mCtrlRotZ->clear(); + calcp->clearVar(LLCalc::X_ROT); + calcp->clearVar(LLCalc::Y_ROT); + calcp->clearVar(LLCalc::Z_ROT); + } + + mMenuClipboardRot->setEnabled(enable_rotate); + mLabelRotation->setEnabled( enable_rotate ); + mCtrlRotX->setEnabled( enable_rotate ); + mCtrlRotY->setEnabled( enable_rotate ); + mCtrlRotZ->setEnabled( enable_rotate ); + + LLUUID owner_id; + std::string owner_name; + LLSelectMgr::getInstance()->selectGetOwner(owner_id, owner_name); + + // BUG? Check for all objects being editable? + S32 roots_selected = LLSelectMgr::getInstance()->getSelection()->getRootObjectCount(); + bool editable = root_objectp->permModify(); + + bool is_flexible = volobjp && volobjp->isFlexible(); + bool is_permanent = root_objectp->flagObjectPermanent(); + bool is_permanent_enforced = root_objectp->isPermanentEnforced(); + bool is_character = root_objectp->flagCharacter(); + llassert(!is_permanent || !is_character); // should never have a permanent object that is also a character + + // Lock checkbox - only modifiable if you own the object. + bool self_owned = (gAgent.getID() == owner_id); + mCheckLock->setEnabled( roots_selected > 0 && self_owned && !is_permanent_enforced); + + // More lock and debit checkbox - get the values + bool valid; + U32 owner_mask_on; + U32 owner_mask_off; + valid = LLSelectMgr::getInstance()->selectGetPerm(PERM_OWNER, &owner_mask_on, &owner_mask_off); + + if(valid) + { + if(owner_mask_on & PERM_MOVE) + { + // owner can move, so not locked + mCheckLock->set(false); + mCheckLock->setTentative(false); + } + else if(owner_mask_off & PERM_MOVE) + { + // owner can't move, so locked + mCheckLock->set(true); + mCheckLock->setTentative(false); + } + else + { + // some locked, some not locked + mCheckLock->set(false); + mCheckLock->setTentative(true); + } + } + + // Physics checkbox + mIsPhysical = root_objectp->flagUsePhysics(); + llassert(!is_permanent || !mIsPhysical); // should never have a permanent object that is also physical + + mCheckPhysics->set( mIsPhysical ); + mCheckPhysics->setEnabled( roots_selected>0 + && (editable || gAgent.isGodlike()) + && !is_flexible && !is_permanent); + + mIsTemporary = root_objectp->flagTemporaryOnRez(); + llassert(!is_permanent || !mIsTemporary); // should never has a permanent object that is also temporary + + mCheckTemporary->set( mIsTemporary ); + mCheckTemporary->setEnabled( roots_selected>0 && editable && !is_permanent); + + mIsPhantom = root_objectp->flagPhantom(); + bool is_volume_detect = root_objectp->flagVolumeDetect(); + llassert(!is_character || !mIsPhantom); // should never have a character that is also a phantom + mCheckPhantom->set( mIsPhantom ); + mCheckPhantom->setEnabled( roots_selected>0 && editable && !is_flexible && !is_permanent_enforced && !is_character && !is_volume_detect); + + //---------------------------------------------------------------------------- + + S32 selected_item = MI_BOX; + S32 selected_hole = MI_HOLE_SAME; + bool enabled = false; + bool hole_enabled = false; + F32 scale_x=1.f, scale_y=1.f; + bool isMesh = false; + + if( !objectp || !objectp->getVolume() || !editable || !single_volume) + { + // Clear out all geometry fields. + mComboBaseType->clear(); + mSpinHollow->clear(); + mSpinCutBegin->clear(); + mSpinCutEnd->clear(); + mCtrlPathBegin->clear(); + mCtrlPathEnd->clear(); + mSpinScaleX->clear(); + mSpinScaleY->clear(); + mSpinTwist->clear(); + mSpinTwistBegin->clear(); + mComboHoleType->clear(); + mSpinShearX->clear(); + mSpinShearY->clear(); + mSpinTaperX->clear(); + mSpinTaperY->clear(); + mSpinRadiusOffset->clear(); + mSpinRevolutions->clear(); + mSpinSkew->clear(); + + mSelectedType = MI_NONE; + } + else + { + // Only allowed to change these parameters for objects + // that you have permissions on AND are not attachments. + enabled = root_objectp->permModify() && !root_objectp->isPermanentEnforced(); + + // Volume type + const LLVolumeParams &volume_params = objectp->getVolume()->getParams(); + U8 path = volume_params.getPathParams().getCurveType(); + U8 profile_and_hole = volume_params.getProfileParams().getCurveType(); + U8 profile = profile_and_hole & LL_PCODE_PROFILE_MASK; + U8 hole = profile_and_hole & LL_PCODE_HOLE_MASK; + + // Scale goes first so we can differentiate between a sphere and a torus, + // which have the same profile and path types. + + // Scale + scale_x = volume_params.getRatioX(); + scale_y = volume_params.getRatioY(); + + bool linear_path = (path == LL_PCODE_PATH_LINE) || (path == LL_PCODE_PATH_FLEXIBLE); + if ( linear_path && profile == LL_PCODE_PROFILE_CIRCLE ) + { + selected_item = MI_CYLINDER; + } + else if ( linear_path && profile == LL_PCODE_PROFILE_SQUARE ) + { + selected_item = MI_BOX; + } + else if ( linear_path && profile == LL_PCODE_PROFILE_ISOTRI ) + { + selected_item = MI_PRISM; + } + else if ( linear_path && profile == LL_PCODE_PROFILE_EQUALTRI ) + { + selected_item = MI_PRISM; + } + else if ( linear_path && profile == LL_PCODE_PROFILE_RIGHTTRI ) + { + selected_item = MI_PRISM; + } + else if (path == LL_PCODE_PATH_FLEXIBLE) // shouldn't happen + { + selected_item = MI_CYLINDER; // reasonable default + } + else if ( path == LL_PCODE_PATH_CIRCLE && profile == LL_PCODE_PROFILE_CIRCLE && scale_y > 0.75f) + { + selected_item = MI_SPHERE; + } + else if ( path == LL_PCODE_PATH_CIRCLE && profile == LL_PCODE_PROFILE_CIRCLE && scale_y <= 0.75f) + { + selected_item = MI_TORUS; + } + else if ( path == LL_PCODE_PATH_CIRCLE && profile == LL_PCODE_PROFILE_CIRCLE_HALF) + { + selected_item = MI_SPHERE; + } + else if ( path == LL_PCODE_PATH_CIRCLE2 && profile == LL_PCODE_PROFILE_CIRCLE ) + { + // Spirals aren't supported. Make it into a sphere. JC + selected_item = MI_SPHERE; + } + else if ( path == LL_PCODE_PATH_CIRCLE && profile == LL_PCODE_PROFILE_EQUALTRI ) + { + selected_item = MI_RING; + } + else if ( path == LL_PCODE_PATH_CIRCLE && profile == LL_PCODE_PROFILE_SQUARE && scale_y <= 0.75f) + { + selected_item = MI_TUBE; + } + else + { + LL_INFOS("FloaterTools") << "Unknown path " << (S32) path << " profile " << (S32) profile << " in getState" << LL_ENDL; + selected_item = MI_BOX; + } + + + if (objectp->getParameterEntryInUse(LLNetworkData::PARAMS_SCULPT)) + { + selected_item = MI_SCULPT; + //LLFirstUse::useSculptedPrim(); + } + + + mComboBaseType ->setCurrentByIndex( selected_item ); + mSelectedType = selected_item; + + // Grab S path + F32 begin_s = volume_params.getBeginS(); + F32 end_s = volume_params.getEndS(); + + // Compute cut and advanced cut from S and T + F32 begin_t = volume_params.getBeginT(); + F32 end_t = volume_params.getEndT(); + + // Hollowness + F32 hollow = 100.f * volume_params.getHollow(); + mSpinHollow->set( hollow ); + calcp->setVar(LLCalc::HOLLOW, hollow); + // All hollow objects allow a shape to be selected. + if (hollow > 0.f) + { + switch (hole) + { + case LL_PCODE_HOLE_CIRCLE: + selected_hole = MI_HOLE_CIRCLE; + break; + case LL_PCODE_HOLE_SQUARE: + selected_hole = MI_HOLE_SQUARE; + break; + case LL_PCODE_HOLE_TRIANGLE: + selected_hole = MI_HOLE_TRIANGLE; + break; + case LL_PCODE_HOLE_SAME: + default: + selected_hole = MI_HOLE_SAME; + break; + } + mComboHoleType->setCurrentByIndex( selected_hole ); + hole_enabled = enabled; + } + else + { + mComboHoleType->setCurrentByIndex( MI_HOLE_SAME ); + hole_enabled = false; + } + + // Cut interpretation varies based on base object type + F32 cut_begin, cut_end, adv_cut_begin, adv_cut_end; + + if ( selected_item == MI_SPHERE || selected_item == MI_TORUS || + selected_item == MI_TUBE || selected_item == MI_RING ) + { + cut_begin = begin_t; + cut_end = end_t; + adv_cut_begin = begin_s; + adv_cut_end = end_s; + } + else + { + cut_begin = begin_s; + cut_end = end_s; + adv_cut_begin = begin_t; + adv_cut_end = end_t; + } + + mSpinCutBegin ->set( cut_begin ); + mSpinCutEnd ->set( cut_end ); + mCtrlPathBegin ->set( adv_cut_begin ); + mCtrlPathEnd ->set( adv_cut_end ); + calcp->setVar(LLCalc::CUT_BEGIN, cut_begin); + calcp->setVar(LLCalc::CUT_END, cut_end); + calcp->setVar(LLCalc::PATH_BEGIN, adv_cut_begin); + calcp->setVar(LLCalc::PATH_END, adv_cut_end); + + // Twist + F32 twist = volume_params.getTwist(); + F32 twist_begin = volume_params.getTwistBegin(); + // Check the path type for conversion. + if (path == LL_PCODE_PATH_LINE || path == LL_PCODE_PATH_FLEXIBLE) + { + twist *= OBJECT_TWIST_LINEAR_MAX; + twist_begin *= OBJECT_TWIST_LINEAR_MAX; + } + else + { + twist *= OBJECT_TWIST_MAX; + twist_begin *= OBJECT_TWIST_MAX; + } + + mSpinTwist ->set( twist ); + mSpinTwistBegin ->set( twist_begin ); + calcp->setVar(LLCalc::TWIST_END, twist); + calcp->setVar(LLCalc::TWIST_BEGIN, twist_begin); + + // Shear + F32 shear_x = volume_params.getShearX(); + F32 shear_y = volume_params.getShearY(); + mSpinShearX->set( shear_x ); + mSpinShearY->set( shear_y ); + calcp->setVar(LLCalc::X_SHEAR, shear_x); + calcp->setVar(LLCalc::Y_SHEAR, shear_y); + + // Taper + F32 taper_x = volume_params.getTaperX(); + F32 taper_y = volume_params.getTaperY(); + mSpinTaperX->set( taper_x ); + mSpinTaperY->set( taper_y ); + calcp->setVar(LLCalc::X_TAPER, taper_x); + calcp->setVar(LLCalc::Y_TAPER, taper_y); + + // Radius offset. + F32 radius_offset = volume_params.getRadiusOffset(); + // Limit radius offset, based on taper and hole size y. + F32 radius_mag = fabs(radius_offset); + F32 hole_y_mag = fabs(scale_y); + F32 taper_y_mag = fabs(taper_y); + // Check to see if the taper effects us. + if ( (radius_offset > 0.f && taper_y < 0.f) || + (radius_offset < 0.f && taper_y > 0.f) ) + { + // The taper does not help increase the radius offset range. + taper_y_mag = 0.f; + } + F32 max_radius_mag = 1.f - hole_y_mag * (1.f - taper_y_mag) / (1.f - hole_y_mag); + // Enforce the maximum magnitude. + if (radius_mag > max_radius_mag) + { + // Check radius offset sign. + if (radius_offset < 0.f) + { + radius_offset = -max_radius_mag; + } + else + { + radius_offset = max_radius_mag; + } + } + mSpinRadiusOffset->set( radius_offset); + calcp->setVar(LLCalc::RADIUS_OFFSET, radius_offset); + + // Revolutions + F32 revolutions = volume_params.getRevolutions(); + mSpinRevolutions->set( revolutions ); + calcp->setVar(LLCalc::REVOLUTIONS, revolutions); + + // Skew + F32 skew = volume_params.getSkew(); + // Limit skew, based on revolutions hole size x. + F32 skew_mag= fabs(skew); + F32 min_skew_mag = 1.0f - 1.0f / (revolutions * scale_x + 1.0f); + // Discontinuity; A revolution of 1 allows skews below 0.5. + if ( fabs(revolutions - 1.0f) < 0.001) + min_skew_mag = 0.0f; + + // Clip skew. + if (skew_mag < min_skew_mag) + { + // Check skew sign. + if (skew < 0.0f) + { + skew = -min_skew_mag; + } + else + { + skew = min_skew_mag; + } + } + mSpinSkew->set( skew ); + calcp->setVar(LLCalc::SKEW, skew); + } + + // Compute control visibility, label names, and twist range. + // Start with defaults. + bool cut_visible = true; + bool hollow_visible = true; + bool top_size_x_visible = true; + bool top_size_y_visible = true; + bool top_shear_x_visible = true; + bool top_shear_y_visible = true; + bool twist_visible = true; + bool advanced_cut_visible = false; + bool taper_visible = false; + bool skew_visible = false; + bool radius_offset_visible = false; + bool revolutions_visible = false; + bool sculpt_texture_visible = false; + F32 twist_min = OBJECT_TWIST_LINEAR_MIN; + F32 twist_max = OBJECT_TWIST_LINEAR_MAX; + F32 twist_inc = OBJECT_TWIST_LINEAR_INC; + + bool advanced_is_dimple = false; + bool advanced_is_slice = false; + bool size_is_hole = false; + + // Tune based on overall volume type + switch (selected_item) + { + case MI_SPHERE: + top_size_x_visible = false; + top_size_y_visible = false; + top_shear_x_visible = false; + top_shear_y_visible = false; + //twist_visible = false; + advanced_cut_visible = true; + advanced_is_dimple = true; + twist_min = OBJECT_TWIST_MIN; + twist_max = OBJECT_TWIST_MAX; + twist_inc = OBJECT_TWIST_INC; + break; + + case MI_TORUS: + case MI_TUBE: + case MI_RING: + //top_size_x_visible = false; + //top_size_y_visible = false; + size_is_hole = true; + skew_visible = true; + advanced_cut_visible = true; + taper_visible = true; + radius_offset_visible = true; + revolutions_visible = true; + twist_min = OBJECT_TWIST_MIN; + twist_max = OBJECT_TWIST_MAX; + twist_inc = OBJECT_TWIST_INC; + + break; + + case MI_SCULPT: + cut_visible = false; + hollow_visible = false; + twist_visible = false; + top_size_x_visible = false; + top_size_y_visible = false; + top_shear_x_visible = false; + top_shear_y_visible = false; + skew_visible = false; + advanced_cut_visible = false; + taper_visible = false; + radius_offset_visible = false; + revolutions_visible = false; + sculpt_texture_visible = true; + + break; + + case MI_BOX: + advanced_cut_visible = true; + advanced_is_slice = true; + break; + + case MI_CYLINDER: + advanced_cut_visible = true; + advanced_is_slice = true; + break; + + case MI_PRISM: + advanced_cut_visible = true; + advanced_is_slice = true; + break; + + default: + break; + } + + // Check if we need to change top size/hole size params. + switch (selected_item) + { + case MI_SPHERE: + case MI_TORUS: + case MI_TUBE: + case MI_RING: + mSpinScaleX->set( scale_x ); + mSpinScaleY->set( scale_y ); + calcp->setVar(LLCalc::X_HOLE, scale_x); + calcp->setVar(LLCalc::Y_HOLE, scale_y); + mSpinScaleX->setMinValue(OBJECT_MIN_HOLE_SIZE); + mSpinScaleX->setMaxValue(OBJECT_MAX_HOLE_SIZE_X); + mSpinScaleY->setMinValue(OBJECT_MIN_HOLE_SIZE); + mSpinScaleY->setMaxValue(OBJECT_MAX_HOLE_SIZE_Y); + break; + default: + if (editable && single_volume) + { + mSpinScaleX->set( 1.f - scale_x ); + mSpinScaleY->set( 1.f - scale_y ); + mSpinScaleX->setMinValue(-1.f); + mSpinScaleX->setMaxValue(1.f); + mSpinScaleY->setMinValue(-1.f); + mSpinScaleY->setMaxValue(1.f); + + // Torus' Hole Size is Box/Cyl/Prism's Taper + calcp->setVar(LLCalc::X_TAPER, 1.f - scale_x); + calcp->setVar(LLCalc::Y_TAPER, 1.f - scale_y); + + // Box/Cyl/Prism have no hole size + calcp->setVar(LLCalc::X_HOLE, 0.f); + calcp->setVar(LLCalc::Y_HOLE, 0.f); + } + break; + } + + // Check if we need to limit the hollow based on the hole type. + if ( selected_hole == MI_HOLE_SQUARE && + ( selected_item == MI_CYLINDER || selected_item == MI_TORUS || + selected_item == MI_PRISM || selected_item == MI_RING || + selected_item == MI_SPHERE ) ) + { + mSpinHollow->setMinValue(0.f); + mSpinHollow->setMaxValue(70.f); + } + else + { + mSpinHollow->setMinValue(0.f); + mSpinHollow->setMaxValue(95.f); + } + + // Update field enablement + mComboBaseType ->setEnabled( enabled ); + mMenuClipboardParams->setEnabled(enabled); + + mLabelCut ->setEnabled( enabled ); + mSpinCutBegin ->setEnabled( enabled ); + mSpinCutEnd ->setEnabled( enabled ); + + mLabelHollow ->setEnabled( enabled ); + mSpinHollow ->setEnabled( enabled ); + mLabelHoleType ->setEnabled( hole_enabled ); + mComboHoleType ->setEnabled( hole_enabled ); + + mLabelTwist ->setEnabled( enabled ); + mSpinTwist ->setEnabled( enabled ); + mSpinTwistBegin ->setEnabled( enabled ); + + mLabelSkew ->setEnabled( enabled ); + mSpinSkew ->setEnabled( enabled ); + + getChildView("scale_hole")->setVisible( false); + getChildView("scale_taper")->setVisible( false); + if (top_size_x_visible || top_size_y_visible) + { + if (size_is_hole) + { + getChildView("scale_hole")->setVisible( true); + getChildView("scale_hole")->setEnabled(enabled); + } + else + { + getChildView("scale_taper")->setVisible( true); + getChildView("scale_taper")->setEnabled(enabled); + } + } + + mSpinScaleX ->setEnabled( enabled ); + mSpinScaleY ->setEnabled( enabled ); + + mLabelShear ->setEnabled( enabled ); + mSpinShearX ->setEnabled( enabled ); + mSpinShearY ->setEnabled( enabled ); + + getChildView("advanced_cut")->setVisible( false); + getChildView("advanced_dimple")->setVisible( false); + getChildView("advanced_slice")->setVisible( false); + + if (advanced_cut_visible) + { + if (advanced_is_dimple) + { + getChildView("advanced_dimple")->setVisible( true); + getChildView("advanced_dimple")->setEnabled(enabled); + } + + else if (advanced_is_slice) + { + getChildView("advanced_slice")->setVisible( true); + getChildView("advanced_slice")->setEnabled(enabled); + } + else + { + getChildView("advanced_cut")->setVisible( true); + getChildView("advanced_cut")->setEnabled(enabled); + } + } + + mCtrlPathBegin ->setEnabled( enabled ); + mCtrlPathEnd ->setEnabled( enabled ); + + mLabelTaper ->setEnabled( enabled ); + mSpinTaperX ->setEnabled( enabled ); + mSpinTaperY ->setEnabled( enabled ); + + mLabelRadiusOffset->setEnabled( enabled ); + mSpinRadiusOffset ->setEnabled( enabled ); + + mLabelRevolutions->setEnabled( enabled ); + mSpinRevolutions ->setEnabled( enabled ); + + // Update field visibility + mLabelCut ->setVisible( cut_visible ); + mSpinCutBegin ->setVisible( cut_visible ); + mSpinCutEnd ->setVisible( cut_visible ); + + mLabelHollow ->setVisible( hollow_visible ); + mSpinHollow ->setVisible( hollow_visible ); + mLabelHoleType ->setVisible( hollow_visible ); + mComboHoleType ->setVisible( hollow_visible ); + + mLabelTwist ->setVisible( twist_visible ); + mSpinTwist ->setVisible( twist_visible ); + mSpinTwistBegin ->setVisible( twist_visible ); + mSpinTwist ->setMinValue( twist_min ); + mSpinTwist ->setMaxValue( twist_max ); + mSpinTwist ->setIncrement( twist_inc ); + mSpinTwistBegin ->setMinValue( twist_min ); + mSpinTwistBegin ->setMaxValue( twist_max ); + mSpinTwistBegin ->setIncrement( twist_inc ); + + mSpinScaleX ->setVisible( top_size_x_visible ); + mSpinScaleY ->setVisible( top_size_y_visible ); + + mLabelSkew ->setVisible( skew_visible ); + mSpinSkew ->setVisible( skew_visible ); + + mLabelShear ->setVisible( top_shear_x_visible || top_shear_y_visible ); + mSpinShearX ->setVisible( top_shear_x_visible ); + mSpinShearY ->setVisible( top_shear_y_visible ); + + mCtrlPathBegin ->setVisible( advanced_cut_visible ); + mCtrlPathEnd ->setVisible( advanced_cut_visible ); + + mLabelTaper ->setVisible( taper_visible ); + mSpinTaperX ->setVisible( taper_visible ); + mSpinTaperY ->setVisible( taper_visible ); + + mLabelRadiusOffset->setVisible( radius_offset_visible ); + mSpinRadiusOffset ->setVisible( radius_offset_visible ); + + mLabelRevolutions->setVisible( revolutions_visible ); + mSpinRevolutions ->setVisible( revolutions_visible ); + + mCtrlSculptTexture->setVisible(sculpt_texture_visible); + mLabelSculptType->setVisible(sculpt_texture_visible); + mCtrlSculptType->setVisible(sculpt_texture_visible); + + + // sculpt texture + if (selected_item == MI_SCULPT) + { + + + LLUUID id; + LLSculptParams *sculpt_params = (LLSculptParams *)objectp->getParameterEntry(LLNetworkData::PARAMS_SCULPT); + + + if (sculpt_params) // if we have a legal sculpt param block for this object: + { + if (mObject != objectp) // we've just selected a new object, so save for undo + { + mSculptTextureRevert = sculpt_params->getSculptTexture(); + mSculptTypeRevert = sculpt_params->getSculptType(); + } + + U8 sculpt_type = sculpt_params->getSculptType(); + U8 sculpt_stitching = sculpt_type & LL_SCULPT_TYPE_MASK; + bool sculpt_invert = sculpt_type & LL_SCULPT_FLAG_INVERT; + bool sculpt_mirror = sculpt_type & LL_SCULPT_FLAG_MIRROR; + isMesh = (sculpt_stitching == LL_SCULPT_TYPE_MESH); + + LLTextureCtrl* mTextureCtrl = getChild("sculpt texture control"); + if(mTextureCtrl) + { + mTextureCtrl->setTentative(false); + mTextureCtrl->setEnabled(editable && !isMesh); + if (editable) + mTextureCtrl->setImageAssetID(sculpt_params->getSculptTexture()); + else + mTextureCtrl->setImageAssetID(LLUUID::null); + } + + mComboBaseType->setEnabled(!isMesh); + mMenuClipboardParams->setEnabled(!isMesh); + + if (mCtrlSculptType) + { + if (sculpt_stitching == LL_SCULPT_TYPE_NONE) + { + // since 'None' is no longer an option in the combo box + // use 'Plane' as an equivalent sculpt type + mCtrlSculptType->setSelectedByValue(LLSD(LL_SCULPT_TYPE_PLANE), true); + } + else + { + mCtrlSculptType->setSelectedByValue(LLSD(sculpt_stitching), true); + } + mCtrlSculptType->setEnabled(editable && !isMesh); + } + + if (mCtrlSculptMirror) + { + mCtrlSculptMirror->set(sculpt_mirror); + mCtrlSculptMirror->setEnabled(editable && !isMesh); + } + + if (mCtrlSculptInvert) + { + mCtrlSculptInvert->set(sculpt_invert); + mCtrlSculptInvert->setEnabled(editable); + } + + if (mLabelSculptType) + { + mLabelSculptType->setEnabled(true); + } + + } + } + else + { + mSculptTextureRevert = LLUUID::null; + } + + mCtrlSculptMirror->setVisible(sculpt_texture_visible && !isMesh); + mCtrlSculptInvert->setVisible(sculpt_texture_visible && !isMesh); + + //---------------------------------------------------------------------------- + + mObject = objectp; + mRootObject = root_objectp; +} + +// static +bool LLPanelObject::precommitValidate( const LLSD& data ) +{ + // TODO: Richard will fill this in later. + return true; // false means that validation failed and new value should not be commited. +} + +void LLPanelObject::sendIsPhysical() +{ + bool value = mCheckPhysics->get(); + if( mIsPhysical != value ) + { + LLSelectMgr::getInstance()->selectionUpdatePhysics(value); + mIsPhysical = value; + + LL_INFOS("FloaterTools") << "update physics sent" << LL_ENDL; + } + else + { + LL_INFOS("FloaterTools") << "update physics not changed" << LL_ENDL; + } +} + +void LLPanelObject::sendIsTemporary() +{ + bool value = mCheckTemporary->get(); + if( mIsTemporary != value ) + { + LLSelectMgr::getInstance()->selectionUpdateTemporary(value); + mIsTemporary = value; + + LL_INFOS("FloaterTools") << "update temporary sent" << LL_ENDL; + } + else + { + LL_INFOS("FloaterTools") << "update temporary not changed" << LL_ENDL; + } +} + + +void LLPanelObject::sendIsPhantom() +{ + bool value = mCheckPhantom->get(); + if( mIsPhantom != value ) + { + LLSelectMgr::getInstance()->selectionUpdatePhantom(value); + mIsPhantom = value; + + LL_INFOS("FloaterTools") << "update phantom sent" << LL_ENDL; + } + else + { + LL_INFOS("FloaterTools") << "update phantom not changed" << LL_ENDL; + } +} + +// static +void LLPanelObject::onCommitParametric( LLUICtrl* ctrl, void* userdata ) +{ + LLPanelObject* self = (LLPanelObject*) userdata; + + if (self->mObject.isNull()) + { + return; + } + + if (self->mObject->getPCode() != LL_PCODE_VOLUME) + { + // Don't allow modification of non-volume objects. + return; + } + + LLVolume *volume = self->mObject->getVolume(); + if (!volume) + { + return; + } + + LLVolumeParams volume_params; + self->getVolumeParams(volume_params); + + + + // set sculpting + S32 selected_type = self->mComboBaseType->getCurrentIndex(); + + if (selected_type == MI_SCULPT) + { + self->mObject->setParameterEntryInUse(LLNetworkData::PARAMS_SCULPT, true, true); + LLSculptParams *sculpt_params = (LLSculptParams *)self->mObject->getParameterEntry(LLNetworkData::PARAMS_SCULPT); + if (sculpt_params) + volume_params.setSculptID(sculpt_params->getSculptTexture(), sculpt_params->getSculptType()); + } + else + { + LLSculptParams *sculpt_params = (LLSculptParams *)self->mObject->getParameterEntry(LLNetworkData::PARAMS_SCULPT); + if (sculpt_params) + self->mObject->setParameterEntryInUse(LLNetworkData::PARAMS_SCULPT, false, true); + } + + // Update the volume, if necessary. + self->mObject->updateVolume(volume_params); + + + // This was added to make sure thate when changes are made, the UI + // adjusts to present valid options. + // *FIX: only some changes, ie, hollow or primitive type changes, + // require a refresh. + self->refresh(); + +} + +void LLPanelObject::getVolumeParams(LLVolumeParams& volume_params) +{ + // Figure out what type of volume to make + S32 was_selected_type = mSelectedType; + S32 selected_type = mComboBaseType->getCurrentIndex(); + U8 profile; + U8 path; + switch ( selected_type ) + { + case MI_CYLINDER: + profile = LL_PCODE_PROFILE_CIRCLE; + path = LL_PCODE_PATH_LINE; + break; + + case MI_BOX: + profile = LL_PCODE_PROFILE_SQUARE; + path = LL_PCODE_PATH_LINE; + break; + + case MI_PRISM: + profile = LL_PCODE_PROFILE_EQUALTRI; + path = LL_PCODE_PATH_LINE; + break; + + case MI_SPHERE: + profile = LL_PCODE_PROFILE_CIRCLE_HALF; + path = LL_PCODE_PATH_CIRCLE; + break; + + case MI_TORUS: + profile = LL_PCODE_PROFILE_CIRCLE; + path = LL_PCODE_PATH_CIRCLE; + break; + + case MI_TUBE: + profile = LL_PCODE_PROFILE_SQUARE; + path = LL_PCODE_PATH_CIRCLE; + break; + + case MI_RING: + profile = LL_PCODE_PROFILE_EQUALTRI; + path = LL_PCODE_PATH_CIRCLE; + break; + + case MI_SCULPT: + profile = LL_PCODE_PROFILE_CIRCLE; + path = LL_PCODE_PATH_CIRCLE; + break; + + default: + LL_WARNS("FloaterTools") << "Unknown base type " << selected_type + << " in getVolumeParams()" << LL_ENDL; + // assume a box + selected_type = MI_BOX; + profile = LL_PCODE_PROFILE_SQUARE; + path = LL_PCODE_PATH_LINE; + break; + } + + + if (path == LL_PCODE_PATH_LINE) + { + LLVOVolume *volobjp = (LLVOVolume *)(LLViewerObject*)(mObject); + if (volobjp->isFlexible()) + { + path = LL_PCODE_PATH_FLEXIBLE; + } + } + + S32 selected_hole = mComboHoleType->getCurrentIndex(); + U8 hole; + switch (selected_hole) + { + case MI_HOLE_CIRCLE: + hole = LL_PCODE_HOLE_CIRCLE; + break; + case MI_HOLE_SQUARE: + hole = LL_PCODE_HOLE_SQUARE; + break; + case MI_HOLE_TRIANGLE: + hole = LL_PCODE_HOLE_TRIANGLE; + break; + case MI_HOLE_SAME: + default: + hole = LL_PCODE_HOLE_SAME; + break; + } + + volume_params.setType(profile | hole, path); + mSelectedType = selected_type; + + // Compute cut start/end + F32 cut_begin = mSpinCutBegin->get(); + F32 cut_end = mSpinCutEnd->get(); + + // Make sure at least OBJECT_CUT_INC of the object survives + if (cut_begin > cut_end - OBJECT_MIN_CUT_INC) + { + cut_begin = cut_end - OBJECT_MIN_CUT_INC; + mSpinCutBegin->set(cut_begin); + } + + F32 adv_cut_begin = mCtrlPathBegin->get(); + F32 adv_cut_end = mCtrlPathEnd->get(); + + // Make sure at least OBJECT_CUT_INC of the object survives + if (adv_cut_begin > adv_cut_end - OBJECT_MIN_CUT_INC) + { + adv_cut_begin = adv_cut_end - OBJECT_MIN_CUT_INC; + mCtrlPathBegin->set(adv_cut_begin); + } + + F32 begin_s, end_s; + F32 begin_t, end_t; + + if (selected_type == MI_SPHERE || selected_type == MI_TORUS || + selected_type == MI_TUBE || selected_type == MI_RING) + { + begin_s = adv_cut_begin; + end_s = adv_cut_end; + + begin_t = cut_begin; + end_t = cut_end; + } + else + { + begin_s = cut_begin; + end_s = cut_end; + + begin_t = adv_cut_begin; + end_t = adv_cut_end; + } + + volume_params.setBeginAndEndS(begin_s, end_s); + volume_params.setBeginAndEndT(begin_t, end_t); + + // Hollowness + F32 hollow = mSpinHollow->get() / 100.f; + + if ( selected_hole == MI_HOLE_SQUARE && + ( selected_type == MI_CYLINDER || selected_type == MI_TORUS || + selected_type == MI_PRISM || selected_type == MI_RING || + selected_type == MI_SPHERE ) ) + { + if (hollow > 0.7f) hollow = 0.7f; + } + + volume_params.setHollow( hollow ); + + // Twist Begin,End + F32 twist_begin = mSpinTwistBegin->get(); + F32 twist = mSpinTwist->get(); + // Check the path type for twist conversion. + if (path == LL_PCODE_PATH_LINE || path == LL_PCODE_PATH_FLEXIBLE) + { + twist_begin /= OBJECT_TWIST_LINEAR_MAX; + twist /= OBJECT_TWIST_LINEAR_MAX; + } + else + { + twist_begin /= OBJECT_TWIST_MAX; + twist /= OBJECT_TWIST_MAX; + } + + volume_params.setTwistBegin(twist_begin); + volume_params.setTwist(twist); + + // Scale X,Y + F32 scale_x = mSpinScaleX->get(); + F32 scale_y = mSpinScaleY->get(); + if ( was_selected_type == MI_BOX || was_selected_type == MI_CYLINDER || was_selected_type == MI_PRISM) + { + scale_x = 1.f - scale_x; + scale_y = 1.f - scale_y; + } + + // Skew + F32 skew = mSpinSkew->get(); + + // Taper X,Y + F32 taper_x = mSpinTaperX->get(); + F32 taper_y = mSpinTaperY->get(); + + // Radius offset + F32 radius_offset = mSpinRadiusOffset->get(); + + // Revolutions + F32 revolutions = mSpinRevolutions->get(); + + if ( selected_type == MI_SPHERE ) + { + // Snap values to valid sphere parameters. + scale_x = 1.0f; + scale_y = 1.0f; + skew = 0.0f; + taper_x = 0.0f; + taper_y = 0.0f; + radius_offset = 0.0f; + revolutions = 1.0f; + } + else if ( selected_type == MI_TORUS || selected_type == MI_TUBE || + selected_type == MI_RING ) + { + scale_x = llclamp( + scale_x, + OBJECT_MIN_HOLE_SIZE, + OBJECT_MAX_HOLE_SIZE_X); + scale_y = llclamp( + scale_y, + OBJECT_MIN_HOLE_SIZE, + OBJECT_MAX_HOLE_SIZE_Y); + + // Limit radius offset, based on taper and hole size y. + F32 radius_mag = fabs(radius_offset); + F32 hole_y_mag = fabs(scale_y); + F32 taper_y_mag = fabs(taper_y); + // Check to see if the taper effects us. + if ( (radius_offset > 0.f && taper_y < 0.f) || + (radius_offset < 0.f && taper_y > 0.f) ) + { + // The taper does not help increase the radius offset range. + taper_y_mag = 0.f; + } + F32 max_radius_mag = 1.f - hole_y_mag * (1.f - taper_y_mag) / (1.f - hole_y_mag); + // Enforce the maximum magnitude. + if (radius_mag > max_radius_mag) + { + // Check radius offset sign. + if (radius_offset < 0.f) + { + radius_offset = -max_radius_mag; + } + else + { + radius_offset = max_radius_mag; + } + } + + // Check the skew value against the revolutions. + F32 skew_mag= fabs(skew); + F32 min_skew_mag = 1.0f - 1.0f / (revolutions * scale_x + 1.0f); + // Discontinuity; A revolution of 1 allows skews below 0.5. + if ( fabs(revolutions - 1.0f) < 0.001) + min_skew_mag = 0.0f; + + // Clip skew. + if (skew_mag < min_skew_mag) + { + // Check skew sign. + if (skew < 0.0f) + { + skew = -min_skew_mag; + } + else + { + skew = min_skew_mag; + } + } + } + + volume_params.setRatio( scale_x, scale_y ); + volume_params.setSkew(skew); + volume_params.setTaper( taper_x, taper_y ); + volume_params.setRadiusOffset(radius_offset); + volume_params.setRevolutions(revolutions); + + // Shear X,Y + F32 shear_x = mSpinShearX->get(); + F32 shear_y = mSpinShearY->get(); + volume_params.setShear( shear_x, shear_y ); + + if (selected_type == MI_SCULPT) + { + volume_params.setSculptID(LLUUID::null, 0); + volume_params.setBeginAndEndT (0, 1); + volume_params.setBeginAndEndS (0, 1); + volume_params.setHollow (0); + volume_params.setTwistBegin (0); + volume_params.setTwistEnd (0); + volume_params.setRatio (1, 0.5); + volume_params.setShear (0, 0); + volume_params.setTaper (0, 0); + volume_params.setRevolutions (1); + volume_params.setRadiusOffset (0); + volume_params.setSkew (0); + } + +} + +// BUG: Make work with multiple objects +void LLPanelObject::sendRotation(bool btn_down) +{ + if (mObject.isNull()) return; + + LLVector3 new_rot(mCtrlRotX->get(), mCtrlRotY->get(), mCtrlRotZ->get()); + new_rot.mV[VX] = ll_round(new_rot.mV[VX], OBJECT_ROTATION_PRECISION); + new_rot.mV[VY] = ll_round(new_rot.mV[VY], OBJECT_ROTATION_PRECISION); + new_rot.mV[VZ] = ll_round(new_rot.mV[VZ], OBJECT_ROTATION_PRECISION); + + // Note: must compare before conversion to radians + LLVector3 delta = new_rot - mCurEulerDegrees; + + if (delta.magVec() >= 0.0005f) + { + mCurEulerDegrees = new_rot; + new_rot *= DEG_TO_RAD; + + LLQuaternion rotation; + rotation.setQuat(new_rot.mV[VX], new_rot.mV[VY], new_rot.mV[VZ]); + + if (mRootObject != mObject) + { + rotation = rotation * ~mRootObject->getRotationRegion(); + } + + // To include avatars into movements and rotation + // If false, all children are selected anyway - move avatar + // If true, not all children are selected - save positions + bool individual_selection = gSavedSettings.getBOOL("EditLinkedParts"); + std::vector& child_positions = mObject->mUnselectedChildrenPositions ; + std::vector child_rotations; + if (mObject->isRootEdit() && individual_selection) + { + mObject->saveUnselectedChildrenRotation(child_rotations) ; + mObject->saveUnselectedChildrenPosition(child_positions) ; + } + + mObject->setRotation(rotation); + LLManip::rebuild(mObject) ; + + // for individually selected roots, we need to counterrotate all the children + if (mObject->isRootEdit() && individual_selection) + { + mObject->resetChildrenRotationAndPosition(child_rotations, child_positions) ; + } + + if(!btn_down) + { + child_positions.clear() ; + LLSelectMgr::getInstance()->sendMultipleUpdate(UPD_ROTATION | UPD_POSITION); + } + } +} + + +// BUG: Make work with multiple objects +void LLPanelObject::sendScale(bool btn_down) +{ + if (mObject.isNull()) return; + + LLVector3 newscale(mCtrlScaleX->get(), mCtrlScaleY->get(), mCtrlScaleZ->get()); + + LLVector3 delta = newscale - mObject->getScale(); + if (delta.magVec() >= 0.0005f || (mSizeChanged && !btn_down)) + { + // scale changed by more than 1/2 millimeter + mSizeChanged = btn_down; + + // check to see if we aren't scaling the textures + // (in which case the tex coord's need to be recomputed) + bool dont_stretch_textures = !LLManipScale::getStretchTextures(); + if (dont_stretch_textures) + { + LLSelectMgr::getInstance()->saveSelectedObjectTransform(SELECT_ACTION_TYPE_SCALE); + } + + mObject->setScale(newscale, true); + + if(!btn_down) + { + LLSelectMgr::getInstance()->sendMultipleUpdate(UPD_SCALE | UPD_POSITION); + } + + LLSelectMgr::getInstance()->adjustTexturesByScale(true, !dont_stretch_textures); +// LL_INFOS() << "scale sent" << LL_ENDL; + } + else + { +// LL_INFOS() << "scale not changed" << LL_ENDL; + } +} + + +void LLPanelObject::sendPosition(bool btn_down) +{ + if (mObject.isNull()) return; + + LLVector3 newpos(mCtrlPosX->get(), mCtrlPosY->get(), mCtrlPosZ->get()); + LLViewerRegion* regionp = mObject->getRegion(); + + if (!regionp) return; + + if (!mObject->isAttachment()) + { + // Clamp the Z height + const F32 height = newpos.mV[VZ]; + const F32 min_height = LLWorld::getInstance()->getMinAllowedZ(mObject, mObject->getPositionGlobal()); + const F32 max_height = LLWorld::getInstance()->getRegionMaxHeight(); + + if ( height < min_height) + { + newpos.mV[VZ] = min_height; + mCtrlPosZ->set( min_height ); + } + else if ( height > max_height ) + { + newpos.mV[VZ] = max_height; + mCtrlPosZ->set( max_height ); + } + + // Grass is always drawn on the ground, so clamp its position to the ground + if (mObject->getPCode() == LL_PCODE_LEGACY_GRASS) + { + mCtrlPosZ->set(LLWorld::getInstance()->resolveLandHeightAgent(newpos) + 1.f); + } + } + else + { + if (newpos.length() > MAX_ATTACHMENT_DIST) + { + newpos.clampLength(MAX_ATTACHMENT_DIST); + mCtrlPosX->set(newpos.mV[VX]); + mCtrlPosY->set(newpos.mV[VY]); + mCtrlPosZ->set(newpos.mV[VZ]); + } + } + + // Make sure new position is in a valid region, so the object + // won't get dumped by the simulator. + LLVector3d new_pos_global = regionp->getPosGlobalFromRegion(newpos); + bool is_valid_pos = true; + if (mObject->isAttachment()) + { + LLVector3 delta_pos = mObject->getPositionEdit() - newpos; + LLVector3d attachment_pos = regionp->getPosGlobalFromRegion(mObject->getPositionRegion() + delta_pos); + is_valid_pos = LLWorld::getInstance()->positionRegionValidGlobal(attachment_pos); + } + else + { + is_valid_pos = LLWorld::getInstance()->positionRegionValidGlobal(new_pos_global); + } + + if (is_valid_pos) + { + // send only if the position is changed, that is, the delta vector is not zero + LLVector3d old_pos_global = mObject->getPositionGlobal(); + LLVector3d delta = new_pos_global - old_pos_global; + // moved more than 1/2 millimeter + if (delta.magVec() >= 0.0005f) + { + if (mRootObject != mObject) + { + newpos = newpos - mRootObject->getPositionRegion(); + newpos = newpos * ~mRootObject->getRotationRegion(); + mObject->setPositionParent(newpos); + } + else + { + mObject->setPositionEdit(newpos); + } + + LLManip::rebuild(mObject) ; + + // for individually selected roots, we need to counter-translate all unselected children + if (mObject->isRootEdit()) + { + // only offset by parent's translation + mObject->resetChildrenPosition(LLVector3(-delta), true, true) ; + } + + if(!btn_down) + { + LLSelectMgr::getInstance()->sendMultipleUpdate(UPD_POSITION); + } + + LLSelectMgr::getInstance()->updateSelectionCenter(); + } + } + else + { + // move failed, so we update the UI with the correct values + LLVector3 vec = mRootObject->getPositionRegion(); + mCtrlPosX->set(vec.mV[VX]); + mCtrlPosY->set(vec.mV[VY]); + mCtrlPosZ->set(vec.mV[VZ]); + } +} + +void LLPanelObject::sendSculpt() +{ + if (mObject.isNull()) + return; + + LLSculptParams sculpt_params; + LLUUID sculpt_id = LLUUID::null; + + if (mCtrlSculptTexture) + { + sculpt_id = mCtrlSculptTexture->getImageAssetID(); + } + + U8 sculpt_type = 0; + + if (mCtrlSculptType) + { + sculpt_type |= mCtrlSculptType->getValue().asInteger(); + } + + bool enabled = sculpt_type != LL_SCULPT_TYPE_MESH; + + if (mCtrlSculptMirror) + { + mCtrlSculptMirror->setEnabled(enabled); + } + + if (mCtrlSculptInvert) + { + mCtrlSculptInvert->setEnabled(enabled); + } + + if ((mCtrlSculptMirror) && (mCtrlSculptMirror->get())) + { + sculpt_type |= LL_SCULPT_FLAG_MIRROR; + } + + if ((mCtrlSculptInvert) && (mCtrlSculptInvert->get())) + { + sculpt_type |= LL_SCULPT_FLAG_INVERT; + } + + sculpt_params.setSculptTexture(sculpt_id, sculpt_type); + mObject->setParameterEntry(LLNetworkData::PARAMS_SCULPT, sculpt_params, true); +} + +void LLPanelObject::refresh() +{ + getState(); + if (mObject.notNull() && mObject->isDead()) + { + mObject = NULL; + } + + if (mRootObject.notNull() && mRootObject->isDead()) + { + mRootObject = NULL; + } + + F32 max_scale = get_default_max_prim_scale(LLPickInfo::isFlora(mObject)); + + getChild("Scale X")->setMaxValue(max_scale); + getChild("Scale Y")->setMaxValue(max_scale); + getChild("Scale Z")->setMaxValue(max_scale); +} + + +void LLPanelObject::draw() +{ + const LLColor4 white( 1.0f, 1.0f, 1.0f, 1); + const LLColor4 red( 1.0f, 0.25f, 0.f, 1); + const LLColor4 green( 0.f, 1.0f, 0.f, 1); + const LLColor4 blue( 0.f, 0.5f, 1.0f, 1); + + // Tune the colors of the labels + LLTool* tool = LLToolMgr::getInstance()->getCurrentTool(); + + if (tool == LLToolCompTranslate::getInstance()) + { + mCtrlPosX ->setLabelColor(red); + mCtrlPosY ->setLabelColor(green); + mCtrlPosZ ->setLabelColor(blue); + + mCtrlScaleX ->setLabelColor(white); + mCtrlScaleY ->setLabelColor(white); + mCtrlScaleZ ->setLabelColor(white); + + mCtrlRotX ->setLabelColor(white); + mCtrlRotY ->setLabelColor(white); + mCtrlRotZ ->setLabelColor(white); + } + else if ( tool == LLToolCompScale::getInstance() ) + { + mCtrlPosX ->setLabelColor(white); + mCtrlPosY ->setLabelColor(white); + mCtrlPosZ ->setLabelColor(white); + + mCtrlScaleX ->setLabelColor(red); + mCtrlScaleY ->setLabelColor(green); + mCtrlScaleZ ->setLabelColor(blue); + + mCtrlRotX ->setLabelColor(white); + mCtrlRotY ->setLabelColor(white); + mCtrlRotZ ->setLabelColor(white); + } + else if ( tool == LLToolCompRotate::getInstance() ) + { + mCtrlPosX ->setLabelColor(white); + mCtrlPosY ->setLabelColor(white); + mCtrlPosZ ->setLabelColor(white); + + mCtrlScaleX ->setLabelColor(white); + mCtrlScaleY ->setLabelColor(white); + mCtrlScaleZ ->setLabelColor(white); + + mCtrlRotX ->setLabelColor(red); + mCtrlRotY ->setLabelColor(green); + mCtrlRotZ ->setLabelColor(blue); + } + else + { + mCtrlPosX ->setLabelColor(white); + mCtrlPosY ->setLabelColor(white); + mCtrlPosZ ->setLabelColor(white); + + mCtrlScaleX ->setLabelColor(white); + mCtrlScaleY ->setLabelColor(white); + mCtrlScaleZ ->setLabelColor(white); + + mCtrlRotX ->setLabelColor(white); + mCtrlRotY ->setLabelColor(white); + mCtrlRotZ ->setLabelColor(white); + } + + LLPanel::draw(); +} + +// virtual +void LLPanelObject::clearCtrls() +{ + LLPanel::clearCtrls(); + + mCheckLock ->set(false); + mCheckLock ->setEnabled( false ); + mCheckPhysics ->set(false); + mCheckPhysics ->setEnabled( false ); + mCheckTemporary ->set(false); + mCheckTemporary ->setEnabled( false ); + mCheckPhantom ->set(false); + mCheckPhantom ->setEnabled( false ); + + // Disable text labels + mLabelPosition ->setEnabled( false ); + mLabelSize ->setEnabled( false ); + mLabelRotation ->setEnabled( false ); + mLabelCut ->setEnabled( false ); + mLabelHollow ->setEnabled( false ); + mLabelHoleType ->setEnabled( false ); + mLabelTwist ->setEnabled( false ); + mLabelSkew ->setEnabled( false ); + mLabelShear ->setEnabled( false ); + mLabelTaper ->setEnabled( false ); + mLabelRadiusOffset->setEnabled( false ); + mLabelRevolutions->setEnabled( false ); + + getChildView("scale_hole")->setEnabled(false); + getChildView("scale_taper")->setEnabled(false); + getChildView("advanced_cut")->setEnabled(false); + getChildView("advanced_dimple")->setEnabled(false); + getChildView("advanced_slice")->setVisible( false); +} + +// +// Static functions +// + +// static +void LLPanelObject::onCommitLock(LLUICtrl *ctrl, void *data) +{ + // Checkbox will have toggled itself + LLPanelObject *self = (LLPanelObject *)data; + + if(self->mRootObject.isNull()) return; + + bool new_state = self->mCheckLock->get(); + + LLSelectMgr::getInstance()->selectionSetObjectPermissions(PERM_OWNER, !new_state, PERM_MOVE | PERM_MODIFY); +} + +// static +void LLPanelObject::onCommitPosition( LLUICtrl* ctrl, void* userdata ) +{ + LLPanelObject* self = (LLPanelObject*) userdata; + bool btn_down = ((LLSpinCtrl*)ctrl)->isMouseHeldDown() ; + self->sendPosition(btn_down); +} + +// static +void LLPanelObject::onCommitScale( LLUICtrl* ctrl, void* userdata ) +{ + LLPanelObject* self = (LLPanelObject*) userdata; + bool btn_down = ((LLSpinCtrl*)ctrl)->isMouseHeldDown() ; + self->sendScale(btn_down); +} + +// static +void LLPanelObject::onCommitRotation( LLUICtrl* ctrl, void* userdata ) +{ + LLPanelObject* self = (LLPanelObject*) userdata; + bool btn_down = ((LLSpinCtrl*)ctrl)->isMouseHeldDown() ; + self->sendRotation(btn_down); +} + +// static +void LLPanelObject::onCommitPhysics( LLUICtrl* ctrl, void* userdata ) +{ + LLPanelObject* self = (LLPanelObject*) userdata; + self->sendIsPhysical(); +} + +// static +void LLPanelObject::onCommitTemporary( LLUICtrl* ctrl, void* userdata ) +{ + LLPanelObject* self = (LLPanelObject*) userdata; + self->sendIsTemporary(); +} + +// static +void LLPanelObject::onCommitPhantom( LLUICtrl* ctrl, void* userdata ) +{ + LLPanelObject* self = (LLPanelObject*) userdata; + self->sendIsPhantom(); +} + +void LLPanelObject::onSelectSculpt(const LLSD& data) +{ + LLTextureCtrl* mTextureCtrl = getChild("sculpt texture control"); + + if (mTextureCtrl) + { + mSculptTextureRevert = mTextureCtrl->getImageAssetID(); + } + + sendSculpt(); +} + + +void LLPanelObject::onCommitSculpt( const LLSD& data ) +{ + sendSculpt(); +} + +bool LLPanelObject::onDropSculpt(LLInventoryItem* item) +{ + LLTextureCtrl* mTextureCtrl = getChild("sculpt texture control"); + + if (mTextureCtrl) + { + LLUUID asset = item->getAssetUUID(); + + mTextureCtrl->setImageAssetID(asset); + mSculptTextureRevert = asset; + } + + return true; +} + + +void LLPanelObject::onCancelSculpt(const LLSD& data) +{ + LLTextureCtrl* mTextureCtrl = getChild("sculpt texture control"); + if(!mTextureCtrl) + return; + + if(mSculptTextureRevert == LLUUID::null) + { + mSculptTextureRevert = SCULPT_DEFAULT_TEXTURE; + } + mTextureCtrl->setImageAssetID(mSculptTextureRevert); + + sendSculpt(); +} + +// static +void LLPanelObject::onCommitSculptType(LLUICtrl *ctrl, void* userdata) +{ + LLPanelObject* self = (LLPanelObject*) userdata; + + self->sendSculpt(); +} + +void LLPanelObject::menuDoToSelected(const LLSD& userdata) +{ + std::string command = userdata.asString(); + + // paste + if (command == "psr_paste") + { + onPastePos(); + onPasteSize(); + onPasteRot(); + } + else if (command == "pos_paste") + { + onPastePos(); + } + else if (command == "size_paste") + { + onPasteSize(); + } + else if (command == "rot_paste") + { + onPasteRot(); + } + else if (command == "params_paste") + { + onPasteParams(); + } + // copy + else if (command == "psr_copy") + { + onCopyPos(); + onCopySize(); + onCopyRot(); + } + else if (command == "pos_copy") + { + onCopyPos(); + } + else if (command == "size_copy") + { + onCopySize(); + } + else if (command == "rot_copy") + { + onCopyRot(); + } + else if (command == "params_copy") + { + onCopyParams(); + } +} + +bool LLPanelObject::menuEnableItem(const LLSD& userdata) +{ + std::string command = userdata.asString(); + + // paste options + if (command == "psr_paste") + { + S32 selected_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount(); + bool single_volume = (LLSelectMgr::getInstance()->selectionAllPCode(LL_PCODE_VOLUME)) + && (selected_count == 1); + + if (!single_volume) + { + return false; + } + + bool enable_move; + bool enable_modify; + + LLSelectMgr::getInstance()->selectGetEditMoveLinksetPermissions(enable_move, enable_modify); + + return enable_move && enable_modify && mHasClipboardPos && mHasClipboardSize && mHasClipboardRot; + } + else if (command == "pos_paste") + { + // assumes that menu won't be active if there is no move permission + return mHasClipboardPos; + } + else if (command == "size_paste") + { + return mHasClipboardSize; + } + else if (command == "rot_paste") + { + return mHasClipboardRot; + } + else if (command == "params_paste") + { + return mClipboardParams.isMap() && (mClipboardParams.size() != 0); + } + // copy options + else if (command == "psr_copy") + { + S32 selected_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount(); + bool single_volume = (LLSelectMgr::getInstance()->selectionAllPCode(LL_PCODE_VOLUME)) + && (selected_count == 1); + + if (!single_volume) + { + return false; + } + + bool enable_move; + bool enable_modify; + + LLSelectMgr::getInstance()->selectGetEditMoveLinksetPermissions(enable_move, enable_modify); + + // since we forbid seeing values we also should forbid copying them + return enable_move && enable_modify; + } + return false; +} + +void LLPanelObject::onCopyPos() +{ + mClipboardPos = LLVector3(mCtrlPosX->get(), mCtrlPosY->get(), mCtrlPosZ->get()); + + std::string stringVec = llformat("<%g, %g, %g>", mClipboardPos.mV[VX], mClipboardPos.mV[VY], mClipboardPos.mV[VZ]); + LLView::getWindow()->copyTextToClipboard(utf8str_to_wstring(stringVec)); + + mHasClipboardPos = true; +} + +void LLPanelObject::onCopySize() +{ + mClipboardSize = LLVector3(mCtrlScaleX->get(), mCtrlScaleY->get(), mCtrlScaleZ->get()); + + std::string stringVec = llformat("<%g, %g, %g>", mClipboardSize.mV[VX], mClipboardSize.mV[VY], mClipboardSize.mV[VZ]); + LLView::getWindow()->copyTextToClipboard(utf8str_to_wstring(stringVec)); + + mHasClipboardSize = true; +} + +void LLPanelObject::onCopyRot() +{ + mClipboardRot = LLVector3(mCtrlRotX->get(), mCtrlRotY->get(), mCtrlRotZ->get()); + + std::string stringVec = llformat("<%g, %g, %g>", mClipboardRot.mV[VX], mClipboardRot.mV[VY], mClipboardRot.mV[VZ]); + LLView::getWindow()->copyTextToClipboard(utf8str_to_wstring(stringVec)); + + mHasClipboardRot = true; +} + +void LLPanelObject::onPastePos() +{ + if (!mHasClipboardPos) return; + if (mObject.isNull()) return; + + LLViewerRegion* regionp = mObject->getRegion(); + if (!regionp) return; + + + // Clamp pos on non-attachments, just keep the prims within the region + if (!mObject->isAttachment()) + { + F32 max_width = regionp->getWidth(); // meters + mClipboardPos.mV[VX] = llclamp(mClipboardPos.mV[VX], 0.f, max_width); + mClipboardPos.mV[VY] = llclamp(mClipboardPos.mV[VY], 0.f, max_width); + //height will get properly clamped by sendPosition + } + else + { + mClipboardPos.clampLength(MAX_ATTACHMENT_DIST); + } + + mCtrlPosX->set( mClipboardPos.mV[VX] ); + mCtrlPosY->set( mClipboardPos.mV[VY] ); + mCtrlPosZ->set( mClipboardPos.mV[VZ] ); + + sendPosition(false); +} + +void LLPanelObject::onPasteSize() +{ + if (!mHasClipboardSize) return; + + mClipboardSize.mV[VX] = llclamp(mClipboardSize.mV[VX], MIN_PRIM_SCALE, DEFAULT_MAX_PRIM_SCALE); + mClipboardSize.mV[VY] = llclamp(mClipboardSize.mV[VY], MIN_PRIM_SCALE, DEFAULT_MAX_PRIM_SCALE); + mClipboardSize.mV[VZ] = llclamp(mClipboardSize.mV[VZ], MIN_PRIM_SCALE, DEFAULT_MAX_PRIM_SCALE); + + mCtrlScaleX->set(mClipboardSize.mV[VX]); + mCtrlScaleY->set(mClipboardSize.mV[VY]); + mCtrlScaleZ->set(mClipboardSize.mV[VZ]); + + sendScale(false); +} + +void LLPanelObject::onPasteRot() +{ + if (!mHasClipboardRot) return; + + mCtrlRotX->set(mClipboardRot.mV[VX]); + mCtrlRotY->set(mClipboardRot.mV[VY]); + mCtrlRotZ->set(mClipboardRot.mV[VZ]); + + sendRotation(false); +} + +void LLPanelObject::onCopyParams() +{ + LLViewerObject* objectp = mObject; + if (!objectp || objectp->isMesh()) + { + return; + } + + mClipboardParams.clear(); + + // Parametrics + LLVolumeParams params; + getVolumeParams(params); + mClipboardParams["volume_params"] = params.asLLSD(); + + // Sculpted Prim + if (objectp->getParameterEntryInUse(LLNetworkData::PARAMS_SCULPT)) + { + LLSculptParams *sculpt_params = (LLSculptParams *)objectp->getParameterEntry(LLNetworkData::PARAMS_SCULPT); + + LLUUID texture_id = sculpt_params->getSculptTexture(); + if (get_can_copy_texture(texture_id)) + { + LL_DEBUGS("FloaterTools") << "Recording texture" << LL_ENDL; + mClipboardParams["sculpt"]["id"] = texture_id; + } + else + { + mClipboardParams["sculpt"]["id"] = SCULPT_DEFAULT_TEXTURE; + } + + mClipboardParams["sculpt"]["type"] = sculpt_params->getSculptType(); + } +} + +void LLPanelObject::onPasteParams() +{ + LLViewerObject* objectp = mObject; + if (!objectp) + { + return; + } + + // Sculpted Prim + if (mClipboardParams.has("sculpt")) + { + LLSculptParams sculpt_params; + LLUUID sculpt_id = mClipboardParams["sculpt"]["id"].asUUID(); + U8 sculpt_type = (U8)mClipboardParams["sculpt"]["type"].asInteger(); + sculpt_params.setSculptTexture(sculpt_id, sculpt_type); + objectp->setParameterEntry(LLNetworkData::PARAMS_SCULPT, sculpt_params, true); + } + else + { + LLSculptParams *sculpt_params = (LLSculptParams *)objectp->getParameterEntry(LLNetworkData::PARAMS_SCULPT); + if (sculpt_params) + { + objectp->setParameterEntryInUse(LLNetworkData::PARAMS_SCULPT, false, true); + } + } + + // volume params + // make sure updateVolume() won't affect flexible + if (mClipboardParams.has("volume_params")) + { + LLVolumeParams params; + params.fromLLSD(mClipboardParams["volume_params"]); + LLVOVolume *volobjp = (LLVOVolume *)objectp; + if (volobjp->isFlexible()) + { + if (params.getPathParams().getCurveType() == LL_PCODE_PATH_LINE) + { + params.getPathParams().setCurveType(LL_PCODE_PATH_FLEXIBLE); + } + } + else if (params.getPathParams().getCurveType() == LL_PCODE_PATH_FLEXIBLE) + { + params.getPathParams().setCurveType(LL_PCODE_PATH_LINE); + } + + objectp->updateVolume(params); + } +} diff --git a/indra/newview/llpanelobject.h b/indra/newview/llpanelobject.h index 5cbe2b64f7..bbc069b96f 100644 --- a/indra/newview/llpanelobject.h +++ b/indra/newview/llpanelobject.h @@ -1,199 +1,199 @@ -/** - * @file llpanelobject.h - * @brief Object editing (position, scale, etc.) in the tools floater - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELOBJECT_H -#define LL_LLPANELOBJECT_H - -#include "v3math.h" -#include "llpanel.h" -#include "llpointer.h" -#include "llvolume.h" - -class LLSpinCtrl; -class LLCheckBoxCtrl; -class LLTextBox; -class LLUICtrl; -class LLButton; -class LLMenuButton; -class LLViewerObject; -class LLComboBox; -class LLColorSwatchCtrl; -class LLTextureCtrl; -class LLInventoryItem; -class LLUUID; - -class LLPanelObject : public LLPanel -{ -public: - LLPanelObject(); - virtual ~LLPanelObject(); - - virtual bool postBuild(); - virtual void draw(); - virtual void clearCtrls(); - - void refresh(); - - static bool precommitValidate(const LLSD& data); - - static void onCommitLock(LLUICtrl *ctrl, void *data); - static void onCommitPosition( LLUICtrl* ctrl, void* userdata); - static void onCommitScale( LLUICtrl* ctrl, void* userdata); - static void onCommitRotation( LLUICtrl* ctrl, void* userdata); - static void onCommitTemporary( LLUICtrl* ctrl, void* userdata); - static void onCommitPhantom( LLUICtrl* ctrl, void* userdata); - static void onCommitPhysics( LLUICtrl* ctrl, void* userdata); - - void onCopyPos(); - void onPastePos(); - void onCopySize(); - void onPasteSize(); - void onCopyRot(); - void onPasteRot(); - void onCopyParams(); - void onPasteParams(); - static void onCommitParametric(LLUICtrl* ctrl, void* userdata); - - - void onCommitSculpt(const LLSD& data); - void onCancelSculpt(const LLSD& data); - void onSelectSculpt(const LLSD& data); - bool onDropSculpt(LLInventoryItem* item); - static void onCommitSculptType( LLUICtrl *ctrl, void* userdata); - - void menuDoToSelected(const LLSD& userdata); - bool menuEnableItem(const LLSD& userdata); - -protected: - void getState(); - - void sendRotation(bool btn_down); - void sendScale(bool btn_down); - void sendPosition(bool btn_down); - void sendIsPhysical(); - void sendIsTemporary(); - void sendIsPhantom(); - - void sendSculpt(); - - void getVolumeParams(LLVolumeParams& volume_params); - -protected: - // Per-object options - LLComboBox* mComboBaseType; - LLMenuButton* mMenuClipboardParams; - - LLTextBox* mLabelCut; - LLSpinCtrl* mSpinCutBegin; - LLSpinCtrl* mSpinCutEnd; - - LLTextBox* mLabelHollow; - LLSpinCtrl* mSpinHollow; - - LLTextBox* mLabelHoleType; - LLComboBox* mComboHoleType; - - LLTextBox* mLabelTwist; - LLSpinCtrl* mSpinTwist; - LLSpinCtrl* mSpinTwistBegin; - - LLSpinCtrl* mSpinScaleX; - LLSpinCtrl* mSpinScaleY; - - LLTextBox* mLabelSkew; - LLSpinCtrl* mSpinSkew; - - LLTextBox* mLabelShear; - LLSpinCtrl* mSpinShearX; - LLSpinCtrl* mSpinShearY; - - // Advanced Path - LLSpinCtrl* mCtrlPathBegin; - LLSpinCtrl* mCtrlPathEnd; - - LLTextBox* mLabelTaper; - LLSpinCtrl* mSpinTaperX; - LLSpinCtrl* mSpinTaperY; - - LLTextBox* mLabelRadiusOffset; - LLSpinCtrl* mSpinRadiusOffset; - - LLTextBox* mLabelRevolutions; - LLSpinCtrl* mSpinRevolutions; - - LLMenuButton* mMenuClipboardPos; - LLTextBox* mLabelPosition; - LLSpinCtrl* mCtrlPosX; - LLSpinCtrl* mCtrlPosY; - LLSpinCtrl* mCtrlPosZ; - - LLMenuButton* mMenuClipboardSize; - LLTextBox* mLabelSize; - LLSpinCtrl* mCtrlScaleX; - LLSpinCtrl* mCtrlScaleY; - LLSpinCtrl* mCtrlScaleZ; - bool mSizeChanged; - - LLMenuButton* mMenuClipboardRot; - LLTextBox* mLabelRotation; - LLSpinCtrl* mCtrlRotX; - LLSpinCtrl* mCtrlRotY; - LLSpinCtrl* mCtrlRotZ; - - LLCheckBoxCtrl *mCheckLock; - LLCheckBoxCtrl *mCheckPhysics; - LLCheckBoxCtrl *mCheckTemporary; - LLCheckBoxCtrl *mCheckPhantom; - - LLTextureCtrl *mCtrlSculptTexture; - LLTextBox *mLabelSculptType; - LLComboBox *mCtrlSculptType; - LLCheckBoxCtrl *mCtrlSculptMirror; - LLCheckBoxCtrl *mCtrlSculptInvert; - - LLVector3 mCurEulerDegrees; // to avoid sending rotation when not changed - bool mIsPhysical; // to avoid sending "physical" when not changed - bool mIsTemporary; // to avoid sending "temporary" when not changed - bool mIsPhantom; // to avoid sending "phantom" when not changed - S32 mSelectedType; // So we know what selected type we last were - - LLUUID mSculptTextureRevert; // so we can revert the sculpt texture on cancel - U8 mSculptTypeRevert; // so we can revert the sculpt type on cancel - - LLVector3 mClipboardPos; - LLVector3 mClipboardSize; - LLVector3 mClipboardRot; - LLSD mClipboardParams; - - bool mHasClipboardPos; - bool mHasClipboardSize; - bool mHasClipboardRot; - - LLPointer mObject; - LLPointer mRootObject; -}; - -#endif +/** + * @file llpanelobject.h + * @brief Object editing (position, scale, etc.) in the tools floater + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELOBJECT_H +#define LL_LLPANELOBJECT_H + +#include "v3math.h" +#include "llpanel.h" +#include "llpointer.h" +#include "llvolume.h" + +class LLSpinCtrl; +class LLCheckBoxCtrl; +class LLTextBox; +class LLUICtrl; +class LLButton; +class LLMenuButton; +class LLViewerObject; +class LLComboBox; +class LLColorSwatchCtrl; +class LLTextureCtrl; +class LLInventoryItem; +class LLUUID; + +class LLPanelObject : public LLPanel +{ +public: + LLPanelObject(); + virtual ~LLPanelObject(); + + virtual bool postBuild(); + virtual void draw(); + virtual void clearCtrls(); + + void refresh(); + + static bool precommitValidate(const LLSD& data); + + static void onCommitLock(LLUICtrl *ctrl, void *data); + static void onCommitPosition( LLUICtrl* ctrl, void* userdata); + static void onCommitScale( LLUICtrl* ctrl, void* userdata); + static void onCommitRotation( LLUICtrl* ctrl, void* userdata); + static void onCommitTemporary( LLUICtrl* ctrl, void* userdata); + static void onCommitPhantom( LLUICtrl* ctrl, void* userdata); + static void onCommitPhysics( LLUICtrl* ctrl, void* userdata); + + void onCopyPos(); + void onPastePos(); + void onCopySize(); + void onPasteSize(); + void onCopyRot(); + void onPasteRot(); + void onCopyParams(); + void onPasteParams(); + static void onCommitParametric(LLUICtrl* ctrl, void* userdata); + + + void onCommitSculpt(const LLSD& data); + void onCancelSculpt(const LLSD& data); + void onSelectSculpt(const LLSD& data); + bool onDropSculpt(LLInventoryItem* item); + static void onCommitSculptType( LLUICtrl *ctrl, void* userdata); + + void menuDoToSelected(const LLSD& userdata); + bool menuEnableItem(const LLSD& userdata); + +protected: + void getState(); + + void sendRotation(bool btn_down); + void sendScale(bool btn_down); + void sendPosition(bool btn_down); + void sendIsPhysical(); + void sendIsTemporary(); + void sendIsPhantom(); + + void sendSculpt(); + + void getVolumeParams(LLVolumeParams& volume_params); + +protected: + // Per-object options + LLComboBox* mComboBaseType; + LLMenuButton* mMenuClipboardParams; + + LLTextBox* mLabelCut; + LLSpinCtrl* mSpinCutBegin; + LLSpinCtrl* mSpinCutEnd; + + LLTextBox* mLabelHollow; + LLSpinCtrl* mSpinHollow; + + LLTextBox* mLabelHoleType; + LLComboBox* mComboHoleType; + + LLTextBox* mLabelTwist; + LLSpinCtrl* mSpinTwist; + LLSpinCtrl* mSpinTwistBegin; + + LLSpinCtrl* mSpinScaleX; + LLSpinCtrl* mSpinScaleY; + + LLTextBox* mLabelSkew; + LLSpinCtrl* mSpinSkew; + + LLTextBox* mLabelShear; + LLSpinCtrl* mSpinShearX; + LLSpinCtrl* mSpinShearY; + + // Advanced Path + LLSpinCtrl* mCtrlPathBegin; + LLSpinCtrl* mCtrlPathEnd; + + LLTextBox* mLabelTaper; + LLSpinCtrl* mSpinTaperX; + LLSpinCtrl* mSpinTaperY; + + LLTextBox* mLabelRadiusOffset; + LLSpinCtrl* mSpinRadiusOffset; + + LLTextBox* mLabelRevolutions; + LLSpinCtrl* mSpinRevolutions; + + LLMenuButton* mMenuClipboardPos; + LLTextBox* mLabelPosition; + LLSpinCtrl* mCtrlPosX; + LLSpinCtrl* mCtrlPosY; + LLSpinCtrl* mCtrlPosZ; + + LLMenuButton* mMenuClipboardSize; + LLTextBox* mLabelSize; + LLSpinCtrl* mCtrlScaleX; + LLSpinCtrl* mCtrlScaleY; + LLSpinCtrl* mCtrlScaleZ; + bool mSizeChanged; + + LLMenuButton* mMenuClipboardRot; + LLTextBox* mLabelRotation; + LLSpinCtrl* mCtrlRotX; + LLSpinCtrl* mCtrlRotY; + LLSpinCtrl* mCtrlRotZ; + + LLCheckBoxCtrl *mCheckLock; + LLCheckBoxCtrl *mCheckPhysics; + LLCheckBoxCtrl *mCheckTemporary; + LLCheckBoxCtrl *mCheckPhantom; + + LLTextureCtrl *mCtrlSculptTexture; + LLTextBox *mLabelSculptType; + LLComboBox *mCtrlSculptType; + LLCheckBoxCtrl *mCtrlSculptMirror; + LLCheckBoxCtrl *mCtrlSculptInvert; + + LLVector3 mCurEulerDegrees; // to avoid sending rotation when not changed + bool mIsPhysical; // to avoid sending "physical" when not changed + bool mIsTemporary; // to avoid sending "temporary" when not changed + bool mIsPhantom; // to avoid sending "phantom" when not changed + S32 mSelectedType; // So we know what selected type we last were + + LLUUID mSculptTextureRevert; // so we can revert the sculpt texture on cancel + U8 mSculptTypeRevert; // so we can revert the sculpt type on cancel + + LLVector3 mClipboardPos; + LLVector3 mClipboardSize; + LLVector3 mClipboardRot; + LLSD mClipboardParams; + + bool mHasClipboardPos; + bool mHasClipboardSize; + bool mHasClipboardRot; + + LLPointer mObject; + LLPointer mRootObject; +}; + +#endif diff --git a/indra/newview/llpanelobjectinventory.cpp b/indra/newview/llpanelobjectinventory.cpp index 46712020b2..d33ccc0216 100644 --- a/indra/newview/llpanelobjectinventory.cpp +++ b/indra/newview/llpanelobjectinventory.cpp @@ -1,1933 +1,1933 @@ -/** - * @file llsidepanelinventory.cpp - * @brief LLPanelObjectInventory class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -//***************************************************************************** -// -// Implementation of the panel inventory - used to view and control a -// task's inventory. -// -//***************************************************************************** - -#include "llviewerprecompiledheaders.h" - -#include "llpanelobjectinventory.h" - -#include "llmenugl.h" -#include "llnotificationsutil.h" -#include "roles_constants.h" - -#include "llagent.h" -#include "llavataractions.h" -#include "llcallbacklist.h" -#include "llbuycurrencyhtml.h" -#include "llfloaterreg.h" -#include "llfolderview.h" -#include "llinventorybridge.h" -#include "llinventorydefines.h" -#include "llinventoryicon.h" -#include "llinventoryfilter.h" -#include "llinventoryfunctions.h" -#include "llmaterialeditor.h" -#include "llpreviewanim.h" -#include "llpreviewgesture.h" -#include "llpreviewnotecard.h" -#include "llpreviewscript.h" -#include "llpreviewsound.h" -#include "llpreviewtexture.h" -#include "llscrollcontainer.h" -#include "llselectmgr.h" -#include "llstatusbar.h" -#include "lltooldraganddrop.h" -#include "lltrans.h" -#include "llviewerassettype.h" -#include "llviewerinventory.h" -#include "llviewerregion.h" -#include "llviewerobjectlist.h" -#include "llviewermessage.h" - -const LLColor4U DEFAULT_WHITE(255, 255, 255); - -///---------------------------------------------------------------------------- -/// Class LLTaskInvFVBridge -///---------------------------------------------------------------------------- - -class LLTaskInvFVBridge : public LLFolderViewModelItemInventory -{ -protected: - LLUUID mUUID; - std::string mName; - mutable std::string mDisplayName; - mutable std::string mSearchableName; - LLPanelObjectInventory* mPanel; - U32 mFlags; - LLAssetType::EType mAssetType; - LLInventoryType::EType mInventoryType; - - LLInventoryObject* findInvObject() const; - LLInventoryItem* findItem() const; - -public: - LLTaskInvFVBridge(LLPanelObjectInventory* panel, - const LLUUID& uuid, - const std::string& name, - U32 flags=0); - virtual ~LLTaskInvFVBridge() {} - - virtual LLFontGL::StyleFlags getLabelStyle() const { return LLFontGL::NORMAL; } - virtual std::string getLabelSuffix() const { return LLStringUtil::null; } - - static LLTaskInvFVBridge* createObjectBridge(LLPanelObjectInventory* panel, - LLInventoryObject* object); - void showProperties(); - S32 getPrice(); - - // LLFolderViewModelItemInventory functionality - virtual const std::string& getName() const; - virtual const std::string& getDisplayName() const; - virtual const std::string& getSearchableName() const; - - virtual std::string getSearchableDescription() const {return LLStringUtil::null;} - virtual std::string getSearchableCreatorName() const {return LLStringUtil::null;} - virtual std::string getSearchableUUIDString() const {return LLStringUtil::null;} - - - virtual PermissionMask getPermissionMask() const { return PERM_NONE; } - /*virtual*/ LLFolderType::EType getPreferredType() const { return LLFolderType::FT_NONE; } - virtual const LLUUID& getUUID() const { return mUUID; } - virtual const LLUUID& getThumbnailUUID() const { return LLUUID::null;} - virtual time_t getCreationDate() const; - virtual void setCreationDate(time_t creation_date_utc); - - virtual LLUIImagePtr getIcon() const; - virtual void openItem(); - virtual bool canOpenItem() const { return false; } - virtual void closeItem() {} - virtual void selectItem() {} - virtual void navigateToFolder(bool new_window = false, bool change_mode = false) {} - virtual bool isItemRenameable() const; - virtual bool renameItem(const std::string& new_name); - virtual bool isItemMovable() const; - virtual bool isItemRemovable(bool check_worn = true) const; - virtual bool removeItem(); - virtual void removeBatch(std::vector& batch); - virtual void move(LLFolderViewModelItem* parent_listener); - virtual bool isItemCopyable(bool can_copy_as_link = true) const; - virtual bool copyToClipboard() const; - virtual bool cutToClipboard(); - virtual bool isClipboardPasteable() const; - virtual void pasteFromClipboard(); - virtual void pasteLinkFromClipboard(); - virtual void buildContextMenu(LLMenuGL& menu, U32 flags); - virtual void performAction(LLInventoryModel* model, std::string action); - virtual bool isUpToDate() const { return true; } - virtual bool hasChildren() const { return false; } - virtual LLInventoryType::EType getInventoryType() const { return LLInventoryType::IT_NONE; } - virtual LLWearableType::EType getWearableType() const { return LLWearableType::WT_NONE; } - virtual LLSettingsType::type_e getSettingsType() const { return LLSettingsType::ST_NONE; } - virtual EInventorySortGroup getSortGroup() const { return SG_ITEM; } - virtual LLInventoryObject* getInventoryObject() const { return findInvObject(); } - - - // LLDragAndDropBridge functionality - virtual LLToolDragAndDrop::ESource getDragSource() const { return LLToolDragAndDrop::SOURCE_WORLD; } - virtual bool startDrag(EDragAndDropType* type, LLUUID* id) const; - virtual bool dragOrDrop(MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - std::string& tooltip_msg); -}; - -LLTaskInvFVBridge::LLTaskInvFVBridge( - LLPanelObjectInventory* panel, - const LLUUID& uuid, - const std::string& name, - U32 flags) -: LLFolderViewModelItemInventory(panel->getRootViewModel()), - mUUID(uuid), - mName(name), - mPanel(panel), - mFlags(flags), - mAssetType(LLAssetType::AT_NONE), - mInventoryType(LLInventoryType::IT_NONE) -{ - const LLInventoryItem *item = findItem(); - if (item) - { - mAssetType = item->getType(); - mInventoryType = item->getInventoryType(); - } -} - -LLInventoryObject* LLTaskInvFVBridge::findInvObject() const -{ - LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); - if (object) - { - return object->getInventoryObject(mUUID); - } - return NULL; -} - - -LLInventoryItem* LLTaskInvFVBridge::findItem() const -{ - return dynamic_cast(findInvObject()); -} - -void LLTaskInvFVBridge::showProperties() -{ - show_task_item_profile(mUUID, mPanel->getTaskUUID()); -} - -S32 LLTaskInvFVBridge::getPrice() -{ - LLInventoryItem* item = findItem(); - if(item) - { - return item->getSaleInfo().getSalePrice(); - } - else - { - return -1; - } -} - -const std::string& LLTaskInvFVBridge::getName() const -{ - return mName; -} - -const std::string& LLTaskInvFVBridge::getDisplayName() const -{ - LLInventoryItem* item = findItem(); - - if(item) - { - mDisplayName.assign(item->getName()); - - // Localize "New Script", "New Script 1", "New Script 2", etc. - if (item->getType() == LLAssetType::AT_LSL_TEXT && - LLStringUtil::startsWith(item->getName(), "New Script")) - { - LLStringUtil::replaceString(mDisplayName, "New Script", LLTrans::getString("PanelContentsNewScript")); - } - - const LLPermissions& perm(item->getPermissions()); - bool copy = gAgent.allowOperation(PERM_COPY, perm, GP_OBJECT_MANIPULATE); - bool mod = gAgent.allowOperation(PERM_MODIFY, perm, GP_OBJECT_MANIPULATE); - bool xfer = gAgent.allowOperation(PERM_TRANSFER, perm, GP_OBJECT_MANIPULATE); - - if(!copy) - { - mDisplayName.append(LLTrans::getString("no_copy")); - } - if(!mod) - { - mDisplayName.append(LLTrans::getString("no_modify")); - } - if(!xfer) - { - mDisplayName.append(LLTrans::getString("no_transfer")); - } - } - - mSearchableName.assign(mDisplayName + getLabelSuffix()); - - return mDisplayName; -} - -const std::string& LLTaskInvFVBridge::getSearchableName() const -{ - return mSearchableName; -} - - -// BUG: No creation dates for task inventory -time_t LLTaskInvFVBridge::getCreationDate() const -{ - return 0; -} - -void LLTaskInvFVBridge::setCreationDate(time_t creation_date_utc) -{} - - -LLUIImagePtr LLTaskInvFVBridge::getIcon() const -{ - const bool item_is_multi = (mFlags & LLInventoryItemFlags::II_FLAGS_OBJECT_HAS_MULTIPLE_ITEMS); - - return LLInventoryIcon::getIcon(mAssetType, mInventoryType, 0, item_is_multi ); -} - -void LLTaskInvFVBridge::openItem() -{ - // no-op. - LL_DEBUGS() << "LLTaskInvFVBridge::openItem()" << LL_ENDL; -} - -bool LLTaskInvFVBridge::isItemRenameable() const -{ - if(gAgent.isGodlike()) return true; - LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); - if(object) - { - LLInventoryItem* item = (LLInventoryItem*)(object->getInventoryObject(mUUID)); - if(item && gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), - GP_OBJECT_MANIPULATE, GOD_LIKE)) - { - return true; - } - } - return false; -} - -bool LLTaskInvFVBridge::renameItem(const std::string& new_name) -{ - LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); - if(object) - { - LLViewerInventoryItem* item = NULL; - item = (LLViewerInventoryItem*)object->getInventoryObject(mUUID); - if(item && (gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), - GP_OBJECT_MANIPULATE, GOD_LIKE))) - { - LLPointer new_item = new LLViewerInventoryItem(item); - new_item->rename(new_name); - object->updateInventory( - new_item, - TASK_INVENTORY_ITEM_KEY, - false); - } - } - return true; -} - -bool LLTaskInvFVBridge::isItemMovable() const -{ - //LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); - //if(object && (object->permModify() || gAgent.isGodlike())) - //{ - // return true; - //} - //return false; - return true; -} - -bool LLTaskInvFVBridge::isItemRemovable(bool check_worn) const -{ - const LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); - if(object - && (object->permModify() || object->permYouOwner())) - { - return true; - } - return false; -} - -bool remove_task_inventory_callback(const LLSD& notification, const LLSD& response, LLPanelObjectInventory* panel) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - LLViewerObject* object = gObjectList.findObject(notification["payload"]["task_id"].asUUID()); - if(option == 0 && object) - { - // yes - LLSD::array_const_iterator list_end = notification["payload"]["inventory_ids"].endArray(); - for (LLSD::array_const_iterator list_it = notification["payload"]["inventory_ids"].beginArray(); - list_it != list_end; - ++list_it) - { - object->removeInventory(list_it->asUUID()); - } - - // refresh the UI. - panel->refresh(); - } - return false; -} - -// helper for remove -// ! REFACTOR ! two_uuids_list_t is also defined in llinventorybridge.h, but differently. -typedef std::pair > panel_two_uuids_list_t; -typedef std::pair remove_data_t; -bool LLTaskInvFVBridge::removeItem() -{ - if(isItemRemovable() && mPanel) - { - LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); - if(object) - { - if(object->permModify()) - { - // just do it. - object->removeInventory(mUUID); - return true; - } - else - { - LLSD payload; - payload["task_id"] = mPanel->getTaskUUID(); - payload["inventory_ids"].append(mUUID); - LLNotificationsUtil::add("RemoveItemWarn", LLSD(), payload, boost::bind(&remove_task_inventory_callback, _1, _2, mPanel)); - return false; - } - } - } - return false; -} - -void LLTaskInvFVBridge::removeBatch(std::vector& batch) -{ - if (!mPanel) - { - return; - } - - LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); - if (!object) - { - return; - } - - if (!object->permModify()) - { - LLSD payload; - payload["task_id"] = mPanel->getTaskUUID(); - for (S32 i = 0; i < (S32)batch.size(); i++) - { - LLTaskInvFVBridge* itemp = (LLTaskInvFVBridge*)batch[i]; - payload["inventory_ids"].append(itemp->getUUID()); - } - LLNotificationsUtil::add("RemoveItemWarn", LLSD(), payload, boost::bind(&remove_task_inventory_callback, _1, _2, mPanel)); - - } - else - { - for (S32 i = 0; i < (S32)batch.size(); i++) - { - LLTaskInvFVBridge* itemp = (LLTaskInvFVBridge*)batch[i]; - - if(itemp->isItemRemovable()) - { - // just do it. - object->removeInventory(itemp->getUUID()); - } - } - } -} - -void LLTaskInvFVBridge::move(LLFolderViewModelItem* parent_listener) -{ -} - -bool LLTaskInvFVBridge::isItemCopyable(bool can_link) const -{ - LLInventoryItem* item = findItem(); - if(!item) return false; - return gAgent.allowOperation(PERM_COPY, item->getPermissions(), - GP_OBJECT_MANIPULATE); -} - -bool LLTaskInvFVBridge::copyToClipboard() const -{ - return false; -} - -bool LLTaskInvFVBridge::cutToClipboard() -{ - return false; -} - -bool LLTaskInvFVBridge::isClipboardPasteable() const -{ - return false; -} - -void LLTaskInvFVBridge::pasteFromClipboard() -{ -} - -void LLTaskInvFVBridge::pasteLinkFromClipboard() -{ -} - -bool LLTaskInvFVBridge::startDrag(EDragAndDropType* type, LLUUID* id) const -{ - //LL_INFOS() << "LLTaskInvFVBridge::startDrag()" << LL_ENDL; - if(mPanel) - { - LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); - if(object) - { - LLInventoryItem* inv = NULL; - if((inv = (LLInventoryItem*)object->getInventoryObject(mUUID))) - { - const LLPermissions& perm = inv->getPermissions(); - bool can_copy = gAgent.allowOperation(PERM_COPY, perm, - GP_OBJECT_MANIPULATE); - if (object->isAttachment() && !can_copy) - { - //RN: no copy contents of attachments cannot be dragged out - // due to a race condition and possible exploit where - // attached objects do not update their inventory items - // when their contents are manipulated - return false; - } - if((can_copy && perm.allowTransferTo(gAgent.getID())) - || object->permYouOwner()) -// || gAgent.isGodlike()) - - { - *type = LLViewerAssetType::lookupDragAndDropType(inv->getType()); - - *id = inv->getUUID(); - return true; - } - } - } - } - return false; -} - -bool LLTaskInvFVBridge::dragOrDrop(MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - std::string& tooltip_msg) -{ - //LL_INFOS() << "LLTaskInvFVBridge::dragOrDrop()" << LL_ENDL; - return false; -} - -// virtual -void LLTaskInvFVBridge::performAction(LLInventoryModel* model, std::string action) -{ - if (action == "task_open") - { - openItem(); - } - else if (action == "task_properties") - { - showProperties(); - } -} - -void LLTaskInvFVBridge::buildContextMenu(LLMenuGL& menu, U32 flags) -{ - LLInventoryItem* item = findItem(); - std::vector items; - std::vector disabled_items; - - if (!item) - { - hide_context_entries(menu, items, disabled_items); - return; - } - - if (canOpenItem()) - { - items.push_back(std::string("Task Open")); - } - items.push_back(std::string("Task Properties")); - if ((flags & FIRST_SELECTED_ITEM) == 0) - { - disabled_items.push_back(std::string("Task Properties")); - } - if(isItemRenameable()) - { - items.push_back(std::string("Task Rename")); - if ((flags & FIRST_SELECTED_ITEM) == 0) - { - disabled_items.push_back(std::string("Task Rename")); - } - } - if(isItemRemovable()) - { - items.push_back(std::string("Task Remove")); - } - - hide_context_entries(menu, items, disabled_items); -} - - -///---------------------------------------------------------------------------- -/// Class LLTaskFolderBridge -///---------------------------------------------------------------------------- - -class LLTaskCategoryBridge : public LLTaskInvFVBridge -{ -public: - LLTaskCategoryBridge( - LLPanelObjectInventory* panel, - const LLUUID& uuid, - const std::string& name); - - virtual LLUIImagePtr getIcon() const; - virtual const std::string& getDisplayName() const; - virtual bool isItemRenameable() const; - // virtual bool isItemCopyable() const { return false; } - virtual bool renameItem(const std::string& new_name); - virtual bool isItemRemovable(bool check_worn = true) const; - virtual void buildContextMenu(LLMenuGL& menu, U32 flags); - virtual bool hasChildren() const; - virtual bool startDrag(EDragAndDropType* type, LLUUID* id) const; - virtual bool dragOrDrop(MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - std::string& tooltip_msg); - virtual bool canOpenItem() const { return true; } - virtual void openItem(); - virtual EInventorySortGroup getSortGroup() const { return SG_NORMAL_FOLDER; } -}; - -LLTaskCategoryBridge::LLTaskCategoryBridge( - LLPanelObjectInventory* panel, - const LLUUID& uuid, - const std::string& name) : - LLTaskInvFVBridge(panel, uuid, name) -{ -} - -LLUIImagePtr LLTaskCategoryBridge::getIcon() const -{ - return LLUI::getUIImage("Inv_FolderClosed"); -} - -// virtual -const std::string& LLTaskCategoryBridge::getDisplayName() const -{ - LLInventoryObject* cat = findInvObject(); - - if (cat) - { - std::string name = cat->getName(); - if (mChildren.size() > 0) - { - // Add item count - // Normally we would be using getLabelSuffix for this - // but object's inventory just uses displaynames - LLStringUtil::format_map_t args; - args["[ITEMS_COUNT]"] = llformat("%d", mChildren.size()); - - name.append(" " + LLTrans::getString("InventoryItemsCount", args)); - } - mDisplayName.assign(name); - } - - return mDisplayName; -} - -bool LLTaskCategoryBridge::isItemRenameable() const -{ - return false; -} - -bool LLTaskCategoryBridge::renameItem(const std::string& new_name) -{ - return false; -} - -bool LLTaskCategoryBridge::isItemRemovable(bool check_worn) const -{ - return false; -} - -void LLTaskCategoryBridge::buildContextMenu(LLMenuGL& menu, U32 flags) -{ - std::vector items; - std::vector disabled_items; - hide_context_entries(menu, items, disabled_items); -} - -bool LLTaskCategoryBridge::hasChildren() const -{ - // return true if we have or do know know if we have children. - // *FIX: For now, return false - we will know for sure soon enough. - return false; -} - -void LLTaskCategoryBridge::openItem() -{ -} - -bool LLTaskCategoryBridge::startDrag(EDragAndDropType* type, LLUUID* id) const -{ - //LL_INFOS() << "LLTaskInvFVBridge::startDrag()" << LL_ENDL; - if(mPanel && mUUID.notNull()) - { - LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); - if(object) - { - const LLInventoryObject* cat = object->getInventoryObject(mUUID); - if ( (cat) && (move_inv_category_world_to_agent(mUUID, LLUUID::null, false)) ) - { - *type = LLViewerAssetType::lookupDragAndDropType(cat->getType()); - *id = mUUID; - return true; - } - } - } - return false; -} - -bool LLTaskCategoryBridge::dragOrDrop(MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - std::string& tooltip_msg) -{ - //LL_INFOS() << "LLTaskCategoryBridge::dragOrDrop()" << LL_ENDL; - bool accept = false; - LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); - if(object) - { - switch(cargo_type) - { - case DAD_CATEGORY: - accept = LLToolDragAndDrop::getInstance()->dadUpdateInventoryCategory(object,drop); - break; - case DAD_TEXTURE: - case DAD_SOUND: - case DAD_LANDMARK: - case DAD_OBJECT: - case DAD_NOTECARD: - case DAD_CLOTHING: - case DAD_BODYPART: - case DAD_ANIMATION: - case DAD_GESTURE: - case DAD_CALLINGCARD: - case DAD_MESH: - case DAD_SETTINGS: - case DAD_MATERIAL: - accept = LLToolDragAndDrop::isInventoryDropAcceptable(object, (LLViewerInventoryItem*)cargo_data); - if(accept && drop) - { - LLToolDragAndDrop::dropInventory(object, - (LLViewerInventoryItem*)cargo_data, - LLToolDragAndDrop::getInstance()->getSource(), - LLToolDragAndDrop::getInstance()->getSourceID()); - } - break; - case DAD_SCRIPT: - // *HACK: In order to resolve SL-22177, we need to block - // drags from notecards and objects onto other - // objects. uncomment the simpler version when we have - // that right. - //accept = LLToolDragAndDrop::isInventoryDropAcceptable(object, (LLViewerInventoryItem*)cargo_data); - if(LLToolDragAndDrop::isInventoryDropAcceptable( - object, (LLViewerInventoryItem*)cargo_data) - && (LLToolDragAndDrop::SOURCE_WORLD != LLToolDragAndDrop::getInstance()->getSource()) - && (LLToolDragAndDrop::SOURCE_NOTECARD != LLToolDragAndDrop::getInstance()->getSource())) - { - accept = true; - } - if(accept && drop) - { - LLViewerInventoryItem* item = (LLViewerInventoryItem*)cargo_data; - // rez in the script active by default, rez in - // inactive if the control key is being held down. - bool active = ((mask & MASK_CONTROL) == 0); - LLToolDragAndDrop::dropScript(object, item, active, - LLToolDragAndDrop::getInstance()->getSource(), - LLToolDragAndDrop::getInstance()->getSourceID()); - } - break; - default: - break; - } - } - return accept; -} - -///---------------------------------------------------------------------------- -/// Class LLTaskTextureBridge -///---------------------------------------------------------------------------- - -class LLTaskTextureBridge : public LLTaskInvFVBridge -{ -public: - LLTaskTextureBridge(LLPanelObjectInventory* panel, - const LLUUID& uuid, - const std::string& name) : - LLTaskInvFVBridge(panel, uuid, name) {} - - virtual bool canOpenItem() const { return true; } - virtual void openItem(); -}; - -void LLTaskTextureBridge::openItem() -{ - LL_INFOS() << "LLTaskTextureBridge::openItem()" << LL_ENDL; - LLPreviewTexture* preview = LLFloaterReg::showTypedInstance("preview_texture", LLSD(mUUID), TAKE_FOCUS_YES); - if(preview) - { - LLInventoryItem* item = findItem(); - if(item) - { - preview->setAuxItem(item); - } - preview->setObjectID(mPanel->getTaskUUID()); - } -} - - -///---------------------------------------------------------------------------- -/// Class LLTaskSoundBridge -///---------------------------------------------------------------------------- - -class LLTaskSoundBridge : public LLTaskInvFVBridge -{ -public: - LLTaskSoundBridge(LLPanelObjectInventory* panel, - const LLUUID& uuid, - const std::string& name) : - LLTaskInvFVBridge(panel, uuid, name) {} - - virtual bool canOpenItem() const { return true; } - virtual void openItem(); - virtual void performAction(LLInventoryModel* model, std::string action); - virtual void buildContextMenu(LLMenuGL& menu, U32 flags); - static void openSoundPreview(void* data); -}; - -void LLTaskSoundBridge::openItem() -{ - openSoundPreview((void*)this); -} - -void LLTaskSoundBridge::openSoundPreview(void* data) -{ - LLTaskSoundBridge* self = (LLTaskSoundBridge*)data; - if(!self) - return; - - LLPreviewSound* preview = LLFloaterReg::showTypedInstance("preview_sound", LLSD(self->mUUID), TAKE_FOCUS_YES); - if (preview) - { - preview->setObjectID(self->mPanel->getTaskUUID()); - } -} - -// virtual -void LLTaskSoundBridge::performAction(LLInventoryModel* model, std::string action) -{ - if (action == "task_play") - { - LLInventoryItem* item = findItem(); - if(item) - { - send_sound_trigger(item->getAssetUUID(), 1.0); - } - } - LLTaskInvFVBridge::performAction(model, action); -} - -void LLTaskSoundBridge::buildContextMenu(LLMenuGL& menu, U32 flags) -{ - LLInventoryItem* item = findItem(); - std::vector items; - std::vector disabled_items; - if (!item) - { - hide_context_entries(menu, items, disabled_items); - return; - } - - if (canOpenItem()) - { - if (!isItemCopyable()) - { - disabled_items.push_back(std::string("Task Open")); - } - } - items.push_back(std::string("Task Properties")); - if ((flags & FIRST_SELECTED_ITEM) == 0) - { - disabled_items.push_back(std::string("Task Properties")); - } - if(isItemRenameable()) - { - items.push_back(std::string("Task Rename")); - } - if(isItemRemovable()) - { - items.push_back(std::string("Task Remove")); - } - - items.push_back(std::string("Task Play")); - - - hide_context_entries(menu, items, disabled_items); -} - -///---------------------------------------------------------------------------- -/// Class LLTaskLandmarkBridge -///---------------------------------------------------------------------------- - -class LLTaskLandmarkBridge : public LLTaskInvFVBridge -{ -public: - LLTaskLandmarkBridge(LLPanelObjectInventory* panel, - const LLUUID& uuid, - const std::string& name) : - LLTaskInvFVBridge(panel, uuid, name) {} -}; - -///---------------------------------------------------------------------------- -/// Class LLTaskCallingCardBridge -///---------------------------------------------------------------------------- - -class LLTaskCallingCardBridge : public LLTaskInvFVBridge -{ -public: - LLTaskCallingCardBridge(LLPanelObjectInventory* panel, - const LLUUID& uuid, - const std::string& name) : - LLTaskInvFVBridge(panel, uuid, name) {} - - virtual bool isItemRenameable() const; - virtual bool renameItem(const std::string& new_name); -}; - -bool LLTaskCallingCardBridge::isItemRenameable() const -{ - return false; -} - -bool LLTaskCallingCardBridge::renameItem(const std::string& new_name) -{ - return false; -} - - -///---------------------------------------------------------------------------- -/// Class LLTaskScriptBridge -///---------------------------------------------------------------------------- - -class LLTaskScriptBridge : public LLTaskInvFVBridge -{ -public: - LLTaskScriptBridge(LLPanelObjectInventory* panel, - const LLUUID& uuid, - const std::string& name) : - LLTaskInvFVBridge(panel, uuid, name) {} - - //static bool enableIfCopyable( void* userdata ); -}; - -class LLTaskLSLBridge : public LLTaskScriptBridge -{ -public: - LLTaskLSLBridge(LLPanelObjectInventory* panel, - const LLUUID& uuid, - const std::string& name) : - LLTaskScriptBridge(panel, uuid, name) {} - - virtual bool canOpenItem() const { return true; } - virtual void openItem(); - virtual bool removeItem(); - //virtual void buildContextMenu(LLMenuGL& menu); - - //static void copyToInventory(void* userdata); -}; - -void LLTaskLSLBridge::openItem() -{ - LL_INFOS() << "LLTaskLSLBridge::openItem() " << mUUID << LL_ENDL; - LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); - if(!object || object->isInventoryPending()) - { - return; - } - if (object->permModify() || gAgent.isGodlike()) - { - LLSD floater_key; - floater_key["taskid"] = mPanel->getTaskUUID(); - floater_key["itemid"] = mUUID; - - LLLiveLSLEditor* preview = LLFloaterReg::showTypedInstance("preview_scriptedit", floater_key, TAKE_FOCUS_YES); - if (preview) - { - LLSelectNode *node = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(NULL, true); - if (node && node->mValid) - { - preview->setObjectName(node->mName); - } - preview->setObjectID(mPanel->getTaskUUID()); - } - } - else - { - LLNotificationsUtil::add("CannotOpenScriptObjectNoMod"); - } -} - -bool LLTaskLSLBridge::removeItem() -{ - LLFloaterReg::hideInstance("preview_scriptedit", LLSD(mUUID)); - return LLTaskInvFVBridge::removeItem(); -} - -///---------------------------------------------------------------------------- -/// Class LLTaskObjectBridge -///---------------------------------------------------------------------------- - -class LLTaskObjectBridge : public LLTaskInvFVBridge -{ -public: - LLTaskObjectBridge(LLPanelObjectInventory* panel, - const LLUUID& uuid, - const std::string& name, - U32 flags = 0) : - LLTaskInvFVBridge(panel, uuid, name, flags) {} -}; - -///---------------------------------------------------------------------------- -/// Class LLTaskNotecardBridge -///---------------------------------------------------------------------------- - -class LLTaskNotecardBridge : public LLTaskInvFVBridge -{ -public: - LLTaskNotecardBridge(LLPanelObjectInventory* panel, - const LLUUID& uuid, - const std::string& name) : - LLTaskInvFVBridge(panel, uuid, name) {} - - virtual bool canOpenItem() const { return true; } - virtual void openItem(); - virtual bool removeItem(); -}; - -void LLTaskNotecardBridge::openItem() -{ - LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); - if(!object || object->isInventoryPending()) - { - return; - } - - // Note: even if we are not allowed to modify copyable notecard, we should be able to view it - LLInventoryItem *item = dynamic_cast(object->getInventoryObject(mUUID)); - bool item_copy = item && gAgent.allowOperation(PERM_COPY, item->getPermissions(), GP_OBJECT_MANIPULATE); - if( item_copy - || object->permModify() - || gAgent.isGodlike()) - { - LLSD floater_key; - floater_key["taskid"] = mPanel->getTaskUUID(); - floater_key["itemid"] = mUUID; - LLPreviewNotecard* preview = LLFloaterReg::showTypedInstance("preview_notecard", floater_key, TAKE_FOCUS_YES); - if (preview) - { - preview->setObjectID(mPanel->getTaskUUID()); - } - } -} - -bool LLTaskNotecardBridge::removeItem() -{ - LLFloaterReg::hideInstance("preview_notecard", LLSD(mUUID)); - return LLTaskInvFVBridge::removeItem(); -} - -///---------------------------------------------------------------------------- -/// Class LLTaskGestureBridge -///---------------------------------------------------------------------------- - -class LLTaskGestureBridge : public LLTaskInvFVBridge -{ -public: - LLTaskGestureBridge(LLPanelObjectInventory* panel, - const LLUUID& uuid, - const std::string& name) : - LLTaskInvFVBridge(panel, uuid, name) {} - - virtual bool canOpenItem() const { return true; } - virtual void openItem(); - virtual bool removeItem(); -}; - -void LLTaskGestureBridge::openItem() -{ - LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); - if(!object || object->isInventoryPending()) - { - return; - } - LLPreviewGesture::show(mUUID, mPanel->getTaskUUID()); -} - -bool LLTaskGestureBridge::removeItem() -{ - // Don't need to deactivate gesture because gestures inside objects can never be active. - LLFloaterReg::hideInstance("preview_gesture", LLSD(mUUID)); - return LLTaskInvFVBridge::removeItem(); -} - -///---------------------------------------------------------------------------- -/// Class LLTaskAnimationBridge -///---------------------------------------------------------------------------- - -class LLTaskAnimationBridge : public LLTaskInvFVBridge -{ -public: - LLTaskAnimationBridge(LLPanelObjectInventory* panel, - const LLUUID& uuid, - const std::string& name) : - LLTaskInvFVBridge(panel, uuid, name) {} - - virtual bool canOpenItem() const { return true; } - virtual void openItem(); - virtual bool removeItem(); -}; - -void LLTaskAnimationBridge::openItem() -{ - LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); - if(!object || object->isInventoryPending()) - { - return; - } - - LLPreviewAnim* preview = LLFloaterReg::showTypedInstance("preview_anim", LLSD(mUUID), TAKE_FOCUS_YES); - if (preview && (object->permModify() || gAgent.isGodlike())) - { - preview->setObjectID(mPanel->getTaskUUID()); - } -} - -bool LLTaskAnimationBridge::removeItem() -{ - LLFloaterReg::hideInstance("preview_anim", LLSD(mUUID)); - return LLTaskInvFVBridge::removeItem(); -} - -///---------------------------------------------------------------------------- -/// Class LLTaskWearableBridge -///---------------------------------------------------------------------------- - -class LLTaskWearableBridge : public LLTaskInvFVBridge -{ -public: - LLTaskWearableBridge(LLPanelObjectInventory* panel, - const LLUUID& uuid, - const std::string& name, - U32 flags) : - LLTaskInvFVBridge(panel, uuid, name, flags) {} - - virtual LLUIImagePtr getIcon() const; -}; - -LLUIImagePtr LLTaskWearableBridge::getIcon() const -{ - return LLInventoryIcon::getIcon(mAssetType, mInventoryType, mFlags, false ); -} - -///---------------------------------------------------------------------------- -/// Class LLTaskSettingsBridge -///---------------------------------------------------------------------------- - -class LLTaskSettingsBridge : public LLTaskInvFVBridge -{ -public: - LLTaskSettingsBridge(LLPanelObjectInventory* panel, - const LLUUID& uuid, - const std::string& name, - U32 flags) : - LLTaskInvFVBridge(panel, uuid, name, flags) {} - - virtual LLUIImagePtr getIcon() const; - virtual LLSettingsType::type_e getSettingsType() const; -}; - -LLUIImagePtr LLTaskSettingsBridge::getIcon() const -{ - return LLInventoryIcon::getIcon(mAssetType, mInventoryType, mFlags, false); -} - -LLSettingsType::type_e LLTaskSettingsBridge::getSettingsType() const -{ - return LLSettingsType::ST_NONE; -} - -///---------------------------------------------------------------------------- -/// Class LLTaskMaterialBridge -///---------------------------------------------------------------------------- - -class LLTaskMaterialBridge : public LLTaskInvFVBridge -{ -public: - LLTaskMaterialBridge(LLPanelObjectInventory* panel, - const LLUUID& uuid, - const std::string& name) : - LLTaskInvFVBridge(panel, uuid, name) {} - - bool canOpenItem() const override { return true; } - void openItem() override; - bool removeItem() override; -}; - -void LLTaskMaterialBridge::openItem() -{ - LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); - if(!object || object->isInventoryPending()) - { - return; - } - - // Note: even if we are not allowed to modify copyable notecard, we should be able to view it - LLInventoryItem *item = dynamic_cast(object->getInventoryObject(mUUID)); - bool item_copy = item && gAgent.allowOperation(PERM_COPY, item->getPermissions(), GP_OBJECT_MANIPULATE); - if( item_copy - || object->permModify() - || gAgent.isGodlike()) - { - LLSD floater_key; - floater_key["taskid"] = mPanel->getTaskUUID(); - floater_key["itemid"] = mUUID; - LLMaterialEditor* mat = LLFloaterReg::getTypedInstance("material_editor", floater_key); - if (mat) - { - mat->setObjectID(mPanel->getTaskUUID()); - mat->openFloater(floater_key); - mat->setFocus(true); - } - } -} - -bool LLTaskMaterialBridge::removeItem() -{ - LLFloaterReg::hideInstance("material_editor", LLSD(mUUID)); - return LLTaskInvFVBridge::removeItem(); -} - - -///---------------------------------------------------------------------------- -/// LLTaskInvFVBridge impl -//---------------------------------------------------------------------------- - -LLTaskInvFVBridge* LLTaskInvFVBridge::createObjectBridge(LLPanelObjectInventory* panel, - LLInventoryObject* object) -{ - LLTaskInvFVBridge* new_bridge = NULL; - const LLInventoryItem* item = dynamic_cast(object); - const U32 itemflags = ( NULL == item ? 0 : item->getFlags() ); - LLAssetType::EType type = object ? object->getType() : LLAssetType::AT_CATEGORY; - LLUUID object_id = object ? object->getUUID() : LLUUID::null; - std::string object_name = object ? object->getName() : std::string(); - - switch(type) - { - case LLAssetType::AT_TEXTURE: - new_bridge = new LLTaskTextureBridge(panel, - object_id, - object_name); - break; - case LLAssetType::AT_SOUND: - new_bridge = new LLTaskSoundBridge(panel, - object_id, - object_name); - break; - case LLAssetType::AT_LANDMARK: - new_bridge = new LLTaskLandmarkBridge(panel, - object_id, - object_name); - break; - case LLAssetType::AT_CALLINGCARD: - new_bridge = new LLTaskCallingCardBridge(panel, - object_id, - object_name); - break; - case LLAssetType::AT_SCRIPT: - // OLD SCRIPTS DEPRECATED - JC - LL_WARNS() << "Old script" << LL_ENDL; - //new_bridge = new LLTaskOldScriptBridge(panel, - // object_id, - // object_name); - break; - case LLAssetType::AT_OBJECT: - new_bridge = new LLTaskObjectBridge(panel, - object_id, - object_name, - itemflags); - break; - case LLAssetType::AT_NOTECARD: - new_bridge = new LLTaskNotecardBridge(panel, - object_id, - object_name); - break; - case LLAssetType::AT_ANIMATION: - new_bridge = new LLTaskAnimationBridge(panel, - object_id, - object_name); - break; - case LLAssetType::AT_GESTURE: - new_bridge = new LLTaskGestureBridge(panel, - object_id, - object_name); - break; - case LLAssetType::AT_CLOTHING: - case LLAssetType::AT_BODYPART: - new_bridge = new LLTaskWearableBridge(panel, - object_id, - object_name, - itemflags); - break; - case LLAssetType::AT_CATEGORY: - new_bridge = new LLTaskCategoryBridge(panel, - object_id, - object_name); - break; - case LLAssetType::AT_LSL_TEXT: - new_bridge = new LLTaskLSLBridge(panel, - object_id, - object_name); - break; - case LLAssetType::AT_SETTINGS: - new_bridge = new LLTaskSettingsBridge(panel, - object_id, - object_name, - itemflags); - break; - case LLAssetType::AT_MATERIAL: - new_bridge = new LLTaskMaterialBridge(panel, - object_id, - object_name); - break; - default: - LL_INFOS() << "Unhandled inventory type (llassetstorage.h): " - << (S32)type << LL_ENDL; - break; - } - return new_bridge; -} - - -///---------------------------------------------------------------------------- -/// Class LLPanelObjectInventory -///---------------------------------------------------------------------------- - -static LLDefaultChildRegistry::Register r("panel_inventory_object"); - -void do_nothing() -{ -} - -// Default constructor -LLPanelObjectInventory::LLPanelObjectInventory(const LLPanelObjectInventory::Params& p) : - LLPanel(p), - mScroller(NULL), - mFolders(NULL), - mHaveInventory(false), - mIsInventoryEmpty(true), - mInventoryNeedsUpdate(false), - mInventoryViewModel(p.name), - mShowRootFolder(p.show_root_folder) -{ - // Setup context menu callbacks - mCommitCallbackRegistrar.add("Inventory.DoToSelected", boost::bind(&LLPanelObjectInventory::doToSelected, this, _2)); - mCommitCallbackRegistrar.add("Inventory.EmptyTrash", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyTrash", LLFolderType::FT_TRASH)); - mCommitCallbackRegistrar.add("Inventory.EmptyLostAndFound", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyLostAndFound", LLFolderType::FT_LOST_AND_FOUND)); - mCommitCallbackRegistrar.add("Inventory.DoCreate", boost::bind(&do_nothing)); - mCommitCallbackRegistrar.add("Inventory.AttachObject", boost::bind(&do_nothing)); - mCommitCallbackRegistrar.add("Inventory.BeginIMSession", boost::bind(&do_nothing)); - mCommitCallbackRegistrar.add("Inventory.Share", boost::bind(&LLAvatarActions::shareWithAvatars, this)); - mCommitCallbackRegistrar.add("Inventory.FileUploadLocation", boost::bind(&do_nothing)); -} - -// Destroys the object -LLPanelObjectInventory::~LLPanelObjectInventory() -{ - if (!gIdleCallbacks.deleteFunction(idle, this)) - { - LL_WARNS() << "LLPanelObjectInventory::~LLPanelObjectInventory() failed to delete callback" << LL_ENDL; - } -} - -bool LLPanelObjectInventory::postBuild() -{ - // clear contents and initialize menus, sets up mFolders - reset(); - - // Register an idle update callback - gIdleCallbacks.addFunction(idle, this); - - return true; -} - -void LLPanelObjectInventory::doToSelected(const LLSD& userdata) -{ - LLInventoryAction::doToSelected(&gInventory, mFolders, userdata.asString()); -} - -void LLPanelObjectInventory::clearContents() -{ - mHaveInventory = false; - mIsInventoryEmpty = true; - if (LLToolDragAndDrop::getInstance() && LLToolDragAndDrop::getInstance()->getSource() == LLToolDragAndDrop::SOURCE_WORLD) - { - LLToolDragAndDrop::getInstance()->endDrag(); - } - - clearItemIDs(); - - if( mScroller ) - { - // removes mFolders - removeChild( mScroller ); //*TODO: Really shouldn't do this during draw()/refresh() - mScroller->die(); - mScroller = NULL; - mFolders = NULL; - } -} - - -void LLPanelObjectInventory::reset() -{ - clearContents(); - - mCommitCallbackRegistrar.pushScope(); // push local callbacks - - // Reset the inventory model to show all folders by default - mInventoryViewModel.getFilter().setShowFolderState(LLInventoryFilter::SHOW_ALL_FOLDERS); - - // Create a new folder view root - LLRect dummy_rect(0, 1, 1, 0); - LLFolderView::Params p; - p.name = "task inventory"; - p.title = "task inventory"; - p.parent_panel = this; - p.tool_tip= LLTrans::getString("PanelContentsTooltip"); - p.listener = LLTaskInvFVBridge::createObjectBridge(this, NULL); - p.folder_indentation = -14; // subtract space normally reserved for folder expanders - p.view_model = &mInventoryViewModel; - p.root = NULL; - p.options_menu = "menu_inventory.xml"; - - mFolders = LLUICtrlFactory::create(p); - - mFolders->setCallbackRegistrar(&mCommitCallbackRegistrar); - mFolders->setEnableRegistrar(&mEnableCallbackRegistrar); - - if (hasFocus()) - { - LLEditMenuHandler::gEditMenuHandler = mFolders; - } - - int offset = hasBorder() ? getBorder()->getBorderWidth() << 1 : 0; - LLRect scroller_rect(0, getRect().getHeight() - offset, getRect().getWidth() - offset, 0); - LLScrollContainer::Params scroll_p; - scroll_p.name("task inventory scroller"); - scroll_p.rect(scroller_rect); - scroll_p.tab_stop(true); - scroll_p.follows.flags(FOLLOWS_ALL); - mScroller = LLUICtrlFactory::create(scroll_p); - addChild(mScroller); - mScroller->addChild(mFolders); - - mFolders->setScrollContainer( mScroller ); - - mCommitCallbackRegistrar.popScope(); -} - -void LLPanelObjectInventory::inventoryChanged(LLViewerObject* object, - LLInventoryObject::object_list_t* inventory, - S32 serial_num, - void* data) -{ - if(!object) return; - - //LL_INFOS() << "invetnory arrived: \n" - // << " panel UUID: " << panel->mTaskUUID << "\n" - // << " task UUID: " << object->mID << LL_ENDL; - if(mTaskUUID == object->mID) - { - mInventoryNeedsUpdate = true; - } -} - -void LLPanelObjectInventory::updateInventory() -{ - //LL_INFOS() << "inventory arrived: \n" - // << " panel UUID: " << panel->mTaskUUID << "\n" - // << " task UUID: " << object->mID << LL_ENDL; - // We're still interested in this task's inventory. - std::vector selected_item_ids; - std::set selected_items; - bool inventory_has_focus = false; - if (mHaveInventory && mFolders) - { - selected_items = mFolders->getSelectionList(); - inventory_has_focus = gFocusMgr.childHasKeyboardFocus(mFolders); - } - for (std::set::iterator it = selected_items.begin(), end_it = selected_items.end(); - it != end_it; - ++it) - { - selected_item_ids.push_back(static_cast((*it)->getViewModelItem())->getUUID()); - } - - LLViewerObject* objectp = gObjectList.findObject(mTaskUUID); - if (objectp) - { - LLInventoryObject* inventory_root = objectp->getInventoryRoot(); - LLInventoryObject::object_list_t contents; - objectp->getInventoryContents(contents); - - if (inventory_root) - { - reset(); - mIsInventoryEmpty = false; - createFolderViews(inventory_root, contents); - mFolders->setEnabled(true); - } - else - { - // TODO: create an empty inventory - mIsInventoryEmpty = true; - } - - mHaveInventory = !mIsInventoryEmpty || !objectp->isInventoryDirty(); - if (objectp->isInventoryDirty()) - { - // Inventory is dirty, yet we received inventoryChanged() callback. - // User changed something during ongoing request. - // Rerequest. It will clear dirty flag and won't create dupplicate requests. - objectp->requestInventory(); - } - } - else - { - // TODO: create an empty inventory - mIsInventoryEmpty = true; - mHaveInventory = true; - } - - // restore previous selection - std::vector::iterator selection_it; - bool first_item = true; - for (selection_it = selected_item_ids.begin(); selection_it != selected_item_ids.end(); ++selection_it) - { - LLFolderViewItem* selected_item = getItemByID(*selection_it); - - if (selected_item) - { - //HACK: "set" first item then "change" each other one to get keyboard focus right - if (first_item) - { - mFolders->setSelection(selected_item, true, inventory_has_focus); - first_item = false; - } - else - { - mFolders->changeSelection(selected_item, true); - } - } - } - - if (mFolders) - { - mFolders->requestArrange(); - } - mInventoryNeedsUpdate = false; - // Edit menu handler is set in onFocusReceived -} - -// *FIX: This is currently a very expensive operation, because we have -// to iterate through the inventory one time for each category. This -// leads to an N^2 based on the category count. This could be greatly -// speeded with an efficient multimap implementation, but we don't -// have that in our current arsenal. -void LLPanelObjectInventory::createFolderViews(LLInventoryObject* inventory_root, LLInventoryObject::object_list_t& contents) -{ - if (!inventory_root) - { - return; - } - // Create a visible root category. - LLTaskInvFVBridge* bridge = NULL; - bridge = LLTaskInvFVBridge::createObjectBridge(this, inventory_root); - if(bridge) - { - LLUIColor item_color = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE); - - LLFolderViewFolder::Params p; - p.name = inventory_root->getName(); - p.tool_tip = p.name; - p.root = mFolders; - p.listener = bridge; - p.font_color = item_color; - p.font_highlight_color = item_color; - - LLFolderViewFolder* new_folder = LLUICtrlFactory::create(p); - - if (mShowRootFolder) - { - new_folder->addToFolder(mFolders); - new_folder->toggleOpen(); - } - - if (!contents.empty()) - { - createViewsForCategory(&contents, inventory_root, mShowRootFolder ? new_folder : mFolders); - } - - if (mShowRootFolder) - { - // Refresh for label to add item count - new_folder->refresh(); - } - } -} - -typedef std::pair obj_folder_pair; - -void LLPanelObjectInventory::createViewsForCategory(LLInventoryObject::object_list_t* inventory, - LLInventoryObject* parent, - LLFolderViewFolder* folder) -{ - LLUIColor item_color = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE); - - // Find all in the first pass - std::vector child_categories; - LLTaskInvFVBridge* bridge; - LLFolderViewItem* view; - - LLInventoryObject::object_list_t::iterator it = inventory->begin(); - LLInventoryObject::object_list_t::iterator end = inventory->end(); - for( ; it != end; ++it) - { - LLInventoryObject* obj = *it; - - if(parent->getUUID() == obj->getParentUUID()) - { - bridge = LLTaskInvFVBridge::createObjectBridge(this, obj); - if(!bridge) - { - continue; - } - if(LLAssetType::AT_CATEGORY == obj->getType()) - { - LLFolderViewFolder::Params p; - p.name = obj->getName(); - p.root = mFolders; - p.listener = bridge; - p.tool_tip = p.name; - p.font_color = item_color; - p.font_highlight_color = item_color; - view = LLUICtrlFactory::create(p); - child_categories.push_back(new obj_folder_pair(obj, - (LLFolderViewFolder*)view)); - } - else - { - LLFolderViewItem::Params params; - params.name(obj->getName()); - params.creation_date(bridge->getCreationDate()); - params.root(mFolders); - params.listener(bridge); - params.rect(LLRect()); - params.tool_tip = params.name; - params.font_color = item_color; - params.font_highlight_color = item_color; - view = LLUICtrlFactory::create (params); - } - view->addToFolder(folder); - addItemID(obj->getUUID(), view); - } - } - - // now, for each category, do the second pass - for(S32 i = 0; i < child_categories.size(); i++) - { - createViewsForCategory(inventory, child_categories[i]->first, - child_categories[i]->second ); - delete child_categories[i]; - } - folder->setChildrenInited(true); -} - -void LLPanelObjectInventory::refresh() -{ - //LL_INFOS() << "LLPanelObjectInventory::refresh()" << LL_ENDL; - bool has_inventory = false; - const bool non_root_ok = true; - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - LLSelectNode* node = selection->getFirstRootNode(NULL, non_root_ok); - if(node && node->mValid) - { - LLViewerObject* object = node->getObject(); - if(object && ((selection->getRootObjectCount() == 1) - || (selection->getObjectCount() == 1))) - { - // determine if we need to make a request. Start with a - // default based on if we have inventory at all. - bool make_request = !mHaveInventory; - - // If the task id is different than what we've stored, - // then make the request. - if(mTaskUUID != object->mID) - { - mTaskUUID = object->mID; - mAttachmentUUID = object->getAttachmentItemID(); - make_request = true; - - // This is a new object so pre-emptively clear the contents - // Otherwise we show the old stuff until the update comes in - clearContents(); - - // Register for updates from this object, - registerVOInventoryListener(object,NULL); - } - else if (mAttachmentUUID != object->getAttachmentItemID()) - { - mAttachmentUUID = object->getAttachmentItemID(); - if (mAttachmentUUID.notNull()) - { - // Server unsubsribes viewer (deselects object) from property - // updates after "ObjectAttach" so we need to resubscribe - LLSelectMgr::getInstance()->sendSelect(); - } - } - - // Based on the node information, we may need to dirty the - // object inventory and get it again. - if(node->mValid) - { - if(node->mInventorySerial != object->getInventorySerial() || object->isInventoryDirty()) - { - make_request = true; - } - } - - // do the request if necessary. - if(make_request) - { - requestVOInventory(); - } - has_inventory = true; - } - } - if(!has_inventory) - { - clearInventoryTask(); - } - mInventoryViewModel.setTaskID(mTaskUUID); - //LL_INFOS() << "LLPanelObjectInventory::refresh() " << mTaskUUID << LL_ENDL; -} - -void LLPanelObjectInventory::clearInventoryTask() -{ - mTaskUUID = LLUUID::null; - mAttachmentUUID = LLUUID::null; - removeVOInventoryListener(); - clearContents(); -} - -void LLPanelObjectInventory::removeSelectedItem() -{ - if(mFolders) - { - mFolders->removeSelectedItems(); - } -} - -void LLPanelObjectInventory::startRenamingSelectedItem() -{ - if(mFolders) - { - mFolders->startRenamingSelectedItem(); - } -} - -void LLPanelObjectInventory::draw() -{ - LLPanel::draw(); - - if(mIsInventoryEmpty) - { - if((LLUUID::null != mTaskUUID) && (!mHaveInventory)) - { - LLFontGL::getFontSansSerif()->renderUTF8(LLTrans::getString("LoadingContents"), 0, - (S32)(getRect().getWidth() * 0.5f), - 10, - LLColor4( 1, 1, 1, 1 ), - LLFontGL::HCENTER, - LLFontGL::BOTTOM); - } - else if(mHaveInventory) - { - LLFontGL::getFontSansSerif()->renderUTF8(LLTrans::getString("NoContents"), 0, - (S32)(getRect().getWidth() * 0.5f), - 10, - LLColor4( 1, 1, 1, 1 ), - LLFontGL::HCENTER, - LLFontGL::BOTTOM); - } - } -} - -void LLPanelObjectInventory::deleteAllChildren() -{ - mScroller = NULL; - mFolders = NULL; - LLView::deleteAllChildren(); -} - -bool LLPanelObjectInventory::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, void *cargo_data, EAcceptance *accept, std::string& tooltip_msg) -{ - if (mFolders) - { - LLFolderViewItem* folderp = mFolders->getNextFromChild(NULL); - if (!folderp) - { - return false; - } - // Try to pass on unmodified mouse coordinates - S32 local_x = x - mFolders->getRect().mLeft; - S32 local_y = y - mFolders->getRect().mBottom; - - if (mFolders->pointInView(local_x, local_y)) - { - return mFolders->handleDragAndDrop(local_x, local_y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); - } - else - { - //force mouse coordinates to be inside folder rectangle - return mFolders->handleDragAndDrop(5, 1, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); - } - } - else - { - return false; - } -} - -//static -void LLPanelObjectInventory::idle(void* user_data) -{ - LLPanelObjectInventory* self = (LLPanelObjectInventory*)user_data; - - if (self->mFolders) - { - self->mFolders->update(); - } - if (self->mInventoryNeedsUpdate) - { - self->updateInventory(); - } -} - -void LLPanelObjectInventory::onFocusLost() -{ - // inventory no longer handles cut/copy/paste/delete - if (LLEditMenuHandler::gEditMenuHandler == mFolders) - { - LLEditMenuHandler::gEditMenuHandler = NULL; - } - - LLPanel::onFocusLost(); -} - -void LLPanelObjectInventory::onFocusReceived() -{ - // inventory now handles cut/copy/paste/delete - LLEditMenuHandler::gEditMenuHandler = mFolders; - - LLPanel::onFocusReceived(); -} - - -LLFolderViewItem* LLPanelObjectInventory::getItemByID( const LLUUID& id ) -{ - std::map::iterator map_it; - map_it = mItemMap.find(id); - if (map_it != mItemMap.end()) - { - return map_it->second; - } - - return NULL; -} - -void LLPanelObjectInventory::removeItemID( const LLUUID& id ) -{ - mItemMap.erase(id); -} - -void LLPanelObjectInventory::addItemID( const LLUUID& id, LLFolderViewItem* itemp ) -{ - mItemMap[id] = itemp; -} - -void LLPanelObjectInventory::clearItemIDs() -{ - mItemMap.clear(); -} - -bool LLPanelObjectInventory::handleKeyHere( KEY key, MASK mask ) -{ - bool handled = false; - switch (key) - { - case KEY_DELETE: -#if LL_DARWIN - case KEY_BACKSPACE: -#endif - // Delete selected items if delete or backspace key hit on the inventory panel - // Note: on Mac laptop keyboards, backspace and delete are one and the same - if (isSelectionRemovable() && mask == MASK_NONE) - { - LLInventoryAction::doToSelected(&gInventory, mFolders, "delete"); - handled = true; - } - break; - } - return handled; -} - -bool LLPanelObjectInventory::isSelectionRemovable() -{ - if (!mFolders || !mFolders->getRoot()) - { - return false; - } - std::set selection_set = mFolders->getRoot()->getSelectionList(); - if (selection_set.empty()) - { - return false; - } - for (std::set::iterator iter = selection_set.begin(); - iter != selection_set.end(); - ++iter) - { - LLFolderViewItem *item = *iter; - const LLFolderViewModelItemInventory *listener = dynamic_cast(item->getViewModelItem()); - if (!listener || !listener->isItemRemovable() || listener->isItemInTrash()) - { - return false; - } - } - return true; -} +/** + * @file llsidepanelinventory.cpp + * @brief LLPanelObjectInventory class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +//***************************************************************************** +// +// Implementation of the panel inventory - used to view and control a +// task's inventory. +// +//***************************************************************************** + +#include "llviewerprecompiledheaders.h" + +#include "llpanelobjectinventory.h" + +#include "llmenugl.h" +#include "llnotificationsutil.h" +#include "roles_constants.h" + +#include "llagent.h" +#include "llavataractions.h" +#include "llcallbacklist.h" +#include "llbuycurrencyhtml.h" +#include "llfloaterreg.h" +#include "llfolderview.h" +#include "llinventorybridge.h" +#include "llinventorydefines.h" +#include "llinventoryicon.h" +#include "llinventoryfilter.h" +#include "llinventoryfunctions.h" +#include "llmaterialeditor.h" +#include "llpreviewanim.h" +#include "llpreviewgesture.h" +#include "llpreviewnotecard.h" +#include "llpreviewscript.h" +#include "llpreviewsound.h" +#include "llpreviewtexture.h" +#include "llscrollcontainer.h" +#include "llselectmgr.h" +#include "llstatusbar.h" +#include "lltooldraganddrop.h" +#include "lltrans.h" +#include "llviewerassettype.h" +#include "llviewerinventory.h" +#include "llviewerregion.h" +#include "llviewerobjectlist.h" +#include "llviewermessage.h" + +const LLColor4U DEFAULT_WHITE(255, 255, 255); + +///---------------------------------------------------------------------------- +/// Class LLTaskInvFVBridge +///---------------------------------------------------------------------------- + +class LLTaskInvFVBridge : public LLFolderViewModelItemInventory +{ +protected: + LLUUID mUUID; + std::string mName; + mutable std::string mDisplayName; + mutable std::string mSearchableName; + LLPanelObjectInventory* mPanel; + U32 mFlags; + LLAssetType::EType mAssetType; + LLInventoryType::EType mInventoryType; + + LLInventoryObject* findInvObject() const; + LLInventoryItem* findItem() const; + +public: + LLTaskInvFVBridge(LLPanelObjectInventory* panel, + const LLUUID& uuid, + const std::string& name, + U32 flags=0); + virtual ~LLTaskInvFVBridge() {} + + virtual LLFontGL::StyleFlags getLabelStyle() const { return LLFontGL::NORMAL; } + virtual std::string getLabelSuffix() const { return LLStringUtil::null; } + + static LLTaskInvFVBridge* createObjectBridge(LLPanelObjectInventory* panel, + LLInventoryObject* object); + void showProperties(); + S32 getPrice(); + + // LLFolderViewModelItemInventory functionality + virtual const std::string& getName() const; + virtual const std::string& getDisplayName() const; + virtual const std::string& getSearchableName() const; + + virtual std::string getSearchableDescription() const {return LLStringUtil::null;} + virtual std::string getSearchableCreatorName() const {return LLStringUtil::null;} + virtual std::string getSearchableUUIDString() const {return LLStringUtil::null;} + + + virtual PermissionMask getPermissionMask() const { return PERM_NONE; } + /*virtual*/ LLFolderType::EType getPreferredType() const { return LLFolderType::FT_NONE; } + virtual const LLUUID& getUUID() const { return mUUID; } + virtual const LLUUID& getThumbnailUUID() const { return LLUUID::null;} + virtual time_t getCreationDate() const; + virtual void setCreationDate(time_t creation_date_utc); + + virtual LLUIImagePtr getIcon() const; + virtual void openItem(); + virtual bool canOpenItem() const { return false; } + virtual void closeItem() {} + virtual void selectItem() {} + virtual void navigateToFolder(bool new_window = false, bool change_mode = false) {} + virtual bool isItemRenameable() const; + virtual bool renameItem(const std::string& new_name); + virtual bool isItemMovable() const; + virtual bool isItemRemovable(bool check_worn = true) const; + virtual bool removeItem(); + virtual void removeBatch(std::vector& batch); + virtual void move(LLFolderViewModelItem* parent_listener); + virtual bool isItemCopyable(bool can_copy_as_link = true) const; + virtual bool copyToClipboard() const; + virtual bool cutToClipboard(); + virtual bool isClipboardPasteable() const; + virtual void pasteFromClipboard(); + virtual void pasteLinkFromClipboard(); + virtual void buildContextMenu(LLMenuGL& menu, U32 flags); + virtual void performAction(LLInventoryModel* model, std::string action); + virtual bool isUpToDate() const { return true; } + virtual bool hasChildren() const { return false; } + virtual LLInventoryType::EType getInventoryType() const { return LLInventoryType::IT_NONE; } + virtual LLWearableType::EType getWearableType() const { return LLWearableType::WT_NONE; } + virtual LLSettingsType::type_e getSettingsType() const { return LLSettingsType::ST_NONE; } + virtual EInventorySortGroup getSortGroup() const { return SG_ITEM; } + virtual LLInventoryObject* getInventoryObject() const { return findInvObject(); } + + + // LLDragAndDropBridge functionality + virtual LLToolDragAndDrop::ESource getDragSource() const { return LLToolDragAndDrop::SOURCE_WORLD; } + virtual bool startDrag(EDragAndDropType* type, LLUUID* id) const; + virtual bool dragOrDrop(MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + std::string& tooltip_msg); +}; + +LLTaskInvFVBridge::LLTaskInvFVBridge( + LLPanelObjectInventory* panel, + const LLUUID& uuid, + const std::string& name, + U32 flags) +: LLFolderViewModelItemInventory(panel->getRootViewModel()), + mUUID(uuid), + mName(name), + mPanel(panel), + mFlags(flags), + mAssetType(LLAssetType::AT_NONE), + mInventoryType(LLInventoryType::IT_NONE) +{ + const LLInventoryItem *item = findItem(); + if (item) + { + mAssetType = item->getType(); + mInventoryType = item->getInventoryType(); + } +} + +LLInventoryObject* LLTaskInvFVBridge::findInvObject() const +{ + LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); + if (object) + { + return object->getInventoryObject(mUUID); + } + return NULL; +} + + +LLInventoryItem* LLTaskInvFVBridge::findItem() const +{ + return dynamic_cast(findInvObject()); +} + +void LLTaskInvFVBridge::showProperties() +{ + show_task_item_profile(mUUID, mPanel->getTaskUUID()); +} + +S32 LLTaskInvFVBridge::getPrice() +{ + LLInventoryItem* item = findItem(); + if(item) + { + return item->getSaleInfo().getSalePrice(); + } + else + { + return -1; + } +} + +const std::string& LLTaskInvFVBridge::getName() const +{ + return mName; +} + +const std::string& LLTaskInvFVBridge::getDisplayName() const +{ + LLInventoryItem* item = findItem(); + + if(item) + { + mDisplayName.assign(item->getName()); + + // Localize "New Script", "New Script 1", "New Script 2", etc. + if (item->getType() == LLAssetType::AT_LSL_TEXT && + LLStringUtil::startsWith(item->getName(), "New Script")) + { + LLStringUtil::replaceString(mDisplayName, "New Script", LLTrans::getString("PanelContentsNewScript")); + } + + const LLPermissions& perm(item->getPermissions()); + bool copy = gAgent.allowOperation(PERM_COPY, perm, GP_OBJECT_MANIPULATE); + bool mod = gAgent.allowOperation(PERM_MODIFY, perm, GP_OBJECT_MANIPULATE); + bool xfer = gAgent.allowOperation(PERM_TRANSFER, perm, GP_OBJECT_MANIPULATE); + + if(!copy) + { + mDisplayName.append(LLTrans::getString("no_copy")); + } + if(!mod) + { + mDisplayName.append(LLTrans::getString("no_modify")); + } + if(!xfer) + { + mDisplayName.append(LLTrans::getString("no_transfer")); + } + } + + mSearchableName.assign(mDisplayName + getLabelSuffix()); + + return mDisplayName; +} + +const std::string& LLTaskInvFVBridge::getSearchableName() const +{ + return mSearchableName; +} + + +// BUG: No creation dates for task inventory +time_t LLTaskInvFVBridge::getCreationDate() const +{ + return 0; +} + +void LLTaskInvFVBridge::setCreationDate(time_t creation_date_utc) +{} + + +LLUIImagePtr LLTaskInvFVBridge::getIcon() const +{ + const bool item_is_multi = (mFlags & LLInventoryItemFlags::II_FLAGS_OBJECT_HAS_MULTIPLE_ITEMS); + + return LLInventoryIcon::getIcon(mAssetType, mInventoryType, 0, item_is_multi ); +} + +void LLTaskInvFVBridge::openItem() +{ + // no-op. + LL_DEBUGS() << "LLTaskInvFVBridge::openItem()" << LL_ENDL; +} + +bool LLTaskInvFVBridge::isItemRenameable() const +{ + if(gAgent.isGodlike()) return true; + LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); + if(object) + { + LLInventoryItem* item = (LLInventoryItem*)(object->getInventoryObject(mUUID)); + if(item && gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), + GP_OBJECT_MANIPULATE, GOD_LIKE)) + { + return true; + } + } + return false; +} + +bool LLTaskInvFVBridge::renameItem(const std::string& new_name) +{ + LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); + if(object) + { + LLViewerInventoryItem* item = NULL; + item = (LLViewerInventoryItem*)object->getInventoryObject(mUUID); + if(item && (gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), + GP_OBJECT_MANIPULATE, GOD_LIKE))) + { + LLPointer new_item = new LLViewerInventoryItem(item); + new_item->rename(new_name); + object->updateInventory( + new_item, + TASK_INVENTORY_ITEM_KEY, + false); + } + } + return true; +} + +bool LLTaskInvFVBridge::isItemMovable() const +{ + //LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); + //if(object && (object->permModify() || gAgent.isGodlike())) + //{ + // return true; + //} + //return false; + return true; +} + +bool LLTaskInvFVBridge::isItemRemovable(bool check_worn) const +{ + const LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); + if(object + && (object->permModify() || object->permYouOwner())) + { + return true; + } + return false; +} + +bool remove_task_inventory_callback(const LLSD& notification, const LLSD& response, LLPanelObjectInventory* panel) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + LLViewerObject* object = gObjectList.findObject(notification["payload"]["task_id"].asUUID()); + if(option == 0 && object) + { + // yes + LLSD::array_const_iterator list_end = notification["payload"]["inventory_ids"].endArray(); + for (LLSD::array_const_iterator list_it = notification["payload"]["inventory_ids"].beginArray(); + list_it != list_end; + ++list_it) + { + object->removeInventory(list_it->asUUID()); + } + + // refresh the UI. + panel->refresh(); + } + return false; +} + +// helper for remove +// ! REFACTOR ! two_uuids_list_t is also defined in llinventorybridge.h, but differently. +typedef std::pair > panel_two_uuids_list_t; +typedef std::pair remove_data_t; +bool LLTaskInvFVBridge::removeItem() +{ + if(isItemRemovable() && mPanel) + { + LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); + if(object) + { + if(object->permModify()) + { + // just do it. + object->removeInventory(mUUID); + return true; + } + else + { + LLSD payload; + payload["task_id"] = mPanel->getTaskUUID(); + payload["inventory_ids"].append(mUUID); + LLNotificationsUtil::add("RemoveItemWarn", LLSD(), payload, boost::bind(&remove_task_inventory_callback, _1, _2, mPanel)); + return false; + } + } + } + return false; +} + +void LLTaskInvFVBridge::removeBatch(std::vector& batch) +{ + if (!mPanel) + { + return; + } + + LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); + if (!object) + { + return; + } + + if (!object->permModify()) + { + LLSD payload; + payload["task_id"] = mPanel->getTaskUUID(); + for (S32 i = 0; i < (S32)batch.size(); i++) + { + LLTaskInvFVBridge* itemp = (LLTaskInvFVBridge*)batch[i]; + payload["inventory_ids"].append(itemp->getUUID()); + } + LLNotificationsUtil::add("RemoveItemWarn", LLSD(), payload, boost::bind(&remove_task_inventory_callback, _1, _2, mPanel)); + + } + else + { + for (S32 i = 0; i < (S32)batch.size(); i++) + { + LLTaskInvFVBridge* itemp = (LLTaskInvFVBridge*)batch[i]; + + if(itemp->isItemRemovable()) + { + // just do it. + object->removeInventory(itemp->getUUID()); + } + } + } +} + +void LLTaskInvFVBridge::move(LLFolderViewModelItem* parent_listener) +{ +} + +bool LLTaskInvFVBridge::isItemCopyable(bool can_link) const +{ + LLInventoryItem* item = findItem(); + if(!item) return false; + return gAgent.allowOperation(PERM_COPY, item->getPermissions(), + GP_OBJECT_MANIPULATE); +} + +bool LLTaskInvFVBridge::copyToClipboard() const +{ + return false; +} + +bool LLTaskInvFVBridge::cutToClipboard() +{ + return false; +} + +bool LLTaskInvFVBridge::isClipboardPasteable() const +{ + return false; +} + +void LLTaskInvFVBridge::pasteFromClipboard() +{ +} + +void LLTaskInvFVBridge::pasteLinkFromClipboard() +{ +} + +bool LLTaskInvFVBridge::startDrag(EDragAndDropType* type, LLUUID* id) const +{ + //LL_INFOS() << "LLTaskInvFVBridge::startDrag()" << LL_ENDL; + if(mPanel) + { + LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); + if(object) + { + LLInventoryItem* inv = NULL; + if((inv = (LLInventoryItem*)object->getInventoryObject(mUUID))) + { + const LLPermissions& perm = inv->getPermissions(); + bool can_copy = gAgent.allowOperation(PERM_COPY, perm, + GP_OBJECT_MANIPULATE); + if (object->isAttachment() && !can_copy) + { + //RN: no copy contents of attachments cannot be dragged out + // due to a race condition and possible exploit where + // attached objects do not update their inventory items + // when their contents are manipulated + return false; + } + if((can_copy && perm.allowTransferTo(gAgent.getID())) + || object->permYouOwner()) +// || gAgent.isGodlike()) + + { + *type = LLViewerAssetType::lookupDragAndDropType(inv->getType()); + + *id = inv->getUUID(); + return true; + } + } + } + } + return false; +} + +bool LLTaskInvFVBridge::dragOrDrop(MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + std::string& tooltip_msg) +{ + //LL_INFOS() << "LLTaskInvFVBridge::dragOrDrop()" << LL_ENDL; + return false; +} + +// virtual +void LLTaskInvFVBridge::performAction(LLInventoryModel* model, std::string action) +{ + if (action == "task_open") + { + openItem(); + } + else if (action == "task_properties") + { + showProperties(); + } +} + +void LLTaskInvFVBridge::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + LLInventoryItem* item = findItem(); + std::vector items; + std::vector disabled_items; + + if (!item) + { + hide_context_entries(menu, items, disabled_items); + return; + } + + if (canOpenItem()) + { + items.push_back(std::string("Task Open")); + } + items.push_back(std::string("Task Properties")); + if ((flags & FIRST_SELECTED_ITEM) == 0) + { + disabled_items.push_back(std::string("Task Properties")); + } + if(isItemRenameable()) + { + items.push_back(std::string("Task Rename")); + if ((flags & FIRST_SELECTED_ITEM) == 0) + { + disabled_items.push_back(std::string("Task Rename")); + } + } + if(isItemRemovable()) + { + items.push_back(std::string("Task Remove")); + } + + hide_context_entries(menu, items, disabled_items); +} + + +///---------------------------------------------------------------------------- +/// Class LLTaskFolderBridge +///---------------------------------------------------------------------------- + +class LLTaskCategoryBridge : public LLTaskInvFVBridge +{ +public: + LLTaskCategoryBridge( + LLPanelObjectInventory* panel, + const LLUUID& uuid, + const std::string& name); + + virtual LLUIImagePtr getIcon() const; + virtual const std::string& getDisplayName() const; + virtual bool isItemRenameable() const; + // virtual bool isItemCopyable() const { return false; } + virtual bool renameItem(const std::string& new_name); + virtual bool isItemRemovable(bool check_worn = true) const; + virtual void buildContextMenu(LLMenuGL& menu, U32 flags); + virtual bool hasChildren() const; + virtual bool startDrag(EDragAndDropType* type, LLUUID* id) const; + virtual bool dragOrDrop(MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + std::string& tooltip_msg); + virtual bool canOpenItem() const { return true; } + virtual void openItem(); + virtual EInventorySortGroup getSortGroup() const { return SG_NORMAL_FOLDER; } +}; + +LLTaskCategoryBridge::LLTaskCategoryBridge( + LLPanelObjectInventory* panel, + const LLUUID& uuid, + const std::string& name) : + LLTaskInvFVBridge(panel, uuid, name) +{ +} + +LLUIImagePtr LLTaskCategoryBridge::getIcon() const +{ + return LLUI::getUIImage("Inv_FolderClosed"); +} + +// virtual +const std::string& LLTaskCategoryBridge::getDisplayName() const +{ + LLInventoryObject* cat = findInvObject(); + + if (cat) + { + std::string name = cat->getName(); + if (mChildren.size() > 0) + { + // Add item count + // Normally we would be using getLabelSuffix for this + // but object's inventory just uses displaynames + LLStringUtil::format_map_t args; + args["[ITEMS_COUNT]"] = llformat("%d", mChildren.size()); + + name.append(" " + LLTrans::getString("InventoryItemsCount", args)); + } + mDisplayName.assign(name); + } + + return mDisplayName; +} + +bool LLTaskCategoryBridge::isItemRenameable() const +{ + return false; +} + +bool LLTaskCategoryBridge::renameItem(const std::string& new_name) +{ + return false; +} + +bool LLTaskCategoryBridge::isItemRemovable(bool check_worn) const +{ + return false; +} + +void LLTaskCategoryBridge::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + std::vector items; + std::vector disabled_items; + hide_context_entries(menu, items, disabled_items); +} + +bool LLTaskCategoryBridge::hasChildren() const +{ + // return true if we have or do know know if we have children. + // *FIX: For now, return false - we will know for sure soon enough. + return false; +} + +void LLTaskCategoryBridge::openItem() +{ +} + +bool LLTaskCategoryBridge::startDrag(EDragAndDropType* type, LLUUID* id) const +{ + //LL_INFOS() << "LLTaskInvFVBridge::startDrag()" << LL_ENDL; + if(mPanel && mUUID.notNull()) + { + LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); + if(object) + { + const LLInventoryObject* cat = object->getInventoryObject(mUUID); + if ( (cat) && (move_inv_category_world_to_agent(mUUID, LLUUID::null, false)) ) + { + *type = LLViewerAssetType::lookupDragAndDropType(cat->getType()); + *id = mUUID; + return true; + } + } + } + return false; +} + +bool LLTaskCategoryBridge::dragOrDrop(MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + std::string& tooltip_msg) +{ + //LL_INFOS() << "LLTaskCategoryBridge::dragOrDrop()" << LL_ENDL; + bool accept = false; + LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); + if(object) + { + switch(cargo_type) + { + case DAD_CATEGORY: + accept = LLToolDragAndDrop::getInstance()->dadUpdateInventoryCategory(object,drop); + break; + case DAD_TEXTURE: + case DAD_SOUND: + case DAD_LANDMARK: + case DAD_OBJECT: + case DAD_NOTECARD: + case DAD_CLOTHING: + case DAD_BODYPART: + case DAD_ANIMATION: + case DAD_GESTURE: + case DAD_CALLINGCARD: + case DAD_MESH: + case DAD_SETTINGS: + case DAD_MATERIAL: + accept = LLToolDragAndDrop::isInventoryDropAcceptable(object, (LLViewerInventoryItem*)cargo_data); + if(accept && drop) + { + LLToolDragAndDrop::dropInventory(object, + (LLViewerInventoryItem*)cargo_data, + LLToolDragAndDrop::getInstance()->getSource(), + LLToolDragAndDrop::getInstance()->getSourceID()); + } + break; + case DAD_SCRIPT: + // *HACK: In order to resolve SL-22177, we need to block + // drags from notecards and objects onto other + // objects. uncomment the simpler version when we have + // that right. + //accept = LLToolDragAndDrop::isInventoryDropAcceptable(object, (LLViewerInventoryItem*)cargo_data); + if(LLToolDragAndDrop::isInventoryDropAcceptable( + object, (LLViewerInventoryItem*)cargo_data) + && (LLToolDragAndDrop::SOURCE_WORLD != LLToolDragAndDrop::getInstance()->getSource()) + && (LLToolDragAndDrop::SOURCE_NOTECARD != LLToolDragAndDrop::getInstance()->getSource())) + { + accept = true; + } + if(accept && drop) + { + LLViewerInventoryItem* item = (LLViewerInventoryItem*)cargo_data; + // rez in the script active by default, rez in + // inactive if the control key is being held down. + bool active = ((mask & MASK_CONTROL) == 0); + LLToolDragAndDrop::dropScript(object, item, active, + LLToolDragAndDrop::getInstance()->getSource(), + LLToolDragAndDrop::getInstance()->getSourceID()); + } + break; + default: + break; + } + } + return accept; +} + +///---------------------------------------------------------------------------- +/// Class LLTaskTextureBridge +///---------------------------------------------------------------------------- + +class LLTaskTextureBridge : public LLTaskInvFVBridge +{ +public: + LLTaskTextureBridge(LLPanelObjectInventory* panel, + const LLUUID& uuid, + const std::string& name) : + LLTaskInvFVBridge(panel, uuid, name) {} + + virtual bool canOpenItem() const { return true; } + virtual void openItem(); +}; + +void LLTaskTextureBridge::openItem() +{ + LL_INFOS() << "LLTaskTextureBridge::openItem()" << LL_ENDL; + LLPreviewTexture* preview = LLFloaterReg::showTypedInstance("preview_texture", LLSD(mUUID), TAKE_FOCUS_YES); + if(preview) + { + LLInventoryItem* item = findItem(); + if(item) + { + preview->setAuxItem(item); + } + preview->setObjectID(mPanel->getTaskUUID()); + } +} + + +///---------------------------------------------------------------------------- +/// Class LLTaskSoundBridge +///---------------------------------------------------------------------------- + +class LLTaskSoundBridge : public LLTaskInvFVBridge +{ +public: + LLTaskSoundBridge(LLPanelObjectInventory* panel, + const LLUUID& uuid, + const std::string& name) : + LLTaskInvFVBridge(panel, uuid, name) {} + + virtual bool canOpenItem() const { return true; } + virtual void openItem(); + virtual void performAction(LLInventoryModel* model, std::string action); + virtual void buildContextMenu(LLMenuGL& menu, U32 flags); + static void openSoundPreview(void* data); +}; + +void LLTaskSoundBridge::openItem() +{ + openSoundPreview((void*)this); +} + +void LLTaskSoundBridge::openSoundPreview(void* data) +{ + LLTaskSoundBridge* self = (LLTaskSoundBridge*)data; + if(!self) + return; + + LLPreviewSound* preview = LLFloaterReg::showTypedInstance("preview_sound", LLSD(self->mUUID), TAKE_FOCUS_YES); + if (preview) + { + preview->setObjectID(self->mPanel->getTaskUUID()); + } +} + +// virtual +void LLTaskSoundBridge::performAction(LLInventoryModel* model, std::string action) +{ + if (action == "task_play") + { + LLInventoryItem* item = findItem(); + if(item) + { + send_sound_trigger(item->getAssetUUID(), 1.0); + } + } + LLTaskInvFVBridge::performAction(model, action); +} + +void LLTaskSoundBridge::buildContextMenu(LLMenuGL& menu, U32 flags) +{ + LLInventoryItem* item = findItem(); + std::vector items; + std::vector disabled_items; + if (!item) + { + hide_context_entries(menu, items, disabled_items); + return; + } + + if (canOpenItem()) + { + if (!isItemCopyable()) + { + disabled_items.push_back(std::string("Task Open")); + } + } + items.push_back(std::string("Task Properties")); + if ((flags & FIRST_SELECTED_ITEM) == 0) + { + disabled_items.push_back(std::string("Task Properties")); + } + if(isItemRenameable()) + { + items.push_back(std::string("Task Rename")); + } + if(isItemRemovable()) + { + items.push_back(std::string("Task Remove")); + } + + items.push_back(std::string("Task Play")); + + + hide_context_entries(menu, items, disabled_items); +} + +///---------------------------------------------------------------------------- +/// Class LLTaskLandmarkBridge +///---------------------------------------------------------------------------- + +class LLTaskLandmarkBridge : public LLTaskInvFVBridge +{ +public: + LLTaskLandmarkBridge(LLPanelObjectInventory* panel, + const LLUUID& uuid, + const std::string& name) : + LLTaskInvFVBridge(panel, uuid, name) {} +}; + +///---------------------------------------------------------------------------- +/// Class LLTaskCallingCardBridge +///---------------------------------------------------------------------------- + +class LLTaskCallingCardBridge : public LLTaskInvFVBridge +{ +public: + LLTaskCallingCardBridge(LLPanelObjectInventory* panel, + const LLUUID& uuid, + const std::string& name) : + LLTaskInvFVBridge(panel, uuid, name) {} + + virtual bool isItemRenameable() const; + virtual bool renameItem(const std::string& new_name); +}; + +bool LLTaskCallingCardBridge::isItemRenameable() const +{ + return false; +} + +bool LLTaskCallingCardBridge::renameItem(const std::string& new_name) +{ + return false; +} + + +///---------------------------------------------------------------------------- +/// Class LLTaskScriptBridge +///---------------------------------------------------------------------------- + +class LLTaskScriptBridge : public LLTaskInvFVBridge +{ +public: + LLTaskScriptBridge(LLPanelObjectInventory* panel, + const LLUUID& uuid, + const std::string& name) : + LLTaskInvFVBridge(panel, uuid, name) {} + + //static bool enableIfCopyable( void* userdata ); +}; + +class LLTaskLSLBridge : public LLTaskScriptBridge +{ +public: + LLTaskLSLBridge(LLPanelObjectInventory* panel, + const LLUUID& uuid, + const std::string& name) : + LLTaskScriptBridge(panel, uuid, name) {} + + virtual bool canOpenItem() const { return true; } + virtual void openItem(); + virtual bool removeItem(); + //virtual void buildContextMenu(LLMenuGL& menu); + + //static void copyToInventory(void* userdata); +}; + +void LLTaskLSLBridge::openItem() +{ + LL_INFOS() << "LLTaskLSLBridge::openItem() " << mUUID << LL_ENDL; + LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); + if(!object || object->isInventoryPending()) + { + return; + } + if (object->permModify() || gAgent.isGodlike()) + { + LLSD floater_key; + floater_key["taskid"] = mPanel->getTaskUUID(); + floater_key["itemid"] = mUUID; + + LLLiveLSLEditor* preview = LLFloaterReg::showTypedInstance("preview_scriptedit", floater_key, TAKE_FOCUS_YES); + if (preview) + { + LLSelectNode *node = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(NULL, true); + if (node && node->mValid) + { + preview->setObjectName(node->mName); + } + preview->setObjectID(mPanel->getTaskUUID()); + } + } + else + { + LLNotificationsUtil::add("CannotOpenScriptObjectNoMod"); + } +} + +bool LLTaskLSLBridge::removeItem() +{ + LLFloaterReg::hideInstance("preview_scriptedit", LLSD(mUUID)); + return LLTaskInvFVBridge::removeItem(); +} + +///---------------------------------------------------------------------------- +/// Class LLTaskObjectBridge +///---------------------------------------------------------------------------- + +class LLTaskObjectBridge : public LLTaskInvFVBridge +{ +public: + LLTaskObjectBridge(LLPanelObjectInventory* panel, + const LLUUID& uuid, + const std::string& name, + U32 flags = 0) : + LLTaskInvFVBridge(panel, uuid, name, flags) {} +}; + +///---------------------------------------------------------------------------- +/// Class LLTaskNotecardBridge +///---------------------------------------------------------------------------- + +class LLTaskNotecardBridge : public LLTaskInvFVBridge +{ +public: + LLTaskNotecardBridge(LLPanelObjectInventory* panel, + const LLUUID& uuid, + const std::string& name) : + LLTaskInvFVBridge(panel, uuid, name) {} + + virtual bool canOpenItem() const { return true; } + virtual void openItem(); + virtual bool removeItem(); +}; + +void LLTaskNotecardBridge::openItem() +{ + LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); + if(!object || object->isInventoryPending()) + { + return; + } + + // Note: even if we are not allowed to modify copyable notecard, we should be able to view it + LLInventoryItem *item = dynamic_cast(object->getInventoryObject(mUUID)); + bool item_copy = item && gAgent.allowOperation(PERM_COPY, item->getPermissions(), GP_OBJECT_MANIPULATE); + if( item_copy + || object->permModify() + || gAgent.isGodlike()) + { + LLSD floater_key; + floater_key["taskid"] = mPanel->getTaskUUID(); + floater_key["itemid"] = mUUID; + LLPreviewNotecard* preview = LLFloaterReg::showTypedInstance("preview_notecard", floater_key, TAKE_FOCUS_YES); + if (preview) + { + preview->setObjectID(mPanel->getTaskUUID()); + } + } +} + +bool LLTaskNotecardBridge::removeItem() +{ + LLFloaterReg::hideInstance("preview_notecard", LLSD(mUUID)); + return LLTaskInvFVBridge::removeItem(); +} + +///---------------------------------------------------------------------------- +/// Class LLTaskGestureBridge +///---------------------------------------------------------------------------- + +class LLTaskGestureBridge : public LLTaskInvFVBridge +{ +public: + LLTaskGestureBridge(LLPanelObjectInventory* panel, + const LLUUID& uuid, + const std::string& name) : + LLTaskInvFVBridge(panel, uuid, name) {} + + virtual bool canOpenItem() const { return true; } + virtual void openItem(); + virtual bool removeItem(); +}; + +void LLTaskGestureBridge::openItem() +{ + LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); + if(!object || object->isInventoryPending()) + { + return; + } + LLPreviewGesture::show(mUUID, mPanel->getTaskUUID()); +} + +bool LLTaskGestureBridge::removeItem() +{ + // Don't need to deactivate gesture because gestures inside objects can never be active. + LLFloaterReg::hideInstance("preview_gesture", LLSD(mUUID)); + return LLTaskInvFVBridge::removeItem(); +} + +///---------------------------------------------------------------------------- +/// Class LLTaskAnimationBridge +///---------------------------------------------------------------------------- + +class LLTaskAnimationBridge : public LLTaskInvFVBridge +{ +public: + LLTaskAnimationBridge(LLPanelObjectInventory* panel, + const LLUUID& uuid, + const std::string& name) : + LLTaskInvFVBridge(panel, uuid, name) {} + + virtual bool canOpenItem() const { return true; } + virtual void openItem(); + virtual bool removeItem(); +}; + +void LLTaskAnimationBridge::openItem() +{ + LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); + if(!object || object->isInventoryPending()) + { + return; + } + + LLPreviewAnim* preview = LLFloaterReg::showTypedInstance("preview_anim", LLSD(mUUID), TAKE_FOCUS_YES); + if (preview && (object->permModify() || gAgent.isGodlike())) + { + preview->setObjectID(mPanel->getTaskUUID()); + } +} + +bool LLTaskAnimationBridge::removeItem() +{ + LLFloaterReg::hideInstance("preview_anim", LLSD(mUUID)); + return LLTaskInvFVBridge::removeItem(); +} + +///---------------------------------------------------------------------------- +/// Class LLTaskWearableBridge +///---------------------------------------------------------------------------- + +class LLTaskWearableBridge : public LLTaskInvFVBridge +{ +public: + LLTaskWearableBridge(LLPanelObjectInventory* panel, + const LLUUID& uuid, + const std::string& name, + U32 flags) : + LLTaskInvFVBridge(panel, uuid, name, flags) {} + + virtual LLUIImagePtr getIcon() const; +}; + +LLUIImagePtr LLTaskWearableBridge::getIcon() const +{ + return LLInventoryIcon::getIcon(mAssetType, mInventoryType, mFlags, false ); +} + +///---------------------------------------------------------------------------- +/// Class LLTaskSettingsBridge +///---------------------------------------------------------------------------- + +class LLTaskSettingsBridge : public LLTaskInvFVBridge +{ +public: + LLTaskSettingsBridge(LLPanelObjectInventory* panel, + const LLUUID& uuid, + const std::string& name, + U32 flags) : + LLTaskInvFVBridge(panel, uuid, name, flags) {} + + virtual LLUIImagePtr getIcon() const; + virtual LLSettingsType::type_e getSettingsType() const; +}; + +LLUIImagePtr LLTaskSettingsBridge::getIcon() const +{ + return LLInventoryIcon::getIcon(mAssetType, mInventoryType, mFlags, false); +} + +LLSettingsType::type_e LLTaskSettingsBridge::getSettingsType() const +{ + return LLSettingsType::ST_NONE; +} + +///---------------------------------------------------------------------------- +/// Class LLTaskMaterialBridge +///---------------------------------------------------------------------------- + +class LLTaskMaterialBridge : public LLTaskInvFVBridge +{ +public: + LLTaskMaterialBridge(LLPanelObjectInventory* panel, + const LLUUID& uuid, + const std::string& name) : + LLTaskInvFVBridge(panel, uuid, name) {} + + bool canOpenItem() const override { return true; } + void openItem() override; + bool removeItem() override; +}; + +void LLTaskMaterialBridge::openItem() +{ + LLViewerObject* object = gObjectList.findObject(mPanel->getTaskUUID()); + if(!object || object->isInventoryPending()) + { + return; + } + + // Note: even if we are not allowed to modify copyable notecard, we should be able to view it + LLInventoryItem *item = dynamic_cast(object->getInventoryObject(mUUID)); + bool item_copy = item && gAgent.allowOperation(PERM_COPY, item->getPermissions(), GP_OBJECT_MANIPULATE); + if( item_copy + || object->permModify() + || gAgent.isGodlike()) + { + LLSD floater_key; + floater_key["taskid"] = mPanel->getTaskUUID(); + floater_key["itemid"] = mUUID; + LLMaterialEditor* mat = LLFloaterReg::getTypedInstance("material_editor", floater_key); + if (mat) + { + mat->setObjectID(mPanel->getTaskUUID()); + mat->openFloater(floater_key); + mat->setFocus(true); + } + } +} + +bool LLTaskMaterialBridge::removeItem() +{ + LLFloaterReg::hideInstance("material_editor", LLSD(mUUID)); + return LLTaskInvFVBridge::removeItem(); +} + + +///---------------------------------------------------------------------------- +/// LLTaskInvFVBridge impl +//---------------------------------------------------------------------------- + +LLTaskInvFVBridge* LLTaskInvFVBridge::createObjectBridge(LLPanelObjectInventory* panel, + LLInventoryObject* object) +{ + LLTaskInvFVBridge* new_bridge = NULL; + const LLInventoryItem* item = dynamic_cast(object); + const U32 itemflags = ( NULL == item ? 0 : item->getFlags() ); + LLAssetType::EType type = object ? object->getType() : LLAssetType::AT_CATEGORY; + LLUUID object_id = object ? object->getUUID() : LLUUID::null; + std::string object_name = object ? object->getName() : std::string(); + + switch(type) + { + case LLAssetType::AT_TEXTURE: + new_bridge = new LLTaskTextureBridge(panel, + object_id, + object_name); + break; + case LLAssetType::AT_SOUND: + new_bridge = new LLTaskSoundBridge(panel, + object_id, + object_name); + break; + case LLAssetType::AT_LANDMARK: + new_bridge = new LLTaskLandmarkBridge(panel, + object_id, + object_name); + break; + case LLAssetType::AT_CALLINGCARD: + new_bridge = new LLTaskCallingCardBridge(panel, + object_id, + object_name); + break; + case LLAssetType::AT_SCRIPT: + // OLD SCRIPTS DEPRECATED - JC + LL_WARNS() << "Old script" << LL_ENDL; + //new_bridge = new LLTaskOldScriptBridge(panel, + // object_id, + // object_name); + break; + case LLAssetType::AT_OBJECT: + new_bridge = new LLTaskObjectBridge(panel, + object_id, + object_name, + itemflags); + break; + case LLAssetType::AT_NOTECARD: + new_bridge = new LLTaskNotecardBridge(panel, + object_id, + object_name); + break; + case LLAssetType::AT_ANIMATION: + new_bridge = new LLTaskAnimationBridge(panel, + object_id, + object_name); + break; + case LLAssetType::AT_GESTURE: + new_bridge = new LLTaskGestureBridge(panel, + object_id, + object_name); + break; + case LLAssetType::AT_CLOTHING: + case LLAssetType::AT_BODYPART: + new_bridge = new LLTaskWearableBridge(panel, + object_id, + object_name, + itemflags); + break; + case LLAssetType::AT_CATEGORY: + new_bridge = new LLTaskCategoryBridge(panel, + object_id, + object_name); + break; + case LLAssetType::AT_LSL_TEXT: + new_bridge = new LLTaskLSLBridge(panel, + object_id, + object_name); + break; + case LLAssetType::AT_SETTINGS: + new_bridge = new LLTaskSettingsBridge(panel, + object_id, + object_name, + itemflags); + break; + case LLAssetType::AT_MATERIAL: + new_bridge = new LLTaskMaterialBridge(panel, + object_id, + object_name); + break; + default: + LL_INFOS() << "Unhandled inventory type (llassetstorage.h): " + << (S32)type << LL_ENDL; + break; + } + return new_bridge; +} + + +///---------------------------------------------------------------------------- +/// Class LLPanelObjectInventory +///---------------------------------------------------------------------------- + +static LLDefaultChildRegistry::Register r("panel_inventory_object"); + +void do_nothing() +{ +} + +// Default constructor +LLPanelObjectInventory::LLPanelObjectInventory(const LLPanelObjectInventory::Params& p) : + LLPanel(p), + mScroller(NULL), + mFolders(NULL), + mHaveInventory(false), + mIsInventoryEmpty(true), + mInventoryNeedsUpdate(false), + mInventoryViewModel(p.name), + mShowRootFolder(p.show_root_folder) +{ + // Setup context menu callbacks + mCommitCallbackRegistrar.add("Inventory.DoToSelected", boost::bind(&LLPanelObjectInventory::doToSelected, this, _2)); + mCommitCallbackRegistrar.add("Inventory.EmptyTrash", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyTrash", LLFolderType::FT_TRASH)); + mCommitCallbackRegistrar.add("Inventory.EmptyLostAndFound", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyLostAndFound", LLFolderType::FT_LOST_AND_FOUND)); + mCommitCallbackRegistrar.add("Inventory.DoCreate", boost::bind(&do_nothing)); + mCommitCallbackRegistrar.add("Inventory.AttachObject", boost::bind(&do_nothing)); + mCommitCallbackRegistrar.add("Inventory.BeginIMSession", boost::bind(&do_nothing)); + mCommitCallbackRegistrar.add("Inventory.Share", boost::bind(&LLAvatarActions::shareWithAvatars, this)); + mCommitCallbackRegistrar.add("Inventory.FileUploadLocation", boost::bind(&do_nothing)); +} + +// Destroys the object +LLPanelObjectInventory::~LLPanelObjectInventory() +{ + if (!gIdleCallbacks.deleteFunction(idle, this)) + { + LL_WARNS() << "LLPanelObjectInventory::~LLPanelObjectInventory() failed to delete callback" << LL_ENDL; + } +} + +bool LLPanelObjectInventory::postBuild() +{ + // clear contents and initialize menus, sets up mFolders + reset(); + + // Register an idle update callback + gIdleCallbacks.addFunction(idle, this); + + return true; +} + +void LLPanelObjectInventory::doToSelected(const LLSD& userdata) +{ + LLInventoryAction::doToSelected(&gInventory, mFolders, userdata.asString()); +} + +void LLPanelObjectInventory::clearContents() +{ + mHaveInventory = false; + mIsInventoryEmpty = true; + if (LLToolDragAndDrop::getInstance() && LLToolDragAndDrop::getInstance()->getSource() == LLToolDragAndDrop::SOURCE_WORLD) + { + LLToolDragAndDrop::getInstance()->endDrag(); + } + + clearItemIDs(); + + if( mScroller ) + { + // removes mFolders + removeChild( mScroller ); //*TODO: Really shouldn't do this during draw()/refresh() + mScroller->die(); + mScroller = NULL; + mFolders = NULL; + } +} + + +void LLPanelObjectInventory::reset() +{ + clearContents(); + + mCommitCallbackRegistrar.pushScope(); // push local callbacks + + // Reset the inventory model to show all folders by default + mInventoryViewModel.getFilter().setShowFolderState(LLInventoryFilter::SHOW_ALL_FOLDERS); + + // Create a new folder view root + LLRect dummy_rect(0, 1, 1, 0); + LLFolderView::Params p; + p.name = "task inventory"; + p.title = "task inventory"; + p.parent_panel = this; + p.tool_tip= LLTrans::getString("PanelContentsTooltip"); + p.listener = LLTaskInvFVBridge::createObjectBridge(this, NULL); + p.folder_indentation = -14; // subtract space normally reserved for folder expanders + p.view_model = &mInventoryViewModel; + p.root = NULL; + p.options_menu = "menu_inventory.xml"; + + mFolders = LLUICtrlFactory::create(p); + + mFolders->setCallbackRegistrar(&mCommitCallbackRegistrar); + mFolders->setEnableRegistrar(&mEnableCallbackRegistrar); + + if (hasFocus()) + { + LLEditMenuHandler::gEditMenuHandler = mFolders; + } + + int offset = hasBorder() ? getBorder()->getBorderWidth() << 1 : 0; + LLRect scroller_rect(0, getRect().getHeight() - offset, getRect().getWidth() - offset, 0); + LLScrollContainer::Params scroll_p; + scroll_p.name("task inventory scroller"); + scroll_p.rect(scroller_rect); + scroll_p.tab_stop(true); + scroll_p.follows.flags(FOLLOWS_ALL); + mScroller = LLUICtrlFactory::create(scroll_p); + addChild(mScroller); + mScroller->addChild(mFolders); + + mFolders->setScrollContainer( mScroller ); + + mCommitCallbackRegistrar.popScope(); +} + +void LLPanelObjectInventory::inventoryChanged(LLViewerObject* object, + LLInventoryObject::object_list_t* inventory, + S32 serial_num, + void* data) +{ + if(!object) return; + + //LL_INFOS() << "invetnory arrived: \n" + // << " panel UUID: " << panel->mTaskUUID << "\n" + // << " task UUID: " << object->mID << LL_ENDL; + if(mTaskUUID == object->mID) + { + mInventoryNeedsUpdate = true; + } +} + +void LLPanelObjectInventory::updateInventory() +{ + //LL_INFOS() << "inventory arrived: \n" + // << " panel UUID: " << panel->mTaskUUID << "\n" + // << " task UUID: " << object->mID << LL_ENDL; + // We're still interested in this task's inventory. + std::vector selected_item_ids; + std::set selected_items; + bool inventory_has_focus = false; + if (mHaveInventory && mFolders) + { + selected_items = mFolders->getSelectionList(); + inventory_has_focus = gFocusMgr.childHasKeyboardFocus(mFolders); + } + for (std::set::iterator it = selected_items.begin(), end_it = selected_items.end(); + it != end_it; + ++it) + { + selected_item_ids.push_back(static_cast((*it)->getViewModelItem())->getUUID()); + } + + LLViewerObject* objectp = gObjectList.findObject(mTaskUUID); + if (objectp) + { + LLInventoryObject* inventory_root = objectp->getInventoryRoot(); + LLInventoryObject::object_list_t contents; + objectp->getInventoryContents(contents); + + if (inventory_root) + { + reset(); + mIsInventoryEmpty = false; + createFolderViews(inventory_root, contents); + mFolders->setEnabled(true); + } + else + { + // TODO: create an empty inventory + mIsInventoryEmpty = true; + } + + mHaveInventory = !mIsInventoryEmpty || !objectp->isInventoryDirty(); + if (objectp->isInventoryDirty()) + { + // Inventory is dirty, yet we received inventoryChanged() callback. + // User changed something during ongoing request. + // Rerequest. It will clear dirty flag and won't create dupplicate requests. + objectp->requestInventory(); + } + } + else + { + // TODO: create an empty inventory + mIsInventoryEmpty = true; + mHaveInventory = true; + } + + // restore previous selection + std::vector::iterator selection_it; + bool first_item = true; + for (selection_it = selected_item_ids.begin(); selection_it != selected_item_ids.end(); ++selection_it) + { + LLFolderViewItem* selected_item = getItemByID(*selection_it); + + if (selected_item) + { + //HACK: "set" first item then "change" each other one to get keyboard focus right + if (first_item) + { + mFolders->setSelection(selected_item, true, inventory_has_focus); + first_item = false; + } + else + { + mFolders->changeSelection(selected_item, true); + } + } + } + + if (mFolders) + { + mFolders->requestArrange(); + } + mInventoryNeedsUpdate = false; + // Edit menu handler is set in onFocusReceived +} + +// *FIX: This is currently a very expensive operation, because we have +// to iterate through the inventory one time for each category. This +// leads to an N^2 based on the category count. This could be greatly +// speeded with an efficient multimap implementation, but we don't +// have that in our current arsenal. +void LLPanelObjectInventory::createFolderViews(LLInventoryObject* inventory_root, LLInventoryObject::object_list_t& contents) +{ + if (!inventory_root) + { + return; + } + // Create a visible root category. + LLTaskInvFVBridge* bridge = NULL; + bridge = LLTaskInvFVBridge::createObjectBridge(this, inventory_root); + if(bridge) + { + LLUIColor item_color = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE); + + LLFolderViewFolder::Params p; + p.name = inventory_root->getName(); + p.tool_tip = p.name; + p.root = mFolders; + p.listener = bridge; + p.font_color = item_color; + p.font_highlight_color = item_color; + + LLFolderViewFolder* new_folder = LLUICtrlFactory::create(p); + + if (mShowRootFolder) + { + new_folder->addToFolder(mFolders); + new_folder->toggleOpen(); + } + + if (!contents.empty()) + { + createViewsForCategory(&contents, inventory_root, mShowRootFolder ? new_folder : mFolders); + } + + if (mShowRootFolder) + { + // Refresh for label to add item count + new_folder->refresh(); + } + } +} + +typedef std::pair obj_folder_pair; + +void LLPanelObjectInventory::createViewsForCategory(LLInventoryObject::object_list_t* inventory, + LLInventoryObject* parent, + LLFolderViewFolder* folder) +{ + LLUIColor item_color = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE); + + // Find all in the first pass + std::vector child_categories; + LLTaskInvFVBridge* bridge; + LLFolderViewItem* view; + + LLInventoryObject::object_list_t::iterator it = inventory->begin(); + LLInventoryObject::object_list_t::iterator end = inventory->end(); + for( ; it != end; ++it) + { + LLInventoryObject* obj = *it; + + if(parent->getUUID() == obj->getParentUUID()) + { + bridge = LLTaskInvFVBridge::createObjectBridge(this, obj); + if(!bridge) + { + continue; + } + if(LLAssetType::AT_CATEGORY == obj->getType()) + { + LLFolderViewFolder::Params p; + p.name = obj->getName(); + p.root = mFolders; + p.listener = bridge; + p.tool_tip = p.name; + p.font_color = item_color; + p.font_highlight_color = item_color; + view = LLUICtrlFactory::create(p); + child_categories.push_back(new obj_folder_pair(obj, + (LLFolderViewFolder*)view)); + } + else + { + LLFolderViewItem::Params params; + params.name(obj->getName()); + params.creation_date(bridge->getCreationDate()); + params.root(mFolders); + params.listener(bridge); + params.rect(LLRect()); + params.tool_tip = params.name; + params.font_color = item_color; + params.font_highlight_color = item_color; + view = LLUICtrlFactory::create (params); + } + view->addToFolder(folder); + addItemID(obj->getUUID(), view); + } + } + + // now, for each category, do the second pass + for(S32 i = 0; i < child_categories.size(); i++) + { + createViewsForCategory(inventory, child_categories[i]->first, + child_categories[i]->second ); + delete child_categories[i]; + } + folder->setChildrenInited(true); +} + +void LLPanelObjectInventory::refresh() +{ + //LL_INFOS() << "LLPanelObjectInventory::refresh()" << LL_ENDL; + bool has_inventory = false; + const bool non_root_ok = true; + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + LLSelectNode* node = selection->getFirstRootNode(NULL, non_root_ok); + if(node && node->mValid) + { + LLViewerObject* object = node->getObject(); + if(object && ((selection->getRootObjectCount() == 1) + || (selection->getObjectCount() == 1))) + { + // determine if we need to make a request. Start with a + // default based on if we have inventory at all. + bool make_request = !mHaveInventory; + + // If the task id is different than what we've stored, + // then make the request. + if(mTaskUUID != object->mID) + { + mTaskUUID = object->mID; + mAttachmentUUID = object->getAttachmentItemID(); + make_request = true; + + // This is a new object so pre-emptively clear the contents + // Otherwise we show the old stuff until the update comes in + clearContents(); + + // Register for updates from this object, + registerVOInventoryListener(object,NULL); + } + else if (mAttachmentUUID != object->getAttachmentItemID()) + { + mAttachmentUUID = object->getAttachmentItemID(); + if (mAttachmentUUID.notNull()) + { + // Server unsubsribes viewer (deselects object) from property + // updates after "ObjectAttach" so we need to resubscribe + LLSelectMgr::getInstance()->sendSelect(); + } + } + + // Based on the node information, we may need to dirty the + // object inventory and get it again. + if(node->mValid) + { + if(node->mInventorySerial != object->getInventorySerial() || object->isInventoryDirty()) + { + make_request = true; + } + } + + // do the request if necessary. + if(make_request) + { + requestVOInventory(); + } + has_inventory = true; + } + } + if(!has_inventory) + { + clearInventoryTask(); + } + mInventoryViewModel.setTaskID(mTaskUUID); + //LL_INFOS() << "LLPanelObjectInventory::refresh() " << mTaskUUID << LL_ENDL; +} + +void LLPanelObjectInventory::clearInventoryTask() +{ + mTaskUUID = LLUUID::null; + mAttachmentUUID = LLUUID::null; + removeVOInventoryListener(); + clearContents(); +} + +void LLPanelObjectInventory::removeSelectedItem() +{ + if(mFolders) + { + mFolders->removeSelectedItems(); + } +} + +void LLPanelObjectInventory::startRenamingSelectedItem() +{ + if(mFolders) + { + mFolders->startRenamingSelectedItem(); + } +} + +void LLPanelObjectInventory::draw() +{ + LLPanel::draw(); + + if(mIsInventoryEmpty) + { + if((LLUUID::null != mTaskUUID) && (!mHaveInventory)) + { + LLFontGL::getFontSansSerif()->renderUTF8(LLTrans::getString("LoadingContents"), 0, + (S32)(getRect().getWidth() * 0.5f), + 10, + LLColor4( 1, 1, 1, 1 ), + LLFontGL::HCENTER, + LLFontGL::BOTTOM); + } + else if(mHaveInventory) + { + LLFontGL::getFontSansSerif()->renderUTF8(LLTrans::getString("NoContents"), 0, + (S32)(getRect().getWidth() * 0.5f), + 10, + LLColor4( 1, 1, 1, 1 ), + LLFontGL::HCENTER, + LLFontGL::BOTTOM); + } + } +} + +void LLPanelObjectInventory::deleteAllChildren() +{ + mScroller = NULL; + mFolders = NULL; + LLView::deleteAllChildren(); +} + +bool LLPanelObjectInventory::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, void *cargo_data, EAcceptance *accept, std::string& tooltip_msg) +{ + if (mFolders) + { + LLFolderViewItem* folderp = mFolders->getNextFromChild(NULL); + if (!folderp) + { + return false; + } + // Try to pass on unmodified mouse coordinates + S32 local_x = x - mFolders->getRect().mLeft; + S32 local_y = y - mFolders->getRect().mBottom; + + if (mFolders->pointInView(local_x, local_y)) + { + return mFolders->handleDragAndDrop(local_x, local_y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); + } + else + { + //force mouse coordinates to be inside folder rectangle + return mFolders->handleDragAndDrop(5, 1, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); + } + } + else + { + return false; + } +} + +//static +void LLPanelObjectInventory::idle(void* user_data) +{ + LLPanelObjectInventory* self = (LLPanelObjectInventory*)user_data; + + if (self->mFolders) + { + self->mFolders->update(); + } + if (self->mInventoryNeedsUpdate) + { + self->updateInventory(); + } +} + +void LLPanelObjectInventory::onFocusLost() +{ + // inventory no longer handles cut/copy/paste/delete + if (LLEditMenuHandler::gEditMenuHandler == mFolders) + { + LLEditMenuHandler::gEditMenuHandler = NULL; + } + + LLPanel::onFocusLost(); +} + +void LLPanelObjectInventory::onFocusReceived() +{ + // inventory now handles cut/copy/paste/delete + LLEditMenuHandler::gEditMenuHandler = mFolders; + + LLPanel::onFocusReceived(); +} + + +LLFolderViewItem* LLPanelObjectInventory::getItemByID( const LLUUID& id ) +{ + std::map::iterator map_it; + map_it = mItemMap.find(id); + if (map_it != mItemMap.end()) + { + return map_it->second; + } + + return NULL; +} + +void LLPanelObjectInventory::removeItemID( const LLUUID& id ) +{ + mItemMap.erase(id); +} + +void LLPanelObjectInventory::addItemID( const LLUUID& id, LLFolderViewItem* itemp ) +{ + mItemMap[id] = itemp; +} + +void LLPanelObjectInventory::clearItemIDs() +{ + mItemMap.clear(); +} + +bool LLPanelObjectInventory::handleKeyHere( KEY key, MASK mask ) +{ + bool handled = false; + switch (key) + { + case KEY_DELETE: +#if LL_DARWIN + case KEY_BACKSPACE: +#endif + // Delete selected items if delete or backspace key hit on the inventory panel + // Note: on Mac laptop keyboards, backspace and delete are one and the same + if (isSelectionRemovable() && mask == MASK_NONE) + { + LLInventoryAction::doToSelected(&gInventory, mFolders, "delete"); + handled = true; + } + break; + } + return handled; +} + +bool LLPanelObjectInventory::isSelectionRemovable() +{ + if (!mFolders || !mFolders->getRoot()) + { + return false; + } + std::set selection_set = mFolders->getRoot()->getSelectionList(); + if (selection_set.empty()) + { + return false; + } + for (std::set::iterator iter = selection_set.begin(); + iter != selection_set.end(); + ++iter) + { + LLFolderViewItem *item = *iter; + const LLFolderViewModelItemInventory *listener = dynamic_cast(item->getViewModelItem()); + if (!listener || !listener->isItemRemovable() || listener->isItemInTrash()) + { + return false; + } + } + return true; +} diff --git a/indra/newview/llpanelobjectinventory.h b/indra/newview/llpanelobjectinventory.h index 00773e3ef8..c150a841ae 100644 --- a/indra/newview/llpanelobjectinventory.h +++ b/indra/newview/llpanelobjectinventory.h @@ -1,122 +1,122 @@ -/** - * @file llpanelobjectinventory.h - * @brief LLPanelObjectInventory class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELOBJECTINVENTORY_H -#define LL_LLPANELOBJECTINVENTORY_H - -#include "llvoinventorylistener.h" -#include "llpanel.h" -#include "llinventorypanel.h" // for LLFolderViewModelInventory - -#include "llinventory.h" - -class LLScrollContainer; -class LLFolderView; -class LLFolderViewFolder; -class LLViewerObject; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLPanelObjectInventory -// -// This class represents the panel used to view and control a -// particular task's inventory. -// -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLPanelObjectInventory : public LLPanel, public LLVOInventoryListener -{ -public: - struct Params : public LLInitParam::Block - { - Optional show_root_folder; - - Params() - : show_root_folder("show_root_folder", true) - {} - }; - - LLPanelObjectInventory(const Params&); - virtual ~LLPanelObjectInventory(); - - virtual bool postBuild(); - - LLFolderViewModelInventory& getRootViewModel() { return mInventoryViewModel; } - - void doToSelected(const LLSD& userdata); - - void refresh(); - const LLUUID& getTaskUUID() { return mTaskUUID;} - void clearInventoryTask(); - void removeSelectedItem(); - void startRenamingSelectedItem(); - - LLFolderView* getRootFolder() const { return mFolders; } - - virtual void draw(); - virtual void deleteAllChildren(); - virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, void *cargo_data, EAcceptance *accept, std::string& tooltip_msg); - - /*virtual*/ void onFocusLost(); - /*virtual*/ void onFocusReceived(); - - static void idle(void* user_data); - -protected: - void reset(); - /*virtual*/ void inventoryChanged(LLViewerObject* object, - LLInventoryObject::object_list_t* inventory, - S32 serial_num, - void* user_data); - void updateInventory(); - void createFolderViews(LLInventoryObject* inventory_root, LLInventoryObject::object_list_t& contents); - void createViewsForCategory(LLInventoryObject::object_list_t* inventory, - LLInventoryObject* parent, - LLFolderViewFolder* folder); - void clearContents(); - LLFolderViewItem* getItemByID(const LLUUID& id); - - void addItemID( const LLUUID& id, LLFolderViewItem* itemp ); - void removeItemID(const LLUUID& id); - void clearItemIDs(); - - bool handleKeyHere( KEY key, MASK mask ); - bool isSelectionRemovable(); - -private: - std::map mItemMap; - - LLScrollContainer* mScroller; - LLFolderView* mFolders; - - LLUUID mTaskUUID; - LLUUID mAttachmentUUID; - bool mHaveInventory; // 'Loading' label and used for initial request - bool mIsInventoryEmpty; // 'Empty' label - bool mInventoryNeedsUpdate; // for idle, set on changed callback - LLFolderViewModelInventory mInventoryViewModel; - bool mShowRootFolder; -}; - -#endif // LL_LLPANELOBJECTINVENTORY_H +/** + * @file llpanelobjectinventory.h + * @brief LLPanelObjectInventory class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELOBJECTINVENTORY_H +#define LL_LLPANELOBJECTINVENTORY_H + +#include "llvoinventorylistener.h" +#include "llpanel.h" +#include "llinventorypanel.h" // for LLFolderViewModelInventory + +#include "llinventory.h" + +class LLScrollContainer; +class LLFolderView; +class LLFolderViewFolder; +class LLViewerObject; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLPanelObjectInventory +// +// This class represents the panel used to view and control a +// particular task's inventory. +// +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLPanelObjectInventory : public LLPanel, public LLVOInventoryListener +{ +public: + struct Params : public LLInitParam::Block + { + Optional show_root_folder; + + Params() + : show_root_folder("show_root_folder", true) + {} + }; + + LLPanelObjectInventory(const Params&); + virtual ~LLPanelObjectInventory(); + + virtual bool postBuild(); + + LLFolderViewModelInventory& getRootViewModel() { return mInventoryViewModel; } + + void doToSelected(const LLSD& userdata); + + void refresh(); + const LLUUID& getTaskUUID() { return mTaskUUID;} + void clearInventoryTask(); + void removeSelectedItem(); + void startRenamingSelectedItem(); + + LLFolderView* getRootFolder() const { return mFolders; } + + virtual void draw(); + virtual void deleteAllChildren(); + virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, void *cargo_data, EAcceptance *accept, std::string& tooltip_msg); + + /*virtual*/ void onFocusLost(); + /*virtual*/ void onFocusReceived(); + + static void idle(void* user_data); + +protected: + void reset(); + /*virtual*/ void inventoryChanged(LLViewerObject* object, + LLInventoryObject::object_list_t* inventory, + S32 serial_num, + void* user_data); + void updateInventory(); + void createFolderViews(LLInventoryObject* inventory_root, LLInventoryObject::object_list_t& contents); + void createViewsForCategory(LLInventoryObject::object_list_t* inventory, + LLInventoryObject* parent, + LLFolderViewFolder* folder); + void clearContents(); + LLFolderViewItem* getItemByID(const LLUUID& id); + + void addItemID( const LLUUID& id, LLFolderViewItem* itemp ); + void removeItemID(const LLUUID& id); + void clearItemIDs(); + + bool handleKeyHere( KEY key, MASK mask ); + bool isSelectionRemovable(); + +private: + std::map mItemMap; + + LLScrollContainer* mScroller; + LLFolderView* mFolders; + + LLUUID mTaskUUID; + LLUUID mAttachmentUUID; + bool mHaveInventory; // 'Loading' label and used for initial request + bool mIsInventoryEmpty; // 'Empty' label + bool mInventoryNeedsUpdate; // for idle, set on changed callback + LLFolderViewModelInventory mInventoryViewModel; + bool mShowRootFolder; +}; + +#endif // LL_LLPANELOBJECTINVENTORY_H diff --git a/indra/newview/llpaneloutfitedit.cpp b/indra/newview/llpaneloutfitedit.cpp index 9687c739fa..ce545ae21d 100644 --- a/indra/newview/llpaneloutfitedit.cpp +++ b/indra/newview/llpaneloutfitedit.cpp @@ -1,1449 +1,1449 @@ -/** - * @file llpaneloutfitedit.cpp - * @brief Displays outfit edit information in Side Tray. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpaneloutfitedit.h" - -// *TODO: reorder includes to match the coding standard -#include "llagent.h" -#include "llagentcamera.h" -#include "llagentwearables.h" -#include "llappearancemgr.h" -#include "lloutfitobserver.h" -#include "llcofwearables.h" -#include "llfilteredwearablelist.h" -#include "llfolderview.h" -#include "llinventory.h" -#include "llinventoryitemslist.h" -#include "llviewercontrol.h" -#include "llui.h" -#include "llfloater.h" -#include "llfloaterreg.h" -#include "llinventoryfunctions.h" -#include "llinventorypanel.h" -#include "llviewermenu.h" -#include "llviewerwindow.h" -#include "llviewerinventory.h" -#include "llbutton.h" -#include "llcombobox.h" -#include "llfiltereditor.h" -#include "llinventorybridge.h" -#include "llinventorymodel.h" -#include "llinventorymodelbackgroundfetch.h" -#include "llloadingindicator.h" -#include "llmenubutton.h" -#include "llpaneloutfitsinventory.h" -#include "lluiconstants.h" -#include "llscrolllistctrl.h" -#include "lltextbox.h" -#include "lltoggleablemenu.h" -#include "lltrans.h" -#include "lluictrlfactory.h" -#include "llsdutil.h" -#include "llsidepanelappearance.h" -#include "lltoggleablemenu.h" -#include "llvoavatarself.h" -#include "llwearablelist.h" -#include "llwearableitemslist.h" -#include "llwearabletype.h" -#include "llweb.h" - -static LLPanelInjector t_outfit_edit("panel_outfit_edit"); - -const U64 WEARABLE_MASK = (1LL << LLInventoryType::IT_WEARABLE); -const U64 ATTACHMENT_MASK = (1LL << LLInventoryType::IT_ATTACHMENT) | (1LL << LLInventoryType::IT_OBJECT); -const U64 ALL_ITEMS_MASK = WEARABLE_MASK | ATTACHMENT_MASK; - -static const std::string REVERT_BTN("revert_btn"); -static const std::string SAVE_AS_BTN("save_as_btn"); -static const std::string SAVE_BTN("save_btn"); - - -/////////////////////////////////////////////////////////////////////////////// -// LLShopURLDispatcher -/////////////////////////////////////////////////////////////////////////////// - -class LLShopURLDispatcher -{ -public: - std::string resolveURL(LLWearableType::EType wearable_type, ESex sex); - std::string resolveURL(LLAssetType::EType asset_type, ESex sex); -}; - -std::string LLShopURLDispatcher::resolveURL(LLWearableType::EType wearable_type, ESex sex) -{ - const std::string prefix = "MarketplaceURL"; - const std::string sex_str = (sex == SEX_MALE) ? "Male" : "Female"; - const std::string type_str = LLWearableType::getInstance()->getTypeName(wearable_type); - - std::string setting_name = prefix; - - switch (wearable_type) - { - case LLWearableType::WT_ALPHA: - case LLWearableType::WT_NONE: - case LLWearableType::WT_INVALID: // just in case, this shouldn't happen - case LLWearableType::WT_COUNT: // just in case, this shouldn't happen - break; - - default: - setting_name += '_'; - setting_name += type_str; - setting_name += sex_str; - break; - } - - return gSavedSettings.getString(setting_name); -} - -std::string LLShopURLDispatcher::resolveURL(LLAssetType::EType asset_type, ESex sex) -{ - const std::string prefix = "MarketplaceURL"; - const std::string sex_str = (sex == SEX_MALE) ? "Male" : "Female"; - const std::string type_str = LLAssetType::lookup(asset_type); - - std::string setting_name = prefix; - - switch (asset_type) - { - case LLAssetType::AT_CLOTHING: - case LLAssetType::AT_OBJECT: - case LLAssetType::AT_BODYPART: - setting_name += '_'; - setting_name += type_str; - setting_name += sex_str; - break; - - // to suppress warnings - default: - break; - } - - return gSavedSettings.getString(setting_name); -} - -/////////////////////////////////////////////////////////////////////////////// -// LLPanelOutfitEditGearMenu -/////////////////////////////////////////////////////////////////////////////// - -class LLPanelOutfitEditGearMenu -{ -public: - static LLToggleableMenu* create() - { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - - registrar.add("Wearable.Create", boost::bind(onCreate, _2)); - - llassert(LLMenuGL::sMenuContainer != NULL); - LLToggleableMenu* menu = LLUICtrlFactory::getInstance()->createFromFile( - "menu_cof_gear.xml", LLMenuGL::sMenuContainer, LLViewerMenuHolderGL::child_registry_t::instance()); - llassert(menu); - if (menu) - { - populateCreateWearableSubmenus(menu); - } - - return menu; - } - -private: - static void onCreate(const LLSD& param) - { - LLWearableType::EType type = LLWearableType::getInstance()->typeNameToType(param.asString()); - if (type == LLWearableType::WT_NONE) - { - LL_WARNS() << "Invalid wearable type" << LL_ENDL; - return; - } - - LLAgentWearables::createWearable(type, true); - } - - // Populate the menu with items like "New Skin", "New Pants", etc. - static void populateCreateWearableSubmenus(LLMenuGL* menu) - { - LLView* menu_clothes = gMenuHolder->getChildView("COF.Gear.New_Clothes", false); - LLView* menu_bp = gMenuHolder->getChildView("COF.Gear.New_Body_Parts", false); - LLWearableType * wearable_type_inst = LLWearableType::getInstance(); - - for (U8 i = LLWearableType::WT_SHAPE; i != (U8) LLWearableType::WT_COUNT; ++i) - { - LLWearableType::EType type = (LLWearableType::EType) i; - const std::string& type_name = wearable_type_inst->getTypeName(type); - - LLMenuItemCallGL::Params p; - p.name = type_name; - p.label = LLTrans::getString(wearable_type_inst->getTypeDefaultNewName(type)); - p.on_click.function_name = "Wearable.Create"; - p.on_click.parameter = LLSD(type_name); - - LLView* parent = wearable_type_inst->getAssetType(type) == LLAssetType::AT_CLOTHING ? menu_clothes : menu_bp; - LLUICtrlFactory::create(p, parent); - } - } -}; - -/////////////////////////////////////////////////////////////////////////////// -// LLAddWearablesGearMenu -/////////////////////////////////////////////////////////////////////////////// - -class LLAddWearablesGearMenu : public LLInitClass -{ -public: - static LLToggleableMenu* create(LLWearableItemsList* flat_list, LLInventoryPanel* inventory_panel) - { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; - - llassert(flat_list); - llassert(inventory_panel); - - LLHandle flat_list_handle = flat_list->getHandle(); - LLHandle inventory_panel_handle = inventory_panel->getHandle(); - - registrar.add("AddWearable.Gear.Sort", boost::bind(onSort, flat_list_handle, inventory_panel_handle, _2)); - enable_registrar.add("AddWearable.Gear.Check", boost::bind(onCheck, flat_list_handle, inventory_panel_handle, _2)); - enable_registrar.add("AddWearable.Gear.Visible", boost::bind(onVisible, inventory_panel_handle, _2)); - - llassert(LLMenuGL::sMenuContainer != NULL); - LLToggleableMenu* menu = LLUICtrlFactory::getInstance()->createFromFile( - "menu_add_wearable_gear.xml", - LLMenuGL::sMenuContainer, LLViewerMenuHolderGL::child_registry_t::instance()); - - return menu; - } - -private: - static void onSort(LLHandle flat_list_handle, - LLHandle inventory_panel_handle, - LLSD::String sort_order_str) - { - if (flat_list_handle.isDead() || inventory_panel_handle.isDead()) return; - - LLWearableItemsList* flat_list = dynamic_cast(flat_list_handle.get()); - LLInventoryPanel* inventory_panel = dynamic_cast(inventory_panel_handle.get()); - - if (!flat_list || !inventory_panel) return; - - LLWearableItemsList::ESortOrder sort_order; - - if ("by_most_recent" == sort_order_str) - { - sort_order = LLWearableItemsList::E_SORT_BY_MOST_RECENT; - } - else if ("by_name" == sort_order_str) - { - sort_order = LLWearableItemsList::E_SORT_BY_NAME; - } - else if ("by_type" == sort_order_str) - { - sort_order = LLWearableItemsList::E_SORT_BY_TYPE_NAME; - } - else - { - LL_WARNS() << "Unrecognized sort order action" << LL_ENDL; - return; - } - - if (inventory_panel->getVisible()) - { - inventory_panel->getFolderViewModel()->setSorter(sort_order); - } - else - { - flat_list->setSortOrder(sort_order); - } - } - - static bool onCheck(LLHandle flat_list_handle, - LLHandle inventory_panel_handle, - LLSD::String sort_order_str) - { - if (flat_list_handle.isDead() || inventory_panel_handle.isDead()) return false; - - LLWearableItemsList* flat_list = dynamic_cast(flat_list_handle.get()); - LLInventoryPanel* inventory_panel = dynamic_cast(inventory_panel_handle.get()); - - if (!inventory_panel || !flat_list) return false; - - // Inventory panel uses its own sort order independent from - // flat list view so this flag is used to distinguish between - // currently visible "tree" or "flat" representation of inventory. - bool inventory_tree_visible = inventory_panel->getVisible(); - - if (inventory_tree_visible) - { - U32 sort_order = inventory_panel->getSortOrder(); - - if ("by_most_recent" == sort_order_str) - { - return LLWearableItemsList::E_SORT_BY_MOST_RECENT & sort_order; - } - else if ("by_name" == sort_order_str) - { - // If inventory panel is not sorted by date then it is sorted by name. - return LLWearableItemsList::E_SORT_BY_MOST_RECENT & ~sort_order; - } - LL_WARNS() << "Unrecognized inventory panel sort order" << LL_ENDL; - } - else - { - LLWearableItemsList::ESortOrder sort_order = flat_list->getSortOrder(); - - if ("by_most_recent" == sort_order_str) - { - return LLWearableItemsList::E_SORT_BY_MOST_RECENT == sort_order; - } - else if ("by_name" == sort_order_str) - { - return LLWearableItemsList::E_SORT_BY_NAME == sort_order; - } - else if ("by_type" == sort_order_str) - { - return LLWearableItemsList::E_SORT_BY_TYPE_NAME == sort_order; - } - LL_WARNS() << "Unrecognized wearable list sort order" << LL_ENDL; - } - return false; - } - - static bool onVisible(LLHandle inventory_panel_handle, - LLSD::String sort_order_str) - { - if (inventory_panel_handle.isDead()) return false; - - LLInventoryPanel* inventory_panel = dynamic_cast(inventory_panel_handle.get()); - - // Enable sorting by type only for the flat list of items - // because inventory panel doesn't support this kind of sorting. - return ( "by_type" == sort_order_str ) - && ( !inventory_panel || !inventory_panel->getVisible() ); - } -}; - -/////////////////////////////////////////////////////////////////////////////// -// LLCOFDragAndDropObserver -/////////////////////////////////////////////////////////////////////////////// - -class LLCOFDragAndDropObserver : public LLInventoryAddItemByAssetObserver -{ -public: - LLCOFDragAndDropObserver(LLInventoryModel* model); - - virtual ~LLCOFDragAndDropObserver(); - - virtual void done(); - -private: - LLInventoryModel* mModel; -}; - -inline LLCOFDragAndDropObserver::LLCOFDragAndDropObserver(LLInventoryModel* model): - mModel(model) -{ - if (model != NULL) - { - model->addObserver(this); - } -} - -inline LLCOFDragAndDropObserver::~LLCOFDragAndDropObserver() -{ - if (mModel != NULL && mModel->containsObserver(this)) - { - mModel->removeObserver(this); - } -} - -void LLCOFDragAndDropObserver::done() -{ - LLAppearanceMgr::instance().updateAppearanceFromCOF(); -} - -/////////////////////////////////////////////////////////////////////////////// -// LLPanelOutfitEdit -/////////////////////////////////////////////////////////////////////////////// - -LLPanelOutfitEdit::LLPanelOutfitEdit() -: LLPanel(), - mSearchFilter(NULL), - mCOFWearables(NULL), - mInventoryItemsPanel(NULL), - mGearMenu(NULL), - mAddWearablesGearMenu(NULL), - mCOFDragAndDropObserver(NULL), - mInitialized(false), - mAddWearablesPanel(NULL), - mFolderViewFilterCmbBox(NULL), - mListViewFilterCmbBox(NULL), - mWearableListManager(NULL), - mPlusBtn(NULL), - mWearablesGearMenuBtn(NULL), - mGearMenuBtn(NULL) -{ - mSavedFolderState = new LLSaveFolderState(); - mSavedFolderState->setApply(false); - - - LLOutfitObserver& observer = LLOutfitObserver::instance(); - observer.addBOFReplacedCallback(boost::bind(&LLPanelOutfitEdit::updateCurrentOutfitName, this)); - observer.addBOFChangedCallback(boost::bind(&LLPanelOutfitEdit::updateVerbs, this)); - observer.addOutfitLockChangedCallback(boost::bind(&LLPanelOutfitEdit::updateVerbs, this)); - observer.addCOFChangedCallback(boost::bind(&LLPanelOutfitEdit::onCOFChanged, this)); - - gAgentWearables.addLoadingStartedCallback(boost::bind(&LLPanelOutfitEdit::onOutfitChanging, this, true)); - gAgentWearables.addLoadedCallback(boost::bind(&LLPanelOutfitEdit::onOutfitChanging, this, false)); - - mFolderViewItemTypes.reserve(NUM_FOLDER_VIEW_ITEM_TYPES); - for (U32 i = 0; i < NUM_FOLDER_VIEW_ITEM_TYPES; i++) - { - mFolderViewItemTypes.push_back(LLLookItemType()); - } - -} - -LLPanelOutfitEdit::~LLPanelOutfitEdit() -{ - delete mWearableListManager; - delete mSavedFolderState; - - delete mCOFDragAndDropObserver; - - while (!mListViewItemTypes.empty()) { - delete mListViewItemTypes.back(); - mListViewItemTypes.pop_back(); - } -} - -bool LLPanelOutfitEdit::postBuild() -{ - // gInventory.isInventoryUsable() no longer needs to be tested per Richard's fix for race conditions between inventory and panels - - mFolderViewItemTypes[FVIT_ALL] = LLLookItemType(getString("Filter.All"), ALL_ITEMS_MASK); - mFolderViewItemTypes[FVIT_WEARABLE] = LLLookItemType(getString("Filter.Clothes/Body"), WEARABLE_MASK); - mFolderViewItemTypes[FVIT_ATTACHMENT] = LLLookItemType(getString("Filter.Objects"), ATTACHMENT_MASK); - - //order is important, see EListViewItemType for order information - mListViewItemTypes.push_back(new LLFilterItem(getString("Filter.All"), new LLFindNonLinksByMask(ALL_ITEMS_MASK))); - mListViewItemTypes.push_back(new LLFilterItem(getString("Filter.Clothing"), new LLIsTypeActual(LLAssetType::AT_CLOTHING))); - mListViewItemTypes.push_back(new LLFilterItem(getString("Filter.Bodyparts"), new LLIsTypeActual(LLAssetType::AT_BODYPART))); - mListViewItemTypes.push_back(new LLFilterItem(getString("Filter.Objects"), new LLFindNonLinksByMask(ATTACHMENT_MASK)));; - mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("shape"), new LLFindActualWearablesOfType(LLWearableType::WT_SHAPE))); - mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("skin"), new LLFindActualWearablesOfType(LLWearableType::WT_SKIN))); - mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("hair"), new LLFindActualWearablesOfType(LLWearableType::WT_HAIR))); - mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("eyes"), new LLFindActualWearablesOfType(LLWearableType::WT_EYES))); - mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("shirt"), new LLFindActualWearablesOfType(LLWearableType::WT_SHIRT))); - mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("pants"), new LLFindActualWearablesOfType(LLWearableType::WT_PANTS))); - mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("shoes"), new LLFindActualWearablesOfType(LLWearableType::WT_SHOES))); - mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("socks"), new LLFindActualWearablesOfType(LLWearableType::WT_SOCKS))); - mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("jacket"), new LLFindActualWearablesOfType(LLWearableType::WT_JACKET))); - mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("gloves"), new LLFindActualWearablesOfType(LLWearableType::WT_GLOVES))); - mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("undershirt"), new LLFindActualWearablesOfType(LLWearableType::WT_UNDERSHIRT))); - mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("underpants"), new LLFindActualWearablesOfType(LLWearableType::WT_UNDERPANTS))); - mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("skirt"), new LLFindActualWearablesOfType(LLWearableType::WT_SKIRT))); - mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("alpha"), new LLFindActualWearablesOfType(LLWearableType::WT_ALPHA))); - mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("tattoo"), new LLFindActualWearablesOfType(LLWearableType::WT_TATTOO))); - mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("physics"), new LLFindActualWearablesOfType(LLWearableType::WT_PHYSICS))); - mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("universal"), new LLFindActualWearablesOfType(LLWearableType::WT_UNIVERSAL))); - - mCurrentOutfitName = getChild("curr_outfit_name"); - mStatus = getChild("status"); - - mFolderViewBtn = getChild("folder_view_btn"); - mListViewBtn = getChild("list_view_btn"); - - childSetCommitCallback("filter_button", boost::bind(&LLPanelOutfitEdit::showWearablesFilter, this), NULL); - childSetCommitCallback("folder_view_btn", boost::bind(&LLPanelOutfitEdit::showWearablesFolderView, this), NULL); - childSetCommitCallback("folder_view_btn", boost::bind(&LLPanelOutfitEdit::saveListSelection, this), NULL); - childSetCommitCallback("list_view_btn", boost::bind(&LLPanelOutfitEdit::showWearablesListView, this), NULL); - childSetCommitCallback("list_view_btn", boost::bind(&LLPanelOutfitEdit::saveListSelection, this), NULL); - childSetCommitCallback("shop_btn_1", boost::bind(&LLPanelOutfitEdit::onShopButtonClicked, this), NULL); - childSetCommitCallback("shop_btn_2", boost::bind(&LLPanelOutfitEdit::onShopButtonClicked, this), NULL); - - setVisibleCallback(boost::bind(&LLPanelOutfitEdit::onVisibilityChanged, this, _2)); - - mWearablesGearMenuBtn = getChild("wearables_gear_menu_btn"); - mGearMenuBtn = getChild("gear_menu_btn"); - - mCOFWearables = findChild("cof_wearables_list"); - mCOFWearables->setCommitCallback(boost::bind(&LLPanelOutfitEdit::filterWearablesBySelectedItem, this)); - - mCOFWearables->getCOFCallbacks().mAddWearable = boost::bind(&LLPanelOutfitEdit::onAddWearableClicked, this); - mCOFWearables->getCOFCallbacks().mEditWearable = boost::bind(&LLPanelOutfitEdit::onEditWearableClicked, this); - mCOFWearables->getCOFCallbacks().mDeleteWearable = boost::bind(&LLPanelOutfitEdit::onRemoveFromOutfitClicked, this); - mCOFWearables->getCOFCallbacks().mMoveWearableCloser = boost::bind(&LLPanelOutfitEdit::moveWearable, this, true); - mCOFWearables->getCOFCallbacks().mMoveWearableFurther = boost::bind(&LLPanelOutfitEdit::moveWearable, this, false); - - mAddWearablesPanel = getChild("add_wearables_panel"); - - mInventoryItemsPanel = getChild("folder_view"); - mInventoryItemsPanel->setFilterTypes(ALL_ITEMS_MASK); - mInventoryItemsPanel->setShowFolderState(LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); - mInventoryItemsPanel->setSelectCallback(boost::bind(&LLPanelOutfitEdit::updatePlusButton, this)); - mInventoryItemsPanel->getRootFolder()->setReshapeCallback(boost::bind(&LLPanelOutfitEdit::updatePlusButton, this)); - - mCOFDragAndDropObserver = new LLCOFDragAndDropObserver(mInventoryItemsPanel->getModel()); - - mFolderViewFilterCmbBox = getChild("folder_view_filter_combobox"); - mFolderViewFilterCmbBox->setCommitCallback(boost::bind(&LLPanelOutfitEdit::onFolderViewFilterCommitted, this, _1)); - mFolderViewFilterCmbBox->removeall(); - for (U32 i = 0; i < mFolderViewItemTypes.size(); ++i) - { - mFolderViewFilterCmbBox->add(mFolderViewItemTypes[i].displayName); - } - mFolderViewFilterCmbBox->setCurrentByIndex(FVIT_ALL); - - mListViewFilterCmbBox = getChild("list_view_filter_combobox"); - mListViewFilterCmbBox->setCommitCallback(boost::bind(&LLPanelOutfitEdit::onListViewFilterCommitted, this, _1)); - mListViewFilterCmbBox->removeall(); - for (U32 i = 0; i < mListViewItemTypes.size(); ++i) - { - mListViewFilterCmbBox->add(mListViewItemTypes[i]->displayName); - } - mListViewFilterCmbBox->setCurrentByIndex(LVIT_ALL); - - mSearchFilter = getChild("look_item_filter"); - mSearchFilter->setCommitCallback(boost::bind(&LLPanelOutfitEdit::onSearchEdit, this, _2)); - - childSetAction("show_add_wearables_btn", boost::bind(&LLPanelOutfitEdit::onAddMoreButtonClicked, this)); - - mPlusBtn = getChild("plus_btn"); - mPlusBtn->setClickedCallback(boost::bind(&LLPanelOutfitEdit::onPlusBtnClicked, this)); - - childSetAction(REVERT_BTN, boost::bind(&LLAppearanceMgr::wearBaseOutfit, LLAppearanceMgr::getInstance())); - - /* - * By default AT_CLOTHING are sorted by (in in MY OUTFITS): - * - by type (types order determined in LLWearableType::EType) - * - each LLWearableType::EType by outer layer on top - * - * In Add More panel AT_CLOTHING should be sorted in a such way: - * - by type (types order determined in LLWearableType::EType) - * - each LLWearableType::EType by name (EXT-8205) - */ - mWearableListViewItemsComparator = new LLWearableItemTypeNameComparator(); - mWearableListViewItemsComparator->setOrder(LLAssetType::AT_CLOTHING, LLWearableItemTypeNameComparator::ORDER_RANK_1, false, true); - - mWearablesListViewPanel = getChild("filtered_wearables_panel"); - mWearableItemsList = getChild("list_view"); - mWearableItemsList->setCommitOnSelectionChange(true); - mWearableItemsList->setCommitCallback(boost::bind(&LLPanelOutfitEdit::updatePlusButton, this)); - mWearableItemsList->setDoubleClickCallback(boost::bind(&LLPanelOutfitEdit::onPlusBtnClicked, this)); - - mWearableItemsList->setComparator(mWearableListViewItemsComparator); - - // Creating "Add Wearables" panel gear menu after initialization of mWearableItemsList and mInventoryItemsPanel. - mAddWearablesGearMenu = LLAddWearablesGearMenu::create(mWearableItemsList, mInventoryItemsPanel); - mWearablesGearMenuBtn->setMenu(mAddWearablesGearMenu); - - mGearMenu = LLPanelOutfitEditGearMenu::create(); - mGearMenuBtn->setMenu(mGearMenu); - - getChild(SAVE_BTN)->setCommitCallback(boost::bind(&LLPanelOutfitEdit::saveOutfit, this, false)); - getChild(SAVE_AS_BTN)->setCommitCallback(boost::bind(&LLPanelOutfitEdit::saveOutfit, this, true)); - - onOutfitChanging(gAgentWearables.isCOFChangeInProgress()); - return true; -} - -// virtual -void LLPanelOutfitEdit::onOpen(const LLSD& key) -{ - if (!mInitialized) - { - // *TODO: this method is called even panel is not visible to user because its parent layout panel is hidden. - // So, we can defer initializing a bit. - mWearableListManager = new LLFilteredWearableListManager(mWearableItemsList, mListViewItemTypes[LVIT_ALL]->collector); - displayCurrentOutfit(); - mInitialized = true; - } -} - -void LLPanelOutfitEdit::moveWearable(bool closer_to_body) -{ - LLUUID item_id = mCOFWearables->getSelectedUUID(); - if (item_id.isNull()) return; - - LLViewerInventoryItem* wearable_to_move = gInventory.getItem(item_id); - LLAppearanceMgr::getInstance()->moveWearable(wearable_to_move, closer_to_body); -} - -void LLPanelOutfitEdit::toggleAddWearablesPanel() -{ - bool current_visibility = mAddWearablesPanel->getVisible(); - showAddWearablesPanel(!current_visibility); -} - -void LLPanelOutfitEdit::showAddWearablesPanel(bool show_add_wearables) -{ - mAddWearablesPanel->setVisible(show_add_wearables); - - getChild("show_add_wearables_btn")->setValue(show_add_wearables); - - updateFiltersVisibility(); - getChildView("filter_button")->setVisible( show_add_wearables); - - //search filter should be disabled - if (!show_add_wearables) - { - getChild("filter_button")->setValue(false); - - mFolderViewFilterCmbBox->setVisible(false); - mListViewFilterCmbBox->setVisible(false); - - showWearablesFilter(); - - /* - * By default AT_CLOTHING are sorted by (in in MY OUTFITS): - * - by type (types order determined in LLWearableType::EType) - * - each LLWearableType::EType by outer layer on top - * - * In Add More panel AT_CLOTHING should be sorted in a such way: - * - by type (types order determined in LLWearableType::EType) - * - each LLWearableType::EType by name (EXT-8205) - */ - mWearableItemsList->setSortOrder(LLWearableItemsList::E_SORT_BY_TYPE_NAME); - - // Reset mWearableItemsList position to top. See EXT-8180. - mWearableItemsList->goToTop(); - } - else - { - mWearableListManager->populateIfNeeded(); - } - - //switching button bars - getChildView("no_add_wearables_button_bar")->setVisible( !show_add_wearables); - getChildView("add_wearables_button_bar")->setVisible( show_add_wearables); -} - -void LLPanelOutfitEdit::showWearablesFilter() -{ - bool filter_visible = getChild("filter_button")->getValue(); - - getChildView("filter_panel")->setVisible( filter_visible); - - if(!filter_visible) - { - mSearchFilter->clear(); - onSearchEdit(LLStringUtil::null); - } - else - { - mSearchFilter->setFocus(true); - } -} - -void LLPanelOutfitEdit::showWearablesListView() -{ - if(switchPanels(mInventoryItemsPanel, mWearablesListViewPanel)) - { - updateWearablesPanelVerbButtons(); - updateFiltersVisibility(); - mWearableListManager->populateIfNeeded(); - } - mListViewBtn->setToggleState(true); -} - -void LLPanelOutfitEdit::showWearablesFolderView() -{ - if(switchPanels(mWearablesListViewPanel, mInventoryItemsPanel)) - { - updateWearablesPanelVerbButtons(); - updateFiltersVisibility(); - } - mFolderViewBtn->setToggleState(true); -} - -void LLPanelOutfitEdit::updateFiltersVisibility() -{ - mListViewFilterCmbBox->setVisible(mWearablesListViewPanel->getVisible()); - mFolderViewFilterCmbBox->setVisible(mInventoryItemsPanel->getVisible()); -} - -void LLPanelOutfitEdit::onFolderViewFilterCommitted(LLUICtrl* ctrl) -{ - S32 curr_filter_type = mFolderViewFilterCmbBox->getCurrentIndex(); - if (curr_filter_type < 0) return; - - mInventoryItemsPanel->setFilterTypes(mFolderViewItemTypes[curr_filter_type].inventoryMask); - - mSavedFolderState->setApply(true); - mInventoryItemsPanel->getRootFolder()->applyFunctorRecursively(*mSavedFolderState); - - LLOpenFoldersWithSelection opener; - mInventoryItemsPanel->getRootFolder()->applyFunctorRecursively(opener); - mInventoryItemsPanel->getRootFolder()->scrollToShowSelection(); - - if (!LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) - { - llassert(false); // this should have been done on startup - LLInventoryModelBackgroundFetch::instance().start(); - } -} - -void LLPanelOutfitEdit::onListViewFilterCommitted(LLUICtrl* ctrl) -{ - S32 curr_filter_type = mListViewFilterCmbBox->getCurrentIndex(); - if (curr_filter_type < 0) return; - - if (curr_filter_type >= LVIT_SHAPE) - { - mWearableItemsList->setMenuWearableType(LLWearableType::EType(curr_filter_type - LVIT_SHAPE)); - } - mWearableListManager->setFilterCollector(mListViewItemTypes[curr_filter_type]->collector); -} - -void LLPanelOutfitEdit::onSearchEdit(const std::string& string) -{ - if (mSearchString != string) - { - mSearchString = string; - - // Searches are case-insensitive - LLStringUtil::toUpper(mSearchString); - LLStringUtil::trimHead(mSearchString); - } - - if (mSearchString == "") - { - mInventoryItemsPanel->setFilterSubString(LLStringUtil::null); - mWearableItemsList->setFilterSubString(LLStringUtil::null, true); - // re-open folders that were initially open - mSavedFolderState->setApply(true); - mInventoryItemsPanel->getRootFolder()->applyFunctorRecursively(*mSavedFolderState); - LLOpenFoldersWithSelection opener; - mInventoryItemsPanel->getRootFolder()->applyFunctorRecursively(opener); - mInventoryItemsPanel->getRootFolder()->scrollToShowSelection(); - } - - if (!LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) - { - llassert(false); // this should have been done on startup - LLInventoryModelBackgroundFetch::instance().start(); - } - - if (mInventoryItemsPanel->getFilterSubString().empty() && mSearchString.empty()) - { - // current filter and new filter empty, do nothing - return; - } - - // save current folder open state if no filter currently applied - if (mInventoryItemsPanel->getFilterSubString().empty()) - { - mSavedFolderState->setApply(false); - mInventoryItemsPanel->getRootFolder()->applyFunctorRecursively(*mSavedFolderState); - } - - // set new filter string - mInventoryItemsPanel->setFilterSubString(mSearchString); - mWearableItemsList->setFilterSubString(mSearchString, true); -} - -void LLPanelOutfitEdit::onPlusBtnClicked(void) -{ - uuid_vec_t selected_items; - getSelectedItemsUUID(selected_items); - - LLPointer link_waiter = new LLUpdateAppearanceOnDestroy; - - for(uuid_vec_t::iterator iter = selected_items.begin(); iter != selected_items.end(); iter++) - { - LLUUID selected_id = *iter; - if (!selected_id.isNull()) - { - //replacing instead of adding the item - LLAppearanceMgr::getInstance()->wearItemOnAvatar(selected_id, false, true, link_waiter); - } - } -} - -void LLPanelOutfitEdit::onVisibilityChanged(const LLSD &in_visible_chain) -{ - showAddWearablesPanel(false); - mWearableItemsList->resetSelection(); - mInventoryItemsPanel->clearSelection(); - - if (in_visible_chain.asBoolean()) - { - update(); - } - else - { - mWearableListManager->holdProgress(); //list population restarts with visibility - } -} - -void LLPanelOutfitEdit::onAddWearableClicked(void) -{ - LLPanelDummyClothingListItem* item = dynamic_cast(mCOFWearables->getSelectedItem()); - - if(item) - { - showFilteredWearablesListView(item->getWearableType()); - } -} - -void LLPanelOutfitEdit::onReplaceMenuItemClicked(LLUUID selected_item_id) -{ - LLViewerInventoryItem* item = gInventory.getLinkedItem(selected_item_id); - - if (item) - { - showFilteredWearablesListView(item->getWearableType()); - } -} - -void LLPanelOutfitEdit::onShopButtonClicked() -{ - static LLShopURLDispatcher url_resolver; - - // will contain the resultant URL - std::string url; - - if (isAgentAvatarValid()) - { - // try to get wearable type from 'Add More' panel first (EXT-7639) - selection_info_t selection_info = getAddMorePanelSelectionType(); - - LLWearableType::EType type = selection_info.first; - - if (selection_info.second > 1) - { - // the second argument is not important in this case: generic market place will be opened - url = url_resolver.resolveURL(LLWearableType::WT_NONE, SEX_FEMALE); - } - else - { - if (type == LLWearableType::WT_NONE) - { - type = getCOFWearablesSelectionType(); - } - - ESex sex = gAgentAvatarp->getSex(); - - // WT_INVALID comes for attachments - if (type != LLWearableType::WT_INVALID && type != LLWearableType::WT_NONE) - { - url = url_resolver.resolveURL(type, sex); - } - - if (url.empty()) - { - url = url_resolver.resolveURL( - mCOFWearables->getExpandedAccordionAssetType(), sex); - } - } - } - else - { - LL_WARNS() << "Agent avatar is invalid" << LL_ENDL; - - // the second argument is not important in this case: generic market place will be opened - url = url_resolver.resolveURL(LLWearableType::WT_NONE, SEX_FEMALE); - } - - LLWeb::loadURL(url); -} - -LLWearableType::EType LLPanelOutfitEdit::getCOFWearablesSelectionType() const -{ - std::vector selected_items; - LLWearableType::EType type = LLWearableType::WT_NONE; - - mCOFWearables->getSelectedItems(selected_items); - - if (selected_items.size() == 1) - { - LLPanel* item = selected_items.front(); - - // LLPanelDummyClothingListItem is lower then LLPanelInventoryListItemBase in hierarchy tree - if (LLPanelDummyClothingListItem* dummy_item = dynamic_cast(item)) - { - type = dummy_item->getWearableType(); - } - else if (LLPanelInventoryListItemBase* real_item = dynamic_cast(item)) - { - type = real_item->getWearableType(); - } - } - - return type; -} - -LLPanelOutfitEdit::selection_info_t LLPanelOutfitEdit::getAddMorePanelSelectionType() const -{ - selection_info_t result = std::make_pair(LLWearableType::WT_NONE, 0); - - if (mAddWearablesPanel != NULL && mAddWearablesPanel->getVisible()) - { - if (mInventoryItemsPanel != NULL && mInventoryItemsPanel->getVisible()) - { - std::set selected_items = mInventoryItemsPanel->getRootFolder()->getSelectionList(); - - result.second = selected_items.size(); - - if (result.second == 1) - { - result.first = getWearableTypeByItemUUID(static_cast((*selected_items.begin())->getViewModelItem())->getUUID()); - } - } - else if (mWearableItemsList != NULL && mWearableItemsList->getVisible()) - { - std::vector selected_uuids; - mWearableItemsList->getSelectedUUIDs(selected_uuids); - - result.second = selected_uuids.size(); - - if (result.second == 1) - { - result.first = getWearableTypeByItemUUID(selected_uuids.front()); - } - } - } - - return result; -} - -LLWearableType::EType LLPanelOutfitEdit::getWearableTypeByItemUUID(const LLUUID& item_uuid) const -{ - LLViewerInventoryItem* item = gInventory.getLinkedItem(item_uuid); - return (item != NULL) ? item->getWearableType() : LLWearableType::WT_NONE; -} - -void LLPanelOutfitEdit::onRemoveFromOutfitClicked(void) -{ - LLUUID id_to_remove = mCOFWearables->getSelectedUUID(); - LLWearableType::EType type = getWearableTypeByItemUUID(id_to_remove); - - LLAppearanceMgr::getInstance()->removeItemFromAvatar(id_to_remove); - - if (!mCOFWearables->getSelectedItem()) - { - mCOFWearables->selectClothing(type); - } -} - - -void LLPanelOutfitEdit::onEditWearableClicked(void) -{ - LLUUID selected_item_id = mCOFWearables->getSelectedUUID(); - if (selected_item_id.notNull()) - { - gAgentWearables.editWearable(selected_item_id); - } -} - -void LLPanelOutfitEdit::updatePlusButton() -{ - uuid_vec_t selected_items; - getSelectedItemsUUID(selected_items); - if (selected_items.empty()) - { - mPlusBtn->setEnabled(false); - return; - } - - // If any of the selected items are not wearable (due to already being worn OR being of the wrong type), disable the add button. - uuid_vec_t::iterator unwearable_item = std::find_if(selected_items.begin(), selected_items.end(), !boost::bind(&get_can_item_be_worn, _1)); - bool can_add = ( unwearable_item == selected_items.end() ); - - mPlusBtn->setEnabled(can_add); - - LLViewerInventoryItem* first_item(gInventory.getItem(selected_items.front())); - - if (can_add && - first_item && - selected_items.size() == 1 && - first_item->getType() == LLAssetType::AT_BODYPART) - { - mPlusBtn->setToolTip(getString("replace_body_part")); - } - else - { - mPlusBtn->setToolTip(LLStringUtil::null); - } - - /* Removing add to look inline button (not part of mvp for viewer 2) - LLRect btn_rect(current_item->getLocalRect().mRight - 50, - current_item->getLocalRect().mTop, - current_item->getLocalRect().mRight - 30, - current_item->getLocalRect().mBottom); - - mAddToLookBtn->setRect(btn_rect); - mAddToLookBtn->setEnabled(true); - if (!mAddToLookBtn->getVisible()) - { - mAddToLookBtn->setVisible(true); - } - - current_item->addChild(mAddToLookBtn); */ -} - - -void LLPanelOutfitEdit::applyFolderViewFilter(EFolderViewItemType type) -{ - mFolderViewFilterCmbBox->setCurrentByIndex(type); - mFolderViewFilterCmbBox->onCommit(); -} - -void LLPanelOutfitEdit::applyListViewFilter(EListViewItemType type) -{ - mListViewFilterCmbBox->setCurrentByIndex(type); - mListViewFilterCmbBox->onCommit(); -} - -void LLPanelOutfitEdit::filterWearablesBySelectedItem(void) -{ - if (!mAddWearablesPanel->getVisible()) return; - - uuid_vec_t ids; - mCOFWearables->getSelectedUUIDs(ids); - - bool nothing_selected = ids.empty(); - bool one_selected = ids.size() == 1; - bool more_than_one_selected = ids.size() > 1; - bool is_dummy_item = (ids.size() && dynamic_cast(mCOFWearables->getSelectedItem())); - - // selected, expanded accordion tabs and selection in flat list view determine filtering when no item is selected in COF - // selection in flat list view participates in determining filtering because of EXT-7963 - // So the priority of criterions in is: - // 1. Selected accordion tab | IF (any accordion selected) - // | filter_type = selected_accordion_type - // 2. Selected item in flat list view | ELSEIF (any item in flat list view selected) - // | filter_type = selected_item_type - // 3. Expanded accordion tab | ELSEIF (any accordion expanded) - // | filter_type = expanded accordion_type - if (nothing_selected) - { - if (mInventoryItemsPanel->getVisible()) - { - return; - } - showWearablesListView(); - - //selected accordion tab is more priority than expanded tab - //and selected item in flat list view of 'Add more' panel when - //determining filtering - LLAssetType::EType type = mCOFWearables->getSelectedAccordionAssetType(); - if (type == LLAssetType::AT_NONE) - { //no accordion selected - - // when no accordion selected then selected item from flat list view - // has more priority than expanded when determining filtering - LLUUID selected_item_id = mWearableItemsList->getSelectedUUID(); - LLViewerInventoryItem* item = gInventory.getLinkedItem(selected_item_id); - if(item) - { - showFilteredWearablesListView(item->getWearableType()); - return; - } - - // when no accordion selected and no selected items in flat list view - // determine filtering according to expanded accordion - type = mCOFWearables->getExpandedAccordionAssetType(); - } - - switch (type) - { - case LLAssetType::AT_OBJECT: - applyListViewFilter(LVIT_ATTACHMENT); - break; - case LLAssetType::AT_BODYPART: - applyListViewFilter(LVIT_BODYPART); - break; - case LLAssetType::AT_CLOTHING: - default: - applyListViewFilter(LVIT_CLOTHING); - break; - } - - return; - } - - //resetting selection if more than one item is selected - if (more_than_one_selected) - { - if (mInventoryItemsPanel->getVisible()) - { - applyFolderViewFilter(FVIT_ALL); - return; - } - - showWearablesListView(); - applyListViewFilter(LVIT_ALL); - return; - } - - - //filter wearables by a type represented by a dummy item - if (one_selected && is_dummy_item) - { - if (mInventoryItemsPanel->getVisible()) - { - applyFolderViewFilter(FVIT_WEARABLE); - return; - } - - onAddWearableClicked(); - return; - } - - LLViewerInventoryItem* item = gInventory.getItem(ids[0]); - if (!item && ids[0].notNull()) - { - if (mInventoryItemsPanel->getVisible()) - { - applyFolderViewFilter(FVIT_ALL); - return; - } - //Inventory misses an item with non-zero id - showWearablesListView(); - applyListViewFilter(LVIT_ALL); - return; - } - - if (item && one_selected && !is_dummy_item) - { - if (item->isWearableType()) - { - if (mInventoryItemsPanel->getVisible()) - { - applyFolderViewFilter(FVIT_WEARABLE); - return; - } - //single clothing or bodypart item is selected - showFilteredWearablesListView(item->getWearableType()); - return; - } - else - { - if (mInventoryItemsPanel->getVisible()) - { - applyFolderViewFilter(FVIT_ATTACHMENT); - return; - } - //attachment is selected - showWearablesListView(); - applyListViewFilter(LVIT_ATTACHMENT); - return; - } - } - -} - - - -void LLPanelOutfitEdit::update() -{ - mCOFWearables->refresh(); - - updateVerbs(); -} - -bool LLPanelOutfitEdit::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - if (cargo_data == NULL) - { - LL_WARNS() << "cargo_data is NULL" << LL_ENDL; - return true; - } - - switch (cargo_type) - { - case DAD_BODYPART: - case DAD_CLOTHING: - case DAD_OBJECT: - case DAD_LINK: - *accept = ACCEPT_YES_MULTI; - break; - default: - *accept = ACCEPT_NO; - } - - if (drop) - { - LLInventoryItem* item = static_cast(cargo_data); - - if (LLAssetType::lookupIsAssetIDKnowable(item->getType())) - { - mCOFDragAndDropObserver->watchAsset(item->getAssetUUID()); - - /* - * Adding request to wear item. If the item is a link, then getLinkedUUID() will - * return the ID of the linked item. Otherwise it will return the item's ID. The - * second argument is used to delay the appearance update until all dragged items - * are added to optimize user experience. - */ - LLAppearanceMgr::instance().addCOFItemLink(item->getLinkedUUID()); - } - else - { - // if asset id is not available for the item we must wear it immediately (attachments only) - LLAppearanceMgr::instance().addCOFItemLink(item->getLinkedUUID(), new LLUpdateAppearanceAndEditWearableOnDestroy(item->getUUID())); - } - } - - return true; -} - -void LLPanelOutfitEdit::displayCurrentOutfit() -{ - if (!getVisible()) - { - setVisible(true); - } - - updateCurrentOutfitName(); - - update(); -} - -void LLPanelOutfitEdit::updateCurrentOutfitName() -{ - std::string current_outfit_name; - if (LLAppearanceMgr::getInstance()->getBaseOutfitName(current_outfit_name)) - { - mCurrentOutfitName->setText(current_outfit_name); - } - else - { - mCurrentOutfitName->setText(getString("No Outfit")); - } -} - -//private -void LLPanelOutfitEdit::updateVerbs() -{ - bool outfit_is_dirty = LLAppearanceMgr::getInstance()->isOutfitDirty(); - bool outfit_locked = LLAppearanceMgr::getInstance()->isOutfitLocked(); - bool has_baseoutfit = LLAppearanceMgr::getInstance()->getBaseOutfitUUID().notNull(); - - getChildView(SAVE_BTN)->setEnabled(!outfit_locked && outfit_is_dirty); - getChildView(REVERT_BTN)->setEnabled(outfit_is_dirty && has_baseoutfit); - - mStatus->setText(outfit_is_dirty ? getString("unsaved_changes") : getString("now_editing")); - - updateCurrentOutfitName(); - - //updating state of "Wear Item" button previously known as "Plus" button - updatePlusButton(); -} - -bool LLPanelOutfitEdit::switchPanels(LLPanel* switch_from_panel, LLPanel* switch_to_panel) -{ - if(switch_from_panel && switch_to_panel && !switch_to_panel->getVisible()) - { - switch_from_panel->setVisible(false); - switch_to_panel->setVisible(true); - return true; - } - return false; -} - -void LLPanelOutfitEdit::resetAccordionState() -{ - if (mCOFWearables != NULL) - { - mCOFWearables->expandDefaultAccordionTab(); - } - else - { - LL_WARNS() << "mCOFWearables is NULL" << LL_ENDL; - } -} - -void LLPanelOutfitEdit::onAddMoreButtonClicked() -{ - toggleAddWearablesPanel(); - filterWearablesBySelectedItem(); -} - -void LLPanelOutfitEdit::showFilteredWearablesListView(LLWearableType::EType type) -{ - showAddWearablesPanel(true); - showWearablesListView(); - - //e_list_view_item_type implicitly contains LLWearableType::EType starting from LVIT_SHAPE - applyListViewFilter(static_cast(LVIT_SHAPE + type)); - mWearableItemsList->setMenuWearableType(type); -} - -static void update_status_widget_rect(LLView * widget, S32 right_border) -{ - LLRect rect = widget->getRect(); - rect.mRight = right_border; - - widget->setShape(rect); -} - -void LLPanelOutfitEdit::onOutfitChanging(bool started) -{ - static LLLoadingIndicator* indicator = getChild("edit_outfit_loading_indicator"); - static LLView* status_panel = getChild("outfit_name_and_status"); - static S32 indicator_delta = status_panel->getRect().getWidth() - indicator->getRect().mLeft; - - S32 delta = started ? indicator_delta : 0; - S32 right_border = status_panel->getRect().getWidth() - delta; - - if (mCurrentOutfitName) - update_status_widget_rect(mCurrentOutfitName, right_border); - if (mStatus) - update_status_widget_rect(mStatus, right_border); - - indicator->setVisible(started); -} - -void LLPanelOutfitEdit::getCurrentItemUUID(LLUUID& selected_id) -{ - if (mInventoryItemsPanel->getVisible()) - { - LLFolderViewItem* curr_item = mInventoryItemsPanel->getRootFolder()->getCurSelectedItem(); - if (!curr_item) return; - - LLFolderViewModelItemInventory* listenerp = static_cast(curr_item->getViewModelItem()); - if (!listenerp) return; - - selected_id = listenerp->getUUID(); - } - else if (mWearablesListViewPanel->getVisible()) - { - selected_id = mWearableItemsList->getSelectedUUID(); - } -} - - -void LLPanelOutfitEdit::getSelectedItemsUUID(uuid_vec_t& uuid_list) -{ - void (uuid_vec_t::* tmp)(LLUUID const &) = &uuid_vec_t::push_back; - if (mInventoryItemsPanel->getVisible()) - { - std::set item_set = mInventoryItemsPanel->getRootFolder()->getSelectionList(); - for (std::set::iterator it = item_set.begin(), end_it = item_set.end(); - it != end_it; - ++it) - { - uuid_list.push_back(static_cast((*it)->getViewModelItem())->getUUID()); - } - } - else if (mWearablesListViewPanel->getVisible()) - { - std::vector item_set; - mWearableItemsList->getSelectedValues(item_set); - - std::for_each(item_set.begin(), item_set.end(), boost::bind( tmp, &uuid_list, boost::bind(&LLSD::asUUID, _1 ))); - } - -// return selected_id; -} - -void LLPanelOutfitEdit::onCOFChanged() -{ - //the panel is only updated when is visible to a user - - // BAP - this check has to be removed because otherwise item name - // changes made when the panel is not visible will not be - // propagated to the panel. - // if (!isInVisibleChain()) return; - - update(); -} - -void LLPanelOutfitEdit::updateWearablesPanelVerbButtons() -{ - if(mWearablesListViewPanel->getVisible()) - { - mFolderViewBtn->setToggleState(false); - mFolderViewBtn->setImageOverlay(getString("folder_view_off"), mFolderViewBtn->getImageOverlayHAlign()); - mListViewBtn->setImageOverlay(getString("list_view_on"), mListViewBtn->getImageOverlayHAlign()); - } - else if(mInventoryItemsPanel->getVisible()) - { - mListViewBtn->setToggleState(false); - mListViewBtn->setImageOverlay(getString("list_view_off"), mListViewBtn->getImageOverlayHAlign()); - mFolderViewBtn->setImageOverlay(getString("folder_view_on"), mFolderViewBtn->getImageOverlayHAlign()); - } -} - -void LLPanelOutfitEdit::saveListSelection() -{ - if(mWearablesListViewPanel->getVisible()) - { - std::set selected_ids = mInventoryItemsPanel->getRootFolder()->getSelectionList(); - - if(!selected_ids.size()) return; - - for (std::set::const_iterator item_id = selected_ids.begin(); item_id != selected_ids.end(); ++item_id) - { - mWearableItemsList->selectItemByUUID(static_cast((*item_id)->getViewModelItem())->getUUID(), true); - } - mWearableItemsList->scrollToShowFirstSelectedItem(); - } - else if(mInventoryItemsPanel->getVisible()) - { - std::vector selected_ids; - mWearableItemsList->getSelectedUUIDs(selected_ids); - - if(!selected_ids.size()) return; - - mInventoryItemsPanel->clearSelection(); - LLFolderView* root = mInventoryItemsPanel->getRootFolder(); - - if(!root) return; - - for(std::vector::const_iterator item_id = selected_ids.begin(); item_id != selected_ids.end(); ++item_id) - { - LLFolderViewItem* item = mInventoryItemsPanel->getItemByID(*item_id); - if (!item) continue; - - LLFolderViewFolder* parent = item->getParentFolder(); - if(parent) - { - parent->setOpenArrangeRecursively(true, LLFolderViewFolder::RECURSE_UP); - } - mInventoryItemsPanel->getRootFolder()->changeSelection(item, true); - } - mInventoryItemsPanel->getRootFolder()->scrollToShowSelection(); - } -} - -void LLPanelOutfitEdit::saveOutfit(bool as_new) -{ - LLPanelOutfitsInventory* panel_outfits_inventory = LLPanelOutfitsInventory::findInstance(); - if (panel_outfits_inventory) - { - panel_outfits_inventory->saveOutfit(as_new); - } -} - -// EOF +/** + * @file llpaneloutfitedit.cpp + * @brief Displays outfit edit information in Side Tray. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpaneloutfitedit.h" + +// *TODO: reorder includes to match the coding standard +#include "llagent.h" +#include "llagentcamera.h" +#include "llagentwearables.h" +#include "llappearancemgr.h" +#include "lloutfitobserver.h" +#include "llcofwearables.h" +#include "llfilteredwearablelist.h" +#include "llfolderview.h" +#include "llinventory.h" +#include "llinventoryitemslist.h" +#include "llviewercontrol.h" +#include "llui.h" +#include "llfloater.h" +#include "llfloaterreg.h" +#include "llinventoryfunctions.h" +#include "llinventorypanel.h" +#include "llviewermenu.h" +#include "llviewerwindow.h" +#include "llviewerinventory.h" +#include "llbutton.h" +#include "llcombobox.h" +#include "llfiltereditor.h" +#include "llinventorybridge.h" +#include "llinventorymodel.h" +#include "llinventorymodelbackgroundfetch.h" +#include "llloadingindicator.h" +#include "llmenubutton.h" +#include "llpaneloutfitsinventory.h" +#include "lluiconstants.h" +#include "llscrolllistctrl.h" +#include "lltextbox.h" +#include "lltoggleablemenu.h" +#include "lltrans.h" +#include "lluictrlfactory.h" +#include "llsdutil.h" +#include "llsidepanelappearance.h" +#include "lltoggleablemenu.h" +#include "llvoavatarself.h" +#include "llwearablelist.h" +#include "llwearableitemslist.h" +#include "llwearabletype.h" +#include "llweb.h" + +static LLPanelInjector t_outfit_edit("panel_outfit_edit"); + +const U64 WEARABLE_MASK = (1LL << LLInventoryType::IT_WEARABLE); +const U64 ATTACHMENT_MASK = (1LL << LLInventoryType::IT_ATTACHMENT) | (1LL << LLInventoryType::IT_OBJECT); +const U64 ALL_ITEMS_MASK = WEARABLE_MASK | ATTACHMENT_MASK; + +static const std::string REVERT_BTN("revert_btn"); +static const std::string SAVE_AS_BTN("save_as_btn"); +static const std::string SAVE_BTN("save_btn"); + + +/////////////////////////////////////////////////////////////////////////////// +// LLShopURLDispatcher +/////////////////////////////////////////////////////////////////////////////// + +class LLShopURLDispatcher +{ +public: + std::string resolveURL(LLWearableType::EType wearable_type, ESex sex); + std::string resolveURL(LLAssetType::EType asset_type, ESex sex); +}; + +std::string LLShopURLDispatcher::resolveURL(LLWearableType::EType wearable_type, ESex sex) +{ + const std::string prefix = "MarketplaceURL"; + const std::string sex_str = (sex == SEX_MALE) ? "Male" : "Female"; + const std::string type_str = LLWearableType::getInstance()->getTypeName(wearable_type); + + std::string setting_name = prefix; + + switch (wearable_type) + { + case LLWearableType::WT_ALPHA: + case LLWearableType::WT_NONE: + case LLWearableType::WT_INVALID: // just in case, this shouldn't happen + case LLWearableType::WT_COUNT: // just in case, this shouldn't happen + break; + + default: + setting_name += '_'; + setting_name += type_str; + setting_name += sex_str; + break; + } + + return gSavedSettings.getString(setting_name); +} + +std::string LLShopURLDispatcher::resolveURL(LLAssetType::EType asset_type, ESex sex) +{ + const std::string prefix = "MarketplaceURL"; + const std::string sex_str = (sex == SEX_MALE) ? "Male" : "Female"; + const std::string type_str = LLAssetType::lookup(asset_type); + + std::string setting_name = prefix; + + switch (asset_type) + { + case LLAssetType::AT_CLOTHING: + case LLAssetType::AT_OBJECT: + case LLAssetType::AT_BODYPART: + setting_name += '_'; + setting_name += type_str; + setting_name += sex_str; + break; + + // to suppress warnings + default: + break; + } + + return gSavedSettings.getString(setting_name); +} + +/////////////////////////////////////////////////////////////////////////////// +// LLPanelOutfitEditGearMenu +/////////////////////////////////////////////////////////////////////////////// + +class LLPanelOutfitEditGearMenu +{ +public: + static LLToggleableMenu* create() + { + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + + registrar.add("Wearable.Create", boost::bind(onCreate, _2)); + + llassert(LLMenuGL::sMenuContainer != NULL); + LLToggleableMenu* menu = LLUICtrlFactory::getInstance()->createFromFile( + "menu_cof_gear.xml", LLMenuGL::sMenuContainer, LLViewerMenuHolderGL::child_registry_t::instance()); + llassert(menu); + if (menu) + { + populateCreateWearableSubmenus(menu); + } + + return menu; + } + +private: + static void onCreate(const LLSD& param) + { + LLWearableType::EType type = LLWearableType::getInstance()->typeNameToType(param.asString()); + if (type == LLWearableType::WT_NONE) + { + LL_WARNS() << "Invalid wearable type" << LL_ENDL; + return; + } + + LLAgentWearables::createWearable(type, true); + } + + // Populate the menu with items like "New Skin", "New Pants", etc. + static void populateCreateWearableSubmenus(LLMenuGL* menu) + { + LLView* menu_clothes = gMenuHolder->getChildView("COF.Gear.New_Clothes", false); + LLView* menu_bp = gMenuHolder->getChildView("COF.Gear.New_Body_Parts", false); + LLWearableType * wearable_type_inst = LLWearableType::getInstance(); + + for (U8 i = LLWearableType::WT_SHAPE; i != (U8) LLWearableType::WT_COUNT; ++i) + { + LLWearableType::EType type = (LLWearableType::EType) i; + const std::string& type_name = wearable_type_inst->getTypeName(type); + + LLMenuItemCallGL::Params p; + p.name = type_name; + p.label = LLTrans::getString(wearable_type_inst->getTypeDefaultNewName(type)); + p.on_click.function_name = "Wearable.Create"; + p.on_click.parameter = LLSD(type_name); + + LLView* parent = wearable_type_inst->getAssetType(type) == LLAssetType::AT_CLOTHING ? menu_clothes : menu_bp; + LLUICtrlFactory::create(p, parent); + } + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// LLAddWearablesGearMenu +/////////////////////////////////////////////////////////////////////////////// + +class LLAddWearablesGearMenu : public LLInitClass +{ +public: + static LLToggleableMenu* create(LLWearableItemsList* flat_list, LLInventoryPanel* inventory_panel) + { + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + + llassert(flat_list); + llassert(inventory_panel); + + LLHandle flat_list_handle = flat_list->getHandle(); + LLHandle inventory_panel_handle = inventory_panel->getHandle(); + + registrar.add("AddWearable.Gear.Sort", boost::bind(onSort, flat_list_handle, inventory_panel_handle, _2)); + enable_registrar.add("AddWearable.Gear.Check", boost::bind(onCheck, flat_list_handle, inventory_panel_handle, _2)); + enable_registrar.add("AddWearable.Gear.Visible", boost::bind(onVisible, inventory_panel_handle, _2)); + + llassert(LLMenuGL::sMenuContainer != NULL); + LLToggleableMenu* menu = LLUICtrlFactory::getInstance()->createFromFile( + "menu_add_wearable_gear.xml", + LLMenuGL::sMenuContainer, LLViewerMenuHolderGL::child_registry_t::instance()); + + return menu; + } + +private: + static void onSort(LLHandle flat_list_handle, + LLHandle inventory_panel_handle, + LLSD::String sort_order_str) + { + if (flat_list_handle.isDead() || inventory_panel_handle.isDead()) return; + + LLWearableItemsList* flat_list = dynamic_cast(flat_list_handle.get()); + LLInventoryPanel* inventory_panel = dynamic_cast(inventory_panel_handle.get()); + + if (!flat_list || !inventory_panel) return; + + LLWearableItemsList::ESortOrder sort_order; + + if ("by_most_recent" == sort_order_str) + { + sort_order = LLWearableItemsList::E_SORT_BY_MOST_RECENT; + } + else if ("by_name" == sort_order_str) + { + sort_order = LLWearableItemsList::E_SORT_BY_NAME; + } + else if ("by_type" == sort_order_str) + { + sort_order = LLWearableItemsList::E_SORT_BY_TYPE_NAME; + } + else + { + LL_WARNS() << "Unrecognized sort order action" << LL_ENDL; + return; + } + + if (inventory_panel->getVisible()) + { + inventory_panel->getFolderViewModel()->setSorter(sort_order); + } + else + { + flat_list->setSortOrder(sort_order); + } + } + + static bool onCheck(LLHandle flat_list_handle, + LLHandle inventory_panel_handle, + LLSD::String sort_order_str) + { + if (flat_list_handle.isDead() || inventory_panel_handle.isDead()) return false; + + LLWearableItemsList* flat_list = dynamic_cast(flat_list_handle.get()); + LLInventoryPanel* inventory_panel = dynamic_cast(inventory_panel_handle.get()); + + if (!inventory_panel || !flat_list) return false; + + // Inventory panel uses its own sort order independent from + // flat list view so this flag is used to distinguish between + // currently visible "tree" or "flat" representation of inventory. + bool inventory_tree_visible = inventory_panel->getVisible(); + + if (inventory_tree_visible) + { + U32 sort_order = inventory_panel->getSortOrder(); + + if ("by_most_recent" == sort_order_str) + { + return LLWearableItemsList::E_SORT_BY_MOST_RECENT & sort_order; + } + else if ("by_name" == sort_order_str) + { + // If inventory panel is not sorted by date then it is sorted by name. + return LLWearableItemsList::E_SORT_BY_MOST_RECENT & ~sort_order; + } + LL_WARNS() << "Unrecognized inventory panel sort order" << LL_ENDL; + } + else + { + LLWearableItemsList::ESortOrder sort_order = flat_list->getSortOrder(); + + if ("by_most_recent" == sort_order_str) + { + return LLWearableItemsList::E_SORT_BY_MOST_RECENT == sort_order; + } + else if ("by_name" == sort_order_str) + { + return LLWearableItemsList::E_SORT_BY_NAME == sort_order; + } + else if ("by_type" == sort_order_str) + { + return LLWearableItemsList::E_SORT_BY_TYPE_NAME == sort_order; + } + LL_WARNS() << "Unrecognized wearable list sort order" << LL_ENDL; + } + return false; + } + + static bool onVisible(LLHandle inventory_panel_handle, + LLSD::String sort_order_str) + { + if (inventory_panel_handle.isDead()) return false; + + LLInventoryPanel* inventory_panel = dynamic_cast(inventory_panel_handle.get()); + + // Enable sorting by type only for the flat list of items + // because inventory panel doesn't support this kind of sorting. + return ( "by_type" == sort_order_str ) + && ( !inventory_panel || !inventory_panel->getVisible() ); + } +}; + +/////////////////////////////////////////////////////////////////////////////// +// LLCOFDragAndDropObserver +/////////////////////////////////////////////////////////////////////////////// + +class LLCOFDragAndDropObserver : public LLInventoryAddItemByAssetObserver +{ +public: + LLCOFDragAndDropObserver(LLInventoryModel* model); + + virtual ~LLCOFDragAndDropObserver(); + + virtual void done(); + +private: + LLInventoryModel* mModel; +}; + +inline LLCOFDragAndDropObserver::LLCOFDragAndDropObserver(LLInventoryModel* model): + mModel(model) +{ + if (model != NULL) + { + model->addObserver(this); + } +} + +inline LLCOFDragAndDropObserver::~LLCOFDragAndDropObserver() +{ + if (mModel != NULL && mModel->containsObserver(this)) + { + mModel->removeObserver(this); + } +} + +void LLCOFDragAndDropObserver::done() +{ + LLAppearanceMgr::instance().updateAppearanceFromCOF(); +} + +/////////////////////////////////////////////////////////////////////////////// +// LLPanelOutfitEdit +/////////////////////////////////////////////////////////////////////////////// + +LLPanelOutfitEdit::LLPanelOutfitEdit() +: LLPanel(), + mSearchFilter(NULL), + mCOFWearables(NULL), + mInventoryItemsPanel(NULL), + mGearMenu(NULL), + mAddWearablesGearMenu(NULL), + mCOFDragAndDropObserver(NULL), + mInitialized(false), + mAddWearablesPanel(NULL), + mFolderViewFilterCmbBox(NULL), + mListViewFilterCmbBox(NULL), + mWearableListManager(NULL), + mPlusBtn(NULL), + mWearablesGearMenuBtn(NULL), + mGearMenuBtn(NULL) +{ + mSavedFolderState = new LLSaveFolderState(); + mSavedFolderState->setApply(false); + + + LLOutfitObserver& observer = LLOutfitObserver::instance(); + observer.addBOFReplacedCallback(boost::bind(&LLPanelOutfitEdit::updateCurrentOutfitName, this)); + observer.addBOFChangedCallback(boost::bind(&LLPanelOutfitEdit::updateVerbs, this)); + observer.addOutfitLockChangedCallback(boost::bind(&LLPanelOutfitEdit::updateVerbs, this)); + observer.addCOFChangedCallback(boost::bind(&LLPanelOutfitEdit::onCOFChanged, this)); + + gAgentWearables.addLoadingStartedCallback(boost::bind(&LLPanelOutfitEdit::onOutfitChanging, this, true)); + gAgentWearables.addLoadedCallback(boost::bind(&LLPanelOutfitEdit::onOutfitChanging, this, false)); + + mFolderViewItemTypes.reserve(NUM_FOLDER_VIEW_ITEM_TYPES); + for (U32 i = 0; i < NUM_FOLDER_VIEW_ITEM_TYPES; i++) + { + mFolderViewItemTypes.push_back(LLLookItemType()); + } + +} + +LLPanelOutfitEdit::~LLPanelOutfitEdit() +{ + delete mWearableListManager; + delete mSavedFolderState; + + delete mCOFDragAndDropObserver; + + while (!mListViewItemTypes.empty()) { + delete mListViewItemTypes.back(); + mListViewItemTypes.pop_back(); + } +} + +bool LLPanelOutfitEdit::postBuild() +{ + // gInventory.isInventoryUsable() no longer needs to be tested per Richard's fix for race conditions between inventory and panels + + mFolderViewItemTypes[FVIT_ALL] = LLLookItemType(getString("Filter.All"), ALL_ITEMS_MASK); + mFolderViewItemTypes[FVIT_WEARABLE] = LLLookItemType(getString("Filter.Clothes/Body"), WEARABLE_MASK); + mFolderViewItemTypes[FVIT_ATTACHMENT] = LLLookItemType(getString("Filter.Objects"), ATTACHMENT_MASK); + + //order is important, see EListViewItemType for order information + mListViewItemTypes.push_back(new LLFilterItem(getString("Filter.All"), new LLFindNonLinksByMask(ALL_ITEMS_MASK))); + mListViewItemTypes.push_back(new LLFilterItem(getString("Filter.Clothing"), new LLIsTypeActual(LLAssetType::AT_CLOTHING))); + mListViewItemTypes.push_back(new LLFilterItem(getString("Filter.Bodyparts"), new LLIsTypeActual(LLAssetType::AT_BODYPART))); + mListViewItemTypes.push_back(new LLFilterItem(getString("Filter.Objects"), new LLFindNonLinksByMask(ATTACHMENT_MASK)));; + mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("shape"), new LLFindActualWearablesOfType(LLWearableType::WT_SHAPE))); + mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("skin"), new LLFindActualWearablesOfType(LLWearableType::WT_SKIN))); + mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("hair"), new LLFindActualWearablesOfType(LLWearableType::WT_HAIR))); + mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("eyes"), new LLFindActualWearablesOfType(LLWearableType::WT_EYES))); + mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("shirt"), new LLFindActualWearablesOfType(LLWearableType::WT_SHIRT))); + mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("pants"), new LLFindActualWearablesOfType(LLWearableType::WT_PANTS))); + mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("shoes"), new LLFindActualWearablesOfType(LLWearableType::WT_SHOES))); + mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("socks"), new LLFindActualWearablesOfType(LLWearableType::WT_SOCKS))); + mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("jacket"), new LLFindActualWearablesOfType(LLWearableType::WT_JACKET))); + mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("gloves"), new LLFindActualWearablesOfType(LLWearableType::WT_GLOVES))); + mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("undershirt"), new LLFindActualWearablesOfType(LLWearableType::WT_UNDERSHIRT))); + mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("underpants"), new LLFindActualWearablesOfType(LLWearableType::WT_UNDERPANTS))); + mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("skirt"), new LLFindActualWearablesOfType(LLWearableType::WT_SKIRT))); + mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("alpha"), new LLFindActualWearablesOfType(LLWearableType::WT_ALPHA))); + mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("tattoo"), new LLFindActualWearablesOfType(LLWearableType::WT_TATTOO))); + mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("physics"), new LLFindActualWearablesOfType(LLWearableType::WT_PHYSICS))); + mListViewItemTypes.push_back(new LLFilterItem(LLTrans::getString("universal"), new LLFindActualWearablesOfType(LLWearableType::WT_UNIVERSAL))); + + mCurrentOutfitName = getChild("curr_outfit_name"); + mStatus = getChild("status"); + + mFolderViewBtn = getChild("folder_view_btn"); + mListViewBtn = getChild("list_view_btn"); + + childSetCommitCallback("filter_button", boost::bind(&LLPanelOutfitEdit::showWearablesFilter, this), NULL); + childSetCommitCallback("folder_view_btn", boost::bind(&LLPanelOutfitEdit::showWearablesFolderView, this), NULL); + childSetCommitCallback("folder_view_btn", boost::bind(&LLPanelOutfitEdit::saveListSelection, this), NULL); + childSetCommitCallback("list_view_btn", boost::bind(&LLPanelOutfitEdit::showWearablesListView, this), NULL); + childSetCommitCallback("list_view_btn", boost::bind(&LLPanelOutfitEdit::saveListSelection, this), NULL); + childSetCommitCallback("shop_btn_1", boost::bind(&LLPanelOutfitEdit::onShopButtonClicked, this), NULL); + childSetCommitCallback("shop_btn_2", boost::bind(&LLPanelOutfitEdit::onShopButtonClicked, this), NULL); + + setVisibleCallback(boost::bind(&LLPanelOutfitEdit::onVisibilityChanged, this, _2)); + + mWearablesGearMenuBtn = getChild("wearables_gear_menu_btn"); + mGearMenuBtn = getChild("gear_menu_btn"); + + mCOFWearables = findChild("cof_wearables_list"); + mCOFWearables->setCommitCallback(boost::bind(&LLPanelOutfitEdit::filterWearablesBySelectedItem, this)); + + mCOFWearables->getCOFCallbacks().mAddWearable = boost::bind(&LLPanelOutfitEdit::onAddWearableClicked, this); + mCOFWearables->getCOFCallbacks().mEditWearable = boost::bind(&LLPanelOutfitEdit::onEditWearableClicked, this); + mCOFWearables->getCOFCallbacks().mDeleteWearable = boost::bind(&LLPanelOutfitEdit::onRemoveFromOutfitClicked, this); + mCOFWearables->getCOFCallbacks().mMoveWearableCloser = boost::bind(&LLPanelOutfitEdit::moveWearable, this, true); + mCOFWearables->getCOFCallbacks().mMoveWearableFurther = boost::bind(&LLPanelOutfitEdit::moveWearable, this, false); + + mAddWearablesPanel = getChild("add_wearables_panel"); + + mInventoryItemsPanel = getChild("folder_view"); + mInventoryItemsPanel->setFilterTypes(ALL_ITEMS_MASK); + mInventoryItemsPanel->setShowFolderState(LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); + mInventoryItemsPanel->setSelectCallback(boost::bind(&LLPanelOutfitEdit::updatePlusButton, this)); + mInventoryItemsPanel->getRootFolder()->setReshapeCallback(boost::bind(&LLPanelOutfitEdit::updatePlusButton, this)); + + mCOFDragAndDropObserver = new LLCOFDragAndDropObserver(mInventoryItemsPanel->getModel()); + + mFolderViewFilterCmbBox = getChild("folder_view_filter_combobox"); + mFolderViewFilterCmbBox->setCommitCallback(boost::bind(&LLPanelOutfitEdit::onFolderViewFilterCommitted, this, _1)); + mFolderViewFilterCmbBox->removeall(); + for (U32 i = 0; i < mFolderViewItemTypes.size(); ++i) + { + mFolderViewFilterCmbBox->add(mFolderViewItemTypes[i].displayName); + } + mFolderViewFilterCmbBox->setCurrentByIndex(FVIT_ALL); + + mListViewFilterCmbBox = getChild("list_view_filter_combobox"); + mListViewFilterCmbBox->setCommitCallback(boost::bind(&LLPanelOutfitEdit::onListViewFilterCommitted, this, _1)); + mListViewFilterCmbBox->removeall(); + for (U32 i = 0; i < mListViewItemTypes.size(); ++i) + { + mListViewFilterCmbBox->add(mListViewItemTypes[i]->displayName); + } + mListViewFilterCmbBox->setCurrentByIndex(LVIT_ALL); + + mSearchFilter = getChild("look_item_filter"); + mSearchFilter->setCommitCallback(boost::bind(&LLPanelOutfitEdit::onSearchEdit, this, _2)); + + childSetAction("show_add_wearables_btn", boost::bind(&LLPanelOutfitEdit::onAddMoreButtonClicked, this)); + + mPlusBtn = getChild("plus_btn"); + mPlusBtn->setClickedCallback(boost::bind(&LLPanelOutfitEdit::onPlusBtnClicked, this)); + + childSetAction(REVERT_BTN, boost::bind(&LLAppearanceMgr::wearBaseOutfit, LLAppearanceMgr::getInstance())); + + /* + * By default AT_CLOTHING are sorted by (in in MY OUTFITS): + * - by type (types order determined in LLWearableType::EType) + * - each LLWearableType::EType by outer layer on top + * + * In Add More panel AT_CLOTHING should be sorted in a such way: + * - by type (types order determined in LLWearableType::EType) + * - each LLWearableType::EType by name (EXT-8205) + */ + mWearableListViewItemsComparator = new LLWearableItemTypeNameComparator(); + mWearableListViewItemsComparator->setOrder(LLAssetType::AT_CLOTHING, LLWearableItemTypeNameComparator::ORDER_RANK_1, false, true); + + mWearablesListViewPanel = getChild("filtered_wearables_panel"); + mWearableItemsList = getChild("list_view"); + mWearableItemsList->setCommitOnSelectionChange(true); + mWearableItemsList->setCommitCallback(boost::bind(&LLPanelOutfitEdit::updatePlusButton, this)); + mWearableItemsList->setDoubleClickCallback(boost::bind(&LLPanelOutfitEdit::onPlusBtnClicked, this)); + + mWearableItemsList->setComparator(mWearableListViewItemsComparator); + + // Creating "Add Wearables" panel gear menu after initialization of mWearableItemsList and mInventoryItemsPanel. + mAddWearablesGearMenu = LLAddWearablesGearMenu::create(mWearableItemsList, mInventoryItemsPanel); + mWearablesGearMenuBtn->setMenu(mAddWearablesGearMenu); + + mGearMenu = LLPanelOutfitEditGearMenu::create(); + mGearMenuBtn->setMenu(mGearMenu); + + getChild(SAVE_BTN)->setCommitCallback(boost::bind(&LLPanelOutfitEdit::saveOutfit, this, false)); + getChild(SAVE_AS_BTN)->setCommitCallback(boost::bind(&LLPanelOutfitEdit::saveOutfit, this, true)); + + onOutfitChanging(gAgentWearables.isCOFChangeInProgress()); + return true; +} + +// virtual +void LLPanelOutfitEdit::onOpen(const LLSD& key) +{ + if (!mInitialized) + { + // *TODO: this method is called even panel is not visible to user because its parent layout panel is hidden. + // So, we can defer initializing a bit. + mWearableListManager = new LLFilteredWearableListManager(mWearableItemsList, mListViewItemTypes[LVIT_ALL]->collector); + displayCurrentOutfit(); + mInitialized = true; + } +} + +void LLPanelOutfitEdit::moveWearable(bool closer_to_body) +{ + LLUUID item_id = mCOFWearables->getSelectedUUID(); + if (item_id.isNull()) return; + + LLViewerInventoryItem* wearable_to_move = gInventory.getItem(item_id); + LLAppearanceMgr::getInstance()->moveWearable(wearable_to_move, closer_to_body); +} + +void LLPanelOutfitEdit::toggleAddWearablesPanel() +{ + bool current_visibility = mAddWearablesPanel->getVisible(); + showAddWearablesPanel(!current_visibility); +} + +void LLPanelOutfitEdit::showAddWearablesPanel(bool show_add_wearables) +{ + mAddWearablesPanel->setVisible(show_add_wearables); + + getChild("show_add_wearables_btn")->setValue(show_add_wearables); + + updateFiltersVisibility(); + getChildView("filter_button")->setVisible( show_add_wearables); + + //search filter should be disabled + if (!show_add_wearables) + { + getChild("filter_button")->setValue(false); + + mFolderViewFilterCmbBox->setVisible(false); + mListViewFilterCmbBox->setVisible(false); + + showWearablesFilter(); + + /* + * By default AT_CLOTHING are sorted by (in in MY OUTFITS): + * - by type (types order determined in LLWearableType::EType) + * - each LLWearableType::EType by outer layer on top + * + * In Add More panel AT_CLOTHING should be sorted in a such way: + * - by type (types order determined in LLWearableType::EType) + * - each LLWearableType::EType by name (EXT-8205) + */ + mWearableItemsList->setSortOrder(LLWearableItemsList::E_SORT_BY_TYPE_NAME); + + // Reset mWearableItemsList position to top. See EXT-8180. + mWearableItemsList->goToTop(); + } + else + { + mWearableListManager->populateIfNeeded(); + } + + //switching button bars + getChildView("no_add_wearables_button_bar")->setVisible( !show_add_wearables); + getChildView("add_wearables_button_bar")->setVisible( show_add_wearables); +} + +void LLPanelOutfitEdit::showWearablesFilter() +{ + bool filter_visible = getChild("filter_button")->getValue(); + + getChildView("filter_panel")->setVisible( filter_visible); + + if(!filter_visible) + { + mSearchFilter->clear(); + onSearchEdit(LLStringUtil::null); + } + else + { + mSearchFilter->setFocus(true); + } +} + +void LLPanelOutfitEdit::showWearablesListView() +{ + if(switchPanels(mInventoryItemsPanel, mWearablesListViewPanel)) + { + updateWearablesPanelVerbButtons(); + updateFiltersVisibility(); + mWearableListManager->populateIfNeeded(); + } + mListViewBtn->setToggleState(true); +} + +void LLPanelOutfitEdit::showWearablesFolderView() +{ + if(switchPanels(mWearablesListViewPanel, mInventoryItemsPanel)) + { + updateWearablesPanelVerbButtons(); + updateFiltersVisibility(); + } + mFolderViewBtn->setToggleState(true); +} + +void LLPanelOutfitEdit::updateFiltersVisibility() +{ + mListViewFilterCmbBox->setVisible(mWearablesListViewPanel->getVisible()); + mFolderViewFilterCmbBox->setVisible(mInventoryItemsPanel->getVisible()); +} + +void LLPanelOutfitEdit::onFolderViewFilterCommitted(LLUICtrl* ctrl) +{ + S32 curr_filter_type = mFolderViewFilterCmbBox->getCurrentIndex(); + if (curr_filter_type < 0) return; + + mInventoryItemsPanel->setFilterTypes(mFolderViewItemTypes[curr_filter_type].inventoryMask); + + mSavedFolderState->setApply(true); + mInventoryItemsPanel->getRootFolder()->applyFunctorRecursively(*mSavedFolderState); + + LLOpenFoldersWithSelection opener; + mInventoryItemsPanel->getRootFolder()->applyFunctorRecursively(opener); + mInventoryItemsPanel->getRootFolder()->scrollToShowSelection(); + + if (!LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) + { + llassert(false); // this should have been done on startup + LLInventoryModelBackgroundFetch::instance().start(); + } +} + +void LLPanelOutfitEdit::onListViewFilterCommitted(LLUICtrl* ctrl) +{ + S32 curr_filter_type = mListViewFilterCmbBox->getCurrentIndex(); + if (curr_filter_type < 0) return; + + if (curr_filter_type >= LVIT_SHAPE) + { + mWearableItemsList->setMenuWearableType(LLWearableType::EType(curr_filter_type - LVIT_SHAPE)); + } + mWearableListManager->setFilterCollector(mListViewItemTypes[curr_filter_type]->collector); +} + +void LLPanelOutfitEdit::onSearchEdit(const std::string& string) +{ + if (mSearchString != string) + { + mSearchString = string; + + // Searches are case-insensitive + LLStringUtil::toUpper(mSearchString); + LLStringUtil::trimHead(mSearchString); + } + + if (mSearchString == "") + { + mInventoryItemsPanel->setFilterSubString(LLStringUtil::null); + mWearableItemsList->setFilterSubString(LLStringUtil::null, true); + // re-open folders that were initially open + mSavedFolderState->setApply(true); + mInventoryItemsPanel->getRootFolder()->applyFunctorRecursively(*mSavedFolderState); + LLOpenFoldersWithSelection opener; + mInventoryItemsPanel->getRootFolder()->applyFunctorRecursively(opener); + mInventoryItemsPanel->getRootFolder()->scrollToShowSelection(); + } + + if (!LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) + { + llassert(false); // this should have been done on startup + LLInventoryModelBackgroundFetch::instance().start(); + } + + if (mInventoryItemsPanel->getFilterSubString().empty() && mSearchString.empty()) + { + // current filter and new filter empty, do nothing + return; + } + + // save current folder open state if no filter currently applied + if (mInventoryItemsPanel->getFilterSubString().empty()) + { + mSavedFolderState->setApply(false); + mInventoryItemsPanel->getRootFolder()->applyFunctorRecursively(*mSavedFolderState); + } + + // set new filter string + mInventoryItemsPanel->setFilterSubString(mSearchString); + mWearableItemsList->setFilterSubString(mSearchString, true); +} + +void LLPanelOutfitEdit::onPlusBtnClicked(void) +{ + uuid_vec_t selected_items; + getSelectedItemsUUID(selected_items); + + LLPointer link_waiter = new LLUpdateAppearanceOnDestroy; + + for(uuid_vec_t::iterator iter = selected_items.begin(); iter != selected_items.end(); iter++) + { + LLUUID selected_id = *iter; + if (!selected_id.isNull()) + { + //replacing instead of adding the item + LLAppearanceMgr::getInstance()->wearItemOnAvatar(selected_id, false, true, link_waiter); + } + } +} + +void LLPanelOutfitEdit::onVisibilityChanged(const LLSD &in_visible_chain) +{ + showAddWearablesPanel(false); + mWearableItemsList->resetSelection(); + mInventoryItemsPanel->clearSelection(); + + if (in_visible_chain.asBoolean()) + { + update(); + } + else + { + mWearableListManager->holdProgress(); //list population restarts with visibility + } +} + +void LLPanelOutfitEdit::onAddWearableClicked(void) +{ + LLPanelDummyClothingListItem* item = dynamic_cast(mCOFWearables->getSelectedItem()); + + if(item) + { + showFilteredWearablesListView(item->getWearableType()); + } +} + +void LLPanelOutfitEdit::onReplaceMenuItemClicked(LLUUID selected_item_id) +{ + LLViewerInventoryItem* item = gInventory.getLinkedItem(selected_item_id); + + if (item) + { + showFilteredWearablesListView(item->getWearableType()); + } +} + +void LLPanelOutfitEdit::onShopButtonClicked() +{ + static LLShopURLDispatcher url_resolver; + + // will contain the resultant URL + std::string url; + + if (isAgentAvatarValid()) + { + // try to get wearable type from 'Add More' panel first (EXT-7639) + selection_info_t selection_info = getAddMorePanelSelectionType(); + + LLWearableType::EType type = selection_info.first; + + if (selection_info.second > 1) + { + // the second argument is not important in this case: generic market place will be opened + url = url_resolver.resolveURL(LLWearableType::WT_NONE, SEX_FEMALE); + } + else + { + if (type == LLWearableType::WT_NONE) + { + type = getCOFWearablesSelectionType(); + } + + ESex sex = gAgentAvatarp->getSex(); + + // WT_INVALID comes for attachments + if (type != LLWearableType::WT_INVALID && type != LLWearableType::WT_NONE) + { + url = url_resolver.resolveURL(type, sex); + } + + if (url.empty()) + { + url = url_resolver.resolveURL( + mCOFWearables->getExpandedAccordionAssetType(), sex); + } + } + } + else + { + LL_WARNS() << "Agent avatar is invalid" << LL_ENDL; + + // the second argument is not important in this case: generic market place will be opened + url = url_resolver.resolveURL(LLWearableType::WT_NONE, SEX_FEMALE); + } + + LLWeb::loadURL(url); +} + +LLWearableType::EType LLPanelOutfitEdit::getCOFWearablesSelectionType() const +{ + std::vector selected_items; + LLWearableType::EType type = LLWearableType::WT_NONE; + + mCOFWearables->getSelectedItems(selected_items); + + if (selected_items.size() == 1) + { + LLPanel* item = selected_items.front(); + + // LLPanelDummyClothingListItem is lower then LLPanelInventoryListItemBase in hierarchy tree + if (LLPanelDummyClothingListItem* dummy_item = dynamic_cast(item)) + { + type = dummy_item->getWearableType(); + } + else if (LLPanelInventoryListItemBase* real_item = dynamic_cast(item)) + { + type = real_item->getWearableType(); + } + } + + return type; +} + +LLPanelOutfitEdit::selection_info_t LLPanelOutfitEdit::getAddMorePanelSelectionType() const +{ + selection_info_t result = std::make_pair(LLWearableType::WT_NONE, 0); + + if (mAddWearablesPanel != NULL && mAddWearablesPanel->getVisible()) + { + if (mInventoryItemsPanel != NULL && mInventoryItemsPanel->getVisible()) + { + std::set selected_items = mInventoryItemsPanel->getRootFolder()->getSelectionList(); + + result.second = selected_items.size(); + + if (result.second == 1) + { + result.first = getWearableTypeByItemUUID(static_cast((*selected_items.begin())->getViewModelItem())->getUUID()); + } + } + else if (mWearableItemsList != NULL && mWearableItemsList->getVisible()) + { + std::vector selected_uuids; + mWearableItemsList->getSelectedUUIDs(selected_uuids); + + result.second = selected_uuids.size(); + + if (result.second == 1) + { + result.first = getWearableTypeByItemUUID(selected_uuids.front()); + } + } + } + + return result; +} + +LLWearableType::EType LLPanelOutfitEdit::getWearableTypeByItemUUID(const LLUUID& item_uuid) const +{ + LLViewerInventoryItem* item = gInventory.getLinkedItem(item_uuid); + return (item != NULL) ? item->getWearableType() : LLWearableType::WT_NONE; +} + +void LLPanelOutfitEdit::onRemoveFromOutfitClicked(void) +{ + LLUUID id_to_remove = mCOFWearables->getSelectedUUID(); + LLWearableType::EType type = getWearableTypeByItemUUID(id_to_remove); + + LLAppearanceMgr::getInstance()->removeItemFromAvatar(id_to_remove); + + if (!mCOFWearables->getSelectedItem()) + { + mCOFWearables->selectClothing(type); + } +} + + +void LLPanelOutfitEdit::onEditWearableClicked(void) +{ + LLUUID selected_item_id = mCOFWearables->getSelectedUUID(); + if (selected_item_id.notNull()) + { + gAgentWearables.editWearable(selected_item_id); + } +} + +void LLPanelOutfitEdit::updatePlusButton() +{ + uuid_vec_t selected_items; + getSelectedItemsUUID(selected_items); + if (selected_items.empty()) + { + mPlusBtn->setEnabled(false); + return; + } + + // If any of the selected items are not wearable (due to already being worn OR being of the wrong type), disable the add button. + uuid_vec_t::iterator unwearable_item = std::find_if(selected_items.begin(), selected_items.end(), !boost::bind(&get_can_item_be_worn, _1)); + bool can_add = ( unwearable_item == selected_items.end() ); + + mPlusBtn->setEnabled(can_add); + + LLViewerInventoryItem* first_item(gInventory.getItem(selected_items.front())); + + if (can_add && + first_item && + selected_items.size() == 1 && + first_item->getType() == LLAssetType::AT_BODYPART) + { + mPlusBtn->setToolTip(getString("replace_body_part")); + } + else + { + mPlusBtn->setToolTip(LLStringUtil::null); + } + + /* Removing add to look inline button (not part of mvp for viewer 2) + LLRect btn_rect(current_item->getLocalRect().mRight - 50, + current_item->getLocalRect().mTop, + current_item->getLocalRect().mRight - 30, + current_item->getLocalRect().mBottom); + + mAddToLookBtn->setRect(btn_rect); + mAddToLookBtn->setEnabled(true); + if (!mAddToLookBtn->getVisible()) + { + mAddToLookBtn->setVisible(true); + } + + current_item->addChild(mAddToLookBtn); */ +} + + +void LLPanelOutfitEdit::applyFolderViewFilter(EFolderViewItemType type) +{ + mFolderViewFilterCmbBox->setCurrentByIndex(type); + mFolderViewFilterCmbBox->onCommit(); +} + +void LLPanelOutfitEdit::applyListViewFilter(EListViewItemType type) +{ + mListViewFilterCmbBox->setCurrentByIndex(type); + mListViewFilterCmbBox->onCommit(); +} + +void LLPanelOutfitEdit::filterWearablesBySelectedItem(void) +{ + if (!mAddWearablesPanel->getVisible()) return; + + uuid_vec_t ids; + mCOFWearables->getSelectedUUIDs(ids); + + bool nothing_selected = ids.empty(); + bool one_selected = ids.size() == 1; + bool more_than_one_selected = ids.size() > 1; + bool is_dummy_item = (ids.size() && dynamic_cast(mCOFWearables->getSelectedItem())); + + // selected, expanded accordion tabs and selection in flat list view determine filtering when no item is selected in COF + // selection in flat list view participates in determining filtering because of EXT-7963 + // So the priority of criterions in is: + // 1. Selected accordion tab | IF (any accordion selected) + // | filter_type = selected_accordion_type + // 2. Selected item in flat list view | ELSEIF (any item in flat list view selected) + // | filter_type = selected_item_type + // 3. Expanded accordion tab | ELSEIF (any accordion expanded) + // | filter_type = expanded accordion_type + if (nothing_selected) + { + if (mInventoryItemsPanel->getVisible()) + { + return; + } + showWearablesListView(); + + //selected accordion tab is more priority than expanded tab + //and selected item in flat list view of 'Add more' panel when + //determining filtering + LLAssetType::EType type = mCOFWearables->getSelectedAccordionAssetType(); + if (type == LLAssetType::AT_NONE) + { //no accordion selected + + // when no accordion selected then selected item from flat list view + // has more priority than expanded when determining filtering + LLUUID selected_item_id = mWearableItemsList->getSelectedUUID(); + LLViewerInventoryItem* item = gInventory.getLinkedItem(selected_item_id); + if(item) + { + showFilteredWearablesListView(item->getWearableType()); + return; + } + + // when no accordion selected and no selected items in flat list view + // determine filtering according to expanded accordion + type = mCOFWearables->getExpandedAccordionAssetType(); + } + + switch (type) + { + case LLAssetType::AT_OBJECT: + applyListViewFilter(LVIT_ATTACHMENT); + break; + case LLAssetType::AT_BODYPART: + applyListViewFilter(LVIT_BODYPART); + break; + case LLAssetType::AT_CLOTHING: + default: + applyListViewFilter(LVIT_CLOTHING); + break; + } + + return; + } + + //resetting selection if more than one item is selected + if (more_than_one_selected) + { + if (mInventoryItemsPanel->getVisible()) + { + applyFolderViewFilter(FVIT_ALL); + return; + } + + showWearablesListView(); + applyListViewFilter(LVIT_ALL); + return; + } + + + //filter wearables by a type represented by a dummy item + if (one_selected && is_dummy_item) + { + if (mInventoryItemsPanel->getVisible()) + { + applyFolderViewFilter(FVIT_WEARABLE); + return; + } + + onAddWearableClicked(); + return; + } + + LLViewerInventoryItem* item = gInventory.getItem(ids[0]); + if (!item && ids[0].notNull()) + { + if (mInventoryItemsPanel->getVisible()) + { + applyFolderViewFilter(FVIT_ALL); + return; + } + //Inventory misses an item with non-zero id + showWearablesListView(); + applyListViewFilter(LVIT_ALL); + return; + } + + if (item && one_selected && !is_dummy_item) + { + if (item->isWearableType()) + { + if (mInventoryItemsPanel->getVisible()) + { + applyFolderViewFilter(FVIT_WEARABLE); + return; + } + //single clothing or bodypart item is selected + showFilteredWearablesListView(item->getWearableType()); + return; + } + else + { + if (mInventoryItemsPanel->getVisible()) + { + applyFolderViewFilter(FVIT_ATTACHMENT); + return; + } + //attachment is selected + showWearablesListView(); + applyListViewFilter(LVIT_ATTACHMENT); + return; + } + } + +} + + + +void LLPanelOutfitEdit::update() +{ + mCOFWearables->refresh(); + + updateVerbs(); +} + +bool LLPanelOutfitEdit::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + if (cargo_data == NULL) + { + LL_WARNS() << "cargo_data is NULL" << LL_ENDL; + return true; + } + + switch (cargo_type) + { + case DAD_BODYPART: + case DAD_CLOTHING: + case DAD_OBJECT: + case DAD_LINK: + *accept = ACCEPT_YES_MULTI; + break; + default: + *accept = ACCEPT_NO; + } + + if (drop) + { + LLInventoryItem* item = static_cast(cargo_data); + + if (LLAssetType::lookupIsAssetIDKnowable(item->getType())) + { + mCOFDragAndDropObserver->watchAsset(item->getAssetUUID()); + + /* + * Adding request to wear item. If the item is a link, then getLinkedUUID() will + * return the ID of the linked item. Otherwise it will return the item's ID. The + * second argument is used to delay the appearance update until all dragged items + * are added to optimize user experience. + */ + LLAppearanceMgr::instance().addCOFItemLink(item->getLinkedUUID()); + } + else + { + // if asset id is not available for the item we must wear it immediately (attachments only) + LLAppearanceMgr::instance().addCOFItemLink(item->getLinkedUUID(), new LLUpdateAppearanceAndEditWearableOnDestroy(item->getUUID())); + } + } + + return true; +} + +void LLPanelOutfitEdit::displayCurrentOutfit() +{ + if (!getVisible()) + { + setVisible(true); + } + + updateCurrentOutfitName(); + + update(); +} + +void LLPanelOutfitEdit::updateCurrentOutfitName() +{ + std::string current_outfit_name; + if (LLAppearanceMgr::getInstance()->getBaseOutfitName(current_outfit_name)) + { + mCurrentOutfitName->setText(current_outfit_name); + } + else + { + mCurrentOutfitName->setText(getString("No Outfit")); + } +} + +//private +void LLPanelOutfitEdit::updateVerbs() +{ + bool outfit_is_dirty = LLAppearanceMgr::getInstance()->isOutfitDirty(); + bool outfit_locked = LLAppearanceMgr::getInstance()->isOutfitLocked(); + bool has_baseoutfit = LLAppearanceMgr::getInstance()->getBaseOutfitUUID().notNull(); + + getChildView(SAVE_BTN)->setEnabled(!outfit_locked && outfit_is_dirty); + getChildView(REVERT_BTN)->setEnabled(outfit_is_dirty && has_baseoutfit); + + mStatus->setText(outfit_is_dirty ? getString("unsaved_changes") : getString("now_editing")); + + updateCurrentOutfitName(); + + //updating state of "Wear Item" button previously known as "Plus" button + updatePlusButton(); +} + +bool LLPanelOutfitEdit::switchPanels(LLPanel* switch_from_panel, LLPanel* switch_to_panel) +{ + if(switch_from_panel && switch_to_panel && !switch_to_panel->getVisible()) + { + switch_from_panel->setVisible(false); + switch_to_panel->setVisible(true); + return true; + } + return false; +} + +void LLPanelOutfitEdit::resetAccordionState() +{ + if (mCOFWearables != NULL) + { + mCOFWearables->expandDefaultAccordionTab(); + } + else + { + LL_WARNS() << "mCOFWearables is NULL" << LL_ENDL; + } +} + +void LLPanelOutfitEdit::onAddMoreButtonClicked() +{ + toggleAddWearablesPanel(); + filterWearablesBySelectedItem(); +} + +void LLPanelOutfitEdit::showFilteredWearablesListView(LLWearableType::EType type) +{ + showAddWearablesPanel(true); + showWearablesListView(); + + //e_list_view_item_type implicitly contains LLWearableType::EType starting from LVIT_SHAPE + applyListViewFilter(static_cast(LVIT_SHAPE + type)); + mWearableItemsList->setMenuWearableType(type); +} + +static void update_status_widget_rect(LLView * widget, S32 right_border) +{ + LLRect rect = widget->getRect(); + rect.mRight = right_border; + + widget->setShape(rect); +} + +void LLPanelOutfitEdit::onOutfitChanging(bool started) +{ + static LLLoadingIndicator* indicator = getChild("edit_outfit_loading_indicator"); + static LLView* status_panel = getChild("outfit_name_and_status"); + static S32 indicator_delta = status_panel->getRect().getWidth() - indicator->getRect().mLeft; + + S32 delta = started ? indicator_delta : 0; + S32 right_border = status_panel->getRect().getWidth() - delta; + + if (mCurrentOutfitName) + update_status_widget_rect(mCurrentOutfitName, right_border); + if (mStatus) + update_status_widget_rect(mStatus, right_border); + + indicator->setVisible(started); +} + +void LLPanelOutfitEdit::getCurrentItemUUID(LLUUID& selected_id) +{ + if (mInventoryItemsPanel->getVisible()) + { + LLFolderViewItem* curr_item = mInventoryItemsPanel->getRootFolder()->getCurSelectedItem(); + if (!curr_item) return; + + LLFolderViewModelItemInventory* listenerp = static_cast(curr_item->getViewModelItem()); + if (!listenerp) return; + + selected_id = listenerp->getUUID(); + } + else if (mWearablesListViewPanel->getVisible()) + { + selected_id = mWearableItemsList->getSelectedUUID(); + } +} + + +void LLPanelOutfitEdit::getSelectedItemsUUID(uuid_vec_t& uuid_list) +{ + void (uuid_vec_t::* tmp)(LLUUID const &) = &uuid_vec_t::push_back; + if (mInventoryItemsPanel->getVisible()) + { + std::set item_set = mInventoryItemsPanel->getRootFolder()->getSelectionList(); + for (std::set::iterator it = item_set.begin(), end_it = item_set.end(); + it != end_it; + ++it) + { + uuid_list.push_back(static_cast((*it)->getViewModelItem())->getUUID()); + } + } + else if (mWearablesListViewPanel->getVisible()) + { + std::vector item_set; + mWearableItemsList->getSelectedValues(item_set); + + std::for_each(item_set.begin(), item_set.end(), boost::bind( tmp, &uuid_list, boost::bind(&LLSD::asUUID, _1 ))); + } + +// return selected_id; +} + +void LLPanelOutfitEdit::onCOFChanged() +{ + //the panel is only updated when is visible to a user + + // BAP - this check has to be removed because otherwise item name + // changes made when the panel is not visible will not be + // propagated to the panel. + // if (!isInVisibleChain()) return; + + update(); +} + +void LLPanelOutfitEdit::updateWearablesPanelVerbButtons() +{ + if(mWearablesListViewPanel->getVisible()) + { + mFolderViewBtn->setToggleState(false); + mFolderViewBtn->setImageOverlay(getString("folder_view_off"), mFolderViewBtn->getImageOverlayHAlign()); + mListViewBtn->setImageOverlay(getString("list_view_on"), mListViewBtn->getImageOverlayHAlign()); + } + else if(mInventoryItemsPanel->getVisible()) + { + mListViewBtn->setToggleState(false); + mListViewBtn->setImageOverlay(getString("list_view_off"), mListViewBtn->getImageOverlayHAlign()); + mFolderViewBtn->setImageOverlay(getString("folder_view_on"), mFolderViewBtn->getImageOverlayHAlign()); + } +} + +void LLPanelOutfitEdit::saveListSelection() +{ + if(mWearablesListViewPanel->getVisible()) + { + std::set selected_ids = mInventoryItemsPanel->getRootFolder()->getSelectionList(); + + if(!selected_ids.size()) return; + + for (std::set::const_iterator item_id = selected_ids.begin(); item_id != selected_ids.end(); ++item_id) + { + mWearableItemsList->selectItemByUUID(static_cast((*item_id)->getViewModelItem())->getUUID(), true); + } + mWearableItemsList->scrollToShowFirstSelectedItem(); + } + else if(mInventoryItemsPanel->getVisible()) + { + std::vector selected_ids; + mWearableItemsList->getSelectedUUIDs(selected_ids); + + if(!selected_ids.size()) return; + + mInventoryItemsPanel->clearSelection(); + LLFolderView* root = mInventoryItemsPanel->getRootFolder(); + + if(!root) return; + + for(std::vector::const_iterator item_id = selected_ids.begin(); item_id != selected_ids.end(); ++item_id) + { + LLFolderViewItem* item = mInventoryItemsPanel->getItemByID(*item_id); + if (!item) continue; + + LLFolderViewFolder* parent = item->getParentFolder(); + if(parent) + { + parent->setOpenArrangeRecursively(true, LLFolderViewFolder::RECURSE_UP); + } + mInventoryItemsPanel->getRootFolder()->changeSelection(item, true); + } + mInventoryItemsPanel->getRootFolder()->scrollToShowSelection(); + } +} + +void LLPanelOutfitEdit::saveOutfit(bool as_new) +{ + LLPanelOutfitsInventory* panel_outfits_inventory = LLPanelOutfitsInventory::findInstance(); + if (panel_outfits_inventory) + { + panel_outfits_inventory->saveOutfit(as_new); + } +} + +// EOF diff --git a/indra/newview/llpaneloutfitedit.h b/indra/newview/llpaneloutfitedit.h index fbc1576cf5..384b7faee4 100644 --- a/indra/newview/llpaneloutfitedit.h +++ b/indra/newview/llpaneloutfitedit.h @@ -1,245 +1,245 @@ -/** - * @file llpaneloutfitedit.h - * @brief Displays outfit edit information in Side Tray. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELOUTFITEDIT_H -#define LL_LLPANELOUTFITEDIT_H - -#include "llpanel.h" - -#include "v3dmath.h" -#include "lluuid.h" - -#include "lliconctrl.h" - -#include "llremoteparcelrequest.h" -#include "llinventory.h" -#include "llinventoryfunctions.h" -#include "llinventorymodel.h" -#include "llwearableitemslist.h" - -class LLButton; -class LLCOFWearables; -class LLComboBox; -class LLTextBox; -class LLInventoryCategory; -class LLOutfitObserver; -class LLCOFDragAndDropObserver; -class LLInventoryPanel; -class LLSaveFolderState; -class LLFolderViewItem; -class LLScrollListCtrl; -class LLToggleableMenu; -class LLFilterEditor; -class LLFilteredWearableListManager; -class LLMenuButton; -class LLMenuGL; -class LLFindNonLinksByMask; -class LLFindWearablesOfType; -class LLWearableItemTypeNameComparator; - -class LLPanelOutfitEdit : public LLPanel -{ - LOG_CLASS(LLPanelOutfitEdit); -public: - - // NOTE: initialize mFolderViewItemTypes at the index of any new enum you add in the LLPanelOutfitEdit() constructor - typedef enum e_folder_view_item_type - { - FVIT_ALL = 0, - FVIT_WEARABLE, // clothing or shape - FVIT_ATTACHMENT, - NUM_FOLDER_VIEW_ITEM_TYPES - } EFolderViewItemType; - - //should reflect order from LLWearableType::EType - typedef enum e_list_view_item_type - { - LVIT_ALL = 0, - LVIT_CLOTHING, - LVIT_BODYPART, - LVIT_ATTACHMENT, - LVIT_SHAPE, - LVIT_SKIN, - LVIT_HAIR, - LVIT_EYES, - LVIT_SHIRT, - LVIT_PANTS, - LVIT_SHOES, - LVIT_SOCKS, - LVIT_JACKET, - LVIT_GLOVES, - LVIT_UNDERSHIRT, - LVIT_UNDERPANTS, - LVIT_SKIRT, - LVIT_ALPHA, - LVIT_TATTOO, - LVIT_PHYSICS, - LVIT_UNIVERSAL, - NUM_LIST_VIEW_ITEM_TYPES - } EListViewItemType; - - struct LLLookItemType { - std::string displayName; - U64 inventoryMask; - LLLookItemType() : displayName("NONE"), inventoryMask(0) {} - LLLookItemType(std::string name, U64 mask) : displayName(name), inventoryMask(mask) {} - }; - - struct LLFilterItem { - std::string displayName; - LLInventoryCollectFunctor* collector; - LLFilterItem() : displayName("NONE"), collector(NULL) {} - LLFilterItem(std::string name, LLInventoryCollectFunctor* _collector) : displayName(name), collector(_collector) {} - ~LLFilterItem() { delete collector; } - - //the struct is not supposed to by copied, either way the destructor kills collector - //LLPointer is not used as it requires LLInventoryCollectFunctor to extend LLRefCount what it doesn't do - private: - LLFilterItem(const LLFilterItem& filter_item) {}; - }; - - LLPanelOutfitEdit(); - /*virtual*/ ~LLPanelOutfitEdit(); - - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); - - void moveWearable(bool closer_to_body); - - void toggleAddWearablesPanel(); - void showAddWearablesPanel(bool show__add_wearables); - - //following methods operate with "add wearables" panel - void showWearablesFilter(); - void showWearablesListView(); - void showWearablesFolderView(); - - void updateFiltersVisibility(); - - void onFolderViewFilterCommitted(LLUICtrl* ctrl); - void onListViewFilterCommitted(LLUICtrl* ctrl); - void onSearchEdit(const std::string& string); - void updatePlusButton(); - void onPlusBtnClicked(void); - - void onVisibilityChanged(const LLSD &in_visible_chain); - - void applyFolderViewFilter(EFolderViewItemType type); - void applyListViewFilter(EListViewItemType type); - - /** - * Filter items in views of Add Wearables Panel and show appropriate view depending on currently selected COF item(s) - * No COF items selected - shows the folder view, reset filter - * 1 COF item selected - shows the list view and filters wearables there by a wearable type of the selected item - * More than 1 COF item selected - shows the list view and filters it by a type of the selected item (attachment or clothing) - */ - void filterWearablesBySelectedItem(void); - - void onRemoveFromOutfitClicked(void); - void onEditWearableClicked(void); - void onAddWearableClicked(void); - void onReplaceMenuItemClicked(LLUUID selected_item_id); - void onShopButtonClicked(); - - void displayCurrentOutfit(); - void updateCurrentOutfitName(); - - void update(); - - void updateVerbs(); - /** - * @brief Helper function. Shows one panel instead of another. - * If panels already switched does nothing and returns false. - * @param switch_from_panel panel to hide - * @param switch_to_panel panel to show - * @retun returns true if switching happened, false if not. - */ - bool switchPanels(LLPanel* switch_from_panel, LLPanel* switch_to_panel); - - void resetAccordionState(); - - virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg); - -private: - void onAddMoreButtonClicked(); - void showFilteredWearablesListView(LLWearableType::EType type); - void onOutfitChanging(bool started); - void getSelectedItemsUUID(uuid_vec_t& uuid_list); - void getCurrentItemUUID(LLUUID& selected_id); - void onCOFChanged(); - void saveOutfit(bool as_new = false); - - /** - * Method preserves selection while switching between folder/list view modes - */ - void saveListSelection(); - - void updateWearablesPanelVerbButtons(); - - typedef std::pair selection_info_t; - - LLWearableType::EType getCOFWearablesSelectionType() const; - selection_info_t getAddMorePanelSelectionType() const; - LLWearableType::EType getWearableTypeByItemUUID(const LLUUID& item_uuid) const; - - LLTextBox* mCurrentOutfitName; - LLTextBox* mStatus; - LLInventoryPanel* mInventoryItemsPanel; - LLFilterEditor* mSearchFilter; - LLSaveFolderState* mSavedFolderState; - std::string mSearchString; - LLButton* mFolderViewBtn; - LLButton* mListViewBtn; - LLButton* mPlusBtn; - LLPanel* mAddWearablesPanel; - - LLComboBox* mFolderViewFilterCmbBox; - LLComboBox* mListViewFilterCmbBox; - - LLFilteredWearableListManager* mWearableListManager; - LLWearableItemsList* mWearableItemsList; - LLPanel* mWearablesListViewPanel; - LLWearableItemTypeNameComparator* mWearableListViewItemsComparator; - - LLCOFDragAndDropObserver* mCOFDragAndDropObserver; - - std::vector mFolderViewItemTypes; - std::vector mListViewItemTypes; - - LLCOFWearables* mCOFWearables; - LLToggleableMenu* mGearMenu; - LLToggleableMenu* mAddWearablesGearMenu; - bool mInitialized; - LLMenuButton* mWearablesGearMenuBtn; - LLMenuButton* mGearMenuBtn; - -}; - -#endif // LL_LLPANELOUTFITEDIT_H +/** + * @file llpaneloutfitedit.h + * @brief Displays outfit edit information in Side Tray. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELOUTFITEDIT_H +#define LL_LLPANELOUTFITEDIT_H + +#include "llpanel.h" + +#include "v3dmath.h" +#include "lluuid.h" + +#include "lliconctrl.h" + +#include "llremoteparcelrequest.h" +#include "llinventory.h" +#include "llinventoryfunctions.h" +#include "llinventorymodel.h" +#include "llwearableitemslist.h" + +class LLButton; +class LLCOFWearables; +class LLComboBox; +class LLTextBox; +class LLInventoryCategory; +class LLOutfitObserver; +class LLCOFDragAndDropObserver; +class LLInventoryPanel; +class LLSaveFolderState; +class LLFolderViewItem; +class LLScrollListCtrl; +class LLToggleableMenu; +class LLFilterEditor; +class LLFilteredWearableListManager; +class LLMenuButton; +class LLMenuGL; +class LLFindNonLinksByMask; +class LLFindWearablesOfType; +class LLWearableItemTypeNameComparator; + +class LLPanelOutfitEdit : public LLPanel +{ + LOG_CLASS(LLPanelOutfitEdit); +public: + + // NOTE: initialize mFolderViewItemTypes at the index of any new enum you add in the LLPanelOutfitEdit() constructor + typedef enum e_folder_view_item_type + { + FVIT_ALL = 0, + FVIT_WEARABLE, // clothing or shape + FVIT_ATTACHMENT, + NUM_FOLDER_VIEW_ITEM_TYPES + } EFolderViewItemType; + + //should reflect order from LLWearableType::EType + typedef enum e_list_view_item_type + { + LVIT_ALL = 0, + LVIT_CLOTHING, + LVIT_BODYPART, + LVIT_ATTACHMENT, + LVIT_SHAPE, + LVIT_SKIN, + LVIT_HAIR, + LVIT_EYES, + LVIT_SHIRT, + LVIT_PANTS, + LVIT_SHOES, + LVIT_SOCKS, + LVIT_JACKET, + LVIT_GLOVES, + LVIT_UNDERSHIRT, + LVIT_UNDERPANTS, + LVIT_SKIRT, + LVIT_ALPHA, + LVIT_TATTOO, + LVIT_PHYSICS, + LVIT_UNIVERSAL, + NUM_LIST_VIEW_ITEM_TYPES + } EListViewItemType; + + struct LLLookItemType { + std::string displayName; + U64 inventoryMask; + LLLookItemType() : displayName("NONE"), inventoryMask(0) {} + LLLookItemType(std::string name, U64 mask) : displayName(name), inventoryMask(mask) {} + }; + + struct LLFilterItem { + std::string displayName; + LLInventoryCollectFunctor* collector; + LLFilterItem() : displayName("NONE"), collector(NULL) {} + LLFilterItem(std::string name, LLInventoryCollectFunctor* _collector) : displayName(name), collector(_collector) {} + ~LLFilterItem() { delete collector; } + + //the struct is not supposed to by copied, either way the destructor kills collector + //LLPointer is not used as it requires LLInventoryCollectFunctor to extend LLRefCount what it doesn't do + private: + LLFilterItem(const LLFilterItem& filter_item) {}; + }; + + LLPanelOutfitEdit(); + /*virtual*/ ~LLPanelOutfitEdit(); + + /*virtual*/ bool postBuild(); + /*virtual*/ void onOpen(const LLSD& key); + + void moveWearable(bool closer_to_body); + + void toggleAddWearablesPanel(); + void showAddWearablesPanel(bool show__add_wearables); + + //following methods operate with "add wearables" panel + void showWearablesFilter(); + void showWearablesListView(); + void showWearablesFolderView(); + + void updateFiltersVisibility(); + + void onFolderViewFilterCommitted(LLUICtrl* ctrl); + void onListViewFilterCommitted(LLUICtrl* ctrl); + void onSearchEdit(const std::string& string); + void updatePlusButton(); + void onPlusBtnClicked(void); + + void onVisibilityChanged(const LLSD &in_visible_chain); + + void applyFolderViewFilter(EFolderViewItemType type); + void applyListViewFilter(EListViewItemType type); + + /** + * Filter items in views of Add Wearables Panel and show appropriate view depending on currently selected COF item(s) + * No COF items selected - shows the folder view, reset filter + * 1 COF item selected - shows the list view and filters wearables there by a wearable type of the selected item + * More than 1 COF item selected - shows the list view and filters it by a type of the selected item (attachment or clothing) + */ + void filterWearablesBySelectedItem(void); + + void onRemoveFromOutfitClicked(void); + void onEditWearableClicked(void); + void onAddWearableClicked(void); + void onReplaceMenuItemClicked(LLUUID selected_item_id); + void onShopButtonClicked(); + + void displayCurrentOutfit(); + void updateCurrentOutfitName(); + + void update(); + + void updateVerbs(); + /** + * @brief Helper function. Shows one panel instead of another. + * If panels already switched does nothing and returns false. + * @param switch_from_panel panel to hide + * @param switch_to_panel panel to show + * @retun returns true if switching happened, false if not. + */ + bool switchPanels(LLPanel* switch_from_panel, LLPanel* switch_to_panel); + + void resetAccordionState(); + + virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + +private: + void onAddMoreButtonClicked(); + void showFilteredWearablesListView(LLWearableType::EType type); + void onOutfitChanging(bool started); + void getSelectedItemsUUID(uuid_vec_t& uuid_list); + void getCurrentItemUUID(LLUUID& selected_id); + void onCOFChanged(); + void saveOutfit(bool as_new = false); + + /** + * Method preserves selection while switching between folder/list view modes + */ + void saveListSelection(); + + void updateWearablesPanelVerbButtons(); + + typedef std::pair selection_info_t; + + LLWearableType::EType getCOFWearablesSelectionType() const; + selection_info_t getAddMorePanelSelectionType() const; + LLWearableType::EType getWearableTypeByItemUUID(const LLUUID& item_uuid) const; + + LLTextBox* mCurrentOutfitName; + LLTextBox* mStatus; + LLInventoryPanel* mInventoryItemsPanel; + LLFilterEditor* mSearchFilter; + LLSaveFolderState* mSavedFolderState; + std::string mSearchString; + LLButton* mFolderViewBtn; + LLButton* mListViewBtn; + LLButton* mPlusBtn; + LLPanel* mAddWearablesPanel; + + LLComboBox* mFolderViewFilterCmbBox; + LLComboBox* mListViewFilterCmbBox; + + LLFilteredWearableListManager* mWearableListManager; + LLWearableItemsList* mWearableItemsList; + LLPanel* mWearablesListViewPanel; + LLWearableItemTypeNameComparator* mWearableListViewItemsComparator; + + LLCOFDragAndDropObserver* mCOFDragAndDropObserver; + + std::vector mFolderViewItemTypes; + std::vector mListViewItemTypes; + + LLCOFWearables* mCOFWearables; + LLToggleableMenu* mGearMenu; + LLToggleableMenu* mAddWearablesGearMenu; + bool mInitialized; + LLMenuButton* mWearablesGearMenuBtn; + LLMenuButton* mGearMenuBtn; + +}; + +#endif // LL_LLPANELOUTFITEDIT_H diff --git a/indra/newview/llpaneloutfitsinventory.cpp b/indra/newview/llpaneloutfitsinventory.cpp index 1e66851257..5b595a48b7 100644 --- a/indra/newview/llpaneloutfitsinventory.cpp +++ b/indra/newview/llpaneloutfitsinventory.cpp @@ -1,381 +1,381 @@ -/** - * @file llpaneloutfitsinventory.cpp - * @brief Outfits inventory panel - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpaneloutfitsinventory.h" - -#include "llagentwearables.h" -#include "llappearancemgr.h" -#include "llfloatersidepanelcontainer.h" -#include "llinventoryfunctions.h" -#include "llinventorymodelbackgroundfetch.h" -#include "llnotificationsutil.h" -#include "lloutfitgallery.h" -#include "lloutfitobserver.h" -#include "lloutfitslist.h" -#include "llpanelappearancetab.h" -#include "llpanelwearing.h" -#include "llsidepanelappearance.h" -#include "lltabcontainer.h" -#include "llviewercontrol.h" -#include "llviewerfoldertype.h" - -static const std::string OUTFITS_TAB_NAME = "outfitslist_tab"; -static const std::string OUTFIT_GALLERY_TAB_NAME = "outfit_gallery_tab"; -static const std::string COF_TAB_NAME = "cof_tab"; - -static const std::string SAVE_AS_BTN("save_as_btn"); -static const std::string SAVE_BTN("save_btn"); - -static LLPanelInjector t_inventory("panel_outfits_inventory"); - -LLPanelOutfitsInventory::LLPanelOutfitsInventory() : - mMyOutfitsPanel(NULL), - mCurrentOutfitPanel(NULL), - mActivePanel(NULL), - mAppearanceTabs(NULL), - mInitialized(false) -{ - gAgentWearables.addLoadedCallback(boost::bind(&LLPanelOutfitsInventory::onWearablesLoaded, this)); - gAgentWearables.addLoadingStartedCallback(boost::bind(&LLPanelOutfitsInventory::onWearablesLoading, this)); - - LLOutfitObserver& observer = LLOutfitObserver::instance(); - observer.addBOFChangedCallback(boost::bind(&LLPanelOutfitsInventory::updateVerbs, this)); - observer.addCOFChangedCallback(boost::bind(&LLPanelOutfitsInventory::updateVerbs, this)); - observer.addOutfitLockChangedCallback(boost::bind(&LLPanelOutfitsInventory::updateVerbs, this)); -} - -LLPanelOutfitsInventory::~LLPanelOutfitsInventory() -{ - if (mAppearanceTabs && mInitialized) - { - gSavedSettings.setS32("LastAppearanceTab", mAppearanceTabs->getCurrentPanelIndex()); - } -} - -// virtual -bool LLPanelOutfitsInventory::postBuild() -{ - initTabPanels(); - initListCommandsHandlers(); - - // Fetch your outfits folder so that the links are in memory. - // ( This is only necessary if we want to show a warning if a user deletes an item that has a - // a link in an outfit, see "ConfirmItemDeleteHasLinks". ) - - const LLUUID &outfits_cat = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); - if (outfits_cat.notNull()) - { - LLInventoryModelBackgroundFetch::instance().start(outfits_cat); - } - - getChild(SAVE_BTN)->setCommitCallback(boost::bind(&LLPanelOutfitsInventory::saveOutfit, this, false)); - getChild(SAVE_AS_BTN)->setCommitCallback(boost::bind(&LLPanelOutfitsInventory::saveOutfit, this, true)); - - return true; -} - -// virtual -void LLPanelOutfitsInventory::onOpen(const LLSD& key) -{ - if (!mInitialized) - { - LLSidepanelAppearance* panel_appearance = getAppearanceSP(); - if (panel_appearance) - { - // *TODO: move these methods to LLPanelOutfitsInventory? - panel_appearance->fetchInventory(); - panel_appearance->refreshCurrentOutfitName(); - } - - if (!mAppearanceTabs->selectTab(gSavedSettings.getS32("LastAppearanceTab"))) - mAppearanceTabs->selectFirstTab(); - - mInitialized = true; - } - - // Make sure we know which tab is selected, update the filter, - // and update verbs. - onTabChange(); - - // *TODO: Auto open the first outfit newly created so new users can see sample outfit contents - /* - static bool should_open_outfit = true; - if (should_open_outfit && gAgent.isFirstLogin()) - { - LLInventoryPanel* outfits_panel = getChild(OUTFITS_TAB_NAME); - if (outfits_panel) - { - LLUUID my_outfits_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); - LLFolderViewFolder* my_outfits_folder = outfits_panel->getRootFolder()->getFolderByID(my_outfits_id); - if (my_outfits_folder) - { - LLFolderViewFolder* first_outfit = dynamic_cast(my_outfits_folder->getFirstChild()); - if (first_outfit) - { - first_outfit->setOpen(true); - } - } - } - } - should_open_outfit = false; - */ -} - -void LLPanelOutfitsInventory::updateVerbs() -{ - if (mListCommands) - { - updateListCommands(); - } -} - -// virtual -void LLPanelOutfitsInventory::onSearchEdit(const std::string& string) -{ - if (!mActivePanel) return; - - if (!LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) - { - llassert(false); // this should have been done on startup - LLInventoryModelBackgroundFetch::instance().start(); - } - - // set new filter string - mActivePanel->setFilterSubString(string); -} - -void LLPanelOutfitsInventory::onWearButtonClick() -{ - if(isOutfitsListPanelActive()) - { - if (mMyOutfitsPanel->hasItemSelected()) - { - mMyOutfitsPanel->wearSelectedItems(); - } - else - { - mMyOutfitsPanel->performAction("replaceoutfit"); - } - } - else if(isOutfitsGalleryPanelActive()) - { - mOutfitGalleryPanel->wearSelectedOutfit(); - } - -} - -bool LLPanelOutfitsInventory::onSaveCommit(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (0 == option) - { - std::string outfit_name = response["message"].asString(); - LLStringUtil::trim(outfit_name); - if( !outfit_name.empty() ) - { - LLAppearanceMgr::getInstance()->makeNewOutfitLinks(outfit_name); - - LLSidepanelAppearance* panel_appearance = getAppearanceSP(); - if (panel_appearance) - { - panel_appearance->showOutfitsInventoryPanel(); - } - - if (mAppearanceTabs) - { - mAppearanceTabs->selectTabByName(OUTFITS_TAB_NAME); - } - } - } - - return false; -} - -void LLPanelOutfitsInventory::onSave() -{ - std::string outfit_name; - - if (!LLAppearanceMgr::getInstance()->getBaseOutfitName(outfit_name)) - { - outfit_name = LLViewerFolderType::lookupNewCategoryName(LLFolderType::FT_OUTFIT); - } - - LLSD args; - args["DESC"] = outfit_name; - - LLSD payload; - //payload["ids"].append(*it); - - LLNotificationsUtil::add("SaveOutfitAs", args, payload, boost::bind(&LLPanelOutfitsInventory::onSaveCommit, this, _1, _2)); -} - -//static -LLPanelOutfitsInventory* LLPanelOutfitsInventory::findInstance() -{ - return dynamic_cast(LLFloaterSidePanelContainer::getPanel("appearance", "panel_outfits_inventory")); -} - -void LLPanelOutfitsInventory::openApearanceTab(const std::string& tab_name) -{ - if (!mAppearanceTabs) return; - mAppearanceTabs->selectTabByName(tab_name); -} - -////////////////////////////////////////////////////////////////////////////////// -// List Commands // - -void LLPanelOutfitsInventory::initListCommandsHandlers() -{ - mListCommands = getChild("bottom_panel"); - mListCommands->childSetAction("wear_btn", boost::bind(&LLPanelOutfitsInventory::onWearButtonClick, this)); - mMyOutfitsPanel->childSetAction("trash_btn", boost::bind(&LLPanelOutfitsInventory::onTrashButtonClick, this)); - mOutfitGalleryPanel->childSetAction("trash_btn", boost::bind(&LLPanelOutfitsInventory::onTrashButtonClick, this)); -} - -void LLPanelOutfitsInventory::updateListCommands() -{ - bool trash_enabled = isActionEnabled("delete"); - bool wear_enabled = isActionEnabled("wear"); - bool wear_visible = !isCOFPanelActive(); - bool make_outfit_enabled = isActionEnabled("save_outfit"); - - LLButton* wear_btn = mListCommands->getChild("wear_btn"); - mMyOutfitsPanel->childSetEnabled("trash_btn", trash_enabled); - mOutfitGalleryPanel->childSetEnabled("trash_btn", trash_enabled); - wear_btn->setEnabled(wear_enabled); - wear_btn->setVisible(wear_visible); - getChild(SAVE_BTN)->setEnabled(make_outfit_enabled); - wear_btn->setToolTip(getString((!isOutfitsGalleryPanelActive() && mMyOutfitsPanel->hasItemSelected()) ? "wear_items_tooltip" : "wear_outfit_tooltip")); -} - -void LLPanelOutfitsInventory::onTrashButtonClick() -{ - if(isOutfitsListPanelActive()) - { - mMyOutfitsPanel->removeSelected(); - } - else if(isOutfitsGalleryPanelActive()) - { - mOutfitGalleryPanel->removeSelected(); - } -} - -bool LLPanelOutfitsInventory::isActionEnabled(const LLSD& userdata) -{ - return mActivePanel && mActivePanel->isActionEnabled(userdata); -} - -// List Commands // -////////////////////////////////////////////////////////////////////////////////// - -////////////////////////////////////////////////////////////////////////////////// -// Tab panels // - -void LLPanelOutfitsInventory::initTabPanels() -{ - //TODO: Add LLOutfitGallery change callback - mCurrentOutfitPanel = findChild(COF_TAB_NAME); - mCurrentOutfitPanel->setSelectionChangeCallback(boost::bind(&LLPanelOutfitsInventory::updateVerbs, this)); - - mMyOutfitsPanel = findChild(OUTFITS_TAB_NAME); - mMyOutfitsPanel->setSelectionChangeCallback(boost::bind(&LLPanelOutfitsInventory::updateVerbs, this)); - - mOutfitGalleryPanel = findChild(OUTFIT_GALLERY_TAB_NAME); - mOutfitGalleryPanel->setSelectionChangeCallback(boost::bind(&LLPanelOutfitsInventory::updateVerbs, this)); - - mAppearanceTabs = getChild("appearance_tabs"); - mAppearanceTabs->setCommitCallback(boost::bind(&LLPanelOutfitsInventory::onTabChange, this)); -} - -void LLPanelOutfitsInventory::onTabChange() -{ - if (!mAppearanceTabs) return; - mActivePanel = dynamic_cast(mAppearanceTabs->getCurrentPanel()); - if (!mActivePanel) return; - - mActivePanel->checkFilterSubString(); - mActivePanel->onOpen(LLSD()); - - updateVerbs(); -} - -bool LLPanelOutfitsInventory::isCOFPanelActive() const -{ - if (!mActivePanel) return false; - - return mActivePanel->getName() == COF_TAB_NAME; -} - -bool LLPanelOutfitsInventory::isOutfitsListPanelActive() const -{ - if (!mActivePanel) return false; - - return mActivePanel->getName() == OUTFITS_TAB_NAME; -} - -bool LLPanelOutfitsInventory::isOutfitsGalleryPanelActive() const -{ - if (!mActivePanel) return false; - - return mActivePanel->getName() == OUTFIT_GALLERY_TAB_NAME; -} - -void LLPanelOutfitsInventory::setWearablesLoading(bool val) -{ - updateVerbs(); -} - -void LLPanelOutfitsInventory::onWearablesLoaded() -{ - setWearablesLoading(false); -} - -void LLPanelOutfitsInventory::onWearablesLoading() -{ - setWearablesLoading(true); -} - -// static -LLSidepanelAppearance* LLPanelOutfitsInventory::getAppearanceSP() -{ - LLSidepanelAppearance* panel_appearance = - dynamic_cast(LLFloaterSidePanelContainer::getPanel("appearance")); - return panel_appearance; -} - -void LLPanelOutfitsInventory::saveOutfit(bool as_new) -{ - if (!as_new && LLAppearanceMgr::getInstance()->updateBaseOutfit()) - { - // we don't need to ask for an outfit name, and updateBaseOutfit() successfully saved. - // If updateBaseOutfit fails, ask for an outfit name anyways - return; - } - - onSave(); -} +/** + * @file llpaneloutfitsinventory.cpp + * @brief Outfits inventory panel + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpaneloutfitsinventory.h" + +#include "llagentwearables.h" +#include "llappearancemgr.h" +#include "llfloatersidepanelcontainer.h" +#include "llinventoryfunctions.h" +#include "llinventorymodelbackgroundfetch.h" +#include "llnotificationsutil.h" +#include "lloutfitgallery.h" +#include "lloutfitobserver.h" +#include "lloutfitslist.h" +#include "llpanelappearancetab.h" +#include "llpanelwearing.h" +#include "llsidepanelappearance.h" +#include "lltabcontainer.h" +#include "llviewercontrol.h" +#include "llviewerfoldertype.h" + +static const std::string OUTFITS_TAB_NAME = "outfitslist_tab"; +static const std::string OUTFIT_GALLERY_TAB_NAME = "outfit_gallery_tab"; +static const std::string COF_TAB_NAME = "cof_tab"; + +static const std::string SAVE_AS_BTN("save_as_btn"); +static const std::string SAVE_BTN("save_btn"); + +static LLPanelInjector t_inventory("panel_outfits_inventory"); + +LLPanelOutfitsInventory::LLPanelOutfitsInventory() : + mMyOutfitsPanel(NULL), + mCurrentOutfitPanel(NULL), + mActivePanel(NULL), + mAppearanceTabs(NULL), + mInitialized(false) +{ + gAgentWearables.addLoadedCallback(boost::bind(&LLPanelOutfitsInventory::onWearablesLoaded, this)); + gAgentWearables.addLoadingStartedCallback(boost::bind(&LLPanelOutfitsInventory::onWearablesLoading, this)); + + LLOutfitObserver& observer = LLOutfitObserver::instance(); + observer.addBOFChangedCallback(boost::bind(&LLPanelOutfitsInventory::updateVerbs, this)); + observer.addCOFChangedCallback(boost::bind(&LLPanelOutfitsInventory::updateVerbs, this)); + observer.addOutfitLockChangedCallback(boost::bind(&LLPanelOutfitsInventory::updateVerbs, this)); +} + +LLPanelOutfitsInventory::~LLPanelOutfitsInventory() +{ + if (mAppearanceTabs && mInitialized) + { + gSavedSettings.setS32("LastAppearanceTab", mAppearanceTabs->getCurrentPanelIndex()); + } +} + +// virtual +bool LLPanelOutfitsInventory::postBuild() +{ + initTabPanels(); + initListCommandsHandlers(); + + // Fetch your outfits folder so that the links are in memory. + // ( This is only necessary if we want to show a warning if a user deletes an item that has a + // a link in an outfit, see "ConfirmItemDeleteHasLinks". ) + + const LLUUID &outfits_cat = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + if (outfits_cat.notNull()) + { + LLInventoryModelBackgroundFetch::instance().start(outfits_cat); + } + + getChild(SAVE_BTN)->setCommitCallback(boost::bind(&LLPanelOutfitsInventory::saveOutfit, this, false)); + getChild(SAVE_AS_BTN)->setCommitCallback(boost::bind(&LLPanelOutfitsInventory::saveOutfit, this, true)); + + return true; +} + +// virtual +void LLPanelOutfitsInventory::onOpen(const LLSD& key) +{ + if (!mInitialized) + { + LLSidepanelAppearance* panel_appearance = getAppearanceSP(); + if (panel_appearance) + { + // *TODO: move these methods to LLPanelOutfitsInventory? + panel_appearance->fetchInventory(); + panel_appearance->refreshCurrentOutfitName(); + } + + if (!mAppearanceTabs->selectTab(gSavedSettings.getS32("LastAppearanceTab"))) + mAppearanceTabs->selectFirstTab(); + + mInitialized = true; + } + + // Make sure we know which tab is selected, update the filter, + // and update verbs. + onTabChange(); + + // *TODO: Auto open the first outfit newly created so new users can see sample outfit contents + /* + static bool should_open_outfit = true; + if (should_open_outfit && gAgent.isFirstLogin()) + { + LLInventoryPanel* outfits_panel = getChild(OUTFITS_TAB_NAME); + if (outfits_panel) + { + LLUUID my_outfits_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + LLFolderViewFolder* my_outfits_folder = outfits_panel->getRootFolder()->getFolderByID(my_outfits_id); + if (my_outfits_folder) + { + LLFolderViewFolder* first_outfit = dynamic_cast(my_outfits_folder->getFirstChild()); + if (first_outfit) + { + first_outfit->setOpen(true); + } + } + } + } + should_open_outfit = false; + */ +} + +void LLPanelOutfitsInventory::updateVerbs() +{ + if (mListCommands) + { + updateListCommands(); + } +} + +// virtual +void LLPanelOutfitsInventory::onSearchEdit(const std::string& string) +{ + if (!mActivePanel) return; + + if (!LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) + { + llassert(false); // this should have been done on startup + LLInventoryModelBackgroundFetch::instance().start(); + } + + // set new filter string + mActivePanel->setFilterSubString(string); +} + +void LLPanelOutfitsInventory::onWearButtonClick() +{ + if(isOutfitsListPanelActive()) + { + if (mMyOutfitsPanel->hasItemSelected()) + { + mMyOutfitsPanel->wearSelectedItems(); + } + else + { + mMyOutfitsPanel->performAction("replaceoutfit"); + } + } + else if(isOutfitsGalleryPanelActive()) + { + mOutfitGalleryPanel->wearSelectedOutfit(); + } + +} + +bool LLPanelOutfitsInventory::onSaveCommit(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (0 == option) + { + std::string outfit_name = response["message"].asString(); + LLStringUtil::trim(outfit_name); + if( !outfit_name.empty() ) + { + LLAppearanceMgr::getInstance()->makeNewOutfitLinks(outfit_name); + + LLSidepanelAppearance* panel_appearance = getAppearanceSP(); + if (panel_appearance) + { + panel_appearance->showOutfitsInventoryPanel(); + } + + if (mAppearanceTabs) + { + mAppearanceTabs->selectTabByName(OUTFITS_TAB_NAME); + } + } + } + + return false; +} + +void LLPanelOutfitsInventory::onSave() +{ + std::string outfit_name; + + if (!LLAppearanceMgr::getInstance()->getBaseOutfitName(outfit_name)) + { + outfit_name = LLViewerFolderType::lookupNewCategoryName(LLFolderType::FT_OUTFIT); + } + + LLSD args; + args["DESC"] = outfit_name; + + LLSD payload; + //payload["ids"].append(*it); + + LLNotificationsUtil::add("SaveOutfitAs", args, payload, boost::bind(&LLPanelOutfitsInventory::onSaveCommit, this, _1, _2)); +} + +//static +LLPanelOutfitsInventory* LLPanelOutfitsInventory::findInstance() +{ + return dynamic_cast(LLFloaterSidePanelContainer::getPanel("appearance", "panel_outfits_inventory")); +} + +void LLPanelOutfitsInventory::openApearanceTab(const std::string& tab_name) +{ + if (!mAppearanceTabs) return; + mAppearanceTabs->selectTabByName(tab_name); +} + +////////////////////////////////////////////////////////////////////////////////// +// List Commands // + +void LLPanelOutfitsInventory::initListCommandsHandlers() +{ + mListCommands = getChild("bottom_panel"); + mListCommands->childSetAction("wear_btn", boost::bind(&LLPanelOutfitsInventory::onWearButtonClick, this)); + mMyOutfitsPanel->childSetAction("trash_btn", boost::bind(&LLPanelOutfitsInventory::onTrashButtonClick, this)); + mOutfitGalleryPanel->childSetAction("trash_btn", boost::bind(&LLPanelOutfitsInventory::onTrashButtonClick, this)); +} + +void LLPanelOutfitsInventory::updateListCommands() +{ + bool trash_enabled = isActionEnabled("delete"); + bool wear_enabled = isActionEnabled("wear"); + bool wear_visible = !isCOFPanelActive(); + bool make_outfit_enabled = isActionEnabled("save_outfit"); + + LLButton* wear_btn = mListCommands->getChild("wear_btn"); + mMyOutfitsPanel->childSetEnabled("trash_btn", trash_enabled); + mOutfitGalleryPanel->childSetEnabled("trash_btn", trash_enabled); + wear_btn->setEnabled(wear_enabled); + wear_btn->setVisible(wear_visible); + getChild(SAVE_BTN)->setEnabled(make_outfit_enabled); + wear_btn->setToolTip(getString((!isOutfitsGalleryPanelActive() && mMyOutfitsPanel->hasItemSelected()) ? "wear_items_tooltip" : "wear_outfit_tooltip")); +} + +void LLPanelOutfitsInventory::onTrashButtonClick() +{ + if(isOutfitsListPanelActive()) + { + mMyOutfitsPanel->removeSelected(); + } + else if(isOutfitsGalleryPanelActive()) + { + mOutfitGalleryPanel->removeSelected(); + } +} + +bool LLPanelOutfitsInventory::isActionEnabled(const LLSD& userdata) +{ + return mActivePanel && mActivePanel->isActionEnabled(userdata); +} + +// List Commands // +////////////////////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////////////// +// Tab panels // + +void LLPanelOutfitsInventory::initTabPanels() +{ + //TODO: Add LLOutfitGallery change callback + mCurrentOutfitPanel = findChild(COF_TAB_NAME); + mCurrentOutfitPanel->setSelectionChangeCallback(boost::bind(&LLPanelOutfitsInventory::updateVerbs, this)); + + mMyOutfitsPanel = findChild(OUTFITS_TAB_NAME); + mMyOutfitsPanel->setSelectionChangeCallback(boost::bind(&LLPanelOutfitsInventory::updateVerbs, this)); + + mOutfitGalleryPanel = findChild(OUTFIT_GALLERY_TAB_NAME); + mOutfitGalleryPanel->setSelectionChangeCallback(boost::bind(&LLPanelOutfitsInventory::updateVerbs, this)); + + mAppearanceTabs = getChild("appearance_tabs"); + mAppearanceTabs->setCommitCallback(boost::bind(&LLPanelOutfitsInventory::onTabChange, this)); +} + +void LLPanelOutfitsInventory::onTabChange() +{ + if (!mAppearanceTabs) return; + mActivePanel = dynamic_cast(mAppearanceTabs->getCurrentPanel()); + if (!mActivePanel) return; + + mActivePanel->checkFilterSubString(); + mActivePanel->onOpen(LLSD()); + + updateVerbs(); +} + +bool LLPanelOutfitsInventory::isCOFPanelActive() const +{ + if (!mActivePanel) return false; + + return mActivePanel->getName() == COF_TAB_NAME; +} + +bool LLPanelOutfitsInventory::isOutfitsListPanelActive() const +{ + if (!mActivePanel) return false; + + return mActivePanel->getName() == OUTFITS_TAB_NAME; +} + +bool LLPanelOutfitsInventory::isOutfitsGalleryPanelActive() const +{ + if (!mActivePanel) return false; + + return mActivePanel->getName() == OUTFIT_GALLERY_TAB_NAME; +} + +void LLPanelOutfitsInventory::setWearablesLoading(bool val) +{ + updateVerbs(); +} + +void LLPanelOutfitsInventory::onWearablesLoaded() +{ + setWearablesLoading(false); +} + +void LLPanelOutfitsInventory::onWearablesLoading() +{ + setWearablesLoading(true); +} + +// static +LLSidepanelAppearance* LLPanelOutfitsInventory::getAppearanceSP() +{ + LLSidepanelAppearance* panel_appearance = + dynamic_cast(LLFloaterSidePanelContainer::getPanel("appearance")); + return panel_appearance; +} + +void LLPanelOutfitsInventory::saveOutfit(bool as_new) +{ + if (!as_new && LLAppearanceMgr::getInstance()->updateBaseOutfit()) + { + // we don't need to ask for an outfit name, and updateBaseOutfit() successfully saved. + // If updateBaseOutfit fails, ask for an outfit name anyways + return; + } + + onSave(); +} diff --git a/indra/newview/llpaneloutfitsinventory.h b/indra/newview/llpaneloutfitsinventory.h index f466dcb287..0c501d5c71 100644 --- a/indra/newview/llpaneloutfitsinventory.h +++ b/indra/newview/llpaneloutfitsinventory.h @@ -1,110 +1,110 @@ -/** - * @file llpaneloutfitsinventory.h - * @brief Outfits inventory panel - * class definition - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELOUTFITSINVENTORY_H -#define LL_LLPANELOUTFITSINVENTORY_H - -#include "llpanel.h" - -class LLOutfitGallery; -class LLOutfitsList; -class LLOutfitListGearMenuBase; -class LLPanelAppearanceTab; -class LLPanelWearing; -class LLMenuGL; -class LLSidepanelAppearance; -class LLTabContainer; - -class LLPanelOutfitsInventory : public LLPanel -{ - LOG_CLASS(LLPanelOutfitsInventory); -public: - LLPanelOutfitsInventory(); - virtual ~LLPanelOutfitsInventory(); - - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); - - void onSearchEdit(const std::string& string); - void onSave(); - void saveOutfit(bool as_new = false); - - bool onSaveCommit(const LLSD& notification, const LLSD& response); - - static LLSidepanelAppearance* getAppearanceSP(); - - static LLPanelOutfitsInventory* findInstance(); - - void openApearanceTab(const std::string& tab_name); - - bool isCOFPanelActive() const; - -protected: - void updateVerbs(); - -private: - LLTabContainer* mAppearanceTabs; - - ////////////////////////////////////////////////////////////////////////////////// - // tab panels // -protected: - void initTabPanels(); - void onTabChange(); - bool isOutfitsListPanelActive() const; - bool isOutfitsGalleryPanelActive() const; - -private: - LLPanelAppearanceTab* mActivePanel; - LLOutfitsList* mMyOutfitsPanel; - LLOutfitGallery* mOutfitGalleryPanel; - LLPanelWearing* mCurrentOutfitPanel; - - // tab panels // - ////////////////////////////////////////////////////////////////////////////////// - - ////////////////////////////////////////////////////////////////////////////////// - // List Commands // -protected: - void initListCommandsHandlers(); - void updateListCommands(); - void onWearButtonClick(); - void showGearMenu(); - void onTrashButtonClick(); - bool isActionEnabled(const LLSD& userdata); - void setWearablesLoading(bool val); - void onWearablesLoaded(); - void onWearablesLoading(); -private: - LLPanel* mListCommands; - LLMenuGL* mMenuAdd; - // List Commands // - ////////////////////////////////////////////////////////////////////////////////// - - bool mInitialized; -}; - -#endif //LL_LLPANELOUTFITSINVENTORY_H +/** + * @file llpaneloutfitsinventory.h + * @brief Outfits inventory panel + * class definition + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELOUTFITSINVENTORY_H +#define LL_LLPANELOUTFITSINVENTORY_H + +#include "llpanel.h" + +class LLOutfitGallery; +class LLOutfitsList; +class LLOutfitListGearMenuBase; +class LLPanelAppearanceTab; +class LLPanelWearing; +class LLMenuGL; +class LLSidepanelAppearance; +class LLTabContainer; + +class LLPanelOutfitsInventory : public LLPanel +{ + LOG_CLASS(LLPanelOutfitsInventory); +public: + LLPanelOutfitsInventory(); + virtual ~LLPanelOutfitsInventory(); + + /*virtual*/ bool postBuild(); + /*virtual*/ void onOpen(const LLSD& key); + + void onSearchEdit(const std::string& string); + void onSave(); + void saveOutfit(bool as_new = false); + + bool onSaveCommit(const LLSD& notification, const LLSD& response); + + static LLSidepanelAppearance* getAppearanceSP(); + + static LLPanelOutfitsInventory* findInstance(); + + void openApearanceTab(const std::string& tab_name); + + bool isCOFPanelActive() const; + +protected: + void updateVerbs(); + +private: + LLTabContainer* mAppearanceTabs; + + ////////////////////////////////////////////////////////////////////////////////// + // tab panels // +protected: + void initTabPanels(); + void onTabChange(); + bool isOutfitsListPanelActive() const; + bool isOutfitsGalleryPanelActive() const; + +private: + LLPanelAppearanceTab* mActivePanel; + LLOutfitsList* mMyOutfitsPanel; + LLOutfitGallery* mOutfitGalleryPanel; + LLPanelWearing* mCurrentOutfitPanel; + + // tab panels // + ////////////////////////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////////////////// + // List Commands // +protected: + void initListCommandsHandlers(); + void updateListCommands(); + void onWearButtonClick(); + void showGearMenu(); + void onTrashButtonClick(); + bool isActionEnabled(const LLSD& userdata); + void setWearablesLoading(bool val); + void onWearablesLoaded(); + void onWearablesLoading(); +private: + LLPanel* mListCommands; + LLMenuGL* mMenuAdd; + // List Commands // + ////////////////////////////////////////////////////////////////////////////////// + + bool mInitialized; +}; + +#endif //LL_LLPANELOUTFITSINVENTORY_H diff --git a/indra/newview/llpanelpeople.cpp b/indra/newview/llpanelpeople.cpp index 68464d59bd..e08179dd58 100644 --- a/indra/newview/llpanelpeople.cpp +++ b/indra/newview/llpanelpeople.cpp @@ -1,1569 +1,1569 @@ -/** - * @file llpanelpeople.cpp - * @brief Side tray "People" panel - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -// libs -#include "llavatarname.h" -#include "llconversationview.h" -#include "llfloaterimcontainer.h" -#include "llfloaterreg.h" -#include "llfloatersidepanelcontainer.h" -#include "llmenubutton.h" -#include "llmenugl.h" -#include "llnotificationsutil.h" -#include "lleventtimer.h" -#include "llfiltereditor.h" -#include "lltabcontainer.h" -#include "lltoggleablemenu.h" -#include "lluictrlfactory.h" - -#include "llpanelpeople.h" - -// newview -#include "llaccordionctrl.h" -#include "llaccordionctrltab.h" -#include "llagent.h" -#include "llagentbenefits.h" -#include "llavataractions.h" -#include "llavatarlist.h" -#include "llavatarlistitem.h" -#include "llavatarnamecache.h" -#include "llcallingcard.h" // for LLAvatarTracker -#include "llcallbacklist.h" -#include "llerror.h" -#include "llfloateravatarpicker.h" -#include "llfriendcard.h" -#include "llgroupactions.h" -#include "llgrouplist.h" -#include "llinventoryobserver.h" -#include "llnetmap.h" -#include "llpanelpeoplemenus.h" -#include "llparticipantlist.h" -#include "llsidetraypanelcontainer.h" -#include "llrecentpeople.h" -#include "llviewercontrol.h" // for gSavedSettings -#include "llviewermenu.h" // for gMenuHolder -#include "llviewerregion.h" -#include "llvoiceclient.h" -#include "llworld.h" -#include "llspeakers.h" -#include "llfloaterwebcontent.h" - -#include "llagentui.h" -#include "llslurl.h" - -#define FRIEND_LIST_UPDATE_TIMEOUT 0.5 -#define NEARBY_LIST_UPDATE_INTERVAL 1 - -static const std::string NEARBY_TAB_NAME = "nearby_panel"; -static const std::string FRIENDS_TAB_NAME = "friends_panel"; -static const std::string GROUP_TAB_NAME = "groups_panel"; -static const std::string RECENT_TAB_NAME = "recent_panel"; -static const std::string BLOCKED_TAB_NAME = "blocked_panel"; // blocked avatars -static const std::string COLLAPSED_BY_USER = "collapsed_by_user"; - -/** Comparator for comparing avatar items by last interaction date */ -class LLAvatarItemRecentComparator : public LLAvatarItemComparator -{ -public: - LLAvatarItemRecentComparator() {}; - virtual ~LLAvatarItemRecentComparator() {}; - -protected: - virtual bool doCompare(const LLAvatarListItem* avatar_item1, const LLAvatarListItem* avatar_item2) const - { - LLRecentPeople& people = LLRecentPeople::instance(); - const LLDate& date1 = people.getDate(avatar_item1->getAvatarId()); - const LLDate& date2 = people.getDate(avatar_item2->getAvatarId()); - - //older comes first - return date1 > date2; - } -}; - -/** Compares avatar items by online status, then by name */ -class LLAvatarItemStatusComparator : public LLAvatarItemComparator -{ -public: - LLAvatarItemStatusComparator() {}; - -protected: - /** - * @return true if item1 < item2, false otherwise - */ - virtual bool doCompare(const LLAvatarListItem* item1, const LLAvatarListItem* item2) const - { - LLAvatarTracker& at = LLAvatarTracker::instance(); - bool online1 = at.isBuddyOnline(item1->getAvatarId()); - bool online2 = at.isBuddyOnline(item2->getAvatarId()); - - if (online1 == online2) - { - std::string name1 = item1->getAvatarName(); - std::string name2 = item2->getAvatarName(); - - LLStringUtil::toUpper(name1); - LLStringUtil::toUpper(name2); - - return name1 < name2; - } - - return online1 > online2; - } -}; - -/** Compares avatar items by distance between you and them */ -class LLAvatarItemDistanceComparator : public LLAvatarItemComparator -{ -public: - typedef std::map < LLUUID, LLVector3d > id_to_pos_map_t; - LLAvatarItemDistanceComparator() {}; - - void updateAvatarsPositions(std::vector& positions, uuid_vec_t& uuids) - { - std::vector::const_iterator - pos_it = positions.begin(), - pos_end = positions.end(); - - uuid_vec_t::const_iterator - id_it = uuids.begin(), - id_end = uuids.end(); - - LLAvatarItemDistanceComparator::id_to_pos_map_t pos_map; - - mAvatarsPositions.clear(); - - for (;pos_it != pos_end && id_it != id_end; ++pos_it, ++id_it ) - { - mAvatarsPositions[*id_it] = *pos_it; - } - }; - -protected: - virtual bool doCompare(const LLAvatarListItem* item1, const LLAvatarListItem* item2) const - { - const LLVector3d& me_pos = gAgent.getPositionGlobal(); - const LLVector3d& item1_pos = mAvatarsPositions.find(item1->getAvatarId())->second; - const LLVector3d& item2_pos = mAvatarsPositions.find(item2->getAvatarId())->second; - - return dist_vec_squared(item1_pos, me_pos) < dist_vec_squared(item2_pos, me_pos); - } -private: - id_to_pos_map_t mAvatarsPositions; -}; - -/** Comparator for comparing nearby avatar items by last spoken time */ -class LLAvatarItemRecentSpeakerComparator : public LLAvatarItemNameComparator -{ -public: - LLAvatarItemRecentSpeakerComparator() {}; - virtual ~LLAvatarItemRecentSpeakerComparator() {}; - -protected: - virtual bool doCompare(const LLAvatarListItem* item1, const LLAvatarListItem* item2) const - { - LLPointer lhs = LLActiveSpeakerMgr::instance().findSpeaker(item1->getAvatarId()); - LLPointer rhs = LLActiveSpeakerMgr::instance().findSpeaker(item2->getAvatarId()); - if ( lhs.notNull() && rhs.notNull() ) - { - // Compare by last speaking time - if( lhs->mLastSpokeTime != rhs->mLastSpokeTime ) - return ( lhs->mLastSpokeTime > rhs->mLastSpokeTime ); - } - else if ( lhs.notNull() ) - { - // True if only item1 speaker info available - return true; - } - else if ( rhs.notNull() ) - { - // False if only item2 speaker info available - return false; - } - // By default compare by name. - return LLAvatarItemNameComparator::doCompare(item1, item2); - } -}; - -class LLAvatarItemRecentArrivalComparator : public LLAvatarItemNameComparator -{ -public: - LLAvatarItemRecentArrivalComparator() {}; - virtual ~LLAvatarItemRecentArrivalComparator() {}; - -protected: - virtual bool doCompare(const LLAvatarListItem* item1, const LLAvatarListItem* item2) const - { - - F32 arr_time1 = LLRecentPeople::instance().getArrivalTimeByID(item1->getAvatarId()); - F32 arr_time2 = LLRecentPeople::instance().getArrivalTimeByID(item2->getAvatarId()); - - if (arr_time1 == arr_time2) - { - std::string name1 = item1->getAvatarName(); - std::string name2 = item2->getAvatarName(); - - LLStringUtil::toUpper(name1); - LLStringUtil::toUpper(name2); - - return name1 < name2; - } - - return arr_time1 > arr_time2; - } -}; - -static const LLAvatarItemRecentComparator RECENT_COMPARATOR; -static const LLAvatarItemStatusComparator STATUS_COMPARATOR; -static LLAvatarItemDistanceComparator DISTANCE_COMPARATOR; -static const LLAvatarItemRecentSpeakerComparator RECENT_SPEAKER_COMPARATOR; -static LLAvatarItemRecentArrivalComparator RECENT_ARRIVAL_COMPARATOR; - -static LLPanelInjector t_people("panel_people"); - -//============================================================================= - -/** - * Updates given list either on regular basis or on external events (up to implementation). - */ -class LLPanelPeople::Updater -{ -public: - typedef boost::function callback_t; - Updater(callback_t cb) - : mCallback(cb) - { - } - - virtual ~Updater() - { - } - - /** - * Activate/deactivate updater. - * - * This may start/stop regular updates. - */ - virtual void setActive(bool) {} - -protected: - void update() - { - mCallback(); - } - - callback_t mCallback; -}; - -/** - * Update buttons on changes in our friend relations (STORM-557). - */ -class LLButtonsUpdater : public LLPanelPeople::Updater, public LLFriendObserver -{ -public: - LLButtonsUpdater(callback_t cb) - : LLPanelPeople::Updater(cb) - { - LLAvatarTracker::instance().addObserver(this); - } - - ~LLButtonsUpdater() - { - LLAvatarTracker::instance().removeObserver(this); - } - - /*virtual*/ void changed(U32 mask) - { - (void) mask; - update(); - } -}; - -class LLAvatarListUpdater : public LLPanelPeople::Updater, public LLEventTimer -{ -public: - LLAvatarListUpdater(callback_t cb, F32 period) - : LLEventTimer(period), - LLPanelPeople::Updater(cb) - { - mEventTimer.stop(); - } - - virtual bool tick() // from LLEventTimer - { - return false; - } -}; - -/** - * Updates the friends list. - * - * Updates the list on external events which trigger the changed() method. - */ -class LLFriendListUpdater : public LLAvatarListUpdater, public LLFriendObserver -{ - LOG_CLASS(LLFriendListUpdater); - class LLInventoryFriendCardObserver; - -public: - friend class LLInventoryFriendCardObserver; - LLFriendListUpdater(callback_t cb) - : LLAvatarListUpdater(cb, FRIEND_LIST_UPDATE_TIMEOUT) - , mIsActive(false) - { - LLAvatarTracker::instance().addObserver(this); - - // For notification when SIP online status changes. - LLVoiceClient::getInstance()->addObserver(this); - mInvObserver = new LLInventoryFriendCardObserver(this); - } - - ~LLFriendListUpdater() - { - // will be deleted by ~LLInventoryModel - //delete mInvObserver; - if (LLVoiceClient::instanceExists()) - { - LLVoiceClient::getInstance()->removeObserver(this); - } - LLAvatarTracker::instance().removeObserver(this); - } - - /*virtual*/ void changed(U32 mask) - { - if (mIsActive) - { - // events can arrive quickly in bulk - we need not process EVERY one of them - - // so we wait a short while to let others pile-in, and process them in aggregate. - mEventTimer.start(); - } - - // save-up all the mask-bits which have come-in - mMask |= mask; - } - - - /*virtual*/ bool tick() - { - if (!mIsActive) return false; - - if (mMask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE | LLFriendObserver::ONLINE)) - { - update(); - } - - // Stop updates. - mEventTimer.stop(); - mMask = 0; - - return false; - } - - // virtual - void setActive(bool active) - { - mIsActive = active; - if (active) - { - tick(); - } - } - -private: - U32 mMask; - LLInventoryFriendCardObserver* mInvObserver; - bool mIsActive; - - /** - * This class is intended for updating Friend List when Inventory Friend Card is added/removed. - * - * The main usage is when Inventory Friends/All content is added while synchronizing with - * friends list on startup is performed. In this case Friend Panel should be updated when - * missing Inventory Friend Card is created. - * *NOTE: updating is fired when Inventory item is added into CallingCards/Friends subfolder. - * Otherwise LLFriendObserver functionality is enough to keep Friends Panel synchronized. - */ - class LLInventoryFriendCardObserver : public LLInventoryObserver - { - LOG_CLASS(LLFriendListUpdater::LLInventoryFriendCardObserver); - - friend class LLFriendListUpdater; - - private: - LLInventoryFriendCardObserver(LLFriendListUpdater* updater) : mUpdater(updater) - { - gInventory.addObserver(this); - } - ~LLInventoryFriendCardObserver() - { - gInventory.removeObserver(this); - } - /*virtual*/ void changed(U32 mask) - { - LL_DEBUGS() << "Inventory changed: " << mask << LL_ENDL; - - static bool synchronize_friends_folders = true; - if (synchronize_friends_folders) - { - // Checks whether "Friends" and "Friends/All" folders exist in "Calling Cards" folder, - // fetches their contents if needed and synchronizes it with buddies list. - // If the folders are not found they are created. - LLFriendCardsManager::instance().syncFriendCardsFolders(); - synchronize_friends_folders = false; - } - - // *NOTE: deleting of InventoryItem is performed via moving to Trash. - // That means LLInventoryObserver::STRUCTURE is present in MASK instead of LLInventoryObserver::REMOVE - if ((CALLINGCARD_ADDED & mask) == CALLINGCARD_ADDED) - { - LL_DEBUGS() << "Calling card added: count: " << gInventory.getChangedIDs().size() - << ", first Inventory ID: "<< (*gInventory.getChangedIDs().begin()) - << LL_ENDL; - - bool friendFound = false; - std::set changedIDs = gInventory.getChangedIDs(); - for (std::set::const_iterator it = changedIDs.begin(); it != changedIDs.end(); ++it) - { - if (isDescendentOfInventoryFriends(*it)) - { - friendFound = true; - break; - } - } - - if (friendFound) - { - LL_DEBUGS() << "friend found, panel should be updated" << LL_ENDL; - mUpdater->changed(LLFriendObserver::ADD); - } - } - } - - bool isDescendentOfInventoryFriends(const LLUUID& invItemID) - { - LLViewerInventoryItem * item = gInventory.getItem(invItemID); - if (NULL == item) - return false; - - return LLFriendCardsManager::instance().isItemInAnyFriendsList(item); - } - LLFriendListUpdater* mUpdater; - - static const U32 CALLINGCARD_ADDED = LLInventoryObserver::ADD | LLInventoryObserver::CALLING_CARD; - }; -}; - -/** - * Periodically updates the nearby people list while the Nearby tab is active. - * - * The period is defined by NEARBY_LIST_UPDATE_INTERVAL constant. - */ -class LLNearbyListUpdater : public LLAvatarListUpdater -{ - LOG_CLASS(LLNearbyListUpdater); - -public: - LLNearbyListUpdater(callback_t cb) - : LLAvatarListUpdater(cb, NEARBY_LIST_UPDATE_INTERVAL) - { - setActive(false); - } - - /*virtual*/ void setActive(bool val) - { - if (val) - { - // update immediately and start regular updates - update(); - mEventTimer.start(); - } - else - { - // stop regular updates - mEventTimer.stop(); - } - } - - /*virtual*/ bool tick() - { - update(); - return false; - } -private: -}; - -/** - * Updates the recent people list (those the agent has recently interacted with). - */ -class LLRecentListUpdater : public LLAvatarListUpdater, public boost::signals2::trackable -{ - LOG_CLASS(LLRecentListUpdater); - -public: - LLRecentListUpdater(callback_t cb) - : LLAvatarListUpdater(cb, 0) - { - LLRecentPeople::instance().setChangedCallback(boost::bind(&LLRecentListUpdater::update, this)); - } -}; - -//============================================================================= - -LLPanelPeople::LLPanelPeople() - : LLPanel(), - mTabContainer(NULL), - mOnlineFriendList(NULL), - mAllFriendList(NULL), - mNearbyList(NULL), - mRecentList(NULL), - mGroupList(NULL), - mMiniMap(NULL) -{ - mFriendListUpdater = new LLFriendListUpdater(boost::bind(&LLPanelPeople::updateFriendList, this)); - mNearbyListUpdater = new LLNearbyListUpdater(boost::bind(&LLPanelPeople::updateNearbyList, this)); - mRecentListUpdater = new LLRecentListUpdater(boost::bind(&LLPanelPeople::updateRecentList, this)); - mButtonsUpdater = new LLButtonsUpdater(boost::bind(&LLPanelPeople::updateButtons, this)); - - mCommitCallbackRegistrar.add("People.AddFriend", boost::bind(&LLPanelPeople::onAddFriendButtonClicked, this)); - mCommitCallbackRegistrar.add("People.AddFriendWizard", boost::bind(&LLPanelPeople::onAddFriendWizButtonClicked, this)); - mCommitCallbackRegistrar.add("People.DelFriend", boost::bind(&LLPanelPeople::onDeleteFriendButtonClicked, this)); - mCommitCallbackRegistrar.add("People.Group.Minus", boost::bind(&LLPanelPeople::onGroupMinusButtonClicked, this)); - mCommitCallbackRegistrar.add("People.Chat", boost::bind(&LLPanelPeople::onChatButtonClicked, this)); - mCommitCallbackRegistrar.add("People.Gear", boost::bind(&LLPanelPeople::onGearButtonClicked, this, _1)); - - mCommitCallbackRegistrar.add("People.Group.Plus.Action", boost::bind(&LLPanelPeople::onGroupPlusMenuItemClicked, this, _2)); - mCommitCallbackRegistrar.add("People.Friends.ViewSort.Action", boost::bind(&LLPanelPeople::onFriendsViewSortMenuItemClicked, this, _2)); - mCommitCallbackRegistrar.add("People.Nearby.ViewSort.Action", boost::bind(&LLPanelPeople::onNearbyViewSortMenuItemClicked, this, _2)); - mCommitCallbackRegistrar.add("People.Groups.ViewSort.Action", boost::bind(&LLPanelPeople::onGroupsViewSortMenuItemClicked, this, _2)); - mCommitCallbackRegistrar.add("People.Recent.ViewSort.Action", boost::bind(&LLPanelPeople::onRecentViewSortMenuItemClicked, this, _2)); - - mEnableCallbackRegistrar.add("People.Friends.ViewSort.CheckItem", boost::bind(&LLPanelPeople::onFriendsViewSortMenuItemCheck, this, _2)); - mEnableCallbackRegistrar.add("People.Recent.ViewSort.CheckItem", boost::bind(&LLPanelPeople::onRecentViewSortMenuItemCheck, this, _2)); - mEnableCallbackRegistrar.add("People.Nearby.ViewSort.CheckItem", boost::bind(&LLPanelPeople::onNearbyViewSortMenuItemCheck, this, _2)); - - mEnableCallbackRegistrar.add("People.Group.Plus.Validate", boost::bind(&LLPanelPeople::onGroupPlusButtonValidate, this)); - - doPeriodically(boost::bind(&LLPanelPeople::updateNearbyArrivalTime, this), 2.0); -} - -LLPanelPeople::~LLPanelPeople() -{ - delete mButtonsUpdater; - delete mNearbyListUpdater; - delete mFriendListUpdater; - delete mRecentListUpdater; - - mNearbyFilterCommitConnection.disconnect(); - mFriedsFilterCommitConnection.disconnect(); - mGroupsFilterCommitConnection.disconnect(); - mRecentFilterCommitConnection.disconnect(); - - if(LLVoiceClient::instanceExists()) - { - LLVoiceClient::getInstance()->removeObserver(this); - } -} - -void LLPanelPeople::onFriendsAccordionExpandedCollapsed(LLUICtrl* ctrl, const LLSD& param, LLAvatarList* avatar_list) -{ - if(!avatar_list) - { - LL_ERRS() << "Bad parameter" << LL_ENDL; - return; - } - - bool expanded = param.asBoolean(); - - setAccordionCollapsedByUser(ctrl, !expanded); - if(!expanded) - { - avatar_list->resetSelection(); - } -} - - -void LLPanelPeople::removePicker() -{ - if(mPicker.get()) - { - mPicker.get()->closeFloater(); - } -} - -bool LLPanelPeople::postBuild() -{ - S32 max_premium = LLAgentBenefitsMgr::get("Premium").getGroupMembershipLimit(); - - mNearbyFilterCommitConnection = getChild("nearby_filter_input")->setCommitCallback(boost::bind(&LLPanelPeople::onFilterEdit, this, _2)); - mFriedsFilterCommitConnection = getChild("friends_filter_input")->setCommitCallback(boost::bind(&LLPanelPeople::onFilterEdit, this, _2)); - mGroupsFilterCommitConnection = getChild("groups_filter_input")->setCommitCallback(boost::bind(&LLPanelPeople::onFilterEdit, this, _2)); - mRecentFilterCommitConnection = getChild("recent_filter_input")->setCommitCallback(boost::bind(&LLPanelPeople::onFilterEdit, this, _2)); - - if(LLAgentBenefitsMgr::current().getGroupMembershipLimit() < max_premium) - { - getChild("groupcount")->setText(getString("GroupCountWithInfo")); - getChild("groupcount")->setURLClickedCallback(boost::bind(&LLPanelPeople::onGroupLimitInfo, this)); - } - - mTabContainer = getChild("tabs"); - mTabContainer->setCommitCallback(boost::bind(&LLPanelPeople::onTabSelected, this, _2)); - mSavedFilters.resize(mTabContainer->getTabCount()); - mSavedOriginalFilters.resize(mTabContainer->getTabCount()); - - LLPanel* friends_tab = getChild(FRIENDS_TAB_NAME); - // updater is active only if panel is visible to user. - friends_tab->setVisibleCallback(boost::bind(&Updater::setActive, mFriendListUpdater, _2)); - friends_tab->setVisibleCallback(boost::bind(&LLPanelPeople::removePicker, this)); - - mOnlineFriendList = friends_tab->getChild("avatars_online"); - mAllFriendList = friends_tab->getChild("avatars_all"); - mOnlineFriendList->setNoItemsCommentText(getString("no_friends_online")); - mOnlineFriendList->setShowIcons("FriendsListShowIcons"); - mOnlineFriendList->showPermissions("FriendsListShowPermissions"); - mOnlineFriendList->setShowCompleteName(!gSavedSettings.getBOOL("FriendsListHideUsernames")); - mAllFriendList->setNoItemsCommentText(getString("no_friends")); - mAllFriendList->setShowIcons("FriendsListShowIcons"); - mAllFriendList->showPermissions("FriendsListShowPermissions"); - mAllFriendList->setShowCompleteName(!gSavedSettings.getBOOL("FriendsListHideUsernames")); - - LLPanel* nearby_tab = getChild(NEARBY_TAB_NAME); - nearby_tab->setVisibleCallback(boost::bind(&Updater::setActive, mNearbyListUpdater, _2)); - mNearbyList = nearby_tab->getChild("avatar_list"); - mNearbyList->setNoItemsCommentText(getString("no_one_near")); - mNearbyList->setNoItemsMsg(getString("no_one_near")); - mNearbyList->setNoFilteredItemsMsg(getString("no_one_filtered_near")); - mNearbyList->setShowIcons("NearbyListShowIcons"); - mNearbyList->setShowCompleteName(!gSavedSettings.getBOOL("NearbyListHideUsernames")); - mMiniMap = (LLNetMap*)getChildView("Net Map",true); - mMiniMap->setToolTipMsg(gSavedSettings.getBOOL("DoubleClickTeleport") ? - getString("AltMiniMapToolTipMsg") : getString("MiniMapToolTipMsg")); - - mRecentList = getChild(RECENT_TAB_NAME)->getChild("avatar_list"); - mRecentList->setNoItemsCommentText(getString("no_recent_people")); - mRecentList->setNoItemsMsg(getString("no_recent_people")); - mRecentList->setNoFilteredItemsMsg(getString("no_filtered_recent_people")); - mRecentList->setShowIcons("RecentListShowIcons"); - - mGroupList = getChild("group_list"); - mGroupList->setNoItemsCommentText(getString("no_groups_msg")); - mGroupList->setNoItemsMsg(getString("no_groups_msg")); - mGroupList->setNoFilteredItemsMsg(getString("no_filtered_groups_msg")); - - mNearbyList->setContextMenu(&LLPanelPeopleMenus::gNearbyPeopleContextMenu); - mRecentList->setContextMenu(&LLPanelPeopleMenus::gPeopleContextMenu); - mAllFriendList->setContextMenu(&LLPanelPeopleMenus::gPeopleContextMenu); - mOnlineFriendList->setContextMenu(&LLPanelPeopleMenus::gPeopleContextMenu); - - setSortOrder(mRecentList, (ESortOrder)gSavedSettings.getU32("RecentPeopleSortOrder"), false); - setSortOrder(mAllFriendList, (ESortOrder)gSavedSettings.getU32("FriendsSortOrder"), false); - setSortOrder(mNearbyList, (ESortOrder)gSavedSettings.getU32("NearbyPeopleSortOrder"), false); - - mOnlineFriendList->setItemDoubleClickCallback(boost::bind(&LLPanelPeople::onAvatarListDoubleClicked, this, _1)); - mAllFriendList->setItemDoubleClickCallback(boost::bind(&LLPanelPeople::onAvatarListDoubleClicked, this, _1)); - mNearbyList->setItemDoubleClickCallback(boost::bind(&LLPanelPeople::onAvatarListDoubleClicked, this, _1)); - mRecentList->setItemDoubleClickCallback(boost::bind(&LLPanelPeople::onAvatarListDoubleClicked, this, _1)); - - mOnlineFriendList->setCommitCallback(boost::bind(&LLPanelPeople::onAvatarListCommitted, this, mOnlineFriendList)); - mAllFriendList->setCommitCallback(boost::bind(&LLPanelPeople::onAvatarListCommitted, this, mAllFriendList)); - mNearbyList->setCommitCallback(boost::bind(&LLPanelPeople::onAvatarListCommitted, this, mNearbyList)); - mRecentList->setCommitCallback(boost::bind(&LLPanelPeople::onAvatarListCommitted, this, mRecentList)); - - // Set openning IM as default on return action for avatar lists - mOnlineFriendList->setReturnCallback(boost::bind(&LLPanelPeople::onImButtonClicked, this)); - mAllFriendList->setReturnCallback(boost::bind(&LLPanelPeople::onImButtonClicked, this)); - mNearbyList->setReturnCallback(boost::bind(&LLPanelPeople::onImButtonClicked, this)); - mRecentList->setReturnCallback(boost::bind(&LLPanelPeople::onImButtonClicked, this)); - - mGroupList->setDoubleClickCallback(boost::bind(&LLPanelPeople::onChatButtonClicked, this)); - mGroupList->setCommitCallback(boost::bind(&LLPanelPeople::updateButtons, this)); - mGroupList->setReturnCallback(boost::bind(&LLPanelPeople::onChatButtonClicked, this)); - - LLMenuButton* groups_gear_btn = getChild("groups_gear_btn"); - - // Use the context menu of the Groups list for the Groups tab gear menu. - LLToggleableMenu* groups_gear_menu = mGroupList->getContextMenu(); - if (groups_gear_menu) - { - groups_gear_btn->setMenu(groups_gear_menu, LLMenuButton::MP_BOTTOM_LEFT); - } - else - { - LL_WARNS() << "People->Groups list menu not found" << LL_ENDL; - } - - LLAccordionCtrlTab* accordion_tab = getChild("tab_all"); - accordion_tab->setDropDownStateChangedCallback( - boost::bind(&LLPanelPeople::onFriendsAccordionExpandedCollapsed, this, _1, _2, mAllFriendList)); - - accordion_tab = getChild("tab_online"); - accordion_tab->setDropDownStateChangedCallback( - boost::bind(&LLPanelPeople::onFriendsAccordionExpandedCollapsed, this, _1, _2, mOnlineFriendList)); - - // Must go after setting commit callback and initializing all pointers to children. - mTabContainer->selectTabByName(NEARBY_TAB_NAME); - - LLVoiceClient::getInstance()->addObserver(this); - - // call this method in case some list is empty and buttons can be in inconsistent state - updateButtons(); - - mOnlineFriendList->setRefreshCompleteCallback(boost::bind(&LLPanelPeople::onFriendListRefreshComplete, this, _1, _2)); - mAllFriendList->setRefreshCompleteCallback(boost::bind(&LLPanelPeople::onFriendListRefreshComplete, this, _1, _2)); - - return true; -} - -// virtual -void LLPanelPeople::onChange(EStatusType status, const std::string &channelURI, bool proximal) -{ - if(status == STATUS_JOINING || status == STATUS_LEFT_CHANNEL) - { - return; - } - - updateButtons(); -} - -void LLPanelPeople::updateFriendListHelpText() -{ - // show special help text for just created account to help finding friends. EXT-4836 - static LLTextBox* no_friends_text = getChild("no_friends_help_text"); - - // Seems sometimes all_friends can be empty because of issue with Inventory loading (clear cache, slow connection...) - // So, lets check all lists to avoid overlapping the text with online list. See EXT-6448. - bool any_friend_exists = mAllFriendList->filterHasMatches() || mOnlineFriendList->filterHasMatches(); - no_friends_text->setVisible(!any_friend_exists); - if (no_friends_text->getVisible()) - { - //update help text for empty lists - const std::string& filter = mSavedOriginalFilters[mTabContainer->getCurrentPanelIndex()]; - - std::string message_name = filter.empty() ? "no_friends_msg" : "no_filtered_friends_msg"; - LLStringUtil::format_map_t args; - args["[SEARCH_TERM]"] = LLURI::escape(filter); - no_friends_text->setText(getString(message_name, args)); - } -} - -void LLPanelPeople::updateFriendList() -{ - if (!mOnlineFriendList || !mAllFriendList) - return; - - // get all buddies we know about - const LLAvatarTracker& av_tracker = LLAvatarTracker::instance(); - LLAvatarTracker::buddy_map_t all_buddies; - av_tracker.copyBuddyList(all_buddies); - - // save them to the online and all friends vectors - uuid_vec_t& online_friendsp = mOnlineFriendList->getIDs(); - uuid_vec_t& all_friendsp = mAllFriendList->getIDs(); - - all_friendsp.clear(); - online_friendsp.clear(); - - uuid_vec_t buddies_uuids; - LLAvatarTracker::buddy_map_t::const_iterator buddies_iter; - - // Fill the avatar list with friends UUIDs - for (buddies_iter = all_buddies.begin(); buddies_iter != all_buddies.end(); ++buddies_iter) - { - buddies_uuids.push_back(buddies_iter->first); - } - - if (buddies_uuids.size() > 0) - { - LL_DEBUGS() << "Friends added to the list: " << buddies_uuids.size() << LL_ENDL; - all_friendsp = buddies_uuids; - } - else - { - LL_DEBUGS() << "No friends found" << LL_ENDL; - } - - LLAvatarTracker::buddy_map_t::const_iterator buddy_it = all_buddies.begin(); - for (; buddy_it != all_buddies.end(); ++buddy_it) - { - LLUUID buddy_id = buddy_it->first; - if (av_tracker.isBuddyOnline(buddy_id)) - online_friendsp.push_back(buddy_id); - } - - /* - * Avatarlists will be hidden by showFriendsAccordionsIfNeeded(), if they do not have items. - * But avatarlist can be updated only if it is visible @see LLAvatarList::draw(); - * So we need to do force update of lists to avoid inconsistency of data and view of avatarlist. - */ - mOnlineFriendList->setDirty(true, !mOnlineFriendList->filterHasMatches());// do force update if list do NOT have items - mAllFriendList->setDirty(true, !mAllFriendList->filterHasMatches()); - //update trash and other buttons according to a selected item - updateButtons(); - showFriendsAccordionsIfNeeded(); -} - -void LLPanelPeople::updateNearbyList() -{ - if (!mNearbyList) - return; - - std::vector positions; - - LLWorld::getInstance()->getAvatars(&mNearbyList->getIDs(), &positions, gAgent.getPositionGlobal(), gSavedSettings.getF32("NearMeRange")); - mNearbyList->setDirty(); - - DISTANCE_COMPARATOR.updateAvatarsPositions(positions, mNearbyList->getIDs()); - LLActiveSpeakerMgr::instance().update(true); -} - -void LLPanelPeople::updateRecentList() -{ - if (!mRecentList) - return; - - LLRecentPeople::instance().get(mRecentList->getIDs()); - mRecentList->setDirty(); -} - -void LLPanelPeople::updateButtons() -{ - std::string cur_tab = getActiveTabName(); - bool friends_tab_active = (cur_tab == FRIENDS_TAB_NAME); - bool group_tab_active = (cur_tab == GROUP_TAB_NAME); - //bool recent_tab_active = (cur_tab == RECENT_TAB_NAME); - LLUUID selected_id; - - uuid_vec_t selected_uuids; - getCurrentItemIDs(selected_uuids); - bool item_selected = (selected_uuids.size() == 1); - bool multiple_selected = (selected_uuids.size() >= 1); - - if (group_tab_active) - { - if (item_selected) - { - selected_id = mGroupList->getSelectedUUID(); - } - - LLPanel* groups_panel = mTabContainer->getCurrentPanel(); - groups_panel->getChildView("minus_btn")->setEnabled(item_selected && selected_id.notNull()); // a real group selected - - U32 groups_count = gAgent.mGroups.size(); - S32 max_groups = LLAgentBenefitsMgr::current().getGroupMembershipLimit(); - U32 groups_remaining = max_groups > groups_count ? max_groups - groups_count : 0; - groups_panel->getChild("groupcount")->setTextArg("[COUNT]", llformat("%d", groups_count)); - groups_panel->getChild("groupcount")->setTextArg("[REMAINING]", llformat("%d", groups_remaining)); - } - else - { - bool is_friend = true; - bool is_self = false; - // Check whether selected avatar is our friend. - if (item_selected) - { - selected_id = selected_uuids.front(); - is_friend = LLAvatarTracker::instance().getBuddyInfo(selected_id) != NULL; - is_self = gAgent.getID() == selected_id; - } - - LLPanel* cur_panel = mTabContainer->getCurrentPanel(); - if (cur_panel) - { - if (cur_panel->hasChild("add_friend_btn", true)) - cur_panel->getChildView("add_friend_btn")->setEnabled(item_selected && !is_friend && !is_self); - - if (friends_tab_active) - { - cur_panel->getChildView("friends_del_btn")->setEnabled(multiple_selected); - } - - if (!group_tab_active) - { - cur_panel->getChildView("gear_btn")->setEnabled(multiple_selected); - } - } - } -} - -std::string LLPanelPeople::getActiveTabName() const -{ - return mTabContainer->getCurrentPanel()->getName(); -} - -LLUUID LLPanelPeople::getCurrentItemID() const -{ - std::string cur_tab = getActiveTabName(); - - if (cur_tab == FRIENDS_TAB_NAME) // this tab has two lists - { - LLUUID cur_online_friend; - - if ((cur_online_friend = mOnlineFriendList->getSelectedUUID()).notNull()) - return cur_online_friend; - - return mAllFriendList->getSelectedUUID(); - } - - if (cur_tab == NEARBY_TAB_NAME) - return mNearbyList->getSelectedUUID(); - - if (cur_tab == RECENT_TAB_NAME) - return mRecentList->getSelectedUUID(); - - if (cur_tab == GROUP_TAB_NAME) - return mGroupList->getSelectedUUID(); - - if (cur_tab == BLOCKED_TAB_NAME) - return LLUUID::null; // FIXME? - - llassert(0 && "unknown tab selected"); - return LLUUID::null; -} - -void LLPanelPeople::getCurrentItemIDs(uuid_vec_t& selected_uuids) const -{ - std::string cur_tab = getActiveTabName(); - - if (cur_tab == FRIENDS_TAB_NAME) - { - // friends tab has two lists - mOnlineFriendList->getSelectedUUIDs(selected_uuids); - mAllFriendList->getSelectedUUIDs(selected_uuids); - } - else if (cur_tab == NEARBY_TAB_NAME) - mNearbyList->getSelectedUUIDs(selected_uuids); - else if (cur_tab == RECENT_TAB_NAME) - mRecentList->getSelectedUUIDs(selected_uuids); - else if (cur_tab == GROUP_TAB_NAME) - mGroupList->getSelectedUUIDs(selected_uuids); - else if (cur_tab == BLOCKED_TAB_NAME) - selected_uuids.clear(); // FIXME? - else - llassert(0 && "unknown tab selected"); - -} - -void LLPanelPeople::setSortOrder(LLAvatarList* list, ESortOrder order, bool save) -{ - switch (order) - { - case E_SORT_BY_NAME: - list->sortByName(); - break; - case E_SORT_BY_STATUS: - list->setComparator(&STATUS_COMPARATOR); - list->sort(); - break; - case E_SORT_BY_MOST_RECENT: - list->setComparator(&RECENT_COMPARATOR); - list->sort(); - break; - case E_SORT_BY_RECENT_SPEAKERS: - list->setComparator(&RECENT_SPEAKER_COMPARATOR); - list->sort(); - break; - case E_SORT_BY_DISTANCE: - list->setComparator(&DISTANCE_COMPARATOR); - list->sort(); - break; - case E_SORT_BY_RECENT_ARRIVAL: - list->setComparator(&RECENT_ARRIVAL_COMPARATOR); - list->sort(); - break; - default: - LL_WARNS() << "Unrecognized people sort order for " << list->getName() << LL_ENDL; - return; - } - - if (save) - { - std::string setting; - - if (list == mAllFriendList || list == mOnlineFriendList) - setting = "FriendsSortOrder"; - else if (list == mRecentList) - setting = "RecentPeopleSortOrder"; - else if (list == mNearbyList) - setting = "NearbyPeopleSortOrder"; - - if (!setting.empty()) - gSavedSettings.setU32(setting, order); - } -} - -void LLPanelPeople::onFilterEdit(const std::string& search_string) -{ - const S32 cur_tab_idx = mTabContainer->getCurrentPanelIndex(); - std::string& filter = mSavedOriginalFilters[cur_tab_idx]; - std::string& saved_filter = mSavedFilters[cur_tab_idx]; - - filter = search_string; - LLStringUtil::trimHead(filter); - - // Searches are case-insensitive - std::string search_upper = filter; - LLStringUtil::toUpper(search_upper); - - if (saved_filter == search_upper) - return; - - saved_filter = search_upper; - - // Apply new filter to the current tab. - const std::string cur_tab = getActiveTabName(); - if (cur_tab == NEARBY_TAB_NAME) - { - mNearbyList->setNameFilter(filter); - } - else if (cur_tab == FRIENDS_TAB_NAME) - { - // store accordion tabs opened/closed state before any manipulation with accordion tabs - if (!saved_filter.empty()) - { - notifyChildren(LLSD().with("action","store_state")); - } - - mOnlineFriendList->setNameFilter(filter); - mAllFriendList->setNameFilter(filter); - - setAccordionCollapsedByUser("tab_online", false); - setAccordionCollapsedByUser("tab_all", false); - showFriendsAccordionsIfNeeded(); - - // restore accordion tabs state _after_ all manipulations - if(saved_filter.empty()) - { - notifyChildren(LLSD().with("action","restore_state")); - } - } - else if (cur_tab == GROUP_TAB_NAME) - { - mGroupList->setNameFilter(filter); - } - else if (cur_tab == RECENT_TAB_NAME) - { - mRecentList->setNameFilter(filter); - } -} - -void LLPanelPeople::onGroupLimitInfo() -{ - LLSD args; - - S32 max_basic = LLAgentBenefitsMgr::get("Base").getGroupMembershipLimit(); - S32 max_premium = LLAgentBenefitsMgr::get("Premium").getGroupMembershipLimit(); - - args["MAX_BASIC"] = max_basic; - args["MAX_PREMIUM"] = max_premium; - - if (LLAgentBenefitsMgr::has("Premium_Plus")) - { - S32 max_premium_plus = LLAgentBenefitsMgr::get("Premium_Plus").getGroupMembershipLimit(); - args["MAX_PREMIUM_PLUS"] = max_premium_plus; - LLNotificationsUtil::add("GroupLimitInfoPlus", args); - } - else - { - LLNotificationsUtil::add("GroupLimitInfo", args); - } -} - -void LLPanelPeople::onTabSelected(const LLSD& param) -{ - std::string tab_name = getChild(param.asString())->getName(); - updateButtons(); - - showFriendsAccordionsIfNeeded(); -} - -void LLPanelPeople::onAvatarListDoubleClicked(LLUICtrl* ctrl) -{ - LLAvatarListItem* item = dynamic_cast(ctrl); - if(!item) - { - return; - } - - LLUUID clicked_id = item->getAvatarId(); - if(gAgent.getID() == clicked_id) - { - return; - } - -#if 0 // SJB: Useful for testing, but not currently functional or to spec - LLAvatarActions::showProfile(clicked_id); -#else // spec says open IM window - LLAvatarActions::startIM(clicked_id); -#endif -} - -void LLPanelPeople::onAvatarListCommitted(LLAvatarList* list) -{ - if (getActiveTabName() == NEARBY_TAB_NAME) - { - uuid_vec_t selected_uuids; - getCurrentItemIDs(selected_uuids); - mMiniMap->setSelected(selected_uuids); - } else - // Make sure only one of the friends lists (online/all) has selection. - if (getActiveTabName() == FRIENDS_TAB_NAME) - { - if (list == mOnlineFriendList) - mAllFriendList->resetSelection(true); - else if (list == mAllFriendList) - mOnlineFriendList->resetSelection(true); - else - llassert(0 && "commit on unknown friends list"); - } - - updateButtons(); -} - -void LLPanelPeople::onAddFriendButtonClicked() -{ - LLUUID id = getCurrentItemID(); - if (id.notNull()) - { - LLAvatarActions::requestFriendshipDialog(id); - } -} - -bool LLPanelPeople::isItemsFreeOfFriends(const uuid_vec_t& uuids) -{ - const LLAvatarTracker& av_tracker = LLAvatarTracker::instance(); - for ( uuid_vec_t::const_iterator - id = uuids.begin(), - id_end = uuids.end(); - id != id_end; ++id ) - { - if (av_tracker.isBuddy (*id)) - { - return false; - } - } - return true; -} - -void LLPanelPeople::onAddFriendWizButtonClicked() -{ - LLPanel* cur_panel = mTabContainer->getCurrentPanel(); - LLView * button = cur_panel->findChild("friends_add_btn", true); - - // Show add friend wizard. - LLFloater* root_floater = gFloaterView->getParentFloater(this); - LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(boost::bind(&LLPanelPeople::onAvatarPicked, _1, _2), false, true, false, root_floater->getName(), button); - if (!picker) - { - return; - } - - // Need to disable 'ok' button when friend occurs in selection - picker->setOkBtnEnableCb(boost::bind(&LLPanelPeople::isItemsFreeOfFriends, this, _1)); - - if (root_floater) - { - root_floater->addDependentFloater(picker); - } - - mPicker = picker->getHandle(); -} - -void LLPanelPeople::onDeleteFriendButtonClicked() -{ - uuid_vec_t selected_uuids; - getCurrentItemIDs(selected_uuids); - - if (selected_uuids.size() == 1) - { - LLAvatarActions::removeFriendDialog( selected_uuids.front() ); - } - else if (selected_uuids.size() > 1) - { - LLAvatarActions::removeFriendsDialog( selected_uuids ); - } -} - -void LLPanelPeople::onChatButtonClicked() -{ - LLUUID group_id = getCurrentItemID(); - if (group_id.notNull()) - LLGroupActions::startIM(group_id); -} - -void LLPanelPeople::onGearButtonClicked(LLUICtrl* btn) -{ - uuid_vec_t selected_uuids; - getCurrentItemIDs(selected_uuids); - // Spawn at bottom left corner of the button. - if (getActiveTabName() == NEARBY_TAB_NAME) - LLPanelPeopleMenus::gNearbyPeopleContextMenu.show(btn, selected_uuids, 0, 0); - else - LLPanelPeopleMenus::gPeopleContextMenu.show(btn, selected_uuids, 0, 0); -} - -void LLPanelPeople::onImButtonClicked() -{ - uuid_vec_t selected_uuids; - getCurrentItemIDs(selected_uuids); - if ( selected_uuids.size() == 1 ) - { - // if selected only one person then start up IM - LLAvatarActions::startIM(selected_uuids.at(0)); - } - else if ( selected_uuids.size() > 1 ) - { - // for multiple selection start up friends conference - LLAvatarActions::startConference(selected_uuids); - } -} - -// static -void LLPanelPeople::onAvatarPicked(const uuid_vec_t& ids, const std::vector names) -{ - if (!names.empty() && !ids.empty()) - LLAvatarActions::requestFriendshipDialog(ids[0], names[0].getCompleteName()); -} - -bool LLPanelPeople::onGroupPlusButtonValidate() -{ - if (!gAgent.canJoinGroups()) - { - LLNotificationsUtil::add("JoinedTooManyGroups"); - return false; - } - - return true; -} - -void LLPanelPeople::onGroupMinusButtonClicked() -{ - LLUUID group_id = getCurrentItemID(); - if (group_id.notNull()) - LLGroupActions::leave(group_id); -} - -void LLPanelPeople::onGroupPlusMenuItemClicked(const LLSD& userdata) -{ - std::string chosen_item = userdata.asString(); - - if (chosen_item == "join_group") - LLGroupActions::search(); - else if (chosen_item == "new_group") - LLGroupActions::createGroup(); -} - -void LLPanelPeople::onFriendsViewSortMenuItemClicked(const LLSD& userdata) -{ - std::string chosen_item = userdata.asString(); - - if (chosen_item == "sort_name") - { - setSortOrder(mAllFriendList, E_SORT_BY_NAME); - } - else if (chosen_item == "sort_status") - { - setSortOrder(mAllFriendList, E_SORT_BY_STATUS); - } - else if (chosen_item == "view_icons") - { - mAllFriendList->toggleIcons(); - mOnlineFriendList->toggleIcons(); - } - else if (chosen_item == "view_permissions") - { - bool show_permissions = !gSavedSettings.getBOOL("FriendsListShowPermissions"); - gSavedSettings.setBOOL("FriendsListShowPermissions", show_permissions); - - mAllFriendList->showPermissions(show_permissions); - mOnlineFriendList->showPermissions(show_permissions); - } - else if (chosen_item == "view_usernames") - { - bool hide_usernames = !gSavedSettings.getBOOL("FriendsListHideUsernames"); - gSavedSettings.setBOOL("FriendsListHideUsernames", hide_usernames); - - mAllFriendList->setShowCompleteName(!hide_usernames); - mAllFriendList->handleDisplayNamesOptionChanged(); - mOnlineFriendList->setShowCompleteName(!hide_usernames); - mOnlineFriendList->handleDisplayNamesOptionChanged(); - } - } - -void LLPanelPeople::onGroupsViewSortMenuItemClicked(const LLSD& userdata) -{ - std::string chosen_item = userdata.asString(); - - if (chosen_item == "show_icons") - { - mGroupList->toggleIcons(); - } -} - -void LLPanelPeople::onNearbyViewSortMenuItemClicked(const LLSD& userdata) -{ - std::string chosen_item = userdata.asString(); - - if (chosen_item == "sort_by_recent_speakers") - { - setSortOrder(mNearbyList, E_SORT_BY_RECENT_SPEAKERS); - } - else if (chosen_item == "sort_name") - { - setSortOrder(mNearbyList, E_SORT_BY_NAME); - } - else if (chosen_item == "view_icons") - { - mNearbyList->toggleIcons(); - } - else if (chosen_item == "sort_distance") - { - setSortOrder(mNearbyList, E_SORT_BY_DISTANCE); - } - else if (chosen_item == "sort_arrival") - { - setSortOrder(mNearbyList, E_SORT_BY_RECENT_ARRIVAL); - } - else if (chosen_item == "view_usernames") - { - bool hide_usernames = !gSavedSettings.getBOOL("NearbyListHideUsernames"); - gSavedSettings.setBOOL("NearbyListHideUsernames", hide_usernames); - - mNearbyList->setShowCompleteName(!hide_usernames); - mNearbyList->handleDisplayNamesOptionChanged(); - } -} - -bool LLPanelPeople::onNearbyViewSortMenuItemCheck(const LLSD& userdata) -{ - std::string item = userdata.asString(); - U32 sort_order = gSavedSettings.getU32("NearbyPeopleSortOrder"); - - if (item == "sort_by_recent_speakers") - return sort_order == E_SORT_BY_RECENT_SPEAKERS; - if (item == "sort_name") - return sort_order == E_SORT_BY_NAME; - if (item == "sort_distance") - return sort_order == E_SORT_BY_DISTANCE; - if (item == "sort_arrival") - return sort_order == E_SORT_BY_RECENT_ARRIVAL; - - return false; -} - -void LLPanelPeople::onRecentViewSortMenuItemClicked(const LLSD& userdata) -{ - std::string chosen_item = userdata.asString(); - - if (chosen_item == "sort_recent") - { - setSortOrder(mRecentList, E_SORT_BY_MOST_RECENT); - } - else if (chosen_item == "sort_name") - { - setSortOrder(mRecentList, E_SORT_BY_NAME); - } - else if (chosen_item == "view_icons") - { - mRecentList->toggleIcons(); - } -} - -bool LLPanelPeople::onFriendsViewSortMenuItemCheck(const LLSD& userdata) -{ - std::string item = userdata.asString(); - U32 sort_order = gSavedSettings.getU32("FriendsSortOrder"); - - if (item == "sort_name") - return sort_order == E_SORT_BY_NAME; - if (item == "sort_status") - return sort_order == E_SORT_BY_STATUS; - - return false; -} - -bool LLPanelPeople::onRecentViewSortMenuItemCheck(const LLSD& userdata) -{ - std::string item = userdata.asString(); - U32 sort_order = gSavedSettings.getU32("RecentPeopleSortOrder"); - - if (item == "sort_recent") - return sort_order == E_SORT_BY_MOST_RECENT; - if (item == "sort_name") - return sort_order == E_SORT_BY_NAME; - - return false; -} - -void LLPanelPeople::onMoreButtonClicked() -{ - // *TODO: not implemented yet -} - -void LLPanelPeople::onOpen(const LLSD& key) -{ - std::string tab_name = key["people_panel_tab_name"]; - if (!tab_name.empty()) - { - mTabContainer->selectTabByName(tab_name); - if(tab_name == BLOCKED_TAB_NAME) - { - LLPanel* blocked_tab = mTabContainer->getCurrentPanel()->findChild("panel_block_list_sidetray"); - if(blocked_tab) - { - blocked_tab->onOpen(key); - } - } - } -} - -bool LLPanelPeople::notifyChildren(const LLSD& info) -{ - if (info.has("task-panel-action") && info["task-panel-action"].asString() == "handle-tri-state") - { - LLSideTrayPanelContainer* container = dynamic_cast(getParent()); - if (!container) - { - LL_WARNS() << "Cannot find People panel container" << LL_ENDL; - return true; - } - - if (container->getCurrentPanelIndex() > 0) - { - // if not on the default panel, switch to it - container->onOpen(LLSD().with(LLSideTrayPanelContainer::PARAM_SUB_PANEL_NAME, getName())); - } - else - LLFloaterReg::hideInstance("people"); - - return true; // this notification is only supposed to be handled by task panels - } - - return LLPanel::notifyChildren(info); -} - -void LLPanelPeople::showAccordion(const std::string name, bool show) -{ - if(name.empty()) - { - LL_WARNS() << "No name provided" << LL_ENDL; - return; - } - - LLAccordionCtrlTab* tab = getChild(name); - tab->setVisible(show); - if(show) - { - // don't expand accordion if it was collapsed by user - if(!isAccordionCollapsedByUser(tab)) - { - // expand accordion - tab->changeOpenClose(false); - } - } -} - -void LLPanelPeople::showFriendsAccordionsIfNeeded() -{ - if(FRIENDS_TAB_NAME == getActiveTabName()) - { - // Expand and show accordions if needed, else - hide them - showAccordion("tab_online", mOnlineFriendList->filterHasMatches()); - showAccordion("tab_all", mAllFriendList->filterHasMatches()); - - // Rearrange accordions - LLAccordionCtrl* accordion = getChild("friends_accordion"); - accordion->arrange(); - - // *TODO: new no_matched_tabs_text attribute was implemented in accordion (EXT-7368). - // this code should be refactored to use it - // keep help text in a synchronization with accordions visibility. - updateFriendListHelpText(); - } -} - -void LLPanelPeople::onFriendListRefreshComplete(LLUICtrl*ctrl, const LLSD& param) -{ - if(ctrl == mOnlineFriendList) - { - showAccordion("tab_online", param.asInteger()); - } - else if(ctrl == mAllFriendList) - { - showAccordion("tab_all", param.asInteger()); - } -} - -void LLPanelPeople::setAccordionCollapsedByUser(LLUICtrl* acc_tab, bool collapsed) -{ - if(!acc_tab) - { - LL_WARNS() << "Invalid parameter" << LL_ENDL; - return; - } - - LLSD param = acc_tab->getValue(); - param[COLLAPSED_BY_USER] = collapsed; - acc_tab->setValue(param); -} - -void LLPanelPeople::setAccordionCollapsedByUser(const std::string& name, bool collapsed) -{ - setAccordionCollapsedByUser(getChild(name), collapsed); -} - -bool LLPanelPeople::isAccordionCollapsedByUser(LLUICtrl* acc_tab) -{ - if(!acc_tab) - { - LL_WARNS() << "Invalid parameter" << LL_ENDL; - return false; - } - - LLSD param = acc_tab->getValue(); - if(!param.has(COLLAPSED_BY_USER)) - { - return false; - } - return param[COLLAPSED_BY_USER].asBoolean(); -} - -bool LLPanelPeople::isAccordionCollapsedByUser(const std::string& name) -{ - return isAccordionCollapsedByUser(getChild(name)); -} - -bool LLPanelPeople::updateNearbyArrivalTime() -{ - std::vector positions; - std::vector uuids; - static LLCachedControl range(gSavedSettings, "NearMeRange"); - LLWorld::getInstance()->getAvatars(&uuids, &positions, gAgent.getPositionGlobal(), range); - LLRecentPeople::instance().updateAvatarsArrivalTime(uuids); - return LLApp::isExiting(); -} - - -// EOF +/** + * @file llpanelpeople.cpp + * @brief Side tray "People" panel + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +// libs +#include "llavatarname.h" +#include "llconversationview.h" +#include "llfloaterimcontainer.h" +#include "llfloaterreg.h" +#include "llfloatersidepanelcontainer.h" +#include "llmenubutton.h" +#include "llmenugl.h" +#include "llnotificationsutil.h" +#include "lleventtimer.h" +#include "llfiltereditor.h" +#include "lltabcontainer.h" +#include "lltoggleablemenu.h" +#include "lluictrlfactory.h" + +#include "llpanelpeople.h" + +// newview +#include "llaccordionctrl.h" +#include "llaccordionctrltab.h" +#include "llagent.h" +#include "llagentbenefits.h" +#include "llavataractions.h" +#include "llavatarlist.h" +#include "llavatarlistitem.h" +#include "llavatarnamecache.h" +#include "llcallingcard.h" // for LLAvatarTracker +#include "llcallbacklist.h" +#include "llerror.h" +#include "llfloateravatarpicker.h" +#include "llfriendcard.h" +#include "llgroupactions.h" +#include "llgrouplist.h" +#include "llinventoryobserver.h" +#include "llnetmap.h" +#include "llpanelpeoplemenus.h" +#include "llparticipantlist.h" +#include "llsidetraypanelcontainer.h" +#include "llrecentpeople.h" +#include "llviewercontrol.h" // for gSavedSettings +#include "llviewermenu.h" // for gMenuHolder +#include "llviewerregion.h" +#include "llvoiceclient.h" +#include "llworld.h" +#include "llspeakers.h" +#include "llfloaterwebcontent.h" + +#include "llagentui.h" +#include "llslurl.h" + +#define FRIEND_LIST_UPDATE_TIMEOUT 0.5 +#define NEARBY_LIST_UPDATE_INTERVAL 1 + +static const std::string NEARBY_TAB_NAME = "nearby_panel"; +static const std::string FRIENDS_TAB_NAME = "friends_panel"; +static const std::string GROUP_TAB_NAME = "groups_panel"; +static const std::string RECENT_TAB_NAME = "recent_panel"; +static const std::string BLOCKED_TAB_NAME = "blocked_panel"; // blocked avatars +static const std::string COLLAPSED_BY_USER = "collapsed_by_user"; + +/** Comparator for comparing avatar items by last interaction date */ +class LLAvatarItemRecentComparator : public LLAvatarItemComparator +{ +public: + LLAvatarItemRecentComparator() {}; + virtual ~LLAvatarItemRecentComparator() {}; + +protected: + virtual bool doCompare(const LLAvatarListItem* avatar_item1, const LLAvatarListItem* avatar_item2) const + { + LLRecentPeople& people = LLRecentPeople::instance(); + const LLDate& date1 = people.getDate(avatar_item1->getAvatarId()); + const LLDate& date2 = people.getDate(avatar_item2->getAvatarId()); + + //older comes first + return date1 > date2; + } +}; + +/** Compares avatar items by online status, then by name */ +class LLAvatarItemStatusComparator : public LLAvatarItemComparator +{ +public: + LLAvatarItemStatusComparator() {}; + +protected: + /** + * @return true if item1 < item2, false otherwise + */ + virtual bool doCompare(const LLAvatarListItem* item1, const LLAvatarListItem* item2) const + { + LLAvatarTracker& at = LLAvatarTracker::instance(); + bool online1 = at.isBuddyOnline(item1->getAvatarId()); + bool online2 = at.isBuddyOnline(item2->getAvatarId()); + + if (online1 == online2) + { + std::string name1 = item1->getAvatarName(); + std::string name2 = item2->getAvatarName(); + + LLStringUtil::toUpper(name1); + LLStringUtil::toUpper(name2); + + return name1 < name2; + } + + return online1 > online2; + } +}; + +/** Compares avatar items by distance between you and them */ +class LLAvatarItemDistanceComparator : public LLAvatarItemComparator +{ +public: + typedef std::map < LLUUID, LLVector3d > id_to_pos_map_t; + LLAvatarItemDistanceComparator() {}; + + void updateAvatarsPositions(std::vector& positions, uuid_vec_t& uuids) + { + std::vector::const_iterator + pos_it = positions.begin(), + pos_end = positions.end(); + + uuid_vec_t::const_iterator + id_it = uuids.begin(), + id_end = uuids.end(); + + LLAvatarItemDistanceComparator::id_to_pos_map_t pos_map; + + mAvatarsPositions.clear(); + + for (;pos_it != pos_end && id_it != id_end; ++pos_it, ++id_it ) + { + mAvatarsPositions[*id_it] = *pos_it; + } + }; + +protected: + virtual bool doCompare(const LLAvatarListItem* item1, const LLAvatarListItem* item2) const + { + const LLVector3d& me_pos = gAgent.getPositionGlobal(); + const LLVector3d& item1_pos = mAvatarsPositions.find(item1->getAvatarId())->second; + const LLVector3d& item2_pos = mAvatarsPositions.find(item2->getAvatarId())->second; + + return dist_vec_squared(item1_pos, me_pos) < dist_vec_squared(item2_pos, me_pos); + } +private: + id_to_pos_map_t mAvatarsPositions; +}; + +/** Comparator for comparing nearby avatar items by last spoken time */ +class LLAvatarItemRecentSpeakerComparator : public LLAvatarItemNameComparator +{ +public: + LLAvatarItemRecentSpeakerComparator() {}; + virtual ~LLAvatarItemRecentSpeakerComparator() {}; + +protected: + virtual bool doCompare(const LLAvatarListItem* item1, const LLAvatarListItem* item2) const + { + LLPointer lhs = LLActiveSpeakerMgr::instance().findSpeaker(item1->getAvatarId()); + LLPointer rhs = LLActiveSpeakerMgr::instance().findSpeaker(item2->getAvatarId()); + if ( lhs.notNull() && rhs.notNull() ) + { + // Compare by last speaking time + if( lhs->mLastSpokeTime != rhs->mLastSpokeTime ) + return ( lhs->mLastSpokeTime > rhs->mLastSpokeTime ); + } + else if ( lhs.notNull() ) + { + // True if only item1 speaker info available + return true; + } + else if ( rhs.notNull() ) + { + // False if only item2 speaker info available + return false; + } + // By default compare by name. + return LLAvatarItemNameComparator::doCompare(item1, item2); + } +}; + +class LLAvatarItemRecentArrivalComparator : public LLAvatarItemNameComparator +{ +public: + LLAvatarItemRecentArrivalComparator() {}; + virtual ~LLAvatarItemRecentArrivalComparator() {}; + +protected: + virtual bool doCompare(const LLAvatarListItem* item1, const LLAvatarListItem* item2) const + { + + F32 arr_time1 = LLRecentPeople::instance().getArrivalTimeByID(item1->getAvatarId()); + F32 arr_time2 = LLRecentPeople::instance().getArrivalTimeByID(item2->getAvatarId()); + + if (arr_time1 == arr_time2) + { + std::string name1 = item1->getAvatarName(); + std::string name2 = item2->getAvatarName(); + + LLStringUtil::toUpper(name1); + LLStringUtil::toUpper(name2); + + return name1 < name2; + } + + return arr_time1 > arr_time2; + } +}; + +static const LLAvatarItemRecentComparator RECENT_COMPARATOR; +static const LLAvatarItemStatusComparator STATUS_COMPARATOR; +static LLAvatarItemDistanceComparator DISTANCE_COMPARATOR; +static const LLAvatarItemRecentSpeakerComparator RECENT_SPEAKER_COMPARATOR; +static LLAvatarItemRecentArrivalComparator RECENT_ARRIVAL_COMPARATOR; + +static LLPanelInjector t_people("panel_people"); + +//============================================================================= + +/** + * Updates given list either on regular basis or on external events (up to implementation). + */ +class LLPanelPeople::Updater +{ +public: + typedef boost::function callback_t; + Updater(callback_t cb) + : mCallback(cb) + { + } + + virtual ~Updater() + { + } + + /** + * Activate/deactivate updater. + * + * This may start/stop regular updates. + */ + virtual void setActive(bool) {} + +protected: + void update() + { + mCallback(); + } + + callback_t mCallback; +}; + +/** + * Update buttons on changes in our friend relations (STORM-557). + */ +class LLButtonsUpdater : public LLPanelPeople::Updater, public LLFriendObserver +{ +public: + LLButtonsUpdater(callback_t cb) + : LLPanelPeople::Updater(cb) + { + LLAvatarTracker::instance().addObserver(this); + } + + ~LLButtonsUpdater() + { + LLAvatarTracker::instance().removeObserver(this); + } + + /*virtual*/ void changed(U32 mask) + { + (void) mask; + update(); + } +}; + +class LLAvatarListUpdater : public LLPanelPeople::Updater, public LLEventTimer +{ +public: + LLAvatarListUpdater(callback_t cb, F32 period) + : LLEventTimer(period), + LLPanelPeople::Updater(cb) + { + mEventTimer.stop(); + } + + virtual bool tick() // from LLEventTimer + { + return false; + } +}; + +/** + * Updates the friends list. + * + * Updates the list on external events which trigger the changed() method. + */ +class LLFriendListUpdater : public LLAvatarListUpdater, public LLFriendObserver +{ + LOG_CLASS(LLFriendListUpdater); + class LLInventoryFriendCardObserver; + +public: + friend class LLInventoryFriendCardObserver; + LLFriendListUpdater(callback_t cb) + : LLAvatarListUpdater(cb, FRIEND_LIST_UPDATE_TIMEOUT) + , mIsActive(false) + { + LLAvatarTracker::instance().addObserver(this); + + // For notification when SIP online status changes. + LLVoiceClient::getInstance()->addObserver(this); + mInvObserver = new LLInventoryFriendCardObserver(this); + } + + ~LLFriendListUpdater() + { + // will be deleted by ~LLInventoryModel + //delete mInvObserver; + if (LLVoiceClient::instanceExists()) + { + LLVoiceClient::getInstance()->removeObserver(this); + } + LLAvatarTracker::instance().removeObserver(this); + } + + /*virtual*/ void changed(U32 mask) + { + if (mIsActive) + { + // events can arrive quickly in bulk - we need not process EVERY one of them - + // so we wait a short while to let others pile-in, and process them in aggregate. + mEventTimer.start(); + } + + // save-up all the mask-bits which have come-in + mMask |= mask; + } + + + /*virtual*/ bool tick() + { + if (!mIsActive) return false; + + if (mMask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE | LLFriendObserver::ONLINE)) + { + update(); + } + + // Stop updates. + mEventTimer.stop(); + mMask = 0; + + return false; + } + + // virtual + void setActive(bool active) + { + mIsActive = active; + if (active) + { + tick(); + } + } + +private: + U32 mMask; + LLInventoryFriendCardObserver* mInvObserver; + bool mIsActive; + + /** + * This class is intended for updating Friend List when Inventory Friend Card is added/removed. + * + * The main usage is when Inventory Friends/All content is added while synchronizing with + * friends list on startup is performed. In this case Friend Panel should be updated when + * missing Inventory Friend Card is created. + * *NOTE: updating is fired when Inventory item is added into CallingCards/Friends subfolder. + * Otherwise LLFriendObserver functionality is enough to keep Friends Panel synchronized. + */ + class LLInventoryFriendCardObserver : public LLInventoryObserver + { + LOG_CLASS(LLFriendListUpdater::LLInventoryFriendCardObserver); + + friend class LLFriendListUpdater; + + private: + LLInventoryFriendCardObserver(LLFriendListUpdater* updater) : mUpdater(updater) + { + gInventory.addObserver(this); + } + ~LLInventoryFriendCardObserver() + { + gInventory.removeObserver(this); + } + /*virtual*/ void changed(U32 mask) + { + LL_DEBUGS() << "Inventory changed: " << mask << LL_ENDL; + + static bool synchronize_friends_folders = true; + if (synchronize_friends_folders) + { + // Checks whether "Friends" and "Friends/All" folders exist in "Calling Cards" folder, + // fetches their contents if needed and synchronizes it with buddies list. + // If the folders are not found they are created. + LLFriendCardsManager::instance().syncFriendCardsFolders(); + synchronize_friends_folders = false; + } + + // *NOTE: deleting of InventoryItem is performed via moving to Trash. + // That means LLInventoryObserver::STRUCTURE is present in MASK instead of LLInventoryObserver::REMOVE + if ((CALLINGCARD_ADDED & mask) == CALLINGCARD_ADDED) + { + LL_DEBUGS() << "Calling card added: count: " << gInventory.getChangedIDs().size() + << ", first Inventory ID: "<< (*gInventory.getChangedIDs().begin()) + << LL_ENDL; + + bool friendFound = false; + std::set changedIDs = gInventory.getChangedIDs(); + for (std::set::const_iterator it = changedIDs.begin(); it != changedIDs.end(); ++it) + { + if (isDescendentOfInventoryFriends(*it)) + { + friendFound = true; + break; + } + } + + if (friendFound) + { + LL_DEBUGS() << "friend found, panel should be updated" << LL_ENDL; + mUpdater->changed(LLFriendObserver::ADD); + } + } + } + + bool isDescendentOfInventoryFriends(const LLUUID& invItemID) + { + LLViewerInventoryItem * item = gInventory.getItem(invItemID); + if (NULL == item) + return false; + + return LLFriendCardsManager::instance().isItemInAnyFriendsList(item); + } + LLFriendListUpdater* mUpdater; + + static const U32 CALLINGCARD_ADDED = LLInventoryObserver::ADD | LLInventoryObserver::CALLING_CARD; + }; +}; + +/** + * Periodically updates the nearby people list while the Nearby tab is active. + * + * The period is defined by NEARBY_LIST_UPDATE_INTERVAL constant. + */ +class LLNearbyListUpdater : public LLAvatarListUpdater +{ + LOG_CLASS(LLNearbyListUpdater); + +public: + LLNearbyListUpdater(callback_t cb) + : LLAvatarListUpdater(cb, NEARBY_LIST_UPDATE_INTERVAL) + { + setActive(false); + } + + /*virtual*/ void setActive(bool val) + { + if (val) + { + // update immediately and start regular updates + update(); + mEventTimer.start(); + } + else + { + // stop regular updates + mEventTimer.stop(); + } + } + + /*virtual*/ bool tick() + { + update(); + return false; + } +private: +}; + +/** + * Updates the recent people list (those the agent has recently interacted with). + */ +class LLRecentListUpdater : public LLAvatarListUpdater, public boost::signals2::trackable +{ + LOG_CLASS(LLRecentListUpdater); + +public: + LLRecentListUpdater(callback_t cb) + : LLAvatarListUpdater(cb, 0) + { + LLRecentPeople::instance().setChangedCallback(boost::bind(&LLRecentListUpdater::update, this)); + } +}; + +//============================================================================= + +LLPanelPeople::LLPanelPeople() + : LLPanel(), + mTabContainer(NULL), + mOnlineFriendList(NULL), + mAllFriendList(NULL), + mNearbyList(NULL), + mRecentList(NULL), + mGroupList(NULL), + mMiniMap(NULL) +{ + mFriendListUpdater = new LLFriendListUpdater(boost::bind(&LLPanelPeople::updateFriendList, this)); + mNearbyListUpdater = new LLNearbyListUpdater(boost::bind(&LLPanelPeople::updateNearbyList, this)); + mRecentListUpdater = new LLRecentListUpdater(boost::bind(&LLPanelPeople::updateRecentList, this)); + mButtonsUpdater = new LLButtonsUpdater(boost::bind(&LLPanelPeople::updateButtons, this)); + + mCommitCallbackRegistrar.add("People.AddFriend", boost::bind(&LLPanelPeople::onAddFriendButtonClicked, this)); + mCommitCallbackRegistrar.add("People.AddFriendWizard", boost::bind(&LLPanelPeople::onAddFriendWizButtonClicked, this)); + mCommitCallbackRegistrar.add("People.DelFriend", boost::bind(&LLPanelPeople::onDeleteFriendButtonClicked, this)); + mCommitCallbackRegistrar.add("People.Group.Minus", boost::bind(&LLPanelPeople::onGroupMinusButtonClicked, this)); + mCommitCallbackRegistrar.add("People.Chat", boost::bind(&LLPanelPeople::onChatButtonClicked, this)); + mCommitCallbackRegistrar.add("People.Gear", boost::bind(&LLPanelPeople::onGearButtonClicked, this, _1)); + + mCommitCallbackRegistrar.add("People.Group.Plus.Action", boost::bind(&LLPanelPeople::onGroupPlusMenuItemClicked, this, _2)); + mCommitCallbackRegistrar.add("People.Friends.ViewSort.Action", boost::bind(&LLPanelPeople::onFriendsViewSortMenuItemClicked, this, _2)); + mCommitCallbackRegistrar.add("People.Nearby.ViewSort.Action", boost::bind(&LLPanelPeople::onNearbyViewSortMenuItemClicked, this, _2)); + mCommitCallbackRegistrar.add("People.Groups.ViewSort.Action", boost::bind(&LLPanelPeople::onGroupsViewSortMenuItemClicked, this, _2)); + mCommitCallbackRegistrar.add("People.Recent.ViewSort.Action", boost::bind(&LLPanelPeople::onRecentViewSortMenuItemClicked, this, _2)); + + mEnableCallbackRegistrar.add("People.Friends.ViewSort.CheckItem", boost::bind(&LLPanelPeople::onFriendsViewSortMenuItemCheck, this, _2)); + mEnableCallbackRegistrar.add("People.Recent.ViewSort.CheckItem", boost::bind(&LLPanelPeople::onRecentViewSortMenuItemCheck, this, _2)); + mEnableCallbackRegistrar.add("People.Nearby.ViewSort.CheckItem", boost::bind(&LLPanelPeople::onNearbyViewSortMenuItemCheck, this, _2)); + + mEnableCallbackRegistrar.add("People.Group.Plus.Validate", boost::bind(&LLPanelPeople::onGroupPlusButtonValidate, this)); + + doPeriodically(boost::bind(&LLPanelPeople::updateNearbyArrivalTime, this), 2.0); +} + +LLPanelPeople::~LLPanelPeople() +{ + delete mButtonsUpdater; + delete mNearbyListUpdater; + delete mFriendListUpdater; + delete mRecentListUpdater; + + mNearbyFilterCommitConnection.disconnect(); + mFriedsFilterCommitConnection.disconnect(); + mGroupsFilterCommitConnection.disconnect(); + mRecentFilterCommitConnection.disconnect(); + + if(LLVoiceClient::instanceExists()) + { + LLVoiceClient::getInstance()->removeObserver(this); + } +} + +void LLPanelPeople::onFriendsAccordionExpandedCollapsed(LLUICtrl* ctrl, const LLSD& param, LLAvatarList* avatar_list) +{ + if(!avatar_list) + { + LL_ERRS() << "Bad parameter" << LL_ENDL; + return; + } + + bool expanded = param.asBoolean(); + + setAccordionCollapsedByUser(ctrl, !expanded); + if(!expanded) + { + avatar_list->resetSelection(); + } +} + + +void LLPanelPeople::removePicker() +{ + if(mPicker.get()) + { + mPicker.get()->closeFloater(); + } +} + +bool LLPanelPeople::postBuild() +{ + S32 max_premium = LLAgentBenefitsMgr::get("Premium").getGroupMembershipLimit(); + + mNearbyFilterCommitConnection = getChild("nearby_filter_input")->setCommitCallback(boost::bind(&LLPanelPeople::onFilterEdit, this, _2)); + mFriedsFilterCommitConnection = getChild("friends_filter_input")->setCommitCallback(boost::bind(&LLPanelPeople::onFilterEdit, this, _2)); + mGroupsFilterCommitConnection = getChild("groups_filter_input")->setCommitCallback(boost::bind(&LLPanelPeople::onFilterEdit, this, _2)); + mRecentFilterCommitConnection = getChild("recent_filter_input")->setCommitCallback(boost::bind(&LLPanelPeople::onFilterEdit, this, _2)); + + if(LLAgentBenefitsMgr::current().getGroupMembershipLimit() < max_premium) + { + getChild("groupcount")->setText(getString("GroupCountWithInfo")); + getChild("groupcount")->setURLClickedCallback(boost::bind(&LLPanelPeople::onGroupLimitInfo, this)); + } + + mTabContainer = getChild("tabs"); + mTabContainer->setCommitCallback(boost::bind(&LLPanelPeople::onTabSelected, this, _2)); + mSavedFilters.resize(mTabContainer->getTabCount()); + mSavedOriginalFilters.resize(mTabContainer->getTabCount()); + + LLPanel* friends_tab = getChild(FRIENDS_TAB_NAME); + // updater is active only if panel is visible to user. + friends_tab->setVisibleCallback(boost::bind(&Updater::setActive, mFriendListUpdater, _2)); + friends_tab->setVisibleCallback(boost::bind(&LLPanelPeople::removePicker, this)); + + mOnlineFriendList = friends_tab->getChild("avatars_online"); + mAllFriendList = friends_tab->getChild("avatars_all"); + mOnlineFriendList->setNoItemsCommentText(getString("no_friends_online")); + mOnlineFriendList->setShowIcons("FriendsListShowIcons"); + mOnlineFriendList->showPermissions("FriendsListShowPermissions"); + mOnlineFriendList->setShowCompleteName(!gSavedSettings.getBOOL("FriendsListHideUsernames")); + mAllFriendList->setNoItemsCommentText(getString("no_friends")); + mAllFriendList->setShowIcons("FriendsListShowIcons"); + mAllFriendList->showPermissions("FriendsListShowPermissions"); + mAllFriendList->setShowCompleteName(!gSavedSettings.getBOOL("FriendsListHideUsernames")); + + LLPanel* nearby_tab = getChild(NEARBY_TAB_NAME); + nearby_tab->setVisibleCallback(boost::bind(&Updater::setActive, mNearbyListUpdater, _2)); + mNearbyList = nearby_tab->getChild("avatar_list"); + mNearbyList->setNoItemsCommentText(getString("no_one_near")); + mNearbyList->setNoItemsMsg(getString("no_one_near")); + mNearbyList->setNoFilteredItemsMsg(getString("no_one_filtered_near")); + mNearbyList->setShowIcons("NearbyListShowIcons"); + mNearbyList->setShowCompleteName(!gSavedSettings.getBOOL("NearbyListHideUsernames")); + mMiniMap = (LLNetMap*)getChildView("Net Map",true); + mMiniMap->setToolTipMsg(gSavedSettings.getBOOL("DoubleClickTeleport") ? + getString("AltMiniMapToolTipMsg") : getString("MiniMapToolTipMsg")); + + mRecentList = getChild(RECENT_TAB_NAME)->getChild("avatar_list"); + mRecentList->setNoItemsCommentText(getString("no_recent_people")); + mRecentList->setNoItemsMsg(getString("no_recent_people")); + mRecentList->setNoFilteredItemsMsg(getString("no_filtered_recent_people")); + mRecentList->setShowIcons("RecentListShowIcons"); + + mGroupList = getChild("group_list"); + mGroupList->setNoItemsCommentText(getString("no_groups_msg")); + mGroupList->setNoItemsMsg(getString("no_groups_msg")); + mGroupList->setNoFilteredItemsMsg(getString("no_filtered_groups_msg")); + + mNearbyList->setContextMenu(&LLPanelPeopleMenus::gNearbyPeopleContextMenu); + mRecentList->setContextMenu(&LLPanelPeopleMenus::gPeopleContextMenu); + mAllFriendList->setContextMenu(&LLPanelPeopleMenus::gPeopleContextMenu); + mOnlineFriendList->setContextMenu(&LLPanelPeopleMenus::gPeopleContextMenu); + + setSortOrder(mRecentList, (ESortOrder)gSavedSettings.getU32("RecentPeopleSortOrder"), false); + setSortOrder(mAllFriendList, (ESortOrder)gSavedSettings.getU32("FriendsSortOrder"), false); + setSortOrder(mNearbyList, (ESortOrder)gSavedSettings.getU32("NearbyPeopleSortOrder"), false); + + mOnlineFriendList->setItemDoubleClickCallback(boost::bind(&LLPanelPeople::onAvatarListDoubleClicked, this, _1)); + mAllFriendList->setItemDoubleClickCallback(boost::bind(&LLPanelPeople::onAvatarListDoubleClicked, this, _1)); + mNearbyList->setItemDoubleClickCallback(boost::bind(&LLPanelPeople::onAvatarListDoubleClicked, this, _1)); + mRecentList->setItemDoubleClickCallback(boost::bind(&LLPanelPeople::onAvatarListDoubleClicked, this, _1)); + + mOnlineFriendList->setCommitCallback(boost::bind(&LLPanelPeople::onAvatarListCommitted, this, mOnlineFriendList)); + mAllFriendList->setCommitCallback(boost::bind(&LLPanelPeople::onAvatarListCommitted, this, mAllFriendList)); + mNearbyList->setCommitCallback(boost::bind(&LLPanelPeople::onAvatarListCommitted, this, mNearbyList)); + mRecentList->setCommitCallback(boost::bind(&LLPanelPeople::onAvatarListCommitted, this, mRecentList)); + + // Set openning IM as default on return action for avatar lists + mOnlineFriendList->setReturnCallback(boost::bind(&LLPanelPeople::onImButtonClicked, this)); + mAllFriendList->setReturnCallback(boost::bind(&LLPanelPeople::onImButtonClicked, this)); + mNearbyList->setReturnCallback(boost::bind(&LLPanelPeople::onImButtonClicked, this)); + mRecentList->setReturnCallback(boost::bind(&LLPanelPeople::onImButtonClicked, this)); + + mGroupList->setDoubleClickCallback(boost::bind(&LLPanelPeople::onChatButtonClicked, this)); + mGroupList->setCommitCallback(boost::bind(&LLPanelPeople::updateButtons, this)); + mGroupList->setReturnCallback(boost::bind(&LLPanelPeople::onChatButtonClicked, this)); + + LLMenuButton* groups_gear_btn = getChild("groups_gear_btn"); + + // Use the context menu of the Groups list for the Groups tab gear menu. + LLToggleableMenu* groups_gear_menu = mGroupList->getContextMenu(); + if (groups_gear_menu) + { + groups_gear_btn->setMenu(groups_gear_menu, LLMenuButton::MP_BOTTOM_LEFT); + } + else + { + LL_WARNS() << "People->Groups list menu not found" << LL_ENDL; + } + + LLAccordionCtrlTab* accordion_tab = getChild("tab_all"); + accordion_tab->setDropDownStateChangedCallback( + boost::bind(&LLPanelPeople::onFriendsAccordionExpandedCollapsed, this, _1, _2, mAllFriendList)); + + accordion_tab = getChild("tab_online"); + accordion_tab->setDropDownStateChangedCallback( + boost::bind(&LLPanelPeople::onFriendsAccordionExpandedCollapsed, this, _1, _2, mOnlineFriendList)); + + // Must go after setting commit callback and initializing all pointers to children. + mTabContainer->selectTabByName(NEARBY_TAB_NAME); + + LLVoiceClient::getInstance()->addObserver(this); + + // call this method in case some list is empty and buttons can be in inconsistent state + updateButtons(); + + mOnlineFriendList->setRefreshCompleteCallback(boost::bind(&LLPanelPeople::onFriendListRefreshComplete, this, _1, _2)); + mAllFriendList->setRefreshCompleteCallback(boost::bind(&LLPanelPeople::onFriendListRefreshComplete, this, _1, _2)); + + return true; +} + +// virtual +void LLPanelPeople::onChange(EStatusType status, const std::string &channelURI, bool proximal) +{ + if(status == STATUS_JOINING || status == STATUS_LEFT_CHANNEL) + { + return; + } + + updateButtons(); +} + +void LLPanelPeople::updateFriendListHelpText() +{ + // show special help text for just created account to help finding friends. EXT-4836 + static LLTextBox* no_friends_text = getChild("no_friends_help_text"); + + // Seems sometimes all_friends can be empty because of issue with Inventory loading (clear cache, slow connection...) + // So, lets check all lists to avoid overlapping the text with online list. See EXT-6448. + bool any_friend_exists = mAllFriendList->filterHasMatches() || mOnlineFriendList->filterHasMatches(); + no_friends_text->setVisible(!any_friend_exists); + if (no_friends_text->getVisible()) + { + //update help text for empty lists + const std::string& filter = mSavedOriginalFilters[mTabContainer->getCurrentPanelIndex()]; + + std::string message_name = filter.empty() ? "no_friends_msg" : "no_filtered_friends_msg"; + LLStringUtil::format_map_t args; + args["[SEARCH_TERM]"] = LLURI::escape(filter); + no_friends_text->setText(getString(message_name, args)); + } +} + +void LLPanelPeople::updateFriendList() +{ + if (!mOnlineFriendList || !mAllFriendList) + return; + + // get all buddies we know about + const LLAvatarTracker& av_tracker = LLAvatarTracker::instance(); + LLAvatarTracker::buddy_map_t all_buddies; + av_tracker.copyBuddyList(all_buddies); + + // save them to the online and all friends vectors + uuid_vec_t& online_friendsp = mOnlineFriendList->getIDs(); + uuid_vec_t& all_friendsp = mAllFriendList->getIDs(); + + all_friendsp.clear(); + online_friendsp.clear(); + + uuid_vec_t buddies_uuids; + LLAvatarTracker::buddy_map_t::const_iterator buddies_iter; + + // Fill the avatar list with friends UUIDs + for (buddies_iter = all_buddies.begin(); buddies_iter != all_buddies.end(); ++buddies_iter) + { + buddies_uuids.push_back(buddies_iter->first); + } + + if (buddies_uuids.size() > 0) + { + LL_DEBUGS() << "Friends added to the list: " << buddies_uuids.size() << LL_ENDL; + all_friendsp = buddies_uuids; + } + else + { + LL_DEBUGS() << "No friends found" << LL_ENDL; + } + + LLAvatarTracker::buddy_map_t::const_iterator buddy_it = all_buddies.begin(); + for (; buddy_it != all_buddies.end(); ++buddy_it) + { + LLUUID buddy_id = buddy_it->first; + if (av_tracker.isBuddyOnline(buddy_id)) + online_friendsp.push_back(buddy_id); + } + + /* + * Avatarlists will be hidden by showFriendsAccordionsIfNeeded(), if they do not have items. + * But avatarlist can be updated only if it is visible @see LLAvatarList::draw(); + * So we need to do force update of lists to avoid inconsistency of data and view of avatarlist. + */ + mOnlineFriendList->setDirty(true, !mOnlineFriendList->filterHasMatches());// do force update if list do NOT have items + mAllFriendList->setDirty(true, !mAllFriendList->filterHasMatches()); + //update trash and other buttons according to a selected item + updateButtons(); + showFriendsAccordionsIfNeeded(); +} + +void LLPanelPeople::updateNearbyList() +{ + if (!mNearbyList) + return; + + std::vector positions; + + LLWorld::getInstance()->getAvatars(&mNearbyList->getIDs(), &positions, gAgent.getPositionGlobal(), gSavedSettings.getF32("NearMeRange")); + mNearbyList->setDirty(); + + DISTANCE_COMPARATOR.updateAvatarsPositions(positions, mNearbyList->getIDs()); + LLActiveSpeakerMgr::instance().update(true); +} + +void LLPanelPeople::updateRecentList() +{ + if (!mRecentList) + return; + + LLRecentPeople::instance().get(mRecentList->getIDs()); + mRecentList->setDirty(); +} + +void LLPanelPeople::updateButtons() +{ + std::string cur_tab = getActiveTabName(); + bool friends_tab_active = (cur_tab == FRIENDS_TAB_NAME); + bool group_tab_active = (cur_tab == GROUP_TAB_NAME); + //bool recent_tab_active = (cur_tab == RECENT_TAB_NAME); + LLUUID selected_id; + + uuid_vec_t selected_uuids; + getCurrentItemIDs(selected_uuids); + bool item_selected = (selected_uuids.size() == 1); + bool multiple_selected = (selected_uuids.size() >= 1); + + if (group_tab_active) + { + if (item_selected) + { + selected_id = mGroupList->getSelectedUUID(); + } + + LLPanel* groups_panel = mTabContainer->getCurrentPanel(); + groups_panel->getChildView("minus_btn")->setEnabled(item_selected && selected_id.notNull()); // a real group selected + + U32 groups_count = gAgent.mGroups.size(); + S32 max_groups = LLAgentBenefitsMgr::current().getGroupMembershipLimit(); + U32 groups_remaining = max_groups > groups_count ? max_groups - groups_count : 0; + groups_panel->getChild("groupcount")->setTextArg("[COUNT]", llformat("%d", groups_count)); + groups_panel->getChild("groupcount")->setTextArg("[REMAINING]", llformat("%d", groups_remaining)); + } + else + { + bool is_friend = true; + bool is_self = false; + // Check whether selected avatar is our friend. + if (item_selected) + { + selected_id = selected_uuids.front(); + is_friend = LLAvatarTracker::instance().getBuddyInfo(selected_id) != NULL; + is_self = gAgent.getID() == selected_id; + } + + LLPanel* cur_panel = mTabContainer->getCurrentPanel(); + if (cur_panel) + { + if (cur_panel->hasChild("add_friend_btn", true)) + cur_panel->getChildView("add_friend_btn")->setEnabled(item_selected && !is_friend && !is_self); + + if (friends_tab_active) + { + cur_panel->getChildView("friends_del_btn")->setEnabled(multiple_selected); + } + + if (!group_tab_active) + { + cur_panel->getChildView("gear_btn")->setEnabled(multiple_selected); + } + } + } +} + +std::string LLPanelPeople::getActiveTabName() const +{ + return mTabContainer->getCurrentPanel()->getName(); +} + +LLUUID LLPanelPeople::getCurrentItemID() const +{ + std::string cur_tab = getActiveTabName(); + + if (cur_tab == FRIENDS_TAB_NAME) // this tab has two lists + { + LLUUID cur_online_friend; + + if ((cur_online_friend = mOnlineFriendList->getSelectedUUID()).notNull()) + return cur_online_friend; + + return mAllFriendList->getSelectedUUID(); + } + + if (cur_tab == NEARBY_TAB_NAME) + return mNearbyList->getSelectedUUID(); + + if (cur_tab == RECENT_TAB_NAME) + return mRecentList->getSelectedUUID(); + + if (cur_tab == GROUP_TAB_NAME) + return mGroupList->getSelectedUUID(); + + if (cur_tab == BLOCKED_TAB_NAME) + return LLUUID::null; // FIXME? + + llassert(0 && "unknown tab selected"); + return LLUUID::null; +} + +void LLPanelPeople::getCurrentItemIDs(uuid_vec_t& selected_uuids) const +{ + std::string cur_tab = getActiveTabName(); + + if (cur_tab == FRIENDS_TAB_NAME) + { + // friends tab has two lists + mOnlineFriendList->getSelectedUUIDs(selected_uuids); + mAllFriendList->getSelectedUUIDs(selected_uuids); + } + else if (cur_tab == NEARBY_TAB_NAME) + mNearbyList->getSelectedUUIDs(selected_uuids); + else if (cur_tab == RECENT_TAB_NAME) + mRecentList->getSelectedUUIDs(selected_uuids); + else if (cur_tab == GROUP_TAB_NAME) + mGroupList->getSelectedUUIDs(selected_uuids); + else if (cur_tab == BLOCKED_TAB_NAME) + selected_uuids.clear(); // FIXME? + else + llassert(0 && "unknown tab selected"); + +} + +void LLPanelPeople::setSortOrder(LLAvatarList* list, ESortOrder order, bool save) +{ + switch (order) + { + case E_SORT_BY_NAME: + list->sortByName(); + break; + case E_SORT_BY_STATUS: + list->setComparator(&STATUS_COMPARATOR); + list->sort(); + break; + case E_SORT_BY_MOST_RECENT: + list->setComparator(&RECENT_COMPARATOR); + list->sort(); + break; + case E_SORT_BY_RECENT_SPEAKERS: + list->setComparator(&RECENT_SPEAKER_COMPARATOR); + list->sort(); + break; + case E_SORT_BY_DISTANCE: + list->setComparator(&DISTANCE_COMPARATOR); + list->sort(); + break; + case E_SORT_BY_RECENT_ARRIVAL: + list->setComparator(&RECENT_ARRIVAL_COMPARATOR); + list->sort(); + break; + default: + LL_WARNS() << "Unrecognized people sort order for " << list->getName() << LL_ENDL; + return; + } + + if (save) + { + std::string setting; + + if (list == mAllFriendList || list == mOnlineFriendList) + setting = "FriendsSortOrder"; + else if (list == mRecentList) + setting = "RecentPeopleSortOrder"; + else if (list == mNearbyList) + setting = "NearbyPeopleSortOrder"; + + if (!setting.empty()) + gSavedSettings.setU32(setting, order); + } +} + +void LLPanelPeople::onFilterEdit(const std::string& search_string) +{ + const S32 cur_tab_idx = mTabContainer->getCurrentPanelIndex(); + std::string& filter = mSavedOriginalFilters[cur_tab_idx]; + std::string& saved_filter = mSavedFilters[cur_tab_idx]; + + filter = search_string; + LLStringUtil::trimHead(filter); + + // Searches are case-insensitive + std::string search_upper = filter; + LLStringUtil::toUpper(search_upper); + + if (saved_filter == search_upper) + return; + + saved_filter = search_upper; + + // Apply new filter to the current tab. + const std::string cur_tab = getActiveTabName(); + if (cur_tab == NEARBY_TAB_NAME) + { + mNearbyList->setNameFilter(filter); + } + else if (cur_tab == FRIENDS_TAB_NAME) + { + // store accordion tabs opened/closed state before any manipulation with accordion tabs + if (!saved_filter.empty()) + { + notifyChildren(LLSD().with("action","store_state")); + } + + mOnlineFriendList->setNameFilter(filter); + mAllFriendList->setNameFilter(filter); + + setAccordionCollapsedByUser("tab_online", false); + setAccordionCollapsedByUser("tab_all", false); + showFriendsAccordionsIfNeeded(); + + // restore accordion tabs state _after_ all manipulations + if(saved_filter.empty()) + { + notifyChildren(LLSD().with("action","restore_state")); + } + } + else if (cur_tab == GROUP_TAB_NAME) + { + mGroupList->setNameFilter(filter); + } + else if (cur_tab == RECENT_TAB_NAME) + { + mRecentList->setNameFilter(filter); + } +} + +void LLPanelPeople::onGroupLimitInfo() +{ + LLSD args; + + S32 max_basic = LLAgentBenefitsMgr::get("Base").getGroupMembershipLimit(); + S32 max_premium = LLAgentBenefitsMgr::get("Premium").getGroupMembershipLimit(); + + args["MAX_BASIC"] = max_basic; + args["MAX_PREMIUM"] = max_premium; + + if (LLAgentBenefitsMgr::has("Premium_Plus")) + { + S32 max_premium_plus = LLAgentBenefitsMgr::get("Premium_Plus").getGroupMembershipLimit(); + args["MAX_PREMIUM_PLUS"] = max_premium_plus; + LLNotificationsUtil::add("GroupLimitInfoPlus", args); + } + else + { + LLNotificationsUtil::add("GroupLimitInfo", args); + } +} + +void LLPanelPeople::onTabSelected(const LLSD& param) +{ + std::string tab_name = getChild(param.asString())->getName(); + updateButtons(); + + showFriendsAccordionsIfNeeded(); +} + +void LLPanelPeople::onAvatarListDoubleClicked(LLUICtrl* ctrl) +{ + LLAvatarListItem* item = dynamic_cast(ctrl); + if(!item) + { + return; + } + + LLUUID clicked_id = item->getAvatarId(); + if(gAgent.getID() == clicked_id) + { + return; + } + +#if 0 // SJB: Useful for testing, but not currently functional or to spec + LLAvatarActions::showProfile(clicked_id); +#else // spec says open IM window + LLAvatarActions::startIM(clicked_id); +#endif +} + +void LLPanelPeople::onAvatarListCommitted(LLAvatarList* list) +{ + if (getActiveTabName() == NEARBY_TAB_NAME) + { + uuid_vec_t selected_uuids; + getCurrentItemIDs(selected_uuids); + mMiniMap->setSelected(selected_uuids); + } else + // Make sure only one of the friends lists (online/all) has selection. + if (getActiveTabName() == FRIENDS_TAB_NAME) + { + if (list == mOnlineFriendList) + mAllFriendList->resetSelection(true); + else if (list == mAllFriendList) + mOnlineFriendList->resetSelection(true); + else + llassert(0 && "commit on unknown friends list"); + } + + updateButtons(); +} + +void LLPanelPeople::onAddFriendButtonClicked() +{ + LLUUID id = getCurrentItemID(); + if (id.notNull()) + { + LLAvatarActions::requestFriendshipDialog(id); + } +} + +bool LLPanelPeople::isItemsFreeOfFriends(const uuid_vec_t& uuids) +{ + const LLAvatarTracker& av_tracker = LLAvatarTracker::instance(); + for ( uuid_vec_t::const_iterator + id = uuids.begin(), + id_end = uuids.end(); + id != id_end; ++id ) + { + if (av_tracker.isBuddy (*id)) + { + return false; + } + } + return true; +} + +void LLPanelPeople::onAddFriendWizButtonClicked() +{ + LLPanel* cur_panel = mTabContainer->getCurrentPanel(); + LLView * button = cur_panel->findChild("friends_add_btn", true); + + // Show add friend wizard. + LLFloater* root_floater = gFloaterView->getParentFloater(this); + LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show(boost::bind(&LLPanelPeople::onAvatarPicked, _1, _2), false, true, false, root_floater->getName(), button); + if (!picker) + { + return; + } + + // Need to disable 'ok' button when friend occurs in selection + picker->setOkBtnEnableCb(boost::bind(&LLPanelPeople::isItemsFreeOfFriends, this, _1)); + + if (root_floater) + { + root_floater->addDependentFloater(picker); + } + + mPicker = picker->getHandle(); +} + +void LLPanelPeople::onDeleteFriendButtonClicked() +{ + uuid_vec_t selected_uuids; + getCurrentItemIDs(selected_uuids); + + if (selected_uuids.size() == 1) + { + LLAvatarActions::removeFriendDialog( selected_uuids.front() ); + } + else if (selected_uuids.size() > 1) + { + LLAvatarActions::removeFriendsDialog( selected_uuids ); + } +} + +void LLPanelPeople::onChatButtonClicked() +{ + LLUUID group_id = getCurrentItemID(); + if (group_id.notNull()) + LLGroupActions::startIM(group_id); +} + +void LLPanelPeople::onGearButtonClicked(LLUICtrl* btn) +{ + uuid_vec_t selected_uuids; + getCurrentItemIDs(selected_uuids); + // Spawn at bottom left corner of the button. + if (getActiveTabName() == NEARBY_TAB_NAME) + LLPanelPeopleMenus::gNearbyPeopleContextMenu.show(btn, selected_uuids, 0, 0); + else + LLPanelPeopleMenus::gPeopleContextMenu.show(btn, selected_uuids, 0, 0); +} + +void LLPanelPeople::onImButtonClicked() +{ + uuid_vec_t selected_uuids; + getCurrentItemIDs(selected_uuids); + if ( selected_uuids.size() == 1 ) + { + // if selected only one person then start up IM + LLAvatarActions::startIM(selected_uuids.at(0)); + } + else if ( selected_uuids.size() > 1 ) + { + // for multiple selection start up friends conference + LLAvatarActions::startConference(selected_uuids); + } +} + +// static +void LLPanelPeople::onAvatarPicked(const uuid_vec_t& ids, const std::vector names) +{ + if (!names.empty() && !ids.empty()) + LLAvatarActions::requestFriendshipDialog(ids[0], names[0].getCompleteName()); +} + +bool LLPanelPeople::onGroupPlusButtonValidate() +{ + if (!gAgent.canJoinGroups()) + { + LLNotificationsUtil::add("JoinedTooManyGroups"); + return false; + } + + return true; +} + +void LLPanelPeople::onGroupMinusButtonClicked() +{ + LLUUID group_id = getCurrentItemID(); + if (group_id.notNull()) + LLGroupActions::leave(group_id); +} + +void LLPanelPeople::onGroupPlusMenuItemClicked(const LLSD& userdata) +{ + std::string chosen_item = userdata.asString(); + + if (chosen_item == "join_group") + LLGroupActions::search(); + else if (chosen_item == "new_group") + LLGroupActions::createGroup(); +} + +void LLPanelPeople::onFriendsViewSortMenuItemClicked(const LLSD& userdata) +{ + std::string chosen_item = userdata.asString(); + + if (chosen_item == "sort_name") + { + setSortOrder(mAllFriendList, E_SORT_BY_NAME); + } + else if (chosen_item == "sort_status") + { + setSortOrder(mAllFriendList, E_SORT_BY_STATUS); + } + else if (chosen_item == "view_icons") + { + mAllFriendList->toggleIcons(); + mOnlineFriendList->toggleIcons(); + } + else if (chosen_item == "view_permissions") + { + bool show_permissions = !gSavedSettings.getBOOL("FriendsListShowPermissions"); + gSavedSettings.setBOOL("FriendsListShowPermissions", show_permissions); + + mAllFriendList->showPermissions(show_permissions); + mOnlineFriendList->showPermissions(show_permissions); + } + else if (chosen_item == "view_usernames") + { + bool hide_usernames = !gSavedSettings.getBOOL("FriendsListHideUsernames"); + gSavedSettings.setBOOL("FriendsListHideUsernames", hide_usernames); + + mAllFriendList->setShowCompleteName(!hide_usernames); + mAllFriendList->handleDisplayNamesOptionChanged(); + mOnlineFriendList->setShowCompleteName(!hide_usernames); + mOnlineFriendList->handleDisplayNamesOptionChanged(); + } + } + +void LLPanelPeople::onGroupsViewSortMenuItemClicked(const LLSD& userdata) +{ + std::string chosen_item = userdata.asString(); + + if (chosen_item == "show_icons") + { + mGroupList->toggleIcons(); + } +} + +void LLPanelPeople::onNearbyViewSortMenuItemClicked(const LLSD& userdata) +{ + std::string chosen_item = userdata.asString(); + + if (chosen_item == "sort_by_recent_speakers") + { + setSortOrder(mNearbyList, E_SORT_BY_RECENT_SPEAKERS); + } + else if (chosen_item == "sort_name") + { + setSortOrder(mNearbyList, E_SORT_BY_NAME); + } + else if (chosen_item == "view_icons") + { + mNearbyList->toggleIcons(); + } + else if (chosen_item == "sort_distance") + { + setSortOrder(mNearbyList, E_SORT_BY_DISTANCE); + } + else if (chosen_item == "sort_arrival") + { + setSortOrder(mNearbyList, E_SORT_BY_RECENT_ARRIVAL); + } + else if (chosen_item == "view_usernames") + { + bool hide_usernames = !gSavedSettings.getBOOL("NearbyListHideUsernames"); + gSavedSettings.setBOOL("NearbyListHideUsernames", hide_usernames); + + mNearbyList->setShowCompleteName(!hide_usernames); + mNearbyList->handleDisplayNamesOptionChanged(); + } +} + +bool LLPanelPeople::onNearbyViewSortMenuItemCheck(const LLSD& userdata) +{ + std::string item = userdata.asString(); + U32 sort_order = gSavedSettings.getU32("NearbyPeopleSortOrder"); + + if (item == "sort_by_recent_speakers") + return sort_order == E_SORT_BY_RECENT_SPEAKERS; + if (item == "sort_name") + return sort_order == E_SORT_BY_NAME; + if (item == "sort_distance") + return sort_order == E_SORT_BY_DISTANCE; + if (item == "sort_arrival") + return sort_order == E_SORT_BY_RECENT_ARRIVAL; + + return false; +} + +void LLPanelPeople::onRecentViewSortMenuItemClicked(const LLSD& userdata) +{ + std::string chosen_item = userdata.asString(); + + if (chosen_item == "sort_recent") + { + setSortOrder(mRecentList, E_SORT_BY_MOST_RECENT); + } + else if (chosen_item == "sort_name") + { + setSortOrder(mRecentList, E_SORT_BY_NAME); + } + else if (chosen_item == "view_icons") + { + mRecentList->toggleIcons(); + } +} + +bool LLPanelPeople::onFriendsViewSortMenuItemCheck(const LLSD& userdata) +{ + std::string item = userdata.asString(); + U32 sort_order = gSavedSettings.getU32("FriendsSortOrder"); + + if (item == "sort_name") + return sort_order == E_SORT_BY_NAME; + if (item == "sort_status") + return sort_order == E_SORT_BY_STATUS; + + return false; +} + +bool LLPanelPeople::onRecentViewSortMenuItemCheck(const LLSD& userdata) +{ + std::string item = userdata.asString(); + U32 sort_order = gSavedSettings.getU32("RecentPeopleSortOrder"); + + if (item == "sort_recent") + return sort_order == E_SORT_BY_MOST_RECENT; + if (item == "sort_name") + return sort_order == E_SORT_BY_NAME; + + return false; +} + +void LLPanelPeople::onMoreButtonClicked() +{ + // *TODO: not implemented yet +} + +void LLPanelPeople::onOpen(const LLSD& key) +{ + std::string tab_name = key["people_panel_tab_name"]; + if (!tab_name.empty()) + { + mTabContainer->selectTabByName(tab_name); + if(tab_name == BLOCKED_TAB_NAME) + { + LLPanel* blocked_tab = mTabContainer->getCurrentPanel()->findChild("panel_block_list_sidetray"); + if(blocked_tab) + { + blocked_tab->onOpen(key); + } + } + } +} + +bool LLPanelPeople::notifyChildren(const LLSD& info) +{ + if (info.has("task-panel-action") && info["task-panel-action"].asString() == "handle-tri-state") + { + LLSideTrayPanelContainer* container = dynamic_cast(getParent()); + if (!container) + { + LL_WARNS() << "Cannot find People panel container" << LL_ENDL; + return true; + } + + if (container->getCurrentPanelIndex() > 0) + { + // if not on the default panel, switch to it + container->onOpen(LLSD().with(LLSideTrayPanelContainer::PARAM_SUB_PANEL_NAME, getName())); + } + else + LLFloaterReg::hideInstance("people"); + + return true; // this notification is only supposed to be handled by task panels + } + + return LLPanel::notifyChildren(info); +} + +void LLPanelPeople::showAccordion(const std::string name, bool show) +{ + if(name.empty()) + { + LL_WARNS() << "No name provided" << LL_ENDL; + return; + } + + LLAccordionCtrlTab* tab = getChild(name); + tab->setVisible(show); + if(show) + { + // don't expand accordion if it was collapsed by user + if(!isAccordionCollapsedByUser(tab)) + { + // expand accordion + tab->changeOpenClose(false); + } + } +} + +void LLPanelPeople::showFriendsAccordionsIfNeeded() +{ + if(FRIENDS_TAB_NAME == getActiveTabName()) + { + // Expand and show accordions if needed, else - hide them + showAccordion("tab_online", mOnlineFriendList->filterHasMatches()); + showAccordion("tab_all", mAllFriendList->filterHasMatches()); + + // Rearrange accordions + LLAccordionCtrl* accordion = getChild("friends_accordion"); + accordion->arrange(); + + // *TODO: new no_matched_tabs_text attribute was implemented in accordion (EXT-7368). + // this code should be refactored to use it + // keep help text in a synchronization with accordions visibility. + updateFriendListHelpText(); + } +} + +void LLPanelPeople::onFriendListRefreshComplete(LLUICtrl*ctrl, const LLSD& param) +{ + if(ctrl == mOnlineFriendList) + { + showAccordion("tab_online", param.asInteger()); + } + else if(ctrl == mAllFriendList) + { + showAccordion("tab_all", param.asInteger()); + } +} + +void LLPanelPeople::setAccordionCollapsedByUser(LLUICtrl* acc_tab, bool collapsed) +{ + if(!acc_tab) + { + LL_WARNS() << "Invalid parameter" << LL_ENDL; + return; + } + + LLSD param = acc_tab->getValue(); + param[COLLAPSED_BY_USER] = collapsed; + acc_tab->setValue(param); +} + +void LLPanelPeople::setAccordionCollapsedByUser(const std::string& name, bool collapsed) +{ + setAccordionCollapsedByUser(getChild(name), collapsed); +} + +bool LLPanelPeople::isAccordionCollapsedByUser(LLUICtrl* acc_tab) +{ + if(!acc_tab) + { + LL_WARNS() << "Invalid parameter" << LL_ENDL; + return false; + } + + LLSD param = acc_tab->getValue(); + if(!param.has(COLLAPSED_BY_USER)) + { + return false; + } + return param[COLLAPSED_BY_USER].asBoolean(); +} + +bool LLPanelPeople::isAccordionCollapsedByUser(const std::string& name) +{ + return isAccordionCollapsedByUser(getChild(name)); +} + +bool LLPanelPeople::updateNearbyArrivalTime() +{ + std::vector positions; + std::vector uuids; + static LLCachedControl range(gSavedSettings, "NearMeRange"); + LLWorld::getInstance()->getAvatars(&uuids, &positions, gAgent.getPositionGlobal(), range); + LLRecentPeople::instance().updateAvatarsArrivalTime(uuids); + return LLApp::isExiting(); +} + + +// EOF diff --git a/indra/newview/llpanelpeople.h b/indra/newview/llpanelpeople.h index 18635c7a61..4edba2543e 100644 --- a/indra/newview/llpanelpeople.h +++ b/indra/newview/llpanelpeople.h @@ -1,157 +1,157 @@ -/** - * @file llpanelpeople.h - * @brief Side tray "People" panel - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELPEOPLE_H -#define LL_LLPANELPEOPLE_H - -#include - -#include "llcallingcard.h" // for avatar tracker -#include "llfloaterwebcontent.h" -#include "llvoiceclient.h" - -class LLAvatarList; -class LLAvatarName; -class LLFilterEditor; -class LLGroupList; -class LLMenuButton; -class LLTabContainer; -class LLNetMap; - -class LLPanelPeople - : public LLPanel - , public LLVoiceClientStatusObserver -{ - LOG_CLASS(LLPanelPeople); -public: - LLPanelPeople(); - virtual ~LLPanelPeople(); - - bool postBuild() override; - void onOpen(const LLSD& key) override; - bool notifyChildren(const LLSD& info) override; - // Implements LLVoiceClientStatusObserver::onChange() to enable call buttons - // when voice is available - void onChange(EStatusType status, const std::string &channelURI, bool proximal) override; - - // internals - class Updater; - - bool updateNearbyArrivalTime(); - -private: - - typedef enum e_sort_oder { - E_SORT_BY_NAME = 0, - E_SORT_BY_STATUS = 1, - E_SORT_BY_MOST_RECENT = 2, - E_SORT_BY_DISTANCE = 3, - E_SORT_BY_RECENT_SPEAKERS = 4, - E_SORT_BY_RECENT_ARRIVAL = 5 - } ESortOrder; - - void removePicker(); - - // methods indirectly called by the updaters - void updateFriendListHelpText(); - void updateFriendList(); - void updateNearbyList(); - void updateRecentList(); - - bool isItemsFreeOfFriends(const uuid_vec_t& uuids); - - void updateButtons(); - std::string getActiveTabName() const; - LLUUID getCurrentItemID() const; - void getCurrentItemIDs(uuid_vec_t& selected_uuids) const; - void setSortOrder(LLAvatarList* list, ESortOrder order, bool save = true); - - // UI callbacks - void onFilterEdit(const std::string& search_string); - void onGroupLimitInfo(); - void onTabSelected(const LLSD& param); - void onAddFriendButtonClicked(); - void onAddFriendWizButtonClicked(); - void onDeleteFriendButtonClicked(); - void onChatButtonClicked(); - void onGearButtonClicked(LLUICtrl* btn); - void onImButtonClicked(); - void onMoreButtonClicked(); - void onAvatarListDoubleClicked(LLUICtrl* ctrl); - void onAvatarListCommitted(LLAvatarList* list); - bool onGroupPlusButtonValidate(); - void onGroupMinusButtonClicked(); - void onGroupPlusMenuItemClicked(const LLSD& userdata); - - void onFriendsViewSortMenuItemClicked(const LLSD& userdata); - void onNearbyViewSortMenuItemClicked(const LLSD& userdata); - void onGroupsViewSortMenuItemClicked(const LLSD& userdata); - void onRecentViewSortMenuItemClicked(const LLSD& userdata); - - bool onFriendsViewSortMenuItemCheck(const LLSD& userdata); - bool onRecentViewSortMenuItemCheck(const LLSD& userdata); - bool onNearbyViewSortMenuItemCheck(const LLSD& userdata); - - // misc callbacks - static void onAvatarPicked(const uuid_vec_t& ids, const std::vector names); - - void onFriendsAccordionExpandedCollapsed(LLUICtrl* ctrl, const LLSD& param, LLAvatarList* avatar_list); - - void showAccordion(const std::string name, bool show); - - void showFriendsAccordionsIfNeeded(); - - void onFriendListRefreshComplete(LLUICtrl*ctrl, const LLSD& param); - - void setAccordionCollapsedByUser(LLUICtrl* acc_tab, bool collapsed); - void setAccordionCollapsedByUser(const std::string& name, bool collapsed); - bool isAccordionCollapsedByUser(LLUICtrl* acc_tab); - bool isAccordionCollapsedByUser(const std::string& name); - - LLTabContainer* mTabContainer; - LLAvatarList* mOnlineFriendList; - LLAvatarList* mAllFriendList; - LLAvatarList* mNearbyList; - LLAvatarList* mRecentList; - LLGroupList* mGroupList; - LLNetMap* mMiniMap; - - std::vector mSavedOriginalFilters; - std::vector mSavedFilters; - - Updater* mFriendListUpdater; - Updater* mNearbyListUpdater; - Updater* mRecentListUpdater; - Updater* mButtonsUpdater; - LLHandle< LLFloater > mPicker; - - boost::signals2::connection mNearbyFilterCommitConnection; - boost::signals2::connection mFriedsFilterCommitConnection; - boost::signals2::connection mGroupsFilterCommitConnection; - boost::signals2::connection mRecentFilterCommitConnection; -}; - -#endif //LL_LLPANELPEOPLE_H +/** + * @file llpanelpeople.h + * @brief Side tray "People" panel + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELPEOPLE_H +#define LL_LLPANELPEOPLE_H + +#include + +#include "llcallingcard.h" // for avatar tracker +#include "llfloaterwebcontent.h" +#include "llvoiceclient.h" + +class LLAvatarList; +class LLAvatarName; +class LLFilterEditor; +class LLGroupList; +class LLMenuButton; +class LLTabContainer; +class LLNetMap; + +class LLPanelPeople + : public LLPanel + , public LLVoiceClientStatusObserver +{ + LOG_CLASS(LLPanelPeople); +public: + LLPanelPeople(); + virtual ~LLPanelPeople(); + + bool postBuild() override; + void onOpen(const LLSD& key) override; + bool notifyChildren(const LLSD& info) override; + // Implements LLVoiceClientStatusObserver::onChange() to enable call buttons + // when voice is available + void onChange(EStatusType status, const std::string &channelURI, bool proximal) override; + + // internals + class Updater; + + bool updateNearbyArrivalTime(); + +private: + + typedef enum e_sort_oder { + E_SORT_BY_NAME = 0, + E_SORT_BY_STATUS = 1, + E_SORT_BY_MOST_RECENT = 2, + E_SORT_BY_DISTANCE = 3, + E_SORT_BY_RECENT_SPEAKERS = 4, + E_SORT_BY_RECENT_ARRIVAL = 5 + } ESortOrder; + + void removePicker(); + + // methods indirectly called by the updaters + void updateFriendListHelpText(); + void updateFriendList(); + void updateNearbyList(); + void updateRecentList(); + + bool isItemsFreeOfFriends(const uuid_vec_t& uuids); + + void updateButtons(); + std::string getActiveTabName() const; + LLUUID getCurrentItemID() const; + void getCurrentItemIDs(uuid_vec_t& selected_uuids) const; + void setSortOrder(LLAvatarList* list, ESortOrder order, bool save = true); + + // UI callbacks + void onFilterEdit(const std::string& search_string); + void onGroupLimitInfo(); + void onTabSelected(const LLSD& param); + void onAddFriendButtonClicked(); + void onAddFriendWizButtonClicked(); + void onDeleteFriendButtonClicked(); + void onChatButtonClicked(); + void onGearButtonClicked(LLUICtrl* btn); + void onImButtonClicked(); + void onMoreButtonClicked(); + void onAvatarListDoubleClicked(LLUICtrl* ctrl); + void onAvatarListCommitted(LLAvatarList* list); + bool onGroupPlusButtonValidate(); + void onGroupMinusButtonClicked(); + void onGroupPlusMenuItemClicked(const LLSD& userdata); + + void onFriendsViewSortMenuItemClicked(const LLSD& userdata); + void onNearbyViewSortMenuItemClicked(const LLSD& userdata); + void onGroupsViewSortMenuItemClicked(const LLSD& userdata); + void onRecentViewSortMenuItemClicked(const LLSD& userdata); + + bool onFriendsViewSortMenuItemCheck(const LLSD& userdata); + bool onRecentViewSortMenuItemCheck(const LLSD& userdata); + bool onNearbyViewSortMenuItemCheck(const LLSD& userdata); + + // misc callbacks + static void onAvatarPicked(const uuid_vec_t& ids, const std::vector names); + + void onFriendsAccordionExpandedCollapsed(LLUICtrl* ctrl, const LLSD& param, LLAvatarList* avatar_list); + + void showAccordion(const std::string name, bool show); + + void showFriendsAccordionsIfNeeded(); + + void onFriendListRefreshComplete(LLUICtrl*ctrl, const LLSD& param); + + void setAccordionCollapsedByUser(LLUICtrl* acc_tab, bool collapsed); + void setAccordionCollapsedByUser(const std::string& name, bool collapsed); + bool isAccordionCollapsedByUser(LLUICtrl* acc_tab); + bool isAccordionCollapsedByUser(const std::string& name); + + LLTabContainer* mTabContainer; + LLAvatarList* mOnlineFriendList; + LLAvatarList* mAllFriendList; + LLAvatarList* mNearbyList; + LLAvatarList* mRecentList; + LLGroupList* mGroupList; + LLNetMap* mMiniMap; + + std::vector mSavedOriginalFilters; + std::vector mSavedFilters; + + Updater* mFriendListUpdater; + Updater* mNearbyListUpdater; + Updater* mRecentListUpdater; + Updater* mButtonsUpdater; + LLHandle< LLFloater > mPicker; + + boost::signals2::connection mNearbyFilterCommitConnection; + boost::signals2::connection mFriedsFilterCommitConnection; + boost::signals2::connection mGroupsFilterCommitConnection; + boost::signals2::connection mRecentFilterCommitConnection; +}; + +#endif //LL_LLPANELPEOPLE_H diff --git a/indra/newview/llpanelpermissions.cpp b/indra/newview/llpanelpermissions.cpp index c916846d94..94603471f7 100644 --- a/indra/newview/llpanelpermissions.cpp +++ b/indra/newview/llpanelpermissions.cpp @@ -1,1351 +1,1351 @@ -/** - * @file llpanelpermissions.cpp - * @brief LLPanelPermissions class implementation - * This class represents the panel in the build view for - * viewing/editing object names, owners, permissions, etc. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelpermissions.h" - -// library includes -#include "lluuid.h" -#include "llpermissions.h" -#include "llcategory.h" -#include "llclickaction.h" -#include "llfocusmgr.h" -#include "llnotificationsutil.h" -#include "llstring.h" - -// project includes -#include "llviewerwindow.h" -#include "llresmgr.h" -#include "lltextbox.h" -#include "llbutton.h" -#include "llcheckboxctrl.h" -#include "llviewerobject.h" -#include "llselectmgr.h" -#include "llagent.h" -#include "llstatusbar.h" // for getBalance() -#include "lllineeditor.h" -#include "llcombobox.h" -#include "lluiconstants.h" -#include "lldbstrings.h" -#include "llfloatergroups.h" -#include "llfloaterreg.h" -#include "llavataractions.h" -#include "llavatariconctrl.h" -#include "llnamebox.h" -#include "llviewercontrol.h" -#include "lluictrlfactory.h" -#include "llspinctrl.h" -#include "roles_constants.h" -#include "llgroupactions.h" -#include "llgroupiconctrl.h" -#include "lltrans.h" -#include "llinventorymodel.h" - -#include "llavatarnamecache.h" -#include "llcachename.h" - - -U8 string_value_to_click_action(std::string p_value); -std::string click_action_to_string_value( U8 action); - -U8 string_value_to_click_action(std::string p_value) -{ - if (p_value == "Touch") - { - return CLICK_ACTION_TOUCH; - } - if (p_value == "Sit") - { - return CLICK_ACTION_SIT; - } - if (p_value == "Buy") - { - return CLICK_ACTION_BUY; - } - if (p_value == "Pay") - { - return CLICK_ACTION_PAY; - } - if (p_value == "Open") - { - return CLICK_ACTION_OPEN; - } - if (p_value == "Zoom") - { - return CLICK_ACTION_ZOOM; - } - if (p_value == "Ignore") - { - return CLICK_ACTION_IGNORE; - } - if (p_value == "None") - { - return CLICK_ACTION_DISABLED; - } - return CLICK_ACTION_TOUCH; -} - -std::string click_action_to_string_value( U8 action) -{ - switch (action) - { - case CLICK_ACTION_TOUCH: - default: - return "Touch"; - break; - case CLICK_ACTION_SIT: - return "Sit"; - break; - case CLICK_ACTION_BUY: - return "Buy"; - break; - case CLICK_ACTION_PAY: - return "Pay"; - break; - case CLICK_ACTION_OPEN: - return "Open"; - break; - case CLICK_ACTION_ZOOM: - return "Zoom"; - break; - case CLICK_ACTION_IGNORE: - return "Ignore"; - break; - case CLICK_ACTION_DISABLED: - return "None"; - break; - } -} - -///---------------------------------------------------------------------------- -/// Class llpanelpermissions -///---------------------------------------------------------------------------- - -// Default constructor -LLPanelPermissions::LLPanelPermissions() : - LLPanel() -{ - setMouseOpaque(false); -} - -bool LLPanelPermissions::postBuild() -{ - childSetCommitCallback("Object Name",LLPanelPermissions::onCommitName,this); - getChild("Object Name")->setPrevalidate(LLTextValidate::validateASCIIPrintableNoPipe); - childSetCommitCallback("Object Description",LLPanelPermissions::onCommitDesc,this); - getChild("Object Description")->setPrevalidate(LLTextValidate::validateASCIIPrintableNoPipe); - - - getChild("button set group")->setCommitCallback(boost::bind(&LLPanelPermissions::onClickGroup,this)); - - childSetCommitCallback("checkbox share with group",LLPanelPermissions::onCommitGroupShare,this); - - childSetAction("button deed",LLPanelPermissions::onClickDeedToGroup,this); - - childSetCommitCallback("checkbox allow everyone move",LLPanelPermissions::onCommitEveryoneMove,this); - - childSetCommitCallback("checkbox allow everyone copy",LLPanelPermissions::onCommitEveryoneCopy,this); - - childSetCommitCallback("checkbox for sale",LLPanelPermissions::onCommitSaleInfo,this); - - childSetCommitCallback("sale type",LLPanelPermissions::onCommitSaleType,this); - - childSetCommitCallback("Edit Cost", LLPanelPermissions::onCommitSaleInfo, this); - - childSetCommitCallback("checkbox next owner can modify",LLPanelPermissions::onCommitNextOwnerModify,this); - childSetCommitCallback("checkbox next owner can copy",LLPanelPermissions::onCommitNextOwnerCopy,this); - childSetCommitCallback("checkbox next owner can transfer",LLPanelPermissions::onCommitNextOwnerTransfer,this); - childSetCommitCallback("clickaction",LLPanelPermissions::onCommitClickAction,this); - childSetCommitCallback("search_check",LLPanelPermissions::onCommitIncludeInSearch,this); - - mLabelGroupName = getChild("Group Name Proxy"); - mLabelOwnerName = getChild("Owner Name"); - mLabelCreatorName = getChild("Creator Name"); - - return true; -} - - -LLPanelPermissions::~LLPanelPermissions() -{ - if (mOwnerCacheConnection.connected()) - { - mOwnerCacheConnection.disconnect(); - } - if (mCreatorCacheConnection.connected()) - { - mCreatorCacheConnection.disconnect(); - } - // base class will take care of everything -} - - -void LLPanelPermissions::disableAll() -{ - getChildView("perm_modify")->setEnabled(false); - getChild("perm_modify")->setValue(LLStringUtil::null); - - getChildView("pathfinding_attributes_value")->setEnabled(false); - getChild("pathfinding_attributes_value")->setValue(LLStringUtil::null); - - getChildView("Creator:")->setEnabled(false); - getChild("Creator Icon")->setVisible(false); - mLabelCreatorName->setValue(LLStringUtil::null); - mLabelCreatorName->setEnabled(false); - - getChildView("Owner:")->setEnabled(false); - getChild("Owner Icon")->setVisible(false); - getChild("Owner Group Icon")->setVisible(false); - mLabelOwnerName->setValue(LLStringUtil::null); - mLabelOwnerName->setEnabled(false); - - getChildView("Group:")->setEnabled(false); - getChild("Group Name Proxy")->setValue(LLStringUtil::null); - getChildView("Group Name Proxy")->setEnabled(false); - getChildView("button set group")->setEnabled(false); - - getChild("Object Name")->setValue(LLStringUtil::null); - getChildView("Object Name")->setEnabled(false); - getChildView("Name:")->setEnabled(false); - getChild("Group Name")->setValue(LLStringUtil::null); - getChildView("Group Name")->setEnabled(false); - getChildView("Description:")->setEnabled(false); - getChild("Object Description")->setValue(LLStringUtil::null); - getChildView("Object Description")->setEnabled(false); - - getChild("checkbox share with group")->setValue(false); - getChildView("checkbox share with group")->setEnabled(false); - getChildView("button deed")->setEnabled(false); - - getChild("checkbox allow everyone move")->setValue(false); - getChildView("checkbox allow everyone move")->setEnabled(false); - getChild("checkbox allow everyone copy")->setValue(false); - getChildView("checkbox allow everyone copy")->setEnabled(false); - - //Next owner can: - getChildView("Next owner can:")->setEnabled(false); - getChild("checkbox next owner can modify")->setValue(false); - getChildView("checkbox next owner can modify")->setEnabled(false); - getChild("checkbox next owner can copy")->setValue(false); - getChildView("checkbox next owner can copy")->setEnabled(false); - getChild("checkbox next owner can transfer")->setValue(false); - getChildView("checkbox next owner can transfer")->setEnabled(false); - - //checkbox for sale - getChild("checkbox for sale")->setValue(false); - getChildView("checkbox for sale")->setEnabled(false); - - //checkbox include in search - getChild("search_check")->setValue(false); - getChildView("search_check")->setEnabled(false); - - LLComboBox* combo_sale_type = getChild("sale type"); - combo_sale_type->setValue(LLSaleInfo::FS_COPY); - combo_sale_type->setEnabled(false); - - getChildView("Cost")->setEnabled(false); - getChild("Cost")->setValue(getString("Cost Default")); - getChild("Edit Cost")->setValue(LLStringUtil::null); - getChildView("Edit Cost")->setEnabled(false); - - getChildView("label click action")->setEnabled(false); - LLComboBox* combo_click_action = getChild("clickaction"); - if (combo_click_action) - { - combo_click_action->setEnabled(false); - combo_click_action->clear(); - } - getChildView("B:")->setVisible(false); - getChildView("O:")->setVisible(false); - getChildView("G:")->setVisible(false); - getChildView("E:")->setVisible(false); - getChildView("N:")->setVisible(false); - getChildView("F:")->setVisible(false); -} - -void LLPanelPermissions::refresh() -{ - LLButton* BtnDeedToGroup = getChild("button deed"); - if(BtnDeedToGroup) - { - std::string deedText; - if (gWarningSettings.getBOOL("DeedObject")) - { - deedText = getString("text deed continued"); - } - else - { - deedText = getString("text deed"); - } - BtnDeedToGroup->setLabelSelected(deedText); - BtnDeedToGroup->setLabelUnselected(deedText); - } - bool root_selected = true; - LLSelectNode* nodep = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(); - S32 object_count = LLSelectMgr::getInstance()->getSelection()->getRootObjectCount(); - if(!nodep || 0 == object_count) - { - nodep = LLSelectMgr::getInstance()->getSelection()->getFirstNode(); - object_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount(); - root_selected = false; - } - - //bool attachment_selected = LLSelectMgr::getInstance()->getSelection()->isAttachment(); - //attachment_selected = false; - LLViewerObject* objectp = NULL; - if(nodep) objectp = nodep->getObject(); - if(!nodep || !objectp)// || attachment_selected) - { - // ...nothing selected - disableAll(); - return; - } - - // figure out a few variables - const bool is_one_object = (object_count == 1); - - // BUG: fails if a root and non-root are both single-selected. - bool is_perm_modify = (LLSelectMgr::getInstance()->getSelection()->getFirstRootNode() - && LLSelectMgr::getInstance()->selectGetRootsModify()) - || LLSelectMgr::getInstance()->selectGetModify(); - bool is_nonpermanent_enforced = (LLSelectMgr::getInstance()->getSelection()->getFirstRootNode() - && LLSelectMgr::getInstance()->selectGetRootsNonPermanentEnforced()) - || LLSelectMgr::getInstance()->selectGetNonPermanentEnforced(); - const LLFocusableElement* keyboard_focus_view = gFocusMgr.getKeyboardFocus(); - - S32 string_index = 0; - std::string MODIFY_INFO_STRINGS[] = - { - getString("text modify info 1"), - getString("text modify info 2"), - getString("text modify info 3"), - getString("text modify info 4"), - getString("text modify info 5"), - getString("text modify info 6") - }; - if (!is_perm_modify) - { - string_index += 2; - } - else if (!is_nonpermanent_enforced) - { - string_index += 4; - } - if (!is_one_object) - { - ++string_index; - } - getChildView("perm_modify")->setEnabled(true); - getChild("perm_modify")->setValue(MODIFY_INFO_STRINGS[string_index]); - - std::string pfAttrName; - - if ((LLSelectMgr::getInstance()->getSelection()->getFirstRootNode() - && LLSelectMgr::getInstance()->selectGetRootsNonPathfinding()) - || LLSelectMgr::getInstance()->selectGetNonPathfinding()) - { - pfAttrName = "Pathfinding_Object_Attr_None"; - } - else if ((LLSelectMgr::getInstance()->getSelection()->getFirstRootNode() - && LLSelectMgr::getInstance()->selectGetRootsPermanent()) - || LLSelectMgr::getInstance()->selectGetPermanent()) - { - pfAttrName = "Pathfinding_Object_Attr_Permanent"; - } - else if ((LLSelectMgr::getInstance()->getSelection()->getFirstRootNode() - && LLSelectMgr::getInstance()->selectGetRootsCharacter()) - || LLSelectMgr::getInstance()->selectGetCharacter()) - { - pfAttrName = "Pathfinding_Object_Attr_Character"; - } - else - { - pfAttrName = "Pathfinding_Object_Attr_MultiSelect"; - } - - getChildView("pathfinding_attributes_value")->setEnabled(true); - getChild("pathfinding_attributes_value")->setValue(LLTrans::getString(pfAttrName)); - - // Update creator text field - getChildView("Creator:")->setEnabled(true); - std::string creator_app_link; - LLSelectMgr::getInstance()->selectGetCreator(mCreatorID, creator_app_link); - - // Style for creator and owner links (both group and agent) - LLStyle::Params style_params; - LLColor4 link_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); - style_params.color = link_color; - style_params.readonly_color = link_color; - style_params.is_link = true; // link will be added later - const LLFontGL* fontp = mLabelCreatorName->getFont(); - style_params.font.name = LLFontGL::nameFromFont(fontp); - style_params.font.size = LLFontGL::sizeFromFont(fontp); - style_params.font.style = "UNDERLINE"; - - LLAvatarName av_name; - style_params.link_href = creator_app_link; - if (LLAvatarNameCache::get(mCreatorID, &av_name)) - { - updateCreatorName(mCreatorID, av_name, style_params); - } - else - { - if (mCreatorCacheConnection.connected()) - { - mCreatorCacheConnection.disconnect(); - } - mLabelCreatorName->setText(LLTrans::getString("None")); - mCreatorCacheConnection = LLAvatarNameCache::get(mCreatorID, boost::bind(&LLPanelPermissions::updateCreatorName, this, _1, _2, style_params)); - } - getChild("Creator Icon")->setValue(mCreatorID); - getChild("Creator Icon")->setVisible(true); - mLabelCreatorName->setEnabled(true); - - // Update owner text field - getChildView("Owner:")->setEnabled(true); - - std::string owner_app_link; - const bool owners_identical = LLSelectMgr::getInstance()->selectGetOwner(mOwnerID, owner_app_link); - - - if (LLSelectMgr::getInstance()->selectIsGroupOwned()) - { - // Group owned already displayed by selectGetOwner - LLGroupMgrGroupData* group_data = LLGroupMgr::getInstance()->getGroupData(mOwnerID); - if (group_data && group_data->isGroupPropertiesDataComplete()) - { - style_params.link_href = owner_app_link; - mLabelOwnerName->setText(group_data->mName, style_params); - getChild("Owner Group Icon")->setIconId(group_data->mInsigniaID); - getChild("Owner Group Icon")->setVisible(true); - getChild("Owner Icon")->setVisible(false); - } - else - { - // Triggers refresh - LLGroupMgr::getInstance()->sendGroupPropertiesRequest(mOwnerID); - } - } - else - { - LLUUID owner_id = mOwnerID; - if (owner_id.isNull()) - { - // Display last owner if public - std::string last_owner_app_link; - LLSelectMgr::getInstance()->selectGetLastOwner(mLastOwnerID, last_owner_app_link); - - // It should never happen that the last owner is null and the owner - // is null, but it seems to be a bug in the simulator right now. JC - if (!mLastOwnerID.isNull() && !last_owner_app_link.empty()) - { - owner_app_link.append(", last "); - owner_app_link.append(last_owner_app_link); - } - owner_id = mLastOwnerID; - } - - style_params.link_href = owner_app_link; - if (LLAvatarNameCache::get(owner_id, &av_name)) - { - updateOwnerName(owner_id, av_name, style_params); - } - else - { - if (mOwnerCacheConnection.connected()) - { - mOwnerCacheConnection.disconnect(); - } - mLabelOwnerName->setText(LLTrans::getString("None")); - mOwnerCacheConnection = LLAvatarNameCache::get(owner_id, boost::bind(&LLPanelPermissions::updateOwnerName, this, _1, _2, style_params)); - } - - getChild("Owner Icon")->setValue(owner_id); - getChild("Owner Icon")->setVisible(true); - getChild("Owner Group Icon")->setVisible(false); - } - mLabelOwnerName->setEnabled(true); - - // update group text field - getChildView("Group:")->setEnabled(true); - getChild("Group Name")->setValue(LLStringUtil::null); - LLUUID group_id; - bool groups_identical = LLSelectMgr::getInstance()->selectGetGroup(group_id); - if (groups_identical) - { - if (mLabelGroupName) - { - mLabelGroupName->setNameID(group_id,true); - mLabelGroupName->setEnabled(true); - } - } - else - { - if (mLabelGroupName) - { - mLabelGroupName->setNameID(LLUUID::null, true); - mLabelGroupName->refresh(LLUUID::null, std::string(), true); - mLabelGroupName->setEnabled(false); - } - } - - getChildView("button set group")->setEnabled(root_selected && owners_identical && (mOwnerID == gAgent.getID()) && is_nonpermanent_enforced); - - getChildView("Name:")->setEnabled(true); - LLLineEditor* LineEditorObjectName = getChild("Object Name"); - getChildView("Description:")->setEnabled(true); - LLLineEditor* LineEditorObjectDesc = getChild("Object Description"); - - if (is_one_object) - { - if (keyboard_focus_view != LineEditorObjectName) - { - getChild("Object Name")->setValue(nodep->mName); - } - - if (LineEditorObjectDesc) - { - if (keyboard_focus_view != LineEditorObjectDesc) - { - LineEditorObjectDesc->setText(nodep->mDescription); - } - } - } - else - { - getChild("Object Name")->setValue(LLStringUtil::null); - LineEditorObjectDesc->setText(LLStringUtil::null); - } - - // figure out the contents of the name, description, & category - bool edit_name_desc = false; - if (is_one_object && objectp->permModify() && !objectp->isPermanentEnforced()) - { - edit_name_desc = true; - } - if (edit_name_desc) - { - getChildView("Object Name")->setEnabled(true); - getChildView("Object Description")->setEnabled(true); - } - else - { - getChildView("Object Name")->setEnabled(false); - getChildView("Object Description")->setEnabled(false); - } - - S32 total_sale_price = 0; - S32 individual_sale_price = 0; - bool is_for_sale_mixed = false; - bool is_sale_price_mixed = false; - U32 num_for_sale = false; - LLSelectMgr::getInstance()->selectGetAggregateSaleInfo(num_for_sale, - is_for_sale_mixed, - is_sale_price_mixed, - total_sale_price, - individual_sale_price); - - const bool self_owned = (gAgent.getID() == mOwnerID); - const bool group_owned = LLSelectMgr::getInstance()->selectIsGroupOwned() ; - const bool public_owned = (mOwnerID.isNull() && !LLSelectMgr::getInstance()->selectIsGroupOwned()); - const bool can_transfer = LLSelectMgr::getInstance()->selectGetRootsTransfer(); - const bool can_copy = LLSelectMgr::getInstance()->selectGetRootsCopy(); - - if (!owners_identical) - { - getChildView("Cost")->setEnabled(false); - getChild("Edit Cost")->setValue(LLStringUtil::null); - getChildView("Edit Cost")->setEnabled(false); - } - // You own these objects. - else if (self_owned || (group_owned && gAgent.hasPowerInGroup(group_id,GP_OBJECT_SET_SALE))) - { - // If there are multiple items for sale then set text to PRICE PER UNIT. - if (num_for_sale > 1) - { - getChild("Cost")->setValue(getString("Cost Per Unit")); - } - else - { - getChild("Cost")->setValue(getString("Cost Default")); - } - - LLSpinCtrl *edit_price = getChild("Edit Cost"); - if (!edit_price->hasFocus()) - { - // If the sale price is mixed then set the cost to MIXED, otherwise - // set to the actual cost. - if ((num_for_sale > 0) && is_for_sale_mixed) - { - edit_price->setTentative(true); - } - else if ((num_for_sale > 0) && is_sale_price_mixed) - { - edit_price->setTentative(true); - } - else - { - edit_price->setValue(individual_sale_price); - } - } - // The edit fields are only enabled if you can sell this object - // and the sale price is not mixed. - bool enable_edit = (num_for_sale && can_transfer) ? !is_for_sale_mixed : false; - getChildView("Cost")->setEnabled(enable_edit); - getChildView("Edit Cost")->setEnabled(enable_edit); - } - // Someone, not you, owns these objects. - else if (!public_owned) - { - getChildView("Cost")->setEnabled(false); - getChildView("Edit Cost")->setEnabled(false); - - // Don't show a price if none of the items are for sale. - if (num_for_sale) - getChild("Edit Cost")->setValue(llformat("%d",total_sale_price)); - else - getChild("Edit Cost")->setValue(LLStringUtil::null); - - // If multiple items are for sale, set text to TOTAL PRICE. - if (num_for_sale > 1) - getChild("Cost")->setValue(getString("Cost Total")); - else - getChild("Cost")->setValue(getString("Cost Default")); - } - // This is a public object. - else - { - getChildView("Cost")->setEnabled(false); - getChild("Cost")->setValue(getString("Cost Default")); - - getChild("Edit Cost")->setValue(LLStringUtil::null); - getChildView("Edit Cost")->setEnabled(false); - } - - // Enable and disable the permissions checkboxes - // based on who owns the object. - // TODO: Creator permissions - - U32 base_mask_on = 0; - U32 base_mask_off = 0; - U32 owner_mask_off = 0; - U32 owner_mask_on = 0; - U32 group_mask_on = 0; - U32 group_mask_off = 0; - U32 everyone_mask_on = 0; - U32 everyone_mask_off = 0; - U32 next_owner_mask_on = 0; - U32 next_owner_mask_off = 0; - - bool valid_base_perms = LLSelectMgr::getInstance()->selectGetPerm(PERM_BASE, - &base_mask_on, - &base_mask_off); - //bool valid_owner_perms =// - LLSelectMgr::getInstance()->selectGetPerm(PERM_OWNER, - &owner_mask_on, - &owner_mask_off); - bool valid_group_perms = LLSelectMgr::getInstance()->selectGetPerm(PERM_GROUP, - &group_mask_on, - &group_mask_off); - - bool valid_everyone_perms = LLSelectMgr::getInstance()->selectGetPerm(PERM_EVERYONE, - &everyone_mask_on, - &everyone_mask_off); - - bool valid_next_perms = LLSelectMgr::getInstance()->selectGetPerm(PERM_NEXT_OWNER, - &next_owner_mask_on, - &next_owner_mask_off); - - - if (gSavedSettings.getBOOL("DebugPermissions") ) - { - if (valid_base_perms) - { - getChild("B:")->setValue("B: " + mask_to_string(base_mask_on)); - getChildView("B:")->setVisible(true); - getChild("O:")->setValue("O: " + mask_to_string(owner_mask_on)); - getChildView("O:")->setVisible(true); - getChild("G:")->setValue("G: " + mask_to_string(group_mask_on)); - getChildView("G:")->setVisible(true); - getChild("E:")->setValue("E: " + mask_to_string(everyone_mask_on)); - getChildView("E:")->setVisible(true); - getChild("N:")->setValue("N: " + mask_to_string(next_owner_mask_on)); - getChildView("N:")->setVisible(true); - } - else if(!root_selected) - { - if(object_count == 1) - { - LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode(); - if (node && node->mValid) - { - getChild("B:")->setValue("B: " + mask_to_string( node->mPermissions->getMaskBase())); - getChildView("B:")->setVisible(true); - getChild("O:")->setValue("O: " + mask_to_string(node->mPermissions->getMaskOwner())); - getChildView("O:")->setVisible(true); - getChild("G:")->setValue("G: " + mask_to_string(node->mPermissions->getMaskGroup())); - getChildView("G:")->setVisible(true); - getChild("E:")->setValue("E: " + mask_to_string(node->mPermissions->getMaskEveryone())); - getChildView("E:")->setVisible(true); - getChild("N:")->setValue("N: " + mask_to_string(node->mPermissions->getMaskNextOwner())); - getChildView("N:")->setVisible(true); - } - } - } - else - { - getChildView("B:")->setVisible(false); - getChildView("O:")->setVisible(false); - getChildView("G:")->setVisible(false); - getChildView("E:")->setVisible(false); - getChildView("N:")->setVisible(false); - } - - U32 flag_mask = 0x0; - if (objectp->permMove()) flag_mask |= PERM_MOVE; - if (objectp->permModify()) flag_mask |= PERM_MODIFY; - if (objectp->permCopy()) flag_mask |= PERM_COPY; - if (objectp->permTransfer()) flag_mask |= PERM_TRANSFER; - - getChild("F:")->setValue("F:" + mask_to_string(flag_mask)); - getChildView("F:")->setVisible( true); - } - else - { - getChildView("B:")->setVisible( false); - getChildView("O:")->setVisible( false); - getChildView("G:")->setVisible( false); - getChildView("E:")->setVisible( false); - getChildView("N:")->setVisible( false); - getChildView("F:")->setVisible( false); - } - - bool has_change_perm_ability = false; - bool has_change_sale_ability = false; - - if (valid_base_perms && is_nonpermanent_enforced && - (self_owned || (group_owned && gAgent.hasPowerInGroup(group_id, GP_OBJECT_MANIPULATE)))) - { - has_change_perm_ability = true; - } - if (valid_base_perms && is_nonpermanent_enforced && - (self_owned || (group_owned && gAgent.hasPowerInGroup(group_id, GP_OBJECT_SET_SALE)))) - { - has_change_sale_ability = true; - } - - if (!has_change_perm_ability && !has_change_sale_ability && !root_selected) - { - // ...must select root to choose permissions - getChild("perm_modify")->setValue(getString("text modify warning")); - } - - if (has_change_perm_ability) - { - getChildView("checkbox share with group")->setEnabled(true); - getChildView("checkbox allow everyone move")->setEnabled(owner_mask_on & PERM_MOVE); - getChildView("checkbox allow everyone copy")->setEnabled(owner_mask_on & PERM_COPY && owner_mask_on & PERM_TRANSFER); - } - else - { - getChildView("checkbox share with group")->setEnabled(false); - getChildView("checkbox allow everyone move")->setEnabled(false); - getChildView("checkbox allow everyone copy")->setEnabled(false); - } - - if (has_change_sale_ability && (owner_mask_on & PERM_TRANSFER)) - { - getChildView("checkbox for sale")->setEnabled(can_transfer || (!can_transfer && num_for_sale)); - // Set the checkbox to tentative if the prices of each object selected - // are not the same. - getChild("checkbox for sale")->setTentative( is_for_sale_mixed); - getChildView("sale type")->setEnabled(num_for_sale && can_transfer && !is_sale_price_mixed); - - getChildView("Next owner can:")->setEnabled(true); - getChildView("checkbox next owner can modify")->setEnabled(base_mask_on & PERM_MODIFY); - getChildView("checkbox next owner can copy")->setEnabled(base_mask_on & PERM_COPY); - getChildView("checkbox next owner can transfer")->setEnabled(next_owner_mask_on & PERM_COPY); - } - else - { - getChildView("checkbox for sale")->setEnabled(false); - getChildView("sale type")->setEnabled(false); - - getChildView("Next owner can:")->setEnabled(false); - getChildView("checkbox next owner can modify")->setEnabled(false); - getChildView("checkbox next owner can copy")->setEnabled(false); - getChildView("checkbox next owner can transfer")->setEnabled(false); - } - - if (valid_group_perms) - { - if ((group_mask_on & PERM_COPY) && (group_mask_on & PERM_MODIFY) && (group_mask_on & PERM_MOVE)) - { - getChild("checkbox share with group")->setValue(true); - getChild("checkbox share with group")->setTentative( false); - getChildView("button deed")->setEnabled(gAgent.hasPowerInGroup(group_id, GP_OBJECT_DEED) && (owner_mask_on & PERM_TRANSFER) && !group_owned && can_transfer); - } - else if ((group_mask_off & PERM_COPY) && (group_mask_off & PERM_MODIFY) && (group_mask_off & PERM_MOVE)) - { - getChild("checkbox share with group")->setValue(false); - getChild("checkbox share with group")->setTentative( false); - getChildView("button deed")->setEnabled(false); - } - else - { - getChild("checkbox share with group")->setValue(true); - getChild("checkbox share with group")->setTentative(!has_change_perm_ability); - getChildView("button deed")->setEnabled(gAgent.hasPowerInGroup(group_id, GP_OBJECT_DEED) && (group_mask_on & PERM_MOVE) && (owner_mask_on & PERM_TRANSFER) && !group_owned && can_transfer); - } - } - - if (valid_everyone_perms) - { - // Move - if (everyone_mask_on & PERM_MOVE) - { - getChild("checkbox allow everyone move")->setValue(true); - getChild("checkbox allow everyone move")->setTentative( false); - } - else if (everyone_mask_off & PERM_MOVE) - { - getChild("checkbox allow everyone move")->setValue(false); - getChild("checkbox allow everyone move")->setTentative( false); - } - else - { - getChild("checkbox allow everyone move")->setValue(true); - getChild("checkbox allow everyone move")->setTentative( true); - } - - // Copy == everyone can't copy - if (everyone_mask_on & PERM_COPY) - { - getChild("checkbox allow everyone copy")->setValue(true); - getChild("checkbox allow everyone copy")->setTentative( !can_copy || !can_transfer); - } - else if (everyone_mask_off & PERM_COPY) - { - getChild("checkbox allow everyone copy")->setValue(false); - getChild("checkbox allow everyone copy")->setTentative( false); - } - else - { - getChild("checkbox allow everyone copy")->setValue(true); - getChild("checkbox allow everyone copy")->setTentative( true); - } - } - - if (valid_next_perms) - { - // Modify == next owner canot modify - if (next_owner_mask_on & PERM_MODIFY) - { - getChild("checkbox next owner can modify")->setValue(true); - getChild("checkbox next owner can modify")->setTentative( false); - } - else if (next_owner_mask_off & PERM_MODIFY) - { - getChild("checkbox next owner can modify")->setValue(false); - getChild("checkbox next owner can modify")->setTentative( false); - } - else - { - getChild("checkbox next owner can modify")->setValue(true); - getChild("checkbox next owner can modify")->setTentative( true); - } - - // Copy == next owner cannot copy - if (next_owner_mask_on & PERM_COPY) - { - getChild("checkbox next owner can copy")->setValue(true); - getChild("checkbox next owner can copy")->setTentative( !can_copy); - } - else if (next_owner_mask_off & PERM_COPY) - { - getChild("checkbox next owner can copy")->setValue(false); - getChild("checkbox next owner can copy")->setTentative( false); - } - else - { - getChild("checkbox next owner can copy")->setValue(true); - getChild("checkbox next owner can copy")->setTentative( true); - } - - // Transfer == next owner cannot transfer - if (next_owner_mask_on & PERM_TRANSFER) - { - getChild("checkbox next owner can transfer")->setValue(true); - getChild("checkbox next owner can transfer")->setTentative( !can_transfer); - } - else if (next_owner_mask_off & PERM_TRANSFER) - { - getChild("checkbox next owner can transfer")->setValue(false); - getChild("checkbox next owner can transfer")->setTentative( false); - } - else - { - getChild("checkbox next owner can transfer")->setValue(true); - getChild("checkbox next owner can transfer")->setTentative( true); - } - } - - // reflect sale information - LLSaleInfo sale_info; - bool valid_sale_info = LLSelectMgr::getInstance()->selectGetSaleInfo(sale_info); - LLSaleInfo::EForSale sale_type = sale_info.getSaleType(); - - LLComboBox* combo_sale_type = getChild("sale type"); - if (valid_sale_info) - { - combo_sale_type->setValue( sale_type == LLSaleInfo::FS_NOT ? LLSaleInfo::FS_COPY : sale_type); - combo_sale_type->setTentative( false); // unfortunately this doesn't do anything at the moment. - } - else - { - // default option is sell copy, determined to be safest - combo_sale_type->setValue( LLSaleInfo::FS_COPY); - combo_sale_type->setTentative( true); // unfortunately this doesn't do anything at the moment. - } - - getChild("checkbox for sale")->setValue((num_for_sale != 0)); - - // HACK: There are some old objects in world that are set for sale, - // but are no-transfer. We need to let users turn for-sale off, but only - // if for-sale is set. - bool cannot_actually_sell = !can_transfer || (!can_copy && sale_type == LLSaleInfo::FS_COPY); - if (cannot_actually_sell) - { - if (num_for_sale && has_change_sale_ability) - { - getChildView("checkbox for sale")->setEnabled(true); - } - } - - // Check search status of objects - const bool all_volume = LLSelectMgr::getInstance()->selectionAllPCode( LL_PCODE_VOLUME ); - bool include_in_search; - const bool all_include_in_search = LLSelectMgr::getInstance()->selectionGetIncludeInSearch(&include_in_search); - getChildView("search_check")->setEnabled(has_change_sale_ability && all_volume); - getChild("search_check")->setValue(include_in_search); - getChild("search_check")->setTentative( !all_include_in_search); - - // Click action (touch, sit, buy, pay, open, play, open media, zoom, ignore) - U8 click_action = 0; - if (LLSelectMgr::getInstance()->selectionGetClickAction(&click_action)) - { - LLComboBox* combo_click_action = getChild("clickaction"); - if (combo_click_action) - { - const std::string combo_value = click_action_to_string_value(click_action); - combo_click_action->setValue(LLSD(combo_value)); - } - } - - if (LLSelectMgr::getInstance()->getSelection()->isAttachment()) - { - getChildView("checkbox for sale")->setEnabled(false); - getChildView("Edit Cost")->setEnabled(false); - getChild("sale type")->setEnabled(false); - } - - getChildView("label click action")->setEnabled(is_perm_modify && is_nonpermanent_enforced && all_volume); - getChildView("clickaction")->setEnabled(is_perm_modify && is_nonpermanent_enforced && all_volume); -} - -// Shorten name if it doesn't fit into max_pixels of two lines -void shorten_name(std::string &name, const LLStyle::Params& style_params, S32 max_pixels) -{ - const LLFontGL* font = style_params.font(); - - LLWString wline = utf8str_to_wstring(name); - // panel supports two lines long names - S32 segment_length = font->maxDrawableChars(wline.c_str(), max_pixels, wline.length(), LLFontGL::WORD_BOUNDARY_IF_POSSIBLE); - if (segment_length == wline.length()) - { - // no work needed - return; - } - - S32 first_line_length = segment_length; - segment_length = font->maxDrawableChars(wline.substr(first_line_length).c_str(), max_pixels, wline.length(), LLFontGL::ANYWHERE); - if (segment_length + first_line_length == wline.length()) - { - // no work needed - return; - } - - // name does not fit, cut it, add ... - const LLWString dots_pad(utf8str_to_wstring(std::string("...."))); - S32 elipses_width = font->getWidthF32(dots_pad.c_str()); - segment_length = font->maxDrawableChars(wline.substr(first_line_length).c_str(), max_pixels - elipses_width, wline.length(), LLFontGL::ANYWHERE); - - name = name.substr(0, segment_length + first_line_length) + std::string("..."); -} - -void LLPanelPermissions::updateOwnerName(const LLUUID& owner_id, const LLAvatarName& owner_name, const LLStyle::Params& style_params) -{ - if (mOwnerCacheConnection.connected()) - { - mOwnerCacheConnection.disconnect(); - } - std::string name = owner_name.getCompleteName(); - shorten_name(name, style_params, mLabelOwnerName->getLocalRect().getWidth()); - mLabelOwnerName->setText(name, style_params); -} - -void LLPanelPermissions::updateCreatorName(const LLUUID& creator_id, const LLAvatarName& creator_name, const LLStyle::Params& style_params) -{ - if (mCreatorCacheConnection.connected()) - { - mCreatorCacheConnection.disconnect(); - } - std::string name = creator_name.getCompleteName(); - shorten_name(name, style_params, mLabelCreatorName->getLocalRect().getWidth()); - mLabelCreatorName->setText(name, style_params); -} - -// static -void LLPanelPermissions::onClickClaim(void*) -{ - // try to claim ownership - LLSelectMgr::getInstance()->sendOwner(gAgent.getID(), gAgent.getGroupID()); -} - -// static -void LLPanelPermissions::onClickRelease(void*) -{ - // try to release ownership - LLSelectMgr::getInstance()->sendOwner(LLUUID::null, LLUUID::null); -} - -void LLPanelPermissions::onClickGroup() -{ - LLUUID owner_id; - std::string name; - bool owners_identical = LLSelectMgr::getInstance()->selectGetOwner(owner_id, name); - LLFloater* parent_floater = gFloaterView->getParentFloater(this); - - if(owners_identical && (owner_id == gAgent.getID())) - { - LLFloaterGroupPicker* fg = LLFloaterReg::showTypedInstance("group_picker", LLSD(gAgent.getID())); - if (fg) - { - fg->setSelectGroupCallback( boost::bind(&LLPanelPermissions::cbGroupID, this, _1) ); - - if (parent_floater) - { - LLRect new_rect = gFloaterView->findNeighboringPosition(parent_floater, fg); - fg->setOrigin(new_rect.mLeft, new_rect.mBottom); - parent_floater->addDependentFloater(fg); - } - } - } -} - -void LLPanelPermissions::cbGroupID(LLUUID group_id) -{ - if(mLabelGroupName) - { - mLabelGroupName->setNameID(group_id, true); - } - LLSelectMgr::getInstance()->sendGroup(group_id); -} - -bool callback_deed_to_group(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (0 == option) - { - LLUUID group_id; - bool groups_identical = LLSelectMgr::getInstance()->selectGetGroup(group_id); - if(group_id.notNull() && groups_identical && (gAgent.hasPowerInGroup(group_id, GP_OBJECT_DEED))) - { - LLSelectMgr::getInstance()->sendOwner(LLUUID::null, group_id, false); - } - } - return false; -} - -void LLPanelPermissions::onClickDeedToGroup(void* data) -{ - LLNotificationsUtil::add( "DeedObjectToGroup", LLSD(), LLSD(), callback_deed_to_group); -} - -///---------------------------------------------------------------------------- -/// Permissions checkboxes -///---------------------------------------------------------------------------- - -// static -void LLPanelPermissions::onCommitPerm(LLUICtrl *ctrl, void *data, U8 field, U32 perm) -{ - LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getFirstRootObject(); - if(!object) return; - - // Checkbox will have toggled itself - // LLPanelPermissions* self = (LLPanelPermissions*)data; - LLCheckBoxCtrl *check = (LLCheckBoxCtrl *)ctrl; - bool new_state = check->get(); - - LLSelectMgr::getInstance()->selectionSetObjectPermissions(field, new_state, perm); -} - -// static -void LLPanelPermissions::onCommitGroupShare(LLUICtrl *ctrl, void *data) -{ - onCommitPerm(ctrl, data, PERM_GROUP, PERM_MODIFY | PERM_MOVE | PERM_COPY); -} - -// static -void LLPanelPermissions::onCommitEveryoneMove(LLUICtrl *ctrl, void *data) -{ - onCommitPerm(ctrl, data, PERM_EVERYONE, PERM_MOVE); -} - - -// static -void LLPanelPermissions::onCommitEveryoneCopy(LLUICtrl *ctrl, void *data) -{ - onCommitPerm(ctrl, data, PERM_EVERYONE, PERM_COPY); -} - -// static -void LLPanelPermissions::onCommitNextOwnerModify(LLUICtrl* ctrl, void* data) -{ - //LL_INFOS() << "LLPanelPermissions::onCommitNextOwnerModify" << LL_ENDL; - onCommitPerm(ctrl, data, PERM_NEXT_OWNER, PERM_MODIFY); -} - -// static -void LLPanelPermissions::onCommitNextOwnerCopy(LLUICtrl* ctrl, void* data) -{ - //LL_INFOS() << "LLPanelPermissions::onCommitNextOwnerCopy" << LL_ENDL; - onCommitPerm(ctrl, data, PERM_NEXT_OWNER, PERM_COPY); -} - -// static -void LLPanelPermissions::onCommitNextOwnerTransfer(LLUICtrl* ctrl, void* data) -{ - //LL_INFOS() << "LLPanelPermissions::onCommitNextOwnerTransfer" << LL_ENDL; - onCommitPerm(ctrl, data, PERM_NEXT_OWNER, PERM_TRANSFER); -} - -// static -void LLPanelPermissions::onCommitName(LLUICtrl*, void* data) -{ - LLPanelPermissions* self = (LLPanelPermissions*)data; - LLLineEditor* tb = self->getChild("Object Name"); - if (!tb) - { - return; - } - LLSelectMgr::getInstance()->selectionSetObjectName(tb->getText()); - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - if (selection->isAttachment() && (selection->getNumNodes() == 1) && !tb->getText().empty()) - { - LLUUID object_id = selection->getFirstObject()->getAttachmentItemID(); - LLViewerInventoryItem* item = findItem(object_id); - if (item) - { - LLPointer new_item = new LLViewerInventoryItem(item); - new_item->rename(tb->getText()); - new_item->updateServer(false); - gInventory.updateItem(new_item); - gInventory.notifyObservers(); - } - } -} - - -// static -void LLPanelPermissions::onCommitDesc(LLUICtrl*, void* data) -{ - LLPanelPermissions* self = (LLPanelPermissions*)data; - LLLineEditor* le = self->getChild("Object Description"); - if (!le) - { - return; - } - LLSelectMgr::getInstance()->selectionSetObjectDescription(le->getText()); - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - if (selection->isAttachment() && (selection->getNumNodes() == 1)) - { - LLUUID object_id = selection->getFirstObject()->getAttachmentItemID(); - LLViewerInventoryItem* item = findItem(object_id); - if (item) - { - LLPointer new_item = new LLViewerInventoryItem(item); - new_item->setDescription(le->getText()); - new_item->updateServer(false); - gInventory.updateItem(new_item); - gInventory.notifyObservers(); - } - } -} - -// static -void LLPanelPermissions::onCommitSaleInfo(LLUICtrl*, void* data) -{ - LLPanelPermissions* self = (LLPanelPermissions*)data; - self->setAllSaleInfo(); -} - -// static -void LLPanelPermissions::onCommitSaleType(LLUICtrl*, void* data) -{ - LLPanelPermissions* self = (LLPanelPermissions*)data; - self->setAllSaleInfo(); -} - -void LLPanelPermissions::setAllSaleInfo() -{ - LL_INFOS() << "LLPanelPermissions::setAllSaleInfo()" << LL_ENDL; - LLSaleInfo::EForSale sale_type = LLSaleInfo::FS_NOT; - - LLCheckBoxCtrl *checkPurchase = getChild("checkbox for sale"); - - // Set the sale type if the object(s) are for sale. - if(checkPurchase && checkPurchase->get()) - { - sale_type = static_cast(getChild("sale type")->getValue().asInteger()); - } - - S32 price = -1; - - LLSpinCtrl *edit_price = getChild("Edit Cost"); - price = (edit_price->getTentative()) ? DEFAULT_PRICE : edit_price->getValue().asInteger(); - - // If somehow an invalid price, turn the sale off. - if (price < 0) - sale_type = LLSaleInfo::FS_NOT; - - LLSaleInfo old_sale_info; - LLSelectMgr::getInstance()->selectGetSaleInfo(old_sale_info); - - LLSaleInfo new_sale_info(sale_type, price); - LLSelectMgr::getInstance()->selectionSetObjectSaleInfo(new_sale_info); - - // Note: won't work right if a root and non-root are both single-selected (here and other places). - bool is_perm_modify = (LLSelectMgr::getInstance()->getSelection()->getFirstRootNode() - && LLSelectMgr::getInstance()->selectGetRootsModify()) - || LLSelectMgr::getInstance()->selectGetModify(); - bool is_nonpermanent_enforced = (LLSelectMgr::getInstance()->getSelection()->getFirstRootNode() - && LLSelectMgr::getInstance()->selectGetRootsNonPermanentEnforced()) - || LLSelectMgr::getInstance()->selectGetNonPermanentEnforced(); - - if (is_perm_modify && is_nonpermanent_enforced) - { - struct f : public LLSelectedObjectFunctor - { - virtual bool apply(LLViewerObject* object) - { - return object->getClickAction() == CLICK_ACTION_BUY - || object->getClickAction() == CLICK_ACTION_TOUCH; - } - } check_actions; - - // Selection should only contain objects that are of target - // action already or of action we are aiming to remove. - bool default_actions = LLSelectMgr::getInstance()->getSelection()->applyToObjects(&check_actions); - - if (default_actions && old_sale_info.isForSale() != new_sale_info.isForSale()) - { - U8 new_click_action = new_sale_info.isForSale() ? CLICK_ACTION_BUY : CLICK_ACTION_TOUCH; - LLSelectMgr::getInstance()->selectionSetClickAction(new_click_action); - } - } -} - -struct LLSelectionPayable : public LLSelectedObjectFunctor -{ - virtual bool apply(LLViewerObject* obj) - { - // can pay if you or your parent has money() event in script - LLViewerObject* parent = (LLViewerObject*)obj->getParent(); - return (obj->flagTakesMoney() - || (parent && parent->flagTakesMoney())); - } -}; - -// static -void LLPanelPermissions::onCommitClickAction(LLUICtrl* ctrl, void*) -{ - LLComboBox* box = (LLComboBox*)ctrl; - if (!box) return; - std::string value = box->getValue().asString(); - U8 click_action = string_value_to_click_action(value); - - if (click_action == CLICK_ACTION_BUY) - { - LLSaleInfo sale_info; - LLSelectMgr::getInstance()->selectGetSaleInfo(sale_info); - if (!sale_info.isForSale()) - { - LLNotificationsUtil::add("CantSetBuyObject"); - - // Set click action back to its old value - U8 click_action = 0; - LLSelectMgr::getInstance()->selectionGetClickAction(&click_action); - std::string item_value = click_action_to_string_value(click_action); - box->setValue(LLSD(item_value)); - return; - } - } - else if (click_action == CLICK_ACTION_PAY) - { - // Verify object has script with money() handler - LLSelectionPayable payable; - bool can_pay = LLSelectMgr::getInstance()->getSelection()->applyToObjects(&payable); - if (!can_pay) - { - // Warn, but do it anyway. - LLNotificationsUtil::add("ClickActionNotPayable"); - } - } - LLSelectMgr::getInstance()->selectionSetClickAction(click_action); -} - -// static -void LLPanelPermissions::onCommitIncludeInSearch(LLUICtrl* ctrl, void*) -{ - LLCheckBoxCtrl* box = (LLCheckBoxCtrl*)ctrl; - llassert(box); - - LLSelectMgr::getInstance()->selectionSetIncludeInSearch(box->get()); -} - - -LLViewerInventoryItem* LLPanelPermissions::findItem(LLUUID &object_id) -{ - if (!object_id.isNull()) - { - return gInventory.getItem(object_id); - } - return NULL; -} +/** + * @file llpanelpermissions.cpp + * @brief LLPanelPermissions class implementation + * This class represents the panel in the build view for + * viewing/editing object names, owners, permissions, etc. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelpermissions.h" + +// library includes +#include "lluuid.h" +#include "llpermissions.h" +#include "llcategory.h" +#include "llclickaction.h" +#include "llfocusmgr.h" +#include "llnotificationsutil.h" +#include "llstring.h" + +// project includes +#include "llviewerwindow.h" +#include "llresmgr.h" +#include "lltextbox.h" +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "llviewerobject.h" +#include "llselectmgr.h" +#include "llagent.h" +#include "llstatusbar.h" // for getBalance() +#include "lllineeditor.h" +#include "llcombobox.h" +#include "lluiconstants.h" +#include "lldbstrings.h" +#include "llfloatergroups.h" +#include "llfloaterreg.h" +#include "llavataractions.h" +#include "llavatariconctrl.h" +#include "llnamebox.h" +#include "llviewercontrol.h" +#include "lluictrlfactory.h" +#include "llspinctrl.h" +#include "roles_constants.h" +#include "llgroupactions.h" +#include "llgroupiconctrl.h" +#include "lltrans.h" +#include "llinventorymodel.h" + +#include "llavatarnamecache.h" +#include "llcachename.h" + + +U8 string_value_to_click_action(std::string p_value); +std::string click_action_to_string_value( U8 action); + +U8 string_value_to_click_action(std::string p_value) +{ + if (p_value == "Touch") + { + return CLICK_ACTION_TOUCH; + } + if (p_value == "Sit") + { + return CLICK_ACTION_SIT; + } + if (p_value == "Buy") + { + return CLICK_ACTION_BUY; + } + if (p_value == "Pay") + { + return CLICK_ACTION_PAY; + } + if (p_value == "Open") + { + return CLICK_ACTION_OPEN; + } + if (p_value == "Zoom") + { + return CLICK_ACTION_ZOOM; + } + if (p_value == "Ignore") + { + return CLICK_ACTION_IGNORE; + } + if (p_value == "None") + { + return CLICK_ACTION_DISABLED; + } + return CLICK_ACTION_TOUCH; +} + +std::string click_action_to_string_value( U8 action) +{ + switch (action) + { + case CLICK_ACTION_TOUCH: + default: + return "Touch"; + break; + case CLICK_ACTION_SIT: + return "Sit"; + break; + case CLICK_ACTION_BUY: + return "Buy"; + break; + case CLICK_ACTION_PAY: + return "Pay"; + break; + case CLICK_ACTION_OPEN: + return "Open"; + break; + case CLICK_ACTION_ZOOM: + return "Zoom"; + break; + case CLICK_ACTION_IGNORE: + return "Ignore"; + break; + case CLICK_ACTION_DISABLED: + return "None"; + break; + } +} + +///---------------------------------------------------------------------------- +/// Class llpanelpermissions +///---------------------------------------------------------------------------- + +// Default constructor +LLPanelPermissions::LLPanelPermissions() : + LLPanel() +{ + setMouseOpaque(false); +} + +bool LLPanelPermissions::postBuild() +{ + childSetCommitCallback("Object Name",LLPanelPermissions::onCommitName,this); + getChild("Object Name")->setPrevalidate(LLTextValidate::validateASCIIPrintableNoPipe); + childSetCommitCallback("Object Description",LLPanelPermissions::onCommitDesc,this); + getChild("Object Description")->setPrevalidate(LLTextValidate::validateASCIIPrintableNoPipe); + + + getChild("button set group")->setCommitCallback(boost::bind(&LLPanelPermissions::onClickGroup,this)); + + childSetCommitCallback("checkbox share with group",LLPanelPermissions::onCommitGroupShare,this); + + childSetAction("button deed",LLPanelPermissions::onClickDeedToGroup,this); + + childSetCommitCallback("checkbox allow everyone move",LLPanelPermissions::onCommitEveryoneMove,this); + + childSetCommitCallback("checkbox allow everyone copy",LLPanelPermissions::onCommitEveryoneCopy,this); + + childSetCommitCallback("checkbox for sale",LLPanelPermissions::onCommitSaleInfo,this); + + childSetCommitCallback("sale type",LLPanelPermissions::onCommitSaleType,this); + + childSetCommitCallback("Edit Cost", LLPanelPermissions::onCommitSaleInfo, this); + + childSetCommitCallback("checkbox next owner can modify",LLPanelPermissions::onCommitNextOwnerModify,this); + childSetCommitCallback("checkbox next owner can copy",LLPanelPermissions::onCommitNextOwnerCopy,this); + childSetCommitCallback("checkbox next owner can transfer",LLPanelPermissions::onCommitNextOwnerTransfer,this); + childSetCommitCallback("clickaction",LLPanelPermissions::onCommitClickAction,this); + childSetCommitCallback("search_check",LLPanelPermissions::onCommitIncludeInSearch,this); + + mLabelGroupName = getChild("Group Name Proxy"); + mLabelOwnerName = getChild("Owner Name"); + mLabelCreatorName = getChild("Creator Name"); + + return true; +} + + +LLPanelPermissions::~LLPanelPermissions() +{ + if (mOwnerCacheConnection.connected()) + { + mOwnerCacheConnection.disconnect(); + } + if (mCreatorCacheConnection.connected()) + { + mCreatorCacheConnection.disconnect(); + } + // base class will take care of everything +} + + +void LLPanelPermissions::disableAll() +{ + getChildView("perm_modify")->setEnabled(false); + getChild("perm_modify")->setValue(LLStringUtil::null); + + getChildView("pathfinding_attributes_value")->setEnabled(false); + getChild("pathfinding_attributes_value")->setValue(LLStringUtil::null); + + getChildView("Creator:")->setEnabled(false); + getChild("Creator Icon")->setVisible(false); + mLabelCreatorName->setValue(LLStringUtil::null); + mLabelCreatorName->setEnabled(false); + + getChildView("Owner:")->setEnabled(false); + getChild("Owner Icon")->setVisible(false); + getChild("Owner Group Icon")->setVisible(false); + mLabelOwnerName->setValue(LLStringUtil::null); + mLabelOwnerName->setEnabled(false); + + getChildView("Group:")->setEnabled(false); + getChild("Group Name Proxy")->setValue(LLStringUtil::null); + getChildView("Group Name Proxy")->setEnabled(false); + getChildView("button set group")->setEnabled(false); + + getChild("Object Name")->setValue(LLStringUtil::null); + getChildView("Object Name")->setEnabled(false); + getChildView("Name:")->setEnabled(false); + getChild("Group Name")->setValue(LLStringUtil::null); + getChildView("Group Name")->setEnabled(false); + getChildView("Description:")->setEnabled(false); + getChild("Object Description")->setValue(LLStringUtil::null); + getChildView("Object Description")->setEnabled(false); + + getChild("checkbox share with group")->setValue(false); + getChildView("checkbox share with group")->setEnabled(false); + getChildView("button deed")->setEnabled(false); + + getChild("checkbox allow everyone move")->setValue(false); + getChildView("checkbox allow everyone move")->setEnabled(false); + getChild("checkbox allow everyone copy")->setValue(false); + getChildView("checkbox allow everyone copy")->setEnabled(false); + + //Next owner can: + getChildView("Next owner can:")->setEnabled(false); + getChild("checkbox next owner can modify")->setValue(false); + getChildView("checkbox next owner can modify")->setEnabled(false); + getChild("checkbox next owner can copy")->setValue(false); + getChildView("checkbox next owner can copy")->setEnabled(false); + getChild("checkbox next owner can transfer")->setValue(false); + getChildView("checkbox next owner can transfer")->setEnabled(false); + + //checkbox for sale + getChild("checkbox for sale")->setValue(false); + getChildView("checkbox for sale")->setEnabled(false); + + //checkbox include in search + getChild("search_check")->setValue(false); + getChildView("search_check")->setEnabled(false); + + LLComboBox* combo_sale_type = getChild("sale type"); + combo_sale_type->setValue(LLSaleInfo::FS_COPY); + combo_sale_type->setEnabled(false); + + getChildView("Cost")->setEnabled(false); + getChild("Cost")->setValue(getString("Cost Default")); + getChild("Edit Cost")->setValue(LLStringUtil::null); + getChildView("Edit Cost")->setEnabled(false); + + getChildView("label click action")->setEnabled(false); + LLComboBox* combo_click_action = getChild("clickaction"); + if (combo_click_action) + { + combo_click_action->setEnabled(false); + combo_click_action->clear(); + } + getChildView("B:")->setVisible(false); + getChildView("O:")->setVisible(false); + getChildView("G:")->setVisible(false); + getChildView("E:")->setVisible(false); + getChildView("N:")->setVisible(false); + getChildView("F:")->setVisible(false); +} + +void LLPanelPermissions::refresh() +{ + LLButton* BtnDeedToGroup = getChild("button deed"); + if(BtnDeedToGroup) + { + std::string deedText; + if (gWarningSettings.getBOOL("DeedObject")) + { + deedText = getString("text deed continued"); + } + else + { + deedText = getString("text deed"); + } + BtnDeedToGroup->setLabelSelected(deedText); + BtnDeedToGroup->setLabelUnselected(deedText); + } + bool root_selected = true; + LLSelectNode* nodep = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(); + S32 object_count = LLSelectMgr::getInstance()->getSelection()->getRootObjectCount(); + if(!nodep || 0 == object_count) + { + nodep = LLSelectMgr::getInstance()->getSelection()->getFirstNode(); + object_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount(); + root_selected = false; + } + + //bool attachment_selected = LLSelectMgr::getInstance()->getSelection()->isAttachment(); + //attachment_selected = false; + LLViewerObject* objectp = NULL; + if(nodep) objectp = nodep->getObject(); + if(!nodep || !objectp)// || attachment_selected) + { + // ...nothing selected + disableAll(); + return; + } + + // figure out a few variables + const bool is_one_object = (object_count == 1); + + // BUG: fails if a root and non-root are both single-selected. + bool is_perm_modify = (LLSelectMgr::getInstance()->getSelection()->getFirstRootNode() + && LLSelectMgr::getInstance()->selectGetRootsModify()) + || LLSelectMgr::getInstance()->selectGetModify(); + bool is_nonpermanent_enforced = (LLSelectMgr::getInstance()->getSelection()->getFirstRootNode() + && LLSelectMgr::getInstance()->selectGetRootsNonPermanentEnforced()) + || LLSelectMgr::getInstance()->selectGetNonPermanentEnforced(); + const LLFocusableElement* keyboard_focus_view = gFocusMgr.getKeyboardFocus(); + + S32 string_index = 0; + std::string MODIFY_INFO_STRINGS[] = + { + getString("text modify info 1"), + getString("text modify info 2"), + getString("text modify info 3"), + getString("text modify info 4"), + getString("text modify info 5"), + getString("text modify info 6") + }; + if (!is_perm_modify) + { + string_index += 2; + } + else if (!is_nonpermanent_enforced) + { + string_index += 4; + } + if (!is_one_object) + { + ++string_index; + } + getChildView("perm_modify")->setEnabled(true); + getChild("perm_modify")->setValue(MODIFY_INFO_STRINGS[string_index]); + + std::string pfAttrName; + + if ((LLSelectMgr::getInstance()->getSelection()->getFirstRootNode() + && LLSelectMgr::getInstance()->selectGetRootsNonPathfinding()) + || LLSelectMgr::getInstance()->selectGetNonPathfinding()) + { + pfAttrName = "Pathfinding_Object_Attr_None"; + } + else if ((LLSelectMgr::getInstance()->getSelection()->getFirstRootNode() + && LLSelectMgr::getInstance()->selectGetRootsPermanent()) + || LLSelectMgr::getInstance()->selectGetPermanent()) + { + pfAttrName = "Pathfinding_Object_Attr_Permanent"; + } + else if ((LLSelectMgr::getInstance()->getSelection()->getFirstRootNode() + && LLSelectMgr::getInstance()->selectGetRootsCharacter()) + || LLSelectMgr::getInstance()->selectGetCharacter()) + { + pfAttrName = "Pathfinding_Object_Attr_Character"; + } + else + { + pfAttrName = "Pathfinding_Object_Attr_MultiSelect"; + } + + getChildView("pathfinding_attributes_value")->setEnabled(true); + getChild("pathfinding_attributes_value")->setValue(LLTrans::getString(pfAttrName)); + + // Update creator text field + getChildView("Creator:")->setEnabled(true); + std::string creator_app_link; + LLSelectMgr::getInstance()->selectGetCreator(mCreatorID, creator_app_link); + + // Style for creator and owner links (both group and agent) + LLStyle::Params style_params; + LLColor4 link_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + style_params.color = link_color; + style_params.readonly_color = link_color; + style_params.is_link = true; // link will be added later + const LLFontGL* fontp = mLabelCreatorName->getFont(); + style_params.font.name = LLFontGL::nameFromFont(fontp); + style_params.font.size = LLFontGL::sizeFromFont(fontp); + style_params.font.style = "UNDERLINE"; + + LLAvatarName av_name; + style_params.link_href = creator_app_link; + if (LLAvatarNameCache::get(mCreatorID, &av_name)) + { + updateCreatorName(mCreatorID, av_name, style_params); + } + else + { + if (mCreatorCacheConnection.connected()) + { + mCreatorCacheConnection.disconnect(); + } + mLabelCreatorName->setText(LLTrans::getString("None")); + mCreatorCacheConnection = LLAvatarNameCache::get(mCreatorID, boost::bind(&LLPanelPermissions::updateCreatorName, this, _1, _2, style_params)); + } + getChild("Creator Icon")->setValue(mCreatorID); + getChild("Creator Icon")->setVisible(true); + mLabelCreatorName->setEnabled(true); + + // Update owner text field + getChildView("Owner:")->setEnabled(true); + + std::string owner_app_link; + const bool owners_identical = LLSelectMgr::getInstance()->selectGetOwner(mOwnerID, owner_app_link); + + + if (LLSelectMgr::getInstance()->selectIsGroupOwned()) + { + // Group owned already displayed by selectGetOwner + LLGroupMgrGroupData* group_data = LLGroupMgr::getInstance()->getGroupData(mOwnerID); + if (group_data && group_data->isGroupPropertiesDataComplete()) + { + style_params.link_href = owner_app_link; + mLabelOwnerName->setText(group_data->mName, style_params); + getChild("Owner Group Icon")->setIconId(group_data->mInsigniaID); + getChild("Owner Group Icon")->setVisible(true); + getChild("Owner Icon")->setVisible(false); + } + else + { + // Triggers refresh + LLGroupMgr::getInstance()->sendGroupPropertiesRequest(mOwnerID); + } + } + else + { + LLUUID owner_id = mOwnerID; + if (owner_id.isNull()) + { + // Display last owner if public + std::string last_owner_app_link; + LLSelectMgr::getInstance()->selectGetLastOwner(mLastOwnerID, last_owner_app_link); + + // It should never happen that the last owner is null and the owner + // is null, but it seems to be a bug in the simulator right now. JC + if (!mLastOwnerID.isNull() && !last_owner_app_link.empty()) + { + owner_app_link.append(", last "); + owner_app_link.append(last_owner_app_link); + } + owner_id = mLastOwnerID; + } + + style_params.link_href = owner_app_link; + if (LLAvatarNameCache::get(owner_id, &av_name)) + { + updateOwnerName(owner_id, av_name, style_params); + } + else + { + if (mOwnerCacheConnection.connected()) + { + mOwnerCacheConnection.disconnect(); + } + mLabelOwnerName->setText(LLTrans::getString("None")); + mOwnerCacheConnection = LLAvatarNameCache::get(owner_id, boost::bind(&LLPanelPermissions::updateOwnerName, this, _1, _2, style_params)); + } + + getChild("Owner Icon")->setValue(owner_id); + getChild("Owner Icon")->setVisible(true); + getChild("Owner Group Icon")->setVisible(false); + } + mLabelOwnerName->setEnabled(true); + + // update group text field + getChildView("Group:")->setEnabled(true); + getChild("Group Name")->setValue(LLStringUtil::null); + LLUUID group_id; + bool groups_identical = LLSelectMgr::getInstance()->selectGetGroup(group_id); + if (groups_identical) + { + if (mLabelGroupName) + { + mLabelGroupName->setNameID(group_id,true); + mLabelGroupName->setEnabled(true); + } + } + else + { + if (mLabelGroupName) + { + mLabelGroupName->setNameID(LLUUID::null, true); + mLabelGroupName->refresh(LLUUID::null, std::string(), true); + mLabelGroupName->setEnabled(false); + } + } + + getChildView("button set group")->setEnabled(root_selected && owners_identical && (mOwnerID == gAgent.getID()) && is_nonpermanent_enforced); + + getChildView("Name:")->setEnabled(true); + LLLineEditor* LineEditorObjectName = getChild("Object Name"); + getChildView("Description:")->setEnabled(true); + LLLineEditor* LineEditorObjectDesc = getChild("Object Description"); + + if (is_one_object) + { + if (keyboard_focus_view != LineEditorObjectName) + { + getChild("Object Name")->setValue(nodep->mName); + } + + if (LineEditorObjectDesc) + { + if (keyboard_focus_view != LineEditorObjectDesc) + { + LineEditorObjectDesc->setText(nodep->mDescription); + } + } + } + else + { + getChild("Object Name")->setValue(LLStringUtil::null); + LineEditorObjectDesc->setText(LLStringUtil::null); + } + + // figure out the contents of the name, description, & category + bool edit_name_desc = false; + if (is_one_object && objectp->permModify() && !objectp->isPermanentEnforced()) + { + edit_name_desc = true; + } + if (edit_name_desc) + { + getChildView("Object Name")->setEnabled(true); + getChildView("Object Description")->setEnabled(true); + } + else + { + getChildView("Object Name")->setEnabled(false); + getChildView("Object Description")->setEnabled(false); + } + + S32 total_sale_price = 0; + S32 individual_sale_price = 0; + bool is_for_sale_mixed = false; + bool is_sale_price_mixed = false; + U32 num_for_sale = false; + LLSelectMgr::getInstance()->selectGetAggregateSaleInfo(num_for_sale, + is_for_sale_mixed, + is_sale_price_mixed, + total_sale_price, + individual_sale_price); + + const bool self_owned = (gAgent.getID() == mOwnerID); + const bool group_owned = LLSelectMgr::getInstance()->selectIsGroupOwned() ; + const bool public_owned = (mOwnerID.isNull() && !LLSelectMgr::getInstance()->selectIsGroupOwned()); + const bool can_transfer = LLSelectMgr::getInstance()->selectGetRootsTransfer(); + const bool can_copy = LLSelectMgr::getInstance()->selectGetRootsCopy(); + + if (!owners_identical) + { + getChildView("Cost")->setEnabled(false); + getChild("Edit Cost")->setValue(LLStringUtil::null); + getChildView("Edit Cost")->setEnabled(false); + } + // You own these objects. + else if (self_owned || (group_owned && gAgent.hasPowerInGroup(group_id,GP_OBJECT_SET_SALE))) + { + // If there are multiple items for sale then set text to PRICE PER UNIT. + if (num_for_sale > 1) + { + getChild("Cost")->setValue(getString("Cost Per Unit")); + } + else + { + getChild("Cost")->setValue(getString("Cost Default")); + } + + LLSpinCtrl *edit_price = getChild("Edit Cost"); + if (!edit_price->hasFocus()) + { + // If the sale price is mixed then set the cost to MIXED, otherwise + // set to the actual cost. + if ((num_for_sale > 0) && is_for_sale_mixed) + { + edit_price->setTentative(true); + } + else if ((num_for_sale > 0) && is_sale_price_mixed) + { + edit_price->setTentative(true); + } + else + { + edit_price->setValue(individual_sale_price); + } + } + // The edit fields are only enabled if you can sell this object + // and the sale price is not mixed. + bool enable_edit = (num_for_sale && can_transfer) ? !is_for_sale_mixed : false; + getChildView("Cost")->setEnabled(enable_edit); + getChildView("Edit Cost")->setEnabled(enable_edit); + } + // Someone, not you, owns these objects. + else if (!public_owned) + { + getChildView("Cost")->setEnabled(false); + getChildView("Edit Cost")->setEnabled(false); + + // Don't show a price if none of the items are for sale. + if (num_for_sale) + getChild("Edit Cost")->setValue(llformat("%d",total_sale_price)); + else + getChild("Edit Cost")->setValue(LLStringUtil::null); + + // If multiple items are for sale, set text to TOTAL PRICE. + if (num_for_sale > 1) + getChild("Cost")->setValue(getString("Cost Total")); + else + getChild("Cost")->setValue(getString("Cost Default")); + } + // This is a public object. + else + { + getChildView("Cost")->setEnabled(false); + getChild("Cost")->setValue(getString("Cost Default")); + + getChild("Edit Cost")->setValue(LLStringUtil::null); + getChildView("Edit Cost")->setEnabled(false); + } + + // Enable and disable the permissions checkboxes + // based on who owns the object. + // TODO: Creator permissions + + U32 base_mask_on = 0; + U32 base_mask_off = 0; + U32 owner_mask_off = 0; + U32 owner_mask_on = 0; + U32 group_mask_on = 0; + U32 group_mask_off = 0; + U32 everyone_mask_on = 0; + U32 everyone_mask_off = 0; + U32 next_owner_mask_on = 0; + U32 next_owner_mask_off = 0; + + bool valid_base_perms = LLSelectMgr::getInstance()->selectGetPerm(PERM_BASE, + &base_mask_on, + &base_mask_off); + //bool valid_owner_perms =// + LLSelectMgr::getInstance()->selectGetPerm(PERM_OWNER, + &owner_mask_on, + &owner_mask_off); + bool valid_group_perms = LLSelectMgr::getInstance()->selectGetPerm(PERM_GROUP, + &group_mask_on, + &group_mask_off); + + bool valid_everyone_perms = LLSelectMgr::getInstance()->selectGetPerm(PERM_EVERYONE, + &everyone_mask_on, + &everyone_mask_off); + + bool valid_next_perms = LLSelectMgr::getInstance()->selectGetPerm(PERM_NEXT_OWNER, + &next_owner_mask_on, + &next_owner_mask_off); + + + if (gSavedSettings.getBOOL("DebugPermissions") ) + { + if (valid_base_perms) + { + getChild("B:")->setValue("B: " + mask_to_string(base_mask_on)); + getChildView("B:")->setVisible(true); + getChild("O:")->setValue("O: " + mask_to_string(owner_mask_on)); + getChildView("O:")->setVisible(true); + getChild("G:")->setValue("G: " + mask_to_string(group_mask_on)); + getChildView("G:")->setVisible(true); + getChild("E:")->setValue("E: " + mask_to_string(everyone_mask_on)); + getChildView("E:")->setVisible(true); + getChild("N:")->setValue("N: " + mask_to_string(next_owner_mask_on)); + getChildView("N:")->setVisible(true); + } + else if(!root_selected) + { + if(object_count == 1) + { + LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode(); + if (node && node->mValid) + { + getChild("B:")->setValue("B: " + mask_to_string( node->mPermissions->getMaskBase())); + getChildView("B:")->setVisible(true); + getChild("O:")->setValue("O: " + mask_to_string(node->mPermissions->getMaskOwner())); + getChildView("O:")->setVisible(true); + getChild("G:")->setValue("G: " + mask_to_string(node->mPermissions->getMaskGroup())); + getChildView("G:")->setVisible(true); + getChild("E:")->setValue("E: " + mask_to_string(node->mPermissions->getMaskEveryone())); + getChildView("E:")->setVisible(true); + getChild("N:")->setValue("N: " + mask_to_string(node->mPermissions->getMaskNextOwner())); + getChildView("N:")->setVisible(true); + } + } + } + else + { + getChildView("B:")->setVisible(false); + getChildView("O:")->setVisible(false); + getChildView("G:")->setVisible(false); + getChildView("E:")->setVisible(false); + getChildView("N:")->setVisible(false); + } + + U32 flag_mask = 0x0; + if (objectp->permMove()) flag_mask |= PERM_MOVE; + if (objectp->permModify()) flag_mask |= PERM_MODIFY; + if (objectp->permCopy()) flag_mask |= PERM_COPY; + if (objectp->permTransfer()) flag_mask |= PERM_TRANSFER; + + getChild("F:")->setValue("F:" + mask_to_string(flag_mask)); + getChildView("F:")->setVisible( true); + } + else + { + getChildView("B:")->setVisible( false); + getChildView("O:")->setVisible( false); + getChildView("G:")->setVisible( false); + getChildView("E:")->setVisible( false); + getChildView("N:")->setVisible( false); + getChildView("F:")->setVisible( false); + } + + bool has_change_perm_ability = false; + bool has_change_sale_ability = false; + + if (valid_base_perms && is_nonpermanent_enforced && + (self_owned || (group_owned && gAgent.hasPowerInGroup(group_id, GP_OBJECT_MANIPULATE)))) + { + has_change_perm_ability = true; + } + if (valid_base_perms && is_nonpermanent_enforced && + (self_owned || (group_owned && gAgent.hasPowerInGroup(group_id, GP_OBJECT_SET_SALE)))) + { + has_change_sale_ability = true; + } + + if (!has_change_perm_ability && !has_change_sale_ability && !root_selected) + { + // ...must select root to choose permissions + getChild("perm_modify")->setValue(getString("text modify warning")); + } + + if (has_change_perm_ability) + { + getChildView("checkbox share with group")->setEnabled(true); + getChildView("checkbox allow everyone move")->setEnabled(owner_mask_on & PERM_MOVE); + getChildView("checkbox allow everyone copy")->setEnabled(owner_mask_on & PERM_COPY && owner_mask_on & PERM_TRANSFER); + } + else + { + getChildView("checkbox share with group")->setEnabled(false); + getChildView("checkbox allow everyone move")->setEnabled(false); + getChildView("checkbox allow everyone copy")->setEnabled(false); + } + + if (has_change_sale_ability && (owner_mask_on & PERM_TRANSFER)) + { + getChildView("checkbox for sale")->setEnabled(can_transfer || (!can_transfer && num_for_sale)); + // Set the checkbox to tentative if the prices of each object selected + // are not the same. + getChild("checkbox for sale")->setTentative( is_for_sale_mixed); + getChildView("sale type")->setEnabled(num_for_sale && can_transfer && !is_sale_price_mixed); + + getChildView("Next owner can:")->setEnabled(true); + getChildView("checkbox next owner can modify")->setEnabled(base_mask_on & PERM_MODIFY); + getChildView("checkbox next owner can copy")->setEnabled(base_mask_on & PERM_COPY); + getChildView("checkbox next owner can transfer")->setEnabled(next_owner_mask_on & PERM_COPY); + } + else + { + getChildView("checkbox for sale")->setEnabled(false); + getChildView("sale type")->setEnabled(false); + + getChildView("Next owner can:")->setEnabled(false); + getChildView("checkbox next owner can modify")->setEnabled(false); + getChildView("checkbox next owner can copy")->setEnabled(false); + getChildView("checkbox next owner can transfer")->setEnabled(false); + } + + if (valid_group_perms) + { + if ((group_mask_on & PERM_COPY) && (group_mask_on & PERM_MODIFY) && (group_mask_on & PERM_MOVE)) + { + getChild("checkbox share with group")->setValue(true); + getChild("checkbox share with group")->setTentative( false); + getChildView("button deed")->setEnabled(gAgent.hasPowerInGroup(group_id, GP_OBJECT_DEED) && (owner_mask_on & PERM_TRANSFER) && !group_owned && can_transfer); + } + else if ((group_mask_off & PERM_COPY) && (group_mask_off & PERM_MODIFY) && (group_mask_off & PERM_MOVE)) + { + getChild("checkbox share with group")->setValue(false); + getChild("checkbox share with group")->setTentative( false); + getChildView("button deed")->setEnabled(false); + } + else + { + getChild("checkbox share with group")->setValue(true); + getChild("checkbox share with group")->setTentative(!has_change_perm_ability); + getChildView("button deed")->setEnabled(gAgent.hasPowerInGroup(group_id, GP_OBJECT_DEED) && (group_mask_on & PERM_MOVE) && (owner_mask_on & PERM_TRANSFER) && !group_owned && can_transfer); + } + } + + if (valid_everyone_perms) + { + // Move + if (everyone_mask_on & PERM_MOVE) + { + getChild("checkbox allow everyone move")->setValue(true); + getChild("checkbox allow everyone move")->setTentative( false); + } + else if (everyone_mask_off & PERM_MOVE) + { + getChild("checkbox allow everyone move")->setValue(false); + getChild("checkbox allow everyone move")->setTentative( false); + } + else + { + getChild("checkbox allow everyone move")->setValue(true); + getChild("checkbox allow everyone move")->setTentative( true); + } + + // Copy == everyone can't copy + if (everyone_mask_on & PERM_COPY) + { + getChild("checkbox allow everyone copy")->setValue(true); + getChild("checkbox allow everyone copy")->setTentative( !can_copy || !can_transfer); + } + else if (everyone_mask_off & PERM_COPY) + { + getChild("checkbox allow everyone copy")->setValue(false); + getChild("checkbox allow everyone copy")->setTentative( false); + } + else + { + getChild("checkbox allow everyone copy")->setValue(true); + getChild("checkbox allow everyone copy")->setTentative( true); + } + } + + if (valid_next_perms) + { + // Modify == next owner canot modify + if (next_owner_mask_on & PERM_MODIFY) + { + getChild("checkbox next owner can modify")->setValue(true); + getChild("checkbox next owner can modify")->setTentative( false); + } + else if (next_owner_mask_off & PERM_MODIFY) + { + getChild("checkbox next owner can modify")->setValue(false); + getChild("checkbox next owner can modify")->setTentative( false); + } + else + { + getChild("checkbox next owner can modify")->setValue(true); + getChild("checkbox next owner can modify")->setTentative( true); + } + + // Copy == next owner cannot copy + if (next_owner_mask_on & PERM_COPY) + { + getChild("checkbox next owner can copy")->setValue(true); + getChild("checkbox next owner can copy")->setTentative( !can_copy); + } + else if (next_owner_mask_off & PERM_COPY) + { + getChild("checkbox next owner can copy")->setValue(false); + getChild("checkbox next owner can copy")->setTentative( false); + } + else + { + getChild("checkbox next owner can copy")->setValue(true); + getChild("checkbox next owner can copy")->setTentative( true); + } + + // Transfer == next owner cannot transfer + if (next_owner_mask_on & PERM_TRANSFER) + { + getChild("checkbox next owner can transfer")->setValue(true); + getChild("checkbox next owner can transfer")->setTentative( !can_transfer); + } + else if (next_owner_mask_off & PERM_TRANSFER) + { + getChild("checkbox next owner can transfer")->setValue(false); + getChild("checkbox next owner can transfer")->setTentative( false); + } + else + { + getChild("checkbox next owner can transfer")->setValue(true); + getChild("checkbox next owner can transfer")->setTentative( true); + } + } + + // reflect sale information + LLSaleInfo sale_info; + bool valid_sale_info = LLSelectMgr::getInstance()->selectGetSaleInfo(sale_info); + LLSaleInfo::EForSale sale_type = sale_info.getSaleType(); + + LLComboBox* combo_sale_type = getChild("sale type"); + if (valid_sale_info) + { + combo_sale_type->setValue( sale_type == LLSaleInfo::FS_NOT ? LLSaleInfo::FS_COPY : sale_type); + combo_sale_type->setTentative( false); // unfortunately this doesn't do anything at the moment. + } + else + { + // default option is sell copy, determined to be safest + combo_sale_type->setValue( LLSaleInfo::FS_COPY); + combo_sale_type->setTentative( true); // unfortunately this doesn't do anything at the moment. + } + + getChild("checkbox for sale")->setValue((num_for_sale != 0)); + + // HACK: There are some old objects in world that are set for sale, + // but are no-transfer. We need to let users turn for-sale off, but only + // if for-sale is set. + bool cannot_actually_sell = !can_transfer || (!can_copy && sale_type == LLSaleInfo::FS_COPY); + if (cannot_actually_sell) + { + if (num_for_sale && has_change_sale_ability) + { + getChildView("checkbox for sale")->setEnabled(true); + } + } + + // Check search status of objects + const bool all_volume = LLSelectMgr::getInstance()->selectionAllPCode( LL_PCODE_VOLUME ); + bool include_in_search; + const bool all_include_in_search = LLSelectMgr::getInstance()->selectionGetIncludeInSearch(&include_in_search); + getChildView("search_check")->setEnabled(has_change_sale_ability && all_volume); + getChild("search_check")->setValue(include_in_search); + getChild("search_check")->setTentative( !all_include_in_search); + + // Click action (touch, sit, buy, pay, open, play, open media, zoom, ignore) + U8 click_action = 0; + if (LLSelectMgr::getInstance()->selectionGetClickAction(&click_action)) + { + LLComboBox* combo_click_action = getChild("clickaction"); + if (combo_click_action) + { + const std::string combo_value = click_action_to_string_value(click_action); + combo_click_action->setValue(LLSD(combo_value)); + } + } + + if (LLSelectMgr::getInstance()->getSelection()->isAttachment()) + { + getChildView("checkbox for sale")->setEnabled(false); + getChildView("Edit Cost")->setEnabled(false); + getChild("sale type")->setEnabled(false); + } + + getChildView("label click action")->setEnabled(is_perm_modify && is_nonpermanent_enforced && all_volume); + getChildView("clickaction")->setEnabled(is_perm_modify && is_nonpermanent_enforced && all_volume); +} + +// Shorten name if it doesn't fit into max_pixels of two lines +void shorten_name(std::string &name, const LLStyle::Params& style_params, S32 max_pixels) +{ + const LLFontGL* font = style_params.font(); + + LLWString wline = utf8str_to_wstring(name); + // panel supports two lines long names + S32 segment_length = font->maxDrawableChars(wline.c_str(), max_pixels, wline.length(), LLFontGL::WORD_BOUNDARY_IF_POSSIBLE); + if (segment_length == wline.length()) + { + // no work needed + return; + } + + S32 first_line_length = segment_length; + segment_length = font->maxDrawableChars(wline.substr(first_line_length).c_str(), max_pixels, wline.length(), LLFontGL::ANYWHERE); + if (segment_length + first_line_length == wline.length()) + { + // no work needed + return; + } + + // name does not fit, cut it, add ... + const LLWString dots_pad(utf8str_to_wstring(std::string("...."))); + S32 elipses_width = font->getWidthF32(dots_pad.c_str()); + segment_length = font->maxDrawableChars(wline.substr(first_line_length).c_str(), max_pixels - elipses_width, wline.length(), LLFontGL::ANYWHERE); + + name = name.substr(0, segment_length + first_line_length) + std::string("..."); +} + +void LLPanelPermissions::updateOwnerName(const LLUUID& owner_id, const LLAvatarName& owner_name, const LLStyle::Params& style_params) +{ + if (mOwnerCacheConnection.connected()) + { + mOwnerCacheConnection.disconnect(); + } + std::string name = owner_name.getCompleteName(); + shorten_name(name, style_params, mLabelOwnerName->getLocalRect().getWidth()); + mLabelOwnerName->setText(name, style_params); +} + +void LLPanelPermissions::updateCreatorName(const LLUUID& creator_id, const LLAvatarName& creator_name, const LLStyle::Params& style_params) +{ + if (mCreatorCacheConnection.connected()) + { + mCreatorCacheConnection.disconnect(); + } + std::string name = creator_name.getCompleteName(); + shorten_name(name, style_params, mLabelCreatorName->getLocalRect().getWidth()); + mLabelCreatorName->setText(name, style_params); +} + +// static +void LLPanelPermissions::onClickClaim(void*) +{ + // try to claim ownership + LLSelectMgr::getInstance()->sendOwner(gAgent.getID(), gAgent.getGroupID()); +} + +// static +void LLPanelPermissions::onClickRelease(void*) +{ + // try to release ownership + LLSelectMgr::getInstance()->sendOwner(LLUUID::null, LLUUID::null); +} + +void LLPanelPermissions::onClickGroup() +{ + LLUUID owner_id; + std::string name; + bool owners_identical = LLSelectMgr::getInstance()->selectGetOwner(owner_id, name); + LLFloater* parent_floater = gFloaterView->getParentFloater(this); + + if(owners_identical && (owner_id == gAgent.getID())) + { + LLFloaterGroupPicker* fg = LLFloaterReg::showTypedInstance("group_picker", LLSD(gAgent.getID())); + if (fg) + { + fg->setSelectGroupCallback( boost::bind(&LLPanelPermissions::cbGroupID, this, _1) ); + + if (parent_floater) + { + LLRect new_rect = gFloaterView->findNeighboringPosition(parent_floater, fg); + fg->setOrigin(new_rect.mLeft, new_rect.mBottom); + parent_floater->addDependentFloater(fg); + } + } + } +} + +void LLPanelPermissions::cbGroupID(LLUUID group_id) +{ + if(mLabelGroupName) + { + mLabelGroupName->setNameID(group_id, true); + } + LLSelectMgr::getInstance()->sendGroup(group_id); +} + +bool callback_deed_to_group(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (0 == option) + { + LLUUID group_id; + bool groups_identical = LLSelectMgr::getInstance()->selectGetGroup(group_id); + if(group_id.notNull() && groups_identical && (gAgent.hasPowerInGroup(group_id, GP_OBJECT_DEED))) + { + LLSelectMgr::getInstance()->sendOwner(LLUUID::null, group_id, false); + } + } + return false; +} + +void LLPanelPermissions::onClickDeedToGroup(void* data) +{ + LLNotificationsUtil::add( "DeedObjectToGroup", LLSD(), LLSD(), callback_deed_to_group); +} + +///---------------------------------------------------------------------------- +/// Permissions checkboxes +///---------------------------------------------------------------------------- + +// static +void LLPanelPermissions::onCommitPerm(LLUICtrl *ctrl, void *data, U8 field, U32 perm) +{ + LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getFirstRootObject(); + if(!object) return; + + // Checkbox will have toggled itself + // LLPanelPermissions* self = (LLPanelPermissions*)data; + LLCheckBoxCtrl *check = (LLCheckBoxCtrl *)ctrl; + bool new_state = check->get(); + + LLSelectMgr::getInstance()->selectionSetObjectPermissions(field, new_state, perm); +} + +// static +void LLPanelPermissions::onCommitGroupShare(LLUICtrl *ctrl, void *data) +{ + onCommitPerm(ctrl, data, PERM_GROUP, PERM_MODIFY | PERM_MOVE | PERM_COPY); +} + +// static +void LLPanelPermissions::onCommitEveryoneMove(LLUICtrl *ctrl, void *data) +{ + onCommitPerm(ctrl, data, PERM_EVERYONE, PERM_MOVE); +} + + +// static +void LLPanelPermissions::onCommitEveryoneCopy(LLUICtrl *ctrl, void *data) +{ + onCommitPerm(ctrl, data, PERM_EVERYONE, PERM_COPY); +} + +// static +void LLPanelPermissions::onCommitNextOwnerModify(LLUICtrl* ctrl, void* data) +{ + //LL_INFOS() << "LLPanelPermissions::onCommitNextOwnerModify" << LL_ENDL; + onCommitPerm(ctrl, data, PERM_NEXT_OWNER, PERM_MODIFY); +} + +// static +void LLPanelPermissions::onCommitNextOwnerCopy(LLUICtrl* ctrl, void* data) +{ + //LL_INFOS() << "LLPanelPermissions::onCommitNextOwnerCopy" << LL_ENDL; + onCommitPerm(ctrl, data, PERM_NEXT_OWNER, PERM_COPY); +} + +// static +void LLPanelPermissions::onCommitNextOwnerTransfer(LLUICtrl* ctrl, void* data) +{ + //LL_INFOS() << "LLPanelPermissions::onCommitNextOwnerTransfer" << LL_ENDL; + onCommitPerm(ctrl, data, PERM_NEXT_OWNER, PERM_TRANSFER); +} + +// static +void LLPanelPermissions::onCommitName(LLUICtrl*, void* data) +{ + LLPanelPermissions* self = (LLPanelPermissions*)data; + LLLineEditor* tb = self->getChild("Object Name"); + if (!tb) + { + return; + } + LLSelectMgr::getInstance()->selectionSetObjectName(tb->getText()); + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + if (selection->isAttachment() && (selection->getNumNodes() == 1) && !tb->getText().empty()) + { + LLUUID object_id = selection->getFirstObject()->getAttachmentItemID(); + LLViewerInventoryItem* item = findItem(object_id); + if (item) + { + LLPointer new_item = new LLViewerInventoryItem(item); + new_item->rename(tb->getText()); + new_item->updateServer(false); + gInventory.updateItem(new_item); + gInventory.notifyObservers(); + } + } +} + + +// static +void LLPanelPermissions::onCommitDesc(LLUICtrl*, void* data) +{ + LLPanelPermissions* self = (LLPanelPermissions*)data; + LLLineEditor* le = self->getChild("Object Description"); + if (!le) + { + return; + } + LLSelectMgr::getInstance()->selectionSetObjectDescription(le->getText()); + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + if (selection->isAttachment() && (selection->getNumNodes() == 1)) + { + LLUUID object_id = selection->getFirstObject()->getAttachmentItemID(); + LLViewerInventoryItem* item = findItem(object_id); + if (item) + { + LLPointer new_item = new LLViewerInventoryItem(item); + new_item->setDescription(le->getText()); + new_item->updateServer(false); + gInventory.updateItem(new_item); + gInventory.notifyObservers(); + } + } +} + +// static +void LLPanelPermissions::onCommitSaleInfo(LLUICtrl*, void* data) +{ + LLPanelPermissions* self = (LLPanelPermissions*)data; + self->setAllSaleInfo(); +} + +// static +void LLPanelPermissions::onCommitSaleType(LLUICtrl*, void* data) +{ + LLPanelPermissions* self = (LLPanelPermissions*)data; + self->setAllSaleInfo(); +} + +void LLPanelPermissions::setAllSaleInfo() +{ + LL_INFOS() << "LLPanelPermissions::setAllSaleInfo()" << LL_ENDL; + LLSaleInfo::EForSale sale_type = LLSaleInfo::FS_NOT; + + LLCheckBoxCtrl *checkPurchase = getChild("checkbox for sale"); + + // Set the sale type if the object(s) are for sale. + if(checkPurchase && checkPurchase->get()) + { + sale_type = static_cast(getChild("sale type")->getValue().asInteger()); + } + + S32 price = -1; + + LLSpinCtrl *edit_price = getChild("Edit Cost"); + price = (edit_price->getTentative()) ? DEFAULT_PRICE : edit_price->getValue().asInteger(); + + // If somehow an invalid price, turn the sale off. + if (price < 0) + sale_type = LLSaleInfo::FS_NOT; + + LLSaleInfo old_sale_info; + LLSelectMgr::getInstance()->selectGetSaleInfo(old_sale_info); + + LLSaleInfo new_sale_info(sale_type, price); + LLSelectMgr::getInstance()->selectionSetObjectSaleInfo(new_sale_info); + + // Note: won't work right if a root and non-root are both single-selected (here and other places). + bool is_perm_modify = (LLSelectMgr::getInstance()->getSelection()->getFirstRootNode() + && LLSelectMgr::getInstance()->selectGetRootsModify()) + || LLSelectMgr::getInstance()->selectGetModify(); + bool is_nonpermanent_enforced = (LLSelectMgr::getInstance()->getSelection()->getFirstRootNode() + && LLSelectMgr::getInstance()->selectGetRootsNonPermanentEnforced()) + || LLSelectMgr::getInstance()->selectGetNonPermanentEnforced(); + + if (is_perm_modify && is_nonpermanent_enforced) + { + struct f : public LLSelectedObjectFunctor + { + virtual bool apply(LLViewerObject* object) + { + return object->getClickAction() == CLICK_ACTION_BUY + || object->getClickAction() == CLICK_ACTION_TOUCH; + } + } check_actions; + + // Selection should only contain objects that are of target + // action already or of action we are aiming to remove. + bool default_actions = LLSelectMgr::getInstance()->getSelection()->applyToObjects(&check_actions); + + if (default_actions && old_sale_info.isForSale() != new_sale_info.isForSale()) + { + U8 new_click_action = new_sale_info.isForSale() ? CLICK_ACTION_BUY : CLICK_ACTION_TOUCH; + LLSelectMgr::getInstance()->selectionSetClickAction(new_click_action); + } + } +} + +struct LLSelectionPayable : public LLSelectedObjectFunctor +{ + virtual bool apply(LLViewerObject* obj) + { + // can pay if you or your parent has money() event in script + LLViewerObject* parent = (LLViewerObject*)obj->getParent(); + return (obj->flagTakesMoney() + || (parent && parent->flagTakesMoney())); + } +}; + +// static +void LLPanelPermissions::onCommitClickAction(LLUICtrl* ctrl, void*) +{ + LLComboBox* box = (LLComboBox*)ctrl; + if (!box) return; + std::string value = box->getValue().asString(); + U8 click_action = string_value_to_click_action(value); + + if (click_action == CLICK_ACTION_BUY) + { + LLSaleInfo sale_info; + LLSelectMgr::getInstance()->selectGetSaleInfo(sale_info); + if (!sale_info.isForSale()) + { + LLNotificationsUtil::add("CantSetBuyObject"); + + // Set click action back to its old value + U8 click_action = 0; + LLSelectMgr::getInstance()->selectionGetClickAction(&click_action); + std::string item_value = click_action_to_string_value(click_action); + box->setValue(LLSD(item_value)); + return; + } + } + else if (click_action == CLICK_ACTION_PAY) + { + // Verify object has script with money() handler + LLSelectionPayable payable; + bool can_pay = LLSelectMgr::getInstance()->getSelection()->applyToObjects(&payable); + if (!can_pay) + { + // Warn, but do it anyway. + LLNotificationsUtil::add("ClickActionNotPayable"); + } + } + LLSelectMgr::getInstance()->selectionSetClickAction(click_action); +} + +// static +void LLPanelPermissions::onCommitIncludeInSearch(LLUICtrl* ctrl, void*) +{ + LLCheckBoxCtrl* box = (LLCheckBoxCtrl*)ctrl; + llassert(box); + + LLSelectMgr::getInstance()->selectionSetIncludeInSearch(box->get()); +} + + +LLViewerInventoryItem* LLPanelPermissions::findItem(LLUUID &object_id) +{ + if (!object_id.isNull()) + { + return gInventory.getItem(object_id); + } + return NULL; +} diff --git a/indra/newview/llpanelpermissions.h b/indra/newview/llpanelpermissions.h index 2d9bd28ac5..77129434ed 100644 --- a/indra/newview/llpanelpermissions.h +++ b/indra/newview/llpanelpermissions.h @@ -1,103 +1,103 @@ -/** - * @file llpanelpermissions.h - * @brief LLPanelPermissions class header file - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELPERMISSIONS_H -#define LL_LLPANELPERMISSIONS_H - -#include "llpanel.h" -#include "llstyle.h" -#include "lluuid.h" - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class llpanelpermissions -// -// Panel for permissions of an object. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLAvatarName; -class LLTextBox; -class LLNameBox; -class LLViewerInventoryItem; - -class LLPanelPermissions : public LLPanel -{ -public: - LLPanelPermissions(); - virtual ~LLPanelPermissions(); - - /*virtual*/ bool postBuild(); - void updateOwnerName(const LLUUID& owner_id, const LLAvatarName& owner_name, const LLStyle::Params& style_params); - void updateCreatorName(const LLUUID& creator_id, const LLAvatarName& creator_name, const LLStyle::Params& style_params); - void refresh(); // refresh all labels as needed - -protected: - // statics - static void onClickClaim(void*); - static void onClickRelease(void*); - void onClickGroup(); - void cbGroupID(LLUUID group_id); - static void onClickDeedToGroup(void*); - - static void onCommitPerm(LLUICtrl *ctrl, void *data, U8 field, U32 perm); - - static void onCommitGroupShare(LLUICtrl *ctrl, void *data); - - static void onCommitEveryoneMove(LLUICtrl *ctrl, void *data); - static void onCommitEveryoneCopy(LLUICtrl *ctrl, void *data); - - static void onCommitNextOwnerModify(LLUICtrl* ctrl, void* data); - static void onCommitNextOwnerCopy(LLUICtrl* ctrl, void* data); - static void onCommitNextOwnerTransfer(LLUICtrl* ctrl, void* data); - - static void onCommitName(LLUICtrl* ctrl, void* data); - static void onCommitDesc(LLUICtrl* ctrl, void* data); - - static void onCommitSaleInfo(LLUICtrl* ctrl, void* data); - static void onCommitSaleType(LLUICtrl* ctrl, void* data); - void setAllSaleInfo(); - - static void onCommitClickAction(LLUICtrl* ctrl, void*); - static void onCommitIncludeInSearch(LLUICtrl* ctrl, void*); - - static LLViewerInventoryItem* findItem(LLUUID &object_id); - -protected: - void disableAll(); - -private: - LLNameBox* mLabelGroupName; // group name - LLTextBox* mLabelOwnerName; - LLTextBox* mLabelCreatorName; - LLUUID mCreatorID; - LLUUID mOwnerID; - LLUUID mLastOwnerID; - - boost::signals2::connection mOwnerCacheConnection; - boost::signals2::connection mCreatorCacheConnection; -}; - - -#endif // LL_LLPANELPERMISSIONS_H +/** + * @file llpanelpermissions.h + * @brief LLPanelPermissions class header file + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELPERMISSIONS_H +#define LL_LLPANELPERMISSIONS_H + +#include "llpanel.h" +#include "llstyle.h" +#include "lluuid.h" + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class llpanelpermissions +// +// Panel for permissions of an object. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLAvatarName; +class LLTextBox; +class LLNameBox; +class LLViewerInventoryItem; + +class LLPanelPermissions : public LLPanel +{ +public: + LLPanelPermissions(); + virtual ~LLPanelPermissions(); + + /*virtual*/ bool postBuild(); + void updateOwnerName(const LLUUID& owner_id, const LLAvatarName& owner_name, const LLStyle::Params& style_params); + void updateCreatorName(const LLUUID& creator_id, const LLAvatarName& creator_name, const LLStyle::Params& style_params); + void refresh(); // refresh all labels as needed + +protected: + // statics + static void onClickClaim(void*); + static void onClickRelease(void*); + void onClickGroup(); + void cbGroupID(LLUUID group_id); + static void onClickDeedToGroup(void*); + + static void onCommitPerm(LLUICtrl *ctrl, void *data, U8 field, U32 perm); + + static void onCommitGroupShare(LLUICtrl *ctrl, void *data); + + static void onCommitEveryoneMove(LLUICtrl *ctrl, void *data); + static void onCommitEveryoneCopy(LLUICtrl *ctrl, void *data); + + static void onCommitNextOwnerModify(LLUICtrl* ctrl, void* data); + static void onCommitNextOwnerCopy(LLUICtrl* ctrl, void* data); + static void onCommitNextOwnerTransfer(LLUICtrl* ctrl, void* data); + + static void onCommitName(LLUICtrl* ctrl, void* data); + static void onCommitDesc(LLUICtrl* ctrl, void* data); + + static void onCommitSaleInfo(LLUICtrl* ctrl, void* data); + static void onCommitSaleType(LLUICtrl* ctrl, void* data); + void setAllSaleInfo(); + + static void onCommitClickAction(LLUICtrl* ctrl, void*); + static void onCommitIncludeInSearch(LLUICtrl* ctrl, void*); + + static LLViewerInventoryItem* findItem(LLUUID &object_id); + +protected: + void disableAll(); + +private: + LLNameBox* mLabelGroupName; // group name + LLTextBox* mLabelOwnerName; + LLTextBox* mLabelCreatorName; + LLUUID mCreatorID; + LLUUID mOwnerID; + LLUUID mLastOwnerID; + + boost::signals2::connection mOwnerCacheConnection; + boost::signals2::connection mCreatorCacheConnection; +}; + + +#endif // LL_LLPANELPERMISSIONS_H diff --git a/indra/newview/llpanelplaceinfo.cpp b/indra/newview/llpanelplaceinfo.cpp index ab1b64e7ea..efce55907e 100644 --- a/indra/newview/llpanelplaceinfo.cpp +++ b/indra/newview/llpanelplaceinfo.cpp @@ -1,320 +1,320 @@ -/** - * @file llpanelplaceinfo.cpp - * @brief Base class for place information in Side Tray. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelplaceinfo.h" -#include "llfloaterprofile.h" -#include "llfloaterreg.h" - -#include "llavatarname.h" -#include "llsdutil.h" - -#include "llsdutil_math.h" - -#include "llregionhandle.h" - -#include "lliconctrl.h" -#include "lltextbox.h" - -#include "lltrans.h" - -#include "llagent.h" -#include "llexpandabletextbox.h" -#include "llslurl.h" -#include "lltexturectrl.h" -#include "llviewerregion.h" -#include "llhttpconstants.h" - -LLPanelPlaceInfo::LLPanelPlaceInfo() -: LLPanel(), - mParcelID(), - mRequestedID(), - mPosRegion(), - mScrollingPanelMinHeight(0), - mScrollingPanelWidth(0), - mInfoType(UNKNOWN), - mScrollingPanel(NULL), - mScrollContainer(NULL), - mDescEditor(NULL) -{} - -//virtual -LLPanelPlaceInfo::~LLPanelPlaceInfo() -{ - if (mParcelID.notNull()) - { - LLRemoteParcelInfoProcessor::getInstance()->removeObserver(mParcelID, this); - } -} - -//virtual -bool LLPanelPlaceInfo::postBuild() -{ - mTitle = getChild("title"); - mCurrentTitle = mTitle->getText(); - - mSnapshotCtrl = getChild("logo"); - mRegionName = getChild("region_title"); - mParcelName = getChild("parcel_title"); - mParcelOwner = getChild("parcel_owner"); - mDescEditor = getChild("description"); - - mMaturityRatingIcon = getChild("maturity_icon"); - mMaturityRatingText = getChild("maturity_value"); - - mScrollingPanel = getChild("scrolling_panel"); - mScrollContainer = getChild("place_scroll"); - - mScrollingPanelMinHeight = mScrollContainer->getScrolledViewRect().getHeight(); - mScrollingPanelWidth = mScrollingPanel->getRect().getWidth(); - - return true; -} - -//virtual -void LLPanelPlaceInfo::resetLocation() -{ - mParcelID.setNull(); - mRequestedID.setNull(); - mPosRegion.clearVec(); - mRegionTitle.clear(); - - std::string loading = LLTrans::getString("LoadingData"); - mMaturityRatingText->setValue(loading); - mRegionName->setTextArg("[REGIONAMEPOS]", loading); - mParcelName->setText(loading); - mParcelOwner->setText(loading); - mDescEditor->setText(loading); - mMaturityRatingIcon->setValue(LLUUID::null); - - mSnapshotCtrl->setImageAssetID(LLUUID::null); -} - -//virtual -void LLPanelPlaceInfo::setParcelID(const LLUUID& parcel_id) -{ - mParcelID = parcel_id; - sendParcelInfoRequest(); -} - -//virtual -void LLPanelPlaceInfo::setInfoType(EInfoType type) -{ - mTitle->setText(mCurrentTitle); - mTitle->setToolTip(mCurrentTitle); - - mInfoType = type; -} - -void LLPanelPlaceInfo::sendParcelInfoRequest() -{ - if (mParcelID != mRequestedID) - { - LLRemoteParcelInfoProcessor::getInstance()->addObserver(mParcelID, this); - LLRemoteParcelInfoProcessor::getInstance()->sendParcelInfoRequest(mParcelID); - - mRequestedID = mParcelID; - } -} - -void LLPanelPlaceInfo::displayParcelInfo(const LLUUID& region_id, - const LLVector3d& pos_global) -{ - LLViewerRegion* region = gAgent.getRegion(); - if (!region) - return; - - mPosRegion.setVec((F32)fmod(pos_global.mdV[VX], (F64)REGION_WIDTH_METERS), - (F32)fmod(pos_global.mdV[VY], (F64)REGION_WIDTH_METERS), - (F32)pos_global.mdV[VZ]); - - LLSD body; - std::string url = region->getCapability("RemoteParcelRequest"); - if (!url.empty()) - { - LLRemoteParcelInfoProcessor::getInstance()->requestRegionParcelInfo(url, - region_id, mPosRegion, pos_global, getObserverHandle()); - } - else - { - mDescEditor->setText(getString("server_update_text")); - } -} - -// virtual -void LLPanelPlaceInfo::setErrorStatus(S32 status, const std::string& reason) -{ - // We only really handle 404 and 499 errors - std::string error_text; - if(status == HTTP_NOT_FOUND) - { - error_text = getString("server_error_text"); - } - else if(status == HTTP_INTERNAL_ERROR) - { - error_text = getString("server_forbidden_text"); - } - else - { - error_text = getString("server_error_text"); - } - - mDescEditor->setText(error_text); - - std::string not_available = getString("not_available"); - mMaturityRatingText->setValue(not_available); - mRegionName->setTextArg("[REGIONAMEPOS]", not_available); - mParcelName->setText(not_available); - mParcelOwner->setText(not_available); - mMaturityRatingIcon->setValue(LLUUID::null); - mRegionTitle.clear(); - - // Enable "Back" button that was disabled when parcel request was sent. - getChild("back_btn")->setEnabled(true); -} - -// virtual -void LLPanelPlaceInfo::processParcelInfo(const LLParcelData& parcel_data) -{ - if(mSnapshotCtrl) - { - mSnapshotCtrl->setImageAssetID(parcel_data.snapshot_id); - } - - S32 region_x; - S32 region_y; - S32 region_z; - - // If the region position is zero, grab position from the global - if (mPosRegion.isExactlyZero()) - { - region_x = ll_round(parcel_data.global_x) % REGION_WIDTH_UNITS; - region_y = ll_round(parcel_data.global_y) % REGION_WIDTH_UNITS; - region_z = ll_round(parcel_data.global_z); - } - else - { - region_x = ll_round(mPosRegion.mV[VX]); - region_y = ll_round(mPosRegion.mV[VY]); - region_z = ll_round(mPosRegion.mV[VZ]); - } - - if (!parcel_data.sim_name.empty()) - { - mRegionTitle = parcel_data.sim_name; - std::string name_and_pos = llformat("%s (%d, %d, %d)", - mRegionTitle.c_str(), region_x, region_y, region_z); - mRegionName->setTextArg("[REGIONAMEPOS]", name_and_pos); - } - else - { - mRegionTitle.clear(); - mRegionName->setText(LLStringUtil::null); - } - - if(!parcel_data.desc.empty()) - { - mDescEditor->setText(parcel_data.desc); - } - else - { - mDescEditor->setText(getString("not_available")); - } - - if (!parcel_data.name.empty()) - { - mParcelTitle = parcel_data.name; - - mParcelName->setText(mParcelTitle); - } - else - { - mParcelName->setText(getString("not_available")); - } -} - -// virtual -void LLPanelPlaceInfo::reshape(S32 width, S32 height, bool called_from_parent) -{ - - // This if was added to force collapsing description textbox on Windows at the beginning of reshape - // (the only case when reshape is skipped here is when it's caused by this textbox, so called_from_parent is false) - // This way it is consistent with Linux where topLost collapses textbox at the beginning of reshape. - // On windows it collapsed only after reshape which caused EXT-8342. - if(called_from_parent) - { - if(mDescEditor) mDescEditor->onTopLost(); - } - - LLPanel::reshape(width, height, called_from_parent); - - if (!mScrollContainer || !mScrollingPanel) - return; - - static LLUICachedControl scrollbar_size ("UIScrollbarSize", 0); - - S32 scroll_height = mScrollContainer->getRect().getHeight(); - if (mScrollingPanelMinHeight > scroll_height) - { - mScrollingPanel->reshape(mScrollingPanelWidth, mScrollingPanelMinHeight); - } - else - { - mScrollingPanel->reshape(mScrollingPanelWidth + scrollbar_size, scroll_height); - } -} - -void LLPanelPlaceInfo::createPick(const LLVector3d& pos_global) -{ - LLPickData data; - data.pos_global = pos_global; - data.name = mParcelTitle.empty() ? mRegionTitle : mParcelTitle; - data.sim_name = mRegionTitle; - data.desc = mDescEditor->getText(); - data.snapshot_id = mSnapshotCtrl->getImageAssetID(); - data.parcel_id = mParcelID; - - LLFloaterProfile* profile_floater = dynamic_cast(LLFloaterReg::showInstance("profile", LLSD().with("id", gAgentID))); - if (profile_floater) - { - profile_floater->createPick(data); - } -} - -// static -void LLPanelPlaceInfo::onNameCache(LLTextBox* text, const std::string& full_name) -{ - text->setText(full_name); -} - -// static -void LLPanelPlaceInfo::onAvatarNameCache(const LLUUID& agent_id, - const LLAvatarName& av_name, - LLTextBox* text) -{ - text->setText( av_name.getCompleteName() ); -} +/** + * @file llpanelplaceinfo.cpp + * @brief Base class for place information in Side Tray. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelplaceinfo.h" +#include "llfloaterprofile.h" +#include "llfloaterreg.h" + +#include "llavatarname.h" +#include "llsdutil.h" + +#include "llsdutil_math.h" + +#include "llregionhandle.h" + +#include "lliconctrl.h" +#include "lltextbox.h" + +#include "lltrans.h" + +#include "llagent.h" +#include "llexpandabletextbox.h" +#include "llslurl.h" +#include "lltexturectrl.h" +#include "llviewerregion.h" +#include "llhttpconstants.h" + +LLPanelPlaceInfo::LLPanelPlaceInfo() +: LLPanel(), + mParcelID(), + mRequestedID(), + mPosRegion(), + mScrollingPanelMinHeight(0), + mScrollingPanelWidth(0), + mInfoType(UNKNOWN), + mScrollingPanel(NULL), + mScrollContainer(NULL), + mDescEditor(NULL) +{} + +//virtual +LLPanelPlaceInfo::~LLPanelPlaceInfo() +{ + if (mParcelID.notNull()) + { + LLRemoteParcelInfoProcessor::getInstance()->removeObserver(mParcelID, this); + } +} + +//virtual +bool LLPanelPlaceInfo::postBuild() +{ + mTitle = getChild("title"); + mCurrentTitle = mTitle->getText(); + + mSnapshotCtrl = getChild("logo"); + mRegionName = getChild("region_title"); + mParcelName = getChild("parcel_title"); + mParcelOwner = getChild("parcel_owner"); + mDescEditor = getChild("description"); + + mMaturityRatingIcon = getChild("maturity_icon"); + mMaturityRatingText = getChild("maturity_value"); + + mScrollingPanel = getChild("scrolling_panel"); + mScrollContainer = getChild("place_scroll"); + + mScrollingPanelMinHeight = mScrollContainer->getScrolledViewRect().getHeight(); + mScrollingPanelWidth = mScrollingPanel->getRect().getWidth(); + + return true; +} + +//virtual +void LLPanelPlaceInfo::resetLocation() +{ + mParcelID.setNull(); + mRequestedID.setNull(); + mPosRegion.clearVec(); + mRegionTitle.clear(); + + std::string loading = LLTrans::getString("LoadingData"); + mMaturityRatingText->setValue(loading); + mRegionName->setTextArg("[REGIONAMEPOS]", loading); + mParcelName->setText(loading); + mParcelOwner->setText(loading); + mDescEditor->setText(loading); + mMaturityRatingIcon->setValue(LLUUID::null); + + mSnapshotCtrl->setImageAssetID(LLUUID::null); +} + +//virtual +void LLPanelPlaceInfo::setParcelID(const LLUUID& parcel_id) +{ + mParcelID = parcel_id; + sendParcelInfoRequest(); +} + +//virtual +void LLPanelPlaceInfo::setInfoType(EInfoType type) +{ + mTitle->setText(mCurrentTitle); + mTitle->setToolTip(mCurrentTitle); + + mInfoType = type; +} + +void LLPanelPlaceInfo::sendParcelInfoRequest() +{ + if (mParcelID != mRequestedID) + { + LLRemoteParcelInfoProcessor::getInstance()->addObserver(mParcelID, this); + LLRemoteParcelInfoProcessor::getInstance()->sendParcelInfoRequest(mParcelID); + + mRequestedID = mParcelID; + } +} + +void LLPanelPlaceInfo::displayParcelInfo(const LLUUID& region_id, + const LLVector3d& pos_global) +{ + LLViewerRegion* region = gAgent.getRegion(); + if (!region) + return; + + mPosRegion.setVec((F32)fmod(pos_global.mdV[VX], (F64)REGION_WIDTH_METERS), + (F32)fmod(pos_global.mdV[VY], (F64)REGION_WIDTH_METERS), + (F32)pos_global.mdV[VZ]); + + LLSD body; + std::string url = region->getCapability("RemoteParcelRequest"); + if (!url.empty()) + { + LLRemoteParcelInfoProcessor::getInstance()->requestRegionParcelInfo(url, + region_id, mPosRegion, pos_global, getObserverHandle()); + } + else + { + mDescEditor->setText(getString("server_update_text")); + } +} + +// virtual +void LLPanelPlaceInfo::setErrorStatus(S32 status, const std::string& reason) +{ + // We only really handle 404 and 499 errors + std::string error_text; + if(status == HTTP_NOT_FOUND) + { + error_text = getString("server_error_text"); + } + else if(status == HTTP_INTERNAL_ERROR) + { + error_text = getString("server_forbidden_text"); + } + else + { + error_text = getString("server_error_text"); + } + + mDescEditor->setText(error_text); + + std::string not_available = getString("not_available"); + mMaturityRatingText->setValue(not_available); + mRegionName->setTextArg("[REGIONAMEPOS]", not_available); + mParcelName->setText(not_available); + mParcelOwner->setText(not_available); + mMaturityRatingIcon->setValue(LLUUID::null); + mRegionTitle.clear(); + + // Enable "Back" button that was disabled when parcel request was sent. + getChild("back_btn")->setEnabled(true); +} + +// virtual +void LLPanelPlaceInfo::processParcelInfo(const LLParcelData& parcel_data) +{ + if(mSnapshotCtrl) + { + mSnapshotCtrl->setImageAssetID(parcel_data.snapshot_id); + } + + S32 region_x; + S32 region_y; + S32 region_z; + + // If the region position is zero, grab position from the global + if (mPosRegion.isExactlyZero()) + { + region_x = ll_round(parcel_data.global_x) % REGION_WIDTH_UNITS; + region_y = ll_round(parcel_data.global_y) % REGION_WIDTH_UNITS; + region_z = ll_round(parcel_data.global_z); + } + else + { + region_x = ll_round(mPosRegion.mV[VX]); + region_y = ll_round(mPosRegion.mV[VY]); + region_z = ll_round(mPosRegion.mV[VZ]); + } + + if (!parcel_data.sim_name.empty()) + { + mRegionTitle = parcel_data.sim_name; + std::string name_and_pos = llformat("%s (%d, %d, %d)", + mRegionTitle.c_str(), region_x, region_y, region_z); + mRegionName->setTextArg("[REGIONAMEPOS]", name_and_pos); + } + else + { + mRegionTitle.clear(); + mRegionName->setText(LLStringUtil::null); + } + + if(!parcel_data.desc.empty()) + { + mDescEditor->setText(parcel_data.desc); + } + else + { + mDescEditor->setText(getString("not_available")); + } + + if (!parcel_data.name.empty()) + { + mParcelTitle = parcel_data.name; + + mParcelName->setText(mParcelTitle); + } + else + { + mParcelName->setText(getString("not_available")); + } +} + +// virtual +void LLPanelPlaceInfo::reshape(S32 width, S32 height, bool called_from_parent) +{ + + // This if was added to force collapsing description textbox on Windows at the beginning of reshape + // (the only case when reshape is skipped here is when it's caused by this textbox, so called_from_parent is false) + // This way it is consistent with Linux where topLost collapses textbox at the beginning of reshape. + // On windows it collapsed only after reshape which caused EXT-8342. + if(called_from_parent) + { + if(mDescEditor) mDescEditor->onTopLost(); + } + + LLPanel::reshape(width, height, called_from_parent); + + if (!mScrollContainer || !mScrollingPanel) + return; + + static LLUICachedControl scrollbar_size ("UIScrollbarSize", 0); + + S32 scroll_height = mScrollContainer->getRect().getHeight(); + if (mScrollingPanelMinHeight > scroll_height) + { + mScrollingPanel->reshape(mScrollingPanelWidth, mScrollingPanelMinHeight); + } + else + { + mScrollingPanel->reshape(mScrollingPanelWidth + scrollbar_size, scroll_height); + } +} + +void LLPanelPlaceInfo::createPick(const LLVector3d& pos_global) +{ + LLPickData data; + data.pos_global = pos_global; + data.name = mParcelTitle.empty() ? mRegionTitle : mParcelTitle; + data.sim_name = mRegionTitle; + data.desc = mDescEditor->getText(); + data.snapshot_id = mSnapshotCtrl->getImageAssetID(); + data.parcel_id = mParcelID; + + LLFloaterProfile* profile_floater = dynamic_cast(LLFloaterReg::showInstance("profile", LLSD().with("id", gAgentID))); + if (profile_floater) + { + profile_floater->createPick(data); + } +} + +// static +void LLPanelPlaceInfo::onNameCache(LLTextBox* text, const std::string& full_name) +{ + text->setText(full_name); +} + +// static +void LLPanelPlaceInfo::onAvatarNameCache(const LLUUID& agent_id, + const LLAvatarName& av_name, + LLTextBox* text) +{ + text->setText( av_name.getCompleteName() ); +} diff --git a/indra/newview/llpanelplaceinfo.h b/indra/newview/llpanelplaceinfo.h index b8eb863be4..6e90603ce5 100644 --- a/indra/newview/llpanelplaceinfo.h +++ b/indra/newview/llpanelplaceinfo.h @@ -1,129 +1,129 @@ -/** - * @file llpanelplaceinfo.h - * @brief Base class for place information in Side Tray. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELPLACEINFO_H -#define LL_LLPANELPLACEINFO_H - -#include "llpanel.h" - -#include "v3dmath.h" -#include "lluuid.h" - -#include "llremoteparcelrequest.h" - -class LLAvatarName; -class LLExpandableTextBox; -class LLIconCtrl; -class LLInventoryItem; -class LLParcel; -class LLScrollContainer; -class LLTextBox; -class LLTextureCtrl; -class LLViewerRegion; -class LLViewerInventoryCategory; - -class LLPanelPlaceInfo : public LLPanel, LLRemoteParcelInfoObserver -{ -public: - enum EInfoType - { - UNKNOWN, - - AGENT, - CREATE_LANDMARK, - LANDMARK, - PLACE, - TELEPORT_HISTORY - }; - - LLPanelPlaceInfo(); - /*virtual*/ ~LLPanelPlaceInfo(); - - /*virtual*/ bool postBuild(); - - // Ignore all old location information, useful if you are - // recycling an existing dialog and need to clear it. - virtual void resetLocation(); - - // Sends a request for data about the given parcel, which will - // only update the location if there is none already available. - /*virtual*/ void setParcelID(const LLUUID& parcel_id); - - // Depending on how the panel was triggered - // (from landmark or current location, or other) - // sets a corresponding title and contents. - virtual void setInfoType(EInfoType type); - - // Requests remote parcel info by parcel ID. - void sendParcelInfoRequest(); - - // Displays information about a remote parcel. - // Sends a request to the server. - void displayParcelInfo(const LLUUID& region_id, - const LLVector3d& pos_global); - - /*virtual*/ void setErrorStatus(S32 status, const std::string& reason); - - /*virtual*/ void processParcelInfo(const LLParcelData& parcel_data); - - /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); - - // Create a pick for the location specified - // by global_pos. - void createPick(const LLVector3d& pos_global); - -protected: - static void onNameCache(LLTextBox* text, const std::string& full_name); - static void onAvatarNameCache(const LLUUID& agent_id, - const LLAvatarName& av_name, - LLTextBox* text); - - /** - * mParcelID is valid only for remote places, in other cases it's null. See resetLocation() - */ - LLUUID mParcelID; - LLUUID mRequestedID; - LLVector3 mPosRegion; - std::string mParcelTitle; // used for pick title without coordinates - std::string mRegionTitle; - std::string mCurrentTitle; - S32 mScrollingPanelMinHeight; - S32 mScrollingPanelWidth; - EInfoType mInfoType; - - LLScrollContainer* mScrollContainer; - LLPanel* mScrollingPanel; - LLTextBox* mTitle; - LLTextureCtrl* mSnapshotCtrl; - LLTextBox* mRegionName; - LLTextBox* mParcelName; - LLTextBox* mParcelOwner; - LLExpandableTextBox* mDescEditor; - LLIconCtrl* mMaturityRatingIcon; - LLTextBox* mMaturityRatingText; -}; - -#endif // LL_LLPANELPLACEINFO_H +/** + * @file llpanelplaceinfo.h + * @brief Base class for place information in Side Tray. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELPLACEINFO_H +#define LL_LLPANELPLACEINFO_H + +#include "llpanel.h" + +#include "v3dmath.h" +#include "lluuid.h" + +#include "llremoteparcelrequest.h" + +class LLAvatarName; +class LLExpandableTextBox; +class LLIconCtrl; +class LLInventoryItem; +class LLParcel; +class LLScrollContainer; +class LLTextBox; +class LLTextureCtrl; +class LLViewerRegion; +class LLViewerInventoryCategory; + +class LLPanelPlaceInfo : public LLPanel, LLRemoteParcelInfoObserver +{ +public: + enum EInfoType + { + UNKNOWN, + + AGENT, + CREATE_LANDMARK, + LANDMARK, + PLACE, + TELEPORT_HISTORY + }; + + LLPanelPlaceInfo(); + /*virtual*/ ~LLPanelPlaceInfo(); + + /*virtual*/ bool postBuild(); + + // Ignore all old location information, useful if you are + // recycling an existing dialog and need to clear it. + virtual void resetLocation(); + + // Sends a request for data about the given parcel, which will + // only update the location if there is none already available. + /*virtual*/ void setParcelID(const LLUUID& parcel_id); + + // Depending on how the panel was triggered + // (from landmark or current location, or other) + // sets a corresponding title and contents. + virtual void setInfoType(EInfoType type); + + // Requests remote parcel info by parcel ID. + void sendParcelInfoRequest(); + + // Displays information about a remote parcel. + // Sends a request to the server. + void displayParcelInfo(const LLUUID& region_id, + const LLVector3d& pos_global); + + /*virtual*/ void setErrorStatus(S32 status, const std::string& reason); + + /*virtual*/ void processParcelInfo(const LLParcelData& parcel_data); + + /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); + + // Create a pick for the location specified + // by global_pos. + void createPick(const LLVector3d& pos_global); + +protected: + static void onNameCache(LLTextBox* text, const std::string& full_name); + static void onAvatarNameCache(const LLUUID& agent_id, + const LLAvatarName& av_name, + LLTextBox* text); + + /** + * mParcelID is valid only for remote places, in other cases it's null. See resetLocation() + */ + LLUUID mParcelID; + LLUUID mRequestedID; + LLVector3 mPosRegion; + std::string mParcelTitle; // used for pick title without coordinates + std::string mRegionTitle; + std::string mCurrentTitle; + S32 mScrollingPanelMinHeight; + S32 mScrollingPanelWidth; + EInfoType mInfoType; + + LLScrollContainer* mScrollContainer; + LLPanel* mScrollingPanel; + LLTextBox* mTitle; + LLTextureCtrl* mSnapshotCtrl; + LLTextBox* mRegionName; + LLTextBox* mParcelName; + LLTextBox* mParcelOwner; + LLExpandableTextBox* mDescEditor; + LLIconCtrl* mMaturityRatingIcon; + LLTextBox* mMaturityRatingText; +}; + +#endif // LL_LLPANELPLACEINFO_H diff --git a/indra/newview/llpanelplaceprofile.cpp b/indra/newview/llpanelplaceprofile.cpp index 9393c6ac7e..4ceeaa5d51 100644 --- a/indra/newview/llpanelplaceprofile.cpp +++ b/indra/newview/llpanelplaceprofile.cpp @@ -1,682 +1,682 @@ -/** - * @file llpanelplaceprofile.cpp - * @brief Displays place profile in Side Tray. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelplaceprofile.h" - -#include "llavatarnamecache.h" -#include "llparcel.h" -#include "message.h" - -#include "llexpandabletextbox.h" -#include "lliconctrl.h" -#include "lllineeditor.h" -#include "lltextbox.h" -#include "lltexteditor.h" - -#include "lltrans.h" - -#include "llaccordionctrl.h" -#include "llaccordionctrltab.h" -#include "llagent.h" -#include "llagentui.h" -#include "llappviewer.h" -#include "llcallbacklist.h" -#include "llbuycurrencyhtml.h" -#include "llslurl.h" -#include "llstatusbar.h" -#include "llviewercontrol.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" - -const F64 COVENANT_REFRESH_TIME_SEC = 60.0f; - -static LLPanelInjector t_place_profile("panel_place_profile"); - -// Statics for textures filenames -static std::string icon_pg; -static std::string icon_m; -static std::string icon_r; -static std::string icon_voice; -static std::string icon_voice_no; -static std::string icon_fly; -static std::string icon_fly_no; -static std::string icon_push; -static std::string icon_push_no; -static std::string icon_build; -static std::string icon_build_no; -static std::string icon_scripts; -static std::string icon_scripts_no; -static std::string icon_damage; -static std::string icon_damage_no; -static std::string icon_see_avs_on; -static std::string icon_see_avs_off; - -LLPanelPlaceProfile::LLPanelPlaceProfile() -: LLPanelPlaceInfo(), - mNextCovenantUpdateTime(0), - mForSalePanel(NULL), - mYouAreHerePanel(NULL), - mSelectedParcelID(-1), - mAccordionCtrl(NULL) -{} - -// virtual -LLPanelPlaceProfile::~LLPanelPlaceProfile() -{ - gIdleCallbacks.deleteFunction(&LLPanelPlaceProfile::updateYouAreHereBanner, this); -} - -// virtual -bool LLPanelPlaceProfile::postBuild() -{ - LLPanelPlaceInfo::postBuild(); - - mForSalePanel = getChild("for_sale_panel"); - mYouAreHerePanel = getChild("here_panel"); - gIdleCallbacks.addFunction(&LLPanelPlaceProfile::updateYouAreHereBanner, this); - - //Icon value should contain sale price of last selected parcel. - mForSalePanel->getChild("icon_for_sale")-> - setMouseDownCallback(boost::bind(&LLPanelPlaceProfile::onForSaleBannerClick, this)); - - mParcelRatingIcon = getChild("rating_icon"); - mParcelRatingText = getChild("rating_value"); - mVoiceIcon = getChild("voice_icon"); - mVoiceText = getChild("voice_value"); - mFlyIcon = getChild("fly_icon"); - mFlyText = getChild("fly_value"); - mPushIcon = getChild("push_icon"); - mPushText = getChild("push_value"); - mBuildIcon = getChild("build_icon"); - mBuildText = getChild("build_value"); - mScriptsIcon = getChild("scripts_icon"); - mScriptsText = getChild("scripts_value"); - mDamageIcon = getChild("damage_icon"); - mDamageText = getChild("damage_value"); - mSeeAVsIcon = getChild("see_avatars_icon"); - mSeeAVsText = getChild("see_avatars_value"); - - mRegionNameText = getChild("region_name"); - mRegionTypeText = getChild("region_type"); - mRegionRatingIcon = getChild("region_rating_icon"); - mRegionRatingText = getChild("region_rating"); - mRegionOwnerText = getChild("region_owner"); - mRegionGroupText = getChild("region_group"); - - mEstateNameText = getChild("estate_name"); - mEstateRatingText = getChild("estate_rating"); - mEstateRatingIcon = getChild("estate_rating_icon"); - mEstateOwnerText = getChild("estate_owner"); - mCovenantText = getChild("covenant"); - - mSalesPriceText = getChild("sales_price"); - mAreaText = getChild("area"); - mTrafficText = getChild("traffic"); - mPrimitivesText = getChild("primitives"); - mParcelScriptsText = getChild("parcel_scripts"); - mTerraformLimitsText = getChild("terraform_limits"); - mSubdivideText = getChild("subdivide"); - mResaleText = getChild("resale"); - mSaleToText = getChild("sale_to"); - mAccordionCtrl = getChild("advanced_info_accordion"); - - icon_pg = getString("icon_PG"); - icon_m = getString("icon_M"); - icon_r = getString("icon_R"); - icon_voice = getString("icon_Voice"); - icon_voice_no = getString("icon_VoiceNo"); - icon_fly = getString("icon_Fly"); - icon_fly_no = getString("icon_FlyNo"); - icon_push = getString("icon_Push"); - icon_push_no = getString("icon_PushNo"); - icon_build = getString("icon_Build"); - icon_build_no = getString("icon_BuildNo"); - icon_scripts = getString("icon_Scripts"); - icon_scripts_no = getString("icon_ScriptsNo"); - icon_damage = getString("icon_Damage"); - icon_damage_no = getString("icon_DamageNo"); - icon_see_avs_on = getString("icon_SeeAVs_On"); - icon_see_avs_off = getString("icon_SeeAVs_Off"); - - mLastSelectedRegionID = LLUUID::null; - mNextCovenantUpdateTime = 0; - - return true; -} - -// virtual -void LLPanelPlaceProfile::resetLocation() -{ - LLPanelPlaceInfo::resetLocation(); - - mLastSelectedRegionID = LLUUID::null; - mNextCovenantUpdateTime = 0; - - mForSalePanel->setVisible(false); - mYouAreHerePanel->setVisible(false); - - std::string loading = LLTrans::getString("LoadingData"); - - mParcelRatingIcon->setValue(loading); - mParcelRatingText->setText(loading); - mVoiceIcon->setValue(loading); - mVoiceText->setText(loading); - mFlyIcon->setValue(loading); - mFlyText->setText(loading); - mPushIcon->setValue(loading); - mPushText->setText(loading); - mBuildIcon->setValue(loading); - mBuildText->setText(loading); - mScriptsIcon->setValue(loading); - mScriptsText->setText(loading); - mDamageIcon->setValue(loading); - mDamageText->setText(loading); - mSeeAVsIcon->setValue(loading); - mSeeAVsText->setText(loading); - - mRegionNameText->setValue(loading); - mRegionTypeText->setValue(loading); - mRegionRatingIcon->setValue(loading); - mRegionRatingText->setValue(loading); - mRegionOwnerText->setValue(loading); - mRegionGroupText->setValue(loading); - - mEstateNameText->setValue(loading); - mEstateRatingText->setValue(loading); - mEstateRatingIcon->setValue(loading); - mEstateOwnerText->setValue(loading); - mCovenantText->setValue(loading); - - mSalesPriceText->setValue(loading); - mAreaText->setValue(loading); - mTrafficText->setValue(loading); - mPrimitivesText->setValue(loading); - mParcelScriptsText->setValue(loading); - mTerraformLimitsText->setValue(loading); - mSubdivideText->setValue(loading); - mResaleText->setValue(loading); - mSaleToText->setValue(loading); - - getChild("sales_tab")->setVisible(true); -} - -// virtual -void LLPanelPlaceProfile::setInfoType(EInfoType type) -{ - bool is_info_type_agent = type == AGENT; - - mMaturityRatingIcon->setVisible(!is_info_type_agent); - mMaturityRatingText->setVisible(!is_info_type_agent); - - getChild("owner_label")->setVisible(is_info_type_agent); - mParcelOwner->setVisible(is_info_type_agent); - - getChild("advanced_info_accordion")->setVisible(is_info_type_agent); - - // If we came from search we want larger description area, approx. 10 lines (see STORM-1311). - // Don't use the maximum available space because that leads to nasty artifacts - // in text editor and expandable text box. - { - const S32 SEARCH_DESC_HEIGHT = 150; - - // Remember original geometry (once). - static const S32 sOrigDescVPad = getChildView("owner_label")->getRect().mBottom - mDescEditor->getRect().mTop; - static const S32 sOrigDescHeight = mDescEditor->getRect().getHeight(); - static const S32 sOrigMRIconVPad = mDescEditor->getRect().mBottom - mMaturityRatingIcon->getRect().mTop; - static const S32 sOrigMRTextVPad = mDescEditor->getRect().mBottom - mMaturityRatingText->getRect().mTop; - - // Resize the description. - const S32 desc_height = is_info_type_agent ? sOrigDescHeight : SEARCH_DESC_HEIGHT; - const S32 desc_top = getChildView("owner_label")->getRect().mBottom - sOrigDescVPad; - LLRect desc_rect = mDescEditor->getRect(); - desc_rect.setOriginAndSize(desc_rect.mLeft, desc_top - desc_height, desc_rect.getWidth(), desc_height); - mDescEditor->reshape(desc_rect.getWidth(), desc_rect.getHeight()); - mDescEditor->setRect(desc_rect); - mDescEditor->updateTextShape(); - - // Move the maturity rating icon/text accordingly. - const S32 mr_icon_bottom = mDescEditor->getRect().mBottom - sOrigMRIconVPad - mMaturityRatingIcon->getRect().getHeight(); - const S32 mr_text_bottom = mDescEditor->getRect().mBottom - sOrigMRTextVPad - mMaturityRatingText->getRect().getHeight(); - mMaturityRatingIcon->setOrigin(mMaturityRatingIcon->getRect().mLeft, mr_icon_bottom); - mMaturityRatingText->setOrigin(mMaturityRatingText->getRect().mLeft, mr_text_bottom); - } - - switch(type) - { - case AGENT: - case PLACE: - default: - mCurrentTitle = getString("title_place"); - break; - - case TELEPORT_HISTORY: - mCurrentTitle = getString("title_teleport_history"); - break; - } - - if (mAccordionCtrl != NULL) - { - mAccordionCtrl->expandDefaultTab(); - } - - LLPanelPlaceInfo::setInfoType(type); -} - -// virtual -void LLPanelPlaceProfile::processParcelInfo(const LLParcelData& parcel_data) -{ - LLPanelPlaceInfo::processParcelInfo(parcel_data); - - // HACK: Flag 0x2 == adult region, - // Flag 0x1 == mature region, otherwise assume PG - if (parcel_data.flags & 0x2) - { - mMaturityRatingIcon->setValue(icon_r); - mMaturityRatingText->setText(LLViewerRegion::accessToString(SIM_ACCESS_ADULT)); - } - else if (parcel_data.flags & 0x1) - { - mMaturityRatingIcon->setValue(icon_m); - mMaturityRatingText->setText(LLViewerRegion::accessToString(SIM_ACCESS_MATURE)); - } - else - { - mMaturityRatingIcon->setValue(icon_pg); - mMaturityRatingText->setText(LLViewerRegion::accessToString(SIM_ACCESS_PG)); - } -} - -// virtual -void LLPanelPlaceProfile::onVisibilityChange(bool new_visibility) -{ - LLPanel::onVisibilityChange(new_visibility); - - LLViewerParcelMgr* parcel_mgr = LLViewerParcelMgr::getInstance(); - if (!parcel_mgr) - return; - - // Remove land selection when panel hides. - if (!new_visibility) - { - if (!parcel_mgr->selectionEmpty()) - { - parcel_mgr->deselectUnused(); - } - } -} - -void LLPanelPlaceProfile::displaySelectedParcelInfo(LLParcel* parcel, - LLViewerRegion* region, - const LLVector3d& pos_global, - bool is_current_parcel) -{ - if (!region || !parcel) - return; - - if (mLastSelectedRegionID != region->getRegionID() - || mNextCovenantUpdateTime < LLTimer::getElapsedSeconds()) - { - // send EstateCovenantInfo message - // Note: LLPanelPlaceProfile doesn't change Covenant's content and any - // changes made by Estate floater should be requested by Estate floater - LLMessageSystem *msg = gMessageSystem; - msg->newMessage("EstateCovenantRequest"); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); - msg->sendReliable(region->getHost()); - mNextCovenantUpdateTime = LLTimer::getElapsedSeconds() + COVENANT_REFRESH_TIME_SEC; - } - - LLParcelData parcel_data; - - // HACK: Converting sim access flags to the format - // returned by remote parcel response. - U8 sim_access = region->getSimAccess(); - switch(sim_access) - { - case SIM_ACCESS_MATURE: - parcel_data.flags = 0x1; - - mParcelRatingIcon->setValue(icon_m); - mRegionRatingIcon->setValue(icon_m); - mEstateRatingIcon->setValue(icon_m); - break; - - case SIM_ACCESS_ADULT: - parcel_data.flags = 0x2; - - mParcelRatingIcon->setValue(icon_r); - mRegionRatingIcon->setValue(icon_r); - mEstateRatingIcon->setValue(icon_r); - break; - - default: - parcel_data.flags = 0; - - mParcelRatingIcon->setValue(icon_pg); - mRegionRatingIcon->setValue(icon_pg); - mEstateRatingIcon->setValue(icon_pg); - } - - std::string rating = LLViewerRegion::accessToString(sim_access); - mParcelRatingText->setText(rating); - mRegionRatingText->setText(rating); - - parcel_data.desc = parcel->getDesc(); - parcel_data.name = parcel->getName(); - parcel_data.sim_name = region->getName(); - parcel_data.snapshot_id = parcel->getSnapshotID(); - mPosRegion.setVec((F32)fmod(pos_global.mdV[VX], (F64)REGION_WIDTH_METERS), - (F32)fmod(pos_global.mdV[VY], (F64)REGION_WIDTH_METERS), - (F32)pos_global.mdV[VZ]); - parcel_data.global_x = pos_global.mdV[VX]; - parcel_data.global_y = pos_global.mdV[VY]; - parcel_data.global_z = pos_global.mdV[VZ]; - parcel_data.owner_id = parcel->getOwnerID(); - - std::string on = getString("on"); - std::string off = getString("off"); - - LLViewerParcelMgr* vpm = LLViewerParcelMgr::getInstance(); - - // Processing parcel characteristics - if (vpm->allowAgentVoice(region, parcel)) - { - mVoiceIcon->setValue(icon_voice); - mVoiceText->setText(on); - } - else - { - mVoiceIcon->setValue(icon_voice_no); - mVoiceText->setText(off); - } - - if (vpm->allowAgentFly(region, parcel)) - { - mFlyIcon->setValue(icon_fly); - mFlyText->setText(on); - } - else - { - mFlyIcon->setValue(icon_fly_no); - mFlyText->setText(off); - } - - if (vpm->allowAgentPush(region, parcel)) - { - mPushIcon->setValue(icon_push); - mPushText->setText(on); - } - else - { - mPushIcon->setValue(icon_push_no); - mPushText->setText(off); - } - - if (vpm->allowAgentBuild(parcel)) - { - mBuildIcon->setValue(icon_build); - mBuildText->setText(on); - } - else - { - mBuildIcon->setValue(icon_build_no); - mBuildText->setText(off); - } - - if (vpm->allowAgentScripts(region, parcel)) - { - mScriptsIcon->setValue(icon_scripts); - mScriptsText->setText(on); - } - else - { - mScriptsIcon->setValue(icon_scripts_no); - mScriptsText->setText(off); - } - - if (vpm->allowAgentDamage(region, parcel)) - { - mDamageIcon->setValue(icon_damage); - mDamageText->setText(on); - } - else - { - mDamageIcon->setValue(icon_damage_no); - mDamageText->setText(off); - } - - if (parcel->getSeeAVs()) - { - mSeeAVsIcon->setValue(icon_see_avs_on); - mSeeAVsText->setText(on); - } - else - { - mSeeAVsIcon->setValue(icon_see_avs_off); - mSeeAVsText->setText(off); - } - - mRegionNameText->setText(region->getName()); - mRegionTypeText->setText(region->getLocalizedSimProductName()); - - // Determine parcel owner - if (parcel->isPublic()) - { - mParcelOwner->setText(getString("public")); - mRegionOwnerText->setText(getString("public")); - } - else - { - if (parcel->getIsGroupOwned()) - { - mRegionOwnerText->setText(getString("group_owned_text")); - - if(!parcel->getGroupID().isNull()) - { - std::string owner = - LLSLURL("group", parcel->getGroupID(), "inspect").getSLURLString(); - mParcelOwner->setText(owner); - } - else - { - std::string owner = getString("none_text"); - mRegionGroupText->setText(owner); - mParcelOwner->setText(owner); - } - } - else - { - // Figure out the owner's name - std::string parcel_owner = - LLSLURL("agent", parcel->getOwnerID(), "inspect").getSLURLString(); - mParcelOwner->setText(parcel_owner); - LLAvatarNameCache::get(region->getOwner(), boost::bind(&LLPanelPlaceInfo::onAvatarNameCache, _1, _2, mRegionOwnerText)); - mRegionGroupText->setText( getString("none_text")); - } - - if(LLParcel::OS_LEASE_PENDING == parcel->getOwnershipStatus()) - { - mRegionOwnerText->setText(mRegionOwnerText->getText() + getString("sale_pending_text")); - } - - if(!parcel->getGroupID().isNull()) - { - // FIXME: Using parcel group as region group. - gCacheName->getGroup(parcel->getGroupID(), - boost::bind(&LLPanelPlaceInfo::onNameCache, mRegionGroupText, _2)); - } - } - - mEstateRatingText->setText(region->getSimAccessString()); - - S32 area; - S32 claim_price; - S32 rent_price; - F32 dwell; - bool for_sale; - vpm->getDisplayInfo(&area, &claim_price, &rent_price, &for_sale, &dwell); - mForSalePanel->setVisible(for_sale); - if (for_sale) - { - const LLUUID& auth_buyer_id = parcel->getAuthorizedBuyerID(); - if(auth_buyer_id.notNull()) - { - LLAvatarNameCache::get(auth_buyer_id, boost::bind(&LLPanelPlaceInfo::onAvatarNameCache, _1, _2, mSaleToText)); - - // Show sales info to a specific person or a group he belongs to. - if (auth_buyer_id != gAgent.getID() && !gAgent.isInGroup(auth_buyer_id)) - { - for_sale = false; - } - } - else - { - mSaleToText->setText(getString("anyone")); - } - - mSalesPriceText->setText(llformat("%s%d ", getString("price_text").c_str(), parcel->getSalePrice())); - mAreaText->setText(llformat("%d %s", area, getString("area_text").c_str())); - mTrafficText->setText(llformat("%.0f", dwell)); - - // Can't have more than region max tasks, regardless of parcel - // object bonus factor. - S32 primitives = llmin(ll_round(parcel->getMaxPrimCapacity() * parcel->getParcelPrimBonus()), - (S32)region->getMaxTasks()); - - mPrimitivesText->setText(llformat("%d %s, %d %s", primitives, getString("available").c_str(), parcel->getPrimCount(), getString("allocated").c_str())); - - if (parcel->getAllowOtherScripts()) - { - mParcelScriptsText->setText(getString("all_residents_text")); - } - else if (parcel->getAllowGroupScripts()) - { - mParcelScriptsText->setText(getString("group_text")); - } - else - { - mParcelScriptsText->setText(off); - } - - mTerraformLimitsText->setText(parcel->getAllowTerraform() ? on : off); - - if (region->getRegionFlag(REGION_FLAGS_ALLOW_PARCEL_CHANGES)) - { - mSubdivideText->setText(getString("can_change")); - } - else - { - mSubdivideText->setText(getString("can_not_change")); - } - if (region->getRegionFlag(REGION_FLAGS_BLOCK_LAND_RESELL)) - { - mResaleText->setText(getString("can_not_resell")); - } - else - { - mResaleText->setText(getString("can_resell")); - } - } - - mSelectedParcelID = parcel->getLocalID(); - mLastSelectedRegionID = region->getRegionID(); - LLPanelPlaceInfo::processParcelInfo(parcel_data); - - mYouAreHerePanel->setVisible(is_current_parcel); - getChild("sales_tab")->setVisible(for_sale); - mAccordionCtrl->arrange(); -} - -void LLPanelPlaceProfile::updateEstateName(const std::string& name) -{ - mEstateNameText->setText(name); -} - -void LLPanelPlaceProfile::updateEstateOwnerName(const std::string& name) -{ - mEstateOwnerText->setText(name); -} - -void LLPanelPlaceProfile::updateCovenantText(const std::string &text) -{ - mCovenantText->setText(text); -} - -void LLPanelPlaceProfile::onForSaleBannerClick() -{ - LLViewerParcelMgr* mgr = LLViewerParcelMgr::getInstance(); - LLParcel* parcel = mgr->getFloatingParcelSelection()->getParcel(); - LLViewerRegion* selected_region = mgr->getSelectionRegion(); - if(parcel && selected_region) - { - if(parcel->getLocalID() == mSelectedParcelID && - mLastSelectedRegionID ==selected_region->getRegionID()) - { - S32 price = parcel->getSalePrice(); - - if(price - gStatusBar->getBalance() > 0) - { - LLStringUtil::format_map_t args; - args["AMOUNT"] = llformat("%d", price); - LLBuyCurrencyHTML::openCurrencyFloater( LLTrans::getString("buying_selected_land", args), price ); - } - else - { - LLViewerParcelMgr::getInstance()->startBuyLand(); - } - } - else - { - LL_WARNS("Places") << "User is trying to buy remote parcel.Operation is not supported"<< LL_ENDL; - } - - } -} - -// static -void LLPanelPlaceProfile::updateYouAreHereBanner(void* userdata) -{ - //YouAreHere Banner should be displayed only for selected places, - // If you want to display it for landmark or teleport history item, you should check by mParcelId - - LLPanelPlaceProfile* self = static_cast(userdata); - if(!self->getVisible()) - return; - - if(!gDisconnected && gAgent.getRegion()) - { - static F32 radius = gSavedSettings.getF32("YouAreHereDistance"); - - bool display_banner = gAgent.getRegion()->getRegionID() == self->mLastSelectedRegionID && - LLAgentUI::checkAgentDistance(self->mPosRegion, radius); - - self->mYouAreHerePanel->setVisible(display_banner); - } -} +/** + * @file llpanelplaceprofile.cpp + * @brief Displays place profile in Side Tray. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelplaceprofile.h" + +#include "llavatarnamecache.h" +#include "llparcel.h" +#include "message.h" + +#include "llexpandabletextbox.h" +#include "lliconctrl.h" +#include "lllineeditor.h" +#include "lltextbox.h" +#include "lltexteditor.h" + +#include "lltrans.h" + +#include "llaccordionctrl.h" +#include "llaccordionctrltab.h" +#include "llagent.h" +#include "llagentui.h" +#include "llappviewer.h" +#include "llcallbacklist.h" +#include "llbuycurrencyhtml.h" +#include "llslurl.h" +#include "llstatusbar.h" +#include "llviewercontrol.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" + +const F64 COVENANT_REFRESH_TIME_SEC = 60.0f; + +static LLPanelInjector t_place_profile("panel_place_profile"); + +// Statics for textures filenames +static std::string icon_pg; +static std::string icon_m; +static std::string icon_r; +static std::string icon_voice; +static std::string icon_voice_no; +static std::string icon_fly; +static std::string icon_fly_no; +static std::string icon_push; +static std::string icon_push_no; +static std::string icon_build; +static std::string icon_build_no; +static std::string icon_scripts; +static std::string icon_scripts_no; +static std::string icon_damage; +static std::string icon_damage_no; +static std::string icon_see_avs_on; +static std::string icon_see_avs_off; + +LLPanelPlaceProfile::LLPanelPlaceProfile() +: LLPanelPlaceInfo(), + mNextCovenantUpdateTime(0), + mForSalePanel(NULL), + mYouAreHerePanel(NULL), + mSelectedParcelID(-1), + mAccordionCtrl(NULL) +{} + +// virtual +LLPanelPlaceProfile::~LLPanelPlaceProfile() +{ + gIdleCallbacks.deleteFunction(&LLPanelPlaceProfile::updateYouAreHereBanner, this); +} + +// virtual +bool LLPanelPlaceProfile::postBuild() +{ + LLPanelPlaceInfo::postBuild(); + + mForSalePanel = getChild("for_sale_panel"); + mYouAreHerePanel = getChild("here_panel"); + gIdleCallbacks.addFunction(&LLPanelPlaceProfile::updateYouAreHereBanner, this); + + //Icon value should contain sale price of last selected parcel. + mForSalePanel->getChild("icon_for_sale")-> + setMouseDownCallback(boost::bind(&LLPanelPlaceProfile::onForSaleBannerClick, this)); + + mParcelRatingIcon = getChild("rating_icon"); + mParcelRatingText = getChild("rating_value"); + mVoiceIcon = getChild("voice_icon"); + mVoiceText = getChild("voice_value"); + mFlyIcon = getChild("fly_icon"); + mFlyText = getChild("fly_value"); + mPushIcon = getChild("push_icon"); + mPushText = getChild("push_value"); + mBuildIcon = getChild("build_icon"); + mBuildText = getChild("build_value"); + mScriptsIcon = getChild("scripts_icon"); + mScriptsText = getChild("scripts_value"); + mDamageIcon = getChild("damage_icon"); + mDamageText = getChild("damage_value"); + mSeeAVsIcon = getChild("see_avatars_icon"); + mSeeAVsText = getChild("see_avatars_value"); + + mRegionNameText = getChild("region_name"); + mRegionTypeText = getChild("region_type"); + mRegionRatingIcon = getChild("region_rating_icon"); + mRegionRatingText = getChild("region_rating"); + mRegionOwnerText = getChild("region_owner"); + mRegionGroupText = getChild("region_group"); + + mEstateNameText = getChild("estate_name"); + mEstateRatingText = getChild("estate_rating"); + mEstateRatingIcon = getChild("estate_rating_icon"); + mEstateOwnerText = getChild("estate_owner"); + mCovenantText = getChild("covenant"); + + mSalesPriceText = getChild("sales_price"); + mAreaText = getChild("area"); + mTrafficText = getChild("traffic"); + mPrimitivesText = getChild("primitives"); + mParcelScriptsText = getChild("parcel_scripts"); + mTerraformLimitsText = getChild("terraform_limits"); + mSubdivideText = getChild("subdivide"); + mResaleText = getChild("resale"); + mSaleToText = getChild("sale_to"); + mAccordionCtrl = getChild("advanced_info_accordion"); + + icon_pg = getString("icon_PG"); + icon_m = getString("icon_M"); + icon_r = getString("icon_R"); + icon_voice = getString("icon_Voice"); + icon_voice_no = getString("icon_VoiceNo"); + icon_fly = getString("icon_Fly"); + icon_fly_no = getString("icon_FlyNo"); + icon_push = getString("icon_Push"); + icon_push_no = getString("icon_PushNo"); + icon_build = getString("icon_Build"); + icon_build_no = getString("icon_BuildNo"); + icon_scripts = getString("icon_Scripts"); + icon_scripts_no = getString("icon_ScriptsNo"); + icon_damage = getString("icon_Damage"); + icon_damage_no = getString("icon_DamageNo"); + icon_see_avs_on = getString("icon_SeeAVs_On"); + icon_see_avs_off = getString("icon_SeeAVs_Off"); + + mLastSelectedRegionID = LLUUID::null; + mNextCovenantUpdateTime = 0; + + return true; +} + +// virtual +void LLPanelPlaceProfile::resetLocation() +{ + LLPanelPlaceInfo::resetLocation(); + + mLastSelectedRegionID = LLUUID::null; + mNextCovenantUpdateTime = 0; + + mForSalePanel->setVisible(false); + mYouAreHerePanel->setVisible(false); + + std::string loading = LLTrans::getString("LoadingData"); + + mParcelRatingIcon->setValue(loading); + mParcelRatingText->setText(loading); + mVoiceIcon->setValue(loading); + mVoiceText->setText(loading); + mFlyIcon->setValue(loading); + mFlyText->setText(loading); + mPushIcon->setValue(loading); + mPushText->setText(loading); + mBuildIcon->setValue(loading); + mBuildText->setText(loading); + mScriptsIcon->setValue(loading); + mScriptsText->setText(loading); + mDamageIcon->setValue(loading); + mDamageText->setText(loading); + mSeeAVsIcon->setValue(loading); + mSeeAVsText->setText(loading); + + mRegionNameText->setValue(loading); + mRegionTypeText->setValue(loading); + mRegionRatingIcon->setValue(loading); + mRegionRatingText->setValue(loading); + mRegionOwnerText->setValue(loading); + mRegionGroupText->setValue(loading); + + mEstateNameText->setValue(loading); + mEstateRatingText->setValue(loading); + mEstateRatingIcon->setValue(loading); + mEstateOwnerText->setValue(loading); + mCovenantText->setValue(loading); + + mSalesPriceText->setValue(loading); + mAreaText->setValue(loading); + mTrafficText->setValue(loading); + mPrimitivesText->setValue(loading); + mParcelScriptsText->setValue(loading); + mTerraformLimitsText->setValue(loading); + mSubdivideText->setValue(loading); + mResaleText->setValue(loading); + mSaleToText->setValue(loading); + + getChild("sales_tab")->setVisible(true); +} + +// virtual +void LLPanelPlaceProfile::setInfoType(EInfoType type) +{ + bool is_info_type_agent = type == AGENT; + + mMaturityRatingIcon->setVisible(!is_info_type_agent); + mMaturityRatingText->setVisible(!is_info_type_agent); + + getChild("owner_label")->setVisible(is_info_type_agent); + mParcelOwner->setVisible(is_info_type_agent); + + getChild("advanced_info_accordion")->setVisible(is_info_type_agent); + + // If we came from search we want larger description area, approx. 10 lines (see STORM-1311). + // Don't use the maximum available space because that leads to nasty artifacts + // in text editor and expandable text box. + { + const S32 SEARCH_DESC_HEIGHT = 150; + + // Remember original geometry (once). + static const S32 sOrigDescVPad = getChildView("owner_label")->getRect().mBottom - mDescEditor->getRect().mTop; + static const S32 sOrigDescHeight = mDescEditor->getRect().getHeight(); + static const S32 sOrigMRIconVPad = mDescEditor->getRect().mBottom - mMaturityRatingIcon->getRect().mTop; + static const S32 sOrigMRTextVPad = mDescEditor->getRect().mBottom - mMaturityRatingText->getRect().mTop; + + // Resize the description. + const S32 desc_height = is_info_type_agent ? sOrigDescHeight : SEARCH_DESC_HEIGHT; + const S32 desc_top = getChildView("owner_label")->getRect().mBottom - sOrigDescVPad; + LLRect desc_rect = mDescEditor->getRect(); + desc_rect.setOriginAndSize(desc_rect.mLeft, desc_top - desc_height, desc_rect.getWidth(), desc_height); + mDescEditor->reshape(desc_rect.getWidth(), desc_rect.getHeight()); + mDescEditor->setRect(desc_rect); + mDescEditor->updateTextShape(); + + // Move the maturity rating icon/text accordingly. + const S32 mr_icon_bottom = mDescEditor->getRect().mBottom - sOrigMRIconVPad - mMaturityRatingIcon->getRect().getHeight(); + const S32 mr_text_bottom = mDescEditor->getRect().mBottom - sOrigMRTextVPad - mMaturityRatingText->getRect().getHeight(); + mMaturityRatingIcon->setOrigin(mMaturityRatingIcon->getRect().mLeft, mr_icon_bottom); + mMaturityRatingText->setOrigin(mMaturityRatingText->getRect().mLeft, mr_text_bottom); + } + + switch(type) + { + case AGENT: + case PLACE: + default: + mCurrentTitle = getString("title_place"); + break; + + case TELEPORT_HISTORY: + mCurrentTitle = getString("title_teleport_history"); + break; + } + + if (mAccordionCtrl != NULL) + { + mAccordionCtrl->expandDefaultTab(); + } + + LLPanelPlaceInfo::setInfoType(type); +} + +// virtual +void LLPanelPlaceProfile::processParcelInfo(const LLParcelData& parcel_data) +{ + LLPanelPlaceInfo::processParcelInfo(parcel_data); + + // HACK: Flag 0x2 == adult region, + // Flag 0x1 == mature region, otherwise assume PG + if (parcel_data.flags & 0x2) + { + mMaturityRatingIcon->setValue(icon_r); + mMaturityRatingText->setText(LLViewerRegion::accessToString(SIM_ACCESS_ADULT)); + } + else if (parcel_data.flags & 0x1) + { + mMaturityRatingIcon->setValue(icon_m); + mMaturityRatingText->setText(LLViewerRegion::accessToString(SIM_ACCESS_MATURE)); + } + else + { + mMaturityRatingIcon->setValue(icon_pg); + mMaturityRatingText->setText(LLViewerRegion::accessToString(SIM_ACCESS_PG)); + } +} + +// virtual +void LLPanelPlaceProfile::onVisibilityChange(bool new_visibility) +{ + LLPanel::onVisibilityChange(new_visibility); + + LLViewerParcelMgr* parcel_mgr = LLViewerParcelMgr::getInstance(); + if (!parcel_mgr) + return; + + // Remove land selection when panel hides. + if (!new_visibility) + { + if (!parcel_mgr->selectionEmpty()) + { + parcel_mgr->deselectUnused(); + } + } +} + +void LLPanelPlaceProfile::displaySelectedParcelInfo(LLParcel* parcel, + LLViewerRegion* region, + const LLVector3d& pos_global, + bool is_current_parcel) +{ + if (!region || !parcel) + return; + + if (mLastSelectedRegionID != region->getRegionID() + || mNextCovenantUpdateTime < LLTimer::getElapsedSeconds()) + { + // send EstateCovenantInfo message + // Note: LLPanelPlaceProfile doesn't change Covenant's content and any + // changes made by Estate floater should be requested by Estate floater + LLMessageSystem *msg = gMessageSystem; + msg->newMessage("EstateCovenantRequest"); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID,gAgent.getSessionID()); + msg->sendReliable(region->getHost()); + mNextCovenantUpdateTime = LLTimer::getElapsedSeconds() + COVENANT_REFRESH_TIME_SEC; + } + + LLParcelData parcel_data; + + // HACK: Converting sim access flags to the format + // returned by remote parcel response. + U8 sim_access = region->getSimAccess(); + switch(sim_access) + { + case SIM_ACCESS_MATURE: + parcel_data.flags = 0x1; + + mParcelRatingIcon->setValue(icon_m); + mRegionRatingIcon->setValue(icon_m); + mEstateRatingIcon->setValue(icon_m); + break; + + case SIM_ACCESS_ADULT: + parcel_data.flags = 0x2; + + mParcelRatingIcon->setValue(icon_r); + mRegionRatingIcon->setValue(icon_r); + mEstateRatingIcon->setValue(icon_r); + break; + + default: + parcel_data.flags = 0; + + mParcelRatingIcon->setValue(icon_pg); + mRegionRatingIcon->setValue(icon_pg); + mEstateRatingIcon->setValue(icon_pg); + } + + std::string rating = LLViewerRegion::accessToString(sim_access); + mParcelRatingText->setText(rating); + mRegionRatingText->setText(rating); + + parcel_data.desc = parcel->getDesc(); + parcel_data.name = parcel->getName(); + parcel_data.sim_name = region->getName(); + parcel_data.snapshot_id = parcel->getSnapshotID(); + mPosRegion.setVec((F32)fmod(pos_global.mdV[VX], (F64)REGION_WIDTH_METERS), + (F32)fmod(pos_global.mdV[VY], (F64)REGION_WIDTH_METERS), + (F32)pos_global.mdV[VZ]); + parcel_data.global_x = pos_global.mdV[VX]; + parcel_data.global_y = pos_global.mdV[VY]; + parcel_data.global_z = pos_global.mdV[VZ]; + parcel_data.owner_id = parcel->getOwnerID(); + + std::string on = getString("on"); + std::string off = getString("off"); + + LLViewerParcelMgr* vpm = LLViewerParcelMgr::getInstance(); + + // Processing parcel characteristics + if (vpm->allowAgentVoice(region, parcel)) + { + mVoiceIcon->setValue(icon_voice); + mVoiceText->setText(on); + } + else + { + mVoiceIcon->setValue(icon_voice_no); + mVoiceText->setText(off); + } + + if (vpm->allowAgentFly(region, parcel)) + { + mFlyIcon->setValue(icon_fly); + mFlyText->setText(on); + } + else + { + mFlyIcon->setValue(icon_fly_no); + mFlyText->setText(off); + } + + if (vpm->allowAgentPush(region, parcel)) + { + mPushIcon->setValue(icon_push); + mPushText->setText(on); + } + else + { + mPushIcon->setValue(icon_push_no); + mPushText->setText(off); + } + + if (vpm->allowAgentBuild(parcel)) + { + mBuildIcon->setValue(icon_build); + mBuildText->setText(on); + } + else + { + mBuildIcon->setValue(icon_build_no); + mBuildText->setText(off); + } + + if (vpm->allowAgentScripts(region, parcel)) + { + mScriptsIcon->setValue(icon_scripts); + mScriptsText->setText(on); + } + else + { + mScriptsIcon->setValue(icon_scripts_no); + mScriptsText->setText(off); + } + + if (vpm->allowAgentDamage(region, parcel)) + { + mDamageIcon->setValue(icon_damage); + mDamageText->setText(on); + } + else + { + mDamageIcon->setValue(icon_damage_no); + mDamageText->setText(off); + } + + if (parcel->getSeeAVs()) + { + mSeeAVsIcon->setValue(icon_see_avs_on); + mSeeAVsText->setText(on); + } + else + { + mSeeAVsIcon->setValue(icon_see_avs_off); + mSeeAVsText->setText(off); + } + + mRegionNameText->setText(region->getName()); + mRegionTypeText->setText(region->getLocalizedSimProductName()); + + // Determine parcel owner + if (parcel->isPublic()) + { + mParcelOwner->setText(getString("public")); + mRegionOwnerText->setText(getString("public")); + } + else + { + if (parcel->getIsGroupOwned()) + { + mRegionOwnerText->setText(getString("group_owned_text")); + + if(!parcel->getGroupID().isNull()) + { + std::string owner = + LLSLURL("group", parcel->getGroupID(), "inspect").getSLURLString(); + mParcelOwner->setText(owner); + } + else + { + std::string owner = getString("none_text"); + mRegionGroupText->setText(owner); + mParcelOwner->setText(owner); + } + } + else + { + // Figure out the owner's name + std::string parcel_owner = + LLSLURL("agent", parcel->getOwnerID(), "inspect").getSLURLString(); + mParcelOwner->setText(parcel_owner); + LLAvatarNameCache::get(region->getOwner(), boost::bind(&LLPanelPlaceInfo::onAvatarNameCache, _1, _2, mRegionOwnerText)); + mRegionGroupText->setText( getString("none_text")); + } + + if(LLParcel::OS_LEASE_PENDING == parcel->getOwnershipStatus()) + { + mRegionOwnerText->setText(mRegionOwnerText->getText() + getString("sale_pending_text")); + } + + if(!parcel->getGroupID().isNull()) + { + // FIXME: Using parcel group as region group. + gCacheName->getGroup(parcel->getGroupID(), + boost::bind(&LLPanelPlaceInfo::onNameCache, mRegionGroupText, _2)); + } + } + + mEstateRatingText->setText(region->getSimAccessString()); + + S32 area; + S32 claim_price; + S32 rent_price; + F32 dwell; + bool for_sale; + vpm->getDisplayInfo(&area, &claim_price, &rent_price, &for_sale, &dwell); + mForSalePanel->setVisible(for_sale); + if (for_sale) + { + const LLUUID& auth_buyer_id = parcel->getAuthorizedBuyerID(); + if(auth_buyer_id.notNull()) + { + LLAvatarNameCache::get(auth_buyer_id, boost::bind(&LLPanelPlaceInfo::onAvatarNameCache, _1, _2, mSaleToText)); + + // Show sales info to a specific person or a group he belongs to. + if (auth_buyer_id != gAgent.getID() && !gAgent.isInGroup(auth_buyer_id)) + { + for_sale = false; + } + } + else + { + mSaleToText->setText(getString("anyone")); + } + + mSalesPriceText->setText(llformat("%s%d ", getString("price_text").c_str(), parcel->getSalePrice())); + mAreaText->setText(llformat("%d %s", area, getString("area_text").c_str())); + mTrafficText->setText(llformat("%.0f", dwell)); + + // Can't have more than region max tasks, regardless of parcel + // object bonus factor. + S32 primitives = llmin(ll_round(parcel->getMaxPrimCapacity() * parcel->getParcelPrimBonus()), + (S32)region->getMaxTasks()); + + mPrimitivesText->setText(llformat("%d %s, %d %s", primitives, getString("available").c_str(), parcel->getPrimCount(), getString("allocated").c_str())); + + if (parcel->getAllowOtherScripts()) + { + mParcelScriptsText->setText(getString("all_residents_text")); + } + else if (parcel->getAllowGroupScripts()) + { + mParcelScriptsText->setText(getString("group_text")); + } + else + { + mParcelScriptsText->setText(off); + } + + mTerraformLimitsText->setText(parcel->getAllowTerraform() ? on : off); + + if (region->getRegionFlag(REGION_FLAGS_ALLOW_PARCEL_CHANGES)) + { + mSubdivideText->setText(getString("can_change")); + } + else + { + mSubdivideText->setText(getString("can_not_change")); + } + if (region->getRegionFlag(REGION_FLAGS_BLOCK_LAND_RESELL)) + { + mResaleText->setText(getString("can_not_resell")); + } + else + { + mResaleText->setText(getString("can_resell")); + } + } + + mSelectedParcelID = parcel->getLocalID(); + mLastSelectedRegionID = region->getRegionID(); + LLPanelPlaceInfo::processParcelInfo(parcel_data); + + mYouAreHerePanel->setVisible(is_current_parcel); + getChild("sales_tab")->setVisible(for_sale); + mAccordionCtrl->arrange(); +} + +void LLPanelPlaceProfile::updateEstateName(const std::string& name) +{ + mEstateNameText->setText(name); +} + +void LLPanelPlaceProfile::updateEstateOwnerName(const std::string& name) +{ + mEstateOwnerText->setText(name); +} + +void LLPanelPlaceProfile::updateCovenantText(const std::string &text) +{ + mCovenantText->setText(text); +} + +void LLPanelPlaceProfile::onForSaleBannerClick() +{ + LLViewerParcelMgr* mgr = LLViewerParcelMgr::getInstance(); + LLParcel* parcel = mgr->getFloatingParcelSelection()->getParcel(); + LLViewerRegion* selected_region = mgr->getSelectionRegion(); + if(parcel && selected_region) + { + if(parcel->getLocalID() == mSelectedParcelID && + mLastSelectedRegionID ==selected_region->getRegionID()) + { + S32 price = parcel->getSalePrice(); + + if(price - gStatusBar->getBalance() > 0) + { + LLStringUtil::format_map_t args; + args["AMOUNT"] = llformat("%d", price); + LLBuyCurrencyHTML::openCurrencyFloater( LLTrans::getString("buying_selected_land", args), price ); + } + else + { + LLViewerParcelMgr::getInstance()->startBuyLand(); + } + } + else + { + LL_WARNS("Places") << "User is trying to buy remote parcel.Operation is not supported"<< LL_ENDL; + } + + } +} + +// static +void LLPanelPlaceProfile::updateYouAreHereBanner(void* userdata) +{ + //YouAreHere Banner should be displayed only for selected places, + // If you want to display it for landmark or teleport history item, you should check by mParcelId + + LLPanelPlaceProfile* self = static_cast(userdata); + if(!self->getVisible()) + return; + + if(!gDisconnected && gAgent.getRegion()) + { + static F32 radius = gSavedSettings.getF32("YouAreHereDistance"); + + bool display_banner = gAgent.getRegion()->getRegionID() == self->mLastSelectedRegionID && + LLAgentUI::checkAgentDistance(self->mPosRegion, radius); + + self->mYouAreHerePanel->setVisible(display_banner); + } +} diff --git a/indra/newview/llpanelplaceprofile.h b/indra/newview/llpanelplaceprofile.h index 836c8baa32..45a20fb86a 100644 --- a/indra/newview/llpanelplaceprofile.h +++ b/indra/newview/llpanelplaceprofile.h @@ -1,121 +1,121 @@ -/** - * @file llpanelplaceprofile.h - * @brief Displays place profile in Side Tray. - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELPLACEPROFILE_H -#define LL_LLPANELPLACEPROFILE_H - -#include "llpanelplaceinfo.h" - -class LLAccordionCtrl; -class LLIconCtrl; -class LLTextEditor; - -class LLPanelPlaceProfile : public LLPanelPlaceInfo -{ -public: - LLPanelPlaceProfile(); - /*virtual*/ ~LLPanelPlaceProfile(); - - /*virtual*/ bool postBuild(); - - /*virtual*/ void resetLocation(); - - /*virtual*/ void setInfoType(EInfoType type); - - /*virtual*/ void processParcelInfo(const LLParcelData& parcel_data); - - /*virtual*/ void onVisibilityChange(bool new_visibility); - - // Displays information about the currently selected parcel - // without sending a request to the server. - // If is_current_parcel true shows "You Are Here" banner. - void displaySelectedParcelInfo(LLParcel* parcel, - LLViewerRegion* region, - const LLVector3d& pos_global, - bool is_current_parcel); - - void updateEstateName(const std::string& name); - void updateEstateOwnerName(const std::string& name); - void updateCovenantText(const std::string &text); - -private: - void onForSaleBannerClick(); - - static void updateYouAreHereBanner(void*);// added to gIdleCallbacks - - /** - * Holds last displayed parcel. Needed for YouAreHere banner. - */ - S32 mSelectedParcelID; - LLUUID mLastSelectedRegionID; - F64 mNextCovenantUpdateTime; //seconds since client start - - LLPanel* mForSalePanel; - LLPanel* mYouAreHerePanel; - - LLIconCtrl* mParcelRatingIcon; - LLTextBox* mParcelRatingText; - LLIconCtrl* mVoiceIcon; - LLTextBox* mVoiceText; - LLIconCtrl* mFlyIcon; - LLTextBox* mFlyText; - LLIconCtrl* mPushIcon; - LLTextBox* mPushText; - LLIconCtrl* mBuildIcon; - LLTextBox* mBuildText; - LLIconCtrl* mScriptsIcon; - LLTextBox* mScriptsText; - LLIconCtrl* mDamageIcon; - LLTextBox* mDamageText; - LLIconCtrl* mSeeAVsIcon; - LLTextBox* mSeeAVsText; - - LLTextBox* mRegionNameText; - LLTextBox* mRegionTypeText; - LLIconCtrl* mRegionRatingIcon; - LLTextBox* mRegionRatingText; - LLTextBox* mRegionOwnerText; - LLTextBox* mRegionGroupText; - - LLTextBox* mEstateNameText; - LLTextBox* mEstateRatingText; - LLIconCtrl* mEstateRatingIcon; - LLTextBox* mEstateOwnerText; - LLTextEditor* mCovenantText; - - LLTextBox* mSalesPriceText; - LLTextBox* mAreaText; - LLTextBox* mTrafficText; - LLTextBox* mPrimitivesText; - LLTextBox* mParcelScriptsText; - LLTextBox* mTerraformLimitsText; - LLTextEditor* mSubdivideText; - LLTextEditor* mResaleText; - LLTextBox* mSaleToText; - LLAccordionCtrl* mAccordionCtrl; -}; - -#endif // LL_LLPANELPLACEPROFILE_H +/** + * @file llpanelplaceprofile.h + * @brief Displays place profile in Side Tray. + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELPLACEPROFILE_H +#define LL_LLPANELPLACEPROFILE_H + +#include "llpanelplaceinfo.h" + +class LLAccordionCtrl; +class LLIconCtrl; +class LLTextEditor; + +class LLPanelPlaceProfile : public LLPanelPlaceInfo +{ +public: + LLPanelPlaceProfile(); + /*virtual*/ ~LLPanelPlaceProfile(); + + /*virtual*/ bool postBuild(); + + /*virtual*/ void resetLocation(); + + /*virtual*/ void setInfoType(EInfoType type); + + /*virtual*/ void processParcelInfo(const LLParcelData& parcel_data); + + /*virtual*/ void onVisibilityChange(bool new_visibility); + + // Displays information about the currently selected parcel + // without sending a request to the server. + // If is_current_parcel true shows "You Are Here" banner. + void displaySelectedParcelInfo(LLParcel* parcel, + LLViewerRegion* region, + const LLVector3d& pos_global, + bool is_current_parcel); + + void updateEstateName(const std::string& name); + void updateEstateOwnerName(const std::string& name); + void updateCovenantText(const std::string &text); + +private: + void onForSaleBannerClick(); + + static void updateYouAreHereBanner(void*);// added to gIdleCallbacks + + /** + * Holds last displayed parcel. Needed for YouAreHere banner. + */ + S32 mSelectedParcelID; + LLUUID mLastSelectedRegionID; + F64 mNextCovenantUpdateTime; //seconds since client start + + LLPanel* mForSalePanel; + LLPanel* mYouAreHerePanel; + + LLIconCtrl* mParcelRatingIcon; + LLTextBox* mParcelRatingText; + LLIconCtrl* mVoiceIcon; + LLTextBox* mVoiceText; + LLIconCtrl* mFlyIcon; + LLTextBox* mFlyText; + LLIconCtrl* mPushIcon; + LLTextBox* mPushText; + LLIconCtrl* mBuildIcon; + LLTextBox* mBuildText; + LLIconCtrl* mScriptsIcon; + LLTextBox* mScriptsText; + LLIconCtrl* mDamageIcon; + LLTextBox* mDamageText; + LLIconCtrl* mSeeAVsIcon; + LLTextBox* mSeeAVsText; + + LLTextBox* mRegionNameText; + LLTextBox* mRegionTypeText; + LLIconCtrl* mRegionRatingIcon; + LLTextBox* mRegionRatingText; + LLTextBox* mRegionOwnerText; + LLTextBox* mRegionGroupText; + + LLTextBox* mEstateNameText; + LLTextBox* mEstateRatingText; + LLIconCtrl* mEstateRatingIcon; + LLTextBox* mEstateOwnerText; + LLTextEditor* mCovenantText; + + LLTextBox* mSalesPriceText; + LLTextBox* mAreaText; + LLTextBox* mTrafficText; + LLTextBox* mPrimitivesText; + LLTextBox* mParcelScriptsText; + LLTextBox* mTerraformLimitsText; + LLTextEditor* mSubdivideText; + LLTextEditor* mResaleText; + LLTextBox* mSaleToText; + LLAccordionCtrl* mAccordionCtrl; +}; + +#endif // LL_LLPANELPLACEPROFILE_H diff --git a/indra/newview/llpanelplaces.cpp b/indra/newview/llpanelplaces.cpp index 6baa223389..7deb1d9fd4 100644 --- a/indra/newview/llpanelplaces.cpp +++ b/indra/newview/llpanelplaces.cpp @@ -1,1358 +1,1358 @@ -/** - * @file llpanelplaces.cpp - * @brief Side Bar "Places" panel - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelplaces.h" - -#include "llassettype.h" -#include "lltimer.h" - -#include "llinventory.h" -#include "lllandmark.h" -#include "llparcel.h" - -#include "llcombobox.h" -#include "llfiltereditor.h" -#include "llfirstuse.h" -#include "llfloaterreg.h" -#include "llfloatersidepanelcontainer.h" -#include "llmenubutton.h" -#include "llnotificationsutil.h" -#include "lltabcontainer.h" -#include "lltexteditor.h" -#include "lltrans.h" -#include "lluictrlfactory.h" - -#include "llwindow.h" - -#include "llagent.h" -#include "llagentpicksinfo.h" -#include "llavatarpropertiesprocessor.h" -#include "llcommandhandler.h" -#include "lldndbutton.h" -#include "llfloaterworldmap.h" -#include "llinventorybridge.h" -#include "llinventoryobserver.h" -#include "llinventorymodel.h" -#include "lllandmarkactions.h" -#include "lllandmarklist.h" -#include "lllayoutstack.h" -#include "llpanellandmarkinfo.h" -#include "llpanellandmarks.h" -#include "llpanelplaceprofile.h" -#include "llpanelteleporthistory.h" -#include "llremoteparcelrequest.h" -#include "llteleporthistorystorage.h" -#include "lltoggleablemenu.h" -#include "llviewerinventory.h" -#include "llviewermenu.h" -#include "llviewermessage.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" -#include "llviewerwindow.h" - -// Constants -static const F32 PLACE_INFO_UPDATE_INTERVAL = 3.0; -static const std::string AGENT_INFO_TYPE = "agent"; -static const std::string CREATE_LANDMARK_INFO_TYPE = "create_landmark"; -static const std::string CREATE_PICK_TYPE = "create_pick"; -static const std::string LANDMARK_INFO_TYPE = "landmark"; -static const std::string REMOTE_PLACE_INFO_TYPE = "remote_place"; -static const std::string TELEPORT_HISTORY_INFO_TYPE = "teleport_history"; -static const std::string LANDMARK_TAB_INFO_TYPE = "open_landmark_tab"; - -// Support for secondlife:///app/parcel/{UUID}/about SLapps -class LLParcelHandler : public LLCommandHandler -{ -public: - // requires trusted browser to trigger - LLParcelHandler() : LLCommandHandler("parcel", UNTRUSTED_THROTTLE) { } - bool handle(const LLSD& params, - const LLSD& query_map, - const std::string& grid, - LLMediaCtrl* web) - { - if (params.size() < 2) - { - return false; - } - - if (!LLUI::getInstance()->mSettingGroups["config"]->getBOOL("EnablePlaceProfile")) - { - LLNotificationsUtil::add("NoPlaceInfo", LLSD(), LLSD(), std::string("SwitchToStandardSkinAndQuit")); - return true; - } - - LLUUID parcel_id; - if (!parcel_id.set(params[0], false)) - { - return false; - } - if (params[1].asString() == "about") - { - if (parcel_id.notNull()) - { - LLSD key; - key["type"] = "remote_place"; - key["id"] = parcel_id; - LLFloaterSidePanelContainer::showPanel("places", key); - return true; - } - } - return false; - } -}; -LLParcelHandler gParcelHandler; - -// Helper functions -static bool is_agent_in_selected_parcel(LLParcel* parcel); -static void onSLURLBuilt(std::string& slurl); - -//Observer classes -class LLPlacesParcelObserver : public LLParcelObserver -{ -public: - LLPlacesParcelObserver(LLPanelPlaces* places_panel) : - LLParcelObserver(), - mPlaces(places_panel) - {} - - /*virtual*/ void changed() - { - if (mPlaces) - mPlaces->changedParcelSelection(); - } - -private: - LLPanelPlaces* mPlaces; -}; - -class LLPlacesInventoryObserver : public LLInventoryAddedObserver -{ -public: - LLPlacesInventoryObserver(LLPanelPlaces* places_panel) : - mPlaces(places_panel) - {} - - /*virtual*/ void changed(U32 mask) - { - LLInventoryAddedObserver::changed(mask); - - if (mPlaces && !mPlaces->tabsCreated()) - { - mPlaces->createTabs(); - } - } - -protected: - /*virtual*/ void done() - { - mPlaces->showAddedLandmarkInfo(gInventory.getAddedIDs()); - } - -private: - LLPanelPlaces* mPlaces; -}; - -class LLPlacesRemoteParcelInfoObserver : public LLRemoteParcelInfoObserver -{ -public: - LLPlacesRemoteParcelInfoObserver(LLPanelPlaces* places_panel) : - LLRemoteParcelInfoObserver(), - mPlaces(places_panel) - {} - - ~LLPlacesRemoteParcelInfoObserver() - { - // remove any in-flight observers - std::set::iterator it; - for (it = mParcelIDs.begin(); it != mParcelIDs.end(); ++it) - { - const LLUUID &id = *it; - LLRemoteParcelInfoProcessor::getInstance()->removeObserver(id, this); - } - mParcelIDs.clear(); - } - - /*virtual*/ void processParcelInfo(const LLParcelData& parcel_data) - { - if (mPlaces) - { - mPlaces->changedGlobalPos(LLVector3d(parcel_data.global_x, - parcel_data.global_y, - parcel_data.global_z)); - } - - mParcelIDs.erase(parcel_data.parcel_id); - LLRemoteParcelInfoProcessor::getInstance()->removeObserver(parcel_data.parcel_id, this); - } - /*virtual*/ void setParcelID(const LLUUID& parcel_id) - { - if (!parcel_id.isNull()) - { - mParcelIDs.insert(parcel_id); - LLRemoteParcelInfoProcessor::getInstance()->addObserver(parcel_id, this); - LLRemoteParcelInfoProcessor::getInstance()->sendParcelInfoRequest(parcel_id); - } - } - /*virtual*/ void setErrorStatus(S32 status, const std::string& reason) - { - LL_ERRS() << "Can't complete remote parcel request. Http Status: " - << status << ". Reason : " << reason << LL_ENDL; - } - -private: - std::set mParcelIDs; - LLPanelPlaces* mPlaces; -}; - - -static LLPanelInjector t_places("panel_places"); - -LLPanelPlaces::LLPanelPlaces() - : LLPanel(), - mActivePanel(NULL), - mFilterEditor(NULL), - mPlaceProfile(NULL), - mLandmarkInfo(NULL), - mItem(NULL), - mPlaceMenu(NULL), - mLandmarkMenu(NULL), - mPosGlobal(), - isLandmarkEditModeOn(false), - mTabsCreated(false) -{ - mParcelObserver = new LLPlacesParcelObserver(this); - mInventoryObserver = new LLPlacesInventoryObserver(this); - mRemoteParcelObserver = new LLPlacesRemoteParcelInfoObserver(this); - - gInventory.addObserver(mInventoryObserver); - - mAgentParcelChangedConnection = gAgent.addParcelChangedCallback( - boost::bind(&LLPanelPlaces::updateVerbs, this)); - - //buildFromFile( "panel_places.xml"); // Called from LLRegisterPanelClass::defaultPanelClassBuilder() -} - -LLPanelPlaces::~LLPanelPlaces() -{ - if (gInventory.containsObserver(mInventoryObserver)) - gInventory.removeObserver(mInventoryObserver); - - LLViewerParcelMgr::getInstance()->removeObserver(mParcelObserver); - - delete mInventoryObserver; - delete mParcelObserver; - delete mRemoteParcelObserver; - - if (mAgentParcelChangedConnection.connected()) - { - mAgentParcelChangedConnection.disconnect(); - } -} - -bool LLPanelPlaces::postBuild() -{ - mTeleportBtn = getChild("teleport_btn"); - mTeleportBtn->setClickedCallback(boost::bind(&LLPanelPlaces::onTeleportButtonClicked, this)); - - mShowOnMapBtn = getChild("map_btn"); - mShowOnMapBtn->setClickedCallback(boost::bind(&LLPanelPlaces::onShowOnMapButtonClicked, this)); - - mSaveBtn = getChild("save_btn"); - mSaveBtn->setClickedCallback(boost::bind(&LLPanelPlaces::onSaveButtonClicked, this)); - - mCancelBtn = getChild("cancel_btn"); - mCancelBtn->setClickedCallback(boost::bind(&LLPanelPlaces::onCancelButtonClicked, this)); - - mCloseBtn = getChild("close_btn"); - mCloseBtn->setClickedCallback(boost::bind(&LLPanelPlaces::onBackButtonClicked, this)); - - mOverflowBtn = getChild("overflow_btn"); - mOverflowBtn->setMouseDownCallback(boost::bind(&LLPanelPlaces::onOverflowButtonClicked, this)); - - mGearMenuButton = getChild("options_gear_btn"); - mGearMenuButton->setMouseDownCallback(boost::bind(&LLPanelPlaces::onGearMenuClick, this)); - - mSortingMenuButton = getChild("sorting_menu_btn"); - mSortingMenuButton->setMouseDownCallback(boost::bind(&LLPanelPlaces::onSortingMenuClick, this)); - - mAddMenuButton = getChild("add_menu_btn"); - mAddMenuButton->setMouseDownCallback(boost::bind(&LLPanelPlaces::onAddMenuClick, this)); - - mRemoveSelectedBtn = getChild("trash_btn"); - mRemoveSelectedBtn->setClickedCallback(boost::bind(&LLPanelPlaces::onRemoveButtonClicked, this)); - - LLDragAndDropButton* trash_btn = (LLDragAndDropButton*)mRemoveSelectedBtn; - trash_btn->setDragAndDropHandler(boost::bind(&LLPanelPlaces::handleDragAndDropToTrash, this - , _4 // bool drop - , _5 // EDragAndDropType cargo_type - , _6 // void* cargo_data - , _7 // EAcceptance* accept - )); - - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - registrar.add("Places.OverflowMenu.Action", boost::bind(&LLPanelPlaces::onOverflowMenuItemClicked, this, _2)); - LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; - enable_registrar.add("Places.OverflowMenu.Enable", boost::bind(&LLPanelPlaces::onOverflowMenuItemEnable, this, _2)); - - mPlaceMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_place.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - if (mPlaceMenu) - { - mPlaceMenu->setAlwaysShowMenu(true); - } - else - { - LL_WARNS() << "Error loading Place menu" << LL_ENDL; - } - - mLandmarkMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_landmark.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - if (!mLandmarkMenu) - { - LL_WARNS() << "Error loading Landmark menu" << LL_ENDL; - } - - mTabContainer = getChild("Places Tabs"); - if (mTabContainer) - { - mTabContainer->setCommitCallback(boost::bind(&LLPanelPlaces::onTabSelected, this)); - } - - mButtonsContainer = getChild("button_layout_panel"); - mButtonsContainer->setVisible(false); - mFilterContainer = getChild("top_menu_panel"); - - mFilterEditor = getChild("Filter"); - if (mFilterEditor) - { - //when list item is being clicked the filter editor looses focus - //committing on focus lost leads to detaching list items - //BUT a detached list item cannot be made selected and must not be clicked onto - mFilterEditor->setCommitOnFocusLost(false); - - mFilterEditor->setCommitCallback(boost::bind(&LLPanelPlaces::onFilterEdit, this, _2, false)); - } - - mPlaceProfile = findChild("panel_place_profile"); - mLandmarkInfo = findChild("panel_landmark_info"); - if (!mPlaceProfile || !mLandmarkInfo) - return false; - - mPlaceProfileBackBtn = mPlaceProfile->getChild("back_btn"); - mPlaceProfileBackBtn->setClickedCallback(boost::bind(&LLPanelPlaces::onBackButtonClicked, this)); - - mLandmarkInfo->getChild("back_btn")->setClickedCallback(boost::bind(&LLPanelPlaces::onBackButtonClicked, this)); - - LLLineEditor* title_editor = mLandmarkInfo->getChild("title_editor"); - title_editor->setKeystrokeCallback(boost::bind(&LLPanelPlaces::onEditButtonClicked, this), NULL); - - LLTextEditor* notes_editor = mLandmarkInfo->getChild("notes_editor"); - notes_editor->setKeystrokeCallback(boost::bind(&LLPanelPlaces::onEditButtonClicked, this)); - - LLComboBox* folder_combo = mLandmarkInfo->getChild("folder_combo"); - folder_combo->setCommitCallback(boost::bind(&LLPanelPlaces::onEditButtonClicked, this)); - - LLButton* edit_btn = mLandmarkInfo->getChild("edit_btn"); - edit_btn->setCommitCallback(boost::bind(&LLPanelPlaces::onEditButtonClicked, this)); - - createTabs(); - updateVerbs(); - - return true; -} - -void LLPanelPlaces::onOpen(const LLSD& key) -{ - if (!mPlaceProfile || !mLandmarkInfo) - return; - - if (key.size() != 0) - { - isLandmarkEditModeOn = false; - std::string key_type = key["type"].asString(); - if (key_type == LANDMARK_TAB_INFO_TYPE) - { - // Small hack: We need to toggle twice. The first toggle moves from the Landmark - // or Teleport History info panel to the Landmark or Teleport History list panel. - // For this first toggle, the mPlaceInfoType should be the one previously used so - // that the state can be corretly set. - // The second toggle forces the list to be set to Landmark. - // This avoids extracting and duplicating all the state logic from togglePlaceInfoPanel() - // here or some specific private method - togglePlaceInfoPanel(false); - mPlaceInfoType = key_type; - togglePlaceInfoPanel(false); - // Update the active tab - onTabSelected(); - // Update the buttons at the bottom of the panel - updateVerbs(); - } - else if (key_type == CREATE_PICK_TYPE) - { - LLUUID item_id = key["item_id"]; - - LLLandmarksPanel* landmarks_panel = - dynamic_cast(mTabContainer->getPanelByName("Landmarks")); - if (landmarks_panel && item_id.notNull()) - { - LLLandmark* landmark = LLLandmarkActions::getLandmark(item_id, boost::bind(&LLLandmarksPanel::doCreatePick, landmarks_panel, _1, item_id)); - if (landmark) - { - landmarks_panel->doCreatePick(landmark, item_id); - } - } - } - else // "create_landmark" - { - mFilterEditor->clear(); - onFilterEdit("", false); - - mPlaceInfoType = key_type; - mPosGlobal.setZero(); - mItem = NULL; - mRegionId.setNull(); - togglePlaceInfoPanel(true); - - if (mPlaceInfoType == AGENT_INFO_TYPE) - { - mPlaceProfile->setInfoType(LLPanelPlaceInfo::AGENT); - if (gAgent.getRegion()) - { - mRegionId = gAgent.getRegion()->getRegionID(); - } - } - else if (mPlaceInfoType == CREATE_LANDMARK_INFO_TYPE) - { - LLUUID dest_folder = key["dest_folder"]; - mLandmarkInfo->setInfoAndCreateLandmark(dest_folder); - - if (key.has("x") && key.has("y") && key.has("z")) - { - mPosGlobal = LLVector3d(key["x"].asReal(), - key["y"].asReal(), - key["z"].asReal()); - } - else - { - mPosGlobal = gAgent.getPositionGlobal(); - } - - mLandmarkInfo->displayParcelInfo(LLUUID(), mPosGlobal); - - mSaveBtn->setEnabled(false); - } - else if (mPlaceInfoType == LANDMARK_INFO_TYPE) - { - mLandmarkInfo->setInfoType(LLPanelPlaceInfo::LANDMARK); - - LLUUID id = key["id"].asUUID(); - LLInventoryItem* item = gInventory.getItem(id); - if (!item) - return; - - bool is_editable = gInventory.isObjectDescendentOf(id, gInventory.getRootFolderID()) - && item->getPermissions().allowModifyBy(gAgent.getID()); - mLandmarkInfo->setCanEdit(is_editable); - - setItem(item); - } - else if (mPlaceInfoType == REMOTE_PLACE_INFO_TYPE) - { - if (key.has("id")) - { - LLUUID parcel_id = key["id"].asUUID(); - mPlaceProfile->setParcelID(parcel_id); - - // query the server to get the global 3D position of this - // parcel - we need this for teleport/mapping functions. - mRemoteParcelObserver->setParcelID(parcel_id); - } - else - { - mPosGlobal = LLVector3d(key["x"].asReal(), - key["y"].asReal(), - key["z"].asReal()); - mPlaceProfile->displayParcelInfo(LLUUID(), mPosGlobal); - } - - mPlaceProfile->setInfoType(LLPanelPlaceInfo::PLACE); - } - else if (mPlaceInfoType == TELEPORT_HISTORY_INFO_TYPE) - { - S32 index = key["id"].asInteger(); - - const LLTeleportHistoryStorage::slurl_list_t& hist_items = - LLTeleportHistoryStorage::getInstance()->getItems(); - - mPosGlobal = hist_items[index].mGlobalPos; - - mPlaceProfile->setInfoType(LLPanelPlaceInfo::TELEPORT_HISTORY); - mPlaceProfile->displayParcelInfo(LLUUID(), mPosGlobal); - } - - updateVerbs(); - } - } - - LLViewerParcelMgr* parcel_mgr = LLViewerParcelMgr::getInstance(); - if (!parcel_mgr) - return; - - mParcelLocalId = parcel_mgr->getAgentParcel()->getLocalID(); - - // Start using LLViewerParcelMgr for land selection if - // information about nearby land is requested. - // Otherwise stop using land selection and deselect land. - if (mPlaceInfoType == AGENT_INFO_TYPE) - { - // We don't know if we are already added to LLViewerParcelMgr observers list - // so try to remove observer not to add an extra one. - parcel_mgr->removeObserver(mParcelObserver); - - parcel_mgr->addObserver(mParcelObserver); - parcel_mgr->selectParcelAt(gAgent.getPositionGlobal()); - } - else - { - parcel_mgr->removeObserver(mParcelObserver); - - // Clear the reference to selection to allow its removal in deselectUnused(). - mParcel.clear(); - - if (!parcel_mgr->selectionEmpty()) - { - parcel_mgr->deselectUnused(); - } - } -} - -void LLPanelPlaces::setItem(LLInventoryItem* item) -{ - if (!mLandmarkInfo || !item) - return; - - mItem = item; - - LLAssetType::EType item_type = mItem->getActualType(); - if (item_type == LLAssetType::AT_LANDMARK || item_type == LLAssetType::AT_LINK) - { - // If the item is a link get a linked item - if (item_type == LLAssetType::AT_LINK) - { - mItem = gInventory.getItem(mItem->getLinkedUUID()); - if (mItem.isNull()) - return; - } - } - else - { - return; - } - - // Check if item is in agent's inventory and he has the permission to modify it. - bool is_landmark_editable = gInventory.isObjectDescendentOf(mItem->getUUID(), gInventory.getRootFolderID()) && - mItem->getPermissions().allowModifyBy(gAgent.getID()); - - mSaveBtn->setEnabled(is_landmark_editable); - - if (is_landmark_editable) - { - if(!mLandmarkInfo->setLandmarkFolder(mItem->getParentUUID()) && !mItem->getParentUUID().isNull()) - { - const LLViewerInventoryCategory* cat = gInventory.getCategory(mItem->getParentUUID()); - if (cat) - { - std::string cat_fullname = LLPanelLandmarkInfo::getFullFolderName(cat); - LLComboBox* folderList = mLandmarkInfo->getChild("folder_combo"); - folderList->add(cat_fullname, cat->getUUID(), ADD_TOP); - } - } - } - - mLandmarkInfo->displayItemInfo(mItem); - - LLLandmark* lm = gLandmarkList.getAsset(mItem->getAssetUUID(), - boost::bind(&LLPanelPlaces::onLandmarkLoaded, this, _1)); - if (lm) - { - onLandmarkLoaded(lm); - } -} - -S32 LLPanelPlaces::notifyParent(const LLSD& info) -{ - if(info.has("update_verbs")) - { - if(mPosGlobal.isExactlyZero()) - { - mPosGlobal.setVec(info["global_x"], info["global_y"], info["global_z"]); - } - - updateVerbs(); - - return 1; - } - return LLPanel::notifyParent(info); -} - -void LLPanelPlaces::onLandmarkLoaded(LLLandmark* landmark) -{ - if (!mLandmarkInfo) - return; - - LLUUID region_id; - landmark->getRegionID(region_id); - landmark->getGlobalPos(mPosGlobal); - mLandmarkInfo->displayParcelInfo(region_id, mPosGlobal); - - updateVerbs(); -} - -void LLPanelPlaces::onFilterEdit(const std::string& search_string, bool force_filter) -{ - if (!mActivePanel) - return; - - if (force_filter || mActivePanel->getFilterSubString() != search_string) - { - std::string string = search_string; - - // Searches are case-insensitive - // but we don't convert the typed string to upper-case so that it can be fed to the web search as-is. - - mActivePanel->onSearchEdit(string); - } -} - -void LLPanelPlaces::onTabSelected() -{ - mActivePanel = dynamic_cast(mTabContainer->getCurrentPanel()); - if (!mActivePanel) - return; - - onFilterEdit(mActivePanel->getFilterSubString(), true); - mActivePanel->updateVerbs(); - - // History panel does not support deletion nor creation - // Hide menus - bool supports_create = mActivePanel->getCreateMenu() != NULL; - childSetVisible("add_btn_panel", supports_create); - - // favorites and inventory can remove items, history can clear history - childSetVisible("trash_btn_panel", true); - - if (supports_create) - { - mRemoveSelectedBtn->setToolTip(getString("tooltip_trash_items")); - } - else - { - mRemoveSelectedBtn->setToolTip(getString("tooltip_trash_history")); - } -} - -void LLPanelPlaces::onTeleportButtonClicked() -{ - LLPanelPlaceInfo* panel = getCurrentInfoPanel(); - if (panel && panel->getVisible()) - { - if (mPlaceInfoType == LANDMARK_INFO_TYPE) - { - if (mItem.isNull()) - { - LL_WARNS() << "NULL landmark item" << LL_ENDL; - llassert(mItem.notNull()); - return; - } - - LLSD payload; - payload["asset_id"] = mItem->getAssetUUID(); - LLSD args; - args["LOCATION"] = mItem->getName(); - LLNotificationsUtil::add("TeleportFromLandmark", args, payload); - } - else if (mPlaceInfoType == AGENT_INFO_TYPE || - mPlaceInfoType == REMOTE_PLACE_INFO_TYPE || - mPlaceInfoType == TELEPORT_HISTORY_INFO_TYPE) - { - LLFloaterWorldMap* worldmap_instance = LLFloaterWorldMap::getInstance(); - if (!mPosGlobal.isExactlyZero() && worldmap_instance) - { - gAgent.teleportViaLocation(mPosGlobal); - worldmap_instance->trackLocation(mPosGlobal); - } - } - } - else - { - if (mActivePanel) - mActivePanel->onTeleport(); - } -} - -void LLPanelPlaces::onShowOnMapButtonClicked() -{ - LLPanelPlaceInfo* panel = getCurrentInfoPanel(); - if (panel && panel->getVisible()) - { - LLFloaterWorldMap* worldmap_instance = LLFloaterWorldMap::getInstance(); - if(!worldmap_instance) - return; - - if (mPlaceInfoType == AGENT_INFO_TYPE || - mPlaceInfoType == CREATE_LANDMARK_INFO_TYPE || - mPlaceInfoType == REMOTE_PLACE_INFO_TYPE || - mPlaceInfoType == TELEPORT_HISTORY_INFO_TYPE) - { - if (!mPosGlobal.isExactlyZero()) - { - worldmap_instance->trackLocation(mPosGlobal); - LLFloaterReg::showInstance("world_map", "center"); - } - } - else if (mPlaceInfoType == LANDMARK_INFO_TYPE) - { - if (mItem.isNull()) - { - LL_WARNS() << "NULL landmark item" << LL_ENDL; - llassert(mItem.notNull()); - return; - } - LLLandmark* landmark = gLandmarkList.getAsset(mItem->getAssetUUID()); - if (!landmark) - return; - - LLVector3d landmark_global_pos; - if (!landmark->getGlobalPos(landmark_global_pos)) - return; - - if (!landmark_global_pos.isExactlyZero()) - { - worldmap_instance->trackLocation(landmark_global_pos); - LLFloaterReg::showInstance("world_map", "center"); - } - } - } - else - { - if (mActivePanel && mActivePanel->isSingleItemSelected()) - { - mActivePanel->onShowOnMap(); - } - else - { - LLFloaterWorldMap* worldmap_instance = LLFloaterWorldMap::getInstance(); - LLVector3d global_pos = gAgent.getPositionGlobal(); - - if (!global_pos.isExactlyZero() && worldmap_instance) - { - worldmap_instance->trackLocation(global_pos); - LLFloaterReg::showInstance("world_map", "center"); - } - } - } -} - -void LLPanelPlaces::onEditButtonClicked() -{ - if (!mLandmarkInfo || isLandmarkEditModeOn) - return; - - isLandmarkEditModeOn = true; - - mLandmarkInfo->toggleLandmarkEditMode(true); - - updateVerbs(); -} - -void LLPanelPlaces::onSaveButtonClicked() -{ - if (!mLandmarkInfo || mItem.isNull()) - return; - - std::string current_title_value = mLandmarkInfo->getLandmarkTitle(); - std::string item_title_value = mItem->getName(); - std::string current_notes_value = mLandmarkInfo->getLandmarkNotes(); - std::string item_notes_value = mItem->getDescription(); - - LLStringUtil::trim(current_title_value); - LLStringUtil::trim(current_notes_value); - - LLUUID folder_id = mLandmarkInfo->getLandmarkFolder(); - bool change_parent = folder_id != mItem->getParentUUID(); - - LLPointer new_item = new LLViewerInventoryItem(mItem); - - if (!current_title_value.empty() && - (item_title_value != current_title_value || item_notes_value != current_notes_value)) - { - new_item->rename(current_title_value); - new_item->setDescription(current_notes_value); - LLPointer cb; - if (change_parent) - { - cb = new LLUpdateLandmarkParent(new_item, folder_id); - } - LLInventoryModel::LLCategoryUpdate up(mItem->getParentUUID(), 0); - gInventory.accountForUpdate(up); - update_inventory_item(new_item, cb); - } - else if (change_parent) - { - LLInventoryModel::update_list_t update; - LLInventoryModel::LLCategoryUpdate old_folder(mItem->getParentUUID(),-1); - update.push_back(old_folder); - LLInventoryModel::LLCategoryUpdate new_folder(folder_id, 1); - update.push_back(new_folder); - gInventory.accountForUpdate(update); - - new_item->setParent(folder_id); - new_item->updateParentOnServer(false); - } - - gInventory.updateItem(new_item); - gInventory.notifyObservers(); - - onCancelButtonClicked(); -} - -void LLPanelPlaces::onCancelButtonClicked() -{ - if (!mLandmarkInfo) - return; - - if (mPlaceInfoType == CREATE_LANDMARK_INFO_TYPE) - { - onBackButtonClicked(); - } - else - { - mLandmarkInfo->toggleLandmarkEditMode(false); - isLandmarkEditModeOn = false; - - updateVerbs(); - - // Reload the landmark properties. - mLandmarkInfo->displayItemInfo(mItem); - } -} - -void LLPanelPlaces::onOverflowButtonClicked() -{ - LLToggleableMenu* menu; - - bool is_agent_place_info_visible = mPlaceInfoType == AGENT_INFO_TYPE; - - if ((is_agent_place_info_visible || - mPlaceInfoType == REMOTE_PLACE_INFO_TYPE || - mPlaceInfoType == TELEPORT_HISTORY_INFO_TYPE) && mPlaceMenu != NULL) - { - menu = mPlaceMenu; - - bool landmark_item_enabled = false; - LLViewerParcelMgr* parcel_mgr = LLViewerParcelMgr::getInstance(); - if (is_agent_place_info_visible - && gAgent.getRegion() - && mRegionId == gAgent.getRegion()->getRegionID() - && parcel_mgr - && parcel_mgr->getAgentParcel()->getLocalID() == mParcelLocalId) - { - // Floater still shows location identical to agent's position - landmark_item_enabled = !LLLandmarkActions::landmarkAlreadyExists(); - } - - // Enable adding a landmark only for agent current parcel and if - // there is no landmark already pointing to that parcel in agent's inventory. - menu->getChild("landmark")->setEnabled(landmark_item_enabled); - // STORM-411 - // Creating landmarks for remote locations is impossible. - // So hide menu item "Make a Landmark" in "Teleport History Profile" panel. - menu->setItemVisible("landmark", mPlaceInfoType != TELEPORT_HISTORY_INFO_TYPE); - menu->arrangeAndClear(); - } - else if (mPlaceInfoType == LANDMARK_INFO_TYPE && mLandmarkMenu != NULL) - { - menu = mLandmarkMenu; - - bool is_landmark_removable = false; - if (mItem.notNull()) - { - const LLUUID& item_id = mItem->getUUID(); - const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - is_landmark_removable = gInventory.isObjectDescendentOf(item_id, gInventory.getRootFolderID()) && - !gInventory.isObjectDescendentOf(item_id, trash_id); - } - - menu->getChild("delete")->setEnabled(is_landmark_removable); - } - else - { - return; - } - - mOverflowBtn->setMenu(menu, LLMenuButton::MP_TOP_RIGHT); -} - -bool LLPanelPlaces::onOverflowMenuItemEnable(const LLSD& param) -{ - std::string value = param.asString(); - if("can_create_pick" == value) - { - return !LLAgentPicksInfo::getInstance()->isPickLimitReached(); - } - return true; -} - -void LLPanelPlaces::onOverflowMenuItemClicked(const LLSD& param) -{ - std::string item = param.asString(); - if (item == "landmark") - { - LLSD key; - key["type"] = CREATE_LANDMARK_INFO_TYPE; - key["x"] = mPosGlobal.mdV[VX]; - key["y"] = mPosGlobal.mdV[VY]; - key["z"] = mPosGlobal.mdV[VZ]; - onOpen(key); - } - else if (item == "copy") - { - LLLandmarkActions::getSLURLfromPosGlobal(mPosGlobal, boost::bind(&onSLURLBuilt, _1)); - } - else if (item == "delete") - { - gInventory.removeItem(mItem->getUUID()); - - onBackButtonClicked(); - } - else if (item == "pick") - { - LLPanelPlaceInfo* panel = getCurrentInfoPanel(); - if (panel) - { - panel->createPick(mPosGlobal); - } - } - else if (item == "add_to_favbar") - { - if ( mItem.notNull() ) - { - const LLUUID& favorites_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); - if ( favorites_id.notNull() ) - { - copy_inventory_item(gAgent.getID(), - mItem->getPermissions().getOwner(), - mItem->getUUID(), - favorites_id, - std::string(), - LLPointer(NULL)); - LL_INFOS() << "Copied inventory item #" << mItem->getUUID() << " to favorites." << LL_ENDL; - } - } - } -} - -void LLPanelPlaces::onBackButtonClicked() -{ - togglePlaceInfoPanel(false); - - // Resetting mPlaceInfoType when Place Info panel is closed. - mPlaceInfoType = LLStringUtil::null; - - isLandmarkEditModeOn = false; - - updateVerbs(); -} - -void LLPanelPlaces::onGearMenuClick() -{ - if (mActivePanel) - { - LLToggleableMenu* menu = mActivePanel->getSelectionMenu(); - mGearMenuButton->setMenu(menu, LLMenuButton::MP_BOTTOM_LEFT); - } -} - -void LLPanelPlaces::onSortingMenuClick() -{ - if (mActivePanel) - { - LLToggleableMenu* menu = mActivePanel->getSortingMenu(); - mSortingMenuButton->setMenu(menu, LLMenuButton::MP_BOTTOM_LEFT); - } -} - -void LLPanelPlaces::onAddMenuClick() -{ - if (mActivePanel) - { - LLToggleableMenu* menu = mActivePanel->getCreateMenu(); - mAddMenuButton->setMenu(menu, LLMenuButton::MP_BOTTOM_LEFT); - } -} - -void LLPanelPlaces::onRemoveButtonClicked() -{ - if (mActivePanel) - { - mActivePanel->onRemoveSelected(); - } -} - -bool LLPanelPlaces::handleDragAndDropToTrash(bool drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept) -{ - if (mActivePanel) - { - return mActivePanel->handleDragAndDropToTrash(drop, cargo_type, cargo_data, accept); - } - return false; -} - -void LLPanelPlaces::togglePlaceInfoPanel(bool visible) -{ - if (!mPlaceProfile || !mLandmarkInfo) - return; - - mTabContainer->setVisible(!visible); - mButtonsContainer->setVisible(visible); - mFilterContainer->setVisible(!visible); - - if (mPlaceInfoType == AGENT_INFO_TYPE || - mPlaceInfoType == REMOTE_PLACE_INFO_TYPE || - mPlaceInfoType == TELEPORT_HISTORY_INFO_TYPE) - { - mPlaceProfile->setVisible(visible); - - if (visible) - { - mPlaceProfile->resetLocation(); - - // Do not reset location info until mResetInfoTimer has expired - // to avoid text blinking. - mResetInfoTimer.setTimerExpirySec(PLACE_INFO_UPDATE_INTERVAL); - - mLandmarkInfo->setVisible(false); - } - else if (mPlaceInfoType == AGENT_INFO_TYPE) - { - LLViewerParcelMgr::getInstance()->removeObserver(mParcelObserver); - - // Clear reference to parcel selection when closing place profile panel. - // LLViewerParcelMgr removes the selection if it has 1 reference to it. - mParcel.clear(); - } - } - else if (mPlaceInfoType == CREATE_LANDMARK_INFO_TYPE || - mPlaceInfoType == LANDMARK_INFO_TYPE || - mPlaceInfoType == LANDMARK_TAB_INFO_TYPE) - { - mLandmarkInfo->setVisible(visible); - mPlaceProfile->setVisible(false); - if (visible) - { - mLandmarkInfo->resetLocation(); - } - else - { - std::string tab_panel_name("Landmarks"); - if (mItem.notNull()) - { - if (gInventory.isObjectDescendentOf(mItem->getUUID(), gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE))) - { - tab_panel_name = "Favorites"; - } - } - - LLLandmarksPanel* landmarks_panel = dynamic_cast(mTabContainer->getPanelByName(tab_panel_name)); - if (landmarks_panel) - { - // If a landmark info is being closed we open the landmarks tab - // and set this landmark selected. - mTabContainer->selectTabPanel(landmarks_panel); - if (mItem.notNull()) - { - landmarks_panel->setItemSelected(mItem->getUUID(), true); - } - else - { - landmarks_panel->resetSelection(); - } - } - } - } -} - -// virtual -void LLPanelPlaces::onVisibilityChange(bool new_visibility) -{ - LLPanel::onVisibilityChange(new_visibility); - - if (!new_visibility && mPlaceInfoType == AGENT_INFO_TYPE) - { - LLViewerParcelMgr::getInstance()->removeObserver(mParcelObserver); - - // Clear reference to parcel selection when closing places panel. - mParcel.clear(); - } -} - -void LLPanelPlaces::changedParcelSelection() -{ - if (!mPlaceProfile) - return; - - LLViewerParcelMgr* parcel_mgr = LLViewerParcelMgr::getInstance(); - mParcel = parcel_mgr->getFloatingParcelSelection(); - LLParcel* parcel = mParcel->getParcel(); - LLViewerRegion* region = parcel_mgr->getSelectionRegion(); - if (!region || !parcel) - return; - - LLVector3d prev_pos_global = mPosGlobal; - - // If agent is inside the selected parcel show agent's region, - // otherwise show region of agent's selection point. - bool is_current_parcel = is_agent_in_selected_parcel(parcel); - if (is_current_parcel) - { - mPosGlobal = gAgent.getPositionGlobal(); - } - else - { - LLVector3d pos_global = gViewerWindow->getLastPick().mPosGlobal; - if (!pos_global.isExactlyZero()) - { - mPosGlobal = pos_global; - } - } - - // Reset location info only if global position has changed - // and update timer has expired to reduce unnecessary text and icons updates. - if (prev_pos_global != mPosGlobal && mResetInfoTimer.hasExpired()) - { - mPlaceProfile->resetLocation(); - mResetInfoTimer.setTimerExpirySec(PLACE_INFO_UPDATE_INTERVAL); - } - - mPlaceProfile->displaySelectedParcelInfo(parcel, region, mPosGlobal, is_current_parcel); - - updateVerbs(); -} - -void LLPanelPlaces::createTabs() -{ - if (!(gInventory.isInventoryUsable() && LLTeleportHistory::getInstance() && !mTabsCreated)) - return; - - LLFavoritesPanel* favorites_panel = new LLFavoritesPanel(); - if (favorites_panel) - { - mTabContainer->addTabPanel( - LLTabContainer::TabPanelParams(). - panel(favorites_panel). - label(getString("favorites_tab_title")). - insert_at(LLTabContainer::END)); - } - - LLLandmarksPanel* landmarks_panel = new LLLandmarksPanel(); - if (landmarks_panel) - { - mTabContainer->addTabPanel( - LLTabContainer::TabPanelParams(). - panel(landmarks_panel). - label(getString("landmarks_tab_title")). - insert_at(LLTabContainer::END)); - } - - LLTeleportHistoryPanel* teleport_history_panel = new LLTeleportHistoryPanel(); - if (teleport_history_panel) - { - mTabContainer->addTabPanel( - LLTabContainer::TabPanelParams(). - panel(teleport_history_panel). - label(getString("teleport_history_tab_title")). - insert_at(LLTabContainer::END)); - } - - mTabContainer->selectFirstTab(); - - mActivePanel = dynamic_cast(mTabContainer->getCurrentPanel()); - - if (mActivePanel) - { - // Filter applied to show all items. - mActivePanel->onSearchEdit(mActivePanel->getFilterSubString()); - - // History panel does not support deletion nor creation - // Hide menus - bool supports_create = mActivePanel->getCreateMenu() != NULL; - childSetVisible("add_btn_panel", supports_create); - - // favorites and inventory can remove items, history can clear history - childSetVisible("trash_btn_panel", true); - - if (supports_create) - { - mRemoveSelectedBtn->setToolTip(getString("tooltip_trash_items")); - } - else - { - mRemoveSelectedBtn->setToolTip(getString("tooltip_trash_history")); - } - - mActivePanel->setRemoveBtn(mRemoveSelectedBtn); - mActivePanel->updateVerbs(); - } - - mTabsCreated = true; -} - -void LLPanelPlaces::changedGlobalPos(const LLVector3d &global_pos) -{ - mPosGlobal = global_pos; - updateVerbs(); -} - -void LLPanelPlaces::showAddedLandmarkInfo(const uuid_set_t& items) -{ - for (uuid_set_t::const_iterator item_iter = items.begin(); - item_iter != items.end(); - ++item_iter) - { - const LLUUID& item_id = (*item_iter); - if(!highlight_offered_object(item_id)) - { - continue; - } - - LLInventoryItem* item = gInventory.getItem(item_id); - - llassert(item); - if (item && (LLAssetType::AT_LANDMARK == item->getType()) ) - { - // Created landmark is passed to Places panel to allow its editing. - // If the panel is closed we don't reopen it until created landmark is loaded. - if("create_landmark" == getPlaceInfoType() && !getItem()) - { - setItem(item); - } - } - } -} - -void LLPanelPlaces::updateVerbs() -{ - bool is_place_info_visible; - - LLPanelPlaceInfo* panel = getCurrentInfoPanel(); - if (panel) - { - is_place_info_visible = panel->getVisible(); - } - else - { - is_place_info_visible = false; - } - - bool is_agent_place_info_visible = mPlaceInfoType == AGENT_INFO_TYPE; - bool is_create_landmark_visible = mPlaceInfoType == CREATE_LANDMARK_INFO_TYPE; - - bool have_3d_pos = ! mPosGlobal.isExactlyZero(); - - mTeleportBtn->setVisible(!is_create_landmark_visible && !isLandmarkEditModeOn); - mShowOnMapBtn->setVisible(!is_create_landmark_visible && !isLandmarkEditModeOn); - mSaveBtn->setVisible(isLandmarkEditModeOn); - mCancelBtn->setVisible(isLandmarkEditModeOn); - mCloseBtn->setVisible(is_create_landmark_visible && !isLandmarkEditModeOn); - - bool show_options_btn = is_place_info_visible && !is_create_landmark_visible && !isLandmarkEditModeOn; - mOverflowBtn->setVisible(show_options_btn); - getChild("lp_options")->setVisible(show_options_btn); - getChild("lp2")->setVisible(!show_options_btn); - - if (is_place_info_visible) - { - mShowOnMapBtn->setEnabled(have_3d_pos); - - if (is_agent_place_info_visible) - { - // We don't need to teleport to the current location - // so check if the location is not within the current parcel. - mTeleportBtn->setEnabled(have_3d_pos && - !LLViewerParcelMgr::getInstance()->inAgentParcel(mPosGlobal)); - } - else if (mPlaceInfoType == LANDMARK_INFO_TYPE || mPlaceInfoType == REMOTE_PLACE_INFO_TYPE) - { - mTeleportBtn->setEnabled(have_3d_pos); - } - } - else - { - if (mActivePanel) - mActivePanel->updateVerbs(); - } -} - -LLPanelPlaceInfo* LLPanelPlaces::getCurrentInfoPanel() -{ - if (mPlaceInfoType == AGENT_INFO_TYPE || - mPlaceInfoType == REMOTE_PLACE_INFO_TYPE || - mPlaceInfoType == TELEPORT_HISTORY_INFO_TYPE) - { - return mPlaceProfile; - } - else if (mPlaceInfoType == CREATE_LANDMARK_INFO_TYPE || - mPlaceInfoType == LANDMARK_INFO_TYPE || - mPlaceInfoType == LANDMARK_TAB_INFO_TYPE) - { - return mLandmarkInfo; - } - - return NULL; -} - -static bool is_agent_in_selected_parcel(LLParcel* parcel) -{ - LLViewerParcelMgr* parcel_mgr = LLViewerParcelMgr::getInstance(); - - LLViewerRegion* region = parcel_mgr->getSelectionRegion(); - if (!region || !parcel) - return false; - - return region == gAgent.getRegion() && - parcel->getLocalID() == parcel_mgr->getAgentParcel()->getLocalID(); -} - -static void onSLURLBuilt(std::string& slurl) -{ - LLView::getWindow()->copyTextToClipboard(utf8str_to_wstring(slurl)); - - LLSD args; - args["SLURL"] = slurl; - - LLNotificationsUtil::add("CopySLURL", args); -} +/** + * @file llpanelplaces.cpp + * @brief Side Bar "Places" panel + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelplaces.h" + +#include "llassettype.h" +#include "lltimer.h" + +#include "llinventory.h" +#include "lllandmark.h" +#include "llparcel.h" + +#include "llcombobox.h" +#include "llfiltereditor.h" +#include "llfirstuse.h" +#include "llfloaterreg.h" +#include "llfloatersidepanelcontainer.h" +#include "llmenubutton.h" +#include "llnotificationsutil.h" +#include "lltabcontainer.h" +#include "lltexteditor.h" +#include "lltrans.h" +#include "lluictrlfactory.h" + +#include "llwindow.h" + +#include "llagent.h" +#include "llagentpicksinfo.h" +#include "llavatarpropertiesprocessor.h" +#include "llcommandhandler.h" +#include "lldndbutton.h" +#include "llfloaterworldmap.h" +#include "llinventorybridge.h" +#include "llinventoryobserver.h" +#include "llinventorymodel.h" +#include "lllandmarkactions.h" +#include "lllandmarklist.h" +#include "lllayoutstack.h" +#include "llpanellandmarkinfo.h" +#include "llpanellandmarks.h" +#include "llpanelplaceprofile.h" +#include "llpanelteleporthistory.h" +#include "llremoteparcelrequest.h" +#include "llteleporthistorystorage.h" +#include "lltoggleablemenu.h" +#include "llviewerinventory.h" +#include "llviewermenu.h" +#include "llviewermessage.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" + +// Constants +static const F32 PLACE_INFO_UPDATE_INTERVAL = 3.0; +static const std::string AGENT_INFO_TYPE = "agent"; +static const std::string CREATE_LANDMARK_INFO_TYPE = "create_landmark"; +static const std::string CREATE_PICK_TYPE = "create_pick"; +static const std::string LANDMARK_INFO_TYPE = "landmark"; +static const std::string REMOTE_PLACE_INFO_TYPE = "remote_place"; +static const std::string TELEPORT_HISTORY_INFO_TYPE = "teleport_history"; +static const std::string LANDMARK_TAB_INFO_TYPE = "open_landmark_tab"; + +// Support for secondlife:///app/parcel/{UUID}/about SLapps +class LLParcelHandler : public LLCommandHandler +{ +public: + // requires trusted browser to trigger + LLParcelHandler() : LLCommandHandler("parcel", UNTRUSTED_THROTTLE) { } + bool handle(const LLSD& params, + const LLSD& query_map, + const std::string& grid, + LLMediaCtrl* web) + { + if (params.size() < 2) + { + return false; + } + + if (!LLUI::getInstance()->mSettingGroups["config"]->getBOOL("EnablePlaceProfile")) + { + LLNotificationsUtil::add("NoPlaceInfo", LLSD(), LLSD(), std::string("SwitchToStandardSkinAndQuit")); + return true; + } + + LLUUID parcel_id; + if (!parcel_id.set(params[0], false)) + { + return false; + } + if (params[1].asString() == "about") + { + if (parcel_id.notNull()) + { + LLSD key; + key["type"] = "remote_place"; + key["id"] = parcel_id; + LLFloaterSidePanelContainer::showPanel("places", key); + return true; + } + } + return false; + } +}; +LLParcelHandler gParcelHandler; + +// Helper functions +static bool is_agent_in_selected_parcel(LLParcel* parcel); +static void onSLURLBuilt(std::string& slurl); + +//Observer classes +class LLPlacesParcelObserver : public LLParcelObserver +{ +public: + LLPlacesParcelObserver(LLPanelPlaces* places_panel) : + LLParcelObserver(), + mPlaces(places_panel) + {} + + /*virtual*/ void changed() + { + if (mPlaces) + mPlaces->changedParcelSelection(); + } + +private: + LLPanelPlaces* mPlaces; +}; + +class LLPlacesInventoryObserver : public LLInventoryAddedObserver +{ +public: + LLPlacesInventoryObserver(LLPanelPlaces* places_panel) : + mPlaces(places_panel) + {} + + /*virtual*/ void changed(U32 mask) + { + LLInventoryAddedObserver::changed(mask); + + if (mPlaces && !mPlaces->tabsCreated()) + { + mPlaces->createTabs(); + } + } + +protected: + /*virtual*/ void done() + { + mPlaces->showAddedLandmarkInfo(gInventory.getAddedIDs()); + } + +private: + LLPanelPlaces* mPlaces; +}; + +class LLPlacesRemoteParcelInfoObserver : public LLRemoteParcelInfoObserver +{ +public: + LLPlacesRemoteParcelInfoObserver(LLPanelPlaces* places_panel) : + LLRemoteParcelInfoObserver(), + mPlaces(places_panel) + {} + + ~LLPlacesRemoteParcelInfoObserver() + { + // remove any in-flight observers + std::set::iterator it; + for (it = mParcelIDs.begin(); it != mParcelIDs.end(); ++it) + { + const LLUUID &id = *it; + LLRemoteParcelInfoProcessor::getInstance()->removeObserver(id, this); + } + mParcelIDs.clear(); + } + + /*virtual*/ void processParcelInfo(const LLParcelData& parcel_data) + { + if (mPlaces) + { + mPlaces->changedGlobalPos(LLVector3d(parcel_data.global_x, + parcel_data.global_y, + parcel_data.global_z)); + } + + mParcelIDs.erase(parcel_data.parcel_id); + LLRemoteParcelInfoProcessor::getInstance()->removeObserver(parcel_data.parcel_id, this); + } + /*virtual*/ void setParcelID(const LLUUID& parcel_id) + { + if (!parcel_id.isNull()) + { + mParcelIDs.insert(parcel_id); + LLRemoteParcelInfoProcessor::getInstance()->addObserver(parcel_id, this); + LLRemoteParcelInfoProcessor::getInstance()->sendParcelInfoRequest(parcel_id); + } + } + /*virtual*/ void setErrorStatus(S32 status, const std::string& reason) + { + LL_ERRS() << "Can't complete remote parcel request. Http Status: " + << status << ". Reason : " << reason << LL_ENDL; + } + +private: + std::set mParcelIDs; + LLPanelPlaces* mPlaces; +}; + + +static LLPanelInjector t_places("panel_places"); + +LLPanelPlaces::LLPanelPlaces() + : LLPanel(), + mActivePanel(NULL), + mFilterEditor(NULL), + mPlaceProfile(NULL), + mLandmarkInfo(NULL), + mItem(NULL), + mPlaceMenu(NULL), + mLandmarkMenu(NULL), + mPosGlobal(), + isLandmarkEditModeOn(false), + mTabsCreated(false) +{ + mParcelObserver = new LLPlacesParcelObserver(this); + mInventoryObserver = new LLPlacesInventoryObserver(this); + mRemoteParcelObserver = new LLPlacesRemoteParcelInfoObserver(this); + + gInventory.addObserver(mInventoryObserver); + + mAgentParcelChangedConnection = gAgent.addParcelChangedCallback( + boost::bind(&LLPanelPlaces::updateVerbs, this)); + + //buildFromFile( "panel_places.xml"); // Called from LLRegisterPanelClass::defaultPanelClassBuilder() +} + +LLPanelPlaces::~LLPanelPlaces() +{ + if (gInventory.containsObserver(mInventoryObserver)) + gInventory.removeObserver(mInventoryObserver); + + LLViewerParcelMgr::getInstance()->removeObserver(mParcelObserver); + + delete mInventoryObserver; + delete mParcelObserver; + delete mRemoteParcelObserver; + + if (mAgentParcelChangedConnection.connected()) + { + mAgentParcelChangedConnection.disconnect(); + } +} + +bool LLPanelPlaces::postBuild() +{ + mTeleportBtn = getChild("teleport_btn"); + mTeleportBtn->setClickedCallback(boost::bind(&LLPanelPlaces::onTeleportButtonClicked, this)); + + mShowOnMapBtn = getChild("map_btn"); + mShowOnMapBtn->setClickedCallback(boost::bind(&LLPanelPlaces::onShowOnMapButtonClicked, this)); + + mSaveBtn = getChild("save_btn"); + mSaveBtn->setClickedCallback(boost::bind(&LLPanelPlaces::onSaveButtonClicked, this)); + + mCancelBtn = getChild("cancel_btn"); + mCancelBtn->setClickedCallback(boost::bind(&LLPanelPlaces::onCancelButtonClicked, this)); + + mCloseBtn = getChild("close_btn"); + mCloseBtn->setClickedCallback(boost::bind(&LLPanelPlaces::onBackButtonClicked, this)); + + mOverflowBtn = getChild("overflow_btn"); + mOverflowBtn->setMouseDownCallback(boost::bind(&LLPanelPlaces::onOverflowButtonClicked, this)); + + mGearMenuButton = getChild("options_gear_btn"); + mGearMenuButton->setMouseDownCallback(boost::bind(&LLPanelPlaces::onGearMenuClick, this)); + + mSortingMenuButton = getChild("sorting_menu_btn"); + mSortingMenuButton->setMouseDownCallback(boost::bind(&LLPanelPlaces::onSortingMenuClick, this)); + + mAddMenuButton = getChild("add_menu_btn"); + mAddMenuButton->setMouseDownCallback(boost::bind(&LLPanelPlaces::onAddMenuClick, this)); + + mRemoveSelectedBtn = getChild("trash_btn"); + mRemoveSelectedBtn->setClickedCallback(boost::bind(&LLPanelPlaces::onRemoveButtonClicked, this)); + + LLDragAndDropButton* trash_btn = (LLDragAndDropButton*)mRemoveSelectedBtn; + trash_btn->setDragAndDropHandler(boost::bind(&LLPanelPlaces::handleDragAndDropToTrash, this + , _4 // bool drop + , _5 // EDragAndDropType cargo_type + , _6 // void* cargo_data + , _7 // EAcceptance* accept + )); + + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + registrar.add("Places.OverflowMenu.Action", boost::bind(&LLPanelPlaces::onOverflowMenuItemClicked, this, _2)); + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + enable_registrar.add("Places.OverflowMenu.Enable", boost::bind(&LLPanelPlaces::onOverflowMenuItemEnable, this, _2)); + + mPlaceMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_place.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + if (mPlaceMenu) + { + mPlaceMenu->setAlwaysShowMenu(true); + } + else + { + LL_WARNS() << "Error loading Place menu" << LL_ENDL; + } + + mLandmarkMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_landmark.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + if (!mLandmarkMenu) + { + LL_WARNS() << "Error loading Landmark menu" << LL_ENDL; + } + + mTabContainer = getChild("Places Tabs"); + if (mTabContainer) + { + mTabContainer->setCommitCallback(boost::bind(&LLPanelPlaces::onTabSelected, this)); + } + + mButtonsContainer = getChild("button_layout_panel"); + mButtonsContainer->setVisible(false); + mFilterContainer = getChild("top_menu_panel"); + + mFilterEditor = getChild("Filter"); + if (mFilterEditor) + { + //when list item is being clicked the filter editor looses focus + //committing on focus lost leads to detaching list items + //BUT a detached list item cannot be made selected and must not be clicked onto + mFilterEditor->setCommitOnFocusLost(false); + + mFilterEditor->setCommitCallback(boost::bind(&LLPanelPlaces::onFilterEdit, this, _2, false)); + } + + mPlaceProfile = findChild("panel_place_profile"); + mLandmarkInfo = findChild("panel_landmark_info"); + if (!mPlaceProfile || !mLandmarkInfo) + return false; + + mPlaceProfileBackBtn = mPlaceProfile->getChild("back_btn"); + mPlaceProfileBackBtn->setClickedCallback(boost::bind(&LLPanelPlaces::onBackButtonClicked, this)); + + mLandmarkInfo->getChild("back_btn")->setClickedCallback(boost::bind(&LLPanelPlaces::onBackButtonClicked, this)); + + LLLineEditor* title_editor = mLandmarkInfo->getChild("title_editor"); + title_editor->setKeystrokeCallback(boost::bind(&LLPanelPlaces::onEditButtonClicked, this), NULL); + + LLTextEditor* notes_editor = mLandmarkInfo->getChild("notes_editor"); + notes_editor->setKeystrokeCallback(boost::bind(&LLPanelPlaces::onEditButtonClicked, this)); + + LLComboBox* folder_combo = mLandmarkInfo->getChild("folder_combo"); + folder_combo->setCommitCallback(boost::bind(&LLPanelPlaces::onEditButtonClicked, this)); + + LLButton* edit_btn = mLandmarkInfo->getChild("edit_btn"); + edit_btn->setCommitCallback(boost::bind(&LLPanelPlaces::onEditButtonClicked, this)); + + createTabs(); + updateVerbs(); + + return true; +} + +void LLPanelPlaces::onOpen(const LLSD& key) +{ + if (!mPlaceProfile || !mLandmarkInfo) + return; + + if (key.size() != 0) + { + isLandmarkEditModeOn = false; + std::string key_type = key["type"].asString(); + if (key_type == LANDMARK_TAB_INFO_TYPE) + { + // Small hack: We need to toggle twice. The first toggle moves from the Landmark + // or Teleport History info panel to the Landmark or Teleport History list panel. + // For this first toggle, the mPlaceInfoType should be the one previously used so + // that the state can be corretly set. + // The second toggle forces the list to be set to Landmark. + // This avoids extracting and duplicating all the state logic from togglePlaceInfoPanel() + // here or some specific private method + togglePlaceInfoPanel(false); + mPlaceInfoType = key_type; + togglePlaceInfoPanel(false); + // Update the active tab + onTabSelected(); + // Update the buttons at the bottom of the panel + updateVerbs(); + } + else if (key_type == CREATE_PICK_TYPE) + { + LLUUID item_id = key["item_id"]; + + LLLandmarksPanel* landmarks_panel = + dynamic_cast(mTabContainer->getPanelByName("Landmarks")); + if (landmarks_panel && item_id.notNull()) + { + LLLandmark* landmark = LLLandmarkActions::getLandmark(item_id, boost::bind(&LLLandmarksPanel::doCreatePick, landmarks_panel, _1, item_id)); + if (landmark) + { + landmarks_panel->doCreatePick(landmark, item_id); + } + } + } + else // "create_landmark" + { + mFilterEditor->clear(); + onFilterEdit("", false); + + mPlaceInfoType = key_type; + mPosGlobal.setZero(); + mItem = NULL; + mRegionId.setNull(); + togglePlaceInfoPanel(true); + + if (mPlaceInfoType == AGENT_INFO_TYPE) + { + mPlaceProfile->setInfoType(LLPanelPlaceInfo::AGENT); + if (gAgent.getRegion()) + { + mRegionId = gAgent.getRegion()->getRegionID(); + } + } + else if (mPlaceInfoType == CREATE_LANDMARK_INFO_TYPE) + { + LLUUID dest_folder = key["dest_folder"]; + mLandmarkInfo->setInfoAndCreateLandmark(dest_folder); + + if (key.has("x") && key.has("y") && key.has("z")) + { + mPosGlobal = LLVector3d(key["x"].asReal(), + key["y"].asReal(), + key["z"].asReal()); + } + else + { + mPosGlobal = gAgent.getPositionGlobal(); + } + + mLandmarkInfo->displayParcelInfo(LLUUID(), mPosGlobal); + + mSaveBtn->setEnabled(false); + } + else if (mPlaceInfoType == LANDMARK_INFO_TYPE) + { + mLandmarkInfo->setInfoType(LLPanelPlaceInfo::LANDMARK); + + LLUUID id = key["id"].asUUID(); + LLInventoryItem* item = gInventory.getItem(id); + if (!item) + return; + + bool is_editable = gInventory.isObjectDescendentOf(id, gInventory.getRootFolderID()) + && item->getPermissions().allowModifyBy(gAgent.getID()); + mLandmarkInfo->setCanEdit(is_editable); + + setItem(item); + } + else if (mPlaceInfoType == REMOTE_PLACE_INFO_TYPE) + { + if (key.has("id")) + { + LLUUID parcel_id = key["id"].asUUID(); + mPlaceProfile->setParcelID(parcel_id); + + // query the server to get the global 3D position of this + // parcel - we need this for teleport/mapping functions. + mRemoteParcelObserver->setParcelID(parcel_id); + } + else + { + mPosGlobal = LLVector3d(key["x"].asReal(), + key["y"].asReal(), + key["z"].asReal()); + mPlaceProfile->displayParcelInfo(LLUUID(), mPosGlobal); + } + + mPlaceProfile->setInfoType(LLPanelPlaceInfo::PLACE); + } + else if (mPlaceInfoType == TELEPORT_HISTORY_INFO_TYPE) + { + S32 index = key["id"].asInteger(); + + const LLTeleportHistoryStorage::slurl_list_t& hist_items = + LLTeleportHistoryStorage::getInstance()->getItems(); + + mPosGlobal = hist_items[index].mGlobalPos; + + mPlaceProfile->setInfoType(LLPanelPlaceInfo::TELEPORT_HISTORY); + mPlaceProfile->displayParcelInfo(LLUUID(), mPosGlobal); + } + + updateVerbs(); + } + } + + LLViewerParcelMgr* parcel_mgr = LLViewerParcelMgr::getInstance(); + if (!parcel_mgr) + return; + + mParcelLocalId = parcel_mgr->getAgentParcel()->getLocalID(); + + // Start using LLViewerParcelMgr for land selection if + // information about nearby land is requested. + // Otherwise stop using land selection and deselect land. + if (mPlaceInfoType == AGENT_INFO_TYPE) + { + // We don't know if we are already added to LLViewerParcelMgr observers list + // so try to remove observer not to add an extra one. + parcel_mgr->removeObserver(mParcelObserver); + + parcel_mgr->addObserver(mParcelObserver); + parcel_mgr->selectParcelAt(gAgent.getPositionGlobal()); + } + else + { + parcel_mgr->removeObserver(mParcelObserver); + + // Clear the reference to selection to allow its removal in deselectUnused(). + mParcel.clear(); + + if (!parcel_mgr->selectionEmpty()) + { + parcel_mgr->deselectUnused(); + } + } +} + +void LLPanelPlaces::setItem(LLInventoryItem* item) +{ + if (!mLandmarkInfo || !item) + return; + + mItem = item; + + LLAssetType::EType item_type = mItem->getActualType(); + if (item_type == LLAssetType::AT_LANDMARK || item_type == LLAssetType::AT_LINK) + { + // If the item is a link get a linked item + if (item_type == LLAssetType::AT_LINK) + { + mItem = gInventory.getItem(mItem->getLinkedUUID()); + if (mItem.isNull()) + return; + } + } + else + { + return; + } + + // Check if item is in agent's inventory and he has the permission to modify it. + bool is_landmark_editable = gInventory.isObjectDescendentOf(mItem->getUUID(), gInventory.getRootFolderID()) && + mItem->getPermissions().allowModifyBy(gAgent.getID()); + + mSaveBtn->setEnabled(is_landmark_editable); + + if (is_landmark_editable) + { + if(!mLandmarkInfo->setLandmarkFolder(mItem->getParentUUID()) && !mItem->getParentUUID().isNull()) + { + const LLViewerInventoryCategory* cat = gInventory.getCategory(mItem->getParentUUID()); + if (cat) + { + std::string cat_fullname = LLPanelLandmarkInfo::getFullFolderName(cat); + LLComboBox* folderList = mLandmarkInfo->getChild("folder_combo"); + folderList->add(cat_fullname, cat->getUUID(), ADD_TOP); + } + } + } + + mLandmarkInfo->displayItemInfo(mItem); + + LLLandmark* lm = gLandmarkList.getAsset(mItem->getAssetUUID(), + boost::bind(&LLPanelPlaces::onLandmarkLoaded, this, _1)); + if (lm) + { + onLandmarkLoaded(lm); + } +} + +S32 LLPanelPlaces::notifyParent(const LLSD& info) +{ + if(info.has("update_verbs")) + { + if(mPosGlobal.isExactlyZero()) + { + mPosGlobal.setVec(info["global_x"], info["global_y"], info["global_z"]); + } + + updateVerbs(); + + return 1; + } + return LLPanel::notifyParent(info); +} + +void LLPanelPlaces::onLandmarkLoaded(LLLandmark* landmark) +{ + if (!mLandmarkInfo) + return; + + LLUUID region_id; + landmark->getRegionID(region_id); + landmark->getGlobalPos(mPosGlobal); + mLandmarkInfo->displayParcelInfo(region_id, mPosGlobal); + + updateVerbs(); +} + +void LLPanelPlaces::onFilterEdit(const std::string& search_string, bool force_filter) +{ + if (!mActivePanel) + return; + + if (force_filter || mActivePanel->getFilterSubString() != search_string) + { + std::string string = search_string; + + // Searches are case-insensitive + // but we don't convert the typed string to upper-case so that it can be fed to the web search as-is. + + mActivePanel->onSearchEdit(string); + } +} + +void LLPanelPlaces::onTabSelected() +{ + mActivePanel = dynamic_cast(mTabContainer->getCurrentPanel()); + if (!mActivePanel) + return; + + onFilterEdit(mActivePanel->getFilterSubString(), true); + mActivePanel->updateVerbs(); + + // History panel does not support deletion nor creation + // Hide menus + bool supports_create = mActivePanel->getCreateMenu() != NULL; + childSetVisible("add_btn_panel", supports_create); + + // favorites and inventory can remove items, history can clear history + childSetVisible("trash_btn_panel", true); + + if (supports_create) + { + mRemoveSelectedBtn->setToolTip(getString("tooltip_trash_items")); + } + else + { + mRemoveSelectedBtn->setToolTip(getString("tooltip_trash_history")); + } +} + +void LLPanelPlaces::onTeleportButtonClicked() +{ + LLPanelPlaceInfo* panel = getCurrentInfoPanel(); + if (panel && panel->getVisible()) + { + if (mPlaceInfoType == LANDMARK_INFO_TYPE) + { + if (mItem.isNull()) + { + LL_WARNS() << "NULL landmark item" << LL_ENDL; + llassert(mItem.notNull()); + return; + } + + LLSD payload; + payload["asset_id"] = mItem->getAssetUUID(); + LLSD args; + args["LOCATION"] = mItem->getName(); + LLNotificationsUtil::add("TeleportFromLandmark", args, payload); + } + else if (mPlaceInfoType == AGENT_INFO_TYPE || + mPlaceInfoType == REMOTE_PLACE_INFO_TYPE || + mPlaceInfoType == TELEPORT_HISTORY_INFO_TYPE) + { + LLFloaterWorldMap* worldmap_instance = LLFloaterWorldMap::getInstance(); + if (!mPosGlobal.isExactlyZero() && worldmap_instance) + { + gAgent.teleportViaLocation(mPosGlobal); + worldmap_instance->trackLocation(mPosGlobal); + } + } + } + else + { + if (mActivePanel) + mActivePanel->onTeleport(); + } +} + +void LLPanelPlaces::onShowOnMapButtonClicked() +{ + LLPanelPlaceInfo* panel = getCurrentInfoPanel(); + if (panel && panel->getVisible()) + { + LLFloaterWorldMap* worldmap_instance = LLFloaterWorldMap::getInstance(); + if(!worldmap_instance) + return; + + if (mPlaceInfoType == AGENT_INFO_TYPE || + mPlaceInfoType == CREATE_LANDMARK_INFO_TYPE || + mPlaceInfoType == REMOTE_PLACE_INFO_TYPE || + mPlaceInfoType == TELEPORT_HISTORY_INFO_TYPE) + { + if (!mPosGlobal.isExactlyZero()) + { + worldmap_instance->trackLocation(mPosGlobal); + LLFloaterReg::showInstance("world_map", "center"); + } + } + else if (mPlaceInfoType == LANDMARK_INFO_TYPE) + { + if (mItem.isNull()) + { + LL_WARNS() << "NULL landmark item" << LL_ENDL; + llassert(mItem.notNull()); + return; + } + LLLandmark* landmark = gLandmarkList.getAsset(mItem->getAssetUUID()); + if (!landmark) + return; + + LLVector3d landmark_global_pos; + if (!landmark->getGlobalPos(landmark_global_pos)) + return; + + if (!landmark_global_pos.isExactlyZero()) + { + worldmap_instance->trackLocation(landmark_global_pos); + LLFloaterReg::showInstance("world_map", "center"); + } + } + } + else + { + if (mActivePanel && mActivePanel->isSingleItemSelected()) + { + mActivePanel->onShowOnMap(); + } + else + { + LLFloaterWorldMap* worldmap_instance = LLFloaterWorldMap::getInstance(); + LLVector3d global_pos = gAgent.getPositionGlobal(); + + if (!global_pos.isExactlyZero() && worldmap_instance) + { + worldmap_instance->trackLocation(global_pos); + LLFloaterReg::showInstance("world_map", "center"); + } + } + } +} + +void LLPanelPlaces::onEditButtonClicked() +{ + if (!mLandmarkInfo || isLandmarkEditModeOn) + return; + + isLandmarkEditModeOn = true; + + mLandmarkInfo->toggleLandmarkEditMode(true); + + updateVerbs(); +} + +void LLPanelPlaces::onSaveButtonClicked() +{ + if (!mLandmarkInfo || mItem.isNull()) + return; + + std::string current_title_value = mLandmarkInfo->getLandmarkTitle(); + std::string item_title_value = mItem->getName(); + std::string current_notes_value = mLandmarkInfo->getLandmarkNotes(); + std::string item_notes_value = mItem->getDescription(); + + LLStringUtil::trim(current_title_value); + LLStringUtil::trim(current_notes_value); + + LLUUID folder_id = mLandmarkInfo->getLandmarkFolder(); + bool change_parent = folder_id != mItem->getParentUUID(); + + LLPointer new_item = new LLViewerInventoryItem(mItem); + + if (!current_title_value.empty() && + (item_title_value != current_title_value || item_notes_value != current_notes_value)) + { + new_item->rename(current_title_value); + new_item->setDescription(current_notes_value); + LLPointer cb; + if (change_parent) + { + cb = new LLUpdateLandmarkParent(new_item, folder_id); + } + LLInventoryModel::LLCategoryUpdate up(mItem->getParentUUID(), 0); + gInventory.accountForUpdate(up); + update_inventory_item(new_item, cb); + } + else if (change_parent) + { + LLInventoryModel::update_list_t update; + LLInventoryModel::LLCategoryUpdate old_folder(mItem->getParentUUID(),-1); + update.push_back(old_folder); + LLInventoryModel::LLCategoryUpdate new_folder(folder_id, 1); + update.push_back(new_folder); + gInventory.accountForUpdate(update); + + new_item->setParent(folder_id); + new_item->updateParentOnServer(false); + } + + gInventory.updateItem(new_item); + gInventory.notifyObservers(); + + onCancelButtonClicked(); +} + +void LLPanelPlaces::onCancelButtonClicked() +{ + if (!mLandmarkInfo) + return; + + if (mPlaceInfoType == CREATE_LANDMARK_INFO_TYPE) + { + onBackButtonClicked(); + } + else + { + mLandmarkInfo->toggleLandmarkEditMode(false); + isLandmarkEditModeOn = false; + + updateVerbs(); + + // Reload the landmark properties. + mLandmarkInfo->displayItemInfo(mItem); + } +} + +void LLPanelPlaces::onOverflowButtonClicked() +{ + LLToggleableMenu* menu; + + bool is_agent_place_info_visible = mPlaceInfoType == AGENT_INFO_TYPE; + + if ((is_agent_place_info_visible || + mPlaceInfoType == REMOTE_PLACE_INFO_TYPE || + mPlaceInfoType == TELEPORT_HISTORY_INFO_TYPE) && mPlaceMenu != NULL) + { + menu = mPlaceMenu; + + bool landmark_item_enabled = false; + LLViewerParcelMgr* parcel_mgr = LLViewerParcelMgr::getInstance(); + if (is_agent_place_info_visible + && gAgent.getRegion() + && mRegionId == gAgent.getRegion()->getRegionID() + && parcel_mgr + && parcel_mgr->getAgentParcel()->getLocalID() == mParcelLocalId) + { + // Floater still shows location identical to agent's position + landmark_item_enabled = !LLLandmarkActions::landmarkAlreadyExists(); + } + + // Enable adding a landmark only for agent current parcel and if + // there is no landmark already pointing to that parcel in agent's inventory. + menu->getChild("landmark")->setEnabled(landmark_item_enabled); + // STORM-411 + // Creating landmarks for remote locations is impossible. + // So hide menu item "Make a Landmark" in "Teleport History Profile" panel. + menu->setItemVisible("landmark", mPlaceInfoType != TELEPORT_HISTORY_INFO_TYPE); + menu->arrangeAndClear(); + } + else if (mPlaceInfoType == LANDMARK_INFO_TYPE && mLandmarkMenu != NULL) + { + menu = mLandmarkMenu; + + bool is_landmark_removable = false; + if (mItem.notNull()) + { + const LLUUID& item_id = mItem->getUUID(); + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + is_landmark_removable = gInventory.isObjectDescendentOf(item_id, gInventory.getRootFolderID()) && + !gInventory.isObjectDescendentOf(item_id, trash_id); + } + + menu->getChild("delete")->setEnabled(is_landmark_removable); + } + else + { + return; + } + + mOverflowBtn->setMenu(menu, LLMenuButton::MP_TOP_RIGHT); +} + +bool LLPanelPlaces::onOverflowMenuItemEnable(const LLSD& param) +{ + std::string value = param.asString(); + if("can_create_pick" == value) + { + return !LLAgentPicksInfo::getInstance()->isPickLimitReached(); + } + return true; +} + +void LLPanelPlaces::onOverflowMenuItemClicked(const LLSD& param) +{ + std::string item = param.asString(); + if (item == "landmark") + { + LLSD key; + key["type"] = CREATE_LANDMARK_INFO_TYPE; + key["x"] = mPosGlobal.mdV[VX]; + key["y"] = mPosGlobal.mdV[VY]; + key["z"] = mPosGlobal.mdV[VZ]; + onOpen(key); + } + else if (item == "copy") + { + LLLandmarkActions::getSLURLfromPosGlobal(mPosGlobal, boost::bind(&onSLURLBuilt, _1)); + } + else if (item == "delete") + { + gInventory.removeItem(mItem->getUUID()); + + onBackButtonClicked(); + } + else if (item == "pick") + { + LLPanelPlaceInfo* panel = getCurrentInfoPanel(); + if (panel) + { + panel->createPick(mPosGlobal); + } + } + else if (item == "add_to_favbar") + { + if ( mItem.notNull() ) + { + const LLUUID& favorites_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); + if ( favorites_id.notNull() ) + { + copy_inventory_item(gAgent.getID(), + mItem->getPermissions().getOwner(), + mItem->getUUID(), + favorites_id, + std::string(), + LLPointer(NULL)); + LL_INFOS() << "Copied inventory item #" << mItem->getUUID() << " to favorites." << LL_ENDL; + } + } + } +} + +void LLPanelPlaces::onBackButtonClicked() +{ + togglePlaceInfoPanel(false); + + // Resetting mPlaceInfoType when Place Info panel is closed. + mPlaceInfoType = LLStringUtil::null; + + isLandmarkEditModeOn = false; + + updateVerbs(); +} + +void LLPanelPlaces::onGearMenuClick() +{ + if (mActivePanel) + { + LLToggleableMenu* menu = mActivePanel->getSelectionMenu(); + mGearMenuButton->setMenu(menu, LLMenuButton::MP_BOTTOM_LEFT); + } +} + +void LLPanelPlaces::onSortingMenuClick() +{ + if (mActivePanel) + { + LLToggleableMenu* menu = mActivePanel->getSortingMenu(); + mSortingMenuButton->setMenu(menu, LLMenuButton::MP_BOTTOM_LEFT); + } +} + +void LLPanelPlaces::onAddMenuClick() +{ + if (mActivePanel) + { + LLToggleableMenu* menu = mActivePanel->getCreateMenu(); + mAddMenuButton->setMenu(menu, LLMenuButton::MP_BOTTOM_LEFT); + } +} + +void LLPanelPlaces::onRemoveButtonClicked() +{ + if (mActivePanel) + { + mActivePanel->onRemoveSelected(); + } +} + +bool LLPanelPlaces::handleDragAndDropToTrash(bool drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept) +{ + if (mActivePanel) + { + return mActivePanel->handleDragAndDropToTrash(drop, cargo_type, cargo_data, accept); + } + return false; +} + +void LLPanelPlaces::togglePlaceInfoPanel(bool visible) +{ + if (!mPlaceProfile || !mLandmarkInfo) + return; + + mTabContainer->setVisible(!visible); + mButtonsContainer->setVisible(visible); + mFilterContainer->setVisible(!visible); + + if (mPlaceInfoType == AGENT_INFO_TYPE || + mPlaceInfoType == REMOTE_PLACE_INFO_TYPE || + mPlaceInfoType == TELEPORT_HISTORY_INFO_TYPE) + { + mPlaceProfile->setVisible(visible); + + if (visible) + { + mPlaceProfile->resetLocation(); + + // Do not reset location info until mResetInfoTimer has expired + // to avoid text blinking. + mResetInfoTimer.setTimerExpirySec(PLACE_INFO_UPDATE_INTERVAL); + + mLandmarkInfo->setVisible(false); + } + else if (mPlaceInfoType == AGENT_INFO_TYPE) + { + LLViewerParcelMgr::getInstance()->removeObserver(mParcelObserver); + + // Clear reference to parcel selection when closing place profile panel. + // LLViewerParcelMgr removes the selection if it has 1 reference to it. + mParcel.clear(); + } + } + else if (mPlaceInfoType == CREATE_LANDMARK_INFO_TYPE || + mPlaceInfoType == LANDMARK_INFO_TYPE || + mPlaceInfoType == LANDMARK_TAB_INFO_TYPE) + { + mLandmarkInfo->setVisible(visible); + mPlaceProfile->setVisible(false); + if (visible) + { + mLandmarkInfo->resetLocation(); + } + else + { + std::string tab_panel_name("Landmarks"); + if (mItem.notNull()) + { + if (gInventory.isObjectDescendentOf(mItem->getUUID(), gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE))) + { + tab_panel_name = "Favorites"; + } + } + + LLLandmarksPanel* landmarks_panel = dynamic_cast(mTabContainer->getPanelByName(tab_panel_name)); + if (landmarks_panel) + { + // If a landmark info is being closed we open the landmarks tab + // and set this landmark selected. + mTabContainer->selectTabPanel(landmarks_panel); + if (mItem.notNull()) + { + landmarks_panel->setItemSelected(mItem->getUUID(), true); + } + else + { + landmarks_panel->resetSelection(); + } + } + } + } +} + +// virtual +void LLPanelPlaces::onVisibilityChange(bool new_visibility) +{ + LLPanel::onVisibilityChange(new_visibility); + + if (!new_visibility && mPlaceInfoType == AGENT_INFO_TYPE) + { + LLViewerParcelMgr::getInstance()->removeObserver(mParcelObserver); + + // Clear reference to parcel selection when closing places panel. + mParcel.clear(); + } +} + +void LLPanelPlaces::changedParcelSelection() +{ + if (!mPlaceProfile) + return; + + LLViewerParcelMgr* parcel_mgr = LLViewerParcelMgr::getInstance(); + mParcel = parcel_mgr->getFloatingParcelSelection(); + LLParcel* parcel = mParcel->getParcel(); + LLViewerRegion* region = parcel_mgr->getSelectionRegion(); + if (!region || !parcel) + return; + + LLVector3d prev_pos_global = mPosGlobal; + + // If agent is inside the selected parcel show agent's region, + // otherwise show region of agent's selection point. + bool is_current_parcel = is_agent_in_selected_parcel(parcel); + if (is_current_parcel) + { + mPosGlobal = gAgent.getPositionGlobal(); + } + else + { + LLVector3d pos_global = gViewerWindow->getLastPick().mPosGlobal; + if (!pos_global.isExactlyZero()) + { + mPosGlobal = pos_global; + } + } + + // Reset location info only if global position has changed + // and update timer has expired to reduce unnecessary text and icons updates. + if (prev_pos_global != mPosGlobal && mResetInfoTimer.hasExpired()) + { + mPlaceProfile->resetLocation(); + mResetInfoTimer.setTimerExpirySec(PLACE_INFO_UPDATE_INTERVAL); + } + + mPlaceProfile->displaySelectedParcelInfo(parcel, region, mPosGlobal, is_current_parcel); + + updateVerbs(); +} + +void LLPanelPlaces::createTabs() +{ + if (!(gInventory.isInventoryUsable() && LLTeleportHistory::getInstance() && !mTabsCreated)) + return; + + LLFavoritesPanel* favorites_panel = new LLFavoritesPanel(); + if (favorites_panel) + { + mTabContainer->addTabPanel( + LLTabContainer::TabPanelParams(). + panel(favorites_panel). + label(getString("favorites_tab_title")). + insert_at(LLTabContainer::END)); + } + + LLLandmarksPanel* landmarks_panel = new LLLandmarksPanel(); + if (landmarks_panel) + { + mTabContainer->addTabPanel( + LLTabContainer::TabPanelParams(). + panel(landmarks_panel). + label(getString("landmarks_tab_title")). + insert_at(LLTabContainer::END)); + } + + LLTeleportHistoryPanel* teleport_history_panel = new LLTeleportHistoryPanel(); + if (teleport_history_panel) + { + mTabContainer->addTabPanel( + LLTabContainer::TabPanelParams(). + panel(teleport_history_panel). + label(getString("teleport_history_tab_title")). + insert_at(LLTabContainer::END)); + } + + mTabContainer->selectFirstTab(); + + mActivePanel = dynamic_cast(mTabContainer->getCurrentPanel()); + + if (mActivePanel) + { + // Filter applied to show all items. + mActivePanel->onSearchEdit(mActivePanel->getFilterSubString()); + + // History panel does not support deletion nor creation + // Hide menus + bool supports_create = mActivePanel->getCreateMenu() != NULL; + childSetVisible("add_btn_panel", supports_create); + + // favorites and inventory can remove items, history can clear history + childSetVisible("trash_btn_panel", true); + + if (supports_create) + { + mRemoveSelectedBtn->setToolTip(getString("tooltip_trash_items")); + } + else + { + mRemoveSelectedBtn->setToolTip(getString("tooltip_trash_history")); + } + + mActivePanel->setRemoveBtn(mRemoveSelectedBtn); + mActivePanel->updateVerbs(); + } + + mTabsCreated = true; +} + +void LLPanelPlaces::changedGlobalPos(const LLVector3d &global_pos) +{ + mPosGlobal = global_pos; + updateVerbs(); +} + +void LLPanelPlaces::showAddedLandmarkInfo(const uuid_set_t& items) +{ + for (uuid_set_t::const_iterator item_iter = items.begin(); + item_iter != items.end(); + ++item_iter) + { + const LLUUID& item_id = (*item_iter); + if(!highlight_offered_object(item_id)) + { + continue; + } + + LLInventoryItem* item = gInventory.getItem(item_id); + + llassert(item); + if (item && (LLAssetType::AT_LANDMARK == item->getType()) ) + { + // Created landmark is passed to Places panel to allow its editing. + // If the panel is closed we don't reopen it until created landmark is loaded. + if("create_landmark" == getPlaceInfoType() && !getItem()) + { + setItem(item); + } + } + } +} + +void LLPanelPlaces::updateVerbs() +{ + bool is_place_info_visible; + + LLPanelPlaceInfo* panel = getCurrentInfoPanel(); + if (panel) + { + is_place_info_visible = panel->getVisible(); + } + else + { + is_place_info_visible = false; + } + + bool is_agent_place_info_visible = mPlaceInfoType == AGENT_INFO_TYPE; + bool is_create_landmark_visible = mPlaceInfoType == CREATE_LANDMARK_INFO_TYPE; + + bool have_3d_pos = ! mPosGlobal.isExactlyZero(); + + mTeleportBtn->setVisible(!is_create_landmark_visible && !isLandmarkEditModeOn); + mShowOnMapBtn->setVisible(!is_create_landmark_visible && !isLandmarkEditModeOn); + mSaveBtn->setVisible(isLandmarkEditModeOn); + mCancelBtn->setVisible(isLandmarkEditModeOn); + mCloseBtn->setVisible(is_create_landmark_visible && !isLandmarkEditModeOn); + + bool show_options_btn = is_place_info_visible && !is_create_landmark_visible && !isLandmarkEditModeOn; + mOverflowBtn->setVisible(show_options_btn); + getChild("lp_options")->setVisible(show_options_btn); + getChild("lp2")->setVisible(!show_options_btn); + + if (is_place_info_visible) + { + mShowOnMapBtn->setEnabled(have_3d_pos); + + if (is_agent_place_info_visible) + { + // We don't need to teleport to the current location + // so check if the location is not within the current parcel. + mTeleportBtn->setEnabled(have_3d_pos && + !LLViewerParcelMgr::getInstance()->inAgentParcel(mPosGlobal)); + } + else if (mPlaceInfoType == LANDMARK_INFO_TYPE || mPlaceInfoType == REMOTE_PLACE_INFO_TYPE) + { + mTeleportBtn->setEnabled(have_3d_pos); + } + } + else + { + if (mActivePanel) + mActivePanel->updateVerbs(); + } +} + +LLPanelPlaceInfo* LLPanelPlaces::getCurrentInfoPanel() +{ + if (mPlaceInfoType == AGENT_INFO_TYPE || + mPlaceInfoType == REMOTE_PLACE_INFO_TYPE || + mPlaceInfoType == TELEPORT_HISTORY_INFO_TYPE) + { + return mPlaceProfile; + } + else if (mPlaceInfoType == CREATE_LANDMARK_INFO_TYPE || + mPlaceInfoType == LANDMARK_INFO_TYPE || + mPlaceInfoType == LANDMARK_TAB_INFO_TYPE) + { + return mLandmarkInfo; + } + + return NULL; +} + +static bool is_agent_in_selected_parcel(LLParcel* parcel) +{ + LLViewerParcelMgr* parcel_mgr = LLViewerParcelMgr::getInstance(); + + LLViewerRegion* region = parcel_mgr->getSelectionRegion(); + if (!region || !parcel) + return false; + + return region == gAgent.getRegion() && + parcel->getLocalID() == parcel_mgr->getAgentParcel()->getLocalID(); +} + +static void onSLURLBuilt(std::string& slurl) +{ + LLView::getWindow()->copyTextToClipboard(utf8str_to_wstring(slurl)); + + LLSD args; + args["SLURL"] = slurl; + + LLNotificationsUtil::add("CopySLURL", args); +} diff --git a/indra/newview/llpanelplaces.h b/indra/newview/llpanelplaces.h index ce50374cc1..fc04d8d45d 100644 --- a/indra/newview/llpanelplaces.h +++ b/indra/newview/llpanelplaces.h @@ -1,169 +1,169 @@ -/** - * @file llpanelplaces.h - * @brief Side Bar "Places" panel - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELPLACES_H -#define LL_LLPANELPLACES_H - -#include "lltimer.h" - -#include "llpanel.h" - -class LLInventoryItem; -class LLFilterEditor; -class LLLandmark; - -class LLPanelLandmarkInfo; -class LLPanelPlaceProfile; - -class LLPanelPlaceInfo; -class LLPanelPlacesTab; -class LLParcelSelection; -class LLPlacesInventoryObserver; -class LLPlacesParcelObserver; -class LLRemoteParcelInfoObserver; -class LLTabContainer; -class LLToggleableMenu; -class LLMenuButton; -class LLLayoutStack; - -typedef std::pair folder_pair_t; - -class LLPanelPlaces : public LLPanel -{ -public: - LLPanelPlaces(); - virtual ~LLPanelPlaces(); - - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); - - // Called on parcel selection change to update place information. - void changedParcelSelection(); - // Called once on agent inventory first change to find out when inventory gets usable - // and to create "My Landmarks" and "Teleport History" tabs. - void createTabs(); - // Called when we receive the global 3D position of a parcel. - void changedGlobalPos(const LLVector3d &global_pos); - - // Opens landmark info panel when agent creates or receives landmark. - void showAddedLandmarkInfo(const uuid_set_t& items); - - void setItem(LLInventoryItem* item); - - LLInventoryItem* getItem() { return mItem; } - - std::string getPlaceInfoType() { return mPlaceInfoType; } - - bool tabsCreated() { return mTabsCreated;} - - /*virtual*/ S32 notifyParent(const LLSD& info); - -private: - void onLandmarkLoaded(LLLandmark* landmark); - void onFilterEdit(const std::string& search_string, bool force_filter); - void onTabSelected(); - - void onTeleportButtonClicked(); - void onShowOnMapButtonClicked(); - void onEditButtonClicked(); - void onSaveButtonClicked(); - void onCancelButtonClicked(); - void onOverflowButtonClicked(); - void onOverflowMenuItemClicked(const LLSD& param); - bool onOverflowMenuItemEnable(const LLSD& param); - void onBackButtonClicked(); - void onGearMenuClick(); - void onSortingMenuClick(); - void onAddMenuClick(); - void onRemoveButtonClicked(); - bool handleDragAndDropToTrash(bool drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept); - - void togglePlaceInfoPanel(bool visible); - - /*virtual*/ void onVisibilityChange(bool new_visibility); - - void updateVerbs(); - - LLPanelPlaceInfo* getCurrentInfoPanel(); - - LLFilterEditor* mFilterEditor; - LLPanelPlacesTab* mActivePanel; - LLTabContainer* mTabContainer; - LLPanel* mButtonsContainer; - LLLayoutStack* mFilterContainer; - LLPanelPlaceProfile* mPlaceProfile; - LLPanelLandmarkInfo* mLandmarkInfo; - - LLToggleableMenu* mPlaceMenu; - LLToggleableMenu* mLandmarkMenu; - - LLButton* mPlaceProfileBackBtn; - LLButton* mTeleportBtn; - LLButton* mShowOnMapBtn; - LLButton* mSaveBtn; - LLButton* mCancelBtn; - LLButton* mCloseBtn; - LLMenuButton* mOverflowBtn; - - // Top menu - LLMenuButton* mGearMenuButton; - LLMenuButton* mSortingMenuButton; - LLMenuButton* mAddMenuButton; - LLButton* mRemoveSelectedBtn; - - LLPlacesInventoryObserver* mInventoryObserver; - LLPlacesParcelObserver* mParcelObserver; - LLRemoteParcelInfoObserver* mRemoteParcelObserver; - - // Pointer to a landmark item or to a linked landmark - LLPointer mItem; - - // Absolute position of the location for teleport, may not - // be available (hence zero) - LLVector3d mPosGlobal; - - // Sets a period of time during which the requested place information - // is expected to be updated and doesn't need to be reset. - LLTimer mResetInfoTimer; - - // Information type currently shown in Place Information panel - std::string mPlaceInfoType; - - // Region and parcel ids, to detect location changes in case of AGENT_INFO_TYPE - LLUUID mRegionId; - S32 mParcelLocalId; - - bool isLandmarkEditModeOn; - - // Holds info whether "My Landmarks" and "Teleport History" tabs have been created. - bool mTabsCreated; - - LLSafeHandle mParcel; - - boost::signals2::connection mAgentParcelChangedConnection; -}; - -#endif //LL_LLPANELPLACES_H +/** + * @file llpanelplaces.h + * @brief Side Bar "Places" panel + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELPLACES_H +#define LL_LLPANELPLACES_H + +#include "lltimer.h" + +#include "llpanel.h" + +class LLInventoryItem; +class LLFilterEditor; +class LLLandmark; + +class LLPanelLandmarkInfo; +class LLPanelPlaceProfile; + +class LLPanelPlaceInfo; +class LLPanelPlacesTab; +class LLParcelSelection; +class LLPlacesInventoryObserver; +class LLPlacesParcelObserver; +class LLRemoteParcelInfoObserver; +class LLTabContainer; +class LLToggleableMenu; +class LLMenuButton; +class LLLayoutStack; + +typedef std::pair folder_pair_t; + +class LLPanelPlaces : public LLPanel +{ +public: + LLPanelPlaces(); + virtual ~LLPanelPlaces(); + + /*virtual*/ bool postBuild(); + /*virtual*/ void onOpen(const LLSD& key); + + // Called on parcel selection change to update place information. + void changedParcelSelection(); + // Called once on agent inventory first change to find out when inventory gets usable + // and to create "My Landmarks" and "Teleport History" tabs. + void createTabs(); + // Called when we receive the global 3D position of a parcel. + void changedGlobalPos(const LLVector3d &global_pos); + + // Opens landmark info panel when agent creates or receives landmark. + void showAddedLandmarkInfo(const uuid_set_t& items); + + void setItem(LLInventoryItem* item); + + LLInventoryItem* getItem() { return mItem; } + + std::string getPlaceInfoType() { return mPlaceInfoType; } + + bool tabsCreated() { return mTabsCreated;} + + /*virtual*/ S32 notifyParent(const LLSD& info); + +private: + void onLandmarkLoaded(LLLandmark* landmark); + void onFilterEdit(const std::string& search_string, bool force_filter); + void onTabSelected(); + + void onTeleportButtonClicked(); + void onShowOnMapButtonClicked(); + void onEditButtonClicked(); + void onSaveButtonClicked(); + void onCancelButtonClicked(); + void onOverflowButtonClicked(); + void onOverflowMenuItemClicked(const LLSD& param); + bool onOverflowMenuItemEnable(const LLSD& param); + void onBackButtonClicked(); + void onGearMenuClick(); + void onSortingMenuClick(); + void onAddMenuClick(); + void onRemoveButtonClicked(); + bool handleDragAndDropToTrash(bool drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept); + + void togglePlaceInfoPanel(bool visible); + + /*virtual*/ void onVisibilityChange(bool new_visibility); + + void updateVerbs(); + + LLPanelPlaceInfo* getCurrentInfoPanel(); + + LLFilterEditor* mFilterEditor; + LLPanelPlacesTab* mActivePanel; + LLTabContainer* mTabContainer; + LLPanel* mButtonsContainer; + LLLayoutStack* mFilterContainer; + LLPanelPlaceProfile* mPlaceProfile; + LLPanelLandmarkInfo* mLandmarkInfo; + + LLToggleableMenu* mPlaceMenu; + LLToggleableMenu* mLandmarkMenu; + + LLButton* mPlaceProfileBackBtn; + LLButton* mTeleportBtn; + LLButton* mShowOnMapBtn; + LLButton* mSaveBtn; + LLButton* mCancelBtn; + LLButton* mCloseBtn; + LLMenuButton* mOverflowBtn; + + // Top menu + LLMenuButton* mGearMenuButton; + LLMenuButton* mSortingMenuButton; + LLMenuButton* mAddMenuButton; + LLButton* mRemoveSelectedBtn; + + LLPlacesInventoryObserver* mInventoryObserver; + LLPlacesParcelObserver* mParcelObserver; + LLRemoteParcelInfoObserver* mRemoteParcelObserver; + + // Pointer to a landmark item or to a linked landmark + LLPointer mItem; + + // Absolute position of the location for teleport, may not + // be available (hence zero) + LLVector3d mPosGlobal; + + // Sets a period of time during which the requested place information + // is expected to be updated and doesn't need to be reset. + LLTimer mResetInfoTimer; + + // Information type currently shown in Place Information panel + std::string mPlaceInfoType; + + // Region and parcel ids, to detect location changes in case of AGENT_INFO_TYPE + LLUUID mRegionId; + S32 mParcelLocalId; + + bool isLandmarkEditModeOn; + + // Holds info whether "My Landmarks" and "Teleport History" tabs have been created. + bool mTabsCreated; + + LLSafeHandle mParcel; + + boost::signals2::connection mAgentParcelChangedConnection; +}; + +#endif //LL_LLPANELPLACES_H diff --git a/indra/newview/llpanelplacestab.h b/indra/newview/llpanelplacestab.h index cbdc97200d..90ad83550e 100644 --- a/indra/newview/llpanelplacestab.h +++ b/indra/newview/llpanelplacestab.h @@ -1,78 +1,78 @@ -/** - * @file llpanelplacestab.h - * @brief Tabs interface for Side Bar "Places" panel - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELPLACESTAB_H -#define LL_LLPANELPLACESTAB_H - -#include "llpanel.h" - -class LLPanelPlaces; -class LLToggleableMenu; - -class LLPanelPlacesTab : public LLPanel -{ -public: - LLPanelPlacesTab() : LLPanel() {} - virtual ~LLPanelPlacesTab() {} - - virtual void onSearchEdit(const std::string& string) = 0; - virtual void updateVerbs() = 0; // Updates buttons at the bottom of Places panel - virtual void onShowOnMap() = 0; - virtual void onShowProfile() = 0; - virtual void onTeleport() = 0; - virtual void onRemoveSelected() = 0; - virtual bool isSingleItemSelected() = 0; - - // returns menu for current selection - virtual LLToggleableMenu* getSelectionMenu() = 0; - virtual LLToggleableMenu* getSortingMenu() = 0; - virtual LLToggleableMenu* getCreateMenu() = 0; - - /** - * Processes drag-n-drop of the Landmarks and folders into trash button. - */ - virtual bool handleDragAndDropToTrash(bool drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept) = 0; - - bool isTabVisible(); // Check if parent TabContainer is visible. - - void onRegionResponse(const LLVector3d& landmark_global_pos, - U64 region_handle, - const std::string& url, - const LLUUID& snapshot_id, - bool teleport); - - const std::string& getFilterSubString() { return sFilterSubString; } - void setFilterSubString(const std::string& string) { sFilterSubString = string; } - - void setRemoveBtn(LLButton* trash_btn) { sRemoveBtn = trash_btn; } - -protected: - // Search string for filtering landmarks and teleport history locations - static std::string sFilterSubString; - static LLButton* sRemoveBtn; -}; - -#endif //LL_LLPANELPLACESTAB_H +/** + * @file llpanelplacestab.h + * @brief Tabs interface for Side Bar "Places" panel + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELPLACESTAB_H +#define LL_LLPANELPLACESTAB_H + +#include "llpanel.h" + +class LLPanelPlaces; +class LLToggleableMenu; + +class LLPanelPlacesTab : public LLPanel +{ +public: + LLPanelPlacesTab() : LLPanel() {} + virtual ~LLPanelPlacesTab() {} + + virtual void onSearchEdit(const std::string& string) = 0; + virtual void updateVerbs() = 0; // Updates buttons at the bottom of Places panel + virtual void onShowOnMap() = 0; + virtual void onShowProfile() = 0; + virtual void onTeleport() = 0; + virtual void onRemoveSelected() = 0; + virtual bool isSingleItemSelected() = 0; + + // returns menu for current selection + virtual LLToggleableMenu* getSelectionMenu() = 0; + virtual LLToggleableMenu* getSortingMenu() = 0; + virtual LLToggleableMenu* getCreateMenu() = 0; + + /** + * Processes drag-n-drop of the Landmarks and folders into trash button. + */ + virtual bool handleDragAndDropToTrash(bool drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept) = 0; + + bool isTabVisible(); // Check if parent TabContainer is visible. + + void onRegionResponse(const LLVector3d& landmark_global_pos, + U64 region_handle, + const std::string& url, + const LLUUID& snapshot_id, + bool teleport); + + const std::string& getFilterSubString() { return sFilterSubString; } + void setFilterSubString(const std::string& string) { sFilterSubString = string; } + + void setRemoveBtn(LLButton* trash_btn) { sRemoveBtn = trash_btn; } + +protected: + // Search string for filtering landmarks and teleport history locations + static std::string sFilterSubString; + static LLButton* sRemoveBtn; +}; + +#endif //LL_LLPANELPLACESTAB_H diff --git a/indra/newview/llpanelpresetscamerapulldown.cpp b/indra/newview/llpanelpresetscamerapulldown.cpp index 1ad1872401..5b0aa28223 100644 --- a/indra/newview/llpanelpresetscamerapulldown.cpp +++ b/indra/newview/llpanelpresetscamerapulldown.cpp @@ -1,152 +1,152 @@ -/** - * @file llpanelpresetscamerapulldown.cpp - * @brief A panel showing a quick way to pick camera presets - * - * $LicenseInfo:firstyear=2017&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2017, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelpresetscamerapulldown.h" - -#include "llviewercontrol.h" -#include "llstatusbar.h" - -#include "llbutton.h" -#include "lltabcontainer.h" -#include "llfloatercamera.h" -#include "llfloaterreg.h" -#include "llfloaterpreference.h" -#include "llpresetsmanager.h" -#include "llsliderctrl.h" -#include "llscrolllistctrl.h" -#include "lltrans.h" - -///---------------------------------------------------------------------------- -/// Class LLPanelPresetsCameraPulldown -///---------------------------------------------------------------------------- - -// Default constructor -LLPanelPresetsCameraPulldown::LLPanelPresetsCameraPulldown() -{ - mCommitCallbackRegistrar.add("Presets.toggleCameraFloater", boost::bind(&LLPanelPresetsCameraPulldown::onViewButtonClick, this, _2)); - mCommitCallbackRegistrar.add("PresetsCamera.RowClick", boost::bind(&LLPanelPresetsCameraPulldown::onRowClick, this, _2)); - - buildFromFile( "panel_presets_camera_pulldown.xml"); -} - -bool LLPanelPresetsCameraPulldown::postBuild() -{ - LLPresetsManager* presetsMgr = LLPresetsManager::getInstance(); - if (presetsMgr) - { - // Make sure there is a default preference file - presetsMgr->createMissingDefault(PRESETS_CAMERA); - - presetsMgr->startWatching(PRESETS_CAMERA); - - presetsMgr->setPresetListChangeCameraCallback(boost::bind(&LLPanelPresetsCameraPulldown::populatePanel, this)); - } - - populatePanel(); - - return LLPanelPulldown::postBuild(); -} - -void LLPanelPresetsCameraPulldown::populatePanel() -{ - LLPresetsManager::getInstance()->loadPresetNamesFromDir(PRESETS_CAMERA, mPresetNames, DEFAULT_BOTTOM); - - LLScrollListCtrl* scroll = getChild("preset_camera_list"); - - if (scroll && mPresetNames.begin() != mPresetNames.end()) - { - scroll->clearRows(); - - std::string active_preset = gSavedSettings.getString("PresetCameraActive"); - if (active_preset == PRESETS_DEFAULT) - { - active_preset = LLTrans::getString(PRESETS_DEFAULT); - } - - for (std::list::const_iterator it = mPresetNames.begin(); it != mPresetNames.end(); ++it) - { - const std::string& name = *it; - LL_DEBUGS() << "adding '" << name << "'" << LL_ENDL; - - LLSD row; - row["columns"][0]["column"] = "preset_name"; - row["columns"][0]["value"] = name; - - bool is_selected_preset = false; - if (name == active_preset) - { - row["columns"][1]["column"] = "icon"; - row["columns"][1]["type"] = "icon"; - row["columns"][1]["value"] = "Check_Mark"; - - is_selected_preset = true; - } - - LLScrollListItem* new_item = scroll->addElement(row); - new_item->setSelected(is_selected_preset); - } - } -} - -void LLPanelPresetsCameraPulldown::onRowClick(const LLSD& user_data) -{ - LLScrollListCtrl* scroll = getChild("preset_camera_list"); - - if (scroll) - { - LLScrollListItem* item = scroll->getFirstSelected(); - if (item) - { - std::string name = item->getColumn(1)->getValue().asString(); - - LL_DEBUGS() << "selected '" << name << "'" << LL_ENDL; - LLFloaterCamera::switchToPreset(name); - - // Scroll grabbed focus, drop it to prevent selection of parent menu - setFocus(false); - - setVisible(false); - } - else - { - LL_DEBUGS() << "none selected" << LL_ENDL; - } - } - else - { - LL_DEBUGS() << "no scroll" << LL_ENDL; - } -} - -void LLPanelPresetsCameraPulldown::onViewButtonClick(const LLSD& user_data) -{ - // close the minicontrol, we're bringing up the big one - setVisible(false); - - LLFloaterReg::toggleInstanceOrBringToFront("camera"); -} +/** + * @file llpanelpresetscamerapulldown.cpp + * @brief A panel showing a quick way to pick camera presets + * + * $LicenseInfo:firstyear=2017&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2017, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelpresetscamerapulldown.h" + +#include "llviewercontrol.h" +#include "llstatusbar.h" + +#include "llbutton.h" +#include "lltabcontainer.h" +#include "llfloatercamera.h" +#include "llfloaterreg.h" +#include "llfloaterpreference.h" +#include "llpresetsmanager.h" +#include "llsliderctrl.h" +#include "llscrolllistctrl.h" +#include "lltrans.h" + +///---------------------------------------------------------------------------- +/// Class LLPanelPresetsCameraPulldown +///---------------------------------------------------------------------------- + +// Default constructor +LLPanelPresetsCameraPulldown::LLPanelPresetsCameraPulldown() +{ + mCommitCallbackRegistrar.add("Presets.toggleCameraFloater", boost::bind(&LLPanelPresetsCameraPulldown::onViewButtonClick, this, _2)); + mCommitCallbackRegistrar.add("PresetsCamera.RowClick", boost::bind(&LLPanelPresetsCameraPulldown::onRowClick, this, _2)); + + buildFromFile( "panel_presets_camera_pulldown.xml"); +} + +bool LLPanelPresetsCameraPulldown::postBuild() +{ + LLPresetsManager* presetsMgr = LLPresetsManager::getInstance(); + if (presetsMgr) + { + // Make sure there is a default preference file + presetsMgr->createMissingDefault(PRESETS_CAMERA); + + presetsMgr->startWatching(PRESETS_CAMERA); + + presetsMgr->setPresetListChangeCameraCallback(boost::bind(&LLPanelPresetsCameraPulldown::populatePanel, this)); + } + + populatePanel(); + + return LLPanelPulldown::postBuild(); +} + +void LLPanelPresetsCameraPulldown::populatePanel() +{ + LLPresetsManager::getInstance()->loadPresetNamesFromDir(PRESETS_CAMERA, mPresetNames, DEFAULT_BOTTOM); + + LLScrollListCtrl* scroll = getChild("preset_camera_list"); + + if (scroll && mPresetNames.begin() != mPresetNames.end()) + { + scroll->clearRows(); + + std::string active_preset = gSavedSettings.getString("PresetCameraActive"); + if (active_preset == PRESETS_DEFAULT) + { + active_preset = LLTrans::getString(PRESETS_DEFAULT); + } + + for (std::list::const_iterator it = mPresetNames.begin(); it != mPresetNames.end(); ++it) + { + const std::string& name = *it; + LL_DEBUGS() << "adding '" << name << "'" << LL_ENDL; + + LLSD row; + row["columns"][0]["column"] = "preset_name"; + row["columns"][0]["value"] = name; + + bool is_selected_preset = false; + if (name == active_preset) + { + row["columns"][1]["column"] = "icon"; + row["columns"][1]["type"] = "icon"; + row["columns"][1]["value"] = "Check_Mark"; + + is_selected_preset = true; + } + + LLScrollListItem* new_item = scroll->addElement(row); + new_item->setSelected(is_selected_preset); + } + } +} + +void LLPanelPresetsCameraPulldown::onRowClick(const LLSD& user_data) +{ + LLScrollListCtrl* scroll = getChild("preset_camera_list"); + + if (scroll) + { + LLScrollListItem* item = scroll->getFirstSelected(); + if (item) + { + std::string name = item->getColumn(1)->getValue().asString(); + + LL_DEBUGS() << "selected '" << name << "'" << LL_ENDL; + LLFloaterCamera::switchToPreset(name); + + // Scroll grabbed focus, drop it to prevent selection of parent menu + setFocus(false); + + setVisible(false); + } + else + { + LL_DEBUGS() << "none selected" << LL_ENDL; + } + } + else + { + LL_DEBUGS() << "no scroll" << LL_ENDL; + } +} + +void LLPanelPresetsCameraPulldown::onViewButtonClick(const LLSD& user_data) +{ + // close the minicontrol, we're bringing up the big one + setVisible(false); + + LLFloaterReg::toggleInstanceOrBringToFront("camera"); +} diff --git a/indra/newview/llpanelpresetscamerapulldown.h b/indra/newview/llpanelpresetscamerapulldown.h index 5d29d8d2bf..9424a48eaa 100644 --- a/indra/newview/llpanelpresetscamerapulldown.h +++ b/indra/newview/llpanelpresetscamerapulldown.h @@ -1,49 +1,49 @@ -/** - * @file llpanelpresetscamerapulldown.h - * @brief A panel showing a quick way to pick camera presets - * - * $LicenseInfo:firstyear=2017&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2017, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELPRESETSCAMERAPULLDOWN_H -#define LL_LLPANELPRESETSCAMERAPULLDOWN_H - -#include "linden_common.h" - -#include "llpanelpulldown.h" - -class LLPanelPresetsCameraPulldown : public LLPanelPulldown -{ - public: - LLPanelPresetsCameraPulldown(); - bool postBuild() override; - void populatePanel(); - - private: - void onViewButtonClick(const LLSD& user_data); - void onRowClick(const LLSD& user_data); - - std::list mPresetNames; - LOG_CLASS(LLPanelPresetsCameraPulldown); -}; - -#endif // LL_LLPANELPRESETSCAMERAPULLDOWN_H +/** + * @file llpanelpresetscamerapulldown.h + * @brief A panel showing a quick way to pick camera presets + * + * $LicenseInfo:firstyear=2017&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2017, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELPRESETSCAMERAPULLDOWN_H +#define LL_LLPANELPRESETSCAMERAPULLDOWN_H + +#include "linden_common.h" + +#include "llpanelpulldown.h" + +class LLPanelPresetsCameraPulldown : public LLPanelPulldown +{ + public: + LLPanelPresetsCameraPulldown(); + bool postBuild() override; + void populatePanel(); + + private: + void onViewButtonClick(const LLSD& user_data); + void onRowClick(const LLSD& user_data); + + std::list mPresetNames; + LOG_CLASS(LLPanelPresetsCameraPulldown); +}; + +#endif // LL_LLPANELPRESETSCAMERAPULLDOWN_H diff --git a/indra/newview/llpanelpresetspulldown.cpp b/indra/newview/llpanelpresetspulldown.cpp index be5563ca21..3b0f1273df 100644 --- a/indra/newview/llpanelpresetspulldown.cpp +++ b/indra/newview/llpanelpresetspulldown.cpp @@ -1,171 +1,171 @@ -/** - * @file llpanelpresetspulldown.cpp - * @brief A panel showing a quick way to pick presets - * - * $LicenseInfo:firstyear=2014&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2014, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelpresetspulldown.h" - -#include "llviewercontrol.h" -#include "llstatusbar.h" - -#include "llbutton.h" -#include "lltabcontainer.h" -#include "llfloater.h" -#include "llfloaterperformance.h" -#include "llfloaterreg.h" -#include "llpresetsmanager.h" -#include "llsliderctrl.h" -#include "llscrolllistctrl.h" -#include "lltrans.h" - -///---------------------------------------------------------------------------- -/// Class LLPanelPresetsPulldown -///---------------------------------------------------------------------------- - -// Default constructor -LLPanelPresetsPulldown::LLPanelPresetsPulldown() -{ - mHoverTimer.stop(); - - mCommitCallbackRegistrar.add("Presets.GoGraphicsPrefs", boost::bind(&LLPanelPresetsPulldown::onGraphicsButtonClick, this, _2)); - mCommitCallbackRegistrar.add("Presets.GoAutofpsPrefs", boost::bind(&LLPanelPresetsPulldown::onAutofpsButtonClick, this, _2)); - mCommitCallbackRegistrar.add("Presets.RowClick", boost::bind(&LLPanelPresetsPulldown::onRowClick, this, _2)); - - buildFromFile( "panel_presets_pulldown.xml"); -} - -bool LLPanelPresetsPulldown::postBuild() -{ - LLPresetsManager* presetsMgr = LLPresetsManager::getInstance(); - presetsMgr->setPresetListChangeCallback(boost::bind(&LLPanelPresetsPulldown::populatePanel, this)); - // Make sure there is a default preference file - presetsMgr->createMissingDefault(PRESETS_GRAPHIC); - - populatePanel(); - - return LLPanelPulldown::postBuild(); -} - -void LLPanelPresetsPulldown::populatePanel() -{ - LLPresetsManager::getInstance()->loadPresetNamesFromDir(PRESETS_GRAPHIC, mPresetNames, DEFAULT_TOP); - - LLScrollListCtrl* scroll = getChild("preset_list"); - - if (scroll && mPresetNames.begin() != mPresetNames.end()) - { - scroll->clearRows(); - - std::string active_preset = gSavedSettings.getString("PresetGraphicActive"); - if (active_preset == PRESETS_DEFAULT) - { - active_preset = LLTrans::getString(PRESETS_DEFAULT); - } - - for (std::list::const_iterator it = mPresetNames.begin(); it != mPresetNames.end(); ++it) - { - const std::string& name = *it; - LL_DEBUGS() << "adding '" << name << "'" << LL_ENDL; - - LLSD row; - row["columns"][0]["column"] = "preset_name"; - row["columns"][0]["value"] = name; - - bool is_selected_preset = false; - if (name == active_preset) - { - row["columns"][1]["column"] = "icon"; - row["columns"][1]["type"] = "icon"; - row["columns"][1]["value"] = "Check_Mark"; - - is_selected_preset = true; - } - - LLScrollListItem* new_item = scroll->addElement(row); - new_item->setSelected(is_selected_preset); - } - } -} - -void LLPanelPresetsPulldown::onRowClick(const LLSD& user_data) -{ - LLScrollListCtrl* scroll = getChild("preset_list"); - - if (scroll) - { - LLScrollListItem* item = scroll->getFirstSelected(); - if (item) - { - std::string name = item->getColumn(1)->getValue().asString(); - - LL_DEBUGS() << "selected '" << name << "'" << LL_ENDL; - LLPresetsManager::getInstance()->loadPreset(PRESETS_GRAPHIC, name); - - // Scroll grabbed focus, drop it to prevent selection of parent menu - setFocus(false); - - setVisible(false); - } - else - { - LL_DEBUGS() << "none selected" << LL_ENDL; - } - } - else - { - LL_DEBUGS() << "no scroll" << LL_ENDL; - } -} - -void LLPanelPresetsPulldown::onGraphicsButtonClick(const LLSD& user_data) -{ - // close the minicontrol, we're bringing up the big one - setVisible(false); - - // bring up the prefs floater - LLFloater* prefsfloater = LLFloaterReg::showInstance("preferences"); - if (prefsfloater) - { - // grab the 'graphics' panel from the preferences floater and - // bring it the front! - LLTabContainer* tabcontainer = prefsfloater->getChild("pref core"); - LLPanel* graphicspanel = prefsfloater->getChild("display"); - if (tabcontainer && graphicspanel) - { - tabcontainer->selectTabPanel(graphicspanel); - } - } -} - -void LLPanelPresetsPulldown::onAutofpsButtonClick(const LLSD& user_data) -{ - setVisible(false); - LLFloaterPerformance* performance_floater = LLFloaterReg::showTypedInstance("performance"); - if (performance_floater) - { - performance_floater->showAutoadjustmentsPanel(); - } -} +/** + * @file llpanelpresetspulldown.cpp + * @brief A panel showing a quick way to pick presets + * + * $LicenseInfo:firstyear=2014&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelpresetspulldown.h" + +#include "llviewercontrol.h" +#include "llstatusbar.h" + +#include "llbutton.h" +#include "lltabcontainer.h" +#include "llfloater.h" +#include "llfloaterperformance.h" +#include "llfloaterreg.h" +#include "llpresetsmanager.h" +#include "llsliderctrl.h" +#include "llscrolllistctrl.h" +#include "lltrans.h" + +///---------------------------------------------------------------------------- +/// Class LLPanelPresetsPulldown +///---------------------------------------------------------------------------- + +// Default constructor +LLPanelPresetsPulldown::LLPanelPresetsPulldown() +{ + mHoverTimer.stop(); + + mCommitCallbackRegistrar.add("Presets.GoGraphicsPrefs", boost::bind(&LLPanelPresetsPulldown::onGraphicsButtonClick, this, _2)); + mCommitCallbackRegistrar.add("Presets.GoAutofpsPrefs", boost::bind(&LLPanelPresetsPulldown::onAutofpsButtonClick, this, _2)); + mCommitCallbackRegistrar.add("Presets.RowClick", boost::bind(&LLPanelPresetsPulldown::onRowClick, this, _2)); + + buildFromFile( "panel_presets_pulldown.xml"); +} + +bool LLPanelPresetsPulldown::postBuild() +{ + LLPresetsManager* presetsMgr = LLPresetsManager::getInstance(); + presetsMgr->setPresetListChangeCallback(boost::bind(&LLPanelPresetsPulldown::populatePanel, this)); + // Make sure there is a default preference file + presetsMgr->createMissingDefault(PRESETS_GRAPHIC); + + populatePanel(); + + return LLPanelPulldown::postBuild(); +} + +void LLPanelPresetsPulldown::populatePanel() +{ + LLPresetsManager::getInstance()->loadPresetNamesFromDir(PRESETS_GRAPHIC, mPresetNames, DEFAULT_TOP); + + LLScrollListCtrl* scroll = getChild("preset_list"); + + if (scroll && mPresetNames.begin() != mPresetNames.end()) + { + scroll->clearRows(); + + std::string active_preset = gSavedSettings.getString("PresetGraphicActive"); + if (active_preset == PRESETS_DEFAULT) + { + active_preset = LLTrans::getString(PRESETS_DEFAULT); + } + + for (std::list::const_iterator it = mPresetNames.begin(); it != mPresetNames.end(); ++it) + { + const std::string& name = *it; + LL_DEBUGS() << "adding '" << name << "'" << LL_ENDL; + + LLSD row; + row["columns"][0]["column"] = "preset_name"; + row["columns"][0]["value"] = name; + + bool is_selected_preset = false; + if (name == active_preset) + { + row["columns"][1]["column"] = "icon"; + row["columns"][1]["type"] = "icon"; + row["columns"][1]["value"] = "Check_Mark"; + + is_selected_preset = true; + } + + LLScrollListItem* new_item = scroll->addElement(row); + new_item->setSelected(is_selected_preset); + } + } +} + +void LLPanelPresetsPulldown::onRowClick(const LLSD& user_data) +{ + LLScrollListCtrl* scroll = getChild("preset_list"); + + if (scroll) + { + LLScrollListItem* item = scroll->getFirstSelected(); + if (item) + { + std::string name = item->getColumn(1)->getValue().asString(); + + LL_DEBUGS() << "selected '" << name << "'" << LL_ENDL; + LLPresetsManager::getInstance()->loadPreset(PRESETS_GRAPHIC, name); + + // Scroll grabbed focus, drop it to prevent selection of parent menu + setFocus(false); + + setVisible(false); + } + else + { + LL_DEBUGS() << "none selected" << LL_ENDL; + } + } + else + { + LL_DEBUGS() << "no scroll" << LL_ENDL; + } +} + +void LLPanelPresetsPulldown::onGraphicsButtonClick(const LLSD& user_data) +{ + // close the minicontrol, we're bringing up the big one + setVisible(false); + + // bring up the prefs floater + LLFloater* prefsfloater = LLFloaterReg::showInstance("preferences"); + if (prefsfloater) + { + // grab the 'graphics' panel from the preferences floater and + // bring it the front! + LLTabContainer* tabcontainer = prefsfloater->getChild("pref core"); + LLPanel* graphicspanel = prefsfloater->getChild("display"); + if (tabcontainer && graphicspanel) + { + tabcontainer->selectTabPanel(graphicspanel); + } + } +} + +void LLPanelPresetsPulldown::onAutofpsButtonClick(const LLSD& user_data) +{ + setVisible(false); + LLFloaterPerformance* performance_floater = LLFloaterReg::showTypedInstance("performance"); + if (performance_floater) + { + performance_floater->showAutoadjustmentsPanel(); + } +} diff --git a/indra/newview/llpanelpresetspulldown.h b/indra/newview/llpanelpresetspulldown.h index a1fede1599..b4d001a02e 100644 --- a/indra/newview/llpanelpresetspulldown.h +++ b/indra/newview/llpanelpresetspulldown.h @@ -1,51 +1,51 @@ -/** - * @file llpanelpresetspulldown.h - * @brief A panel showing a quick way to pick presets - * - * $LicenseInfo:firstyear=2014&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2014, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELPRESETSPULLDOWN_H -#define LL_LLPANELPRESETSPULLDOWN_H - -#include "linden_common.h" - -#include "llpanelpulldown.h" - - -class LLPanelPresetsPulldown : public LLPanelPulldown -{ - public: - LLPanelPresetsPulldown(); - bool postBuild() override; - void populatePanel(); - - private: - void onGraphicsButtonClick(const LLSD& user_data); - void onAutofpsButtonClick(const LLSD& user_data); - void onRowClick(const LLSD& user_data); - - std::list mPresetNames; - LOG_CLASS(LLPanelPresetsPulldown); -}; - -#endif // LL_LLPANELPRESETSPULLDOWN_H +/** + * @file llpanelpresetspulldown.h + * @brief A panel showing a quick way to pick presets + * + * $LicenseInfo:firstyear=2014&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELPRESETSPULLDOWN_H +#define LL_LLPANELPRESETSPULLDOWN_H + +#include "linden_common.h" + +#include "llpanelpulldown.h" + + +class LLPanelPresetsPulldown : public LLPanelPulldown +{ + public: + LLPanelPresetsPulldown(); + bool postBuild() override; + void populatePanel(); + + private: + void onGraphicsButtonClick(const LLSD& user_data); + void onAutofpsButtonClick(const LLSD& user_data); + void onRowClick(const LLSD& user_data); + + std::list mPresetNames; + LOG_CLASS(LLPanelPresetsPulldown); +}; + +#endif // LL_LLPANELPRESETSPULLDOWN_H diff --git a/indra/newview/llpanelprimmediacontrols.cpp b/indra/newview/llpanelprimmediacontrols.cpp index c13c61bfbf..1299c8c656 100644 --- a/indra/newview/llpanelprimmediacontrols.cpp +++ b/indra/newview/llpanelprimmediacontrols.cpp @@ -1,1471 +1,1471 @@ -/** - * @file llpanelprimmediacontrols.cpp - * @brief media controls popup panel - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llagent.h" -#include "llagentcamera.h" -#include "llparcel.h" -#include "llpanel.h" -#include "llselectmgr.h" -#include "llmediaentry.h" -#include "llrender.h" -#include "lldrawable.h" -#include "llviewerwindow.h" -#include "lluictrlfactory.h" -#include "llbutton.h" -#include "llface.h" -#include "llcombobox.h" -#include "lllayoutstack.h" -#include "llslider.h" -#include "llhudview.h" -#include "lliconctrl.h" -#include "lltoolpie.h" -#include "llviewercamera.h" -#include "llviewerobjectlist.h" -#include "llpanelprimmediacontrols.h" -#include "llpluginclassmedia.h" -#include "llprogressbar.h" -#include "llsliderctrl.h" -#include "llstring.h" -#include "llviewercontrol.h" -#include "llviewerdisplay.h" -#include "llviewerparcelmgr.h" -#include "llviewermedia.h" -#include "llviewermediafocus.h" -#include "llvovolume.h" -#include "llweb.h" -#include "llwindow.h" -#include "llwindowshade.h" -#include "llfloatertools.h" // to enable hide if build tools are up -#include "llvector4a.h" - -// Functions pulled from pipeline.cpp -glh::matrix4f get_current_modelview(); -glh::matrix4f get_current_projection(); -// Functions pulled from llviewerdisplay.cpp -bool get_hud_matrices(glh::matrix4f &proj, glh::matrix4f &model); - -// Warning: make sure these two match! -const LLPanelPrimMediaControls::EZoomLevel LLPanelPrimMediaControls::kZoomLevels[] = { ZOOM_NONE, ZOOM_MEDIUM }; -const int LLPanelPrimMediaControls::kNumZoomLevels = 2; - -const F32 EXCEEDING_ZOOM_DISTANCE = 0.5f; -const S32 ADDR_LEFT_PAD = 3; - -// -// LLPanelPrimMediaControls -// - -LLPanelPrimMediaControls::LLPanelPrimMediaControls() : - mAlpha(1.f), - mCurrentURL(""), - mPreviousURL(""), - mPauseFadeout(false), - mUpdateSlider(true), - mClearFaceOnFade(false), - mCurrentRate(0.0), - mMovieDuration(0.0), - mTargetObjectID(LLUUID::null), - mTargetObjectFace(0), - mTargetImplID(LLUUID::null), - mTargetObjectNormal(LLVector3::zero), - mZoomObjectID(LLUUID::null), - mZoomObjectFace(0), - mVolumeSliderVisible(0), - mZoomedCameraPos(), - mWindowShade(NULL), - mHideImmediately(false), - mSecureURL(false), - mMediaPlaySliderCtrlMouseDownValue(0.0) -{ - mCommitCallbackRegistrar.add("MediaCtrl.Close", boost::bind(&LLPanelPrimMediaControls::onClickClose, this)); - mCommitCallbackRegistrar.add("MediaCtrl.Back", boost::bind(&LLPanelPrimMediaControls::onClickBack, this)); - mCommitCallbackRegistrar.add("MediaCtrl.Forward", boost::bind(&LLPanelPrimMediaControls::onClickForward, this)); - mCommitCallbackRegistrar.add("MediaCtrl.Home", boost::bind(&LLPanelPrimMediaControls::onClickHome, this)); - mCommitCallbackRegistrar.add("MediaCtrl.Stop", boost::bind(&LLPanelPrimMediaControls::onClickStop, this)); - mCommitCallbackRegistrar.add("MediaCtrl.MediaStop", boost::bind(&LLPanelPrimMediaControls::onClickMediaStop, this)); - mCommitCallbackRegistrar.add("MediaCtrl.Reload", boost::bind(&LLPanelPrimMediaControls::onClickReload, this)); - mCommitCallbackRegistrar.add("MediaCtrl.Play", boost::bind(&LLPanelPrimMediaControls::onClickPlay, this)); - mCommitCallbackRegistrar.add("MediaCtrl.Pause", boost::bind(&LLPanelPrimMediaControls::onClickPause, this)); - mCommitCallbackRegistrar.add("MediaCtrl.Open", boost::bind(&LLPanelPrimMediaControls::onClickOpen, this)); - mCommitCallbackRegistrar.add("MediaCtrl.Zoom", boost::bind(&LLPanelPrimMediaControls::onClickZoom, this)); - mCommitCallbackRegistrar.add("MediaCtrl.CommitURL", boost::bind(&LLPanelPrimMediaControls::onCommitURL, this)); - mCommitCallbackRegistrar.add("MediaCtrl.MouseDown", boost::bind(&LLPanelPrimMediaControls::onMediaPlaySliderCtrlMouseDown, this)); - mCommitCallbackRegistrar.add("MediaCtrl.MouseUp", boost::bind(&LLPanelPrimMediaControls::onMediaPlaySliderCtrlMouseUp, this)); - mCommitCallbackRegistrar.add("MediaCtrl.CommitVolumeUp", boost::bind(&LLPanelPrimMediaControls::onCommitVolumeUp, this)); - mCommitCallbackRegistrar.add("MediaCtrl.CommitVolumeDown", boost::bind(&LLPanelPrimMediaControls::onCommitVolumeDown, this)); - mCommitCallbackRegistrar.add("MediaCtrl.Volume", boost::bind(&LLPanelPrimMediaControls::onCommitVolumeSlider, this)); - mCommitCallbackRegistrar.add("MediaCtrl.ToggleMute", boost::bind(&LLPanelPrimMediaControls::onToggleMute, this)); - mCommitCallbackRegistrar.add("MediaCtrl.ShowVolumeSlider", boost::bind(&LLPanelPrimMediaControls::showVolumeSlider, this)); - mCommitCallbackRegistrar.add("MediaCtrl.HideVolumeSlider", boost::bind(&LLPanelPrimMediaControls::hideVolumeSlider, this)); - mCommitCallbackRegistrar.add("MediaCtrl.SkipBack", boost::bind(&LLPanelPrimMediaControls::onClickSkipBack, this)); - mCommitCallbackRegistrar.add("MediaCtrl.SkipForward", boost::bind(&LLPanelPrimMediaControls::onClickSkipForward, this)); - - buildFromFile( "panel_prim_media_controls.xml"); - mInactivityTimer.reset(); - mFadeTimer.stop(); - mCurrentZoom = ZOOM_NONE; - mScrollState = SCROLL_NONE; - - mPanelHandle.bind(this); - - mInactiveTimeout = gSavedSettings.getF32("MediaControlTimeout"); - mControlFadeTime = gSavedSettings.getF32("MediaControlFadeTime"); -} - -LLPanelPrimMediaControls::~LLPanelPrimMediaControls() -{ -} - -bool LLPanelPrimMediaControls::postBuild() -{ - mMediaRegion = getChild("media_region"); - mBackCtrl = getChild("back"); - mFwdCtrl = getChild("fwd"); - mReloadCtrl = getChild("reload"); - mPlayCtrl = getChild("play"); - mPauseCtrl = getChild("pause"); - mStopCtrl = getChild("stop"); - mMediaStopCtrl = getChild("media_stop"); - mHomeCtrl = getChild("home"); - mUnzoomCtrl = getChild("close"); // This is actually "unzoom" - mOpenCtrl = getChild("new_window"); - mZoomCtrl = getChild("zoom_frame"); - mMediaProgressPanel = getChild("media_progress_indicator"); - mMediaProgressBar = getChild("media_progress_bar"); - mMediaAddressCtrl = getChild("media_address"); - mMediaAddress = getChild("media_address_url"); - mMediaPlaySliderPanel = getChild("media_play_position"); - mMediaPlaySliderCtrl = getChild("media_play_slider"); - mSkipFwdCtrl = getChild("skip_forward"); - mSkipBackCtrl = getChild("skip_back"); - mVolumeCtrl = getChild("media_volume"); - mMuteBtn = getChild("media_mute_button"); - mVolumeSliderCtrl = getChild("volume_slider"); - mWhitelistIcon = getChild("media_whitelist_flag"); - mSecureLockIcon = getChild("media_secure_lock_flag"); - mMediaControlsStack = getChild("media_controls"); - mLeftBookend = getChild("left_bookend"); - mRightBookend = getChild("right_bookend"); - mBackgroundImage = LLUI::getUIImage(getString("control_background_image_name")); - mVolumeSliderBackgroundImage = LLUI::getUIImage(getString("control_background_image_name")); - LLStringUtil::convertToF32(getString("skip_step"), mSkipStep); - LLStringUtil::convertToS32(getString("min_width"), mMinWidth); - LLStringUtil::convertToS32(getString("min_height"), mMinHeight); - LLStringUtil::convertToF32(getString("zoom_near_padding"), mZoomNearPadding); - LLStringUtil::convertToF32(getString("zoom_medium_padding"), mZoomMediumPadding); - LLStringUtil::convertToF32(getString("zoom_far_padding"), mZoomFarPadding); - LLStringUtil::convertToS32(getString("top_world_view_avoid_zone"), mTopWorldViewAvoidZone); - - // These are currently removed...but getChild creates a "dummy" widget. - // This class handles them missing. - mMediaPanelScroll = findChild("media_panel_scroll"); - mScrollUpCtrl = findChild("scrollup"); - mScrollLeftCtrl = findChild("scrollleft"); - mScrollRightCtrl = findChild("scrollright"); - mScrollDownCtrl = findChild("scrolldown"); - - if (mScrollUpCtrl) - { - mScrollUpCtrl->setClickedCallback(onScrollUp, this); - mScrollUpCtrl->setHeldDownCallback(onScrollUpHeld, this); - mScrollUpCtrl->setMouseUpCallback(onScrollStop, this); - } - if (mScrollLeftCtrl) - { - mScrollLeftCtrl->setClickedCallback(onScrollLeft, this); - mScrollLeftCtrl->setHeldDownCallback(onScrollLeftHeld, this); - mScrollLeftCtrl->setMouseUpCallback(onScrollStop, this); - } - if (mScrollRightCtrl) - { - mScrollRightCtrl->setClickedCallback(onScrollRight, this); - mScrollRightCtrl->setHeldDownCallback(onScrollRightHeld, this); - mScrollRightCtrl->setMouseUpCallback(onScrollStop, this); - } - if (mScrollDownCtrl) - { - mScrollDownCtrl->setClickedCallback(onScrollDown, this); - mScrollDownCtrl->setHeldDownCallback(onScrollDownHeld, this); - mScrollDownCtrl->setMouseUpCallback(onScrollStop, this); - } - - mMediaAddress->setFocusReceivedCallback(boost::bind(&LLPanelPrimMediaControls::onInputURL, _1, this )); - - gAgent.setMouselookModeInCallback(boost::bind(&LLPanelPrimMediaControls::onMouselookModeIn, this)); - - LLWindowShade::Params window_shade_params; - window_shade_params.name = "window_shade"; - - mCurrentZoom = ZOOM_NONE; - // clicks on buttons do not remove keyboard focus from media - setIsChrome(true); - return true; -} - -void LLPanelPrimMediaControls::setMediaFace(LLPointer objectp, S32 face, viewer_media_t media_impl, LLVector3 pick_normal) -{ - if (media_impl.notNull() && objectp.notNull()) - { - LLUUID prev_id = mTargetImplID; - mTargetImplID = media_impl->getMediaTextureID(); - mTargetObjectID = objectp->getID(); - mTargetObjectFace = face; - mTargetObjectNormal = pick_normal; - mClearFaceOnFade = false; - - if (prev_id != mTargetImplID) - mVolumeSliderCtrl->setValue(media_impl->getVolume()); - } - else - { - // This happens on a timer now. -// mTargetImplID = LLUUID::null; -// mTargetObjectID = LLUUID::null; -// mTargetObjectFace = 0; - mClearFaceOnFade = true; - } - - updateShape(); -} - -void LLPanelPrimMediaControls::focusOnTarget() -{ - // Sets the media focus to the current target of the LLPanelPrimMediaControls. - // This is how we transition from hover to focus when the user clicks on a control. - LLViewerMediaImpl* media_impl = getTargetMediaImpl(); - if(media_impl) - { - if (!media_impl->hasFocus()) - { - // The current target doesn't have media focus -- focus on it. - LLViewerObject* objectp = getTargetObject(); - LLViewerMediaFocus::getInstance()->setFocusFace(objectp, mTargetObjectFace, media_impl, mTargetObjectNormal); - } - } -} - -LLViewerMediaImpl* LLPanelPrimMediaControls::getTargetMediaImpl() -{ - return LLViewerMedia::getInstance()->getMediaImplFromTextureID(mTargetImplID); -} - -LLViewerObject* LLPanelPrimMediaControls::getTargetObject() -{ - return gObjectList.findObject(mTargetObjectID); -} - -LLPluginClassMedia* LLPanelPrimMediaControls::getTargetMediaPlugin() -{ - LLViewerMediaImpl* impl = getTargetMediaImpl(); - if(impl && impl->hasMedia()) - { - return impl->getMediaPlugin(); - } - - return NULL; -} - -void LLPanelPrimMediaControls::updateShape() -{ - LLViewerMediaImpl* media_impl = getTargetMediaImpl(); - LLViewerObject* objectp = getTargetObject(); - - if(!media_impl || gFloaterTools->getVisible()) - { - setVisible(false); - return; - } - - LLPluginClassMedia* media_plugin = NULL; - if(media_impl->hasMedia()) - { - media_plugin = media_impl->getMediaPlugin(); - } - - LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - - bool can_navigate = parcel->getMediaAllowNavigate(); - bool enabled = false; - bool is_zoomed = (mCurrentZoom != ZOOM_NONE) && (mTargetObjectID == mZoomObjectID) && (mTargetObjectFace == mZoomObjectFace) && !isZoomDistExceeding(); - - // There is no such thing as "has_focus" being different from normal controls set - // anymore (as of user feedback from bri 10/09). So we cheat here and force 'has_focus' - // to 'true' (or, actually, we use a setting) - bool has_focus = (gSavedSettings.getBOOL("PrimMediaControlsUseHoverControlSet")) ? media_impl->hasFocus() : true; - setVisible(enabled); - - if (objectp) - { - bool hasPermsControl = true; - bool mini_controls = false; - LLMediaEntry *media_data = objectp->getTE(mTargetObjectFace)->getMediaData(); - if (media_data && NULL != dynamic_cast(objectp)) - { - // Don't show the media controls if we do not have permissions - enabled = dynamic_cast(objectp)->hasMediaPermission(media_data, LLVOVolume::MEDIA_PERM_CONTROL); - hasPermsControl = dynamic_cast(objectp)->hasMediaPermission(media_data, LLVOVolume::MEDIA_PERM_CONTROL); - mini_controls = (LLMediaEntry::MINI == media_data->getControls()); - } - const bool is_hud = objectp->isHUDAttachment(); - - // - // Set the state of the buttons - // - - // XXX RSP: TODO: FIXME: clean this up so that it is clearer what mode we are in, - // and that only the proper controls get made visible/enabled according to that mode. - mBackCtrl->setVisible(has_focus); - mFwdCtrl->setVisible(has_focus); - mReloadCtrl->setVisible(has_focus); - mStopCtrl->setVisible(false); - mHomeCtrl->setVisible(has_focus); - mZoomCtrl->setVisible(!is_zoomed); - mUnzoomCtrl->setVisible(is_zoomed); - mOpenCtrl->setVisible(true); - mMediaAddressCtrl->setVisible(has_focus && !mini_controls); - mMediaPlaySliderPanel->setVisible(has_focus && !mini_controls); - mVolumeCtrl->setVisible(false); - - mWhitelistIcon->setVisible(!mini_controls && (media_data)?media_data->getWhiteListEnable():false); - // Disable zoom if HUD - mZoomCtrl->setEnabled(!is_hud); - mUnzoomCtrl->setEnabled(!is_hud); - mSecureURL = false; - mCurrentURL = media_impl->getCurrentMediaURL(); - - mBackCtrl->setEnabled((media_impl != NULL) && media_impl->canNavigateBack() && can_navigate); - mFwdCtrl->setEnabled((media_impl != NULL) && media_impl->canNavigateForward() && can_navigate); - mStopCtrl->setEnabled(has_focus && can_navigate); - mHomeCtrl->setEnabled(has_focus && can_navigate); - LLPluginClassMediaOwner::EMediaStatus result = ((media_impl != NULL) && media_impl->hasMedia()) ? media_plugin->getStatus() : LLPluginClassMediaOwner::MEDIA_NONE; - - mVolumeCtrl->setVisible(has_focus); - mVolumeCtrl->setEnabled(has_focus); - mVolumeSliderCtrl->setEnabled(has_focus && shouldVolumeSliderBeVisible()); - mVolumeSliderCtrl->setVisible(has_focus && shouldVolumeSliderBeVisible()); - - if(media_plugin && media_plugin->pluginSupportsMediaTime()) - { - mReloadCtrl->setEnabled(false); - mReloadCtrl->setVisible(false); - mMediaStopCtrl->setVisible(has_focus); - mHomeCtrl->setVisible(has_focus); - mBackCtrl->setVisible(false); - mFwdCtrl->setVisible(false); - mMediaAddressCtrl->setVisible(false); - mMediaAddressCtrl->setEnabled(false); - mMediaPlaySliderPanel->setVisible(has_focus && !mini_controls); - mMediaPlaySliderPanel->setEnabled(has_focus && !mini_controls); - mSkipFwdCtrl->setVisible(has_focus && !mini_controls); - mSkipFwdCtrl->setEnabled(has_focus && !mini_controls); - mSkipBackCtrl->setVisible(has_focus && !mini_controls); - mSkipBackCtrl->setEnabled(has_focus && !mini_controls); - - mVolumeCtrl->setVisible(has_focus); - mVolumeCtrl->setEnabled(has_focus); - mVolumeSliderCtrl->setEnabled(has_focus && shouldVolumeSliderBeVisible()); - mVolumeSliderCtrl->setVisible(has_focus && shouldVolumeSliderBeVisible()); - - mWhitelistIcon->setVisible(false); - mSecureURL = false; - if (mMediaPanelScroll) - { - mMediaPanelScroll->setVisible(false); - mScrollUpCtrl->setVisible(false); - mScrollDownCtrl->setVisible(false); - mScrollRightCtrl->setVisible(false); - mScrollDownCtrl->setVisible(false); - } - - F32 volume = media_impl->getVolume(); - // movie's url changed - if(mCurrentURL!=mPreviousURL) - { - mMovieDuration = media_plugin->getDuration(); - mPreviousURL = mCurrentURL; - } - - if(mMovieDuration == 0) - { - mMovieDuration = media_plugin->getDuration(); - mMediaPlaySliderCtrl->setValue(0); - mMediaPlaySliderCtrl->setEnabled(false); - } - // TODO: What if it's not fully loaded - - if(mUpdateSlider && mMovieDuration!= 0) - { - F64 current_time = media_plugin->getCurrentTime(); - F32 percent = current_time / mMovieDuration; - mMediaPlaySliderCtrl->setValue(percent); - mMediaPlaySliderCtrl->setEnabled(true); - } - - // video volume - if(volume <= 0.0) - { - mMuteBtn->setToggleState(true); - } - else if (volume >= 1.0) - { - mMuteBtn->setToggleState(false); - } - else - { - mMuteBtn->setToggleState(false); - } - - switch(result) - { - case LLPluginClassMediaOwner::MEDIA_PLAYING: - mPlayCtrl->setEnabled(false); - mPlayCtrl->setVisible(false); - mPauseCtrl->setEnabled(true); - mPauseCtrl->setVisible(has_focus); - - break; - case LLPluginClassMediaOwner::MEDIA_PAUSED: - default: - mPauseCtrl->setEnabled(false); - mPauseCtrl->setVisible(false); - mPlayCtrl->setEnabled(true); - mPlayCtrl->setVisible(has_focus); - break; - } - } - else // web based - { - if(media_plugin) - { - mCurrentURL = media_plugin->getLocation(); - } - else - { - mCurrentURL.clear(); - } - - mPlayCtrl->setVisible(false); - mPauseCtrl->setVisible(false); - mMediaStopCtrl->setVisible(false); - mMediaAddressCtrl->setVisible(has_focus && !mini_controls); - mMediaAddressCtrl->setEnabled(has_focus && !mini_controls); - mMediaPlaySliderPanel->setVisible(false); - mMediaPlaySliderPanel->setEnabled(false); - mSkipFwdCtrl->setVisible(false); - mSkipFwdCtrl->setEnabled(false); - mSkipBackCtrl->setVisible(false); - mSkipBackCtrl->setEnabled(false); - - if(media_impl->getVolume() <= 0.0) - { - mMuteBtn->setToggleState(true); - } - else - { - mMuteBtn->setToggleState(false); - } - - if (mMediaPanelScroll) - { - mMediaPanelScroll->setVisible(has_focus); - mScrollUpCtrl->setVisible(has_focus); - mScrollDownCtrl->setVisible(has_focus); - mScrollRightCtrl->setVisible(has_focus); - mScrollDownCtrl->setVisible(has_focus); - } - // TODO: get the secure lock bool from media plug in - std::string prefix = std::string("https://"); - std::string test_prefix = mCurrentURL.substr(0, prefix.length()); - LLStringUtil::toLower(test_prefix); - mSecureURL = has_focus && (test_prefix == prefix); - - S32 left_pad = mSecureURL ? mSecureLockIcon->getRect().getWidth() : ADDR_LEFT_PAD; - mMediaAddress->setTextPadding(left_pad, 0); - - if(mCurrentURL!=mPreviousURL) - { - setCurrentURL(); - mPreviousURL = mCurrentURL; - } - - if(result == LLPluginClassMediaOwner::MEDIA_LOADING) - { - mReloadCtrl->setEnabled(false); - mReloadCtrl->setVisible(false); - mStopCtrl->setEnabled(true); - mStopCtrl->setVisible(has_focus); - } - else - { - mReloadCtrl->setEnabled(true); - mReloadCtrl->setVisible(has_focus); - mStopCtrl->setEnabled(false); - mStopCtrl->setVisible(false); - } - } - - - if(media_plugin) - { - // - // Handle progress bar - // - if(LLPluginClassMediaOwner::MEDIA_LOADING == media_plugin->getStatus()) - { - mMediaProgressPanel->setVisible(true); - mMediaProgressBar->setValue(media_plugin->getProgressPercent()); - } - else - { - mMediaProgressPanel->setVisible(false); - } - } - - if(media_impl) - { - // - // Handle Scrolling - // - switch (mScrollState) - { - case SCROLL_UP: - media_impl->scrollWheel(0, 0, 0, -1, MASK_NONE); - break; - case SCROLL_DOWN: - media_impl->scrollWheel(0, 0, 0, 1, MASK_NONE); - break; - case SCROLL_LEFT: - media_impl->scrollWheel(0, 0, 1, 0, MASK_NONE); - // media_impl->handleKeyHere(KEY_LEFT, MASK_NONE); - break; - case SCROLL_RIGHT: - media_impl->scrollWheel(0, 0, -1, 0, MASK_NONE); - // media_impl->handleKeyHere(KEY_RIGHT, MASK_NONE); - break; - case SCROLL_NONE: - default: - break; - } - } - - // Web plugins and HUD may have media controls invisible for user, but still need scroll mouse events. - // LLView checks for visibleEnabledAndContains() and won't pass events to invisible panel, so instead - // of hiding whole panel hide each control instead (if user has no perms). - // Note: It might be beneficial to keep panel visible for all plugins to make behavior consistent, but - // for now limiting change to cases that need events. - - if (!is_hud && (!media_plugin || media_plugin->pluginSupportsMediaTime())) - { - setVisible(enabled); - } - else - { - if( !hasPermsControl ) - { - mBackCtrl->setVisible(false); - mFwdCtrl->setVisible(false); - mReloadCtrl->setVisible(false); - mStopCtrl->setVisible(false); - mHomeCtrl->setVisible(false); - mZoomCtrl->setVisible(false); - mUnzoomCtrl->setVisible(false); - mOpenCtrl->setVisible(false); - mMediaAddressCtrl->setVisible(false); - mMediaPlaySliderPanel->setVisible(false); - mVolumeCtrl->setVisible(false); - mMediaProgressPanel->setVisible(false); - mVolumeSliderCtrl->setVisible(false); - } - - setVisible(true); - } - - // - // Calculate position and shape of the controls - // - std::vector::iterator vert_it; - std::vector::iterator vert_end; - std::vector vect_face; - - LLVolume* volume = objectp->getVolume(); - - if (volume) - { - const LLVolumeFace& vf = volume->getVolumeFace(mTargetObjectFace); - - LLVector3 ext[2]; - ext[0].set(vf.mExtents[0].getF32ptr()); - ext[1].set(vf.mExtents[1].getF32ptr()); - - LLVector3 center = (ext[0]+ext[1])*0.5f; - LLVector3 size = (ext[1]-ext[0])*0.5f; - LLVector3 vert[] = - { - center + size.scaledVec(LLVector3(1,1,1)), - center + size.scaledVec(LLVector3(-1,1,1)), - center + size.scaledVec(LLVector3(1,-1,1)), - center + size.scaledVec(LLVector3(-1,-1,1)), - center + size.scaledVec(LLVector3(1,1,-1)), - center + size.scaledVec(LLVector3(-1,1,-1)), - center + size.scaledVec(LLVector3(1,-1,-1)), - center + size.scaledVec(LLVector3(-1,-1,-1)), - }; - - LLVOVolume* vo = (LLVOVolume*) objectp; - - for (U32 i = 0; i < 8; i++) - { - vect_face.push_back(vo->volumePositionToAgent(vert[i])); - } - } - vert_it = vect_face.begin(); - vert_end = vect_face.end(); - - glh::matrix4f mat; - if (!is_hud) - { - mat = get_current_projection() * get_current_modelview(); - } - else { - glh::matrix4f proj, modelview; - if (get_hud_matrices(proj, modelview)) - mat = proj * modelview; - } - LLVector3 min = LLVector3(1,1,1); - LLVector3 max = LLVector3(-1,-1,-1); - for(; vert_it != vert_end; ++vert_it) - { - // project silhouette vertices into screen space - glh::vec3f screen_vert = glh::vec3f(vert_it->mV); - mat.mult_matrix_vec(screen_vert); - - // add to screenspace bounding box - update_min_max(min, max, LLVector3(screen_vert.v)); - } - - // convert screenspace bbox to pixels (in screen coords) - LLRect window_rect = gViewerWindow->getWorldViewRectScaled(); - LLCoordGL screen_min; - screen_min.mX = ll_round((F32)window_rect.mLeft + (F32)window_rect.getWidth() * (min.mV[VX] + 1.f) * 0.5f); - screen_min.mY = ll_round((F32)window_rect.mBottom + (F32)window_rect.getHeight() * (min.mV[VY] + 1.f) * 0.5f); - - LLCoordGL screen_max; - screen_max.mX = ll_round((F32)window_rect.mLeft + (F32)window_rect.getWidth() * (max.mV[VX] + 1.f) * 0.5f); - screen_max.mY = ll_round((F32)window_rect.mBottom + (F32)window_rect.getHeight() * (max.mV[VY] + 1.f) * 0.5f); - - // grow panel so that screenspace bounding box fits inside "media_region" element of panel - LLRect media_panel_rect; - // Get the height of the controls (less the volume slider) - S32 controls_height = mMediaControlsStack->getRect().getHeight() - mVolumeSliderCtrl->getRect().getHeight(); - getParent()->screenRectToLocal(LLRect(screen_min.mX, screen_max.mY, screen_max.mX, screen_min.mY), &media_panel_rect); - media_panel_rect.mTop += controls_height; - - // keep all parts of panel on-screen - // Area of the top of the world view to avoid putting the controls - window_rect.mTop -= mTopWorldViewAvoidZone; - // Don't include "spacing" bookends on left & right of the media controls - window_rect.mLeft -= mLeftBookend->getRect().getWidth(); - window_rect.mRight += mRightBookend->getRect().getWidth(); - // Don't include the volume slider - window_rect.mBottom -= mVolumeSliderCtrl->getRect().getHeight(); - media_panel_rect.intersectWith(window_rect); - - // clamp to minimum size, keeping rect inside window - S32 centerX = media_panel_rect.getCenterX(); - S32 centerY = media_panel_rect.getCenterY(); - // Shrink screen rect by min width and height, to ensure containment - window_rect.stretch(-mMinWidth/2, -mMinHeight/2); - window_rect.clampPointToRect(centerX, centerY); - media_panel_rect.setCenterAndSize(centerX, centerY, - llmax(mMinWidth, media_panel_rect.getWidth()), - llmax(mMinHeight, media_panel_rect.getHeight())); - - // Finally set the size of the panel - setShape(media_panel_rect, true); - - // Test mouse position to see if the cursor is stationary - LLCoordWindow cursor_pos_window; - getWindow()->getCursorPosition(&cursor_pos_window); - - // If last pos is not equal to current pos, the mouse has moved - // We need to reset the timer, and make sure the panel is visible - if(cursor_pos_window.mX != mLastCursorPos.mX || - cursor_pos_window.mY != mLastCursorPos.mY || - mScrollState != SCROLL_NONE) - { - mInactivityTimer.start(); - mLastCursorPos = cursor_pos_window; - } - - if(isMouseOver() || hasFocus()) - { - // Never fade the controls if the mouse is over them or they have keyboard focus. - mFadeTimer.stop(); - } - else if(!mClearFaceOnFade && (mInactivityTimer.getElapsedTimeF32() < mInactiveTimeout)) - { - // Mouse is over the object, but has not been stationary for long enough to fade the UI - mFadeTimer.stop(); - } - else if(! mFadeTimer.getStarted() ) - { - // we need to start fading the UI (and we have not already started) - mFadeTimer.reset(); - mFadeTimer.start(); - } - else - { - // I don't think this is correct anymore. This is done in draw() after the fade has completed. - // setVisible(false); - } - } -} - -/*virtual*/ -void LLPanelPrimMediaControls::draw() -{ - LLViewerMediaImpl* impl = getTargetMediaImpl(); - if (impl) - { - LLNotificationPtr notification = impl->getCurrentNotification(); - if (notification != mActiveNotification) - { - mActiveNotification = notification; - if (notification) - { - showNotification(notification); - } - else - { - hideNotification(); - } - } - } - - F32 alpha = getDrawContext().mAlpha; - if(mHideImmediately) - { - //hide this panel - clearFaceOnFade(); - - mHideImmediately = false; - } - else if(mFadeTimer.getStarted()) - { - F32 time = mFadeTimer.getElapsedTimeF32(); - alpha *= llmax(lerp(1.0, 0.0, time / mControlFadeTime), 0.0f); - - if(time >= mControlFadeTime) - { - //hide this panel - clearFaceOnFade(); - } - } - - // Show/hide the lock icon for secure browsing - mSecureLockIcon->setVisible(mSecureURL && !mMediaAddress->hasFocus()); - - // Build rect for icon area in coord system of this panel - // Assumes layout_stack is a direct child of this panel - mMediaControlsStack->updateLayout(); - - // adjust for layout stack spacing - S32 space = mMediaControlsStack->getPanelSpacing() + 2; - LLRect controls_bg_area = mMediaControlsStack->getRect(); - - controls_bg_area.mTop += space + 2; - - // adjust to ignore space from volume slider - controls_bg_area.mBottom += mVolumeSliderCtrl->getRect().getHeight(); - - // adjust to ignore space from left bookend padding - controls_bg_area.mLeft += mLeftBookend->getRect().getWidth() - space; - - // ignore space from right bookend padding - controls_bg_area.mRight -= mRightBookend->getRect().getWidth() - space - 2; - - // draw control background UI image - - LLViewerObject* objectp = getTargetObject(); - LLMediaEntry *media_data(0); - - if( objectp ) - media_data = objectp->getTE(mTargetObjectFace)->getMediaData(); - - if( !dynamic_cast(objectp) || !media_data || dynamic_cast(objectp)->hasMediaPermission(media_data, LLVOVolume::MEDIA_PERM_CONTROL) ) - mBackgroundImage->draw( controls_bg_area, UI_VERTEX_COLOR % alpha); - - // draw volume slider background UI image - if (mVolumeSliderCtrl->getVisible()) - { - LLRect volume_slider_rect; - screenRectToLocal(mVolumeSliderCtrl->calcScreenRect(), &volume_slider_rect); - mVolumeSliderBackgroundImage->draw(volume_slider_rect, UI_VERTEX_COLOR % alpha); - } - - { - LLViewDrawContext context(alpha); - LLPanel::draw(); - } -} - -bool LLPanelPrimMediaControls::handleScrollWheel(S32 x, S32 y, S32 clicks) -{ - mInactivityTimer.start(); - bool res = false; - - // Unlike other mouse events, we need to handle scroll here otherwise - // it will be intercepted by camera and won't reach toolpie - if (LLViewerMediaFocus::getInstance()->isHoveringOverFocused()) - { - // either let toolpie handle this or expose mHoverPick.mUVCoords in some way - res = LLToolPie::getInstance()->handleScrollWheel(x, y, clicks); - } - - return res; -} - -bool LLPanelPrimMediaControls::handleScrollHWheel(S32 x, S32 y, S32 clicks) -{ - mInactivityTimer.start(); - bool res = false; - - if (LLViewerMediaFocus::getInstance()->isHoveringOverFocused()) - { - // either let toolpie handle this or expose mHoverPick.mUVCoords in some way - res = LLToolPie::getInstance()->handleScrollHWheel(x, y, clicks); - } - - return res; -} - -bool LLPanelPrimMediaControls::handleMouseDown(S32 x, S32 y, MASK mask) -{ - mInactivityTimer.start(); - return LLPanel::handleMouseDown(x, y, mask); -} - -bool LLPanelPrimMediaControls::handleMouseUp(S32 x, S32 y, MASK mask) -{ - mInactivityTimer.start(); - return LLPanel::handleMouseUp(x, y, mask); -} - -bool LLPanelPrimMediaControls::handleKeyHere( KEY key, MASK mask ) -{ - mInactivityTimer.start(); - return LLPanel::handleKeyHere(key, mask); -} - -bool LLPanelPrimMediaControls::isMouseOver() -{ - bool result = false; - - if( getVisible() ) - { - LLCoordWindow cursor_pos_window; - LLCoordScreen cursor_pos_screen; - LLCoordGL cursor_pos_gl; - S32 x, y; - getWindow()->getCursorPosition(&cursor_pos_window); - cursor_pos_gl = cursor_pos_window.convert(); - - if(mMediaControlsStack->getVisible()) - { - mMediaControlsStack->screenPointToLocal(cursor_pos_gl.mX, cursor_pos_gl.mY, &x, &y); - - LLView *hit_child = mMediaControlsStack->childFromPoint(x, y); - if(hit_child && hit_child->getVisible()) - { - // This was useful for debugging both coordinate translation and view hieararchy problems... - // LL_INFOS() << "mouse coords: " << x << ", " << y << " hit child " << hit_child->getName() << LL_ENDL; - - // This will be a direct child of the LLLayoutStack, which should be a layout_panel. - // These may not shown/hidden by the logic in updateShape(), so we need to do another hit test on the children of the layout panel, - // which are the actual controls. - hit_child->screenPointToLocal(cursor_pos_gl.mX, cursor_pos_gl.mY, &x, &y); - - LLView *hit_child_2 = hit_child->childFromPoint(x, y); - if(hit_child_2 && hit_child_2->getVisible()) - { - // This was useful for debugging both coordinate translation and view hieararchy problems... - // LL_INFOS() << " mouse coords: " << x << ", " << y << " hit child 2 " << hit_child_2->getName() << LL_ENDL; - result = true; - } - } - } - } - - return result; -} - - -void LLPanelPrimMediaControls::onClickClose() -{ - close(); -} - -void LLPanelPrimMediaControls::close() -{ - resetZoomLevel(true); - LLViewerMediaFocus::getInstance()->clearFocus(); - setVisible(false); -} - - -void LLPanelPrimMediaControls::onClickBack() -{ - focusOnTarget(); - - LLViewerMediaImpl* impl =getTargetMediaImpl(); - - if (impl) - { - impl->navigateBack(); - } -} - -void LLPanelPrimMediaControls::onClickForward() -{ - focusOnTarget(); - - LLViewerMediaImpl* impl = getTargetMediaImpl(); - - if (impl) - { - impl->navigateForward(); - } -} - -void LLPanelPrimMediaControls::onClickHome() -{ - focusOnTarget(); - - LLViewerMediaImpl* impl = getTargetMediaImpl(); - - if(impl) - { - impl->navigateHome(); - } -} - -void LLPanelPrimMediaControls::onClickOpen() -{ - LLViewerMediaImpl* impl = getTargetMediaImpl(); - if(impl) - { - LLWeb::loadURL(impl->getCurrentMediaURL()); - } -} - -void LLPanelPrimMediaControls::onClickReload() -{ - focusOnTarget(); - - //LLViewerMedia::navigateHome(); - LLViewerMediaImpl* impl = getTargetMediaImpl(); - - if(impl) - { - impl->navigateReload(); - } -} - -void LLPanelPrimMediaControls::onClickPlay() -{ - focusOnTarget(); - - LLViewerMediaImpl* impl = getTargetMediaImpl(); - - if(impl) - { - impl->play(); - } -} - -void LLPanelPrimMediaControls::onClickPause() -{ - focusOnTarget(); - - LLViewerMediaImpl* impl = getTargetMediaImpl(); - - if(impl) - { - impl->pause(); - } -} - -void LLPanelPrimMediaControls::onClickStop() -{ - focusOnTarget(); - - LLViewerMediaImpl* impl = getTargetMediaImpl(); - - if(impl) - { - impl->navigateStop(); - } -} - -void LLPanelPrimMediaControls::onClickMediaStop() -{ - focusOnTarget(); - - LLViewerMediaImpl* impl = getTargetMediaImpl(); - - if(impl) - { - impl->stop(); - } -} - -void LLPanelPrimMediaControls::onClickSkipBack() -{ - focusOnTarget(); - - LLViewerMediaImpl* impl =getTargetMediaImpl(); - - if (impl) - { - impl->skipBack(mSkipStep); - } -} - -void LLPanelPrimMediaControls::onClickSkipForward() -{ - focusOnTarget(); - - LLViewerMediaImpl* impl = getTargetMediaImpl(); - - if (impl) - { - impl->skipForward(mSkipStep); - } -} - -void LLPanelPrimMediaControls::onClickZoom() -{ - focusOnTarget(); - - if(mCurrentZoom == ZOOM_NONE) - { - nextZoomLevel(); - } -} - -void LLPanelPrimMediaControls::nextZoomLevel() -{ - LLViewerObject* objectp = getTargetObject(); - if(objectp && objectp->isHUDAttachment()) - { - // Never allow zooming on HUD attachments. - return; - } - - int index = 0; - while (index < kNumZoomLevels) - { - if (kZoomLevels[index] == mCurrentZoom) - { - index++; - break; - } - index++; - } - mCurrentZoom = kZoomLevels[index % kNumZoomLevels]; - updateZoom(); -} - -void LLPanelPrimMediaControls::resetZoomLevel(bool reset_camera) -{ - if(mCurrentZoom != ZOOM_NONE) - { - mCurrentZoom = ZOOM_NONE; - if(reset_camera) - { - updateZoom(); - } - } -} - -void LLPanelPrimMediaControls::updateZoom() -{ - F32 zoom_padding = 0.0f; - switch (mCurrentZoom) - { - case ZOOM_NONE: - { - gAgentCamera.setFocusOnAvatar(true, ANIMATE); - break; - } - case ZOOM_FAR: - { - zoom_padding = mZoomFarPadding; - break; - } - case ZOOM_MEDIUM: - { - zoom_padding = mZoomMediumPadding; - break; - } - case ZOOM_NEAR: - { - zoom_padding = mZoomNearPadding; - break; - } - default: - { - gAgentCamera.setFocusOnAvatar(true, ANIMATE); - break; - } - } - - if (zoom_padding > 0.0f) - { - // since we only zoom into medium for now, always set zoom_in constraint to true - mZoomedCameraPos = LLViewerMediaFocus::setCameraZoom(getTargetObject(), mTargetObjectNormal, zoom_padding, true); - } - - // Remember the object ID/face we zoomed into, so we can update the zoom icon appropriately - mZoomObjectID = mTargetObjectID; - mZoomObjectFace = mTargetObjectFace; -} - -void LLPanelPrimMediaControls::onScrollUp(void* user_data) -{ - LLPanelPrimMediaControls* this_panel = static_cast (user_data); - this_panel->focusOnTarget(); - - LLViewerMediaImpl* impl = this_panel->getTargetMediaImpl(); - - if(impl) - { - impl->scrollWheel(0, 0, 0, -1, MASK_NONE); - } -} -void LLPanelPrimMediaControls::onScrollUpHeld(void* user_data) -{ - LLPanelPrimMediaControls* this_panel = static_cast (user_data); - this_panel->mScrollState = SCROLL_UP; -} -void LLPanelPrimMediaControls::onScrollRight(void* user_data) -{ - LLPanelPrimMediaControls* this_panel = static_cast (user_data); - this_panel->focusOnTarget(); - - LLViewerMediaImpl* impl = this_panel->getTargetMediaImpl(); - - if(impl) - { - impl->scrollWheel(0, 0, -1, 0, MASK_NONE); -// impl->handleKeyHere(KEY_RIGHT, MASK_NONE); - } -} -void LLPanelPrimMediaControls::onScrollRightHeld(void* user_data) -{ - LLPanelPrimMediaControls* this_panel = static_cast (user_data); - this_panel->mScrollState = SCROLL_RIGHT; -} - -void LLPanelPrimMediaControls::onScrollLeft(void* user_data) -{ - LLPanelPrimMediaControls* this_panel = static_cast (user_data); - this_panel->focusOnTarget(); - - LLViewerMediaImpl* impl = this_panel->getTargetMediaImpl(); - - if(impl) - { - impl->scrollWheel(0, 0, 1, 0, MASK_NONE); -// impl->handleKeyHere(KEY_LEFT, MASK_NONE); - } -} -void LLPanelPrimMediaControls::onScrollLeftHeld(void* user_data) -{ - LLPanelPrimMediaControls* this_panel = static_cast (user_data); - this_panel->mScrollState = SCROLL_LEFT; -} - -void LLPanelPrimMediaControls::onScrollDown(void* user_data) -{ - LLPanelPrimMediaControls* this_panel = static_cast (user_data); - this_panel->focusOnTarget(); - - LLViewerMediaImpl* impl = this_panel->getTargetMediaImpl(); - - if(impl) - { - impl->scrollWheel(0, 0, 0, 1, MASK_NONE); - } -} -void LLPanelPrimMediaControls::onScrollDownHeld(void* user_data) -{ - LLPanelPrimMediaControls* this_panel = static_cast (user_data); - this_panel->mScrollState = SCROLL_DOWN; -} - -void LLPanelPrimMediaControls::onScrollStop(void* user_data) -{ - LLPanelPrimMediaControls* this_panel = static_cast (user_data); - this_panel->mScrollState = SCROLL_NONE; -} - -void LLPanelPrimMediaControls::onCommitURL() -{ - focusOnTarget(); - - std::string url = mMediaAddress->getValue().asString(); - if(getTargetMediaImpl() && !url.empty()) - { - getTargetMediaImpl()->navigateTo( url, "", true); - - // Make sure keyboard focus is set to the media focus object. - gFocusMgr.setKeyboardFocus(LLViewerMediaFocus::getInstance()); - - } - mPauseFadeout = false; - mFadeTimer.start(); -} - - -void LLPanelPrimMediaControls::onInputURL(LLFocusableElement* caller, void *userdata) -{ - - LLPanelPrimMediaControls* this_panel = static_cast (userdata); - this_panel->focusOnTarget(); - - this_panel->mPauseFadeout = true; - this_panel->mFadeTimer.stop(); - this_panel->mFadeTimer.reset(); - -} - -void LLPanelPrimMediaControls::setCurrentURL() -{ -#ifdef USE_COMBO_BOX_FOR_MEDIA_URL -// LLComboBox* media_address_combo = getChild("media_address_combo"); -// // redirects will navigate momentarily to about:blank, don't add to history -// if (media_address_combo && mCurrentURL != "about:blank") -// { -// media_address_combo->remove(mCurrentURL); -// media_address_combo->add(mCurrentURL); -// media_address_combo->selectByValue(mCurrentURL); -// } -#else // USE_COMBO_BOX_FOR_MEDIA_URL - if (mMediaAddress && mCurrentURL != "about:blank") - { - mMediaAddress->setValue(mCurrentURL); - } -#endif // USE_COMBO_BOX_FOR_MEDIA_URL -} - - -void LLPanelPrimMediaControls::onMediaPlaySliderCtrlMouseDown() -{ - mMediaPlaySliderCtrlMouseDownValue = mMediaPlaySliderCtrl->getValue().asReal(); - - mUpdateSlider = false; -} - -void LLPanelPrimMediaControls::onMediaPlaySliderCtrlMouseUp() -{ - F64 cur_value = mMediaPlaySliderCtrl->getValue().asReal(); - - if (mMediaPlaySliderCtrlMouseDownValue != cur_value) - { - focusOnTarget(); - - LLViewerMediaImpl* media_impl = getTargetMediaImpl(); - if (media_impl) - { - if (cur_value <= 0.0) - { - media_impl->stop(); - } - else - { - media_impl->seek(cur_value * mMovieDuration); - } - } - - mUpdateSlider = true; - } -} - -void LLPanelPrimMediaControls::onCommitVolumeUp() -{ - focusOnTarget(); - - LLViewerMediaImpl* media_impl = getTargetMediaImpl(); - if (media_impl) - { - F32 volume = media_impl->getVolume(); - - volume += 0.1f; - if(volume >= 1.0f) - { - volume = 1.0f; - } - - media_impl->setVolume(volume); - mMuteBtn->setToggleState(false); - } -} - -void LLPanelPrimMediaControls::onCommitVolumeDown() -{ - focusOnTarget(); - - LLViewerMediaImpl* media_impl = getTargetMediaImpl(); - if (media_impl) - { - F32 volume = media_impl->getVolume(); - - volume -= 0.1f; - if(volume <= 0.0f) - { - volume = 0.0f; - } - - media_impl->setVolume(volume); - mMuteBtn->setToggleState(false); - } -} - -void LLPanelPrimMediaControls::onCommitVolumeSlider() -{ - focusOnTarget(); - - LLViewerMediaImpl* media_impl = getTargetMediaImpl(); - if (media_impl) - { - media_impl->setVolume(mVolumeSliderCtrl->getValueF32()); - } -} - -void LLPanelPrimMediaControls::onToggleMute() -{ - focusOnTarget(); - - LLViewerMediaImpl* media_impl = getTargetMediaImpl(); - if (media_impl) - { - F32 volume = media_impl->getVolume(); - - if(volume > 0.0) - { - media_impl->setVolume(0.0); - } - else if (mVolumeSliderCtrl->getValueF32() == 0.0) - { - media_impl->setVolume(1.0); - mVolumeSliderCtrl->setValue(1.0); - } - else - { - media_impl->setVolume(mVolumeSliderCtrl->getValueF32()); - } - } -} - -void LLPanelPrimMediaControls::showVolumeSlider() -{ - mVolumeSliderVisible++; -} - -void LLPanelPrimMediaControls::hideVolumeSlider() -{ - mVolumeSliderVisible--; -} - -bool LLPanelPrimMediaControls::shouldVolumeSliderBeVisible() -{ - return mVolumeSliderVisible > 0; -} - -bool LLPanelPrimMediaControls::isZoomDistExceeding() -{ - return (gAgentCamera.getCameraPositionGlobal() - mZoomedCameraPos).normalize() >= EXCEEDING_ZOOM_DISTANCE; -} - -void LLPanelPrimMediaControls::clearFaceOnFade() -{ - if(mClearFaceOnFade) - { - // Hiding this object makes scroll events go missing after it fades out - // (see DEV-41755 for a full description of the train wreck). - // Only hide the controls when we're untargeting. - setVisible(false); - - mClearFaceOnFade = false; - mVolumeSliderVisible = 0; - mTargetImplID = LLUUID::null; - mTargetObjectID = LLUUID::null; - mTargetObjectFace = 0; - } -} - -void LLPanelPrimMediaControls::onMouselookModeIn() -{ - LLViewerMediaFocus::getInstance()->clearHover(); - mHideImmediately = true; -} - -void LLPanelPrimMediaControls::showNotification(LLNotificationPtr notify) -{ - delete mWindowShade; - LLWindowShade::Params params; - params.rect = mMediaRegion->getLocalRect(); - params.follows.flags = FOLLOWS_ALL; - - //HACK: don't hardcode this - if (notify->getIcon() == "Popup_Caution") - { - params.bg_image.name = "Yellow_Gradient"; - params.text_color = LLColor4::black; - } - else - { - //HACK: make this a property of the notification itself, "cancellable" - params.can_close = false; - params.text_color.control = "LabelTextColor"; - } - - mWindowShade = LLUICtrlFactory::create(params); - - mMediaRegion->addChild(mWindowShade); - mWindowShade->show(notify); -} - -void LLPanelPrimMediaControls::hideNotification() -{ - if (mWindowShade) - { - mWindowShade->hide(); - } -} +/** + * @file llpanelprimmediacontrols.cpp + * @brief media controls popup panel + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llagent.h" +#include "llagentcamera.h" +#include "llparcel.h" +#include "llpanel.h" +#include "llselectmgr.h" +#include "llmediaentry.h" +#include "llrender.h" +#include "lldrawable.h" +#include "llviewerwindow.h" +#include "lluictrlfactory.h" +#include "llbutton.h" +#include "llface.h" +#include "llcombobox.h" +#include "lllayoutstack.h" +#include "llslider.h" +#include "llhudview.h" +#include "lliconctrl.h" +#include "lltoolpie.h" +#include "llviewercamera.h" +#include "llviewerobjectlist.h" +#include "llpanelprimmediacontrols.h" +#include "llpluginclassmedia.h" +#include "llprogressbar.h" +#include "llsliderctrl.h" +#include "llstring.h" +#include "llviewercontrol.h" +#include "llviewerdisplay.h" +#include "llviewerparcelmgr.h" +#include "llviewermedia.h" +#include "llviewermediafocus.h" +#include "llvovolume.h" +#include "llweb.h" +#include "llwindow.h" +#include "llwindowshade.h" +#include "llfloatertools.h" // to enable hide if build tools are up +#include "llvector4a.h" + +// Functions pulled from pipeline.cpp +glh::matrix4f get_current_modelview(); +glh::matrix4f get_current_projection(); +// Functions pulled from llviewerdisplay.cpp +bool get_hud_matrices(glh::matrix4f &proj, glh::matrix4f &model); + +// Warning: make sure these two match! +const LLPanelPrimMediaControls::EZoomLevel LLPanelPrimMediaControls::kZoomLevels[] = { ZOOM_NONE, ZOOM_MEDIUM }; +const int LLPanelPrimMediaControls::kNumZoomLevels = 2; + +const F32 EXCEEDING_ZOOM_DISTANCE = 0.5f; +const S32 ADDR_LEFT_PAD = 3; + +// +// LLPanelPrimMediaControls +// + +LLPanelPrimMediaControls::LLPanelPrimMediaControls() : + mAlpha(1.f), + mCurrentURL(""), + mPreviousURL(""), + mPauseFadeout(false), + mUpdateSlider(true), + mClearFaceOnFade(false), + mCurrentRate(0.0), + mMovieDuration(0.0), + mTargetObjectID(LLUUID::null), + mTargetObjectFace(0), + mTargetImplID(LLUUID::null), + mTargetObjectNormal(LLVector3::zero), + mZoomObjectID(LLUUID::null), + mZoomObjectFace(0), + mVolumeSliderVisible(0), + mZoomedCameraPos(), + mWindowShade(NULL), + mHideImmediately(false), + mSecureURL(false), + mMediaPlaySliderCtrlMouseDownValue(0.0) +{ + mCommitCallbackRegistrar.add("MediaCtrl.Close", boost::bind(&LLPanelPrimMediaControls::onClickClose, this)); + mCommitCallbackRegistrar.add("MediaCtrl.Back", boost::bind(&LLPanelPrimMediaControls::onClickBack, this)); + mCommitCallbackRegistrar.add("MediaCtrl.Forward", boost::bind(&LLPanelPrimMediaControls::onClickForward, this)); + mCommitCallbackRegistrar.add("MediaCtrl.Home", boost::bind(&LLPanelPrimMediaControls::onClickHome, this)); + mCommitCallbackRegistrar.add("MediaCtrl.Stop", boost::bind(&LLPanelPrimMediaControls::onClickStop, this)); + mCommitCallbackRegistrar.add("MediaCtrl.MediaStop", boost::bind(&LLPanelPrimMediaControls::onClickMediaStop, this)); + mCommitCallbackRegistrar.add("MediaCtrl.Reload", boost::bind(&LLPanelPrimMediaControls::onClickReload, this)); + mCommitCallbackRegistrar.add("MediaCtrl.Play", boost::bind(&LLPanelPrimMediaControls::onClickPlay, this)); + mCommitCallbackRegistrar.add("MediaCtrl.Pause", boost::bind(&LLPanelPrimMediaControls::onClickPause, this)); + mCommitCallbackRegistrar.add("MediaCtrl.Open", boost::bind(&LLPanelPrimMediaControls::onClickOpen, this)); + mCommitCallbackRegistrar.add("MediaCtrl.Zoom", boost::bind(&LLPanelPrimMediaControls::onClickZoom, this)); + mCommitCallbackRegistrar.add("MediaCtrl.CommitURL", boost::bind(&LLPanelPrimMediaControls::onCommitURL, this)); + mCommitCallbackRegistrar.add("MediaCtrl.MouseDown", boost::bind(&LLPanelPrimMediaControls::onMediaPlaySliderCtrlMouseDown, this)); + mCommitCallbackRegistrar.add("MediaCtrl.MouseUp", boost::bind(&LLPanelPrimMediaControls::onMediaPlaySliderCtrlMouseUp, this)); + mCommitCallbackRegistrar.add("MediaCtrl.CommitVolumeUp", boost::bind(&LLPanelPrimMediaControls::onCommitVolumeUp, this)); + mCommitCallbackRegistrar.add("MediaCtrl.CommitVolumeDown", boost::bind(&LLPanelPrimMediaControls::onCommitVolumeDown, this)); + mCommitCallbackRegistrar.add("MediaCtrl.Volume", boost::bind(&LLPanelPrimMediaControls::onCommitVolumeSlider, this)); + mCommitCallbackRegistrar.add("MediaCtrl.ToggleMute", boost::bind(&LLPanelPrimMediaControls::onToggleMute, this)); + mCommitCallbackRegistrar.add("MediaCtrl.ShowVolumeSlider", boost::bind(&LLPanelPrimMediaControls::showVolumeSlider, this)); + mCommitCallbackRegistrar.add("MediaCtrl.HideVolumeSlider", boost::bind(&LLPanelPrimMediaControls::hideVolumeSlider, this)); + mCommitCallbackRegistrar.add("MediaCtrl.SkipBack", boost::bind(&LLPanelPrimMediaControls::onClickSkipBack, this)); + mCommitCallbackRegistrar.add("MediaCtrl.SkipForward", boost::bind(&LLPanelPrimMediaControls::onClickSkipForward, this)); + + buildFromFile( "panel_prim_media_controls.xml"); + mInactivityTimer.reset(); + mFadeTimer.stop(); + mCurrentZoom = ZOOM_NONE; + mScrollState = SCROLL_NONE; + + mPanelHandle.bind(this); + + mInactiveTimeout = gSavedSettings.getF32("MediaControlTimeout"); + mControlFadeTime = gSavedSettings.getF32("MediaControlFadeTime"); +} + +LLPanelPrimMediaControls::~LLPanelPrimMediaControls() +{ +} + +bool LLPanelPrimMediaControls::postBuild() +{ + mMediaRegion = getChild("media_region"); + mBackCtrl = getChild("back"); + mFwdCtrl = getChild("fwd"); + mReloadCtrl = getChild("reload"); + mPlayCtrl = getChild("play"); + mPauseCtrl = getChild("pause"); + mStopCtrl = getChild("stop"); + mMediaStopCtrl = getChild("media_stop"); + mHomeCtrl = getChild("home"); + mUnzoomCtrl = getChild("close"); // This is actually "unzoom" + mOpenCtrl = getChild("new_window"); + mZoomCtrl = getChild("zoom_frame"); + mMediaProgressPanel = getChild("media_progress_indicator"); + mMediaProgressBar = getChild("media_progress_bar"); + mMediaAddressCtrl = getChild("media_address"); + mMediaAddress = getChild("media_address_url"); + mMediaPlaySliderPanel = getChild("media_play_position"); + mMediaPlaySliderCtrl = getChild("media_play_slider"); + mSkipFwdCtrl = getChild("skip_forward"); + mSkipBackCtrl = getChild("skip_back"); + mVolumeCtrl = getChild("media_volume"); + mMuteBtn = getChild("media_mute_button"); + mVolumeSliderCtrl = getChild("volume_slider"); + mWhitelistIcon = getChild("media_whitelist_flag"); + mSecureLockIcon = getChild("media_secure_lock_flag"); + mMediaControlsStack = getChild("media_controls"); + mLeftBookend = getChild("left_bookend"); + mRightBookend = getChild("right_bookend"); + mBackgroundImage = LLUI::getUIImage(getString("control_background_image_name")); + mVolumeSliderBackgroundImage = LLUI::getUIImage(getString("control_background_image_name")); + LLStringUtil::convertToF32(getString("skip_step"), mSkipStep); + LLStringUtil::convertToS32(getString("min_width"), mMinWidth); + LLStringUtil::convertToS32(getString("min_height"), mMinHeight); + LLStringUtil::convertToF32(getString("zoom_near_padding"), mZoomNearPadding); + LLStringUtil::convertToF32(getString("zoom_medium_padding"), mZoomMediumPadding); + LLStringUtil::convertToF32(getString("zoom_far_padding"), mZoomFarPadding); + LLStringUtil::convertToS32(getString("top_world_view_avoid_zone"), mTopWorldViewAvoidZone); + + // These are currently removed...but getChild creates a "dummy" widget. + // This class handles them missing. + mMediaPanelScroll = findChild("media_panel_scroll"); + mScrollUpCtrl = findChild("scrollup"); + mScrollLeftCtrl = findChild("scrollleft"); + mScrollRightCtrl = findChild("scrollright"); + mScrollDownCtrl = findChild("scrolldown"); + + if (mScrollUpCtrl) + { + mScrollUpCtrl->setClickedCallback(onScrollUp, this); + mScrollUpCtrl->setHeldDownCallback(onScrollUpHeld, this); + mScrollUpCtrl->setMouseUpCallback(onScrollStop, this); + } + if (mScrollLeftCtrl) + { + mScrollLeftCtrl->setClickedCallback(onScrollLeft, this); + mScrollLeftCtrl->setHeldDownCallback(onScrollLeftHeld, this); + mScrollLeftCtrl->setMouseUpCallback(onScrollStop, this); + } + if (mScrollRightCtrl) + { + mScrollRightCtrl->setClickedCallback(onScrollRight, this); + mScrollRightCtrl->setHeldDownCallback(onScrollRightHeld, this); + mScrollRightCtrl->setMouseUpCallback(onScrollStop, this); + } + if (mScrollDownCtrl) + { + mScrollDownCtrl->setClickedCallback(onScrollDown, this); + mScrollDownCtrl->setHeldDownCallback(onScrollDownHeld, this); + mScrollDownCtrl->setMouseUpCallback(onScrollStop, this); + } + + mMediaAddress->setFocusReceivedCallback(boost::bind(&LLPanelPrimMediaControls::onInputURL, _1, this )); + + gAgent.setMouselookModeInCallback(boost::bind(&LLPanelPrimMediaControls::onMouselookModeIn, this)); + + LLWindowShade::Params window_shade_params; + window_shade_params.name = "window_shade"; + + mCurrentZoom = ZOOM_NONE; + // clicks on buttons do not remove keyboard focus from media + setIsChrome(true); + return true; +} + +void LLPanelPrimMediaControls::setMediaFace(LLPointer objectp, S32 face, viewer_media_t media_impl, LLVector3 pick_normal) +{ + if (media_impl.notNull() && objectp.notNull()) + { + LLUUID prev_id = mTargetImplID; + mTargetImplID = media_impl->getMediaTextureID(); + mTargetObjectID = objectp->getID(); + mTargetObjectFace = face; + mTargetObjectNormal = pick_normal; + mClearFaceOnFade = false; + + if (prev_id != mTargetImplID) + mVolumeSliderCtrl->setValue(media_impl->getVolume()); + } + else + { + // This happens on a timer now. +// mTargetImplID = LLUUID::null; +// mTargetObjectID = LLUUID::null; +// mTargetObjectFace = 0; + mClearFaceOnFade = true; + } + + updateShape(); +} + +void LLPanelPrimMediaControls::focusOnTarget() +{ + // Sets the media focus to the current target of the LLPanelPrimMediaControls. + // This is how we transition from hover to focus when the user clicks on a control. + LLViewerMediaImpl* media_impl = getTargetMediaImpl(); + if(media_impl) + { + if (!media_impl->hasFocus()) + { + // The current target doesn't have media focus -- focus on it. + LLViewerObject* objectp = getTargetObject(); + LLViewerMediaFocus::getInstance()->setFocusFace(objectp, mTargetObjectFace, media_impl, mTargetObjectNormal); + } + } +} + +LLViewerMediaImpl* LLPanelPrimMediaControls::getTargetMediaImpl() +{ + return LLViewerMedia::getInstance()->getMediaImplFromTextureID(mTargetImplID); +} + +LLViewerObject* LLPanelPrimMediaControls::getTargetObject() +{ + return gObjectList.findObject(mTargetObjectID); +} + +LLPluginClassMedia* LLPanelPrimMediaControls::getTargetMediaPlugin() +{ + LLViewerMediaImpl* impl = getTargetMediaImpl(); + if(impl && impl->hasMedia()) + { + return impl->getMediaPlugin(); + } + + return NULL; +} + +void LLPanelPrimMediaControls::updateShape() +{ + LLViewerMediaImpl* media_impl = getTargetMediaImpl(); + LLViewerObject* objectp = getTargetObject(); + + if(!media_impl || gFloaterTools->getVisible()) + { + setVisible(false); + return; + } + + LLPluginClassMedia* media_plugin = NULL; + if(media_impl->hasMedia()) + { + media_plugin = media_impl->getMediaPlugin(); + } + + LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + + bool can_navigate = parcel->getMediaAllowNavigate(); + bool enabled = false; + bool is_zoomed = (mCurrentZoom != ZOOM_NONE) && (mTargetObjectID == mZoomObjectID) && (mTargetObjectFace == mZoomObjectFace) && !isZoomDistExceeding(); + + // There is no such thing as "has_focus" being different from normal controls set + // anymore (as of user feedback from bri 10/09). So we cheat here and force 'has_focus' + // to 'true' (or, actually, we use a setting) + bool has_focus = (gSavedSettings.getBOOL("PrimMediaControlsUseHoverControlSet")) ? media_impl->hasFocus() : true; + setVisible(enabled); + + if (objectp) + { + bool hasPermsControl = true; + bool mini_controls = false; + LLMediaEntry *media_data = objectp->getTE(mTargetObjectFace)->getMediaData(); + if (media_data && NULL != dynamic_cast(objectp)) + { + // Don't show the media controls if we do not have permissions + enabled = dynamic_cast(objectp)->hasMediaPermission(media_data, LLVOVolume::MEDIA_PERM_CONTROL); + hasPermsControl = dynamic_cast(objectp)->hasMediaPermission(media_data, LLVOVolume::MEDIA_PERM_CONTROL); + mini_controls = (LLMediaEntry::MINI == media_data->getControls()); + } + const bool is_hud = objectp->isHUDAttachment(); + + // + // Set the state of the buttons + // + + // XXX RSP: TODO: FIXME: clean this up so that it is clearer what mode we are in, + // and that only the proper controls get made visible/enabled according to that mode. + mBackCtrl->setVisible(has_focus); + mFwdCtrl->setVisible(has_focus); + mReloadCtrl->setVisible(has_focus); + mStopCtrl->setVisible(false); + mHomeCtrl->setVisible(has_focus); + mZoomCtrl->setVisible(!is_zoomed); + mUnzoomCtrl->setVisible(is_zoomed); + mOpenCtrl->setVisible(true); + mMediaAddressCtrl->setVisible(has_focus && !mini_controls); + mMediaPlaySliderPanel->setVisible(has_focus && !mini_controls); + mVolumeCtrl->setVisible(false); + + mWhitelistIcon->setVisible(!mini_controls && (media_data)?media_data->getWhiteListEnable():false); + // Disable zoom if HUD + mZoomCtrl->setEnabled(!is_hud); + mUnzoomCtrl->setEnabled(!is_hud); + mSecureURL = false; + mCurrentURL = media_impl->getCurrentMediaURL(); + + mBackCtrl->setEnabled((media_impl != NULL) && media_impl->canNavigateBack() && can_navigate); + mFwdCtrl->setEnabled((media_impl != NULL) && media_impl->canNavigateForward() && can_navigate); + mStopCtrl->setEnabled(has_focus && can_navigate); + mHomeCtrl->setEnabled(has_focus && can_navigate); + LLPluginClassMediaOwner::EMediaStatus result = ((media_impl != NULL) && media_impl->hasMedia()) ? media_plugin->getStatus() : LLPluginClassMediaOwner::MEDIA_NONE; + + mVolumeCtrl->setVisible(has_focus); + mVolumeCtrl->setEnabled(has_focus); + mVolumeSliderCtrl->setEnabled(has_focus && shouldVolumeSliderBeVisible()); + mVolumeSliderCtrl->setVisible(has_focus && shouldVolumeSliderBeVisible()); + + if(media_plugin && media_plugin->pluginSupportsMediaTime()) + { + mReloadCtrl->setEnabled(false); + mReloadCtrl->setVisible(false); + mMediaStopCtrl->setVisible(has_focus); + mHomeCtrl->setVisible(has_focus); + mBackCtrl->setVisible(false); + mFwdCtrl->setVisible(false); + mMediaAddressCtrl->setVisible(false); + mMediaAddressCtrl->setEnabled(false); + mMediaPlaySliderPanel->setVisible(has_focus && !mini_controls); + mMediaPlaySliderPanel->setEnabled(has_focus && !mini_controls); + mSkipFwdCtrl->setVisible(has_focus && !mini_controls); + mSkipFwdCtrl->setEnabled(has_focus && !mini_controls); + mSkipBackCtrl->setVisible(has_focus && !mini_controls); + mSkipBackCtrl->setEnabled(has_focus && !mini_controls); + + mVolumeCtrl->setVisible(has_focus); + mVolumeCtrl->setEnabled(has_focus); + mVolumeSliderCtrl->setEnabled(has_focus && shouldVolumeSliderBeVisible()); + mVolumeSliderCtrl->setVisible(has_focus && shouldVolumeSliderBeVisible()); + + mWhitelistIcon->setVisible(false); + mSecureURL = false; + if (mMediaPanelScroll) + { + mMediaPanelScroll->setVisible(false); + mScrollUpCtrl->setVisible(false); + mScrollDownCtrl->setVisible(false); + mScrollRightCtrl->setVisible(false); + mScrollDownCtrl->setVisible(false); + } + + F32 volume = media_impl->getVolume(); + // movie's url changed + if(mCurrentURL!=mPreviousURL) + { + mMovieDuration = media_plugin->getDuration(); + mPreviousURL = mCurrentURL; + } + + if(mMovieDuration == 0) + { + mMovieDuration = media_plugin->getDuration(); + mMediaPlaySliderCtrl->setValue(0); + mMediaPlaySliderCtrl->setEnabled(false); + } + // TODO: What if it's not fully loaded + + if(mUpdateSlider && mMovieDuration!= 0) + { + F64 current_time = media_plugin->getCurrentTime(); + F32 percent = current_time / mMovieDuration; + mMediaPlaySliderCtrl->setValue(percent); + mMediaPlaySliderCtrl->setEnabled(true); + } + + // video volume + if(volume <= 0.0) + { + mMuteBtn->setToggleState(true); + } + else if (volume >= 1.0) + { + mMuteBtn->setToggleState(false); + } + else + { + mMuteBtn->setToggleState(false); + } + + switch(result) + { + case LLPluginClassMediaOwner::MEDIA_PLAYING: + mPlayCtrl->setEnabled(false); + mPlayCtrl->setVisible(false); + mPauseCtrl->setEnabled(true); + mPauseCtrl->setVisible(has_focus); + + break; + case LLPluginClassMediaOwner::MEDIA_PAUSED: + default: + mPauseCtrl->setEnabled(false); + mPauseCtrl->setVisible(false); + mPlayCtrl->setEnabled(true); + mPlayCtrl->setVisible(has_focus); + break; + } + } + else // web based + { + if(media_plugin) + { + mCurrentURL = media_plugin->getLocation(); + } + else + { + mCurrentURL.clear(); + } + + mPlayCtrl->setVisible(false); + mPauseCtrl->setVisible(false); + mMediaStopCtrl->setVisible(false); + mMediaAddressCtrl->setVisible(has_focus && !mini_controls); + mMediaAddressCtrl->setEnabled(has_focus && !mini_controls); + mMediaPlaySliderPanel->setVisible(false); + mMediaPlaySliderPanel->setEnabled(false); + mSkipFwdCtrl->setVisible(false); + mSkipFwdCtrl->setEnabled(false); + mSkipBackCtrl->setVisible(false); + mSkipBackCtrl->setEnabled(false); + + if(media_impl->getVolume() <= 0.0) + { + mMuteBtn->setToggleState(true); + } + else + { + mMuteBtn->setToggleState(false); + } + + if (mMediaPanelScroll) + { + mMediaPanelScroll->setVisible(has_focus); + mScrollUpCtrl->setVisible(has_focus); + mScrollDownCtrl->setVisible(has_focus); + mScrollRightCtrl->setVisible(has_focus); + mScrollDownCtrl->setVisible(has_focus); + } + // TODO: get the secure lock bool from media plug in + std::string prefix = std::string("https://"); + std::string test_prefix = mCurrentURL.substr(0, prefix.length()); + LLStringUtil::toLower(test_prefix); + mSecureURL = has_focus && (test_prefix == prefix); + + S32 left_pad = mSecureURL ? mSecureLockIcon->getRect().getWidth() : ADDR_LEFT_PAD; + mMediaAddress->setTextPadding(left_pad, 0); + + if(mCurrentURL!=mPreviousURL) + { + setCurrentURL(); + mPreviousURL = mCurrentURL; + } + + if(result == LLPluginClassMediaOwner::MEDIA_LOADING) + { + mReloadCtrl->setEnabled(false); + mReloadCtrl->setVisible(false); + mStopCtrl->setEnabled(true); + mStopCtrl->setVisible(has_focus); + } + else + { + mReloadCtrl->setEnabled(true); + mReloadCtrl->setVisible(has_focus); + mStopCtrl->setEnabled(false); + mStopCtrl->setVisible(false); + } + } + + + if(media_plugin) + { + // + // Handle progress bar + // + if(LLPluginClassMediaOwner::MEDIA_LOADING == media_plugin->getStatus()) + { + mMediaProgressPanel->setVisible(true); + mMediaProgressBar->setValue(media_plugin->getProgressPercent()); + } + else + { + mMediaProgressPanel->setVisible(false); + } + } + + if(media_impl) + { + // + // Handle Scrolling + // + switch (mScrollState) + { + case SCROLL_UP: + media_impl->scrollWheel(0, 0, 0, -1, MASK_NONE); + break; + case SCROLL_DOWN: + media_impl->scrollWheel(0, 0, 0, 1, MASK_NONE); + break; + case SCROLL_LEFT: + media_impl->scrollWheel(0, 0, 1, 0, MASK_NONE); + // media_impl->handleKeyHere(KEY_LEFT, MASK_NONE); + break; + case SCROLL_RIGHT: + media_impl->scrollWheel(0, 0, -1, 0, MASK_NONE); + // media_impl->handleKeyHere(KEY_RIGHT, MASK_NONE); + break; + case SCROLL_NONE: + default: + break; + } + } + + // Web plugins and HUD may have media controls invisible for user, but still need scroll mouse events. + // LLView checks for visibleEnabledAndContains() and won't pass events to invisible panel, so instead + // of hiding whole panel hide each control instead (if user has no perms). + // Note: It might be beneficial to keep panel visible for all plugins to make behavior consistent, but + // for now limiting change to cases that need events. + + if (!is_hud && (!media_plugin || media_plugin->pluginSupportsMediaTime())) + { + setVisible(enabled); + } + else + { + if( !hasPermsControl ) + { + mBackCtrl->setVisible(false); + mFwdCtrl->setVisible(false); + mReloadCtrl->setVisible(false); + mStopCtrl->setVisible(false); + mHomeCtrl->setVisible(false); + mZoomCtrl->setVisible(false); + mUnzoomCtrl->setVisible(false); + mOpenCtrl->setVisible(false); + mMediaAddressCtrl->setVisible(false); + mMediaPlaySliderPanel->setVisible(false); + mVolumeCtrl->setVisible(false); + mMediaProgressPanel->setVisible(false); + mVolumeSliderCtrl->setVisible(false); + } + + setVisible(true); + } + + // + // Calculate position and shape of the controls + // + std::vector::iterator vert_it; + std::vector::iterator vert_end; + std::vector vect_face; + + LLVolume* volume = objectp->getVolume(); + + if (volume) + { + const LLVolumeFace& vf = volume->getVolumeFace(mTargetObjectFace); + + LLVector3 ext[2]; + ext[0].set(vf.mExtents[0].getF32ptr()); + ext[1].set(vf.mExtents[1].getF32ptr()); + + LLVector3 center = (ext[0]+ext[1])*0.5f; + LLVector3 size = (ext[1]-ext[0])*0.5f; + LLVector3 vert[] = + { + center + size.scaledVec(LLVector3(1,1,1)), + center + size.scaledVec(LLVector3(-1,1,1)), + center + size.scaledVec(LLVector3(1,-1,1)), + center + size.scaledVec(LLVector3(-1,-1,1)), + center + size.scaledVec(LLVector3(1,1,-1)), + center + size.scaledVec(LLVector3(-1,1,-1)), + center + size.scaledVec(LLVector3(1,-1,-1)), + center + size.scaledVec(LLVector3(-1,-1,-1)), + }; + + LLVOVolume* vo = (LLVOVolume*) objectp; + + for (U32 i = 0; i < 8; i++) + { + vect_face.push_back(vo->volumePositionToAgent(vert[i])); + } + } + vert_it = vect_face.begin(); + vert_end = vect_face.end(); + + glh::matrix4f mat; + if (!is_hud) + { + mat = get_current_projection() * get_current_modelview(); + } + else { + glh::matrix4f proj, modelview; + if (get_hud_matrices(proj, modelview)) + mat = proj * modelview; + } + LLVector3 min = LLVector3(1,1,1); + LLVector3 max = LLVector3(-1,-1,-1); + for(; vert_it != vert_end; ++vert_it) + { + // project silhouette vertices into screen space + glh::vec3f screen_vert = glh::vec3f(vert_it->mV); + mat.mult_matrix_vec(screen_vert); + + // add to screenspace bounding box + update_min_max(min, max, LLVector3(screen_vert.v)); + } + + // convert screenspace bbox to pixels (in screen coords) + LLRect window_rect = gViewerWindow->getWorldViewRectScaled(); + LLCoordGL screen_min; + screen_min.mX = ll_round((F32)window_rect.mLeft + (F32)window_rect.getWidth() * (min.mV[VX] + 1.f) * 0.5f); + screen_min.mY = ll_round((F32)window_rect.mBottom + (F32)window_rect.getHeight() * (min.mV[VY] + 1.f) * 0.5f); + + LLCoordGL screen_max; + screen_max.mX = ll_round((F32)window_rect.mLeft + (F32)window_rect.getWidth() * (max.mV[VX] + 1.f) * 0.5f); + screen_max.mY = ll_round((F32)window_rect.mBottom + (F32)window_rect.getHeight() * (max.mV[VY] + 1.f) * 0.5f); + + // grow panel so that screenspace bounding box fits inside "media_region" element of panel + LLRect media_panel_rect; + // Get the height of the controls (less the volume slider) + S32 controls_height = mMediaControlsStack->getRect().getHeight() - mVolumeSliderCtrl->getRect().getHeight(); + getParent()->screenRectToLocal(LLRect(screen_min.mX, screen_max.mY, screen_max.mX, screen_min.mY), &media_panel_rect); + media_panel_rect.mTop += controls_height; + + // keep all parts of panel on-screen + // Area of the top of the world view to avoid putting the controls + window_rect.mTop -= mTopWorldViewAvoidZone; + // Don't include "spacing" bookends on left & right of the media controls + window_rect.mLeft -= mLeftBookend->getRect().getWidth(); + window_rect.mRight += mRightBookend->getRect().getWidth(); + // Don't include the volume slider + window_rect.mBottom -= mVolumeSliderCtrl->getRect().getHeight(); + media_panel_rect.intersectWith(window_rect); + + // clamp to minimum size, keeping rect inside window + S32 centerX = media_panel_rect.getCenterX(); + S32 centerY = media_panel_rect.getCenterY(); + // Shrink screen rect by min width and height, to ensure containment + window_rect.stretch(-mMinWidth/2, -mMinHeight/2); + window_rect.clampPointToRect(centerX, centerY); + media_panel_rect.setCenterAndSize(centerX, centerY, + llmax(mMinWidth, media_panel_rect.getWidth()), + llmax(mMinHeight, media_panel_rect.getHeight())); + + // Finally set the size of the panel + setShape(media_panel_rect, true); + + // Test mouse position to see if the cursor is stationary + LLCoordWindow cursor_pos_window; + getWindow()->getCursorPosition(&cursor_pos_window); + + // If last pos is not equal to current pos, the mouse has moved + // We need to reset the timer, and make sure the panel is visible + if(cursor_pos_window.mX != mLastCursorPos.mX || + cursor_pos_window.mY != mLastCursorPos.mY || + mScrollState != SCROLL_NONE) + { + mInactivityTimer.start(); + mLastCursorPos = cursor_pos_window; + } + + if(isMouseOver() || hasFocus()) + { + // Never fade the controls if the mouse is over them or they have keyboard focus. + mFadeTimer.stop(); + } + else if(!mClearFaceOnFade && (mInactivityTimer.getElapsedTimeF32() < mInactiveTimeout)) + { + // Mouse is over the object, but has not been stationary for long enough to fade the UI + mFadeTimer.stop(); + } + else if(! mFadeTimer.getStarted() ) + { + // we need to start fading the UI (and we have not already started) + mFadeTimer.reset(); + mFadeTimer.start(); + } + else + { + // I don't think this is correct anymore. This is done in draw() after the fade has completed. + // setVisible(false); + } + } +} + +/*virtual*/ +void LLPanelPrimMediaControls::draw() +{ + LLViewerMediaImpl* impl = getTargetMediaImpl(); + if (impl) + { + LLNotificationPtr notification = impl->getCurrentNotification(); + if (notification != mActiveNotification) + { + mActiveNotification = notification; + if (notification) + { + showNotification(notification); + } + else + { + hideNotification(); + } + } + } + + F32 alpha = getDrawContext().mAlpha; + if(mHideImmediately) + { + //hide this panel + clearFaceOnFade(); + + mHideImmediately = false; + } + else if(mFadeTimer.getStarted()) + { + F32 time = mFadeTimer.getElapsedTimeF32(); + alpha *= llmax(lerp(1.0, 0.0, time / mControlFadeTime), 0.0f); + + if(time >= mControlFadeTime) + { + //hide this panel + clearFaceOnFade(); + } + } + + // Show/hide the lock icon for secure browsing + mSecureLockIcon->setVisible(mSecureURL && !mMediaAddress->hasFocus()); + + // Build rect for icon area in coord system of this panel + // Assumes layout_stack is a direct child of this panel + mMediaControlsStack->updateLayout(); + + // adjust for layout stack spacing + S32 space = mMediaControlsStack->getPanelSpacing() + 2; + LLRect controls_bg_area = mMediaControlsStack->getRect(); + + controls_bg_area.mTop += space + 2; + + // adjust to ignore space from volume slider + controls_bg_area.mBottom += mVolumeSliderCtrl->getRect().getHeight(); + + // adjust to ignore space from left bookend padding + controls_bg_area.mLeft += mLeftBookend->getRect().getWidth() - space; + + // ignore space from right bookend padding + controls_bg_area.mRight -= mRightBookend->getRect().getWidth() - space - 2; + + // draw control background UI image + + LLViewerObject* objectp = getTargetObject(); + LLMediaEntry *media_data(0); + + if( objectp ) + media_data = objectp->getTE(mTargetObjectFace)->getMediaData(); + + if( !dynamic_cast(objectp) || !media_data || dynamic_cast(objectp)->hasMediaPermission(media_data, LLVOVolume::MEDIA_PERM_CONTROL) ) + mBackgroundImage->draw( controls_bg_area, UI_VERTEX_COLOR % alpha); + + // draw volume slider background UI image + if (mVolumeSliderCtrl->getVisible()) + { + LLRect volume_slider_rect; + screenRectToLocal(mVolumeSliderCtrl->calcScreenRect(), &volume_slider_rect); + mVolumeSliderBackgroundImage->draw(volume_slider_rect, UI_VERTEX_COLOR % alpha); + } + + { + LLViewDrawContext context(alpha); + LLPanel::draw(); + } +} + +bool LLPanelPrimMediaControls::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + mInactivityTimer.start(); + bool res = false; + + // Unlike other mouse events, we need to handle scroll here otherwise + // it will be intercepted by camera and won't reach toolpie + if (LLViewerMediaFocus::getInstance()->isHoveringOverFocused()) + { + // either let toolpie handle this or expose mHoverPick.mUVCoords in some way + res = LLToolPie::getInstance()->handleScrollWheel(x, y, clicks); + } + + return res; +} + +bool LLPanelPrimMediaControls::handleScrollHWheel(S32 x, S32 y, S32 clicks) +{ + mInactivityTimer.start(); + bool res = false; + + if (LLViewerMediaFocus::getInstance()->isHoveringOverFocused()) + { + // either let toolpie handle this or expose mHoverPick.mUVCoords in some way + res = LLToolPie::getInstance()->handleScrollHWheel(x, y, clicks); + } + + return res; +} + +bool LLPanelPrimMediaControls::handleMouseDown(S32 x, S32 y, MASK mask) +{ + mInactivityTimer.start(); + return LLPanel::handleMouseDown(x, y, mask); +} + +bool LLPanelPrimMediaControls::handleMouseUp(S32 x, S32 y, MASK mask) +{ + mInactivityTimer.start(); + return LLPanel::handleMouseUp(x, y, mask); +} + +bool LLPanelPrimMediaControls::handleKeyHere( KEY key, MASK mask ) +{ + mInactivityTimer.start(); + return LLPanel::handleKeyHere(key, mask); +} + +bool LLPanelPrimMediaControls::isMouseOver() +{ + bool result = false; + + if( getVisible() ) + { + LLCoordWindow cursor_pos_window; + LLCoordScreen cursor_pos_screen; + LLCoordGL cursor_pos_gl; + S32 x, y; + getWindow()->getCursorPosition(&cursor_pos_window); + cursor_pos_gl = cursor_pos_window.convert(); + + if(mMediaControlsStack->getVisible()) + { + mMediaControlsStack->screenPointToLocal(cursor_pos_gl.mX, cursor_pos_gl.mY, &x, &y); + + LLView *hit_child = mMediaControlsStack->childFromPoint(x, y); + if(hit_child && hit_child->getVisible()) + { + // This was useful for debugging both coordinate translation and view hieararchy problems... + // LL_INFOS() << "mouse coords: " << x << ", " << y << " hit child " << hit_child->getName() << LL_ENDL; + + // This will be a direct child of the LLLayoutStack, which should be a layout_panel. + // These may not shown/hidden by the logic in updateShape(), so we need to do another hit test on the children of the layout panel, + // which are the actual controls. + hit_child->screenPointToLocal(cursor_pos_gl.mX, cursor_pos_gl.mY, &x, &y); + + LLView *hit_child_2 = hit_child->childFromPoint(x, y); + if(hit_child_2 && hit_child_2->getVisible()) + { + // This was useful for debugging both coordinate translation and view hieararchy problems... + // LL_INFOS() << " mouse coords: " << x << ", " << y << " hit child 2 " << hit_child_2->getName() << LL_ENDL; + result = true; + } + } + } + } + + return result; +} + + +void LLPanelPrimMediaControls::onClickClose() +{ + close(); +} + +void LLPanelPrimMediaControls::close() +{ + resetZoomLevel(true); + LLViewerMediaFocus::getInstance()->clearFocus(); + setVisible(false); +} + + +void LLPanelPrimMediaControls::onClickBack() +{ + focusOnTarget(); + + LLViewerMediaImpl* impl =getTargetMediaImpl(); + + if (impl) + { + impl->navigateBack(); + } +} + +void LLPanelPrimMediaControls::onClickForward() +{ + focusOnTarget(); + + LLViewerMediaImpl* impl = getTargetMediaImpl(); + + if (impl) + { + impl->navigateForward(); + } +} + +void LLPanelPrimMediaControls::onClickHome() +{ + focusOnTarget(); + + LLViewerMediaImpl* impl = getTargetMediaImpl(); + + if(impl) + { + impl->navigateHome(); + } +} + +void LLPanelPrimMediaControls::onClickOpen() +{ + LLViewerMediaImpl* impl = getTargetMediaImpl(); + if(impl) + { + LLWeb::loadURL(impl->getCurrentMediaURL()); + } +} + +void LLPanelPrimMediaControls::onClickReload() +{ + focusOnTarget(); + + //LLViewerMedia::navigateHome(); + LLViewerMediaImpl* impl = getTargetMediaImpl(); + + if(impl) + { + impl->navigateReload(); + } +} + +void LLPanelPrimMediaControls::onClickPlay() +{ + focusOnTarget(); + + LLViewerMediaImpl* impl = getTargetMediaImpl(); + + if(impl) + { + impl->play(); + } +} + +void LLPanelPrimMediaControls::onClickPause() +{ + focusOnTarget(); + + LLViewerMediaImpl* impl = getTargetMediaImpl(); + + if(impl) + { + impl->pause(); + } +} + +void LLPanelPrimMediaControls::onClickStop() +{ + focusOnTarget(); + + LLViewerMediaImpl* impl = getTargetMediaImpl(); + + if(impl) + { + impl->navigateStop(); + } +} + +void LLPanelPrimMediaControls::onClickMediaStop() +{ + focusOnTarget(); + + LLViewerMediaImpl* impl = getTargetMediaImpl(); + + if(impl) + { + impl->stop(); + } +} + +void LLPanelPrimMediaControls::onClickSkipBack() +{ + focusOnTarget(); + + LLViewerMediaImpl* impl =getTargetMediaImpl(); + + if (impl) + { + impl->skipBack(mSkipStep); + } +} + +void LLPanelPrimMediaControls::onClickSkipForward() +{ + focusOnTarget(); + + LLViewerMediaImpl* impl = getTargetMediaImpl(); + + if (impl) + { + impl->skipForward(mSkipStep); + } +} + +void LLPanelPrimMediaControls::onClickZoom() +{ + focusOnTarget(); + + if(mCurrentZoom == ZOOM_NONE) + { + nextZoomLevel(); + } +} + +void LLPanelPrimMediaControls::nextZoomLevel() +{ + LLViewerObject* objectp = getTargetObject(); + if(objectp && objectp->isHUDAttachment()) + { + // Never allow zooming on HUD attachments. + return; + } + + int index = 0; + while (index < kNumZoomLevels) + { + if (kZoomLevels[index] == mCurrentZoom) + { + index++; + break; + } + index++; + } + mCurrentZoom = kZoomLevels[index % kNumZoomLevels]; + updateZoom(); +} + +void LLPanelPrimMediaControls::resetZoomLevel(bool reset_camera) +{ + if(mCurrentZoom != ZOOM_NONE) + { + mCurrentZoom = ZOOM_NONE; + if(reset_camera) + { + updateZoom(); + } + } +} + +void LLPanelPrimMediaControls::updateZoom() +{ + F32 zoom_padding = 0.0f; + switch (mCurrentZoom) + { + case ZOOM_NONE: + { + gAgentCamera.setFocusOnAvatar(true, ANIMATE); + break; + } + case ZOOM_FAR: + { + zoom_padding = mZoomFarPadding; + break; + } + case ZOOM_MEDIUM: + { + zoom_padding = mZoomMediumPadding; + break; + } + case ZOOM_NEAR: + { + zoom_padding = mZoomNearPadding; + break; + } + default: + { + gAgentCamera.setFocusOnAvatar(true, ANIMATE); + break; + } + } + + if (zoom_padding > 0.0f) + { + // since we only zoom into medium for now, always set zoom_in constraint to true + mZoomedCameraPos = LLViewerMediaFocus::setCameraZoom(getTargetObject(), mTargetObjectNormal, zoom_padding, true); + } + + // Remember the object ID/face we zoomed into, so we can update the zoom icon appropriately + mZoomObjectID = mTargetObjectID; + mZoomObjectFace = mTargetObjectFace; +} + +void LLPanelPrimMediaControls::onScrollUp(void* user_data) +{ + LLPanelPrimMediaControls* this_panel = static_cast (user_data); + this_panel->focusOnTarget(); + + LLViewerMediaImpl* impl = this_panel->getTargetMediaImpl(); + + if(impl) + { + impl->scrollWheel(0, 0, 0, -1, MASK_NONE); + } +} +void LLPanelPrimMediaControls::onScrollUpHeld(void* user_data) +{ + LLPanelPrimMediaControls* this_panel = static_cast (user_data); + this_panel->mScrollState = SCROLL_UP; +} +void LLPanelPrimMediaControls::onScrollRight(void* user_data) +{ + LLPanelPrimMediaControls* this_panel = static_cast (user_data); + this_panel->focusOnTarget(); + + LLViewerMediaImpl* impl = this_panel->getTargetMediaImpl(); + + if(impl) + { + impl->scrollWheel(0, 0, -1, 0, MASK_NONE); +// impl->handleKeyHere(KEY_RIGHT, MASK_NONE); + } +} +void LLPanelPrimMediaControls::onScrollRightHeld(void* user_data) +{ + LLPanelPrimMediaControls* this_panel = static_cast (user_data); + this_panel->mScrollState = SCROLL_RIGHT; +} + +void LLPanelPrimMediaControls::onScrollLeft(void* user_data) +{ + LLPanelPrimMediaControls* this_panel = static_cast (user_data); + this_panel->focusOnTarget(); + + LLViewerMediaImpl* impl = this_panel->getTargetMediaImpl(); + + if(impl) + { + impl->scrollWheel(0, 0, 1, 0, MASK_NONE); +// impl->handleKeyHere(KEY_LEFT, MASK_NONE); + } +} +void LLPanelPrimMediaControls::onScrollLeftHeld(void* user_data) +{ + LLPanelPrimMediaControls* this_panel = static_cast (user_data); + this_panel->mScrollState = SCROLL_LEFT; +} + +void LLPanelPrimMediaControls::onScrollDown(void* user_data) +{ + LLPanelPrimMediaControls* this_panel = static_cast (user_data); + this_panel->focusOnTarget(); + + LLViewerMediaImpl* impl = this_panel->getTargetMediaImpl(); + + if(impl) + { + impl->scrollWheel(0, 0, 0, 1, MASK_NONE); + } +} +void LLPanelPrimMediaControls::onScrollDownHeld(void* user_data) +{ + LLPanelPrimMediaControls* this_panel = static_cast (user_data); + this_panel->mScrollState = SCROLL_DOWN; +} + +void LLPanelPrimMediaControls::onScrollStop(void* user_data) +{ + LLPanelPrimMediaControls* this_panel = static_cast (user_data); + this_panel->mScrollState = SCROLL_NONE; +} + +void LLPanelPrimMediaControls::onCommitURL() +{ + focusOnTarget(); + + std::string url = mMediaAddress->getValue().asString(); + if(getTargetMediaImpl() && !url.empty()) + { + getTargetMediaImpl()->navigateTo( url, "", true); + + // Make sure keyboard focus is set to the media focus object. + gFocusMgr.setKeyboardFocus(LLViewerMediaFocus::getInstance()); + + } + mPauseFadeout = false; + mFadeTimer.start(); +} + + +void LLPanelPrimMediaControls::onInputURL(LLFocusableElement* caller, void *userdata) +{ + + LLPanelPrimMediaControls* this_panel = static_cast (userdata); + this_panel->focusOnTarget(); + + this_panel->mPauseFadeout = true; + this_panel->mFadeTimer.stop(); + this_panel->mFadeTimer.reset(); + +} + +void LLPanelPrimMediaControls::setCurrentURL() +{ +#ifdef USE_COMBO_BOX_FOR_MEDIA_URL +// LLComboBox* media_address_combo = getChild("media_address_combo"); +// // redirects will navigate momentarily to about:blank, don't add to history +// if (media_address_combo && mCurrentURL != "about:blank") +// { +// media_address_combo->remove(mCurrentURL); +// media_address_combo->add(mCurrentURL); +// media_address_combo->selectByValue(mCurrentURL); +// } +#else // USE_COMBO_BOX_FOR_MEDIA_URL + if (mMediaAddress && mCurrentURL != "about:blank") + { + mMediaAddress->setValue(mCurrentURL); + } +#endif // USE_COMBO_BOX_FOR_MEDIA_URL +} + + +void LLPanelPrimMediaControls::onMediaPlaySliderCtrlMouseDown() +{ + mMediaPlaySliderCtrlMouseDownValue = mMediaPlaySliderCtrl->getValue().asReal(); + + mUpdateSlider = false; +} + +void LLPanelPrimMediaControls::onMediaPlaySliderCtrlMouseUp() +{ + F64 cur_value = mMediaPlaySliderCtrl->getValue().asReal(); + + if (mMediaPlaySliderCtrlMouseDownValue != cur_value) + { + focusOnTarget(); + + LLViewerMediaImpl* media_impl = getTargetMediaImpl(); + if (media_impl) + { + if (cur_value <= 0.0) + { + media_impl->stop(); + } + else + { + media_impl->seek(cur_value * mMovieDuration); + } + } + + mUpdateSlider = true; + } +} + +void LLPanelPrimMediaControls::onCommitVolumeUp() +{ + focusOnTarget(); + + LLViewerMediaImpl* media_impl = getTargetMediaImpl(); + if (media_impl) + { + F32 volume = media_impl->getVolume(); + + volume += 0.1f; + if(volume >= 1.0f) + { + volume = 1.0f; + } + + media_impl->setVolume(volume); + mMuteBtn->setToggleState(false); + } +} + +void LLPanelPrimMediaControls::onCommitVolumeDown() +{ + focusOnTarget(); + + LLViewerMediaImpl* media_impl = getTargetMediaImpl(); + if (media_impl) + { + F32 volume = media_impl->getVolume(); + + volume -= 0.1f; + if(volume <= 0.0f) + { + volume = 0.0f; + } + + media_impl->setVolume(volume); + mMuteBtn->setToggleState(false); + } +} + +void LLPanelPrimMediaControls::onCommitVolumeSlider() +{ + focusOnTarget(); + + LLViewerMediaImpl* media_impl = getTargetMediaImpl(); + if (media_impl) + { + media_impl->setVolume(mVolumeSliderCtrl->getValueF32()); + } +} + +void LLPanelPrimMediaControls::onToggleMute() +{ + focusOnTarget(); + + LLViewerMediaImpl* media_impl = getTargetMediaImpl(); + if (media_impl) + { + F32 volume = media_impl->getVolume(); + + if(volume > 0.0) + { + media_impl->setVolume(0.0); + } + else if (mVolumeSliderCtrl->getValueF32() == 0.0) + { + media_impl->setVolume(1.0); + mVolumeSliderCtrl->setValue(1.0); + } + else + { + media_impl->setVolume(mVolumeSliderCtrl->getValueF32()); + } + } +} + +void LLPanelPrimMediaControls::showVolumeSlider() +{ + mVolumeSliderVisible++; +} + +void LLPanelPrimMediaControls::hideVolumeSlider() +{ + mVolumeSliderVisible--; +} + +bool LLPanelPrimMediaControls::shouldVolumeSliderBeVisible() +{ + return mVolumeSliderVisible > 0; +} + +bool LLPanelPrimMediaControls::isZoomDistExceeding() +{ + return (gAgentCamera.getCameraPositionGlobal() - mZoomedCameraPos).normalize() >= EXCEEDING_ZOOM_DISTANCE; +} + +void LLPanelPrimMediaControls::clearFaceOnFade() +{ + if(mClearFaceOnFade) + { + // Hiding this object makes scroll events go missing after it fades out + // (see DEV-41755 for a full description of the train wreck). + // Only hide the controls when we're untargeting. + setVisible(false); + + mClearFaceOnFade = false; + mVolumeSliderVisible = 0; + mTargetImplID = LLUUID::null; + mTargetObjectID = LLUUID::null; + mTargetObjectFace = 0; + } +} + +void LLPanelPrimMediaControls::onMouselookModeIn() +{ + LLViewerMediaFocus::getInstance()->clearHover(); + mHideImmediately = true; +} + +void LLPanelPrimMediaControls::showNotification(LLNotificationPtr notify) +{ + delete mWindowShade; + LLWindowShade::Params params; + params.rect = mMediaRegion->getLocalRect(); + params.follows.flags = FOLLOWS_ALL; + + //HACK: don't hardcode this + if (notify->getIcon() == "Popup_Caution") + { + params.bg_image.name = "Yellow_Gradient"; + params.text_color = LLColor4::black; + } + else + { + //HACK: make this a property of the notification itself, "cancellable" + params.can_close = false; + params.text_color.control = "LabelTextColor"; + } + + mWindowShade = LLUICtrlFactory::create(params); + + mMediaRegion->addChild(mWindowShade); + mWindowShade->show(notify); +} + +void LLPanelPrimMediaControls::hideNotification() +{ + if (mWindowShade) + { + mWindowShade->hide(); + } +} diff --git a/indra/newview/llpanelprimmediacontrols.h b/indra/newview/llpanelprimmediacontrols.h index c5297b59d0..57c32e3a5f 100644 --- a/indra/newview/llpanelprimmediacontrols.h +++ b/indra/newview/llpanelprimmediacontrols.h @@ -1,234 +1,234 @@ -/** - * @file llpanelprimmediacontrols.h - * @brief Pop-up media controls panel - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_PANELPRIMMEDIACONTROLS_H -#define LL_PANELPRIMMEDIACONTROLS_H - -#include "llpanel.h" -#include "llviewermedia.h" -#include "llnotificationptr.h" -#include "llcoord.h" - -class LLButton; -class LLIconCtrl; -class LLLayoutStack; -class LLProgressBar; -class LLSliderCtrl; -class LLViewerMediaImpl; -class LLWindowShade; -class LLLineEditor; - -class LLPanelPrimMediaControls : public LLPanel -{ -public: - LLPanelPrimMediaControls(); - virtual ~LLPanelPrimMediaControls(); - bool postBuild() override; - void draw() override; - bool handleScrollWheel(S32 x, S32 y, S32 clicks) override; - bool handleScrollHWheel(S32 x, S32 y, S32 clicks) override; - - bool handleMouseDown(S32 x, S32 y, MASK mask) override; - bool handleMouseUp(S32 x, S32 y, MASK mask) override; - bool handleKeyHere(KEY key, MASK mask) override; - - void updateShape(); - bool isMouseOver(); - - void showNotification(LLNotificationPtr notify); - void hideNotification(); - - - enum EZoomLevel - { - ZOOM_NONE = 0, - ZOOM_FAR, - ZOOM_MEDIUM, - ZOOM_NEAR - }; - - EZoomLevel getZoomLevel() const { return mCurrentZoom; } - void nextZoomLevel(); - void resetZoomLevel(bool reset_camera = true); - void close(); - - LLHandle getHandle() const { return mPanelHandle; } - void setMediaFace(LLPointer objectp, S32 face, viewer_media_t media_impl, LLVector3 pick_normal = LLVector3::zero); - - - static const EZoomLevel kZoomLevels[]; - static const int kNumZoomLevels; - - enum EScrollDir - { - SCROLL_UP = 0, - SCROLL_DOWN, - SCROLL_LEFT, - SCROLL_RIGHT, - SCROLL_NONE - }; - -private: - void onClickClose(); - void onClickBack(); - void onClickForward(); - void onClickHome(); - void onClickOpen(); - void onClickReload(); - void onClickPlay(); - void onClickPause(); - void onClickStop(); - void onClickZoom(); - void onClickSkipBack(); - void onClickSkipForward(); - void onClickMediaStop(); - void onCommitURL(); - - void updateZoom(); - void setCurrentURL(); - - void onMediaPlaySliderCtrlMouseDown(); - void onMediaPlaySliderCtrlMouseUp(); - - void onCommitVolumeUp(); - void onCommitVolumeDown(); - void onCommitVolumeSlider(); - void onToggleMute(); - void showVolumeSlider(); - void hideVolumeSlider(); - bool shouldVolumeSliderBeVisible(); - - bool isZoomDistExceeding(); - - static void onScrollUp(void* user_data); - static void onScrollUpHeld(void* user_data); - static void onScrollLeft(void* user_data); - static void onScrollLeftHeld(void* user_data); - static void onScrollRight(void* user_data); - static void onScrollRightHeld(void* user_data); - static void onScrollDown(void* user_data); - static void onScrollDownHeld(void* user_data); - static void onScrollStop(void* user_data); - - static void onInputURL(LLFocusableElement* caller, void *userdata); - static bool hasControlsPermission(LLViewerObject *obj, const LLMediaEntry *media_entry); - - void focusOnTarget(); - - LLViewerMediaImpl* getTargetMediaImpl(); - LLViewerObject* getTargetObject(); - LLPluginClassMedia* getTargetMediaPlugin(); - -private: - - void clearFaceOnFade(); - - void onMouselookModeIn(); - - LLView *mMediaRegion; - LLUICtrl *mBackCtrl; - LLUICtrl *mFwdCtrl; - LLUICtrl *mReloadCtrl; - LLUICtrl *mPlayCtrl; - LLUICtrl *mPauseCtrl; - LLUICtrl *mStopCtrl; - LLUICtrl *mMediaStopCtrl; - LLUICtrl *mHomeCtrl; - LLUICtrl *mUnzoomCtrl; - LLUICtrl *mOpenCtrl; - LLUICtrl *mSkipBackCtrl; - LLUICtrl *mSkipFwdCtrl; - LLUICtrl *mZoomCtrl; - LLPanel *mMediaProgressPanel; - LLProgressBar *mMediaProgressBar; - LLUICtrl *mMediaAddressCtrl; - LLLineEditor *mMediaAddress; - LLUICtrl *mMediaPlaySliderPanel; - LLUICtrl *mMediaPlaySliderCtrl; - LLUICtrl *mVolumeCtrl; - LLButton *mMuteBtn; - LLSliderCtrl *mVolumeSliderCtrl; - LLIconCtrl *mWhitelistIcon; - LLIconCtrl *mSecureLockIcon; - LLLayoutStack *mMediaControlsStack; - LLUICtrl *mLeftBookend; - LLUICtrl *mRightBookend; - LLUIImage* mBackgroundImage; - LLUIImage* mVolumeSliderBackgroundImage; - LLWindowShade* mWindowShade; - F32 mSkipStep; - S32 mMinWidth; - S32 mMinHeight; - F32 mZoomNearPadding; - F32 mZoomMediumPadding; - F32 mZoomFarPadding; - S32 mTopWorldViewAvoidZone; - - LLVector3d mZoomedCameraPos; - - LLUICtrl *mMediaPanelScroll; - LLButton *mScrollUpCtrl; - LLButton *mScrollLeftCtrl; - LLButton *mScrollRightCtrl; - LLButton *mScrollDownCtrl; - - bool mPauseFadeout; - bool mUpdateSlider; - bool mClearFaceOnFade; - bool mHideImmediately; - bool mSecureURL; - - LLMatrix4 mLastCameraMat; - EZoomLevel mCurrentZoom; - EScrollDir mScrollState; - LLCoordWindow mLastCursorPos; - LLFrameTimer mInactivityTimer; - LLFrameTimer mFadeTimer; - F32 mInactiveTimeout; - F32 mControlFadeTime; - LLRootHandle mPanelHandle; - F32 mAlpha; - std::string mCurrentURL; - std::string mPreviousURL; - F64 mCurrentRate; - F64 mMovieDuration; - - LLUUID mTargetObjectID; - S32 mTargetObjectFace; - LLUUID mTargetImplID; - LLVector3 mTargetObjectNormal; - - LLUUID mZoomObjectID; - S32 mZoomObjectFace; - - S32 mVolumeSliderVisible; - - LLNotificationPtr mActiveNotification; - - F64 mMediaPlaySliderCtrlMouseDownValue; -}; - -#endif // LL_PANELPRIMMEDIACONTROLS_H +/** + * @file llpanelprimmediacontrols.h + * @brief Pop-up media controls panel + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_PANELPRIMMEDIACONTROLS_H +#define LL_PANELPRIMMEDIACONTROLS_H + +#include "llpanel.h" +#include "llviewermedia.h" +#include "llnotificationptr.h" +#include "llcoord.h" + +class LLButton; +class LLIconCtrl; +class LLLayoutStack; +class LLProgressBar; +class LLSliderCtrl; +class LLViewerMediaImpl; +class LLWindowShade; +class LLLineEditor; + +class LLPanelPrimMediaControls : public LLPanel +{ +public: + LLPanelPrimMediaControls(); + virtual ~LLPanelPrimMediaControls(); + bool postBuild() override; + void draw() override; + bool handleScrollWheel(S32 x, S32 y, S32 clicks) override; + bool handleScrollHWheel(S32 x, S32 y, S32 clicks) override; + + bool handleMouseDown(S32 x, S32 y, MASK mask) override; + bool handleMouseUp(S32 x, S32 y, MASK mask) override; + bool handleKeyHere(KEY key, MASK mask) override; + + void updateShape(); + bool isMouseOver(); + + void showNotification(LLNotificationPtr notify); + void hideNotification(); + + + enum EZoomLevel + { + ZOOM_NONE = 0, + ZOOM_FAR, + ZOOM_MEDIUM, + ZOOM_NEAR + }; + + EZoomLevel getZoomLevel() const { return mCurrentZoom; } + void nextZoomLevel(); + void resetZoomLevel(bool reset_camera = true); + void close(); + + LLHandle getHandle() const { return mPanelHandle; } + void setMediaFace(LLPointer objectp, S32 face, viewer_media_t media_impl, LLVector3 pick_normal = LLVector3::zero); + + + static const EZoomLevel kZoomLevels[]; + static const int kNumZoomLevels; + + enum EScrollDir + { + SCROLL_UP = 0, + SCROLL_DOWN, + SCROLL_LEFT, + SCROLL_RIGHT, + SCROLL_NONE + }; + +private: + void onClickClose(); + void onClickBack(); + void onClickForward(); + void onClickHome(); + void onClickOpen(); + void onClickReload(); + void onClickPlay(); + void onClickPause(); + void onClickStop(); + void onClickZoom(); + void onClickSkipBack(); + void onClickSkipForward(); + void onClickMediaStop(); + void onCommitURL(); + + void updateZoom(); + void setCurrentURL(); + + void onMediaPlaySliderCtrlMouseDown(); + void onMediaPlaySliderCtrlMouseUp(); + + void onCommitVolumeUp(); + void onCommitVolumeDown(); + void onCommitVolumeSlider(); + void onToggleMute(); + void showVolumeSlider(); + void hideVolumeSlider(); + bool shouldVolumeSliderBeVisible(); + + bool isZoomDistExceeding(); + + static void onScrollUp(void* user_data); + static void onScrollUpHeld(void* user_data); + static void onScrollLeft(void* user_data); + static void onScrollLeftHeld(void* user_data); + static void onScrollRight(void* user_data); + static void onScrollRightHeld(void* user_data); + static void onScrollDown(void* user_data); + static void onScrollDownHeld(void* user_data); + static void onScrollStop(void* user_data); + + static void onInputURL(LLFocusableElement* caller, void *userdata); + static bool hasControlsPermission(LLViewerObject *obj, const LLMediaEntry *media_entry); + + void focusOnTarget(); + + LLViewerMediaImpl* getTargetMediaImpl(); + LLViewerObject* getTargetObject(); + LLPluginClassMedia* getTargetMediaPlugin(); + +private: + + void clearFaceOnFade(); + + void onMouselookModeIn(); + + LLView *mMediaRegion; + LLUICtrl *mBackCtrl; + LLUICtrl *mFwdCtrl; + LLUICtrl *mReloadCtrl; + LLUICtrl *mPlayCtrl; + LLUICtrl *mPauseCtrl; + LLUICtrl *mStopCtrl; + LLUICtrl *mMediaStopCtrl; + LLUICtrl *mHomeCtrl; + LLUICtrl *mUnzoomCtrl; + LLUICtrl *mOpenCtrl; + LLUICtrl *mSkipBackCtrl; + LLUICtrl *mSkipFwdCtrl; + LLUICtrl *mZoomCtrl; + LLPanel *mMediaProgressPanel; + LLProgressBar *mMediaProgressBar; + LLUICtrl *mMediaAddressCtrl; + LLLineEditor *mMediaAddress; + LLUICtrl *mMediaPlaySliderPanel; + LLUICtrl *mMediaPlaySliderCtrl; + LLUICtrl *mVolumeCtrl; + LLButton *mMuteBtn; + LLSliderCtrl *mVolumeSliderCtrl; + LLIconCtrl *mWhitelistIcon; + LLIconCtrl *mSecureLockIcon; + LLLayoutStack *mMediaControlsStack; + LLUICtrl *mLeftBookend; + LLUICtrl *mRightBookend; + LLUIImage* mBackgroundImage; + LLUIImage* mVolumeSliderBackgroundImage; + LLWindowShade* mWindowShade; + F32 mSkipStep; + S32 mMinWidth; + S32 mMinHeight; + F32 mZoomNearPadding; + F32 mZoomMediumPadding; + F32 mZoomFarPadding; + S32 mTopWorldViewAvoidZone; + + LLVector3d mZoomedCameraPos; + + LLUICtrl *mMediaPanelScroll; + LLButton *mScrollUpCtrl; + LLButton *mScrollLeftCtrl; + LLButton *mScrollRightCtrl; + LLButton *mScrollDownCtrl; + + bool mPauseFadeout; + bool mUpdateSlider; + bool mClearFaceOnFade; + bool mHideImmediately; + bool mSecureURL; + + LLMatrix4 mLastCameraMat; + EZoomLevel mCurrentZoom; + EScrollDir mScrollState; + LLCoordWindow mLastCursorPos; + LLFrameTimer mInactivityTimer; + LLFrameTimer mFadeTimer; + F32 mInactiveTimeout; + F32 mControlFadeTime; + LLRootHandle mPanelHandle; + F32 mAlpha; + std::string mCurrentURL; + std::string mPreviousURL; + F64 mCurrentRate; + F64 mMovieDuration; + + LLUUID mTargetObjectID; + S32 mTargetObjectFace; + LLUUID mTargetImplID; + LLVector3 mTargetObjectNormal; + + LLUUID mZoomObjectID; + S32 mZoomObjectFace; + + S32 mVolumeSliderVisible; + + LLNotificationPtr mActiveNotification; + + F64 mMediaPlaySliderCtrlMouseDownValue; +}; + +#endif // LL_PANELPRIMMEDIACONTROLS_H diff --git a/indra/newview/llpanelprofile.cpp b/indra/newview/llpanelprofile.cpp index adceee80d3..579f0941c3 100644 --- a/indra/newview/llpanelprofile.cpp +++ b/indra/newview/llpanelprofile.cpp @@ -1,2431 +1,2431 @@ -/** -* @file llpanelprofile.cpp -* @brief Profile panel implementation -* -* $LicenseInfo:firstyear=2022&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2022, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#include "llviewerprecompiledheaders.h" -#include "llpanelprofile.h" - -// Common -#include "llavatarnamecache.h" -#include "llsdutil.h" -#include "llslurl.h" -#include "lldateutil.h" //ageFromDate - -// UI -#include "llavatariconctrl.h" -#include "llclipboard.h" -#include "llcheckboxctrl.h" -#include "llcombobox.h" -#include "lllineeditor.h" -#include "llloadingindicator.h" -#include "llmenubutton.h" -#include "lltabcontainer.h" -#include "lltextbox.h" -#include "lltexteditor.h" -#include "lltexturectrl.h" -#include "lltoggleablemenu.h" -#include "lltooldraganddrop.h" -#include "llgrouplist.h" -#include "llurlaction.h" - -// Image -#include "llimagej2c.h" - -// Newview -#include "llagent.h" //gAgent -#include "llagentpicksinfo.h" -#include "llavataractions.h" -#include "llavatarpropertiesprocessor.h" -#include "llcallingcard.h" -#include "llcommandhandler.h" -#include "llfloaterprofiletexture.h" -#include "llfloaterreg.h" -#include "llfloaterreporter.h" -#include "llfilepicker.h" -#include "llfirstuse.h" -#include "llgroupactions.h" -#include "lllogchat.h" -#include "llmutelist.h" -#include "llnotificationsutil.h" -#include "llpanelblockedlist.h" -#include "llpanelprofileclassifieds.h" -#include "llpanelprofilepicks.h" -#include "lltrans.h" -#include "llviewercontrol.h" -#include "llviewermenu.h" //is_agent_mappable -#include "llviewermenufile.h" -#include "llviewertexturelist.h" -#include "llvoiceclient.h" -#include "llweb.h" - - -static LLPanelInjector t_panel_profile_secondlife("panel_profile_secondlife"); -static LLPanelInjector t_panel_web("panel_profile_web"); -static LLPanelInjector t_panel_picks("panel_profile_picks"); -static LLPanelInjector t_panel_firstlife("panel_profile_firstlife"); -static LLPanelInjector t_panel_notes("panel_profile_notes"); -static LLPanelInjector t_panel_profile("panel_profile"); - -static const std::string PANEL_SECONDLIFE = "panel_profile_secondlife"; -static const std::string PANEL_WEB = "panel_profile_web"; -static const std::string PANEL_PICKS = "panel_profile_picks"; -static const std::string PANEL_CLASSIFIEDS = "panel_profile_classifieds"; -static const std::string PANEL_FIRSTLIFE = "panel_profile_firstlife"; -static const std::string PANEL_NOTES = "panel_profile_notes"; -static const std::string PANEL_PROFILE_VIEW = "panel_profile_view"; - -static const std::string PROFILE_PROPERTIES_CAP = "AgentProfile"; -static const std::string PROFILE_IMAGE_UPLOAD_CAP = "UploadAgentProfileImage"; - - -////////////////////////////////////////////////////////////////////////// - -LLUUID post_profile_image(std::string cap_url, const LLSD &first_data, std::string path_to_image, LLHandle *handle) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("post_profile_image_coro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpHeaders::ptr_t httpHeaders; - - LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); - httpOpts->setFollowRedirects(true); - - LLSD result = httpAdapter->postAndSuspend(httpRequest, cap_url, first_data, httpOpts, httpHeaders); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - // todo: notification? - LL_WARNS("AvatarProperties") << "Failed to get uploader cap " << status.toString() << LL_ENDL; - return LLUUID::null; - } - if (!result.has("uploader")) - { - // todo: notification? - LL_WARNS("AvatarProperties") << "Failed to get uploader cap, response contains no data." << LL_ENDL; - return LLUUID::null; - } - std::string uploader_cap = result["uploader"].asString(); - if (uploader_cap.empty()) - { - LL_WARNS("AvatarProperties") << "Failed to get uploader cap, cap invalid." << LL_ENDL; - return LLUUID::null; - } - - // Upload the image - LLCore::HttpRequest::ptr_t uploaderhttpRequest(new LLCore::HttpRequest); - LLCore::HttpHeaders::ptr_t uploaderhttpHeaders(new LLCore::HttpHeaders); - LLCore::HttpOptions::ptr_t uploaderhttpOpts(new LLCore::HttpOptions); - S64 length; - - { - llifstream instream(path_to_image.c_str(), std::iostream::binary | std::iostream::ate); - if (!instream.is_open()) - { - LL_WARNS("AvatarProperties") << "Failed to open file " << path_to_image << LL_ENDL; - return LLUUID::null; - } - length = instream.tellg(); - } - - uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/jp2"); // optional - uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_LENGTH, llformat("%d", length)); // required! - uploaderhttpOpts->setFollowRedirects(true); - - result = httpAdapter->postFileAndSuspend(uploaderhttpRequest, uploader_cap, path_to_image, uploaderhttpOpts, uploaderhttpHeaders); - - httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - LL_DEBUGS("AvatarProperties") << result << LL_ENDL; - - if (!status) - { - LL_WARNS("AvatarProperties") << "Failed to upload image " << status.toString() << LL_ENDL; - return LLUUID::null; - } - - if (result["state"].asString() != "complete") - { - if (result.has("message")) - { - LL_WARNS("AvatarProperties") << "Failed to upload image, state " << result["state"] << " message: " << result["message"] << LL_ENDL; - } - else - { - LL_WARNS("AvatarProperties") << "Failed to upload image " << result << LL_ENDL; - } - return LLUUID::null; - } - - return result["new_asset"].asUUID(); -} - -enum EProfileImageType -{ - PROFILE_IMAGE_SL, - PROFILE_IMAGE_FL, -}; - -void post_profile_image_coro(std::string cap_url, EProfileImageType type, std::string path_to_image, LLHandle *handle) -{ - LLSD data; - switch (type) - { - case PROFILE_IMAGE_SL: - data["profile-image-asset"] = "sl_image_id"; - break; - case PROFILE_IMAGE_FL: - data["profile-image-asset"] = "fl_image_id"; - break; - } - - LLUUID result = post_profile_image(cap_url, data, path_to_image, handle); - - // reset loading indicator - if (!handle->isDead()) - { - switch (type) - { - case PROFILE_IMAGE_SL: - { - LLPanelProfileSecondLife* panel = static_cast(handle->get()); - if (result.notNull()) - { - panel->setProfileImageUploaded(result); - } - else - { - // failure, just stop progress indicator - panel->setProfileImageUploading(false); - } - break; - } - case PROFILE_IMAGE_FL: - { - LLPanelProfileFirstLife* panel = static_cast(handle->get()); - if (result.notNull()) - { - panel->setProfileImageUploaded(result); - } - else - { - // failure, just stop progress indicator - panel->setProfileImageUploading(false); - } - break; - } - } - } - - if (type == PROFILE_IMAGE_SL && result.notNull()) - { - LLAvatarIconIDCache::getInstance()->add(gAgentID, result); - // Should trigger callbacks in icon controls - LLAvatarPropertiesProcessor::getInstance()->sendAvatarPropertiesRequest(gAgentID); - } - - // Cleanup - LLFile::remove(path_to_image); - delete handle; -} - -////////////////////////////////////////////////////////////////////////// -// LLProfileHandler - -class LLProfileHandler : public LLCommandHandler -{ -public: - // requires trusted browser to trigger - LLProfileHandler() : LLCommandHandler("profile", UNTRUSTED_THROTTLE) { } - - bool handle(const LLSD& params, - const LLSD& query_map, - const std::string& grid, - LLMediaCtrl* web) - { - if (params.size() < 1) return false; - std::string agent_name = params[0]; - LL_INFOS() << "Profile, agent_name " << agent_name << LL_ENDL; - std::string url = getProfileURL(agent_name); - LLWeb::loadURLInternal(url); - - return true; - } -}; -LLProfileHandler gProfileHandler; - - -////////////////////////////////////////////////////////////////////////// -// LLAgentHandler - -class LLAgentHandler : public LLCommandHandler -{ -public: - // requires trusted browser to trigger - LLAgentHandler() : LLCommandHandler("agent", UNTRUSTED_THROTTLE) { } - - virtual bool canHandleUntrusted( - const LLSD& params, - const LLSD& query_map, - LLMediaCtrl* web, - const std::string& nav_type) - { - if (params.size() < 2) - { - return true; // don't block, will fail later - } - - if (nav_type == NAV_TYPE_CLICKED - || nav_type == NAV_TYPE_EXTERNAL) - { - return true; - } - - const std::string verb = params[1].asString(); - if (verb == "about" || verb == "inspect" || verb == "reportAbuse") - { - return true; - } - return false; - } - - bool handle(const LLSD& params, - const LLSD& query_map, - const std::string& grid, - LLMediaCtrl* web) - { - if (params.size() < 2) return false; - LLUUID avatar_id; - if (!avatar_id.set(params[0], false)) - { - return false; - } - - const std::string verb = params[1].asString(); - if (verb == "about") - { - LLAvatarActions::showProfile(avatar_id); - return true; - } - - if (verb == "inspect") - { - LLFloaterReg::showInstance("inspect_avatar", LLSD().with("avatar_id", avatar_id)); - return true; - } - - if (verb == "im") - { - LLAvatarActions::startIM(avatar_id); - return true; - } - - if (verb == "pay") - { - if (!LLUI::getInstance()->mSettingGroups["config"]->getBOOL("EnableAvatarPay")) - { - LLNotificationsUtil::add("NoAvatarPay", LLSD(), LLSD(), std::string("SwitchToStandardSkinAndQuit")); - return true; - } - - LLAvatarActions::pay(avatar_id); - return true; - } - - if (verb == "offerteleport") - { - LLAvatarActions::offerTeleport(avatar_id); - return true; - } - - if (verb == "requestfriend") - { - LLAvatarActions::requestFriendshipDialog(avatar_id); - return true; - } - - if (verb == "removefriend") - { - LLAvatarActions::removeFriendDialog(avatar_id); - return true; - } - - if (verb == "mute") - { - if (! LLAvatarActions::isBlocked(avatar_id)) - { - LLAvatarActions::toggleBlock(avatar_id); - } - return true; - } - - if (verb == "unmute") - { - if (LLAvatarActions::isBlocked(avatar_id)) - { - LLAvatarActions::toggleBlock(avatar_id); - } - return true; - } - - if (verb == "block") - { - if (params.size() > 2) - { - const std::string object_name = LLURI::unescape(params[2].asString()); - LLMute mute(avatar_id, object_name, LLMute::OBJECT); - LLMuteList::getInstance()->add(mute); - LLPanelBlockedList::showPanelAndSelect(mute.mID); - } - return true; - } - - if (verb == "unblock") - { - if (params.size() > 2) - { - const std::string object_name = params[2].asString(); - LLMute mute(avatar_id, object_name, LLMute::OBJECT); - LLMuteList::getInstance()->remove(mute); - } - return true; - } - - // reportAbuse is here due to convoluted avatar handling - // in LLScrollListCtrl and LLTextBase - if (verb == "reportAbuse" && web == NULL) - { - LLAvatarName av_name; - if (LLAvatarNameCache::get(avatar_id, &av_name)) - { - LLFloaterReporter::showFromAvatar(avatar_id, av_name.getCompleteName()); - } - else - { - LLFloaterReporter::showFromAvatar(avatar_id, "not avaliable"); - } - return true; - } - return false; - } -}; -LLAgentHandler gAgentHandler; - - -///---------------------------------------------------------------------------- -/// LLFloaterProfilePermissions -///---------------------------------------------------------------------------- - -class LLFloaterProfilePermissions - : public LLFloater - , public LLFriendObserver -{ -public: - LLFloaterProfilePermissions(LLView * owner, const LLUUID &avatar_id); - ~LLFloaterProfilePermissions(); - bool postBuild() override; - void onOpen(const LLSD& key) override; - void draw() override; - void changed(U32 mask) override; // LLFriendObserver - - void onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name); - bool hasUnsavedChanges() { return mHasUnsavedPermChanges; } - - void onApplyRights(); - -private: - void fillRightsData(); - void rightsConfirmationCallback(const LLSD& notification, const LLSD& response); - void confirmModifyRights(bool grant); - void onCommitSeeOnlineRights(); - void onCommitEditRights(); - void onCancel(); - - LLTextBase* mDescription; - LLCheckBoxCtrl* mOnlineStatus; - LLCheckBoxCtrl* mMapRights; - LLCheckBoxCtrl* mEditObjectRights; - LLButton* mOkBtn; - LLButton* mCancelBtn; - - LLUUID mAvatarID; - F32 mContextConeOpacity; - bool mHasUnsavedPermChanges; - LLHandle mOwnerHandle; - - boost::signals2::connection mAvatarNameCacheConnection; -}; - -LLFloaterProfilePermissions::LLFloaterProfilePermissions(LLView * owner, const LLUUID &avatar_id) - : LLFloater(LLSD()) - , mAvatarID(avatar_id) - , mContextConeOpacity(0.0f) - , mHasUnsavedPermChanges(false) - , mOwnerHandle(owner->getHandle()) -{ - buildFromFile("floater_profile_permissions.xml"); -} - -LLFloaterProfilePermissions::~LLFloaterProfilePermissions() -{ - mAvatarNameCacheConnection.disconnect(); - if (mAvatarID.notNull()) - { - LLAvatarTracker::instance().removeParticularFriendObserver(mAvatarID, this); - } -} - -bool LLFloaterProfilePermissions::postBuild() -{ - mDescription = getChild("perm_description"); - mOnlineStatus = getChild("online_check"); - mMapRights = getChild("map_check"); - mEditObjectRights = getChild("objects_check"); - mOkBtn = getChild("perms_btn_ok"); - mCancelBtn = getChild("perms_btn_cancel"); - - mOnlineStatus->setCommitCallback([this](LLUICtrl*, void*) { onCommitSeeOnlineRights(); }, nullptr); - mMapRights->setCommitCallback([this](LLUICtrl*, void*) { mHasUnsavedPermChanges = true; }, nullptr); - mEditObjectRights->setCommitCallback([this](LLUICtrl*, void*) { onCommitEditRights(); }, nullptr); - mOkBtn->setCommitCallback([this](LLUICtrl*, void*) { onApplyRights(); }, nullptr); - mCancelBtn->setCommitCallback([this](LLUICtrl*, void*) { onCancel(); }, nullptr); - - return true; -} - -void LLFloaterProfilePermissions::onOpen(const LLSD& key) -{ - if (LLAvatarActions::isFriend(mAvatarID)) - { - LLAvatarTracker::instance().addParticularFriendObserver(mAvatarID, this); - fillRightsData(); - } - - mCancelBtn->setFocus(true); - - mAvatarNameCacheConnection = LLAvatarNameCache::get(mAvatarID, boost::bind(&LLFloaterProfilePermissions::onAvatarNameCache, this, _1, _2)); -} - -void LLFloaterProfilePermissions::draw() -{ - // drawFrustum - LLView *owner = mOwnerHandle.get(); - static LLCachedControl max_opacity(gSavedSettings, "PickerContextOpacity", 0.4f); - drawConeToOwner(mContextConeOpacity, max_opacity, owner); - LLFloater::draw(); -} - -void LLFloaterProfilePermissions::changed(U32 mask) -{ - if (mask != LLFriendObserver::ONLINE) - { - fillRightsData(); - } -} - -void LLFloaterProfilePermissions::onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name) -{ - mAvatarNameCacheConnection.disconnect(); - - LLStringUtil::format_map_t args; - args["[AGENT_NAME]"] = av_name.getDisplayName(); - std::string descritpion = getString("description_string", args); - mDescription->setValue(descritpion); -} - -void LLFloaterProfilePermissions::fillRightsData() -{ - const LLRelationship* relation = LLAvatarTracker::instance().getBuddyInfo(mAvatarID); - // If true - we are viewing friend's profile, enable check boxes and set values. - if (relation) - { - S32 rights = relation->getRightsGrantedTo(); - - bool see_online = LLRelationship::GRANT_ONLINE_STATUS & rights; - mOnlineStatus->setValue(see_online); - mMapRights->setEnabled(see_online); - mMapRights->setValue(LLRelationship::GRANT_MAP_LOCATION & rights); - mEditObjectRights->setValue(LLRelationship::GRANT_MODIFY_OBJECTS & rights); - } - else - { - closeFloater(); - LL_INFOS("ProfilePermissions") << "Floater closing since agent is no longer a friend" << LL_ENDL; - } -} - -void LLFloaterProfilePermissions::rightsConfirmationCallback(const LLSD& notification, - const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option != 0) // canceled - { - mEditObjectRights->setValue(!mEditObjectRights->getValue().asBoolean()); - } - else - { - mHasUnsavedPermChanges = true; - } -} - -void LLFloaterProfilePermissions::confirmModifyRights(bool grant) -{ - LLSD args; - args["NAME"] = LLSLURL("agent", mAvatarID, "completename").getSLURLString(); - LLNotificationsUtil::add(grant ? "GrantModifyRights" : "RevokeModifyRights", args, LLSD(), - boost::bind(&LLFloaterProfilePermissions::rightsConfirmationCallback, this, _1, _2)); -} - -void LLFloaterProfilePermissions::onCommitSeeOnlineRights() -{ - bool see_online = mOnlineStatus->getValue().asBoolean(); - mMapRights->setEnabled(see_online); - if (see_online) - { - const LLRelationship* relation = LLAvatarTracker::instance().getBuddyInfo(mAvatarID); - if (relation) - { - S32 rights = relation->getRightsGrantedTo(); - mMapRights->setValue(LLRelationship::GRANT_MAP_LOCATION & rights); - } - else - { - closeFloater(); - LL_INFOS("ProfilePermissions") << "Floater closing since agent is no longer a friend" << LL_ENDL; - } - } - else - { - mMapRights->setValue(false); - } - mHasUnsavedPermChanges = true; -} - -void LLFloaterProfilePermissions::onCommitEditRights() -{ - const LLRelationship* buddy_relationship = LLAvatarTracker::instance().getBuddyInfo(mAvatarID); - - if (!buddy_relationship) - { - LL_WARNS("ProfilePermissions") << "Trying to modify rights for non-friend avatar. Closing floater." << LL_ENDL; - closeFloater(); - return; - } - - bool allow_modify_objects = mEditObjectRights->getValue().asBoolean(); - - // if modify objects checkbox clicked - if (buddy_relationship->isRightGrantedTo( - LLRelationship::GRANT_MODIFY_OBJECTS) != allow_modify_objects) - { - confirmModifyRights(allow_modify_objects); - } -} - -void LLFloaterProfilePermissions::onApplyRights() -{ - const LLRelationship* buddy_relationship = LLAvatarTracker::instance().getBuddyInfo(mAvatarID); - - if (!buddy_relationship) - { - LL_WARNS("ProfilePermissions") << "Trying to modify rights for non-friend avatar. Skipped." << LL_ENDL; - return; - } - - S32 rights = 0; - - if (mOnlineStatus->getValue().asBoolean()) - { - rights |= LLRelationship::GRANT_ONLINE_STATUS; - } - if (mMapRights->getValue().asBoolean()) - { - rights |= LLRelationship::GRANT_MAP_LOCATION; - } - if (mEditObjectRights->getValue().asBoolean()) - { - rights |= LLRelationship::GRANT_MODIFY_OBJECTS; - } - - LLAvatarPropertiesProcessor::getInstance()->sendFriendRights(mAvatarID, rights); - - closeFloater(); -} - -void LLFloaterProfilePermissions::onCancel() -{ - closeFloater(); -} - -////////////////////////////////////////////////////////////////////////// -// LLPanelProfileSecondLife - -LLPanelProfileSecondLife::LLPanelProfileSecondLife() - : LLPanelProfilePropertiesProcessorTab() - , mAvatarNameCacheConnection() - , mHasUnsavedDescriptionChanges(false) - , mWaitingForImageUpload(false) - , mAllowPublish(false) - , mHideAge(false) -{ -} - -LLPanelProfileSecondLife::~LLPanelProfileSecondLife() -{ - if (getAvatarId().notNull()) - { - LLAvatarTracker::instance().removeParticularFriendObserver(getAvatarId(), this); - } - - if (LLVoiceClient::instanceExists()) - { - LLVoiceClient::getInstance()->removeObserver((LLVoiceClientStatusObserver*)this); - } - - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } -} - -bool LLPanelProfileSecondLife::postBuild() -{ - mGroupList = getChild("group_list"); - mShowInSearchCombo = getChild("show_in_search"); - mHideAgeCombo = getChild("hide_age"); - mSecondLifePic = getChild("2nd_life_pic"); - mSecondLifePicLayout = getChild("image_panel"); - mDescriptionEdit = getChild("sl_description_edit"); - mAgentActionMenuButton = getChild("agent_actions_menu"); - mSaveDescriptionChanges = getChild("save_description_changes"); - mDiscardDescriptionChanges = getChild("discard_description_changes"); - mCanSeeOnlineIcon = getChild("can_see_online"); - mCantSeeOnlineIcon = getChild("cant_see_online"); - mCanSeeOnMapIcon = getChild("can_see_on_map"); - mCantSeeOnMapIcon = getChild("cant_see_on_map"); - mCanEditObjectsIcon = getChild("can_edit_objects"); - mCantEditObjectsIcon = getChild("cant_edit_objects"); - - mShowInSearchCombo->setCommitCallback([this](LLUICtrl*, void*) { onShowInSearchCallback(); }, nullptr); - mHideAgeCombo->setCommitCallback([this](LLUICtrl*, void*) { onHideAgeCallback(); }, nullptr); - mGroupList->setDoubleClickCallback([this](LLUICtrl*, S32 x, S32 y, MASK mask) { LLPanelProfileSecondLife::openGroupProfile(); }); - mGroupList->setReturnCallback([this](LLUICtrl*, const LLSD&) { LLPanelProfileSecondLife::openGroupProfile(); }); - mSaveDescriptionChanges->setCommitCallback([this](LLUICtrl*, void*) { onSaveDescriptionChanges(); }, nullptr); - mDiscardDescriptionChanges->setCommitCallback([this](LLUICtrl*, void*) { onDiscardDescriptionChanges(); }, nullptr); - mDescriptionEdit->setKeystrokeCallback([this](LLTextEditor* caller) { onSetDescriptionDirty(); }); - - mCanSeeOnlineIcon->setMouseUpCallback([this](LLUICtrl*, S32 x, S32 y, MASK mask) { onShowAgentPermissionsDialog(); }); - mCantSeeOnlineIcon->setMouseUpCallback([this](LLUICtrl*, S32 x, S32 y, MASK mask) { onShowAgentPermissionsDialog(); }); - mCanSeeOnMapIcon->setMouseUpCallback([this](LLUICtrl*, S32 x, S32 y, MASK mask) { onShowAgentPermissionsDialog(); }); - mCantSeeOnMapIcon->setMouseUpCallback([this](LLUICtrl*, S32 x, S32 y, MASK mask) { onShowAgentPermissionsDialog(); }); - mCanEditObjectsIcon->setMouseUpCallback([this](LLUICtrl*, S32 x, S32 y, MASK mask) { onShowAgentPermissionsDialog(); }); - mCantEditObjectsIcon->setMouseUpCallback([this](LLUICtrl*, S32 x, S32 y, MASK mask) { onShowAgentPermissionsDialog(); }); - mSecondLifePic->setMouseUpCallback([this](LLUICtrl*, S32 x, S32 y, MASK mask) { onShowAgentProfileTexture(); }); - - return true; -} - -void LLPanelProfileSecondLife::onOpen(const LLSD& key) -{ - LLPanelProfileTab::onOpen(key); - - resetData(); - - LLUUID avatar_id = getAvatarId(); - - bool own_profile = getSelfProfile(); - - mGroupList->setShowNone(!own_profile); - - childSetVisible("notes_panel", !own_profile); - childSetVisible("settings_panel", own_profile); - childSetVisible("about_buttons_panel", own_profile); - - if (own_profile) - { - // Group list control cannot toggle ForAgent loading - // Less than ideal, but viewing own profile via search is edge case - mGroupList->enableForAgent(false); - } - - // Init menu, menu needs to be created in scope of a registar to work correctly. - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar commit; - commit.add("Profile.Commit", [this](LLUICtrl*, const LLSD& userdata) { onCommitMenu(userdata); }); - - LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable; - enable.add("Profile.EnableItem", [this](LLUICtrl*, const LLSD& userdata) { return onEnableMenu(userdata); }); - enable.add("Profile.CheckItem", [this](LLUICtrl*, const LLSD& userdata) { return onCheckMenu(userdata); }); - - if (own_profile) - { - mAgentActionMenuButton->setMenu("menu_profile_self.xml", LLMenuButton::MP_BOTTOM_RIGHT); - } - else - { - // Todo: use PeopleContextMenu instead? - mAgentActionMenuButton->setMenu("menu_profile_other.xml", LLMenuButton::MP_BOTTOM_RIGHT); - } - - mDescriptionEdit->setParseHTML(!own_profile); - - if (!own_profile) - { - mVoiceStatus = LLAvatarActions::canCall() && (LLAvatarActions::isFriend(avatar_id) ? LLAvatarTracker::instance().isBuddyOnline(avatar_id) : true); - updateOnlineStatus(); - fillRightsData(); - } - - mAvatarNameCacheConnection = LLAvatarNameCache::get(getAvatarId(), boost::bind(&LLPanelProfileSecondLife::onAvatarNameCache, this, _1, _2)); -} - - -bool LLPanelProfileSecondLife::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - // Try children first - if (LLPanelProfileTab::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg) - && *accept != ACCEPT_NO) - { - return true; - } - - // No point sharing with own profile - if (getSelfProfile()) - { - return false; - } - - // Exclude fields that look like they are editable. - S32 child_x = 0; - S32 child_y = 0; - if (localPointToOtherView(x, y, &child_x, &child_y, mDescriptionEdit) - && mDescriptionEdit->pointInView(child_x, child_y)) - { - return false; - } - - if (localPointToOtherView(x, y, &child_x, &child_y, mGroupList) - && mGroupList->pointInView(child_x, child_y)) - { - return false; - } - - // Share - LLToolDragAndDrop::handleGiveDragAndDrop(getAvatarId(), - LLUUID::null, - drop, - cargo_type, - cargo_data, - accept); - return true; -} - -void LLPanelProfileSecondLife::refreshName() -{ - if (!mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection = LLAvatarNameCache::get(getAvatarId(), boost::bind(&LLPanelProfileSecondLife::onAvatarNameCache, this, _1, _2)); - } -} - -void LLPanelProfileSecondLife::resetData() -{ - resetLoading(); - - // Set default image and 1:1 dimensions for it - mSecondLifePic->setValue("Generic_Person_Large"); - - LLRect imageRect = mSecondLifePicLayout->getRect(); - mSecondLifePicLayout->reshape(imageRect.getWidth(), imageRect.getWidth()); - - setDescriptionText(LLStringUtil::null); - mGroups.clear(); - mGroupList->setGroups(mGroups); - - bool own_profile = getSelfProfile(); - mCanSeeOnlineIcon->setVisible(false); - mCantSeeOnlineIcon->setVisible(!own_profile); - mCanSeeOnMapIcon->setVisible(false); - mCantSeeOnMapIcon->setVisible(!own_profile); - mCanEditObjectsIcon->setVisible(false); - mCantEditObjectsIcon->setVisible(!own_profile); - - mCanSeeOnlineIcon->setEnabled(false); - mCantSeeOnlineIcon->setEnabled(false); - mCanSeeOnMapIcon->setEnabled(false); - mCantSeeOnMapIcon->setEnabled(false); - mCanEditObjectsIcon->setEnabled(false); - mCantEditObjectsIcon->setEnabled(false); - - childSetVisible("partner_layout", false); - childSetVisible("badge_layout", false); - childSetVisible("partner_spacer_layout", true); -} - -void LLPanelProfileSecondLife::processProperties(void* data, EAvatarProcessorType type) -{ - if (APT_PROPERTIES == type) - { - LLAvatarData* avatar_data = static_cast(data); - if (avatar_data && getAvatarId() == avatar_data->avatar_id) - { - processProfileProperties(avatar_data); - } - } -} - -void LLPanelProfileSecondLife::processProfileProperties(const LLAvatarData* avatar_data) -{ - const LLRelationship* relationship = LLAvatarTracker::instance().getBuddyInfo(getAvatarId()); - if ((relationship != NULL || gAgent.isGodlike()) && !getSelfProfile()) - { - // Relies onto friend observer to get information about online status updates. - // Once SL-17506 gets implemented, condition might need to become: - // (gAgent.isGodlike() || isRightGrantedFrom || flags & AVATAR_ONLINE) - processOnlineStatus(relationship != NULL, - gAgent.isGodlike() || relationship->isRightGrantedFrom(LLRelationship::GRANT_ONLINE_STATUS), - (avatar_data->flags & AVATAR_ONLINE)); - } - - fillCommonData(avatar_data); - - fillPartnerData(avatar_data); - - fillAccountStatus(avatar_data); - - LLAvatarData::group_list_t::const_iterator it = avatar_data->group_list.begin(); - const LLAvatarData::group_list_t::const_iterator it_end = avatar_data->group_list.end(); - - for (; it_end != it; ++it) - { - LLAvatarData::LLGroupData group_data = *it; - mGroups[group_data.group_name] = group_data.group_id; - } - - mGroupList->setGroups(mGroups); - - setLoaded(); -} - -void LLPanelProfileSecondLife::openGroupProfile() -{ - LLUUID group_id = mGroupList->getSelectedUUID(); - LLGroupActions::show(group_id); -} - -void LLPanelProfileSecondLife::onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name) -{ - mAvatarNameCacheConnection.disconnect(); - getChild("display_name")->setValue(av_name.getDisplayName()); - getChild("user_name")->setValue(av_name.getAccountName()); -} - -void LLPanelProfileSecondLife::setProfileImageUploading(bool loading) -{ - LLLoadingIndicator* indicator = getChild("image_upload_indicator"); - indicator->setVisible(loading); - if (loading) - { - indicator->start(); - } - else - { - indicator->stop(); - } - mWaitingForImageUpload = loading; -} - -void LLPanelProfileSecondLife::setProfileImageUploaded(const LLUUID &image_asset_id) -{ - mSecondLifePic->setValue(image_asset_id); - - LLFloater *floater = mFloaterProfileTextureHandle.get(); - if (floater) - { - LLFloaterProfileTexture * texture_view = dynamic_cast(floater); - texture_view->loadAsset(mSecondLifePic->getImageAssetId()); - } - - setProfileImageUploading(false); -} - -bool LLPanelProfileSecondLife::hasUnsavedChanges() -{ - LLFloater *floater = mFloaterPermissionsHandle.get(); - if (floater) - { - LLFloaterProfilePermissions* perm = dynamic_cast(floater); - if (perm && perm->hasUnsavedChanges()) - { - return true; - } - } - // if floater - return mHasUnsavedDescriptionChanges; -} - -void LLPanelProfileSecondLife::commitUnsavedChanges() -{ - LLFloater *floater = mFloaterPermissionsHandle.get(); - if (floater) - { - LLFloaterProfilePermissions* perm = dynamic_cast(floater); - if (perm && perm->hasUnsavedChanges()) - { - perm->onApplyRights(); - } - } - if (mHasUnsavedDescriptionChanges) - { - onSaveDescriptionChanges(); - } -} - -void LLPanelProfileSecondLife::fillCommonData(const LLAvatarData* avatar_data) -{ - // Refresh avatar id in cache with new info to prevent re-requests - // and to make sure icons in text will be up to date - LLAvatarIconIDCache::getInstance()->add(avatar_data->avatar_id, avatar_data->image_id); - - fillAgeData(avatar_data); - - setDescriptionText(avatar_data->about_text); - - mSecondLifePic->setValue(avatar_data->image_id); - - if (getSelfProfile()) - { - mAllowPublish = avatar_data->flags & AVATAR_ALLOW_PUBLISH; - mShowInSearchCombo->setValue(mAllowPublish); - } -} - -void LLPanelProfileSecondLife::fillPartnerData(const LLAvatarData* avatar_data) -{ - LLTextBox* partner_text_ctrl = getChild("partner_link"); - if (avatar_data->partner_id.notNull()) - { - childSetVisible("partner_layout", true); - LLStringUtil::format_map_t args; - args["[LINK]"] = LLSLURL("agent", avatar_data->partner_id, "inspect").getSLURLString(); - std::string partner_text = getString("partner_text", args); - partner_text_ctrl->setText(partner_text); - } - else - { - childSetVisible("partner_layout", false); - } -} - -void LLPanelProfileSecondLife::fillAccountStatus(const LLAvatarData* avatar_data) -{ - LLStringUtil::format_map_t args; - args["[ACCTTYPE]"] = LLAvatarPropertiesProcessor::accountType(avatar_data); - args["[PAYMENTINFO]"] = LLAvatarPropertiesProcessor::paymentInfo(avatar_data); - - std::string caption_text = getString("CaptionTextAcctInfo", args); - getChild("account_info")->setValue(caption_text); - - const S32 LINDEN_EMPLOYEE_INDEX = 3; - LLDate sl_release; - sl_release.fromYMDHMS(2003, 6, 23, 0, 0, 0); - std::string customer_lower = avatar_data->customer_type; - LLStringUtil::toLower(customer_lower); - if (avatar_data->caption_index == LINDEN_EMPLOYEE_INDEX) - { - getChild("badge_icon")->setValue("Profile_Badge_Linden"); - getChild("badge_text")->setValue(getString("BadgeLinden")); - childSetVisible("badge_layout", true); - childSetVisible("partner_spacer_layout", false); - } - else if (avatar_data->born_on < sl_release) - { - getChild("badge_icon")->setValue("Profile_Badge_Beta"); - getChild("badge_text")->setValue(getString("BadgeBeta")); - childSetVisible("badge_layout", true); - childSetVisible("partner_spacer_layout", false); - } - else if (customer_lower == "beta_lifetime") - { - getChild("badge_icon")->setValue("Profile_Badge_Beta_Lifetime"); - getChild("badge_text")->setValue(getString("BadgeBetaLifetime")); - childSetVisible("badge_layout", true); - childSetVisible("partner_spacer_layout", false); - } - else if (customer_lower == "lifetime") - { - getChild("badge_icon")->setValue("Profile_Badge_Lifetime"); - getChild("badge_text")->setValue(getString("BadgeLifetime")); - childSetVisible("badge_layout", true); - childSetVisible("partner_spacer_layout", false); - } - else if (customer_lower == "secondlifetime_premium") - { - getChild("badge_icon")->setValue("Profile_Badge_Premium_Lifetime"); - getChild("badge_text")->setValue(getString("BadgePremiumLifetime")); - childSetVisible("badge_layout", true); - childSetVisible("partner_spacer_layout", false); - } - else if (customer_lower == "secondlifetime_premium_plus") - { - getChild("badge_icon")->setValue("Profile_Badge_Pplus_Lifetime"); - getChild("badge_text")->setValue(getString("BadgePremiumPlusLifetime")); - childSetVisible("badge_layout", true); - childSetVisible("partner_spacer_layout", false); - } - else - { - childSetVisible("badge_layout", false); - childSetVisible("partner_spacer_layout", true); - } -} - -void LLPanelProfileSecondLife::fillRightsData() -{ - if (getSelfProfile()) - { - return; - } - - const LLRelationship* relation = LLAvatarTracker::instance().getBuddyInfo(getAvatarId()); - // If true - we are viewing friend's profile, enable check boxes and set values. - if (relation) - { - S32 rights = relation->getRightsGrantedTo(); - bool can_see_online = LLRelationship::GRANT_ONLINE_STATUS & rights; - bool can_see_on_map = LLRelationship::GRANT_MAP_LOCATION & rights; - bool can_edit_objects = LLRelationship::GRANT_MODIFY_OBJECTS & rights; - - mCanSeeOnlineIcon->setVisible(can_see_online); - mCantSeeOnlineIcon->setVisible(!can_see_online); - mCanSeeOnMapIcon->setVisible(can_see_on_map); - mCantSeeOnMapIcon->setVisible(!can_see_on_map); - mCanEditObjectsIcon->setVisible(can_edit_objects); - mCantEditObjectsIcon->setVisible(!can_edit_objects); - - mCanSeeOnlineIcon->setEnabled(true); - mCantSeeOnlineIcon->setEnabled(true); - mCanSeeOnMapIcon->setEnabled(true); - mCantSeeOnMapIcon->setEnabled(true); - mCanEditObjectsIcon->setEnabled(true); - mCantEditObjectsIcon->setEnabled(true); - } - else - { - mCanSeeOnlineIcon->setVisible(false); - mCantSeeOnlineIcon->setVisible(false); - mCanSeeOnMapIcon->setVisible(false); - mCantSeeOnMapIcon->setVisible(false); - mCanEditObjectsIcon->setVisible(false); - mCantEditObjectsIcon->setVisible(false); - } -} - -void LLPanelProfileSecondLife::fillAgeData(const LLAvatarData* avatar_data) -{ - // Date from server comes already converted to stl timezone, - // so display it as an UTC + 0 - bool hide_age = avatar_data->hide_age && !getSelfProfile(); - std::string name_and_date = getString(hide_age ? "date_format_short" : "date_format_full"); - LLSD args_name; - args_name["datetime"] = (S32)avatar_data->born_on.secondsSinceEpoch(); - LLStringUtil::format(name_and_date, args_name); - getChild("sl_birth_date")->setValue(name_and_date); - - LLUICtrl* userAgeCtrl = getChild("user_age"); - if (hide_age) - { - userAgeCtrl->setVisible(false); - } - else - { - std::string register_date = getString("age_format"); - LLSD args_age; - args_age["[AGE]"] = LLDateUtil::ageFromDate(avatar_data->born_on, LLDate::now()); - LLStringUtil::format(register_date, args_age); - userAgeCtrl->setValue(register_date); - } - - bool showHideAgeCombo = false; - if (getSelfProfile()) - { - if (LLAvatarPropertiesProcessor::getInstance()->isHideAgeSupportedByServer()) - { - F64 birth = avatar_data->born_on.secondsSinceEpoch(); - F64 now = LLDate::now().secondsSinceEpoch(); - if (now - birth > 365 * 24 * 60 * 60) - { - mHideAge = avatar_data->hide_age; - mHideAgeCombo->setValue(mHideAge); - showHideAgeCombo = true; - } - } - } - mHideAgeCombo->setVisible(showHideAgeCombo); -} - -void LLPanelProfileSecondLife::onImageLoaded(bool success, LLViewerFetchedTexture *imagep) -{ - LLRect imageRect = mSecondLifePicLayout->getRect(); - if (!success || imagep->getFullWidth() == imagep->getFullHeight()) - { - mSecondLifePicLayout->reshape(imageRect.getWidth(), imageRect.getWidth()); - } - else - { - // assume 3:4, for sake of firestorm - mSecondLifePicLayout->reshape(imageRect.getWidth(), imageRect.getWidth() * 3 / 4); - } -} - -// virtual, called by LLAvatarTracker -void LLPanelProfileSecondLife::changed(U32 mask) -{ - updateOnlineStatus(); - if (mask != LLFriendObserver::ONLINE) - { - fillRightsData(); - } -} - -// virtual, called by LLVoiceClient -void LLPanelProfileSecondLife::onChange(EStatusType status, const std::string &channelURI, bool proximal) -{ - if(status == STATUS_JOINING || status == STATUS_LEFT_CHANNEL) - { - return; - } - - mVoiceStatus = LLAvatarActions::canCall() && (LLAvatarActions::isFriend(getAvatarId()) ? LLAvatarTracker::instance().isBuddyOnline(getAvatarId()) : true); -} - -void LLPanelProfileSecondLife::setAvatarId(const LLUUID& avatar_id) -{ - if (avatar_id.notNull()) - { - if (getAvatarId().notNull()) - { - LLAvatarTracker::instance().removeParticularFriendObserver(getAvatarId(), this); - } - - LLPanelProfilePropertiesProcessorTab::setAvatarId(avatar_id); - - if (LLAvatarActions::isFriend(getAvatarId())) - { - LLAvatarTracker::instance().addParticularFriendObserver(getAvatarId(), this); - } - } -} - -// method was disabled according to EXT-2022. Re-enabled & improved according to EXT-3880 -void LLPanelProfileSecondLife::updateOnlineStatus() -{ - const LLRelationship* relationship = LLAvatarTracker::instance().getBuddyInfo(getAvatarId()); - if (relationship != NULL) - { - // For friend let check if he allowed me to see his status - bool online = relationship->isOnline(); - bool perm_granted = relationship->isRightGrantedFrom(LLRelationship::GRANT_ONLINE_STATUS); - processOnlineStatus(true, perm_granted, online); - } - else - { - childSetVisible("friend_layout", false); - childSetVisible("online_layout", false); - childSetVisible("offline_layout", false); - } -} - -void LLPanelProfileSecondLife::processOnlineStatus(bool is_friend, bool show_online, bool online) -{ - childSetVisible("friend_layout", is_friend); - childSetVisible("online_layout", online && show_online); - childSetVisible("offline_layout", !online && show_online); -} - -void LLPanelProfileSecondLife::setLoaded() -{ - LLPanelProfileTab::setLoaded(); - - if (getSelfProfile()) - { - mShowInSearchCombo->setEnabled(true); - if (mHideAgeCombo->getVisible()) - { - mHideAgeCombo->setEnabled(true); - } - mDescriptionEdit->setEnabled(true); - } -} - - -class LLProfileImagePicker : public LLFilePickerThread -{ -public: - LLProfileImagePicker(EProfileImageType type, LLHandle *handle); - ~LLProfileImagePicker(); - void notify(const std::vector& filenames) override; - -private: - LLHandle *mHandle; - EProfileImageType mType; -}; - -LLProfileImagePicker::LLProfileImagePicker(EProfileImageType type, LLHandle *handle) - : LLFilePickerThread(LLFilePicker::FFLOAD_IMAGE), - mHandle(handle), - mType(type) -{ -} - -LLProfileImagePicker::~LLProfileImagePicker() -{ - delete mHandle; -} - -void LLProfileImagePicker::notify(const std::vector& filenames) -{ - if (mHandle->isDead()) - { - return; - } - if (filenames.empty()) - { - return; - } - std::string file_path = filenames[0]; - if (file_path.empty()) - { - return; - } - - // generate a temp texture file for coroutine - std::string temp_file = gDirUtilp->getTempFilename(); - U32 codec = LLImageBase::getCodecFromExtension(gDirUtilp->getExtension(file_path)); - const S32 MAX_DIM = 256; - if (!LLViewerTextureList::createUploadFile(file_path, temp_file, codec, MAX_DIM)) - { - LLSD notif_args; - notif_args["REASON"] = LLImage::getLastThreadError().c_str(); - LLNotificationsUtil::add("CannotUploadTexture", notif_args); - LL_WARNS("AvatarProperties") << "Failed to upload profile image of type " << (S32)mType << ", " << notif_args["REASON"].asString() << LL_ENDL; - return; - } - - std::string cap_url = gAgent.getRegionCapability(PROFILE_IMAGE_UPLOAD_CAP); - if (cap_url.empty()) - { - LLSD args; - args["CAPABILITY"] = PROFILE_IMAGE_UPLOAD_CAP; - LLNotificationsUtil::add("RegionCapabilityRequestError", args); - LL_WARNS("AvatarProperties") << "Failed to upload profile image of type " << (S32)mType << ", no cap found" << LL_ENDL; - return; - } - - switch (mType) - { - case PROFILE_IMAGE_SL: - { - LLPanelProfileSecondLife* panel = static_cast(mHandle->get()); - panel->setProfileImageUploading(true); - } - break; - case PROFILE_IMAGE_FL: - { - LLPanelProfileFirstLife* panel = static_cast(mHandle->get()); - panel->setProfileImageUploading(true); - } - break; - } - - LLCoros::instance().launch("postAgentUserImageCoro", - boost::bind(post_profile_image_coro, cap_url, mType, temp_file, mHandle)); - - mHandle = nullptr; // transferred to post_profile_image_coro -} - -void LLPanelProfileSecondLife::onCommitMenu(const LLSD& userdata) -{ - const std::string item_name = userdata.asString(); - const LLUUID agent_id = getAvatarId(); - // todo: consider moving this into LLAvatarActions::onCommit(name, id) - // and making all other flaoters, like people menu do the same - if (item_name == "im") - { - LLAvatarActions::startIM(agent_id); - } - else if (item_name == "offer_teleport") - { - LLAvatarActions::offerTeleport(agent_id); - } - else if (item_name == "request_teleport") - { - LLAvatarActions::teleportRequest(agent_id); - } - else if (item_name == "voice_call") - { - LLAvatarActions::startCall(agent_id); - } - else if (item_name == "chat_history") - { - LLAvatarActions::viewChatHistory(agent_id); - } - else if (item_name == "add_friend") - { - LLAvatarActions::requestFriendshipDialog(agent_id); - } - else if (item_name == "remove_friend") - { - LLAvatarActions::removeFriendDialog(agent_id); - } - else if (item_name == "invite_to_group") - { - LLAvatarActions::inviteToGroup(agent_id); - } - else if (item_name == "can_show_on_map") - { - LLAvatarActions::showOnMap(agent_id); - } - else if (item_name == "share") - { - LLAvatarActions::share(agent_id); - } - else if (item_name == "pay") - { - LLAvatarActions::pay(agent_id); - } - else if (item_name == "toggle_block_agent") - { - LLAvatarActions::toggleBlock(agent_id); - } - else if (item_name == "copy_user_id") - { - LLWString wstr = utf8str_to_wstring(getAvatarId().asString()); - LLClipboard::instance().copyToClipboard(wstr, 0, wstr.size()); - } - else if (item_name == "agent_permissions") - { - onShowAgentPermissionsDialog(); - } - else if (item_name == "copy_display_name" - || item_name == "copy_username") - { - LLAvatarName av_name; - if (!LLAvatarNameCache::get(getAvatarId(), &av_name)) - { - // shouldn't happen, option is supposed to be invisible while name is fetching - LL_WARNS() << "Failed to get agent data" << LL_ENDL; - return; - } - LLWString wstr; - if (item_name == "copy_display_name") - { - wstr = utf8str_to_wstring(av_name.getDisplayName(true)); - } - else if (item_name == "copy_username") - { - wstr = utf8str_to_wstring(av_name.getUserName()); - } - LLClipboard::instance().copyToClipboard(wstr, 0, wstr.size()); - } - else if (item_name == "edit_display_name") - { - LLAvatarNameCache::get(getAvatarId(), boost::bind(&LLPanelProfileSecondLife::onAvatarNameCacheSetName, this, _1, _2)); - LLFirstUse::setDisplayName(false); - } - else if (item_name == "edit_partner") - { - std::string url = "https://[GRID]/my/account/partners.php"; - LLSD subs; - url = LLWeb::expandURLSubstitutions(url, subs); - LLUrlAction::openURL(url); - } - else if (item_name == "upload_photo") - { - (new LLProfileImagePicker(PROFILE_IMAGE_SL, new LLHandle(LLPanel::getHandle())))->getFile(); - - LLFloater* floaterp = mFloaterTexturePickerHandle.get(); - if (floaterp) - { - floaterp->closeFloater(); - } - } - else if (item_name == "change_photo") - { - onShowTexturePicker(); - } - else if (item_name == "remove_photo") - { - onCommitProfileImage(LLUUID::null); - - LLFloater* floaterp = mFloaterTexturePickerHandle.get(); - if (floaterp) - { - floaterp->closeFloater(); - } - } -} - -bool LLPanelProfileSecondLife::onEnableMenu(const LLSD& userdata) -{ - const std::string item_name = userdata.asString(); - const LLUUID agent_id = getAvatarId(); - if (item_name == "offer_teleport" || item_name == "request_teleport") - { - return LLAvatarActions::canOfferTeleport(agent_id); - } - else if (item_name == "voice_call") - { - return mVoiceStatus; - } - else if (item_name == "chat_history") - { - return LLLogChat::isTranscriptExist(agent_id); - } - else if (item_name == "add_friend") - { - return !LLAvatarActions::isFriend(agent_id); - } - else if (item_name == "remove_friend") - { - return LLAvatarActions::isFriend(agent_id); - } - else if (item_name == "can_show_on_map") - { - return (LLAvatarTracker::instance().isBuddyOnline(agent_id) && is_agent_mappable(agent_id)) - || gAgent.isGodlike(); - } - else if (item_name == "toggle_block_agent") - { - return LLAvatarActions::canBlock(agent_id); - } - else if (item_name == "agent_permissions") - { - return LLAvatarActions::isFriend(agent_id); - } - else if (item_name == "copy_display_name" - || item_name == "copy_username") - { - return !mAvatarNameCacheConnection.connected(); - } - else if (item_name == "upload_photo" - || item_name == "change_photo") - { - std::string cap_url = gAgent.getRegionCapability(PROFILE_IMAGE_UPLOAD_CAP); - return !cap_url.empty() && !mWaitingForImageUpload && getIsLoaded(); - } - else if (item_name == "remove_photo") - { - std::string cap_url = gAgent.getRegionCapability(PROFILE_PROPERTIES_CAP); - return mSecondLifePic->getImageAssetId().notNull() && !cap_url.empty() && !mWaitingForImageUpload && getIsLoaded(); - } - - return false; -} - -bool LLPanelProfileSecondLife::onCheckMenu(const LLSD& userdata) -{ - const std::string item_name = userdata.asString(); - const LLUUID agent_id = getAvatarId(); - if (item_name == "toggle_block_agent") - { - return LLAvatarActions::isBlocked(agent_id); - } - return false; -} - -void LLPanelProfileSecondLife::onAvatarNameCacheSetName(const LLUUID& agent_id, const LLAvatarName& av_name) -{ - if (av_name.getDisplayName().empty()) - { - // something is wrong, tell user to try again later - LLNotificationsUtil::add("SetDisplayNameFailedGeneric"); - return; - } - - LL_INFOS("LegacyProfile") << "name-change now " << LLDate::now() << " next_update " - << LLDate(av_name.mNextUpdate) << LL_ENDL; - F64 now_secs = LLDate::now().secondsSinceEpoch(); - - if (now_secs < av_name.mNextUpdate) - { - // if the update time is more than a year in the future, it means updates have been blocked - // show a more general message - static const S32 YEAR = 60*60*24*365; - if (now_secs + YEAR < av_name.mNextUpdate) - { - LLNotificationsUtil::add("SetDisplayNameBlocked"); - return; - } - } - - LLFloaterReg::showInstance("display_name"); -} - -void LLPanelProfileSecondLife::setDescriptionText(const std::string &text) -{ - mSaveDescriptionChanges->setEnabled(false); - mDiscardDescriptionChanges->setEnabled(false); - mHasUnsavedDescriptionChanges = false; - - mDescriptionText = text; - mDescriptionEdit->setValue(mDescriptionText); -} - -void LLPanelProfileSecondLife::onSetDescriptionDirty() -{ - mSaveDescriptionChanges->setEnabled(true); - mDiscardDescriptionChanges->setEnabled(true); - mHasUnsavedDescriptionChanges = true; -} - -void LLPanelProfileSecondLife::onShowInSearchCallback() -{ - bool value = mShowInSearchCombo->getValue().asInteger(); - if (value == mAllowPublish) - return; - - mAllowPublish = value; - saveAgentUserInfoCoro("allow_publish", value); -} - -void LLPanelProfileSecondLife::onHideAgeCallback() -{ - bool value = mHideAgeCombo->getValue().asInteger(); - if (value == mHideAge) - return; - - mHideAge = value; - saveAgentUserInfoCoro("hide_age", value); -} - -void LLPanelProfileSecondLife::onSaveDescriptionChanges() -{ - mDescriptionText = mDescriptionEdit->getValue().asString(); - saveAgentUserInfoCoro("sl_about_text", mDescriptionText); - - mSaveDescriptionChanges->setEnabled(false); - mDiscardDescriptionChanges->setEnabled(false); - mHasUnsavedDescriptionChanges = false; -} - -void LLPanelProfileSecondLife::onDiscardDescriptionChanges() -{ - setDescriptionText(mDescriptionText); -} - -void LLPanelProfileSecondLife::onShowAgentPermissionsDialog() -{ - LLFloater *floater = mFloaterPermissionsHandle.get(); - if (!floater) - { - LLFloater* parent_floater = gFloaterView->getParentFloater(this); - if (parent_floater) - { - LLFloaterProfilePermissions * perms = new LLFloaterProfilePermissions(parent_floater, getAvatarId()); - mFloaterPermissionsHandle = perms->getHandle(); - perms->openFloater(); - perms->setVisibleAndFrontmost(true); - - parent_floater->addDependentFloater(mFloaterPermissionsHandle); - } - } - else // already open - { - floater->setMinimized(false); - floater->setVisibleAndFrontmost(true); - } -} - -void LLPanelProfileSecondLife::onShowAgentProfileTexture() -{ - if (!getIsLoaded()) - { - return; - } - - LLFloater *floater = mFloaterProfileTextureHandle.get(); - if (!floater) - { - LLFloater* parent_floater = gFloaterView->getParentFloater(this); - if (parent_floater) - { - LLFloaterProfileTexture * texture_view = new LLFloaterProfileTexture(parent_floater); - mFloaterProfileTextureHandle = texture_view->getHandle(); - if (mSecondLifePic->getImageAssetId().notNull()) - { - texture_view->loadAsset(mSecondLifePic->getImageAssetId()); - } - else - { - texture_view->resetAsset(); - } - texture_view->openFloater(); - texture_view->setVisibleAndFrontmost(true); - - parent_floater->addDependentFloater(mFloaterProfileTextureHandle); - } - } - else // already open - { - LLFloaterProfileTexture * texture_view = dynamic_cast(floater); - texture_view->setMinimized(false); - texture_view->setVisibleAndFrontmost(true); - if (mSecondLifePic->getImageAssetId().notNull()) - { - texture_view->loadAsset(mSecondLifePic->getImageAssetId()); - } - else - { - texture_view->resetAsset(); - } - } -} - -void LLPanelProfileSecondLife::onShowTexturePicker() -{ - LLFloater* floaterp = mFloaterTexturePickerHandle.get(); - - // Show the dialog - if (!floaterp) - { - LLFloater* parent_floater = gFloaterView->getParentFloater(this); - if (parent_floater) - { - // because inventory construction is somewhat slow - getWindow()->setCursor(UI_CURSOR_WAIT); - LLFloaterTexturePicker* texture_floaterp = new LLFloaterTexturePicker( - this, - mSecondLifePic->getImageAssetId(), - LLUUID::null, - mSecondLifePic->getImageAssetId(), - false, - false, - "SELECT PHOTO", - PERM_NONE, - PERM_NONE, - false, - NULL, - PICK_TEXTURE); - - mFloaterTexturePickerHandle = texture_floaterp->getHandle(); - - texture_floaterp->setOnFloaterCommitCallback([this](LLTextureCtrl::ETexturePickOp op, LLPickerSource source, const LLUUID& asset_id, const LLUUID&, const LLUUID&) - { - if (op == LLTextureCtrl::TEXTURE_SELECT) - { - onCommitProfileImage(asset_id); - } - }); - texture_floaterp->setLocalTextureEnabled(false); - texture_floaterp->setBakeTextureEnabled(false); - texture_floaterp->setCanApply(false, true, false); - - parent_floater->addDependentFloater(mFloaterTexturePickerHandle); - - texture_floaterp->openFloater(); - texture_floaterp->setFocus(true); - } - } - else - { - floaterp->setMinimized(false); - floaterp->setVisibleAndFrontmost(true); - } -} - -void LLPanelProfileSecondLife::onCommitProfileImage(const LLUUID& id) -{ - if (mSecondLifePic->getImageAssetId() == id) - return; - - std::function callback = [id](bool result) - { - if (result) - { - LLAvatarIconIDCache::getInstance()->add(gAgentID, id); - // Should trigger callbacks in icon controls (or request Legacy) - LLAvatarPropertiesProcessor::getInstance()->sendAvatarPropertiesRequest(gAgentID); - } - }; - - if (!saveAgentUserInfoCoro("sl_image_id", id, callback)) - return; - - mSecondLifePic->setValue(id); - - LLFloater *floater = mFloaterProfileTextureHandle.get(); - if (floater) - { - LLFloaterProfileTexture * texture_view = dynamic_cast(floater); - if (id == LLUUID::null) - { - texture_view->resetAsset(); - } - else - { - texture_view->loadAsset(id); - } - } -} - -////////////////////////////////////////////////////////////////////////// -// LLPanelProfileWeb - -LLPanelProfileWeb::LLPanelProfileWeb() - : LLPanelProfileTab() - , mWebBrowser(NULL) - , mAvatarNameCacheConnection() -{ -} - -LLPanelProfileWeb::~LLPanelProfileWeb() -{ - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } -} - -void LLPanelProfileWeb::onOpen(const LLSD& key) -{ - LLPanelProfileTab::onOpen(key); - - resetData(); - - mAvatarNameCacheConnection = LLAvatarNameCache::get(getAvatarId(), boost::bind(&LLPanelProfileWeb::onAvatarNameCache, this, _1, _2)); -} - -bool LLPanelProfileWeb::postBuild() -{ - mWebBrowser = getChild("profile_html"); - mWebBrowser->addObserver(this); - mWebBrowser->setHomePageUrl("about:blank"); - - return true; -} - -void LLPanelProfileWeb::resetData() -{ - mWebBrowser->navigateHome(); -} - -void LLPanelProfileWeb::updateData() -{ - LLUUID avatar_id = getAvatarId(); - if (!getStarted() && avatar_id.notNull() && !mURLWebProfile.empty()) - { - setIsLoading(); - - mWebBrowser->setVisible(true); - mPerformanceTimer.start(); - mWebBrowser->navigateTo(mURLWebProfile, HTTP_CONTENT_TEXT_HTML); - } -} - -void LLPanelProfileWeb::onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name) -{ - mAvatarNameCacheConnection.disconnect(); - - std::string username = av_name.getAccountName(); - if (username.empty()) - { - username = LLCacheName::buildUsername(av_name.getDisplayName()); - } - else - { - LLStringUtil::replaceChar(username, ' ', '.'); - } - - mURLWebProfile = getProfileURL(username, true); - if (mURLWebProfile.empty()) - { - return; - } - - //if the tab was opened before name was resolved, load the panel now - updateData(); -} - -void LLPanelProfileWeb::onCommitLoad(LLUICtrl* ctrl) -{ - if (!mURLHome.empty()) - { - LLSD::String valstr = ctrl->getValue().asString(); - if (valstr.empty()) - { - mWebBrowser->setVisible(true); - mPerformanceTimer.start(); - mWebBrowser->navigateTo( mURLHome, HTTP_CONTENT_TEXT_HTML ); - } - else if (valstr == "popout") - { - // open in viewer's browser, new window - LLWeb::loadURLInternal(mURLHome); - } - else if (valstr == "external") - { - // open in external browser - LLWeb::loadURLExternal(mURLHome); - } - } -} - -void LLPanelProfileWeb::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) -{ - switch(event) - { - case MEDIA_EVENT_STATUS_TEXT_CHANGED: - childSetValue("status_text", LLSD( self->getStatusText() ) ); - break; - - case MEDIA_EVENT_NAVIGATE_BEGIN: - { - if (mFirstNavigate) - { - mFirstNavigate = false; - } - else - { - mPerformanceTimer.start(); - } - } - break; - - case MEDIA_EVENT_NAVIGATE_COMPLETE: - { - LLStringUtil::format_map_t args; - args["[TIME]"] = llformat("%.2f", mPerformanceTimer.getElapsedTimeF32()); - childSetValue("status_text", LLSD( getString("LoadTime", args)) ); - - setLoaded(); - } - break; - - default: - // Having a default case makes the compiler happy. - break; - } -} - - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -LLPanelProfileFirstLife::LLPanelProfileFirstLife() - : LLPanelProfilePropertiesProcessorTab() - , mHasUnsavedChanges(false) -{ -} - -LLPanelProfileFirstLife::~LLPanelProfileFirstLife() -{ -} - -bool LLPanelProfileFirstLife::postBuild() -{ - mDescriptionEdit = getChild("fl_description_edit"); - mPicture = getChild("real_world_pic"); - - mUploadPhoto = getChild("fl_upload_image"); - mChangePhoto = getChild("fl_change_image"); - mRemovePhoto = getChild("fl_remove_image"); - mSaveChanges = getChild("fl_save_changes"); - mDiscardChanges = getChild("fl_discard_changes"); - - mUploadPhoto->setCommitCallback([this](LLUICtrl*, void*) { onUploadPhoto(); }, nullptr); - mChangePhoto->setCommitCallback([this](LLUICtrl*, void*) { onChangePhoto(); }, nullptr); - mRemovePhoto->setCommitCallback([this](LLUICtrl*, void*) { onRemovePhoto(); }, nullptr); - mSaveChanges->setCommitCallback([this](LLUICtrl*, void*) { onSaveDescriptionChanges(); }, nullptr); - mDiscardChanges->setCommitCallback([this](LLUICtrl*, void*) { onDiscardDescriptionChanges(); }, nullptr); - mDescriptionEdit->setKeystrokeCallback([this](LLTextEditor* caller) { onSetDescriptionDirty(); }); - - return true; -} - -void LLPanelProfileFirstLife::onOpen(const LLSD& key) -{ - LLPanelProfileTab::onOpen(key); - - if (!getSelfProfile()) - { - // Otherwise as the only focusable element it will be selected - mDescriptionEdit->setTabStop(false); - } - - resetData(); -} - -void LLPanelProfileFirstLife::setProfileImageUploading(bool loading) -{ - mUploadPhoto->setEnabled(!loading); - mChangePhoto->setEnabled(!loading); - mRemovePhoto->setEnabled(!loading && mPicture->getImageAssetId().notNull()); - - LLLoadingIndicator* indicator = getChild("image_upload_indicator"); - indicator->setVisible(loading); - if (loading) - { - indicator->start(); - } - else - { - indicator->stop(); - } -} - -void LLPanelProfileFirstLife::setProfileImageUploaded(const LLUUID &image_asset_id) -{ - mPicture->setValue(image_asset_id); - setProfileImageUploading(false); -} - -void LLPanelProfileFirstLife::commitUnsavedChanges() -{ - if (mHasUnsavedChanges) - { - onSaveDescriptionChanges(); - } -} - -void LLPanelProfileFirstLife::onUploadPhoto() -{ - (new LLProfileImagePicker(PROFILE_IMAGE_FL, new LLHandle(LLPanel::getHandle())))->getFile(); - - LLFloater* floaterp = mFloaterTexturePickerHandle.get(); - if (floaterp) - { - floaterp->closeFloater(); - } -} - -void LLPanelProfileFirstLife::onChangePhoto() -{ - LLFloater* floaterp = mFloaterTexturePickerHandle.get(); - - // Show the dialog - if (!floaterp) - { - LLFloater* parent_floater = gFloaterView->getParentFloater(this); - if (parent_floater) - { - // because inventory construction is somewhat slow - getWindow()->setCursor(UI_CURSOR_WAIT); - LLFloaterTexturePicker* texture_floaterp = new LLFloaterTexturePicker( - this, - mPicture->getImageAssetId(), - LLUUID::null, - mPicture->getImageAssetId(), - false, - false, - "SELECT PHOTO", - PERM_NONE, - PERM_NONE, - false, - NULL, - PICK_TEXTURE); - - mFloaterTexturePickerHandle = texture_floaterp->getHandle(); - - texture_floaterp->setOnFloaterCommitCallback([this](LLTextureCtrl::ETexturePickOp op, LLPickerSource source, const LLUUID& asset_id, const LLUUID&, const LLUUID&) - { - if (op == LLTextureCtrl::TEXTURE_SELECT) - { - onCommitPhoto(asset_id); - } - }); - texture_floaterp->setLocalTextureEnabled(false); - texture_floaterp->setCanApply(false, true, false); - - parent_floater->addDependentFloater(mFloaterTexturePickerHandle); - - texture_floaterp->openFloater(); - texture_floaterp->setFocus(true); - } - } - else - { - floaterp->setMinimized(false); - floaterp->setVisibleAndFrontmost(true); - } -} - -void LLPanelProfileFirstLife::onRemovePhoto() -{ - onCommitPhoto(LLUUID::null); - - LLFloater* floaterp = mFloaterTexturePickerHandle.get(); - if (floaterp) - { - floaterp->closeFloater(); - } -} - -void LLPanelProfileFirstLife::onCommitPhoto(const LLUUID& id) -{ - if (mPicture->getImageAssetId() == id) - return; - - if (!saveAgentUserInfoCoro("fl_image_id", id)) - return; - - mPicture->setValue(id); - - mRemovePhoto->setEnabled(id.notNull()); -} - -void LLPanelProfileFirstLife::setDescriptionText(const std::string &text) -{ - mSaveChanges->setEnabled(false); - mDiscardChanges->setEnabled(false); - mHasUnsavedChanges = false; - - mCurrentDescription = text; - mDescriptionEdit->setValue(mCurrentDescription); -} - -void LLPanelProfileFirstLife::onSetDescriptionDirty() -{ - mSaveChanges->setEnabled(true); - mDiscardChanges->setEnabled(true); - mHasUnsavedChanges = true; -} - -void LLPanelProfileFirstLife::onSaveDescriptionChanges() -{ - mCurrentDescription = mDescriptionEdit->getValue().asString(); - saveAgentUserInfoCoro("fl_about_text", mCurrentDescription); - - mSaveChanges->setEnabled(false); - mDiscardChanges->setEnabled(false); - mHasUnsavedChanges = false; -} - -void LLPanelProfileFirstLife::onDiscardDescriptionChanges() -{ - setDescriptionText(mCurrentDescription); -} - -void LLPanelProfileFirstLife::processProperties(void* data, EAvatarProcessorType type) -{ - if (APT_PROPERTIES == type) - { - LLAvatarData* avatar_data = static_cast(data); - if (avatar_data && getAvatarId() == avatar_data->avatar_id) - { - processProperties(avatar_data); - } - } -} - -void LLPanelProfileFirstLife::processProperties(const LLAvatarData* avatar_data) -{ - setDescriptionText(avatar_data->fl_about_text); - - mPicture->setValue(avatar_data->fl_image_id); - - setLoaded(); -} - -void LLPanelProfileFirstLife::resetData() -{ - setDescriptionText(std::string()); - mPicture->setValue(LLUUID::null); - - mUploadPhoto->setVisible(getSelfProfile()); - mChangePhoto->setVisible(getSelfProfile()); - mRemovePhoto->setVisible(getSelfProfile()); - mSaveChanges->setVisible(getSelfProfile()); - mDiscardChanges->setVisible(getSelfProfile()); -} - -void LLPanelProfileFirstLife::setLoaded() -{ - LLPanelProfileTab::setLoaded(); - - if (getSelfProfile()) - { - mDescriptionEdit->setEnabled(true); - mPicture->setEnabled(true); - mRemovePhoto->setEnabled(mPicture->getImageAssetId().notNull()); - } -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -LLPanelProfileNotes::LLPanelProfileNotes() -: LLPanelProfilePropertiesProcessorTab() - , mHasUnsavedChanges(false) -{ - -} - -LLPanelProfileNotes::~LLPanelProfileNotes() -{ -} - -void LLPanelProfileNotes::commitUnsavedChanges() -{ - if (mHasUnsavedChanges) - { - onSaveNotesChanges(); - } -} - -bool LLPanelProfileNotes::postBuild() -{ - mNotesEditor = getChild("notes_edit"); - mSaveChanges = getChild("notes_save_changes"); - mDiscardChanges = getChild("notes_discard_changes"); - - mSaveChanges->setCommitCallback([this](LLUICtrl*, void*) { onSaveNotesChanges(); }, nullptr); - mDiscardChanges->setCommitCallback([this](LLUICtrl*, void*) { onDiscardNotesChanges(); }, nullptr); - mNotesEditor->setKeystrokeCallback([this](LLTextEditor* caller) { onSetNotesDirty(); }); - - return true; -} - -void LLPanelProfileNotes::onOpen(const LLSD& key) -{ - LLPanelProfileTab::onOpen(key); - - resetData(); -} - -void LLPanelProfileNotes::setNotesText(const std::string &text) -{ - mSaveChanges->setEnabled(false); - mDiscardChanges->setEnabled(false); - mHasUnsavedChanges = false; - - mCurrentNotes = text; - mNotesEditor->setValue(mCurrentNotes); -} - -void LLPanelProfileNotes::onSetNotesDirty() -{ - mSaveChanges->setEnabled(true); - mDiscardChanges->setEnabled(true); - mHasUnsavedChanges = true; -} - -void LLPanelProfileNotes::onSaveNotesChanges() -{ - mCurrentNotes = mNotesEditor->getValue().asString(); - saveAgentUserInfoCoro("notes", mCurrentNotes); - - mSaveChanges->setEnabled(false); - mDiscardChanges->setEnabled(false); - mHasUnsavedChanges = false; -} - -void LLPanelProfileNotes::onDiscardNotesChanges() -{ - setNotesText(mCurrentNotes); -} - -void LLPanelProfileNotes::processProperties(void* data, EAvatarProcessorType type) -{ - if (APT_PROPERTIES == type) - { - LLAvatarData* avatar_data = static_cast(data); - if (avatar_data && getAvatarId() == avatar_data->avatar_id) - { - processProperties(avatar_data); - } - } -} - -void LLPanelProfileNotes::processProperties(const LLAvatarData* avatar_data) -{ - setNotesText(avatar_data->notes); - mNotesEditor->setEnabled(true); - setLoaded(); -} - -void LLPanelProfileNotes::resetData() -{ - resetLoading(); - setNotesText(std::string()); -} - - -////////////////////////////////////////////////////////////////////////// -// LLPanelProfile - -LLPanelProfile::LLPanelProfile() - : LLPanelProfileTab() -{ -} - -LLPanelProfile::~LLPanelProfile() -{ -} - -bool LLPanelProfile::postBuild() -{ - return true; -} - -void LLPanelProfile::onTabChange() -{ - LLPanelProfileTab* active_panel = dynamic_cast(mTabContainer->getCurrentPanel()); - if (active_panel) - { - active_panel->updateData(); - } -} - -void LLPanelProfile::onOpen(const LLSD& key) -{ - LLUUID avatar_id = key["id"].asUUID(); - - // Don't reload the same profile - if (getAvatarId() == avatar_id) - { - return; - } - - LLPanelProfileTab::onOpen(avatar_id); - - mTabContainer = getChild("panel_profile_tabs"); - mPanelSecondlife = findChild(PANEL_SECONDLIFE); - mPanelWeb = findChild(PANEL_WEB); - mPanelPicks = findChild(PANEL_PICKS); - mPanelClassifieds = findChild(PANEL_CLASSIFIEDS); - mPanelFirstlife = findChild(PANEL_FIRSTLIFE); - mPanelNotes = findChild(PANEL_NOTES); - - mPanelSecondlife->onOpen(avatar_id); - mPanelWeb->onOpen(avatar_id); - mPanelPicks->onOpen(avatar_id); - mPanelClassifieds->onOpen(avatar_id); - mPanelFirstlife->onOpen(avatar_id); - mPanelNotes->onOpen(avatar_id); - - // Always request the base profile info - resetLoading(); - updateData(); - - // Some tabs only request data when opened - mTabContainer->setCommitCallback(boost::bind(&LLPanelProfile::onTabChange, this)); -} - -void LLPanelProfile::updateData() -{ - LLUUID avatar_id = getAvatarId(); - // Todo: getIsloading functionality needs to be expanded to - // include 'inited' or 'data_provided' state to not rerequest - if (!getStarted() && avatar_id.notNull()) - { - setIsLoading(); - - mPanelSecondlife->setIsLoading(); - mPanelPicks->setIsLoading(); - mPanelFirstlife->setIsLoading(); - mPanelNotes->setIsLoading(); - - LLAvatarPropertiesProcessor::getInstance()->sendAvatarPropertiesRequest(getAvatarId()); - } -} - -void LLPanelProfile::refreshName() -{ - mPanelSecondlife->refreshName(); -} - -void LLPanelProfile::createPick(const LLPickData &data) -{ - mTabContainer->selectTabPanel(mPanelPicks); - mPanelPicks->createPick(data); -} - -void LLPanelProfile::showPick(const LLUUID& pick_id) -{ - if (pick_id.notNull()) - { - mPanelPicks->selectPick(pick_id); - } - mTabContainer->selectTabPanel(mPanelPicks); -} - -bool LLPanelProfile::isPickTabSelected() -{ - return (mTabContainer->getCurrentPanel() == mPanelPicks); -} - -bool LLPanelProfile::isNotesTabSelected() -{ - return (mTabContainer->getCurrentPanel() == mPanelNotes); -} - -bool LLPanelProfile::hasUnsavedChanges() -{ - return mPanelSecondlife->hasUnsavedChanges() - || mPanelPicks->hasUnsavedChanges() - || mPanelClassifieds->hasUnsavedChanges() - || mPanelFirstlife->hasUnsavedChanges() - || mPanelNotes->hasUnsavedChanges(); -} - -bool LLPanelProfile::hasUnpublishedClassifieds() -{ - return mPanelClassifieds->hasNewClassifieds(); -} - -void LLPanelProfile::commitUnsavedChanges() -{ - mPanelSecondlife->commitUnsavedChanges(); - mPanelPicks->commitUnsavedChanges(); - mPanelClassifieds->commitUnsavedChanges(); - mPanelFirstlife->commitUnsavedChanges(); - mPanelNotes->commitUnsavedChanges(); -} - -void LLPanelProfile::showClassified(const LLUUID& classified_id, bool edit) -{ - if (classified_id.notNull()) - { - mPanelClassifieds->selectClassified(classified_id, edit); - } - mTabContainer->selectTabPanel(mPanelClassifieds); -} - -void LLPanelProfile::createClassified() -{ - mPanelClassifieds->createClassified(); - mTabContainer->selectTabPanel(mPanelClassifieds); -} - +/** +* @file llpanelprofile.cpp +* @brief Profile panel implementation +* +* $LicenseInfo:firstyear=2022&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2022, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" +#include "llpanelprofile.h" + +// Common +#include "llavatarnamecache.h" +#include "llsdutil.h" +#include "llslurl.h" +#include "lldateutil.h" //ageFromDate + +// UI +#include "llavatariconctrl.h" +#include "llclipboard.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "lllineeditor.h" +#include "llloadingindicator.h" +#include "llmenubutton.h" +#include "lltabcontainer.h" +#include "lltextbox.h" +#include "lltexteditor.h" +#include "lltexturectrl.h" +#include "lltoggleablemenu.h" +#include "lltooldraganddrop.h" +#include "llgrouplist.h" +#include "llurlaction.h" + +// Image +#include "llimagej2c.h" + +// Newview +#include "llagent.h" //gAgent +#include "llagentpicksinfo.h" +#include "llavataractions.h" +#include "llavatarpropertiesprocessor.h" +#include "llcallingcard.h" +#include "llcommandhandler.h" +#include "llfloaterprofiletexture.h" +#include "llfloaterreg.h" +#include "llfloaterreporter.h" +#include "llfilepicker.h" +#include "llfirstuse.h" +#include "llgroupactions.h" +#include "lllogchat.h" +#include "llmutelist.h" +#include "llnotificationsutil.h" +#include "llpanelblockedlist.h" +#include "llpanelprofileclassifieds.h" +#include "llpanelprofilepicks.h" +#include "lltrans.h" +#include "llviewercontrol.h" +#include "llviewermenu.h" //is_agent_mappable +#include "llviewermenufile.h" +#include "llviewertexturelist.h" +#include "llvoiceclient.h" +#include "llweb.h" + + +static LLPanelInjector t_panel_profile_secondlife("panel_profile_secondlife"); +static LLPanelInjector t_panel_web("panel_profile_web"); +static LLPanelInjector t_panel_picks("panel_profile_picks"); +static LLPanelInjector t_panel_firstlife("panel_profile_firstlife"); +static LLPanelInjector t_panel_notes("panel_profile_notes"); +static LLPanelInjector t_panel_profile("panel_profile"); + +static const std::string PANEL_SECONDLIFE = "panel_profile_secondlife"; +static const std::string PANEL_WEB = "panel_profile_web"; +static const std::string PANEL_PICKS = "panel_profile_picks"; +static const std::string PANEL_CLASSIFIEDS = "panel_profile_classifieds"; +static const std::string PANEL_FIRSTLIFE = "panel_profile_firstlife"; +static const std::string PANEL_NOTES = "panel_profile_notes"; +static const std::string PANEL_PROFILE_VIEW = "panel_profile_view"; + +static const std::string PROFILE_PROPERTIES_CAP = "AgentProfile"; +static const std::string PROFILE_IMAGE_UPLOAD_CAP = "UploadAgentProfileImage"; + + +////////////////////////////////////////////////////////////////////////// + +LLUUID post_profile_image(std::string cap_url, const LLSD &first_data, std::string path_to_image, LLHandle *handle) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("post_profile_image_coro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpHeaders::ptr_t httpHeaders; + + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + httpOpts->setFollowRedirects(true); + + LLSD result = httpAdapter->postAndSuspend(httpRequest, cap_url, first_data, httpOpts, httpHeaders); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + // todo: notification? + LL_WARNS("AvatarProperties") << "Failed to get uploader cap " << status.toString() << LL_ENDL; + return LLUUID::null; + } + if (!result.has("uploader")) + { + // todo: notification? + LL_WARNS("AvatarProperties") << "Failed to get uploader cap, response contains no data." << LL_ENDL; + return LLUUID::null; + } + std::string uploader_cap = result["uploader"].asString(); + if (uploader_cap.empty()) + { + LL_WARNS("AvatarProperties") << "Failed to get uploader cap, cap invalid." << LL_ENDL; + return LLUUID::null; + } + + // Upload the image + LLCore::HttpRequest::ptr_t uploaderhttpRequest(new LLCore::HttpRequest); + LLCore::HttpHeaders::ptr_t uploaderhttpHeaders(new LLCore::HttpHeaders); + LLCore::HttpOptions::ptr_t uploaderhttpOpts(new LLCore::HttpOptions); + S64 length; + + { + llifstream instream(path_to_image.c_str(), std::iostream::binary | std::iostream::ate); + if (!instream.is_open()) + { + LL_WARNS("AvatarProperties") << "Failed to open file " << path_to_image << LL_ENDL; + return LLUUID::null; + } + length = instream.tellg(); + } + + uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/jp2"); // optional + uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_LENGTH, llformat("%d", length)); // required! + uploaderhttpOpts->setFollowRedirects(true); + + result = httpAdapter->postFileAndSuspend(uploaderhttpRequest, uploader_cap, path_to_image, uploaderhttpOpts, uploaderhttpHeaders); + + httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + LL_DEBUGS("AvatarProperties") << result << LL_ENDL; + + if (!status) + { + LL_WARNS("AvatarProperties") << "Failed to upload image " << status.toString() << LL_ENDL; + return LLUUID::null; + } + + if (result["state"].asString() != "complete") + { + if (result.has("message")) + { + LL_WARNS("AvatarProperties") << "Failed to upload image, state " << result["state"] << " message: " << result["message"] << LL_ENDL; + } + else + { + LL_WARNS("AvatarProperties") << "Failed to upload image " << result << LL_ENDL; + } + return LLUUID::null; + } + + return result["new_asset"].asUUID(); +} + +enum EProfileImageType +{ + PROFILE_IMAGE_SL, + PROFILE_IMAGE_FL, +}; + +void post_profile_image_coro(std::string cap_url, EProfileImageType type, std::string path_to_image, LLHandle *handle) +{ + LLSD data; + switch (type) + { + case PROFILE_IMAGE_SL: + data["profile-image-asset"] = "sl_image_id"; + break; + case PROFILE_IMAGE_FL: + data["profile-image-asset"] = "fl_image_id"; + break; + } + + LLUUID result = post_profile_image(cap_url, data, path_to_image, handle); + + // reset loading indicator + if (!handle->isDead()) + { + switch (type) + { + case PROFILE_IMAGE_SL: + { + LLPanelProfileSecondLife* panel = static_cast(handle->get()); + if (result.notNull()) + { + panel->setProfileImageUploaded(result); + } + else + { + // failure, just stop progress indicator + panel->setProfileImageUploading(false); + } + break; + } + case PROFILE_IMAGE_FL: + { + LLPanelProfileFirstLife* panel = static_cast(handle->get()); + if (result.notNull()) + { + panel->setProfileImageUploaded(result); + } + else + { + // failure, just stop progress indicator + panel->setProfileImageUploading(false); + } + break; + } + } + } + + if (type == PROFILE_IMAGE_SL && result.notNull()) + { + LLAvatarIconIDCache::getInstance()->add(gAgentID, result); + // Should trigger callbacks in icon controls + LLAvatarPropertiesProcessor::getInstance()->sendAvatarPropertiesRequest(gAgentID); + } + + // Cleanup + LLFile::remove(path_to_image); + delete handle; +} + +////////////////////////////////////////////////////////////////////////// +// LLProfileHandler + +class LLProfileHandler : public LLCommandHandler +{ +public: + // requires trusted browser to trigger + LLProfileHandler() : LLCommandHandler("profile", UNTRUSTED_THROTTLE) { } + + bool handle(const LLSD& params, + const LLSD& query_map, + const std::string& grid, + LLMediaCtrl* web) + { + if (params.size() < 1) return false; + std::string agent_name = params[0]; + LL_INFOS() << "Profile, agent_name " << agent_name << LL_ENDL; + std::string url = getProfileURL(agent_name); + LLWeb::loadURLInternal(url); + + return true; + } +}; +LLProfileHandler gProfileHandler; + + +////////////////////////////////////////////////////////////////////////// +// LLAgentHandler + +class LLAgentHandler : public LLCommandHandler +{ +public: + // requires trusted browser to trigger + LLAgentHandler() : LLCommandHandler("agent", UNTRUSTED_THROTTLE) { } + + virtual bool canHandleUntrusted( + const LLSD& params, + const LLSD& query_map, + LLMediaCtrl* web, + const std::string& nav_type) + { + if (params.size() < 2) + { + return true; // don't block, will fail later + } + + if (nav_type == NAV_TYPE_CLICKED + || nav_type == NAV_TYPE_EXTERNAL) + { + return true; + } + + const std::string verb = params[1].asString(); + if (verb == "about" || verb == "inspect" || verb == "reportAbuse") + { + return true; + } + return false; + } + + bool handle(const LLSD& params, + const LLSD& query_map, + const std::string& grid, + LLMediaCtrl* web) + { + if (params.size() < 2) return false; + LLUUID avatar_id; + if (!avatar_id.set(params[0], false)) + { + return false; + } + + const std::string verb = params[1].asString(); + if (verb == "about") + { + LLAvatarActions::showProfile(avatar_id); + return true; + } + + if (verb == "inspect") + { + LLFloaterReg::showInstance("inspect_avatar", LLSD().with("avatar_id", avatar_id)); + return true; + } + + if (verb == "im") + { + LLAvatarActions::startIM(avatar_id); + return true; + } + + if (verb == "pay") + { + if (!LLUI::getInstance()->mSettingGroups["config"]->getBOOL("EnableAvatarPay")) + { + LLNotificationsUtil::add("NoAvatarPay", LLSD(), LLSD(), std::string("SwitchToStandardSkinAndQuit")); + return true; + } + + LLAvatarActions::pay(avatar_id); + return true; + } + + if (verb == "offerteleport") + { + LLAvatarActions::offerTeleport(avatar_id); + return true; + } + + if (verb == "requestfriend") + { + LLAvatarActions::requestFriendshipDialog(avatar_id); + return true; + } + + if (verb == "removefriend") + { + LLAvatarActions::removeFriendDialog(avatar_id); + return true; + } + + if (verb == "mute") + { + if (! LLAvatarActions::isBlocked(avatar_id)) + { + LLAvatarActions::toggleBlock(avatar_id); + } + return true; + } + + if (verb == "unmute") + { + if (LLAvatarActions::isBlocked(avatar_id)) + { + LLAvatarActions::toggleBlock(avatar_id); + } + return true; + } + + if (verb == "block") + { + if (params.size() > 2) + { + const std::string object_name = LLURI::unescape(params[2].asString()); + LLMute mute(avatar_id, object_name, LLMute::OBJECT); + LLMuteList::getInstance()->add(mute); + LLPanelBlockedList::showPanelAndSelect(mute.mID); + } + return true; + } + + if (verb == "unblock") + { + if (params.size() > 2) + { + const std::string object_name = params[2].asString(); + LLMute mute(avatar_id, object_name, LLMute::OBJECT); + LLMuteList::getInstance()->remove(mute); + } + return true; + } + + // reportAbuse is here due to convoluted avatar handling + // in LLScrollListCtrl and LLTextBase + if (verb == "reportAbuse" && web == NULL) + { + LLAvatarName av_name; + if (LLAvatarNameCache::get(avatar_id, &av_name)) + { + LLFloaterReporter::showFromAvatar(avatar_id, av_name.getCompleteName()); + } + else + { + LLFloaterReporter::showFromAvatar(avatar_id, "not avaliable"); + } + return true; + } + return false; + } +}; +LLAgentHandler gAgentHandler; + + +///---------------------------------------------------------------------------- +/// LLFloaterProfilePermissions +///---------------------------------------------------------------------------- + +class LLFloaterProfilePermissions + : public LLFloater + , public LLFriendObserver +{ +public: + LLFloaterProfilePermissions(LLView * owner, const LLUUID &avatar_id); + ~LLFloaterProfilePermissions(); + bool postBuild() override; + void onOpen(const LLSD& key) override; + void draw() override; + void changed(U32 mask) override; // LLFriendObserver + + void onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name); + bool hasUnsavedChanges() { return mHasUnsavedPermChanges; } + + void onApplyRights(); + +private: + void fillRightsData(); + void rightsConfirmationCallback(const LLSD& notification, const LLSD& response); + void confirmModifyRights(bool grant); + void onCommitSeeOnlineRights(); + void onCommitEditRights(); + void onCancel(); + + LLTextBase* mDescription; + LLCheckBoxCtrl* mOnlineStatus; + LLCheckBoxCtrl* mMapRights; + LLCheckBoxCtrl* mEditObjectRights; + LLButton* mOkBtn; + LLButton* mCancelBtn; + + LLUUID mAvatarID; + F32 mContextConeOpacity; + bool mHasUnsavedPermChanges; + LLHandle mOwnerHandle; + + boost::signals2::connection mAvatarNameCacheConnection; +}; + +LLFloaterProfilePermissions::LLFloaterProfilePermissions(LLView * owner, const LLUUID &avatar_id) + : LLFloater(LLSD()) + , mAvatarID(avatar_id) + , mContextConeOpacity(0.0f) + , mHasUnsavedPermChanges(false) + , mOwnerHandle(owner->getHandle()) +{ + buildFromFile("floater_profile_permissions.xml"); +} + +LLFloaterProfilePermissions::~LLFloaterProfilePermissions() +{ + mAvatarNameCacheConnection.disconnect(); + if (mAvatarID.notNull()) + { + LLAvatarTracker::instance().removeParticularFriendObserver(mAvatarID, this); + } +} + +bool LLFloaterProfilePermissions::postBuild() +{ + mDescription = getChild("perm_description"); + mOnlineStatus = getChild("online_check"); + mMapRights = getChild("map_check"); + mEditObjectRights = getChild("objects_check"); + mOkBtn = getChild("perms_btn_ok"); + mCancelBtn = getChild("perms_btn_cancel"); + + mOnlineStatus->setCommitCallback([this](LLUICtrl*, void*) { onCommitSeeOnlineRights(); }, nullptr); + mMapRights->setCommitCallback([this](LLUICtrl*, void*) { mHasUnsavedPermChanges = true; }, nullptr); + mEditObjectRights->setCommitCallback([this](LLUICtrl*, void*) { onCommitEditRights(); }, nullptr); + mOkBtn->setCommitCallback([this](LLUICtrl*, void*) { onApplyRights(); }, nullptr); + mCancelBtn->setCommitCallback([this](LLUICtrl*, void*) { onCancel(); }, nullptr); + + return true; +} + +void LLFloaterProfilePermissions::onOpen(const LLSD& key) +{ + if (LLAvatarActions::isFriend(mAvatarID)) + { + LLAvatarTracker::instance().addParticularFriendObserver(mAvatarID, this); + fillRightsData(); + } + + mCancelBtn->setFocus(true); + + mAvatarNameCacheConnection = LLAvatarNameCache::get(mAvatarID, boost::bind(&LLFloaterProfilePermissions::onAvatarNameCache, this, _1, _2)); +} + +void LLFloaterProfilePermissions::draw() +{ + // drawFrustum + LLView *owner = mOwnerHandle.get(); + static LLCachedControl max_opacity(gSavedSettings, "PickerContextOpacity", 0.4f); + drawConeToOwner(mContextConeOpacity, max_opacity, owner); + LLFloater::draw(); +} + +void LLFloaterProfilePermissions::changed(U32 mask) +{ + if (mask != LLFriendObserver::ONLINE) + { + fillRightsData(); + } +} + +void LLFloaterProfilePermissions::onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name) +{ + mAvatarNameCacheConnection.disconnect(); + + LLStringUtil::format_map_t args; + args["[AGENT_NAME]"] = av_name.getDisplayName(); + std::string descritpion = getString("description_string", args); + mDescription->setValue(descritpion); +} + +void LLFloaterProfilePermissions::fillRightsData() +{ + const LLRelationship* relation = LLAvatarTracker::instance().getBuddyInfo(mAvatarID); + // If true - we are viewing friend's profile, enable check boxes and set values. + if (relation) + { + S32 rights = relation->getRightsGrantedTo(); + + bool see_online = LLRelationship::GRANT_ONLINE_STATUS & rights; + mOnlineStatus->setValue(see_online); + mMapRights->setEnabled(see_online); + mMapRights->setValue(LLRelationship::GRANT_MAP_LOCATION & rights); + mEditObjectRights->setValue(LLRelationship::GRANT_MODIFY_OBJECTS & rights); + } + else + { + closeFloater(); + LL_INFOS("ProfilePermissions") << "Floater closing since agent is no longer a friend" << LL_ENDL; + } +} + +void LLFloaterProfilePermissions::rightsConfirmationCallback(const LLSD& notification, + const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) // canceled + { + mEditObjectRights->setValue(!mEditObjectRights->getValue().asBoolean()); + } + else + { + mHasUnsavedPermChanges = true; + } +} + +void LLFloaterProfilePermissions::confirmModifyRights(bool grant) +{ + LLSD args; + args["NAME"] = LLSLURL("agent", mAvatarID, "completename").getSLURLString(); + LLNotificationsUtil::add(grant ? "GrantModifyRights" : "RevokeModifyRights", args, LLSD(), + boost::bind(&LLFloaterProfilePermissions::rightsConfirmationCallback, this, _1, _2)); +} + +void LLFloaterProfilePermissions::onCommitSeeOnlineRights() +{ + bool see_online = mOnlineStatus->getValue().asBoolean(); + mMapRights->setEnabled(see_online); + if (see_online) + { + const LLRelationship* relation = LLAvatarTracker::instance().getBuddyInfo(mAvatarID); + if (relation) + { + S32 rights = relation->getRightsGrantedTo(); + mMapRights->setValue(LLRelationship::GRANT_MAP_LOCATION & rights); + } + else + { + closeFloater(); + LL_INFOS("ProfilePermissions") << "Floater closing since agent is no longer a friend" << LL_ENDL; + } + } + else + { + mMapRights->setValue(false); + } + mHasUnsavedPermChanges = true; +} + +void LLFloaterProfilePermissions::onCommitEditRights() +{ + const LLRelationship* buddy_relationship = LLAvatarTracker::instance().getBuddyInfo(mAvatarID); + + if (!buddy_relationship) + { + LL_WARNS("ProfilePermissions") << "Trying to modify rights for non-friend avatar. Closing floater." << LL_ENDL; + closeFloater(); + return; + } + + bool allow_modify_objects = mEditObjectRights->getValue().asBoolean(); + + // if modify objects checkbox clicked + if (buddy_relationship->isRightGrantedTo( + LLRelationship::GRANT_MODIFY_OBJECTS) != allow_modify_objects) + { + confirmModifyRights(allow_modify_objects); + } +} + +void LLFloaterProfilePermissions::onApplyRights() +{ + const LLRelationship* buddy_relationship = LLAvatarTracker::instance().getBuddyInfo(mAvatarID); + + if (!buddy_relationship) + { + LL_WARNS("ProfilePermissions") << "Trying to modify rights for non-friend avatar. Skipped." << LL_ENDL; + return; + } + + S32 rights = 0; + + if (mOnlineStatus->getValue().asBoolean()) + { + rights |= LLRelationship::GRANT_ONLINE_STATUS; + } + if (mMapRights->getValue().asBoolean()) + { + rights |= LLRelationship::GRANT_MAP_LOCATION; + } + if (mEditObjectRights->getValue().asBoolean()) + { + rights |= LLRelationship::GRANT_MODIFY_OBJECTS; + } + + LLAvatarPropertiesProcessor::getInstance()->sendFriendRights(mAvatarID, rights); + + closeFloater(); +} + +void LLFloaterProfilePermissions::onCancel() +{ + closeFloater(); +} + +////////////////////////////////////////////////////////////////////////// +// LLPanelProfileSecondLife + +LLPanelProfileSecondLife::LLPanelProfileSecondLife() + : LLPanelProfilePropertiesProcessorTab() + , mAvatarNameCacheConnection() + , mHasUnsavedDescriptionChanges(false) + , mWaitingForImageUpload(false) + , mAllowPublish(false) + , mHideAge(false) +{ +} + +LLPanelProfileSecondLife::~LLPanelProfileSecondLife() +{ + if (getAvatarId().notNull()) + { + LLAvatarTracker::instance().removeParticularFriendObserver(getAvatarId(), this); + } + + if (LLVoiceClient::instanceExists()) + { + LLVoiceClient::getInstance()->removeObserver((LLVoiceClientStatusObserver*)this); + } + + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } +} + +bool LLPanelProfileSecondLife::postBuild() +{ + mGroupList = getChild("group_list"); + mShowInSearchCombo = getChild("show_in_search"); + mHideAgeCombo = getChild("hide_age"); + mSecondLifePic = getChild("2nd_life_pic"); + mSecondLifePicLayout = getChild("image_panel"); + mDescriptionEdit = getChild("sl_description_edit"); + mAgentActionMenuButton = getChild("agent_actions_menu"); + mSaveDescriptionChanges = getChild("save_description_changes"); + mDiscardDescriptionChanges = getChild("discard_description_changes"); + mCanSeeOnlineIcon = getChild("can_see_online"); + mCantSeeOnlineIcon = getChild("cant_see_online"); + mCanSeeOnMapIcon = getChild("can_see_on_map"); + mCantSeeOnMapIcon = getChild("cant_see_on_map"); + mCanEditObjectsIcon = getChild("can_edit_objects"); + mCantEditObjectsIcon = getChild("cant_edit_objects"); + + mShowInSearchCombo->setCommitCallback([this](LLUICtrl*, void*) { onShowInSearchCallback(); }, nullptr); + mHideAgeCombo->setCommitCallback([this](LLUICtrl*, void*) { onHideAgeCallback(); }, nullptr); + mGroupList->setDoubleClickCallback([this](LLUICtrl*, S32 x, S32 y, MASK mask) { LLPanelProfileSecondLife::openGroupProfile(); }); + mGroupList->setReturnCallback([this](LLUICtrl*, const LLSD&) { LLPanelProfileSecondLife::openGroupProfile(); }); + mSaveDescriptionChanges->setCommitCallback([this](LLUICtrl*, void*) { onSaveDescriptionChanges(); }, nullptr); + mDiscardDescriptionChanges->setCommitCallback([this](LLUICtrl*, void*) { onDiscardDescriptionChanges(); }, nullptr); + mDescriptionEdit->setKeystrokeCallback([this](LLTextEditor* caller) { onSetDescriptionDirty(); }); + + mCanSeeOnlineIcon->setMouseUpCallback([this](LLUICtrl*, S32 x, S32 y, MASK mask) { onShowAgentPermissionsDialog(); }); + mCantSeeOnlineIcon->setMouseUpCallback([this](LLUICtrl*, S32 x, S32 y, MASK mask) { onShowAgentPermissionsDialog(); }); + mCanSeeOnMapIcon->setMouseUpCallback([this](LLUICtrl*, S32 x, S32 y, MASK mask) { onShowAgentPermissionsDialog(); }); + mCantSeeOnMapIcon->setMouseUpCallback([this](LLUICtrl*, S32 x, S32 y, MASK mask) { onShowAgentPermissionsDialog(); }); + mCanEditObjectsIcon->setMouseUpCallback([this](LLUICtrl*, S32 x, S32 y, MASK mask) { onShowAgentPermissionsDialog(); }); + mCantEditObjectsIcon->setMouseUpCallback([this](LLUICtrl*, S32 x, S32 y, MASK mask) { onShowAgentPermissionsDialog(); }); + mSecondLifePic->setMouseUpCallback([this](LLUICtrl*, S32 x, S32 y, MASK mask) { onShowAgentProfileTexture(); }); + + return true; +} + +void LLPanelProfileSecondLife::onOpen(const LLSD& key) +{ + LLPanelProfileTab::onOpen(key); + + resetData(); + + LLUUID avatar_id = getAvatarId(); + + bool own_profile = getSelfProfile(); + + mGroupList->setShowNone(!own_profile); + + childSetVisible("notes_panel", !own_profile); + childSetVisible("settings_panel", own_profile); + childSetVisible("about_buttons_panel", own_profile); + + if (own_profile) + { + // Group list control cannot toggle ForAgent loading + // Less than ideal, but viewing own profile via search is edge case + mGroupList->enableForAgent(false); + } + + // Init menu, menu needs to be created in scope of a registar to work correctly. + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar commit; + commit.add("Profile.Commit", [this](LLUICtrl*, const LLSD& userdata) { onCommitMenu(userdata); }); + + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable; + enable.add("Profile.EnableItem", [this](LLUICtrl*, const LLSD& userdata) { return onEnableMenu(userdata); }); + enable.add("Profile.CheckItem", [this](LLUICtrl*, const LLSD& userdata) { return onCheckMenu(userdata); }); + + if (own_profile) + { + mAgentActionMenuButton->setMenu("menu_profile_self.xml", LLMenuButton::MP_BOTTOM_RIGHT); + } + else + { + // Todo: use PeopleContextMenu instead? + mAgentActionMenuButton->setMenu("menu_profile_other.xml", LLMenuButton::MP_BOTTOM_RIGHT); + } + + mDescriptionEdit->setParseHTML(!own_profile); + + if (!own_profile) + { + mVoiceStatus = LLAvatarActions::canCall() && (LLAvatarActions::isFriend(avatar_id) ? LLAvatarTracker::instance().isBuddyOnline(avatar_id) : true); + updateOnlineStatus(); + fillRightsData(); + } + + mAvatarNameCacheConnection = LLAvatarNameCache::get(getAvatarId(), boost::bind(&LLPanelProfileSecondLife::onAvatarNameCache, this, _1, _2)); +} + + +bool LLPanelProfileSecondLife::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + // Try children first + if (LLPanelProfileTab::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg) + && *accept != ACCEPT_NO) + { + return true; + } + + // No point sharing with own profile + if (getSelfProfile()) + { + return false; + } + + // Exclude fields that look like they are editable. + S32 child_x = 0; + S32 child_y = 0; + if (localPointToOtherView(x, y, &child_x, &child_y, mDescriptionEdit) + && mDescriptionEdit->pointInView(child_x, child_y)) + { + return false; + } + + if (localPointToOtherView(x, y, &child_x, &child_y, mGroupList) + && mGroupList->pointInView(child_x, child_y)) + { + return false; + } + + // Share + LLToolDragAndDrop::handleGiveDragAndDrop(getAvatarId(), + LLUUID::null, + drop, + cargo_type, + cargo_data, + accept); + return true; +} + +void LLPanelProfileSecondLife::refreshName() +{ + if (!mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection = LLAvatarNameCache::get(getAvatarId(), boost::bind(&LLPanelProfileSecondLife::onAvatarNameCache, this, _1, _2)); + } +} + +void LLPanelProfileSecondLife::resetData() +{ + resetLoading(); + + // Set default image and 1:1 dimensions for it + mSecondLifePic->setValue("Generic_Person_Large"); + + LLRect imageRect = mSecondLifePicLayout->getRect(); + mSecondLifePicLayout->reshape(imageRect.getWidth(), imageRect.getWidth()); + + setDescriptionText(LLStringUtil::null); + mGroups.clear(); + mGroupList->setGroups(mGroups); + + bool own_profile = getSelfProfile(); + mCanSeeOnlineIcon->setVisible(false); + mCantSeeOnlineIcon->setVisible(!own_profile); + mCanSeeOnMapIcon->setVisible(false); + mCantSeeOnMapIcon->setVisible(!own_profile); + mCanEditObjectsIcon->setVisible(false); + mCantEditObjectsIcon->setVisible(!own_profile); + + mCanSeeOnlineIcon->setEnabled(false); + mCantSeeOnlineIcon->setEnabled(false); + mCanSeeOnMapIcon->setEnabled(false); + mCantSeeOnMapIcon->setEnabled(false); + mCanEditObjectsIcon->setEnabled(false); + mCantEditObjectsIcon->setEnabled(false); + + childSetVisible("partner_layout", false); + childSetVisible("badge_layout", false); + childSetVisible("partner_spacer_layout", true); +} + +void LLPanelProfileSecondLife::processProperties(void* data, EAvatarProcessorType type) +{ + if (APT_PROPERTIES == type) + { + LLAvatarData* avatar_data = static_cast(data); + if (avatar_data && getAvatarId() == avatar_data->avatar_id) + { + processProfileProperties(avatar_data); + } + } +} + +void LLPanelProfileSecondLife::processProfileProperties(const LLAvatarData* avatar_data) +{ + const LLRelationship* relationship = LLAvatarTracker::instance().getBuddyInfo(getAvatarId()); + if ((relationship != NULL || gAgent.isGodlike()) && !getSelfProfile()) + { + // Relies onto friend observer to get information about online status updates. + // Once SL-17506 gets implemented, condition might need to become: + // (gAgent.isGodlike() || isRightGrantedFrom || flags & AVATAR_ONLINE) + processOnlineStatus(relationship != NULL, + gAgent.isGodlike() || relationship->isRightGrantedFrom(LLRelationship::GRANT_ONLINE_STATUS), + (avatar_data->flags & AVATAR_ONLINE)); + } + + fillCommonData(avatar_data); + + fillPartnerData(avatar_data); + + fillAccountStatus(avatar_data); + + LLAvatarData::group_list_t::const_iterator it = avatar_data->group_list.begin(); + const LLAvatarData::group_list_t::const_iterator it_end = avatar_data->group_list.end(); + + for (; it_end != it; ++it) + { + LLAvatarData::LLGroupData group_data = *it; + mGroups[group_data.group_name] = group_data.group_id; + } + + mGroupList->setGroups(mGroups); + + setLoaded(); +} + +void LLPanelProfileSecondLife::openGroupProfile() +{ + LLUUID group_id = mGroupList->getSelectedUUID(); + LLGroupActions::show(group_id); +} + +void LLPanelProfileSecondLife::onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name) +{ + mAvatarNameCacheConnection.disconnect(); + getChild("display_name")->setValue(av_name.getDisplayName()); + getChild("user_name")->setValue(av_name.getAccountName()); +} + +void LLPanelProfileSecondLife::setProfileImageUploading(bool loading) +{ + LLLoadingIndicator* indicator = getChild("image_upload_indicator"); + indicator->setVisible(loading); + if (loading) + { + indicator->start(); + } + else + { + indicator->stop(); + } + mWaitingForImageUpload = loading; +} + +void LLPanelProfileSecondLife::setProfileImageUploaded(const LLUUID &image_asset_id) +{ + mSecondLifePic->setValue(image_asset_id); + + LLFloater *floater = mFloaterProfileTextureHandle.get(); + if (floater) + { + LLFloaterProfileTexture * texture_view = dynamic_cast(floater); + texture_view->loadAsset(mSecondLifePic->getImageAssetId()); + } + + setProfileImageUploading(false); +} + +bool LLPanelProfileSecondLife::hasUnsavedChanges() +{ + LLFloater *floater = mFloaterPermissionsHandle.get(); + if (floater) + { + LLFloaterProfilePermissions* perm = dynamic_cast(floater); + if (perm && perm->hasUnsavedChanges()) + { + return true; + } + } + // if floater + return mHasUnsavedDescriptionChanges; +} + +void LLPanelProfileSecondLife::commitUnsavedChanges() +{ + LLFloater *floater = mFloaterPermissionsHandle.get(); + if (floater) + { + LLFloaterProfilePermissions* perm = dynamic_cast(floater); + if (perm && perm->hasUnsavedChanges()) + { + perm->onApplyRights(); + } + } + if (mHasUnsavedDescriptionChanges) + { + onSaveDescriptionChanges(); + } +} + +void LLPanelProfileSecondLife::fillCommonData(const LLAvatarData* avatar_data) +{ + // Refresh avatar id in cache with new info to prevent re-requests + // and to make sure icons in text will be up to date + LLAvatarIconIDCache::getInstance()->add(avatar_data->avatar_id, avatar_data->image_id); + + fillAgeData(avatar_data); + + setDescriptionText(avatar_data->about_text); + + mSecondLifePic->setValue(avatar_data->image_id); + + if (getSelfProfile()) + { + mAllowPublish = avatar_data->flags & AVATAR_ALLOW_PUBLISH; + mShowInSearchCombo->setValue(mAllowPublish); + } +} + +void LLPanelProfileSecondLife::fillPartnerData(const LLAvatarData* avatar_data) +{ + LLTextBox* partner_text_ctrl = getChild("partner_link"); + if (avatar_data->partner_id.notNull()) + { + childSetVisible("partner_layout", true); + LLStringUtil::format_map_t args; + args["[LINK]"] = LLSLURL("agent", avatar_data->partner_id, "inspect").getSLURLString(); + std::string partner_text = getString("partner_text", args); + partner_text_ctrl->setText(partner_text); + } + else + { + childSetVisible("partner_layout", false); + } +} + +void LLPanelProfileSecondLife::fillAccountStatus(const LLAvatarData* avatar_data) +{ + LLStringUtil::format_map_t args; + args["[ACCTTYPE]"] = LLAvatarPropertiesProcessor::accountType(avatar_data); + args["[PAYMENTINFO]"] = LLAvatarPropertiesProcessor::paymentInfo(avatar_data); + + std::string caption_text = getString("CaptionTextAcctInfo", args); + getChild("account_info")->setValue(caption_text); + + const S32 LINDEN_EMPLOYEE_INDEX = 3; + LLDate sl_release; + sl_release.fromYMDHMS(2003, 6, 23, 0, 0, 0); + std::string customer_lower = avatar_data->customer_type; + LLStringUtil::toLower(customer_lower); + if (avatar_data->caption_index == LINDEN_EMPLOYEE_INDEX) + { + getChild("badge_icon")->setValue("Profile_Badge_Linden"); + getChild("badge_text")->setValue(getString("BadgeLinden")); + childSetVisible("badge_layout", true); + childSetVisible("partner_spacer_layout", false); + } + else if (avatar_data->born_on < sl_release) + { + getChild("badge_icon")->setValue("Profile_Badge_Beta"); + getChild("badge_text")->setValue(getString("BadgeBeta")); + childSetVisible("badge_layout", true); + childSetVisible("partner_spacer_layout", false); + } + else if (customer_lower == "beta_lifetime") + { + getChild("badge_icon")->setValue("Profile_Badge_Beta_Lifetime"); + getChild("badge_text")->setValue(getString("BadgeBetaLifetime")); + childSetVisible("badge_layout", true); + childSetVisible("partner_spacer_layout", false); + } + else if (customer_lower == "lifetime") + { + getChild("badge_icon")->setValue("Profile_Badge_Lifetime"); + getChild("badge_text")->setValue(getString("BadgeLifetime")); + childSetVisible("badge_layout", true); + childSetVisible("partner_spacer_layout", false); + } + else if (customer_lower == "secondlifetime_premium") + { + getChild("badge_icon")->setValue("Profile_Badge_Premium_Lifetime"); + getChild("badge_text")->setValue(getString("BadgePremiumLifetime")); + childSetVisible("badge_layout", true); + childSetVisible("partner_spacer_layout", false); + } + else if (customer_lower == "secondlifetime_premium_plus") + { + getChild("badge_icon")->setValue("Profile_Badge_Pplus_Lifetime"); + getChild("badge_text")->setValue(getString("BadgePremiumPlusLifetime")); + childSetVisible("badge_layout", true); + childSetVisible("partner_spacer_layout", false); + } + else + { + childSetVisible("badge_layout", false); + childSetVisible("partner_spacer_layout", true); + } +} + +void LLPanelProfileSecondLife::fillRightsData() +{ + if (getSelfProfile()) + { + return; + } + + const LLRelationship* relation = LLAvatarTracker::instance().getBuddyInfo(getAvatarId()); + // If true - we are viewing friend's profile, enable check boxes and set values. + if (relation) + { + S32 rights = relation->getRightsGrantedTo(); + bool can_see_online = LLRelationship::GRANT_ONLINE_STATUS & rights; + bool can_see_on_map = LLRelationship::GRANT_MAP_LOCATION & rights; + bool can_edit_objects = LLRelationship::GRANT_MODIFY_OBJECTS & rights; + + mCanSeeOnlineIcon->setVisible(can_see_online); + mCantSeeOnlineIcon->setVisible(!can_see_online); + mCanSeeOnMapIcon->setVisible(can_see_on_map); + mCantSeeOnMapIcon->setVisible(!can_see_on_map); + mCanEditObjectsIcon->setVisible(can_edit_objects); + mCantEditObjectsIcon->setVisible(!can_edit_objects); + + mCanSeeOnlineIcon->setEnabled(true); + mCantSeeOnlineIcon->setEnabled(true); + mCanSeeOnMapIcon->setEnabled(true); + mCantSeeOnMapIcon->setEnabled(true); + mCanEditObjectsIcon->setEnabled(true); + mCantEditObjectsIcon->setEnabled(true); + } + else + { + mCanSeeOnlineIcon->setVisible(false); + mCantSeeOnlineIcon->setVisible(false); + mCanSeeOnMapIcon->setVisible(false); + mCantSeeOnMapIcon->setVisible(false); + mCanEditObjectsIcon->setVisible(false); + mCantEditObjectsIcon->setVisible(false); + } +} + +void LLPanelProfileSecondLife::fillAgeData(const LLAvatarData* avatar_data) +{ + // Date from server comes already converted to stl timezone, + // so display it as an UTC + 0 + bool hide_age = avatar_data->hide_age && !getSelfProfile(); + std::string name_and_date = getString(hide_age ? "date_format_short" : "date_format_full"); + LLSD args_name; + args_name["datetime"] = (S32)avatar_data->born_on.secondsSinceEpoch(); + LLStringUtil::format(name_and_date, args_name); + getChild("sl_birth_date")->setValue(name_and_date); + + LLUICtrl* userAgeCtrl = getChild("user_age"); + if (hide_age) + { + userAgeCtrl->setVisible(false); + } + else + { + std::string register_date = getString("age_format"); + LLSD args_age; + args_age["[AGE]"] = LLDateUtil::ageFromDate(avatar_data->born_on, LLDate::now()); + LLStringUtil::format(register_date, args_age); + userAgeCtrl->setValue(register_date); + } + + bool showHideAgeCombo = false; + if (getSelfProfile()) + { + if (LLAvatarPropertiesProcessor::getInstance()->isHideAgeSupportedByServer()) + { + F64 birth = avatar_data->born_on.secondsSinceEpoch(); + F64 now = LLDate::now().secondsSinceEpoch(); + if (now - birth > 365 * 24 * 60 * 60) + { + mHideAge = avatar_data->hide_age; + mHideAgeCombo->setValue(mHideAge); + showHideAgeCombo = true; + } + } + } + mHideAgeCombo->setVisible(showHideAgeCombo); +} + +void LLPanelProfileSecondLife::onImageLoaded(bool success, LLViewerFetchedTexture *imagep) +{ + LLRect imageRect = mSecondLifePicLayout->getRect(); + if (!success || imagep->getFullWidth() == imagep->getFullHeight()) + { + mSecondLifePicLayout->reshape(imageRect.getWidth(), imageRect.getWidth()); + } + else + { + // assume 3:4, for sake of firestorm + mSecondLifePicLayout->reshape(imageRect.getWidth(), imageRect.getWidth() * 3 / 4); + } +} + +// virtual, called by LLAvatarTracker +void LLPanelProfileSecondLife::changed(U32 mask) +{ + updateOnlineStatus(); + if (mask != LLFriendObserver::ONLINE) + { + fillRightsData(); + } +} + +// virtual, called by LLVoiceClient +void LLPanelProfileSecondLife::onChange(EStatusType status, const std::string &channelURI, bool proximal) +{ + if(status == STATUS_JOINING || status == STATUS_LEFT_CHANNEL) + { + return; + } + + mVoiceStatus = LLAvatarActions::canCall() && (LLAvatarActions::isFriend(getAvatarId()) ? LLAvatarTracker::instance().isBuddyOnline(getAvatarId()) : true); +} + +void LLPanelProfileSecondLife::setAvatarId(const LLUUID& avatar_id) +{ + if (avatar_id.notNull()) + { + if (getAvatarId().notNull()) + { + LLAvatarTracker::instance().removeParticularFriendObserver(getAvatarId(), this); + } + + LLPanelProfilePropertiesProcessorTab::setAvatarId(avatar_id); + + if (LLAvatarActions::isFriend(getAvatarId())) + { + LLAvatarTracker::instance().addParticularFriendObserver(getAvatarId(), this); + } + } +} + +// method was disabled according to EXT-2022. Re-enabled & improved according to EXT-3880 +void LLPanelProfileSecondLife::updateOnlineStatus() +{ + const LLRelationship* relationship = LLAvatarTracker::instance().getBuddyInfo(getAvatarId()); + if (relationship != NULL) + { + // For friend let check if he allowed me to see his status + bool online = relationship->isOnline(); + bool perm_granted = relationship->isRightGrantedFrom(LLRelationship::GRANT_ONLINE_STATUS); + processOnlineStatus(true, perm_granted, online); + } + else + { + childSetVisible("friend_layout", false); + childSetVisible("online_layout", false); + childSetVisible("offline_layout", false); + } +} + +void LLPanelProfileSecondLife::processOnlineStatus(bool is_friend, bool show_online, bool online) +{ + childSetVisible("friend_layout", is_friend); + childSetVisible("online_layout", online && show_online); + childSetVisible("offline_layout", !online && show_online); +} + +void LLPanelProfileSecondLife::setLoaded() +{ + LLPanelProfileTab::setLoaded(); + + if (getSelfProfile()) + { + mShowInSearchCombo->setEnabled(true); + if (mHideAgeCombo->getVisible()) + { + mHideAgeCombo->setEnabled(true); + } + mDescriptionEdit->setEnabled(true); + } +} + + +class LLProfileImagePicker : public LLFilePickerThread +{ +public: + LLProfileImagePicker(EProfileImageType type, LLHandle *handle); + ~LLProfileImagePicker(); + void notify(const std::vector& filenames) override; + +private: + LLHandle *mHandle; + EProfileImageType mType; +}; + +LLProfileImagePicker::LLProfileImagePicker(EProfileImageType type, LLHandle *handle) + : LLFilePickerThread(LLFilePicker::FFLOAD_IMAGE), + mHandle(handle), + mType(type) +{ +} + +LLProfileImagePicker::~LLProfileImagePicker() +{ + delete mHandle; +} + +void LLProfileImagePicker::notify(const std::vector& filenames) +{ + if (mHandle->isDead()) + { + return; + } + if (filenames.empty()) + { + return; + } + std::string file_path = filenames[0]; + if (file_path.empty()) + { + return; + } + + // generate a temp texture file for coroutine + std::string temp_file = gDirUtilp->getTempFilename(); + U32 codec = LLImageBase::getCodecFromExtension(gDirUtilp->getExtension(file_path)); + const S32 MAX_DIM = 256; + if (!LLViewerTextureList::createUploadFile(file_path, temp_file, codec, MAX_DIM)) + { + LLSD notif_args; + notif_args["REASON"] = LLImage::getLastThreadError().c_str(); + LLNotificationsUtil::add("CannotUploadTexture", notif_args); + LL_WARNS("AvatarProperties") << "Failed to upload profile image of type " << (S32)mType << ", " << notif_args["REASON"].asString() << LL_ENDL; + return; + } + + std::string cap_url = gAgent.getRegionCapability(PROFILE_IMAGE_UPLOAD_CAP); + if (cap_url.empty()) + { + LLSD args; + args["CAPABILITY"] = PROFILE_IMAGE_UPLOAD_CAP; + LLNotificationsUtil::add("RegionCapabilityRequestError", args); + LL_WARNS("AvatarProperties") << "Failed to upload profile image of type " << (S32)mType << ", no cap found" << LL_ENDL; + return; + } + + switch (mType) + { + case PROFILE_IMAGE_SL: + { + LLPanelProfileSecondLife* panel = static_cast(mHandle->get()); + panel->setProfileImageUploading(true); + } + break; + case PROFILE_IMAGE_FL: + { + LLPanelProfileFirstLife* panel = static_cast(mHandle->get()); + panel->setProfileImageUploading(true); + } + break; + } + + LLCoros::instance().launch("postAgentUserImageCoro", + boost::bind(post_profile_image_coro, cap_url, mType, temp_file, mHandle)); + + mHandle = nullptr; // transferred to post_profile_image_coro +} + +void LLPanelProfileSecondLife::onCommitMenu(const LLSD& userdata) +{ + const std::string item_name = userdata.asString(); + const LLUUID agent_id = getAvatarId(); + // todo: consider moving this into LLAvatarActions::onCommit(name, id) + // and making all other flaoters, like people menu do the same + if (item_name == "im") + { + LLAvatarActions::startIM(agent_id); + } + else if (item_name == "offer_teleport") + { + LLAvatarActions::offerTeleport(agent_id); + } + else if (item_name == "request_teleport") + { + LLAvatarActions::teleportRequest(agent_id); + } + else if (item_name == "voice_call") + { + LLAvatarActions::startCall(agent_id); + } + else if (item_name == "chat_history") + { + LLAvatarActions::viewChatHistory(agent_id); + } + else if (item_name == "add_friend") + { + LLAvatarActions::requestFriendshipDialog(agent_id); + } + else if (item_name == "remove_friend") + { + LLAvatarActions::removeFriendDialog(agent_id); + } + else if (item_name == "invite_to_group") + { + LLAvatarActions::inviteToGroup(agent_id); + } + else if (item_name == "can_show_on_map") + { + LLAvatarActions::showOnMap(agent_id); + } + else if (item_name == "share") + { + LLAvatarActions::share(agent_id); + } + else if (item_name == "pay") + { + LLAvatarActions::pay(agent_id); + } + else if (item_name == "toggle_block_agent") + { + LLAvatarActions::toggleBlock(agent_id); + } + else if (item_name == "copy_user_id") + { + LLWString wstr = utf8str_to_wstring(getAvatarId().asString()); + LLClipboard::instance().copyToClipboard(wstr, 0, wstr.size()); + } + else if (item_name == "agent_permissions") + { + onShowAgentPermissionsDialog(); + } + else if (item_name == "copy_display_name" + || item_name == "copy_username") + { + LLAvatarName av_name; + if (!LLAvatarNameCache::get(getAvatarId(), &av_name)) + { + // shouldn't happen, option is supposed to be invisible while name is fetching + LL_WARNS() << "Failed to get agent data" << LL_ENDL; + return; + } + LLWString wstr; + if (item_name == "copy_display_name") + { + wstr = utf8str_to_wstring(av_name.getDisplayName(true)); + } + else if (item_name == "copy_username") + { + wstr = utf8str_to_wstring(av_name.getUserName()); + } + LLClipboard::instance().copyToClipboard(wstr, 0, wstr.size()); + } + else if (item_name == "edit_display_name") + { + LLAvatarNameCache::get(getAvatarId(), boost::bind(&LLPanelProfileSecondLife::onAvatarNameCacheSetName, this, _1, _2)); + LLFirstUse::setDisplayName(false); + } + else if (item_name == "edit_partner") + { + std::string url = "https://[GRID]/my/account/partners.php"; + LLSD subs; + url = LLWeb::expandURLSubstitutions(url, subs); + LLUrlAction::openURL(url); + } + else if (item_name == "upload_photo") + { + (new LLProfileImagePicker(PROFILE_IMAGE_SL, new LLHandle(LLPanel::getHandle())))->getFile(); + + LLFloater* floaterp = mFloaterTexturePickerHandle.get(); + if (floaterp) + { + floaterp->closeFloater(); + } + } + else if (item_name == "change_photo") + { + onShowTexturePicker(); + } + else if (item_name == "remove_photo") + { + onCommitProfileImage(LLUUID::null); + + LLFloater* floaterp = mFloaterTexturePickerHandle.get(); + if (floaterp) + { + floaterp->closeFloater(); + } + } +} + +bool LLPanelProfileSecondLife::onEnableMenu(const LLSD& userdata) +{ + const std::string item_name = userdata.asString(); + const LLUUID agent_id = getAvatarId(); + if (item_name == "offer_teleport" || item_name == "request_teleport") + { + return LLAvatarActions::canOfferTeleport(agent_id); + } + else if (item_name == "voice_call") + { + return mVoiceStatus; + } + else if (item_name == "chat_history") + { + return LLLogChat::isTranscriptExist(agent_id); + } + else if (item_name == "add_friend") + { + return !LLAvatarActions::isFriend(agent_id); + } + else if (item_name == "remove_friend") + { + return LLAvatarActions::isFriend(agent_id); + } + else if (item_name == "can_show_on_map") + { + return (LLAvatarTracker::instance().isBuddyOnline(agent_id) && is_agent_mappable(agent_id)) + || gAgent.isGodlike(); + } + else if (item_name == "toggle_block_agent") + { + return LLAvatarActions::canBlock(agent_id); + } + else if (item_name == "agent_permissions") + { + return LLAvatarActions::isFriend(agent_id); + } + else if (item_name == "copy_display_name" + || item_name == "copy_username") + { + return !mAvatarNameCacheConnection.connected(); + } + else if (item_name == "upload_photo" + || item_name == "change_photo") + { + std::string cap_url = gAgent.getRegionCapability(PROFILE_IMAGE_UPLOAD_CAP); + return !cap_url.empty() && !mWaitingForImageUpload && getIsLoaded(); + } + else if (item_name == "remove_photo") + { + std::string cap_url = gAgent.getRegionCapability(PROFILE_PROPERTIES_CAP); + return mSecondLifePic->getImageAssetId().notNull() && !cap_url.empty() && !mWaitingForImageUpload && getIsLoaded(); + } + + return false; +} + +bool LLPanelProfileSecondLife::onCheckMenu(const LLSD& userdata) +{ + const std::string item_name = userdata.asString(); + const LLUUID agent_id = getAvatarId(); + if (item_name == "toggle_block_agent") + { + return LLAvatarActions::isBlocked(agent_id); + } + return false; +} + +void LLPanelProfileSecondLife::onAvatarNameCacheSetName(const LLUUID& agent_id, const LLAvatarName& av_name) +{ + if (av_name.getDisplayName().empty()) + { + // something is wrong, tell user to try again later + LLNotificationsUtil::add("SetDisplayNameFailedGeneric"); + return; + } + + LL_INFOS("LegacyProfile") << "name-change now " << LLDate::now() << " next_update " + << LLDate(av_name.mNextUpdate) << LL_ENDL; + F64 now_secs = LLDate::now().secondsSinceEpoch(); + + if (now_secs < av_name.mNextUpdate) + { + // if the update time is more than a year in the future, it means updates have been blocked + // show a more general message + static const S32 YEAR = 60*60*24*365; + if (now_secs + YEAR < av_name.mNextUpdate) + { + LLNotificationsUtil::add("SetDisplayNameBlocked"); + return; + } + } + + LLFloaterReg::showInstance("display_name"); +} + +void LLPanelProfileSecondLife::setDescriptionText(const std::string &text) +{ + mSaveDescriptionChanges->setEnabled(false); + mDiscardDescriptionChanges->setEnabled(false); + mHasUnsavedDescriptionChanges = false; + + mDescriptionText = text; + mDescriptionEdit->setValue(mDescriptionText); +} + +void LLPanelProfileSecondLife::onSetDescriptionDirty() +{ + mSaveDescriptionChanges->setEnabled(true); + mDiscardDescriptionChanges->setEnabled(true); + mHasUnsavedDescriptionChanges = true; +} + +void LLPanelProfileSecondLife::onShowInSearchCallback() +{ + bool value = mShowInSearchCombo->getValue().asInteger(); + if (value == mAllowPublish) + return; + + mAllowPublish = value; + saveAgentUserInfoCoro("allow_publish", value); +} + +void LLPanelProfileSecondLife::onHideAgeCallback() +{ + bool value = mHideAgeCombo->getValue().asInteger(); + if (value == mHideAge) + return; + + mHideAge = value; + saveAgentUserInfoCoro("hide_age", value); +} + +void LLPanelProfileSecondLife::onSaveDescriptionChanges() +{ + mDescriptionText = mDescriptionEdit->getValue().asString(); + saveAgentUserInfoCoro("sl_about_text", mDescriptionText); + + mSaveDescriptionChanges->setEnabled(false); + mDiscardDescriptionChanges->setEnabled(false); + mHasUnsavedDescriptionChanges = false; +} + +void LLPanelProfileSecondLife::onDiscardDescriptionChanges() +{ + setDescriptionText(mDescriptionText); +} + +void LLPanelProfileSecondLife::onShowAgentPermissionsDialog() +{ + LLFloater *floater = mFloaterPermissionsHandle.get(); + if (!floater) + { + LLFloater* parent_floater = gFloaterView->getParentFloater(this); + if (parent_floater) + { + LLFloaterProfilePermissions * perms = new LLFloaterProfilePermissions(parent_floater, getAvatarId()); + mFloaterPermissionsHandle = perms->getHandle(); + perms->openFloater(); + perms->setVisibleAndFrontmost(true); + + parent_floater->addDependentFloater(mFloaterPermissionsHandle); + } + } + else // already open + { + floater->setMinimized(false); + floater->setVisibleAndFrontmost(true); + } +} + +void LLPanelProfileSecondLife::onShowAgentProfileTexture() +{ + if (!getIsLoaded()) + { + return; + } + + LLFloater *floater = mFloaterProfileTextureHandle.get(); + if (!floater) + { + LLFloater* parent_floater = gFloaterView->getParentFloater(this); + if (parent_floater) + { + LLFloaterProfileTexture * texture_view = new LLFloaterProfileTexture(parent_floater); + mFloaterProfileTextureHandle = texture_view->getHandle(); + if (mSecondLifePic->getImageAssetId().notNull()) + { + texture_view->loadAsset(mSecondLifePic->getImageAssetId()); + } + else + { + texture_view->resetAsset(); + } + texture_view->openFloater(); + texture_view->setVisibleAndFrontmost(true); + + parent_floater->addDependentFloater(mFloaterProfileTextureHandle); + } + } + else // already open + { + LLFloaterProfileTexture * texture_view = dynamic_cast(floater); + texture_view->setMinimized(false); + texture_view->setVisibleAndFrontmost(true); + if (mSecondLifePic->getImageAssetId().notNull()) + { + texture_view->loadAsset(mSecondLifePic->getImageAssetId()); + } + else + { + texture_view->resetAsset(); + } + } +} + +void LLPanelProfileSecondLife::onShowTexturePicker() +{ + LLFloater* floaterp = mFloaterTexturePickerHandle.get(); + + // Show the dialog + if (!floaterp) + { + LLFloater* parent_floater = gFloaterView->getParentFloater(this); + if (parent_floater) + { + // because inventory construction is somewhat slow + getWindow()->setCursor(UI_CURSOR_WAIT); + LLFloaterTexturePicker* texture_floaterp = new LLFloaterTexturePicker( + this, + mSecondLifePic->getImageAssetId(), + LLUUID::null, + mSecondLifePic->getImageAssetId(), + false, + false, + "SELECT PHOTO", + PERM_NONE, + PERM_NONE, + false, + NULL, + PICK_TEXTURE); + + mFloaterTexturePickerHandle = texture_floaterp->getHandle(); + + texture_floaterp->setOnFloaterCommitCallback([this](LLTextureCtrl::ETexturePickOp op, LLPickerSource source, const LLUUID& asset_id, const LLUUID&, const LLUUID&) + { + if (op == LLTextureCtrl::TEXTURE_SELECT) + { + onCommitProfileImage(asset_id); + } + }); + texture_floaterp->setLocalTextureEnabled(false); + texture_floaterp->setBakeTextureEnabled(false); + texture_floaterp->setCanApply(false, true, false); + + parent_floater->addDependentFloater(mFloaterTexturePickerHandle); + + texture_floaterp->openFloater(); + texture_floaterp->setFocus(true); + } + } + else + { + floaterp->setMinimized(false); + floaterp->setVisibleAndFrontmost(true); + } +} + +void LLPanelProfileSecondLife::onCommitProfileImage(const LLUUID& id) +{ + if (mSecondLifePic->getImageAssetId() == id) + return; + + std::function callback = [id](bool result) + { + if (result) + { + LLAvatarIconIDCache::getInstance()->add(gAgentID, id); + // Should trigger callbacks in icon controls (or request Legacy) + LLAvatarPropertiesProcessor::getInstance()->sendAvatarPropertiesRequest(gAgentID); + } + }; + + if (!saveAgentUserInfoCoro("sl_image_id", id, callback)) + return; + + mSecondLifePic->setValue(id); + + LLFloater *floater = mFloaterProfileTextureHandle.get(); + if (floater) + { + LLFloaterProfileTexture * texture_view = dynamic_cast(floater); + if (id == LLUUID::null) + { + texture_view->resetAsset(); + } + else + { + texture_view->loadAsset(id); + } + } +} + +////////////////////////////////////////////////////////////////////////// +// LLPanelProfileWeb + +LLPanelProfileWeb::LLPanelProfileWeb() + : LLPanelProfileTab() + , mWebBrowser(NULL) + , mAvatarNameCacheConnection() +{ +} + +LLPanelProfileWeb::~LLPanelProfileWeb() +{ + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } +} + +void LLPanelProfileWeb::onOpen(const LLSD& key) +{ + LLPanelProfileTab::onOpen(key); + + resetData(); + + mAvatarNameCacheConnection = LLAvatarNameCache::get(getAvatarId(), boost::bind(&LLPanelProfileWeb::onAvatarNameCache, this, _1, _2)); +} + +bool LLPanelProfileWeb::postBuild() +{ + mWebBrowser = getChild("profile_html"); + mWebBrowser->addObserver(this); + mWebBrowser->setHomePageUrl("about:blank"); + + return true; +} + +void LLPanelProfileWeb::resetData() +{ + mWebBrowser->navigateHome(); +} + +void LLPanelProfileWeb::updateData() +{ + LLUUID avatar_id = getAvatarId(); + if (!getStarted() && avatar_id.notNull() && !mURLWebProfile.empty()) + { + setIsLoading(); + + mWebBrowser->setVisible(true); + mPerformanceTimer.start(); + mWebBrowser->navigateTo(mURLWebProfile, HTTP_CONTENT_TEXT_HTML); + } +} + +void LLPanelProfileWeb::onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name) +{ + mAvatarNameCacheConnection.disconnect(); + + std::string username = av_name.getAccountName(); + if (username.empty()) + { + username = LLCacheName::buildUsername(av_name.getDisplayName()); + } + else + { + LLStringUtil::replaceChar(username, ' ', '.'); + } + + mURLWebProfile = getProfileURL(username, true); + if (mURLWebProfile.empty()) + { + return; + } + + //if the tab was opened before name was resolved, load the panel now + updateData(); +} + +void LLPanelProfileWeb::onCommitLoad(LLUICtrl* ctrl) +{ + if (!mURLHome.empty()) + { + LLSD::String valstr = ctrl->getValue().asString(); + if (valstr.empty()) + { + mWebBrowser->setVisible(true); + mPerformanceTimer.start(); + mWebBrowser->navigateTo( mURLHome, HTTP_CONTENT_TEXT_HTML ); + } + else if (valstr == "popout") + { + // open in viewer's browser, new window + LLWeb::loadURLInternal(mURLHome); + } + else if (valstr == "external") + { + // open in external browser + LLWeb::loadURLExternal(mURLHome); + } + } +} + +void LLPanelProfileWeb::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) +{ + switch(event) + { + case MEDIA_EVENT_STATUS_TEXT_CHANGED: + childSetValue("status_text", LLSD( self->getStatusText() ) ); + break; + + case MEDIA_EVENT_NAVIGATE_BEGIN: + { + if (mFirstNavigate) + { + mFirstNavigate = false; + } + else + { + mPerformanceTimer.start(); + } + } + break; + + case MEDIA_EVENT_NAVIGATE_COMPLETE: + { + LLStringUtil::format_map_t args; + args["[TIME]"] = llformat("%.2f", mPerformanceTimer.getElapsedTimeF32()); + childSetValue("status_text", LLSD( getString("LoadTime", args)) ); + + setLoaded(); + } + break; + + default: + // Having a default case makes the compiler happy. + break; + } +} + + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +LLPanelProfileFirstLife::LLPanelProfileFirstLife() + : LLPanelProfilePropertiesProcessorTab() + , mHasUnsavedChanges(false) +{ +} + +LLPanelProfileFirstLife::~LLPanelProfileFirstLife() +{ +} + +bool LLPanelProfileFirstLife::postBuild() +{ + mDescriptionEdit = getChild("fl_description_edit"); + mPicture = getChild("real_world_pic"); + + mUploadPhoto = getChild("fl_upload_image"); + mChangePhoto = getChild("fl_change_image"); + mRemovePhoto = getChild("fl_remove_image"); + mSaveChanges = getChild("fl_save_changes"); + mDiscardChanges = getChild("fl_discard_changes"); + + mUploadPhoto->setCommitCallback([this](LLUICtrl*, void*) { onUploadPhoto(); }, nullptr); + mChangePhoto->setCommitCallback([this](LLUICtrl*, void*) { onChangePhoto(); }, nullptr); + mRemovePhoto->setCommitCallback([this](LLUICtrl*, void*) { onRemovePhoto(); }, nullptr); + mSaveChanges->setCommitCallback([this](LLUICtrl*, void*) { onSaveDescriptionChanges(); }, nullptr); + mDiscardChanges->setCommitCallback([this](LLUICtrl*, void*) { onDiscardDescriptionChanges(); }, nullptr); + mDescriptionEdit->setKeystrokeCallback([this](LLTextEditor* caller) { onSetDescriptionDirty(); }); + + return true; +} + +void LLPanelProfileFirstLife::onOpen(const LLSD& key) +{ + LLPanelProfileTab::onOpen(key); + + if (!getSelfProfile()) + { + // Otherwise as the only focusable element it will be selected + mDescriptionEdit->setTabStop(false); + } + + resetData(); +} + +void LLPanelProfileFirstLife::setProfileImageUploading(bool loading) +{ + mUploadPhoto->setEnabled(!loading); + mChangePhoto->setEnabled(!loading); + mRemovePhoto->setEnabled(!loading && mPicture->getImageAssetId().notNull()); + + LLLoadingIndicator* indicator = getChild("image_upload_indicator"); + indicator->setVisible(loading); + if (loading) + { + indicator->start(); + } + else + { + indicator->stop(); + } +} + +void LLPanelProfileFirstLife::setProfileImageUploaded(const LLUUID &image_asset_id) +{ + mPicture->setValue(image_asset_id); + setProfileImageUploading(false); +} + +void LLPanelProfileFirstLife::commitUnsavedChanges() +{ + if (mHasUnsavedChanges) + { + onSaveDescriptionChanges(); + } +} + +void LLPanelProfileFirstLife::onUploadPhoto() +{ + (new LLProfileImagePicker(PROFILE_IMAGE_FL, new LLHandle(LLPanel::getHandle())))->getFile(); + + LLFloater* floaterp = mFloaterTexturePickerHandle.get(); + if (floaterp) + { + floaterp->closeFloater(); + } +} + +void LLPanelProfileFirstLife::onChangePhoto() +{ + LLFloater* floaterp = mFloaterTexturePickerHandle.get(); + + // Show the dialog + if (!floaterp) + { + LLFloater* parent_floater = gFloaterView->getParentFloater(this); + if (parent_floater) + { + // because inventory construction is somewhat slow + getWindow()->setCursor(UI_CURSOR_WAIT); + LLFloaterTexturePicker* texture_floaterp = new LLFloaterTexturePicker( + this, + mPicture->getImageAssetId(), + LLUUID::null, + mPicture->getImageAssetId(), + false, + false, + "SELECT PHOTO", + PERM_NONE, + PERM_NONE, + false, + NULL, + PICK_TEXTURE); + + mFloaterTexturePickerHandle = texture_floaterp->getHandle(); + + texture_floaterp->setOnFloaterCommitCallback([this](LLTextureCtrl::ETexturePickOp op, LLPickerSource source, const LLUUID& asset_id, const LLUUID&, const LLUUID&) + { + if (op == LLTextureCtrl::TEXTURE_SELECT) + { + onCommitPhoto(asset_id); + } + }); + texture_floaterp->setLocalTextureEnabled(false); + texture_floaterp->setCanApply(false, true, false); + + parent_floater->addDependentFloater(mFloaterTexturePickerHandle); + + texture_floaterp->openFloater(); + texture_floaterp->setFocus(true); + } + } + else + { + floaterp->setMinimized(false); + floaterp->setVisibleAndFrontmost(true); + } +} + +void LLPanelProfileFirstLife::onRemovePhoto() +{ + onCommitPhoto(LLUUID::null); + + LLFloater* floaterp = mFloaterTexturePickerHandle.get(); + if (floaterp) + { + floaterp->closeFloater(); + } +} + +void LLPanelProfileFirstLife::onCommitPhoto(const LLUUID& id) +{ + if (mPicture->getImageAssetId() == id) + return; + + if (!saveAgentUserInfoCoro("fl_image_id", id)) + return; + + mPicture->setValue(id); + + mRemovePhoto->setEnabled(id.notNull()); +} + +void LLPanelProfileFirstLife::setDescriptionText(const std::string &text) +{ + mSaveChanges->setEnabled(false); + mDiscardChanges->setEnabled(false); + mHasUnsavedChanges = false; + + mCurrentDescription = text; + mDescriptionEdit->setValue(mCurrentDescription); +} + +void LLPanelProfileFirstLife::onSetDescriptionDirty() +{ + mSaveChanges->setEnabled(true); + mDiscardChanges->setEnabled(true); + mHasUnsavedChanges = true; +} + +void LLPanelProfileFirstLife::onSaveDescriptionChanges() +{ + mCurrentDescription = mDescriptionEdit->getValue().asString(); + saveAgentUserInfoCoro("fl_about_text", mCurrentDescription); + + mSaveChanges->setEnabled(false); + mDiscardChanges->setEnabled(false); + mHasUnsavedChanges = false; +} + +void LLPanelProfileFirstLife::onDiscardDescriptionChanges() +{ + setDescriptionText(mCurrentDescription); +} + +void LLPanelProfileFirstLife::processProperties(void* data, EAvatarProcessorType type) +{ + if (APT_PROPERTIES == type) + { + LLAvatarData* avatar_data = static_cast(data); + if (avatar_data && getAvatarId() == avatar_data->avatar_id) + { + processProperties(avatar_data); + } + } +} + +void LLPanelProfileFirstLife::processProperties(const LLAvatarData* avatar_data) +{ + setDescriptionText(avatar_data->fl_about_text); + + mPicture->setValue(avatar_data->fl_image_id); + + setLoaded(); +} + +void LLPanelProfileFirstLife::resetData() +{ + setDescriptionText(std::string()); + mPicture->setValue(LLUUID::null); + + mUploadPhoto->setVisible(getSelfProfile()); + mChangePhoto->setVisible(getSelfProfile()); + mRemovePhoto->setVisible(getSelfProfile()); + mSaveChanges->setVisible(getSelfProfile()); + mDiscardChanges->setVisible(getSelfProfile()); +} + +void LLPanelProfileFirstLife::setLoaded() +{ + LLPanelProfileTab::setLoaded(); + + if (getSelfProfile()) + { + mDescriptionEdit->setEnabled(true); + mPicture->setEnabled(true); + mRemovePhoto->setEnabled(mPicture->getImageAssetId().notNull()); + } +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +LLPanelProfileNotes::LLPanelProfileNotes() +: LLPanelProfilePropertiesProcessorTab() + , mHasUnsavedChanges(false) +{ + +} + +LLPanelProfileNotes::~LLPanelProfileNotes() +{ +} + +void LLPanelProfileNotes::commitUnsavedChanges() +{ + if (mHasUnsavedChanges) + { + onSaveNotesChanges(); + } +} + +bool LLPanelProfileNotes::postBuild() +{ + mNotesEditor = getChild("notes_edit"); + mSaveChanges = getChild("notes_save_changes"); + mDiscardChanges = getChild("notes_discard_changes"); + + mSaveChanges->setCommitCallback([this](LLUICtrl*, void*) { onSaveNotesChanges(); }, nullptr); + mDiscardChanges->setCommitCallback([this](LLUICtrl*, void*) { onDiscardNotesChanges(); }, nullptr); + mNotesEditor->setKeystrokeCallback([this](LLTextEditor* caller) { onSetNotesDirty(); }); + + return true; +} + +void LLPanelProfileNotes::onOpen(const LLSD& key) +{ + LLPanelProfileTab::onOpen(key); + + resetData(); +} + +void LLPanelProfileNotes::setNotesText(const std::string &text) +{ + mSaveChanges->setEnabled(false); + mDiscardChanges->setEnabled(false); + mHasUnsavedChanges = false; + + mCurrentNotes = text; + mNotesEditor->setValue(mCurrentNotes); +} + +void LLPanelProfileNotes::onSetNotesDirty() +{ + mSaveChanges->setEnabled(true); + mDiscardChanges->setEnabled(true); + mHasUnsavedChanges = true; +} + +void LLPanelProfileNotes::onSaveNotesChanges() +{ + mCurrentNotes = mNotesEditor->getValue().asString(); + saveAgentUserInfoCoro("notes", mCurrentNotes); + + mSaveChanges->setEnabled(false); + mDiscardChanges->setEnabled(false); + mHasUnsavedChanges = false; +} + +void LLPanelProfileNotes::onDiscardNotesChanges() +{ + setNotesText(mCurrentNotes); +} + +void LLPanelProfileNotes::processProperties(void* data, EAvatarProcessorType type) +{ + if (APT_PROPERTIES == type) + { + LLAvatarData* avatar_data = static_cast(data); + if (avatar_data && getAvatarId() == avatar_data->avatar_id) + { + processProperties(avatar_data); + } + } +} + +void LLPanelProfileNotes::processProperties(const LLAvatarData* avatar_data) +{ + setNotesText(avatar_data->notes); + mNotesEditor->setEnabled(true); + setLoaded(); +} + +void LLPanelProfileNotes::resetData() +{ + resetLoading(); + setNotesText(std::string()); +} + + +////////////////////////////////////////////////////////////////////////// +// LLPanelProfile + +LLPanelProfile::LLPanelProfile() + : LLPanelProfileTab() +{ +} + +LLPanelProfile::~LLPanelProfile() +{ +} + +bool LLPanelProfile::postBuild() +{ + return true; +} + +void LLPanelProfile::onTabChange() +{ + LLPanelProfileTab* active_panel = dynamic_cast(mTabContainer->getCurrentPanel()); + if (active_panel) + { + active_panel->updateData(); + } +} + +void LLPanelProfile::onOpen(const LLSD& key) +{ + LLUUID avatar_id = key["id"].asUUID(); + + // Don't reload the same profile + if (getAvatarId() == avatar_id) + { + return; + } + + LLPanelProfileTab::onOpen(avatar_id); + + mTabContainer = getChild("panel_profile_tabs"); + mPanelSecondlife = findChild(PANEL_SECONDLIFE); + mPanelWeb = findChild(PANEL_WEB); + mPanelPicks = findChild(PANEL_PICKS); + mPanelClassifieds = findChild(PANEL_CLASSIFIEDS); + mPanelFirstlife = findChild(PANEL_FIRSTLIFE); + mPanelNotes = findChild(PANEL_NOTES); + + mPanelSecondlife->onOpen(avatar_id); + mPanelWeb->onOpen(avatar_id); + mPanelPicks->onOpen(avatar_id); + mPanelClassifieds->onOpen(avatar_id); + mPanelFirstlife->onOpen(avatar_id); + mPanelNotes->onOpen(avatar_id); + + // Always request the base profile info + resetLoading(); + updateData(); + + // Some tabs only request data when opened + mTabContainer->setCommitCallback(boost::bind(&LLPanelProfile::onTabChange, this)); +} + +void LLPanelProfile::updateData() +{ + LLUUID avatar_id = getAvatarId(); + // Todo: getIsloading functionality needs to be expanded to + // include 'inited' or 'data_provided' state to not rerequest + if (!getStarted() && avatar_id.notNull()) + { + setIsLoading(); + + mPanelSecondlife->setIsLoading(); + mPanelPicks->setIsLoading(); + mPanelFirstlife->setIsLoading(); + mPanelNotes->setIsLoading(); + + LLAvatarPropertiesProcessor::getInstance()->sendAvatarPropertiesRequest(getAvatarId()); + } +} + +void LLPanelProfile::refreshName() +{ + mPanelSecondlife->refreshName(); +} + +void LLPanelProfile::createPick(const LLPickData &data) +{ + mTabContainer->selectTabPanel(mPanelPicks); + mPanelPicks->createPick(data); +} + +void LLPanelProfile::showPick(const LLUUID& pick_id) +{ + if (pick_id.notNull()) + { + mPanelPicks->selectPick(pick_id); + } + mTabContainer->selectTabPanel(mPanelPicks); +} + +bool LLPanelProfile::isPickTabSelected() +{ + return (mTabContainer->getCurrentPanel() == mPanelPicks); +} + +bool LLPanelProfile::isNotesTabSelected() +{ + return (mTabContainer->getCurrentPanel() == mPanelNotes); +} + +bool LLPanelProfile::hasUnsavedChanges() +{ + return mPanelSecondlife->hasUnsavedChanges() + || mPanelPicks->hasUnsavedChanges() + || mPanelClassifieds->hasUnsavedChanges() + || mPanelFirstlife->hasUnsavedChanges() + || mPanelNotes->hasUnsavedChanges(); +} + +bool LLPanelProfile::hasUnpublishedClassifieds() +{ + return mPanelClassifieds->hasNewClassifieds(); +} + +void LLPanelProfile::commitUnsavedChanges() +{ + mPanelSecondlife->commitUnsavedChanges(); + mPanelPicks->commitUnsavedChanges(); + mPanelClassifieds->commitUnsavedChanges(); + mPanelFirstlife->commitUnsavedChanges(); + mPanelNotes->commitUnsavedChanges(); +} + +void LLPanelProfile::showClassified(const LLUUID& classified_id, bool edit) +{ + if (classified_id.notNull()) + { + mPanelClassifieds->selectClassified(classified_id, edit); + } + mTabContainer->selectTabPanel(mPanelClassifieds); +} + +void LLPanelProfile::createClassified() +{ + mPanelClassifieds->createClassified(); + mTabContainer->selectTabPanel(mPanelClassifieds); +} + diff --git a/indra/newview/llpanelprofile.h b/indra/newview/llpanelprofile.h index 2d39d4004b..7fa9690530 100644 --- a/indra/newview/llpanelprofile.h +++ b/indra/newview/llpanelprofile.h @@ -1,383 +1,383 @@ -/** -* @file llpanelprofile.h -* @brief Profile panel -* -* $LicenseInfo:firstyear=2022&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2022, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#ifndef LL_LLPANELPROFILE_H -#define LL_LLPANELPROFILE_H - -#include "llavatarpropertiesprocessor.h" -#include "llcallingcard.h" -#include "llfloater.h" -#include "llpanel.h" -#include "llpanelavatar.h" -#include "llmediactrl.h" -#include "llvoiceclient.h" - -// class LLPanelProfileClassifieds; -// class LLTabContainer; - -// class LLPanelProfileSecondLife; -// class LLPanelProfileWeb; -// class LLPanelProfilePicks; -// class LLPanelProfileFirstLife; -// class LLPanelProfileNotes; - -class LLAvatarName; -class LLButton; -class LLCheckBoxCtrl; -class LLComboBox; -class LLIconCtrl; -class LLTabContainer; -class LLTextBox; -class LLTextureCtrl; -class LLMediaCtrl; -class LLGroupList; -class LLTextBase; -class LLMenuButton; -class LLLineEditor; -class LLTextEditor; -class LLPanelProfileClassifieds; -class LLPanelProfilePicks; -class LLProfileImageCtrl; -class LLViewerFetchedTexture; - - -/** -* Panel for displaying Avatar's second life related info. -*/ -class LLPanelProfileSecondLife - : public LLPanelProfilePropertiesProcessorTab - , public LLFriendObserver - , public LLVoiceClientStatusObserver -{ -public: - LLPanelProfileSecondLife(); - /*virtual*/ ~LLPanelProfileSecondLife(); - - void onOpen(const LLSD& key) override; - - bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) override; - - /** - * LLFriendObserver trigger - */ - void changed(U32 mask) override; - - // Implements LLVoiceClientStatusObserver::onChange() to enable the call - // button when voice is available - void onChange(EStatusType status, const std::string &channelURI, bool proximal) override; - - void setAvatarId(const LLUUID& avatar_id) override; - - bool postBuild() override; - - void resetData() override; - - void refreshName(); - - void onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name); - - void setProfileImageUploading(bool loading); - void setProfileImageUploaded(const LLUUID &image_asset_id); - - bool hasUnsavedChanges() override; - void commitUnsavedChanges() override; - - void processProperties(void* data, EAvatarProcessorType type) override; - -protected: - /** - * Process profile related data received from server. - */ - void processProfileProperties(const LLAvatarData* avatar_data); - - /** - * Fills common for Avatar profile and My Profile fields. - */ - void fillCommonData(const LLAvatarData* avatar_data); - - /** - * Fills partner data. - */ - void fillPartnerData(const LLAvatarData* avatar_data); - - /** - * Fills account status. - */ - void fillAccountStatus(const LLAvatarData* avatar_data); - - /** - * Sets permissions specific icon - */ - void fillRightsData(); - - /** - * Fills user name, display name, age. - */ - void fillAgeData(const LLAvatarData* avatar_data); - - void onImageLoaded(bool success, LLViewerFetchedTexture *imagep); - - /** - * Displays avatar's online status if possible. - * - * Requirements from EXT-3880: - * For friends: - * - Online when online and privacy settings allow to show - * - Offline when offline and privacy settings allow to show - * - Else: nothing - * For other avatars: - * - Online when online and was not set in Preferences/"Only Friends & Groups can see when I am online" - * - Else: Offline - */ - void updateOnlineStatus(); - void processOnlineStatus(bool is_friend, bool show_online, bool online); - -private: - void setLoaded() override; - void onCommitMenu(const LLSD& userdata); - bool onEnableMenu(const LLSD& userdata); - bool onCheckMenu(const LLSD& userdata); - void onAvatarNameCacheSetName(const LLUUID& id, const LLAvatarName& av_name); - - void setDescriptionText(const std::string &text); - void onSetDescriptionDirty(); - void onShowInSearchCallback(); - void onHideAgeCallback(); - void onSaveDescriptionChanges(); - void onDiscardDescriptionChanges(); - void onShowAgentPermissionsDialog(); - void onShowAgentProfileTexture(); - void onShowTexturePicker(); - void onCommitProfileImage(const LLUUID& id); - -private: - typedef std::map group_map_t; - group_map_t mGroups; - void openGroupProfile(); - - LLGroupList* mGroupList; - LLComboBox* mShowInSearchCombo; - LLComboBox* mHideAgeCombo; - LLProfileImageCtrl* mSecondLifePic; - LLPanel* mSecondLifePicLayout; - LLTextEditor* mDescriptionEdit; - LLMenuButton* mAgentActionMenuButton; - LLButton* mSaveDescriptionChanges; - LLButton* mDiscardDescriptionChanges; - LLIconCtrl* mCanSeeOnlineIcon; - LLIconCtrl* mCantSeeOnlineIcon; - LLIconCtrl* mCanSeeOnMapIcon; - LLIconCtrl* mCantSeeOnMapIcon; - LLIconCtrl* mCanEditObjectsIcon; - LLIconCtrl* mCantEditObjectsIcon; - - LLHandle mFloaterPermissionsHandle; - LLHandle mFloaterProfileTextureHandle; - LLHandle mFloaterTexturePickerHandle; - - bool mHasUnsavedDescriptionChanges; - bool mVoiceStatus; - bool mWaitingForImageUpload; - bool mAllowPublish; - bool mHideAge; - std::string mDescriptionText; - boost::signals2::connection mAvatarNameCacheConnection; -}; - - -/** -* Panel for displaying Avatar's web profile and home page. -*/ -class LLPanelProfileWeb - : public LLPanelProfileTab - , public LLViewerMediaObserver -{ -public: - LLPanelProfileWeb(); - /*virtual*/ ~LLPanelProfileWeb(); - - void onOpen(const LLSD& key) override; - - bool postBuild() override; - - void resetData() override; - - /** - * Loads web profile. - */ - void updateData() override; - - void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) override; - - void onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name); - -protected: - void onCommitLoad(LLUICtrl* ctrl); - -private: - std::string mURLHome; - std::string mURLWebProfile; - LLMediaCtrl* mWebBrowser; - - LLFrameTimer mPerformanceTimer; - bool mFirstNavigate; - - boost::signals2::connection mAvatarNameCacheConnection; -}; - -/** -* Panel for displaying Avatar's first life related info. -*/ -class LLPanelProfileFirstLife - : public LLPanelProfilePropertiesProcessorTab -{ -public: - LLPanelProfileFirstLife(); - /*virtual*/ ~LLPanelProfileFirstLife(); - - void onOpen(const LLSD& key) override; - - bool postBuild() override; - - void processProperties(void* data, EAvatarProcessorType type) override; - void processProperties(const LLAvatarData* avatar_data); - - void resetData() override; - - void setProfileImageUploading(bool loading); - void setProfileImageUploaded(const LLUUID &image_asset_id); - - bool hasUnsavedChanges() override { return mHasUnsavedChanges; } - void commitUnsavedChanges() override; - -protected: - void setLoaded() override; - - void onUploadPhoto(); - void onChangePhoto(); - void onRemovePhoto(); - void onCommitPhoto(const LLUUID& id); - void setDescriptionText(const std::string &text); - void onSetDescriptionDirty(); - void onSaveDescriptionChanges(); - void onDiscardDescriptionChanges(); - - LLTextEditor* mDescriptionEdit; - LLProfileImageCtrl* mPicture; - LLButton* mUploadPhoto; - LLButton* mChangePhoto; - LLButton* mRemovePhoto; - LLButton* mSaveChanges; - LLButton* mDiscardChanges; - - LLHandle mFloaterTexturePickerHandle; - - std::string mCurrentDescription; - bool mHasUnsavedChanges; -}; - -/** - * Panel for displaying Avatar's notes and modifying friend's rights. - */ -class LLPanelProfileNotes - : public LLPanelProfilePropertiesProcessorTab -{ -public: - LLPanelProfileNotes(); - /*virtual*/ ~LLPanelProfileNotes(); - - void onOpen(const LLSD& key) override; - - bool postBuild() override; - - void processProperties(void* data, EAvatarProcessorType type) override; - void processProperties(const LLAvatarData* avatar_data); - - void resetData() override; - - bool hasUnsavedChanges() override { return mHasUnsavedChanges; } - void commitUnsavedChanges() override; - -protected: - void setNotesText(const std::string &text); - void onSetNotesDirty(); - void onSaveNotesChanges(); - void onDiscardNotesChanges(); - - LLTextEditor* mNotesEditor; - LLButton* mSaveChanges; - LLButton* mDiscardChanges; - - std::string mCurrentNotes; - bool mHasUnsavedChanges; -}; - - -/** -* Container panel for the profile tabs -*/ -class LLPanelProfile - : public LLPanelProfileTab -{ -public: - LLPanelProfile(); - /*virtual*/ ~LLPanelProfile(); - - bool postBuild() override; - - void updateData() override; - void refreshName(); - - void onOpen(const LLSD& key) override; - - void createPick(const LLPickData &data); - void showPick(const LLUUID& pick_id = LLUUID::null); - bool isPickTabSelected(); - bool isNotesTabSelected(); - bool hasUnsavedChanges() override; - bool hasUnpublishedClassifieds(); - void commitUnsavedChanges() override; - - void showClassified(const LLUUID& classified_id = LLUUID::null, bool edit = false); - void createClassified(); - -private: - void onTabChange(); - - LLPanelProfileSecondLife* mPanelSecondlife; - LLPanelProfileWeb* mPanelWeb; - LLPanelProfilePicks* mPanelPicks; - LLPanelProfileClassifieds* mPanelClassifieds; - LLPanelProfileFirstLife* mPanelFirstlife; - LLPanelProfileNotes* mPanelNotes; - LLTabContainer* mTabContainer; -}; - -#endif //LL_LLPANELPROFILE_H +/** +* @file llpanelprofile.h +* @brief Profile panel +* +* $LicenseInfo:firstyear=2022&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2022, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#ifndef LL_LLPANELPROFILE_H +#define LL_LLPANELPROFILE_H + +#include "llavatarpropertiesprocessor.h" +#include "llcallingcard.h" +#include "llfloater.h" +#include "llpanel.h" +#include "llpanelavatar.h" +#include "llmediactrl.h" +#include "llvoiceclient.h" + +// class LLPanelProfileClassifieds; +// class LLTabContainer; + +// class LLPanelProfileSecondLife; +// class LLPanelProfileWeb; +// class LLPanelProfilePicks; +// class LLPanelProfileFirstLife; +// class LLPanelProfileNotes; + +class LLAvatarName; +class LLButton; +class LLCheckBoxCtrl; +class LLComboBox; +class LLIconCtrl; +class LLTabContainer; +class LLTextBox; +class LLTextureCtrl; +class LLMediaCtrl; +class LLGroupList; +class LLTextBase; +class LLMenuButton; +class LLLineEditor; +class LLTextEditor; +class LLPanelProfileClassifieds; +class LLPanelProfilePicks; +class LLProfileImageCtrl; +class LLViewerFetchedTexture; + + +/** +* Panel for displaying Avatar's second life related info. +*/ +class LLPanelProfileSecondLife + : public LLPanelProfilePropertiesProcessorTab + , public LLFriendObserver + , public LLVoiceClientStatusObserver +{ +public: + LLPanelProfileSecondLife(); + /*virtual*/ ~LLPanelProfileSecondLife(); + + void onOpen(const LLSD& key) override; + + bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) override; + + /** + * LLFriendObserver trigger + */ + void changed(U32 mask) override; + + // Implements LLVoiceClientStatusObserver::onChange() to enable the call + // button when voice is available + void onChange(EStatusType status, const std::string &channelURI, bool proximal) override; + + void setAvatarId(const LLUUID& avatar_id) override; + + bool postBuild() override; + + void resetData() override; + + void refreshName(); + + void onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name); + + void setProfileImageUploading(bool loading); + void setProfileImageUploaded(const LLUUID &image_asset_id); + + bool hasUnsavedChanges() override; + void commitUnsavedChanges() override; + + void processProperties(void* data, EAvatarProcessorType type) override; + +protected: + /** + * Process profile related data received from server. + */ + void processProfileProperties(const LLAvatarData* avatar_data); + + /** + * Fills common for Avatar profile and My Profile fields. + */ + void fillCommonData(const LLAvatarData* avatar_data); + + /** + * Fills partner data. + */ + void fillPartnerData(const LLAvatarData* avatar_data); + + /** + * Fills account status. + */ + void fillAccountStatus(const LLAvatarData* avatar_data); + + /** + * Sets permissions specific icon + */ + void fillRightsData(); + + /** + * Fills user name, display name, age. + */ + void fillAgeData(const LLAvatarData* avatar_data); + + void onImageLoaded(bool success, LLViewerFetchedTexture *imagep); + + /** + * Displays avatar's online status if possible. + * + * Requirements from EXT-3880: + * For friends: + * - Online when online and privacy settings allow to show + * - Offline when offline and privacy settings allow to show + * - Else: nothing + * For other avatars: + * - Online when online and was not set in Preferences/"Only Friends & Groups can see when I am online" + * - Else: Offline + */ + void updateOnlineStatus(); + void processOnlineStatus(bool is_friend, bool show_online, bool online); + +private: + void setLoaded() override; + void onCommitMenu(const LLSD& userdata); + bool onEnableMenu(const LLSD& userdata); + bool onCheckMenu(const LLSD& userdata); + void onAvatarNameCacheSetName(const LLUUID& id, const LLAvatarName& av_name); + + void setDescriptionText(const std::string &text); + void onSetDescriptionDirty(); + void onShowInSearchCallback(); + void onHideAgeCallback(); + void onSaveDescriptionChanges(); + void onDiscardDescriptionChanges(); + void onShowAgentPermissionsDialog(); + void onShowAgentProfileTexture(); + void onShowTexturePicker(); + void onCommitProfileImage(const LLUUID& id); + +private: + typedef std::map group_map_t; + group_map_t mGroups; + void openGroupProfile(); + + LLGroupList* mGroupList; + LLComboBox* mShowInSearchCombo; + LLComboBox* mHideAgeCombo; + LLProfileImageCtrl* mSecondLifePic; + LLPanel* mSecondLifePicLayout; + LLTextEditor* mDescriptionEdit; + LLMenuButton* mAgentActionMenuButton; + LLButton* mSaveDescriptionChanges; + LLButton* mDiscardDescriptionChanges; + LLIconCtrl* mCanSeeOnlineIcon; + LLIconCtrl* mCantSeeOnlineIcon; + LLIconCtrl* mCanSeeOnMapIcon; + LLIconCtrl* mCantSeeOnMapIcon; + LLIconCtrl* mCanEditObjectsIcon; + LLIconCtrl* mCantEditObjectsIcon; + + LLHandle mFloaterPermissionsHandle; + LLHandle mFloaterProfileTextureHandle; + LLHandle mFloaterTexturePickerHandle; + + bool mHasUnsavedDescriptionChanges; + bool mVoiceStatus; + bool mWaitingForImageUpload; + bool mAllowPublish; + bool mHideAge; + std::string mDescriptionText; + boost::signals2::connection mAvatarNameCacheConnection; +}; + + +/** +* Panel for displaying Avatar's web profile and home page. +*/ +class LLPanelProfileWeb + : public LLPanelProfileTab + , public LLViewerMediaObserver +{ +public: + LLPanelProfileWeb(); + /*virtual*/ ~LLPanelProfileWeb(); + + void onOpen(const LLSD& key) override; + + bool postBuild() override; + + void resetData() override; + + /** + * Loads web profile. + */ + void updateData() override; + + void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) override; + + void onAvatarNameCache(const LLUUID& agent_id, const LLAvatarName& av_name); + +protected: + void onCommitLoad(LLUICtrl* ctrl); + +private: + std::string mURLHome; + std::string mURLWebProfile; + LLMediaCtrl* mWebBrowser; + + LLFrameTimer mPerformanceTimer; + bool mFirstNavigate; + + boost::signals2::connection mAvatarNameCacheConnection; +}; + +/** +* Panel for displaying Avatar's first life related info. +*/ +class LLPanelProfileFirstLife + : public LLPanelProfilePropertiesProcessorTab +{ +public: + LLPanelProfileFirstLife(); + /*virtual*/ ~LLPanelProfileFirstLife(); + + void onOpen(const LLSD& key) override; + + bool postBuild() override; + + void processProperties(void* data, EAvatarProcessorType type) override; + void processProperties(const LLAvatarData* avatar_data); + + void resetData() override; + + void setProfileImageUploading(bool loading); + void setProfileImageUploaded(const LLUUID &image_asset_id); + + bool hasUnsavedChanges() override { return mHasUnsavedChanges; } + void commitUnsavedChanges() override; + +protected: + void setLoaded() override; + + void onUploadPhoto(); + void onChangePhoto(); + void onRemovePhoto(); + void onCommitPhoto(const LLUUID& id); + void setDescriptionText(const std::string &text); + void onSetDescriptionDirty(); + void onSaveDescriptionChanges(); + void onDiscardDescriptionChanges(); + + LLTextEditor* mDescriptionEdit; + LLProfileImageCtrl* mPicture; + LLButton* mUploadPhoto; + LLButton* mChangePhoto; + LLButton* mRemovePhoto; + LLButton* mSaveChanges; + LLButton* mDiscardChanges; + + LLHandle mFloaterTexturePickerHandle; + + std::string mCurrentDescription; + bool mHasUnsavedChanges; +}; + +/** + * Panel for displaying Avatar's notes and modifying friend's rights. + */ +class LLPanelProfileNotes + : public LLPanelProfilePropertiesProcessorTab +{ +public: + LLPanelProfileNotes(); + /*virtual*/ ~LLPanelProfileNotes(); + + void onOpen(const LLSD& key) override; + + bool postBuild() override; + + void processProperties(void* data, EAvatarProcessorType type) override; + void processProperties(const LLAvatarData* avatar_data); + + void resetData() override; + + bool hasUnsavedChanges() override { return mHasUnsavedChanges; } + void commitUnsavedChanges() override; + +protected: + void setNotesText(const std::string &text); + void onSetNotesDirty(); + void onSaveNotesChanges(); + void onDiscardNotesChanges(); + + LLTextEditor* mNotesEditor; + LLButton* mSaveChanges; + LLButton* mDiscardChanges; + + std::string mCurrentNotes; + bool mHasUnsavedChanges; +}; + + +/** +* Container panel for the profile tabs +*/ +class LLPanelProfile + : public LLPanelProfileTab +{ +public: + LLPanelProfile(); + /*virtual*/ ~LLPanelProfile(); + + bool postBuild() override; + + void updateData() override; + void refreshName(); + + void onOpen(const LLSD& key) override; + + void createPick(const LLPickData &data); + void showPick(const LLUUID& pick_id = LLUUID::null); + bool isPickTabSelected(); + bool isNotesTabSelected(); + bool hasUnsavedChanges() override; + bool hasUnpublishedClassifieds(); + void commitUnsavedChanges() override; + + void showClassified(const LLUUID& classified_id = LLUUID::null, bool edit = false); + void createClassified(); + +private: + void onTabChange(); + + LLPanelProfileSecondLife* mPanelSecondlife; + LLPanelProfileWeb* mPanelWeb; + LLPanelProfilePicks* mPanelPicks; + LLPanelProfileClassifieds* mPanelClassifieds; + LLPanelProfileFirstLife* mPanelFirstlife; + LLPanelProfileNotes* mPanelNotes; + LLTabContainer* mTabContainer; +}; + +#endif //LL_LLPANELPROFILE_H diff --git a/indra/newview/llpanelprofileclassifieds.cpp b/indra/newview/llpanelprofileclassifieds.cpp index 0b42cff698..62829b0745 100644 --- a/indra/newview/llpanelprofileclassifieds.cpp +++ b/indra/newview/llpanelprofileclassifieds.cpp @@ -1,1556 +1,1556 @@ -/** - * @file llpanelprofileclassifieds.cpp - * @brief LLPanelProfileClassifieds and related class implementations - * - * $LicenseInfo:firstyear=2022&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2022, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelprofileclassifieds.h" - -#include "llagent.h" -#include "llavataractions.h" -#include "llavatarpropertiesprocessor.h" -#include "llclassifiedflags.h" -#include "llcombobox.h" -#include "llcommandhandler.h" // for classified HTML detail page click tracking -#include "llcorehttputil.h" -#include "lldispatcher.h" -#include "llfloaterclassified.h" -#include "llfloaterreg.h" -#include "llfloatersidepanelcontainer.h" -#include "llfloaterworldmap.h" -#include "lliconctrl.h" -#include "lllineeditor.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "llpanelavatar.h" -#include "llparcel.h" -#include "llregistry.h" -#include "llscrollcontainer.h" -#include "llstartup.h" -#include "llstatusbar.h" -#include "lltabcontainer.h" -#include "lltexteditor.h" -#include "lltexturectrl.h" -#include "lltrans.h" -#include "llviewergenericmessage.h" // send_generic_message -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" -#include "llviewertexture.h" -#include "llviewertexture.h" - - -//*TODO: verify this limit -const S32 MAX_AVATAR_CLASSIFIEDS = 100; - -const S32 MINIMUM_PRICE_FOR_LISTING = 50; // L$ -const S32 DEFAULT_EDIT_CLASSIFIED_SCROLL_HEIGHT = 530; - -//static -LLPanelProfileClassified::panel_list_t LLPanelProfileClassified::sAllPanels; - -static LLPanelInjector t_panel_profile_classifieds("panel_profile_classifieds"); -static LLPanelInjector t_panel_profile_classified("panel_profile_classified"); - -class LLClassifiedHandler : public LLCommandHandler, public LLAvatarPropertiesObserver -{ -public: - // throttle calls from untrusted browsers - LLClassifiedHandler() : LLCommandHandler("classified", UNTRUSTED_THROTTLE) {} - - std::set mClassifiedIds; - std::string mRequestVerb; - - virtual bool canHandleUntrusted( - const LLSD& params, - const LLSD& query_map, - LLMediaCtrl* web, - const std::string& nav_type) - { - if (params.size() < 1) - { - return true; // don't block, will fail later - } - - if (nav_type == NAV_TYPE_CLICKED - || nav_type == NAV_TYPE_EXTERNAL) - { - return true; - } - - const std::string verb = params[0].asString(); - if (verb == "create") - { - return false; - } - return true; - } - - bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) - { - if (LLStartUp::getStartupState() < STATE_STARTED) - { - return true; - } - - if (!LLUI::getInstance()->mSettingGroups["config"]->getBOOL("EnableClassifieds")) - { - LLNotificationsUtil::add("NoClassifieds", LLSD(), LLSD(), std::string("SwitchToStandardSkinAndQuit")); - return true; - } - - // handle app/classified/create urls first - if (params.size() == 1 && params[0].asString() == "create") - { - LLAvatarActions::createClassified(); - return true; - } - - // then handle the general app/classified/{UUID}/{CMD} urls - if (params.size() < 2) - { - return false; - } - - // get the ID for the classified - LLUUID classified_id; - if (!classified_id.set(params[0], false)) - { - return false; - } - - // show the classified in the side tray. - // need to ask the server for more info first though... - const std::string verb = params[1].asString(); - if (verb == "about") - { - mRequestVerb = verb; - mClassifiedIds.insert(classified_id); - LLAvatarPropertiesProcessor::getInstance()->addObserver(LLUUID(), this); - LLAvatarPropertiesProcessor::getInstance()->sendClassifiedInfoRequest(classified_id); - return true; - } - else if (verb == "edit") - { - LLAvatarActions::showClassified(gAgent.getID(), classified_id, true); - return true; - } - - return false; - } - - void openClassified(LLAvatarClassifiedInfo* c_info) - { - if (mRequestVerb == "about") - { - if (c_info->creator_id == gAgent.getID()) - { - LLAvatarActions::showClassified(gAgent.getID(), c_info->classified_id, false); - } - else - { - LLSD params; - params["id"] = c_info->creator_id; - params["classified_id"] = c_info->classified_id; - params["classified_creator_id"] = c_info->creator_id; - params["classified_snapshot_id"] = c_info->snapshot_id; - params["classified_name"] = c_info->name; - params["classified_desc"] = c_info->description; - params["from_search"] = true; - - LLFloaterClassified* floaterp = LLFloaterReg::getTypedInstance("classified", params); - if (floaterp) - { - floaterp->openFloater(params); - floaterp->setVisibleAndFrontmost(); - } - } - } - } - - void processProperties(void* data, EAvatarProcessorType type) - { - if (APT_CLASSIFIED_INFO != type) - { - return; - } - - // is this the classified that we asked for? - LLAvatarClassifiedInfo* c_info = static_cast(data); - if (!c_info || mClassifiedIds.find(c_info->classified_id) == mClassifiedIds.end()) - { - return; - } - - // open the detail side tray for this classified - openClassified(c_info); - - // remove our observer now that we're done - mClassifiedIds.erase(c_info->classified_id); - LLAvatarPropertiesProcessor::getInstance()->removeObserver(LLUUID(), this); - } -}; -LLClassifiedHandler gClassifiedHandler; - -////////////////////////////////////////////////////////////////////////// - - -//----------------------------------------------------------------------------- -// LLPanelProfileClassifieds -//----------------------------------------------------------------------------- - -LLPanelProfileClassifieds::LLPanelProfileClassifieds() - : LLPanelProfilePropertiesProcessorTab() - , mClassifiedToSelectOnLoad(LLUUID::null) - , mClassifiedEditOnLoad(false) - , mSheduledClassifiedCreation(false) -{ -} - -LLPanelProfileClassifieds::~LLPanelProfileClassifieds() -{ -} - -void LLPanelProfileClassifieds::onOpen(const LLSD& key) -{ - LLPanelProfilePropertiesProcessorTab::onOpen(key); - - resetData(); - - bool own_profile = getSelfProfile(); - if (own_profile) - { - mNewButton->setVisible(true); - mNewButton->setEnabled(false); - - mDeleteButton->setVisible(true); - mDeleteButton->setEnabled(false); - } - - childSetVisible("buttons_header", own_profile); - -} - -void LLPanelProfileClassifieds::selectClassified(const LLUUID& classified_id, bool edit) -{ - if (getIsLoaded()) - { - for (S32 tab_idx = 0; tab_idx < mTabContainer->getTabCount(); ++tab_idx) - { - LLPanelProfileClassified* classified_panel = dynamic_cast(mTabContainer->getPanelByIndex(tab_idx)); - if (classified_panel) - { - if (classified_panel->getClassifiedId() == classified_id) - { - mTabContainer->selectTabPanel(classified_panel); - if (edit) - { - classified_panel->setEditMode(true); - } - break; - } - } - } - } - else - { - mClassifiedToSelectOnLoad = classified_id; - mClassifiedEditOnLoad = edit; - } -} - -void LLPanelProfileClassifieds::createClassified() -{ - if (getIsLoaded()) - { - mNoItemsLabel->setVisible(false); - LLPanelProfileClassified* classified_panel = LLPanelProfileClassified::create(); - classified_panel->onOpen(LLSD()); - mTabContainer->addTabPanel( - LLTabContainer::TabPanelParams(). - panel(classified_panel). - select_tab(true). - label(classified_panel->getClassifiedName())); - updateButtons(); - } - else - { - mSheduledClassifiedCreation = true; - } -} - -bool LLPanelProfileClassifieds::postBuild() -{ - mTabContainer = getChild("tab_classifieds"); - mNoItemsLabel = getChild("classifieds_panel_text"); - mNewButton = getChild("new_btn"); - mDeleteButton = getChild("delete_btn"); - - mNewButton->setCommitCallback(boost::bind(&LLPanelProfileClassifieds::onClickNewBtn, this)); - mDeleteButton->setCommitCallback(boost::bind(&LLPanelProfileClassifieds::onClickDelete, this)); - - return true; -} - -void LLPanelProfileClassifieds::onClickNewBtn() -{ - mNoItemsLabel->setVisible(false); - LLPanelProfileClassified* classified_panel = LLPanelProfileClassified::create(); - classified_panel->onOpen(LLSD()); - mTabContainer->addTabPanel( - LLTabContainer::TabPanelParams(). - panel(classified_panel). - select_tab(true). - label(classified_panel->getClassifiedName())); - updateButtons(); -} - -void LLPanelProfileClassifieds::onClickDelete() -{ - LLPanelProfileClassified* classified_panel = dynamic_cast(mTabContainer->getCurrentPanel()); - if (classified_panel) - { - LLUUID classified_id = classified_panel->getClassifiedId(); - LLSD args; - args["CLASSIFIED"] = classified_panel->getClassifiedName(); - LLSD payload; - payload["classified_id"] = classified_id; - payload["tab_idx"] = mTabContainer->getCurrentPanelIndex(); - LLNotificationsUtil::add("ProfileDeleteClassified", args, payload, - boost::bind(&LLPanelProfileClassifieds::callbackDeleteClassified, this, _1, _2)); - } -} - -void LLPanelProfileClassifieds::callbackDeleteClassified(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - - if (0 == option) - { - LLUUID classified_id = notification["payload"]["classified_id"].asUUID(); - S32 tab_idx = notification["payload"]["tab_idx"].asInteger(); - - LLPanelProfileClassified* classified_panel = dynamic_cast(mTabContainer->getPanelByIndex(tab_idx)); - if (classified_panel && classified_panel->getClassifiedId() == classified_id) - { - mTabContainer->removeTabPanel(classified_panel); - } - - if (classified_id.notNull()) - { - LLAvatarPropertiesProcessor::getInstance()->sendClassifiedDelete(classified_id); - } - - updateButtons(); - - bool no_data = !mTabContainer->getTabCount(); - mNoItemsLabel->setVisible(no_data); - } -} - -void LLPanelProfileClassifieds::processProperties(void* data, EAvatarProcessorType type) -{ - if ((APT_CLASSIFIEDS == type) || (APT_CLASSIFIED_INFO == type)) - { - LLUUID avatar_id = getAvatarId(); - - LLAvatarClassifieds* c_info = static_cast(data); - if (c_info && getAvatarId() == c_info->target_id) - { - // do not clear classified list in case we will receive two or more data packets. - // list has been cleared in updateData(). (fix for EXT-6436) - LLUUID selected_id = mClassifiedToSelectOnLoad; - bool has_selection = false; - - LLAvatarClassifieds::classifieds_list_t::const_iterator it = c_info->classifieds_list.begin(); - for (; c_info->classifieds_list.end() != it; ++it) - { - LLAvatarClassifieds::classified_data c_data = *it; - - LLPanelProfileClassified* classified_panel = LLPanelProfileClassified::create(); - - LLSD params; - params["classified_creator_id"] = avatar_id; - params["classified_id"] = c_data.classified_id; - params["classified_name"] = c_data.name; - params["from_search"] = (selected_id == c_data.classified_id); //SLURL handling and stats tracking - params["edit"] = (selected_id == c_data.classified_id) && mClassifiedEditOnLoad; - classified_panel->onOpen(params); - - mTabContainer->addTabPanel( - LLTabContainer::TabPanelParams(). - panel(classified_panel). - select_tab(selected_id == c_data.classified_id). - label(c_data.name)); - - if (selected_id == c_data.classified_id) - { - has_selection = true; - } - } - - if (mSheduledClassifiedCreation) - { - LLPanelProfileClassified* classified_panel = LLPanelProfileClassified::create(); - classified_panel->onOpen(LLSD()); - mTabContainer->addTabPanel( - LLTabContainer::TabPanelParams(). - panel(classified_panel). - select_tab(!has_selection). - label(classified_panel->getClassifiedName())); - has_selection = true; - } - - // reset 'do on load' values - mClassifiedToSelectOnLoad = LLUUID::null; - mClassifiedEditOnLoad = false; - mSheduledClassifiedCreation = false; - - // set even if not visible, user might delete own - // calassified and this string will need to be shown - if (getSelfProfile()) - { - mNoItemsLabel->setValue(LLTrans::getString("NoClassifiedsText")); - } - else - { - mNoItemsLabel->setValue(LLTrans::getString("NoAvatarClassifiedsText")); - } - - bool has_data = mTabContainer->getTabCount() > 0; - mNoItemsLabel->setVisible(!has_data); - if (has_data && !has_selection) - { - mTabContainer->selectFirstTab(); - } - - setLoaded(); - updateButtons(); - } - } -} - -void LLPanelProfileClassifieds::resetData() -{ - resetLoading(); - mTabContainer->deleteAllTabs(); -} - -void LLPanelProfileClassifieds::updateButtons() -{ - if (getSelfProfile()) - { - mNewButton->setEnabled(canAddNewClassified()); - mDeleteButton->setEnabled(canDeleteClassified()); - } -} - -void LLPanelProfileClassifieds::updateData() -{ - // Send picks request only once - LLUUID avatar_id = getAvatarId(); - if (!getStarted() && avatar_id.notNull()) - { - setIsLoading(); - mNoItemsLabel->setValue(LLTrans::getString("PicksClassifiedsLoadingText")); - mNoItemsLabel->setVisible(true); - - LLAvatarPropertiesProcessor::getInstance()->sendAvatarClassifiedsRequest(avatar_id); - } -} - -bool LLPanelProfileClassifieds::hasNewClassifieds() -{ - for (S32 tab_idx = 0; tab_idx < mTabContainer->getTabCount(); ++tab_idx) - { - LLPanelProfileClassified* classified_panel = dynamic_cast(mTabContainer->getPanelByIndex(tab_idx)); - if (classified_panel && classified_panel->isNew()) - { - return true; - } - } - return false; -} - -bool LLPanelProfileClassifieds::hasUnsavedChanges() -{ - for (S32 tab_idx = 0; tab_idx < mTabContainer->getTabCount(); ++tab_idx) - { - LLPanelProfileClassified* classified_panel = dynamic_cast(mTabContainer->getPanelByIndex(tab_idx)); - if (classified_panel && classified_panel->isDirty()) // includes 'new' - { - return true; - } - } - return false; -} - -bool LLPanelProfileClassifieds::canAddNewClassified() -{ - return (mTabContainer->getTabCount() < MAX_AVATAR_CLASSIFIEDS); -} - -bool LLPanelProfileClassifieds::canDeleteClassified() -{ - return (mTabContainer->getTabCount() > 0); -} - -void LLPanelProfileClassifieds::commitUnsavedChanges() -{ - if (getIsLoaded()) - { - for (S32 tab_idx = 0; tab_idx < mTabContainer->getTabCount(); ++tab_idx) - { - LLPanelProfileClassified* classified_panel = dynamic_cast(mTabContainer->getPanelByIndex(tab_idx)); - if (classified_panel && classified_panel->isDirty() && !classified_panel->isNew()) - { - classified_panel->doSave(); - } - } - } -} -//----------------------------------------------------------------------------- -// LLDispatchClassifiedClickThrough -//----------------------------------------------------------------------------- - -// "classifiedclickthrough" -// strings[0] = classified_id -// strings[1] = teleport_clicks -// strings[2] = map_clicks -// strings[3] = profile_clicks -class LLDispatchClassifiedClickThrough : public LLDispatchHandler -{ -public: - virtual bool operator()( - const LLDispatcher* dispatcher, - const std::string& key, - const LLUUID& invoice, - const sparam_t& strings) - { - if (strings.size() != 4) return false; - LLUUID classified_id(strings[0]); - S32 teleport_clicks = atoi(strings[1].c_str()); - S32 map_clicks = atoi(strings[2].c_str()); - S32 profile_clicks = atoi(strings[3].c_str()); - - LLPanelProfileClassified::setClickThrough( - classified_id, teleport_clicks, map_clicks, profile_clicks, false); - - return true; - } -}; -static LLDispatchClassifiedClickThrough sClassifiedClickThrough; - - -//----------------------------------------------------------------------------- -// LLPanelProfileClassified -//----------------------------------------------------------------------------- - -static const S32 CB_ITEM_MATURE = 0; -static const S32 CB_ITEM_PG = 1; - -LLPanelProfileClassified::LLPanelProfileClassified() - : LLPanelProfilePropertiesProcessorTab() - , mInfoLoaded(false) - , mTeleportClicksOld(0) - , mMapClicksOld(0) - , mProfileClicksOld(0) - , mTeleportClicksNew(0) - , mMapClicksNew(0) - , mProfileClicksNew(0) - , mPriceForListing(0) - , mSnapshotCtrl(NULL) - , mPublishFloater(NULL) - , mIsNew(false) - , mIsNewWithErrors(false) - , mCanClose(false) - , mEditMode(false) - , mEditOnLoad(false) -{ - sAllPanels.push_back(this); -} - -LLPanelProfileClassified::~LLPanelProfileClassified() -{ - sAllPanels.remove(this); - gGenericDispatcher.addHandler("classifiedclickthrough", NULL); // deregister our handler -} - -//static -LLPanelProfileClassified* LLPanelProfileClassified::create() -{ - LLPanelProfileClassified* panel = new LLPanelProfileClassified(); - panel->buildFromFile("panel_profile_classified.xml"); - return panel; -} - -bool LLPanelProfileClassified::postBuild() -{ - mScrollContainer = getChild("profile_scroll"); - mInfoPanel = getChild("info_panel"); - mInfoScroll = getChild("info_scroll_content_panel"); - mEditPanel = getChild("edit_panel"); - - mSnapshotCtrl = getChild("classified_snapshot"); - mEditIcon = getChild("edit_icon"); - - //info - mClassifiedNameText = getChild("classified_name"); - mClassifiedDescText = getChild("classified_desc"); - mLocationText = getChild("classified_location"); - mCategoryText = getChild("category"); - mContentTypeText = getChild("content_type"); - mContentTypeM = getChild("content_type_moderate"); - mContentTypeG = getChild("content_type_general"); - mPriceText = getChild("price_for_listing"); - mAutoRenewText = getChild("auto_renew"); - - mMapButton = getChild("show_on_map_btn"); - mTeleportButton = getChild("teleport_btn"); - mEditButton = getChild("edit_btn"); - - //edit - mClassifiedNameEdit = getChild("classified_name_edit"); - mClassifiedDescEdit = getChild("classified_desc_edit"); - mLocationEdit = getChild("classified_location_edit"); - mCategoryCombo = getChild("category_edit"); - mContentTypeCombo = getChild("content_type_edit"); - mAutoRenewEdit = getChild("auto_renew_edit"); - - mSaveButton = getChild("save_changes_btn"); - mSetLocationButton = getChild("set_to_curr_location_btn"); - mCancelButton = getChild("cancel_btn"); - - mUtilityBtnCnt = getChild("util_buttons_lp"); - mPublishBtnsCnt = getChild("publish_layout_panel"); - mCancelBtnCnt = getChild("cancel_btn_lp"); - mSaveBtnCnt = getChild("save_btn_lp"); - - mSnapshotCtrl->setOnSelectCallback(boost::bind(&LLPanelProfileClassified::onTextureSelected, this)); - mSnapshotCtrl->setMouseEnterCallback(boost::bind(&LLPanelProfileClassified::onTexturePickerMouseEnter, this)); - mSnapshotCtrl->setMouseLeaveCallback(boost::bind(&LLPanelProfileClassified::onTexturePickerMouseLeave, this)); - mSnapshotCtrl->setAllowLocalTexture(false); - mSnapshotCtrl->setBakeTextureEnabled(false); - mEditIcon->setVisible(false); - - mMapButton->setCommitCallback(boost::bind(&LLPanelProfileClassified::onMapClick, this)); - mTeleportButton->setCommitCallback(boost::bind(&LLPanelProfileClassified::onTeleportClick, this)); - mEditButton->setCommitCallback(boost::bind(&LLPanelProfileClassified::onEditClick, this)); - mSaveButton->setCommitCallback(boost::bind(&LLPanelProfileClassified::onSaveClick, this)); - mSetLocationButton->setCommitCallback(boost::bind(&LLPanelProfileClassified::onSetLocationClick, this)); - mCancelButton->setCommitCallback(boost::bind(&LLPanelProfileClassified::onCancelClick, this)); - - LLClassifiedInfo::cat_map::iterator iter; - for (iter = LLClassifiedInfo::sCategories.begin(); - iter != LLClassifiedInfo::sCategories.end(); - iter++) - { - mCategoryCombo->add(LLTrans::getString(iter->second)); - } - - mClassifiedNameEdit->setKeystrokeCallback(boost::bind(&LLPanelProfileClassified::onTitleChange, this), NULL); - mClassifiedDescEdit->setKeystrokeCallback(boost::bind(&LLPanelProfileClassified::onChange, this)); - mCategoryCombo->setCommitCallback(boost::bind(&LLPanelProfileClassified::onChange, this)); - mContentTypeCombo->setCommitCallback(boost::bind(&LLPanelProfileClassified::onChange, this)); - mAutoRenewEdit->setCommitCallback(boost::bind(&LLPanelProfileClassified::onChange, this)); - - return true; -} - -void LLPanelProfileClassified::onOpen(const LLSD& key) -{ - mIsNew = key.isUndefined(); - - resetData(); - resetControls(); - scrollToTop(); - - // classified is not created yet - bool is_new = isNew() || isNewWithErrors(); - - if(is_new) - { - LLPanelProfilePropertiesProcessorTab::setAvatarId(gAgent.getID()); - - setPosGlobal(gAgent.getPositionGlobal()); - - LLUUID snapshot_id = LLUUID::null; - std::string desc; - LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - if(parcel) - { - desc = parcel->getDesc(); - snapshot_id = parcel->getSnapshotID(); - } - - std::string region_name = LLTrans::getString("ClassifiedUpdateAfterPublish"); - LLViewerRegion* region = gAgent.getRegion(); - if (region) - { - region_name = region->getName(); - } - - setClassifiedName(makeClassifiedName()); - setDescription(desc); - setSnapshotId(snapshot_id); - setClassifiedLocation(createLocationText(getLocationNotice(), region_name, getPosGlobal())); - // server will set valid parcel id - setParcelId(LLUUID::null); - - mSaveButton->setLabelArg("[LABEL]", getString("publish_label")); - - setEditMode(true); - enableSave(true); - enableEditing(true); - resetDirty(); - setInfoLoaded(false); - } - else - { - LLUUID avatar_id = key["classified_creator_id"]; - if(avatar_id.isNull()) - { - return; - } - LLPanelProfilePropertiesProcessorTab::setAvatarId(avatar_id); - - setClassifiedId(key["classified_id"]); - setClassifiedName(key["classified_name"]); - setFromSearch(key["from_search"]); - mEditOnLoad = key["edit"]; - - LL_INFOS() << "Opening classified [" << getClassifiedName() << "] (" << getClassifiedId() << ")" << LL_ENDL; - - LLAvatarPropertiesProcessor::getInstance()->sendClassifiedInfoRequest(getClassifiedId()); - - gGenericDispatcher.addHandler("classifiedclickthrough", &sClassifiedClickThrough); - - if (gAgent.getRegion()) - { - // While we're at it let's get the stats from the new table if that - // capability exists. - std::string url = gAgent.getRegion()->getCapability("SearchStatRequest"); - if (!url.empty()) - { - LL_INFOS() << "Classified stat request via capability" << LL_ENDL; - LLSD body; - LLUUID classifiedId = getClassifiedId(); - body["classified_id"] = classifiedId; - LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost(url, body, - boost::bind(&LLPanelProfileClassified::handleSearchStatResponse, classifiedId, _1)); - } - } - // Update classified click stats. - // *TODO: Should we do this when opening not from search? - if (!fromSearch() ) - { - sendClickMessage("profile"); - } - - setInfoLoaded(false); - } - - - bool is_self = getSelfProfile(); - getChildView("auto_renew_layout_panel")->setVisible(is_self); - getChildView("clickthrough_layout_panel")->setVisible(is_self); - - updateButtons(); -} - -void LLPanelProfileClassified::processProperties(void* data, EAvatarProcessorType type) -{ - if (APT_CLASSIFIED_INFO != type) - { - return; - } - - LLAvatarClassifiedInfo* c_info = static_cast(data); - if(c_info && getClassifiedId() == c_info->classified_id) - { - // see LLPanelProfileClassified::sendUpdate() for notes - if (mIsNewWithErrors) - { - // We just published it - setEditMode(false); - } - mIsNewWithErrors = false; - mIsNew = false; - - setClassifiedName(c_info->name); - setDescription(c_info->description); - setSnapshotId(c_info->snapshot_id); - setParcelId(c_info->parcel_id); - setPosGlobal(c_info->pos_global); - setSimName(c_info->sim_name); - - setClassifiedLocation(createLocationText(c_info->parcel_name, c_info->sim_name, c_info->pos_global)); - - mCategoryText->setValue(LLClassifiedInfo::sCategories[c_info->category]); - // *HACK see LLPanelProfileClassified::sendUpdate() - setCategory(c_info->category - 1); - - bool mature = is_cf_mature(c_info->flags); - setContentType(mature); - - bool auto_renew = is_cf_auto_renew(c_info->flags); - std::string auto_renew_str = auto_renew ? getString("auto_renew_on") : getString("auto_renew_off"); - mAutoRenewText->setValue(auto_renew_str); - mAutoRenewEdit->setValue(auto_renew); - - static LLUIString price_str = getString("l$_price"); - price_str.setArg("[PRICE]", llformat("%d", c_info->price_for_listing)); - mPriceText->setValue(LLSD(price_str)); - - static std::string date_fmt = getString("date_fmt"); - std::string date_str = date_fmt; - LLStringUtil::format(date_str, LLSD().with("datetime", (S32) c_info->creation_date)); - getChild("creation_date")->setValue(date_str); - - resetDirty(); - setInfoLoaded(true); - enableSave(false); - enableEditing(true); - - // for just created classified - in case user opened edit panel before processProperties() callback - mSaveButton->setLabelArg("[LABEL]", getString("save_label")); - - setLoaded(); - updateButtons(); - - if (mEditOnLoad) - { - setEditMode(true); - } - } - -} - -void LLPanelProfileClassified::setEditMode(bool edit_mode) -{ - mEditMode = edit_mode; - - mInfoPanel->setVisible(!edit_mode); - mEditPanel->setVisible(edit_mode); - - // snapshot control is common between info and edit, - // enable it only when in edit mode - mSnapshotCtrl->setEnabled(edit_mode); - - scrollToTop(); - updateButtons(); - updateInfoRect(); -} - -void LLPanelProfileClassified::updateButtons() -{ - bool edit_mode = getEditMode(); - mUtilityBtnCnt->setVisible(!edit_mode); - - // cancel button should either delete unpublished - // classified or not be there at all - mCancelBtnCnt->setVisible(edit_mode && !mIsNew); - mPublishBtnsCnt->setVisible(edit_mode); - mSaveBtnCnt->setVisible(edit_mode); - mEditButton->setVisible(!edit_mode && getSelfProfile()); -} - -void LLPanelProfileClassified::updateInfoRect() -{ - if (getEditMode()) - { - // info_scroll_content_panel contains both info and edit panel - // info panel can be very large and scroll bar will carry over. - // Resize info panel to prevent scroll carry over when in edit mode. - mInfoScroll->reshape(mInfoScroll->getRect().getWidth(), DEFAULT_EDIT_CLASSIFIED_SCROLL_HEIGHT, false); - } - else - { - // Adjust text height to make description scrollable. - S32 new_height = mClassifiedDescText->getTextBoundingRect().getHeight(); - LLRect visible_rect = mClassifiedDescText->getVisibleDocumentRect(); - S32 delta_height = new_height - visible_rect.getHeight() + 5; - - LLRect rect = mInfoScroll->getRect(); - mInfoScroll->reshape(rect.getWidth(), rect.getHeight() + delta_height, false); - } -} - -void LLPanelProfileClassified::enableEditing(bool enable) -{ - mEditButton->setEnabled(enable); - mClassifiedNameEdit->setEnabled(enable); - mClassifiedDescEdit->setEnabled(enable); - mSetLocationButton->setEnabled(enable); - mCategoryCombo->setEnabled(enable); - mContentTypeCombo->setEnabled(enable); - mAutoRenewEdit->setEnabled(enable); -} - -void LLPanelProfileClassified::resetControls() -{ - updateButtons(); - - mCategoryCombo->setCurrentByIndex(0); - mContentTypeCombo->setCurrentByIndex(0); - mAutoRenewEdit->setValue(false); - mPriceForListing = MINIMUM_PRICE_FOR_LISTING; -} - -void LLPanelProfileClassified::onEditClick() -{ - setEditMode(true); -} - -void LLPanelProfileClassified::onCancelClick() -{ - if (isNew()) - { - mClassifiedNameEdit->setValue(mClassifiedNameText->getValue()); - mClassifiedDescEdit->setValue(mClassifiedDescText->getValue()); - mLocationEdit->setValue(mLocationText->getValue()); - mCategoryCombo->setCurrentByIndex(0); - mContentTypeCombo->setCurrentByIndex(0); - mAutoRenewEdit->setValue(false); - mPriceForListing = MINIMUM_PRICE_FOR_LISTING; - } - else - { - updateTabLabel(mClassifiedNameText->getValue()); - - // Reload data to undo changes to forms - LLAvatarPropertiesProcessor::getInstance()->sendClassifiedInfoRequest(getClassifiedId()); - } - - setInfoLoaded(false); - - setEditMode(false); -} - -void LLPanelProfileClassified::onSaveClick() -{ - mCanClose = false; - - if(!isValidName()) - { - notifyInvalidName(); - return; - } - if(isNew() || isNewWithErrors()) - { - if(gStatusBar->getBalance() < MINIMUM_PRICE_FOR_LISTING) - { - LLNotificationsUtil::add("ClassifiedInsufficientFunds"); - return; - } - - mPublishFloater = LLFloaterReg::findTypedInstance( - "publish_classified", LLSD()); - - if(!mPublishFloater) - { - mPublishFloater = LLFloaterReg::getTypedInstance( - "publish_classified", LLSD()); - - mPublishFloater->setPublishClickedCallback(boost::bind - (&LLPanelProfileClassified::onPublishFloaterPublishClicked, this)); - } - - // set spinner value before it has focus or value wont be set - mPublishFloater->setPrice(getPriceForListing()); - mPublishFloater->openFloater(mPublishFloater->getKey()); - mPublishFloater->center(); - } - else - { - doSave(); - } -} - -/*static*/ -void LLPanelProfileClassified::handleSearchStatResponse(LLUUID classifiedId, LLSD result) -{ - S32 teleport = result["teleport_clicks"].asInteger(); - S32 map = result["map_clicks"].asInteger(); - S32 profile = result["profile_clicks"].asInteger(); - S32 search_teleport = result["search_teleport_clicks"].asInteger(); - S32 search_map = result["search_map_clicks"].asInteger(); - S32 search_profile = result["search_profile_clicks"].asInteger(); - - LLPanelProfileClassified::setClickThrough(classifiedId, - teleport + search_teleport, - map + search_map, - profile + search_profile, - true); -} - -void LLPanelProfileClassified::resetData() -{ - setClassifiedName(LLStringUtil::null); - setDescription(LLStringUtil::null); - setClassifiedLocation(LLStringUtil::null); - setClassifiedId(LLUUID::null); - setSnapshotId(LLUUID::null); - setPosGlobal(LLVector3d::zero); - setParcelId(LLUUID::null); - setSimName(LLStringUtil::null); - setFromSearch(false); - - // reset click stats - mTeleportClicksOld = 0; - mMapClicksOld = 0; - mProfileClicksOld = 0; - mTeleportClicksNew = 0; - mMapClicksNew = 0; - mProfileClicksNew = 0; - - mPriceForListing = MINIMUM_PRICE_FOR_LISTING; - - mCategoryText->setValue(LLStringUtil::null); - mContentTypeText->setValue(LLStringUtil::null); - getChild("click_through_text")->setValue(LLStringUtil::null); - mEditButton->setValue(LLStringUtil::null); - getChild("creation_date")->setValue(LLStringUtil::null); - mContentTypeM->setVisible(false); - mContentTypeG->setVisible(false); -} - -void LLPanelProfileClassified::setClassifiedName(const std::string& name) -{ - mClassifiedNameText->setValue(name); - mClassifiedNameEdit->setValue(name); -} - -std::string LLPanelProfileClassified::getClassifiedName() -{ - return mClassifiedNameEdit->getValue().asString(); -} - -void LLPanelProfileClassified::setDescription(const std::string& desc) -{ - mClassifiedDescText->setValue(desc); - mClassifiedDescEdit->setValue(desc); - - updateInfoRect(); -} - -std::string LLPanelProfileClassified::getDescription() -{ - return mClassifiedDescEdit->getValue().asString(); -} - -void LLPanelProfileClassified::setClassifiedLocation(const std::string& location) -{ - mLocationText->setValue(location); - mLocationEdit->setValue(location); -} - -std::string LLPanelProfileClassified::getClassifiedLocation() -{ - return mLocationText->getValue().asString(); -} - -void LLPanelProfileClassified::setSnapshotId(const LLUUID& id) -{ - mSnapshotCtrl->setValue(id); -} - -LLUUID LLPanelProfileClassified::getSnapshotId() -{ - return mSnapshotCtrl->getValue().asUUID(); -} - -// static -void LLPanelProfileClassified::setClickThrough( - const LLUUID& classified_id, - S32 teleport, - S32 map, - S32 profile, - bool from_new_table) -{ - LL_INFOS() << "Click-through data for classified " << classified_id << " arrived: [" - << teleport << ", " << map << ", " << profile << "] (" - << (from_new_table ? "new" : "old") << ")" << LL_ENDL; - - for (panel_list_t::iterator iter = sAllPanels.begin(); iter != sAllPanels.end(); ++iter) - { - LLPanelProfileClassified* self = *iter; - if (self->getClassifiedId() != classified_id) - { - continue; - } - - // *HACK: Skip LLPanelProfileClassified instances: they don't display clicks data. - // Those instances should not be in the list at all. - if (typeid(*self) != typeid(LLPanelProfileClassified)) - { - continue; - } - - LL_INFOS() << "Updating classified info panel" << LL_ENDL; - - // We need to check to see if the data came from the new stat_table - // or the old classified table. We also need to cache the data from - // the two separate sources so as to display the aggregate totals. - - if (from_new_table) - { - self->mTeleportClicksNew = teleport; - self->mMapClicksNew = map; - self->mProfileClicksNew = profile; - } - else - { - self->mTeleportClicksOld = teleport; - self->mMapClicksOld = map; - self->mProfileClicksOld = profile; - } - - static LLUIString ct_str = self->getString("click_through_text_fmt"); - - ct_str.setArg("[TELEPORT]", llformat("%d", self->mTeleportClicksNew + self->mTeleportClicksOld)); - ct_str.setArg("[MAP]", llformat("%d", self->mMapClicksNew + self->mMapClicksOld)); - ct_str.setArg("[PROFILE]", llformat("%d", self->mProfileClicksNew + self->mProfileClicksOld)); - - self->getChild("click_through_text")->setValue(ct_str.getString()); - // *HACK: remove this when there is enough room for click stats in the info panel - self->getChildView("click_through_text")->setToolTip(ct_str.getString()); - - LL_INFOS() << "teleport: " << llformat("%d", self->mTeleportClicksNew + self->mTeleportClicksOld) - << ", map: " << llformat("%d", self->mMapClicksNew + self->mMapClicksOld) - << ", profile: " << llformat("%d", self->mProfileClicksNew + self->mProfileClicksOld) - << LL_ENDL; - } -} - -// static -std::string LLPanelProfileClassified::createLocationText( - const std::string& original_name, - const std::string& sim_name, - const LLVector3d& pos_global) -{ - std::string location_text; - - location_text.append(original_name); - - if (!sim_name.empty()) - { - if (!location_text.empty()) - location_text.append(", "); - location_text.append(sim_name); - } - - if (!location_text.empty()) - location_text.append(" "); - - if (!pos_global.isNull()) - { - S32 region_x = ll_round((F32)pos_global.mdV[VX]) % REGION_WIDTH_UNITS; - S32 region_y = ll_round((F32)pos_global.mdV[VY]) % REGION_WIDTH_UNITS; - S32 region_z = ll_round((F32)pos_global.mdV[VZ]); - location_text.append(llformat(" (%d, %d, %d)", region_x, region_y, region_z)); - } - - return location_text; -} - -void LLPanelProfileClassified::scrollToTop() -{ - if (mScrollContainer) - { - mScrollContainer->goToTop(); - } -} - -//info -// static -// *TODO: move out of the panel -void LLPanelProfileClassified::sendClickMessage( - const std::string& type, - bool from_search, - const LLUUID& classified_id, - const LLUUID& parcel_id, - const LLVector3d& global_pos, - const std::string& sim_name) -{ - if (gAgent.getRegion()) - { - // You're allowed to click on your own ads to reassure yourself - // that the system is working. - LLSD body; - body["type"] = type; - body["from_search"] = from_search; - body["classified_id"] = classified_id; - body["parcel_id"] = parcel_id; - body["dest_pos_global"] = global_pos.getValue(); - body["region_name"] = sim_name; - - std::string url = gAgent.getRegion()->getCapability("SearchStatTracking"); - LL_INFOS() << "Sending click msg via capability (url=" << url << ")" << LL_ENDL; - LL_INFOS() << "body: [" << body << "]" << LL_ENDL; - LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, body, - "SearchStatTracking Click report sent.", "SearchStatTracking Click report NOT sent."); - } -} - -void LLPanelProfileClassified::sendClickMessage(const std::string& type) -{ - sendClickMessage( - type, - fromSearch(), - getClassifiedId(), - getParcelId(), - getPosGlobal(), - getSimName()); -} - -void LLPanelProfileClassified::onMapClick() -{ - sendClickMessage("map"); - LLFloaterWorldMap::getInstance()->trackLocation(getPosGlobal()); - LLFloaterReg::showInstance("world_map", "center"); -} - -void LLPanelProfileClassified::onTeleportClick() -{ - if (!getPosGlobal().isExactlyZero()) - { - sendClickMessage("teleport"); - gAgent.teleportViaLocation(getPosGlobal()); - LLFloaterWorldMap::getInstance()->trackLocation(getPosGlobal()); - } -} - -bool LLPanelProfileClassified::isDirty() const -{ - if(mIsNew) - { - return true; - } - - bool dirty = false; - dirty |= mSnapshotCtrl->isDirty(); - dirty |= mClassifiedNameEdit->isDirty(); - dirty |= mClassifiedDescEdit->isDirty(); - dirty |= mCategoryCombo->isDirty(); - dirty |= mContentTypeCombo->isDirty(); - dirty |= mAutoRenewEdit->isDirty(); - - return dirty; -} - -void LLPanelProfileClassified::resetDirty() -{ - mSnapshotCtrl->resetDirty(); - mClassifiedNameEdit->resetDirty(); - - // call blockUndo() to really reset dirty(and make isDirty work as intended) - mClassifiedDescEdit->blockUndo(); - mClassifiedDescEdit->resetDirty(); - - mCategoryCombo->resetDirty(); - mContentTypeCombo->resetDirty(); - mAutoRenewEdit->resetDirty(); -} - -bool LLPanelProfileClassified::canClose() -{ - return mCanClose; -} - -U32 LLPanelProfileClassified::getContentType() -{ - return mContentTypeCombo->getCurrentIndex(); -} - -void LLPanelProfileClassified::setContentType(bool mature) -{ - static std::string mature_str = getString("type_mature"); - static std::string pg_str = getString("type_pg"); - mContentTypeText->setValue(mature ? mature_str : pg_str); - mContentTypeM->setVisible(mature); - mContentTypeG->setVisible(!mature); - mContentTypeCombo->setCurrentByIndex(mature ? CB_ITEM_MATURE : CB_ITEM_PG); - mContentTypeCombo->resetDirty(); -} - -bool LLPanelProfileClassified::getAutoRenew() -{ - return mAutoRenewEdit->getValue().asBoolean(); -} - -void LLPanelProfileClassified::sendUpdate() -{ - LLAvatarClassifiedInfo c_data; - - if(getClassifiedId().isNull()) - { - setClassifiedId(LLUUID::generateNewID()); - } - - c_data.agent_id = gAgent.getID(); - c_data.classified_id = getClassifiedId(); - // *HACK - // Categories on server start with 1 while combo-box index starts with 0 - c_data.category = getCategory() + 1; - c_data.name = getClassifiedName(); - c_data.description = getDescription(); - c_data.parcel_id = getParcelId(); - c_data.snapshot_id = getSnapshotId(); - c_data.pos_global = getPosGlobal(); - c_data.flags = getFlags(); - c_data.price_for_listing = getPriceForListing(); - - LLAvatarPropertiesProcessor::getInstance()->sendClassifiedInfoUpdate(&c_data); - - if(isNew()) - { - // Lets assume there will be some error. - // Successful sendClassifiedInfoUpdate will trigger processProperties and - // let us know there was no error. - mIsNewWithErrors = true; - } -} - -U32 LLPanelProfileClassified::getCategory() -{ - return mCategoryCombo->getCurrentIndex(); -} - -void LLPanelProfileClassified::setCategory(U32 category) -{ - mCategoryCombo->setCurrentByIndex(category); - mCategoryCombo->resetDirty(); -} - -U8 LLPanelProfileClassified::getFlags() -{ - bool auto_renew = mAutoRenewEdit->getValue().asBoolean(); - - bool mature = mContentTypeCombo->getCurrentIndex() == CB_ITEM_MATURE; - - return pack_classified_flags_request(auto_renew, false, mature, false); -} - -void LLPanelProfileClassified::enableSave(bool enable) -{ - mSaveButton->setEnabled(enable); -} - -std::string LLPanelProfileClassified::makeClassifiedName() -{ - std::string name; - - LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - if(parcel) - { - name = parcel->getName(); - } - - if(!name.empty()) - { - return name; - } - - LLViewerRegion* region = gAgent.getRegion(); - if(region) - { - name = region->getName(); - } - - return name; -} - -void LLPanelProfileClassified::onSetLocationClick() -{ - setPosGlobal(gAgent.getPositionGlobal()); - setParcelId(LLUUID::null); - - std::string region_name = LLTrans::getString("ClassifiedUpdateAfterPublish"); - LLViewerRegion* region = gAgent.getRegion(); - if (region) - { - region_name = region->getName(); - } - - setClassifiedLocation(createLocationText(getLocationNotice(), region_name, getPosGlobal())); - - // mark classified as dirty - setValue(LLSD()); - - onChange(); -} - -void LLPanelProfileClassified::onChange() -{ - enableSave(isDirty()); -} - -void LLPanelProfileClassified::onTitleChange() -{ - updateTabLabel(getClassifiedName()); - onChange(); -} - -void LLPanelProfileClassified::doSave() -{ - //*TODO: Fix all of this - - mCanClose = true; - sendUpdate(); - updateTabLabel(getClassifiedName()); - resetDirty(); - - if (!canClose()) - { - return; - } - - if (!isNew() && !isNewWithErrors()) - { - setEditMode(false); - return; - } - - updateButtons(); -} - -void LLPanelProfileClassified::onPublishFloaterPublishClicked() -{ - if (mPublishFloater->getPrice() < MINIMUM_PRICE_FOR_LISTING) - { - LLSD args; - args["MIN_PRICE"] = MINIMUM_PRICE_FOR_LISTING; - LLNotificationsUtil::add("MinClassifiedPrice", args); - return; - } - - setPriceForListing(mPublishFloater->getPrice()); - - doSave(); -} - -std::string LLPanelProfileClassified::getLocationNotice() -{ - static std::string location_notice = getString("location_notice"); - return location_notice; -} - -bool LLPanelProfileClassified::isValidName() -{ - std::string name = getClassifiedName(); - if (name.empty()) - { - return false; - } - if (!isalnum(name[0])) - { - return false; - } - - return true; -} - -void LLPanelProfileClassified::notifyInvalidName() -{ - std::string name = getClassifiedName(); - if (name.empty()) - { - LLNotificationsUtil::add("BlankClassifiedName"); - } - else if (!isalnum(name[0])) - { - LLNotificationsUtil::add("ClassifiedMustBeAlphanumeric"); - } -} - -void LLPanelProfileClassified::onTexturePickerMouseEnter() -{ - mEditIcon->setVisible(true); -} - -void LLPanelProfileClassified::onTexturePickerMouseLeave() -{ - mEditIcon->setVisible(false); -} - -void LLPanelProfileClassified::onTextureSelected() -{ - setSnapshotId(mSnapshotCtrl->getValue().asUUID()); - onChange(); -} - -void LLPanelProfileClassified::updateTabLabel(const std::string& title) -{ - setLabel(title); - LLTabContainer* parent = dynamic_cast(getParent()); - if (parent) - { - parent->setCurrentTabName(title); - } -} - - -//----------------------------------------------------------------------------- -// LLPublishClassifiedFloater -//----------------------------------------------------------------------------- - -LLPublishClassifiedFloater::LLPublishClassifiedFloater(const LLSD& key) - : LLFloater(key) -{ -} - -LLPublishClassifiedFloater::~LLPublishClassifiedFloater() -{ -} - -bool LLPublishClassifiedFloater::postBuild() -{ - LLFloater::postBuild(); - - childSetAction("publish_btn", boost::bind(&LLFloater::closeFloater, this, false)); - childSetAction("cancel_btn", boost::bind(&LLFloater::closeFloater, this, false)); - - return true; -} - -void LLPublishClassifiedFloater::setPrice(S32 price) -{ - getChild("price_for_listing")->setValue(price); -} - -S32 LLPublishClassifiedFloater::getPrice() -{ - return getChild("price_for_listing")->getValue().asInteger(); -} - -void LLPublishClassifiedFloater::setPublishClickedCallback(const commit_signal_t::slot_type& cb) -{ - getChild("publish_btn")->setClickedCallback(cb); -} - -void LLPublishClassifiedFloater::setCancelClickedCallback(const commit_signal_t::slot_type& cb) -{ - getChild("cancel_btn")->setClickedCallback(cb); -} +/** + * @file llpanelprofileclassifieds.cpp + * @brief LLPanelProfileClassifieds and related class implementations + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2022, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelprofileclassifieds.h" + +#include "llagent.h" +#include "llavataractions.h" +#include "llavatarpropertiesprocessor.h" +#include "llclassifiedflags.h" +#include "llcombobox.h" +#include "llcommandhandler.h" // for classified HTML detail page click tracking +#include "llcorehttputil.h" +#include "lldispatcher.h" +#include "llfloaterclassified.h" +#include "llfloaterreg.h" +#include "llfloatersidepanelcontainer.h" +#include "llfloaterworldmap.h" +#include "lliconctrl.h" +#include "lllineeditor.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "llpanelavatar.h" +#include "llparcel.h" +#include "llregistry.h" +#include "llscrollcontainer.h" +#include "llstartup.h" +#include "llstatusbar.h" +#include "lltabcontainer.h" +#include "lltexteditor.h" +#include "lltexturectrl.h" +#include "lltrans.h" +#include "llviewergenericmessage.h" // send_generic_message +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "llviewertexture.h" +#include "llviewertexture.h" + + +//*TODO: verify this limit +const S32 MAX_AVATAR_CLASSIFIEDS = 100; + +const S32 MINIMUM_PRICE_FOR_LISTING = 50; // L$ +const S32 DEFAULT_EDIT_CLASSIFIED_SCROLL_HEIGHT = 530; + +//static +LLPanelProfileClassified::panel_list_t LLPanelProfileClassified::sAllPanels; + +static LLPanelInjector t_panel_profile_classifieds("panel_profile_classifieds"); +static LLPanelInjector t_panel_profile_classified("panel_profile_classified"); + +class LLClassifiedHandler : public LLCommandHandler, public LLAvatarPropertiesObserver +{ +public: + // throttle calls from untrusted browsers + LLClassifiedHandler() : LLCommandHandler("classified", UNTRUSTED_THROTTLE) {} + + std::set mClassifiedIds; + std::string mRequestVerb; + + virtual bool canHandleUntrusted( + const LLSD& params, + const LLSD& query_map, + LLMediaCtrl* web, + const std::string& nav_type) + { + if (params.size() < 1) + { + return true; // don't block, will fail later + } + + if (nav_type == NAV_TYPE_CLICKED + || nav_type == NAV_TYPE_EXTERNAL) + { + return true; + } + + const std::string verb = params[0].asString(); + if (verb == "create") + { + return false; + } + return true; + } + + bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) + { + if (LLStartUp::getStartupState() < STATE_STARTED) + { + return true; + } + + if (!LLUI::getInstance()->mSettingGroups["config"]->getBOOL("EnableClassifieds")) + { + LLNotificationsUtil::add("NoClassifieds", LLSD(), LLSD(), std::string("SwitchToStandardSkinAndQuit")); + return true; + } + + // handle app/classified/create urls first + if (params.size() == 1 && params[0].asString() == "create") + { + LLAvatarActions::createClassified(); + return true; + } + + // then handle the general app/classified/{UUID}/{CMD} urls + if (params.size() < 2) + { + return false; + } + + // get the ID for the classified + LLUUID classified_id; + if (!classified_id.set(params[0], false)) + { + return false; + } + + // show the classified in the side tray. + // need to ask the server for more info first though... + const std::string verb = params[1].asString(); + if (verb == "about") + { + mRequestVerb = verb; + mClassifiedIds.insert(classified_id); + LLAvatarPropertiesProcessor::getInstance()->addObserver(LLUUID(), this); + LLAvatarPropertiesProcessor::getInstance()->sendClassifiedInfoRequest(classified_id); + return true; + } + else if (verb == "edit") + { + LLAvatarActions::showClassified(gAgent.getID(), classified_id, true); + return true; + } + + return false; + } + + void openClassified(LLAvatarClassifiedInfo* c_info) + { + if (mRequestVerb == "about") + { + if (c_info->creator_id == gAgent.getID()) + { + LLAvatarActions::showClassified(gAgent.getID(), c_info->classified_id, false); + } + else + { + LLSD params; + params["id"] = c_info->creator_id; + params["classified_id"] = c_info->classified_id; + params["classified_creator_id"] = c_info->creator_id; + params["classified_snapshot_id"] = c_info->snapshot_id; + params["classified_name"] = c_info->name; + params["classified_desc"] = c_info->description; + params["from_search"] = true; + + LLFloaterClassified* floaterp = LLFloaterReg::getTypedInstance("classified", params); + if (floaterp) + { + floaterp->openFloater(params); + floaterp->setVisibleAndFrontmost(); + } + } + } + } + + void processProperties(void* data, EAvatarProcessorType type) + { + if (APT_CLASSIFIED_INFO != type) + { + return; + } + + // is this the classified that we asked for? + LLAvatarClassifiedInfo* c_info = static_cast(data); + if (!c_info || mClassifiedIds.find(c_info->classified_id) == mClassifiedIds.end()) + { + return; + } + + // open the detail side tray for this classified + openClassified(c_info); + + // remove our observer now that we're done + mClassifiedIds.erase(c_info->classified_id); + LLAvatarPropertiesProcessor::getInstance()->removeObserver(LLUUID(), this); + } +}; +LLClassifiedHandler gClassifiedHandler; + +////////////////////////////////////////////////////////////////////////// + + +//----------------------------------------------------------------------------- +// LLPanelProfileClassifieds +//----------------------------------------------------------------------------- + +LLPanelProfileClassifieds::LLPanelProfileClassifieds() + : LLPanelProfilePropertiesProcessorTab() + , mClassifiedToSelectOnLoad(LLUUID::null) + , mClassifiedEditOnLoad(false) + , mSheduledClassifiedCreation(false) +{ +} + +LLPanelProfileClassifieds::~LLPanelProfileClassifieds() +{ +} + +void LLPanelProfileClassifieds::onOpen(const LLSD& key) +{ + LLPanelProfilePropertiesProcessorTab::onOpen(key); + + resetData(); + + bool own_profile = getSelfProfile(); + if (own_profile) + { + mNewButton->setVisible(true); + mNewButton->setEnabled(false); + + mDeleteButton->setVisible(true); + mDeleteButton->setEnabled(false); + } + + childSetVisible("buttons_header", own_profile); + +} + +void LLPanelProfileClassifieds::selectClassified(const LLUUID& classified_id, bool edit) +{ + if (getIsLoaded()) + { + for (S32 tab_idx = 0; tab_idx < mTabContainer->getTabCount(); ++tab_idx) + { + LLPanelProfileClassified* classified_panel = dynamic_cast(mTabContainer->getPanelByIndex(tab_idx)); + if (classified_panel) + { + if (classified_panel->getClassifiedId() == classified_id) + { + mTabContainer->selectTabPanel(classified_panel); + if (edit) + { + classified_panel->setEditMode(true); + } + break; + } + } + } + } + else + { + mClassifiedToSelectOnLoad = classified_id; + mClassifiedEditOnLoad = edit; + } +} + +void LLPanelProfileClassifieds::createClassified() +{ + if (getIsLoaded()) + { + mNoItemsLabel->setVisible(false); + LLPanelProfileClassified* classified_panel = LLPanelProfileClassified::create(); + classified_panel->onOpen(LLSD()); + mTabContainer->addTabPanel( + LLTabContainer::TabPanelParams(). + panel(classified_panel). + select_tab(true). + label(classified_panel->getClassifiedName())); + updateButtons(); + } + else + { + mSheduledClassifiedCreation = true; + } +} + +bool LLPanelProfileClassifieds::postBuild() +{ + mTabContainer = getChild("tab_classifieds"); + mNoItemsLabel = getChild("classifieds_panel_text"); + mNewButton = getChild("new_btn"); + mDeleteButton = getChild("delete_btn"); + + mNewButton->setCommitCallback(boost::bind(&LLPanelProfileClassifieds::onClickNewBtn, this)); + mDeleteButton->setCommitCallback(boost::bind(&LLPanelProfileClassifieds::onClickDelete, this)); + + return true; +} + +void LLPanelProfileClassifieds::onClickNewBtn() +{ + mNoItemsLabel->setVisible(false); + LLPanelProfileClassified* classified_panel = LLPanelProfileClassified::create(); + classified_panel->onOpen(LLSD()); + mTabContainer->addTabPanel( + LLTabContainer::TabPanelParams(). + panel(classified_panel). + select_tab(true). + label(classified_panel->getClassifiedName())); + updateButtons(); +} + +void LLPanelProfileClassifieds::onClickDelete() +{ + LLPanelProfileClassified* classified_panel = dynamic_cast(mTabContainer->getCurrentPanel()); + if (classified_panel) + { + LLUUID classified_id = classified_panel->getClassifiedId(); + LLSD args; + args["CLASSIFIED"] = classified_panel->getClassifiedName(); + LLSD payload; + payload["classified_id"] = classified_id; + payload["tab_idx"] = mTabContainer->getCurrentPanelIndex(); + LLNotificationsUtil::add("ProfileDeleteClassified", args, payload, + boost::bind(&LLPanelProfileClassifieds::callbackDeleteClassified, this, _1, _2)); + } +} + +void LLPanelProfileClassifieds::callbackDeleteClassified(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + + if (0 == option) + { + LLUUID classified_id = notification["payload"]["classified_id"].asUUID(); + S32 tab_idx = notification["payload"]["tab_idx"].asInteger(); + + LLPanelProfileClassified* classified_panel = dynamic_cast(mTabContainer->getPanelByIndex(tab_idx)); + if (classified_panel && classified_panel->getClassifiedId() == classified_id) + { + mTabContainer->removeTabPanel(classified_panel); + } + + if (classified_id.notNull()) + { + LLAvatarPropertiesProcessor::getInstance()->sendClassifiedDelete(classified_id); + } + + updateButtons(); + + bool no_data = !mTabContainer->getTabCount(); + mNoItemsLabel->setVisible(no_data); + } +} + +void LLPanelProfileClassifieds::processProperties(void* data, EAvatarProcessorType type) +{ + if ((APT_CLASSIFIEDS == type) || (APT_CLASSIFIED_INFO == type)) + { + LLUUID avatar_id = getAvatarId(); + + LLAvatarClassifieds* c_info = static_cast(data); + if (c_info && getAvatarId() == c_info->target_id) + { + // do not clear classified list in case we will receive two or more data packets. + // list has been cleared in updateData(). (fix for EXT-6436) + LLUUID selected_id = mClassifiedToSelectOnLoad; + bool has_selection = false; + + LLAvatarClassifieds::classifieds_list_t::const_iterator it = c_info->classifieds_list.begin(); + for (; c_info->classifieds_list.end() != it; ++it) + { + LLAvatarClassifieds::classified_data c_data = *it; + + LLPanelProfileClassified* classified_panel = LLPanelProfileClassified::create(); + + LLSD params; + params["classified_creator_id"] = avatar_id; + params["classified_id"] = c_data.classified_id; + params["classified_name"] = c_data.name; + params["from_search"] = (selected_id == c_data.classified_id); //SLURL handling and stats tracking + params["edit"] = (selected_id == c_data.classified_id) && mClassifiedEditOnLoad; + classified_panel->onOpen(params); + + mTabContainer->addTabPanel( + LLTabContainer::TabPanelParams(). + panel(classified_panel). + select_tab(selected_id == c_data.classified_id). + label(c_data.name)); + + if (selected_id == c_data.classified_id) + { + has_selection = true; + } + } + + if (mSheduledClassifiedCreation) + { + LLPanelProfileClassified* classified_panel = LLPanelProfileClassified::create(); + classified_panel->onOpen(LLSD()); + mTabContainer->addTabPanel( + LLTabContainer::TabPanelParams(). + panel(classified_panel). + select_tab(!has_selection). + label(classified_panel->getClassifiedName())); + has_selection = true; + } + + // reset 'do on load' values + mClassifiedToSelectOnLoad = LLUUID::null; + mClassifiedEditOnLoad = false; + mSheduledClassifiedCreation = false; + + // set even if not visible, user might delete own + // calassified and this string will need to be shown + if (getSelfProfile()) + { + mNoItemsLabel->setValue(LLTrans::getString("NoClassifiedsText")); + } + else + { + mNoItemsLabel->setValue(LLTrans::getString("NoAvatarClassifiedsText")); + } + + bool has_data = mTabContainer->getTabCount() > 0; + mNoItemsLabel->setVisible(!has_data); + if (has_data && !has_selection) + { + mTabContainer->selectFirstTab(); + } + + setLoaded(); + updateButtons(); + } + } +} + +void LLPanelProfileClassifieds::resetData() +{ + resetLoading(); + mTabContainer->deleteAllTabs(); +} + +void LLPanelProfileClassifieds::updateButtons() +{ + if (getSelfProfile()) + { + mNewButton->setEnabled(canAddNewClassified()); + mDeleteButton->setEnabled(canDeleteClassified()); + } +} + +void LLPanelProfileClassifieds::updateData() +{ + // Send picks request only once + LLUUID avatar_id = getAvatarId(); + if (!getStarted() && avatar_id.notNull()) + { + setIsLoading(); + mNoItemsLabel->setValue(LLTrans::getString("PicksClassifiedsLoadingText")); + mNoItemsLabel->setVisible(true); + + LLAvatarPropertiesProcessor::getInstance()->sendAvatarClassifiedsRequest(avatar_id); + } +} + +bool LLPanelProfileClassifieds::hasNewClassifieds() +{ + for (S32 tab_idx = 0; tab_idx < mTabContainer->getTabCount(); ++tab_idx) + { + LLPanelProfileClassified* classified_panel = dynamic_cast(mTabContainer->getPanelByIndex(tab_idx)); + if (classified_panel && classified_panel->isNew()) + { + return true; + } + } + return false; +} + +bool LLPanelProfileClassifieds::hasUnsavedChanges() +{ + for (S32 tab_idx = 0; tab_idx < mTabContainer->getTabCount(); ++tab_idx) + { + LLPanelProfileClassified* classified_panel = dynamic_cast(mTabContainer->getPanelByIndex(tab_idx)); + if (classified_panel && classified_panel->isDirty()) // includes 'new' + { + return true; + } + } + return false; +} + +bool LLPanelProfileClassifieds::canAddNewClassified() +{ + return (mTabContainer->getTabCount() < MAX_AVATAR_CLASSIFIEDS); +} + +bool LLPanelProfileClassifieds::canDeleteClassified() +{ + return (mTabContainer->getTabCount() > 0); +} + +void LLPanelProfileClassifieds::commitUnsavedChanges() +{ + if (getIsLoaded()) + { + for (S32 tab_idx = 0; tab_idx < mTabContainer->getTabCount(); ++tab_idx) + { + LLPanelProfileClassified* classified_panel = dynamic_cast(mTabContainer->getPanelByIndex(tab_idx)); + if (classified_panel && classified_panel->isDirty() && !classified_panel->isNew()) + { + classified_panel->doSave(); + } + } + } +} +//----------------------------------------------------------------------------- +// LLDispatchClassifiedClickThrough +//----------------------------------------------------------------------------- + +// "classifiedclickthrough" +// strings[0] = classified_id +// strings[1] = teleport_clicks +// strings[2] = map_clicks +// strings[3] = profile_clicks +class LLDispatchClassifiedClickThrough : public LLDispatchHandler +{ +public: + virtual bool operator()( + const LLDispatcher* dispatcher, + const std::string& key, + const LLUUID& invoice, + const sparam_t& strings) + { + if (strings.size() != 4) return false; + LLUUID classified_id(strings[0]); + S32 teleport_clicks = atoi(strings[1].c_str()); + S32 map_clicks = atoi(strings[2].c_str()); + S32 profile_clicks = atoi(strings[3].c_str()); + + LLPanelProfileClassified::setClickThrough( + classified_id, teleport_clicks, map_clicks, profile_clicks, false); + + return true; + } +}; +static LLDispatchClassifiedClickThrough sClassifiedClickThrough; + + +//----------------------------------------------------------------------------- +// LLPanelProfileClassified +//----------------------------------------------------------------------------- + +static const S32 CB_ITEM_MATURE = 0; +static const S32 CB_ITEM_PG = 1; + +LLPanelProfileClassified::LLPanelProfileClassified() + : LLPanelProfilePropertiesProcessorTab() + , mInfoLoaded(false) + , mTeleportClicksOld(0) + , mMapClicksOld(0) + , mProfileClicksOld(0) + , mTeleportClicksNew(0) + , mMapClicksNew(0) + , mProfileClicksNew(0) + , mPriceForListing(0) + , mSnapshotCtrl(NULL) + , mPublishFloater(NULL) + , mIsNew(false) + , mIsNewWithErrors(false) + , mCanClose(false) + , mEditMode(false) + , mEditOnLoad(false) +{ + sAllPanels.push_back(this); +} + +LLPanelProfileClassified::~LLPanelProfileClassified() +{ + sAllPanels.remove(this); + gGenericDispatcher.addHandler("classifiedclickthrough", NULL); // deregister our handler +} + +//static +LLPanelProfileClassified* LLPanelProfileClassified::create() +{ + LLPanelProfileClassified* panel = new LLPanelProfileClassified(); + panel->buildFromFile("panel_profile_classified.xml"); + return panel; +} + +bool LLPanelProfileClassified::postBuild() +{ + mScrollContainer = getChild("profile_scroll"); + mInfoPanel = getChild("info_panel"); + mInfoScroll = getChild("info_scroll_content_panel"); + mEditPanel = getChild("edit_panel"); + + mSnapshotCtrl = getChild("classified_snapshot"); + mEditIcon = getChild("edit_icon"); + + //info + mClassifiedNameText = getChild("classified_name"); + mClassifiedDescText = getChild("classified_desc"); + mLocationText = getChild("classified_location"); + mCategoryText = getChild("category"); + mContentTypeText = getChild("content_type"); + mContentTypeM = getChild("content_type_moderate"); + mContentTypeG = getChild("content_type_general"); + mPriceText = getChild("price_for_listing"); + mAutoRenewText = getChild("auto_renew"); + + mMapButton = getChild("show_on_map_btn"); + mTeleportButton = getChild("teleport_btn"); + mEditButton = getChild("edit_btn"); + + //edit + mClassifiedNameEdit = getChild("classified_name_edit"); + mClassifiedDescEdit = getChild("classified_desc_edit"); + mLocationEdit = getChild("classified_location_edit"); + mCategoryCombo = getChild("category_edit"); + mContentTypeCombo = getChild("content_type_edit"); + mAutoRenewEdit = getChild("auto_renew_edit"); + + mSaveButton = getChild("save_changes_btn"); + mSetLocationButton = getChild("set_to_curr_location_btn"); + mCancelButton = getChild("cancel_btn"); + + mUtilityBtnCnt = getChild("util_buttons_lp"); + mPublishBtnsCnt = getChild("publish_layout_panel"); + mCancelBtnCnt = getChild("cancel_btn_lp"); + mSaveBtnCnt = getChild("save_btn_lp"); + + mSnapshotCtrl->setOnSelectCallback(boost::bind(&LLPanelProfileClassified::onTextureSelected, this)); + mSnapshotCtrl->setMouseEnterCallback(boost::bind(&LLPanelProfileClassified::onTexturePickerMouseEnter, this)); + mSnapshotCtrl->setMouseLeaveCallback(boost::bind(&LLPanelProfileClassified::onTexturePickerMouseLeave, this)); + mSnapshotCtrl->setAllowLocalTexture(false); + mSnapshotCtrl->setBakeTextureEnabled(false); + mEditIcon->setVisible(false); + + mMapButton->setCommitCallback(boost::bind(&LLPanelProfileClassified::onMapClick, this)); + mTeleportButton->setCommitCallback(boost::bind(&LLPanelProfileClassified::onTeleportClick, this)); + mEditButton->setCommitCallback(boost::bind(&LLPanelProfileClassified::onEditClick, this)); + mSaveButton->setCommitCallback(boost::bind(&LLPanelProfileClassified::onSaveClick, this)); + mSetLocationButton->setCommitCallback(boost::bind(&LLPanelProfileClassified::onSetLocationClick, this)); + mCancelButton->setCommitCallback(boost::bind(&LLPanelProfileClassified::onCancelClick, this)); + + LLClassifiedInfo::cat_map::iterator iter; + for (iter = LLClassifiedInfo::sCategories.begin(); + iter != LLClassifiedInfo::sCategories.end(); + iter++) + { + mCategoryCombo->add(LLTrans::getString(iter->second)); + } + + mClassifiedNameEdit->setKeystrokeCallback(boost::bind(&LLPanelProfileClassified::onTitleChange, this), NULL); + mClassifiedDescEdit->setKeystrokeCallback(boost::bind(&LLPanelProfileClassified::onChange, this)); + mCategoryCombo->setCommitCallback(boost::bind(&LLPanelProfileClassified::onChange, this)); + mContentTypeCombo->setCommitCallback(boost::bind(&LLPanelProfileClassified::onChange, this)); + mAutoRenewEdit->setCommitCallback(boost::bind(&LLPanelProfileClassified::onChange, this)); + + return true; +} + +void LLPanelProfileClassified::onOpen(const LLSD& key) +{ + mIsNew = key.isUndefined(); + + resetData(); + resetControls(); + scrollToTop(); + + // classified is not created yet + bool is_new = isNew() || isNewWithErrors(); + + if(is_new) + { + LLPanelProfilePropertiesProcessorTab::setAvatarId(gAgent.getID()); + + setPosGlobal(gAgent.getPositionGlobal()); + + LLUUID snapshot_id = LLUUID::null; + std::string desc; + LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if(parcel) + { + desc = parcel->getDesc(); + snapshot_id = parcel->getSnapshotID(); + } + + std::string region_name = LLTrans::getString("ClassifiedUpdateAfterPublish"); + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + region_name = region->getName(); + } + + setClassifiedName(makeClassifiedName()); + setDescription(desc); + setSnapshotId(snapshot_id); + setClassifiedLocation(createLocationText(getLocationNotice(), region_name, getPosGlobal())); + // server will set valid parcel id + setParcelId(LLUUID::null); + + mSaveButton->setLabelArg("[LABEL]", getString("publish_label")); + + setEditMode(true); + enableSave(true); + enableEditing(true); + resetDirty(); + setInfoLoaded(false); + } + else + { + LLUUID avatar_id = key["classified_creator_id"]; + if(avatar_id.isNull()) + { + return; + } + LLPanelProfilePropertiesProcessorTab::setAvatarId(avatar_id); + + setClassifiedId(key["classified_id"]); + setClassifiedName(key["classified_name"]); + setFromSearch(key["from_search"]); + mEditOnLoad = key["edit"]; + + LL_INFOS() << "Opening classified [" << getClassifiedName() << "] (" << getClassifiedId() << ")" << LL_ENDL; + + LLAvatarPropertiesProcessor::getInstance()->sendClassifiedInfoRequest(getClassifiedId()); + + gGenericDispatcher.addHandler("classifiedclickthrough", &sClassifiedClickThrough); + + if (gAgent.getRegion()) + { + // While we're at it let's get the stats from the new table if that + // capability exists. + std::string url = gAgent.getRegion()->getCapability("SearchStatRequest"); + if (!url.empty()) + { + LL_INFOS() << "Classified stat request via capability" << LL_ENDL; + LLSD body; + LLUUID classifiedId = getClassifiedId(); + body["classified_id"] = classifiedId; + LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost(url, body, + boost::bind(&LLPanelProfileClassified::handleSearchStatResponse, classifiedId, _1)); + } + } + // Update classified click stats. + // *TODO: Should we do this when opening not from search? + if (!fromSearch() ) + { + sendClickMessage("profile"); + } + + setInfoLoaded(false); + } + + + bool is_self = getSelfProfile(); + getChildView("auto_renew_layout_panel")->setVisible(is_self); + getChildView("clickthrough_layout_panel")->setVisible(is_self); + + updateButtons(); +} + +void LLPanelProfileClassified::processProperties(void* data, EAvatarProcessorType type) +{ + if (APT_CLASSIFIED_INFO != type) + { + return; + } + + LLAvatarClassifiedInfo* c_info = static_cast(data); + if(c_info && getClassifiedId() == c_info->classified_id) + { + // see LLPanelProfileClassified::sendUpdate() for notes + if (mIsNewWithErrors) + { + // We just published it + setEditMode(false); + } + mIsNewWithErrors = false; + mIsNew = false; + + setClassifiedName(c_info->name); + setDescription(c_info->description); + setSnapshotId(c_info->snapshot_id); + setParcelId(c_info->parcel_id); + setPosGlobal(c_info->pos_global); + setSimName(c_info->sim_name); + + setClassifiedLocation(createLocationText(c_info->parcel_name, c_info->sim_name, c_info->pos_global)); + + mCategoryText->setValue(LLClassifiedInfo::sCategories[c_info->category]); + // *HACK see LLPanelProfileClassified::sendUpdate() + setCategory(c_info->category - 1); + + bool mature = is_cf_mature(c_info->flags); + setContentType(mature); + + bool auto_renew = is_cf_auto_renew(c_info->flags); + std::string auto_renew_str = auto_renew ? getString("auto_renew_on") : getString("auto_renew_off"); + mAutoRenewText->setValue(auto_renew_str); + mAutoRenewEdit->setValue(auto_renew); + + static LLUIString price_str = getString("l$_price"); + price_str.setArg("[PRICE]", llformat("%d", c_info->price_for_listing)); + mPriceText->setValue(LLSD(price_str)); + + static std::string date_fmt = getString("date_fmt"); + std::string date_str = date_fmt; + LLStringUtil::format(date_str, LLSD().with("datetime", (S32) c_info->creation_date)); + getChild("creation_date")->setValue(date_str); + + resetDirty(); + setInfoLoaded(true); + enableSave(false); + enableEditing(true); + + // for just created classified - in case user opened edit panel before processProperties() callback + mSaveButton->setLabelArg("[LABEL]", getString("save_label")); + + setLoaded(); + updateButtons(); + + if (mEditOnLoad) + { + setEditMode(true); + } + } + +} + +void LLPanelProfileClassified::setEditMode(bool edit_mode) +{ + mEditMode = edit_mode; + + mInfoPanel->setVisible(!edit_mode); + mEditPanel->setVisible(edit_mode); + + // snapshot control is common between info and edit, + // enable it only when in edit mode + mSnapshotCtrl->setEnabled(edit_mode); + + scrollToTop(); + updateButtons(); + updateInfoRect(); +} + +void LLPanelProfileClassified::updateButtons() +{ + bool edit_mode = getEditMode(); + mUtilityBtnCnt->setVisible(!edit_mode); + + // cancel button should either delete unpublished + // classified or not be there at all + mCancelBtnCnt->setVisible(edit_mode && !mIsNew); + mPublishBtnsCnt->setVisible(edit_mode); + mSaveBtnCnt->setVisible(edit_mode); + mEditButton->setVisible(!edit_mode && getSelfProfile()); +} + +void LLPanelProfileClassified::updateInfoRect() +{ + if (getEditMode()) + { + // info_scroll_content_panel contains both info and edit panel + // info panel can be very large and scroll bar will carry over. + // Resize info panel to prevent scroll carry over when in edit mode. + mInfoScroll->reshape(mInfoScroll->getRect().getWidth(), DEFAULT_EDIT_CLASSIFIED_SCROLL_HEIGHT, false); + } + else + { + // Adjust text height to make description scrollable. + S32 new_height = mClassifiedDescText->getTextBoundingRect().getHeight(); + LLRect visible_rect = mClassifiedDescText->getVisibleDocumentRect(); + S32 delta_height = new_height - visible_rect.getHeight() + 5; + + LLRect rect = mInfoScroll->getRect(); + mInfoScroll->reshape(rect.getWidth(), rect.getHeight() + delta_height, false); + } +} + +void LLPanelProfileClassified::enableEditing(bool enable) +{ + mEditButton->setEnabled(enable); + mClassifiedNameEdit->setEnabled(enable); + mClassifiedDescEdit->setEnabled(enable); + mSetLocationButton->setEnabled(enable); + mCategoryCombo->setEnabled(enable); + mContentTypeCombo->setEnabled(enable); + mAutoRenewEdit->setEnabled(enable); +} + +void LLPanelProfileClassified::resetControls() +{ + updateButtons(); + + mCategoryCombo->setCurrentByIndex(0); + mContentTypeCombo->setCurrentByIndex(0); + mAutoRenewEdit->setValue(false); + mPriceForListing = MINIMUM_PRICE_FOR_LISTING; +} + +void LLPanelProfileClassified::onEditClick() +{ + setEditMode(true); +} + +void LLPanelProfileClassified::onCancelClick() +{ + if (isNew()) + { + mClassifiedNameEdit->setValue(mClassifiedNameText->getValue()); + mClassifiedDescEdit->setValue(mClassifiedDescText->getValue()); + mLocationEdit->setValue(mLocationText->getValue()); + mCategoryCombo->setCurrentByIndex(0); + mContentTypeCombo->setCurrentByIndex(0); + mAutoRenewEdit->setValue(false); + mPriceForListing = MINIMUM_PRICE_FOR_LISTING; + } + else + { + updateTabLabel(mClassifiedNameText->getValue()); + + // Reload data to undo changes to forms + LLAvatarPropertiesProcessor::getInstance()->sendClassifiedInfoRequest(getClassifiedId()); + } + + setInfoLoaded(false); + + setEditMode(false); +} + +void LLPanelProfileClassified::onSaveClick() +{ + mCanClose = false; + + if(!isValidName()) + { + notifyInvalidName(); + return; + } + if(isNew() || isNewWithErrors()) + { + if(gStatusBar->getBalance() < MINIMUM_PRICE_FOR_LISTING) + { + LLNotificationsUtil::add("ClassifiedInsufficientFunds"); + return; + } + + mPublishFloater = LLFloaterReg::findTypedInstance( + "publish_classified", LLSD()); + + if(!mPublishFloater) + { + mPublishFloater = LLFloaterReg::getTypedInstance( + "publish_classified", LLSD()); + + mPublishFloater->setPublishClickedCallback(boost::bind + (&LLPanelProfileClassified::onPublishFloaterPublishClicked, this)); + } + + // set spinner value before it has focus or value wont be set + mPublishFloater->setPrice(getPriceForListing()); + mPublishFloater->openFloater(mPublishFloater->getKey()); + mPublishFloater->center(); + } + else + { + doSave(); + } +} + +/*static*/ +void LLPanelProfileClassified::handleSearchStatResponse(LLUUID classifiedId, LLSD result) +{ + S32 teleport = result["teleport_clicks"].asInteger(); + S32 map = result["map_clicks"].asInteger(); + S32 profile = result["profile_clicks"].asInteger(); + S32 search_teleport = result["search_teleport_clicks"].asInteger(); + S32 search_map = result["search_map_clicks"].asInteger(); + S32 search_profile = result["search_profile_clicks"].asInteger(); + + LLPanelProfileClassified::setClickThrough(classifiedId, + teleport + search_teleport, + map + search_map, + profile + search_profile, + true); +} + +void LLPanelProfileClassified::resetData() +{ + setClassifiedName(LLStringUtil::null); + setDescription(LLStringUtil::null); + setClassifiedLocation(LLStringUtil::null); + setClassifiedId(LLUUID::null); + setSnapshotId(LLUUID::null); + setPosGlobal(LLVector3d::zero); + setParcelId(LLUUID::null); + setSimName(LLStringUtil::null); + setFromSearch(false); + + // reset click stats + mTeleportClicksOld = 0; + mMapClicksOld = 0; + mProfileClicksOld = 0; + mTeleportClicksNew = 0; + mMapClicksNew = 0; + mProfileClicksNew = 0; + + mPriceForListing = MINIMUM_PRICE_FOR_LISTING; + + mCategoryText->setValue(LLStringUtil::null); + mContentTypeText->setValue(LLStringUtil::null); + getChild("click_through_text")->setValue(LLStringUtil::null); + mEditButton->setValue(LLStringUtil::null); + getChild("creation_date")->setValue(LLStringUtil::null); + mContentTypeM->setVisible(false); + mContentTypeG->setVisible(false); +} + +void LLPanelProfileClassified::setClassifiedName(const std::string& name) +{ + mClassifiedNameText->setValue(name); + mClassifiedNameEdit->setValue(name); +} + +std::string LLPanelProfileClassified::getClassifiedName() +{ + return mClassifiedNameEdit->getValue().asString(); +} + +void LLPanelProfileClassified::setDescription(const std::string& desc) +{ + mClassifiedDescText->setValue(desc); + mClassifiedDescEdit->setValue(desc); + + updateInfoRect(); +} + +std::string LLPanelProfileClassified::getDescription() +{ + return mClassifiedDescEdit->getValue().asString(); +} + +void LLPanelProfileClassified::setClassifiedLocation(const std::string& location) +{ + mLocationText->setValue(location); + mLocationEdit->setValue(location); +} + +std::string LLPanelProfileClassified::getClassifiedLocation() +{ + return mLocationText->getValue().asString(); +} + +void LLPanelProfileClassified::setSnapshotId(const LLUUID& id) +{ + mSnapshotCtrl->setValue(id); +} + +LLUUID LLPanelProfileClassified::getSnapshotId() +{ + return mSnapshotCtrl->getValue().asUUID(); +} + +// static +void LLPanelProfileClassified::setClickThrough( + const LLUUID& classified_id, + S32 teleport, + S32 map, + S32 profile, + bool from_new_table) +{ + LL_INFOS() << "Click-through data for classified " << classified_id << " arrived: [" + << teleport << ", " << map << ", " << profile << "] (" + << (from_new_table ? "new" : "old") << ")" << LL_ENDL; + + for (panel_list_t::iterator iter = sAllPanels.begin(); iter != sAllPanels.end(); ++iter) + { + LLPanelProfileClassified* self = *iter; + if (self->getClassifiedId() != classified_id) + { + continue; + } + + // *HACK: Skip LLPanelProfileClassified instances: they don't display clicks data. + // Those instances should not be in the list at all. + if (typeid(*self) != typeid(LLPanelProfileClassified)) + { + continue; + } + + LL_INFOS() << "Updating classified info panel" << LL_ENDL; + + // We need to check to see if the data came from the new stat_table + // or the old classified table. We also need to cache the data from + // the two separate sources so as to display the aggregate totals. + + if (from_new_table) + { + self->mTeleportClicksNew = teleport; + self->mMapClicksNew = map; + self->mProfileClicksNew = profile; + } + else + { + self->mTeleportClicksOld = teleport; + self->mMapClicksOld = map; + self->mProfileClicksOld = profile; + } + + static LLUIString ct_str = self->getString("click_through_text_fmt"); + + ct_str.setArg("[TELEPORT]", llformat("%d", self->mTeleportClicksNew + self->mTeleportClicksOld)); + ct_str.setArg("[MAP]", llformat("%d", self->mMapClicksNew + self->mMapClicksOld)); + ct_str.setArg("[PROFILE]", llformat("%d", self->mProfileClicksNew + self->mProfileClicksOld)); + + self->getChild("click_through_text")->setValue(ct_str.getString()); + // *HACK: remove this when there is enough room for click stats in the info panel + self->getChildView("click_through_text")->setToolTip(ct_str.getString()); + + LL_INFOS() << "teleport: " << llformat("%d", self->mTeleportClicksNew + self->mTeleportClicksOld) + << ", map: " << llformat("%d", self->mMapClicksNew + self->mMapClicksOld) + << ", profile: " << llformat("%d", self->mProfileClicksNew + self->mProfileClicksOld) + << LL_ENDL; + } +} + +// static +std::string LLPanelProfileClassified::createLocationText( + const std::string& original_name, + const std::string& sim_name, + const LLVector3d& pos_global) +{ + std::string location_text; + + location_text.append(original_name); + + if (!sim_name.empty()) + { + if (!location_text.empty()) + location_text.append(", "); + location_text.append(sim_name); + } + + if (!location_text.empty()) + location_text.append(" "); + + if (!pos_global.isNull()) + { + S32 region_x = ll_round((F32)pos_global.mdV[VX]) % REGION_WIDTH_UNITS; + S32 region_y = ll_round((F32)pos_global.mdV[VY]) % REGION_WIDTH_UNITS; + S32 region_z = ll_round((F32)pos_global.mdV[VZ]); + location_text.append(llformat(" (%d, %d, %d)", region_x, region_y, region_z)); + } + + return location_text; +} + +void LLPanelProfileClassified::scrollToTop() +{ + if (mScrollContainer) + { + mScrollContainer->goToTop(); + } +} + +//info +// static +// *TODO: move out of the panel +void LLPanelProfileClassified::sendClickMessage( + const std::string& type, + bool from_search, + const LLUUID& classified_id, + const LLUUID& parcel_id, + const LLVector3d& global_pos, + const std::string& sim_name) +{ + if (gAgent.getRegion()) + { + // You're allowed to click on your own ads to reassure yourself + // that the system is working. + LLSD body; + body["type"] = type; + body["from_search"] = from_search; + body["classified_id"] = classified_id; + body["parcel_id"] = parcel_id; + body["dest_pos_global"] = global_pos.getValue(); + body["region_name"] = sim_name; + + std::string url = gAgent.getRegion()->getCapability("SearchStatTracking"); + LL_INFOS() << "Sending click msg via capability (url=" << url << ")" << LL_ENDL; + LL_INFOS() << "body: [" << body << "]" << LL_ENDL; + LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, body, + "SearchStatTracking Click report sent.", "SearchStatTracking Click report NOT sent."); + } +} + +void LLPanelProfileClassified::sendClickMessage(const std::string& type) +{ + sendClickMessage( + type, + fromSearch(), + getClassifiedId(), + getParcelId(), + getPosGlobal(), + getSimName()); +} + +void LLPanelProfileClassified::onMapClick() +{ + sendClickMessage("map"); + LLFloaterWorldMap::getInstance()->trackLocation(getPosGlobal()); + LLFloaterReg::showInstance("world_map", "center"); +} + +void LLPanelProfileClassified::onTeleportClick() +{ + if (!getPosGlobal().isExactlyZero()) + { + sendClickMessage("teleport"); + gAgent.teleportViaLocation(getPosGlobal()); + LLFloaterWorldMap::getInstance()->trackLocation(getPosGlobal()); + } +} + +bool LLPanelProfileClassified::isDirty() const +{ + if(mIsNew) + { + return true; + } + + bool dirty = false; + dirty |= mSnapshotCtrl->isDirty(); + dirty |= mClassifiedNameEdit->isDirty(); + dirty |= mClassifiedDescEdit->isDirty(); + dirty |= mCategoryCombo->isDirty(); + dirty |= mContentTypeCombo->isDirty(); + dirty |= mAutoRenewEdit->isDirty(); + + return dirty; +} + +void LLPanelProfileClassified::resetDirty() +{ + mSnapshotCtrl->resetDirty(); + mClassifiedNameEdit->resetDirty(); + + // call blockUndo() to really reset dirty(and make isDirty work as intended) + mClassifiedDescEdit->blockUndo(); + mClassifiedDescEdit->resetDirty(); + + mCategoryCombo->resetDirty(); + mContentTypeCombo->resetDirty(); + mAutoRenewEdit->resetDirty(); +} + +bool LLPanelProfileClassified::canClose() +{ + return mCanClose; +} + +U32 LLPanelProfileClassified::getContentType() +{ + return mContentTypeCombo->getCurrentIndex(); +} + +void LLPanelProfileClassified::setContentType(bool mature) +{ + static std::string mature_str = getString("type_mature"); + static std::string pg_str = getString("type_pg"); + mContentTypeText->setValue(mature ? mature_str : pg_str); + mContentTypeM->setVisible(mature); + mContentTypeG->setVisible(!mature); + mContentTypeCombo->setCurrentByIndex(mature ? CB_ITEM_MATURE : CB_ITEM_PG); + mContentTypeCombo->resetDirty(); +} + +bool LLPanelProfileClassified::getAutoRenew() +{ + return mAutoRenewEdit->getValue().asBoolean(); +} + +void LLPanelProfileClassified::sendUpdate() +{ + LLAvatarClassifiedInfo c_data; + + if(getClassifiedId().isNull()) + { + setClassifiedId(LLUUID::generateNewID()); + } + + c_data.agent_id = gAgent.getID(); + c_data.classified_id = getClassifiedId(); + // *HACK + // Categories on server start with 1 while combo-box index starts with 0 + c_data.category = getCategory() + 1; + c_data.name = getClassifiedName(); + c_data.description = getDescription(); + c_data.parcel_id = getParcelId(); + c_data.snapshot_id = getSnapshotId(); + c_data.pos_global = getPosGlobal(); + c_data.flags = getFlags(); + c_data.price_for_listing = getPriceForListing(); + + LLAvatarPropertiesProcessor::getInstance()->sendClassifiedInfoUpdate(&c_data); + + if(isNew()) + { + // Lets assume there will be some error. + // Successful sendClassifiedInfoUpdate will trigger processProperties and + // let us know there was no error. + mIsNewWithErrors = true; + } +} + +U32 LLPanelProfileClassified::getCategory() +{ + return mCategoryCombo->getCurrentIndex(); +} + +void LLPanelProfileClassified::setCategory(U32 category) +{ + mCategoryCombo->setCurrentByIndex(category); + mCategoryCombo->resetDirty(); +} + +U8 LLPanelProfileClassified::getFlags() +{ + bool auto_renew = mAutoRenewEdit->getValue().asBoolean(); + + bool mature = mContentTypeCombo->getCurrentIndex() == CB_ITEM_MATURE; + + return pack_classified_flags_request(auto_renew, false, mature, false); +} + +void LLPanelProfileClassified::enableSave(bool enable) +{ + mSaveButton->setEnabled(enable); +} + +std::string LLPanelProfileClassified::makeClassifiedName() +{ + std::string name; + + LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if(parcel) + { + name = parcel->getName(); + } + + if(!name.empty()) + { + return name; + } + + LLViewerRegion* region = gAgent.getRegion(); + if(region) + { + name = region->getName(); + } + + return name; +} + +void LLPanelProfileClassified::onSetLocationClick() +{ + setPosGlobal(gAgent.getPositionGlobal()); + setParcelId(LLUUID::null); + + std::string region_name = LLTrans::getString("ClassifiedUpdateAfterPublish"); + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + region_name = region->getName(); + } + + setClassifiedLocation(createLocationText(getLocationNotice(), region_name, getPosGlobal())); + + // mark classified as dirty + setValue(LLSD()); + + onChange(); +} + +void LLPanelProfileClassified::onChange() +{ + enableSave(isDirty()); +} + +void LLPanelProfileClassified::onTitleChange() +{ + updateTabLabel(getClassifiedName()); + onChange(); +} + +void LLPanelProfileClassified::doSave() +{ + //*TODO: Fix all of this + + mCanClose = true; + sendUpdate(); + updateTabLabel(getClassifiedName()); + resetDirty(); + + if (!canClose()) + { + return; + } + + if (!isNew() && !isNewWithErrors()) + { + setEditMode(false); + return; + } + + updateButtons(); +} + +void LLPanelProfileClassified::onPublishFloaterPublishClicked() +{ + if (mPublishFloater->getPrice() < MINIMUM_PRICE_FOR_LISTING) + { + LLSD args; + args["MIN_PRICE"] = MINIMUM_PRICE_FOR_LISTING; + LLNotificationsUtil::add("MinClassifiedPrice", args); + return; + } + + setPriceForListing(mPublishFloater->getPrice()); + + doSave(); +} + +std::string LLPanelProfileClassified::getLocationNotice() +{ + static std::string location_notice = getString("location_notice"); + return location_notice; +} + +bool LLPanelProfileClassified::isValidName() +{ + std::string name = getClassifiedName(); + if (name.empty()) + { + return false; + } + if (!isalnum(name[0])) + { + return false; + } + + return true; +} + +void LLPanelProfileClassified::notifyInvalidName() +{ + std::string name = getClassifiedName(); + if (name.empty()) + { + LLNotificationsUtil::add("BlankClassifiedName"); + } + else if (!isalnum(name[0])) + { + LLNotificationsUtil::add("ClassifiedMustBeAlphanumeric"); + } +} + +void LLPanelProfileClassified::onTexturePickerMouseEnter() +{ + mEditIcon->setVisible(true); +} + +void LLPanelProfileClassified::onTexturePickerMouseLeave() +{ + mEditIcon->setVisible(false); +} + +void LLPanelProfileClassified::onTextureSelected() +{ + setSnapshotId(mSnapshotCtrl->getValue().asUUID()); + onChange(); +} + +void LLPanelProfileClassified::updateTabLabel(const std::string& title) +{ + setLabel(title); + LLTabContainer* parent = dynamic_cast(getParent()); + if (parent) + { + parent->setCurrentTabName(title); + } +} + + +//----------------------------------------------------------------------------- +// LLPublishClassifiedFloater +//----------------------------------------------------------------------------- + +LLPublishClassifiedFloater::LLPublishClassifiedFloater(const LLSD& key) + : LLFloater(key) +{ +} + +LLPublishClassifiedFloater::~LLPublishClassifiedFloater() +{ +} + +bool LLPublishClassifiedFloater::postBuild() +{ + LLFloater::postBuild(); + + childSetAction("publish_btn", boost::bind(&LLFloater::closeFloater, this, false)); + childSetAction("cancel_btn", boost::bind(&LLFloater::closeFloater, this, false)); + + return true; +} + +void LLPublishClassifiedFloater::setPrice(S32 price) +{ + getChild("price_for_listing")->setValue(price); +} + +S32 LLPublishClassifiedFloater::getPrice() +{ + return getChild("price_for_listing")->getValue().asInteger(); +} + +void LLPublishClassifiedFloater::setPublishClickedCallback(const commit_signal_t::slot_type& cb) +{ + getChild("publish_btn")->setClickedCallback(cb); +} + +void LLPublishClassifiedFloater::setCancelClickedCallback(const commit_signal_t::slot_type& cb) +{ + getChild("cancel_btn")->setClickedCallback(cb); +} diff --git a/indra/newview/llpanelsnapshot.cpp b/indra/newview/llpanelsnapshot.cpp index 5dc5a7437a..2536dce606 100644 --- a/indra/newview/llpanelsnapshot.cpp +++ b/indra/newview/llpanelsnapshot.cpp @@ -1,245 +1,245 @@ -/** - * @file llpanelsnapshot.cpp - * @brief Snapshot panel base class - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llpanelsnapshot.h" - -// libs -#include "llcombobox.h" -#include "llfloater.h" -#include "llfloatersnapshot.h" -#include "llsliderctrl.h" -#include "llspinctrl.h" -#include "lltrans.h" - -// newview -#include "llsidetraypanelcontainer.h" -#include "llviewercontrol.h" // gSavedSettings - -#include "llagentbenefits.h" - -constexpr S32 MAX_TEXTURE_SIZE = 512 ; //max upload texture size 512 * 512 - -S32 power_of_two(S32 sz, S32 upper) -{ - S32 res = upper; - while( upper >= sz) - { - res = upper; - upper >>= 1; - } - return res; -} - -LLPanelSnapshot::LLPanelSnapshot() - : mSnapshotFloater(NULL) -{} - -// virtual -bool LLPanelSnapshot::postBuild() -{ - getChild("save_btn")->setLabelArg("[UPLOAD_COST]", std::to_string(LLAgentBenefitsMgr::current().getTextureUploadCost())); - getChild(getImageSizeComboName())->setCommitCallback(boost::bind(&LLPanelSnapshot::onResolutionComboCommit, this, _1)); - if (!getWidthSpinnerName().empty()) - { - getChild(getWidthSpinnerName())->setCommitCallback(boost::bind(&LLPanelSnapshot::onCustomResolutionCommit, this)); - } - if (!getHeightSpinnerName().empty()) - { - getChild(getHeightSpinnerName())->setCommitCallback(boost::bind(&LLPanelSnapshot::onCustomResolutionCommit, this)); - } - if (!getAspectRatioCBName().empty()) - { - getChild(getAspectRatioCBName())->setCommitCallback(boost::bind(&LLPanelSnapshot::onKeepAspectRatioCommit, this, _1)); - } - updateControls(LLSD()); - - mSnapshotFloater = getParentByType(); - return true; -} - -// virtual -void LLPanelSnapshot::onOpen(const LLSD& key) -{ - S32 old_format = gSavedSettings.getS32("SnapshotFormat"); - S32 new_format = (S32) getImageFormat(); - - gSavedSettings.setS32("SnapshotFormat", new_format); - setCtrlsEnabled(true); - - // Switching panels will likely change image format. - // Not updating preview right away may lead to errors, - // e.g. attempt to send a large BMP image by email. - if (old_format != new_format) - { - getParentByType()->notify(LLSD().with("image-format-change", true)); - } -} - -LLSnapshotModel::ESnapshotFormat LLPanelSnapshot::getImageFormat() const -{ - return LLSnapshotModel::SNAPSHOT_FORMAT_JPEG; -} - -void LLPanelSnapshot::enableControls(bool enable) -{ - setCtrlsEnabled(enable); -} - -LLSpinCtrl* LLPanelSnapshot::getWidthSpinner() -{ - llassert(!getWidthSpinnerName().empty()); - return getChild(getWidthSpinnerName()); -} - -LLSpinCtrl* LLPanelSnapshot::getHeightSpinner() -{ - llassert(!getHeightSpinnerName().empty()); - return getChild(getHeightSpinnerName()); -} - -S32 LLPanelSnapshot::getTypedPreviewWidth() const -{ - llassert(!getWidthSpinnerName().empty()); - return getChild(getWidthSpinnerName())->getValue().asInteger(); -} - -S32 LLPanelSnapshot::getTypedPreviewHeight() const -{ - llassert(!getHeightSpinnerName().empty()); - return getChild(getHeightSpinnerName())->getValue().asInteger(); -} - -void LLPanelSnapshot::enableAspectRatioCheckbox(bool enable) -{ - llassert(!getAspectRatioCBName().empty()); - getChild(getAspectRatioCBName())->setEnabled(enable); -} - -LLSideTrayPanelContainer* LLPanelSnapshot::getParentContainer() -{ - LLSideTrayPanelContainer* parent = dynamic_cast(getParent()); - if (!parent) - { - LL_WARNS() << "Cannot find panel container" << LL_ENDL; - return NULL; - } - - return parent; -} - -void LLPanelSnapshot::updateImageQualityLevel() -{ - LLSliderCtrl* quality_slider = getChild("image_quality_slider"); - S32 quality_val = llfloor((F32) quality_slider->getValue().asReal()); - - std::string quality_lvl; - - if (quality_val < 20) - { - quality_lvl = LLTrans::getString("snapshot_quality_very_low"); - } - else if (quality_val < 40) - { - quality_lvl = LLTrans::getString("snapshot_quality_low"); - } - else if (quality_val < 60) - { - quality_lvl = LLTrans::getString("snapshot_quality_medium"); - } - else if (quality_val < 80) - { - quality_lvl = LLTrans::getString("snapshot_quality_high"); - } - else - { - quality_lvl = LLTrans::getString("snapshot_quality_very_high"); - } - - getChild("image_quality_level")->setTextArg("[QLVL]", quality_lvl); -} - -void LLPanelSnapshot::goBack() -{ - LLSideTrayPanelContainer* parent = getParentContainer(); - if (parent) - { - parent->openPreviousPanel(); - parent->getCurrentPanel()->onOpen(LLSD()); - } -} - -void LLPanelSnapshot::cancel() -{ - goBack(); - getParentByType()->notify(LLSD().with("set-ready", true)); -} - -void LLPanelSnapshot::onCustomResolutionCommit() -{ - LLSD info; - std::string widthSpinnerName = getWidthSpinnerName(); - std::string heightSpinnerName = getHeightSpinnerName(); - llassert(!widthSpinnerName.empty() && !heightSpinnerName.empty()); - LLSpinCtrl *widthSpinner = getChild(widthSpinnerName); - LLSpinCtrl *heightSpinner = getChild(heightSpinnerName); - if (getName() == "panel_snapshot_inventory") - { - S32 width = widthSpinner->getValue().asInteger(); - width = power_of_two(width, MAX_TEXTURE_SIZE); - info["w"] = width; - widthSpinner->setIncrement(width >> 1); - widthSpinner->forceSetValue(width); - S32 height = heightSpinner->getValue().asInteger(); - height = power_of_two(height, MAX_TEXTURE_SIZE); - heightSpinner->setIncrement(height >> 1); - heightSpinner->forceSetValue(height); - info["h"] = height; - } - else - { - info["w"] = widthSpinner->getValue().asInteger(); - info["h"] = heightSpinner->getValue().asInteger(); - } - getParentByType()->notify(LLSD().with("custom-res-change", info)); -} - -void LLPanelSnapshot::onResolutionComboCommit(LLUICtrl* ctrl) -{ - LLSD info; - info["combo-res-change"]["control-name"] = ctrl->getName(); - getParentByType()->notify(info); -} - -void LLPanelSnapshot::onKeepAspectRatioCommit(LLUICtrl* ctrl) -{ - getParentByType()->notify(LLSD().with("keep-aspect-change", ctrl->getValue().asBoolean())); -} - -LLSnapshotModel::ESnapshotType LLPanelSnapshot::getSnapshotType() -{ - return LLSnapshotModel::SNAPSHOT_WEB; -} +/** + * @file llpanelsnapshot.cpp + * @brief Snapshot panel base class + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llpanelsnapshot.h" + +// libs +#include "llcombobox.h" +#include "llfloater.h" +#include "llfloatersnapshot.h" +#include "llsliderctrl.h" +#include "llspinctrl.h" +#include "lltrans.h" + +// newview +#include "llsidetraypanelcontainer.h" +#include "llviewercontrol.h" // gSavedSettings + +#include "llagentbenefits.h" + +constexpr S32 MAX_TEXTURE_SIZE = 512 ; //max upload texture size 512 * 512 + +S32 power_of_two(S32 sz, S32 upper) +{ + S32 res = upper; + while( upper >= sz) + { + res = upper; + upper >>= 1; + } + return res; +} + +LLPanelSnapshot::LLPanelSnapshot() + : mSnapshotFloater(NULL) +{} + +// virtual +bool LLPanelSnapshot::postBuild() +{ + getChild("save_btn")->setLabelArg("[UPLOAD_COST]", std::to_string(LLAgentBenefitsMgr::current().getTextureUploadCost())); + getChild(getImageSizeComboName())->setCommitCallback(boost::bind(&LLPanelSnapshot::onResolutionComboCommit, this, _1)); + if (!getWidthSpinnerName().empty()) + { + getChild(getWidthSpinnerName())->setCommitCallback(boost::bind(&LLPanelSnapshot::onCustomResolutionCommit, this)); + } + if (!getHeightSpinnerName().empty()) + { + getChild(getHeightSpinnerName())->setCommitCallback(boost::bind(&LLPanelSnapshot::onCustomResolutionCommit, this)); + } + if (!getAspectRatioCBName().empty()) + { + getChild(getAspectRatioCBName())->setCommitCallback(boost::bind(&LLPanelSnapshot::onKeepAspectRatioCommit, this, _1)); + } + updateControls(LLSD()); + + mSnapshotFloater = getParentByType(); + return true; +} + +// virtual +void LLPanelSnapshot::onOpen(const LLSD& key) +{ + S32 old_format = gSavedSettings.getS32("SnapshotFormat"); + S32 new_format = (S32) getImageFormat(); + + gSavedSettings.setS32("SnapshotFormat", new_format); + setCtrlsEnabled(true); + + // Switching panels will likely change image format. + // Not updating preview right away may lead to errors, + // e.g. attempt to send a large BMP image by email. + if (old_format != new_format) + { + getParentByType()->notify(LLSD().with("image-format-change", true)); + } +} + +LLSnapshotModel::ESnapshotFormat LLPanelSnapshot::getImageFormat() const +{ + return LLSnapshotModel::SNAPSHOT_FORMAT_JPEG; +} + +void LLPanelSnapshot::enableControls(bool enable) +{ + setCtrlsEnabled(enable); +} + +LLSpinCtrl* LLPanelSnapshot::getWidthSpinner() +{ + llassert(!getWidthSpinnerName().empty()); + return getChild(getWidthSpinnerName()); +} + +LLSpinCtrl* LLPanelSnapshot::getHeightSpinner() +{ + llassert(!getHeightSpinnerName().empty()); + return getChild(getHeightSpinnerName()); +} + +S32 LLPanelSnapshot::getTypedPreviewWidth() const +{ + llassert(!getWidthSpinnerName().empty()); + return getChild(getWidthSpinnerName())->getValue().asInteger(); +} + +S32 LLPanelSnapshot::getTypedPreviewHeight() const +{ + llassert(!getHeightSpinnerName().empty()); + return getChild(getHeightSpinnerName())->getValue().asInteger(); +} + +void LLPanelSnapshot::enableAspectRatioCheckbox(bool enable) +{ + llassert(!getAspectRatioCBName().empty()); + getChild(getAspectRatioCBName())->setEnabled(enable); +} + +LLSideTrayPanelContainer* LLPanelSnapshot::getParentContainer() +{ + LLSideTrayPanelContainer* parent = dynamic_cast(getParent()); + if (!parent) + { + LL_WARNS() << "Cannot find panel container" << LL_ENDL; + return NULL; + } + + return parent; +} + +void LLPanelSnapshot::updateImageQualityLevel() +{ + LLSliderCtrl* quality_slider = getChild("image_quality_slider"); + S32 quality_val = llfloor((F32) quality_slider->getValue().asReal()); + + std::string quality_lvl; + + if (quality_val < 20) + { + quality_lvl = LLTrans::getString("snapshot_quality_very_low"); + } + else if (quality_val < 40) + { + quality_lvl = LLTrans::getString("snapshot_quality_low"); + } + else if (quality_val < 60) + { + quality_lvl = LLTrans::getString("snapshot_quality_medium"); + } + else if (quality_val < 80) + { + quality_lvl = LLTrans::getString("snapshot_quality_high"); + } + else + { + quality_lvl = LLTrans::getString("snapshot_quality_very_high"); + } + + getChild("image_quality_level")->setTextArg("[QLVL]", quality_lvl); +} + +void LLPanelSnapshot::goBack() +{ + LLSideTrayPanelContainer* parent = getParentContainer(); + if (parent) + { + parent->openPreviousPanel(); + parent->getCurrentPanel()->onOpen(LLSD()); + } +} + +void LLPanelSnapshot::cancel() +{ + goBack(); + getParentByType()->notify(LLSD().with("set-ready", true)); +} + +void LLPanelSnapshot::onCustomResolutionCommit() +{ + LLSD info; + std::string widthSpinnerName = getWidthSpinnerName(); + std::string heightSpinnerName = getHeightSpinnerName(); + llassert(!widthSpinnerName.empty() && !heightSpinnerName.empty()); + LLSpinCtrl *widthSpinner = getChild(widthSpinnerName); + LLSpinCtrl *heightSpinner = getChild(heightSpinnerName); + if (getName() == "panel_snapshot_inventory") + { + S32 width = widthSpinner->getValue().asInteger(); + width = power_of_two(width, MAX_TEXTURE_SIZE); + info["w"] = width; + widthSpinner->setIncrement(width >> 1); + widthSpinner->forceSetValue(width); + S32 height = heightSpinner->getValue().asInteger(); + height = power_of_two(height, MAX_TEXTURE_SIZE); + heightSpinner->setIncrement(height >> 1); + heightSpinner->forceSetValue(height); + info["h"] = height; + } + else + { + info["w"] = widthSpinner->getValue().asInteger(); + info["h"] = heightSpinner->getValue().asInteger(); + } + getParentByType()->notify(LLSD().with("custom-res-change", info)); +} + +void LLPanelSnapshot::onResolutionComboCommit(LLUICtrl* ctrl) +{ + LLSD info; + info["combo-res-change"]["control-name"] = ctrl->getName(); + getParentByType()->notify(info); +} + +void LLPanelSnapshot::onKeepAspectRatioCommit(LLUICtrl* ctrl) +{ + getParentByType()->notify(LLSD().with("keep-aspect-change", ctrl->getValue().asBoolean())); +} + +LLSnapshotModel::ESnapshotType LLPanelSnapshot::getSnapshotType() +{ + return LLSnapshotModel::SNAPSHOT_WEB; +} diff --git a/indra/newview/llpanelsnapshot.h b/indra/newview/llpanelsnapshot.h index 36a9dca255..3f5ad274b8 100644 --- a/indra/newview/llpanelsnapshot.h +++ b/indra/newview/llpanelsnapshot.h @@ -1,79 +1,79 @@ -/** - * @file llpanelsnapshot.h - * @brief Snapshot panel base class - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELSNAPSHOT_H -#define LL_LLPANELSNAPSHOT_H - -//#include "llfloatersnapshot.h" -#include "llpanel.h" -#include "llsnapshotmodel.h" - -class LLSpinCtrl; -class LLSideTrayPanelContainer; -class LLFloaterSnapshotBase; - -/** - * Snapshot panel base class. - */ -class LLPanelSnapshot: public LLPanel -{ -public: - LLPanelSnapshot(); - - bool postBuild() override; - void onOpen(const LLSD& key) override; - - virtual std::string getWidthSpinnerName() const = 0; - virtual std::string getHeightSpinnerName() const = 0; - virtual std::string getAspectRatioCBName() const = 0; - virtual std::string getImageSizeComboName() const = 0; - virtual std::string getImageSizePanelName() const = 0; - - virtual S32 getTypedPreviewWidth() const; - virtual S32 getTypedPreviewHeight() const; - virtual LLSpinCtrl* getWidthSpinner(); - virtual LLSpinCtrl* getHeightSpinner(); - virtual void enableAspectRatioCheckbox(bool enable); - virtual LLSnapshotModel::ESnapshotFormat getImageFormat() const; - virtual LLSnapshotModel::ESnapshotType getSnapshotType(); - virtual void updateControls(const LLSD& info) = 0; ///< Update controls from saved settings - void enableControls(bool enable); - -protected: - LLSideTrayPanelContainer* getParentContainer(); - void updateImageQualityLevel(); - void goBack(); ///< Switch to the default (Snapshot Options) panel - virtual void cancel(); - - // common UI callbacks - void onCustomResolutionCommit(); - void onResolutionComboCommit(LLUICtrl* ctrl); - void onKeepAspectRatioCommit(LLUICtrl* ctrl); - - LLFloaterSnapshotBase* mSnapshotFloater; -}; - -#endif // LL_LLPANELSNAPSHOT_H +/** + * @file llpanelsnapshot.h + * @brief Snapshot panel base class + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELSNAPSHOT_H +#define LL_LLPANELSNAPSHOT_H + +//#include "llfloatersnapshot.h" +#include "llpanel.h" +#include "llsnapshotmodel.h" + +class LLSpinCtrl; +class LLSideTrayPanelContainer; +class LLFloaterSnapshotBase; + +/** + * Snapshot panel base class. + */ +class LLPanelSnapshot: public LLPanel +{ +public: + LLPanelSnapshot(); + + bool postBuild() override; + void onOpen(const LLSD& key) override; + + virtual std::string getWidthSpinnerName() const = 0; + virtual std::string getHeightSpinnerName() const = 0; + virtual std::string getAspectRatioCBName() const = 0; + virtual std::string getImageSizeComboName() const = 0; + virtual std::string getImageSizePanelName() const = 0; + + virtual S32 getTypedPreviewWidth() const; + virtual S32 getTypedPreviewHeight() const; + virtual LLSpinCtrl* getWidthSpinner(); + virtual LLSpinCtrl* getHeightSpinner(); + virtual void enableAspectRatioCheckbox(bool enable); + virtual LLSnapshotModel::ESnapshotFormat getImageFormat() const; + virtual LLSnapshotModel::ESnapshotType getSnapshotType(); + virtual void updateControls(const LLSD& info) = 0; ///< Update controls from saved settings + void enableControls(bool enable); + +protected: + LLSideTrayPanelContainer* getParentContainer(); + void updateImageQualityLevel(); + void goBack(); ///< Switch to the default (Snapshot Options) panel + virtual void cancel(); + + // common UI callbacks + void onCustomResolutionCommit(); + void onResolutionComboCommit(LLUICtrl* ctrl); + void onKeepAspectRatioCommit(LLUICtrl* ctrl); + + LLFloaterSnapshotBase* mSnapshotFloater; +}; + +#endif // LL_LLPANELSNAPSHOT_H diff --git a/indra/newview/llpanelsnapshotinventory.cpp b/indra/newview/llpanelsnapshotinventory.cpp index 954441da3b..4abb89120b 100644 --- a/indra/newview/llpanelsnapshotinventory.cpp +++ b/indra/newview/llpanelsnapshotinventory.cpp @@ -1,211 +1,211 @@ -/** - * @file llpanelsnapshotinventory.cpp - * @brief The panel provides UI for saving snapshot as an inventory texture. - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llcombobox.h" -#include "llsidetraypanelcontainer.h" -#include "llspinctrl.h" - -#include "llfloatersnapshot.h" // FIXME: replace with a snapshot storage model -#include "llpanelsnapshot.h" -#include "llsnapshotlivepreview.h" -#include "llviewercontrol.h" // gSavedSettings -#include "llstatusbar.h" // can_afford_transaction() -#include "llnotificationsutil.h" - -#include "llagentbenefits.h" - -/** - * The panel provides UI for saving snapshot as an inventory texture. - */ -class LLPanelSnapshotInventoryBase - : public LLPanelSnapshot -{ - LOG_CLASS(LLPanelSnapshotInventoryBase); - -public: - LLPanelSnapshotInventoryBase(); - - /*virtual*/ bool postBuild(); -protected: - void onSend(); - /*virtual*/ LLSnapshotModel::ESnapshotType getSnapshotType(); -}; - -class LLPanelSnapshotInventory - : public LLPanelSnapshotInventoryBase -{ - LOG_CLASS(LLPanelSnapshotInventory); - -public: - LLPanelSnapshotInventory(); - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); - - void onResolutionCommit(LLUICtrl* ctrl); - -private: - /*virtual*/ std::string getWidthSpinnerName() const { return "inventory_snapshot_width"; } - /*virtual*/ std::string getHeightSpinnerName() const { return "inventory_snapshot_height"; } - /*virtual*/ std::string getAspectRatioCBName() const { return "inventory_keep_aspect_check"; } - /*virtual*/ std::string getImageSizeComboName() const { return "texture_size_combo"; } - /*virtual*/ std::string getImageSizePanelName() const { return LLStringUtil::null; } - /*virtual*/ void updateControls(const LLSD& info); - -}; - -class LLPanelOutfitSnapshotInventory - : public LLPanelSnapshotInventoryBase -{ - LOG_CLASS(LLPanelOutfitSnapshotInventory); - -public: - LLPanelOutfitSnapshotInventory(); - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); - -private: - /*virtual*/ std::string getWidthSpinnerName() const { return ""; } - /*virtual*/ std::string getHeightSpinnerName() const { return ""; } - /*virtual*/ std::string getAspectRatioCBName() const { return ""; } - /*virtual*/ std::string getImageSizeComboName() const { return "texture_size_combo"; } - /*virtual*/ std::string getImageSizePanelName() const { return LLStringUtil::null; } - /*virtual*/ void updateControls(const LLSD& info); - - /*virtual*/ void cancel(); -}; - -static LLPanelInjector panel_class1("llpanelsnapshotinventory"); - -static LLPanelInjector panel_class2("llpaneloutfitsnapshotinventory"); - -LLPanelSnapshotInventoryBase::LLPanelSnapshotInventoryBase() -{ -} - -bool LLPanelSnapshotInventoryBase::postBuild() -{ - return LLPanelSnapshot::postBuild(); -} - -LLSnapshotModel::ESnapshotType LLPanelSnapshotInventoryBase::getSnapshotType() -{ - return LLSnapshotModel::SNAPSHOT_TEXTURE; -} - -LLPanelSnapshotInventory::LLPanelSnapshotInventory() -{ - mCommitCallbackRegistrar.add("Inventory.Save", boost::bind(&LLPanelSnapshotInventory::onSend, this)); - mCommitCallbackRegistrar.add("Inventory.Cancel", boost::bind(&LLPanelSnapshotInventory::cancel, this)); -} - -// virtual -bool LLPanelSnapshotInventory::postBuild() -{ - getChild(getWidthSpinnerName())->setAllowEdit(false); - getChild(getHeightSpinnerName())->setAllowEdit(false); - - getChild(getImageSizeComboName())->setCommitCallback(boost::bind(&LLPanelSnapshotInventory::onResolutionCommit, this, _1)); - return LLPanelSnapshotInventoryBase::postBuild(); -} - -// virtual -void LLPanelSnapshotInventory::onOpen(const LLSD& key) -{ - LLPanelSnapshot::onOpen(key); -} - -// virtual -void LLPanelSnapshotInventory::updateControls(const LLSD& info) -{ - const bool have_snapshot = info.has("have-snapshot") ? info["have-snapshot"].asBoolean() : true; - getChild("save_btn")->setEnabled(have_snapshot); -} - -void LLPanelSnapshotInventory::onResolutionCommit(LLUICtrl* ctrl) -{ - bool current_window_selected = (getChild(getImageSizeComboName())->getCurrentIndex() == 3); - getChild(getWidthSpinnerName())->setVisible(!current_window_selected); - getChild(getHeightSpinnerName())->setVisible(!current_window_selected); -} - -void LLPanelSnapshotInventoryBase::onSend() -{ - S32 expected_upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost(); - if (can_afford_transaction(expected_upload_cost)) - { - if (mSnapshotFloater) - { - mSnapshotFloater->saveTexture(); - mSnapshotFloater->postSave(); - } - } - else - { - LLSD args; - args["COST"] = llformat("%d", expected_upload_cost); - LLNotificationsUtil::add("ErrorPhotoCannotAfford", args); - if (mSnapshotFloater) - { - mSnapshotFloater->inventorySaveFailed(); - } - } -} - -LLPanelOutfitSnapshotInventory::LLPanelOutfitSnapshotInventory() -{ - mCommitCallbackRegistrar.add("Inventory.SaveOutfitPhoto", boost::bind(&LLPanelOutfitSnapshotInventory::onSend, this)); - mCommitCallbackRegistrar.add("Inventory.SaveOutfitCancel", boost::bind(&LLPanelOutfitSnapshotInventory::cancel, this)); -} - -// virtual -bool LLPanelOutfitSnapshotInventory::postBuild() -{ - return LLPanelSnapshotInventoryBase::postBuild(); -} - -// virtual -void LLPanelOutfitSnapshotInventory::onOpen(const LLSD& key) -{ - getChild("hint_lbl")->setTextArg("[UPLOAD_COST]", llformat("%d", LLAgentBenefitsMgr::current().getTextureUploadCost())); - LLPanelSnapshot::onOpen(key); -} - -// virtual -void LLPanelOutfitSnapshotInventory::updateControls(const LLSD& info) -{ - const bool have_snapshot = info.has("have-snapshot") ? info["have-snapshot"].asBoolean() : true; - getChild("save_btn")->setEnabled(have_snapshot); -} - -void LLPanelOutfitSnapshotInventory::cancel() -{ - if (mSnapshotFloater) - { - mSnapshotFloater->closeFloater(); - } -} +/** + * @file llpanelsnapshotinventory.cpp + * @brief The panel provides UI for saving snapshot as an inventory texture. + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llcombobox.h" +#include "llsidetraypanelcontainer.h" +#include "llspinctrl.h" + +#include "llfloatersnapshot.h" // FIXME: replace with a snapshot storage model +#include "llpanelsnapshot.h" +#include "llsnapshotlivepreview.h" +#include "llviewercontrol.h" // gSavedSettings +#include "llstatusbar.h" // can_afford_transaction() +#include "llnotificationsutil.h" + +#include "llagentbenefits.h" + +/** + * The panel provides UI for saving snapshot as an inventory texture. + */ +class LLPanelSnapshotInventoryBase + : public LLPanelSnapshot +{ + LOG_CLASS(LLPanelSnapshotInventoryBase); + +public: + LLPanelSnapshotInventoryBase(); + + /*virtual*/ bool postBuild(); +protected: + void onSend(); + /*virtual*/ LLSnapshotModel::ESnapshotType getSnapshotType(); +}; + +class LLPanelSnapshotInventory + : public LLPanelSnapshotInventoryBase +{ + LOG_CLASS(LLPanelSnapshotInventory); + +public: + LLPanelSnapshotInventory(); + /*virtual*/ bool postBuild(); + /*virtual*/ void onOpen(const LLSD& key); + + void onResolutionCommit(LLUICtrl* ctrl); + +private: + /*virtual*/ std::string getWidthSpinnerName() const { return "inventory_snapshot_width"; } + /*virtual*/ std::string getHeightSpinnerName() const { return "inventory_snapshot_height"; } + /*virtual*/ std::string getAspectRatioCBName() const { return "inventory_keep_aspect_check"; } + /*virtual*/ std::string getImageSizeComboName() const { return "texture_size_combo"; } + /*virtual*/ std::string getImageSizePanelName() const { return LLStringUtil::null; } + /*virtual*/ void updateControls(const LLSD& info); + +}; + +class LLPanelOutfitSnapshotInventory + : public LLPanelSnapshotInventoryBase +{ + LOG_CLASS(LLPanelOutfitSnapshotInventory); + +public: + LLPanelOutfitSnapshotInventory(); + /*virtual*/ bool postBuild(); + /*virtual*/ void onOpen(const LLSD& key); + +private: + /*virtual*/ std::string getWidthSpinnerName() const { return ""; } + /*virtual*/ std::string getHeightSpinnerName() const { return ""; } + /*virtual*/ std::string getAspectRatioCBName() const { return ""; } + /*virtual*/ std::string getImageSizeComboName() const { return "texture_size_combo"; } + /*virtual*/ std::string getImageSizePanelName() const { return LLStringUtil::null; } + /*virtual*/ void updateControls(const LLSD& info); + + /*virtual*/ void cancel(); +}; + +static LLPanelInjector panel_class1("llpanelsnapshotinventory"); + +static LLPanelInjector panel_class2("llpaneloutfitsnapshotinventory"); + +LLPanelSnapshotInventoryBase::LLPanelSnapshotInventoryBase() +{ +} + +bool LLPanelSnapshotInventoryBase::postBuild() +{ + return LLPanelSnapshot::postBuild(); +} + +LLSnapshotModel::ESnapshotType LLPanelSnapshotInventoryBase::getSnapshotType() +{ + return LLSnapshotModel::SNAPSHOT_TEXTURE; +} + +LLPanelSnapshotInventory::LLPanelSnapshotInventory() +{ + mCommitCallbackRegistrar.add("Inventory.Save", boost::bind(&LLPanelSnapshotInventory::onSend, this)); + mCommitCallbackRegistrar.add("Inventory.Cancel", boost::bind(&LLPanelSnapshotInventory::cancel, this)); +} + +// virtual +bool LLPanelSnapshotInventory::postBuild() +{ + getChild(getWidthSpinnerName())->setAllowEdit(false); + getChild(getHeightSpinnerName())->setAllowEdit(false); + + getChild(getImageSizeComboName())->setCommitCallback(boost::bind(&LLPanelSnapshotInventory::onResolutionCommit, this, _1)); + return LLPanelSnapshotInventoryBase::postBuild(); +} + +// virtual +void LLPanelSnapshotInventory::onOpen(const LLSD& key) +{ + LLPanelSnapshot::onOpen(key); +} + +// virtual +void LLPanelSnapshotInventory::updateControls(const LLSD& info) +{ + const bool have_snapshot = info.has("have-snapshot") ? info["have-snapshot"].asBoolean() : true; + getChild("save_btn")->setEnabled(have_snapshot); +} + +void LLPanelSnapshotInventory::onResolutionCommit(LLUICtrl* ctrl) +{ + bool current_window_selected = (getChild(getImageSizeComboName())->getCurrentIndex() == 3); + getChild(getWidthSpinnerName())->setVisible(!current_window_selected); + getChild(getHeightSpinnerName())->setVisible(!current_window_selected); +} + +void LLPanelSnapshotInventoryBase::onSend() +{ + S32 expected_upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost(); + if (can_afford_transaction(expected_upload_cost)) + { + if (mSnapshotFloater) + { + mSnapshotFloater->saveTexture(); + mSnapshotFloater->postSave(); + } + } + else + { + LLSD args; + args["COST"] = llformat("%d", expected_upload_cost); + LLNotificationsUtil::add("ErrorPhotoCannotAfford", args); + if (mSnapshotFloater) + { + mSnapshotFloater->inventorySaveFailed(); + } + } +} + +LLPanelOutfitSnapshotInventory::LLPanelOutfitSnapshotInventory() +{ + mCommitCallbackRegistrar.add("Inventory.SaveOutfitPhoto", boost::bind(&LLPanelOutfitSnapshotInventory::onSend, this)); + mCommitCallbackRegistrar.add("Inventory.SaveOutfitCancel", boost::bind(&LLPanelOutfitSnapshotInventory::cancel, this)); +} + +// virtual +bool LLPanelOutfitSnapshotInventory::postBuild() +{ + return LLPanelSnapshotInventoryBase::postBuild(); +} + +// virtual +void LLPanelOutfitSnapshotInventory::onOpen(const LLSD& key) +{ + getChild("hint_lbl")->setTextArg("[UPLOAD_COST]", llformat("%d", LLAgentBenefitsMgr::current().getTextureUploadCost())); + LLPanelSnapshot::onOpen(key); +} + +// virtual +void LLPanelOutfitSnapshotInventory::updateControls(const LLSD& info) +{ + const bool have_snapshot = info.has("have-snapshot") ? info["have-snapshot"].asBoolean() : true; + getChild("save_btn")->setEnabled(have_snapshot); +} + +void LLPanelOutfitSnapshotInventory::cancel() +{ + if (mSnapshotFloater) + { + mSnapshotFloater->closeFloater(); + } +} diff --git a/indra/newview/llpanelsnapshotlocal.cpp b/indra/newview/llpanelsnapshotlocal.cpp index 8bf11660f0..366030c0fa 100644 --- a/indra/newview/llpanelsnapshotlocal.cpp +++ b/indra/newview/llpanelsnapshotlocal.cpp @@ -1,189 +1,189 @@ -/** - * @file llpanelsnapshotlocal.cpp - * @brief The panel provides UI for saving snapshot to a local folder. - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llcombobox.h" -#include "llsidetraypanelcontainer.h" -#include "llsliderctrl.h" -#include "llspinctrl.h" - -#include "llfloatersnapshot.h" // FIXME: replace with a snapshot storage model -#include "llpanelsnapshot.h" -#include "llsnapshotlivepreview.h" -#include "llviewercontrol.h" // gSavedSettings -#include "llviewerwindow.h" - -/** - * The panel provides UI for saving snapshot to a local folder. - */ -class LLPanelSnapshotLocal -: public LLPanelSnapshot -{ - LOG_CLASS(LLPanelSnapshotLocal); - -public: - LLPanelSnapshotLocal(); - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); - -private: - /*virtual*/ std::string getWidthSpinnerName() const { return "local_snapshot_width"; } - /*virtual*/ std::string getHeightSpinnerName() const { return "local_snapshot_height"; } - /*virtual*/ std::string getAspectRatioCBName() const { return "local_keep_aspect_check"; } - /*virtual*/ std::string getImageSizeComboName() const { return "local_size_combo"; } - /*virtual*/ std::string getImageSizePanelName() const { return "local_image_size_lp"; } - /*virtual*/ LLSnapshotModel::ESnapshotFormat getImageFormat() const; - /*virtual*/ LLSnapshotModel::ESnapshotType getSnapshotType(); - /*virtual*/ void updateControls(const LLSD& info); - - S32 mLocalFormat; - - void onFormatComboCommit(LLUICtrl* ctrl); - void onQualitySliderCommit(LLUICtrl* ctrl); - void onSaveFlyoutCommit(LLUICtrl* ctrl); - - void onLocalSaved(); - void onLocalCanceled(); -}; - -static LLPanelInjector panel_class("llpanelsnapshotlocal"); - -LLPanelSnapshotLocal::LLPanelSnapshotLocal() -{ - mLocalFormat = gSavedSettings.getS32("SnapshotFormat"); - mCommitCallbackRegistrar.add("Local.Cancel", boost::bind(&LLPanelSnapshotLocal::cancel, this)); -} - -// virtual -bool LLPanelSnapshotLocal::postBuild() -{ - getChild("image_quality_slider")->setCommitCallback(boost::bind(&LLPanelSnapshotLocal::onQualitySliderCommit, this, _1)); - getChild("local_format_combo")->setCommitCallback(boost::bind(&LLPanelSnapshotLocal::onFormatComboCommit, this, _1)); - getChild("save_btn")->setCommitCallback(boost::bind(&LLPanelSnapshotLocal::onSaveFlyoutCommit, this, _1)); - - return LLPanelSnapshot::postBuild(); -} - -// virtual -void LLPanelSnapshotLocal::onOpen(const LLSD& key) -{ - if(gSavedSettings.getS32("SnapshotFormat") != mLocalFormat) - { - getChild("local_format_combo")->selectNthItem(mLocalFormat); - } - LLPanelSnapshot::onOpen(key); -} - -// virtual -LLSnapshotModel::ESnapshotFormat LLPanelSnapshotLocal::getImageFormat() const -{ - LLSnapshotModel::ESnapshotFormat fmt = LLSnapshotModel::SNAPSHOT_FORMAT_PNG; - - LLComboBox* local_format_combo = getChild("local_format_combo"); - const std::string id = local_format_combo->getValue().asString(); - if (id == "PNG") - { - fmt = LLSnapshotModel::SNAPSHOT_FORMAT_PNG; - } - else if (id == "JPEG") - { - fmt = LLSnapshotModel::SNAPSHOT_FORMAT_JPEG; - } - else if (id == "BMP") - { - fmt = LLSnapshotModel::SNAPSHOT_FORMAT_BMP; - } - - return fmt; -} - -// virtual -void LLPanelSnapshotLocal::updateControls(const LLSD& info) -{ - LLSnapshotModel::ESnapshotFormat fmt = - (LLSnapshotModel::ESnapshotFormat) gSavedSettings.getS32("SnapshotFormat"); - getChild("local_format_combo")->selectNthItem((S32) fmt); - - const bool show_quality_ctrls = (fmt == LLSnapshotModel::SNAPSHOT_FORMAT_JPEG); - getChild("image_quality_slider")->setVisible(show_quality_ctrls); - getChild("image_quality_level")->setVisible(show_quality_ctrls); - - getChild("image_quality_slider")->setValue(gSavedSettings.getS32("SnapshotQuality")); - updateImageQualityLevel(); - - const bool have_snapshot = info.has("have-snapshot") ? info["have-snapshot"].asBoolean() : true; - getChild("save_btn")->setEnabled(have_snapshot); -} - -void LLPanelSnapshotLocal::onFormatComboCommit(LLUICtrl* ctrl) -{ - mLocalFormat = getImageFormat(); - // will call updateControls() - LLFloaterSnapshot::getInstance()->notify(LLSD().with("image-format-change", true)); -} - -void LLPanelSnapshotLocal::onQualitySliderCommit(LLUICtrl* ctrl) -{ - updateImageQualityLevel(); - - LLSliderCtrl* slider = (LLSliderCtrl*)ctrl; - S32 quality_val = llfloor((F32)slider->getValue().asReal()); - LLSD info; - info["image-quality-change"] = quality_val; - LLFloaterSnapshot::getInstance()->notify(info); -} - -void LLPanelSnapshotLocal::onSaveFlyoutCommit(LLUICtrl* ctrl) -{ - if (ctrl->getValue().asString() == "save as") - { - gViewerWindow->resetSnapshotLoc(); - } - - LLFloaterSnapshot* floater = LLFloaterSnapshot::getInstance(); - - floater->notify(LLSD().with("set-working", true)); - floater->saveLocal((boost::bind(&LLPanelSnapshotLocal::onLocalSaved, this)), (boost::bind(&LLPanelSnapshotLocal::onLocalCanceled, this))); -} - -void LLPanelSnapshotLocal::onLocalSaved() -{ - mSnapshotFloater->postSave(); - LLFloaterSnapshot::getInstance()->notify(LLSD().with("set-finished", LLSD().with("ok", true).with("msg", "local"))); -} - -void LLPanelSnapshotLocal::onLocalCanceled() -{ - cancel(); - LLFloaterSnapshot::getInstance()->notify(LLSD().with("set-finished", LLSD().with("ok", false).with("msg", "local"))); -} - - -LLSnapshotModel::ESnapshotType LLPanelSnapshotLocal::getSnapshotType() -{ - return LLSnapshotModel::SNAPSHOT_LOCAL; -} +/** + * @file llpanelsnapshotlocal.cpp + * @brief The panel provides UI for saving snapshot to a local folder. + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llcombobox.h" +#include "llsidetraypanelcontainer.h" +#include "llsliderctrl.h" +#include "llspinctrl.h" + +#include "llfloatersnapshot.h" // FIXME: replace with a snapshot storage model +#include "llpanelsnapshot.h" +#include "llsnapshotlivepreview.h" +#include "llviewercontrol.h" // gSavedSettings +#include "llviewerwindow.h" + +/** + * The panel provides UI for saving snapshot to a local folder. + */ +class LLPanelSnapshotLocal +: public LLPanelSnapshot +{ + LOG_CLASS(LLPanelSnapshotLocal); + +public: + LLPanelSnapshotLocal(); + /*virtual*/ bool postBuild(); + /*virtual*/ void onOpen(const LLSD& key); + +private: + /*virtual*/ std::string getWidthSpinnerName() const { return "local_snapshot_width"; } + /*virtual*/ std::string getHeightSpinnerName() const { return "local_snapshot_height"; } + /*virtual*/ std::string getAspectRatioCBName() const { return "local_keep_aspect_check"; } + /*virtual*/ std::string getImageSizeComboName() const { return "local_size_combo"; } + /*virtual*/ std::string getImageSizePanelName() const { return "local_image_size_lp"; } + /*virtual*/ LLSnapshotModel::ESnapshotFormat getImageFormat() const; + /*virtual*/ LLSnapshotModel::ESnapshotType getSnapshotType(); + /*virtual*/ void updateControls(const LLSD& info); + + S32 mLocalFormat; + + void onFormatComboCommit(LLUICtrl* ctrl); + void onQualitySliderCommit(LLUICtrl* ctrl); + void onSaveFlyoutCommit(LLUICtrl* ctrl); + + void onLocalSaved(); + void onLocalCanceled(); +}; + +static LLPanelInjector panel_class("llpanelsnapshotlocal"); + +LLPanelSnapshotLocal::LLPanelSnapshotLocal() +{ + mLocalFormat = gSavedSettings.getS32("SnapshotFormat"); + mCommitCallbackRegistrar.add("Local.Cancel", boost::bind(&LLPanelSnapshotLocal::cancel, this)); +} + +// virtual +bool LLPanelSnapshotLocal::postBuild() +{ + getChild("image_quality_slider")->setCommitCallback(boost::bind(&LLPanelSnapshotLocal::onQualitySliderCommit, this, _1)); + getChild("local_format_combo")->setCommitCallback(boost::bind(&LLPanelSnapshotLocal::onFormatComboCommit, this, _1)); + getChild("save_btn")->setCommitCallback(boost::bind(&LLPanelSnapshotLocal::onSaveFlyoutCommit, this, _1)); + + return LLPanelSnapshot::postBuild(); +} + +// virtual +void LLPanelSnapshotLocal::onOpen(const LLSD& key) +{ + if(gSavedSettings.getS32("SnapshotFormat") != mLocalFormat) + { + getChild("local_format_combo")->selectNthItem(mLocalFormat); + } + LLPanelSnapshot::onOpen(key); +} + +// virtual +LLSnapshotModel::ESnapshotFormat LLPanelSnapshotLocal::getImageFormat() const +{ + LLSnapshotModel::ESnapshotFormat fmt = LLSnapshotModel::SNAPSHOT_FORMAT_PNG; + + LLComboBox* local_format_combo = getChild("local_format_combo"); + const std::string id = local_format_combo->getValue().asString(); + if (id == "PNG") + { + fmt = LLSnapshotModel::SNAPSHOT_FORMAT_PNG; + } + else if (id == "JPEG") + { + fmt = LLSnapshotModel::SNAPSHOT_FORMAT_JPEG; + } + else if (id == "BMP") + { + fmt = LLSnapshotModel::SNAPSHOT_FORMAT_BMP; + } + + return fmt; +} + +// virtual +void LLPanelSnapshotLocal::updateControls(const LLSD& info) +{ + LLSnapshotModel::ESnapshotFormat fmt = + (LLSnapshotModel::ESnapshotFormat) gSavedSettings.getS32("SnapshotFormat"); + getChild("local_format_combo")->selectNthItem((S32) fmt); + + const bool show_quality_ctrls = (fmt == LLSnapshotModel::SNAPSHOT_FORMAT_JPEG); + getChild("image_quality_slider")->setVisible(show_quality_ctrls); + getChild("image_quality_level")->setVisible(show_quality_ctrls); + + getChild("image_quality_slider")->setValue(gSavedSettings.getS32("SnapshotQuality")); + updateImageQualityLevel(); + + const bool have_snapshot = info.has("have-snapshot") ? info["have-snapshot"].asBoolean() : true; + getChild("save_btn")->setEnabled(have_snapshot); +} + +void LLPanelSnapshotLocal::onFormatComboCommit(LLUICtrl* ctrl) +{ + mLocalFormat = getImageFormat(); + // will call updateControls() + LLFloaterSnapshot::getInstance()->notify(LLSD().with("image-format-change", true)); +} + +void LLPanelSnapshotLocal::onQualitySliderCommit(LLUICtrl* ctrl) +{ + updateImageQualityLevel(); + + LLSliderCtrl* slider = (LLSliderCtrl*)ctrl; + S32 quality_val = llfloor((F32)slider->getValue().asReal()); + LLSD info; + info["image-quality-change"] = quality_val; + LLFloaterSnapshot::getInstance()->notify(info); +} + +void LLPanelSnapshotLocal::onSaveFlyoutCommit(LLUICtrl* ctrl) +{ + if (ctrl->getValue().asString() == "save as") + { + gViewerWindow->resetSnapshotLoc(); + } + + LLFloaterSnapshot* floater = LLFloaterSnapshot::getInstance(); + + floater->notify(LLSD().with("set-working", true)); + floater->saveLocal((boost::bind(&LLPanelSnapshotLocal::onLocalSaved, this)), (boost::bind(&LLPanelSnapshotLocal::onLocalCanceled, this))); +} + +void LLPanelSnapshotLocal::onLocalSaved() +{ + mSnapshotFloater->postSave(); + LLFloaterSnapshot::getInstance()->notify(LLSD().with("set-finished", LLSD().with("ok", true).with("msg", "local"))); +} + +void LLPanelSnapshotLocal::onLocalCanceled() +{ + cancel(); + LLFloaterSnapshot::getInstance()->notify(LLSD().with("set-finished", LLSD().with("ok", false).with("msg", "local"))); +} + + +LLSnapshotModel::ESnapshotType LLPanelSnapshotLocal::getSnapshotType() +{ + return LLSnapshotModel::SNAPSHOT_LOCAL; +} diff --git a/indra/newview/llpanelsnapshotoptions.cpp b/indra/newview/llpanelsnapshotoptions.cpp index 786fd96f3d..776de460a9 100644 --- a/indra/newview/llpanelsnapshotoptions.cpp +++ b/indra/newview/llpanelsnapshotoptions.cpp @@ -1,129 +1,129 @@ -/** - * @file llpanelsnapshotoptions.cpp - * @brief Snapshot posting options panel. - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanel.h" -#include "llsidetraypanelcontainer.h" - -#include "llfloatersnapshot.h" // FIXME: create a snapshot model -#include "llfloaterreg.h" - -#include "llagentbenefits.h" - - -/** - * Provides several ways to save a snapshot. - */ -class LLPanelSnapshotOptions -: public LLPanel -{ - LOG_CLASS(LLPanelSnapshotOptions); - -public: - LLPanelSnapshotOptions(); - ~LLPanelSnapshotOptions(); - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); - -private: - void updateUploadCost(); - void openPanel(const std::string& panel_name); - void onSaveToProfile(); - void onSaveToEmail(); - void onSaveToInventory(); - void onSaveToComputer(); - - LLFloaterSnapshotBase* mSnapshotFloater; -}; - -static LLPanelInjector panel_class("llpanelsnapshotoptions"); - -LLPanelSnapshotOptions::LLPanelSnapshotOptions() -{ - mCommitCallbackRegistrar.add("Snapshot.SaveToProfile", boost::bind(&LLPanelSnapshotOptions::onSaveToProfile, this)); - mCommitCallbackRegistrar.add("Snapshot.SaveToEmail", boost::bind(&LLPanelSnapshotOptions::onSaveToEmail, this)); - mCommitCallbackRegistrar.add("Snapshot.SaveToInventory", boost::bind(&LLPanelSnapshotOptions::onSaveToInventory, this)); - mCommitCallbackRegistrar.add("Snapshot.SaveToComputer", boost::bind(&LLPanelSnapshotOptions::onSaveToComputer, this)); -} - -LLPanelSnapshotOptions::~LLPanelSnapshotOptions() -{ -} - -// virtual -bool LLPanelSnapshotOptions::postBuild() -{ - mSnapshotFloater = getParentByType(); - return LLPanel::postBuild(); -} - -// virtual -void LLPanelSnapshotOptions::onOpen(const LLSD& key) -{ - updateUploadCost(); -} - -void LLPanelSnapshotOptions::updateUploadCost() -{ - S32 upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost(); - getChild("save_to_inventory_btn")->setLabelArg("[AMOUNT]", llformat("%d", upload_cost)); -} - -void LLPanelSnapshotOptions::openPanel(const std::string& panel_name) -{ - LLSideTrayPanelContainer* parent = dynamic_cast(getParent()); - if (!parent) - { - LL_WARNS() << "Cannot find panel container" << LL_ENDL; - return; - } - - parent->openPanel(panel_name); - parent->getCurrentPanel()->onOpen(LLSD()); - mSnapshotFloater->postPanelSwitch(); -} - -void LLPanelSnapshotOptions::onSaveToProfile() -{ - openPanel("panel_snapshot_profile"); -} - -void LLPanelSnapshotOptions::onSaveToEmail() -{ - openPanel("panel_snapshot_postcard"); -} - -void LLPanelSnapshotOptions::onSaveToInventory() -{ - openPanel("panel_snapshot_inventory"); -} - -void LLPanelSnapshotOptions::onSaveToComputer() -{ - openPanel("panel_snapshot_local"); -} - +/** + * @file llpanelsnapshotoptions.cpp + * @brief Snapshot posting options panel. + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanel.h" +#include "llsidetraypanelcontainer.h" + +#include "llfloatersnapshot.h" // FIXME: create a snapshot model +#include "llfloaterreg.h" + +#include "llagentbenefits.h" + + +/** + * Provides several ways to save a snapshot. + */ +class LLPanelSnapshotOptions +: public LLPanel +{ + LOG_CLASS(LLPanelSnapshotOptions); + +public: + LLPanelSnapshotOptions(); + ~LLPanelSnapshotOptions(); + /*virtual*/ bool postBuild(); + /*virtual*/ void onOpen(const LLSD& key); + +private: + void updateUploadCost(); + void openPanel(const std::string& panel_name); + void onSaveToProfile(); + void onSaveToEmail(); + void onSaveToInventory(); + void onSaveToComputer(); + + LLFloaterSnapshotBase* mSnapshotFloater; +}; + +static LLPanelInjector panel_class("llpanelsnapshotoptions"); + +LLPanelSnapshotOptions::LLPanelSnapshotOptions() +{ + mCommitCallbackRegistrar.add("Snapshot.SaveToProfile", boost::bind(&LLPanelSnapshotOptions::onSaveToProfile, this)); + mCommitCallbackRegistrar.add("Snapshot.SaveToEmail", boost::bind(&LLPanelSnapshotOptions::onSaveToEmail, this)); + mCommitCallbackRegistrar.add("Snapshot.SaveToInventory", boost::bind(&LLPanelSnapshotOptions::onSaveToInventory, this)); + mCommitCallbackRegistrar.add("Snapshot.SaveToComputer", boost::bind(&LLPanelSnapshotOptions::onSaveToComputer, this)); +} + +LLPanelSnapshotOptions::~LLPanelSnapshotOptions() +{ +} + +// virtual +bool LLPanelSnapshotOptions::postBuild() +{ + mSnapshotFloater = getParentByType(); + return LLPanel::postBuild(); +} + +// virtual +void LLPanelSnapshotOptions::onOpen(const LLSD& key) +{ + updateUploadCost(); +} + +void LLPanelSnapshotOptions::updateUploadCost() +{ + S32 upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost(); + getChild("save_to_inventory_btn")->setLabelArg("[AMOUNT]", llformat("%d", upload_cost)); +} + +void LLPanelSnapshotOptions::openPanel(const std::string& panel_name) +{ + LLSideTrayPanelContainer* parent = dynamic_cast(getParent()); + if (!parent) + { + LL_WARNS() << "Cannot find panel container" << LL_ENDL; + return; + } + + parent->openPanel(panel_name); + parent->getCurrentPanel()->onOpen(LLSD()); + mSnapshotFloater->postPanelSwitch(); +} + +void LLPanelSnapshotOptions::onSaveToProfile() +{ + openPanel("panel_snapshot_profile"); +} + +void LLPanelSnapshotOptions::onSaveToEmail() +{ + openPanel("panel_snapshot_postcard"); +} + +void LLPanelSnapshotOptions::onSaveToInventory() +{ + openPanel("panel_snapshot_inventory"); +} + +void LLPanelSnapshotOptions::onSaveToComputer() +{ + openPanel("panel_snapshot_local"); +} + diff --git a/indra/newview/llpanelsnapshotpostcard.cpp b/indra/newview/llpanelsnapshotpostcard.cpp index 81060b3eb0..23e8789e3f 100644 --- a/indra/newview/llpanelsnapshotpostcard.cpp +++ b/indra/newview/llpanelsnapshotpostcard.cpp @@ -1,253 +1,253 @@ -/** - * @file llpanelsnapshotpostcard.cpp - * @brief Postcard sending panel. - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llcombobox.h" -#include "llnotificationsutil.h" -#include "llsidetraypanelcontainer.h" -#include "llsliderctrl.h" -#include "llspinctrl.h" -#include "lltexteditor.h" - -#include "llagent.h" -#include "llagentui.h" -#include "llfloatersnapshot.h" // FIXME: replace with a snapshot storage model -#include "llpanelsnapshot.h" -#include "llpostcard.h" -#include "llregex.h" -#include "llsnapshotlivepreview.h" -#include "llviewercontrol.h" // gSavedSettings -#include "llviewerwindow.h" -#include "llviewerregion.h" - -#include - -/** - * Sends postcard via email. - */ -class LLPanelSnapshotPostcard -: public LLPanelSnapshot -{ - LOG_CLASS(LLPanelSnapshotPostcard); - -public: - LLPanelSnapshotPostcard(); - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); - -private: - /*virtual*/ std::string getWidthSpinnerName() const { return "postcard_snapshot_width"; } - /*virtual*/ std::string getHeightSpinnerName() const { return "postcard_snapshot_height"; } - /*virtual*/ std::string getAspectRatioCBName() const { return "postcard_keep_aspect_check"; } - /*virtual*/ std::string getImageSizeComboName() const { return "postcard_size_combo"; } - /*virtual*/ std::string getImageSizePanelName() const { return "postcard_image_size_lp"; } - /*virtual*/ LLSnapshotModel::ESnapshotFormat getImageFormat() const { return LLSnapshotModel::SNAPSHOT_FORMAT_JPEG; } - /*virtual*/ LLSnapshotModel::ESnapshotType getSnapshotType(); - /*virtual*/ void updateControls(const LLSD& info); - - bool missingSubjMsgAlertCallback(const LLSD& notification, const LLSD& response); - static void sendPostcardFinished(LLSD result); - void sendPostcard(); - - void onMsgFormFocusRecieved(); - void onFormatComboCommit(LLUICtrl* ctrl); - void onQualitySliderCommit(LLUICtrl* ctrl); - void onSend(); - - bool mHasFirstMsgFocus; -}; - -static LLPanelInjector panel_class("llpanelsnapshotpostcard"); - -LLPanelSnapshotPostcard::LLPanelSnapshotPostcard() -: mHasFirstMsgFocus(false) -{ - mCommitCallbackRegistrar.add("Postcard.Send", boost::bind(&LLPanelSnapshotPostcard::onSend, this)); - mCommitCallbackRegistrar.add("Postcard.Cancel", boost::bind(&LLPanelSnapshotPostcard::cancel, this)); - -} - -// virtual -bool LLPanelSnapshotPostcard::postBuild() -{ - // For the first time a user focuses to .the msg box, all text will be selected. - getChild("msg_form")->setFocusChangedCallback(boost::bind(&LLPanelSnapshotPostcard::onMsgFormFocusRecieved, this)); - - getChild("to_form")->setFocus(true); - - getChild("image_quality_slider")->setCommitCallback(boost::bind(&LLPanelSnapshotPostcard::onQualitySliderCommit, this, _1)); - - return LLPanelSnapshot::postBuild(); -} - -// virtual -void LLPanelSnapshotPostcard::onOpen(const LLSD& key) -{ - LLUICtrl* name_form = getChild("name_form"); - if (name_form && name_form->getValue().asString().empty()) - { - std::string name_string; - LLAgentUI::buildFullname(name_string); - getChild("name_form")->setValue(LLSD(name_string)); - } - - LLPanelSnapshot::onOpen(key); -} - -// virtual -void LLPanelSnapshotPostcard::updateControls(const LLSD& info) -{ - getChild("image_quality_slider")->setValue(gSavedSettings.getS32("SnapshotQuality")); - updateImageQualityLevel(); - - const bool have_snapshot = info.has("have-snapshot") ? info["have-snapshot"].asBoolean() : true; - getChild("send_btn")->setEnabled(have_snapshot); -} - -bool LLPanelSnapshotPostcard::missingSubjMsgAlertCallback(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if(0 == option) - { - // User clicked OK - if((getChild("subject_form")->getValue().asString()).empty()) - { - // Stuff the subject back into the form. - getChild("subject_form")->setValue(getString("default_subject")); - } - - if (!mHasFirstMsgFocus) - { - // The user never switched focus to the message window. - // Using the default string. - getChild("msg_form")->setValue(getString("default_message")); - } - - sendPostcard(); - } - return false; -} - - -void LLPanelSnapshotPostcard::sendPostcardFinished(LLSD result) -{ - LL_WARNS() << result << LL_ENDL; - - std::string state = result["state"].asString(); - - LLPostCard::reportPostResult((state == "complete")); -} - - -void LLPanelSnapshotPostcard::sendPostcard() -{ - if (!gAgent.getRegion()) return; - - // upload the image - std::string url = gAgent.getRegion()->getCapability("SendPostcard"); - if (!url.empty()) - { - LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared( - getChild("name_form")->getValue().asString(), - getChild("to_form")->getValue().asString(), - getChild("subject_form")->getValue().asString(), - getChild("msg_form")->getValue().asString(), - mSnapshotFloater->getPosTakenGlobal(), - mSnapshotFloater->getImageData(), - [](LLUUID, LLUUID, LLUUID, LLSD response) { - LLPanelSnapshotPostcard::sendPostcardFinished(response); - })); - - LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo); - } - else - { - LL_WARNS() << "Postcards unavailable in this region." << LL_ENDL; - } - - - // Give user feedback of the event. - gViewerWindow->playSnapshotAnimAndSound(); - - mSnapshotFloater->postSave(); -} - -void LLPanelSnapshotPostcard::onMsgFormFocusRecieved() -{ - LLTextEditor* msg_form = getChild("msg_form"); - if (msg_form->hasFocus() && !mHasFirstMsgFocus) - { - mHasFirstMsgFocus = true; - msg_form->setText(LLStringUtil::null); - } -} - -void LLPanelSnapshotPostcard::onFormatComboCommit(LLUICtrl* ctrl) -{ - // will call updateControls() - LLFloaterSnapshot::getInstance()->notify(LLSD().with("image-format-change", true)); -} - -void LLPanelSnapshotPostcard::onQualitySliderCommit(LLUICtrl* ctrl) -{ - updateImageQualityLevel(); - - LLSliderCtrl* slider = (LLSliderCtrl*)ctrl; - S32 quality_val = llfloor((F32)slider->getValue().asReal()); - LLSD info; - info["image-quality-change"] = quality_val; - LLFloaterSnapshot::getInstance()->notify(info); // updates the "SnapshotQuality" setting -} - -void LLPanelSnapshotPostcard::onSend() -{ - // Validate input. - std::string to(getChild("to_form")->getValue().asString()); - - boost::regex email_format("[A-Za-z0-9.%+-_]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}(,[ \t]*[A-Za-z0-9.%+-_]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,})*"); - - if (to.empty() || !ll_regex_match(to, email_format)) - { - LLNotificationsUtil::add("PromptRecipientEmail"); - return; - } - - std::string subject(getChild("subject_form")->getValue().asString()); - if(subject.empty() || !mHasFirstMsgFocus) - { - LLNotificationsUtil::add("PromptMissingSubjMsg", LLSD(), LLSD(), boost::bind(&LLPanelSnapshotPostcard::missingSubjMsgAlertCallback, this, _1, _2)); - return; - } - - // Send postcard. - sendPostcard(); -} - -LLSnapshotModel::ESnapshotType LLPanelSnapshotPostcard::getSnapshotType() -{ - return LLSnapshotModel::SNAPSHOT_POSTCARD; -} +/** + * @file llpanelsnapshotpostcard.cpp + * @brief Postcard sending panel. + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llcombobox.h" +#include "llnotificationsutil.h" +#include "llsidetraypanelcontainer.h" +#include "llsliderctrl.h" +#include "llspinctrl.h" +#include "lltexteditor.h" + +#include "llagent.h" +#include "llagentui.h" +#include "llfloatersnapshot.h" // FIXME: replace with a snapshot storage model +#include "llpanelsnapshot.h" +#include "llpostcard.h" +#include "llregex.h" +#include "llsnapshotlivepreview.h" +#include "llviewercontrol.h" // gSavedSettings +#include "llviewerwindow.h" +#include "llviewerregion.h" + +#include + +/** + * Sends postcard via email. + */ +class LLPanelSnapshotPostcard +: public LLPanelSnapshot +{ + LOG_CLASS(LLPanelSnapshotPostcard); + +public: + LLPanelSnapshotPostcard(); + /*virtual*/ bool postBuild(); + /*virtual*/ void onOpen(const LLSD& key); + +private: + /*virtual*/ std::string getWidthSpinnerName() const { return "postcard_snapshot_width"; } + /*virtual*/ std::string getHeightSpinnerName() const { return "postcard_snapshot_height"; } + /*virtual*/ std::string getAspectRatioCBName() const { return "postcard_keep_aspect_check"; } + /*virtual*/ std::string getImageSizeComboName() const { return "postcard_size_combo"; } + /*virtual*/ std::string getImageSizePanelName() const { return "postcard_image_size_lp"; } + /*virtual*/ LLSnapshotModel::ESnapshotFormat getImageFormat() const { return LLSnapshotModel::SNAPSHOT_FORMAT_JPEG; } + /*virtual*/ LLSnapshotModel::ESnapshotType getSnapshotType(); + /*virtual*/ void updateControls(const LLSD& info); + + bool missingSubjMsgAlertCallback(const LLSD& notification, const LLSD& response); + static void sendPostcardFinished(LLSD result); + void sendPostcard(); + + void onMsgFormFocusRecieved(); + void onFormatComboCommit(LLUICtrl* ctrl); + void onQualitySliderCommit(LLUICtrl* ctrl); + void onSend(); + + bool mHasFirstMsgFocus; +}; + +static LLPanelInjector panel_class("llpanelsnapshotpostcard"); + +LLPanelSnapshotPostcard::LLPanelSnapshotPostcard() +: mHasFirstMsgFocus(false) +{ + mCommitCallbackRegistrar.add("Postcard.Send", boost::bind(&LLPanelSnapshotPostcard::onSend, this)); + mCommitCallbackRegistrar.add("Postcard.Cancel", boost::bind(&LLPanelSnapshotPostcard::cancel, this)); + +} + +// virtual +bool LLPanelSnapshotPostcard::postBuild() +{ + // For the first time a user focuses to .the msg box, all text will be selected. + getChild("msg_form")->setFocusChangedCallback(boost::bind(&LLPanelSnapshotPostcard::onMsgFormFocusRecieved, this)); + + getChild("to_form")->setFocus(true); + + getChild("image_quality_slider")->setCommitCallback(boost::bind(&LLPanelSnapshotPostcard::onQualitySliderCommit, this, _1)); + + return LLPanelSnapshot::postBuild(); +} + +// virtual +void LLPanelSnapshotPostcard::onOpen(const LLSD& key) +{ + LLUICtrl* name_form = getChild("name_form"); + if (name_form && name_form->getValue().asString().empty()) + { + std::string name_string; + LLAgentUI::buildFullname(name_string); + getChild("name_form")->setValue(LLSD(name_string)); + } + + LLPanelSnapshot::onOpen(key); +} + +// virtual +void LLPanelSnapshotPostcard::updateControls(const LLSD& info) +{ + getChild("image_quality_slider")->setValue(gSavedSettings.getS32("SnapshotQuality")); + updateImageQualityLevel(); + + const bool have_snapshot = info.has("have-snapshot") ? info["have-snapshot"].asBoolean() : true; + getChild("send_btn")->setEnabled(have_snapshot); +} + +bool LLPanelSnapshotPostcard::missingSubjMsgAlertCallback(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if(0 == option) + { + // User clicked OK + if((getChild("subject_form")->getValue().asString()).empty()) + { + // Stuff the subject back into the form. + getChild("subject_form")->setValue(getString("default_subject")); + } + + if (!mHasFirstMsgFocus) + { + // The user never switched focus to the message window. + // Using the default string. + getChild("msg_form")->setValue(getString("default_message")); + } + + sendPostcard(); + } + return false; +} + + +void LLPanelSnapshotPostcard::sendPostcardFinished(LLSD result) +{ + LL_WARNS() << result << LL_ENDL; + + std::string state = result["state"].asString(); + + LLPostCard::reportPostResult((state == "complete")); +} + + +void LLPanelSnapshotPostcard::sendPostcard() +{ + if (!gAgent.getRegion()) return; + + // upload the image + std::string url = gAgent.getRegion()->getCapability("SendPostcard"); + if (!url.empty()) + { + LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared( + getChild("name_form")->getValue().asString(), + getChild("to_form")->getValue().asString(), + getChild("subject_form")->getValue().asString(), + getChild("msg_form")->getValue().asString(), + mSnapshotFloater->getPosTakenGlobal(), + mSnapshotFloater->getImageData(), + [](LLUUID, LLUUID, LLUUID, LLSD response) { + LLPanelSnapshotPostcard::sendPostcardFinished(response); + })); + + LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo); + } + else + { + LL_WARNS() << "Postcards unavailable in this region." << LL_ENDL; + } + + + // Give user feedback of the event. + gViewerWindow->playSnapshotAnimAndSound(); + + mSnapshotFloater->postSave(); +} + +void LLPanelSnapshotPostcard::onMsgFormFocusRecieved() +{ + LLTextEditor* msg_form = getChild("msg_form"); + if (msg_form->hasFocus() && !mHasFirstMsgFocus) + { + mHasFirstMsgFocus = true; + msg_form->setText(LLStringUtil::null); + } +} + +void LLPanelSnapshotPostcard::onFormatComboCommit(LLUICtrl* ctrl) +{ + // will call updateControls() + LLFloaterSnapshot::getInstance()->notify(LLSD().with("image-format-change", true)); +} + +void LLPanelSnapshotPostcard::onQualitySliderCommit(LLUICtrl* ctrl) +{ + updateImageQualityLevel(); + + LLSliderCtrl* slider = (LLSliderCtrl*)ctrl; + S32 quality_val = llfloor((F32)slider->getValue().asReal()); + LLSD info; + info["image-quality-change"] = quality_val; + LLFloaterSnapshot::getInstance()->notify(info); // updates the "SnapshotQuality" setting +} + +void LLPanelSnapshotPostcard::onSend() +{ + // Validate input. + std::string to(getChild("to_form")->getValue().asString()); + + boost::regex email_format("[A-Za-z0-9.%+-_]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}(,[ \t]*[A-Za-z0-9.%+-_]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,})*"); + + if (to.empty() || !ll_regex_match(to, email_format)) + { + LLNotificationsUtil::add("PromptRecipientEmail"); + return; + } + + std::string subject(getChild("subject_form")->getValue().asString()); + if(subject.empty() || !mHasFirstMsgFocus) + { + LLNotificationsUtil::add("PromptMissingSubjMsg", LLSD(), LLSD(), boost::bind(&LLPanelSnapshotPostcard::missingSubjMsgAlertCallback, this, _1, _2)); + return; + } + + // Send postcard. + sendPostcard(); +} + +LLSnapshotModel::ESnapshotType LLPanelSnapshotPostcard::getSnapshotType() +{ + return LLSnapshotModel::SNAPSHOT_POSTCARD; +} diff --git a/indra/newview/llpanelsnapshotprofile.cpp b/indra/newview/llpanelsnapshotprofile.cpp index 3a1d0aa9a9..aa257dea9e 100644 --- a/indra/newview/llpanelsnapshotprofile.cpp +++ b/indra/newview/llpanelsnapshotprofile.cpp @@ -1,101 +1,101 @@ -/** - * @file llpanelsnapshotprofile.cpp - * @brief Posts a snapshot to My Profile feed. - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -// libs -#include "llcombobox.h" -#include "llfloaterreg.h" -#include "llpanel.h" -#include "llspinctrl.h" - -// newview -#include "llfloatersnapshot.h" -#include "llpanelsnapshot.h" -#include "llsidetraypanelcontainer.h" -#include "llwebprofile.h" - -/** - * Posts a snapshot to My Profile feed. - */ -class LLPanelSnapshotProfile -: public LLPanelSnapshot -{ - LOG_CLASS(LLPanelSnapshotProfile); - -public: - LLPanelSnapshotProfile(); - - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); - -private: - /*virtual*/ std::string getWidthSpinnerName() const { return "profile_snapshot_width"; } - /*virtual*/ std::string getHeightSpinnerName() const { return "profile_snapshot_height"; } - /*virtual*/ std::string getAspectRatioCBName() const { return "profile_keep_aspect_check"; } - /*virtual*/ std::string getImageSizeComboName() const { return "profile_size_combo"; } - /*virtual*/ std::string getImageSizePanelName() const { return "profile_image_size_lp"; } - /*virtual*/ LLSnapshotModel::ESnapshotFormat getImageFormat() const { return LLSnapshotModel::SNAPSHOT_FORMAT_PNG; } - /*virtual*/ void updateControls(const LLSD& info); - - void onSend(); -}; - -static LLPanelInjector panel_class("llpanelsnapshotprofile"); - -LLPanelSnapshotProfile::LLPanelSnapshotProfile() -{ - mCommitCallbackRegistrar.add("PostToProfile.Send", boost::bind(&LLPanelSnapshotProfile::onSend, this)); - mCommitCallbackRegistrar.add("PostToProfile.Cancel", boost::bind(&LLPanelSnapshotProfile::cancel, this)); -} - -// virtual -bool LLPanelSnapshotProfile::postBuild() -{ - return LLPanelSnapshot::postBuild(); -} - -// virtual -void LLPanelSnapshotProfile::onOpen(const LLSD& key) -{ - LLPanelSnapshot::onOpen(key); -} - -// virtual -void LLPanelSnapshotProfile::updateControls(const LLSD& info) -{ - const bool have_snapshot = info.has("have-snapshot") ? info["have-snapshot"].asBoolean() : true; - getChild("post_btn")->setEnabled(have_snapshot); -} - -void LLPanelSnapshotProfile::onSend() -{ - std::string caption = getChild("caption")->getValue().asString(); - bool add_location = getChild("add_location_cb")->getValue().asBoolean(); - - LLWebProfile::uploadImage(mSnapshotFloater->getImageData(), caption, add_location); - mSnapshotFloater->postSave(); -} +/** + * @file llpanelsnapshotprofile.cpp + * @brief Posts a snapshot to My Profile feed. + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +// libs +#include "llcombobox.h" +#include "llfloaterreg.h" +#include "llpanel.h" +#include "llspinctrl.h" + +// newview +#include "llfloatersnapshot.h" +#include "llpanelsnapshot.h" +#include "llsidetraypanelcontainer.h" +#include "llwebprofile.h" + +/** + * Posts a snapshot to My Profile feed. + */ +class LLPanelSnapshotProfile +: public LLPanelSnapshot +{ + LOG_CLASS(LLPanelSnapshotProfile); + +public: + LLPanelSnapshotProfile(); + + /*virtual*/ bool postBuild(); + /*virtual*/ void onOpen(const LLSD& key); + +private: + /*virtual*/ std::string getWidthSpinnerName() const { return "profile_snapshot_width"; } + /*virtual*/ std::string getHeightSpinnerName() const { return "profile_snapshot_height"; } + /*virtual*/ std::string getAspectRatioCBName() const { return "profile_keep_aspect_check"; } + /*virtual*/ std::string getImageSizeComboName() const { return "profile_size_combo"; } + /*virtual*/ std::string getImageSizePanelName() const { return "profile_image_size_lp"; } + /*virtual*/ LLSnapshotModel::ESnapshotFormat getImageFormat() const { return LLSnapshotModel::SNAPSHOT_FORMAT_PNG; } + /*virtual*/ void updateControls(const LLSD& info); + + void onSend(); +}; + +static LLPanelInjector panel_class("llpanelsnapshotprofile"); + +LLPanelSnapshotProfile::LLPanelSnapshotProfile() +{ + mCommitCallbackRegistrar.add("PostToProfile.Send", boost::bind(&LLPanelSnapshotProfile::onSend, this)); + mCommitCallbackRegistrar.add("PostToProfile.Cancel", boost::bind(&LLPanelSnapshotProfile::cancel, this)); +} + +// virtual +bool LLPanelSnapshotProfile::postBuild() +{ + return LLPanelSnapshot::postBuild(); +} + +// virtual +void LLPanelSnapshotProfile::onOpen(const LLSD& key) +{ + LLPanelSnapshot::onOpen(key); +} + +// virtual +void LLPanelSnapshotProfile::updateControls(const LLSD& info) +{ + const bool have_snapshot = info.has("have-snapshot") ? info["have-snapshot"].asBoolean() : true; + getChild("post_btn")->setEnabled(have_snapshot); +} + +void LLPanelSnapshotProfile::onSend() +{ + std::string caption = getChild("caption")->getValue().asString(); + bool add_location = getChild("add_location_cb")->getValue().asBoolean(); + + LLWebProfile::uploadImage(mSnapshotFloater->getImageData(), caption, add_location); + mSnapshotFloater->postSave(); +} diff --git a/indra/newview/llpanelteleporthistory.cpp b/indra/newview/llpanelteleporthistory.cpp index 05a5fffe15..d453d2c914 100644 --- a/indra/newview/llpanelteleporthistory.cpp +++ b/indra/newview/llpanelteleporthistory.cpp @@ -1,1197 +1,1197 @@ -/** - * @file llpanelteleporthistory.cpp - * @brief Teleport history represented by a scrolling list - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llfloaterreg.h" -#include "llmenubutton.h" - -#include "llfloaterworldmap.h" -#include "llpanelteleporthistory.h" -#include "llworldmap.h" -#include "llteleporthistorystorage.h" -#include "lltextutil.h" - -#include "llaccordionctrl.h" -#include "llaccordionctrltab.h" -#include "llflatlistview.h" -#include "llfloatersidepanelcontainer.h" -#include "llnotificationsutil.h" -#include "lltextbox.h" -#include "lltoggleablemenu.h" -#include "llviewermenu.h" -#include "lllandmarkactions.h" -#include "llclipboard.h" -#include "lltrans.h" - -// Maximum number of items that can be added to a list in one pass. -// Used to limit time spent for items list update per frame. -static const U32 ADD_LIMIT = 50; - -static const std::string COLLAPSED_BY_USER = "collapsed_by_user"; - -class LLTeleportHistoryFlatItem : public LLPanel -{ -public: - LLTeleportHistoryFlatItem(S32 index, LLToggleableMenu *menu, const std::string ®ion_name, - LLDate date, const std::string &hl); - virtual ~LLTeleportHistoryFlatItem(); - - virtual bool postBuild(); - - /*virtual*/ S32 notify(const LLSD& info); - - S32 getIndex() { return mIndex; } - void setIndex(S32 index) { mIndex = index; } - const std::string& getRegionName() { return mRegionName;} - void setRegionName(const std::string& name); - void setDate(LLDate date); - void setHighlightedText(const std::string& text); - void updateTitle(); - void updateTimestamp(); - std::string getTimestamp(); - - /*virtual*/ void setValue(const LLSD& value); - - void onMouseEnter(S32 x, S32 y, MASK mask); - void onMouseLeave(S32 x, S32 y, MASK mask); - virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); - - static void showPlaceInfoPanel(S32 index); - - LLHandle getItemHandle() { mItemHandle.bind(this); return mItemHandle; } - -private: - void onProfileBtnClick(); - void showMenu(S32 x, S32 y); - - LLButton* mProfileBtn; - LLTextBox* mTitle; - LLTextBox* mTimeTextBox; - - LLToggleableMenu *mMenu; - - S32 mIndex; - std::string mRegionName; - std::string mHighlight; - LLDate mDate; - LLRootHandle mItemHandle; -}; - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -class LLTeleportHistoryFlatItemStorage: public LLSingleton -{ - LLSINGLETON_EMPTY_CTOR(LLTeleportHistoryFlatItemStorage); -protected: - typedef std::vector< LLHandle > flat_item_list_t; - -public: - LLTeleportHistoryFlatItem* getFlatItemForPersistentItem ( - LLToggleableMenu *menu, - const LLTeleportHistoryPersistentItem& persistent_item, - const S32 cur_item_index, - const std::string &hl); - - void removeItem(LLTeleportHistoryFlatItem* item); - - void purge(); - -private: - - flat_item_list_t mItems; -}; - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -LLTeleportHistoryFlatItem::LLTeleportHistoryFlatItem(S32 index, LLToggleableMenu *menu, const std::string ®ion_name, - LLDate date, const std::string &hl) -: LLPanel(), - mIndex(index), - mMenu(menu), - mRegionName(region_name), - mDate(date), - mHighlight(hl) -{ - buildFromFile("panel_teleport_history_item.xml"); -} - -LLTeleportHistoryFlatItem::~LLTeleportHistoryFlatItem() -{ -} - -//virtual -bool LLTeleportHistoryFlatItem::postBuild() -{ - mTitle = getChild("region"); - - mTimeTextBox = getChild("timestamp"); - - mProfileBtn = getChild("profile_btn"); - - mProfileBtn->setClickedCallback(boost::bind(&LLTeleportHistoryFlatItem::onProfileBtnClick, this)); - - updateTitle(); - updateTimestamp(); - - return true; -} - -S32 LLTeleportHistoryFlatItem::notify(const LLSD& info) -{ - if(info.has("detach")) - { - delete mMouseDownSignal; - mMouseDownSignal = NULL; - delete mRightMouseDownSignal; - mRightMouseDownSignal = NULL; - return 1; - } - return 0; -} - -void LLTeleportHistoryFlatItem::setValue(const LLSD& value) -{ - if (!value.isMap()) return;; - if (!value.has("selected")) return; - getChildView("selected_icon")->setVisible( value["selected"]); -} - -void LLTeleportHistoryFlatItem::setHighlightedText(const std::string& text) -{ - mHighlight = text; -} - -void LLTeleportHistoryFlatItem::setRegionName(const std::string& name) -{ - mRegionName = name; -} - -void LLTeleportHistoryFlatItem::setDate(LLDate date) -{ - mDate = date; -} - -std::string LLTeleportHistoryFlatItem::getTimestamp() -{ - const LLDate &date = mDate; - std::string timestamp = ""; - - LLDate now = LLDate::now(); - S32 now_year, now_month, now_day, now_hour, now_min, now_sec; - now.split(&now_year, &now_month, &now_day, &now_hour, &now_min, &now_sec); - - const S32 seconds_in_day = 24 * 60 * 60; - S32 seconds_today = now_hour * 60 * 60 + now_min * 60 + now_sec; - S32 time_diff = (S32) now.secondsSinceEpoch() - (S32) date.secondsSinceEpoch(); - - // Only show timestamp for today and yesterday - if(time_diff < seconds_today + seconds_in_day) - { - timestamp = "[" + LLTrans::getString("TimeHour12")+"]:[" - + LLTrans::getString("TimeMin")+"] ["+ LLTrans::getString("TimeAMPM")+"]"; - LLSD substitution; - substitution["datetime"] = (S32) date.secondsSinceEpoch(); - LLStringUtil::format(timestamp, substitution); - } - - return timestamp; - -} - -void LLTeleportHistoryFlatItem::updateTitle() -{ - static LLUIColor sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", LLColor4U(255, 255, 255)); - - LLTextUtil::textboxSetHighlightedVal( - mTitle, - LLStyle::Params().color(sFgColor), - mRegionName, - mHighlight); -} - -void LLTeleportHistoryFlatItem::updateTimestamp() -{ - static LLUIColor sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", LLColor4U(255, 255, 255)); - - LLTextUtil::textboxSetHighlightedVal( - mTimeTextBox, - LLStyle::Params().color(sFgColor), - getTimestamp(), - mHighlight); -} - -void LLTeleportHistoryFlatItem::onMouseEnter(S32 x, S32 y, MASK mask) -{ - getChildView("hovered_icon")->setVisible( true); - mProfileBtn->setVisible(true); - - LLPanel::onMouseEnter(x, y, mask); -} - -void LLTeleportHistoryFlatItem::onMouseLeave(S32 x, S32 y, MASK mask) -{ - getChildView("hovered_icon")->setVisible( false); - mProfileBtn->setVisible(false); - - LLPanel::onMouseLeave(x, y, mask); -} - -// virtual -bool LLTeleportHistoryFlatItem::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - LLPanel::handleRightMouseDown(x, y, mask); - showMenu(x, y); - return true; -} - -void LLTeleportHistoryFlatItem::showPlaceInfoPanel(S32 index) -{ - LLSD params; - params["id"] = index; - params["type"] = "teleport_history"; - - LLFloaterSidePanelContainer::showPanel("places", params); -} - -void LLTeleportHistoryFlatItem::onProfileBtnClick() -{ - LLTeleportHistoryFlatItem::showPlaceInfoPanel(mIndex); -} - -void LLTeleportHistoryFlatItem::showMenu(S32 x, S32 y) -{ - mMenu->setButtonRect(this); - mMenu->buildDrawLabels(); - mMenu->arrangeAndClear(); - mMenu->updateParent(LLMenuGL::sMenuContainer); - - LLMenuGL::showPopup(this, mMenu, x, y); -} - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -LLTeleportHistoryFlatItem* -LLTeleportHistoryFlatItemStorage::getFlatItemForPersistentItem ( - LLToggleableMenu *menu, - const LLTeleportHistoryPersistentItem& persistent_item, - const S32 cur_item_index, - const std::string &hl) -{ - LLTeleportHistoryFlatItem* item = NULL; - if ( cur_item_index < (S32) mItems.size() ) - { - item = mItems[cur_item_index].get(); - if (item->getParent() == NULL) - { - item->setIndex(cur_item_index); - item->setRegionName(persistent_item.mTitle); - item->setDate(persistent_item.mDate); - item->setHighlightedText(hl); - item->setVisible(true); - item->updateTitle(); - item->updateTimestamp(); - } - else - { - // Item already added to parent - item = NULL; - } - } - - if ( !item ) - { - item = new LLTeleportHistoryFlatItem(cur_item_index, - menu, - persistent_item.mTitle, - persistent_item.mDate, - hl); - mItems.push_back(item->getItemHandle()); - } - - return item; -} - -void LLTeleportHistoryFlatItemStorage::removeItem(LLTeleportHistoryFlatItem* item) -{ - if (item) - { - flat_item_list_t::iterator item_iter = std::find(mItems.begin(), - mItems.end(), - item->getItemHandle()); - if (item_iter != mItems.end()) - { - mItems.erase(item_iter); - } - } -} - -void LLTeleportHistoryFlatItemStorage::purge() -{ - for ( flat_item_list_t::iterator - it = mItems.begin(), - it_end = mItems.end(); - it != it_end; ++it ) - { - LLHandle item_handle = *it; - if ( !item_handle.isDead() && item_handle.get()->getParent() == NULL ) - { - item_handle.get()->die(); - } - } - mItems.clear(); -} - -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - - -// Not yet implemented; need to remove buildPanel() from constructor when we switch -//static LLRegisterPanelClassWrapper t_teleport_history("panel_teleport_history"); - -LLTeleportHistoryPanel::LLTeleportHistoryPanel() - : LLPanelPlacesTab(), - mDirty(true), - mCurrentItem(0), - mTeleportHistory(NULL), - mHistoryAccordion(NULL), - mAccordionTabMenu(NULL), - mLastSelectedFlatlList(NULL), - mLastSelectedItemIndex(-1), - mGearItemMenu(NULL), - mSortingMenu(NULL) -{ - buildFromFile( "panel_teleport_history.xml"); -} - -LLTeleportHistoryPanel::~LLTeleportHistoryPanel() -{ - LLTeleportHistoryFlatItemStorage::instance().purge(); - mTeleportHistoryChangedConnection.disconnect(); -} - -bool LLTeleportHistoryPanel::postBuild() -{ - mCommitCallbackRegistrar.add("TeleportHistory.GearMenu.Action", boost::bind(&LLTeleportHistoryPanel::onGearMenuAction, this, _2)); - mEnableCallbackRegistrar.add("TeleportHistory.GearMenu.Enable", boost::bind(&LLTeleportHistoryPanel::isActionEnabled, this, _2)); - - // init menus before list, since menus are passed to list - mGearItemMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_teleport_history_item.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - mGearItemMenu->setAlwaysShowMenu(true); // all items can be disabled if nothing is selected, show anyway - mSortingMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_teleport_history_gear.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - - mTeleportHistory = LLTeleportHistoryStorage::getInstance(); - if (mTeleportHistory) - { - mTeleportHistoryChangedConnection = mTeleportHistory->setHistoryChangedCallback(boost::bind(&LLTeleportHistoryPanel::onTeleportHistoryChange, this, _1)); - } - - mHistoryAccordion = getChild("history_accordion"); - - if (mHistoryAccordion) - { - for (child_list_const_iter_t iter = mHistoryAccordion->beginChild(); iter != mHistoryAccordion->endChild(); iter++) - { - if (dynamic_cast(*iter)) - { - LLAccordionCtrlTab* tab = (LLAccordionCtrlTab*)*iter; - tab->setRightMouseDownCallback(boost::bind(&LLTeleportHistoryPanel::onAccordionTabRightClick, this, _1, _2, _3, _4)); - tab->setDisplayChildren(false); - tab->setDropDownStateChangedCallback(boost::bind(&LLTeleportHistoryPanel::onAccordionExpand, this, _1, _2)); - - // All accordion tabs are collapsed initially - setAccordionCollapsedByUser(tab, true); - - mItemContainers.push_back(tab); - - LLFlatListView* fl = getFlatListViewFromTab(tab); - if (fl) - { - fl->setCommitOnSelectionChange(true); - fl->setDoubleClickCallback(boost::bind(&LLTeleportHistoryPanel::onDoubleClickItem, this)); - fl->setCommitCallback(boost::bind(&LLTeleportHistoryPanel::handleItemSelect, this, fl)); - fl->setReturnCallback(boost::bind(&LLTeleportHistoryPanel::onReturnKeyPressed, this)); - } - } - } - - // Open first 2 accordion tabs - if (mItemContainers.size() > 1) - { - LLAccordionCtrlTab* tab = mItemContainers.at(mItemContainers.size() - 1); - tab->setDisplayChildren(true); - setAccordionCollapsedByUser(tab, false); - } - - if (mItemContainers.size() > 2) - { - LLAccordionCtrlTab* tab = mItemContainers.at(mItemContainers.size() - 2); - tab->setDisplayChildren(true); - setAccordionCollapsedByUser(tab, false); - } - } - - return true; -} - -// virtual -void LLTeleportHistoryPanel::draw() -{ - if (mDirty) - refresh(); - - LLPanelPlacesTab::draw(); -} - -// virtual -void LLTeleportHistoryPanel::onSearchEdit(const std::string& string) -{ - sFilterSubString = string; - showTeleportHistory(); -} - -// virtual -bool LLTeleportHistoryPanel::isSingleItemSelected() -{ - return mLastSelectedFlatlList && mLastSelectedFlatlList->getSelectedItem(); -} - -// virtual -void LLTeleportHistoryPanel::onShowOnMap() -{ - if (!mLastSelectedFlatlList) - return; - - LLTeleportHistoryFlatItem* itemp = dynamic_cast (mLastSelectedFlatlList->getSelectedItem()); - - if(!itemp) - return; - - LLVector3d global_pos = mTeleportHistory->getItems()[itemp->getIndex()].mGlobalPos; - - if (!global_pos.isExactlyZero()) - { - LLFloaterWorldMap::getInstance()->trackLocation(global_pos); - LLFloaterReg::showInstance("world_map", "center"); - } -} - -//virtual -void LLTeleportHistoryPanel::onShowProfile() -{ - if (!mLastSelectedFlatlList) - return; - - LLTeleportHistoryFlatItem* itemp = dynamic_cast (mLastSelectedFlatlList->getSelectedItem()); - - if(!itemp) - return; - - LLTeleportHistoryFlatItem::showPlaceInfoPanel(itemp->getIndex()); -} - -// virtual -void LLTeleportHistoryPanel::onTeleport() -{ - if (!mLastSelectedFlatlList) - return; - - LLTeleportHistoryFlatItem* itemp = dynamic_cast (mLastSelectedFlatlList->getSelectedItem()); - if(!itemp) - return; - - // teleport to existing item in history, so we don't add it again - confirmTeleport(itemp->getIndex()); -} - -// virtual -void LLTeleportHistoryPanel::onRemoveSelected() -{ - LLNotificationsUtil::add("ConfirmClearTeleportHistory", LLSD(), LLSD(), boost::bind(&LLTeleportHistoryPanel::onClearTeleportHistoryDialog, this, _1, _2)); -} - -/* -// virtual -void LLTeleportHistoryPanel::onCopySLURL() -{ - LLScrollListItem* itemp = mHistoryItems->getFirstSelected(); - if(!itemp) - return; - - S32 index = itemp->getColumn(LIST_INDEX)->getValue().asInteger(); - - const LLTeleportHistory::slurl_list_t& hist_items = mTeleportHistory->getItems(); - - LLVector3d global_pos = hist_items[index].mGlobalPos; - - U64 new_region_handle = to_region_handle(global_pos); - - LLWorldMapMessage::url_callback_t cb = boost::bind( - &LLPanelPlacesTab::onRegionResponse, this, - global_pos, _1, _2, _3, _4); - - LLWorldMap::getInstance()->sendHandleRegionRequest(new_region_handle, cb, std::string("unused"), false); -} -*/ - -// virtual -void LLTeleportHistoryPanel::updateVerbs() -{ - if (!isTabVisible()) - return; - - if (sRemoveBtn) - { - sRemoveBtn->setEnabled(true); - } -} - -// virtual -LLToggleableMenu* LLTeleportHistoryPanel::getSelectionMenu() -{ - return mGearItemMenu; -} - -// virtual -LLToggleableMenu* LLTeleportHistoryPanel::getSortingMenu() -{ - return mSortingMenu; -} - -// virtual -LLToggleableMenu* LLTeleportHistoryPanel::getCreateMenu() -{ - return NULL; -} - -void LLTeleportHistoryPanel::getNextTab(const LLDate& item_date, S32& tab_idx, LLDate& tab_date) -{ - const U32 seconds_in_day = 24 * 60 * 60; - - S32 tabs_cnt = mItemContainers.size(); - S32 curr_year = 0, curr_month = 0, curr_day = 0; - - tab_date = LLDate::now(); - tab_date.split(&curr_year, &curr_month, &curr_day); - tab_date.fromYMDHMS(curr_year, curr_month, curr_day); // Set hour, min, and sec to 0 - tab_date.secondsSinceEpoch(tab_date.secondsSinceEpoch() + seconds_in_day); - - tab_idx = -1; - - while (tab_idx < tabs_cnt - 1 && item_date < tab_date) - { - tab_idx++; - - if (tab_idx <= tabs_cnt - 4) - { - // All tabs, except last three, are tabs for one day, so just push tab_date back by one day - tab_date.secondsSinceEpoch(tab_date.secondsSinceEpoch() - seconds_in_day); - } - else if (tab_idx == tabs_cnt - 3) // 6 day and older, low boundary is 1 month - { - tab_date = LLDate::now(); - tab_date.split(&curr_year, &curr_month, &curr_day); - curr_month--; - if (0 == curr_month) - { - curr_month = 12; - curr_year--; - } - tab_date.fromYMDHMS(curr_year, curr_month, curr_day); - } - else if (tab_idx == tabs_cnt - 2) // 1 month and older, low boundary is 6 months - { - tab_date = LLDate::now(); - tab_date.split(&curr_year, &curr_month, &curr_day); - if (curr_month > 6) - { - curr_month -= 6; - } - else - { - curr_month += 6; - curr_year--; - } - tab_date.fromYMDHMS(curr_year, curr_month, curr_day); - } - else // 6 months and older - { - tab_date.secondsSinceEpoch(0); - } - } -} - -// Called to add items, no more, than ADD_LIMIT at time -void LLTeleportHistoryPanel::refresh() -{ - if (!mHistoryAccordion) - { - mDirty = false; - return; - } - - const LLTeleportHistoryStorage::slurl_list_t& items = mTeleportHistory->getItems(); - - // Setting tab_boundary_date to "now", so date from any item would be earlier, than boundary. - // That leads to call to getNextTab to get right tab_idx in first pass - LLDate tab_boundary_date = LLDate::now(); - - LLFlatListView* curr_flat_view = NULL; - std::string filter_string = sFilterSubString; - LLStringUtil::toUpper(filter_string); - - U32 added_items = 0; - while (mCurrentItem >= 0) - { - // Filtering - if (!filter_string.empty()) - { - std::string landmark_title(items[mCurrentItem].mTitle); - LLStringUtil::toUpper(landmark_title); - if( std::string::npos == landmark_title.find(filter_string) ) - { - mCurrentItem--; - continue; - } - } - - // Checking whether date of item is earlier, than tab_boundary_date. - // In that case, item should be added to another tab - const LLDate &date = items[mCurrentItem].mDate; - - if (date < tab_boundary_date) - { - // Getting apropriate tab_idx for this and subsequent items, - // tab_boundary_date would be earliest possible date for this tab - S32 tab_idx = 0; - getNextTab(date, tab_idx, tab_boundary_date); - tab_idx = mItemContainers.size() - 1 - tab_idx; - if (tab_idx >= 0) - { - LLAccordionCtrlTab* tab = mItemContainers.at(tab_idx); - tab->setVisible(true); - - // Expand all accordion tabs when filtering - if(!sFilterSubString.empty()) - { - //store accordion tab state when filter is not empty - tab->notifyChildren(LLSD().with("action","store_state")); - - tab->setDisplayChildren(true); - } - // Restore each tab's expand state when not filtering - else - { - bool collapsed = isAccordionCollapsedByUser(tab); - tab->setDisplayChildren(!collapsed); - - //restore accordion state after all those accodrion tabmanipulations - tab->notifyChildren(LLSD().with("action","restore_state")); - } - - curr_flat_view = getFlatListViewFromTab(tab); - } - } - - if (curr_flat_view) - { - LLTeleportHistoryFlatItem* item = - LLTeleportHistoryFlatItemStorage::instance() - .getFlatItemForPersistentItem(mGearItemMenu, - items[mCurrentItem], - mCurrentItem, - filter_string); - if ( !curr_flat_view->addItem(item, LLUUID::null, ADD_BOTTOM, false) ) - LL_ERRS() << "Couldn't add flat item to teleport history." << LL_ENDL; - if (mLastSelectedItemIndex == mCurrentItem) - curr_flat_view->selectItem(item, true); - } - - mCurrentItem--; - - if (++added_items >= ADD_LIMIT) - break; - } - - for (S32 n = mItemContainers.size() - 1; n >= 0; --n) - { - LLAccordionCtrlTab* tab = mItemContainers.at(n); - LLFlatListView* fv = getFlatListViewFromTab(tab); - if (fv) - { - fv->notify(LLSD().with("rearrange", LLSD())); - } - } - - mHistoryAccordion->setFilterSubString(sFilterSubString); - - mHistoryAccordion->arrange(); - - updateVerbs(); - - if (mCurrentItem < 0) - mDirty = false; -} - -void LLTeleportHistoryPanel::onTeleportHistoryChange(S32 removed_index) -{ - mLastSelectedItemIndex = -1; - - if (-1 == removed_index) - showTeleportHistory(); // recreate all items - else - { - replaceItem(removed_index); // replace removed item by most recent - updateVerbs(); - } -} - -void LLTeleportHistoryPanel::replaceItem(S32 removed_index) -{ - // Flat list for 'Today' (mItemContainers keeps accordion tabs in reverse order) - LLFlatListView* fv = NULL; - - if (mItemContainers.size() > 0) - { - fv = getFlatListViewFromTab(mItemContainers[mItemContainers.size() - 1]); - } - - // Empty flat list for 'Today' means that other flat lists are empty as well, - // so all items from teleport history should be added. - if (!fv || fv->size() == 0) - { - showTeleportHistory(); - return; - } - - const LLTeleportHistoryStorage::slurl_list_t& history_items = mTeleportHistory->getItems(); - LLTeleportHistoryFlatItem* item = LLTeleportHistoryFlatItemStorage::instance() - .getFlatItemForPersistentItem(mGearItemMenu, - history_items[history_items.size() - 1], // Most recent item, it was added instead of removed - history_items.size(), // index will be decremented inside loop below - sFilterSubString); - - fv->addItem(item, LLUUID::null, ADD_TOP); - - // Index of each item, from last to removed item should be decremented - // to point to the right item in LLTeleportHistoryStorage - for (S32 tab_idx = mItemContainers.size() - 1; tab_idx >= 0; --tab_idx) - { - LLAccordionCtrlTab* tab = mItemContainers.at(tab_idx); - if (!tab->getVisible()) - continue; - - fv = getFlatListViewFromTab(tab); - if (!fv) - { - showTeleportHistory(); - return; - } - - std::vector items; - fv->getItems(items); - - S32 items_cnt = items.size(); - for (S32 n = 0; n < items_cnt; ++n) - { - LLTeleportHistoryFlatItem *item = (LLTeleportHistoryFlatItem*) items[n]; - - if (item->getIndex() == removed_index) - { - LLTeleportHistoryFlatItemStorage::instance().removeItem(item); - - fv->removeItem(item); - - // If flat list becames empty, then accordion tab should be hidden - if (fv->size() == 0) - tab->setVisible(false); - - mHistoryAccordion->arrange(); - - return; // No need to decrement idexes for the rest of items - } - - item->setIndex(item->getIndex() - 1); - } - } -} - -void LLTeleportHistoryPanel::showTeleportHistory() -{ - mDirty = true; - - // Starting to add items from last one, in reverse order, - // since TeleportHistory keeps most recent item at the end - if (!mTeleportHistory) - { - mTeleportHistory = LLTeleportHistoryStorage::getInstance(); - } - - mCurrentItem = mTeleportHistory->getItems().size() - 1; - - for (S32 n = mItemContainers.size() - 1; n >= 0; --n) - { - LLAccordionCtrlTab* tab = mItemContainers.at(n); - if (tab) - { - tab->setVisible(false); - - LLFlatListView* fv = getFlatListViewFromTab(tab); - if (fv) - { - // Detached panels are managed by LLTeleportHistoryFlatItemStorage - std::vector detached_items; - fv->detachItems(detached_items); - } - } - } -} - -void LLTeleportHistoryPanel::handleItemSelect(LLFlatListView* selected) -{ - mLastSelectedFlatlList = selected; - LLTeleportHistoryFlatItem* item = dynamic_cast (mLastSelectedFlatlList->getSelectedItem()); - if (item) - mLastSelectedItemIndex = item->getIndex(); - - S32 tabs_cnt = mItemContainers.size(); - - for (S32 n = 0; n < tabs_cnt; n++) - { - LLAccordionCtrlTab* tab = mItemContainers.at(n); - - if (!tab->getVisible()) - continue; - - LLFlatListView *flv = getFlatListViewFromTab(tab); - if (!flv) - continue; - - if (flv == selected) - continue; - - flv->resetSelection(true); - } - - updateVerbs(); -} - -void LLTeleportHistoryPanel::onReturnKeyPressed() -{ - // Teleport to selected region as default action on return key pressed - onTeleport(); -} - -void LLTeleportHistoryPanel::onDoubleClickItem() -{ - // If item got doubleclick, then that item is already selected - onTeleport(); -} - -void LLTeleportHistoryPanel::onAccordionTabRightClick(LLView *view, S32 x, S32 y, MASK mask) -{ - LLAccordionCtrlTab *tab = (LLAccordionCtrlTab *) view; - - // If click occurred below the header, don't show this menu - if (y < tab->getRect().getHeight() - tab->getHeaderHeight() - tab->getPaddingBottom()) - return; - - if (mAccordionTabMenu) - { - //preventing parent (menu holder) from deleting already "dead" context menus on exit - LLView* parent = mAccordionTabMenu->getParent(); - if (parent) - { - parent->removeChild(mAccordionTabMenu); - } - delete mAccordionTabMenu; - } - - // set up the callbacks for all of the avatar menu items - // (N.B. callbacks don't take const refs as mID is local scope) - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - - registrar.add("TeleportHistory.TabOpen", boost::bind(&LLTeleportHistoryPanel::onAccordionTabOpen, this, tab)); - registrar.add("TeleportHistory.TabClose", boost::bind(&LLTeleportHistoryPanel::onAccordionTabClose, this, tab)); - - // create the context menu from the XUI - llassert(LLMenuGL::sMenuContainer != NULL); - mAccordionTabMenu = LLUICtrlFactory::getInstance()->createFromFile( - "menu_teleport_history_tab.xml", LLMenuGL::sMenuContainer, LLViewerMenuHolderGL::child_registry_t::instance()); - - mAccordionTabMenu->setItemVisible("TabOpen", !tab->isExpanded()); - mAccordionTabMenu->setItemVisible("TabClose", tab->isExpanded()); - - mAccordionTabMenu->show(x, y); - LLMenuGL::showPopup(tab, mAccordionTabMenu, x, y); -} - -void LLTeleportHistoryPanel::onAccordionTabOpen(LLAccordionCtrlTab *tab) -{ - tab->setDisplayChildren(true); - mHistoryAccordion->arrange(); -} - -void LLTeleportHistoryPanel::onAccordionTabClose(LLAccordionCtrlTab *tab) -{ - tab->setDisplayChildren(false); - mHistoryAccordion->arrange(); -} - -bool LLTeleportHistoryPanel::onClearTeleportHistoryDialog(const LLSD& notification, const LLSD& response) -{ - - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - - if (0 == option) - { - // order does matter, call this first or teleport history will contain one record(current location) - LLTeleportHistory::getInstance()->purgeItems(); - - LLTeleportHistoryStorage *th = LLTeleportHistoryStorage::getInstance(); - th->purgeItems(); - th->save(); - } - - return false; -} - -LLFlatListView* LLTeleportHistoryPanel::getFlatListViewFromTab(LLAccordionCtrlTab *tab) -{ - for (child_list_const_iter_t iter = tab->beginChild(); iter != tab->endChild(); iter++) - { - if (dynamic_cast(*iter)) - { - return (LLFlatListView*)*iter; // There should be one scroll list per tab. - } - } - - return NULL; -} - -void LLTeleportHistoryPanel::gotSLURLCallback(const std::string& slurl) -{ - LLClipboard::instance().copyToClipboard(utf8str_to_wstring(slurl), 0, slurl.size()); - - LLSD args; - args["SLURL"] = slurl; - - LLNotificationsUtil::add("CopySLURL", args); -} - -void LLTeleportHistoryPanel::onGearMenuAction(const LLSD& userdata) -{ - std::string command_name = userdata.asString(); - - if ("expand_all" == command_name) - { - S32 tabs_cnt = mItemContainers.size(); - - for (S32 n = 0; n < tabs_cnt; n++) - { - mItemContainers.at(n)->setDisplayChildren(true); - } - mHistoryAccordion->arrange(); - } - else if ("collapse_all" == command_name) - { - S32 tabs_cnt = mItemContainers.size(); - - for (S32 n = 0; n < tabs_cnt; n++) - { - mItemContainers.at(n)->setDisplayChildren(false); - } - mHistoryAccordion->arrange(); - - if (mLastSelectedFlatlList) - { - mLastSelectedFlatlList->resetSelection(); - } - } - - S32 index = -1; - if (mLastSelectedFlatlList) - { - LLTeleportHistoryFlatItem* itemp = dynamic_cast (mLastSelectedFlatlList->getSelectedItem()); - if (itemp) - { - index = itemp->getIndex(); - } - } - - if ("teleport" == command_name) - { - confirmTeleport(index); - } - else if ("view" == command_name) - { - LLTeleportHistoryFlatItem::showPlaceInfoPanel(index); - } - else if ("show_on_map" == command_name) - { - LLTeleportHistoryStorage::getInstance()->showItemOnMap(index); - } - else if ("copy_slurl" == command_name) - { - LLVector3d globalPos = LLTeleportHistoryStorage::getInstance()->getItems()[index].mGlobalPos; - LLLandmarkActions::getSLURLfromPosGlobal(globalPos, - boost::bind(&LLTeleportHistoryPanel::gotSLURLCallback, _1)); - } - else if ("remove" == command_name) - { - LLTeleportHistoryStorage::getInstance()->removeItem(index); - LLTeleportHistoryStorage::getInstance()->save(); - showTeleportHistory(); - } -} - -bool LLTeleportHistoryPanel::isActionEnabled(const LLSD& userdata) const -{ - std::string command_name = userdata.asString(); - - if (command_name == "collapse_all" - || command_name == "expand_all") - { - S32 tabs_cnt = mItemContainers.size(); - - bool has_expanded_tabs = false; - bool has_collapsed_tabs = false; - - for (S32 n = 0; n < tabs_cnt; n++) - { - LLAccordionCtrlTab* tab = mItemContainers.at(n); - if (!tab->getVisible()) - continue; - - if (tab->getDisplayChildren()) - { - has_expanded_tabs = true; - } - else - { - has_collapsed_tabs = true; - } - - if (has_expanded_tabs && has_collapsed_tabs) - { - break; - } - } - - if (command_name == "collapse_all") - { - return has_expanded_tabs; - } - - if (command_name == "expand_all") - { - return has_collapsed_tabs; - } - } - - if (command_name == "clear_history") - { - return mTeleportHistory->getItems().size() > 0; - } - - if ("teleport" == command_name - || "view" == command_name - || "show_on_map" == command_name - || "copy_slurl" == command_name - || "remove" == command_name) - { - if (!mLastSelectedFlatlList) - { - return false; - } - LLTeleportHistoryFlatItem* itemp = dynamic_cast (mLastSelectedFlatlList->getSelectedItem()); - return itemp != NULL; - } - - return false; -} - -void LLTeleportHistoryPanel::setAccordionCollapsedByUser(LLUICtrl* acc_tab, bool collapsed) -{ - LLSD param = acc_tab->getValue(); - param[COLLAPSED_BY_USER] = collapsed; - acc_tab->setValue(param); -} - -bool LLTeleportHistoryPanel::isAccordionCollapsedByUser(LLUICtrl* acc_tab) -{ - LLSD param = acc_tab->getValue(); - if(!param.has(COLLAPSED_BY_USER)) - { - return false; - } - return param[COLLAPSED_BY_USER].asBoolean(); -} - -void LLTeleportHistoryPanel::onAccordionExpand(LLUICtrl* ctrl, const LLSD& param) -{ - bool expanded = param.asBoolean(); - // Save accordion tab state to restore it in refresh() - setAccordionCollapsedByUser(ctrl, !expanded); - - // Reset selection upon accordion being collapsed - // to disable "Teleport" and "Map" buttons for hidden item. - if (!expanded && mLastSelectedFlatlList) - { - mLastSelectedFlatlList->resetSelection(); - } -} - -// static -void LLTeleportHistoryPanel::confirmTeleport(S32 hist_idx) -{ - LLSD args; - args["HISTORY_ENTRY"] = LLTeleportHistoryStorage::getInstance()->getItems()[hist_idx].mTitle; - LLNotificationsUtil::add("TeleportToHistoryEntry", args, LLSD(), - boost::bind(&LLTeleportHistoryPanel::onTeleportConfirmation, _1, _2, hist_idx)); -} - -// Called when user reacts upon teleport confirmation dialog. -// static -bool LLTeleportHistoryPanel::onTeleportConfirmation(const LLSD& notification, const LLSD& response, S32 hist_idx) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - - if (0 == option) - { - // Teleport to given history item. - LLTeleportHistoryStorage::getInstance()->goToItem(hist_idx); - } - - return false; -} +/** + * @file llpanelteleporthistory.cpp + * @brief Teleport history represented by a scrolling list + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloaterreg.h" +#include "llmenubutton.h" + +#include "llfloaterworldmap.h" +#include "llpanelteleporthistory.h" +#include "llworldmap.h" +#include "llteleporthistorystorage.h" +#include "lltextutil.h" + +#include "llaccordionctrl.h" +#include "llaccordionctrltab.h" +#include "llflatlistview.h" +#include "llfloatersidepanelcontainer.h" +#include "llnotificationsutil.h" +#include "lltextbox.h" +#include "lltoggleablemenu.h" +#include "llviewermenu.h" +#include "lllandmarkactions.h" +#include "llclipboard.h" +#include "lltrans.h" + +// Maximum number of items that can be added to a list in one pass. +// Used to limit time spent for items list update per frame. +static const U32 ADD_LIMIT = 50; + +static const std::string COLLAPSED_BY_USER = "collapsed_by_user"; + +class LLTeleportHistoryFlatItem : public LLPanel +{ +public: + LLTeleportHistoryFlatItem(S32 index, LLToggleableMenu *menu, const std::string ®ion_name, + LLDate date, const std::string &hl); + virtual ~LLTeleportHistoryFlatItem(); + + virtual bool postBuild(); + + /*virtual*/ S32 notify(const LLSD& info); + + S32 getIndex() { return mIndex; } + void setIndex(S32 index) { mIndex = index; } + const std::string& getRegionName() { return mRegionName;} + void setRegionName(const std::string& name); + void setDate(LLDate date); + void setHighlightedText(const std::string& text); + void updateTitle(); + void updateTimestamp(); + std::string getTimestamp(); + + /*virtual*/ void setValue(const LLSD& value); + + void onMouseEnter(S32 x, S32 y, MASK mask); + void onMouseLeave(S32 x, S32 y, MASK mask); + virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); + + static void showPlaceInfoPanel(S32 index); + + LLHandle getItemHandle() { mItemHandle.bind(this); return mItemHandle; } + +private: + void onProfileBtnClick(); + void showMenu(S32 x, S32 y); + + LLButton* mProfileBtn; + LLTextBox* mTitle; + LLTextBox* mTimeTextBox; + + LLToggleableMenu *mMenu; + + S32 mIndex; + std::string mRegionName; + std::string mHighlight; + LLDate mDate; + LLRootHandle mItemHandle; +}; + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +class LLTeleportHistoryFlatItemStorage: public LLSingleton +{ + LLSINGLETON_EMPTY_CTOR(LLTeleportHistoryFlatItemStorage); +protected: + typedef std::vector< LLHandle > flat_item_list_t; + +public: + LLTeleportHistoryFlatItem* getFlatItemForPersistentItem ( + LLToggleableMenu *menu, + const LLTeleportHistoryPersistentItem& persistent_item, + const S32 cur_item_index, + const std::string &hl); + + void removeItem(LLTeleportHistoryFlatItem* item); + + void purge(); + +private: + + flat_item_list_t mItems; +}; + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +LLTeleportHistoryFlatItem::LLTeleportHistoryFlatItem(S32 index, LLToggleableMenu *menu, const std::string ®ion_name, + LLDate date, const std::string &hl) +: LLPanel(), + mIndex(index), + mMenu(menu), + mRegionName(region_name), + mDate(date), + mHighlight(hl) +{ + buildFromFile("panel_teleport_history_item.xml"); +} + +LLTeleportHistoryFlatItem::~LLTeleportHistoryFlatItem() +{ +} + +//virtual +bool LLTeleportHistoryFlatItem::postBuild() +{ + mTitle = getChild("region"); + + mTimeTextBox = getChild("timestamp"); + + mProfileBtn = getChild("profile_btn"); + + mProfileBtn->setClickedCallback(boost::bind(&LLTeleportHistoryFlatItem::onProfileBtnClick, this)); + + updateTitle(); + updateTimestamp(); + + return true; +} + +S32 LLTeleportHistoryFlatItem::notify(const LLSD& info) +{ + if(info.has("detach")) + { + delete mMouseDownSignal; + mMouseDownSignal = NULL; + delete mRightMouseDownSignal; + mRightMouseDownSignal = NULL; + return 1; + } + return 0; +} + +void LLTeleportHistoryFlatItem::setValue(const LLSD& value) +{ + if (!value.isMap()) return;; + if (!value.has("selected")) return; + getChildView("selected_icon")->setVisible( value["selected"]); +} + +void LLTeleportHistoryFlatItem::setHighlightedText(const std::string& text) +{ + mHighlight = text; +} + +void LLTeleportHistoryFlatItem::setRegionName(const std::string& name) +{ + mRegionName = name; +} + +void LLTeleportHistoryFlatItem::setDate(LLDate date) +{ + mDate = date; +} + +std::string LLTeleportHistoryFlatItem::getTimestamp() +{ + const LLDate &date = mDate; + std::string timestamp = ""; + + LLDate now = LLDate::now(); + S32 now_year, now_month, now_day, now_hour, now_min, now_sec; + now.split(&now_year, &now_month, &now_day, &now_hour, &now_min, &now_sec); + + const S32 seconds_in_day = 24 * 60 * 60; + S32 seconds_today = now_hour * 60 * 60 + now_min * 60 + now_sec; + S32 time_diff = (S32) now.secondsSinceEpoch() - (S32) date.secondsSinceEpoch(); + + // Only show timestamp for today and yesterday + if(time_diff < seconds_today + seconds_in_day) + { + timestamp = "[" + LLTrans::getString("TimeHour12")+"]:[" + + LLTrans::getString("TimeMin")+"] ["+ LLTrans::getString("TimeAMPM")+"]"; + LLSD substitution; + substitution["datetime"] = (S32) date.secondsSinceEpoch(); + LLStringUtil::format(timestamp, substitution); + } + + return timestamp; + +} + +void LLTeleportHistoryFlatItem::updateTitle() +{ + static LLUIColor sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", LLColor4U(255, 255, 255)); + + LLTextUtil::textboxSetHighlightedVal( + mTitle, + LLStyle::Params().color(sFgColor), + mRegionName, + mHighlight); +} + +void LLTeleportHistoryFlatItem::updateTimestamp() +{ + static LLUIColor sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", LLColor4U(255, 255, 255)); + + LLTextUtil::textboxSetHighlightedVal( + mTimeTextBox, + LLStyle::Params().color(sFgColor), + getTimestamp(), + mHighlight); +} + +void LLTeleportHistoryFlatItem::onMouseEnter(S32 x, S32 y, MASK mask) +{ + getChildView("hovered_icon")->setVisible( true); + mProfileBtn->setVisible(true); + + LLPanel::onMouseEnter(x, y, mask); +} + +void LLTeleportHistoryFlatItem::onMouseLeave(S32 x, S32 y, MASK mask) +{ + getChildView("hovered_icon")->setVisible( false); + mProfileBtn->setVisible(false); + + LLPanel::onMouseLeave(x, y, mask); +} + +// virtual +bool LLTeleportHistoryFlatItem::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + LLPanel::handleRightMouseDown(x, y, mask); + showMenu(x, y); + return true; +} + +void LLTeleportHistoryFlatItem::showPlaceInfoPanel(S32 index) +{ + LLSD params; + params["id"] = index; + params["type"] = "teleport_history"; + + LLFloaterSidePanelContainer::showPanel("places", params); +} + +void LLTeleportHistoryFlatItem::onProfileBtnClick() +{ + LLTeleportHistoryFlatItem::showPlaceInfoPanel(mIndex); +} + +void LLTeleportHistoryFlatItem::showMenu(S32 x, S32 y) +{ + mMenu->setButtonRect(this); + mMenu->buildDrawLabels(); + mMenu->arrangeAndClear(); + mMenu->updateParent(LLMenuGL::sMenuContainer); + + LLMenuGL::showPopup(this, mMenu, x, y); +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +LLTeleportHistoryFlatItem* +LLTeleportHistoryFlatItemStorage::getFlatItemForPersistentItem ( + LLToggleableMenu *menu, + const LLTeleportHistoryPersistentItem& persistent_item, + const S32 cur_item_index, + const std::string &hl) +{ + LLTeleportHistoryFlatItem* item = NULL; + if ( cur_item_index < (S32) mItems.size() ) + { + item = mItems[cur_item_index].get(); + if (item->getParent() == NULL) + { + item->setIndex(cur_item_index); + item->setRegionName(persistent_item.mTitle); + item->setDate(persistent_item.mDate); + item->setHighlightedText(hl); + item->setVisible(true); + item->updateTitle(); + item->updateTimestamp(); + } + else + { + // Item already added to parent + item = NULL; + } + } + + if ( !item ) + { + item = new LLTeleportHistoryFlatItem(cur_item_index, + menu, + persistent_item.mTitle, + persistent_item.mDate, + hl); + mItems.push_back(item->getItemHandle()); + } + + return item; +} + +void LLTeleportHistoryFlatItemStorage::removeItem(LLTeleportHistoryFlatItem* item) +{ + if (item) + { + flat_item_list_t::iterator item_iter = std::find(mItems.begin(), + mItems.end(), + item->getItemHandle()); + if (item_iter != mItems.end()) + { + mItems.erase(item_iter); + } + } +} + +void LLTeleportHistoryFlatItemStorage::purge() +{ + for ( flat_item_list_t::iterator + it = mItems.begin(), + it_end = mItems.end(); + it != it_end; ++it ) + { + LLHandle item_handle = *it; + if ( !item_handle.isDead() && item_handle.get()->getParent() == NULL ) + { + item_handle.get()->die(); + } + } + mItems.clear(); +} + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// + + +// Not yet implemented; need to remove buildPanel() from constructor when we switch +//static LLRegisterPanelClassWrapper t_teleport_history("panel_teleport_history"); + +LLTeleportHistoryPanel::LLTeleportHistoryPanel() + : LLPanelPlacesTab(), + mDirty(true), + mCurrentItem(0), + mTeleportHistory(NULL), + mHistoryAccordion(NULL), + mAccordionTabMenu(NULL), + mLastSelectedFlatlList(NULL), + mLastSelectedItemIndex(-1), + mGearItemMenu(NULL), + mSortingMenu(NULL) +{ + buildFromFile( "panel_teleport_history.xml"); +} + +LLTeleportHistoryPanel::~LLTeleportHistoryPanel() +{ + LLTeleportHistoryFlatItemStorage::instance().purge(); + mTeleportHistoryChangedConnection.disconnect(); +} + +bool LLTeleportHistoryPanel::postBuild() +{ + mCommitCallbackRegistrar.add("TeleportHistory.GearMenu.Action", boost::bind(&LLTeleportHistoryPanel::onGearMenuAction, this, _2)); + mEnableCallbackRegistrar.add("TeleportHistory.GearMenu.Enable", boost::bind(&LLTeleportHistoryPanel::isActionEnabled, this, _2)); + + // init menus before list, since menus are passed to list + mGearItemMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_teleport_history_item.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + mGearItemMenu->setAlwaysShowMenu(true); // all items can be disabled if nothing is selected, show anyway + mSortingMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_teleport_history_gear.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + + mTeleportHistory = LLTeleportHistoryStorage::getInstance(); + if (mTeleportHistory) + { + mTeleportHistoryChangedConnection = mTeleportHistory->setHistoryChangedCallback(boost::bind(&LLTeleportHistoryPanel::onTeleportHistoryChange, this, _1)); + } + + mHistoryAccordion = getChild("history_accordion"); + + if (mHistoryAccordion) + { + for (child_list_const_iter_t iter = mHistoryAccordion->beginChild(); iter != mHistoryAccordion->endChild(); iter++) + { + if (dynamic_cast(*iter)) + { + LLAccordionCtrlTab* tab = (LLAccordionCtrlTab*)*iter; + tab->setRightMouseDownCallback(boost::bind(&LLTeleportHistoryPanel::onAccordionTabRightClick, this, _1, _2, _3, _4)); + tab->setDisplayChildren(false); + tab->setDropDownStateChangedCallback(boost::bind(&LLTeleportHistoryPanel::onAccordionExpand, this, _1, _2)); + + // All accordion tabs are collapsed initially + setAccordionCollapsedByUser(tab, true); + + mItemContainers.push_back(tab); + + LLFlatListView* fl = getFlatListViewFromTab(tab); + if (fl) + { + fl->setCommitOnSelectionChange(true); + fl->setDoubleClickCallback(boost::bind(&LLTeleportHistoryPanel::onDoubleClickItem, this)); + fl->setCommitCallback(boost::bind(&LLTeleportHistoryPanel::handleItemSelect, this, fl)); + fl->setReturnCallback(boost::bind(&LLTeleportHistoryPanel::onReturnKeyPressed, this)); + } + } + } + + // Open first 2 accordion tabs + if (mItemContainers.size() > 1) + { + LLAccordionCtrlTab* tab = mItemContainers.at(mItemContainers.size() - 1); + tab->setDisplayChildren(true); + setAccordionCollapsedByUser(tab, false); + } + + if (mItemContainers.size() > 2) + { + LLAccordionCtrlTab* tab = mItemContainers.at(mItemContainers.size() - 2); + tab->setDisplayChildren(true); + setAccordionCollapsedByUser(tab, false); + } + } + + return true; +} + +// virtual +void LLTeleportHistoryPanel::draw() +{ + if (mDirty) + refresh(); + + LLPanelPlacesTab::draw(); +} + +// virtual +void LLTeleportHistoryPanel::onSearchEdit(const std::string& string) +{ + sFilterSubString = string; + showTeleportHistory(); +} + +// virtual +bool LLTeleportHistoryPanel::isSingleItemSelected() +{ + return mLastSelectedFlatlList && mLastSelectedFlatlList->getSelectedItem(); +} + +// virtual +void LLTeleportHistoryPanel::onShowOnMap() +{ + if (!mLastSelectedFlatlList) + return; + + LLTeleportHistoryFlatItem* itemp = dynamic_cast (mLastSelectedFlatlList->getSelectedItem()); + + if(!itemp) + return; + + LLVector3d global_pos = mTeleportHistory->getItems()[itemp->getIndex()].mGlobalPos; + + if (!global_pos.isExactlyZero()) + { + LLFloaterWorldMap::getInstance()->trackLocation(global_pos); + LLFloaterReg::showInstance("world_map", "center"); + } +} + +//virtual +void LLTeleportHistoryPanel::onShowProfile() +{ + if (!mLastSelectedFlatlList) + return; + + LLTeleportHistoryFlatItem* itemp = dynamic_cast (mLastSelectedFlatlList->getSelectedItem()); + + if(!itemp) + return; + + LLTeleportHistoryFlatItem::showPlaceInfoPanel(itemp->getIndex()); +} + +// virtual +void LLTeleportHistoryPanel::onTeleport() +{ + if (!mLastSelectedFlatlList) + return; + + LLTeleportHistoryFlatItem* itemp = dynamic_cast (mLastSelectedFlatlList->getSelectedItem()); + if(!itemp) + return; + + // teleport to existing item in history, so we don't add it again + confirmTeleport(itemp->getIndex()); +} + +// virtual +void LLTeleportHistoryPanel::onRemoveSelected() +{ + LLNotificationsUtil::add("ConfirmClearTeleportHistory", LLSD(), LLSD(), boost::bind(&LLTeleportHistoryPanel::onClearTeleportHistoryDialog, this, _1, _2)); +} + +/* +// virtual +void LLTeleportHistoryPanel::onCopySLURL() +{ + LLScrollListItem* itemp = mHistoryItems->getFirstSelected(); + if(!itemp) + return; + + S32 index = itemp->getColumn(LIST_INDEX)->getValue().asInteger(); + + const LLTeleportHistory::slurl_list_t& hist_items = mTeleportHistory->getItems(); + + LLVector3d global_pos = hist_items[index].mGlobalPos; + + U64 new_region_handle = to_region_handle(global_pos); + + LLWorldMapMessage::url_callback_t cb = boost::bind( + &LLPanelPlacesTab::onRegionResponse, this, + global_pos, _1, _2, _3, _4); + + LLWorldMap::getInstance()->sendHandleRegionRequest(new_region_handle, cb, std::string("unused"), false); +} +*/ + +// virtual +void LLTeleportHistoryPanel::updateVerbs() +{ + if (!isTabVisible()) + return; + + if (sRemoveBtn) + { + sRemoveBtn->setEnabled(true); + } +} + +// virtual +LLToggleableMenu* LLTeleportHistoryPanel::getSelectionMenu() +{ + return mGearItemMenu; +} + +// virtual +LLToggleableMenu* LLTeleportHistoryPanel::getSortingMenu() +{ + return mSortingMenu; +} + +// virtual +LLToggleableMenu* LLTeleportHistoryPanel::getCreateMenu() +{ + return NULL; +} + +void LLTeleportHistoryPanel::getNextTab(const LLDate& item_date, S32& tab_idx, LLDate& tab_date) +{ + const U32 seconds_in_day = 24 * 60 * 60; + + S32 tabs_cnt = mItemContainers.size(); + S32 curr_year = 0, curr_month = 0, curr_day = 0; + + tab_date = LLDate::now(); + tab_date.split(&curr_year, &curr_month, &curr_day); + tab_date.fromYMDHMS(curr_year, curr_month, curr_day); // Set hour, min, and sec to 0 + tab_date.secondsSinceEpoch(tab_date.secondsSinceEpoch() + seconds_in_day); + + tab_idx = -1; + + while (tab_idx < tabs_cnt - 1 && item_date < tab_date) + { + tab_idx++; + + if (tab_idx <= tabs_cnt - 4) + { + // All tabs, except last three, are tabs for one day, so just push tab_date back by one day + tab_date.secondsSinceEpoch(tab_date.secondsSinceEpoch() - seconds_in_day); + } + else if (tab_idx == tabs_cnt - 3) // 6 day and older, low boundary is 1 month + { + tab_date = LLDate::now(); + tab_date.split(&curr_year, &curr_month, &curr_day); + curr_month--; + if (0 == curr_month) + { + curr_month = 12; + curr_year--; + } + tab_date.fromYMDHMS(curr_year, curr_month, curr_day); + } + else if (tab_idx == tabs_cnt - 2) // 1 month and older, low boundary is 6 months + { + tab_date = LLDate::now(); + tab_date.split(&curr_year, &curr_month, &curr_day); + if (curr_month > 6) + { + curr_month -= 6; + } + else + { + curr_month += 6; + curr_year--; + } + tab_date.fromYMDHMS(curr_year, curr_month, curr_day); + } + else // 6 months and older + { + tab_date.secondsSinceEpoch(0); + } + } +} + +// Called to add items, no more, than ADD_LIMIT at time +void LLTeleportHistoryPanel::refresh() +{ + if (!mHistoryAccordion) + { + mDirty = false; + return; + } + + const LLTeleportHistoryStorage::slurl_list_t& items = mTeleportHistory->getItems(); + + // Setting tab_boundary_date to "now", so date from any item would be earlier, than boundary. + // That leads to call to getNextTab to get right tab_idx in first pass + LLDate tab_boundary_date = LLDate::now(); + + LLFlatListView* curr_flat_view = NULL; + std::string filter_string = sFilterSubString; + LLStringUtil::toUpper(filter_string); + + U32 added_items = 0; + while (mCurrentItem >= 0) + { + // Filtering + if (!filter_string.empty()) + { + std::string landmark_title(items[mCurrentItem].mTitle); + LLStringUtil::toUpper(landmark_title); + if( std::string::npos == landmark_title.find(filter_string) ) + { + mCurrentItem--; + continue; + } + } + + // Checking whether date of item is earlier, than tab_boundary_date. + // In that case, item should be added to another tab + const LLDate &date = items[mCurrentItem].mDate; + + if (date < tab_boundary_date) + { + // Getting apropriate tab_idx for this and subsequent items, + // tab_boundary_date would be earliest possible date for this tab + S32 tab_idx = 0; + getNextTab(date, tab_idx, tab_boundary_date); + tab_idx = mItemContainers.size() - 1 - tab_idx; + if (tab_idx >= 0) + { + LLAccordionCtrlTab* tab = mItemContainers.at(tab_idx); + tab->setVisible(true); + + // Expand all accordion tabs when filtering + if(!sFilterSubString.empty()) + { + //store accordion tab state when filter is not empty + tab->notifyChildren(LLSD().with("action","store_state")); + + tab->setDisplayChildren(true); + } + // Restore each tab's expand state when not filtering + else + { + bool collapsed = isAccordionCollapsedByUser(tab); + tab->setDisplayChildren(!collapsed); + + //restore accordion state after all those accodrion tabmanipulations + tab->notifyChildren(LLSD().with("action","restore_state")); + } + + curr_flat_view = getFlatListViewFromTab(tab); + } + } + + if (curr_flat_view) + { + LLTeleportHistoryFlatItem* item = + LLTeleportHistoryFlatItemStorage::instance() + .getFlatItemForPersistentItem(mGearItemMenu, + items[mCurrentItem], + mCurrentItem, + filter_string); + if ( !curr_flat_view->addItem(item, LLUUID::null, ADD_BOTTOM, false) ) + LL_ERRS() << "Couldn't add flat item to teleport history." << LL_ENDL; + if (mLastSelectedItemIndex == mCurrentItem) + curr_flat_view->selectItem(item, true); + } + + mCurrentItem--; + + if (++added_items >= ADD_LIMIT) + break; + } + + for (S32 n = mItemContainers.size() - 1; n >= 0; --n) + { + LLAccordionCtrlTab* tab = mItemContainers.at(n); + LLFlatListView* fv = getFlatListViewFromTab(tab); + if (fv) + { + fv->notify(LLSD().with("rearrange", LLSD())); + } + } + + mHistoryAccordion->setFilterSubString(sFilterSubString); + + mHistoryAccordion->arrange(); + + updateVerbs(); + + if (mCurrentItem < 0) + mDirty = false; +} + +void LLTeleportHistoryPanel::onTeleportHistoryChange(S32 removed_index) +{ + mLastSelectedItemIndex = -1; + + if (-1 == removed_index) + showTeleportHistory(); // recreate all items + else + { + replaceItem(removed_index); // replace removed item by most recent + updateVerbs(); + } +} + +void LLTeleportHistoryPanel::replaceItem(S32 removed_index) +{ + // Flat list for 'Today' (mItemContainers keeps accordion tabs in reverse order) + LLFlatListView* fv = NULL; + + if (mItemContainers.size() > 0) + { + fv = getFlatListViewFromTab(mItemContainers[mItemContainers.size() - 1]); + } + + // Empty flat list for 'Today' means that other flat lists are empty as well, + // so all items from teleport history should be added. + if (!fv || fv->size() == 0) + { + showTeleportHistory(); + return; + } + + const LLTeleportHistoryStorage::slurl_list_t& history_items = mTeleportHistory->getItems(); + LLTeleportHistoryFlatItem* item = LLTeleportHistoryFlatItemStorage::instance() + .getFlatItemForPersistentItem(mGearItemMenu, + history_items[history_items.size() - 1], // Most recent item, it was added instead of removed + history_items.size(), // index will be decremented inside loop below + sFilterSubString); + + fv->addItem(item, LLUUID::null, ADD_TOP); + + // Index of each item, from last to removed item should be decremented + // to point to the right item in LLTeleportHistoryStorage + for (S32 tab_idx = mItemContainers.size() - 1; tab_idx >= 0; --tab_idx) + { + LLAccordionCtrlTab* tab = mItemContainers.at(tab_idx); + if (!tab->getVisible()) + continue; + + fv = getFlatListViewFromTab(tab); + if (!fv) + { + showTeleportHistory(); + return; + } + + std::vector items; + fv->getItems(items); + + S32 items_cnt = items.size(); + for (S32 n = 0; n < items_cnt; ++n) + { + LLTeleportHistoryFlatItem *item = (LLTeleportHistoryFlatItem*) items[n]; + + if (item->getIndex() == removed_index) + { + LLTeleportHistoryFlatItemStorage::instance().removeItem(item); + + fv->removeItem(item); + + // If flat list becames empty, then accordion tab should be hidden + if (fv->size() == 0) + tab->setVisible(false); + + mHistoryAccordion->arrange(); + + return; // No need to decrement idexes for the rest of items + } + + item->setIndex(item->getIndex() - 1); + } + } +} + +void LLTeleportHistoryPanel::showTeleportHistory() +{ + mDirty = true; + + // Starting to add items from last one, in reverse order, + // since TeleportHistory keeps most recent item at the end + if (!mTeleportHistory) + { + mTeleportHistory = LLTeleportHistoryStorage::getInstance(); + } + + mCurrentItem = mTeleportHistory->getItems().size() - 1; + + for (S32 n = mItemContainers.size() - 1; n >= 0; --n) + { + LLAccordionCtrlTab* tab = mItemContainers.at(n); + if (tab) + { + tab->setVisible(false); + + LLFlatListView* fv = getFlatListViewFromTab(tab); + if (fv) + { + // Detached panels are managed by LLTeleportHistoryFlatItemStorage + std::vector detached_items; + fv->detachItems(detached_items); + } + } + } +} + +void LLTeleportHistoryPanel::handleItemSelect(LLFlatListView* selected) +{ + mLastSelectedFlatlList = selected; + LLTeleportHistoryFlatItem* item = dynamic_cast (mLastSelectedFlatlList->getSelectedItem()); + if (item) + mLastSelectedItemIndex = item->getIndex(); + + S32 tabs_cnt = mItemContainers.size(); + + for (S32 n = 0; n < tabs_cnt; n++) + { + LLAccordionCtrlTab* tab = mItemContainers.at(n); + + if (!tab->getVisible()) + continue; + + LLFlatListView *flv = getFlatListViewFromTab(tab); + if (!flv) + continue; + + if (flv == selected) + continue; + + flv->resetSelection(true); + } + + updateVerbs(); +} + +void LLTeleportHistoryPanel::onReturnKeyPressed() +{ + // Teleport to selected region as default action on return key pressed + onTeleport(); +} + +void LLTeleportHistoryPanel::onDoubleClickItem() +{ + // If item got doubleclick, then that item is already selected + onTeleport(); +} + +void LLTeleportHistoryPanel::onAccordionTabRightClick(LLView *view, S32 x, S32 y, MASK mask) +{ + LLAccordionCtrlTab *tab = (LLAccordionCtrlTab *) view; + + // If click occurred below the header, don't show this menu + if (y < tab->getRect().getHeight() - tab->getHeaderHeight() - tab->getPaddingBottom()) + return; + + if (mAccordionTabMenu) + { + //preventing parent (menu holder) from deleting already "dead" context menus on exit + LLView* parent = mAccordionTabMenu->getParent(); + if (parent) + { + parent->removeChild(mAccordionTabMenu); + } + delete mAccordionTabMenu; + } + + // set up the callbacks for all of the avatar menu items + // (N.B. callbacks don't take const refs as mID is local scope) + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + + registrar.add("TeleportHistory.TabOpen", boost::bind(&LLTeleportHistoryPanel::onAccordionTabOpen, this, tab)); + registrar.add("TeleportHistory.TabClose", boost::bind(&LLTeleportHistoryPanel::onAccordionTabClose, this, tab)); + + // create the context menu from the XUI + llassert(LLMenuGL::sMenuContainer != NULL); + mAccordionTabMenu = LLUICtrlFactory::getInstance()->createFromFile( + "menu_teleport_history_tab.xml", LLMenuGL::sMenuContainer, LLViewerMenuHolderGL::child_registry_t::instance()); + + mAccordionTabMenu->setItemVisible("TabOpen", !tab->isExpanded()); + mAccordionTabMenu->setItemVisible("TabClose", tab->isExpanded()); + + mAccordionTabMenu->show(x, y); + LLMenuGL::showPopup(tab, mAccordionTabMenu, x, y); +} + +void LLTeleportHistoryPanel::onAccordionTabOpen(LLAccordionCtrlTab *tab) +{ + tab->setDisplayChildren(true); + mHistoryAccordion->arrange(); +} + +void LLTeleportHistoryPanel::onAccordionTabClose(LLAccordionCtrlTab *tab) +{ + tab->setDisplayChildren(false); + mHistoryAccordion->arrange(); +} + +bool LLTeleportHistoryPanel::onClearTeleportHistoryDialog(const LLSD& notification, const LLSD& response) +{ + + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + + if (0 == option) + { + // order does matter, call this first or teleport history will contain one record(current location) + LLTeleportHistory::getInstance()->purgeItems(); + + LLTeleportHistoryStorage *th = LLTeleportHistoryStorage::getInstance(); + th->purgeItems(); + th->save(); + } + + return false; +} + +LLFlatListView* LLTeleportHistoryPanel::getFlatListViewFromTab(LLAccordionCtrlTab *tab) +{ + for (child_list_const_iter_t iter = tab->beginChild(); iter != tab->endChild(); iter++) + { + if (dynamic_cast(*iter)) + { + return (LLFlatListView*)*iter; // There should be one scroll list per tab. + } + } + + return NULL; +} + +void LLTeleportHistoryPanel::gotSLURLCallback(const std::string& slurl) +{ + LLClipboard::instance().copyToClipboard(utf8str_to_wstring(slurl), 0, slurl.size()); + + LLSD args; + args["SLURL"] = slurl; + + LLNotificationsUtil::add("CopySLURL", args); +} + +void LLTeleportHistoryPanel::onGearMenuAction(const LLSD& userdata) +{ + std::string command_name = userdata.asString(); + + if ("expand_all" == command_name) + { + S32 tabs_cnt = mItemContainers.size(); + + for (S32 n = 0; n < tabs_cnt; n++) + { + mItemContainers.at(n)->setDisplayChildren(true); + } + mHistoryAccordion->arrange(); + } + else if ("collapse_all" == command_name) + { + S32 tabs_cnt = mItemContainers.size(); + + for (S32 n = 0; n < tabs_cnt; n++) + { + mItemContainers.at(n)->setDisplayChildren(false); + } + mHistoryAccordion->arrange(); + + if (mLastSelectedFlatlList) + { + mLastSelectedFlatlList->resetSelection(); + } + } + + S32 index = -1; + if (mLastSelectedFlatlList) + { + LLTeleportHistoryFlatItem* itemp = dynamic_cast (mLastSelectedFlatlList->getSelectedItem()); + if (itemp) + { + index = itemp->getIndex(); + } + } + + if ("teleport" == command_name) + { + confirmTeleport(index); + } + else if ("view" == command_name) + { + LLTeleportHistoryFlatItem::showPlaceInfoPanel(index); + } + else if ("show_on_map" == command_name) + { + LLTeleportHistoryStorage::getInstance()->showItemOnMap(index); + } + else if ("copy_slurl" == command_name) + { + LLVector3d globalPos = LLTeleportHistoryStorage::getInstance()->getItems()[index].mGlobalPos; + LLLandmarkActions::getSLURLfromPosGlobal(globalPos, + boost::bind(&LLTeleportHistoryPanel::gotSLURLCallback, _1)); + } + else if ("remove" == command_name) + { + LLTeleportHistoryStorage::getInstance()->removeItem(index); + LLTeleportHistoryStorage::getInstance()->save(); + showTeleportHistory(); + } +} + +bool LLTeleportHistoryPanel::isActionEnabled(const LLSD& userdata) const +{ + std::string command_name = userdata.asString(); + + if (command_name == "collapse_all" + || command_name == "expand_all") + { + S32 tabs_cnt = mItemContainers.size(); + + bool has_expanded_tabs = false; + bool has_collapsed_tabs = false; + + for (S32 n = 0; n < tabs_cnt; n++) + { + LLAccordionCtrlTab* tab = mItemContainers.at(n); + if (!tab->getVisible()) + continue; + + if (tab->getDisplayChildren()) + { + has_expanded_tabs = true; + } + else + { + has_collapsed_tabs = true; + } + + if (has_expanded_tabs && has_collapsed_tabs) + { + break; + } + } + + if (command_name == "collapse_all") + { + return has_expanded_tabs; + } + + if (command_name == "expand_all") + { + return has_collapsed_tabs; + } + } + + if (command_name == "clear_history") + { + return mTeleportHistory->getItems().size() > 0; + } + + if ("teleport" == command_name + || "view" == command_name + || "show_on_map" == command_name + || "copy_slurl" == command_name + || "remove" == command_name) + { + if (!mLastSelectedFlatlList) + { + return false; + } + LLTeleportHistoryFlatItem* itemp = dynamic_cast (mLastSelectedFlatlList->getSelectedItem()); + return itemp != NULL; + } + + return false; +} + +void LLTeleportHistoryPanel::setAccordionCollapsedByUser(LLUICtrl* acc_tab, bool collapsed) +{ + LLSD param = acc_tab->getValue(); + param[COLLAPSED_BY_USER] = collapsed; + acc_tab->setValue(param); +} + +bool LLTeleportHistoryPanel::isAccordionCollapsedByUser(LLUICtrl* acc_tab) +{ + LLSD param = acc_tab->getValue(); + if(!param.has(COLLAPSED_BY_USER)) + { + return false; + } + return param[COLLAPSED_BY_USER].asBoolean(); +} + +void LLTeleportHistoryPanel::onAccordionExpand(LLUICtrl* ctrl, const LLSD& param) +{ + bool expanded = param.asBoolean(); + // Save accordion tab state to restore it in refresh() + setAccordionCollapsedByUser(ctrl, !expanded); + + // Reset selection upon accordion being collapsed + // to disable "Teleport" and "Map" buttons for hidden item. + if (!expanded && mLastSelectedFlatlList) + { + mLastSelectedFlatlList->resetSelection(); + } +} + +// static +void LLTeleportHistoryPanel::confirmTeleport(S32 hist_idx) +{ + LLSD args; + args["HISTORY_ENTRY"] = LLTeleportHistoryStorage::getInstance()->getItems()[hist_idx].mTitle; + LLNotificationsUtil::add("TeleportToHistoryEntry", args, LLSD(), + boost::bind(&LLTeleportHistoryPanel::onTeleportConfirmation, _1, _2, hist_idx)); +} + +// Called when user reacts upon teleport confirmation dialog. +// static +bool LLTeleportHistoryPanel::onTeleportConfirmation(const LLSD& notification, const LLSD& response, S32 hist_idx) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + + if (0 == option) + { + // Teleport to given history item. + LLTeleportHistoryStorage::getInstance()->goToItem(hist_idx); + } + + return false; +} diff --git a/indra/newview/llpanelteleporthistory.h b/indra/newview/llpanelteleporthistory.h index c00a41cadc..48b795cc5e 100644 --- a/indra/newview/llpanelteleporthistory.h +++ b/indra/newview/llpanelteleporthistory.h @@ -1,118 +1,118 @@ -/** - * @file llpanelteleporthistory.h - * @brief Teleport history represented by a scrolling list - * class definition - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELTELEPORTHISTORY_H -#define LL_LLPANELTELEPORTHISTORY_H - -#include "lluictrlfactory.h" - -#include "llpanelplacestab.h" -#include "llteleporthistory.h" -#include "llmenugl.h" - -class LLTeleportHistoryStorage; -class LLAccordionCtrl; -class LLAccordionCtrlTab; -class LLFlatListView; -class LLMenuButton; - -class LLTeleportHistoryPanel : public LLPanelPlacesTab -{ -public: - LLTeleportHistoryPanel(); - virtual ~LLTeleportHistoryPanel(); - - bool postBuild() override; - void draw() override; - - void onSearchEdit(const std::string& string) override; - void onShowOnMap() override; - void onShowProfile() override; - void onTeleport() override; - ///*virtual*/ void onCopySLURL(); - void onRemoveSelected() override; - void updateVerbs() override; - bool isSingleItemSelected() override; - - LLToggleableMenu* getSelectionMenu() override; - LLToggleableMenu* getSortingMenu() override; - LLToggleableMenu* getCreateMenu() override; - - bool handleDragAndDropToTrash(bool drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept) override { return false; } - -private: - - void onDoubleClickItem(); - void onReturnKeyPressed(); - void onAccordionTabRightClick(LLView *view, S32 x, S32 y, MASK mask); - void onAccordionTabOpen(LLAccordionCtrlTab *tab); - void onAccordionTabClose(LLAccordionCtrlTab *tab); - void onExpandAllFolders(); - void onCollapseAllFolders(); - void onClearTeleportHistory(); - bool onClearTeleportHistoryDialog(const LLSD& notification, const LLSD& response); - - void refresh() override; - void getNextTab(const LLDate& item_date, S32& curr_tab, LLDate& tab_date); - void onTeleportHistoryChange(S32 removed_index); - void replaceItem(S32 removed_index); - void showTeleportHistory(); - void handleItemSelect(LLFlatListView* ); - LLFlatListView* getFlatListViewFromTab(LLAccordionCtrlTab *); - static void gotSLURLCallback(const std::string& slurl); - void onGearMenuAction(const LLSD& userdata); - bool isActionEnabled(const LLSD& userdata) const; - - void setAccordionCollapsedByUser(LLUICtrl* acc_tab, bool collapsed); - bool isAccordionCollapsedByUser(LLUICtrl* acc_tab); - void onAccordionExpand(LLUICtrl* ctrl, const LLSD& param); - - static void confirmTeleport(S32 hist_idx); - static bool onTeleportConfirmation(const LLSD& notification, const LLSD& response, S32 hist_idx); - - LLTeleportHistoryStorage* mTeleportHistory; - LLAccordionCtrl* mHistoryAccordion; - - LLFlatListView* mLastSelectedFlatlList; - S32 mLastSelectedItemIndex; - bool mDirty; - S32 mCurrentItem; - - typedef std::vector item_containers_t; - item_containers_t mItemContainers; - - LLContextMenu* mAccordionTabMenu; - - LLToggleableMenu* mGearItemMenu; - LLToggleableMenu* mSortingMenu; - - boost::signals2::connection mTeleportHistoryChangedConnection; -}; - - - -#endif //LL_LLPANELTELEPORTHISTORY_H +/** + * @file llpanelteleporthistory.h + * @brief Teleport history represented by a scrolling list + * class definition + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELTELEPORTHISTORY_H +#define LL_LLPANELTELEPORTHISTORY_H + +#include "lluictrlfactory.h" + +#include "llpanelplacestab.h" +#include "llteleporthistory.h" +#include "llmenugl.h" + +class LLTeleportHistoryStorage; +class LLAccordionCtrl; +class LLAccordionCtrlTab; +class LLFlatListView; +class LLMenuButton; + +class LLTeleportHistoryPanel : public LLPanelPlacesTab +{ +public: + LLTeleportHistoryPanel(); + virtual ~LLTeleportHistoryPanel(); + + bool postBuild() override; + void draw() override; + + void onSearchEdit(const std::string& string) override; + void onShowOnMap() override; + void onShowProfile() override; + void onTeleport() override; + ///*virtual*/ void onCopySLURL(); + void onRemoveSelected() override; + void updateVerbs() override; + bool isSingleItemSelected() override; + + LLToggleableMenu* getSelectionMenu() override; + LLToggleableMenu* getSortingMenu() override; + LLToggleableMenu* getCreateMenu() override; + + bool handleDragAndDropToTrash(bool drop, EDragAndDropType cargo_type, void* cargo_data, EAcceptance* accept) override { return false; } + +private: + + void onDoubleClickItem(); + void onReturnKeyPressed(); + void onAccordionTabRightClick(LLView *view, S32 x, S32 y, MASK mask); + void onAccordionTabOpen(LLAccordionCtrlTab *tab); + void onAccordionTabClose(LLAccordionCtrlTab *tab); + void onExpandAllFolders(); + void onCollapseAllFolders(); + void onClearTeleportHistory(); + bool onClearTeleportHistoryDialog(const LLSD& notification, const LLSD& response); + + void refresh() override; + void getNextTab(const LLDate& item_date, S32& curr_tab, LLDate& tab_date); + void onTeleportHistoryChange(S32 removed_index); + void replaceItem(S32 removed_index); + void showTeleportHistory(); + void handleItemSelect(LLFlatListView* ); + LLFlatListView* getFlatListViewFromTab(LLAccordionCtrlTab *); + static void gotSLURLCallback(const std::string& slurl); + void onGearMenuAction(const LLSD& userdata); + bool isActionEnabled(const LLSD& userdata) const; + + void setAccordionCollapsedByUser(LLUICtrl* acc_tab, bool collapsed); + bool isAccordionCollapsedByUser(LLUICtrl* acc_tab); + void onAccordionExpand(LLUICtrl* ctrl, const LLSD& param); + + static void confirmTeleport(S32 hist_idx); + static bool onTeleportConfirmation(const LLSD& notification, const LLSD& response, S32 hist_idx); + + LLTeleportHistoryStorage* mTeleportHistory; + LLAccordionCtrl* mHistoryAccordion; + + LLFlatListView* mLastSelectedFlatlList; + S32 mLastSelectedItemIndex; + bool mDirty; + S32 mCurrentItem; + + typedef std::vector item_containers_t; + item_containers_t mItemContainers; + + LLContextMenu* mAccordionTabMenu; + + LLToggleableMenu* mGearItemMenu; + LLToggleableMenu* mSortingMenu; + + boost::signals2::connection mTeleportHistoryChangedConnection; +}; + + + +#endif //LL_LLPANELTELEPORTHISTORY_H diff --git a/indra/newview/llpaneltiptoast.cpp b/indra/newview/llpaneltiptoast.cpp index a87c7d0129..afed140075 100644 --- a/indra/newview/llpaneltiptoast.cpp +++ b/indra/newview/llpaneltiptoast.cpp @@ -1,63 +1,63 @@ -/** - * @file llpaneltiptoast.cpp - * @brief Represents a base class of tip toast panels. - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpaneltiptoast.h" - -bool LLPanelTipToast::postBuild() -{ - mMessageText= findChild("message"); - - if (mMessageText != NULL) - { - mMessageText->setMouseUpCallback(boost::bind(&LLPanelTipToast::onMessageTextClick,this)); - setMouseUpCallback(boost::bind(&LLPanelTipToast::onPanelClick, this, _2, _3, _4)); - } - else - { - llassert(!"Can't find child 'message' text box."); - return false; - } - - return true; -} - -void LLPanelTipToast::onMessageTextClick() -{ - // notify parent toast about need hide - LLSD info; - info["action"] = "hide_toast"; - notifyParent(info); -} - -void LLPanelTipToast::onPanelClick(S32 x, S32 y, MASK mask) -{ - if (!mMessageText->getRect().pointInRect(x, y)) - { - onMessageTextClick(); - } -} +/** + * @file llpaneltiptoast.cpp + * @brief Represents a base class of tip toast panels. + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpaneltiptoast.h" + +bool LLPanelTipToast::postBuild() +{ + mMessageText= findChild("message"); + + if (mMessageText != NULL) + { + mMessageText->setMouseUpCallback(boost::bind(&LLPanelTipToast::onMessageTextClick,this)); + setMouseUpCallback(boost::bind(&LLPanelTipToast::onPanelClick, this, _2, _3, _4)); + } + else + { + llassert(!"Can't find child 'message' text box."); + return false; + } + + return true; +} + +void LLPanelTipToast::onMessageTextClick() +{ + // notify parent toast about need hide + LLSD info; + info["action"] = "hide_toast"; + notifyParent(info); +} + +void LLPanelTipToast::onPanelClick(S32 x, S32 y, MASK mask) +{ + if (!mMessageText->getRect().pointInRect(x, y)) + { + onMessageTextClick(); + } +} diff --git a/indra/newview/llpaneltiptoast.h b/indra/newview/llpaneltiptoast.h index 66e2252ac4..85f2a7583a 100644 --- a/indra/newview/llpaneltiptoast.h +++ b/indra/newview/llpaneltiptoast.h @@ -1,51 +1,51 @@ -/** - * @file llpaneltiptoast.h - * @brief Represents a base class of tip toast panels. - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#include "lltoastpanel.h" - -#ifndef LL_PANELTOASTTIP_H -#define LL_PANELTOASTTIP_H - -/** - * Base class for tip toast panels. - * - * Tip toast panels are required to have text message box named as 'message'. - */ -class LLPanelTipToast : public LLToastPanel -{ - LOG_CLASS(LLPanelTipToast); -public: - LLPanelTipToast(const LLNotificationPtr& notification): LLToastPanel(notification) {} - bool postBuild() override; -private: - void onMessageTextClick(); - void onPanelClick(S32 x, S32 y, MASK mask); - - LLUICtrl* mMessageText; -}; - -#endif /* LL_PANELTOASTTIP_H */ +/** + * @file llpaneltiptoast.h + * @brief Represents a base class of tip toast panels. + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#include "lltoastpanel.h" + +#ifndef LL_PANELTOASTTIP_H +#define LL_PANELTOASTTIP_H + +/** + * Base class for tip toast panels. + * + * Tip toast panels are required to have text message box named as 'message'. + */ +class LLPanelTipToast : public LLToastPanel +{ + LOG_CLASS(LLPanelTipToast); +public: + LLPanelTipToast(const LLNotificationPtr& notification): LLToastPanel(notification) {} + bool postBuild() override; +private: + void onMessageTextClick(); + void onPanelClick(S32 x, S32 y, MASK mask); + + LLUICtrl* mMessageText; +}; + +#endif /* LL_PANELTOASTTIP_H */ diff --git a/indra/newview/llpaneltopinfobar.cpp b/indra/newview/llpaneltopinfobar.cpp index 30ef3c09ed..e7ac11d570 100644 --- a/indra/newview/llpaneltopinfobar.cpp +++ b/indra/newview/llpaneltopinfobar.cpp @@ -1,480 +1,480 @@ -/** - * @file llpaneltopinfobar.cpp - * @brief Coordinates and Parcel Settings information panel definition - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpaneltopinfobar.h" - -#include "llagent.h" -#include "llagentui.h" -#include "llclipboard.h" -#include "llfloaterreg.h" -#include "llfloatersidepanelcontainer.h" -#include "lllandmarkactions.h" -#include "lllocationinputctrl.h" -#include "llnotificationsutil.h" -#include "llparcel.h" -#include "llslurl.h" -#include "llstatusbar.h" -#include "lltrans.h" -#include "llviewercontrol.h" -#include "llviewerinventory.h" -#include "llviewermenu.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" - -class LLPanelTopInfoBar::LLParcelChangeObserver : public LLParcelObserver -{ -public: - LLParcelChangeObserver(LLPanelTopInfoBar* topInfoBar) : mTopInfoBar(topInfoBar) {} - -private: - /*virtual*/ void changed() - { - if (mTopInfoBar) - { - mTopInfoBar->updateParcelIcons(); - } - } - - LLPanelTopInfoBar* mTopInfoBar; -}; - -LLPanelTopInfoBar::LLPanelTopInfoBar(): mParcelChangedObserver(0) -{ - buildFromFile( "panel_topinfo_bar.xml"); -} - -LLPanelTopInfoBar::~LLPanelTopInfoBar() -{ - if (mParcelChangedObserver) - { - LLViewerParcelMgr::getInstance()->removeObserver(mParcelChangedObserver); - delete mParcelChangedObserver; - } - - if (mParcelPropsCtrlConnection.connected()) - { - mParcelPropsCtrlConnection.disconnect(); - } - - if (mParcelMgrConnection.connected()) - { - mParcelMgrConnection.disconnect(); - } - - if (mShowCoordsCtrlConnection.connected()) - { - mShowCoordsCtrlConnection.disconnect(); - } -} - -void LLPanelTopInfoBar::initParcelIcons() -{ - mParcelIcon[VOICE_ICON] = getChild("voice_icon"); - mParcelIcon[FLY_ICON] = getChild("fly_icon"); - mParcelIcon[PUSH_ICON] = getChild("push_icon"); - mParcelIcon[BUILD_ICON] = getChild("build_icon"); - mParcelIcon[SCRIPTS_ICON] = getChild("scripts_icon"); - mParcelIcon[DAMAGE_ICON] = getChild("damage_icon"); - mParcelIcon[SEE_AVATARS_ICON] = getChild("see_avatars_icon"); - - mParcelIcon[VOICE_ICON]->setToolTip(LLTrans::getString("LocationCtrlVoiceTooltip")); - mParcelIcon[FLY_ICON]->setToolTip(LLTrans::getString("LocationCtrlFlyTooltip")); - mParcelIcon[PUSH_ICON]->setToolTip(LLTrans::getString("LocationCtrlPushTooltip")); - mParcelIcon[BUILD_ICON]->setToolTip(LLTrans::getString("LocationCtrlBuildTooltip")); - mParcelIcon[SCRIPTS_ICON]->setToolTip(LLTrans::getString("LocationCtrlScriptsTooltip")); - mParcelIcon[DAMAGE_ICON]->setToolTip(LLTrans::getString("LocationCtrlDamageTooltip")); - mParcelIcon[SEE_AVATARS_ICON]->setToolTip(LLTrans::getString("LocationCtrlSeeAVsTooltip")); - - mParcelIcon[VOICE_ICON]->setMouseDownCallback(boost::bind(&LLPanelTopInfoBar::onParcelIconClick, this, VOICE_ICON)); - mParcelIcon[FLY_ICON]->setMouseDownCallback(boost::bind(&LLPanelTopInfoBar::onParcelIconClick, this, FLY_ICON)); - mParcelIcon[PUSH_ICON]->setMouseDownCallback(boost::bind(&LLPanelTopInfoBar::onParcelIconClick, this, PUSH_ICON)); - mParcelIcon[BUILD_ICON]->setMouseDownCallback(boost::bind(&LLPanelTopInfoBar::onParcelIconClick, this, BUILD_ICON)); - mParcelIcon[SCRIPTS_ICON]->setMouseDownCallback(boost::bind(&LLPanelTopInfoBar::onParcelIconClick, this, SCRIPTS_ICON)); - mParcelIcon[DAMAGE_ICON]->setMouseDownCallback(boost::bind(&LLPanelTopInfoBar::onParcelIconClick, this, DAMAGE_ICON)); - mParcelIcon[SEE_AVATARS_ICON]->setMouseDownCallback(boost::bind(&LLPanelTopInfoBar::onParcelIconClick, this, SEE_AVATARS_ICON)); - - mDamageText->setText(LLStringExplicit("100%")); -} - -void LLPanelTopInfoBar::handleLoginComplete() -{ - // An agent parcel update hasn't occurred yet, so - // we have to manually set location and the icons. - update(); -} - -bool LLPanelTopInfoBar::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - if(!LLUICtrl::CommitCallbackRegistry::getValue("TopInfoBar.Action")) - { - LLUICtrl::CommitCallbackRegistry::currentRegistrar() - .add("TopInfoBar.Action", boost::bind(&LLPanelTopInfoBar::onContextMenuItemClicked, this, _2)); - } - show_topinfobar_context_menu(this, x, y); - return true; -} - -bool LLPanelTopInfoBar::postBuild() -{ - mInfoBtn = getChild("place_info_btn"); - mInfoBtn->setClickedCallback(boost::bind(&LLPanelTopInfoBar::onInfoButtonClicked, this)); - mInfoBtn->setToolTip(LLTrans::getString("LocationCtrlInfoBtnTooltip")); - - mParcelInfoText = getChild("parcel_info_text"); - mDamageText = getChild("damage_text"); - - initParcelIcons(); - - mParcelChangedObserver = new LLParcelChangeObserver(this); - LLViewerParcelMgr::getInstance()->addObserver(mParcelChangedObserver); - - // Connecting signal for updating parcel icons on "Show Parcel Properties" setting change. - LLControlVariable* ctrl = gSavedSettings.getControl("NavBarShowParcelProperties").get(); - if (ctrl) - { - mParcelPropsCtrlConnection = ctrl->getSignal()->connect(boost::bind(&LLPanelTopInfoBar::updateParcelIcons, this)); - } - - // Connecting signal for updating parcel text on "Show Coordinates" setting change. - ctrl = gSavedSettings.getControl("NavBarShowCoordinates").get(); - if (ctrl) - { - mShowCoordsCtrlConnection = ctrl->getSignal()->connect(boost::bind(&LLPanelTopInfoBar::onNavBarShowParcelPropertiesCtrlChanged, this)); - } - - mParcelMgrConnection = gAgent.addParcelChangedCallback( - boost::bind(&LLPanelTopInfoBar::onAgentParcelChange, this)); - - setVisibleCallback(boost::bind(&LLPanelTopInfoBar::onVisibilityChanged, this, _2)); - - return true; -} - -void LLPanelTopInfoBar::onNavBarShowParcelPropertiesCtrlChanged() -{ - std::string new_text; - - // don't need to have separate show_coords variable; if user requested the coords to be shown - // they will be added during the next call to the draw() method. - buildLocationString(new_text, false); - setParcelInfoText(new_text); -} - -// when panel is shown, all minimized floaters should be shifted downwards to prevent overlapping of -// PanelTopInfoBar. See EXT-7951. -void LLPanelTopInfoBar::onVisibilityChanged(const LLSD& show) -{ - // this height is used as a vertical offset for ALREADY MINIMIZED floaters - // when PanelTopInfoBar visibility changes - S32 height = getRect().getHeight(); - - // this vertical offset is used for a start minimize position of floaters that - // are NOT MIMIMIZED YET - S32 minimize_pos_offset = 0; - - if (show.asBoolean()) - { - height = minimize_pos_offset = -height; - } - - gFloaterView->shiftFloaters(0, height); - gFloaterView->setMinimizePositionVerticalOffset(minimize_pos_offset); -} - -boost::signals2::connection LLPanelTopInfoBar::setResizeCallback( const resize_signal_t::slot_type& cb ) -{ - return mResizeSignal.connect(cb); -} - -void LLPanelTopInfoBar::draw() -{ - updateParcelInfoText(); - updateHealth(); - - LLPanel::draw(); -} - -void LLPanelTopInfoBar::buildLocationString(std::string& loc_str, bool show_coords) -{ - LLAgentUI::ELocationFormat format = - (show_coords ? LLAgentUI::LOCATION_FORMAT_FULL : LLAgentUI::LOCATION_FORMAT_NO_COORDS); - - if (!LLAgentUI::buildLocationString(loc_str, format)) - { - loc_str = "???"; - } -} - -void LLPanelTopInfoBar::setParcelInfoText(const std::string& new_text) -{ - LLRect old_rect = getRect(); - const LLFontGL* font = mParcelInfoText->getFont(); - S32 new_text_width = font->getWidth(new_text); - - mParcelInfoText->setText(new_text); - - LLRect rect = mParcelInfoText->getRect(); - rect.setOriginAndSize(rect.mLeft, rect.mBottom, new_text_width, rect.getHeight()); - - mParcelInfoText->reshape(rect.getWidth(), rect.getHeight(), true); - mParcelInfoText->setRect(rect); - layoutParcelIcons(); - - if (old_rect != getRect()) - { - mResizeSignal(); - } -} - -void LLPanelTopInfoBar::update() -{ - std::string new_text; - - // don't need to have separate show_coords variable; if user requested the coords to be shown - // they will be added during the next call to the draw() method. - buildLocationString(new_text, false); - setParcelInfoText(new_text); - - updateParcelIcons(); -} - -void LLPanelTopInfoBar::updateParcelInfoText() -{ - static LLUICachedControl show_coords("NavBarShowCoordinates", false); - - if (show_coords) - { - std::string new_text; - - buildLocationString(new_text, show_coords); - setParcelInfoText(new_text); - } -} - -void LLPanelTopInfoBar::updateParcelIcons() -{ - LLViewerParcelMgr* vpm = LLViewerParcelMgr::getInstance(); - - LLViewerRegion* agent_region = gAgent.getRegion(); - LLParcel* agent_parcel = vpm->getAgentParcel(); - if (!agent_region || !agent_parcel) - return; - - if (gSavedSettings.getBOOL("NavBarShowParcelProperties")) - { - LLParcel* current_parcel; - LLViewerRegion* selection_region = vpm->getSelectionRegion(); - LLParcel* selected_parcel = vpm->getParcelSelection()->getParcel(); - - // If agent is in selected parcel we use its properties because - // they are updated more often by LLViewerParcelMgr than agent parcel properties. - // See LLViewerParcelMgr::processParcelProperties(). - // This is needed to reflect parcel restrictions changes without having to leave - // the parcel and then enter it again. See EXT-2987 - if (selected_parcel && selected_parcel->getLocalID() == agent_parcel->getLocalID() - && selection_region == agent_region) - { - current_parcel = selected_parcel; - } - else - { - current_parcel = agent_parcel; - } - - bool allow_voice = vpm->allowAgentVoice(agent_region, current_parcel); - bool allow_fly = vpm->allowAgentFly(agent_region, current_parcel); - bool allow_push = vpm->allowAgentPush(agent_region, current_parcel); - bool allow_build = vpm->allowAgentBuild(current_parcel); // true when anyone is allowed to build. See EXT-4610. - bool allow_scripts = vpm->allowAgentScripts(agent_region, current_parcel); - bool allow_damage = vpm->allowAgentDamage(agent_region, current_parcel); - bool see_avs = current_parcel->getSeeAVs(); - - // Most icons are "block this ability" - mParcelIcon[VOICE_ICON]->setVisible( !allow_voice ); - mParcelIcon[FLY_ICON]->setVisible( !allow_fly ); - mParcelIcon[PUSH_ICON]->setVisible( !allow_push ); - mParcelIcon[BUILD_ICON]->setVisible( !allow_build ); - mParcelIcon[SCRIPTS_ICON]->setVisible( !allow_scripts ); - mParcelIcon[DAMAGE_ICON]->setVisible( allow_damage ); - mDamageText->setVisible(allow_damage); - mParcelIcon[SEE_AVATARS_ICON]->setVisible( !see_avs ); - - layoutParcelIcons(); - } - else - { - for (S32 i = 0; i < ICON_COUNT; ++i) - { - mParcelIcon[i]->setVisible(false); - } - mDamageText->setVisible(false); - } -} - -void LLPanelTopInfoBar::updateHealth() -{ - static LLUICachedControl show_icons("NavBarShowParcelProperties", false); - - // *FIXME: Status bar owns health information, should be in agent - if (show_icons && gStatusBar) - { - static S32 last_health = -1; - S32 health = gStatusBar->getHealth(); - if (health != last_health) - { - std::string text = llformat("%d%%", health); - mDamageText->setText(text); - last_health = health; - } - } -} - -void LLPanelTopInfoBar::layoutParcelIcons() -{ - LLRect old_rect = getRect(); - - // TODO: remove hard-coded values and read them as xml parameters - static const int FIRST_ICON_HPAD = 32; - static const int LAST_ICON_HPAD = 11; - - S32 left = mParcelInfoText->getRect().mRight + FIRST_ICON_HPAD; - - left = layoutWidget(mDamageText, left); - - for (int i = ICON_COUNT - 1; i >= 0; --i) - { - left = layoutWidget(mParcelIcon[i], left); - } - - LLRect rect = getRect(); - rect.set(rect.mLeft, rect.mTop, left + LAST_ICON_HPAD, rect.mBottom); - setRect(rect); - - if (old_rect != getRect()) - { - mResizeSignal(); - } -} - -S32 LLPanelTopInfoBar::layoutWidget(LLUICtrl* ctrl, S32 left) -{ - // TODO: remove hard-coded values and read them as xml parameters - static const int ICON_HPAD = 2; - - if (ctrl->getVisible()) - { - LLRect rect = ctrl->getRect(); - rect.mRight = left + rect.getWidth(); - rect.mLeft = left; - - ctrl->setRect(rect); - left += rect.getWidth() + ICON_HPAD; - } - - return left; -} - -void LLPanelTopInfoBar::onParcelIconClick(EParcelIcon icon) -{ - switch (icon) - { - case VOICE_ICON: - LLNotificationsUtil::add("NoVoice"); - break; - case FLY_ICON: - LLNotificationsUtil::add("NoFly"); - break; - case PUSH_ICON: - LLNotificationsUtil::add("PushRestricted"); - break; - case BUILD_ICON: - LLNotificationsUtil::add("NoBuild"); - break; - case SCRIPTS_ICON: - { - LLViewerRegion* region = gAgent.getRegion(); - if(region && region->getRegionFlag(REGION_FLAGS_ESTATE_SKIP_SCRIPTS)) - { - LLNotificationsUtil::add("ScriptsStopped"); - } - else if(region && region->getRegionFlag(REGION_FLAGS_SKIP_SCRIPTS)) - { - LLNotificationsUtil::add("ScriptsNotRunning"); - } - else - { - LLNotificationsUtil::add("NoOutsideScripts"); - } - break; - } - case DAMAGE_ICON: - LLNotificationsUtil::add("NotSafe"); - break; - case SEE_AVATARS_ICON: - LLNotificationsUtil::add("SeeAvatars"); - break; - case ICON_COUNT: - break; - // no default to get compiler warning when a new icon gets added - } -} - -void LLPanelTopInfoBar::onAgentParcelChange() -{ - update(); -} - -void LLPanelTopInfoBar::onContextMenuItemClicked(const LLSD::String& item) -{ - if (item == "landmark") - { - LLViewerInventoryItem* landmark = LLLandmarkActions::findLandmarkForAgentPos(); - - if(landmark == NULL) - { - LLFloaterReg::showInstance("add_landmark"); - } - else - { - LLFloaterSidePanelContainer::showPanel("places", LLSD().with("type", "landmark").with("id",landmark->getUUID())); - } - } - else if (item == "copy") - { - LLSLURL slurl; - LLAgentUI::buildSLURL(slurl, false); - LLUIString location_str(slurl.getSLURLString()); - - LLClipboard::instance().copyToClipboard(location_str,0,location_str.length()); - } -} - -void LLPanelTopInfoBar::onInfoButtonClicked() -{ - LLFloaterSidePanelContainer::showPanel("places", LLSD().with("type", "agent")); -} +/** + * @file llpaneltopinfobar.cpp + * @brief Coordinates and Parcel Settings information panel definition + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpaneltopinfobar.h" + +#include "llagent.h" +#include "llagentui.h" +#include "llclipboard.h" +#include "llfloaterreg.h" +#include "llfloatersidepanelcontainer.h" +#include "lllandmarkactions.h" +#include "lllocationinputctrl.h" +#include "llnotificationsutil.h" +#include "llparcel.h" +#include "llslurl.h" +#include "llstatusbar.h" +#include "lltrans.h" +#include "llviewercontrol.h" +#include "llviewerinventory.h" +#include "llviewermenu.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" + +class LLPanelTopInfoBar::LLParcelChangeObserver : public LLParcelObserver +{ +public: + LLParcelChangeObserver(LLPanelTopInfoBar* topInfoBar) : mTopInfoBar(topInfoBar) {} + +private: + /*virtual*/ void changed() + { + if (mTopInfoBar) + { + mTopInfoBar->updateParcelIcons(); + } + } + + LLPanelTopInfoBar* mTopInfoBar; +}; + +LLPanelTopInfoBar::LLPanelTopInfoBar(): mParcelChangedObserver(0) +{ + buildFromFile( "panel_topinfo_bar.xml"); +} + +LLPanelTopInfoBar::~LLPanelTopInfoBar() +{ + if (mParcelChangedObserver) + { + LLViewerParcelMgr::getInstance()->removeObserver(mParcelChangedObserver); + delete mParcelChangedObserver; + } + + if (mParcelPropsCtrlConnection.connected()) + { + mParcelPropsCtrlConnection.disconnect(); + } + + if (mParcelMgrConnection.connected()) + { + mParcelMgrConnection.disconnect(); + } + + if (mShowCoordsCtrlConnection.connected()) + { + mShowCoordsCtrlConnection.disconnect(); + } +} + +void LLPanelTopInfoBar::initParcelIcons() +{ + mParcelIcon[VOICE_ICON] = getChild("voice_icon"); + mParcelIcon[FLY_ICON] = getChild("fly_icon"); + mParcelIcon[PUSH_ICON] = getChild("push_icon"); + mParcelIcon[BUILD_ICON] = getChild("build_icon"); + mParcelIcon[SCRIPTS_ICON] = getChild("scripts_icon"); + mParcelIcon[DAMAGE_ICON] = getChild("damage_icon"); + mParcelIcon[SEE_AVATARS_ICON] = getChild("see_avatars_icon"); + + mParcelIcon[VOICE_ICON]->setToolTip(LLTrans::getString("LocationCtrlVoiceTooltip")); + mParcelIcon[FLY_ICON]->setToolTip(LLTrans::getString("LocationCtrlFlyTooltip")); + mParcelIcon[PUSH_ICON]->setToolTip(LLTrans::getString("LocationCtrlPushTooltip")); + mParcelIcon[BUILD_ICON]->setToolTip(LLTrans::getString("LocationCtrlBuildTooltip")); + mParcelIcon[SCRIPTS_ICON]->setToolTip(LLTrans::getString("LocationCtrlScriptsTooltip")); + mParcelIcon[DAMAGE_ICON]->setToolTip(LLTrans::getString("LocationCtrlDamageTooltip")); + mParcelIcon[SEE_AVATARS_ICON]->setToolTip(LLTrans::getString("LocationCtrlSeeAVsTooltip")); + + mParcelIcon[VOICE_ICON]->setMouseDownCallback(boost::bind(&LLPanelTopInfoBar::onParcelIconClick, this, VOICE_ICON)); + mParcelIcon[FLY_ICON]->setMouseDownCallback(boost::bind(&LLPanelTopInfoBar::onParcelIconClick, this, FLY_ICON)); + mParcelIcon[PUSH_ICON]->setMouseDownCallback(boost::bind(&LLPanelTopInfoBar::onParcelIconClick, this, PUSH_ICON)); + mParcelIcon[BUILD_ICON]->setMouseDownCallback(boost::bind(&LLPanelTopInfoBar::onParcelIconClick, this, BUILD_ICON)); + mParcelIcon[SCRIPTS_ICON]->setMouseDownCallback(boost::bind(&LLPanelTopInfoBar::onParcelIconClick, this, SCRIPTS_ICON)); + mParcelIcon[DAMAGE_ICON]->setMouseDownCallback(boost::bind(&LLPanelTopInfoBar::onParcelIconClick, this, DAMAGE_ICON)); + mParcelIcon[SEE_AVATARS_ICON]->setMouseDownCallback(boost::bind(&LLPanelTopInfoBar::onParcelIconClick, this, SEE_AVATARS_ICON)); + + mDamageText->setText(LLStringExplicit("100%")); +} + +void LLPanelTopInfoBar::handleLoginComplete() +{ + // An agent parcel update hasn't occurred yet, so + // we have to manually set location and the icons. + update(); +} + +bool LLPanelTopInfoBar::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + if(!LLUICtrl::CommitCallbackRegistry::getValue("TopInfoBar.Action")) + { + LLUICtrl::CommitCallbackRegistry::currentRegistrar() + .add("TopInfoBar.Action", boost::bind(&LLPanelTopInfoBar::onContextMenuItemClicked, this, _2)); + } + show_topinfobar_context_menu(this, x, y); + return true; +} + +bool LLPanelTopInfoBar::postBuild() +{ + mInfoBtn = getChild("place_info_btn"); + mInfoBtn->setClickedCallback(boost::bind(&LLPanelTopInfoBar::onInfoButtonClicked, this)); + mInfoBtn->setToolTip(LLTrans::getString("LocationCtrlInfoBtnTooltip")); + + mParcelInfoText = getChild("parcel_info_text"); + mDamageText = getChild("damage_text"); + + initParcelIcons(); + + mParcelChangedObserver = new LLParcelChangeObserver(this); + LLViewerParcelMgr::getInstance()->addObserver(mParcelChangedObserver); + + // Connecting signal for updating parcel icons on "Show Parcel Properties" setting change. + LLControlVariable* ctrl = gSavedSettings.getControl("NavBarShowParcelProperties").get(); + if (ctrl) + { + mParcelPropsCtrlConnection = ctrl->getSignal()->connect(boost::bind(&LLPanelTopInfoBar::updateParcelIcons, this)); + } + + // Connecting signal for updating parcel text on "Show Coordinates" setting change. + ctrl = gSavedSettings.getControl("NavBarShowCoordinates").get(); + if (ctrl) + { + mShowCoordsCtrlConnection = ctrl->getSignal()->connect(boost::bind(&LLPanelTopInfoBar::onNavBarShowParcelPropertiesCtrlChanged, this)); + } + + mParcelMgrConnection = gAgent.addParcelChangedCallback( + boost::bind(&LLPanelTopInfoBar::onAgentParcelChange, this)); + + setVisibleCallback(boost::bind(&LLPanelTopInfoBar::onVisibilityChanged, this, _2)); + + return true; +} + +void LLPanelTopInfoBar::onNavBarShowParcelPropertiesCtrlChanged() +{ + std::string new_text; + + // don't need to have separate show_coords variable; if user requested the coords to be shown + // they will be added during the next call to the draw() method. + buildLocationString(new_text, false); + setParcelInfoText(new_text); +} + +// when panel is shown, all minimized floaters should be shifted downwards to prevent overlapping of +// PanelTopInfoBar. See EXT-7951. +void LLPanelTopInfoBar::onVisibilityChanged(const LLSD& show) +{ + // this height is used as a vertical offset for ALREADY MINIMIZED floaters + // when PanelTopInfoBar visibility changes + S32 height = getRect().getHeight(); + + // this vertical offset is used for a start minimize position of floaters that + // are NOT MIMIMIZED YET + S32 minimize_pos_offset = 0; + + if (show.asBoolean()) + { + height = minimize_pos_offset = -height; + } + + gFloaterView->shiftFloaters(0, height); + gFloaterView->setMinimizePositionVerticalOffset(minimize_pos_offset); +} + +boost::signals2::connection LLPanelTopInfoBar::setResizeCallback( const resize_signal_t::slot_type& cb ) +{ + return mResizeSignal.connect(cb); +} + +void LLPanelTopInfoBar::draw() +{ + updateParcelInfoText(); + updateHealth(); + + LLPanel::draw(); +} + +void LLPanelTopInfoBar::buildLocationString(std::string& loc_str, bool show_coords) +{ + LLAgentUI::ELocationFormat format = + (show_coords ? LLAgentUI::LOCATION_FORMAT_FULL : LLAgentUI::LOCATION_FORMAT_NO_COORDS); + + if (!LLAgentUI::buildLocationString(loc_str, format)) + { + loc_str = "???"; + } +} + +void LLPanelTopInfoBar::setParcelInfoText(const std::string& new_text) +{ + LLRect old_rect = getRect(); + const LLFontGL* font = mParcelInfoText->getFont(); + S32 new_text_width = font->getWidth(new_text); + + mParcelInfoText->setText(new_text); + + LLRect rect = mParcelInfoText->getRect(); + rect.setOriginAndSize(rect.mLeft, rect.mBottom, new_text_width, rect.getHeight()); + + mParcelInfoText->reshape(rect.getWidth(), rect.getHeight(), true); + mParcelInfoText->setRect(rect); + layoutParcelIcons(); + + if (old_rect != getRect()) + { + mResizeSignal(); + } +} + +void LLPanelTopInfoBar::update() +{ + std::string new_text; + + // don't need to have separate show_coords variable; if user requested the coords to be shown + // they will be added during the next call to the draw() method. + buildLocationString(new_text, false); + setParcelInfoText(new_text); + + updateParcelIcons(); +} + +void LLPanelTopInfoBar::updateParcelInfoText() +{ + static LLUICachedControl show_coords("NavBarShowCoordinates", false); + + if (show_coords) + { + std::string new_text; + + buildLocationString(new_text, show_coords); + setParcelInfoText(new_text); + } +} + +void LLPanelTopInfoBar::updateParcelIcons() +{ + LLViewerParcelMgr* vpm = LLViewerParcelMgr::getInstance(); + + LLViewerRegion* agent_region = gAgent.getRegion(); + LLParcel* agent_parcel = vpm->getAgentParcel(); + if (!agent_region || !agent_parcel) + return; + + if (gSavedSettings.getBOOL("NavBarShowParcelProperties")) + { + LLParcel* current_parcel; + LLViewerRegion* selection_region = vpm->getSelectionRegion(); + LLParcel* selected_parcel = vpm->getParcelSelection()->getParcel(); + + // If agent is in selected parcel we use its properties because + // they are updated more often by LLViewerParcelMgr than agent parcel properties. + // See LLViewerParcelMgr::processParcelProperties(). + // This is needed to reflect parcel restrictions changes without having to leave + // the parcel and then enter it again. See EXT-2987 + if (selected_parcel && selected_parcel->getLocalID() == agent_parcel->getLocalID() + && selection_region == agent_region) + { + current_parcel = selected_parcel; + } + else + { + current_parcel = agent_parcel; + } + + bool allow_voice = vpm->allowAgentVoice(agent_region, current_parcel); + bool allow_fly = vpm->allowAgentFly(agent_region, current_parcel); + bool allow_push = vpm->allowAgentPush(agent_region, current_parcel); + bool allow_build = vpm->allowAgentBuild(current_parcel); // true when anyone is allowed to build. See EXT-4610. + bool allow_scripts = vpm->allowAgentScripts(agent_region, current_parcel); + bool allow_damage = vpm->allowAgentDamage(agent_region, current_parcel); + bool see_avs = current_parcel->getSeeAVs(); + + // Most icons are "block this ability" + mParcelIcon[VOICE_ICON]->setVisible( !allow_voice ); + mParcelIcon[FLY_ICON]->setVisible( !allow_fly ); + mParcelIcon[PUSH_ICON]->setVisible( !allow_push ); + mParcelIcon[BUILD_ICON]->setVisible( !allow_build ); + mParcelIcon[SCRIPTS_ICON]->setVisible( !allow_scripts ); + mParcelIcon[DAMAGE_ICON]->setVisible( allow_damage ); + mDamageText->setVisible(allow_damage); + mParcelIcon[SEE_AVATARS_ICON]->setVisible( !see_avs ); + + layoutParcelIcons(); + } + else + { + for (S32 i = 0; i < ICON_COUNT; ++i) + { + mParcelIcon[i]->setVisible(false); + } + mDamageText->setVisible(false); + } +} + +void LLPanelTopInfoBar::updateHealth() +{ + static LLUICachedControl show_icons("NavBarShowParcelProperties", false); + + // *FIXME: Status bar owns health information, should be in agent + if (show_icons && gStatusBar) + { + static S32 last_health = -1; + S32 health = gStatusBar->getHealth(); + if (health != last_health) + { + std::string text = llformat("%d%%", health); + mDamageText->setText(text); + last_health = health; + } + } +} + +void LLPanelTopInfoBar::layoutParcelIcons() +{ + LLRect old_rect = getRect(); + + // TODO: remove hard-coded values and read them as xml parameters + static const int FIRST_ICON_HPAD = 32; + static const int LAST_ICON_HPAD = 11; + + S32 left = mParcelInfoText->getRect().mRight + FIRST_ICON_HPAD; + + left = layoutWidget(mDamageText, left); + + for (int i = ICON_COUNT - 1; i >= 0; --i) + { + left = layoutWidget(mParcelIcon[i], left); + } + + LLRect rect = getRect(); + rect.set(rect.mLeft, rect.mTop, left + LAST_ICON_HPAD, rect.mBottom); + setRect(rect); + + if (old_rect != getRect()) + { + mResizeSignal(); + } +} + +S32 LLPanelTopInfoBar::layoutWidget(LLUICtrl* ctrl, S32 left) +{ + // TODO: remove hard-coded values and read them as xml parameters + static const int ICON_HPAD = 2; + + if (ctrl->getVisible()) + { + LLRect rect = ctrl->getRect(); + rect.mRight = left + rect.getWidth(); + rect.mLeft = left; + + ctrl->setRect(rect); + left += rect.getWidth() + ICON_HPAD; + } + + return left; +} + +void LLPanelTopInfoBar::onParcelIconClick(EParcelIcon icon) +{ + switch (icon) + { + case VOICE_ICON: + LLNotificationsUtil::add("NoVoice"); + break; + case FLY_ICON: + LLNotificationsUtil::add("NoFly"); + break; + case PUSH_ICON: + LLNotificationsUtil::add("PushRestricted"); + break; + case BUILD_ICON: + LLNotificationsUtil::add("NoBuild"); + break; + case SCRIPTS_ICON: + { + LLViewerRegion* region = gAgent.getRegion(); + if(region && region->getRegionFlag(REGION_FLAGS_ESTATE_SKIP_SCRIPTS)) + { + LLNotificationsUtil::add("ScriptsStopped"); + } + else if(region && region->getRegionFlag(REGION_FLAGS_SKIP_SCRIPTS)) + { + LLNotificationsUtil::add("ScriptsNotRunning"); + } + else + { + LLNotificationsUtil::add("NoOutsideScripts"); + } + break; + } + case DAMAGE_ICON: + LLNotificationsUtil::add("NotSafe"); + break; + case SEE_AVATARS_ICON: + LLNotificationsUtil::add("SeeAvatars"); + break; + case ICON_COUNT: + break; + // no default to get compiler warning when a new icon gets added + } +} + +void LLPanelTopInfoBar::onAgentParcelChange() +{ + update(); +} + +void LLPanelTopInfoBar::onContextMenuItemClicked(const LLSD::String& item) +{ + if (item == "landmark") + { + LLViewerInventoryItem* landmark = LLLandmarkActions::findLandmarkForAgentPos(); + + if(landmark == NULL) + { + LLFloaterReg::showInstance("add_landmark"); + } + else + { + LLFloaterSidePanelContainer::showPanel("places", LLSD().with("type", "landmark").with("id",landmark->getUUID())); + } + } + else if (item == "copy") + { + LLSLURL slurl; + LLAgentUI::buildSLURL(slurl, false); + LLUIString location_str(slurl.getSLURLString()); + + LLClipboard::instance().copyToClipboard(location_str,0,location_str.length()); + } +} + +void LLPanelTopInfoBar::onInfoButtonClicked() +{ + LLFloaterSidePanelContainer::showPanel("places", LLSD().with("type", "agent")); +} diff --git a/indra/newview/llpaneltopinfobar.h b/indra/newview/llpaneltopinfobar.h index 3d7b584c10..f4238ad5e7 100644 --- a/indra/newview/llpaneltopinfobar.h +++ b/indra/newview/llpaneltopinfobar.h @@ -1,178 +1,178 @@ -/** - * @file llpaneltopinfobar.h - * @brief Coordinates and Parcel Settings information panel definition - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLPANELTOPINFOBAR_H_ -#define LLPANELTOPINFOBAR_H_ - -#include "llpanel.h" -#include "llinitdestroyclass.h" - -class LLButton; -class LLTextBox; -class LLIconCtrl; -class LLParcelChangeObserver; - -class LLPanelTopInfoBar : public LLPanel, public LLSingleton, private LLDestroyClass -{ - LLSINGLETON(LLPanelTopInfoBar); - ~LLPanelTopInfoBar(); - LOG_CLASS(LLPanelTopInfoBar); - - friend class LLDestroyClass; - -public: - typedef boost::signals2::signal resize_signal_t; - - bool postBuild() override; - void draw() override; - - /** - * Updates location and parcel icons on login complete - */ - void handleLoginComplete(); - - /** - * Called when the top info bar gets shown or hidden - */ - void onVisibilityChanged(const LLSD& show); - - boost::signals2::connection setResizeCallback( const resize_signal_t::slot_type& cb ); - -private: - class LLParcelChangeObserver; - - friend class LLParcelChangeObserver; - - enum EParcelIcon - { - VOICE_ICON = 0, - FLY_ICON, // 1 - PUSH_ICON, // 2 - BUILD_ICON, // 3 - SCRIPTS_ICON, // 4 - DAMAGE_ICON, // 5 - SEE_AVATARS_ICON, // 6 - ICON_COUNT // 7 total - }; - - /** - * Initializes parcel icons controls. Called from the constructor. - */ - void initParcelIcons(); - - bool handleRightMouseDown(S32 x, S32 y, MASK mask) override; - - /** - * Handles clicks on the parcel icons. - */ - void onParcelIconClick(EParcelIcon icon); - - /** - * Handles clicks on the info buttons. - */ - void onInfoButtonClicked(); - - /** - * Called when agent changes the parcel. - */ - void onAgentParcelChange(); - - /** - * Called when context menu item is clicked. - */ - void onContextMenuItemClicked(const LLSD::String& userdata); - - /** - * Called when user checks/unchecks Show Coordinates menu item. - */ - void onNavBarShowParcelPropertiesCtrlChanged(); - - /** - * Shorthand to call updateParcelInfoText() and updateParcelIcons(). - */ - void update(); - - /** - * Updates parcel info text (mParcelInfoText). - */ - void updateParcelInfoText(); - - /** - * Updates parcel icons (mParcelIcon[]). - */ - void updateParcelIcons(); - - /** - * Updates health information (mDamageText). - */ - void updateHealth(); - - /** - * Lays out all parcel icons starting from right edge of the mParcelInfoText + 11px - * (see screenshots in EXT-5808 for details). - */ - void layoutParcelIcons(); - - /** - * Lays out a widget. Widget's rect mLeft becomes equal to the 'left' argument. - */ - S32 layoutWidget(LLUICtrl* ctrl, S32 left); - - /** - * Generates location string and returns it in the loc_str parameter. - */ - void buildLocationString(std::string& loc_str, bool show_coords); - - /** - * Sets new value to the mParcelInfoText and updates the size of the top bar. - */ - void setParcelInfoText(const std::string& new_text); - - /** - * Implementation of LLDestroyClass - */ - static void destroyClass() - { - if (LLPanelTopInfoBar::instanceExists()) - { - LLPanelTopInfoBar::getInstance()->setEnabled(false); - } - } - - LLButton* mInfoBtn; - LLTextBox* mParcelInfoText; - LLTextBox* mDamageText; - LLIconCtrl* mParcelIcon[ICON_COUNT]; - LLParcelChangeObserver* mParcelChangedObserver; - - boost::signals2::connection mParcelPropsCtrlConnection; - boost::signals2::connection mShowCoordsCtrlConnection; - boost::signals2::connection mParcelMgrConnection; - - resize_signal_t mResizeSignal; -}; - -#endif /* LLPANELTOPINFOBAR_H_ */ +/** + * @file llpaneltopinfobar.h + * @brief Coordinates and Parcel Settings information panel definition + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLPANELTOPINFOBAR_H_ +#define LLPANELTOPINFOBAR_H_ + +#include "llpanel.h" +#include "llinitdestroyclass.h" + +class LLButton; +class LLTextBox; +class LLIconCtrl; +class LLParcelChangeObserver; + +class LLPanelTopInfoBar : public LLPanel, public LLSingleton, private LLDestroyClass +{ + LLSINGLETON(LLPanelTopInfoBar); + ~LLPanelTopInfoBar(); + LOG_CLASS(LLPanelTopInfoBar); + + friend class LLDestroyClass; + +public: + typedef boost::signals2::signal resize_signal_t; + + bool postBuild() override; + void draw() override; + + /** + * Updates location and parcel icons on login complete + */ + void handleLoginComplete(); + + /** + * Called when the top info bar gets shown or hidden + */ + void onVisibilityChanged(const LLSD& show); + + boost::signals2::connection setResizeCallback( const resize_signal_t::slot_type& cb ); + +private: + class LLParcelChangeObserver; + + friend class LLParcelChangeObserver; + + enum EParcelIcon + { + VOICE_ICON = 0, + FLY_ICON, // 1 + PUSH_ICON, // 2 + BUILD_ICON, // 3 + SCRIPTS_ICON, // 4 + DAMAGE_ICON, // 5 + SEE_AVATARS_ICON, // 6 + ICON_COUNT // 7 total + }; + + /** + * Initializes parcel icons controls. Called from the constructor. + */ + void initParcelIcons(); + + bool handleRightMouseDown(S32 x, S32 y, MASK mask) override; + + /** + * Handles clicks on the parcel icons. + */ + void onParcelIconClick(EParcelIcon icon); + + /** + * Handles clicks on the info buttons. + */ + void onInfoButtonClicked(); + + /** + * Called when agent changes the parcel. + */ + void onAgentParcelChange(); + + /** + * Called when context menu item is clicked. + */ + void onContextMenuItemClicked(const LLSD::String& userdata); + + /** + * Called when user checks/unchecks Show Coordinates menu item. + */ + void onNavBarShowParcelPropertiesCtrlChanged(); + + /** + * Shorthand to call updateParcelInfoText() and updateParcelIcons(). + */ + void update(); + + /** + * Updates parcel info text (mParcelInfoText). + */ + void updateParcelInfoText(); + + /** + * Updates parcel icons (mParcelIcon[]). + */ + void updateParcelIcons(); + + /** + * Updates health information (mDamageText). + */ + void updateHealth(); + + /** + * Lays out all parcel icons starting from right edge of the mParcelInfoText + 11px + * (see screenshots in EXT-5808 for details). + */ + void layoutParcelIcons(); + + /** + * Lays out a widget. Widget's rect mLeft becomes equal to the 'left' argument. + */ + S32 layoutWidget(LLUICtrl* ctrl, S32 left); + + /** + * Generates location string and returns it in the loc_str parameter. + */ + void buildLocationString(std::string& loc_str, bool show_coords); + + /** + * Sets new value to the mParcelInfoText and updates the size of the top bar. + */ + void setParcelInfoText(const std::string& new_text); + + /** + * Implementation of LLDestroyClass + */ + static void destroyClass() + { + if (LLPanelTopInfoBar::instanceExists()) + { + LLPanelTopInfoBar::getInstance()->setEnabled(false); + } + } + + LLButton* mInfoBtn; + LLTextBox* mParcelInfoText; + LLTextBox* mDamageText; + LLIconCtrl* mParcelIcon[ICON_COUNT]; + LLParcelChangeObserver* mParcelChangedObserver; + + boost::signals2::connection mParcelPropsCtrlConnection; + boost::signals2::connection mShowCoordsCtrlConnection; + boost::signals2::connection mParcelMgrConnection; + + resize_signal_t mResizeSignal; +}; + +#endif /* LLPANELTOPINFOBAR_H_ */ diff --git a/indra/newview/llpanelvoicedevicesettings.cpp b/indra/newview/llpanelvoicedevicesettings.cpp index 64f9cf0ab7..9c3891b438 100644 --- a/indra/newview/llpanelvoicedevicesettings.cpp +++ b/indra/newview/llpanelvoicedevicesettings.cpp @@ -1,366 +1,366 @@ -/** - * @file llpanelvoicedevicesettings.cpp - * @author Richard Nelson - * @brief Voice communication set-up - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelvoicedevicesettings.h" - -// Viewer includes -#include "llcombobox.h" -#include "llsliderctrl.h" -#include "llstartup.h" -#include "llviewercontrol.h" -#include "llvoiceclient.h" -#include "llvoicechannel.h" - -// Library includes (after viewer) -#include "lluictrlfactory.h" - -static LLPanelInjector t_panel_group_general("panel_voice_device_settings"); -static const std::string DEFAULT_DEVICE("Default"); - - -LLPanelVoiceDeviceSettings::LLPanelVoiceDeviceSettings() - : LLPanel() -{ - mCtrlInputDevices = NULL; - mCtrlOutputDevices = NULL; - mInputDevice = gSavedSettings.getString("VoiceInputAudioDevice"); - mOutputDevice = gSavedSettings.getString("VoiceOutputAudioDevice"); - mDevicesUpdated = false; //obsolete - mUseTuningMode = true; - - // grab "live" mic volume level - mMicVolume = gSavedSettings.getF32("AudioLevelMic"); - -} - -LLPanelVoiceDeviceSettings::~LLPanelVoiceDeviceSettings() -{ -} - -bool LLPanelVoiceDeviceSettings::postBuild() -{ - LLSlider* volume_slider = getChild("mic_volume_slider"); - // set mic volume tuning slider based on last mic volume setting - volume_slider->setValue(mMicVolume); - - mCtrlInputDevices = getChild("voice_input_device"); - mCtrlOutputDevices = getChild("voice_output_device"); - mUnmuteBtn = getChild("unmute_btn"); - - mCtrlInputDevices->setCommitCallback( - boost::bind(&LLPanelVoiceDeviceSettings::onCommitInputDevice, this)); - mCtrlOutputDevices->setCommitCallback( - boost::bind(&LLPanelVoiceDeviceSettings::onCommitOutputDevice, this)); - mUnmuteBtn->setCommitCallback( - boost::bind(&LLPanelVoiceDeviceSettings::onCommitUnmute, this)); - - mLocalizedDeviceNames[DEFAULT_DEVICE] = getString("default_text"); - mLocalizedDeviceNames["No Device"] = getString("name_no_device"); - mLocalizedDeviceNames["Default System Device"] = getString("name_default_system_device"); - - mCtrlOutputDevices->setMouseDownCallback(boost::bind(&LLPanelVoiceDeviceSettings::onOutputDevicesClicked, this)); - mCtrlInputDevices->setMouseDownCallback(boost::bind(&LLPanelVoiceDeviceSettings::onInputDevicesClicked, this)); - - - return true; -} - -// virtual -void LLPanelVoiceDeviceSettings::onVisibilityChange ( bool new_visibility ) -{ - if (new_visibility) - { - initialize(); - } - else - { - cleanup(); - // when closing this window, turn of visiblity control so that - // next time preferences is opened we don't suspend voice - gSavedSettings.setBOOL("ShowDeviceSettings", false); - } -} -void LLPanelVoiceDeviceSettings::draw() -{ - refresh(); - - // let user know that volume indicator is not yet available - bool is_in_tuning_mode = LLVoiceClient::getInstance()->inTuningMode(); - bool voice_enabled = LLVoiceClient::getInstance()->voiceEnabled(); - if (voice_enabled) - { - getChildView("wait_text")->setVisible( !is_in_tuning_mode && mUseTuningMode); - getChildView("disabled_text")->setVisible(false); - mUnmuteBtn->setVisible(false); - } - else - { - getChildView("wait_text")->setVisible(false); - - static LLCachedControl chat_enabled(gSavedSettings, "EnableVoiceChat"); - // If voice isn't enabled, it is either disabled or muted - bool voice_disabled = chat_enabled() || LLStartUp::getStartupState() <= STATE_LOGIN_WAIT; - getChildView("disabled_text")->setVisible(voice_disabled); - mUnmuteBtn->setVisible(!voice_disabled); - } - - LLPanel::draw(); - - if (is_in_tuning_mode && voice_enabled) - { - const S32 num_bars = 5; - F32 voice_power = LLVoiceClient::getInstance()->tuningGetEnergy() / LLVoiceClient::OVERDRIVEN_POWER_LEVEL; - S32 discrete_power = llmin(num_bars, llfloor(voice_power * (F32)num_bars + 0.1f)); - - for(S32 power_bar_idx = 0; power_bar_idx < num_bars; power_bar_idx++) - { - std::string view_name = llformat("%s%d", "bar", power_bar_idx); - LLView* bar_view = getChild(view_name); - if (bar_view) - { - gl_rect_2d(bar_view->getRect(), LLColor4::grey, true); - - LLColor4 color; - if (power_bar_idx < discrete_power) - { - color = (power_bar_idx >= 3) ? LLUIColorTable::instance().getColor("OverdrivenColor") : LLUIColorTable::instance().getColor("SpeakingColor"); - } - else - { - color = LLUIColorTable::instance().getColor("PanelFocusBackgroundColor"); - } - - LLRect color_rect = bar_view->getRect(); - color_rect.stretch(-1); - gl_rect_2d(color_rect, color, true); - } - } - } -} - -void LLPanelVoiceDeviceSettings::apply() -{ - std::string s; - if(mCtrlInputDevices) - { - s = mCtrlInputDevices->getValue().asString(); - gSavedSettings.setString("VoiceInputAudioDevice", s); - mInputDevice = s; - } - - if(mCtrlOutputDevices) - { - s = mCtrlOutputDevices->getValue().asString(); - gSavedSettings.setString("VoiceOutputAudioDevice", s); - mOutputDevice = s; - } - - // assume we are being destroyed by closing our embedding window - LLSlider* volume_slider = getChild("mic_volume_slider"); - if(volume_slider) - { - F32 slider_value = (F32)volume_slider->getValue().asReal(); - gSavedSettings.setF32("AudioLevelMic", slider_value); - mMicVolume = slider_value; - } -} - -void LLPanelVoiceDeviceSettings::cancel() -{ - gSavedSettings.setString("VoiceInputAudioDevice", mInputDevice); - gSavedSettings.setString("VoiceOutputAudioDevice", mOutputDevice); - - if(mCtrlInputDevices) - mCtrlInputDevices->setValue(mInputDevice); - - if(mCtrlOutputDevices) - mCtrlOutputDevices->setValue(mOutputDevice); - - gSavedSettings.setF32("AudioLevelMic", mMicVolume); - LLSlider* volume_slider = getChild("mic_volume_slider"); - if(volume_slider) - { - volume_slider->setValue(mMicVolume); - } -} - -void LLPanelVoiceDeviceSettings::refresh() -{ - //grab current volume - LLSlider* volume_slider = getChild("mic_volume_slider"); - // set mic volume tuning slider based on last mic volume setting - F32 current_volume = (F32)volume_slider->getValue().asReal(); - LLVoiceClient::getInstance()->tuningSetMicVolume(current_volume); - - // Fill in popup menus - bool device_settings_available = LLVoiceClient::getInstance()->deviceSettingsAvailable(); - - if (mCtrlInputDevices) - { - mCtrlInputDevices->setEnabled(device_settings_available); - } - - if (mCtrlOutputDevices) - { - mCtrlOutputDevices->setEnabled(device_settings_available); - } - - getChild("mic_volume_slider")->setEnabled(device_settings_available); - - if(!device_settings_available) - { - // The combo boxes are disabled, since we can't get the device settings from the daemon just now. - // Put the currently set default (ONLY) in the box, and select it. - if(mCtrlInputDevices) - { - mCtrlInputDevices->removeall(); - mCtrlInputDevices->add(getLocalizedDeviceName(mInputDevice), mInputDevice, ADD_BOTTOM); - mCtrlInputDevices->setValue(mInputDevice); - } - if(mCtrlOutputDevices) - { - mCtrlOutputDevices->removeall(); - mCtrlOutputDevices->add(getLocalizedDeviceName(mOutputDevice), mOutputDevice, ADD_BOTTOM); - mCtrlOutputDevices->setValue(mOutputDevice); - } - } - else if (LLVoiceClient::getInstance()->deviceSettingsUpdated()) - { - LLVoiceDeviceList::const_iterator device; - - if(mCtrlInputDevices) - { - mCtrlInputDevices->removeall(); - mCtrlInputDevices->add(getLocalizedDeviceName(DEFAULT_DEVICE), DEFAULT_DEVICE, ADD_BOTTOM); - - for(device=LLVoiceClient::getInstance()->getCaptureDevices().begin(); - device != LLVoiceClient::getInstance()->getCaptureDevices().end(); - device++) - { - mCtrlInputDevices->add(getLocalizedDeviceName(device->display_name), device->full_name, ADD_BOTTOM); - } - - // Fix invalid input audio device preference. - if (!mCtrlInputDevices->setSelectedByValue(mInputDevice, true)) - { - mCtrlInputDevices->setValue(DEFAULT_DEVICE); - gSavedSettings.setString("VoiceInputAudioDevice", DEFAULT_DEVICE); - mInputDevice = DEFAULT_DEVICE; - } - } - - if(mCtrlOutputDevices) - { - mCtrlOutputDevices->removeall(); - mCtrlOutputDevices->add(getLocalizedDeviceName(DEFAULT_DEVICE), DEFAULT_DEVICE, ADD_BOTTOM); - - for(device = LLVoiceClient::getInstance()->getRenderDevices().begin(); - device != LLVoiceClient::getInstance()->getRenderDevices().end(); - device++) - { - mCtrlOutputDevices->add(getLocalizedDeviceName(device->display_name), device->full_name, ADD_BOTTOM); - } - - // Fix invalid output audio device preference. - if (!mCtrlOutputDevices->setSelectedByValue(mOutputDevice, true)) - { - mCtrlOutputDevices->setValue(DEFAULT_DEVICE); - gSavedSettings.setString("VoiceOutputAudioDevice", DEFAULT_DEVICE); - mOutputDevice = DEFAULT_DEVICE; - } - } - } -} - -void LLPanelVoiceDeviceSettings::initialize() -{ - mInputDevice = gSavedSettings.getString("VoiceInputAudioDevice"); - mOutputDevice = gSavedSettings.getString("VoiceOutputAudioDevice"); - mMicVolume = gSavedSettings.getF32("AudioLevelMic"); - - // ask for new device enumeration - LLVoiceClient::getInstance()->refreshDeviceLists(); - - // put voice client in "tuning" mode - if (mUseTuningMode) - { - LLVoiceClient::getInstance()->tuningStart(); - LLVoiceChannel::suspend(); - } -} - -void LLPanelVoiceDeviceSettings::cleanup() -{ - if (mUseTuningMode) - { - LLVoiceClient::getInstance()->tuningStop(); - LLVoiceChannel::resume(); - } -} - -// returns English name if no translation found -std::string LLPanelVoiceDeviceSettings::getLocalizedDeviceName(const std::string& en_dev_name) -{ - std::map::const_iterator it = mLocalizedDeviceNames.find(en_dev_name); - return it != mLocalizedDeviceNames.end() ? it->second : en_dev_name; -} - -void LLPanelVoiceDeviceSettings::onCommitInputDevice() -{ - if(LLVoiceClient::getInstance()) - { - mInputDevice = mCtrlInputDevices->getValue().asString(); - LLVoiceClient::getInstance()->setRenderDevice(mInputDevice); - } -} - -void LLPanelVoiceDeviceSettings::onCommitOutputDevice() -{ - if(LLVoiceClient::getInstance()) - { - - mOutputDevice = mCtrlOutputDevices->getValue().asString(); - LLVoiceClient::getInstance()->setRenderDevice(mOutputDevice); - } -} - -void LLPanelVoiceDeviceSettings::onOutputDevicesClicked() -{ - LLVoiceClient::getInstance()->refreshDeviceLists(false); // fill in the pop up menus again if needed. -} - -void LLPanelVoiceDeviceSettings::onInputDevicesClicked() -{ - LLVoiceClient::getInstance()->refreshDeviceLists(false); // fill in the pop up menus again if needed. -} - -void LLPanelVoiceDeviceSettings::onCommitUnmute() -{ - gSavedSettings.setBOOL("EnableVoiceChat", true); -} +/** + * @file llpanelvoicedevicesettings.cpp + * @author Richard Nelson + * @brief Voice communication set-up + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelvoicedevicesettings.h" + +// Viewer includes +#include "llcombobox.h" +#include "llsliderctrl.h" +#include "llstartup.h" +#include "llviewercontrol.h" +#include "llvoiceclient.h" +#include "llvoicechannel.h" + +// Library includes (after viewer) +#include "lluictrlfactory.h" + +static LLPanelInjector t_panel_group_general("panel_voice_device_settings"); +static const std::string DEFAULT_DEVICE("Default"); + + +LLPanelVoiceDeviceSettings::LLPanelVoiceDeviceSettings() + : LLPanel() +{ + mCtrlInputDevices = NULL; + mCtrlOutputDevices = NULL; + mInputDevice = gSavedSettings.getString("VoiceInputAudioDevice"); + mOutputDevice = gSavedSettings.getString("VoiceOutputAudioDevice"); + mDevicesUpdated = false; //obsolete + mUseTuningMode = true; + + // grab "live" mic volume level + mMicVolume = gSavedSettings.getF32("AudioLevelMic"); + +} + +LLPanelVoiceDeviceSettings::~LLPanelVoiceDeviceSettings() +{ +} + +bool LLPanelVoiceDeviceSettings::postBuild() +{ + LLSlider* volume_slider = getChild("mic_volume_slider"); + // set mic volume tuning slider based on last mic volume setting + volume_slider->setValue(mMicVolume); + + mCtrlInputDevices = getChild("voice_input_device"); + mCtrlOutputDevices = getChild("voice_output_device"); + mUnmuteBtn = getChild("unmute_btn"); + + mCtrlInputDevices->setCommitCallback( + boost::bind(&LLPanelVoiceDeviceSettings::onCommitInputDevice, this)); + mCtrlOutputDevices->setCommitCallback( + boost::bind(&LLPanelVoiceDeviceSettings::onCommitOutputDevice, this)); + mUnmuteBtn->setCommitCallback( + boost::bind(&LLPanelVoiceDeviceSettings::onCommitUnmute, this)); + + mLocalizedDeviceNames[DEFAULT_DEVICE] = getString("default_text"); + mLocalizedDeviceNames["No Device"] = getString("name_no_device"); + mLocalizedDeviceNames["Default System Device"] = getString("name_default_system_device"); + + mCtrlOutputDevices->setMouseDownCallback(boost::bind(&LLPanelVoiceDeviceSettings::onOutputDevicesClicked, this)); + mCtrlInputDevices->setMouseDownCallback(boost::bind(&LLPanelVoiceDeviceSettings::onInputDevicesClicked, this)); + + + return true; +} + +// virtual +void LLPanelVoiceDeviceSettings::onVisibilityChange ( bool new_visibility ) +{ + if (new_visibility) + { + initialize(); + } + else + { + cleanup(); + // when closing this window, turn of visiblity control so that + // next time preferences is opened we don't suspend voice + gSavedSettings.setBOOL("ShowDeviceSettings", false); + } +} +void LLPanelVoiceDeviceSettings::draw() +{ + refresh(); + + // let user know that volume indicator is not yet available + bool is_in_tuning_mode = LLVoiceClient::getInstance()->inTuningMode(); + bool voice_enabled = LLVoiceClient::getInstance()->voiceEnabled(); + if (voice_enabled) + { + getChildView("wait_text")->setVisible( !is_in_tuning_mode && mUseTuningMode); + getChildView("disabled_text")->setVisible(false); + mUnmuteBtn->setVisible(false); + } + else + { + getChildView("wait_text")->setVisible(false); + + static LLCachedControl chat_enabled(gSavedSettings, "EnableVoiceChat"); + // If voice isn't enabled, it is either disabled or muted + bool voice_disabled = chat_enabled() || LLStartUp::getStartupState() <= STATE_LOGIN_WAIT; + getChildView("disabled_text")->setVisible(voice_disabled); + mUnmuteBtn->setVisible(!voice_disabled); + } + + LLPanel::draw(); + + if (is_in_tuning_mode && voice_enabled) + { + const S32 num_bars = 5; + F32 voice_power = LLVoiceClient::getInstance()->tuningGetEnergy() / LLVoiceClient::OVERDRIVEN_POWER_LEVEL; + S32 discrete_power = llmin(num_bars, llfloor(voice_power * (F32)num_bars + 0.1f)); + + for(S32 power_bar_idx = 0; power_bar_idx < num_bars; power_bar_idx++) + { + std::string view_name = llformat("%s%d", "bar", power_bar_idx); + LLView* bar_view = getChild(view_name); + if (bar_view) + { + gl_rect_2d(bar_view->getRect(), LLColor4::grey, true); + + LLColor4 color; + if (power_bar_idx < discrete_power) + { + color = (power_bar_idx >= 3) ? LLUIColorTable::instance().getColor("OverdrivenColor") : LLUIColorTable::instance().getColor("SpeakingColor"); + } + else + { + color = LLUIColorTable::instance().getColor("PanelFocusBackgroundColor"); + } + + LLRect color_rect = bar_view->getRect(); + color_rect.stretch(-1); + gl_rect_2d(color_rect, color, true); + } + } + } +} + +void LLPanelVoiceDeviceSettings::apply() +{ + std::string s; + if(mCtrlInputDevices) + { + s = mCtrlInputDevices->getValue().asString(); + gSavedSettings.setString("VoiceInputAudioDevice", s); + mInputDevice = s; + } + + if(mCtrlOutputDevices) + { + s = mCtrlOutputDevices->getValue().asString(); + gSavedSettings.setString("VoiceOutputAudioDevice", s); + mOutputDevice = s; + } + + // assume we are being destroyed by closing our embedding window + LLSlider* volume_slider = getChild("mic_volume_slider"); + if(volume_slider) + { + F32 slider_value = (F32)volume_slider->getValue().asReal(); + gSavedSettings.setF32("AudioLevelMic", slider_value); + mMicVolume = slider_value; + } +} + +void LLPanelVoiceDeviceSettings::cancel() +{ + gSavedSettings.setString("VoiceInputAudioDevice", mInputDevice); + gSavedSettings.setString("VoiceOutputAudioDevice", mOutputDevice); + + if(mCtrlInputDevices) + mCtrlInputDevices->setValue(mInputDevice); + + if(mCtrlOutputDevices) + mCtrlOutputDevices->setValue(mOutputDevice); + + gSavedSettings.setF32("AudioLevelMic", mMicVolume); + LLSlider* volume_slider = getChild("mic_volume_slider"); + if(volume_slider) + { + volume_slider->setValue(mMicVolume); + } +} + +void LLPanelVoiceDeviceSettings::refresh() +{ + //grab current volume + LLSlider* volume_slider = getChild("mic_volume_slider"); + // set mic volume tuning slider based on last mic volume setting + F32 current_volume = (F32)volume_slider->getValue().asReal(); + LLVoiceClient::getInstance()->tuningSetMicVolume(current_volume); + + // Fill in popup menus + bool device_settings_available = LLVoiceClient::getInstance()->deviceSettingsAvailable(); + + if (mCtrlInputDevices) + { + mCtrlInputDevices->setEnabled(device_settings_available); + } + + if (mCtrlOutputDevices) + { + mCtrlOutputDevices->setEnabled(device_settings_available); + } + + getChild("mic_volume_slider")->setEnabled(device_settings_available); + + if(!device_settings_available) + { + // The combo boxes are disabled, since we can't get the device settings from the daemon just now. + // Put the currently set default (ONLY) in the box, and select it. + if(mCtrlInputDevices) + { + mCtrlInputDevices->removeall(); + mCtrlInputDevices->add(getLocalizedDeviceName(mInputDevice), mInputDevice, ADD_BOTTOM); + mCtrlInputDevices->setValue(mInputDevice); + } + if(mCtrlOutputDevices) + { + mCtrlOutputDevices->removeall(); + mCtrlOutputDevices->add(getLocalizedDeviceName(mOutputDevice), mOutputDevice, ADD_BOTTOM); + mCtrlOutputDevices->setValue(mOutputDevice); + } + } + else if (LLVoiceClient::getInstance()->deviceSettingsUpdated()) + { + LLVoiceDeviceList::const_iterator device; + + if(mCtrlInputDevices) + { + mCtrlInputDevices->removeall(); + mCtrlInputDevices->add(getLocalizedDeviceName(DEFAULT_DEVICE), DEFAULT_DEVICE, ADD_BOTTOM); + + for(device=LLVoiceClient::getInstance()->getCaptureDevices().begin(); + device != LLVoiceClient::getInstance()->getCaptureDevices().end(); + device++) + { + mCtrlInputDevices->add(getLocalizedDeviceName(device->display_name), device->full_name, ADD_BOTTOM); + } + + // Fix invalid input audio device preference. + if (!mCtrlInputDevices->setSelectedByValue(mInputDevice, true)) + { + mCtrlInputDevices->setValue(DEFAULT_DEVICE); + gSavedSettings.setString("VoiceInputAudioDevice", DEFAULT_DEVICE); + mInputDevice = DEFAULT_DEVICE; + } + } + + if(mCtrlOutputDevices) + { + mCtrlOutputDevices->removeall(); + mCtrlOutputDevices->add(getLocalizedDeviceName(DEFAULT_DEVICE), DEFAULT_DEVICE, ADD_BOTTOM); + + for(device = LLVoiceClient::getInstance()->getRenderDevices().begin(); + device != LLVoiceClient::getInstance()->getRenderDevices().end(); + device++) + { + mCtrlOutputDevices->add(getLocalizedDeviceName(device->display_name), device->full_name, ADD_BOTTOM); + } + + // Fix invalid output audio device preference. + if (!mCtrlOutputDevices->setSelectedByValue(mOutputDevice, true)) + { + mCtrlOutputDevices->setValue(DEFAULT_DEVICE); + gSavedSettings.setString("VoiceOutputAudioDevice", DEFAULT_DEVICE); + mOutputDevice = DEFAULT_DEVICE; + } + } + } +} + +void LLPanelVoiceDeviceSettings::initialize() +{ + mInputDevice = gSavedSettings.getString("VoiceInputAudioDevice"); + mOutputDevice = gSavedSettings.getString("VoiceOutputAudioDevice"); + mMicVolume = gSavedSettings.getF32("AudioLevelMic"); + + // ask for new device enumeration + LLVoiceClient::getInstance()->refreshDeviceLists(); + + // put voice client in "tuning" mode + if (mUseTuningMode) + { + LLVoiceClient::getInstance()->tuningStart(); + LLVoiceChannel::suspend(); + } +} + +void LLPanelVoiceDeviceSettings::cleanup() +{ + if (mUseTuningMode) + { + LLVoiceClient::getInstance()->tuningStop(); + LLVoiceChannel::resume(); + } +} + +// returns English name if no translation found +std::string LLPanelVoiceDeviceSettings::getLocalizedDeviceName(const std::string& en_dev_name) +{ + std::map::const_iterator it = mLocalizedDeviceNames.find(en_dev_name); + return it != mLocalizedDeviceNames.end() ? it->second : en_dev_name; +} + +void LLPanelVoiceDeviceSettings::onCommitInputDevice() +{ + if(LLVoiceClient::getInstance()) + { + mInputDevice = mCtrlInputDevices->getValue().asString(); + LLVoiceClient::getInstance()->setRenderDevice(mInputDevice); + } +} + +void LLPanelVoiceDeviceSettings::onCommitOutputDevice() +{ + if(LLVoiceClient::getInstance()) + { + + mOutputDevice = mCtrlOutputDevices->getValue().asString(); + LLVoiceClient::getInstance()->setRenderDevice(mOutputDevice); + } +} + +void LLPanelVoiceDeviceSettings::onOutputDevicesClicked() +{ + LLVoiceClient::getInstance()->refreshDeviceLists(false); // fill in the pop up menus again if needed. +} + +void LLPanelVoiceDeviceSettings::onInputDevicesClicked() +{ + LLVoiceClient::getInstance()->refreshDeviceLists(false); // fill in the pop up menus again if needed. +} + +void LLPanelVoiceDeviceSettings::onCommitUnmute() +{ + gSavedSettings.setBOOL("EnableVoiceChat", true); +} diff --git a/indra/newview/llpanelvoicedevicesettings.h b/indra/newview/llpanelvoicedevicesettings.h index c3b699b6ff..815396cbd1 100644 --- a/indra/newview/llpanelvoicedevicesettings.h +++ b/indra/newview/llpanelvoicedevicesettings.h @@ -1,71 +1,71 @@ -/** - * @file llpanelvoicedevicesettings.h - * @author Richard Nelson - * @brief Voice communication set-up wizard - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELVOICEDEVICESETTINGS_H -#define LL_LLPANELVOICEDEVICESETTINGS_H - -#include "llpanel.h" - -class LLPanelVoiceDeviceSettings : public LLPanel -{ -public: - LLPanelVoiceDeviceSettings(); - ~LLPanelVoiceDeviceSettings(); - - /*virtual*/ void draw(); - /*virtual*/ bool postBuild(); - void apply(); - void cancel(); - void refresh(); - void initialize(); - void cleanup(); - - /*virtual*/ void onVisibilityChange ( bool new_visibility ); - - void setUseTuningMode(bool use) { mUseTuningMode = use; }; - -protected: - std::string getLocalizedDeviceName(const std::string& en_dev_name); - - void onCommitInputDevice(); - void onCommitOutputDevice(); - void onOutputDevicesClicked(); - void onInputDevicesClicked(); - void onCommitUnmute(); - - F32 mMicVolume; - std::string mInputDevice; - std::string mOutputDevice; - class LLComboBox *mCtrlInputDevices; - class LLComboBox *mCtrlOutputDevices; - class LLButton *mUnmuteBtn; - bool mDevicesUpdated; - bool mUseTuningMode; - std::map mLocalizedDeviceNames; -}; - -#endif // LL_LLPANELVOICEDEVICESETTINGS_H +/** + * @file llpanelvoicedevicesettings.h + * @author Richard Nelson + * @brief Voice communication set-up wizard + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELVOICEDEVICESETTINGS_H +#define LL_LLPANELVOICEDEVICESETTINGS_H + +#include "llpanel.h" + +class LLPanelVoiceDeviceSettings : public LLPanel +{ +public: + LLPanelVoiceDeviceSettings(); + ~LLPanelVoiceDeviceSettings(); + + /*virtual*/ void draw(); + /*virtual*/ bool postBuild(); + void apply(); + void cancel(); + void refresh(); + void initialize(); + void cleanup(); + + /*virtual*/ void onVisibilityChange ( bool new_visibility ); + + void setUseTuningMode(bool use) { mUseTuningMode = use; }; + +protected: + std::string getLocalizedDeviceName(const std::string& en_dev_name); + + void onCommitInputDevice(); + void onCommitOutputDevice(); + void onOutputDevicesClicked(); + void onInputDevicesClicked(); + void onCommitUnmute(); + + F32 mMicVolume; + std::string mInputDevice; + std::string mOutputDevice; + class LLComboBox *mCtrlInputDevices; + class LLComboBox *mCtrlOutputDevices; + class LLButton *mUnmuteBtn; + bool mDevicesUpdated; + bool mUseTuningMode; + std::map mLocalizedDeviceNames; +}; + +#endif // LL_LLPANELVOICEDEVICESETTINGS_H diff --git a/indra/newview/llpanelvoiceeffect.cpp b/indra/newview/llpanelvoiceeffect.cpp index b3ab25af5b..a0129b2cb1 100644 --- a/indra/newview/llpanelvoiceeffect.cpp +++ b/indra/newview/llpanelvoiceeffect.cpp @@ -1,165 +1,165 @@ -/** - * @file llpanelvoiceeffect.cpp - * @author Aimee - * @brief Panel to select Voice Morphs. - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelvoiceeffect.h" - -#include "llcombobox.h" -#include "llfloaterreg.h" -#include "llpanel.h" -#include "lltrans.h" -#include "lltransientfloatermgr.h" -#include "llvoiceclient.h" -#include "llweb.h" - -static LLPanelInjector t_panel_voice_effect("panel_voice_effect"); - -LLPanelVoiceEffect::LLPanelVoiceEffect() - : mVoiceEffectCombo(NULL) -{ - mCommitCallbackRegistrar.add("Voice.CommitVoiceEffect", boost::bind(&LLPanelVoiceEffect::onCommitVoiceEffect, this)); -} - -LLPanelVoiceEffect::~LLPanelVoiceEffect() -{ - LLView* combo_list_view = mVoiceEffectCombo->getChildView("ComboBox"); - LLTransientFloaterMgr::getInstance()->removeControlView(combo_list_view); - - if(LLVoiceClient::instanceExists()) - { - LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); - if (effect_interface) - { - effect_interface->removeObserver(this); - } - } -} - -// virtual -bool LLPanelVoiceEffect::postBuild() -{ - mVoiceEffectCombo = getChild("voice_effect"); - - // Need to tell LLTransientFloaterMgr about the combo list, otherwise it can't - // be clicked while in a docked floater as it extends outside the floater area. - LLView* combo_list_view = mVoiceEffectCombo->getChildView("ComboBox"); - LLTransientFloaterMgr::getInstance()->addControlView(combo_list_view); - - LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); - if (effect_interface) - { - effect_interface->addObserver(this); - } - - update(true); - - return true; -} - -////////////////////////////////////////////////////////////////////////// -/// PRIVATE SECTION -////////////////////////////////////////////////////////////////////////// - -void LLPanelVoiceEffect::onCommitVoiceEffect() -{ - LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); - if (!effect_interface) - { - mVoiceEffectCombo->setEnabled(false); - return; - } - - LLSD value = mVoiceEffectCombo->getValue(); - if (value.asInteger() == PREVIEW_VOICE_EFFECTS) - { - // Open the Voice Morph preview floater - LLFloaterReg::showInstance("voice_effect"); - } - else if (value.asInteger() == GET_VOICE_EFFECTS) - { - // Open the voice morphing info web page - LLWeb::loadURL(LLTrans::getString("voice_morphing_url")); - } - else - { - effect_interface->setVoiceEffect(value.asUUID()); - } - - mVoiceEffectCombo->setValue(effect_interface->getVoiceEffect()); -} - -// virtual -void LLPanelVoiceEffect::onVoiceEffectChanged(bool effect_list_updated) -{ - update(effect_list_updated); -} - -void LLPanelVoiceEffect::update(bool list_updated) -{ - if (mVoiceEffectCombo) - { - LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); - if (!effect_interface) return; - if (list_updated) - { - // Add the default "No Voice Morph" entry. - mVoiceEffectCombo->removeall(); - mVoiceEffectCombo->add(getString("no_voice_effect"), LLUUID::null); - mVoiceEffectCombo->addSeparator(); - - // Add entries for each Voice Morph. - const voice_effect_list_t& effect_list = effect_interface->getVoiceEffectList(); - if (!effect_list.empty()) - { - for (voice_effect_list_t::const_iterator it = effect_list.begin(); it != effect_list.end(); ++it) - { - mVoiceEffectCombo->add(it->first, it->second, ADD_BOTTOM); - } - - mVoiceEffectCombo->addSeparator(); - } - - // Add the fixed entries to go to the preview floater or marketing page. - mVoiceEffectCombo->add(getString("preview_voice_effects"), PREVIEW_VOICE_EFFECTS); - mVoiceEffectCombo->add(getString("get_voice_effects"), GET_VOICE_EFFECTS); - } - - if (effect_interface && LLVoiceClient::instance().isVoiceWorking()) - { - // Select the current Voice Morph. - mVoiceEffectCombo->setValue(effect_interface->getVoiceEffect()); - mVoiceEffectCombo->setEnabled(true); - } - else - { - // If voice isn't working or Voice Effects are not supported disable the control. - mVoiceEffectCombo->setValue(LLUUID::null); - mVoiceEffectCombo->setEnabled(false); - } - } -} +/** + * @file llpanelvoiceeffect.cpp + * @author Aimee + * @brief Panel to select Voice Morphs. + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelvoiceeffect.h" + +#include "llcombobox.h" +#include "llfloaterreg.h" +#include "llpanel.h" +#include "lltrans.h" +#include "lltransientfloatermgr.h" +#include "llvoiceclient.h" +#include "llweb.h" + +static LLPanelInjector t_panel_voice_effect("panel_voice_effect"); + +LLPanelVoiceEffect::LLPanelVoiceEffect() + : mVoiceEffectCombo(NULL) +{ + mCommitCallbackRegistrar.add("Voice.CommitVoiceEffect", boost::bind(&LLPanelVoiceEffect::onCommitVoiceEffect, this)); +} + +LLPanelVoiceEffect::~LLPanelVoiceEffect() +{ + LLView* combo_list_view = mVoiceEffectCombo->getChildView("ComboBox"); + LLTransientFloaterMgr::getInstance()->removeControlView(combo_list_view); + + if(LLVoiceClient::instanceExists()) + { + LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); + if (effect_interface) + { + effect_interface->removeObserver(this); + } + } +} + +// virtual +bool LLPanelVoiceEffect::postBuild() +{ + mVoiceEffectCombo = getChild("voice_effect"); + + // Need to tell LLTransientFloaterMgr about the combo list, otherwise it can't + // be clicked while in a docked floater as it extends outside the floater area. + LLView* combo_list_view = mVoiceEffectCombo->getChildView("ComboBox"); + LLTransientFloaterMgr::getInstance()->addControlView(combo_list_view); + + LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); + if (effect_interface) + { + effect_interface->addObserver(this); + } + + update(true); + + return true; +} + +////////////////////////////////////////////////////////////////////////// +/// PRIVATE SECTION +////////////////////////////////////////////////////////////////////////// + +void LLPanelVoiceEffect::onCommitVoiceEffect() +{ + LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); + if (!effect_interface) + { + mVoiceEffectCombo->setEnabled(false); + return; + } + + LLSD value = mVoiceEffectCombo->getValue(); + if (value.asInteger() == PREVIEW_VOICE_EFFECTS) + { + // Open the Voice Morph preview floater + LLFloaterReg::showInstance("voice_effect"); + } + else if (value.asInteger() == GET_VOICE_EFFECTS) + { + // Open the voice morphing info web page + LLWeb::loadURL(LLTrans::getString("voice_morphing_url")); + } + else + { + effect_interface->setVoiceEffect(value.asUUID()); + } + + mVoiceEffectCombo->setValue(effect_interface->getVoiceEffect()); +} + +// virtual +void LLPanelVoiceEffect::onVoiceEffectChanged(bool effect_list_updated) +{ + update(effect_list_updated); +} + +void LLPanelVoiceEffect::update(bool list_updated) +{ + if (mVoiceEffectCombo) + { + LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); + if (!effect_interface) return; + if (list_updated) + { + // Add the default "No Voice Morph" entry. + mVoiceEffectCombo->removeall(); + mVoiceEffectCombo->add(getString("no_voice_effect"), LLUUID::null); + mVoiceEffectCombo->addSeparator(); + + // Add entries for each Voice Morph. + const voice_effect_list_t& effect_list = effect_interface->getVoiceEffectList(); + if (!effect_list.empty()) + { + for (voice_effect_list_t::const_iterator it = effect_list.begin(); it != effect_list.end(); ++it) + { + mVoiceEffectCombo->add(it->first, it->second, ADD_BOTTOM); + } + + mVoiceEffectCombo->addSeparator(); + } + + // Add the fixed entries to go to the preview floater or marketing page. + mVoiceEffectCombo->add(getString("preview_voice_effects"), PREVIEW_VOICE_EFFECTS); + mVoiceEffectCombo->add(getString("get_voice_effects"), GET_VOICE_EFFECTS); + } + + if (effect_interface && LLVoiceClient::instance().isVoiceWorking()) + { + // Select the current Voice Morph. + mVoiceEffectCombo->setValue(effect_interface->getVoiceEffect()); + mVoiceEffectCombo->setEnabled(true); + } + else + { + // If voice isn't working or Voice Effects are not supported disable the control. + mVoiceEffectCombo->setValue(LLUUID::null); + mVoiceEffectCombo->setEnabled(false); + } + } +} diff --git a/indra/newview/llpanelvoiceeffect.h b/indra/newview/llpanelvoiceeffect.h index 515c85bd03..f920e41081 100644 --- a/indra/newview/llpanelvoiceeffect.h +++ b/indra/newview/llpanelvoiceeffect.h @@ -1,67 +1,67 @@ -/** - * @file llpanelvoiceeffect.h - * @author Aimee - * @brief Panel to select Voice Effects. - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_PANELVOICEEFFECT_H -#define LL_PANELVOICEEFFECT_H - -#include "llpanel.h" -#include "llvoiceclient.h" - -class LLComboBox; - -class LLPanelVoiceEffect - : public LLPanel - , public LLVoiceEffectObserver -{ -public: - LOG_CLASS(LLPanelVoiceEffect); - - LLPanelVoiceEffect(); - virtual ~LLPanelVoiceEffect(); - - bool postBuild() override; - -private: - void onCommitVoiceEffect(); - void update(bool list_updated); - - /// Called by voice effect provider when voice effect list is changed. - void onVoiceEffectChanged(bool effect_list_updated) override; - - // Fixed entries in the Voice Morph list - typedef enum e_voice_effect_combo_items - { - NO_VOICE_EFFECT = 0, - PREVIEW_VOICE_EFFECTS = 1, - GET_VOICE_EFFECTS = 2 - } EVoiceEffectComboItems; - - LLComboBox* mVoiceEffectCombo; -}; - - -#endif //LL_PANELVOICEEFFECT_H +/** + * @file llpanelvoiceeffect.h + * @author Aimee + * @brief Panel to select Voice Effects. + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_PANELVOICEEFFECT_H +#define LL_PANELVOICEEFFECT_H + +#include "llpanel.h" +#include "llvoiceclient.h" + +class LLComboBox; + +class LLPanelVoiceEffect + : public LLPanel + , public LLVoiceEffectObserver +{ +public: + LOG_CLASS(LLPanelVoiceEffect); + + LLPanelVoiceEffect(); + virtual ~LLPanelVoiceEffect(); + + bool postBuild() override; + +private: + void onCommitVoiceEffect(); + void update(bool list_updated); + + /// Called by voice effect provider when voice effect list is changed. + void onVoiceEffectChanged(bool effect_list_updated) override; + + // Fixed entries in the Voice Morph list + typedef enum e_voice_effect_combo_items + { + NO_VOICE_EFFECT = 0, + PREVIEW_VOICE_EFFECTS = 1, + GET_VOICE_EFFECTS = 2 + } EVoiceEffectComboItems; + + LLComboBox* mVoiceEffectCombo; +}; + + +#endif //LL_PANELVOICEEFFECT_H diff --git a/indra/newview/llpanelvolume.cpp b/indra/newview/llpanelvolume.cpp index 6322c61fb7..655713be14 100644 --- a/indra/newview/llpanelvolume.cpp +++ b/indra/newview/llpanelvolume.cpp @@ -1,1591 +1,1591 @@ -/** - * @file llpanelvolume.cpp - * @brief Object editing (position, scale, etc.) in the tools floater - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -// file include -#include "llpanelvolume.h" - -// linden library includes -#include "llclickaction.h" -#include "llerror.h" -#include "llfontgl.h" -#include "llflexibleobject.h" -#include "llmaterialtable.h" -#include "llpermissionsflags.h" -#include "llstring.h" -#include "llvolume.h" -#include "m3math.h" -#include "material_codes.h" - -// project includes -#include "llbutton.h" -#include "llcheckboxctrl.h" -#include "llcolorswatch.h" -#include "lltexturectrl.h" -#include "llcombobox.h" -//#include "llfirstuse.h" -#include "llfloaterreg.h" -#include "llfocusmgr.h" -#include "llmanipscale.h" -#include "llinventorymodel.h" -#include "llmenubutton.h" -#include "llpreviewscript.h" -#include "llresmgr.h" -#include "llselectmgr.h" -#include "llspinctrl.h" -#include "lltextbox.h" -#include "lltool.h" -#include "lltoolcomp.h" -#include "lltooldraganddrop.h" -#include "lltoolmgr.h" -#include "lltrans.h" -#include "llui.h" -#include "llviewerobject.h" -#include "llviewerregion.h" -#include "llviewerwindow.h" -#include "llvovolume.h" -#include "llworld.h" -#include "pipeline.h" -#include "llviewershadermgr.h" -#include "llnotificationsutil.h" - -#include "lldrawpool.h" -#include "lluictrlfactory.h" - -// For mesh physics -#include "llagent.h" -#include "llviewercontrol.h" -#include "llmeshrepository.h" - -#include "llvoavatarself.h" - -#include - - -const F32 DEFAULT_GRAVITY_MULTIPLIER = 1.f; -const F32 DEFAULT_DENSITY = 1000.f; - -// "Features" Tab -bool LLPanelVolume::postBuild() -{ - // Flexible Objects Parameters - { - childSetCommitCallback("Animated Mesh Checkbox Ctrl", boost::bind(&LLPanelVolume::onCommitAnimatedMeshCheckbox, this, _1, _2), NULL); - childSetCommitCallback("Flexible1D Checkbox Ctrl", boost::bind(&LLPanelVolume::onCommitIsFlexible, this, _1, _2), NULL); - childSetCommitCallback("FlexNumSections",onCommitFlexible,this); - getChild("FlexNumSections")->setValidateBeforeCommit(precommitValidate); - childSetCommitCallback("FlexGravity",onCommitFlexible,this); - getChild("FlexGravity")->setValidateBeforeCommit(precommitValidate); - childSetCommitCallback("FlexFriction",onCommitFlexible,this); - getChild("FlexFriction")->setValidateBeforeCommit(precommitValidate); - childSetCommitCallback("FlexWind",onCommitFlexible,this); - getChild("FlexWind")->setValidateBeforeCommit(precommitValidate); - childSetCommitCallback("FlexTension",onCommitFlexible,this); - getChild("FlexTension")->setValidateBeforeCommit(precommitValidate); - childSetCommitCallback("FlexForceX",onCommitFlexible,this); - getChild("FlexForceX")->setValidateBeforeCommit(precommitValidate); - childSetCommitCallback("FlexForceY",onCommitFlexible,this); - getChild("FlexForceY")->setValidateBeforeCommit(precommitValidate); - childSetCommitCallback("FlexForceZ",onCommitFlexible,this); - getChild("FlexForceZ")->setValidateBeforeCommit(precommitValidate); - } - - // LIGHT Parameters - { - childSetCommitCallback("Light Checkbox Ctrl",onCommitIsLight,this); - LLColorSwatchCtrl* LightColorSwatch = getChild("colorswatch"); - if(LightColorSwatch){ - LightColorSwatch->setOnCancelCallback(boost::bind(&LLPanelVolume::onLightCancelColor, this, _2)); - LightColorSwatch->setOnSelectCallback(boost::bind(&LLPanelVolume::onLightSelectColor, this, _2)); - childSetCommitCallback("colorswatch",onCommitLight,this); - } - - LLTextureCtrl* LightTexPicker = getChild("light texture control"); - if (LightTexPicker) - { - LightTexPicker->setOnCancelCallback(boost::bind(&LLPanelVolume::onLightCancelTexture, this, _2)); - LightTexPicker->setOnSelectCallback(boost::bind(&LLPanelVolume::onLightSelectTexture, this, _2)); - childSetCommitCallback("light texture control", onCommitLight, this); - } - - childSetCommitCallback("Light Intensity",onCommitLight,this); - getChild("Light Intensity")->setValidateBeforeCommit(precommitValidate); - childSetCommitCallback("Light Radius",onCommitLight,this); - getChild("Light Radius")->setValidateBeforeCommit(precommitValidate); - childSetCommitCallback("Light Falloff",onCommitLight,this); - getChild("Light Falloff")->setValidateBeforeCommit(precommitValidate); - - childSetCommitCallback("Light FOV", onCommitLight, this); - getChild("Light FOV")->setValidateBeforeCommit( precommitValidate); - childSetCommitCallback("Light Focus", onCommitLight, this); - getChild("Light Focus")->setValidateBeforeCommit( precommitValidate); - childSetCommitCallback("Light Ambiance", onCommitLight, this); - getChild("Light Ambiance")->setValidateBeforeCommit( precommitValidate); - } - - // REFLECTION PROBE Parameters - { - childSetCommitCallback("Reflection Probe", onCommitIsReflectionProbe, this); - childSetCommitCallback("Probe Dynamic", onCommitProbe, this); - childSetCommitCallback("Probe Volume Type", onCommitProbe, this); - childSetCommitCallback("Probe Ambiance", onCommitProbe, this); - childSetCommitCallback("Probe Near Clip", onCommitProbe, this); - } - - // PHYSICS Parameters - { - // PhysicsShapeType combobox - mComboPhysicsShapeType = getChild("Physics Shape Type Combo Ctrl"); - mComboPhysicsShapeType->setCommitCallback(boost::bind(&LLPanelVolume::sendPhysicsShapeType, this, _1, mComboPhysicsShapeType)); - - // PhysicsGravity - mSpinPhysicsGravity = getChild("Physics Gravity"); - mSpinPhysicsGravity->setCommitCallback(boost::bind(&LLPanelVolume::sendPhysicsGravity, this, _1, mSpinPhysicsGravity)); - - // PhysicsFriction - mSpinPhysicsFriction = getChild("Physics Friction"); - mSpinPhysicsFriction->setCommitCallback(boost::bind(&LLPanelVolume::sendPhysicsFriction, this, _1, mSpinPhysicsFriction)); - - // PhysicsDensity - mSpinPhysicsDensity = getChild("Physics Density"); - mSpinPhysicsDensity->setCommitCallback(boost::bind(&LLPanelVolume::sendPhysicsDensity, this, _1, mSpinPhysicsDensity)); - - // PhysicsRestitution - mSpinPhysicsRestitution = getChild("Physics Restitution"); - mSpinPhysicsRestitution->setCommitCallback(boost::bind(&LLPanelVolume::sendPhysicsRestitution, this, _1, mSpinPhysicsRestitution)); - } - - mMenuClipboardFeatures = getChild("clipboard_features_params_btn"); - mMenuClipboardLight = getChild("clipboard_light_params_btn"); - - std::map material_name_map; - material_name_map["Stone"]= LLTrans::getString("Stone"); - material_name_map["Metal"]= LLTrans::getString("Metal"); - material_name_map["Glass"]= LLTrans::getString("Glass"); - material_name_map["Wood"]= LLTrans::getString("Wood"); - material_name_map["Flesh"]= LLTrans::getString("Flesh"); - material_name_map["Plastic"]= LLTrans::getString("Plastic"); - material_name_map["Rubber"]= LLTrans::getString("Rubber"); - material_name_map["Light"]= LLTrans::getString("Light"); - - LLMaterialTable::basic.initTableTransNames(material_name_map); - - // material type popup - mComboMaterial = getChild("material"); - childSetCommitCallback("material",onCommitMaterial,this); - mComboMaterial->removeall(); - - for (LLMaterialTable::info_list_t::iterator iter = LLMaterialTable::basic.mMaterialInfoList.begin(); - iter != LLMaterialTable::basic.mMaterialInfoList.end(); ++iter) - { - LLMaterialInfo* minfop = *iter; - if (minfop->mMCode != LL_MCODE_LIGHT) - { - mComboMaterial->add(minfop->mName); - } - } - mComboMaterialItemCount = mComboMaterial->getItemCount(); - - // Start with everyone disabled - clearCtrls(); - - return true; -} - -LLPanelVolume::LLPanelVolume() - : LLPanel(), - mComboMaterialItemCount(0) -{ - setMouseOpaque(false); - - mCommitCallbackRegistrar.add("PanelVolume.menuDoToSelected", boost::bind(&LLPanelVolume::menuDoToSelected, this, _2)); - mEnableCallbackRegistrar.add("PanelVolume.menuEnable", boost::bind(&LLPanelVolume::menuEnableItem, this, _2)); -} - - -LLPanelVolume::~LLPanelVolume() -{ - // Children all cleaned up by default view destructor. -} - -void LLPanelVolume::getState( ) -{ - LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getFirstRootObject(); - LLViewerObject* root_objectp = objectp; - if(!objectp) - { - objectp = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); - // *FIX: shouldn't we just keep the child? - if (objectp) - { - LLViewerObject* parentp = objectp->getRootEdit(); - - if (parentp) - { - root_objectp = parentp; - } - else - { - root_objectp = objectp; - } - } - } - - LLVOVolume *volobjp = NULL; - if ( objectp && (objectp->getPCode() == LL_PCODE_VOLUME)) - { - volobjp = (LLVOVolume *)objectp; - } - LLVOVolume *root_volobjp = NULL; - if (root_objectp && (root_objectp->getPCode() == LL_PCODE_VOLUME)) - { - root_volobjp = (LLVOVolume *)root_objectp; - } - - if( !objectp ) - { - //forfeit focus - if (gFocusMgr.childHasKeyboardFocus(this)) - { - gFocusMgr.setKeyboardFocus(NULL); - } - - // Disable all text input fields - clearCtrls(); - - return; - } - - LLUUID owner_id; - std::string owner_name; - LLSelectMgr::getInstance()->selectGetOwner(owner_id, owner_name); - - // BUG? Check for all objects being editable? - bool editable = root_objectp->permModify() && !root_objectp->isPermanentEnforced(); - bool single_volume = LLSelectMgr::getInstance()->selectionAllPCode( LL_PCODE_VOLUME ) - && LLSelectMgr::getInstance()->getSelection()->getObjectCount() == 1; - bool single_root_volume = LLSelectMgr::getInstance()->selectionAllPCode( LL_PCODE_VOLUME ) && - LLSelectMgr::getInstance()->getSelection()->getRootObjectCount() == 1; - - // Select Single Message - if (single_volume) - { - getChildView("edit_object")->setVisible(true); - getChildView("edit_object")->setEnabled(true); - getChildView("select_single")->setVisible(false); - } - else - { - getChildView("edit_object")->setVisible(false); - getChildView("select_single")->setVisible(true); - getChildView("select_single")->setEnabled(true); - } - - // Light properties - bool is_light = volobjp && volobjp->getIsLight(); - getChild("Light Checkbox Ctrl")->setValue(is_light); - getChildView("Light Checkbox Ctrl")->setEnabled(editable && single_volume && volobjp); - - if (is_light && editable && single_volume) - { - //mLabelColor ->setEnabled( true ); - LLColorSwatchCtrl* LightColorSwatch = getChild("colorswatch"); - if(LightColorSwatch) - { - LightColorSwatch->setEnabled( true ); - LightColorSwatch->setValid( true ); - LightColorSwatch->set(volobjp->getLightSRGBBaseColor()); - } - - LLTextureCtrl* LightTextureCtrl = getChild("light texture control"); - if (LightTextureCtrl) - { - LightTextureCtrl->setEnabled(true); - LightTextureCtrl->setValid(true); - LightTextureCtrl->setImageAssetID(volobjp->getLightTextureID()); - } - - getChildView("Light Intensity")->setEnabled(true); - getChildView("Light Radius")->setEnabled(true); - getChildView("Light Falloff")->setEnabled(true); - - getChildView("Light FOV")->setEnabled(true); - getChildView("Light Focus")->setEnabled(true); - getChildView("Light Ambiance")->setEnabled(true); - - getChild("Light Intensity")->setValue(volobjp->getLightIntensity()); - getChild("Light Radius")->setValue(volobjp->getLightRadius()); - getChild("Light Falloff")->setValue(volobjp->getLightFalloff()); - - LLVector3 params = volobjp->getSpotLightParams(); - getChild("Light FOV")->setValue(params.mV[0]); - getChild("Light Focus")->setValue(params.mV[1]); - getChild("Light Ambiance")->setValue(params.mV[2]); - - mLightSavedColor = volobjp->getLightSRGBBaseColor(); - } - else - { - getChild("Light Intensity", true)->clear(); - getChild("Light Radius", true)->clear(); - getChild("Light Falloff", true)->clear(); - - LLColorSwatchCtrl* LightColorSwatch = getChild("colorswatch"); - if(LightColorSwatch) - { - LightColorSwatch->setEnabled( false ); - LightColorSwatch->setValid( false ); - } - LLTextureCtrl* LightTextureCtrl = getChild("light texture control"); - if (LightTextureCtrl) - { - LightTextureCtrl->setEnabled(false); - LightTextureCtrl->setValid(false); - - if (objectp->isAttachment()) - { - LightTextureCtrl->setImmediateFilterPermMask(PERM_COPY | PERM_TRANSFER); - } - else - { - LightTextureCtrl->setImmediateFilterPermMask(PERM_NONE); - } - } - - getChildView("Light Intensity")->setEnabled(false); - getChildView("Light Radius")->setEnabled(false); - getChildView("Light Falloff")->setEnabled(false); - - getChildView("Light FOV")->setEnabled(false); - getChildView("Light Focus")->setEnabled(false); - getChildView("Light Ambiance")->setEnabled(false); - } - - // Reflection Probe - bool is_probe = volobjp && volobjp->isReflectionProbe(); - getChild("Reflection Probe")->setValue(is_probe); - getChildView("Reflection Probe")->setEnabled(editable && single_volume && volobjp && !volobjp->isMesh()); - - bool probe_enabled = is_probe && editable && single_volume; - - getChildView("Probe Dynamic")->setEnabled(probe_enabled); - getChildView("Probe Volume Type")->setEnabled(probe_enabled); - getChildView("Probe Ambiance")->setEnabled(probe_enabled); - getChildView("Probe Near Clip")->setEnabled(probe_enabled); - - if (!probe_enabled) - { - getChild("Probe Volume Type", true)->clear(); - getChild("Probe Ambiance", true)->clear(); - getChild("Probe Near Clip", true)->clear(); - getChild("Probe Dynamic", true)->clear(); - } - else - { - std::string volume_type; - if (volobjp->getReflectionProbeIsBox()) - { - volume_type = "Box"; - } - else - { - volume_type = "Sphere"; - } - - getChild("Probe Volume Type", true)->setValue(volume_type); - getChild("Probe Ambiance", true)->setValue(volobjp->getReflectionProbeAmbiance()); - getChild("Probe Near Clip", true)->setValue(volobjp->getReflectionProbeNearClip()); - getChild("Probe Dynamic", true)->setValue(volobjp->getReflectionProbeIsDynamic()); - } - - // Animated Mesh - bool is_animated_mesh = single_root_volume && root_volobjp && root_volobjp->isAnimatedObject(); - getChild("Animated Mesh Checkbox Ctrl")->setValue(is_animated_mesh); - bool enabled_animated_object_box = false; - if (root_volobjp && root_volobjp == volobjp) - { - enabled_animated_object_box = single_root_volume && root_volobjp && root_volobjp->canBeAnimatedObject() && editable; -#if 0 - if (!enabled_animated_object_box) - { - LL_INFOS() << "not enabled: srv " << single_root_volume << " root_volobjp " << (bool) root_volobjp << LL_ENDL; - if (root_volobjp) - { - LL_INFOS() << " cba " << root_volobjp->canBeAnimatedObject() - << " editable " << editable << " permModify() " << root_volobjp->permModify() - << " ispermenf " << root_volobjp->isPermanentEnforced() << LL_ENDL; - } - } -#endif - if (enabled_animated_object_box && !is_animated_mesh && - root_volobjp->isAttachment() && !gAgentAvatarp->canAttachMoreAnimatedObjects()) - { - // Turning this attachment animated would cause us to exceed the limit. - enabled_animated_object_box = false; - } - } - getChildView("Animated Mesh Checkbox Ctrl")->setEnabled(enabled_animated_object_box); - - //refresh any bakes - if (root_volobjp) - { - root_volobjp->refreshBakeTexture(); - - LLViewerObject::const_child_list_t& child_list = root_volobjp->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); ++iter) - { - LLViewerObject* objectp = *iter; - if (objectp) - { - objectp->refreshBakeTexture(); - } - } - - if (gAgentAvatarp) - { - gAgentAvatarp->updateMeshVisibility(); - } - } - - // Flexible properties - bool is_flexible = volobjp && volobjp->isFlexible(); - getChild("Flexible1D Checkbox Ctrl")->setValue(is_flexible); - if (is_flexible || (volobjp && volobjp->canBeFlexible())) - { - getChildView("Flexible1D Checkbox Ctrl")->setEnabled(editable && single_volume && volobjp && !volobjp->isMesh() && !objectp->isPermanentEnforced()); - } - else - { - getChildView("Flexible1D Checkbox Ctrl")->setEnabled(false); - } - if (is_flexible && editable && single_volume) - { - getChildView("FlexNumSections")->setVisible(true); - getChildView("FlexGravity")->setVisible(true); - getChildView("FlexTension")->setVisible(true); - getChildView("FlexFriction")->setVisible(true); - getChildView("FlexWind")->setVisible(true); - getChildView("FlexForceX")->setVisible(true); - getChildView("FlexForceY")->setVisible(true); - getChildView("FlexForceZ")->setVisible(true); - - getChildView("FlexNumSections")->setEnabled(true); - getChildView("FlexGravity")->setEnabled(true); - getChildView("FlexTension")->setEnabled(true); - getChildView("FlexFriction")->setEnabled(true); - getChildView("FlexWind")->setEnabled(true); - getChildView("FlexForceX")->setEnabled(true); - getChildView("FlexForceY")->setEnabled(true); - getChildView("FlexForceZ")->setEnabled(true); - - LLFlexibleObjectData *attributes = (LLFlexibleObjectData *)objectp->getParameterEntry(LLNetworkData::PARAMS_FLEXIBLE); - - getChild("FlexNumSections")->setValue((F32)attributes->getSimulateLOD()); - getChild("FlexGravity")->setValue(attributes->getGravity()); - getChild("FlexTension")->setValue(attributes->getTension()); - getChild("FlexFriction")->setValue(attributes->getAirFriction()); - getChild("FlexWind")->setValue(attributes->getWindSensitivity()); - getChild("FlexForceX")->setValue(attributes->getUserForce().mV[VX]); - getChild("FlexForceY")->setValue(attributes->getUserForce().mV[VY]); - getChild("FlexForceZ")->setValue(attributes->getUserForce().mV[VZ]); - } - else - { - getChild("FlexNumSections", true)->clear(); - getChild("FlexGravity", true)->clear(); - getChild("FlexTension", true)->clear(); - getChild("FlexFriction", true)->clear(); - getChild("FlexWind", true)->clear(); - getChild("FlexForceX", true)->clear(); - getChild("FlexForceY", true)->clear(); - getChild("FlexForceZ", true)->clear(); - - getChildView("FlexNumSections")->setEnabled(false); - getChildView("FlexGravity")->setEnabled(false); - getChildView("FlexTension")->setEnabled(false); - getChildView("FlexFriction")->setEnabled(false); - getChildView("FlexWind")->setEnabled(false); - getChildView("FlexForceX")->setEnabled(false); - getChildView("FlexForceY")->setEnabled(false); - getChildView("FlexForceZ")->setEnabled(false); - } - - // Material properties - - // Update material part - // slightly inefficient - materials are unique per object, not per TE - U8 material_code = 0; - struct f : public LLSelectedTEGetFunctor - { - U8 get(LLViewerObject* object, S32 te) - { - return object->getMaterial(); - } - } func; - bool material_same = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &func, material_code ); - std::string LEGACY_FULLBRIGHT_DESC = LLTrans::getString("Fullbright"); - if (editable && single_volume && material_same) - { - mComboMaterial->setEnabled( true ); - if (material_code == LL_MCODE_LIGHT) - { - if (mComboMaterial->getItemCount() == mComboMaterialItemCount) - { - mComboMaterial->add(LEGACY_FULLBRIGHT_DESC); - } - mComboMaterial->setSimple(LEGACY_FULLBRIGHT_DESC); - } - else - { - if (mComboMaterial->getItemCount() != mComboMaterialItemCount) - { - mComboMaterial->remove(LEGACY_FULLBRIGHT_DESC); - } - - mComboMaterial->setSimple(std::string(LLMaterialTable::basic.getName(material_code))); - } - } - else - { - mComboMaterial->setEnabled( false ); - } - - // Physics properties - - mSpinPhysicsGravity->set(objectp->getPhysicsGravity()); - mSpinPhysicsGravity->setEnabled(editable); - - mSpinPhysicsFriction->set(objectp->getPhysicsFriction()); - mSpinPhysicsFriction->setEnabled(editable); - - mSpinPhysicsDensity->set(objectp->getPhysicsDensity()); - mSpinPhysicsDensity->setEnabled(editable); - - mSpinPhysicsRestitution->set(objectp->getPhysicsRestitution()); - mSpinPhysicsRestitution->setEnabled(editable); - - // update the physics shape combo to include allowed physics shapes - mComboPhysicsShapeType->removeall(); - mComboPhysicsShapeType->add(getString("None"), LLSD(1)); - - bool isMesh = false; - LLSculptParams *sculpt_params = (LLSculptParams *)objectp->getParameterEntry(LLNetworkData::PARAMS_SCULPT); - if (sculpt_params) - { - U8 sculpt_type = sculpt_params->getSculptType(); - U8 sculpt_stitching = sculpt_type & LL_SCULPT_TYPE_MASK; - isMesh = (sculpt_stitching == LL_SCULPT_TYPE_MESH); - } - - if(isMesh && objectp) - { - const LLVolumeParams &volume_params = objectp->getVolume()->getParams(); - LLUUID mesh_id = volume_params.getSculptID(); - if(gMeshRepo.hasPhysicsShape(mesh_id)) - { - // if a mesh contains an uploaded or decomposed physics mesh, - // allow 'Prim' - mComboPhysicsShapeType->add(getString("Prim"), LLSD(0)); - } - } - else - { - // simple prims always allow physics shape prim - mComboPhysicsShapeType->add(getString("Prim"), LLSD(0)); - } - - mComboPhysicsShapeType->add(getString("Convex Hull"), LLSD(2)); - mComboPhysicsShapeType->setValue(LLSD(objectp->getPhysicsShapeType())); - mComboPhysicsShapeType->setEnabled(editable && !objectp->isPermanentEnforced() && ((root_objectp == NULL) || !root_objectp->isPermanentEnforced())); - - mObject = objectp; - mRootObject = root_objectp; - - mMenuClipboardFeatures->setEnabled(editable && single_volume && volobjp); // Note: physics doesn't need to be limited by single volume - mMenuClipboardLight->setEnabled(editable && single_volume && volobjp); -} - -// static -bool LLPanelVolume::precommitValidate( const LLSD& data ) -{ - // TODO: Richard will fill this in later. - return true; // false means that validation failed and new value should not be commited. -} - - -void LLPanelVolume::refresh() -{ - getState(); - if (mObject.notNull() && mObject->isDead()) - { - mObject = NULL; - } - - if (mRootObject.notNull() && mRootObject->isDead()) - { - mRootObject = NULL; - } - - bool enable_mesh = false; - - LLSD sim_features; - LLViewerRegion *region = gAgent.getRegion(); - if(region) - { - LLSD sim_features; - region->getSimulatorFeatures(sim_features); - enable_mesh = sim_features.has("PhysicsShapeTypes"); - } - getChildView("label physicsshapetype")->setVisible(enable_mesh); - getChildView("Physics Shape Type Combo Ctrl")->setVisible(enable_mesh); - getChildView("Physics Gravity")->setVisible(enable_mesh); - getChildView("Physics Friction")->setVisible(enable_mesh); - getChildView("Physics Density")->setVisible(enable_mesh); - getChildView("Physics Restitution")->setVisible(enable_mesh); - - /* TODO: add/remove individual physics shape types as per the PhysicsShapeTypes simulator features */ -} - - -void LLPanelVolume::draw() -{ - LLPanel::draw(); -} - -// virtual -void LLPanelVolume::clearCtrls() -{ - LLPanel::clearCtrls(); - - getChildView("select_single")->setEnabled(false); - getChildView("select_single")->setVisible(true); - getChildView("edit_object")->setEnabled(false); - getChildView("edit_object")->setVisible(false); - getChildView("Light Checkbox Ctrl")->setEnabled(false);; - LLColorSwatchCtrl* LightColorSwatch = getChild("colorswatch"); - if(LightColorSwatch) - { - LightColorSwatch->setEnabled( false ); - LightColorSwatch->setValid( false ); - } - LLTextureCtrl* LightTextureCtrl = getChild("light texture control"); - if(LightTextureCtrl) - { - LightTextureCtrl->setEnabled( false ); - LightTextureCtrl->setValid( false ); - } - - getChildView("Light Intensity")->setEnabled(false); - getChildView("Light Radius")->setEnabled(false); - getChildView("Light Falloff")->setEnabled(false); - - getChildView("Reflection Probe")->setEnabled(false);; - getChildView("Probe Volume Type")->setEnabled(false); - getChildView("Probe Dynamic")->setEnabled(false); - getChildView("Probe Ambiance")->setEnabled(false); - getChildView("Probe Near Clip")->setEnabled(false); - getChildView("Animated Mesh Checkbox Ctrl")->setEnabled(false); - getChildView("Flexible1D Checkbox Ctrl")->setEnabled(false); - getChildView("FlexNumSections")->setEnabled(false); - getChildView("FlexGravity")->setEnabled(false); - getChildView("FlexTension")->setEnabled(false); - getChildView("FlexFriction")->setEnabled(false); - getChildView("FlexWind")->setEnabled(false); - getChildView("FlexForceX")->setEnabled(false); - getChildView("FlexForceY")->setEnabled(false); - getChildView("FlexForceZ")->setEnabled(false); - - mSpinPhysicsGravity->setEnabled(false); - mSpinPhysicsFriction->setEnabled(false); - mSpinPhysicsDensity->setEnabled(false); - mSpinPhysicsRestitution->setEnabled(false); - - mComboMaterial->setEnabled( false ); -} - -// -// Static functions -// - -void LLPanelVolume::sendIsLight() -{ - LLViewerObject* objectp = mObject; - if (!objectp || (objectp->getPCode() != LL_PCODE_VOLUME)) - { - return; - } - LLVOVolume *volobjp = (LLVOVolume *)objectp; - - bool value = getChild("Light Checkbox Ctrl")->getValue(); - volobjp->setIsLight(value); - LL_INFOS() << "update light sent" << LL_ENDL; -} - -void notify_cant_select_reflection_probe() -{ - if (!gSavedSettings.getBOOL("SelectReflectionProbes")) - { - LLNotificationsUtil::add("CantSelectReflectionProbe"); - } -} - -void LLPanelVolume::sendIsReflectionProbe() -{ - LLViewerObject* objectp = mObject; - if (!objectp || (objectp->getPCode() != LL_PCODE_VOLUME)) - { - return; - } - LLVOVolume* volobjp = (LLVOVolume*)objectp; - - bool value = getChild("Reflection Probe")->getValue(); - bool old_value = volobjp->isReflectionProbe(); - - if (value && value != old_value) - { // defer to notification util as to whether or not we *really* make this object a reflection probe - LLNotificationsUtil::add("ReflectionProbeApplied", LLSD(), LLSD(), boost::bind(&LLPanelVolume::doSendIsReflectionProbe, this, _1, _2)); - } - else - { - if (value) - { - notify_cant_select_reflection_probe(); - } - else if (objectp->flagPhantom()) - { - LLViewerObject* root = objectp->getRootEdit(); - bool in_linkeset = root != objectp || objectp->numChildren() > 0; - if (in_linkeset) - { - // In linkset with a phantom flag - objectp->setFlags(FLAGS_PHANTOM, false); - } - } - volobjp->setIsReflectionProbe(value); - } -} - -void LLPanelVolume::doSendIsReflectionProbe(const LLSD & notification, const LLSD & response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) // YES - { - LLViewerObject* objectp = mObject; - if (!objectp || (objectp->getPCode() != LL_PCODE_VOLUME)) - { - return; - } - LLVOVolume* volobjp = (LLVOVolume*)objectp; - - notify_cant_select_reflection_probe(); - volobjp->setIsReflectionProbe(true); - - { // has become a reflection probe, slam to a 10m sphere and pop up a message - // warning people about the pitfalls of reflection probes - - auto* select_mgr = LLSelectMgr::getInstance(); - - select_mgr->selectionUpdatePhantom(true); - select_mgr->selectionSetGLTFMaterial(LLUUID::null); - select_mgr->selectionSetAlphaOnly(0.f); - - LLVolumeParams params; - params.getPathParams().setCurveType(LL_PCODE_PATH_CIRCLE); - params.getProfileParams().setCurveType(LL_PCODE_PROFILE_CIRCLE_HALF); - mObject->updateVolume(params); - } - } - else - { - // cancelled, touch up UI state - getChild("Reflection Probe")->setValue(false); - } -} - -void LLPanelVolume::sendIsFlexible() -{ - LLViewerObject* objectp = mObject; - if (!objectp || (objectp->getPCode() != LL_PCODE_VOLUME)) - { - return; - } - LLVOVolume *volobjp = (LLVOVolume *)objectp; - - bool is_flexible = getChild("Flexible1D Checkbox Ctrl")->getValue(); - //bool is_flexible = mCheckFlexible1D->get(); - - if (is_flexible) - { - //LLFirstUse::useFlexible(); - - if (objectp->getClickAction() == CLICK_ACTION_SIT) - { - LLSelectMgr::getInstance()->selectionSetClickAction(CLICK_ACTION_NONE); - } - - } - - if (volobjp->setIsFlexible(is_flexible)) - { - mObject->sendShapeUpdate(); - LLSelectMgr::getInstance()->selectionUpdatePhantom(volobjp->flagPhantom()); - } - - LL_INFOS() << "update flexible sent" << LL_ENDL; -} - -void LLPanelVolume::sendPhysicsShapeType(LLUICtrl* ctrl, void* userdata) -{ - U8 type = ctrl->getValue().asInteger(); - LLSelectMgr::getInstance()->selectionSetPhysicsType(type); - - refreshCost(); -} - -void LLPanelVolume::sendPhysicsGravity(LLUICtrl* ctrl, void* userdata) -{ - F32 val = ctrl->getValue().asReal(); - LLSelectMgr::getInstance()->selectionSetGravity(val); -} - -void LLPanelVolume::sendPhysicsFriction(LLUICtrl* ctrl, void* userdata) -{ - F32 val = ctrl->getValue().asReal(); - LLSelectMgr::getInstance()->selectionSetFriction(val); -} - -void LLPanelVolume::sendPhysicsRestitution(LLUICtrl* ctrl, void* userdata) -{ - F32 val = ctrl->getValue().asReal(); - LLSelectMgr::getInstance()->selectionSetRestitution(val); -} - -void LLPanelVolume::sendPhysicsDensity(LLUICtrl* ctrl, void* userdata) -{ - F32 val = ctrl->getValue().asReal(); - LLSelectMgr::getInstance()->selectionSetDensity(val); -} - -void LLPanelVolume::refreshCost() -{ - LLViewerObject* obj = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); - - if (obj) - { - obj->getObjectCost(); - } -} - -void LLPanelVolume::onLightCancelColor(const LLSD& data) -{ - LLColorSwatchCtrl* LightColorSwatch = getChild("colorswatch"); - if(LightColorSwatch) - { - LightColorSwatch->setColor(mLightSavedColor); - } - onLightSelectColor(data); -} - -void LLPanelVolume::onLightCancelTexture(const LLSD& data) -{ - LLTextureCtrl* LightTextureCtrl = getChild("light texture control"); - LLVOVolume *volobjp = (LLVOVolume *) mObject.get(); - - if (volobjp && LightTextureCtrl) - { - // Cancel the light texture as requested - // NORSPEC-292 - // - // Texture picker triggers cancel both in case of actual cancel and in case of - // selection of "None" texture. - LLUUID tex_id = LightTextureCtrl->getImageAssetID(); - bool is_spotlight = volobjp->isLightSpotlight(); - setLightTextureID(tex_id, LightTextureCtrl->getImageItemID(), volobjp); //updates spotlight - - if (!is_spotlight && tex_id.notNull()) - { - LLVector3 spot_params = volobjp->getSpotLightParams(); - getChild("Light FOV")->setValue(spot_params.mV[0]); - getChild("Light Focus")->setValue(spot_params.mV[1]); - getChild("Light Ambiance")->setValue(spot_params.mV[2]); - } - } -} - -void LLPanelVolume::onLightSelectColor(const LLSD& data) -{ - LLViewerObject* objectp = mObject; - if (!objectp || (objectp->getPCode() != LL_PCODE_VOLUME)) - { - return; - } - LLVOVolume *volobjp = (LLVOVolume *)objectp; - - - LLColorSwatchCtrl* LightColorSwatch = getChild("colorswatch"); - if(LightColorSwatch) - { - LLColor4 clr = LightColorSwatch->get(); - LLColor3 clr3( clr ); - volobjp->setLightSRGBColor(clr3); - mLightSavedColor = clr; - } -} - -void LLPanelVolume::onLightSelectTexture(const LLSD& data) -{ - if (mObject.isNull() || (mObject->getPCode() != LL_PCODE_VOLUME)) - { - return; - } - LLVOVolume *volobjp = (LLVOVolume *) mObject.get(); - - - LLTextureCtrl* LightTextureCtrl = getChild("light texture control"); - if(LightTextureCtrl) - { - LLUUID id = LightTextureCtrl->getImageAssetID(); - setLightTextureID(id, LightTextureCtrl->getImageItemID(), volobjp); - } -} - -void LLPanelVolume::onCopyFeatures() -{ - LLViewerObject* objectp = mObject; - if (!objectp) - { - return; - } - - LLSD clipboard; - - LLVOVolume *volobjp = NULL; - if (objectp && (objectp->getPCode() == LL_PCODE_VOLUME)) - { - volobjp = (LLVOVolume *)objectp; - } - - // Flexi Prim - if (volobjp && volobjp->isFlexible()) - { - LLFlexibleObjectData *attributes = (LLFlexibleObjectData *)objectp->getParameterEntry(LLNetworkData::PARAMS_FLEXIBLE); - if (attributes) - { - clipboard["flex"]["lod"] = attributes->getSimulateLOD(); - clipboard["flex"]["gav"] = attributes->getGravity(); - clipboard["flex"]["ten"] = attributes->getTension(); - clipboard["flex"]["fri"] = attributes->getAirFriction(); - clipboard["flex"]["sen"] = attributes->getWindSensitivity(); - LLVector3 force = attributes->getUserForce(); - clipboard["flex"]["forx"] = force.mV[0]; - clipboard["flex"]["fory"] = force.mV[1]; - clipboard["flex"]["forz"] = force.mV[2]; - } - } - - // Physics - { - clipboard["physics"]["shape"] = objectp->getPhysicsShapeType(); - clipboard["physics"]["gravity"] = objectp->getPhysicsGravity(); - clipboard["physics"]["friction"] = objectp->getPhysicsFriction(); - clipboard["physics"]["density"] = objectp->getPhysicsDensity(); - clipboard["physics"]["restitution"] = objectp->getPhysicsRestitution(); - - U8 material_code = 0; - struct f : public LLSelectedTEGetFunctor - { - U8 get(LLViewerObject* object, S32 te) - { - return object->getMaterial(); - } - } func; - bool material_same = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue(&func, material_code); - // This should always be true since material should be per object. - if (material_same) - { - clipboard["physics"]["material"] = material_code; - } - } - - mClipboardParams["features"] = clipboard; -} - -void LLPanelVolume::onPasteFeatures() -{ - LLViewerObject* objectp = mObject; - if (!objectp && mClipboardParams.has("features")) - { - return; - } - - LLSD &clipboard = mClipboardParams["features"]; - - LLVOVolume *volobjp = NULL; - if (objectp && (objectp->getPCode() == LL_PCODE_VOLUME)) - { - volobjp = (LLVOVolume *)objectp; - } - - // Physics - bool is_root = objectp->isRoot(); - - // Not sure if phantom should go under physics, but doesn't fit elsewhere - bool is_phantom = clipboard["is_phantom"].asBoolean() && is_root; - LLSelectMgr::getInstance()->selectionUpdatePhantom(is_phantom); - - bool is_physical = clipboard["is_physical"].asBoolean() && is_root; - LLSelectMgr::getInstance()->selectionUpdatePhysics(is_physical); - - if (clipboard.has("physics")) - { - objectp->setPhysicsShapeType((U8)clipboard["physics"]["shape"].asInteger()); - U8 cur_material = objectp->getMaterial(); - U8 material = (U8)clipboard["physics"]["material"].asInteger() | (cur_material & ~LL_MCODE_MASK); - - objectp->setMaterial(material); - objectp->sendMaterialUpdate(); - objectp->setPhysicsGravity(clipboard["physics"]["gravity"].asReal()); - objectp->setPhysicsFriction(clipboard["physics"]["friction"].asReal()); - objectp->setPhysicsDensity(clipboard["physics"]["density"].asReal()); - objectp->setPhysicsRestitution(clipboard["physics"]["restitution"].asReal()); - objectp->updateFlags(true); - } - - // Flexible - bool is_flexible = clipboard.has("flex"); - if (is_flexible && volobjp->canBeFlexible()) - { - LLVOVolume *volobjp = (LLVOVolume *)objectp; - bool update_shape = false; - - // do before setParameterEntry or it will think that it is already flexi - update_shape = volobjp->setIsFlexible(is_flexible); - - if (objectp->getClickAction() == CLICK_ACTION_SIT) - { - objectp->setClickAction(CLICK_ACTION_NONE); - } - - LLFlexibleObjectData *attributes = (LLFlexibleObjectData *)objectp->getParameterEntry(LLNetworkData::PARAMS_FLEXIBLE); - if (attributes) - { - LLFlexibleObjectData new_attributes; - new_attributes = *attributes; - new_attributes.setSimulateLOD(clipboard["flex"]["lod"].asInteger()); - new_attributes.setGravity(clipboard["flex"]["gav"].asReal()); - new_attributes.setTension(clipboard["flex"]["ten"].asReal()); - new_attributes.setAirFriction(clipboard["flex"]["fri"].asReal()); - new_attributes.setWindSensitivity(clipboard["flex"]["sen"].asReal()); - F32 fx = (F32)clipboard["flex"]["forx"].asReal(); - F32 fy = (F32)clipboard["flex"]["fory"].asReal(); - F32 fz = (F32)clipboard["flex"]["forz"].asReal(); - LLVector3 force(fx, fy, fz); - new_attributes.setUserForce(force); - objectp->setParameterEntry(LLNetworkData::PARAMS_FLEXIBLE, new_attributes, true); - } - - if (update_shape) - { - mObject->sendShapeUpdate(); - LLSelectMgr::getInstance()->selectionUpdatePhantom(volobjp->flagPhantom()); - } - } - else - { - LLVOVolume *volobjp = (LLVOVolume *)objectp; - if (volobjp->setIsFlexible(false)) - { - mObject->sendShapeUpdate(); - LLSelectMgr::getInstance()->selectionUpdatePhantom(volobjp->flagPhantom()); - } - } -} - -void LLPanelVolume::onCopyLight() -{ - LLViewerObject* objectp = mObject; - if (!objectp) - { - return; - } - - LLSD clipboard; - - LLVOVolume *volobjp = NULL; - if (objectp && (objectp->getPCode() == LL_PCODE_VOLUME)) - { - volobjp = (LLVOVolume *)objectp; - } - - // Light Source - if (volobjp && volobjp->getIsLight()) - { - clipboard["light"]["intensity"] = volobjp->getLightIntensity(); - clipboard["light"]["radius"] = volobjp->getLightRadius(); - clipboard["light"]["falloff"] = volobjp->getLightFalloff(); - LLColor3 color = volobjp->getLightSRGBColor(); - clipboard["light"]["r"] = color.mV[0]; - clipboard["light"]["g"] = color.mV[1]; - clipboard["light"]["b"] = color.mV[2]; - - // Spotlight - if (volobjp->isLightSpotlight()) - { - LLUUID id = volobjp->getLightTextureID(); - if (id.notNull() && get_can_copy_texture(id)) - { - clipboard["spot"]["id"] = id; - LLVector3 spot_params = volobjp->getSpotLightParams(); - clipboard["spot"]["fov"] = spot_params.mV[0]; - clipboard["spot"]["focus"] = spot_params.mV[1]; - clipboard["spot"]["ambiance"] = spot_params.mV[2]; - } - } - } - - if (volobjp && volobjp->isReflectionProbe()) - { - clipboard["reflection_probe"]["is_box"] = volobjp->getReflectionProbeIsBox(); - clipboard["reflection_probe"]["ambiance"] = volobjp->getReflectionProbeAmbiance(); - clipboard["reflection_probe"]["near_clip"] = volobjp->getReflectionProbeNearClip(); - clipboard["reflection_probe"]["dynamic"] = volobjp->getReflectionProbeIsDynamic(); - } - - mClipboardParams["light"] = clipboard; -} - -void LLPanelVolume::onPasteLight() -{ - LLViewerObject* objectp = mObject; - if (!objectp && mClipboardParams.has("light")) - { - return; - } - - LLSD &clipboard = mClipboardParams["light"]; - - LLVOVolume *volobjp = NULL; - if (objectp && (objectp->getPCode() == LL_PCODE_VOLUME)) - { - volobjp = (LLVOVolume *)objectp; - } - - // Light Source - if (volobjp) - { - if (clipboard.has("light")) - { - volobjp->setIsLight(true); - volobjp->setLightIntensity((F32)clipboard["light"]["intensity"].asReal()); - volobjp->setLightRadius((F32)clipboard["light"]["radius"].asReal()); - volobjp->setLightFalloff((F32)clipboard["light"]["falloff"].asReal()); - F32 r = (F32)clipboard["light"]["r"].asReal(); - F32 g = (F32)clipboard["light"]["g"].asReal(); - F32 b = (F32)clipboard["light"]["b"].asReal(); - volobjp->setLightSRGBColor(LLColor3(r, g, b)); - } - else - { - volobjp->setIsLight(false); - } - - if (clipboard.has("spot")) - { - volobjp->setLightTextureID(clipboard["spot"]["id"].asUUID()); - LLVector3 spot_params; - spot_params.mV[0] = (F32)clipboard["spot"]["fov"].asReal(); - spot_params.mV[1] = (F32)clipboard["spot"]["focus"].asReal(); - spot_params.mV[2] = (F32)clipboard["spot"]["ambiance"].asReal(); - volobjp->setSpotLightParams(spot_params); - } - - if (clipboard.has("reflection_probe")) - { - volobjp->setIsReflectionProbe(true); - volobjp->setReflectionProbeIsBox(clipboard["reflection_probe"]["is_box"].asBoolean()); - volobjp->setReflectionProbeAmbiance((F32)clipboard["reflection_probe"]["ambiance"].asReal()); - volobjp->setReflectionProbeNearClip((F32)clipboard["reflection_probe"]["near_clip"].asReal()); - volobjp->setReflectionProbeIsDynamic(clipboard["reflection_probe"]["dynamic"].asBoolean()); - } - else - { - if (objectp->flagPhantom()) - { - LLViewerObject* root = objectp->getRootEdit(); - bool in_linkeset = root != objectp || objectp->numChildren() > 0; - if (in_linkeset) - { - // In linkset with a phantom flag - objectp->setFlags(FLAGS_PHANTOM, false); - } - } - - volobjp->setIsReflectionProbe(false); - } - } -} - -void LLPanelVolume::menuDoToSelected(const LLSD& userdata) -{ - std::string command = userdata.asString(); - - // paste - if (command == "features_paste") - { - onPasteFeatures(); - } - else if (command == "light_paste") - { - onPasteLight(); - } - // copy - else if (command == "features_copy") - { - onCopyFeatures(); - } - else if (command == "light_copy") - { - onCopyLight(); - } -} - -bool LLPanelVolume::menuEnableItem(const LLSD& userdata) -{ - std::string command = userdata.asString(); - - // paste options - if (command == "features_paste") - { - return mClipboardParams.has("features"); - } - else if (command == "light_paste") - { - return mClipboardParams.has("light"); - } - return false; -} - -// static -void LLPanelVolume::onCommitMaterial( LLUICtrl* ctrl, void* userdata ) -{ - LLPanelVolume* self = (LLPanelVolume*)userdata; - LLComboBox* box = (LLComboBox*) ctrl; - - if (box) - { - // apply the currently selected material to the object - const std::string& material_name = box->getSimple(); - std::string LEGACY_FULLBRIGHT_DESC = LLTrans::getString("Fullbright"); - if (material_name != LEGACY_FULLBRIGHT_DESC) - { - U8 material_code = LLMaterialTable::basic.getMCode(material_name); - if (self) - { - LLViewerObject* objectp = self->mObject; - if (objectp) - { - objectp->setPhysicsGravity(DEFAULT_GRAVITY_MULTIPLIER); - objectp->setPhysicsFriction(LLMaterialTable::basic.getFriction(material_code)); - //currently density is always set to 1000 serverside regardless of chosen material, - //actual material density should be used here, if this behavior change - objectp->setPhysicsDensity(DEFAULT_DENSITY); - objectp->setPhysicsRestitution(LLMaterialTable::basic.getRestitution(material_code)); - } - } - LLSelectMgr::getInstance()->selectionSetMaterial(material_code); - } - } -} - -// static -void LLPanelVolume::onCommitLight( LLUICtrl* ctrl, void* userdata ) -{ - LLPanelVolume* self = (LLPanelVolume*) userdata; - LLViewerObject* objectp = self->mObject; - if (!objectp || (objectp->getPCode() != LL_PCODE_VOLUME)) - { - return; - } - LLVOVolume *volobjp = (LLVOVolume *)objectp; - - - volobjp->setLightIntensity((F32)self->getChild("Light Intensity")->getValue().asReal()); - volobjp->setLightRadius((F32)self->getChild("Light Radius")->getValue().asReal()); - volobjp->setLightFalloff((F32)self->getChild("Light Falloff")->getValue().asReal()); - - LLColorSwatchCtrl* LightColorSwatch = self->getChild("colorswatch"); - if(LightColorSwatch) - { - LLColor4 clr = LightColorSwatch->get(); - volobjp->setLightSRGBColor(LLColor3(clr)); - } - - LLTextureCtrl* LightTextureCtrl = self->getChild("light texture control"); - if(LightTextureCtrl) - { - LLUUID id = LightTextureCtrl->getImageAssetID(); - LLUUID item_id = LightTextureCtrl->getImageItemID(); - if (id.notNull()) - { - if (!volobjp->isLightSpotlight()) - { //this commit is making this a spot light, set UI to default params - setLightTextureID(id, item_id, volobjp); - LLVector3 spot_params = volobjp->getSpotLightParams(); - self->getChild("Light FOV")->setValue(spot_params.mV[0]); - self->getChild("Light Focus")->setValue(spot_params.mV[1]); - self->getChild("Light Ambiance")->setValue(spot_params.mV[2]); - } - else - { //modifying existing params, this time volobjp won't change params on its own. - if (volobjp->getLightTextureID() != id) - { - setLightTextureID(id, item_id, volobjp); - } - - LLVector3 spot_params; - spot_params.mV[0] = (F32) self->getChild("Light FOV")->getValue().asReal(); - spot_params.mV[1] = (F32) self->getChild("Light Focus")->getValue().asReal(); - spot_params.mV[2] = (F32) self->getChild("Light Ambiance")->getValue().asReal(); - volobjp->setSpotLightParams(spot_params); - } - } - else if (volobjp->isLightSpotlight()) - { //no longer a spot light - setLightTextureID(id, item_id, volobjp); - //self->getChildView("Light FOV")->setEnabled(false); - //self->getChildView("Light Focus")->setEnabled(false); - //self->getChildView("Light Ambiance")->setEnabled(false); - } - } - - -} - -//static -void LLPanelVolume::onCommitProbe(LLUICtrl* ctrl, void* userdata) -{ - LLPanelVolume* self = (LLPanelVolume*)userdata; - LLViewerObject* objectp = self->mObject; - if (!objectp || (objectp->getPCode() != LL_PCODE_VOLUME)) - { - return; - } - LLVOVolume* volobjp = (LLVOVolume*)objectp; - - volobjp->setReflectionProbeAmbiance((F32)self->getChild("Probe Ambiance")->getValue().asReal()); - volobjp->setReflectionProbeNearClip((F32)self->getChild("Probe Near Clip")->getValue().asReal()); - volobjp->setReflectionProbeIsDynamic(self->getChild("Probe Dynamic")->getValue().asBoolean()); - - std::string shape_type = self->getChild("Probe Volume Type")->getValue().asString(); - - bool is_box = shape_type == "Box"; - - if (volobjp->setReflectionProbeIsBox(is_box)) - { - // make the volume match the probe - auto* select_mgr = LLSelectMgr::getInstance(); - - select_mgr->selectionUpdatePhantom(true); - select_mgr->selectionSetGLTFMaterial(LLUUID::null); - select_mgr->selectionSetAlphaOnly(0.f); - - U8 profile, path; - - if (!is_box) - { - profile = LL_PCODE_PROFILE_CIRCLE_HALF; - path = LL_PCODE_PATH_CIRCLE; - - F32 scale = volobjp->getScale().mV[0]; - volobjp->setScale(LLVector3(scale, scale, scale), false); - LLSelectMgr::getInstance()->sendMultipleUpdate(UPD_ROTATION | UPD_POSITION | UPD_SCALE); - } - else - { - profile = LL_PCODE_PROFILE_SQUARE; - path = LL_PCODE_PATH_LINE; - } - - LLVolumeParams params; - params.getProfileParams().setCurveType(profile); - params.getPathParams().setCurveType(path); - objectp->updateVolume(params); - } - -} - -// static -void LLPanelVolume::onCommitIsLight( LLUICtrl* ctrl, void* userdata ) -{ - LLPanelVolume* self = (LLPanelVolume*) userdata; - self->sendIsLight(); -} - -// static -void LLPanelVolume::setLightTextureID(const LLUUID &asset_id, const LLUUID &item_id, LLVOVolume* volobjp) -{ - if (volobjp) - { - LLViewerInventoryItem* item = gInventory.getItem(item_id); - - if (item && volobjp->isAttachment()) - { - const LLPermissions& perm = item->getPermissions(); - bool unrestricted = (perm.getMaskBase() & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED; - if (!unrestricted) - { - // Attachments are in world and in inventory simultaneously, - // at the moment server doesn't support such a situation. - return; - } - } - - if (item && !item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID())) - { - LLToolDragAndDrop::handleDropMaterialProtections(volobjp, item, LLToolDragAndDrop::SOURCE_AGENT, LLUUID::null); - } - - volobjp->setLightTextureID(asset_id); - } -} -//---------------------------------------------------------------------------- - -// static -void LLPanelVolume::onCommitIsReflectionProbe(LLUICtrl* ctrl, void* userdata) -{ - LLPanelVolume* self = (LLPanelVolume*)userdata; - self->sendIsReflectionProbe(); -} - -//---------------------------------------------------------------------------- - -// static -void LLPanelVolume::onCommitFlexible( LLUICtrl* ctrl, void* userdata ) -{ - LLPanelVolume* self = (LLPanelVolume*) userdata; - LLViewerObject* objectp = self->mObject; - if (!objectp || (objectp->getPCode() != LL_PCODE_VOLUME)) - { - return; - } - - LLFlexibleObjectData *attributes = (LLFlexibleObjectData *)objectp->getParameterEntry(LLNetworkData::PARAMS_FLEXIBLE); - if (attributes) - { - LLFlexibleObjectData new_attributes; - new_attributes = *attributes; - - - new_attributes.setSimulateLOD(self->getChild("FlexNumSections")->getValue().asInteger());//(S32)self->mSpinSections->get()); - new_attributes.setGravity((F32)self->getChild("FlexGravity")->getValue().asReal()); - new_attributes.setTension((F32)self->getChild("FlexTension")->getValue().asReal()); - new_attributes.setAirFriction((F32)self->getChild("FlexFriction")->getValue().asReal()); - new_attributes.setWindSensitivity((F32)self->getChild("FlexWind")->getValue().asReal()); - F32 fx = (F32)self->getChild("FlexForceX")->getValue().asReal(); - F32 fy = (F32)self->getChild("FlexForceY")->getValue().asReal(); - F32 fz = (F32)self->getChild("FlexForceZ")->getValue().asReal(); - LLVector3 force(fx,fy,fz); - - new_attributes.setUserForce(force); - objectp->setParameterEntry(LLNetworkData::PARAMS_FLEXIBLE, new_attributes, true); - } - - // Values may fail validation - self->refresh(); -} - -void LLPanelVolume::onCommitAnimatedMeshCheckbox(LLUICtrl *, void*) -{ - LLViewerObject* objectp = mObject; - if (!objectp || (objectp->getPCode() != LL_PCODE_VOLUME)) - { - return; - } - LLVOVolume *volobjp = (LLVOVolume *)objectp; - bool animated_mesh = getChild("Animated Mesh Checkbox Ctrl")->getValue(); - U32 flags = volobjp->getExtendedMeshFlags(); - U32 new_flags = flags; - if (animated_mesh) - { - new_flags |= LLExtendedMeshParams::ANIMATED_MESH_ENABLED_FLAG; - } - else - { - new_flags &= ~LLExtendedMeshParams::ANIMATED_MESH_ENABLED_FLAG; - } - if (new_flags != flags) - { - volobjp->setExtendedMeshFlags(new_flags); - } - - //refresh any bakes - if (volobjp) - { - volobjp->refreshBakeTexture(); - - LLViewerObject::const_child_list_t& child_list = volobjp->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); ++iter) - { - LLViewerObject* objectp = *iter; - if (objectp) - { - objectp->refreshBakeTexture(); - } - } - - if (gAgentAvatarp) - { - gAgentAvatarp->updateMeshVisibility(); - } - } -} - -void LLPanelVolume::onCommitIsFlexible(LLUICtrl *, void*) -{ - if (mObject->flagObjectPermanent()) - { - LLNotificationsUtil::add("PathfindingLinksets_ChangeToFlexiblePath", LLSD(), LLSD(), boost::bind(&LLPanelVolume::handleResponseChangeToFlexible, this, _1, _2)); - } - else - { - sendIsFlexible(); - } -} - -void LLPanelVolume::handleResponseChangeToFlexible(const LLSD &pNotification, const LLSD &pResponse) -{ - if (LLNotificationsUtil::getSelectedOption(pNotification, pResponse) == 0) - { - sendIsFlexible(); - } - else - { - getChild("Flexible1D Checkbox Ctrl")->setValue(false); - } -} +/** + * @file llpanelvolume.cpp + * @brief Object editing (position, scale, etc.) in the tools floater + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +// file include +#include "llpanelvolume.h" + +// linden library includes +#include "llclickaction.h" +#include "llerror.h" +#include "llfontgl.h" +#include "llflexibleobject.h" +#include "llmaterialtable.h" +#include "llpermissionsflags.h" +#include "llstring.h" +#include "llvolume.h" +#include "m3math.h" +#include "material_codes.h" + +// project includes +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "llcolorswatch.h" +#include "lltexturectrl.h" +#include "llcombobox.h" +//#include "llfirstuse.h" +#include "llfloaterreg.h" +#include "llfocusmgr.h" +#include "llmanipscale.h" +#include "llinventorymodel.h" +#include "llmenubutton.h" +#include "llpreviewscript.h" +#include "llresmgr.h" +#include "llselectmgr.h" +#include "llspinctrl.h" +#include "lltextbox.h" +#include "lltool.h" +#include "lltoolcomp.h" +#include "lltooldraganddrop.h" +#include "lltoolmgr.h" +#include "lltrans.h" +#include "llui.h" +#include "llviewerobject.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" +#include "llvovolume.h" +#include "llworld.h" +#include "pipeline.h" +#include "llviewershadermgr.h" +#include "llnotificationsutil.h" + +#include "lldrawpool.h" +#include "lluictrlfactory.h" + +// For mesh physics +#include "llagent.h" +#include "llviewercontrol.h" +#include "llmeshrepository.h" + +#include "llvoavatarself.h" + +#include + + +const F32 DEFAULT_GRAVITY_MULTIPLIER = 1.f; +const F32 DEFAULT_DENSITY = 1000.f; + +// "Features" Tab +bool LLPanelVolume::postBuild() +{ + // Flexible Objects Parameters + { + childSetCommitCallback("Animated Mesh Checkbox Ctrl", boost::bind(&LLPanelVolume::onCommitAnimatedMeshCheckbox, this, _1, _2), NULL); + childSetCommitCallback("Flexible1D Checkbox Ctrl", boost::bind(&LLPanelVolume::onCommitIsFlexible, this, _1, _2), NULL); + childSetCommitCallback("FlexNumSections",onCommitFlexible,this); + getChild("FlexNumSections")->setValidateBeforeCommit(precommitValidate); + childSetCommitCallback("FlexGravity",onCommitFlexible,this); + getChild("FlexGravity")->setValidateBeforeCommit(precommitValidate); + childSetCommitCallback("FlexFriction",onCommitFlexible,this); + getChild("FlexFriction")->setValidateBeforeCommit(precommitValidate); + childSetCommitCallback("FlexWind",onCommitFlexible,this); + getChild("FlexWind")->setValidateBeforeCommit(precommitValidate); + childSetCommitCallback("FlexTension",onCommitFlexible,this); + getChild("FlexTension")->setValidateBeforeCommit(precommitValidate); + childSetCommitCallback("FlexForceX",onCommitFlexible,this); + getChild("FlexForceX")->setValidateBeforeCommit(precommitValidate); + childSetCommitCallback("FlexForceY",onCommitFlexible,this); + getChild("FlexForceY")->setValidateBeforeCommit(precommitValidate); + childSetCommitCallback("FlexForceZ",onCommitFlexible,this); + getChild("FlexForceZ")->setValidateBeforeCommit(precommitValidate); + } + + // LIGHT Parameters + { + childSetCommitCallback("Light Checkbox Ctrl",onCommitIsLight,this); + LLColorSwatchCtrl* LightColorSwatch = getChild("colorswatch"); + if(LightColorSwatch){ + LightColorSwatch->setOnCancelCallback(boost::bind(&LLPanelVolume::onLightCancelColor, this, _2)); + LightColorSwatch->setOnSelectCallback(boost::bind(&LLPanelVolume::onLightSelectColor, this, _2)); + childSetCommitCallback("colorswatch",onCommitLight,this); + } + + LLTextureCtrl* LightTexPicker = getChild("light texture control"); + if (LightTexPicker) + { + LightTexPicker->setOnCancelCallback(boost::bind(&LLPanelVolume::onLightCancelTexture, this, _2)); + LightTexPicker->setOnSelectCallback(boost::bind(&LLPanelVolume::onLightSelectTexture, this, _2)); + childSetCommitCallback("light texture control", onCommitLight, this); + } + + childSetCommitCallback("Light Intensity",onCommitLight,this); + getChild("Light Intensity")->setValidateBeforeCommit(precommitValidate); + childSetCommitCallback("Light Radius",onCommitLight,this); + getChild("Light Radius")->setValidateBeforeCommit(precommitValidate); + childSetCommitCallback("Light Falloff",onCommitLight,this); + getChild("Light Falloff")->setValidateBeforeCommit(precommitValidate); + + childSetCommitCallback("Light FOV", onCommitLight, this); + getChild("Light FOV")->setValidateBeforeCommit( precommitValidate); + childSetCommitCallback("Light Focus", onCommitLight, this); + getChild("Light Focus")->setValidateBeforeCommit( precommitValidate); + childSetCommitCallback("Light Ambiance", onCommitLight, this); + getChild("Light Ambiance")->setValidateBeforeCommit( precommitValidate); + } + + // REFLECTION PROBE Parameters + { + childSetCommitCallback("Reflection Probe", onCommitIsReflectionProbe, this); + childSetCommitCallback("Probe Dynamic", onCommitProbe, this); + childSetCommitCallback("Probe Volume Type", onCommitProbe, this); + childSetCommitCallback("Probe Ambiance", onCommitProbe, this); + childSetCommitCallback("Probe Near Clip", onCommitProbe, this); + } + + // PHYSICS Parameters + { + // PhysicsShapeType combobox + mComboPhysicsShapeType = getChild("Physics Shape Type Combo Ctrl"); + mComboPhysicsShapeType->setCommitCallback(boost::bind(&LLPanelVolume::sendPhysicsShapeType, this, _1, mComboPhysicsShapeType)); + + // PhysicsGravity + mSpinPhysicsGravity = getChild("Physics Gravity"); + mSpinPhysicsGravity->setCommitCallback(boost::bind(&LLPanelVolume::sendPhysicsGravity, this, _1, mSpinPhysicsGravity)); + + // PhysicsFriction + mSpinPhysicsFriction = getChild("Physics Friction"); + mSpinPhysicsFriction->setCommitCallback(boost::bind(&LLPanelVolume::sendPhysicsFriction, this, _1, mSpinPhysicsFriction)); + + // PhysicsDensity + mSpinPhysicsDensity = getChild("Physics Density"); + mSpinPhysicsDensity->setCommitCallback(boost::bind(&LLPanelVolume::sendPhysicsDensity, this, _1, mSpinPhysicsDensity)); + + // PhysicsRestitution + mSpinPhysicsRestitution = getChild("Physics Restitution"); + mSpinPhysicsRestitution->setCommitCallback(boost::bind(&LLPanelVolume::sendPhysicsRestitution, this, _1, mSpinPhysicsRestitution)); + } + + mMenuClipboardFeatures = getChild("clipboard_features_params_btn"); + mMenuClipboardLight = getChild("clipboard_light_params_btn"); + + std::map material_name_map; + material_name_map["Stone"]= LLTrans::getString("Stone"); + material_name_map["Metal"]= LLTrans::getString("Metal"); + material_name_map["Glass"]= LLTrans::getString("Glass"); + material_name_map["Wood"]= LLTrans::getString("Wood"); + material_name_map["Flesh"]= LLTrans::getString("Flesh"); + material_name_map["Plastic"]= LLTrans::getString("Plastic"); + material_name_map["Rubber"]= LLTrans::getString("Rubber"); + material_name_map["Light"]= LLTrans::getString("Light"); + + LLMaterialTable::basic.initTableTransNames(material_name_map); + + // material type popup + mComboMaterial = getChild("material"); + childSetCommitCallback("material",onCommitMaterial,this); + mComboMaterial->removeall(); + + for (LLMaterialTable::info_list_t::iterator iter = LLMaterialTable::basic.mMaterialInfoList.begin(); + iter != LLMaterialTable::basic.mMaterialInfoList.end(); ++iter) + { + LLMaterialInfo* minfop = *iter; + if (minfop->mMCode != LL_MCODE_LIGHT) + { + mComboMaterial->add(minfop->mName); + } + } + mComboMaterialItemCount = mComboMaterial->getItemCount(); + + // Start with everyone disabled + clearCtrls(); + + return true; +} + +LLPanelVolume::LLPanelVolume() + : LLPanel(), + mComboMaterialItemCount(0) +{ + setMouseOpaque(false); + + mCommitCallbackRegistrar.add("PanelVolume.menuDoToSelected", boost::bind(&LLPanelVolume::menuDoToSelected, this, _2)); + mEnableCallbackRegistrar.add("PanelVolume.menuEnable", boost::bind(&LLPanelVolume::menuEnableItem, this, _2)); +} + + +LLPanelVolume::~LLPanelVolume() +{ + // Children all cleaned up by default view destructor. +} + +void LLPanelVolume::getState( ) +{ + LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getFirstRootObject(); + LLViewerObject* root_objectp = objectp; + if(!objectp) + { + objectp = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); + // *FIX: shouldn't we just keep the child? + if (objectp) + { + LLViewerObject* parentp = objectp->getRootEdit(); + + if (parentp) + { + root_objectp = parentp; + } + else + { + root_objectp = objectp; + } + } + } + + LLVOVolume *volobjp = NULL; + if ( objectp && (objectp->getPCode() == LL_PCODE_VOLUME)) + { + volobjp = (LLVOVolume *)objectp; + } + LLVOVolume *root_volobjp = NULL; + if (root_objectp && (root_objectp->getPCode() == LL_PCODE_VOLUME)) + { + root_volobjp = (LLVOVolume *)root_objectp; + } + + if( !objectp ) + { + //forfeit focus + if (gFocusMgr.childHasKeyboardFocus(this)) + { + gFocusMgr.setKeyboardFocus(NULL); + } + + // Disable all text input fields + clearCtrls(); + + return; + } + + LLUUID owner_id; + std::string owner_name; + LLSelectMgr::getInstance()->selectGetOwner(owner_id, owner_name); + + // BUG? Check for all objects being editable? + bool editable = root_objectp->permModify() && !root_objectp->isPermanentEnforced(); + bool single_volume = LLSelectMgr::getInstance()->selectionAllPCode( LL_PCODE_VOLUME ) + && LLSelectMgr::getInstance()->getSelection()->getObjectCount() == 1; + bool single_root_volume = LLSelectMgr::getInstance()->selectionAllPCode( LL_PCODE_VOLUME ) && + LLSelectMgr::getInstance()->getSelection()->getRootObjectCount() == 1; + + // Select Single Message + if (single_volume) + { + getChildView("edit_object")->setVisible(true); + getChildView("edit_object")->setEnabled(true); + getChildView("select_single")->setVisible(false); + } + else + { + getChildView("edit_object")->setVisible(false); + getChildView("select_single")->setVisible(true); + getChildView("select_single")->setEnabled(true); + } + + // Light properties + bool is_light = volobjp && volobjp->getIsLight(); + getChild("Light Checkbox Ctrl")->setValue(is_light); + getChildView("Light Checkbox Ctrl")->setEnabled(editable && single_volume && volobjp); + + if (is_light && editable && single_volume) + { + //mLabelColor ->setEnabled( true ); + LLColorSwatchCtrl* LightColorSwatch = getChild("colorswatch"); + if(LightColorSwatch) + { + LightColorSwatch->setEnabled( true ); + LightColorSwatch->setValid( true ); + LightColorSwatch->set(volobjp->getLightSRGBBaseColor()); + } + + LLTextureCtrl* LightTextureCtrl = getChild("light texture control"); + if (LightTextureCtrl) + { + LightTextureCtrl->setEnabled(true); + LightTextureCtrl->setValid(true); + LightTextureCtrl->setImageAssetID(volobjp->getLightTextureID()); + } + + getChildView("Light Intensity")->setEnabled(true); + getChildView("Light Radius")->setEnabled(true); + getChildView("Light Falloff")->setEnabled(true); + + getChildView("Light FOV")->setEnabled(true); + getChildView("Light Focus")->setEnabled(true); + getChildView("Light Ambiance")->setEnabled(true); + + getChild("Light Intensity")->setValue(volobjp->getLightIntensity()); + getChild("Light Radius")->setValue(volobjp->getLightRadius()); + getChild("Light Falloff")->setValue(volobjp->getLightFalloff()); + + LLVector3 params = volobjp->getSpotLightParams(); + getChild("Light FOV")->setValue(params.mV[0]); + getChild("Light Focus")->setValue(params.mV[1]); + getChild("Light Ambiance")->setValue(params.mV[2]); + + mLightSavedColor = volobjp->getLightSRGBBaseColor(); + } + else + { + getChild("Light Intensity", true)->clear(); + getChild("Light Radius", true)->clear(); + getChild("Light Falloff", true)->clear(); + + LLColorSwatchCtrl* LightColorSwatch = getChild("colorswatch"); + if(LightColorSwatch) + { + LightColorSwatch->setEnabled( false ); + LightColorSwatch->setValid( false ); + } + LLTextureCtrl* LightTextureCtrl = getChild("light texture control"); + if (LightTextureCtrl) + { + LightTextureCtrl->setEnabled(false); + LightTextureCtrl->setValid(false); + + if (objectp->isAttachment()) + { + LightTextureCtrl->setImmediateFilterPermMask(PERM_COPY | PERM_TRANSFER); + } + else + { + LightTextureCtrl->setImmediateFilterPermMask(PERM_NONE); + } + } + + getChildView("Light Intensity")->setEnabled(false); + getChildView("Light Radius")->setEnabled(false); + getChildView("Light Falloff")->setEnabled(false); + + getChildView("Light FOV")->setEnabled(false); + getChildView("Light Focus")->setEnabled(false); + getChildView("Light Ambiance")->setEnabled(false); + } + + // Reflection Probe + bool is_probe = volobjp && volobjp->isReflectionProbe(); + getChild("Reflection Probe")->setValue(is_probe); + getChildView("Reflection Probe")->setEnabled(editable && single_volume && volobjp && !volobjp->isMesh()); + + bool probe_enabled = is_probe && editable && single_volume; + + getChildView("Probe Dynamic")->setEnabled(probe_enabled); + getChildView("Probe Volume Type")->setEnabled(probe_enabled); + getChildView("Probe Ambiance")->setEnabled(probe_enabled); + getChildView("Probe Near Clip")->setEnabled(probe_enabled); + + if (!probe_enabled) + { + getChild("Probe Volume Type", true)->clear(); + getChild("Probe Ambiance", true)->clear(); + getChild("Probe Near Clip", true)->clear(); + getChild("Probe Dynamic", true)->clear(); + } + else + { + std::string volume_type; + if (volobjp->getReflectionProbeIsBox()) + { + volume_type = "Box"; + } + else + { + volume_type = "Sphere"; + } + + getChild("Probe Volume Type", true)->setValue(volume_type); + getChild("Probe Ambiance", true)->setValue(volobjp->getReflectionProbeAmbiance()); + getChild("Probe Near Clip", true)->setValue(volobjp->getReflectionProbeNearClip()); + getChild("Probe Dynamic", true)->setValue(volobjp->getReflectionProbeIsDynamic()); + } + + // Animated Mesh + bool is_animated_mesh = single_root_volume && root_volobjp && root_volobjp->isAnimatedObject(); + getChild("Animated Mesh Checkbox Ctrl")->setValue(is_animated_mesh); + bool enabled_animated_object_box = false; + if (root_volobjp && root_volobjp == volobjp) + { + enabled_animated_object_box = single_root_volume && root_volobjp && root_volobjp->canBeAnimatedObject() && editable; +#if 0 + if (!enabled_animated_object_box) + { + LL_INFOS() << "not enabled: srv " << single_root_volume << " root_volobjp " << (bool) root_volobjp << LL_ENDL; + if (root_volobjp) + { + LL_INFOS() << " cba " << root_volobjp->canBeAnimatedObject() + << " editable " << editable << " permModify() " << root_volobjp->permModify() + << " ispermenf " << root_volobjp->isPermanentEnforced() << LL_ENDL; + } + } +#endif + if (enabled_animated_object_box && !is_animated_mesh && + root_volobjp->isAttachment() && !gAgentAvatarp->canAttachMoreAnimatedObjects()) + { + // Turning this attachment animated would cause us to exceed the limit. + enabled_animated_object_box = false; + } + } + getChildView("Animated Mesh Checkbox Ctrl")->setEnabled(enabled_animated_object_box); + + //refresh any bakes + if (root_volobjp) + { + root_volobjp->refreshBakeTexture(); + + LLViewerObject::const_child_list_t& child_list = root_volobjp->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); ++iter) + { + LLViewerObject* objectp = *iter; + if (objectp) + { + objectp->refreshBakeTexture(); + } + } + + if (gAgentAvatarp) + { + gAgentAvatarp->updateMeshVisibility(); + } + } + + // Flexible properties + bool is_flexible = volobjp && volobjp->isFlexible(); + getChild("Flexible1D Checkbox Ctrl")->setValue(is_flexible); + if (is_flexible || (volobjp && volobjp->canBeFlexible())) + { + getChildView("Flexible1D Checkbox Ctrl")->setEnabled(editable && single_volume && volobjp && !volobjp->isMesh() && !objectp->isPermanentEnforced()); + } + else + { + getChildView("Flexible1D Checkbox Ctrl")->setEnabled(false); + } + if (is_flexible && editable && single_volume) + { + getChildView("FlexNumSections")->setVisible(true); + getChildView("FlexGravity")->setVisible(true); + getChildView("FlexTension")->setVisible(true); + getChildView("FlexFriction")->setVisible(true); + getChildView("FlexWind")->setVisible(true); + getChildView("FlexForceX")->setVisible(true); + getChildView("FlexForceY")->setVisible(true); + getChildView("FlexForceZ")->setVisible(true); + + getChildView("FlexNumSections")->setEnabled(true); + getChildView("FlexGravity")->setEnabled(true); + getChildView("FlexTension")->setEnabled(true); + getChildView("FlexFriction")->setEnabled(true); + getChildView("FlexWind")->setEnabled(true); + getChildView("FlexForceX")->setEnabled(true); + getChildView("FlexForceY")->setEnabled(true); + getChildView("FlexForceZ")->setEnabled(true); + + LLFlexibleObjectData *attributes = (LLFlexibleObjectData *)objectp->getParameterEntry(LLNetworkData::PARAMS_FLEXIBLE); + + getChild("FlexNumSections")->setValue((F32)attributes->getSimulateLOD()); + getChild("FlexGravity")->setValue(attributes->getGravity()); + getChild("FlexTension")->setValue(attributes->getTension()); + getChild("FlexFriction")->setValue(attributes->getAirFriction()); + getChild("FlexWind")->setValue(attributes->getWindSensitivity()); + getChild("FlexForceX")->setValue(attributes->getUserForce().mV[VX]); + getChild("FlexForceY")->setValue(attributes->getUserForce().mV[VY]); + getChild("FlexForceZ")->setValue(attributes->getUserForce().mV[VZ]); + } + else + { + getChild("FlexNumSections", true)->clear(); + getChild("FlexGravity", true)->clear(); + getChild("FlexTension", true)->clear(); + getChild("FlexFriction", true)->clear(); + getChild("FlexWind", true)->clear(); + getChild("FlexForceX", true)->clear(); + getChild("FlexForceY", true)->clear(); + getChild("FlexForceZ", true)->clear(); + + getChildView("FlexNumSections")->setEnabled(false); + getChildView("FlexGravity")->setEnabled(false); + getChildView("FlexTension")->setEnabled(false); + getChildView("FlexFriction")->setEnabled(false); + getChildView("FlexWind")->setEnabled(false); + getChildView("FlexForceX")->setEnabled(false); + getChildView("FlexForceY")->setEnabled(false); + getChildView("FlexForceZ")->setEnabled(false); + } + + // Material properties + + // Update material part + // slightly inefficient - materials are unique per object, not per TE + U8 material_code = 0; + struct f : public LLSelectedTEGetFunctor + { + U8 get(LLViewerObject* object, S32 te) + { + return object->getMaterial(); + } + } func; + bool material_same = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &func, material_code ); + std::string LEGACY_FULLBRIGHT_DESC = LLTrans::getString("Fullbright"); + if (editable && single_volume && material_same) + { + mComboMaterial->setEnabled( true ); + if (material_code == LL_MCODE_LIGHT) + { + if (mComboMaterial->getItemCount() == mComboMaterialItemCount) + { + mComboMaterial->add(LEGACY_FULLBRIGHT_DESC); + } + mComboMaterial->setSimple(LEGACY_FULLBRIGHT_DESC); + } + else + { + if (mComboMaterial->getItemCount() != mComboMaterialItemCount) + { + mComboMaterial->remove(LEGACY_FULLBRIGHT_DESC); + } + + mComboMaterial->setSimple(std::string(LLMaterialTable::basic.getName(material_code))); + } + } + else + { + mComboMaterial->setEnabled( false ); + } + + // Physics properties + + mSpinPhysicsGravity->set(objectp->getPhysicsGravity()); + mSpinPhysicsGravity->setEnabled(editable); + + mSpinPhysicsFriction->set(objectp->getPhysicsFriction()); + mSpinPhysicsFriction->setEnabled(editable); + + mSpinPhysicsDensity->set(objectp->getPhysicsDensity()); + mSpinPhysicsDensity->setEnabled(editable); + + mSpinPhysicsRestitution->set(objectp->getPhysicsRestitution()); + mSpinPhysicsRestitution->setEnabled(editable); + + // update the physics shape combo to include allowed physics shapes + mComboPhysicsShapeType->removeall(); + mComboPhysicsShapeType->add(getString("None"), LLSD(1)); + + bool isMesh = false; + LLSculptParams *sculpt_params = (LLSculptParams *)objectp->getParameterEntry(LLNetworkData::PARAMS_SCULPT); + if (sculpt_params) + { + U8 sculpt_type = sculpt_params->getSculptType(); + U8 sculpt_stitching = sculpt_type & LL_SCULPT_TYPE_MASK; + isMesh = (sculpt_stitching == LL_SCULPT_TYPE_MESH); + } + + if(isMesh && objectp) + { + const LLVolumeParams &volume_params = objectp->getVolume()->getParams(); + LLUUID mesh_id = volume_params.getSculptID(); + if(gMeshRepo.hasPhysicsShape(mesh_id)) + { + // if a mesh contains an uploaded or decomposed physics mesh, + // allow 'Prim' + mComboPhysicsShapeType->add(getString("Prim"), LLSD(0)); + } + } + else + { + // simple prims always allow physics shape prim + mComboPhysicsShapeType->add(getString("Prim"), LLSD(0)); + } + + mComboPhysicsShapeType->add(getString("Convex Hull"), LLSD(2)); + mComboPhysicsShapeType->setValue(LLSD(objectp->getPhysicsShapeType())); + mComboPhysicsShapeType->setEnabled(editable && !objectp->isPermanentEnforced() && ((root_objectp == NULL) || !root_objectp->isPermanentEnforced())); + + mObject = objectp; + mRootObject = root_objectp; + + mMenuClipboardFeatures->setEnabled(editable && single_volume && volobjp); // Note: physics doesn't need to be limited by single volume + mMenuClipboardLight->setEnabled(editable && single_volume && volobjp); +} + +// static +bool LLPanelVolume::precommitValidate( const LLSD& data ) +{ + // TODO: Richard will fill this in later. + return true; // false means that validation failed and new value should not be commited. +} + + +void LLPanelVolume::refresh() +{ + getState(); + if (mObject.notNull() && mObject->isDead()) + { + mObject = NULL; + } + + if (mRootObject.notNull() && mRootObject->isDead()) + { + mRootObject = NULL; + } + + bool enable_mesh = false; + + LLSD sim_features; + LLViewerRegion *region = gAgent.getRegion(); + if(region) + { + LLSD sim_features; + region->getSimulatorFeatures(sim_features); + enable_mesh = sim_features.has("PhysicsShapeTypes"); + } + getChildView("label physicsshapetype")->setVisible(enable_mesh); + getChildView("Physics Shape Type Combo Ctrl")->setVisible(enable_mesh); + getChildView("Physics Gravity")->setVisible(enable_mesh); + getChildView("Physics Friction")->setVisible(enable_mesh); + getChildView("Physics Density")->setVisible(enable_mesh); + getChildView("Physics Restitution")->setVisible(enable_mesh); + + /* TODO: add/remove individual physics shape types as per the PhysicsShapeTypes simulator features */ +} + + +void LLPanelVolume::draw() +{ + LLPanel::draw(); +} + +// virtual +void LLPanelVolume::clearCtrls() +{ + LLPanel::clearCtrls(); + + getChildView("select_single")->setEnabled(false); + getChildView("select_single")->setVisible(true); + getChildView("edit_object")->setEnabled(false); + getChildView("edit_object")->setVisible(false); + getChildView("Light Checkbox Ctrl")->setEnabled(false);; + LLColorSwatchCtrl* LightColorSwatch = getChild("colorswatch"); + if(LightColorSwatch) + { + LightColorSwatch->setEnabled( false ); + LightColorSwatch->setValid( false ); + } + LLTextureCtrl* LightTextureCtrl = getChild("light texture control"); + if(LightTextureCtrl) + { + LightTextureCtrl->setEnabled( false ); + LightTextureCtrl->setValid( false ); + } + + getChildView("Light Intensity")->setEnabled(false); + getChildView("Light Radius")->setEnabled(false); + getChildView("Light Falloff")->setEnabled(false); + + getChildView("Reflection Probe")->setEnabled(false);; + getChildView("Probe Volume Type")->setEnabled(false); + getChildView("Probe Dynamic")->setEnabled(false); + getChildView("Probe Ambiance")->setEnabled(false); + getChildView("Probe Near Clip")->setEnabled(false); + getChildView("Animated Mesh Checkbox Ctrl")->setEnabled(false); + getChildView("Flexible1D Checkbox Ctrl")->setEnabled(false); + getChildView("FlexNumSections")->setEnabled(false); + getChildView("FlexGravity")->setEnabled(false); + getChildView("FlexTension")->setEnabled(false); + getChildView("FlexFriction")->setEnabled(false); + getChildView("FlexWind")->setEnabled(false); + getChildView("FlexForceX")->setEnabled(false); + getChildView("FlexForceY")->setEnabled(false); + getChildView("FlexForceZ")->setEnabled(false); + + mSpinPhysicsGravity->setEnabled(false); + mSpinPhysicsFriction->setEnabled(false); + mSpinPhysicsDensity->setEnabled(false); + mSpinPhysicsRestitution->setEnabled(false); + + mComboMaterial->setEnabled( false ); +} + +// +// Static functions +// + +void LLPanelVolume::sendIsLight() +{ + LLViewerObject* objectp = mObject; + if (!objectp || (objectp->getPCode() != LL_PCODE_VOLUME)) + { + return; + } + LLVOVolume *volobjp = (LLVOVolume *)objectp; + + bool value = getChild("Light Checkbox Ctrl")->getValue(); + volobjp->setIsLight(value); + LL_INFOS() << "update light sent" << LL_ENDL; +} + +void notify_cant_select_reflection_probe() +{ + if (!gSavedSettings.getBOOL("SelectReflectionProbes")) + { + LLNotificationsUtil::add("CantSelectReflectionProbe"); + } +} + +void LLPanelVolume::sendIsReflectionProbe() +{ + LLViewerObject* objectp = mObject; + if (!objectp || (objectp->getPCode() != LL_PCODE_VOLUME)) + { + return; + } + LLVOVolume* volobjp = (LLVOVolume*)objectp; + + bool value = getChild("Reflection Probe")->getValue(); + bool old_value = volobjp->isReflectionProbe(); + + if (value && value != old_value) + { // defer to notification util as to whether or not we *really* make this object a reflection probe + LLNotificationsUtil::add("ReflectionProbeApplied", LLSD(), LLSD(), boost::bind(&LLPanelVolume::doSendIsReflectionProbe, this, _1, _2)); + } + else + { + if (value) + { + notify_cant_select_reflection_probe(); + } + else if (objectp->flagPhantom()) + { + LLViewerObject* root = objectp->getRootEdit(); + bool in_linkeset = root != objectp || objectp->numChildren() > 0; + if (in_linkeset) + { + // In linkset with a phantom flag + objectp->setFlags(FLAGS_PHANTOM, false); + } + } + volobjp->setIsReflectionProbe(value); + } +} + +void LLPanelVolume::doSendIsReflectionProbe(const LLSD & notification, const LLSD & response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // YES + { + LLViewerObject* objectp = mObject; + if (!objectp || (objectp->getPCode() != LL_PCODE_VOLUME)) + { + return; + } + LLVOVolume* volobjp = (LLVOVolume*)objectp; + + notify_cant_select_reflection_probe(); + volobjp->setIsReflectionProbe(true); + + { // has become a reflection probe, slam to a 10m sphere and pop up a message + // warning people about the pitfalls of reflection probes + + auto* select_mgr = LLSelectMgr::getInstance(); + + select_mgr->selectionUpdatePhantom(true); + select_mgr->selectionSetGLTFMaterial(LLUUID::null); + select_mgr->selectionSetAlphaOnly(0.f); + + LLVolumeParams params; + params.getPathParams().setCurveType(LL_PCODE_PATH_CIRCLE); + params.getProfileParams().setCurveType(LL_PCODE_PROFILE_CIRCLE_HALF); + mObject->updateVolume(params); + } + } + else + { + // cancelled, touch up UI state + getChild("Reflection Probe")->setValue(false); + } +} + +void LLPanelVolume::sendIsFlexible() +{ + LLViewerObject* objectp = mObject; + if (!objectp || (objectp->getPCode() != LL_PCODE_VOLUME)) + { + return; + } + LLVOVolume *volobjp = (LLVOVolume *)objectp; + + bool is_flexible = getChild("Flexible1D Checkbox Ctrl")->getValue(); + //bool is_flexible = mCheckFlexible1D->get(); + + if (is_flexible) + { + //LLFirstUse::useFlexible(); + + if (objectp->getClickAction() == CLICK_ACTION_SIT) + { + LLSelectMgr::getInstance()->selectionSetClickAction(CLICK_ACTION_NONE); + } + + } + + if (volobjp->setIsFlexible(is_flexible)) + { + mObject->sendShapeUpdate(); + LLSelectMgr::getInstance()->selectionUpdatePhantom(volobjp->flagPhantom()); + } + + LL_INFOS() << "update flexible sent" << LL_ENDL; +} + +void LLPanelVolume::sendPhysicsShapeType(LLUICtrl* ctrl, void* userdata) +{ + U8 type = ctrl->getValue().asInteger(); + LLSelectMgr::getInstance()->selectionSetPhysicsType(type); + + refreshCost(); +} + +void LLPanelVolume::sendPhysicsGravity(LLUICtrl* ctrl, void* userdata) +{ + F32 val = ctrl->getValue().asReal(); + LLSelectMgr::getInstance()->selectionSetGravity(val); +} + +void LLPanelVolume::sendPhysicsFriction(LLUICtrl* ctrl, void* userdata) +{ + F32 val = ctrl->getValue().asReal(); + LLSelectMgr::getInstance()->selectionSetFriction(val); +} + +void LLPanelVolume::sendPhysicsRestitution(LLUICtrl* ctrl, void* userdata) +{ + F32 val = ctrl->getValue().asReal(); + LLSelectMgr::getInstance()->selectionSetRestitution(val); +} + +void LLPanelVolume::sendPhysicsDensity(LLUICtrl* ctrl, void* userdata) +{ + F32 val = ctrl->getValue().asReal(); + LLSelectMgr::getInstance()->selectionSetDensity(val); +} + +void LLPanelVolume::refreshCost() +{ + LLViewerObject* obj = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); + + if (obj) + { + obj->getObjectCost(); + } +} + +void LLPanelVolume::onLightCancelColor(const LLSD& data) +{ + LLColorSwatchCtrl* LightColorSwatch = getChild("colorswatch"); + if(LightColorSwatch) + { + LightColorSwatch->setColor(mLightSavedColor); + } + onLightSelectColor(data); +} + +void LLPanelVolume::onLightCancelTexture(const LLSD& data) +{ + LLTextureCtrl* LightTextureCtrl = getChild("light texture control"); + LLVOVolume *volobjp = (LLVOVolume *) mObject.get(); + + if (volobjp && LightTextureCtrl) + { + // Cancel the light texture as requested + // NORSPEC-292 + // + // Texture picker triggers cancel both in case of actual cancel and in case of + // selection of "None" texture. + LLUUID tex_id = LightTextureCtrl->getImageAssetID(); + bool is_spotlight = volobjp->isLightSpotlight(); + setLightTextureID(tex_id, LightTextureCtrl->getImageItemID(), volobjp); //updates spotlight + + if (!is_spotlight && tex_id.notNull()) + { + LLVector3 spot_params = volobjp->getSpotLightParams(); + getChild("Light FOV")->setValue(spot_params.mV[0]); + getChild("Light Focus")->setValue(spot_params.mV[1]); + getChild("Light Ambiance")->setValue(spot_params.mV[2]); + } + } +} + +void LLPanelVolume::onLightSelectColor(const LLSD& data) +{ + LLViewerObject* objectp = mObject; + if (!objectp || (objectp->getPCode() != LL_PCODE_VOLUME)) + { + return; + } + LLVOVolume *volobjp = (LLVOVolume *)objectp; + + + LLColorSwatchCtrl* LightColorSwatch = getChild("colorswatch"); + if(LightColorSwatch) + { + LLColor4 clr = LightColorSwatch->get(); + LLColor3 clr3( clr ); + volobjp->setLightSRGBColor(clr3); + mLightSavedColor = clr; + } +} + +void LLPanelVolume::onLightSelectTexture(const LLSD& data) +{ + if (mObject.isNull() || (mObject->getPCode() != LL_PCODE_VOLUME)) + { + return; + } + LLVOVolume *volobjp = (LLVOVolume *) mObject.get(); + + + LLTextureCtrl* LightTextureCtrl = getChild("light texture control"); + if(LightTextureCtrl) + { + LLUUID id = LightTextureCtrl->getImageAssetID(); + setLightTextureID(id, LightTextureCtrl->getImageItemID(), volobjp); + } +} + +void LLPanelVolume::onCopyFeatures() +{ + LLViewerObject* objectp = mObject; + if (!objectp) + { + return; + } + + LLSD clipboard; + + LLVOVolume *volobjp = NULL; + if (objectp && (objectp->getPCode() == LL_PCODE_VOLUME)) + { + volobjp = (LLVOVolume *)objectp; + } + + // Flexi Prim + if (volobjp && volobjp->isFlexible()) + { + LLFlexibleObjectData *attributes = (LLFlexibleObjectData *)objectp->getParameterEntry(LLNetworkData::PARAMS_FLEXIBLE); + if (attributes) + { + clipboard["flex"]["lod"] = attributes->getSimulateLOD(); + clipboard["flex"]["gav"] = attributes->getGravity(); + clipboard["flex"]["ten"] = attributes->getTension(); + clipboard["flex"]["fri"] = attributes->getAirFriction(); + clipboard["flex"]["sen"] = attributes->getWindSensitivity(); + LLVector3 force = attributes->getUserForce(); + clipboard["flex"]["forx"] = force.mV[0]; + clipboard["flex"]["fory"] = force.mV[1]; + clipboard["flex"]["forz"] = force.mV[2]; + } + } + + // Physics + { + clipboard["physics"]["shape"] = objectp->getPhysicsShapeType(); + clipboard["physics"]["gravity"] = objectp->getPhysicsGravity(); + clipboard["physics"]["friction"] = objectp->getPhysicsFriction(); + clipboard["physics"]["density"] = objectp->getPhysicsDensity(); + clipboard["physics"]["restitution"] = objectp->getPhysicsRestitution(); + + U8 material_code = 0; + struct f : public LLSelectedTEGetFunctor + { + U8 get(LLViewerObject* object, S32 te) + { + return object->getMaterial(); + } + } func; + bool material_same = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue(&func, material_code); + // This should always be true since material should be per object. + if (material_same) + { + clipboard["physics"]["material"] = material_code; + } + } + + mClipboardParams["features"] = clipboard; +} + +void LLPanelVolume::onPasteFeatures() +{ + LLViewerObject* objectp = mObject; + if (!objectp && mClipboardParams.has("features")) + { + return; + } + + LLSD &clipboard = mClipboardParams["features"]; + + LLVOVolume *volobjp = NULL; + if (objectp && (objectp->getPCode() == LL_PCODE_VOLUME)) + { + volobjp = (LLVOVolume *)objectp; + } + + // Physics + bool is_root = objectp->isRoot(); + + // Not sure if phantom should go under physics, but doesn't fit elsewhere + bool is_phantom = clipboard["is_phantom"].asBoolean() && is_root; + LLSelectMgr::getInstance()->selectionUpdatePhantom(is_phantom); + + bool is_physical = clipboard["is_physical"].asBoolean() && is_root; + LLSelectMgr::getInstance()->selectionUpdatePhysics(is_physical); + + if (clipboard.has("physics")) + { + objectp->setPhysicsShapeType((U8)clipboard["physics"]["shape"].asInteger()); + U8 cur_material = objectp->getMaterial(); + U8 material = (U8)clipboard["physics"]["material"].asInteger() | (cur_material & ~LL_MCODE_MASK); + + objectp->setMaterial(material); + objectp->sendMaterialUpdate(); + objectp->setPhysicsGravity(clipboard["physics"]["gravity"].asReal()); + objectp->setPhysicsFriction(clipboard["physics"]["friction"].asReal()); + objectp->setPhysicsDensity(clipboard["physics"]["density"].asReal()); + objectp->setPhysicsRestitution(clipboard["physics"]["restitution"].asReal()); + objectp->updateFlags(true); + } + + // Flexible + bool is_flexible = clipboard.has("flex"); + if (is_flexible && volobjp->canBeFlexible()) + { + LLVOVolume *volobjp = (LLVOVolume *)objectp; + bool update_shape = false; + + // do before setParameterEntry or it will think that it is already flexi + update_shape = volobjp->setIsFlexible(is_flexible); + + if (objectp->getClickAction() == CLICK_ACTION_SIT) + { + objectp->setClickAction(CLICK_ACTION_NONE); + } + + LLFlexibleObjectData *attributes = (LLFlexibleObjectData *)objectp->getParameterEntry(LLNetworkData::PARAMS_FLEXIBLE); + if (attributes) + { + LLFlexibleObjectData new_attributes; + new_attributes = *attributes; + new_attributes.setSimulateLOD(clipboard["flex"]["lod"].asInteger()); + new_attributes.setGravity(clipboard["flex"]["gav"].asReal()); + new_attributes.setTension(clipboard["flex"]["ten"].asReal()); + new_attributes.setAirFriction(clipboard["flex"]["fri"].asReal()); + new_attributes.setWindSensitivity(clipboard["flex"]["sen"].asReal()); + F32 fx = (F32)clipboard["flex"]["forx"].asReal(); + F32 fy = (F32)clipboard["flex"]["fory"].asReal(); + F32 fz = (F32)clipboard["flex"]["forz"].asReal(); + LLVector3 force(fx, fy, fz); + new_attributes.setUserForce(force); + objectp->setParameterEntry(LLNetworkData::PARAMS_FLEXIBLE, new_attributes, true); + } + + if (update_shape) + { + mObject->sendShapeUpdate(); + LLSelectMgr::getInstance()->selectionUpdatePhantom(volobjp->flagPhantom()); + } + } + else + { + LLVOVolume *volobjp = (LLVOVolume *)objectp; + if (volobjp->setIsFlexible(false)) + { + mObject->sendShapeUpdate(); + LLSelectMgr::getInstance()->selectionUpdatePhantom(volobjp->flagPhantom()); + } + } +} + +void LLPanelVolume::onCopyLight() +{ + LLViewerObject* objectp = mObject; + if (!objectp) + { + return; + } + + LLSD clipboard; + + LLVOVolume *volobjp = NULL; + if (objectp && (objectp->getPCode() == LL_PCODE_VOLUME)) + { + volobjp = (LLVOVolume *)objectp; + } + + // Light Source + if (volobjp && volobjp->getIsLight()) + { + clipboard["light"]["intensity"] = volobjp->getLightIntensity(); + clipboard["light"]["radius"] = volobjp->getLightRadius(); + clipboard["light"]["falloff"] = volobjp->getLightFalloff(); + LLColor3 color = volobjp->getLightSRGBColor(); + clipboard["light"]["r"] = color.mV[0]; + clipboard["light"]["g"] = color.mV[1]; + clipboard["light"]["b"] = color.mV[2]; + + // Spotlight + if (volobjp->isLightSpotlight()) + { + LLUUID id = volobjp->getLightTextureID(); + if (id.notNull() && get_can_copy_texture(id)) + { + clipboard["spot"]["id"] = id; + LLVector3 spot_params = volobjp->getSpotLightParams(); + clipboard["spot"]["fov"] = spot_params.mV[0]; + clipboard["spot"]["focus"] = spot_params.mV[1]; + clipboard["spot"]["ambiance"] = spot_params.mV[2]; + } + } + } + + if (volobjp && volobjp->isReflectionProbe()) + { + clipboard["reflection_probe"]["is_box"] = volobjp->getReflectionProbeIsBox(); + clipboard["reflection_probe"]["ambiance"] = volobjp->getReflectionProbeAmbiance(); + clipboard["reflection_probe"]["near_clip"] = volobjp->getReflectionProbeNearClip(); + clipboard["reflection_probe"]["dynamic"] = volobjp->getReflectionProbeIsDynamic(); + } + + mClipboardParams["light"] = clipboard; +} + +void LLPanelVolume::onPasteLight() +{ + LLViewerObject* objectp = mObject; + if (!objectp && mClipboardParams.has("light")) + { + return; + } + + LLSD &clipboard = mClipboardParams["light"]; + + LLVOVolume *volobjp = NULL; + if (objectp && (objectp->getPCode() == LL_PCODE_VOLUME)) + { + volobjp = (LLVOVolume *)objectp; + } + + // Light Source + if (volobjp) + { + if (clipboard.has("light")) + { + volobjp->setIsLight(true); + volobjp->setLightIntensity((F32)clipboard["light"]["intensity"].asReal()); + volobjp->setLightRadius((F32)clipboard["light"]["radius"].asReal()); + volobjp->setLightFalloff((F32)clipboard["light"]["falloff"].asReal()); + F32 r = (F32)clipboard["light"]["r"].asReal(); + F32 g = (F32)clipboard["light"]["g"].asReal(); + F32 b = (F32)clipboard["light"]["b"].asReal(); + volobjp->setLightSRGBColor(LLColor3(r, g, b)); + } + else + { + volobjp->setIsLight(false); + } + + if (clipboard.has("spot")) + { + volobjp->setLightTextureID(clipboard["spot"]["id"].asUUID()); + LLVector3 spot_params; + spot_params.mV[0] = (F32)clipboard["spot"]["fov"].asReal(); + spot_params.mV[1] = (F32)clipboard["spot"]["focus"].asReal(); + spot_params.mV[2] = (F32)clipboard["spot"]["ambiance"].asReal(); + volobjp->setSpotLightParams(spot_params); + } + + if (clipboard.has("reflection_probe")) + { + volobjp->setIsReflectionProbe(true); + volobjp->setReflectionProbeIsBox(clipboard["reflection_probe"]["is_box"].asBoolean()); + volobjp->setReflectionProbeAmbiance((F32)clipboard["reflection_probe"]["ambiance"].asReal()); + volobjp->setReflectionProbeNearClip((F32)clipboard["reflection_probe"]["near_clip"].asReal()); + volobjp->setReflectionProbeIsDynamic(clipboard["reflection_probe"]["dynamic"].asBoolean()); + } + else + { + if (objectp->flagPhantom()) + { + LLViewerObject* root = objectp->getRootEdit(); + bool in_linkeset = root != objectp || objectp->numChildren() > 0; + if (in_linkeset) + { + // In linkset with a phantom flag + objectp->setFlags(FLAGS_PHANTOM, false); + } + } + + volobjp->setIsReflectionProbe(false); + } + } +} + +void LLPanelVolume::menuDoToSelected(const LLSD& userdata) +{ + std::string command = userdata.asString(); + + // paste + if (command == "features_paste") + { + onPasteFeatures(); + } + else if (command == "light_paste") + { + onPasteLight(); + } + // copy + else if (command == "features_copy") + { + onCopyFeatures(); + } + else if (command == "light_copy") + { + onCopyLight(); + } +} + +bool LLPanelVolume::menuEnableItem(const LLSD& userdata) +{ + std::string command = userdata.asString(); + + // paste options + if (command == "features_paste") + { + return mClipboardParams.has("features"); + } + else if (command == "light_paste") + { + return mClipboardParams.has("light"); + } + return false; +} + +// static +void LLPanelVolume::onCommitMaterial( LLUICtrl* ctrl, void* userdata ) +{ + LLPanelVolume* self = (LLPanelVolume*)userdata; + LLComboBox* box = (LLComboBox*) ctrl; + + if (box) + { + // apply the currently selected material to the object + const std::string& material_name = box->getSimple(); + std::string LEGACY_FULLBRIGHT_DESC = LLTrans::getString("Fullbright"); + if (material_name != LEGACY_FULLBRIGHT_DESC) + { + U8 material_code = LLMaterialTable::basic.getMCode(material_name); + if (self) + { + LLViewerObject* objectp = self->mObject; + if (objectp) + { + objectp->setPhysicsGravity(DEFAULT_GRAVITY_MULTIPLIER); + objectp->setPhysicsFriction(LLMaterialTable::basic.getFriction(material_code)); + //currently density is always set to 1000 serverside regardless of chosen material, + //actual material density should be used here, if this behavior change + objectp->setPhysicsDensity(DEFAULT_DENSITY); + objectp->setPhysicsRestitution(LLMaterialTable::basic.getRestitution(material_code)); + } + } + LLSelectMgr::getInstance()->selectionSetMaterial(material_code); + } + } +} + +// static +void LLPanelVolume::onCommitLight( LLUICtrl* ctrl, void* userdata ) +{ + LLPanelVolume* self = (LLPanelVolume*) userdata; + LLViewerObject* objectp = self->mObject; + if (!objectp || (objectp->getPCode() != LL_PCODE_VOLUME)) + { + return; + } + LLVOVolume *volobjp = (LLVOVolume *)objectp; + + + volobjp->setLightIntensity((F32)self->getChild("Light Intensity")->getValue().asReal()); + volobjp->setLightRadius((F32)self->getChild("Light Radius")->getValue().asReal()); + volobjp->setLightFalloff((F32)self->getChild("Light Falloff")->getValue().asReal()); + + LLColorSwatchCtrl* LightColorSwatch = self->getChild("colorswatch"); + if(LightColorSwatch) + { + LLColor4 clr = LightColorSwatch->get(); + volobjp->setLightSRGBColor(LLColor3(clr)); + } + + LLTextureCtrl* LightTextureCtrl = self->getChild("light texture control"); + if(LightTextureCtrl) + { + LLUUID id = LightTextureCtrl->getImageAssetID(); + LLUUID item_id = LightTextureCtrl->getImageItemID(); + if (id.notNull()) + { + if (!volobjp->isLightSpotlight()) + { //this commit is making this a spot light, set UI to default params + setLightTextureID(id, item_id, volobjp); + LLVector3 spot_params = volobjp->getSpotLightParams(); + self->getChild("Light FOV")->setValue(spot_params.mV[0]); + self->getChild("Light Focus")->setValue(spot_params.mV[1]); + self->getChild("Light Ambiance")->setValue(spot_params.mV[2]); + } + else + { //modifying existing params, this time volobjp won't change params on its own. + if (volobjp->getLightTextureID() != id) + { + setLightTextureID(id, item_id, volobjp); + } + + LLVector3 spot_params; + spot_params.mV[0] = (F32) self->getChild("Light FOV")->getValue().asReal(); + spot_params.mV[1] = (F32) self->getChild("Light Focus")->getValue().asReal(); + spot_params.mV[2] = (F32) self->getChild("Light Ambiance")->getValue().asReal(); + volobjp->setSpotLightParams(spot_params); + } + } + else if (volobjp->isLightSpotlight()) + { //no longer a spot light + setLightTextureID(id, item_id, volobjp); + //self->getChildView("Light FOV")->setEnabled(false); + //self->getChildView("Light Focus")->setEnabled(false); + //self->getChildView("Light Ambiance")->setEnabled(false); + } + } + + +} + +//static +void LLPanelVolume::onCommitProbe(LLUICtrl* ctrl, void* userdata) +{ + LLPanelVolume* self = (LLPanelVolume*)userdata; + LLViewerObject* objectp = self->mObject; + if (!objectp || (objectp->getPCode() != LL_PCODE_VOLUME)) + { + return; + } + LLVOVolume* volobjp = (LLVOVolume*)objectp; + + volobjp->setReflectionProbeAmbiance((F32)self->getChild("Probe Ambiance")->getValue().asReal()); + volobjp->setReflectionProbeNearClip((F32)self->getChild("Probe Near Clip")->getValue().asReal()); + volobjp->setReflectionProbeIsDynamic(self->getChild("Probe Dynamic")->getValue().asBoolean()); + + std::string shape_type = self->getChild("Probe Volume Type")->getValue().asString(); + + bool is_box = shape_type == "Box"; + + if (volobjp->setReflectionProbeIsBox(is_box)) + { + // make the volume match the probe + auto* select_mgr = LLSelectMgr::getInstance(); + + select_mgr->selectionUpdatePhantom(true); + select_mgr->selectionSetGLTFMaterial(LLUUID::null); + select_mgr->selectionSetAlphaOnly(0.f); + + U8 profile, path; + + if (!is_box) + { + profile = LL_PCODE_PROFILE_CIRCLE_HALF; + path = LL_PCODE_PATH_CIRCLE; + + F32 scale = volobjp->getScale().mV[0]; + volobjp->setScale(LLVector3(scale, scale, scale), false); + LLSelectMgr::getInstance()->sendMultipleUpdate(UPD_ROTATION | UPD_POSITION | UPD_SCALE); + } + else + { + profile = LL_PCODE_PROFILE_SQUARE; + path = LL_PCODE_PATH_LINE; + } + + LLVolumeParams params; + params.getProfileParams().setCurveType(profile); + params.getPathParams().setCurveType(path); + objectp->updateVolume(params); + } + +} + +// static +void LLPanelVolume::onCommitIsLight( LLUICtrl* ctrl, void* userdata ) +{ + LLPanelVolume* self = (LLPanelVolume*) userdata; + self->sendIsLight(); +} + +// static +void LLPanelVolume::setLightTextureID(const LLUUID &asset_id, const LLUUID &item_id, LLVOVolume* volobjp) +{ + if (volobjp) + { + LLViewerInventoryItem* item = gInventory.getItem(item_id); + + if (item && volobjp->isAttachment()) + { + const LLPermissions& perm = item->getPermissions(); + bool unrestricted = (perm.getMaskBase() & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED; + if (!unrestricted) + { + // Attachments are in world and in inventory simultaneously, + // at the moment server doesn't support such a situation. + return; + } + } + + if (item && !item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID())) + { + LLToolDragAndDrop::handleDropMaterialProtections(volobjp, item, LLToolDragAndDrop::SOURCE_AGENT, LLUUID::null); + } + + volobjp->setLightTextureID(asset_id); + } +} +//---------------------------------------------------------------------------- + +// static +void LLPanelVolume::onCommitIsReflectionProbe(LLUICtrl* ctrl, void* userdata) +{ + LLPanelVolume* self = (LLPanelVolume*)userdata; + self->sendIsReflectionProbe(); +} + +//---------------------------------------------------------------------------- + +// static +void LLPanelVolume::onCommitFlexible( LLUICtrl* ctrl, void* userdata ) +{ + LLPanelVolume* self = (LLPanelVolume*) userdata; + LLViewerObject* objectp = self->mObject; + if (!objectp || (objectp->getPCode() != LL_PCODE_VOLUME)) + { + return; + } + + LLFlexibleObjectData *attributes = (LLFlexibleObjectData *)objectp->getParameterEntry(LLNetworkData::PARAMS_FLEXIBLE); + if (attributes) + { + LLFlexibleObjectData new_attributes; + new_attributes = *attributes; + + + new_attributes.setSimulateLOD(self->getChild("FlexNumSections")->getValue().asInteger());//(S32)self->mSpinSections->get()); + new_attributes.setGravity((F32)self->getChild("FlexGravity")->getValue().asReal()); + new_attributes.setTension((F32)self->getChild("FlexTension")->getValue().asReal()); + new_attributes.setAirFriction((F32)self->getChild("FlexFriction")->getValue().asReal()); + new_attributes.setWindSensitivity((F32)self->getChild("FlexWind")->getValue().asReal()); + F32 fx = (F32)self->getChild("FlexForceX")->getValue().asReal(); + F32 fy = (F32)self->getChild("FlexForceY")->getValue().asReal(); + F32 fz = (F32)self->getChild("FlexForceZ")->getValue().asReal(); + LLVector3 force(fx,fy,fz); + + new_attributes.setUserForce(force); + objectp->setParameterEntry(LLNetworkData::PARAMS_FLEXIBLE, new_attributes, true); + } + + // Values may fail validation + self->refresh(); +} + +void LLPanelVolume::onCommitAnimatedMeshCheckbox(LLUICtrl *, void*) +{ + LLViewerObject* objectp = mObject; + if (!objectp || (objectp->getPCode() != LL_PCODE_VOLUME)) + { + return; + } + LLVOVolume *volobjp = (LLVOVolume *)objectp; + bool animated_mesh = getChild("Animated Mesh Checkbox Ctrl")->getValue(); + U32 flags = volobjp->getExtendedMeshFlags(); + U32 new_flags = flags; + if (animated_mesh) + { + new_flags |= LLExtendedMeshParams::ANIMATED_MESH_ENABLED_FLAG; + } + else + { + new_flags &= ~LLExtendedMeshParams::ANIMATED_MESH_ENABLED_FLAG; + } + if (new_flags != flags) + { + volobjp->setExtendedMeshFlags(new_flags); + } + + //refresh any bakes + if (volobjp) + { + volobjp->refreshBakeTexture(); + + LLViewerObject::const_child_list_t& child_list = volobjp->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); ++iter) + { + LLViewerObject* objectp = *iter; + if (objectp) + { + objectp->refreshBakeTexture(); + } + } + + if (gAgentAvatarp) + { + gAgentAvatarp->updateMeshVisibility(); + } + } +} + +void LLPanelVolume::onCommitIsFlexible(LLUICtrl *, void*) +{ + if (mObject->flagObjectPermanent()) + { + LLNotificationsUtil::add("PathfindingLinksets_ChangeToFlexiblePath", LLSD(), LLSD(), boost::bind(&LLPanelVolume::handleResponseChangeToFlexible, this, _1, _2)); + } + else + { + sendIsFlexible(); + } +} + +void LLPanelVolume::handleResponseChangeToFlexible(const LLSD &pNotification, const LLSD &pResponse) +{ + if (LLNotificationsUtil::getSelectedOption(pNotification, pResponse) == 0) + { + sendIsFlexible(); + } + else + { + getChild("Flexible1D Checkbox Ctrl")->setValue(false); + } +} diff --git a/indra/newview/llpanelvolume.h b/indra/newview/llpanelvolume.h index f7b7f92f6c..7e31a3135b 100644 --- a/indra/newview/llpanelvolume.h +++ b/indra/newview/llpanelvolume.h @@ -1,151 +1,151 @@ -/** - * @file llpanelvolume.h - * @brief Object editing (position, scale, etc.) in the tools floater - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELVOLUME_H -#define LL_LLPANELVOLUME_H - -#include "v3math.h" -#include "llpanel.h" -#include "llpointer.h" -#include "llvolume.h" - -class LLSpinCtrl; -class LLCheckBoxCtrl; -class LLTextBox; -class LLUICtrl; -class LLButton; -class LLMenuButton; -class LLViewerObject; -class LLComboBox; -class LLColorSwatchCtrl; -class LLVOVolume; - -class LLPanelVolume : public LLPanel -{ -public: - LLPanelVolume(); - virtual ~LLPanelVolume(); - - virtual void draw(); - virtual void clearCtrls(); - - virtual bool postBuild(); - - void refresh(); - - void sendIsLight(); - - // when an object is becoming a refleciton probe, present a dialog asking for confirmation - // otherwise, send the reflection probe update immediately - void sendIsReflectionProbe(); - - // callback for handling response of the ok/cancel/ignore dialog for making an object a reflection probe - void doSendIsReflectionProbe(const LLSD& notification, const LLSD& response); - - void sendIsFlexible(); - - static bool precommitValidate(const LLSD& data); - - static void onCommitIsLight( LLUICtrl* ctrl, void* userdata); - static void onCommitLight( LLUICtrl* ctrl, void* userdata); - static void onCommitIsReflectionProbe(LLUICtrl* ctrl, void* userdata); - static void onCommitProbe(LLUICtrl* ctrl, void* userdata); - void onCommitIsFlexible( LLUICtrl* ctrl, void* userdata); - static void onCommitFlexible( LLUICtrl* ctrl, void* userdata); - void onCommitAnimatedMeshCheckbox(LLUICtrl* ctrl, void* userdata); - static void onCommitPhysicsParam( LLUICtrl* ctrl, void* userdata); - static void onCommitMaterial( LLUICtrl* ctrl, void* userdata); - - void onLightCancelColor(const LLSD& data); - void onLightSelectColor(const LLSD& data); - - void onLightCancelTexture(const LLSD& data); - void onLightSelectTexture(const LLSD& data); - - static void setLightTextureID(const LLUUID &asset_id, const LLUUID &item_id, LLVOVolume* volobjp); - - void onCopyFeatures(); - void onPasteFeatures(); - void onCopyLight(); - void onPasteLight(); - - void menuDoToSelected(const LLSD& userdata); - bool menuEnableItem(const LLSD& userdata); - -protected: - void getState(); - void refreshCost(); - -protected: - void sendPhysicsShapeType(LLUICtrl* ctrl, void* userdata); - void sendPhysicsGravity(LLUICtrl* ctrl, void* userdata); - void sendPhysicsFriction(LLUICtrl* ctrl, void* userdata); - void sendPhysicsRestitution(LLUICtrl* ctrl, void* userdata); - void sendPhysicsDensity(LLUICtrl* ctrl, void* userdata); - - void handleResponseChangeToFlexible(const LLSD &pNotification, const LLSD &pResponse); - -/* - LLTextBox* mLabelSelectSingleMessage; - // Light - LLCheckBoxCtrl* mCheckLight; - LLCheckBoxCtrl* mCheckFlexible1D; - LLTextBox* mLabelColor; - LLColorSwatchCtrl* mLightColorSwatch; - LLSpinCtrl* mLightIntensity; - LLSpinCtrl* mLightRadius; - LLSpinCtrl* mLightFalloff; - LLSpinCtrl* mLightCutoff; - // Flexibile - LLSpinCtrl* mSpinSections; - LLSpinCtrl* mSpinGravity; - LLSpinCtrl* mSpinTension; - LLSpinCtrl* mSpinFriction; - LLSpinCtrl* mSpinWind; - LLSpinCtrl* mSpinForce[3]; -*/ - - S32 mComboMaterialItemCount; - LLComboBox* mComboMaterial; - - - LLColor4 mLightSavedColor; - LLPointer mObject; - LLPointer mRootObject; - - LLComboBox* mComboPhysicsShapeType; - LLSpinCtrl* mSpinPhysicsGravity; - LLSpinCtrl* mSpinPhysicsFriction; - LLSpinCtrl* mSpinPhysicsDensity; - LLSpinCtrl* mSpinPhysicsRestitution; - - LLMenuButton* mMenuClipboardFeatures; - LLMenuButton* mMenuClipboardLight; - - LLSD mClipboardParams; -}; - -#endif +/** + * @file llpanelvolume.h + * @brief Object editing (position, scale, etc.) in the tools floater + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELVOLUME_H +#define LL_LLPANELVOLUME_H + +#include "v3math.h" +#include "llpanel.h" +#include "llpointer.h" +#include "llvolume.h" + +class LLSpinCtrl; +class LLCheckBoxCtrl; +class LLTextBox; +class LLUICtrl; +class LLButton; +class LLMenuButton; +class LLViewerObject; +class LLComboBox; +class LLColorSwatchCtrl; +class LLVOVolume; + +class LLPanelVolume : public LLPanel +{ +public: + LLPanelVolume(); + virtual ~LLPanelVolume(); + + virtual void draw(); + virtual void clearCtrls(); + + virtual bool postBuild(); + + void refresh(); + + void sendIsLight(); + + // when an object is becoming a refleciton probe, present a dialog asking for confirmation + // otherwise, send the reflection probe update immediately + void sendIsReflectionProbe(); + + // callback for handling response of the ok/cancel/ignore dialog for making an object a reflection probe + void doSendIsReflectionProbe(const LLSD& notification, const LLSD& response); + + void sendIsFlexible(); + + static bool precommitValidate(const LLSD& data); + + static void onCommitIsLight( LLUICtrl* ctrl, void* userdata); + static void onCommitLight( LLUICtrl* ctrl, void* userdata); + static void onCommitIsReflectionProbe(LLUICtrl* ctrl, void* userdata); + static void onCommitProbe(LLUICtrl* ctrl, void* userdata); + void onCommitIsFlexible( LLUICtrl* ctrl, void* userdata); + static void onCommitFlexible( LLUICtrl* ctrl, void* userdata); + void onCommitAnimatedMeshCheckbox(LLUICtrl* ctrl, void* userdata); + static void onCommitPhysicsParam( LLUICtrl* ctrl, void* userdata); + static void onCommitMaterial( LLUICtrl* ctrl, void* userdata); + + void onLightCancelColor(const LLSD& data); + void onLightSelectColor(const LLSD& data); + + void onLightCancelTexture(const LLSD& data); + void onLightSelectTexture(const LLSD& data); + + static void setLightTextureID(const LLUUID &asset_id, const LLUUID &item_id, LLVOVolume* volobjp); + + void onCopyFeatures(); + void onPasteFeatures(); + void onCopyLight(); + void onPasteLight(); + + void menuDoToSelected(const LLSD& userdata); + bool menuEnableItem(const LLSD& userdata); + +protected: + void getState(); + void refreshCost(); + +protected: + void sendPhysicsShapeType(LLUICtrl* ctrl, void* userdata); + void sendPhysicsGravity(LLUICtrl* ctrl, void* userdata); + void sendPhysicsFriction(LLUICtrl* ctrl, void* userdata); + void sendPhysicsRestitution(LLUICtrl* ctrl, void* userdata); + void sendPhysicsDensity(LLUICtrl* ctrl, void* userdata); + + void handleResponseChangeToFlexible(const LLSD &pNotification, const LLSD &pResponse); + +/* + LLTextBox* mLabelSelectSingleMessage; + // Light + LLCheckBoxCtrl* mCheckLight; + LLCheckBoxCtrl* mCheckFlexible1D; + LLTextBox* mLabelColor; + LLColorSwatchCtrl* mLightColorSwatch; + LLSpinCtrl* mLightIntensity; + LLSpinCtrl* mLightRadius; + LLSpinCtrl* mLightFalloff; + LLSpinCtrl* mLightCutoff; + // Flexibile + LLSpinCtrl* mSpinSections; + LLSpinCtrl* mSpinGravity; + LLSpinCtrl* mSpinTension; + LLSpinCtrl* mSpinFriction; + LLSpinCtrl* mSpinWind; + LLSpinCtrl* mSpinForce[3]; +*/ + + S32 mComboMaterialItemCount; + LLComboBox* mComboMaterial; + + + LLColor4 mLightSavedColor; + LLPointer mObject; + LLPointer mRootObject; + + LLComboBox* mComboPhysicsShapeType; + LLSpinCtrl* mSpinPhysicsGravity; + LLSpinCtrl* mSpinPhysicsFriction; + LLSpinCtrl* mSpinPhysicsDensity; + LLSpinCtrl* mSpinPhysicsRestitution; + + LLMenuButton* mMenuClipboardFeatures; + LLMenuButton* mMenuClipboardLight; + + LLSD mClipboardParams; +}; + +#endif diff --git a/indra/newview/llpanelvolumepulldown.cpp b/indra/newview/llpanelvolumepulldown.cpp index b08cd0c1cb..046bcd7f59 100644 --- a/indra/newview/llpanelvolumepulldown.cpp +++ b/indra/newview/llpanelvolumepulldown.cpp @@ -1,117 +1,117 @@ -/** - * @file llpanelvolumepulldown.cpp - * @author Tofu Linden - * @brief A floater showing the master volume pull-down - * - * $LicenseInfo:firstyear=2008&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelvolumepulldown.h" - -// Viewer libs -#include "llviewercontrol.h" -#include "llstatusbar.h" - -// Linden libs -#include "llbutton.h" -#include "llcheckboxctrl.h" -#include "lltabcontainer.h" -#include "llfloaterreg.h" -#include "llfloaterpreference.h" -#include "llsliderctrl.h" - -///---------------------------------------------------------------------------- -/// Class LLPanelVolumePulldown -///---------------------------------------------------------------------------- - -// Default constructor -LLPanelVolumePulldown::LLPanelVolumePulldown() -{ - mCommitCallbackRegistrar.add("Vol.setControlFalse", boost::bind(&LLPanelVolumePulldown::setControlFalse, this, _2)); - mCommitCallbackRegistrar.add("Vol.SetSounds", boost::bind(&LLPanelVolumePulldown::onClickSetSounds, this)); - mCommitCallbackRegistrar.add("Vol.updateCheckbox", boost::bind(&LLPanelVolumePulldown::updateCheckbox, this, _1, _2)); - mCommitCallbackRegistrar.add("Vol.GoAudioPrefs", boost::bind(&LLPanelVolumePulldown::onAdvancedButtonClick, this, _2)); - buildFromFile( "panel_volume_pulldown.xml"); -} - -bool LLPanelVolumePulldown::postBuild() -{ - return LLPanelPulldown::postBuild(); -} - -void LLPanelVolumePulldown::onAdvancedButtonClick(const LLSD& user_data) -{ - // close the global volume minicontrol, we're bringing up the big one - setVisible(false); - - // bring up the prefs floater - LLFloaterPreference* prefsfloater = dynamic_cast - (LLFloaterReg::showInstance("preferences")); - if (prefsfloater) - { - // grab the 'audio' panel from the preferences floater and - // bring it the front! - LLTabContainer* tabcontainer = prefsfloater->getChild("pref core"); - LLPanel* audiopanel = prefsfloater->getChild("audio"); - if (tabcontainer && audiopanel) - { - tabcontainer->selectTabPanel(audiopanel); - } - } -} - -void LLPanelVolumePulldown::setControlFalse(const LLSD& user_data) -{ - std::string control_name = user_data.asString(); - LLControlVariable* control = findControl(control_name); - - if (control) - control->set(LLSD(false)); -} - -void LLPanelVolumePulldown::updateCheckbox(LLUICtrl* ctrl, const LLSD& user_data) -{ - std::string control_name = user_data.asString(); - if (control_name == "MediaAutoPlay") - { - std::string name = ctrl->getName(); - - // Disable "Allow Media to auto play" only when both - // "Streaming Music" and "Media" are unchecked. STORM-513. - if ((name == "enable_music") || (name == "enable_media")) - { - bool music_enabled = getChild("enable_music")->get(); - bool media_enabled = getChild("enable_media")->get(); - - getChild("media_auto_play_combo")->setEnabled(music_enabled || media_enabled); - } - } -} - -void LLPanelVolumePulldown::onClickSetSounds() -{ - // Disable Enable gesture sounds checkbox if the master sound is disabled - // or if sound effects are disabled. - getChild("gesture_audio_play_btn")->setEnabled(!gSavedSettings.getBOOL("MuteSounds")); -} +/** + * @file llpanelvolumepulldown.cpp + * @author Tofu Linden + * @brief A floater showing the master volume pull-down + * + * $LicenseInfo:firstyear=2008&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelvolumepulldown.h" + +// Viewer libs +#include "llviewercontrol.h" +#include "llstatusbar.h" + +// Linden libs +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "lltabcontainer.h" +#include "llfloaterreg.h" +#include "llfloaterpreference.h" +#include "llsliderctrl.h" + +///---------------------------------------------------------------------------- +/// Class LLPanelVolumePulldown +///---------------------------------------------------------------------------- + +// Default constructor +LLPanelVolumePulldown::LLPanelVolumePulldown() +{ + mCommitCallbackRegistrar.add("Vol.setControlFalse", boost::bind(&LLPanelVolumePulldown::setControlFalse, this, _2)); + mCommitCallbackRegistrar.add("Vol.SetSounds", boost::bind(&LLPanelVolumePulldown::onClickSetSounds, this)); + mCommitCallbackRegistrar.add("Vol.updateCheckbox", boost::bind(&LLPanelVolumePulldown::updateCheckbox, this, _1, _2)); + mCommitCallbackRegistrar.add("Vol.GoAudioPrefs", boost::bind(&LLPanelVolumePulldown::onAdvancedButtonClick, this, _2)); + buildFromFile( "panel_volume_pulldown.xml"); +} + +bool LLPanelVolumePulldown::postBuild() +{ + return LLPanelPulldown::postBuild(); +} + +void LLPanelVolumePulldown::onAdvancedButtonClick(const LLSD& user_data) +{ + // close the global volume minicontrol, we're bringing up the big one + setVisible(false); + + // bring up the prefs floater + LLFloaterPreference* prefsfloater = dynamic_cast + (LLFloaterReg::showInstance("preferences")); + if (prefsfloater) + { + // grab the 'audio' panel from the preferences floater and + // bring it the front! + LLTabContainer* tabcontainer = prefsfloater->getChild("pref core"); + LLPanel* audiopanel = prefsfloater->getChild("audio"); + if (tabcontainer && audiopanel) + { + tabcontainer->selectTabPanel(audiopanel); + } + } +} + +void LLPanelVolumePulldown::setControlFalse(const LLSD& user_data) +{ + std::string control_name = user_data.asString(); + LLControlVariable* control = findControl(control_name); + + if (control) + control->set(LLSD(false)); +} + +void LLPanelVolumePulldown::updateCheckbox(LLUICtrl* ctrl, const LLSD& user_data) +{ + std::string control_name = user_data.asString(); + if (control_name == "MediaAutoPlay") + { + std::string name = ctrl->getName(); + + // Disable "Allow Media to auto play" only when both + // "Streaming Music" and "Media" are unchecked. STORM-513. + if ((name == "enable_music") || (name == "enable_media")) + { + bool music_enabled = getChild("enable_music")->get(); + bool media_enabled = getChild("enable_media")->get(); + + getChild("media_auto_play_combo")->setEnabled(music_enabled || media_enabled); + } + } +} + +void LLPanelVolumePulldown::onClickSetSounds() +{ + // Disable Enable gesture sounds checkbox if the master sound is disabled + // or if sound effects are disabled. + getChild("gesture_audio_play_btn")->setEnabled(!gSavedSettings.getBOOL("MuteSounds")); +} diff --git a/indra/newview/llpanelvolumepulldown.h b/indra/newview/llpanelvolumepulldown.h index 055986dbe4..2ebac06972 100644 --- a/indra/newview/llpanelvolumepulldown.h +++ b/indra/newview/llpanelvolumepulldown.h @@ -1,51 +1,51 @@ -/** - * @file llpanelvolumepulldown.h - * @author Tofu Linden - * @brief A panel showing the master volume pull-down - * - * $LicenseInfo:firstyear=2008&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELVOLUMEPULLDOWN_H -#define LL_LLPANELVOLUMEPULLDOWN_H - -#include "linden_common.h" - -#include "llpanelpulldown.h" - -class LLPanelVolumePulldown : public LLPanelPulldown -{ - public: - LLPanelVolumePulldown(); - bool postBuild() override; - - private: - void setControlFalse(const LLSD& user_data); - void onClickSetSounds(); - // Disables "Allow Media to auto play" check box only when both - // "Streaming Music" and "Media" are unchecked. Otherwise enables it. - void updateCheckbox(LLUICtrl* ctrl, const LLSD& user_data); - void onAdvancedButtonClick(const LLSD& user_data); -}; - - -#endif // LL_LLPANELVOLUMEPULLDOWN_H +/** + * @file llpanelvolumepulldown.h + * @author Tofu Linden + * @brief A panel showing the master volume pull-down + * + * $LicenseInfo:firstyear=2008&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELVOLUMEPULLDOWN_H +#define LL_LLPANELVOLUMEPULLDOWN_H + +#include "linden_common.h" + +#include "llpanelpulldown.h" + +class LLPanelVolumePulldown : public LLPanelPulldown +{ + public: + LLPanelVolumePulldown(); + bool postBuild() override; + + private: + void setControlFalse(const LLSD& user_data); + void onClickSetSounds(); + // Disables "Allow Media to auto play" check box only when both + // "Streaming Music" and "Media" are unchecked. Otherwise enables it. + void updateCheckbox(LLUICtrl* ctrl, const LLSD& user_data); + void onAdvancedButtonClick(const LLSD& user_data); +}; + + +#endif // LL_LLPANELVOLUMEPULLDOWN_H diff --git a/indra/newview/llpanelwearing.cpp b/indra/newview/llpanelwearing.cpp index df7ce40ac5..537bd0d303 100644 --- a/indra/newview/llpanelwearing.cpp +++ b/indra/newview/llpanelwearing.cpp @@ -1,599 +1,599 @@ -/** - * @file llpanelwearing.cpp - * @brief List of agent's worn items. - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpanelwearing.h" - -#include "lltoggleablemenu.h" - -#include "llagent.h" -#include "llaccordionctrl.h" -#include "llaccordionctrltab.h" -#include "llappearancemgr.h" -#include "llfloatersidepanelcontainer.h" -#include "llinventoryfunctions.h" -#include "llinventoryicon.h" -#include "llinventorymodel.h" -#include "llinventoryobserver.h" -#include "llmenubutton.h" -#include "lloutfitobserver.h" -#include "llscrolllistctrl.h" -#include "llviewermenu.h" -#include "llviewerregion.h" -#include "llwearableitemslist.h" -#include "llsdserialize.h" -#include "llclipboard.h" - -// Context menu and Gear menu helper. -static void edit_outfit() -{ - LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "edit_outfit")); -} - -////////////////////////////////////////////////////////////////////////// - -class LLWearingGearMenu -{ -public: - LLWearingGearMenu(LLPanelWearing* panel_wearing) - : mMenu(NULL), mPanelWearing(panel_wearing) - { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; - - registrar.add("Gear.TouchAttach", boost::bind(&LLWearingGearMenu::handleMultiple, this, handle_attachment_touch)); - registrar.add("Gear.EditItem", boost::bind(&LLWearingGearMenu::handleMultiple, this, handle_item_edit)); - registrar.add("Gear.EditOutfit", boost::bind(&edit_outfit)); - registrar.add("Gear.TakeOff", boost::bind(&LLPanelWearing::onRemoveItem, mPanelWearing)); - registrar.add("Gear.Copy", boost::bind(&LLPanelWearing::copyToClipboard, mPanelWearing)); - - enable_registrar.add("Gear.OnEnable", boost::bind(&LLPanelWearing::isActionEnabled, mPanelWearing, _2)); - - mMenu = LLUICtrlFactory::getInstance()->createFromFile( - "menu_wearing_gear.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - llassert(mMenu); - } - - LLToggleableMenu* getMenu() { return mMenu; } - -private: - void handleMultiple(std::function functor) - { - uuid_vec_t selected_item_ids; - mPanelWearing->getSelectedItemsUUIDs(selected_item_ids); - - for (const LLUUID& item_id : selected_item_ids) - { - functor(item_id); - } - } - - LLToggleableMenu* mMenu; - LLPanelWearing* mPanelWearing; -}; - -////////////////////////////////////////////////////////////////////////// - -class LLWearingContextMenu : public LLListContextMenu -{ -protected: - /* virtual */ LLContextMenu* createMenu() - { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - - registrar.add("Wearing.TouchAttach", boost::bind(handleMultiple, handle_attachment_touch, mUUIDs)); - registrar.add("Wearing.EditItem", boost::bind(handleMultiple, handle_item_edit, mUUIDs)); - registrar.add("Wearing.EditOutfit", boost::bind(&edit_outfit)); - registrar.add("Wearing.ShowOriginal", boost::bind(show_item_original, mUUIDs.front())); - registrar.add("Wearing.TakeOff", - boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), mUUIDs, no_op)); - registrar.add("Wearing.Detach", - boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), mUUIDs, no_op)); - LLContextMenu* menu = createFromFile("menu_wearing_tab.xml"); - - updateMenuItemsVisibility(menu); - - return menu; - } - - void updateMenuItemsVisibility(LLContextMenu* menu) - { - bool bp_selected = false; // true if body parts selected - bool clothes_selected = false; - bool attachments_selected = false; - - // See what types of wearables are selected. - for (uuid_vec_t::const_iterator it = mUUIDs.begin(); it != mUUIDs.end(); ++it) - { - LLViewerInventoryItem* item = gInventory.getItem(*it); - - if (!item) - { - LL_WARNS() << "Invalid item" << LL_ENDL; - continue; - } - - LLAssetType::EType type = item->getType(); - if (type == LLAssetType::AT_CLOTHING) - { - clothes_selected = true; - } - else if (type == LLAssetType::AT_BODYPART) - { - bp_selected = true; - } - else if (type == LLAssetType::AT_OBJECT || type == LLAssetType::AT_GESTURE) - { - attachments_selected = true; - } - } - - // Enable/disable some menu items depending on the selection. - bool show_touch = !bp_selected && !clothes_selected && attachments_selected; - bool show_edit = bp_selected || clothes_selected || attachments_selected; - bool allow_detach = !bp_selected && !clothes_selected && attachments_selected; - bool allow_take_off = !bp_selected && clothes_selected && !attachments_selected; - - menu->setItemVisible("touch_attach", show_touch); - menu->setItemEnabled("touch_attach", 1 == mUUIDs.size() && enable_attachment_touch(mUUIDs.front())); - menu->setItemVisible("edit_item", show_edit); - menu->setItemEnabled("edit_item", 1 == mUUIDs.size() && get_is_item_editable(mUUIDs.front())); - menu->setItemVisible("take_off", allow_take_off); - menu->setItemVisible("detach", allow_detach); - menu->setItemVisible("edit_outfit_separator", show_touch | show_edit | allow_take_off || allow_detach); - menu->setItemVisible("show_original", mUUIDs.size() == 1); - } -}; - -////////////////////////////////////////////////////////////////////////// - -class LLTempAttachmentsContextMenu : public LLListContextMenu -{ -public: - LLTempAttachmentsContextMenu(LLPanelWearing* panel_wearing) - : mPanelWearing(panel_wearing) - {} -protected: - /* virtual */ LLContextMenu* createMenu() - { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - - registrar.add("Wearing.EditItem", boost::bind(&LLPanelWearing::onEditAttachment, mPanelWearing)); - registrar.add("Wearing.Detach", boost::bind(&LLPanelWearing::onRemoveAttachment, mPanelWearing)); - LLContextMenu* menu = createFromFile("menu_wearing_tab.xml"); - - updateMenuItemsVisibility(menu); - - return menu; - } - - void updateMenuItemsVisibility(LLContextMenu* menu) - { - menu->setItemVisible("touch_attach", true); - menu->setItemEnabled("touch_attach", 1 == mUUIDs.size()); - menu->setItemVisible("edit_item", true); - menu->setItemEnabled("edit_item", 1 == mUUIDs.size()); - menu->setItemVisible("take_off", false); - menu->setItemVisible("detach", true); - menu->setItemVisible("edit_outfit_separator", false); - menu->setItemVisible("show_original", false); - menu->setItemVisible("edit_outfit", false); - } - - LLPanelWearing* mPanelWearing; -}; - -////////////////////////////////////////////////////////////////////////// - -static LLPanelInjector t_panel_wearing("panel_wearing"); - -LLPanelWearing::LLPanelWearing() - : LLPanelAppearanceTab() - , mCOFItemsList(NULL) - , mIsInitialized(false) - , mAttachmentsChangedConnection() -{ - mGearMenu = new LLWearingGearMenu(this); - mContextMenu = new LLWearingContextMenu(); - mAttachmentsMenu = new LLTempAttachmentsContextMenu(this); -} - -LLPanelWearing::~LLPanelWearing() -{ - delete mGearMenu; - delete mContextMenu; - delete mAttachmentsMenu; - - if (mAttachmentsChangedConnection.connected()) - { - mAttachmentsChangedConnection.disconnect(); - } -} - -bool LLPanelWearing::postBuild() -{ - mAccordionCtrl = getChild("wearables_accordion"); - mWearablesTab = getChild("tab_wearables"); - mWearablesTab->setIgnoreResizeNotification(true); - mAttachmentsTab = getChild("tab_temp_attachments"); - mAttachmentsTab->setDropDownStateChangedCallback(boost::bind(&LLPanelWearing::onAccordionTabStateChanged, this)); - - mCOFItemsList = getChild("cof_items_list"); - mCOFItemsList->setRightMouseDownCallback(boost::bind(&LLPanelWearing::onWearableItemsListRightClick, this, _1, _2, _3)); - - mTempItemsList = getChild("temp_attachments_list"); - mTempItemsList->setFgUnselectedColor(LLColor4::white); - mTempItemsList->setRightMouseDownCallback(boost::bind(&LLPanelWearing::onTempAttachmentsListRightClick, this, _1, _2, _3)); - - LLMenuButton* menu_gear_btn = getChild("options_gear_btn"); - - menu_gear_btn->setMenu(mGearMenu->getMenu()); - - return true; -} - -//virtual -void LLPanelWearing::onOpen(const LLSD& /*info*/) -{ - if (!mIsInitialized) - { - // *TODO: I'm not sure is this check necessary but it never match while developing. - if (!gInventory.isInventoryUsable()) - return; - - const LLUUID cof = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); - - // *TODO: I'm not sure is this check necessary but it never match while developing. - LLViewerInventoryCategory* category = gInventory.getCategory(cof); - if (!category) - return; - - // Start observing changes in Current Outfit category. - LLOutfitObserver::instance().addCOFChangedCallback(boost::bind(&LLWearableItemsList::updateList, mCOFItemsList, cof)); - - // Fetch Current Outfit contents and refresh the list to display - // initially fetched items. If not all items are fetched now - // the observer will refresh the list as soon as the new items - // arrive. - category->fetch(); - - mCOFItemsList->updateList(cof); - - mIsInitialized = true; - } -} - -void LLPanelWearing::draw() -{ - if (mUpdateTimer.getStarted() && (mUpdateTimer.getElapsedTimeF32() > 0.1)) - { - mUpdateTimer.stop(); - updateAttachmentsList(); - } - LLPanel::draw(); -} - -void LLPanelWearing::onAccordionTabStateChanged() -{ - if(mAttachmentsTab->isExpanded()) - { - startUpdateTimer(); - mAttachmentsChangedConnection = LLAppearanceMgr::instance().setAttachmentsChangedCallback(boost::bind(&LLPanelWearing::startUpdateTimer, this)); - } - else - { - if (mAttachmentsChangedConnection.connected()) - { - mAttachmentsChangedConnection.disconnect(); - } - } -} - -void LLPanelWearing::startUpdateTimer() -{ - if (!mUpdateTimer.getStarted()) - { - mUpdateTimer.start(); - } - else - { - mUpdateTimer.reset(); - } -} - -// virtual -void LLPanelWearing::onFilterSubStringChanged(const std::string& new_string, const std::string& old_string) -{ - mCOFItemsList->setFilterSubString(new_string, true); - - mAccordionCtrl->arrange(); -} - -// virtual -bool LLPanelWearing::isActionEnabled(const LLSD& userdata) -{ - const std::string command_name = userdata.asString(); - - if (command_name == "save_outfit") - { - bool outfit_locked = LLAppearanceMgr::getInstance()->isOutfitLocked(); - bool outfit_dirty = LLAppearanceMgr::getInstance()->isOutfitDirty(); - // allow save only if outfit isn't locked and is dirty - return !outfit_locked && outfit_dirty; - } - - if (command_name == "take_off") - { - if (mWearablesTab->isExpanded()) - { - return hasItemSelected() && canTakeOffSelected(); - } - else - { - LLScrollListItem* item = mTempItemsList->getFirstSelected(); - if (item && item->getUUID().notNull()) - { - return true; - } - } - } - - uuid_vec_t selected_uuids; - getSelectedItemsUUIDs(selected_uuids); - - if (command_name == "touch_attach") - { - return (1 == selected_uuids.size()) && (enable_attachment_touch(selected_uuids.front())); - } - else if (command_name == "edit_item") - { - return (1 == selected_uuids.size()) && (get_is_item_editable(selected_uuids.front())); - } - - return false; -} - -void LLPanelWearing::updateAttachmentsList() -{ - std::vector attachs = LLAgentWearables::getTempAttachments(); - mTempItemsList->deleteAllItems(); - mAttachmentsMap.clear(); - if(!attachs.empty()) - { - if(!populateAttachmentsList()) - { - requestAttachmentDetails(); - } - } - else - { - std::string no_attachments = getString("no_attachments"); - LLSD row; - row["columns"][0]["column"] = "text"; - row["columns"][0]["value"] = no_attachments; - row["columns"][0]["font"] = "SansSerifBold"; - mTempItemsList->addElement(row); - } -} - -bool LLPanelWearing::populateAttachmentsList(bool update) -{ - bool populated = true; - if(mTempItemsList) - { - mTempItemsList->deleteAllItems(); - mAttachmentsMap.clear(); - std::vector attachs = LLAgentWearables::getTempAttachments(); - - std::string icon_name = LLInventoryIcon::getIconName(LLAssetType::AT_OBJECT, LLInventoryType::IT_OBJECT); - for (std::vector::iterator iter = attachs.begin(); - iter != attachs.end(); ++iter) - { - LLViewerObject *attachment = *iter; - LLSD row; - row["id"] = attachment->getID(); - row["columns"][0]["column"] = "icon"; - row["columns"][0]["type"] = "icon"; - row["columns"][0]["value"] = icon_name; - row["columns"][1]["column"] = "text"; - if(mObjectNames.count(attachment->getID()) && !mObjectNames[attachment->getID()].empty()) - { - row["columns"][1]["value"] = mObjectNames[attachment->getID()]; - } - else if(update) - { - row["columns"][1]["value"] = attachment->getID(); - populated = false; - } - else - { - row["columns"][1]["value"] = "Loading..."; - populated = false; - } - mTempItemsList->addElement(row); - mAttachmentsMap[attachment->getID()] = attachment; - } - } - return populated; -} - -void LLPanelWearing::requestAttachmentDetails() -{ - LLSD body; - std::string url = gAgent.getRegionCapability("AttachmentResources"); - if (!url.empty()) - { - LLCoros::instance().launch("LLPanelWearing::getAttachmentLimitsCoro", - boost::bind(&LLPanelWearing::getAttachmentLimitsCoro, this, url)); - } -} - -void LLPanelWearing::getAttachmentLimitsCoro(std::string url) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getAttachmentLimitsCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD result = httpAdapter->getAndSuspend(httpRequest, url); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS() << "Unable to retrieve attachment limits." << LL_ENDL; - return; - } - - result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); - setAttachmentDetails(result); -} - - -void LLPanelWearing::setAttachmentDetails(LLSD content) -{ - mObjectNames.clear(); - S32 number_attachments = content["attachments"].size(); - for(int i = 0; i < number_attachments; i++) - { - S32 number_objects = content["attachments"][i]["objects"].size(); - for(int j = 0; j < number_objects; j++) - { - LLUUID task_id = content["attachments"][i]["objects"][j]["id"].asUUID(); - std::string name = content["attachments"][i]["objects"][j]["name"].asString(); - mObjectNames[task_id] = name; - } - } - if(!mObjectNames.empty()) - { - populateAttachmentsList(true); - } -} - -boost::signals2::connection LLPanelWearing::setSelectionChangeCallback(commit_callback_t cb) -{ - if (!mCOFItemsList) return boost::signals2::connection(); - - return mCOFItemsList->setCommitCallback(cb); -} - -void LLPanelWearing::onWearableItemsListRightClick(LLUICtrl* ctrl, S32 x, S32 y) -{ - LLWearableItemsList* list = dynamic_cast(ctrl); - if (!list) return; - - uuid_vec_t selected_uuids; - - list->getSelectedUUIDs(selected_uuids); - - mContextMenu->show(ctrl, selected_uuids, x, y); -} - -void LLPanelWearing::onTempAttachmentsListRightClick(LLUICtrl* ctrl, S32 x, S32 y) -{ - LLScrollListCtrl* list = dynamic_cast(ctrl); - if (!list) return; - list->selectItemAt(x, y, MASK_NONE); - uuid_vec_t selected_uuids; - - if(list->getCurrentID().notNull()) - { - selected_uuids.push_back(list->getCurrentID()); - mAttachmentsMenu->show(ctrl, selected_uuids, x, y); - } -} - -bool LLPanelWearing::hasItemSelected() -{ - return mCOFItemsList->getSelectedItem() != NULL; -} - -void LLPanelWearing::getSelectedItemsUUIDs(uuid_vec_t& selected_uuids) const -{ - mCOFItemsList->getSelectedUUIDs(selected_uuids); -} - -void LLPanelWearing::onEditAttachment() -{ - LLScrollListItem* item = mTempItemsList->getFirstSelected(); - if (item) - { - LLSelectMgr::getInstance()->deselectAll(); - LLSelectMgr::getInstance()->selectObjectAndFamily(mAttachmentsMap[item->getUUID()]); - handle_object_edit(); - } -} - -void LLPanelWearing::onRemoveAttachment() -{ - LLScrollListItem* item = mTempItemsList->getFirstSelected(); - if (item && item->getUUID().notNull()) - { - LLSelectMgr::getInstance()->deselectAll(); - LLSelectMgr::getInstance()->selectObjectAndFamily(mAttachmentsMap[item->getUUID()]); - LLSelectMgr::getInstance()->sendDetach(); - } -} - -void LLPanelWearing::onRemoveItem() -{ - if (mWearablesTab->isExpanded()) - { - uuid_vec_t selected_uuids; - getSelectedItemsUUIDs(selected_uuids); - LLAppearanceMgr::instance().removeItemsFromAvatar(selected_uuids); - } - else - { - onRemoveAttachment(); - } -} - - -void LLPanelWearing::copyToClipboard() -{ - std::string text; - std::vector data; - mCOFItemsList->getValues(data); - - for(std::vector::const_iterator iter = data.begin(); iter != data.end();) - { - LLSD uuid = (*iter); - LLViewerInventoryItem* item = gInventory.getItem(uuid); - - iter++; - if (item != NULL) - { - // Append a newline to all but the last line - text += iter != data.end() ? item->getName() + "\n" : item->getName(); - } - } - - LLClipboard::instance().copyToClipboard(utf8str_to_wstring(text),0,text.size()); -} -// EOF +/** + * @file llpanelwearing.cpp + * @brief List of agent's worn items. + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanelwearing.h" + +#include "lltoggleablemenu.h" + +#include "llagent.h" +#include "llaccordionctrl.h" +#include "llaccordionctrltab.h" +#include "llappearancemgr.h" +#include "llfloatersidepanelcontainer.h" +#include "llinventoryfunctions.h" +#include "llinventoryicon.h" +#include "llinventorymodel.h" +#include "llinventoryobserver.h" +#include "llmenubutton.h" +#include "lloutfitobserver.h" +#include "llscrolllistctrl.h" +#include "llviewermenu.h" +#include "llviewerregion.h" +#include "llwearableitemslist.h" +#include "llsdserialize.h" +#include "llclipboard.h" + +// Context menu and Gear menu helper. +static void edit_outfit() +{ + LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "edit_outfit")); +} + +////////////////////////////////////////////////////////////////////////// + +class LLWearingGearMenu +{ +public: + LLWearingGearMenu(LLPanelWearing* panel_wearing) + : mMenu(NULL), mPanelWearing(panel_wearing) + { + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; + + registrar.add("Gear.TouchAttach", boost::bind(&LLWearingGearMenu::handleMultiple, this, handle_attachment_touch)); + registrar.add("Gear.EditItem", boost::bind(&LLWearingGearMenu::handleMultiple, this, handle_item_edit)); + registrar.add("Gear.EditOutfit", boost::bind(&edit_outfit)); + registrar.add("Gear.TakeOff", boost::bind(&LLPanelWearing::onRemoveItem, mPanelWearing)); + registrar.add("Gear.Copy", boost::bind(&LLPanelWearing::copyToClipboard, mPanelWearing)); + + enable_registrar.add("Gear.OnEnable", boost::bind(&LLPanelWearing::isActionEnabled, mPanelWearing, _2)); + + mMenu = LLUICtrlFactory::getInstance()->createFromFile( + "menu_wearing_gear.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + llassert(mMenu); + } + + LLToggleableMenu* getMenu() { return mMenu; } + +private: + void handleMultiple(std::function functor) + { + uuid_vec_t selected_item_ids; + mPanelWearing->getSelectedItemsUUIDs(selected_item_ids); + + for (const LLUUID& item_id : selected_item_ids) + { + functor(item_id); + } + } + + LLToggleableMenu* mMenu; + LLPanelWearing* mPanelWearing; +}; + +////////////////////////////////////////////////////////////////////////// + +class LLWearingContextMenu : public LLListContextMenu +{ +protected: + /* virtual */ LLContextMenu* createMenu() + { + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + + registrar.add("Wearing.TouchAttach", boost::bind(handleMultiple, handle_attachment_touch, mUUIDs)); + registrar.add("Wearing.EditItem", boost::bind(handleMultiple, handle_item_edit, mUUIDs)); + registrar.add("Wearing.EditOutfit", boost::bind(&edit_outfit)); + registrar.add("Wearing.ShowOriginal", boost::bind(show_item_original, mUUIDs.front())); + registrar.add("Wearing.TakeOff", + boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), mUUIDs, no_op)); + registrar.add("Wearing.Detach", + boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), mUUIDs, no_op)); + LLContextMenu* menu = createFromFile("menu_wearing_tab.xml"); + + updateMenuItemsVisibility(menu); + + return menu; + } + + void updateMenuItemsVisibility(LLContextMenu* menu) + { + bool bp_selected = false; // true if body parts selected + bool clothes_selected = false; + bool attachments_selected = false; + + // See what types of wearables are selected. + for (uuid_vec_t::const_iterator it = mUUIDs.begin(); it != mUUIDs.end(); ++it) + { + LLViewerInventoryItem* item = gInventory.getItem(*it); + + if (!item) + { + LL_WARNS() << "Invalid item" << LL_ENDL; + continue; + } + + LLAssetType::EType type = item->getType(); + if (type == LLAssetType::AT_CLOTHING) + { + clothes_selected = true; + } + else if (type == LLAssetType::AT_BODYPART) + { + bp_selected = true; + } + else if (type == LLAssetType::AT_OBJECT || type == LLAssetType::AT_GESTURE) + { + attachments_selected = true; + } + } + + // Enable/disable some menu items depending on the selection. + bool show_touch = !bp_selected && !clothes_selected && attachments_selected; + bool show_edit = bp_selected || clothes_selected || attachments_selected; + bool allow_detach = !bp_selected && !clothes_selected && attachments_selected; + bool allow_take_off = !bp_selected && clothes_selected && !attachments_selected; + + menu->setItemVisible("touch_attach", show_touch); + menu->setItemEnabled("touch_attach", 1 == mUUIDs.size() && enable_attachment_touch(mUUIDs.front())); + menu->setItemVisible("edit_item", show_edit); + menu->setItemEnabled("edit_item", 1 == mUUIDs.size() && get_is_item_editable(mUUIDs.front())); + menu->setItemVisible("take_off", allow_take_off); + menu->setItemVisible("detach", allow_detach); + menu->setItemVisible("edit_outfit_separator", show_touch | show_edit | allow_take_off || allow_detach); + menu->setItemVisible("show_original", mUUIDs.size() == 1); + } +}; + +////////////////////////////////////////////////////////////////////////// + +class LLTempAttachmentsContextMenu : public LLListContextMenu +{ +public: + LLTempAttachmentsContextMenu(LLPanelWearing* panel_wearing) + : mPanelWearing(panel_wearing) + {} +protected: + /* virtual */ LLContextMenu* createMenu() + { + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + + registrar.add("Wearing.EditItem", boost::bind(&LLPanelWearing::onEditAttachment, mPanelWearing)); + registrar.add("Wearing.Detach", boost::bind(&LLPanelWearing::onRemoveAttachment, mPanelWearing)); + LLContextMenu* menu = createFromFile("menu_wearing_tab.xml"); + + updateMenuItemsVisibility(menu); + + return menu; + } + + void updateMenuItemsVisibility(LLContextMenu* menu) + { + menu->setItemVisible("touch_attach", true); + menu->setItemEnabled("touch_attach", 1 == mUUIDs.size()); + menu->setItemVisible("edit_item", true); + menu->setItemEnabled("edit_item", 1 == mUUIDs.size()); + menu->setItemVisible("take_off", false); + menu->setItemVisible("detach", true); + menu->setItemVisible("edit_outfit_separator", false); + menu->setItemVisible("show_original", false); + menu->setItemVisible("edit_outfit", false); + } + + LLPanelWearing* mPanelWearing; +}; + +////////////////////////////////////////////////////////////////////////// + +static LLPanelInjector t_panel_wearing("panel_wearing"); + +LLPanelWearing::LLPanelWearing() + : LLPanelAppearanceTab() + , mCOFItemsList(NULL) + , mIsInitialized(false) + , mAttachmentsChangedConnection() +{ + mGearMenu = new LLWearingGearMenu(this); + mContextMenu = new LLWearingContextMenu(); + mAttachmentsMenu = new LLTempAttachmentsContextMenu(this); +} + +LLPanelWearing::~LLPanelWearing() +{ + delete mGearMenu; + delete mContextMenu; + delete mAttachmentsMenu; + + if (mAttachmentsChangedConnection.connected()) + { + mAttachmentsChangedConnection.disconnect(); + } +} + +bool LLPanelWearing::postBuild() +{ + mAccordionCtrl = getChild("wearables_accordion"); + mWearablesTab = getChild("tab_wearables"); + mWearablesTab->setIgnoreResizeNotification(true); + mAttachmentsTab = getChild("tab_temp_attachments"); + mAttachmentsTab->setDropDownStateChangedCallback(boost::bind(&LLPanelWearing::onAccordionTabStateChanged, this)); + + mCOFItemsList = getChild("cof_items_list"); + mCOFItemsList->setRightMouseDownCallback(boost::bind(&LLPanelWearing::onWearableItemsListRightClick, this, _1, _2, _3)); + + mTempItemsList = getChild("temp_attachments_list"); + mTempItemsList->setFgUnselectedColor(LLColor4::white); + mTempItemsList->setRightMouseDownCallback(boost::bind(&LLPanelWearing::onTempAttachmentsListRightClick, this, _1, _2, _3)); + + LLMenuButton* menu_gear_btn = getChild("options_gear_btn"); + + menu_gear_btn->setMenu(mGearMenu->getMenu()); + + return true; +} + +//virtual +void LLPanelWearing::onOpen(const LLSD& /*info*/) +{ + if (!mIsInitialized) + { + // *TODO: I'm not sure is this check necessary but it never match while developing. + if (!gInventory.isInventoryUsable()) + return; + + const LLUUID cof = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); + + // *TODO: I'm not sure is this check necessary but it never match while developing. + LLViewerInventoryCategory* category = gInventory.getCategory(cof); + if (!category) + return; + + // Start observing changes in Current Outfit category. + LLOutfitObserver::instance().addCOFChangedCallback(boost::bind(&LLWearableItemsList::updateList, mCOFItemsList, cof)); + + // Fetch Current Outfit contents and refresh the list to display + // initially fetched items. If not all items are fetched now + // the observer will refresh the list as soon as the new items + // arrive. + category->fetch(); + + mCOFItemsList->updateList(cof); + + mIsInitialized = true; + } +} + +void LLPanelWearing::draw() +{ + if (mUpdateTimer.getStarted() && (mUpdateTimer.getElapsedTimeF32() > 0.1)) + { + mUpdateTimer.stop(); + updateAttachmentsList(); + } + LLPanel::draw(); +} + +void LLPanelWearing::onAccordionTabStateChanged() +{ + if(mAttachmentsTab->isExpanded()) + { + startUpdateTimer(); + mAttachmentsChangedConnection = LLAppearanceMgr::instance().setAttachmentsChangedCallback(boost::bind(&LLPanelWearing::startUpdateTimer, this)); + } + else + { + if (mAttachmentsChangedConnection.connected()) + { + mAttachmentsChangedConnection.disconnect(); + } + } +} + +void LLPanelWearing::startUpdateTimer() +{ + if (!mUpdateTimer.getStarted()) + { + mUpdateTimer.start(); + } + else + { + mUpdateTimer.reset(); + } +} + +// virtual +void LLPanelWearing::onFilterSubStringChanged(const std::string& new_string, const std::string& old_string) +{ + mCOFItemsList->setFilterSubString(new_string, true); + + mAccordionCtrl->arrange(); +} + +// virtual +bool LLPanelWearing::isActionEnabled(const LLSD& userdata) +{ + const std::string command_name = userdata.asString(); + + if (command_name == "save_outfit") + { + bool outfit_locked = LLAppearanceMgr::getInstance()->isOutfitLocked(); + bool outfit_dirty = LLAppearanceMgr::getInstance()->isOutfitDirty(); + // allow save only if outfit isn't locked and is dirty + return !outfit_locked && outfit_dirty; + } + + if (command_name == "take_off") + { + if (mWearablesTab->isExpanded()) + { + return hasItemSelected() && canTakeOffSelected(); + } + else + { + LLScrollListItem* item = mTempItemsList->getFirstSelected(); + if (item && item->getUUID().notNull()) + { + return true; + } + } + } + + uuid_vec_t selected_uuids; + getSelectedItemsUUIDs(selected_uuids); + + if (command_name == "touch_attach") + { + return (1 == selected_uuids.size()) && (enable_attachment_touch(selected_uuids.front())); + } + else if (command_name == "edit_item") + { + return (1 == selected_uuids.size()) && (get_is_item_editable(selected_uuids.front())); + } + + return false; +} + +void LLPanelWearing::updateAttachmentsList() +{ + std::vector attachs = LLAgentWearables::getTempAttachments(); + mTempItemsList->deleteAllItems(); + mAttachmentsMap.clear(); + if(!attachs.empty()) + { + if(!populateAttachmentsList()) + { + requestAttachmentDetails(); + } + } + else + { + std::string no_attachments = getString("no_attachments"); + LLSD row; + row["columns"][0]["column"] = "text"; + row["columns"][0]["value"] = no_attachments; + row["columns"][0]["font"] = "SansSerifBold"; + mTempItemsList->addElement(row); + } +} + +bool LLPanelWearing::populateAttachmentsList(bool update) +{ + bool populated = true; + if(mTempItemsList) + { + mTempItemsList->deleteAllItems(); + mAttachmentsMap.clear(); + std::vector attachs = LLAgentWearables::getTempAttachments(); + + std::string icon_name = LLInventoryIcon::getIconName(LLAssetType::AT_OBJECT, LLInventoryType::IT_OBJECT); + for (std::vector::iterator iter = attachs.begin(); + iter != attachs.end(); ++iter) + { + LLViewerObject *attachment = *iter; + LLSD row; + row["id"] = attachment->getID(); + row["columns"][0]["column"] = "icon"; + row["columns"][0]["type"] = "icon"; + row["columns"][0]["value"] = icon_name; + row["columns"][1]["column"] = "text"; + if(mObjectNames.count(attachment->getID()) && !mObjectNames[attachment->getID()].empty()) + { + row["columns"][1]["value"] = mObjectNames[attachment->getID()]; + } + else if(update) + { + row["columns"][1]["value"] = attachment->getID(); + populated = false; + } + else + { + row["columns"][1]["value"] = "Loading..."; + populated = false; + } + mTempItemsList->addElement(row); + mAttachmentsMap[attachment->getID()] = attachment; + } + } + return populated; +} + +void LLPanelWearing::requestAttachmentDetails() +{ + LLSD body; + std::string url = gAgent.getRegionCapability("AttachmentResources"); + if (!url.empty()) + { + LLCoros::instance().launch("LLPanelWearing::getAttachmentLimitsCoro", + boost::bind(&LLPanelWearing::getAttachmentLimitsCoro, this, url)); + } +} + +void LLPanelWearing::getAttachmentLimitsCoro(std::string url) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getAttachmentLimitsCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD result = httpAdapter->getAndSuspend(httpRequest, url); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS() << "Unable to retrieve attachment limits." << LL_ENDL; + return; + } + + result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); + setAttachmentDetails(result); +} + + +void LLPanelWearing::setAttachmentDetails(LLSD content) +{ + mObjectNames.clear(); + S32 number_attachments = content["attachments"].size(); + for(int i = 0; i < number_attachments; i++) + { + S32 number_objects = content["attachments"][i]["objects"].size(); + for(int j = 0; j < number_objects; j++) + { + LLUUID task_id = content["attachments"][i]["objects"][j]["id"].asUUID(); + std::string name = content["attachments"][i]["objects"][j]["name"].asString(); + mObjectNames[task_id] = name; + } + } + if(!mObjectNames.empty()) + { + populateAttachmentsList(true); + } +} + +boost::signals2::connection LLPanelWearing::setSelectionChangeCallback(commit_callback_t cb) +{ + if (!mCOFItemsList) return boost::signals2::connection(); + + return mCOFItemsList->setCommitCallback(cb); +} + +void LLPanelWearing::onWearableItemsListRightClick(LLUICtrl* ctrl, S32 x, S32 y) +{ + LLWearableItemsList* list = dynamic_cast(ctrl); + if (!list) return; + + uuid_vec_t selected_uuids; + + list->getSelectedUUIDs(selected_uuids); + + mContextMenu->show(ctrl, selected_uuids, x, y); +} + +void LLPanelWearing::onTempAttachmentsListRightClick(LLUICtrl* ctrl, S32 x, S32 y) +{ + LLScrollListCtrl* list = dynamic_cast(ctrl); + if (!list) return; + list->selectItemAt(x, y, MASK_NONE); + uuid_vec_t selected_uuids; + + if(list->getCurrentID().notNull()) + { + selected_uuids.push_back(list->getCurrentID()); + mAttachmentsMenu->show(ctrl, selected_uuids, x, y); + } +} + +bool LLPanelWearing::hasItemSelected() +{ + return mCOFItemsList->getSelectedItem() != NULL; +} + +void LLPanelWearing::getSelectedItemsUUIDs(uuid_vec_t& selected_uuids) const +{ + mCOFItemsList->getSelectedUUIDs(selected_uuids); +} + +void LLPanelWearing::onEditAttachment() +{ + LLScrollListItem* item = mTempItemsList->getFirstSelected(); + if (item) + { + LLSelectMgr::getInstance()->deselectAll(); + LLSelectMgr::getInstance()->selectObjectAndFamily(mAttachmentsMap[item->getUUID()]); + handle_object_edit(); + } +} + +void LLPanelWearing::onRemoveAttachment() +{ + LLScrollListItem* item = mTempItemsList->getFirstSelected(); + if (item && item->getUUID().notNull()) + { + LLSelectMgr::getInstance()->deselectAll(); + LLSelectMgr::getInstance()->selectObjectAndFamily(mAttachmentsMap[item->getUUID()]); + LLSelectMgr::getInstance()->sendDetach(); + } +} + +void LLPanelWearing::onRemoveItem() +{ + if (mWearablesTab->isExpanded()) + { + uuid_vec_t selected_uuids; + getSelectedItemsUUIDs(selected_uuids); + LLAppearanceMgr::instance().removeItemsFromAvatar(selected_uuids); + } + else + { + onRemoveAttachment(); + } +} + + +void LLPanelWearing::copyToClipboard() +{ + std::string text; + std::vector data; + mCOFItemsList->getValues(data); + + for(std::vector::const_iterator iter = data.begin(); iter != data.end();) + { + LLSD uuid = (*iter); + LLViewerInventoryItem* item = gInventory.getItem(uuid); + + iter++; + if (item != NULL) + { + // Append a newline to all but the last line + text += iter != data.end() ? item->getName() + "\n" : item->getName(); + } + } + + LLClipboard::instance().copyToClipboard(utf8str_to_wstring(text),0,text.size()); +} +// EOF diff --git a/indra/newview/llpanelwearing.h b/indra/newview/llpanelwearing.h index 9c3f19c3de..ea0787d0ef 100644 --- a/indra/newview/llpanelwearing.h +++ b/indra/newview/llpanelwearing.h @@ -1,113 +1,113 @@ -/** - * @file llpanelwearing.h - * @brief List of agent's worn items. - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPANELWEARING_H -#define LL_LLPANELWEARING_H - -#include "llpanel.h" - -// newview -#include "llpanelappearancetab.h" -#include "llselectmgr.h" -#include "lltimer.h" - -class LLAccordionCtrl; -class LLAccordionCtrlTab; -class LLInventoryCategoriesObserver; -class LLListContextMenu; -class LLScrollListCtrl; -class LLWearableItemsList; -class LLWearingGearMenu; - -/** - * @class LLPanelWearing - * - * A list of agents's currently worn items represented by - * a flat list view. - * Starts fetching necessary inventory content on first opening. - */ -class LLPanelWearing : public LLPanelAppearanceTab -{ -public: - LLPanelWearing(); - virtual ~LLPanelWearing(); - - /*virtual*/ bool postBuild(); - - /*virtual*/ void draw(); - - /*virtual*/ void onOpen(const LLSD& info); - - /*virtual*/ void onFilterSubStringChanged(const std::string& new_string, const std::string& old_string); - - /*virtual*/ bool isActionEnabled(const LLSD& userdata); - - /*virtual*/ void getSelectedItemsUUIDs(uuid_vec_t& selected_uuids) const; - - /*virtual*/ void copyToClipboard(); - - void startUpdateTimer(); - void updateAttachmentsList(); - - boost::signals2::connection setSelectionChangeCallback(commit_callback_t cb); - - bool hasItemSelected(); - - bool populateAttachmentsList(bool update = false); - void onAccordionTabStateChanged(); - void setAttachmentDetails(LLSD content); - void requestAttachmentDetails(); - void onRemoveItem(); - void onEditAttachment(); - void onRemoveAttachment(); - -private: - void onWearableItemsListRightClick(LLUICtrl* ctrl, S32 x, S32 y); - void onTempAttachmentsListRightClick(LLUICtrl* ctrl, S32 x, S32 y); - - void getAttachmentLimitsCoro(std::string url); - - LLWearableItemsList* mCOFItemsList; - LLScrollListCtrl* mTempItemsList; - LLWearingGearMenu* mGearMenu; - LLListContextMenu* mContextMenu; - LLListContextMenu* mAttachmentsMenu; - - LLAccordionCtrlTab* mWearablesTab; - LLAccordionCtrlTab* mAttachmentsTab; - LLAccordionCtrl* mAccordionCtrl; - - std::map mAttachmentsMap; - - std::map mObjectNames; - - boost::signals2::connection mAttachmentsChangedConnection; - LLFrameTimer mUpdateTimer; - - bool mIsInitialized; -}; - -#endif //LL_LLPANELWEARING_H +/** + * @file llpanelwearing.h + * @brief List of agent's worn items. + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPANELWEARING_H +#define LL_LLPANELWEARING_H + +#include "llpanel.h" + +// newview +#include "llpanelappearancetab.h" +#include "llselectmgr.h" +#include "lltimer.h" + +class LLAccordionCtrl; +class LLAccordionCtrlTab; +class LLInventoryCategoriesObserver; +class LLListContextMenu; +class LLScrollListCtrl; +class LLWearableItemsList; +class LLWearingGearMenu; + +/** + * @class LLPanelWearing + * + * A list of agents's currently worn items represented by + * a flat list view. + * Starts fetching necessary inventory content on first opening. + */ +class LLPanelWearing : public LLPanelAppearanceTab +{ +public: + LLPanelWearing(); + virtual ~LLPanelWearing(); + + /*virtual*/ bool postBuild(); + + /*virtual*/ void draw(); + + /*virtual*/ void onOpen(const LLSD& info); + + /*virtual*/ void onFilterSubStringChanged(const std::string& new_string, const std::string& old_string); + + /*virtual*/ bool isActionEnabled(const LLSD& userdata); + + /*virtual*/ void getSelectedItemsUUIDs(uuid_vec_t& selected_uuids) const; + + /*virtual*/ void copyToClipboard(); + + void startUpdateTimer(); + void updateAttachmentsList(); + + boost::signals2::connection setSelectionChangeCallback(commit_callback_t cb); + + bool hasItemSelected(); + + bool populateAttachmentsList(bool update = false); + void onAccordionTabStateChanged(); + void setAttachmentDetails(LLSD content); + void requestAttachmentDetails(); + void onRemoveItem(); + void onEditAttachment(); + void onRemoveAttachment(); + +private: + void onWearableItemsListRightClick(LLUICtrl* ctrl, S32 x, S32 y); + void onTempAttachmentsListRightClick(LLUICtrl* ctrl, S32 x, S32 y); + + void getAttachmentLimitsCoro(std::string url); + + LLWearableItemsList* mCOFItemsList; + LLScrollListCtrl* mTempItemsList; + LLWearingGearMenu* mGearMenu; + LLListContextMenu* mContextMenu; + LLListContextMenu* mAttachmentsMenu; + + LLAccordionCtrlTab* mWearablesTab; + LLAccordionCtrlTab* mAttachmentsTab; + LLAccordionCtrl* mAccordionCtrl; + + std::map mAttachmentsMap; + + std::map mObjectNames; + + boost::signals2::connection mAttachmentsChangedConnection; + LLFrameTimer mUpdateTimer; + + bool mIsInitialized; +}; + +#endif //LL_LLPANELWEARING_H diff --git a/indra/newview/llparcelselection.cpp b/indra/newview/llparcelselection.cpp index f1ad1999b1..3bfa886bfe 100644 --- a/indra/newview/llparcelselection.cpp +++ b/indra/newview/llparcelselection.cpp @@ -1,82 +1,82 @@ -/** - * @file llparcelselection.cpp - * @brief Information about the currently selected parcel - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llparcelselection.h" - -#include "llparcel.h" - - -// -// LLParcelSelection -// -LLParcelSelection::LLParcelSelection() : - mParcel(NULL), - mSelectedMultipleOwners(false), - mWholeParcelSelected(false), - mSelectedSelfCount(0), - mSelectedOtherCount(0), - mSelectedPublicCount(0) -{ -} - -LLParcelSelection::LLParcelSelection(LLParcel* parcel) : - mParcel(parcel), - mSelectedMultipleOwners(false), - mWholeParcelSelected(false), - mSelectedSelfCount(0), - mSelectedOtherCount(0), - mSelectedPublicCount(0) -{ -} - -LLParcelSelection::~LLParcelSelection() -{ -} - -bool LLParcelSelection::getMultipleOwners() const -{ - return mSelectedMultipleOwners; -} - - -bool LLParcelSelection::getWholeParcelSelected() const -{ - return mWholeParcelSelected; -} - - -S32 LLParcelSelection::getClaimableArea() const -{ - const S32 UNIT_AREA = S32( PARCEL_GRID_STEP_METERS * PARCEL_GRID_STEP_METERS ); - return mSelectedPublicCount * UNIT_AREA; -} - -bool LLParcelSelection::hasOthersSelected() const -{ - return mSelectedOtherCount != 0; -} +/** + * @file llparcelselection.cpp + * @brief Information about the currently selected parcel + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llparcelselection.h" + +#include "llparcel.h" + + +// +// LLParcelSelection +// +LLParcelSelection::LLParcelSelection() : + mParcel(NULL), + mSelectedMultipleOwners(false), + mWholeParcelSelected(false), + mSelectedSelfCount(0), + mSelectedOtherCount(0), + mSelectedPublicCount(0) +{ +} + +LLParcelSelection::LLParcelSelection(LLParcel* parcel) : + mParcel(parcel), + mSelectedMultipleOwners(false), + mWholeParcelSelected(false), + mSelectedSelfCount(0), + mSelectedOtherCount(0), + mSelectedPublicCount(0) +{ +} + +LLParcelSelection::~LLParcelSelection() +{ +} + +bool LLParcelSelection::getMultipleOwners() const +{ + return mSelectedMultipleOwners; +} + + +bool LLParcelSelection::getWholeParcelSelected() const +{ + return mWholeParcelSelected; +} + + +S32 LLParcelSelection::getClaimableArea() const +{ + const S32 UNIT_AREA = S32( PARCEL_GRID_STEP_METERS * PARCEL_GRID_STEP_METERS ); + return mSelectedPublicCount * UNIT_AREA; +} + +bool LLParcelSelection::hasOthersSelected() const +{ + return mSelectedOtherCount != 0; +} diff --git a/indra/newview/llparcelselection.h b/indra/newview/llparcelselection.h index 9e41daeb50..f1b20572fc 100644 --- a/indra/newview/llparcelselection.h +++ b/indra/newview/llparcelselection.h @@ -1,79 +1,79 @@ -/** - * @file llparcelselection.h - * @brief Information about the currently selected parcel - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLPARCELSELECTION_H -#define LLPARCELSELECTION_H - -#include "llrefcount.h" -#include "llsafehandle.h" - -class LLParcel; - -class LLParcelSelection : public LLRefCount -{ - friend class LLViewerParcelMgr; - friend class LLSafeHandle; - -protected: - ~LLParcelSelection(); - -public: - LLParcelSelection(LLParcel* parcel); - LLParcelSelection(); - - // this can return NULL at any time, as parcel selection - // might have been invalidated. - LLParcel* getParcel() { return mParcel; } - - // Return the number of grid units that are owned by you within - // the selection (computed by server). - S32 getSelfCount() const { return mSelectedSelfCount; } - - // Returns area that will actually be claimed in meters squared. - S32 getClaimableArea() const; - bool hasOthersSelected() const; - - // Does the selection have multiple land owners in it? - bool getMultipleOwners() const; - - // Is the entire parcel selected, or just a part? - bool getWholeParcelSelected() const; - -private: - void setParcel(LLParcel* parcel) { mParcel = parcel; } - -private: - LLParcel* mParcel; - bool mSelectedMultipleOwners; - bool mWholeParcelSelected; - S32 mSelectedSelfCount; - S32 mSelectedOtherCount; - S32 mSelectedPublicCount; -}; - -typedef LLSafeHandle LLParcelSelectionHandle; - -#endif +/** + * @file llparcelselection.h + * @brief Information about the currently selected parcel + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLPARCELSELECTION_H +#define LLPARCELSELECTION_H + +#include "llrefcount.h" +#include "llsafehandle.h" + +class LLParcel; + +class LLParcelSelection : public LLRefCount +{ + friend class LLViewerParcelMgr; + friend class LLSafeHandle; + +protected: + ~LLParcelSelection(); + +public: + LLParcelSelection(LLParcel* parcel); + LLParcelSelection(); + + // this can return NULL at any time, as parcel selection + // might have been invalidated. + LLParcel* getParcel() { return mParcel; } + + // Return the number of grid units that are owned by you within + // the selection (computed by server). + S32 getSelfCount() const { return mSelectedSelfCount; } + + // Returns area that will actually be claimed in meters squared. + S32 getClaimableArea() const; + bool hasOthersSelected() const; + + // Does the selection have multiple land owners in it? + bool getMultipleOwners() const; + + // Is the entire parcel selected, or just a part? + bool getWholeParcelSelected() const; + +private: + void setParcel(LLParcel* parcel) { mParcel = parcel; } + +private: + LLParcel* mParcel; + bool mSelectedMultipleOwners; + bool mWholeParcelSelected; + S32 mSelectedSelfCount; + S32 mSelectedOtherCount; + S32 mSelectedPublicCount; +}; + +typedef LLSafeHandle LLParcelSelectionHandle; + +#endif diff --git a/indra/newview/llpathfindingcharacter.cpp b/indra/newview/llpathfindingcharacter.cpp index 1ac2bc5b7b..66cc26469e 100644 --- a/indra/newview/llpathfindingcharacter.cpp +++ b/indra/newview/llpathfindingcharacter.cpp @@ -1,99 +1,99 @@ -/** -* @file llpathfindingcharacter.cpp -* @brief Definition of a pathfinding character that contains various properties required for havok pathfinding. -* @author Stinson@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - - -#include "llviewerprecompiledheaders.h" - -#include "llpathfindingcharacter.h" - -#include - -#include "llpathfindingobject.h" -#include "llsd.h" - -#define CHARACTER_CPU_TIME_FIELD "cpu_time" -#define CHARACTER_HORIZONTAL_FIELD "horizontal" -#define CHARACTER_LENGTH_FIELD "length" -#define CHARACTER_RADIUS_FIELD "radius" - -//--------------------------------------------------------------------------- -// LLPathfindingCharacter -//--------------------------------------------------------------------------- - -LLPathfindingCharacter::LLPathfindingCharacter(const std::string &pUUID, const LLSD& pCharacterData) - : LLPathfindingObject(pUUID, pCharacterData), - mCPUTime(0U), - mIsHorizontal(false), - mLength(0.0f), - mRadius(0.0f) -{ - parseCharacterData(pCharacterData); -} - -LLPathfindingCharacter::LLPathfindingCharacter(const LLPathfindingCharacter& pOther) - : LLPathfindingObject(pOther), - mCPUTime(pOther.mCPUTime), - mIsHorizontal(pOther.mIsHorizontal), - mLength(pOther.mLength), - mRadius(pOther.mRadius) -{ -} - -LLPathfindingCharacter::~LLPathfindingCharacter() -{ -} - -LLPathfindingCharacter& LLPathfindingCharacter::operator =(const LLPathfindingCharacter& pOther) -{ - dynamic_cast(*this) = pOther; - - mCPUTime = pOther.mCPUTime; - mIsHorizontal = pOther.mIsHorizontal; - mLength = pOther.mLength; - mRadius = pOther.mRadius; - - return *this; -} - -void LLPathfindingCharacter::parseCharacterData(const LLSD &pCharacterData) -{ - llassert(pCharacterData.has(CHARACTER_CPU_TIME_FIELD)); - llassert(pCharacterData.get(CHARACTER_CPU_TIME_FIELD).isReal()); - mCPUTime = pCharacterData.get(CHARACTER_CPU_TIME_FIELD).asReal(); - - llassert(pCharacterData.has(CHARACTER_HORIZONTAL_FIELD)); - llassert(pCharacterData.get(CHARACTER_HORIZONTAL_FIELD).isBoolean()); - mIsHorizontal = pCharacterData.get(CHARACTER_HORIZONTAL_FIELD).asBoolean(); - - llassert(pCharacterData.has(CHARACTER_LENGTH_FIELD)); - llassert(pCharacterData.get(CHARACTER_LENGTH_FIELD).isReal()); - mLength = pCharacterData.get(CHARACTER_LENGTH_FIELD).asReal(); - - llassert(pCharacterData.has(CHARACTER_RADIUS_FIELD)); - llassert(pCharacterData.get(CHARACTER_RADIUS_FIELD).isReal()); - mRadius = pCharacterData.get(CHARACTER_RADIUS_FIELD).asReal(); -} +/** +* @file llpathfindingcharacter.cpp +* @brief Definition of a pathfinding character that contains various properties required for havok pathfinding. +* @author Stinson@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + + +#include "llviewerprecompiledheaders.h" + +#include "llpathfindingcharacter.h" + +#include + +#include "llpathfindingobject.h" +#include "llsd.h" + +#define CHARACTER_CPU_TIME_FIELD "cpu_time" +#define CHARACTER_HORIZONTAL_FIELD "horizontal" +#define CHARACTER_LENGTH_FIELD "length" +#define CHARACTER_RADIUS_FIELD "radius" + +//--------------------------------------------------------------------------- +// LLPathfindingCharacter +//--------------------------------------------------------------------------- + +LLPathfindingCharacter::LLPathfindingCharacter(const std::string &pUUID, const LLSD& pCharacterData) + : LLPathfindingObject(pUUID, pCharacterData), + mCPUTime(0U), + mIsHorizontal(false), + mLength(0.0f), + mRadius(0.0f) +{ + parseCharacterData(pCharacterData); +} + +LLPathfindingCharacter::LLPathfindingCharacter(const LLPathfindingCharacter& pOther) + : LLPathfindingObject(pOther), + mCPUTime(pOther.mCPUTime), + mIsHorizontal(pOther.mIsHorizontal), + mLength(pOther.mLength), + mRadius(pOther.mRadius) +{ +} + +LLPathfindingCharacter::~LLPathfindingCharacter() +{ +} + +LLPathfindingCharacter& LLPathfindingCharacter::operator =(const LLPathfindingCharacter& pOther) +{ + dynamic_cast(*this) = pOther; + + mCPUTime = pOther.mCPUTime; + mIsHorizontal = pOther.mIsHorizontal; + mLength = pOther.mLength; + mRadius = pOther.mRadius; + + return *this; +} + +void LLPathfindingCharacter::parseCharacterData(const LLSD &pCharacterData) +{ + llassert(pCharacterData.has(CHARACTER_CPU_TIME_FIELD)); + llassert(pCharacterData.get(CHARACTER_CPU_TIME_FIELD).isReal()); + mCPUTime = pCharacterData.get(CHARACTER_CPU_TIME_FIELD).asReal(); + + llassert(pCharacterData.has(CHARACTER_HORIZONTAL_FIELD)); + llassert(pCharacterData.get(CHARACTER_HORIZONTAL_FIELD).isBoolean()); + mIsHorizontal = pCharacterData.get(CHARACTER_HORIZONTAL_FIELD).asBoolean(); + + llassert(pCharacterData.has(CHARACTER_LENGTH_FIELD)); + llassert(pCharacterData.get(CHARACTER_LENGTH_FIELD).isReal()); + mLength = pCharacterData.get(CHARACTER_LENGTH_FIELD).asReal(); + + llassert(pCharacterData.has(CHARACTER_RADIUS_FIELD)); + llassert(pCharacterData.get(CHARACTER_RADIUS_FIELD).isReal()); + mRadius = pCharacterData.get(CHARACTER_RADIUS_FIELD).asReal(); +} diff --git a/indra/newview/llpathfindingcharacter.h b/indra/newview/llpathfindingcharacter.h index e8a4f94121..ed621d975a 100644 --- a/indra/newview/llpathfindingcharacter.h +++ b/indra/newview/llpathfindingcharacter.h @@ -1,63 +1,63 @@ -/** -* @file llpathfindingcharacter.h -* @brief Definition of a pathfinding character that contains various properties required for havok pathfinding. -* @author Stinson@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ -#ifndef LL_LLPATHFINDINGCHARACTER_H -#define LL_LLPATHFINDINGCHARACTER_H - -#include - -#include "llpathfindingobject.h" - -class LLSD; - -class LLPathfindingCharacter : public LLPathfindingObject -{ -public: - LLPathfindingCharacter(const std::string &pUUID, const LLSD &pCharacterData); - LLPathfindingCharacter(const LLPathfindingCharacter& pOther); - virtual ~LLPathfindingCharacter(); - - LLPathfindingCharacter& operator =(const LLPathfindingCharacter& pOther); - - inline F32 getCPUTime() const {return mCPUTime;}; - - inline bool isHorizontal() const {return mIsHorizontal;}; - inline F32 getLength() const {return mLength;}; - inline F32 getRadius() const {return mRadius;}; - -protected: - -private: - void parseCharacterData(const LLSD &pCharacterData); - - F32 mCPUTime; - - bool mIsHorizontal; - F32 mLength; - F32 mRadius; -}; - -#endif // LL_LLPATHFINDINGCHARACTER_H +/** +* @file llpathfindingcharacter.h +* @brief Definition of a pathfinding character that contains various properties required for havok pathfinding. +* @author Stinson@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LL_LLPATHFINDINGCHARACTER_H +#define LL_LLPATHFINDINGCHARACTER_H + +#include + +#include "llpathfindingobject.h" + +class LLSD; + +class LLPathfindingCharacter : public LLPathfindingObject +{ +public: + LLPathfindingCharacter(const std::string &pUUID, const LLSD &pCharacterData); + LLPathfindingCharacter(const LLPathfindingCharacter& pOther); + virtual ~LLPathfindingCharacter(); + + LLPathfindingCharacter& operator =(const LLPathfindingCharacter& pOther); + + inline F32 getCPUTime() const {return mCPUTime;}; + + inline bool isHorizontal() const {return mIsHorizontal;}; + inline F32 getLength() const {return mLength;}; + inline F32 getRadius() const {return mRadius;}; + +protected: + +private: + void parseCharacterData(const LLSD &pCharacterData); + + F32 mCPUTime; + + bool mIsHorizontal; + F32 mLength; + F32 mRadius; +}; + +#endif // LL_LLPATHFINDINGCHARACTER_H diff --git a/indra/newview/llpathfindinglinkset.cpp b/indra/newview/llpathfindinglinkset.cpp index 034233695e..47400f9570 100644 --- a/indra/newview/llpathfindinglinkset.cpp +++ b/indra/newview/llpathfindinglinkset.cpp @@ -1,408 +1,408 @@ -/** -* @file llpathfindinglinkset.cpp -* @brief Definition of a pathfinding linkset that contains various properties required for havok pathfinding. -* @author Stinson@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - - -#include "llviewerprecompiledheaders.h" - -#include "llpathfindinglinkset.h" - -#include - -#include "llpathfindingobject.h" -#include "llsd.h" - -#define LINKSET_LAND_IMPACT_FIELD "landimpact" -#define LINKSET_MODIFIABLE_FIELD "modifiable" -#define LINKSET_CATEGORY_FIELD "navmesh_category" -#define LINKSET_CAN_BE_VOLUME "can_be_volume" -#define LINKSET_IS_SCRIPTED_FIELD "is_scripted" -#define LINKSET_PHANTOM_FIELD "phantom" -#define LINKSET_WALKABILITY_A_FIELD "A" -#define LINKSET_WALKABILITY_B_FIELD "B" -#define LINKSET_WALKABILITY_C_FIELD "C" -#define LINKSET_WALKABILITY_D_FIELD "D" - -#define LINKSET_CATEGORY_VALUE_INCLUDE 0 -#define LINKSET_CATEGORY_VALUE_EXCLUDE 1 -#define LINKSET_CATEGORY_VALUE_IGNORE 2 - -//--------------------------------------------------------------------------- -// LLPathfindingLinkset -//--------------------------------------------------------------------------- - -const S32 LLPathfindingLinkset::MIN_WALKABILITY_VALUE(0); -const S32 LLPathfindingLinkset::MAX_WALKABILITY_VALUE(100); - -LLPathfindingLinkset::LLPathfindingLinkset(const LLSD& pTerrainData) - : LLPathfindingObject(), - mIsTerrain(true), - mLandImpact(0U), - mIsModifiable(false), - mCanBeVolume(false), - mIsScripted(false), - mHasIsScripted(true), - mLinksetUse(kUnknown), - mWalkabilityCoefficientA(MIN_WALKABILITY_VALUE), - mWalkabilityCoefficientB(MIN_WALKABILITY_VALUE), - mWalkabilityCoefficientC(MIN_WALKABILITY_VALUE), - mWalkabilityCoefficientD(MIN_WALKABILITY_VALUE) -{ - parsePathfindingData(pTerrainData); -} - -LLPathfindingLinkset::LLPathfindingLinkset(const std::string &pUUID, const LLSD& pLinksetData) - : LLPathfindingObject(pUUID, pLinksetData), - mIsTerrain(false), - mLandImpact(0U), - mIsModifiable(true), - mCanBeVolume(true), - mIsScripted(false), - mHasIsScripted(false), - mLinksetUse(kUnknown), - mWalkabilityCoefficientA(MIN_WALKABILITY_VALUE), - mWalkabilityCoefficientB(MIN_WALKABILITY_VALUE), - mWalkabilityCoefficientC(MIN_WALKABILITY_VALUE), - mWalkabilityCoefficientD(MIN_WALKABILITY_VALUE) -{ - parseLinksetData(pLinksetData); - parsePathfindingData(pLinksetData); -} - -LLPathfindingLinkset::LLPathfindingLinkset(const LLPathfindingLinkset& pOther) - : LLPathfindingObject(pOther), - mIsTerrain(pOther.mIsTerrain), - mLandImpact(pOther.mLandImpact), - mIsModifiable(pOther.mIsModifiable), - mCanBeVolume(pOther.mCanBeVolume), - mIsScripted(pOther.mIsScripted), - mHasIsScripted(pOther.mHasIsScripted), - mLinksetUse(pOther.mLinksetUse), - mWalkabilityCoefficientA(pOther.mWalkabilityCoefficientA), - mWalkabilityCoefficientB(pOther.mWalkabilityCoefficientB), - mWalkabilityCoefficientC(pOther.mWalkabilityCoefficientC), - mWalkabilityCoefficientD(pOther.mWalkabilityCoefficientD) -{ -} - -LLPathfindingLinkset::~LLPathfindingLinkset() -{ -} - -LLPathfindingLinkset& LLPathfindingLinkset::operator =(const LLPathfindingLinkset& pOther) -{ - dynamic_cast(*this) = pOther; - - mIsTerrain = pOther.mIsTerrain; - mLandImpact = pOther.mLandImpact; - mIsModifiable = pOther.mIsModifiable; - mCanBeVolume = pOther.mCanBeVolume; - mIsScripted = pOther.mIsScripted; - mHasIsScripted = pOther.mHasIsScripted; - mLinksetUse = pOther.mLinksetUse; - mWalkabilityCoefficientA = pOther.mWalkabilityCoefficientA; - mWalkabilityCoefficientB = pOther.mWalkabilityCoefficientB; - mWalkabilityCoefficientC = pOther.mWalkabilityCoefficientC; - mWalkabilityCoefficientD = pOther.mWalkabilityCoefficientD; - - return *this; -} - -bool LLPathfindingLinkset::isPhantom() const -{ - return isPhantom(getLinksetUse()); -} - -LLPathfindingLinkset::ELinksetUse LLPathfindingLinkset::getLinksetUseWithToggledPhantom(ELinksetUse pLinksetUse) -{ - bool isPhantom = LLPathfindingLinkset::isPhantom(pLinksetUse); - ENavMeshGenerationCategory navMeshGenerationCategory = getNavMeshGenerationCategory(pLinksetUse); - - return getLinksetUse(!isPhantom, navMeshGenerationCategory); -} - -bool LLPathfindingLinkset::isShowUnmodifiablePhantomWarning(ELinksetUse pLinksetUse) const -{ - return (!isModifiable() && (isPhantom() != isPhantom(pLinksetUse))); -} - -bool LLPathfindingLinkset::isShowPhantomToggleWarning(ELinksetUse pLinksetUse) const -{ - return (isModifiable() && (isPhantom() != isPhantom(pLinksetUse))); -} - -bool LLPathfindingLinkset::isShowCannotBeVolumeWarning(ELinksetUse pLinksetUse) const -{ - return (!canBeVolume() && ((pLinksetUse == kMaterialVolume) || (pLinksetUse == kExclusionVolume))); -} - -LLSD LLPathfindingLinkset::encodeAlteredFields(ELinksetUse pLinksetUse, S32 pA, S32 pB, S32 pC, S32 pD) const -{ - LLSD itemData; - - if (!isTerrain() && (pLinksetUse != kUnknown) && (getLinksetUse() != pLinksetUse) && - (canBeVolume() || ((pLinksetUse != kMaterialVolume) && (pLinksetUse != kExclusionVolume)))) - { - if (isModifiable()) - { - itemData[LINKSET_PHANTOM_FIELD] = static_cast(isPhantom(pLinksetUse)); - } - - itemData[LINKSET_CATEGORY_FIELD] = convertCategoryToLLSD(getNavMeshGenerationCategory(pLinksetUse)); - } - - if (mWalkabilityCoefficientA != pA) - { - itemData[LINKSET_WALKABILITY_A_FIELD] = llclamp(pA, MIN_WALKABILITY_VALUE, MAX_WALKABILITY_VALUE); - } - - if (mWalkabilityCoefficientB != pB) - { - itemData[LINKSET_WALKABILITY_B_FIELD] = llclamp(pB, MIN_WALKABILITY_VALUE, MAX_WALKABILITY_VALUE); - } - - if (mWalkabilityCoefficientC != pC) - { - itemData[LINKSET_WALKABILITY_C_FIELD] = llclamp(pC, MIN_WALKABILITY_VALUE, MAX_WALKABILITY_VALUE); - } - - if (mWalkabilityCoefficientD != pD) - { - itemData[LINKSET_WALKABILITY_D_FIELD] = llclamp(pD, MIN_WALKABILITY_VALUE, MAX_WALKABILITY_VALUE); - } - - return itemData; -} - -void LLPathfindingLinkset::parseLinksetData(const LLSD &pLinksetData) -{ - llassert(pLinksetData.has(LINKSET_LAND_IMPACT_FIELD)); - llassert(pLinksetData.get(LINKSET_LAND_IMPACT_FIELD).isInteger()); - llassert(pLinksetData.get(LINKSET_LAND_IMPACT_FIELD).asInteger() >= 0); - mLandImpact = pLinksetData.get(LINKSET_LAND_IMPACT_FIELD).asInteger(); - - llassert(pLinksetData.has(LINKSET_MODIFIABLE_FIELD)); - llassert(pLinksetData.get(LINKSET_MODIFIABLE_FIELD).isBoolean()); - mIsModifiable = pLinksetData.get(LINKSET_MODIFIABLE_FIELD).asBoolean(); - - mHasIsScripted = pLinksetData.has(LINKSET_IS_SCRIPTED_FIELD); - if (mHasIsScripted) - { - llassert(pLinksetData.get(LINKSET_IS_SCRIPTED_FIELD).isBoolean()); - mIsScripted = pLinksetData.get(LINKSET_IS_SCRIPTED_FIELD).asBoolean(); - } -} - -void LLPathfindingLinkset::parsePathfindingData(const LLSD &pLinksetData) -{ - bool isPhantom = false; - if (pLinksetData.has(LINKSET_PHANTOM_FIELD)) - { - llassert(pLinksetData.get(LINKSET_PHANTOM_FIELD).isBoolean()); - isPhantom = pLinksetData.get(LINKSET_PHANTOM_FIELD).asBoolean(); - } - - llassert(pLinksetData.has(LINKSET_CATEGORY_FIELD)); - mLinksetUse = getLinksetUse(isPhantom, convertCategoryFromLLSD(pLinksetData.get(LINKSET_CATEGORY_FIELD))); - - if (pLinksetData.has(LINKSET_CAN_BE_VOLUME)) - { - llassert(pLinksetData.get(LINKSET_CAN_BE_VOLUME).isBoolean()); - mCanBeVolume = pLinksetData.get(LINKSET_CAN_BE_VOLUME).asBoolean(); - } - - llassert(pLinksetData.has(LINKSET_WALKABILITY_A_FIELD)); - llassert(pLinksetData.get(LINKSET_WALKABILITY_A_FIELD).isInteger()); - mWalkabilityCoefficientA = pLinksetData.get(LINKSET_WALKABILITY_A_FIELD).asInteger(); - llassert(mWalkabilityCoefficientA >= MIN_WALKABILITY_VALUE); - llassert(mWalkabilityCoefficientA <= MAX_WALKABILITY_VALUE); - - llassert(pLinksetData.has(LINKSET_WALKABILITY_B_FIELD)); - llassert(pLinksetData.get(LINKSET_WALKABILITY_B_FIELD).isInteger()); - mWalkabilityCoefficientB = pLinksetData.get(LINKSET_WALKABILITY_B_FIELD).asInteger(); - llassert(mWalkabilityCoefficientB >= MIN_WALKABILITY_VALUE); - llassert(mWalkabilityCoefficientB <= MAX_WALKABILITY_VALUE); - - llassert(pLinksetData.has(LINKSET_WALKABILITY_C_FIELD)); - llassert(pLinksetData.get(LINKSET_WALKABILITY_C_FIELD).isInteger()); - mWalkabilityCoefficientC = pLinksetData.get(LINKSET_WALKABILITY_C_FIELD).asInteger(); - llassert(mWalkabilityCoefficientC >= MIN_WALKABILITY_VALUE); - llassert(mWalkabilityCoefficientC <= MAX_WALKABILITY_VALUE); - - llassert(pLinksetData.has(LINKSET_WALKABILITY_D_FIELD)); - llassert(pLinksetData.get(LINKSET_WALKABILITY_D_FIELD).isInteger()); - mWalkabilityCoefficientD = pLinksetData.get(LINKSET_WALKABILITY_D_FIELD).asInteger(); - llassert(mWalkabilityCoefficientD >= MIN_WALKABILITY_VALUE); - llassert(mWalkabilityCoefficientD <= MAX_WALKABILITY_VALUE); -} - -bool LLPathfindingLinkset::isPhantom(ELinksetUse pLinksetUse) -{ - bool retVal; - - switch (pLinksetUse) - { - case kWalkable : - case kStaticObstacle : - case kDynamicObstacle : - retVal = false; - break; - case kMaterialVolume : - case kExclusionVolume : - case kDynamicPhantom : - retVal = true; - break; - case kUnknown : - default : - retVal = false; - llassert(0); - break; - } - - return retVal; -} - -LLPathfindingLinkset::ELinksetUse LLPathfindingLinkset::getLinksetUse(bool pIsPhantom, ENavMeshGenerationCategory pNavMeshGenerationCategory) -{ - ELinksetUse linksetUse = kUnknown; - - if (pIsPhantom) - { - switch (pNavMeshGenerationCategory) - { - case kNavMeshGenerationIgnore : - linksetUse = kDynamicPhantom; - break; - case kNavMeshGenerationInclude : - linksetUse = kMaterialVolume; - break; - case kNavMeshGenerationExclude : - linksetUse = kExclusionVolume; - break; - default : - linksetUse = kUnknown; - llassert(0); - break; - } - } - else - { - switch (pNavMeshGenerationCategory) - { - case kNavMeshGenerationIgnore : - linksetUse = kDynamicObstacle; - break; - case kNavMeshGenerationInclude : - linksetUse = kWalkable; - break; - case kNavMeshGenerationExclude : - linksetUse = kStaticObstacle; - break; - default : - linksetUse = kUnknown; - llassert(0); - break; - } - } - - return linksetUse; -} - -LLPathfindingLinkset::ENavMeshGenerationCategory LLPathfindingLinkset::getNavMeshGenerationCategory(ELinksetUse pLinksetUse) -{ - ENavMeshGenerationCategory navMeshGenerationCategory; - switch (pLinksetUse) - { - case kWalkable : - case kMaterialVolume : - navMeshGenerationCategory = kNavMeshGenerationInclude; - break; - case kStaticObstacle : - case kExclusionVolume : - navMeshGenerationCategory = kNavMeshGenerationExclude; - break; - case kDynamicObstacle : - case kDynamicPhantom : - navMeshGenerationCategory = kNavMeshGenerationIgnore; - break; - case kUnknown : - default : - navMeshGenerationCategory = kNavMeshGenerationIgnore; - llassert(0); - break; - } - - return navMeshGenerationCategory; -} - -LLSD LLPathfindingLinkset::convertCategoryToLLSD(ENavMeshGenerationCategory pNavMeshGenerationCategory) -{ - LLSD llsd; - - switch (pNavMeshGenerationCategory) - { - case kNavMeshGenerationIgnore : - llsd = static_cast(LINKSET_CATEGORY_VALUE_IGNORE); - break; - case kNavMeshGenerationInclude : - llsd = static_cast(LINKSET_CATEGORY_VALUE_INCLUDE); - break; - case kNavMeshGenerationExclude : - llsd = static_cast(LINKSET_CATEGORY_VALUE_EXCLUDE); - break; - default : - llsd = static_cast(LINKSET_CATEGORY_VALUE_IGNORE); - llassert(0); - break; - } - - return llsd; -} - -LLPathfindingLinkset::ENavMeshGenerationCategory LLPathfindingLinkset::convertCategoryFromLLSD(const LLSD &llsd) -{ - ENavMeshGenerationCategory navMeshGenerationCategory; - - llassert(llsd.isInteger()); - switch (llsd.asInteger()) - { - case LINKSET_CATEGORY_VALUE_IGNORE : - navMeshGenerationCategory = kNavMeshGenerationIgnore; - break; - case LINKSET_CATEGORY_VALUE_INCLUDE : - navMeshGenerationCategory = kNavMeshGenerationInclude; - break; - case LINKSET_CATEGORY_VALUE_EXCLUDE : - navMeshGenerationCategory = kNavMeshGenerationExclude; - break; - default : - navMeshGenerationCategory = kNavMeshGenerationIgnore; - llassert(0); - break; - } - - return navMeshGenerationCategory; -} +/** +* @file llpathfindinglinkset.cpp +* @brief Definition of a pathfinding linkset that contains various properties required for havok pathfinding. +* @author Stinson@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + + +#include "llviewerprecompiledheaders.h" + +#include "llpathfindinglinkset.h" + +#include + +#include "llpathfindingobject.h" +#include "llsd.h" + +#define LINKSET_LAND_IMPACT_FIELD "landimpact" +#define LINKSET_MODIFIABLE_FIELD "modifiable" +#define LINKSET_CATEGORY_FIELD "navmesh_category" +#define LINKSET_CAN_BE_VOLUME "can_be_volume" +#define LINKSET_IS_SCRIPTED_FIELD "is_scripted" +#define LINKSET_PHANTOM_FIELD "phantom" +#define LINKSET_WALKABILITY_A_FIELD "A" +#define LINKSET_WALKABILITY_B_FIELD "B" +#define LINKSET_WALKABILITY_C_FIELD "C" +#define LINKSET_WALKABILITY_D_FIELD "D" + +#define LINKSET_CATEGORY_VALUE_INCLUDE 0 +#define LINKSET_CATEGORY_VALUE_EXCLUDE 1 +#define LINKSET_CATEGORY_VALUE_IGNORE 2 + +//--------------------------------------------------------------------------- +// LLPathfindingLinkset +//--------------------------------------------------------------------------- + +const S32 LLPathfindingLinkset::MIN_WALKABILITY_VALUE(0); +const S32 LLPathfindingLinkset::MAX_WALKABILITY_VALUE(100); + +LLPathfindingLinkset::LLPathfindingLinkset(const LLSD& pTerrainData) + : LLPathfindingObject(), + mIsTerrain(true), + mLandImpact(0U), + mIsModifiable(false), + mCanBeVolume(false), + mIsScripted(false), + mHasIsScripted(true), + mLinksetUse(kUnknown), + mWalkabilityCoefficientA(MIN_WALKABILITY_VALUE), + mWalkabilityCoefficientB(MIN_WALKABILITY_VALUE), + mWalkabilityCoefficientC(MIN_WALKABILITY_VALUE), + mWalkabilityCoefficientD(MIN_WALKABILITY_VALUE) +{ + parsePathfindingData(pTerrainData); +} + +LLPathfindingLinkset::LLPathfindingLinkset(const std::string &pUUID, const LLSD& pLinksetData) + : LLPathfindingObject(pUUID, pLinksetData), + mIsTerrain(false), + mLandImpact(0U), + mIsModifiable(true), + mCanBeVolume(true), + mIsScripted(false), + mHasIsScripted(false), + mLinksetUse(kUnknown), + mWalkabilityCoefficientA(MIN_WALKABILITY_VALUE), + mWalkabilityCoefficientB(MIN_WALKABILITY_VALUE), + mWalkabilityCoefficientC(MIN_WALKABILITY_VALUE), + mWalkabilityCoefficientD(MIN_WALKABILITY_VALUE) +{ + parseLinksetData(pLinksetData); + parsePathfindingData(pLinksetData); +} + +LLPathfindingLinkset::LLPathfindingLinkset(const LLPathfindingLinkset& pOther) + : LLPathfindingObject(pOther), + mIsTerrain(pOther.mIsTerrain), + mLandImpact(pOther.mLandImpact), + mIsModifiable(pOther.mIsModifiable), + mCanBeVolume(pOther.mCanBeVolume), + mIsScripted(pOther.mIsScripted), + mHasIsScripted(pOther.mHasIsScripted), + mLinksetUse(pOther.mLinksetUse), + mWalkabilityCoefficientA(pOther.mWalkabilityCoefficientA), + mWalkabilityCoefficientB(pOther.mWalkabilityCoefficientB), + mWalkabilityCoefficientC(pOther.mWalkabilityCoefficientC), + mWalkabilityCoefficientD(pOther.mWalkabilityCoefficientD) +{ +} + +LLPathfindingLinkset::~LLPathfindingLinkset() +{ +} + +LLPathfindingLinkset& LLPathfindingLinkset::operator =(const LLPathfindingLinkset& pOther) +{ + dynamic_cast(*this) = pOther; + + mIsTerrain = pOther.mIsTerrain; + mLandImpact = pOther.mLandImpact; + mIsModifiable = pOther.mIsModifiable; + mCanBeVolume = pOther.mCanBeVolume; + mIsScripted = pOther.mIsScripted; + mHasIsScripted = pOther.mHasIsScripted; + mLinksetUse = pOther.mLinksetUse; + mWalkabilityCoefficientA = pOther.mWalkabilityCoefficientA; + mWalkabilityCoefficientB = pOther.mWalkabilityCoefficientB; + mWalkabilityCoefficientC = pOther.mWalkabilityCoefficientC; + mWalkabilityCoefficientD = pOther.mWalkabilityCoefficientD; + + return *this; +} + +bool LLPathfindingLinkset::isPhantom() const +{ + return isPhantom(getLinksetUse()); +} + +LLPathfindingLinkset::ELinksetUse LLPathfindingLinkset::getLinksetUseWithToggledPhantom(ELinksetUse pLinksetUse) +{ + bool isPhantom = LLPathfindingLinkset::isPhantom(pLinksetUse); + ENavMeshGenerationCategory navMeshGenerationCategory = getNavMeshGenerationCategory(pLinksetUse); + + return getLinksetUse(!isPhantom, navMeshGenerationCategory); +} + +bool LLPathfindingLinkset::isShowUnmodifiablePhantomWarning(ELinksetUse pLinksetUse) const +{ + return (!isModifiable() && (isPhantom() != isPhantom(pLinksetUse))); +} + +bool LLPathfindingLinkset::isShowPhantomToggleWarning(ELinksetUse pLinksetUse) const +{ + return (isModifiable() && (isPhantom() != isPhantom(pLinksetUse))); +} + +bool LLPathfindingLinkset::isShowCannotBeVolumeWarning(ELinksetUse pLinksetUse) const +{ + return (!canBeVolume() && ((pLinksetUse == kMaterialVolume) || (pLinksetUse == kExclusionVolume))); +} + +LLSD LLPathfindingLinkset::encodeAlteredFields(ELinksetUse pLinksetUse, S32 pA, S32 pB, S32 pC, S32 pD) const +{ + LLSD itemData; + + if (!isTerrain() && (pLinksetUse != kUnknown) && (getLinksetUse() != pLinksetUse) && + (canBeVolume() || ((pLinksetUse != kMaterialVolume) && (pLinksetUse != kExclusionVolume)))) + { + if (isModifiable()) + { + itemData[LINKSET_PHANTOM_FIELD] = static_cast(isPhantom(pLinksetUse)); + } + + itemData[LINKSET_CATEGORY_FIELD] = convertCategoryToLLSD(getNavMeshGenerationCategory(pLinksetUse)); + } + + if (mWalkabilityCoefficientA != pA) + { + itemData[LINKSET_WALKABILITY_A_FIELD] = llclamp(pA, MIN_WALKABILITY_VALUE, MAX_WALKABILITY_VALUE); + } + + if (mWalkabilityCoefficientB != pB) + { + itemData[LINKSET_WALKABILITY_B_FIELD] = llclamp(pB, MIN_WALKABILITY_VALUE, MAX_WALKABILITY_VALUE); + } + + if (mWalkabilityCoefficientC != pC) + { + itemData[LINKSET_WALKABILITY_C_FIELD] = llclamp(pC, MIN_WALKABILITY_VALUE, MAX_WALKABILITY_VALUE); + } + + if (mWalkabilityCoefficientD != pD) + { + itemData[LINKSET_WALKABILITY_D_FIELD] = llclamp(pD, MIN_WALKABILITY_VALUE, MAX_WALKABILITY_VALUE); + } + + return itemData; +} + +void LLPathfindingLinkset::parseLinksetData(const LLSD &pLinksetData) +{ + llassert(pLinksetData.has(LINKSET_LAND_IMPACT_FIELD)); + llassert(pLinksetData.get(LINKSET_LAND_IMPACT_FIELD).isInteger()); + llassert(pLinksetData.get(LINKSET_LAND_IMPACT_FIELD).asInteger() >= 0); + mLandImpact = pLinksetData.get(LINKSET_LAND_IMPACT_FIELD).asInteger(); + + llassert(pLinksetData.has(LINKSET_MODIFIABLE_FIELD)); + llassert(pLinksetData.get(LINKSET_MODIFIABLE_FIELD).isBoolean()); + mIsModifiable = pLinksetData.get(LINKSET_MODIFIABLE_FIELD).asBoolean(); + + mHasIsScripted = pLinksetData.has(LINKSET_IS_SCRIPTED_FIELD); + if (mHasIsScripted) + { + llassert(pLinksetData.get(LINKSET_IS_SCRIPTED_FIELD).isBoolean()); + mIsScripted = pLinksetData.get(LINKSET_IS_SCRIPTED_FIELD).asBoolean(); + } +} + +void LLPathfindingLinkset::parsePathfindingData(const LLSD &pLinksetData) +{ + bool isPhantom = false; + if (pLinksetData.has(LINKSET_PHANTOM_FIELD)) + { + llassert(pLinksetData.get(LINKSET_PHANTOM_FIELD).isBoolean()); + isPhantom = pLinksetData.get(LINKSET_PHANTOM_FIELD).asBoolean(); + } + + llassert(pLinksetData.has(LINKSET_CATEGORY_FIELD)); + mLinksetUse = getLinksetUse(isPhantom, convertCategoryFromLLSD(pLinksetData.get(LINKSET_CATEGORY_FIELD))); + + if (pLinksetData.has(LINKSET_CAN_BE_VOLUME)) + { + llassert(pLinksetData.get(LINKSET_CAN_BE_VOLUME).isBoolean()); + mCanBeVolume = pLinksetData.get(LINKSET_CAN_BE_VOLUME).asBoolean(); + } + + llassert(pLinksetData.has(LINKSET_WALKABILITY_A_FIELD)); + llassert(pLinksetData.get(LINKSET_WALKABILITY_A_FIELD).isInteger()); + mWalkabilityCoefficientA = pLinksetData.get(LINKSET_WALKABILITY_A_FIELD).asInteger(); + llassert(mWalkabilityCoefficientA >= MIN_WALKABILITY_VALUE); + llassert(mWalkabilityCoefficientA <= MAX_WALKABILITY_VALUE); + + llassert(pLinksetData.has(LINKSET_WALKABILITY_B_FIELD)); + llassert(pLinksetData.get(LINKSET_WALKABILITY_B_FIELD).isInteger()); + mWalkabilityCoefficientB = pLinksetData.get(LINKSET_WALKABILITY_B_FIELD).asInteger(); + llassert(mWalkabilityCoefficientB >= MIN_WALKABILITY_VALUE); + llassert(mWalkabilityCoefficientB <= MAX_WALKABILITY_VALUE); + + llassert(pLinksetData.has(LINKSET_WALKABILITY_C_FIELD)); + llassert(pLinksetData.get(LINKSET_WALKABILITY_C_FIELD).isInteger()); + mWalkabilityCoefficientC = pLinksetData.get(LINKSET_WALKABILITY_C_FIELD).asInteger(); + llassert(mWalkabilityCoefficientC >= MIN_WALKABILITY_VALUE); + llassert(mWalkabilityCoefficientC <= MAX_WALKABILITY_VALUE); + + llassert(pLinksetData.has(LINKSET_WALKABILITY_D_FIELD)); + llassert(pLinksetData.get(LINKSET_WALKABILITY_D_FIELD).isInteger()); + mWalkabilityCoefficientD = pLinksetData.get(LINKSET_WALKABILITY_D_FIELD).asInteger(); + llassert(mWalkabilityCoefficientD >= MIN_WALKABILITY_VALUE); + llassert(mWalkabilityCoefficientD <= MAX_WALKABILITY_VALUE); +} + +bool LLPathfindingLinkset::isPhantom(ELinksetUse pLinksetUse) +{ + bool retVal; + + switch (pLinksetUse) + { + case kWalkable : + case kStaticObstacle : + case kDynamicObstacle : + retVal = false; + break; + case kMaterialVolume : + case kExclusionVolume : + case kDynamicPhantom : + retVal = true; + break; + case kUnknown : + default : + retVal = false; + llassert(0); + break; + } + + return retVal; +} + +LLPathfindingLinkset::ELinksetUse LLPathfindingLinkset::getLinksetUse(bool pIsPhantom, ENavMeshGenerationCategory pNavMeshGenerationCategory) +{ + ELinksetUse linksetUse = kUnknown; + + if (pIsPhantom) + { + switch (pNavMeshGenerationCategory) + { + case kNavMeshGenerationIgnore : + linksetUse = kDynamicPhantom; + break; + case kNavMeshGenerationInclude : + linksetUse = kMaterialVolume; + break; + case kNavMeshGenerationExclude : + linksetUse = kExclusionVolume; + break; + default : + linksetUse = kUnknown; + llassert(0); + break; + } + } + else + { + switch (pNavMeshGenerationCategory) + { + case kNavMeshGenerationIgnore : + linksetUse = kDynamicObstacle; + break; + case kNavMeshGenerationInclude : + linksetUse = kWalkable; + break; + case kNavMeshGenerationExclude : + linksetUse = kStaticObstacle; + break; + default : + linksetUse = kUnknown; + llassert(0); + break; + } + } + + return linksetUse; +} + +LLPathfindingLinkset::ENavMeshGenerationCategory LLPathfindingLinkset::getNavMeshGenerationCategory(ELinksetUse pLinksetUse) +{ + ENavMeshGenerationCategory navMeshGenerationCategory; + switch (pLinksetUse) + { + case kWalkable : + case kMaterialVolume : + navMeshGenerationCategory = kNavMeshGenerationInclude; + break; + case kStaticObstacle : + case kExclusionVolume : + navMeshGenerationCategory = kNavMeshGenerationExclude; + break; + case kDynamicObstacle : + case kDynamicPhantom : + navMeshGenerationCategory = kNavMeshGenerationIgnore; + break; + case kUnknown : + default : + navMeshGenerationCategory = kNavMeshGenerationIgnore; + llassert(0); + break; + } + + return navMeshGenerationCategory; +} + +LLSD LLPathfindingLinkset::convertCategoryToLLSD(ENavMeshGenerationCategory pNavMeshGenerationCategory) +{ + LLSD llsd; + + switch (pNavMeshGenerationCategory) + { + case kNavMeshGenerationIgnore : + llsd = static_cast(LINKSET_CATEGORY_VALUE_IGNORE); + break; + case kNavMeshGenerationInclude : + llsd = static_cast(LINKSET_CATEGORY_VALUE_INCLUDE); + break; + case kNavMeshGenerationExclude : + llsd = static_cast(LINKSET_CATEGORY_VALUE_EXCLUDE); + break; + default : + llsd = static_cast(LINKSET_CATEGORY_VALUE_IGNORE); + llassert(0); + break; + } + + return llsd; +} + +LLPathfindingLinkset::ENavMeshGenerationCategory LLPathfindingLinkset::convertCategoryFromLLSD(const LLSD &llsd) +{ + ENavMeshGenerationCategory navMeshGenerationCategory; + + llassert(llsd.isInteger()); + switch (llsd.asInteger()) + { + case LINKSET_CATEGORY_VALUE_IGNORE : + navMeshGenerationCategory = kNavMeshGenerationIgnore; + break; + case LINKSET_CATEGORY_VALUE_INCLUDE : + navMeshGenerationCategory = kNavMeshGenerationInclude; + break; + case LINKSET_CATEGORY_VALUE_EXCLUDE : + navMeshGenerationCategory = kNavMeshGenerationExclude; + break; + default : + navMeshGenerationCategory = kNavMeshGenerationIgnore; + llassert(0); + break; + } + + return navMeshGenerationCategory; +} diff --git a/indra/newview/llpathfindinglinkset.h b/indra/newview/llpathfindinglinkset.h index 05acfe58b4..0f2268861f 100644 --- a/indra/newview/llpathfindinglinkset.h +++ b/indra/newview/llpathfindinglinkset.h @@ -1,114 +1,114 @@ -/** -* @file llpathfindinglinkset.h -* @brief Definition of a pathfinding linkset that contains various properties required for havok pathfinding. -* @author Stinson@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ -#ifndef LL_LLPATHFINDINGLINKSET_H -#define LL_LLPATHFINDINGLINKSET_H - -#include - -#include "llpathfindingobject.h" - -class LLSD; - -class LLPathfindingLinkset : public LLPathfindingObject -{ -public: - typedef enum - { - kUnknown, - kWalkable, - kStaticObstacle, - kDynamicObstacle, - kMaterialVolume, - kExclusionVolume, - kDynamicPhantom - } ELinksetUse; - - LLPathfindingLinkset(const LLSD &pTerrainData); - LLPathfindingLinkset(const std::string &pUUID, const LLSD &pLinksetData); - LLPathfindingLinkset(const LLPathfindingLinkset& pOther); - virtual ~LLPathfindingLinkset(); - - LLPathfindingLinkset& operator = (const LLPathfindingLinkset& pOther); - - inline bool isTerrain() const {return mIsTerrain;}; - inline U32 getLandImpact() const {return mLandImpact;}; - bool isModifiable() const {return mIsModifiable;}; - bool isPhantom() const; - bool canBeVolume() const {return mCanBeVolume;}; - static ELinksetUse getLinksetUseWithToggledPhantom(ELinksetUse pLinksetUse); - - inline ELinksetUse getLinksetUse() const {return mLinksetUse;}; - - inline bool isScripted() const {return mIsScripted;}; - inline bool hasIsScripted() const {return mHasIsScripted;}; - - inline S32 getWalkabilityCoefficientA() const {return mWalkabilityCoefficientA;}; - inline S32 getWalkabilityCoefficientB() const {return mWalkabilityCoefficientB;}; - inline S32 getWalkabilityCoefficientC() const {return mWalkabilityCoefficientC;}; - inline S32 getWalkabilityCoefficientD() const {return mWalkabilityCoefficientD;}; - - bool isShowUnmodifiablePhantomWarning(ELinksetUse pLinksetUse) const; - bool isShowPhantomToggleWarning(ELinksetUse pLinksetUse) const; - bool isShowCannotBeVolumeWarning(ELinksetUse pLinksetUse) const; - LLSD encodeAlteredFields(ELinksetUse pLinksetUse, S32 pA, S32 pB, S32 pC, S32 pD) const; - - static const S32 MIN_WALKABILITY_VALUE; - static const S32 MAX_WALKABILITY_VALUE; - -protected: - -private: - typedef enum - { - kNavMeshGenerationIgnore, - kNavMeshGenerationInclude, - kNavMeshGenerationExclude - } ENavMeshGenerationCategory; - - void parseLinksetData(const LLSD &pLinksetData); - void parsePathfindingData(const LLSD &pLinksetData); - - static bool isPhantom(ELinksetUse pLinksetUse); - static ELinksetUse getLinksetUse(bool pIsPhantom, ENavMeshGenerationCategory pNavMeshGenerationCategory); - static ENavMeshGenerationCategory getNavMeshGenerationCategory(ELinksetUse pLinksetUse); - static LLSD convertCategoryToLLSD(ENavMeshGenerationCategory pNavMeshGenerationCategory); - static ENavMeshGenerationCategory convertCategoryFromLLSD(const LLSD &llsd); - - bool mIsTerrain; - U32 mLandImpact; - bool mIsModifiable; - bool mCanBeVolume; - bool mIsScripted; - bool mHasIsScripted; - ELinksetUse mLinksetUse; - S32 mWalkabilityCoefficientA; - S32 mWalkabilityCoefficientB; - S32 mWalkabilityCoefficientC; - S32 mWalkabilityCoefficientD; -}; - -#endif // LL_LLPATHFINDINGLINKSET_H +/** +* @file llpathfindinglinkset.h +* @brief Definition of a pathfinding linkset that contains various properties required for havok pathfinding. +* @author Stinson@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LL_LLPATHFINDINGLINKSET_H +#define LL_LLPATHFINDINGLINKSET_H + +#include + +#include "llpathfindingobject.h" + +class LLSD; + +class LLPathfindingLinkset : public LLPathfindingObject +{ +public: + typedef enum + { + kUnknown, + kWalkable, + kStaticObstacle, + kDynamicObstacle, + kMaterialVolume, + kExclusionVolume, + kDynamicPhantom + } ELinksetUse; + + LLPathfindingLinkset(const LLSD &pTerrainData); + LLPathfindingLinkset(const std::string &pUUID, const LLSD &pLinksetData); + LLPathfindingLinkset(const LLPathfindingLinkset& pOther); + virtual ~LLPathfindingLinkset(); + + LLPathfindingLinkset& operator = (const LLPathfindingLinkset& pOther); + + inline bool isTerrain() const {return mIsTerrain;}; + inline U32 getLandImpact() const {return mLandImpact;}; + bool isModifiable() const {return mIsModifiable;}; + bool isPhantom() const; + bool canBeVolume() const {return mCanBeVolume;}; + static ELinksetUse getLinksetUseWithToggledPhantom(ELinksetUse pLinksetUse); + + inline ELinksetUse getLinksetUse() const {return mLinksetUse;}; + + inline bool isScripted() const {return mIsScripted;}; + inline bool hasIsScripted() const {return mHasIsScripted;}; + + inline S32 getWalkabilityCoefficientA() const {return mWalkabilityCoefficientA;}; + inline S32 getWalkabilityCoefficientB() const {return mWalkabilityCoefficientB;}; + inline S32 getWalkabilityCoefficientC() const {return mWalkabilityCoefficientC;}; + inline S32 getWalkabilityCoefficientD() const {return mWalkabilityCoefficientD;}; + + bool isShowUnmodifiablePhantomWarning(ELinksetUse pLinksetUse) const; + bool isShowPhantomToggleWarning(ELinksetUse pLinksetUse) const; + bool isShowCannotBeVolumeWarning(ELinksetUse pLinksetUse) const; + LLSD encodeAlteredFields(ELinksetUse pLinksetUse, S32 pA, S32 pB, S32 pC, S32 pD) const; + + static const S32 MIN_WALKABILITY_VALUE; + static const S32 MAX_WALKABILITY_VALUE; + +protected: + +private: + typedef enum + { + kNavMeshGenerationIgnore, + kNavMeshGenerationInclude, + kNavMeshGenerationExclude + } ENavMeshGenerationCategory; + + void parseLinksetData(const LLSD &pLinksetData); + void parsePathfindingData(const LLSD &pLinksetData); + + static bool isPhantom(ELinksetUse pLinksetUse); + static ELinksetUse getLinksetUse(bool pIsPhantom, ENavMeshGenerationCategory pNavMeshGenerationCategory); + static ENavMeshGenerationCategory getNavMeshGenerationCategory(ELinksetUse pLinksetUse); + static LLSD convertCategoryToLLSD(ENavMeshGenerationCategory pNavMeshGenerationCategory); + static ENavMeshGenerationCategory convertCategoryFromLLSD(const LLSD &llsd); + + bool mIsTerrain; + U32 mLandImpact; + bool mIsModifiable; + bool mCanBeVolume; + bool mIsScripted; + bool mHasIsScripted; + ELinksetUse mLinksetUse; + S32 mWalkabilityCoefficientA; + S32 mWalkabilityCoefficientB; + S32 mWalkabilityCoefficientC; + S32 mWalkabilityCoefficientD; +}; + +#endif // LL_LLPATHFINDINGLINKSET_H diff --git a/indra/newview/llpathfindinglinksetlist.cpp b/indra/newview/llpathfindinglinksetlist.cpp index 497b132a97..2012d7a094 100644 --- a/indra/newview/llpathfindinglinksetlist.cpp +++ b/indra/newview/llpathfindinglinksetlist.cpp @@ -1,213 +1,213 @@ -/** -* @file llpathfindinglinksetlist.cpp -* @brief Implementation of llpathfindinglinksetlist -* @author Stinson@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - - -#include "llviewerprecompiledheaders.h" - -#include "llpathfindinglinksetlist.h" - -#include -#include - -#include "llpathfindinglinkset.h" -#include "llpathfindingobject.h" -#include "llpathfindingobjectlist.h" -#include "llsd.h" - -//--------------------------------------------------------------------------- -// LLPathfindingLinksetList -//--------------------------------------------------------------------------- - -LLPathfindingLinksetList::LLPathfindingLinksetList() - : LLPathfindingObjectList() -{ -} - -LLPathfindingLinksetList::LLPathfindingLinksetList(const LLSD& pLinksetListData) - : LLPathfindingObjectList() -{ - parseLinksetListData(pLinksetListData); -} - -LLPathfindingLinksetList::~LLPathfindingLinksetList() -{ -} - -LLSD LLPathfindingLinksetList::encodeObjectFields(LLPathfindingLinkset::ELinksetUse pLinksetUse, S32 pA, S32 pB, S32 pC, S32 pD) const -{ - LLSD listData; - - for (const_iterator linksetIter = begin(); linksetIter != end(); ++linksetIter) - { - const LLPathfindingObjectPtr objectPtr = linksetIter->second; - const LLPathfindingLinkset *linkset = dynamic_cast(objectPtr.get()); - - if (!linkset->isTerrain()) - { - LLSD linksetData = linkset->encodeAlteredFields(pLinksetUse, pA, pB, pC, pD); - if (!linksetData.isUndefined()) - { - const std::string& uuid(linksetIter->first); - listData[uuid] = linksetData; - } - } - } - - return listData; -} - -LLSD LLPathfindingLinksetList::encodeTerrainFields(LLPathfindingLinkset::ELinksetUse pLinksetUse, S32 pA, S32 pB, S32 pC, S32 pD) const -{ - LLSD terrainData; - - for (const_iterator linksetIter = begin(); linksetIter != end(); ++linksetIter) - { - const LLPathfindingObjectPtr objectPtr = linksetIter->second; - const LLPathfindingLinkset *linkset = dynamic_cast(objectPtr.get()); - - if (linkset->isTerrain()) - { - terrainData = linkset->encodeAlteredFields(pLinksetUse, pA, pB, pC, pD); - break; - } - } - - return terrainData; -} - -bool LLPathfindingLinksetList::isShowUnmodifiablePhantomWarning(LLPathfindingLinkset::ELinksetUse pLinksetUse) const -{ - bool isShowWarning = false; - - for (const_iterator objectIter = begin(); !isShowWarning && (objectIter != end()); ++objectIter) - { - const LLPathfindingObjectPtr objectPtr = objectIter->second; - const LLPathfindingLinkset *linkset = dynamic_cast(objectPtr.get()); - isShowWarning = linkset->isShowUnmodifiablePhantomWarning(pLinksetUse); - } - - return isShowWarning; -} - -bool LLPathfindingLinksetList::isShowPhantomToggleWarning(LLPathfindingLinkset::ELinksetUse pLinksetUse) const -{ - bool isShowWarning = false; - - for (const_iterator objectIter = begin(); !isShowWarning && (objectIter != end()); ++objectIter) - { - const LLPathfindingObjectPtr objectPtr = objectIter->second; - const LLPathfindingLinkset *linkset = dynamic_cast(objectPtr.get()); - isShowWarning = linkset->isShowPhantomToggleWarning(pLinksetUse); - } - - return isShowWarning; -} - -bool LLPathfindingLinksetList::isShowCannotBeVolumeWarning(LLPathfindingLinkset::ELinksetUse pLinksetUse) const -{ - bool isShowWarning = false; - - for (const_iterator objectIter = begin(); !isShowWarning && (objectIter != end()); ++objectIter) - { - const LLPathfindingObjectPtr objectPtr = objectIter->second; - const LLPathfindingLinkset *linkset = dynamic_cast(objectPtr.get()); - isShowWarning = linkset->isShowCannotBeVolumeWarning(pLinksetUse); - } - - return isShowWarning; -} - -void LLPathfindingLinksetList::determinePossibleStates(bool &pCanBeWalkable, bool &pCanBeStaticObstacle, bool &pCanBeDynamicObstacle, - bool &pCanBeMaterialVolume, bool &pCanBeExclusionVolume, bool &pCanBeDynamicPhantom) const -{ - pCanBeWalkable = false; - pCanBeStaticObstacle = false; - pCanBeDynamicObstacle = false; - pCanBeMaterialVolume = false; - pCanBeExclusionVolume = false; - pCanBeDynamicPhantom = false; - - for (const_iterator objectIter = begin(); - !(pCanBeWalkable && pCanBeStaticObstacle && pCanBeDynamicObstacle && pCanBeMaterialVolume && pCanBeExclusionVolume && pCanBeDynamicPhantom) && (objectIter != end()); - ++objectIter) - { - const LLPathfindingObjectPtr objectPtr = objectIter->second; - const LLPathfindingLinkset *linkset = dynamic_cast(objectPtr.get()); - - if (linkset->isTerrain()) - { - pCanBeWalkable = true; - } - else - { - if (linkset->isModifiable()) - { - pCanBeWalkable = true; - pCanBeStaticObstacle = true; - pCanBeDynamicObstacle = true; - pCanBeDynamicPhantom = true; - if (linkset->canBeVolume()) - { - pCanBeMaterialVolume = true; - pCanBeExclusionVolume = true; - } - } - else if (linkset->isPhantom()) - { - pCanBeDynamicPhantom = true; - if (linkset->canBeVolume()) - { - pCanBeMaterialVolume = true; - pCanBeExclusionVolume = true; - } - } - else - { - pCanBeWalkable = true; - pCanBeStaticObstacle = true; - pCanBeDynamicObstacle = true; - } - } - } -} - -void LLPathfindingLinksetList::parseLinksetListData(const LLSD& pLinksetListData) -{ - LLPathfindingObjectMap &objectMap = getObjectMap(); - - for (LLSD::map_const_iterator linksetDataIter = pLinksetListData.beginMap(); - linksetDataIter != pLinksetListData.endMap(); ++linksetDataIter) - { - const std::string& uuid(linksetDataIter->first); - const LLSD& linksetData = linksetDataIter->second; - if(linksetData.size() != 0) - { - LLPathfindingObjectPtr linksetPtr(new LLPathfindingLinkset(uuid, linksetData)); - objectMap.insert(std::pair(uuid, linksetPtr)); - } - } -} +/** +* @file llpathfindinglinksetlist.cpp +* @brief Implementation of llpathfindinglinksetlist +* @author Stinson@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + + +#include "llviewerprecompiledheaders.h" + +#include "llpathfindinglinksetlist.h" + +#include +#include + +#include "llpathfindinglinkset.h" +#include "llpathfindingobject.h" +#include "llpathfindingobjectlist.h" +#include "llsd.h" + +//--------------------------------------------------------------------------- +// LLPathfindingLinksetList +//--------------------------------------------------------------------------- + +LLPathfindingLinksetList::LLPathfindingLinksetList() + : LLPathfindingObjectList() +{ +} + +LLPathfindingLinksetList::LLPathfindingLinksetList(const LLSD& pLinksetListData) + : LLPathfindingObjectList() +{ + parseLinksetListData(pLinksetListData); +} + +LLPathfindingLinksetList::~LLPathfindingLinksetList() +{ +} + +LLSD LLPathfindingLinksetList::encodeObjectFields(LLPathfindingLinkset::ELinksetUse pLinksetUse, S32 pA, S32 pB, S32 pC, S32 pD) const +{ + LLSD listData; + + for (const_iterator linksetIter = begin(); linksetIter != end(); ++linksetIter) + { + const LLPathfindingObjectPtr objectPtr = linksetIter->second; + const LLPathfindingLinkset *linkset = dynamic_cast(objectPtr.get()); + + if (!linkset->isTerrain()) + { + LLSD linksetData = linkset->encodeAlteredFields(pLinksetUse, pA, pB, pC, pD); + if (!linksetData.isUndefined()) + { + const std::string& uuid(linksetIter->first); + listData[uuid] = linksetData; + } + } + } + + return listData; +} + +LLSD LLPathfindingLinksetList::encodeTerrainFields(LLPathfindingLinkset::ELinksetUse pLinksetUse, S32 pA, S32 pB, S32 pC, S32 pD) const +{ + LLSD terrainData; + + for (const_iterator linksetIter = begin(); linksetIter != end(); ++linksetIter) + { + const LLPathfindingObjectPtr objectPtr = linksetIter->second; + const LLPathfindingLinkset *linkset = dynamic_cast(objectPtr.get()); + + if (linkset->isTerrain()) + { + terrainData = linkset->encodeAlteredFields(pLinksetUse, pA, pB, pC, pD); + break; + } + } + + return terrainData; +} + +bool LLPathfindingLinksetList::isShowUnmodifiablePhantomWarning(LLPathfindingLinkset::ELinksetUse pLinksetUse) const +{ + bool isShowWarning = false; + + for (const_iterator objectIter = begin(); !isShowWarning && (objectIter != end()); ++objectIter) + { + const LLPathfindingObjectPtr objectPtr = objectIter->second; + const LLPathfindingLinkset *linkset = dynamic_cast(objectPtr.get()); + isShowWarning = linkset->isShowUnmodifiablePhantomWarning(pLinksetUse); + } + + return isShowWarning; +} + +bool LLPathfindingLinksetList::isShowPhantomToggleWarning(LLPathfindingLinkset::ELinksetUse pLinksetUse) const +{ + bool isShowWarning = false; + + for (const_iterator objectIter = begin(); !isShowWarning && (objectIter != end()); ++objectIter) + { + const LLPathfindingObjectPtr objectPtr = objectIter->second; + const LLPathfindingLinkset *linkset = dynamic_cast(objectPtr.get()); + isShowWarning = linkset->isShowPhantomToggleWarning(pLinksetUse); + } + + return isShowWarning; +} + +bool LLPathfindingLinksetList::isShowCannotBeVolumeWarning(LLPathfindingLinkset::ELinksetUse pLinksetUse) const +{ + bool isShowWarning = false; + + for (const_iterator objectIter = begin(); !isShowWarning && (objectIter != end()); ++objectIter) + { + const LLPathfindingObjectPtr objectPtr = objectIter->second; + const LLPathfindingLinkset *linkset = dynamic_cast(objectPtr.get()); + isShowWarning = linkset->isShowCannotBeVolumeWarning(pLinksetUse); + } + + return isShowWarning; +} + +void LLPathfindingLinksetList::determinePossibleStates(bool &pCanBeWalkable, bool &pCanBeStaticObstacle, bool &pCanBeDynamicObstacle, + bool &pCanBeMaterialVolume, bool &pCanBeExclusionVolume, bool &pCanBeDynamicPhantom) const +{ + pCanBeWalkable = false; + pCanBeStaticObstacle = false; + pCanBeDynamicObstacle = false; + pCanBeMaterialVolume = false; + pCanBeExclusionVolume = false; + pCanBeDynamicPhantom = false; + + for (const_iterator objectIter = begin(); + !(pCanBeWalkable && pCanBeStaticObstacle && pCanBeDynamicObstacle && pCanBeMaterialVolume && pCanBeExclusionVolume && pCanBeDynamicPhantom) && (objectIter != end()); + ++objectIter) + { + const LLPathfindingObjectPtr objectPtr = objectIter->second; + const LLPathfindingLinkset *linkset = dynamic_cast(objectPtr.get()); + + if (linkset->isTerrain()) + { + pCanBeWalkable = true; + } + else + { + if (linkset->isModifiable()) + { + pCanBeWalkable = true; + pCanBeStaticObstacle = true; + pCanBeDynamicObstacle = true; + pCanBeDynamicPhantom = true; + if (linkset->canBeVolume()) + { + pCanBeMaterialVolume = true; + pCanBeExclusionVolume = true; + } + } + else if (linkset->isPhantom()) + { + pCanBeDynamicPhantom = true; + if (linkset->canBeVolume()) + { + pCanBeMaterialVolume = true; + pCanBeExclusionVolume = true; + } + } + else + { + pCanBeWalkable = true; + pCanBeStaticObstacle = true; + pCanBeDynamicObstacle = true; + } + } + } +} + +void LLPathfindingLinksetList::parseLinksetListData(const LLSD& pLinksetListData) +{ + LLPathfindingObjectMap &objectMap = getObjectMap(); + + for (LLSD::map_const_iterator linksetDataIter = pLinksetListData.beginMap(); + linksetDataIter != pLinksetListData.endMap(); ++linksetDataIter) + { + const std::string& uuid(linksetDataIter->first); + const LLSD& linksetData = linksetDataIter->second; + if(linksetData.size() != 0) + { + LLPathfindingObjectPtr linksetPtr(new LLPathfindingLinkset(uuid, linksetData)); + objectMap.insert(std::pair(uuid, linksetPtr)); + } + } +} diff --git a/indra/newview/llpathfindinglinksetlist.h b/indra/newview/llpathfindinglinksetlist.h index e50997c4ff..12b418ada0 100644 --- a/indra/newview/llpathfindinglinksetlist.h +++ b/indra/newview/llpathfindinglinksetlist.h @@ -1,58 +1,58 @@ -/** -* @file llpathfindinglinksetlist.h -* @brief Header file for llpathfindinglinksetlist -* @author Stinson@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ -#ifndef LL_LLPATHFINDINGLINKSETLIST_H -#define LL_LLPATHFINDINGLINKSETLIST_H - -#include "llpathfindinglinkset.h" -#include "llpathfindingobjectlist.h" - -class LLSD; - -class LLPathfindingLinksetList : public LLPathfindingObjectList -{ -public: - LLPathfindingLinksetList(); - LLPathfindingLinksetList(const LLSD& pLinksetListData); - virtual ~LLPathfindingLinksetList(); - - LLSD encodeObjectFields(LLPathfindingLinkset::ELinksetUse pLinksetUse, S32 pA, S32 pB, S32 pC, S32 pD) const; - LLSD encodeTerrainFields(LLPathfindingLinkset::ELinksetUse pLinksetUse, S32 pA, S32 pB, S32 pC, S32 pD) const; - - bool isShowUnmodifiablePhantomWarning(LLPathfindingLinkset::ELinksetUse pLinksetUse) const; - bool isShowPhantomToggleWarning(LLPathfindingLinkset::ELinksetUse pLinksetUse) const; - bool isShowCannotBeVolumeWarning(LLPathfindingLinkset::ELinksetUse pLinksetUse) const; - - void determinePossibleStates(bool &pCanBeWalkable, bool &pCanBeStaticObstacle, bool &pCanBeDynamicObstacle, - bool &pCanBeMaterialVolume, bool &pCanBeExclusionVolume, bool &pCanBeDynamicPhantom) const; - -protected: - -private: - void parseLinksetListData(const LLSD& pLinksetListData); -}; - -#endif // LL_LLPATHFINDINGLINKSETLIST_H +/** +* @file llpathfindinglinksetlist.h +* @brief Header file for llpathfindinglinksetlist +* @author Stinson@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LL_LLPATHFINDINGLINKSETLIST_H +#define LL_LLPATHFINDINGLINKSETLIST_H + +#include "llpathfindinglinkset.h" +#include "llpathfindingobjectlist.h" + +class LLSD; + +class LLPathfindingLinksetList : public LLPathfindingObjectList +{ +public: + LLPathfindingLinksetList(); + LLPathfindingLinksetList(const LLSD& pLinksetListData); + virtual ~LLPathfindingLinksetList(); + + LLSD encodeObjectFields(LLPathfindingLinkset::ELinksetUse pLinksetUse, S32 pA, S32 pB, S32 pC, S32 pD) const; + LLSD encodeTerrainFields(LLPathfindingLinkset::ELinksetUse pLinksetUse, S32 pA, S32 pB, S32 pC, S32 pD) const; + + bool isShowUnmodifiablePhantomWarning(LLPathfindingLinkset::ELinksetUse pLinksetUse) const; + bool isShowPhantomToggleWarning(LLPathfindingLinkset::ELinksetUse pLinksetUse) const; + bool isShowCannotBeVolumeWarning(LLPathfindingLinkset::ELinksetUse pLinksetUse) const; + + void determinePossibleStates(bool &pCanBeWalkable, bool &pCanBeStaticObstacle, bool &pCanBeDynamicObstacle, + bool &pCanBeMaterialVolume, bool &pCanBeExclusionVolume, bool &pCanBeDynamicPhantom) const; + +protected: + +private: + void parseLinksetListData(const LLSD& pLinksetListData); +}; + +#endif // LL_LLPATHFINDINGLINKSETLIST_H diff --git a/indra/newview/llpathfindingmanager.cpp b/indra/newview/llpathfindingmanager.cpp index ed141d3920..5e7bc4fb3b 100644 --- a/indra/newview/llpathfindingmanager.cpp +++ b/indra/newview/llpathfindingmanager.cpp @@ -1,918 +1,918 @@ -/** -* @file llpathfindingmanager.cpp -* @brief Implementation of llpathfindingmanager -* @author Stinson@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - - -#include "llviewerprecompiledheaders.h" - -#include "llpathfindingmanager.h" - -#include -#include - -#include -#include -#include -#include - -#include "llagent.h" -#include "llhttpnode.h" -#include "llnotificationsutil.h" -#include "llpathfindingcharacterlist.h" -#include "llpathfindinglinkset.h" -#include "llpathfindinglinksetlist.h" -#include "llpathfindingnavmesh.h" -#include "llpathfindingnavmeshstatus.h" -#include "llpathfindingobject.h" -#include "llpathinglib.h" -#include "llsingleton.h" -#include "llsd.h" -#include "lltrans.h" -#include "lluuid.h" -#include "llviewerregion.h" -#include "llweb.h" -#include "llcorehttputil.h" -#include "llworld.h" - -#define CAP_SERVICE_RETRIEVE_NAVMESH "RetrieveNavMeshSrc" - -#define CAP_SERVICE_NAVMESH_STATUS "NavMeshGenerationStatus" - -#define CAP_SERVICE_GET_OBJECT_LINKSETS "RegionObjects" -#define CAP_SERVICE_SET_OBJECT_LINKSETS "ObjectNavMeshProperties" -#define CAP_SERVICE_TERRAIN_LINKSETS "TerrainNavMeshProperties" - -#define CAP_SERVICE_CHARACTERS "CharacterProperties" - -#define SIM_MESSAGE_NAVMESH_STATUS_UPDATE "/message/NavMeshStatusUpdate" -#define SIM_MESSAGE_AGENT_STATE_UPDATE "/message/AgentStateUpdate" -#define SIM_MESSAGE_BODY_FIELD "body" - -#define CAP_SERVICE_AGENT_STATE "AgentState" - -#define AGENT_STATE_CAN_REBAKE_REGION_FIELD "can_modify_navmesh" - -//--------------------------------------------------------------------------- -// LLNavMeshSimStateChangeNode -//--------------------------------------------------------------------------- - -class LLNavMeshSimStateChangeNode : public LLHTTPNode -{ -public: - virtual void post(ResponsePtr pResponse, const LLSD &pContext, const LLSD &pInput) const; -}; - -LLHTTPRegistration gHTTPRegistrationNavMeshSimStateChangeNode(SIM_MESSAGE_NAVMESH_STATUS_UPDATE); - - -//--------------------------------------------------------------------------- -// LLAgentStateChangeNode -//--------------------------------------------------------------------------- -class LLAgentStateChangeNode : public LLHTTPNode -{ -public: - virtual void post(ResponsePtr pResponse, const LLSD &pContext, const LLSD &pInput) const; -}; - -LLHTTPRegistration gHTTPRegistrationAgentStateChangeNode(SIM_MESSAGE_AGENT_STATE_UPDATE); - -//--------------------------------------------------------------------------- -// LinksetsResponder -//--------------------------------------------------------------------------- - -class LinksetsResponder -{ -public: - LinksetsResponder(LLPathfindingManager::request_id_t pRequestId, LLPathfindingManager::object_request_callback_t pLinksetsCallback, bool pIsObjectRequested, bool pIsTerrainRequested); - virtual ~LinksetsResponder(); - - void handleObjectLinksetsResult(const LLSD &pContent); - void handleObjectLinksetsError(); - void handleTerrainLinksetsResult(const LLSD &pContent); - void handleTerrainLinksetsError(); - - typedef std::shared_ptr ptr_t; - -protected: - -private: - void sendCallback(); - - typedef enum - { - kNotRequested, - kWaiting, - kReceivedGood, - kReceivedError - } EMessagingState; - - LLPathfindingManager::request_id_t mRequestId; - LLPathfindingManager::object_request_callback_t mLinksetsCallback; - - EMessagingState mObjectMessagingState; - EMessagingState mTerrainMessagingState; - - LLPathfindingObjectListPtr mObjectLinksetListPtr; - LLPathfindingObjectPtr mTerrainLinksetPtr; -}; - -typedef std::shared_ptr LinksetsResponderPtr; - -//--------------------------------------------------------------------------- -// LLPathfindingManager -//--------------------------------------------------------------------------- - -LLPathfindingManager::LLPathfindingManager(): - mNavMeshMap(), - mAgentStateSignal() -{ -} - -LLPathfindingManager::~LLPathfindingManager() -{ - quitSystem(); -} - -void LLPathfindingManager::initSystem() -{ - if (LLPathingLib::getInstance() == NULL) - { - LLPathingLib::initSystem(); - } -} - -void LLPathfindingManager::quitSystem() -{ - if (LLPathingLib::getInstance() != NULL) - { - LLPathingLib::quitSystem(); - } -} - -bool LLPathfindingManager::isPathfindingViewEnabled() const -{ - return (LLPathingLib::getInstance() != NULL); -} - -bool LLPathfindingManager::isPathfindingEnabledForCurrentRegion() const -{ - return isPathfindingEnabledForRegion(getCurrentRegion()); -} - -bool LLPathfindingManager::isPathfindingEnabledForRegion(LLViewerRegion *pRegion) const -{ - std::string retrieveNavMeshURL = getRetrieveNavMeshURLForRegion(pRegion); - return !retrieveNavMeshURL.empty(); -} - -bool LLPathfindingManager::isAllowViewTerrainProperties() const -{ - LLViewerRegion* region = getCurrentRegion(); - return (gAgent.isGodlike() || ((region != NULL) && region->canManageEstate())); -} - -LLPathfindingNavMesh::navmesh_slot_t LLPathfindingManager::registerNavMeshListenerForRegion(LLViewerRegion *pRegion, LLPathfindingNavMesh::navmesh_callback_t pNavMeshCallback) -{ - LLPathfindingNavMeshPtr navMeshPtr = getNavMeshForRegion(pRegion); - return navMeshPtr->registerNavMeshListener(pNavMeshCallback); -} - -void LLPathfindingManager::requestGetNavMeshForRegion(LLViewerRegion *pRegion, bool pIsGetStatusOnly) -{ - LLPathfindingNavMeshPtr navMeshPtr = getNavMeshForRegion(pRegion); - - if (pRegion == NULL) - { - navMeshPtr->handleNavMeshNotEnabled(); - } - else if (!pRegion->capabilitiesReceived()) - { - navMeshPtr->handleNavMeshWaitForRegionLoad(); - pRegion->setCapabilitiesReceivedCallback(boost::bind(&LLPathfindingManager::handleDeferredGetNavMeshForRegion, this, _1, pIsGetStatusOnly)); - } - else if (!isPathfindingEnabledForRegion(pRegion)) - { - navMeshPtr->handleNavMeshNotEnabled(); - } - else - { - std::string navMeshStatusURL = getNavMeshStatusURLForRegion(pRegion); - llassert(!navMeshStatusURL.empty()); - navMeshPtr->handleNavMeshCheckVersion(); - - U64 regionHandle = pRegion->getHandle(); - std::string coroname = LLCoros::instance().launch("LLPathfindingManager::navMeshStatusRequestCoro", - boost::bind(&LLPathfindingManager::navMeshStatusRequestCoro, this, navMeshStatusURL, regionHandle, pIsGetStatusOnly)); - } -} - -void LLPathfindingManager::requestGetLinksets(request_id_t pRequestId, object_request_callback_t pLinksetsCallback) const -{ - LLPathfindingObjectListPtr emptyLinksetListPtr; - LLViewerRegion *currentRegion = getCurrentRegion(); - - if (currentRegion == NULL) - { - pLinksetsCallback(pRequestId, kRequestNotEnabled, emptyLinksetListPtr); - } - else if (!currentRegion->capabilitiesReceived()) - { - pLinksetsCallback(pRequestId, kRequestStarted, emptyLinksetListPtr); - currentRegion->setCapabilitiesReceivedCallback(boost::bind(&LLPathfindingManager::handleDeferredGetLinksetsForRegion, this, _1, pRequestId, pLinksetsCallback)); - } - else - { - std::string objectLinksetsURL = getRetrieveObjectLinksetsURLForCurrentRegion(); - std::string terrainLinksetsURL = getTerrainLinksetsURLForCurrentRegion(); - if (objectLinksetsURL.empty() || terrainLinksetsURL.empty()) - { - pLinksetsCallback(pRequestId, kRequestNotEnabled, emptyLinksetListPtr); - } - else - { - pLinksetsCallback(pRequestId, kRequestStarted, emptyLinksetListPtr); - - bool doRequestTerrain = isAllowViewTerrainProperties(); - LinksetsResponder::ptr_t linksetsResponderPtr(new LinksetsResponder(pRequestId, pLinksetsCallback, true, doRequestTerrain)); - - std::string coroname = LLCoros::instance().launch("LLPathfindingManager::linksetObjectsCoro", - boost::bind(&LLPathfindingManager::linksetObjectsCoro, this, objectLinksetsURL, linksetsResponderPtr, LLSD())); - - if (doRequestTerrain) - { - std::string coroname = LLCoros::instance().launch("LLPathfindingManager::linksetTerrainCoro", - boost::bind(&LLPathfindingManager::linksetTerrainCoro, this, terrainLinksetsURL, linksetsResponderPtr, LLSD())); - } - } - } -} - -void LLPathfindingManager::requestSetLinksets(request_id_t pRequestId, const LLPathfindingObjectListPtr &pLinksetListPtr, LLPathfindingLinkset::ELinksetUse pLinksetUse, S32 pA, S32 pB, S32 pC, S32 pD, object_request_callback_t pLinksetsCallback) const -{ - LLPathfindingObjectListPtr emptyLinksetListPtr; - - std::string objectLinksetsURL = getChangeObjectLinksetsURLForCurrentRegion(); - std::string terrainLinksetsURL = getTerrainLinksetsURLForCurrentRegion(); - if (objectLinksetsURL.empty() || terrainLinksetsURL.empty()) - { - pLinksetsCallback(pRequestId, kRequestNotEnabled, emptyLinksetListPtr); - } - else if ((pLinksetListPtr == NULL) || pLinksetListPtr->isEmpty()) - { - pLinksetsCallback(pRequestId, kRequestCompleted, emptyLinksetListPtr); - } - else - { - const LLPathfindingLinksetList *linksetList = dynamic_cast(pLinksetListPtr.get()); - - LLSD objectPostData = linksetList->encodeObjectFields(pLinksetUse, pA, pB, pC, pD); - LLSD terrainPostData; - if (isAllowViewTerrainProperties()) - { - terrainPostData = linksetList->encodeTerrainFields(pLinksetUse, pA, pB, pC, pD); - } - - if (objectPostData.isUndefined() && terrainPostData.isUndefined()) - { - pLinksetsCallback(pRequestId, kRequestCompleted, emptyLinksetListPtr); - } - else - { - pLinksetsCallback(pRequestId, kRequestStarted, emptyLinksetListPtr); - - LinksetsResponder::ptr_t linksetsResponderPtr(new LinksetsResponder(pRequestId, pLinksetsCallback, !objectPostData.isUndefined(), !terrainPostData.isUndefined())); - - if (!objectPostData.isUndefined()) - { - std::string coroname = LLCoros::instance().launch("LLPathfindingManager::linksetObjectsCoro", - boost::bind(&LLPathfindingManager::linksetObjectsCoro, this, objectLinksetsURL, linksetsResponderPtr, objectPostData)); - } - - if (!terrainPostData.isUndefined()) - { - std::string coroname = LLCoros::instance().launch("LLPathfindingManager::linksetTerrainCoro", - boost::bind(&LLPathfindingManager::linksetTerrainCoro, this, terrainLinksetsURL, linksetsResponderPtr, terrainPostData)); - } - } - } -} - -void LLPathfindingManager::requestGetCharacters(request_id_t pRequestId, object_request_callback_t pCharactersCallback) const -{ - LLPathfindingObjectListPtr emptyCharacterListPtr; - - LLViewerRegion *currentRegion = getCurrentRegion(); - - if (currentRegion == NULL) - { - pCharactersCallback(pRequestId, kRequestNotEnabled, emptyCharacterListPtr); - } - else if (!currentRegion->capabilitiesReceived()) - { - pCharactersCallback(pRequestId, kRequestStarted, emptyCharacterListPtr); - currentRegion->setCapabilitiesReceivedCallback(boost::bind(&LLPathfindingManager::handleDeferredGetCharactersForRegion, this, _1, pRequestId, pCharactersCallback)); - } - else - { - std::string charactersURL = getCharactersURLForCurrentRegion(); - if (charactersURL.empty()) - { - pCharactersCallback(pRequestId, kRequestNotEnabled, emptyCharacterListPtr); - } - else - { - pCharactersCallback(pRequestId, kRequestStarted, emptyCharacterListPtr); - - std::string coroname = LLCoros::instance().launch("LLPathfindingManager::charactersCoro", - boost::bind(&LLPathfindingManager::charactersCoro, this, charactersURL, pRequestId, pCharactersCallback)); - } - } -} - -LLPathfindingManager::agent_state_slot_t LLPathfindingManager::registerAgentStateListener(agent_state_callback_t pAgentStateCallback) -{ - return mAgentStateSignal.connect(pAgentStateCallback); -} - -void LLPathfindingManager::requestGetAgentState() -{ - LLViewerRegion *currentRegion = getCurrentRegion(); - - if (currentRegion == NULL) - { - mAgentStateSignal(false); - } - else - { - if (!currentRegion->capabilitiesReceived()) - { - currentRegion->setCapabilitiesReceivedCallback(boost::bind(&LLPathfindingManager::handleDeferredGetAgentStateForRegion, this, _1)); - } - else if (!isPathfindingEnabledForRegion(currentRegion)) - { - mAgentStateSignal(false); - } - else - { - std::string agentStateURL = getAgentStateURLForRegion(currentRegion); - llassert(!agentStateURL.empty()); - - std::string coroname = LLCoros::instance().launch("LLPathfindingManager::navAgentStateRequestCoro", - boost::bind(&LLPathfindingManager::navAgentStateRequestCoro, this, agentStateURL)); - } - } -} - -void LLPathfindingManager::requestRebakeNavMesh(rebake_navmesh_callback_t pRebakeNavMeshCallback) -{ - LLViewerRegion *currentRegion = getCurrentRegion(); - - if (currentRegion == NULL) - { - pRebakeNavMeshCallback(false); - } - else if (!isPathfindingEnabledForRegion(currentRegion)) - { - pRebakeNavMeshCallback(false); - } - else - { - std::string navMeshStatusURL = getNavMeshStatusURLForCurrentRegion(); - llassert(!navMeshStatusURL.empty()); - - std::string coroname = LLCoros::instance().launch("LLPathfindingManager::navMeshRebakeCoro", - boost::bind(&LLPathfindingManager::navMeshRebakeCoro, this, navMeshStatusURL, pRebakeNavMeshCallback)); - } -} - -void LLPathfindingManager::handleDeferredGetAgentStateForRegion(const LLUUID &pRegionUUID) -{ - LLViewerRegion *currentRegion = getCurrentRegion(); - - if ((currentRegion != NULL) && (currentRegion->getRegionID() == pRegionUUID)) - { - requestGetAgentState(); - } -} - -void LLPathfindingManager::handleDeferredGetNavMeshForRegion(const LLUUID &pRegionUUID, bool pIsGetStatusOnly) -{ - LLViewerRegion *currentRegion = getCurrentRegion(); - - if ((currentRegion != NULL) && (currentRegion->getRegionID() == pRegionUUID)) - { - requestGetNavMeshForRegion(currentRegion, pIsGetStatusOnly); - } -} - -void LLPathfindingManager::handleDeferredGetLinksetsForRegion(const LLUUID &pRegionUUID, request_id_t pRequestId, object_request_callback_t pLinksetsCallback) const -{ - LLViewerRegion *currentRegion = getCurrentRegion(); - - if ((currentRegion != NULL) && (currentRegion->getRegionID() == pRegionUUID)) - { - requestGetLinksets(pRequestId, pLinksetsCallback); - } -} - -void LLPathfindingManager::handleDeferredGetCharactersForRegion(const LLUUID &pRegionUUID, request_id_t pRequestId, object_request_callback_t pCharactersCallback) const -{ - LLViewerRegion *currentRegion = getCurrentRegion(); - - if ((currentRegion != NULL) && (currentRegion->getRegionID() == pRegionUUID)) - { - requestGetCharacters(pRequestId, pCharactersCallback); - } -} - -void LLPathfindingManager::navMeshStatusRequestCoro(std::string url, U64 regionHandle, bool isGetStatusOnly) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("NavMeshStatusRequest", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLViewerRegion *region = LLWorld::getInstance()->getRegionFromHandle(regionHandle); - if (!region) - { - LL_WARNS("PathfindingManager") << "Attempting to retrieve navmesh status for region that has gone away." << LL_ENDL; - return; - } - LLUUID regionUUID = region->getRegionID(); - - region = NULL; - LLSD result = httpAdapter->getAndSuspend(httpRequest, url); - - region = LLWorld::getInstance()->getRegionFromHandle(regionHandle); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - LLPathfindingNavMeshStatus navMeshStatus(regionUUID); - if (!status) - { - LL_WARNS("PathfindingManager") << "HTTP status, " << status.toTerseString() << - ". Building using empty status." << LL_ENDL; - } - else - { - result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); - navMeshStatus = LLPathfindingNavMeshStatus(regionUUID, result); - } - - LLPathfindingNavMeshPtr navMeshPtr = getNavMeshForRegion(regionUUID); - - if (!navMeshStatus.isValid()) - { - navMeshPtr->handleNavMeshError(); - return; - } - else if (navMeshPtr->hasNavMeshVersion(navMeshStatus)) - { - navMeshPtr->handleRefresh(navMeshStatus); - return; - } - else if (isGetStatusOnly) - { - navMeshPtr->handleNavMeshNewVersion(navMeshStatus); - return; - } - - if ((!region) || !region->isAlive()) - { - LL_WARNS("PathfindingManager") << "About to update navmesh status for region that has gone away." << LL_ENDL; - navMeshPtr->handleNavMeshNotEnabled(); - return; - } - - std::string navMeshURL = getRetrieveNavMeshURLForRegion(region); - - if (navMeshURL.empty()) - { - navMeshPtr->handleNavMeshNotEnabled(); - return; - } - - navMeshPtr->handleNavMeshStart(navMeshStatus); - - LLSD postData; - result = httpAdapter->postAndSuspend(httpRequest, navMeshURL, postData); - - U32 navMeshVersion = navMeshStatus.getVersion(); - - if (!status) - { - LL_WARNS("PathfindingManager") << "HTTP status, " << status.toTerseString() << - ". reporting error." << LL_ENDL; - navMeshPtr->handleNavMeshError(navMeshVersion); - } - else - { - result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); - navMeshPtr->handleNavMeshResult(result, navMeshVersion); - - } - -} - -void LLPathfindingManager::navAgentStateRequestCoro(std::string url) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("NavAgentStateRequest", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD result = httpAdapter->getAndSuspend(httpRequest, url); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - bool canRebake = false; - if (!status) - { - LL_WARNS("PathfindingManager") << "HTTP status, " << status.toTerseString() << - ". Building using empty status." << LL_ENDL; - } - else - { - llassert(result.has(AGENT_STATE_CAN_REBAKE_REGION_FIELD)); - llassert(result.get(AGENT_STATE_CAN_REBAKE_REGION_FIELD).isBoolean()); - canRebake = result.get(AGENT_STATE_CAN_REBAKE_REGION_FIELD).asBoolean(); - } - - handleAgentState(canRebake); -} - -void LLPathfindingManager::navMeshRebakeCoro(std::string url, rebake_navmesh_callback_t rebakeNavMeshCallback) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("NavMeshRebake", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - - LLSD postData = LLSD::emptyMap(); - postData["command"] = "rebuild"; - - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - bool success = true; - if (!status) - { - LL_WARNS("PathfindingManager") << "HTTP status, " << status.toTerseString() << - ". Rebake failed." << LL_ENDL; - success = false; - } - - rebakeNavMeshCallback(success); -} - -// If called with putData undefined this coroutine will issue a get. If there -// is data in putData it will be PUT to the URL. -void LLPathfindingManager::linksetObjectsCoro(std::string url, LinksetsResponder::ptr_t linksetsResponsderPtr, LLSD putData) const -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("LinksetObjects", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD result; - - if (putData.isUndefined()) - { - result = httpAdapter->getAndSuspend(httpRequest, url); - } - else - { - result = httpAdapter->putAndSuspend(httpRequest, url, putData); - } - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS("PathfindingManager") << "HTTP status, " << status.toTerseString() << - ". linksetObjects failed." << LL_ENDL; - linksetsResponsderPtr->handleObjectLinksetsError(); - } - else - { - result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); - linksetsResponsderPtr->handleObjectLinksetsResult(result); - } -} - -// If called with putData undefined this coroutine will issue a GET. If there -// is data in putData it will be PUT to the URL. -void LLPathfindingManager::linksetTerrainCoro(std::string url, LinksetsResponder::ptr_t linksetsResponsderPtr, LLSD putData) const -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("LinksetTerrain", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD result; - - if (putData.isUndefined()) - { - result = httpAdapter->getAndSuspend(httpRequest, url); - } - else - { - result = httpAdapter->putAndSuspend(httpRequest, url, putData); - } - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS("PathfindingManager") << "HTTP status, " << status.toTerseString() << - ". linksetTerrain failed." << LL_ENDL; - linksetsResponsderPtr->handleTerrainLinksetsError(); - } - else - { - result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); - linksetsResponsderPtr->handleTerrainLinksetsResult(result); - } - -} - -void LLPathfindingManager::charactersCoro(std::string url, request_id_t requestId, object_request_callback_t callback) const -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("LinksetTerrain", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD result = httpAdapter->getAndSuspend(httpRequest, url); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS("PathfindingManager") << "HTTP status, " << status.toTerseString() << - ". characters failed." << LL_ENDL; - - LLPathfindingObjectListPtr characterListPtr = LLPathfindingObjectListPtr(new LLPathfindingCharacterList()); - callback(requestId, LLPathfindingManager::kRequestError, characterListPtr); - } - else - { - result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); - LLPathfindingObjectListPtr characterListPtr = LLPathfindingObjectListPtr(new LLPathfindingCharacterList(result)); - callback(requestId, LLPathfindingManager::kRequestCompleted, characterListPtr); - } -} - -void LLPathfindingManager::handleNavMeshStatusUpdate(const LLPathfindingNavMeshStatus &pNavMeshStatus) -{ - LLPathfindingNavMeshPtr navMeshPtr = getNavMeshForRegion(pNavMeshStatus.getRegionUUID()); - - if (!pNavMeshStatus.isValid()) - { - navMeshPtr->handleNavMeshError(); - } - else - { - navMeshPtr->handleNavMeshNewVersion(pNavMeshStatus); - } -} - -void LLPathfindingManager::handleAgentState(bool pCanRebakeRegion) -{ - mAgentStateSignal(pCanRebakeRegion); -} - -LLPathfindingNavMeshPtr LLPathfindingManager::getNavMeshForRegion(const LLUUID &pRegionUUID) -{ - LLPathfindingNavMeshPtr navMeshPtr; - NavMeshMap::iterator navMeshIter = mNavMeshMap.find(pRegionUUID); - if (navMeshIter == mNavMeshMap.end()) - { - navMeshPtr = LLPathfindingNavMeshPtr(new LLPathfindingNavMesh(pRegionUUID)); - mNavMeshMap.insert(std::pair(pRegionUUID, navMeshPtr)); - } - else - { - navMeshPtr = navMeshIter->second; - } - - return navMeshPtr; -} - -LLPathfindingNavMeshPtr LLPathfindingManager::getNavMeshForRegion(LLViewerRegion *pRegion) -{ - LLUUID regionUUID; - if (pRegion != NULL) - { - regionUUID = pRegion->getRegionID(); - } - - return getNavMeshForRegion(regionUUID); -} - -std::string LLPathfindingManager::getNavMeshStatusURLForCurrentRegion() const -{ - return getNavMeshStatusURLForRegion(getCurrentRegion()); -} - -std::string LLPathfindingManager::getNavMeshStatusURLForRegion(LLViewerRegion *pRegion) const -{ - return getCapabilityURLForRegion(pRegion, CAP_SERVICE_NAVMESH_STATUS); -} - -std::string LLPathfindingManager::getRetrieveNavMeshURLForRegion(LLViewerRegion *pRegion) const -{ - return getCapabilityURLForRegion(pRegion, CAP_SERVICE_RETRIEVE_NAVMESH); -} - -std::string LLPathfindingManager::getRetrieveObjectLinksetsURLForCurrentRegion() const -{ - return getCapabilityURLForCurrentRegion(CAP_SERVICE_GET_OBJECT_LINKSETS); -} - -std::string LLPathfindingManager::getChangeObjectLinksetsURLForCurrentRegion() const -{ - return getCapabilityURLForCurrentRegion(CAP_SERVICE_SET_OBJECT_LINKSETS); -} - -std::string LLPathfindingManager::getTerrainLinksetsURLForCurrentRegion() const -{ - return getCapabilityURLForCurrentRegion(CAP_SERVICE_TERRAIN_LINKSETS); -} - -std::string LLPathfindingManager::getCharactersURLForCurrentRegion() const -{ - return getCapabilityURLForCurrentRegion(CAP_SERVICE_CHARACTERS); -} - -std::string LLPathfindingManager::getAgentStateURLForRegion(LLViewerRegion *pRegion) const -{ - return getCapabilityURLForRegion(pRegion, CAP_SERVICE_AGENT_STATE); -} - -std::string LLPathfindingManager::getCapabilityURLForCurrentRegion(const std::string &pCapabilityName) const -{ - return getCapabilityURLForRegion(getCurrentRegion(), pCapabilityName); -} - -std::string LLPathfindingManager::getCapabilityURLForRegion(LLViewerRegion *pRegion, const std::string &pCapabilityName) const -{ - std::string capabilityURL(""); - - if (pRegion != NULL) - { - capabilityURL = pRegion->getCapability(pCapabilityName); - } - - if (capabilityURL.empty()) - { - LL_WARNS() << "cannot find capability '" << pCapabilityName << "' for current region '" - << ((pRegion != NULL) ? pRegion->getName() : "") << "'" << LL_ENDL; - } - - return capabilityURL; -} - -LLViewerRegion *LLPathfindingManager::getCurrentRegion() const -{ - return gAgent.getRegion(); -} - -//--------------------------------------------------------------------------- -// LLNavMeshSimStateChangeNode -//--------------------------------------------------------------------------- - -void LLNavMeshSimStateChangeNode::post(ResponsePtr pResponse, const LLSD &pContext, const LLSD &pInput) const -{ - llassert(pInput.has(SIM_MESSAGE_BODY_FIELD)); - llassert(pInput.get(SIM_MESSAGE_BODY_FIELD).isMap()); - LLPathfindingNavMeshStatus navMeshStatus(pInput.get(SIM_MESSAGE_BODY_FIELD)); - LLPathfindingManager::getInstance()->handleNavMeshStatusUpdate(navMeshStatus); -} - -//--------------------------------------------------------------------------- -// LLAgentStateChangeNode -//--------------------------------------------------------------------------- - -void LLAgentStateChangeNode::post(ResponsePtr pResponse, const LLSD &pContext, const LLSD &pInput) const -{ - llassert(pInput.has(SIM_MESSAGE_BODY_FIELD)); - llassert(pInput.get(SIM_MESSAGE_BODY_FIELD).isMap()); - llassert(pInput.get(SIM_MESSAGE_BODY_FIELD).has(AGENT_STATE_CAN_REBAKE_REGION_FIELD)); - llassert(pInput.get(SIM_MESSAGE_BODY_FIELD).get(AGENT_STATE_CAN_REBAKE_REGION_FIELD).isBoolean()); - bool canRebakeRegion = pInput.get(SIM_MESSAGE_BODY_FIELD).get(AGENT_STATE_CAN_REBAKE_REGION_FIELD).asBoolean(); - - LLPathfindingManager::getInstance()->handleAgentState(canRebakeRegion); -} - -//--------------------------------------------------------------------------- -// LinksetsResponder -//--------------------------------------------------------------------------- -LinksetsResponder::LinksetsResponder(LLPathfindingManager::request_id_t pRequestId, LLPathfindingManager::object_request_callback_t pLinksetsCallback, bool pIsObjectRequested, bool pIsTerrainRequested) - : mRequestId(pRequestId), - mLinksetsCallback(pLinksetsCallback), - mObjectMessagingState(pIsObjectRequested ? kWaiting : kNotRequested), - mTerrainMessagingState(pIsTerrainRequested ? kWaiting : kNotRequested), - mObjectLinksetListPtr(), - mTerrainLinksetPtr() -{ -} - -LinksetsResponder::~LinksetsResponder() -{ -} - -void LinksetsResponder::handleObjectLinksetsResult(const LLSD &pContent) -{ - mObjectLinksetListPtr = LLPathfindingObjectListPtr(new LLPathfindingLinksetList(pContent)); - - mObjectMessagingState = kReceivedGood; - if (mTerrainMessagingState != kWaiting) - { - sendCallback(); - } -} - -void LinksetsResponder::handleObjectLinksetsError() -{ - LL_WARNS() << "LinksetsResponder object linksets error" << LL_ENDL; - mObjectMessagingState = kReceivedError; - if (mTerrainMessagingState != kWaiting) - { - sendCallback(); - } -} - -void LinksetsResponder::handleTerrainLinksetsResult(const LLSD &pContent) -{ - mTerrainLinksetPtr = LLPathfindingObjectPtr(new LLPathfindingLinkset(pContent)); - - mTerrainMessagingState = kReceivedGood; - if (mObjectMessagingState != kWaiting) - { - sendCallback(); - } -} - -void LinksetsResponder::handleTerrainLinksetsError() -{ - LL_WARNS() << "LinksetsResponder terrain linksets error" << LL_ENDL; - mTerrainMessagingState = kReceivedError; - if (mObjectMessagingState != kWaiting) - { - sendCallback(); - } -} - -void LinksetsResponder::sendCallback() -{ - llassert(mObjectMessagingState != kWaiting); - llassert(mTerrainMessagingState != kWaiting); - LLPathfindingManager::ERequestStatus requestStatus = - ((((mObjectMessagingState == kReceivedGood) || (mObjectMessagingState == kNotRequested)) && - ((mTerrainMessagingState == kReceivedGood) || (mTerrainMessagingState == kNotRequested))) ? - LLPathfindingManager::kRequestCompleted : LLPathfindingManager::kRequestError); - - if (mObjectMessagingState != kReceivedGood) - { - mObjectLinksetListPtr = LLPathfindingObjectListPtr(new LLPathfindingLinksetList()); - } - - if (mTerrainMessagingState == kReceivedGood) - { - mObjectLinksetListPtr->update(mTerrainLinksetPtr); - } - - mLinksetsCallback(mRequestId, requestStatus, mObjectLinksetListPtr); -} +/** +* @file llpathfindingmanager.cpp +* @brief Implementation of llpathfindingmanager +* @author Stinson@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + + +#include "llviewerprecompiledheaders.h" + +#include "llpathfindingmanager.h" + +#include +#include + +#include +#include +#include +#include + +#include "llagent.h" +#include "llhttpnode.h" +#include "llnotificationsutil.h" +#include "llpathfindingcharacterlist.h" +#include "llpathfindinglinkset.h" +#include "llpathfindinglinksetlist.h" +#include "llpathfindingnavmesh.h" +#include "llpathfindingnavmeshstatus.h" +#include "llpathfindingobject.h" +#include "llpathinglib.h" +#include "llsingleton.h" +#include "llsd.h" +#include "lltrans.h" +#include "lluuid.h" +#include "llviewerregion.h" +#include "llweb.h" +#include "llcorehttputil.h" +#include "llworld.h" + +#define CAP_SERVICE_RETRIEVE_NAVMESH "RetrieveNavMeshSrc" + +#define CAP_SERVICE_NAVMESH_STATUS "NavMeshGenerationStatus" + +#define CAP_SERVICE_GET_OBJECT_LINKSETS "RegionObjects" +#define CAP_SERVICE_SET_OBJECT_LINKSETS "ObjectNavMeshProperties" +#define CAP_SERVICE_TERRAIN_LINKSETS "TerrainNavMeshProperties" + +#define CAP_SERVICE_CHARACTERS "CharacterProperties" + +#define SIM_MESSAGE_NAVMESH_STATUS_UPDATE "/message/NavMeshStatusUpdate" +#define SIM_MESSAGE_AGENT_STATE_UPDATE "/message/AgentStateUpdate" +#define SIM_MESSAGE_BODY_FIELD "body" + +#define CAP_SERVICE_AGENT_STATE "AgentState" + +#define AGENT_STATE_CAN_REBAKE_REGION_FIELD "can_modify_navmesh" + +//--------------------------------------------------------------------------- +// LLNavMeshSimStateChangeNode +//--------------------------------------------------------------------------- + +class LLNavMeshSimStateChangeNode : public LLHTTPNode +{ +public: + virtual void post(ResponsePtr pResponse, const LLSD &pContext, const LLSD &pInput) const; +}; + +LLHTTPRegistration gHTTPRegistrationNavMeshSimStateChangeNode(SIM_MESSAGE_NAVMESH_STATUS_UPDATE); + + +//--------------------------------------------------------------------------- +// LLAgentStateChangeNode +//--------------------------------------------------------------------------- +class LLAgentStateChangeNode : public LLHTTPNode +{ +public: + virtual void post(ResponsePtr pResponse, const LLSD &pContext, const LLSD &pInput) const; +}; + +LLHTTPRegistration gHTTPRegistrationAgentStateChangeNode(SIM_MESSAGE_AGENT_STATE_UPDATE); + +//--------------------------------------------------------------------------- +// LinksetsResponder +//--------------------------------------------------------------------------- + +class LinksetsResponder +{ +public: + LinksetsResponder(LLPathfindingManager::request_id_t pRequestId, LLPathfindingManager::object_request_callback_t pLinksetsCallback, bool pIsObjectRequested, bool pIsTerrainRequested); + virtual ~LinksetsResponder(); + + void handleObjectLinksetsResult(const LLSD &pContent); + void handleObjectLinksetsError(); + void handleTerrainLinksetsResult(const LLSD &pContent); + void handleTerrainLinksetsError(); + + typedef std::shared_ptr ptr_t; + +protected: + +private: + void sendCallback(); + + typedef enum + { + kNotRequested, + kWaiting, + kReceivedGood, + kReceivedError + } EMessagingState; + + LLPathfindingManager::request_id_t mRequestId; + LLPathfindingManager::object_request_callback_t mLinksetsCallback; + + EMessagingState mObjectMessagingState; + EMessagingState mTerrainMessagingState; + + LLPathfindingObjectListPtr mObjectLinksetListPtr; + LLPathfindingObjectPtr mTerrainLinksetPtr; +}; + +typedef std::shared_ptr LinksetsResponderPtr; + +//--------------------------------------------------------------------------- +// LLPathfindingManager +//--------------------------------------------------------------------------- + +LLPathfindingManager::LLPathfindingManager(): + mNavMeshMap(), + mAgentStateSignal() +{ +} + +LLPathfindingManager::~LLPathfindingManager() +{ + quitSystem(); +} + +void LLPathfindingManager::initSystem() +{ + if (LLPathingLib::getInstance() == NULL) + { + LLPathingLib::initSystem(); + } +} + +void LLPathfindingManager::quitSystem() +{ + if (LLPathingLib::getInstance() != NULL) + { + LLPathingLib::quitSystem(); + } +} + +bool LLPathfindingManager::isPathfindingViewEnabled() const +{ + return (LLPathingLib::getInstance() != NULL); +} + +bool LLPathfindingManager::isPathfindingEnabledForCurrentRegion() const +{ + return isPathfindingEnabledForRegion(getCurrentRegion()); +} + +bool LLPathfindingManager::isPathfindingEnabledForRegion(LLViewerRegion *pRegion) const +{ + std::string retrieveNavMeshURL = getRetrieveNavMeshURLForRegion(pRegion); + return !retrieveNavMeshURL.empty(); +} + +bool LLPathfindingManager::isAllowViewTerrainProperties() const +{ + LLViewerRegion* region = getCurrentRegion(); + return (gAgent.isGodlike() || ((region != NULL) && region->canManageEstate())); +} + +LLPathfindingNavMesh::navmesh_slot_t LLPathfindingManager::registerNavMeshListenerForRegion(LLViewerRegion *pRegion, LLPathfindingNavMesh::navmesh_callback_t pNavMeshCallback) +{ + LLPathfindingNavMeshPtr navMeshPtr = getNavMeshForRegion(pRegion); + return navMeshPtr->registerNavMeshListener(pNavMeshCallback); +} + +void LLPathfindingManager::requestGetNavMeshForRegion(LLViewerRegion *pRegion, bool pIsGetStatusOnly) +{ + LLPathfindingNavMeshPtr navMeshPtr = getNavMeshForRegion(pRegion); + + if (pRegion == NULL) + { + navMeshPtr->handleNavMeshNotEnabled(); + } + else if (!pRegion->capabilitiesReceived()) + { + navMeshPtr->handleNavMeshWaitForRegionLoad(); + pRegion->setCapabilitiesReceivedCallback(boost::bind(&LLPathfindingManager::handleDeferredGetNavMeshForRegion, this, _1, pIsGetStatusOnly)); + } + else if (!isPathfindingEnabledForRegion(pRegion)) + { + navMeshPtr->handleNavMeshNotEnabled(); + } + else + { + std::string navMeshStatusURL = getNavMeshStatusURLForRegion(pRegion); + llassert(!navMeshStatusURL.empty()); + navMeshPtr->handleNavMeshCheckVersion(); + + U64 regionHandle = pRegion->getHandle(); + std::string coroname = LLCoros::instance().launch("LLPathfindingManager::navMeshStatusRequestCoro", + boost::bind(&LLPathfindingManager::navMeshStatusRequestCoro, this, navMeshStatusURL, regionHandle, pIsGetStatusOnly)); + } +} + +void LLPathfindingManager::requestGetLinksets(request_id_t pRequestId, object_request_callback_t pLinksetsCallback) const +{ + LLPathfindingObjectListPtr emptyLinksetListPtr; + LLViewerRegion *currentRegion = getCurrentRegion(); + + if (currentRegion == NULL) + { + pLinksetsCallback(pRequestId, kRequestNotEnabled, emptyLinksetListPtr); + } + else if (!currentRegion->capabilitiesReceived()) + { + pLinksetsCallback(pRequestId, kRequestStarted, emptyLinksetListPtr); + currentRegion->setCapabilitiesReceivedCallback(boost::bind(&LLPathfindingManager::handleDeferredGetLinksetsForRegion, this, _1, pRequestId, pLinksetsCallback)); + } + else + { + std::string objectLinksetsURL = getRetrieveObjectLinksetsURLForCurrentRegion(); + std::string terrainLinksetsURL = getTerrainLinksetsURLForCurrentRegion(); + if (objectLinksetsURL.empty() || terrainLinksetsURL.empty()) + { + pLinksetsCallback(pRequestId, kRequestNotEnabled, emptyLinksetListPtr); + } + else + { + pLinksetsCallback(pRequestId, kRequestStarted, emptyLinksetListPtr); + + bool doRequestTerrain = isAllowViewTerrainProperties(); + LinksetsResponder::ptr_t linksetsResponderPtr(new LinksetsResponder(pRequestId, pLinksetsCallback, true, doRequestTerrain)); + + std::string coroname = LLCoros::instance().launch("LLPathfindingManager::linksetObjectsCoro", + boost::bind(&LLPathfindingManager::linksetObjectsCoro, this, objectLinksetsURL, linksetsResponderPtr, LLSD())); + + if (doRequestTerrain) + { + std::string coroname = LLCoros::instance().launch("LLPathfindingManager::linksetTerrainCoro", + boost::bind(&LLPathfindingManager::linksetTerrainCoro, this, terrainLinksetsURL, linksetsResponderPtr, LLSD())); + } + } + } +} + +void LLPathfindingManager::requestSetLinksets(request_id_t pRequestId, const LLPathfindingObjectListPtr &pLinksetListPtr, LLPathfindingLinkset::ELinksetUse pLinksetUse, S32 pA, S32 pB, S32 pC, S32 pD, object_request_callback_t pLinksetsCallback) const +{ + LLPathfindingObjectListPtr emptyLinksetListPtr; + + std::string objectLinksetsURL = getChangeObjectLinksetsURLForCurrentRegion(); + std::string terrainLinksetsURL = getTerrainLinksetsURLForCurrentRegion(); + if (objectLinksetsURL.empty() || terrainLinksetsURL.empty()) + { + pLinksetsCallback(pRequestId, kRequestNotEnabled, emptyLinksetListPtr); + } + else if ((pLinksetListPtr == NULL) || pLinksetListPtr->isEmpty()) + { + pLinksetsCallback(pRequestId, kRequestCompleted, emptyLinksetListPtr); + } + else + { + const LLPathfindingLinksetList *linksetList = dynamic_cast(pLinksetListPtr.get()); + + LLSD objectPostData = linksetList->encodeObjectFields(pLinksetUse, pA, pB, pC, pD); + LLSD terrainPostData; + if (isAllowViewTerrainProperties()) + { + terrainPostData = linksetList->encodeTerrainFields(pLinksetUse, pA, pB, pC, pD); + } + + if (objectPostData.isUndefined() && terrainPostData.isUndefined()) + { + pLinksetsCallback(pRequestId, kRequestCompleted, emptyLinksetListPtr); + } + else + { + pLinksetsCallback(pRequestId, kRequestStarted, emptyLinksetListPtr); + + LinksetsResponder::ptr_t linksetsResponderPtr(new LinksetsResponder(pRequestId, pLinksetsCallback, !objectPostData.isUndefined(), !terrainPostData.isUndefined())); + + if (!objectPostData.isUndefined()) + { + std::string coroname = LLCoros::instance().launch("LLPathfindingManager::linksetObjectsCoro", + boost::bind(&LLPathfindingManager::linksetObjectsCoro, this, objectLinksetsURL, linksetsResponderPtr, objectPostData)); + } + + if (!terrainPostData.isUndefined()) + { + std::string coroname = LLCoros::instance().launch("LLPathfindingManager::linksetTerrainCoro", + boost::bind(&LLPathfindingManager::linksetTerrainCoro, this, terrainLinksetsURL, linksetsResponderPtr, terrainPostData)); + } + } + } +} + +void LLPathfindingManager::requestGetCharacters(request_id_t pRequestId, object_request_callback_t pCharactersCallback) const +{ + LLPathfindingObjectListPtr emptyCharacterListPtr; + + LLViewerRegion *currentRegion = getCurrentRegion(); + + if (currentRegion == NULL) + { + pCharactersCallback(pRequestId, kRequestNotEnabled, emptyCharacterListPtr); + } + else if (!currentRegion->capabilitiesReceived()) + { + pCharactersCallback(pRequestId, kRequestStarted, emptyCharacterListPtr); + currentRegion->setCapabilitiesReceivedCallback(boost::bind(&LLPathfindingManager::handleDeferredGetCharactersForRegion, this, _1, pRequestId, pCharactersCallback)); + } + else + { + std::string charactersURL = getCharactersURLForCurrentRegion(); + if (charactersURL.empty()) + { + pCharactersCallback(pRequestId, kRequestNotEnabled, emptyCharacterListPtr); + } + else + { + pCharactersCallback(pRequestId, kRequestStarted, emptyCharacterListPtr); + + std::string coroname = LLCoros::instance().launch("LLPathfindingManager::charactersCoro", + boost::bind(&LLPathfindingManager::charactersCoro, this, charactersURL, pRequestId, pCharactersCallback)); + } + } +} + +LLPathfindingManager::agent_state_slot_t LLPathfindingManager::registerAgentStateListener(agent_state_callback_t pAgentStateCallback) +{ + return mAgentStateSignal.connect(pAgentStateCallback); +} + +void LLPathfindingManager::requestGetAgentState() +{ + LLViewerRegion *currentRegion = getCurrentRegion(); + + if (currentRegion == NULL) + { + mAgentStateSignal(false); + } + else + { + if (!currentRegion->capabilitiesReceived()) + { + currentRegion->setCapabilitiesReceivedCallback(boost::bind(&LLPathfindingManager::handleDeferredGetAgentStateForRegion, this, _1)); + } + else if (!isPathfindingEnabledForRegion(currentRegion)) + { + mAgentStateSignal(false); + } + else + { + std::string agentStateURL = getAgentStateURLForRegion(currentRegion); + llassert(!agentStateURL.empty()); + + std::string coroname = LLCoros::instance().launch("LLPathfindingManager::navAgentStateRequestCoro", + boost::bind(&LLPathfindingManager::navAgentStateRequestCoro, this, agentStateURL)); + } + } +} + +void LLPathfindingManager::requestRebakeNavMesh(rebake_navmesh_callback_t pRebakeNavMeshCallback) +{ + LLViewerRegion *currentRegion = getCurrentRegion(); + + if (currentRegion == NULL) + { + pRebakeNavMeshCallback(false); + } + else if (!isPathfindingEnabledForRegion(currentRegion)) + { + pRebakeNavMeshCallback(false); + } + else + { + std::string navMeshStatusURL = getNavMeshStatusURLForCurrentRegion(); + llassert(!navMeshStatusURL.empty()); + + std::string coroname = LLCoros::instance().launch("LLPathfindingManager::navMeshRebakeCoro", + boost::bind(&LLPathfindingManager::navMeshRebakeCoro, this, navMeshStatusURL, pRebakeNavMeshCallback)); + } +} + +void LLPathfindingManager::handleDeferredGetAgentStateForRegion(const LLUUID &pRegionUUID) +{ + LLViewerRegion *currentRegion = getCurrentRegion(); + + if ((currentRegion != NULL) && (currentRegion->getRegionID() == pRegionUUID)) + { + requestGetAgentState(); + } +} + +void LLPathfindingManager::handleDeferredGetNavMeshForRegion(const LLUUID &pRegionUUID, bool pIsGetStatusOnly) +{ + LLViewerRegion *currentRegion = getCurrentRegion(); + + if ((currentRegion != NULL) && (currentRegion->getRegionID() == pRegionUUID)) + { + requestGetNavMeshForRegion(currentRegion, pIsGetStatusOnly); + } +} + +void LLPathfindingManager::handleDeferredGetLinksetsForRegion(const LLUUID &pRegionUUID, request_id_t pRequestId, object_request_callback_t pLinksetsCallback) const +{ + LLViewerRegion *currentRegion = getCurrentRegion(); + + if ((currentRegion != NULL) && (currentRegion->getRegionID() == pRegionUUID)) + { + requestGetLinksets(pRequestId, pLinksetsCallback); + } +} + +void LLPathfindingManager::handleDeferredGetCharactersForRegion(const LLUUID &pRegionUUID, request_id_t pRequestId, object_request_callback_t pCharactersCallback) const +{ + LLViewerRegion *currentRegion = getCurrentRegion(); + + if ((currentRegion != NULL) && (currentRegion->getRegionID() == pRegionUUID)) + { + requestGetCharacters(pRequestId, pCharactersCallback); + } +} + +void LLPathfindingManager::navMeshStatusRequestCoro(std::string url, U64 regionHandle, bool isGetStatusOnly) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("NavMeshStatusRequest", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLViewerRegion *region = LLWorld::getInstance()->getRegionFromHandle(regionHandle); + if (!region) + { + LL_WARNS("PathfindingManager") << "Attempting to retrieve navmesh status for region that has gone away." << LL_ENDL; + return; + } + LLUUID regionUUID = region->getRegionID(); + + region = NULL; + LLSD result = httpAdapter->getAndSuspend(httpRequest, url); + + region = LLWorld::getInstance()->getRegionFromHandle(regionHandle); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + LLPathfindingNavMeshStatus navMeshStatus(regionUUID); + if (!status) + { + LL_WARNS("PathfindingManager") << "HTTP status, " << status.toTerseString() << + ". Building using empty status." << LL_ENDL; + } + else + { + result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); + navMeshStatus = LLPathfindingNavMeshStatus(regionUUID, result); + } + + LLPathfindingNavMeshPtr navMeshPtr = getNavMeshForRegion(regionUUID); + + if (!navMeshStatus.isValid()) + { + navMeshPtr->handleNavMeshError(); + return; + } + else if (navMeshPtr->hasNavMeshVersion(navMeshStatus)) + { + navMeshPtr->handleRefresh(navMeshStatus); + return; + } + else if (isGetStatusOnly) + { + navMeshPtr->handleNavMeshNewVersion(navMeshStatus); + return; + } + + if ((!region) || !region->isAlive()) + { + LL_WARNS("PathfindingManager") << "About to update navmesh status for region that has gone away." << LL_ENDL; + navMeshPtr->handleNavMeshNotEnabled(); + return; + } + + std::string navMeshURL = getRetrieveNavMeshURLForRegion(region); + + if (navMeshURL.empty()) + { + navMeshPtr->handleNavMeshNotEnabled(); + return; + } + + navMeshPtr->handleNavMeshStart(navMeshStatus); + + LLSD postData; + result = httpAdapter->postAndSuspend(httpRequest, navMeshURL, postData); + + U32 navMeshVersion = navMeshStatus.getVersion(); + + if (!status) + { + LL_WARNS("PathfindingManager") << "HTTP status, " << status.toTerseString() << + ". reporting error." << LL_ENDL; + navMeshPtr->handleNavMeshError(navMeshVersion); + } + else + { + result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); + navMeshPtr->handleNavMeshResult(result, navMeshVersion); + + } + +} + +void LLPathfindingManager::navAgentStateRequestCoro(std::string url) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("NavAgentStateRequest", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD result = httpAdapter->getAndSuspend(httpRequest, url); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + bool canRebake = false; + if (!status) + { + LL_WARNS("PathfindingManager") << "HTTP status, " << status.toTerseString() << + ". Building using empty status." << LL_ENDL; + } + else + { + llassert(result.has(AGENT_STATE_CAN_REBAKE_REGION_FIELD)); + llassert(result.get(AGENT_STATE_CAN_REBAKE_REGION_FIELD).isBoolean()); + canRebake = result.get(AGENT_STATE_CAN_REBAKE_REGION_FIELD).asBoolean(); + } + + handleAgentState(canRebake); +} + +void LLPathfindingManager::navMeshRebakeCoro(std::string url, rebake_navmesh_callback_t rebakeNavMeshCallback) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("NavMeshRebake", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + + LLSD postData = LLSD::emptyMap(); + postData["command"] = "rebuild"; + + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + bool success = true; + if (!status) + { + LL_WARNS("PathfindingManager") << "HTTP status, " << status.toTerseString() << + ". Rebake failed." << LL_ENDL; + success = false; + } + + rebakeNavMeshCallback(success); +} + +// If called with putData undefined this coroutine will issue a get. If there +// is data in putData it will be PUT to the URL. +void LLPathfindingManager::linksetObjectsCoro(std::string url, LinksetsResponder::ptr_t linksetsResponsderPtr, LLSD putData) const +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("LinksetObjects", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD result; + + if (putData.isUndefined()) + { + result = httpAdapter->getAndSuspend(httpRequest, url); + } + else + { + result = httpAdapter->putAndSuspend(httpRequest, url, putData); + } + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS("PathfindingManager") << "HTTP status, " << status.toTerseString() << + ". linksetObjects failed." << LL_ENDL; + linksetsResponsderPtr->handleObjectLinksetsError(); + } + else + { + result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); + linksetsResponsderPtr->handleObjectLinksetsResult(result); + } +} + +// If called with putData undefined this coroutine will issue a GET. If there +// is data in putData it will be PUT to the URL. +void LLPathfindingManager::linksetTerrainCoro(std::string url, LinksetsResponder::ptr_t linksetsResponsderPtr, LLSD putData) const +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("LinksetTerrain", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD result; + + if (putData.isUndefined()) + { + result = httpAdapter->getAndSuspend(httpRequest, url); + } + else + { + result = httpAdapter->putAndSuspend(httpRequest, url, putData); + } + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS("PathfindingManager") << "HTTP status, " << status.toTerseString() << + ". linksetTerrain failed." << LL_ENDL; + linksetsResponsderPtr->handleTerrainLinksetsError(); + } + else + { + result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); + linksetsResponsderPtr->handleTerrainLinksetsResult(result); + } + +} + +void LLPathfindingManager::charactersCoro(std::string url, request_id_t requestId, object_request_callback_t callback) const +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("LinksetTerrain", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD result = httpAdapter->getAndSuspend(httpRequest, url); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS("PathfindingManager") << "HTTP status, " << status.toTerseString() << + ". characters failed." << LL_ENDL; + + LLPathfindingObjectListPtr characterListPtr = LLPathfindingObjectListPtr(new LLPathfindingCharacterList()); + callback(requestId, LLPathfindingManager::kRequestError, characterListPtr); + } + else + { + result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); + LLPathfindingObjectListPtr characterListPtr = LLPathfindingObjectListPtr(new LLPathfindingCharacterList(result)); + callback(requestId, LLPathfindingManager::kRequestCompleted, characterListPtr); + } +} + +void LLPathfindingManager::handleNavMeshStatusUpdate(const LLPathfindingNavMeshStatus &pNavMeshStatus) +{ + LLPathfindingNavMeshPtr navMeshPtr = getNavMeshForRegion(pNavMeshStatus.getRegionUUID()); + + if (!pNavMeshStatus.isValid()) + { + navMeshPtr->handleNavMeshError(); + } + else + { + navMeshPtr->handleNavMeshNewVersion(pNavMeshStatus); + } +} + +void LLPathfindingManager::handleAgentState(bool pCanRebakeRegion) +{ + mAgentStateSignal(pCanRebakeRegion); +} + +LLPathfindingNavMeshPtr LLPathfindingManager::getNavMeshForRegion(const LLUUID &pRegionUUID) +{ + LLPathfindingNavMeshPtr navMeshPtr; + NavMeshMap::iterator navMeshIter = mNavMeshMap.find(pRegionUUID); + if (navMeshIter == mNavMeshMap.end()) + { + navMeshPtr = LLPathfindingNavMeshPtr(new LLPathfindingNavMesh(pRegionUUID)); + mNavMeshMap.insert(std::pair(pRegionUUID, navMeshPtr)); + } + else + { + navMeshPtr = navMeshIter->second; + } + + return navMeshPtr; +} + +LLPathfindingNavMeshPtr LLPathfindingManager::getNavMeshForRegion(LLViewerRegion *pRegion) +{ + LLUUID regionUUID; + if (pRegion != NULL) + { + regionUUID = pRegion->getRegionID(); + } + + return getNavMeshForRegion(regionUUID); +} + +std::string LLPathfindingManager::getNavMeshStatusURLForCurrentRegion() const +{ + return getNavMeshStatusURLForRegion(getCurrentRegion()); +} + +std::string LLPathfindingManager::getNavMeshStatusURLForRegion(LLViewerRegion *pRegion) const +{ + return getCapabilityURLForRegion(pRegion, CAP_SERVICE_NAVMESH_STATUS); +} + +std::string LLPathfindingManager::getRetrieveNavMeshURLForRegion(LLViewerRegion *pRegion) const +{ + return getCapabilityURLForRegion(pRegion, CAP_SERVICE_RETRIEVE_NAVMESH); +} + +std::string LLPathfindingManager::getRetrieveObjectLinksetsURLForCurrentRegion() const +{ + return getCapabilityURLForCurrentRegion(CAP_SERVICE_GET_OBJECT_LINKSETS); +} + +std::string LLPathfindingManager::getChangeObjectLinksetsURLForCurrentRegion() const +{ + return getCapabilityURLForCurrentRegion(CAP_SERVICE_SET_OBJECT_LINKSETS); +} + +std::string LLPathfindingManager::getTerrainLinksetsURLForCurrentRegion() const +{ + return getCapabilityURLForCurrentRegion(CAP_SERVICE_TERRAIN_LINKSETS); +} + +std::string LLPathfindingManager::getCharactersURLForCurrentRegion() const +{ + return getCapabilityURLForCurrentRegion(CAP_SERVICE_CHARACTERS); +} + +std::string LLPathfindingManager::getAgentStateURLForRegion(LLViewerRegion *pRegion) const +{ + return getCapabilityURLForRegion(pRegion, CAP_SERVICE_AGENT_STATE); +} + +std::string LLPathfindingManager::getCapabilityURLForCurrentRegion(const std::string &pCapabilityName) const +{ + return getCapabilityURLForRegion(getCurrentRegion(), pCapabilityName); +} + +std::string LLPathfindingManager::getCapabilityURLForRegion(LLViewerRegion *pRegion, const std::string &pCapabilityName) const +{ + std::string capabilityURL(""); + + if (pRegion != NULL) + { + capabilityURL = pRegion->getCapability(pCapabilityName); + } + + if (capabilityURL.empty()) + { + LL_WARNS() << "cannot find capability '" << pCapabilityName << "' for current region '" + << ((pRegion != NULL) ? pRegion->getName() : "") << "'" << LL_ENDL; + } + + return capabilityURL; +} + +LLViewerRegion *LLPathfindingManager::getCurrentRegion() const +{ + return gAgent.getRegion(); +} + +//--------------------------------------------------------------------------- +// LLNavMeshSimStateChangeNode +//--------------------------------------------------------------------------- + +void LLNavMeshSimStateChangeNode::post(ResponsePtr pResponse, const LLSD &pContext, const LLSD &pInput) const +{ + llassert(pInput.has(SIM_MESSAGE_BODY_FIELD)); + llassert(pInput.get(SIM_MESSAGE_BODY_FIELD).isMap()); + LLPathfindingNavMeshStatus navMeshStatus(pInput.get(SIM_MESSAGE_BODY_FIELD)); + LLPathfindingManager::getInstance()->handleNavMeshStatusUpdate(navMeshStatus); +} + +//--------------------------------------------------------------------------- +// LLAgentStateChangeNode +//--------------------------------------------------------------------------- + +void LLAgentStateChangeNode::post(ResponsePtr pResponse, const LLSD &pContext, const LLSD &pInput) const +{ + llassert(pInput.has(SIM_MESSAGE_BODY_FIELD)); + llassert(pInput.get(SIM_MESSAGE_BODY_FIELD).isMap()); + llassert(pInput.get(SIM_MESSAGE_BODY_FIELD).has(AGENT_STATE_CAN_REBAKE_REGION_FIELD)); + llassert(pInput.get(SIM_MESSAGE_BODY_FIELD).get(AGENT_STATE_CAN_REBAKE_REGION_FIELD).isBoolean()); + bool canRebakeRegion = pInput.get(SIM_MESSAGE_BODY_FIELD).get(AGENT_STATE_CAN_REBAKE_REGION_FIELD).asBoolean(); + + LLPathfindingManager::getInstance()->handleAgentState(canRebakeRegion); +} + +//--------------------------------------------------------------------------- +// LinksetsResponder +//--------------------------------------------------------------------------- +LinksetsResponder::LinksetsResponder(LLPathfindingManager::request_id_t pRequestId, LLPathfindingManager::object_request_callback_t pLinksetsCallback, bool pIsObjectRequested, bool pIsTerrainRequested) + : mRequestId(pRequestId), + mLinksetsCallback(pLinksetsCallback), + mObjectMessagingState(pIsObjectRequested ? kWaiting : kNotRequested), + mTerrainMessagingState(pIsTerrainRequested ? kWaiting : kNotRequested), + mObjectLinksetListPtr(), + mTerrainLinksetPtr() +{ +} + +LinksetsResponder::~LinksetsResponder() +{ +} + +void LinksetsResponder::handleObjectLinksetsResult(const LLSD &pContent) +{ + mObjectLinksetListPtr = LLPathfindingObjectListPtr(new LLPathfindingLinksetList(pContent)); + + mObjectMessagingState = kReceivedGood; + if (mTerrainMessagingState != kWaiting) + { + sendCallback(); + } +} + +void LinksetsResponder::handleObjectLinksetsError() +{ + LL_WARNS() << "LinksetsResponder object linksets error" << LL_ENDL; + mObjectMessagingState = kReceivedError; + if (mTerrainMessagingState != kWaiting) + { + sendCallback(); + } +} + +void LinksetsResponder::handleTerrainLinksetsResult(const LLSD &pContent) +{ + mTerrainLinksetPtr = LLPathfindingObjectPtr(new LLPathfindingLinkset(pContent)); + + mTerrainMessagingState = kReceivedGood; + if (mObjectMessagingState != kWaiting) + { + sendCallback(); + } +} + +void LinksetsResponder::handleTerrainLinksetsError() +{ + LL_WARNS() << "LinksetsResponder terrain linksets error" << LL_ENDL; + mTerrainMessagingState = kReceivedError; + if (mObjectMessagingState != kWaiting) + { + sendCallback(); + } +} + +void LinksetsResponder::sendCallback() +{ + llassert(mObjectMessagingState != kWaiting); + llassert(mTerrainMessagingState != kWaiting); + LLPathfindingManager::ERequestStatus requestStatus = + ((((mObjectMessagingState == kReceivedGood) || (mObjectMessagingState == kNotRequested)) && + ((mTerrainMessagingState == kReceivedGood) || (mTerrainMessagingState == kNotRequested))) ? + LLPathfindingManager::kRequestCompleted : LLPathfindingManager::kRequestError); + + if (mObjectMessagingState != kReceivedGood) + { + mObjectLinksetListPtr = LLPathfindingObjectListPtr(new LLPathfindingLinksetList()); + } + + if (mTerrainMessagingState == kReceivedGood) + { + mObjectLinksetListPtr->update(mTerrainLinksetPtr); + } + + mLinksetsCallback(mRequestId, requestStatus, mObjectLinksetListPtr); +} diff --git a/indra/newview/llpathfindingmanager.h b/indra/newview/llpathfindingmanager.h index a1fc74c48e..663cb3cf34 100644 --- a/indra/newview/llpathfindingmanager.h +++ b/indra/newview/llpathfindingmanager.h @@ -1,138 +1,138 @@ -/** -* @file llpathfindingmanager.h -* @brief Header file for llpathfindingmanager -* @author Stinson@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ -#ifndef LL_LLPATHFINDINGMANAGER_H -#define LL_LLPATHFINDINGMANAGER_H - -#include -#include - -#include -#include - -#include "llpathfindinglinkset.h" -#include "llpathfindingobjectlist.h" -#include "llpathfindingnavmesh.h" -#include "llsingleton.h" -#include "llcoros.h" -#include "lleventcoro.h" - -class LLPathfindingNavMeshStatus; -class LLUUID; -class LLViewerRegion; - -class LinksetsResponder; - -class LLPathfindingManager : public LLSingleton -{ - LLSINGLETON(LLPathfindingManager); - virtual ~LLPathfindingManager(); - - friend class LLNavMeshSimStateChangeNode; - friend class NavMeshStatusResponder; - friend class LLAgentStateChangeNode; - friend class AgentStateResponder; -public: - typedef enum { - kRequestStarted, - kRequestCompleted, - kRequestNotEnabled, - kRequestError - } ERequestStatus; - - void initSystem(); - void quitSystem(); - - bool isPathfindingViewEnabled() const; - bool isPathfindingEnabledForCurrentRegion() const; - bool isPathfindingEnabledForRegion(LLViewerRegion *pRegion) const; - - bool isAllowViewTerrainProperties() const; - - LLPathfindingNavMesh::navmesh_slot_t registerNavMeshListenerForRegion(LLViewerRegion *pRegion, LLPathfindingNavMesh::navmesh_callback_t pNavMeshCallback); - void requestGetNavMeshForRegion(LLViewerRegion *pRegion, bool pIsGetStatusOnly); - - typedef U32 request_id_t; - typedef boost::function object_request_callback_t; - - void requestGetLinksets(request_id_t pRequestId, object_request_callback_t pLinksetsCallback) const; - void requestSetLinksets(request_id_t pRequestId, const LLPathfindingObjectListPtr &pLinksetListPtr, LLPathfindingLinkset::ELinksetUse pLinksetUse, S32 pA, S32 pB, S32 pC, S32 pD, object_request_callback_t pLinksetsCallback) const; - - void requestGetCharacters(request_id_t pRequestId, object_request_callback_t pCharactersCallback) const; - - typedef boost::function agent_state_callback_t; - typedef boost::signals2::signal agent_state_signal_t; - typedef boost::signals2::connection agent_state_slot_t; - - agent_state_slot_t registerAgentStateListener(agent_state_callback_t pAgentStateCallback); - void requestGetAgentState(); - - typedef boost::function rebake_navmesh_callback_t; - void requestRebakeNavMesh(rebake_navmesh_callback_t pRebakeNavMeshCallback); - -protected: - -private: - - typedef std::map NavMeshMap; - - void handleDeferredGetAgentStateForRegion(const LLUUID &pRegionUUID); - void handleDeferredGetNavMeshForRegion(const LLUUID &pRegionUUID, bool pIsGetStatusOnly); - void handleDeferredGetLinksetsForRegion(const LLUUID &pRegionUUID, request_id_t pRequestId, object_request_callback_t pLinksetsCallback) const; - void handleDeferredGetCharactersForRegion(const LLUUID &pRegionUUID, request_id_t pRequestId, object_request_callback_t pCharactersCallback) const; - - void navMeshStatusRequestCoro(std::string url, U64 regionHandle, bool isGetStatusOnly); - void navAgentStateRequestCoro(std::string url); - void navMeshRebakeCoro(std::string url, rebake_navmesh_callback_t rebakeNavMeshCallback); - void linksetObjectsCoro(std::string url, std::shared_ptr linksetsResponsderPtr, LLSD putData) const; - void linksetTerrainCoro(std::string url, std::shared_ptr linksetsResponsderPtr, LLSD putData) const; - void charactersCoro(std::string url, request_id_t requestId, object_request_callback_t callback) const; - - //void handleNavMeshStatusRequest(const LLPathfindingNavMeshStatus &pNavMeshStatus, LLViewerRegion *pRegion, bool pIsGetStatusOnly); - void handleNavMeshStatusUpdate(const LLPathfindingNavMeshStatus &pNavMeshStatus); - - void handleAgentState(bool pCanRebakeRegion); - - LLPathfindingNavMeshPtr getNavMeshForRegion(const LLUUID &pRegionUUID); - LLPathfindingNavMeshPtr getNavMeshForRegion(LLViewerRegion *pRegion); - - std::string getNavMeshStatusURLForCurrentRegion() const; - std::string getNavMeshStatusURLForRegion(LLViewerRegion *pRegion) const; - std::string getRetrieveNavMeshURLForRegion(LLViewerRegion *pRegion) const; - std::string getRetrieveObjectLinksetsURLForCurrentRegion() const; - std::string getChangeObjectLinksetsURLForCurrentRegion() const; - std::string getTerrainLinksetsURLForCurrentRegion() const; - std::string getCharactersURLForCurrentRegion() const; - std::string getAgentStateURLForRegion(LLViewerRegion *pRegion) const; - std::string getCapabilityURLForCurrentRegion(const std::string &pCapabilityName) const; - std::string getCapabilityURLForRegion(LLViewerRegion *pRegion, const std::string &pCapabilityName) const; - LLViewerRegion *getCurrentRegion() const; - - NavMeshMap mNavMeshMap; - agent_state_signal_t mAgentStateSignal; -}; - -#endif // LL_LLPATHFINDINGMANAGER_H +/** +* @file llpathfindingmanager.h +* @brief Header file for llpathfindingmanager +* @author Stinson@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LL_LLPATHFINDINGMANAGER_H +#define LL_LLPATHFINDINGMANAGER_H + +#include +#include + +#include +#include + +#include "llpathfindinglinkset.h" +#include "llpathfindingobjectlist.h" +#include "llpathfindingnavmesh.h" +#include "llsingleton.h" +#include "llcoros.h" +#include "lleventcoro.h" + +class LLPathfindingNavMeshStatus; +class LLUUID; +class LLViewerRegion; + +class LinksetsResponder; + +class LLPathfindingManager : public LLSingleton +{ + LLSINGLETON(LLPathfindingManager); + virtual ~LLPathfindingManager(); + + friend class LLNavMeshSimStateChangeNode; + friend class NavMeshStatusResponder; + friend class LLAgentStateChangeNode; + friend class AgentStateResponder; +public: + typedef enum { + kRequestStarted, + kRequestCompleted, + kRequestNotEnabled, + kRequestError + } ERequestStatus; + + void initSystem(); + void quitSystem(); + + bool isPathfindingViewEnabled() const; + bool isPathfindingEnabledForCurrentRegion() const; + bool isPathfindingEnabledForRegion(LLViewerRegion *pRegion) const; + + bool isAllowViewTerrainProperties() const; + + LLPathfindingNavMesh::navmesh_slot_t registerNavMeshListenerForRegion(LLViewerRegion *pRegion, LLPathfindingNavMesh::navmesh_callback_t pNavMeshCallback); + void requestGetNavMeshForRegion(LLViewerRegion *pRegion, bool pIsGetStatusOnly); + + typedef U32 request_id_t; + typedef boost::function object_request_callback_t; + + void requestGetLinksets(request_id_t pRequestId, object_request_callback_t pLinksetsCallback) const; + void requestSetLinksets(request_id_t pRequestId, const LLPathfindingObjectListPtr &pLinksetListPtr, LLPathfindingLinkset::ELinksetUse pLinksetUse, S32 pA, S32 pB, S32 pC, S32 pD, object_request_callback_t pLinksetsCallback) const; + + void requestGetCharacters(request_id_t pRequestId, object_request_callback_t pCharactersCallback) const; + + typedef boost::function agent_state_callback_t; + typedef boost::signals2::signal agent_state_signal_t; + typedef boost::signals2::connection agent_state_slot_t; + + agent_state_slot_t registerAgentStateListener(agent_state_callback_t pAgentStateCallback); + void requestGetAgentState(); + + typedef boost::function rebake_navmesh_callback_t; + void requestRebakeNavMesh(rebake_navmesh_callback_t pRebakeNavMeshCallback); + +protected: + +private: + + typedef std::map NavMeshMap; + + void handleDeferredGetAgentStateForRegion(const LLUUID &pRegionUUID); + void handleDeferredGetNavMeshForRegion(const LLUUID &pRegionUUID, bool pIsGetStatusOnly); + void handleDeferredGetLinksetsForRegion(const LLUUID &pRegionUUID, request_id_t pRequestId, object_request_callback_t pLinksetsCallback) const; + void handleDeferredGetCharactersForRegion(const LLUUID &pRegionUUID, request_id_t pRequestId, object_request_callback_t pCharactersCallback) const; + + void navMeshStatusRequestCoro(std::string url, U64 regionHandle, bool isGetStatusOnly); + void navAgentStateRequestCoro(std::string url); + void navMeshRebakeCoro(std::string url, rebake_navmesh_callback_t rebakeNavMeshCallback); + void linksetObjectsCoro(std::string url, std::shared_ptr linksetsResponsderPtr, LLSD putData) const; + void linksetTerrainCoro(std::string url, std::shared_ptr linksetsResponsderPtr, LLSD putData) const; + void charactersCoro(std::string url, request_id_t requestId, object_request_callback_t callback) const; + + //void handleNavMeshStatusRequest(const LLPathfindingNavMeshStatus &pNavMeshStatus, LLViewerRegion *pRegion, bool pIsGetStatusOnly); + void handleNavMeshStatusUpdate(const LLPathfindingNavMeshStatus &pNavMeshStatus); + + void handleAgentState(bool pCanRebakeRegion); + + LLPathfindingNavMeshPtr getNavMeshForRegion(const LLUUID &pRegionUUID); + LLPathfindingNavMeshPtr getNavMeshForRegion(LLViewerRegion *pRegion); + + std::string getNavMeshStatusURLForCurrentRegion() const; + std::string getNavMeshStatusURLForRegion(LLViewerRegion *pRegion) const; + std::string getRetrieveNavMeshURLForRegion(LLViewerRegion *pRegion) const; + std::string getRetrieveObjectLinksetsURLForCurrentRegion() const; + std::string getChangeObjectLinksetsURLForCurrentRegion() const; + std::string getTerrainLinksetsURLForCurrentRegion() const; + std::string getCharactersURLForCurrentRegion() const; + std::string getAgentStateURLForRegion(LLViewerRegion *pRegion) const; + std::string getCapabilityURLForCurrentRegion(const std::string &pCapabilityName) const; + std::string getCapabilityURLForRegion(LLViewerRegion *pRegion, const std::string &pCapabilityName) const; + LLViewerRegion *getCurrentRegion() const; + + NavMeshMap mNavMeshMap; + agent_state_signal_t mAgentStateSignal; +}; + +#endif // LL_LLPATHFINDINGMANAGER_H diff --git a/indra/newview/llpathfindingobject.h b/indra/newview/llpathfindingobject.h index 4adf780268..17f12efd83 100644 --- a/indra/newview/llpathfindingobject.h +++ b/indra/newview/llpathfindingobject.h @@ -1,92 +1,92 @@ -/** -* @file llpathfindingobject.h -* @brief Header file for llpathfindingobject -* @author Stinson@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ -#ifndef LL_LLPATHFINDINGOBJECT_H -#define LL_LLPATHFINDINGOBJECT_H - -#include - -#include -#include -#include - -#include "llavatarname.h" -#include "llavatarnamecache.h" -#include "lluuid.h" -#include "v3math.h" - -class LLPathfindingObject; -class LLSD; - -typedef std::shared_ptr LLPathfindingObjectPtr; - -class LLPathfindingObject -{ -public: - LLPathfindingObject(); - LLPathfindingObject(const std::string &pUUID, const LLSD &pObjectData); - LLPathfindingObject(const LLPathfindingObject& pOther); - virtual ~LLPathfindingObject(); - - LLPathfindingObject& operator =(const LLPathfindingObject& pOther); - - inline const LLUUID& getUUID() const {return mUUID;}; - inline const std::string& getName() const {return mName;}; - inline const std::string& getDescription() const {return mDescription;}; - inline bool hasOwner() const {return mOwnerUUID.notNull();}; - inline bool hasOwnerName() const {return mHasOwnerName;}; - std::string getOwnerName() const; - inline bool isGroupOwned() const {return mIsGroupOwned;}; - inline const LLVector3& getLocation() const {return mLocation;}; - - typedef boost::function name_callback_t; - typedef boost::signals2::signal name_signal_t; - typedef boost::signals2::connection name_connection_t; - - name_connection_t registerOwnerNameListener(name_callback_t pOwnerNameCallback); - -protected: - -private: - void parseObjectData(const LLSD &pObjectData); - - void fetchOwnerName(); - void handleAvatarNameFetch(const LLUUID &pOwnerUUID, const LLAvatarName &pAvatarName); - void disconnectAvatarNameCacheConnection(); - - LLUUID mUUID; - std::string mName; - std::string mDescription; - LLUUID mOwnerUUID; - bool mHasOwnerName; - LLAvatarName mOwnerName; - LLAvatarNameCache::callback_connection_t mAvatarNameCacheConnection; - bool mIsGroupOwned; - LLVector3 mLocation; - name_signal_t mOwnerNameSignal; -}; - -#endif // LL_LLPATHFINDINGOBJECT_H +/** +* @file llpathfindingobject.h +* @brief Header file for llpathfindingobject +* @author Stinson@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LL_LLPATHFINDINGOBJECT_H +#define LL_LLPATHFINDINGOBJECT_H + +#include + +#include +#include +#include + +#include "llavatarname.h" +#include "llavatarnamecache.h" +#include "lluuid.h" +#include "v3math.h" + +class LLPathfindingObject; +class LLSD; + +typedef std::shared_ptr LLPathfindingObjectPtr; + +class LLPathfindingObject +{ +public: + LLPathfindingObject(); + LLPathfindingObject(const std::string &pUUID, const LLSD &pObjectData); + LLPathfindingObject(const LLPathfindingObject& pOther); + virtual ~LLPathfindingObject(); + + LLPathfindingObject& operator =(const LLPathfindingObject& pOther); + + inline const LLUUID& getUUID() const {return mUUID;}; + inline const std::string& getName() const {return mName;}; + inline const std::string& getDescription() const {return mDescription;}; + inline bool hasOwner() const {return mOwnerUUID.notNull();}; + inline bool hasOwnerName() const {return mHasOwnerName;}; + std::string getOwnerName() const; + inline bool isGroupOwned() const {return mIsGroupOwned;}; + inline const LLVector3& getLocation() const {return mLocation;}; + + typedef boost::function name_callback_t; + typedef boost::signals2::signal name_signal_t; + typedef boost::signals2::connection name_connection_t; + + name_connection_t registerOwnerNameListener(name_callback_t pOwnerNameCallback); + +protected: + +private: + void parseObjectData(const LLSD &pObjectData); + + void fetchOwnerName(); + void handleAvatarNameFetch(const LLUUID &pOwnerUUID, const LLAvatarName &pAvatarName); + void disconnectAvatarNameCacheConnection(); + + LLUUID mUUID; + std::string mName; + std::string mDescription; + LLUUID mOwnerUUID; + bool mHasOwnerName; + LLAvatarName mOwnerName; + LLAvatarNameCache::callback_connection_t mAvatarNameCacheConnection; + bool mIsGroupOwned; + LLVector3 mLocation; + name_signal_t mOwnerNameSignal; +}; + +#endif // LL_LLPATHFINDINGOBJECT_H diff --git a/indra/newview/llpathfindingpathtool.cpp b/indra/newview/llpathfindingpathtool.cpp index 9d771beceb..a92a8fe138 100644 --- a/indra/newview/llpathfindingpathtool.cpp +++ b/indra/newview/llpathfindingpathtool.cpp @@ -1,466 +1,466 @@ -/** -* @file llpathfindingpathtool.cpp -* @brief Implementation of llpathfindingpathtool -* @author Stinson@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - - -#include "llviewerprecompiledheaders.h" - -#include "llpathfindingpathtool.h" - -#include -#include - -#include "llagent.h" -#include "llpathfindingmanager.h" -#include "llpathinglib.h" -#include "llsingleton.h" -#include "lltool.h" -#include "llviewercamera.h" -#include "llviewerregion.h" -#include "llviewerwindow.h" - -#define PATH_TOOL_NAME "PathfindingPathTool" - -LLPathfindingPathTool::LLPathfindingPathTool() - : LLTool(PATH_TOOL_NAME), - mFinalPathData(), - mTempPathData(), - mPathResult(LLPathingLib::LLPL_NO_PATH), - mCharacterType(kCharacterTypeNone), - mPathEventSignal(), - mIsLeftMouseButtonHeld(false), - mIsMiddleMouseButtonHeld(false), - mIsRightMouseButtonHeld(false) -{ - setCharacterWidth(1.0f); - setCharacterType(mCharacterType); -} - -LLPathfindingPathTool::~LLPathfindingPathTool() -{ -} - -bool LLPathfindingPathTool::handleMouseDown(S32 pX, S32 pY, MASK pMask) -{ - bool returnVal = false; - - if (!mIsLeftMouseButtonHeld && !mIsMiddleMouseButtonHeld && !mIsRightMouseButtonHeld) - { - if (isAnyPathToolModKeys(pMask)) - { - gViewerWindow->setCursor(isPointAModKeys(pMask) - ? UI_CURSOR_TOOLPATHFINDING_PATH_START_ADD - : UI_CURSOR_TOOLPATHFINDING_PATH_END_ADD); - computeFinalPoints(pX, pY, pMask); - mIsLeftMouseButtonHeld = true; - setMouseCapture(true); - returnVal = true; - } - else if (!isCameraModKeys(pMask)) - { - gViewerWindow->setCursor(UI_CURSOR_TOOLNO); - mIsLeftMouseButtonHeld = true; - setMouseCapture(true); - returnVal = true; - } - } - mIsLeftMouseButtonHeld = true; - - return returnVal; -} - -bool LLPathfindingPathTool::handleMouseUp(S32 pX, S32 pY, MASK pMask) -{ - bool returnVal = false; - - if (mIsLeftMouseButtonHeld && !mIsMiddleMouseButtonHeld && !mIsRightMouseButtonHeld) - { - computeFinalPoints(pX, pY, pMask); - setMouseCapture(false); - returnVal = true; - } - mIsLeftMouseButtonHeld = false; - - return returnVal; -} - -bool LLPathfindingPathTool::handleMiddleMouseDown(S32 pX, S32 pY, MASK pMask) -{ - setMouseCapture(true); - mIsMiddleMouseButtonHeld = true; - gViewerWindow->setCursor(UI_CURSOR_TOOLNO); - - return true; -} - -bool LLPathfindingPathTool::handleMiddleMouseUp(S32 pX, S32 pY, MASK pMask) -{ - if (!mIsLeftMouseButtonHeld && mIsMiddleMouseButtonHeld && !mIsRightMouseButtonHeld) - { - setMouseCapture(false); - } - mIsMiddleMouseButtonHeld = false; - - return true; -} - -bool LLPathfindingPathTool::handleRightMouseDown(S32 pX, S32 pY, MASK pMask) -{ - setMouseCapture(true); - mIsRightMouseButtonHeld = true; - gViewerWindow->setCursor(UI_CURSOR_TOOLNO); - - return true; -} - -bool LLPathfindingPathTool::handleRightMouseUp(S32 pX, S32 pY, MASK pMask) -{ - if (!mIsLeftMouseButtonHeld && !mIsMiddleMouseButtonHeld && mIsRightMouseButtonHeld) - { - setMouseCapture(false); - } - mIsRightMouseButtonHeld = false; - - return true; -} - -bool LLPathfindingPathTool::handleDoubleClick(S32 pX, S32 pY, MASK pMask) -{ - return true; -} - -bool LLPathfindingPathTool::handleHover(S32 pX, S32 pY, MASK pMask) -{ - bool returnVal = false; - - if (!mIsLeftMouseButtonHeld && !mIsMiddleMouseButtonHeld && !mIsRightMouseButtonHeld && !isAnyPathToolModKeys(pMask)) - { - gViewerWindow->setCursor(UI_CURSOR_TOOLPATHFINDING); - } - - if (!mIsMiddleMouseButtonHeld && !mIsRightMouseButtonHeld && isAnyPathToolModKeys(pMask)) - { - gViewerWindow->setCursor(isPointAModKeys(pMask) - ? (mIsLeftMouseButtonHeld ? UI_CURSOR_TOOLPATHFINDING_PATH_START_ADD : UI_CURSOR_TOOLPATHFINDING_PATH_START) - : (mIsLeftMouseButtonHeld ? UI_CURSOR_TOOLPATHFINDING_PATH_END_ADD : UI_CURSOR_TOOLPATHFINDING_PATH_END)); - computeTempPoints(pX, pY, pMask); - returnVal = true; - } - else - { - clearTemp(); - computeFinalPath(); - } - - return returnVal; -} - -bool LLPathfindingPathTool::handleKey(KEY pKey, MASK pMask) -{ - // Eat the escape key or else the camera tool will pick up and reset to default view. This, - // in turn, will cause some other methods to get called. And one of those methods will reset - // the current toolset back to the basic toolset. This means that the pathfinding path toolset - // will no longer be active, but typically with pathfinding path elements on screen. - return (pKey == KEY_ESCAPE); -} - -LLPathfindingPathTool::EPathStatus LLPathfindingPathTool::getPathStatus() const -{ - EPathStatus status = kPathStatusUnknown; - - if (LLPathingLib::getInstance() == NULL) - { - status = kPathStatusNotImplemented; - } - else if ((gAgent.getRegion() != NULL) && !gAgent.getRegion()->capabilitiesReceived()) - { - status = kPathStatusUnknown; - } - else if (!LLPathfindingManager::getInstance()->isPathfindingEnabledForCurrentRegion()) - { - status = kPathStatusNotEnabled; - } - else if (!hasFinalA() && !hasFinalB()) - { - status = kPathStatusChooseStartAndEndPoints; - } - else if (!hasFinalA()) - { - status = kPathStatusChooseStartPoint; - } - else if (!hasFinalB()) - { - status = kPathStatusChooseEndPoint; - } - else if (mPathResult == LLPathingLib::LLPL_PATH_GENERATED_OK) - { - status = kPathStatusHasValidPath; - } - else if (mPathResult == LLPathingLib::LLPL_NO_PATH) - { - status = kPathStatusHasInvalidPath; - } - else - { - status = kPathStatusError; - } - - return status; -} - -F32 LLPathfindingPathTool::getCharacterWidth() const -{ - return mFinalPathData.mCharacterWidth; -} - -void LLPathfindingPathTool::setCharacterWidth(F32 pCharacterWidth) -{ - mFinalPathData.mCharacterWidth = pCharacterWidth; - mTempPathData.mCharacterWidth = pCharacterWidth; - computeFinalPath(); -} - -LLPathfindingPathTool::ECharacterType LLPathfindingPathTool::getCharacterType() const -{ - return mCharacterType; -} - -void LLPathfindingPathTool::setCharacterType(ECharacterType pCharacterType) -{ - mCharacterType = pCharacterType; - - LLPathingLib::LLPLCharacterType characterType; - switch (pCharacterType) - { - case kCharacterTypeNone : - characterType = LLPathingLib::LLPL_CHARACTER_TYPE_NONE; - break; - case kCharacterTypeA : - characterType = LLPathingLib::LLPL_CHARACTER_TYPE_A; - break; - case kCharacterTypeB : - characterType = LLPathingLib::LLPL_CHARACTER_TYPE_B; - break; - case kCharacterTypeC : - characterType = LLPathingLib::LLPL_CHARACTER_TYPE_C; - break; - case kCharacterTypeD : - characterType = LLPathingLib::LLPL_CHARACTER_TYPE_D; - break; - default : - characterType = LLPathingLib::LLPL_CHARACTER_TYPE_NONE; - llassert(0); - break; - } - mFinalPathData.mCharacterType = characterType; - mTempPathData.mCharacterType = characterType; - computeFinalPath(); -} - -bool LLPathfindingPathTool::isRenderPath() const -{ - return (hasFinalA() || hasFinalB() || hasTempA() || hasTempB()); -} - -void LLPathfindingPathTool::clearPath() -{ - clearFinal(); - clearTemp(); - computeFinalPath(); -} - -LLPathfindingPathTool::path_event_slot_t LLPathfindingPathTool::registerPathEventListener(path_event_callback_t pPathEventCallback) -{ - return mPathEventSignal.connect(pPathEventCallback); -} - -bool LLPathfindingPathTool::isAnyPathToolModKeys(MASK pMask) const -{ - return ((pMask & (MASK_CONTROL|MASK_SHIFT)) != 0); -} - -bool LLPathfindingPathTool::isPointAModKeys(MASK pMask) const -{ - return ((pMask & MASK_CONTROL) != 0); -} - -bool LLPathfindingPathTool::isPointBModKeys(MASK pMask) const -{ - return ((pMask & MASK_SHIFT) != 0); -} - -bool LLPathfindingPathTool::isCameraModKeys(MASK pMask) const -{ - return ((pMask & MASK_ALT) != 0); -} - -void LLPathfindingPathTool::getRayPoints(S32 pX, S32 pY, LLVector3 &pRayStart, LLVector3 &pRayEnd) const -{ - LLVector3 dv = gViewerWindow->mouseDirectionGlobal(pX, pY); - LLVector3 mousePos = LLViewerCamera::getInstance()->getOrigin(); - pRayStart = mousePos; - pRayEnd = mousePos + dv * 150; -} - -void LLPathfindingPathTool::computeFinalPoints(S32 pX, S32 pY, MASK pMask) -{ - LLVector3 rayStart, rayEnd; - getRayPoints(pX, pY, rayStart, rayEnd); - - if (isPointAModKeys(pMask)) - { - setFinalA(rayStart, rayEnd); - } - else if (isPointBModKeys(pMask)) - { - setFinalB(rayStart, rayEnd); - } - computeFinalPath(); -} - -void LLPathfindingPathTool::computeTempPoints(S32 pX, S32 pY, MASK pMask) -{ - LLVector3 rayStart, rayEnd; - getRayPoints(pX, pY, rayStart, rayEnd); - - if (isPointAModKeys(pMask)) - { - setTempA(rayStart, rayEnd); - if (hasFinalB()) - { - setTempB(getFinalBStart(), getFinalBEnd()); - } - } - else if (isPointBModKeys(pMask)) - { - if (hasFinalA()) - { - setTempA(getFinalAStart(), getFinalAEnd()); - } - setTempB(rayStart, rayEnd); - } - computeTempPath(); -} - -void LLPathfindingPathTool::setFinalA(const LLVector3 &pStartPoint, const LLVector3 &pEndPoint) -{ - mFinalPathData.mStartPointA = pStartPoint; - mFinalPathData.mEndPointA = pEndPoint; - mFinalPathData.mHasPointA = true; -} - -bool LLPathfindingPathTool::hasFinalA() const -{ - return mFinalPathData.mHasPointA; -} - -const LLVector3 &LLPathfindingPathTool::getFinalAStart() const -{ - return mFinalPathData.mStartPointA; -} - -const LLVector3 &LLPathfindingPathTool::getFinalAEnd() const -{ - return mFinalPathData.mEndPointA; -} - -void LLPathfindingPathTool::setTempA(const LLVector3 &pStartPoint, const LLVector3 &pEndPoint) -{ - mTempPathData.mStartPointA = pStartPoint; - mTempPathData.mEndPointA = pEndPoint; - mTempPathData.mHasPointA = true; -} - -bool LLPathfindingPathTool::hasTempA() const -{ - return mTempPathData.mHasPointA; -} - -void LLPathfindingPathTool::setFinalB(const LLVector3 &pStartPoint, const LLVector3 &pEndPoint) -{ - mFinalPathData.mStartPointB = pStartPoint; - mFinalPathData.mEndPointB = pEndPoint; - mFinalPathData.mHasPointB = true; -} - -bool LLPathfindingPathTool::hasFinalB() const -{ - return mFinalPathData.mHasPointB; -} - -const LLVector3 &LLPathfindingPathTool::getFinalBStart() const -{ - return mFinalPathData.mStartPointB; -} - -const LLVector3 &LLPathfindingPathTool::getFinalBEnd() const -{ - return mFinalPathData.mEndPointB; -} - -void LLPathfindingPathTool::setTempB(const LLVector3 &pStartPoint, const LLVector3 &pEndPoint) -{ - mTempPathData.mStartPointB = pStartPoint; - mTempPathData.mEndPointB = pEndPoint; - mTempPathData.mHasPointB = true; -} - -bool LLPathfindingPathTool::hasTempB() const -{ - return mTempPathData.mHasPointB; -} - -void LLPathfindingPathTool::clearFinal() -{ - mFinalPathData.mHasPointA = false; - mFinalPathData.mHasPointB = false; -} - -void LLPathfindingPathTool::clearTemp() -{ - mTempPathData.mHasPointA = false; - mTempPathData.mHasPointB = false; -} - -void LLPathfindingPathTool::computeFinalPath() -{ - mPathResult = LLPathingLib::LLPL_NO_PATH; - if (LLPathingLib::getInstance() != NULL) - { - mPathResult = LLPathingLib::getInstance()->generatePath(mFinalPathData); - } - mPathEventSignal(); -} - -void LLPathfindingPathTool::computeTempPath() -{ - mPathResult = LLPathingLib::LLPL_NO_PATH; - if (LLPathingLib::getInstance() != NULL) - { - mPathResult = LLPathingLib::getInstance()->generatePath(mTempPathData); - } - mPathEventSignal(); -} +/** +* @file llpathfindingpathtool.cpp +* @brief Implementation of llpathfindingpathtool +* @author Stinson@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + + +#include "llviewerprecompiledheaders.h" + +#include "llpathfindingpathtool.h" + +#include +#include + +#include "llagent.h" +#include "llpathfindingmanager.h" +#include "llpathinglib.h" +#include "llsingleton.h" +#include "lltool.h" +#include "llviewercamera.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" + +#define PATH_TOOL_NAME "PathfindingPathTool" + +LLPathfindingPathTool::LLPathfindingPathTool() + : LLTool(PATH_TOOL_NAME), + mFinalPathData(), + mTempPathData(), + mPathResult(LLPathingLib::LLPL_NO_PATH), + mCharacterType(kCharacterTypeNone), + mPathEventSignal(), + mIsLeftMouseButtonHeld(false), + mIsMiddleMouseButtonHeld(false), + mIsRightMouseButtonHeld(false) +{ + setCharacterWidth(1.0f); + setCharacterType(mCharacterType); +} + +LLPathfindingPathTool::~LLPathfindingPathTool() +{ +} + +bool LLPathfindingPathTool::handleMouseDown(S32 pX, S32 pY, MASK pMask) +{ + bool returnVal = false; + + if (!mIsLeftMouseButtonHeld && !mIsMiddleMouseButtonHeld && !mIsRightMouseButtonHeld) + { + if (isAnyPathToolModKeys(pMask)) + { + gViewerWindow->setCursor(isPointAModKeys(pMask) + ? UI_CURSOR_TOOLPATHFINDING_PATH_START_ADD + : UI_CURSOR_TOOLPATHFINDING_PATH_END_ADD); + computeFinalPoints(pX, pY, pMask); + mIsLeftMouseButtonHeld = true; + setMouseCapture(true); + returnVal = true; + } + else if (!isCameraModKeys(pMask)) + { + gViewerWindow->setCursor(UI_CURSOR_TOOLNO); + mIsLeftMouseButtonHeld = true; + setMouseCapture(true); + returnVal = true; + } + } + mIsLeftMouseButtonHeld = true; + + return returnVal; +} + +bool LLPathfindingPathTool::handleMouseUp(S32 pX, S32 pY, MASK pMask) +{ + bool returnVal = false; + + if (mIsLeftMouseButtonHeld && !mIsMiddleMouseButtonHeld && !mIsRightMouseButtonHeld) + { + computeFinalPoints(pX, pY, pMask); + setMouseCapture(false); + returnVal = true; + } + mIsLeftMouseButtonHeld = false; + + return returnVal; +} + +bool LLPathfindingPathTool::handleMiddleMouseDown(S32 pX, S32 pY, MASK pMask) +{ + setMouseCapture(true); + mIsMiddleMouseButtonHeld = true; + gViewerWindow->setCursor(UI_CURSOR_TOOLNO); + + return true; +} + +bool LLPathfindingPathTool::handleMiddleMouseUp(S32 pX, S32 pY, MASK pMask) +{ + if (!mIsLeftMouseButtonHeld && mIsMiddleMouseButtonHeld && !mIsRightMouseButtonHeld) + { + setMouseCapture(false); + } + mIsMiddleMouseButtonHeld = false; + + return true; +} + +bool LLPathfindingPathTool::handleRightMouseDown(S32 pX, S32 pY, MASK pMask) +{ + setMouseCapture(true); + mIsRightMouseButtonHeld = true; + gViewerWindow->setCursor(UI_CURSOR_TOOLNO); + + return true; +} + +bool LLPathfindingPathTool::handleRightMouseUp(S32 pX, S32 pY, MASK pMask) +{ + if (!mIsLeftMouseButtonHeld && !mIsMiddleMouseButtonHeld && mIsRightMouseButtonHeld) + { + setMouseCapture(false); + } + mIsRightMouseButtonHeld = false; + + return true; +} + +bool LLPathfindingPathTool::handleDoubleClick(S32 pX, S32 pY, MASK pMask) +{ + return true; +} + +bool LLPathfindingPathTool::handleHover(S32 pX, S32 pY, MASK pMask) +{ + bool returnVal = false; + + if (!mIsLeftMouseButtonHeld && !mIsMiddleMouseButtonHeld && !mIsRightMouseButtonHeld && !isAnyPathToolModKeys(pMask)) + { + gViewerWindow->setCursor(UI_CURSOR_TOOLPATHFINDING); + } + + if (!mIsMiddleMouseButtonHeld && !mIsRightMouseButtonHeld && isAnyPathToolModKeys(pMask)) + { + gViewerWindow->setCursor(isPointAModKeys(pMask) + ? (mIsLeftMouseButtonHeld ? UI_CURSOR_TOOLPATHFINDING_PATH_START_ADD : UI_CURSOR_TOOLPATHFINDING_PATH_START) + : (mIsLeftMouseButtonHeld ? UI_CURSOR_TOOLPATHFINDING_PATH_END_ADD : UI_CURSOR_TOOLPATHFINDING_PATH_END)); + computeTempPoints(pX, pY, pMask); + returnVal = true; + } + else + { + clearTemp(); + computeFinalPath(); + } + + return returnVal; +} + +bool LLPathfindingPathTool::handleKey(KEY pKey, MASK pMask) +{ + // Eat the escape key or else the camera tool will pick up and reset to default view. This, + // in turn, will cause some other methods to get called. And one of those methods will reset + // the current toolset back to the basic toolset. This means that the pathfinding path toolset + // will no longer be active, but typically with pathfinding path elements on screen. + return (pKey == KEY_ESCAPE); +} + +LLPathfindingPathTool::EPathStatus LLPathfindingPathTool::getPathStatus() const +{ + EPathStatus status = kPathStatusUnknown; + + if (LLPathingLib::getInstance() == NULL) + { + status = kPathStatusNotImplemented; + } + else if ((gAgent.getRegion() != NULL) && !gAgent.getRegion()->capabilitiesReceived()) + { + status = kPathStatusUnknown; + } + else if (!LLPathfindingManager::getInstance()->isPathfindingEnabledForCurrentRegion()) + { + status = kPathStatusNotEnabled; + } + else if (!hasFinalA() && !hasFinalB()) + { + status = kPathStatusChooseStartAndEndPoints; + } + else if (!hasFinalA()) + { + status = kPathStatusChooseStartPoint; + } + else if (!hasFinalB()) + { + status = kPathStatusChooseEndPoint; + } + else if (mPathResult == LLPathingLib::LLPL_PATH_GENERATED_OK) + { + status = kPathStatusHasValidPath; + } + else if (mPathResult == LLPathingLib::LLPL_NO_PATH) + { + status = kPathStatusHasInvalidPath; + } + else + { + status = kPathStatusError; + } + + return status; +} + +F32 LLPathfindingPathTool::getCharacterWidth() const +{ + return mFinalPathData.mCharacterWidth; +} + +void LLPathfindingPathTool::setCharacterWidth(F32 pCharacterWidth) +{ + mFinalPathData.mCharacterWidth = pCharacterWidth; + mTempPathData.mCharacterWidth = pCharacterWidth; + computeFinalPath(); +} + +LLPathfindingPathTool::ECharacterType LLPathfindingPathTool::getCharacterType() const +{ + return mCharacterType; +} + +void LLPathfindingPathTool::setCharacterType(ECharacterType pCharacterType) +{ + mCharacterType = pCharacterType; + + LLPathingLib::LLPLCharacterType characterType; + switch (pCharacterType) + { + case kCharacterTypeNone : + characterType = LLPathingLib::LLPL_CHARACTER_TYPE_NONE; + break; + case kCharacterTypeA : + characterType = LLPathingLib::LLPL_CHARACTER_TYPE_A; + break; + case kCharacterTypeB : + characterType = LLPathingLib::LLPL_CHARACTER_TYPE_B; + break; + case kCharacterTypeC : + characterType = LLPathingLib::LLPL_CHARACTER_TYPE_C; + break; + case kCharacterTypeD : + characterType = LLPathingLib::LLPL_CHARACTER_TYPE_D; + break; + default : + characterType = LLPathingLib::LLPL_CHARACTER_TYPE_NONE; + llassert(0); + break; + } + mFinalPathData.mCharacterType = characterType; + mTempPathData.mCharacterType = characterType; + computeFinalPath(); +} + +bool LLPathfindingPathTool::isRenderPath() const +{ + return (hasFinalA() || hasFinalB() || hasTempA() || hasTempB()); +} + +void LLPathfindingPathTool::clearPath() +{ + clearFinal(); + clearTemp(); + computeFinalPath(); +} + +LLPathfindingPathTool::path_event_slot_t LLPathfindingPathTool::registerPathEventListener(path_event_callback_t pPathEventCallback) +{ + return mPathEventSignal.connect(pPathEventCallback); +} + +bool LLPathfindingPathTool::isAnyPathToolModKeys(MASK pMask) const +{ + return ((pMask & (MASK_CONTROL|MASK_SHIFT)) != 0); +} + +bool LLPathfindingPathTool::isPointAModKeys(MASK pMask) const +{ + return ((pMask & MASK_CONTROL) != 0); +} + +bool LLPathfindingPathTool::isPointBModKeys(MASK pMask) const +{ + return ((pMask & MASK_SHIFT) != 0); +} + +bool LLPathfindingPathTool::isCameraModKeys(MASK pMask) const +{ + return ((pMask & MASK_ALT) != 0); +} + +void LLPathfindingPathTool::getRayPoints(S32 pX, S32 pY, LLVector3 &pRayStart, LLVector3 &pRayEnd) const +{ + LLVector3 dv = gViewerWindow->mouseDirectionGlobal(pX, pY); + LLVector3 mousePos = LLViewerCamera::getInstance()->getOrigin(); + pRayStart = mousePos; + pRayEnd = mousePos + dv * 150; +} + +void LLPathfindingPathTool::computeFinalPoints(S32 pX, S32 pY, MASK pMask) +{ + LLVector3 rayStart, rayEnd; + getRayPoints(pX, pY, rayStart, rayEnd); + + if (isPointAModKeys(pMask)) + { + setFinalA(rayStart, rayEnd); + } + else if (isPointBModKeys(pMask)) + { + setFinalB(rayStart, rayEnd); + } + computeFinalPath(); +} + +void LLPathfindingPathTool::computeTempPoints(S32 pX, S32 pY, MASK pMask) +{ + LLVector3 rayStart, rayEnd; + getRayPoints(pX, pY, rayStart, rayEnd); + + if (isPointAModKeys(pMask)) + { + setTempA(rayStart, rayEnd); + if (hasFinalB()) + { + setTempB(getFinalBStart(), getFinalBEnd()); + } + } + else if (isPointBModKeys(pMask)) + { + if (hasFinalA()) + { + setTempA(getFinalAStart(), getFinalAEnd()); + } + setTempB(rayStart, rayEnd); + } + computeTempPath(); +} + +void LLPathfindingPathTool::setFinalA(const LLVector3 &pStartPoint, const LLVector3 &pEndPoint) +{ + mFinalPathData.mStartPointA = pStartPoint; + mFinalPathData.mEndPointA = pEndPoint; + mFinalPathData.mHasPointA = true; +} + +bool LLPathfindingPathTool::hasFinalA() const +{ + return mFinalPathData.mHasPointA; +} + +const LLVector3 &LLPathfindingPathTool::getFinalAStart() const +{ + return mFinalPathData.mStartPointA; +} + +const LLVector3 &LLPathfindingPathTool::getFinalAEnd() const +{ + return mFinalPathData.mEndPointA; +} + +void LLPathfindingPathTool::setTempA(const LLVector3 &pStartPoint, const LLVector3 &pEndPoint) +{ + mTempPathData.mStartPointA = pStartPoint; + mTempPathData.mEndPointA = pEndPoint; + mTempPathData.mHasPointA = true; +} + +bool LLPathfindingPathTool::hasTempA() const +{ + return mTempPathData.mHasPointA; +} + +void LLPathfindingPathTool::setFinalB(const LLVector3 &pStartPoint, const LLVector3 &pEndPoint) +{ + mFinalPathData.mStartPointB = pStartPoint; + mFinalPathData.mEndPointB = pEndPoint; + mFinalPathData.mHasPointB = true; +} + +bool LLPathfindingPathTool::hasFinalB() const +{ + return mFinalPathData.mHasPointB; +} + +const LLVector3 &LLPathfindingPathTool::getFinalBStart() const +{ + return mFinalPathData.mStartPointB; +} + +const LLVector3 &LLPathfindingPathTool::getFinalBEnd() const +{ + return mFinalPathData.mEndPointB; +} + +void LLPathfindingPathTool::setTempB(const LLVector3 &pStartPoint, const LLVector3 &pEndPoint) +{ + mTempPathData.mStartPointB = pStartPoint; + mTempPathData.mEndPointB = pEndPoint; + mTempPathData.mHasPointB = true; +} + +bool LLPathfindingPathTool::hasTempB() const +{ + return mTempPathData.mHasPointB; +} + +void LLPathfindingPathTool::clearFinal() +{ + mFinalPathData.mHasPointA = false; + mFinalPathData.mHasPointB = false; +} + +void LLPathfindingPathTool::clearTemp() +{ + mTempPathData.mHasPointA = false; + mTempPathData.mHasPointB = false; +} + +void LLPathfindingPathTool::computeFinalPath() +{ + mPathResult = LLPathingLib::LLPL_NO_PATH; + if (LLPathingLib::getInstance() != NULL) + { + mPathResult = LLPathingLib::getInstance()->generatePath(mFinalPathData); + } + mPathEventSignal(); +} + +void LLPathfindingPathTool::computeTempPath() +{ + mPathResult = LLPathingLib::LLPL_NO_PATH; + if (LLPathingLib::getInstance() != NULL) + { + mPathResult = LLPathingLib::getInstance()->generatePath(mTempPathData); + } + mPathEventSignal(); +} diff --git a/indra/newview/llpathfindingpathtool.h b/indra/newview/llpathfindingpathtool.h index c263c8ad76..189b9d4954 100644 --- a/indra/newview/llpathfindingpathtool.h +++ b/indra/newview/llpathfindingpathtool.h @@ -1,138 +1,138 @@ -/** -* @file llpathfindingpathtool.h -* @brief Header file for llpathfindingpathtool -* @author Stinson@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ -#ifndef LL_LLPATHFINDINGPATHTOOL_H -#define LL_LLPATHFINDINGPATHTOOL_H - -#include -#include - -#include "llpathinglib.h" -#include "llsingleton.h" -#include "lltool.h" - -class LLPathfindingPathTool : public LLTool, public LLSingleton -{ - LLSINGLETON(LLPathfindingPathTool); - virtual ~LLPathfindingPathTool(); - -public: - typedef enum - { - kPathStatusUnknown, - kPathStatusChooseStartAndEndPoints, - kPathStatusChooseStartPoint, - kPathStatusChooseEndPoint, - kPathStatusHasValidPath, - kPathStatusHasInvalidPath, - kPathStatusNotEnabled, - kPathStatusNotImplemented, - kPathStatusError - } EPathStatus; - - typedef enum - { - kCharacterTypeNone, - kCharacterTypeA, - kCharacterTypeB, - kCharacterTypeC, - kCharacterTypeD - } ECharacterType; - - typedef boost::function path_event_callback_t; - typedef boost::signals2::signal path_event_signal_t; - typedef boost::signals2::connection path_event_slot_t; - - virtual bool handleMouseDown(S32 pX, S32 pY, MASK pMask) override; - virtual bool handleMouseUp(S32 pX, S32 pY, MASK pMask) override; - virtual bool handleMiddleMouseDown(S32 pX, S32 pY, MASK pMask) override; - virtual bool handleMiddleMouseUp(S32 pX, S32 pY, MASK pMask) override; - virtual bool handleRightMouseDown(S32 pX, S32 pY, MASK pMask) override; - virtual bool handleRightMouseUp(S32 pX, S32 pY, MASK pMask) override; - virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; - - virtual bool handleHover(S32 pX, S32 pY, MASK pMask) override; - - virtual bool handleKey(KEY pKey, MASK pMask) override; - - EPathStatus getPathStatus() const; - - F32 getCharacterWidth() const; - void setCharacterWidth(F32 pCharacterWidth); - - ECharacterType getCharacterType() const; - void setCharacterType(ECharacterType pCharacterType); - - bool isRenderPath() const; - void clearPath(); - - path_event_slot_t registerPathEventListener(path_event_callback_t pPathEventCallback); - -protected: - -private: - bool isAnyPathToolModKeys(MASK pMask) const; - bool isPointAModKeys(MASK pMask) const; - bool isPointBModKeys(MASK pMask) const; - bool isCameraModKeys(MASK pMask) const; - - void getRayPoints(S32 pX, S32 pY, LLVector3 &pRayStart, LLVector3 &pRayEnd) const; - void computeFinalPoints(S32 pX, S32 pY, MASK pMask); - void computeTempPoints(S32 pX, S32 pY, MASK pMask); - - void setFinalA(const LLVector3 &pStartPoint, const LLVector3 &pEndPoint); - bool hasFinalA() const; - const LLVector3 &getFinalAStart() const; - const LLVector3 &getFinalAEnd() const; - - void setTempA(const LLVector3 &pStartPoint, const LLVector3 &pEndPoint); - bool hasTempA() const; - - void setFinalB(const LLVector3 &pStartPoint, const LLVector3 &pEndPoint); - bool hasFinalB() const; - const LLVector3 &getFinalBStart() const; - const LLVector3 &getFinalBEnd() const; - - void setTempB(const LLVector3 &pStartPoint, const LLVector3 &pEndPoint); - bool hasTempB() const; - - void clearFinal(); - void clearTemp(); - - void computeFinalPath(); - void computeTempPath(); - - LLPathingLib::PathingPacket mFinalPathData; - LLPathingLib::PathingPacket mTempPathData; - LLPathingLib::LLPLResult mPathResult; - ECharacterType mCharacterType; - path_event_signal_t mPathEventSignal; - bool mIsLeftMouseButtonHeld; - bool mIsMiddleMouseButtonHeld; - bool mIsRightMouseButtonHeld; -}; - -#endif // LL_LLPATHFINDINGPATHTOOL_H +/** +* @file llpathfindingpathtool.h +* @brief Header file for llpathfindingpathtool +* @author Stinson@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LL_LLPATHFINDINGPATHTOOL_H +#define LL_LLPATHFINDINGPATHTOOL_H + +#include +#include + +#include "llpathinglib.h" +#include "llsingleton.h" +#include "lltool.h" + +class LLPathfindingPathTool : public LLTool, public LLSingleton +{ + LLSINGLETON(LLPathfindingPathTool); + virtual ~LLPathfindingPathTool(); + +public: + typedef enum + { + kPathStatusUnknown, + kPathStatusChooseStartAndEndPoints, + kPathStatusChooseStartPoint, + kPathStatusChooseEndPoint, + kPathStatusHasValidPath, + kPathStatusHasInvalidPath, + kPathStatusNotEnabled, + kPathStatusNotImplemented, + kPathStatusError + } EPathStatus; + + typedef enum + { + kCharacterTypeNone, + kCharacterTypeA, + kCharacterTypeB, + kCharacterTypeC, + kCharacterTypeD + } ECharacterType; + + typedef boost::function path_event_callback_t; + typedef boost::signals2::signal path_event_signal_t; + typedef boost::signals2::connection path_event_slot_t; + + virtual bool handleMouseDown(S32 pX, S32 pY, MASK pMask) override; + virtual bool handleMouseUp(S32 pX, S32 pY, MASK pMask) override; + virtual bool handleMiddleMouseDown(S32 pX, S32 pY, MASK pMask) override; + virtual bool handleMiddleMouseUp(S32 pX, S32 pY, MASK pMask) override; + virtual bool handleRightMouseDown(S32 pX, S32 pY, MASK pMask) override; + virtual bool handleRightMouseUp(S32 pX, S32 pY, MASK pMask) override; + virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; + + virtual bool handleHover(S32 pX, S32 pY, MASK pMask) override; + + virtual bool handleKey(KEY pKey, MASK pMask) override; + + EPathStatus getPathStatus() const; + + F32 getCharacterWidth() const; + void setCharacterWidth(F32 pCharacterWidth); + + ECharacterType getCharacterType() const; + void setCharacterType(ECharacterType pCharacterType); + + bool isRenderPath() const; + void clearPath(); + + path_event_slot_t registerPathEventListener(path_event_callback_t pPathEventCallback); + +protected: + +private: + bool isAnyPathToolModKeys(MASK pMask) const; + bool isPointAModKeys(MASK pMask) const; + bool isPointBModKeys(MASK pMask) const; + bool isCameraModKeys(MASK pMask) const; + + void getRayPoints(S32 pX, S32 pY, LLVector3 &pRayStart, LLVector3 &pRayEnd) const; + void computeFinalPoints(S32 pX, S32 pY, MASK pMask); + void computeTempPoints(S32 pX, S32 pY, MASK pMask); + + void setFinalA(const LLVector3 &pStartPoint, const LLVector3 &pEndPoint); + bool hasFinalA() const; + const LLVector3 &getFinalAStart() const; + const LLVector3 &getFinalAEnd() const; + + void setTempA(const LLVector3 &pStartPoint, const LLVector3 &pEndPoint); + bool hasTempA() const; + + void setFinalB(const LLVector3 &pStartPoint, const LLVector3 &pEndPoint); + bool hasFinalB() const; + const LLVector3 &getFinalBStart() const; + const LLVector3 &getFinalBEnd() const; + + void setTempB(const LLVector3 &pStartPoint, const LLVector3 &pEndPoint); + bool hasTempB() const; + + void clearFinal(); + void clearTemp(); + + void computeFinalPath(); + void computeTempPath(); + + LLPathingLib::PathingPacket mFinalPathData; + LLPathingLib::PathingPacket mTempPathData; + LLPathingLib::LLPLResult mPathResult; + ECharacterType mCharacterType; + path_event_signal_t mPathEventSignal; + bool mIsLeftMouseButtonHeld; + bool mIsMiddleMouseButtonHeld; + bool mIsRightMouseButtonHeld; +}; + +#endif // LL_LLPATHFINDINGPATHTOOL_H diff --git a/indra/newview/llperfstats.cpp b/indra/newview/llperfstats.cpp index 3cf208f6d3..04b39a63f0 100644 --- a/indra/newview/llperfstats.cpp +++ b/indra/newview/llperfstats.cpp @@ -1,548 +1,548 @@ -/** -* @file llperfstats.cpp -* @brief Statistics collection to support autotune and perf flaoter. -* -* $LicenseInfo:firstyear=2022&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2022, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#include "llviewerprecompiledheaders.h" -#include "llperfstats.h" -#include "llcontrol.h" -#include "pipeline.h" -#include "llagentcamera.h" -#include "llviewerwindow.h" -#include "llvoavatar.h" -#include "llwindow.h" -#include "llworld.h" -#include - -extern LLControlGroup gSavedSettings; - -namespace LLPerfStats -{ - // avatar timing metrics in ms (updated once per mainloop iteration) - std::atomic sTotalAvatarTime = 0.f; - std::atomic sAverageAvatarTime = 0.f; - std::atomic sMaxAvatarTime = 0.f; - - std::atomic tunedAvatars{0}; - std::atomic renderAvatarMaxART_ns{(U64)(ART_UNLIMITED_NANOS)}; // highest render time we'll allow without culling features - bool belowTargetFPS{false}; - U32 lastGlobalPrefChange{0}; - U32 lastSleepedFrame{0}; - U64 meanFrameTime{0}; - std::mutex bufferToggleLock{}; - - F64 cpu_hertz{0.0}; - U32 vsync_max_fps{60}; - - Tunables tunables; - - std::atomic StatsRecorder::writeBuffer{0}; - bool StatsRecorder::collectionEnabled{true}; - LLUUID StatsRecorder::focusAv{LLUUID::null}; - bool StatsRecorder::autotuneInit{false}; - std::array StatsRecorder::statsDoubleBuffer{ {} }; - std::array StatsRecorder::max{ {} }; - std::array StatsRecorder::sum{ {} }; - - void Tunables::applyUpdates() - { - assert_main_thread(); - // these following variables are proxies for pipeline statics we do not need a two way update (no llviewercontrol handler) - if( tuningFlag & NonImpostors ){ gSavedSettings.setU32("RenderAvatarMaxNonImpostors", nonImpostors); }; - if( tuningFlag & ReflectionDetail ){ gSavedSettings.setS32("RenderReflectionDetail", reflectionDetail); }; - if( tuningFlag & FarClip ){ gSavedSettings.setF32("RenderFarClip", farClip); }; - if( tuningFlag & UserMinDrawDistance ){ gSavedSettings.setF32("AutoTuneRenderFarClipMin", userMinDrawDistance); }; - if( tuningFlag & UserTargetDrawDistance ){ gSavedSettings.setF32("AutoTuneRenderFarClipTarget", userTargetDrawDistance); }; - if( tuningFlag & UserImpostorDistance ){ gSavedSettings.setF32("AutoTuneImpostorFarAwayDistance", userImpostorDistance); }; - if( tuningFlag & UserImpostorDistanceTuningEnabled ){ gSavedSettings.setBOOL("AutoTuneImpostorByDistEnabled", userImpostorDistanceTuningEnabled); }; - if( tuningFlag & UserFPSTuningStrategy ){ gSavedSettings.setU32("TuningFPSStrategy", userFPSTuningStrategy); }; - if( tuningFlag & UserAutoTuneEnabled ){ gSavedSettings.setBOOL("AutoTuneFPS", userAutoTuneEnabled); }; - if( tuningFlag & UserAutoTuneLock ){ gSavedSettings.setBOOL("AutoTuneLock", userAutoTuneLock); }; - if( tuningFlag & UserTargetFPS ){ gSavedSettings.setU32("TargetFPS", userTargetFPS); }; - // Note: The Max ART slider is logarithmic and thus we have an intermediate proxy value - if( tuningFlag & UserARTCutoff ){ gSavedSettings.setF32("RenderAvatarMaxART", userARTCutoffSliderValue); }; - resetChanges(); - } - - void Tunables::updateRenderCostLimitFromSettings() - { - assert_main_thread(); - const auto newval = gSavedSettings.getF32("RenderAvatarMaxART"); - if(newval < log10(LLPerfStats::ART_UNLIMITED_NANOS/1000)) - { - LLPerfStats::renderAvatarMaxART_ns = pow(10,newval)*1000; - } - else - { - LLPerfStats::renderAvatarMaxART_ns = 0; - } - } - - // static - void Tunables::updateSettingsFromRenderCostLimit() - { - if( userARTCutoffSliderValue != log10( ( (F32)LLPerfStats::renderAvatarMaxART_ns )/1000 ) ) - { - if( LLPerfStats::renderAvatarMaxART_ns != 0 ) - { - updateUserARTCutoffSlider(log10( ( (F32)LLPerfStats::renderAvatarMaxART_ns )/1000 ) ); - } - else - { - updateUserARTCutoffSlider(log10( (F32)LLPerfStats::ART_UNLIMITED_NANOS/1000 ) ); - } - } - } - - void Tunables::initialiseFromSettings() - { - assert_main_thread(); - // the following variables are two way and have "push" in llviewercontrol - LLPerfStats::tunables.userMinDrawDistance = gSavedSettings.getF32("AutoTuneRenderFarClipMin"); - LLPerfStats::tunables.userTargetDrawDistance = gSavedSettings.getF32("AutoTuneRenderFarClipTarget"); - LLPerfStats::tunables.userImpostorDistance = gSavedSettings.getF32("AutoTuneImpostorFarAwayDistance"); - LLPerfStats::tunables.userImpostorDistanceTuningEnabled = gSavedSettings.getBOOL("AutoTuneImpostorByDistEnabled"); - LLPerfStats::tunables.userFPSTuningStrategy = gSavedSettings.getU32("TuningFPSStrategy"); - LLPerfStats::tunables.userTargetFPS = gSavedSettings.getU32("TargetFPS"); - LLPerfStats::tunables.vsyncEnabled = gSavedSettings.getBOOL("RenderVSyncEnable"); - - LLPerfStats::tunables.userAutoTuneLock = gSavedSettings.getBOOL("AutoTuneLock") && gSavedSettings.getU32("KeepAutoTuneLock"); - - if(gSavedSettings.getBOOL("AutoTuneLock") && !gSavedSettings.getU32("KeepAutoTuneLock")) - { - gSavedSettings.setBOOL("AutoTuneLock", false); - } - - LLPerfStats::tunables.userAutoTuneEnabled = LLPerfStats::tunables.userAutoTuneLock; - - if (LLPerfStats::tunables.userAutoTuneEnabled && !gSavedSettings.getBOOL("AutoTuneFPS")) - { - gSavedSettings.setBOOL("AutoTuneFPS", true); - } - - // Note: The Max ART slider is logarithmic and thus we have an intermediate proxy value - updateRenderCostLimitFromSettings(); - resetChanges(); - } - - StatsRecorder::StatsRecorder() - { - // create a queue - tunables.initialiseFromSettings(); - LLPerfStats::cpu_hertz = (F64)LLTrace::BlockTimer::countsPerSecond(); - LLPerfStats::vsync_max_fps = gViewerWindow->getWindow()->getRefreshRate(); - } - - // static - void StatsRecorder::toggleBuffer() - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS; - using ST = StatType_t; - - bool unreliable{false}; - LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_FRAME); - auto& sceneStats = statsDoubleBuffer[writeBuffer][static_cast(ObjType_t::OT_GENERAL)][LLUUID::null]; - auto& lastStats = statsDoubleBuffer[writeBuffer ^ 1][static_cast(ObjType_t::OT_GENERAL)][LLUUID::null]; - - static constexpr std::initializer_list sceneStatsToAvg = { - StatType_t::RENDER_FRAME, - StatType_t::RENDER_DISPLAY, - StatType_t::RENDER_HUDS, - StatType_t::RENDER_UI, - StatType_t::RENDER_SWAP, - // RENDER_LFS, - // RENDER_MESHREPO, - StatType_t::RENDER_IDLE }; - -#if 0 - static constexpr std::initializer_list avatarStatsToAvg = { - StatType_t::RENDER_GEOMETRY, - StatType_t::RENDER_SHADOWS, - StatType_t::RENDER_COMBINED, - StatType_t::RENDER_IDLE }; -#endif - - - if( /*sceneStats[static_cast(StatType_t::RENDER_FPSLIMIT)] != 0 ||*/ sceneStats[static_cast(StatType_t::RENDER_SLEEP)] != 0 ) - { - unreliable = true; - //lastStats[static_cast(StatType_t::RENDER_FPSLIMIT)] = sceneStats[static_cast(StatType_t::RENDER_FPSLIMIT)]; - lastStats[static_cast(StatType_t::RENDER_SLEEP)] = sceneStats[static_cast(StatType_t::RENDER_SLEEP)]; - lastStats[static_cast(StatType_t::RENDER_FRAME)] = sceneStats[static_cast(StatType_t::RENDER_FRAME)]; // bring over the total frame render time to deal with region crossing overlap issues - } - - if(!unreliable) - { - // only use these stats when things are reliable. - - for(auto & statEntry : sceneStatsToAvg) - { - auto avg = lastStats[static_cast(statEntry)]; - auto val = sceneStats[static_cast(statEntry)]; - sceneStats[static_cast(statEntry)] = avg + (val / SMOOTHING_PERIODS) - (avg / SMOOTHING_PERIODS); - // LL_INFOS("scenestats") << "Scenestat: " << static_cast(statEntry) << " before=" << avg << " new=" << val << " newavg=" << statsDoubleBuffer[writeBuffer][static_cast(ObjType_t::OT_GENERAL)][LLUUID::null][static_cast(statEntry)] << LL_ENDL; - } - } - - // swap the buffers - if(enabled()) - { - std::lock_guard lock{bufferToggleLock}; - writeBuffer ^= 1; - }; // note we are relying on atomic updates here. The risk is low and would cause minor errors in the stats display. - - // clean the write maps in all cases. - auto& statsTypeMatrix = statsDoubleBuffer[writeBuffer]; - for(auto& statsMapByType : statsTypeMatrix) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_STATS("Clear stats maps"); - for(auto& stat_entry : statsMapByType) - { - std::fill_n(stat_entry.second.begin() ,static_cast(ST::STATS_COUNT),0); - } - statsMapByType.clear(); - } - for(int i=0; i< static_cast(ObjType_t::OT_COUNT); i++) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_STATS("clear max/sum"); - max[writeBuffer][i].fill(0); - sum[writeBuffer][i].fill(0); - } - - // and now adjust the proxy vars so that the main thread can adjust the visuals. - if(autotuneInit && tunables.userAutoTuneEnabled) - { - updateAvatarParams(); - } - } - - // clear buffers when we change region or need a hard reset. - // static - void StatsRecorder::clearStatsBuffers() - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS; - using ST = StatType_t; - - auto& statsTypeMatrix = statsDoubleBuffer[writeBuffer]; - for(auto& statsMap : statsTypeMatrix) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_STATS("Clear stats maps"); - for(auto& stat_entry : statsMap) - { - std::fill_n(stat_entry.second.begin() ,static_cast(ST::STATS_COUNT),0); - } - statsMap.clear(); - } - for(int i=0; i< static_cast(ObjType_t::OT_COUNT); i++) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_STATS("clear max/sum"); - max[writeBuffer][i].fill(0); - sum[writeBuffer][i].fill(0); - } - // swap the clean buffers in - if(enabled()) - { - std::lock_guard lock{bufferToggleLock}; - writeBuffer ^= 1; - }; - // repeat before we start processing new stuff - for(auto& statsMap : statsTypeMatrix) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_STATS("Clear stats maps"); - for(auto& stat_entry : statsMap) - { - std::fill_n(stat_entry.second.begin() ,static_cast(ST::STATS_COUNT),0); - } - statsMap.clear(); - } - for(int i=0; i< static_cast(ObjType_t::OT_COUNT); i++) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_STATS("clear max/sum"); - max[writeBuffer][i].fill(0); - sum[writeBuffer][i].fill(0); - } - } - - // called once per main loop iteration on main thread - void updateClass() - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS; - - sTotalAvatarTime = LLVOAvatar::getTotalGPURenderTime(); - sAverageAvatarTime = LLVOAvatar::getAverageGPURenderTime(); - sMaxAvatarTime = LLVOAvatar::getMaxGPURenderTime(); - } - - //static - int StatsRecorder::countNearbyAvatars(S32 distance) - { - const auto our_pos = gAgentCamera.getCameraPositionGlobal(); - - std::vector positions; - uuid_vec_t avatar_ids; - LLWorld::getInstance()->getAvatars(&avatar_ids, &positions, our_pos, distance); - return positions.size(); - } - - const U32 NUM_PERIODS = 50; - void StatsRecorder::updateMeanFrameTime(U64 cur_frame_time_raw) - { - static std::deque frame_time_deque; - frame_time_deque.push_front(cur_frame_time_raw); - if (frame_time_deque.size() > NUM_PERIODS) - { - frame_time_deque.pop_back(); - } - - std::vector buf(frame_time_deque.begin(), frame_time_deque.end()); - std::sort(buf.begin(), buf.end()); - - LLPerfStats::meanFrameTime = (buf.size() % 2 == 0) ? (buf[buf.size() / 2 - 1] + buf[buf.size() / 2]) / 2 : buf[buf.size() / 2]; - } - U64 StatsRecorder::getMeanTotalFrameTime() - { - return LLPerfStats::meanFrameTime; - } - - // static - void StatsRecorder::updateAvatarParams() - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS; - - if(tunables.autoTuneTimeout) - { - LLPerfStats::lastSleepedFrame = gFrameCount; - tunables.autoTuneTimeout = false; - return; - } - // sleep time is basically forced sleep when window out of focus - auto tot_sleep_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_SLEEP); - // similar to sleep time, induced by FPS limit - //auto tot_limit_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_FPSLIMIT); - - - // the time spent this frame on the "doFrame" call. Treated as "tot time for frame" - auto tot_frame_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_FRAME); - - if( tot_sleep_time_raw != 0 ) - { - // Note: we do not average sleep - // if at some point we need to, the averaging will need to take this into account or - // we forever think we're in the background due to residuals. - LL_DEBUGS() << "No tuning when not in focus" << LL_ENDL; - LLPerfStats::lastSleepedFrame = gFrameCount; - return; - } - - U32 target_fps = tunables.vsyncEnabled ? std::min(LLPerfStats::vsync_max_fps, tunables.userTargetFPS) : tunables.userTargetFPS; - - if(LLPerfStats::lastSleepedFrame != 0) - { - // wait a short time after viewer regains focus - if((gFrameCount - LLPerfStats::lastSleepedFrame) > target_fps * 5) - { - LLPerfStats::lastSleepedFrame = 0; - } - else - { - return; - } - } - updateMeanFrameTime(tot_frame_time_raw); - - if(tunables.userImpostorDistanceTuningEnabled) - { - // if we have less than the user's "max Non-Impostors" avatars within the desired range then adjust the limit. - // also adjusts back up again for nearby crowds. - auto count = countNearbyAvatars(std::min(LLPipeline::RenderFarClip, tunables.userImpostorDistance)); - if( count != tunables.nonImpostors ) - { - tunables.updateNonImposters( (count < LLVOAvatar::NON_IMPOSTORS_MAX_SLIDER)?count : 0 ); - LL_DEBUGS("AutoTune") << "There are " << count << "avatars within " << std::min(LLPipeline::RenderFarClip, tunables.userImpostorDistance) << "m of the camera" << LL_ENDL; - } - } - - auto av_render_max_raw = ms_to_raw(sMaxAvatarTime); - // Is our target frame time lower than current? If so we need to take action to reduce draw overheads. - // cumulative avatar time (includes idle processing, attachments and base av) - auto tot_avatar_time_raw = ms_to_raw(sTotalAvatarTime); - - // The frametime budget we have based on the target FPS selected - auto target_frame_time_raw = (U64)llround(LLPerfStats::cpu_hertz / (target_fps == 0 ? 1 : target_fps)); - // LL_INFOS() << "Effective FPS(raw):" << tot_frame_time_raw << " Target:" << target_frame_time_raw << LL_ENDL; - auto inferredFPS{1000/(U32)std::max(raw_to_ms(tot_frame_time_raw),1.0)}; - U32 settingsChangeFrequency{inferredFPS > 50?inferredFPS:50}; - /*if( tot_limit_time_raw != 0) - { - // This could be problematic. - tot_frame_time_raw -= tot_limit_time_raw; - }*/ - - F64 time_buf = target_frame_time_raw * 0.1; - - // 1) Is the target frame time lower than current? - if ((target_frame_time_raw + time_buf) <= tot_frame_time_raw) - { - if (target_frame_time_raw - time_buf >= getMeanTotalFrameTime()) - { - belowTargetFPS = false; - LLPerfStats::lastGlobalPrefChange = gFrameCount; - return; - } - - if (!belowTargetFPS) - { - // this is the first frame under. hold fire to add a little hysteresis - belowTargetFPS = true; - LLPerfStats::lastGlobalPrefChange = gFrameCount; - } - // if so we've got work to do - - // how much of the frame was spent on non avatar related work? - U64 non_avatar_time_raw = tot_frame_time_raw > tot_avatar_time_raw ? tot_frame_time_raw - tot_avatar_time_raw : 0; - - // If the target frame time < scene time (estimated as non_avatar time) - U64 target_avatar_time_raw; - if(target_frame_time_raw < non_avatar_time_raw) - { - // we cannnot do this by avatar adjustment alone. - if((gFrameCount - LLPerfStats::lastGlobalPrefChange) > settingsChangeFrequency) // give changes a short time to take effect. - { - if(tunables.userFPSTuningStrategy != TUNE_AVATARS_ONLY) - { - // 1 - hack the water to opaque. all non opaque have a significant hit, this is a big boost for (arguably) a minor visual hit. - // the other reflection options make comparatively little change and if this overshoots we'll be stepping back up later -# if 0 // TODO RenderReflectionDetail went away - if(LLPipeline::RenderReflectionDetail != -2) - { - LLPerfStats::tunables.updateReflectionDetail(-2); - LLPerfStats::lastGlobalPrefChange = gFrameCount; - return; - } - else // deliberately "else" here so we only do one of these in any given frame -#endif - { - // step down the DD by 10m per update - auto new_dd = (LLPipeline::RenderFarClip - DD_STEP > tunables.userMinDrawDistance)?(LLPipeline::RenderFarClip - DD_STEP) : tunables.userMinDrawDistance; - if(new_dd != LLPipeline::RenderFarClip) - { - LLPerfStats::tunables.updateFarClip( new_dd ); - LLPerfStats::lastGlobalPrefChange = gFrameCount; - return; - } - } - } - // if we reach here, we've no more changes to make to tune scenery so we'll resort to agressive Avatar tuning - // Note: moved from outside "if changefrequency elapsed" to stop fallthrough and allow scenery changes time to take effect. - target_avatar_time_raw = 0; - } - else - { - // we made a settings change recently so let's give it time. - return; - } - } - else - { - // set desired avatar budget. - target_avatar_time_raw = target_frame_time_raw - non_avatar_time_raw; - } - - if ((target_avatar_time_raw < tot_avatar_time_raw) && (tunables.userFPSTuningStrategy != TUNE_SCENE_ONLY)) - { - // we need to spend less time drawing avatars to meet our budget - auto new_render_limit_ns {LLPerfStats::raw_to_ns(av_render_max_raw)}; - // max render this frame may be higher than the last (cos new entrants and jitter) so make sure we are heading in the right direction - if( new_render_limit_ns > renderAvatarMaxART_ns ) - { - new_render_limit_ns = renderAvatarMaxART_ns; - } - - if (new_render_limit_ns > LLPerfStats::ART_MIN_ADJUST_DOWN_NANOS) - { - new_render_limit_ns -= LLPerfStats::ART_MIN_ADJUST_DOWN_NANOS; - } - - // bounce at the bottom to prevent "no limit" - new_render_limit_ns = std::max((U64)new_render_limit_ns, (U64)LLPerfStats::ART_MINIMUM_NANOS); - - // assign the new value - if (renderAvatarMaxART_ns != new_render_limit_ns) - { - renderAvatarMaxART_ns = new_render_limit_ns; - tunables.updateSettingsFromRenderCostLimit(); - } - // LL_DEBUGS() << "AUTO_TUNE: avatar_budget adjusted to:" << new_render_limit_ns << LL_ENDL; - } - // LL_DEBUGS() << "AUTO_TUNE: Target frame time:"<< LLPerfStats::raw_to_us(target_frame_time_raw) << "usecs (non_avatar is " << LLPerfStats::raw_to_us(non_avatar_time_raw) << "usecs) Max cost limited=" << renderAvatarMaxART_ns << LL_ENDL; - } - else if ((LLPerfStats::raw_to_ns(target_frame_time_raw) > (LLPerfStats::raw_to_ns(tot_frame_time_raw) + renderAvatarMaxART_ns)) || - (tunables.vsyncEnabled && (target_fps == LLPerfStats::vsync_max_fps) && (target_frame_time_raw > getMeanTotalFrameTime()))) - { - if (belowTargetFPS) - { - // we reached target, force a pause - lastGlobalPrefChange = gFrameCount; - belowTargetFPS = false; - } - - // once we're over the FPS target we slow down further - if ((gFrameCount - lastGlobalPrefChange) > settingsChangeFrequency * 3) - { - if (!tunables.userAutoTuneLock) - { - // we've reached the target and stayed long enough to consider stable. - // turn off if we are not locked. - tunables.updateUserAutoTuneEnabled(false); - } - if (renderAvatarMaxART_ns > 0 && - LLPerfStats::tunedAvatars > 0 && - tunables.userFPSTuningStrategy != TUNE_SCENE_ONLY) - { - // if we have more time to spare let's shift up little in the hope we'll restore an avatar. - U64 up_step = LLPerfStats::tunedAvatars > 2 ? LLPerfStats::ART_MIN_ADJUST_UP_NANOS : LLPerfStats::ART_MIN_ADJUST_UP_NANOS * 2; - renderAvatarMaxART_ns += up_step; - tunables.updateSettingsFromRenderCostLimit(); - return; - } - if (tunables.userFPSTuningStrategy != TUNE_AVATARS_ONLY) - { - if (LLPipeline::RenderFarClip < tunables.userTargetDrawDistance) - { - LLPerfStats::tunables.updateFarClip( std::min(LLPipeline::RenderFarClip + DD_STEP, tunables.userTargetDrawDistance) ); - LLPerfStats::lastGlobalPrefChange = gFrameCount; - return; - } - if ((tot_frame_time_raw * 1.5) < target_frame_time_raw) - { - // if everything else is "max" and we have >50% headroom let's knock the water quality up a notch at a time. -# if 0 // RenderReflectionDetail went away - LLPerfStats::tunables.updateReflectionDetail( std::min(LLPipeline::RenderReflectionDetail + 1, tunables.userTargetReflections) ); -#endif - } - } - } - } - } -} +/** +* @file llperfstats.cpp +* @brief Statistics collection to support autotune and perf flaoter. +* +* $LicenseInfo:firstyear=2022&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2022, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" +#include "llperfstats.h" +#include "llcontrol.h" +#include "pipeline.h" +#include "llagentcamera.h" +#include "llviewerwindow.h" +#include "llvoavatar.h" +#include "llwindow.h" +#include "llworld.h" +#include + +extern LLControlGroup gSavedSettings; + +namespace LLPerfStats +{ + // avatar timing metrics in ms (updated once per mainloop iteration) + std::atomic sTotalAvatarTime = 0.f; + std::atomic sAverageAvatarTime = 0.f; + std::atomic sMaxAvatarTime = 0.f; + + std::atomic tunedAvatars{0}; + std::atomic renderAvatarMaxART_ns{(U64)(ART_UNLIMITED_NANOS)}; // highest render time we'll allow without culling features + bool belowTargetFPS{false}; + U32 lastGlobalPrefChange{0}; + U32 lastSleepedFrame{0}; + U64 meanFrameTime{0}; + std::mutex bufferToggleLock{}; + + F64 cpu_hertz{0.0}; + U32 vsync_max_fps{60}; + + Tunables tunables; + + std::atomic StatsRecorder::writeBuffer{0}; + bool StatsRecorder::collectionEnabled{true}; + LLUUID StatsRecorder::focusAv{LLUUID::null}; + bool StatsRecorder::autotuneInit{false}; + std::array StatsRecorder::statsDoubleBuffer{ {} }; + std::array StatsRecorder::max{ {} }; + std::array StatsRecorder::sum{ {} }; + + void Tunables::applyUpdates() + { + assert_main_thread(); + // these following variables are proxies for pipeline statics we do not need a two way update (no llviewercontrol handler) + if( tuningFlag & NonImpostors ){ gSavedSettings.setU32("RenderAvatarMaxNonImpostors", nonImpostors); }; + if( tuningFlag & ReflectionDetail ){ gSavedSettings.setS32("RenderReflectionDetail", reflectionDetail); }; + if( tuningFlag & FarClip ){ gSavedSettings.setF32("RenderFarClip", farClip); }; + if( tuningFlag & UserMinDrawDistance ){ gSavedSettings.setF32("AutoTuneRenderFarClipMin", userMinDrawDistance); }; + if( tuningFlag & UserTargetDrawDistance ){ gSavedSettings.setF32("AutoTuneRenderFarClipTarget", userTargetDrawDistance); }; + if( tuningFlag & UserImpostorDistance ){ gSavedSettings.setF32("AutoTuneImpostorFarAwayDistance", userImpostorDistance); }; + if( tuningFlag & UserImpostorDistanceTuningEnabled ){ gSavedSettings.setBOOL("AutoTuneImpostorByDistEnabled", userImpostorDistanceTuningEnabled); }; + if( tuningFlag & UserFPSTuningStrategy ){ gSavedSettings.setU32("TuningFPSStrategy", userFPSTuningStrategy); }; + if( tuningFlag & UserAutoTuneEnabled ){ gSavedSettings.setBOOL("AutoTuneFPS", userAutoTuneEnabled); }; + if( tuningFlag & UserAutoTuneLock ){ gSavedSettings.setBOOL("AutoTuneLock", userAutoTuneLock); }; + if( tuningFlag & UserTargetFPS ){ gSavedSettings.setU32("TargetFPS", userTargetFPS); }; + // Note: The Max ART slider is logarithmic and thus we have an intermediate proxy value + if( tuningFlag & UserARTCutoff ){ gSavedSettings.setF32("RenderAvatarMaxART", userARTCutoffSliderValue); }; + resetChanges(); + } + + void Tunables::updateRenderCostLimitFromSettings() + { + assert_main_thread(); + const auto newval = gSavedSettings.getF32("RenderAvatarMaxART"); + if(newval < log10(LLPerfStats::ART_UNLIMITED_NANOS/1000)) + { + LLPerfStats::renderAvatarMaxART_ns = pow(10,newval)*1000; + } + else + { + LLPerfStats::renderAvatarMaxART_ns = 0; + } + } + + // static + void Tunables::updateSettingsFromRenderCostLimit() + { + if( userARTCutoffSliderValue != log10( ( (F32)LLPerfStats::renderAvatarMaxART_ns )/1000 ) ) + { + if( LLPerfStats::renderAvatarMaxART_ns != 0 ) + { + updateUserARTCutoffSlider(log10( ( (F32)LLPerfStats::renderAvatarMaxART_ns )/1000 ) ); + } + else + { + updateUserARTCutoffSlider(log10( (F32)LLPerfStats::ART_UNLIMITED_NANOS/1000 ) ); + } + } + } + + void Tunables::initialiseFromSettings() + { + assert_main_thread(); + // the following variables are two way and have "push" in llviewercontrol + LLPerfStats::tunables.userMinDrawDistance = gSavedSettings.getF32("AutoTuneRenderFarClipMin"); + LLPerfStats::tunables.userTargetDrawDistance = gSavedSettings.getF32("AutoTuneRenderFarClipTarget"); + LLPerfStats::tunables.userImpostorDistance = gSavedSettings.getF32("AutoTuneImpostorFarAwayDistance"); + LLPerfStats::tunables.userImpostorDistanceTuningEnabled = gSavedSettings.getBOOL("AutoTuneImpostorByDistEnabled"); + LLPerfStats::tunables.userFPSTuningStrategy = gSavedSettings.getU32("TuningFPSStrategy"); + LLPerfStats::tunables.userTargetFPS = gSavedSettings.getU32("TargetFPS"); + LLPerfStats::tunables.vsyncEnabled = gSavedSettings.getBOOL("RenderVSyncEnable"); + + LLPerfStats::tunables.userAutoTuneLock = gSavedSettings.getBOOL("AutoTuneLock") && gSavedSettings.getU32("KeepAutoTuneLock"); + + if(gSavedSettings.getBOOL("AutoTuneLock") && !gSavedSettings.getU32("KeepAutoTuneLock")) + { + gSavedSettings.setBOOL("AutoTuneLock", false); + } + + LLPerfStats::tunables.userAutoTuneEnabled = LLPerfStats::tunables.userAutoTuneLock; + + if (LLPerfStats::tunables.userAutoTuneEnabled && !gSavedSettings.getBOOL("AutoTuneFPS")) + { + gSavedSettings.setBOOL("AutoTuneFPS", true); + } + + // Note: The Max ART slider is logarithmic and thus we have an intermediate proxy value + updateRenderCostLimitFromSettings(); + resetChanges(); + } + + StatsRecorder::StatsRecorder() + { + // create a queue + tunables.initialiseFromSettings(); + LLPerfStats::cpu_hertz = (F64)LLTrace::BlockTimer::countsPerSecond(); + LLPerfStats::vsync_max_fps = gViewerWindow->getWindow()->getRefreshRate(); + } + + // static + void StatsRecorder::toggleBuffer() + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS; + using ST = StatType_t; + + bool unreliable{false}; + LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_FRAME); + auto& sceneStats = statsDoubleBuffer[writeBuffer][static_cast(ObjType_t::OT_GENERAL)][LLUUID::null]; + auto& lastStats = statsDoubleBuffer[writeBuffer ^ 1][static_cast(ObjType_t::OT_GENERAL)][LLUUID::null]; + + static constexpr std::initializer_list sceneStatsToAvg = { + StatType_t::RENDER_FRAME, + StatType_t::RENDER_DISPLAY, + StatType_t::RENDER_HUDS, + StatType_t::RENDER_UI, + StatType_t::RENDER_SWAP, + // RENDER_LFS, + // RENDER_MESHREPO, + StatType_t::RENDER_IDLE }; + +#if 0 + static constexpr std::initializer_list avatarStatsToAvg = { + StatType_t::RENDER_GEOMETRY, + StatType_t::RENDER_SHADOWS, + StatType_t::RENDER_COMBINED, + StatType_t::RENDER_IDLE }; +#endif + + + if( /*sceneStats[static_cast(StatType_t::RENDER_FPSLIMIT)] != 0 ||*/ sceneStats[static_cast(StatType_t::RENDER_SLEEP)] != 0 ) + { + unreliable = true; + //lastStats[static_cast(StatType_t::RENDER_FPSLIMIT)] = sceneStats[static_cast(StatType_t::RENDER_FPSLIMIT)]; + lastStats[static_cast(StatType_t::RENDER_SLEEP)] = sceneStats[static_cast(StatType_t::RENDER_SLEEP)]; + lastStats[static_cast(StatType_t::RENDER_FRAME)] = sceneStats[static_cast(StatType_t::RENDER_FRAME)]; // bring over the total frame render time to deal with region crossing overlap issues + } + + if(!unreliable) + { + // only use these stats when things are reliable. + + for(auto & statEntry : sceneStatsToAvg) + { + auto avg = lastStats[static_cast(statEntry)]; + auto val = sceneStats[static_cast(statEntry)]; + sceneStats[static_cast(statEntry)] = avg + (val / SMOOTHING_PERIODS) - (avg / SMOOTHING_PERIODS); + // LL_INFOS("scenestats") << "Scenestat: " << static_cast(statEntry) << " before=" << avg << " new=" << val << " newavg=" << statsDoubleBuffer[writeBuffer][static_cast(ObjType_t::OT_GENERAL)][LLUUID::null][static_cast(statEntry)] << LL_ENDL; + } + } + + // swap the buffers + if(enabled()) + { + std::lock_guard lock{bufferToggleLock}; + writeBuffer ^= 1; + }; // note we are relying on atomic updates here. The risk is low and would cause minor errors in the stats display. + + // clean the write maps in all cases. + auto& statsTypeMatrix = statsDoubleBuffer[writeBuffer]; + for(auto& statsMapByType : statsTypeMatrix) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_STATS("Clear stats maps"); + for(auto& stat_entry : statsMapByType) + { + std::fill_n(stat_entry.second.begin() ,static_cast(ST::STATS_COUNT),0); + } + statsMapByType.clear(); + } + for(int i=0; i< static_cast(ObjType_t::OT_COUNT); i++) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_STATS("clear max/sum"); + max[writeBuffer][i].fill(0); + sum[writeBuffer][i].fill(0); + } + + // and now adjust the proxy vars so that the main thread can adjust the visuals. + if(autotuneInit && tunables.userAutoTuneEnabled) + { + updateAvatarParams(); + } + } + + // clear buffers when we change region or need a hard reset. + // static + void StatsRecorder::clearStatsBuffers() + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS; + using ST = StatType_t; + + auto& statsTypeMatrix = statsDoubleBuffer[writeBuffer]; + for(auto& statsMap : statsTypeMatrix) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_STATS("Clear stats maps"); + for(auto& stat_entry : statsMap) + { + std::fill_n(stat_entry.second.begin() ,static_cast(ST::STATS_COUNT),0); + } + statsMap.clear(); + } + for(int i=0; i< static_cast(ObjType_t::OT_COUNT); i++) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_STATS("clear max/sum"); + max[writeBuffer][i].fill(0); + sum[writeBuffer][i].fill(0); + } + // swap the clean buffers in + if(enabled()) + { + std::lock_guard lock{bufferToggleLock}; + writeBuffer ^= 1; + }; + // repeat before we start processing new stuff + for(auto& statsMap : statsTypeMatrix) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_STATS("Clear stats maps"); + for(auto& stat_entry : statsMap) + { + std::fill_n(stat_entry.second.begin() ,static_cast(ST::STATS_COUNT),0); + } + statsMap.clear(); + } + for(int i=0; i< static_cast(ObjType_t::OT_COUNT); i++) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_STATS("clear max/sum"); + max[writeBuffer][i].fill(0); + sum[writeBuffer][i].fill(0); + } + } + + // called once per main loop iteration on main thread + void updateClass() + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS; + + sTotalAvatarTime = LLVOAvatar::getTotalGPURenderTime(); + sAverageAvatarTime = LLVOAvatar::getAverageGPURenderTime(); + sMaxAvatarTime = LLVOAvatar::getMaxGPURenderTime(); + } + + //static + int StatsRecorder::countNearbyAvatars(S32 distance) + { + const auto our_pos = gAgentCamera.getCameraPositionGlobal(); + + std::vector positions; + uuid_vec_t avatar_ids; + LLWorld::getInstance()->getAvatars(&avatar_ids, &positions, our_pos, distance); + return positions.size(); + } + + const U32 NUM_PERIODS = 50; + void StatsRecorder::updateMeanFrameTime(U64 cur_frame_time_raw) + { + static std::deque frame_time_deque; + frame_time_deque.push_front(cur_frame_time_raw); + if (frame_time_deque.size() > NUM_PERIODS) + { + frame_time_deque.pop_back(); + } + + std::vector buf(frame_time_deque.begin(), frame_time_deque.end()); + std::sort(buf.begin(), buf.end()); + + LLPerfStats::meanFrameTime = (buf.size() % 2 == 0) ? (buf[buf.size() / 2 - 1] + buf[buf.size() / 2]) / 2 : buf[buf.size() / 2]; + } + U64 StatsRecorder::getMeanTotalFrameTime() + { + return LLPerfStats::meanFrameTime; + } + + // static + void StatsRecorder::updateAvatarParams() + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_STATS; + + if(tunables.autoTuneTimeout) + { + LLPerfStats::lastSleepedFrame = gFrameCount; + tunables.autoTuneTimeout = false; + return; + } + // sleep time is basically forced sleep when window out of focus + auto tot_sleep_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_SLEEP); + // similar to sleep time, induced by FPS limit + //auto tot_limit_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_FPSLIMIT); + + + // the time spent this frame on the "doFrame" call. Treated as "tot time for frame" + auto tot_frame_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_FRAME); + + if( tot_sleep_time_raw != 0 ) + { + // Note: we do not average sleep + // if at some point we need to, the averaging will need to take this into account or + // we forever think we're in the background due to residuals. + LL_DEBUGS() << "No tuning when not in focus" << LL_ENDL; + LLPerfStats::lastSleepedFrame = gFrameCount; + return; + } + + U32 target_fps = tunables.vsyncEnabled ? std::min(LLPerfStats::vsync_max_fps, tunables.userTargetFPS) : tunables.userTargetFPS; + + if(LLPerfStats::lastSleepedFrame != 0) + { + // wait a short time after viewer regains focus + if((gFrameCount - LLPerfStats::lastSleepedFrame) > target_fps * 5) + { + LLPerfStats::lastSleepedFrame = 0; + } + else + { + return; + } + } + updateMeanFrameTime(tot_frame_time_raw); + + if(tunables.userImpostorDistanceTuningEnabled) + { + // if we have less than the user's "max Non-Impostors" avatars within the desired range then adjust the limit. + // also adjusts back up again for nearby crowds. + auto count = countNearbyAvatars(std::min(LLPipeline::RenderFarClip, tunables.userImpostorDistance)); + if( count != tunables.nonImpostors ) + { + tunables.updateNonImposters( (count < LLVOAvatar::NON_IMPOSTORS_MAX_SLIDER)?count : 0 ); + LL_DEBUGS("AutoTune") << "There are " << count << "avatars within " << std::min(LLPipeline::RenderFarClip, tunables.userImpostorDistance) << "m of the camera" << LL_ENDL; + } + } + + auto av_render_max_raw = ms_to_raw(sMaxAvatarTime); + // Is our target frame time lower than current? If so we need to take action to reduce draw overheads. + // cumulative avatar time (includes idle processing, attachments and base av) + auto tot_avatar_time_raw = ms_to_raw(sTotalAvatarTime); + + // The frametime budget we have based on the target FPS selected + auto target_frame_time_raw = (U64)llround(LLPerfStats::cpu_hertz / (target_fps == 0 ? 1 : target_fps)); + // LL_INFOS() << "Effective FPS(raw):" << tot_frame_time_raw << " Target:" << target_frame_time_raw << LL_ENDL; + auto inferredFPS{1000/(U32)std::max(raw_to_ms(tot_frame_time_raw),1.0)}; + U32 settingsChangeFrequency{inferredFPS > 50?inferredFPS:50}; + /*if( tot_limit_time_raw != 0) + { + // This could be problematic. + tot_frame_time_raw -= tot_limit_time_raw; + }*/ + + F64 time_buf = target_frame_time_raw * 0.1; + + // 1) Is the target frame time lower than current? + if ((target_frame_time_raw + time_buf) <= tot_frame_time_raw) + { + if (target_frame_time_raw - time_buf >= getMeanTotalFrameTime()) + { + belowTargetFPS = false; + LLPerfStats::lastGlobalPrefChange = gFrameCount; + return; + } + + if (!belowTargetFPS) + { + // this is the first frame under. hold fire to add a little hysteresis + belowTargetFPS = true; + LLPerfStats::lastGlobalPrefChange = gFrameCount; + } + // if so we've got work to do + + // how much of the frame was spent on non avatar related work? + U64 non_avatar_time_raw = tot_frame_time_raw > tot_avatar_time_raw ? tot_frame_time_raw - tot_avatar_time_raw : 0; + + // If the target frame time < scene time (estimated as non_avatar time) + U64 target_avatar_time_raw; + if(target_frame_time_raw < non_avatar_time_raw) + { + // we cannnot do this by avatar adjustment alone. + if((gFrameCount - LLPerfStats::lastGlobalPrefChange) > settingsChangeFrequency) // give changes a short time to take effect. + { + if(tunables.userFPSTuningStrategy != TUNE_AVATARS_ONLY) + { + // 1 - hack the water to opaque. all non opaque have a significant hit, this is a big boost for (arguably) a minor visual hit. + // the other reflection options make comparatively little change and if this overshoots we'll be stepping back up later +# if 0 // TODO RenderReflectionDetail went away + if(LLPipeline::RenderReflectionDetail != -2) + { + LLPerfStats::tunables.updateReflectionDetail(-2); + LLPerfStats::lastGlobalPrefChange = gFrameCount; + return; + } + else // deliberately "else" here so we only do one of these in any given frame +#endif + { + // step down the DD by 10m per update + auto new_dd = (LLPipeline::RenderFarClip - DD_STEP > tunables.userMinDrawDistance)?(LLPipeline::RenderFarClip - DD_STEP) : tunables.userMinDrawDistance; + if(new_dd != LLPipeline::RenderFarClip) + { + LLPerfStats::tunables.updateFarClip( new_dd ); + LLPerfStats::lastGlobalPrefChange = gFrameCount; + return; + } + } + } + // if we reach here, we've no more changes to make to tune scenery so we'll resort to agressive Avatar tuning + // Note: moved from outside "if changefrequency elapsed" to stop fallthrough and allow scenery changes time to take effect. + target_avatar_time_raw = 0; + } + else + { + // we made a settings change recently so let's give it time. + return; + } + } + else + { + // set desired avatar budget. + target_avatar_time_raw = target_frame_time_raw - non_avatar_time_raw; + } + + if ((target_avatar_time_raw < tot_avatar_time_raw) && (tunables.userFPSTuningStrategy != TUNE_SCENE_ONLY)) + { + // we need to spend less time drawing avatars to meet our budget + auto new_render_limit_ns {LLPerfStats::raw_to_ns(av_render_max_raw)}; + // max render this frame may be higher than the last (cos new entrants and jitter) so make sure we are heading in the right direction + if( new_render_limit_ns > renderAvatarMaxART_ns ) + { + new_render_limit_ns = renderAvatarMaxART_ns; + } + + if (new_render_limit_ns > LLPerfStats::ART_MIN_ADJUST_DOWN_NANOS) + { + new_render_limit_ns -= LLPerfStats::ART_MIN_ADJUST_DOWN_NANOS; + } + + // bounce at the bottom to prevent "no limit" + new_render_limit_ns = std::max((U64)new_render_limit_ns, (U64)LLPerfStats::ART_MINIMUM_NANOS); + + // assign the new value + if (renderAvatarMaxART_ns != new_render_limit_ns) + { + renderAvatarMaxART_ns = new_render_limit_ns; + tunables.updateSettingsFromRenderCostLimit(); + } + // LL_DEBUGS() << "AUTO_TUNE: avatar_budget adjusted to:" << new_render_limit_ns << LL_ENDL; + } + // LL_DEBUGS() << "AUTO_TUNE: Target frame time:"<< LLPerfStats::raw_to_us(target_frame_time_raw) << "usecs (non_avatar is " << LLPerfStats::raw_to_us(non_avatar_time_raw) << "usecs) Max cost limited=" << renderAvatarMaxART_ns << LL_ENDL; + } + else if ((LLPerfStats::raw_to_ns(target_frame_time_raw) > (LLPerfStats::raw_to_ns(tot_frame_time_raw) + renderAvatarMaxART_ns)) || + (tunables.vsyncEnabled && (target_fps == LLPerfStats::vsync_max_fps) && (target_frame_time_raw > getMeanTotalFrameTime()))) + { + if (belowTargetFPS) + { + // we reached target, force a pause + lastGlobalPrefChange = gFrameCount; + belowTargetFPS = false; + } + + // once we're over the FPS target we slow down further + if ((gFrameCount - lastGlobalPrefChange) > settingsChangeFrequency * 3) + { + if (!tunables.userAutoTuneLock) + { + // we've reached the target and stayed long enough to consider stable. + // turn off if we are not locked. + tunables.updateUserAutoTuneEnabled(false); + } + if (renderAvatarMaxART_ns > 0 && + LLPerfStats::tunedAvatars > 0 && + tunables.userFPSTuningStrategy != TUNE_SCENE_ONLY) + { + // if we have more time to spare let's shift up little in the hope we'll restore an avatar. + U64 up_step = LLPerfStats::tunedAvatars > 2 ? LLPerfStats::ART_MIN_ADJUST_UP_NANOS : LLPerfStats::ART_MIN_ADJUST_UP_NANOS * 2; + renderAvatarMaxART_ns += up_step; + tunables.updateSettingsFromRenderCostLimit(); + return; + } + if (tunables.userFPSTuningStrategy != TUNE_AVATARS_ONLY) + { + if (LLPipeline::RenderFarClip < tunables.userTargetDrawDistance) + { + LLPerfStats::tunables.updateFarClip( std::min(LLPipeline::RenderFarClip + DD_STEP, tunables.userTargetDrawDistance) ); + LLPerfStats::lastGlobalPrefChange = gFrameCount; + return; + } + if ((tot_frame_time_raw * 1.5) < target_frame_time_raw) + { + // if everything else is "max" and we have >50% headroom let's knock the water quality up a notch at a time. +# if 0 // RenderReflectionDetail went away + LLPerfStats::tunables.updateReflectionDetail( std::min(LLPipeline::RenderReflectionDetail + 1, tunables.userTargetReflections) ); +#endif + } + } + } + } + } +} diff --git a/indra/newview/llphysicsmotion.cpp b/indra/newview/llphysicsmotion.cpp index 69cc682e80..b6bcd6dd7d 100644 --- a/indra/newview/llphysicsmotion.cpp +++ b/indra/newview/llphysicsmotion.cpp @@ -1,776 +1,776 @@ -/** - * @file llphysicsmotion.cpp - * @brief Implementation of LLPhysicsMotion class. - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -//----------------------------------------------------------------------------- -// Header Files -//----------------------------------------------------------------------------- -#include "llviewerprecompiledheaders.h" -#include "linden_common.h" - -#include "m3math.h" -#include "v3dmath.h" - -#include "llphysicsmotion.h" -#include "llagent.h" -#include "llcharacter.h" -#include "llviewercontrol.h" -#include "llviewervisualparam.h" -#include "llvoavatarself.h" - -typedef std::map controller_map_t; -typedef std::map default_controller_map_t; - -#define MIN_REQUIRED_PIXEL_AREA_AVATAR_PHYSICS_MOTION 0.f -// we use TIME_ITERATION_STEP_MAX in division operation, make sure this is a simple -// value and devision result won't end with repeated/recurring tail like 1.333(3) -#define TIME_ITERATION_STEP_MAX 0.05f // minimal step size will end up as 0.025 - -inline F64 llsgn(const F64 a) -{ - if (a >= 0) - return 1; - return -1; -} - -/* - At a high level, this works by setting temporary parameters that are not stored - in the avatar's list of params, and are not conveyed to other users. We accomplish - this by creating some new temporary driven params inside avatar_lad that are then driven - by the actual params that the user sees and sets. For example, in the old system, - the user sets a param called breast bouyancy, which controls the Z value of the breasts. - In our new system, the user still sets the breast bouyancy, but that param is redefined - as a driver param so that affects a new temporary driven param that the bounce is applied - to. -*/ - -class LLPhysicsMotion -{ -public: - typedef enum - { - SMOOTHING = 0, - MASS, - GRAVITY, - SPRING, - GAIN, - DAMPING, - DRAG, - MAX_EFFECT, - NUM_PARAMS - } eParamName; - - /* - param_driver_name: The param that controls the params that are being affected by the physics. - joint_name: The joint that the body part is attached to. The joint is - used to determine the orientation (rotation) of the body part. - - character: The avatar that this physics affects. - - motion_direction_vec: The direction (in world coordinates) that determines the - motion. For example, (0,0,1) is up-down, and means that up-down motion is what - determines how this joint moves. - - controllers: The various settings (e.g. spring force, mass) that determine how - the body part behaves. - */ - LLPhysicsMotion(const std::string ¶m_driver_name, - const std::string &joint_name, - LLCharacter *character, - const LLVector3 &motion_direction_vec, - const controller_map_t &controllers) : - mParamDriverName(param_driver_name), - mJointName(joint_name), - mMotionDirectionVec(motion_direction_vec), - mParamDriver(NULL), - mParamControllers(controllers), - mCharacter(character), - mLastTime(0), - mPosition_local(0), - mVelocityJoint_local(0), - mPositionLastUpdate_local(0) - { - mJointState = new LLJointState; - - for (U32 i = 0; i < NUM_PARAMS; ++i) - { - mParamCache[i] = NULL; - } - } - - bool initialize(); - - ~LLPhysicsMotion() {} - - bool onUpdate(F32 time); - - LLPointer getJointState() - { - return mJointState; - } -protected: - - F32 getParamValue(eParamName param) - { - static std::string controller_key[] = - { - "Smoothing", - "Mass", - "Gravity", - "Spring", - "Gain", - "Damping", - "Drag", - "MaxEffect" - }; - - if (!mParamCache[param]) - { - const controller_map_t::const_iterator& entry = mParamControllers.find(controller_key[param]); - if (entry == mParamControllers.end()) - { - return sDefaultController[controller_key[param]]; - } - const std::string& param_name = (*entry).second.c_str(); - mParamCache[param] = mCharacter->getVisualParam(param_name.c_str()); - } - - if (mParamCache[param]) - { - return mParamCache[param]->getWeight(); - } - else - { - return sDefaultController[controller_key[param]]; - } - } - - - void setParamValue(const LLViewerVisualParam *param, - const F32 new_value_local, - F32 behavior_maxeffect); - - F32 toLocal(const LLVector3 &world); - F32 calculateVelocity_local(const F32 time_delta); - F32 calculateAcceleration_local(F32 velocity_local, const F32 time_delta); -private: - const std::string mParamDriverName; - const std::string mParamControllerName; - const LLVector3 mMotionDirectionVec; - const std::string mJointName; - - F32 mPosition_local; - F32 mVelocityJoint_local; // How fast the joint is moving - F32 mAccelerationJoint_local; // Acceleration on the joint - - F32 mVelocity_local; // How fast the param is moving - F32 mPositionLastUpdate_local; - LLVector3 mPosition_world; - - LLViewerVisualParam *mParamDriver; - const controller_map_t mParamControllers; - - LLPointer mJointState; - LLCharacter *mCharacter; - - F32 mLastTime; - - LLVisualParam* mParamCache[NUM_PARAMS]; - - static default_controller_map_t sDefaultController; -}; - -default_controller_map_t initDefaultController() -{ - default_controller_map_t controller; - controller["Mass"] = 0.2f; - controller["Gravity"] = 0.0f; - controller["Damping"] = .05f; - controller["Drag"] = 0.15f; - controller["MaxEffect"] = 0.1f; - controller["Spring"] = 0.1f; - controller["Gain"] = 10.0f; - return controller; -} - -default_controller_map_t LLPhysicsMotion::sDefaultController = initDefaultController(); - -bool LLPhysicsMotion::initialize() -{ - if (!mJointState->setJoint(mCharacter->getJoint(mJointName.c_str()))) - return false; - mJointState->setUsage(LLJointState::ROT); - - mParamDriver = (LLViewerVisualParam*)mCharacter->getVisualParam(mParamDriverName.c_str()); - if (mParamDriver == NULL) - { - LL_INFOS() << "Failure reading in [ " << mParamDriverName << " ]" << LL_ENDL; - return false; - } - - return true; -} - -LLPhysicsMotionController::LLPhysicsMotionController(const LLUUID &id) : - LLMotion(id), - mCharacter(NULL) -{ - mName = "breast_motion"; -} - -LLPhysicsMotionController::~LLPhysicsMotionController() -{ - for (motion_vec_t::iterator iter = mMotions.begin(); - iter != mMotions.end(); - ++iter) - { - delete (*iter); - } -} - -bool LLPhysicsMotionController::onActivate() -{ - return true; -} - -void LLPhysicsMotionController::onDeactivate() -{ -} - -LLMotion::LLMotionInitStatus LLPhysicsMotionController::onInitialize(LLCharacter *character) -{ - mCharacter = character; - - mMotions.clear(); - - // Breast Cleavage - { - controller_map_t controller; - controller["Mass"] = "Breast_Physics_Mass"; - controller["Gravity"] = "Breast_Physics_Gravity"; - controller["Drag"] = "Breast_Physics_Drag"; - controller["Damping"] = "Breast_Physics_InOut_Damping"; - controller["MaxEffect"] = "Breast_Physics_InOut_Max_Effect"; - controller["Spring"] = "Breast_Physics_InOut_Spring"; - controller["Gain"] = "Breast_Physics_InOut_Gain"; - LLPhysicsMotion *motion = new LLPhysicsMotion("Breast_Physics_InOut_Controller", - "mChest", - character, - LLVector3(-1,0,0), - controller); - if (!motion->initialize()) - { - llassert_always(false); - return STATUS_FAILURE; - } - addMotion(motion); - } - - // Breast Bounce - { - controller_map_t controller; - controller["Mass"] = "Breast_Physics_Mass"; - controller["Gravity"] = "Breast_Physics_Gravity"; - controller["Drag"] = "Breast_Physics_Drag"; - controller["Damping"] = "Breast_Physics_UpDown_Damping"; - controller["MaxEffect"] = "Breast_Physics_UpDown_Max_Effect"; - controller["Spring"] = "Breast_Physics_UpDown_Spring"; - controller["Gain"] = "Breast_Physics_UpDown_Gain"; - LLPhysicsMotion *motion = new LLPhysicsMotion("Breast_Physics_UpDown_Controller", - "mChest", - character, - LLVector3(0,0,1), - controller); - if (!motion->initialize()) - { - llassert_always(false); - return STATUS_FAILURE; - } - addMotion(motion); - } - - // Breast Sway - { - controller_map_t controller; - controller["Mass"] = "Breast_Physics_Mass"; - controller["Gravity"] = "Breast_Physics_Gravity"; - controller["Drag"] = "Breast_Physics_Drag"; - controller["Damping"] = "Breast_Physics_LeftRight_Damping"; - controller["MaxEffect"] = "Breast_Physics_LeftRight_Max_Effect"; - controller["Spring"] = "Breast_Physics_LeftRight_Spring"; - controller["Gain"] = "Breast_Physics_LeftRight_Gain"; - LLPhysicsMotion *motion = new LLPhysicsMotion("Breast_Physics_LeftRight_Controller", - "mChest", - character, - LLVector3(0,-1,0), - controller); - if (!motion->initialize()) - { - llassert_always(false); - return STATUS_FAILURE; - } - addMotion(motion); - } - // Butt Bounce - { - controller_map_t controller; - controller["Mass"] = "Butt_Physics_Mass"; - controller["Gravity"] = "Butt_Physics_Gravity"; - controller["Drag"] = "Butt_Physics_Drag"; - controller["Damping"] = "Butt_Physics_UpDown_Damping"; - controller["MaxEffect"] = "Butt_Physics_UpDown_Max_Effect"; - controller["Spring"] = "Butt_Physics_UpDown_Spring"; - controller["Gain"] = "Butt_Physics_UpDown_Gain"; - LLPhysicsMotion *motion = new LLPhysicsMotion("Butt_Physics_UpDown_Controller", - "mPelvis", - character, - LLVector3(0,0,-1), - controller); - if (!motion->initialize()) - { - llassert_always(false); - return STATUS_FAILURE; - } - addMotion(motion); - } - - // Butt LeftRight - { - controller_map_t controller; - controller["Mass"] = "Butt_Physics_Mass"; - controller["Gravity"] = "Butt_Physics_Gravity"; - controller["Drag"] = "Butt_Physics_Drag"; - controller["Damping"] = "Butt_Physics_LeftRight_Damping"; - controller["MaxEffect"] = "Butt_Physics_LeftRight_Max_Effect"; - controller["Spring"] = "Butt_Physics_LeftRight_Spring"; - controller["Gain"] = "Butt_Physics_LeftRight_Gain"; - LLPhysicsMotion *motion = new LLPhysicsMotion("Butt_Physics_LeftRight_Controller", - "mPelvis", - character, - LLVector3(0,-1,0), - controller); - if (!motion->initialize()) - { - llassert_always(false); - return STATUS_FAILURE; - } - addMotion(motion); - } - - // Belly Bounce - { - controller_map_t controller; - controller["Mass"] = "Belly_Physics_Mass"; - controller["Gravity"] = "Belly_Physics_Gravity"; - controller["Drag"] = "Belly_Physics_Drag"; - controller["Damping"] = "Belly_Physics_UpDown_Damping"; - controller["MaxEffect"] = "Belly_Physics_UpDown_Max_Effect"; - controller["Spring"] = "Belly_Physics_UpDown_Spring"; - controller["Gain"] = "Belly_Physics_UpDown_Gain"; - LLPhysicsMotion *motion = new LLPhysicsMotion("Belly_Physics_UpDown_Controller", - "mPelvis", - character, - LLVector3(0,0,-1), - controller); - if (!motion->initialize()) - { - llassert_always(false); - return STATUS_FAILURE; - } - addMotion(motion); - } - - return STATUS_SUCCESS; -} - -void LLPhysicsMotionController::addMotion(LLPhysicsMotion *motion) -{ - addJointState(motion->getJointState()); - mMotions.push_back(motion); -} - -F32 LLPhysicsMotionController::getMinPixelArea() -{ - return MIN_REQUIRED_PIXEL_AREA_AVATAR_PHYSICS_MOTION; -} - -// Local space means "parameter space". -F32 LLPhysicsMotion::toLocal(const LLVector3 &world) -{ - LLJoint *joint = mJointState->getJoint(); - const LLQuaternion rotation_world = joint->getWorldRotation(); - - LLVector3 dir_world = mMotionDirectionVec * rotation_world; - dir_world.normalize(); - return world * dir_world; -} - -F32 LLPhysicsMotion::calculateVelocity_local(const F32 time_delta) -{ - const F32 world_to_model_scale = 100.0f; - LLJoint *joint = mJointState->getJoint(); - const LLVector3 position_world = joint->getWorldPosition(); - const LLVector3 last_position_world = mPosition_world; - const LLVector3 positionchange_world = (position_world-last_position_world) * world_to_model_scale; - const F32 velocity_local = toLocal(positionchange_world) / time_delta; - return velocity_local; -} - -F32 LLPhysicsMotion::calculateAcceleration_local(const F32 velocity_local, const F32 time_delta) -{ -// const F32 smoothing = getParamValue("Smoothing"); - static const F32 smoothing = 3.0f; // Removed smoothing param since it's probably not necessary - const F32 acceleration_local = (velocity_local - mVelocityJoint_local) / time_delta; - - const F32 smoothed_acceleration_local = - acceleration_local * 1.0/smoothing + - mAccelerationJoint_local * (smoothing-1.0)/smoothing; - - return smoothed_acceleration_local; -} - -bool LLPhysicsMotionController::onUpdate(F32 time, U8* joint_mask) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - // Skip if disabled globally. - if (!gSavedSettings.getBOOL("AvatarPhysics")) - { - return true; - } - - bool update_visuals = false; - for (motion_vec_t::iterator iter = mMotions.begin(); - iter != mMotions.end(); - ++iter) - { - LLPhysicsMotion *motion = (*iter); - update_visuals |= motion->onUpdate(time); - } - - if (update_visuals) - mCharacter->updateVisualParams(); - - return true; -} - -// Return true if character has to update visual params. -bool LLPhysicsMotion::onUpdate(F32 time) -{ - // static FILE *mFileWrite = fopen("c:\\temp\\avatar_data.txt","w"); - - if (!mParamDriver) - return false; - - if (!mLastTime || mLastTime >= time) - { - mLastTime = time; - return false; - } - - //////////////////////////////////////////////////////////////////////////////// - // Get all parameters and settings - // - - const F32 time_delta = time - mLastTime; - - // If less than 1FPS, we don't want to be spending time updating physics at all. - if (time_delta > 1.0) - { - mLastTime = time; - return false; - } - - // Higher LOD is better. This controls the granularity - // and frequency of updates for the motions. - const F32 lod_factor = LLVOAvatar::sPhysicsLODFactor; - if (lod_factor == 0) - { - return true; - } - - LLJoint *joint = mJointState->getJoint(); - - const F32 behavior_mass = getParamValue(MASS); - const F32 behavior_gravity = getParamValue(GRAVITY); - const F32 behavior_spring = getParamValue(SPRING); - const F32 behavior_gain = getParamValue(GAIN); - const F32 behavior_damping = getParamValue(DAMPING); - const F32 behavior_drag = getParamValue(DRAG); - F32 behavior_maxeffect = getParamValue(MAX_EFFECT); - - const bool physics_test = false; // Enable this to simulate bouncing on all parts. - - if (physics_test) - behavior_maxeffect = 1.0f; - - // Normalize the param position to be from [0,1]. - // We have to use normalized values because there may be more than one driven param, - // and each of these driven params may have its own range. - // This means we'll do all our calculations in normalized [0,1] local coordinates. - const F32 position_user_local = (mParamDriver->getWeight() - mParamDriver->getMinWeight()) / (mParamDriver->getMaxWeight() - mParamDriver->getMinWeight()); - - // - // End parameters and settings - //////////////////////////////////////////////////////////////////////////////// - - - //////////////////////////////////////////////////////////////////////////////// - // Calculate velocity and acceleration in parameter space. - // - - const F32 joint_local_factor = 30.0; - const F32 velocity_joint_local = calculateVelocity_local(time_delta * joint_local_factor); - const F32 acceleration_joint_local = calculateAcceleration_local(velocity_joint_local, time_delta * joint_local_factor); - - // - // End velocity and acceleration - //////////////////////////////////////////////////////////////////////////////// - - bool update_visuals = false; - - // Break up the physics into a bunch of iterations so that differing framerates will show - // roughly the same behavior. - // Explanation/example: Lets assume we have a bouncing object. Said abjects bounces at a - // trajectory that has points A>B>C. Object bounces from A to B with specific speed. - // It needs time T to move from A to B. - // As long as our frame's time significantly smaller then T our motion will be split into - // multiple parts. with each part speed will decrease. Object will reach B position (roughly) - // and bounce/fall back to A. - // But if frame's time (F_T) is larger then T, object will move with same speed for whole F_T - // and will jump over point B up to C ending up with increased amplitude. To avoid that we - // split F_T into smaller portions so that when frame's time is too long object can virtually - // bounce at right (relatively) position. - // Note: this doesn't look to be optimal, since it provides only "roughly same" behavior, but - // irregularity at higher fps looks to be insignificant so it works good enough for low fps. - U32 steps = (U32)(time_delta / TIME_ITERATION_STEP_MAX) + 1; - F32 time_iteration_step = time_delta / (F32)steps; //minimal step size ends up as 0.025 - for (U32 i = 0; i < steps; i++) - { - // mPositon_local should be in normalized 0,1 range already. Just making sure... - const F32 position_current_local = llclamp(mPosition_local, - 0.0f, - 1.0f); - // If the effect is turned off then don't process unless we need one more update - // to set the position to the default (i.e. user) position. - if ((behavior_maxeffect == 0) && (position_current_local == position_user_local)) - { - return update_visuals; - } - - //////////////////////////////////////////////////////////////////////////////// - // Calculate the total force - // - - // Spring force is a restoring force towards the original user-set breast position. - // F = kx - const F32 spring_length = position_current_local - position_user_local; - const F32 force_spring = -spring_length * behavior_spring; - - // Acceleration is the force that comes from the change in velocity of the torso. - // F = ma - const F32 force_accel = behavior_gain * (acceleration_joint_local * behavior_mass); - - // Gravity always points downward in world space. - // F = mg - const LLVector3 gravity_world(0,0,1); - const F32 force_gravity = (toLocal(gravity_world) * behavior_gravity * behavior_mass); - - // Damping is a restoring force that opposes the current velocity. - // F = -kv - const F32 force_damping = -behavior_damping * mVelocity_local; - - // Drag is a force imparted by velocity (intuitively it is similar to wind resistance) - // F = .5kv^2 - const F32 force_drag = .5*behavior_drag*velocity_joint_local*velocity_joint_local*llsgn(velocity_joint_local); - - const F32 force_net = (force_accel + - force_gravity + - force_spring + - force_damping + - force_drag); - - // - // End total force - //////////////////////////////////////////////////////////////////////////////// - - - //////////////////////////////////////////////////////////////////////////////// - // Calculate new params - // - - // Calculate the new acceleration based on the net force. - // a = F/m - const F32 acceleration_new_local = force_net / behavior_mass; - static const F32 max_velocity = 100.0f; // magic number, used to be customizable. - F32 velocity_new_local = mVelocity_local + acceleration_new_local*time_iteration_step; - velocity_new_local = llclamp(velocity_new_local, - -max_velocity, max_velocity); - - // Temporary debugging setting to cause all avatars to move, for profiling purposes. - if (physics_test) - { - velocity_new_local = sin(time*4.0); - } - // Calculate the new parameters, or remain unchanged if max speed is 0. - F32 position_new_local = position_current_local + velocity_new_local*time_iteration_step; - if (behavior_maxeffect == 0) - position_new_local = position_user_local; - - // Zero out the velocity if the param is being pushed beyond its limits. - if ((position_new_local < 0 && velocity_new_local < 0) || - (position_new_local > 1 && velocity_new_local > 0)) - { - velocity_new_local = 0; - } - - // Check for NaN values. A NaN value is detected if the variables doesn't equal itself. - // If NaN, then reset everything. - if ((mPosition_local != mPosition_local) || - (mVelocity_local != mVelocity_local) || - (position_new_local != position_new_local)) - { - position_new_local = 0; - mVelocity_local = 0; - mVelocityJoint_local = 0; - mAccelerationJoint_local = 0; - mPosition_local = 0; - mPosition_world = LLVector3(0,0,0); - } - - const F32 position_new_local_clamped = llclamp(position_new_local, - 0.0f, - 1.0f); - - LLDriverParam *driver_param = dynamic_cast(mParamDriver); - llassert_always(driver_param); - if (driver_param) - { - // If this is one of our "hidden" driver params, then make sure it's - // the default value. - if ((driver_param->getGroup() != VISUAL_PARAM_GROUP_TWEAKABLE) && - (driver_param->getGroup() != VISUAL_PARAM_GROUP_TWEAKABLE_NO_TRANSMIT)) - { - mCharacter->setVisualParamWeight(driver_param, 0); - } - S32 num_driven = driver_param->getDrivenParamsCount(); - for (S32 i = 0; i < num_driven; ++i) - { - const LLViewerVisualParam *driven_param = driver_param->getDrivenParam(i); - setParamValue(driven_param,position_new_local_clamped, behavior_maxeffect); - } - } - - // - // End calculate new params - //////////////////////////////////////////////////////////////////////////////// - - //////////////////////////////////////////////////////////////////////////////// - // Conditionally update the visual params - // - - // Updating the visual params (i.e. what the user sees) is fairly expensive. - // So only update if the params have changed enough, and also take into account - // the graphics LOD settings. - - // For non-self, if the avatar is small enough visually, then don't update. - const F32 area_for_max_settings = 0.0; - const F32 area_for_min_settings = 1400.0; - const F32 area_for_this_setting = area_for_max_settings + (area_for_min_settings-area_for_max_settings)*(1.0-lod_factor); - const F32 pixel_area = sqrtf(mCharacter->getPixelArea()); - - const bool is_self = (dynamic_cast(mCharacter) != NULL); - if ((pixel_area > area_for_this_setting) || is_self) - { - const F32 position_diff_local = llabs(mPositionLastUpdate_local-position_new_local_clamped); - const F32 min_delta = (1.0001f-lod_factor)*0.4f; - if (llabs(position_diff_local) > min_delta) - { - update_visuals = true; - mPositionLastUpdate_local = position_new_local; - } - } - - // - // End update visual params - //////////////////////////////////////////////////////////////////////////////// - - mVelocity_local = velocity_new_local; - mAccelerationJoint_local = acceleration_joint_local; - mPosition_local = position_new_local; - } - mLastTime = time; - mPosition_world = joint->getWorldPosition(); - mVelocityJoint_local = velocity_joint_local; - - - /* - // Write out debugging info into a spreadsheet. - if (mFileWrite != NULL && is_self) - { - fprintf(mFileWrite,"%f\t%f\t%f \t\t%f \t\t%f\t%f\t%f\t \t\t%f\t%f\t%f\t%f\t%f \t\t%f\t%f\t%f\n", - position_new_local, - velocity_new_local, - acceleration_new_local, - - time_delta, - - mPosition_world[0], - mPosition_world[1], - mPosition_world[2], - - force_net, - force_spring, - force_accel, - force_damping, - force_drag, - - spring_length, - velocity_joint_local, - acceleration_joint_local - ); - } - */ - - return update_visuals; -} - -// Range of new_value_local is assumed to be [0 , 1] normalized. -void LLPhysicsMotion::setParamValue(const LLViewerVisualParam *param, - F32 new_value_normalized, - F32 behavior_maxeffect) -{ - const F32 value_min_local = param->getMinWeight(); - const F32 value_max_local = param->getMaxWeight(); - const F32 min_val = 0.5f-behavior_maxeffect/2.0; - const F32 max_val = 0.5f+behavior_maxeffect/2.0; - - // Scale from [0,1] to [min_val,max_val] - const F32 new_value_rescaled = min_val + (max_val-min_val) * new_value_normalized; - - // Scale from [0,1] to [value_min_local,value_max_local] - const F32 new_value_local = value_min_local + (value_max_local-value_min_local) * new_value_rescaled; - - mCharacter->setVisualParamWeight(param, new_value_local); -} +/** + * @file llphysicsmotion.cpp + * @brief Implementation of LLPhysicsMotion class. + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "llviewerprecompiledheaders.h" +#include "linden_common.h" + +#include "m3math.h" +#include "v3dmath.h" + +#include "llphysicsmotion.h" +#include "llagent.h" +#include "llcharacter.h" +#include "llviewercontrol.h" +#include "llviewervisualparam.h" +#include "llvoavatarself.h" + +typedef std::map controller_map_t; +typedef std::map default_controller_map_t; + +#define MIN_REQUIRED_PIXEL_AREA_AVATAR_PHYSICS_MOTION 0.f +// we use TIME_ITERATION_STEP_MAX in division operation, make sure this is a simple +// value and devision result won't end with repeated/recurring tail like 1.333(3) +#define TIME_ITERATION_STEP_MAX 0.05f // minimal step size will end up as 0.025 + +inline F64 llsgn(const F64 a) +{ + if (a >= 0) + return 1; + return -1; +} + +/* + At a high level, this works by setting temporary parameters that are not stored + in the avatar's list of params, and are not conveyed to other users. We accomplish + this by creating some new temporary driven params inside avatar_lad that are then driven + by the actual params that the user sees and sets. For example, in the old system, + the user sets a param called breast bouyancy, which controls the Z value of the breasts. + In our new system, the user still sets the breast bouyancy, but that param is redefined + as a driver param so that affects a new temporary driven param that the bounce is applied + to. +*/ + +class LLPhysicsMotion +{ +public: + typedef enum + { + SMOOTHING = 0, + MASS, + GRAVITY, + SPRING, + GAIN, + DAMPING, + DRAG, + MAX_EFFECT, + NUM_PARAMS + } eParamName; + + /* + param_driver_name: The param that controls the params that are being affected by the physics. + joint_name: The joint that the body part is attached to. The joint is + used to determine the orientation (rotation) of the body part. + + character: The avatar that this physics affects. + + motion_direction_vec: The direction (in world coordinates) that determines the + motion. For example, (0,0,1) is up-down, and means that up-down motion is what + determines how this joint moves. + + controllers: The various settings (e.g. spring force, mass) that determine how + the body part behaves. + */ + LLPhysicsMotion(const std::string ¶m_driver_name, + const std::string &joint_name, + LLCharacter *character, + const LLVector3 &motion_direction_vec, + const controller_map_t &controllers) : + mParamDriverName(param_driver_name), + mJointName(joint_name), + mMotionDirectionVec(motion_direction_vec), + mParamDriver(NULL), + mParamControllers(controllers), + mCharacter(character), + mLastTime(0), + mPosition_local(0), + mVelocityJoint_local(0), + mPositionLastUpdate_local(0) + { + mJointState = new LLJointState; + + for (U32 i = 0; i < NUM_PARAMS; ++i) + { + mParamCache[i] = NULL; + } + } + + bool initialize(); + + ~LLPhysicsMotion() {} + + bool onUpdate(F32 time); + + LLPointer getJointState() + { + return mJointState; + } +protected: + + F32 getParamValue(eParamName param) + { + static std::string controller_key[] = + { + "Smoothing", + "Mass", + "Gravity", + "Spring", + "Gain", + "Damping", + "Drag", + "MaxEffect" + }; + + if (!mParamCache[param]) + { + const controller_map_t::const_iterator& entry = mParamControllers.find(controller_key[param]); + if (entry == mParamControllers.end()) + { + return sDefaultController[controller_key[param]]; + } + const std::string& param_name = (*entry).second.c_str(); + mParamCache[param] = mCharacter->getVisualParam(param_name.c_str()); + } + + if (mParamCache[param]) + { + return mParamCache[param]->getWeight(); + } + else + { + return sDefaultController[controller_key[param]]; + } + } + + + void setParamValue(const LLViewerVisualParam *param, + const F32 new_value_local, + F32 behavior_maxeffect); + + F32 toLocal(const LLVector3 &world); + F32 calculateVelocity_local(const F32 time_delta); + F32 calculateAcceleration_local(F32 velocity_local, const F32 time_delta); +private: + const std::string mParamDriverName; + const std::string mParamControllerName; + const LLVector3 mMotionDirectionVec; + const std::string mJointName; + + F32 mPosition_local; + F32 mVelocityJoint_local; // How fast the joint is moving + F32 mAccelerationJoint_local; // Acceleration on the joint + + F32 mVelocity_local; // How fast the param is moving + F32 mPositionLastUpdate_local; + LLVector3 mPosition_world; + + LLViewerVisualParam *mParamDriver; + const controller_map_t mParamControllers; + + LLPointer mJointState; + LLCharacter *mCharacter; + + F32 mLastTime; + + LLVisualParam* mParamCache[NUM_PARAMS]; + + static default_controller_map_t sDefaultController; +}; + +default_controller_map_t initDefaultController() +{ + default_controller_map_t controller; + controller["Mass"] = 0.2f; + controller["Gravity"] = 0.0f; + controller["Damping"] = .05f; + controller["Drag"] = 0.15f; + controller["MaxEffect"] = 0.1f; + controller["Spring"] = 0.1f; + controller["Gain"] = 10.0f; + return controller; +} + +default_controller_map_t LLPhysicsMotion::sDefaultController = initDefaultController(); + +bool LLPhysicsMotion::initialize() +{ + if (!mJointState->setJoint(mCharacter->getJoint(mJointName.c_str()))) + return false; + mJointState->setUsage(LLJointState::ROT); + + mParamDriver = (LLViewerVisualParam*)mCharacter->getVisualParam(mParamDriverName.c_str()); + if (mParamDriver == NULL) + { + LL_INFOS() << "Failure reading in [ " << mParamDriverName << " ]" << LL_ENDL; + return false; + } + + return true; +} + +LLPhysicsMotionController::LLPhysicsMotionController(const LLUUID &id) : + LLMotion(id), + mCharacter(NULL) +{ + mName = "breast_motion"; +} + +LLPhysicsMotionController::~LLPhysicsMotionController() +{ + for (motion_vec_t::iterator iter = mMotions.begin(); + iter != mMotions.end(); + ++iter) + { + delete (*iter); + } +} + +bool LLPhysicsMotionController::onActivate() +{ + return true; +} + +void LLPhysicsMotionController::onDeactivate() +{ +} + +LLMotion::LLMotionInitStatus LLPhysicsMotionController::onInitialize(LLCharacter *character) +{ + mCharacter = character; + + mMotions.clear(); + + // Breast Cleavage + { + controller_map_t controller; + controller["Mass"] = "Breast_Physics_Mass"; + controller["Gravity"] = "Breast_Physics_Gravity"; + controller["Drag"] = "Breast_Physics_Drag"; + controller["Damping"] = "Breast_Physics_InOut_Damping"; + controller["MaxEffect"] = "Breast_Physics_InOut_Max_Effect"; + controller["Spring"] = "Breast_Physics_InOut_Spring"; + controller["Gain"] = "Breast_Physics_InOut_Gain"; + LLPhysicsMotion *motion = new LLPhysicsMotion("Breast_Physics_InOut_Controller", + "mChest", + character, + LLVector3(-1,0,0), + controller); + if (!motion->initialize()) + { + llassert_always(false); + return STATUS_FAILURE; + } + addMotion(motion); + } + + // Breast Bounce + { + controller_map_t controller; + controller["Mass"] = "Breast_Physics_Mass"; + controller["Gravity"] = "Breast_Physics_Gravity"; + controller["Drag"] = "Breast_Physics_Drag"; + controller["Damping"] = "Breast_Physics_UpDown_Damping"; + controller["MaxEffect"] = "Breast_Physics_UpDown_Max_Effect"; + controller["Spring"] = "Breast_Physics_UpDown_Spring"; + controller["Gain"] = "Breast_Physics_UpDown_Gain"; + LLPhysicsMotion *motion = new LLPhysicsMotion("Breast_Physics_UpDown_Controller", + "mChest", + character, + LLVector3(0,0,1), + controller); + if (!motion->initialize()) + { + llassert_always(false); + return STATUS_FAILURE; + } + addMotion(motion); + } + + // Breast Sway + { + controller_map_t controller; + controller["Mass"] = "Breast_Physics_Mass"; + controller["Gravity"] = "Breast_Physics_Gravity"; + controller["Drag"] = "Breast_Physics_Drag"; + controller["Damping"] = "Breast_Physics_LeftRight_Damping"; + controller["MaxEffect"] = "Breast_Physics_LeftRight_Max_Effect"; + controller["Spring"] = "Breast_Physics_LeftRight_Spring"; + controller["Gain"] = "Breast_Physics_LeftRight_Gain"; + LLPhysicsMotion *motion = new LLPhysicsMotion("Breast_Physics_LeftRight_Controller", + "mChest", + character, + LLVector3(0,-1,0), + controller); + if (!motion->initialize()) + { + llassert_always(false); + return STATUS_FAILURE; + } + addMotion(motion); + } + // Butt Bounce + { + controller_map_t controller; + controller["Mass"] = "Butt_Physics_Mass"; + controller["Gravity"] = "Butt_Physics_Gravity"; + controller["Drag"] = "Butt_Physics_Drag"; + controller["Damping"] = "Butt_Physics_UpDown_Damping"; + controller["MaxEffect"] = "Butt_Physics_UpDown_Max_Effect"; + controller["Spring"] = "Butt_Physics_UpDown_Spring"; + controller["Gain"] = "Butt_Physics_UpDown_Gain"; + LLPhysicsMotion *motion = new LLPhysicsMotion("Butt_Physics_UpDown_Controller", + "mPelvis", + character, + LLVector3(0,0,-1), + controller); + if (!motion->initialize()) + { + llassert_always(false); + return STATUS_FAILURE; + } + addMotion(motion); + } + + // Butt LeftRight + { + controller_map_t controller; + controller["Mass"] = "Butt_Physics_Mass"; + controller["Gravity"] = "Butt_Physics_Gravity"; + controller["Drag"] = "Butt_Physics_Drag"; + controller["Damping"] = "Butt_Physics_LeftRight_Damping"; + controller["MaxEffect"] = "Butt_Physics_LeftRight_Max_Effect"; + controller["Spring"] = "Butt_Physics_LeftRight_Spring"; + controller["Gain"] = "Butt_Physics_LeftRight_Gain"; + LLPhysicsMotion *motion = new LLPhysicsMotion("Butt_Physics_LeftRight_Controller", + "mPelvis", + character, + LLVector3(0,-1,0), + controller); + if (!motion->initialize()) + { + llassert_always(false); + return STATUS_FAILURE; + } + addMotion(motion); + } + + // Belly Bounce + { + controller_map_t controller; + controller["Mass"] = "Belly_Physics_Mass"; + controller["Gravity"] = "Belly_Physics_Gravity"; + controller["Drag"] = "Belly_Physics_Drag"; + controller["Damping"] = "Belly_Physics_UpDown_Damping"; + controller["MaxEffect"] = "Belly_Physics_UpDown_Max_Effect"; + controller["Spring"] = "Belly_Physics_UpDown_Spring"; + controller["Gain"] = "Belly_Physics_UpDown_Gain"; + LLPhysicsMotion *motion = new LLPhysicsMotion("Belly_Physics_UpDown_Controller", + "mPelvis", + character, + LLVector3(0,0,-1), + controller); + if (!motion->initialize()) + { + llassert_always(false); + return STATUS_FAILURE; + } + addMotion(motion); + } + + return STATUS_SUCCESS; +} + +void LLPhysicsMotionController::addMotion(LLPhysicsMotion *motion) +{ + addJointState(motion->getJointState()); + mMotions.push_back(motion); +} + +F32 LLPhysicsMotionController::getMinPixelArea() +{ + return MIN_REQUIRED_PIXEL_AREA_AVATAR_PHYSICS_MOTION; +} + +// Local space means "parameter space". +F32 LLPhysicsMotion::toLocal(const LLVector3 &world) +{ + LLJoint *joint = mJointState->getJoint(); + const LLQuaternion rotation_world = joint->getWorldRotation(); + + LLVector3 dir_world = mMotionDirectionVec * rotation_world; + dir_world.normalize(); + return world * dir_world; +} + +F32 LLPhysicsMotion::calculateVelocity_local(const F32 time_delta) +{ + const F32 world_to_model_scale = 100.0f; + LLJoint *joint = mJointState->getJoint(); + const LLVector3 position_world = joint->getWorldPosition(); + const LLVector3 last_position_world = mPosition_world; + const LLVector3 positionchange_world = (position_world-last_position_world) * world_to_model_scale; + const F32 velocity_local = toLocal(positionchange_world) / time_delta; + return velocity_local; +} + +F32 LLPhysicsMotion::calculateAcceleration_local(const F32 velocity_local, const F32 time_delta) +{ +// const F32 smoothing = getParamValue("Smoothing"); + static const F32 smoothing = 3.0f; // Removed smoothing param since it's probably not necessary + const F32 acceleration_local = (velocity_local - mVelocityJoint_local) / time_delta; + + const F32 smoothed_acceleration_local = + acceleration_local * 1.0/smoothing + + mAccelerationJoint_local * (smoothing-1.0)/smoothing; + + return smoothed_acceleration_local; +} + +bool LLPhysicsMotionController::onUpdate(F32 time, U8* joint_mask) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + // Skip if disabled globally. + if (!gSavedSettings.getBOOL("AvatarPhysics")) + { + return true; + } + + bool update_visuals = false; + for (motion_vec_t::iterator iter = mMotions.begin(); + iter != mMotions.end(); + ++iter) + { + LLPhysicsMotion *motion = (*iter); + update_visuals |= motion->onUpdate(time); + } + + if (update_visuals) + mCharacter->updateVisualParams(); + + return true; +} + +// Return true if character has to update visual params. +bool LLPhysicsMotion::onUpdate(F32 time) +{ + // static FILE *mFileWrite = fopen("c:\\temp\\avatar_data.txt","w"); + + if (!mParamDriver) + return false; + + if (!mLastTime || mLastTime >= time) + { + mLastTime = time; + return false; + } + + //////////////////////////////////////////////////////////////////////////////// + // Get all parameters and settings + // + + const F32 time_delta = time - mLastTime; + + // If less than 1FPS, we don't want to be spending time updating physics at all. + if (time_delta > 1.0) + { + mLastTime = time; + return false; + } + + // Higher LOD is better. This controls the granularity + // and frequency of updates for the motions. + const F32 lod_factor = LLVOAvatar::sPhysicsLODFactor; + if (lod_factor == 0) + { + return true; + } + + LLJoint *joint = mJointState->getJoint(); + + const F32 behavior_mass = getParamValue(MASS); + const F32 behavior_gravity = getParamValue(GRAVITY); + const F32 behavior_spring = getParamValue(SPRING); + const F32 behavior_gain = getParamValue(GAIN); + const F32 behavior_damping = getParamValue(DAMPING); + const F32 behavior_drag = getParamValue(DRAG); + F32 behavior_maxeffect = getParamValue(MAX_EFFECT); + + const bool physics_test = false; // Enable this to simulate bouncing on all parts. + + if (physics_test) + behavior_maxeffect = 1.0f; + + // Normalize the param position to be from [0,1]. + // We have to use normalized values because there may be more than one driven param, + // and each of these driven params may have its own range. + // This means we'll do all our calculations in normalized [0,1] local coordinates. + const F32 position_user_local = (mParamDriver->getWeight() - mParamDriver->getMinWeight()) / (mParamDriver->getMaxWeight() - mParamDriver->getMinWeight()); + + // + // End parameters and settings + //////////////////////////////////////////////////////////////////////////////// + + + //////////////////////////////////////////////////////////////////////////////// + // Calculate velocity and acceleration in parameter space. + // + + const F32 joint_local_factor = 30.0; + const F32 velocity_joint_local = calculateVelocity_local(time_delta * joint_local_factor); + const F32 acceleration_joint_local = calculateAcceleration_local(velocity_joint_local, time_delta * joint_local_factor); + + // + // End velocity and acceleration + //////////////////////////////////////////////////////////////////////////////// + + bool update_visuals = false; + + // Break up the physics into a bunch of iterations so that differing framerates will show + // roughly the same behavior. + // Explanation/example: Lets assume we have a bouncing object. Said abjects bounces at a + // trajectory that has points A>B>C. Object bounces from A to B with specific speed. + // It needs time T to move from A to B. + // As long as our frame's time significantly smaller then T our motion will be split into + // multiple parts. with each part speed will decrease. Object will reach B position (roughly) + // and bounce/fall back to A. + // But if frame's time (F_T) is larger then T, object will move with same speed for whole F_T + // and will jump over point B up to C ending up with increased amplitude. To avoid that we + // split F_T into smaller portions so that when frame's time is too long object can virtually + // bounce at right (relatively) position. + // Note: this doesn't look to be optimal, since it provides only "roughly same" behavior, but + // irregularity at higher fps looks to be insignificant so it works good enough for low fps. + U32 steps = (U32)(time_delta / TIME_ITERATION_STEP_MAX) + 1; + F32 time_iteration_step = time_delta / (F32)steps; //minimal step size ends up as 0.025 + for (U32 i = 0; i < steps; i++) + { + // mPositon_local should be in normalized 0,1 range already. Just making sure... + const F32 position_current_local = llclamp(mPosition_local, + 0.0f, + 1.0f); + // If the effect is turned off then don't process unless we need one more update + // to set the position to the default (i.e. user) position. + if ((behavior_maxeffect == 0) && (position_current_local == position_user_local)) + { + return update_visuals; + } + + //////////////////////////////////////////////////////////////////////////////// + // Calculate the total force + // + + // Spring force is a restoring force towards the original user-set breast position. + // F = kx + const F32 spring_length = position_current_local - position_user_local; + const F32 force_spring = -spring_length * behavior_spring; + + // Acceleration is the force that comes from the change in velocity of the torso. + // F = ma + const F32 force_accel = behavior_gain * (acceleration_joint_local * behavior_mass); + + // Gravity always points downward in world space. + // F = mg + const LLVector3 gravity_world(0,0,1); + const F32 force_gravity = (toLocal(gravity_world) * behavior_gravity * behavior_mass); + + // Damping is a restoring force that opposes the current velocity. + // F = -kv + const F32 force_damping = -behavior_damping * mVelocity_local; + + // Drag is a force imparted by velocity (intuitively it is similar to wind resistance) + // F = .5kv^2 + const F32 force_drag = .5*behavior_drag*velocity_joint_local*velocity_joint_local*llsgn(velocity_joint_local); + + const F32 force_net = (force_accel + + force_gravity + + force_spring + + force_damping + + force_drag); + + // + // End total force + //////////////////////////////////////////////////////////////////////////////// + + + //////////////////////////////////////////////////////////////////////////////// + // Calculate new params + // + + // Calculate the new acceleration based on the net force. + // a = F/m + const F32 acceleration_new_local = force_net / behavior_mass; + static const F32 max_velocity = 100.0f; // magic number, used to be customizable. + F32 velocity_new_local = mVelocity_local + acceleration_new_local*time_iteration_step; + velocity_new_local = llclamp(velocity_new_local, + -max_velocity, max_velocity); + + // Temporary debugging setting to cause all avatars to move, for profiling purposes. + if (physics_test) + { + velocity_new_local = sin(time*4.0); + } + // Calculate the new parameters, or remain unchanged if max speed is 0. + F32 position_new_local = position_current_local + velocity_new_local*time_iteration_step; + if (behavior_maxeffect == 0) + position_new_local = position_user_local; + + // Zero out the velocity if the param is being pushed beyond its limits. + if ((position_new_local < 0 && velocity_new_local < 0) || + (position_new_local > 1 && velocity_new_local > 0)) + { + velocity_new_local = 0; + } + + // Check for NaN values. A NaN value is detected if the variables doesn't equal itself. + // If NaN, then reset everything. + if ((mPosition_local != mPosition_local) || + (mVelocity_local != mVelocity_local) || + (position_new_local != position_new_local)) + { + position_new_local = 0; + mVelocity_local = 0; + mVelocityJoint_local = 0; + mAccelerationJoint_local = 0; + mPosition_local = 0; + mPosition_world = LLVector3(0,0,0); + } + + const F32 position_new_local_clamped = llclamp(position_new_local, + 0.0f, + 1.0f); + + LLDriverParam *driver_param = dynamic_cast(mParamDriver); + llassert_always(driver_param); + if (driver_param) + { + // If this is one of our "hidden" driver params, then make sure it's + // the default value. + if ((driver_param->getGroup() != VISUAL_PARAM_GROUP_TWEAKABLE) && + (driver_param->getGroup() != VISUAL_PARAM_GROUP_TWEAKABLE_NO_TRANSMIT)) + { + mCharacter->setVisualParamWeight(driver_param, 0); + } + S32 num_driven = driver_param->getDrivenParamsCount(); + for (S32 i = 0; i < num_driven; ++i) + { + const LLViewerVisualParam *driven_param = driver_param->getDrivenParam(i); + setParamValue(driven_param,position_new_local_clamped, behavior_maxeffect); + } + } + + // + // End calculate new params + //////////////////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////////////////// + // Conditionally update the visual params + // + + // Updating the visual params (i.e. what the user sees) is fairly expensive. + // So only update if the params have changed enough, and also take into account + // the graphics LOD settings. + + // For non-self, if the avatar is small enough visually, then don't update. + const F32 area_for_max_settings = 0.0; + const F32 area_for_min_settings = 1400.0; + const F32 area_for_this_setting = area_for_max_settings + (area_for_min_settings-area_for_max_settings)*(1.0-lod_factor); + const F32 pixel_area = sqrtf(mCharacter->getPixelArea()); + + const bool is_self = (dynamic_cast(mCharacter) != NULL); + if ((pixel_area > area_for_this_setting) || is_self) + { + const F32 position_diff_local = llabs(mPositionLastUpdate_local-position_new_local_clamped); + const F32 min_delta = (1.0001f-lod_factor)*0.4f; + if (llabs(position_diff_local) > min_delta) + { + update_visuals = true; + mPositionLastUpdate_local = position_new_local; + } + } + + // + // End update visual params + //////////////////////////////////////////////////////////////////////////////// + + mVelocity_local = velocity_new_local; + mAccelerationJoint_local = acceleration_joint_local; + mPosition_local = position_new_local; + } + mLastTime = time; + mPosition_world = joint->getWorldPosition(); + mVelocityJoint_local = velocity_joint_local; + + + /* + // Write out debugging info into a spreadsheet. + if (mFileWrite != NULL && is_self) + { + fprintf(mFileWrite,"%f\t%f\t%f \t\t%f \t\t%f\t%f\t%f\t \t\t%f\t%f\t%f\t%f\t%f \t\t%f\t%f\t%f\n", + position_new_local, + velocity_new_local, + acceleration_new_local, + + time_delta, + + mPosition_world[0], + mPosition_world[1], + mPosition_world[2], + + force_net, + force_spring, + force_accel, + force_damping, + force_drag, + + spring_length, + velocity_joint_local, + acceleration_joint_local + ); + } + */ + + return update_visuals; +} + +// Range of new_value_local is assumed to be [0 , 1] normalized. +void LLPhysicsMotion::setParamValue(const LLViewerVisualParam *param, + F32 new_value_normalized, + F32 behavior_maxeffect) +{ + const F32 value_min_local = param->getMinWeight(); + const F32 value_max_local = param->getMaxWeight(); + const F32 min_val = 0.5f-behavior_maxeffect/2.0; + const F32 max_val = 0.5f+behavior_maxeffect/2.0; + + // Scale from [0,1] to [min_val,max_val] + const F32 new_value_rescaled = min_val + (max_val-min_val) * new_value_normalized; + + // Scale from [0,1] to [value_min_local,value_max_local] + const F32 new_value_local = value_min_local + (value_max_local-value_min_local) * new_value_rescaled; + + mCharacter->setVisualParamWeight(param, new_value_local); +} diff --git a/indra/newview/llphysicsmotion.h b/indra/newview/llphysicsmotion.h index ed187ac96e..9f1278d22e 100644 --- a/indra/newview/llphysicsmotion.h +++ b/indra/newview/llphysicsmotion.h @@ -1,118 +1,118 @@ -/** - * @file llphysicsmotion.h - * @brief Implementation of LLPhysicsMotion class. - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPHYSICSMOTIONCONTROLLER_H -#define LL_LLPHYSICSMOTIONCONTROLLER_H - -//----------------------------------------------------------------------------- -// Header files -//----------------------------------------------------------------------------- -#include "llmotion.h" -#include "llframetimer.h" - -#define PHYSICS_MOTION_FADEIN_TIME 1.0f -#define PHYSICS_MOTION_FADEOUT_TIME 1.0f - -class LLPhysicsMotion; - -//----------------------------------------------------------------------------- -// class LLPhysicsMotion -//----------------------------------------------------------------------------- -class LLPhysicsMotionController : - public LLMotion -{ -public: - // Constructor - LLPhysicsMotionController(const LLUUID &id); - - // Destructor - virtual ~LLPhysicsMotionController(); - -public: - //------------------------------------------------------------------------- - // functions to support MotionController and MotionRegistry - //------------------------------------------------------------------------- - - // static constructor - // all subclasses must implement such a function and register it - static LLMotion *create(const LLUUID &id) { return new LLPhysicsMotionController(id); } - -public: - //------------------------------------------------------------------------- - // animation callbacks to be implemented by subclasses - //------------------------------------------------------------------------- - - // motions must specify whether or not they loop - virtual bool getLoop() { return true; } - - // motions must report their total duration - virtual F32 getDuration() { return 0.0; } - - // motions must report their "ease in" duration - virtual F32 getEaseInDuration() { return PHYSICS_MOTION_FADEIN_TIME; } - - // motions must report their "ease out" duration. - virtual F32 getEaseOutDuration() { return PHYSICS_MOTION_FADEOUT_TIME; } - - // called to determine when a motion should be activated/deactivated based on avatar pixel coverage - virtual F32 getMinPixelArea(); - - // motions must report their priority - virtual LLJoint::JointPriority getPriority() { return LLJoint::MEDIUM_PRIORITY; } - - virtual LLMotionBlendType getBlendType() { return ADDITIVE_BLEND; } - - // run-time (post constructor) initialization, - // called after parameters have been set - // must return true to indicate success and be available for activation - virtual LLMotionInitStatus onInitialize(LLCharacter *character); - - // called when a motion is activated - // must return true to indicate success, or else - // it will be deactivated - virtual bool onActivate(); - - // called per time step - // must return true while it is active, and - // must return false when the motion is completed. - virtual bool onUpdate(F32 time, U8* joint_mask); - - // called when a motion is deactivated - virtual void onDeactivate(); - - LLCharacter* getCharacter() { return mCharacter; } - -protected: - void addMotion(LLPhysicsMotion *motion); -private: - LLCharacter* mCharacter; - - typedef std::vector motion_vec_t; - motion_vec_t mMotions; -}; - -#endif // LL_LLPHYSICSMOTION_H - +/** + * @file llphysicsmotion.h + * @brief Implementation of LLPhysicsMotion class. + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPHYSICSMOTIONCONTROLLER_H +#define LL_LLPHYSICSMOTIONCONTROLLER_H + +//----------------------------------------------------------------------------- +// Header files +//----------------------------------------------------------------------------- +#include "llmotion.h" +#include "llframetimer.h" + +#define PHYSICS_MOTION_FADEIN_TIME 1.0f +#define PHYSICS_MOTION_FADEOUT_TIME 1.0f + +class LLPhysicsMotion; + +//----------------------------------------------------------------------------- +// class LLPhysicsMotion +//----------------------------------------------------------------------------- +class LLPhysicsMotionController : + public LLMotion +{ +public: + // Constructor + LLPhysicsMotionController(const LLUUID &id); + + // Destructor + virtual ~LLPhysicsMotionController(); + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return new LLPhysicsMotionController(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + + // motions must specify whether or not they loop + virtual bool getLoop() { return true; } + + // motions must report their total duration + virtual F32 getDuration() { return 0.0; } + + // motions must report their "ease in" duration + virtual F32 getEaseInDuration() { return PHYSICS_MOTION_FADEIN_TIME; } + + // motions must report their "ease out" duration. + virtual F32 getEaseOutDuration() { return PHYSICS_MOTION_FADEOUT_TIME; } + + // called to determine when a motion should be activated/deactivated based on avatar pixel coverage + virtual F32 getMinPixelArea(); + + // motions must report their priority + virtual LLJoint::JointPriority getPriority() { return LLJoint::MEDIUM_PRIORITY; } + + virtual LLMotionBlendType getBlendType() { return ADDITIVE_BLEND; } + + // run-time (post constructor) initialization, + // called after parameters have been set + // must return true to indicate success and be available for activation + virtual LLMotionInitStatus onInitialize(LLCharacter *character); + + // called when a motion is activated + // must return true to indicate success, or else + // it will be deactivated + virtual bool onActivate(); + + // called per time step + // must return true while it is active, and + // must return false when the motion is completed. + virtual bool onUpdate(F32 time, U8* joint_mask); + + // called when a motion is deactivated + virtual void onDeactivate(); + + LLCharacter* getCharacter() { return mCharacter; } + +protected: + void addMotion(LLPhysicsMotion *motion); +private: + LLCharacter* mCharacter; + + typedef std::vector motion_vec_t; + motion_vec_t mMotions; +}; + +#endif // LL_LLPHYSICSMOTION_H + diff --git a/indra/newview/llphysicsshapebuilderutil.h b/indra/newview/llphysicsshapebuilderutil.h index 506a71af4b..33c2d0a8b6 100644 --- a/indra/newview/llphysicsshapebuilderutil.h +++ b/indra/newview/llphysicsshapebuilderutil.h @@ -1,142 +1,142 @@ -/** - * @file llphysicsshapebuilder.h - * @brief Generic system to convert LL(Physics)VolumeParams to physics shapes - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_PHYSICS_SHAPE_BUILDER_H -#define LL_PHYSICS_SHAPE_BUILDER_H - -#include "indra_constants.h" -#include "llvolume.h" - -#define USE_SHAPE_QUANTIZATION 0 - -#define SHAPE_BUILDER_DEFAULT_VOLUME_DETAIL 1 - -#define SHAPE_BUILDER_IMPLICIT_THRESHOLD_HOLLOW 0.10f -#define SHAPE_BUILDER_IMPLICIT_THRESHOLD_HOLLOW_SPHERES 0.90f -#define SHAPE_BUILDER_IMPLICIT_THRESHOLD_PATH_CUT 0.05f -#define SHAPE_BUILDER_IMPLICIT_THRESHOLD_TAPER 0.05f -#define SHAPE_BUILDER_IMPLICIT_THRESHOLD_TWIST 0.09f -#define SHAPE_BUILDER_IMPLICIT_THRESHOLD_SHEAR 0.05f - -const F32 COLLISION_TOLERANCE = 0.1f; - -const F32 SHAPE_BUILDER_ENTRY_SNAP_SCALE_BIN_SIZE = 0.15f; -const F32 SHAPE_BUILDER_ENTRY_SNAP_PARAMETER_BIN_SIZE = 0.010f; -const F32 SHAPE_BUILDER_CONVEXIFICATION_SIZE = 2.f * COLLISION_TOLERANCE; -const F32 SHAPE_BUILDER_MIN_GEOMETRY_SIZE = 0.5f * COLLISION_TOLERANCE; -const F32 SHAPE_BUILDER_USER_MESH_CONVEXIFICATION_SIZE = 0.5f; - -class LLPhysicsVolumeParams : public LLVolumeParams -{ -public: - - LLPhysicsVolumeParams( const LLVolumeParams& params, bool forceConvex ) : - LLVolumeParams( params ), - mForceConvex(forceConvex) {} - - bool operator==(const LLPhysicsVolumeParams ¶ms) const - { - return ( LLVolumeParams::operator==(params) && (mForceConvex == params.mForceConvex) ); - } - - bool operator!=(const LLPhysicsVolumeParams ¶ms) const - { - return !operator==(params); - } - - bool operator<(const LLPhysicsVolumeParams ¶ms) const - { - if ( LLVolumeParams::operator!=(params) ) - { - return LLVolumeParams::operator<(params); - } - - return !params.mForceConvex && mForceConvex; - } - - bool shouldForceConvex() const { return mForceConvex; } - -private: - bool mForceConvex; -}; - - -class LLPhysicsShapeBuilderUtil -{ -public: - - class PhysicsShapeSpecification - { - public: - enum ShapeType - { - // Primitive types - BOX, - SPHERE, - CYLINDER, - - USER_CONVEX, // User specified they wanted the convex hull of the volume - - PRIM_CONVEX, // Either a volume that is inherently convex but not a primitive type, or a shape - // with dimensions such that will convexify it anyway. - - SCULPT, // Special case for traditional sculpts--they are the convex hull of a single particular set of volume params - - USER_MESH, // A user mesh. May or may not contain a convex decomposition. - - PRIM_MESH, // A non-convex volume which we have to represent accurately - - INVALID - }; - - PhysicsShapeSpecification() : - mType( INVALID ), - mScale( 0.f, 0.f, 0.f ), - mCenter( 0.f, 0.f, 0.f ) {} - - bool isConvex() { return (mType != USER_MESH && mType != PRIM_MESH && mType != INVALID); } - bool isMesh() { return (mType == USER_MESH) || (mType == PRIM_MESH); } - - ShapeType getType() { return mType; } - const LLVector3& getScale() { return mScale; } - const LLVector3& getCenter() { return mCenter; } - - private: - friend class LLPhysicsShapeBuilderUtil; - - ShapeType mType; - - // Dimensions of an AABB around the shape - LLVector3 mScale; - - // Offset of shape from origin of primitive's reference frame - LLVector3 mCenter; - }; - - static void determinePhysicsShape( const LLPhysicsVolumeParams& volume_params, const LLVector3& scale, PhysicsShapeSpecification& specOut ); -}; - -#endif //LL_PHYSICS_SHAPE_BUILDER_H +/** + * @file llphysicsshapebuilder.h + * @brief Generic system to convert LL(Physics)VolumeParams to physics shapes + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_PHYSICS_SHAPE_BUILDER_H +#define LL_PHYSICS_SHAPE_BUILDER_H + +#include "indra_constants.h" +#include "llvolume.h" + +#define USE_SHAPE_QUANTIZATION 0 + +#define SHAPE_BUILDER_DEFAULT_VOLUME_DETAIL 1 + +#define SHAPE_BUILDER_IMPLICIT_THRESHOLD_HOLLOW 0.10f +#define SHAPE_BUILDER_IMPLICIT_THRESHOLD_HOLLOW_SPHERES 0.90f +#define SHAPE_BUILDER_IMPLICIT_THRESHOLD_PATH_CUT 0.05f +#define SHAPE_BUILDER_IMPLICIT_THRESHOLD_TAPER 0.05f +#define SHAPE_BUILDER_IMPLICIT_THRESHOLD_TWIST 0.09f +#define SHAPE_BUILDER_IMPLICIT_THRESHOLD_SHEAR 0.05f + +const F32 COLLISION_TOLERANCE = 0.1f; + +const F32 SHAPE_BUILDER_ENTRY_SNAP_SCALE_BIN_SIZE = 0.15f; +const F32 SHAPE_BUILDER_ENTRY_SNAP_PARAMETER_BIN_SIZE = 0.010f; +const F32 SHAPE_BUILDER_CONVEXIFICATION_SIZE = 2.f * COLLISION_TOLERANCE; +const F32 SHAPE_BUILDER_MIN_GEOMETRY_SIZE = 0.5f * COLLISION_TOLERANCE; +const F32 SHAPE_BUILDER_USER_MESH_CONVEXIFICATION_SIZE = 0.5f; + +class LLPhysicsVolumeParams : public LLVolumeParams +{ +public: + + LLPhysicsVolumeParams( const LLVolumeParams& params, bool forceConvex ) : + LLVolumeParams( params ), + mForceConvex(forceConvex) {} + + bool operator==(const LLPhysicsVolumeParams ¶ms) const + { + return ( LLVolumeParams::operator==(params) && (mForceConvex == params.mForceConvex) ); + } + + bool operator!=(const LLPhysicsVolumeParams ¶ms) const + { + return !operator==(params); + } + + bool operator<(const LLPhysicsVolumeParams ¶ms) const + { + if ( LLVolumeParams::operator!=(params) ) + { + return LLVolumeParams::operator<(params); + } + + return !params.mForceConvex && mForceConvex; + } + + bool shouldForceConvex() const { return mForceConvex; } + +private: + bool mForceConvex; +}; + + +class LLPhysicsShapeBuilderUtil +{ +public: + + class PhysicsShapeSpecification + { + public: + enum ShapeType + { + // Primitive types + BOX, + SPHERE, + CYLINDER, + + USER_CONVEX, // User specified they wanted the convex hull of the volume + + PRIM_CONVEX, // Either a volume that is inherently convex but not a primitive type, or a shape + // with dimensions such that will convexify it anyway. + + SCULPT, // Special case for traditional sculpts--they are the convex hull of a single particular set of volume params + + USER_MESH, // A user mesh. May or may not contain a convex decomposition. + + PRIM_MESH, // A non-convex volume which we have to represent accurately + + INVALID + }; + + PhysicsShapeSpecification() : + mType( INVALID ), + mScale( 0.f, 0.f, 0.f ), + mCenter( 0.f, 0.f, 0.f ) {} + + bool isConvex() { return (mType != USER_MESH && mType != PRIM_MESH && mType != INVALID); } + bool isMesh() { return (mType == USER_MESH) || (mType == PRIM_MESH); } + + ShapeType getType() { return mType; } + const LLVector3& getScale() { return mScale; } + const LLVector3& getCenter() { return mCenter; } + + private: + friend class LLPhysicsShapeBuilderUtil; + + ShapeType mType; + + // Dimensions of an AABB around the shape + LLVector3 mScale; + + // Offset of shape from origin of primitive's reference frame + LLVector3 mCenter; + }; + + static void determinePhysicsShape( const LLPhysicsVolumeParams& volume_params, const LLVector3& scale, PhysicsShapeSpecification& specOut ); +}; + +#endif //LL_PHYSICS_SHAPE_BUILDER_H diff --git a/indra/newview/llplacesfolderview.cpp b/indra/newview/llplacesfolderview.cpp index b3563a0b8a..770f927723 100644 --- a/indra/newview/llplacesfolderview.cpp +++ b/indra/newview/llplacesfolderview.cpp @@ -1,85 +1,85 @@ -/** -* @file llplacesfolderview.cpp -* @brief llplacesfolderview used within llplacesinventorypanel -* @author Gilbert@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#include "llviewerprecompiledheaders.h" - -#include "llplacesfolderview.h" - -#include "llplacesinventorypanel.h" -#include "llpanellandmarks.h" -#include "llmenugl.h" - -LLPlacesFolderView::LLPlacesFolderView(const LLFolderView::Params& p) - : LLFolderView(p) -{ - // we do not need auto select functionality in places landmarks, so override default behavior. - // this disables applying of the LLSelectFirstFilteredItem in LLFolderView::doIdle. - // Fixed issues: EXT-1631, EXT-4994. - mAutoSelectOverride = true; -} - -bool LLPlacesFolderView::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - // let children to change selection first - childrenHandleRightMouseDown(x, y, mask); - mParentLandmarksPanel->setCurrentSelectedList((LLPlacesInventoryPanel*)getParentPanel()); - - // then determine its type and set necessary menu handle - if (getCurSelectedItem()) - { - LLInventoryType::EType inventory_type = static_cast(getCurSelectedItem()->getViewModelItem())->getInventoryType(); - inventory_type_menu_handle_t::iterator it_handle = mMenuHandlesByInventoryType.find(inventory_type); - - if (it_handle != mMenuHandlesByInventoryType.end()) - { - mPopupMenuHandle = (*it_handle).second; - } - else - { - LL_WARNS() << "Requested menu handle for non-setup inventory type: " << inventory_type << LL_ENDL; - } - - } - - return LLFolderView::handleRightMouseDown(x, y, mask); -} - -void LLPlacesFolderView::updateMenu() -{ - LLFolderView::updateMenu(); - LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get(); - if (menu && menu->getVisible()) - { - mParentLandmarksPanel->updateMenuVisibility(menu); - } -} - -void LLPlacesFolderView::setupMenuHandle(LLInventoryType::EType asset_type, LLHandle menu_handle) -{ - mMenuHandlesByInventoryType[asset_type] = menu_handle; -} - +/** +* @file llplacesfolderview.cpp +* @brief llplacesfolderview used within llplacesinventorypanel +* @author Gilbert@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" + +#include "llplacesfolderview.h" + +#include "llplacesinventorypanel.h" +#include "llpanellandmarks.h" +#include "llmenugl.h" + +LLPlacesFolderView::LLPlacesFolderView(const LLFolderView::Params& p) + : LLFolderView(p) +{ + // we do not need auto select functionality in places landmarks, so override default behavior. + // this disables applying of the LLSelectFirstFilteredItem in LLFolderView::doIdle. + // Fixed issues: EXT-1631, EXT-4994. + mAutoSelectOverride = true; +} + +bool LLPlacesFolderView::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + // let children to change selection first + childrenHandleRightMouseDown(x, y, mask); + mParentLandmarksPanel->setCurrentSelectedList((LLPlacesInventoryPanel*)getParentPanel()); + + // then determine its type and set necessary menu handle + if (getCurSelectedItem()) + { + LLInventoryType::EType inventory_type = static_cast(getCurSelectedItem()->getViewModelItem())->getInventoryType(); + inventory_type_menu_handle_t::iterator it_handle = mMenuHandlesByInventoryType.find(inventory_type); + + if (it_handle != mMenuHandlesByInventoryType.end()) + { + mPopupMenuHandle = (*it_handle).second; + } + else + { + LL_WARNS() << "Requested menu handle for non-setup inventory type: " << inventory_type << LL_ENDL; + } + + } + + return LLFolderView::handleRightMouseDown(x, y, mask); +} + +void LLPlacesFolderView::updateMenu() +{ + LLFolderView::updateMenu(); + LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandle.get(); + if (menu && menu->getVisible()) + { + mParentLandmarksPanel->updateMenuVisibility(menu); + } +} + +void LLPlacesFolderView::setupMenuHandle(LLInventoryType::EType asset_type, LLHandle menu_handle) +{ + mMenuHandlesByInventoryType[asset_type] = menu_handle; +} + diff --git a/indra/newview/llplacesfolderview.h b/indra/newview/llplacesfolderview.h index 6b23c78777..4b1cbcd992 100644 --- a/indra/newview/llplacesfolderview.h +++ b/indra/newview/llplacesfolderview.h @@ -1,74 +1,74 @@ -/** -* @file llplacesfolderview.h -* @brief llplacesfolderview used within llplacesinventorypanel -* @author Gilbert@lindenlab.com -* -* $LicenseInfo:firstyear=2012&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2012, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ -#ifndef LL_LLPLACESFOLDERVIEW_H -#define LL_LLPLACESFOLDERVIEW_H - -#include "llfolderview.h" -#include "llinventorypanel.h" - -class LLLandmarksPanel; - -class LLPlacesFolderView : public LLFolderView -{ -public: - - struct Params : public LLInitParam::Block - { - Params() - {} - }; - - LLPlacesFolderView(const LLFolderView::Params& p); - /** - * Handles right mouse down - * - * Contains workaround for EXT-2786: sets current selected list for landmark - * panel using @c mParentLandmarksPanel which is set in @c LLLandmarksPanel::initLandmarksPanel - */ - /*virtual*/ bool handleRightMouseDown( S32 x, S32 y, MASK mask ); - - /*virtual*/ void updateMenu(); - - void setupMenuHandle(LLInventoryType::EType asset_type, LLHandle menu_handle); - - void setParentLandmarksPanel(LLLandmarksPanel* panel) - { - mParentLandmarksPanel = panel; - } - -private: - /** - * holds pointer to landmark panel. This pointer is used in @c LLPlacesFolderView::handleRightMouseDown - */ - LLLandmarksPanel* mParentLandmarksPanel; - typedef std::map > inventory_type_menu_handle_t; - inventory_type_menu_handle_t mMenuHandlesByInventoryType; - -}; - -#endif // LL_LLPLACESFOLDERVIEW_H - +/** +* @file llplacesfolderview.h +* @brief llplacesfolderview used within llplacesinventorypanel +* @author Gilbert@lindenlab.com +* +* $LicenseInfo:firstyear=2012&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2012, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LL_LLPLACESFOLDERVIEW_H +#define LL_LLPLACESFOLDERVIEW_H + +#include "llfolderview.h" +#include "llinventorypanel.h" + +class LLLandmarksPanel; + +class LLPlacesFolderView : public LLFolderView +{ +public: + + struct Params : public LLInitParam::Block + { + Params() + {} + }; + + LLPlacesFolderView(const LLFolderView::Params& p); + /** + * Handles right mouse down + * + * Contains workaround for EXT-2786: sets current selected list for landmark + * panel using @c mParentLandmarksPanel which is set in @c LLLandmarksPanel::initLandmarksPanel + */ + /*virtual*/ bool handleRightMouseDown( S32 x, S32 y, MASK mask ); + + /*virtual*/ void updateMenu(); + + void setupMenuHandle(LLInventoryType::EType asset_type, LLHandle menu_handle); + + void setParentLandmarksPanel(LLLandmarksPanel* panel) + { + mParentLandmarksPanel = panel; + } + +private: + /** + * holds pointer to landmark panel. This pointer is used in @c LLPlacesFolderView::handleRightMouseDown + */ + LLLandmarksPanel* mParentLandmarksPanel; + typedef std::map > inventory_type_menu_handle_t; + inventory_type_menu_handle_t mMenuHandlesByInventoryType; + +}; + +#endif // LL_LLPLACESFOLDERVIEW_H + diff --git a/indra/newview/llplacesinventorypanel.cpp b/indra/newview/llplacesinventorypanel.cpp index 9fc1e72c89..03be8a4b2c 100644 --- a/indra/newview/llplacesinventorypanel.cpp +++ b/indra/newview/llplacesinventorypanel.cpp @@ -1,132 +1,132 @@ -/** - * @file llplacesinventorypanel.cpp - * @brief LLPlacesInventoryPanel class definition - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llscrollcontainer.h" - -#include "llplacesinventorypanel.h" - -#include "llfolderviewmodel.h" -#include "llplacesfolderview.h" -#include "llinventorybridge.h" -#include "llinventoryfunctions.h" -#include "llpanellandmarks.h" -#include "llplacesinventorybridge.h" -#include "llviewerfoldertype.h" - -static LLDefaultChildRegistry::Register r("places_inventory_panel"); - -static const LLPlacesInventoryBridgeBuilder PLACES_INVENTORY_BUILDER; - -LLPlacesInventoryPanel::LLPlacesInventoryPanel(const Params& p) : - LLAssetFilteredInventoryPanel(p), - mSavedFolderState(NULL) -{ - mInvFVBridgeBuilder = &PLACES_INVENTORY_BUILDER; - mSavedFolderState = new LLSaveFolderState(); - mSavedFolderState->setApply(false); -} - - -LLPlacesInventoryPanel::~LLPlacesInventoryPanel() -{ - delete mSavedFolderState; -} - - -LLFolderView * LLPlacesInventoryPanel::createFolderRoot(LLUUID root_id ) -{ - LLPlacesFolderView::Params p; - - p.name = getName(); - p.title = getLabel(); - p.rect = LLRect(0, 0, getRect().getWidth(), 0); - p.parent_panel = this; - p.tool_tip = p.name; - p.listener = mInvFVBridgeBuilder->createBridge( LLAssetType::AT_CATEGORY, - LLAssetType::AT_CATEGORY, - LLInventoryType::IT_CATEGORY, - this, - &mInventoryViewModel, - NULL, - root_id); - p.view_model = &mInventoryViewModel; - p.use_label_suffix = mParams.use_label_suffix; - p.allow_multiselect = mAllowMultiSelect; - p.allow_drag = mAllowDrag; - p.show_empty_message = mShowEmptyMessage; - p.show_item_link_overlays = mShowItemLinkOverlays; - p.root = NULL; - p.use_ellipses = mParams.folder_view.use_ellipses; - p.options_menu = "menu_inventory.xml"; - - return LLUICtrlFactory::create(p); -} - -// save current folder open state -void LLPlacesInventoryPanel::saveFolderState() -{ - mSavedFolderState->setApply(false); - mFolderRoot.get()->applyFunctorRecursively(*mSavedFolderState); -} - -// re-open folders which state was saved -void LLPlacesInventoryPanel::restoreFolderState() -{ - mSavedFolderState->setApply(true); - mFolderRoot.get()->applyFunctorRecursively(*mSavedFolderState); - LLOpenFoldersWithSelection opener; - mFolderRoot.get()->applyFunctorRecursively(opener); - mFolderRoot.get()->scrollToShowSelection(); -} - -S32 LLPlacesInventoryPanel::notify(const LLSD& info) -{ - if(info.has("action")) - { - std::string str_action = info["action"]; - if(str_action == "select_first") - { - return mFolderRoot.get()->notify(info); - } - else if(str_action == "select_last") - { - return mFolderRoot.get()->notify(info); - } - } - return 0; -} - -bool LLPlacesInventoryPanel::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, void *cargo_data, - EAcceptance *accept, std::string &tooltip_msg) -{ - if (mAcceptsDragAndDrop) - { - return LLInventoryPanel::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); - } - return false; -} +/** + * @file llplacesinventorypanel.cpp + * @brief LLPlacesInventoryPanel class definition + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llscrollcontainer.h" + +#include "llplacesinventorypanel.h" + +#include "llfolderviewmodel.h" +#include "llplacesfolderview.h" +#include "llinventorybridge.h" +#include "llinventoryfunctions.h" +#include "llpanellandmarks.h" +#include "llplacesinventorybridge.h" +#include "llviewerfoldertype.h" + +static LLDefaultChildRegistry::Register r("places_inventory_panel"); + +static const LLPlacesInventoryBridgeBuilder PLACES_INVENTORY_BUILDER; + +LLPlacesInventoryPanel::LLPlacesInventoryPanel(const Params& p) : + LLAssetFilteredInventoryPanel(p), + mSavedFolderState(NULL) +{ + mInvFVBridgeBuilder = &PLACES_INVENTORY_BUILDER; + mSavedFolderState = new LLSaveFolderState(); + mSavedFolderState->setApply(false); +} + + +LLPlacesInventoryPanel::~LLPlacesInventoryPanel() +{ + delete mSavedFolderState; +} + + +LLFolderView * LLPlacesInventoryPanel::createFolderRoot(LLUUID root_id ) +{ + LLPlacesFolderView::Params p; + + p.name = getName(); + p.title = getLabel(); + p.rect = LLRect(0, 0, getRect().getWidth(), 0); + p.parent_panel = this; + p.tool_tip = p.name; + p.listener = mInvFVBridgeBuilder->createBridge( LLAssetType::AT_CATEGORY, + LLAssetType::AT_CATEGORY, + LLInventoryType::IT_CATEGORY, + this, + &mInventoryViewModel, + NULL, + root_id); + p.view_model = &mInventoryViewModel; + p.use_label_suffix = mParams.use_label_suffix; + p.allow_multiselect = mAllowMultiSelect; + p.allow_drag = mAllowDrag; + p.show_empty_message = mShowEmptyMessage; + p.show_item_link_overlays = mShowItemLinkOverlays; + p.root = NULL; + p.use_ellipses = mParams.folder_view.use_ellipses; + p.options_menu = "menu_inventory.xml"; + + return LLUICtrlFactory::create(p); +} + +// save current folder open state +void LLPlacesInventoryPanel::saveFolderState() +{ + mSavedFolderState->setApply(false); + mFolderRoot.get()->applyFunctorRecursively(*mSavedFolderState); +} + +// re-open folders which state was saved +void LLPlacesInventoryPanel::restoreFolderState() +{ + mSavedFolderState->setApply(true); + mFolderRoot.get()->applyFunctorRecursively(*mSavedFolderState); + LLOpenFoldersWithSelection opener; + mFolderRoot.get()->applyFunctorRecursively(opener); + mFolderRoot.get()->scrollToShowSelection(); +} + +S32 LLPlacesInventoryPanel::notify(const LLSD& info) +{ + if(info.has("action")) + { + std::string str_action = info["action"]; + if(str_action == "select_first") + { + return mFolderRoot.get()->notify(info); + } + else if(str_action == "select_last") + { + return mFolderRoot.get()->notify(info); + } + } + return 0; +} + +bool LLPlacesInventoryPanel::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, void *cargo_data, + EAcceptance *accept, std::string &tooltip_msg) +{ + if (mAcceptsDragAndDrop) + { + return LLInventoryPanel::handleDragAndDrop(x, y, mask, drop, cargo_type, cargo_data, accept, tooltip_msg); + } + return false; +} diff --git a/indra/newview/llplacesinventorypanel.h b/indra/newview/llplacesinventorypanel.h index 6c7604902b..9b33369f5f 100644 --- a/indra/newview/llplacesinventorypanel.h +++ b/indra/newview/llplacesinventorypanel.h @@ -1,63 +1,63 @@ -/** - * @file llplacesinventorypanel.h - * @brief LLPlacesInventoryPanel class declaration - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLINVENTORYSUBTREEPANEL_H -#define LL_LLINVENTORYSUBTREEPANEL_H - -#include "llinventorypanel.h" - -class LLLandmarksPanel; -class LLFolderView; - -class LLPlacesInventoryPanel : public LLAssetFilteredInventoryPanel -{ -public: - struct Params - : public LLInitParam::Block - { - Params() - { - filter_asset_types = "landmark"; - } - }; - - LLPlacesInventoryPanel(const Params& p); - ~LLPlacesInventoryPanel(); - - LLFolderView * createFolderRoot(LLUUID root_id ) override; - void saveFolderState(); - void restoreFolderState(); - - virtual S32 notify(const LLSD& info) override; - - bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, void *cargo_data, - EAcceptance *accept, std::string &tooltip_msg) override; - -private: - LLSaveFolderState* mSavedFolderState; -}; - -#endif //LL_LLINVENTORYSUBTREEPANEL_H +/** + * @file llplacesinventorypanel.h + * @brief LLPlacesInventoryPanel class declaration + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLINVENTORYSUBTREEPANEL_H +#define LL_LLINVENTORYSUBTREEPANEL_H + +#include "llinventorypanel.h" + +class LLLandmarksPanel; +class LLFolderView; + +class LLPlacesInventoryPanel : public LLAssetFilteredInventoryPanel +{ +public: + struct Params + : public LLInitParam::Block + { + Params() + { + filter_asset_types = "landmark"; + } + }; + + LLPlacesInventoryPanel(const Params& p); + ~LLPlacesInventoryPanel(); + + LLFolderView * createFolderRoot(LLUUID root_id ) override; + void saveFolderState(); + void restoreFolderState(); + + virtual S32 notify(const LLSD& info) override; + + bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, EDragAndDropType cargo_type, void *cargo_data, + EAcceptance *accept, std::string &tooltip_msg) override; + +private: + LLSaveFolderState* mSavedFolderState; +}; + +#endif //LL_LLINVENTORYSUBTREEPANEL_H diff --git a/indra/newview/llpopupview.cpp b/indra/newview/llpopupview.cpp index 153aa19c1a..cc55b3c8db 100644 --- a/indra/newview/llpopupview.cpp +++ b/indra/newview/llpopupview.cpp @@ -1,271 +1,271 @@ -/** - * @file llpopupview.cpp - * @brief Holds transient popups - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ -#include "llviewerprecompiledheaders.h" - -#include "llpopupview.h" - -static LLPanelInjector r("popup_holder"); - -bool view_visible_and_enabled(LLView* viewp) -{ - return viewp->getVisible() && viewp->getEnabled(); -} - -bool view_visible(LLView* viewp) -{ - return viewp->getVisible(); -} - - -LLPopupView::LLPopupView(const LLPopupView::Params& p) -: LLPanel(p) -{ - // register ourself as handler of UI popups - LLUI::getInstance()->setPopupFuncs(boost::bind(&LLPopupView::addPopup, this, _1), boost::bind(&LLPopupView::removePopup, this, _1), boost::bind(&LLPopupView::clearPopups, this)); -} - -LLPopupView::~LLPopupView() -{ - // set empty callback function so we can't handle popups anymore - LLUI::getInstance()->setPopupFuncs(LLUI::add_popup_t(), LLUI::remove_popup_t(), LLUI::clear_popups_t()); -} - -void LLPopupView::draw() -{ - S32 screen_x, screen_y; - - // remove dead popups - for (popup_list_t::iterator popup_it = mPopups.begin(); - popup_it != mPopups.end();) - { - if (!popup_it->get()) - { - mPopups.erase(popup_it++); - } - else - { - popup_it++; - } - } - - // draw in reverse order (most recent is on top) - for (popup_list_t::reverse_iterator popup_it = mPopups.rbegin(); - popup_it != mPopups.rend();) - { - LLView* popup = popup_it->get(); - - if (popup->getVisible()) - { - popup->localPointToScreen(0, 0, &screen_x, &screen_y); - - LLUI::pushMatrix(); - { - LLUI::translate( (F32) screen_x, (F32) screen_y); - popup->draw(); - } - LLUI::popMatrix(); - } - ++popup_it; - } - - LLPanel::draw(); -} - -bool LLPopupView::handleMouseEvent(boost::function func, - boost::function predicate, - S32 x, S32 y, - bool close_popups) -{ - bool handled = false; - - // make a copy of list of popups, in case list is modified during mouse event handling - popup_list_t popups(mPopups); - for (popup_list_t::iterator popup_it = popups.begin(), popup_end = popups.end(); - popup_it != popup_end; - ++popup_it) - { - LLView* popup = popup_it->get(); - if (!popup - || !predicate(popup)) - { - continue; - } - - S32 popup_x, popup_y; - if (localPointToOtherView(x, y, &popup_x, &popup_y, popup) - && popup->pointInView(popup_x, popup_y)) - { - if (func(popup, popup_x, popup_y)) - { - handled = true; - break; - } - } - - if (close_popups) - { - mPopups.remove(*popup_it); - popup->onTopLost(); - } - } - - return handled; -} - - -bool LLPopupView::handleMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = handleMouseEvent(boost::bind(&LLMouseHandler::handleMouseDown, _1, _2, _3, mask), view_visible_and_enabled, x, y, true); - if (!handled) - { - handled = LLPanel::handleMouseDown(x, y, mask); - } - return handled; -} - -bool LLPopupView::handleMouseUp(S32 x, S32 y, MASK mask) -{ - bool handled = handleMouseEvent(boost::bind(&LLMouseHandler::handleMouseUp, _1, _2, _3, mask), view_visible_and_enabled, x, y, false); - if (!handled) - { - handled = LLPanel::handleMouseUp(x, y, mask); - } - return handled; -} - -bool LLPopupView::handleMiddleMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = handleMouseEvent(boost::bind(&LLMouseHandler::handleMiddleMouseDown, _1, _2, _3, mask), view_visible_and_enabled, x, y, true); - if (!handled) - { - handled = LLPanel::handleMiddleMouseDown(x, y, mask); - } - return handled; -} - -bool LLPopupView::handleMiddleMouseUp(S32 x, S32 y, MASK mask) -{ - bool handled = handleMouseEvent(boost::bind(&LLMouseHandler::handleMiddleMouseUp, _1, _2, _3, mask), view_visible_and_enabled, x, y, false); - if (!handled) - { - handled = LLPanel::handleMiddleMouseUp(x, y, mask); - } - return handled; -} - -bool LLPopupView::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = handleMouseEvent(boost::bind(&LLMouseHandler::handleRightMouseDown, _1, _2, _3, mask), view_visible_and_enabled, x, y, true); - if (!handled) - { - handled = LLPanel::handleRightMouseDown(x, y, mask); - } - return handled; -} - -bool LLPopupView::handleRightMouseUp(S32 x, S32 y, MASK mask) -{ - bool handled = handleMouseEvent(boost::bind(&LLMouseHandler::handleRightMouseUp, _1, _2, _3, mask), view_visible_and_enabled, x, y, false); - if (!handled) - { - handled = LLPanel::handleRightMouseUp(x, y, mask); - } - return handled; -} - -bool LLPopupView::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - bool handled = handleMouseEvent(boost::bind(&LLMouseHandler::handleDoubleClick, _1, _2, _3, mask), view_visible_and_enabled, x, y, false); - if (!handled) - { - handled = LLPanel::handleDoubleClick(x, y, mask); - } - return handled; -} - -bool LLPopupView::handleHover(S32 x, S32 y, MASK mask) -{ - bool handled = handleMouseEvent(boost::bind(&LLMouseHandler::handleHover, _1, _2, _3, mask), view_visible_and_enabled, x, y, false); - if (!handled) - { - handled = LLPanel::handleHover(x, y, mask); - } - return handled; -} - -bool LLPopupView::handleScrollWheel(S32 x, S32 y, S32 clicks) -{ - bool handled = handleMouseEvent(boost::bind(&LLMouseHandler::handleScrollWheel, _1, _2, _3, clicks), view_visible_and_enabled, x, y, false); - if (!handled) - { - handled = LLPanel::handleScrollWheel(x, y, clicks); - } - return handled; -} - -bool LLPopupView::handleToolTip(S32 x, S32 y, MASK mask) -{ - bool handled = handleMouseEvent(boost::bind(&LLMouseHandler::handleToolTip, _1, _2, _3, mask), view_visible, x, y, false); - if (!handled) - { - handled = LLPanel::handleToolTip(x, y, mask); - } - return handled; -} - -void LLPopupView::addPopup(LLView* popup) -{ - if (popup) - { - mPopups.remove(popup->getHandle()); - mPopups.push_front(popup->getHandle()); - } -} - -void LLPopupView::removePopup(LLView* popup) -{ - if (popup) - { - mPopups.remove(popup->getHandle()); - popup->onTopLost(); - } -} - -void LLPopupView::clearPopups() -{ - while (!mPopups.empty()) - { - popup_list_t::iterator popup_it = mPopups.begin(); - LLView* popup = popup_it->get(); - // Remove before notifying in case it will cause removePopup - mPopups.erase(popup_it); - if (popup) - { - popup->onTopLost(); - } -} -} - +/** + * @file llpopupview.cpp + * @brief Holds transient popups + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#include "llviewerprecompiledheaders.h" + +#include "llpopupview.h" + +static LLPanelInjector r("popup_holder"); + +bool view_visible_and_enabled(LLView* viewp) +{ + return viewp->getVisible() && viewp->getEnabled(); +} + +bool view_visible(LLView* viewp) +{ + return viewp->getVisible(); +} + + +LLPopupView::LLPopupView(const LLPopupView::Params& p) +: LLPanel(p) +{ + // register ourself as handler of UI popups + LLUI::getInstance()->setPopupFuncs(boost::bind(&LLPopupView::addPopup, this, _1), boost::bind(&LLPopupView::removePopup, this, _1), boost::bind(&LLPopupView::clearPopups, this)); +} + +LLPopupView::~LLPopupView() +{ + // set empty callback function so we can't handle popups anymore + LLUI::getInstance()->setPopupFuncs(LLUI::add_popup_t(), LLUI::remove_popup_t(), LLUI::clear_popups_t()); +} + +void LLPopupView::draw() +{ + S32 screen_x, screen_y; + + // remove dead popups + for (popup_list_t::iterator popup_it = mPopups.begin(); + popup_it != mPopups.end();) + { + if (!popup_it->get()) + { + mPopups.erase(popup_it++); + } + else + { + popup_it++; + } + } + + // draw in reverse order (most recent is on top) + for (popup_list_t::reverse_iterator popup_it = mPopups.rbegin(); + popup_it != mPopups.rend();) + { + LLView* popup = popup_it->get(); + + if (popup->getVisible()) + { + popup->localPointToScreen(0, 0, &screen_x, &screen_y); + + LLUI::pushMatrix(); + { + LLUI::translate( (F32) screen_x, (F32) screen_y); + popup->draw(); + } + LLUI::popMatrix(); + } + ++popup_it; + } + + LLPanel::draw(); +} + +bool LLPopupView::handleMouseEvent(boost::function func, + boost::function predicate, + S32 x, S32 y, + bool close_popups) +{ + bool handled = false; + + // make a copy of list of popups, in case list is modified during mouse event handling + popup_list_t popups(mPopups); + for (popup_list_t::iterator popup_it = popups.begin(), popup_end = popups.end(); + popup_it != popup_end; + ++popup_it) + { + LLView* popup = popup_it->get(); + if (!popup + || !predicate(popup)) + { + continue; + } + + S32 popup_x, popup_y; + if (localPointToOtherView(x, y, &popup_x, &popup_y, popup) + && popup->pointInView(popup_x, popup_y)) + { + if (func(popup, popup_x, popup_y)) + { + handled = true; + break; + } + } + + if (close_popups) + { + mPopups.remove(*popup_it); + popup->onTopLost(); + } + } + + return handled; +} + + +bool LLPopupView::handleMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = handleMouseEvent(boost::bind(&LLMouseHandler::handleMouseDown, _1, _2, _3, mask), view_visible_and_enabled, x, y, true); + if (!handled) + { + handled = LLPanel::handleMouseDown(x, y, mask); + } + return handled; +} + +bool LLPopupView::handleMouseUp(S32 x, S32 y, MASK mask) +{ + bool handled = handleMouseEvent(boost::bind(&LLMouseHandler::handleMouseUp, _1, _2, _3, mask), view_visible_and_enabled, x, y, false); + if (!handled) + { + handled = LLPanel::handleMouseUp(x, y, mask); + } + return handled; +} + +bool LLPopupView::handleMiddleMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = handleMouseEvent(boost::bind(&LLMouseHandler::handleMiddleMouseDown, _1, _2, _3, mask), view_visible_and_enabled, x, y, true); + if (!handled) + { + handled = LLPanel::handleMiddleMouseDown(x, y, mask); + } + return handled; +} + +bool LLPopupView::handleMiddleMouseUp(S32 x, S32 y, MASK mask) +{ + bool handled = handleMouseEvent(boost::bind(&LLMouseHandler::handleMiddleMouseUp, _1, _2, _3, mask), view_visible_and_enabled, x, y, false); + if (!handled) + { + handled = LLPanel::handleMiddleMouseUp(x, y, mask); + } + return handled; +} + +bool LLPopupView::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = handleMouseEvent(boost::bind(&LLMouseHandler::handleRightMouseDown, _1, _2, _3, mask), view_visible_and_enabled, x, y, true); + if (!handled) + { + handled = LLPanel::handleRightMouseDown(x, y, mask); + } + return handled; +} + +bool LLPopupView::handleRightMouseUp(S32 x, S32 y, MASK mask) +{ + bool handled = handleMouseEvent(boost::bind(&LLMouseHandler::handleRightMouseUp, _1, _2, _3, mask), view_visible_and_enabled, x, y, false); + if (!handled) + { + handled = LLPanel::handleRightMouseUp(x, y, mask); + } + return handled; +} + +bool LLPopupView::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + bool handled = handleMouseEvent(boost::bind(&LLMouseHandler::handleDoubleClick, _1, _2, _3, mask), view_visible_and_enabled, x, y, false); + if (!handled) + { + handled = LLPanel::handleDoubleClick(x, y, mask); + } + return handled; +} + +bool LLPopupView::handleHover(S32 x, S32 y, MASK mask) +{ + bool handled = handleMouseEvent(boost::bind(&LLMouseHandler::handleHover, _1, _2, _3, mask), view_visible_and_enabled, x, y, false); + if (!handled) + { + handled = LLPanel::handleHover(x, y, mask); + } + return handled; +} + +bool LLPopupView::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + bool handled = handleMouseEvent(boost::bind(&LLMouseHandler::handleScrollWheel, _1, _2, _3, clicks), view_visible_and_enabled, x, y, false); + if (!handled) + { + handled = LLPanel::handleScrollWheel(x, y, clicks); + } + return handled; +} + +bool LLPopupView::handleToolTip(S32 x, S32 y, MASK mask) +{ + bool handled = handleMouseEvent(boost::bind(&LLMouseHandler::handleToolTip, _1, _2, _3, mask), view_visible, x, y, false); + if (!handled) + { + handled = LLPanel::handleToolTip(x, y, mask); + } + return handled; +} + +void LLPopupView::addPopup(LLView* popup) +{ + if (popup) + { + mPopups.remove(popup->getHandle()); + mPopups.push_front(popup->getHandle()); + } +} + +void LLPopupView::removePopup(LLView* popup) +{ + if (popup) + { + mPopups.remove(popup->getHandle()); + popup->onTopLost(); + } +} + +void LLPopupView::clearPopups() +{ + while (!mPopups.empty()) + { + popup_list_t::iterator popup_it = mPopups.begin(); + LLView* popup = popup_it->get(); + // Remove before notifying in case it will cause removePopup + mPopups.erase(popup_it); + if (popup) + { + popup->onTopLost(); + } +} +} + diff --git a/indra/newview/llpopupview.h b/indra/newview/llpopupview.h index 3487e89768..4a88866185 100644 --- a/indra/newview/llpopupview.h +++ b/indra/newview/llpopupview.h @@ -1,61 +1,61 @@ -/** - * @file llpopupview.h - * @brief Holds transient popups - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPOPUPVIEW_H -#define LL_LLPOPUPVIEW_H - -#include "llpanel.h" - -class LLPopupView : public LLPanel -{ -public: - LLPopupView(const Params& p = LLPanel::Params()); - ~LLPopupView(); - - /*virtual*/ void draw(); - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMiddleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMiddleMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleRightMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleScrollWheel(S32 x, S32 y, S32 clicks); - /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); - - void addPopup(LLView* popup); - void removePopup(LLView* popup); - void clearPopups(); - - typedef std::list > popup_list_t; - popup_list_t getCurrentPopups() { return mPopups; } - -private: - bool handleMouseEvent(boost::function, boost::function, S32 x, S32 y, bool close_popups); - popup_list_t mPopups; -}; -#endif //LL_LLROOTVIEW_H +/** + * @file llpopupview.h + * @brief Holds transient popups + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPOPUPVIEW_H +#define LL_LLPOPUPVIEW_H + +#include "llpanel.h" + +class LLPopupView : public LLPanel +{ +public: + LLPopupView(const Params& p = LLPanel::Params()); + ~LLPopupView(); + + /*virtual*/ void draw(); + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMiddleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMiddleMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleRightMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleScrollWheel(S32 x, S32 y, S32 clicks); + /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); + + void addPopup(LLView* popup); + void removePopup(LLView* popup); + void clearPopups(); + + typedef std::list > popup_list_t; + popup_list_t getCurrentPopups() { return mPopups; } + +private: + bool handleMouseEvent(boost::function, boost::function, S32 x, S32 y, bool close_popups); + popup_list_t mPopups; +}; +#endif //LL_LLROOTVIEW_H diff --git a/indra/newview/llpresetsmanager.cpp b/indra/newview/llpresetsmanager.cpp index 794a03a146..afd58af056 100644 --- a/indra/newview/llpresetsmanager.cpp +++ b/indra/newview/llpresetsmanager.cpp @@ -1,592 +1,592 @@ -/** - * @file llpresetsmanager.cpp - * @brief Implementation for the LLPresetsManager class. - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include - -#include "llpresetsmanager.h" - -#include "lldiriterator.h" -#include "llfloater.h" -#include "llsdserialize.h" -#include "lltrans.h" -#include "lluictrlfactory.h" -#include "llviewercontrol.h" -#include "llfloaterpreference.h" -#include "llfloaterreg.h" -#include "llfeaturemanager.h" -#include "llagentcamera.h" -#include "llfile.h" - -LLPresetsManager::LLPresetsManager() -{ -} - -LLPresetsManager::~LLPresetsManager() -{ - mCameraChangedSignal.disconnect(); -} - -void LLPresetsManager::triggerChangeCameraSignal() -{ - mPresetListChangeCameraSignal(); -} - -void LLPresetsManager::triggerChangeSignal() -{ - mPresetListChangeSignal(); -} - -void LLPresetsManager::createMissingDefault(const std::string& subdirectory) -{ - if(gDirUtilp->getLindenUserDir().empty()) - { - return; - } - - if (PRESETS_CAMERA == subdirectory) - { - createCameraDefaultPresets(); - return; - } - - std::string default_file = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, PRESETS_DIR, - subdirectory, PRESETS_DEFAULT + ".xml"); - if (!gDirUtilp->fileExists(default_file)) - { - LL_INFOS() << "No default preset found -- creating one at " << default_file << LL_ENDL; - - // Write current settings as the default - savePreset(subdirectory, PRESETS_DEFAULT, true); - } - else - { - LL_DEBUGS() << "default preset exists; no-op" << LL_ENDL; - } -} - -void LLPresetsManager::createCameraDefaultPresets() -{ - bool is_default_created = createDefaultCameraPreset(PRESETS_REAR_VIEW); - is_default_created |= createDefaultCameraPreset(PRESETS_FRONT_VIEW); - is_default_created |= createDefaultCameraPreset(PRESETS_SIDE_VIEW); - - if (is_default_created) - { - triggerChangeCameraSignal(); - } -} - -void LLPresetsManager::startWatching(const std::string& subdirectory) -{ - if (PRESETS_CAMERA == subdirectory) - { - std::vector name_list; - getControlNames(name_list); - - for (std::vector::iterator it = name_list.begin(); it != name_list.end(); ++it) - { - std::string ctrl_name = *it; - if (gSavedSettings.controlExists(ctrl_name)) - { - LLPointer cntrl_ptr = gSavedSettings.getControl(ctrl_name); - if (cntrl_ptr.isNull()) - { - LL_WARNS("Init") << "Unable to set signal on global setting '" << ctrl_name - << "'" << LL_ENDL; - } - else - { - mCameraChangedSignal = cntrl_ptr->getCommitSignal()->connect(boost::bind(&settingChanged)); - } - } - } - } -} - -std::string LLPresetsManager::getPresetsDir(const std::string& subdirectory) -{ - std::string presets_path = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, PRESETS_DIR); - - LLFile::mkdir(presets_path); - - std::string dest_path = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, PRESETS_DIR, subdirectory); - if (!gDirUtilp->fileExists(dest_path)) - LLFile::mkdir(dest_path); - - return dest_path; -} - -void LLPresetsManager::loadPresetNamesFromDir(const std::string& subdirectory, preset_name_list_t& presets, EDefaultOptions default_option) -{ - bool IS_CAMERA = (PRESETS_CAMERA == subdirectory); - bool IS_GRAPHIC = (PRESETS_GRAPHIC == subdirectory); - - std::string dir = LLPresetsManager::getInstance()->getPresetsDir(subdirectory); - LL_INFOS("AppInit") << "Loading list of preset names from " << dir << LL_ENDL; - - mPresetNames.clear(); - - LLDirIterator dir_iter(dir, "*.xml"); - bool found = true; - while (found) - { - std::string file; - found = dir_iter.next(file); - - if (found) - { - std::string path = gDirUtilp->add(dir, file); - std::string name = LLURI::unescape(gDirUtilp->getBaseFileName(path, /*strip_exten = */ true)); - LL_DEBUGS() << " Found preset '" << name << "'" << LL_ENDL; - - if (IS_CAMERA) - { - if (isTemplateCameraPreset(name)) - { - continue; - } - if ((default_option == DEFAULT_HIDE) || (default_option == DEFAULT_BOTTOM)) - { - if (isDefaultCameraPreset(name)) - { - continue; - } - } - mPresetNames.push_back(name); - } - if (IS_GRAPHIC) - { - if (PRESETS_DEFAULT != name) - { - mPresetNames.push_back(name); - } - else - { - switch (default_option) - { - case DEFAULT_SHOW: - mPresetNames.push_back(LLTrans::getString(PRESETS_DEFAULT)); - break; - - case DEFAULT_TOP: - mPresetNames.push_front(LLTrans::getString(PRESETS_DEFAULT)); - break; - - case DEFAULT_HIDE: - default: - break; - } - } - } - } - } - - if (IS_CAMERA) - { - mPresetNames.sort(LLStringUtil::precedesDict); - if (default_option == DEFAULT_BOTTOM) - { - mPresetNames.push_back(PRESETS_FRONT_VIEW); - mPresetNames.push_back(PRESETS_REAR_VIEW); - mPresetNames.push_back(PRESETS_SIDE_VIEW); - } - } - - presets = mPresetNames; -} - -bool LLPresetsManager::mCameraDirty = false; -bool LLPresetsManager::mIgnoreChangedSignal = false; - -void LLPresetsManager::setCameraDirty(bool dirty) -{ - mCameraDirty = dirty; -} - -bool LLPresetsManager::isCameraDirty() -{ - return mCameraDirty; -} - -void LLPresetsManager::settingChanged() -{ - setCameraDirty(true); - - static LLCachedControl preset_camera_active(gSavedSettings, "PresetCameraActive", ""); - std::string preset_name = preset_camera_active; - if (!preset_name.empty() && !mIgnoreChangedSignal) - { - gSavedSettings.setString("PresetCameraActive", ""); - - // Hack call because this is a static routine - LLPresetsManager::getInstance()->triggerChangeCameraSignal(); - } -} - -void LLPresetsManager::getControlNames(std::vector& names) -{ - const std::vector camera_controls = boost::assign::list_of - // From panel_preferences_move.xml - ("CameraAngle") - ("CameraOffsetScale") - // From llagentcamera.cpp - ("CameraOffsetBuild") - ("TrackFocusObject") - ("CameraOffsetRearView") - ("FocusOffsetRearView") - ("AvatarSitRotation") - ; - names = camera_controls; -} - -bool LLPresetsManager::savePreset(const std::string& subdirectory, std::string name, bool createDefault) -{ - bool IS_CAMERA = (PRESETS_CAMERA == subdirectory); - bool IS_GRAPHIC = (PRESETS_GRAPHIC == subdirectory); - - if (LLTrans::getString(PRESETS_DEFAULT) == name) - { - name = PRESETS_DEFAULT; - } - if (!createDefault && name == PRESETS_DEFAULT) - { - LL_WARNS() << "Should not overwrite default" << LL_ENDL; - return false; - } - - if (isTemplateCameraPreset(name)) - { - LL_WARNS() << "Should not overwrite template presets" << LL_ENDL; - return false; - } - - bool saved = false; - std::vector name_list; - - if (IS_GRAPHIC) - { - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); - if (instance && !createDefault) - { - gSavedSettings.setString("PresetGraphicActive", name); - instance->getControlNames(name_list); - LL_DEBUGS() << "saving preset '" << name << "'; " << name_list.size() << " names" << LL_ENDL; - name_list.push_back("PresetGraphicActive"); - } - else - { - LL_WARNS("Presets") << "preferences floater instance not found" << LL_ENDL; - } - } - else if (IS_CAMERA) - { - name_list.clear(); - getControlNames(name_list); - name_list.push_back("PresetCameraActive"); - } - else - { - LL_ERRS() << "Invalid presets directory '" << subdirectory << "'" << LL_ENDL; - } - - // make an empty llsd - LLSD paramsData(LLSD::emptyMap()); - - // Create a default graphics preset from hw recommended settings - if (IS_GRAPHIC && createDefault && name == PRESETS_DEFAULT) - { - paramsData = LLFeatureManager::getInstance()->getRecommendedSettingsMap(); - if (gSavedSettings.getU32("RenderAvatarMaxComplexity") == 0) - { - // use the recommended setting as an initial one (MAINT-6435) - gSavedSettings.setU32("RenderAvatarMaxComplexity", paramsData["RenderAvatarMaxComplexity"]["Value"].asInteger()); - } - } - else - { - ECameraPreset new_camera_preset = (ECameraPreset)gSavedSettings.getU32("CameraPresetType"); - if (IS_CAMERA) - { - if (isDefaultCameraPreset(name)) - { - if (PRESETS_REAR_VIEW == name) - { - new_camera_preset = CAMERA_PRESET_REAR_VIEW; - } - else if (PRESETS_SIDE_VIEW == name) - { - new_camera_preset = CAMERA_PRESET_GROUP_VIEW; - } - else if (PRESETS_FRONT_VIEW == name) - { - new_camera_preset = CAMERA_PRESET_FRONT_VIEW; - } - } - else - { - new_camera_preset = CAMERA_PRESET_CUSTOM; - } - } - for (std::vector::iterator it = name_list.begin(); it != name_list.end(); ++it) - { - std::string ctrl_name = *it; - - LLControlVariable* ctrl = gSavedSettings.getControl(ctrl_name).get(); - if (ctrl) - { - std::string comment = ctrl->getComment(); - std::string type = LLControlGroup::typeEnumToString(ctrl->type()); - LLSD value = ctrl->getValue(); - - paramsData[ctrl_name]["Comment"] = comment; - paramsData[ctrl_name]["Persist"] = 1; - paramsData[ctrl_name]["Type"] = type; - paramsData[ctrl_name]["Value"] = value; - } - } - if (IS_CAMERA) - { - gSavedSettings.setU32("CameraPresetType", new_camera_preset); - } - } - - std::string pathName(getPresetsDir(subdirectory) + gDirUtilp->getDirDelimiter() + LLURI::escape(name) + ".xml"); - - // If the active preset name is the only thing in the list, don't save the list - if (paramsData.size() > 1) - { - // write to file - llofstream presetsXML(pathName.c_str()); - if (presetsXML.is_open()) - { - LLPointer formatter = new LLSDXMLFormatter(); - formatter->format(paramsData, presetsXML, LLSDFormatter::OPTIONS_PRETTY); - presetsXML.close(); - saved = true; - - LL_DEBUGS() << "saved preset '" << name << "'; " << paramsData.size() << " parameters" << LL_ENDL; - - if (IS_GRAPHIC) - { - gSavedSettings.setString("PresetGraphicActive", name); - // signal interested parties - triggerChangeSignal(); - } - - if (IS_CAMERA) - { - gSavedSettings.setString("PresetCameraActive", name); - setCameraDirty(false); - // signal interested parties - triggerChangeCameraSignal(); - } - } - else - { - LL_WARNS("Presets") << "Cannot open for output preset file " << pathName << LL_ENDL; - } - } - else - { - LL_INFOS() << "No settings available to be saved" << LL_ENDL; - } - - return saved; -} - -bool LLPresetsManager::setPresetNamesInComboBox(const std::string& subdirectory, LLComboBox* combo, EDefaultOptions default_option) -{ - bool sts = true; - - combo->clearRows(); - combo->setEnabled(true); - - std::list preset_names; - loadPresetNamesFromDir(subdirectory, preset_names, default_option); - - if (preset_names.begin() != preset_names.end()) - { - for (std::list::const_iterator it = preset_names.begin(); it != preset_names.end(); ++it) - { - const std::string& name = *it; - combo->add(name, name); - } - } - else - { - combo->setLabel(LLTrans::getString("preset_combo_label")); - combo->setEnabled(PRESETS_CAMERA != subdirectory); - sts = false; - } - - return sts; -} - -void LLPresetsManager::loadPreset(const std::string& subdirectory, std::string name) -{ - if (LLTrans::getString(PRESETS_DEFAULT) == name) - { - name = PRESETS_DEFAULT; - } - - std::string full_path(getPresetsDir(subdirectory) + gDirUtilp->getDirDelimiter() + LLURI::escape(name) + ".xml"); - - LL_DEBUGS() << "attempting to load preset '"< 0) - { - mIgnoreChangedSignal = false; - if(PRESETS_GRAPHIC == subdirectory) - { - gSavedSettings.setString("PresetGraphicActive", name); - - LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); - if (instance) - { - instance->refreshEnabledGraphics(); - } - triggerChangeSignal(); - } - if(PRESETS_CAMERA == subdirectory) - { - gSavedSettings.setString("PresetCameraActive", name); - triggerChangeCameraSignal(); - - //SL-20277 old preset files may contain settings that should be ignored when loading camera presets - if (appearance_camera_movement != (bool)gSavedSettings.getBOOL("AppearanceCameraMovement")) - { - gSavedSettings.setBOOL("AppearanceCameraMovement", appearance_camera_movement); - } - if (edit_camera_movement != (bool)gSavedSettings.getBOOL("EditCameraMovement")) - { - gSavedSettings.setBOOL("EditCameraMovement", edit_camera_movement); - } - } - } - else - { - mIgnoreChangedSignal = false; - LL_WARNS("Presets") << "failed to load preset '"<deleteFilesInDir(getPresetsDir(subdirectory), LLURI::escape(name) + ".xml") < 1) - { - LL_WARNS("Presets") << "Error removing preset " << name << " from disk" << LL_ENDL; - sts = false; - } - - // If you delete the preset that is currently marked as loaded then also indicate that no preset is loaded. - if(PRESETS_GRAPHIC == subdirectory) - { - if (gSavedSettings.getString("PresetGraphicActive") == name) - { - gSavedSettings.setString("PresetGraphicActive", ""); - } - // signal interested parties - triggerChangeSignal(); - } - - if(PRESETS_CAMERA == subdirectory) - { - if (gSavedSettings.getString("PresetCameraActive") == name) - { - gSavedSettings.setString("PresetCameraActive", ""); - } - // signal interested parties - triggerChangeCameraSignal(); - } - - return sts; -} - -bool LLPresetsManager::isDefaultCameraPreset(std::string preset_name) -{ - return (preset_name == PRESETS_REAR_VIEW || preset_name == PRESETS_SIDE_VIEW || preset_name == PRESETS_FRONT_VIEW); -} - -bool LLPresetsManager::isTemplateCameraPreset(std::string preset_name) -{ - return (preset_name == PRESETS_REAR || preset_name == PRESETS_SIDE || preset_name == PRESETS_FRONT); -} - -void LLPresetsManager::resetCameraPreset(std::string preset_name) -{ - if (isDefaultCameraPreset(preset_name)) - { - createDefaultCameraPreset(preset_name, true); - - if (gSavedSettings.getString("PresetCameraActive") == preset_name) - { - loadPreset(PRESETS_CAMERA, preset_name); - } - } -} - -bool LLPresetsManager::createDefaultCameraPreset(std::string preset_name, bool force_reset) -{ - std::string preset_file = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, PRESETS_DIR, - PRESETS_CAMERA, LLURI::escape(preset_name) + ".xml"); - if (!gDirUtilp->fileExists(preset_file) || force_reset) - { - std::string template_name = preset_name.substr(0, preset_name.size() - PRESETS_VIEW_SUFFIX.size()); - std::string default_template_file = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, PRESETS_CAMERA, template_name + ".xml"); - return LLFile::copy(default_template_file, preset_file); - } - return false; -} - -boost::signals2::connection LLPresetsManager::setPresetListChangeCameraCallback(const preset_list_signal_t::slot_type& cb) -{ - return mPresetListChangeCameraSignal.connect(cb); -} - -boost::signals2::connection LLPresetsManager::setPresetListChangeCallback(const preset_list_signal_t::slot_type& cb) -{ - return mPresetListChangeSignal.connect(cb); -} +/** + * @file llpresetsmanager.cpp + * @brief Implementation for the LLPresetsManager class. + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include + +#include "llpresetsmanager.h" + +#include "lldiriterator.h" +#include "llfloater.h" +#include "llsdserialize.h" +#include "lltrans.h" +#include "lluictrlfactory.h" +#include "llviewercontrol.h" +#include "llfloaterpreference.h" +#include "llfloaterreg.h" +#include "llfeaturemanager.h" +#include "llagentcamera.h" +#include "llfile.h" + +LLPresetsManager::LLPresetsManager() +{ +} + +LLPresetsManager::~LLPresetsManager() +{ + mCameraChangedSignal.disconnect(); +} + +void LLPresetsManager::triggerChangeCameraSignal() +{ + mPresetListChangeCameraSignal(); +} + +void LLPresetsManager::triggerChangeSignal() +{ + mPresetListChangeSignal(); +} + +void LLPresetsManager::createMissingDefault(const std::string& subdirectory) +{ + if(gDirUtilp->getLindenUserDir().empty()) + { + return; + } + + if (PRESETS_CAMERA == subdirectory) + { + createCameraDefaultPresets(); + return; + } + + std::string default_file = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, PRESETS_DIR, + subdirectory, PRESETS_DEFAULT + ".xml"); + if (!gDirUtilp->fileExists(default_file)) + { + LL_INFOS() << "No default preset found -- creating one at " << default_file << LL_ENDL; + + // Write current settings as the default + savePreset(subdirectory, PRESETS_DEFAULT, true); + } + else + { + LL_DEBUGS() << "default preset exists; no-op" << LL_ENDL; + } +} + +void LLPresetsManager::createCameraDefaultPresets() +{ + bool is_default_created = createDefaultCameraPreset(PRESETS_REAR_VIEW); + is_default_created |= createDefaultCameraPreset(PRESETS_FRONT_VIEW); + is_default_created |= createDefaultCameraPreset(PRESETS_SIDE_VIEW); + + if (is_default_created) + { + triggerChangeCameraSignal(); + } +} + +void LLPresetsManager::startWatching(const std::string& subdirectory) +{ + if (PRESETS_CAMERA == subdirectory) + { + std::vector name_list; + getControlNames(name_list); + + for (std::vector::iterator it = name_list.begin(); it != name_list.end(); ++it) + { + std::string ctrl_name = *it; + if (gSavedSettings.controlExists(ctrl_name)) + { + LLPointer cntrl_ptr = gSavedSettings.getControl(ctrl_name); + if (cntrl_ptr.isNull()) + { + LL_WARNS("Init") << "Unable to set signal on global setting '" << ctrl_name + << "'" << LL_ENDL; + } + else + { + mCameraChangedSignal = cntrl_ptr->getCommitSignal()->connect(boost::bind(&settingChanged)); + } + } + } + } +} + +std::string LLPresetsManager::getPresetsDir(const std::string& subdirectory) +{ + std::string presets_path = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, PRESETS_DIR); + + LLFile::mkdir(presets_path); + + std::string dest_path = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, PRESETS_DIR, subdirectory); + if (!gDirUtilp->fileExists(dest_path)) + LLFile::mkdir(dest_path); + + return dest_path; +} + +void LLPresetsManager::loadPresetNamesFromDir(const std::string& subdirectory, preset_name_list_t& presets, EDefaultOptions default_option) +{ + bool IS_CAMERA = (PRESETS_CAMERA == subdirectory); + bool IS_GRAPHIC = (PRESETS_GRAPHIC == subdirectory); + + std::string dir = LLPresetsManager::getInstance()->getPresetsDir(subdirectory); + LL_INFOS("AppInit") << "Loading list of preset names from " << dir << LL_ENDL; + + mPresetNames.clear(); + + LLDirIterator dir_iter(dir, "*.xml"); + bool found = true; + while (found) + { + std::string file; + found = dir_iter.next(file); + + if (found) + { + std::string path = gDirUtilp->add(dir, file); + std::string name = LLURI::unescape(gDirUtilp->getBaseFileName(path, /*strip_exten = */ true)); + LL_DEBUGS() << " Found preset '" << name << "'" << LL_ENDL; + + if (IS_CAMERA) + { + if (isTemplateCameraPreset(name)) + { + continue; + } + if ((default_option == DEFAULT_HIDE) || (default_option == DEFAULT_BOTTOM)) + { + if (isDefaultCameraPreset(name)) + { + continue; + } + } + mPresetNames.push_back(name); + } + if (IS_GRAPHIC) + { + if (PRESETS_DEFAULT != name) + { + mPresetNames.push_back(name); + } + else + { + switch (default_option) + { + case DEFAULT_SHOW: + mPresetNames.push_back(LLTrans::getString(PRESETS_DEFAULT)); + break; + + case DEFAULT_TOP: + mPresetNames.push_front(LLTrans::getString(PRESETS_DEFAULT)); + break; + + case DEFAULT_HIDE: + default: + break; + } + } + } + } + } + + if (IS_CAMERA) + { + mPresetNames.sort(LLStringUtil::precedesDict); + if (default_option == DEFAULT_BOTTOM) + { + mPresetNames.push_back(PRESETS_FRONT_VIEW); + mPresetNames.push_back(PRESETS_REAR_VIEW); + mPresetNames.push_back(PRESETS_SIDE_VIEW); + } + } + + presets = mPresetNames; +} + +bool LLPresetsManager::mCameraDirty = false; +bool LLPresetsManager::mIgnoreChangedSignal = false; + +void LLPresetsManager::setCameraDirty(bool dirty) +{ + mCameraDirty = dirty; +} + +bool LLPresetsManager::isCameraDirty() +{ + return mCameraDirty; +} + +void LLPresetsManager::settingChanged() +{ + setCameraDirty(true); + + static LLCachedControl preset_camera_active(gSavedSettings, "PresetCameraActive", ""); + std::string preset_name = preset_camera_active; + if (!preset_name.empty() && !mIgnoreChangedSignal) + { + gSavedSettings.setString("PresetCameraActive", ""); + + // Hack call because this is a static routine + LLPresetsManager::getInstance()->triggerChangeCameraSignal(); + } +} + +void LLPresetsManager::getControlNames(std::vector& names) +{ + const std::vector camera_controls = boost::assign::list_of + // From panel_preferences_move.xml + ("CameraAngle") + ("CameraOffsetScale") + // From llagentcamera.cpp + ("CameraOffsetBuild") + ("TrackFocusObject") + ("CameraOffsetRearView") + ("FocusOffsetRearView") + ("AvatarSitRotation") + ; + names = camera_controls; +} + +bool LLPresetsManager::savePreset(const std::string& subdirectory, std::string name, bool createDefault) +{ + bool IS_CAMERA = (PRESETS_CAMERA == subdirectory); + bool IS_GRAPHIC = (PRESETS_GRAPHIC == subdirectory); + + if (LLTrans::getString(PRESETS_DEFAULT) == name) + { + name = PRESETS_DEFAULT; + } + if (!createDefault && name == PRESETS_DEFAULT) + { + LL_WARNS() << "Should not overwrite default" << LL_ENDL; + return false; + } + + if (isTemplateCameraPreset(name)) + { + LL_WARNS() << "Should not overwrite template presets" << LL_ENDL; + return false; + } + + bool saved = false; + std::vector name_list; + + if (IS_GRAPHIC) + { + LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); + if (instance && !createDefault) + { + gSavedSettings.setString("PresetGraphicActive", name); + instance->getControlNames(name_list); + LL_DEBUGS() << "saving preset '" << name << "'; " << name_list.size() << " names" << LL_ENDL; + name_list.push_back("PresetGraphicActive"); + } + else + { + LL_WARNS("Presets") << "preferences floater instance not found" << LL_ENDL; + } + } + else if (IS_CAMERA) + { + name_list.clear(); + getControlNames(name_list); + name_list.push_back("PresetCameraActive"); + } + else + { + LL_ERRS() << "Invalid presets directory '" << subdirectory << "'" << LL_ENDL; + } + + // make an empty llsd + LLSD paramsData(LLSD::emptyMap()); + + // Create a default graphics preset from hw recommended settings + if (IS_GRAPHIC && createDefault && name == PRESETS_DEFAULT) + { + paramsData = LLFeatureManager::getInstance()->getRecommendedSettingsMap(); + if (gSavedSettings.getU32("RenderAvatarMaxComplexity") == 0) + { + // use the recommended setting as an initial one (MAINT-6435) + gSavedSettings.setU32("RenderAvatarMaxComplexity", paramsData["RenderAvatarMaxComplexity"]["Value"].asInteger()); + } + } + else + { + ECameraPreset new_camera_preset = (ECameraPreset)gSavedSettings.getU32("CameraPresetType"); + if (IS_CAMERA) + { + if (isDefaultCameraPreset(name)) + { + if (PRESETS_REAR_VIEW == name) + { + new_camera_preset = CAMERA_PRESET_REAR_VIEW; + } + else if (PRESETS_SIDE_VIEW == name) + { + new_camera_preset = CAMERA_PRESET_GROUP_VIEW; + } + else if (PRESETS_FRONT_VIEW == name) + { + new_camera_preset = CAMERA_PRESET_FRONT_VIEW; + } + } + else + { + new_camera_preset = CAMERA_PRESET_CUSTOM; + } + } + for (std::vector::iterator it = name_list.begin(); it != name_list.end(); ++it) + { + std::string ctrl_name = *it; + + LLControlVariable* ctrl = gSavedSettings.getControl(ctrl_name).get(); + if (ctrl) + { + std::string comment = ctrl->getComment(); + std::string type = LLControlGroup::typeEnumToString(ctrl->type()); + LLSD value = ctrl->getValue(); + + paramsData[ctrl_name]["Comment"] = comment; + paramsData[ctrl_name]["Persist"] = 1; + paramsData[ctrl_name]["Type"] = type; + paramsData[ctrl_name]["Value"] = value; + } + } + if (IS_CAMERA) + { + gSavedSettings.setU32("CameraPresetType", new_camera_preset); + } + } + + std::string pathName(getPresetsDir(subdirectory) + gDirUtilp->getDirDelimiter() + LLURI::escape(name) + ".xml"); + + // If the active preset name is the only thing in the list, don't save the list + if (paramsData.size() > 1) + { + // write to file + llofstream presetsXML(pathName.c_str()); + if (presetsXML.is_open()) + { + LLPointer formatter = new LLSDXMLFormatter(); + formatter->format(paramsData, presetsXML, LLSDFormatter::OPTIONS_PRETTY); + presetsXML.close(); + saved = true; + + LL_DEBUGS() << "saved preset '" << name << "'; " << paramsData.size() << " parameters" << LL_ENDL; + + if (IS_GRAPHIC) + { + gSavedSettings.setString("PresetGraphicActive", name); + // signal interested parties + triggerChangeSignal(); + } + + if (IS_CAMERA) + { + gSavedSettings.setString("PresetCameraActive", name); + setCameraDirty(false); + // signal interested parties + triggerChangeCameraSignal(); + } + } + else + { + LL_WARNS("Presets") << "Cannot open for output preset file " << pathName << LL_ENDL; + } + } + else + { + LL_INFOS() << "No settings available to be saved" << LL_ENDL; + } + + return saved; +} + +bool LLPresetsManager::setPresetNamesInComboBox(const std::string& subdirectory, LLComboBox* combo, EDefaultOptions default_option) +{ + bool sts = true; + + combo->clearRows(); + combo->setEnabled(true); + + std::list preset_names; + loadPresetNamesFromDir(subdirectory, preset_names, default_option); + + if (preset_names.begin() != preset_names.end()) + { + for (std::list::const_iterator it = preset_names.begin(); it != preset_names.end(); ++it) + { + const std::string& name = *it; + combo->add(name, name); + } + } + else + { + combo->setLabel(LLTrans::getString("preset_combo_label")); + combo->setEnabled(PRESETS_CAMERA != subdirectory); + sts = false; + } + + return sts; +} + +void LLPresetsManager::loadPreset(const std::string& subdirectory, std::string name) +{ + if (LLTrans::getString(PRESETS_DEFAULT) == name) + { + name = PRESETS_DEFAULT; + } + + std::string full_path(getPresetsDir(subdirectory) + gDirUtilp->getDirDelimiter() + LLURI::escape(name) + ".xml"); + + LL_DEBUGS() << "attempting to load preset '"< 0) + { + mIgnoreChangedSignal = false; + if(PRESETS_GRAPHIC == subdirectory) + { + gSavedSettings.setString("PresetGraphicActive", name); + + LLFloaterPreference* instance = LLFloaterReg::findTypedInstance("preferences"); + if (instance) + { + instance->refreshEnabledGraphics(); + } + triggerChangeSignal(); + } + if(PRESETS_CAMERA == subdirectory) + { + gSavedSettings.setString("PresetCameraActive", name); + triggerChangeCameraSignal(); + + //SL-20277 old preset files may contain settings that should be ignored when loading camera presets + if (appearance_camera_movement != (bool)gSavedSettings.getBOOL("AppearanceCameraMovement")) + { + gSavedSettings.setBOOL("AppearanceCameraMovement", appearance_camera_movement); + } + if (edit_camera_movement != (bool)gSavedSettings.getBOOL("EditCameraMovement")) + { + gSavedSettings.setBOOL("EditCameraMovement", edit_camera_movement); + } + } + } + else + { + mIgnoreChangedSignal = false; + LL_WARNS("Presets") << "failed to load preset '"<deleteFilesInDir(getPresetsDir(subdirectory), LLURI::escape(name) + ".xml") < 1) + { + LL_WARNS("Presets") << "Error removing preset " << name << " from disk" << LL_ENDL; + sts = false; + } + + // If you delete the preset that is currently marked as loaded then also indicate that no preset is loaded. + if(PRESETS_GRAPHIC == subdirectory) + { + if (gSavedSettings.getString("PresetGraphicActive") == name) + { + gSavedSettings.setString("PresetGraphicActive", ""); + } + // signal interested parties + triggerChangeSignal(); + } + + if(PRESETS_CAMERA == subdirectory) + { + if (gSavedSettings.getString("PresetCameraActive") == name) + { + gSavedSettings.setString("PresetCameraActive", ""); + } + // signal interested parties + triggerChangeCameraSignal(); + } + + return sts; +} + +bool LLPresetsManager::isDefaultCameraPreset(std::string preset_name) +{ + return (preset_name == PRESETS_REAR_VIEW || preset_name == PRESETS_SIDE_VIEW || preset_name == PRESETS_FRONT_VIEW); +} + +bool LLPresetsManager::isTemplateCameraPreset(std::string preset_name) +{ + return (preset_name == PRESETS_REAR || preset_name == PRESETS_SIDE || preset_name == PRESETS_FRONT); +} + +void LLPresetsManager::resetCameraPreset(std::string preset_name) +{ + if (isDefaultCameraPreset(preset_name)) + { + createDefaultCameraPreset(preset_name, true); + + if (gSavedSettings.getString("PresetCameraActive") == preset_name) + { + loadPreset(PRESETS_CAMERA, preset_name); + } + } +} + +bool LLPresetsManager::createDefaultCameraPreset(std::string preset_name, bool force_reset) +{ + std::string preset_file = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, PRESETS_DIR, + PRESETS_CAMERA, LLURI::escape(preset_name) + ".xml"); + if (!gDirUtilp->fileExists(preset_file) || force_reset) + { + std::string template_name = preset_name.substr(0, preset_name.size() - PRESETS_VIEW_SUFFIX.size()); + std::string default_template_file = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, PRESETS_CAMERA, template_name + ".xml"); + return LLFile::copy(default_template_file, preset_file); + } + return false; +} + +boost::signals2::connection LLPresetsManager::setPresetListChangeCameraCallback(const preset_list_signal_t::slot_type& cb) +{ + return mPresetListChangeCameraSignal.connect(cb); +} + +boost::signals2::connection LLPresetsManager::setPresetListChangeCallback(const preset_list_signal_t::slot_type& cb) +{ + return mPresetListChangeSignal.connect(cb); +} diff --git a/indra/newview/llpreview.cpp b/indra/newview/llpreview.cpp index b44c015d87..3f3e1766b4 100644 --- a/indra/newview/llpreview.cpp +++ b/indra/newview/llpreview.cpp @@ -1,568 +1,568 @@ -/** - * @file llpreview.cpp - * @brief LLPreview class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpreview.h" - -#include "lllineeditor.h" -#include "llinventorydefines.h" -#include "llinventorymodel.h" -#include "llresmgr.h" -#include "lltextbox.h" -#include "llfloaterreg.h" -#include "llfocusmgr.h" -#include "lltooldraganddrop.h" -#include "llradiogroup.h" -#include "llassetstorage.h" -#include "llviewerassettype.h" -#include "llviewermessage.h" -#include "llviewerobject.h" -#include "llviewerobjectlist.h" -#include "lldbstrings.h" -#include "llagent.h" -#include "llvoavatarself.h" -#include "llselectmgr.h" -#include "llviewerinventory.h" -#include "llviewerwindow.h" -#include "lltrans.h" -#include "roles_constants.h" - -// Constants - -LLPreview::LLPreview(const LLSD& key) -: LLFloater(key), - mItemUUID(key.has("itemid") ? key.get("itemid").asUUID() : key.asUUID()), - mObjectUUID(), // set later by setObjectID() - mCopyToInvBtn( NULL ), - mForceClose(false), - mUserResized(false), - mCloseAfterSave(false), - mAssetStatus(PREVIEW_ASSET_UNLOADED), - mDirty(true), - mSaveDialogShown(false) -{ - mAuxItem = new LLInventoryItem; - // don't necessarily steal focus on creation -- sometimes these guys pop up without user action - setAutoFocus(false); - - gInventory.addObserver(this); - - refreshFromItem(); -} - -bool LLPreview::postBuild() -{ - refreshFromItem(); - return true; -} - -LLPreview::~LLPreview() -{ - gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() - gInventory.removeObserver(this); -} - -void LLPreview::setObjectID(const LLUUID& object_id) -{ - mObjectUUID = object_id; - if (getAssetStatus() == PREVIEW_ASSET_UNLOADED) - { - loadAsset(); - } - refreshFromItem(); -} - -void LLPreview::setItem( LLInventoryItem* item ) -{ - mItem = item; - if (mItem && getAssetStatus() == PREVIEW_ASSET_UNLOADED) - { - loadAsset(); - } - refreshFromItem(); -} - -const LLInventoryItem *LLPreview::getItem() const -{ - const LLInventoryItem *item = NULL; - if (mItem.notNull()) - { - item = mItem; - } - else if (mObjectUUID.isNull()) - { - if (mItemUUID.notNull()) - { - // it's an inventory item, so get the item. - item = gInventory.getItem(mItemUUID); - } - } - else - { - // it's an object's inventory item. - LLViewerObject* object = gObjectList.findObject(mObjectUUID); - if(object) - { - item = dynamic_cast(object->getInventoryObject(mItemUUID)); - } - } - return item; -} - -// Sub-classes should override this function if they allow editing -void LLPreview::onCommit() -{ - const LLViewerInventoryItem *item = dynamic_cast(getItem()); - if(item) - { - if (!item->isFinished()) - { - // We are attempting to save an item that was never loaded - LL_WARNS() << "LLPreview::onCommit() called with mIsComplete == false" - << " Type: " << item->getType() - << " ID: " << item->getUUID() - << LL_ENDL; - return; - } - - LLPointer new_item = new LLViewerInventoryItem(item); - new_item->setDescription(getChild("desc")->getValue().asString()); - - std::string new_name = getChild("name")->getValue().asString(); - if ( (new_item->getName() != new_name) && !new_name.empty()) - { - new_item->rename(getChild("name")->getValue().asString()); - } - - if(mObjectUUID.notNull()) - { - // must be in an object - LLViewerObject* object = gObjectList.findObject(mObjectUUID); - if(object) - { - object->updateInventory( - new_item, - TASK_INVENTORY_ITEM_KEY, - false); - } - } - else if(item->getPermissions().getOwner() == gAgent.getID()) - { - new_item->updateServer(false); - gInventory.updateItem(new_item); - gInventory.notifyObservers(); - - // If the item is an attachment that is currently being worn, - // update the object itself. - if( item->getType() == LLAssetType::AT_OBJECT ) - { - if (isAgentAvatarValid()) - { - LLViewerObject* obj = gAgentAvatarp->getWornAttachment( item->getUUID() ); - if( obj ) - { - LLSelectMgr::getInstance()->deselectAll(); - LLSelectMgr::getInstance()->addAsIndividual( obj, SELECT_ALL_TES, false ); - LLSelectMgr::getInstance()->selectionSetObjectDescription( getChild("desc")->getValue().asString() ); - - LLSelectMgr::getInstance()->deselectAll(); - } - } - } - } - } -} - -void LLPreview::changed(U32 mask) -{ - mDirty = true; -} - -void LLPreview::setNotecardInfo(const LLUUID& notecard_inv_id, - const LLUUID& object_id) -{ - mNotecardInventoryID = notecard_inv_id; - mNotecardObjectID = object_id; -} - -void LLPreview::draw() -{ - LLFloater::draw(); - if (mDirty) - { - mDirty = false; - refreshFromItem(); - } -} - -void LLPreview::refreshFromItem() -{ - const LLInventoryItem* item = getItem(); - if (!item) - { - return; - } - if (hasString("Title")) - { - LLStringUtil::format_map_t args; - args["[NAME]"] = item->getName(); - LLUIString title = getString("Title", args); - setTitle(title.getString()); - } - getChild("desc")->setValue(item->getDescription()); - - getChildView("desc")->setEnabled(canModify(mObjectUUID, item)); -} - -// static -bool LLPreview::canModify(const LLUUID taskUUID, const LLInventoryItem* item) -{ - const LLViewerObject* object = nullptr; - if (taskUUID.notNull()) - { - object = gObjectList.findObject(taskUUID); - } - - return canModify(object, item); -} - -// static -bool LLPreview::canModify(const LLViewerObject* object, const LLInventoryItem* item) -{ - if (object && !object->permModify()) - { - // No permission to edit in-world inventory - return false; - } - - return item && gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), GP_OBJECT_MANIPULATE); -} - -// static -void LLPreview::onText(LLUICtrl*, void* userdata) -{ - LLPreview* self = (LLPreview*) userdata; - self->onCommit(); -} - -// static -void LLPreview::onRadio(LLUICtrl*, void* userdata) -{ - LLPreview* self = (LLPreview*) userdata; - self->onCommit(); -} - -// static -void LLPreview::hide(const LLUUID& item_uuid, bool no_saving /* = false */ ) -{ - LLFloater* floater = LLFloaterReg::findInstance("preview", LLSD(item_uuid)); - if (!floater) floater = LLFloaterReg::findInstance("preview_avatar", LLSD(item_uuid)); - - LLPreview* preview = dynamic_cast(floater); - if (preview) - { - if ( no_saving ) - { - preview->mForceClose = true; - } - preview->closeFloater(); - } -} - -// static -void LLPreview::dirty(const LLUUID& item_uuid) -{ - LLFloater* floater = LLFloaterReg::findInstance("preview", LLSD(item_uuid)); - if (!floater) floater = LLFloaterReg::findInstance("preview_avatar", LLSD(item_uuid)); - - LLPreview* preview = dynamic_cast(floater); - if(preview) - { - preview->mDirty = true; - } -} - -bool LLPreview::handleMouseDown(S32 x, S32 y, MASK mask) -{ - if(mClientRect.pointInRect(x, y)) - { - // No handler needed for focus lost since this class has no - // state that depends on it. - bringToFront(x, y); - gFocusMgr.setMouseCapture(this); - S32 screen_x; - S32 screen_y; - localPointToScreen(x, y, &screen_x, &screen_y ); - LLToolDragAndDrop::getInstance()->setDragStart(screen_x, screen_y); - return true; - } - return LLFloater::handleMouseDown(x, y, mask); -} - -bool LLPreview::handleMouseUp(S32 x, S32 y, MASK mask) -{ - if(hasMouseCapture()) - { - gFocusMgr.setMouseCapture(NULL); - return true; - } - return LLFloater::handleMouseUp(x, y, mask); -} - -bool LLPreview::handleHover(S32 x, S32 y, MASK mask) -{ - if(hasMouseCapture()) - { - S32 screen_x; - S32 screen_y; - const LLInventoryItem *item = getItem(); - - localPointToScreen(x, y, &screen_x, &screen_y ); - if(item - && item->getPermissions().allowCopyBy(gAgent.getID(), - gAgent.getGroupID()) - && LLToolDragAndDrop::getInstance()->isOverThreshold(screen_x, screen_y)) - { - EDragAndDropType type; - type = LLViewerAssetType::lookupDragAndDropType(item->getType()); - LLToolDragAndDrop::ESource src = LLToolDragAndDrop::SOURCE_LIBRARY; - if(!mObjectUUID.isNull()) - { - src = LLToolDragAndDrop::SOURCE_WORLD; - } - else if(item->getPermissions().getOwner() == gAgent.getID()) - { - src = LLToolDragAndDrop::SOURCE_AGENT; - } - LLToolDragAndDrop::getInstance()->beginDrag(type, - item->getUUID(), - src, - mObjectUUID); - return LLToolDragAndDrop::getInstance()->handleHover(x, y, mask ); - } - } - return LLFloater::handleHover(x,y,mask); -} - -void LLPreview::onOpen(const LLSD& key) -{ - if (!getFloaterHost() && !getHost() && getAssetStatus() == PREVIEW_ASSET_UNLOADED) - { - loadAsset(); - } -} - -void LLPreview::setAuxItem( const LLInventoryItem* item ) -{ - if ( mAuxItem ) - mAuxItem->copyItem(item); -} - -// static -void LLPreview::onBtnCopyToInv(void* userdata) -{ - LLPreview* self = (LLPreview*) userdata; - LLInventoryItem *item = self->mAuxItem; - - if(item && item->getUUID().notNull()) - { - // Copy to inventory - if (self->mNotecardInventoryID.notNull()) - { - copy_inventory_from_notecard(LLUUID::null, - self->mNotecardObjectID, - self->mNotecardInventoryID, - item); - } - else if (self->mObjectUUID.notNull()) - { - // item is in in-world inventory - LLViewerObject* object = gObjectList.findObject(self->mObjectUUID); - LLPermissions perm(item->getPermissions()); - if(object - &&(perm.allowCopyBy(gAgent.getID(), gAgent.getGroupID()) - && perm.allowTransferTo(gAgent.getID()))) - { - // copy to default folder - set_dad_inventory_item(item, LLUUID::null); - object->moveInventory(LLUUID::null, item->getUUID()); - } - } - else - { - LLPointer cb = NULL; - copy_inventory_item( - gAgent.getID(), - item->getPermissions().getOwner(), - item->getUUID(), - LLUUID::null, - std::string(), - cb); - } - } - self->closeFloater(); -} - -// static -void LLPreview::onKeepBtn(void* data) -{ - LLPreview* self = (LLPreview*)data; - self->closeFloater(); -} - -// static -void LLPreview::onDiscardBtn(void* data) -{ - LLPreview* self = (LLPreview*)data; - - const LLInventoryItem* item = self->getItem(); - if (!item) return; - - self->mForceClose = true; - self->closeFloater(); - - // Move the item to the trash - const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - if (item->getParentUUID() != trash_id) - { - LLInventoryModel::update_list_t update; - LLInventoryModel::LLCategoryUpdate old_folder(item->getParentUUID(),-1); - update.push_back(old_folder); - LLInventoryModel::LLCategoryUpdate new_folder(trash_id, 1); - update.push_back(new_folder); - gInventory.accountForUpdate(update); - - LLPointer new_item = new LLViewerInventoryItem(item); - new_item->setParent(trash_id); - // no need to restamp it though it's a move into trash because - // it's a brand new item already. - new_item->updateParentOnServer(false); - gInventory.updateItem(new_item); - gInventory.notifyObservers(); - } -} - -void LLPreview::handleReshape(const LLRect& new_rect, bool by_user) -{ - if(by_user - && (new_rect.getWidth() != getRect().getWidth() || new_rect.getHeight() != getRect().getHeight())) - { - userResized(); - } - LLFloater::handleReshape(new_rect, by_user); -} - -// -// LLMultiPreview -// - -LLMultiPreview::LLMultiPreview() - : LLMultiFloater(LLSD()) -{ - // start with a rect in the top-left corner ; will get resized - LLRect rect; - rect.setLeftTopAndSize(0, gViewerWindow->getWindowHeightScaled(), 200, 400); - setRect(rect); - - LLFloater* last_floater = LLFloaterReg::getLastFloaterInGroup("preview"); - if (last_floater) - { - stackWith(*last_floater); - } - setTitle(LLTrans::getString("MultiPreviewTitle")); - buildTabContainer(); - setCanResize(true); -} - -void LLMultiPreview::onOpen(const LLSD& key) -{ - // Floater could be something else than LLPreview, eg LLFloaterProfile. - LLPreview* frontmost_preview = dynamic_cast(mTabContainer->getCurrentPanel()); - - if (frontmost_preview && frontmost_preview->getAssetStatus() == LLPreview::PREVIEW_ASSET_UNLOADED) - { - frontmost_preview->loadAsset(); - } - LLMultiFloater::onOpen(key); -} - - -void LLMultiPreview::handleReshape(const LLRect& new_rect, bool by_user) -{ - if(new_rect.getWidth() != getRect().getWidth() || new_rect.getHeight() != getRect().getHeight()) - { - // Floater could be something else than LLPreview, eg LLFloaterProfile. - LLPreview* frontmost_preview = dynamic_cast(mTabContainer->getCurrentPanel()); - - if (frontmost_preview) - { - frontmost_preview->userResized(); - } - } - LLFloater::handleReshape(new_rect, by_user); -} - - -void LLMultiPreview::tabOpen(LLFloater* opened_floater, bool from_click) -{ - // Floater could be something else than LLPreview, eg LLFloaterProfile. - LLPreview* opened_preview = dynamic_cast(opened_floater); - - if (opened_preview && opened_preview->getAssetStatus() == LLPreview::PREVIEW_ASSET_UNLOADED) - { - opened_preview->loadAsset(); - } -} - - -void LLPreview::setAssetId(const LLUUID& asset_id) -{ - const LLViewerInventoryItem* item = dynamic_cast(getItem()); - if(NULL == item) - { - return; - } - - if(mObjectUUID.isNull()) - { - // Update avatar inventory asset_id. - LLPointer new_item = new LLViewerInventoryItem(item); - new_item->setAssetUUID(asset_id); - gInventory.updateItem(new_item); - gInventory.notifyObservers(); - } - else - { - // Update object inventory asset_id. - LLViewerObject* object = gObjectList.findObject(mObjectUUID); - if(NULL == object) - { - return; - } - object->updateViewerInventoryAsset(item, asset_id); - } -} +/** + * @file llpreview.cpp + * @brief LLPreview class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpreview.h" + +#include "lllineeditor.h" +#include "llinventorydefines.h" +#include "llinventorymodel.h" +#include "llresmgr.h" +#include "lltextbox.h" +#include "llfloaterreg.h" +#include "llfocusmgr.h" +#include "lltooldraganddrop.h" +#include "llradiogroup.h" +#include "llassetstorage.h" +#include "llviewerassettype.h" +#include "llviewermessage.h" +#include "llviewerobject.h" +#include "llviewerobjectlist.h" +#include "lldbstrings.h" +#include "llagent.h" +#include "llvoavatarself.h" +#include "llselectmgr.h" +#include "llviewerinventory.h" +#include "llviewerwindow.h" +#include "lltrans.h" +#include "roles_constants.h" + +// Constants + +LLPreview::LLPreview(const LLSD& key) +: LLFloater(key), + mItemUUID(key.has("itemid") ? key.get("itemid").asUUID() : key.asUUID()), + mObjectUUID(), // set later by setObjectID() + mCopyToInvBtn( NULL ), + mForceClose(false), + mUserResized(false), + mCloseAfterSave(false), + mAssetStatus(PREVIEW_ASSET_UNLOADED), + mDirty(true), + mSaveDialogShown(false) +{ + mAuxItem = new LLInventoryItem; + // don't necessarily steal focus on creation -- sometimes these guys pop up without user action + setAutoFocus(false); + + gInventory.addObserver(this); + + refreshFromItem(); +} + +bool LLPreview::postBuild() +{ + refreshFromItem(); + return true; +} + +LLPreview::~LLPreview() +{ + gFocusMgr.releaseFocusIfNeeded( this ); // calls onCommit() + gInventory.removeObserver(this); +} + +void LLPreview::setObjectID(const LLUUID& object_id) +{ + mObjectUUID = object_id; + if (getAssetStatus() == PREVIEW_ASSET_UNLOADED) + { + loadAsset(); + } + refreshFromItem(); +} + +void LLPreview::setItem( LLInventoryItem* item ) +{ + mItem = item; + if (mItem && getAssetStatus() == PREVIEW_ASSET_UNLOADED) + { + loadAsset(); + } + refreshFromItem(); +} + +const LLInventoryItem *LLPreview::getItem() const +{ + const LLInventoryItem *item = NULL; + if (mItem.notNull()) + { + item = mItem; + } + else if (mObjectUUID.isNull()) + { + if (mItemUUID.notNull()) + { + // it's an inventory item, so get the item. + item = gInventory.getItem(mItemUUID); + } + } + else + { + // it's an object's inventory item. + LLViewerObject* object = gObjectList.findObject(mObjectUUID); + if(object) + { + item = dynamic_cast(object->getInventoryObject(mItemUUID)); + } + } + return item; +} + +// Sub-classes should override this function if they allow editing +void LLPreview::onCommit() +{ + const LLViewerInventoryItem *item = dynamic_cast(getItem()); + if(item) + { + if (!item->isFinished()) + { + // We are attempting to save an item that was never loaded + LL_WARNS() << "LLPreview::onCommit() called with mIsComplete == false" + << " Type: " << item->getType() + << " ID: " << item->getUUID() + << LL_ENDL; + return; + } + + LLPointer new_item = new LLViewerInventoryItem(item); + new_item->setDescription(getChild("desc")->getValue().asString()); + + std::string new_name = getChild("name")->getValue().asString(); + if ( (new_item->getName() != new_name) && !new_name.empty()) + { + new_item->rename(getChild("name")->getValue().asString()); + } + + if(mObjectUUID.notNull()) + { + // must be in an object + LLViewerObject* object = gObjectList.findObject(mObjectUUID); + if(object) + { + object->updateInventory( + new_item, + TASK_INVENTORY_ITEM_KEY, + false); + } + } + else if(item->getPermissions().getOwner() == gAgent.getID()) + { + new_item->updateServer(false); + gInventory.updateItem(new_item); + gInventory.notifyObservers(); + + // If the item is an attachment that is currently being worn, + // update the object itself. + if( item->getType() == LLAssetType::AT_OBJECT ) + { + if (isAgentAvatarValid()) + { + LLViewerObject* obj = gAgentAvatarp->getWornAttachment( item->getUUID() ); + if( obj ) + { + LLSelectMgr::getInstance()->deselectAll(); + LLSelectMgr::getInstance()->addAsIndividual( obj, SELECT_ALL_TES, false ); + LLSelectMgr::getInstance()->selectionSetObjectDescription( getChild("desc")->getValue().asString() ); + + LLSelectMgr::getInstance()->deselectAll(); + } + } + } + } + } +} + +void LLPreview::changed(U32 mask) +{ + mDirty = true; +} + +void LLPreview::setNotecardInfo(const LLUUID& notecard_inv_id, + const LLUUID& object_id) +{ + mNotecardInventoryID = notecard_inv_id; + mNotecardObjectID = object_id; +} + +void LLPreview::draw() +{ + LLFloater::draw(); + if (mDirty) + { + mDirty = false; + refreshFromItem(); + } +} + +void LLPreview::refreshFromItem() +{ + const LLInventoryItem* item = getItem(); + if (!item) + { + return; + } + if (hasString("Title")) + { + LLStringUtil::format_map_t args; + args["[NAME]"] = item->getName(); + LLUIString title = getString("Title", args); + setTitle(title.getString()); + } + getChild("desc")->setValue(item->getDescription()); + + getChildView("desc")->setEnabled(canModify(mObjectUUID, item)); +} + +// static +bool LLPreview::canModify(const LLUUID taskUUID, const LLInventoryItem* item) +{ + const LLViewerObject* object = nullptr; + if (taskUUID.notNull()) + { + object = gObjectList.findObject(taskUUID); + } + + return canModify(object, item); +} + +// static +bool LLPreview::canModify(const LLViewerObject* object, const LLInventoryItem* item) +{ + if (object && !object->permModify()) + { + // No permission to edit in-world inventory + return false; + } + + return item && gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), GP_OBJECT_MANIPULATE); +} + +// static +void LLPreview::onText(LLUICtrl*, void* userdata) +{ + LLPreview* self = (LLPreview*) userdata; + self->onCommit(); +} + +// static +void LLPreview::onRadio(LLUICtrl*, void* userdata) +{ + LLPreview* self = (LLPreview*) userdata; + self->onCommit(); +} + +// static +void LLPreview::hide(const LLUUID& item_uuid, bool no_saving /* = false */ ) +{ + LLFloater* floater = LLFloaterReg::findInstance("preview", LLSD(item_uuid)); + if (!floater) floater = LLFloaterReg::findInstance("preview_avatar", LLSD(item_uuid)); + + LLPreview* preview = dynamic_cast(floater); + if (preview) + { + if ( no_saving ) + { + preview->mForceClose = true; + } + preview->closeFloater(); + } +} + +// static +void LLPreview::dirty(const LLUUID& item_uuid) +{ + LLFloater* floater = LLFloaterReg::findInstance("preview", LLSD(item_uuid)); + if (!floater) floater = LLFloaterReg::findInstance("preview_avatar", LLSD(item_uuid)); + + LLPreview* preview = dynamic_cast(floater); + if(preview) + { + preview->mDirty = true; + } +} + +bool LLPreview::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if(mClientRect.pointInRect(x, y)) + { + // No handler needed for focus lost since this class has no + // state that depends on it. + bringToFront(x, y); + gFocusMgr.setMouseCapture(this); + S32 screen_x; + S32 screen_y; + localPointToScreen(x, y, &screen_x, &screen_y ); + LLToolDragAndDrop::getInstance()->setDragStart(screen_x, screen_y); + return true; + } + return LLFloater::handleMouseDown(x, y, mask); +} + +bool LLPreview::handleMouseUp(S32 x, S32 y, MASK mask) +{ + if(hasMouseCapture()) + { + gFocusMgr.setMouseCapture(NULL); + return true; + } + return LLFloater::handleMouseUp(x, y, mask); +} + +bool LLPreview::handleHover(S32 x, S32 y, MASK mask) +{ + if(hasMouseCapture()) + { + S32 screen_x; + S32 screen_y; + const LLInventoryItem *item = getItem(); + + localPointToScreen(x, y, &screen_x, &screen_y ); + if(item + && item->getPermissions().allowCopyBy(gAgent.getID(), + gAgent.getGroupID()) + && LLToolDragAndDrop::getInstance()->isOverThreshold(screen_x, screen_y)) + { + EDragAndDropType type; + type = LLViewerAssetType::lookupDragAndDropType(item->getType()); + LLToolDragAndDrop::ESource src = LLToolDragAndDrop::SOURCE_LIBRARY; + if(!mObjectUUID.isNull()) + { + src = LLToolDragAndDrop::SOURCE_WORLD; + } + else if(item->getPermissions().getOwner() == gAgent.getID()) + { + src = LLToolDragAndDrop::SOURCE_AGENT; + } + LLToolDragAndDrop::getInstance()->beginDrag(type, + item->getUUID(), + src, + mObjectUUID); + return LLToolDragAndDrop::getInstance()->handleHover(x, y, mask ); + } + } + return LLFloater::handleHover(x,y,mask); +} + +void LLPreview::onOpen(const LLSD& key) +{ + if (!getFloaterHost() && !getHost() && getAssetStatus() == PREVIEW_ASSET_UNLOADED) + { + loadAsset(); + } +} + +void LLPreview::setAuxItem( const LLInventoryItem* item ) +{ + if ( mAuxItem ) + mAuxItem->copyItem(item); +} + +// static +void LLPreview::onBtnCopyToInv(void* userdata) +{ + LLPreview* self = (LLPreview*) userdata; + LLInventoryItem *item = self->mAuxItem; + + if(item && item->getUUID().notNull()) + { + // Copy to inventory + if (self->mNotecardInventoryID.notNull()) + { + copy_inventory_from_notecard(LLUUID::null, + self->mNotecardObjectID, + self->mNotecardInventoryID, + item); + } + else if (self->mObjectUUID.notNull()) + { + // item is in in-world inventory + LLViewerObject* object = gObjectList.findObject(self->mObjectUUID); + LLPermissions perm(item->getPermissions()); + if(object + &&(perm.allowCopyBy(gAgent.getID(), gAgent.getGroupID()) + && perm.allowTransferTo(gAgent.getID()))) + { + // copy to default folder + set_dad_inventory_item(item, LLUUID::null); + object->moveInventory(LLUUID::null, item->getUUID()); + } + } + else + { + LLPointer cb = NULL; + copy_inventory_item( + gAgent.getID(), + item->getPermissions().getOwner(), + item->getUUID(), + LLUUID::null, + std::string(), + cb); + } + } + self->closeFloater(); +} + +// static +void LLPreview::onKeepBtn(void* data) +{ + LLPreview* self = (LLPreview*)data; + self->closeFloater(); +} + +// static +void LLPreview::onDiscardBtn(void* data) +{ + LLPreview* self = (LLPreview*)data; + + const LLInventoryItem* item = self->getItem(); + if (!item) return; + + self->mForceClose = true; + self->closeFloater(); + + // Move the item to the trash + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + if (item->getParentUUID() != trash_id) + { + LLInventoryModel::update_list_t update; + LLInventoryModel::LLCategoryUpdate old_folder(item->getParentUUID(),-1); + update.push_back(old_folder); + LLInventoryModel::LLCategoryUpdate new_folder(trash_id, 1); + update.push_back(new_folder); + gInventory.accountForUpdate(update); + + LLPointer new_item = new LLViewerInventoryItem(item); + new_item->setParent(trash_id); + // no need to restamp it though it's a move into trash because + // it's a brand new item already. + new_item->updateParentOnServer(false); + gInventory.updateItem(new_item); + gInventory.notifyObservers(); + } +} + +void LLPreview::handleReshape(const LLRect& new_rect, bool by_user) +{ + if(by_user + && (new_rect.getWidth() != getRect().getWidth() || new_rect.getHeight() != getRect().getHeight())) + { + userResized(); + } + LLFloater::handleReshape(new_rect, by_user); +} + +// +// LLMultiPreview +// + +LLMultiPreview::LLMultiPreview() + : LLMultiFloater(LLSD()) +{ + // start with a rect in the top-left corner ; will get resized + LLRect rect; + rect.setLeftTopAndSize(0, gViewerWindow->getWindowHeightScaled(), 200, 400); + setRect(rect); + + LLFloater* last_floater = LLFloaterReg::getLastFloaterInGroup("preview"); + if (last_floater) + { + stackWith(*last_floater); + } + setTitle(LLTrans::getString("MultiPreviewTitle")); + buildTabContainer(); + setCanResize(true); +} + +void LLMultiPreview::onOpen(const LLSD& key) +{ + // Floater could be something else than LLPreview, eg LLFloaterProfile. + LLPreview* frontmost_preview = dynamic_cast(mTabContainer->getCurrentPanel()); + + if (frontmost_preview && frontmost_preview->getAssetStatus() == LLPreview::PREVIEW_ASSET_UNLOADED) + { + frontmost_preview->loadAsset(); + } + LLMultiFloater::onOpen(key); +} + + +void LLMultiPreview::handleReshape(const LLRect& new_rect, bool by_user) +{ + if(new_rect.getWidth() != getRect().getWidth() || new_rect.getHeight() != getRect().getHeight()) + { + // Floater could be something else than LLPreview, eg LLFloaterProfile. + LLPreview* frontmost_preview = dynamic_cast(mTabContainer->getCurrentPanel()); + + if (frontmost_preview) + { + frontmost_preview->userResized(); + } + } + LLFloater::handleReshape(new_rect, by_user); +} + + +void LLMultiPreview::tabOpen(LLFloater* opened_floater, bool from_click) +{ + // Floater could be something else than LLPreview, eg LLFloaterProfile. + LLPreview* opened_preview = dynamic_cast(opened_floater); + + if (opened_preview && opened_preview->getAssetStatus() == LLPreview::PREVIEW_ASSET_UNLOADED) + { + opened_preview->loadAsset(); + } +} + + +void LLPreview::setAssetId(const LLUUID& asset_id) +{ + const LLViewerInventoryItem* item = dynamic_cast(getItem()); + if(NULL == item) + { + return; + } + + if(mObjectUUID.isNull()) + { + // Update avatar inventory asset_id. + LLPointer new_item = new LLViewerInventoryItem(item); + new_item->setAssetUUID(asset_id); + gInventory.updateItem(new_item); + gInventory.notifyObservers(); + } + else + { + // Update object inventory asset_id. + LLViewerObject* object = gObjectList.findObject(mObjectUUID); + if(NULL == object) + { + return; + } + object->updateViewerInventoryAsset(item, asset_id); + } +} diff --git a/indra/newview/llpreview.h b/indra/newview/llpreview.h index a714396813..b8c5477301 100644 --- a/indra/newview/llpreview.h +++ b/indra/newview/llpreview.h @@ -1,164 +1,164 @@ -/** - * @file llpreview.h - * @brief LLPreview class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPREVIEW_H -#define LL_LLPREVIEW_H - -#include "llmultifloater.h" -#include "llresizehandle.h" -#include "llpointer.h" -#include "lluuid.h" -#include "llinventoryobserver.h" -#include "llextendedstatus.h" -#include - -class LLInventoryItem; -class LLViewerObject; -class LLLineEditor; -class LLRadioGroup; -class LLPreview; - -class LLMultiPreview : public LLMultiFloater -{ -public: - LLMultiPreview(); - - /*virtual*/void onOpen(const LLSD& key); - /*virtual*/void tabOpen(LLFloater* opened_floater, bool from_click); - /*virtual*/ void handleReshape(const LLRect& new_rect, bool by_user = false); - -}; - -// https://wiki.lindenlab.com/mediawiki/index.php?title=LLPreview&oldid=81373 - -class LLPreview : public LLFloater, LLInventoryObserver -{ -public: - typedef enum e_asset_status - { - PREVIEW_ASSET_ERROR, - PREVIEW_ASSET_UNLOADED, - PREVIEW_ASSET_LOADING, - PREVIEW_ASSET_LOADED - } EAssetStatus; -public: - LLPreview(const LLSD& key ); - virtual ~LLPreview(); - - /*virtual*/ bool postBuild(); - - virtual void setObjectID(const LLUUID& object_id); - void setItem( LLInventoryItem* item ); - - void setAssetId(const LLUUID& asset_id); - const LLInventoryItem* getItem() const; // searches if not constructed with it - - static void hide(const LLUUID& item_uuid, bool no_saving = false ); - static void dirty(const LLUUID& item_uuid); - - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - virtual bool handleHover(S32 x, S32 y, MASK mask); - virtual void onOpen(const LLSD& key); - - virtual void setAuxItem( const LLInventoryItem* item ); - - static void onBtnCopyToInv(void* userdata); - - static void onKeepBtn(void* data); - static void onDiscardBtn(void* data); - /*virtual*/ void handleReshape(const LLRect& new_rect, bool by_user = false); - - void userResized() { mUserResized = true; }; - - virtual void loadAsset() { mAssetStatus = PREVIEW_ASSET_LOADED; } - virtual EAssetStatus getAssetStatus() { return mAssetStatus;} - - // Why is this at the LLPreview level? JC - void setNotecardInfo(const LLUUID& notecard_inv_id, const LLUUID& object_id); - - // llview - /*virtual*/ void draw(); - virtual void refreshFromItem(); - - // We can't modify Item or description in preview if either in-world Object - // or Item itself is unmodifiable - static bool canModify(const LLUUID taskUUID, const LLInventoryItem* item); - static bool canModify(const LLViewerObject* object, const LLInventoryItem* item); - -protected: - virtual void onCommit(); - - static void onText(LLUICtrl*, void* userdata); - static void onRadio(LLUICtrl*, void* userdata); - - // for LLInventoryObserver - virtual void changed(U32 mask); - bool mDirty; - bool mSaveDialogShown; - -protected: - LLUUID mItemUUID; - - // mObjectUUID will have a value if it is associated with a task in - // the world, and will be == LLUUID::null if it's in the agent - // inventory. - LLUUID mObjectUUID; - - LLRect mClientRect; - - LLPointer mAuxItem; // HACK! - LLPointer mItem; // For embedded items (Landmarks) - LLButton* mCopyToInvBtn; - - // Close without saving changes - bool mForceClose; - - bool mUserResized; - - // When closing springs a "Want to save?" dialog, we want - // to keep the preview open until the save completes. - bool mCloseAfterSave; - - EAssetStatus mAssetStatus; - - LLUUID mNotecardInventoryID; - // I am unsure if this is always the same as mObjectUUID, or why it exists - // at the LLPreview level. JC 2009-06-24 - LLUUID mNotecardObjectID; -}; - - -const S32 PREVIEW_BORDER = 4; -const S32 PREVIEW_PAD = 5; - -const S32 PREVIEW_LINE_HEIGHT = 19; -const S32 PREVIEW_BORDER_WIDTH = 2; -const S32 PREVIEW_RESIZE_HANDLE_SIZE = S32(RESIZE_HANDLE_WIDTH * OO_SQRT2) + PREVIEW_BORDER_WIDTH; -const S32 PREVIEW_VPAD = 2; -const S32 PREVIEW_HEADER_SIZE = 2*PREVIEW_LINE_HEIGHT + 2 * PREVIEW_VPAD; - -#endif // LL_LLPREVIEW_H +/** + * @file llpreview.h + * @brief LLPreview class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPREVIEW_H +#define LL_LLPREVIEW_H + +#include "llmultifloater.h" +#include "llresizehandle.h" +#include "llpointer.h" +#include "lluuid.h" +#include "llinventoryobserver.h" +#include "llextendedstatus.h" +#include + +class LLInventoryItem; +class LLViewerObject; +class LLLineEditor; +class LLRadioGroup; +class LLPreview; + +class LLMultiPreview : public LLMultiFloater +{ +public: + LLMultiPreview(); + + /*virtual*/void onOpen(const LLSD& key); + /*virtual*/void tabOpen(LLFloater* opened_floater, bool from_click); + /*virtual*/ void handleReshape(const LLRect& new_rect, bool by_user = false); + +}; + +// https://wiki.lindenlab.com/mediawiki/index.php?title=LLPreview&oldid=81373 + +class LLPreview : public LLFloater, LLInventoryObserver +{ +public: + typedef enum e_asset_status + { + PREVIEW_ASSET_ERROR, + PREVIEW_ASSET_UNLOADED, + PREVIEW_ASSET_LOADING, + PREVIEW_ASSET_LOADED + } EAssetStatus; +public: + LLPreview(const LLSD& key ); + virtual ~LLPreview(); + + /*virtual*/ bool postBuild(); + + virtual void setObjectID(const LLUUID& object_id); + void setItem( LLInventoryItem* item ); + + void setAssetId(const LLUUID& asset_id); + const LLInventoryItem* getItem() const; // searches if not constructed with it + + static void hide(const LLUUID& item_uuid, bool no_saving = false ); + static void dirty(const LLUUID& item_uuid); + + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + virtual bool handleHover(S32 x, S32 y, MASK mask); + virtual void onOpen(const LLSD& key); + + virtual void setAuxItem( const LLInventoryItem* item ); + + static void onBtnCopyToInv(void* userdata); + + static void onKeepBtn(void* data); + static void onDiscardBtn(void* data); + /*virtual*/ void handleReshape(const LLRect& new_rect, bool by_user = false); + + void userResized() { mUserResized = true; }; + + virtual void loadAsset() { mAssetStatus = PREVIEW_ASSET_LOADED; } + virtual EAssetStatus getAssetStatus() { return mAssetStatus;} + + // Why is this at the LLPreview level? JC + void setNotecardInfo(const LLUUID& notecard_inv_id, const LLUUID& object_id); + + // llview + /*virtual*/ void draw(); + virtual void refreshFromItem(); + + // We can't modify Item or description in preview if either in-world Object + // or Item itself is unmodifiable + static bool canModify(const LLUUID taskUUID, const LLInventoryItem* item); + static bool canModify(const LLViewerObject* object, const LLInventoryItem* item); + +protected: + virtual void onCommit(); + + static void onText(LLUICtrl*, void* userdata); + static void onRadio(LLUICtrl*, void* userdata); + + // for LLInventoryObserver + virtual void changed(U32 mask); + bool mDirty; + bool mSaveDialogShown; + +protected: + LLUUID mItemUUID; + + // mObjectUUID will have a value if it is associated with a task in + // the world, and will be == LLUUID::null if it's in the agent + // inventory. + LLUUID mObjectUUID; + + LLRect mClientRect; + + LLPointer mAuxItem; // HACK! + LLPointer mItem; // For embedded items (Landmarks) + LLButton* mCopyToInvBtn; + + // Close without saving changes + bool mForceClose; + + bool mUserResized; + + // When closing springs a "Want to save?" dialog, we want + // to keep the preview open until the save completes. + bool mCloseAfterSave; + + EAssetStatus mAssetStatus; + + LLUUID mNotecardInventoryID; + // I am unsure if this is always the same as mObjectUUID, or why it exists + // at the LLPreview level. JC 2009-06-24 + LLUUID mNotecardObjectID; +}; + + +const S32 PREVIEW_BORDER = 4; +const S32 PREVIEW_PAD = 5; + +const S32 PREVIEW_LINE_HEIGHT = 19; +const S32 PREVIEW_BORDER_WIDTH = 2; +const S32 PREVIEW_RESIZE_HANDLE_SIZE = S32(RESIZE_HANDLE_WIDTH * OO_SQRT2) + PREVIEW_BORDER_WIDTH; +const S32 PREVIEW_VPAD = 2; +const S32 PREVIEW_HEADER_SIZE = 2*PREVIEW_LINE_HEIGHT + 2 * PREVIEW_VPAD; + +#endif // LL_LLPREVIEW_H diff --git a/indra/newview/llpreviewanim.cpp b/indra/newview/llpreviewanim.cpp index 361462dae8..7d11c09738 100644 --- a/indra/newview/llpreviewanim.cpp +++ b/indra/newview/llpreviewanim.cpp @@ -1,237 +1,237 @@ -/** - * @file llpreviewanim.cpp - * @brief LLPreviewAnim class implementation - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpreviewanim.h" -#include "llbutton.h" -#include "llresmgr.h" -#include "llinventory.h" -#include "llvoavatarself.h" -#include "llagent.h" // gAgent -#include "llkeyframemotion.h" -#include "llfilepicker.h" -#include "lllineeditor.h" -#include "lltrans.h" -#include "lluictrlfactory.h" -#include "lluictrlfactory.h" -#include "lldatapacker.h" - -extern LLAgent gAgent; -const S32 ADVANCED_VPAD = 3; - -LLPreviewAnim::LLPreviewAnim(const LLSD& key) - : LLPreview( key ) -{ - mCommitCallbackRegistrar.add("PreviewAnim.Play", boost::bind(&LLPreviewAnim::play, this, _2)); -} - -// virtual -bool LLPreviewAnim::postBuild() -{ - childSetCommitCallback("desc", LLPreview::onText, this); - getChild("desc")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); - getChild("adv_trigger")->setClickedCallback(boost::bind(&LLPreviewAnim::showAdvanced, this)); - pAdvancedStatsTextBox = getChild("AdvancedStats"); - - // Assume that advanced stats start visible (for XUI preview tool's purposes) - pAdvancedStatsTextBox->setVisible(false); - LLRect rect = getRect(); - reshape(rect.getWidth(), rect.getHeight() - pAdvancedStatsTextBox->getRect().getHeight() - ADVANCED_VPAD, false); - - return LLPreview::postBuild(); -} - -// llinventorybridge also calls into here -void LLPreviewAnim::play(const LLSD& param) -{ - const LLInventoryItem *item = getItem(); - - if(item) - { - LLUUID itemID=item->getAssetUUID(); - - std::string btn_name = param.asString(); - LLButton* btn_inuse; - LLButton* btn_other; - - if ("Inworld" == btn_name) - { - btn_inuse = getChild("Inworld"); - btn_other = getChild("Locally"); - } - else if ("Locally" == btn_name) - { - btn_inuse = getChild("Locally"); - btn_other = getChild("Inworld"); - } - else - { - return; - } - - if (btn_inuse) - { - btn_inuse->toggleState(); - } - - if (btn_other) - { - btn_other->setEnabled(false); - } - - if (getChild(btn_name)->getValue().asBoolean() ) - { - if("Inworld" == btn_name) - { - gAgent.sendAnimationRequest(itemID, ANIM_REQUEST_START); - } - else - { - gAgentAvatarp->startMotion(item->getAssetUUID()); - } - - LLMotion* motion = gAgentAvatarp->findMotion(itemID); - if (motion) - { - mItemID = itemID; - mDidStart = false; - } - } - else - { - gAgentAvatarp->stopMotion(itemID); - gAgent.sendAnimationRequest(itemID, ANIM_REQUEST_STOP); - - if (btn_other) - { - btn_other->setEnabled(true); - } - } - } -} - -// virtual -void LLPreviewAnim::draw() -{ - LLPreview::draw(); - if (!this->mItemID.isNull()) - { - LLMotion* motion = gAgentAvatarp->findMotion(this->mItemID); - if (motion) - { - if (motion->isStopped() && this->mDidStart) - { - cleanup(); - } - if(gAgentAvatarp->isMotionActive(this->mItemID) && !this->mDidStart) - { - const LLInventoryItem *item = getItem(); - LLMotion* motion = gAgentAvatarp->findMotion(this->mItemID); - if (item && motion) - { - motion->setName(item->getName()); - } - this->mDidStart = true; - } - } - } -} - -// virtual -void LLPreviewAnim::refreshFromItem() -{ - const LLInventoryItem* item = getItem(); - if (!item) - { - return; - } - - // Preload motion - gAgentAvatarp->createMotion(item->getAssetUUID()); - - LLPreview::refreshFromItem(); -} - -void LLPreviewAnim::cleanup() -{ - this->mItemID = LLUUID::null; - this->mDidStart = false; - getChild("Inworld")->setValue(false); - getChild("Locally")->setValue(false); - getChild("Inworld")->setEnabled(true); - getChild("Locally")->setEnabled(true); -} - -// virtual -void LLPreviewAnim::onClose(bool app_quitting) -{ - const LLInventoryItem *item = getItem(); - - if(item) - { - gAgentAvatarp->stopMotion(item->getAssetUUID()); - gAgent.sendAnimationRequest(item->getAssetUUID(), ANIM_REQUEST_STOP); - } -} - -void LLPreviewAnim::showAdvanced() -{ - bool was_visible = pAdvancedStatsTextBox->getVisible(); - - if (was_visible) - { - pAdvancedStatsTextBox->setVisible(false); - LLRect rect = getRect(); - reshape(rect.getWidth(), rect.getHeight() - pAdvancedStatsTextBox->getRect().getHeight() - ADVANCED_VPAD, false); - } - else - { - pAdvancedStatsTextBox->setVisible(true); - LLRect rect = getRect(); - reshape(rect.getWidth(), rect.getHeight() + pAdvancedStatsTextBox->getRect().getHeight() + ADVANCED_VPAD, false); - - LLMotion *motion = NULL; - const LLInventoryItem* item = getItem(); - if (item) - { - // if motion exists, will return existing one. - // Needed because viewer can purge motions - motion = gAgentAvatarp->createMotion(item->getAssetUUID()); - } - - // set text - if (motion) - { - pAdvancedStatsTextBox->setTextArg("[PRIORITY]", llformat("%d", motion->getPriority())); - pAdvancedStatsTextBox->setTextArg("[DURATION]", llformat("%.2f", motion->getDuration())); - pAdvancedStatsTextBox->setTextArg("[EASE_IN]", llformat("%.2f", motion->getEaseInDuration())); - pAdvancedStatsTextBox->setTextArg("[EASE_OUT]", llformat("%.2f", motion->getEaseOutDuration())); - pAdvancedStatsTextBox->setTextArg("[IS_LOOP]", (motion->getLoop() ? LLTrans::getString("PermYes") : LLTrans::getString("PermNo"))); - pAdvancedStatsTextBox->setTextArg("[NUM_JOINTS]", llformat("%d", motion->getNumJointMotions())); - } - } -} +/** + * @file llpreviewanim.cpp + * @brief LLPreviewAnim class implementation + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpreviewanim.h" +#include "llbutton.h" +#include "llresmgr.h" +#include "llinventory.h" +#include "llvoavatarself.h" +#include "llagent.h" // gAgent +#include "llkeyframemotion.h" +#include "llfilepicker.h" +#include "lllineeditor.h" +#include "lltrans.h" +#include "lluictrlfactory.h" +#include "lluictrlfactory.h" +#include "lldatapacker.h" + +extern LLAgent gAgent; +const S32 ADVANCED_VPAD = 3; + +LLPreviewAnim::LLPreviewAnim(const LLSD& key) + : LLPreview( key ) +{ + mCommitCallbackRegistrar.add("PreviewAnim.Play", boost::bind(&LLPreviewAnim::play, this, _2)); +} + +// virtual +bool LLPreviewAnim::postBuild() +{ + childSetCommitCallback("desc", LLPreview::onText, this); + getChild("desc")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); + getChild("adv_trigger")->setClickedCallback(boost::bind(&LLPreviewAnim::showAdvanced, this)); + pAdvancedStatsTextBox = getChild("AdvancedStats"); + + // Assume that advanced stats start visible (for XUI preview tool's purposes) + pAdvancedStatsTextBox->setVisible(false); + LLRect rect = getRect(); + reshape(rect.getWidth(), rect.getHeight() - pAdvancedStatsTextBox->getRect().getHeight() - ADVANCED_VPAD, false); + + return LLPreview::postBuild(); +} + +// llinventorybridge also calls into here +void LLPreviewAnim::play(const LLSD& param) +{ + const LLInventoryItem *item = getItem(); + + if(item) + { + LLUUID itemID=item->getAssetUUID(); + + std::string btn_name = param.asString(); + LLButton* btn_inuse; + LLButton* btn_other; + + if ("Inworld" == btn_name) + { + btn_inuse = getChild("Inworld"); + btn_other = getChild("Locally"); + } + else if ("Locally" == btn_name) + { + btn_inuse = getChild("Locally"); + btn_other = getChild("Inworld"); + } + else + { + return; + } + + if (btn_inuse) + { + btn_inuse->toggleState(); + } + + if (btn_other) + { + btn_other->setEnabled(false); + } + + if (getChild(btn_name)->getValue().asBoolean() ) + { + if("Inworld" == btn_name) + { + gAgent.sendAnimationRequest(itemID, ANIM_REQUEST_START); + } + else + { + gAgentAvatarp->startMotion(item->getAssetUUID()); + } + + LLMotion* motion = gAgentAvatarp->findMotion(itemID); + if (motion) + { + mItemID = itemID; + mDidStart = false; + } + } + else + { + gAgentAvatarp->stopMotion(itemID); + gAgent.sendAnimationRequest(itemID, ANIM_REQUEST_STOP); + + if (btn_other) + { + btn_other->setEnabled(true); + } + } + } +} + +// virtual +void LLPreviewAnim::draw() +{ + LLPreview::draw(); + if (!this->mItemID.isNull()) + { + LLMotion* motion = gAgentAvatarp->findMotion(this->mItemID); + if (motion) + { + if (motion->isStopped() && this->mDidStart) + { + cleanup(); + } + if(gAgentAvatarp->isMotionActive(this->mItemID) && !this->mDidStart) + { + const LLInventoryItem *item = getItem(); + LLMotion* motion = gAgentAvatarp->findMotion(this->mItemID); + if (item && motion) + { + motion->setName(item->getName()); + } + this->mDidStart = true; + } + } + } +} + +// virtual +void LLPreviewAnim::refreshFromItem() +{ + const LLInventoryItem* item = getItem(); + if (!item) + { + return; + } + + // Preload motion + gAgentAvatarp->createMotion(item->getAssetUUID()); + + LLPreview::refreshFromItem(); +} + +void LLPreviewAnim::cleanup() +{ + this->mItemID = LLUUID::null; + this->mDidStart = false; + getChild("Inworld")->setValue(false); + getChild("Locally")->setValue(false); + getChild("Inworld")->setEnabled(true); + getChild("Locally")->setEnabled(true); +} + +// virtual +void LLPreviewAnim::onClose(bool app_quitting) +{ + const LLInventoryItem *item = getItem(); + + if(item) + { + gAgentAvatarp->stopMotion(item->getAssetUUID()); + gAgent.sendAnimationRequest(item->getAssetUUID(), ANIM_REQUEST_STOP); + } +} + +void LLPreviewAnim::showAdvanced() +{ + bool was_visible = pAdvancedStatsTextBox->getVisible(); + + if (was_visible) + { + pAdvancedStatsTextBox->setVisible(false); + LLRect rect = getRect(); + reshape(rect.getWidth(), rect.getHeight() - pAdvancedStatsTextBox->getRect().getHeight() - ADVANCED_VPAD, false); + } + else + { + pAdvancedStatsTextBox->setVisible(true); + LLRect rect = getRect(); + reshape(rect.getWidth(), rect.getHeight() + pAdvancedStatsTextBox->getRect().getHeight() + ADVANCED_VPAD, false); + + LLMotion *motion = NULL; + const LLInventoryItem* item = getItem(); + if (item) + { + // if motion exists, will return existing one. + // Needed because viewer can purge motions + motion = gAgentAvatarp->createMotion(item->getAssetUUID()); + } + + // set text + if (motion) + { + pAdvancedStatsTextBox->setTextArg("[PRIORITY]", llformat("%d", motion->getPriority())); + pAdvancedStatsTextBox->setTextArg("[DURATION]", llformat("%.2f", motion->getDuration())); + pAdvancedStatsTextBox->setTextArg("[EASE_IN]", llformat("%.2f", motion->getEaseInDuration())); + pAdvancedStatsTextBox->setTextArg("[EASE_OUT]", llformat("%.2f", motion->getEaseOutDuration())); + pAdvancedStatsTextBox->setTextArg("[IS_LOOP]", (motion->getLoop() ? LLTrans::getString("PermYes") : LLTrans::getString("PermNo"))); + pAdvancedStatsTextBox->setTextArg("[NUM_JOINTS]", llformat("%d", motion->getNumJointMotions())); + } + } +} diff --git a/indra/newview/llpreviewanim.h b/indra/newview/llpreviewanim.h index 95108f2667..34fa0cf5cb 100644 --- a/indra/newview/llpreviewanim.h +++ b/indra/newview/llpreviewanim.h @@ -1,57 +1,57 @@ -/** - * @file llpreviewanim.h - * @brief LLPreviewAnim class definition - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPREVIEWANIM_H -#define LL_LLPREVIEWANIM_H - -#include "llpreview.h" -#include "llcharacter.h" - -class LLMotion; -class LLTextBox; - -class LLPreviewAnim : public LLPreview -{ -public: - - LLPreviewAnim(const LLSD& key); - bool postBuild() override; - void onClose(bool app_quitting) override; - void draw() override; - void refreshFromItem() override; - - void cleanup(); // cleanup 'playing' state - void play(const LLSD& param); - void showAdvanced(); - -protected: - - LLUUID mItemID; // Not an item id, but a playing asset id - bool mDidStart; - LLTextBox* pAdvancedStatsTextBox; -}; - -#endif // LL_LLPREVIEWANIM_H +/** + * @file llpreviewanim.h + * @brief LLPreviewAnim class definition + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPREVIEWANIM_H +#define LL_LLPREVIEWANIM_H + +#include "llpreview.h" +#include "llcharacter.h" + +class LLMotion; +class LLTextBox; + +class LLPreviewAnim : public LLPreview +{ +public: + + LLPreviewAnim(const LLSD& key); + bool postBuild() override; + void onClose(bool app_quitting) override; + void draw() override; + void refreshFromItem() override; + + void cleanup(); // cleanup 'playing' state + void play(const LLSD& param); + void showAdvanced(); + +protected: + + LLUUID mItemID; // Not an item id, but a playing asset id + bool mDidStart; + LLTextBox* pAdvancedStatsTextBox; +}; + +#endif // LL_LLPREVIEWANIM_H diff --git a/indra/newview/llpreviewgesture.cpp b/indra/newview/llpreviewgesture.cpp index 010dc48391..56f23a0458 100644 --- a/indra/newview/llpreviewgesture.cpp +++ b/indra/newview/llpreviewgesture.cpp @@ -1,1816 +1,1816 @@ -/** - * @file llpreviewgesture.cpp - * @brief Editing UI for inventory-based gestures. - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llpreviewgesture.h" - -#include "llagent.h" -#include "llanimstatelabels.h" -#include "llanimationstates.h" -#include "llappviewer.h" -#include "llcheckboxctrl.h" -#include "llcombobox.h" -#include "lldatapacker.h" -#include "lldelayedgestureerror.h" -#include "llfloaterreg.h" -#include "llgesturemgr.h" -#include "llinventorydefines.h" -#include "llinventoryfunctions.h" -#include "llinventorymodel.h" -#include "llinventorymodelbackgroundfetch.h" -#include "llkeyboard.h" -#include "llmultigesture.h" -#include "llnotificationsutil.h" -#include "llradiogroup.h" -#include "llresmgr.h" -#include "lltrans.h" -#include "llfilesystem.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llviewerstats.h" -#include "llviewerassetupload.h" - -std::string NONE_LABEL; -std::string SHIFT_LABEL; -std::string CTRL_LABEL; - -void dialog_refresh_all(); - -// used for getting - -class LLInventoryGestureAvailable : public LLInventoryCompletionObserver -{ -public: - LLInventoryGestureAvailable() {} - -protected: - virtual void done(); -}; - -void LLInventoryGestureAvailable::done() -{ - for(uuid_vec_t::iterator it = mComplete.begin(); it != mComplete.end(); ++it) - { - LLPreviewGesture* preview = LLFloaterReg::findTypedInstance("preview_gesture", *it); - if(preview) - { - preview->refresh(); - } - } - gInventory.removeObserver(this); - delete this; -} - -// Used for sorting -struct SortItemPtrsByName -{ - bool operator()(const LLInventoryItem* i1, const LLInventoryItem* i2) - { - return (LLStringUtil::compareDict(i1->getName(), i2->getName()) < 0); - } -}; - -// static -LLPreviewGesture* LLPreviewGesture::show(const LLUUID& item_id, const LLUUID& object_id) -{ - LLPreviewGesture* preview = LLFloaterReg::showTypedInstance("preview_gesture", LLSD(item_id), TAKE_FOCUS_YES); - if (!preview) - { - return NULL; - } - - preview->setObjectID(object_id); - - // Start speculative download of sounds and animations - const LLUUID animation_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_ANIMATION); - LLInventoryModelBackgroundFetch::instance().start(animation_folder_id); - - const LLUUID sound_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_SOUND); - LLInventoryModelBackgroundFetch::instance().start(sound_folder_id); - - // this will call refresh when we have everything. - LLViewerInventoryItem* item = (LLViewerInventoryItem*)preview->getItem(); - if (item && !item->isFinished()) - { - LLInventoryGestureAvailable* observer; - observer = new LLInventoryGestureAvailable(); - observer->watchItem(item_id); - gInventory.addObserver(observer); - item->fetchFromServer(); - } - else - { - // not sure this is necessary. - preview->refresh(); - } - - return preview; -} - -void LLPreviewGesture::draw() -{ - // Skip LLPreview::draw() to avoid description update - LLFloater::draw(); -} - -// virtual -bool LLPreviewGesture::handleKeyHere(KEY key, MASK mask) -{ - if(('S' == key) && (MASK_CONTROL == (mask & MASK_CONTROL))) - { - saveIfNeeded(); - return true; - } - - return LLPreview::handleKeyHere(key, mask); -} - - -// virtual -bool LLPreviewGesture::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg) -{ - bool handled = true; - switch(cargo_type) - { - case DAD_ANIMATION: - case DAD_SOUND: - { - // TODO: Don't allow this if you can't transfer the sound/animation - - // make a script step - LLInventoryItem* item = (LLInventoryItem*)cargo_data; - if (item - && gInventory.getItem(item->getUUID())) - { - LLPermissions perm = item->getPermissions(); - if (!((perm.getMaskBase() & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED)) - { - *accept = ACCEPT_NO; - if (tooltip_msg.empty()) - { - tooltip_msg.assign("Only animations and sounds\n" - "with unrestricted permissions\n" - "can be added to a gesture."); - } - break; - } - else if (drop) - { - LLScrollListItem* line = NULL; - if (cargo_type == DAD_ANIMATION) - { - line = addStep( STEP_ANIMATION ); - LLGestureStepAnimation* anim = (LLGestureStepAnimation*)line->getUserdata(); - anim->mAnimAssetID = item->getAssetUUID(); - anim->mAnimName = item->getName(); - } - else if (cargo_type == DAD_SOUND) - { - line = addStep( STEP_SOUND ); - LLGestureStepSound* sound = (LLGestureStepSound*)line->getUserdata(); - sound->mSoundAssetID = item->getAssetUUID(); - sound->mSoundName = item->getName(); - } - updateLabel(line); - mDirty = true; - refresh(); - } - *accept = ACCEPT_YES_COPY_MULTI; - } - else - { - // Not in user's inventory means it was in object inventory - *accept = ACCEPT_NO; - } - break; - } - default: - *accept = ACCEPT_NO; - if (tooltip_msg.empty()) - { - tooltip_msg.assign("Only animations and sounds\n" - "can be added to a gesture."); - } - break; - } - return handled; -} - - -// virtual -bool LLPreviewGesture::canClose() -{ - - if(!mDirty || mForceClose) - { - return true; - } - else - { - if(!mSaveDialogShown) - { - mSaveDialogShown = true; - // Bring up view-modal dialog: Save changes? Yes, No, Cancel - LLNotificationsUtil::add("SaveChanges", LLSD(), LLSD(), - boost::bind(&LLPreviewGesture::handleSaveChangesDialog, this, _1, _2) ); - } - return false; - } -} - -// virtual -void LLPreviewGesture::onClose(bool app_quitting) -{ - LLGestureMgr::instance().stopGesture(mPreviewGesture); -} - -// virtual -void LLPreviewGesture::onUpdateSucceeded() -{ - refresh(); -} - -void LLPreviewGesture::onVisibilityChanged ( const LLSD& new_visibility ) -{ - if (new_visibility.asBoolean()) - { - refresh(); - } -} - - -bool LLPreviewGesture::handleSaveChangesDialog(const LLSD& notification, const LLSD& response) -{ - mSaveDialogShown = false; - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - switch(option) - { - case 0: // "Yes" - LLGestureMgr::instance().stopGesture(mPreviewGesture); - mCloseAfterSave = true; - onClickSave(this); - break; - - case 1: // "No" - LLGestureMgr::instance().stopGesture(mPreviewGesture); - mDirty = false; // Force the dirty flag because user has clicked NO on confirm save dialog... - closeFloater(); - break; - - case 2: // "Cancel" - default: - // If we were quitting, we didn't really mean it. - LLAppViewer::instance()->abortQuit(); - break; - } - return false; -} - - -LLPreviewGesture::LLPreviewGesture(const LLSD& key) -: LLPreview(key), - mTriggerEditor(NULL), - mModifierCombo(NULL), - mKeyCombo(NULL), - mLibraryList(NULL), - mAddBtn(NULL), - mUpBtn(NULL), - mDownBtn(NULL), - mDeleteBtn(NULL), - mStepList(NULL), - mOptionsText(NULL), - mAnimationRadio(NULL), - mAnimationCombo(NULL), - mSoundCombo(NULL), - mChatEditor(NULL), - mSaveBtn(NULL), - mPreviewBtn(NULL), - mPreviewGesture(NULL), - mDirty(false) -{ - NONE_LABEL = LLTrans::getString("---"); - SHIFT_LABEL = LLTrans::getString("KBShift"); - CTRL_LABEL = LLTrans::getString("KBCtrl"); -} - - -LLPreviewGesture::~LLPreviewGesture() -{ - // Userdata for all steps is a LLGestureStep we need to clean up - std::vector data_list = mStepList->getAllData(); - std::vector::iterator data_itor; - for (data_itor = data_list.begin(); data_itor != data_list.end(); ++data_itor) - { - LLScrollListItem* item = *data_itor; - LLGestureStep* step = (LLGestureStep*)item->getUserdata(); - delete step; - step = NULL; - } -} - - -bool LLPreviewGesture::postBuild() -{ - setVisibleCallback(boost::bind(&LLPreviewGesture::onVisibilityChanged, this, _2)); - - LLLineEditor* edit; - LLComboBox* combo; - LLButton* btn; - LLScrollListCtrl* list; - LLTextBox* text; - LLCheckBoxCtrl* check; - - edit = getChild("desc"); - edit->setKeystrokeCallback(onKeystrokeCommit, this); - - edit = getChild("trigger_editor"); - edit->setKeystrokeCallback(onKeystrokeCommit, this); - edit->setCommitCallback(onCommitSetDirty, this); - edit->setCommitOnFocusLost(true); - edit->setIgnoreTab(true); - mTriggerEditor = edit; - - text = getChild("replace_text"); - text->setEnabled(false); - mReplaceText = text; - - edit = getChild("replace_editor"); - edit->setEnabled(false); - edit->setKeystrokeCallback(onKeystrokeCommit, this); - edit->setCommitCallback(onCommitSetDirty, this); - edit->setCommitOnFocusLost(true); - edit->setIgnoreTab(true); - mReplaceEditor = edit; - - combo = getChild( "modifier_combo"); - combo->setCommitCallback(boost::bind(&LLPreviewGesture::onCommitKeyorModifier, this)); - mModifierCombo = combo; - - combo = getChild( "key_combo"); - combo->setCommitCallback(boost::bind(&LLPreviewGesture::onCommitKeyorModifier, this)); - mKeyCombo = combo; - - list = getChild("library_list"); - list->setCommitCallback(onCommitLibrary, this); - list->setDoubleClickCallback(onClickAdd, this); - mLibraryList = list; - - btn = getChild( "add_btn"); - btn->setClickedCallback(onClickAdd, this); - btn->setEnabled(false); - mAddBtn = btn; - - btn = getChild( "up_btn"); - btn->setClickedCallback(onClickUp, this); - btn->setEnabled(false); - mUpBtn = btn; - - btn = getChild( "down_btn"); - btn->setClickedCallback(onClickDown, this); - btn->setEnabled(false); - mDownBtn = btn; - - btn = getChild( "delete_btn"); - btn->setClickedCallback(onClickDelete, this); - btn->setEnabled(false); - mDeleteBtn = btn; - - list = getChild("step_list"); - list->setCommitCallback(onCommitStep, this); - mStepList = list; - - // Options - mOptionsText = getChild("options_text"); - - combo = getChild( "animation_list"); - combo->setVisible(false); - combo->setCommitCallback(onCommitAnimation, this); - mAnimationCombo = combo; - - LLRadioGroup* group; - group = getChild("animation_trigger_type"); - group->setVisible(false); - group->setCommitCallback(onCommitAnimationTrigger, this); - mAnimationRadio = group; - - combo = getChild( "sound_list"); - combo->setVisible(false); - combo->setCommitCallback(onCommitSound, this); - mSoundCombo = combo; - - edit = getChild("chat_editor"); - edit->setVisible(false); - edit->setCommitCallback(onCommitChat, this); - //edit->setKeystrokeCallback(onKeystrokeCommit, this); - edit->setCommitOnFocusLost(true); - edit->setIgnoreTab(true); - mChatEditor = edit; - - check = getChild( "wait_key_release_check"); - check->setVisible(false); - check->setCommitCallback(onCommitWait, this); - mWaitKeyReleaseCheck = check; - - check = getChild( "wait_anim_check"); - check->setVisible(false); - check->setCommitCallback(onCommitWait, this); - mWaitAnimCheck = check; - - check = getChild( "wait_time_check"); - check->setVisible(false); - check->setCommitCallback(onCommitWait, this); - mWaitTimeCheck = check; - - edit = getChild("wait_time_editor"); - edit->setEnabled(false); - edit->setVisible(false); - edit->setPrevalidate(LLTextValidate::validateFloat); -// edit->setKeystrokeCallback(onKeystrokeCommit, this); - edit->setCommitOnFocusLost(true); - edit->setCommitCallback(onCommitWaitTime, this); - edit->setIgnoreTab(true); - mWaitTimeEditor = edit; - - // Buttons at the bottom - check = getChild( "active_check"); - check->setCommitCallback(onCommitActive, this); - mActiveCheck = check; - - btn = getChild( "save_btn"); - btn->setClickedCallback(onClickSave, this); - mSaveBtn = btn; - - btn = getChild( "preview_btn"); - btn->setClickedCallback(onClickPreview, this); - mPreviewBtn = btn; - - - // Populate the comboboxes - addModifiers(); - addKeys(); - addAnimations(); - addSounds(); - - const LLInventoryItem* item = getItem(); - - if (item) - { - getChild("desc")->setValue(item->getDescription()); - getChild("desc")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); - } - - return LLPreview::postBuild(); -} - - -void LLPreviewGesture::addModifiers() -{ - LLComboBox* combo = mModifierCombo; - - combo->add( NONE_LABEL, ADD_BOTTOM ); - combo->add( SHIFT_LABEL, ADD_BOTTOM ); - combo->add( CTRL_LABEL, ADD_BOTTOM ); - combo->setCurrentByIndex(0); -} - -void LLPreviewGesture::addKeys() -{ - LLComboBox* combo = mKeyCombo; - - combo->add( NONE_LABEL ); - for (KEY key = KEY_F2; key <= KEY_F12; key++) - { - combo->add( LLKeyboard::stringFromKey(key), ADD_BOTTOM ); - } - combo->setCurrentByIndex(0); -} - - -// TODO: Sort the legacy and non-legacy together? -void LLPreviewGesture::addAnimations() -{ - LLComboBox* combo = mAnimationCombo; - - combo->removeall(); - - std::string none_text = getString("none_text"); - - combo->add(none_text, LLUUID::null); - - // Add all the default (legacy) animations - S32 i; - for (i = 0; i < gUserAnimStatesCount; ++i) - { - // Use the user-readable name - std::string label = LLAnimStateLabels::getStateLabel( gUserAnimStates[i].mName ); - const LLUUID& id = gUserAnimStates[i].mID; - combo->add(label, id); - } - - // Get all inventory items that are animations - LLViewerInventoryCategory::cat_array_t cats; - LLViewerInventoryItem::item_array_t items; - LLIsTypeWithPermissions is_copyable_animation(LLAssetType::AT_ANIMATION, - PERM_ITEM_UNRESTRICTED, - gAgent.getID(), - gAgent.getGroupID()); - gInventory.collectDescendentsIf(gInventory.getRootFolderID(), - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - is_copyable_animation); - - // Copy into something we can sort - std::vector animations; - - S32 count = items.size(); - for(i = 0; i < count; ++i) - { - animations.push_back( items.at(i) ); - } - - // Do the sort - std::sort(animations.begin(), animations.end(), SortItemPtrsByName()); - - // And load up the combobox - std::vector::iterator it; - for (it = animations.begin(); it != animations.end(); ++it) - { - LLInventoryItem* item = *it; - - combo->add(item->getName(), item->getAssetUUID(), ADD_BOTTOM); - } -} - - -void LLPreviewGesture::addSounds() -{ - LLComboBox* combo = mSoundCombo; - combo->removeall(); - - std::string none_text = getString("none_text"); - - combo->add(none_text, LLUUID::null); - - // Get all inventory items that are sounds - LLViewerInventoryCategory::cat_array_t cats; - LLViewerInventoryItem::item_array_t items; - LLIsTypeWithPermissions is_copyable_sound(LLAssetType::AT_SOUND, - PERM_ITEM_UNRESTRICTED, - gAgent.getID(), - gAgent.getGroupID()); - gInventory.collectDescendentsIf(gInventory.getRootFolderID(), - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - is_copyable_sound); - - // Copy sounds into something we can sort - std::vector sounds; - - S32 i; - S32 count = items.size(); - for(i = 0; i < count; ++i) - { - sounds.push_back( items.at(i) ); - } - - // Do the sort - std::sort(sounds.begin(), sounds.end(), SortItemPtrsByName()); - - // And load up the combobox - std::vector::iterator it; - for (it = sounds.begin(); it != sounds.end(); ++it) - { - LLInventoryItem* item = *it; - - combo->add(item->getName(), item->getAssetUUID(), ADD_BOTTOM); - } -} - - -void LLPreviewGesture::refresh() -{ - LLPreview::refresh(); - // If previewing or item is incomplete, all controls are disabled - LLViewerInventoryItem* item = (LLViewerInventoryItem*)getItem(); - bool is_complete = item && item->isFinished(); - if (mPreviewGesture || !is_complete) - { - - getChildView("desc")->setEnabled(false); - //mDescEditor->setEnabled(false); - mTriggerEditor->setEnabled(false); - mReplaceText->setEnabled(false); - mReplaceEditor->setEnabled(false); - mModifierCombo->setEnabled(false); - mKeyCombo->setEnabled(false); - mLibraryList->setEnabled(false); - mAddBtn->setEnabled(false); - mUpBtn->setEnabled(false); - mDownBtn->setEnabled(false); - mDeleteBtn->setEnabled(false); - mStepList->setEnabled(false); - mOptionsText->setEnabled(false); - mAnimationCombo->setEnabled(false); - mAnimationRadio->setEnabled(false); - mSoundCombo->setEnabled(false); - mChatEditor->setEnabled(false); - mWaitKeyReleaseCheck->setEnabled(false); - mWaitAnimCheck->setEnabled(false); - mWaitTimeCheck->setEnabled(false); - mWaitTimeEditor->setEnabled(false); - mActiveCheck->setEnabled(false); - mSaveBtn->setEnabled(false); - - // Make sure preview button is enabled, so we can stop it - mPreviewBtn->setEnabled(true); - return; - } - - bool modifiable = item->getPermissions().allowModifyBy(gAgent.getID()); - - getChildView("desc")->setEnabled(modifiable); - mTriggerEditor->setEnabled(true); - mLibraryList->setEnabled(modifiable); - mStepList->setEnabled(modifiable); - mOptionsText->setEnabled(modifiable); - mAnimationCombo->setEnabled(modifiable); - mAnimationRadio->setEnabled(modifiable); - mSoundCombo->setEnabled(modifiable); - mChatEditor->setEnabled(modifiable); - mWaitKeyReleaseCheck->setEnabled(modifiable); - mWaitAnimCheck->setEnabled(modifiable); - mWaitTimeCheck->setEnabled(modifiable); - mWaitTimeEditor->setEnabled(modifiable); - mActiveCheck->setEnabled(true); - - const std::string& trigger = mTriggerEditor->getText(); - bool have_trigger = !trigger.empty(); - - const std::string& replace = mReplaceEditor->getText(); - bool have_replace = !replace.empty(); - - LLScrollListItem* library_item = mLibraryList->getFirstSelected(); - bool have_library = (library_item != NULL); - - LLScrollListItem* step_item = mStepList->getFirstSelected(); - S32 step_index = mStepList->getFirstSelectedIndex(); - S32 step_count = mStepList->getItemCount(); - bool have_step = (step_item != NULL); - - mReplaceText->setEnabled(have_trigger || have_replace); - mReplaceEditor->setEnabled(have_trigger || have_replace); - - mModifierCombo->setEnabled(true); - mKeyCombo->setEnabled(true); - - mAddBtn->setEnabled(modifiable && have_library); - mUpBtn->setEnabled(modifiable && have_step && step_index > 0); - mDownBtn->setEnabled(modifiable && have_step && step_index < step_count-1); - mDeleteBtn->setEnabled(modifiable && have_step); - - // Assume all not visible - mAnimationCombo->setVisible(false); - mAnimationRadio->setVisible(false); - mSoundCombo->setVisible(false); - mChatEditor->setVisible(false); - mWaitKeyReleaseCheck->setVisible(false); - mWaitAnimCheck->setVisible(false); - mWaitTimeCheck->setVisible(false); - mWaitTimeEditor->setVisible(false); - - std::string optionstext; - - if (have_step) - { - // figure out the type, show proper options, update text - LLGestureStep* step = (LLGestureStep*)step_item->getUserdata(); - EStepType type = step->getType(); - - switch(type) - { - case STEP_ANIMATION: - { - LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step; - optionstext = getString("step_anim"); - mAnimationCombo->setVisible(true); - mAnimationRadio->setVisible(true); - mAnimationRadio->setSelectedIndex((anim_step->mFlags & ANIM_FLAG_STOP) ? 1 : 0); - mAnimationCombo->setCurrentByID(anim_step->mAnimAssetID); - break; - } - case STEP_SOUND: - { - LLGestureStepSound* sound_step = (LLGestureStepSound*)step; - optionstext = getString("step_sound"); - mSoundCombo->setVisible(true); - mSoundCombo->setCurrentByID(sound_step->mSoundAssetID); - break; - } - case STEP_CHAT: - { - LLGestureStepChat* chat_step = (LLGestureStepChat*)step; - optionstext = getString("step_chat"); - mChatEditor->setVisible(true); - mChatEditor->setText(chat_step->mChatText); - break; - } - case STEP_WAIT: - { - LLGestureStepWait* wait_step = (LLGestureStepWait*)step; - optionstext = getString("step_wait"); - mWaitKeyReleaseCheck->setVisible(true); - mWaitKeyReleaseCheck->set(wait_step->mFlags & WAIT_FLAG_KEY_RELEASE); - mWaitAnimCheck->setVisible(true); - mWaitAnimCheck->set(wait_step->mFlags & WAIT_FLAG_ALL_ANIM); - mWaitTimeCheck->setVisible(true); - mWaitTimeCheck->set(wait_step->mFlags & WAIT_FLAG_TIME); - mWaitTimeEditor->setVisible(true); - std::string buffer = llformat("%.1f", (double)wait_step->mWaitSeconds); - mWaitTimeEditor->setText(buffer); - break; - } - default: - break; - } - } - - mOptionsText->setText(optionstext); - - bool active = LLGestureMgr::instance().isGestureActive(mItemUUID); - mActiveCheck->set(active); - - // Can only preview if there are steps - mPreviewBtn->setEnabled(step_count > 0); - - // And can only save if changes have been made - mSaveBtn->setEnabled(mDirty); - addAnimations(); - addSounds(); -} - - -void LLPreviewGesture::initDefaultGesture() -{ - LLScrollListItem* item; - item = addStep( STEP_ANIMATION ); - LLGestureStepAnimation* anim = (LLGestureStepAnimation*)item->getUserdata(); - anim->mAnimAssetID = ANIM_AGENT_HELLO; - anim->mAnimName = LLTrans::getString("Wave"); - updateLabel(item); - - item = addStep( STEP_WAIT ); - LLGestureStepWait* wait = (LLGestureStepWait*)item->getUserdata(); - wait->mFlags = WAIT_FLAG_ALL_ANIM; - updateLabel(item); - - item = addStep( STEP_CHAT ); - LLGestureStepChat* chat_step = (LLGestureStepChat*)item->getUserdata(); - chat_step->mChatText = LLTrans::getString("HelloAvatar"); - updateLabel(item); - - // Start with item list selected - mStepList->selectFirstItem(); - - // this is *new* content, so we are dirty - mDirty = true; -} - - -void LLPreviewGesture::loadAsset() -{ - const LLInventoryItem* item = getItem(); - if (!item) - { - // Don't set asset status here; we may not have set the item id yet - // (e.g. when this gets called initially) - //mAssetStatus = PREVIEW_ASSET_ERROR; - return; - } - - LLUUID asset_id = item->getAssetUUID(); - if (asset_id.isNull()) - { - // Freshly created gesture, don't need to load asset. - // Blank gesture will be fine. - initDefaultGesture(); - refresh(); - mAssetStatus = PREVIEW_ASSET_LOADED; - return; - } - - // TODO: Based on item->getPermissions().allow* - // could enable/disable UI. - - // Copy the UUID, because the user might close the preview - // window if the download gets stalled. - LLUUID* item_idp = new LLUUID(mItemUUID); - - const bool high_priority = true; - gAssetStorage->getAssetData(asset_id, - LLAssetType::AT_GESTURE, - onLoadComplete, - (void**)item_idp, - high_priority); - mAssetStatus = PREVIEW_ASSET_LOADING; -} - - -// static -void LLPreviewGesture::onLoadComplete(const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status) -{ - LLUUID* item_idp = (LLUUID*)user_data; - - LLPreviewGesture* self = LLFloaterReg::findTypedInstance("preview_gesture", *item_idp); - if (self) - { - if (0 == status) - { - LLFileSystem file(asset_uuid, type, LLFileSystem::READ); - S32 size = file.getSize(); - - std::vector buffer(size+1); - file.read((U8*)&buffer[0], size); - buffer[size] = '\0'; - - LLMultiGesture* gesture = new LLMultiGesture(); - - LLDataPackerAsciiBuffer dp(&buffer[0], size+1); - bool ok = gesture->deserialize(dp); - - if (ok) - { - // Everything has been successful. Load up the UI. - self->loadUIFromGesture(gesture); - - self->mStepList->selectFirstItem(); - - self->mDirty = false; - self->refresh(); - self->refreshFromItem(); // to update description and title - } - else - { - LL_WARNS() << "Unable to load gesture" << LL_ENDL; - } - - delete gesture; - gesture = NULL; - - self->mAssetStatus = PREVIEW_ASSET_LOADED; - } - else - { - if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status || - LL_ERR_FILE_EMPTY == status) - { - LLDelayedGestureError::gestureMissing( *item_idp ); - } - else - { - LLDelayedGestureError::gestureFailedToLoad( *item_idp ); - } - - LL_WARNS() << "Problem loading gesture: " << status << LL_ENDL; - self->mAssetStatus = PREVIEW_ASSET_ERROR; - } - } - delete item_idp; - item_idp = NULL; -} - - -void LLPreviewGesture::loadUIFromGesture(LLMultiGesture* gesture) -{ - /*LLInventoryItem* item = getItem(); - - - - if (item) - { - LLLineEditor* descEditor = getChild("desc"); - descEditor->setText(item->getDescription()); - }*/ - - mTriggerEditor->setText(gesture->mTrigger); - - mReplaceEditor->setText(gesture->mReplaceText); - - switch (gesture->mMask) - { - default: - case MASK_NONE: - mModifierCombo->setSimple( NONE_LABEL ); - break; - case MASK_SHIFT: - mModifierCombo->setSimple( SHIFT_LABEL ); - break; - case MASK_CONTROL: - mModifierCombo->setSimple( CTRL_LABEL ); - break; - } - - mModifierCombo->setEnabledByValue(CTRL_LABEL, gesture->mKey != KEY_F10); - - mKeyCombo->setCurrentByIndex(0); - if (gesture->mKey != KEY_NONE) - { - mKeyCombo->setSimple(LLKeyboard::stringFromKey(gesture->mKey)); - } - - mKeyCombo->setEnabledByValue(LLKeyboard::stringFromKey(KEY_F10), gesture->mMask != MASK_CONTROL); - - // Make UI steps for each gesture step - S32 i; - S32 count = gesture->mSteps.size(); - for (i = 0; i < count; ++i) - { - LLGestureStep* step = gesture->mSteps[i]; - - LLGestureStep* new_step = NULL; - - switch(step->getType()) - { - case STEP_ANIMATION: - { - LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step; - LLGestureStepAnimation* new_anim_step = - new LLGestureStepAnimation(*anim_step); - new_step = new_anim_step; - break; - } - case STEP_SOUND: - { - LLGestureStepSound* sound_step = (LLGestureStepSound*)step; - LLGestureStepSound* new_sound_step = - new LLGestureStepSound(*sound_step); - new_step = new_sound_step; - break; - } - case STEP_CHAT: - { - LLGestureStepChat* chat_step = (LLGestureStepChat*)step; - LLGestureStepChat* new_chat_step = - new LLGestureStepChat(*chat_step); - new_step = new_chat_step; - break; - } - case STEP_WAIT: - { - LLGestureStepWait* wait_step = (LLGestureStepWait*)step; - LLGestureStepWait* new_wait_step = - new LLGestureStepWait(*wait_step); - new_step = new_wait_step; - break; - } - default: - { - break; - } - } - - if (!new_step) continue; - - // Create an enabled item with this step - LLSD row; - row["columns"][0]["value"] = getLabel( new_step->getLabel()); - row["columns"][0]["font"] = "SANSSERIF_SMALL"; - LLScrollListItem* item = mStepList->addElement(row); - item->setUserdata(new_step); - } -} - -// Helpful structure so we can look up the inventory item -// after the save finishes. -struct LLSaveInfo -{ - LLSaveInfo(const LLUUID& item_id, const LLUUID& object_id, const std::string& desc, - const LLTransactionID tid) - : mItemUUID(item_id), mObjectUUID(object_id), mDesc(desc), mTransactionID(tid) - { - } - - LLUUID mItemUUID; - LLUUID mObjectUUID; - std::string mDesc; - LLTransactionID mTransactionID; -}; - - -void LLPreviewGesture::finishInventoryUpload(LLUUID itemId, LLUUID newAssetId) -{ - // If this gesture is active, then we need to update the in-memory - // active map with the new pointer. - if (LLGestureMgr::instance().isGestureActive(itemId)) - { - // Active gesture edited from menu. - LLGestureMgr::instance().replaceGesture(itemId, newAssetId); - gInventory.notifyObservers(); - } - - //gesture will have a new asset_id - LLPreviewGesture* previewp = LLFloaterReg::findTypedInstance("preview_gesture", LLSD(itemId)); - if (previewp) - { - previewp->onUpdateSucceeded(); - } -} - - -void LLPreviewGesture::saveIfNeeded() -{ - if (!gAssetStorage) - { - LL_WARNS() << "Can't save gesture, no asset storage system." << LL_ENDL; - return; - } - - if (!mDirty) - { - return; - } - - // Copy the UI into a gesture - LLMultiGesture* gesture = createGesture(); - - // Serialize the gesture - S32 maxSize = gesture->getMaxSerialSize(); - char* buffer = new char[maxSize]; - - LLDataPackerAsciiBuffer dp(buffer, maxSize); - - bool ok = gesture->serialize(dp); - - if (dp.getCurrentSize() > 1000) - { - LLNotificationsUtil::add("GestureSaveFailedTooManySteps"); - - delete gesture; - gesture = NULL; - return; - } - else if (!ok) - { - LLNotificationsUtil::add("GestureSaveFailedTryAgain"); - delete gesture; - gesture = NULL; - return; - } - - LLAssetID assetId; - LLPreview::onCommit(); - bool delayedUpload(false); - - LLViewerInventoryItem* item = (LLViewerInventoryItem*) getItem(); - if (item) - { - const LLViewerRegion* region = gAgent.getRegion(); - if (!region) - { - LL_WARNS() << "Not connected to a region, cannot save gesture." << LL_ENDL; - return; - } - std::string agent_url = region->getCapability("UpdateGestureAgentInventory"); - std::string task_url = region->getCapability("UpdateGestureTaskInventory"); - - if (!agent_url.empty() && !task_url.empty()) - { - std::string url; - LLResourceUploadInfo::ptr_t uploadInfo; - - if (mObjectUUID.isNull() && !agent_url.empty()) - { - //need to disable the preview floater so item - //isn't re-saved before new asset arrives - //fake out refresh. - item->setComplete(false); - refresh(); - item->setComplete(true); - - uploadInfo = std::make_shared(mItemUUID, LLAssetType::AT_GESTURE, buffer, - [](LLUUID itemId, LLUUID newAssetId, LLUUID, LLSD) - { - LLPreviewGesture::finishInventoryUpload(itemId, newAssetId); - }, - nullptr); - url = agent_url; - } - else if (!mObjectUUID.isNull() && !task_url.empty()) - { - uploadInfo = std::make_shared(mObjectUUID, mItemUUID, LLAssetType::AT_GESTURE, buffer, nullptr, nullptr); - url = task_url; - } - - if (!url.empty() && uploadInfo) - { - delayedUpload = true; - - LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo); - } - - } - else if (gAssetStorage) - { - // Every save gets a new UUID. Yup. - LLTransactionID tid; - tid.generate(); - assetId = tid.makeAssetID(gAgent.getSecureSessionID()); - - LLFileSystem file(assetId, LLAssetType::AT_GESTURE, LLFileSystem::APPEND); - - S32 size = dp.getCurrentSize(); - file.write((U8*)buffer, size); - - LLLineEditor* descEditor = getChild("desc"); - LLSaveInfo* info = new LLSaveInfo(mItemUUID, mObjectUUID, descEditor->getText(), tid); - gAssetStorage->storeAssetData(tid, LLAssetType::AT_GESTURE, onSaveComplete, info, false); - } - - } - - // If this gesture is active, then we need to update the in-memory - // active map with the new pointer. - if (!delayedUpload && LLGestureMgr::instance().isGestureActive(mItemUUID)) - { - // gesture manager now owns the pointer - LLGestureMgr::instance().replaceGesture(mItemUUID, gesture, assetId); - - // replaceGesture may deactivate other gestures so let the - // inventory know. - gInventory.notifyObservers(); - } - else - { - // we're done with this gesture - delete gesture; - gesture = NULL; - } - - mDirty = false; - // refresh will be called when callback - // if triggered when delayedUpload - if(!delayedUpload) - { - refresh(); - } - -} - - -// TODO: This is very similar to LLPreviewNotecard::onSaveComplete. -// Could merge code. -// static -void LLPreviewGesture::onSaveComplete(const LLUUID& asset_uuid, void* user_data, S32 status, LLExtStat ext_status) // StoreAssetData callback (fixed) -{ - LLSaveInfo* info = (LLSaveInfo*)user_data; - if (info && (status == 0)) - { - if(info->mObjectUUID.isNull()) - { - // Saving into user inventory - LLViewerInventoryItem* item; - item = (LLViewerInventoryItem*)gInventory.getItem(info->mItemUUID); - if(item) - { - LLPointer new_item = new LLViewerInventoryItem(item); - new_item->setDescription(info->mDesc); - new_item->setTransactionID(info->mTransactionID); - new_item->setAssetUUID(asset_uuid); - new_item->updateServer(false); - gInventory.updateItem(new_item); - gInventory.notifyObservers(); - } - else - { - LL_WARNS() << "Inventory item for gesture " << info->mItemUUID - << " is no longer in agent inventory." << LL_ENDL; - } - } - else - { - // Saving into in-world object inventory - LLViewerObject* object = gObjectList.findObject(info->mObjectUUID); - LLViewerInventoryItem* item = NULL; - if(object) - { - item = (LLViewerInventoryItem*)object->getInventoryObject(info->mItemUUID); - } - if(object && item) - { - item->setDescription(info->mDesc); - item->setAssetUUID(asset_uuid); - item->setTransactionID(info->mTransactionID); - object->updateInventory(item, TASK_INVENTORY_ITEM_KEY, false); - dialog_refresh_all(); - } - else - { - LLNotificationsUtil::add("GestureSaveFailedObjectNotFound"); - } - } - - // Find our window and close it if requested. - LLPreviewGesture* previewp = LLFloaterReg::findTypedInstance("preview_gesture", info->mItemUUID); - if (previewp && previewp->mCloseAfterSave) - { - previewp->closeFloater(); - } - } - else - { - LL_WARNS() << "Problem saving gesture: " << status << LL_ENDL; - LLSD args; - args["REASON"] = std::string(LLAssetStorage::getErrorString(status)); - LLNotificationsUtil::add("GestureSaveFailedReason", args); - } - delete info; - info = NULL; -} - - -LLMultiGesture* LLPreviewGesture::createGesture() -{ - LLMultiGesture* gesture = new LLMultiGesture(); - - gesture->mTrigger = mTriggerEditor->getText(); - gesture->mReplaceText = mReplaceEditor->getText(); - - const std::string& modifier = mModifierCombo->getSimple(); - if (modifier == CTRL_LABEL) - { - gesture->mMask = MASK_CONTROL; - } - else if (modifier == SHIFT_LABEL) - { - gesture->mMask = MASK_SHIFT; - } - else - { - gesture->mMask = MASK_NONE; - } - - if (mKeyCombo->getCurrentIndex() == 0) - { - gesture->mKey = KEY_NONE; - } - else - { - const std::string& key_string = mKeyCombo->getSimple(); - LLKeyboard::keyFromString(key_string, &(gesture->mKey)); - } - - std::vector data_list = mStepList->getAllData(); - std::vector::iterator data_itor; - for (data_itor = data_list.begin(); data_itor != data_list.end(); ++data_itor) - { - LLScrollListItem* item = *data_itor; - LLGestureStep* step = (LLGestureStep*)item->getUserdata(); - - switch(step->getType()) - { - case STEP_ANIMATION: - { - // Copy UI-generated step into actual gesture step - LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step; - LLGestureStepAnimation* new_anim_step = - new LLGestureStepAnimation(*anim_step); - gesture->mSteps.push_back(new_anim_step); - break; - } - case STEP_SOUND: - { - // Copy UI-generated step into actual gesture step - LLGestureStepSound* sound_step = (LLGestureStepSound*)step; - LLGestureStepSound* new_sound_step = - new LLGestureStepSound(*sound_step); - gesture->mSteps.push_back(new_sound_step); - break; - } - case STEP_CHAT: - { - // Copy UI-generated step into actual gesture step - LLGestureStepChat* chat_step = (LLGestureStepChat*)step; - LLGestureStepChat* new_chat_step = - new LLGestureStepChat(*chat_step); - gesture->mSteps.push_back(new_chat_step); - break; - } - case STEP_WAIT: - { - // Copy UI-generated step into actual gesture step - LLGestureStepWait* wait_step = (LLGestureStepWait*)step; - LLGestureStepWait* new_wait_step = - new LLGestureStepWait(*wait_step); - gesture->mSteps.push_back(new_wait_step); - break; - } - default: - { - break; - } - } - } - - return gesture; -} - - -void LLPreviewGesture::onCommitKeyorModifier() -{ - // SL-14139: ctrl-F10 is currently used to access top menu, - // so don't allow to bound gestures to this combination. - - mKeyCombo->setEnabledByValue(LLKeyboard::stringFromKey(KEY_F10), mModifierCombo->getSimple() != CTRL_LABEL); - mModifierCombo->setEnabledByValue(CTRL_LABEL, mKeyCombo->getSimple() != LLKeyboard::stringFromKey(KEY_F10)); - mDirty = true; - refresh(); -} - -// static -void LLPreviewGesture::updateLabel(LLScrollListItem* item) -{ - LLGestureStep* step = (LLGestureStep*)item->getUserdata(); - - LLScrollListCell* cell = item->getColumn(0); - LLScrollListText* text_cell = (LLScrollListText*)cell; - std::string label = getLabel( step->getLabel()); - text_cell->setText(label); -} - -// static -void LLPreviewGesture::onCommitSetDirty(LLUICtrl* ctrl, void* data) -{ - LLPreviewGesture* self = (LLPreviewGesture*)data; - self->mDirty = true; - self->refresh(); -} - -// static -void LLPreviewGesture::onCommitLibrary(LLUICtrl* ctrl, void* data) -{ - LLPreviewGesture* self = (LLPreviewGesture*)data; - - LLScrollListItem* library_item = self->mLibraryList->getFirstSelected(); - if (library_item) - { - self->mStepList->deselectAllItems(); - self->refresh(); - } -} - - -// static -void LLPreviewGesture::onCommitStep(LLUICtrl* ctrl, void* data) -{ - LLPreviewGesture* self = (LLPreviewGesture*)data; - - LLScrollListItem* step_item = self->mStepList->getFirstSelected(); - if (!step_item) return; - - self->mLibraryList->deselectAllItems(); - self->refresh(); -} - - -// static -void LLPreviewGesture::onCommitAnimation(LLUICtrl* ctrl, void* data) -{ - LLPreviewGesture* self = (LLPreviewGesture*)data; - - LLScrollListItem* step_item = self->mStepList->getFirstSelected(); - if (step_item) - { - LLGestureStep* step = (LLGestureStep*)step_item->getUserdata(); - if (step->getType() == STEP_ANIMATION) - { - // Assign the animation name - LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step; - if (self->mAnimationCombo->getCurrentIndex() == 0) - { - anim_step->mAnimName.clear(); - anim_step->mAnimAssetID.setNull(); - } - else - { - anim_step->mAnimName = self->mAnimationCombo->getSimple(); - anim_step->mAnimAssetID = self->mAnimationCombo->getCurrentID(); - } - //anim_step->mFlags = 0x0; - - // Update the UI label in the list - updateLabel(step_item); - - self->mDirty = true; - self->refresh(); - } - } -} - -// static -void LLPreviewGesture::onCommitAnimationTrigger(LLUICtrl* ctrl, void *data) -{ - LLPreviewGesture* self = (LLPreviewGesture*)data; - - LLScrollListItem* step_item = self->mStepList->getFirstSelected(); - if (step_item) - { - LLGestureStep* step = (LLGestureStep*)step_item->getUserdata(); - if (step->getType() == STEP_ANIMATION) - { - LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step; - if (self->mAnimationRadio->getSelectedIndex() == 0) - { - // start - anim_step->mFlags &= ~ANIM_FLAG_STOP; - } - else - { - // stop - anim_step->mFlags |= ANIM_FLAG_STOP; - } - // Update the UI label in the list - updateLabel(step_item); - - self->mDirty = true; - self->refresh(); - } - } -} - -// static -void LLPreviewGesture::onCommitSound(LLUICtrl* ctrl, void* data) -{ - LLPreviewGesture* self = (LLPreviewGesture*)data; - - LLScrollListItem* step_item = self->mStepList->getFirstSelected(); - if (step_item) - { - LLGestureStep* step = (LLGestureStep*)step_item->getUserdata(); - if (step->getType() == STEP_SOUND) - { - // Assign the sound name - LLGestureStepSound* sound_step = (LLGestureStepSound*)step; - sound_step->mSoundName = self->mSoundCombo->getSimple(); - sound_step->mSoundAssetID = self->mSoundCombo->getCurrentID(); - sound_step->mFlags = 0x0; - - // Update the UI label in the list - updateLabel(step_item); - - self->mDirty = true; - self->refresh(); - } - } -} - -// static -void LLPreviewGesture::onCommitChat(LLUICtrl* ctrl, void* data) -{ - LLPreviewGesture* self = (LLPreviewGesture*)data; - - LLScrollListItem* step_item = self->mStepList->getFirstSelected(); - if (!step_item) return; - - LLGestureStep* step = (LLGestureStep*)step_item->getUserdata(); - if (step->getType() != STEP_CHAT) return; - - LLGestureStepChat* chat_step = (LLGestureStepChat*)step; - chat_step->mChatText = self->mChatEditor->getText(); - chat_step->mFlags = 0x0; - - // Update the UI label in the list - updateLabel(step_item); - - self->mDirty = true; - self->refresh(); -} - -// static -void LLPreviewGesture::onCommitWait(LLUICtrl* ctrl, void* data) -{ - LLPreviewGesture* self = (LLPreviewGesture*)data; - - LLScrollListItem* step_item = self->mStepList->getFirstSelected(); - if (!step_item) return; - - LLGestureStep* step = (LLGestureStep*)step_item->getUserdata(); - if (step->getType() != STEP_WAIT) return; - - LLGestureStepWait* wait_step = (LLGestureStepWait*)step; - U32 flags = 0x0; - if (self->mWaitKeyReleaseCheck->get()) flags |= WAIT_FLAG_KEY_RELEASE; - if (self->mWaitAnimCheck->get()) flags |= WAIT_FLAG_ALL_ANIM; - if (self->mWaitTimeCheck->get()) flags |= WAIT_FLAG_TIME; - wait_step->mFlags = flags; - - { - LLLocale locale(LLLocale::USER_LOCALE); - - F32 wait_seconds = (F32)atof(self->mWaitTimeEditor->getText().c_str()); - if (wait_seconds < 0.f) wait_seconds = 0.f; - if (wait_seconds > 3600.f) wait_seconds = 3600.f; - wait_step->mWaitSeconds = wait_seconds; - } - - // Enable the input area if necessary - self->mWaitTimeEditor->setEnabled(self->mWaitTimeCheck->get()); - - // Update the UI label in the list - updateLabel(step_item); - - self->mDirty = true; - self->refresh(); -} - -// static -void LLPreviewGesture::onCommitWaitTime(LLUICtrl* ctrl, void* data) -{ - LLPreviewGesture* self = (LLPreviewGesture*)data; - - LLScrollListItem* step_item = self->mStepList->getFirstSelected(); - if (!step_item) return; - - LLGestureStep* step = (LLGestureStep*)step_item->getUserdata(); - if (step->getType() != STEP_WAIT) return; - - self->mWaitTimeCheck->set(true); - onCommitWait(ctrl, data); -} - - -// static -void LLPreviewGesture::onKeystrokeCommit(LLLineEditor* caller, - void* data) -{ - // Just commit every keystroke - onCommitSetDirty(caller, data); -} - -// static -void LLPreviewGesture::onClickAdd(void* data) -{ - LLPreviewGesture* self = (LLPreviewGesture*)data; - - LLScrollListItem* library_item = self->mLibraryList->getFirstSelected(); - if (!library_item) return; - - S32 library_item_index = self->mLibraryList->getFirstSelectedIndex(); - - const LLScrollListCell* library_cell = library_item->getColumn(0); - const std::string& library_text = library_cell->getValue().asString(); - - if( library_item_index >= STEP_EOF ) - { - LL_ERRS() << "Unknown step type: " << library_text << LL_ENDL; - return; - } - - self->addStep( (EStepType)library_item_index ); - self->mDirty = true; - self->refresh(); -} - -LLScrollListItem* LLPreviewGesture::addStep( const EStepType step_type ) -{ - // Order of enum EStepType MUST match the library_list element in floater_preview_gesture.xml - - LLGestureStep* step = NULL; - switch( step_type) - { - case STEP_ANIMATION: - step = new LLGestureStepAnimation(); - - break; - case STEP_SOUND: - step = new LLGestureStepSound(); - break; - case STEP_CHAT: - step = new LLGestureStepChat(); - break; - case STEP_WAIT: - step = new LLGestureStepWait(); - break; - default: - LL_ERRS() << "Unknown step type: " << (S32)step_type << LL_ENDL; - return NULL; - } - - - // Create an enabled item with this step - LLSD row; - row["columns"][0]["value"] = getLabel(step->getLabel()); - row["columns"][0]["font"] = "SANSSERIF_SMALL"; - LLScrollListItem* step_item = mStepList->addElement(row); - step_item->setUserdata(step); - - // And move selection to the list on the right - mLibraryList->deselectAllItems(); - mStepList->deselectAllItems(); - - step_item->setSelected(true); - - return step_item; -} - -// static -std::string LLPreviewGesture::getLabel(std::vector labels) -{ - std::vector v_labels = labels ; - std::string result(""); - - if( v_labels.size() != 2) - { - return result; - } - - if(v_labels[0]=="Chat") - { - result=LLTrans::getString("Chat Message"); - } - else if(v_labels[0]=="Sound") - { - result=LLTrans::getString("Sound"); - } - else if(v_labels[0]=="Wait") - { - result=LLTrans::getString("Wait"); - } - else if(v_labels[0]=="AnimFlagStop") - { - result=LLTrans::getString("AnimFlagStop"); - } - else if(v_labels[0]=="AnimFlagStart") - { - result=LLTrans::getString("AnimFlagStart"); - } - - // lets localize action value - std::string action = v_labels[1]; - if ("None" == action) - { - action = LLTrans::getString("GestureActionNone"); - } - else if ("until animations are done" == action) - { - action = LLFloaterReg::getInstance("preview_gesture")->getChild("wait_anim_check")->getLabel(); - } - result.append(action); - return result; - -} -// static -void LLPreviewGesture::onClickUp(void* data) -{ - LLPreviewGesture* self = (LLPreviewGesture*)data; - - S32 selected_index = self->mStepList->getFirstSelectedIndex(); - if (selected_index > 0) - { - self->mStepList->swapWithPrevious(selected_index); - self->mDirty = true; - self->refresh(); - } -} - -// static -void LLPreviewGesture::onClickDown(void* data) -{ - LLPreviewGesture* self = (LLPreviewGesture*)data; - - S32 selected_index = self->mStepList->getFirstSelectedIndex(); - if (selected_index < 0) return; - - S32 count = self->mStepList->getItemCount(); - if (selected_index < count-1) - { - self->mStepList->swapWithNext(selected_index); - self->mDirty = true; - self->refresh(); - } -} - -// static -void LLPreviewGesture::onClickDelete(void* data) -{ - LLPreviewGesture* self = (LLPreviewGesture*)data; - - LLScrollListItem* item = self->mStepList->getFirstSelected(); - S32 selected_index = self->mStepList->getFirstSelectedIndex(); - if (item && selected_index >= 0) - { - LLGestureStep* step = (LLGestureStep*)item->getUserdata(); - delete step; - step = NULL; - - self->mStepList->deleteSingleItem(selected_index); - - self->mDirty = true; - self->refresh(); - } -} - -// static -void LLPreviewGesture::onCommitActive(LLUICtrl* ctrl, void* data) -{ - LLPreviewGesture* self = (LLPreviewGesture*)data; - if (!LLGestureMgr::instance().isGestureActive(self->mItemUUID)) - { - LLGestureMgr::instance().activateGesture(self->mItemUUID); - } - else - { - LLGestureMgr::instance().deactivateGesture(self->mItemUUID); - } - - // Make sure the (active) label in the inventory gets updated. - LLViewerInventoryItem* item = gInventory.getItem(self->mItemUUID); - if (item) - { - gInventory.updateItem(item); - gInventory.notifyObservers(); - } - - self->refresh(); -} - -// static -void LLPreviewGesture::onClickSave(void* data) -{ - LLPreviewGesture* self = (LLPreviewGesture*)data; - self->saveIfNeeded(); -} - -// static -void LLPreviewGesture::onClickPreview(void* data) -{ - LLPreviewGesture* self = (LLPreviewGesture*)data; - - if (!self->mPreviewGesture) - { - // make temporary gesture - self->mPreviewGesture = self->createGesture(); - - // add a callback - self->mPreviewGesture->mDoneCallback = onDonePreview; - self->mPreviewGesture->mCallbackData = self; - - // set the button title - self->mPreviewBtn->setLabel(self->getString("stop_txt")); - - // play it, and delete when done - LLGestureMgr::instance().playGesture(self->mPreviewGesture); - - self->refresh(); - } - else - { - // Will call onDonePreview() below - LLGestureMgr::instance().stopGesture(self->mPreviewGesture); - - self->refresh(); - } -} - - -// static -void LLPreviewGesture::onDonePreview(LLMultiGesture* gesture, void* data) -{ - LLPreviewGesture* self = (LLPreviewGesture*)data; - - self->mPreviewBtn->setLabel(self->getString("preview_txt")); - - delete self->mPreviewGesture; - self->mPreviewGesture = NULL; - - self->refresh(); -} +/** + * @file llpreviewgesture.cpp + * @brief Editing UI for inventory-based gestures. + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llpreviewgesture.h" + +#include "llagent.h" +#include "llanimstatelabels.h" +#include "llanimationstates.h" +#include "llappviewer.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "lldatapacker.h" +#include "lldelayedgestureerror.h" +#include "llfloaterreg.h" +#include "llgesturemgr.h" +#include "llinventorydefines.h" +#include "llinventoryfunctions.h" +#include "llinventorymodel.h" +#include "llinventorymodelbackgroundfetch.h" +#include "llkeyboard.h" +#include "llmultigesture.h" +#include "llnotificationsutil.h" +#include "llradiogroup.h" +#include "llresmgr.h" +#include "lltrans.h" +#include "llfilesystem.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llviewerstats.h" +#include "llviewerassetupload.h" + +std::string NONE_LABEL; +std::string SHIFT_LABEL; +std::string CTRL_LABEL; + +void dialog_refresh_all(); + +// used for getting + +class LLInventoryGestureAvailable : public LLInventoryCompletionObserver +{ +public: + LLInventoryGestureAvailable() {} + +protected: + virtual void done(); +}; + +void LLInventoryGestureAvailable::done() +{ + for(uuid_vec_t::iterator it = mComplete.begin(); it != mComplete.end(); ++it) + { + LLPreviewGesture* preview = LLFloaterReg::findTypedInstance("preview_gesture", *it); + if(preview) + { + preview->refresh(); + } + } + gInventory.removeObserver(this); + delete this; +} + +// Used for sorting +struct SortItemPtrsByName +{ + bool operator()(const LLInventoryItem* i1, const LLInventoryItem* i2) + { + return (LLStringUtil::compareDict(i1->getName(), i2->getName()) < 0); + } +}; + +// static +LLPreviewGesture* LLPreviewGesture::show(const LLUUID& item_id, const LLUUID& object_id) +{ + LLPreviewGesture* preview = LLFloaterReg::showTypedInstance("preview_gesture", LLSD(item_id), TAKE_FOCUS_YES); + if (!preview) + { + return NULL; + } + + preview->setObjectID(object_id); + + // Start speculative download of sounds and animations + const LLUUID animation_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_ANIMATION); + LLInventoryModelBackgroundFetch::instance().start(animation_folder_id); + + const LLUUID sound_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_SOUND); + LLInventoryModelBackgroundFetch::instance().start(sound_folder_id); + + // this will call refresh when we have everything. + LLViewerInventoryItem* item = (LLViewerInventoryItem*)preview->getItem(); + if (item && !item->isFinished()) + { + LLInventoryGestureAvailable* observer; + observer = new LLInventoryGestureAvailable(); + observer->watchItem(item_id); + gInventory.addObserver(observer); + item->fetchFromServer(); + } + else + { + // not sure this is necessary. + preview->refresh(); + } + + return preview; +} + +void LLPreviewGesture::draw() +{ + // Skip LLPreview::draw() to avoid description update + LLFloater::draw(); +} + +// virtual +bool LLPreviewGesture::handleKeyHere(KEY key, MASK mask) +{ + if(('S' == key) && (MASK_CONTROL == (mask & MASK_CONTROL))) + { + saveIfNeeded(); + return true; + } + + return LLPreview::handleKeyHere(key, mask); +} + + +// virtual +bool LLPreviewGesture::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + bool handled = true; + switch(cargo_type) + { + case DAD_ANIMATION: + case DAD_SOUND: + { + // TODO: Don't allow this if you can't transfer the sound/animation + + // make a script step + LLInventoryItem* item = (LLInventoryItem*)cargo_data; + if (item + && gInventory.getItem(item->getUUID())) + { + LLPermissions perm = item->getPermissions(); + if (!((perm.getMaskBase() & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED)) + { + *accept = ACCEPT_NO; + if (tooltip_msg.empty()) + { + tooltip_msg.assign("Only animations and sounds\n" + "with unrestricted permissions\n" + "can be added to a gesture."); + } + break; + } + else if (drop) + { + LLScrollListItem* line = NULL; + if (cargo_type == DAD_ANIMATION) + { + line = addStep( STEP_ANIMATION ); + LLGestureStepAnimation* anim = (LLGestureStepAnimation*)line->getUserdata(); + anim->mAnimAssetID = item->getAssetUUID(); + anim->mAnimName = item->getName(); + } + else if (cargo_type == DAD_SOUND) + { + line = addStep( STEP_SOUND ); + LLGestureStepSound* sound = (LLGestureStepSound*)line->getUserdata(); + sound->mSoundAssetID = item->getAssetUUID(); + sound->mSoundName = item->getName(); + } + updateLabel(line); + mDirty = true; + refresh(); + } + *accept = ACCEPT_YES_COPY_MULTI; + } + else + { + // Not in user's inventory means it was in object inventory + *accept = ACCEPT_NO; + } + break; + } + default: + *accept = ACCEPT_NO; + if (tooltip_msg.empty()) + { + tooltip_msg.assign("Only animations and sounds\n" + "can be added to a gesture."); + } + break; + } + return handled; +} + + +// virtual +bool LLPreviewGesture::canClose() +{ + + if(!mDirty || mForceClose) + { + return true; + } + else + { + if(!mSaveDialogShown) + { + mSaveDialogShown = true; + // Bring up view-modal dialog: Save changes? Yes, No, Cancel + LLNotificationsUtil::add("SaveChanges", LLSD(), LLSD(), + boost::bind(&LLPreviewGesture::handleSaveChangesDialog, this, _1, _2) ); + } + return false; + } +} + +// virtual +void LLPreviewGesture::onClose(bool app_quitting) +{ + LLGestureMgr::instance().stopGesture(mPreviewGesture); +} + +// virtual +void LLPreviewGesture::onUpdateSucceeded() +{ + refresh(); +} + +void LLPreviewGesture::onVisibilityChanged ( const LLSD& new_visibility ) +{ + if (new_visibility.asBoolean()) + { + refresh(); + } +} + + +bool LLPreviewGesture::handleSaveChangesDialog(const LLSD& notification, const LLSD& response) +{ + mSaveDialogShown = false; + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + switch(option) + { + case 0: // "Yes" + LLGestureMgr::instance().stopGesture(mPreviewGesture); + mCloseAfterSave = true; + onClickSave(this); + break; + + case 1: // "No" + LLGestureMgr::instance().stopGesture(mPreviewGesture); + mDirty = false; // Force the dirty flag because user has clicked NO on confirm save dialog... + closeFloater(); + break; + + case 2: // "Cancel" + default: + // If we were quitting, we didn't really mean it. + LLAppViewer::instance()->abortQuit(); + break; + } + return false; +} + + +LLPreviewGesture::LLPreviewGesture(const LLSD& key) +: LLPreview(key), + mTriggerEditor(NULL), + mModifierCombo(NULL), + mKeyCombo(NULL), + mLibraryList(NULL), + mAddBtn(NULL), + mUpBtn(NULL), + mDownBtn(NULL), + mDeleteBtn(NULL), + mStepList(NULL), + mOptionsText(NULL), + mAnimationRadio(NULL), + mAnimationCombo(NULL), + mSoundCombo(NULL), + mChatEditor(NULL), + mSaveBtn(NULL), + mPreviewBtn(NULL), + mPreviewGesture(NULL), + mDirty(false) +{ + NONE_LABEL = LLTrans::getString("---"); + SHIFT_LABEL = LLTrans::getString("KBShift"); + CTRL_LABEL = LLTrans::getString("KBCtrl"); +} + + +LLPreviewGesture::~LLPreviewGesture() +{ + // Userdata for all steps is a LLGestureStep we need to clean up + std::vector data_list = mStepList->getAllData(); + std::vector::iterator data_itor; + for (data_itor = data_list.begin(); data_itor != data_list.end(); ++data_itor) + { + LLScrollListItem* item = *data_itor; + LLGestureStep* step = (LLGestureStep*)item->getUserdata(); + delete step; + step = NULL; + } +} + + +bool LLPreviewGesture::postBuild() +{ + setVisibleCallback(boost::bind(&LLPreviewGesture::onVisibilityChanged, this, _2)); + + LLLineEditor* edit; + LLComboBox* combo; + LLButton* btn; + LLScrollListCtrl* list; + LLTextBox* text; + LLCheckBoxCtrl* check; + + edit = getChild("desc"); + edit->setKeystrokeCallback(onKeystrokeCommit, this); + + edit = getChild("trigger_editor"); + edit->setKeystrokeCallback(onKeystrokeCommit, this); + edit->setCommitCallback(onCommitSetDirty, this); + edit->setCommitOnFocusLost(true); + edit->setIgnoreTab(true); + mTriggerEditor = edit; + + text = getChild("replace_text"); + text->setEnabled(false); + mReplaceText = text; + + edit = getChild("replace_editor"); + edit->setEnabled(false); + edit->setKeystrokeCallback(onKeystrokeCommit, this); + edit->setCommitCallback(onCommitSetDirty, this); + edit->setCommitOnFocusLost(true); + edit->setIgnoreTab(true); + mReplaceEditor = edit; + + combo = getChild( "modifier_combo"); + combo->setCommitCallback(boost::bind(&LLPreviewGesture::onCommitKeyorModifier, this)); + mModifierCombo = combo; + + combo = getChild( "key_combo"); + combo->setCommitCallback(boost::bind(&LLPreviewGesture::onCommitKeyorModifier, this)); + mKeyCombo = combo; + + list = getChild("library_list"); + list->setCommitCallback(onCommitLibrary, this); + list->setDoubleClickCallback(onClickAdd, this); + mLibraryList = list; + + btn = getChild( "add_btn"); + btn->setClickedCallback(onClickAdd, this); + btn->setEnabled(false); + mAddBtn = btn; + + btn = getChild( "up_btn"); + btn->setClickedCallback(onClickUp, this); + btn->setEnabled(false); + mUpBtn = btn; + + btn = getChild( "down_btn"); + btn->setClickedCallback(onClickDown, this); + btn->setEnabled(false); + mDownBtn = btn; + + btn = getChild( "delete_btn"); + btn->setClickedCallback(onClickDelete, this); + btn->setEnabled(false); + mDeleteBtn = btn; + + list = getChild("step_list"); + list->setCommitCallback(onCommitStep, this); + mStepList = list; + + // Options + mOptionsText = getChild("options_text"); + + combo = getChild( "animation_list"); + combo->setVisible(false); + combo->setCommitCallback(onCommitAnimation, this); + mAnimationCombo = combo; + + LLRadioGroup* group; + group = getChild("animation_trigger_type"); + group->setVisible(false); + group->setCommitCallback(onCommitAnimationTrigger, this); + mAnimationRadio = group; + + combo = getChild( "sound_list"); + combo->setVisible(false); + combo->setCommitCallback(onCommitSound, this); + mSoundCombo = combo; + + edit = getChild("chat_editor"); + edit->setVisible(false); + edit->setCommitCallback(onCommitChat, this); + //edit->setKeystrokeCallback(onKeystrokeCommit, this); + edit->setCommitOnFocusLost(true); + edit->setIgnoreTab(true); + mChatEditor = edit; + + check = getChild( "wait_key_release_check"); + check->setVisible(false); + check->setCommitCallback(onCommitWait, this); + mWaitKeyReleaseCheck = check; + + check = getChild( "wait_anim_check"); + check->setVisible(false); + check->setCommitCallback(onCommitWait, this); + mWaitAnimCheck = check; + + check = getChild( "wait_time_check"); + check->setVisible(false); + check->setCommitCallback(onCommitWait, this); + mWaitTimeCheck = check; + + edit = getChild("wait_time_editor"); + edit->setEnabled(false); + edit->setVisible(false); + edit->setPrevalidate(LLTextValidate::validateFloat); +// edit->setKeystrokeCallback(onKeystrokeCommit, this); + edit->setCommitOnFocusLost(true); + edit->setCommitCallback(onCommitWaitTime, this); + edit->setIgnoreTab(true); + mWaitTimeEditor = edit; + + // Buttons at the bottom + check = getChild( "active_check"); + check->setCommitCallback(onCommitActive, this); + mActiveCheck = check; + + btn = getChild( "save_btn"); + btn->setClickedCallback(onClickSave, this); + mSaveBtn = btn; + + btn = getChild( "preview_btn"); + btn->setClickedCallback(onClickPreview, this); + mPreviewBtn = btn; + + + // Populate the comboboxes + addModifiers(); + addKeys(); + addAnimations(); + addSounds(); + + const LLInventoryItem* item = getItem(); + + if (item) + { + getChild("desc")->setValue(item->getDescription()); + getChild("desc")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); + } + + return LLPreview::postBuild(); +} + + +void LLPreviewGesture::addModifiers() +{ + LLComboBox* combo = mModifierCombo; + + combo->add( NONE_LABEL, ADD_BOTTOM ); + combo->add( SHIFT_LABEL, ADD_BOTTOM ); + combo->add( CTRL_LABEL, ADD_BOTTOM ); + combo->setCurrentByIndex(0); +} + +void LLPreviewGesture::addKeys() +{ + LLComboBox* combo = mKeyCombo; + + combo->add( NONE_LABEL ); + for (KEY key = KEY_F2; key <= KEY_F12; key++) + { + combo->add( LLKeyboard::stringFromKey(key), ADD_BOTTOM ); + } + combo->setCurrentByIndex(0); +} + + +// TODO: Sort the legacy and non-legacy together? +void LLPreviewGesture::addAnimations() +{ + LLComboBox* combo = mAnimationCombo; + + combo->removeall(); + + std::string none_text = getString("none_text"); + + combo->add(none_text, LLUUID::null); + + // Add all the default (legacy) animations + S32 i; + for (i = 0; i < gUserAnimStatesCount; ++i) + { + // Use the user-readable name + std::string label = LLAnimStateLabels::getStateLabel( gUserAnimStates[i].mName ); + const LLUUID& id = gUserAnimStates[i].mID; + combo->add(label, id); + } + + // Get all inventory items that are animations + LLViewerInventoryCategory::cat_array_t cats; + LLViewerInventoryItem::item_array_t items; + LLIsTypeWithPermissions is_copyable_animation(LLAssetType::AT_ANIMATION, + PERM_ITEM_UNRESTRICTED, + gAgent.getID(), + gAgent.getGroupID()); + gInventory.collectDescendentsIf(gInventory.getRootFolderID(), + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + is_copyable_animation); + + // Copy into something we can sort + std::vector animations; + + S32 count = items.size(); + for(i = 0; i < count; ++i) + { + animations.push_back( items.at(i) ); + } + + // Do the sort + std::sort(animations.begin(), animations.end(), SortItemPtrsByName()); + + // And load up the combobox + std::vector::iterator it; + for (it = animations.begin(); it != animations.end(); ++it) + { + LLInventoryItem* item = *it; + + combo->add(item->getName(), item->getAssetUUID(), ADD_BOTTOM); + } +} + + +void LLPreviewGesture::addSounds() +{ + LLComboBox* combo = mSoundCombo; + combo->removeall(); + + std::string none_text = getString("none_text"); + + combo->add(none_text, LLUUID::null); + + // Get all inventory items that are sounds + LLViewerInventoryCategory::cat_array_t cats; + LLViewerInventoryItem::item_array_t items; + LLIsTypeWithPermissions is_copyable_sound(LLAssetType::AT_SOUND, + PERM_ITEM_UNRESTRICTED, + gAgent.getID(), + gAgent.getGroupID()); + gInventory.collectDescendentsIf(gInventory.getRootFolderID(), + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + is_copyable_sound); + + // Copy sounds into something we can sort + std::vector sounds; + + S32 i; + S32 count = items.size(); + for(i = 0; i < count; ++i) + { + sounds.push_back( items.at(i) ); + } + + // Do the sort + std::sort(sounds.begin(), sounds.end(), SortItemPtrsByName()); + + // And load up the combobox + std::vector::iterator it; + for (it = sounds.begin(); it != sounds.end(); ++it) + { + LLInventoryItem* item = *it; + + combo->add(item->getName(), item->getAssetUUID(), ADD_BOTTOM); + } +} + + +void LLPreviewGesture::refresh() +{ + LLPreview::refresh(); + // If previewing or item is incomplete, all controls are disabled + LLViewerInventoryItem* item = (LLViewerInventoryItem*)getItem(); + bool is_complete = item && item->isFinished(); + if (mPreviewGesture || !is_complete) + { + + getChildView("desc")->setEnabled(false); + //mDescEditor->setEnabled(false); + mTriggerEditor->setEnabled(false); + mReplaceText->setEnabled(false); + mReplaceEditor->setEnabled(false); + mModifierCombo->setEnabled(false); + mKeyCombo->setEnabled(false); + mLibraryList->setEnabled(false); + mAddBtn->setEnabled(false); + mUpBtn->setEnabled(false); + mDownBtn->setEnabled(false); + mDeleteBtn->setEnabled(false); + mStepList->setEnabled(false); + mOptionsText->setEnabled(false); + mAnimationCombo->setEnabled(false); + mAnimationRadio->setEnabled(false); + mSoundCombo->setEnabled(false); + mChatEditor->setEnabled(false); + mWaitKeyReleaseCheck->setEnabled(false); + mWaitAnimCheck->setEnabled(false); + mWaitTimeCheck->setEnabled(false); + mWaitTimeEditor->setEnabled(false); + mActiveCheck->setEnabled(false); + mSaveBtn->setEnabled(false); + + // Make sure preview button is enabled, so we can stop it + mPreviewBtn->setEnabled(true); + return; + } + + bool modifiable = item->getPermissions().allowModifyBy(gAgent.getID()); + + getChildView("desc")->setEnabled(modifiable); + mTriggerEditor->setEnabled(true); + mLibraryList->setEnabled(modifiable); + mStepList->setEnabled(modifiable); + mOptionsText->setEnabled(modifiable); + mAnimationCombo->setEnabled(modifiable); + mAnimationRadio->setEnabled(modifiable); + mSoundCombo->setEnabled(modifiable); + mChatEditor->setEnabled(modifiable); + mWaitKeyReleaseCheck->setEnabled(modifiable); + mWaitAnimCheck->setEnabled(modifiable); + mWaitTimeCheck->setEnabled(modifiable); + mWaitTimeEditor->setEnabled(modifiable); + mActiveCheck->setEnabled(true); + + const std::string& trigger = mTriggerEditor->getText(); + bool have_trigger = !trigger.empty(); + + const std::string& replace = mReplaceEditor->getText(); + bool have_replace = !replace.empty(); + + LLScrollListItem* library_item = mLibraryList->getFirstSelected(); + bool have_library = (library_item != NULL); + + LLScrollListItem* step_item = mStepList->getFirstSelected(); + S32 step_index = mStepList->getFirstSelectedIndex(); + S32 step_count = mStepList->getItemCount(); + bool have_step = (step_item != NULL); + + mReplaceText->setEnabled(have_trigger || have_replace); + mReplaceEditor->setEnabled(have_trigger || have_replace); + + mModifierCombo->setEnabled(true); + mKeyCombo->setEnabled(true); + + mAddBtn->setEnabled(modifiable && have_library); + mUpBtn->setEnabled(modifiable && have_step && step_index > 0); + mDownBtn->setEnabled(modifiable && have_step && step_index < step_count-1); + mDeleteBtn->setEnabled(modifiable && have_step); + + // Assume all not visible + mAnimationCombo->setVisible(false); + mAnimationRadio->setVisible(false); + mSoundCombo->setVisible(false); + mChatEditor->setVisible(false); + mWaitKeyReleaseCheck->setVisible(false); + mWaitAnimCheck->setVisible(false); + mWaitTimeCheck->setVisible(false); + mWaitTimeEditor->setVisible(false); + + std::string optionstext; + + if (have_step) + { + // figure out the type, show proper options, update text + LLGestureStep* step = (LLGestureStep*)step_item->getUserdata(); + EStepType type = step->getType(); + + switch(type) + { + case STEP_ANIMATION: + { + LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step; + optionstext = getString("step_anim"); + mAnimationCombo->setVisible(true); + mAnimationRadio->setVisible(true); + mAnimationRadio->setSelectedIndex((anim_step->mFlags & ANIM_FLAG_STOP) ? 1 : 0); + mAnimationCombo->setCurrentByID(anim_step->mAnimAssetID); + break; + } + case STEP_SOUND: + { + LLGestureStepSound* sound_step = (LLGestureStepSound*)step; + optionstext = getString("step_sound"); + mSoundCombo->setVisible(true); + mSoundCombo->setCurrentByID(sound_step->mSoundAssetID); + break; + } + case STEP_CHAT: + { + LLGestureStepChat* chat_step = (LLGestureStepChat*)step; + optionstext = getString("step_chat"); + mChatEditor->setVisible(true); + mChatEditor->setText(chat_step->mChatText); + break; + } + case STEP_WAIT: + { + LLGestureStepWait* wait_step = (LLGestureStepWait*)step; + optionstext = getString("step_wait"); + mWaitKeyReleaseCheck->setVisible(true); + mWaitKeyReleaseCheck->set(wait_step->mFlags & WAIT_FLAG_KEY_RELEASE); + mWaitAnimCheck->setVisible(true); + mWaitAnimCheck->set(wait_step->mFlags & WAIT_FLAG_ALL_ANIM); + mWaitTimeCheck->setVisible(true); + mWaitTimeCheck->set(wait_step->mFlags & WAIT_FLAG_TIME); + mWaitTimeEditor->setVisible(true); + std::string buffer = llformat("%.1f", (double)wait_step->mWaitSeconds); + mWaitTimeEditor->setText(buffer); + break; + } + default: + break; + } + } + + mOptionsText->setText(optionstext); + + bool active = LLGestureMgr::instance().isGestureActive(mItemUUID); + mActiveCheck->set(active); + + // Can only preview if there are steps + mPreviewBtn->setEnabled(step_count > 0); + + // And can only save if changes have been made + mSaveBtn->setEnabled(mDirty); + addAnimations(); + addSounds(); +} + + +void LLPreviewGesture::initDefaultGesture() +{ + LLScrollListItem* item; + item = addStep( STEP_ANIMATION ); + LLGestureStepAnimation* anim = (LLGestureStepAnimation*)item->getUserdata(); + anim->mAnimAssetID = ANIM_AGENT_HELLO; + anim->mAnimName = LLTrans::getString("Wave"); + updateLabel(item); + + item = addStep( STEP_WAIT ); + LLGestureStepWait* wait = (LLGestureStepWait*)item->getUserdata(); + wait->mFlags = WAIT_FLAG_ALL_ANIM; + updateLabel(item); + + item = addStep( STEP_CHAT ); + LLGestureStepChat* chat_step = (LLGestureStepChat*)item->getUserdata(); + chat_step->mChatText = LLTrans::getString("HelloAvatar"); + updateLabel(item); + + // Start with item list selected + mStepList->selectFirstItem(); + + // this is *new* content, so we are dirty + mDirty = true; +} + + +void LLPreviewGesture::loadAsset() +{ + const LLInventoryItem* item = getItem(); + if (!item) + { + // Don't set asset status here; we may not have set the item id yet + // (e.g. when this gets called initially) + //mAssetStatus = PREVIEW_ASSET_ERROR; + return; + } + + LLUUID asset_id = item->getAssetUUID(); + if (asset_id.isNull()) + { + // Freshly created gesture, don't need to load asset. + // Blank gesture will be fine. + initDefaultGesture(); + refresh(); + mAssetStatus = PREVIEW_ASSET_LOADED; + return; + } + + // TODO: Based on item->getPermissions().allow* + // could enable/disable UI. + + // Copy the UUID, because the user might close the preview + // window if the download gets stalled. + LLUUID* item_idp = new LLUUID(mItemUUID); + + const bool high_priority = true; + gAssetStorage->getAssetData(asset_id, + LLAssetType::AT_GESTURE, + onLoadComplete, + (void**)item_idp, + high_priority); + mAssetStatus = PREVIEW_ASSET_LOADING; +} + + +// static +void LLPreviewGesture::onLoadComplete(const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status) +{ + LLUUID* item_idp = (LLUUID*)user_data; + + LLPreviewGesture* self = LLFloaterReg::findTypedInstance("preview_gesture", *item_idp); + if (self) + { + if (0 == status) + { + LLFileSystem file(asset_uuid, type, LLFileSystem::READ); + S32 size = file.getSize(); + + std::vector buffer(size+1); + file.read((U8*)&buffer[0], size); + buffer[size] = '\0'; + + LLMultiGesture* gesture = new LLMultiGesture(); + + LLDataPackerAsciiBuffer dp(&buffer[0], size+1); + bool ok = gesture->deserialize(dp); + + if (ok) + { + // Everything has been successful. Load up the UI. + self->loadUIFromGesture(gesture); + + self->mStepList->selectFirstItem(); + + self->mDirty = false; + self->refresh(); + self->refreshFromItem(); // to update description and title + } + else + { + LL_WARNS() << "Unable to load gesture" << LL_ENDL; + } + + delete gesture; + gesture = NULL; + + self->mAssetStatus = PREVIEW_ASSET_LOADED; + } + else + { + if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status || + LL_ERR_FILE_EMPTY == status) + { + LLDelayedGestureError::gestureMissing( *item_idp ); + } + else + { + LLDelayedGestureError::gestureFailedToLoad( *item_idp ); + } + + LL_WARNS() << "Problem loading gesture: " << status << LL_ENDL; + self->mAssetStatus = PREVIEW_ASSET_ERROR; + } + } + delete item_idp; + item_idp = NULL; +} + + +void LLPreviewGesture::loadUIFromGesture(LLMultiGesture* gesture) +{ + /*LLInventoryItem* item = getItem(); + + + + if (item) + { + LLLineEditor* descEditor = getChild("desc"); + descEditor->setText(item->getDescription()); + }*/ + + mTriggerEditor->setText(gesture->mTrigger); + + mReplaceEditor->setText(gesture->mReplaceText); + + switch (gesture->mMask) + { + default: + case MASK_NONE: + mModifierCombo->setSimple( NONE_LABEL ); + break; + case MASK_SHIFT: + mModifierCombo->setSimple( SHIFT_LABEL ); + break; + case MASK_CONTROL: + mModifierCombo->setSimple( CTRL_LABEL ); + break; + } + + mModifierCombo->setEnabledByValue(CTRL_LABEL, gesture->mKey != KEY_F10); + + mKeyCombo->setCurrentByIndex(0); + if (gesture->mKey != KEY_NONE) + { + mKeyCombo->setSimple(LLKeyboard::stringFromKey(gesture->mKey)); + } + + mKeyCombo->setEnabledByValue(LLKeyboard::stringFromKey(KEY_F10), gesture->mMask != MASK_CONTROL); + + // Make UI steps for each gesture step + S32 i; + S32 count = gesture->mSteps.size(); + for (i = 0; i < count; ++i) + { + LLGestureStep* step = gesture->mSteps[i]; + + LLGestureStep* new_step = NULL; + + switch(step->getType()) + { + case STEP_ANIMATION: + { + LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step; + LLGestureStepAnimation* new_anim_step = + new LLGestureStepAnimation(*anim_step); + new_step = new_anim_step; + break; + } + case STEP_SOUND: + { + LLGestureStepSound* sound_step = (LLGestureStepSound*)step; + LLGestureStepSound* new_sound_step = + new LLGestureStepSound(*sound_step); + new_step = new_sound_step; + break; + } + case STEP_CHAT: + { + LLGestureStepChat* chat_step = (LLGestureStepChat*)step; + LLGestureStepChat* new_chat_step = + new LLGestureStepChat(*chat_step); + new_step = new_chat_step; + break; + } + case STEP_WAIT: + { + LLGestureStepWait* wait_step = (LLGestureStepWait*)step; + LLGestureStepWait* new_wait_step = + new LLGestureStepWait(*wait_step); + new_step = new_wait_step; + break; + } + default: + { + break; + } + } + + if (!new_step) continue; + + // Create an enabled item with this step + LLSD row; + row["columns"][0]["value"] = getLabel( new_step->getLabel()); + row["columns"][0]["font"] = "SANSSERIF_SMALL"; + LLScrollListItem* item = mStepList->addElement(row); + item->setUserdata(new_step); + } +} + +// Helpful structure so we can look up the inventory item +// after the save finishes. +struct LLSaveInfo +{ + LLSaveInfo(const LLUUID& item_id, const LLUUID& object_id, const std::string& desc, + const LLTransactionID tid) + : mItemUUID(item_id), mObjectUUID(object_id), mDesc(desc), mTransactionID(tid) + { + } + + LLUUID mItemUUID; + LLUUID mObjectUUID; + std::string mDesc; + LLTransactionID mTransactionID; +}; + + +void LLPreviewGesture::finishInventoryUpload(LLUUID itemId, LLUUID newAssetId) +{ + // If this gesture is active, then we need to update the in-memory + // active map with the new pointer. + if (LLGestureMgr::instance().isGestureActive(itemId)) + { + // Active gesture edited from menu. + LLGestureMgr::instance().replaceGesture(itemId, newAssetId); + gInventory.notifyObservers(); + } + + //gesture will have a new asset_id + LLPreviewGesture* previewp = LLFloaterReg::findTypedInstance("preview_gesture", LLSD(itemId)); + if (previewp) + { + previewp->onUpdateSucceeded(); + } +} + + +void LLPreviewGesture::saveIfNeeded() +{ + if (!gAssetStorage) + { + LL_WARNS() << "Can't save gesture, no asset storage system." << LL_ENDL; + return; + } + + if (!mDirty) + { + return; + } + + // Copy the UI into a gesture + LLMultiGesture* gesture = createGesture(); + + // Serialize the gesture + S32 maxSize = gesture->getMaxSerialSize(); + char* buffer = new char[maxSize]; + + LLDataPackerAsciiBuffer dp(buffer, maxSize); + + bool ok = gesture->serialize(dp); + + if (dp.getCurrentSize() > 1000) + { + LLNotificationsUtil::add("GestureSaveFailedTooManySteps"); + + delete gesture; + gesture = NULL; + return; + } + else if (!ok) + { + LLNotificationsUtil::add("GestureSaveFailedTryAgain"); + delete gesture; + gesture = NULL; + return; + } + + LLAssetID assetId; + LLPreview::onCommit(); + bool delayedUpload(false); + + LLViewerInventoryItem* item = (LLViewerInventoryItem*) getItem(); + if (item) + { + const LLViewerRegion* region = gAgent.getRegion(); + if (!region) + { + LL_WARNS() << "Not connected to a region, cannot save gesture." << LL_ENDL; + return; + } + std::string agent_url = region->getCapability("UpdateGestureAgentInventory"); + std::string task_url = region->getCapability("UpdateGestureTaskInventory"); + + if (!agent_url.empty() && !task_url.empty()) + { + std::string url; + LLResourceUploadInfo::ptr_t uploadInfo; + + if (mObjectUUID.isNull() && !agent_url.empty()) + { + //need to disable the preview floater so item + //isn't re-saved before new asset arrives + //fake out refresh. + item->setComplete(false); + refresh(); + item->setComplete(true); + + uploadInfo = std::make_shared(mItemUUID, LLAssetType::AT_GESTURE, buffer, + [](LLUUID itemId, LLUUID newAssetId, LLUUID, LLSD) + { + LLPreviewGesture::finishInventoryUpload(itemId, newAssetId); + }, + nullptr); + url = agent_url; + } + else if (!mObjectUUID.isNull() && !task_url.empty()) + { + uploadInfo = std::make_shared(mObjectUUID, mItemUUID, LLAssetType::AT_GESTURE, buffer, nullptr, nullptr); + url = task_url; + } + + if (!url.empty() && uploadInfo) + { + delayedUpload = true; + + LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo); + } + + } + else if (gAssetStorage) + { + // Every save gets a new UUID. Yup. + LLTransactionID tid; + tid.generate(); + assetId = tid.makeAssetID(gAgent.getSecureSessionID()); + + LLFileSystem file(assetId, LLAssetType::AT_GESTURE, LLFileSystem::APPEND); + + S32 size = dp.getCurrentSize(); + file.write((U8*)buffer, size); + + LLLineEditor* descEditor = getChild("desc"); + LLSaveInfo* info = new LLSaveInfo(mItemUUID, mObjectUUID, descEditor->getText(), tid); + gAssetStorage->storeAssetData(tid, LLAssetType::AT_GESTURE, onSaveComplete, info, false); + } + + } + + // If this gesture is active, then we need to update the in-memory + // active map with the new pointer. + if (!delayedUpload && LLGestureMgr::instance().isGestureActive(mItemUUID)) + { + // gesture manager now owns the pointer + LLGestureMgr::instance().replaceGesture(mItemUUID, gesture, assetId); + + // replaceGesture may deactivate other gestures so let the + // inventory know. + gInventory.notifyObservers(); + } + else + { + // we're done with this gesture + delete gesture; + gesture = NULL; + } + + mDirty = false; + // refresh will be called when callback + // if triggered when delayedUpload + if(!delayedUpload) + { + refresh(); + } + +} + + +// TODO: This is very similar to LLPreviewNotecard::onSaveComplete. +// Could merge code. +// static +void LLPreviewGesture::onSaveComplete(const LLUUID& asset_uuid, void* user_data, S32 status, LLExtStat ext_status) // StoreAssetData callback (fixed) +{ + LLSaveInfo* info = (LLSaveInfo*)user_data; + if (info && (status == 0)) + { + if(info->mObjectUUID.isNull()) + { + // Saving into user inventory + LLViewerInventoryItem* item; + item = (LLViewerInventoryItem*)gInventory.getItem(info->mItemUUID); + if(item) + { + LLPointer new_item = new LLViewerInventoryItem(item); + new_item->setDescription(info->mDesc); + new_item->setTransactionID(info->mTransactionID); + new_item->setAssetUUID(asset_uuid); + new_item->updateServer(false); + gInventory.updateItem(new_item); + gInventory.notifyObservers(); + } + else + { + LL_WARNS() << "Inventory item for gesture " << info->mItemUUID + << " is no longer in agent inventory." << LL_ENDL; + } + } + else + { + // Saving into in-world object inventory + LLViewerObject* object = gObjectList.findObject(info->mObjectUUID); + LLViewerInventoryItem* item = NULL; + if(object) + { + item = (LLViewerInventoryItem*)object->getInventoryObject(info->mItemUUID); + } + if(object && item) + { + item->setDescription(info->mDesc); + item->setAssetUUID(asset_uuid); + item->setTransactionID(info->mTransactionID); + object->updateInventory(item, TASK_INVENTORY_ITEM_KEY, false); + dialog_refresh_all(); + } + else + { + LLNotificationsUtil::add("GestureSaveFailedObjectNotFound"); + } + } + + // Find our window and close it if requested. + LLPreviewGesture* previewp = LLFloaterReg::findTypedInstance("preview_gesture", info->mItemUUID); + if (previewp && previewp->mCloseAfterSave) + { + previewp->closeFloater(); + } + } + else + { + LL_WARNS() << "Problem saving gesture: " << status << LL_ENDL; + LLSD args; + args["REASON"] = std::string(LLAssetStorage::getErrorString(status)); + LLNotificationsUtil::add("GestureSaveFailedReason", args); + } + delete info; + info = NULL; +} + + +LLMultiGesture* LLPreviewGesture::createGesture() +{ + LLMultiGesture* gesture = new LLMultiGesture(); + + gesture->mTrigger = mTriggerEditor->getText(); + gesture->mReplaceText = mReplaceEditor->getText(); + + const std::string& modifier = mModifierCombo->getSimple(); + if (modifier == CTRL_LABEL) + { + gesture->mMask = MASK_CONTROL; + } + else if (modifier == SHIFT_LABEL) + { + gesture->mMask = MASK_SHIFT; + } + else + { + gesture->mMask = MASK_NONE; + } + + if (mKeyCombo->getCurrentIndex() == 0) + { + gesture->mKey = KEY_NONE; + } + else + { + const std::string& key_string = mKeyCombo->getSimple(); + LLKeyboard::keyFromString(key_string, &(gesture->mKey)); + } + + std::vector data_list = mStepList->getAllData(); + std::vector::iterator data_itor; + for (data_itor = data_list.begin(); data_itor != data_list.end(); ++data_itor) + { + LLScrollListItem* item = *data_itor; + LLGestureStep* step = (LLGestureStep*)item->getUserdata(); + + switch(step->getType()) + { + case STEP_ANIMATION: + { + // Copy UI-generated step into actual gesture step + LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step; + LLGestureStepAnimation* new_anim_step = + new LLGestureStepAnimation(*anim_step); + gesture->mSteps.push_back(new_anim_step); + break; + } + case STEP_SOUND: + { + // Copy UI-generated step into actual gesture step + LLGestureStepSound* sound_step = (LLGestureStepSound*)step; + LLGestureStepSound* new_sound_step = + new LLGestureStepSound(*sound_step); + gesture->mSteps.push_back(new_sound_step); + break; + } + case STEP_CHAT: + { + // Copy UI-generated step into actual gesture step + LLGestureStepChat* chat_step = (LLGestureStepChat*)step; + LLGestureStepChat* new_chat_step = + new LLGestureStepChat(*chat_step); + gesture->mSteps.push_back(new_chat_step); + break; + } + case STEP_WAIT: + { + // Copy UI-generated step into actual gesture step + LLGestureStepWait* wait_step = (LLGestureStepWait*)step; + LLGestureStepWait* new_wait_step = + new LLGestureStepWait(*wait_step); + gesture->mSteps.push_back(new_wait_step); + break; + } + default: + { + break; + } + } + } + + return gesture; +} + + +void LLPreviewGesture::onCommitKeyorModifier() +{ + // SL-14139: ctrl-F10 is currently used to access top menu, + // so don't allow to bound gestures to this combination. + + mKeyCombo->setEnabledByValue(LLKeyboard::stringFromKey(KEY_F10), mModifierCombo->getSimple() != CTRL_LABEL); + mModifierCombo->setEnabledByValue(CTRL_LABEL, mKeyCombo->getSimple() != LLKeyboard::stringFromKey(KEY_F10)); + mDirty = true; + refresh(); +} + +// static +void LLPreviewGesture::updateLabel(LLScrollListItem* item) +{ + LLGestureStep* step = (LLGestureStep*)item->getUserdata(); + + LLScrollListCell* cell = item->getColumn(0); + LLScrollListText* text_cell = (LLScrollListText*)cell; + std::string label = getLabel( step->getLabel()); + text_cell->setText(label); +} + +// static +void LLPreviewGesture::onCommitSetDirty(LLUICtrl* ctrl, void* data) +{ + LLPreviewGesture* self = (LLPreviewGesture*)data; + self->mDirty = true; + self->refresh(); +} + +// static +void LLPreviewGesture::onCommitLibrary(LLUICtrl* ctrl, void* data) +{ + LLPreviewGesture* self = (LLPreviewGesture*)data; + + LLScrollListItem* library_item = self->mLibraryList->getFirstSelected(); + if (library_item) + { + self->mStepList->deselectAllItems(); + self->refresh(); + } +} + + +// static +void LLPreviewGesture::onCommitStep(LLUICtrl* ctrl, void* data) +{ + LLPreviewGesture* self = (LLPreviewGesture*)data; + + LLScrollListItem* step_item = self->mStepList->getFirstSelected(); + if (!step_item) return; + + self->mLibraryList->deselectAllItems(); + self->refresh(); +} + + +// static +void LLPreviewGesture::onCommitAnimation(LLUICtrl* ctrl, void* data) +{ + LLPreviewGesture* self = (LLPreviewGesture*)data; + + LLScrollListItem* step_item = self->mStepList->getFirstSelected(); + if (step_item) + { + LLGestureStep* step = (LLGestureStep*)step_item->getUserdata(); + if (step->getType() == STEP_ANIMATION) + { + // Assign the animation name + LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step; + if (self->mAnimationCombo->getCurrentIndex() == 0) + { + anim_step->mAnimName.clear(); + anim_step->mAnimAssetID.setNull(); + } + else + { + anim_step->mAnimName = self->mAnimationCombo->getSimple(); + anim_step->mAnimAssetID = self->mAnimationCombo->getCurrentID(); + } + //anim_step->mFlags = 0x0; + + // Update the UI label in the list + updateLabel(step_item); + + self->mDirty = true; + self->refresh(); + } + } +} + +// static +void LLPreviewGesture::onCommitAnimationTrigger(LLUICtrl* ctrl, void *data) +{ + LLPreviewGesture* self = (LLPreviewGesture*)data; + + LLScrollListItem* step_item = self->mStepList->getFirstSelected(); + if (step_item) + { + LLGestureStep* step = (LLGestureStep*)step_item->getUserdata(); + if (step->getType() == STEP_ANIMATION) + { + LLGestureStepAnimation* anim_step = (LLGestureStepAnimation*)step; + if (self->mAnimationRadio->getSelectedIndex() == 0) + { + // start + anim_step->mFlags &= ~ANIM_FLAG_STOP; + } + else + { + // stop + anim_step->mFlags |= ANIM_FLAG_STOP; + } + // Update the UI label in the list + updateLabel(step_item); + + self->mDirty = true; + self->refresh(); + } + } +} + +// static +void LLPreviewGesture::onCommitSound(LLUICtrl* ctrl, void* data) +{ + LLPreviewGesture* self = (LLPreviewGesture*)data; + + LLScrollListItem* step_item = self->mStepList->getFirstSelected(); + if (step_item) + { + LLGestureStep* step = (LLGestureStep*)step_item->getUserdata(); + if (step->getType() == STEP_SOUND) + { + // Assign the sound name + LLGestureStepSound* sound_step = (LLGestureStepSound*)step; + sound_step->mSoundName = self->mSoundCombo->getSimple(); + sound_step->mSoundAssetID = self->mSoundCombo->getCurrentID(); + sound_step->mFlags = 0x0; + + // Update the UI label in the list + updateLabel(step_item); + + self->mDirty = true; + self->refresh(); + } + } +} + +// static +void LLPreviewGesture::onCommitChat(LLUICtrl* ctrl, void* data) +{ + LLPreviewGesture* self = (LLPreviewGesture*)data; + + LLScrollListItem* step_item = self->mStepList->getFirstSelected(); + if (!step_item) return; + + LLGestureStep* step = (LLGestureStep*)step_item->getUserdata(); + if (step->getType() != STEP_CHAT) return; + + LLGestureStepChat* chat_step = (LLGestureStepChat*)step; + chat_step->mChatText = self->mChatEditor->getText(); + chat_step->mFlags = 0x0; + + // Update the UI label in the list + updateLabel(step_item); + + self->mDirty = true; + self->refresh(); +} + +// static +void LLPreviewGesture::onCommitWait(LLUICtrl* ctrl, void* data) +{ + LLPreviewGesture* self = (LLPreviewGesture*)data; + + LLScrollListItem* step_item = self->mStepList->getFirstSelected(); + if (!step_item) return; + + LLGestureStep* step = (LLGestureStep*)step_item->getUserdata(); + if (step->getType() != STEP_WAIT) return; + + LLGestureStepWait* wait_step = (LLGestureStepWait*)step; + U32 flags = 0x0; + if (self->mWaitKeyReleaseCheck->get()) flags |= WAIT_FLAG_KEY_RELEASE; + if (self->mWaitAnimCheck->get()) flags |= WAIT_FLAG_ALL_ANIM; + if (self->mWaitTimeCheck->get()) flags |= WAIT_FLAG_TIME; + wait_step->mFlags = flags; + + { + LLLocale locale(LLLocale::USER_LOCALE); + + F32 wait_seconds = (F32)atof(self->mWaitTimeEditor->getText().c_str()); + if (wait_seconds < 0.f) wait_seconds = 0.f; + if (wait_seconds > 3600.f) wait_seconds = 3600.f; + wait_step->mWaitSeconds = wait_seconds; + } + + // Enable the input area if necessary + self->mWaitTimeEditor->setEnabled(self->mWaitTimeCheck->get()); + + // Update the UI label in the list + updateLabel(step_item); + + self->mDirty = true; + self->refresh(); +} + +// static +void LLPreviewGesture::onCommitWaitTime(LLUICtrl* ctrl, void* data) +{ + LLPreviewGesture* self = (LLPreviewGesture*)data; + + LLScrollListItem* step_item = self->mStepList->getFirstSelected(); + if (!step_item) return; + + LLGestureStep* step = (LLGestureStep*)step_item->getUserdata(); + if (step->getType() != STEP_WAIT) return; + + self->mWaitTimeCheck->set(true); + onCommitWait(ctrl, data); +} + + +// static +void LLPreviewGesture::onKeystrokeCommit(LLLineEditor* caller, + void* data) +{ + // Just commit every keystroke + onCommitSetDirty(caller, data); +} + +// static +void LLPreviewGesture::onClickAdd(void* data) +{ + LLPreviewGesture* self = (LLPreviewGesture*)data; + + LLScrollListItem* library_item = self->mLibraryList->getFirstSelected(); + if (!library_item) return; + + S32 library_item_index = self->mLibraryList->getFirstSelectedIndex(); + + const LLScrollListCell* library_cell = library_item->getColumn(0); + const std::string& library_text = library_cell->getValue().asString(); + + if( library_item_index >= STEP_EOF ) + { + LL_ERRS() << "Unknown step type: " << library_text << LL_ENDL; + return; + } + + self->addStep( (EStepType)library_item_index ); + self->mDirty = true; + self->refresh(); +} + +LLScrollListItem* LLPreviewGesture::addStep( const EStepType step_type ) +{ + // Order of enum EStepType MUST match the library_list element in floater_preview_gesture.xml + + LLGestureStep* step = NULL; + switch( step_type) + { + case STEP_ANIMATION: + step = new LLGestureStepAnimation(); + + break; + case STEP_SOUND: + step = new LLGestureStepSound(); + break; + case STEP_CHAT: + step = new LLGestureStepChat(); + break; + case STEP_WAIT: + step = new LLGestureStepWait(); + break; + default: + LL_ERRS() << "Unknown step type: " << (S32)step_type << LL_ENDL; + return NULL; + } + + + // Create an enabled item with this step + LLSD row; + row["columns"][0]["value"] = getLabel(step->getLabel()); + row["columns"][0]["font"] = "SANSSERIF_SMALL"; + LLScrollListItem* step_item = mStepList->addElement(row); + step_item->setUserdata(step); + + // And move selection to the list on the right + mLibraryList->deselectAllItems(); + mStepList->deselectAllItems(); + + step_item->setSelected(true); + + return step_item; +} + +// static +std::string LLPreviewGesture::getLabel(std::vector labels) +{ + std::vector v_labels = labels ; + std::string result(""); + + if( v_labels.size() != 2) + { + return result; + } + + if(v_labels[0]=="Chat") + { + result=LLTrans::getString("Chat Message"); + } + else if(v_labels[0]=="Sound") + { + result=LLTrans::getString("Sound"); + } + else if(v_labels[0]=="Wait") + { + result=LLTrans::getString("Wait"); + } + else if(v_labels[0]=="AnimFlagStop") + { + result=LLTrans::getString("AnimFlagStop"); + } + else if(v_labels[0]=="AnimFlagStart") + { + result=LLTrans::getString("AnimFlagStart"); + } + + // lets localize action value + std::string action = v_labels[1]; + if ("None" == action) + { + action = LLTrans::getString("GestureActionNone"); + } + else if ("until animations are done" == action) + { + action = LLFloaterReg::getInstance("preview_gesture")->getChild("wait_anim_check")->getLabel(); + } + result.append(action); + return result; + +} +// static +void LLPreviewGesture::onClickUp(void* data) +{ + LLPreviewGesture* self = (LLPreviewGesture*)data; + + S32 selected_index = self->mStepList->getFirstSelectedIndex(); + if (selected_index > 0) + { + self->mStepList->swapWithPrevious(selected_index); + self->mDirty = true; + self->refresh(); + } +} + +// static +void LLPreviewGesture::onClickDown(void* data) +{ + LLPreviewGesture* self = (LLPreviewGesture*)data; + + S32 selected_index = self->mStepList->getFirstSelectedIndex(); + if (selected_index < 0) return; + + S32 count = self->mStepList->getItemCount(); + if (selected_index < count-1) + { + self->mStepList->swapWithNext(selected_index); + self->mDirty = true; + self->refresh(); + } +} + +// static +void LLPreviewGesture::onClickDelete(void* data) +{ + LLPreviewGesture* self = (LLPreviewGesture*)data; + + LLScrollListItem* item = self->mStepList->getFirstSelected(); + S32 selected_index = self->mStepList->getFirstSelectedIndex(); + if (item && selected_index >= 0) + { + LLGestureStep* step = (LLGestureStep*)item->getUserdata(); + delete step; + step = NULL; + + self->mStepList->deleteSingleItem(selected_index); + + self->mDirty = true; + self->refresh(); + } +} + +// static +void LLPreviewGesture::onCommitActive(LLUICtrl* ctrl, void* data) +{ + LLPreviewGesture* self = (LLPreviewGesture*)data; + if (!LLGestureMgr::instance().isGestureActive(self->mItemUUID)) + { + LLGestureMgr::instance().activateGesture(self->mItemUUID); + } + else + { + LLGestureMgr::instance().deactivateGesture(self->mItemUUID); + } + + // Make sure the (active) label in the inventory gets updated. + LLViewerInventoryItem* item = gInventory.getItem(self->mItemUUID); + if (item) + { + gInventory.updateItem(item); + gInventory.notifyObservers(); + } + + self->refresh(); +} + +// static +void LLPreviewGesture::onClickSave(void* data) +{ + LLPreviewGesture* self = (LLPreviewGesture*)data; + self->saveIfNeeded(); +} + +// static +void LLPreviewGesture::onClickPreview(void* data) +{ + LLPreviewGesture* self = (LLPreviewGesture*)data; + + if (!self->mPreviewGesture) + { + // make temporary gesture + self->mPreviewGesture = self->createGesture(); + + // add a callback + self->mPreviewGesture->mDoneCallback = onDonePreview; + self->mPreviewGesture->mCallbackData = self; + + // set the button title + self->mPreviewBtn->setLabel(self->getString("stop_txt")); + + // play it, and delete when done + LLGestureMgr::instance().playGesture(self->mPreviewGesture); + + self->refresh(); + } + else + { + // Will call onDonePreview() below + LLGestureMgr::instance().stopGesture(self->mPreviewGesture); + + self->refresh(); + } +} + + +// static +void LLPreviewGesture::onDonePreview(LLMultiGesture* gesture, void* data) +{ + LLPreviewGesture* self = (LLPreviewGesture*)data; + + self->mPreviewBtn->setLabel(self->getString("preview_txt")); + + delete self->mPreviewGesture; + self->mPreviewGesture = NULL; + + self->refresh(); +} diff --git a/indra/newview/llpreviewgesture.h b/indra/newview/llpreviewgesture.h index c78ca5cd68..75f22df76f 100644 --- a/indra/newview/llpreviewgesture.h +++ b/indra/newview/llpreviewgesture.h @@ -1,170 +1,170 @@ -/** - * @file llpreviewgesture.h - * @brief Editing UI for inventory-based gestures. - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPREVIEWGESTURE_H -#define LL_LLPREVIEWGESTURE_H - -#include "llassettype.h" -#include "llpreview.h" -#include "llmultigesture.h" - -class LLLineEditor; -class LLTextBox; -class LLCheckBoxCtrl; -class LLComboBox; -class LLScrollListCtrl; -class LLScrollListItem; -class LLButton; -class LLRadioGroup; - -class LLPreviewGesture : public LLPreview -{ -public: - // Pass an object_id if this gesture is inside an object in the world, - // otherwise use LLUUID::null. - static LLPreviewGesture* show(const LLUUID& item_id, const LLUUID& object_id); - - LLPreviewGesture(const LLSD& key); - virtual ~LLPreviewGesture(); - - // LLView - /*virtual*/ void draw(); - /*virtual*/ bool handleKeyHere(KEY key, MASK mask); - /*virtual*/ bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - std::string& tooltip_msg); - - // LLPanel - /*virtual*/ bool postBuild(); - - // LLFloater - /*virtual*/ bool canClose(); - /*virtual*/ void onClose(bool app_quitting); - /*virtual*/ void onUpdateSucceeded(); - /*virtual*/ void refresh(); - -protected: - // Populate various comboboxes - void addModifiers(); - void addKeys(); - void addAnimations(); - void addSounds(); - - void initDefaultGesture(); - - void loadAsset(); - - static void onLoadComplete(const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status); - - void loadUIFromGesture(LLMultiGesture* gesture); - - void saveIfNeeded(); - - static void onSaveComplete(const LLUUID& asset_uuid, - void* user_data, - S32 status, LLExtStat ext_status); - - bool handleSaveChangesDialog(const LLSD& notification, const LLSD& response); - - // Write UI back into gesture - LLMultiGesture* createGesture(); - - // Add a step. Pass the name of the step, like "Animation", - // "Sound", "Chat", or "Wait" - LLScrollListItem* addStep(const enum EStepType step_type); - - void onVisibilityChanged ( const LLSD& new_visibility ); - - void onCommitKeyorModifier(); - - static std::string getLabel(std::vector labels); - static void updateLabel(LLScrollListItem* item); - - static void onCommitSetDirty(LLUICtrl* ctrl, void* data); - static void onCommitLibrary(LLUICtrl* ctrl, void* data); - static void onCommitStep(LLUICtrl* ctrl, void* data); - static void onCommitAnimation(LLUICtrl* ctrl, void* data); - static void onCommitSound(LLUICtrl* ctrl, void* data); - static void onCommitChat(LLUICtrl* ctrl, void* data); - static void onCommitWait(LLUICtrl* ctrl, void* data); - static void onCommitWaitTime(LLUICtrl* ctrl, void* data); - - static void onCommitAnimationTrigger(LLUICtrl* ctrl, void *data); - - // Handy function to commit each keystroke - static void onKeystrokeCommit(LLLineEditor* caller, void* data); - - static void onClickAdd(void* data); - static void onClickUp(void* data); - static void onClickDown(void* data); - static void onClickDelete(void* data); - - static void onCommitActive(LLUICtrl* ctrl, void* data); - static void onClickSave(void* data); - static void onClickPreview(void* data); - - static void onDonePreview(LLMultiGesture* gesture, void* data); - - static void finishInventoryUpload(LLUUID itemId, LLUUID newAssetId); -private: - // LLPreview contains mDescEditor - LLLineEditor* mTriggerEditor; - LLTextBox* mReplaceText; - LLLineEditor* mReplaceEditor; - LLComboBox* mModifierCombo; - LLComboBox* mKeyCombo; - - LLScrollListCtrl* mLibraryList; - LLButton* mAddBtn; - LLButton* mUpBtn; - LLButton* mDownBtn; - LLButton* mDeleteBtn; - LLScrollListCtrl* mStepList; - - // Options panels for items in gesture list - LLTextBox* mOptionsText; - LLRadioGroup* mAnimationRadio; - LLComboBox* mAnimationCombo; - LLComboBox* mSoundCombo; - LLLineEditor* mChatEditor; - LLCheckBoxCtrl* mWaitKeyReleaseCheck; - LLCheckBoxCtrl* mWaitAnimCheck; - LLCheckBoxCtrl* mWaitTimeCheck; - LLLineEditor* mWaitTimeEditor; - - LLCheckBoxCtrl* mActiveCheck; - LLButton* mSaveBtn; - LLButton* mPreviewBtn; - - LLMultiGesture* mPreviewGesture; - bool mDirty; -}; - -#endif // LL_LLPREVIEWGESTURE_H +/** + * @file llpreviewgesture.h + * @brief Editing UI for inventory-based gestures. + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPREVIEWGESTURE_H +#define LL_LLPREVIEWGESTURE_H + +#include "llassettype.h" +#include "llpreview.h" +#include "llmultigesture.h" + +class LLLineEditor; +class LLTextBox; +class LLCheckBoxCtrl; +class LLComboBox; +class LLScrollListCtrl; +class LLScrollListItem; +class LLButton; +class LLRadioGroup; + +class LLPreviewGesture : public LLPreview +{ +public: + // Pass an object_id if this gesture is inside an object in the world, + // otherwise use LLUUID::null. + static LLPreviewGesture* show(const LLUUID& item_id, const LLUUID& object_id); + + LLPreviewGesture(const LLSD& key); + virtual ~LLPreviewGesture(); + + // LLView + /*virtual*/ void draw(); + /*virtual*/ bool handleKeyHere(KEY key, MASK mask); + /*virtual*/ bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + + // LLPanel + /*virtual*/ bool postBuild(); + + // LLFloater + /*virtual*/ bool canClose(); + /*virtual*/ void onClose(bool app_quitting); + /*virtual*/ void onUpdateSucceeded(); + /*virtual*/ void refresh(); + +protected: + // Populate various comboboxes + void addModifiers(); + void addKeys(); + void addAnimations(); + void addSounds(); + + void initDefaultGesture(); + + void loadAsset(); + + static void onLoadComplete(const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status); + + void loadUIFromGesture(LLMultiGesture* gesture); + + void saveIfNeeded(); + + static void onSaveComplete(const LLUUID& asset_uuid, + void* user_data, + S32 status, LLExtStat ext_status); + + bool handleSaveChangesDialog(const LLSD& notification, const LLSD& response); + + // Write UI back into gesture + LLMultiGesture* createGesture(); + + // Add a step. Pass the name of the step, like "Animation", + // "Sound", "Chat", or "Wait" + LLScrollListItem* addStep(const enum EStepType step_type); + + void onVisibilityChanged ( const LLSD& new_visibility ); + + void onCommitKeyorModifier(); + + static std::string getLabel(std::vector labels); + static void updateLabel(LLScrollListItem* item); + + static void onCommitSetDirty(LLUICtrl* ctrl, void* data); + static void onCommitLibrary(LLUICtrl* ctrl, void* data); + static void onCommitStep(LLUICtrl* ctrl, void* data); + static void onCommitAnimation(LLUICtrl* ctrl, void* data); + static void onCommitSound(LLUICtrl* ctrl, void* data); + static void onCommitChat(LLUICtrl* ctrl, void* data); + static void onCommitWait(LLUICtrl* ctrl, void* data); + static void onCommitWaitTime(LLUICtrl* ctrl, void* data); + + static void onCommitAnimationTrigger(LLUICtrl* ctrl, void *data); + + // Handy function to commit each keystroke + static void onKeystrokeCommit(LLLineEditor* caller, void* data); + + static void onClickAdd(void* data); + static void onClickUp(void* data); + static void onClickDown(void* data); + static void onClickDelete(void* data); + + static void onCommitActive(LLUICtrl* ctrl, void* data); + static void onClickSave(void* data); + static void onClickPreview(void* data); + + static void onDonePreview(LLMultiGesture* gesture, void* data); + + static void finishInventoryUpload(LLUUID itemId, LLUUID newAssetId); +private: + // LLPreview contains mDescEditor + LLLineEditor* mTriggerEditor; + LLTextBox* mReplaceText; + LLLineEditor* mReplaceEditor; + LLComboBox* mModifierCombo; + LLComboBox* mKeyCombo; + + LLScrollListCtrl* mLibraryList; + LLButton* mAddBtn; + LLButton* mUpBtn; + LLButton* mDownBtn; + LLButton* mDeleteBtn; + LLScrollListCtrl* mStepList; + + // Options panels for items in gesture list + LLTextBox* mOptionsText; + LLRadioGroup* mAnimationRadio; + LLComboBox* mAnimationCombo; + LLComboBox* mSoundCombo; + LLLineEditor* mChatEditor; + LLCheckBoxCtrl* mWaitKeyReleaseCheck; + LLCheckBoxCtrl* mWaitAnimCheck; + LLCheckBoxCtrl* mWaitTimeCheck; + LLLineEditor* mWaitTimeEditor; + + LLCheckBoxCtrl* mActiveCheck; + LLButton* mSaveBtn; + LLButton* mPreviewBtn; + + LLMultiGesture* mPreviewGesture; + bool mDirty; +}; + +#endif // LL_LLPREVIEWGESTURE_H diff --git a/indra/newview/llpreviewnotecard.cpp b/indra/newview/llpreviewnotecard.cpp index 96ba5a1d76..6fa9c59194 100644 --- a/indra/newview/llpreviewnotecard.cpp +++ b/indra/newview/llpreviewnotecard.cpp @@ -1,918 +1,918 @@ -/** - * @file llpreviewnotecard.cpp - * @brief Implementation of the notecard editor - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpreviewnotecard.h" - -#include "llinventory.h" - -#include "llagent.h" -#include "lldraghandle.h" -#include "llexternaleditor.h" -#include "llviewerwindow.h" -#include "llbutton.h" -#include "llfloaterreg.h" -#include "llinventorydefines.h" -#include "llinventorymodel.h" -#include "lllineeditor.h" -#include "llmd5.h" -#include "llnotificationsutil.h" -#include "llmd5.h" -#include "llresmgr.h" -#include "roles_constants.h" -#include "llscrollbar.h" -#include "llselectmgr.h" -#include "lltrans.h" -#include "llviewertexteditor.h" -#include "llfilesystem.h" -#include "llviewerinventory.h" -#include "llviewerobject.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "lldir.h" -#include "llviewerstats.h" -#include "llviewercontrol.h" // gSavedSettings -#include "llappviewer.h" // app_abort_quit() -#include "lllineeditor.h" -#include "lluictrlfactory.h" -#include "llviewerassetupload.h" - -///---------------------------------------------------------------------------- -/// Class LLPreviewNotecard -///---------------------------------------------------------------------------- - -// Default constructor -LLPreviewNotecard::LLPreviewNotecard(const LLSD& key) //const LLUUID& item_id, - : LLPreview( key ), - mLiveFile(NULL) -{ - const LLInventoryItem *item = getItem(); - if (item) - { - mAssetID = item->getAssetUUID(); - } -} - -LLPreviewNotecard::~LLPreviewNotecard() -{ - delete mLiveFile; -} - -bool LLPreviewNotecard::postBuild() -{ - mEditor = getChild("Notecard Editor"); - mEditor->setNotecardInfo(mItemUUID, mObjectID, getKey()); - mEditor->makePristine(); - - childSetAction("Save", onClickSave, this); - getChildView("lock")->setVisible( false); - - childSetAction("Delete", onClickDelete, this); - getChildView("Delete")->setEnabled(false); - - childSetAction("Edit", onClickEdit, this); - - const LLInventoryItem* item = getItem(); - - childSetCommitCallback("desc", LLPreview::onText, this); - if (item) - { - getChild("desc")->setValue(item->getDescription()); - bool source_library = mObjectUUID.isNull() && gInventory.isObjectDescendentOf(item->getUUID(), gInventory.getLibraryRootFolderID()); - getChildView("Delete")->setEnabled(!source_library); - } - getChild("desc")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); - - return LLPreview::postBuild(); -} - -bool LLPreviewNotecard::saveItem() -{ - LLInventoryItem* item = gInventory.getItem(mItemUUID); - return saveIfNeeded(item); -} - -void LLPreviewNotecard::setEnabled(bool enabled) -{ - - LLViewerTextEditor* editor = getChild("Notecard Editor"); - - getChildView("Notecard Editor")->setEnabled(enabled); - getChildView("lock")->setVisible( !enabled); - getChildView("desc")->setEnabled(enabled); - getChildView("Save")->setEnabled(enabled && editor && (!editor->isPristine())); -} - - -void LLPreviewNotecard::draw() -{ - LLViewerTextEditor* editor = getChild("Notecard Editor"); - bool changed = !editor->isPristine(); - - getChildView("Save")->setEnabled(changed && getEnabled()); - - LLPreview::draw(); -} - -// virtual -bool LLPreviewNotecard::handleKeyHere(KEY key, MASK mask) -{ - if(('S' == key) && (MASK_CONTROL == (mask & MASK_CONTROL))) - { - saveIfNeeded(); - return true; - } - - return LLPreview::handleKeyHere(key, mask); -} - -// virtual -bool LLPreviewNotecard::canClose() -{ - LLViewerTextEditor* editor = getChild("Notecard Editor"); - - if(mForceClose || editor->isPristine()) - { - return true; - } - else - { - if(!mSaveDialogShown) - { - mSaveDialogShown = true; - // Bring up view-modal dialog: Save changes? Yes, No, Cancel - LLNotificationsUtil::add("SaveChanges", LLSD(), LLSD(), boost::bind(&LLPreviewNotecard::handleSaveChangesDialog,this, _1, _2)); - } - return false; - } -} - -/* virtual */ -void LLPreviewNotecard::setObjectID(const LLUUID& object_id) -{ - LLPreview::setObjectID(object_id); - - LLViewerTextEditor* editor = getChild("Notecard Editor"); - editor->setNotecardObjectID(mObjectUUID); - editor->makePristine(); -} - -const LLInventoryItem* LLPreviewNotecard::getDragItem() -{ - LLViewerTextEditor* editor = getChild("Notecard Editor"); - - if(editor) - { - return editor->getDragItem(); - } - return NULL; -} - -bool LLPreviewNotecard::hasEmbeddedInventory() -{ - LLViewerTextEditor* editor = NULL; - editor = getChild("Notecard Editor"); - if (!editor) return false; - return editor->hasEmbeddedInventory(); -} - -void LLPreviewNotecard::refreshFromInventory(const LLUUID& new_item_id) -{ - if (new_item_id.notNull()) - { - mItemUUID = new_item_id; - setKey(LLSD(new_item_id)); - } - LL_DEBUGS() << "LLPreviewNotecard::refreshFromInventory()" << LL_ENDL; - loadAsset(); -} - -void LLPreviewNotecard::updateTitleButtons() -{ - LLPreview::updateTitleButtons(); - - LLUICtrl* lock_btn = getChild("lock"); - if(lock_btn->getVisible() && !isMinimized()) // lock button stays visible if floater is minimized. - { - LLRect lock_rc = lock_btn->getRect(); - LLRect buttons_rect = getDragHandle()->getButtonsRect(); - buttons_rect.mLeft = lock_rc.mLeft; - getDragHandle()->setButtonsRect(buttons_rect); - } -} - -void LLPreviewNotecard::loadAsset() -{ - // request the asset. - const LLInventoryItem* item = getItem(); - LLViewerTextEditor* editor = getChild("Notecard Editor"); - - if (!editor) - return; - - bool fail = false; - - if(item) - { - LLPermissions perm(item->getPermissions()); - bool is_owner = gAgent.allowOperation(PERM_OWNER, perm, GP_OBJECT_MANIPULATE); - bool allow_copy = gAgent.allowOperation(PERM_COPY, perm, GP_OBJECT_MANIPULATE); - bool allow_modify = canModify(mObjectUUID, item); - bool source_library = mObjectUUID.isNull() && gInventory.isObjectDescendentOf(mItemUUID, gInventory.getLibraryRootFolderID()); - - if (allow_copy || gAgent.isGodlike()) - { - mAssetID = item->getAssetUUID(); - if(mAssetID.isNull()) - { - editor->setText(LLStringUtil::null); - editor->makePristine(); - editor->setEnabled(true); - mAssetStatus = PREVIEW_ASSET_LOADED; - } - else - { - LLHost source_sim = LLHost(); - LLSD* user_data = nullptr; - if (mObjectUUID.notNull()) - { - LLViewerObject *objectp = gObjectList.findObject(mObjectUUID); - if (objectp && objectp->getRegion()) - { - source_sim = objectp->getRegion()->getHost(); - } - else - { - // The object that we're trying to look at disappeared, bail. - LL_WARNS() << "Can't find object " << mObjectUUID << " associated with notecard." << LL_ENDL; - mAssetID.setNull(); - editor->setText(getString("no_object")); - editor->makePristine(); - editor->setEnabled(false); - mAssetStatus = PREVIEW_ASSET_LOADED; - return; - } - user_data = new LLSD(); - user_data->with("taskid", mObjectUUID).with("itemid", mItemUUID); - } - else - { - user_data = new LLSD(mItemUUID); - } - - gAssetStorage->getInvItemAsset(source_sim, - gAgent.getID(), - gAgent.getSessionID(), - item->getPermissions().getOwner(), - mObjectUUID, - item->getUUID(), - item->getAssetUUID(), - item->getType(), - &onLoadComplete, - (void*)user_data, - true); - mAssetStatus = PREVIEW_ASSET_LOADING; - } - } - else - { - mAssetID.setNull(); - editor->setText(getString("not_allowed")); - editor->makePristine(); - editor->setEnabled(false); - mAssetStatus = PREVIEW_ASSET_LOADED; - } - - if(!allow_modify) - { - editor->setEnabled(false); - getChildView("lock")->setVisible( true); - getChildView("Edit")->setEnabled(false); - } - - if((allow_modify || is_owner) && !source_library) - { - getChildView("Delete")->setEnabled(true); - } - } - else if (mObjectUUID.notNull() && mItemUUID.notNull()) - { - LLViewerObject* objectp = gObjectList.findObject(mObjectUUID); - if (objectp && (objectp->isInventoryPending() || objectp->isInventoryDirty())) - { - // It's a notecard in object's inventory and we failed to get it because inventory is not up to date. - // Subscribe for callback and retry at inventoryChanged() - registerVOInventoryListener(objectp, NULL); //removes previous listener - - if (objectp->isInventoryDirty()) - { - objectp->requestInventory(); - } - } - else - { - fail = true; - } - } - else - { - fail = true; - } - - if (fail) - { - editor->setText(LLStringUtil::null); - editor->makePristine(); - editor->setEnabled(true); - // Don't set asset status here; we may not have set the item id yet - // (e.g. when this gets called initially) - //mAssetStatus = PREVIEW_ASSET_LOADED; - } -} - -// static -void LLPreviewNotecard::onLoadComplete(const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status) -{ - LL_INFOS() << "LLPreviewNotecard::onLoadComplete()" << LL_ENDL; - LLSD* floater_key = (LLSD*)user_data; - LLPreviewNotecard* preview = LLFloaterReg::findTypedInstance("preview_notecard", *floater_key); - if( preview ) - { - if(0 == status) - { - LLFileSystem file(asset_uuid, type, LLFileSystem::READ); - - S32 file_length = file.getSize(); - - std::vector buffer(file_length+1); - file.read((U8*)&buffer[0], file_length); - - // put a EOS at the end - buffer[file_length] = 0; - - - LLViewerTextEditor* previewEditor = preview->getChild("Notecard Editor"); - - if( (file_length > 19) && !strncmp( &buffer[0], "Linden text version", 19 ) ) - { - if( !previewEditor->importBuffer( &buffer[0], file_length+1 ) ) - { - LL_WARNS() << "Problem importing notecard" << LL_ENDL; - } - } - else - { - // Version 0 (just text, doesn't include version number) - previewEditor->setText(LLStringExplicit(&buffer[0])); - } - - previewEditor->makePristine(); - bool modifiable = preview->canModify(preview->mObjectID, preview->getItem()); - preview->setEnabled(modifiable); - preview->syncExternal(); - preview->mAssetStatus = PREVIEW_ASSET_LOADED; - } - else - { - if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status || - LL_ERR_FILE_EMPTY == status) - { - LLNotificationsUtil::add("NotecardMissing"); - } - else if (LL_ERR_INSUFFICIENT_PERMISSIONS == status) - { - LLNotificationsUtil::add("NotecardNoPermissions"); - } - else - { - LLNotificationsUtil::add("UnableToLoadNotecard"); - } - - LL_WARNS() << "Problem loading notecard: " << status << LL_ENDL; - preview->mAssetStatus = PREVIEW_ASSET_ERROR; - } - } - delete floater_key; -} - -// static -void LLPreviewNotecard::onClickSave(void* user_data) -{ - //LL_INFOS() << "LLPreviewNotecard::onBtnSave()" << LL_ENDL; - LLPreviewNotecard* preview = (LLPreviewNotecard*)user_data; - if(preview) - { - preview->saveIfNeeded(); - } -} - - -// static -void LLPreviewNotecard::onClickDelete(void* user_data) -{ - LLPreviewNotecard* preview = (LLPreviewNotecard*)user_data; - if(preview) - { - preview->deleteNotecard(); - } -} - -// static -void LLPreviewNotecard::onClickEdit(void* user_data) -{ - LLPreviewNotecard* preview = (LLPreviewNotecard*)user_data; - if (preview) - { - preview->openInExternalEditor(); - } -} - -struct LLSaveNotecardInfo -{ - LLPreviewNotecard* mSelf; - LLUUID mItemUUID; - LLUUID mObjectUUID; - LLTransactionID mTransactionID; - LLPointer mCopyItem; - LLSaveNotecardInfo(LLPreviewNotecard* self, const LLUUID& item_id, const LLUUID& object_id, - const LLTransactionID& transaction_id, LLInventoryItem* copyitem) : - mSelf(self), mItemUUID(item_id), mObjectUUID(object_id), mTransactionID(transaction_id), mCopyItem(copyitem) - { - } -}; - -void LLPreviewNotecard::finishInventoryUpload(LLUUID itemId, LLUUID newAssetId, LLUUID newItemId) -{ - // Update the UI with the new asset. - LLPreviewNotecard* nc = LLFloaterReg::findTypedInstance("preview_notecard", LLSD(itemId)); - if (nc) - { - // *HACK: we have to delete the asset in the cache so - // that the viewer will redownload it. This is only - // really necessary if the asset had to be modified by - // the uploader, so this can be optimized away in some - // cases. A better design is to have a new uuid if the - // script actually changed the asset. - if (nc->hasEmbeddedInventory()) - { - LLFileSystem::removeFile(newAssetId, LLAssetType::AT_NOTECARD); - } - if (newItemId.isNull()) - { - nc->setAssetId(newAssetId); - nc->refreshFromInventory(); - } - else - { - nc->refreshFromInventory(newItemId); - } - } -} - -void LLPreviewNotecard::finishTaskUpload(LLUUID itemId, LLUUID newAssetId, LLUUID taskId) -{ - - LLSD floater_key; - floater_key["taskid"] = taskId; - floater_key["itemid"] = itemId; - LLPreviewNotecard* nc = LLFloaterReg::findTypedInstance("preview_notecard", floater_key); - if (nc) - { - if (nc->hasEmbeddedInventory()) - { - LLFileSystem::removeFile(newAssetId, LLAssetType::AT_NOTECARD); - } - nc->setAssetId(newAssetId); - nc->refreshFromInventory(); - } -} - -bool LLPreviewNotecard::saveIfNeeded(LLInventoryItem* copyitem, bool sync) -{ - LLViewerTextEditor* editor = getChild("Notecard Editor"); - - if(!editor) - { - LL_WARNS() << "Cannot get handle to the notecard editor." << LL_ENDL; - return false; - } - - if(!editor->isPristine()) - { - std::string buffer; - if (!editor->exportBuffer(buffer)) - { - return false; - } - - editor->makePristine(); - const LLInventoryItem* item = getItem(); - // save it out to database - if (item) - { - const LLViewerRegion* region = gAgent.getRegion(); - if (!region) - { - LL_WARNS() << "Not connected to a region, cannot save notecard." << LL_ENDL; - return false; - } - std::string agent_url = region->getCapability("UpdateNotecardAgentInventory"); - std::string task_url = region->getCapability("UpdateNotecardTaskInventory"); - - if (!agent_url.empty() && !task_url.empty()) - { - std::string url; - LLResourceUploadInfo::ptr_t uploadInfo; - - if (mObjectUUID.isNull() && !agent_url.empty()) - { - uploadInfo = std::make_shared(mItemUUID, LLAssetType::AT_NOTECARD, buffer, - [](LLUUID itemId, LLUUID newAssetId, LLUUID newItemId, LLSD) { - LLPreviewNotecard::finishInventoryUpload(itemId, newAssetId, newItemId); - }, - nullptr); - url = agent_url; - } - else if (!mObjectUUID.isNull() && !task_url.empty()) - { - LLUUID object_uuid(mObjectUUID); - uploadInfo = std::make_shared(mObjectUUID, mItemUUID, LLAssetType::AT_NOTECARD, buffer, - [object_uuid](LLUUID itemId, LLUUID, LLUUID newAssetId, LLSD) { - LLPreviewNotecard::finishTaskUpload(itemId, newAssetId, object_uuid); - }, - nullptr); - url = task_url; - } - - if (!url.empty() && uploadInfo) - { - mAssetStatus = PREVIEW_ASSET_LOADING; - setEnabled(false); - - LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo); - } - - } - else if (gAssetStorage) - { - // We need to update the asset information - LLTransactionID tid; - LLAssetID asset_id; - tid.generate(); - asset_id = tid.makeAssetID(gAgent.getSecureSessionID()); - - LLFileSystem file(asset_id, LLAssetType::AT_NOTECARD, LLFileSystem::APPEND); - - - LLSaveNotecardInfo* info = new LLSaveNotecardInfo(this, mItemUUID, mObjectUUID, - tid, copyitem); - - S32 size = buffer.length() + 1; - file.write((U8*)buffer.c_str(), size); - - gAssetStorage->storeAssetData(tid, LLAssetType::AT_NOTECARD, - &onSaveComplete, - (void*)info, - false); - return true; - } - else // !gAssetStorage - { - LL_WARNS() << "Not connected to an asset storage system." << LL_ENDL; - return false; - } - if(mCloseAfterSave) - { - closeFloater(); - } - } - } - return true; -} - -void LLPreviewNotecard::syncExternal() -{ - // Sync with external editor. - std::string tmp_file = getTmpFileName(); - llstat s; - if (LLFile::stat(tmp_file, &s) == 0) // file exists - { - if (mLiveFile) mLiveFile->ignoreNextUpdate(); - writeToFile(tmp_file); - } -} - -/*virtual*/ -void LLPreviewNotecard::inventoryChanged(LLViewerObject* object, - LLInventoryObject::object_list_t* inventory, - S32 serial_num, - void* user_data) -{ - removeVOInventoryListener(); - loadAsset(); -} - - -void LLPreviewNotecard::deleteNotecard() -{ - LLNotificationsUtil::add("DeleteNotecard", LLSD(), LLSD(), boost::bind(&LLPreviewNotecard::handleConfirmDeleteDialog,this, _1, _2)); -} - -// static -void LLPreviewNotecard::onSaveComplete(const LLUUID& asset_uuid, void* user_data, S32 status, LLExtStat ext_status) // StoreAssetData callback (fixed) -{ - LLSaveNotecardInfo* info = (LLSaveNotecardInfo*)user_data; - if(info && (0 == status)) - { - if(info->mObjectUUID.isNull()) - { - LLViewerInventoryItem* item; - item = (LLViewerInventoryItem*)gInventory.getItem(info->mItemUUID); - if(item) - { - LLPointer new_item = new LLViewerInventoryItem(item); - new_item->setAssetUUID(asset_uuid); - new_item->setTransactionID(info->mTransactionID); - new_item->updateServer(false); - gInventory.updateItem(new_item); - gInventory.notifyObservers(); - } - else - { - LL_WARNS() << "Inventory item for script " << info->mItemUUID - << " is no longer in agent inventory." << LL_ENDL; - } - } - else - { - LLViewerObject* object = gObjectList.findObject(info->mObjectUUID); - LLViewerInventoryItem* item = NULL; - if(object) - { - item = (LLViewerInventoryItem*)object->getInventoryObject(info->mItemUUID); - } - if(object && item) - { - item->setAssetUUID(asset_uuid); - item->setTransactionID(info->mTransactionID); - object->updateInventory(item, TASK_INVENTORY_ITEM_KEY, false); - dialog_refresh_all(); - } - else - { - LLNotificationsUtil::add("SaveNotecardFailObjectNotFound"); - } - } - // Perform item copy to inventory - if (info->mCopyItem.notNull()) - { - LLViewerTextEditor* editor = info->mSelf->getChild("Notecard Editor"); - if (editor) - { - editor->copyInventory(info->mCopyItem); - } - } - - // Find our window and close it if requested. - - LLPreviewNotecard* previewp = LLFloaterReg::findTypedInstance("preview_notecard", info->mItemUUID); - if (previewp && previewp->mCloseAfterSave) - { - previewp->closeFloater(); - } - } - else - { - LL_WARNS() << "Problem saving notecard: " << status << LL_ENDL; - LLSD args; - args["REASON"] = std::string(LLAssetStorage::getErrorString(status)); - LLNotificationsUtil::add("SaveNotecardFailReason", args); - } - - std::string uuid_string; - asset_uuid.toString(uuid_string); - std::string filename; - filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,uuid_string) + ".tmp"; - LLFile::remove(filename); - delete info; -} - -bool LLPreviewNotecard::handleSaveChangesDialog(const LLSD& notification, const LLSD& response) -{ - mSaveDialogShown = false; - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - switch(option) - { - case 0: // "Yes" - mCloseAfterSave = true; - LLPreviewNotecard::onClickSave((void*)this); - break; - - case 1: // "No" - mForceClose = true; - closeFloater(); - break; - - case 2: // "Cancel" - default: - // If we were quitting, we didn't really mean it. - LLAppViewer::instance()->abortQuit(); - break; - } - return false; -} - -bool LLPreviewNotecard::handleConfirmDeleteDialog(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option != 0) - { - // canceled - return false; - } - - if (mObjectUUID.isNull()) - { - // move item from agent's inventory into trash - LLViewerInventoryItem* item = gInventory.getItem(mItemUUID); - if (item != NULL) - { - const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - gInventory.changeItemParent(item, trash_id, false); - } - } - else - { - // delete item from inventory of in-world object - LLViewerObject* object = gObjectList.findObject(mObjectUUID); - if(object) - { - LLViewerInventoryItem* item = dynamic_cast(object->getInventoryObject(mItemUUID)); - if (item != NULL) - { - object->removeInventory(mItemUUID); - } - } - } - - // close floater, ignore unsaved changes - mForceClose = true; - closeFloater(); - return false; -} - -void LLPreviewNotecard::openInExternalEditor() -{ - delete mLiveFile; // deletes file - - // Save the notecard to a temporary file. - std::string filename = getTmpFileName(); - writeToFile(filename); - - // Start watching file changes. - mLiveFile = new LLLiveLSLFile(filename, boost::bind(&LLPreviewNotecard::onExternalChange, this, _1)); - mLiveFile->ignoreNextUpdate(); - mLiveFile->addToEventTimer(); - - // Open it in external editor. - { - LLExternalEditor ed; - LLExternalEditor::EErrorCode status; - std::string msg; - - status = ed.setCommand("LL_SCRIPT_EDITOR"); - if (status != LLExternalEditor::EC_SUCCESS) - { - if (status == LLExternalEditor::EC_NOT_SPECIFIED) // Use custom message for this error. - { - msg = LLTrans::getString("ExternalEditorNotSet"); - } - else - { - msg = LLExternalEditor::getErrorMessage(status); - } - - LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", msg)); - return; - } - - status = ed.run(filename); - if (status != LLExternalEditor::EC_SUCCESS) - { - msg = LLExternalEditor::getErrorMessage(status); - LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", msg)); - } - } -} - -bool LLPreviewNotecard::onExternalChange(const std::string& filename) -{ - if (!loadNotecardText(filename)) - { - return false; - } - - // Disable sync to avoid recursive load->save->load calls. - saveIfNeeded(NULL, false); - return true; -} - -bool LLPreviewNotecard::loadNotecardText(const std::string& filename) -{ - if (filename.empty()) - { - LL_WARNS() << "Empty file name" << LL_ENDL; - return false; - } - - LLFILE* file = LLFile::fopen(filename, "rb"); /*Flawfinder: ignore*/ - if (!file) - { - LL_WARNS() << "Error opening " << filename << LL_ENDL; - return false; - } - - // read in the whole file - fseek(file, 0L, SEEK_END); - size_t file_length = (size_t)ftell(file); - fseek(file, 0L, SEEK_SET); - char* buffer = new char[file_length + 1]; - size_t nread = fread(buffer, 1, file_length, file); - if (nread < file_length) - { - LL_WARNS() << "Short read" << LL_ENDL; - } - buffer[nread] = '\0'; - fclose(file); - - std::string text = std::string(buffer); - LLStringUtil::replaceTabsWithSpaces(text, LLTextEditor::spacesPerTab()); - - mEditor->setText(text); - delete[] buffer; - - return true; -} - -bool LLPreviewNotecard::writeToFile(const std::string& filename) -{ - LLFILE* fp = LLFile::fopen(filename, "wb"); - if (!fp) - { - LL_WARNS() << "Unable to write to " << filename << LL_ENDL; - return false; - } - - std::string utf8text = mEditor->getText(); - - if (utf8text.size() == 0) - { - utf8text = " "; - } - - fputs(utf8text.c_str(), fp); - fclose(fp); - return true; -} - - -std::string LLPreviewNotecard::getTmpFileName() -{ - std::string notecard_id = mObjectID.asString() + "_" + mItemUUID.asString(); - - // Use MD5 sum to make the file name shorter and not exceed maximum path length. - char notecard_id_hash_str[33]; /* Flawfinder: ignore */ - LLMD5 notecard_id_hash((const U8 *)notecard_id.c_str()); - notecard_id_hash.hex_digest(notecard_id_hash_str); - - return std::string(LLFile::tmpdir()) + "sl_notecard_" + notecard_id_hash_str + ".txt"; -} - - -// EOF +/** + * @file llpreviewnotecard.cpp + * @brief Implementation of the notecard editor + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpreviewnotecard.h" + +#include "llinventory.h" + +#include "llagent.h" +#include "lldraghandle.h" +#include "llexternaleditor.h" +#include "llviewerwindow.h" +#include "llbutton.h" +#include "llfloaterreg.h" +#include "llinventorydefines.h" +#include "llinventorymodel.h" +#include "lllineeditor.h" +#include "llmd5.h" +#include "llnotificationsutil.h" +#include "llmd5.h" +#include "llresmgr.h" +#include "roles_constants.h" +#include "llscrollbar.h" +#include "llselectmgr.h" +#include "lltrans.h" +#include "llviewertexteditor.h" +#include "llfilesystem.h" +#include "llviewerinventory.h" +#include "llviewerobject.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "lldir.h" +#include "llviewerstats.h" +#include "llviewercontrol.h" // gSavedSettings +#include "llappviewer.h" // app_abort_quit() +#include "lllineeditor.h" +#include "lluictrlfactory.h" +#include "llviewerassetupload.h" + +///---------------------------------------------------------------------------- +/// Class LLPreviewNotecard +///---------------------------------------------------------------------------- + +// Default constructor +LLPreviewNotecard::LLPreviewNotecard(const LLSD& key) //const LLUUID& item_id, + : LLPreview( key ), + mLiveFile(NULL) +{ + const LLInventoryItem *item = getItem(); + if (item) + { + mAssetID = item->getAssetUUID(); + } +} + +LLPreviewNotecard::~LLPreviewNotecard() +{ + delete mLiveFile; +} + +bool LLPreviewNotecard::postBuild() +{ + mEditor = getChild("Notecard Editor"); + mEditor->setNotecardInfo(mItemUUID, mObjectID, getKey()); + mEditor->makePristine(); + + childSetAction("Save", onClickSave, this); + getChildView("lock")->setVisible( false); + + childSetAction("Delete", onClickDelete, this); + getChildView("Delete")->setEnabled(false); + + childSetAction("Edit", onClickEdit, this); + + const LLInventoryItem* item = getItem(); + + childSetCommitCallback("desc", LLPreview::onText, this); + if (item) + { + getChild("desc")->setValue(item->getDescription()); + bool source_library = mObjectUUID.isNull() && gInventory.isObjectDescendentOf(item->getUUID(), gInventory.getLibraryRootFolderID()); + getChildView("Delete")->setEnabled(!source_library); + } + getChild("desc")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); + + return LLPreview::postBuild(); +} + +bool LLPreviewNotecard::saveItem() +{ + LLInventoryItem* item = gInventory.getItem(mItemUUID); + return saveIfNeeded(item); +} + +void LLPreviewNotecard::setEnabled(bool enabled) +{ + + LLViewerTextEditor* editor = getChild("Notecard Editor"); + + getChildView("Notecard Editor")->setEnabled(enabled); + getChildView("lock")->setVisible( !enabled); + getChildView("desc")->setEnabled(enabled); + getChildView("Save")->setEnabled(enabled && editor && (!editor->isPristine())); +} + + +void LLPreviewNotecard::draw() +{ + LLViewerTextEditor* editor = getChild("Notecard Editor"); + bool changed = !editor->isPristine(); + + getChildView("Save")->setEnabled(changed && getEnabled()); + + LLPreview::draw(); +} + +// virtual +bool LLPreviewNotecard::handleKeyHere(KEY key, MASK mask) +{ + if(('S' == key) && (MASK_CONTROL == (mask & MASK_CONTROL))) + { + saveIfNeeded(); + return true; + } + + return LLPreview::handleKeyHere(key, mask); +} + +// virtual +bool LLPreviewNotecard::canClose() +{ + LLViewerTextEditor* editor = getChild("Notecard Editor"); + + if(mForceClose || editor->isPristine()) + { + return true; + } + else + { + if(!mSaveDialogShown) + { + mSaveDialogShown = true; + // Bring up view-modal dialog: Save changes? Yes, No, Cancel + LLNotificationsUtil::add("SaveChanges", LLSD(), LLSD(), boost::bind(&LLPreviewNotecard::handleSaveChangesDialog,this, _1, _2)); + } + return false; + } +} + +/* virtual */ +void LLPreviewNotecard::setObjectID(const LLUUID& object_id) +{ + LLPreview::setObjectID(object_id); + + LLViewerTextEditor* editor = getChild("Notecard Editor"); + editor->setNotecardObjectID(mObjectUUID); + editor->makePristine(); +} + +const LLInventoryItem* LLPreviewNotecard::getDragItem() +{ + LLViewerTextEditor* editor = getChild("Notecard Editor"); + + if(editor) + { + return editor->getDragItem(); + } + return NULL; +} + +bool LLPreviewNotecard::hasEmbeddedInventory() +{ + LLViewerTextEditor* editor = NULL; + editor = getChild("Notecard Editor"); + if (!editor) return false; + return editor->hasEmbeddedInventory(); +} + +void LLPreviewNotecard::refreshFromInventory(const LLUUID& new_item_id) +{ + if (new_item_id.notNull()) + { + mItemUUID = new_item_id; + setKey(LLSD(new_item_id)); + } + LL_DEBUGS() << "LLPreviewNotecard::refreshFromInventory()" << LL_ENDL; + loadAsset(); +} + +void LLPreviewNotecard::updateTitleButtons() +{ + LLPreview::updateTitleButtons(); + + LLUICtrl* lock_btn = getChild("lock"); + if(lock_btn->getVisible() && !isMinimized()) // lock button stays visible if floater is minimized. + { + LLRect lock_rc = lock_btn->getRect(); + LLRect buttons_rect = getDragHandle()->getButtonsRect(); + buttons_rect.mLeft = lock_rc.mLeft; + getDragHandle()->setButtonsRect(buttons_rect); + } +} + +void LLPreviewNotecard::loadAsset() +{ + // request the asset. + const LLInventoryItem* item = getItem(); + LLViewerTextEditor* editor = getChild("Notecard Editor"); + + if (!editor) + return; + + bool fail = false; + + if(item) + { + LLPermissions perm(item->getPermissions()); + bool is_owner = gAgent.allowOperation(PERM_OWNER, perm, GP_OBJECT_MANIPULATE); + bool allow_copy = gAgent.allowOperation(PERM_COPY, perm, GP_OBJECT_MANIPULATE); + bool allow_modify = canModify(mObjectUUID, item); + bool source_library = mObjectUUID.isNull() && gInventory.isObjectDescendentOf(mItemUUID, gInventory.getLibraryRootFolderID()); + + if (allow_copy || gAgent.isGodlike()) + { + mAssetID = item->getAssetUUID(); + if(mAssetID.isNull()) + { + editor->setText(LLStringUtil::null); + editor->makePristine(); + editor->setEnabled(true); + mAssetStatus = PREVIEW_ASSET_LOADED; + } + else + { + LLHost source_sim = LLHost(); + LLSD* user_data = nullptr; + if (mObjectUUID.notNull()) + { + LLViewerObject *objectp = gObjectList.findObject(mObjectUUID); + if (objectp && objectp->getRegion()) + { + source_sim = objectp->getRegion()->getHost(); + } + else + { + // The object that we're trying to look at disappeared, bail. + LL_WARNS() << "Can't find object " << mObjectUUID << " associated with notecard." << LL_ENDL; + mAssetID.setNull(); + editor->setText(getString("no_object")); + editor->makePristine(); + editor->setEnabled(false); + mAssetStatus = PREVIEW_ASSET_LOADED; + return; + } + user_data = new LLSD(); + user_data->with("taskid", mObjectUUID).with("itemid", mItemUUID); + } + else + { + user_data = new LLSD(mItemUUID); + } + + gAssetStorage->getInvItemAsset(source_sim, + gAgent.getID(), + gAgent.getSessionID(), + item->getPermissions().getOwner(), + mObjectUUID, + item->getUUID(), + item->getAssetUUID(), + item->getType(), + &onLoadComplete, + (void*)user_data, + true); + mAssetStatus = PREVIEW_ASSET_LOADING; + } + } + else + { + mAssetID.setNull(); + editor->setText(getString("not_allowed")); + editor->makePristine(); + editor->setEnabled(false); + mAssetStatus = PREVIEW_ASSET_LOADED; + } + + if(!allow_modify) + { + editor->setEnabled(false); + getChildView("lock")->setVisible( true); + getChildView("Edit")->setEnabled(false); + } + + if((allow_modify || is_owner) && !source_library) + { + getChildView("Delete")->setEnabled(true); + } + } + else if (mObjectUUID.notNull() && mItemUUID.notNull()) + { + LLViewerObject* objectp = gObjectList.findObject(mObjectUUID); + if (objectp && (objectp->isInventoryPending() || objectp->isInventoryDirty())) + { + // It's a notecard in object's inventory and we failed to get it because inventory is not up to date. + // Subscribe for callback and retry at inventoryChanged() + registerVOInventoryListener(objectp, NULL); //removes previous listener + + if (objectp->isInventoryDirty()) + { + objectp->requestInventory(); + } + } + else + { + fail = true; + } + } + else + { + fail = true; + } + + if (fail) + { + editor->setText(LLStringUtil::null); + editor->makePristine(); + editor->setEnabled(true); + // Don't set asset status here; we may not have set the item id yet + // (e.g. when this gets called initially) + //mAssetStatus = PREVIEW_ASSET_LOADED; + } +} + +// static +void LLPreviewNotecard::onLoadComplete(const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status) +{ + LL_INFOS() << "LLPreviewNotecard::onLoadComplete()" << LL_ENDL; + LLSD* floater_key = (LLSD*)user_data; + LLPreviewNotecard* preview = LLFloaterReg::findTypedInstance("preview_notecard", *floater_key); + if( preview ) + { + if(0 == status) + { + LLFileSystem file(asset_uuid, type, LLFileSystem::READ); + + S32 file_length = file.getSize(); + + std::vector buffer(file_length+1); + file.read((U8*)&buffer[0], file_length); + + // put a EOS at the end + buffer[file_length] = 0; + + + LLViewerTextEditor* previewEditor = preview->getChild("Notecard Editor"); + + if( (file_length > 19) && !strncmp( &buffer[0], "Linden text version", 19 ) ) + { + if( !previewEditor->importBuffer( &buffer[0], file_length+1 ) ) + { + LL_WARNS() << "Problem importing notecard" << LL_ENDL; + } + } + else + { + // Version 0 (just text, doesn't include version number) + previewEditor->setText(LLStringExplicit(&buffer[0])); + } + + previewEditor->makePristine(); + bool modifiable = preview->canModify(preview->mObjectID, preview->getItem()); + preview->setEnabled(modifiable); + preview->syncExternal(); + preview->mAssetStatus = PREVIEW_ASSET_LOADED; + } + else + { + if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status || + LL_ERR_FILE_EMPTY == status) + { + LLNotificationsUtil::add("NotecardMissing"); + } + else if (LL_ERR_INSUFFICIENT_PERMISSIONS == status) + { + LLNotificationsUtil::add("NotecardNoPermissions"); + } + else + { + LLNotificationsUtil::add("UnableToLoadNotecard"); + } + + LL_WARNS() << "Problem loading notecard: " << status << LL_ENDL; + preview->mAssetStatus = PREVIEW_ASSET_ERROR; + } + } + delete floater_key; +} + +// static +void LLPreviewNotecard::onClickSave(void* user_data) +{ + //LL_INFOS() << "LLPreviewNotecard::onBtnSave()" << LL_ENDL; + LLPreviewNotecard* preview = (LLPreviewNotecard*)user_data; + if(preview) + { + preview->saveIfNeeded(); + } +} + + +// static +void LLPreviewNotecard::onClickDelete(void* user_data) +{ + LLPreviewNotecard* preview = (LLPreviewNotecard*)user_data; + if(preview) + { + preview->deleteNotecard(); + } +} + +// static +void LLPreviewNotecard::onClickEdit(void* user_data) +{ + LLPreviewNotecard* preview = (LLPreviewNotecard*)user_data; + if (preview) + { + preview->openInExternalEditor(); + } +} + +struct LLSaveNotecardInfo +{ + LLPreviewNotecard* mSelf; + LLUUID mItemUUID; + LLUUID mObjectUUID; + LLTransactionID mTransactionID; + LLPointer mCopyItem; + LLSaveNotecardInfo(LLPreviewNotecard* self, const LLUUID& item_id, const LLUUID& object_id, + const LLTransactionID& transaction_id, LLInventoryItem* copyitem) : + mSelf(self), mItemUUID(item_id), mObjectUUID(object_id), mTransactionID(transaction_id), mCopyItem(copyitem) + { + } +}; + +void LLPreviewNotecard::finishInventoryUpload(LLUUID itemId, LLUUID newAssetId, LLUUID newItemId) +{ + // Update the UI with the new asset. + LLPreviewNotecard* nc = LLFloaterReg::findTypedInstance("preview_notecard", LLSD(itemId)); + if (nc) + { + // *HACK: we have to delete the asset in the cache so + // that the viewer will redownload it. This is only + // really necessary if the asset had to be modified by + // the uploader, so this can be optimized away in some + // cases. A better design is to have a new uuid if the + // script actually changed the asset. + if (nc->hasEmbeddedInventory()) + { + LLFileSystem::removeFile(newAssetId, LLAssetType::AT_NOTECARD); + } + if (newItemId.isNull()) + { + nc->setAssetId(newAssetId); + nc->refreshFromInventory(); + } + else + { + nc->refreshFromInventory(newItemId); + } + } +} + +void LLPreviewNotecard::finishTaskUpload(LLUUID itemId, LLUUID newAssetId, LLUUID taskId) +{ + + LLSD floater_key; + floater_key["taskid"] = taskId; + floater_key["itemid"] = itemId; + LLPreviewNotecard* nc = LLFloaterReg::findTypedInstance("preview_notecard", floater_key); + if (nc) + { + if (nc->hasEmbeddedInventory()) + { + LLFileSystem::removeFile(newAssetId, LLAssetType::AT_NOTECARD); + } + nc->setAssetId(newAssetId); + nc->refreshFromInventory(); + } +} + +bool LLPreviewNotecard::saveIfNeeded(LLInventoryItem* copyitem, bool sync) +{ + LLViewerTextEditor* editor = getChild("Notecard Editor"); + + if(!editor) + { + LL_WARNS() << "Cannot get handle to the notecard editor." << LL_ENDL; + return false; + } + + if(!editor->isPristine()) + { + std::string buffer; + if (!editor->exportBuffer(buffer)) + { + return false; + } + + editor->makePristine(); + const LLInventoryItem* item = getItem(); + // save it out to database + if (item) + { + const LLViewerRegion* region = gAgent.getRegion(); + if (!region) + { + LL_WARNS() << "Not connected to a region, cannot save notecard." << LL_ENDL; + return false; + } + std::string agent_url = region->getCapability("UpdateNotecardAgentInventory"); + std::string task_url = region->getCapability("UpdateNotecardTaskInventory"); + + if (!agent_url.empty() && !task_url.empty()) + { + std::string url; + LLResourceUploadInfo::ptr_t uploadInfo; + + if (mObjectUUID.isNull() && !agent_url.empty()) + { + uploadInfo = std::make_shared(mItemUUID, LLAssetType::AT_NOTECARD, buffer, + [](LLUUID itemId, LLUUID newAssetId, LLUUID newItemId, LLSD) { + LLPreviewNotecard::finishInventoryUpload(itemId, newAssetId, newItemId); + }, + nullptr); + url = agent_url; + } + else if (!mObjectUUID.isNull() && !task_url.empty()) + { + LLUUID object_uuid(mObjectUUID); + uploadInfo = std::make_shared(mObjectUUID, mItemUUID, LLAssetType::AT_NOTECARD, buffer, + [object_uuid](LLUUID itemId, LLUUID, LLUUID newAssetId, LLSD) { + LLPreviewNotecard::finishTaskUpload(itemId, newAssetId, object_uuid); + }, + nullptr); + url = task_url; + } + + if (!url.empty() && uploadInfo) + { + mAssetStatus = PREVIEW_ASSET_LOADING; + setEnabled(false); + + LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo); + } + + } + else if (gAssetStorage) + { + // We need to update the asset information + LLTransactionID tid; + LLAssetID asset_id; + tid.generate(); + asset_id = tid.makeAssetID(gAgent.getSecureSessionID()); + + LLFileSystem file(asset_id, LLAssetType::AT_NOTECARD, LLFileSystem::APPEND); + + + LLSaveNotecardInfo* info = new LLSaveNotecardInfo(this, mItemUUID, mObjectUUID, + tid, copyitem); + + S32 size = buffer.length() + 1; + file.write((U8*)buffer.c_str(), size); + + gAssetStorage->storeAssetData(tid, LLAssetType::AT_NOTECARD, + &onSaveComplete, + (void*)info, + false); + return true; + } + else // !gAssetStorage + { + LL_WARNS() << "Not connected to an asset storage system." << LL_ENDL; + return false; + } + if(mCloseAfterSave) + { + closeFloater(); + } + } + } + return true; +} + +void LLPreviewNotecard::syncExternal() +{ + // Sync with external editor. + std::string tmp_file = getTmpFileName(); + llstat s; + if (LLFile::stat(tmp_file, &s) == 0) // file exists + { + if (mLiveFile) mLiveFile->ignoreNextUpdate(); + writeToFile(tmp_file); + } +} + +/*virtual*/ +void LLPreviewNotecard::inventoryChanged(LLViewerObject* object, + LLInventoryObject::object_list_t* inventory, + S32 serial_num, + void* user_data) +{ + removeVOInventoryListener(); + loadAsset(); +} + + +void LLPreviewNotecard::deleteNotecard() +{ + LLNotificationsUtil::add("DeleteNotecard", LLSD(), LLSD(), boost::bind(&LLPreviewNotecard::handleConfirmDeleteDialog,this, _1, _2)); +} + +// static +void LLPreviewNotecard::onSaveComplete(const LLUUID& asset_uuid, void* user_data, S32 status, LLExtStat ext_status) // StoreAssetData callback (fixed) +{ + LLSaveNotecardInfo* info = (LLSaveNotecardInfo*)user_data; + if(info && (0 == status)) + { + if(info->mObjectUUID.isNull()) + { + LLViewerInventoryItem* item; + item = (LLViewerInventoryItem*)gInventory.getItem(info->mItemUUID); + if(item) + { + LLPointer new_item = new LLViewerInventoryItem(item); + new_item->setAssetUUID(asset_uuid); + new_item->setTransactionID(info->mTransactionID); + new_item->updateServer(false); + gInventory.updateItem(new_item); + gInventory.notifyObservers(); + } + else + { + LL_WARNS() << "Inventory item for script " << info->mItemUUID + << " is no longer in agent inventory." << LL_ENDL; + } + } + else + { + LLViewerObject* object = gObjectList.findObject(info->mObjectUUID); + LLViewerInventoryItem* item = NULL; + if(object) + { + item = (LLViewerInventoryItem*)object->getInventoryObject(info->mItemUUID); + } + if(object && item) + { + item->setAssetUUID(asset_uuid); + item->setTransactionID(info->mTransactionID); + object->updateInventory(item, TASK_INVENTORY_ITEM_KEY, false); + dialog_refresh_all(); + } + else + { + LLNotificationsUtil::add("SaveNotecardFailObjectNotFound"); + } + } + // Perform item copy to inventory + if (info->mCopyItem.notNull()) + { + LLViewerTextEditor* editor = info->mSelf->getChild("Notecard Editor"); + if (editor) + { + editor->copyInventory(info->mCopyItem); + } + } + + // Find our window and close it if requested. + + LLPreviewNotecard* previewp = LLFloaterReg::findTypedInstance("preview_notecard", info->mItemUUID); + if (previewp && previewp->mCloseAfterSave) + { + previewp->closeFloater(); + } + } + else + { + LL_WARNS() << "Problem saving notecard: " << status << LL_ENDL; + LLSD args; + args["REASON"] = std::string(LLAssetStorage::getErrorString(status)); + LLNotificationsUtil::add("SaveNotecardFailReason", args); + } + + std::string uuid_string; + asset_uuid.toString(uuid_string); + std::string filename; + filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,uuid_string) + ".tmp"; + LLFile::remove(filename); + delete info; +} + +bool LLPreviewNotecard::handleSaveChangesDialog(const LLSD& notification, const LLSD& response) +{ + mSaveDialogShown = false; + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + switch(option) + { + case 0: // "Yes" + mCloseAfterSave = true; + LLPreviewNotecard::onClickSave((void*)this); + break; + + case 1: // "No" + mForceClose = true; + closeFloater(); + break; + + case 2: // "Cancel" + default: + // If we were quitting, we didn't really mean it. + LLAppViewer::instance()->abortQuit(); + break; + } + return false; +} + +bool LLPreviewNotecard::handleConfirmDeleteDialog(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) + { + // canceled + return false; + } + + if (mObjectUUID.isNull()) + { + // move item from agent's inventory into trash + LLViewerInventoryItem* item = gInventory.getItem(mItemUUID); + if (item != NULL) + { + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + gInventory.changeItemParent(item, trash_id, false); + } + } + else + { + // delete item from inventory of in-world object + LLViewerObject* object = gObjectList.findObject(mObjectUUID); + if(object) + { + LLViewerInventoryItem* item = dynamic_cast(object->getInventoryObject(mItemUUID)); + if (item != NULL) + { + object->removeInventory(mItemUUID); + } + } + } + + // close floater, ignore unsaved changes + mForceClose = true; + closeFloater(); + return false; +} + +void LLPreviewNotecard::openInExternalEditor() +{ + delete mLiveFile; // deletes file + + // Save the notecard to a temporary file. + std::string filename = getTmpFileName(); + writeToFile(filename); + + // Start watching file changes. + mLiveFile = new LLLiveLSLFile(filename, boost::bind(&LLPreviewNotecard::onExternalChange, this, _1)); + mLiveFile->ignoreNextUpdate(); + mLiveFile->addToEventTimer(); + + // Open it in external editor. + { + LLExternalEditor ed; + LLExternalEditor::EErrorCode status; + std::string msg; + + status = ed.setCommand("LL_SCRIPT_EDITOR"); + if (status != LLExternalEditor::EC_SUCCESS) + { + if (status == LLExternalEditor::EC_NOT_SPECIFIED) // Use custom message for this error. + { + msg = LLTrans::getString("ExternalEditorNotSet"); + } + else + { + msg = LLExternalEditor::getErrorMessage(status); + } + + LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", msg)); + return; + } + + status = ed.run(filename); + if (status != LLExternalEditor::EC_SUCCESS) + { + msg = LLExternalEditor::getErrorMessage(status); + LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", msg)); + } + } +} + +bool LLPreviewNotecard::onExternalChange(const std::string& filename) +{ + if (!loadNotecardText(filename)) + { + return false; + } + + // Disable sync to avoid recursive load->save->load calls. + saveIfNeeded(NULL, false); + return true; +} + +bool LLPreviewNotecard::loadNotecardText(const std::string& filename) +{ + if (filename.empty()) + { + LL_WARNS() << "Empty file name" << LL_ENDL; + return false; + } + + LLFILE* file = LLFile::fopen(filename, "rb"); /*Flawfinder: ignore*/ + if (!file) + { + LL_WARNS() << "Error opening " << filename << LL_ENDL; + return false; + } + + // read in the whole file + fseek(file, 0L, SEEK_END); + size_t file_length = (size_t)ftell(file); + fseek(file, 0L, SEEK_SET); + char* buffer = new char[file_length + 1]; + size_t nread = fread(buffer, 1, file_length, file); + if (nread < file_length) + { + LL_WARNS() << "Short read" << LL_ENDL; + } + buffer[nread] = '\0'; + fclose(file); + + std::string text = std::string(buffer); + LLStringUtil::replaceTabsWithSpaces(text, LLTextEditor::spacesPerTab()); + + mEditor->setText(text); + delete[] buffer; + + return true; +} + +bool LLPreviewNotecard::writeToFile(const std::string& filename) +{ + LLFILE* fp = LLFile::fopen(filename, "wb"); + if (!fp) + { + LL_WARNS() << "Unable to write to " << filename << LL_ENDL; + return false; + } + + std::string utf8text = mEditor->getText(); + + if (utf8text.size() == 0) + { + utf8text = " "; + } + + fputs(utf8text.c_str(), fp); + fclose(fp); + return true; +} + + +std::string LLPreviewNotecard::getTmpFileName() +{ + std::string notecard_id = mObjectID.asString() + "_" + mItemUUID.asString(); + + // Use MD5 sum to make the file name shorter and not exceed maximum path length. + char notecard_id_hash_str[33]; /* Flawfinder: ignore */ + LLMD5 notecard_id_hash((const U8 *)notecard_id.c_str()); + notecard_id_hash.hex_digest(notecard_id_hash_str); + + return std::string(LLFile::tmpdir()) + "sl_notecard_" + notecard_id_hash_str + ".txt"; +} + + +// EOF diff --git a/indra/newview/llpreviewnotecard.h b/indra/newview/llpreviewnotecard.h index bf426f6276..db677b1cf9 100644 --- a/indra/newview/llpreviewnotecard.h +++ b/indra/newview/llpreviewnotecard.h @@ -1,130 +1,130 @@ -/** - * @file llpreviewnotecard.h - * @brief LLPreviewNotecard class header file - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPREVIEWNOTECARD_H -#define LL_LLPREVIEWNOTECARD_H - -#include "llpreview.h" -#include "llassetstorage.h" -#include "llpreviewscript.h" -#include "lliconctrl.h" -#include "llvoinventorylistener.h" - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLPreviewNotecard -// -// This class allows us to edit notecards -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLViewerTextEditor; -class LLButton; - -class LLPreviewNotecard : public LLPreview, public LLVOInventoryListener -{ -public: - LLPreviewNotecard(const LLSD& key); - virtual ~LLPreviewNotecard(); - - bool saveItem(); - void setObjectID(const LLUUID& object_id) override; - - // llview - void draw() override; - bool handleKeyHere(KEY key, MASK mask) override; - void setEnabled(bool enabled) override; - - // llfloater - bool canClose() override; - - // llpanel - bool postBuild() override; - - // reach into the text editor, and grab the drag item - const LLInventoryItem* getDragItem(); - - - // return true if there is any embedded inventory. - bool hasEmbeddedInventory(); - - // After saving a notecard, the tcp based upload system will - // change the asset, therefore, we need to re-fetch it from the - // asset system. :( - void refreshFromInventory(const LLUUID& item_id = LLUUID::null); - - void syncExternal(); - - void inventoryChanged(LLViewerObject* object, - LLInventoryObject::object_list_t* inventory, - S32 serial_num, - void* user_data) override; - -protected: - - void updateTitleButtons() override; - void loadAsset() override; - bool saveIfNeeded(LLInventoryItem* copyitem = NULL, bool sync = true); - - void deleteNotecard(); - - static void onLoadComplete(const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status); - - static void onClickSave(void* data); - - static void onClickDelete(void* data); - - static void onClickEdit(void* data); - - static void onSaveComplete(const LLUUID& asset_uuid, - void* user_data, - S32 status, LLExtStat ext_status); - - bool handleSaveChangesDialog(const LLSD& notification, const LLSD& response); - bool handleConfirmDeleteDialog(const LLSD& notification, const LLSD& response); - - static void finishInventoryUpload(LLUUID itemId, LLUUID newAssetId, LLUUID newItemId); - static void finishTaskUpload(LLUUID itemId, LLUUID newAssetId, LLUUID taskId); - - void openInExternalEditor(); - bool onExternalChange(const std::string& filename); - bool loadNotecardText(const std::string& filename); - bool writeToFile(const std::string& filename); - std::string getTmpFileName(); - -protected: - LLViewerTextEditor* mEditor; - LLButton* mSaveBtn; - - LLUUID mAssetID; - - LLUUID mObjectID; - - LLLiveLSLFile* mLiveFile; -}; - - -#endif // LL_LLPREVIEWNOTECARD_H +/** + * @file llpreviewnotecard.h + * @brief LLPreviewNotecard class header file + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPREVIEWNOTECARD_H +#define LL_LLPREVIEWNOTECARD_H + +#include "llpreview.h" +#include "llassetstorage.h" +#include "llpreviewscript.h" +#include "lliconctrl.h" +#include "llvoinventorylistener.h" + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLPreviewNotecard +// +// This class allows us to edit notecards +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLViewerTextEditor; +class LLButton; + +class LLPreviewNotecard : public LLPreview, public LLVOInventoryListener +{ +public: + LLPreviewNotecard(const LLSD& key); + virtual ~LLPreviewNotecard(); + + bool saveItem(); + void setObjectID(const LLUUID& object_id) override; + + // llview + void draw() override; + bool handleKeyHere(KEY key, MASK mask) override; + void setEnabled(bool enabled) override; + + // llfloater + bool canClose() override; + + // llpanel + bool postBuild() override; + + // reach into the text editor, and grab the drag item + const LLInventoryItem* getDragItem(); + + + // return true if there is any embedded inventory. + bool hasEmbeddedInventory(); + + // After saving a notecard, the tcp based upload system will + // change the asset, therefore, we need to re-fetch it from the + // asset system. :( + void refreshFromInventory(const LLUUID& item_id = LLUUID::null); + + void syncExternal(); + + void inventoryChanged(LLViewerObject* object, + LLInventoryObject::object_list_t* inventory, + S32 serial_num, + void* user_data) override; + +protected: + + void updateTitleButtons() override; + void loadAsset() override; + bool saveIfNeeded(LLInventoryItem* copyitem = NULL, bool sync = true); + + void deleteNotecard(); + + static void onLoadComplete(const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status); + + static void onClickSave(void* data); + + static void onClickDelete(void* data); + + static void onClickEdit(void* data); + + static void onSaveComplete(const LLUUID& asset_uuid, + void* user_data, + S32 status, LLExtStat ext_status); + + bool handleSaveChangesDialog(const LLSD& notification, const LLSD& response); + bool handleConfirmDeleteDialog(const LLSD& notification, const LLSD& response); + + static void finishInventoryUpload(LLUUID itemId, LLUUID newAssetId, LLUUID newItemId); + static void finishTaskUpload(LLUUID itemId, LLUUID newAssetId, LLUUID taskId); + + void openInExternalEditor(); + bool onExternalChange(const std::string& filename); + bool loadNotecardText(const std::string& filename); + bool writeToFile(const std::string& filename); + std::string getTmpFileName(); + +protected: + LLViewerTextEditor* mEditor; + LLButton* mSaveBtn; + + LLUUID mAssetID; + + LLUUID mObjectID; + + LLLiveLSLFile* mLiveFile; +}; + + +#endif // LL_LLPREVIEWNOTECARD_H diff --git a/indra/newview/llpreviewscript.cpp b/indra/newview/llpreviewscript.cpp index 202b34a59f..a6ab08601d 100644 --- a/indra/newview/llpreviewscript.cpp +++ b/indra/newview/llpreviewscript.cpp @@ -1,2491 +1,2491 @@ -/** - * @file llpreviewscript.cpp - * @brief LLPreviewScript class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llpreviewscript.h" - -#include "llassetstorage.h" -#include "llbutton.h" -#include "llcheckboxctrl.h" -#include "llcombobox.h" -#include "lldir.h" -#include "llexternaleditor.h" -#include "llfilepicker.h" -#include "llfloaterreg.h" -#include "llinventorydefines.h" -#include "llinventorymodel.h" -#include "llkeyboard.h" -#include "lllineeditor.h" -#include "llmd5.h" -#include "llhelp.h" -#include "llnotificationsutil.h" -#include "llresmgr.h" -#include "llscrollbar.h" -#include "llscrollcontainer.h" -#include "llscrolllistctrl.h" -#include "llscrolllistitem.h" -#include "llscrolllistcell.h" -#include "llsdserialize.h" -#include "llslider.h" -#include "lltooldraganddrop.h" -#include "llfilesystem.h" - -#include "llagent.h" -#include "llmenugl.h" -#include "roles_constants.h" -#include "llselectmgr.h" -#include "llviewerinventory.h" -#include "llviewermenu.h" -#include "llviewermenufile.h" // LLFilePickerReplyThread -#include "llviewerobject.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llkeyboard.h" -#include "llscrollcontainer.h" -#include "llcheckboxctrl.h" -#include "llscripteditor.h" -#include "llselectmgr.h" -#include "lltooldraganddrop.h" -#include "llscrolllistctrl.h" -#include "lltextbox.h" -#include "llslider.h" -#include "lldir.h" -#include "llcombobox.h" -#include "llviewerstats.h" -#include "llviewerwindow.h" -#include "lluictrlfactory.h" -#include "llmediactrl.h" -#include "lluictrlfactory.h" -#include "lltrans.h" -#include "llviewercontrol.h" -#include "llappviewer.h" -#include "llfloatergotoline.h" -#include "llexperiencecache.h" -#include "llfloaterexperienceprofile.h" -#include "llviewerassetupload.h" -#include "lltoggleablemenu.h" -#include "llmenubutton.h" -#include "llinventoryfunctions.h" - -const std::string HELLO_LSL = - "default\n" - "{\n" - " state_entry()\n" - " {\n" - " llSay(0, \"Hello, Avatar!\");\n" - " }\n" - "\n" - " touch_start(integer total_number)\n" - " {\n" - " llSay(0, \"Touched.\");\n" - " }\n" - "}\n"; -const std::string HELP_LSL_PORTAL_TOPIC = "LSL_Portal"; - -const std::string DEFAULT_SCRIPT_NAME = "New Script"; // *TODO:Translate? -const std::string DEFAULT_SCRIPT_DESC = "(No Description)"; // *TODO:Translate? - -// Description and header information -const S32 MAX_HISTORY_COUNT = 10; -const F32 LIVE_HELP_REFRESH_TIME = 1.f; - -static bool have_script_upload_cap(LLUUID& object_id) -{ - LLViewerObject* object = gObjectList.findObject(object_id); - return object && (! object->getRegion()->getCapability("UpdateScriptTask").empty()); -} - -/// --------------------------------------------------------------------------- -/// LLLiveLSLFile -/// --------------------------------------------------------------------------- - -LLLiveLSLFile::LLLiveLSLFile(std::string file_path, change_callback_t change_cb) -: mOnChangeCallback(change_cb) -, mIgnoreNextUpdate(false) -, LLLiveFile(file_path, 1.0) -{ - llassert(mOnChangeCallback); -} - -LLLiveLSLFile::~LLLiveLSLFile() -{ - LLFile::remove(filename()); -} - -bool LLLiveLSLFile::loadFile() -{ - if (mIgnoreNextUpdate) - { - mIgnoreNextUpdate = false; - return true; - } - - return mOnChangeCallback(filename()); -} - -/// --------------------------------------------------------------------------- -/// LLFloaterScriptSearch -/// --------------------------------------------------------------------------- -class LLFloaterScriptSearch : public LLFloater -{ -public: - LLFloaterScriptSearch(LLScriptEdCore* editor_core); - ~LLFloaterScriptSearch(); - - /*virtual*/ bool postBuild(); - static void show(LLScriptEdCore* editor_core); - static void onBtnSearch(void* userdata); - void handleBtnSearch(); - - static void onBtnReplace(void* userdata); - void handleBtnReplace(); - - static void onBtnReplaceAll(void* userdata); - void handleBtnReplaceAll(); - - LLScriptEdCore* getEditorCore() { return mEditorCore; } - static LLFloaterScriptSearch* getInstance() { return sInstance; } - - virtual bool hasAccelerators() const; - virtual bool handleKeyHere(KEY key, MASK mask); - -private: - - LLScriptEdCore* mEditorCore; - static LLFloaterScriptSearch* sInstance; - -protected: - LLLineEditor* mSearchBox; - LLLineEditor* mReplaceBox; - void onSearchBoxCommit(); -}; - -LLFloaterScriptSearch* LLFloaterScriptSearch::sInstance = NULL; - -LLFloaterScriptSearch::LLFloaterScriptSearch(LLScriptEdCore* editor_core) -: LLFloater(LLSD()), - mSearchBox(NULL), - mReplaceBox(NULL), - mEditorCore(editor_core) -{ - buildFromFile("floater_script_search.xml"); - - sInstance = this; - - // find floater in which script panel is embedded - LLView* viewp = (LLView*)editor_core; - while(viewp) - { - LLFloater* floaterp = dynamic_cast(viewp); - if (floaterp) - { - floaterp->addDependentFloater(this); - break; - } - viewp = viewp->getParent(); - } -} - -bool LLFloaterScriptSearch::postBuild() -{ - mReplaceBox = getChild("replace_text"); - mSearchBox = getChild("search_text"); - mSearchBox->setCommitCallback(boost::bind(&LLFloaterScriptSearch::onSearchBoxCommit, this)); - mSearchBox->setCommitOnFocusLost(false); - childSetAction("search_btn", onBtnSearch,this); - childSetAction("replace_btn", onBtnReplace,this); - childSetAction("replace_all_btn", onBtnReplaceAll,this); - - setDefaultBtn("search_btn"); - - return true; -} - -//static -void LLFloaterScriptSearch::show(LLScriptEdCore* editor_core) -{ - LLSD::String search_text; - LLSD::String replace_text; - if (sInstance && sInstance->mEditorCore && sInstance->mEditorCore != editor_core) - { - search_text=sInstance->mSearchBox->getValue().asString(); - replace_text=sInstance->mReplaceBox->getValue().asString(); - sInstance->closeFloater(); - delete sInstance; - } - - if (!sInstance) - { - // sInstance will be assigned in the constructor. - new LLFloaterScriptSearch(editor_core); - sInstance->mSearchBox->setValue(search_text); - sInstance->mReplaceBox->setValue(replace_text); - } - - sInstance->openFloater(); -} - -LLFloaterScriptSearch::~LLFloaterScriptSearch() -{ - sInstance = NULL; -} - -// static -void LLFloaterScriptSearch::onBtnSearch(void *userdata) -{ - LLFloaterScriptSearch* self = (LLFloaterScriptSearch*)userdata; - self->handleBtnSearch(); -} - -void LLFloaterScriptSearch::handleBtnSearch() -{ - LLCheckBoxCtrl* caseChk = getChild("case_text"); - mEditorCore->mEditor->selectNext(mSearchBox->getValue().asString(), caseChk->get()); -} - -// static -void LLFloaterScriptSearch::onBtnReplace(void *userdata) -{ - LLFloaterScriptSearch* self = (LLFloaterScriptSearch*)userdata; - self->handleBtnReplace(); -} - -void LLFloaterScriptSearch::handleBtnReplace() -{ - LLCheckBoxCtrl* caseChk = getChild("case_text"); - mEditorCore->mEditor->replaceText(mSearchBox->getValue().asString(), mReplaceBox->getValue().asString(), caseChk->get()); -} - -// static -void LLFloaterScriptSearch::onBtnReplaceAll(void *userdata) -{ - LLFloaterScriptSearch* self = (LLFloaterScriptSearch*)userdata; - self->handleBtnReplaceAll(); -} - -void LLFloaterScriptSearch::handleBtnReplaceAll() -{ - LLCheckBoxCtrl* caseChk = getChild("case_text"); - mEditorCore->mEditor->replaceTextAll(mSearchBox->getValue().asString(), mReplaceBox->getValue().asString(), caseChk->get()); -} - -bool LLFloaterScriptSearch::hasAccelerators() const -{ - if (mEditorCore) - { - return mEditorCore->hasAccelerators(); - } - return false; -} - -bool LLFloaterScriptSearch::handleKeyHere(KEY key, MASK mask) -{ - if (mEditorCore) - { - bool handled = mEditorCore->handleKeyHere(key, mask); - if (!handled) - { - LLFloater::handleKeyHere(key, mask); - } - } - - return false; -} - -void LLFloaterScriptSearch::onSearchBoxCommit() -{ - if (mEditorCore && mEditorCore->mEditor) - { - LLCheckBoxCtrl* caseChk = getChild("case_text"); - mEditorCore->mEditor->selectNext(mSearchBox->getValue().asString(), caseChk->get()); - } -} - -/// --------------------------------------------------------------------------- - -class LLScriptMovedObserver : public LLInventoryObserver -{ - public: - LLScriptMovedObserver(LLPreviewLSL *floater) : mPreview(floater) { gInventory.addObserver(this); } - virtual ~LLScriptMovedObserver() { gInventory.removeObserver(this); } - virtual void changed(U32 mask); - - private: - LLPreviewLSL *mPreview; -}; - -void LLScriptMovedObserver::changed(U32 mask) -{ - const std::set &mChangedItemIDs = gInventory.getChangedIDs(); - std::set::const_iterator it; - - const LLUUID &item_id = mPreview->getScriptID(); - - for (it = mChangedItemIDs.begin(); it != mChangedItemIDs.end(); it++) - { - if (*it == item_id) - { - if ((mask & (LLInventoryObserver::STRUCTURE)) != 0) - { - mPreview->setDirty(); - } - } - } -} - -/// --------------------------------------------------------------------------- -/// LLScriptEdCore -/// --------------------------------------------------------------------------- - -struct LLSECKeywordCompare -{ - bool operator()(const std::string& lhs, const std::string& rhs) - { - return (LLStringUtil::compareDictInsensitive( lhs, rhs ) < 0 ); - } -}; - -LLScriptEdCore::LLScriptEdCore( - LLScriptEdContainer* container, - const std::string& sample, - const LLHandle& floater_handle, - void (*load_callback)(void*), - void (*save_callback)(void*, bool), - void (*search_replace_callback) (void* userdata), - void* userdata, - bool live, - S32 bottom_pad) - : - LLPanel(), - mSampleText(sample), - mEditor( NULL ), - mLoadCallback( load_callback ), - mSaveCallback( save_callback ), - mSearchReplaceCallback( search_replace_callback ), - mUserdata( userdata ), - mForceClose( false ), - mLastHelpToken(NULL), - mLiveHelpHistorySize(0), - mEnableSave(false), - mLiveFile(NULL), - mLive(live), - mContainer(container), - mHasScriptData(false), - mScriptRemoved(false), - mSaveDialogShown(false) -{ - setFollowsAll(); - setBorderVisible(false); - - setXMLFilename("panel_script_ed.xml"); - llassert_always(mContainer != NULL); -} - -LLScriptEdCore::~LLScriptEdCore() -{ - deleteBridges(); - - // If the search window is up for this editor, close it. - LLFloaterScriptSearch* script_search = LLFloaterScriptSearch::getInstance(); - if (script_search && script_search->getEditorCore() == this) - { - script_search->closeFloater(); - delete script_search; - } - - delete mLiveFile; - if (mSyntaxIDConnection.connected()) - { - mSyntaxIDConnection.disconnect(); - } -} - -void LLLiveLSLEditor::experienceChanged() -{ - if(mScriptEd->getAssociatedExperience() != mExperiences->getSelectedValue().asUUID()) - { - mScriptEd->enableSave(getIsModifiable()); - //getChildView("Save_btn")->setEnabled(true); - mScriptEd->setAssociatedExperience(mExperiences->getSelectedValue().asUUID()); - updateExperiencePanel(); - } -} - -void LLLiveLSLEditor::onViewProfile( LLUICtrl *ui, void* userdata ) -{ - LLLiveLSLEditor* self = (LLLiveLSLEditor*)userdata; - - LLUUID id; - if(self->mExperienceEnabled->get()) - { - id=self->mScriptEd->getAssociatedExperience(); - if(id.notNull()) - { - LLFloaterReg::showInstance("experience_profile", id, true); - } - } - -} - -void LLLiveLSLEditor::onToggleExperience( LLUICtrl *ui, void* userdata ) -{ - LLLiveLSLEditor* self = (LLLiveLSLEditor*)userdata; - - LLUUID id; - if(self->mExperienceEnabled->get()) - { - if(self->mScriptEd->getAssociatedExperience().isNull()) - { - id=self->mExperienceIds.beginArray()->asUUID(); - } - } - - if(id != self->mScriptEd->getAssociatedExperience()) - { - self->mScriptEd->enableSave(self->getIsModifiable()); - } - self->mScriptEd->setAssociatedExperience(id); - - self->updateExperiencePanel(); -} - -bool LLScriptEdCore::postBuild() -{ - mErrorList = getChild("lsl errors"); - - mFunctions = getChild("Insert..."); - - childSetCommitCallback("Insert...", &LLScriptEdCore::onBtnInsertFunction, this); - - mEditor = getChild("Script Editor"); - - childSetCommitCallback("lsl errors", &LLScriptEdCore::onErrorList, this); - childSetAction("Save_btn", boost::bind(&LLScriptEdCore::doSave,this,false)); - childSetAction("Edit_btn", boost::bind(&LLScriptEdCore::openInExternalEditor, this)); - - initMenu(); - - mSyntaxIDConnection = LLSyntaxIdLSL::getInstance()->addSyntaxIDCallback(boost::bind(&LLScriptEdCore::processKeywords, this)); - - // Intialise keyword highlighting for the current simulator's version of LSL - LLSyntaxIdLSL::getInstance()->initialize(); - processKeywords(); - - mCommitCallbackRegistrar.add("FontSize.Set", boost::bind(&LLScriptEdCore::onChangeFontSize, this, _2)); - mEnableCallbackRegistrar.add("FontSize.Check", boost::bind(&LLScriptEdCore::isFontSizeChecked, this, _2)); - - LLToggleableMenu *context_menu = LLUICtrlFactory::getInstance()->createFromFile( - "menu_lsl_font_size.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - getChild("font_btn")->setMenu(context_menu, LLMenuButton::MP_BOTTOM_LEFT, true); - - return true; -} - -void LLScriptEdCore::processKeywords() -{ - LL_DEBUGS("SyntaxLSL") << "Processing keywords" << LL_ENDL; - mEditor->clearSegments(); - mEditor->initKeywords(); - mEditor->loadKeywords(); - - string_vec_t primary_keywords; - string_vec_t secondary_keywords; - LLKeywordToken *token; - LLKeywords::keyword_iterator_t token_it; - for (token_it = mEditor->keywordsBegin(); token_it != mEditor->keywordsEnd(); ++token_it) - { - token = token_it->second; - if (token->getType() == LLKeywordToken::TT_FUNCTION) - { - primary_keywords.push_back( wstring_to_utf8str(token->getToken()) ); - } - else - { - secondary_keywords.push_back( wstring_to_utf8str(token->getToken()) ); - } - } - for (string_vec_t::const_iterator iter = primary_keywords.begin(); - iter!= primary_keywords.end(); ++iter) - { - mFunctions->add(*iter); - } - for (string_vec_t::const_iterator iter = secondary_keywords.begin(); - iter!= secondary_keywords.end(); ++iter) - { - mFunctions->add(*iter); - } -} - -void LLScriptEdCore::initMenu() -{ - // *TODO: Skinning - make these callbacks data driven - LLMenuItemCallGL* menuItem; - - menuItem = getChild("Save"); - menuItem->setClickCallback(boost::bind(&LLScriptEdCore::doSave, this, false)); - menuItem->setEnableCallback(boost::bind(&LLScriptEdCore::hasChanged, this)); - - menuItem = getChild("Revert All Changes"); - menuItem->setClickCallback(boost::bind(&LLScriptEdCore::onBtnUndoChanges, this)); - menuItem->setEnableCallback(boost::bind(&LLScriptEdCore::hasChanged, this)); - - menuItem = getChild("Undo"); - menuItem->setClickCallback(boost::bind(&LLTextEditor::undo, mEditor)); - menuItem->setEnableCallback(boost::bind(&LLTextEditor::canUndo, mEditor)); - - menuItem = getChild("Redo"); - menuItem->setClickCallback(boost::bind(&LLTextEditor::redo, mEditor)); - menuItem->setEnableCallback(boost::bind(&LLTextEditor::canRedo, mEditor)); - - menuItem = getChild("Cut"); - menuItem->setClickCallback(boost::bind(&LLTextEditor::cut, mEditor)); - menuItem->setEnableCallback(boost::bind(&LLTextEditor::canCut, mEditor)); - - menuItem = getChild("Copy"); - menuItem->setClickCallback(boost::bind(&LLTextEditor::copy, mEditor)); - menuItem->setEnableCallback(boost::bind(&LLTextEditor::canCopy, mEditor)); - - menuItem = getChild("Paste"); - menuItem->setClickCallback(boost::bind(&LLTextEditor::paste, mEditor)); - menuItem->setEnableCallback(boost::bind(&LLTextEditor::canPaste, mEditor)); - - menuItem = getChild("Select All"); - menuItem->setClickCallback(boost::bind(&LLTextEditor::selectAll, mEditor)); - menuItem->setEnableCallback(boost::bind(&LLTextEditor::canSelectAll, mEditor)); - - menuItem = getChild("Deselect"); - menuItem->setClickCallback(boost::bind(&LLTextEditor::deselect, mEditor)); - menuItem->setEnableCallback(boost::bind(&LLTextEditor::canDeselect, mEditor)); - - menuItem = getChild("Search / Replace..."); - menuItem->setClickCallback(boost::bind(&LLFloaterScriptSearch::show, this)); - - menuItem = getChild("Go to line..."); - menuItem->setClickCallback(boost::bind(&LLFloaterGotoLine::show, this)); - - menuItem = getChild("Keyword Help..."); - menuItem->setClickCallback(boost::bind(&LLScriptEdCore::onBtnDynamicHelp, this)); - - menuItem = getChild("LoadFromFile"); - menuItem->setClickCallback(boost::bind(&LLScriptEdCore::onBtnLoadFromFile, this)); - menuItem->setEnableCallback(boost::bind(&LLScriptEdCore::enableLoadFromFileMenu, this)); - - menuItem = getChild("SaveToFile"); - menuItem->setClickCallback(boost::bind(&LLScriptEdCore::onBtnSaveToFile, this)); - menuItem->setEnableCallback(boost::bind(&LLScriptEdCore::enableSaveToFileMenu, this)); -} - -void LLScriptEdCore::setScriptText(const std::string& text, bool is_valid) -{ - if (mEditor) - { - mEditor->setText(text); - mHasScriptData = is_valid; - } -} - -void LLScriptEdCore::makeEditorPristine() -{ - if (mEditor) - { - mEditor->makePristine(); - } -} - -bool LLScriptEdCore::loadScriptText(const std::string& filename) -{ - if (filename.empty()) - { - LL_WARNS() << "Empty file name" << LL_ENDL; - return false; - } - - LLFILE* file = LLFile::fopen(filename, "rb"); /*Flawfinder: ignore*/ - if (!file) - { - LL_WARNS() << "Error opening " << filename << LL_ENDL; - return false; - } - - // read in the whole file - fseek(file, 0L, SEEK_END); - size_t file_length = (size_t) ftell(file); - fseek(file, 0L, SEEK_SET); - char* buffer = new char[file_length+1]; - size_t nread = fread(buffer, 1, file_length, file); - if (nread < file_length) - { - LL_WARNS() << "Short read" << LL_ENDL; - } - buffer[nread] = '\0'; - fclose(file); - - std::string text = std::string(buffer); - LLStringUtil::replaceTabsWithSpaces(text, LLTextEditor::spacesPerTab()); - - mEditor->setText(text); - delete[] buffer; - - return true; -} - -bool LLScriptEdCore::writeToFile(const std::string& filename) -{ - LLFILE* fp = LLFile::fopen(filename, "wb"); - if (!fp) - { - LL_WARNS() << "Unable to write to " << filename << LL_ENDL; - - LLSD row; - row["columns"][0]["value"] = "Error writing to local file. Is your hard drive full?"; - row["columns"][0]["font"] = "SANSSERIF_SMALL"; - mErrorList->addElement(row); - return false; - } - - std::string utf8text = mEditor->getText(); - - // Special case for a completely empty script - stuff in one space so it can store properly. See SL-46889 - if (utf8text.size() == 0) - { - utf8text = " "; - } - - fputs(utf8text.c_str(), fp); - fclose(fp); - return true; -} - -void LLScriptEdCore::sync() -{ - // Sync with external editor. - if (mLiveFile) - { - std::string tmp_file = mLiveFile->filename(); - llstat s; - if (LLFile::stat(tmp_file, &s) == 0) // file exists - { - mLiveFile->ignoreNextUpdate(); - writeToFile(tmp_file); - } - } -} - -bool LLScriptEdCore::hasChanged() -{ - if (!mEditor) return false; - - return ((!mEditor->isPristine() || mEnableSave) && mHasScriptData); -} - -void LLScriptEdCore::draw() -{ - bool script_changed = hasChanged(); - getChildView("Save_btn")->setEnabled(script_changed && !mScriptRemoved); - - if( mEditor->hasFocus() ) - { - S32 line = 0; - S32 column = 0; - mEditor->getCurrentLineAndColumn( &line, &column, false ); // don't include wordwrap - LLStringUtil::format_map_t args; - std::string cursor_pos; - args["[LINE]"] = llformat ("%d", line); - args["[COLUMN]"] = llformat ("%d", column); - cursor_pos = LLTrans::getString("CursorPos", args); - getChild("line_col")->setValue(cursor_pos); - } - else - { - getChild("line_col")->setValue(LLStringUtil::null); - } - - updateDynamicHelp(); - - LLPanel::draw(); -} - -void LLScriptEdCore::updateDynamicHelp(bool immediate) -{ - LLFloater* help_floater = mLiveHelpHandle.get(); - if (!help_floater) return; - - // update back and forward buttons - LLButton* fwd_button = help_floater->getChild("fwd_btn"); - LLButton* back_button = help_floater->getChild("back_btn"); - LLMediaCtrl* browser = help_floater->getChild("lsl_guide_html"); - back_button->setEnabled(browser->canNavigateBack()); - fwd_button->setEnabled(browser->canNavigateForward()); - - if (!immediate && !gSavedSettings.getBOOL("ScriptHelpFollowsCursor")) - { - return; - } - - LLTextSegmentPtr segment = NULL; - std::vector selected_segments; - mEditor->getSelectedSegments(selected_segments); - LLKeywordToken* token; - // try segments in selection range first - std::vector::iterator segment_iter; - for (segment_iter = selected_segments.begin(); segment_iter != selected_segments.end(); ++segment_iter) - { - token = (*segment_iter)->getToken(); - if(token && isKeyword(token)) - { - segment = *segment_iter; - break; - } - } - - // then try previous segment in case we just typed it - if (!segment) - { - const LLTextSegmentPtr test_segment = mEditor->getPreviousSegment(); - token = test_segment->getToken(); - if(token && isKeyword(token)) - { - segment = test_segment; - } - } - - if (segment) - { - if (segment->getToken() != mLastHelpToken) - { - mLastHelpToken = segment->getToken(); - mLiveHelpTimer.start(); - } - if (immediate || (mLiveHelpTimer.getStarted() && mLiveHelpTimer.getElapsedTimeF32() > LIVE_HELP_REFRESH_TIME)) - { - // Use Wtext since segment's start/end are made for wstring and will - // result in a shift for case of multi-byte symbols inside std::string. - LLWString segment_text = mEditor->getWText().substr(segment->getStart(), segment->getEnd() - segment->getStart()); - std::string help_string = wstring_to_utf8str(segment_text); - setHelpPage(help_string); - mLiveHelpTimer.stop(); - } - } - else - { - if (immediate) - { - setHelpPage(LLStringUtil::null); - } - } -} - -bool LLScriptEdCore::isKeyword(LLKeywordToken* token) -{ - switch(token->getType()) - { - case LLKeywordToken::TT_CONSTANT: - case LLKeywordToken::TT_CONTROL: - case LLKeywordToken::TT_EVENT: - case LLKeywordToken::TT_FUNCTION: - case LLKeywordToken::TT_SECTION: - case LLKeywordToken::TT_TYPE: - case LLKeywordToken::TT_WORD: - return true; - - default: - return false; - } -} - -void LLScriptEdCore::setHelpPage(const std::string& help_string) -{ - LLFloater* help_floater = mLiveHelpHandle.get(); - if (!help_floater) return; - - LLMediaCtrl* web_browser = help_floater->getChild("lsl_guide_html"); - if (!web_browser) return; - - LLComboBox* history_combo = help_floater->getChild("history_combo"); - if (!history_combo) return; - - LLUIString url_string = gSavedSettings.getString("LSLHelpURL"); - - url_string.setArg("[LSL_STRING]", help_string.empty() ? HELP_LSL_PORTAL_TOPIC : help_string); - - addHelpItemToHistory(help_string); - - web_browser->navigateTo(url_string); - -} - - -void LLScriptEdCore::addHelpItemToHistory(const std::string& help_string) -{ - if (help_string.empty()) return; - - LLFloater* help_floater = mLiveHelpHandle.get(); - if (!help_floater) return; - - LLComboBox* history_combo = help_floater->getChild("history_combo"); - if (!history_combo) return; - - // separate history items from full item list - if (mLiveHelpHistorySize == 0) - { - history_combo->addSeparator(ADD_TOP); - } - // delete all history items over history limit - while(mLiveHelpHistorySize > MAX_HISTORY_COUNT - 1) - { - history_combo->remove(mLiveHelpHistorySize - 1); - mLiveHelpHistorySize--; - } - - history_combo->setSimple(help_string); - S32 index = history_combo->getCurrentIndex(); - - // if help string exists in the combo box - if (index >= 0) - { - S32 cur_index = history_combo->getCurrentIndex(); - if (cur_index < mLiveHelpHistorySize) - { - // item found in history, bubble up to top - history_combo->remove(history_combo->getCurrentIndex()); - mLiveHelpHistorySize--; - } - } - history_combo->add(help_string, LLSD(help_string), ADD_TOP); - history_combo->selectFirstItem(); - mLiveHelpHistorySize++; -} - -bool LLScriptEdCore::canClose() -{ - if(mForceClose || !hasChanged() || mScriptRemoved) - { - return true; - } - else - { - if(!mSaveDialogShown) - { - mSaveDialogShown = true; - // Bring up view-modal dialog: Save changes? Yes, No, Cancel - LLNotificationsUtil::add("SaveChanges", LLSD(), LLSD(), boost::bind(&LLScriptEdCore::handleSaveChangesDialog, this, _1, _2)); - } - return false; - } -} - -void LLScriptEdCore::setEnableEditing(bool enable) -{ - mEditor->setEnabled(enable); - getChildView("Edit_btn")->setEnabled(enable); -} - -bool LLScriptEdCore::handleSaveChangesDialog(const LLSD& notification, const LLSD& response ) -{ - mSaveDialogShown = false; - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - switch( option ) - { - case 0: // "Yes" - // close after saving - doSave( true ); - break; - - case 1: // "No" - mForceClose = true; - // This will close immediately because mForceClose is true, so we won't - // infinite loop with these dialogs. JC - ((LLFloater*) getParent())->closeFloater(); - break; - - case 2: // "Cancel" - default: - // If we were quitting, we didn't really mean it. - LLAppViewer::instance()->abortQuit(); - break; - } - return false; -} - -void LLScriptEdCore::onBtnDynamicHelp() -{ - LLFloater* live_help_floater = mLiveHelpHandle.get(); - if (!live_help_floater) - { - live_help_floater = new LLFloater(LLSD()); - live_help_floater->buildFromFile("floater_lsl_guide.xml"); - LLFloater* parent = dynamic_cast(getParent()); - llassert(parent); - if (parent) - parent->addDependentFloater(live_help_floater, true); - live_help_floater->childSetCommitCallback("lock_check", onCheckLock, this); - live_help_floater->getChild("lock_check")->setValue(gSavedSettings.getBOOL("ScriptHelpFollowsCursor")); - live_help_floater->childSetCommitCallback("history_combo", onHelpComboCommit, this); - live_help_floater->childSetAction("back_btn", onClickBack, this); - live_help_floater->childSetAction("fwd_btn", onClickForward, this); - - LLMediaCtrl* browser = live_help_floater->getChild("lsl_guide_html"); - browser->setAlwaysRefresh(true); - - LLComboBox* help_combo = live_help_floater->getChild("history_combo"); - LLKeywordToken *token; - LLKeywords::keyword_iterator_t token_it; - for (token_it = mEditor->keywordsBegin(); - token_it != mEditor->keywordsEnd(); - ++token_it) - { - token = token_it->second; - help_combo->add(wstring_to_utf8str(token->getToken())); - } - help_combo->sortByName(); - - // re-initialize help variables - mLastHelpToken = NULL; - mLiveHelpHandle = live_help_floater->getHandle(); - mLiveHelpHistorySize = 0; - } - - bool visible = true; - bool take_focus = true; - live_help_floater->setVisible(visible); - live_help_floater->setFrontmost(take_focus); - - updateDynamicHelp(true); -} - -//static -void LLScriptEdCore::onClickBack(void* userdata) -{ - LLScriptEdCore* corep = (LLScriptEdCore*)userdata; - LLFloater* live_help_floater = corep->mLiveHelpHandle.get(); - if (live_help_floater) - { - LLMediaCtrl* browserp = live_help_floater->getChild("lsl_guide_html"); - if (browserp) - { - browserp->navigateBack(); - } - } -} - -//static -void LLScriptEdCore::onClickForward(void* userdata) -{ - LLScriptEdCore* corep = (LLScriptEdCore*)userdata; - LLFloater* live_help_floater = corep->mLiveHelpHandle.get(); - if (live_help_floater) - { - LLMediaCtrl* browserp = live_help_floater->getChild("lsl_guide_html"); - if (browserp) - { - browserp->navigateForward(); - } - } -} - -// static -void LLScriptEdCore::onCheckLock(LLUICtrl* ctrl, void* userdata) -{ - LLScriptEdCore* corep = (LLScriptEdCore*)userdata; - - // clear out token any time we lock the frame, so we will refresh web page immediately when unlocked - gSavedSettings.setBOOL("ScriptHelpFollowsCursor", ctrl->getValue().asBoolean()); - - corep->mLastHelpToken = NULL; -} - -// static -void LLScriptEdCore::onBtnInsertSample(void* userdata) -{ - LLScriptEdCore* self = (LLScriptEdCore*) userdata; - - // Insert sample code - self->mEditor->selectAll(); - self->mEditor->cut(); - self->mEditor->insertText(self->mSampleText); -} - -// static -void LLScriptEdCore::onHelpComboCommit(LLUICtrl* ctrl, void* userdata) -{ - LLScriptEdCore* corep = (LLScriptEdCore*)userdata; - - LLFloater* live_help_floater = corep->mLiveHelpHandle.get(); - if (live_help_floater) - { - std::string help_string = ctrl->getValue().asString(); - - corep->addHelpItemToHistory(help_string); - - LLMediaCtrl* web_browser = live_help_floater->getChild("lsl_guide_html"); - LLUIString url_string = gSavedSettings.getString("LSLHelpURL"); - url_string.setArg("[LSL_STRING]", help_string); - web_browser->navigateTo(url_string); - } -} - -// static -void LLScriptEdCore::onBtnInsertFunction(LLUICtrl *ui, void* userdata) -{ - LLScriptEdCore* self = (LLScriptEdCore*) userdata; - - // Insert sample code - if(self->mEditor->getEnabled()) - { - self->mEditor->insertText(self->mFunctions->getSimple()); - } - self->mEditor->setFocus(true); - self->setHelpPage(self->mFunctions->getSimple()); -} - -void LLScriptEdCore::doSave( bool close_after_save ) -{ - add(LLStatViewer::LSL_SAVES, 1); - - if( mSaveCallback ) - { - mSaveCallback( mUserdata, close_after_save ); - } -} - -void LLScriptEdCore::openInExternalEditor() -{ - delete mLiveFile; // deletes file - - // Generate a suitable filename - std::string script_name = mScriptName; - std::string forbidden_chars = "<>:\"\\/|?*"; - for (std::string::iterator c = forbidden_chars.begin(); c != forbidden_chars.end(); c++) - { - script_name.erase(std::remove(script_name.begin(), script_name.end(), *c), script_name.end()); - } - std::string filename = mContainer->getTmpFileName(script_name); - - // Save the script to a temporary file. - if (!writeToFile(filename)) - { - // In case some characters from script name are forbidden - // and not accounted for, name is too long or some other issue, - // try file that doesn't include script name - script_name.clear(); - filename = mContainer->getTmpFileName(script_name); - writeToFile(filename); - } - - // Start watching file changes. - mLiveFile = new LLLiveLSLFile(filename, boost::bind(&LLScriptEdContainer::onExternalChange, mContainer, _1)); - mLiveFile->addToEventTimer(); - - // Open it in external editor. - { - LLExternalEditor ed; - LLExternalEditor::EErrorCode status; - std::string msg; - - status = ed.setCommand("LL_SCRIPT_EDITOR"); - if (status != LLExternalEditor::EC_SUCCESS) - { - if (status == LLExternalEditor::EC_NOT_SPECIFIED) // Use custom message for this error. - { - msg = LLTrans::getString("ExternalEditorNotSet"); - } - else - { - msg = LLExternalEditor::getErrorMessage(status); - } - - LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", msg)); - return; - } - - status = ed.run(filename); - if (status != LLExternalEditor::EC_SUCCESS) - { - msg = LLExternalEditor::getErrorMessage(status); - LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", msg)); - } - } -} - -void LLScriptEdCore::onBtnUndoChanges() -{ - if( !mEditor->tryToRevertToPristineState() ) - { - LLNotificationsUtil::add("ScriptCannotUndo", LLSD(), LLSD(), boost::bind(&LLScriptEdCore::handleReloadFromServerDialog, this, _1, _2)); - } -} - -// static -void LLScriptEdCore::onErrorList(LLUICtrl*, void* user_data) -{ - LLScriptEdCore* self = (LLScriptEdCore*)user_data; - LLScrollListItem* item = self->mErrorList->getFirstSelected(); - if(item) - { - // *FIX: replace with boost grep - S32 row = 0; - S32 column = 0; - const LLScrollListCell* cell = item->getColumn(0); - std::string line(cell->getValue().asString()); - line.erase(0, 1); - LLStringUtil::replaceChar(line, ',',' '); - LLStringUtil::replaceChar(line, ')',' '); - sscanf(line.c_str(), "%d %d", &row, &column); - //LL_INFOS() << "LLScriptEdCore::onErrorList() - " << row << ", " - //<< column << LL_ENDL; - self->mEditor->setCursor(row, column); - self->mEditor->setFocus(true); - } -} - -bool LLScriptEdCore::handleReloadFromServerDialog(const LLSD& notification, const LLSD& response ) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - switch( option ) - { - case 0: // "Yes" - if( mLoadCallback ) - { - setScriptText(getString("loading"), false); - mLoadCallback(mUserdata); - } - break; - - case 1: // "No" - break; - - default: - llassert(0); - break; - } - return false; -} - -void LLScriptEdCore::selectFirstError() -{ - // Select the first item; - mErrorList->selectFirstItem(); - onErrorList(mErrorList, this); -} - - -struct LLEntryAndEdCore -{ - LLScriptEdCore* mCore; - LLEntryAndEdCore(LLScriptEdCore* core) : - mCore(core) - {} -}; - -void LLScriptEdCore::deleteBridges() -{ - S32 count = mBridges.size(); - LLEntryAndEdCore* eandc; - for(S32 i = 0; i < count; i++) - { - eandc = mBridges.at(i); - delete eandc; - mBridges[i] = NULL; - } - mBridges.clear(); -} - -// virtual -bool LLScriptEdCore::handleKeyHere(KEY key, MASK mask) -{ - bool just_control = MASK_CONTROL == (mask & MASK_MODIFIERS); - - if(('S' == key) && just_control) - { - if(mSaveCallback) - { - // don't close after saving - mSaveCallback(mUserdata, false); - } - - return true; - } - - if(('F' == key) && just_control) - { - if(mSearchReplaceCallback) - { - mSearchReplaceCallback(mUserdata); - } - - return true; - } - - return false; -} - -void LLScriptEdCore::onBtnLoadFromFile( void* data ) -{ - LLFilePickerReplyThread::startPicker(boost::bind(&LLScriptEdCore::loadScriptFromFile, _1, data), LLFilePicker::FFLOAD_SCRIPT, false); -} - -void LLScriptEdCore::loadScriptFromFile(const std::vector& filenames, void* data) -{ - std::string filename = filenames[0]; - - llifstream fin(filename.c_str()); - - std::string line; - std::string text; - std::string linetotal; - while (!fin.eof()) - { - getline(fin, line); - text += line; - if (!fin.eof()) - { - text += "\n"; - } - } - fin.close(); - - // Only replace the script if there is something to replace with. - LLScriptEdCore* self = (LLScriptEdCore*)data; - if (self && (text.length() > 0)) - { - self->mEditor->selectAll(); - LLWString script(utf8str_to_wstring(text)); - self->mEditor->insertText(script); - } -} - -void LLScriptEdCore::onBtnSaveToFile( void* userdata ) -{ - add(LLStatViewer::LSL_SAVES, 1); - - LLScriptEdCore* self = (LLScriptEdCore*) userdata; - - if( self->mSaveCallback ) - { - LLFilePickerReplyThread::startPicker(boost::bind(&LLScriptEdCore::saveScriptToFile, _1, userdata), LLFilePicker::FFSAVE_SCRIPT, self->mScriptName); - } -} - -void LLScriptEdCore::saveScriptToFile(const std::vector& filenames, void* data) -{ - LLScriptEdCore* self = (LLScriptEdCore*)data; - if (self) - { - std::string filename = filenames[0]; - std::string scriptText = self->mEditor->getText(); - llofstream fout(filename.c_str()); - fout << (scriptText); - fout.close(); - self->mSaveCallback(self->mUserdata, false); - } -} - -bool LLScriptEdCore::canLoadOrSaveToFile( void* userdata ) -{ - LLScriptEdCore* self = (LLScriptEdCore*) userdata; - return self->mEditor->canLoadOrSaveToFile(); -} - -// static -bool LLScriptEdCore::enableSaveToFileMenu(void* userdata) -{ - LLScriptEdCore* self = (LLScriptEdCore*)userdata; - if (!self || !self->mEditor) return false; - return self->mEditor->canLoadOrSaveToFile(); -} - -// static -bool LLScriptEdCore::enableLoadFromFileMenu(void* userdata) -{ - LLScriptEdCore* self = (LLScriptEdCore*)userdata; - return (self && self->mEditor) ? self->mEditor->canLoadOrSaveToFile() : false; -} - -LLUUID LLScriptEdCore::getAssociatedExperience()const -{ - return mAssociatedExperience; -} - -void LLScriptEdCore::onChangeFontSize(const LLSD &userdata) -{ - const std::string font_name = userdata.asString(); - gSavedSettings.setString("LSLFontSizeName", font_name); -} - -bool LLScriptEdCore::isFontSizeChecked(const LLSD &userdata) -{ - const std::string current_size_name = LLScriptEditor::getScriptFontSize(); - const std::string size_name = userdata.asString(); - - return (size_name == current_size_name); -} - - void LLLiveLSLEditor::setExperienceIds( const LLSD& experience_ids ) -{ - mExperienceIds=experience_ids; - updateExperiencePanel(); -} - - -void LLLiveLSLEditor::updateExperiencePanel() -{ - if(mScriptEd->getAssociatedExperience().isNull()) - { - mExperienceEnabled->set(false); - mExperiences->setVisible(false); - if(mExperienceIds.size()>0) - { - mExperienceEnabled->setEnabled(true); - mExperienceEnabled->setToolTip(getString("add_experiences")); - } - else - { - mExperienceEnabled->setEnabled(false); - mExperienceEnabled->setToolTip(getString("no_experiences")); - } - getChild("view_profile")->setVisible(false); - } - else - { - mExperienceEnabled->setToolTip(getString("experience_enabled")); - mExperienceEnabled->setEnabled(getIsModifiable()); - mExperiences->setVisible(true); - mExperienceEnabled->set(true); - getChild("view_profile")->setToolTip(getString("show_experience_profile")); - buildExperienceList(); - } -} - -void LLLiveLSLEditor::buildExperienceList() -{ - mExperiences->clearRows(); - bool foundAssociated=false; - const LLUUID& associated = mScriptEd->getAssociatedExperience(); - LLUUID last; - LLScrollListItem* item; - for(LLSD::array_const_iterator it = mExperienceIds.beginArray(); it != mExperienceIds.endArray(); ++it) - { - LLUUID id = it->asUUID(); - EAddPosition position = ADD_BOTTOM; - if(id == associated) - { - foundAssociated = true; - position = ADD_TOP; - } - - const LLSD& experience = LLExperienceCache::instance().get(id); - if(experience.isUndefined()) - { - mExperiences->add(getString("loading"), id, position); - last = id; - } - else - { - std::string experience_name_string = experience[LLExperienceCache::NAME].asString(); - if (experience_name_string.empty()) - { - experience_name_string = LLTrans::getString("ExperienceNameUntitled"); - } - mExperiences->add(experience_name_string, id, position); - } - } - - if(!foundAssociated ) - { - const LLSD& experience = LLExperienceCache::instance().get(associated); - if(experience.isDefined()) - { - std::string experience_name_string = experience[LLExperienceCache::NAME].asString(); - if (experience_name_string.empty()) - { - experience_name_string = LLTrans::getString("ExperienceNameUntitled"); - } - item=mExperiences->add(experience_name_string, associated, ADD_TOP); - } - else - { - item=mExperiences->add(getString("loading"), associated, ADD_TOP); - last = associated; - } - item->setEnabled(false); - } - - if(last.notNull()) - { - mExperiences->setEnabled(false); - LLExperienceCache::instance().get(last, boost::bind(&LLLiveLSLEditor::buildExperienceList, this)); - } - else - { - mExperiences->setEnabled(true); - mExperiences->sortByName(true); - mExperiences->setCurrentByIndex(mExperiences->getCurrentIndex()); - getChild("view_profile")->setVisible(true); - } -} - - -void LLScriptEdCore::setAssociatedExperience( const LLUUID& experience_id ) -{ - mAssociatedExperience = experience_id; -} - - - -void LLLiveLSLEditor::requestExperiences() -{ - if (!getIsModifiable()) - { - return; - } - - LLViewerRegion* region = gAgent.getRegion(); - if (region) - { - std::string lookup_url=region->getCapability("GetCreatorExperiences"); - if(!lookup_url.empty()) - { - LLCoreHttpUtil::HttpCoroutineAdapter::completionCallback_t success = - boost::bind(&LLLiveLSLEditor::receiveExperienceIds, _1, getDerivedHandle()); - - LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpGet(lookup_url, success); - } - } -} - -/*static*/ -void LLLiveLSLEditor::receiveExperienceIds(LLSD result, LLHandle hparent) -{ - LLLiveLSLEditor* parent = hparent.get(); - if (!parent) - return; - - parent->setExperienceIds(result["experience_ids"]); -} - - -/// --------------------------------------------------------------------------- -/// LLScriptEdContainer -/// --------------------------------------------------------------------------- - -LLScriptEdContainer::LLScriptEdContainer(const LLSD& key) : - LLPreview(key) -, mScriptEd(NULL) -{ -} - -std::string LLScriptEdContainer::getTmpFileName(const std::string& script_name) -{ - // Take script inventory item id (within the object inventory) - // to consideration so that it's possible to edit multiple scripts - // in the same object inventory simultaneously (STORM-781). - std::string script_id = mObjectUUID.asString() + "_" + mItemUUID.asString(); - - // Use MD5 sum to make the file name shorter and not exceed maximum path length. - char script_id_hash_str[33]; /* Flawfinder: ignore */ - LLMD5 script_id_hash((const U8 *)script_id.c_str()); - script_id_hash.hex_digest(script_id_hash_str); - - if (script_name.empty()) - { - return std::string(LLFile::tmpdir()) + "sl_script_" + script_id_hash_str + ".lsl"; - } - else - { - return std::string(LLFile::tmpdir()) + "sl_script_" + script_name + "_" + script_id_hash_str + ".lsl"; - } -} - -bool LLScriptEdContainer::onExternalChange(const std::string& filename) -{ - if (!mScriptEd->loadScriptText(filename)) - { - return false; - } - - // Disable sync to avoid recursive load->save->load calls. - saveIfNeeded(false); - return true; -} - -bool LLScriptEdContainer::handleKeyHere(KEY key, MASK mask) -{ - if (('A' == key) && (MASK_CONTROL == (mask & MASK_MODIFIERS))) - { - mScriptEd->selectAll(); - return true; - } - - if (!LLPreview::handleKeyHere(key, mask)) - { - return mScriptEd->handleKeyHere(key, mask); - } - return true; -} - -/// --------------------------------------------------------------------------- -/// LLPreviewLSL -/// --------------------------------------------------------------------------- - -struct LLScriptSaveInfo -{ - LLUUID mItemUUID; - std::string mDescription; - LLTransactionID mTransactionID; - - LLScriptSaveInfo(const LLUUID& uuid, const std::string& desc, LLTransactionID tid) : - mItemUUID(uuid), mDescription(desc), mTransactionID(tid) {} -}; - - - -//static -void* LLPreviewLSL::createScriptEdPanel(void* userdata) -{ - - LLPreviewLSL *self = (LLPreviewLSL*)userdata; - - self->mScriptEd = new LLScriptEdCore( - self, - HELLO_LSL, - self->getHandle(), - LLPreviewLSL::onLoad, - LLPreviewLSL::onSave, - LLPreviewLSL::onSearchReplace, - self, - false, - 0); - return self->mScriptEd; -} - - -LLPreviewLSL::LLPreviewLSL(const LLSD& key ) -: LLScriptEdContainer(key), - mPendingUploads(0) -{ - mFactoryMap["script panel"] = LLCallbackMap(LLPreviewLSL::createScriptEdPanel, this); - - mItemObserver = new LLScriptMovedObserver(this); -} - -LLPreviewLSL::~LLPreviewLSL() -{ - delete mItemObserver; - mItemObserver = NULL; -} - -// virtual -bool LLPreviewLSL::postBuild() -{ - const LLInventoryItem* item = getItem(); - - llassert(item); - if (item) - { - getChild("desc")->setValue(item->getDescription()); - - std::string item_path = get_category_path(item->getParentUUID()); - getChild("path_txt")->setValue(item_path); - getChild("path_txt")->setToolTip(item_path); - } - childSetCommitCallback("desc", LLPreview::onText, this); - getChild("desc")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); - - return LLPreview::postBuild(); -} - -void LLPreviewLSL::draw() -{ - const LLInventoryItem* item = getItem(); - if(!item) - { - setTitle(LLTrans::getString("ScriptWasDeleted")); - mScriptEd->setItemRemoved(true); - } - else if (mDirty) - { - std::string item_path = get_category_path(item->getParentUUID()); - getChild("path_txt")->setValue(item_path); - getChild("path_txt")->setToolTip(item_path); - } - LLPreview::draw(); -} -// virtual -void LLPreviewLSL::callbackLSLCompileSucceeded() -{ - LL_INFOS() << "LSL Bytecode saved" << LL_ENDL; - mScriptEd->mErrorList->setCommentText(LLTrans::getString("CompileSuccessful")); - mScriptEd->mErrorList->setCommentText(LLTrans::getString("SaveComplete")); - closeIfNeeded(); -} - -// virtual -void LLPreviewLSL::callbackLSLCompileFailed(const LLSD& compile_errors) -{ - LL_INFOS() << "Compile failed!" << LL_ENDL; - - for(LLSD::array_const_iterator line = compile_errors.beginArray(); - line < compile_errors.endArray(); - line++) - { - LLSD row; - std::string error_message = line->asString(); - LLStringUtil::stripNonprintable(error_message); - row["columns"][0]["value"] = error_message; - row["columns"][0]["font"] = "OCRA"; - mScriptEd->mErrorList->addElement(row); - } - mScriptEd->selectFirstError(); - closeIfNeeded(); -} - -void LLPreviewLSL::loadAsset() -{ - // *HACK: we poke into inventory to see if it's there, and if so, - // then it might be part of the inventory library. If it's in the - // library, then you can see the script, but not modify it. - const LLInventoryItem* item = gInventory.getItem(mItemUUID); - bool is_library = item - && !gInventory.isObjectDescendentOf(mItemUUID, - gInventory.getRootFolderID()); - if(!item) - { - // do the more generic search. - getItem(); - } - if(item) - { - bool is_copyable = gAgent.allowOperation(PERM_COPY, - item->getPermissions(), GP_OBJECT_MANIPULATE); - bool is_modifiable = gAgent.allowOperation(PERM_MODIFY, - item->getPermissions(), GP_OBJECT_MANIPULATE); - if (gAgent.isGodlike() || (is_copyable && (is_modifiable || is_library))) - { - LLUUID* new_uuid = new LLUUID(mItemUUID); - gAssetStorage->getInvItemAsset(LLHost(), - gAgent.getID(), - gAgent.getSessionID(), - item->getPermissions().getOwner(), - LLUUID::null, - item->getUUID(), - item->getAssetUUID(), - item->getType(), - &LLPreviewLSL::onLoadComplete, - (void*)new_uuid, - true); - mAssetStatus = PREVIEW_ASSET_LOADING; - } - else - { - mScriptEd->setScriptText(mScriptEd->getString("can_not_view"), false); - mScriptEd->mEditor->makePristine(); - mScriptEd->mFunctions->setEnabled(false); - mAssetStatus = PREVIEW_ASSET_LOADED; - } - getChildView("lock")->setVisible( !is_modifiable); - mScriptEd->getChildView("Insert...")->setEnabled(is_modifiable); - } - else - { - mScriptEd->setScriptText(std::string(HELLO_LSL), true); - mScriptEd->setEnableEditing(true); - mAssetStatus = PREVIEW_ASSET_LOADED; - } -} - - -bool LLPreviewLSL::canClose() -{ - return mScriptEd->canClose(); -} - -void LLPreviewLSL::closeIfNeeded() -{ - // Find our window and close it if requested. - getWindow()->decBusyCount(); - mPendingUploads--; - if (mPendingUploads <= 0 && mCloseAfterSave) - { - closeFloater(); - } -} - -void LLPreviewLSL::onSearchReplace(void* userdata) -{ - LLPreviewLSL* self = (LLPreviewLSL*)userdata; - LLScriptEdCore* sec = self->mScriptEd; - LLFloaterScriptSearch::show(sec); -} - -// static -void LLPreviewLSL::onLoad(void* userdata) -{ - LLPreviewLSL* self = (LLPreviewLSL*)userdata; - self->loadAsset(); -} - -// static -void LLPreviewLSL::onSave(void* userdata, bool close_after_save) -{ - LLPreviewLSL* self = (LLPreviewLSL*)userdata; - self->mCloseAfterSave = close_after_save; - self->saveIfNeeded(); -} - -/*static*/ -void LLPreviewLSL::finishedLSLUpload(LLUUID itemId, LLSD response) -{ - // Find our window and close it if requested. - LLPreviewLSL* preview = LLFloaterReg::findTypedInstance("preview_script", LLSD(itemId)); - if (preview) - { - // Bytecode save completed - if (response["compiled"]) - { - preview->callbackLSLCompileSucceeded(); - } - else - { - preview->callbackLSLCompileFailed(response["errors"]); - } - } -} - -bool LLPreviewLSL::failedLSLUpload(LLUUID itemId, LLUUID taskId, LLSD response, std::string reason) -{ - LLSD floater_key; - if (taskId.notNull()) - { - floater_key["taskid"] = taskId; - floater_key["itemid"] = itemId; - } - else - { - floater_key = LLSD(itemId); - } - - LLPreviewLSL* preview = LLFloaterReg::findTypedInstance("preview_script", floater_key); - if (preview) - { - // unfreeze floater - LLSD errors; - errors.append(LLTrans::getString("UploadFailed") + reason); - preview->callbackLSLCompileFailed(errors); - return true; - } - - return false; -} - -// Save needs to compile the text in the buffer. If the compile -// succeeds, then save both assets out to the database. If the compile -// fails, go ahead and save the text anyway. -void LLPreviewLSL::saveIfNeeded(bool sync /*= true*/) -{ - if (!mScriptEd->hasChanged()) - { - return; - } - - mPendingUploads = 0; - mScriptEd->mErrorList->deleteAllItems(); - mScriptEd->mEditor->makePristine(); - - if (sync) - { - mScriptEd->sync(); - } - - if (!gAgent.getRegion()) return; - const LLInventoryItem *inv_item = getItem(); - // save it out to asset server - std::string url = gAgent.getRegion()->getCapability("UpdateScriptAgent"); - if(inv_item) - { - getWindow()->incBusyCount(); - mPendingUploads++; - if (!url.empty()) - { - std::string buffer(mScriptEd->mEditor->getText()); - - LLUUID old_asset_id = inv_item->getAssetUUID().isNull() ? mScriptEd->getAssetID() : inv_item->getAssetUUID(); - - LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared(mItemUUID, buffer, - [old_asset_id](LLUUID itemId, LLUUID, LLUUID, LLSD response) { - LLFileSystem::removeFile(old_asset_id, LLAssetType::AT_LSL_TEXT); - LLPreviewLSL::finishedLSLUpload(itemId, response); - }, - LLPreviewLSL::failedLSLUpload)); - - LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo); - } - } -} - -// static -void LLPreviewLSL::onLoadComplete(const LLUUID& asset_uuid, LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status) -{ - LL_DEBUGS() << "LLPreviewLSL::onLoadComplete: got uuid " << asset_uuid - << LL_ENDL; - LLUUID* item_uuid = (LLUUID*)user_data; - LLPreviewLSL* preview = LLFloaterReg::findTypedInstance("preview_script", *item_uuid); - if( preview ) - { - if(0 == status) - { - LLFileSystem file(asset_uuid, type); - S32 file_length = file.getSize(); - - std::vector buffer(file_length+1); - file.read((U8*)&buffer[0], file_length); - - // put a EOS at the end - buffer[file_length] = 0; - preview->mScriptEd->setScriptText(LLStringExplicit(&buffer[0]), true); - preview->mScriptEd->mEditor->makePristine(); - - std::string script_name = DEFAULT_SCRIPT_NAME; - LLInventoryItem* item = gInventory.getItem(*item_uuid); - bool is_modifiable = false; - if (item) - { - if (!item->getName().empty()) - { - script_name = item->getName(); - } - if (gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), GP_OBJECT_MANIPULATE)) - { - is_modifiable = true; - } - } - preview->mScriptEd->setScriptName(script_name); - preview->mScriptEd->setEnableEditing(is_modifiable); - preview->mScriptEd->setAssetID(asset_uuid); - preview->mAssetStatus = PREVIEW_ASSET_LOADED; - } - else - { - if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status || - LL_ERR_FILE_EMPTY == status) - { - LLNotificationsUtil::add("ScriptMissing"); - } - else if (LL_ERR_INSUFFICIENT_PERMISSIONS == status) - { - LLNotificationsUtil::add("ScriptNoPermissions"); - } - else - { - LLNotificationsUtil::add("UnableToLoadScript"); - } - - preview->mAssetStatus = PREVIEW_ASSET_ERROR; - LL_WARNS() << "Problem loading script: " << status << LL_ENDL; - } - } - delete item_uuid; -} - - -/// --------------------------------------------------------------------------- -/// LLLiveLSLEditor -/// --------------------------------------------------------------------------- - - -//static -void* LLLiveLSLEditor::createScriptEdPanel(void* userdata) -{ - LLLiveLSLEditor *self = (LLLiveLSLEditor*)userdata; - - self->mScriptEd = new LLScriptEdCore( - self, - HELLO_LSL, - self->getHandle(), - &LLLiveLSLEditor::onLoad, - &LLLiveLSLEditor::onSave, - &LLLiveLSLEditor::onSearchReplace, - self, - true, - 0); - return self->mScriptEd; -} - - -LLLiveLSLEditor::LLLiveLSLEditor(const LLSD& key) : - LLScriptEdContainer(key), - mAskedForRunningInfo(false), - mHaveRunningInfo(false), - mCloseAfterSave(false), - mPendingUploads(0), - mIsModifiable(false), - mIsNew(false), - mIsSaving(false), - mObjectName("") -{ - mFactoryMap["script ed panel"] = LLCallbackMap(LLLiveLSLEditor::createScriptEdPanel, this); -} - -bool LLLiveLSLEditor::postBuild() -{ - childSetCommitCallback("running", LLLiveLSLEditor::onRunningCheckboxClicked, this); - getChildView("running")->setEnabled(false); - - childSetAction("Reset",&LLLiveLSLEditor::onReset,this); - getChildView("Reset")->setEnabled(true); - - mMonoCheckbox = getChild("mono"); - childSetCommitCallback("mono", &LLLiveLSLEditor::onMonoCheckboxClicked, this); - getChildView("mono")->setEnabled(true); - - mScriptEd->mEditor->makePristine(); - mScriptEd->mEditor->setFocus(true); - - - mExperiences = getChild("Experiences..."); - mExperiences->setCommitCallback(boost::bind(&LLLiveLSLEditor::experienceChanged, this)); - - mExperienceEnabled = getChild("enable_xp"); - - childSetCommitCallback("enable_xp", onToggleExperience, this); - childSetCommitCallback("view_profile", onViewProfile, this); - - - return LLPreview::postBuild(); -} - -// virtual -void LLLiveLSLEditor::callbackLSLCompileSucceeded(const LLUUID& task_id, - const LLUUID& item_id, - bool is_script_running) -{ - LL_DEBUGS() << "LSL Bytecode saved" << LL_ENDL; - mScriptEd->mErrorList->setCommentText(LLTrans::getString("CompileSuccessful")); - mScriptEd->mErrorList->setCommentText(LLTrans::getString("SaveComplete")); - getChild("running")->set(is_script_running); - mIsSaving = false; - closeIfNeeded(); -} - -// virtual -void LLLiveLSLEditor::callbackLSLCompileFailed(const LLSD& compile_errors) -{ - LL_DEBUGS() << "Compile failed!" << LL_ENDL; - for(LLSD::array_const_iterator line = compile_errors.beginArray(); - line < compile_errors.endArray(); - line++) - { - LLSD row; - std::string error_message = line->asString(); - LLStringUtil::stripNonprintable(error_message); - row["columns"][0]["value"] = error_message; - // *TODO: change to "MONOSPACE" and change llfontgl.cpp? - row["columns"][0]["font"] = "OCRA"; - mScriptEd->mErrorList->addElement(row); - } - mScriptEd->selectFirstError(); - mIsSaving = false; - closeIfNeeded(); -} - -void LLLiveLSLEditor::loadAsset() -{ - //LL_INFOS() << "LLLiveLSLEditor::loadAsset()" << LL_ENDL; - if(!mIsNew) - { - LLViewerObject* object = gObjectList.findObject(mObjectUUID); - if(object) - { - LLViewerInventoryItem* item = dynamic_cast(object->getInventoryObject(mItemUUID)); - - if(item) - { - LLViewerRegion* region = object->getRegion(); - std::string url = std::string(); - if(region) - { - url = region->getCapability("GetMetadata"); - } - LLExperienceCache::instance().fetchAssociatedExperience(item->getParentUUID(), item->getUUID(), url, - boost::bind(&LLLiveLSLEditor::setAssociatedExperience, getDerivedHandle(), _1)); - - bool isGodlike = gAgent.isGodlike(); - bool copyManipulate = gAgent.allowOperation(PERM_COPY, item->getPermissions(), GP_OBJECT_MANIPULATE); - mIsModifiable = gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), GP_OBJECT_MANIPULATE); - - if(!isGodlike && (!copyManipulate || !mIsModifiable)) - { - mItem = new LLViewerInventoryItem(item); - mScriptEd->setScriptText(getString("not_allowed"), false); - mScriptEd->mEditor->makePristine(); - mScriptEd->enableSave(false); - mAssetStatus = PREVIEW_ASSET_LOADED; - } - else if(copyManipulate || isGodlike) - { - mItem = new LLViewerInventoryItem(item); - // request the text from the object - LLSD* user_data = new LLSD(); - user_data->with("taskid", mObjectUUID).with("itemid", mItemUUID); - gAssetStorage->getInvItemAsset(object->getRegion()->getHost(), - gAgent.getID(), - gAgent.getSessionID(), - item->getPermissions().getOwner(), - object->getID(), - item->getUUID(), - item->getAssetUUID(), - item->getType(), - &LLLiveLSLEditor::onLoadComplete, - (void*)user_data, - true); - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_GetScriptRunning); - msg->nextBlockFast(_PREHASH_Script); - msg->addUUIDFast(_PREHASH_ObjectID, mObjectUUID); - msg->addUUIDFast(_PREHASH_ItemID, mItemUUID); - msg->sendReliable(object->getRegion()->getHost()); - mAskedForRunningInfo = true; - mAssetStatus = PREVIEW_ASSET_LOADING; - } - } - - if(mItem.isNull()) - { - mScriptEd->setScriptText(LLStringUtil::null, false); - mScriptEd->mEditor->makePristine(); - mAssetStatus = PREVIEW_ASSET_LOADED; - mIsModifiable = false; - } - - refreshFromItem(); - getChild("obj_name")->setValue(mObjectName); - // This is commented out, because we don't completely - // handle script exports yet. - /* - // request the exports from the object - gMessageSystem->newMessage("GetScriptExports"); - gMessageSystem->nextBlock("ScriptBlock"); - gMessageSystem->addUUID("AgentID", gAgent.getID()); - U32 local_id = object->getLocalID(); - gMessageSystem->addData("LocalID", &local_id); - gMessageSystem->addUUID("ItemID", mItemUUID); - LLHost host(object->getRegion()->getIP(), - object->getRegion()->getPort()); - gMessageSystem->sendReliable(host); - */ - } - } - else - { - mScriptEd->setScriptText(std::string(HELLO_LSL), true); - mScriptEd->enableSave(false); - LLPermissions perm; - perm.init(gAgent.getID(), gAgent.getID(), LLUUID::null, gAgent.getGroupID()); - perm.initMasks(PERM_ALL, PERM_ALL, PERM_NONE, PERM_NONE, PERM_MOVE | PERM_TRANSFER); - mItem = new LLViewerInventoryItem(mItemUUID, - mObjectUUID, - perm, - LLUUID::null, - LLAssetType::AT_LSL_TEXT, - LLInventoryType::IT_LSL, - DEFAULT_SCRIPT_NAME, - DEFAULT_SCRIPT_DESC, - LLSaleInfo::DEFAULT, - LLInventoryItemFlags::II_FLAGS_NONE, - time_corrected()); - mAssetStatus = PREVIEW_ASSET_LOADED; - } - - requestExperiences(); -} - -// static -void LLLiveLSLEditor::onLoadComplete(const LLUUID& asset_id, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status) -{ - LL_DEBUGS() << "LLLiveLSLEditor::onLoadComplete: got uuid " << asset_id - << LL_ENDL; - LLSD* floater_key = (LLSD*)user_data; - - LLLiveLSLEditor* instance = LLFloaterReg::findTypedInstance("preview_scriptedit", *floater_key); - - if(instance ) - { - if( LL_ERR_NOERR == status ) - { - instance->loadScriptText(asset_id, type); - instance->mScriptEd->setEnableEditing(true); - instance->mAssetStatus = PREVIEW_ASSET_LOADED; - instance->mScriptEd->setAssetID(asset_id); - } - else - { - if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status || - LL_ERR_FILE_EMPTY == status) - { - LLNotificationsUtil::add("ScriptMissing"); - } - else if (LL_ERR_INSUFFICIENT_PERMISSIONS == status) - { - LLNotificationsUtil::add("ScriptNoPermissions"); - } - else - { - LLNotificationsUtil::add("UnableToLoadScript"); - } - instance->mAssetStatus = PREVIEW_ASSET_ERROR; - } - } - - delete floater_key; -} - -void LLLiveLSLEditor::loadScriptText(const LLUUID &uuid, LLAssetType::EType type) -{ - LLFileSystem file(uuid, type); - S32 file_length = file.getSize(); - std::vector buffer(file_length + 1); - file.read((U8*)&buffer[0], file_length); - - if (file.getLastBytesRead() != file_length || - file_length <= 0) - { - LL_WARNS() << "Error reading " << uuid << ":" << type << LL_ENDL; - } - - buffer[file_length] = '\0'; - - mScriptEd->setScriptText(LLStringExplicit(&buffer[0]), true); - mScriptEd->makeEditorPristine(); - - std::string script_name = DEFAULT_SCRIPT_NAME; - const LLInventoryItem* inv_item = getItem(); - - if(inv_item) - { - script_name = inv_item->getName(); - } - mScriptEd->setScriptName(script_name); -} - - -void LLLiveLSLEditor::onRunningCheckboxClicked( LLUICtrl*, void* userdata ) -{ - LLLiveLSLEditor* self = (LLLiveLSLEditor*) userdata; - LLViewerObject* object = gObjectList.findObject( self->mObjectUUID ); - LLCheckBoxCtrl* runningCheckbox = self->getChild("running"); - bool running = runningCheckbox->get(); - //self->mRunningCheckbox->get(); - if( object ) - { - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_SetScriptRunning); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_Script); - msg->addUUIDFast(_PREHASH_ObjectID, self->mObjectUUID); - msg->addUUIDFast(_PREHASH_ItemID, self->mItemUUID); - msg->addBOOLFast(_PREHASH_Running, running); - msg->sendReliable(object->getRegion()->getHost()); - } - else - { - runningCheckbox->set(!running); - LLNotificationsUtil::add("CouldNotStartStopScript"); - } -} - -void LLLiveLSLEditor::onReset(void *userdata) -{ - LLLiveLSLEditor* self = (LLLiveLSLEditor*) userdata; - - LLViewerObject* object = gObjectList.findObject( self->mObjectUUID ); - if(object) - { - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_ScriptReset); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_Script); - msg->addUUIDFast(_PREHASH_ObjectID, self->mObjectUUID); - msg->addUUIDFast(_PREHASH_ItemID, self->mItemUUID); - msg->sendReliable(object->getRegion()->getHost()); - } - else - { - LLNotificationsUtil::add("CouldNotStartStopScript"); - } -} - -void LLLiveLSLEditor::draw() -{ - LLViewerObject* object = gObjectList.findObject(mObjectUUID); - LLCheckBoxCtrl* runningCheckbox = getChild( "running"); - if(object && mAskedForRunningInfo && mHaveRunningInfo) - { - if(object->permAnyOwner()) - { - runningCheckbox->setLabel(getString("script_running")); - runningCheckbox->setEnabled(!mIsSaving); - } - else - { - runningCheckbox->setLabel(getString("public_objects_can_not_run")); - runningCheckbox->setEnabled(false); - - // *FIX: Set it to false so that the ui is correct for - // a box that is released to public. It could be - // incorrect after a release/claim cycle, but will be - // correct after clicking on it. - runningCheckbox->set(false); - mMonoCheckbox->set(false); - } - } - else if(!object) - { - // HACK: Display this information in the title bar. - // Really ought to put in main window. - setTitle(LLTrans::getString("ObjectOutOfRange")); - runningCheckbox->setEnabled(false); - mMonoCheckbox->setEnabled(false); - // object may have fallen out of range. - mHaveRunningInfo = false; - } - - LLPreview::draw(); -} - - -void LLLiveLSLEditor::onSearchReplace(void* userdata) -{ - LLLiveLSLEditor* self = (LLLiveLSLEditor*)userdata; - - LLScriptEdCore* sec = self->mScriptEd; - LLFloaterScriptSearch::show(sec); -} - -struct LLLiveLSLSaveData -{ - LLLiveLSLSaveData(const LLUUID& id, const LLViewerInventoryItem* item, bool active); - LLUUID mSaveObjectID; - LLPointer mItem; - bool mActive; -}; - -LLLiveLSLSaveData::LLLiveLSLSaveData(const LLUUID& id, - const LLViewerInventoryItem* item, - bool active) : - mSaveObjectID(id), - mActive(active) -{ - llassert(item); - mItem = new LLViewerInventoryItem(item); -} - -/*static*/ -void LLLiveLSLEditor::finishLSLUpload(LLUUID itemId, LLUUID taskId, LLUUID newAssetId, LLSD response, bool isRunning) -{ - LLSD floater_key; - floater_key["taskid"] = taskId; - floater_key["itemid"] = itemId; - - LLLiveLSLEditor* preview = LLFloaterReg::findTypedInstance("preview_scriptedit", floater_key); - if (preview) - { - preview->mItem->setAssetUUID(newAssetId); - preview->mScriptEd->setAssetID(newAssetId); - - // Bytecode save completed - if (response["compiled"]) - { - preview->callbackLSLCompileSucceeded(taskId, itemId, isRunning); - } - else - { - preview->callbackLSLCompileFailed(response["errors"]); - } - } -} - -// virtual -void LLLiveLSLEditor::saveIfNeeded(bool sync /*= true*/) -{ - LLViewerObject* object = gObjectList.findObject(mObjectUUID); - if (!object) - { - LLNotificationsUtil::add("SaveScriptFailObjectNotFound"); - return; - } - - if (mItem.isNull() || !mItem->isFinished()) - { - // $NOTE: While the error message may not be exactly correct, - // it's pretty close. - LLNotificationsUtil::add("SaveScriptFailObjectNotFound"); - return; - } - - // get the latest info about it. We used to be losing the script - // name on save, because the viewer object version of the item, - // and the editor version would get out of synch. Here's a good - // place to synch them back up. - LLInventoryItem* inv_item = dynamic_cast(object->getInventoryObject(mItemUUID)); - if (inv_item) - { - mItem->copyItem(inv_item); - } - - // Don't need to save if we're pristine - if (!mScriptEd->hasChanged()) - { - return; - } - - mPendingUploads = 0; - - // save the script - mScriptEd->enableSave(false); - mScriptEd->mEditor->makePristine(); - mScriptEd->mErrorList->deleteAllItems(); - mScriptEd->mEditor->makePristine(); - - if (sync) - { - mScriptEd->sync(); - } - - bool isRunning = getChild("running")->get(); - getWindow()->incBusyCount(); - mPendingUploads++; - - std::string url = object->getRegion()->getCapability("UpdateScriptTask"); - - if (!url.empty()) - { - std::string buffer(mScriptEd->mEditor->getText()); - LLUUID old_asset_id = mScriptEd->getAssetID(); - - LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared(mObjectUUID, mItemUUID, - monoChecked() ? LLScriptAssetUpload::MONO : LLScriptAssetUpload::LSL2, - isRunning, mScriptEd->getAssociatedExperience(), buffer, - [isRunning, old_asset_id](LLUUID itemId, LLUUID taskId, LLUUID newAssetId, LLSD response) { - LLFileSystem::removeFile(old_asset_id, LLAssetType::AT_LSL_TEXT); - LLLiveLSLEditor::finishLSLUpload(itemId, taskId, newAssetId, response, isRunning); - }, - nullptr)); // needs failure handling? - - LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo); - } -} - -bool LLLiveLSLEditor::canClose() -{ - return mScriptEd->canClose(); -} - -void LLLiveLSLEditor::closeIfNeeded() -{ - getWindow()->decBusyCount(); - mPendingUploads--; - if ((mPendingUploads <= 0) && mCloseAfterSave) - { - closeFloater(); - } -} - -// static -void LLLiveLSLEditor::onLoad(void* userdata) -{ - LLLiveLSLEditor* self = (LLLiveLSLEditor*)userdata; - self->loadAsset(); -} - -// static -void LLLiveLSLEditor::onSave(void* userdata, bool close_after_save) -{ - if (LLLiveLSLEditor* self = (LLLiveLSLEditor*)userdata) - { - self->mCloseAfterSave = close_after_save; - self->mScriptEd->mErrorList->setCommentText(""); - self->saveIfNeeded(); - } -} - -// static -void LLLiveLSLEditor::processScriptRunningReply(LLMessageSystem* msg, void**) -{ - LLUUID item_id; - LLUUID object_id; - msg->getUUIDFast(_PREHASH_Script, _PREHASH_ObjectID, object_id); - msg->getUUIDFast(_PREHASH_Script, _PREHASH_ItemID, item_id); - - LLSD floater_key; - floater_key["taskid"] = object_id; - floater_key["itemid"] = item_id; - if (LLLiveLSLEditor* instance = LLFloaterReg::findTypedInstance("preview_scriptedit", floater_key)) - { - instance->mHaveRunningInfo = true; - bool running; - msg->getBOOLFast(_PREHASH_Script, _PREHASH_Running, running); - LLCheckBoxCtrl* runningCheckbox = instance->getChild("running"); - runningCheckbox->set(running); - bool mono; - msg->getBOOLFast(_PREHASH_Script, "Mono", mono); - LLCheckBoxCtrl* monoCheckbox = instance->getChild("mono"); - monoCheckbox->setEnabled(instance->getIsModifiable() && have_script_upload_cap(object_id)); - monoCheckbox->set(mono); - } -} - -void LLLiveLSLEditor::onMonoCheckboxClicked(LLUICtrl*, void* userdata) -{ - LLLiveLSLEditor* self = static_cast(userdata); - self->mMonoCheckbox->setEnabled(have_script_upload_cap(self->mObjectUUID)); - self->mScriptEd->enableSave(self->getIsModifiable()); -} - -bool LLLiveLSLEditor::monoChecked() const -{ - return mMonoCheckbox && mMonoCheckbox->getValue(); -} - -void LLLiveLSLEditor::setAssociatedExperience( LLHandle editor, const LLSD& experience ) -{ - if (LLLiveLSLEditor* scriptEd = editor.get()) - { - LLUUID id; - if (experience.has(LLExperienceCache::EXPERIENCE_ID)) - { - id=experience[LLExperienceCache::EXPERIENCE_ID].asUUID(); - } - scriptEd->mScriptEd->setAssociatedExperience(id); - scriptEd->updateExperiencePanel(); - } -} +/** + * @file llpreviewscript.cpp + * @brief LLPreviewScript class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpreviewscript.h" + +#include "llassetstorage.h" +#include "llbutton.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "lldir.h" +#include "llexternaleditor.h" +#include "llfilepicker.h" +#include "llfloaterreg.h" +#include "llinventorydefines.h" +#include "llinventorymodel.h" +#include "llkeyboard.h" +#include "lllineeditor.h" +#include "llmd5.h" +#include "llhelp.h" +#include "llnotificationsutil.h" +#include "llresmgr.h" +#include "llscrollbar.h" +#include "llscrollcontainer.h" +#include "llscrolllistctrl.h" +#include "llscrolllistitem.h" +#include "llscrolllistcell.h" +#include "llsdserialize.h" +#include "llslider.h" +#include "lltooldraganddrop.h" +#include "llfilesystem.h" + +#include "llagent.h" +#include "llmenugl.h" +#include "roles_constants.h" +#include "llselectmgr.h" +#include "llviewerinventory.h" +#include "llviewermenu.h" +#include "llviewermenufile.h" // LLFilePickerReplyThread +#include "llviewerobject.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llkeyboard.h" +#include "llscrollcontainer.h" +#include "llcheckboxctrl.h" +#include "llscripteditor.h" +#include "llselectmgr.h" +#include "lltooldraganddrop.h" +#include "llscrolllistctrl.h" +#include "lltextbox.h" +#include "llslider.h" +#include "lldir.h" +#include "llcombobox.h" +#include "llviewerstats.h" +#include "llviewerwindow.h" +#include "lluictrlfactory.h" +#include "llmediactrl.h" +#include "lluictrlfactory.h" +#include "lltrans.h" +#include "llviewercontrol.h" +#include "llappviewer.h" +#include "llfloatergotoline.h" +#include "llexperiencecache.h" +#include "llfloaterexperienceprofile.h" +#include "llviewerassetupload.h" +#include "lltoggleablemenu.h" +#include "llmenubutton.h" +#include "llinventoryfunctions.h" + +const std::string HELLO_LSL = + "default\n" + "{\n" + " state_entry()\n" + " {\n" + " llSay(0, \"Hello, Avatar!\");\n" + " }\n" + "\n" + " touch_start(integer total_number)\n" + " {\n" + " llSay(0, \"Touched.\");\n" + " }\n" + "}\n"; +const std::string HELP_LSL_PORTAL_TOPIC = "LSL_Portal"; + +const std::string DEFAULT_SCRIPT_NAME = "New Script"; // *TODO:Translate? +const std::string DEFAULT_SCRIPT_DESC = "(No Description)"; // *TODO:Translate? + +// Description and header information +const S32 MAX_HISTORY_COUNT = 10; +const F32 LIVE_HELP_REFRESH_TIME = 1.f; + +static bool have_script_upload_cap(LLUUID& object_id) +{ + LLViewerObject* object = gObjectList.findObject(object_id); + return object && (! object->getRegion()->getCapability("UpdateScriptTask").empty()); +} + +/// --------------------------------------------------------------------------- +/// LLLiveLSLFile +/// --------------------------------------------------------------------------- + +LLLiveLSLFile::LLLiveLSLFile(std::string file_path, change_callback_t change_cb) +: mOnChangeCallback(change_cb) +, mIgnoreNextUpdate(false) +, LLLiveFile(file_path, 1.0) +{ + llassert(mOnChangeCallback); +} + +LLLiveLSLFile::~LLLiveLSLFile() +{ + LLFile::remove(filename()); +} + +bool LLLiveLSLFile::loadFile() +{ + if (mIgnoreNextUpdate) + { + mIgnoreNextUpdate = false; + return true; + } + + return mOnChangeCallback(filename()); +} + +/// --------------------------------------------------------------------------- +/// LLFloaterScriptSearch +/// --------------------------------------------------------------------------- +class LLFloaterScriptSearch : public LLFloater +{ +public: + LLFloaterScriptSearch(LLScriptEdCore* editor_core); + ~LLFloaterScriptSearch(); + + /*virtual*/ bool postBuild(); + static void show(LLScriptEdCore* editor_core); + static void onBtnSearch(void* userdata); + void handleBtnSearch(); + + static void onBtnReplace(void* userdata); + void handleBtnReplace(); + + static void onBtnReplaceAll(void* userdata); + void handleBtnReplaceAll(); + + LLScriptEdCore* getEditorCore() { return mEditorCore; } + static LLFloaterScriptSearch* getInstance() { return sInstance; } + + virtual bool hasAccelerators() const; + virtual bool handleKeyHere(KEY key, MASK mask); + +private: + + LLScriptEdCore* mEditorCore; + static LLFloaterScriptSearch* sInstance; + +protected: + LLLineEditor* mSearchBox; + LLLineEditor* mReplaceBox; + void onSearchBoxCommit(); +}; + +LLFloaterScriptSearch* LLFloaterScriptSearch::sInstance = NULL; + +LLFloaterScriptSearch::LLFloaterScriptSearch(LLScriptEdCore* editor_core) +: LLFloater(LLSD()), + mSearchBox(NULL), + mReplaceBox(NULL), + mEditorCore(editor_core) +{ + buildFromFile("floater_script_search.xml"); + + sInstance = this; + + // find floater in which script panel is embedded + LLView* viewp = (LLView*)editor_core; + while(viewp) + { + LLFloater* floaterp = dynamic_cast(viewp); + if (floaterp) + { + floaterp->addDependentFloater(this); + break; + } + viewp = viewp->getParent(); + } +} + +bool LLFloaterScriptSearch::postBuild() +{ + mReplaceBox = getChild("replace_text"); + mSearchBox = getChild("search_text"); + mSearchBox->setCommitCallback(boost::bind(&LLFloaterScriptSearch::onSearchBoxCommit, this)); + mSearchBox->setCommitOnFocusLost(false); + childSetAction("search_btn", onBtnSearch,this); + childSetAction("replace_btn", onBtnReplace,this); + childSetAction("replace_all_btn", onBtnReplaceAll,this); + + setDefaultBtn("search_btn"); + + return true; +} + +//static +void LLFloaterScriptSearch::show(LLScriptEdCore* editor_core) +{ + LLSD::String search_text; + LLSD::String replace_text; + if (sInstance && sInstance->mEditorCore && sInstance->mEditorCore != editor_core) + { + search_text=sInstance->mSearchBox->getValue().asString(); + replace_text=sInstance->mReplaceBox->getValue().asString(); + sInstance->closeFloater(); + delete sInstance; + } + + if (!sInstance) + { + // sInstance will be assigned in the constructor. + new LLFloaterScriptSearch(editor_core); + sInstance->mSearchBox->setValue(search_text); + sInstance->mReplaceBox->setValue(replace_text); + } + + sInstance->openFloater(); +} + +LLFloaterScriptSearch::~LLFloaterScriptSearch() +{ + sInstance = NULL; +} + +// static +void LLFloaterScriptSearch::onBtnSearch(void *userdata) +{ + LLFloaterScriptSearch* self = (LLFloaterScriptSearch*)userdata; + self->handleBtnSearch(); +} + +void LLFloaterScriptSearch::handleBtnSearch() +{ + LLCheckBoxCtrl* caseChk = getChild("case_text"); + mEditorCore->mEditor->selectNext(mSearchBox->getValue().asString(), caseChk->get()); +} + +// static +void LLFloaterScriptSearch::onBtnReplace(void *userdata) +{ + LLFloaterScriptSearch* self = (LLFloaterScriptSearch*)userdata; + self->handleBtnReplace(); +} + +void LLFloaterScriptSearch::handleBtnReplace() +{ + LLCheckBoxCtrl* caseChk = getChild("case_text"); + mEditorCore->mEditor->replaceText(mSearchBox->getValue().asString(), mReplaceBox->getValue().asString(), caseChk->get()); +} + +// static +void LLFloaterScriptSearch::onBtnReplaceAll(void *userdata) +{ + LLFloaterScriptSearch* self = (LLFloaterScriptSearch*)userdata; + self->handleBtnReplaceAll(); +} + +void LLFloaterScriptSearch::handleBtnReplaceAll() +{ + LLCheckBoxCtrl* caseChk = getChild("case_text"); + mEditorCore->mEditor->replaceTextAll(mSearchBox->getValue().asString(), mReplaceBox->getValue().asString(), caseChk->get()); +} + +bool LLFloaterScriptSearch::hasAccelerators() const +{ + if (mEditorCore) + { + return mEditorCore->hasAccelerators(); + } + return false; +} + +bool LLFloaterScriptSearch::handleKeyHere(KEY key, MASK mask) +{ + if (mEditorCore) + { + bool handled = mEditorCore->handleKeyHere(key, mask); + if (!handled) + { + LLFloater::handleKeyHere(key, mask); + } + } + + return false; +} + +void LLFloaterScriptSearch::onSearchBoxCommit() +{ + if (mEditorCore && mEditorCore->mEditor) + { + LLCheckBoxCtrl* caseChk = getChild("case_text"); + mEditorCore->mEditor->selectNext(mSearchBox->getValue().asString(), caseChk->get()); + } +} + +/// --------------------------------------------------------------------------- + +class LLScriptMovedObserver : public LLInventoryObserver +{ + public: + LLScriptMovedObserver(LLPreviewLSL *floater) : mPreview(floater) { gInventory.addObserver(this); } + virtual ~LLScriptMovedObserver() { gInventory.removeObserver(this); } + virtual void changed(U32 mask); + + private: + LLPreviewLSL *mPreview; +}; + +void LLScriptMovedObserver::changed(U32 mask) +{ + const std::set &mChangedItemIDs = gInventory.getChangedIDs(); + std::set::const_iterator it; + + const LLUUID &item_id = mPreview->getScriptID(); + + for (it = mChangedItemIDs.begin(); it != mChangedItemIDs.end(); it++) + { + if (*it == item_id) + { + if ((mask & (LLInventoryObserver::STRUCTURE)) != 0) + { + mPreview->setDirty(); + } + } + } +} + +/// --------------------------------------------------------------------------- +/// LLScriptEdCore +/// --------------------------------------------------------------------------- + +struct LLSECKeywordCompare +{ + bool operator()(const std::string& lhs, const std::string& rhs) + { + return (LLStringUtil::compareDictInsensitive( lhs, rhs ) < 0 ); + } +}; + +LLScriptEdCore::LLScriptEdCore( + LLScriptEdContainer* container, + const std::string& sample, + const LLHandle& floater_handle, + void (*load_callback)(void*), + void (*save_callback)(void*, bool), + void (*search_replace_callback) (void* userdata), + void* userdata, + bool live, + S32 bottom_pad) + : + LLPanel(), + mSampleText(sample), + mEditor( NULL ), + mLoadCallback( load_callback ), + mSaveCallback( save_callback ), + mSearchReplaceCallback( search_replace_callback ), + mUserdata( userdata ), + mForceClose( false ), + mLastHelpToken(NULL), + mLiveHelpHistorySize(0), + mEnableSave(false), + mLiveFile(NULL), + mLive(live), + mContainer(container), + mHasScriptData(false), + mScriptRemoved(false), + mSaveDialogShown(false) +{ + setFollowsAll(); + setBorderVisible(false); + + setXMLFilename("panel_script_ed.xml"); + llassert_always(mContainer != NULL); +} + +LLScriptEdCore::~LLScriptEdCore() +{ + deleteBridges(); + + // If the search window is up for this editor, close it. + LLFloaterScriptSearch* script_search = LLFloaterScriptSearch::getInstance(); + if (script_search && script_search->getEditorCore() == this) + { + script_search->closeFloater(); + delete script_search; + } + + delete mLiveFile; + if (mSyntaxIDConnection.connected()) + { + mSyntaxIDConnection.disconnect(); + } +} + +void LLLiveLSLEditor::experienceChanged() +{ + if(mScriptEd->getAssociatedExperience() != mExperiences->getSelectedValue().asUUID()) + { + mScriptEd->enableSave(getIsModifiable()); + //getChildView("Save_btn")->setEnabled(true); + mScriptEd->setAssociatedExperience(mExperiences->getSelectedValue().asUUID()); + updateExperiencePanel(); + } +} + +void LLLiveLSLEditor::onViewProfile( LLUICtrl *ui, void* userdata ) +{ + LLLiveLSLEditor* self = (LLLiveLSLEditor*)userdata; + + LLUUID id; + if(self->mExperienceEnabled->get()) + { + id=self->mScriptEd->getAssociatedExperience(); + if(id.notNull()) + { + LLFloaterReg::showInstance("experience_profile", id, true); + } + } + +} + +void LLLiveLSLEditor::onToggleExperience( LLUICtrl *ui, void* userdata ) +{ + LLLiveLSLEditor* self = (LLLiveLSLEditor*)userdata; + + LLUUID id; + if(self->mExperienceEnabled->get()) + { + if(self->mScriptEd->getAssociatedExperience().isNull()) + { + id=self->mExperienceIds.beginArray()->asUUID(); + } + } + + if(id != self->mScriptEd->getAssociatedExperience()) + { + self->mScriptEd->enableSave(self->getIsModifiable()); + } + self->mScriptEd->setAssociatedExperience(id); + + self->updateExperiencePanel(); +} + +bool LLScriptEdCore::postBuild() +{ + mErrorList = getChild("lsl errors"); + + mFunctions = getChild("Insert..."); + + childSetCommitCallback("Insert...", &LLScriptEdCore::onBtnInsertFunction, this); + + mEditor = getChild("Script Editor"); + + childSetCommitCallback("lsl errors", &LLScriptEdCore::onErrorList, this); + childSetAction("Save_btn", boost::bind(&LLScriptEdCore::doSave,this,false)); + childSetAction("Edit_btn", boost::bind(&LLScriptEdCore::openInExternalEditor, this)); + + initMenu(); + + mSyntaxIDConnection = LLSyntaxIdLSL::getInstance()->addSyntaxIDCallback(boost::bind(&LLScriptEdCore::processKeywords, this)); + + // Intialise keyword highlighting for the current simulator's version of LSL + LLSyntaxIdLSL::getInstance()->initialize(); + processKeywords(); + + mCommitCallbackRegistrar.add("FontSize.Set", boost::bind(&LLScriptEdCore::onChangeFontSize, this, _2)); + mEnableCallbackRegistrar.add("FontSize.Check", boost::bind(&LLScriptEdCore::isFontSizeChecked, this, _2)); + + LLToggleableMenu *context_menu = LLUICtrlFactory::getInstance()->createFromFile( + "menu_lsl_font_size.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + getChild("font_btn")->setMenu(context_menu, LLMenuButton::MP_BOTTOM_LEFT, true); + + return true; +} + +void LLScriptEdCore::processKeywords() +{ + LL_DEBUGS("SyntaxLSL") << "Processing keywords" << LL_ENDL; + mEditor->clearSegments(); + mEditor->initKeywords(); + mEditor->loadKeywords(); + + string_vec_t primary_keywords; + string_vec_t secondary_keywords; + LLKeywordToken *token; + LLKeywords::keyword_iterator_t token_it; + for (token_it = mEditor->keywordsBegin(); token_it != mEditor->keywordsEnd(); ++token_it) + { + token = token_it->second; + if (token->getType() == LLKeywordToken::TT_FUNCTION) + { + primary_keywords.push_back( wstring_to_utf8str(token->getToken()) ); + } + else + { + secondary_keywords.push_back( wstring_to_utf8str(token->getToken()) ); + } + } + for (string_vec_t::const_iterator iter = primary_keywords.begin(); + iter!= primary_keywords.end(); ++iter) + { + mFunctions->add(*iter); + } + for (string_vec_t::const_iterator iter = secondary_keywords.begin(); + iter!= secondary_keywords.end(); ++iter) + { + mFunctions->add(*iter); + } +} + +void LLScriptEdCore::initMenu() +{ + // *TODO: Skinning - make these callbacks data driven + LLMenuItemCallGL* menuItem; + + menuItem = getChild("Save"); + menuItem->setClickCallback(boost::bind(&LLScriptEdCore::doSave, this, false)); + menuItem->setEnableCallback(boost::bind(&LLScriptEdCore::hasChanged, this)); + + menuItem = getChild("Revert All Changes"); + menuItem->setClickCallback(boost::bind(&LLScriptEdCore::onBtnUndoChanges, this)); + menuItem->setEnableCallback(boost::bind(&LLScriptEdCore::hasChanged, this)); + + menuItem = getChild("Undo"); + menuItem->setClickCallback(boost::bind(&LLTextEditor::undo, mEditor)); + menuItem->setEnableCallback(boost::bind(&LLTextEditor::canUndo, mEditor)); + + menuItem = getChild("Redo"); + menuItem->setClickCallback(boost::bind(&LLTextEditor::redo, mEditor)); + menuItem->setEnableCallback(boost::bind(&LLTextEditor::canRedo, mEditor)); + + menuItem = getChild("Cut"); + menuItem->setClickCallback(boost::bind(&LLTextEditor::cut, mEditor)); + menuItem->setEnableCallback(boost::bind(&LLTextEditor::canCut, mEditor)); + + menuItem = getChild("Copy"); + menuItem->setClickCallback(boost::bind(&LLTextEditor::copy, mEditor)); + menuItem->setEnableCallback(boost::bind(&LLTextEditor::canCopy, mEditor)); + + menuItem = getChild("Paste"); + menuItem->setClickCallback(boost::bind(&LLTextEditor::paste, mEditor)); + menuItem->setEnableCallback(boost::bind(&LLTextEditor::canPaste, mEditor)); + + menuItem = getChild("Select All"); + menuItem->setClickCallback(boost::bind(&LLTextEditor::selectAll, mEditor)); + menuItem->setEnableCallback(boost::bind(&LLTextEditor::canSelectAll, mEditor)); + + menuItem = getChild("Deselect"); + menuItem->setClickCallback(boost::bind(&LLTextEditor::deselect, mEditor)); + menuItem->setEnableCallback(boost::bind(&LLTextEditor::canDeselect, mEditor)); + + menuItem = getChild("Search / Replace..."); + menuItem->setClickCallback(boost::bind(&LLFloaterScriptSearch::show, this)); + + menuItem = getChild("Go to line..."); + menuItem->setClickCallback(boost::bind(&LLFloaterGotoLine::show, this)); + + menuItem = getChild("Keyword Help..."); + menuItem->setClickCallback(boost::bind(&LLScriptEdCore::onBtnDynamicHelp, this)); + + menuItem = getChild("LoadFromFile"); + menuItem->setClickCallback(boost::bind(&LLScriptEdCore::onBtnLoadFromFile, this)); + menuItem->setEnableCallback(boost::bind(&LLScriptEdCore::enableLoadFromFileMenu, this)); + + menuItem = getChild("SaveToFile"); + menuItem->setClickCallback(boost::bind(&LLScriptEdCore::onBtnSaveToFile, this)); + menuItem->setEnableCallback(boost::bind(&LLScriptEdCore::enableSaveToFileMenu, this)); +} + +void LLScriptEdCore::setScriptText(const std::string& text, bool is_valid) +{ + if (mEditor) + { + mEditor->setText(text); + mHasScriptData = is_valid; + } +} + +void LLScriptEdCore::makeEditorPristine() +{ + if (mEditor) + { + mEditor->makePristine(); + } +} + +bool LLScriptEdCore::loadScriptText(const std::string& filename) +{ + if (filename.empty()) + { + LL_WARNS() << "Empty file name" << LL_ENDL; + return false; + } + + LLFILE* file = LLFile::fopen(filename, "rb"); /*Flawfinder: ignore*/ + if (!file) + { + LL_WARNS() << "Error opening " << filename << LL_ENDL; + return false; + } + + // read in the whole file + fseek(file, 0L, SEEK_END); + size_t file_length = (size_t) ftell(file); + fseek(file, 0L, SEEK_SET); + char* buffer = new char[file_length+1]; + size_t nread = fread(buffer, 1, file_length, file); + if (nread < file_length) + { + LL_WARNS() << "Short read" << LL_ENDL; + } + buffer[nread] = '\0'; + fclose(file); + + std::string text = std::string(buffer); + LLStringUtil::replaceTabsWithSpaces(text, LLTextEditor::spacesPerTab()); + + mEditor->setText(text); + delete[] buffer; + + return true; +} + +bool LLScriptEdCore::writeToFile(const std::string& filename) +{ + LLFILE* fp = LLFile::fopen(filename, "wb"); + if (!fp) + { + LL_WARNS() << "Unable to write to " << filename << LL_ENDL; + + LLSD row; + row["columns"][0]["value"] = "Error writing to local file. Is your hard drive full?"; + row["columns"][0]["font"] = "SANSSERIF_SMALL"; + mErrorList->addElement(row); + return false; + } + + std::string utf8text = mEditor->getText(); + + // Special case for a completely empty script - stuff in one space so it can store properly. See SL-46889 + if (utf8text.size() == 0) + { + utf8text = " "; + } + + fputs(utf8text.c_str(), fp); + fclose(fp); + return true; +} + +void LLScriptEdCore::sync() +{ + // Sync with external editor. + if (mLiveFile) + { + std::string tmp_file = mLiveFile->filename(); + llstat s; + if (LLFile::stat(tmp_file, &s) == 0) // file exists + { + mLiveFile->ignoreNextUpdate(); + writeToFile(tmp_file); + } + } +} + +bool LLScriptEdCore::hasChanged() +{ + if (!mEditor) return false; + + return ((!mEditor->isPristine() || mEnableSave) && mHasScriptData); +} + +void LLScriptEdCore::draw() +{ + bool script_changed = hasChanged(); + getChildView("Save_btn")->setEnabled(script_changed && !mScriptRemoved); + + if( mEditor->hasFocus() ) + { + S32 line = 0; + S32 column = 0; + mEditor->getCurrentLineAndColumn( &line, &column, false ); // don't include wordwrap + LLStringUtil::format_map_t args; + std::string cursor_pos; + args["[LINE]"] = llformat ("%d", line); + args["[COLUMN]"] = llformat ("%d", column); + cursor_pos = LLTrans::getString("CursorPos", args); + getChild("line_col")->setValue(cursor_pos); + } + else + { + getChild("line_col")->setValue(LLStringUtil::null); + } + + updateDynamicHelp(); + + LLPanel::draw(); +} + +void LLScriptEdCore::updateDynamicHelp(bool immediate) +{ + LLFloater* help_floater = mLiveHelpHandle.get(); + if (!help_floater) return; + + // update back and forward buttons + LLButton* fwd_button = help_floater->getChild("fwd_btn"); + LLButton* back_button = help_floater->getChild("back_btn"); + LLMediaCtrl* browser = help_floater->getChild("lsl_guide_html"); + back_button->setEnabled(browser->canNavigateBack()); + fwd_button->setEnabled(browser->canNavigateForward()); + + if (!immediate && !gSavedSettings.getBOOL("ScriptHelpFollowsCursor")) + { + return; + } + + LLTextSegmentPtr segment = NULL; + std::vector selected_segments; + mEditor->getSelectedSegments(selected_segments); + LLKeywordToken* token; + // try segments in selection range first + std::vector::iterator segment_iter; + for (segment_iter = selected_segments.begin(); segment_iter != selected_segments.end(); ++segment_iter) + { + token = (*segment_iter)->getToken(); + if(token && isKeyword(token)) + { + segment = *segment_iter; + break; + } + } + + // then try previous segment in case we just typed it + if (!segment) + { + const LLTextSegmentPtr test_segment = mEditor->getPreviousSegment(); + token = test_segment->getToken(); + if(token && isKeyword(token)) + { + segment = test_segment; + } + } + + if (segment) + { + if (segment->getToken() != mLastHelpToken) + { + mLastHelpToken = segment->getToken(); + mLiveHelpTimer.start(); + } + if (immediate || (mLiveHelpTimer.getStarted() && mLiveHelpTimer.getElapsedTimeF32() > LIVE_HELP_REFRESH_TIME)) + { + // Use Wtext since segment's start/end are made for wstring and will + // result in a shift for case of multi-byte symbols inside std::string. + LLWString segment_text = mEditor->getWText().substr(segment->getStart(), segment->getEnd() - segment->getStart()); + std::string help_string = wstring_to_utf8str(segment_text); + setHelpPage(help_string); + mLiveHelpTimer.stop(); + } + } + else + { + if (immediate) + { + setHelpPage(LLStringUtil::null); + } + } +} + +bool LLScriptEdCore::isKeyword(LLKeywordToken* token) +{ + switch(token->getType()) + { + case LLKeywordToken::TT_CONSTANT: + case LLKeywordToken::TT_CONTROL: + case LLKeywordToken::TT_EVENT: + case LLKeywordToken::TT_FUNCTION: + case LLKeywordToken::TT_SECTION: + case LLKeywordToken::TT_TYPE: + case LLKeywordToken::TT_WORD: + return true; + + default: + return false; + } +} + +void LLScriptEdCore::setHelpPage(const std::string& help_string) +{ + LLFloater* help_floater = mLiveHelpHandle.get(); + if (!help_floater) return; + + LLMediaCtrl* web_browser = help_floater->getChild("lsl_guide_html"); + if (!web_browser) return; + + LLComboBox* history_combo = help_floater->getChild("history_combo"); + if (!history_combo) return; + + LLUIString url_string = gSavedSettings.getString("LSLHelpURL"); + + url_string.setArg("[LSL_STRING]", help_string.empty() ? HELP_LSL_PORTAL_TOPIC : help_string); + + addHelpItemToHistory(help_string); + + web_browser->navigateTo(url_string); + +} + + +void LLScriptEdCore::addHelpItemToHistory(const std::string& help_string) +{ + if (help_string.empty()) return; + + LLFloater* help_floater = mLiveHelpHandle.get(); + if (!help_floater) return; + + LLComboBox* history_combo = help_floater->getChild("history_combo"); + if (!history_combo) return; + + // separate history items from full item list + if (mLiveHelpHistorySize == 0) + { + history_combo->addSeparator(ADD_TOP); + } + // delete all history items over history limit + while(mLiveHelpHistorySize > MAX_HISTORY_COUNT - 1) + { + history_combo->remove(mLiveHelpHistorySize - 1); + mLiveHelpHistorySize--; + } + + history_combo->setSimple(help_string); + S32 index = history_combo->getCurrentIndex(); + + // if help string exists in the combo box + if (index >= 0) + { + S32 cur_index = history_combo->getCurrentIndex(); + if (cur_index < mLiveHelpHistorySize) + { + // item found in history, bubble up to top + history_combo->remove(history_combo->getCurrentIndex()); + mLiveHelpHistorySize--; + } + } + history_combo->add(help_string, LLSD(help_string), ADD_TOP); + history_combo->selectFirstItem(); + mLiveHelpHistorySize++; +} + +bool LLScriptEdCore::canClose() +{ + if(mForceClose || !hasChanged() || mScriptRemoved) + { + return true; + } + else + { + if(!mSaveDialogShown) + { + mSaveDialogShown = true; + // Bring up view-modal dialog: Save changes? Yes, No, Cancel + LLNotificationsUtil::add("SaveChanges", LLSD(), LLSD(), boost::bind(&LLScriptEdCore::handleSaveChangesDialog, this, _1, _2)); + } + return false; + } +} + +void LLScriptEdCore::setEnableEditing(bool enable) +{ + mEditor->setEnabled(enable); + getChildView("Edit_btn")->setEnabled(enable); +} + +bool LLScriptEdCore::handleSaveChangesDialog(const LLSD& notification, const LLSD& response ) +{ + mSaveDialogShown = false; + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + switch( option ) + { + case 0: // "Yes" + // close after saving + doSave( true ); + break; + + case 1: // "No" + mForceClose = true; + // This will close immediately because mForceClose is true, so we won't + // infinite loop with these dialogs. JC + ((LLFloater*) getParent())->closeFloater(); + break; + + case 2: // "Cancel" + default: + // If we were quitting, we didn't really mean it. + LLAppViewer::instance()->abortQuit(); + break; + } + return false; +} + +void LLScriptEdCore::onBtnDynamicHelp() +{ + LLFloater* live_help_floater = mLiveHelpHandle.get(); + if (!live_help_floater) + { + live_help_floater = new LLFloater(LLSD()); + live_help_floater->buildFromFile("floater_lsl_guide.xml"); + LLFloater* parent = dynamic_cast(getParent()); + llassert(parent); + if (parent) + parent->addDependentFloater(live_help_floater, true); + live_help_floater->childSetCommitCallback("lock_check", onCheckLock, this); + live_help_floater->getChild("lock_check")->setValue(gSavedSettings.getBOOL("ScriptHelpFollowsCursor")); + live_help_floater->childSetCommitCallback("history_combo", onHelpComboCommit, this); + live_help_floater->childSetAction("back_btn", onClickBack, this); + live_help_floater->childSetAction("fwd_btn", onClickForward, this); + + LLMediaCtrl* browser = live_help_floater->getChild("lsl_guide_html"); + browser->setAlwaysRefresh(true); + + LLComboBox* help_combo = live_help_floater->getChild("history_combo"); + LLKeywordToken *token; + LLKeywords::keyword_iterator_t token_it; + for (token_it = mEditor->keywordsBegin(); + token_it != mEditor->keywordsEnd(); + ++token_it) + { + token = token_it->second; + help_combo->add(wstring_to_utf8str(token->getToken())); + } + help_combo->sortByName(); + + // re-initialize help variables + mLastHelpToken = NULL; + mLiveHelpHandle = live_help_floater->getHandle(); + mLiveHelpHistorySize = 0; + } + + bool visible = true; + bool take_focus = true; + live_help_floater->setVisible(visible); + live_help_floater->setFrontmost(take_focus); + + updateDynamicHelp(true); +} + +//static +void LLScriptEdCore::onClickBack(void* userdata) +{ + LLScriptEdCore* corep = (LLScriptEdCore*)userdata; + LLFloater* live_help_floater = corep->mLiveHelpHandle.get(); + if (live_help_floater) + { + LLMediaCtrl* browserp = live_help_floater->getChild("lsl_guide_html"); + if (browserp) + { + browserp->navigateBack(); + } + } +} + +//static +void LLScriptEdCore::onClickForward(void* userdata) +{ + LLScriptEdCore* corep = (LLScriptEdCore*)userdata; + LLFloater* live_help_floater = corep->mLiveHelpHandle.get(); + if (live_help_floater) + { + LLMediaCtrl* browserp = live_help_floater->getChild("lsl_guide_html"); + if (browserp) + { + browserp->navigateForward(); + } + } +} + +// static +void LLScriptEdCore::onCheckLock(LLUICtrl* ctrl, void* userdata) +{ + LLScriptEdCore* corep = (LLScriptEdCore*)userdata; + + // clear out token any time we lock the frame, so we will refresh web page immediately when unlocked + gSavedSettings.setBOOL("ScriptHelpFollowsCursor", ctrl->getValue().asBoolean()); + + corep->mLastHelpToken = NULL; +} + +// static +void LLScriptEdCore::onBtnInsertSample(void* userdata) +{ + LLScriptEdCore* self = (LLScriptEdCore*) userdata; + + // Insert sample code + self->mEditor->selectAll(); + self->mEditor->cut(); + self->mEditor->insertText(self->mSampleText); +} + +// static +void LLScriptEdCore::onHelpComboCommit(LLUICtrl* ctrl, void* userdata) +{ + LLScriptEdCore* corep = (LLScriptEdCore*)userdata; + + LLFloater* live_help_floater = corep->mLiveHelpHandle.get(); + if (live_help_floater) + { + std::string help_string = ctrl->getValue().asString(); + + corep->addHelpItemToHistory(help_string); + + LLMediaCtrl* web_browser = live_help_floater->getChild("lsl_guide_html"); + LLUIString url_string = gSavedSettings.getString("LSLHelpURL"); + url_string.setArg("[LSL_STRING]", help_string); + web_browser->navigateTo(url_string); + } +} + +// static +void LLScriptEdCore::onBtnInsertFunction(LLUICtrl *ui, void* userdata) +{ + LLScriptEdCore* self = (LLScriptEdCore*) userdata; + + // Insert sample code + if(self->mEditor->getEnabled()) + { + self->mEditor->insertText(self->mFunctions->getSimple()); + } + self->mEditor->setFocus(true); + self->setHelpPage(self->mFunctions->getSimple()); +} + +void LLScriptEdCore::doSave( bool close_after_save ) +{ + add(LLStatViewer::LSL_SAVES, 1); + + if( mSaveCallback ) + { + mSaveCallback( mUserdata, close_after_save ); + } +} + +void LLScriptEdCore::openInExternalEditor() +{ + delete mLiveFile; // deletes file + + // Generate a suitable filename + std::string script_name = mScriptName; + std::string forbidden_chars = "<>:\"\\/|?*"; + for (std::string::iterator c = forbidden_chars.begin(); c != forbidden_chars.end(); c++) + { + script_name.erase(std::remove(script_name.begin(), script_name.end(), *c), script_name.end()); + } + std::string filename = mContainer->getTmpFileName(script_name); + + // Save the script to a temporary file. + if (!writeToFile(filename)) + { + // In case some characters from script name are forbidden + // and not accounted for, name is too long or some other issue, + // try file that doesn't include script name + script_name.clear(); + filename = mContainer->getTmpFileName(script_name); + writeToFile(filename); + } + + // Start watching file changes. + mLiveFile = new LLLiveLSLFile(filename, boost::bind(&LLScriptEdContainer::onExternalChange, mContainer, _1)); + mLiveFile->addToEventTimer(); + + // Open it in external editor. + { + LLExternalEditor ed; + LLExternalEditor::EErrorCode status; + std::string msg; + + status = ed.setCommand("LL_SCRIPT_EDITOR"); + if (status != LLExternalEditor::EC_SUCCESS) + { + if (status == LLExternalEditor::EC_NOT_SPECIFIED) // Use custom message for this error. + { + msg = LLTrans::getString("ExternalEditorNotSet"); + } + else + { + msg = LLExternalEditor::getErrorMessage(status); + } + + LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", msg)); + return; + } + + status = ed.run(filename); + if (status != LLExternalEditor::EC_SUCCESS) + { + msg = LLExternalEditor::getErrorMessage(status); + LLNotificationsUtil::add("GenericAlert", LLSD().with("MESSAGE", msg)); + } + } +} + +void LLScriptEdCore::onBtnUndoChanges() +{ + if( !mEditor->tryToRevertToPristineState() ) + { + LLNotificationsUtil::add("ScriptCannotUndo", LLSD(), LLSD(), boost::bind(&LLScriptEdCore::handleReloadFromServerDialog, this, _1, _2)); + } +} + +// static +void LLScriptEdCore::onErrorList(LLUICtrl*, void* user_data) +{ + LLScriptEdCore* self = (LLScriptEdCore*)user_data; + LLScrollListItem* item = self->mErrorList->getFirstSelected(); + if(item) + { + // *FIX: replace with boost grep + S32 row = 0; + S32 column = 0; + const LLScrollListCell* cell = item->getColumn(0); + std::string line(cell->getValue().asString()); + line.erase(0, 1); + LLStringUtil::replaceChar(line, ',',' '); + LLStringUtil::replaceChar(line, ')',' '); + sscanf(line.c_str(), "%d %d", &row, &column); + //LL_INFOS() << "LLScriptEdCore::onErrorList() - " << row << ", " + //<< column << LL_ENDL; + self->mEditor->setCursor(row, column); + self->mEditor->setFocus(true); + } +} + +bool LLScriptEdCore::handleReloadFromServerDialog(const LLSD& notification, const LLSD& response ) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + switch( option ) + { + case 0: // "Yes" + if( mLoadCallback ) + { + setScriptText(getString("loading"), false); + mLoadCallback(mUserdata); + } + break; + + case 1: // "No" + break; + + default: + llassert(0); + break; + } + return false; +} + +void LLScriptEdCore::selectFirstError() +{ + // Select the first item; + mErrorList->selectFirstItem(); + onErrorList(mErrorList, this); +} + + +struct LLEntryAndEdCore +{ + LLScriptEdCore* mCore; + LLEntryAndEdCore(LLScriptEdCore* core) : + mCore(core) + {} +}; + +void LLScriptEdCore::deleteBridges() +{ + S32 count = mBridges.size(); + LLEntryAndEdCore* eandc; + for(S32 i = 0; i < count; i++) + { + eandc = mBridges.at(i); + delete eandc; + mBridges[i] = NULL; + } + mBridges.clear(); +} + +// virtual +bool LLScriptEdCore::handleKeyHere(KEY key, MASK mask) +{ + bool just_control = MASK_CONTROL == (mask & MASK_MODIFIERS); + + if(('S' == key) && just_control) + { + if(mSaveCallback) + { + // don't close after saving + mSaveCallback(mUserdata, false); + } + + return true; + } + + if(('F' == key) && just_control) + { + if(mSearchReplaceCallback) + { + mSearchReplaceCallback(mUserdata); + } + + return true; + } + + return false; +} + +void LLScriptEdCore::onBtnLoadFromFile( void* data ) +{ + LLFilePickerReplyThread::startPicker(boost::bind(&LLScriptEdCore::loadScriptFromFile, _1, data), LLFilePicker::FFLOAD_SCRIPT, false); +} + +void LLScriptEdCore::loadScriptFromFile(const std::vector& filenames, void* data) +{ + std::string filename = filenames[0]; + + llifstream fin(filename.c_str()); + + std::string line; + std::string text; + std::string linetotal; + while (!fin.eof()) + { + getline(fin, line); + text += line; + if (!fin.eof()) + { + text += "\n"; + } + } + fin.close(); + + // Only replace the script if there is something to replace with. + LLScriptEdCore* self = (LLScriptEdCore*)data; + if (self && (text.length() > 0)) + { + self->mEditor->selectAll(); + LLWString script(utf8str_to_wstring(text)); + self->mEditor->insertText(script); + } +} + +void LLScriptEdCore::onBtnSaveToFile( void* userdata ) +{ + add(LLStatViewer::LSL_SAVES, 1); + + LLScriptEdCore* self = (LLScriptEdCore*) userdata; + + if( self->mSaveCallback ) + { + LLFilePickerReplyThread::startPicker(boost::bind(&LLScriptEdCore::saveScriptToFile, _1, userdata), LLFilePicker::FFSAVE_SCRIPT, self->mScriptName); + } +} + +void LLScriptEdCore::saveScriptToFile(const std::vector& filenames, void* data) +{ + LLScriptEdCore* self = (LLScriptEdCore*)data; + if (self) + { + std::string filename = filenames[0]; + std::string scriptText = self->mEditor->getText(); + llofstream fout(filename.c_str()); + fout << (scriptText); + fout.close(); + self->mSaveCallback(self->mUserdata, false); + } +} + +bool LLScriptEdCore::canLoadOrSaveToFile( void* userdata ) +{ + LLScriptEdCore* self = (LLScriptEdCore*) userdata; + return self->mEditor->canLoadOrSaveToFile(); +} + +// static +bool LLScriptEdCore::enableSaveToFileMenu(void* userdata) +{ + LLScriptEdCore* self = (LLScriptEdCore*)userdata; + if (!self || !self->mEditor) return false; + return self->mEditor->canLoadOrSaveToFile(); +} + +// static +bool LLScriptEdCore::enableLoadFromFileMenu(void* userdata) +{ + LLScriptEdCore* self = (LLScriptEdCore*)userdata; + return (self && self->mEditor) ? self->mEditor->canLoadOrSaveToFile() : false; +} + +LLUUID LLScriptEdCore::getAssociatedExperience()const +{ + return mAssociatedExperience; +} + +void LLScriptEdCore::onChangeFontSize(const LLSD &userdata) +{ + const std::string font_name = userdata.asString(); + gSavedSettings.setString("LSLFontSizeName", font_name); +} + +bool LLScriptEdCore::isFontSizeChecked(const LLSD &userdata) +{ + const std::string current_size_name = LLScriptEditor::getScriptFontSize(); + const std::string size_name = userdata.asString(); + + return (size_name == current_size_name); +} + + void LLLiveLSLEditor::setExperienceIds( const LLSD& experience_ids ) +{ + mExperienceIds=experience_ids; + updateExperiencePanel(); +} + + +void LLLiveLSLEditor::updateExperiencePanel() +{ + if(mScriptEd->getAssociatedExperience().isNull()) + { + mExperienceEnabled->set(false); + mExperiences->setVisible(false); + if(mExperienceIds.size()>0) + { + mExperienceEnabled->setEnabled(true); + mExperienceEnabled->setToolTip(getString("add_experiences")); + } + else + { + mExperienceEnabled->setEnabled(false); + mExperienceEnabled->setToolTip(getString("no_experiences")); + } + getChild("view_profile")->setVisible(false); + } + else + { + mExperienceEnabled->setToolTip(getString("experience_enabled")); + mExperienceEnabled->setEnabled(getIsModifiable()); + mExperiences->setVisible(true); + mExperienceEnabled->set(true); + getChild("view_profile")->setToolTip(getString("show_experience_profile")); + buildExperienceList(); + } +} + +void LLLiveLSLEditor::buildExperienceList() +{ + mExperiences->clearRows(); + bool foundAssociated=false; + const LLUUID& associated = mScriptEd->getAssociatedExperience(); + LLUUID last; + LLScrollListItem* item; + for(LLSD::array_const_iterator it = mExperienceIds.beginArray(); it != mExperienceIds.endArray(); ++it) + { + LLUUID id = it->asUUID(); + EAddPosition position = ADD_BOTTOM; + if(id == associated) + { + foundAssociated = true; + position = ADD_TOP; + } + + const LLSD& experience = LLExperienceCache::instance().get(id); + if(experience.isUndefined()) + { + mExperiences->add(getString("loading"), id, position); + last = id; + } + else + { + std::string experience_name_string = experience[LLExperienceCache::NAME].asString(); + if (experience_name_string.empty()) + { + experience_name_string = LLTrans::getString("ExperienceNameUntitled"); + } + mExperiences->add(experience_name_string, id, position); + } + } + + if(!foundAssociated ) + { + const LLSD& experience = LLExperienceCache::instance().get(associated); + if(experience.isDefined()) + { + std::string experience_name_string = experience[LLExperienceCache::NAME].asString(); + if (experience_name_string.empty()) + { + experience_name_string = LLTrans::getString("ExperienceNameUntitled"); + } + item=mExperiences->add(experience_name_string, associated, ADD_TOP); + } + else + { + item=mExperiences->add(getString("loading"), associated, ADD_TOP); + last = associated; + } + item->setEnabled(false); + } + + if(last.notNull()) + { + mExperiences->setEnabled(false); + LLExperienceCache::instance().get(last, boost::bind(&LLLiveLSLEditor::buildExperienceList, this)); + } + else + { + mExperiences->setEnabled(true); + mExperiences->sortByName(true); + mExperiences->setCurrentByIndex(mExperiences->getCurrentIndex()); + getChild("view_profile")->setVisible(true); + } +} + + +void LLScriptEdCore::setAssociatedExperience( const LLUUID& experience_id ) +{ + mAssociatedExperience = experience_id; +} + + + +void LLLiveLSLEditor::requestExperiences() +{ + if (!getIsModifiable()) + { + return; + } + + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + std::string lookup_url=region->getCapability("GetCreatorExperiences"); + if(!lookup_url.empty()) + { + LLCoreHttpUtil::HttpCoroutineAdapter::completionCallback_t success = + boost::bind(&LLLiveLSLEditor::receiveExperienceIds, _1, getDerivedHandle()); + + LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpGet(lookup_url, success); + } + } +} + +/*static*/ +void LLLiveLSLEditor::receiveExperienceIds(LLSD result, LLHandle hparent) +{ + LLLiveLSLEditor* parent = hparent.get(); + if (!parent) + return; + + parent->setExperienceIds(result["experience_ids"]); +} + + +/// --------------------------------------------------------------------------- +/// LLScriptEdContainer +/// --------------------------------------------------------------------------- + +LLScriptEdContainer::LLScriptEdContainer(const LLSD& key) : + LLPreview(key) +, mScriptEd(NULL) +{ +} + +std::string LLScriptEdContainer::getTmpFileName(const std::string& script_name) +{ + // Take script inventory item id (within the object inventory) + // to consideration so that it's possible to edit multiple scripts + // in the same object inventory simultaneously (STORM-781). + std::string script_id = mObjectUUID.asString() + "_" + mItemUUID.asString(); + + // Use MD5 sum to make the file name shorter and not exceed maximum path length. + char script_id_hash_str[33]; /* Flawfinder: ignore */ + LLMD5 script_id_hash((const U8 *)script_id.c_str()); + script_id_hash.hex_digest(script_id_hash_str); + + if (script_name.empty()) + { + return std::string(LLFile::tmpdir()) + "sl_script_" + script_id_hash_str + ".lsl"; + } + else + { + return std::string(LLFile::tmpdir()) + "sl_script_" + script_name + "_" + script_id_hash_str + ".lsl"; + } +} + +bool LLScriptEdContainer::onExternalChange(const std::string& filename) +{ + if (!mScriptEd->loadScriptText(filename)) + { + return false; + } + + // Disable sync to avoid recursive load->save->load calls. + saveIfNeeded(false); + return true; +} + +bool LLScriptEdContainer::handleKeyHere(KEY key, MASK mask) +{ + if (('A' == key) && (MASK_CONTROL == (mask & MASK_MODIFIERS))) + { + mScriptEd->selectAll(); + return true; + } + + if (!LLPreview::handleKeyHere(key, mask)) + { + return mScriptEd->handleKeyHere(key, mask); + } + return true; +} + +/// --------------------------------------------------------------------------- +/// LLPreviewLSL +/// --------------------------------------------------------------------------- + +struct LLScriptSaveInfo +{ + LLUUID mItemUUID; + std::string mDescription; + LLTransactionID mTransactionID; + + LLScriptSaveInfo(const LLUUID& uuid, const std::string& desc, LLTransactionID tid) : + mItemUUID(uuid), mDescription(desc), mTransactionID(tid) {} +}; + + + +//static +void* LLPreviewLSL::createScriptEdPanel(void* userdata) +{ + + LLPreviewLSL *self = (LLPreviewLSL*)userdata; + + self->mScriptEd = new LLScriptEdCore( + self, + HELLO_LSL, + self->getHandle(), + LLPreviewLSL::onLoad, + LLPreviewLSL::onSave, + LLPreviewLSL::onSearchReplace, + self, + false, + 0); + return self->mScriptEd; +} + + +LLPreviewLSL::LLPreviewLSL(const LLSD& key ) +: LLScriptEdContainer(key), + mPendingUploads(0) +{ + mFactoryMap["script panel"] = LLCallbackMap(LLPreviewLSL::createScriptEdPanel, this); + + mItemObserver = new LLScriptMovedObserver(this); +} + +LLPreviewLSL::~LLPreviewLSL() +{ + delete mItemObserver; + mItemObserver = NULL; +} + +// virtual +bool LLPreviewLSL::postBuild() +{ + const LLInventoryItem* item = getItem(); + + llassert(item); + if (item) + { + getChild("desc")->setValue(item->getDescription()); + + std::string item_path = get_category_path(item->getParentUUID()); + getChild("path_txt")->setValue(item_path); + getChild("path_txt")->setToolTip(item_path); + } + childSetCommitCallback("desc", LLPreview::onText, this); + getChild("desc")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); + + return LLPreview::postBuild(); +} + +void LLPreviewLSL::draw() +{ + const LLInventoryItem* item = getItem(); + if(!item) + { + setTitle(LLTrans::getString("ScriptWasDeleted")); + mScriptEd->setItemRemoved(true); + } + else if (mDirty) + { + std::string item_path = get_category_path(item->getParentUUID()); + getChild("path_txt")->setValue(item_path); + getChild("path_txt")->setToolTip(item_path); + } + LLPreview::draw(); +} +// virtual +void LLPreviewLSL::callbackLSLCompileSucceeded() +{ + LL_INFOS() << "LSL Bytecode saved" << LL_ENDL; + mScriptEd->mErrorList->setCommentText(LLTrans::getString("CompileSuccessful")); + mScriptEd->mErrorList->setCommentText(LLTrans::getString("SaveComplete")); + closeIfNeeded(); +} + +// virtual +void LLPreviewLSL::callbackLSLCompileFailed(const LLSD& compile_errors) +{ + LL_INFOS() << "Compile failed!" << LL_ENDL; + + for(LLSD::array_const_iterator line = compile_errors.beginArray(); + line < compile_errors.endArray(); + line++) + { + LLSD row; + std::string error_message = line->asString(); + LLStringUtil::stripNonprintable(error_message); + row["columns"][0]["value"] = error_message; + row["columns"][0]["font"] = "OCRA"; + mScriptEd->mErrorList->addElement(row); + } + mScriptEd->selectFirstError(); + closeIfNeeded(); +} + +void LLPreviewLSL::loadAsset() +{ + // *HACK: we poke into inventory to see if it's there, and if so, + // then it might be part of the inventory library. If it's in the + // library, then you can see the script, but not modify it. + const LLInventoryItem* item = gInventory.getItem(mItemUUID); + bool is_library = item + && !gInventory.isObjectDescendentOf(mItemUUID, + gInventory.getRootFolderID()); + if(!item) + { + // do the more generic search. + getItem(); + } + if(item) + { + bool is_copyable = gAgent.allowOperation(PERM_COPY, + item->getPermissions(), GP_OBJECT_MANIPULATE); + bool is_modifiable = gAgent.allowOperation(PERM_MODIFY, + item->getPermissions(), GP_OBJECT_MANIPULATE); + if (gAgent.isGodlike() || (is_copyable && (is_modifiable || is_library))) + { + LLUUID* new_uuid = new LLUUID(mItemUUID); + gAssetStorage->getInvItemAsset(LLHost(), + gAgent.getID(), + gAgent.getSessionID(), + item->getPermissions().getOwner(), + LLUUID::null, + item->getUUID(), + item->getAssetUUID(), + item->getType(), + &LLPreviewLSL::onLoadComplete, + (void*)new_uuid, + true); + mAssetStatus = PREVIEW_ASSET_LOADING; + } + else + { + mScriptEd->setScriptText(mScriptEd->getString("can_not_view"), false); + mScriptEd->mEditor->makePristine(); + mScriptEd->mFunctions->setEnabled(false); + mAssetStatus = PREVIEW_ASSET_LOADED; + } + getChildView("lock")->setVisible( !is_modifiable); + mScriptEd->getChildView("Insert...")->setEnabled(is_modifiable); + } + else + { + mScriptEd->setScriptText(std::string(HELLO_LSL), true); + mScriptEd->setEnableEditing(true); + mAssetStatus = PREVIEW_ASSET_LOADED; + } +} + + +bool LLPreviewLSL::canClose() +{ + return mScriptEd->canClose(); +} + +void LLPreviewLSL::closeIfNeeded() +{ + // Find our window and close it if requested. + getWindow()->decBusyCount(); + mPendingUploads--; + if (mPendingUploads <= 0 && mCloseAfterSave) + { + closeFloater(); + } +} + +void LLPreviewLSL::onSearchReplace(void* userdata) +{ + LLPreviewLSL* self = (LLPreviewLSL*)userdata; + LLScriptEdCore* sec = self->mScriptEd; + LLFloaterScriptSearch::show(sec); +} + +// static +void LLPreviewLSL::onLoad(void* userdata) +{ + LLPreviewLSL* self = (LLPreviewLSL*)userdata; + self->loadAsset(); +} + +// static +void LLPreviewLSL::onSave(void* userdata, bool close_after_save) +{ + LLPreviewLSL* self = (LLPreviewLSL*)userdata; + self->mCloseAfterSave = close_after_save; + self->saveIfNeeded(); +} + +/*static*/ +void LLPreviewLSL::finishedLSLUpload(LLUUID itemId, LLSD response) +{ + // Find our window and close it if requested. + LLPreviewLSL* preview = LLFloaterReg::findTypedInstance("preview_script", LLSD(itemId)); + if (preview) + { + // Bytecode save completed + if (response["compiled"]) + { + preview->callbackLSLCompileSucceeded(); + } + else + { + preview->callbackLSLCompileFailed(response["errors"]); + } + } +} + +bool LLPreviewLSL::failedLSLUpload(LLUUID itemId, LLUUID taskId, LLSD response, std::string reason) +{ + LLSD floater_key; + if (taskId.notNull()) + { + floater_key["taskid"] = taskId; + floater_key["itemid"] = itemId; + } + else + { + floater_key = LLSD(itemId); + } + + LLPreviewLSL* preview = LLFloaterReg::findTypedInstance("preview_script", floater_key); + if (preview) + { + // unfreeze floater + LLSD errors; + errors.append(LLTrans::getString("UploadFailed") + reason); + preview->callbackLSLCompileFailed(errors); + return true; + } + + return false; +} + +// Save needs to compile the text in the buffer. If the compile +// succeeds, then save both assets out to the database. If the compile +// fails, go ahead and save the text anyway. +void LLPreviewLSL::saveIfNeeded(bool sync /*= true*/) +{ + if (!mScriptEd->hasChanged()) + { + return; + } + + mPendingUploads = 0; + mScriptEd->mErrorList->deleteAllItems(); + mScriptEd->mEditor->makePristine(); + + if (sync) + { + mScriptEd->sync(); + } + + if (!gAgent.getRegion()) return; + const LLInventoryItem *inv_item = getItem(); + // save it out to asset server + std::string url = gAgent.getRegion()->getCapability("UpdateScriptAgent"); + if(inv_item) + { + getWindow()->incBusyCount(); + mPendingUploads++; + if (!url.empty()) + { + std::string buffer(mScriptEd->mEditor->getText()); + + LLUUID old_asset_id = inv_item->getAssetUUID().isNull() ? mScriptEd->getAssetID() : inv_item->getAssetUUID(); + + LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared(mItemUUID, buffer, + [old_asset_id](LLUUID itemId, LLUUID, LLUUID, LLSD response) { + LLFileSystem::removeFile(old_asset_id, LLAssetType::AT_LSL_TEXT); + LLPreviewLSL::finishedLSLUpload(itemId, response); + }, + LLPreviewLSL::failedLSLUpload)); + + LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo); + } + } +} + +// static +void LLPreviewLSL::onLoadComplete(const LLUUID& asset_uuid, LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status) +{ + LL_DEBUGS() << "LLPreviewLSL::onLoadComplete: got uuid " << asset_uuid + << LL_ENDL; + LLUUID* item_uuid = (LLUUID*)user_data; + LLPreviewLSL* preview = LLFloaterReg::findTypedInstance("preview_script", *item_uuid); + if( preview ) + { + if(0 == status) + { + LLFileSystem file(asset_uuid, type); + S32 file_length = file.getSize(); + + std::vector buffer(file_length+1); + file.read((U8*)&buffer[0], file_length); + + // put a EOS at the end + buffer[file_length] = 0; + preview->mScriptEd->setScriptText(LLStringExplicit(&buffer[0]), true); + preview->mScriptEd->mEditor->makePristine(); + + std::string script_name = DEFAULT_SCRIPT_NAME; + LLInventoryItem* item = gInventory.getItem(*item_uuid); + bool is_modifiable = false; + if (item) + { + if (!item->getName().empty()) + { + script_name = item->getName(); + } + if (gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), GP_OBJECT_MANIPULATE)) + { + is_modifiable = true; + } + } + preview->mScriptEd->setScriptName(script_name); + preview->mScriptEd->setEnableEditing(is_modifiable); + preview->mScriptEd->setAssetID(asset_uuid); + preview->mAssetStatus = PREVIEW_ASSET_LOADED; + } + else + { + if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status || + LL_ERR_FILE_EMPTY == status) + { + LLNotificationsUtil::add("ScriptMissing"); + } + else if (LL_ERR_INSUFFICIENT_PERMISSIONS == status) + { + LLNotificationsUtil::add("ScriptNoPermissions"); + } + else + { + LLNotificationsUtil::add("UnableToLoadScript"); + } + + preview->mAssetStatus = PREVIEW_ASSET_ERROR; + LL_WARNS() << "Problem loading script: " << status << LL_ENDL; + } + } + delete item_uuid; +} + + +/// --------------------------------------------------------------------------- +/// LLLiveLSLEditor +/// --------------------------------------------------------------------------- + + +//static +void* LLLiveLSLEditor::createScriptEdPanel(void* userdata) +{ + LLLiveLSLEditor *self = (LLLiveLSLEditor*)userdata; + + self->mScriptEd = new LLScriptEdCore( + self, + HELLO_LSL, + self->getHandle(), + &LLLiveLSLEditor::onLoad, + &LLLiveLSLEditor::onSave, + &LLLiveLSLEditor::onSearchReplace, + self, + true, + 0); + return self->mScriptEd; +} + + +LLLiveLSLEditor::LLLiveLSLEditor(const LLSD& key) : + LLScriptEdContainer(key), + mAskedForRunningInfo(false), + mHaveRunningInfo(false), + mCloseAfterSave(false), + mPendingUploads(0), + mIsModifiable(false), + mIsNew(false), + mIsSaving(false), + mObjectName("") +{ + mFactoryMap["script ed panel"] = LLCallbackMap(LLLiveLSLEditor::createScriptEdPanel, this); +} + +bool LLLiveLSLEditor::postBuild() +{ + childSetCommitCallback("running", LLLiveLSLEditor::onRunningCheckboxClicked, this); + getChildView("running")->setEnabled(false); + + childSetAction("Reset",&LLLiveLSLEditor::onReset,this); + getChildView("Reset")->setEnabled(true); + + mMonoCheckbox = getChild("mono"); + childSetCommitCallback("mono", &LLLiveLSLEditor::onMonoCheckboxClicked, this); + getChildView("mono")->setEnabled(true); + + mScriptEd->mEditor->makePristine(); + mScriptEd->mEditor->setFocus(true); + + + mExperiences = getChild("Experiences..."); + mExperiences->setCommitCallback(boost::bind(&LLLiveLSLEditor::experienceChanged, this)); + + mExperienceEnabled = getChild("enable_xp"); + + childSetCommitCallback("enable_xp", onToggleExperience, this); + childSetCommitCallback("view_profile", onViewProfile, this); + + + return LLPreview::postBuild(); +} + +// virtual +void LLLiveLSLEditor::callbackLSLCompileSucceeded(const LLUUID& task_id, + const LLUUID& item_id, + bool is_script_running) +{ + LL_DEBUGS() << "LSL Bytecode saved" << LL_ENDL; + mScriptEd->mErrorList->setCommentText(LLTrans::getString("CompileSuccessful")); + mScriptEd->mErrorList->setCommentText(LLTrans::getString("SaveComplete")); + getChild("running")->set(is_script_running); + mIsSaving = false; + closeIfNeeded(); +} + +// virtual +void LLLiveLSLEditor::callbackLSLCompileFailed(const LLSD& compile_errors) +{ + LL_DEBUGS() << "Compile failed!" << LL_ENDL; + for(LLSD::array_const_iterator line = compile_errors.beginArray(); + line < compile_errors.endArray(); + line++) + { + LLSD row; + std::string error_message = line->asString(); + LLStringUtil::stripNonprintable(error_message); + row["columns"][0]["value"] = error_message; + // *TODO: change to "MONOSPACE" and change llfontgl.cpp? + row["columns"][0]["font"] = "OCRA"; + mScriptEd->mErrorList->addElement(row); + } + mScriptEd->selectFirstError(); + mIsSaving = false; + closeIfNeeded(); +} + +void LLLiveLSLEditor::loadAsset() +{ + //LL_INFOS() << "LLLiveLSLEditor::loadAsset()" << LL_ENDL; + if(!mIsNew) + { + LLViewerObject* object = gObjectList.findObject(mObjectUUID); + if(object) + { + LLViewerInventoryItem* item = dynamic_cast(object->getInventoryObject(mItemUUID)); + + if(item) + { + LLViewerRegion* region = object->getRegion(); + std::string url = std::string(); + if(region) + { + url = region->getCapability("GetMetadata"); + } + LLExperienceCache::instance().fetchAssociatedExperience(item->getParentUUID(), item->getUUID(), url, + boost::bind(&LLLiveLSLEditor::setAssociatedExperience, getDerivedHandle(), _1)); + + bool isGodlike = gAgent.isGodlike(); + bool copyManipulate = gAgent.allowOperation(PERM_COPY, item->getPermissions(), GP_OBJECT_MANIPULATE); + mIsModifiable = gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), GP_OBJECT_MANIPULATE); + + if(!isGodlike && (!copyManipulate || !mIsModifiable)) + { + mItem = new LLViewerInventoryItem(item); + mScriptEd->setScriptText(getString("not_allowed"), false); + mScriptEd->mEditor->makePristine(); + mScriptEd->enableSave(false); + mAssetStatus = PREVIEW_ASSET_LOADED; + } + else if(copyManipulate || isGodlike) + { + mItem = new LLViewerInventoryItem(item); + // request the text from the object + LLSD* user_data = new LLSD(); + user_data->with("taskid", mObjectUUID).with("itemid", mItemUUID); + gAssetStorage->getInvItemAsset(object->getRegion()->getHost(), + gAgent.getID(), + gAgent.getSessionID(), + item->getPermissions().getOwner(), + object->getID(), + item->getUUID(), + item->getAssetUUID(), + item->getType(), + &LLLiveLSLEditor::onLoadComplete, + (void*)user_data, + true); + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_GetScriptRunning); + msg->nextBlockFast(_PREHASH_Script); + msg->addUUIDFast(_PREHASH_ObjectID, mObjectUUID); + msg->addUUIDFast(_PREHASH_ItemID, mItemUUID); + msg->sendReliable(object->getRegion()->getHost()); + mAskedForRunningInfo = true; + mAssetStatus = PREVIEW_ASSET_LOADING; + } + } + + if(mItem.isNull()) + { + mScriptEd->setScriptText(LLStringUtil::null, false); + mScriptEd->mEditor->makePristine(); + mAssetStatus = PREVIEW_ASSET_LOADED; + mIsModifiable = false; + } + + refreshFromItem(); + getChild("obj_name")->setValue(mObjectName); + // This is commented out, because we don't completely + // handle script exports yet. + /* + // request the exports from the object + gMessageSystem->newMessage("GetScriptExports"); + gMessageSystem->nextBlock("ScriptBlock"); + gMessageSystem->addUUID("AgentID", gAgent.getID()); + U32 local_id = object->getLocalID(); + gMessageSystem->addData("LocalID", &local_id); + gMessageSystem->addUUID("ItemID", mItemUUID); + LLHost host(object->getRegion()->getIP(), + object->getRegion()->getPort()); + gMessageSystem->sendReliable(host); + */ + } + } + else + { + mScriptEd->setScriptText(std::string(HELLO_LSL), true); + mScriptEd->enableSave(false); + LLPermissions perm; + perm.init(gAgent.getID(), gAgent.getID(), LLUUID::null, gAgent.getGroupID()); + perm.initMasks(PERM_ALL, PERM_ALL, PERM_NONE, PERM_NONE, PERM_MOVE | PERM_TRANSFER); + mItem = new LLViewerInventoryItem(mItemUUID, + mObjectUUID, + perm, + LLUUID::null, + LLAssetType::AT_LSL_TEXT, + LLInventoryType::IT_LSL, + DEFAULT_SCRIPT_NAME, + DEFAULT_SCRIPT_DESC, + LLSaleInfo::DEFAULT, + LLInventoryItemFlags::II_FLAGS_NONE, + time_corrected()); + mAssetStatus = PREVIEW_ASSET_LOADED; + } + + requestExperiences(); +} + +// static +void LLLiveLSLEditor::onLoadComplete(const LLUUID& asset_id, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status) +{ + LL_DEBUGS() << "LLLiveLSLEditor::onLoadComplete: got uuid " << asset_id + << LL_ENDL; + LLSD* floater_key = (LLSD*)user_data; + + LLLiveLSLEditor* instance = LLFloaterReg::findTypedInstance("preview_scriptedit", *floater_key); + + if(instance ) + { + if( LL_ERR_NOERR == status ) + { + instance->loadScriptText(asset_id, type); + instance->mScriptEd->setEnableEditing(true); + instance->mAssetStatus = PREVIEW_ASSET_LOADED; + instance->mScriptEd->setAssetID(asset_id); + } + else + { + if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status || + LL_ERR_FILE_EMPTY == status) + { + LLNotificationsUtil::add("ScriptMissing"); + } + else if (LL_ERR_INSUFFICIENT_PERMISSIONS == status) + { + LLNotificationsUtil::add("ScriptNoPermissions"); + } + else + { + LLNotificationsUtil::add("UnableToLoadScript"); + } + instance->mAssetStatus = PREVIEW_ASSET_ERROR; + } + } + + delete floater_key; +} + +void LLLiveLSLEditor::loadScriptText(const LLUUID &uuid, LLAssetType::EType type) +{ + LLFileSystem file(uuid, type); + S32 file_length = file.getSize(); + std::vector buffer(file_length + 1); + file.read((U8*)&buffer[0], file_length); + + if (file.getLastBytesRead() != file_length || + file_length <= 0) + { + LL_WARNS() << "Error reading " << uuid << ":" << type << LL_ENDL; + } + + buffer[file_length] = '\0'; + + mScriptEd->setScriptText(LLStringExplicit(&buffer[0]), true); + mScriptEd->makeEditorPristine(); + + std::string script_name = DEFAULT_SCRIPT_NAME; + const LLInventoryItem* inv_item = getItem(); + + if(inv_item) + { + script_name = inv_item->getName(); + } + mScriptEd->setScriptName(script_name); +} + + +void LLLiveLSLEditor::onRunningCheckboxClicked( LLUICtrl*, void* userdata ) +{ + LLLiveLSLEditor* self = (LLLiveLSLEditor*) userdata; + LLViewerObject* object = gObjectList.findObject( self->mObjectUUID ); + LLCheckBoxCtrl* runningCheckbox = self->getChild("running"); + bool running = runningCheckbox->get(); + //self->mRunningCheckbox->get(); + if( object ) + { + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_SetScriptRunning); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_Script); + msg->addUUIDFast(_PREHASH_ObjectID, self->mObjectUUID); + msg->addUUIDFast(_PREHASH_ItemID, self->mItemUUID); + msg->addBOOLFast(_PREHASH_Running, running); + msg->sendReliable(object->getRegion()->getHost()); + } + else + { + runningCheckbox->set(!running); + LLNotificationsUtil::add("CouldNotStartStopScript"); + } +} + +void LLLiveLSLEditor::onReset(void *userdata) +{ + LLLiveLSLEditor* self = (LLLiveLSLEditor*) userdata; + + LLViewerObject* object = gObjectList.findObject( self->mObjectUUID ); + if(object) + { + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_ScriptReset); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_Script); + msg->addUUIDFast(_PREHASH_ObjectID, self->mObjectUUID); + msg->addUUIDFast(_PREHASH_ItemID, self->mItemUUID); + msg->sendReliable(object->getRegion()->getHost()); + } + else + { + LLNotificationsUtil::add("CouldNotStartStopScript"); + } +} + +void LLLiveLSLEditor::draw() +{ + LLViewerObject* object = gObjectList.findObject(mObjectUUID); + LLCheckBoxCtrl* runningCheckbox = getChild( "running"); + if(object && mAskedForRunningInfo && mHaveRunningInfo) + { + if(object->permAnyOwner()) + { + runningCheckbox->setLabel(getString("script_running")); + runningCheckbox->setEnabled(!mIsSaving); + } + else + { + runningCheckbox->setLabel(getString("public_objects_can_not_run")); + runningCheckbox->setEnabled(false); + + // *FIX: Set it to false so that the ui is correct for + // a box that is released to public. It could be + // incorrect after a release/claim cycle, but will be + // correct after clicking on it. + runningCheckbox->set(false); + mMonoCheckbox->set(false); + } + } + else if(!object) + { + // HACK: Display this information in the title bar. + // Really ought to put in main window. + setTitle(LLTrans::getString("ObjectOutOfRange")); + runningCheckbox->setEnabled(false); + mMonoCheckbox->setEnabled(false); + // object may have fallen out of range. + mHaveRunningInfo = false; + } + + LLPreview::draw(); +} + + +void LLLiveLSLEditor::onSearchReplace(void* userdata) +{ + LLLiveLSLEditor* self = (LLLiveLSLEditor*)userdata; + + LLScriptEdCore* sec = self->mScriptEd; + LLFloaterScriptSearch::show(sec); +} + +struct LLLiveLSLSaveData +{ + LLLiveLSLSaveData(const LLUUID& id, const LLViewerInventoryItem* item, bool active); + LLUUID mSaveObjectID; + LLPointer mItem; + bool mActive; +}; + +LLLiveLSLSaveData::LLLiveLSLSaveData(const LLUUID& id, + const LLViewerInventoryItem* item, + bool active) : + mSaveObjectID(id), + mActive(active) +{ + llassert(item); + mItem = new LLViewerInventoryItem(item); +} + +/*static*/ +void LLLiveLSLEditor::finishLSLUpload(LLUUID itemId, LLUUID taskId, LLUUID newAssetId, LLSD response, bool isRunning) +{ + LLSD floater_key; + floater_key["taskid"] = taskId; + floater_key["itemid"] = itemId; + + LLLiveLSLEditor* preview = LLFloaterReg::findTypedInstance("preview_scriptedit", floater_key); + if (preview) + { + preview->mItem->setAssetUUID(newAssetId); + preview->mScriptEd->setAssetID(newAssetId); + + // Bytecode save completed + if (response["compiled"]) + { + preview->callbackLSLCompileSucceeded(taskId, itemId, isRunning); + } + else + { + preview->callbackLSLCompileFailed(response["errors"]); + } + } +} + +// virtual +void LLLiveLSLEditor::saveIfNeeded(bool sync /*= true*/) +{ + LLViewerObject* object = gObjectList.findObject(mObjectUUID); + if (!object) + { + LLNotificationsUtil::add("SaveScriptFailObjectNotFound"); + return; + } + + if (mItem.isNull() || !mItem->isFinished()) + { + // $NOTE: While the error message may not be exactly correct, + // it's pretty close. + LLNotificationsUtil::add("SaveScriptFailObjectNotFound"); + return; + } + + // get the latest info about it. We used to be losing the script + // name on save, because the viewer object version of the item, + // and the editor version would get out of synch. Here's a good + // place to synch them back up. + LLInventoryItem* inv_item = dynamic_cast(object->getInventoryObject(mItemUUID)); + if (inv_item) + { + mItem->copyItem(inv_item); + } + + // Don't need to save if we're pristine + if (!mScriptEd->hasChanged()) + { + return; + } + + mPendingUploads = 0; + + // save the script + mScriptEd->enableSave(false); + mScriptEd->mEditor->makePristine(); + mScriptEd->mErrorList->deleteAllItems(); + mScriptEd->mEditor->makePristine(); + + if (sync) + { + mScriptEd->sync(); + } + + bool isRunning = getChild("running")->get(); + getWindow()->incBusyCount(); + mPendingUploads++; + + std::string url = object->getRegion()->getCapability("UpdateScriptTask"); + + if (!url.empty()) + { + std::string buffer(mScriptEd->mEditor->getText()); + LLUUID old_asset_id = mScriptEd->getAssetID(); + + LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared(mObjectUUID, mItemUUID, + monoChecked() ? LLScriptAssetUpload::MONO : LLScriptAssetUpload::LSL2, + isRunning, mScriptEd->getAssociatedExperience(), buffer, + [isRunning, old_asset_id](LLUUID itemId, LLUUID taskId, LLUUID newAssetId, LLSD response) { + LLFileSystem::removeFile(old_asset_id, LLAssetType::AT_LSL_TEXT); + LLLiveLSLEditor::finishLSLUpload(itemId, taskId, newAssetId, response, isRunning); + }, + nullptr)); // needs failure handling? + + LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo); + } +} + +bool LLLiveLSLEditor::canClose() +{ + return mScriptEd->canClose(); +} + +void LLLiveLSLEditor::closeIfNeeded() +{ + getWindow()->decBusyCount(); + mPendingUploads--; + if ((mPendingUploads <= 0) && mCloseAfterSave) + { + closeFloater(); + } +} + +// static +void LLLiveLSLEditor::onLoad(void* userdata) +{ + LLLiveLSLEditor* self = (LLLiveLSLEditor*)userdata; + self->loadAsset(); +} + +// static +void LLLiveLSLEditor::onSave(void* userdata, bool close_after_save) +{ + if (LLLiveLSLEditor* self = (LLLiveLSLEditor*)userdata) + { + self->mCloseAfterSave = close_after_save; + self->mScriptEd->mErrorList->setCommentText(""); + self->saveIfNeeded(); + } +} + +// static +void LLLiveLSLEditor::processScriptRunningReply(LLMessageSystem* msg, void**) +{ + LLUUID item_id; + LLUUID object_id; + msg->getUUIDFast(_PREHASH_Script, _PREHASH_ObjectID, object_id); + msg->getUUIDFast(_PREHASH_Script, _PREHASH_ItemID, item_id); + + LLSD floater_key; + floater_key["taskid"] = object_id; + floater_key["itemid"] = item_id; + if (LLLiveLSLEditor* instance = LLFloaterReg::findTypedInstance("preview_scriptedit", floater_key)) + { + instance->mHaveRunningInfo = true; + bool running; + msg->getBOOLFast(_PREHASH_Script, _PREHASH_Running, running); + LLCheckBoxCtrl* runningCheckbox = instance->getChild("running"); + runningCheckbox->set(running); + bool mono; + msg->getBOOLFast(_PREHASH_Script, "Mono", mono); + LLCheckBoxCtrl* monoCheckbox = instance->getChild("mono"); + monoCheckbox->setEnabled(instance->getIsModifiable() && have_script_upload_cap(object_id)); + monoCheckbox->set(mono); + } +} + +void LLLiveLSLEditor::onMonoCheckboxClicked(LLUICtrl*, void* userdata) +{ + LLLiveLSLEditor* self = static_cast(userdata); + self->mMonoCheckbox->setEnabled(have_script_upload_cap(self->mObjectUUID)); + self->mScriptEd->enableSave(self->getIsModifiable()); +} + +bool LLLiveLSLEditor::monoChecked() const +{ + return mMonoCheckbox && mMonoCheckbox->getValue(); +} + +void LLLiveLSLEditor::setAssociatedExperience( LLHandle editor, const LLSD& experience ) +{ + if (LLLiveLSLEditor* scriptEd = editor.get()) + { + LLUUID id; + if (experience.has(LLExperienceCache::EXPERIENCE_ID)) + { + id=experience[LLExperienceCache::EXPERIENCE_ID].asUUID(); + } + scriptEd->mScriptEd->setAssociatedExperience(id); + scriptEd->updateExperiencePanel(); + } +} diff --git a/indra/newview/llpreviewscript.h b/indra/newview/llpreviewscript.h index 84933bcedf..9a3bc35f12 100644 --- a/indra/newview/llpreviewscript.h +++ b/indra/newview/llpreviewscript.h @@ -1,359 +1,359 @@ -/** - * @file llpreviewscript.h - * @brief LLPreviewScript class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPREVIEWSCRIPT_H -#define LL_LLPREVIEWSCRIPT_H - -#include "llpreview.h" -#include "lltabcontainer.h" -#include "llinventory.h" -#include "llcombobox.h" -#include "lliconctrl.h" -#include "llframetimer.h" -#include "llfloatergotoline.h" -#include "lllivefile.h" -#include "llsyntaxid.h" -#include "llscripteditor.h" - -class LLLiveLSLFile; -class LLMessageSystem; -class LLScriptEditor; -class LLButton; -class LLCheckBoxCtrl; -class LLScrollListCtrl; -class LLViewerObject; -struct LLEntryAndEdCore; -class LLMenuBarGL; -class LLFloaterScriptSearch; -class LLKeywordToken; -class LLViewerInventoryItem; -class LLScriptEdContainer; -class LLFloaterGotoLine; -class LLFloaterExperienceProfile; -class LLScriptMovedObserver; - -class LLLiveLSLFile : public LLLiveFile -{ -public: - typedef boost::function change_callback_t; - - LLLiveLSLFile(std::string file_path, change_callback_t change_cb); - ~LLLiveLSLFile(); - - void ignoreNextUpdate() { mIgnoreNextUpdate = true; } - -protected: - /*virtual*/ bool loadFile(); - - change_callback_t mOnChangeCallback; - bool mIgnoreNextUpdate; -}; - -// Inner, implementation class. LLPreviewScript and LLLiveLSLEditor each own one of these. -class LLScriptEdCore : public LLPanel -{ - friend class LLPreviewScript; - friend class LLPreviewLSL; - friend class LLLiveLSLEditor; - friend class LLFloaterScriptSearch; - friend class LLScriptEdContainer; - friend class LLFloaterGotoLine; - -protected: - // Supposed to be invoked only by the container. - LLScriptEdCore( - LLScriptEdContainer* container, - const std::string& sample, - const LLHandle& floater_handle, - void (*load_callback)(void* userdata), - void (*save_callback)(void* userdata, bool close_after_save), - void (*search_replace_callback)(void* userdata), - void* userdata, - bool live, - S32 bottom_pad = 0); // pad below bottom row of buttons -public: - ~LLScriptEdCore(); - - void initMenu(); - void processKeywords(); - - virtual void draw(); - /*virtual*/ bool postBuild(); - bool canClose(); - void setEnableEditing(bool enable); - bool canLoadOrSaveToFile( void* userdata ); - - void setScriptText(const std::string& text, bool is_valid); - void makeEditorPristine(); - bool loadScriptText(const std::string& filename); - bool writeToFile(const std::string& filename); - void sync(); - - void doSave( bool close_after_save ); - - bool handleSaveChangesDialog(const LLSD& notification, const LLSD& response); - bool handleReloadFromServerDialog(const LLSD& notification, const LLSD& response); - - void openInExternalEditor(); - - static void onCheckLock(LLUICtrl*, void*); - static void onHelpComboCommit(LLUICtrl* ctrl, void* userdata); - static void onClickBack(void* userdata); - static void onClickForward(void* userdata); - static void onBtnInsertSample(void*); - static void onBtnInsertFunction(LLUICtrl*, void*); - static void onBtnLoadFromFile(void*); - static void onBtnSaveToFile(void*); - - static void loadScriptFromFile(const std::vector& filenames, void* data); - static void saveScriptToFile(const std::vector& filenames, void* data); - - static bool enableSaveToFileMenu(void* userdata); - static bool enableLoadFromFileMenu(void* userdata); - - virtual bool hasAccelerators() const { return true; } - LLUUID getAssociatedExperience()const; - void setAssociatedExperience( const LLUUID& experience_id ); - - void setScriptName(const std::string& name){mScriptName = name;}; - - void setItemRemoved(bool script_removed){mScriptRemoved = script_removed;}; - - void setAssetID( const LLUUID& asset_id){ mAssetID = asset_id; }; - LLUUID getAssetID() { return mAssetID; } - - bool isFontSizeChecked(const LLSD &userdata); - void onChangeFontSize(const LLSD &size_name); - - virtual bool handleKeyHere(KEY key, MASK mask); - void selectAll() { mEditor->selectAll(); } - - private: - void onBtnDynamicHelp(); - void onBtnUndoChanges(); - - bool hasChanged(); - - void selectFirstError(); - - void enableSave(bool b) {mEnableSave = b;} - -protected: - void deleteBridges(); - void setHelpPage(const std::string& help_string); - void updateDynamicHelp(bool immediate = false); - bool isKeyword(LLKeywordToken* token); - void addHelpItemToHistory(const std::string& help_string); - static void onErrorList(LLUICtrl*, void* user_data); - - bool mLive; - -private: - std::string mSampleText; - std::string mScriptName; - LLScriptEditor* mEditor; - void (*mLoadCallback)(void* userdata); - void (*mSaveCallback)(void* userdata, bool close_after_save); - void (*mSearchReplaceCallback) (void* userdata); - void* mUserdata; - LLComboBox *mFunctions; - bool mForceClose; - LLPanel* mCodePanel; - LLScrollListCtrl* mErrorList; - std::vector mBridges; - LLHandle mLiveHelpHandle; - LLKeywordToken* mLastHelpToken; - LLFrameTimer mLiveHelpTimer; - S32 mLiveHelpHistorySize; - bool mEnableSave; - bool mHasScriptData; - LLLiveLSLFile* mLiveFile; - LLUUID mAssociatedExperience; - bool mScriptRemoved; - bool mSaveDialogShown; - LLUUID mAssetID; - - LLScriptEdContainer* mContainer; // parent view - -public: - boost::signals2::connection mSyntaxIDConnection; - -}; - -class LLScriptEdContainer : public LLPreview -{ - friend class LLScriptEdCore; - -public: - LLScriptEdContainer(const LLSD& key); - LLScriptEdContainer(const LLSD& key, const bool live); - - bool handleKeyHere(KEY key, MASK mask); - -protected: - std::string getTmpFileName(const std::string& script_name); - bool onExternalChange(const std::string& filename); - virtual void saveIfNeeded(bool sync = true) = 0; - - LLScriptEdCore* mScriptEd; -}; - -// Used to view and edit an LSL script from your inventory. -class LLPreviewLSL : public LLScriptEdContainer -{ -public: - LLPreviewLSL(const LLSD& key ); - ~LLPreviewLSL(); - - LLUUID getScriptID() { return mItemUUID; } - - void setDirty() { mDirty = true; } - - virtual void callbackLSLCompileSucceeded(); - virtual void callbackLSLCompileFailed(const LLSD& compile_errors); - - /*virtual*/ bool postBuild(); - -protected: - virtual void draw(); - virtual bool canClose(); - void closeIfNeeded(); - - virtual void loadAsset(); - /*virtual*/ void saveIfNeeded(bool sync = true); - - static void onSearchReplace(void* userdata); - static void onLoad(void* userdata); - static void onSave(void* userdata, bool close_after_save); - - static void onLoadComplete(const LLUUID& uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status); - -protected: - static void* createScriptEdPanel(void* userdata); - - static void finishedLSLUpload(LLUUID itemId, LLSD response); - static bool failedLSLUpload(LLUUID itemId, LLUUID taskId, LLSD response, std::string reason); -protected: - - // Can safely close only after both text and bytecode are uploaded - S32 mPendingUploads; - - LLScriptMovedObserver* mItemObserver; - -}; - - -// Used to view and edit an LSL script that is attached to an object. -class LLLiveLSLEditor : public LLScriptEdContainer -{ - friend class LLLiveLSLFile; -public: - LLLiveLSLEditor(const LLSD& key); - - - static void processScriptRunningReply(LLMessageSystem* msg, void**); - - virtual void callbackLSLCompileSucceeded(const LLUUID& task_id, - const LLUUID& item_id, - bool is_script_running); - virtual void callbackLSLCompileFailed(const LLSD& compile_errors); - - /*virtual*/ bool postBuild(); - - void setIsNew() { mIsNew = true; } - - static void setAssociatedExperience( LLHandle editor, const LLSD& experience ); - static void onToggleExperience(LLUICtrl *ui, void* userdata); - static void onViewProfile(LLUICtrl *ui, void* userdata); - - void setExperienceIds(const LLSD& experience_ids); - void buildExperienceList(); - void updateExperiencePanel(); - void requestExperiences(); - void experienceChanged(); - - void setObjectName(std::string name) { mObjectName = name; } - -private: - virtual bool canClose(); - void closeIfNeeded(); - virtual void draw(); - - virtual void loadAsset(); - /*virtual*/ void saveIfNeeded(bool sync = true); - bool monoChecked() const; - - - static void onSearchReplace(void* userdata); - static void onLoad(void* userdata); - static void onSave(void* userdata, bool close_after_save); - - static void onLoadComplete(const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status); - static void onRunningCheckboxClicked(LLUICtrl*, void* userdata); - static void onReset(void* userdata); - - void loadScriptText(const LLUUID &uuid, LLAssetType::EType type); - - static void* createScriptEdPanel(void* userdata); - - static void onMonoCheckboxClicked(LLUICtrl*, void* userdata); - - static void finishLSLUpload(LLUUID itemId, LLUUID taskId, LLUUID newAssetId, LLSD response, bool isRunning); - static void receiveExperienceIds(LLSD result, LLHandle parent); - -private: - bool mIsNew; - //LLUUID mTransmitID; - //LLCheckBoxCtrl* mRunningCheckbox; - bool mAskedForRunningInfo; - bool mHaveRunningInfo; - //LLButton* mResetButton; - LLPointer mItem; - bool mCloseAfterSave; - // need to save both text and script, so need to decide when done - S32 mPendingUploads; - - bool mIsSaving; - - bool getIsModifiable() const { return mIsModifiable; } // Evaluated on load assert - - LLCheckBoxCtrl* mMonoCheckbox; - bool mIsModifiable; - - - LLComboBox* mExperiences; - LLCheckBoxCtrl* mExperienceEnabled; - LLSD mExperienceIds; - - LLHandle mExperienceProfile; - std::string mObjectName; -}; - -#endif // LL_LLPREVIEWSCRIPT_H +/** + * @file llpreviewscript.h + * @brief LLPreviewScript class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPREVIEWSCRIPT_H +#define LL_LLPREVIEWSCRIPT_H + +#include "llpreview.h" +#include "lltabcontainer.h" +#include "llinventory.h" +#include "llcombobox.h" +#include "lliconctrl.h" +#include "llframetimer.h" +#include "llfloatergotoline.h" +#include "lllivefile.h" +#include "llsyntaxid.h" +#include "llscripteditor.h" + +class LLLiveLSLFile; +class LLMessageSystem; +class LLScriptEditor; +class LLButton; +class LLCheckBoxCtrl; +class LLScrollListCtrl; +class LLViewerObject; +struct LLEntryAndEdCore; +class LLMenuBarGL; +class LLFloaterScriptSearch; +class LLKeywordToken; +class LLViewerInventoryItem; +class LLScriptEdContainer; +class LLFloaterGotoLine; +class LLFloaterExperienceProfile; +class LLScriptMovedObserver; + +class LLLiveLSLFile : public LLLiveFile +{ +public: + typedef boost::function change_callback_t; + + LLLiveLSLFile(std::string file_path, change_callback_t change_cb); + ~LLLiveLSLFile(); + + void ignoreNextUpdate() { mIgnoreNextUpdate = true; } + +protected: + /*virtual*/ bool loadFile(); + + change_callback_t mOnChangeCallback; + bool mIgnoreNextUpdate; +}; + +// Inner, implementation class. LLPreviewScript and LLLiveLSLEditor each own one of these. +class LLScriptEdCore : public LLPanel +{ + friend class LLPreviewScript; + friend class LLPreviewLSL; + friend class LLLiveLSLEditor; + friend class LLFloaterScriptSearch; + friend class LLScriptEdContainer; + friend class LLFloaterGotoLine; + +protected: + // Supposed to be invoked only by the container. + LLScriptEdCore( + LLScriptEdContainer* container, + const std::string& sample, + const LLHandle& floater_handle, + void (*load_callback)(void* userdata), + void (*save_callback)(void* userdata, bool close_after_save), + void (*search_replace_callback)(void* userdata), + void* userdata, + bool live, + S32 bottom_pad = 0); // pad below bottom row of buttons +public: + ~LLScriptEdCore(); + + void initMenu(); + void processKeywords(); + + virtual void draw(); + /*virtual*/ bool postBuild(); + bool canClose(); + void setEnableEditing(bool enable); + bool canLoadOrSaveToFile( void* userdata ); + + void setScriptText(const std::string& text, bool is_valid); + void makeEditorPristine(); + bool loadScriptText(const std::string& filename); + bool writeToFile(const std::string& filename); + void sync(); + + void doSave( bool close_after_save ); + + bool handleSaveChangesDialog(const LLSD& notification, const LLSD& response); + bool handleReloadFromServerDialog(const LLSD& notification, const LLSD& response); + + void openInExternalEditor(); + + static void onCheckLock(LLUICtrl*, void*); + static void onHelpComboCommit(LLUICtrl* ctrl, void* userdata); + static void onClickBack(void* userdata); + static void onClickForward(void* userdata); + static void onBtnInsertSample(void*); + static void onBtnInsertFunction(LLUICtrl*, void*); + static void onBtnLoadFromFile(void*); + static void onBtnSaveToFile(void*); + + static void loadScriptFromFile(const std::vector& filenames, void* data); + static void saveScriptToFile(const std::vector& filenames, void* data); + + static bool enableSaveToFileMenu(void* userdata); + static bool enableLoadFromFileMenu(void* userdata); + + virtual bool hasAccelerators() const { return true; } + LLUUID getAssociatedExperience()const; + void setAssociatedExperience( const LLUUID& experience_id ); + + void setScriptName(const std::string& name){mScriptName = name;}; + + void setItemRemoved(bool script_removed){mScriptRemoved = script_removed;}; + + void setAssetID( const LLUUID& asset_id){ mAssetID = asset_id; }; + LLUUID getAssetID() { return mAssetID; } + + bool isFontSizeChecked(const LLSD &userdata); + void onChangeFontSize(const LLSD &size_name); + + virtual bool handleKeyHere(KEY key, MASK mask); + void selectAll() { mEditor->selectAll(); } + + private: + void onBtnDynamicHelp(); + void onBtnUndoChanges(); + + bool hasChanged(); + + void selectFirstError(); + + void enableSave(bool b) {mEnableSave = b;} + +protected: + void deleteBridges(); + void setHelpPage(const std::string& help_string); + void updateDynamicHelp(bool immediate = false); + bool isKeyword(LLKeywordToken* token); + void addHelpItemToHistory(const std::string& help_string); + static void onErrorList(LLUICtrl*, void* user_data); + + bool mLive; + +private: + std::string mSampleText; + std::string mScriptName; + LLScriptEditor* mEditor; + void (*mLoadCallback)(void* userdata); + void (*mSaveCallback)(void* userdata, bool close_after_save); + void (*mSearchReplaceCallback) (void* userdata); + void* mUserdata; + LLComboBox *mFunctions; + bool mForceClose; + LLPanel* mCodePanel; + LLScrollListCtrl* mErrorList; + std::vector mBridges; + LLHandle mLiveHelpHandle; + LLKeywordToken* mLastHelpToken; + LLFrameTimer mLiveHelpTimer; + S32 mLiveHelpHistorySize; + bool mEnableSave; + bool mHasScriptData; + LLLiveLSLFile* mLiveFile; + LLUUID mAssociatedExperience; + bool mScriptRemoved; + bool mSaveDialogShown; + LLUUID mAssetID; + + LLScriptEdContainer* mContainer; // parent view + +public: + boost::signals2::connection mSyntaxIDConnection; + +}; + +class LLScriptEdContainer : public LLPreview +{ + friend class LLScriptEdCore; + +public: + LLScriptEdContainer(const LLSD& key); + LLScriptEdContainer(const LLSD& key, const bool live); + + bool handleKeyHere(KEY key, MASK mask); + +protected: + std::string getTmpFileName(const std::string& script_name); + bool onExternalChange(const std::string& filename); + virtual void saveIfNeeded(bool sync = true) = 0; + + LLScriptEdCore* mScriptEd; +}; + +// Used to view and edit an LSL script from your inventory. +class LLPreviewLSL : public LLScriptEdContainer +{ +public: + LLPreviewLSL(const LLSD& key ); + ~LLPreviewLSL(); + + LLUUID getScriptID() { return mItemUUID; } + + void setDirty() { mDirty = true; } + + virtual void callbackLSLCompileSucceeded(); + virtual void callbackLSLCompileFailed(const LLSD& compile_errors); + + /*virtual*/ bool postBuild(); + +protected: + virtual void draw(); + virtual bool canClose(); + void closeIfNeeded(); + + virtual void loadAsset(); + /*virtual*/ void saveIfNeeded(bool sync = true); + + static void onSearchReplace(void* userdata); + static void onLoad(void* userdata); + static void onSave(void* userdata, bool close_after_save); + + static void onLoadComplete(const LLUUID& uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status); + +protected: + static void* createScriptEdPanel(void* userdata); + + static void finishedLSLUpload(LLUUID itemId, LLSD response); + static bool failedLSLUpload(LLUUID itemId, LLUUID taskId, LLSD response, std::string reason); +protected: + + // Can safely close only after both text and bytecode are uploaded + S32 mPendingUploads; + + LLScriptMovedObserver* mItemObserver; + +}; + + +// Used to view and edit an LSL script that is attached to an object. +class LLLiveLSLEditor : public LLScriptEdContainer +{ + friend class LLLiveLSLFile; +public: + LLLiveLSLEditor(const LLSD& key); + + + static void processScriptRunningReply(LLMessageSystem* msg, void**); + + virtual void callbackLSLCompileSucceeded(const LLUUID& task_id, + const LLUUID& item_id, + bool is_script_running); + virtual void callbackLSLCompileFailed(const LLSD& compile_errors); + + /*virtual*/ bool postBuild(); + + void setIsNew() { mIsNew = true; } + + static void setAssociatedExperience( LLHandle editor, const LLSD& experience ); + static void onToggleExperience(LLUICtrl *ui, void* userdata); + static void onViewProfile(LLUICtrl *ui, void* userdata); + + void setExperienceIds(const LLSD& experience_ids); + void buildExperienceList(); + void updateExperiencePanel(); + void requestExperiences(); + void experienceChanged(); + + void setObjectName(std::string name) { mObjectName = name; } + +private: + virtual bool canClose(); + void closeIfNeeded(); + virtual void draw(); + + virtual void loadAsset(); + /*virtual*/ void saveIfNeeded(bool sync = true); + bool monoChecked() const; + + + static void onSearchReplace(void* userdata); + static void onLoad(void* userdata); + static void onSave(void* userdata, bool close_after_save); + + static void onLoadComplete(const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status); + static void onRunningCheckboxClicked(LLUICtrl*, void* userdata); + static void onReset(void* userdata); + + void loadScriptText(const LLUUID &uuid, LLAssetType::EType type); + + static void* createScriptEdPanel(void* userdata); + + static void onMonoCheckboxClicked(LLUICtrl*, void* userdata); + + static void finishLSLUpload(LLUUID itemId, LLUUID taskId, LLUUID newAssetId, LLSD response, bool isRunning); + static void receiveExperienceIds(LLSD result, LLHandle parent); + +private: + bool mIsNew; + //LLUUID mTransmitID; + //LLCheckBoxCtrl* mRunningCheckbox; + bool mAskedForRunningInfo; + bool mHaveRunningInfo; + //LLButton* mResetButton; + LLPointer mItem; + bool mCloseAfterSave; + // need to save both text and script, so need to decide when done + S32 mPendingUploads; + + bool mIsSaving; + + bool getIsModifiable() const { return mIsModifiable; } // Evaluated on load assert + + LLCheckBoxCtrl* mMonoCheckbox; + bool mIsModifiable; + + + LLComboBox* mExperiences; + LLCheckBoxCtrl* mExperienceEnabled; + LLSD mExperienceIds; + + LLHandle mExperienceProfile; + std::string mObjectName; +}; + +#endif // LL_LLPREVIEWSCRIPT_H diff --git a/indra/newview/llpreviewsound.cpp b/indra/newview/llpreviewsound.cpp index 797b676e17..eab40429f9 100644 --- a/indra/newview/llpreviewsound.cpp +++ b/indra/newview/llpreviewsound.cpp @@ -1,100 +1,100 @@ -/** - * @file llpreviewsound.cpp - * @brief LLPreviewSound class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llaudioengine.h" -#include "llagent.h" // gAgent -#include "llbutton.h" -#include "llinventory.h" -#include "lllineeditor.h" -#include "llpreviewsound.h" -#include "llresmgr.h" -#include "llviewercontrol.h" -#include "llviewermessage.h" // send_guid_sound_trigger -#include "lluictrlfactory.h" - -extern LLAudioEngine* gAudiop; -extern LLAgent gAgent; - -const F32 SOUND_GAIN = 1.0f; - -LLPreviewSound::LLPreviewSound(const LLSD& key) - : LLPreview( key ) -{ -} - -// virtual -bool LLPreviewSound::postBuild() -{ - const LLInventoryItem* item = getItem(); - if (item) - { - getChild("desc")->setValue(item->getDescription()); - if (gAudiop) - { - gAudiop->preloadSound(item->getAssetUUID()); // preload the sound - } - } - - childSetAction("Sound play btn",&LLPreviewSound::playSound,this); - childSetAction("Sound audition btn",&LLPreviewSound::auditionSound,this); - - LLButton* button = getChild("Sound play btn"); - button->setSoundFlags(LLView::SILENT); - - button = getChild("Sound audition btn"); - button->setSoundFlags(LLView::SILENT); - - childSetCommitCallback("desc", LLPreview::onText, this); - getChild("desc")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); - - return LLPreview::postBuild(); -} - -// static -void LLPreviewSound::playSound( void *userdata ) -{ - LLPreviewSound* self = (LLPreviewSound*) userdata; - const LLInventoryItem *item = self->getItem(); - - if(item && gAudiop) - { - send_sound_trigger(item->getAssetUUID(), SOUND_GAIN); - } -} - -// static -void LLPreviewSound::auditionSound( void *userdata ) -{ - LLPreviewSound* self = (LLPreviewSound*) userdata; - const LLInventoryItem *item = self->getItem(); - - if(item && gAudiop) - { - gAudiop->triggerSound(item->getAssetUUID(), gAgent.getID(), SOUND_GAIN, LLAudioEngine::AUDIO_TYPE_SFX); - } -} +/** + * @file llpreviewsound.cpp + * @brief LLPreviewSound class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llaudioengine.h" +#include "llagent.h" // gAgent +#include "llbutton.h" +#include "llinventory.h" +#include "lllineeditor.h" +#include "llpreviewsound.h" +#include "llresmgr.h" +#include "llviewercontrol.h" +#include "llviewermessage.h" // send_guid_sound_trigger +#include "lluictrlfactory.h" + +extern LLAudioEngine* gAudiop; +extern LLAgent gAgent; + +const F32 SOUND_GAIN = 1.0f; + +LLPreviewSound::LLPreviewSound(const LLSD& key) + : LLPreview( key ) +{ +} + +// virtual +bool LLPreviewSound::postBuild() +{ + const LLInventoryItem* item = getItem(); + if (item) + { + getChild("desc")->setValue(item->getDescription()); + if (gAudiop) + { + gAudiop->preloadSound(item->getAssetUUID()); // preload the sound + } + } + + childSetAction("Sound play btn",&LLPreviewSound::playSound,this); + childSetAction("Sound audition btn",&LLPreviewSound::auditionSound,this); + + LLButton* button = getChild("Sound play btn"); + button->setSoundFlags(LLView::SILENT); + + button = getChild("Sound audition btn"); + button->setSoundFlags(LLView::SILENT); + + childSetCommitCallback("desc", LLPreview::onText, this); + getChild("desc")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); + + return LLPreview::postBuild(); +} + +// static +void LLPreviewSound::playSound( void *userdata ) +{ + LLPreviewSound* self = (LLPreviewSound*) userdata; + const LLInventoryItem *item = self->getItem(); + + if(item && gAudiop) + { + send_sound_trigger(item->getAssetUUID(), SOUND_GAIN); + } +} + +// static +void LLPreviewSound::auditionSound( void *userdata ) +{ + LLPreviewSound* self = (LLPreviewSound*) userdata; + const LLInventoryItem *item = self->getItem(); + + if(item && gAudiop) + { + gAudiop->triggerSound(item->getAssetUUID(), gAgent.getID(), SOUND_GAIN, LLAudioEngine::AUDIO_TYPE_SFX); + } +} diff --git a/indra/newview/llpreviewsound.h b/indra/newview/llpreviewsound.h index 6ecfc3977f..b456a7e854 100644 --- a/indra/newview/llpreviewsound.h +++ b/indra/newview/llpreviewsound.h @@ -1,44 +1,44 @@ -/** - * @file llpreviewsound.h - * @brief LLPreviewSound class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPREVIEWSOUND_H -#define LL_LLPREVIEWSOUND_H - -#include "llpreview.h" - -class LLPreviewSound : public LLPreview -{ -public: - LLPreviewSound(const LLSD& key); - - static void playSound( void* userdata ); - static void auditionSound( void* userdata ); - -protected: - bool postBuild() override; -}; - -#endif // LL_LLPREVIEWSOUND_H +/** + * @file llpreviewsound.h + * @brief LLPreviewSound class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPREVIEWSOUND_H +#define LL_LLPREVIEWSOUND_H + +#include "llpreview.h" + +class LLPreviewSound : public LLPreview +{ +public: + LLPreviewSound(const LLSD& key); + + static void playSound( void* userdata ); + static void auditionSound( void* userdata ); + +protected: + bool postBuild() override; +}; + +#endif // LL_LLPREVIEWSOUND_H diff --git a/indra/newview/llpreviewtexture.cpp b/indra/newview/llpreviewtexture.cpp index 50a3a37fcc..68454bcd60 100644 --- a/indra/newview/llpreviewtexture.cpp +++ b/indra/newview/llpreviewtexture.cpp @@ -1,743 +1,743 @@ -/** - * @file llpreviewtexture.cpp - * @brief LLPreviewTexture class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llwindow.h" - -#include "llpreviewtexture.h" - -#include "llagent.h" -#include "llbutton.h" -#include "llcombobox.h" -#include "llfilepicker.h" -#include "llfloaterreg.h" -#include "llimagetga.h" -#include "llimagepng.h" -#include "llinventory.h" -#include "llinventorymodel.h" -#include "llnotificationsutil.h" -#include "llresmgr.h" -#include "lltrans.h" -#include "lltextbox.h" -#include "lltextureview.h" -#include "llui.h" -#include "llviewerinventory.h" -#include "llviewermenufile.h" // LLFilePickerReplyThread -#include "llviewertexture.h" -#include "llviewertexturelist.h" -#include "lluictrlfactory.h" -#include "llviewercontrol.h" -#include "llviewerwindow.h" -#include "lllineeditor.h" - -#include - -const S32 CLIENT_RECT_VPAD = 4; - -const F32 SECONDS_TO_SHOW_FILE_SAVED_MSG = 8.f; - -const F32 PREVIEW_TEXTURE_MAX_ASPECT = 200.f; -const F32 PREVIEW_TEXTURE_MIN_ASPECT = 0.005f; - - -LLPreviewTexture::LLPreviewTexture(const LLSD& key) - : LLPreview(key), - mLoadingFullImage( false ), - mSavingMultiple(false), - mShowKeepDiscard(false), - mCopyToInv(false), - mIsCopyable(false), - mIsFullPerm(false), - mUpdateDimensions(true), - mLastHeight(0), - mLastWidth(0), - mAspectRatio(0.f), - mPreviewToSave(false), - mImage(NULL), - mImageOldBoostLevel(LLGLTexture::BOOST_NONE) -{ - updateImageID(); - if (key.has("save_as")) - { - mPreviewToSave = true; - } -} - -LLPreviewTexture::~LLPreviewTexture() -{ - LLLoadedCallbackEntry::cleanUpCallbackList(&mCallbackTextureList) ; - - if( mLoadingFullImage ) - { - getWindow()->decBusyCount(); - } - - if (mImage.notNull()) - { - mImage->setBoostLevel(mImageOldBoostLevel); - mImage = NULL; - } -} - -void LLPreviewTexture::populateRatioList() -{ - // Fill in ratios list with common aspect ratio values - mRatiosList.clear(); - mRatiosList.push_back(LLTrans::getString("Unconstrained")); - mRatiosList.push_back("1:1"); - mRatiosList.push_back("4:3"); - mRatiosList.push_back("10:7"); - mRatiosList.push_back("3:2"); - mRatiosList.push_back("16:10"); - mRatiosList.push_back("16:9"); - mRatiosList.push_back("2:1"); - - // Now fill combo box with provided list - LLComboBox* combo = getChild("combo_aspect_ratio"); - combo->removeall(); - - for (std::vector::const_iterator it = mRatiosList.begin(); it != mRatiosList.end(); ++it) - { - combo->add(*it); - } -} - -// virtual -bool LLPreviewTexture::postBuild() -{ - if (mCopyToInv) - { - getChild("Keep")->setLabel(getString("Copy")); - childSetAction("Keep",LLPreview::onBtnCopyToInv,this); - getChildView("Discard")->setVisible( false); - } - else if (mShowKeepDiscard) - { - childSetAction("Keep",onKeepBtn,this); - childSetAction("Discard",onDiscardBtn,this); - } - else - { - getChildView("Keep")->setVisible( false); - getChildView("Discard")->setVisible( false); - } - - childSetAction("save_tex_btn", LLPreviewTexture::onSaveAsBtn, this); - getChildView("save_tex_btn")->setVisible( true); - getChildView("save_tex_btn")->setEnabled(canSaveAs()); - - const LLInventoryItem* item = getItem(); - if (item) - { - if (!mCopyToInv) - { - childSetCommitCallback("desc", LLPreview::onText, this); - getChild("desc")->setValue(item->getDescription()); - getChild("desc")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); - } - bool source_library = mObjectUUID.isNull() && gInventory.isObjectDescendentOf(item->getUUID(), gInventory.getLibraryRootFolderID()); - if (source_library) - { - getChildView("Discard")->setEnabled(false); - } - } - - // Fill in ratios list and combo box with common aspect ratio values - populateRatioList(); - - childSetCommitCallback("combo_aspect_ratio", onAspectRatioCommit, this); - - LLComboBox* combo = getChild("combo_aspect_ratio"); - combo->setCurrentByIndex(0); - - return LLPreview::postBuild(); -} - -// static -void LLPreviewTexture::onSaveAsBtn(void* data) -{ - LLPreviewTexture* self = (LLPreviewTexture*)data; - self->saveAs(); -} - -void LLPreviewTexture::draw() -{ - updateDimensions(); - - LLPreview::draw(); - - if (!isMinimized()) - { - LLGLSUIDefault gls_ui; - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - const LLRect& border = mClientRect; - LLRect interior = mClientRect; - interior.stretch( -PREVIEW_BORDER_WIDTH ); - - // ...border - gl_rect_2d( border, LLColor4(0.f, 0.f, 0.f, 1.f)); - gl_rect_2d_checkerboard( interior ); - - if ( mImage.notNull() ) - { - // Draw the texture - gGL.diffuseColor3f( 1.f, 1.f, 1.f ); - gl_draw_scaled_image(interior.mLeft, - interior.mBottom, - interior.getWidth(), - interior.getHeight(), - mImage); - - // Pump the texture priority - F32 pixel_area = mLoadingFullImage ? (F32)MAX_IMAGE_AREA : (F32)(interior.getWidth() * interior.getHeight() ); - mImage->addTextureStats( pixel_area ); - - // Don't bother decoding more than we can display, unless - // we're loading the full image. - if (!mLoadingFullImage) - { - S32 int_width = interior.getWidth(); - S32 int_height = interior.getHeight(); - mImage->setKnownDrawSize(int_width, int_height); - } - else - { - // Don't use this feature - mImage->setKnownDrawSize(0, 0); - } - - if( mLoadingFullImage ) - { - LLFontGL::getFontSansSerif()->renderUTF8(LLTrans::getString("Receiving"), 0, - interior.mLeft + 4, - interior.mBottom + 4, - LLColor4::white, LLFontGL::LEFT, LLFontGL::BOTTOM, - LLFontGL::NORMAL, - LLFontGL::DROP_SHADOW); - - F32 data_progress = mImage->getDownloadProgress() ; - - // Draw the progress bar. - const S32 BAR_HEIGHT = 12; - const S32 BAR_LEFT_PAD = 80; - S32 left = interior.mLeft + 4 + BAR_LEFT_PAD; - S32 bar_width = getRect().getWidth() - left - RESIZE_HANDLE_WIDTH - 2; - S32 top = interior.mBottom + 4 + BAR_HEIGHT; - S32 right = left + bar_width; - S32 bottom = top - BAR_HEIGHT; - - LLColor4 background_color(0.f, 0.f, 0.f, 0.75f); - LLColor4 decoded_color(0.f, 1.f, 0.f, 1.0f); - LLColor4 downloaded_color(0.f, 0.5f, 0.f, 1.0f); - - gl_rect_2d(left, top, right, bottom, background_color); - - if (data_progress > 0.0f) - { - // Downloaded bytes - right = left + llfloor(data_progress * (F32)bar_width); - if (right > left) - { - gl_rect_2d(left, top, right, bottom, downloaded_color); - } - } - } - else - if( !mSavedFileTimer.hasExpired() ) - { - LLFontGL::getFontSansSerif()->renderUTF8(LLTrans::getString("FileSaved"), 0, - interior.mLeft + 4, - interior.mBottom + 4, - LLColor4::white, LLFontGL::LEFT, LLFontGL::BOTTOM, - LLFontGL::NORMAL, - LLFontGL::DROP_SHADOW); - } - } - } - -} - - -// virtual -bool LLPreviewTexture::canSaveAs() const -{ - return mIsFullPerm && !mLoadingFullImage && mImage.notNull() && !mImage->isMissingAsset(); -} - - -// virtual -void LLPreviewTexture::saveAs() -{ - if( mLoadingFullImage ) - return; - - std::string filename = getItem() ? LLDir::getScrubbedFileName(getItem()->getName()) : LLStringUtil::null; - LLFilePickerReplyThread::startPicker(boost::bind(&LLPreviewTexture::saveTextureToFile, this, _1), LLFilePicker::FFSAVE_TGAPNG, filename); -} - -void LLPreviewTexture::saveTextureToFile(const std::vector& filenames) -{ - const LLInventoryItem* item = getItem(); - if (item && mPreviewToSave) - { - mPreviewToSave = false; - LLFloaterReg::showTypedInstance("preview_texture", item->getUUID()); - } - - // remember the user-approved/edited file name. - mSaveFileName = filenames[0]; - mSavingMultiple = false; - mLoadingFullImage = true; - getWindow()->incBusyCount(); - - LL_DEBUGS("FileSaveAs") << "Scheduling saving file to " << mSaveFileName << LL_ENDL; - - mImage->forceToSaveRawImage(0);//re-fetch the raw image if the old one is removed. - mImage->setLoadedCallback(LLPreviewTexture::onFileLoadedForSave, - 0, true, false, new LLUUID(mItemUUID), &mCallbackTextureList); -} - - -void LLPreviewTexture::saveMultipleToFile(const std::string& file_name) -{ - std::string texture_location(gSavedSettings.getString("TextureSaveLocation")); - std::string texture_name = LLDir::getScrubbedFileName(file_name.empty() ? getItem()->getName() : file_name); - - mSaveFileName = texture_location + gDirUtilp->getDirDelimiter() + texture_name + ".png"; - - mSavingMultiple = true; - mLoadingFullImage = true; - getWindow()->incBusyCount(); - - LL_DEBUGS("FileSaveAs") << "Scheduling saving file to " << mSaveFileName << LL_ENDL; - - mImage->forceToSaveRawImage(0);//re-fetch the raw image if the old one is removed. - mImage->setLoadedCallback(LLPreviewTexture::onFileLoadedForSave, - 0, true, false, new LLUUID(mItemUUID), &mCallbackTextureList); -} - -// virtual -void LLPreviewTexture::reshape(S32 width, S32 height, bool called_from_parent) -{ - LLPreview::reshape(width, height, called_from_parent); - - LLRect dim_rect(getChildView("dimensions")->getRect()); - - S32 horiz_pad = 2 * (LLPANEL_BORDER_WIDTH + PREVIEW_PAD) + PREVIEW_RESIZE_HANDLE_SIZE; - - // add space for dimensions and aspect ratio - S32 info_height = dim_rect.mTop + CLIENT_RECT_VPAD; - if (getChild("buttons_panel")->getVisible()) - { - info_height += getChild("buttons_panel")->getRect().getHeight(); - } - LLRect client_rect(horiz_pad, getRect().getHeight(), getRect().getWidth() - horiz_pad, 0); - client_rect.mTop -= (PREVIEW_HEADER_SIZE + CLIENT_RECT_VPAD); - client_rect.mBottom += PREVIEW_BORDER + CLIENT_RECT_VPAD + info_height ; - - S32 client_width = client_rect.getWidth(); - S32 client_height = client_rect.getHeight(); - - if (mAspectRatio > 0.f) - { - if(mAspectRatio > 1.f) - { - client_height = llceil((F32)client_width / mAspectRatio); - if(client_height > client_rect.getHeight()) - { - client_height = client_rect.getHeight(); - client_width = llceil((F32)client_height * mAspectRatio); - } - } - else//mAspectRatio < 1.f - { - client_width = llceil((F32)client_height * mAspectRatio); - if(client_width > client_rect.getWidth()) - { - client_width = client_rect.getWidth(); - client_height = llceil((F32)client_width / mAspectRatio); - } - } - } - - mClientRect.setLeftTopAndSize(client_rect.getCenterX() - (client_width / 2), client_rect.getCenterY() + (client_height / 2), client_width, client_height); - -} - -// virtual -void LLPreviewTexture::onFocusReceived() -{ - LLPreview::onFocusReceived(); -} - -void LLPreviewTexture::openToSave() -{ - mPreviewToSave = true; -} - -void LLPreviewTexture::hideCtrlButtons() -{ - getChildView("desc txt")->setVisible(false); - getChildView("desc")->setVisible(false); - getChild("preview_stack")->collapsePanel(getChild("buttons_panel"), true); - getChild("buttons_panel")->setVisible(false); - getChild("combo_aspect_ratio")->setCurrentByIndex(0); //unconstrained - reshape(getRect().getWidth(), getRect().getHeight()); -} - -// static -void LLPreviewTexture::onFileLoadedForSave(bool success, - LLViewerFetchedTexture *src_vi, - LLImageRaw* src, - LLImageRaw* aux_src, - S32 discard_level, - bool final, - void* userdata) -{ - LLUUID* item_uuid = (LLUUID*) userdata; - - LLPreviewTexture* self = LLFloaterReg::findTypedInstance("preview_texture", *item_uuid); - - if( final || !success ) - { - delete item_uuid; - - if( self ) - { - self->getWindow()->decBusyCount(); - self->mLoadingFullImage = false; - } - } - - if( self && final && success ) - { - LL_DEBUGS("FileSaveAs") << "Saving file to " << self->mSaveFileName << LL_ENDL; - const U32 ext_length = 3; - std::string extension = self->mSaveFileName.substr( self->mSaveFileName.length() - ext_length); - - std::string filepath; - if (self->mSavingMultiple) - { - std::string part_path = self->mSaveFileName.substr(0, self->mSaveFileName.length() - ext_length - 1); - - S32 i = 0; - S32 err = 0; - do - { - filepath = part_path; - - if (i != 0) - { - filepath += llformat("_%.3d", i); - } - - filepath += "."; - filepath += extension; - - llstat stat_info; - err = LLFile::stat(filepath, &stat_info); - i++; - } while (-1 != err); // Search until the file is not found (i.e., stat() gives an error). - } - else - { - filepath = self->mSaveFileName; - } - - LLStringUtil::toLower(extension); - // We only support saving in PNG or TGA format - LLPointer image; - if(extension == "png") - { - image = new LLImagePNG; - } - else if(extension == "tga") - { - image = new LLImageTGA; - } - - if( image && !image->encode( src, 0 ) ) - { - LLSD args; - args["FILE"] = filepath; - LLNotificationsUtil::add("CannotEncodeFile", args); - } - else if( image && !image->save(filepath) ) - { - LLSD args; - args["FILE"] = filepath; - LLNotificationsUtil::add("CannotWriteFile", args); - } - else - { - self->mSavedFileTimer.reset(); - self->mSavedFileTimer.setTimerExpirySec( SECONDS_TO_SHOW_FILE_SAVED_MSG ); - } - LL_DEBUGS("FileSaveAs") << "Done saving file to " << filepath << LL_ENDL; - - self->mSaveFileName.clear(); - } - - if( self && !success ) - { - LLNotificationsUtil::add("CannotDownloadFile"); - } - -} - - -// It takes a while until we get height and width information. -// When we receive it, reshape the window accordingly. -void LLPreviewTexture::updateDimensions() -{ - if (!mImage) - { - return; - } - if ((mImage->getFullWidth() * mImage->getFullHeight()) == 0) - { - return; - } - - S32 img_width = mImage->getFullWidth(); - S32 img_height = mImage->getFullHeight(); - - if (mAssetStatus != PREVIEW_ASSET_LOADED - || mLastWidth != img_width - || mLastHeight != img_height) - { - mAssetStatus = PREVIEW_ASSET_LOADED; - // Asset has been fully loaded, adjust aspect ratio - adjustAspectRatio(); - } - - - // Update the width/height display every time - getChild("dimensions")->setTextArg("[WIDTH]", llformat("%d", img_width)); - getChild("dimensions")->setTextArg("[HEIGHT]", llformat("%d", img_height)); - - mLastHeight = img_height; - mLastWidth = img_width; - - // Reshape the floater only when required - if (mUpdateDimensions) - { - mUpdateDimensions = false; - - //reshape floater - reshape(getRect().getWidth(), getRect().getHeight()); - - gFloaterView->adjustToFitScreen(this, false); - - LLRect dim_rect(getChildView("dimensions")->getRect()); - LLRect aspect_label_rect(getChildView("aspect_ratio")->getRect()); - getChildView("aspect_ratio")->setVisible( dim_rect.mRight < aspect_label_rect.mLeft); - } -} - - -// Return true if everything went fine, false if we somewhat modified the ratio as we bumped on border values -bool LLPreviewTexture::setAspectRatio(const F32 width, const F32 height) -{ - mUpdateDimensions = true; - - // We don't allow negative width or height. Also, if height is positive but too small, we reset to default - // A default 0.f value for mAspectRatio means "unconstrained" in the rest of the code - if ((width <= 0.f) || (height <= F_APPROXIMATELY_ZERO)) - { - mAspectRatio = 0.f; - return false; - } - - // Compute and store the ratio - F32 ratio = width / height; - mAspectRatio = llclamp(ratio, PREVIEW_TEXTURE_MIN_ASPECT, PREVIEW_TEXTURE_MAX_ASPECT); - - // Return false if we clamped the value, true otherwise - return (ratio == mAspectRatio); -} - - -void LLPreviewTexture::onAspectRatioCommit(LLUICtrl* ctrl, void* userdata) -{ - LLPreviewTexture* self = (LLPreviewTexture*) userdata; - - std::string ratio(ctrl->getValue().asString()); - std::string::size_type separator(ratio.find_first_of(":/\\")); - - if (std::string::npos == separator) { - // If there's no separator assume we want an unconstrained ratio - self->setAspectRatio( 0.f, 0.f ); - return; - } - - F32 width, height; - std::istringstream numerator(ratio.substr(0, separator)); - std::istringstream denominator(ratio.substr(separator + 1)); - numerator >> width; - denominator >> height; - - self->setAspectRatio( width, height ); -} - -void LLPreviewTexture::loadAsset() -{ - mImage = LLViewerTextureManager::getFetchedTexture(mImageID, FTT_DEFAULT, MIPMAP_TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); - mImageOldBoostLevel = mImage->getBoostLevel(); - mImage->setBoostLevel(LLGLTexture::BOOST_PREVIEW); - mImage->forceToSaveRawImage(0) ; - mAssetStatus = PREVIEW_ASSET_LOADING; - mUpdateDimensions = true; - updateDimensions(); - getChildView("save_tex_btn")->setEnabled(canSaveAs()); - if (mObjectUUID.notNull()) - { - // check that we can copy inworld items into inventory - getChildView("Keep")->setEnabled(mIsCopyable); - } - else - { - // check that we can remove item - bool source_library = gInventory.isObjectDescendentOf(mItemUUID, gInventory.getLibraryRootFolderID()); - if (source_library) - { - getChildView("Discard")->setEnabled(false); - } - } -} - -LLPreview::EAssetStatus LLPreviewTexture::getAssetStatus() -{ - if (mImage.notNull() && (mImage->getFullWidth() * mImage->getFullHeight() > 0)) - { - mAssetStatus = PREVIEW_ASSET_LOADED; - } - return mAssetStatus; -} - -void LLPreviewTexture::adjustAspectRatio() -{ - S32 w = mImage->getFullWidth(); - S32 h = mImage->getFullHeight(); - - // Determine aspect ratio of the image - S32 tmp; - while (h != 0) - { - tmp = w % h; - w = h; - h = tmp; - } - S32 divisor = w; - S32 num = mImage->getFullWidth() / divisor; - S32 denom = mImage->getFullHeight() / divisor; - - if (setAspectRatio(num, denom)) - { - // Select corresponding ratio entry in the combo list - LLComboBox* combo = getChild("combo_aspect_ratio"); - if (combo) - { - std::ostringstream ratio; - ratio << num << ":" << denom; - std::vector::const_iterator found = std::find(mRatiosList.begin(), mRatiosList.end(), ratio.str()); - if (found == mRatiosList.end()) - { - // No existing ratio found, create an element that will show image at original ratio - populateRatioList(); // makes sure previous custom ratio is cleared - std::string ratio = std::to_string(num)+":" + std::to_string(denom); - mRatiosList.push_back(ratio); - combo->add(ratio); - combo->setCurrentByIndex(mRatiosList.size()- 1); - } - else - { - combo->setCurrentByIndex(found - mRatiosList.begin()); - } - } - } - else - { - // Aspect ratio was set to unconstrained or was clamped - LLComboBox* combo = getChild("combo_aspect_ratio"); - if (combo) - { - combo->setCurrentByIndex(0); //unconstrained - } - } - - mUpdateDimensions = true; -} - -void LLPreviewTexture::updateImageID() -{ - const LLViewerInventoryItem *item = static_cast(getItem()); - if(item) - { - mImageID = item->getAssetUUID(); - - // here's the old logic... - //mShowKeepDiscard = item->getPermissions().getCreator() != gAgent.getID(); - // here's the new logic... 'cos we hate disappearing buttons. - mShowKeepDiscard = true; - - mCopyToInv = false; - LLPermissions perm(item->getPermissions()); - mIsCopyable = perm.allowCopyBy(gAgent.getID(), gAgent.getGroupID()) && perm.allowTransferTo(gAgent.getID()); - mIsFullPerm = item->checkPermissionsSet(PERM_ITEM_UNRESTRICTED); - } - else // not an item, assume it's an asset id - { - mImageID = mItemUUID; - mShowKeepDiscard = false; - mCopyToInv = true; - mIsCopyable = true; - mIsFullPerm = true; - } - -} - -/* virtual */ -void LLPreviewTexture::setObjectID(const LLUUID& object_id) -{ - mObjectUUID = object_id; - - const LLUUID old_image_id = mImageID; - - // Update what image we're pointing to, such as if we just specified the mObjectID - // that this mItemID is part of. - updateImageID(); - - // If the imageID has changed, start over and reload the new image. - if (mImageID != old_image_id) - { - mAssetStatus = PREVIEW_ASSET_UNLOADED; - loadAsset(); - } - refreshFromItem(); -} +/** + * @file llpreviewtexture.cpp + * @brief LLPreviewTexture class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llwindow.h" + +#include "llpreviewtexture.h" + +#include "llagent.h" +#include "llbutton.h" +#include "llcombobox.h" +#include "llfilepicker.h" +#include "llfloaterreg.h" +#include "llimagetga.h" +#include "llimagepng.h" +#include "llinventory.h" +#include "llinventorymodel.h" +#include "llnotificationsutil.h" +#include "llresmgr.h" +#include "lltrans.h" +#include "lltextbox.h" +#include "lltextureview.h" +#include "llui.h" +#include "llviewerinventory.h" +#include "llviewermenufile.h" // LLFilePickerReplyThread +#include "llviewertexture.h" +#include "llviewertexturelist.h" +#include "lluictrlfactory.h" +#include "llviewercontrol.h" +#include "llviewerwindow.h" +#include "lllineeditor.h" + +#include + +const S32 CLIENT_RECT_VPAD = 4; + +const F32 SECONDS_TO_SHOW_FILE_SAVED_MSG = 8.f; + +const F32 PREVIEW_TEXTURE_MAX_ASPECT = 200.f; +const F32 PREVIEW_TEXTURE_MIN_ASPECT = 0.005f; + + +LLPreviewTexture::LLPreviewTexture(const LLSD& key) + : LLPreview(key), + mLoadingFullImage( false ), + mSavingMultiple(false), + mShowKeepDiscard(false), + mCopyToInv(false), + mIsCopyable(false), + mIsFullPerm(false), + mUpdateDimensions(true), + mLastHeight(0), + mLastWidth(0), + mAspectRatio(0.f), + mPreviewToSave(false), + mImage(NULL), + mImageOldBoostLevel(LLGLTexture::BOOST_NONE) +{ + updateImageID(); + if (key.has("save_as")) + { + mPreviewToSave = true; + } +} + +LLPreviewTexture::~LLPreviewTexture() +{ + LLLoadedCallbackEntry::cleanUpCallbackList(&mCallbackTextureList) ; + + if( mLoadingFullImage ) + { + getWindow()->decBusyCount(); + } + + if (mImage.notNull()) + { + mImage->setBoostLevel(mImageOldBoostLevel); + mImage = NULL; + } +} + +void LLPreviewTexture::populateRatioList() +{ + // Fill in ratios list with common aspect ratio values + mRatiosList.clear(); + mRatiosList.push_back(LLTrans::getString("Unconstrained")); + mRatiosList.push_back("1:1"); + mRatiosList.push_back("4:3"); + mRatiosList.push_back("10:7"); + mRatiosList.push_back("3:2"); + mRatiosList.push_back("16:10"); + mRatiosList.push_back("16:9"); + mRatiosList.push_back("2:1"); + + // Now fill combo box with provided list + LLComboBox* combo = getChild("combo_aspect_ratio"); + combo->removeall(); + + for (std::vector::const_iterator it = mRatiosList.begin(); it != mRatiosList.end(); ++it) + { + combo->add(*it); + } +} + +// virtual +bool LLPreviewTexture::postBuild() +{ + if (mCopyToInv) + { + getChild("Keep")->setLabel(getString("Copy")); + childSetAction("Keep",LLPreview::onBtnCopyToInv,this); + getChildView("Discard")->setVisible( false); + } + else if (mShowKeepDiscard) + { + childSetAction("Keep",onKeepBtn,this); + childSetAction("Discard",onDiscardBtn,this); + } + else + { + getChildView("Keep")->setVisible( false); + getChildView("Discard")->setVisible( false); + } + + childSetAction("save_tex_btn", LLPreviewTexture::onSaveAsBtn, this); + getChildView("save_tex_btn")->setVisible( true); + getChildView("save_tex_btn")->setEnabled(canSaveAs()); + + const LLInventoryItem* item = getItem(); + if (item) + { + if (!mCopyToInv) + { + childSetCommitCallback("desc", LLPreview::onText, this); + getChild("desc")->setValue(item->getDescription()); + getChild("desc")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); + } + bool source_library = mObjectUUID.isNull() && gInventory.isObjectDescendentOf(item->getUUID(), gInventory.getLibraryRootFolderID()); + if (source_library) + { + getChildView("Discard")->setEnabled(false); + } + } + + // Fill in ratios list and combo box with common aspect ratio values + populateRatioList(); + + childSetCommitCallback("combo_aspect_ratio", onAspectRatioCommit, this); + + LLComboBox* combo = getChild("combo_aspect_ratio"); + combo->setCurrentByIndex(0); + + return LLPreview::postBuild(); +} + +// static +void LLPreviewTexture::onSaveAsBtn(void* data) +{ + LLPreviewTexture* self = (LLPreviewTexture*)data; + self->saveAs(); +} + +void LLPreviewTexture::draw() +{ + updateDimensions(); + + LLPreview::draw(); + + if (!isMinimized()) + { + LLGLSUIDefault gls_ui; + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + const LLRect& border = mClientRect; + LLRect interior = mClientRect; + interior.stretch( -PREVIEW_BORDER_WIDTH ); + + // ...border + gl_rect_2d( border, LLColor4(0.f, 0.f, 0.f, 1.f)); + gl_rect_2d_checkerboard( interior ); + + if ( mImage.notNull() ) + { + // Draw the texture + gGL.diffuseColor3f( 1.f, 1.f, 1.f ); + gl_draw_scaled_image(interior.mLeft, + interior.mBottom, + interior.getWidth(), + interior.getHeight(), + mImage); + + // Pump the texture priority + F32 pixel_area = mLoadingFullImage ? (F32)MAX_IMAGE_AREA : (F32)(interior.getWidth() * interior.getHeight() ); + mImage->addTextureStats( pixel_area ); + + // Don't bother decoding more than we can display, unless + // we're loading the full image. + if (!mLoadingFullImage) + { + S32 int_width = interior.getWidth(); + S32 int_height = interior.getHeight(); + mImage->setKnownDrawSize(int_width, int_height); + } + else + { + // Don't use this feature + mImage->setKnownDrawSize(0, 0); + } + + if( mLoadingFullImage ) + { + LLFontGL::getFontSansSerif()->renderUTF8(LLTrans::getString("Receiving"), 0, + interior.mLeft + 4, + interior.mBottom + 4, + LLColor4::white, LLFontGL::LEFT, LLFontGL::BOTTOM, + LLFontGL::NORMAL, + LLFontGL::DROP_SHADOW); + + F32 data_progress = mImage->getDownloadProgress() ; + + // Draw the progress bar. + const S32 BAR_HEIGHT = 12; + const S32 BAR_LEFT_PAD = 80; + S32 left = interior.mLeft + 4 + BAR_LEFT_PAD; + S32 bar_width = getRect().getWidth() - left - RESIZE_HANDLE_WIDTH - 2; + S32 top = interior.mBottom + 4 + BAR_HEIGHT; + S32 right = left + bar_width; + S32 bottom = top - BAR_HEIGHT; + + LLColor4 background_color(0.f, 0.f, 0.f, 0.75f); + LLColor4 decoded_color(0.f, 1.f, 0.f, 1.0f); + LLColor4 downloaded_color(0.f, 0.5f, 0.f, 1.0f); + + gl_rect_2d(left, top, right, bottom, background_color); + + if (data_progress > 0.0f) + { + // Downloaded bytes + right = left + llfloor(data_progress * (F32)bar_width); + if (right > left) + { + gl_rect_2d(left, top, right, bottom, downloaded_color); + } + } + } + else + if( !mSavedFileTimer.hasExpired() ) + { + LLFontGL::getFontSansSerif()->renderUTF8(LLTrans::getString("FileSaved"), 0, + interior.mLeft + 4, + interior.mBottom + 4, + LLColor4::white, LLFontGL::LEFT, LLFontGL::BOTTOM, + LLFontGL::NORMAL, + LLFontGL::DROP_SHADOW); + } + } + } + +} + + +// virtual +bool LLPreviewTexture::canSaveAs() const +{ + return mIsFullPerm && !mLoadingFullImage && mImage.notNull() && !mImage->isMissingAsset(); +} + + +// virtual +void LLPreviewTexture::saveAs() +{ + if( mLoadingFullImage ) + return; + + std::string filename = getItem() ? LLDir::getScrubbedFileName(getItem()->getName()) : LLStringUtil::null; + LLFilePickerReplyThread::startPicker(boost::bind(&LLPreviewTexture::saveTextureToFile, this, _1), LLFilePicker::FFSAVE_TGAPNG, filename); +} + +void LLPreviewTexture::saveTextureToFile(const std::vector& filenames) +{ + const LLInventoryItem* item = getItem(); + if (item && mPreviewToSave) + { + mPreviewToSave = false; + LLFloaterReg::showTypedInstance("preview_texture", item->getUUID()); + } + + // remember the user-approved/edited file name. + mSaveFileName = filenames[0]; + mSavingMultiple = false; + mLoadingFullImage = true; + getWindow()->incBusyCount(); + + LL_DEBUGS("FileSaveAs") << "Scheduling saving file to " << mSaveFileName << LL_ENDL; + + mImage->forceToSaveRawImage(0);//re-fetch the raw image if the old one is removed. + mImage->setLoadedCallback(LLPreviewTexture::onFileLoadedForSave, + 0, true, false, new LLUUID(mItemUUID), &mCallbackTextureList); +} + + +void LLPreviewTexture::saveMultipleToFile(const std::string& file_name) +{ + std::string texture_location(gSavedSettings.getString("TextureSaveLocation")); + std::string texture_name = LLDir::getScrubbedFileName(file_name.empty() ? getItem()->getName() : file_name); + + mSaveFileName = texture_location + gDirUtilp->getDirDelimiter() + texture_name + ".png"; + + mSavingMultiple = true; + mLoadingFullImage = true; + getWindow()->incBusyCount(); + + LL_DEBUGS("FileSaveAs") << "Scheduling saving file to " << mSaveFileName << LL_ENDL; + + mImage->forceToSaveRawImage(0);//re-fetch the raw image if the old one is removed. + mImage->setLoadedCallback(LLPreviewTexture::onFileLoadedForSave, + 0, true, false, new LLUUID(mItemUUID), &mCallbackTextureList); +} + +// virtual +void LLPreviewTexture::reshape(S32 width, S32 height, bool called_from_parent) +{ + LLPreview::reshape(width, height, called_from_parent); + + LLRect dim_rect(getChildView("dimensions")->getRect()); + + S32 horiz_pad = 2 * (LLPANEL_BORDER_WIDTH + PREVIEW_PAD) + PREVIEW_RESIZE_HANDLE_SIZE; + + // add space for dimensions and aspect ratio + S32 info_height = dim_rect.mTop + CLIENT_RECT_VPAD; + if (getChild("buttons_panel")->getVisible()) + { + info_height += getChild("buttons_panel")->getRect().getHeight(); + } + LLRect client_rect(horiz_pad, getRect().getHeight(), getRect().getWidth() - horiz_pad, 0); + client_rect.mTop -= (PREVIEW_HEADER_SIZE + CLIENT_RECT_VPAD); + client_rect.mBottom += PREVIEW_BORDER + CLIENT_RECT_VPAD + info_height ; + + S32 client_width = client_rect.getWidth(); + S32 client_height = client_rect.getHeight(); + + if (mAspectRatio > 0.f) + { + if(mAspectRatio > 1.f) + { + client_height = llceil((F32)client_width / mAspectRatio); + if(client_height > client_rect.getHeight()) + { + client_height = client_rect.getHeight(); + client_width = llceil((F32)client_height * mAspectRatio); + } + } + else//mAspectRatio < 1.f + { + client_width = llceil((F32)client_height * mAspectRatio); + if(client_width > client_rect.getWidth()) + { + client_width = client_rect.getWidth(); + client_height = llceil((F32)client_width / mAspectRatio); + } + } + } + + mClientRect.setLeftTopAndSize(client_rect.getCenterX() - (client_width / 2), client_rect.getCenterY() + (client_height / 2), client_width, client_height); + +} + +// virtual +void LLPreviewTexture::onFocusReceived() +{ + LLPreview::onFocusReceived(); +} + +void LLPreviewTexture::openToSave() +{ + mPreviewToSave = true; +} + +void LLPreviewTexture::hideCtrlButtons() +{ + getChildView("desc txt")->setVisible(false); + getChildView("desc")->setVisible(false); + getChild("preview_stack")->collapsePanel(getChild("buttons_panel"), true); + getChild("buttons_panel")->setVisible(false); + getChild("combo_aspect_ratio")->setCurrentByIndex(0); //unconstrained + reshape(getRect().getWidth(), getRect().getHeight()); +} + +// static +void LLPreviewTexture::onFileLoadedForSave(bool success, + LLViewerFetchedTexture *src_vi, + LLImageRaw* src, + LLImageRaw* aux_src, + S32 discard_level, + bool final, + void* userdata) +{ + LLUUID* item_uuid = (LLUUID*) userdata; + + LLPreviewTexture* self = LLFloaterReg::findTypedInstance("preview_texture", *item_uuid); + + if( final || !success ) + { + delete item_uuid; + + if( self ) + { + self->getWindow()->decBusyCount(); + self->mLoadingFullImage = false; + } + } + + if( self && final && success ) + { + LL_DEBUGS("FileSaveAs") << "Saving file to " << self->mSaveFileName << LL_ENDL; + const U32 ext_length = 3; + std::string extension = self->mSaveFileName.substr( self->mSaveFileName.length() - ext_length); + + std::string filepath; + if (self->mSavingMultiple) + { + std::string part_path = self->mSaveFileName.substr(0, self->mSaveFileName.length() - ext_length - 1); + + S32 i = 0; + S32 err = 0; + do + { + filepath = part_path; + + if (i != 0) + { + filepath += llformat("_%.3d", i); + } + + filepath += "."; + filepath += extension; + + llstat stat_info; + err = LLFile::stat(filepath, &stat_info); + i++; + } while (-1 != err); // Search until the file is not found (i.e., stat() gives an error). + } + else + { + filepath = self->mSaveFileName; + } + + LLStringUtil::toLower(extension); + // We only support saving in PNG or TGA format + LLPointer image; + if(extension == "png") + { + image = new LLImagePNG; + } + else if(extension == "tga") + { + image = new LLImageTGA; + } + + if( image && !image->encode( src, 0 ) ) + { + LLSD args; + args["FILE"] = filepath; + LLNotificationsUtil::add("CannotEncodeFile", args); + } + else if( image && !image->save(filepath) ) + { + LLSD args; + args["FILE"] = filepath; + LLNotificationsUtil::add("CannotWriteFile", args); + } + else + { + self->mSavedFileTimer.reset(); + self->mSavedFileTimer.setTimerExpirySec( SECONDS_TO_SHOW_FILE_SAVED_MSG ); + } + LL_DEBUGS("FileSaveAs") << "Done saving file to " << filepath << LL_ENDL; + + self->mSaveFileName.clear(); + } + + if( self && !success ) + { + LLNotificationsUtil::add("CannotDownloadFile"); + } + +} + + +// It takes a while until we get height and width information. +// When we receive it, reshape the window accordingly. +void LLPreviewTexture::updateDimensions() +{ + if (!mImage) + { + return; + } + if ((mImage->getFullWidth() * mImage->getFullHeight()) == 0) + { + return; + } + + S32 img_width = mImage->getFullWidth(); + S32 img_height = mImage->getFullHeight(); + + if (mAssetStatus != PREVIEW_ASSET_LOADED + || mLastWidth != img_width + || mLastHeight != img_height) + { + mAssetStatus = PREVIEW_ASSET_LOADED; + // Asset has been fully loaded, adjust aspect ratio + adjustAspectRatio(); + } + + + // Update the width/height display every time + getChild("dimensions")->setTextArg("[WIDTH]", llformat("%d", img_width)); + getChild("dimensions")->setTextArg("[HEIGHT]", llformat("%d", img_height)); + + mLastHeight = img_height; + mLastWidth = img_width; + + // Reshape the floater only when required + if (mUpdateDimensions) + { + mUpdateDimensions = false; + + //reshape floater + reshape(getRect().getWidth(), getRect().getHeight()); + + gFloaterView->adjustToFitScreen(this, false); + + LLRect dim_rect(getChildView("dimensions")->getRect()); + LLRect aspect_label_rect(getChildView("aspect_ratio")->getRect()); + getChildView("aspect_ratio")->setVisible( dim_rect.mRight < aspect_label_rect.mLeft); + } +} + + +// Return true if everything went fine, false if we somewhat modified the ratio as we bumped on border values +bool LLPreviewTexture::setAspectRatio(const F32 width, const F32 height) +{ + mUpdateDimensions = true; + + // We don't allow negative width or height. Also, if height is positive but too small, we reset to default + // A default 0.f value for mAspectRatio means "unconstrained" in the rest of the code + if ((width <= 0.f) || (height <= F_APPROXIMATELY_ZERO)) + { + mAspectRatio = 0.f; + return false; + } + + // Compute and store the ratio + F32 ratio = width / height; + mAspectRatio = llclamp(ratio, PREVIEW_TEXTURE_MIN_ASPECT, PREVIEW_TEXTURE_MAX_ASPECT); + + // Return false if we clamped the value, true otherwise + return (ratio == mAspectRatio); +} + + +void LLPreviewTexture::onAspectRatioCommit(LLUICtrl* ctrl, void* userdata) +{ + LLPreviewTexture* self = (LLPreviewTexture*) userdata; + + std::string ratio(ctrl->getValue().asString()); + std::string::size_type separator(ratio.find_first_of(":/\\")); + + if (std::string::npos == separator) { + // If there's no separator assume we want an unconstrained ratio + self->setAspectRatio( 0.f, 0.f ); + return; + } + + F32 width, height; + std::istringstream numerator(ratio.substr(0, separator)); + std::istringstream denominator(ratio.substr(separator + 1)); + numerator >> width; + denominator >> height; + + self->setAspectRatio( width, height ); +} + +void LLPreviewTexture::loadAsset() +{ + mImage = LLViewerTextureManager::getFetchedTexture(mImageID, FTT_DEFAULT, MIPMAP_TRUE, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + mImageOldBoostLevel = mImage->getBoostLevel(); + mImage->setBoostLevel(LLGLTexture::BOOST_PREVIEW); + mImage->forceToSaveRawImage(0) ; + mAssetStatus = PREVIEW_ASSET_LOADING; + mUpdateDimensions = true; + updateDimensions(); + getChildView("save_tex_btn")->setEnabled(canSaveAs()); + if (mObjectUUID.notNull()) + { + // check that we can copy inworld items into inventory + getChildView("Keep")->setEnabled(mIsCopyable); + } + else + { + // check that we can remove item + bool source_library = gInventory.isObjectDescendentOf(mItemUUID, gInventory.getLibraryRootFolderID()); + if (source_library) + { + getChildView("Discard")->setEnabled(false); + } + } +} + +LLPreview::EAssetStatus LLPreviewTexture::getAssetStatus() +{ + if (mImage.notNull() && (mImage->getFullWidth() * mImage->getFullHeight() > 0)) + { + mAssetStatus = PREVIEW_ASSET_LOADED; + } + return mAssetStatus; +} + +void LLPreviewTexture::adjustAspectRatio() +{ + S32 w = mImage->getFullWidth(); + S32 h = mImage->getFullHeight(); + + // Determine aspect ratio of the image + S32 tmp; + while (h != 0) + { + tmp = w % h; + w = h; + h = tmp; + } + S32 divisor = w; + S32 num = mImage->getFullWidth() / divisor; + S32 denom = mImage->getFullHeight() / divisor; + + if (setAspectRatio(num, denom)) + { + // Select corresponding ratio entry in the combo list + LLComboBox* combo = getChild("combo_aspect_ratio"); + if (combo) + { + std::ostringstream ratio; + ratio << num << ":" << denom; + std::vector::const_iterator found = std::find(mRatiosList.begin(), mRatiosList.end(), ratio.str()); + if (found == mRatiosList.end()) + { + // No existing ratio found, create an element that will show image at original ratio + populateRatioList(); // makes sure previous custom ratio is cleared + std::string ratio = std::to_string(num)+":" + std::to_string(denom); + mRatiosList.push_back(ratio); + combo->add(ratio); + combo->setCurrentByIndex(mRatiosList.size()- 1); + } + else + { + combo->setCurrentByIndex(found - mRatiosList.begin()); + } + } + } + else + { + // Aspect ratio was set to unconstrained or was clamped + LLComboBox* combo = getChild("combo_aspect_ratio"); + if (combo) + { + combo->setCurrentByIndex(0); //unconstrained + } + } + + mUpdateDimensions = true; +} + +void LLPreviewTexture::updateImageID() +{ + const LLViewerInventoryItem *item = static_cast(getItem()); + if(item) + { + mImageID = item->getAssetUUID(); + + // here's the old logic... + //mShowKeepDiscard = item->getPermissions().getCreator() != gAgent.getID(); + // here's the new logic... 'cos we hate disappearing buttons. + mShowKeepDiscard = true; + + mCopyToInv = false; + LLPermissions perm(item->getPermissions()); + mIsCopyable = perm.allowCopyBy(gAgent.getID(), gAgent.getGroupID()) && perm.allowTransferTo(gAgent.getID()); + mIsFullPerm = item->checkPermissionsSet(PERM_ITEM_UNRESTRICTED); + } + else // not an item, assume it's an asset id + { + mImageID = mItemUUID; + mShowKeepDiscard = false; + mCopyToInv = true; + mIsCopyable = true; + mIsFullPerm = true; + } + +} + +/* virtual */ +void LLPreviewTexture::setObjectID(const LLUUID& object_id) +{ + mObjectUUID = object_id; + + const LLUUID old_image_id = mImageID; + + // Update what image we're pointing to, such as if we just specified the mObjectID + // that this mItemID is part of. + updateImageID(); + + // If the imageID has changed, start over and reload the new image. + if (mImageID != old_image_id) + { + mAssetStatus = PREVIEW_ASSET_UNLOADED; + loadAsset(); + } + refreshFromItem(); +} diff --git a/indra/newview/llpreviewtexture.h b/indra/newview/llpreviewtexture.h index 282e1fccef..e55d61ef10 100644 --- a/indra/newview/llpreviewtexture.h +++ b/indra/newview/llpreviewtexture.h @@ -1,109 +1,109 @@ -/** - * @file llpreviewtexture.h - * @brief LLPreviewTexture class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPREVIEWTEXTURE_H -#define LL_LLPREVIEWTEXTURE_H - -#include "llpreview.h" -#include "llbutton.h" -#include "llframetimer.h" -#include "llviewertexture.h" - -class LLComboBox; -class LLImageRaw; - -class LLPreviewTexture : public LLPreview -{ -public: - LLPreviewTexture(const LLSD& key); - ~LLPreviewTexture(); - - virtual void draw(); - - virtual bool canSaveAs() const; - virtual void saveAs(); - - virtual void loadAsset(); - virtual EAssetStatus getAssetStatus(); - - virtual void reshape(S32 width, S32 height, bool called_from_parent = true); - virtual void onFocusReceived(); - - static void onFileLoadedForSave( - bool success, - LLViewerFetchedTexture *src_vi, - LLImageRaw* src, - LLImageRaw* aux_src, - S32 discard_level, - bool final, - void* userdata ); - void openToSave(); - - void saveTextureToFile(const std::vector& filenames); - void saveMultipleToFile(const std::string& file_name = ""); - - static void onSaveAsBtn(void* data); - - void hideCtrlButtons(); - - /*virtual*/ void setObjectID(const LLUUID& object_id); -protected: - void init(); - void populateRatioList(); - /* virtual */ bool postBuild(); - bool setAspectRatio(const F32 width, const F32 height); - static void onAspectRatioCommit(LLUICtrl*,void* userdata); - void adjustAspectRatio(); - -private: - void updateImageID(); // set what image is being uploaded. - void updateDimensions(); - LLUUID mImageID; - LLPointer mImage; - S32 mImageOldBoostLevel; - std::string mSaveFileName; - LLFrameTimer mSavedFileTimer; - bool mSavingMultiple; - bool mLoadingFullImage; - bool mShowKeepDiscard; - bool mCopyToInv; - - // Save the image once it's loaded. - bool mPreviewToSave; - - // This is stored off in a member variable, because the save-as - // button and drag and drop functionality need to know. - bool mIsCopyable; - bool mIsFullPerm; - bool mUpdateDimensions; - S32 mLastHeight; - S32 mLastWidth; - F32 mAspectRatio; - - LLLoadedCallbackEntry::source_callback_list_t mCallbackTextureList ; - std::vector mRatiosList; -}; -#endif // LL_LLPREVIEWTEXTURE_H +/** + * @file llpreviewtexture.h + * @brief LLPreviewTexture class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPREVIEWTEXTURE_H +#define LL_LLPREVIEWTEXTURE_H + +#include "llpreview.h" +#include "llbutton.h" +#include "llframetimer.h" +#include "llviewertexture.h" + +class LLComboBox; +class LLImageRaw; + +class LLPreviewTexture : public LLPreview +{ +public: + LLPreviewTexture(const LLSD& key); + ~LLPreviewTexture(); + + virtual void draw(); + + virtual bool canSaveAs() const; + virtual void saveAs(); + + virtual void loadAsset(); + virtual EAssetStatus getAssetStatus(); + + virtual void reshape(S32 width, S32 height, bool called_from_parent = true); + virtual void onFocusReceived(); + + static void onFileLoadedForSave( + bool success, + LLViewerFetchedTexture *src_vi, + LLImageRaw* src, + LLImageRaw* aux_src, + S32 discard_level, + bool final, + void* userdata ); + void openToSave(); + + void saveTextureToFile(const std::vector& filenames); + void saveMultipleToFile(const std::string& file_name = ""); + + static void onSaveAsBtn(void* data); + + void hideCtrlButtons(); + + /*virtual*/ void setObjectID(const LLUUID& object_id); +protected: + void init(); + void populateRatioList(); + /* virtual */ bool postBuild(); + bool setAspectRatio(const F32 width, const F32 height); + static void onAspectRatioCommit(LLUICtrl*,void* userdata); + void adjustAspectRatio(); + +private: + void updateImageID(); // set what image is being uploaded. + void updateDimensions(); + LLUUID mImageID; + LLPointer mImage; + S32 mImageOldBoostLevel; + std::string mSaveFileName; + LLFrameTimer mSavedFileTimer; + bool mSavingMultiple; + bool mLoadingFullImage; + bool mShowKeepDiscard; + bool mCopyToInv; + + // Save the image once it's loaded. + bool mPreviewToSave; + + // This is stored off in a member variable, because the save-as + // button and drag and drop functionality need to know. + bool mIsCopyable; + bool mIsFullPerm; + bool mUpdateDimensions; + S32 mLastHeight; + S32 mLastWidth; + F32 mAspectRatio; + + LLLoadedCallbackEntry::source_callback_list_t mCallbackTextureList ; + std::vector mRatiosList; +}; +#endif // LL_LLPREVIEWTEXTURE_H diff --git a/indra/newview/llprogressview.cpp b/indra/newview/llprogressview.cpp index 623f617d29..e03984a44c 100644 --- a/indra/newview/llprogressview.cpp +++ b/indra/newview/llprogressview.cpp @@ -1,655 +1,655 @@ -/** - * @file llprogressview.cpp - * @brief LLProgressView class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llprogressview.h" - -#include "indra_constants.h" -#include "llmath.h" -#include "llgl.h" -#include "llrender.h" -#include "llui.h" -#include "llfontgl.h" -#include "lltimer.h" -#include "lltextbox.h" -#include "llglheaders.h" - -#include "llagent.h" -#include "llbutton.h" -#include "llcallbacklist.h" -#include "llfocusmgr.h" -#include "llnotifications.h" -#include "llprogressbar.h" -#include "llstartup.h" -#include "llviewercontrol.h" -#include "llviewertexturelist.h" -#include "llviewerwindow.h" -#include "llappviewer.h" -#include "llweb.h" -#include "lluictrlfactory.h" -#include "llpanellogin.h" - -LLProgressView* LLProgressView::sInstance = NULL; - -S32 gStartImageWidth = 1; -S32 gStartImageHeight = 1; -const F32 FADE_TO_WORLD_TIME = 1.0f; - -static LLPanelInjector r("progress_view"); - -// XUI: Translate -LLProgressView::LLProgressView() -: LLPanel(), - mPercentDone( 0.f ), - mMediaCtrl( NULL ), - mMouseDownInActiveArea( false ), - mUpdateEvents("LLProgressView"), - mFadeToWorldTimer(), - mFadeFromLoginTimer(), - mStartupComplete(false) -{ - mUpdateEvents.listen("self", boost::bind(&LLProgressView::handleUpdate, this, _1)); - mFadeToWorldTimer.stop(); - mFadeFromLoginTimer.stop(); -} - -bool LLProgressView::postBuild() -{ - mProgressBar = getChild("login_progress_bar"); - - // media control that is used to play intro video - mMediaCtrl = getChild("login_media_panel"); - mMediaCtrl->setVisible( false ); // hidden initially - mMediaCtrl->addObserver( this ); // watch events - - LLViewerMedia::getInstance()->setOnlyAudibleMediaTextureID(mMediaCtrl->getTextureID()); - - mCancelBtn = getChild("cancel_btn"); - mCancelBtn->setClickedCallback( LLProgressView::onCancelButtonClicked, NULL ); - - getChild("title_text")->setText(LLStringExplicit(LLAppViewer::instance()->getSecondLifeTitle())); - - getChild("message_text")->setClickedCallback(onClickMessage, this); - - // hidden initially, until we need it - setVisible(false); - - LLNotifications::instance().getChannel("AlertModal")->connectChanged(boost::bind(&LLProgressView::onAlertModal, this, _1)); - - sInstance = this; - return true; -} - - -LLProgressView::~LLProgressView() -{ - // Just in case something went wrong, make sure we deregister our idle callback. - gIdleCallbacks.deleteFunction(onIdle, this); - - gFocusMgr.releaseFocusIfNeeded( this ); - - sInstance = NULL; -} - -bool LLProgressView::handleHover(S32 x, S32 y, MASK mask) -{ - if( childrenHandleHover( x, y, mask ) == NULL ) - { - gViewerWindow->setCursor(UI_CURSOR_WAIT); - } - return true; -} - - -bool LLProgressView::handleKeyHere(KEY key, MASK mask) -{ - // Suck up all keystokes except CTRL-Q. - if( ('Q' == key) && (MASK_CONTROL == mask) ) - { - LLAppViewer::instance()->userQuit(); - } - return true; -} - -void LLProgressView::revealIntroPanel() -{ - // if user hasn't yet seen intro video - std::string intro_url = gSavedSettings.getString("PostFirstLoginIntroURL"); - if ( intro_url.length() > 0 && - gSavedSettings.getBOOL("BrowserJavascriptEnabled") && - !gSavedSettings.getBOOL("PostFirstLoginIntroViewed")) - { - // hide the progress bar - getChild("stack1")->setVisible(false); - - // navigate to intro URL and reveal widget - mMediaCtrl->navigateTo( intro_url ); - mMediaCtrl->setVisible( true ); - - - // flag as having seen the new user post login intro - gSavedSettings.setBOOL("PostFirstLoginIntroViewed", true ); - - mMediaCtrl->setFocus(true); - } - - mFadeFromLoginTimer.start(); - gIdleCallbacks.addFunction(onIdle, this); -} - -void LLProgressView::setStartupComplete() -{ - mStartupComplete = true; - - // if we are not showing a video, fade into world - if (!mMediaCtrl->getVisible()) - { - mFadeFromLoginTimer.stop(); - mFadeToWorldTimer.start(); - } -} - -void LLProgressView::setVisible(bool visible) -{ - if (!visible && mFadeFromLoginTimer.getStarted()) - { - mFadeFromLoginTimer.stop(); - } - // hiding progress view - if (getVisible() && !visible) - { - LLPanel::setVisible(false); - } - // showing progress view - else if (visible && (!getVisible() || mFadeToWorldTimer.getStarted())) - { - setFocus(true); - mFadeToWorldTimer.stop(); - LLPanel::setVisible(true); - } -} - - -void LLProgressView::drawStartTexture(F32 alpha) -{ - gGL.pushMatrix(); - if (gStartTexture) - { - LLGLSUIDefault gls_ui; - gGL.getTexUnit(0)->bind(gStartTexture.get()); - gGL.color4f(1.f, 1.f, 1.f, alpha); - F32 image_aspect = (F32)gStartImageWidth / (F32)gStartImageHeight; - S32 width = getRect().getWidth(); - S32 height = getRect().getHeight(); - F32 view_aspect = (F32)width / (F32)height; - // stretch image to maintain aspect ratio - if (image_aspect > view_aspect) - { - gGL.translatef(-0.5f * (image_aspect / view_aspect - 1.f) * width, 0.f, 0.f); - gGL.scalef(image_aspect / view_aspect, 1.f, 1.f); - } - else - { - gGL.translatef(0.f, -0.5f * (view_aspect / image_aspect - 1.f) * height, 0.f); - gGL.scalef(1.f, view_aspect / image_aspect, 1.f); - } - gl_rect_2d_simple_tex( getRect().getWidth(), getRect().getHeight() ); - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - } - else - { - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.color4f(0.f, 0.f, 0.f, 1.f); - gl_rect_2d(getRect()); - } - gGL.popMatrix(); -} - -void LLProgressView::drawLogos(F32 alpha) -{ - if (mLogosList.empty()) - { - return; - } - - // logos are tied to label, - // due to potential resizes we have to figure offsets out on draw or resize - LLTextBox *logos_label = getChild("logos_lbl"); - S32 offset_x, offset_y; - logos_label->localPointToScreen(0, 0, &offset_x, &offset_y); - std::vector::const_iterator iter = mLogosList.begin(); - std::vector::const_iterator end = mLogosList.end(); - for (; iter != end; iter++) - { - gl_draw_scaled_image_with_border(iter->mDrawRect.mLeft + offset_x, - iter->mDrawRect.mBottom + offset_y, - iter->mDrawRect.getWidth(), - iter->mDrawRect.getHeight(), - iter->mTexturep.get(), - UI_VERTEX_COLOR % alpha, - false, - iter->mClipRect, - iter->mOffsetRect); - } -} - -void LLProgressView::draw() -{ - static LLTimer timer; - - if (mFadeFromLoginTimer.getStarted()) - { - F32 alpha = clamp_rescale(mFadeFromLoginTimer.getElapsedTimeF32(), 0.f, FADE_TO_WORLD_TIME, 0.f, 1.f); - LLViewDrawContext context(alpha); - - if (!mMediaCtrl->getVisible()) - { - drawStartTexture(alpha); - } - - LLPanel::draw(); - drawLogos(alpha); - return; - } - - // handle fade out to world view when we're asked to - if (mFadeToWorldTimer.getStarted()) - { - // draw fading panel - F32 alpha = clamp_rescale(mFadeToWorldTimer.getElapsedTimeF32(), 0.f, FADE_TO_WORLD_TIME, 1.f, 0.f); - LLViewDrawContext context(alpha); - - drawStartTexture(alpha); - LLPanel::draw(); - drawLogos(alpha); - - // faded out completely - remove panel and reveal world - if (mFadeToWorldTimer.getElapsedTimeF32() > FADE_TO_WORLD_TIME ) - { - mFadeToWorldTimer.stop(); - - LLViewerMedia::getInstance()->setOnlyAudibleMediaTextureID(LLUUID::null); - - // Fade is complete, release focus - gFocusMgr.releaseFocusIfNeeded( this ); - - // turn off panel that hosts intro so we see the world - setVisible(false); - - // stop observing events since we no longer care - mMediaCtrl->remObserver( this ); - - // hide the intro - mMediaCtrl->setVisible( false ); - - // navigate away from intro page to something innocuous since 'unload' is broken right now - //mMediaCtrl->navigateTo( "about:blank" ); - - // FIXME: this causes a crash that i haven't been able to fix - mMediaCtrl->unloadMediaSource(); - - releaseTextures(); - } - return; - } - - drawStartTexture(1.0f); - // draw children - LLPanel::draw(); - drawLogos(1.0f); -} - -void LLProgressView::setText(const std::string& text) -{ - getChild("progress_text")->setValue(text); -} - -void LLProgressView::setPercent(const F32 percent) -{ - mProgressBar->setValue(percent); -} - -void LLProgressView::setMessage(const std::string& msg) -{ - mMessage = msg; - getChild("message_text")->setValue(mMessage); -} - -void LLProgressView::loadLogo(const std::string &path, - const U8 image_codec, - const LLRect &pos_rect, - const LLRectf &clip_rect, - const LLRectf &offset_rect) -{ - // We need these images very early, so we have to force-load them, otherwise they might not load in time. - if (!gDirUtilp->fileExists(path)) - { - return; - } - - LLPointer start_image_frmted = LLImageFormatted::createFromType(image_codec); - if (!start_image_frmted->load(path)) - { - LL_WARNS("AppInit") << "Image load failed: " << path << LL_ENDL; - return; - } - - LLPointer raw = new LLImageRaw; - if (!start_image_frmted->decode(raw, 0.0f)) - { - LL_WARNS("AppInit") << "Image decode failed " << path << LL_ENDL; - return; - } - // HACK: getLocalTexture allows only power of two dimentions - raw->expandToPowerOfTwo(); - - TextureData data; - data.mTexturep = LLViewerTextureManager::getLocalTexture(raw.get(), false); - data.mDrawRect = pos_rect; - data.mClipRect = clip_rect; - data.mOffsetRect = offset_rect; - mLogosList.push_back(data); -} - -void LLProgressView::initLogos() -{ - mLogosList.clear(); - - const U8 image_codec = IMG_CODEC_PNG; - const LLRectf default_clip(0.f, 1.f, 1.f, 0.f); - const S32 default_height = 28; - const S32 default_pad = 15; - - S32 icon_width, icon_height; - - // We don't know final screen rect yet, so we can't precalculate position fully - LLTextBox *logos_label = getChild("logos_lbl"); - S32 texture_start_x = logos_label->getFont()->getWidthF32(logos_label->getText()) + default_pad; - S32 texture_start_y = -7; - - // Normally we would just preload these textures from textures.xml, - // and display them via icon control, but they are only needed on - // startup and preloaded/UI ones stay forever - // (and this code was done already so simply reused it) - std::string temp_str = gDirUtilp->getExpandedFilename(LL_PATH_DEFAULT_SKIN, "textures", "3p_icons"); - - temp_str += gDirUtilp->getDirDelimiter(); - -#ifdef LL_HAVOK - // original image size is 342x113, central element is on a larger side - // plus internal padding, so it gets slightly more height than desired 32 - icon_width = 88; - icon_height = 29; - S32 pad_havok_y = -1; - loadLogo(temp_str + "havok_logo.png", - image_codec, - LLRect(texture_start_x, texture_start_y + pad_havok_y + icon_height, texture_start_x + icon_width, texture_start_y + pad_havok_y), - default_clip, - default_clip); - - texture_start_x += icon_width + default_pad; -#endif //LL_HAVOK - - // 108x41 - icon_width = 74; - icon_height = default_height; - loadLogo(temp_str + "vivox_logo.png", - image_codec, - LLRect(texture_start_x, texture_start_y + icon_height, texture_start_x + icon_width, texture_start_y), - default_clip, - default_clip); -} - -void LLProgressView::initStartTexture(S32 location_id, bool is_in_production) -{ - if (gStartTexture.notNull()) - { - gStartTexture = NULL; - LL_INFOS("AppInit") << "re-initializing start screen" << LL_ENDL; - } - - LL_DEBUGS("AppInit") << "Loading startup bitmap..." << LL_ENDL; - - U8 image_codec = IMG_CODEC_PNG; - std::string temp_str = gDirUtilp->getLindenUserDir() + gDirUtilp->getDirDelimiter(); - - if ((S32)START_LOCATION_ID_LAST == location_id) - { - temp_str += LLStartUp::getScreenLastFilename(); - } - else - { - std::string path = temp_str + LLStartUp::getScreenHomeFilename(); - - if (!gDirUtilp->fileExists(path) && is_in_production) - { - // Fallback to old file, can be removed later - // Home image only sets when user changes home, so it will take time for users to switch to pngs - temp_str += "screen_home.bmp"; - image_codec = IMG_CODEC_BMP; - } - else - { - temp_str = path; - } - } - - LLPointer start_image_frmted = LLImageFormatted::createFromType(image_codec); - - // Turn off start screen to get around the occasional readback - // driver bug - if (!gSavedSettings.getBOOL("UseStartScreen")) - { - LL_INFOS("AppInit") << "Bitmap load disabled" << LL_ENDL; - return; - } - else if (!start_image_frmted->load(temp_str)) - { - LL_WARNS("AppInit") << "Bitmap load failed" << LL_ENDL; - gStartTexture = NULL; - } - else - { - gStartImageWidth = start_image_frmted->getWidth(); - gStartImageHeight = start_image_frmted->getHeight(); - - LLPointer raw = new LLImageRaw; - if (!start_image_frmted->decode(raw, 0.0f)) - { - LL_WARNS("AppInit") << "Bitmap decode failed" << LL_ENDL; - gStartTexture = NULL; - } - else - { - // HACK: getLocalTexture allows only power of two dimentions - raw->expandToPowerOfTwo(); - gStartTexture = LLViewerTextureManager::getLocalTexture(raw.get(), false); - } - } - - if (gStartTexture.isNull()) - { - gStartTexture = LLViewerTexture::sBlackImagep; - gStartImageWidth = gStartTexture->getWidth(); - gStartImageHeight = gStartTexture->getHeight(); - } -} - -void LLProgressView::initTextures(S32 location_id, bool is_in_production) -{ - initStartTexture(location_id, is_in_production); - initLogos(); - - childSetVisible("panel_icons", !mLogosList.empty()); - childSetVisible("panel_top_spacer", mLogosList.empty()); -} - -void LLProgressView::releaseTextures() -{ - gStartTexture = NULL; - mLogosList.clear(); - - childSetVisible("panel_top_spacer", true); - childSetVisible("panel_icons", false); -} - -void LLProgressView::setCancelButtonVisible(bool b, const std::string& label) -{ - mCancelBtn->setVisible(b); - mCancelBtn->setEnabled(b); - mCancelBtn->setLabelSelected(label); - mCancelBtn->setLabelUnselected(label); -} - -// static -void LLProgressView::onCancelButtonClicked(void*) -{ - // Quitting viewer here should happen only when "Quit" button is pressed while starting up. - // Check for startup state is used here instead of teleport state to avoid quitting when - // cancel is pressed while teleporting inside region (EXT-4911) - if (LLStartUp::getStartupState() < STATE_STARTED) - { - LL_INFOS() << "User requesting quit during login" << LL_ENDL; - LLAppViewer::instance()->requestQuit(); - } - else - { - gAgent.teleportCancel(); - sInstance->mCancelBtn->setEnabled(false); - sInstance->setVisible(false); - } -} - -// static -void LLProgressView::onClickMessage(void* data) -{ - LLProgressView* viewp = (LLProgressView*)data; - if ( viewp != NULL && ! viewp->mMessage.empty() ) - { - std::string url_to_open( "" ); - - size_t start_pos; - start_pos = viewp->mMessage.find( "https://" ); - if (start_pos == std::string::npos) - start_pos = viewp->mMessage.find( "http://" ); - if (start_pos == std::string::npos) - start_pos = viewp->mMessage.find( "ftp://" ); - - if ( start_pos != std::string::npos ) - { - size_t end_pos = viewp->mMessage.find_first_of( " \n\r\t", start_pos ); - if ( end_pos != std::string::npos ) - url_to_open = viewp->mMessage.substr( start_pos, end_pos - start_pos ); - else - url_to_open = viewp->mMessage.substr( start_pos ); - - LLWeb::loadURLExternal( url_to_open ); - } - } -} - -bool LLProgressView::handleUpdate(const LLSD& event_data) -{ - LLSD message = event_data.get("message"); - LLSD desc = event_data.get("desc"); - LLSD percent = event_data.get("percent"); - - if(message.isDefined()) - { - setMessage(message.asString()); - } - - if(desc.isDefined()) - { - setText(desc.asString()); - } - - if(percent.isDefined()) - { - setPercent(percent.asReal()); - } - return false; -} - -bool LLProgressView::onAlertModal(const LLSD& notify) -{ - // if the progress view is visible, it will obscure the notification window - // in this case, we want to auto-accept WebLaunchExternalTarget notifications - if (isInVisibleChain() && notify["sigtype"].asString() == "add") - { - LLNotificationPtr notifyp = LLNotifications::instance().find(notify["id"].asUUID()); - if (notifyp && notifyp->getName() == "WebLaunchExternalTarget") - { - notifyp->respondWithDefault(); - } - } - return false; -} - -void LLProgressView::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) -{ - // the intro web content calls javascript::window.close() when it's done - if( event == MEDIA_EVENT_CLOSE_REQUEST ) - { - if (mStartupComplete) - { - //make sure other timer has stopped - mFadeFromLoginTimer.stop(); - mFadeToWorldTimer.start(); - } - else - { - // hide the media ctrl and wait for startup to be completed before fading to world - mMediaCtrl->setVisible(false); - if (mMediaCtrl->getMediaPlugin()) - { - mMediaCtrl->getMediaPlugin()->stop(); - } - - // show the progress bar - getChild("stack1")->setVisible(true); - } - } -} - - -// static -void LLProgressView::onIdle(void* user_data) -{ - LLProgressView* self = (LLProgressView*) user_data; - - // Close login panel on mFadeToWorldTimer expiration. - if (self->mFadeFromLoginTimer.getStarted() && - self->mFadeFromLoginTimer.getElapsedTimeF32() > FADE_TO_WORLD_TIME) - { - self->mFadeFromLoginTimer.stop(); - LLPanelLogin::closePanel(); - - // Nothing to do anymore. - gIdleCallbacks.deleteFunction(onIdle, user_data); - } -} +/** + * @file llprogressview.cpp + * @brief LLProgressView class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llprogressview.h" + +#include "indra_constants.h" +#include "llmath.h" +#include "llgl.h" +#include "llrender.h" +#include "llui.h" +#include "llfontgl.h" +#include "lltimer.h" +#include "lltextbox.h" +#include "llglheaders.h" + +#include "llagent.h" +#include "llbutton.h" +#include "llcallbacklist.h" +#include "llfocusmgr.h" +#include "llnotifications.h" +#include "llprogressbar.h" +#include "llstartup.h" +#include "llviewercontrol.h" +#include "llviewertexturelist.h" +#include "llviewerwindow.h" +#include "llappviewer.h" +#include "llweb.h" +#include "lluictrlfactory.h" +#include "llpanellogin.h" + +LLProgressView* LLProgressView::sInstance = NULL; + +S32 gStartImageWidth = 1; +S32 gStartImageHeight = 1; +const F32 FADE_TO_WORLD_TIME = 1.0f; + +static LLPanelInjector r("progress_view"); + +// XUI: Translate +LLProgressView::LLProgressView() +: LLPanel(), + mPercentDone( 0.f ), + mMediaCtrl( NULL ), + mMouseDownInActiveArea( false ), + mUpdateEvents("LLProgressView"), + mFadeToWorldTimer(), + mFadeFromLoginTimer(), + mStartupComplete(false) +{ + mUpdateEvents.listen("self", boost::bind(&LLProgressView::handleUpdate, this, _1)); + mFadeToWorldTimer.stop(); + mFadeFromLoginTimer.stop(); +} + +bool LLProgressView::postBuild() +{ + mProgressBar = getChild("login_progress_bar"); + + // media control that is used to play intro video + mMediaCtrl = getChild("login_media_panel"); + mMediaCtrl->setVisible( false ); // hidden initially + mMediaCtrl->addObserver( this ); // watch events + + LLViewerMedia::getInstance()->setOnlyAudibleMediaTextureID(mMediaCtrl->getTextureID()); + + mCancelBtn = getChild("cancel_btn"); + mCancelBtn->setClickedCallback( LLProgressView::onCancelButtonClicked, NULL ); + + getChild("title_text")->setText(LLStringExplicit(LLAppViewer::instance()->getSecondLifeTitle())); + + getChild("message_text")->setClickedCallback(onClickMessage, this); + + // hidden initially, until we need it + setVisible(false); + + LLNotifications::instance().getChannel("AlertModal")->connectChanged(boost::bind(&LLProgressView::onAlertModal, this, _1)); + + sInstance = this; + return true; +} + + +LLProgressView::~LLProgressView() +{ + // Just in case something went wrong, make sure we deregister our idle callback. + gIdleCallbacks.deleteFunction(onIdle, this); + + gFocusMgr.releaseFocusIfNeeded( this ); + + sInstance = NULL; +} + +bool LLProgressView::handleHover(S32 x, S32 y, MASK mask) +{ + if( childrenHandleHover( x, y, mask ) == NULL ) + { + gViewerWindow->setCursor(UI_CURSOR_WAIT); + } + return true; +} + + +bool LLProgressView::handleKeyHere(KEY key, MASK mask) +{ + // Suck up all keystokes except CTRL-Q. + if( ('Q' == key) && (MASK_CONTROL == mask) ) + { + LLAppViewer::instance()->userQuit(); + } + return true; +} + +void LLProgressView::revealIntroPanel() +{ + // if user hasn't yet seen intro video + std::string intro_url = gSavedSettings.getString("PostFirstLoginIntroURL"); + if ( intro_url.length() > 0 && + gSavedSettings.getBOOL("BrowserJavascriptEnabled") && + !gSavedSettings.getBOOL("PostFirstLoginIntroViewed")) + { + // hide the progress bar + getChild("stack1")->setVisible(false); + + // navigate to intro URL and reveal widget + mMediaCtrl->navigateTo( intro_url ); + mMediaCtrl->setVisible( true ); + + + // flag as having seen the new user post login intro + gSavedSettings.setBOOL("PostFirstLoginIntroViewed", true ); + + mMediaCtrl->setFocus(true); + } + + mFadeFromLoginTimer.start(); + gIdleCallbacks.addFunction(onIdle, this); +} + +void LLProgressView::setStartupComplete() +{ + mStartupComplete = true; + + // if we are not showing a video, fade into world + if (!mMediaCtrl->getVisible()) + { + mFadeFromLoginTimer.stop(); + mFadeToWorldTimer.start(); + } +} + +void LLProgressView::setVisible(bool visible) +{ + if (!visible && mFadeFromLoginTimer.getStarted()) + { + mFadeFromLoginTimer.stop(); + } + // hiding progress view + if (getVisible() && !visible) + { + LLPanel::setVisible(false); + } + // showing progress view + else if (visible && (!getVisible() || mFadeToWorldTimer.getStarted())) + { + setFocus(true); + mFadeToWorldTimer.stop(); + LLPanel::setVisible(true); + } +} + + +void LLProgressView::drawStartTexture(F32 alpha) +{ + gGL.pushMatrix(); + if (gStartTexture) + { + LLGLSUIDefault gls_ui; + gGL.getTexUnit(0)->bind(gStartTexture.get()); + gGL.color4f(1.f, 1.f, 1.f, alpha); + F32 image_aspect = (F32)gStartImageWidth / (F32)gStartImageHeight; + S32 width = getRect().getWidth(); + S32 height = getRect().getHeight(); + F32 view_aspect = (F32)width / (F32)height; + // stretch image to maintain aspect ratio + if (image_aspect > view_aspect) + { + gGL.translatef(-0.5f * (image_aspect / view_aspect - 1.f) * width, 0.f, 0.f); + gGL.scalef(image_aspect / view_aspect, 1.f, 1.f); + } + else + { + gGL.translatef(0.f, -0.5f * (view_aspect / image_aspect - 1.f) * height, 0.f); + gGL.scalef(1.f, view_aspect / image_aspect, 1.f); + } + gl_rect_2d_simple_tex( getRect().getWidth(), getRect().getHeight() ); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + } + else + { + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gGL.color4f(0.f, 0.f, 0.f, 1.f); + gl_rect_2d(getRect()); + } + gGL.popMatrix(); +} + +void LLProgressView::drawLogos(F32 alpha) +{ + if (mLogosList.empty()) + { + return; + } + + // logos are tied to label, + // due to potential resizes we have to figure offsets out on draw or resize + LLTextBox *logos_label = getChild("logos_lbl"); + S32 offset_x, offset_y; + logos_label->localPointToScreen(0, 0, &offset_x, &offset_y); + std::vector::const_iterator iter = mLogosList.begin(); + std::vector::const_iterator end = mLogosList.end(); + for (; iter != end; iter++) + { + gl_draw_scaled_image_with_border(iter->mDrawRect.mLeft + offset_x, + iter->mDrawRect.mBottom + offset_y, + iter->mDrawRect.getWidth(), + iter->mDrawRect.getHeight(), + iter->mTexturep.get(), + UI_VERTEX_COLOR % alpha, + false, + iter->mClipRect, + iter->mOffsetRect); + } +} + +void LLProgressView::draw() +{ + static LLTimer timer; + + if (mFadeFromLoginTimer.getStarted()) + { + F32 alpha = clamp_rescale(mFadeFromLoginTimer.getElapsedTimeF32(), 0.f, FADE_TO_WORLD_TIME, 0.f, 1.f); + LLViewDrawContext context(alpha); + + if (!mMediaCtrl->getVisible()) + { + drawStartTexture(alpha); + } + + LLPanel::draw(); + drawLogos(alpha); + return; + } + + // handle fade out to world view when we're asked to + if (mFadeToWorldTimer.getStarted()) + { + // draw fading panel + F32 alpha = clamp_rescale(mFadeToWorldTimer.getElapsedTimeF32(), 0.f, FADE_TO_WORLD_TIME, 1.f, 0.f); + LLViewDrawContext context(alpha); + + drawStartTexture(alpha); + LLPanel::draw(); + drawLogos(alpha); + + // faded out completely - remove panel and reveal world + if (mFadeToWorldTimer.getElapsedTimeF32() > FADE_TO_WORLD_TIME ) + { + mFadeToWorldTimer.stop(); + + LLViewerMedia::getInstance()->setOnlyAudibleMediaTextureID(LLUUID::null); + + // Fade is complete, release focus + gFocusMgr.releaseFocusIfNeeded( this ); + + // turn off panel that hosts intro so we see the world + setVisible(false); + + // stop observing events since we no longer care + mMediaCtrl->remObserver( this ); + + // hide the intro + mMediaCtrl->setVisible( false ); + + // navigate away from intro page to something innocuous since 'unload' is broken right now + //mMediaCtrl->navigateTo( "about:blank" ); + + // FIXME: this causes a crash that i haven't been able to fix + mMediaCtrl->unloadMediaSource(); + + releaseTextures(); + } + return; + } + + drawStartTexture(1.0f); + // draw children + LLPanel::draw(); + drawLogos(1.0f); +} + +void LLProgressView::setText(const std::string& text) +{ + getChild("progress_text")->setValue(text); +} + +void LLProgressView::setPercent(const F32 percent) +{ + mProgressBar->setValue(percent); +} + +void LLProgressView::setMessage(const std::string& msg) +{ + mMessage = msg; + getChild("message_text")->setValue(mMessage); +} + +void LLProgressView::loadLogo(const std::string &path, + const U8 image_codec, + const LLRect &pos_rect, + const LLRectf &clip_rect, + const LLRectf &offset_rect) +{ + // We need these images very early, so we have to force-load them, otherwise they might not load in time. + if (!gDirUtilp->fileExists(path)) + { + return; + } + + LLPointer start_image_frmted = LLImageFormatted::createFromType(image_codec); + if (!start_image_frmted->load(path)) + { + LL_WARNS("AppInit") << "Image load failed: " << path << LL_ENDL; + return; + } + + LLPointer raw = new LLImageRaw; + if (!start_image_frmted->decode(raw, 0.0f)) + { + LL_WARNS("AppInit") << "Image decode failed " << path << LL_ENDL; + return; + } + // HACK: getLocalTexture allows only power of two dimentions + raw->expandToPowerOfTwo(); + + TextureData data; + data.mTexturep = LLViewerTextureManager::getLocalTexture(raw.get(), false); + data.mDrawRect = pos_rect; + data.mClipRect = clip_rect; + data.mOffsetRect = offset_rect; + mLogosList.push_back(data); +} + +void LLProgressView::initLogos() +{ + mLogosList.clear(); + + const U8 image_codec = IMG_CODEC_PNG; + const LLRectf default_clip(0.f, 1.f, 1.f, 0.f); + const S32 default_height = 28; + const S32 default_pad = 15; + + S32 icon_width, icon_height; + + // We don't know final screen rect yet, so we can't precalculate position fully + LLTextBox *logos_label = getChild("logos_lbl"); + S32 texture_start_x = logos_label->getFont()->getWidthF32(logos_label->getText()) + default_pad; + S32 texture_start_y = -7; + + // Normally we would just preload these textures from textures.xml, + // and display them via icon control, but they are only needed on + // startup and preloaded/UI ones stay forever + // (and this code was done already so simply reused it) + std::string temp_str = gDirUtilp->getExpandedFilename(LL_PATH_DEFAULT_SKIN, "textures", "3p_icons"); + + temp_str += gDirUtilp->getDirDelimiter(); + +#ifdef LL_HAVOK + // original image size is 342x113, central element is on a larger side + // plus internal padding, so it gets slightly more height than desired 32 + icon_width = 88; + icon_height = 29; + S32 pad_havok_y = -1; + loadLogo(temp_str + "havok_logo.png", + image_codec, + LLRect(texture_start_x, texture_start_y + pad_havok_y + icon_height, texture_start_x + icon_width, texture_start_y + pad_havok_y), + default_clip, + default_clip); + + texture_start_x += icon_width + default_pad; +#endif //LL_HAVOK + + // 108x41 + icon_width = 74; + icon_height = default_height; + loadLogo(temp_str + "vivox_logo.png", + image_codec, + LLRect(texture_start_x, texture_start_y + icon_height, texture_start_x + icon_width, texture_start_y), + default_clip, + default_clip); +} + +void LLProgressView::initStartTexture(S32 location_id, bool is_in_production) +{ + if (gStartTexture.notNull()) + { + gStartTexture = NULL; + LL_INFOS("AppInit") << "re-initializing start screen" << LL_ENDL; + } + + LL_DEBUGS("AppInit") << "Loading startup bitmap..." << LL_ENDL; + + U8 image_codec = IMG_CODEC_PNG; + std::string temp_str = gDirUtilp->getLindenUserDir() + gDirUtilp->getDirDelimiter(); + + if ((S32)START_LOCATION_ID_LAST == location_id) + { + temp_str += LLStartUp::getScreenLastFilename(); + } + else + { + std::string path = temp_str + LLStartUp::getScreenHomeFilename(); + + if (!gDirUtilp->fileExists(path) && is_in_production) + { + // Fallback to old file, can be removed later + // Home image only sets when user changes home, so it will take time for users to switch to pngs + temp_str += "screen_home.bmp"; + image_codec = IMG_CODEC_BMP; + } + else + { + temp_str = path; + } + } + + LLPointer start_image_frmted = LLImageFormatted::createFromType(image_codec); + + // Turn off start screen to get around the occasional readback + // driver bug + if (!gSavedSettings.getBOOL("UseStartScreen")) + { + LL_INFOS("AppInit") << "Bitmap load disabled" << LL_ENDL; + return; + } + else if (!start_image_frmted->load(temp_str)) + { + LL_WARNS("AppInit") << "Bitmap load failed" << LL_ENDL; + gStartTexture = NULL; + } + else + { + gStartImageWidth = start_image_frmted->getWidth(); + gStartImageHeight = start_image_frmted->getHeight(); + + LLPointer raw = new LLImageRaw; + if (!start_image_frmted->decode(raw, 0.0f)) + { + LL_WARNS("AppInit") << "Bitmap decode failed" << LL_ENDL; + gStartTexture = NULL; + } + else + { + // HACK: getLocalTexture allows only power of two dimentions + raw->expandToPowerOfTwo(); + gStartTexture = LLViewerTextureManager::getLocalTexture(raw.get(), false); + } + } + + if (gStartTexture.isNull()) + { + gStartTexture = LLViewerTexture::sBlackImagep; + gStartImageWidth = gStartTexture->getWidth(); + gStartImageHeight = gStartTexture->getHeight(); + } +} + +void LLProgressView::initTextures(S32 location_id, bool is_in_production) +{ + initStartTexture(location_id, is_in_production); + initLogos(); + + childSetVisible("panel_icons", !mLogosList.empty()); + childSetVisible("panel_top_spacer", mLogosList.empty()); +} + +void LLProgressView::releaseTextures() +{ + gStartTexture = NULL; + mLogosList.clear(); + + childSetVisible("panel_top_spacer", true); + childSetVisible("panel_icons", false); +} + +void LLProgressView::setCancelButtonVisible(bool b, const std::string& label) +{ + mCancelBtn->setVisible(b); + mCancelBtn->setEnabled(b); + mCancelBtn->setLabelSelected(label); + mCancelBtn->setLabelUnselected(label); +} + +// static +void LLProgressView::onCancelButtonClicked(void*) +{ + // Quitting viewer here should happen only when "Quit" button is pressed while starting up. + // Check for startup state is used here instead of teleport state to avoid quitting when + // cancel is pressed while teleporting inside region (EXT-4911) + if (LLStartUp::getStartupState() < STATE_STARTED) + { + LL_INFOS() << "User requesting quit during login" << LL_ENDL; + LLAppViewer::instance()->requestQuit(); + } + else + { + gAgent.teleportCancel(); + sInstance->mCancelBtn->setEnabled(false); + sInstance->setVisible(false); + } +} + +// static +void LLProgressView::onClickMessage(void* data) +{ + LLProgressView* viewp = (LLProgressView*)data; + if ( viewp != NULL && ! viewp->mMessage.empty() ) + { + std::string url_to_open( "" ); + + size_t start_pos; + start_pos = viewp->mMessage.find( "https://" ); + if (start_pos == std::string::npos) + start_pos = viewp->mMessage.find( "http://" ); + if (start_pos == std::string::npos) + start_pos = viewp->mMessage.find( "ftp://" ); + + if ( start_pos != std::string::npos ) + { + size_t end_pos = viewp->mMessage.find_first_of( " \n\r\t", start_pos ); + if ( end_pos != std::string::npos ) + url_to_open = viewp->mMessage.substr( start_pos, end_pos - start_pos ); + else + url_to_open = viewp->mMessage.substr( start_pos ); + + LLWeb::loadURLExternal( url_to_open ); + } + } +} + +bool LLProgressView::handleUpdate(const LLSD& event_data) +{ + LLSD message = event_data.get("message"); + LLSD desc = event_data.get("desc"); + LLSD percent = event_data.get("percent"); + + if(message.isDefined()) + { + setMessage(message.asString()); + } + + if(desc.isDefined()) + { + setText(desc.asString()); + } + + if(percent.isDefined()) + { + setPercent(percent.asReal()); + } + return false; +} + +bool LLProgressView::onAlertModal(const LLSD& notify) +{ + // if the progress view is visible, it will obscure the notification window + // in this case, we want to auto-accept WebLaunchExternalTarget notifications + if (isInVisibleChain() && notify["sigtype"].asString() == "add") + { + LLNotificationPtr notifyp = LLNotifications::instance().find(notify["id"].asUUID()); + if (notifyp && notifyp->getName() == "WebLaunchExternalTarget") + { + notifyp->respondWithDefault(); + } + } + return false; +} + +void LLProgressView::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) +{ + // the intro web content calls javascript::window.close() when it's done + if( event == MEDIA_EVENT_CLOSE_REQUEST ) + { + if (mStartupComplete) + { + //make sure other timer has stopped + mFadeFromLoginTimer.stop(); + mFadeToWorldTimer.start(); + } + else + { + // hide the media ctrl and wait for startup to be completed before fading to world + mMediaCtrl->setVisible(false); + if (mMediaCtrl->getMediaPlugin()) + { + mMediaCtrl->getMediaPlugin()->stop(); + } + + // show the progress bar + getChild("stack1")->setVisible(true); + } + } +} + + +// static +void LLProgressView::onIdle(void* user_data) +{ + LLProgressView* self = (LLProgressView*) user_data; + + // Close login panel on mFadeToWorldTimer expiration. + if (self->mFadeFromLoginTimer.getStarted() && + self->mFadeFromLoginTimer.getElapsedTimeF32() > FADE_TO_WORLD_TIME) + { + self->mFadeFromLoginTimer.stop(); + LLPanelLogin::closePanel(); + + // Nothing to do anymore. + gIdleCallbacks.deleteFunction(onIdle, user_data); + } +} diff --git a/indra/newview/llprogressview.h b/indra/newview/llprogressview.h index 6a57865a5c..db3f4a2e32 100644 --- a/indra/newview/llprogressview.h +++ b/indra/newview/llprogressview.h @@ -1,125 +1,125 @@ -/** - * @file llprogressview.h - * @brief LLProgressView class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLPROGRESSVIEW_H -#define LL_LLPROGRESSVIEW_H - -#include "llpanel.h" -#include "llmediactrl.h" -#include "llframetimer.h" -#include "llevents.h" - -class LLImageRaw; -class LLButton; -class LLProgressBar; -class LLViewerTexture; - -class LLProgressView : - public LLPanel, - public LLViewerMediaObserver - -{ - LOG_CLASS(LLProgressView); - -public: - LLProgressView(); - virtual ~LLProgressView(); - - bool postBuild(); - - /*virtual*/ void draw(); - void drawStartTexture(F32 alpha); - void drawLogos(F32 alpha); - - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleKeyHere(KEY key, MASK mask); - /*virtual*/ void setVisible(bool visible); - - // inherited from LLViewerMediaObserver - /*virtual*/ void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event); - - void setText(const std::string& text); - void setPercent(const F32 percent); - - // Set it to NULL when you want to eliminate the message. - void setMessage(const std::string& msg); - - // turns on (under certain circumstances) the into video after login - void revealIntroPanel(); - - void setStartupComplete(); - - // we have to preload local textures to make sure they won't be grey - void initTextures(S32 location_id, bool is_in_production); - void releaseTextures(); - - void setCancelButtonVisible(bool b, const std::string& label); - - static void onCancelButtonClicked( void* ); - static void onClickMessage(void*); - bool onAlertModal(const LLSD& sd); - -protected: - LLProgressBar* mProgressBar; - LLMediaCtrl* mMediaCtrl; - F32 mPercentDone; - std::string mMessage; - LLButton* mCancelBtn; - LLFrameTimer mFadeToWorldTimer; - LLFrameTimer mFadeFromLoginTimer; - LLRect mOutlineRect; - bool mMouseDownInActiveArea; - bool mStartupComplete; - - // The LLEventStream mUpdateEvents depends upon this class being a singleton - // to avoid pump name conflicts. - static LLProgressView* sInstance; - LLEventStream mUpdateEvents; - - bool handleUpdate(const LLSD& event_data); - static void onIdle(void* user_data); - void loadLogo(const std::string &path, const U8 image_codec, const LLRect &pos_rect, const LLRectf &clip_rect, const LLRectf &offset_rect); - // logos have unusual location and need to be preloaded to not appear grey, then deleted - void initLogos(); - // Loads a bitmap to display during load - void initStartTexture(S32 location_id, bool is_in_production); - -private: - // We need to draw textures on login, but only once. - // So this vector gets filled up for textures to render and gets cleaned later - // Some textures have unusual requirements, so we are rendering directly - class TextureData - { - public: - LLPointer mTexturep; - LLRect mDrawRect; - LLRectf mClipRect; - LLRectf mOffsetRect; - }; - std::vector mLogosList; -}; - -#endif // LL_LLPROGRESSVIEW_H +/** + * @file llprogressview.h + * @brief LLProgressView class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLPROGRESSVIEW_H +#define LL_LLPROGRESSVIEW_H + +#include "llpanel.h" +#include "llmediactrl.h" +#include "llframetimer.h" +#include "llevents.h" + +class LLImageRaw; +class LLButton; +class LLProgressBar; +class LLViewerTexture; + +class LLProgressView : + public LLPanel, + public LLViewerMediaObserver + +{ + LOG_CLASS(LLProgressView); + +public: + LLProgressView(); + virtual ~LLProgressView(); + + bool postBuild(); + + /*virtual*/ void draw(); + void drawStartTexture(F32 alpha); + void drawLogos(F32 alpha); + + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleKeyHere(KEY key, MASK mask); + /*virtual*/ void setVisible(bool visible); + + // inherited from LLViewerMediaObserver + /*virtual*/ void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event); + + void setText(const std::string& text); + void setPercent(const F32 percent); + + // Set it to NULL when you want to eliminate the message. + void setMessage(const std::string& msg); + + // turns on (under certain circumstances) the into video after login + void revealIntroPanel(); + + void setStartupComplete(); + + // we have to preload local textures to make sure they won't be grey + void initTextures(S32 location_id, bool is_in_production); + void releaseTextures(); + + void setCancelButtonVisible(bool b, const std::string& label); + + static void onCancelButtonClicked( void* ); + static void onClickMessage(void*); + bool onAlertModal(const LLSD& sd); + +protected: + LLProgressBar* mProgressBar; + LLMediaCtrl* mMediaCtrl; + F32 mPercentDone; + std::string mMessage; + LLButton* mCancelBtn; + LLFrameTimer mFadeToWorldTimer; + LLFrameTimer mFadeFromLoginTimer; + LLRect mOutlineRect; + bool mMouseDownInActiveArea; + bool mStartupComplete; + + // The LLEventStream mUpdateEvents depends upon this class being a singleton + // to avoid pump name conflicts. + static LLProgressView* sInstance; + LLEventStream mUpdateEvents; + + bool handleUpdate(const LLSD& event_data); + static void onIdle(void* user_data); + void loadLogo(const std::string &path, const U8 image_codec, const LLRect &pos_rect, const LLRectf &clip_rect, const LLRectf &offset_rect); + // logos have unusual location and need to be preloaded to not appear grey, then deleted + void initLogos(); + // Loads a bitmap to display during load + void initStartTexture(S32 location_id, bool is_in_production); + +private: + // We need to draw textures on login, but only once. + // So this vector gets filled up for textures to render and gets cleaned later + // Some textures have unusual requirements, so we are rendering directly + class TextureData + { + public: + LLPointer mTexturep; + LLRect mDrawRect; + LLRectf mClipRect; + LLRectf mOffsetRect; + }; + std::vector mLogosList; +}; + +#endif // LL_LLPROGRESSVIEW_H diff --git a/indra/newview/llregioninfomodel.cpp b/indra/newview/llregioninfomodel.cpp index 88b3614b4d..c15a5559aa 100644 --- a/indra/newview/llregioninfomodel.cpp +++ b/indra/newview/llregioninfomodel.cpp @@ -1,254 +1,254 @@ -/** - * @file llregioninfomodel.cpp - * @brief Region info model - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llregioninfomodel.h" - -// libs -#include "message.h" -#include "llregionflags.h" - -// viewer -#include "llagent.h" -#include "llviewerregion.h" - -void LLRegionInfoModel::reset() -{ - mSimAccess = 0; - mAgentLimit = 0; - mHardAgentLimit = 100; - - mRegionFlags = 0; - mEstateID = 0; - mParentEstateID = 0; - - mPricePerMeter = 0; - mRedirectGridX = 0; - mRedirectGridY = 0; - - mBillableFactor = 0.0f; - mObjectBonusFactor = 0.0f; - mWaterHeight = 0.0f; - mTerrainRaiseLimit = 0.0f; - mTerrainLowerLimit = 0.0f; - mSunHour = 0.0f; - - mUseEstateSun = false; - - mSimType.clear(); - mSimName.clear(); -} - -LLRegionInfoModel::LLRegionInfoModel() -{ - reset(); -} - -boost::signals2::connection LLRegionInfoModel::setUpdateCallback(const update_signal_t::slot_type& cb) -{ - return mUpdateSignal.connect(cb); -} - -void LLRegionInfoModel::sendRegionTerrain(const LLUUID& invoice) const -{ - std::string buffer; - std::vector strings; - - // ========================================== - // Assemble and send setregionterrain message - // "setregionterrain" - // strings[0] = float water height - // strings[1] = float terrain raise - // strings[2] = float terrain lower - // strings[3] = 'Y' use estate time - // strings[4] = 'Y' fixed sun - // strings[5] = float sun_hour - // strings[6] = from estate, 'Y' use global time - // strings[7] = from estate, 'Y' fixed sun - // strings[8] = from estate, float sun_hour - - // *NOTE: this resets estate sun info. - bool estate_global_time = true; - bool estate_fixed_sun = false; - F32 estate_sun_hour = 0.f; - - buffer = llformat("%f", mWaterHeight); - strings.push_back(buffer); - buffer = llformat("%f", mTerrainRaiseLimit); - strings.push_back(buffer); - buffer = llformat("%f", mTerrainLowerLimit); - strings.push_back(buffer); - buffer = llformat("%s", (mUseEstateSun ? "Y" : "N")); - strings.push_back(buffer); - buffer = llformat("%s", (getUseFixedSun() ? "Y" : "N")); - strings.push_back(buffer); - buffer = llformat("%f", mSunHour); - strings.push_back(buffer); - buffer = llformat("%s", (estate_global_time ? "Y" : "N") ); - strings.push_back(buffer); - buffer = llformat("%s", (estate_fixed_sun ? "Y" : "N") ); - strings.push_back(buffer); - buffer = llformat("%f", estate_sun_hour); - strings.push_back(buffer); - - sendEstateOwnerMessage(gMessageSystem, "setregionterrain", invoice, strings); -} - -bool LLRegionInfoModel::getUseFixedSun() const -{ - return ((mRegionFlags & REGION_FLAGS_SUN_FIXED) != 0); -} - -void LLRegionInfoModel::setUseFixedSun(bool fixed) -{ - if (fixed) - { - mRegionFlags |= REGION_FLAGS_SUN_FIXED; - } - else - { - mRegionFlags &= ~REGION_FLAGS_SUN_FIXED; - } -} - -void LLRegionInfoModel::update(LLMessageSystem* msg) -{ - reset(); - - msg->getStringFast(_PREHASH_RegionInfo, _PREHASH_SimName, mSimName); - msg->getU32Fast(_PREHASH_RegionInfo, _PREHASH_EstateID, mEstateID); - msg->getU32Fast(_PREHASH_RegionInfo, _PREHASH_ParentEstateID, mParentEstateID); - msg->getU8Fast(_PREHASH_RegionInfo, _PREHASH_SimAccess, mSimAccess); - msg->getU8Fast(_PREHASH_RegionInfo, _PREHASH_MaxAgents, mAgentLimit); - - msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_ObjectBonusFactor, mObjectBonusFactor); - msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_BillableFactor, mBillableFactor); - msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_WaterHeight, mWaterHeight); - msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_TerrainRaiseLimit, mTerrainRaiseLimit); - msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_TerrainLowerLimit, mTerrainLowerLimit); - msg->getS32Fast(_PREHASH_RegionInfo, _PREHASH_PricePerMeter, mPricePerMeter); - msg->getS32Fast(_PREHASH_RegionInfo, _PREHASH_RedirectGridX, mRedirectGridX); - msg->getS32Fast(_PREHASH_RegionInfo, _PREHASH_RedirectGridY, mRedirectGridY); - - msg->getBOOL(_PREHASH_RegionInfo, _PREHASH_UseEstateSun, mUseEstateSun); - - // actually the "last set" sun hour, not the current sun hour. JC - msg->getF32(_PREHASH_RegionInfo, _PREHASH_SunHour, mSunHour); - LL_DEBUGS("WindlightSync") << "Got region sun hour: " << mSunHour << LL_ENDL; - - msg->getS32Fast(_PREHASH_RegionInfo2, _PREHASH_HardMaxAgents, mHardAgentLimit); - - if (msg->has(_PREHASH_RegionInfo3)) - { - msg->getU64Fast(_PREHASH_RegionInfo3, _PREHASH_RegionFlagsExtended, mRegionFlags); - } - else - { - U32 flags = 0; - msg->getU32Fast(_PREHASH_RegionInfo, _PREHASH_RegionFlags, flags); - mRegionFlags = flags; - } - - if (msg->has(_PREHASH_RegionInfo5)) - { - F32 chat_whisper_range; - F32 chat_normal_range; - F32 chat_shout_range; - F32 chat_whisper_offset; - F32 chat_normal_offset; - F32 chat_shout_offset; - U32 chat_flags; - - msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatWhisperRange, chat_whisper_range); - msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatNormalRange, chat_normal_range); - msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatShoutRange, chat_shout_range); - msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatWhisperOffset, chat_whisper_offset); - msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatNormalOffset, chat_normal_offset); - msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatShoutOffset, chat_shout_offset); - msg->getU32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatFlags, chat_flags); - - LL_INFOS() << "Whisper range: " << chat_whisper_range << " normal range: " << chat_normal_range << " shout range: " << chat_shout_range - << " whisper offset: " << chat_whisper_offset << " normal offset: " << chat_normal_offset << " shout offset: " << chat_shout_offset - << " chat flags: " << chat_flags << LL_ENDL; - } - - // the only reasonable way to decide if we actually have any data is to - // check to see if any of these fields have nonzero sizes - if (msg->getSize(_PREHASH_RegionInfo2, _PREHASH_ProductSKU) > 0 || - msg->getSize(_PREHASH_RegionInfo2, "ProductName") > 0) - { - msg->getString(_PREHASH_RegionInfo2, "ProductName", mSimType); - } - - // Let interested parties know that region info has been updated. - mUpdateSignal(); -} - -// static -void LLRegionInfoModel::sendEstateOwnerMessage( - LLMessageSystem* msg, - const std::string& request, - const LLUUID& invoice, - const std::vector& strings) -{ - LLViewerRegion* cur_region = gAgent.getRegion(); - - if (!cur_region) - { - LL_WARNS() << "Agent region not set" << LL_ENDL; - return; - } - - LL_INFOS() << "Sending estate request '" << request << "'" << LL_ENDL; - msg->newMessage("EstateOwnerMessage"); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addUUIDFast(_PREHASH_TransactionID, LLUUID::null); //not used - msg->nextBlock("MethodData"); - msg->addString("Method", request); - msg->addUUID("Invoice", invoice); - - if (strings.empty()) - { - msg->nextBlock("ParamList"); - msg->addString("Parameter", NULL); - } - else - { - std::vector::const_iterator it = strings.begin(); - std::vector::const_iterator end = strings.end(); - for (unsigned i = 0; it != end; ++it, ++i) - { - LL_DEBUGS() << "- [" << i << "] " << (*it) << LL_ENDL; - msg->nextBlock("ParamList"); - msg->addString("Parameter", *it); - } - } - - msg->sendReliable(cur_region->getHost()); -} +/** + * @file llregioninfomodel.cpp + * @brief Region info model + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llregioninfomodel.h" + +// libs +#include "message.h" +#include "llregionflags.h" + +// viewer +#include "llagent.h" +#include "llviewerregion.h" + +void LLRegionInfoModel::reset() +{ + mSimAccess = 0; + mAgentLimit = 0; + mHardAgentLimit = 100; + + mRegionFlags = 0; + mEstateID = 0; + mParentEstateID = 0; + + mPricePerMeter = 0; + mRedirectGridX = 0; + mRedirectGridY = 0; + + mBillableFactor = 0.0f; + mObjectBonusFactor = 0.0f; + mWaterHeight = 0.0f; + mTerrainRaiseLimit = 0.0f; + mTerrainLowerLimit = 0.0f; + mSunHour = 0.0f; + + mUseEstateSun = false; + + mSimType.clear(); + mSimName.clear(); +} + +LLRegionInfoModel::LLRegionInfoModel() +{ + reset(); +} + +boost::signals2::connection LLRegionInfoModel::setUpdateCallback(const update_signal_t::slot_type& cb) +{ + return mUpdateSignal.connect(cb); +} + +void LLRegionInfoModel::sendRegionTerrain(const LLUUID& invoice) const +{ + std::string buffer; + std::vector strings; + + // ========================================== + // Assemble and send setregionterrain message + // "setregionterrain" + // strings[0] = float water height + // strings[1] = float terrain raise + // strings[2] = float terrain lower + // strings[3] = 'Y' use estate time + // strings[4] = 'Y' fixed sun + // strings[5] = float sun_hour + // strings[6] = from estate, 'Y' use global time + // strings[7] = from estate, 'Y' fixed sun + // strings[8] = from estate, float sun_hour + + // *NOTE: this resets estate sun info. + bool estate_global_time = true; + bool estate_fixed_sun = false; + F32 estate_sun_hour = 0.f; + + buffer = llformat("%f", mWaterHeight); + strings.push_back(buffer); + buffer = llformat("%f", mTerrainRaiseLimit); + strings.push_back(buffer); + buffer = llformat("%f", mTerrainLowerLimit); + strings.push_back(buffer); + buffer = llformat("%s", (mUseEstateSun ? "Y" : "N")); + strings.push_back(buffer); + buffer = llformat("%s", (getUseFixedSun() ? "Y" : "N")); + strings.push_back(buffer); + buffer = llformat("%f", mSunHour); + strings.push_back(buffer); + buffer = llformat("%s", (estate_global_time ? "Y" : "N") ); + strings.push_back(buffer); + buffer = llformat("%s", (estate_fixed_sun ? "Y" : "N") ); + strings.push_back(buffer); + buffer = llformat("%f", estate_sun_hour); + strings.push_back(buffer); + + sendEstateOwnerMessage(gMessageSystem, "setregionterrain", invoice, strings); +} + +bool LLRegionInfoModel::getUseFixedSun() const +{ + return ((mRegionFlags & REGION_FLAGS_SUN_FIXED) != 0); +} + +void LLRegionInfoModel::setUseFixedSun(bool fixed) +{ + if (fixed) + { + mRegionFlags |= REGION_FLAGS_SUN_FIXED; + } + else + { + mRegionFlags &= ~REGION_FLAGS_SUN_FIXED; + } +} + +void LLRegionInfoModel::update(LLMessageSystem* msg) +{ + reset(); + + msg->getStringFast(_PREHASH_RegionInfo, _PREHASH_SimName, mSimName); + msg->getU32Fast(_PREHASH_RegionInfo, _PREHASH_EstateID, mEstateID); + msg->getU32Fast(_PREHASH_RegionInfo, _PREHASH_ParentEstateID, mParentEstateID); + msg->getU8Fast(_PREHASH_RegionInfo, _PREHASH_SimAccess, mSimAccess); + msg->getU8Fast(_PREHASH_RegionInfo, _PREHASH_MaxAgents, mAgentLimit); + + msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_ObjectBonusFactor, mObjectBonusFactor); + msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_BillableFactor, mBillableFactor); + msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_WaterHeight, mWaterHeight); + msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_TerrainRaiseLimit, mTerrainRaiseLimit); + msg->getF32Fast(_PREHASH_RegionInfo, _PREHASH_TerrainLowerLimit, mTerrainLowerLimit); + msg->getS32Fast(_PREHASH_RegionInfo, _PREHASH_PricePerMeter, mPricePerMeter); + msg->getS32Fast(_PREHASH_RegionInfo, _PREHASH_RedirectGridX, mRedirectGridX); + msg->getS32Fast(_PREHASH_RegionInfo, _PREHASH_RedirectGridY, mRedirectGridY); + + msg->getBOOL(_PREHASH_RegionInfo, _PREHASH_UseEstateSun, mUseEstateSun); + + // actually the "last set" sun hour, not the current sun hour. JC + msg->getF32(_PREHASH_RegionInfo, _PREHASH_SunHour, mSunHour); + LL_DEBUGS("WindlightSync") << "Got region sun hour: " << mSunHour << LL_ENDL; + + msg->getS32Fast(_PREHASH_RegionInfo2, _PREHASH_HardMaxAgents, mHardAgentLimit); + + if (msg->has(_PREHASH_RegionInfo3)) + { + msg->getU64Fast(_PREHASH_RegionInfo3, _PREHASH_RegionFlagsExtended, mRegionFlags); + } + else + { + U32 flags = 0; + msg->getU32Fast(_PREHASH_RegionInfo, _PREHASH_RegionFlags, flags); + mRegionFlags = flags; + } + + if (msg->has(_PREHASH_RegionInfo5)) + { + F32 chat_whisper_range; + F32 chat_normal_range; + F32 chat_shout_range; + F32 chat_whisper_offset; + F32 chat_normal_offset; + F32 chat_shout_offset; + U32 chat_flags; + + msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatWhisperRange, chat_whisper_range); + msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatNormalRange, chat_normal_range); + msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatShoutRange, chat_shout_range); + msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatWhisperOffset, chat_whisper_offset); + msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatNormalOffset, chat_normal_offset); + msg->getF32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatShoutOffset, chat_shout_offset); + msg->getU32Fast(_PREHASH_RegionInfo5, _PREHASH_ChatFlags, chat_flags); + + LL_INFOS() << "Whisper range: " << chat_whisper_range << " normal range: " << chat_normal_range << " shout range: " << chat_shout_range + << " whisper offset: " << chat_whisper_offset << " normal offset: " << chat_normal_offset << " shout offset: " << chat_shout_offset + << " chat flags: " << chat_flags << LL_ENDL; + } + + // the only reasonable way to decide if we actually have any data is to + // check to see if any of these fields have nonzero sizes + if (msg->getSize(_PREHASH_RegionInfo2, _PREHASH_ProductSKU) > 0 || + msg->getSize(_PREHASH_RegionInfo2, "ProductName") > 0) + { + msg->getString(_PREHASH_RegionInfo2, "ProductName", mSimType); + } + + // Let interested parties know that region info has been updated. + mUpdateSignal(); +} + +// static +void LLRegionInfoModel::sendEstateOwnerMessage( + LLMessageSystem* msg, + const std::string& request, + const LLUUID& invoice, + const std::vector& strings) +{ + LLViewerRegion* cur_region = gAgent.getRegion(); + + if (!cur_region) + { + LL_WARNS() << "Agent region not set" << LL_ENDL; + return; + } + + LL_INFOS() << "Sending estate request '" << request << "'" << LL_ENDL; + msg->newMessage("EstateOwnerMessage"); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addUUIDFast(_PREHASH_TransactionID, LLUUID::null); //not used + msg->nextBlock("MethodData"); + msg->addString("Method", request); + msg->addUUID("Invoice", invoice); + + if (strings.empty()) + { + msg->nextBlock("ParamList"); + msg->addString("Parameter", NULL); + } + else + { + std::vector::const_iterator it = strings.begin(); + std::vector::const_iterator end = strings.end(); + for (unsigned i = 0; it != end; ++it, ++i) + { + LL_DEBUGS() << "- [" << i << "] " << (*it) << LL_ENDL; + msg->nextBlock("ParamList"); + msg->addString("Parameter", *it); + } + } + + msg->sendReliable(cur_region->getHost()); +} diff --git a/indra/newview/llregioninfomodel.h b/indra/newview/llregioninfomodel.h index f4314c0fac..08608d6fd8 100644 --- a/indra/newview/llregioninfomodel.h +++ b/indra/newview/llregioninfomodel.h @@ -1,100 +1,100 @@ -/** - * @file llregioninfomodel.h - * @brief Region info model - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLREGIONINFOMODEL_H -#define LL_LLREGIONINFOMODEL_H - -class LLMessageSystem; - -#include "llsingleton.h" - -/** - * Contains region info, notifies interested parties of its changes. - */ -class LLRegionInfoModel : public LLSingleton -{ - LLSINGLETON(LLRegionInfoModel); - LOG_CLASS(LLRegionInfoModel); - -public: - typedef boost::signals2::signal update_signal_t; - boost::signals2::connection setUpdateCallback(const update_signal_t::slot_type& cb); - - void sendRegionTerrain(const LLUUID& invoice) const; /// upload region terrain data - - bool getUseFixedSun() const; - - void setUseFixedSun(bool fixed); - - // *TODO: Add getters and make the data private. - U8 mSimAccess; - U8 mAgentLimit; - - S32 mHardAgentLimit; - - U64 mRegionFlags; - U32 mEstateID; - U32 mParentEstateID; - - S32 mPricePerMeter; - S32 mRedirectGridX; - S32 mRedirectGridY; - - F32 mBillableFactor; - F32 mObjectBonusFactor; - F32 mWaterHeight; - F32 mTerrainRaiseLimit; - F32 mTerrainLowerLimit; - F32 mSunHour; // 6..30 - - bool mUseEstateSun; - - std::string mSimName; - std::string mSimType; - -protected: - friend class LLViewerRegion; - - - /** - * Refresh model with data from the incoming server message. - */ - void update(LLMessageSystem* msg); - -private: - void reset(); - - // *FIXME: Duplicated code from LLPanelRegionInfo - static void sendEstateOwnerMessage( - LLMessageSystem* msg, - const std::string& request, - const LLUUID& invoice, - const std::vector& strings); - - update_signal_t mUpdateSignal; -}; - -#endif // LL_LLREGIONINFOMODEL_H +/** + * @file llregioninfomodel.h + * @brief Region info model + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLREGIONINFOMODEL_H +#define LL_LLREGIONINFOMODEL_H + +class LLMessageSystem; + +#include "llsingleton.h" + +/** + * Contains region info, notifies interested parties of its changes. + */ +class LLRegionInfoModel : public LLSingleton +{ + LLSINGLETON(LLRegionInfoModel); + LOG_CLASS(LLRegionInfoModel); + +public: + typedef boost::signals2::signal update_signal_t; + boost::signals2::connection setUpdateCallback(const update_signal_t::slot_type& cb); + + void sendRegionTerrain(const LLUUID& invoice) const; /// upload region terrain data + + bool getUseFixedSun() const; + + void setUseFixedSun(bool fixed); + + // *TODO: Add getters and make the data private. + U8 mSimAccess; + U8 mAgentLimit; + + S32 mHardAgentLimit; + + U64 mRegionFlags; + U32 mEstateID; + U32 mParentEstateID; + + S32 mPricePerMeter; + S32 mRedirectGridX; + S32 mRedirectGridY; + + F32 mBillableFactor; + F32 mObjectBonusFactor; + F32 mWaterHeight; + F32 mTerrainRaiseLimit; + F32 mTerrainLowerLimit; + F32 mSunHour; // 6..30 + + bool mUseEstateSun; + + std::string mSimName; + std::string mSimType; + +protected: + friend class LLViewerRegion; + + + /** + * Refresh model with data from the incoming server message. + */ + void update(LLMessageSystem* msg); + +private: + void reset(); + + // *FIXME: Duplicated code from LLPanelRegionInfo + static void sendEstateOwnerMessage( + LLMessageSystem* msg, + const std::string& request, + const LLUUID& invoice, + const std::vector& strings); + + update_signal_t mUpdateSignal; +}; + +#endif // LL_LLREGIONINFOMODEL_H diff --git a/indra/newview/llscenemonitor.cpp b/indra/newview/llscenemonitor.cpp index 5f7adfec90..8e45a60f86 100644 --- a/indra/newview/llscenemonitor.cpp +++ b/indra/newview/llscenemonitor.cpp @@ -1,757 +1,757 @@ -/** - * @file llscenemonitor.cpp - * @brief monitor the scene loading process. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llrendertarget.h" -#include "llscenemonitor.h" -#include "llviewerwindow.h" -#include "llviewerdisplay.h" -#include "llviewercontrol.h" -#include "llviewershadermgr.h" -#include "llui.h" -#include "llstartup.h" -#include "llappviewer.h" -#include "llwindow.h" -#include "llpointer.h" -#include "llspatialpartition.h" -#include "llagent.h" -#include "pipeline.h" -#include "llviewerparcelmgr.h" -#include "llviewerpartsim.h" - -LLSceneMonitorView* gSceneMonitorView = NULL; - -// -//The procedures of monitoring when the scene finishes loading visually, -//i.e., no pixel differences among frames, are: -//1, freeze all dynamic objects and avatars; -//2, (?) disable all sky and water; -//3, capture frames periodically, by calling "capture()"; -//4, compute pixel differences between two latest captured frames, by calling "compare()", results are stored at mDiff; -//5, compute the number of pixels in mDiff above some tolerance threshold in GPU, by calling "calcDiffAggregate()"; -//6, use gl occlusion query to fetch the result from GPU, by calling "fetchQueryResult()"; -//END. -// - -LLSceneMonitor::LLSceneMonitor() : - mEnabled(false), - mDiff(NULL), - mDiffResult(0.f), - mDiffTolerance(0.1f), - mDiffState(WAITING_FOR_NEXT_DIFF), - mDebugViewerVisible(false), - mQueryObject(0), - mDiffPixelRatio(0.5f) -{ - mFrames[0] = NULL; - mFrames[1] = NULL; -} - -LLSceneMonitor::~LLSceneMonitor() -{ - mDiffState = VIEWER_QUITTING; - reset(); - - mDitheringTexture = NULL; -} - -void LLSceneMonitor::reset() -{ - delete mFrames[0]; - delete mFrames[1]; - delete mDiff; - - mFrames[0] = NULL; - mFrames[1] = NULL; - mDiff = NULL; - - mMonitorRecording.reset(); - mSceneLoadRecording.reset(); - mRecordingTimer.reset(); - - unfreezeScene(); - - if(mQueryObject > 0) - { - LLOcclusionCullingGroup::releaseOcclusionQueryObjectName(mQueryObject); - mQueryObject = 0; - } -} - -void LLSceneMonitor::generateDitheringTexture(S32 width, S32 height) -{ -#if 1 - //4 * 4 matrix - mDitherMatrixWidth = 4; - S32 dither_matrix[4][4] = - { - {1, 9, 3, 11}, - {13, 5, 15, 7}, - {4, 12, 2, 10}, - {16, 8, 14, 6} - }; - - mDitherScale = 255.f / 17; -#else - //8 * 8 matrix - mDitherMatrixWidth = 16; - S32 dither_matrix[16][16] = - { - {1, 49, 13, 61, 4, 52, 16, 64, 1, 49, 13, 61, 4, 52, 16, 64}, - {33, 17, 45, 29, 36, 20, 48, 32, 33, 17, 45, 29, 36, 20, 48, 32}, - {9, 57, 5, 53, 12, 60, 8, 56, 9, 57, 5, 53, 12, 60, 8, 56}, - {41, 25, 37, 21, 44, 28, 40, 24, 41, 25, 37, 21, 44, 28, 40, 24}, - {3, 51, 15, 63, 2, 50, 14, 62, 3, 51, 15, 63, 2, 50, 14, 62}, - {35, 19, 47, 31, 34, 18, 46, 30, 35, 19, 47, 31, 34, 18, 46, 30}, - {11, 59, 7, 55, 10, 58, 6, 54, 11, 59, 7, 55, 10, 58, 6, 54}, - {43, 27, 39, 23, 42, 26, 38, 22, 43, 27, 39, 23, 42, 26, 38, 22}, - {1, 49, 13, 61, 4, 52, 16, 64, 1, 49, 13, 61, 4, 52, 16, 64}, - {33, 17, 45, 29, 36, 20, 48, 32, 33, 17, 45, 29, 36, 20, 48, 32}, - {9, 57, 5, 53, 12, 60, 8, 56, 9, 57, 5, 53, 12, 60, 8, 56}, - {41, 25, 37, 21, 44, 28, 40, 24, 41, 25, 37, 21, 44, 28, 40, 24}, - {3, 51, 15, 63, 2, 50, 14, 62, 3, 51, 15, 63, 2, 50, 14, 62}, - {35, 19, 47, 31, 34, 18, 46, 30, 35, 19, 47, 31, 34, 18, 46, 30}, - {11, 59, 7, 55, 10, 58, 6, 54, 11, 59, 7, 55, 10, 58, 6, 54}, - {43, 27, 39, 23, 42, 26, 38, 22, 43, 27, 39, 23, 42, 26, 38, 22} - }; - - mDitherScale = 255.f / 65; -#endif - - LLPointer image_raw = new LLImageRaw(mDitherMatrixWidth, mDitherMatrixWidth, 3); - U8* data = image_raw->getData(); - for (S32 i = 0; i < mDitherMatrixWidth; i++) - { - for (S32 j = 0; j < mDitherMatrixWidth; j++) - { - U8 val = dither_matrix[i][j]; - *data++ = val; - *data++ = val; - *data++ = val; - } - } - - mDitheringTexture = LLViewerTextureManager::getLocalTexture(image_raw.get(), false) ; - mDitheringTexture->setAddressMode(LLTexUnit::TAM_WRAP); - mDitheringTexture->setFilteringOption(LLTexUnit::TFO_POINT); - - mDitherScaleS = (F32)width / mDitherMatrixWidth; - mDitherScaleT = (F32)height / mDitherMatrixWidth; -} - -void LLSceneMonitor::setDebugViewerVisible(bool visible) -{ - mDebugViewerVisible = visible; -} - -LLRenderTarget& LLSceneMonitor::getCaptureTarget() -{ - LLRenderTarget* cur_target = NULL; - - S32 width = gViewerWindow->getWorldViewWidthRaw(); - S32 height = gViewerWindow->getWorldViewHeightRaw(); - - if(!mFrames[0]) - { - mFrames[0] = new LLRenderTarget(); - mFrames[0]->allocate(width, height, GL_RGB); - gGL.getTexUnit(0)->bind(mFrames[0]); - gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT); - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - cur_target = mFrames[0]; - } - else if(!mFrames[1]) - { - mFrames[1] = new LLRenderTarget(); - mFrames[1]->allocate(width, height, GL_RGB); - gGL.getTexUnit(0)->bind(mFrames[1]); - gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT); - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - cur_target = mFrames[1]; - } - else //swap - { - cur_target = mFrames[0]; - mFrames[0] = mFrames[1]; - mFrames[1] = cur_target; - } - - if(cur_target->getWidth() != width || cur_target->getHeight() != height) //size changed - { - cur_target->resize(width, height); - } - - // we're promising the target exists - return *cur_target; -} - -void LLSceneMonitor::freezeAvatar(LLCharacter* avatarp) -{ - if(mEnabled) - { - mAvatarPauseHandles.push_back(avatarp->requestPause()); - } -} - -void LLSceneMonitor::freezeScene() -{ - if(!mEnabled) - { - return; - } - - //freeze all avatars - for (std::vector::iterator iter = LLCharacter::sInstances.begin(); - iter != LLCharacter::sInstances.end(); ++iter) - { - freezeAvatar((LLCharacter*)(*iter)); - } - - // freeze everything else - gSavedSettings.setBOOL("FreezeTime", true); - - //disable sky, water and clouds - gPipeline.clearRenderTypeMask(LLPipeline::RENDER_TYPE_SKY, LLPipeline::RENDER_TYPE_WL_SKY, - LLPipeline::RENDER_TYPE_WATER, LLPipeline::RENDER_TYPE_CLOUDS, LLPipeline::END_RENDER_TYPES); - - //disable particle system - LLViewerPartSim::getInstance()->enable(false); -} - -void LLSceneMonitor::unfreezeScene() -{ - //thaw all avatars - mAvatarPauseHandles.clear(); - - if(mDiffState == VIEWER_QUITTING) - { - return; - } - - // thaw everything else - gSavedSettings.setBOOL("FreezeTime", false); - - //enable sky, water and clouds - gPipeline.setRenderTypeMask(LLPipeline::RENDER_TYPE_SKY, LLPipeline::RENDER_TYPE_WL_SKY, - LLPipeline::RENDER_TYPE_WATER, LLPipeline::RENDER_TYPE_CLOUDS, LLPipeline::END_RENDER_TYPES); - - //enable particle system - LLViewerPartSim::getInstance()->enable(true); -} - -void LLSceneMonitor::capture() -{ - static U32 last_capture_frame = 0; - static LLCachedControl monitor_enabled(gSavedSettings, "SceneLoadingMonitorEnabled"); - static LLCachedControl scene_load_sample_time(gSavedSettings, "SceneLoadingMonitorSampleTime"); - static bool force_capture = true; - - bool enabled = monitor_enabled || mDebugViewerVisible; - if(mEnabled != enabled) - { - if(mEnabled) - { - mEnabled = enabled; - unfreezeScene(); - reset(); - force_capture = true; - } - else - { - mEnabled = enabled; - reset(); - freezeScene(); - } - } - - if (mEnabled - && (mMonitorRecording.getSum(*LLViewerCamera::getVelocityStat()) > 0.1f - || mMonitorRecording.getSum(*LLViewerCamera::getAngularVelocityStat()) > 0.05f)) - { - reset(); - freezeScene(); - force_capture = true; - } - - if(mEnabled - && (mRecordingTimer.getElapsedTimeF32() > scene_load_sample_time() - || force_capture) - && last_capture_frame != gFrameCount) - { - force_capture = false; - - mSceneLoadRecording.resume(); - mMonitorRecording.resume(); - - last_capture_frame = gFrameCount; - - LLRenderTarget& cur_target = getCaptureTarget(); - - U32 old_FBO = LLRenderTarget::sCurFBO; - - gGL.getTexUnit(0)->bind(&cur_target); - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); //point to the main frame buffer. - - glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, cur_target.getWidth(), cur_target.getHeight()); //copy the content - - glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glBindFramebuffer(GL_FRAMEBUFFER, old_FBO); - - mDiffState = NEED_DIFF; - } -} - -bool LLSceneMonitor::needsUpdate() const -{ - return mDiffState == NEED_DIFF; -} - -static LLStaticHashedString sDitherScale("dither_scale"); -static LLStaticHashedString sDitherScaleS("dither_scale_s"); -static LLStaticHashedString sDitherScaleT("dither_scale_t"); - -void LLSceneMonitor::compare() -{ -#ifdef LL_WINDOWS - if(mDiffState != NEED_DIFF) - { - return; - } - - if(!mFrames[0] || !mFrames[1]) - { - return; - } - if(mFrames[0]->getWidth() != mFrames[1]->getWidth() || mFrames[0]->getHeight() != mFrames[1]->getHeight()) - { //size does not match - return; - } - - mDiffState = EXECUTE_DIFF; - - S32 width = gViewerWindow->getWindowWidthRaw(); - S32 height = gViewerWindow->getWindowHeightRaw(); - if(!mDiff) - { - mDiff = new LLRenderTarget(); - mDiff->allocate(width, height, GL_RGBA); - - generateDitheringTexture(width, height); - } - else if(mDiff->getWidth() != width || mDiff->getHeight() != height) - { - mDiff->resize(width, height); - generateDitheringTexture(width, height); - } - - mDiff->bindTarget(); - mDiff->clear(); - - gTwoTextureCompareProgram.bind(); - - gTwoTextureCompareProgram.uniform1f(sDitherScale, mDitherScale); - gTwoTextureCompareProgram.uniform1f(sDitherScaleS, mDitherScaleS); - gTwoTextureCompareProgram.uniform1f(sDitherScaleT, mDitherScaleT); - - gGL.getTexUnit(0)->activate(); - gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE); - gGL.getTexUnit(0)->bind(mFrames[0]); - gGL.getTexUnit(0)->activate(); - - gGL.getTexUnit(1)->activate(); - gGL.getTexUnit(1)->enable(LLTexUnit::TT_TEXTURE); - gGL.getTexUnit(1)->bind(mFrames[1]); - gGL.getTexUnit(1)->activate(); - - gGL.getTexUnit(2)->activate(); - gGL.getTexUnit(2)->enable(LLTexUnit::TT_TEXTURE); - gGL.getTexUnit(2)->bind(mDitheringTexture); - gGL.getTexUnit(2)->activate(); - - gl_rect_2d_simple_tex(width, height); - - mDiff->flush(); - - gTwoTextureCompareProgram.unbind(); - - gGL.getTexUnit(0)->disable(); - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.getTexUnit(1)->disable(); - gGL.getTexUnit(1)->unbind(LLTexUnit::TT_TEXTURE); - gGL.getTexUnit(2)->disable(); - gGL.getTexUnit(2)->unbind(LLTexUnit::TT_TEXTURE); - - if (!mDebugViewerVisible) - { - calcDiffAggregate(); - } -#endif -} - -static LLStaticHashedString sTolerance("tolerance"); - -//calculate Diff aggregate information in GPU, and enable gl occlusion query to capture it. -void LLSceneMonitor::calcDiffAggregate() -{ -#ifdef LL_WINDOWS - - if(mDiffState != EXECUTE_DIFF && !mDebugViewerVisible) - { - return; - } - - if(!mQueryObject) - { - mQueryObject = LLOcclusionCullingGroup::getNewOcclusionQueryObjectName(); - } - - LLGLDepthTest depth(true, false, GL_ALWAYS); - if(!mDebugViewerVisible) - { - glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); - } - - LLGLSLShader* cur_shader = NULL; - - cur_shader = LLGLSLShader::sCurBoundShaderPtr; - gOneTextureFilterProgram.bind(); - gOneTextureFilterProgram.uniform1f(sTolerance, mDiffTolerance); - - if(mDiffState == EXECUTE_DIFF) - { - glBeginQuery(GL_SAMPLES_PASSED, mQueryObject); - } - - gl_draw_scaled_target(0, 0, S32(mDiff->getWidth() * mDiffPixelRatio), S32(mDiff->getHeight() * mDiffPixelRatio), mDiff); - - if(mDiffState == EXECUTE_DIFF) - { - glEndQuery(GL_SAMPLES_PASSED); - mDiffState = WAIT_ON_RESULT; - } - - gOneTextureFilterProgram.unbind(); - - if(cur_shader != NULL) - { - cur_shader->bind(); - } - - if(!mDebugViewerVisible) - { - glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - } -#endif -} - -static LLTrace::EventStatHandle<> sFramePixelDiff("FramePixelDifference"); -void LLSceneMonitor::fetchQueryResult() -{ - // also throttle timing here, to avoid going below sample time due to phasing with frame capture - static LLCachedControl scene_load_sample_time_control(gSavedSettings, "SceneLoadingMonitorSampleTime"); - F32Seconds scene_load_sample_time = (F32Seconds)scene_load_sample_time_control(); - - if(mDiffState == WAIT_ON_RESULT - && !LLAppViewer::instance()->quitRequested()) - { - mDiffState = WAITING_FOR_NEXT_DIFF; - - GLuint available = 0; - glGetQueryObjectuiv(mQueryObject, GL_QUERY_RESULT_AVAILABLE, &available); - if(available) - { - GLuint count = 0; - glGetQueryObjectuiv(mQueryObject, GL_QUERY_RESULT, &count); - - mDiffResult = sqrtf(count * 0.5f / (mDiff->getWidth() * mDiff->getHeight() * mDiffPixelRatio * mDiffPixelRatio)); //0.5 -> (front face + back face) - - LL_DEBUGS("SceneMonitor") << "Frame difference: " << mDiffResult << LL_ENDL; - record(sFramePixelDiff, mDiffResult); - - static LLCachedControl diff_threshold(gSavedSettings,"SceneLoadingMonitorPixelDiffThreshold"); - F32Seconds elapsed_time = mRecordingTimer.getElapsedTimeF32(); - - if (elapsed_time > scene_load_sample_time) - { - if(mDiffResult > diff_threshold()) - { - mSceneLoadRecording.extend(); - llassert_always(mSceneLoadRecording.getResults().getLastRecording().getDuration() > scene_load_sample_time); - } - else - { - mSceneLoadRecording.nextPeriod(); - } - mRecordingTimer.reset(); - } - } - } -} - -//dump results to a file _scene_xmonitor_results.csv -void LLSceneMonitor::dumpToFile(const std::string &file_name) -{ - if (!hasResults()) return; - - LL_INFOS("SceneMonitor") << "Saving scene load stats to " << file_name << LL_ENDL; - try - { - llofstream os(file_name.c_str()); - - os << std::setprecision(10); - - LLTrace::PeriodicRecording& scene_load_recording = mSceneLoadRecording.getResults(); - const U32 frame_count = scene_load_recording.getNumRecordedPeriods(); - - F64Seconds frame_time; - - os << "Stat"; - for (S32 frame = 1; frame <= frame_count; frame++) - { - frame_time += scene_load_recording.getPrevRecording(frame_count - frame).getDuration(); - os << ", " << frame_time.value(); - } - os << '\n'; - - os << "Sample period(s)"; - for (S32 frame = 1; frame <= frame_count; frame++) - { - frame_time = scene_load_recording.getPrevRecording(frame_count - frame).getDuration(); - os << ", " << frame_time.value(); - } - os << '\n'; - - - typedef LLTrace::StatType trace_count; - for (auto& it : trace_count::instance_snapshot()) - { - std::ostringstream row; - row << std::setprecision(10); - - row << it.getName(); - - const char* unit_label = it.getUnitLabel(); - if (unit_label[0]) - { - row << "(" << unit_label << ")"; - } - - S32 samples = 0; - - for (S32 frame = 1; frame <= frame_count; frame++) - { - LLTrace::Recording& recording = scene_load_recording.getPrevRecording(frame_count - frame); - samples += recording.getSampleCount(it); - row << ", " << recording.getSum(it); - } - - row << '\n'; - - if (samples > 0) - { - os << row.str(); - } - } - - typedef LLTrace::StatType trace_event; - - for (auto& it : trace_event::instance_snapshot()) - { - std::ostringstream row; - row << std::setprecision(10); - row << it.getName(); - - const char* unit_label = it.getUnitLabel(); - if (unit_label[0]) - { - row << "(" << unit_label << ")"; - } - - S32 samples = 0; - - for (S32 frame = 1; frame <= frame_count; frame++) - { - LLTrace::Recording& recording = scene_load_recording.getPrevRecording(frame_count - frame); - samples += recording.getSampleCount(it); - F64 mean = recording.getMean(it); - if (llisnan(mean)) - { - row << ", n/a"; - } - else - { - row << ", " << mean; - } - } - - row << '\n'; - - if (samples > 0) - { - os << row.str(); - } - } - - typedef LLTrace::StatType trace_sample; - - for (auto& it : trace_sample::instance_snapshot()) - { - std::ostringstream row; - row << std::setprecision(10); - row << it.getName(); - - const char* unit_label = it.getUnitLabel(); - if (unit_label[0]) - { - row << "(" << unit_label << ")"; - } - - S32 samples = 0; - - for (S32 frame = 1; frame <= frame_count; frame++) - { - LLTrace::Recording& recording = scene_load_recording.getPrevRecording(frame_count - frame); - samples += recording.getSampleCount(it); - F64 mean = recording.getMean(it); - if (llisnan(mean)) - { - row << ", n/a"; - } - else - { - row << ", " << mean; - } - } - - row << '\n'; - - if (samples > 0) - { - os << row.str(); - } - } - - os.flush(); - os.close(); - } - catch (const std::ios_base::failure &e) - { - LL_WARNS() << "Unable to dump scene monitor results: " << e.what() << LL_ENDL; - } -} - -//------------------------------------------------------------------------------------------------------------- -//definition of class LLSceneMonitorView -//------------------------------------------------------------------------------------------------------------- -LLSceneMonitorView::LLSceneMonitorView(const LLRect& rect) - : LLFloater(LLSD()) -{ - setRect(rect); - setVisible(false); - - setCanMinimize(false); - setCanClose(true); - - sTeleportFinishConnection = LLViewerParcelMgr::getInstance()->setTeleportFinishedCallback(boost::bind(&LLSceneMonitorView::onTeleportFinished, this)); -} - -LLSceneMonitorView::~LLSceneMonitorView() -{ - sTeleportFinishConnection.disconnect(); -} - -void LLSceneMonitorView::onClose(bool app_quitting) -{ - setVisible(false); -} - -void LLSceneMonitorView::onClickCloseBtn(bool app_quitting) -{ - setVisible(false); -} - -void LLSceneMonitorView::onTeleportFinished() -{ - if(isInVisibleChain()) - { - LLSceneMonitor::getInstance()->reset(); - } -} - -void LLSceneMonitorView::onVisibilityChange(bool visible) -{ - LLSceneMonitor::getInstance()->setDebugViewerVisible(visible); -} - -void LLSceneMonitorView::draw() -{ - const LLRenderTarget* target = LLSceneMonitor::getInstance()->getDiffTarget(); - if(!target) - { - return; - } - - F32 ratio = LLSceneMonitor::getInstance()->getDiffPixelRatio(); - S32 height = (S32)(target->getHeight() * ratio); - S32 width = (S32)(target->getWidth() * ratio); - - LLRect new_rect; - new_rect.setLeftTopAndSize(getRect().mLeft, getRect().mTop, width, height); - setRect(new_rect); - - //draw background - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gl_rect_2d(0, getRect().getHeight(), getRect().getWidth(), 0, LLColor4(0.f, 0.f, 0.f, 0.25f)); - - LLSceneMonitor::getInstance()->calcDiffAggregate(); - - //show some texts - LLColor4 color = LLColor4::white; - S32 line_height = LLFontGL::getFontMonospace()->getLineHeight(); - - S32 lines = 0; - std::string num_str = llformat("Frame difference: %.6f", LLSceneMonitor::getInstance()->getDiffResult()); - LLFontGL::getFontMonospace()->renderUTF8(num_str, 0, 5, getRect().getHeight() - line_height * lines, color, LLFontGL::LEFT, LLFontGL::TOP); - lines++; - - num_str = llformat("Pixel tolerance: (R+G+B) < %.4f", LLSceneMonitor::getInstance()->getDiffTolerance()); - LLFontGL::getFontMonospace()->renderUTF8(num_str, 0, 5, getRect().getHeight() - line_height * lines, color, LLFontGL::LEFT, LLFontGL::TOP); - lines++; - - num_str = llformat("Sampling time: %.3f seconds", gSavedSettings.getF32("SceneLoadingMonitorSampleTime")); - LLFontGL::getFontMonospace()->renderUTF8(num_str, 0, 5, getRect().getHeight() - line_height * lines, color, LLFontGL::LEFT, LLFontGL::TOP); - lines++; - - num_str = llformat("Scene Loading time: %.3f seconds", (F32)LLSceneMonitor::getInstance()->getRecording()->getResults().getDuration().value()); - LLFontGL::getFontMonospace()->renderUTF8(num_str, 0, 5, getRect().getHeight() - line_height * lines, color, LLFontGL::LEFT, LLFontGL::TOP); - lines++; - - LLView::draw(); -} - +/** + * @file llscenemonitor.cpp + * @brief monitor the scene loading process. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llrendertarget.h" +#include "llscenemonitor.h" +#include "llviewerwindow.h" +#include "llviewerdisplay.h" +#include "llviewercontrol.h" +#include "llviewershadermgr.h" +#include "llui.h" +#include "llstartup.h" +#include "llappviewer.h" +#include "llwindow.h" +#include "llpointer.h" +#include "llspatialpartition.h" +#include "llagent.h" +#include "pipeline.h" +#include "llviewerparcelmgr.h" +#include "llviewerpartsim.h" + +LLSceneMonitorView* gSceneMonitorView = NULL; + +// +//The procedures of monitoring when the scene finishes loading visually, +//i.e., no pixel differences among frames, are: +//1, freeze all dynamic objects and avatars; +//2, (?) disable all sky and water; +//3, capture frames periodically, by calling "capture()"; +//4, compute pixel differences between two latest captured frames, by calling "compare()", results are stored at mDiff; +//5, compute the number of pixels in mDiff above some tolerance threshold in GPU, by calling "calcDiffAggregate()"; +//6, use gl occlusion query to fetch the result from GPU, by calling "fetchQueryResult()"; +//END. +// + +LLSceneMonitor::LLSceneMonitor() : + mEnabled(false), + mDiff(NULL), + mDiffResult(0.f), + mDiffTolerance(0.1f), + mDiffState(WAITING_FOR_NEXT_DIFF), + mDebugViewerVisible(false), + mQueryObject(0), + mDiffPixelRatio(0.5f) +{ + mFrames[0] = NULL; + mFrames[1] = NULL; +} + +LLSceneMonitor::~LLSceneMonitor() +{ + mDiffState = VIEWER_QUITTING; + reset(); + + mDitheringTexture = NULL; +} + +void LLSceneMonitor::reset() +{ + delete mFrames[0]; + delete mFrames[1]; + delete mDiff; + + mFrames[0] = NULL; + mFrames[1] = NULL; + mDiff = NULL; + + mMonitorRecording.reset(); + mSceneLoadRecording.reset(); + mRecordingTimer.reset(); + + unfreezeScene(); + + if(mQueryObject > 0) + { + LLOcclusionCullingGroup::releaseOcclusionQueryObjectName(mQueryObject); + mQueryObject = 0; + } +} + +void LLSceneMonitor::generateDitheringTexture(S32 width, S32 height) +{ +#if 1 + //4 * 4 matrix + mDitherMatrixWidth = 4; + S32 dither_matrix[4][4] = + { + {1, 9, 3, 11}, + {13, 5, 15, 7}, + {4, 12, 2, 10}, + {16, 8, 14, 6} + }; + + mDitherScale = 255.f / 17; +#else + //8 * 8 matrix + mDitherMatrixWidth = 16; + S32 dither_matrix[16][16] = + { + {1, 49, 13, 61, 4, 52, 16, 64, 1, 49, 13, 61, 4, 52, 16, 64}, + {33, 17, 45, 29, 36, 20, 48, 32, 33, 17, 45, 29, 36, 20, 48, 32}, + {9, 57, 5, 53, 12, 60, 8, 56, 9, 57, 5, 53, 12, 60, 8, 56}, + {41, 25, 37, 21, 44, 28, 40, 24, 41, 25, 37, 21, 44, 28, 40, 24}, + {3, 51, 15, 63, 2, 50, 14, 62, 3, 51, 15, 63, 2, 50, 14, 62}, + {35, 19, 47, 31, 34, 18, 46, 30, 35, 19, 47, 31, 34, 18, 46, 30}, + {11, 59, 7, 55, 10, 58, 6, 54, 11, 59, 7, 55, 10, 58, 6, 54}, + {43, 27, 39, 23, 42, 26, 38, 22, 43, 27, 39, 23, 42, 26, 38, 22}, + {1, 49, 13, 61, 4, 52, 16, 64, 1, 49, 13, 61, 4, 52, 16, 64}, + {33, 17, 45, 29, 36, 20, 48, 32, 33, 17, 45, 29, 36, 20, 48, 32}, + {9, 57, 5, 53, 12, 60, 8, 56, 9, 57, 5, 53, 12, 60, 8, 56}, + {41, 25, 37, 21, 44, 28, 40, 24, 41, 25, 37, 21, 44, 28, 40, 24}, + {3, 51, 15, 63, 2, 50, 14, 62, 3, 51, 15, 63, 2, 50, 14, 62}, + {35, 19, 47, 31, 34, 18, 46, 30, 35, 19, 47, 31, 34, 18, 46, 30}, + {11, 59, 7, 55, 10, 58, 6, 54, 11, 59, 7, 55, 10, 58, 6, 54}, + {43, 27, 39, 23, 42, 26, 38, 22, 43, 27, 39, 23, 42, 26, 38, 22} + }; + + mDitherScale = 255.f / 65; +#endif + + LLPointer image_raw = new LLImageRaw(mDitherMatrixWidth, mDitherMatrixWidth, 3); + U8* data = image_raw->getData(); + for (S32 i = 0; i < mDitherMatrixWidth; i++) + { + for (S32 j = 0; j < mDitherMatrixWidth; j++) + { + U8 val = dither_matrix[i][j]; + *data++ = val; + *data++ = val; + *data++ = val; + } + } + + mDitheringTexture = LLViewerTextureManager::getLocalTexture(image_raw.get(), false) ; + mDitheringTexture->setAddressMode(LLTexUnit::TAM_WRAP); + mDitheringTexture->setFilteringOption(LLTexUnit::TFO_POINT); + + mDitherScaleS = (F32)width / mDitherMatrixWidth; + mDitherScaleT = (F32)height / mDitherMatrixWidth; +} + +void LLSceneMonitor::setDebugViewerVisible(bool visible) +{ + mDebugViewerVisible = visible; +} + +LLRenderTarget& LLSceneMonitor::getCaptureTarget() +{ + LLRenderTarget* cur_target = NULL; + + S32 width = gViewerWindow->getWorldViewWidthRaw(); + S32 height = gViewerWindow->getWorldViewHeightRaw(); + + if(!mFrames[0]) + { + mFrames[0] = new LLRenderTarget(); + mFrames[0]->allocate(width, height, GL_RGB); + gGL.getTexUnit(0)->bind(mFrames[0]); + gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + cur_target = mFrames[0]; + } + else if(!mFrames[1]) + { + mFrames[1] = new LLRenderTarget(); + mFrames[1]->allocate(width, height, GL_RGB); + gGL.getTexUnit(0)->bind(mFrames[1]); + gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + cur_target = mFrames[1]; + } + else //swap + { + cur_target = mFrames[0]; + mFrames[0] = mFrames[1]; + mFrames[1] = cur_target; + } + + if(cur_target->getWidth() != width || cur_target->getHeight() != height) //size changed + { + cur_target->resize(width, height); + } + + // we're promising the target exists + return *cur_target; +} + +void LLSceneMonitor::freezeAvatar(LLCharacter* avatarp) +{ + if(mEnabled) + { + mAvatarPauseHandles.push_back(avatarp->requestPause()); + } +} + +void LLSceneMonitor::freezeScene() +{ + if(!mEnabled) + { + return; + } + + //freeze all avatars + for (std::vector::iterator iter = LLCharacter::sInstances.begin(); + iter != LLCharacter::sInstances.end(); ++iter) + { + freezeAvatar((LLCharacter*)(*iter)); + } + + // freeze everything else + gSavedSettings.setBOOL("FreezeTime", true); + + //disable sky, water and clouds + gPipeline.clearRenderTypeMask(LLPipeline::RENDER_TYPE_SKY, LLPipeline::RENDER_TYPE_WL_SKY, + LLPipeline::RENDER_TYPE_WATER, LLPipeline::RENDER_TYPE_CLOUDS, LLPipeline::END_RENDER_TYPES); + + //disable particle system + LLViewerPartSim::getInstance()->enable(false); +} + +void LLSceneMonitor::unfreezeScene() +{ + //thaw all avatars + mAvatarPauseHandles.clear(); + + if(mDiffState == VIEWER_QUITTING) + { + return; + } + + // thaw everything else + gSavedSettings.setBOOL("FreezeTime", false); + + //enable sky, water and clouds + gPipeline.setRenderTypeMask(LLPipeline::RENDER_TYPE_SKY, LLPipeline::RENDER_TYPE_WL_SKY, + LLPipeline::RENDER_TYPE_WATER, LLPipeline::RENDER_TYPE_CLOUDS, LLPipeline::END_RENDER_TYPES); + + //enable particle system + LLViewerPartSim::getInstance()->enable(true); +} + +void LLSceneMonitor::capture() +{ + static U32 last_capture_frame = 0; + static LLCachedControl monitor_enabled(gSavedSettings, "SceneLoadingMonitorEnabled"); + static LLCachedControl scene_load_sample_time(gSavedSettings, "SceneLoadingMonitorSampleTime"); + static bool force_capture = true; + + bool enabled = monitor_enabled || mDebugViewerVisible; + if(mEnabled != enabled) + { + if(mEnabled) + { + mEnabled = enabled; + unfreezeScene(); + reset(); + force_capture = true; + } + else + { + mEnabled = enabled; + reset(); + freezeScene(); + } + } + + if (mEnabled + && (mMonitorRecording.getSum(*LLViewerCamera::getVelocityStat()) > 0.1f + || mMonitorRecording.getSum(*LLViewerCamera::getAngularVelocityStat()) > 0.05f)) + { + reset(); + freezeScene(); + force_capture = true; + } + + if(mEnabled + && (mRecordingTimer.getElapsedTimeF32() > scene_load_sample_time() + || force_capture) + && last_capture_frame != gFrameCount) + { + force_capture = false; + + mSceneLoadRecording.resume(); + mMonitorRecording.resume(); + + last_capture_frame = gFrameCount; + + LLRenderTarget& cur_target = getCaptureTarget(); + + U32 old_FBO = LLRenderTarget::sCurFBO; + + gGL.getTexUnit(0)->bind(&cur_target); + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); //point to the main frame buffer. + + glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, cur_target.getWidth(), cur_target.getHeight()); //copy the content + + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + glBindFramebuffer(GL_FRAMEBUFFER, old_FBO); + + mDiffState = NEED_DIFF; + } +} + +bool LLSceneMonitor::needsUpdate() const +{ + return mDiffState == NEED_DIFF; +} + +static LLStaticHashedString sDitherScale("dither_scale"); +static LLStaticHashedString sDitherScaleS("dither_scale_s"); +static LLStaticHashedString sDitherScaleT("dither_scale_t"); + +void LLSceneMonitor::compare() +{ +#ifdef LL_WINDOWS + if(mDiffState != NEED_DIFF) + { + return; + } + + if(!mFrames[0] || !mFrames[1]) + { + return; + } + if(mFrames[0]->getWidth() != mFrames[1]->getWidth() || mFrames[0]->getHeight() != mFrames[1]->getHeight()) + { //size does not match + return; + } + + mDiffState = EXECUTE_DIFF; + + S32 width = gViewerWindow->getWindowWidthRaw(); + S32 height = gViewerWindow->getWindowHeightRaw(); + if(!mDiff) + { + mDiff = new LLRenderTarget(); + mDiff->allocate(width, height, GL_RGBA); + + generateDitheringTexture(width, height); + } + else if(mDiff->getWidth() != width || mDiff->getHeight() != height) + { + mDiff->resize(width, height); + generateDitheringTexture(width, height); + } + + mDiff->bindTarget(); + mDiff->clear(); + + gTwoTextureCompareProgram.bind(); + + gTwoTextureCompareProgram.uniform1f(sDitherScale, mDitherScale); + gTwoTextureCompareProgram.uniform1f(sDitherScaleS, mDitherScaleS); + gTwoTextureCompareProgram.uniform1f(sDitherScaleT, mDitherScaleT); + + gGL.getTexUnit(0)->activate(); + gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE); + gGL.getTexUnit(0)->bind(mFrames[0]); + gGL.getTexUnit(0)->activate(); + + gGL.getTexUnit(1)->activate(); + gGL.getTexUnit(1)->enable(LLTexUnit::TT_TEXTURE); + gGL.getTexUnit(1)->bind(mFrames[1]); + gGL.getTexUnit(1)->activate(); + + gGL.getTexUnit(2)->activate(); + gGL.getTexUnit(2)->enable(LLTexUnit::TT_TEXTURE); + gGL.getTexUnit(2)->bind(mDitheringTexture); + gGL.getTexUnit(2)->activate(); + + gl_rect_2d_simple_tex(width, height); + + mDiff->flush(); + + gTwoTextureCompareProgram.unbind(); + + gGL.getTexUnit(0)->disable(); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gGL.getTexUnit(1)->disable(); + gGL.getTexUnit(1)->unbind(LLTexUnit::TT_TEXTURE); + gGL.getTexUnit(2)->disable(); + gGL.getTexUnit(2)->unbind(LLTexUnit::TT_TEXTURE); + + if (!mDebugViewerVisible) + { + calcDiffAggregate(); + } +#endif +} + +static LLStaticHashedString sTolerance("tolerance"); + +//calculate Diff aggregate information in GPU, and enable gl occlusion query to capture it. +void LLSceneMonitor::calcDiffAggregate() +{ +#ifdef LL_WINDOWS + + if(mDiffState != EXECUTE_DIFF && !mDebugViewerVisible) + { + return; + } + + if(!mQueryObject) + { + mQueryObject = LLOcclusionCullingGroup::getNewOcclusionQueryObjectName(); + } + + LLGLDepthTest depth(true, false, GL_ALWAYS); + if(!mDebugViewerVisible) + { + glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); + } + + LLGLSLShader* cur_shader = NULL; + + cur_shader = LLGLSLShader::sCurBoundShaderPtr; + gOneTextureFilterProgram.bind(); + gOneTextureFilterProgram.uniform1f(sTolerance, mDiffTolerance); + + if(mDiffState == EXECUTE_DIFF) + { + glBeginQuery(GL_SAMPLES_PASSED, mQueryObject); + } + + gl_draw_scaled_target(0, 0, S32(mDiff->getWidth() * mDiffPixelRatio), S32(mDiff->getHeight() * mDiffPixelRatio), mDiff); + + if(mDiffState == EXECUTE_DIFF) + { + glEndQuery(GL_SAMPLES_PASSED); + mDiffState = WAIT_ON_RESULT; + } + + gOneTextureFilterProgram.unbind(); + + if(cur_shader != NULL) + { + cur_shader->bind(); + } + + if(!mDebugViewerVisible) + { + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + } +#endif +} + +static LLTrace::EventStatHandle<> sFramePixelDiff("FramePixelDifference"); +void LLSceneMonitor::fetchQueryResult() +{ + // also throttle timing here, to avoid going below sample time due to phasing with frame capture + static LLCachedControl scene_load_sample_time_control(gSavedSettings, "SceneLoadingMonitorSampleTime"); + F32Seconds scene_load_sample_time = (F32Seconds)scene_load_sample_time_control(); + + if(mDiffState == WAIT_ON_RESULT + && !LLAppViewer::instance()->quitRequested()) + { + mDiffState = WAITING_FOR_NEXT_DIFF; + + GLuint available = 0; + glGetQueryObjectuiv(mQueryObject, GL_QUERY_RESULT_AVAILABLE, &available); + if(available) + { + GLuint count = 0; + glGetQueryObjectuiv(mQueryObject, GL_QUERY_RESULT, &count); + + mDiffResult = sqrtf(count * 0.5f / (mDiff->getWidth() * mDiff->getHeight() * mDiffPixelRatio * mDiffPixelRatio)); //0.5 -> (front face + back face) + + LL_DEBUGS("SceneMonitor") << "Frame difference: " << mDiffResult << LL_ENDL; + record(sFramePixelDiff, mDiffResult); + + static LLCachedControl diff_threshold(gSavedSettings,"SceneLoadingMonitorPixelDiffThreshold"); + F32Seconds elapsed_time = mRecordingTimer.getElapsedTimeF32(); + + if (elapsed_time > scene_load_sample_time) + { + if(mDiffResult > diff_threshold()) + { + mSceneLoadRecording.extend(); + llassert_always(mSceneLoadRecording.getResults().getLastRecording().getDuration() > scene_load_sample_time); + } + else + { + mSceneLoadRecording.nextPeriod(); + } + mRecordingTimer.reset(); + } + } + } +} + +//dump results to a file _scene_xmonitor_results.csv +void LLSceneMonitor::dumpToFile(const std::string &file_name) +{ + if (!hasResults()) return; + + LL_INFOS("SceneMonitor") << "Saving scene load stats to " << file_name << LL_ENDL; + try + { + llofstream os(file_name.c_str()); + + os << std::setprecision(10); + + LLTrace::PeriodicRecording& scene_load_recording = mSceneLoadRecording.getResults(); + const U32 frame_count = scene_load_recording.getNumRecordedPeriods(); + + F64Seconds frame_time; + + os << "Stat"; + for (S32 frame = 1; frame <= frame_count; frame++) + { + frame_time += scene_load_recording.getPrevRecording(frame_count - frame).getDuration(); + os << ", " << frame_time.value(); + } + os << '\n'; + + os << "Sample period(s)"; + for (S32 frame = 1; frame <= frame_count; frame++) + { + frame_time = scene_load_recording.getPrevRecording(frame_count - frame).getDuration(); + os << ", " << frame_time.value(); + } + os << '\n'; + + + typedef LLTrace::StatType trace_count; + for (auto& it : trace_count::instance_snapshot()) + { + std::ostringstream row; + row << std::setprecision(10); + + row << it.getName(); + + const char* unit_label = it.getUnitLabel(); + if (unit_label[0]) + { + row << "(" << unit_label << ")"; + } + + S32 samples = 0; + + for (S32 frame = 1; frame <= frame_count; frame++) + { + LLTrace::Recording& recording = scene_load_recording.getPrevRecording(frame_count - frame); + samples += recording.getSampleCount(it); + row << ", " << recording.getSum(it); + } + + row << '\n'; + + if (samples > 0) + { + os << row.str(); + } + } + + typedef LLTrace::StatType trace_event; + + for (auto& it : trace_event::instance_snapshot()) + { + std::ostringstream row; + row << std::setprecision(10); + row << it.getName(); + + const char* unit_label = it.getUnitLabel(); + if (unit_label[0]) + { + row << "(" << unit_label << ")"; + } + + S32 samples = 0; + + for (S32 frame = 1; frame <= frame_count; frame++) + { + LLTrace::Recording& recording = scene_load_recording.getPrevRecording(frame_count - frame); + samples += recording.getSampleCount(it); + F64 mean = recording.getMean(it); + if (llisnan(mean)) + { + row << ", n/a"; + } + else + { + row << ", " << mean; + } + } + + row << '\n'; + + if (samples > 0) + { + os << row.str(); + } + } + + typedef LLTrace::StatType trace_sample; + + for (auto& it : trace_sample::instance_snapshot()) + { + std::ostringstream row; + row << std::setprecision(10); + row << it.getName(); + + const char* unit_label = it.getUnitLabel(); + if (unit_label[0]) + { + row << "(" << unit_label << ")"; + } + + S32 samples = 0; + + for (S32 frame = 1; frame <= frame_count; frame++) + { + LLTrace::Recording& recording = scene_load_recording.getPrevRecording(frame_count - frame); + samples += recording.getSampleCount(it); + F64 mean = recording.getMean(it); + if (llisnan(mean)) + { + row << ", n/a"; + } + else + { + row << ", " << mean; + } + } + + row << '\n'; + + if (samples > 0) + { + os << row.str(); + } + } + + os.flush(); + os.close(); + } + catch (const std::ios_base::failure &e) + { + LL_WARNS() << "Unable to dump scene monitor results: " << e.what() << LL_ENDL; + } +} + +//------------------------------------------------------------------------------------------------------------- +//definition of class LLSceneMonitorView +//------------------------------------------------------------------------------------------------------------- +LLSceneMonitorView::LLSceneMonitorView(const LLRect& rect) + : LLFloater(LLSD()) +{ + setRect(rect); + setVisible(false); + + setCanMinimize(false); + setCanClose(true); + + sTeleportFinishConnection = LLViewerParcelMgr::getInstance()->setTeleportFinishedCallback(boost::bind(&LLSceneMonitorView::onTeleportFinished, this)); +} + +LLSceneMonitorView::~LLSceneMonitorView() +{ + sTeleportFinishConnection.disconnect(); +} + +void LLSceneMonitorView::onClose(bool app_quitting) +{ + setVisible(false); +} + +void LLSceneMonitorView::onClickCloseBtn(bool app_quitting) +{ + setVisible(false); +} + +void LLSceneMonitorView::onTeleportFinished() +{ + if(isInVisibleChain()) + { + LLSceneMonitor::getInstance()->reset(); + } +} + +void LLSceneMonitorView::onVisibilityChange(bool visible) +{ + LLSceneMonitor::getInstance()->setDebugViewerVisible(visible); +} + +void LLSceneMonitorView::draw() +{ + const LLRenderTarget* target = LLSceneMonitor::getInstance()->getDiffTarget(); + if(!target) + { + return; + } + + F32 ratio = LLSceneMonitor::getInstance()->getDiffPixelRatio(); + S32 height = (S32)(target->getHeight() * ratio); + S32 width = (S32)(target->getWidth() * ratio); + + LLRect new_rect; + new_rect.setLeftTopAndSize(getRect().mLeft, getRect().mTop, width, height); + setRect(new_rect); + + //draw background + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gl_rect_2d(0, getRect().getHeight(), getRect().getWidth(), 0, LLColor4(0.f, 0.f, 0.f, 0.25f)); + + LLSceneMonitor::getInstance()->calcDiffAggregate(); + + //show some texts + LLColor4 color = LLColor4::white; + S32 line_height = LLFontGL::getFontMonospace()->getLineHeight(); + + S32 lines = 0; + std::string num_str = llformat("Frame difference: %.6f", LLSceneMonitor::getInstance()->getDiffResult()); + LLFontGL::getFontMonospace()->renderUTF8(num_str, 0, 5, getRect().getHeight() - line_height * lines, color, LLFontGL::LEFT, LLFontGL::TOP); + lines++; + + num_str = llformat("Pixel tolerance: (R+G+B) < %.4f", LLSceneMonitor::getInstance()->getDiffTolerance()); + LLFontGL::getFontMonospace()->renderUTF8(num_str, 0, 5, getRect().getHeight() - line_height * lines, color, LLFontGL::LEFT, LLFontGL::TOP); + lines++; + + num_str = llformat("Sampling time: %.3f seconds", gSavedSettings.getF32("SceneLoadingMonitorSampleTime")); + LLFontGL::getFontMonospace()->renderUTF8(num_str, 0, 5, getRect().getHeight() - line_height * lines, color, LLFontGL::LEFT, LLFontGL::TOP); + lines++; + + num_str = llformat("Scene Loading time: %.3f seconds", (F32)LLSceneMonitor::getInstance()->getRecording()->getResults().getDuration().value()); + LLFontGL::getFontMonospace()->renderUTF8(num_str, 0, 5, getRect().getHeight() - line_height * lines, color, LLFontGL::LEFT, LLFontGL::TOP); + lines++; + + LLView::draw(); +} + diff --git a/indra/newview/llscenemonitor.h b/indra/newview/llscenemonitor.h index 779933e1a2..24260d8970 100644 --- a/indra/newview/llscenemonitor.h +++ b/indra/newview/llscenemonitor.h @@ -1,129 +1,129 @@ -/** - * @file llscenemonitor.h - * @brief monitor the process of scene loading - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLSCENE_MONITOR_H -#define LL_LLSCENE_MONITOR_H - -#include "llsingleton.h" -#include "llmath.h" -#include "llfloater.h" -#include "llcharacter.h" -#include "lltracerecording.h" - -class LLCharacter; -class LLRenderTarget; -class LLViewerTexture; - -class LLSceneMonitor : public LLSingleton -{ - LLSINGLETON(LLSceneMonitor); - ~LLSceneMonitor(); - LOG_CLASS(LLSceneMonitor); -public: - - void freezeAvatar(LLCharacter* avatarp); - void setDebugViewerVisible(bool visible); - - void capture(); //capture the main frame buffer - void compare(); //compare the stored two buffers. - void fetchQueryResult(); - void calcDiffAggregate(); - void setDiffTolerance(F32 tol) {mDiffTolerance = tol;} - - const LLRenderTarget* getDiffTarget() const {return mDiff;} - F32 getDiffTolerance() const {return mDiffTolerance;} - F32 getDiffResult() const { return mDiffResult;} - F32 getDiffPixelRatio() const { return mDiffPixelRatio;} - bool isEnabled()const {return mEnabled;} - bool needsUpdate() const; - - const LLTrace::ExtendablePeriodicRecording* getRecording() const {return &mSceneLoadRecording;} - void dumpToFile(const std::string &file_name); - bool hasResults() const { return mSceneLoadRecording.getResults().getDuration() != S32Seconds(0);} - - void reset(); - -private: - void freezeScene(); - void unfreezeScene(); - - LLRenderTarget& getCaptureTarget(); - void generateDitheringTexture(S32 width, S32 height); - -private: - bool mEnabled, - mDebugViewerVisible; - - enum EDiffState - { - WAITING_FOR_NEXT_DIFF, - NEED_DIFF, - EXECUTE_DIFF, - WAIT_ON_RESULT, - VIEWER_QUITTING - } mDiffState; - - LLRenderTarget* mFrames[2]; - LLRenderTarget* mDiff; - - GLuint mQueryObject; //used for glQuery - F32 mDiffResult, //aggregate results of mDiff. - mDiffTolerance, //pixels are filtered out when R+G+B < mDiffTolerance - mDiffPixelRatio; //ratio of pixels used for comparison against the original mDiff size along one dimension - - LLPointer mDitheringTexture; - S32 mDitherMatrixWidth; - F32 mDitherScale, - mDitherScaleS, - mDitherScaleT; - - std::vector mAvatarPauseHandles; - - LLTimer mRecordingTimer; - LLTrace::ExtendablePeriodicRecording mSceneLoadRecording; - LLTrace::Recording mMonitorRecording; -}; - -class LLSceneMonitorView : public LLFloater -{ -public: - LLSceneMonitorView(const LLRect& rect); - ~LLSceneMonitorView(); - virtual void draw(); - - virtual void onVisibilityChange(bool visible); - -protected: - virtual void onClose(bool app_quitting=false); - virtual void onClickCloseBtn(bool app_quitting=false); - void onTeleportFinished(); - boost::signals2::connection sTeleportFinishConnection; -}; - -extern LLSceneMonitorView* gSceneMonitorView; - -#endif - +/** + * @file llscenemonitor.h + * @brief monitor the process of scene loading + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSCENE_MONITOR_H +#define LL_LLSCENE_MONITOR_H + +#include "llsingleton.h" +#include "llmath.h" +#include "llfloater.h" +#include "llcharacter.h" +#include "lltracerecording.h" + +class LLCharacter; +class LLRenderTarget; +class LLViewerTexture; + +class LLSceneMonitor : public LLSingleton +{ + LLSINGLETON(LLSceneMonitor); + ~LLSceneMonitor(); + LOG_CLASS(LLSceneMonitor); +public: + + void freezeAvatar(LLCharacter* avatarp); + void setDebugViewerVisible(bool visible); + + void capture(); //capture the main frame buffer + void compare(); //compare the stored two buffers. + void fetchQueryResult(); + void calcDiffAggregate(); + void setDiffTolerance(F32 tol) {mDiffTolerance = tol;} + + const LLRenderTarget* getDiffTarget() const {return mDiff;} + F32 getDiffTolerance() const {return mDiffTolerance;} + F32 getDiffResult() const { return mDiffResult;} + F32 getDiffPixelRatio() const { return mDiffPixelRatio;} + bool isEnabled()const {return mEnabled;} + bool needsUpdate() const; + + const LLTrace::ExtendablePeriodicRecording* getRecording() const {return &mSceneLoadRecording;} + void dumpToFile(const std::string &file_name); + bool hasResults() const { return mSceneLoadRecording.getResults().getDuration() != S32Seconds(0);} + + void reset(); + +private: + void freezeScene(); + void unfreezeScene(); + + LLRenderTarget& getCaptureTarget(); + void generateDitheringTexture(S32 width, S32 height); + +private: + bool mEnabled, + mDebugViewerVisible; + + enum EDiffState + { + WAITING_FOR_NEXT_DIFF, + NEED_DIFF, + EXECUTE_DIFF, + WAIT_ON_RESULT, + VIEWER_QUITTING + } mDiffState; + + LLRenderTarget* mFrames[2]; + LLRenderTarget* mDiff; + + GLuint mQueryObject; //used for glQuery + F32 mDiffResult, //aggregate results of mDiff. + mDiffTolerance, //pixels are filtered out when R+G+B < mDiffTolerance + mDiffPixelRatio; //ratio of pixels used for comparison against the original mDiff size along one dimension + + LLPointer mDitheringTexture; + S32 mDitherMatrixWidth; + F32 mDitherScale, + mDitherScaleS, + mDitherScaleT; + + std::vector mAvatarPauseHandles; + + LLTimer mRecordingTimer; + LLTrace::ExtendablePeriodicRecording mSceneLoadRecording; + LLTrace::Recording mMonitorRecording; +}; + +class LLSceneMonitorView : public LLFloater +{ +public: + LLSceneMonitorView(const LLRect& rect); + ~LLSceneMonitorView(); + virtual void draw(); + + virtual void onVisibilityChange(bool visible); + +protected: + virtual void onClose(bool app_quitting=false); + virtual void onClickCloseBtn(bool app_quitting=false); + void onTeleportFinished(); + boost::signals2::connection sTeleportFinishConnection; +}; + +extern LLSceneMonitorView* gSceneMonitorView; + +#endif + diff --git a/indra/newview/llsceneview.cpp b/indra/newview/llsceneview.cpp index b3abfa2357..26859800fd 100644 --- a/indra/newview/llsceneview.cpp +++ b/indra/newview/llsceneview.cpp @@ -1,435 +1,435 @@ -/** - * @file llsceneview.cpp - * @brief LLSceneView class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llsceneview.h" -#include "llviewerwindow.h" -#include "pipeline.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llagent.h" -#include "llvolumemgr.h" -#include "llmeshrepository.h" - -LLSceneView* gSceneView = NULL; - -//borrow this helper function from llfasttimerview.cpp -template -void removeOutliers(std::vector& data, F32 k); - - -LLSceneView::LLSceneView(const LLRect& rect) - : LLFloater(LLSD()) -{ - setRect(rect); - setVisible(false); - - setCanMinimize(false); - setCanClose(true); -} - -void LLSceneView::onClose(bool) -{ - setVisible(false); -} - -void LLSceneView::onClickCloseBtn(bool) -{ - setVisible(false); -} - -void LLSceneView::draw() -{ - S32 margin = 10; - S32 height = (S32) (gViewerWindow->getWindowRectScaled().getHeight()*0.75f); - S32 width = (S32) (gViewerWindow->getWindowRectScaled().getWidth() * 0.75f); - - LLRect new_rect; - new_rect.setLeftTopAndSize(getRect().mLeft, getRect().mTop, width, height); - setRect(new_rect); - - // Draw the window background - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gl_rect_2d(0, getRect().getHeight(), getRect().getWidth(), 0, LLColor4(0.f, 0.f, 0.f, 0.25f)); - - - //aggregate some statistics - - //object sizes - std::vector size[2]; - - //triangle counts - std::vector triangles[2]; - std::vector visible_triangles[2]; - S32 total_visible_triangles[] = {0, 0}; - S32 total_triangles[] = {0, 0}; - - S32 total_visible_bytes[] = {0, 0}; - S32 total_bytes[] = {0, 0}; - - //streaming cost - std::vector streaming_cost[2]; - F32 total_streaming[] = { 0.f, 0.f }; - - //physics cost - std::vector physics_cost[2]; - F32 total_physics[] = { 0.f, 0.f }; - - LLViewerRegion* region = gAgent.getRegion(); - if (region) - { - for (U32 i = 0; i < gObjectList.getNumObjects(); ++i) - { - LLViewerObject* object = gObjectList.getObject(i); - - if (object && - object->getVolume()&& - object->getRegion() == region) - { - U32 idx = object->isAttachment() ? 1 : 0; - - LLVolume* volume = object->getVolume(); - - F32 radius = object->getScale().magVec(); - size[idx].push_back(radius); - - S32 visible = volume->getNumTriangles(); - S32 high_triangles = object->getHighLODTriangleCount(); - - total_visible_triangles[idx] += visible; - total_triangles[idx] += high_triangles; - - visible_triangles[idx].push_back(visible); - triangles[idx].push_back(high_triangles); - - F32 streaming = object->getStreamingCost(); - total_streaming[idx] += streaming; - streaming_cost[idx].push_back(streaming); - - F32 physics = object->getPhysicsCost(); - total_physics[idx] += physics; - physics_cost[idx].push_back(physics); - - S32 bytes = 0; - S32 visible_bytes = 0; - LLMeshCostData costs; - if (object->getCostData(costs)) - { - bytes = costs.getSizeTotal(); - visible_bytes = costs.getSizeByLOD(object->getLOD()); - } - - total_bytes[idx] += bytes; - total_visible_bytes[idx] += visible_bytes; - } - } - } - - const char* category[] = - { - "Region", - "Attachment" - }; - - S32 graph_pos[4]; - - for (U32 i = 0; i < 4; ++i) - { - graph_pos[i] = new_rect.getHeight()/4*(i+1); - } - - for (U32 idx = 0; idx < 2; idx++) - { - if (!size[idx].empty()) - { //display graph of object sizes - std::sort(size[idx].begin(), size[idx].end()); - - ll_remove_outliers(size[idx], 1.f); - - LLRect size_rect; - - if (idx == 0) - { - size_rect = LLRect(margin, graph_pos[0]-margin, new_rect.getWidth()/2-margin, margin*2); - } - else - { - size_rect = LLRect(margin+new_rect.getWidth()/2, graph_pos[0]-margin, new_rect.getWidth()-margin, margin*2); - } - - gl_rect_2d(size_rect, LLColor4::white, false); - - F32 size_domain[] = { 128.f, 0.f }; - - //get domain of sizes - for (U32 i = 0; i < size[idx].size(); ++i) - { - size_domain[0] = llmin(size_domain[0], size[idx][i]); - size_domain[1] = llmax(size_domain[1], size[idx][i]); - } - - F32 size_range = size_domain[1]-size_domain[0]; - - U32 count = size[idx].size(); - - F32 total = 0.f; - - gGL.begin(LLRender::LINE_STRIP); - - for (U32 i = 0; i < count; ++i) - { - F32 rad = size[idx][i]; - total += rad; - F32 y = (rad-size_domain[0])/size_range*size_rect.getHeight()+size_rect.mBottom; - F32 x = (F32) i / count * size_rect.getWidth() + size_rect.mLeft; - - gGL.vertex2f(x,y); - - if (i%4096 == 0) - { - gGL.end(); - gGL.flush(); - gGL.begin(LLRender::LINE_STRIP); - } - } - - gGL.end(); - gGL.flush(); - - std::string label = llformat("%s Object Sizes (m) -- [%.1f, %.1f] Mean: %.1f Median: %.1f -- %d samples", - category[idx], size_domain[0], size_domain[1], total/count, size[idx][count/2], count); - - LLFontGL::getFontMonospace()->renderUTF8(label, - 0 , size_rect.mLeft, size_rect.mTop+margin, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP); - - } - } - - for (U32 idx = 0; idx < 2; ++idx) - { - if (!triangles[idx].empty()) - { //plot graph of visible/total triangles - std::sort(triangles[idx].begin(), triangles[idx].end()); - - ll_remove_outliers(triangles[idx], 1.f); - - LLRect tri_rect; - if (idx == 0) - { - tri_rect = LLRect(margin, graph_pos[1]-margin, new_rect.getWidth()/2-margin, graph_pos[0]+margin); - } - else - { - tri_rect = LLRect(new_rect.getWidth()/2+margin, graph_pos[1]-margin, new_rect.getWidth()-margin, graph_pos[0]+margin); - } - - gl_rect_2d(tri_rect, LLColor4::white, false); - - S32 tri_domain[] = { 65536, 0 }; - - //get domain of triangle counts - for (U32 i = 0; i < triangles[idx].size(); ++i) - { - tri_domain[0] = llmin(tri_domain[0], triangles[idx][i]); - tri_domain[1] = llmax(tri_domain[1], triangles[idx][i]); - } - - U32 triangle_range = tri_domain[1]-tri_domain[0]; - - U32 count = triangles[idx].size(); - - gGL.begin(LLRender::LINE_STRIP); - //plot triangles - for (U32 i = 0; i < count; ++i) - { - U32 tri_count = triangles[idx][i]; - F32 y = (F32) (tri_count-tri_domain[0])/triangle_range*tri_rect.getHeight()+tri_rect.mBottom; - F32 x = (F32) i / count * tri_rect.getWidth() + tri_rect.mLeft; - - gGL.vertex2f(x,y); - - if (i%4096 == 0) - { - gGL.end(); - gGL.flush(); - gGL.begin(LLRender::LINE_STRIP); - } - } - - gGL.end(); - gGL.flush(); - - count = visible_triangles[idx].size(); - - std::string label = llformat("%s Object Triangle Counts (Ktris) -- Visible: %.2f/%.2f (%.2f KB Visible)", - category[idx], total_visible_triangles[idx]/1024.f, total_triangles[idx]/1024.f, total_visible_bytes[idx]/1024.f); - - LLFontGL::getFontMonospace()->renderUTF8(label, - 0 , tri_rect.mLeft, tri_rect.mTop+margin, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP); - - } - } - - for (U32 idx = 0; idx < 2; ++idx) - { - if (!streaming_cost[idx].empty()) - { //plot graph of streaming cost - std::sort(streaming_cost[idx].begin(), streaming_cost[idx].end()); - - ll_remove_outliers(streaming_cost[idx], 1.f); - - LLRect tri_rect; - if (idx == 0) - { - tri_rect = LLRect(margin, graph_pos[2]-margin, new_rect.getWidth()/2-margin, graph_pos[1]+margin); - } - else - { - tri_rect = LLRect(new_rect.getWidth()/2+margin, graph_pos[2]-margin, new_rect.getWidth()-margin, graph_pos[1]+margin); - } - - gl_rect_2d(tri_rect, LLColor4::white, false); - - F32 streaming_domain[] = { 65536, 0 }; - - //get domain of triangle counts - for (U32 i = 0; i < streaming_cost[idx].size(); ++i) - { - streaming_domain[0] = llmin(streaming_domain[0], streaming_cost[idx][i]); - streaming_domain[1] = llmax(streaming_domain[1], streaming_cost[idx][i]); - } - - F32 cost_range = streaming_domain[1]-streaming_domain[0]; - - U32 count = streaming_cost[idx].size(); - - F32 total = 0; - - gGL.begin(LLRender::LINE_STRIP); - //plot triangles - for (U32 i = 0; i < count; ++i) - { - F32 sc = streaming_cost[idx][i]; - total += sc; - F32 y = (F32) (sc-streaming_domain[0])/cost_range*tri_rect.getHeight()+tri_rect.mBottom; - F32 x = (F32) i / count * tri_rect.getWidth() + tri_rect.mLeft; - - gGL.vertex2f(x,y); - - if (i%4096 == 0) - { - gGL.end(); - gGL.flush(); - gGL.begin(LLRender::LINE_STRIP); - } - } - - gGL.end(); - gGL.flush(); - - std::string label = llformat("%s Object Streaming Cost -- [%.2f, %.2f] Mean: %.2f Total: %.2f", - category[idx], streaming_domain[0], streaming_domain[1], total/count, total_streaming[idx]); - - LLFontGL::getFontMonospace()->renderUTF8(label, - 0 , tri_rect.mLeft, tri_rect.mTop+margin, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP); - - } - } - - for (U32 idx = 0; idx < 2; ++idx) - { - if (!physics_cost[idx].empty()) - { //plot graph of physics cost - std::sort(physics_cost[idx].begin(), physics_cost[idx].end()); - - ll_remove_outliers(physics_cost[idx], 1.f); - - LLRect tri_rect; - if (idx == 0) - { - tri_rect = LLRect(margin, graph_pos[3]-margin, new_rect.getWidth()/2-margin, graph_pos[2]+margin); - } - else - { - tri_rect = LLRect(new_rect.getWidth()/2+margin, graph_pos[3]-margin, new_rect.getWidth()-margin, graph_pos[2]+margin); - } - - gl_rect_2d(tri_rect, LLColor4::white, false); - - F32 physics_domain[] = { 65536, 0 }; - - //get domain of triangle counts - for (U32 i = 0; i < physics_cost[idx].size(); ++i) - { - physics_domain[0] = llmin(physics_domain[0], physics_cost[idx][i]); - physics_domain[1] = llmax(physics_domain[1], physics_cost[idx][i]); - } - - F32 cost_range = physics_domain[1]-physics_domain[0]; - - U32 count = physics_cost[idx].size(); - - F32 total = 0; - - gGL.begin(LLRender::LINE_STRIP); - //plot triangles - for (U32 i = 0; i < count; ++i) - { - F32 pc = physics_cost[idx][i]; - total += pc; - F32 y = (F32) (pc-physics_domain[0])/cost_range*tri_rect.getHeight()+tri_rect.mBottom; - F32 x = (F32) i / count * tri_rect.getWidth() + tri_rect.mLeft; - - gGL.vertex2f(x,y); - - if (i%4096 == 0) - { - gGL.end(); - gGL.flush(); - gGL.begin(LLRender::LINE_STRIP); - } - } - - gGL.end(); - gGL.flush(); - - std::string label = llformat("%s Object Physics Cost -- [%.2f, %.2f] Mean: %.2f Total: %.2f", - category[idx], physics_domain[0], physics_domain[1], total/count, total_physics[idx]); - - LLFontGL::getFontMonospace()->renderUTF8(label, - 0 , tri_rect.mLeft, tri_rect.mTop+margin, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP); - - } - } - - - - - LLView::draw(); -} - - +/** + * @file llsceneview.cpp + * @brief LLSceneView class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llsceneview.h" +#include "llviewerwindow.h" +#include "pipeline.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llagent.h" +#include "llvolumemgr.h" +#include "llmeshrepository.h" + +LLSceneView* gSceneView = NULL; + +//borrow this helper function from llfasttimerview.cpp +template +void removeOutliers(std::vector& data, F32 k); + + +LLSceneView::LLSceneView(const LLRect& rect) + : LLFloater(LLSD()) +{ + setRect(rect); + setVisible(false); + + setCanMinimize(false); + setCanClose(true); +} + +void LLSceneView::onClose(bool) +{ + setVisible(false); +} + +void LLSceneView::onClickCloseBtn(bool) +{ + setVisible(false); +} + +void LLSceneView::draw() +{ + S32 margin = 10; + S32 height = (S32) (gViewerWindow->getWindowRectScaled().getHeight()*0.75f); + S32 width = (S32) (gViewerWindow->getWindowRectScaled().getWidth() * 0.75f); + + LLRect new_rect; + new_rect.setLeftTopAndSize(getRect().mLeft, getRect().mTop, width, height); + setRect(new_rect); + + // Draw the window background + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gl_rect_2d(0, getRect().getHeight(), getRect().getWidth(), 0, LLColor4(0.f, 0.f, 0.f, 0.25f)); + + + //aggregate some statistics + + //object sizes + std::vector size[2]; + + //triangle counts + std::vector triangles[2]; + std::vector visible_triangles[2]; + S32 total_visible_triangles[] = {0, 0}; + S32 total_triangles[] = {0, 0}; + + S32 total_visible_bytes[] = {0, 0}; + S32 total_bytes[] = {0, 0}; + + //streaming cost + std::vector streaming_cost[2]; + F32 total_streaming[] = { 0.f, 0.f }; + + //physics cost + std::vector physics_cost[2]; + F32 total_physics[] = { 0.f, 0.f }; + + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + for (U32 i = 0; i < gObjectList.getNumObjects(); ++i) + { + LLViewerObject* object = gObjectList.getObject(i); + + if (object && + object->getVolume()&& + object->getRegion() == region) + { + U32 idx = object->isAttachment() ? 1 : 0; + + LLVolume* volume = object->getVolume(); + + F32 radius = object->getScale().magVec(); + size[idx].push_back(radius); + + S32 visible = volume->getNumTriangles(); + S32 high_triangles = object->getHighLODTriangleCount(); + + total_visible_triangles[idx] += visible; + total_triangles[idx] += high_triangles; + + visible_triangles[idx].push_back(visible); + triangles[idx].push_back(high_triangles); + + F32 streaming = object->getStreamingCost(); + total_streaming[idx] += streaming; + streaming_cost[idx].push_back(streaming); + + F32 physics = object->getPhysicsCost(); + total_physics[idx] += physics; + physics_cost[idx].push_back(physics); + + S32 bytes = 0; + S32 visible_bytes = 0; + LLMeshCostData costs; + if (object->getCostData(costs)) + { + bytes = costs.getSizeTotal(); + visible_bytes = costs.getSizeByLOD(object->getLOD()); + } + + total_bytes[idx] += bytes; + total_visible_bytes[idx] += visible_bytes; + } + } + } + + const char* category[] = + { + "Region", + "Attachment" + }; + + S32 graph_pos[4]; + + for (U32 i = 0; i < 4; ++i) + { + graph_pos[i] = new_rect.getHeight()/4*(i+1); + } + + for (U32 idx = 0; idx < 2; idx++) + { + if (!size[idx].empty()) + { //display graph of object sizes + std::sort(size[idx].begin(), size[idx].end()); + + ll_remove_outliers(size[idx], 1.f); + + LLRect size_rect; + + if (idx == 0) + { + size_rect = LLRect(margin, graph_pos[0]-margin, new_rect.getWidth()/2-margin, margin*2); + } + else + { + size_rect = LLRect(margin+new_rect.getWidth()/2, graph_pos[0]-margin, new_rect.getWidth()-margin, margin*2); + } + + gl_rect_2d(size_rect, LLColor4::white, false); + + F32 size_domain[] = { 128.f, 0.f }; + + //get domain of sizes + for (U32 i = 0; i < size[idx].size(); ++i) + { + size_domain[0] = llmin(size_domain[0], size[idx][i]); + size_domain[1] = llmax(size_domain[1], size[idx][i]); + } + + F32 size_range = size_domain[1]-size_domain[0]; + + U32 count = size[idx].size(); + + F32 total = 0.f; + + gGL.begin(LLRender::LINE_STRIP); + + for (U32 i = 0; i < count; ++i) + { + F32 rad = size[idx][i]; + total += rad; + F32 y = (rad-size_domain[0])/size_range*size_rect.getHeight()+size_rect.mBottom; + F32 x = (F32) i / count * size_rect.getWidth() + size_rect.mLeft; + + gGL.vertex2f(x,y); + + if (i%4096 == 0) + { + gGL.end(); + gGL.flush(); + gGL.begin(LLRender::LINE_STRIP); + } + } + + gGL.end(); + gGL.flush(); + + std::string label = llformat("%s Object Sizes (m) -- [%.1f, %.1f] Mean: %.1f Median: %.1f -- %d samples", + category[idx], size_domain[0], size_domain[1], total/count, size[idx][count/2], count); + + LLFontGL::getFontMonospace()->renderUTF8(label, + 0 , size_rect.mLeft, size_rect.mTop+margin, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP); + + } + } + + for (U32 idx = 0; idx < 2; ++idx) + { + if (!triangles[idx].empty()) + { //plot graph of visible/total triangles + std::sort(triangles[idx].begin(), triangles[idx].end()); + + ll_remove_outliers(triangles[idx], 1.f); + + LLRect tri_rect; + if (idx == 0) + { + tri_rect = LLRect(margin, graph_pos[1]-margin, new_rect.getWidth()/2-margin, graph_pos[0]+margin); + } + else + { + tri_rect = LLRect(new_rect.getWidth()/2+margin, graph_pos[1]-margin, new_rect.getWidth()-margin, graph_pos[0]+margin); + } + + gl_rect_2d(tri_rect, LLColor4::white, false); + + S32 tri_domain[] = { 65536, 0 }; + + //get domain of triangle counts + for (U32 i = 0; i < triangles[idx].size(); ++i) + { + tri_domain[0] = llmin(tri_domain[0], triangles[idx][i]); + tri_domain[1] = llmax(tri_domain[1], triangles[idx][i]); + } + + U32 triangle_range = tri_domain[1]-tri_domain[0]; + + U32 count = triangles[idx].size(); + + gGL.begin(LLRender::LINE_STRIP); + //plot triangles + for (U32 i = 0; i < count; ++i) + { + U32 tri_count = triangles[idx][i]; + F32 y = (F32) (tri_count-tri_domain[0])/triangle_range*tri_rect.getHeight()+tri_rect.mBottom; + F32 x = (F32) i / count * tri_rect.getWidth() + tri_rect.mLeft; + + gGL.vertex2f(x,y); + + if (i%4096 == 0) + { + gGL.end(); + gGL.flush(); + gGL.begin(LLRender::LINE_STRIP); + } + } + + gGL.end(); + gGL.flush(); + + count = visible_triangles[idx].size(); + + std::string label = llformat("%s Object Triangle Counts (Ktris) -- Visible: %.2f/%.2f (%.2f KB Visible)", + category[idx], total_visible_triangles[idx]/1024.f, total_triangles[idx]/1024.f, total_visible_bytes[idx]/1024.f); + + LLFontGL::getFontMonospace()->renderUTF8(label, + 0 , tri_rect.mLeft, tri_rect.mTop+margin, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP); + + } + } + + for (U32 idx = 0; idx < 2; ++idx) + { + if (!streaming_cost[idx].empty()) + { //plot graph of streaming cost + std::sort(streaming_cost[idx].begin(), streaming_cost[idx].end()); + + ll_remove_outliers(streaming_cost[idx], 1.f); + + LLRect tri_rect; + if (idx == 0) + { + tri_rect = LLRect(margin, graph_pos[2]-margin, new_rect.getWidth()/2-margin, graph_pos[1]+margin); + } + else + { + tri_rect = LLRect(new_rect.getWidth()/2+margin, graph_pos[2]-margin, new_rect.getWidth()-margin, graph_pos[1]+margin); + } + + gl_rect_2d(tri_rect, LLColor4::white, false); + + F32 streaming_domain[] = { 65536, 0 }; + + //get domain of triangle counts + for (U32 i = 0; i < streaming_cost[idx].size(); ++i) + { + streaming_domain[0] = llmin(streaming_domain[0], streaming_cost[idx][i]); + streaming_domain[1] = llmax(streaming_domain[1], streaming_cost[idx][i]); + } + + F32 cost_range = streaming_domain[1]-streaming_domain[0]; + + U32 count = streaming_cost[idx].size(); + + F32 total = 0; + + gGL.begin(LLRender::LINE_STRIP); + //plot triangles + for (U32 i = 0; i < count; ++i) + { + F32 sc = streaming_cost[idx][i]; + total += sc; + F32 y = (F32) (sc-streaming_domain[0])/cost_range*tri_rect.getHeight()+tri_rect.mBottom; + F32 x = (F32) i / count * tri_rect.getWidth() + tri_rect.mLeft; + + gGL.vertex2f(x,y); + + if (i%4096 == 0) + { + gGL.end(); + gGL.flush(); + gGL.begin(LLRender::LINE_STRIP); + } + } + + gGL.end(); + gGL.flush(); + + std::string label = llformat("%s Object Streaming Cost -- [%.2f, %.2f] Mean: %.2f Total: %.2f", + category[idx], streaming_domain[0], streaming_domain[1], total/count, total_streaming[idx]); + + LLFontGL::getFontMonospace()->renderUTF8(label, + 0 , tri_rect.mLeft, tri_rect.mTop+margin, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP); + + } + } + + for (U32 idx = 0; idx < 2; ++idx) + { + if (!physics_cost[idx].empty()) + { //plot graph of physics cost + std::sort(physics_cost[idx].begin(), physics_cost[idx].end()); + + ll_remove_outliers(physics_cost[idx], 1.f); + + LLRect tri_rect; + if (idx == 0) + { + tri_rect = LLRect(margin, graph_pos[3]-margin, new_rect.getWidth()/2-margin, graph_pos[2]+margin); + } + else + { + tri_rect = LLRect(new_rect.getWidth()/2+margin, graph_pos[3]-margin, new_rect.getWidth()-margin, graph_pos[2]+margin); + } + + gl_rect_2d(tri_rect, LLColor4::white, false); + + F32 physics_domain[] = { 65536, 0 }; + + //get domain of triangle counts + for (U32 i = 0; i < physics_cost[idx].size(); ++i) + { + physics_domain[0] = llmin(physics_domain[0], physics_cost[idx][i]); + physics_domain[1] = llmax(physics_domain[1], physics_cost[idx][i]); + } + + F32 cost_range = physics_domain[1]-physics_domain[0]; + + U32 count = physics_cost[idx].size(); + + F32 total = 0; + + gGL.begin(LLRender::LINE_STRIP); + //plot triangles + for (U32 i = 0; i < count; ++i) + { + F32 pc = physics_cost[idx][i]; + total += pc; + F32 y = (F32) (pc-physics_domain[0])/cost_range*tri_rect.getHeight()+tri_rect.mBottom; + F32 x = (F32) i / count * tri_rect.getWidth() + tri_rect.mLeft; + + gGL.vertex2f(x,y); + + if (i%4096 == 0) + { + gGL.end(); + gGL.flush(); + gGL.begin(LLRender::LINE_STRIP); + } + } + + gGL.end(); + gGL.flush(); + + std::string label = llformat("%s Object Physics Cost -- [%.2f, %.2f] Mean: %.2f Total: %.2f", + category[idx], physics_domain[0], physics_domain[1], total/count, total_physics[idx]); + + LLFontGL::getFontMonospace()->renderUTF8(label, + 0 , tri_rect.mLeft, tri_rect.mTop+margin, LLColor4::white, LLFontGL::LEFT, LLFontGL::TOP); + + } + } + + + + + LLView::draw(); +} + + diff --git a/indra/newview/llscreenchannel.cpp b/indra/newview/llscreenchannel.cpp index c766462227..89ec2bf72e 100644 --- a/indra/newview/llscreenchannel.cpp +++ b/indra/newview/llscreenchannel.cpp @@ -1,1136 +1,1136 @@ -/** - * @file llscreenchannel.cpp - * @brief Class implements a channel on a screen in which appropriate toasts may appear. - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#include "llviewerprecompiledheaders.h" // must be first include - -#include "lliconctrl.h" -#include "lltextbox.h" -#include "llscreenchannel.h" - -#include "lltoastpanel.h" -#include "llviewercontrol.h" -#include "llviewerwindow.h" -#include "llfloaterreg.h" -#include "lltrans.h" -#include "llagent.h" -#include "lldockablefloater.h" -#include "llsyswellwindow.h" -#include "llfloaterimsession.h" -#include "llscriptfloater.h" -#include "llrootview.h" - -#include - -using namespace LLNotificationsUI; - -bool LLScreenChannel::mWasStartUpToastShown = false; - -LLRect LLScreenChannelBase::getChannelRect() -{ - LL_PROFILE_ZONE_SCOPED; - - if (mFloaterSnapRegion == NULL) - { - mFloaterSnapRegion = gViewerWindow->getRootView()->getChildView("floater_snap_region"); - } - - if (mChicletRegion == NULL) - { - mChicletRegion = gViewerWindow->getRootView()->getChildView("chiclet_container"); - } - - LLRect channel_rect; - LLRect chiclet_rect; - - mFloaterSnapRegion->localRectToScreen(mFloaterSnapRegion->getLocalRect(), &channel_rect); - mChicletRegion->localRectToScreen(mChicletRegion->getLocalRect(), &chiclet_rect); - - channel_rect.mTop = chiclet_rect.mBottom; - return channel_rect; -} - - -//-------------------------------------------------------------------------- -////////////////////// -// LLScreenChannelBase -////////////////////// - -LLScreenChannelBase::LLScreenChannelBase(const Params& p) -: LLUICtrl(p), - mToastAlignment(p.toast_align), - mCanStoreToasts(true), - mHiddenToastsNum(0), - mHoveredToast(NULL), - mControlHovering(false), - mShowToasts(true), - mID(p.id), - mDisplayToastsAlways(p.display_toasts_always), - mChannelAlignment(p.channel_align), - mFloaterSnapRegion(NULL), - mChicletRegion(NULL) -{ - mID = p.id; - - setMouseOpaque( false ); - setVisible(false); -} - -bool LLScreenChannelBase::postBuild() -{ - if (mFloaterSnapRegion == NULL) - { - mFloaterSnapRegion = gViewerWindow->getRootView()->getChildView("floater_snap_region"); - } - - if (mChicletRegion == NULL) - { - mChicletRegion = gViewerWindow->getRootView()->getChildView("chiclet_container"); - } - - return true; -} - -void LLScreenChannelBase::reshape(S32 width, S32 height, bool called_from_parent) -{ - if (mChannelAlignment == CA_CENTRE) - { - // Keep notifications and alerts centered - // WorldViewRectScaled is out of date at reshape but Window has same width - S32 channel_bound = gViewerWindow->getWindowRectScaled().getWidth() / 2; - setRect(LLRect(channel_bound, 0, channel_bound, 0)); - updateRect(); //sets top and bottom only - } - redrawToasts(); -} - -bool LLScreenChannelBase::isHovering() -{ - if (!mHoveredToast) - { - return false; - } - - return mHoveredToast->isHovered(); -} - -void LLScreenChannelBase::updatePositionAndSize(LLRect rect) -{ - LLRect this_rect = getRect(); - - this_rect.mTop = rect.mTop; - switch(mChannelAlignment) - { - case CA_LEFT : - break; - case CA_CENTRE : - this_rect.setCenterAndSize( (rect.getWidth()) / 2, rect.getHeight() / 2, this_rect.getWidth(), this_rect.getHeight()); - break; - case CA_RIGHT : - this_rect.setLeftTopAndSize(rect.mRight - this_rect.getWidth(), - this_rect.mTop, - this_rect.getWidth(), - this_rect.getHeight()); - } - setRect(this_rect); - redrawToasts(); - -} - -void LLScreenChannelBase::init(S32 channel_left, S32 channel_right) -{ - // top and bottom set by updateRect() - setRect(LLRect(channel_left, 0, channel_right, 0)); - updateRect(); - setVisible(true); -} - -void LLScreenChannelBase::updateRect() -{ - const S32 CHANNEL_BOTTOM_PANEL_MARGIN = 35; - S32 channel_top = getChannelRect().mTop; - S32 channel_bottom = getChannelRect().mBottom + CHANNEL_BOTTOM_PANEL_MARGIN; - S32 channel_left = getRect().mLeft; - S32 channel_right = getRect().mRight; - setRect(LLRect(channel_left, channel_top, channel_right, channel_bottom)); -} - -//-------------------------------------------------------------------------- -////////////////////// -// LLScreenChannel -////////////////////// -//-------------------------------------------------------------------------- -LLScreenChannel::LLScreenChannel(const Params& p) -: LLScreenChannelBase(p), - mStartUpToastPanel(NULL) -{ -} - -//-------------------------------------------------------------------------- -void LLScreenChannel::init(S32 channel_left, S32 channel_right) -{ - LLScreenChannelBase::init(channel_left, channel_right); - LLRect channel_rect = getChannelRect(); - updatePositionAndSize(channel_rect); -} - -//-------------------------------------------------------------------------- -LLScreenChannel::~LLScreenChannel() -{ - -} - -std::list LLScreenChannel::findToasts(const Matcher& matcher) -{ - std::list res; - - // collect stored toasts - for (std::vector::iterator it = mStoredToastList.begin(); it - != mStoredToastList.end(); it++) - { - const LLToast* toast = it->getToast(); - if (toast && matcher.matches(toast->getNotification())) - { - res.push_back(toast); - } - } - - // collect displayed toasts - for (std::vector::iterator it = mToastList.begin(); it - != mToastList.end(); it++) - { - const LLToast* toast = it->getToast(); - if (toast && matcher.matches(toast->getNotification())) - { - res.push_back(toast); - } - } - - return res; -} - -//-------------------------------------------------------------------------- -void LLScreenChannel::updatePositionAndSize(LLRect new_world_rect) -{ - LLRect this_rect = getRect(); - - switch(mChannelAlignment) - { - case CA_LEFT : - this_rect.mTop = (S32) (new_world_rect.getHeight() * getHeightRatio()); - break; - case CA_CENTRE : - LLScreenChannelBase::updatePositionAndSize(new_world_rect); - return; - case CA_RIGHT : - this_rect.mTop = (S32) (new_world_rect.getHeight() * getHeightRatio()); - this_rect.setLeftTopAndSize(new_world_rect.mRight - this_rect.getWidth(), - this_rect.mTop, - this_rect.getWidth(), - this_rect.getHeight()); - } - setRect(this_rect); - redrawToasts(); -} - -//-------------------------------------------------------------------------- -void LLScreenChannel::addToast(const LLToast::Params& p) -{ - LL_PROFILE_ZONE_SCOPED - bool store_toast = false, show_toast = false; - - if (mDisplayToastsAlways) - { - show_toast = true; - } - else - { - show_toast = mWasStartUpToastShown && (mShowToasts || p.force_show); - } - store_toast = !show_toast && p.can_be_stored && mCanStoreToasts; - - if(!show_toast && !store_toast) - { - if(gAgent.isDoNotDisturb()) - { - return; - } - LLNotificationPtr notification = LLNotifications::instance().find(p.notif_id); - - if (notification && - (!notification->canLogToIM() || !notification->hasFormElements())) - { - // only cancel notification if it isn't being used in IM session - LLNotifications::instance().cancel(notification); - } - - // It was assumed that the toast would take ownership of the panel pointer. - // But since we have decided not to display the toast, kill the panel to - // prevent the memory leak. - if (p.panel != NULL) - { - p.panel()->die(); - } - return; - } - - LLToast* toast = new LLToast(p); - ToastElem new_toast_elem(toast->getHandle()); - - toast->setOnFadeCallback(boost::bind(&LLScreenChannel::onToastFade, this, _1)); - toast->setOnToastDestroyedCallback(boost::bind(&LLScreenChannel::onToastDestroyed, this, _1)); - if(mControlHovering) - { - toast->setOnToastHoverCallback(boost::bind(&LLScreenChannel::onToastHover, this, _1, _2)); - toast->setMouseEnterCallback(boost::bind(&LLScreenChannel::stopToastTimer, this, toast)); - toast->setMouseLeaveCallback(boost::bind(&LLScreenChannel::startToastTimer, this, toast)); - } - - if(show_toast) - { - mToastList.push_back(new_toast_elem); - if(p.can_be_stored) - { - // store toasts immediately - EXT-3762 - storeToast(new_toast_elem); - } - updateShowToastsState(); - redrawToasts(); - } - else // store_toast - { - mHiddenToastsNum++; - storeToast(new_toast_elem); - } -} - -//-------------------------------------------------------------------------- -void LLScreenChannel::onToastDestroyed(LLToast* toast) -{ - std::vector::iterator it = find(mToastList.begin(), mToastList.end(), static_cast(toast)); - - if(it != mToastList.end()) - { - mToastList.erase(it); - } - - it = find(mStoredToastList.begin(), mStoredToastList.end(), static_cast(toast)); - - if(it != mStoredToastList.end()) - { - mStoredToastList.erase(it); - } - - // if destroyed toast is hovered - reset hovered - if (mHoveredToast == toast) - { - mHoveredToast = NULL; - } -} - - -//-------------------------------------------------------------------------- -void LLScreenChannel::onToastFade(LLToast* toast) -{ - std::vector::iterator it = find(mToastList.begin(), mToastList.end(), static_cast(toast)); - - if(it != mToastList.end()) - { - bool delete_toast = !mCanStoreToasts || !toast->getCanBeStored(); - if(delete_toast) - { - mToastList.erase(it); - deleteToast(toast); - } - else - { - storeToast((*it)); - mToastList.erase(it); - } - - redrawToasts(); - } -} - -//-------------------------------------------------------------------------- -void LLScreenChannel::deleteToast(LLToast* toast) -{ - if (!toast || toast->isDead()) - { - return; - } - - // send signal to observers about destroying of a toast - toast->closeToast(); - - // update channel's Hovering state - // turning hovering off manually because onMouseLeave won't happen if a toast was closed using a keyboard - if(mHoveredToast == toast) - { - mHoveredToast = NULL; - } -} - -//-------------------------------------------------------------------------- - -void LLScreenChannel::storeToast(ToastElem& toast_elem) -{ - // do not store clones - std::vector::iterator it = find(mStoredToastList.begin(), mStoredToastList.end(), toast_elem.getID()); - if( it != mStoredToastList.end() ) - return; - - const LLToast* toast = toast_elem.getToast(); - if (toast) - { - mStoredToastList.push_back(toast_elem); - mOnStoreToast(toast->getPanel(), toast->getNotificationID()); - } -} - -//-------------------------------------------------------------------------- -void LLScreenChannel::loadStoredToastsToChannel() -{ - std::vector::iterator it; - - if(mStoredToastList.size() == 0) - return; - - for(it = mStoredToastList.begin(); it != mStoredToastList.end(); ++it) - { - LLToast* toast = it->getToast(); - if (toast) - { - toast->setIsHidden(false); - toast->startTimer(); - mToastList.push_back(*it); - } - } - - mStoredToastList.clear(); - redrawToasts(); -} - -//-------------------------------------------------------------------------- -void LLScreenChannel::loadStoredToastByNotificationIDToChannel(LLUUID id) -{ - std::vector::iterator it = find(mStoredToastList.begin(), mStoredToastList.end(), id); - - if( it == mStoredToastList.end() ) - return; - - LLToast* toast = it->getToast(); - if (toast) - { - if(toast->getVisible()) - { - // toast is already in channel - return; - } - - toast->setIsHidden(false); - toast->startTimer(); - mToastList.push_back(*it); - } - - redrawToasts(); -} - -//-------------------------------------------------------------------------- -void LLScreenChannel::killToastByNotificationID(LLUUID id) -{ - // searching among toasts on a screen - std::vector::iterator it = find(mToastList.begin(), mToastList.end(), id); - LLNotificationPtr notification = LLNotifications::instance().find(id); - if (!notification) return; - - if( it != mToastList.end()) - { - LLToast* toast = it->getToast(); - // if it is a notification toast and notification is UnResponded - then respond on it - // else - simply destroy a toast - // - // NOTE: if a notification is unresponded this function will be called twice for the same toast. - // At first, the notification will be discarded, at second (it will be caused by discarding), - // the toast will be destroyed. - if(toast && toast->isNotificationValid()) - { - if (!notification->canLogToIM() || !notification->hasFormElements()) - { - // only cancel notification if it isn't being used in IM session - LLNotifications::instance().cancel(notification); - } - } - else - { - removeToastByNotificationID(id); - } - } - else - { - // searching among stored toasts - it = find(mStoredToastList.begin(), mStoredToastList.end(), id); - - if( it != mStoredToastList.end() ) - { - LLToast* toast = it->getToast(); - if (toast) - { - if (!notification->canLogToIM() || !notification->hasFormElements()) - { - // only cancel notification if it isn't being used in IM session - LLNotifications::instance().cancel(notification); - } - deleteToast(toast); - } - } - - // Call find() once more, because the mStoredToastList could have been changed - // via notification cancellation and the iterator could have become invalid. - it = find(mStoredToastList.begin(), mStoredToastList.end(), id); - if (it != mStoredToastList.end()) - { - mStoredToastList.erase(it); - } - } -} - -void LLScreenChannel::removeToastByNotificationID(LLUUID id) -{ - std::vector::iterator it = find(mToastList.begin(), mToastList.end(), id); - while( it != mToastList.end()) - { - deleteToast(it->getToast()); - mToastList.erase(it); - redrawToasts(); - // find next toast with matching id - it = find(mToastList.begin(), mToastList.end(), id); - } - - it = find(mStoredToastList.begin(), mStoredToastList.end(), id); - if (it != mStoredToastList.end()) - { - deleteToast(it->getToast()); - mStoredToastList.erase(it); - } -} - - -void LLScreenChannel::killMatchedToasts(const Matcher& matcher) -{ - std::list to_delete = findToasts(matcher); - for (std::list::iterator it = to_delete.begin(); it - != to_delete.end(); it++) - { - killToastByNotificationID((*it)-> getNotificationID()); - } -} - -//-------------------------------------------------------------------------- -void LLScreenChannel::modifyToastByNotificationID(LLUUID id, LLPanel* panel) -{ - std::vector::iterator it = find(mToastList.begin(), mToastList.end(), id); - - LLPanel* panel_to_delete = panel; - - if( it != mToastList.end() && panel) - { - LLToast* toast = it->getToast(); - if (toast) - { - LLPanel* old_panel = toast->getPanel(); - toast->removeChild(old_panel); - panel_to_delete = old_panel; - toast->insertPanel(panel); - toast->startTimer(); - } - redrawToasts(); - } - - delete panel_to_delete; -} - -//-------------------------------------------------------------------------- -void LLScreenChannel::redrawToasts() -{ - if (!getParent()) - { - // connect to floater snap region just to get resize events, we don't care about being a proper widget - mFloaterSnapRegion->addChild(this); - setFollows(FOLLOWS_ALL); - } - - if(mToastList.size() == 0) - return; - - switch(mToastAlignment) - { - case NA_TOP : - showToastsTop(); - break; - - case NA_CENTRE : - showToastsCentre(); - break; - - case NA_BOTTOM : - showToastsBottom(); - } -} - -//-------------------------------------------------------------------------- -void LLScreenChannel::showToastsBottom() -{ - LLRect toast_rect; - S32 bottom = getRect().mBottom - gFloaterView->getRect().mBottom; - S32 toast_margin = 0; - std::vector::reverse_iterator it; - - updateRect(); - - LLDockableFloater* floater = dynamic_cast(LLDockableFloater::getInstanceHandle().get()); - - // Use a local variable instead of mToastList. - // mToastList can be modified during recursive calls and then all iteratos will be invalidated. - std::vector vToastList( mToastList ); - - for(it = vToastList.rbegin(); it != vToastList.rend(); ++it) - { - if(it != vToastList.rbegin()) - { - LLToast* toast = (it-1)->getToast(); - if (!toast) - { - LL_WARNS() << "Attempt to display a deleted toast." << LL_ENDL; - return; - } - - bottom = toast->getRect().mTop - toast->getTopPad(); - toast_margin = gSavedSettings.getS32("ToastGap"); - } - - LLToast* toast = it->getToast(); - if(!toast) - { - LL_WARNS() << "Attempt to display a deleted toast." << LL_ENDL; - return; - } - - toast_rect = toast->getRect(); - toast_rect.setOriginAndSize(getRect().mRight - toast_rect.getWidth(), - bottom + toast_margin, toast_rect.getWidth(), - toast_rect.getHeight()); - toast->setRect(toast_rect); - - if(floater && floater->overlapsScreenChannel()) - { - if(it == vToastList.rbegin()) - { - // move first toast above docked floater - S32 shift = floater->getRect().getHeight(); - if(floater->getDockControl()) - { - shift += floater->getDockControl()->getTongueHeight(); - } - toast->translate(0, shift); - } - - LLRect channel_rect = getChannelRect(); - // don't show toasts if there is not enough space - if(toast_rect.mTop > channel_rect.mTop) - { - break; - } - } - - bool stop_showing_toasts = toast->getRect().mTop > getRect().mTop; - - if(!stop_showing_toasts) - { - if( it != vToastList.rend()-1) - { - S32 toast_top = toast->getRect().mTop + gSavedSettings.getS32("ToastGap"); - stop_showing_toasts = toast_top > getRect().mTop; - } - } - - // at least one toast should be visible - - if(it == vToastList.rbegin()) - { - stop_showing_toasts = false; - } - - if(stop_showing_toasts) - break; - - if( !toast->getVisible() ) - { - // HACK - // EXT-2653: it is necessary to prevent overlapping for secondary showed toasts - toast->setVisible(true); - } - if(!toast->hasFocus()) - { - // Fixing Z-order of toasts (EXT-4862) - // Next toast will be positioned under this one. - gFloaterView->sendChildToBack(toast); - } - } - - // Dismiss toasts we don't have space for (STORM-391). - if(it != vToastList.rend()) - { - mHiddenToastsNum = 0; - - for(; it != vToastList.rend(); it++) - { - LLToast* toast = it->getToast(); - if (toast) - { - toast->hide(); - } - } - } -} - -//-------------------------------------------------------------------------- -void LLScreenChannel::showToastsCentre() -{ - LLToast* toast = mToastList[0].getToast(); - if (!toast) - { - LL_WARNS() << "Attempt to display a deleted toast." << LL_ENDL; - return; - } - - LLRect toast_rect; - S32 bottom = (getRect().mTop - getRect().mBottom)/2 + toast->getRect().getHeight()/2; - std::vector::reverse_iterator it; - - for(it = mToastList.rbegin(); it != mToastList.rend(); ++it) - { - LLToast* toast = it->getToast(); - if (!toast) - { - LL_WARNS() << "Attempt to display a deleted toast." << LL_ENDL; - return; - } - - toast_rect = toast->getRect(); - toast_rect.setLeftTopAndSize(getRect().mLeft - toast_rect.getWidth() / 2, bottom + toast_rect.getHeight() / 2 + gSavedSettings.getS32("ToastGap"), toast_rect.getWidth() ,toast_rect.getHeight()); - toast->setRect(toast_rect); - - toast->setVisible(true); - } -} - -//-------------------------------------------------------------------------- -void LLScreenChannel::showToastsTop() -{ - LLRect channel_rect = getChannelRect(); - - LLRect toast_rect; - S32 top = channel_rect.mTop; - std::vector::reverse_iterator it; - - updateRect(); - - LLDockableFloater* floater = dynamic_cast(LLDockableFloater::getInstanceHandle().get()); - - // Use a local variable instead of mToastList. - // mToastList can be modified during recursive calls and then all iteratos will be invalidated. - std::vector vToastList( mToastList ); - - for(it = vToastList.rbegin(); it != vToastList.rend(); ++it) - { - if(it != vToastList.rbegin()) - { - LLToast* toast = (it-1)->getToast(); - if (!toast) - { - LL_WARNS() << "Attempt to display a deleted toast." << LL_ENDL; - return; - } - - top = toast->getRect().mBottom - toast->getTopPad(); - gSavedSettings.getS32("ToastGap"); - } - - LLToast* toast = it->getToast(); - if (!toast) - { - LL_WARNS() << "Attempt to display a deleted toast." << LL_ENDL; - return; - } - - toast_rect = toast->getRect(); - toast_rect.setLeftTopAndSize(channel_rect.mRight - toast_rect.getWidth(), - top, toast_rect.getWidth(), - toast_rect.getHeight()); - toast->setRect(toast_rect); - - if(floater && floater->overlapsScreenChannel()) - { - if(it == vToastList.rbegin()) - { - // move first toast above docked floater - S32 shift = -floater->getRect().getHeight(); - if(floater->getDockControl()) - { - shift -= floater->getDockControl()->getTongueHeight(); - } - toast->translate(0, shift); - } - - LLRect channel_rect = getChannelRect(); - // don't show toasts if there is not enough space - if(toast_rect.mBottom < channel_rect.mBottom) - { - break; - } - } - - bool stop_showing_toasts = toast->getRect().mBottom < channel_rect.mBottom; - - if(!stop_showing_toasts) - { - if( it != vToastList.rend()-1) - { - S32 toast_bottom = toast->getRect().mBottom - gSavedSettings.getS32("ToastGap"); - stop_showing_toasts = toast_bottom < channel_rect.mBottom; - } - } - - // at least one toast should be visible - if(it == vToastList.rbegin()) - { - stop_showing_toasts = false; - } - - if(stop_showing_toasts) - break; - - if (!toast->getVisible()) - { - // HACK - // EXT-2653: it is necessary to prevent overlapping for secondary showed toasts - toast->setVisible(true); - } - if (!toast->hasFocus()) - { - // Fixing Z-order of toasts (EXT-4862) - // Next toast will be positioned under this one. - gFloaterView->sendChildToBack(toast); - } - } - - // Dismiss toasts we don't have space for (STORM-391). - std::vector toasts_to_hide; - - if(it != vToastList.rend()) - { - mHiddenToastsNum = 0; - - for(; it != vToastList.rend(); it++) - { - LLToast* toast = it->getToast(); - if (toast) - { - toasts_to_hide.push_back(toast); - } - } - } - - for (std::vector::iterator it = toasts_to_hide.begin(), end_it = toasts_to_hide.end(); - it != end_it; - ++it) - { - (*it)->hide(); - } -} - -//-------------------------------------------------------------------------- -void LLScreenChannel::createStartUpToast(S32 notif_num, F32 timer) -{ - LLScreenChannelBase::updateRect(); - - LLRect toast_rect; - LLToast::Params p; - p.lifetime_secs = timer; - p.enable_hide_btn = false; - mStartUpToastPanel = new LLToast(p); - - if(!mStartUpToastPanel) - return; - - mStartUpToastPanel->setOnFadeCallback(boost::bind(&LLScreenChannel::onStartUpToastHide, this)); - - LLPanel* wrapper_panel = mStartUpToastPanel->getChild("wrapper_panel"); - LLTextBox* text_box = mStartUpToastPanel->getChild("toast_text"); - - std::string text = LLTrans::getString("StartUpNotifications"); - - toast_rect = mStartUpToastPanel->getRect(); - mStartUpToastPanel->reshape(getRect().getWidth(), toast_rect.getHeight(), true); - - text_box->setValue(text); - text_box->setVisible(true); - - text_box->reshapeToFitText(); - text_box->setOrigin(text_box->getRect().mLeft, (wrapper_panel->getRect().getHeight() - text_box->getRect().getHeight())/2); - - toast_rect.setLeftTopAndSize(0, getRect().getHeight() - gSavedSettings.getS32("ToastGap"), getRect().getWidth(), toast_rect.getHeight()); - mStartUpToastPanel->setRect(toast_rect); - - addChild(mStartUpToastPanel); - - mStartUpToastPanel->setVisible(true); -} - -// static -------------------------------------------------------------------------- -F32 LLScreenChannel::getHeightRatio() -{ - F32 ratio = gSavedSettings.getF32("NotificationChannelHeightRatio"); - if(0.0f > ratio) - { - ratio = 0.0f; - } - else if(1.0f < ratio) - { - ratio = 1.0f; - } - return ratio; -} - -//-------------------------------------------------------------------------- -void LLScreenChannel::updateStartUpString(S32 num) -{ - // *TODO: update string if notifications are arriving while the StartUp toast is on a screen -} - -//-------------------------------------------------------------------------- -void LLScreenChannel::onStartUpToastHide() -{ - onCommit(); -} - -//-------------------------------------------------------------------------- -void LLScreenChannel::closeStartUpToast() -{ - if(mStartUpToastPanel != NULL) - { - mStartUpToastPanel->setVisible(false); - mStartUpToastPanel = NULL; - } -} - -void LLNotificationsUI::LLScreenChannel::stopToastTimer(LLToast* toast) -{ - if (!toast || toast != mHoveredToast) return; - - // Pause fade timer of the hovered toast. - toast->stopTimer(); -} - -void LLNotificationsUI::LLScreenChannel::startToastTimer(LLToast* toast) -{ - if (!toast || toast == mHoveredToast) - { - return; - } - - // Reset its fade timer. - toast->startTimer(); -} - -//-------------------------------------------------------------------------- -void LLScreenChannel::hideToastsFromScreen() -{ - for(std::vector::iterator it = mToastList.begin(); it != mToastList.end(); it++) - { - LLToast* toast = it->getToast(); - if (toast) - { - toast->setVisible(false); - } - else - { - LL_WARNS() << "Attempt to hide a deleted toast." << LL_ENDL; - } - } -} - -//-------------------------------------------------------------------------- -void LLScreenChannel::hideToast(const LLUUID& notification_id) -{ - std::vector::iterator it = find(mToastList.begin(), mToastList.end(), notification_id); - if(mToastList.end() != it) - { - LLToast* toast = it->getToast(); - if (toast) - { - toast->hide(); - } - else - { - LL_WARNS() << "Attempt to hide a deleted toast." << LL_ENDL; - } - } -} - -void LLScreenChannel::closeHiddenToasts(const Matcher& matcher) -{ - // since we can't guarantee that close toast operation doesn't change mToastList - // we collect matched toasts that should be closed into separate list - std::list toasts; - for (std::vector::iterator it = mToastList.begin(); it - != mToastList.end(); it++) - { - LLToast* toast = it->getToast(); - // add to list valid toast that match to provided matcher criteria - if (toast != NULL && !toast->isDead() && toast->getNotification() != NULL - && !toast->getVisible() && matcher.matches(toast->getNotification())) - { - toasts.push_back(toast); - } - } - - // close collected toasts - for (std::list::iterator it = toasts.begin(); it - != toasts.end(); it++) - { - LLToast* toast = *it; - toast->closeFloater(); - } -} - -//-------------------------------------------------------------------------- -void LLScreenChannel::removeToastsFromChannel() -{ - hideToastsFromScreen(); - for(std::vector::iterator it = mToastList.begin(); it != mToastList.end(); it++) - { - deleteToast(it->getToast()); - } - mToastList.clear(); -} - -//-------------------------------------------------------------------------- -void LLScreenChannel::removeAndStoreAllStorableToasts() -{ - if(mToastList.size() == 0) - return; - - hideToastsFromScreen(); - for(std::vector::iterator it = mToastList.begin(); it != mToastList.end();) - { - LLToast* toast = it->getToast(); - if(toast && toast->getCanBeStored()) - { - storeToast(*it); - it = mToastList.erase(it); - } - else - { - ++it; - } - } - redrawToasts(); -} - -//-------------------------------------------------------------------------- -void LLScreenChannel::removeToastsBySessionID(LLUUID id) -{ - if(mToastList.size() == 0) - return; - - hideToastsFromScreen(); - for(std::vector::iterator it = mToastList.begin(); it != mToastList.end();) - { - LLToast* toast = it->getToast(); - if(toast && toast->getSessionID() == id) - { - deleteToast(toast); - it = mToastList.erase(it); - } - else - { - ++it; - } - } - redrawToasts(); -} - -//-------------------------------------------------------------------------- -void LLScreenChannel::onToastHover(LLToast* toast, bool mouse_enter) -{ - // because of LLViewerWindow::updateUI() that NOT ALWAYS calls onMouseEnter BEFORE onMouseLeave - // we must check hovering directly to prevent incorrect setting for hovering in a channel - if (mouse_enter) - { - if (toast->isHovered()) - { - mHoveredToast = toast; - } - } - else if (mHoveredToast != NULL) - { - if (!mHoveredToast->isHovered()) - { - mHoveredToast = NULL; - } - } - - redrawToasts(); -} - -//-------------------------------------------------------------------------- -void LLScreenChannel::updateShowToastsState() -{ - LLDockableFloater* floater = dynamic_cast(LLDockableFloater::getInstanceHandle().get()); - - if(!floater) - { - setShowToasts(true); - return; - } - - updateRect(); -} - -//-------------------------------------------------------------------------- - -LLToast* LLScreenChannel::getToastByNotificationID(LLUUID id) -{ - std::vector::iterator it = find(mStoredToastList.begin(), - mStoredToastList.end(), id); - - if (it == mStoredToastList.end()) - return NULL; - - return it->getToast(); -} +/** + * @file llscreenchannel.cpp + * @brief Class implements a channel on a screen in which appropriate toasts may appear. + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#include "llviewerprecompiledheaders.h" // must be first include + +#include "lliconctrl.h" +#include "lltextbox.h" +#include "llscreenchannel.h" + +#include "lltoastpanel.h" +#include "llviewercontrol.h" +#include "llviewerwindow.h" +#include "llfloaterreg.h" +#include "lltrans.h" +#include "llagent.h" +#include "lldockablefloater.h" +#include "llsyswellwindow.h" +#include "llfloaterimsession.h" +#include "llscriptfloater.h" +#include "llrootview.h" + +#include + +using namespace LLNotificationsUI; + +bool LLScreenChannel::mWasStartUpToastShown = false; + +LLRect LLScreenChannelBase::getChannelRect() +{ + LL_PROFILE_ZONE_SCOPED; + + if (mFloaterSnapRegion == NULL) + { + mFloaterSnapRegion = gViewerWindow->getRootView()->getChildView("floater_snap_region"); + } + + if (mChicletRegion == NULL) + { + mChicletRegion = gViewerWindow->getRootView()->getChildView("chiclet_container"); + } + + LLRect channel_rect; + LLRect chiclet_rect; + + mFloaterSnapRegion->localRectToScreen(mFloaterSnapRegion->getLocalRect(), &channel_rect); + mChicletRegion->localRectToScreen(mChicletRegion->getLocalRect(), &chiclet_rect); + + channel_rect.mTop = chiclet_rect.mBottom; + return channel_rect; +} + + +//-------------------------------------------------------------------------- +////////////////////// +// LLScreenChannelBase +////////////////////// + +LLScreenChannelBase::LLScreenChannelBase(const Params& p) +: LLUICtrl(p), + mToastAlignment(p.toast_align), + mCanStoreToasts(true), + mHiddenToastsNum(0), + mHoveredToast(NULL), + mControlHovering(false), + mShowToasts(true), + mID(p.id), + mDisplayToastsAlways(p.display_toasts_always), + mChannelAlignment(p.channel_align), + mFloaterSnapRegion(NULL), + mChicletRegion(NULL) +{ + mID = p.id; + + setMouseOpaque( false ); + setVisible(false); +} + +bool LLScreenChannelBase::postBuild() +{ + if (mFloaterSnapRegion == NULL) + { + mFloaterSnapRegion = gViewerWindow->getRootView()->getChildView("floater_snap_region"); + } + + if (mChicletRegion == NULL) + { + mChicletRegion = gViewerWindow->getRootView()->getChildView("chiclet_container"); + } + + return true; +} + +void LLScreenChannelBase::reshape(S32 width, S32 height, bool called_from_parent) +{ + if (mChannelAlignment == CA_CENTRE) + { + // Keep notifications and alerts centered + // WorldViewRectScaled is out of date at reshape but Window has same width + S32 channel_bound = gViewerWindow->getWindowRectScaled().getWidth() / 2; + setRect(LLRect(channel_bound, 0, channel_bound, 0)); + updateRect(); //sets top and bottom only + } + redrawToasts(); +} + +bool LLScreenChannelBase::isHovering() +{ + if (!mHoveredToast) + { + return false; + } + + return mHoveredToast->isHovered(); +} + +void LLScreenChannelBase::updatePositionAndSize(LLRect rect) +{ + LLRect this_rect = getRect(); + + this_rect.mTop = rect.mTop; + switch(mChannelAlignment) + { + case CA_LEFT : + break; + case CA_CENTRE : + this_rect.setCenterAndSize( (rect.getWidth()) / 2, rect.getHeight() / 2, this_rect.getWidth(), this_rect.getHeight()); + break; + case CA_RIGHT : + this_rect.setLeftTopAndSize(rect.mRight - this_rect.getWidth(), + this_rect.mTop, + this_rect.getWidth(), + this_rect.getHeight()); + } + setRect(this_rect); + redrawToasts(); + +} + +void LLScreenChannelBase::init(S32 channel_left, S32 channel_right) +{ + // top and bottom set by updateRect() + setRect(LLRect(channel_left, 0, channel_right, 0)); + updateRect(); + setVisible(true); +} + +void LLScreenChannelBase::updateRect() +{ + const S32 CHANNEL_BOTTOM_PANEL_MARGIN = 35; + S32 channel_top = getChannelRect().mTop; + S32 channel_bottom = getChannelRect().mBottom + CHANNEL_BOTTOM_PANEL_MARGIN; + S32 channel_left = getRect().mLeft; + S32 channel_right = getRect().mRight; + setRect(LLRect(channel_left, channel_top, channel_right, channel_bottom)); +} + +//-------------------------------------------------------------------------- +////////////////////// +// LLScreenChannel +////////////////////// +//-------------------------------------------------------------------------- +LLScreenChannel::LLScreenChannel(const Params& p) +: LLScreenChannelBase(p), + mStartUpToastPanel(NULL) +{ +} + +//-------------------------------------------------------------------------- +void LLScreenChannel::init(S32 channel_left, S32 channel_right) +{ + LLScreenChannelBase::init(channel_left, channel_right); + LLRect channel_rect = getChannelRect(); + updatePositionAndSize(channel_rect); +} + +//-------------------------------------------------------------------------- +LLScreenChannel::~LLScreenChannel() +{ + +} + +std::list LLScreenChannel::findToasts(const Matcher& matcher) +{ + std::list res; + + // collect stored toasts + for (std::vector::iterator it = mStoredToastList.begin(); it + != mStoredToastList.end(); it++) + { + const LLToast* toast = it->getToast(); + if (toast && matcher.matches(toast->getNotification())) + { + res.push_back(toast); + } + } + + // collect displayed toasts + for (std::vector::iterator it = mToastList.begin(); it + != mToastList.end(); it++) + { + const LLToast* toast = it->getToast(); + if (toast && matcher.matches(toast->getNotification())) + { + res.push_back(toast); + } + } + + return res; +} + +//-------------------------------------------------------------------------- +void LLScreenChannel::updatePositionAndSize(LLRect new_world_rect) +{ + LLRect this_rect = getRect(); + + switch(mChannelAlignment) + { + case CA_LEFT : + this_rect.mTop = (S32) (new_world_rect.getHeight() * getHeightRatio()); + break; + case CA_CENTRE : + LLScreenChannelBase::updatePositionAndSize(new_world_rect); + return; + case CA_RIGHT : + this_rect.mTop = (S32) (new_world_rect.getHeight() * getHeightRatio()); + this_rect.setLeftTopAndSize(new_world_rect.mRight - this_rect.getWidth(), + this_rect.mTop, + this_rect.getWidth(), + this_rect.getHeight()); + } + setRect(this_rect); + redrawToasts(); +} + +//-------------------------------------------------------------------------- +void LLScreenChannel::addToast(const LLToast::Params& p) +{ + LL_PROFILE_ZONE_SCOPED + bool store_toast = false, show_toast = false; + + if (mDisplayToastsAlways) + { + show_toast = true; + } + else + { + show_toast = mWasStartUpToastShown && (mShowToasts || p.force_show); + } + store_toast = !show_toast && p.can_be_stored && mCanStoreToasts; + + if(!show_toast && !store_toast) + { + if(gAgent.isDoNotDisturb()) + { + return; + } + LLNotificationPtr notification = LLNotifications::instance().find(p.notif_id); + + if (notification && + (!notification->canLogToIM() || !notification->hasFormElements())) + { + // only cancel notification if it isn't being used in IM session + LLNotifications::instance().cancel(notification); + } + + // It was assumed that the toast would take ownership of the panel pointer. + // But since we have decided not to display the toast, kill the panel to + // prevent the memory leak. + if (p.panel != NULL) + { + p.panel()->die(); + } + return; + } + + LLToast* toast = new LLToast(p); + ToastElem new_toast_elem(toast->getHandle()); + + toast->setOnFadeCallback(boost::bind(&LLScreenChannel::onToastFade, this, _1)); + toast->setOnToastDestroyedCallback(boost::bind(&LLScreenChannel::onToastDestroyed, this, _1)); + if(mControlHovering) + { + toast->setOnToastHoverCallback(boost::bind(&LLScreenChannel::onToastHover, this, _1, _2)); + toast->setMouseEnterCallback(boost::bind(&LLScreenChannel::stopToastTimer, this, toast)); + toast->setMouseLeaveCallback(boost::bind(&LLScreenChannel::startToastTimer, this, toast)); + } + + if(show_toast) + { + mToastList.push_back(new_toast_elem); + if(p.can_be_stored) + { + // store toasts immediately - EXT-3762 + storeToast(new_toast_elem); + } + updateShowToastsState(); + redrawToasts(); + } + else // store_toast + { + mHiddenToastsNum++; + storeToast(new_toast_elem); + } +} + +//-------------------------------------------------------------------------- +void LLScreenChannel::onToastDestroyed(LLToast* toast) +{ + std::vector::iterator it = find(mToastList.begin(), mToastList.end(), static_cast(toast)); + + if(it != mToastList.end()) + { + mToastList.erase(it); + } + + it = find(mStoredToastList.begin(), mStoredToastList.end(), static_cast(toast)); + + if(it != mStoredToastList.end()) + { + mStoredToastList.erase(it); + } + + // if destroyed toast is hovered - reset hovered + if (mHoveredToast == toast) + { + mHoveredToast = NULL; + } +} + + +//-------------------------------------------------------------------------- +void LLScreenChannel::onToastFade(LLToast* toast) +{ + std::vector::iterator it = find(mToastList.begin(), mToastList.end(), static_cast(toast)); + + if(it != mToastList.end()) + { + bool delete_toast = !mCanStoreToasts || !toast->getCanBeStored(); + if(delete_toast) + { + mToastList.erase(it); + deleteToast(toast); + } + else + { + storeToast((*it)); + mToastList.erase(it); + } + + redrawToasts(); + } +} + +//-------------------------------------------------------------------------- +void LLScreenChannel::deleteToast(LLToast* toast) +{ + if (!toast || toast->isDead()) + { + return; + } + + // send signal to observers about destroying of a toast + toast->closeToast(); + + // update channel's Hovering state + // turning hovering off manually because onMouseLeave won't happen if a toast was closed using a keyboard + if(mHoveredToast == toast) + { + mHoveredToast = NULL; + } +} + +//-------------------------------------------------------------------------- + +void LLScreenChannel::storeToast(ToastElem& toast_elem) +{ + // do not store clones + std::vector::iterator it = find(mStoredToastList.begin(), mStoredToastList.end(), toast_elem.getID()); + if( it != mStoredToastList.end() ) + return; + + const LLToast* toast = toast_elem.getToast(); + if (toast) + { + mStoredToastList.push_back(toast_elem); + mOnStoreToast(toast->getPanel(), toast->getNotificationID()); + } +} + +//-------------------------------------------------------------------------- +void LLScreenChannel::loadStoredToastsToChannel() +{ + std::vector::iterator it; + + if(mStoredToastList.size() == 0) + return; + + for(it = mStoredToastList.begin(); it != mStoredToastList.end(); ++it) + { + LLToast* toast = it->getToast(); + if (toast) + { + toast->setIsHidden(false); + toast->startTimer(); + mToastList.push_back(*it); + } + } + + mStoredToastList.clear(); + redrawToasts(); +} + +//-------------------------------------------------------------------------- +void LLScreenChannel::loadStoredToastByNotificationIDToChannel(LLUUID id) +{ + std::vector::iterator it = find(mStoredToastList.begin(), mStoredToastList.end(), id); + + if( it == mStoredToastList.end() ) + return; + + LLToast* toast = it->getToast(); + if (toast) + { + if(toast->getVisible()) + { + // toast is already in channel + return; + } + + toast->setIsHidden(false); + toast->startTimer(); + mToastList.push_back(*it); + } + + redrawToasts(); +} + +//-------------------------------------------------------------------------- +void LLScreenChannel::killToastByNotificationID(LLUUID id) +{ + // searching among toasts on a screen + std::vector::iterator it = find(mToastList.begin(), mToastList.end(), id); + LLNotificationPtr notification = LLNotifications::instance().find(id); + if (!notification) return; + + if( it != mToastList.end()) + { + LLToast* toast = it->getToast(); + // if it is a notification toast and notification is UnResponded - then respond on it + // else - simply destroy a toast + // + // NOTE: if a notification is unresponded this function will be called twice for the same toast. + // At first, the notification will be discarded, at second (it will be caused by discarding), + // the toast will be destroyed. + if(toast && toast->isNotificationValid()) + { + if (!notification->canLogToIM() || !notification->hasFormElements()) + { + // only cancel notification if it isn't being used in IM session + LLNotifications::instance().cancel(notification); + } + } + else + { + removeToastByNotificationID(id); + } + } + else + { + // searching among stored toasts + it = find(mStoredToastList.begin(), mStoredToastList.end(), id); + + if( it != mStoredToastList.end() ) + { + LLToast* toast = it->getToast(); + if (toast) + { + if (!notification->canLogToIM() || !notification->hasFormElements()) + { + // only cancel notification if it isn't being used in IM session + LLNotifications::instance().cancel(notification); + } + deleteToast(toast); + } + } + + // Call find() once more, because the mStoredToastList could have been changed + // via notification cancellation and the iterator could have become invalid. + it = find(mStoredToastList.begin(), mStoredToastList.end(), id); + if (it != mStoredToastList.end()) + { + mStoredToastList.erase(it); + } + } +} + +void LLScreenChannel::removeToastByNotificationID(LLUUID id) +{ + std::vector::iterator it = find(mToastList.begin(), mToastList.end(), id); + while( it != mToastList.end()) + { + deleteToast(it->getToast()); + mToastList.erase(it); + redrawToasts(); + // find next toast with matching id + it = find(mToastList.begin(), mToastList.end(), id); + } + + it = find(mStoredToastList.begin(), mStoredToastList.end(), id); + if (it != mStoredToastList.end()) + { + deleteToast(it->getToast()); + mStoredToastList.erase(it); + } +} + + +void LLScreenChannel::killMatchedToasts(const Matcher& matcher) +{ + std::list to_delete = findToasts(matcher); + for (std::list::iterator it = to_delete.begin(); it + != to_delete.end(); it++) + { + killToastByNotificationID((*it)-> getNotificationID()); + } +} + +//-------------------------------------------------------------------------- +void LLScreenChannel::modifyToastByNotificationID(LLUUID id, LLPanel* panel) +{ + std::vector::iterator it = find(mToastList.begin(), mToastList.end(), id); + + LLPanel* panel_to_delete = panel; + + if( it != mToastList.end() && panel) + { + LLToast* toast = it->getToast(); + if (toast) + { + LLPanel* old_panel = toast->getPanel(); + toast->removeChild(old_panel); + panel_to_delete = old_panel; + toast->insertPanel(panel); + toast->startTimer(); + } + redrawToasts(); + } + + delete panel_to_delete; +} + +//-------------------------------------------------------------------------- +void LLScreenChannel::redrawToasts() +{ + if (!getParent()) + { + // connect to floater snap region just to get resize events, we don't care about being a proper widget + mFloaterSnapRegion->addChild(this); + setFollows(FOLLOWS_ALL); + } + + if(mToastList.size() == 0) + return; + + switch(mToastAlignment) + { + case NA_TOP : + showToastsTop(); + break; + + case NA_CENTRE : + showToastsCentre(); + break; + + case NA_BOTTOM : + showToastsBottom(); + } +} + +//-------------------------------------------------------------------------- +void LLScreenChannel::showToastsBottom() +{ + LLRect toast_rect; + S32 bottom = getRect().mBottom - gFloaterView->getRect().mBottom; + S32 toast_margin = 0; + std::vector::reverse_iterator it; + + updateRect(); + + LLDockableFloater* floater = dynamic_cast(LLDockableFloater::getInstanceHandle().get()); + + // Use a local variable instead of mToastList. + // mToastList can be modified during recursive calls and then all iteratos will be invalidated. + std::vector vToastList( mToastList ); + + for(it = vToastList.rbegin(); it != vToastList.rend(); ++it) + { + if(it != vToastList.rbegin()) + { + LLToast* toast = (it-1)->getToast(); + if (!toast) + { + LL_WARNS() << "Attempt to display a deleted toast." << LL_ENDL; + return; + } + + bottom = toast->getRect().mTop - toast->getTopPad(); + toast_margin = gSavedSettings.getS32("ToastGap"); + } + + LLToast* toast = it->getToast(); + if(!toast) + { + LL_WARNS() << "Attempt to display a deleted toast." << LL_ENDL; + return; + } + + toast_rect = toast->getRect(); + toast_rect.setOriginAndSize(getRect().mRight - toast_rect.getWidth(), + bottom + toast_margin, toast_rect.getWidth(), + toast_rect.getHeight()); + toast->setRect(toast_rect); + + if(floater && floater->overlapsScreenChannel()) + { + if(it == vToastList.rbegin()) + { + // move first toast above docked floater + S32 shift = floater->getRect().getHeight(); + if(floater->getDockControl()) + { + shift += floater->getDockControl()->getTongueHeight(); + } + toast->translate(0, shift); + } + + LLRect channel_rect = getChannelRect(); + // don't show toasts if there is not enough space + if(toast_rect.mTop > channel_rect.mTop) + { + break; + } + } + + bool stop_showing_toasts = toast->getRect().mTop > getRect().mTop; + + if(!stop_showing_toasts) + { + if( it != vToastList.rend()-1) + { + S32 toast_top = toast->getRect().mTop + gSavedSettings.getS32("ToastGap"); + stop_showing_toasts = toast_top > getRect().mTop; + } + } + + // at least one toast should be visible + + if(it == vToastList.rbegin()) + { + stop_showing_toasts = false; + } + + if(stop_showing_toasts) + break; + + if( !toast->getVisible() ) + { + // HACK + // EXT-2653: it is necessary to prevent overlapping for secondary showed toasts + toast->setVisible(true); + } + if(!toast->hasFocus()) + { + // Fixing Z-order of toasts (EXT-4862) + // Next toast will be positioned under this one. + gFloaterView->sendChildToBack(toast); + } + } + + // Dismiss toasts we don't have space for (STORM-391). + if(it != vToastList.rend()) + { + mHiddenToastsNum = 0; + + for(; it != vToastList.rend(); it++) + { + LLToast* toast = it->getToast(); + if (toast) + { + toast->hide(); + } + } + } +} + +//-------------------------------------------------------------------------- +void LLScreenChannel::showToastsCentre() +{ + LLToast* toast = mToastList[0].getToast(); + if (!toast) + { + LL_WARNS() << "Attempt to display a deleted toast." << LL_ENDL; + return; + } + + LLRect toast_rect; + S32 bottom = (getRect().mTop - getRect().mBottom)/2 + toast->getRect().getHeight()/2; + std::vector::reverse_iterator it; + + for(it = mToastList.rbegin(); it != mToastList.rend(); ++it) + { + LLToast* toast = it->getToast(); + if (!toast) + { + LL_WARNS() << "Attempt to display a deleted toast." << LL_ENDL; + return; + } + + toast_rect = toast->getRect(); + toast_rect.setLeftTopAndSize(getRect().mLeft - toast_rect.getWidth() / 2, bottom + toast_rect.getHeight() / 2 + gSavedSettings.getS32("ToastGap"), toast_rect.getWidth() ,toast_rect.getHeight()); + toast->setRect(toast_rect); + + toast->setVisible(true); + } +} + +//-------------------------------------------------------------------------- +void LLScreenChannel::showToastsTop() +{ + LLRect channel_rect = getChannelRect(); + + LLRect toast_rect; + S32 top = channel_rect.mTop; + std::vector::reverse_iterator it; + + updateRect(); + + LLDockableFloater* floater = dynamic_cast(LLDockableFloater::getInstanceHandle().get()); + + // Use a local variable instead of mToastList. + // mToastList can be modified during recursive calls and then all iteratos will be invalidated. + std::vector vToastList( mToastList ); + + for(it = vToastList.rbegin(); it != vToastList.rend(); ++it) + { + if(it != vToastList.rbegin()) + { + LLToast* toast = (it-1)->getToast(); + if (!toast) + { + LL_WARNS() << "Attempt to display a deleted toast." << LL_ENDL; + return; + } + + top = toast->getRect().mBottom - toast->getTopPad(); + gSavedSettings.getS32("ToastGap"); + } + + LLToast* toast = it->getToast(); + if (!toast) + { + LL_WARNS() << "Attempt to display a deleted toast." << LL_ENDL; + return; + } + + toast_rect = toast->getRect(); + toast_rect.setLeftTopAndSize(channel_rect.mRight - toast_rect.getWidth(), + top, toast_rect.getWidth(), + toast_rect.getHeight()); + toast->setRect(toast_rect); + + if(floater && floater->overlapsScreenChannel()) + { + if(it == vToastList.rbegin()) + { + // move first toast above docked floater + S32 shift = -floater->getRect().getHeight(); + if(floater->getDockControl()) + { + shift -= floater->getDockControl()->getTongueHeight(); + } + toast->translate(0, shift); + } + + LLRect channel_rect = getChannelRect(); + // don't show toasts if there is not enough space + if(toast_rect.mBottom < channel_rect.mBottom) + { + break; + } + } + + bool stop_showing_toasts = toast->getRect().mBottom < channel_rect.mBottom; + + if(!stop_showing_toasts) + { + if( it != vToastList.rend()-1) + { + S32 toast_bottom = toast->getRect().mBottom - gSavedSettings.getS32("ToastGap"); + stop_showing_toasts = toast_bottom < channel_rect.mBottom; + } + } + + // at least one toast should be visible + if(it == vToastList.rbegin()) + { + stop_showing_toasts = false; + } + + if(stop_showing_toasts) + break; + + if (!toast->getVisible()) + { + // HACK + // EXT-2653: it is necessary to prevent overlapping for secondary showed toasts + toast->setVisible(true); + } + if (!toast->hasFocus()) + { + // Fixing Z-order of toasts (EXT-4862) + // Next toast will be positioned under this one. + gFloaterView->sendChildToBack(toast); + } + } + + // Dismiss toasts we don't have space for (STORM-391). + std::vector toasts_to_hide; + + if(it != vToastList.rend()) + { + mHiddenToastsNum = 0; + + for(; it != vToastList.rend(); it++) + { + LLToast* toast = it->getToast(); + if (toast) + { + toasts_to_hide.push_back(toast); + } + } + } + + for (std::vector::iterator it = toasts_to_hide.begin(), end_it = toasts_to_hide.end(); + it != end_it; + ++it) + { + (*it)->hide(); + } +} + +//-------------------------------------------------------------------------- +void LLScreenChannel::createStartUpToast(S32 notif_num, F32 timer) +{ + LLScreenChannelBase::updateRect(); + + LLRect toast_rect; + LLToast::Params p; + p.lifetime_secs = timer; + p.enable_hide_btn = false; + mStartUpToastPanel = new LLToast(p); + + if(!mStartUpToastPanel) + return; + + mStartUpToastPanel->setOnFadeCallback(boost::bind(&LLScreenChannel::onStartUpToastHide, this)); + + LLPanel* wrapper_panel = mStartUpToastPanel->getChild("wrapper_panel"); + LLTextBox* text_box = mStartUpToastPanel->getChild("toast_text"); + + std::string text = LLTrans::getString("StartUpNotifications"); + + toast_rect = mStartUpToastPanel->getRect(); + mStartUpToastPanel->reshape(getRect().getWidth(), toast_rect.getHeight(), true); + + text_box->setValue(text); + text_box->setVisible(true); + + text_box->reshapeToFitText(); + text_box->setOrigin(text_box->getRect().mLeft, (wrapper_panel->getRect().getHeight() - text_box->getRect().getHeight())/2); + + toast_rect.setLeftTopAndSize(0, getRect().getHeight() - gSavedSettings.getS32("ToastGap"), getRect().getWidth(), toast_rect.getHeight()); + mStartUpToastPanel->setRect(toast_rect); + + addChild(mStartUpToastPanel); + + mStartUpToastPanel->setVisible(true); +} + +// static -------------------------------------------------------------------------- +F32 LLScreenChannel::getHeightRatio() +{ + F32 ratio = gSavedSettings.getF32("NotificationChannelHeightRatio"); + if(0.0f > ratio) + { + ratio = 0.0f; + } + else if(1.0f < ratio) + { + ratio = 1.0f; + } + return ratio; +} + +//-------------------------------------------------------------------------- +void LLScreenChannel::updateStartUpString(S32 num) +{ + // *TODO: update string if notifications are arriving while the StartUp toast is on a screen +} + +//-------------------------------------------------------------------------- +void LLScreenChannel::onStartUpToastHide() +{ + onCommit(); +} + +//-------------------------------------------------------------------------- +void LLScreenChannel::closeStartUpToast() +{ + if(mStartUpToastPanel != NULL) + { + mStartUpToastPanel->setVisible(false); + mStartUpToastPanel = NULL; + } +} + +void LLNotificationsUI::LLScreenChannel::stopToastTimer(LLToast* toast) +{ + if (!toast || toast != mHoveredToast) return; + + // Pause fade timer of the hovered toast. + toast->stopTimer(); +} + +void LLNotificationsUI::LLScreenChannel::startToastTimer(LLToast* toast) +{ + if (!toast || toast == mHoveredToast) + { + return; + } + + // Reset its fade timer. + toast->startTimer(); +} + +//-------------------------------------------------------------------------- +void LLScreenChannel::hideToastsFromScreen() +{ + for(std::vector::iterator it = mToastList.begin(); it != mToastList.end(); it++) + { + LLToast* toast = it->getToast(); + if (toast) + { + toast->setVisible(false); + } + else + { + LL_WARNS() << "Attempt to hide a deleted toast." << LL_ENDL; + } + } +} + +//-------------------------------------------------------------------------- +void LLScreenChannel::hideToast(const LLUUID& notification_id) +{ + std::vector::iterator it = find(mToastList.begin(), mToastList.end(), notification_id); + if(mToastList.end() != it) + { + LLToast* toast = it->getToast(); + if (toast) + { + toast->hide(); + } + else + { + LL_WARNS() << "Attempt to hide a deleted toast." << LL_ENDL; + } + } +} + +void LLScreenChannel::closeHiddenToasts(const Matcher& matcher) +{ + // since we can't guarantee that close toast operation doesn't change mToastList + // we collect matched toasts that should be closed into separate list + std::list toasts; + for (std::vector::iterator it = mToastList.begin(); it + != mToastList.end(); it++) + { + LLToast* toast = it->getToast(); + // add to list valid toast that match to provided matcher criteria + if (toast != NULL && !toast->isDead() && toast->getNotification() != NULL + && !toast->getVisible() && matcher.matches(toast->getNotification())) + { + toasts.push_back(toast); + } + } + + // close collected toasts + for (std::list::iterator it = toasts.begin(); it + != toasts.end(); it++) + { + LLToast* toast = *it; + toast->closeFloater(); + } +} + +//-------------------------------------------------------------------------- +void LLScreenChannel::removeToastsFromChannel() +{ + hideToastsFromScreen(); + for(std::vector::iterator it = mToastList.begin(); it != mToastList.end(); it++) + { + deleteToast(it->getToast()); + } + mToastList.clear(); +} + +//-------------------------------------------------------------------------- +void LLScreenChannel::removeAndStoreAllStorableToasts() +{ + if(mToastList.size() == 0) + return; + + hideToastsFromScreen(); + for(std::vector::iterator it = mToastList.begin(); it != mToastList.end();) + { + LLToast* toast = it->getToast(); + if(toast && toast->getCanBeStored()) + { + storeToast(*it); + it = mToastList.erase(it); + } + else + { + ++it; + } + } + redrawToasts(); +} + +//-------------------------------------------------------------------------- +void LLScreenChannel::removeToastsBySessionID(LLUUID id) +{ + if(mToastList.size() == 0) + return; + + hideToastsFromScreen(); + for(std::vector::iterator it = mToastList.begin(); it != mToastList.end();) + { + LLToast* toast = it->getToast(); + if(toast && toast->getSessionID() == id) + { + deleteToast(toast); + it = mToastList.erase(it); + } + else + { + ++it; + } + } + redrawToasts(); +} + +//-------------------------------------------------------------------------- +void LLScreenChannel::onToastHover(LLToast* toast, bool mouse_enter) +{ + // because of LLViewerWindow::updateUI() that NOT ALWAYS calls onMouseEnter BEFORE onMouseLeave + // we must check hovering directly to prevent incorrect setting for hovering in a channel + if (mouse_enter) + { + if (toast->isHovered()) + { + mHoveredToast = toast; + } + } + else if (mHoveredToast != NULL) + { + if (!mHoveredToast->isHovered()) + { + mHoveredToast = NULL; + } + } + + redrawToasts(); +} + +//-------------------------------------------------------------------------- +void LLScreenChannel::updateShowToastsState() +{ + LLDockableFloater* floater = dynamic_cast(LLDockableFloater::getInstanceHandle().get()); + + if(!floater) + { + setShowToasts(true); + return; + } + + updateRect(); +} + +//-------------------------------------------------------------------------- + +LLToast* LLScreenChannel::getToastByNotificationID(LLUUID id) +{ + std::vector::iterator it = find(mStoredToastList.begin(), + mStoredToastList.end(), id); + + if (it == mStoredToastList.end()) + return NULL; + + return it->getToast(); +} diff --git a/indra/newview/llscreenchannel.h b/indra/newview/llscreenchannel.h index f28181f63a..22fb41e559 100644 --- a/indra/newview/llscreenchannel.h +++ b/indra/newview/llscreenchannel.h @@ -1,313 +1,313 @@ -/** - * @file llscreenchannel.h - * @brief Class implements a channel on a screen in which appropriate toasts may appear. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLSCREENCHANNEL_H -#define LL_LLSCREENCHANNEL_H - -#include "lltoast.h" - -#include -#include - -namespace LLNotificationsUI -{ - const LLUUID ALERT_CHANNEL_UUID("F3E07BC8-A973-476D-8C7F-F3B7293975D1"); - const LLUUID NOTIFICATION_CHANNEL_UUID("AEED3193-8709-4693-8558-7452CCA97AE5"); - const LLUUID NEARBY_CHAT_CHANNEL_UUID("E1158BD6-661C-4981-9DAD-4DCBFF062502"); - const LLUUID STARTUP_CHANNEL_UUID("B56AF90D-6684-48E4-B1E4-722D3DEB2CB6"); - - const S32 NOTIFY_BOX_WIDTH = 305; - -typedef enum e_notification_toast_alignment -{ - NA_TOP, - NA_CENTRE, - NA_BOTTOM, -} EToastAlignment; - -typedef enum e_channel_alignment -{ - CA_LEFT, - CA_CENTRE, - CA_RIGHT, -} EChannelAlignment; - -class LLScreenChannelBase : public LLUICtrl -{ - friend class LLChannelManager; -public: - struct Params : public LLInitParam::Block - { - Mandatory id; - Optional display_toasts_always; - Optional toast_align; - Optional channel_align; - - Params() - : id("id", LLUUID("")), - display_toasts_always("display_toasts_always", false), - toast_align("toast_align", NA_BOTTOM), - channel_align("channel_align", CA_LEFT) - {} - }; - - LLScreenChannelBase(const Params&); - - bool postBuild(); - - void reshape(S32 width, S32 height, bool called_from_parent = true); - - // Channel's outfit-functions - // update channel's size and position in the World View - virtual void updatePositionAndSize(LLRect rect); - - // initialization of channel's shape and position - virtual void init(S32 channel_left, S32 channel_right); - - // kill or modify a toast by its ID - virtual void killToastByNotificationID(LLUUID id) {}; - virtual void modifyToastNotificationByID(LLUUID id, LLSD data) {}; - virtual void removeToastByNotificationID(LLUUID id){}; - - // hide all toasts from screen, but not remove them from a channel - virtual void hideToastsFromScreen() {}; - // removes all toasts from a channel - virtual void removeToastsFromChannel() {}; - - // show all toasts in a channel - virtual void redrawToasts() {}; - - - // Channel's behavior-functions - // set whether a channel will control hovering inside itself or not - virtual void setControlHovering(bool control) { mControlHovering = control; } - - - bool isHovering(); - - void setCanStoreToasts(bool store) { mCanStoreToasts = store; } - - bool getDisplayToastsAlways() { return mDisplayToastsAlways; } - - // get number of hidden notifications from a channel - S32 getNumberOfHiddenToasts() { return mHiddenToastsNum;} - - - void setShowToasts(bool show) { mShowToasts = show; } - bool getShowToasts() { return mShowToasts; } - - // get toast allignment preset for a channel - e_notification_toast_alignment getToastAlignment() {return mToastAlignment;} - - // get ID of a channel - LLUUID getChannelID() { return mID; } - LLHandle getHandle() { return getDerivedHandle(); } - -protected: - void updateRect(); - LLRect getChannelRect(); - - // Channel's flags - bool mControlHovering; - LLToast* mHoveredToast; - bool mCanStoreToasts; - bool mDisplayToastsAlways; - // controls whether a channel shows toasts or not - bool mShowToasts; - // - EToastAlignment mToastAlignment; - EChannelAlignment mChannelAlignment; - - S32 mHiddenToastsNum; - - // channel's ID - LLUUID mID; - - LLView* mFloaterSnapRegion; - LLView* mChicletRegion; -}; - - -/** - * Screen channel manages toasts visibility and positioning on the screen. - */ -class LLScreenChannel : public LLScreenChannelBase -{ - friend class LLChannelManager; -public: - LLScreenChannel(const Params&); - virtual ~LLScreenChannel(); - - class Matcher - { - public: - Matcher(){} - virtual ~Matcher() {} - virtual bool matches(const LLNotificationPtr) const = 0; - }; - - std::list findToasts(const Matcher& matcher); - - // Channel's outfit-functions - // update channel's size and position in the World View - void updatePositionAndSize(LLRect new_rect); - // initialization of channel's shape and position - void init(S32 channel_left, S32 channel_right); - - // Operating with toasts - // add a toast to a channel - void addToast(const LLToast::Params& p); - // kill or modify a toast by its ID - void killToastByNotificationID(LLUUID id); - void removeToastByNotificationID(LLUUID id); - void killMatchedToasts(const Matcher& matcher); - void modifyToastByNotificationID(LLUUID id, LLPanel* panel); - // hide all toasts from screen, but not remove them from a channel - void hideToastsFromScreen(); - // hide toast by notification id - void hideToast(const LLUUID& notification_id); - - /** - * Closes hidden matched toasts from channel. - */ - void closeHiddenToasts(const Matcher& matcher); - - // removes all toasts from a channel - void removeToastsFromChannel(); - // show all toasts in a channel - void redrawToasts(); - // - void loadStoredToastsToChannel(); - // finds a toast among stored by its Notification ID and throws it on a screen to a channel - void loadStoredToastByNotificationIDToChannel(LLUUID id); - // removes from channel all toasts that belongs to the certain IM session - void removeToastsBySessionID(LLUUID id); - // remove all storable toasts from screen and store them - void removeAndStoreAllStorableToasts(); - // close the StartUp Toast - void closeStartUpToast(); - - - /** Stop fading given toast */ - virtual void stopToastTimer(LLToast* toast); - - /** Start fading given toast */ - virtual void startToastTimer(LLToast* toast); - - // get StartUp Toast's state - static bool getStartUpToastShown() { return mWasStartUpToastShown; } - // tell all channels that the StartUp toast was shown and allow them showing of toasts - static void setStartUpToastShown() { mWasStartUpToastShown = true; } - // let a channel update its ShowToast flag - void updateShowToastsState(); - - - // Channel's other interface functions functions - // update number of notifications in the StartUp Toast - void updateStartUpString(S32 num); - - LLToast* getToastByNotificationID(LLUUID id); - - // Channel's signals - // signal on storing of faded toasts event - typedef boost::signals2::signal store_toast_signal_t; - boost::signals2::connection addOnStoreToastCallback(store_toast_signal_t::slot_type cb) { return mOnStoreToast.connect(cb); } - -private: - store_toast_signal_t mOnStoreToast; - - class ToastElem - { - public: - ToastElem(const LLHandle& toast) : mToast(toast) - { - } - - ToastElem(const ToastElem& toast_elem) : mToast(toast_elem.mToast) - { - } - - LLToast* getToast() const - { - return mToast.get(); - } - - LLUUID getID() const - { - return mToast.isDead() ? LLUUID() : mToast.get()->getNotificationID(); - } - - bool operator == (const LLUUID &id_op) const - { - return (getID() == id_op); - } - - bool operator == (LLPanel* panel_op) const - { - return (mToast.get() == panel_op); - } - - private: - LLHandle mToast; - }; - - // Channel's handlers - void onToastHover(LLToast* toast, bool mouse_enter); - void onToastFade(LLToast* toast); - void onToastDestroyed(LLToast* toast); - void onStartUpToastHide(); - - // - void storeToast(ToastElem& toast_elem); - // send signal to observers about destroying of a toast, update channel's Hovering state, close the toast - void deleteToast(LLToast* toast); - - // show-functions depending on allignment of toasts - void showToastsBottom(); - void showToastsCentre(); - void showToastsTop(); - - // create the StartUp Toast - void createStartUpToast(S32 notif_num, F32 timer); - - /** - * Notification channel and World View ratio(0.0 - always show 1 notification, 1.0 - max ratio). - */ - static F32 getHeightRatio(); - - // Channel's flags - static bool mWasStartUpToastShown; - - // attributes for the StartUp Toast - LLToast* mStartUpToastPanel; - - - std::vector mToastList; - std::vector mStoredToastList; -}; - -} -#endif +/** + * @file llscreenchannel.h + * @brief Class implements a channel on a screen in which appropriate toasts may appear. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSCREENCHANNEL_H +#define LL_LLSCREENCHANNEL_H + +#include "lltoast.h" + +#include +#include + +namespace LLNotificationsUI +{ + const LLUUID ALERT_CHANNEL_UUID("F3E07BC8-A973-476D-8C7F-F3B7293975D1"); + const LLUUID NOTIFICATION_CHANNEL_UUID("AEED3193-8709-4693-8558-7452CCA97AE5"); + const LLUUID NEARBY_CHAT_CHANNEL_UUID("E1158BD6-661C-4981-9DAD-4DCBFF062502"); + const LLUUID STARTUP_CHANNEL_UUID("B56AF90D-6684-48E4-B1E4-722D3DEB2CB6"); + + const S32 NOTIFY_BOX_WIDTH = 305; + +typedef enum e_notification_toast_alignment +{ + NA_TOP, + NA_CENTRE, + NA_BOTTOM, +} EToastAlignment; + +typedef enum e_channel_alignment +{ + CA_LEFT, + CA_CENTRE, + CA_RIGHT, +} EChannelAlignment; + +class LLScreenChannelBase : public LLUICtrl +{ + friend class LLChannelManager; +public: + struct Params : public LLInitParam::Block + { + Mandatory id; + Optional display_toasts_always; + Optional toast_align; + Optional channel_align; + + Params() + : id("id", LLUUID("")), + display_toasts_always("display_toasts_always", false), + toast_align("toast_align", NA_BOTTOM), + channel_align("channel_align", CA_LEFT) + {} + }; + + LLScreenChannelBase(const Params&); + + bool postBuild(); + + void reshape(S32 width, S32 height, bool called_from_parent = true); + + // Channel's outfit-functions + // update channel's size and position in the World View + virtual void updatePositionAndSize(LLRect rect); + + // initialization of channel's shape and position + virtual void init(S32 channel_left, S32 channel_right); + + // kill or modify a toast by its ID + virtual void killToastByNotificationID(LLUUID id) {}; + virtual void modifyToastNotificationByID(LLUUID id, LLSD data) {}; + virtual void removeToastByNotificationID(LLUUID id){}; + + // hide all toasts from screen, but not remove them from a channel + virtual void hideToastsFromScreen() {}; + // removes all toasts from a channel + virtual void removeToastsFromChannel() {}; + + // show all toasts in a channel + virtual void redrawToasts() {}; + + + // Channel's behavior-functions + // set whether a channel will control hovering inside itself or not + virtual void setControlHovering(bool control) { mControlHovering = control; } + + + bool isHovering(); + + void setCanStoreToasts(bool store) { mCanStoreToasts = store; } + + bool getDisplayToastsAlways() { return mDisplayToastsAlways; } + + // get number of hidden notifications from a channel + S32 getNumberOfHiddenToasts() { return mHiddenToastsNum;} + + + void setShowToasts(bool show) { mShowToasts = show; } + bool getShowToasts() { return mShowToasts; } + + // get toast allignment preset for a channel + e_notification_toast_alignment getToastAlignment() {return mToastAlignment;} + + // get ID of a channel + LLUUID getChannelID() { return mID; } + LLHandle getHandle() { return getDerivedHandle(); } + +protected: + void updateRect(); + LLRect getChannelRect(); + + // Channel's flags + bool mControlHovering; + LLToast* mHoveredToast; + bool mCanStoreToasts; + bool mDisplayToastsAlways; + // controls whether a channel shows toasts or not + bool mShowToasts; + // + EToastAlignment mToastAlignment; + EChannelAlignment mChannelAlignment; + + S32 mHiddenToastsNum; + + // channel's ID + LLUUID mID; + + LLView* mFloaterSnapRegion; + LLView* mChicletRegion; +}; + + +/** + * Screen channel manages toasts visibility and positioning on the screen. + */ +class LLScreenChannel : public LLScreenChannelBase +{ + friend class LLChannelManager; +public: + LLScreenChannel(const Params&); + virtual ~LLScreenChannel(); + + class Matcher + { + public: + Matcher(){} + virtual ~Matcher() {} + virtual bool matches(const LLNotificationPtr) const = 0; + }; + + std::list findToasts(const Matcher& matcher); + + // Channel's outfit-functions + // update channel's size and position in the World View + void updatePositionAndSize(LLRect new_rect); + // initialization of channel's shape and position + void init(S32 channel_left, S32 channel_right); + + // Operating with toasts + // add a toast to a channel + void addToast(const LLToast::Params& p); + // kill or modify a toast by its ID + void killToastByNotificationID(LLUUID id); + void removeToastByNotificationID(LLUUID id); + void killMatchedToasts(const Matcher& matcher); + void modifyToastByNotificationID(LLUUID id, LLPanel* panel); + // hide all toasts from screen, but not remove them from a channel + void hideToastsFromScreen(); + // hide toast by notification id + void hideToast(const LLUUID& notification_id); + + /** + * Closes hidden matched toasts from channel. + */ + void closeHiddenToasts(const Matcher& matcher); + + // removes all toasts from a channel + void removeToastsFromChannel(); + // show all toasts in a channel + void redrawToasts(); + // + void loadStoredToastsToChannel(); + // finds a toast among stored by its Notification ID and throws it on a screen to a channel + void loadStoredToastByNotificationIDToChannel(LLUUID id); + // removes from channel all toasts that belongs to the certain IM session + void removeToastsBySessionID(LLUUID id); + // remove all storable toasts from screen and store them + void removeAndStoreAllStorableToasts(); + // close the StartUp Toast + void closeStartUpToast(); + + + /** Stop fading given toast */ + virtual void stopToastTimer(LLToast* toast); + + /** Start fading given toast */ + virtual void startToastTimer(LLToast* toast); + + // get StartUp Toast's state + static bool getStartUpToastShown() { return mWasStartUpToastShown; } + // tell all channels that the StartUp toast was shown and allow them showing of toasts + static void setStartUpToastShown() { mWasStartUpToastShown = true; } + // let a channel update its ShowToast flag + void updateShowToastsState(); + + + // Channel's other interface functions functions + // update number of notifications in the StartUp Toast + void updateStartUpString(S32 num); + + LLToast* getToastByNotificationID(LLUUID id); + + // Channel's signals + // signal on storing of faded toasts event + typedef boost::signals2::signal store_toast_signal_t; + boost::signals2::connection addOnStoreToastCallback(store_toast_signal_t::slot_type cb) { return mOnStoreToast.connect(cb); } + +private: + store_toast_signal_t mOnStoreToast; + + class ToastElem + { + public: + ToastElem(const LLHandle& toast) : mToast(toast) + { + } + + ToastElem(const ToastElem& toast_elem) : mToast(toast_elem.mToast) + { + } + + LLToast* getToast() const + { + return mToast.get(); + } + + LLUUID getID() const + { + return mToast.isDead() ? LLUUID() : mToast.get()->getNotificationID(); + } + + bool operator == (const LLUUID &id_op) const + { + return (getID() == id_op); + } + + bool operator == (LLPanel* panel_op) const + { + return (mToast.get() == panel_op); + } + + private: + LLHandle mToast; + }; + + // Channel's handlers + void onToastHover(LLToast* toast, bool mouse_enter); + void onToastFade(LLToast* toast); + void onToastDestroyed(LLToast* toast); + void onStartUpToastHide(); + + // + void storeToast(ToastElem& toast_elem); + // send signal to observers about destroying of a toast, update channel's Hovering state, close the toast + void deleteToast(LLToast* toast); + + // show-functions depending on allignment of toasts + void showToastsBottom(); + void showToastsCentre(); + void showToastsTop(); + + // create the StartUp Toast + void createStartUpToast(S32 notif_num, F32 timer); + + /** + * Notification channel and World View ratio(0.0 - always show 1 notification, 1.0 - max ratio). + */ + static F32 getHeightRatio(); + + // Channel's flags + static bool mWasStartUpToastShown; + + // attributes for the StartUp Toast + LLToast* mStartUpToastPanel; + + + std::vector mToastList; + std::vector mStoredToastList; +}; + +} +#endif diff --git a/indra/newview/llscripteditor.cpp b/indra/newview/llscripteditor.cpp index 74b99d575e..6eb8cf0b37 100644 --- a/indra/newview/llscripteditor.cpp +++ b/indra/newview/llscripteditor.cpp @@ -1,246 +1,246 @@ -/** - * @file llscripteditor.cpp - * @author Cinder Roxley - * @brief Text editor widget used for viewing and editing scripts - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llscripteditor.h" - -#include "llsyntaxid.h" -#include "lllocalcliprect.h" -#include "llviewercontrol.h" - -const S32 UI_TEXTEDITOR_LINE_NUMBER_MARGIN = 32; - -static LLDefaultChildRegistry::Register r("script_editor"); - -LLScriptEditor::Params::Params() -: show_line_numbers("show_line_numbers", true), - default_font_size("default_font_size", false) -{} - - -LLScriptEditor::LLScriptEditor(const Params& p) -: LLTextEditor(p) -, mShowLineNumbers(p.show_line_numbers), - mUseDefaultFontSize(p.default_font_size) -{ - if (mShowLineNumbers) - { - mHPad += UI_TEXTEDITOR_LINE_NUMBER_MARGIN; - updateRects(); - } -} - -bool LLScriptEditor::postBuild() -{ - gSavedSettings.getControl("LSLFontSizeName")->getCommitSignal()->connect(boost::bind(&LLScriptEditor::onFontSizeChange, this)); - return LLTextEditor::postBuild(); -} - -void LLScriptEditor::draw() -{ - { - // pad clipping rectangle so that cursor can draw at full width - // when at left edge of mVisibleTextRect - LLRect clip_rect(mVisibleTextRect); - clip_rect.stretch(1); - LLLocalClipRect clip(clip_rect); - } - - LLTextBase::draw(); - drawLineNumbers(); - - drawPreeditMarker(); - - //RN: the decision was made to always show the orange border for keyboard focus but do not put an insertion caret - // when in readonly mode - mBorder->setKeyboardFocusHighlight( hasFocus() );// && !mReadOnly); -} - -void LLScriptEditor::drawLineNumbers() -{ - LLGLSUIDefault gls_ui; - LLRect scrolled_view_rect = getVisibleDocumentRect(); - LLRect content_rect = getVisibleTextRect(); - LLLocalClipRect clip(content_rect); - S32 first_line = getFirstVisibleLine(); - S32 num_lines = getLineCount(); - if (first_line >= num_lines) - { - return; - } - - S32 cursor_line = mLineInfoList[getLineNumFromDocIndex(mCursorPos)].mLineNum; - - if (mShowLineNumbers) - { - S32 left = 0; - S32 top = getRect().getHeight(); - S32 bottom = 0; - - gl_rect_2d(left, top, UI_TEXTEDITOR_LINE_NUMBER_MARGIN, bottom, mReadOnlyBgColor.get() ); // line number area always read-only - gl_rect_2d(UI_TEXTEDITOR_LINE_NUMBER_MARGIN, top, UI_TEXTEDITOR_LINE_NUMBER_MARGIN-1, bottom, LLColor4::grey3); // separator - - S32 last_line_num = -1; - - for (S32 cur_line = first_line; cur_line < num_lines; cur_line++) - { - line_info& line = mLineInfoList[cur_line]; - - if ((line.mRect.mTop - scrolled_view_rect.mBottom) < mVisibleTextRect.mBottom) - { - break; - } - - S32 line_bottom = line.mRect.mBottom - scrolled_view_rect.mBottom + mVisibleTextRect.mBottom; - // draw the line numbers - if(line.mLineNum != last_line_num && line.mRect.mTop <= scrolled_view_rect.mTop) - { - const LLWString ltext = utf8str_to_wstring(llformat("%d", line.mLineNum )); - bool is_cur_line = cursor_line == line.mLineNum; - const U8 style = is_cur_line ? LLFontGL::BOLD : LLFontGL::NORMAL; - const LLColor4 fg_color = is_cur_line ? mCursorColor : mReadOnlyFgColor; - getScriptFont()->render( - ltext, // string to draw - 0, // begin offset - UI_TEXTEDITOR_LINE_NUMBER_MARGIN - 2, // x - line_bottom, // y - fg_color, - LLFontGL::RIGHT, // horizontal alignment - LLFontGL::BOTTOM, // vertical alignment - style, - LLFontGL::NO_SHADOW, - S32_MAX, // max chars - UI_TEXTEDITOR_LINE_NUMBER_MARGIN - 2); // max pixels - last_line_num = line.mLineNum; - } - } - } -} - -void LLScriptEditor::initKeywords() -{ - mKeywords.initialize(LLSyntaxIdLSL::getInstance()->getKeywordsXML()); -} - -void LLScriptEditor::loadKeywords() -{ - LL_PROFILE_ZONE_SCOPED; - mKeywords.processTokens(); - - LLStyleConstSP style = new LLStyle(LLStyle::Params().font(getScriptFont()).color(mDefaultColor.get())); - - segment_vec_t segment_list; - mKeywords.findSegments(&segment_list, getWText(), *this, style); - - mSegments.clear(); - segment_set_t::iterator insert_it = mSegments.begin(); - for (segment_vec_t::iterator list_it = segment_list.begin(); list_it != segment_list.end(); ++list_it) - { - insert_it = mSegments.insert(insert_it, *list_it); - } -} - -void LLScriptEditor::updateSegments() -{ - if (mReflowIndex < S32_MAX && mKeywords.isLoaded() && mParseOnTheFly) - { - LL_PROFILE_ZONE_SCOPED; - - LLStyleConstSP style = new LLStyle(LLStyle::Params().font(getScriptFont()).color(mDefaultColor.get())); - - // HACK: No non-ascii keywords for now - segment_vec_t segment_list; - mKeywords.findSegments(&segment_list, getWText(), *this, style); - - clearSegments(); - for (segment_vec_t::iterator list_it = segment_list.begin(); list_it != segment_list.end(); ++list_it) - { - insertSegment(*list_it); - } - } - - LLTextBase::updateSegments(); -} - -void LLScriptEditor::clearSegments() -{ - if (!mSegments.empty()) - { - mSegments.clear(); - } -} - -// Most of this is shamelessly copied from LLTextBase -void LLScriptEditor::drawSelectionBackground() -{ - // Draw selection even if we don't have keyboard focus for search/replace - if( hasSelection() && !mLineInfoList.empty()) - { - std::vector selection_rects = getSelectionRects(); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - const LLColor4& color = mReadOnly ? mReadOnlyFgColor : mFgColor; - F32 alpha = hasFocus() ? 0.7f : 0.3f; - alpha *= getDrawContext().mAlpha; - // We want to shift the color to something readable but distinct - LLColor4 selection_color((1.f + color.mV[VRED]) * 0.5f, - (1.f + color.mV[VGREEN]) * 0.5f, - (1.f + color.mV[VBLUE]) * 0.5f, - alpha); - LLRect content_display_rect = getVisibleDocumentRect(); - - for (std::vector::iterator rect_it = selection_rects.begin(); - rect_it != selection_rects.end(); - ++rect_it) - { - LLRect selection_rect = *rect_it; - selection_rect = *rect_it; - selection_rect.translate(mVisibleTextRect.mLeft - content_display_rect.mLeft, mVisibleTextRect.mBottom - content_display_rect.mBottom); - gl_rect_2d(selection_rect, selection_color); - } - } -} - -std::string LLScriptEditor::getScriptFontSize() -{ - static LLCachedControl size_name(gSavedSettings, "LSLFontSizeName", "Monospace"); - return size_name; -} - -LLFontGL* LLScriptEditor::getScriptFont() -{ - std::string font_size_name = mUseDefaultFontSize ? "Monospace" : getScriptFontSize(); - return LLFontGL::getFont(LLFontDescriptor("Monospace", font_size_name, 0)); -} - -void LLScriptEditor::onFontSizeChange() -{ - if (!mUseDefaultFontSize) - { - needsReflow(); - } -} +/** + * @file llscripteditor.cpp + * @author Cinder Roxley + * @brief Text editor widget used for viewing and editing scripts + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llscripteditor.h" + +#include "llsyntaxid.h" +#include "lllocalcliprect.h" +#include "llviewercontrol.h" + +const S32 UI_TEXTEDITOR_LINE_NUMBER_MARGIN = 32; + +static LLDefaultChildRegistry::Register r("script_editor"); + +LLScriptEditor::Params::Params() +: show_line_numbers("show_line_numbers", true), + default_font_size("default_font_size", false) +{} + + +LLScriptEditor::LLScriptEditor(const Params& p) +: LLTextEditor(p) +, mShowLineNumbers(p.show_line_numbers), + mUseDefaultFontSize(p.default_font_size) +{ + if (mShowLineNumbers) + { + mHPad += UI_TEXTEDITOR_LINE_NUMBER_MARGIN; + updateRects(); + } +} + +bool LLScriptEditor::postBuild() +{ + gSavedSettings.getControl("LSLFontSizeName")->getCommitSignal()->connect(boost::bind(&LLScriptEditor::onFontSizeChange, this)); + return LLTextEditor::postBuild(); +} + +void LLScriptEditor::draw() +{ + { + // pad clipping rectangle so that cursor can draw at full width + // when at left edge of mVisibleTextRect + LLRect clip_rect(mVisibleTextRect); + clip_rect.stretch(1); + LLLocalClipRect clip(clip_rect); + } + + LLTextBase::draw(); + drawLineNumbers(); + + drawPreeditMarker(); + + //RN: the decision was made to always show the orange border for keyboard focus but do not put an insertion caret + // when in readonly mode + mBorder->setKeyboardFocusHighlight( hasFocus() );// && !mReadOnly); +} + +void LLScriptEditor::drawLineNumbers() +{ + LLGLSUIDefault gls_ui; + LLRect scrolled_view_rect = getVisibleDocumentRect(); + LLRect content_rect = getVisibleTextRect(); + LLLocalClipRect clip(content_rect); + S32 first_line = getFirstVisibleLine(); + S32 num_lines = getLineCount(); + if (first_line >= num_lines) + { + return; + } + + S32 cursor_line = mLineInfoList[getLineNumFromDocIndex(mCursorPos)].mLineNum; + + if (mShowLineNumbers) + { + S32 left = 0; + S32 top = getRect().getHeight(); + S32 bottom = 0; + + gl_rect_2d(left, top, UI_TEXTEDITOR_LINE_NUMBER_MARGIN, bottom, mReadOnlyBgColor.get() ); // line number area always read-only + gl_rect_2d(UI_TEXTEDITOR_LINE_NUMBER_MARGIN, top, UI_TEXTEDITOR_LINE_NUMBER_MARGIN-1, bottom, LLColor4::grey3); // separator + + S32 last_line_num = -1; + + for (S32 cur_line = first_line; cur_line < num_lines; cur_line++) + { + line_info& line = mLineInfoList[cur_line]; + + if ((line.mRect.mTop - scrolled_view_rect.mBottom) < mVisibleTextRect.mBottom) + { + break; + } + + S32 line_bottom = line.mRect.mBottom - scrolled_view_rect.mBottom + mVisibleTextRect.mBottom; + // draw the line numbers + if(line.mLineNum != last_line_num && line.mRect.mTop <= scrolled_view_rect.mTop) + { + const LLWString ltext = utf8str_to_wstring(llformat("%d", line.mLineNum )); + bool is_cur_line = cursor_line == line.mLineNum; + const U8 style = is_cur_line ? LLFontGL::BOLD : LLFontGL::NORMAL; + const LLColor4 fg_color = is_cur_line ? mCursorColor : mReadOnlyFgColor; + getScriptFont()->render( + ltext, // string to draw + 0, // begin offset + UI_TEXTEDITOR_LINE_NUMBER_MARGIN - 2, // x + line_bottom, // y + fg_color, + LLFontGL::RIGHT, // horizontal alignment + LLFontGL::BOTTOM, // vertical alignment + style, + LLFontGL::NO_SHADOW, + S32_MAX, // max chars + UI_TEXTEDITOR_LINE_NUMBER_MARGIN - 2); // max pixels + last_line_num = line.mLineNum; + } + } + } +} + +void LLScriptEditor::initKeywords() +{ + mKeywords.initialize(LLSyntaxIdLSL::getInstance()->getKeywordsXML()); +} + +void LLScriptEditor::loadKeywords() +{ + LL_PROFILE_ZONE_SCOPED; + mKeywords.processTokens(); + + LLStyleConstSP style = new LLStyle(LLStyle::Params().font(getScriptFont()).color(mDefaultColor.get())); + + segment_vec_t segment_list; + mKeywords.findSegments(&segment_list, getWText(), *this, style); + + mSegments.clear(); + segment_set_t::iterator insert_it = mSegments.begin(); + for (segment_vec_t::iterator list_it = segment_list.begin(); list_it != segment_list.end(); ++list_it) + { + insert_it = mSegments.insert(insert_it, *list_it); + } +} + +void LLScriptEditor::updateSegments() +{ + if (mReflowIndex < S32_MAX && mKeywords.isLoaded() && mParseOnTheFly) + { + LL_PROFILE_ZONE_SCOPED; + + LLStyleConstSP style = new LLStyle(LLStyle::Params().font(getScriptFont()).color(mDefaultColor.get())); + + // HACK: No non-ascii keywords for now + segment_vec_t segment_list; + mKeywords.findSegments(&segment_list, getWText(), *this, style); + + clearSegments(); + for (segment_vec_t::iterator list_it = segment_list.begin(); list_it != segment_list.end(); ++list_it) + { + insertSegment(*list_it); + } + } + + LLTextBase::updateSegments(); +} + +void LLScriptEditor::clearSegments() +{ + if (!mSegments.empty()) + { + mSegments.clear(); + } +} + +// Most of this is shamelessly copied from LLTextBase +void LLScriptEditor::drawSelectionBackground() +{ + // Draw selection even if we don't have keyboard focus for search/replace + if( hasSelection() && !mLineInfoList.empty()) + { + std::vector selection_rects = getSelectionRects(); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + const LLColor4& color = mReadOnly ? mReadOnlyFgColor : mFgColor; + F32 alpha = hasFocus() ? 0.7f : 0.3f; + alpha *= getDrawContext().mAlpha; + // We want to shift the color to something readable but distinct + LLColor4 selection_color((1.f + color.mV[VRED]) * 0.5f, + (1.f + color.mV[VGREEN]) * 0.5f, + (1.f + color.mV[VBLUE]) * 0.5f, + alpha); + LLRect content_display_rect = getVisibleDocumentRect(); + + for (std::vector::iterator rect_it = selection_rects.begin(); + rect_it != selection_rects.end(); + ++rect_it) + { + LLRect selection_rect = *rect_it; + selection_rect = *rect_it; + selection_rect.translate(mVisibleTextRect.mLeft - content_display_rect.mLeft, mVisibleTextRect.mBottom - content_display_rect.mBottom); + gl_rect_2d(selection_rect, selection_color); + } + } +} + +std::string LLScriptEditor::getScriptFontSize() +{ + static LLCachedControl size_name(gSavedSettings, "LSLFontSizeName", "Monospace"); + return size_name; +} + +LLFontGL* LLScriptEditor::getScriptFont() +{ + std::string font_size_name = mUseDefaultFontSize ? "Monospace" : getScriptFontSize(); + return LLFontGL::getFont(LLFontDescriptor("Monospace", font_size_name, 0)); +} + +void LLScriptEditor::onFontSizeChange() +{ + if (!mUseDefaultFontSize) + { + needsReflow(); + } +} diff --git a/indra/newview/llscripteditor.h b/indra/newview/llscripteditor.h index 71ae3c93f3..1632ebe834 100644 --- a/indra/newview/llscripteditor.h +++ b/indra/newview/llscripteditor.h @@ -1,76 +1,76 @@ -/** - * @file llscripteditor.h - * @author Cinder Roxley - * @brief Text editor widget used for viewing and editing scripts - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_SCRIPTEDITOR_H -#define LL_SCRIPTEDITOR_H - -#include "lltexteditor.h" - -class LLScriptEditor : public LLTextEditor -{ -public: - - struct Params : public LLInitParam::Block - { - Optional show_line_numbers; - Optional default_font_size; - Params(); - }; - - virtual ~LLScriptEditor() {}; - - // LLView override - virtual void draw(); - bool postBuild(); - - void initKeywords(); - void loadKeywords(); - /* virtual */ void clearSegments(); - LLKeywords::keyword_iterator_t keywordsBegin() { return mKeywords.begin(); } - LLKeywords::keyword_iterator_t keywordsEnd() { return mKeywords.end(); } - - static std::string getScriptFontSize(); - LLFontGL* getScriptFont(); - void onFontSizeChange(); - - protected: - friend class LLUICtrlFactory; - LLScriptEditor(const Params& p); - -private: - void drawLineNumbers(); - /* virtual */ void updateSegments(); - /* virtual */ void drawSelectionBackground(); - void loadKeywords(const std::string& filename_keywords, - const std::string& filename_colors); - - LLKeywords mKeywords; - bool mShowLineNumbers; - bool mUseDefaultFontSize; -}; - -#endif // LL_SCRIPTEDITOR_H +/** + * @file llscripteditor.h + * @author Cinder Roxley + * @brief Text editor widget used for viewing and editing scripts + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_SCRIPTEDITOR_H +#define LL_SCRIPTEDITOR_H + +#include "lltexteditor.h" + +class LLScriptEditor : public LLTextEditor +{ +public: + + struct Params : public LLInitParam::Block + { + Optional show_line_numbers; + Optional default_font_size; + Params(); + }; + + virtual ~LLScriptEditor() {}; + + // LLView override + virtual void draw(); + bool postBuild(); + + void initKeywords(); + void loadKeywords(); + /* virtual */ void clearSegments(); + LLKeywords::keyword_iterator_t keywordsBegin() { return mKeywords.begin(); } + LLKeywords::keyword_iterator_t keywordsEnd() { return mKeywords.end(); } + + static std::string getScriptFontSize(); + LLFontGL* getScriptFont(); + void onFontSizeChange(); + + protected: + friend class LLUICtrlFactory; + LLScriptEditor(const Params& p); + +private: + void drawLineNumbers(); + /* virtual */ void updateSegments(); + /* virtual */ void drawSelectionBackground(); + void loadKeywords(const std::string& filename_keywords, + const std::string& filename_colors); + + LLKeywords mKeywords; + bool mShowLineNumbers; + bool mUseDefaultFontSize; +}; + +#endif // LL_SCRIPTEDITOR_H diff --git a/indra/newview/llscriptfloater.cpp b/indra/newview/llscriptfloater.cpp index 64e4502563..22a9dd0027 100644 --- a/indra/newview/llscriptfloater.cpp +++ b/indra/newview/llscriptfloater.cpp @@ -1,778 +1,778 @@ -/** - * @file llscriptfloater.cpp - * @brief LLScriptFloater class definition - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llscriptfloater.h" -#include "llagentcamera.h" - -#include "llchannelmanager.h" -#include "llchiclet.h" -#include "llchicletbar.h" -#include "llfloaterreg.h" -#include "lllslconstants.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "llscreenchannel.h" -#include "llsyswellwindow.h" -#include "lltoastnotifypanel.h" -#include "lltoastscripttextbox.h" -#include "lltrans.h" -#include "llviewerobjectlist.h" -#include "llviewerwindow.h" -#include "llfloaterimsession.h" - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -LLUUID notification_id_to_object_id(const LLUUID& notification_id) -{ - LLNotificationPtr notification = LLNotifications::getInstance()->find(notification_id); - if(notification) - { - return notification->getPayload()["object_id"].asUUID(); - } - return LLUUID::null; -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - - -LLScriptFloater::LLScriptFloater(const LLSD& key) -: LLDockableFloater(NULL, true, key) -, mScriptForm(NULL) -, mSaveFloaterPosition(false) -{ - setMouseDownCallback(boost::bind(&LLScriptFloater::onMouseDown, this)); - setOverlapsScreenChannel(true); - mIsDockedStateForcedCallback = boost::bind(&LLAgentCamera::cameraMouselook, &gAgentCamera); -} - -bool LLScriptFloater::toggle(const LLUUID& notification_id) -{ - LLScriptFloater* floater = LLFloaterReg::findTypedInstance("script_floater", notification_id); - - // show existing floater - if(floater) - { - if(floater->getVisible()) - { - floater->setVisible(false); - return false; - } - else - { - floater->setVisible(true); - floater->setFocus(false); - } - } - // create and show new floater - else - { - show(notification_id); - } - - LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); - if (NULL != chiclet_panelp) - { - chiclet_panelp->setChicletToggleState(notification_id, true); - } - - return true; -} - -LLScriptFloater* LLScriptFloater::show(const LLUUID& notification_id) -{ - LLScriptFloater* floater = LLFloaterReg::getTypedInstance("script_floater", notification_id); - floater->setNotificationId(notification_id); - floater->createForm(notification_id); - - //LLDialog(LLGiveInventory and LLLoadURL) should no longer steal focus (see EXT-5445) - floater->setAutoFocus(false); - - if(LLScriptFloaterManager::OBJ_SCRIPT == LLScriptFloaterManager::getObjectType(notification_id)) - { - floater->setSavePosition(true); - floater->restorePosition(); - } - else - { - floater->dockToChiclet(true); - } - - //LLDialog(LLGiveInventory and LLLoadURL) should no longer steal focus (see EXT-5445) - LLFloaterReg::showTypedInstance("script_floater", notification_id, false); - - return floater; -} - -void LLScriptFloater::setNotificationId(const LLUUID& id) -{ - mNotificationId = id; - // Lets save object id now while notification exists - mObjectId = notification_id_to_object_id(id); -} - -void LLScriptFloater::createForm(const LLUUID& notification_id) -{ - // delete old form - if(mScriptForm) - { - removeChild(mScriptForm); - mScriptForm->die(); - } - - LLNotificationPtr notification = LLNotifications::getInstance()->find(notification_id); - if(NULL == notification) - { - return; - } - - // create new form - LLRect toast_rect = getRect(); - if (isScriptTextbox(notification)) - { - mScriptForm = new LLToastScriptTextbox(notification); - } - else - { - // LLToastNotifyPanel will fit own content in vertical direction, - // but it needs an initial rect to properly calculate its width - // Use an initial rect of the script floater to make the floater - // window more configurable. - mScriptForm = new LLToastNotifyPanel(notification, toast_rect); - } - addChild(mScriptForm); - - // position form on floater - mScriptForm->setOrigin(0, 0); - - // make floater size fit form size - LLRect panel_rect = mScriptForm->getRect(); - toast_rect.setLeftTopAndSize(toast_rect.mLeft, toast_rect.mTop, panel_rect.getWidth(), panel_rect.getHeight() + getHeaderHeight()); - setShape(toast_rect); -} - -void LLScriptFloater::onClose(bool app_quitting) -{ - savePosition(); - - if(getNotificationId().notNull()) - { - // we shouldn't kill notification on exit since it may be used as persistent. - if (app_quitting) - { - LLScriptFloaterManager::getInstance()->onRemoveNotification(getNotificationId()); - } - else - { - LLScriptFloaterManager::getInstance()->removeNotification(getNotificationId()); - } - } -} - -void LLScriptFloater::setDocked(bool docked, bool pop_on_undock /* = true */) -{ - LLDockableFloater::setDocked(docked, pop_on_undock); - - savePosition(); - - hideToastsIfNeeded(); -} - -void LLScriptFloater::setVisible(bool visible) -{ - LLDockableFloater::setVisible(visible); - - hideToastsIfNeeded(); - - if(!visible) - { - LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); - if (NULL != chiclet_panelp) - { - LLIMChiclet * chicletp = chiclet_panelp->findChiclet(getNotificationId()); - if(NULL != chicletp) - { - chicletp->setToggleState(false); - } - } - } -} - -void LLScriptFloater::onMouseDown() -{ - if(getNotificationId().notNull()) - { - LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); - if (NULL != chiclet_panelp) - { - LLIMChiclet * chicletp = chiclet_panelp->findChiclet(getNotificationId()); - // Remove new message icon - if (NULL == chicletp) - { - LL_ERRS() << "Dock chiclet for LLScriptFloater doesn't exist" << LL_ENDL; - } - else - { - chicletp->setShowNewMessagesIcon(false); - } - } - } -} - -void LLScriptFloater::savePosition() -{ - if(getSavePosition() && mObjectId.notNull()) - { - LLScriptFloaterManager::FloaterPositionInfo fpi = {getRect(), isDocked()}; - LLScriptFloaterManager::getInstance()->saveFloaterPosition(mObjectId, fpi); - } -} - -void LLScriptFloater::restorePosition() -{ - LLScriptFloaterManager::FloaterPositionInfo fpi; - if(LLScriptFloaterManager::getInstance()->getFloaterPosition(mObjectId, fpi)) - { - dockToChiclet(fpi.mDockState); - if(!fpi.mDockState) - { - // Un-docked floater is opened in 0,0, now move it to saved position - translate(fpi.mRect.mLeft - getRect().mLeft, fpi.mRect.mTop - getRect().mTop); - } - } - else - { - dockToChiclet(true); - } -} - -void LLScriptFloater::onFocusLost() -{ - if(getNotificationId().notNull()) - { - LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); - if (NULL != chiclet_panelp) - { - chiclet_panelp->setChicletToggleState(getNotificationId(), false); - } - } -} - -void LLScriptFloater::onFocusReceived() -{ - // first focus will be received before setObjectId() call - don't toggle chiclet - if(getNotificationId().notNull()) - { - LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); - if (NULL != chiclet_panelp) - { - chiclet_panelp->setChicletToggleState(getNotificationId(), true); - } - } -} - -void LLScriptFloater::dockToChiclet(bool dock) -{ - if (getDockControl() == NULL) - { - LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); - if (NULL != chiclet_panelp) - { - LLChiclet * chicletp = chiclet_panelp->findChiclet(getNotificationId()); - if (NULL == chicletp) - { - LL_WARNS() << "Dock chiclet for LLScriptFloater doesn't exist" << LL_ENDL; - return; - } - - chiclet_panelp->scrollToChiclet(chicletp); - - // Stop saving position while we dock floater - bool save = getSavePosition(); - setSavePosition(false); - - setDockControl(new LLDockControl(chicletp, this, getDockTongue(), - LLDockControl::BOTTOM)); - - setDocked(dock); - - // Restore saving - setSavePosition(save); - } - } -} - -void LLScriptFloater::hideToastsIfNeeded() -{ - using namespace LLNotificationsUI; - - // find channel - LLScreenChannel* channel = dynamic_cast(LLChannelManager::getInstance()->findChannelByID( - LLNotificationsUI::NOTIFICATION_CHANNEL_UUID)); - // update notification channel state - if(channel) - { - channel->updateShowToastsState(); - channel->redrawToasts(); - } -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -LLScriptFloaterManager::LLScriptFloaterManager() - : mDialogLimitationsSlot() -{ -} - -void LLScriptFloaterManager::onAddNotification(const LLUUID& notification_id) -{ - if(notification_id.isNull()) - { - LL_WARNS() << "Invalid notification ID" << LL_ENDL; - return; - } - - if (!mDialogLimitationsSlot.connected()) - { - LLPointer cntrl_ptr = gSavedSettings.getControl("ScriptDialogLimitations"); - if (cntrl_ptr.notNull()) - { - mDialogLimitationsSlot = cntrl_ptr->getCommitSignal()->connect(boost::bind(&clearScriptNotifications)); - } - else - { - LL_WARNS() << "Unable to set signal on setting 'ScriptDialogLimitations'" << LL_ENDL; - } - } - - // get scripted Object's ID - LLUUID object_id = notification_id_to_object_id(notification_id); - - // Need to indicate of "new message" for object chiclets according to requirements - // specified in the Message Bar design specification. See EXT-3142. - bool set_new_message = false; - EObjectType obj_type = getObjectType(notification_id); - - // LLDialog can spawn only one instance, LLLoadURL and LLGiveInventory can spawn unlimited number of instances - if(OBJ_SCRIPT == obj_type) - { - static LLCachedControl script_dialog_limitations(gSavedSettings, "ScriptDialogLimitations", 0); - script_notification_map_t::const_iterator it = mNotifications.end(); - switch (script_dialog_limitations) - { - case SCRIPT_PER_CHANNEL: - { - // If an Object spawns more-than-one floater per channel, only the newest one is shown. - // The previous is automatically closed. - LLNotificationPtr notification = LLNotifications::instance().find(notification_id); - if (notification) - { - it = findUsingObjectIdAndChannel(object_id, notification->getPayload()["chat_channel"].asInteger()); - } - break; - } - case SCRIPT_ATTACHMENT_PER_CHANNEL: - { - LLViewerObject* objectp = gObjectList.findObject(object_id); - if (objectp && objectp->getAttachmentItemID().notNull()) //in user inventory - { - LLNotificationPtr notification = LLNotifications::instance().find(notification_id); - if (notification) - { - it = findUsingObjectIdAndChannel(object_id, notification->getPayload()["chat_channel"].asInteger()); - } - } - else - { - it = findUsingObjectId(object_id); - } - break; - } - case SCRIPT_HUD_PER_CHANNEL: - { - LLViewerObject* objectp = gObjectList.findObject(object_id); - if (objectp && objectp->isHUDAttachment()) - { - LLNotificationPtr notification = LLNotifications::instance().find(notification_id); - if (notification) - { - it = findUsingObjectIdAndChannel(object_id, notification->getPayload()["chat_channel"].asInteger()); - } - } - else - { - it = findUsingObjectId(object_id); - } - break; - } - case SCRIPT_HUD_UNCONSTRAINED: - { - LLViewerObject* objectp = gObjectList.findObject(object_id); - if (objectp && objectp->isHUDAttachment()) - { - // don't remove existing floaters - break; - } - else - { - it = findUsingObjectId(object_id); - } - break; - } - case SCRIPT_PER_OBJECT: - default: - { - // If an Object spawns more-than-one floater, only the newest one is shown. - // The previous is automatically closed. - it = findUsingObjectId(object_id); - break; - } - } - - if(it != mNotifications.end()) - { - LLUUID old_id = it->first; // copy LLUUID to prevent use after free when it is erased below - LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); - if (NULL != chiclet_panelp) - { - LLIMChiclet * chicletp = chiclet_panelp->findChiclet(old_id); - if (NULL != chicletp) - { - // Pass the new_message icon state further. - set_new_message = chicletp->getShowNewMessagesIcon(); - chicletp->hidePopupMenu(); - } - } - - LLScriptFloater* floater = LLFloaterReg::findTypedInstance("script_floater", old_id); - if (floater) - { - // Generate chiclet with a "new message" indicator if a docked window was opened but not in focus. See EXT-3142. - set_new_message |= !floater->hasFocus(); - } - - removeNotification(old_id); - } - } - - mNotifications.insert(std::make_pair(notification_id, object_id)); - - LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); - if (NULL != chiclet_panelp) - { - // Create inventory offer chiclet for offer type notifications - if( OBJ_GIVE_INVENTORY == obj_type ) - { - chiclet_panelp->createChiclet(notification_id); - } - else - { - chiclet_panelp->createChiclet(notification_id); - } - } - - LLIMWellWindow::getInstance()->addObjectRow(notification_id, set_new_message); - - LLSD data; - data["notification_id"] = notification_id; - data["new_message"] = set_new_message; - data["unread"] = 1; // each object has got only one floater - mNewObjectSignal(data); - - toggleScriptFloater(notification_id, set_new_message); -} - -void LLScriptFloaterManager::removeNotification(const LLUUID& notification_id) -{ - LLNotificationPtr notification = LLNotifications::instance().find(notification_id); - if (notification != NULL && !notification->isCancelled()) - { - LLNotificationsUtil::cancel(notification); - } - - onRemoveNotification(notification_id); -} - -void LLScriptFloaterManager::onRemoveNotification(const LLUUID& notification_id) -{ - if(notification_id.isNull()) - { - LL_WARNS() << "Invalid notification ID" << LL_ENDL; - return; - } - - // remove related chiclet - if (LLChicletBar::instanceExists()) - { - LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); - if (NULL != chiclet_panelp) - { - chiclet_panelp->removeChiclet(notification_id); - } - } - - LLIMWellWindow* im_well_window = LLIMWellWindow::findInstance(); - if (im_well_window) - { - im_well_window->removeObjectRow(notification_id); - } - - mNotifications.erase(notification_id); - - // close floater - LLScriptFloater* floater = LLFloaterReg::findTypedInstance("script_floater", notification_id); - if(floater) - { - floater->savePosition(); - floater->setNotificationId(LLUUID::null); - floater->closeFloater(); - } -} - -void LLScriptFloaterManager::toggleScriptFloater(const LLUUID& notification_id, bool set_new_message) -{ - LLSD data; - data["notification_id"] = notification_id; - data["new_message"] = set_new_message; - mToggleFloaterSignal(data); - - // toggle floater - LLScriptFloater::toggle(notification_id); -} - -LLUUID LLScriptFloaterManager::findObjectId(const LLUUID& notification_id) -{ - script_notification_map_t::const_iterator it = mNotifications.find(notification_id); - if(mNotifications.end() != it) - { - return it->second; - } - return LLUUID::null; -} - -LLUUID LLScriptFloaterManager::findNotificationId(const LLUUID& object_id) -{ - if(object_id.notNull()) - { - script_notification_map_t::const_iterator it = findUsingObjectId(object_id); - if(mNotifications.end() != it) - { - return it->first; - } - } - return LLUUID::null; -} - -// static -LLScriptFloaterManager::EObjectType LLScriptFloaterManager::getObjectType(const LLUUID& notification_id) -{ - if(notification_id.isNull()) - { - LL_WARNS() << "Invalid notification ID" << LL_ENDL; - return OBJ_UNKNOWN; - } - - static const object_type_map TYPE_MAP = initObjectTypeMap(); - - LLNotificationPtr notification = LLNotificationsUtil::find(notification_id); - object_type_map::const_iterator it = TYPE_MAP.find(notification->getName()); - if(it != TYPE_MAP.end()) - { - return it->second; - } - - LL_WARNS() << "Unknown object type" << LL_ENDL; - return OBJ_UNKNOWN; -} - -// static -std::string LLScriptFloaterManager::getObjectName(const LLUUID& notification_id) -{ - using namespace LLNotificationsUI; - LLNotificationPtr notification = LLNotifications::getInstance()->find(notification_id); - if(!notification) - { - LL_WARNS() << "Invalid notification" << LL_ENDL; - return LLStringUtil::null; - } - - std::string text; - - switch(LLScriptFloaterManager::getObjectType(notification_id)) - { - case LLScriptFloaterManager::OBJ_SCRIPT: - text = notification->getSubstitutions()["TITLE"].asString(); - break; - case LLScriptFloaterManager::OBJ_LOAD_URL: - text = notification->getSubstitutions()["OBJECTNAME"].asString(); - break; - case LLScriptFloaterManager::OBJ_GIVE_INVENTORY: - text = notification->getSubstitutions()["OBJECTFROMNAME"].asString(); - break; - default: - text = LLTrans::getString("object"); - break; - } - - return text; -} - -//static -LLScriptFloaterManager::object_type_map LLScriptFloaterManager::initObjectTypeMap() -{ - object_type_map type_map; - type_map["ScriptDialog"] = OBJ_SCRIPT; - type_map["ScriptDialogGroup"] = OBJ_SCRIPT; - type_map["LoadWebPage"] = OBJ_LOAD_URL; - type_map["ObjectGiveItem"] = OBJ_GIVE_INVENTORY; - return type_map; -} - -LLScriptFloaterManager::script_notification_map_t::const_iterator LLScriptFloaterManager::findUsingObjectId(const LLUUID& object_id) -{ - script_notification_map_t::const_iterator it = mNotifications.begin(); - for(; mNotifications.end() != it; ++it) - { - if(object_id == it->second) - { - return it; - } - } - return mNotifications.end(); -} - -LLScriptFloaterManager::script_notification_map_t::const_iterator LLScriptFloaterManager::findUsingObjectIdAndChannel(const LLUUID& object_id, S32 im_channel) -{ - script_notification_map_t::const_iterator it = mNotifications.begin(); - for (; mNotifications.end() != it; ++it) - { - if (object_id == it->second) - { - LLNotificationPtr notification = LLNotifications::instance().find(it->first); - if (notification && (im_channel == notification->getPayload()["chat_channel"].asInteger())) - { - return it; - } - } - } - return mNotifications.end(); -} - -void LLScriptFloaterManager::saveFloaterPosition(const LLUUID& object_id, const FloaterPositionInfo& fpi) -{ - if(object_id.notNull()) - { - LLScriptFloaterManager::getInstance()->mFloaterPositions[object_id] = fpi; - } - else - { - LL_WARNS() << "Invalid object id" << LL_ENDL; - } -} - -bool LLScriptFloaterManager::getFloaterPosition(const LLUUID& object_id, FloaterPositionInfo& fpi) -{ - floater_position_map_t::const_iterator it = mFloaterPositions.find(object_id); - if(LLScriptFloaterManager::getInstance()->mFloaterPositions.end() != it) - { - fpi = it->second; - return true; - } - return false; -} - -void LLScriptFloaterManager::setFloaterVisible(const LLUUID& notification_id, bool visible) -{ - LLScriptFloater* floater = LLFloaterReg::findTypedInstance( - "script_floater", notification_id); - if(floater) - { - floater->setVisible(visible); - } -} - -//static -void LLScriptFloaterManager::clearScriptNotifications() -{ - LLScriptFloaterManager* inst = LLScriptFloaterManager::getInstance(); - static const object_type_map TYPE_MAP = initObjectTypeMap(); - - script_notification_map_t::const_iterator ntf_it = inst->mNotifications.begin(); - while (inst->mNotifications.end() != ntf_it) - { - LLUUID notification_id = ntf_it->first; - ntf_it++; // onRemoveNotification() erases notification - LLNotificationPtr notification = LLNotifications::instance().find(notification_id); - if (notification) - { - object_type_map::const_iterator map_it = TYPE_MAP.find(notification->getName()); - if (map_it != TYPE_MAP.end() && map_it->second == OBJ_SCRIPT) - { - if (notification != NULL && !notification->isCancelled()) - { - LLNotificationsUtil::cancel(notification); - } - inst->onRemoveNotification(notification_id); - } - } - } -} - -////////////////////////////////////////////////////////////////// - -bool LLScriptFloater::isScriptTextbox(LLNotificationPtr notification) -{ - // get a form for the notification - LLNotificationFormPtr form(notification->getForm()); - - if (form) - { - // get number of elements in the form - int num_options = form->getNumElements(); - - // if ANY of the buttons have the magic lltextbox string as - // name, then treat the whole dialog as a simple text entry - // box (i.e. mixed button and textbox forms are not supported) - for (int i=0; igetElement(i); - if (form_element["name"].asString() == TEXTBOX_MAGIC_TOKEN) - { - return true; - } - } - } - - return false; -} - -// EOF +/** + * @file llscriptfloater.cpp + * @brief LLScriptFloater class definition + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llscriptfloater.h" +#include "llagentcamera.h" + +#include "llchannelmanager.h" +#include "llchiclet.h" +#include "llchicletbar.h" +#include "llfloaterreg.h" +#include "lllslconstants.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "llscreenchannel.h" +#include "llsyswellwindow.h" +#include "lltoastnotifypanel.h" +#include "lltoastscripttextbox.h" +#include "lltrans.h" +#include "llviewerobjectlist.h" +#include "llviewerwindow.h" +#include "llfloaterimsession.h" + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +LLUUID notification_id_to_object_id(const LLUUID& notification_id) +{ + LLNotificationPtr notification = LLNotifications::getInstance()->find(notification_id); + if(notification) + { + return notification->getPayload()["object_id"].asUUID(); + } + return LLUUID::null; +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + + +LLScriptFloater::LLScriptFloater(const LLSD& key) +: LLDockableFloater(NULL, true, key) +, mScriptForm(NULL) +, mSaveFloaterPosition(false) +{ + setMouseDownCallback(boost::bind(&LLScriptFloater::onMouseDown, this)); + setOverlapsScreenChannel(true); + mIsDockedStateForcedCallback = boost::bind(&LLAgentCamera::cameraMouselook, &gAgentCamera); +} + +bool LLScriptFloater::toggle(const LLUUID& notification_id) +{ + LLScriptFloater* floater = LLFloaterReg::findTypedInstance("script_floater", notification_id); + + // show existing floater + if(floater) + { + if(floater->getVisible()) + { + floater->setVisible(false); + return false; + } + else + { + floater->setVisible(true); + floater->setFocus(false); + } + } + // create and show new floater + else + { + show(notification_id); + } + + LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); + if (NULL != chiclet_panelp) + { + chiclet_panelp->setChicletToggleState(notification_id, true); + } + + return true; +} + +LLScriptFloater* LLScriptFloater::show(const LLUUID& notification_id) +{ + LLScriptFloater* floater = LLFloaterReg::getTypedInstance("script_floater", notification_id); + floater->setNotificationId(notification_id); + floater->createForm(notification_id); + + //LLDialog(LLGiveInventory and LLLoadURL) should no longer steal focus (see EXT-5445) + floater->setAutoFocus(false); + + if(LLScriptFloaterManager::OBJ_SCRIPT == LLScriptFloaterManager::getObjectType(notification_id)) + { + floater->setSavePosition(true); + floater->restorePosition(); + } + else + { + floater->dockToChiclet(true); + } + + //LLDialog(LLGiveInventory and LLLoadURL) should no longer steal focus (see EXT-5445) + LLFloaterReg::showTypedInstance("script_floater", notification_id, false); + + return floater; +} + +void LLScriptFloater::setNotificationId(const LLUUID& id) +{ + mNotificationId = id; + // Lets save object id now while notification exists + mObjectId = notification_id_to_object_id(id); +} + +void LLScriptFloater::createForm(const LLUUID& notification_id) +{ + // delete old form + if(mScriptForm) + { + removeChild(mScriptForm); + mScriptForm->die(); + } + + LLNotificationPtr notification = LLNotifications::getInstance()->find(notification_id); + if(NULL == notification) + { + return; + } + + // create new form + LLRect toast_rect = getRect(); + if (isScriptTextbox(notification)) + { + mScriptForm = new LLToastScriptTextbox(notification); + } + else + { + // LLToastNotifyPanel will fit own content in vertical direction, + // but it needs an initial rect to properly calculate its width + // Use an initial rect of the script floater to make the floater + // window more configurable. + mScriptForm = new LLToastNotifyPanel(notification, toast_rect); + } + addChild(mScriptForm); + + // position form on floater + mScriptForm->setOrigin(0, 0); + + // make floater size fit form size + LLRect panel_rect = mScriptForm->getRect(); + toast_rect.setLeftTopAndSize(toast_rect.mLeft, toast_rect.mTop, panel_rect.getWidth(), panel_rect.getHeight() + getHeaderHeight()); + setShape(toast_rect); +} + +void LLScriptFloater::onClose(bool app_quitting) +{ + savePosition(); + + if(getNotificationId().notNull()) + { + // we shouldn't kill notification on exit since it may be used as persistent. + if (app_quitting) + { + LLScriptFloaterManager::getInstance()->onRemoveNotification(getNotificationId()); + } + else + { + LLScriptFloaterManager::getInstance()->removeNotification(getNotificationId()); + } + } +} + +void LLScriptFloater::setDocked(bool docked, bool pop_on_undock /* = true */) +{ + LLDockableFloater::setDocked(docked, pop_on_undock); + + savePosition(); + + hideToastsIfNeeded(); +} + +void LLScriptFloater::setVisible(bool visible) +{ + LLDockableFloater::setVisible(visible); + + hideToastsIfNeeded(); + + if(!visible) + { + LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); + if (NULL != chiclet_panelp) + { + LLIMChiclet * chicletp = chiclet_panelp->findChiclet(getNotificationId()); + if(NULL != chicletp) + { + chicletp->setToggleState(false); + } + } + } +} + +void LLScriptFloater::onMouseDown() +{ + if(getNotificationId().notNull()) + { + LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); + if (NULL != chiclet_panelp) + { + LLIMChiclet * chicletp = chiclet_panelp->findChiclet(getNotificationId()); + // Remove new message icon + if (NULL == chicletp) + { + LL_ERRS() << "Dock chiclet for LLScriptFloater doesn't exist" << LL_ENDL; + } + else + { + chicletp->setShowNewMessagesIcon(false); + } + } + } +} + +void LLScriptFloater::savePosition() +{ + if(getSavePosition() && mObjectId.notNull()) + { + LLScriptFloaterManager::FloaterPositionInfo fpi = {getRect(), isDocked()}; + LLScriptFloaterManager::getInstance()->saveFloaterPosition(mObjectId, fpi); + } +} + +void LLScriptFloater::restorePosition() +{ + LLScriptFloaterManager::FloaterPositionInfo fpi; + if(LLScriptFloaterManager::getInstance()->getFloaterPosition(mObjectId, fpi)) + { + dockToChiclet(fpi.mDockState); + if(!fpi.mDockState) + { + // Un-docked floater is opened in 0,0, now move it to saved position + translate(fpi.mRect.mLeft - getRect().mLeft, fpi.mRect.mTop - getRect().mTop); + } + } + else + { + dockToChiclet(true); + } +} + +void LLScriptFloater::onFocusLost() +{ + if(getNotificationId().notNull()) + { + LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); + if (NULL != chiclet_panelp) + { + chiclet_panelp->setChicletToggleState(getNotificationId(), false); + } + } +} + +void LLScriptFloater::onFocusReceived() +{ + // first focus will be received before setObjectId() call - don't toggle chiclet + if(getNotificationId().notNull()) + { + LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); + if (NULL != chiclet_panelp) + { + chiclet_panelp->setChicletToggleState(getNotificationId(), true); + } + } +} + +void LLScriptFloater::dockToChiclet(bool dock) +{ + if (getDockControl() == NULL) + { + LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); + if (NULL != chiclet_panelp) + { + LLChiclet * chicletp = chiclet_panelp->findChiclet(getNotificationId()); + if (NULL == chicletp) + { + LL_WARNS() << "Dock chiclet for LLScriptFloater doesn't exist" << LL_ENDL; + return; + } + + chiclet_panelp->scrollToChiclet(chicletp); + + // Stop saving position while we dock floater + bool save = getSavePosition(); + setSavePosition(false); + + setDockControl(new LLDockControl(chicletp, this, getDockTongue(), + LLDockControl::BOTTOM)); + + setDocked(dock); + + // Restore saving + setSavePosition(save); + } + } +} + +void LLScriptFloater::hideToastsIfNeeded() +{ + using namespace LLNotificationsUI; + + // find channel + LLScreenChannel* channel = dynamic_cast(LLChannelManager::getInstance()->findChannelByID( + LLNotificationsUI::NOTIFICATION_CHANNEL_UUID)); + // update notification channel state + if(channel) + { + channel->updateShowToastsState(); + channel->redrawToasts(); + } +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +LLScriptFloaterManager::LLScriptFloaterManager() + : mDialogLimitationsSlot() +{ +} + +void LLScriptFloaterManager::onAddNotification(const LLUUID& notification_id) +{ + if(notification_id.isNull()) + { + LL_WARNS() << "Invalid notification ID" << LL_ENDL; + return; + } + + if (!mDialogLimitationsSlot.connected()) + { + LLPointer cntrl_ptr = gSavedSettings.getControl("ScriptDialogLimitations"); + if (cntrl_ptr.notNull()) + { + mDialogLimitationsSlot = cntrl_ptr->getCommitSignal()->connect(boost::bind(&clearScriptNotifications)); + } + else + { + LL_WARNS() << "Unable to set signal on setting 'ScriptDialogLimitations'" << LL_ENDL; + } + } + + // get scripted Object's ID + LLUUID object_id = notification_id_to_object_id(notification_id); + + // Need to indicate of "new message" for object chiclets according to requirements + // specified in the Message Bar design specification. See EXT-3142. + bool set_new_message = false; + EObjectType obj_type = getObjectType(notification_id); + + // LLDialog can spawn only one instance, LLLoadURL and LLGiveInventory can spawn unlimited number of instances + if(OBJ_SCRIPT == obj_type) + { + static LLCachedControl script_dialog_limitations(gSavedSettings, "ScriptDialogLimitations", 0); + script_notification_map_t::const_iterator it = mNotifications.end(); + switch (script_dialog_limitations) + { + case SCRIPT_PER_CHANNEL: + { + // If an Object spawns more-than-one floater per channel, only the newest one is shown. + // The previous is automatically closed. + LLNotificationPtr notification = LLNotifications::instance().find(notification_id); + if (notification) + { + it = findUsingObjectIdAndChannel(object_id, notification->getPayload()["chat_channel"].asInteger()); + } + break; + } + case SCRIPT_ATTACHMENT_PER_CHANNEL: + { + LLViewerObject* objectp = gObjectList.findObject(object_id); + if (objectp && objectp->getAttachmentItemID().notNull()) //in user inventory + { + LLNotificationPtr notification = LLNotifications::instance().find(notification_id); + if (notification) + { + it = findUsingObjectIdAndChannel(object_id, notification->getPayload()["chat_channel"].asInteger()); + } + } + else + { + it = findUsingObjectId(object_id); + } + break; + } + case SCRIPT_HUD_PER_CHANNEL: + { + LLViewerObject* objectp = gObjectList.findObject(object_id); + if (objectp && objectp->isHUDAttachment()) + { + LLNotificationPtr notification = LLNotifications::instance().find(notification_id); + if (notification) + { + it = findUsingObjectIdAndChannel(object_id, notification->getPayload()["chat_channel"].asInteger()); + } + } + else + { + it = findUsingObjectId(object_id); + } + break; + } + case SCRIPT_HUD_UNCONSTRAINED: + { + LLViewerObject* objectp = gObjectList.findObject(object_id); + if (objectp && objectp->isHUDAttachment()) + { + // don't remove existing floaters + break; + } + else + { + it = findUsingObjectId(object_id); + } + break; + } + case SCRIPT_PER_OBJECT: + default: + { + // If an Object spawns more-than-one floater, only the newest one is shown. + // The previous is automatically closed. + it = findUsingObjectId(object_id); + break; + } + } + + if(it != mNotifications.end()) + { + LLUUID old_id = it->first; // copy LLUUID to prevent use after free when it is erased below + LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); + if (NULL != chiclet_panelp) + { + LLIMChiclet * chicletp = chiclet_panelp->findChiclet(old_id); + if (NULL != chicletp) + { + // Pass the new_message icon state further. + set_new_message = chicletp->getShowNewMessagesIcon(); + chicletp->hidePopupMenu(); + } + } + + LLScriptFloater* floater = LLFloaterReg::findTypedInstance("script_floater", old_id); + if (floater) + { + // Generate chiclet with a "new message" indicator if a docked window was opened but not in focus. See EXT-3142. + set_new_message |= !floater->hasFocus(); + } + + removeNotification(old_id); + } + } + + mNotifications.insert(std::make_pair(notification_id, object_id)); + + LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); + if (NULL != chiclet_panelp) + { + // Create inventory offer chiclet for offer type notifications + if( OBJ_GIVE_INVENTORY == obj_type ) + { + chiclet_panelp->createChiclet(notification_id); + } + else + { + chiclet_panelp->createChiclet(notification_id); + } + } + + LLIMWellWindow::getInstance()->addObjectRow(notification_id, set_new_message); + + LLSD data; + data["notification_id"] = notification_id; + data["new_message"] = set_new_message; + data["unread"] = 1; // each object has got only one floater + mNewObjectSignal(data); + + toggleScriptFloater(notification_id, set_new_message); +} + +void LLScriptFloaterManager::removeNotification(const LLUUID& notification_id) +{ + LLNotificationPtr notification = LLNotifications::instance().find(notification_id); + if (notification != NULL && !notification->isCancelled()) + { + LLNotificationsUtil::cancel(notification); + } + + onRemoveNotification(notification_id); +} + +void LLScriptFloaterManager::onRemoveNotification(const LLUUID& notification_id) +{ + if(notification_id.isNull()) + { + LL_WARNS() << "Invalid notification ID" << LL_ENDL; + return; + } + + // remove related chiclet + if (LLChicletBar::instanceExists()) + { + LLChicletPanel * chiclet_panelp = LLChicletBar::getInstance()->getChicletPanel(); + if (NULL != chiclet_panelp) + { + chiclet_panelp->removeChiclet(notification_id); + } + } + + LLIMWellWindow* im_well_window = LLIMWellWindow::findInstance(); + if (im_well_window) + { + im_well_window->removeObjectRow(notification_id); + } + + mNotifications.erase(notification_id); + + // close floater + LLScriptFloater* floater = LLFloaterReg::findTypedInstance("script_floater", notification_id); + if(floater) + { + floater->savePosition(); + floater->setNotificationId(LLUUID::null); + floater->closeFloater(); + } +} + +void LLScriptFloaterManager::toggleScriptFloater(const LLUUID& notification_id, bool set_new_message) +{ + LLSD data; + data["notification_id"] = notification_id; + data["new_message"] = set_new_message; + mToggleFloaterSignal(data); + + // toggle floater + LLScriptFloater::toggle(notification_id); +} + +LLUUID LLScriptFloaterManager::findObjectId(const LLUUID& notification_id) +{ + script_notification_map_t::const_iterator it = mNotifications.find(notification_id); + if(mNotifications.end() != it) + { + return it->second; + } + return LLUUID::null; +} + +LLUUID LLScriptFloaterManager::findNotificationId(const LLUUID& object_id) +{ + if(object_id.notNull()) + { + script_notification_map_t::const_iterator it = findUsingObjectId(object_id); + if(mNotifications.end() != it) + { + return it->first; + } + } + return LLUUID::null; +} + +// static +LLScriptFloaterManager::EObjectType LLScriptFloaterManager::getObjectType(const LLUUID& notification_id) +{ + if(notification_id.isNull()) + { + LL_WARNS() << "Invalid notification ID" << LL_ENDL; + return OBJ_UNKNOWN; + } + + static const object_type_map TYPE_MAP = initObjectTypeMap(); + + LLNotificationPtr notification = LLNotificationsUtil::find(notification_id); + object_type_map::const_iterator it = TYPE_MAP.find(notification->getName()); + if(it != TYPE_MAP.end()) + { + return it->second; + } + + LL_WARNS() << "Unknown object type" << LL_ENDL; + return OBJ_UNKNOWN; +} + +// static +std::string LLScriptFloaterManager::getObjectName(const LLUUID& notification_id) +{ + using namespace LLNotificationsUI; + LLNotificationPtr notification = LLNotifications::getInstance()->find(notification_id); + if(!notification) + { + LL_WARNS() << "Invalid notification" << LL_ENDL; + return LLStringUtil::null; + } + + std::string text; + + switch(LLScriptFloaterManager::getObjectType(notification_id)) + { + case LLScriptFloaterManager::OBJ_SCRIPT: + text = notification->getSubstitutions()["TITLE"].asString(); + break; + case LLScriptFloaterManager::OBJ_LOAD_URL: + text = notification->getSubstitutions()["OBJECTNAME"].asString(); + break; + case LLScriptFloaterManager::OBJ_GIVE_INVENTORY: + text = notification->getSubstitutions()["OBJECTFROMNAME"].asString(); + break; + default: + text = LLTrans::getString("object"); + break; + } + + return text; +} + +//static +LLScriptFloaterManager::object_type_map LLScriptFloaterManager::initObjectTypeMap() +{ + object_type_map type_map; + type_map["ScriptDialog"] = OBJ_SCRIPT; + type_map["ScriptDialogGroup"] = OBJ_SCRIPT; + type_map["LoadWebPage"] = OBJ_LOAD_URL; + type_map["ObjectGiveItem"] = OBJ_GIVE_INVENTORY; + return type_map; +} + +LLScriptFloaterManager::script_notification_map_t::const_iterator LLScriptFloaterManager::findUsingObjectId(const LLUUID& object_id) +{ + script_notification_map_t::const_iterator it = mNotifications.begin(); + for(; mNotifications.end() != it; ++it) + { + if(object_id == it->second) + { + return it; + } + } + return mNotifications.end(); +} + +LLScriptFloaterManager::script_notification_map_t::const_iterator LLScriptFloaterManager::findUsingObjectIdAndChannel(const LLUUID& object_id, S32 im_channel) +{ + script_notification_map_t::const_iterator it = mNotifications.begin(); + for (; mNotifications.end() != it; ++it) + { + if (object_id == it->second) + { + LLNotificationPtr notification = LLNotifications::instance().find(it->first); + if (notification && (im_channel == notification->getPayload()["chat_channel"].asInteger())) + { + return it; + } + } + } + return mNotifications.end(); +} + +void LLScriptFloaterManager::saveFloaterPosition(const LLUUID& object_id, const FloaterPositionInfo& fpi) +{ + if(object_id.notNull()) + { + LLScriptFloaterManager::getInstance()->mFloaterPositions[object_id] = fpi; + } + else + { + LL_WARNS() << "Invalid object id" << LL_ENDL; + } +} + +bool LLScriptFloaterManager::getFloaterPosition(const LLUUID& object_id, FloaterPositionInfo& fpi) +{ + floater_position_map_t::const_iterator it = mFloaterPositions.find(object_id); + if(LLScriptFloaterManager::getInstance()->mFloaterPositions.end() != it) + { + fpi = it->second; + return true; + } + return false; +} + +void LLScriptFloaterManager::setFloaterVisible(const LLUUID& notification_id, bool visible) +{ + LLScriptFloater* floater = LLFloaterReg::findTypedInstance( + "script_floater", notification_id); + if(floater) + { + floater->setVisible(visible); + } +} + +//static +void LLScriptFloaterManager::clearScriptNotifications() +{ + LLScriptFloaterManager* inst = LLScriptFloaterManager::getInstance(); + static const object_type_map TYPE_MAP = initObjectTypeMap(); + + script_notification_map_t::const_iterator ntf_it = inst->mNotifications.begin(); + while (inst->mNotifications.end() != ntf_it) + { + LLUUID notification_id = ntf_it->first; + ntf_it++; // onRemoveNotification() erases notification + LLNotificationPtr notification = LLNotifications::instance().find(notification_id); + if (notification) + { + object_type_map::const_iterator map_it = TYPE_MAP.find(notification->getName()); + if (map_it != TYPE_MAP.end() && map_it->second == OBJ_SCRIPT) + { + if (notification != NULL && !notification->isCancelled()) + { + LLNotificationsUtil::cancel(notification); + } + inst->onRemoveNotification(notification_id); + } + } + } +} + +////////////////////////////////////////////////////////////////// + +bool LLScriptFloater::isScriptTextbox(LLNotificationPtr notification) +{ + // get a form for the notification + LLNotificationFormPtr form(notification->getForm()); + + if (form) + { + // get number of elements in the form + int num_options = form->getNumElements(); + + // if ANY of the buttons have the magic lltextbox string as + // name, then treat the whole dialog as a simple text entry + // box (i.e. mixed button and textbox forms are not supported) + for (int i=0; igetElement(i); + if (form_element["name"].asString() == TEXTBOX_MAGIC_TOKEN) + { + return true; + } + } + } + + return false; +} + +// EOF diff --git a/indra/newview/llscriptfloater.h b/indra/newview/llscriptfloater.h index fc82f8222f..8f3b8e99f6 100644 --- a/indra/newview/llscriptfloater.h +++ b/indra/newview/llscriptfloater.h @@ -1,230 +1,230 @@ -/** - * @file llscriptfloater.h - * @brief LLScriptFloater class definition - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_SCRIPTFLOATER_H -#define LL_SCRIPTFLOATER_H - -#include "lltransientdockablefloater.h" -#include "llnotificationptr.h" - -class LLToastPanel; - -/** - * Handles script notifications ("ScriptDialog" and "ScriptDialogGroup") - * and manages Script Floaters. - */ -class LLScriptFloaterManager : public LLSingleton -{ - // *TODO - // LLScriptFloaterManager and LLScriptFloater will need some refactoring after we - // know how script notifications should look like. - LLSINGLETON(LLScriptFloaterManager); -public: - - typedef enum e_object_type - { - OBJ_SCRIPT, - OBJ_GIVE_INVENTORY, - OBJ_LOAD_URL, - - OBJ_UNKNOWN - }EObjectType; - - typedef enum e_limitation_type - { - SCRIPT_PER_OBJECT = 0, - SCRIPT_PER_CHANNEL = 1, - SCRIPT_ATTACHMENT_PER_CHANNEL, - SCRIPT_HUD_PER_CHANNEL, - SCRIPT_HUD_UNCONSTRAINED - }ELimitationType; - - /** - * Handles new notifications. - * Saves notification and object ids, removes old notification if needed, creates script chiclet - * Note that one object can spawn one script floater. - */ - void onAddNotification(const LLUUID& notification_id); - - /** - * Removes notification. - */ - void removeNotification(const LLUUID& notification_id); - - /** - * Handles notification removal. - * Removes script notification toast, removes script chiclet, closes script floater - */ - void onRemoveNotification(const LLUUID& notification_id); - - /** - * Toggles script floater. - * Removes "new message" icon from chiclet and removes notification toast. - */ - void toggleScriptFloater(const LLUUID& object_id, bool set_new_message = false); - - LLUUID findObjectId(const LLUUID& notification_id); - - LLUUID findNotificationId(const LLUUID& object_id); - - static EObjectType getObjectType(const LLUUID& notification_id); - - static std::string getObjectName(const LLUUID& notification_id); - - typedef boost::signals2::signal object_signal_t; - - boost::signals2::connection addNewObjectCallback(const object_signal_t::slot_type& cb) { return mNewObjectSignal.connect(cb); } - boost::signals2::connection addToggleObjectFloaterCallback(const object_signal_t::slot_type& cb) { return mToggleFloaterSignal.connect(cb); } - - struct FloaterPositionInfo - { - LLRect mRect; - bool mDockState; - }; - - void saveFloaterPosition(const LLUUID& object_id, const FloaterPositionInfo& fpi); - - bool getFloaterPosition(const LLUUID& object_id, FloaterPositionInfo& fpi); - - void setFloaterVisible(const LLUUID& notification_id, bool visible); - -protected: - - /** - * Removes all script-dialog notifications - */ - static void clearScriptNotifications(); - - typedef std::map object_type_map; - - static object_type_map initObjectTypeMap(); - - // - typedef std::map script_notification_map_t; - - script_notification_map_t::const_iterator findUsingObjectId(const LLUUID& object_id); - script_notification_map_t::const_iterator findUsingObjectIdAndChannel(const LLUUID& object_id, S32 im_channel); - -private: - - script_notification_map_t mNotifications; - - object_signal_t mNewObjectSignal; - object_signal_t mToggleFloaterSignal; - - // - typedef std::map floater_position_map_t; - - floater_position_map_t mFloaterPositions; - boost::signals2::connection mDialogLimitationsSlot; -}; - -/** - * Floater script forms. - * LLScriptFloater will create script form based on notification data and - * will auto fit the form. - */ -class LLScriptFloater : public LLDockableFloater -{ -public: - - /** - * key - UUID of scripted Object - */ - LLScriptFloater(const LLSD& key); - - virtual ~LLScriptFloater(){}; - - /** - * Toggle existing floater or create and show a new one. - */ - static bool toggle(const LLUUID& object_id); - - /** - * Creates and shows floater - */ - static LLScriptFloater* show(const LLUUID& object_id); - - const LLUUID& getNotificationId() { return mNotificationId; } - - void setNotificationId(const LLUUID& id); - - /** - * Close notification if script floater is closed. - */ - /*virtual*/ void onClose(bool app_quitting); - - /** - * Hide all notification toasts when we show dockable floater - */ - /*virtual*/ void setDocked(bool docked, bool pop_on_undock = true); - - /** - * Hide all notification toasts when we show dockable floater - */ - /*virtual*/ void setVisible(bool visible); - - bool getSavePosition() { return mSaveFloaterPosition; } - - void setSavePosition(bool save) { mSaveFloaterPosition = save; } - - void savePosition(); - - void restorePosition(); - -protected: - - /** - * Creates script form, will delete old form if floater is shown for same object. - */ - void createForm(const LLUUID& object_id); - - /** - * Hide all notification toasts. - */ - static void hideToastsIfNeeded(); - - /** - * Removes chiclets new messages icon - */ - void onMouseDown(); - - /*virtual*/ void onFocusLost(); - - /*virtual*/ void onFocusReceived(); - - void dockToChiclet(bool dock); - -private: - bool isScriptTextbox(LLNotificationPtr notification); - - LLToastPanel* mScriptForm; - LLUUID mNotificationId; - LLUUID mObjectId; - bool mSaveFloaterPosition; -}; - -#endif //LL_SCRIPTFLOATER_H +/** + * @file llscriptfloater.h + * @brief LLScriptFloater class definition + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_SCRIPTFLOATER_H +#define LL_SCRIPTFLOATER_H + +#include "lltransientdockablefloater.h" +#include "llnotificationptr.h" + +class LLToastPanel; + +/** + * Handles script notifications ("ScriptDialog" and "ScriptDialogGroup") + * and manages Script Floaters. + */ +class LLScriptFloaterManager : public LLSingleton +{ + // *TODO + // LLScriptFloaterManager and LLScriptFloater will need some refactoring after we + // know how script notifications should look like. + LLSINGLETON(LLScriptFloaterManager); +public: + + typedef enum e_object_type + { + OBJ_SCRIPT, + OBJ_GIVE_INVENTORY, + OBJ_LOAD_URL, + + OBJ_UNKNOWN + }EObjectType; + + typedef enum e_limitation_type + { + SCRIPT_PER_OBJECT = 0, + SCRIPT_PER_CHANNEL = 1, + SCRIPT_ATTACHMENT_PER_CHANNEL, + SCRIPT_HUD_PER_CHANNEL, + SCRIPT_HUD_UNCONSTRAINED + }ELimitationType; + + /** + * Handles new notifications. + * Saves notification and object ids, removes old notification if needed, creates script chiclet + * Note that one object can spawn one script floater. + */ + void onAddNotification(const LLUUID& notification_id); + + /** + * Removes notification. + */ + void removeNotification(const LLUUID& notification_id); + + /** + * Handles notification removal. + * Removes script notification toast, removes script chiclet, closes script floater + */ + void onRemoveNotification(const LLUUID& notification_id); + + /** + * Toggles script floater. + * Removes "new message" icon from chiclet and removes notification toast. + */ + void toggleScriptFloater(const LLUUID& object_id, bool set_new_message = false); + + LLUUID findObjectId(const LLUUID& notification_id); + + LLUUID findNotificationId(const LLUUID& object_id); + + static EObjectType getObjectType(const LLUUID& notification_id); + + static std::string getObjectName(const LLUUID& notification_id); + + typedef boost::signals2::signal object_signal_t; + + boost::signals2::connection addNewObjectCallback(const object_signal_t::slot_type& cb) { return mNewObjectSignal.connect(cb); } + boost::signals2::connection addToggleObjectFloaterCallback(const object_signal_t::slot_type& cb) { return mToggleFloaterSignal.connect(cb); } + + struct FloaterPositionInfo + { + LLRect mRect; + bool mDockState; + }; + + void saveFloaterPosition(const LLUUID& object_id, const FloaterPositionInfo& fpi); + + bool getFloaterPosition(const LLUUID& object_id, FloaterPositionInfo& fpi); + + void setFloaterVisible(const LLUUID& notification_id, bool visible); + +protected: + + /** + * Removes all script-dialog notifications + */ + static void clearScriptNotifications(); + + typedef std::map object_type_map; + + static object_type_map initObjectTypeMap(); + + // + typedef std::map script_notification_map_t; + + script_notification_map_t::const_iterator findUsingObjectId(const LLUUID& object_id); + script_notification_map_t::const_iterator findUsingObjectIdAndChannel(const LLUUID& object_id, S32 im_channel); + +private: + + script_notification_map_t mNotifications; + + object_signal_t mNewObjectSignal; + object_signal_t mToggleFloaterSignal; + + // + typedef std::map floater_position_map_t; + + floater_position_map_t mFloaterPositions; + boost::signals2::connection mDialogLimitationsSlot; +}; + +/** + * Floater script forms. + * LLScriptFloater will create script form based on notification data and + * will auto fit the form. + */ +class LLScriptFloater : public LLDockableFloater +{ +public: + + /** + * key - UUID of scripted Object + */ + LLScriptFloater(const LLSD& key); + + virtual ~LLScriptFloater(){}; + + /** + * Toggle existing floater or create and show a new one. + */ + static bool toggle(const LLUUID& object_id); + + /** + * Creates and shows floater + */ + static LLScriptFloater* show(const LLUUID& object_id); + + const LLUUID& getNotificationId() { return mNotificationId; } + + void setNotificationId(const LLUUID& id); + + /** + * Close notification if script floater is closed. + */ + /*virtual*/ void onClose(bool app_quitting); + + /** + * Hide all notification toasts when we show dockable floater + */ + /*virtual*/ void setDocked(bool docked, bool pop_on_undock = true); + + /** + * Hide all notification toasts when we show dockable floater + */ + /*virtual*/ void setVisible(bool visible); + + bool getSavePosition() { return mSaveFloaterPosition; } + + void setSavePosition(bool save) { mSaveFloaterPosition = save; } + + void savePosition(); + + void restorePosition(); + +protected: + + /** + * Creates script form, will delete old form if floater is shown for same object. + */ + void createForm(const LLUUID& object_id); + + /** + * Hide all notification toasts. + */ + static void hideToastsIfNeeded(); + + /** + * Removes chiclets new messages icon + */ + void onMouseDown(); + + /*virtual*/ void onFocusLost(); + + /*virtual*/ void onFocusReceived(); + + void dockToChiclet(bool dock); + +private: + bool isScriptTextbox(LLNotificationPtr notification); + + LLToastPanel* mScriptForm; + LLUUID mNotificationId; + LLUUID mObjectId; + bool mSaveFloaterPosition; +}; + +#endif //LL_SCRIPTFLOATER_H diff --git a/indra/newview/llscrollingpanelparam.cpp b/indra/newview/llscrollingpanelparam.cpp index d1d3e4f988..8dfe21e4cb 100644 --- a/indra/newview/llscrollingpanelparam.cpp +++ b/indra/newview/llscrollingpanelparam.cpp @@ -1,353 +1,353 @@ -/** - * @file llscrollingpanelparam.cpp - * @brief UI panel for a list of visual param panels - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llscrollingpanelparam.h" -#include "llviewerjointmesh.h" -#include "llviewervisualparam.h" -#include "llwearable.h" -#include "llviewervisualparam.h" -#include "lltoolmorph.h" -#include "lltrans.h" -#include "llbutton.h" -#include "llsliderctrl.h" -#include "llagent.h" -#include "llviewborder.h" -#include "llvoavatarself.h" - -// Constants for LLPanelVisualParam -const F32 LLScrollingPanelParam::PARAM_STEP_TIME_THRESHOLD = 0.25f; - -const S32 LLScrollingPanelParam::PARAM_HINT_WIDTH = 128; -const S32 LLScrollingPanelParam::PARAM_HINT_HEIGHT = 128; - -// LLScrollingPanelParam -//static -S32 LLScrollingPanelParam::sUpdateDelayFrames = 0; - -LLScrollingPanelParam::LLScrollingPanelParam( const LLPanel::Params& panel_params, - LLViewerJointMesh* mesh, LLViewerVisualParam* param, bool allow_modify, LLWearable* wearable, LLJoint* jointp, bool use_hints ) - : LLScrollingPanelParamBase( panel_params, mesh, param, allow_modify, wearable, jointp, use_hints) -{ - mLessBtn = getChild("less"); - mMoreBtn = getChild("more"); - mLeftBorder = getChild("left_border"); - mRightBorder = getChild("right_border"); - mMinParamText = getChild("min param text"); - mMaxParamText = getChild("max param text"); - - // *HACK To avoid hard coding texture position, lets use border's position for texture. - static LLUICachedControl slider_ctrl_height ("UISliderctrlHeight", 0); - S32 pos_x = mLeftBorder->getRect().mLeft + mLeftBorder->getBorderWidth(); - S32 pos_y = mLeftBorder->getRect().mBottom + mLeftBorder->getBorderWidth(); - F32 min_weight = param->getMinWeight(); - F32 max_weight = param->getMaxWeight(); - - mHintMin = new LLVisualParamHint( pos_x, pos_y, PARAM_HINT_WIDTH, PARAM_HINT_HEIGHT, mesh, (LLViewerVisualParam*) wearable->getVisualParam(param->getID()), wearable, min_weight, jointp); - pos_x = mRightBorder->getRect().mLeft + mLeftBorder->getBorderWidth(); - mHintMax = new LLVisualParamHint( pos_x, pos_y, PARAM_HINT_WIDTH, PARAM_HINT_HEIGHT, mesh, (LLViewerVisualParam*) wearable->getVisualParam(param->getID()), wearable, max_weight, jointp ); - - mHintMin->setAllowsUpdates( false ); - mHintMax->setAllowsUpdates( false ); - - mMinParamText->setValue(LLTrans::getString(param->getMinDisplayName())); - mMaxParamText->setValue(LLTrans::getString(param->getMaxDisplayName())); - - mLessBtn->setMouseDownCallback(LLScrollingPanelParam::onHintMinMouseDown, this); - mLessBtn->setMouseUpCallback(LLScrollingPanelParam::onHintMinMouseUp, this); - mLessBtn->setHeldDownCallback(LLScrollingPanelParam::onHintMinHeldDown, this); - mLessBtn->setHeldDownDelay(PARAM_STEP_TIME_THRESHOLD); - - mMoreBtn->setMouseDownCallback(LLScrollingPanelParam::onHintMaxMouseDown, this); - mMoreBtn->setMouseUpCallback(LLScrollingPanelParam::onHintMaxMouseUp, this); - mMoreBtn->setHeldDownCallback(LLScrollingPanelParam::onHintMaxHeldDown, this); - mMoreBtn->setHeldDownDelay(PARAM_STEP_TIME_THRESHOLD); - - setVisible(false); - setBorderVisible( false ); -} - -LLScrollingPanelParam::~LLScrollingPanelParam() -{ -} -void LLScrollingPanelParam::updatePanel(bool allow_modify) -{ - if (!mWearable) - { - // not editing a wearable just now, no update necessary - return; - } - LLScrollingPanelParamBase::updatePanel(allow_modify); - - mHintMin->requestUpdate( sUpdateDelayFrames++ ); - mHintMax->requestUpdate( sUpdateDelayFrames++ ); - mLessBtn->setEnabled(mAllowModify); - mMoreBtn->setEnabled(mAllowModify); -} - -void LLScrollingPanelParam::setVisible( bool visible ) -{ - if( getVisible() != visible ) - { - LLPanel::setVisible( visible ); - if (mHintMin) - mHintMin->setAllowsUpdates( visible ); - if (mHintMax) - mHintMax->setAllowsUpdates( visible ); - - if( visible ) - { - if (mHintMin) - mHintMin->setUpdateDelayFrames( sUpdateDelayFrames++ ); - if (mHintMax) - mHintMax->setUpdateDelayFrames( sUpdateDelayFrames++ ); - } - } -} - -void LLScrollingPanelParam::draw() -{ - if( !mWearable ) - { - return; - } - - mLessBtn->setVisible( mHintMin->getVisible()); - mMoreBtn->setVisible( mHintMax->getVisible()); - - // hide borders if texture has been loaded - mLeftBorder->setVisible( !mHintMin->getVisible()); - mRightBorder->setVisible( !mHintMax->getVisible()); - - // Draw all the children except for the labels - mMinParamText->setVisible( false ); - mMaxParamText->setVisible( false ); - LLPanel::draw(); - - // If we're in a focused floater, don't apply the floater's alpha to visual param hint, - // making its behavior similar to texture controls'. - F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); - - // Draw the hints over the "less" and "more" buttons. - gGL.pushUIMatrix(); - { - const LLRect& r = mHintMin->getRect(); - gGL.translateUI((F32)r.mLeft, (F32)r.mBottom, 0.f); - mHintMin->draw(alpha); - } - gGL.popUIMatrix(); - - gGL.pushUIMatrix(); - { - const LLRect& r = mHintMax->getRect(); - gGL.translateUI((F32)r.mLeft, (F32)r.mBottom, 0.f); - mHintMax->draw(alpha); - } - gGL.popUIMatrix(); - - - // Draw labels on top of the buttons - mMinParamText->setVisible( true ); - drawChild(mMinParamText); - - mMaxParamText->setVisible( true ); - drawChild(mMaxParamText); -} - -// static -void LLScrollingPanelParam::onSliderMouseDown(LLUICtrl* ctrl, void* userdata) -{ -} - -// static -void LLScrollingPanelParam::onSliderMouseUp(LLUICtrl* ctrl, void* userdata) -{ - LLScrollingPanelParam* self = (LLScrollingPanelParam*) userdata; - LLVisualParamHint::requestHintUpdates( self->mHintMin, self->mHintMax ); -} - -// static -void LLScrollingPanelParam::onHintMinMouseDown( void* userdata ) -{ - LLScrollingPanelParam* self = (LLScrollingPanelParam*) userdata; - self->onHintMouseDown( self->mHintMin ); -} - -// static -void LLScrollingPanelParam::onHintMaxMouseDown( void* userdata ) -{ - LLScrollingPanelParam* self = (LLScrollingPanelParam*) userdata; - self->onHintMouseDown( self->mHintMax ); -} - - -void LLScrollingPanelParam::onHintMouseDown( LLVisualParamHint* hint ) -{ - // morph towards this result - F32 current_weight = mWearable->getVisualParamWeight( hint->getVisualParam()->getID() ); - - // if we have maxed out on this morph, we shouldn't be able to click it - if( hint->getVisualParamWeight() != current_weight ) - { - mMouseDownTimer.reset(); - mLastHeldTime = 0.f; - } -} - -// static -void LLScrollingPanelParam::onHintMinHeldDown( void* userdata ) -{ - LLScrollingPanelParam* self = (LLScrollingPanelParam*) userdata; - self->onHintHeldDown( self->mHintMin ); -} - -// static -void LLScrollingPanelParam::onHintMaxHeldDown( void* userdata ) -{ - LLScrollingPanelParam* self = (LLScrollingPanelParam*) userdata; - self->onHintHeldDown( self->mHintMax ); -} - -void LLScrollingPanelParam::onHintHeldDown( LLVisualParamHint* hint ) -{ - F32 current_weight = mWearable->getVisualParamWeight( hint->getVisualParam()->getID() ); - - if (current_weight != hint->getVisualParamWeight() ) - { - const F32 FULL_BLEND_TIME = 2.f; - F32 elapsed_time = mMouseDownTimer.getElapsedTimeF32() - mLastHeldTime; - mLastHeldTime += elapsed_time; - - F32 new_weight; - if (current_weight > hint->getVisualParamWeight() ) - { - new_weight = current_weight - (elapsed_time / FULL_BLEND_TIME); - } - else - { - new_weight = current_weight + (elapsed_time / FULL_BLEND_TIME); - } - - // Make sure we're not taking the slider out of bounds - // (this is where some simple UI limits are stored) - F32 new_percent = weightToPercent(new_weight); - LLSliderCtrl* slider = getChild("param slider"); - if (slider) - { - if (slider->getMinValue() < new_percent - && new_percent < slider->getMaxValue()) - { - mWearable->setVisualParamWeight( hint->getVisualParam()->getID(), new_weight); - mWearable->writeToAvatar(gAgentAvatarp); - gAgentAvatarp->updateVisualParams(); - - slider->setValue( weightToPercent( new_weight ) ); - } - } - } -} - -// static -void LLScrollingPanelParam::onHintMinMouseUp( void* userdata ) -{ - LLScrollingPanelParam* self = (LLScrollingPanelParam*) userdata; - - F32 elapsed_time = self->mMouseDownTimer.getElapsedTimeF32(); - - LLVisualParamHint* hint = self->mHintMin; - - if (elapsed_time < PARAM_STEP_TIME_THRESHOLD) - { - // step in direction - F32 current_weight = self->mWearable->getVisualParamWeight( hint->getVisualParam()->getID() ); - F32 range = self->mHintMax->getVisualParamWeight() - self->mHintMin->getVisualParamWeight(); - // step a fraction in the negative directiona - F32 new_weight = current_weight - (range / 10.f); - F32 new_percent = self->weightToPercent(new_weight); - LLSliderCtrl* slider = self->getChild("param slider"); - if (slider) - { - if (slider->getMinValue() < new_percent - && new_percent < slider->getMaxValue()) - { - self->mWearable->setVisualParamWeight(hint->getVisualParam()->getID(), new_weight); - self->mWearable->writeToAvatar(gAgentAvatarp); - slider->setValue( self->weightToPercent( new_weight ) ); - } - } - } - - LLVisualParamHint::requestHintUpdates( self->mHintMin, self->mHintMax ); -} - -void LLScrollingPanelParam::onHintMaxMouseUp( void* userdata ) -{ - LLScrollingPanelParam* self = (LLScrollingPanelParam*) userdata; - - F32 elapsed_time = self->mMouseDownTimer.getElapsedTimeF32(); - - if (isAgentAvatarValid()) - { - LLVisualParamHint* hint = self->mHintMax; - - if (elapsed_time < PARAM_STEP_TIME_THRESHOLD) - { - // step in direction - F32 current_weight = self->mWearable->getVisualParamWeight( hint->getVisualParam()->getID() ); - F32 range = self->mHintMax->getVisualParamWeight() - self->mHintMin->getVisualParamWeight(); - // step a fraction in the negative direction - F32 new_weight = current_weight + (range / 10.f); - F32 new_percent = self->weightToPercent(new_weight); - LLSliderCtrl* slider = self->getChild("param slider"); - if (slider) - { - if (slider->getMinValue() < new_percent - && new_percent < slider->getMaxValue()) - { - self->mWearable->setVisualParamWeight(hint->getVisualParam()->getID(), new_weight); - self->mWearable->writeToAvatar(gAgentAvatarp); - slider->setValue( self->weightToPercent( new_weight ) ); - } - } - } - } - - LLVisualParamHint::requestHintUpdates( self->mHintMin, self->mHintMax ); -} - - -F32 LLScrollingPanelParam::weightToPercent( F32 weight ) -{ - LLViewerVisualParam* param = mParam; - return (weight - param->getMinWeight()) / (param->getMaxWeight() - param->getMinWeight()) * 100.f; -} - -F32 LLScrollingPanelParam::percentToWeight( F32 percent ) -{ - LLViewerVisualParam* param = mParam; - return percent / 100.f * (param->getMaxWeight() - param->getMinWeight()) + param->getMinWeight(); -} +/** + * @file llscrollingpanelparam.cpp + * @brief UI panel for a list of visual param panels + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llscrollingpanelparam.h" +#include "llviewerjointmesh.h" +#include "llviewervisualparam.h" +#include "llwearable.h" +#include "llviewervisualparam.h" +#include "lltoolmorph.h" +#include "lltrans.h" +#include "llbutton.h" +#include "llsliderctrl.h" +#include "llagent.h" +#include "llviewborder.h" +#include "llvoavatarself.h" + +// Constants for LLPanelVisualParam +const F32 LLScrollingPanelParam::PARAM_STEP_TIME_THRESHOLD = 0.25f; + +const S32 LLScrollingPanelParam::PARAM_HINT_WIDTH = 128; +const S32 LLScrollingPanelParam::PARAM_HINT_HEIGHT = 128; + +// LLScrollingPanelParam +//static +S32 LLScrollingPanelParam::sUpdateDelayFrames = 0; + +LLScrollingPanelParam::LLScrollingPanelParam( const LLPanel::Params& panel_params, + LLViewerJointMesh* mesh, LLViewerVisualParam* param, bool allow_modify, LLWearable* wearable, LLJoint* jointp, bool use_hints ) + : LLScrollingPanelParamBase( panel_params, mesh, param, allow_modify, wearable, jointp, use_hints) +{ + mLessBtn = getChild("less"); + mMoreBtn = getChild("more"); + mLeftBorder = getChild("left_border"); + mRightBorder = getChild("right_border"); + mMinParamText = getChild("min param text"); + mMaxParamText = getChild("max param text"); + + // *HACK To avoid hard coding texture position, lets use border's position for texture. + static LLUICachedControl slider_ctrl_height ("UISliderctrlHeight", 0); + S32 pos_x = mLeftBorder->getRect().mLeft + mLeftBorder->getBorderWidth(); + S32 pos_y = mLeftBorder->getRect().mBottom + mLeftBorder->getBorderWidth(); + F32 min_weight = param->getMinWeight(); + F32 max_weight = param->getMaxWeight(); + + mHintMin = new LLVisualParamHint( pos_x, pos_y, PARAM_HINT_WIDTH, PARAM_HINT_HEIGHT, mesh, (LLViewerVisualParam*) wearable->getVisualParam(param->getID()), wearable, min_weight, jointp); + pos_x = mRightBorder->getRect().mLeft + mLeftBorder->getBorderWidth(); + mHintMax = new LLVisualParamHint( pos_x, pos_y, PARAM_HINT_WIDTH, PARAM_HINT_HEIGHT, mesh, (LLViewerVisualParam*) wearable->getVisualParam(param->getID()), wearable, max_weight, jointp ); + + mHintMin->setAllowsUpdates( false ); + mHintMax->setAllowsUpdates( false ); + + mMinParamText->setValue(LLTrans::getString(param->getMinDisplayName())); + mMaxParamText->setValue(LLTrans::getString(param->getMaxDisplayName())); + + mLessBtn->setMouseDownCallback(LLScrollingPanelParam::onHintMinMouseDown, this); + mLessBtn->setMouseUpCallback(LLScrollingPanelParam::onHintMinMouseUp, this); + mLessBtn->setHeldDownCallback(LLScrollingPanelParam::onHintMinHeldDown, this); + mLessBtn->setHeldDownDelay(PARAM_STEP_TIME_THRESHOLD); + + mMoreBtn->setMouseDownCallback(LLScrollingPanelParam::onHintMaxMouseDown, this); + mMoreBtn->setMouseUpCallback(LLScrollingPanelParam::onHintMaxMouseUp, this); + mMoreBtn->setHeldDownCallback(LLScrollingPanelParam::onHintMaxHeldDown, this); + mMoreBtn->setHeldDownDelay(PARAM_STEP_TIME_THRESHOLD); + + setVisible(false); + setBorderVisible( false ); +} + +LLScrollingPanelParam::~LLScrollingPanelParam() +{ +} +void LLScrollingPanelParam::updatePanel(bool allow_modify) +{ + if (!mWearable) + { + // not editing a wearable just now, no update necessary + return; + } + LLScrollingPanelParamBase::updatePanel(allow_modify); + + mHintMin->requestUpdate( sUpdateDelayFrames++ ); + mHintMax->requestUpdate( sUpdateDelayFrames++ ); + mLessBtn->setEnabled(mAllowModify); + mMoreBtn->setEnabled(mAllowModify); +} + +void LLScrollingPanelParam::setVisible( bool visible ) +{ + if( getVisible() != visible ) + { + LLPanel::setVisible( visible ); + if (mHintMin) + mHintMin->setAllowsUpdates( visible ); + if (mHintMax) + mHintMax->setAllowsUpdates( visible ); + + if( visible ) + { + if (mHintMin) + mHintMin->setUpdateDelayFrames( sUpdateDelayFrames++ ); + if (mHintMax) + mHintMax->setUpdateDelayFrames( sUpdateDelayFrames++ ); + } + } +} + +void LLScrollingPanelParam::draw() +{ + if( !mWearable ) + { + return; + } + + mLessBtn->setVisible( mHintMin->getVisible()); + mMoreBtn->setVisible( mHintMax->getVisible()); + + // hide borders if texture has been loaded + mLeftBorder->setVisible( !mHintMin->getVisible()); + mRightBorder->setVisible( !mHintMax->getVisible()); + + // Draw all the children except for the labels + mMinParamText->setVisible( false ); + mMaxParamText->setVisible( false ); + LLPanel::draw(); + + // If we're in a focused floater, don't apply the floater's alpha to visual param hint, + // making its behavior similar to texture controls'. + F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); + + // Draw the hints over the "less" and "more" buttons. + gGL.pushUIMatrix(); + { + const LLRect& r = mHintMin->getRect(); + gGL.translateUI((F32)r.mLeft, (F32)r.mBottom, 0.f); + mHintMin->draw(alpha); + } + gGL.popUIMatrix(); + + gGL.pushUIMatrix(); + { + const LLRect& r = mHintMax->getRect(); + gGL.translateUI((F32)r.mLeft, (F32)r.mBottom, 0.f); + mHintMax->draw(alpha); + } + gGL.popUIMatrix(); + + + // Draw labels on top of the buttons + mMinParamText->setVisible( true ); + drawChild(mMinParamText); + + mMaxParamText->setVisible( true ); + drawChild(mMaxParamText); +} + +// static +void LLScrollingPanelParam::onSliderMouseDown(LLUICtrl* ctrl, void* userdata) +{ +} + +// static +void LLScrollingPanelParam::onSliderMouseUp(LLUICtrl* ctrl, void* userdata) +{ + LLScrollingPanelParam* self = (LLScrollingPanelParam*) userdata; + LLVisualParamHint::requestHintUpdates( self->mHintMin, self->mHintMax ); +} + +// static +void LLScrollingPanelParam::onHintMinMouseDown( void* userdata ) +{ + LLScrollingPanelParam* self = (LLScrollingPanelParam*) userdata; + self->onHintMouseDown( self->mHintMin ); +} + +// static +void LLScrollingPanelParam::onHintMaxMouseDown( void* userdata ) +{ + LLScrollingPanelParam* self = (LLScrollingPanelParam*) userdata; + self->onHintMouseDown( self->mHintMax ); +} + + +void LLScrollingPanelParam::onHintMouseDown( LLVisualParamHint* hint ) +{ + // morph towards this result + F32 current_weight = mWearable->getVisualParamWeight( hint->getVisualParam()->getID() ); + + // if we have maxed out on this morph, we shouldn't be able to click it + if( hint->getVisualParamWeight() != current_weight ) + { + mMouseDownTimer.reset(); + mLastHeldTime = 0.f; + } +} + +// static +void LLScrollingPanelParam::onHintMinHeldDown( void* userdata ) +{ + LLScrollingPanelParam* self = (LLScrollingPanelParam*) userdata; + self->onHintHeldDown( self->mHintMin ); +} + +// static +void LLScrollingPanelParam::onHintMaxHeldDown( void* userdata ) +{ + LLScrollingPanelParam* self = (LLScrollingPanelParam*) userdata; + self->onHintHeldDown( self->mHintMax ); +} + +void LLScrollingPanelParam::onHintHeldDown( LLVisualParamHint* hint ) +{ + F32 current_weight = mWearable->getVisualParamWeight( hint->getVisualParam()->getID() ); + + if (current_weight != hint->getVisualParamWeight() ) + { + const F32 FULL_BLEND_TIME = 2.f; + F32 elapsed_time = mMouseDownTimer.getElapsedTimeF32() - mLastHeldTime; + mLastHeldTime += elapsed_time; + + F32 new_weight; + if (current_weight > hint->getVisualParamWeight() ) + { + new_weight = current_weight - (elapsed_time / FULL_BLEND_TIME); + } + else + { + new_weight = current_weight + (elapsed_time / FULL_BLEND_TIME); + } + + // Make sure we're not taking the slider out of bounds + // (this is where some simple UI limits are stored) + F32 new_percent = weightToPercent(new_weight); + LLSliderCtrl* slider = getChild("param slider"); + if (slider) + { + if (slider->getMinValue() < new_percent + && new_percent < slider->getMaxValue()) + { + mWearable->setVisualParamWeight( hint->getVisualParam()->getID(), new_weight); + mWearable->writeToAvatar(gAgentAvatarp); + gAgentAvatarp->updateVisualParams(); + + slider->setValue( weightToPercent( new_weight ) ); + } + } + } +} + +// static +void LLScrollingPanelParam::onHintMinMouseUp( void* userdata ) +{ + LLScrollingPanelParam* self = (LLScrollingPanelParam*) userdata; + + F32 elapsed_time = self->mMouseDownTimer.getElapsedTimeF32(); + + LLVisualParamHint* hint = self->mHintMin; + + if (elapsed_time < PARAM_STEP_TIME_THRESHOLD) + { + // step in direction + F32 current_weight = self->mWearable->getVisualParamWeight( hint->getVisualParam()->getID() ); + F32 range = self->mHintMax->getVisualParamWeight() - self->mHintMin->getVisualParamWeight(); + // step a fraction in the negative directiona + F32 new_weight = current_weight - (range / 10.f); + F32 new_percent = self->weightToPercent(new_weight); + LLSliderCtrl* slider = self->getChild("param slider"); + if (slider) + { + if (slider->getMinValue() < new_percent + && new_percent < slider->getMaxValue()) + { + self->mWearable->setVisualParamWeight(hint->getVisualParam()->getID(), new_weight); + self->mWearable->writeToAvatar(gAgentAvatarp); + slider->setValue( self->weightToPercent( new_weight ) ); + } + } + } + + LLVisualParamHint::requestHintUpdates( self->mHintMin, self->mHintMax ); +} + +void LLScrollingPanelParam::onHintMaxMouseUp( void* userdata ) +{ + LLScrollingPanelParam* self = (LLScrollingPanelParam*) userdata; + + F32 elapsed_time = self->mMouseDownTimer.getElapsedTimeF32(); + + if (isAgentAvatarValid()) + { + LLVisualParamHint* hint = self->mHintMax; + + if (elapsed_time < PARAM_STEP_TIME_THRESHOLD) + { + // step in direction + F32 current_weight = self->mWearable->getVisualParamWeight( hint->getVisualParam()->getID() ); + F32 range = self->mHintMax->getVisualParamWeight() - self->mHintMin->getVisualParamWeight(); + // step a fraction in the negative direction + F32 new_weight = current_weight + (range / 10.f); + F32 new_percent = self->weightToPercent(new_weight); + LLSliderCtrl* slider = self->getChild("param slider"); + if (slider) + { + if (slider->getMinValue() < new_percent + && new_percent < slider->getMaxValue()) + { + self->mWearable->setVisualParamWeight(hint->getVisualParam()->getID(), new_weight); + self->mWearable->writeToAvatar(gAgentAvatarp); + slider->setValue( self->weightToPercent( new_weight ) ); + } + } + } + } + + LLVisualParamHint::requestHintUpdates( self->mHintMin, self->mHintMax ); +} + + +F32 LLScrollingPanelParam::weightToPercent( F32 weight ) +{ + LLViewerVisualParam* param = mParam; + return (weight - param->getMinWeight()) / (param->getMaxWeight() - param->getMinWeight()) * 100.f; +} + +F32 LLScrollingPanelParam::percentToWeight( F32 percent ) +{ + LLViewerVisualParam* param = mParam; + return percent / 100.f * (param->getMaxWeight() - param->getMinWeight()) + param->getMinWeight(); +} diff --git a/indra/newview/llscrollingpanelparam.h b/indra/newview/llscrollingpanelparam.h index c62ee4e62c..3aba4e4e40 100644 --- a/indra/newview/llscrollingpanelparam.h +++ b/indra/newview/llscrollingpanelparam.h @@ -1,94 +1,94 @@ -/** - * @file llscrollingpanelparam.h - * @brief the scrolling panel containing a list of visual param - * panels - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_SCROLLINGPANELPARAM_H -#define LL_SCROLLINGPANELPARAM_H - -#include "llscrollingpanelparambase.h" - -class LLBorder; -class LLButton; -class LLViewerJointMesh; -class LLViewerVisualParam; -class LLWearable; -class LLVisualParamHint; -class LLViewerVisualParam; -class LLJoint; - -class LLScrollingPanelParam : public LLScrollingPanelParamBase -{ -public: - LLScrollingPanelParam( const LLPanel::Params& panel_params, - LLViewerJointMesh* mesh, LLViewerVisualParam* param, bool allow_modify, LLWearable* wearable, LLJoint* jointp, bool use_hints = true ); - virtual ~LLScrollingPanelParam(); - - void draw() override; - void setVisible(bool visible) override; - void updatePanel(bool allow_modify) override; - - static void onSliderMouseDown(LLUICtrl* ctrl, void* userdata); - static void onSliderMouseUp(LLUICtrl* ctrl, void* userdata); - - static void onHintMinMouseDown(void* userdata); - static void onHintMinHeldDown(void* userdata); - static void onHintMaxMouseDown(void* userdata); - static void onHintMaxHeldDown(void* userdata); - static void onHintMinMouseUp(void* userdata); - static void onHintMaxMouseUp(void* userdata); - - void onHintMouseDown( LLVisualParamHint* hint ); - void onHintHeldDown( LLVisualParamHint* hint ); - - F32 weightToPercent( F32 weight ); - F32 percentToWeight( F32 percent ); - -public: - // Constants for LLPanelVisualParam - const static F32 PARAM_STEP_TIME_THRESHOLD; - - const static S32 PARAM_HINT_WIDTH; - const static S32 PARAM_HINT_HEIGHT; - -public: - LLPointer mHintMin; - LLPointer mHintMax; - static S32 sUpdateDelayFrames; - -protected: - LLTimer mMouseDownTimer; // timer for how long mouse has been held down on a hint. - F32 mLastHeldTime; - bool mAllowModify; - - LLButton* mLessBtn; - LLButton* mMoreBtn; - LLViewBorder* mLeftBorder; - LLViewBorder* mRightBorder; - LLUICtrl* mMinParamText; - LLUICtrl* mMaxParamText; -}; - -#endif +/** + * @file llscrollingpanelparam.h + * @brief the scrolling panel containing a list of visual param + * panels + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_SCROLLINGPANELPARAM_H +#define LL_SCROLLINGPANELPARAM_H + +#include "llscrollingpanelparambase.h" + +class LLBorder; +class LLButton; +class LLViewerJointMesh; +class LLViewerVisualParam; +class LLWearable; +class LLVisualParamHint; +class LLViewerVisualParam; +class LLJoint; + +class LLScrollingPanelParam : public LLScrollingPanelParamBase +{ +public: + LLScrollingPanelParam( const LLPanel::Params& panel_params, + LLViewerJointMesh* mesh, LLViewerVisualParam* param, bool allow_modify, LLWearable* wearable, LLJoint* jointp, bool use_hints = true ); + virtual ~LLScrollingPanelParam(); + + void draw() override; + void setVisible(bool visible) override; + void updatePanel(bool allow_modify) override; + + static void onSliderMouseDown(LLUICtrl* ctrl, void* userdata); + static void onSliderMouseUp(LLUICtrl* ctrl, void* userdata); + + static void onHintMinMouseDown(void* userdata); + static void onHintMinHeldDown(void* userdata); + static void onHintMaxMouseDown(void* userdata); + static void onHintMaxHeldDown(void* userdata); + static void onHintMinMouseUp(void* userdata); + static void onHintMaxMouseUp(void* userdata); + + void onHintMouseDown( LLVisualParamHint* hint ); + void onHintHeldDown( LLVisualParamHint* hint ); + + F32 weightToPercent( F32 weight ); + F32 percentToWeight( F32 percent ); + +public: + // Constants for LLPanelVisualParam + const static F32 PARAM_STEP_TIME_THRESHOLD; + + const static S32 PARAM_HINT_WIDTH; + const static S32 PARAM_HINT_HEIGHT; + +public: + LLPointer mHintMin; + LLPointer mHintMax; + static S32 sUpdateDelayFrames; + +protected: + LLTimer mMouseDownTimer; // timer for how long mouse has been held down on a hint. + F32 mLastHeldTime; + bool mAllowModify; + + LLButton* mLessBtn; + LLButton* mMoreBtn; + LLViewBorder* mLeftBorder; + LLViewBorder* mRightBorder; + LLUICtrl* mMinParamText; + LLUICtrl* mMaxParamText; +}; + +#endif diff --git a/indra/newview/llscrollingpanelparambase.cpp b/indra/newview/llscrollingpanelparambase.cpp index 6d1b3971bf..247639aa48 100644 --- a/indra/newview/llscrollingpanelparambase.cpp +++ b/indra/newview/llscrollingpanelparambase.cpp @@ -1,112 +1,112 @@ -/** - * @file llscrollingpanelparam.cpp - * @brief UI panel for a list of visual param panels - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llscrollingpanelparambase.h" -#include "llviewerjointmesh.h" -#include "llviewervisualparam.h" -#include "llwearable.h" -#include "llviewervisualparam.h" -#include "lltoolmorph.h" -#include "lltrans.h" -#include "llbutton.h" -#include "llsliderctrl.h" -#include "llagent.h" -#include "llviewborder.h" -#include "llvoavatarself.h" - -LLScrollingPanelParamBase::LLScrollingPanelParamBase( const LLPanel::Params& panel_params, - LLViewerJointMesh* mesh, LLViewerVisualParam* param, bool allow_modify, LLWearable* wearable, LLJoint* jointp, bool use_hints) - : LLScrollingPanel( panel_params ), - mParam(param), - mAllowModify(allow_modify), - mWearable(wearable) -{ - if (use_hints) - buildFromFile( "panel_scrolling_param.xml"); - else - buildFromFile( "panel_scrolling_param_base.xml"); - - getChild("param slider")->setValue(weightToPercent(param->getWeight())); - - std::string display_name = LLTrans::getString(param->getDisplayName()); - getChild("param slider")->setLabelArg("[DESC]", display_name); - getChildView("param slider")->setEnabled(mAllowModify); - childSetCommitCallback("param slider", LLScrollingPanelParamBase::onSliderMoved, this); - - setVisible(false); - setBorderVisible( false ); -} - -LLScrollingPanelParamBase::~LLScrollingPanelParamBase() -{ -} - -void LLScrollingPanelParamBase::updatePanel(bool allow_modify) -{ - LLViewerVisualParam* param = mParam; - - if (!mWearable) - { - // not editing a wearable just now, no update necessary - return; - } - - F32 current_weight = mWearable->getVisualParamWeight( param->getID() ); - getChild("param slider")->setValue(weightToPercent( current_weight ) ); - mAllowModify = allow_modify; - getChildView("param slider")->setEnabled(mAllowModify); -} - -// static -void LLScrollingPanelParamBase::onSliderMoved(LLUICtrl* ctrl, void* userdata) -{ - LLSliderCtrl* slider = (LLSliderCtrl*) ctrl; - LLScrollingPanelParamBase* self = (LLScrollingPanelParamBase*) userdata; - LLViewerVisualParam* param = self->mParam; - - F32 current_weight = self->mWearable->getVisualParamWeight( param->getID() ); - F32 new_weight = self->percentToWeight( (F32)slider->getValue().asReal() ); - if (current_weight != new_weight ) - { - self->mWearable->setVisualParamWeight( param->getID(), new_weight); - self->mWearable->writeToAvatar(gAgentAvatarp); - gAgentAvatarp->updateVisualParams(); - } -} - -F32 LLScrollingPanelParamBase::weightToPercent( F32 weight ) -{ - LLViewerVisualParam* param = mParam; - return (weight - param->getMinWeight()) / (param->getMaxWeight() - param->getMinWeight()) * 100.f; -} - -F32 LLScrollingPanelParamBase::percentToWeight( F32 percent ) -{ - LLViewerVisualParam* param = mParam; - return percent / 100.f * (param->getMaxWeight() - param->getMinWeight()) + param->getMinWeight(); -} +/** + * @file llscrollingpanelparam.cpp + * @brief UI panel for a list of visual param panels + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llscrollingpanelparambase.h" +#include "llviewerjointmesh.h" +#include "llviewervisualparam.h" +#include "llwearable.h" +#include "llviewervisualparam.h" +#include "lltoolmorph.h" +#include "lltrans.h" +#include "llbutton.h" +#include "llsliderctrl.h" +#include "llagent.h" +#include "llviewborder.h" +#include "llvoavatarself.h" + +LLScrollingPanelParamBase::LLScrollingPanelParamBase( const LLPanel::Params& panel_params, + LLViewerJointMesh* mesh, LLViewerVisualParam* param, bool allow_modify, LLWearable* wearable, LLJoint* jointp, bool use_hints) + : LLScrollingPanel( panel_params ), + mParam(param), + mAllowModify(allow_modify), + mWearable(wearable) +{ + if (use_hints) + buildFromFile( "panel_scrolling_param.xml"); + else + buildFromFile( "panel_scrolling_param_base.xml"); + + getChild("param slider")->setValue(weightToPercent(param->getWeight())); + + std::string display_name = LLTrans::getString(param->getDisplayName()); + getChild("param slider")->setLabelArg("[DESC]", display_name); + getChildView("param slider")->setEnabled(mAllowModify); + childSetCommitCallback("param slider", LLScrollingPanelParamBase::onSliderMoved, this); + + setVisible(false); + setBorderVisible( false ); +} + +LLScrollingPanelParamBase::~LLScrollingPanelParamBase() +{ +} + +void LLScrollingPanelParamBase::updatePanel(bool allow_modify) +{ + LLViewerVisualParam* param = mParam; + + if (!mWearable) + { + // not editing a wearable just now, no update necessary + return; + } + + F32 current_weight = mWearable->getVisualParamWeight( param->getID() ); + getChild("param slider")->setValue(weightToPercent( current_weight ) ); + mAllowModify = allow_modify; + getChildView("param slider")->setEnabled(mAllowModify); +} + +// static +void LLScrollingPanelParamBase::onSliderMoved(LLUICtrl* ctrl, void* userdata) +{ + LLSliderCtrl* slider = (LLSliderCtrl*) ctrl; + LLScrollingPanelParamBase* self = (LLScrollingPanelParamBase*) userdata; + LLViewerVisualParam* param = self->mParam; + + F32 current_weight = self->mWearable->getVisualParamWeight( param->getID() ); + F32 new_weight = self->percentToWeight( (F32)slider->getValue().asReal() ); + if (current_weight != new_weight ) + { + self->mWearable->setVisualParamWeight( param->getID(), new_weight); + self->mWearable->writeToAvatar(gAgentAvatarp); + gAgentAvatarp->updateVisualParams(); + } +} + +F32 LLScrollingPanelParamBase::weightToPercent( F32 weight ) +{ + LLViewerVisualParam* param = mParam; + return (weight - param->getMinWeight()) / (param->getMaxWeight() - param->getMinWeight()) * 100.f; +} + +F32 LLScrollingPanelParamBase::percentToWeight( F32 percent ) +{ + LLViewerVisualParam* param = mParam; + return percent / 100.f * (param->getMaxWeight() - param->getMinWeight()) + param->getMinWeight(); +} diff --git a/indra/newview/llscrollingpanelparambase.h b/indra/newview/llscrollingpanelparambase.h index 656ca87c05..9deafcc81a 100644 --- a/indra/newview/llscrollingpanelparambase.h +++ b/indra/newview/llscrollingpanelparambase.h @@ -1,62 +1,62 @@ -/** - * @file llscrollingpanelparam.h - * @brief the scrolling panel containing a list of visual param - * panels - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_SCROLLINGPANELPARAMBASE_H -#define LL_SCROLLINGPANELPARAMBASE_H - -#include "llpanel.h" -#include "llscrollingpanellist.h" - -class LLViewerJointMesh; -class LLViewerVisualParam; -class LLWearable; -class LLVisualParamHint; -class LLViewerVisualParam; -class LLJoint; - -class LLScrollingPanelParamBase : public LLScrollingPanel -{ -public: - LLScrollingPanelParamBase( const LLPanel::Params& panel_params, - LLViewerJointMesh* mesh, LLViewerVisualParam* param, bool allow_modify, LLWearable* wearable, LLJoint* jointp, bool use_hints = false ); - virtual ~LLScrollingPanelParamBase(); - - void updatePanel(bool allow_modify) override; - - static void onSliderMoved(LLUICtrl* ctrl, void* userdata); - - F32 weightToPercent( F32 weight ); - F32 percentToWeight( F32 percent ); - -public: - LLViewerVisualParam* mParam; -protected: - bool mAllowModify; - LLWearable *mWearable; -}; - -#endif +/** + * @file llscrollingpanelparam.h + * @brief the scrolling panel containing a list of visual param + * panels + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_SCROLLINGPANELPARAMBASE_H +#define LL_SCROLLINGPANELPARAMBASE_H + +#include "llpanel.h" +#include "llscrollingpanellist.h" + +class LLViewerJointMesh; +class LLViewerVisualParam; +class LLWearable; +class LLVisualParamHint; +class LLViewerVisualParam; +class LLJoint; + +class LLScrollingPanelParamBase : public LLScrollingPanel +{ +public: + LLScrollingPanelParamBase( const LLPanel::Params& panel_params, + LLViewerJointMesh* mesh, LLViewerVisualParam* param, bool allow_modify, LLWearable* wearable, LLJoint* jointp, bool use_hints = false ); + virtual ~LLScrollingPanelParamBase(); + + void updatePanel(bool allow_modify) override; + + static void onSliderMoved(LLUICtrl* ctrl, void* userdata); + + F32 weightToPercent( F32 weight ); + F32 percentToWeight( F32 percent ); + +public: + LLViewerVisualParam* mParam; +protected: + bool mAllowModify; + LLWearable *mWearable; +}; + +#endif diff --git a/indra/newview/llsearchableui.cpp b/indra/newview/llsearchableui.cpp index c9bedeb0dd..c20f5d4230 100644 --- a/indra/newview/llsearchableui.cpp +++ b/indra/newview/llsearchableui.cpp @@ -1,175 +1,175 @@ -/** -* @file llsearchableui.cpp -* -* $LicenseInfo:firstyear=2019&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2019, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#include "llviewerprecompiledheaders.h" -#include "llsearchableui.h" - -#include "llview.h" -#include "lltabcontainer.h" -#include "llmenugl.h" - -ll::prefs::SearchableItem::~SearchableItem() -{} - -void ll::prefs::SearchableItem::setNotHighlighted() -{ - mCtrl->setHighlighted( false ); -} - -bool ll::prefs::SearchableItem::hightlightAndHide( LLWString const &aFilter ) -{ - if( mCtrl->getHighlighted() ) - return true; - - LLView const *pView = dynamic_cast< LLView const* >( mCtrl ); - if( pView && !pView->getVisible() ) - return false; - - if( aFilter.empty() ) - { - mCtrl->setHighlighted( false ); - return true; - } - - if( mLabel.find( aFilter ) != LLWString::npos ) - { - mCtrl->setHighlighted( true ); - return true; - } - - return false; -} - -ll::prefs::PanelData::~PanelData() -{} - -bool ll::prefs::PanelData::hightlightAndHide( LLWString const &aFilter ) -{ - for( tSearchableItemList::iterator itr = mChildren.begin(); itr != mChildren.end(); ++itr ) - (*itr)->setNotHighlighted(); - - for (tPanelDataList::iterator itr = mChildPanel.begin(); itr != mChildPanel.end(); ++itr) - (*itr)->setNotHighlighted(); - - if (aFilter.empty()) - { - return true; - } - - bool bVisible(false); - for( tSearchableItemList::iterator itr = mChildren.begin(); itr != mChildren.end(); ++itr ) - bVisible |= (*itr)->hightlightAndHide( aFilter ); - - for( tPanelDataList::iterator itr = mChildPanel.begin(); itr != mChildPanel.end(); ++itr ) - bVisible |= (*itr)->hightlightAndHide( aFilter ); - - return bVisible; -} - -void ll::prefs::PanelData::setNotHighlighted() -{ - for (tSearchableItemList::iterator itr = mChildren.begin(); itr != mChildren.end(); ++itr) - (*itr)->setNotHighlighted(); - - for (tPanelDataList::iterator itr = mChildPanel.begin(); itr != mChildPanel.end(); ++itr) - (*itr)->setNotHighlighted(); -} - -bool ll::prefs::TabContainerData::hightlightAndHide( LLWString const &aFilter ) -{ - for( tSearchableItemList::iterator itr = mChildren.begin(); itr != mChildren.end(); ++itr ) - (*itr)->setNotHighlighted( ); - - bool bVisible(false); - for( tSearchableItemList::iterator itr = mChildren.begin(); itr != mChildren.end(); ++itr ) - bVisible |= (*itr)->hightlightAndHide( aFilter ); - - for( tPanelDataList::iterator itr = mChildPanel.begin(); itr != mChildPanel.end(); ++itr ) - { - bool bPanelVisible = (*itr)->hightlightAndHide( aFilter ); - if( (*itr)->mPanel ) - mTabContainer->setTabVisibility( (*itr)->mPanel, bPanelVisible ); - bVisible |= bPanelVisible; - } - - return bVisible; -} - -ll::statusbar::SearchableItem::SearchableItem() - : mMenu(0) - , mCtrl(0) - , mWasHiddenBySearch( false ) -{ } - -void ll::statusbar::SearchableItem::setNotHighlighted( ) -{ - for( tSearchableItemList::iterator itr = mChildren.begin(); itr != mChildren.end(); ++itr ) - (*itr)->setNotHighlighted( ); - - if( mCtrl ) - { - mCtrl->setHighlighted( false ); - - if (mWasHiddenBySearch) - { - mMenu->setVisible(true); - mWasHiddenBySearch = false; - } - } -} - -bool ll::statusbar::SearchableItem::hightlightAndHide(LLWString const &aFilter, bool hide) -{ - if ((mMenu && !mMenu->getVisible() && !mWasHiddenBySearch) || dynamic_cast(mMenu)) - return false; - - setNotHighlighted( ); - - if( aFilter.empty() ) - { - if( mCtrl ) - mCtrl->setHighlighted( false ); - return true; - } - - bool bHighlighted(!hide); - if( mLabel.find( aFilter ) != LLWString::npos ) - { - if( mCtrl ) - mCtrl->setHighlighted( true ); - bHighlighted = true; - } - - bool bVisible(false); - for (tSearchableItemList::iterator itr = mChildren.begin(); itr != mChildren.end(); ++itr) - bVisible |= (*itr)->hightlightAndHide(aFilter, !bHighlighted); - - if (mCtrl && !bVisible && !bHighlighted) - { - mWasHiddenBySearch = true; - mMenu->setVisible(false); - } - return bVisible || bHighlighted; -} +/** +* @file llsearchableui.cpp +* +* $LicenseInfo:firstyear=2019&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2019, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" +#include "llsearchableui.h" + +#include "llview.h" +#include "lltabcontainer.h" +#include "llmenugl.h" + +ll::prefs::SearchableItem::~SearchableItem() +{} + +void ll::prefs::SearchableItem::setNotHighlighted() +{ + mCtrl->setHighlighted( false ); +} + +bool ll::prefs::SearchableItem::hightlightAndHide( LLWString const &aFilter ) +{ + if( mCtrl->getHighlighted() ) + return true; + + LLView const *pView = dynamic_cast< LLView const* >( mCtrl ); + if( pView && !pView->getVisible() ) + return false; + + if( aFilter.empty() ) + { + mCtrl->setHighlighted( false ); + return true; + } + + if( mLabel.find( aFilter ) != LLWString::npos ) + { + mCtrl->setHighlighted( true ); + return true; + } + + return false; +} + +ll::prefs::PanelData::~PanelData() +{} + +bool ll::prefs::PanelData::hightlightAndHide( LLWString const &aFilter ) +{ + for( tSearchableItemList::iterator itr = mChildren.begin(); itr != mChildren.end(); ++itr ) + (*itr)->setNotHighlighted(); + + for (tPanelDataList::iterator itr = mChildPanel.begin(); itr != mChildPanel.end(); ++itr) + (*itr)->setNotHighlighted(); + + if (aFilter.empty()) + { + return true; + } + + bool bVisible(false); + for( tSearchableItemList::iterator itr = mChildren.begin(); itr != mChildren.end(); ++itr ) + bVisible |= (*itr)->hightlightAndHide( aFilter ); + + for( tPanelDataList::iterator itr = mChildPanel.begin(); itr != mChildPanel.end(); ++itr ) + bVisible |= (*itr)->hightlightAndHide( aFilter ); + + return bVisible; +} + +void ll::prefs::PanelData::setNotHighlighted() +{ + for (tSearchableItemList::iterator itr = mChildren.begin(); itr != mChildren.end(); ++itr) + (*itr)->setNotHighlighted(); + + for (tPanelDataList::iterator itr = mChildPanel.begin(); itr != mChildPanel.end(); ++itr) + (*itr)->setNotHighlighted(); +} + +bool ll::prefs::TabContainerData::hightlightAndHide( LLWString const &aFilter ) +{ + for( tSearchableItemList::iterator itr = mChildren.begin(); itr != mChildren.end(); ++itr ) + (*itr)->setNotHighlighted( ); + + bool bVisible(false); + for( tSearchableItemList::iterator itr = mChildren.begin(); itr != mChildren.end(); ++itr ) + bVisible |= (*itr)->hightlightAndHide( aFilter ); + + for( tPanelDataList::iterator itr = mChildPanel.begin(); itr != mChildPanel.end(); ++itr ) + { + bool bPanelVisible = (*itr)->hightlightAndHide( aFilter ); + if( (*itr)->mPanel ) + mTabContainer->setTabVisibility( (*itr)->mPanel, bPanelVisible ); + bVisible |= bPanelVisible; + } + + return bVisible; +} + +ll::statusbar::SearchableItem::SearchableItem() + : mMenu(0) + , mCtrl(0) + , mWasHiddenBySearch( false ) +{ } + +void ll::statusbar::SearchableItem::setNotHighlighted( ) +{ + for( tSearchableItemList::iterator itr = mChildren.begin(); itr != mChildren.end(); ++itr ) + (*itr)->setNotHighlighted( ); + + if( mCtrl ) + { + mCtrl->setHighlighted( false ); + + if (mWasHiddenBySearch) + { + mMenu->setVisible(true); + mWasHiddenBySearch = false; + } + } +} + +bool ll::statusbar::SearchableItem::hightlightAndHide(LLWString const &aFilter, bool hide) +{ + if ((mMenu && !mMenu->getVisible() && !mWasHiddenBySearch) || dynamic_cast(mMenu)) + return false; + + setNotHighlighted( ); + + if( aFilter.empty() ) + { + if( mCtrl ) + mCtrl->setHighlighted( false ); + return true; + } + + bool bHighlighted(!hide); + if( mLabel.find( aFilter ) != LLWString::npos ) + { + if( mCtrl ) + mCtrl->setHighlighted( true ); + bHighlighted = true; + } + + bool bVisible(false); + for (tSearchableItemList::iterator itr = mChildren.begin(); itr != mChildren.end(); ++itr) + bVisible |= (*itr)->hightlightAndHide(aFilter, !bHighlighted); + + if (mCtrl && !bVisible && !bHighlighted) + { + mWasHiddenBySearch = true; + mMenu->setVisible(false); + } + return bVisible || bHighlighted; +} diff --git a/indra/newview/llsearchcombobox.cpp b/indra/newview/llsearchcombobox.cpp index d60656a55d..10d9ef81eb 100644 --- a/indra/newview/llsearchcombobox.cpp +++ b/indra/newview/llsearchcombobox.cpp @@ -1,263 +1,263 @@ -/** - * @file llsearchcombobox.cpp - * @brief Search Combobox implementation - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llsearchcombobox.h" - -#include "llkeyboard.h" -#include "lltrans.h" // for LLTrans::getString() -#include "lluictrlfactory.h" - -static LLDefaultChildRegistry::Register r1("search_combo_box"); - -class LLSearchHistoryBuilder -{ -public: - LLSearchHistoryBuilder(LLSearchComboBox* combo_box, const std::string& filter); - - virtual void buildSearchHistory(); - - virtual ~LLSearchHistoryBuilder(){} - -protected: - - virtual bool filterSearchHistory(); - - LLSearchComboBox* mComboBox; - std::string mFilter; - LLSearchHistory::search_history_list_t mFilteredSearchHistory; -}; - -LLSearchComboBox::Params::Params() -: search_button("search_button"), - dropdown_button_visible("dropdown_button_visible", false) -{} - -LLSearchComboBox::LLSearchComboBox(const Params&p) -: LLComboBox(p) -{ - S32 btn_top = p.search_button.top_pad + p.search_button.rect.height; - S32 btn_right = p.search_button.rect.width + p.search_button.left_pad; - LLRect search_btn_rect(p.search_button.left_pad, btn_top, btn_right, p.search_button.top_pad); - - LLButton::Params button_params(p.search_button); - button_params.name(std::string("search_btn")); - button_params.rect(search_btn_rect) ; - button_params.follows.flags(FOLLOWS_LEFT|FOLLOWS_TOP); - button_params.tab_stop(false); - button_params.click_callback.function(boost::bind(&LLSearchComboBox::onSelectionCommit, this)); - mSearchButton = LLUICtrlFactory::create(button_params); - mTextEntry->addChild(mSearchButton); - mTextEntry->setPassDelete(true); - - setButtonVisible(p.dropdown_button_visible); - mTextEntry->setCommitCallback(boost::bind(&LLComboBox::onTextCommit, this, _2)); - mTextEntry->setKeystrokeCallback(boost::bind(&LLComboBox::onTextEntry, this, _1), NULL); - setCommitCallback(boost::bind(&LLSearchComboBox::onSelectionCommit, this)); - setPrearrangeCallback(boost::bind(&LLSearchComboBox::onSearchPrearrange, this, _2)); - mSearchButton->setCommitCallback(boost::bind(&LLSearchComboBox::onTextCommit, this, _2)); -} - -void LLSearchComboBox::rebuildSearchHistory(const std::string& filter) -{ - LLSearchHistoryBuilder builder(this, filter); - builder.buildSearchHistory(); -} - -void LLSearchComboBox::onSearchPrearrange(const LLSD& data) -{ - std::string filter = data.asString(); - rebuildSearchHistory(filter); - - mList->mouseOverHighlightNthItem(-1); // Clear highlight on the last selected item. -} - -void LLSearchComboBox::onTextEntry(LLLineEditor* line_editor) -{ - KEY key = gKeyboard->currentKey(); - - if (line_editor->getText().empty()) - { - prearrangeList(); // resets filter - hideList(); - } - // Typing? (moving cursor should not affect showing the list) - else if (key != KEY_LEFT && key != KEY_RIGHT && key != KEY_HOME && key != KEY_END) - { - prearrangeList(line_editor->getText()); - if (mList->getItemCount() != 0) - { - showList(); - focusTextEntry(); - } - else - { - // Hide the list if it's empty. - hideList(); - } - } - LLComboBox::onTextEntry(line_editor); -} - -void LLSearchComboBox::focusTextEntry() -{ - // We can't use "mTextEntry->setFocus(true)" instead because - // if the "select_on_focus" parameter is true it places the cursor - // at the beginning (after selecting text), thus screwing up updateSelection(). - if (mTextEntry) - { - gFocusMgr.setKeyboardFocus(mTextEntry); - - // Let the editor handle editing hotkeys (STORM-431). - LLEditMenuHandler::gEditMenuHandler = mTextEntry; - } -} - -void LLSearchComboBox::hideList() -{ - LLComboBox::hideList(); - if (mTextEntry && hasFocus()) - focusTextEntry(); -} - -LLSearchComboBox::~LLSearchComboBox() -{ -} - -void LLSearchComboBox::onSelectionCommit() -{ - std::string search_query = getSimple(); - LLStringUtil::trim(search_query); - - // Order of add() and mTextEntry->setText does matter because add() will select first item - // in drop down list and its label will be copied to text box rewriting mTextEntry->setText() call - if(!search_query.empty()) - { - remove(search_query); - add(search_query, ADD_TOP); - } - - mTextEntry->setText(search_query); - setControlValue(search_query); -} - -bool LLSearchComboBox::remove(const std::string& name) -{ - bool found = mList->selectItemByLabel(name, false); - - if (found) - { - LLScrollListItem* item = mList->getFirstSelected(); - if (item) - { - LLComboBox::remove(mList->getItemIndex(item)); - } - } - - return found; -} - -void LLSearchComboBox::clearHistory() -{ - removeall(); - setTextEntry(LLStringUtil::null); -} - -bool LLSearchComboBox::handleKeyHere(KEY key,MASK mask ) -{ - if(mTextEntry->hasFocus() && MASK_NONE == mask && KEY_DOWN == key) - { - S32 first = 0; - S32 size = 0; - - // get entered text (without auto-complete part) - mTextEntry->getSelectionRange(&first, &size); - std::string search_query = mTextEntry->getText(); - search_query.erase(first, size); - - onSearchPrearrange(search_query); - } - return LLComboBox::handleKeyHere(key, mask); -} - -LLSearchHistoryBuilder::LLSearchHistoryBuilder(LLSearchComboBox* combo_box, const std::string& filter) -: mComboBox(combo_box) -, mFilter(filter) -{ -} - -bool LLSearchHistoryBuilder::filterSearchHistory() -{ - // *TODO: an STL algorithm would look nicer - mFilteredSearchHistory.clear(); - - std::string filter_copy = mFilter; - LLStringUtil::toLower(filter_copy); - - LLSearchHistory::search_history_list_t history = - LLSearchHistory::getInstance()->getSearchHistoryList(); - - LLSearchHistory::search_history_list_t::const_iterator it = history.begin(); - for ( ; it != history.end(); ++it) - { - std::string search_query = (*it).search_query; - LLStringUtil::toLower(search_query); - - if (search_query.find(filter_copy) != std::string::npos) - mFilteredSearchHistory.push_back(*it); - } - - return mFilteredSearchHistory.size(); -} - -void LLSearchHistoryBuilder::buildSearchHistory() -{ - mFilteredSearchHistory.clear(); - - LLSearchHistory::search_history_list_t filtered_items; - LLSearchHistory::search_history_list_t* itemsp = NULL; - LLSearchHistory* sh = LLSearchHistory::getInstance(); - - if (mFilter.empty()) - { - itemsp = &sh->getSearchHistoryList(); - } - else - { - filterSearchHistory(); - itemsp = &mFilteredSearchHistory; - itemsp->sort(); - } - - mComboBox->removeall(); - - LLSearchHistory::search_history_list_t::const_iterator it = itemsp->begin(); - for ( ; it != itemsp->end(); it++) - { - LLSearchHistory::LLSearchHistoryItem item = *it; - mComboBox->add(item.search_query); - } -} +/** + * @file llsearchcombobox.cpp + * @brief Search Combobox implementation + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llsearchcombobox.h" + +#include "llkeyboard.h" +#include "lltrans.h" // for LLTrans::getString() +#include "lluictrlfactory.h" + +static LLDefaultChildRegistry::Register r1("search_combo_box"); + +class LLSearchHistoryBuilder +{ +public: + LLSearchHistoryBuilder(LLSearchComboBox* combo_box, const std::string& filter); + + virtual void buildSearchHistory(); + + virtual ~LLSearchHistoryBuilder(){} + +protected: + + virtual bool filterSearchHistory(); + + LLSearchComboBox* mComboBox; + std::string mFilter; + LLSearchHistory::search_history_list_t mFilteredSearchHistory; +}; + +LLSearchComboBox::Params::Params() +: search_button("search_button"), + dropdown_button_visible("dropdown_button_visible", false) +{} + +LLSearchComboBox::LLSearchComboBox(const Params&p) +: LLComboBox(p) +{ + S32 btn_top = p.search_button.top_pad + p.search_button.rect.height; + S32 btn_right = p.search_button.rect.width + p.search_button.left_pad; + LLRect search_btn_rect(p.search_button.left_pad, btn_top, btn_right, p.search_button.top_pad); + + LLButton::Params button_params(p.search_button); + button_params.name(std::string("search_btn")); + button_params.rect(search_btn_rect) ; + button_params.follows.flags(FOLLOWS_LEFT|FOLLOWS_TOP); + button_params.tab_stop(false); + button_params.click_callback.function(boost::bind(&LLSearchComboBox::onSelectionCommit, this)); + mSearchButton = LLUICtrlFactory::create(button_params); + mTextEntry->addChild(mSearchButton); + mTextEntry->setPassDelete(true); + + setButtonVisible(p.dropdown_button_visible); + mTextEntry->setCommitCallback(boost::bind(&LLComboBox::onTextCommit, this, _2)); + mTextEntry->setKeystrokeCallback(boost::bind(&LLComboBox::onTextEntry, this, _1), NULL); + setCommitCallback(boost::bind(&LLSearchComboBox::onSelectionCommit, this)); + setPrearrangeCallback(boost::bind(&LLSearchComboBox::onSearchPrearrange, this, _2)); + mSearchButton->setCommitCallback(boost::bind(&LLSearchComboBox::onTextCommit, this, _2)); +} + +void LLSearchComboBox::rebuildSearchHistory(const std::string& filter) +{ + LLSearchHistoryBuilder builder(this, filter); + builder.buildSearchHistory(); +} + +void LLSearchComboBox::onSearchPrearrange(const LLSD& data) +{ + std::string filter = data.asString(); + rebuildSearchHistory(filter); + + mList->mouseOverHighlightNthItem(-1); // Clear highlight on the last selected item. +} + +void LLSearchComboBox::onTextEntry(LLLineEditor* line_editor) +{ + KEY key = gKeyboard->currentKey(); + + if (line_editor->getText().empty()) + { + prearrangeList(); // resets filter + hideList(); + } + // Typing? (moving cursor should not affect showing the list) + else if (key != KEY_LEFT && key != KEY_RIGHT && key != KEY_HOME && key != KEY_END) + { + prearrangeList(line_editor->getText()); + if (mList->getItemCount() != 0) + { + showList(); + focusTextEntry(); + } + else + { + // Hide the list if it's empty. + hideList(); + } + } + LLComboBox::onTextEntry(line_editor); +} + +void LLSearchComboBox::focusTextEntry() +{ + // We can't use "mTextEntry->setFocus(true)" instead because + // if the "select_on_focus" parameter is true it places the cursor + // at the beginning (after selecting text), thus screwing up updateSelection(). + if (mTextEntry) + { + gFocusMgr.setKeyboardFocus(mTextEntry); + + // Let the editor handle editing hotkeys (STORM-431). + LLEditMenuHandler::gEditMenuHandler = mTextEntry; + } +} + +void LLSearchComboBox::hideList() +{ + LLComboBox::hideList(); + if (mTextEntry && hasFocus()) + focusTextEntry(); +} + +LLSearchComboBox::~LLSearchComboBox() +{ +} + +void LLSearchComboBox::onSelectionCommit() +{ + std::string search_query = getSimple(); + LLStringUtil::trim(search_query); + + // Order of add() and mTextEntry->setText does matter because add() will select first item + // in drop down list and its label will be copied to text box rewriting mTextEntry->setText() call + if(!search_query.empty()) + { + remove(search_query); + add(search_query, ADD_TOP); + } + + mTextEntry->setText(search_query); + setControlValue(search_query); +} + +bool LLSearchComboBox::remove(const std::string& name) +{ + bool found = mList->selectItemByLabel(name, false); + + if (found) + { + LLScrollListItem* item = mList->getFirstSelected(); + if (item) + { + LLComboBox::remove(mList->getItemIndex(item)); + } + } + + return found; +} + +void LLSearchComboBox::clearHistory() +{ + removeall(); + setTextEntry(LLStringUtil::null); +} + +bool LLSearchComboBox::handleKeyHere(KEY key,MASK mask ) +{ + if(mTextEntry->hasFocus() && MASK_NONE == mask && KEY_DOWN == key) + { + S32 first = 0; + S32 size = 0; + + // get entered text (without auto-complete part) + mTextEntry->getSelectionRange(&first, &size); + std::string search_query = mTextEntry->getText(); + search_query.erase(first, size); + + onSearchPrearrange(search_query); + } + return LLComboBox::handleKeyHere(key, mask); +} + +LLSearchHistoryBuilder::LLSearchHistoryBuilder(LLSearchComboBox* combo_box, const std::string& filter) +: mComboBox(combo_box) +, mFilter(filter) +{ +} + +bool LLSearchHistoryBuilder::filterSearchHistory() +{ + // *TODO: an STL algorithm would look nicer + mFilteredSearchHistory.clear(); + + std::string filter_copy = mFilter; + LLStringUtil::toLower(filter_copy); + + LLSearchHistory::search_history_list_t history = + LLSearchHistory::getInstance()->getSearchHistoryList(); + + LLSearchHistory::search_history_list_t::const_iterator it = history.begin(); + for ( ; it != history.end(); ++it) + { + std::string search_query = (*it).search_query; + LLStringUtil::toLower(search_query); + + if (search_query.find(filter_copy) != std::string::npos) + mFilteredSearchHistory.push_back(*it); + } + + return mFilteredSearchHistory.size(); +} + +void LLSearchHistoryBuilder::buildSearchHistory() +{ + mFilteredSearchHistory.clear(); + + LLSearchHistory::search_history_list_t filtered_items; + LLSearchHistory::search_history_list_t* itemsp = NULL; + LLSearchHistory* sh = LLSearchHistory::getInstance(); + + if (mFilter.empty()) + { + itemsp = &sh->getSearchHistoryList(); + } + else + { + filterSearchHistory(); + itemsp = &mFilteredSearchHistory; + itemsp->sort(); + } + + mComboBox->removeall(); + + LLSearchHistory::search_history_list_t::const_iterator it = itemsp->begin(); + for ( ; it != itemsp->end(); it++) + { + LLSearchHistory::LLSearchHistoryItem item = *it; + mComboBox->add(item.search_query); + } +} diff --git a/indra/newview/llsearchcombobox.h b/indra/newview/llsearchcombobox.h index 89362ccc7b..e0162bac80 100644 --- a/indra/newview/llsearchcombobox.h +++ b/indra/newview/llsearchcombobox.h @@ -1,104 +1,104 @@ -/** - * @file llsearchcombobox.h - * @brief LLSearchComboBox class definition - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLSEARCHCOMBOBOX_H -#define LL_LLSEARCHCOMBOBOX_H - -#include "llcombobox.h" -#include "llsearchhistory.h" - -/** - * Search control with text box for search queries and a drop down list - * with recent queries. Supports text auto-complete and filtering of drop down list - * according to typed text. - */ -class LLSearchComboBox : public LLComboBox -{ -public: - - struct Params : public LLInitParam::Block - { - Optional search_button; - Optional dropdown_button_visible; - - Params(); - }; - - /** - * Removes an entry from combo box, case insensitive - */ - bool remove(const std::string& name); - - /** - * Clears search history - */ - void clearHistory(); - - /*virtual*/ bool handleKeyHere(KEY key, MASK mask); - - ~LLSearchComboBox(); - -protected: - - LLSearchComboBox(const Params&p); - friend class LLUICtrlFactory; - - /** - * Handles typing in text box - */ - void onTextEntry(LLLineEditor* line_editor); - - /** - * Hides drop down list and focuses text box - */ - void hideList(); - - /** - * Rebuilds search history, case insensitive - * If filter is an empty string - whole history will be added to combo box - * if filter is valid string - only matching entries will be added - */ - virtual void rebuildSearchHistory(const std::string& filter); - - /** - * Callback for prearrange event - */ - void onSearchPrearrange(const LLSD& data); - - /** - * Callback for text box or combo box commit - */ - void onSelectionCommit(); - - /** - * Sets focus to text box - */ - void focusTextEntry(); - - LLButton* mSearchButton; -}; - -#endif //LL_LLSEARCHCOMBOBOX_H +/** + * @file llsearchcombobox.h + * @brief LLSearchComboBox class definition + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSEARCHCOMBOBOX_H +#define LL_LLSEARCHCOMBOBOX_H + +#include "llcombobox.h" +#include "llsearchhistory.h" + +/** + * Search control with text box for search queries and a drop down list + * with recent queries. Supports text auto-complete and filtering of drop down list + * according to typed text. + */ +class LLSearchComboBox : public LLComboBox +{ +public: + + struct Params : public LLInitParam::Block + { + Optional search_button; + Optional dropdown_button_visible; + + Params(); + }; + + /** + * Removes an entry from combo box, case insensitive + */ + bool remove(const std::string& name); + + /** + * Clears search history + */ + void clearHistory(); + + /*virtual*/ bool handleKeyHere(KEY key, MASK mask); + + ~LLSearchComboBox(); + +protected: + + LLSearchComboBox(const Params&p); + friend class LLUICtrlFactory; + + /** + * Handles typing in text box + */ + void onTextEntry(LLLineEditor* line_editor); + + /** + * Hides drop down list and focuses text box + */ + void hideList(); + + /** + * Rebuilds search history, case insensitive + * If filter is an empty string - whole history will be added to combo box + * if filter is valid string - only matching entries will be added + */ + virtual void rebuildSearchHistory(const std::string& filter); + + /** + * Callback for prearrange event + */ + void onSearchPrearrange(const LLSD& data); + + /** + * Callback for text box or combo box commit + */ + void onSelectionCommit(); + + /** + * Sets focus to text box + */ + void focusTextEntry(); + + LLButton* mSearchButton; +}; + +#endif //LL_LLSEARCHCOMBOBOX_H diff --git a/indra/newview/llsechandler_basic.cpp b/indra/newview/llsechandler_basic.cpp index dffe81d4b1..104bf79832 100644 --- a/indra/newview/llsechandler_basic.cpp +++ b/indra/newview/llsechandler_basic.cpp @@ -1,1972 +1,1972 @@ -/** - * @file llsechandler_basic.cpp - * @brief Security API for services such as certificate handling - * secure local storage, etc. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#include "llviewerprecompiledheaders.h" -#include "llsecapi.h" -#include "llsechandler_basic.h" -#include "llsdserialize.h" -#include "llviewernetwork.h" -#include "llxorcipher.h" -#include "llfile.h" -#include "lldir.h" -#include "llviewercontrol.h" -#include "llexception.h" -#include "stringize.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "llmachineid.h" - - -static const std::string DEFAULT_CREDENTIAL_STORAGE = "credential"; - -// 128 bits of salt data... -#define STORE_SALT_SIZE 16 -#define BUFFER_READ_SIZE 256 -std::string cert_string_from_asn1_string(ASN1_STRING* value); -std::string cert_string_from_octet_string(ASN1_OCTET_STRING* value); - -LLSD _basic_constraints_ext(X509* cert); -LLSD _key_usage_ext(X509* cert); -LLSD _ext_key_usage_ext(X509* cert); -std::string _subject_key_identifier(X509 *cert); -LLSD _authority_key_identifier(X509* cert); -void _validateCert(int validation_policy, - LLPointer cert, - const LLSD& validation_params, - int depth); - -LLBasicCertificate::LLBasicCertificate(const std::string& pem_cert, - const LLSD* validation_params) -{ - // BIO_new_mem_buf returns a read only bio, but takes a void* which isn't const - // so we need to cast it. - BIO * pem_bio = BIO_new_mem_buf((void*)pem_cert.c_str(), pem_cert.length()); - if(pem_bio == NULL) - { - LL_WARNS("SECAPI") << "Could not allocate an openssl memory BIO." << LL_ENDL; - LLTHROW(LLAllocationCertException(LLSD::emptyMap())); - } - mCert = NULL; - PEM_read_bio_X509(pem_bio, &mCert, 0, NULL); - BIO_free(pem_bio); - if (!mCert) - { - LL_WARNS("SECAPI") << "Could not decode certificate to x509." << LL_ENDL; - LLTHROW(LLInvalidCertificate(LLSD::emptyMap())); - } -} - - -LLBasicCertificate::LLBasicCertificate(X509* pCert, - const LLSD* validation_params) -{ - if (!pCert) - { - LLTHROW(LLInvalidCertificate(LLSD::emptyMap())); - } - mCert = X509_dup(pCert); - // it is tempting to run _validateCert here, but doing so causes problems - // the trick is figuring out which aspects to validate. TBD -} - -LLBasicCertificate::~LLBasicCertificate() -{ - if(mCert) - { - X509_free(mCert); - mCert = NULL; - } -} - -// -// retrieve the pem using the openssl functionality -std::string LLBasicCertificate::getPem() const -{ - char * pem_bio_chars = NULL; - // a BIO is the equivalent of a 'std::stream', and - // can be a file, mem stream, whatever. Grab a memory based - // BIO for the result - BIO *pem_bio = BIO_new(BIO_s_mem()); - if (!pem_bio) - { - LL_WARNS("SECAPI") << "Could not allocate an openssl memory BIO." << LL_ENDL; - return std::string(); - } - PEM_write_bio_X509(pem_bio, mCert); - int length = BIO_get_mem_data(pem_bio, &pem_bio_chars); - std::string result = std::string(pem_bio_chars, length); - BIO_free(pem_bio); - return result; -} - -// get the DER encoding for the cert -// DER is a binary encoding format for certs... -std::vector LLBasicCertificate::getBinary() const -{ - U8 * der_bio_data = NULL; - // get a memory bio - BIO *der_bio = BIO_new(BIO_s_mem()); - if (!der_bio) - { - LL_WARNS("SECAPI") << "Could not allocate an openssl memory BIO." << LL_ENDL; - return std::vector(); - } - i2d_X509_bio(der_bio, mCert); - int length = BIO_get_mem_data(der_bio, &der_bio_data); - std::vector result(length); - // vectors are guranteed to be a contiguous chunk of memory. - memcpy(&result[0], der_bio_data, length); - BIO_free(der_bio); - return result; -} - - -void LLBasicCertificate::getLLSD(LLSD &llsd) -{ - if (mLLSDInfo.isUndefined()) - { - _initLLSD(); - } - llsd = mLLSDInfo; -} - -// Initialize the LLSD info for the certificate -LLSD& LLBasicCertificate::_initLLSD() -{ - - // call the various helpers to build the LLSD - mLLSDInfo[CERT_SUBJECT_NAME] = cert_name_from_X509_NAME(X509_get_subject_name(mCert)); - mLLSDInfo[CERT_ISSUER_NAME] = cert_name_from_X509_NAME(X509_get_issuer_name(mCert)); - mLLSDInfo[CERT_SUBJECT_NAME_STRING] = cert_string_name_from_X509_NAME(X509_get_subject_name(mCert)); - mLLSDInfo[CERT_ISSUER_NAME_STRING] = cert_string_name_from_X509_NAME(X509_get_issuer_name(mCert)); - ASN1_INTEGER *sn = X509_get_serialNumber(mCert); - if (sn != NULL) - { - mLLSDInfo[CERT_SERIAL_NUMBER] = cert_string_from_asn1_integer(sn); - } - - mLLSDInfo[CERT_VALID_TO] = cert_date_from_asn1_time(X509_get_notAfter(mCert)); - mLLSDInfo[CERT_VALID_FROM] = cert_date_from_asn1_time(X509_get_notBefore(mCert)); - // add the known extensions - mLLSDInfo[CERT_BASIC_CONSTRAINTS] = _basic_constraints_ext(mCert); - mLLSDInfo[CERT_KEY_USAGE] = _key_usage_ext(mCert); - mLLSDInfo[CERT_EXTENDED_KEY_USAGE] = _ext_key_usage_ext(mCert); - mLLSDInfo[CERT_SUBJECT_KEY_IDENTFIER] = _subject_key_identifier(mCert); - mLLSDInfo[CERT_AUTHORITY_KEY_IDENTIFIER] = _authority_key_identifier(mCert); - return mLLSDInfo; -} - -// Retrieve the basic constraints info -LLSD _basic_constraints_ext(X509* cert) -{ - LLSD result; - BASIC_CONSTRAINTS *bs = (BASIC_CONSTRAINTS *)X509_get_ext_d2i(cert, NID_basic_constraints, NULL, NULL); - if(bs) - { - result = LLSD::emptyMap(); - // Determines whether the cert can be used as a CA - result[CERT_BASIC_CONSTRAINTS_CA] = (bool)bs->ca; - - if(bs->pathlen) - { - // the pathlen determines how deep a certificate chain can be from - // this CA - if((bs->pathlen->type == V_ASN1_NEG_INTEGER) - || !bs->ca) - { - result[CERT_BASIC_CONSTRAINTS_PATHLEN] = 0; - } - else - { - result[CERT_BASIC_CONSTRAINTS_PATHLEN] = (int)ASN1_INTEGER_get(bs->pathlen); - } - } - - BASIC_CONSTRAINTS_free( bs ); - } - return result; -} - -// retrieve the key usage, which specifies how the cert can be used. -// -LLSD _key_usage_ext(X509* cert) -{ - LLSD result; - ASN1_STRING *usage_str = (ASN1_STRING *)X509_get_ext_d2i(cert, NID_key_usage, NULL, NULL); - if(usage_str) - { - result = LLSD::emptyArray(); - long usage = 0; - if(usage_str->length > 0) - { - usage = usage_str->data[0]; - if(usage_str->length > 1) - { - usage |= usage_str->data[1] << 8; - } - } - ASN1_STRING_free(usage_str); - if(usage) - { - if(usage & KU_DIGITAL_SIGNATURE) result.append(LLSD((std::string)CERT_KU_DIGITAL_SIGNATURE)); - if(usage & KU_NON_REPUDIATION) result.append(LLSD((std::string)CERT_KU_NON_REPUDIATION)); - if(usage & KU_KEY_ENCIPHERMENT) result.append(LLSD((std::string)CERT_KU_KEY_ENCIPHERMENT)); - if(usage & KU_DATA_ENCIPHERMENT) result.append(LLSD((std::string)CERT_KU_DATA_ENCIPHERMENT)); - if(usage & KU_KEY_AGREEMENT) result.append(LLSD((std::string)CERT_KU_KEY_AGREEMENT)); - if(usage & KU_KEY_CERT_SIGN) result.append(LLSD((std::string)CERT_KU_CERT_SIGN)); - if(usage & KU_CRL_SIGN) result.append(LLSD((std::string)CERT_KU_CRL_SIGN)); - if(usage & KU_ENCIPHER_ONLY) result.append(LLSD((std::string)CERT_KU_ENCIPHER_ONLY)); - if(usage & KU_DECIPHER_ONLY) result.append(LLSD((std::string)CERT_KU_DECIPHER_ONLY)); - } - } - return result; -} - -// retrieve the extended key usage for the cert -LLSD _ext_key_usage_ext(X509* cert) -{ - LLSD result; - EXTENDED_KEY_USAGE *eku = (EXTENDED_KEY_USAGE *)X509_get_ext_d2i(cert, NID_ext_key_usage, NULL, NULL); - if(eku) - { - result = LLSD::emptyArray(); - while(sk_ASN1_OBJECT_num(eku)) - { - ASN1_OBJECT *usage = sk_ASN1_OBJECT_pop(eku); - if(usage) - { - int nid = OBJ_obj2nid(usage); - if (nid) - { - std::string sn = OBJ_nid2sn(nid); - result.append(sn); - } - ASN1_OBJECT_free(usage); - } - } - - EXTENDED_KEY_USAGE_free( eku ); - } - return result; -} - -// retrieve the subject key identifier of the cert -std::string _subject_key_identifier(X509 *cert) -{ - std::string result; - ASN1_OCTET_STRING *skeyid = (ASN1_OCTET_STRING *)X509_get_ext_d2i(cert, NID_subject_key_identifier, NULL, NULL); - if(skeyid) - { - result = cert_string_from_octet_string(skeyid); - ASN1_OCTET_STRING_free( skeyid ); - } - return result; -} - -// retrieve the authority key identifier of the cert -LLSD _authority_key_identifier(X509* cert) -{ - LLSD result; - AUTHORITY_KEYID *akeyid = (AUTHORITY_KEYID *)X509_get_ext_d2i(cert, NID_authority_key_identifier, NULL, NULL); - if(akeyid) - { - result = LLSD::emptyMap(); - if(akeyid->keyid) - { - result[CERT_AUTHORITY_KEY_IDENTIFIER_ID] = cert_string_from_octet_string(akeyid->keyid); - } - if(akeyid->serial) - { - result[CERT_AUTHORITY_KEY_IDENTIFIER_SERIAL] = cert_string_from_asn1_integer(akeyid->serial); - } - - AUTHORITY_KEYID_free( akeyid ); - } - // we ignore the issuer name in the authority key identifier, we check the issue name via - // the the issuer name entry in the cert. - return result; -} - -// retrieve an openssl x509 object, -// which must be freed by X509_free -X509* LLBasicCertificate::getOpenSSLX509() const -{ - return X509_dup(mCert); -} - -// generate a single string containing the subject or issuer -// name of the cert. -std::string cert_string_name_from_X509_NAME(X509_NAME* name) -{ - char * name_bio_chars = NULL; - // get a memory bio - BIO *name_bio = BIO_new(BIO_s_mem()); - // stream the name into the bio. The name will be in the 'short name' format - X509_NAME_print_ex(name_bio, name, 0, XN_FLAG_RFC2253); - int length = BIO_get_mem_data(name_bio, &name_bio_chars); - std::string result = std::string(name_bio_chars, length); - BIO_free(name_bio); - return result; -} - -// generate an LLSD from a certificate name (issuer or subject name). -// the name will be strings indexed by the 'long form' -LLSD cert_name_from_X509_NAME(X509_NAME* name) -{ - LLSD result = LLSD::emptyMap(); - int name_entries = X509_NAME_entry_count(name); - for (int entry_index=0; entry_index < name_entries; entry_index++) - { - char buffer[32]; - X509_NAME_ENTRY *entry = X509_NAME_get_entry(name, entry_index); - - std::string name_value = std::string((const char*)ASN1_STRING_get0_data(X509_NAME_ENTRY_get_data(entry)), - ASN1_STRING_length(X509_NAME_ENTRY_get_data(entry))); - - ASN1_OBJECT* name_obj = X509_NAME_ENTRY_get_object(entry); - OBJ_obj2txt(buffer, sizeof(buffer), name_obj, 0); - std::string obj_buffer_str = std::string(buffer); - result[obj_buffer_str] = name_value; - } - - return result; -} - -// Generate a string from an ASN1 integer. ASN1 Integers are -// bignums, so they can be 'infinitely' long, therefore we -// cannot simply use a conversion to U64 or something. -// We retrieve as a readable string for UI - -std::string cert_string_from_asn1_integer(ASN1_INTEGER* value) -{ - std::string result; - BIGNUM *bn = ASN1_INTEGER_to_BN(value, NULL); - if(bn) - { - char * ascii_bn = BN_bn2hex(bn); - - if(ascii_bn) - { - result = ascii_bn; - OPENSSL_free(ascii_bn); - } - BN_free(bn); - } - return result; -} - -// Generate a string from an OCTET string. -// we retrieve as a - -std::string cert_string_from_octet_string(ASN1_OCTET_STRING* value) -{ - - std::stringstream result; - result << std::hex << std::setprecision(2); - for (int i=0; i < value->length; i++) - { - if (i != 0) - { - result << ":"; - } - result << std::setfill('0') << std::setw(2) << (int)value->data[i]; - } - return result.str(); -} - -// Generate a string from an ASN1 integer. ASN1 Integers are -// bignums, so they can be 'infinitely' long, therefore we -// cannot simply use a conversion to U64 or something. -// We retrieve as a readable string for UI - -std::string cert_string_from_asn1_string(ASN1_STRING* value) -{ - char * string_bio_chars = NULL; - std::string result; - // get a memory bio - BIO *string_bio = BIO_new(BIO_s_mem()); - if(!string_bio) - { - // stream the name into the bio. The name will be in the 'short name' format - ASN1_STRING_print_ex(string_bio, value, ASN1_STRFLGS_RFC2253); - int length = BIO_get_mem_data(string_bio, &string_bio_chars); - result = std::string(string_bio_chars, length); - BIO_free(string_bio); - } - else - { - LL_WARNS("SECAPI") << "Could not allocate an openssl memory BIO." << LL_ENDL; - } - - return result; -} - -// retrieve a date structure from an ASN1 time, for -// validity checking. -LLDate cert_date_from_asn1_time(ASN1_TIME* asn1_time) -{ - - struct tm timestruct = {0}; - int i = asn1_time->length; - - if (i < 10) - { - return LLDate(); - } - // convert the date from the ASN1 time (which is a string in ZULU time), to - // a timeval. - timestruct.tm_year = (asn1_time->data[0]-'0') * 10 + (asn1_time->data[1]-'0'); - - /* Deal with Year 2000 */ - if (timestruct.tm_year < 70) - timestruct.tm_year += 100; - - timestruct.tm_mon = (asn1_time->data[2]-'0') * 10 + (asn1_time->data[3]-'0') - 1; - timestruct.tm_mday = (asn1_time->data[4]-'0') * 10 + (asn1_time->data[5]-'0'); - timestruct.tm_hour = (asn1_time->data[6]-'0') * 10 + (asn1_time->data[7]-'0'); - timestruct.tm_min = (asn1_time->data[8]-'0') * 10 + (asn1_time->data[9]-'0'); - timestruct.tm_sec = (asn1_time->data[10]-'0') * 10 + (asn1_time->data[11]-'0'); - -#if LL_WINDOWS - return LLDate((F64)_mkgmtime(×truct)); -#else // LL_WINDOWS - return LLDate((F64)timegm(×truct)); -#endif // LL_WINDOWS -} - -// class LLBasicCertificateVector -// This class represents a list of certificates, implemented by a vector of certificate pointers. -// it contains implementations of the virtual functions for iterators, search, add, remove, etc. -// - -// Find a certificate in the list. -// It will find a cert that has minimally the params listed, with the values being the same -LLBasicCertificateVector::iterator LLBasicCertificateVector::find(const LLSD& params) -{ - // loop through the entire vector comparing the values in the certs - // against those passed in via the params. - // params should be a map. Only the items specified in the map will be - // checked, but they must match exactly, even if they're maps or arrays. - bool found = false; - iterator cert = begin(); - while ( !found && cert != end() ) - { - found = true; - LLSD cert_info; - (*cert)->getLLSD(cert_info); - for (LLSD::map_const_iterator param = params.beginMap(); - found && param != params.endMap(); - param++) - { - if ( !cert_info.has((std::string)param->first) - || !valueCompareLLSD(cert_info[(std::string)param->first], param->second)) - { - found = false; - } - } - if (!found) - { - cert++; - } - } - return cert; -} - -// Insert a certificate into the store. If the certificate already -// exists in the store, nothing is done. -void LLBasicCertificateVector::insert(iterator _iter, - LLPointer cert) -{ - LLSD cert_info; - cert->getLLSD(cert_info); - if (cert_info.isMap() && cert_info.has(CERT_SUBJECT_KEY_IDENTFIER)) - { - LLSD existing_cert_info = LLSD::emptyMap(); - existing_cert_info[CERT_SUBJECT_KEY_IDENTFIER] = cert_info[CERT_SUBJECT_KEY_IDENTFIER]; - if(find(existing_cert_info) == end()) - { - BasicIteratorImpl *basic_iter = dynamic_cast(_iter.mImpl.get()); - if (basic_iter) - { - mCerts.insert(basic_iter->mIter, cert); - } - else - { - LL_WARNS("SECAPI") << "Invalid certificate postion vector" - << LL_ENDL; - } - } - else - { - LL_DEBUGS("SECAPI") << "Certificate already in vector: " - << "'" << cert_info << "'" - << LL_ENDL; - } - - } - else - { - LL_WARNS("SECAPI") << "Certificate does not have Subject Key Identifier; not inserted: " - << "'" << cert_info << "'" - << LL_ENDL; - } -} - -// remove a certificate from the store -LLPointer LLBasicCertificateVector::erase(iterator _iter) -{ - - if (_iter != end()) - { - BasicIteratorImpl *basic_iter = dynamic_cast(_iter.mImpl.get()); - LLPointer result = (*_iter); - mCerts.erase(basic_iter->mIter); - return result; - } - return NULL; -} - - -// -// LLBasicCertificateStore -// This class represents a store of CA certificates. The basic implementation -// uses a crt file such as the ca-bundle.crt in the existing SL implementation. -LLBasicCertificateStore::LLBasicCertificateStore(const std::string& filename) -{ - mFilename = filename; - load_from_file(filename); -} - -void LLBasicCertificateStore::load_from_file(const std::string& filename) -{ - int loaded = 0; - int rejected = 0; - - // scan the PEM file extracting each certificate - if (LLFile::isfile(filename)) - { - BIO* file_bio = BIO_new(BIO_s_file()); - if(file_bio) - { - if (BIO_read_filename(file_bio, filename.c_str()) > 0) - { - X509 *cert_x509 = NULL; - while((PEM_read_bio_X509(file_bio, &cert_x509, 0, NULL)) && - (cert_x509 != NULL)) - { - try - { - LLPointer new_cert(new LLBasicCertificate(cert_x509)); - LLSD validation_params; - _validateCert(VALIDATION_POLICY_TIME, - new_cert, - validation_params, - 0); - add(new_cert); - LL_DEBUGS("SECAPI") << "Loaded valid cert for " - << "Name '" << cert_string_name_from_X509_NAME(X509_get_subject_name(cert_x509)) << "'"; - std::string skeyid(_subject_key_identifier(cert_x509)); - LL_CONT << " Id '" << skeyid << "'" - << LL_ENDL; - loaded++; - } - catch (LLCertException& cert_exception) - { - LLSD cert_info(cert_exception.getCertData()); - LL_DEBUGS("SECAPI_BADCERT","SECAPI") << "invalid certificate (" << cert_exception.what() << "): " << cert_info << LL_ENDL; - rejected++; - } - catch (...) - { - LOG_UNHANDLED_EXCEPTION("creating certificate from the certificate store file"); - rejected++; - } - X509_free(cert_x509); - cert_x509 = NULL; - } - BIO_free(file_bio); - } - else - { - LL_WARNS("SECAPI") << "BIO read failed for " << filename << LL_ENDL; - } - - LL_INFOS("SECAPI") << "loaded " << loaded << " good certificates (rejected " << rejected << ") from " << filename << LL_ENDL; - } - else - { - LL_WARNS("SECAPI") << "Could not allocate a file BIO" << LL_ENDL; - } - } - else - { - // since the user certificate store may not be there, this is not a warning - LL_INFOS("SECAPI") << "Certificate store not found at " << filename << LL_ENDL; - } -} - - -LLBasicCertificateStore::~LLBasicCertificateStore() -{ -} - - -// persist the store -void LLBasicCertificateStore::save() -{ - llofstream file_store(mFilename.c_str(), std::ios_base::binary); - if(!file_store.fail()) - { - for(iterator cert = begin(); - cert != end(); - cert++) - { - std::string pem = (*cert)->getPem(); - if(!pem.empty()) - { - file_store << (*cert)->getPem() << std::endl; - } - } - file_store.close(); - } - else - { - LL_WARNS("SECAPI") << "Could not open certificate store " << mFilename << "for save" << LL_ENDL; - } -} - -// return the store id -std::string LLBasicCertificateStore::storeId() const -{ - // this is the basic handler which uses the ca-bundle.crt store, - // so we ignore this. - return std::string(""); -} - - -// -// LLBasicCertificateChain -// This class represents a chain of certs, each cert being signed by the next cert -// in the chain. Certs must be properly signed by the parent -LLBasicCertificateChain::LLBasicCertificateChain(X509_STORE_CTX* store) -{ - - // we're passed in a context, which contains a cert, and a blob of untrusted - // certificates which compose the chain. - if((store == NULL) || X509_STORE_CTX_get0_cert(store) == NULL) - { - LL_WARNS("SECAPI") << "An invalid store context was passed in when trying to create a certificate chain" << LL_ENDL; - return; - } - // grab the child cert - LLPointer current = new LLBasicCertificate(X509_STORE_CTX_get0_cert(store)); - - add(current); - if(X509_STORE_CTX_get0_untrusted(store) != NULL) - { - // if there are other certs in the chain, we build up a vector - // of untrusted certs so we can search for the parents of each - // consecutive cert. - LLBasicCertificateVector untrusted_certs; - for(int i = 0; i < sk_X509_num(X509_STORE_CTX_get0_untrusted(store)); i++) - { - LLPointer cert = new LLBasicCertificate(sk_X509_value(X509_STORE_CTX_get0_untrusted(store), i)); - untrusted_certs.add(cert); - - } - while(untrusted_certs.size() > 0) - { - LLSD find_data = LLSD::emptyMap(); - LLSD cert_data; - current->getLLSD(cert_data); - // we simply build the chain via subject/issuer name as the - // client should not have passed in multiple CA's with the same - // subject name. If they did, it'll come out in the wash during - // validation. - find_data[CERT_SUBJECT_NAME_STRING] = cert_data[CERT_ISSUER_NAME_STRING]; - LLBasicCertificateVector::iterator issuer = untrusted_certs.find(find_data); - if (issuer != untrusted_certs.end()) - { - current = untrusted_certs.erase(issuer); - add(current); - } - else - { - break; - } - } - } -} - - -// subdomain wildcard specifiers can be divided into 3 parts -// the part before the first *, the part after the first * but before -// the second *, and the part after the second *. -// It then iterates over the second for each place in the string -// that it matches. ie if the subdomain was testfoofoobar, and -// the wildcard was test*foo*bar, it would match test, then -// recursively match foofoobar and foobar - -bool _cert_subdomain_wildcard_match(const std::string& subdomain, - const std::string& wildcard) -{ - // split wildcard into the portion before the *, and the portion after - - int wildcard_pos = wildcard.find_first_of('*'); - // check the case where there is no wildcard. - if(wildcard_pos == wildcard.npos) - { - return (subdomain == wildcard); - } - - // we need to match the first part of the subdomain string up to the wildcard - // position - if(subdomain.substr(0, wildcard_pos) != wildcard.substr(0, wildcard_pos)) - { - // the first portions of the strings didn't match - return false; - } - - // as the portion of the wildcard string before the * matched, we need to check the - // portion afterwards. Grab that portion. - std::string new_wildcard_string = wildcard.substr( wildcard_pos+1, wildcard.npos); - if(new_wildcard_string.empty()) - { - // we had nothing after the *, so it's an automatic match - return true; - } - - // grab the portion of the remaining wildcard string before the next '*'. We need to find this - // within the remaining subdomain string. and then recursively check. - std::string new_wildcard_match_string = new_wildcard_string.substr(0, new_wildcard_string.find_first_of('*')); - - // grab the portion of the subdomain after the part that matched the initial wildcard portion - std::string new_subdomain = subdomain.substr(wildcard_pos, subdomain.npos); - - // iterate through the current subdomain, finding instances of the match string. - int sub_pos = new_subdomain.find_first_of(new_wildcard_match_string); - while(sub_pos != std::string::npos) - { - new_subdomain = new_subdomain.substr(sub_pos, std::string::npos); - if(_cert_subdomain_wildcard_match(new_subdomain, new_wildcard_string)) - { - return true; - } - sub_pos = new_subdomain.find_first_of(new_wildcard_match_string, 1); - - - } - // didn't find any instances of the match string that worked in the subdomain, so fail. - return false; -} - - -// RFC2459 does not address wildcards as part of it's name matching -// specification, and there is no RFC specifying wildcard matching, -// RFC2818 does a few statements about wildcard matching, but is very -// general. Generally, wildcard matching is per implementation, although -// it's pretty similar. -// in our case, we use the '*' wildcard character only, within each -// subdomain. The hostname and the CN specification should have the -// same number of subdomains. -// We then iterate that algorithm over each subdomain. -bool _cert_hostname_wildcard_match(const std::string& hostname, const std::string& common_name) -{ - std::string new_hostname = hostname; - std::string new_cn = common_name; - - // find the last '.' in the hostname and the match name. - int subdomain_pos = new_hostname.find_last_of('.'); - int subcn_pos = new_cn.find_last_of('.'); - - // if the last char is a '.', strip it - if(subdomain_pos == (new_hostname.length()-1)) - { - new_hostname = new_hostname.substr(0, subdomain_pos); - subdomain_pos = new_hostname.find_last_of('.'); - } - if(subcn_pos == (new_cn.length()-1)) - { - new_cn = new_cn.substr(0, subcn_pos); - subcn_pos = new_cn.find_last_of('.'); - } - - // Check to see if there are any further '.' in the string. - while((subcn_pos != std::string::npos) && (subdomain_pos != std::string::npos)) - { - // snip out last subdomain in both the match string and the hostname - // The last bit for 'my.current.host.com' would be 'com' - std::string cn_part = new_cn.substr(subcn_pos+1, std::string::npos); - std::string hostname_part = new_hostname.substr(subdomain_pos+1, std::string::npos); - - if(!_cert_subdomain_wildcard_match(new_hostname.substr(subdomain_pos+1, std::string::npos), - cn_part)) - { - return false; - } - new_hostname = new_hostname.substr(0, subdomain_pos); - new_cn = new_cn.substr(0, subcn_pos); - subdomain_pos = new_hostname.find_last_of('.'); - subcn_pos = new_cn.find_last_of('.'); - } - // check to see if the most significant portion of the common name is '*'. If so, we can - // simply return success as child domains are also matched. - if(new_cn == "*") - { - // if it's just a '*' we support all child domains as well, so '*. - return true; - } - - return _cert_subdomain_wildcard_match(new_hostname, new_cn); - -} - -// validate that the LLSD array in llsd_set contains the llsd_value -bool _LLSDArrayIncludesValue(const LLSD& llsd_set, LLSD llsd_value) -{ - for(LLSD::array_const_iterator set_value = llsd_set.beginArray(); - set_value != llsd_set.endArray(); - set_value++) - { - if(valueCompareLLSD((*set_value), llsd_value)) - { - return true; - } - } - return false; -} - -void _validateCert(int validation_policy, - LLPointer cert, - const LLSD& validation_params, - int depth) -{ - LLSD current_cert_info; - cert->getLLSD(current_cert_info); - // check basic properties exist in the cert - if(!current_cert_info.has(CERT_SUBJECT_NAME) || !current_cert_info.has(CERT_SUBJECT_NAME_STRING)) - { - LLTHROW(LLCertException(current_cert_info, "Cert doesn't have a Subject Name")); - } - - if(!current_cert_info.has(CERT_ISSUER_NAME_STRING)) - { - LLTHROW(LLCertException(current_cert_info, "Cert doesn't have an Issuer Name")); - } - - // check basic properties exist in the cert - if(!current_cert_info.has(CERT_VALID_FROM) || !current_cert_info.has(CERT_VALID_TO)) - { - LLTHROW(LLCertException(current_cert_info, "Cert doesn't have an expiration period")); - } - if (!current_cert_info.has(CERT_SUBJECT_KEY_IDENTFIER)) - { - LLTHROW(LLCertException(current_cert_info, "Cert doesn't have a Subject Key Id")); - } - - if (validation_policy & VALIDATION_POLICY_TIME) - { - LLDate validation_date(time(NULL)); - if(validation_params.has(CERT_VALIDATION_DATE)) - { - validation_date = validation_params[CERT_VALIDATION_DATE]; - } - - if((validation_date < current_cert_info[CERT_VALID_FROM].asDate()) || - (validation_date > current_cert_info[CERT_VALID_TO].asDate())) - { - LLTHROW(LLCertValidationExpirationException(current_cert_info, validation_date)); - } - } - if (validation_policy & VALIDATION_POLICY_SSL_KU) - { - // This stanza of code was changed 2021-06-09 as per details in SL-15370. - // Brief summary: a renewed certificate from Akamai only contains the - // 'Digital Signature' field and not the 'Key Encipherment' one. This code - // used to look for both and throw an exception at startup (ignored) and - // (for example) when buying L$ in the Viewer (fails with a UI message - // and an entry in the Viewer log). This modified code removes the second - // check for the 'Key Encipherment' field. If Akamai can provide a - // replacement certificate that has both fields, then this modified code - // will not be required. - if (current_cert_info.has(CERT_KEY_USAGE) && current_cert_info[CERT_KEY_USAGE].isArray() && - !(_LLSDArrayIncludesValue(current_cert_info[CERT_KEY_USAGE], - LLSD((std::string)CERT_KU_DIGITAL_SIGNATURE))) - ) - { - LLTHROW(LLCertKeyUsageValidationException(current_cert_info)); - } - // only validate EKU if the cert has it - if(current_cert_info.has(CERT_EXTENDED_KEY_USAGE) - && current_cert_info[CERT_EXTENDED_KEY_USAGE].isArray() - && (!_LLSDArrayIncludesValue(current_cert_info[CERT_EXTENDED_KEY_USAGE], - LLSD((std::string)CERT_EKU_TLS_SERVER_AUTH))) - && (!_LLSDArrayIncludesValue(current_cert_info[CERT_EXTENDED_KEY_USAGE], - LLSD((std::string)CERT_EKU_SERVER_AUTH))) - ) - { - LLTHROW(LLCertKeyUsageValidationException(current_cert_info)); - } - } - if (validation_policy & VALIDATION_POLICY_CA_KU) - { - if (current_cert_info.has(CERT_KEY_USAGE) && current_cert_info[CERT_KEY_USAGE].isArray() && - (!_LLSDArrayIncludesValue(current_cert_info[CERT_KEY_USAGE], - (std::string)CERT_KU_CERT_SIGN))) - { - LLTHROW(LLCertKeyUsageValidationException(current_cert_info)); - } - } - - // validate basic constraints - if ((validation_policy & VALIDATION_POLICY_CA_BASIC_CONSTRAINTS) && - current_cert_info.has(CERT_BASIC_CONSTRAINTS) && - current_cert_info[CERT_BASIC_CONSTRAINTS].isMap()) - { - if(!current_cert_info[CERT_BASIC_CONSTRAINTS].has(CERT_BASIC_CONSTRAINTS_CA) || - !current_cert_info[CERT_BASIC_CONSTRAINTS][CERT_BASIC_CONSTRAINTS_CA]) - { - LLTHROW(LLCertBasicConstraintsValidationException(current_cert_info)); - } - if (current_cert_info[CERT_BASIC_CONSTRAINTS].has(CERT_BASIC_CONSTRAINTS_PATHLEN) && - ((current_cert_info[CERT_BASIC_CONSTRAINTS][CERT_BASIC_CONSTRAINTS_PATHLEN].asInteger() != 0) && - (depth > current_cert_info[CERT_BASIC_CONSTRAINTS][CERT_BASIC_CONSTRAINTS_PATHLEN].asInteger()))) - { - LLTHROW(LLCertBasicConstraintsValidationException(current_cert_info)); - } - } -} - -bool _verify_signature(LLPointer parent, - LLPointer child) -{ - bool verify_result = false; - LLSD cert1, cert2; - parent->getLLSD(cert1); - child->getLLSD(cert2); - X509 *signing_cert = parent->getOpenSSLX509(); - X509 *child_cert = child->getOpenSSLX509(); - if((signing_cert != NULL) && (child_cert != NULL)) - { - EVP_PKEY *pkey = X509_get_pubkey(signing_cert); - - - if(pkey) - { - int verify_code = X509_verify(child_cert, pkey); - verify_result = ( verify_code > 0); - EVP_PKEY_free(pkey); - } - else - { - LL_WARNS("SECAPI") << "Could not validate the cert chain signature, as the public key of the signing cert could not be retrieved" << LL_ENDL; - } - - } - else - { - LL_WARNS("SECAPI") << "Signature verification failed as there are no certs in the chain" << LL_ENDL; - } - if(child_cert) - { - X509_free(child_cert); - } - if(signing_cert) - { - X509_free(signing_cert); - } - return verify_result; -} - - -// validate the certificate chain against a store. -// There are many aspects of cert validatioin policy involved in -// trust validation. The policies in this validation algorithm include -// * Hostname matching for SSL certs -// * Expiration time matching -// * Signature validation -// * Chain trust (is the cert chain trusted against the store) -// * Basic constraints -// * key usage and extended key usage -// TODO: We should add 'authority key identifier' for chaining. -// This algorithm doesn't simply validate the chain by itself -// and verify the last cert is in the certificate store, or points -// to a cert in the store. It validates whether any cert in the chain -// is trusted in the store, even if it's not the last one. -void LLBasicCertificateStore::validate(int validation_policy, - LLPointer cert_chain, - const LLSD& validation_params) -{ - // If --no-verify-ssl-cert was passed on the command line, stop right now. - if (gSavedSettings.getBOOL("NoVerifySSLCert")) - { - LL_WARNS_ONCE("SECAPI") << "All Certificate validation disabled; viewer operation is insecure" << LL_ENDL; - return; - } - - if(cert_chain->size() < 1) - { - LLTHROW(LLCertException(LLSD::emptyMap(), "No certs in chain")); - } - iterator current_cert = cert_chain->begin(); - LLSD validation_date; - if (validation_params.has(CERT_VALIDATION_DATE)) - { - validation_date = validation_params[CERT_VALIDATION_DATE]; - } - - // get LLSD info from the cert to throw in any exception - LLSD current_cert_info; - (*current_cert)->getLLSD(current_cert_info); - - if (validation_policy & VALIDATION_POLICY_HOSTNAME) - { - if(!validation_params.has(CERT_HOSTNAME)) - { - LLTHROW(LLCertException(current_cert_info, "No hostname passed in for validation")); - } - if(!current_cert_info.has(CERT_SUBJECT_NAME) || !current_cert_info[CERT_SUBJECT_NAME].has(CERT_NAME_CN)) - { - LLTHROW(LLInvalidCertificate(current_cert_info)); - } - - LL_DEBUGS("SECAPI") << "Validating the hostname " << validation_params[CERT_HOSTNAME].asString() << - "against the cert CN " << current_cert_info[CERT_SUBJECT_NAME][CERT_NAME_CN].asString() << LL_ENDL; - if(!_cert_hostname_wildcard_match(validation_params[CERT_HOSTNAME].asString(), - current_cert_info[CERT_SUBJECT_NAME][CERT_NAME_CN].asString())) - { - throw LLCertValidationHostnameException(validation_params[CERT_HOSTNAME].asString(), - current_cert_info); - } - } - - // check the cache of already validated certs - X509* cert_x509 = (*current_cert)->getOpenSSLX509(); - if(!cert_x509) - { - LLTHROW(LLInvalidCertificate(current_cert_info)); - } - - std::string subject_name(cert_string_name_from_X509_NAME(X509_get_subject_name(cert_x509))); - std::string skeyid(_subject_key_identifier(cert_x509)); - - LL_DEBUGS("SECAPI") << "attempting to validate cert " - << " for '" << (validation_params.has(CERT_HOSTNAME) ? validation_params[CERT_HOSTNAME].asString() : "(unknown hostname)") << "'" - << " as subject name '" << subject_name << "'" - << " subject key id '" << skeyid << "'" - << LL_ENDL; - - X509_free( cert_x509 ); - cert_x509 = NULL; - if (skeyid.empty()) - { - LLTHROW(LLCertException(current_cert_info, "No Subject Key Id")); - } - - t_cert_cache::iterator cache_entry = mTrustedCertCache.find(skeyid); - if(cache_entry != mTrustedCertCache.end()) - { - // this cert is in the cache, so validate the time. - if (validation_policy & VALIDATION_POLICY_TIME) - { - LLDate validation_date; - if(validation_params.has(CERT_VALIDATION_DATE)) - { - validation_date = validation_params[CERT_VALIDATION_DATE]; - } - else - { - validation_date = LLDate(time(NULL)); // current time - } - - if((validation_date < cache_entry->second.first) || - (validation_date > cache_entry->second.second)) - { - LLTHROW(LLCertValidationExpirationException(current_cert_info, validation_date)); - } - } - // successfully found in cache - LL_DEBUGS("SECAPI") << "Valid cert for '" << validation_params[CERT_HOSTNAME].asString() << "'" - << " skeyid '" << skeyid << "'" - << " found in cache" - << LL_ENDL; - return; - } - if(current_cert_info.isUndefined()) - { - (*current_cert)->getLLSD(current_cert_info); - } - LLDate from_time = current_cert_info[CERT_VALID_FROM].asDate(); - LLDate to_time = current_cert_info[CERT_VALID_TO].asDate(); - int depth = 0; - LLPointer previous_cert; - // loop through the cert chain, validating the current cert against the next one. - while(current_cert != cert_chain->end()) - { - int local_validation_policy = validation_policy; - if(current_cert == cert_chain->begin()) - { - // for the child cert, we don't validate CA stuff - local_validation_policy &= ~(VALIDATION_POLICY_CA_KU | - VALIDATION_POLICY_CA_BASIC_CONSTRAINTS); - } - else - { - // for non-child certs, we don't validate SSL Key usage - local_validation_policy &= ~VALIDATION_POLICY_SSL_KU; - if(!_verify_signature((*current_cert), - previous_cert)) - { - LLSD previous_cert_info; - previous_cert->getLLSD(previous_cert_info); - LLTHROW(LLCertValidationInvalidSignatureException(previous_cert_info)); - } - } - _validateCert(local_validation_policy, - (*current_cert), - validation_params, - depth); - - // look for a CA in the CA store that may belong to this chain. - LLSD cert_search_params = LLSD::emptyMap(); - // is the cert itself in the store? - cert_search_params[CERT_SUBJECT_KEY_IDENTFIER] = current_cert_info[CERT_SUBJECT_KEY_IDENTFIER]; - LLCertificateStore::iterator found_store_cert = find(cert_search_params); - if(found_store_cert != end()) - { - mTrustedCertCache[skeyid] = std::pair(from_time, to_time); - LL_DEBUGS("SECAPI") << "Valid cert " - << " for '" << (validation_params.has(CERT_HOSTNAME) ? validation_params[CERT_HOSTNAME].asString() : "(unknown hostname)") << "'"; - X509* cert_x509 = (*found_store_cert)->getOpenSSLX509(); - std::string found_cert_subject_name(cert_string_name_from_X509_NAME(X509_get_subject_name(cert_x509))); - X509_free(cert_x509); - LL_CONT << " as '" << found_cert_subject_name << "'" - << " skeyid '" << current_cert_info[CERT_SUBJECT_KEY_IDENTFIER].asString() << "'" - << " found in cert store" - << LL_ENDL; - return; - } - - // is the parent in the cert store? - - cert_search_params = LLSD::emptyMap(); - cert_search_params[CERT_SUBJECT_NAME_STRING] = current_cert_info[CERT_ISSUER_NAME_STRING]; - if (current_cert_info.has(CERT_AUTHORITY_KEY_IDENTIFIER)) - { - LLSD cert_aki = current_cert_info[CERT_AUTHORITY_KEY_IDENTIFIER]; - if(cert_aki.has(CERT_AUTHORITY_KEY_IDENTIFIER_ID)) - { - cert_search_params[CERT_SUBJECT_KEY_IDENTFIER] = cert_aki[CERT_AUTHORITY_KEY_IDENTIFIER_ID]; - } - if(cert_aki.has(CERT_AUTHORITY_KEY_IDENTIFIER_SERIAL)) - { - cert_search_params[CERT_SERIAL_NUMBER] = cert_aki[CERT_AUTHORITY_KEY_IDENTIFIER_SERIAL]; - } - } - found_store_cert = find(cert_search_params); - - if(found_store_cert != end()) - { - // validate the store cert against the depth - _validateCert(validation_policy & VALIDATION_POLICY_CA_BASIC_CONSTRAINTS, - (*found_store_cert), - LLSD(), - depth); - - // verify the signature of the CA - if(!_verify_signature((*found_store_cert), - (*current_cert))) - { - LLTHROW(LLCertValidationInvalidSignatureException(current_cert_info)); - } - // successfully validated. - mTrustedCertCache[skeyid] = std::pair(from_time, to_time); - LL_DEBUGS("SECAPI") << "Verified and cached cert for '" << validation_params[CERT_HOSTNAME].asString() << "'" - << " as '" << subject_name << "'" - << " id '" << skeyid << "'" - << " using CA '" << cert_search_params[CERT_SUBJECT_NAME_STRING] << "'" - << " with id '" << cert_search_params[CERT_SUBJECT_KEY_IDENTFIER].asString() << "' found in cert store" - << LL_ENDL; - return; - } - previous_cert = (*current_cert); - current_cert++; - depth++; - if(current_cert != cert_chain->end()) - { - (*current_cert)->getLLSD(current_cert_info); - } - } - if (validation_policy & VALIDATION_POLICY_TRUSTED) - { - // we reached the end without finding a trusted cert. - LLSD last_cert_info; - ((*cert_chain)[cert_chain->size()-1])->getLLSD(last_cert_info); - LLTHROW(LLCertValidationTrustException(last_cert_info)); - } - else - { - LL_DEBUGS("SECAPI") << "! Caching untrusted cert for '" << subject_name << "'" - << " skeyid '" << skeyid << "' in cert store because ! VALIDATION_POLICY_TRUSTED" - << LL_ENDL; - mTrustedCertCache[skeyid] = std::pair(from_time, to_time); - } -} - - -// LLSecAPIBasicHandler Class -// Interface handler class for the various security storage handlers. - -// We read the file on construction, and write it on destruction. This -// means multiple processes cannot modify the datastore. -LLSecAPIBasicHandler::LLSecAPIBasicHandler(const std::string& protected_data_file, - const std::string& legacy_password_path) -{ - mProtectedDataFilename = protected_data_file; - mProtectedDataMap = LLSD::emptyMap(); - mLegacyPasswordPath = legacy_password_path; - -} - -LLSecAPIBasicHandler::LLSecAPIBasicHandler() -{ -} - - -void LLSecAPIBasicHandler::init() -{ - mProtectedDataMap = LLSD::emptyMap(); - if (mProtectedDataFilename.length() == 0) - { - mProtectedDataFilename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, - "bin_conf.dat"); - mLegacyPasswordPath = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "password.dat"); - - mProtectedDataFilename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, - "bin_conf.dat"); - std::string store_file = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, - "CA.pem"); - - - LL_INFOS("SECAPI") << "Loading user certificate store from " << store_file << LL_ENDL; - mStore = new LLBasicCertificateStore(store_file); - - // grab the application ca-bundle.crt file that contains the well-known certs shipped - // with the product - std::string ca_file_path = gDirUtilp->getCAFile(); - LL_INFOS("SECAPI") << "Loading application certificate store from " << ca_file_path << LL_ENDL; - LLPointer app_ca_store = new LLBasicCertificateStore(ca_file_path); - - // push the applicate CA files into the store, therefore adding any new CA certs that - // updated - for(LLCertificateVector::iterator i = app_ca_store->begin(); - i != app_ca_store->end(); - i++) - { - mStore->add(*i); - } - - } - _readProtectedData(); // initialize mProtectedDataMap - // may throw LLProtectedDataException if saved datamap is not decryptable -} -LLSecAPIBasicHandler::~LLSecAPIBasicHandler() -{ - _writeProtectedData(); -} - -void LLSecAPIBasicHandler::_readProtectedData(unsigned char *unique_id, U32 id_len) -{ - // attempt to load the file into our map - LLPointer parser = new LLSDXMLParser(); - llifstream protected_data_stream(mProtectedDataFilename.c_str(), - llifstream::binary); - - if (!protected_data_stream.fail()) { - U8 salt[STORE_SALT_SIZE]; - U8 buffer[BUFFER_READ_SIZE]; - U8 decrypted_buffer[BUFFER_READ_SIZE]; - int decrypted_length; - LLXORCipher cipher(unique_id, id_len); - - // read in the salt and key - protected_data_stream.read((char *)salt, STORE_SALT_SIZE); - if (protected_data_stream.gcount() < STORE_SALT_SIZE) - { - LLTHROW(LLProtectedDataException("Config file too short.")); - } - - cipher.decrypt(salt, STORE_SALT_SIZE); - - // totally lame. As we're not using the OS level protected data, we need to - // at least obfuscate the data. We do this by using a salt stored at the head of the file - // to encrypt the data, therefore obfuscating it from someone using simple existing tools. - // We do include the MAC address as part of the obfuscation, which would require an - // attacker to get the MAC address as well as the protected store, which improves things - // somewhat. It would be better to use the password, but as this store - // will be used to store the SL password when the user decides to have SL remember it, - // so we can't use that. OS-dependent store implementations will use the OS password/storage - // mechanisms and are considered to be more secure. - // We've a strong intent to move to OS dependent protected data stores. - - - // read in the rest of the file. - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); - // todo: ctx error handling - - EVP_DecryptInit(ctx, EVP_rc4(), salt, NULL); - // allocate memory: - std::string decrypted_data; - - while(protected_data_stream.good()) { - // read data as a block: - protected_data_stream.read((char *)buffer, BUFFER_READ_SIZE); - - EVP_DecryptUpdate(ctx, decrypted_buffer, &decrypted_length, - buffer, protected_data_stream.gcount()); - decrypted_data.append((const char *)decrypted_buffer, protected_data_stream.gcount()); - } - - // RC4 is a stream cipher, so we don't bother to EVP_DecryptFinal, as there is - // no block padding. - EVP_CIPHER_CTX_free(ctx); - std::istringstream parse_stream(decrypted_data); - if (parser->parse(parse_stream, mProtectedDataMap, - LLSDSerialize::SIZE_UNLIMITED) == LLSDParser::PARSE_FAILURE) - { - LLTHROW(LLProtectedDataException("Config file cannot be decrypted.")); - } - } -} - -void LLSecAPIBasicHandler::_readProtectedData() -{ - unsigned char unique_id[MAC_ADDRESS_BYTES]; - try - { - // try default id - LLMachineID::getUniqueID(unique_id, sizeof(unique_id)); - _readProtectedData(unique_id, sizeof(unique_id)); - } - catch(LLProtectedDataException&) - { - // try with legacy id, it will return false if it is identical to getUniqueID - // or if it is not assigned/not in use - if (LLMachineID::getLegacyID(unique_id, sizeof(unique_id))) - { - _readProtectedData(unique_id, sizeof(unique_id)); - } - else - { - throw; - } - } -} - -void LLSecAPIBasicHandler::_writeProtectedData() -{ - std::ostringstream formatted_data_ostream; - U8 salt[STORE_SALT_SIZE]; - U8 buffer[BUFFER_READ_SIZE]; - U8 encrypted_buffer[BUFFER_READ_SIZE]; - - - if(mProtectedDataMap.isUndefined()) - { - LLFile::remove(mProtectedDataFilename); - return; - } - // create a string with the formatted data. - LLSDSerialize::toXML(mProtectedDataMap, formatted_data_ostream); - std::istringstream formatted_data_istream(formatted_data_ostream.str()); - // generate the seed - RAND_bytes(salt, STORE_SALT_SIZE); - - - // write to a temp file so we don't clobber the initial file if there is - // an error. - std::string tmp_filename = mProtectedDataFilename + ".tmp"; - - llofstream protected_data_stream(tmp_filename.c_str(), - std::ios_base::binary); - EVP_CIPHER_CTX *ctx = NULL; - try - { - - ctx = EVP_CIPHER_CTX_new(); - // todo: ctx error handling - - EVP_EncryptInit(ctx, EVP_rc4(), salt, NULL); - unsigned char unique_id[MAC_ADDRESS_BYTES]; - LLMachineID::getUniqueID(unique_id, sizeof(unique_id)); - LLXORCipher cipher(unique_id, sizeof(unique_id)); - cipher.encrypt(salt, STORE_SALT_SIZE); - protected_data_stream.write((const char *)salt, STORE_SALT_SIZE); - - while (formatted_data_istream.good()) - { - formatted_data_istream.read((char *)buffer, BUFFER_READ_SIZE); - if(formatted_data_istream.gcount() == 0) - { - break; - } - int encrypted_length; - EVP_EncryptUpdate(ctx, encrypted_buffer, &encrypted_length, - buffer, formatted_data_istream.gcount()); - protected_data_stream.write((const char *)encrypted_buffer, encrypted_length); - } - - // no EVP_EncrypteFinal, as this is a stream cipher - EVP_CIPHER_CTX_free(ctx); - - protected_data_stream.close(); - } - catch (...) - { - LOG_UNHANDLED_EXCEPTION("LLProtectedDataException(Error writing Protected Data Store)"); - // it's good practice to clean up any secure information on error - // (even though this file isn't really secure. Perhaps in the future - // it may be, however. - LLFile::remove(tmp_filename); - - if (ctx) - { - EVP_CIPHER_CTX_free(ctx); - } - - // EXP-1825 crash in LLSecAPIBasicHandler::_writeProtectedData() - // Decided throwing an exception here was overkill until we figure out why this happens - //LLTHROW(LLProtectedDataException("Error writing Protected Data Store")); - } - - try - { - // move the temporary file to the specified file location. - if((( (LLFile::isfile(mProtectedDataFilename) != 0) - && (LLFile::remove(mProtectedDataFilename) != 0))) - || (LLFile::rename(tmp_filename, mProtectedDataFilename))) - { - LL_WARNS() << "LLProtectedDataException(Could not overwrite protected data store)" << LL_ENDL; - LLFile::remove(tmp_filename); - - // EXP-1825 crash in LLSecAPIBasicHandler::_writeProtectedData() - // Decided throwing an exception here was overkill until we figure out why this happens - //LLTHROW(LLProtectedDataException("Could not overwrite protected data store")); - } - } - catch (...) - { - LOG_UNHANDLED_EXCEPTION(STRINGIZE("renaming '" << tmp_filename << "' to '" - << mProtectedDataFilename << "'")); - // it's good practice to clean up any secure information on error - // (even though this file isn't really secure. Perhaps in the future - // it may be, however). - LLFile::remove(tmp_filename); - - //crash in LLSecAPIBasicHandler::_writeProtectedData() - // Decided throwing an exception here was overkill until we figure out why this happens - //LLTHROW(LLProtectedDataException("Error writing Protected Data Store")); - } -} - -// instantiate a certificate from a pem string -LLPointer LLSecAPIBasicHandler::getCertificate(const std::string& pem_cert) -{ - LLPointer result = new LLBasicCertificate(pem_cert); - return result; -} - - - -// instiate a certificate from an openssl X509 structure -LLPointer LLSecAPIBasicHandler::getCertificate(X509* openssl_cert) -{ - LLPointer result = new LLBasicCertificate(openssl_cert); - return result; -} - -// instantiate a chain from an X509_STORE_CTX -LLPointer LLSecAPIBasicHandler::getCertificateChain(X509_STORE_CTX* chain) -{ - LLPointer result = new LLBasicCertificateChain(chain); - return result; -} - -// instantiate a cert store given it's id. if a persisted version -// exists, it'll be loaded. If not, one will be created (but not -// persisted) -LLPointer LLSecAPIBasicHandler::getCertificateStore(const std::string& store_id) -{ - return mStore; -} - -// retrieve protected data -LLSD LLSecAPIBasicHandler::getProtectedData(const std::string& data_type, - const std::string& data_id) -{ - - if (mProtectedDataMap.has(data_type) && - mProtectedDataMap[data_type].isMap() && - mProtectedDataMap[data_type].has(data_id)) - { - return mProtectedDataMap[data_type][data_id]; - } - - return LLSD(); -} - -void LLSecAPIBasicHandler::deleteProtectedData(const std::string& data_type, - const std::string& data_id) -{ - if (mProtectedDataMap.has(data_type) && - mProtectedDataMap[data_type].isMap() && - mProtectedDataMap[data_type].has(data_id)) - { - mProtectedDataMap[data_type].erase(data_id); - } -} - - -// -// persist data in a protected store -// -void LLSecAPIBasicHandler::setProtectedData(const std::string& data_type, - const std::string& data_id, - const LLSD& data) -{ - if (!mProtectedDataMap.has(data_type) || !mProtectedDataMap[data_type].isMap()) { - mProtectedDataMap[data_type] = LLSD::emptyMap(); - } - - mProtectedDataMap[data_type][data_id] = data; -} - -// persist data in a protected store's map -void LLSecAPIBasicHandler::addToProtectedMap(const std::string& data_type, - const std::string& data_id, - const std::string& map_elem, - const LLSD& data) -{ - if (!mProtectedDataMap.has(data_type) || !mProtectedDataMap[data_type].isMap()) { - mProtectedDataMap[data_type] = LLSD::emptyMap(); - } - - if (!mProtectedDataMap[data_type].has(data_id) || !mProtectedDataMap[data_type][data_id].isMap()) { - mProtectedDataMap[data_type][data_id] = LLSD::emptyMap(); - } - - mProtectedDataMap[data_type][data_id][map_elem] = data; -} - -// remove data from protected store's map -void LLSecAPIBasicHandler::removeFromProtectedMap(const std::string& data_type, - const std::string& data_id, - const std::string& map_elem) -{ - if (mProtectedDataMap.has(data_type) && - mProtectedDataMap[data_type].isMap() && - mProtectedDataMap[data_type].has(data_id) && - mProtectedDataMap[data_type][data_id].isMap() && - mProtectedDataMap[data_type][data_id].has(map_elem)) - { - mProtectedDataMap[data_type][data_id].erase(map_elem); - } -} - -void LLSecAPIBasicHandler::syncProtectedMap() -{ - // TODO - consider unifing these functions - _writeProtectedData(); -} -// -// Create a credential object from an identifier and authenticator. credentials are -// per grid. -LLPointer LLSecAPIBasicHandler::createCredential(const std::string& grid, - const LLSD& identifier, - const LLSD& authenticator) -{ - LLPointer result = new LLSecAPIBasicCredential(grid); - result->setCredentialData(identifier, authenticator); - return result; -} - -// Load a credential from default credential store, given the grid -LLPointer LLSecAPIBasicHandler::loadCredential(const std::string& grid) -{ - LLSD credential = getProtectedData(DEFAULT_CREDENTIAL_STORAGE, grid); - LLPointer result = new LLSecAPIBasicCredential(grid); - if(credential.isMap() && - credential.has("identifier")) - { - - LLSD identifier = credential["identifier"]; - LLSD authenticator; - if (credential.has("authenticator")) - { - authenticator = credential["authenticator"]; - } - result->setCredentialData(identifier, authenticator); - } - else - { - // credential was not in protected storage, so pull the credential - // from the legacy store. - std::string first_name = gSavedSettings.getString("FirstName"); - std::string last_name = gSavedSettings.getString("LastName"); - - if ((first_name != "") && - (last_name != "")) - { - LLSD identifier = LLSD::emptyMap(); - LLSD authenticator; - identifier["type"] = "agent"; - identifier["first_name"] = first_name; - identifier["last_name"] = last_name; - - std::string legacy_password = _legacyLoadPassword(); - if (legacy_password.length() > 0) - { - authenticator = LLSD::emptyMap(); - authenticator["type"] = "hash"; - authenticator["algorithm"] = "md5"; - authenticator["secret"] = legacy_password; - } - result->setCredentialData(identifier, authenticator); - } - } - return result; -} - -// Save the credential to the credential store. Save the authenticator also if requested. -// That feature is used to implement the 'remember password' functionality. -void LLSecAPIBasicHandler::saveCredential(LLPointer cred, bool save_authenticator) -{ - LLSD credential = LLSD::emptyMap(); - credential["identifier"] = cred->getIdentifier(); - if (save_authenticator) - { - credential["authenticator"] = cred->getAuthenticator(); - } - LL_DEBUGS("SECAPI") << "Saving Credential " << cred->getGrid() << ":" << cred->userID() << " " << save_authenticator << LL_ENDL; - setProtectedData(DEFAULT_CREDENTIAL_STORAGE, cred->getGrid(), credential); - //*TODO: If we're saving Agni credentials, should we write the - // credentials to the legacy password.dat/etc? - _writeProtectedData(); -} - -// Remove a credential from the credential store. -void LLSecAPIBasicHandler::deleteCredential(LLPointer cred) -{ - LLSD undefVal; - deleteProtectedData(DEFAULT_CREDENTIAL_STORAGE, cred->getGrid()); - cred->setCredentialData(undefVal, undefVal); - _writeProtectedData(); -} - -// has map of credentials declared as specific storage -bool LLSecAPIBasicHandler::hasCredentialMap(const std::string& storage, const std::string& grid) -{ - if (storage == DEFAULT_CREDENTIAL_STORAGE) - { - LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL; - } - - LLSD credential = getProtectedData(storage, grid); - - return credential.isMap(); -} - -// returns true if map is empty or does not exist -bool LLSecAPIBasicHandler::emptyCredentialMap(const std::string& storage, const std::string& grid) -{ - if (storage == DEFAULT_CREDENTIAL_STORAGE) - { - LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL; - } - - LLSD credential = getProtectedData(storage, grid); - - return !credential.isMap() || credential.size() == 0; -} - -// Load map of credentials from specified credential store, given the grid -void LLSecAPIBasicHandler::loadCredentialMap(const std::string& storage, const std::string& grid, credential_map_t& credential_map) -{ - if (storage == DEFAULT_CREDENTIAL_STORAGE) - { - LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL; - } - - LLSD credential = getProtectedData(storage, grid); - if (credential.isMap()) - { - LLSD::map_const_iterator crd_it = credential.beginMap(); - for (; crd_it != credential.endMap(); crd_it++) - { - LLSD::String name = crd_it->first; - const LLSD &link_map = crd_it->second; - LLPointer result = new LLSecAPIBasicCredential(grid); - if (link_map.has("identifier")) - { - LLSD identifier = link_map["identifier"]; - LLSD authenticator; - if (link_map.has("authenticator")) - { - authenticator = link_map["authenticator"]; - } - result->setCredentialData(identifier, authenticator); - } - credential_map[name] = result; - } - } -} - -LLPointer LLSecAPIBasicHandler::loadFromCredentialMap(const std::string& storage, const std::string& grid, const std::string& userkey) -{ - if (storage == DEFAULT_CREDENTIAL_STORAGE) - { - LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL; - } - - LLPointer result = new LLSecAPIBasicCredential(grid); - - LLSD credential = getProtectedData(storage, grid); - if (credential.isMap() && credential.has(userkey) && credential[userkey].has("identifier")) - { - LLSD identifier = credential[userkey]["identifier"]; - LLSD authenticator; - if (credential[userkey].has("authenticator")) - { - authenticator = credential[userkey]["authenticator"]; - } - result->setCredentialData(identifier, authenticator); - } - - return result; -} - -// add item to map of credentials from specific storage -void LLSecAPIBasicHandler::addToCredentialMap(const std::string& storage, LLPointer cred, bool save_authenticator) -{ - if (storage == DEFAULT_CREDENTIAL_STORAGE) - { - LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL; - } - - std::string user_id = cred->userID(); - LLSD credential = LLSD::emptyMap(); - credential["identifier"] = cred->getIdentifier(); - if (save_authenticator) - { - credential["authenticator"] = cred->getAuthenticator(); - } - LL_DEBUGS("SECAPI") << "Saving Credential " << cred->getGrid() << ":" << cred->userID() << " " << save_authenticator << LL_ENDL; - addToProtectedMap(storage, cred->getGrid(), user_id, credential); - - _writeProtectedData(); -} - -// remove item from map of credentials from specific storage -void LLSecAPIBasicHandler::removeFromCredentialMap(const std::string& storage, LLPointer cred) -{ - if (storage == DEFAULT_CREDENTIAL_STORAGE) - { - LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL; - } - - LLSD undefVal; - removeFromProtectedMap(storage, cred->getGrid(), cred->userID()); - cred->setCredentialData(undefVal, undefVal); - _writeProtectedData(); -} - -// remove item from map of credentials from specific storage -void LLSecAPIBasicHandler::removeFromCredentialMap(const std::string& storage, const std::string& grid, const std::string& userkey) -{ - if (storage == DEFAULT_CREDENTIAL_STORAGE) - { - LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL; - } - - LLSD undefVal; - LLPointer cred = loadFromCredentialMap(storage, grid, userkey); - removeFromProtectedMap(storage, grid, userkey); - cred->setCredentialData(undefVal, undefVal); - _writeProtectedData(); -} - -// remove item from map of credentials from specific storage -void LLSecAPIBasicHandler::removeCredentialMap(const std::string& storage, const std::string& grid) -{ - deleteProtectedData(storage, grid); - _writeProtectedData(); -} - -// load the legacy hash for agni, and decrypt it given the -// mac address -std::string LLSecAPIBasicHandler::_legacyLoadPassword() -{ - const S32 HASHED_LENGTH = 32; - std::vector buffer(HASHED_LENGTH); - llifstream password_file(mLegacyPasswordPath.c_str(), llifstream::binary); - - if(password_file.fail()) - { - return std::string(""); - } - - password_file.read((char*)&buffer[0], buffer.size()); - if(password_file.gcount() != buffer.size()) - { - return std::string(""); - } - - // Decipher with MAC address - unsigned char unique_id[MAC_ADDRESS_BYTES]; - LLMachineID::getUniqueID(unique_id, sizeof(unique_id)); - LLXORCipher cipher(unique_id, sizeof(unique_id)); - cipher.decrypt(&buffer[0], buffer.size()); - - return std::string((const char*)&buffer[0], buffer.size()); -} - - -// return an identifier for the user -std::string LLSecAPIBasicCredential::userID() const -{ - if (!mIdentifier.isMap()) - { - return mGrid + "(null)"; - } - else if ((std::string)mIdentifier["type"] == "agent") - { - std::string id = (std::string)mIdentifier["first_name"] + "_" + (std::string)mIdentifier["last_name"]; - LLStringUtil::toLower(id); - return id; - } - else if ((std::string)mIdentifier["type"] == "account") - { - std::string id = (std::string)mIdentifier["account_name"]; - LLStringUtil::toLower(id); - return id; - } - - return "unknown"; -} - -// return a printable user identifier -std::string LLSecAPIBasicCredential::asString() const -{ - if (!mIdentifier.isMap()) - { - return mGrid + ":(null)"; - } - else if ((std::string)mIdentifier["type"] == "agent") - { - return mGrid + ":" + (std::string)mIdentifier["first_name"] + " " + (std::string)mIdentifier["last_name"]; - } - else if ((std::string)mIdentifier["type"] == "account") - { - return mGrid + ":" + (std::string)mIdentifier["account_name"]; - } - - return mGrid + ":(unknown type)"; -} - - -bool valueCompareLLSD(const LLSD& lhs, const LLSD& rhs) -{ - if (lhs.type() != rhs.type()) - { - return false; - } - if (lhs.isMap()) - { - // iterate through the map, verifying the right hand side has all of the - // values that the left hand side has. - for (LLSD::map_const_iterator litt = lhs.beginMap(); - litt != lhs.endMap(); - litt++) - { - if (!rhs.has(litt->first)) - { - return false; - } - } - - // Now validate that the left hand side has everything the - // right hand side has, and that the values are equal. - for (LLSD::map_const_iterator ritt = rhs.beginMap(); - ritt != rhs.endMap(); - ritt++) - { - if (!lhs.has(ritt->first)) - { - return false; - } - if (!valueCompareLLSD(lhs[ritt->first], ritt->second)) - { - return false; - } - } - return true; - } - else if (lhs.isArray()) - { - LLSD::array_const_iterator ritt = rhs.beginArray(); - // iterate through the array, comparing - for (LLSD::array_const_iterator litt = lhs.beginArray(); - litt != lhs.endArray(); - litt++) - { - if (!valueCompareLLSD(*ritt, *litt)) - { - return false; - } - ritt++; - } - - return (ritt == rhs.endArray()); - } - else - { - // simple type, compare as string - return (lhs.asString() == rhs.asString()); - } - -} +/** + * @file llsechandler_basic.cpp + * @brief Security API for services such as certificate handling + * secure local storage, etc. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#include "llviewerprecompiledheaders.h" +#include "llsecapi.h" +#include "llsechandler_basic.h" +#include "llsdserialize.h" +#include "llviewernetwork.h" +#include "llxorcipher.h" +#include "llfile.h" +#include "lldir.h" +#include "llviewercontrol.h" +#include "llexception.h" +#include "stringize.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "llmachineid.h" + + +static const std::string DEFAULT_CREDENTIAL_STORAGE = "credential"; + +// 128 bits of salt data... +#define STORE_SALT_SIZE 16 +#define BUFFER_READ_SIZE 256 +std::string cert_string_from_asn1_string(ASN1_STRING* value); +std::string cert_string_from_octet_string(ASN1_OCTET_STRING* value); + +LLSD _basic_constraints_ext(X509* cert); +LLSD _key_usage_ext(X509* cert); +LLSD _ext_key_usage_ext(X509* cert); +std::string _subject_key_identifier(X509 *cert); +LLSD _authority_key_identifier(X509* cert); +void _validateCert(int validation_policy, + LLPointer cert, + const LLSD& validation_params, + int depth); + +LLBasicCertificate::LLBasicCertificate(const std::string& pem_cert, + const LLSD* validation_params) +{ + // BIO_new_mem_buf returns a read only bio, but takes a void* which isn't const + // so we need to cast it. + BIO * pem_bio = BIO_new_mem_buf((void*)pem_cert.c_str(), pem_cert.length()); + if(pem_bio == NULL) + { + LL_WARNS("SECAPI") << "Could not allocate an openssl memory BIO." << LL_ENDL; + LLTHROW(LLAllocationCertException(LLSD::emptyMap())); + } + mCert = NULL; + PEM_read_bio_X509(pem_bio, &mCert, 0, NULL); + BIO_free(pem_bio); + if (!mCert) + { + LL_WARNS("SECAPI") << "Could not decode certificate to x509." << LL_ENDL; + LLTHROW(LLInvalidCertificate(LLSD::emptyMap())); + } +} + + +LLBasicCertificate::LLBasicCertificate(X509* pCert, + const LLSD* validation_params) +{ + if (!pCert) + { + LLTHROW(LLInvalidCertificate(LLSD::emptyMap())); + } + mCert = X509_dup(pCert); + // it is tempting to run _validateCert here, but doing so causes problems + // the trick is figuring out which aspects to validate. TBD +} + +LLBasicCertificate::~LLBasicCertificate() +{ + if(mCert) + { + X509_free(mCert); + mCert = NULL; + } +} + +// +// retrieve the pem using the openssl functionality +std::string LLBasicCertificate::getPem() const +{ + char * pem_bio_chars = NULL; + // a BIO is the equivalent of a 'std::stream', and + // can be a file, mem stream, whatever. Grab a memory based + // BIO for the result + BIO *pem_bio = BIO_new(BIO_s_mem()); + if (!pem_bio) + { + LL_WARNS("SECAPI") << "Could not allocate an openssl memory BIO." << LL_ENDL; + return std::string(); + } + PEM_write_bio_X509(pem_bio, mCert); + int length = BIO_get_mem_data(pem_bio, &pem_bio_chars); + std::string result = std::string(pem_bio_chars, length); + BIO_free(pem_bio); + return result; +} + +// get the DER encoding for the cert +// DER is a binary encoding format for certs... +std::vector LLBasicCertificate::getBinary() const +{ + U8 * der_bio_data = NULL; + // get a memory bio + BIO *der_bio = BIO_new(BIO_s_mem()); + if (!der_bio) + { + LL_WARNS("SECAPI") << "Could not allocate an openssl memory BIO." << LL_ENDL; + return std::vector(); + } + i2d_X509_bio(der_bio, mCert); + int length = BIO_get_mem_data(der_bio, &der_bio_data); + std::vector result(length); + // vectors are guranteed to be a contiguous chunk of memory. + memcpy(&result[0], der_bio_data, length); + BIO_free(der_bio); + return result; +} + + +void LLBasicCertificate::getLLSD(LLSD &llsd) +{ + if (mLLSDInfo.isUndefined()) + { + _initLLSD(); + } + llsd = mLLSDInfo; +} + +// Initialize the LLSD info for the certificate +LLSD& LLBasicCertificate::_initLLSD() +{ + + // call the various helpers to build the LLSD + mLLSDInfo[CERT_SUBJECT_NAME] = cert_name_from_X509_NAME(X509_get_subject_name(mCert)); + mLLSDInfo[CERT_ISSUER_NAME] = cert_name_from_X509_NAME(X509_get_issuer_name(mCert)); + mLLSDInfo[CERT_SUBJECT_NAME_STRING] = cert_string_name_from_X509_NAME(X509_get_subject_name(mCert)); + mLLSDInfo[CERT_ISSUER_NAME_STRING] = cert_string_name_from_X509_NAME(X509_get_issuer_name(mCert)); + ASN1_INTEGER *sn = X509_get_serialNumber(mCert); + if (sn != NULL) + { + mLLSDInfo[CERT_SERIAL_NUMBER] = cert_string_from_asn1_integer(sn); + } + + mLLSDInfo[CERT_VALID_TO] = cert_date_from_asn1_time(X509_get_notAfter(mCert)); + mLLSDInfo[CERT_VALID_FROM] = cert_date_from_asn1_time(X509_get_notBefore(mCert)); + // add the known extensions + mLLSDInfo[CERT_BASIC_CONSTRAINTS] = _basic_constraints_ext(mCert); + mLLSDInfo[CERT_KEY_USAGE] = _key_usage_ext(mCert); + mLLSDInfo[CERT_EXTENDED_KEY_USAGE] = _ext_key_usage_ext(mCert); + mLLSDInfo[CERT_SUBJECT_KEY_IDENTFIER] = _subject_key_identifier(mCert); + mLLSDInfo[CERT_AUTHORITY_KEY_IDENTIFIER] = _authority_key_identifier(mCert); + return mLLSDInfo; +} + +// Retrieve the basic constraints info +LLSD _basic_constraints_ext(X509* cert) +{ + LLSD result; + BASIC_CONSTRAINTS *bs = (BASIC_CONSTRAINTS *)X509_get_ext_d2i(cert, NID_basic_constraints, NULL, NULL); + if(bs) + { + result = LLSD::emptyMap(); + // Determines whether the cert can be used as a CA + result[CERT_BASIC_CONSTRAINTS_CA] = (bool)bs->ca; + + if(bs->pathlen) + { + // the pathlen determines how deep a certificate chain can be from + // this CA + if((bs->pathlen->type == V_ASN1_NEG_INTEGER) + || !bs->ca) + { + result[CERT_BASIC_CONSTRAINTS_PATHLEN] = 0; + } + else + { + result[CERT_BASIC_CONSTRAINTS_PATHLEN] = (int)ASN1_INTEGER_get(bs->pathlen); + } + } + + BASIC_CONSTRAINTS_free( bs ); + } + return result; +} + +// retrieve the key usage, which specifies how the cert can be used. +// +LLSD _key_usage_ext(X509* cert) +{ + LLSD result; + ASN1_STRING *usage_str = (ASN1_STRING *)X509_get_ext_d2i(cert, NID_key_usage, NULL, NULL); + if(usage_str) + { + result = LLSD::emptyArray(); + long usage = 0; + if(usage_str->length > 0) + { + usage = usage_str->data[0]; + if(usage_str->length > 1) + { + usage |= usage_str->data[1] << 8; + } + } + ASN1_STRING_free(usage_str); + if(usage) + { + if(usage & KU_DIGITAL_SIGNATURE) result.append(LLSD((std::string)CERT_KU_DIGITAL_SIGNATURE)); + if(usage & KU_NON_REPUDIATION) result.append(LLSD((std::string)CERT_KU_NON_REPUDIATION)); + if(usage & KU_KEY_ENCIPHERMENT) result.append(LLSD((std::string)CERT_KU_KEY_ENCIPHERMENT)); + if(usage & KU_DATA_ENCIPHERMENT) result.append(LLSD((std::string)CERT_KU_DATA_ENCIPHERMENT)); + if(usage & KU_KEY_AGREEMENT) result.append(LLSD((std::string)CERT_KU_KEY_AGREEMENT)); + if(usage & KU_KEY_CERT_SIGN) result.append(LLSD((std::string)CERT_KU_CERT_SIGN)); + if(usage & KU_CRL_SIGN) result.append(LLSD((std::string)CERT_KU_CRL_SIGN)); + if(usage & KU_ENCIPHER_ONLY) result.append(LLSD((std::string)CERT_KU_ENCIPHER_ONLY)); + if(usage & KU_DECIPHER_ONLY) result.append(LLSD((std::string)CERT_KU_DECIPHER_ONLY)); + } + } + return result; +} + +// retrieve the extended key usage for the cert +LLSD _ext_key_usage_ext(X509* cert) +{ + LLSD result; + EXTENDED_KEY_USAGE *eku = (EXTENDED_KEY_USAGE *)X509_get_ext_d2i(cert, NID_ext_key_usage, NULL, NULL); + if(eku) + { + result = LLSD::emptyArray(); + while(sk_ASN1_OBJECT_num(eku)) + { + ASN1_OBJECT *usage = sk_ASN1_OBJECT_pop(eku); + if(usage) + { + int nid = OBJ_obj2nid(usage); + if (nid) + { + std::string sn = OBJ_nid2sn(nid); + result.append(sn); + } + ASN1_OBJECT_free(usage); + } + } + + EXTENDED_KEY_USAGE_free( eku ); + } + return result; +} + +// retrieve the subject key identifier of the cert +std::string _subject_key_identifier(X509 *cert) +{ + std::string result; + ASN1_OCTET_STRING *skeyid = (ASN1_OCTET_STRING *)X509_get_ext_d2i(cert, NID_subject_key_identifier, NULL, NULL); + if(skeyid) + { + result = cert_string_from_octet_string(skeyid); + ASN1_OCTET_STRING_free( skeyid ); + } + return result; +} + +// retrieve the authority key identifier of the cert +LLSD _authority_key_identifier(X509* cert) +{ + LLSD result; + AUTHORITY_KEYID *akeyid = (AUTHORITY_KEYID *)X509_get_ext_d2i(cert, NID_authority_key_identifier, NULL, NULL); + if(akeyid) + { + result = LLSD::emptyMap(); + if(akeyid->keyid) + { + result[CERT_AUTHORITY_KEY_IDENTIFIER_ID] = cert_string_from_octet_string(akeyid->keyid); + } + if(akeyid->serial) + { + result[CERT_AUTHORITY_KEY_IDENTIFIER_SERIAL] = cert_string_from_asn1_integer(akeyid->serial); + } + + AUTHORITY_KEYID_free( akeyid ); + } + // we ignore the issuer name in the authority key identifier, we check the issue name via + // the the issuer name entry in the cert. + return result; +} + +// retrieve an openssl x509 object, +// which must be freed by X509_free +X509* LLBasicCertificate::getOpenSSLX509() const +{ + return X509_dup(mCert); +} + +// generate a single string containing the subject or issuer +// name of the cert. +std::string cert_string_name_from_X509_NAME(X509_NAME* name) +{ + char * name_bio_chars = NULL; + // get a memory bio + BIO *name_bio = BIO_new(BIO_s_mem()); + // stream the name into the bio. The name will be in the 'short name' format + X509_NAME_print_ex(name_bio, name, 0, XN_FLAG_RFC2253); + int length = BIO_get_mem_data(name_bio, &name_bio_chars); + std::string result = std::string(name_bio_chars, length); + BIO_free(name_bio); + return result; +} + +// generate an LLSD from a certificate name (issuer or subject name). +// the name will be strings indexed by the 'long form' +LLSD cert_name_from_X509_NAME(X509_NAME* name) +{ + LLSD result = LLSD::emptyMap(); + int name_entries = X509_NAME_entry_count(name); + for (int entry_index=0; entry_index < name_entries; entry_index++) + { + char buffer[32]; + X509_NAME_ENTRY *entry = X509_NAME_get_entry(name, entry_index); + + std::string name_value = std::string((const char*)ASN1_STRING_get0_data(X509_NAME_ENTRY_get_data(entry)), + ASN1_STRING_length(X509_NAME_ENTRY_get_data(entry))); + + ASN1_OBJECT* name_obj = X509_NAME_ENTRY_get_object(entry); + OBJ_obj2txt(buffer, sizeof(buffer), name_obj, 0); + std::string obj_buffer_str = std::string(buffer); + result[obj_buffer_str] = name_value; + } + + return result; +} + +// Generate a string from an ASN1 integer. ASN1 Integers are +// bignums, so they can be 'infinitely' long, therefore we +// cannot simply use a conversion to U64 or something. +// We retrieve as a readable string for UI + +std::string cert_string_from_asn1_integer(ASN1_INTEGER* value) +{ + std::string result; + BIGNUM *bn = ASN1_INTEGER_to_BN(value, NULL); + if(bn) + { + char * ascii_bn = BN_bn2hex(bn); + + if(ascii_bn) + { + result = ascii_bn; + OPENSSL_free(ascii_bn); + } + BN_free(bn); + } + return result; +} + +// Generate a string from an OCTET string. +// we retrieve as a + +std::string cert_string_from_octet_string(ASN1_OCTET_STRING* value) +{ + + std::stringstream result; + result << std::hex << std::setprecision(2); + for (int i=0; i < value->length; i++) + { + if (i != 0) + { + result << ":"; + } + result << std::setfill('0') << std::setw(2) << (int)value->data[i]; + } + return result.str(); +} + +// Generate a string from an ASN1 integer. ASN1 Integers are +// bignums, so they can be 'infinitely' long, therefore we +// cannot simply use a conversion to U64 or something. +// We retrieve as a readable string for UI + +std::string cert_string_from_asn1_string(ASN1_STRING* value) +{ + char * string_bio_chars = NULL; + std::string result; + // get a memory bio + BIO *string_bio = BIO_new(BIO_s_mem()); + if(!string_bio) + { + // stream the name into the bio. The name will be in the 'short name' format + ASN1_STRING_print_ex(string_bio, value, ASN1_STRFLGS_RFC2253); + int length = BIO_get_mem_data(string_bio, &string_bio_chars); + result = std::string(string_bio_chars, length); + BIO_free(string_bio); + } + else + { + LL_WARNS("SECAPI") << "Could not allocate an openssl memory BIO." << LL_ENDL; + } + + return result; +} + +// retrieve a date structure from an ASN1 time, for +// validity checking. +LLDate cert_date_from_asn1_time(ASN1_TIME* asn1_time) +{ + + struct tm timestruct = {0}; + int i = asn1_time->length; + + if (i < 10) + { + return LLDate(); + } + // convert the date from the ASN1 time (which is a string in ZULU time), to + // a timeval. + timestruct.tm_year = (asn1_time->data[0]-'0') * 10 + (asn1_time->data[1]-'0'); + + /* Deal with Year 2000 */ + if (timestruct.tm_year < 70) + timestruct.tm_year += 100; + + timestruct.tm_mon = (asn1_time->data[2]-'0') * 10 + (asn1_time->data[3]-'0') - 1; + timestruct.tm_mday = (asn1_time->data[4]-'0') * 10 + (asn1_time->data[5]-'0'); + timestruct.tm_hour = (asn1_time->data[6]-'0') * 10 + (asn1_time->data[7]-'0'); + timestruct.tm_min = (asn1_time->data[8]-'0') * 10 + (asn1_time->data[9]-'0'); + timestruct.tm_sec = (asn1_time->data[10]-'0') * 10 + (asn1_time->data[11]-'0'); + +#if LL_WINDOWS + return LLDate((F64)_mkgmtime(×truct)); +#else // LL_WINDOWS + return LLDate((F64)timegm(×truct)); +#endif // LL_WINDOWS +} + +// class LLBasicCertificateVector +// This class represents a list of certificates, implemented by a vector of certificate pointers. +// it contains implementations of the virtual functions for iterators, search, add, remove, etc. +// + +// Find a certificate in the list. +// It will find a cert that has minimally the params listed, with the values being the same +LLBasicCertificateVector::iterator LLBasicCertificateVector::find(const LLSD& params) +{ + // loop through the entire vector comparing the values in the certs + // against those passed in via the params. + // params should be a map. Only the items specified in the map will be + // checked, but they must match exactly, even if they're maps or arrays. + bool found = false; + iterator cert = begin(); + while ( !found && cert != end() ) + { + found = true; + LLSD cert_info; + (*cert)->getLLSD(cert_info); + for (LLSD::map_const_iterator param = params.beginMap(); + found && param != params.endMap(); + param++) + { + if ( !cert_info.has((std::string)param->first) + || !valueCompareLLSD(cert_info[(std::string)param->first], param->second)) + { + found = false; + } + } + if (!found) + { + cert++; + } + } + return cert; +} + +// Insert a certificate into the store. If the certificate already +// exists in the store, nothing is done. +void LLBasicCertificateVector::insert(iterator _iter, + LLPointer cert) +{ + LLSD cert_info; + cert->getLLSD(cert_info); + if (cert_info.isMap() && cert_info.has(CERT_SUBJECT_KEY_IDENTFIER)) + { + LLSD existing_cert_info = LLSD::emptyMap(); + existing_cert_info[CERT_SUBJECT_KEY_IDENTFIER] = cert_info[CERT_SUBJECT_KEY_IDENTFIER]; + if(find(existing_cert_info) == end()) + { + BasicIteratorImpl *basic_iter = dynamic_cast(_iter.mImpl.get()); + if (basic_iter) + { + mCerts.insert(basic_iter->mIter, cert); + } + else + { + LL_WARNS("SECAPI") << "Invalid certificate postion vector" + << LL_ENDL; + } + } + else + { + LL_DEBUGS("SECAPI") << "Certificate already in vector: " + << "'" << cert_info << "'" + << LL_ENDL; + } + + } + else + { + LL_WARNS("SECAPI") << "Certificate does not have Subject Key Identifier; not inserted: " + << "'" << cert_info << "'" + << LL_ENDL; + } +} + +// remove a certificate from the store +LLPointer LLBasicCertificateVector::erase(iterator _iter) +{ + + if (_iter != end()) + { + BasicIteratorImpl *basic_iter = dynamic_cast(_iter.mImpl.get()); + LLPointer result = (*_iter); + mCerts.erase(basic_iter->mIter); + return result; + } + return NULL; +} + + +// +// LLBasicCertificateStore +// This class represents a store of CA certificates. The basic implementation +// uses a crt file such as the ca-bundle.crt in the existing SL implementation. +LLBasicCertificateStore::LLBasicCertificateStore(const std::string& filename) +{ + mFilename = filename; + load_from_file(filename); +} + +void LLBasicCertificateStore::load_from_file(const std::string& filename) +{ + int loaded = 0; + int rejected = 0; + + // scan the PEM file extracting each certificate + if (LLFile::isfile(filename)) + { + BIO* file_bio = BIO_new(BIO_s_file()); + if(file_bio) + { + if (BIO_read_filename(file_bio, filename.c_str()) > 0) + { + X509 *cert_x509 = NULL; + while((PEM_read_bio_X509(file_bio, &cert_x509, 0, NULL)) && + (cert_x509 != NULL)) + { + try + { + LLPointer new_cert(new LLBasicCertificate(cert_x509)); + LLSD validation_params; + _validateCert(VALIDATION_POLICY_TIME, + new_cert, + validation_params, + 0); + add(new_cert); + LL_DEBUGS("SECAPI") << "Loaded valid cert for " + << "Name '" << cert_string_name_from_X509_NAME(X509_get_subject_name(cert_x509)) << "'"; + std::string skeyid(_subject_key_identifier(cert_x509)); + LL_CONT << " Id '" << skeyid << "'" + << LL_ENDL; + loaded++; + } + catch (LLCertException& cert_exception) + { + LLSD cert_info(cert_exception.getCertData()); + LL_DEBUGS("SECAPI_BADCERT","SECAPI") << "invalid certificate (" << cert_exception.what() << "): " << cert_info << LL_ENDL; + rejected++; + } + catch (...) + { + LOG_UNHANDLED_EXCEPTION("creating certificate from the certificate store file"); + rejected++; + } + X509_free(cert_x509); + cert_x509 = NULL; + } + BIO_free(file_bio); + } + else + { + LL_WARNS("SECAPI") << "BIO read failed for " << filename << LL_ENDL; + } + + LL_INFOS("SECAPI") << "loaded " << loaded << " good certificates (rejected " << rejected << ") from " << filename << LL_ENDL; + } + else + { + LL_WARNS("SECAPI") << "Could not allocate a file BIO" << LL_ENDL; + } + } + else + { + // since the user certificate store may not be there, this is not a warning + LL_INFOS("SECAPI") << "Certificate store not found at " << filename << LL_ENDL; + } +} + + +LLBasicCertificateStore::~LLBasicCertificateStore() +{ +} + + +// persist the store +void LLBasicCertificateStore::save() +{ + llofstream file_store(mFilename.c_str(), std::ios_base::binary); + if(!file_store.fail()) + { + for(iterator cert = begin(); + cert != end(); + cert++) + { + std::string pem = (*cert)->getPem(); + if(!pem.empty()) + { + file_store << (*cert)->getPem() << std::endl; + } + } + file_store.close(); + } + else + { + LL_WARNS("SECAPI") << "Could not open certificate store " << mFilename << "for save" << LL_ENDL; + } +} + +// return the store id +std::string LLBasicCertificateStore::storeId() const +{ + // this is the basic handler which uses the ca-bundle.crt store, + // so we ignore this. + return std::string(""); +} + + +// +// LLBasicCertificateChain +// This class represents a chain of certs, each cert being signed by the next cert +// in the chain. Certs must be properly signed by the parent +LLBasicCertificateChain::LLBasicCertificateChain(X509_STORE_CTX* store) +{ + + // we're passed in a context, which contains a cert, and a blob of untrusted + // certificates which compose the chain. + if((store == NULL) || X509_STORE_CTX_get0_cert(store) == NULL) + { + LL_WARNS("SECAPI") << "An invalid store context was passed in when trying to create a certificate chain" << LL_ENDL; + return; + } + // grab the child cert + LLPointer current = new LLBasicCertificate(X509_STORE_CTX_get0_cert(store)); + + add(current); + if(X509_STORE_CTX_get0_untrusted(store) != NULL) + { + // if there are other certs in the chain, we build up a vector + // of untrusted certs so we can search for the parents of each + // consecutive cert. + LLBasicCertificateVector untrusted_certs; + for(int i = 0; i < sk_X509_num(X509_STORE_CTX_get0_untrusted(store)); i++) + { + LLPointer cert = new LLBasicCertificate(sk_X509_value(X509_STORE_CTX_get0_untrusted(store), i)); + untrusted_certs.add(cert); + + } + while(untrusted_certs.size() > 0) + { + LLSD find_data = LLSD::emptyMap(); + LLSD cert_data; + current->getLLSD(cert_data); + // we simply build the chain via subject/issuer name as the + // client should not have passed in multiple CA's with the same + // subject name. If they did, it'll come out in the wash during + // validation. + find_data[CERT_SUBJECT_NAME_STRING] = cert_data[CERT_ISSUER_NAME_STRING]; + LLBasicCertificateVector::iterator issuer = untrusted_certs.find(find_data); + if (issuer != untrusted_certs.end()) + { + current = untrusted_certs.erase(issuer); + add(current); + } + else + { + break; + } + } + } +} + + +// subdomain wildcard specifiers can be divided into 3 parts +// the part before the first *, the part after the first * but before +// the second *, and the part after the second *. +// It then iterates over the second for each place in the string +// that it matches. ie if the subdomain was testfoofoobar, and +// the wildcard was test*foo*bar, it would match test, then +// recursively match foofoobar and foobar + +bool _cert_subdomain_wildcard_match(const std::string& subdomain, + const std::string& wildcard) +{ + // split wildcard into the portion before the *, and the portion after + + int wildcard_pos = wildcard.find_first_of('*'); + // check the case where there is no wildcard. + if(wildcard_pos == wildcard.npos) + { + return (subdomain == wildcard); + } + + // we need to match the first part of the subdomain string up to the wildcard + // position + if(subdomain.substr(0, wildcard_pos) != wildcard.substr(0, wildcard_pos)) + { + // the first portions of the strings didn't match + return false; + } + + // as the portion of the wildcard string before the * matched, we need to check the + // portion afterwards. Grab that portion. + std::string new_wildcard_string = wildcard.substr( wildcard_pos+1, wildcard.npos); + if(new_wildcard_string.empty()) + { + // we had nothing after the *, so it's an automatic match + return true; + } + + // grab the portion of the remaining wildcard string before the next '*'. We need to find this + // within the remaining subdomain string. and then recursively check. + std::string new_wildcard_match_string = new_wildcard_string.substr(0, new_wildcard_string.find_first_of('*')); + + // grab the portion of the subdomain after the part that matched the initial wildcard portion + std::string new_subdomain = subdomain.substr(wildcard_pos, subdomain.npos); + + // iterate through the current subdomain, finding instances of the match string. + int sub_pos = new_subdomain.find_first_of(new_wildcard_match_string); + while(sub_pos != std::string::npos) + { + new_subdomain = new_subdomain.substr(sub_pos, std::string::npos); + if(_cert_subdomain_wildcard_match(new_subdomain, new_wildcard_string)) + { + return true; + } + sub_pos = new_subdomain.find_first_of(new_wildcard_match_string, 1); + + + } + // didn't find any instances of the match string that worked in the subdomain, so fail. + return false; +} + + +// RFC2459 does not address wildcards as part of it's name matching +// specification, and there is no RFC specifying wildcard matching, +// RFC2818 does a few statements about wildcard matching, but is very +// general. Generally, wildcard matching is per implementation, although +// it's pretty similar. +// in our case, we use the '*' wildcard character only, within each +// subdomain. The hostname and the CN specification should have the +// same number of subdomains. +// We then iterate that algorithm over each subdomain. +bool _cert_hostname_wildcard_match(const std::string& hostname, const std::string& common_name) +{ + std::string new_hostname = hostname; + std::string new_cn = common_name; + + // find the last '.' in the hostname and the match name. + int subdomain_pos = new_hostname.find_last_of('.'); + int subcn_pos = new_cn.find_last_of('.'); + + // if the last char is a '.', strip it + if(subdomain_pos == (new_hostname.length()-1)) + { + new_hostname = new_hostname.substr(0, subdomain_pos); + subdomain_pos = new_hostname.find_last_of('.'); + } + if(subcn_pos == (new_cn.length()-1)) + { + new_cn = new_cn.substr(0, subcn_pos); + subcn_pos = new_cn.find_last_of('.'); + } + + // Check to see if there are any further '.' in the string. + while((subcn_pos != std::string::npos) && (subdomain_pos != std::string::npos)) + { + // snip out last subdomain in both the match string and the hostname + // The last bit for 'my.current.host.com' would be 'com' + std::string cn_part = new_cn.substr(subcn_pos+1, std::string::npos); + std::string hostname_part = new_hostname.substr(subdomain_pos+1, std::string::npos); + + if(!_cert_subdomain_wildcard_match(new_hostname.substr(subdomain_pos+1, std::string::npos), + cn_part)) + { + return false; + } + new_hostname = new_hostname.substr(0, subdomain_pos); + new_cn = new_cn.substr(0, subcn_pos); + subdomain_pos = new_hostname.find_last_of('.'); + subcn_pos = new_cn.find_last_of('.'); + } + // check to see if the most significant portion of the common name is '*'. If so, we can + // simply return success as child domains are also matched. + if(new_cn == "*") + { + // if it's just a '*' we support all child domains as well, so '*. + return true; + } + + return _cert_subdomain_wildcard_match(new_hostname, new_cn); + +} + +// validate that the LLSD array in llsd_set contains the llsd_value +bool _LLSDArrayIncludesValue(const LLSD& llsd_set, LLSD llsd_value) +{ + for(LLSD::array_const_iterator set_value = llsd_set.beginArray(); + set_value != llsd_set.endArray(); + set_value++) + { + if(valueCompareLLSD((*set_value), llsd_value)) + { + return true; + } + } + return false; +} + +void _validateCert(int validation_policy, + LLPointer cert, + const LLSD& validation_params, + int depth) +{ + LLSD current_cert_info; + cert->getLLSD(current_cert_info); + // check basic properties exist in the cert + if(!current_cert_info.has(CERT_SUBJECT_NAME) || !current_cert_info.has(CERT_SUBJECT_NAME_STRING)) + { + LLTHROW(LLCertException(current_cert_info, "Cert doesn't have a Subject Name")); + } + + if(!current_cert_info.has(CERT_ISSUER_NAME_STRING)) + { + LLTHROW(LLCertException(current_cert_info, "Cert doesn't have an Issuer Name")); + } + + // check basic properties exist in the cert + if(!current_cert_info.has(CERT_VALID_FROM) || !current_cert_info.has(CERT_VALID_TO)) + { + LLTHROW(LLCertException(current_cert_info, "Cert doesn't have an expiration period")); + } + if (!current_cert_info.has(CERT_SUBJECT_KEY_IDENTFIER)) + { + LLTHROW(LLCertException(current_cert_info, "Cert doesn't have a Subject Key Id")); + } + + if (validation_policy & VALIDATION_POLICY_TIME) + { + LLDate validation_date(time(NULL)); + if(validation_params.has(CERT_VALIDATION_DATE)) + { + validation_date = validation_params[CERT_VALIDATION_DATE]; + } + + if((validation_date < current_cert_info[CERT_VALID_FROM].asDate()) || + (validation_date > current_cert_info[CERT_VALID_TO].asDate())) + { + LLTHROW(LLCertValidationExpirationException(current_cert_info, validation_date)); + } + } + if (validation_policy & VALIDATION_POLICY_SSL_KU) + { + // This stanza of code was changed 2021-06-09 as per details in SL-15370. + // Brief summary: a renewed certificate from Akamai only contains the + // 'Digital Signature' field and not the 'Key Encipherment' one. This code + // used to look for both and throw an exception at startup (ignored) and + // (for example) when buying L$ in the Viewer (fails with a UI message + // and an entry in the Viewer log). This modified code removes the second + // check for the 'Key Encipherment' field. If Akamai can provide a + // replacement certificate that has both fields, then this modified code + // will not be required. + if (current_cert_info.has(CERT_KEY_USAGE) && current_cert_info[CERT_KEY_USAGE].isArray() && + !(_LLSDArrayIncludesValue(current_cert_info[CERT_KEY_USAGE], + LLSD((std::string)CERT_KU_DIGITAL_SIGNATURE))) + ) + { + LLTHROW(LLCertKeyUsageValidationException(current_cert_info)); + } + // only validate EKU if the cert has it + if(current_cert_info.has(CERT_EXTENDED_KEY_USAGE) + && current_cert_info[CERT_EXTENDED_KEY_USAGE].isArray() + && (!_LLSDArrayIncludesValue(current_cert_info[CERT_EXTENDED_KEY_USAGE], + LLSD((std::string)CERT_EKU_TLS_SERVER_AUTH))) + && (!_LLSDArrayIncludesValue(current_cert_info[CERT_EXTENDED_KEY_USAGE], + LLSD((std::string)CERT_EKU_SERVER_AUTH))) + ) + { + LLTHROW(LLCertKeyUsageValidationException(current_cert_info)); + } + } + if (validation_policy & VALIDATION_POLICY_CA_KU) + { + if (current_cert_info.has(CERT_KEY_USAGE) && current_cert_info[CERT_KEY_USAGE].isArray() && + (!_LLSDArrayIncludesValue(current_cert_info[CERT_KEY_USAGE], + (std::string)CERT_KU_CERT_SIGN))) + { + LLTHROW(LLCertKeyUsageValidationException(current_cert_info)); + } + } + + // validate basic constraints + if ((validation_policy & VALIDATION_POLICY_CA_BASIC_CONSTRAINTS) && + current_cert_info.has(CERT_BASIC_CONSTRAINTS) && + current_cert_info[CERT_BASIC_CONSTRAINTS].isMap()) + { + if(!current_cert_info[CERT_BASIC_CONSTRAINTS].has(CERT_BASIC_CONSTRAINTS_CA) || + !current_cert_info[CERT_BASIC_CONSTRAINTS][CERT_BASIC_CONSTRAINTS_CA]) + { + LLTHROW(LLCertBasicConstraintsValidationException(current_cert_info)); + } + if (current_cert_info[CERT_BASIC_CONSTRAINTS].has(CERT_BASIC_CONSTRAINTS_PATHLEN) && + ((current_cert_info[CERT_BASIC_CONSTRAINTS][CERT_BASIC_CONSTRAINTS_PATHLEN].asInteger() != 0) && + (depth > current_cert_info[CERT_BASIC_CONSTRAINTS][CERT_BASIC_CONSTRAINTS_PATHLEN].asInteger()))) + { + LLTHROW(LLCertBasicConstraintsValidationException(current_cert_info)); + } + } +} + +bool _verify_signature(LLPointer parent, + LLPointer child) +{ + bool verify_result = false; + LLSD cert1, cert2; + parent->getLLSD(cert1); + child->getLLSD(cert2); + X509 *signing_cert = parent->getOpenSSLX509(); + X509 *child_cert = child->getOpenSSLX509(); + if((signing_cert != NULL) && (child_cert != NULL)) + { + EVP_PKEY *pkey = X509_get_pubkey(signing_cert); + + + if(pkey) + { + int verify_code = X509_verify(child_cert, pkey); + verify_result = ( verify_code > 0); + EVP_PKEY_free(pkey); + } + else + { + LL_WARNS("SECAPI") << "Could not validate the cert chain signature, as the public key of the signing cert could not be retrieved" << LL_ENDL; + } + + } + else + { + LL_WARNS("SECAPI") << "Signature verification failed as there are no certs in the chain" << LL_ENDL; + } + if(child_cert) + { + X509_free(child_cert); + } + if(signing_cert) + { + X509_free(signing_cert); + } + return verify_result; +} + + +// validate the certificate chain against a store. +// There are many aspects of cert validatioin policy involved in +// trust validation. The policies in this validation algorithm include +// * Hostname matching for SSL certs +// * Expiration time matching +// * Signature validation +// * Chain trust (is the cert chain trusted against the store) +// * Basic constraints +// * key usage and extended key usage +// TODO: We should add 'authority key identifier' for chaining. +// This algorithm doesn't simply validate the chain by itself +// and verify the last cert is in the certificate store, or points +// to a cert in the store. It validates whether any cert in the chain +// is trusted in the store, even if it's not the last one. +void LLBasicCertificateStore::validate(int validation_policy, + LLPointer cert_chain, + const LLSD& validation_params) +{ + // If --no-verify-ssl-cert was passed on the command line, stop right now. + if (gSavedSettings.getBOOL("NoVerifySSLCert")) + { + LL_WARNS_ONCE("SECAPI") << "All Certificate validation disabled; viewer operation is insecure" << LL_ENDL; + return; + } + + if(cert_chain->size() < 1) + { + LLTHROW(LLCertException(LLSD::emptyMap(), "No certs in chain")); + } + iterator current_cert = cert_chain->begin(); + LLSD validation_date; + if (validation_params.has(CERT_VALIDATION_DATE)) + { + validation_date = validation_params[CERT_VALIDATION_DATE]; + } + + // get LLSD info from the cert to throw in any exception + LLSD current_cert_info; + (*current_cert)->getLLSD(current_cert_info); + + if (validation_policy & VALIDATION_POLICY_HOSTNAME) + { + if(!validation_params.has(CERT_HOSTNAME)) + { + LLTHROW(LLCertException(current_cert_info, "No hostname passed in for validation")); + } + if(!current_cert_info.has(CERT_SUBJECT_NAME) || !current_cert_info[CERT_SUBJECT_NAME].has(CERT_NAME_CN)) + { + LLTHROW(LLInvalidCertificate(current_cert_info)); + } + + LL_DEBUGS("SECAPI") << "Validating the hostname " << validation_params[CERT_HOSTNAME].asString() << + "against the cert CN " << current_cert_info[CERT_SUBJECT_NAME][CERT_NAME_CN].asString() << LL_ENDL; + if(!_cert_hostname_wildcard_match(validation_params[CERT_HOSTNAME].asString(), + current_cert_info[CERT_SUBJECT_NAME][CERT_NAME_CN].asString())) + { + throw LLCertValidationHostnameException(validation_params[CERT_HOSTNAME].asString(), + current_cert_info); + } + } + + // check the cache of already validated certs + X509* cert_x509 = (*current_cert)->getOpenSSLX509(); + if(!cert_x509) + { + LLTHROW(LLInvalidCertificate(current_cert_info)); + } + + std::string subject_name(cert_string_name_from_X509_NAME(X509_get_subject_name(cert_x509))); + std::string skeyid(_subject_key_identifier(cert_x509)); + + LL_DEBUGS("SECAPI") << "attempting to validate cert " + << " for '" << (validation_params.has(CERT_HOSTNAME) ? validation_params[CERT_HOSTNAME].asString() : "(unknown hostname)") << "'" + << " as subject name '" << subject_name << "'" + << " subject key id '" << skeyid << "'" + << LL_ENDL; + + X509_free( cert_x509 ); + cert_x509 = NULL; + if (skeyid.empty()) + { + LLTHROW(LLCertException(current_cert_info, "No Subject Key Id")); + } + + t_cert_cache::iterator cache_entry = mTrustedCertCache.find(skeyid); + if(cache_entry != mTrustedCertCache.end()) + { + // this cert is in the cache, so validate the time. + if (validation_policy & VALIDATION_POLICY_TIME) + { + LLDate validation_date; + if(validation_params.has(CERT_VALIDATION_DATE)) + { + validation_date = validation_params[CERT_VALIDATION_DATE]; + } + else + { + validation_date = LLDate(time(NULL)); // current time + } + + if((validation_date < cache_entry->second.first) || + (validation_date > cache_entry->second.second)) + { + LLTHROW(LLCertValidationExpirationException(current_cert_info, validation_date)); + } + } + // successfully found in cache + LL_DEBUGS("SECAPI") << "Valid cert for '" << validation_params[CERT_HOSTNAME].asString() << "'" + << " skeyid '" << skeyid << "'" + << " found in cache" + << LL_ENDL; + return; + } + if(current_cert_info.isUndefined()) + { + (*current_cert)->getLLSD(current_cert_info); + } + LLDate from_time = current_cert_info[CERT_VALID_FROM].asDate(); + LLDate to_time = current_cert_info[CERT_VALID_TO].asDate(); + int depth = 0; + LLPointer previous_cert; + // loop through the cert chain, validating the current cert against the next one. + while(current_cert != cert_chain->end()) + { + int local_validation_policy = validation_policy; + if(current_cert == cert_chain->begin()) + { + // for the child cert, we don't validate CA stuff + local_validation_policy &= ~(VALIDATION_POLICY_CA_KU | + VALIDATION_POLICY_CA_BASIC_CONSTRAINTS); + } + else + { + // for non-child certs, we don't validate SSL Key usage + local_validation_policy &= ~VALIDATION_POLICY_SSL_KU; + if(!_verify_signature((*current_cert), + previous_cert)) + { + LLSD previous_cert_info; + previous_cert->getLLSD(previous_cert_info); + LLTHROW(LLCertValidationInvalidSignatureException(previous_cert_info)); + } + } + _validateCert(local_validation_policy, + (*current_cert), + validation_params, + depth); + + // look for a CA in the CA store that may belong to this chain. + LLSD cert_search_params = LLSD::emptyMap(); + // is the cert itself in the store? + cert_search_params[CERT_SUBJECT_KEY_IDENTFIER] = current_cert_info[CERT_SUBJECT_KEY_IDENTFIER]; + LLCertificateStore::iterator found_store_cert = find(cert_search_params); + if(found_store_cert != end()) + { + mTrustedCertCache[skeyid] = std::pair(from_time, to_time); + LL_DEBUGS("SECAPI") << "Valid cert " + << " for '" << (validation_params.has(CERT_HOSTNAME) ? validation_params[CERT_HOSTNAME].asString() : "(unknown hostname)") << "'"; + X509* cert_x509 = (*found_store_cert)->getOpenSSLX509(); + std::string found_cert_subject_name(cert_string_name_from_X509_NAME(X509_get_subject_name(cert_x509))); + X509_free(cert_x509); + LL_CONT << " as '" << found_cert_subject_name << "'" + << " skeyid '" << current_cert_info[CERT_SUBJECT_KEY_IDENTFIER].asString() << "'" + << " found in cert store" + << LL_ENDL; + return; + } + + // is the parent in the cert store? + + cert_search_params = LLSD::emptyMap(); + cert_search_params[CERT_SUBJECT_NAME_STRING] = current_cert_info[CERT_ISSUER_NAME_STRING]; + if (current_cert_info.has(CERT_AUTHORITY_KEY_IDENTIFIER)) + { + LLSD cert_aki = current_cert_info[CERT_AUTHORITY_KEY_IDENTIFIER]; + if(cert_aki.has(CERT_AUTHORITY_KEY_IDENTIFIER_ID)) + { + cert_search_params[CERT_SUBJECT_KEY_IDENTFIER] = cert_aki[CERT_AUTHORITY_KEY_IDENTIFIER_ID]; + } + if(cert_aki.has(CERT_AUTHORITY_KEY_IDENTIFIER_SERIAL)) + { + cert_search_params[CERT_SERIAL_NUMBER] = cert_aki[CERT_AUTHORITY_KEY_IDENTIFIER_SERIAL]; + } + } + found_store_cert = find(cert_search_params); + + if(found_store_cert != end()) + { + // validate the store cert against the depth + _validateCert(validation_policy & VALIDATION_POLICY_CA_BASIC_CONSTRAINTS, + (*found_store_cert), + LLSD(), + depth); + + // verify the signature of the CA + if(!_verify_signature((*found_store_cert), + (*current_cert))) + { + LLTHROW(LLCertValidationInvalidSignatureException(current_cert_info)); + } + // successfully validated. + mTrustedCertCache[skeyid] = std::pair(from_time, to_time); + LL_DEBUGS("SECAPI") << "Verified and cached cert for '" << validation_params[CERT_HOSTNAME].asString() << "'" + << " as '" << subject_name << "'" + << " id '" << skeyid << "'" + << " using CA '" << cert_search_params[CERT_SUBJECT_NAME_STRING] << "'" + << " with id '" << cert_search_params[CERT_SUBJECT_KEY_IDENTFIER].asString() << "' found in cert store" + << LL_ENDL; + return; + } + previous_cert = (*current_cert); + current_cert++; + depth++; + if(current_cert != cert_chain->end()) + { + (*current_cert)->getLLSD(current_cert_info); + } + } + if (validation_policy & VALIDATION_POLICY_TRUSTED) + { + // we reached the end without finding a trusted cert. + LLSD last_cert_info; + ((*cert_chain)[cert_chain->size()-1])->getLLSD(last_cert_info); + LLTHROW(LLCertValidationTrustException(last_cert_info)); + } + else + { + LL_DEBUGS("SECAPI") << "! Caching untrusted cert for '" << subject_name << "'" + << " skeyid '" << skeyid << "' in cert store because ! VALIDATION_POLICY_TRUSTED" + << LL_ENDL; + mTrustedCertCache[skeyid] = std::pair(from_time, to_time); + } +} + + +// LLSecAPIBasicHandler Class +// Interface handler class for the various security storage handlers. + +// We read the file on construction, and write it on destruction. This +// means multiple processes cannot modify the datastore. +LLSecAPIBasicHandler::LLSecAPIBasicHandler(const std::string& protected_data_file, + const std::string& legacy_password_path) +{ + mProtectedDataFilename = protected_data_file; + mProtectedDataMap = LLSD::emptyMap(); + mLegacyPasswordPath = legacy_password_path; + +} + +LLSecAPIBasicHandler::LLSecAPIBasicHandler() +{ +} + + +void LLSecAPIBasicHandler::init() +{ + mProtectedDataMap = LLSD::emptyMap(); + if (mProtectedDataFilename.length() == 0) + { + mProtectedDataFilename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, + "bin_conf.dat"); + mLegacyPasswordPath = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "password.dat"); + + mProtectedDataFilename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, + "bin_conf.dat"); + std::string store_file = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, + "CA.pem"); + + + LL_INFOS("SECAPI") << "Loading user certificate store from " << store_file << LL_ENDL; + mStore = new LLBasicCertificateStore(store_file); + + // grab the application ca-bundle.crt file that contains the well-known certs shipped + // with the product + std::string ca_file_path = gDirUtilp->getCAFile(); + LL_INFOS("SECAPI") << "Loading application certificate store from " << ca_file_path << LL_ENDL; + LLPointer app_ca_store = new LLBasicCertificateStore(ca_file_path); + + // push the applicate CA files into the store, therefore adding any new CA certs that + // updated + for(LLCertificateVector::iterator i = app_ca_store->begin(); + i != app_ca_store->end(); + i++) + { + mStore->add(*i); + } + + } + _readProtectedData(); // initialize mProtectedDataMap + // may throw LLProtectedDataException if saved datamap is not decryptable +} +LLSecAPIBasicHandler::~LLSecAPIBasicHandler() +{ + _writeProtectedData(); +} + +void LLSecAPIBasicHandler::_readProtectedData(unsigned char *unique_id, U32 id_len) +{ + // attempt to load the file into our map + LLPointer parser = new LLSDXMLParser(); + llifstream protected_data_stream(mProtectedDataFilename.c_str(), + llifstream::binary); + + if (!protected_data_stream.fail()) { + U8 salt[STORE_SALT_SIZE]; + U8 buffer[BUFFER_READ_SIZE]; + U8 decrypted_buffer[BUFFER_READ_SIZE]; + int decrypted_length; + LLXORCipher cipher(unique_id, id_len); + + // read in the salt and key + protected_data_stream.read((char *)salt, STORE_SALT_SIZE); + if (protected_data_stream.gcount() < STORE_SALT_SIZE) + { + LLTHROW(LLProtectedDataException("Config file too short.")); + } + + cipher.decrypt(salt, STORE_SALT_SIZE); + + // totally lame. As we're not using the OS level protected data, we need to + // at least obfuscate the data. We do this by using a salt stored at the head of the file + // to encrypt the data, therefore obfuscating it from someone using simple existing tools. + // We do include the MAC address as part of the obfuscation, which would require an + // attacker to get the MAC address as well as the protected store, which improves things + // somewhat. It would be better to use the password, but as this store + // will be used to store the SL password when the user decides to have SL remember it, + // so we can't use that. OS-dependent store implementations will use the OS password/storage + // mechanisms and are considered to be more secure. + // We've a strong intent to move to OS dependent protected data stores. + + + // read in the rest of the file. + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + // todo: ctx error handling + + EVP_DecryptInit(ctx, EVP_rc4(), salt, NULL); + // allocate memory: + std::string decrypted_data; + + while(protected_data_stream.good()) { + // read data as a block: + protected_data_stream.read((char *)buffer, BUFFER_READ_SIZE); + + EVP_DecryptUpdate(ctx, decrypted_buffer, &decrypted_length, + buffer, protected_data_stream.gcount()); + decrypted_data.append((const char *)decrypted_buffer, protected_data_stream.gcount()); + } + + // RC4 is a stream cipher, so we don't bother to EVP_DecryptFinal, as there is + // no block padding. + EVP_CIPHER_CTX_free(ctx); + std::istringstream parse_stream(decrypted_data); + if (parser->parse(parse_stream, mProtectedDataMap, + LLSDSerialize::SIZE_UNLIMITED) == LLSDParser::PARSE_FAILURE) + { + LLTHROW(LLProtectedDataException("Config file cannot be decrypted.")); + } + } +} + +void LLSecAPIBasicHandler::_readProtectedData() +{ + unsigned char unique_id[MAC_ADDRESS_BYTES]; + try + { + // try default id + LLMachineID::getUniqueID(unique_id, sizeof(unique_id)); + _readProtectedData(unique_id, sizeof(unique_id)); + } + catch(LLProtectedDataException&) + { + // try with legacy id, it will return false if it is identical to getUniqueID + // or if it is not assigned/not in use + if (LLMachineID::getLegacyID(unique_id, sizeof(unique_id))) + { + _readProtectedData(unique_id, sizeof(unique_id)); + } + else + { + throw; + } + } +} + +void LLSecAPIBasicHandler::_writeProtectedData() +{ + std::ostringstream formatted_data_ostream; + U8 salt[STORE_SALT_SIZE]; + U8 buffer[BUFFER_READ_SIZE]; + U8 encrypted_buffer[BUFFER_READ_SIZE]; + + + if(mProtectedDataMap.isUndefined()) + { + LLFile::remove(mProtectedDataFilename); + return; + } + // create a string with the formatted data. + LLSDSerialize::toXML(mProtectedDataMap, formatted_data_ostream); + std::istringstream formatted_data_istream(formatted_data_ostream.str()); + // generate the seed + RAND_bytes(salt, STORE_SALT_SIZE); + + + // write to a temp file so we don't clobber the initial file if there is + // an error. + std::string tmp_filename = mProtectedDataFilename + ".tmp"; + + llofstream protected_data_stream(tmp_filename.c_str(), + std::ios_base::binary); + EVP_CIPHER_CTX *ctx = NULL; + try + { + + ctx = EVP_CIPHER_CTX_new(); + // todo: ctx error handling + + EVP_EncryptInit(ctx, EVP_rc4(), salt, NULL); + unsigned char unique_id[MAC_ADDRESS_BYTES]; + LLMachineID::getUniqueID(unique_id, sizeof(unique_id)); + LLXORCipher cipher(unique_id, sizeof(unique_id)); + cipher.encrypt(salt, STORE_SALT_SIZE); + protected_data_stream.write((const char *)salt, STORE_SALT_SIZE); + + while (formatted_data_istream.good()) + { + formatted_data_istream.read((char *)buffer, BUFFER_READ_SIZE); + if(formatted_data_istream.gcount() == 0) + { + break; + } + int encrypted_length; + EVP_EncryptUpdate(ctx, encrypted_buffer, &encrypted_length, + buffer, formatted_data_istream.gcount()); + protected_data_stream.write((const char *)encrypted_buffer, encrypted_length); + } + + // no EVP_EncrypteFinal, as this is a stream cipher + EVP_CIPHER_CTX_free(ctx); + + protected_data_stream.close(); + } + catch (...) + { + LOG_UNHANDLED_EXCEPTION("LLProtectedDataException(Error writing Protected Data Store)"); + // it's good practice to clean up any secure information on error + // (even though this file isn't really secure. Perhaps in the future + // it may be, however. + LLFile::remove(tmp_filename); + + if (ctx) + { + EVP_CIPHER_CTX_free(ctx); + } + + // EXP-1825 crash in LLSecAPIBasicHandler::_writeProtectedData() + // Decided throwing an exception here was overkill until we figure out why this happens + //LLTHROW(LLProtectedDataException("Error writing Protected Data Store")); + } + + try + { + // move the temporary file to the specified file location. + if((( (LLFile::isfile(mProtectedDataFilename) != 0) + && (LLFile::remove(mProtectedDataFilename) != 0))) + || (LLFile::rename(tmp_filename, mProtectedDataFilename))) + { + LL_WARNS() << "LLProtectedDataException(Could not overwrite protected data store)" << LL_ENDL; + LLFile::remove(tmp_filename); + + // EXP-1825 crash in LLSecAPIBasicHandler::_writeProtectedData() + // Decided throwing an exception here was overkill until we figure out why this happens + //LLTHROW(LLProtectedDataException("Could not overwrite protected data store")); + } + } + catch (...) + { + LOG_UNHANDLED_EXCEPTION(STRINGIZE("renaming '" << tmp_filename << "' to '" + << mProtectedDataFilename << "'")); + // it's good practice to clean up any secure information on error + // (even though this file isn't really secure. Perhaps in the future + // it may be, however). + LLFile::remove(tmp_filename); + + //crash in LLSecAPIBasicHandler::_writeProtectedData() + // Decided throwing an exception here was overkill until we figure out why this happens + //LLTHROW(LLProtectedDataException("Error writing Protected Data Store")); + } +} + +// instantiate a certificate from a pem string +LLPointer LLSecAPIBasicHandler::getCertificate(const std::string& pem_cert) +{ + LLPointer result = new LLBasicCertificate(pem_cert); + return result; +} + + + +// instiate a certificate from an openssl X509 structure +LLPointer LLSecAPIBasicHandler::getCertificate(X509* openssl_cert) +{ + LLPointer result = new LLBasicCertificate(openssl_cert); + return result; +} + +// instantiate a chain from an X509_STORE_CTX +LLPointer LLSecAPIBasicHandler::getCertificateChain(X509_STORE_CTX* chain) +{ + LLPointer result = new LLBasicCertificateChain(chain); + return result; +} + +// instantiate a cert store given it's id. if a persisted version +// exists, it'll be loaded. If not, one will be created (but not +// persisted) +LLPointer LLSecAPIBasicHandler::getCertificateStore(const std::string& store_id) +{ + return mStore; +} + +// retrieve protected data +LLSD LLSecAPIBasicHandler::getProtectedData(const std::string& data_type, + const std::string& data_id) +{ + + if (mProtectedDataMap.has(data_type) && + mProtectedDataMap[data_type].isMap() && + mProtectedDataMap[data_type].has(data_id)) + { + return mProtectedDataMap[data_type][data_id]; + } + + return LLSD(); +} + +void LLSecAPIBasicHandler::deleteProtectedData(const std::string& data_type, + const std::string& data_id) +{ + if (mProtectedDataMap.has(data_type) && + mProtectedDataMap[data_type].isMap() && + mProtectedDataMap[data_type].has(data_id)) + { + mProtectedDataMap[data_type].erase(data_id); + } +} + + +// +// persist data in a protected store +// +void LLSecAPIBasicHandler::setProtectedData(const std::string& data_type, + const std::string& data_id, + const LLSD& data) +{ + if (!mProtectedDataMap.has(data_type) || !mProtectedDataMap[data_type].isMap()) { + mProtectedDataMap[data_type] = LLSD::emptyMap(); + } + + mProtectedDataMap[data_type][data_id] = data; +} + +// persist data in a protected store's map +void LLSecAPIBasicHandler::addToProtectedMap(const std::string& data_type, + const std::string& data_id, + const std::string& map_elem, + const LLSD& data) +{ + if (!mProtectedDataMap.has(data_type) || !mProtectedDataMap[data_type].isMap()) { + mProtectedDataMap[data_type] = LLSD::emptyMap(); + } + + if (!mProtectedDataMap[data_type].has(data_id) || !mProtectedDataMap[data_type][data_id].isMap()) { + mProtectedDataMap[data_type][data_id] = LLSD::emptyMap(); + } + + mProtectedDataMap[data_type][data_id][map_elem] = data; +} + +// remove data from protected store's map +void LLSecAPIBasicHandler::removeFromProtectedMap(const std::string& data_type, + const std::string& data_id, + const std::string& map_elem) +{ + if (mProtectedDataMap.has(data_type) && + mProtectedDataMap[data_type].isMap() && + mProtectedDataMap[data_type].has(data_id) && + mProtectedDataMap[data_type][data_id].isMap() && + mProtectedDataMap[data_type][data_id].has(map_elem)) + { + mProtectedDataMap[data_type][data_id].erase(map_elem); + } +} + +void LLSecAPIBasicHandler::syncProtectedMap() +{ + // TODO - consider unifing these functions + _writeProtectedData(); +} +// +// Create a credential object from an identifier and authenticator. credentials are +// per grid. +LLPointer LLSecAPIBasicHandler::createCredential(const std::string& grid, + const LLSD& identifier, + const LLSD& authenticator) +{ + LLPointer result = new LLSecAPIBasicCredential(grid); + result->setCredentialData(identifier, authenticator); + return result; +} + +// Load a credential from default credential store, given the grid +LLPointer LLSecAPIBasicHandler::loadCredential(const std::string& grid) +{ + LLSD credential = getProtectedData(DEFAULT_CREDENTIAL_STORAGE, grid); + LLPointer result = new LLSecAPIBasicCredential(grid); + if(credential.isMap() && + credential.has("identifier")) + { + + LLSD identifier = credential["identifier"]; + LLSD authenticator; + if (credential.has("authenticator")) + { + authenticator = credential["authenticator"]; + } + result->setCredentialData(identifier, authenticator); + } + else + { + // credential was not in protected storage, so pull the credential + // from the legacy store. + std::string first_name = gSavedSettings.getString("FirstName"); + std::string last_name = gSavedSettings.getString("LastName"); + + if ((first_name != "") && + (last_name != "")) + { + LLSD identifier = LLSD::emptyMap(); + LLSD authenticator; + identifier["type"] = "agent"; + identifier["first_name"] = first_name; + identifier["last_name"] = last_name; + + std::string legacy_password = _legacyLoadPassword(); + if (legacy_password.length() > 0) + { + authenticator = LLSD::emptyMap(); + authenticator["type"] = "hash"; + authenticator["algorithm"] = "md5"; + authenticator["secret"] = legacy_password; + } + result->setCredentialData(identifier, authenticator); + } + } + return result; +} + +// Save the credential to the credential store. Save the authenticator also if requested. +// That feature is used to implement the 'remember password' functionality. +void LLSecAPIBasicHandler::saveCredential(LLPointer cred, bool save_authenticator) +{ + LLSD credential = LLSD::emptyMap(); + credential["identifier"] = cred->getIdentifier(); + if (save_authenticator) + { + credential["authenticator"] = cred->getAuthenticator(); + } + LL_DEBUGS("SECAPI") << "Saving Credential " << cred->getGrid() << ":" << cred->userID() << " " << save_authenticator << LL_ENDL; + setProtectedData(DEFAULT_CREDENTIAL_STORAGE, cred->getGrid(), credential); + //*TODO: If we're saving Agni credentials, should we write the + // credentials to the legacy password.dat/etc? + _writeProtectedData(); +} + +// Remove a credential from the credential store. +void LLSecAPIBasicHandler::deleteCredential(LLPointer cred) +{ + LLSD undefVal; + deleteProtectedData(DEFAULT_CREDENTIAL_STORAGE, cred->getGrid()); + cred->setCredentialData(undefVal, undefVal); + _writeProtectedData(); +} + +// has map of credentials declared as specific storage +bool LLSecAPIBasicHandler::hasCredentialMap(const std::string& storage, const std::string& grid) +{ + if (storage == DEFAULT_CREDENTIAL_STORAGE) + { + LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL; + } + + LLSD credential = getProtectedData(storage, grid); + + return credential.isMap(); +} + +// returns true if map is empty or does not exist +bool LLSecAPIBasicHandler::emptyCredentialMap(const std::string& storage, const std::string& grid) +{ + if (storage == DEFAULT_CREDENTIAL_STORAGE) + { + LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL; + } + + LLSD credential = getProtectedData(storage, grid); + + return !credential.isMap() || credential.size() == 0; +} + +// Load map of credentials from specified credential store, given the grid +void LLSecAPIBasicHandler::loadCredentialMap(const std::string& storage, const std::string& grid, credential_map_t& credential_map) +{ + if (storage == DEFAULT_CREDENTIAL_STORAGE) + { + LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL; + } + + LLSD credential = getProtectedData(storage, grid); + if (credential.isMap()) + { + LLSD::map_const_iterator crd_it = credential.beginMap(); + for (; crd_it != credential.endMap(); crd_it++) + { + LLSD::String name = crd_it->first; + const LLSD &link_map = crd_it->second; + LLPointer result = new LLSecAPIBasicCredential(grid); + if (link_map.has("identifier")) + { + LLSD identifier = link_map["identifier"]; + LLSD authenticator; + if (link_map.has("authenticator")) + { + authenticator = link_map["authenticator"]; + } + result->setCredentialData(identifier, authenticator); + } + credential_map[name] = result; + } + } +} + +LLPointer LLSecAPIBasicHandler::loadFromCredentialMap(const std::string& storage, const std::string& grid, const std::string& userkey) +{ + if (storage == DEFAULT_CREDENTIAL_STORAGE) + { + LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL; + } + + LLPointer result = new LLSecAPIBasicCredential(grid); + + LLSD credential = getProtectedData(storage, grid); + if (credential.isMap() && credential.has(userkey) && credential[userkey].has("identifier")) + { + LLSD identifier = credential[userkey]["identifier"]; + LLSD authenticator; + if (credential[userkey].has("authenticator")) + { + authenticator = credential[userkey]["authenticator"]; + } + result->setCredentialData(identifier, authenticator); + } + + return result; +} + +// add item to map of credentials from specific storage +void LLSecAPIBasicHandler::addToCredentialMap(const std::string& storage, LLPointer cred, bool save_authenticator) +{ + if (storage == DEFAULT_CREDENTIAL_STORAGE) + { + LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL; + } + + std::string user_id = cred->userID(); + LLSD credential = LLSD::emptyMap(); + credential["identifier"] = cred->getIdentifier(); + if (save_authenticator) + { + credential["authenticator"] = cred->getAuthenticator(); + } + LL_DEBUGS("SECAPI") << "Saving Credential " << cred->getGrid() << ":" << cred->userID() << " " << save_authenticator << LL_ENDL; + addToProtectedMap(storage, cred->getGrid(), user_id, credential); + + _writeProtectedData(); +} + +// remove item from map of credentials from specific storage +void LLSecAPIBasicHandler::removeFromCredentialMap(const std::string& storage, LLPointer cred) +{ + if (storage == DEFAULT_CREDENTIAL_STORAGE) + { + LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL; + } + + LLSD undefVal; + removeFromProtectedMap(storage, cred->getGrid(), cred->userID()); + cred->setCredentialData(undefVal, undefVal); + _writeProtectedData(); +} + +// remove item from map of credentials from specific storage +void LLSecAPIBasicHandler::removeFromCredentialMap(const std::string& storage, const std::string& grid, const std::string& userkey) +{ + if (storage == DEFAULT_CREDENTIAL_STORAGE) + { + LL_ERRS() << "Storing maps in default, single-items storage is not allowed" << LL_ENDL; + } + + LLSD undefVal; + LLPointer cred = loadFromCredentialMap(storage, grid, userkey); + removeFromProtectedMap(storage, grid, userkey); + cred->setCredentialData(undefVal, undefVal); + _writeProtectedData(); +} + +// remove item from map of credentials from specific storage +void LLSecAPIBasicHandler::removeCredentialMap(const std::string& storage, const std::string& grid) +{ + deleteProtectedData(storage, grid); + _writeProtectedData(); +} + +// load the legacy hash for agni, and decrypt it given the +// mac address +std::string LLSecAPIBasicHandler::_legacyLoadPassword() +{ + const S32 HASHED_LENGTH = 32; + std::vector buffer(HASHED_LENGTH); + llifstream password_file(mLegacyPasswordPath.c_str(), llifstream::binary); + + if(password_file.fail()) + { + return std::string(""); + } + + password_file.read((char*)&buffer[0], buffer.size()); + if(password_file.gcount() != buffer.size()) + { + return std::string(""); + } + + // Decipher with MAC address + unsigned char unique_id[MAC_ADDRESS_BYTES]; + LLMachineID::getUniqueID(unique_id, sizeof(unique_id)); + LLXORCipher cipher(unique_id, sizeof(unique_id)); + cipher.decrypt(&buffer[0], buffer.size()); + + return std::string((const char*)&buffer[0], buffer.size()); +} + + +// return an identifier for the user +std::string LLSecAPIBasicCredential::userID() const +{ + if (!mIdentifier.isMap()) + { + return mGrid + "(null)"; + } + else if ((std::string)mIdentifier["type"] == "agent") + { + std::string id = (std::string)mIdentifier["first_name"] + "_" + (std::string)mIdentifier["last_name"]; + LLStringUtil::toLower(id); + return id; + } + else if ((std::string)mIdentifier["type"] == "account") + { + std::string id = (std::string)mIdentifier["account_name"]; + LLStringUtil::toLower(id); + return id; + } + + return "unknown"; +} + +// return a printable user identifier +std::string LLSecAPIBasicCredential::asString() const +{ + if (!mIdentifier.isMap()) + { + return mGrid + ":(null)"; + } + else if ((std::string)mIdentifier["type"] == "agent") + { + return mGrid + ":" + (std::string)mIdentifier["first_name"] + " " + (std::string)mIdentifier["last_name"]; + } + else if ((std::string)mIdentifier["type"] == "account") + { + return mGrid + ":" + (std::string)mIdentifier["account_name"]; + } + + return mGrid + ":(unknown type)"; +} + + +bool valueCompareLLSD(const LLSD& lhs, const LLSD& rhs) +{ + if (lhs.type() != rhs.type()) + { + return false; + } + if (lhs.isMap()) + { + // iterate through the map, verifying the right hand side has all of the + // values that the left hand side has. + for (LLSD::map_const_iterator litt = lhs.beginMap(); + litt != lhs.endMap(); + litt++) + { + if (!rhs.has(litt->first)) + { + return false; + } + } + + // Now validate that the left hand side has everything the + // right hand side has, and that the values are equal. + for (LLSD::map_const_iterator ritt = rhs.beginMap(); + ritt != rhs.endMap(); + ritt++) + { + if (!lhs.has(ritt->first)) + { + return false; + } + if (!valueCompareLLSD(lhs[ritt->first], ritt->second)) + { + return false; + } + } + return true; + } + else if (lhs.isArray()) + { + LLSD::array_const_iterator ritt = rhs.beginArray(); + // iterate through the array, comparing + for (LLSD::array_const_iterator litt = lhs.beginArray(); + litt != lhs.endArray(); + litt++) + { + if (!valueCompareLLSD(*ritt, *litt)) + { + return false; + } + ritt++; + } + + return (ritt == rhs.endArray()); + } + else + { + // simple type, compare as string + return (lhs.asString() == rhs.asString()); + } + +} diff --git a/indra/newview/llselectmgr.cpp b/indra/newview/llselectmgr.cpp index 4a82bd175e..b7fc2ef123 100644 --- a/indra/newview/llselectmgr.cpp +++ b/indra/newview/llselectmgr.cpp @@ -1,8616 +1,8616 @@ -/** - * @file llselectmgr.cpp - * @brief A manager for selected objects and faces. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -// file include -#define LLSELECTMGR_CPP -#include "llselectmgr.h" -#include "llmaterialmgr.h" - -// library includes -#include "llcachename.h" -#include "llavatarnamecache.h" -#include "lldbstrings.h" -#include "llgl.h" -#include "llmediaentry.h" -#include "llrender.h" -#include "llnotifications.h" -#include "llpermissions.h" -#include "llpermissionsflags.h" -#include "lltrans.h" -#include "llundo.h" -#include "lluuid.h" -#include "llvolume.h" -#include "llcontrolavatar.h" -#include "message.h" -#include "object_flags.h" -#include "llquaternion.h" - -// viewer includes -#include "llagent.h" -#include "llagentcamera.h" -#include "llattachmentsmgr.h" -#include "llviewerwindow.h" -#include "lldrawable.h" -#include "llfloaterinspect.h" -#include "llfloaterreporter.h" -#include "llfloaterreg.h" -#include "llfloatertools.h" -#include "llframetimer.h" -#include "llfocusmgr.h" -#include "llgltfmateriallist.h" -#include "llhudeffecttrail.h" -#include "llhudmanager.h" -#include "llinventorymodel.h" -#include "llmenugl.h" -#include "llmeshrepository.h" -#include "llmutelist.h" -#include "llnotificationsutil.h" -#include "llsidepaneltaskinfo.h" -#include "llslurl.h" -#include "llstatusbar.h" -#include "llsurface.h" -#include "lltool.h" -#include "lltooldraganddrop.h" -#include "lltoolmgr.h" -#include "lltoolpie.h" -#include "llui.h" -#include "llviewercamera.h" -#include "llviewercontrol.h" -#include "llviewertexturelist.h" -#include "llviewermedia.h" -#include "llviewermediafocus.h" -#include "llviewermenu.h" -#include "llviewerobject.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llviewerstats.h" -#include "llvoavatarself.h" -#include "llvovolume.h" -#include "pipeline.h" -#include "llviewershadermgr.h" -#include "llpanelface.h" -#include "llglheaders.h" -#include "llinventoryobserver.h" - -LLViewerObject* getSelectedParentObject(LLViewerObject *object) ; -// -// Consts -// - -const F32 SILHOUETTE_UPDATE_THRESHOLD_SQUARED = 0.02f; -const S32 MAX_SILS_PER_FRAME = 50; -const S32 MAX_OBJECTS_PER_PACKET = 254; -// For linked sets -const S32 MAX_CHILDREN_PER_TASK = 255; - -// -// Globals -// - -//bool gDebugSelectMgr = false; - -//bool gHideSelectedObjects = false; -//bool gAllowSelectAvatar = false; - -bool LLSelectMgr::sRectSelectInclusive = true; -bool LLSelectMgr::sRenderHiddenSelections = true; -bool LLSelectMgr::sRenderLightRadius = false; -F32 LLSelectMgr::sHighlightThickness = 0.f; -F32 LLSelectMgr::sHighlightUScale = 0.f; -F32 LLSelectMgr::sHighlightVScale = 0.f; -F32 LLSelectMgr::sHighlightAlpha = 0.f; -F32 LLSelectMgr::sHighlightAlphaTest = 0.f; -F32 LLSelectMgr::sHighlightUAnim = 0.f; -F32 LLSelectMgr::sHighlightVAnim = 0.f; -LLColor4 LLSelectMgr::sSilhouetteParentColor; -LLColor4 LLSelectMgr::sSilhouetteChildColor; -LLColor4 LLSelectMgr::sHighlightInspectColor; -LLColor4 LLSelectMgr::sHighlightParentColor; -LLColor4 LLSelectMgr::sHighlightChildColor; -LLColor4 LLSelectMgr::sContextSilhouetteColor; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// struct LLDeRezInfo -// -// Used to keep track of important derez info. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -struct LLDeRezInfo -{ - EDeRezDestination mDestination; - LLUUID mDestinationID; - LLDeRezInfo(EDeRezDestination dest, const LLUUID& dest_id) : - mDestination(dest), mDestinationID(dest_id) {} -}; - -// -// Imports -// - -//----------------------------------------------------------------------------- -// ~LLSelectionCallbackData() -//----------------------------------------------------------------------------- - -LLSelectionCallbackData::LLSelectionCallbackData() -{ - LLSelectMgr *instance = LLSelectMgr::getInstance(); - LLObjectSelectionHandle selection = instance->getSelection(); - if (!selection->getNumNodes()) - { - return; - } - mSelectedObjects = new LLObjectSelection(); - - for (LLObjectSelection::iterator iter = selection->begin(); - iter != selection->end();) - { - LLObjectSelection::iterator curiter = iter++; - - LLSelectNode *nodep = *curiter; - LLViewerObject* objectp = nodep->getObject(); - - if (!objectp) - { - mSelectedObjects->mSelectType = SELECT_TYPE_WORLD; - } - else - { - LLSelectNode* new_nodep = new LLSelectNode(*nodep); - mSelectedObjects->addNode(new_nodep); - - if (objectp->isHUDAttachment()) - { - mSelectedObjects->mSelectType = SELECT_TYPE_HUD; - } - else if (objectp->isAttachment()) - { - mSelectedObjects->mSelectType = SELECT_TYPE_ATTACHMENT; - } - else - { - mSelectedObjects->mSelectType = SELECT_TYPE_WORLD; - } - } - } -} - - -// -// Functions -// - -void LLSelectMgr::cleanupGlobals() -{ - LLSelectMgr::getInstance()->clearSelections(); -} - -//----------------------------------------------------------------------------- -// LLSelectMgr() -//----------------------------------------------------------------------------- -LLSelectMgr::LLSelectMgr() - : mHideSelectedObjects(LLCachedControl(gSavedSettings, "HideSelectedObjects", false)), - mRenderHighlightSelections(LLCachedControl(gSavedSettings, "RenderHighlightSelections", true)), - mAllowSelectAvatar( LLCachedControl(gSavedSettings, "AllowSelectAvatar", false)), - mDebugSelectMgr(LLCachedControl(gSavedSettings, "DebugSelectMgr", false)) -{ - mTEMode = false; - mTextureChannel = LLRender::DIFFUSE_MAP; - mLastCameraPos.clearVec(); - - sHighlightThickness = gSavedSettings.getF32("SelectionHighlightThickness"); - sHighlightUScale = gSavedSettings.getF32("SelectionHighlightUScale"); - sHighlightVScale = gSavedSettings.getF32("SelectionHighlightVScale"); - sHighlightAlpha = gSavedSettings.getF32("SelectionHighlightAlpha") * 2; - sHighlightAlphaTest = gSavedSettings.getF32("SelectionHighlightAlphaTest"); - sHighlightUAnim = gSavedSettings.getF32("SelectionHighlightUAnim"); - sHighlightVAnim = gSavedSettings.getF32("SelectionHighlightVAnim"); - - sSilhouetteParentColor =LLUIColorTable::instance().getColor("SilhouetteParentColor"); - sSilhouetteChildColor = LLUIColorTable::instance().getColor("SilhouetteChildColor"); - sHighlightParentColor = LLUIColorTable::instance().getColor("HighlightParentColor"); - sHighlightChildColor = LLUIColorTable::instance().getColor("HighlightChildColor"); - sHighlightInspectColor = LLUIColorTable::instance().getColor("HighlightInspectColor"); - sContextSilhouetteColor = LLUIColorTable::instance().getColor("ContextSilhouetteColor")*0.5f; - - sRenderLightRadius = gSavedSettings.getBOOL("RenderLightRadius"); - - mRenderSilhouettes = true; - - mGridMode = GRID_MODE_WORLD; - gSavedSettings.setS32("GridMode", (S32)GRID_MODE_WORLD); - - mSelectedObjects = new LLObjectSelection(); - mHoverObjects = new LLObjectSelection(); - mHighlightedObjects = new LLObjectSelection(); - - mForceSelection = false; - mShowSelection = false; -} - - -//----------------------------------------------------------------------------- -// ~LLSelectMgr() -//----------------------------------------------------------------------------- -LLSelectMgr::~LLSelectMgr() -{ - clearSelections(); -} - -void LLSelectMgr::clearSelections() -{ - mHoverObjects->deleteAllNodes(); - mSelectedObjects->deleteAllNodes(); - mHighlightedObjects->deleteAllNodes(); - mRectSelectedObjects.clear(); - mGridObjects.deleteAllNodes(); - - LLPipeline::setRenderHighlightTextureChannel(LLRender::DIFFUSE_MAP); -} - -void LLSelectMgr::update() -{ - mSelectedObjects->cleanupNodes(); -} - -void LLSelectMgr::updateEffects() -{ - //keep reference grid objects active - struct f : public LLSelectedObjectFunctor - { - virtual bool apply(LLViewerObject* object) - { - LLDrawable* drawable = object->mDrawable; - if (drawable) - { - gPipeline.markMoved(drawable); - } - return true; - } - } func; - mGridObjects.applyToObjects(&func); - - if (mEffectsTimer.getElapsedTimeF32() > 1.f) - { - mSelectedObjects->updateEffects(); - mEffectsTimer.reset(); - } -} - -void LLSelectMgr::resetObjectOverrides() -{ - resetObjectOverrides(getSelection()); -} - -void LLSelectMgr::resetObjectOverrides(LLObjectSelectionHandle selected_handle) -{ - struct f : public LLSelectedNodeFunctor - { - f(bool a, LLSelectMgr* p) : mAvatarOverridesPersist(a), mManager(p) {} - bool mAvatarOverridesPersist; - LLSelectMgr* mManager; - virtual bool apply(LLSelectNode* node) - { - if (mAvatarOverridesPersist) - { - LLViewerObject* object = node->getObject(); - if (object && !object->getParent()) - { - LLVOAvatar* avatar = object->asAvatar(); - if (avatar) - { - mManager->mAvatarOverridesMap.emplace(avatar->getID(), AvatarPositionOverride(node->mLastPositionLocal, node->mLastRotation, object)); - } - } - } - node->mLastPositionLocal.setVec(0, 0, 0); - node->mLastRotation = LLQuaternion(); - node->mLastScale.setVec(0, 0, 0); - return true; - } - } func(mAllowSelectAvatar, this); - - selected_handle->applyToNodes(&func); -} - -void LLSelectMgr::overrideObjectUpdates() -{ - //override any position updates from simulator on objects being edited - struct f : public LLSelectedNodeFunctor - { - virtual bool apply(LLSelectNode* selectNode) - { - LLViewerObject* object = selectNode->getObject(); - if (object && object->permMove() && !object->isPermanentEnforced()) - { - if (!selectNode->mLastPositionLocal.isExactlyZero()) - { - object->setPosition(selectNode->mLastPositionLocal); - } - if (selectNode->mLastRotation != LLQuaternion()) - { - object->setRotation(selectNode->mLastRotation); - } - if (!selectNode->mLastScale.isExactlyZero()) - { - object->setScale(selectNode->mLastScale); - } - } - return true; - } - } func; - getSelection()->applyToNodes(&func); -} - -void LLSelectMgr::resetAvatarOverrides() -{ - mAvatarOverridesMap.clear(); -} - -void LLSelectMgr::overrideAvatarUpdates() -{ - if (mAvatarOverridesMap.size() == 0) - { - return; - } - - if (!mAllowSelectAvatar || !gFloaterTools) - { - resetAvatarOverrides(); - return; - } - - if (!gFloaterTools->getVisible() && getSelection()->isEmpty()) - { - // when user switches selection, floater is invisible and selection is empty - LLToolset *toolset = LLToolMgr::getInstance()->getCurrentToolset(); - if (toolset->isShowFloaterTools() - && toolset->isToolSelected(0)) // Pie tool - { - resetAvatarOverrides(); - return; - } - } - - // remove selected avatars from this list, - // but set object overrides to make sure avatar won't snap back - struct f : public LLSelectedNodeFunctor - { - f(LLSelectMgr* p) : mManager(p) {} - LLSelectMgr* mManager; - virtual bool apply(LLSelectNode* selectNode) - { - LLViewerObject* object = selectNode->getObject(); - if (object && !object->getParent()) - { - LLVOAvatar* avatar = object->asAvatar(); - if (avatar) - { - uuid_av_override_map_t::iterator iter = mManager->mAvatarOverridesMap.find(avatar->getID()); - if (iter != mManager->mAvatarOverridesMap.end()) - { - if (selectNode->mLastPositionLocal.isExactlyZero()) - { - selectNode->mLastPositionLocal = iter->second.mLastPositionLocal; - } - if (selectNode->mLastRotation == LLQuaternion()) - { - selectNode->mLastRotation = iter->second.mLastRotation; - } - mManager->mAvatarOverridesMap.erase(iter); - } - } - } - return true; - } - } func(this); - getSelection()->applyToNodes(&func); - - // Override avatar positions - uuid_av_override_map_t::iterator it = mAvatarOverridesMap.begin(); - while (it != mAvatarOverridesMap.end()) - { - if (it->second.mObject->isDead()) - { - it = mAvatarOverridesMap.erase(it); - } - else - { - if (!it->second.mLastPositionLocal.isExactlyZero()) - { - it->second.mObject->setPosition(it->second.mLastPositionLocal); - } - if (it->second.mLastRotation != LLQuaternion()) - { - it->second.mObject->setRotation(it->second.mLastRotation); - } - it++; - } - } -} - -//----------------------------------------------------------------------------- -// Select just the object, not any other group members. -//----------------------------------------------------------------------------- -LLObjectSelectionHandle LLSelectMgr::selectObjectOnly(LLViewerObject* object, S32 face) -{ - llassert( object ); - - //remember primary object - mSelectedObjects->mPrimaryObject = object; - - // Don't add an object that is already in the list - if (object->isSelected() ) { - // make sure point at position is updated - updatePointAt(); - gEditMenuHandler = this; - return NULL; - } - - if (!canSelectObject(object)) - { - //make_ui_sound("UISndInvalidOp"); - return NULL; - } - - // LL_INFOS() << "Adding object to selected object list" << LL_ENDL; - - // Place it in the list and tag it. - // This will refresh dialogs. - addAsIndividual(object, face); - - // Stop the object from moving (this anticipates changes on the - // simulator in LLTask::userSelect) - // *FIX: shouldn't zero out these either - object->setVelocity(LLVector3::zero); - object->setAcceleration(LLVector3::zero); - //object->setAngularVelocity(LLVector3::zero); - object->resetRot(); - - // Always send to simulator, so you get a copy of the - // permissions structure back. - gMessageSystem->newMessageFast(_PREHASH_ObjectSelect); - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - gMessageSystem->nextBlockFast(_PREHASH_ObjectData); - gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, object->getLocalID() ); - LLViewerRegion* regionp = object->getRegion(); - gMessageSystem->sendReliable( regionp->getHost()); - - updatePointAt(); - updateSelectionCenter(); - saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); - - // have selection manager handle edit menu immediately after - // user selects an object - if (mSelectedObjects->getObjectCount()) - { - gEditMenuHandler = this; - } - - return mSelectedObjects; -} - -//----------------------------------------------------------------------------- -// Select the object, parents and children. -//----------------------------------------------------------------------------- -LLObjectSelectionHandle LLSelectMgr::selectObjectAndFamily(LLViewerObject* obj, bool add_to_end, bool ignore_select_owned) -{ - llassert( obj ); - - //remember primary object - mSelectedObjects->mPrimaryObject = obj; - - // This may be incorrect if things weren't family selected before... - djs 07/08/02 - // Don't add an object that is already in the list - if (obj->isSelected() ) - { - // make sure pointat position is updated - updatePointAt(); - gEditMenuHandler = this; - return NULL; - } - - if (!canSelectObject(obj,ignore_select_owned)) - { - //make_ui_sound("UISndInvalidOp"); - return NULL; - } - - // Since we're selecting a family, start at the root, but - // don't include an avatar. - LLViewerObject* root = obj; - - while(!root->isAvatar() && root->getParent()) - { - LLViewerObject* parent = (LLViewerObject*)root->getParent(); - if (parent->isAvatar()) - { - break; - } - root = parent; - } - - // Collect all of the objects - std::vector objects; - - root->addThisAndNonJointChildren(objects); - addAsFamily(objects, add_to_end); - - updateSelectionCenter(); - saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); - updatePointAt(); - - dialog_refresh_all(); - - // Always send to simulator, so you get a copy of the permissions - // structure back. - sendSelect(); - - // Stop the object from moving (this anticipates changes on the - // simulator in LLTask::userSelect) - root->setVelocity(LLVector3::zero); - root->setAcceleration(LLVector3::zero); - //root->setAngularVelocity(LLVector3::zero); - root->resetRot(); - - // leave component mode - if (gSavedSettings.getBOOL("EditLinkedParts")) - { - gSavedSettings.setBOOL("EditLinkedParts", false); - promoteSelectionToRoot(); - } - - // have selection manager handle edit menu immediately after - // user selects an object - if (mSelectedObjects->getObjectCount()) - { - gEditMenuHandler = this; - } - - return mSelectedObjects; -} - -//----------------------------------------------------------------------------- -// Select the object, parents and children. -//----------------------------------------------------------------------------- -LLObjectSelectionHandle LLSelectMgr::selectObjectAndFamily(const std::vector& object_list, - bool send_to_sim) -{ - // Collect all of the objects, children included - std::vector objects; - - //clear primary object (no primary object) - mSelectedObjects->mPrimaryObject = NULL; - - if (object_list.size() < 1) - { - return NULL; - } - - // NOTE -- we add the objects in REVERSE ORDER - // to preserve the order in the mSelectedObjects list - for (std::vector::const_reverse_iterator riter = object_list.rbegin(); - riter != object_list.rend(); ++riter) - { - LLViewerObject *object = *riter; - - llassert( object ); - - if (!canSelectObject(object)) continue; - - object->addThisAndNonJointChildren(objects); - addAsFamily(objects); - - // Stop the object from moving (this anticipates changes on the - // simulator in LLTask::userSelect) - object->setVelocity(LLVector3::zero); - object->setAcceleration(LLVector3::zero); - //object->setAngularVelocity(LLVector3::zero); - object->resetRot(); - } - - updateSelectionCenter(); - saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); - updatePointAt(); - dialog_refresh_all(); - - // Almost always send to simulator, so you get a copy of the permissions - // structure back. - // JC: The one case where you don't want to do this is if you're selecting - // all the objects on a sim. - if (send_to_sim) - { - sendSelect(); - } - - // leave component mode - if (gSavedSettings.getBOOL("EditLinkedParts")) - { - gSavedSettings.setBOOL("EditLinkedParts", false); - promoteSelectionToRoot(); - } - - // have selection manager handle edit menu immediately after - // user selects an object - if (mSelectedObjects->getObjectCount()) - { - gEditMenuHandler = this; - } - - return mSelectedObjects; -} - -// Use for when the simulator kills an object. This version also -// handles informing the current tool of the object's deletion. -// -// Caller needs to call dialog_refresh_all if necessary. -bool LLSelectMgr::removeObjectFromSelections(const LLUUID &id) -{ - bool object_found = false; - LLTool *tool = NULL; - - tool = LLToolMgr::getInstance()->getCurrentTool(); - - // It's possible that the tool is editing an object that is not selected - LLViewerObject* tool_editing_object = tool->getEditingObject(); - if( tool_editing_object && tool_editing_object->mID == id) - { - tool->stopEditing(); - object_found = true; - } - - // Iterate through selected objects list and kill the object - if( !object_found ) - { - for (LLObjectSelection::iterator iter = getSelection()->begin(); - iter != getSelection()->end(); ) - { - LLObjectSelection::iterator curiter = iter++; - LLViewerObject* object = (*curiter)->getObject(); - if (object->mID == id) - { - if (tool) - { - tool->stopEditing(); - } - - // lose the selection, don't tell simulator, it knows - deselectObjectAndFamily(object, false); - object_found = true; - break; // must break here, may have removed multiple objects from list - } - else if (object->isAvatar() && object->getParent() && ((LLViewerObject*)object->getParent())->mID == id) - { - // It's possible the item being removed has an avatar sitting on it - // So remove the avatar that is sitting on the object. - deselectObjectAndFamily(object, false); - break; // must break here, may have removed multiple objects from list - } - } - } - - return object_found; -} - -bool LLSelectMgr::linkObjects() -{ - if (!LLSelectMgr::getInstance()->selectGetAllRootsValid()) - { - LLNotificationsUtil::add("UnableToLinkWhileDownloading"); - return true; - } - - S32 object_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount(); - if (object_count > MAX_CHILDREN_PER_TASK + 1) - { - LLSD args; - args["COUNT"] = llformat("%d", object_count); - int max = MAX_CHILDREN_PER_TASK+1; - args["MAX"] = llformat("%d", max); - LLNotificationsUtil::add("UnableToLinkObjects", args); - return true; - } - - if (LLSelectMgr::getInstance()->getSelection()->getRootObjectCount() < 2) - { - LLNotificationsUtil::add("CannotLinkIncompleteSet"); - return true; - } - - if (!LLSelectMgr::getInstance()->selectGetRootsModify()) - { - LLNotificationsUtil::add("CannotLinkModify"); - return true; - } - - if (!LLSelectMgr::getInstance()->selectGetRootsNonPermanentEnforced()) - { - LLNotificationsUtil::add("CannotLinkPermanent"); - return true; - } - - LLUUID owner_id; - std::string owner_name; - if (!LLSelectMgr::getInstance()->selectGetOwner(owner_id, owner_name)) - { - // we don't actually care if you're the owner, but novices are - // the most likely to be stumped by this one, so offer the - // easiest and most likely solution. - LLNotificationsUtil::add("CannotLinkDifferentOwners"); - return true; - } - - if (!LLSelectMgr::getInstance()->selectGetSameRegion()) - { - LLNotificationsUtil::add("CannotLinkAcrossRegions"); - return true; - } - - LLSelectMgr::getInstance()->sendLink(); - - return true; -} - -bool LLSelectMgr::unlinkObjects() -{ - S32 min_objects_for_confirm = gSavedSettings.getS32("MinObjectsForUnlinkConfirm"); - S32 unlink_object_count = mSelectedObjects->getObjectCount(); // clears out nodes with NULL objects - if (unlink_object_count >= min_objects_for_confirm - && unlink_object_count > mSelectedObjects->getRootObjectCount()) - { - // total count > root count means that there are childer inside and that there are linksets that will be unlinked - LLNotificationsUtil::add("ConfirmUnlink", LLSD(), LLSD(), boost::bind(&LLSelectMgr::confirmUnlinkObjects, this, _1, _2)); - return true; - } - - LLSelectMgr::getInstance()->sendDelink(); - return true; -} - -void LLSelectMgr::confirmUnlinkObjects(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - // if Cancel pressed - if (option == 1) - { - return; - } - - LLSelectMgr::getInstance()->sendDelink(); - return; -} - -// in order to link, all objects must have the same owner, and the -// agent must have the ability to modify all of the objects. However, -// we're not answering that question with this method. The question -// we're answering is: does the user have a reasonable expectation -// that a link operation should work? If so, return true, false -// otherwise. this allows the handle_link method to more finely check -// the selection and give an error message when the uer has a -// reasonable expectation for the link to work, but it will fail. -// -// For animated objects, there's additional check that if the -// selection includes at least one animated object, the total mesh -// triangle count cannot exceed the designated limit. -bool LLSelectMgr::enableLinkObjects() -{ - bool new_value = false; - // check if there are at least 2 objects selected, and that the - // user can modify at least one of the selected objects. - - // in component mode, can't link - if (!gSavedSettings.getBOOL("EditLinkedParts")) - { - if(LLSelectMgr::getInstance()->selectGetAllRootsValid() && LLSelectMgr::getInstance()->getSelection()->getRootObjectCount() >= 2) - { - struct f : public LLSelectedObjectFunctor - { - virtual bool apply(LLViewerObject* object) - { - LLViewerObject *root_object = (object == NULL) ? NULL : object->getRootEdit(); - return object->permModify() && !object->isPermanentEnforced() && - ((root_object == NULL) || !root_object->isPermanentEnforced()); - } - } func; - const bool firstonly = true; - new_value = LLSelectMgr::getInstance()->getSelection()->applyToRootObjects(&func, firstonly); - } - } - if (!LLSelectMgr::getInstance()->getSelection()->checkAnimatedObjectLinkable()) - { - new_value = false; - } - return new_value; -} - -bool LLSelectMgr::enableUnlinkObjects() -{ - LLViewerObject* first_editable_object = LLSelectMgr::getInstance()->getSelection()->getFirstEditableObject(); - LLViewerObject *root_object = (first_editable_object == NULL) ? NULL : first_editable_object->getRootEdit(); - - bool new_value = LLSelectMgr::getInstance()->selectGetAllRootsValid() && - first_editable_object && - !first_editable_object->isAttachment() && !first_editable_object->isPermanentEnforced() && - ((root_object == NULL) || !root_object->isPermanentEnforced()); - - return new_value; -} - -void LLSelectMgr::deselectObjectAndFamily(LLViewerObject* object, bool send_to_sim, bool include_entire_object) -{ - // bail if nothing selected or if object wasn't selected in the first place - if(!object) return; - if(!object->isSelected()) return; - - // Collect all of the objects, and remove them - std::vector objects; - - if (include_entire_object) - { - // Since we're selecting a family, start at the root, but - // don't include an avatar. - LLViewerObject* root = object; - - while(!root->isAvatar() && root->getParent()) - { - LLViewerObject* parent = (LLViewerObject*)root->getParent(); - if (parent->isAvatar()) - { - break; - } - root = parent; - } - - object = root; - } - else - { - object = (LLViewerObject*)object->getRoot(); - } - - object->addThisAndAllChildren(objects); - remove(objects); - - if (!send_to_sim) return; - - //----------------------------------------------------------- - // Inform simulator of deselection - //----------------------------------------------------------- - LLViewerRegion* regionp = object->getRegion(); - - bool start_new_message = true; - S32 select_count = 0; - - LLMessageSystem* msg = gMessageSystem; - for (U32 i = 0; i < objects.size(); i++) - { - if (start_new_message) - { - msg->newMessageFast(_PREHASH_ObjectDeselect); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - select_count++; - start_new_message = false; - } - - msg->nextBlockFast(_PREHASH_ObjectData); - msg->addU32Fast(_PREHASH_ObjectLocalID, (objects[i])->getLocalID()); - select_count++; - - // Zap the angular velocity, as the sim will set it to zero - objects[i]->setAngularVelocity( 0,0,0 ); - objects[i]->setVelocity( 0,0,0 ); - - if(msg->isSendFull(NULL) || select_count >= MAX_OBJECTS_PER_PACKET) - { - msg->sendReliable(regionp->getHost() ); - select_count = 0; - start_new_message = true; - } - } - - if (!start_new_message) - { - msg->sendReliable(regionp->getHost() ); - } - - updatePointAt(); - updateSelectionCenter(); -} - -void LLSelectMgr::deselectObjectOnly(LLViewerObject* object, bool send_to_sim) -{ - // bail if nothing selected or if object wasn't selected in the first place - if (!object) return; - if (!object->isSelected() ) return; - - // Zap the angular velocity, as the sim will set it to zero - object->setAngularVelocity( 0,0,0 ); - object->setVelocity( 0,0,0 ); - - if (send_to_sim) - { - LLViewerRegion* region = object->getRegion(); - gMessageSystem->newMessageFast(_PREHASH_ObjectDeselect); - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - gMessageSystem->nextBlockFast(_PREHASH_ObjectData); - gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, object->getLocalID() ); - gMessageSystem->sendReliable(region->getHost()); - } - - // This will refresh dialogs. - remove( object ); - - updatePointAt(); - updateSelectionCenter(); -} - - -//----------------------------------------------------------------------------- -// addAsFamily -//----------------------------------------------------------------------------- - -void LLSelectMgr::addAsFamily(std::vector& objects, bool add_to_end) -{ - for (std::vector::iterator iter = objects.begin(); - iter != objects.end(); ++iter) - { - LLViewerObject* objectp = *iter; - - // Can't select yourself - if (objectp->mID == gAgentID - && !mAllowSelectAvatar) - { - continue; - } - - if (!objectp->isSelected()) - { - LLSelectNode *nodep = new LLSelectNode(objectp, true); - if (add_to_end) - { - mSelectedObjects->addNodeAtEnd(nodep); - } - else - { - mSelectedObjects->addNode(nodep); - } - objectp->setSelected(true); - - if (objectp->getNumTEs() > 0) - { - nodep->selectAllTEs(true); - objectp->setAllTESelected(true); - } - else - { - // object has no faces, so don't mess with faces - } - } - else - { - // we want this object to be selected for real - // so clear transient flag - LLSelectNode* select_node = mSelectedObjects->findNode(objectp); - if (select_node) - { - select_node->setTransient(false); - } - } - } - saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); -} - -//----------------------------------------------------------------------------- -// addAsIndividual() - a single object, face, etc -//----------------------------------------------------------------------------- -void LLSelectMgr::addAsIndividual(LLViewerObject *objectp, S32 face, bool undoable) -{ - // check to see if object is already in list - LLSelectNode *nodep = mSelectedObjects->findNode(objectp); - - // if not in list, add it - if (!nodep) - { - nodep = new LLSelectNode(objectp, true); - mSelectedObjects->addNode(nodep); - llassert_always(nodep->getObject()); - } - else - { - // make this a full-fledged selection - nodep->setTransient(false); - // Move it to the front of the list - mSelectedObjects->moveNodeToFront(nodep); - } - - // Make sure the object is tagged as selected - objectp->setSelected( true ); - - // And make sure we don't consider it as part of a family - nodep->mIndividualSelection = true; - - // Handle face selection - if (objectp->getNumTEs() <= 0) - { - // object has no faces, so don't do anything - } - else if (face == SELECT_ALL_TES) - { - nodep->selectAllTEs(true); - objectp->setAllTESelected(true); - } - else if (0 <= face && face < SELECT_MAX_TES) - { - nodep->selectTE(face, true); - objectp->setTESelected(face, true); - } - else - { - LL_ERRS() << "LLSelectMgr::add face " << face << " out-of-range" << LL_ENDL; - return; - } - - saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); - updateSelectionCenter(); - dialog_refresh_all(); -} - - -LLObjectSelectionHandle LLSelectMgr::setHoverObject(LLViewerObject *objectp, S32 face) -{ - if (!objectp) - { - mHoverObjects->deleteAllNodes(); - return NULL; - } - - // Can't select yourself - if (objectp->mID == gAgentID) - { - mHoverObjects->deleteAllNodes(); - return NULL; - } - - // Can't select land - if (objectp->getPCode() == LLViewerObject::LL_VO_SURFACE_PATCH) - { - mHoverObjects->deleteAllNodes(); - return NULL; - } - - mHoverObjects->mPrimaryObject = objectp; - - objectp = objectp->getRootEdit(); - - // is the requested object the same as the existing hover object root? - // NOTE: there is only ever one linked set in mHoverObjects - if (mHoverObjects->getFirstRootObject() != objectp) - { - - // Collect all of the objects - std::vector objects; - objectp = objectp->getRootEdit(); - objectp->addThisAndNonJointChildren(objects); - - mHoverObjects->deleteAllNodes(); - for (std::vector::iterator iter = objects.begin(); - iter != objects.end(); ++iter) - { - LLViewerObject* cur_objectp = *iter; - if(!cur_objectp || cur_objectp->isDead()) - { - continue; - } - LLSelectNode* nodep = new LLSelectNode(cur_objectp, false); - nodep->selectTE(face, true); - mHoverObjects->addNodeAtEnd(nodep); - } - - requestObjectPropertiesFamily(objectp); - } - - return mHoverObjects; -} - -LLSelectNode *LLSelectMgr::getHoverNode() -{ - return mHoverObjects->getFirstRootNode(); -} - -LLSelectNode *LLSelectMgr::getPrimaryHoverNode() -{ - return mHoverObjects->mSelectNodeMap[mHoverObjects->mPrimaryObject]; -} - -void LLSelectMgr::highlightObjectOnly(LLViewerObject* objectp) -{ - if (!objectp) - { - return; - } - - if (objectp->getPCode() != LL_PCODE_VOLUME) - { - return; - } - - if ((gSavedSettings.getBOOL("SelectOwnedOnly") && !objectp->permYouOwner()) - || (gSavedSettings.getBOOL("SelectMovableOnly") && (!objectp->permMove() || objectp->isPermanentEnforced()))) - { - // only select my own objects - return; - } - - mRectSelectedObjects.insert(objectp); -} - -void LLSelectMgr::highlightObjectAndFamily(LLViewerObject* objectp) -{ - if (!objectp) - { - return; - } - - LLViewerObject* root_obj = (LLViewerObject*)objectp->getRoot(); - - highlightObjectOnly(root_obj); - - LLViewerObject::const_child_list_t& child_list = root_obj->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); iter++) - { - LLViewerObject* child = *iter; - highlightObjectOnly(child); - } -} - -// Note that this ignores the "select owned only" flag -// It's also more efficient than calling the single-object version over and over. -void LLSelectMgr::highlightObjectAndFamily(const std::vector& objects) -{ - for (std::vector::const_iterator iter1 = objects.begin(); - iter1 != objects.end(); ++iter1) - { - LLViewerObject* object = *iter1; - - if (!object) - { - continue; - } - if (object->getPCode() != LL_PCODE_VOLUME) - { - continue; - } - - LLViewerObject* root = (LLViewerObject*)object->getRoot(); - mRectSelectedObjects.insert(root); - - LLViewerObject::const_child_list_t& child_list = root->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter2 = child_list.begin(); - iter2 != child_list.end(); iter2++) - { - LLViewerObject* child = *iter2; - mRectSelectedObjects.insert(child); - } - } -} - -void LLSelectMgr::unhighlightObjectOnly(LLViewerObject* objectp) -{ - if (!objectp || (objectp->getPCode() != LL_PCODE_VOLUME)) - { - return; - } - - mRectSelectedObjects.erase(objectp); -} - -void LLSelectMgr::unhighlightObjectAndFamily(LLViewerObject* objectp) -{ - if (!objectp) - { - return; - } - - LLViewerObject* root_obj = (LLViewerObject*)objectp->getRoot(); - - unhighlightObjectOnly(root_obj); - - LLViewerObject::const_child_list_t& child_list = root_obj->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); iter++) - { - LLViewerObject* child = *iter; - unhighlightObjectOnly(child); - } -} - - -void LLSelectMgr::unhighlightAll() -{ - mRectSelectedObjects.clear(); - mHighlightedObjects->deleteAllNodes(); -} - -LLObjectSelectionHandle LLSelectMgr::selectHighlightedObjects() -{ - if (!mHighlightedObjects->getNumNodes()) - { - return NULL; - } - - //clear primary object - mSelectedObjects->mPrimaryObject = NULL; - - for (LLObjectSelection::iterator iter = getHighlightedObjects()->begin(); - iter != getHighlightedObjects()->end(); ) - { - LLObjectSelection::iterator curiter = iter++; - - LLSelectNode *nodep = *curiter; - LLViewerObject* objectp = nodep->getObject(); - - if (!canSelectObject(objectp)) - { - continue; - } - - // already selected - if (objectp->isSelected()) - { - continue; - } - - LLSelectNode* new_nodep = new LLSelectNode(*nodep); - mSelectedObjects->addNode(new_nodep); - - // flag this object as selected - objectp->setSelected(true); - objectp->setAllTESelected(true); - - mSelectedObjects->mSelectType = getSelectTypeForObject(objectp); - - // request properties on root objects - if (objectp->isRootEdit()) - { - requestObjectPropertiesFamily(objectp); - } - } - - // pack up messages to let sim know these objects are selected - sendSelect(); - unhighlightAll(); - updateSelectionCenter(); - saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); - updatePointAt(); - - if (mSelectedObjects->getObjectCount()) - { - gEditMenuHandler = this; - } - - return mSelectedObjects; -} - -void LLSelectMgr::deselectHighlightedObjects() -{ - bool select_linked_set = !gSavedSettings.getBOOL("EditLinkedParts"); - for (std::set >::iterator iter = mRectSelectedObjects.begin(); - iter != mRectSelectedObjects.end(); iter++) - { - LLViewerObject *objectp = *iter; - if (!select_linked_set) - { - deselectObjectOnly(objectp); - } - else - { - LLViewerObject* root_object = (LLViewerObject*)objectp->getRoot(); - if (root_object->isSelected()) - { - deselectObjectAndFamily(root_object); - } - } - } - - unhighlightAll(); -} - -void LLSelectMgr::addGridObject(LLViewerObject* objectp) -{ - LLSelectNode* nodep = new LLSelectNode(objectp, false); - mGridObjects.addNodeAtEnd(nodep); - - LLViewerObject::const_child_list_t& child_list = objectp->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); iter++) - { - LLViewerObject* child = *iter; - nodep = new LLSelectNode(child, false); - mGridObjects.addNodeAtEnd(nodep); - } -} - -void LLSelectMgr::clearGridObjects() -{ - mGridObjects.deleteAllNodes(); -} - -void LLSelectMgr::setGridMode(EGridMode mode) -{ - mGridMode = mode; - gSavedSettings.setS32("GridMode", mode); - updateSelectionCenter(); -} - -void LLSelectMgr::getGrid(LLVector3& origin, LLQuaternion &rotation, LLVector3 &scale, bool for_snap_guides) -{ - mGridObjects.cleanupNodes(); - - LLViewerObject* first_grid_object = mGridObjects.getFirstObject(); - - if (mGridMode == GRID_MODE_LOCAL && mSelectedObjects->getObjectCount()) - { - //LLViewerObject* root = getSelectedParentObject(mSelectedObjects->getFirstObject()); - mGridOrigin = mSavedSelectionBBox.getCenterAgent(); - mGridScale = mSavedSelectionBBox.getExtentLocal() * 0.5f; - - // DEV-12570 Just taking the saved selection box rotation prevents - // wild rotations of linked sets while in local grid mode - //if(mSelectedObjects->getObjectCount() < 2 || !root || root->mDrawable.isNull()) - { - mGridRotation = mSavedSelectionBBox.getRotation(); - } - /*else //set to the root object - { - mGridRotation = root->getRenderRotation(); - }*/ - } - else if (mGridMode == GRID_MODE_REF_OBJECT && first_grid_object && first_grid_object->mDrawable.notNull()) - { - LLSelectNode *node = mSelectedObjects->findNode(first_grid_object); - if (!for_snap_guides && node) - { - mGridRotation = node->mSavedRotation; - } - else - { - mGridRotation = first_grid_object->getRenderRotation(); - } - - LLVector4a min_extents(F32_MAX); - LLVector4a max_extents(-F32_MAX); - bool grid_changed = false; - for (LLObjectSelection::iterator iter = mGridObjects.begin(); - iter != mGridObjects.end(); ++iter) - { - LLViewerObject* object = (*iter)->getObject(); - LLDrawable* drawable = object->mDrawable; - if (drawable) - { - const LLVector4a* ext = drawable->getSpatialExtents(); - update_min_max(min_extents, max_extents, ext[0]); - update_min_max(min_extents, max_extents, ext[1]); - grid_changed = true; - } - } - if (grid_changed) - { - LLVector4a center, size; - center.setAdd(min_extents, max_extents); - center.mul(0.5f); - size.setSub(max_extents, min_extents); - size.mul(0.5f); - - mGridOrigin.set(center.getF32ptr()); - LLDrawable* drawable = first_grid_object->mDrawable; - if (drawable && drawable->isActive()) - { - mGridOrigin = mGridOrigin * first_grid_object->getRenderMatrix(); - } - mGridScale.set(size.getF32ptr()); - } - } - else // GRID_MODE_WORLD or just plain default - { - const bool non_root_ok = true; - LLViewerObject* first_object = mSelectedObjects->getFirstRootObject(non_root_ok); - - mGridOrigin.clearVec(); - mGridRotation.loadIdentity(); - - mSelectedObjects->mSelectType = getSelectTypeForObject( first_object ); - - switch (mSelectedObjects->mSelectType) - { - case SELECT_TYPE_ATTACHMENT: - if (first_object && first_object->getRootEdit()->mDrawable.notNull()) - { - // this means this object *has* to be an attachment - LLXform* attachment_point_xform = first_object->getRootEdit()->mDrawable->mXform.getParent(); - mGridOrigin = attachment_point_xform->getWorldPosition(); - mGridRotation = attachment_point_xform->getWorldRotation(); - mGridScale = LLVector3(1.f, 1.f, 1.f) * gSavedSettings.getF32("GridResolution"); - } - break; - case SELECT_TYPE_HUD: - mGridScale = LLVector3(1.f, 1.f, 1.f) * llmin(gSavedSettings.getF32("GridResolution"), 0.5f); - break; - case SELECT_TYPE_WORLD: - mGridScale = LLVector3(1.f, 1.f, 1.f) * gSavedSettings.getF32("GridResolution"); - break; - } - } - llassert(mGridOrigin.isFinite()); - - origin = mGridOrigin; - rotation = mGridRotation; - scale = mGridScale; -} - -//----------------------------------------------------------------------------- -// remove() - an array of objects -//----------------------------------------------------------------------------- - -void LLSelectMgr::remove(std::vector& objects) -{ - for (std::vector::iterator iter = objects.begin(); - iter != objects.end(); ++iter) - { - LLViewerObject* objectp = *iter; - LLSelectNode* nodep = mSelectedObjects->findNode(objectp); - if (nodep) - { - objectp->setSelected(false); - mSelectedObjects->removeNode(nodep); - nodep = NULL; - } - } - updateSelectionCenter(); - dialog_refresh_all(); -} - - -//----------------------------------------------------------------------------- -// remove() - a single object -//----------------------------------------------------------------------------- -void LLSelectMgr::remove(LLViewerObject *objectp, S32 te, bool undoable) -{ - // get object node (and verify it is in the selected list) - LLSelectNode *nodep = mSelectedObjects->findNode(objectp); - if (!nodep) - { - return; - } - - // if face = all, remove object from list - if ((objectp->getNumTEs() <= 0) || (te == SELECT_ALL_TES)) - { - // Remove all faces (or the object doesn't have faces) so remove the node - mSelectedObjects->removeNode(nodep); - nodep = NULL; - objectp->setSelected( false ); - } - else if (0 <= te && te < SELECT_MAX_TES) - { - // ...valid face, check to see if it was on - if (nodep->isTESelected(te)) - { - nodep->selectTE(te, false); - objectp->setTESelected(te, false); - } - else - { - LL_ERRS() << "LLSelectMgr::remove - tried to remove TE " << te << " that wasn't selected" << LL_ENDL; - return; - } - - // ...check to see if this operation turned off all faces - bool found = false; - for (S32 i = 0; i < nodep->getObject()->getNumTEs(); i++) - { - found = found || nodep->isTESelected(i); - } - - // ...all faces now turned off, so remove - if (!found) - { - mSelectedObjects->removeNode(nodep); - nodep = NULL; - objectp->setSelected( false ); - // *FIXME: Doesn't update simulator that object is no longer selected - } - } - else - { - // ...out of range face - LL_ERRS() << "LLSelectMgr::remove - TE " << te << " out of range" << LL_ENDL; - } - - updateSelectionCenter(); - dialog_refresh_all(); -} - - -//----------------------------------------------------------------------------- -// removeAll() -//----------------------------------------------------------------------------- -void LLSelectMgr::removeAll() -{ - for (LLObjectSelection::iterator iter = mSelectedObjects->begin(); - iter != mSelectedObjects->end(); iter++ ) - { - LLViewerObject *objectp = (*iter)->getObject(); - objectp->setSelected( false ); - } - - mSelectedObjects->deleteAllNodes(); - - updateSelectionCenter(); - dialog_refresh_all(); -} - -//----------------------------------------------------------------------------- -// promoteSelectionToRoot() -//----------------------------------------------------------------------------- -void LLSelectMgr::promoteSelectionToRoot() -{ - std::set selection_set; - - bool selection_changed = false; - - for (LLObjectSelection::iterator iter = getSelection()->begin(); - iter != getSelection()->end(); ) - { - LLObjectSelection::iterator curiter = iter++; - LLSelectNode* nodep = *curiter; - LLViewerObject* object = nodep->getObject(); - - if (nodep->mIndividualSelection) - { - selection_changed = true; - } - - LLViewerObject* parentp = object; - while(parentp->getParent() && !(parentp->isRootEdit())) - { - parentp = (LLViewerObject*)parentp->getParent(); - } - - selection_set.insert(parentp); - } - - if (selection_changed) - { - deselectAll(); - - std::set::iterator set_iter; - for (set_iter = selection_set.begin(); set_iter != selection_set.end(); ++set_iter) - { - selectObjectAndFamily(*set_iter); - } - } -} - -//----------------------------------------------------------------------------- -// demoteSelectionToIndividuals() -//----------------------------------------------------------------------------- -void LLSelectMgr::demoteSelectionToIndividuals() -{ - std::vector objects; - - for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); - iter != getSelection()->root_end(); iter++) - { - LLViewerObject* object = (*iter)->getObject(); - object->addThisAndNonJointChildren(objects); - } - - if (!objects.empty()) - { - deselectAll(); - for (std::vector::iterator iter = objects.begin(); - iter != objects.end(); ++iter) - { - LLViewerObject* objectp = *iter; - selectObjectOnly(objectp); - } - } -} - -//----------------------------------------------------------------------------- -// dump() -//----------------------------------------------------------------------------- -void LLSelectMgr::dump() -{ - LL_INFOS() << "Selection Manager: " << mSelectedObjects->getNumNodes() << " items" << LL_ENDL; - - LL_INFOS() << "TE mode " << mTEMode << LL_ENDL; - - S32 count = 0; - for (LLObjectSelection::iterator iter = getSelection()->begin(); - iter != getSelection()->end(); iter++ ) - { - LLViewerObject* objectp = (*iter)->getObject(); - LL_INFOS() << "Object " << count << " type " << LLPrimitive::pCodeToString(objectp->getPCode()) << LL_ENDL; - LL_INFOS() << " hasLSL " << objectp->flagScripted() << LL_ENDL; - LL_INFOS() << " hasTouch " << objectp->flagHandleTouch() << LL_ENDL; - LL_INFOS() << " hasMoney " << objectp->flagTakesMoney() << LL_ENDL; - LL_INFOS() << " getposition " << objectp->getPosition() << LL_ENDL; - LL_INFOS() << " getpositionAgent " << objectp->getPositionAgent() << LL_ENDL; - LL_INFOS() << " getpositionRegion " << objectp->getPositionRegion() << LL_ENDL; - LL_INFOS() << " getpositionGlobal " << objectp->getPositionGlobal() << LL_ENDL; - LLDrawable* drawablep = objectp->mDrawable; - LL_INFOS() << " " << (drawablep&& drawablep->isVisible() ? "visible" : "invisible") << LL_ENDL; - LL_INFOS() << " " << (drawablep&& drawablep->isState(LLDrawable::FORCE_INVISIBLE) ? "force_invisible" : "") << LL_ENDL; - count++; - } - - // Face iterator - for (LLObjectSelection::iterator iter = getSelection()->begin(); - iter != getSelection()->end(); iter++ ) - { - LLSelectNode* node = *iter; - LLViewerObject* objectp = node->getObject(); - if (!objectp) - continue; - for (S32 te = 0; te < objectp->getNumTEs(); ++te ) - { - if (node->isTESelected(te)) - { - LL_INFOS() << "Object " << objectp << " te " << te << LL_ENDL; - } - } - } - - LL_INFOS() << mHighlightedObjects->getNumNodes() << " objects currently highlighted." << LL_ENDL; - - LL_INFOS() << "Center global " << mSelectionCenterGlobal << LL_ENDL; -} - -//----------------------------------------------------------------------------- -// cleanup() -//----------------------------------------------------------------------------- -void LLSelectMgr::cleanup() -{ - mSilhouetteImagep = NULL; -} - - -//--------------------------------------------------------------------------- -// Manipulate properties of selected objects -//--------------------------------------------------------------------------- - -struct LLSelectMgrSendFunctor : public LLSelectedObjectFunctor -{ - virtual bool apply(LLViewerObject* object) - { - if (object->permModify()) - { - object->sendTEUpdate(); - } - return true; - } -}; - -void LLObjectSelection::applyNoCopyTextureToTEs(LLViewerInventoryItem* item) -{ - if (!item) - { - return; - } - LLViewerTexture* image = LLViewerTextureManager::getFetchedTexture(item->getAssetUUID()); - - for (iterator iter = begin(); iter != end(); ++iter) - { - LLSelectNode* node = *iter; - LLViewerObject* object = (*iter)->getObject(); - if (!object) - { - continue; - } - - S32 num_tes = llmin((S32)object->getNumTEs(), (S32)object->getNumFaces()); - bool texture_copied = false; - bool updated = false; - for (S32 te = 0; te < num_tes; ++te) - { - if (node->isTESelected(te)) - { - //(no-copy) textures must be moved to the object's inventory only once - // without making any copies - if (!texture_copied) - { - LLToolDragAndDrop::handleDropMaterialProtections(object, item, LLToolDragAndDrop::SOURCE_AGENT, LLUUID::null); - texture_copied = true; - } - - // apply texture for the selected faces - add(LLStatViewer::EDIT_TEXTURE, 1); - object->setTEImage(te, image); - updated = true; - } - } - - if (updated) // not nessesary? sendTEUpdate update supposed to be done by sendfunc - { - dialog_refresh_all(); - - // send the update to the simulator - object->sendTEUpdate(); - } - } -} - -bool LLObjectSelection::applyRestrictedPbrMaterialToTEs(LLViewerInventoryItem* item) -{ - if (!item) - { - return false; - } - - LLUUID asset_id = item->getAssetUUID(); - if (asset_id.isNull()) - { - asset_id = LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID; - } - - bool material_copied_all_faces = true; - - for (iterator iter = begin(); iter != end(); ++iter) - { - LLSelectNode* node = *iter; - LLViewerObject* object = (*iter)->getObject(); - if (!object) - { - continue; - } - - S32 num_tes = llmin((S32)object->getNumTEs(), (S32)object->getNumFaces()); - bool material_copied = false; - for (S32 te = 0; te < num_tes; ++te) - { - if (node->isTESelected(te)) - { - //(no-copy), (no-modify), and (no-transfer) materials must be moved to the object's inventory only once - // without making any copies - if (!material_copied && asset_id.notNull()) - { - material_copied = (bool)LLToolDragAndDrop::handleDropMaterialProtections(object, item, LLToolDragAndDrop::SOURCE_AGENT, LLUUID::null); - } - if (!material_copied) - { - // Applying the material is not possible for this object given the current inventory - material_copied_all_faces = false; - break; - } - - // apply texture for the selected faces - // blank out most override data on the server - //add(LLStatViewer::EDIT_TEXTURE, 1); - object->setRenderMaterialID(te, asset_id); - } - } - } - - LLGLTFMaterialList::flushUpdates(); - - return material_copied_all_faces; -} - - -//----------------------------------------------------------------------------- -// selectionSetImage() -//----------------------------------------------------------------------------- -// *TODO: re-arch texture applying out of lltooldraganddrop -bool LLSelectMgr::selectionSetImage(const LLUUID& imageid) -{ - // First for (no copy) textures and multiple object selection - LLViewerInventoryItem* item = gInventory.getItem(imageid); - if(item - && !item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID()) - && (mSelectedObjects->getNumNodes() > 1) ) - { - LL_DEBUGS() << "Attempted to apply no-copy texture " << imageid - << " to multiple objects" << LL_ENDL; - - LLNotificationsUtil::add("FailedToApplyTextureNoCopyToMultiple"); - return false; - } - - struct f : public LLSelectedTEFunctor - { - LLViewerInventoryItem* mItem; - LLUUID mImageID; - f(LLViewerInventoryItem* item, const LLUUID& id) : mItem(item), mImageID(id) {} - bool apply(LLViewerObject* objectp, S32 te) - { - if(!objectp || !objectp->permModify()) - { - return false; - } - - // Might be better to run willObjectAcceptInventory - if (mItem && objectp->isAttachment()) - { - const LLPermissions& perm = mItem->getPermissions(); - bool unrestricted = (perm.getMaskBase() & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED; - if (!unrestricted) - { - // Attachments are in world and in inventory simultaneously, - // at the moment server doesn't support such a situation. - return false; - } - } - - if (mItem) - { - LLToolDragAndDrop::dropTextureOneFace(objectp, - te, - mItem, - LLToolDragAndDrop::SOURCE_AGENT, - LLUUID::null, - false); - } - else // not an inventory item - { - // Texture picker defaults aren't inventory items - // * Don't need to worry about permissions for them - // * Can just apply the texture and be done with it. - objectp->setTEImage(te, LLViewerTextureManager::getFetchedTexture(mImageID, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE)); - } - - return true; - } - }; - - if (item && !item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID())) - { - getSelection()->applyNoCopyTextureToTEs(item); - } - else - { - f setfunc(item, imageid); - getSelection()->applyToTEs(&setfunc); - } - - - struct g : public LLSelectedObjectFunctor - { - LLViewerInventoryItem* mItem; - g(LLViewerInventoryItem* item) : mItem(item) {} - virtual bool apply(LLViewerObject* object) - { - if (!mItem) - { - object->sendTEUpdate(); - // 1 particle effect per object - LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM, true); - effectp->setSourceObject(gAgentAvatarp); - effectp->setTargetObject(object); - effectp->setDuration(LL_HUD_DUR_SHORT); - effectp->setColor(LLColor4U(gAgent.getEffectColor())); - } - return true; - } - } sendfunc(item); - getSelection()->applyToObjects(&sendfunc); - - return true; -} - -//----------------------------------------------------------------------------- -// selectionSetGLTFMaterial() -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectionSetGLTFMaterial(const LLUUID& mat_id) -{ - // First for (no copy) textures and multiple object selection - LLViewerInventoryItem* item = gInventory.getItem(mat_id); - if (item - && !item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID()) - && (mSelectedObjects->getNumNodes() > 1)) - { - LL_DEBUGS() << "Attempted to apply no-copy material " << mat_id - << "to multiple objects" << LL_ENDL; - - LLNotificationsUtil::add("FailedToApplyGLTFNoCopyToMultiple"); - return false; - } - - struct f : public LLSelectedTEFunctor - { - LLViewerInventoryItem* mItem; - LLUUID mMatId; - bool material_copied_any_face = false; - bool material_copied_all_faces = true; - f(LLViewerInventoryItem* item, const LLUUID& id) : mItem(item), mMatId(id) {} - bool apply(LLViewerObject* objectp, S32 te) - { - if (!objectp || !objectp->permModify()) - { - return false; - } - LLUUID asset_id = mMatId; - if (mItem) - { - const LLPermissions& perm = mItem->getPermissions(); - bool from_library = perm.getOwner() == ALEXANDRIA_LINDEN_ID; - if (objectp->isAttachment()) - { - bool unrestricted = (perm.getMaskBase() & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED; - - if (!unrestricted && !from_library) - { - // Attachments are in world and in inventory simultaneously, - // at the moment server doesn't support such a situation. - return false; - } - } - - if (!from_library - // Check if item may be copied into the object's inventory - && !LLToolDragAndDrop::handleDropMaterialProtections(objectp, mItem, LLToolDragAndDrop::SOURCE_AGENT, LLUUID::null)) - { - return false; - } - - asset_id = mItem->getAssetUUID(); - if (asset_id.isNull()) - { - asset_id = LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID; - } - } - - // Blank out most override data on the object and send to server - objectp->setRenderMaterialID(te, asset_id); - - return true; - } - }; - - bool success = true; - if (item - && (!item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID()) || - !item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID()) || - !item->getPermissions().allowOperationBy(PERM_MODIFY, gAgent.getID()) - ) - && item->getPermissions().getOwner() != ALEXANDRIA_LINDEN_ID - ) - { - success = success && getSelection()->applyRestrictedPbrMaterialToTEs(item); - } - else - { - f setfunc(item, mat_id); - success = success && getSelection()->applyToTEs(&setfunc); - } - - struct g : public LLSelectedObjectFunctor - { - LLViewerInventoryItem* mItem; - g(LLViewerInventoryItem* item) : mItem(item) {} - virtual bool apply(LLViewerObject* object) - { - if (object && !object->permModify()) - { - return false; - } - - if (!mItem) - { - // 1 particle effect per object - LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM, true); - effectp->setSourceObject(gAgentAvatarp); - effectp->setTargetObject(object); - effectp->setDuration(LL_HUD_DUR_SHORT); - effectp->setColor(LLColor4U(gAgent.getEffectColor())); - } - - dialog_refresh_all(); - object->sendTEUpdate(); - return true; - } - } sendfunc(item); - success = success && getSelection()->applyToObjects(&sendfunc); - - LLGLTFMaterialList::flushUpdates(); - - return success; -} - -//----------------------------------------------------------------------------- -// selectionSetColor() -//----------------------------------------------------------------------------- -void LLSelectMgr::selectionSetColor(const LLColor4 &color) -{ - struct f : public LLSelectedTEFunctor - { - LLColor4 mColor; - f(const LLColor4& c) : mColor(c) {} - bool apply(LLViewerObject* object, S32 te) - { - if (object->permModify()) - { - object->setTEColor(te, mColor); - } - return true; - } - } setfunc(color); - getSelection()->applyToTEs(&setfunc); - - LLSelectMgrSendFunctor sendfunc; - getSelection()->applyToObjects(&sendfunc); -} - -//----------------------------------------------------------------------------- -// selectionSetColorOnly() -//----------------------------------------------------------------------------- -void LLSelectMgr::selectionSetColorOnly(const LLColor4 &color) -{ - struct f : public LLSelectedTEFunctor - { - LLColor4 mColor; - f(const LLColor4& c) : mColor(c) {} - bool apply(LLViewerObject* object, S32 te) - { - if (object->permModify()) - { - LLColor4 prev_color = object->getTE(te)->getColor(); - mColor.mV[VALPHA] = prev_color.mV[VALPHA]; - // update viewer side color in anticipation of update from simulator - object->setTEColor(te, mColor); - } - return true; - } - } setfunc(color); - getSelection()->applyToTEs(&setfunc); - - LLSelectMgrSendFunctor sendfunc; - getSelection()->applyToObjects(&sendfunc); -} - -//----------------------------------------------------------------------------- -// selectionSetAlphaOnly() -//----------------------------------------------------------------------------- -void LLSelectMgr::selectionSetAlphaOnly(const F32 alpha) -{ - struct f : public LLSelectedTEFunctor - { - F32 mAlpha; - f(const F32& a) : mAlpha(a) {} - bool apply(LLViewerObject* object, S32 te) - { - if (object->permModify()) - { - LLColor4 prev_color = object->getTE(te)->getColor(); - prev_color.mV[VALPHA] = mAlpha; - // update viewer side color in anticipation of update from simulator - object->setTEColor(te, prev_color); - } - return true; - } - } setfunc(alpha); - getSelection()->applyToTEs(&setfunc); - - LLSelectMgrSendFunctor sendfunc; - getSelection()->applyToObjects(&sendfunc); -} - -void LLSelectMgr::selectionRevertColors() -{ - struct f : public LLSelectedTEFunctor - { - LLObjectSelectionHandle mSelectedObjects; - f(LLObjectSelectionHandle sel) : mSelectedObjects(sel) {} - bool apply(LLViewerObject* object, S32 te) - { - if (object->permModify()) - { - LLSelectNode* nodep = mSelectedObjects->findNode(object); - if (nodep && te < (S32)nodep->mSavedColors.size()) - { - LLColor4 color = nodep->mSavedColors[te]; - // update viewer side color in anticipation of update from simulator - object->setTEColor(te, color); - } - } - return true; - } - } setfunc(mSelectedObjects); - getSelection()->applyToTEs(&setfunc); - - LLSelectMgrSendFunctor sendfunc; - getSelection()->applyToObjects(&sendfunc); -} - -void LLSelectMgr::selectionRevertShinyColors() -{ - struct f : public LLSelectedTEFunctor - { - LLObjectSelectionHandle mSelectedObjects; - f(LLObjectSelectionHandle sel) : mSelectedObjects(sel) {} - bool apply(LLViewerObject* object, S32 te) - { - if (object->permModify()) - { - LLSelectNode* nodep = mSelectedObjects->findNode(object); - if (nodep && te < (S32)nodep->mSavedShinyColors.size()) - { - LLColor4 color = nodep->mSavedShinyColors[te]; - // update viewer side color in anticipation of update from simulator - LLMaterialPtr old_mat = object->getTE(te)->getMaterialParams(); - if (!old_mat.isNull()) - { - LLMaterialPtr new_mat = gFloaterTools->getPanelFace()->createDefaultMaterial(old_mat); - new_mat->setSpecularLightColor(color); - object->getTE(te)->setMaterialParams(new_mat); - LLMaterialMgr::getInstance()->put(object->getID(), te, *new_mat); - } - } - } - return true; - } - } setfunc(mSelectedObjects); - getSelection()->applyToTEs(&setfunc); - - LLSelectMgrSendFunctor sendfunc; - getSelection()->applyToObjects(&sendfunc); -} - -bool LLSelectMgr::selectionRevertTextures() -{ - struct f : public LLSelectedTEFunctor - { - LLObjectSelectionHandle mSelectedObjects; - f(LLObjectSelectionHandle sel) : mSelectedObjects(sel) {} - bool apply(LLViewerObject* object, S32 te) - { - if (object->permModify()) - { - LLSelectNode* nodep = mSelectedObjects->findNode(object); - if (nodep && te < (S32)nodep->mSavedTextures.size()) - { - LLUUID id = nodep->mSavedTextures[te]; - // update textures on viewer side - if (id.isNull()) - { - // this was probably a no-copy texture, leave image as-is - return false; - } - else - { - object->setTEImage(te, LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE)); - - } - } - } - return true; - } - } setfunc(mSelectedObjects); - bool revert_successful = getSelection()->applyToTEs(&setfunc); - - LLSelectMgrSendFunctor sendfunc; - getSelection()->applyToObjects(&sendfunc); - - return revert_successful; -} - -void LLSelectMgr::selectionRevertGLTFMaterials() -{ - struct f : public LLSelectedTEFunctor - { - LLObjectSelectionHandle mSelectedObjects; - f(LLObjectSelectionHandle sel) : mSelectedObjects(sel) {} - bool apply(LLViewerObject* objectp, S32 te) - { - if (objectp && !objectp->permModify()) - { - return false; - } - - LLSelectNode* nodep = mSelectedObjects->findNode(objectp); - if (nodep && te < (S32)nodep->mSavedGLTFMaterialIds.size()) - { - // Restore base material - LLUUID asset_id = nodep->mSavedGLTFMaterialIds[te]; - - // Update material locally - objectp->setRenderMaterialID(te, asset_id, false /*wait for LLGLTFMaterialList update*/); - objectp->setTEGLTFMaterialOverride(te, nodep->mSavedGLTFOverrideMaterials[te]); - - // Enqueue update to server - if (asset_id.notNull()) - { - // Restore overrides and base material - LLGLTFMaterialList::queueApply(objectp, te, asset_id, nodep->mSavedGLTFOverrideMaterials[te]); - } - else - { - //blank override out - LLGLTFMaterialList::queueApply(objectp, te, asset_id); - } - - } - return true; - } - } setfunc(mSelectedObjects); - getSelection()->applyToTEs(&setfunc); -} - -void LLSelectMgr::selectionSetBumpmap(U8 bumpmap, const LLUUID &image_id) -{ - struct f : public LLSelectedTEFunctor - { - U8 mBump; - f(const U8& b) : mBump(b) {} - bool apply(LLViewerObject* object, S32 te) - { - if (object->permModify()) - { - // update viewer side color in anticipation of update from simulator - object->setTEBumpmap(te, mBump); - } - return true; - } - } setfunc(bumpmap); - - LLViewerInventoryItem* item = gInventory.getItem(image_id); - if(item - && !item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID()) - && (mSelectedObjects->getNumNodes() > 1) ) - { - LL_WARNS() << "Attempted to apply no-copy texture to multiple objects" << LL_ENDL; - return; - } - - if (item && !item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID())) - { - LLViewerObject *object = mSelectedObjects->getFirstRootObject(); - if (!object) - { - return; - } - const LLPermissions& perm = item->getPermissions(); - bool unrestricted = (perm.getMaskBase() & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED; - bool attached = object->isAttachment(); - if (attached && !unrestricted) - { - // Attachments are in world and in inventory simultaneously, - // at the moment server doesn't support such a situation. - return; - } - LLToolDragAndDrop::handleDropMaterialProtections(object, item, LLToolDragAndDrop::SOURCE_AGENT, LLUUID::null); - } - getSelection()->applyToTEs(&setfunc); - - LLSelectMgrSendFunctor sendfunc; - getSelection()->applyToObjects(&sendfunc); -} - -void LLSelectMgr::selectionSetTexGen(U8 texgen) -{ - struct f : public LLSelectedTEFunctor - { - U8 mTexgen; - f(const U8& t) : mTexgen(t) {} - bool apply(LLViewerObject* object, S32 te) - { - if (object->permModify()) - { - // update viewer side color in anticipation of update from simulator - object->setTETexGen(te, mTexgen); - } - return true; - } - } setfunc(texgen); - getSelection()->applyToTEs(&setfunc); - - LLSelectMgrSendFunctor sendfunc; - getSelection()->applyToObjects(&sendfunc); -} - - -void LLSelectMgr::selectionSetShiny(U8 shiny, const LLUUID &image_id) -{ - struct f : public LLSelectedTEFunctor - { - U8 mShiny; - f(const U8& t) : mShiny(t) {} - bool apply(LLViewerObject* object, S32 te) - { - if (object->permModify()) - { - // update viewer side color in anticipation of update from simulator - object->setTEShiny(te, mShiny); - } - return true; - } - } setfunc(shiny); - - LLViewerInventoryItem* item = gInventory.getItem(image_id); - if(item - && !item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID()) - && (mSelectedObjects->getNumNodes() > 1) ) - { - LL_WARNS() << "Attempted to apply no-copy texture to multiple objects" << LL_ENDL; - return; - } - - if (item && !item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID())) - { - LLViewerObject *object = mSelectedObjects->getFirstRootObject(); - if (!object) - { - return; - } - const LLPermissions& perm = item->getPermissions(); - bool unrestricted = (perm.getMaskBase() & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED; - bool attached = object->isAttachment(); - if (attached && !unrestricted) - { - // Attachments are in world and in inventory simultaneously, - // at the moment server doesn't support such a situation. - return; - } - LLToolDragAndDrop::handleDropMaterialProtections(object, item, LLToolDragAndDrop::SOURCE_AGENT, LLUUID::null); - } - getSelection()->applyToTEs(&setfunc); - - LLSelectMgrSendFunctor sendfunc; - getSelection()->applyToObjects(&sendfunc); -} - -void LLSelectMgr::selectionSetFullbright(U8 fullbright) -{ - struct f : public LLSelectedTEFunctor - { - U8 mFullbright; - f(const U8& t) : mFullbright(t) {} - bool apply(LLViewerObject* object, S32 te) - { - if (object->permModify()) - { - // update viewer side color in anticipation of update from simulator - object->setTEFullbright(te, mFullbright); - } - return true; - } - } setfunc(fullbright); - getSelection()->applyToTEs(&setfunc); - - struct g : public LLSelectedObjectFunctor - { - U8 mFullbright; - g(const U8& t) : mFullbright(t) {} - virtual bool apply(LLViewerObject* object) - { - if (object->permModify()) - { - object->sendTEUpdate(); - if (mFullbright) - { - U8 material = object->getMaterial(); - U8 mcode = material & LL_MCODE_MASK; - if (mcode == LL_MCODE_LIGHT) - { - mcode = LL_MCODE_GLASS; - material = (material & ~LL_MCODE_MASK) | mcode; - object->setMaterial(material); - object->sendMaterialUpdate(); - } - } - } - return true; - } - } sendfunc(fullbright); - getSelection()->applyToObjects(&sendfunc); -} - -// This function expects media_data to be a map containing relevant -// media data name/value pairs (e.g. home_url, etc.) -void LLSelectMgr::selectionSetMedia(U8 media_type, const LLSD &media_data) -{ - struct f : public LLSelectedTEFunctor - { - U8 mMediaFlags; - const LLSD &mMediaData; - f(const U8& t, const LLSD& d) : mMediaFlags(t), mMediaData(d) {} - bool apply(LLViewerObject* object, S32 te) - { - if (object->permModify()) - { - // If we are adding media, then check the current state of the - // media data on this face. - // - If it does not have media, AND we are NOT setting the HOME URL, then do NOT add media to this - // face. - // - If it does not have media, and we ARE setting the HOME URL, add media to this face. - // - If it does already have media, add/update media to/on this face - // If we are removing media, just do it (ignore the passed-in LLSD). - if (mMediaFlags & LLTextureEntry::MF_HAS_MEDIA) - { - llassert(mMediaData.isMap()); - const LLTextureEntry *texture_entry = object->getTE(te); - if (!mMediaData.isMap() || - ((NULL != texture_entry) && !texture_entry->hasMedia() && !mMediaData.has(LLMediaEntry::HOME_URL_KEY))) - { - // skip adding/updating media - } - else { - // Add/update media - object->setTEMediaFlags(te, mMediaFlags); - LLVOVolume *vo = dynamic_cast(object); - llassert(NULL != vo); - if (NULL != vo) - { - vo->syncMediaData(te, mMediaData, true/*merge*/, true/*ignore_agent*/); - } - } - } - else - { - // delete media (or just set the flags) - object->setTEMediaFlags(te, mMediaFlags); - } - } - return true; - } - } setfunc(media_type, media_data); - getSelection()->applyToTEs(&setfunc); - - struct f2 : public LLSelectedObjectFunctor - { - virtual bool apply(LLViewerObject* object) - { - if (object->permModify()) - { - object->sendTEUpdate(); - LLVOVolume *vo = dynamic_cast(object); - llassert(NULL != vo); - // It's okay to skip this object if hasMedia() is false... - // the sendTEUpdate() above would remove all media data if it were - // there. - if (NULL != vo && vo->hasMedia()) - { - // Send updated media data FOR THE ENTIRE OBJECT - vo->sendMediaDataUpdate(); - } - } - return true; - } - } func2; - mSelectedObjects->applyToObjects( &func2 ); -} - -void LLSelectMgr::selectionSetGlow(F32 glow) -{ - struct f1 : public LLSelectedTEFunctor - { - F32 mGlow; - f1(F32 glow) : mGlow(glow) {}; - bool apply(LLViewerObject* object, S32 face) - { - if (object->permModify()) - { - // update viewer side color in anticipation of update from simulator - object->setTEGlow(face, mGlow); - } - return true; - } - } func1(glow); - mSelectedObjects->applyToTEs( &func1 ); - - struct f2 : public LLSelectedObjectFunctor - { - virtual bool apply(LLViewerObject* object) - { - if (object->permModify()) - { - object->sendTEUpdate(); - } - return true; - } - } func2; - mSelectedObjects->applyToObjects( &func2 ); -} - -void LLSelectMgr::selectionSetMaterialParams(LLSelectedTEMaterialFunctor* material_func, int te) -{ - struct f1 : public LLSelectedTEFunctor - { - LLMaterialPtr mMaterial; - f1(LLSelectedTEMaterialFunctor* material_func, int te) : _material_func(material_func), _specific_te(te) {} - - bool apply(LLViewerObject* object, S32 te) - { - if (_specific_te == -1 || (te == _specific_te)) - { - if (object && object->permModify() && _material_func) - { - LLTextureEntry* tep = object->getTE(te); - if (tep) - { - LLMaterialPtr current_material = tep->getMaterialParams(); - _material_func->apply(object, te, tep, current_material); - } - } - } - return true; - } - - LLSelectedTEMaterialFunctor* _material_func; - int _specific_te; - } func1(material_func, te); - mSelectedObjects->applyToTEs( &func1 ); - - struct f2 : public LLSelectedObjectFunctor - { - virtual bool apply(LLViewerObject* object) - { - if (object->permModify()) - { - object->sendTEUpdate(); - } - return true; - } - } func2; - mSelectedObjects->applyToObjects( &func2 ); -} - -void LLSelectMgr::selectionRemoveMaterial() -{ - struct f1 : public LLSelectedTEFunctor - { - bool apply(LLViewerObject* object, S32 face) - { - if (object->permModify()) - { - LL_DEBUGS("Materials") << "Removing material from object " << object->getID() << " face " << face << LL_ENDL; - LLMaterialMgr::getInstance()->remove(object->getID(),face); - object->setTEMaterialParams(face, NULL); - } - return true; - } - } func1; - mSelectedObjects->applyToTEs( &func1 ); - - struct f2 : public LLSelectedObjectFunctor - { - virtual bool apply(LLViewerObject* object) - { - if (object->permModify()) - { - object->sendTEUpdate(); - } - return true; - } - } func2; - mSelectedObjects->applyToObjects( &func2 ); -} - - -//----------------------------------------------------------------------------- -// findObjectPermissions() -//----------------------------------------------------------------------------- -LLPermissions* LLSelectMgr::findObjectPermissions(const LLViewerObject* object) -{ - for (LLObjectSelection::valid_iterator iter = getSelection()->valid_begin(); - iter != getSelection()->valid_end(); iter++ ) - { - LLSelectNode* nodep = *iter; - if (nodep->getObject() == object) - { - return nodep->mPermissions; - } - } - - return NULL; -} - - -//----------------------------------------------------------------------------- -// selectionGetGlow() -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectionGetGlow(F32 *glow) -{ - bool identical; - F32 lglow = 0.f; - struct f1 : public LLSelectedTEGetFunctor - { - F32 get(LLViewerObject* object, S32 face) - { - return object->getTE(face)->getGlow(); - } - } func; - identical = mSelectedObjects->getSelectedTEValue( &func, lglow ); - - *glow = lglow; - return identical; -} - - -void LLSelectMgr::selectionSetPhysicsType(U8 type) -{ - struct f : public LLSelectedObjectFunctor - { - U8 mType; - f(const U8& t) : mType(t) {} - virtual bool apply(LLViewerObject* object) - { - if (object->permModify()) - { - object->setPhysicsShapeType(mType); - object->updateFlags(true); - } - return true; - } - } sendfunc(type); - getSelection()->applyToObjects(&sendfunc); -} - -void LLSelectMgr::selectionSetFriction(F32 friction) -{ - struct f : public LLSelectedObjectFunctor - { - F32 mFriction; - f(const F32& friction) : mFriction(friction) {} - virtual bool apply(LLViewerObject* object) - { - if (object->permModify()) - { - object->setPhysicsFriction(mFriction); - object->updateFlags(true); - } - return true; - } - } sendfunc(friction); - getSelection()->applyToObjects(&sendfunc); -} - -void LLSelectMgr::selectionSetGravity(F32 gravity ) -{ - struct f : public LLSelectedObjectFunctor - { - F32 mGravity; - f(const F32& gravity) : mGravity(gravity) {} - virtual bool apply(LLViewerObject* object) - { - if (object->permModify()) - { - object->setPhysicsGravity(mGravity); - object->updateFlags(true); - } - return true; - } - } sendfunc(gravity); - getSelection()->applyToObjects(&sendfunc); -} - -void LLSelectMgr::selectionSetDensity(F32 density ) -{ - struct f : public LLSelectedObjectFunctor - { - F32 mDensity; - f(const F32& density ) : mDensity(density) {} - virtual bool apply(LLViewerObject* object) - { - if (object->permModify()) - { - object->setPhysicsDensity(mDensity); - object->updateFlags(true); - } - return true; - } - } sendfunc(density); - getSelection()->applyToObjects(&sendfunc); -} - -void LLSelectMgr::selectionSetRestitution(F32 restitution) -{ - struct f : public LLSelectedObjectFunctor - { - F32 mRestitution; - f(const F32& restitution ) : mRestitution(restitution) {} - virtual bool apply(LLViewerObject* object) - { - if (object->permModify()) - { - object->setPhysicsRestitution(mRestitution); - object->updateFlags(true); - } - return true; - } - } sendfunc(restitution); - getSelection()->applyToObjects(&sendfunc); -} - - -//----------------------------------------------------------------------------- -// selectionSetMaterial() -//----------------------------------------------------------------------------- -void LLSelectMgr::selectionSetMaterial(U8 material) -{ - struct f : public LLSelectedObjectFunctor - { - U8 mMaterial; - f(const U8& t) : mMaterial(t) {} - virtual bool apply(LLViewerObject* object) - { - if (object->permModify()) - { - U8 cur_material = object->getMaterial(); - U8 material = mMaterial | (cur_material & ~LL_MCODE_MASK); - object->setMaterial(material); - object->sendMaterialUpdate(); - } - return true; - } - } sendfunc(material); - getSelection()->applyToObjects(&sendfunc); -} - -// true if all selected objects have this PCode -bool LLSelectMgr::selectionAllPCode(LLPCode code) -{ - struct f : public LLSelectedObjectFunctor - { - LLPCode mCode; - f(const LLPCode& t) : mCode(t) {} - virtual bool apply(LLViewerObject* object) - { - if (object->getPCode() != mCode) - { - return false; - } - return true; - } - } func(code); - bool res = getSelection()->applyToObjects(&func); - return res; -} - -bool LLSelectMgr::selectionGetIncludeInSearch(bool* include_in_search_out) -{ - LLViewerObject *object = mSelectedObjects->getFirstRootObject(); - if (!object) return false; - - bool include_in_search = object->getIncludeInSearch(); - - bool identical = true; - - for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); - iter != getSelection()->root_end(); iter++) - { - LLViewerObject* object = (*iter)->getObject(); - - if ( include_in_search != object->getIncludeInSearch()) - { - identical = false; - break; - } - } - - *include_in_search_out = include_in_search; - return identical; -} - -void LLSelectMgr::selectionSetIncludeInSearch(bool include_in_search) -{ - LLViewerObject* object = NULL; - for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); - iter != getSelection()->root_end(); iter++) - { - object = (*iter)->getObject(); - object->setIncludeInSearch(include_in_search); - } - sendListToRegions( - "ObjectIncludeInSearch", - packAgentAndSessionID, - packObjectIncludeInSearch, - logNoOp, - &include_in_search, - SEND_ONLY_ROOTS); -} - -bool LLSelectMgr::selectionGetClickAction(U8 *out_action) -{ - LLViewerObject *object = mSelectedObjects->getFirstObject(); - if (!object) - { - return false; - } - - U8 action = object->getClickAction(); - *out_action = action; - - struct f : public LLSelectedObjectFunctor - { - U8 mAction; - f(const U8& t) : mAction(t) {} - virtual bool apply(LLViewerObject* object) - { - if ( mAction != object->getClickAction()) - { - return false; - } - return true; - } - } func(action); - bool res = getSelection()->applyToObjects(&func); - return res; -} - -void LLSelectMgr::selectionSetClickAction(U8 action) -{ - struct f : public LLSelectedObjectFunctor - { - U8 mAction; - f(const U8& t) : mAction(t) {} - virtual bool apply(LLViewerObject* object) - { - object->setClickAction(mAction); - return true; - } - } func(action); - getSelection()->applyToObjects(&func); - - sendListToRegions("ObjectClickAction", - packAgentAndSessionID, - packObjectClickAction, - logNoOp, - &action, - SEND_INDIVIDUALS); -} - - -//----------------------------------------------------------------------------- -// godlike requests -//----------------------------------------------------------------------------- - -typedef std::pair godlike_request_t; - -void LLSelectMgr::sendGodlikeRequest(const std::string& request, const std::string& param) -{ - // If the agent is neither godlike nor an estate owner, the server - // will reject the request. - std::string message_type; - if (gAgent.isGodlike()) - { - message_type = "GodlikeMessage"; - } - else - { - message_type = "EstateOwnerMessage"; - } - - godlike_request_t data(request, param); - if(!mSelectedObjects->getRootObjectCount()) - { - LLMessageSystem* msg = gMessageSystem; - msg->newMessage(message_type.c_str()); - LLSelectMgr::packGodlikeHead(&data); - gAgent.sendReliableMessage(); - } - else - { - sendListToRegions(message_type, packGodlikeHead, packObjectIDAsParam, logNoOp, &data, SEND_ONLY_ROOTS); - } -} - -void LLSelectMgr::packGodlikeHead(void* user_data) -{ - LLMessageSystem* msg = gMessageSystem; - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addUUID("TransactionID", LLUUID::null); - godlike_request_t* data = (godlike_request_t*)user_data; - msg->nextBlock("MethodData"); - msg->addString("Method", data->first); - msg->addUUID("Invoice", LLUUID::null); - - // The parameters used to be restricted to either string or - // integer. This mimics that behavior under the new 'string-only' - // parameter list by not packing a string if there wasn't one - // specified. The object ids will be packed in the - // packObjectIDAsParam() method. - if(data->second.size() > 0) - { - msg->nextBlock("ParamList"); - msg->addString("Parameter", data->second); - } -} - -// static -void LLSelectMgr::logNoOp(LLSelectNode* node, void *) -{ -} - -// static -void LLSelectMgr::logAttachmentRequest(LLSelectNode* node, void *) -{ - LLAttachmentsMgr::instance().onAttachmentRequested(node->mItemID); -} - -// static -void LLSelectMgr::logDetachRequest(LLSelectNode* node, void *) -{ - LLAttachmentsMgr::instance().onDetachRequested(node->mItemID); -} - -// static -void LLSelectMgr::packObjectIDAsParam(LLSelectNode* node, void *) -{ - std::string buf = llformat("%u", node->getObject()->getLocalID()); - gMessageSystem->nextBlock("ParamList"); - gMessageSystem->addString("Parameter", buf); -} - -//----------------------------------------------------------------------------- -// selectionTexScaleAutofit() -//----------------------------------------------------------------------------- -void LLSelectMgr::selectionTexScaleAutofit(F32 repeats_per_meter) -{ - struct f : public LLSelectedTEFunctor - { - F32 mRepeatsPerMeter; - f(const F32& t) : mRepeatsPerMeter(t) {} - bool apply(LLViewerObject* object, S32 te) - { - - if (object->permModify()) - { - // Compute S,T to axis mapping - U32 s_axis, t_axis; - if (!LLPrimitive::getTESTAxes(te, &s_axis, &t_axis)) - { - return true; - } - - F32 new_s = object->getScale().mV[s_axis] * mRepeatsPerMeter; - F32 new_t = object->getScale().mV[t_axis] * mRepeatsPerMeter; - - object->setTEScale(te, new_s, new_t); - } - return true; - } - } setfunc(repeats_per_meter); - getSelection()->applyToTEs(&setfunc); - - LLSelectMgrSendFunctor sendfunc; - getSelection()->applyToObjects(&sendfunc); -} - - - -// Called at the end of a scale operation, this adjusts the textures to attempt to -// maintain a constant repeats per meter. -// BUG: Only works for flex boxes. -//----------------------------------------------------------------------------- -// adjustTexturesByScale() -//----------------------------------------------------------------------------- -void LLSelectMgr::adjustTexturesByScale(bool send_to_sim, bool stretch) -{ - for (LLObjectSelection::iterator iter = getSelection()->begin(); - iter != getSelection()->end(); iter++) - { - LLSelectNode* selectNode = *iter; - LLViewerObject* object = selectNode->getObject(); - - if (!object) - { - continue; - } - - if (!object->permModify()) - { - continue; - } - - if (object->getNumTEs() == 0) - { - continue; - } - - bool send = false; - - for (U8 te_num = 0; te_num < object->getNumTEs(); te_num++) - { - const LLTextureEntry* tep = object->getTE(te_num); - - bool planar = tep->getTexGen() == LLTextureEntry::TEX_GEN_PLANAR; - if (planar == stretch) - { - // Figure out how S,T changed with scale operation - U32 s_axis, t_axis; - if (!LLPrimitive::getTESTAxes(te_num, &s_axis, &t_axis)) - { - continue; - } - - LLVector3 object_scale = object->getScale(); - LLVector3 diffuse_scale_ratio = selectNode->mTextureScaleRatios[te_num]; - - // We like these to track together. NORSPEC-96 - // - LLVector3 normal_scale_ratio = diffuse_scale_ratio; - LLVector3 specular_scale_ratio = diffuse_scale_ratio; - - // Apply new scale to face - if (planar) - { - F32 diffuse_scale_s = diffuse_scale_ratio.mV[s_axis]/object_scale.mV[s_axis]; - F32 diffuse_scale_t = diffuse_scale_ratio.mV[t_axis]/object_scale.mV[t_axis]; - - F32 normal_scale_s = normal_scale_ratio.mV[s_axis]/object_scale.mV[s_axis]; - F32 normal_scale_t = normal_scale_ratio.mV[t_axis]/object_scale.mV[t_axis]; - - F32 specular_scale_s = specular_scale_ratio.mV[s_axis]/object_scale.mV[s_axis]; - F32 specular_scale_t = specular_scale_ratio.mV[t_axis]/object_scale.mV[t_axis]; - - object->setTEScale(te_num, diffuse_scale_s, diffuse_scale_t); - - LLTextureEntry* tep = object->getTE(te_num); - - if (tep && !tep->getMaterialParams().isNull()) - { - LLMaterialPtr orig = tep->getMaterialParams(); - LLMaterialPtr p = gFloaterTools->getPanelFace()->createDefaultMaterial(orig); - p->setNormalRepeat(normal_scale_s, normal_scale_t); - p->setSpecularRepeat(specular_scale_s, specular_scale_t); - - LLMaterialMgr::getInstance()->put(object->getID(), te_num, *p); - } - } - else - { - - F32 diffuse_scale_s = diffuse_scale_ratio.mV[s_axis]*object_scale.mV[s_axis]; - F32 diffuse_scale_t = diffuse_scale_ratio.mV[t_axis]*object_scale.mV[t_axis]; - - F32 normal_scale_s = normal_scale_ratio.mV[s_axis]*object_scale.mV[s_axis]; - F32 normal_scale_t = normal_scale_ratio.mV[t_axis]*object_scale.mV[t_axis]; - - F32 specular_scale_s = specular_scale_ratio.mV[s_axis]*object_scale.mV[s_axis]; - F32 specular_scale_t = specular_scale_ratio.mV[t_axis]*object_scale.mV[t_axis]; - - object->setTEScale(te_num, diffuse_scale_s,diffuse_scale_t); - - LLTextureEntry* tep = object->getTE(te_num); - - if (tep && !tep->getMaterialParams().isNull()) - { - LLMaterialPtr orig = tep->getMaterialParams(); - LLMaterialPtr p = gFloaterTools->getPanelFace()->createDefaultMaterial(orig); - - p->setNormalRepeat(normal_scale_s, normal_scale_t); - p->setSpecularRepeat(specular_scale_s, specular_scale_t); - - LLMaterialMgr::getInstance()->put(object->getID(), te_num, *p); - } - } - send = send_to_sim; - } - } - - if (send) - { - object->sendTEUpdate(); - } - } -} - -//----------------------------------------------------------------------------- -// selectGetAllRootsValid() -// Returns true if the viewer has information on all selected objects -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectGetAllRootsValid() -{ - for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); - iter != getSelection()->root_end(); ++iter ) - { - LLSelectNode* node = *iter; - if( !node->mValid ) - { - return false; - } - } - return true; -} - - -//----------------------------------------------------------------------------- -// selectGetAllValid() -// Returns true if the viewer has information on all selected objects -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectGetAllValid() -{ - for (LLObjectSelection::iterator iter = getSelection()->begin(); - iter != getSelection()->end(); ++iter ) - { - LLSelectNode* node = *iter; - if( !node->mValid ) - { - return false; - } - } - return true; -} - -//----------------------------------------------------------------------------- -// selectGetAllValidAndObjectsFound() - return true if selections are -// valid and objects are found. -// -// For EXT-3114 - same as selectGetModify() without the modify check. -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectGetAllValidAndObjectsFound() -{ - for (LLObjectSelection::iterator iter = getSelection()->begin(); - iter != getSelection()->end(); iter++ ) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - if( !object || !node->mValid ) - { - return false; - } - } - return true; -} - -//----------------------------------------------------------------------------- -// selectGetModify() - return true if current agent can modify all -// selected objects. -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectGetModify() -{ - for (LLObjectSelection::iterator iter = getSelection()->begin(); - iter != getSelection()->end(); iter++ ) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - if( !object || !node->mValid ) - { - return false; - } - if( !object->permModify() ) - { - return false; - } - } - return true; -} - -//----------------------------------------------------------------------------- -// selectGetRootsModify() - return true if current agent can modify all -// selected root objects. -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectGetRootsModify() -{ - for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); - iter != getSelection()->root_end(); iter++ ) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - if( !node->mValid ) - { - return false; - } - if( !object->permModify() ) - { - return false; - } - } - - return true; -} - -//----------------------------------------------------------------------------- -// selectGetSameRegion() - return true if all objects are in same region -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectGetSameRegion() -{ - if (getSelection()->isEmpty()) - { - return true; - } - LLViewerObject* object = getSelection()->getFirstObject(); - if (!object) - { - return false; - } - LLViewerRegion* current_region = object->getRegion(); - - for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); - iter != getSelection()->root_end(); iter++) - { - LLSelectNode* node = *iter; - object = node->getObject(); - if (!node->mValid || !object || current_region != object->getRegion()) - { - return false; - } - } - - return true; -} - -//----------------------------------------------------------------------------- -// selectGetNonPermanentEnforced() - return true if all objects are not -// permanent enforced -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectGetNonPermanentEnforced() -{ - for (LLObjectSelection::iterator iter = getSelection()->begin(); - iter != getSelection()->end(); iter++ ) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - if( !object || !node->mValid ) - { - return false; - } - if( object->isPermanentEnforced()) - { - return false; - } - } - return true; -} - -//----------------------------------------------------------------------------- -// selectGetRootsNonPermanentEnforced() - return true if all root objects are -// not permanent enforced -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectGetRootsNonPermanentEnforced() -{ - for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); - iter != getSelection()->root_end(); iter++ ) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - if( !node->mValid ) - { - return false; - } - if( object->isPermanentEnforced()) - { - return false; - } - } - - return true; -} - -//----------------------------------------------------------------------------- -// selectGetPermanent() - return true if all objects are permanent -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectGetPermanent() -{ - for (LLObjectSelection::iterator iter = getSelection()->begin(); - iter != getSelection()->end(); iter++ ) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - if( !object || !node->mValid ) - { - return false; - } - if( !object->flagObjectPermanent()) - { - return false; - } - } - return true; -} - -//----------------------------------------------------------------------------- -// selectGetRootsPermanent() - return true if all root objects are -// permanent -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectGetRootsPermanent() -{ - for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); - iter != getSelection()->root_end(); iter++ ) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - if( !node->mValid ) - { - return false; - } - if( !object->flagObjectPermanent()) - { - return false; - } - } - - return true; -} - -//----------------------------------------------------------------------------- -// selectGetCharacter() - return true if all objects are character -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectGetCharacter() -{ - for (LLObjectSelection::iterator iter = getSelection()->begin(); - iter != getSelection()->end(); iter++ ) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - if( !object || !node->mValid ) - { - return false; - } - if( !object->flagCharacter()) - { - return false; - } - } - return true; -} - -//----------------------------------------------------------------------------- -// selectGetRootsCharacter() - return true if all root objects are -// character -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectGetRootsCharacter() -{ - for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); - iter != getSelection()->root_end(); iter++ ) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - if( !node->mValid ) - { - return false; - } - if( !object->flagCharacter()) - { - return false; - } - } - - return true; -} - -//----------------------------------------------------------------------------- -// selectGetNonPathfinding() - return true if all objects are not pathfinding -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectGetNonPathfinding() -{ - for (LLObjectSelection::iterator iter = getSelection()->begin(); - iter != getSelection()->end(); iter++ ) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - if( !object || !node->mValid ) - { - return false; - } - if( object->flagObjectPermanent() || object->flagCharacter()) - { - return false; - } - } - return true; -} - -//----------------------------------------------------------------------------- -// selectGetRootsNonPathfinding() - return true if all root objects are not -// pathfinding -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectGetRootsNonPathfinding() -{ - for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); - iter != getSelection()->root_end(); iter++ ) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - if( !node->mValid ) - { - return false; - } - if( object->flagObjectPermanent() || object->flagCharacter()) - { - return false; - } - } - - return true; -} - -//----------------------------------------------------------------------------- -// selectGetNonPermanent() - return true if all objects are not permanent -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectGetNonPermanent() -{ - for (LLObjectSelection::iterator iter = getSelection()->begin(); - iter != getSelection()->end(); iter++ ) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - if( !object || !node->mValid ) - { - return false; - } - if( object->flagObjectPermanent()) - { - return false; - } - } - return true; -} - -//----------------------------------------------------------------------------- -// selectGetRootsNonPermanent() - return true if all root objects are not -// permanent -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectGetRootsNonPermanent() -{ - for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); - iter != getSelection()->root_end(); iter++ ) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - if( !node->mValid ) - { - return false; - } - if( object->flagObjectPermanent()) - { - return false; - } - } - - return true; -} - -//----------------------------------------------------------------------------- -// selectGetNonCharacter() - return true if all objects are not character -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectGetNonCharacter() -{ - for (LLObjectSelection::iterator iter = getSelection()->begin(); - iter != getSelection()->end(); iter++ ) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - if( !object || !node->mValid ) - { - return false; - } - if( object->flagCharacter()) - { - return false; - } - } - return true; -} - -//----------------------------------------------------------------------------- -// selectGetRootsNonCharacter() - return true if all root objects are not -// character -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectGetRootsNonCharacter() -{ - for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); - iter != getSelection()->root_end(); iter++ ) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - if( !node->mValid ) - { - return false; - } - if( object->flagCharacter()) - { - return false; - } - } - - return true; -} - - -//----------------------------------------------------------------------------- -// selectGetEditableLinksets() - return true if all objects are editable -// pathfinding linksets -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectGetEditableLinksets() -{ - for (LLObjectSelection::iterator iter = getSelection()->begin(); - iter != getSelection()->end(); iter++ ) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - if( !object || !node->mValid ) - { - return false; - } - if (object->flagUsePhysics() || - object->flagTemporaryOnRez() || - object->flagCharacter() || - object->flagVolumeDetect() || - object->flagAnimSource() || - (object->getRegion() != gAgent.getRegion()) || - (!gAgent.isGodlike() && - !gAgent.canManageEstate() && - !object->permYouOwner() && - !object->permMove())) - { - return false; - } - } - return true; -} - -//----------------------------------------------------------------------------- -// selectGetViewableCharacters() - return true if all objects are characters -// viewable within the pathfinding characters floater -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectGetViewableCharacters() -{ - for (LLObjectSelection::iterator iter = getSelection()->begin(); - iter != getSelection()->end(); iter++ ) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - if( !object || !node->mValid ) - { - return false; - } - if( !object->flagCharacter() || - (object->getRegion() != gAgent.getRegion())) - { - return false; - } - } - return true; -} - -//----------------------------------------------------------------------------- -// selectGetRootsTransfer() - return true if current agent can transfer all -// selected root objects. -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectGetRootsTransfer() -{ - for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); - iter != getSelection()->root_end(); iter++ ) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - if( !node->mValid ) - { - return false; - } - if(!object->permTransfer()) - { - return false; - } - } - return true; -} - -//----------------------------------------------------------------------------- -// selectGetRootsCopy() - return true if current agent can copy all -// selected root objects. -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectGetRootsCopy() -{ - for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); - iter != getSelection()->root_end(); iter++ ) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - if( !node->mValid ) - { - return false; - } - if(!object->permCopy()) - { - return false; - } - } - return true; -} - -struct LLSelectGetFirstTest -{ - LLSelectGetFirstTest() : mIdentical(true), mFirst(true) { } - virtual ~LLSelectGetFirstTest() { } - - // returns false to break out of the iteration. - bool checkMatchingNode(LLSelectNode* node) - { - if (!node || !node->mValid) - { - return false; - } - - if (mFirst) - { - mFirstValue = getValueFromNode(node); - mFirst = false; - } - else - { - if ( mFirstValue != getValueFromNode(node) ) - { - mIdentical = false; - // stop testing once we know not all selected are identical. - return false; - } - } - // continue testing. - return true; - } - - bool mIdentical; - LLUUID mFirstValue; - -protected: - virtual const LLUUID& getValueFromNode(LLSelectNode* node) = 0; - -private: - bool mFirst; -}; - -void LLSelectMgr::getFirst(LLSelectGetFirstTest* test) -{ - if (gSavedSettings.getBOOL("EditLinkedParts")) - { - for (LLObjectSelection::valid_iterator iter = getSelection()->valid_begin(); - iter != getSelection()->valid_end(); ++iter ) - { - if (!test->checkMatchingNode(*iter)) - { - break; - } - } - } - else - { - for (LLObjectSelection::root_object_iterator iter = getSelection()->root_object_begin(); - iter != getSelection()->root_object_end(); ++iter ) - { - if (!test->checkMatchingNode(*iter)) - { - break; - } - } - } -} - -//----------------------------------------------------------------------------- -// selectGetCreator() -// Creator information only applies to roots unless editing linked parts. -//----------------------------------------------------------------------------- -struct LLSelectGetFirstCreator : public LLSelectGetFirstTest -{ -protected: - virtual const LLUUID& getValueFromNode(LLSelectNode* node) - { - return node->mPermissions->getCreator(); - } -}; - -bool LLSelectMgr::selectGetCreator(LLUUID& result_id, std::string& name) -{ - LLSelectGetFirstCreator test; - getFirst(&test); - - if (test.mFirstValue.isNull()) - { - name = LLTrans::getString("AvatarNameNobody"); - return false; - } - - result_id = test.mFirstValue; - - if (test.mIdentical) - { - name = LLSLURL("agent", test.mFirstValue, "inspect").getSLURLString(); - } - else - { - name = LLTrans::getString("AvatarNameMultiple"); - } - - return test.mIdentical; -} - -//----------------------------------------------------------------------------- -// selectGetOwner() -// Owner information only applies to roots unless editing linked parts. -//----------------------------------------------------------------------------- -struct LLSelectGetFirstOwner : public LLSelectGetFirstTest -{ -protected: - virtual const LLUUID& getValueFromNode(LLSelectNode* node) - { - // Don't use 'getOwnership' since we return a reference, not a copy. - // Will return LLUUID::null if unowned (which is not allowed and should never happen.) - return node->mPermissions->isGroupOwned() ? node->mPermissions->getGroup() : node->mPermissions->getOwner(); - } -}; - -bool LLSelectMgr::selectGetOwner(LLUUID& result_id, std::string& name) -{ - LLSelectGetFirstOwner test; - getFirst(&test); - - if (test.mFirstValue.isNull()) - { - return false; - } - - result_id = test.mFirstValue; - - if (test.mIdentical) - { - bool group_owned = selectIsGroupOwned(); - if (group_owned) - { - name = LLSLURL("group", test.mFirstValue, "inspect").getSLURLString(); - } - else - { - name = LLSLURL("agent", test.mFirstValue, "inspect").getSLURLString(); - } - } - else - { - name = LLTrans::getString("AvatarNameMultiple"); - } - - return test.mIdentical; -} - -//----------------------------------------------------------------------------- -// selectGetLastOwner() -// Owner information only applies to roots unless editing linked parts. -//----------------------------------------------------------------------------- -struct LLSelectGetFirstLastOwner : public LLSelectGetFirstTest -{ -protected: - virtual const LLUUID& getValueFromNode(LLSelectNode* node) - { - return node->mPermissions->getLastOwner(); - } -}; - -bool LLSelectMgr::selectGetLastOwner(LLUUID& result_id, std::string& name) -{ - LLSelectGetFirstLastOwner test; - getFirst(&test); - - if (test.mFirstValue.isNull()) - { - return false; - } - - result_id = test.mFirstValue; - - if (test.mIdentical) - { - name = LLSLURL("agent", test.mFirstValue, "inspect").getSLURLString(); - } - else - { - name.assign( "" ); - } - - return test.mIdentical; -} - -//----------------------------------------------------------------------------- -// selectGetGroup() -// Group information only applies to roots unless editing linked parts. -//----------------------------------------------------------------------------- -struct LLSelectGetFirstGroup : public LLSelectGetFirstTest -{ -protected: - virtual const LLUUID& getValueFromNode(LLSelectNode* node) - { - return node->mPermissions->getGroup(); - } -}; - -bool LLSelectMgr::selectGetGroup(LLUUID& result_id) -{ - LLSelectGetFirstGroup test; - getFirst(&test); - - result_id = test.mFirstValue; - return test.mIdentical; -} - -//----------------------------------------------------------------------------- -// selectIsGroupOwned() -// Only operates on root nodes unless editing linked parts. -// Returns true if the first selected is group owned. -//----------------------------------------------------------------------------- -struct LLSelectGetFirstGroupOwner : public LLSelectGetFirstTest -{ -protected: - virtual const LLUUID& getValueFromNode(LLSelectNode* node) - { - if (node->mPermissions->isGroupOwned()) - { - return node->mPermissions->getGroup(); - } - return LLUUID::null; - } -}; - -bool LLSelectMgr::selectIsGroupOwned() -{ - LLSelectGetFirstGroupOwner test; - getFirst(&test); - - return test.mFirstValue.notNull(); -} - -//----------------------------------------------------------------------------- -// selectGetPerm() -// Only operates on root nodes. -// Returns true if all have valid data. -// mask_on has bits set to true where all permissions are true -// mask_off has bits set to true where all permissions are false -// if a bit is off both in mask_on and mask_off, the values differ within -// the selection. -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectGetPerm(U8 which_perm, U32* mask_on, U32* mask_off) -{ - U32 mask; - U32 mask_and = 0xffffffff; - U32 mask_or = 0x00000000; - bool all_valid = false; - - for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); - iter != getSelection()->root_end(); iter++) - { - LLSelectNode* node = *iter; - - if (!node->mValid) - { - all_valid = false; - break; - } - - all_valid = true; - - switch( which_perm ) - { - case PERM_BASE: - mask = node->mPermissions->getMaskBase(); - break; - case PERM_OWNER: - mask = node->mPermissions->getMaskOwner(); - break; - case PERM_GROUP: - mask = node->mPermissions->getMaskGroup(); - break; - case PERM_EVERYONE: - mask = node->mPermissions->getMaskEveryone(); - break; - case PERM_NEXT_OWNER: - mask = node->mPermissions->getMaskNextOwner(); - break; - default: - mask = 0x0; - break; - } - mask_and &= mask; - mask_or |= mask; - } - - if (all_valid) - { - // ...true through all ANDs means all true - *mask_on = mask_and; - - // ...false through all ORs means all false - *mask_off = ~mask_or; - return true; - } - else - { - *mask_on = 0; - *mask_off = 0; - return false; - } -} - - - -bool LLSelectMgr::selectGetPermissions(LLPermissions& result_perm) -{ - bool first = true; - LLPermissions perm; - for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); - iter != getSelection()->root_end(); iter++ ) - { - LLSelectNode* node = *iter; - if (!node->mValid) - { - return false; - } - - if (first) - { - perm = *(node->mPermissions); - first = false; - } - else - { - perm.accumulate(*(node->mPermissions)); - } - } - - result_perm = perm; - - return true; -} - - -void LLSelectMgr::selectDelete() -{ - S32 deleteable_count = 0; - - bool locked_but_deleteable_object = false; - bool no_copy_but_deleteable_object = false; - bool all_owned_by_you = true; - - for (LLObjectSelection::iterator iter = getSelection()->begin(); - iter != getSelection()->end(); iter++) - { - LLViewerObject* obj = (*iter)->getObject(); - - if( obj->isAttachment() ) - { - continue; - } - - deleteable_count++; - - // Check to see if you can delete objects which are locked. - if(!obj->permMove()) - { - locked_but_deleteable_object = true; - } - if(!obj->permCopy()) - { - no_copy_but_deleteable_object = true; - } - if(!obj->permYouOwner()) - { - all_owned_by_you = false; - } - } - - if( 0 == deleteable_count ) - { - make_ui_sound("UISndInvalidOp"); - return; - } - - LLNotification::Params params("ConfirmObjectDeleteLock"); - params.functor.function(boost::bind(&LLSelectMgr::confirmDelete, _1, _2, getSelection())); - - if(locked_but_deleteable_object || - no_copy_but_deleteable_object || - !all_owned_by_you) - { - // convert any transient pie-menu selections to full selection so this operation - // has some context - // NOTE: if user cancels delete operation, this will potentially leave objects selected outside of build mode - // but this is ok, if not ideal - convertTransient(); - - //This is messy, but needed to get all english our of the UI. - if(locked_but_deleteable_object && !no_copy_but_deleteable_object && all_owned_by_you) - { - //Locked only - params.name("ConfirmObjectDeleteLock"); - } - else if(!locked_but_deleteable_object && no_copy_but_deleteable_object && all_owned_by_you) - { - //No Copy only - params.name("ConfirmObjectDeleteNoCopy"); - } - else if(!locked_but_deleteable_object && !no_copy_but_deleteable_object && !all_owned_by_you) - { - //not owned only - params.name("ConfirmObjectDeleteNoOwn"); - } - else if(locked_but_deleteable_object && no_copy_but_deleteable_object && all_owned_by_you) - { - //locked and no copy - params.name("ConfirmObjectDeleteLockNoCopy"); - } - else if(locked_but_deleteable_object && !no_copy_but_deleteable_object && !all_owned_by_you) - { - //locked and not owned - params.name("ConfirmObjectDeleteLockNoOwn"); - } - else if(!locked_but_deleteable_object && no_copy_but_deleteable_object && !all_owned_by_you) - { - //no copy and not owned - params.name("ConfirmObjectDeleteNoCopyNoOwn"); - } - else - { - //locked, no copy and not owned - params.name("ConfirmObjectDeleteLockNoCopyNoOwn"); - } - - LLNotifications::instance().add(params); - } - else - { - LLNotifications::instance().forceResponse(params, 0); - } -} - -// static -bool LLSelectMgr::confirmDelete(const LLSD& notification, const LLSD& response, LLObjectSelectionHandle handle) -{ - S32 option = LLNotification::getSelectedOption(notification, response); - if (!handle->getObjectCount()) - { - LL_WARNS() << "Nothing to delete!" << LL_ENDL; - return false; - } - - switch(option) - { - case 0: - { - // TODO: Make sure you have delete permissions on all of them. - const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - // attempt to derez into the trash. - LLDeRezInfo info(DRD_TRASH, trash_id); - LLSelectMgr::getInstance()->sendListToRegions("DeRezObject", - packDeRezHeader, - packObjectLocalID, - logNoOp, - (void*) &info, - SEND_ONLY_ROOTS); - // VEFFECT: Delete Object - one effect for all deletes - if (LLSelectMgr::getInstance()->mSelectedObjects->mSelectType != SELECT_TYPE_HUD) - { - LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_POINT, true); - effectp->setPositionGlobal( LLSelectMgr::getInstance()->getSelectionCenterGlobal() ); - effectp->setColor(LLColor4U(gAgent.getEffectColor())); - F32 duration = 0.5f; - duration += LLSelectMgr::getInstance()->mSelectedObjects->getObjectCount() / 64.f; - effectp->setDuration(duration); - } - - gAgentCamera.setLookAt(LOOKAT_TARGET_CLEAR); - - // Keep track of how many objects have been deleted. - add(LLStatViewer::DELETE_OBJECT, LLSelectMgr::getInstance()->mSelectedObjects->getObjectCount()); - } - break; - case 1: - default: - break; - } - return false; -} - - -void LLSelectMgr::selectForceDelete() -{ - sendListToRegions( - "ObjectDelete", - packDeleteHeader, - packObjectLocalID, - logNoOp, - (void*)true, - SEND_ONLY_ROOTS); -} - -bool LLSelectMgr::selectGetEditMoveLinksetPermissions(bool &move, bool &modify) -{ - move = true; - modify = true; - bool selecting_linked_set = !gSavedSettings.getBOOL("EditLinkedParts"); - - for (LLObjectSelection::iterator iter = getSelection()->begin(); - iter != getSelection()->end(); iter++) - { - LLSelectNode* nodep = *iter; - LLViewerObject* object = nodep->getObject(); - if (!object || !nodep->mValid) - { - move = false; - modify = false; - return false; - } - - LLViewerObject *root_object = object->getRootEdit(); - bool this_object_movable = false; - if (object->permMove() && !object->isPermanentEnforced() && - ((root_object == NULL) || !root_object->isPermanentEnforced()) && - (object->permModify() || selecting_linked_set)) - { - this_object_movable = true; - } - move = move && this_object_movable; - modify = modify && object->permModify(); - } - - return true; -} - -void LLSelectMgr::selectGetAggregateSaleInfo(U32 &num_for_sale, - bool &is_for_sale_mixed, - bool &is_sale_price_mixed, - S32 &total_sale_price, - S32 &individual_sale_price) -{ - num_for_sale = 0; - is_for_sale_mixed = false; - is_sale_price_mixed = false; - total_sale_price = 0; - individual_sale_price = 0; - - - // Empty set. - if (getSelection()->root_begin() == getSelection()->root_end()) - return; - - LLSelectNode *node = *(getSelection()->root_begin()); - const bool first_node_for_sale = node->mSaleInfo.isForSale(); - const S32 first_node_sale_price = node->mSaleInfo.getSalePrice(); - - for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); - iter != getSelection()->root_end(); iter++) - { - LLSelectNode* node = *iter; - const bool node_for_sale = node->mSaleInfo.isForSale(); - const S32 node_sale_price = node->mSaleInfo.getSalePrice(); - - // Set mixed if the fields don't match the first node's fields. - if (node_for_sale != first_node_for_sale) - is_for_sale_mixed = true; - if (node_sale_price != first_node_sale_price) - is_sale_price_mixed = true; - - if (node_for_sale) - { - total_sale_price += node_sale_price; - num_for_sale ++; - } - } - - individual_sale_price = first_node_sale_price; - if (is_for_sale_mixed) - { - is_sale_price_mixed = true; - individual_sale_price = 0; - } -} - -// returns true if all nodes are valid. method also stores an -// accumulated sale info. -bool LLSelectMgr::selectGetSaleInfo(LLSaleInfo& result_sale_info) -{ - bool first = true; - LLSaleInfo sale_info; - for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); - iter != getSelection()->root_end(); iter++ ) - { - LLSelectNode* node = *iter; - if (!node->mValid) - { - return false; - } - - if (first) - { - sale_info = node->mSaleInfo; - first = false; - } - else - { - sale_info.accumulate(node->mSaleInfo); - } - } - - result_sale_info = sale_info; - - return true; -} - -bool LLSelectMgr::selectGetAggregatePermissions(LLAggregatePermissions& result_perm) -{ - bool first = true; - LLAggregatePermissions perm; - for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); - iter != getSelection()->root_end(); iter++ ) - { - LLSelectNode* node = *iter; - if (!node->mValid) - { - return false; - } - - if (first) - { - perm = node->mAggregatePerm; - first = false; - } - else - { - perm.aggregate(node->mAggregatePerm); - } - } - - result_perm = perm; - - return true; -} - -bool LLSelectMgr::selectGetAggregateTexturePermissions(LLAggregatePermissions& result_perm) -{ - bool first = true; - LLAggregatePermissions perm; - for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); - iter != getSelection()->root_end(); iter++ ) - { - LLSelectNode* node = *iter; - if (!node->mValid) - { - return false; - } - - LLAggregatePermissions t_perm = node->getObject()->permYouOwner() ? node->mAggregateTexturePermOwner : node->mAggregateTexturePerm; - if (first) - { - perm = t_perm; - first = false; - } - else - { - perm.aggregate(t_perm); - } - } - - result_perm = perm; - - return true; -} - -bool LLSelectMgr::isMovableAvatarSelected() -{ - if (mAllowSelectAvatar && getSelection()->getObjectCount() == 1) - { - // nothing but avatar should be selected, so check that - // there is only one selected object and it is a root - LLViewerObject* obj = getSelection()->getFirstRootObject(); - return obj && obj->isAvatar() && getSelection()->getFirstMoveableNode(true); - } - return false; -} - -//-------------------------------------------------------------------- -// Duplicate objects -//-------------------------------------------------------------------- - -// JC - If this doesn't work right, duplicate the selection list -// before doing anything, do a deselect, then send the duplicate -// messages. -struct LLDuplicateData -{ - LLVector3 offset; - U32 flags; -}; - -void LLSelectMgr::selectDuplicate(const LLVector3& offset, bool select_copy) -{ - if (mSelectedObjects->isAttachment()) - { - //RN: do not duplicate attachments - make_ui_sound("UISndInvalidOp"); - return; - } - if (!canDuplicate()) - { - LLSelectNode* node = getSelection()->getFirstRootNode(NULL, true); - if (node) - { - LLSD args; - args["OBJ_NAME"] = node->mName; - LLNotificationsUtil::add("NoCopyPermsNoObject", args); - return; - } - } - LLDuplicateData data; - - data.offset = offset; - data.flags = (select_copy ? FLAGS_CREATE_SELECTED : 0x0); - - sendListToRegions("ObjectDuplicate", packDuplicateHeader, packDuplicate, logNoOp, &data, SEND_ONLY_ROOTS); - - if (select_copy) - { - // the new copy will be coming in selected - deselectAll(); - } - else - { - for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); - iter != getSelection()->root_end(); iter++ ) - { - LLSelectNode* node = *iter; - node->mDuplicated = true; - node->mDuplicatePos = node->getObject()->getPositionGlobal(); - node->mDuplicateRot = node->getObject()->getRotation(); - } - } -} - -void LLSelectMgr::repeatDuplicate() -{ - if (mSelectedObjects->isAttachment()) - { - //RN: do not duplicate attachments - make_ui_sound("UISndInvalidOp"); - return; - } - - std::vector non_duplicated_objects; - - for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); - iter != getSelection()->root_end(); iter++ ) - { - LLSelectNode* node = *iter; - if (!node->mDuplicated) - { - non_duplicated_objects.push_back(node->getObject()); - } - } - - // make sure only previously duplicated objects are selected - for (std::vector::iterator iter = non_duplicated_objects.begin(); - iter != non_duplicated_objects.end(); ++iter) - { - LLViewerObject* objectp = *iter; - deselectObjectAndFamily(objectp); - } - - // duplicate objects in place - LLDuplicateData data; - - data.offset = LLVector3::zero; - data.flags = 0x0; - - sendListToRegions("ObjectDuplicate", packDuplicateHeader, packDuplicate, logNoOp, &data, SEND_ONLY_ROOTS); - - // move current selection based on delta from duplication position and update duplication position - for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); - iter != getSelection()->root_end(); iter++ ) - { - LLSelectNode* node = *iter; - if (node->mDuplicated) - { - LLQuaternion cur_rot = node->getObject()->getRotation(); - LLQuaternion rot_delta = (~node->mDuplicateRot * cur_rot); - LLQuaternion new_rot = cur_rot * rot_delta; - LLVector3d cur_pos = node->getObject()->getPositionGlobal(); - LLVector3d new_pos = cur_pos + ((cur_pos - node->mDuplicatePos) * rot_delta); - - node->mDuplicatePos = node->getObject()->getPositionGlobal(); - node->mDuplicateRot = node->getObject()->getRotation(); - node->getObject()->setPositionGlobal(new_pos); - node->getObject()->setRotation(new_rot); - } - } - - sendMultipleUpdate(UPD_ROTATION | UPD_POSITION); -} - -// static -void LLSelectMgr::packDuplicate( LLSelectNode* node, void *duplicate_data ) -{ - gMessageSystem->nextBlockFast(_PREHASH_ObjectData); - gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, node->getObject()->getLocalID()); -} - - -//-------------------------------------------------------------------- -// Duplicate On Ray -//-------------------------------------------------------------------- - -// Duplicates the selected objects, but places the copy along a cast -// ray. -struct LLDuplicateOnRayData -{ - LLVector3 mRayStartRegion; - LLVector3 mRayEndRegion; - bool mBypassRaycast; - bool mRayEndIsIntersection; - LLUUID mRayTargetID; - bool mCopyCenters; - bool mCopyRotates; - U32 mFlags; -}; - -void LLSelectMgr::selectDuplicateOnRay(const LLVector3 &ray_start_region, - const LLVector3 &ray_end_region, - bool bypass_raycast, - bool ray_end_is_intersection, - const LLUUID &ray_target_id, - bool copy_centers, - bool copy_rotates, - bool select_copy) -{ - if (mSelectedObjects->isAttachment()) - { - // do not duplicate attachments - make_ui_sound("UISndInvalidOp"); - return; - } - - LLDuplicateOnRayData data; - - data.mRayStartRegion = ray_start_region; - data.mRayEndRegion = ray_end_region; - data.mBypassRaycast = bypass_raycast; - data.mRayEndIsIntersection = ray_end_is_intersection; - data.mRayTargetID = ray_target_id; - data.mCopyCenters = copy_centers; - data.mCopyRotates = copy_rotates; - data.mFlags = (select_copy ? FLAGS_CREATE_SELECTED : 0x0); - - sendListToRegions("ObjectDuplicateOnRay", - packDuplicateOnRayHead, packObjectLocalID, logNoOp, &data, SEND_ONLY_ROOTS); - - if (select_copy) - { - // the new copy will be coming in selected - deselectAll(); - } -} - -// static -void LLSelectMgr::packDuplicateOnRayHead(void *user_data) -{ - LLMessageSystem *msg = gMessageSystem; - LLDuplicateOnRayData *data = (LLDuplicateOnRayData *)user_data; - - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); - msg->addUUIDFast(_PREHASH_GroupID, gAgent.getGroupID() ); - msg->addVector3Fast(_PREHASH_RayStart, data->mRayStartRegion ); - msg->addVector3Fast(_PREHASH_RayEnd, data->mRayEndRegion ); - msg->addBOOLFast(_PREHASH_BypassRaycast, data->mBypassRaycast ); - msg->addBOOLFast(_PREHASH_RayEndIsIntersection, data->mRayEndIsIntersection ); - msg->addBOOLFast(_PREHASH_CopyCenters, data->mCopyCenters ); - msg->addBOOLFast(_PREHASH_CopyRotates, data->mCopyRotates ); - msg->addUUIDFast(_PREHASH_RayTargetID, data->mRayTargetID ); - msg->addU32Fast(_PREHASH_DuplicateFlags, data->mFlags ); -} - - - -//------------------------------------------------------------------------ -// Object position, scale, rotation update, all-in-one -//------------------------------------------------------------------------ - -void LLSelectMgr::sendMultipleUpdate(U32 type) -{ - if (type == UPD_NONE) return; - // send individual updates when selecting textures or individual objects - ESendType send_type = (!gSavedSettings.getBOOL("EditLinkedParts") && !getTEMode()) ? SEND_ONLY_ROOTS : SEND_ROOTS_FIRST; - if (send_type == SEND_ONLY_ROOTS) - { - // tell simulator to apply to whole linked sets - type |= UPD_LINKED_SETS; - } - - sendListToRegions( - "MultipleObjectUpdate", - packAgentAndSessionID, - packMultipleUpdate, - logNoOp, - &type, - send_type); -} - -// static -void LLSelectMgr::packMultipleUpdate(LLSelectNode* node, void *user_data) -{ - LLViewerObject* object = node->getObject(); - U32 *type32 = (U32 *)user_data; - U8 type = (U8)*type32; - U8 data[256]; - - gMessageSystem->nextBlockFast(_PREHASH_ObjectData); - gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, object->getLocalID() ); - gMessageSystem->addU8Fast(_PREHASH_Type, type ); - - S32 offset = 0; - - // JC: You MUST pack the data in this order. The receiving - // routine process_multiple_update_message on simulator will - // extract them in this order. - - if (type & UPD_POSITION) - { - htolememcpy(&data[offset], &(object->getPosition().mV), MVT_LLVector3, 12); - offset += 12; - } - if (type & UPD_ROTATION) - { - LLQuaternion quat = object->getRotation(); - LLVector3 vec = quat.packToVector3(); - htolememcpy(&data[offset], &(vec.mV), MVT_LLQuaternion, 12); - offset += 12; - } - if (type & UPD_SCALE) - { - //LL_INFOS() << "Sending object scale " << object->getScale() << LL_ENDL; - htolememcpy(&data[offset], &(object->getScale().mV), MVT_LLVector3, 12); - offset += 12; - } - gMessageSystem->addBinaryDataFast(_PREHASH_Data, data, offset); -} - -//------------------------------------------------------------------------ -// Ownership -//------------------------------------------------------------------------ -struct LLOwnerData -{ - LLUUID owner_id; - LLUUID group_id; - bool override; -}; - -void LLSelectMgr::sendOwner(const LLUUID& owner_id, - const LLUUID& group_id, - bool override) -{ - LLOwnerData data; - - data.owner_id = owner_id; - data.group_id = group_id; - data.override = override; - - sendListToRegions("ObjectOwner", packOwnerHead, packObjectLocalID, logNoOp, &data, SEND_ONLY_ROOTS); -} - -// static -void LLSelectMgr::packOwnerHead(void *user_data) -{ - LLOwnerData *data = (LLOwnerData *)user_data; - - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); - gMessageSystem->nextBlockFast(_PREHASH_HeaderData); - gMessageSystem->addBOOLFast(_PREHASH_Override, data->override); - gMessageSystem->addUUIDFast(_PREHASH_OwnerID, data->owner_id); - gMessageSystem->addUUIDFast(_PREHASH_GroupID, data->group_id); -} - -//------------------------------------------------------------------------ -// Group -//------------------------------------------------------------------------ - -void LLSelectMgr::sendGroup(const LLUUID& group_id) -{ - LLUUID local_group_id(group_id); - sendListToRegions("ObjectGroup", packAgentAndSessionAndGroupID, packObjectLocalID, logNoOp, &local_group_id, SEND_ONLY_ROOTS); -} - - -//------------------------------------------------------------------------ -// Buy -//------------------------------------------------------------------------ - -struct LLBuyData -{ - std::vector mObjectsSent; - LLUUID mCategoryID; - LLSaleInfo mSaleInfo; -}; - -// *NOTE: does not work for multiple object buy, which UI does not -// currently support sale info is used for verification only, if it -// doesn't match region info then sale is canceled Need to get sale -// info -as displayed in the UI- for every item. -void LLSelectMgr::sendBuy(const LLUUID& buyer_id, const LLUUID& category_id, const LLSaleInfo sale_info) -{ - LLBuyData buy; - buy.mCategoryID = category_id; - buy.mSaleInfo = sale_info; - sendListToRegions("ObjectBuy", packAgentGroupAndCatID, packBuyObjectIDs, logNoOp, &buy, SEND_ONLY_ROOTS); -} - -// static -void LLSelectMgr::packBuyObjectIDs(LLSelectNode* node, void* data) -{ - LLBuyData* buy = (LLBuyData*)data; - - LLViewerObject* object = node->getObject(); - if (std::find(buy->mObjectsSent.begin(), buy->mObjectsSent.end(), object) == buy->mObjectsSent.end()) - { - buy->mObjectsSent.push_back(object); - gMessageSystem->nextBlockFast(_PREHASH_ObjectData); - gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, object->getLocalID() ); - gMessageSystem->addU8Fast(_PREHASH_SaleType, buy->mSaleInfo.getSaleType()); - gMessageSystem->addS32Fast(_PREHASH_SalePrice, buy->mSaleInfo.getSalePrice()); - } -} - -//------------------------------------------------------------------------ -// Permissions -//------------------------------------------------------------------------ - -struct LLPermData -{ - U8 mField; - bool mSet; - U32 mMask; - bool mOverride; -}; - -// TODO: Make this able to fail elegantly. -void LLSelectMgr::selectionSetObjectPermissions(U8 field, - bool set, - U32 mask, - bool override) -{ - LLPermData data; - - data.mField = field; - data.mSet = set; - data.mMask = mask; - data.mOverride = override; - - sendListToRegions("ObjectPermissions", packPermissionsHead, packPermissions, logNoOp, &data, SEND_ONLY_ROOTS); -} - -void LLSelectMgr::packPermissionsHead(void* user_data) -{ - LLPermData* data = (LLPermData*)user_data; - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - gMessageSystem->nextBlockFast(_PREHASH_HeaderData); - gMessageSystem->addBOOLFast(_PREHASH_Override, data->mOverride); -} - - -// Now that you've added a bunch of objects, send a select message -// on the entire list for efficiency. -/* -void LLSelectMgr::sendSelect() -{ - LL_ERRS() << "Not implemented" << LL_ENDL; -} -*/ - -void LLSelectMgr::deselectAll() -{ - if (!mSelectedObjects->getNumNodes()) - { - return; - } - - // Zap the angular velocity, as the sim will set it to zero - for (LLObjectSelection::iterator iter = mSelectedObjects->begin(); - iter != mSelectedObjects->end(); iter++ ) - { - LLViewerObject *objectp = (*iter)->getObject(); - objectp->setAngularVelocity( 0,0,0 ); - objectp->setVelocity( 0,0,0 ); - } - - sendListToRegions( - "ObjectDeselect", - packAgentAndSessionID, - packObjectLocalID, - logNoOp, - NULL, - SEND_INDIVIDUALS); - - removeAll(); - - mLastSentSelectionCenterGlobal.clearVec(); - - updatePointAt(); -} - -void LLSelectMgr::deselectAllForStandingUp() -{ - /* - This function is similar deselectAll() except for the first if statement - which was removed. This is needed as a workaround for DEV-2854 - */ - - // Zap the angular velocity, as the sim will set it to zero - for (LLObjectSelection::iterator iter = mSelectedObjects->begin(); - iter != mSelectedObjects->end(); iter++ ) - { - LLViewerObject *objectp = (*iter)->getObject(); - objectp->setAngularVelocity( 0,0,0 ); - objectp->setVelocity( 0,0,0 ); - } - - sendListToRegions( - "ObjectDeselect", - packAgentAndSessionID, - packObjectLocalID, - logNoOp, - NULL, - SEND_INDIVIDUALS); - - removeAll(); - - mLastSentSelectionCenterGlobal.clearVec(); - - updatePointAt(); -} - -void LLSelectMgr::deselectUnused() -{ - // no more outstanding references to this selection - if (mSelectedObjects->getNumRefs() == 1) - { - deselectAll(); - } -} - -void LLSelectMgr::convertTransient() -{ - LLObjectSelection::iterator node_it; - for (node_it = mSelectedObjects->begin(); node_it != mSelectedObjects->end(); ++node_it) - { - LLSelectNode *nodep = *node_it; - nodep->setTransient(false); - } -} - -void LLSelectMgr::deselectAllIfTooFar() -{ - if (mSelectedObjects->isEmpty() || mSelectedObjects->mSelectType == SELECT_TYPE_HUD) - { - return; - } - - // HACK: Don't deselect when we're navigating to rate an object's - // owner or creator. JC - if (gMenuObject->getVisible()) - { - return; - } - - LLVector3d selectionCenter = getSelectionCenterGlobal(); - if (gSavedSettings.getBOOL("LimitSelectDistance") - && (!mSelectedObjects->getPrimaryObject() || !mSelectedObjects->getPrimaryObject()->isAvatar()) - && (mSelectedObjects->getPrimaryObject() != LLViewerMediaFocus::getInstance()->getFocusedObject()) - && !mSelectedObjects->isAttachment() - && !selectionCenter.isExactlyZero()) - { - F32 deselect_dist = gSavedSettings.getF32("MaxSelectDistance"); - F32 deselect_dist_sq = deselect_dist * deselect_dist; - - LLVector3d select_delta = gAgent.getPositionGlobal() - selectionCenter; - F32 select_dist_sq = (F32) select_delta.magVecSquared(); - - if (select_dist_sq > deselect_dist_sq) - { - if (mDebugSelectMgr) - { - LL_INFOS() << "Selection manager: auto-deselecting, select_dist = " << (F32) sqrt(select_dist_sq) << LL_ENDL; - LL_INFOS() << "agent pos global = " << gAgent.getPositionGlobal() << LL_ENDL; - LL_INFOS() << "selection pos global = " << selectionCenter << LL_ENDL; - } - - deselectAll(); - } - } -} - - -void LLSelectMgr::selectionSetObjectName(const std::string& name) -{ - std::string name_copy(name); - - // we only work correctly if 1 object is selected. - if(mSelectedObjects->getRootObjectCount() == 1) - { - sendListToRegions("ObjectName", - packAgentAndSessionID, - packObjectName, - logNoOp, - (void*)(&name_copy), - SEND_ONLY_ROOTS); - } - else if(mSelectedObjects->getObjectCount() == 1) - { - sendListToRegions("ObjectName", - packAgentAndSessionID, - packObjectName, - logNoOp, - (void*)(&name_copy), - SEND_INDIVIDUALS); - } -} - -void LLSelectMgr::selectionSetObjectDescription(const std::string& desc) -{ - std::string desc_copy(desc); - - // we only work correctly if 1 object is selected. - if(mSelectedObjects->getRootObjectCount() == 1) - { - sendListToRegions("ObjectDescription", - packAgentAndSessionID, - packObjectDescription, - logNoOp, - (void*)(&desc_copy), - SEND_ONLY_ROOTS); - } - else if(mSelectedObjects->getObjectCount() == 1) - { - sendListToRegions("ObjectDescription", - packAgentAndSessionID, - packObjectDescription, - logNoOp, - (void*)(&desc_copy), - SEND_INDIVIDUALS); - } -} - -void LLSelectMgr::selectionSetObjectCategory(const LLCategory& category) -{ - // for now, we only want to be able to set one root category at - // a time. - if(mSelectedObjects->getRootObjectCount() != 1) return; - sendListToRegions("ObjectCategory", - packAgentAndSessionID, - packObjectCategory, - logNoOp, - (void*)(&category), - SEND_ONLY_ROOTS); -} - -void LLSelectMgr::selectionSetObjectSaleInfo(const LLSaleInfo& sale_info) -{ - sendListToRegions("ObjectSaleInfo", - packAgentAndSessionID, - packObjectSaleInfo, - logNoOp, - (void*)(&sale_info), - SEND_ONLY_ROOTS); -} - -//---------------------------------------------------------------------- -// Attachments -//---------------------------------------------------------------------- - -void LLSelectMgr::sendAttach(U8 attachment_point, bool replace) -{ - sendAttach(mSelectedObjects, attachment_point, replace); -} - -void LLSelectMgr::sendAttach(LLObjectSelectionHandle selection_handle, U8 attachment_point, bool replace) -{ - if (selection_handle.isNull()) - { - return; - } - - LLViewerObject* attach_object = selection_handle->getFirstRootObject(); - - if (!attach_object || !isAgentAvatarValid() || selection_handle->mSelectType != SELECT_TYPE_WORLD) - { - return; - } - - bool build_mode = LLToolMgr::getInstance()->inEdit(); - // Special case: Attach to default location for this object. - if (0 == attachment_point || - get_if_there(gAgentAvatarp->mAttachmentPoints, (S32)attachment_point, (LLViewerJointAttachment*)NULL)) - { - if (!replace || attachment_point != 0) - { - // If we know the attachment point then we got here by clicking an - // "Attach to..." context menu item, so we should add, not replace. - attachment_point |= ATTACHMENT_ADD; - } - - sendListToRegions( - selection_handle, - "ObjectAttach", - packAgentIDAndSessionAndAttachment, - packObjectIDAndRotation, - logAttachmentRequest, - &attachment_point, - SEND_ONLY_ROOTS ); - if (!build_mode) - { - // After "ObjectAttach" server will unsubscribe us from properties updates - // so either deselect objects or resend selection after attach packet reaches server - // In case of build_mode LLPanelObjectInventory::refresh() will deal with selection - // Still unsubscribe even in case selection_handle is not current selection - deselectAll(); - } - } -} - -void LLSelectMgr::sendDetach() -{ - if (!mSelectedObjects->getNumNodes() || mSelectedObjects->mSelectType == SELECT_TYPE_WORLD) - { - return; - } - - sendListToRegions( - "ObjectDetach", - packAgentAndSessionID, - packObjectLocalID, - logDetachRequest, - NULL, - SEND_ONLY_ROOTS ); -} - - -void LLSelectMgr::sendDropAttachment() -{ - if (!mSelectedObjects->getNumNodes() || mSelectedObjects->mSelectType == SELECT_TYPE_WORLD) - { - return; - } - - sendListToRegions( - "ObjectDrop", - packAgentAndSessionID, - packObjectLocalID, - logDetachRequest, - NULL, - SEND_ONLY_ROOTS); -} - -//---------------------------------------------------------------------- -// Links -//---------------------------------------------------------------------- - -void LLSelectMgr::sendLink() -{ - if (!mSelectedObjects->getNumNodes()) - { - return; - } - - sendListToRegions( - "ObjectLink", - packAgentAndSessionID, - packObjectLocalID, - logNoOp, - NULL, - SEND_ONLY_ROOTS); -} - -void LLSelectMgr::sendDelink() -{ - if (!mSelectedObjects->getNumNodes()) - { - return; - } - - struct f : public LLSelectedObjectFunctor - { //on delink, any modifyable object should - f() {} - - virtual bool apply(LLViewerObject* object) - { - if (object->permModify()) - { - if (object->getPhysicsShapeType() == LLViewerObject::PHYSICS_SHAPE_NONE) - { - object->setPhysicsShapeType(LLViewerObject::PHYSICS_SHAPE_CONVEX_HULL); - object->updateFlags(); - } - } - return true; - } - } sendfunc; - getSelection()->applyToObjects(&sendfunc); - - - // Delink needs to send individuals so you can unlink a single object from - // a linked set. - sendListToRegions( - "ObjectDelink", - packAgentAndSessionID, - packObjectLocalID, - logNoOp, - NULL, - SEND_INDIVIDUALS); -} - - -//---------------------------------------------------------------------- -// Hinges -//---------------------------------------------------------------------- - -/* -void LLSelectMgr::sendHinge(U8 type) -{ - if (!mSelectedObjects->getNumNodes()) - { - return; - } - - sendListToRegions( - "ObjectHinge", - packHingeHead, - packObjectLocalID, - &type, - SEND_ONLY_ROOTS); -} - - -void LLSelectMgr::sendDehinge() -{ - if (!mSelectedObjects->getNumNodes()) - { - return; - } - - sendListToRegions( - "ObjectDehinge", - packAgentAndSessionID, - packObjectLocalID, - NULL, - SEND_ONLY_ROOTS); -}*/ - -void LLSelectMgr::sendSelect() -{ - if (!mSelectedObjects->getNumNodes()) - { - return; - } - - sendListToRegions( - "ObjectSelect", - packAgentAndSessionID, - packObjectLocalID, - logNoOp, - NULL, - SEND_INDIVIDUALS); -} - -// static -void LLSelectMgr::packHingeHead(void *user_data) -{ - U8 *type = (U8 *)user_data; - - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); - gMessageSystem->nextBlockFast(_PREHASH_JointType); - gMessageSystem->addU8Fast(_PREHASH_Type, *type ); -} - - -void LLSelectMgr::selectionDump() -{ - struct f : public LLSelectedObjectFunctor - { - virtual bool apply(LLViewerObject* object) - { - object->dump(); - return true; - } - } func; - getSelection()->applyToObjects(&func); -} - -void LLSelectMgr::saveSelectedObjectColors() -{ - struct f : public LLSelectedNodeFunctor - { - virtual bool apply(LLSelectNode* node) - { - node->saveColors(); - return true; - } - } func; - getSelection()->applyToNodes(&func); -} - -void LLSelectMgr::saveSelectedShinyColors() -{ - struct f : public LLSelectedNodeFunctor - { - virtual bool apply(LLSelectNode* node) - { - node->saveShinyColors(); - return true; - } - } func; - getSelection()->applyToNodes(&func); -} - -void LLSelectMgr::saveSelectedObjectTextures() -{ - // invalidate current selection so we update saved textures - struct f : public LLSelectedNodeFunctor - { - virtual bool apply(LLSelectNode* node) - { - node->mValid = false; - return true; - } - } func; - getSelection()->applyToNodes(&func); - - // request object properties message to get updated permissions data - sendSelect(); -} - - -// This routine should be called whenever a drag is initiated. -// also need to know to which simulator to send update message -void LLSelectMgr::saveSelectedObjectTransform(EActionType action_type) -{ - if (mSelectedObjects->isEmpty()) - { - // nothing selected, so nothing to save - return; - } - - struct f : public LLSelectedNodeFunctor - { - EActionType mActionType; - LLSelectMgr* mManager; - f(EActionType a, LLSelectMgr* p) : mActionType(a), mManager(p) {} - virtual bool apply(LLSelectNode* selectNode) - { - LLViewerObject* object = selectNode->getObject(); - if (!object) - { - return true; // skip - } - selectNode->mSavedPositionLocal = object->getPosition(); - if (object->isAttachment()) - { - if (object->isRootEdit()) - { - LLXform* parent_xform = object->mDrawable->getXform()->getParent(); - if (parent_xform) - { - selectNode->mSavedPositionGlobal = gAgent.getPosGlobalFromAgent((object->getPosition() * parent_xform->getWorldRotation()) + parent_xform->getWorldPosition()); - } - else - { - selectNode->mSavedPositionGlobal = object->getPositionGlobal(); - } - } - else - { - LLViewerObject* attachment_root = (LLViewerObject*)object->getParent(); - LLXform* parent_xform = attachment_root ? attachment_root->mDrawable->getXform()->getParent() : NULL; - if (parent_xform) - { - LLVector3 root_pos = (attachment_root->getPosition() * parent_xform->getWorldRotation()) + parent_xform->getWorldPosition(); - LLQuaternion root_rot = (attachment_root->getRotation() * parent_xform->getWorldRotation()); - selectNode->mSavedPositionGlobal = gAgent.getPosGlobalFromAgent((object->getPosition() * root_rot) + root_pos); - } - else - { - selectNode->mSavedPositionGlobal = object->getPositionGlobal(); - } - } - selectNode->mSavedRotation = object->getRenderRotation(); - } - else - { - selectNode->mSavedPositionGlobal = object->getPositionGlobal(); - selectNode->mSavedRotation = object->getRotationRegion(); - } - - selectNode->mSavedScale = object->getScale(); - selectNode->saveTextureScaleRatios(mManager->mTextureChannel); - return true; - } - } func(action_type, this); - getSelection()->applyToNodes(&func); - - mSavedSelectionBBox = getBBoxOfSelection(); -} - -struct LLSelectMgrApplyFlags : public LLSelectedObjectFunctor -{ - LLSelectMgrApplyFlags(U32 flags, bool state) : mFlags(flags), mState(state) {} - U32 mFlags; - bool mState; - virtual bool apply(LLViewerObject* object) - { - if ( object->permModify()) - { - if (object->isRoot()) // don't send for child objects - { - object->setFlags( mFlags, mState); - } - else if (FLAGS_WORLD & mFlags && ((LLViewerObject*)object->getRoot())->isSelected()) - { - // FLAGS_WORLD are shared by all items in linkset - object->setFlagsWithoutUpdate(FLAGS_WORLD & mFlags, mState); - } - }; - return true; - } -}; - -void LLSelectMgr::selectionUpdatePhysics(bool physics) -{ - LLSelectMgrApplyFlags func( FLAGS_USE_PHYSICS, physics); - getSelection()->applyToObjects(&func); -} - -void LLSelectMgr::selectionUpdateTemporary(bool is_temporary) -{ - LLSelectMgrApplyFlags func( FLAGS_TEMPORARY_ON_REZ, is_temporary); - getSelection()->applyToObjects(&func); -} - -void LLSelectMgr::selectionUpdatePhantom(bool is_phantom) -{ - LLSelectMgrApplyFlags func( FLAGS_PHANTOM, is_phantom); - getSelection()->applyToObjects(&func); -} - -//---------------------------------------------------------------------- -// Helpful packing functions for sendObjectMessage() -//---------------------------------------------------------------------- - -// static -void LLSelectMgr::packAgentIDAndSessionAndAttachment( void *user_data) -{ - U8 *attachment_point = (U8*)user_data; - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - gMessageSystem->addU8Fast(_PREHASH_AttachmentPoint, *attachment_point); -} - -// static -void LLSelectMgr::packAgentID( void *user_data) -{ - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); -} - -// static -void LLSelectMgr::packAgentAndSessionID(void* user_data) -{ - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); -} - -// static -void LLSelectMgr::packAgentAndGroupID(void* user_data) -{ - LLOwnerData *data = (LLOwnerData *)user_data; - - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, data->owner_id ); - gMessageSystem->addUUIDFast(_PREHASH_GroupID, data->group_id ); -} - -// static -void LLSelectMgr::packAgentAndSessionAndGroupID(void* user_data) -{ - LLUUID* group_idp = (LLUUID*) user_data; - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - gMessageSystem->addUUIDFast(_PREHASH_GroupID, *group_idp); -} - -// static -void LLSelectMgr::packDuplicateHeader(void* data) -{ - LLUUID group_id(gAgent.getGroupID()); - packAgentAndSessionAndGroupID(&group_id); - - LLDuplicateData* dup_data = (LLDuplicateData*) data; - - gMessageSystem->nextBlockFast(_PREHASH_SharedData); - gMessageSystem->addVector3Fast(_PREHASH_Offset, dup_data->offset); - gMessageSystem->addU32Fast(_PREHASH_DuplicateFlags, dup_data->flags); -} - -// static -void LLSelectMgr::packDeleteHeader(void* userdata) -{ - bool force = (bool)(intptr_t)userdata; - - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - gMessageSystem->addBOOLFast(_PREHASH_Force, force); -} - -// static -void LLSelectMgr::packAgentGroupAndCatID(void* user_data) -{ - LLBuyData* buy = (LLBuyData*)user_data; - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - gMessageSystem->addUUIDFast(_PREHASH_GroupID, gAgent.getGroupID()); - gMessageSystem->addUUIDFast(_PREHASH_CategoryID, buy->mCategoryID); -} - -//static -void LLSelectMgr::packDeRezHeader(void* user_data) -{ - LLDeRezInfo* info = (LLDeRezInfo*)user_data; - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - gMessageSystem->nextBlockFast(_PREHASH_AgentBlock); - gMessageSystem->addUUIDFast(_PREHASH_GroupID, gAgent.getGroupID()); - gMessageSystem->addU8Fast(_PREHASH_Destination, (U8)info->mDestination); - gMessageSystem->addUUIDFast(_PREHASH_DestinationID, info->mDestinationID); - LLUUID tid; - tid.generate(); - gMessageSystem->addUUIDFast(_PREHASH_TransactionID, tid); - const U8 PACKET = 1; - gMessageSystem->addU8Fast(_PREHASH_PacketCount, PACKET); - gMessageSystem->addU8Fast(_PREHASH_PacketNumber, PACKET); -} - -// static -void LLSelectMgr::packObjectID(LLSelectNode* node, void *user_data) -{ - gMessageSystem->nextBlockFast(_PREHASH_ObjectData); - gMessageSystem->addUUIDFast(_PREHASH_ObjectID, node->getObject()->mID ); -} - -void LLSelectMgr::packObjectIDAndRotation(LLSelectNode* node, void *user_data) -{ - gMessageSystem->nextBlockFast(_PREHASH_ObjectData); - gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, node->getObject()->getLocalID() ); - gMessageSystem->addQuatFast(_PREHASH_Rotation, node->getObject()->getRotation()); -} - -void LLSelectMgr::packObjectClickAction(LLSelectNode* node, void *user_data) -{ - gMessageSystem->nextBlockFast(_PREHASH_ObjectData); - gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, node->getObject()->getLocalID() ); - gMessageSystem->addU8("ClickAction", node->getObject()->getClickAction()); -} - -void LLSelectMgr::packObjectIncludeInSearch(LLSelectNode* node, void *user_data) -{ - gMessageSystem->nextBlockFast(_PREHASH_ObjectData); - gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, node->getObject()->getLocalID() ); - gMessageSystem->addBOOL("IncludeInSearch", node->getObject()->getIncludeInSearch()); -} - -// static -void LLSelectMgr::packObjectLocalID(LLSelectNode* node, void *) -{ - gMessageSystem->nextBlockFast(_PREHASH_ObjectData); - gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, node->getObject()->getLocalID()); -} - -// static -void LLSelectMgr::packObjectName(LLSelectNode* node, void* user_data) -{ - const std::string* name = (const std::string*)user_data; - if(!name->empty()) - { - gMessageSystem->nextBlockFast(_PREHASH_ObjectData); - gMessageSystem->addU32Fast(_PREHASH_LocalID, node->getObject()->getLocalID()); - gMessageSystem->addStringFast(_PREHASH_Name, *name); - } -} - -// static -void LLSelectMgr::packObjectDescription(LLSelectNode* node, void* user_data) -{ - const std::string* desc = (const std::string*)user_data; - if(desc) - { // Empty (non-null, but zero length) descriptions are OK - gMessageSystem->nextBlockFast(_PREHASH_ObjectData); - gMessageSystem->addU32Fast(_PREHASH_LocalID, node->getObject()->getLocalID()); - gMessageSystem->addStringFast(_PREHASH_Description, *desc); - } -} - -// static -void LLSelectMgr::packObjectCategory(LLSelectNode* node, void* user_data) -{ - LLCategory* category = (LLCategory*)user_data; - if(!category) return; - gMessageSystem->nextBlockFast(_PREHASH_ObjectData); - gMessageSystem->addU32Fast(_PREHASH_LocalID, node->getObject()->getLocalID()); - category->packMessage(gMessageSystem); -} - -// static -void LLSelectMgr::packObjectSaleInfo(LLSelectNode* node, void* user_data) -{ - LLSaleInfo* sale_info = (LLSaleInfo*)user_data; - if(!sale_info) return; - gMessageSystem->nextBlockFast(_PREHASH_ObjectData); - gMessageSystem->addU32Fast(_PREHASH_LocalID, node->getObject()->getLocalID()); - sale_info->packMessage(gMessageSystem); -} - -// static -void LLSelectMgr::packPhysics(LLSelectNode* node, void *user_data) -{ -} - -// static -void LLSelectMgr::packShape(LLSelectNode* node, void *user_data) -{ -} - -// static -void LLSelectMgr::packPermissions(LLSelectNode* node, void *user_data) -{ - LLPermData *data = (LLPermData *)user_data; - - gMessageSystem->nextBlockFast(_PREHASH_ObjectData); - gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, node->getObject()->getLocalID()); - - gMessageSystem->addU8Fast(_PREHASH_Field, data->mField); - gMessageSystem->addBOOLFast(_PREHASH_Set, data->mSet); - gMessageSystem->addU32Fast(_PREHASH_Mask, data->mMask); -} - -// Utility function to send some information to every region containing -// an object on the selection list. We want to do this to reduce the total -// number of packets sent by the viewer. -void LLSelectMgr::sendListToRegions(const std::string& message_name, - void (*pack_header)(void *user_data), - void (*pack_body)(LLSelectNode* node, void *user_data), - void (*log_func)(LLSelectNode* node, void *user_data), - void *user_data, - ESendType send_type) -{ - sendListToRegions(mSelectedObjects, message_name, pack_header, pack_body, log_func, user_data, send_type); -} -void LLSelectMgr::sendListToRegions(LLObjectSelectionHandle selected_handle, - const std::string& message_name, - void (*pack_header)(void *user_data), - void (*pack_body)(LLSelectNode* node, void *user_data), - void (*log_func)(LLSelectNode* node, void *user_data), - void *user_data, - ESendType send_type) -{ - LLSelectNode* node; - LLSelectNode* linkset_root = NULL; - LLViewerRegion* last_region; - LLViewerRegion* current_region; - S32 objects_in_this_packet = 0; - - bool link_operation = message_name == "ObjectLink"; - - if (mAllowSelectAvatar) - { - if (selected_handle->getObjectCount() == 1 - && selected_handle->getFirstObject() != NULL - && selected_handle->getFirstObject()->isAvatar()) - { - // Server doesn't move avatars at the moment, it is a local debug feature, - // but server does update position regularly, so do not drop mLastPositionLocal - // Position override for avatar gets reset in LLAgentCamera::resetView(). - } - else - { - // drop mLastPositionLocal (allow next update through) - resetObjectOverrides(selected_handle); - } - } - else - { - //clear update override data (allow next update through) - resetObjectOverrides(selected_handle); - } - - std::queue nodes_to_send; - - struct push_all : public LLSelectedNodeFunctor - { - std::queue& nodes_to_send; - push_all(std::queue& n) : nodes_to_send(n) {} - virtual bool apply(LLSelectNode* node) - { - if (node->getObject()) - { - nodes_to_send.push(node); - } - return true; - } - }; - struct push_some : public LLSelectedNodeFunctor - { - std::queue& nodes_to_send; - bool mRoots; - push_some(std::queue& n, bool roots) : nodes_to_send(n), mRoots(roots) {} - virtual bool apply(LLSelectNode* node) - { - if (node->getObject()) - { - bool is_root = node->getObject()->isRootEdit(); - if ((mRoots && is_root) || (!mRoots && !is_root)) - { - nodes_to_send.push(node); - } - } - return true; - } - }; - struct push_all pushall(nodes_to_send); - struct push_some pushroots(nodes_to_send, true); - struct push_some pushnonroots(nodes_to_send, false); - - switch(send_type) - { - case SEND_ONLY_ROOTS: - if(message_name == "ObjectBuy") - selected_handle->applyToRootNodes(&pushroots); - else - selected_handle->applyToRootNodes(&pushall); - - break; - case SEND_INDIVIDUALS: - selected_handle->applyToNodes(&pushall); - break; - case SEND_ROOTS_FIRST: - // first roots... - selected_handle->applyToNodes(&pushroots); - // then children... - selected_handle->applyToNodes(&pushnonroots); - break; - case SEND_CHILDREN_FIRST: - // first children... - selected_handle->applyToNodes(&pushnonroots); - // then roots... - selected_handle->applyToNodes(&pushroots); - break; - - default: - LL_ERRS() << "Bad send type " << send_type << " passed to SendListToRegions()" << LL_ENDL; - } - - // bail if nothing selected - if (nodes_to_send.empty()) - { - return; - } - - node = nodes_to_send.front(); - nodes_to_send.pop(); - - // cache last region information - current_region = node->getObject()->getRegion(); - - // Start duplicate message - // CRO: this isn't - gMessageSystem->newMessage(message_name.c_str()); - (*pack_header)(user_data); - - // For each object - while (node != NULL) - { - // remember the last region, look up the current one - last_region = current_region; - current_region = node->getObject()->getRegion(); - - // if to same simulator and message not too big - if ((current_region == last_region) - && (! gMessageSystem->isSendFull(NULL)) - && (objects_in_this_packet < MAX_OBJECTS_PER_PACKET)) - { - if (link_operation && linkset_root == NULL) - { - // linksets over 254 will be split into multiple messages, - // but we need to provide same root for all messages or we will get separate linksets - linkset_root = node; - } - // add another instance of the body of the data - (*pack_body)(node, user_data); - // do any related logging - (*log_func)(node, user_data); - ++objects_in_this_packet; - - // and on to the next object - if(nodes_to_send.empty()) - { - node = NULL; - } - else - { - node = nodes_to_send.front(); - nodes_to_send.pop(); - } - } - else - { - // otherwise send current message and start new one - gMessageSystem->sendReliable( last_region->getHost()); - objects_in_this_packet = 0; - - gMessageSystem->newMessage(message_name.c_str()); - (*pack_header)(user_data); - - if (linkset_root != NULL) - { - if (current_region != last_region) - { - // root should be in one region with the child, reset it - linkset_root = NULL; - } - else - { - // add root instance into new message - (*pack_body)(linkset_root, user_data); - ++objects_in_this_packet; - } - } - - // don't move to the next object, we still need to add the - // body data. - } - } - - // flush messages - if (gMessageSystem->getCurrentSendTotal() > 0) - { - gMessageSystem->sendReliable( current_region->getHost()); - } - else - { - gMessageSystem->clearMessage(); - } - - // LL_INFOS() << "sendListToRegions " << message_name << " obj " << objects_sent << " pkt " << packets_sent << LL_ENDL; -} - - -// -// Network communications -// - -void LLSelectMgr::requestObjectPropertiesFamily(LLViewerObject* object) -{ - LLMessageSystem* msg = gMessageSystem; - - msg->newMessageFast(_PREHASH_RequestObjectPropertiesFamily); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ObjectData); - msg->addU32Fast(_PREHASH_RequestFlags, 0x0 ); - msg->addUUIDFast(_PREHASH_ObjectID, object->mID ); - - LLViewerRegion* regionp = object->getRegion(); - msg->sendReliable( regionp->getHost() ); -} - - -// static -void LLSelectMgr::processObjectProperties(LLMessageSystem* msg, void** user_data) -{ - S32 i; - S32 count = msg->getNumberOfBlocksFast(_PREHASH_ObjectData); - for (i = 0; i < count; i++) - { - LLUUID id; - msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_ObjectID, id, i); - - LLUUID creator_id; - LLUUID owner_id; - LLUUID group_id; - LLUUID last_owner_id; - U64 creation_date; - LLUUID extra_id; - U32 base_mask, owner_mask, group_mask, everyone_mask, next_owner_mask; - LLSaleInfo sale_info; - LLCategory category; - LLAggregatePermissions ag_perms; - LLAggregatePermissions ag_texture_perms; - LLAggregatePermissions ag_texture_perms_owner; - - msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_CreatorID, creator_id, i); - msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_OwnerID, owner_id, i); - msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_GroupID, group_id, i); - msg->getU64Fast(_PREHASH_ObjectData, _PREHASH_CreationDate, creation_date, i); - msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_BaseMask, base_mask, i); - msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_OwnerMask, owner_mask, i); - msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_GroupMask, group_mask, i); - msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_EveryoneMask, everyone_mask, i); - msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_NextOwnerMask, next_owner_mask, i); - sale_info.unpackMultiMessage(msg, _PREHASH_ObjectData, i); - - ag_perms.unpackMessage(msg, _PREHASH_ObjectData, _PREHASH_AggregatePerms, i); - ag_texture_perms.unpackMessage(msg, _PREHASH_ObjectData, _PREHASH_AggregatePermTextures, i); - ag_texture_perms_owner.unpackMessage(msg, _PREHASH_ObjectData, _PREHASH_AggregatePermTexturesOwner, i); - category.unpackMultiMessage(msg, _PREHASH_ObjectData, i); - - S16 inv_serial = 0; - msg->getS16Fast(_PREHASH_ObjectData, _PREHASH_InventorySerial, inv_serial, i); - - LLUUID item_id; - msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_ItemID, item_id, i); - LLUUID folder_id; - msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_FolderID, folder_id, i); - LLUUID from_task_id; - msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_FromTaskID, from_task_id, i); - - msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_LastOwnerID, last_owner_id, i); - - std::string name; - msg->getStringFast(_PREHASH_ObjectData, _PREHASH_Name, name, i); - std::string desc; - msg->getStringFast(_PREHASH_ObjectData, _PREHASH_Description, desc, i); - - std::string touch_name; - msg->getStringFast(_PREHASH_ObjectData, _PREHASH_TouchName, touch_name, i); - std::string sit_name; - msg->getStringFast(_PREHASH_ObjectData, _PREHASH_SitName, sit_name, i); - - //unpack TE IDs - uuid_vec_t texture_ids; - S32 size = msg->getSizeFast(_PREHASH_ObjectData, i, _PREHASH_TextureID); - if (size > 0) - { - S8 packed_buffer[SELECT_MAX_TES * UUID_BYTES]; - msg->getBinaryDataFast(_PREHASH_ObjectData, _PREHASH_TextureID, packed_buffer, 0, i, SELECT_MAX_TES * UUID_BYTES); - - for (S32 buf_offset = 0; buf_offset < size; buf_offset += UUID_BYTES) - { - LLUUID tid; - memcpy(tid.mData, packed_buffer + buf_offset, UUID_BYTES); /* Flawfinder: ignore */ - texture_ids.push_back(tid); - } - } - - - // Iterate through nodes at end, since it can be on both the regular AND hover list - struct f : public LLSelectedNodeFunctor - { - LLUUID mID; - f(const LLUUID& id) : mID(id) {} - virtual bool apply(LLSelectNode* node) - { - return (node->getObject() && node->getObject()->mID == mID); - } - } func(id); - LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode(&func); - - if (!node) - { - LL_WARNS() << "Couldn't find object " << id << " selected." << LL_ENDL; - } - else - { - // save texture data as soon as we get texture perms first time - bool save_textures = !node->mValid; - if (node->mInventorySerial != inv_serial && node->getObject()) - { - node->getObject()->dirtyInventory(); - - // Even if this isn't object's first udpate, inventory changed - // and some of the applied textures might have been in inventory - // so update texture list. - save_textures = true; - } - - if (save_textures) - { - bool can_copy = false; - bool can_transfer = false; - - LLAggregatePermissions::EValue value = LLAggregatePermissions::AP_NONE; - if(node->getObject()->permYouOwner()) - { - value = ag_texture_perms_owner.getValue(PERM_COPY); - if (value == LLAggregatePermissions::AP_EMPTY || value == LLAggregatePermissions::AP_ALL) - { - can_copy = true; - } - value = ag_texture_perms_owner.getValue(PERM_TRANSFER); - if (value == LLAggregatePermissions::AP_EMPTY || value == LLAggregatePermissions::AP_ALL) - { - can_transfer = true; - } - } - else - { - value = ag_texture_perms.getValue(PERM_COPY); - if (value == LLAggregatePermissions::AP_EMPTY || value == LLAggregatePermissions::AP_ALL) - { - can_copy = true; - } - value = ag_texture_perms.getValue(PERM_TRANSFER); - if (value == LLAggregatePermissions::AP_EMPTY || value == LLAggregatePermissions::AP_ALL) - { - can_transfer = true; - } - } - - if (can_copy && can_transfer) - { - node->saveTextures(texture_ids); - } - - if (can_copy && can_transfer && node->getObject()->getVolume()) - { - uuid_vec_t material_ids; - gltf_materials_vec_t override_materials; - LLVOVolume* vobjp = (LLVOVolume*)node->getObject(); - for (int i = 0; i < vobjp->getNumTEs(); ++i) - { - material_ids.push_back(vobjp->getRenderMaterialID(i)); - - // Make a copy to ensure we won't affect live material - // with any potential changes nor live changes will be - // reflected in a saved copy. - // Like changes from local material (reuses pointer) or - // from live editor (revert mechanics might modify this) - LLGLTFMaterial* old_override = node->getObject()->getTE(i)->getGLTFMaterialOverride(); - if (old_override) - { - LLPointer mat = new LLGLTFMaterial(*old_override); - override_materials.push_back(mat); - } - else - { - override_materials.push_back(nullptr); - } - } - // processObjectProperties does not include overrides so this - // might need to be moved to LLGLTFMaterialOverrideDispatchHandler - node->saveGLTFMaterials(material_ids, override_materials); - } - } - - node->mValid = true; - node->mPermissions->init(creator_id, owner_id, - last_owner_id, group_id); - node->mPermissions->initMasks(base_mask, owner_mask, everyone_mask, group_mask, next_owner_mask); - node->mCreationDate = creation_date; - node->mItemID = item_id; - node->mFolderID = folder_id; - node->mFromTaskID = from_task_id; - node->mName.assign(name); - node->mDescription.assign(desc); - node->mSaleInfo = sale_info; - node->mAggregatePerm = ag_perms; - node->mAggregateTexturePerm = ag_texture_perms; - node->mAggregateTexturePermOwner = ag_texture_perms_owner; - node->mCategory = category; - node->mInventorySerial = inv_serial; - node->mSitName.assign(sit_name); - node->mTouchName.assign(touch_name); - } - } - - dialog_refresh_all(); - - // hack for left-click buy object - LLToolPie::selectionPropertiesReceived(); -} - -// static -void LLSelectMgr::processObjectPropertiesFamily(LLMessageSystem* msg, void** user_data) -{ - LLUUID id; - - U32 request_flags; - LLUUID creator_id; - LLUUID owner_id; - LLUUID group_id; - LLUUID extra_id; - U32 base_mask, owner_mask, group_mask, everyone_mask, next_owner_mask; - LLSaleInfo sale_info; - LLCategory category; - - msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_RequestFlags, request_flags ); - msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_ObjectID, id ); - msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_OwnerID, owner_id ); - msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_GroupID, group_id ); - msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_BaseMask, base_mask ); - msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_OwnerMask, owner_mask ); - msg->getU32Fast(_PREHASH_ObjectData,_PREHASH_GroupMask, group_mask ); - msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_EveryoneMask, everyone_mask ); - msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_NextOwnerMask, next_owner_mask); - sale_info.unpackMessage(msg, _PREHASH_ObjectData); - category.unpackMessage(msg, _PREHASH_ObjectData); - - LLUUID last_owner_id; - msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_LastOwnerID, last_owner_id ); - - // unpack name & desc - std::string name; - msg->getStringFast(_PREHASH_ObjectData, _PREHASH_Name, name); - - std::string desc; - msg->getStringFast(_PREHASH_ObjectData, _PREHASH_Description, desc); - - // the reporter widget askes the server for info about picked objects - if (request_flags & COMPLAINT_REPORT_REQUEST ) - { - LLFloaterReporter *reporterp = LLFloaterReg::findTypedInstance("reporter"); - if (reporterp) - { - LLAvatarName av_name; - LLAvatarNameCache::get(owner_id, &av_name); - reporterp->setPickedObjectProperties(name, av_name.getUserName(), owner_id); - } - } - else if (request_flags & OBJECT_PAY_REQUEST) - { - // check if the owner of the paid object is muted - LLMuteList::getInstance()->autoRemove(owner_id, LLMuteList::AR_MONEY); - } - - // Now look through all of the hovered nodes - struct f : public LLSelectedNodeFunctor - { - LLUUID mID; - f(const LLUUID& id) : mID(id) {} - virtual bool apply(LLSelectNode* node) - { - return (node->getObject() && node->getObject()->mID == mID); - } - } func(id); - LLSelectNode* node = LLSelectMgr::getInstance()->mHoverObjects->getFirstNode(&func); - - if (node) - { - node->mValid = true; - node->mPermissions->init(LLUUID::null, owner_id, - last_owner_id, group_id); - node->mPermissions->initMasks(base_mask, owner_mask, everyone_mask, group_mask, next_owner_mask); - node->mSaleInfo = sale_info; - node->mCategory = category; - node->mName.assign(name); - node->mDescription.assign(desc); - } - - dialog_refresh_all(); -} - - -// static -void LLSelectMgr::processForceObjectSelect(LLMessageSystem* msg, void**) -{ - bool reset_list; - msg->getBOOL("Header", "ResetList", reset_list); - - if (reset_list) - { - LLSelectMgr::getInstance()->deselectAll(); - } - - LLUUID full_id; - S32 local_id; - LLViewerObject* object; - std::vector objects; - S32 i; - S32 block_count = msg->getNumberOfBlocks("Data"); - - for (i = 0; i < block_count; i++) - { - msg->getS32("Data", "LocalID", local_id, i); - - gObjectList.getUUIDFromLocal(full_id, - local_id, - msg->getSenderIP(), - msg->getSenderPort()); - object = gObjectList.findObject(full_id); - if (object) - { - objects.push_back(object); - } - } - - // Don't select, just highlight - LLSelectMgr::getInstance()->highlightObjectAndFamily(objects); -} - -extern F32 gGLModelView[16]; - -void LLSelectMgr::updateSilhouettes() -{ - S32 num_sils_genned = 0; - - LLVector3d cameraPos = gAgentCamera.getCameraPositionGlobal(); - F32 currentCameraZoom = gAgentCamera.getCurrentCameraBuildOffset(); - - if (!mSilhouetteImagep) - { - mSilhouetteImagep = LLViewerTextureManager::getFetchedTextureFromFile("silhouette.j2c", FTT_LOCAL_FILE, true, LLGLTexture::BOOST_UI); - } - - mHighlightedObjects->cleanupNodes(); - - if((cameraPos - mLastCameraPos).magVecSquared() > SILHOUETTE_UPDATE_THRESHOLD_SQUARED * currentCameraZoom * currentCameraZoom) - { - struct f : public LLSelectedObjectFunctor - { - virtual bool apply(LLViewerObject* object) - { - object->setChanged(LLXform::SILHOUETTE); - return true; - } - } func; - getSelection()->applyToObjects(&func); - - mLastCameraPos = gAgentCamera.getCameraPositionGlobal(); - } - - std::vector changed_objects; - - updateSelectionSilhouette(mSelectedObjects, num_sils_genned, changed_objects); - if (mRectSelectedObjects.size() > 0) - { - //gGLSPipelineSelection.set(); - - //mSilhouetteImagep->bindTexture(); - //glAlphaFunc(GL_GREATER, sHighlightAlphaTest); - - std::set roots; - - // sync mHighlightedObjects with mRectSelectedObjects since the latter is rebuilt every frame and former - // persists from frame to frame to avoid regenerating object silhouettes - // mHighlightedObjects includes all siblings of rect selected objects - - bool select_linked_set = !gSavedSettings.getBOOL("EditLinkedParts"); - - // generate list of roots from current object selection - for (std::set >::iterator iter = mRectSelectedObjects.begin(); - iter != mRectSelectedObjects.end(); iter++) - { - LLViewerObject *objectp = *iter; - if (select_linked_set) - { - LLViewerObject *rootp = (LLViewerObject*)objectp->getRoot(); - roots.insert(rootp); - } - else - { - roots.insert(objectp); - } - } - - // remove highlight nodes not in roots list - std::vector remove_these_nodes; - std::vector remove_these_roots; - - for (LLObjectSelection::iterator iter = mHighlightedObjects->begin(); - iter != mHighlightedObjects->end(); iter++) - { - LLSelectNode* node = *iter; - LLViewerObject* objectp = node->getObject(); - if (!objectp) - continue; - if (objectp->isRoot() || !select_linked_set) - { - if (roots.count(objectp) == 0) - { - remove_these_nodes.push_back(node); - } - else - { - remove_these_roots.push_back(objectp); - } - } - else - { - LLViewerObject* rootp = (LLViewerObject*)objectp->getRoot(); - - if (roots.count(rootp) == 0) - { - remove_these_nodes.push_back(node); - } - } - } - - // remove all highlight nodes no longer in rectangle selection - for (std::vector::iterator iter = remove_these_nodes.begin(); - iter != remove_these_nodes.end(); ++iter) - { - LLSelectNode* nodep = *iter; - mHighlightedObjects->removeNode(nodep); - } - - // remove all root objects already being highlighted - for (std::vector::iterator iter = remove_these_roots.begin(); - iter != remove_these_roots.end(); ++iter) - { - LLViewerObject* objectp = *iter; - roots.erase(objectp); - } - - // add all new objects in rectangle selection - for (std::set::iterator iter = roots.begin(); - iter != roots.end(); iter++) - { - LLViewerObject* objectp = *iter; - if (!canSelectObject(objectp)) - { - continue; - } - - LLSelectNode* rect_select_root_node = new LLSelectNode(objectp, true); - rect_select_root_node->selectAllTEs(true); - - if (!select_linked_set) - { - rect_select_root_node->mIndividualSelection = true; - } - else - { - LLViewerObject::const_child_list_t& child_list = objectp->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); iter++) - { - LLViewerObject* child_objectp = *iter; - - if (!canSelectObject(child_objectp)) - { - continue; - } - - LLSelectNode* rect_select_node = new LLSelectNode(child_objectp, true); - rect_select_node->selectAllTEs(true); - mHighlightedObjects->addNodeAtEnd(rect_select_node); - } - } - - // Add the root last, to preserve order for link operations. - mHighlightedObjects->addNodeAtEnd(rect_select_root_node); - } - - num_sils_genned = 0; - - // render silhouettes for highlighted objects - //bool subtracting_from_selection = (gKeyboard->currentMask(true) == MASK_CONTROL); - for (S32 pass = 0; pass < 2; pass++) - { - for (LLObjectSelection::iterator iter = mHighlightedObjects->begin(); - iter != mHighlightedObjects->end(); iter++) - { - LLSelectNode* node = *iter; - LLViewerObject* objectp = node->getObject(); - if (!objectp) - continue; - - // do roots first, then children so that root flags are cleared ASAP - bool roots_only = (pass == 0); - bool is_root = objectp->isRootEdit(); - if (roots_only != is_root) - { - continue; - } - - if (!node->mSilhouetteExists - || objectp->isChanged(LLXform::SILHOUETTE) - || (objectp->getParent() && objectp->getParent()->isChanged(LLXform::SILHOUETTE))) - { - if (num_sils_genned++ < MAX_SILS_PER_FRAME) - { - generateSilhouette(node, LLViewerCamera::getInstance()->getOrigin()); - changed_objects.push_back(objectp); - } - else if (objectp->isAttachment() && objectp->getRootEdit()->mDrawable.notNull()) - { - //RN: hack for orthogonal projection of HUD attachments - LLViewerJointAttachment* attachment_pt = (LLViewerJointAttachment*)objectp->getRootEdit()->mDrawable->getParent(); - if (attachment_pt && attachment_pt->getIsHUDAttachment()) - { - LLVector3 camera_pos = LLVector3(-10000.f, 0.f, 0.f); - generateSilhouette(node, camera_pos); - } - } - } - //LLColor4 highlight_color; - // - //if (subtracting_from_selection) - //{ - // node->renderOneSilhouette(LLColor4::red); - //} - //else if (!objectp->isSelected()) - //{ - // highlight_color = objectp->isRoot() ? sHighlightParentColor : sHighlightChildColor; - // node->renderOneSilhouette(highlight_color); - //} - } - } - //mSilhouetteImagep->unbindTexture(0, GL_TEXTURE_2D); - } - else - { - mHighlightedObjects->deleteAllNodes(); - } - - for (std::vector::iterator iter = changed_objects.begin(); - iter != changed_objects.end(); ++iter) - { - // clear flags after traversing node list (as child objects need to refer to parent flags, etc) - LLViewerObject* objectp = *iter; - objectp->clearChanged(LLXform::MOVED | LLXform::SILHOUETTE); - } -} - -void LLSelectMgr::updateSelectionSilhouette(LLObjectSelectionHandle object_handle, S32& num_sils_genned, std::vector& changed_objects) -{ - if (object_handle->getNumNodes()) - { - //gGLSPipelineSelection.set(); - - //mSilhouetteImagep->bindTexture(); - //glAlphaFunc(GL_GREATER, sHighlightAlphaTest); - - for (S32 pass = 0; pass < 2; pass++) - { - for (LLObjectSelection::iterator iter = object_handle->begin(); - iter != object_handle->end(); iter++) - { - LLSelectNode* node = *iter; - LLViewerObject* objectp = node->getObject(); - if (!objectp) - continue; - // do roots first, then children so that root flags are cleared ASAP - bool roots_only = (pass == 0); - bool is_root = (objectp->isRootEdit()); - if (roots_only != is_root || objectp->mDrawable.isNull()) - { - continue; - } - - if (!node->mSilhouetteExists - || objectp->isChanged(LLXform::SILHOUETTE) - || (objectp->getParent() && objectp->getParent()->isChanged(LLXform::SILHOUETTE))) - { - if (num_sils_genned++ < MAX_SILS_PER_FRAME)// && objectp->mDrawable->isVisible()) - { - generateSilhouette(node, LLViewerCamera::getInstance()->getOrigin()); - changed_objects.push_back(objectp); - } - else if (objectp->isAttachment()) - { - //RN: hack for orthogonal projection of HUD attachments - LLViewerJointAttachment* attachment_pt = (LLViewerJointAttachment*)objectp->getRootEdit()->mDrawable->getParent(); - if (attachment_pt && attachment_pt->getIsHUDAttachment()) - { - LLVector3 camera_pos = LLVector3(-10000.f, 0.f, 0.f); - generateSilhouette(node, camera_pos); - } - } - } - } - } - } -} -void LLSelectMgr::renderSilhouettes(bool for_hud) -{ - if (!mRenderSilhouettes || !mRenderHighlightSelections) - { - return; - } - - gGL.getTexUnit(0)->bind(mSilhouetteImagep); - LLGLSPipelineSelection gls_select; - LLGLEnable blend(GL_BLEND); - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); - - if (isAgentAvatarValid() && for_hud) - { - LLBBox hud_bbox = gAgentAvatarp->getHUDBBox(); - - F32 cur_zoom = gAgentCamera.mHUDCurZoom; - - // set up transform to encompass bounding box of HUD - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.pushMatrix(); - gGL.loadIdentity(); - F32 depth = llmax(1.f, hud_bbox.getExtentLocal().mV[VX] * 1.1f); - gGL.ortho(-0.5f * LLViewerCamera::getInstance()->getAspect(), 0.5f * LLViewerCamera::getInstance()->getAspect(), -0.5f, 0.5f, 0.f, depth); - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - gGL.pushUIMatrix(); - gGL.loadUIIdentity(); - gGL.loadIdentity(); - gGL.loadMatrix(OGL_TO_CFR_ROTATION); // Load Cory's favorite reference frame - gGL.translatef(-hud_bbox.getCenterLocal().mV[VX] + (depth *0.5f), 0.f, 0.f); - gGL.scalef(cur_zoom, cur_zoom, cur_zoom); - } - - bool wireframe_selection = (gFloaterTools && gFloaterTools->getVisible()) || LLSelectMgr::sRenderHiddenSelections; - F32 fogCfx = (F32)llclamp((LLSelectMgr::getInstance()->getSelectionCenterGlobal() - gAgentCamera.getCameraPositionGlobal()).magVec() / (LLSelectMgr::getInstance()->getBBoxOfSelection().getExtentLocal().magVec() * 4), 0.0, 1.0); - - static LLColor4 sParentColor = LLColor4(sSilhouetteParentColor[VRED], sSilhouetteParentColor[VGREEN], sSilhouetteParentColor[VBLUE], LLSelectMgr::sHighlightAlpha); - static LLColor4 sChildColor = LLColor4(sSilhouetteChildColor[VRED], sSilhouetteChildColor[VGREEN], sSilhouetteChildColor[VBLUE], LLSelectMgr::sHighlightAlpha); - - auto renderMeshSelection_f = [fogCfx, wireframe_selection](LLSelectNode* node, LLViewerObject* objectp, LLColor4 hlColor) - { - LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; - - if (shader) - { - gDebugProgram.bind(); - } - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - - bool is_hud_object = objectp->isHUDAttachment(); - - if (!is_hud_object) - { - gGL.loadIdentity(); - gGL.multMatrix(gGLModelView); - } - - if (objectp->mDrawable->isActive()) - { - gGL.multMatrix((F32*)objectp->getRenderMatrix().mMatrix); - } - else if (!is_hud_object) - { - LLVector3 trans = objectp->getRegion()->getOriginAgent(); - gGL.translatef(trans.mV[0], trans.mV[1], trans.mV[2]); - } - - bool bRenderHidenSelection = node->isTransient() ? false : LLSelectMgr::sRenderHiddenSelections; - - - LLVOVolume* vobj = objectp->mDrawable->getVOVolume(); - if (vobj) - { - LLVertexBuffer::unbind(); - gGL.pushMatrix(); - gGL.multMatrix((F32*)vobj->getRelativeXform().mMatrix); - - if (objectp->mDrawable->isState(LLDrawable::RIGGED)) - { - vobj->updateRiggedVolume(true); - } - } - - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - - S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); // avatars have TEs but no faces - for (S32 te = 0; te < num_tes; ++te) - { - if (node->isTESelected(te)) - { - objectp->mDrawable->getFace(te)->renderOneWireframe(hlColor, fogCfx, wireframe_selection, bRenderHidenSelection, nullptr != shader); - } - } - - gGL.popMatrix(); - gGL.popMatrix(); - - glLineWidth(1.f); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - - if (shader) - { - shader->bind(); - } - }; - - if (mSelectedObjects->getNumNodes()) - { - LLUUID inspect_item_id= LLUUID::null; - LLFloaterInspect* inspect_instance = LLFloaterReg::getTypedInstance("inspect"); - if(inspect_instance && inspect_instance->getVisible()) - { - inspect_item_id = inspect_instance->getSelectedUUID(); - } - else - { - LLSidepanelTaskInfo *panel_task_info = LLSidepanelTaskInfo::getActivePanel(); - if (panel_task_info) - { - inspect_item_id = panel_task_info->getSelectedUUID(); - } - } - - LLUUID focus_item_id = LLViewerMediaFocus::getInstance()->getFocusedObjectID(); - for (S32 pass = 0; pass < 2; pass++) - { - for (LLObjectSelection::iterator iter = mSelectedObjects->begin(); - iter != mSelectedObjects->end(); iter++) - { - LLSelectNode* node = *iter; - - if (getTEMode() && !node->hasSelectedTE()) - continue; - - LLViewerObject* objectp = node->getObject(); - if (!objectp) - continue; - - if (objectp->mDrawable - && objectp->mDrawable->getVOVolume() - && objectp->mDrawable->getVOVolume()->isMesh()) - { - LLColor4 hlColor = objectp->isRootEdit() ? sParentColor : sChildColor; - if (objectp->getID() == inspect_item_id) - { - hlColor = sHighlightInspectColor; - } - else if (node->isTransient()) - { - hlColor = sContextSilhouetteColor; - } - renderMeshSelection_f(node, objectp, hlColor); - } - else - { - if (objectp->isHUDAttachment() != for_hud) - { - continue; - } - if (objectp->getID() == focus_item_id) - { - node->renderOneSilhouette(gFocusMgr.getFocusColor()); - } - else if (objectp->getID() == inspect_item_id) - { - node->renderOneSilhouette(sHighlightInspectColor); - } - else if (node->isTransient()) - { - bool oldHidden = LLSelectMgr::sRenderHiddenSelections; - LLSelectMgr::sRenderHiddenSelections = false; - node->renderOneSilhouette(sContextSilhouetteColor); - LLSelectMgr::sRenderHiddenSelections = oldHidden; - } - else if (objectp->isRootEdit()) - { - node->renderOneSilhouette(sSilhouetteParentColor); - } - else - { - node->renderOneSilhouette(sSilhouetteChildColor); - } - } - } //for all selected node's - } //for pass - } - - if (mHighlightedObjects->getNumNodes()) - { - // render silhouettes for highlighted objects - bool subtracting_from_selection = (gKeyboard->currentMask(true) == MASK_CONTROL); - for (S32 pass = 0; pass < 2; pass++) - { - for (LLObjectSelection::iterator iter = mHighlightedObjects->begin(); - iter != mHighlightedObjects->end(); iter++) - { - LLSelectNode* node = *iter; - LLViewerObject* objectp = node->getObject(); - if (!objectp) - continue; - if (objectp->isHUDAttachment() != for_hud) - { - continue; - } - - LLColor4 highlight_color = objectp->isRoot() ? sHighlightParentColor : sHighlightChildColor; - if (objectp->mDrawable - && objectp->mDrawable->getVOVolume() - && objectp->mDrawable->getVOVolume()->isMesh()) - { - renderMeshSelection_f(node, objectp, subtracting_from_selection ? LLColor4::red : highlight_color); - } - else if (subtracting_from_selection) - { - node->renderOneSilhouette(LLColor4::red); - } - else if (!objectp->isSelected()) - { - node->renderOneSilhouette(highlight_color); - } - } - } - } - - if (isAgentAvatarValid() && for_hud) - { - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.popMatrix(); - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.popMatrix(); - gGL.popUIMatrix(); - stop_glerror(); - } - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); -} - -void LLSelectMgr::generateSilhouette(LLSelectNode* nodep, const LLVector3& view_point) -{ - LLViewerObject* objectp = nodep->getObject(); - - if (objectp && objectp->getPCode() == LL_PCODE_VOLUME) - { - ((LLVOVolume*)objectp)->generateSilhouette(nodep, view_point); - } -} - -// -// Utility classes -// -LLSelectNode::LLSelectNode(LLViewerObject* object, bool glow) -: mObject(object), - mIndividualSelection(false), - mTransient(false), - mValid(false), - mPermissions(new LLPermissions()), - mInventorySerial(0), - mSilhouetteExists(false), - mDuplicated(false), - mTESelectMask(0), - mLastTESelected(0), - mName(LLStringUtil::null), - mDescription(LLStringUtil::null), - mTouchName(LLStringUtil::null), - mSitName(LLStringUtil::null), - mCreationDate(0) -{ - saveColors(); - saveShinyColors(); -} - -LLSelectNode::LLSelectNode(const LLSelectNode& nodep) -{ - mTESelectMask = nodep.mTESelectMask; - mLastTESelected = nodep.mLastTESelected; - - mIndividualSelection = nodep.mIndividualSelection; - - mValid = nodep.mValid; - mTransient = nodep.mTransient; - mPermissions = new LLPermissions(*nodep.mPermissions); - mSaleInfo = nodep.mSaleInfo;; - mAggregatePerm = nodep.mAggregatePerm; - mAggregateTexturePerm = nodep.mAggregateTexturePerm; - mAggregateTexturePermOwner = nodep.mAggregateTexturePermOwner; - mName = nodep.mName; - mDescription = nodep.mDescription; - mCategory = nodep.mCategory; - mInventorySerial = 0; - mSavedPositionLocal = nodep.mSavedPositionLocal; - mSavedPositionGlobal = nodep.mSavedPositionGlobal; - mSavedScale = nodep.mSavedScale; - mSavedRotation = nodep.mSavedRotation; - mDuplicated = nodep.mDuplicated; - mDuplicatePos = nodep.mDuplicatePos; - mDuplicateRot = nodep.mDuplicateRot; - mItemID = nodep.mItemID; - mFolderID = nodep.mFolderID; - mFromTaskID = nodep.mFromTaskID; - mTouchName = nodep.mTouchName; - mSitName = nodep.mSitName; - mCreationDate = nodep.mCreationDate; - - mSilhouetteVertices = nodep.mSilhouetteVertices; - mSilhouetteNormals = nodep.mSilhouetteNormals; - mSilhouetteExists = nodep.mSilhouetteExists; - mObject = nodep.mObject; - - std::vector::const_iterator color_iter; - mSavedColors.clear(); - for (color_iter = nodep.mSavedColors.begin(); color_iter != nodep.mSavedColors.end(); ++color_iter) - { - mSavedColors.push_back(*color_iter); - } - mSavedShinyColors.clear(); - for (color_iter = nodep.mSavedShinyColors.begin(); color_iter != nodep.mSavedShinyColors.end(); ++color_iter) - { - mSavedShinyColors.push_back(*color_iter); - } - - saveTextures(nodep.mSavedTextures); - saveGLTFMaterials(nodep.mSavedGLTFMaterialIds, nodep.mSavedGLTFOverrideMaterials); -} - -LLSelectNode::~LLSelectNode() -{ - LLSelectMgr *manager = LLSelectMgr::getInstance(); - if (manager->mAllowSelectAvatar - && (!mLastPositionLocal.isExactlyZero() - || mLastRotation != LLQuaternion())) - { - LLViewerObject* object = getObject(); //isDead() check - if (object && !object->getParent()) - { - LLVOAvatar* avatar = object->asAvatar(); - if (avatar) - { - // Avatar was moved and needs to stay that way - manager->mAvatarOverridesMap.emplace(avatar->getID(), LLSelectMgr::AvatarPositionOverride(mLastPositionLocal, mLastRotation, object)); - } - } - } - - - delete mPermissions; - mPermissions = NULL; -} - -void LLSelectNode::selectAllTEs(bool b) -{ - mTESelectMask = b ? TE_SELECT_MASK_ALL : 0x0; - mLastTESelected = 0; -} - -void LLSelectNode::selectTE(S32 te_index, bool selected) -{ - if (te_index < 0 || te_index >= SELECT_MAX_TES) - { - return; - } - S32 mask = 0x1 << te_index; - if(selected) - { - mTESelectMask |= mask; - } - else - { - mTESelectMask &= ~mask; - } - mLastTESelected = te_index; -} - -bool LLSelectNode::isTESelected(S32 te_index) const -{ - if (te_index < 0 || te_index >= mObject->getNumTEs()) - { - return false; - } - return (mTESelectMask & (0x1 << te_index)) != 0; -} - -S32 LLSelectNode::getLastSelectedTE() const -{ - if (!isTESelected(mLastTESelected)) - { - return -1; - } - return mLastTESelected; -} - -LLViewerObject* LLSelectNode::getObject() -{ - if (!mObject) - { - return NULL; - } - else if (mObject->isDead()) - { - mObject = NULL; - } - return mObject; -} - -void LLSelectNode::setObject(LLViewerObject* object) -{ - mObject = object; -} - -void LLSelectNode::saveColors() -{ - if (mObject.notNull()) - { - mSavedColors.clear(); - for (S32 i = 0; i < mObject->getNumTEs(); i++) - { - const LLTextureEntry* tep = mObject->getTE(i); - mSavedColors.push_back(tep->getColor()); - } - } -} - -void LLSelectNode::saveShinyColors() -{ - if (mObject.notNull()) - { - mSavedShinyColors.clear(); - for (S32 i = 0; i < mObject->getNumTEs(); i++) - { - const LLMaterialPtr mat = mObject->getTE(i)->getMaterialParams(); - if (!mat.isNull()) - { - mSavedShinyColors.push_back(mat->getSpecularLightColor()); - } - else - { - mSavedShinyColors.push_back(LLColor4::white); - } - } - } -} - -void LLSelectNode::saveTextures(const uuid_vec_t& textures) -{ - if (mObject.notNull()) - { - mSavedTextures.clear(); - - for (uuid_vec_t::const_iterator texture_it = textures.begin(); - texture_it != textures.end(); ++texture_it) - { - mSavedTextures.push_back(*texture_it); - } - } -} - -void LLSelectNode::saveGLTFMaterials(const uuid_vec_t& materials, const gltf_materials_vec_t& override_materials) -{ - if (mObject.notNull()) - { - mSavedGLTFMaterialIds.clear(); - mSavedGLTFOverrideMaterials.clear(); - - for (uuid_vec_t::const_iterator materials_it = materials.begin(); - materials_it != materials.end(); ++materials_it) - { - mSavedGLTFMaterialIds.push_back(*materials_it); - } - - for (gltf_materials_vec_t::const_iterator mat_it = override_materials.begin(); - mat_it != override_materials.end(); ++mat_it) - { - mSavedGLTFOverrideMaterials.push_back(*mat_it); - } - } -} - -void LLSelectNode::saveTextureScaleRatios(LLRender::eTexIndex index_to_query) -{ - mTextureScaleRatios.clear(); - - if (mObject.notNull()) - { - - LLVector3 scale = mObject->getScale(); - - for (U8 i = 0; i < mObject->getNumTEs(); i++) - { - F32 diffuse_s = 1.0f; - F32 diffuse_t = 1.0f; - - LLVector3 v; - const LLTextureEntry* tep = mObject->getTE(i); - if (!tep) - continue; - - U32 s_axis = VX; - U32 t_axis = VY; - LLPrimitive::getTESTAxes(i, &s_axis, &t_axis); - - tep->getScale(&diffuse_s,&diffuse_t); - - if (tep->getTexGen() == LLTextureEntry::TEX_GEN_PLANAR) - { - v.mV[s_axis] = diffuse_s*scale.mV[s_axis]; - v.mV[t_axis] = diffuse_t*scale.mV[t_axis]; - mTextureScaleRatios.push_back(v); - } - else - { - v.mV[s_axis] = diffuse_s/scale.mV[s_axis]; - v.mV[t_axis] = diffuse_t/scale.mV[t_axis]; - mTextureScaleRatios.push_back(v); - } - } - } -} - - -// This implementation should be similar to LLTask::allowOperationOnTask -bool LLSelectNode::allowOperationOnNode(PermissionBit op, U64 group_proxy_power) const -{ - // Extract ownership. - bool object_is_group_owned = false; - LLUUID object_owner_id; - mPermissions->getOwnership(object_owner_id, object_is_group_owned); - - // Operations on invalid or public objects is not allowed. - if (!mObject || (mObject->isDead()) || !mPermissions->isOwned()) - { - return false; - } - - // The transfer permissions can never be given through proxy. - if (PERM_TRANSFER == op) - { - // The owner of an agent-owned object can transfer to themselves. - if ( !object_is_group_owned - && (gAgent.getID() == object_owner_id) ) - { - return true; - } - else - { - // Otherwise check aggregate permissions. - return mObject->permTransfer(); - } - } - - if (PERM_MOVE == op - || PERM_MODIFY == op) - { - // only owners can move or modify their attachments - // no proxy allowed. - if (mObject->isAttachment() && object_owner_id != gAgent.getID()) - { - return false; - } - } - - // Calculate proxy_agent_id and group_id to use for permissions checks. - // proxy_agent_id may be set to the object owner through group powers. - // group_id can only be set to the object's group, if the agent is in that group. - LLUUID group_id = LLUUID::null; - LLUUID proxy_agent_id = gAgent.getID(); - - // Gods can always operate. - if (gAgent.isGodlike()) - { - return true; - } - - // Check if the agent is in the same group as the object. - LLUUID object_group_id = mPermissions->getGroup(); - if (object_group_id.notNull() && - gAgent.isInGroup(object_group_id)) - { - // Assume the object's group during this operation. - group_id = object_group_id; - } - - // Only allow proxy powers for PERM_COPY if the actual agent can - // receive the item (ie has PERM_TRANSFER permissions). - // NOTE: op == PERM_TRANSFER has already been handled, but if - // that ever changes we need to BLOCK proxy powers for PERM_TRANSFER. DK 03/28/06 - if (PERM_COPY != op || mPermissions->allowTransferTo(gAgent.getID())) - { - // Check if the agent can assume ownership through group proxy or agent-granted proxy. - if ( ( object_is_group_owned - && gAgent.hasPowerInGroup(object_owner_id, group_proxy_power)) - // Only allow proxy for move, modify, and copy. - || ( (PERM_MOVE == op || PERM_MODIFY == op || PERM_COPY == op) - && (!object_is_group_owned - && gAgent.isGrantedProxy(*mPermissions)))) - { - // This agent is able to assume the ownership role for this operation. - proxy_agent_id = object_owner_id; - } - } - - // We now have max ownership information. - if (PERM_OWNER == op) - { - // This this was just a check for ownership, we can now return the answer. - return proxy_agent_id == object_owner_id; - } - - // check permissions to see if the agent can operate - return (mPermissions->allowOperationBy(op, proxy_agent_id, group_id)); -} - -//----------------------------------------------------------------------------- -// renderOneSilhouette() -//----------------------------------------------------------------------------- -void LLSelectNode::renderOneSilhouette(const LLColor4 &color) -{ - LLViewerObject* objectp = getObject(); - if (!objectp) - { - return; - } - - LLDrawable* drawable = objectp->mDrawable; - if(!drawable) - { - return; - } - - LLVOVolume* vobj = drawable->getVOVolume(); - if (vobj && vobj->isMesh()) - { - //This check (if(...)) with assert here just for ensure that this situation will not happens, and can be removed later. For example on the next release. - llassert(!"renderOneWireframe() was removed SL-10194"); - return; - } - - if (!mSilhouetteExists) - { - return; - } - - bool is_hud_object = objectp->isHUDAttachment(); - - if (mSilhouetteVertices.size() == 0 || mSilhouetteNormals.size() != mSilhouetteVertices.size()) - { - return; - } - - - LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; - - if (shader) - { //use UI program for selection highlights (texture color modulated by vertex color) - gUIProgram.bind(); - } - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - gGL.pushUIMatrix(); - gGL.loadUIIdentity(); - - if (!is_hud_object) - { - gGL.loadIdentity(); - gGL.multMatrix(gGLModelView); - } - - - if (drawable->isActive()) - { - gGL.multMatrix((F32*) objectp->getRenderMatrix().mMatrix); - } - - LLVolume *volume = objectp->getVolume(); - if (volume) - { - F32 silhouette_thickness; - if (isAgentAvatarValid() && is_hud_object) - { - silhouette_thickness = LLSelectMgr::sHighlightThickness / gAgentCamera.mHUDCurZoom; - } - else - { - LLVector3 view_vector = LLViewerCamera::getInstance()->getOrigin() - objectp->getRenderPosition(); - silhouette_thickness = view_vector.magVec() * LLSelectMgr::sHighlightThickness * (LLViewerCamera::getInstance()->getView() / LLViewerCamera::getInstance()->getDefaultFOV()); - } - F32 animationTime = (F32)LLFrameTimer::getElapsedSeconds(); - - F32 u_coord = fmod(animationTime * LLSelectMgr::sHighlightUAnim, 1.f); - F32 v_coord = 1.f - fmod(animationTime * LLSelectMgr::sHighlightVAnim, 1.f); - F32 u_divisor = 1.f / ((F32)(mSilhouetteVertices.size() - 1)); - - if (LLSelectMgr::sRenderHiddenSelections) // && gFloaterTools && gFloaterTools->getVisible()) - { - gGL.flush(); - gGL.blendFunc(LLRender::BF_SOURCE_COLOR, LLRender::BF_ONE); - - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE, GL_GEQUAL); - gGL.flush(); - gGL.begin(LLRender::LINES); - { - gGL.color4f(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], 0.4f); - - for(S32 i = 0; i < mSilhouetteVertices.size(); i += 2) - { - u_coord += u_divisor * LLSelectMgr::sHighlightUScale; - gGL.texCoord2f( u_coord, v_coord ); - gGL.vertex3fv( mSilhouetteVertices[i].mV); - u_coord += u_divisor * LLSelectMgr::sHighlightUScale; - gGL.texCoord2f( u_coord, v_coord ); - gGL.vertex3fv(mSilhouetteVertices[i+1].mV); - } - } - gGL.end(); - u_coord = fmod(animationTime * LLSelectMgr::sHighlightUAnim, 1.f); - } - - gGL.flush(); - gGL.setSceneBlendType(LLRender::BT_ALPHA); - gGL.begin(LLRender::TRIANGLES); - { - for(S32 i = 0; i < mSilhouetteVertices.size(); i+=2) - { - if (!mSilhouetteNormals[i].isFinite() || - !mSilhouetteNormals[i+1].isFinite()) - { //skip skewed segments - continue; - } - - LLVector3 v[4]; - LLVector2 tc[4]; - v[0] = mSilhouetteVertices[i] + (mSilhouetteNormals[i] * silhouette_thickness); - tc[0].set(u_coord, v_coord + LLSelectMgr::sHighlightVScale); - - v[1] = mSilhouetteVertices[i]; - tc[1].set(u_coord, v_coord); - - u_coord += u_divisor * LLSelectMgr::sHighlightUScale; - - v[2] = mSilhouetteVertices[i+1] + (mSilhouetteNormals[i+1] * silhouette_thickness); - tc[2].set(u_coord, v_coord + LLSelectMgr::sHighlightVScale); - - v[3] = mSilhouetteVertices[i+1]; - tc[3].set(u_coord,v_coord); - - gGL.color4f(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], 0.0f); //LLSelectMgr::sHighlightAlpha); - gGL.texCoord2fv(tc[0].mV); - gGL.vertex3fv( v[0].mV ); - - gGL.color4f(color.mV[VRED]*2, color.mV[VGREEN]*2, color.mV[VBLUE]*2, LLSelectMgr::sHighlightAlpha); - gGL.texCoord2fv( tc[1].mV ); - gGL.vertex3fv( v[1].mV ); - - gGL.color4f(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], 0.0f); //LLSelectMgr::sHighlightAlpha); - gGL.texCoord2fv( tc[2].mV ); - gGL.vertex3fv( v[2].mV ); - - gGL.vertex3fv( v[2].mV ); - - gGL.color4f(color.mV[VRED]*2, color.mV[VGREEN]*2, color.mV[VBLUE]*2, LLSelectMgr::sHighlightAlpha); - gGL.texCoord2fv( tc[1].mV ); - gGL.vertex3fv( v[1].mV ); - - gGL.texCoord2fv( tc[3].mV ); - gGL.vertex3fv( v[3].mV ); - } - } - gGL.end(); - gGL.flush(); - } - gGL.popMatrix(); - gGL.popUIMatrix(); - - if (shader) - { - shader->bind(); - } -} - -// -// Utility Functions -// - -// *DEPRECATED: See header comment. -void dialog_refresh_all() -{ - // This is the easiest place to fire the update signal, as it will - // make cleaning up the functions below easier. Also, sometimes entities - // outside the selection manager change properties of selected objects - // and call into this function. Yuck. - LLSelectMgr::getInstance()->mUpdateSignal(); - - // *TODO: Eliminate all calls into outside classes below, make those - // objects register with the update signal. - - gFloaterTools->dirty(); - - gMenuObject->needsArrange(); - - if( gMenuAttachmentSelf->getVisible() ) - { - gMenuAttachmentSelf->arrange(); - } - if( gMenuAttachmentOther->getVisible() ) - { - gMenuAttachmentOther->arrange(); - } - - LLFloaterInspect* inspect_instance = LLFloaterReg::getTypedInstance("inspect"); - if(inspect_instance) - { - inspect_instance->dirty(); - } - - LLSidepanelTaskInfo *panel_task_info = LLSidepanelTaskInfo::getActivePanel(); - if (panel_task_info) - { - panel_task_info->dirty(); - } -} - -S32 get_family_count(LLViewerObject *parent) -{ - if (!parent) - { - LL_WARNS() << "Trying to get_family_count on null parent!" << LL_ENDL; - } - S32 count = 1; // for this object - LLViewerObject::const_child_list_t& child_list = parent->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); iter++) - { - LLViewerObject* child = *iter; - - if (!child) - { - LL_WARNS() << "Family object has NULL child! Show Doug." << LL_ENDL; - } - else if (child->isDead()) - { - LL_WARNS() << "Family object has dead child object. Show Doug." << LL_ENDL; - } - else - { - if (LLSelectMgr::getInstance()->canSelectObject(child)) - { - count += get_family_count( child ); - } - } - } - return count; -} - -//----------------------------------------------------------------------------- -// updateSelectionCenter -// -// FIXME this is a grab bag of functionality only some of which has to do -// with the selection center -// ----------------------------------------------------------------------------- -void LLSelectMgr::updateSelectionCenter() -{ - const F32 MOVE_SELECTION_THRESHOLD = 1.f; // Movement threshold in meters for updating selection - // center (tractor beam) - - // override any avatar updates received - // Works only if avatar was repositioned - // and edit floater is visible - overrideAvatarUpdates(); - //override any object updates received - //for selected objects - overrideObjectUpdates(); - - LLViewerObject* object = mSelectedObjects->getFirstObject(); - if (!object) - { - // nothing selected, probably grabbing - // Ignore by setting to avatar origin. - mSelectionCenterGlobal.clearVec(); - mShowSelection = false; - mSelectionBBox = LLBBox(); - resetAgentHUDZoom(); - } - else - { - mSelectedObjects->mSelectType = getSelectTypeForObject(object); - - if (mSelectedObjects->mSelectType != SELECT_TYPE_HUD && isAgentAvatarValid()) - { - // reset hud ZOOM - resetAgentHUDZoom(); - } - - mShowSelection = false; - LLBBox bbox; - - // have stuff selected - LLVector3d select_center; - // keep a list of jointed objects for showing the joint HUDEffects - - // Initialize the bounding box to the root prim, so the BBox orientation - // matches the root prim's (affecting the orientation of the manipulators). - bbox.addBBoxAgent( (mSelectedObjects->getFirstRootObject(true))->getBoundingBoxAgent() ); - - for (LLObjectSelection::iterator iter = mSelectedObjects->begin(); - iter != mSelectedObjects->end(); iter++) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - if (!object) - continue; - - LLViewerObject *root = object->getRootEdit(); - if (mSelectedObjects->mSelectType == SELECT_TYPE_WORLD && // not an attachment - !root->isChild(gAgentAvatarp) && // not the object you're sitting on - !object->isAvatar()) // not another avatar - { - mShowSelection = true; - } - - bbox.addBBoxAgent( object->getBoundingBoxAgent() ); - } - - LLVector3 bbox_center_agent = bbox.getCenterAgent(); - mSelectionCenterGlobal = gAgent.getPosGlobalFromAgent(bbox_center_agent); - mSelectionBBox = bbox; - - } - - if ( !(gAgentID == LLUUID::null)) - { - LLTool *tool = LLToolMgr::getInstance()->getCurrentTool(); - if (mShowSelection) - { - LLVector3d select_center_global; - - if( tool->isEditing() ) - { - select_center_global = tool->getEditingPointGlobal(); - } - else - { - select_center_global = mSelectionCenterGlobal; - } - - // Send selection center if moved beyond threshold (used to animate tractor beam) - LLVector3d diff; - diff = select_center_global - mLastSentSelectionCenterGlobal; - - if ( diff.magVecSquared() > MOVE_SELECTION_THRESHOLD*MOVE_SELECTION_THRESHOLD ) - { - // Transmit updated selection center - mLastSentSelectionCenterGlobal = select_center_global; - } - } - } - - // give up edit menu if no objects selected - if (gEditMenuHandler == this && mSelectedObjects->getObjectCount() == 0) - { - gEditMenuHandler = NULL; - } - - pauseAssociatedAvatars(); -} - -//----------------------------------------------------------------------------- -// pauseAssociatedAvatars -// -// If the selection includes an attachment or an animated object, the -// associated avatars should pause their animations until they are no -// longer selected. -//----------------------------------------------------------------------------- -void LLSelectMgr::pauseAssociatedAvatars() -{ - mPauseRequests.clear(); - - for (LLObjectSelection::iterator iter = mSelectedObjects->begin(); - iter != mSelectedObjects->end(); iter++) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - if (!object) - continue; - - mSelectedObjects->mSelectType = getSelectTypeForObject(object); - - LLVOAvatar* parent_av = NULL; - if (mSelectedObjects->mSelectType == SELECT_TYPE_ATTACHMENT) - { - // Selection can be obsolete, confirm that this is an attachment - // and find parent avatar - parent_av = object->getAvatarAncestor(); - } - - // Can be both an attachment and animated object - if (parent_av) - { - // It's an attachment. Pause the avatar it's attached to. - mPauseRequests.push_back(parent_av->requestPause()); - } - - if (object->isAnimatedObject() && object->getControlAvatar()) - { - // It's an animated object. Pause the control avatar. - mPauseRequests.push_back(object->getControlAvatar()->requestPause()); - } - } -} - -void LLSelectMgr::updatePointAt() -{ - if (mShowSelection) - { - if (mSelectedObjects->getObjectCount()) - { - LLVector3 select_offset; - const LLPickInfo& pick = gViewerWindow->getLastPick(); - LLViewerObject *click_object = pick.getObject(); - if (click_object && click_object->isSelected()) - { - // clicked on another object in our selection group, use that as target - select_offset.setVec(pick.mObjectOffset); - select_offset.rotVec(~click_object->getRenderRotation()); - - gAgentCamera.setPointAt(POINTAT_TARGET_SELECT, click_object, select_offset); - gAgentCamera.setLookAt(LOOKAT_TARGET_SELECT, click_object, select_offset); - } - else - { - // didn't click on an object this time, revert to pointing at center of first object - gAgentCamera.setPointAt(POINTAT_TARGET_SELECT, mSelectedObjects->getFirstObject()); - gAgentCamera.setLookAt(LOOKAT_TARGET_SELECT, mSelectedObjects->getFirstObject()); - } - } - else - { - gAgentCamera.setPointAt(POINTAT_TARGET_CLEAR); - gAgentCamera.setLookAt(LOOKAT_TARGET_CLEAR); - } - } - else - { - gAgentCamera.setPointAt(POINTAT_TARGET_CLEAR); - gAgentCamera.setLookAt(LOOKAT_TARGET_CLEAR); - } -} - -//----------------------------------------------------------------------------- -// getBBoxOfSelection() -//----------------------------------------------------------------------------- -LLBBox LLSelectMgr::getBBoxOfSelection() const -{ - return mSelectionBBox; -} - - -//----------------------------------------------------------------------------- -// canUndo() -//----------------------------------------------------------------------------- -bool LLSelectMgr::canUndo() const -{ - // Can edit or can move - return const_cast(this)->mSelectedObjects->getFirstUndoEnabledObject() != NULL; // HACK: casting away constness - MG; -} - -//----------------------------------------------------------------------------- -// undo() -//----------------------------------------------------------------------------- -void LLSelectMgr::undo() -{ - bool select_linked_set = !gSavedSettings.getBOOL("EditLinkedParts"); - LLUUID group_id(gAgent.getGroupID()); - sendListToRegions("Undo", packAgentAndSessionAndGroupID, packObjectID, logNoOp, &group_id, select_linked_set ? SEND_ONLY_ROOTS : SEND_CHILDREN_FIRST); -} - -//----------------------------------------------------------------------------- -// canRedo() -//----------------------------------------------------------------------------- -bool LLSelectMgr::canRedo() const -{ - return const_cast(this)->mSelectedObjects->getFirstEditableObject() != NULL; // HACK: casting away constness - MG -} - -//----------------------------------------------------------------------------- -// redo() -//----------------------------------------------------------------------------- -void LLSelectMgr::redo() -{ - bool select_linked_set = !gSavedSettings.getBOOL("EditLinkedParts"); - LLUUID group_id(gAgent.getGroupID()); - sendListToRegions("Redo", packAgentAndSessionAndGroupID, packObjectID, logNoOp, &group_id, select_linked_set ? SEND_ONLY_ROOTS : SEND_CHILDREN_FIRST); -} - -//----------------------------------------------------------------------------- -// canDoDelete() -//----------------------------------------------------------------------------- -bool LLSelectMgr::canDoDelete() const -{ - bool can_delete = false; - // This function is "logically const" - it does not change state in - // a way visible outside the selection manager. - LLSelectMgr* self = const_cast(this); - LLViewerObject* obj = self->mSelectedObjects->getFirstDeleteableObject(); - // Note: Can only delete root objects (see getFirstDeleteableObject() for more info) - if (obj!= NULL) - { - // all the faces needs to be selected - if(self->mSelectedObjects->contains(obj,SELECT_ALL_TES )) - { - can_delete = true; - } - } - - return can_delete; -} - -//----------------------------------------------------------------------------- -// doDelete() -//----------------------------------------------------------------------------- -void LLSelectMgr::doDelete() -{ - selectDelete(); -} - -//----------------------------------------------------------------------------- -// canDeselect() -//----------------------------------------------------------------------------- -bool LLSelectMgr::canDeselect() const -{ - return !mSelectedObjects->isEmpty(); -} - -//----------------------------------------------------------------------------- -// deselect() -//----------------------------------------------------------------------------- -void LLSelectMgr::deselect() -{ - deselectAll(); -} -//----------------------------------------------------------------------------- -// canDuplicate() -//----------------------------------------------------------------------------- -bool LLSelectMgr::canDuplicate() const -{ - return const_cast(this)->mSelectedObjects->getFirstCopyableObject() != NULL; // HACK: casting away constness - MG -} -//----------------------------------------------------------------------------- -// duplicate() -//----------------------------------------------------------------------------- -void LLSelectMgr::duplicate() -{ - LLVector3 offset(0.5f, 0.5f, 0.f); - selectDuplicate(offset, true); -} - -ESelectType LLSelectMgr::getSelectTypeForObject(LLViewerObject* object) -{ - if (!object) - { - return SELECT_TYPE_WORLD; - } - if (object->isHUDAttachment()) - { - return SELECT_TYPE_HUD; - } - else if (object->isAttachment()) - { - return SELECT_TYPE_ATTACHMENT; - } - else - { - return SELECT_TYPE_WORLD; - } -} - -void LLSelectMgr::validateSelection() -{ - struct f : public LLSelectedObjectFunctor - { - virtual bool apply(LLViewerObject* object) - { - if (!LLSelectMgr::getInstance()->canSelectObject(object)) - { - LLSelectMgr::getInstance()->deselectObjectOnly(object); - } - return true; - } - } func; - getSelection()->applyToObjects(&func); -} - -bool LLSelectMgr::canSelectObject(LLViewerObject* object, bool ignore_select_owned) -{ - // Never select dead objects - if (!object || object->isDead()) - { - return false; - } - - if (mForceSelection) - { - return true; - } - - if(!ignore_select_owned) - { - if ((gSavedSettings.getBOOL("SelectOwnedOnly") && !object->permYouOwner()) || - (gSavedSettings.getBOOL("SelectMovableOnly") && (!object->permMove() || object->isPermanentEnforced()))) - { - // only select my own objects - return false; - } - } - - // Can't select orphans - if (object->isOrphaned()) return false; - - // Can't select avatars - if (object->isAvatar()) return false; - - // Can't select land - if (object->getPCode() == LLViewerObject::LL_VO_SURFACE_PATCH) return false; - - ESelectType selection_type = getSelectTypeForObject(object); - if (mSelectedObjects->getObjectCount() > 0 && mSelectedObjects->mSelectType != selection_type) return false; - - return true; -} - -bool LLSelectMgr::setForceSelection(bool force) -{ - std::swap(mForceSelection,force); - return force; -} - -void LLSelectMgr::resetAgentHUDZoom() -{ - if (gAgentCamera.mHUDTargetZoom != 1) - { - gAgentCamera.mHUDTargetZoom = 1.f; - gAgentCamera.mHUDCurZoom = 1.f; - } -} - -void LLSelectMgr::getAgentHUDZoom(F32 &target_zoom, F32 ¤t_zoom) const -{ - target_zoom = gAgentCamera.mHUDTargetZoom; - current_zoom = gAgentCamera.mHUDCurZoom; -} - -void LLSelectMgr::setAgentHUDZoom(F32 target_zoom, F32 current_zoom) -{ - gAgentCamera.mHUDTargetZoom = target_zoom; - gAgentCamera.mHUDCurZoom = current_zoom; -} - -///////////////////////////////////////////////////////////////////////////// -// Object selection iterator helpers -///////////////////////////////////////////////////////////////////////////// -bool LLObjectSelection::is_root::operator()(LLSelectNode *node) -{ - LLViewerObject* object = node->getObject(); - return (object != NULL) && !node->mIndividualSelection && (object->isRootEdit()); -} - -bool LLObjectSelection::is_valid_root::operator()(LLSelectNode *node) -{ - LLViewerObject* object = node->getObject(); - return (object != NULL) && node->mValid && !node->mIndividualSelection && (object->isRootEdit()); -} - -bool LLObjectSelection::is_root_object::operator()(LLSelectNode *node) -{ - LLViewerObject* object = node->getObject(); - return (object != NULL) && (object->isRootEdit()); -} - -LLObjectSelection::LLObjectSelection() : - LLRefCount(), - mSelectType(SELECT_TYPE_WORLD) -{ -} - -LLObjectSelection::~LLObjectSelection() -{ - deleteAllNodes(); -} - -void LLObjectSelection::cleanupNodes() -{ - for (list_t::iterator iter = mList.begin(); iter != mList.end(); ) - { - list_t::iterator curiter = iter++; - LLSelectNode* node = *curiter; - if (node->getObject() == NULL || node->getObject()->isDead()) - { - mList.erase(curiter); - delete node; - } - } -} - -void LLObjectSelection::updateEffects() -{ -} - -S32 LLObjectSelection::getNumNodes() -{ - return mList.size(); -} - -void LLObjectSelection::addNode(LLSelectNode *nodep) -{ - llassert_always(nodep->getObject() && !nodep->getObject()->isDead()); - mList.push_front(nodep); - mSelectNodeMap[nodep->getObject()] = nodep; -} - -void LLObjectSelection::addNodeAtEnd(LLSelectNode *nodep) -{ - llassert_always(nodep->getObject() && !nodep->getObject()->isDead()); - mList.push_back(nodep); - mSelectNodeMap[nodep->getObject()] = nodep; -} - -void LLObjectSelection::moveNodeToFront(LLSelectNode *nodep) -{ - mList.remove(nodep); - mList.push_front(nodep); -} - -void LLObjectSelection::removeNode(LLSelectNode *nodep) -{ - mSelectNodeMap.erase(nodep->getObject()); - if (nodep->getObject() == mPrimaryObject) - { - mPrimaryObject = NULL; - } - nodep->setObject(NULL); // Will get erased in cleanupNodes() - mList.remove(nodep); -} - -void LLObjectSelection::deleteAllNodes() -{ - std::for_each(mList.begin(), mList.end(), DeletePointer()); - mList.clear(); - mSelectNodeMap.clear(); - mPrimaryObject = NULL; -} - -LLSelectNode* LLObjectSelection::findNode(LLViewerObject* objectp) -{ - std::map, LLSelectNode*>::iterator found_it = mSelectNodeMap.find(objectp); - if (found_it != mSelectNodeMap.end()) - { - return found_it->second; - } - return NULL; -} - -//----------------------------------------------------------------------------- -// isEmpty() -//----------------------------------------------------------------------------- -bool LLObjectSelection::isEmpty() const -{ - return (mList.size() == 0); -} - - -//----------------------------------------------------------------------------- -// getObjectCount() - returns number of non null objects -//----------------------------------------------------------------------------- -S32 LLObjectSelection::getObjectCount() -{ - cleanupNodes(); - S32 count = mList.size(); - - return count; -} - -F32 LLObjectSelection::getSelectedObjectCost() -{ - cleanupNodes(); - F32 cost = 0.f; - - for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - - if (object) - { - cost += object->getObjectCost(); - } - } - - return cost; -} - -F32 LLObjectSelection::getSelectedLinksetCost() -{ - cleanupNodes(); - F32 cost = 0.f; - - std::set me_roots; - - for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - - if (object && !object->isAttachment()) - { - LLViewerObject* root = static_cast(object->getRoot()); - if (root) - { - if (me_roots.find(root) == me_roots.end()) - { - me_roots.insert(root); - cost += root->getLinksetCost(); - } - } - } - } - - return cost; -} - -F32 LLObjectSelection::getSelectedPhysicsCost() -{ - cleanupNodes(); - F32 cost = 0.f; - - for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - - if (object) - { - cost += object->getPhysicsCost(); - } - } - - return cost; -} - -F32 LLObjectSelection::getSelectedLinksetPhysicsCost() -{ - cleanupNodes(); - F32 cost = 0.f; - - std::set me_roots; - - for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - - if (object) - { - LLViewerObject* root = static_cast(object->getRoot()); - if (root) - { - if (me_roots.find(root) == me_roots.end()) - { - me_roots.insert(root); - cost += root->getLinksetPhysicsCost(); - } - } - } - } - - return cost; -} - -F32 LLObjectSelection::getSelectedObjectStreamingCost(S32* total_bytes, S32* visible_bytes) -{ - F32 cost = 0.f; - for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - - if (object) - { - cost += object->getStreamingCost(); - - S32 bytes = 0; - S32 visible = 0; - LLMeshCostData costs; - if (object->getCostData(costs)) - { - bytes = costs.getSizeTotal(); - visible = costs.getSizeByLOD(object->getLOD()); - } - if (total_bytes) - { - *total_bytes += bytes; - } - - if (visible_bytes) - { - *visible_bytes += visible; - } - } - } - - return cost; -} - -U32 LLObjectSelection::getSelectedObjectTriangleCount(S32* vcount) -{ - U32 count = 0; - for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - - if (object) - { - S32 vt = 0; - count += object->getTriangleCount(&vt); - *vcount += vt; - } - } - - return count; -} - -S32 LLObjectSelection::getSelectedObjectRenderCost() -{ - S32 cost = 0; - LLVOVolume::texture_cost_t textures; - typedef std::set uuid_list_t; - uuid_list_t computed_objects; - - typedef std::list > child_list_t; - typedef const child_list_t const_child_list_t; - - // add render cost of complete linksets first, to get accurate texture counts - for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter) - { - LLSelectNode* node = *iter; - - LLVOVolume* object = (LLVOVolume*)node->getObject(); - - if (object && object->isRootEdit()) - { - cost += object->getRenderCost(textures); - computed_objects.insert(object->getID()); - - const_child_list_t children = object->getChildren(); - for (const_child_list_t::const_iterator child_iter = children.begin(); - child_iter != children.end(); - ++child_iter) - { - LLViewerObject* child_obj = *child_iter; - LLVOVolume *child = dynamic_cast( child_obj ); - if (child) - { - cost += child->getRenderCost(textures); - computed_objects.insert(child->getID()); - } - } - - for (LLVOVolume::texture_cost_t::iterator iter = textures.begin(); iter != textures.end(); ++iter) - { - // add the cost of each individual texture in the linkset - cost += LLVOVolume::getTextureCost(*iter); - } - - textures.clear(); - } - } - - // add any partial linkset objects, texture cost may be slightly misleading - for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter) - { - LLSelectNode* node = *iter; - LLVOVolume* object = (LLVOVolume*)node->getObject(); - - if (object && computed_objects.find(object->getID()) == computed_objects.end() ) - { - cost += object->getRenderCost(textures); - computed_objects.insert(object->getID()); - } - - for (LLVOVolume::texture_cost_t::iterator iter = textures.begin(); iter != textures.end(); ++iter) - { - // add the cost of each individual texture in the linkset - cost += LLVOVolume::getTextureCost(*iter); - } - - textures.clear(); - } - - return cost; -} - -//----------------------------------------------------------------------------- -// getTECount() -//----------------------------------------------------------------------------- -S32 LLObjectSelection::getTECount() -{ - S32 count = 0; - for (LLObjectSelection::iterator iter = begin(); iter != end(); iter++) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - if (!object) - continue; - S32 num_tes = object->getNumTEs(); - for (S32 te = 0; te < num_tes; te++) - { - if (node->isTESelected(te)) - { - ++count; - } - } - } - return count; -} - -//----------------------------------------------------------------------------- -// getRootObjectCount() -//----------------------------------------------------------------------------- -S32 LLObjectSelection::getRootObjectCount() -{ - S32 count = 0; - for (LLObjectSelection::root_iterator iter = root_begin(); iter != root_end(); iter++) - { - ++count; - } - return count; -} - -bool LLObjectSelection::applyToObjects(LLSelectedObjectFunctor* func) -{ - bool result = true; - for (iterator iter = begin(); iter != end(); ) - { - iterator nextiter = iter++; - LLViewerObject* object = (*nextiter)->getObject(); - if (!object) - continue; - bool r = func->apply(object); - result = result && r; - } - return result; -} - -bool LLObjectSelection::checkAnimatedObjectEstTris() -{ - F32 est_tris = 0; - F32 max_tris = 0; - S32 anim_count = 0; - for (root_iterator iter = root_begin(); iter != root_end(); ++iter) - { - LLViewerObject* object = (*iter)->getObject(); - if (!object) - continue; - if (object->isAnimatedObject()) - { - anim_count++; - } - est_tris += object->recursiveGetEstTrianglesMax(); - max_tris = llmax((F32)max_tris,(F32)object->getAnimatedObjectMaxTris()); - } - return anim_count==0 || est_tris <= max_tris; -} - -bool LLObjectSelection::checkAnimatedObjectLinkable() -{ - return checkAnimatedObjectEstTris(); -} - -bool LLObjectSelection::applyToRootObjects(LLSelectedObjectFunctor* func, bool firstonly) -{ - bool result = !firstonly; - for (root_iterator iter = root_begin(); iter != root_end(); ) - { - root_iterator nextiter = iter++; - LLViewerObject* object = (*nextiter)->getObject(); - if (!object) - continue; - bool r = func->apply(object); - if (firstonly && r) - return true; - else - result = result && r; - } - return result; -} - -bool LLObjectSelection::applyToTEs(LLSelectedTEFunctor* func, bool firstonly) -{ - bool result = !firstonly; - for (iterator iter = begin(); iter != end(); ) - { - iterator nextiter = iter++; - LLSelectNode* node = *nextiter; - LLViewerObject* object = (*nextiter)->getObject(); - if (!object) - continue; - S32 num_tes = llmin((S32)object->getNumTEs(), (S32)object->getNumFaces()); // avatars have TEs but no faces - for (S32 te = 0; te < num_tes; ++te) - { - if (node->isTESelected(te)) - { - bool r = func->apply(object, te); - if (firstonly && r) - return true; - else - result = result && r; - } - } - } - return result; -} - -bool LLObjectSelection::applyToNodes(LLSelectedNodeFunctor *func, bool firstonly) -{ - bool result = !firstonly; - for (iterator iter = begin(); iter != end(); ) - { - iterator nextiter = iter++; - LLSelectNode* node = *nextiter; - bool r = func->apply(node); - if (firstonly && r) - return true; - else - result = result && r; - } - return result; -} - -bool LLObjectSelection::applyToRootNodes(LLSelectedNodeFunctor *func, bool firstonly) -{ - bool result = !firstonly; - for (root_iterator iter = root_begin(); iter != root_end(); ) - { - root_iterator nextiter = iter++; - LLSelectNode* node = *nextiter; - bool r = func->apply(node); - if (firstonly && r) - return true; - else - result = result && r; - } - return result; -} - -bool LLObjectSelection::isMultipleTESelected() -{ - bool te_selected = false; - // ...all faces - for (LLObjectSelection::iterator iter = begin(); - iter != end(); iter++) - { - LLSelectNode* nodep = *iter; - for (S32 i = 0; i < SELECT_MAX_TES; i++) - { - if(nodep->isTESelected(i)) - { - if(te_selected) - { - return true; - } - te_selected = true; - } - } - } - return false; -} - -//----------------------------------------------------------------------------- -// contains() -//----------------------------------------------------------------------------- -bool LLObjectSelection::contains(LLViewerObject* object) -{ - return findNode(object) != NULL; -} - - -//----------------------------------------------------------------------------- -// contains() -//----------------------------------------------------------------------------- -bool LLObjectSelection::contains(LLViewerObject* object, S32 te) -{ - if (te == SELECT_ALL_TES) - { - // ...all faces - for (LLObjectSelection::iterator iter = begin(); - iter != end(); iter++) - { - LLSelectNode* nodep = *iter; - if (nodep->getObject() == object) - { - // Optimization - if (nodep->getTESelectMask() == TE_SELECT_MASK_ALL) - { - return true; - } - - bool all_selected = true; - for (S32 i = 0; i < object->getNumTEs(); i++) - { - all_selected = all_selected && nodep->isTESelected(i); - } - return all_selected; - } - } - return false; - } - else - { - // ...one face - for (LLObjectSelection::iterator iter = begin(); iter != end(); iter++) - { - LLSelectNode* nodep = *iter; - if (nodep->getObject() == object && nodep->isTESelected(te)) - { - return true; - } - } - return false; - } -} - -// returns true is any node is currenly worn as an attachment -bool LLObjectSelection::isAttachment() -{ - return (mSelectType == SELECT_TYPE_ATTACHMENT || mSelectType == SELECT_TYPE_HUD); -} - -//----------------------------------------------------------------------------- -// getSelectedParentObject() -//----------------------------------------------------------------------------- -LLViewerObject* getSelectedParentObject(LLViewerObject *object) -{ - LLViewerObject *parent; - while (object && (parent = (LLViewerObject*)object->getParent())) - { - if (parent->isSelected()) - { - object = parent; - } - else - { - break; - } - } - return object; -} - -//----------------------------------------------------------------------------- -// getFirstNode -//----------------------------------------------------------------------------- -LLSelectNode* LLObjectSelection::getFirstNode(LLSelectedNodeFunctor* func) -{ - for (iterator iter = begin(); iter != end(); ++iter) - { - LLSelectNode* node = *iter; - if (func == NULL || func->apply(node)) - { - return node; - } - } - return NULL; -} - -LLSelectNode* LLObjectSelection::getFirstRootNode(LLSelectedNodeFunctor* func, bool non_root_ok) -{ - for (root_iterator iter = root_begin(); iter != root_end(); ++iter) - { - LLSelectNode* node = *iter; - if (func == NULL || func->apply(node)) - { - return node; - } - } - if (non_root_ok) - { - // Get non root - return getFirstNode(func); - } - return NULL; -} - - -//----------------------------------------------------------------------------- -// getFirstSelectedObject -//----------------------------------------------------------------------------- -LLViewerObject* LLObjectSelection::getFirstSelectedObject(LLSelectedNodeFunctor* func, bool get_parent) -{ - LLSelectNode* res = getFirstNode(func); - if (res && get_parent) - { - return getSelectedParentObject(res->getObject()); - } - else if (res) - { - return res->getObject(); - } - return NULL; -} - -//----------------------------------------------------------------------------- -// getFirstObject() -//----------------------------------------------------------------------------- -LLViewerObject* LLObjectSelection::getFirstObject() -{ - LLSelectNode* res = getFirstNode(NULL); - return res ? res->getObject() : NULL; -} - -//----------------------------------------------------------------------------- -// getFirstRootObject() -//----------------------------------------------------------------------------- -LLViewerObject* LLObjectSelection::getFirstRootObject(bool non_root_ok) -{ - LLSelectNode* res = getFirstRootNode(NULL, non_root_ok); - return res ? res->getObject() : NULL; -} - -//----------------------------------------------------------------------------- -// getFirstMoveableNode() -//----------------------------------------------------------------------------- -LLSelectNode* LLObjectSelection::getFirstMoveableNode(bool get_root_first) -{ - struct f : public LLSelectedNodeFunctor - { - bool apply(LLSelectNode* node) - { - LLViewerObject* obj = node->getObject(); - return obj && obj->permMove() && !obj->isPermanentEnforced(); - } - } func; - LLSelectNode* res = get_root_first ? getFirstRootNode(&func, true) : getFirstNode(&func); - return res; -} - -//----------------------------------------------------------------------------- -// getFirstCopyableObject() -//----------------------------------------------------------------------------- -LLViewerObject* LLObjectSelection::getFirstCopyableObject(bool get_parent) -{ - struct f : public LLSelectedNodeFunctor - { - bool apply(LLSelectNode* node) - { - LLViewerObject* obj = node->getObject(); - return obj && obj->permCopy() && !obj->isAttachment(); - } - } func; - return getFirstSelectedObject(&func, get_parent); -} - -//----------------------------------------------------------------------------- -// getFirstDeleteableObject() -//----------------------------------------------------------------------------- -LLViewerObject* LLObjectSelection::getFirstDeleteableObject() -{ - //RN: don't currently support deletion of child objects, as that requires separating them first - // then derezzing to trash - - struct f : public LLSelectedNodeFunctor - { - bool apply(LLSelectNode* node) - { - LLViewerObject* obj = node->getObject(); - // you can delete an object if you are the owner - // or you have permission to modify it. - if( obj && !obj->isPermanentEnforced() && - ( (obj->permModify()) || - (obj->permYouOwner()) || - (!obj->permAnyOwner()) )) // public - { - if( !obj->isAttachment() ) - { - return true; - } - } - return false; - } - } func; - LLSelectNode* node = getFirstNode(&func); - return node ? node->getObject() : NULL; -} - -//----------------------------------------------------------------------------- -// getFirstEditableObject() -//----------------------------------------------------------------------------- -LLViewerObject* LLObjectSelection::getFirstEditableObject(bool get_parent) -{ - struct f : public LLSelectedNodeFunctor - { - bool apply(LLSelectNode* node) - { - LLViewerObject* obj = node->getObject(); - return obj && obj->permModify(); - } - } func; - return getFirstSelectedObject(&func, get_parent); -} - -//----------------------------------------------------------------------------- -// getFirstMoveableObject() -//----------------------------------------------------------------------------- -LLViewerObject* LLObjectSelection::getFirstMoveableObject(bool get_parent) -{ - struct f : public LLSelectedNodeFunctor - { - bool apply(LLSelectNode* node) - { - LLViewerObject* obj = node->getObject(); - return obj && obj->permMove() && !obj->isPermanentEnforced(); - } - } func; - return getFirstSelectedObject(&func, get_parent); -} - -//----------------------------------------------------------------------------- -// getFirstUndoEnabledObject() -//----------------------------------------------------------------------------- -LLViewerObject* LLObjectSelection::getFirstUndoEnabledObject(bool get_parent) -{ - struct f : public LLSelectedNodeFunctor - { - bool apply(LLSelectNode* node) - { - LLViewerObject* obj = node->getObject(); - return obj && (obj->permModify() || (obj->permMove() && !obj->isPermanentEnforced())); - } - } func; - return getFirstSelectedObject(&func, get_parent); -} - -//----------------------------------------------------------------------------- -// Position + Rotation update methods called from LLViewerJoystick -//----------------------------------------------------------------------------- -bool LLSelectMgr::selectionMove(const LLVector3& displ, - F32 roll, F32 pitch, F32 yaw, U32 update_type) -{ - if (update_type == UPD_NONE) - { - return false; - } - - LLVector3 displ_global; - bool update_success = true; - bool update_position = update_type & UPD_POSITION; - bool update_rotation = update_type & UPD_ROTATION; - const bool noedit_linked_parts = !gSavedSettings.getBOOL("EditLinkedParts"); - - if (update_position) - { - // calculate the distance of the object closest to the camera origin - F32 min_dist_squared = F32_MAX; // value will be overridden in the loop - - LLVector3 obj_pos; - for (LLObjectSelection::root_iterator it = getSelection()->root_begin(); - it != getSelection()->root_end(); ++it) - { - obj_pos = (*it)->getObject()->getPositionEdit(); - - F32 obj_dist_squared = dist_vec_squared(obj_pos, LLViewerCamera::getInstance()->getOrigin()); - if (obj_dist_squared < min_dist_squared) - { - min_dist_squared = obj_dist_squared; - } - } - - // factor the distance into the displacement vector. This will get us - // equally visible movements for both close and far away selections. - F32 min_dist = sqrt((F32) sqrtf(min_dist_squared)) / 2; - displ_global.setVec(displ.mV[0] * min_dist, - displ.mV[1] * min_dist, - displ.mV[2] * min_dist); - - // equates to: Displ_global = Displ * M_cam_axes_in_global_frame - displ_global = LLViewerCamera::getInstance()->rotateToAbsolute(displ_global); - } - - LLQuaternion new_rot; - if (update_rotation) - { - // let's calculate the rotation around each camera axes - LLQuaternion qx(roll, LLViewerCamera::getInstance()->getAtAxis()); - LLQuaternion qy(pitch, LLViewerCamera::getInstance()->getLeftAxis()); - LLQuaternion qz(yaw, LLViewerCamera::getInstance()->getUpAxis()); - new_rot.setQuat(qx * qy * qz); - } - - LLViewerObject *obj; - S32 obj_count = getSelection()->getObjectCount(); - for (LLObjectSelection::root_iterator it = getSelection()->root_begin(); - it != getSelection()->root_end(); ++it ) - { - obj = (*it)->getObject(); - bool enable_pos = false, enable_rot = false; - bool perm_move = obj->permMove() && !obj->isPermanentEnforced(); - bool perm_mod = obj->permModify(); - - LLVector3d sel_center(getSelectionCenterGlobal()); - - if (update_rotation) - { - enable_rot = perm_move - && ((perm_mod && !obj->isAttachment()) || noedit_linked_parts); - - if (enable_rot) - { - int children_count = obj->getChildren().size(); - if (obj_count > 1 && children_count > 0) - { - // for linked sets, rotate around the group center - const LLVector3 t(obj->getPositionGlobal() - sel_center); - - // Ra = T x R x T^-1 - LLMatrix4 mt; mt.setTranslation(t); - const LLMatrix4 mnew_rot(new_rot); - LLMatrix4 mt_1; mt_1.setTranslation(-t); - mt *= mnew_rot; - mt *= mt_1; - - // Rfin = Rcur * Ra - obj->setRotation(obj->getRotationEdit() * mt.quaternion()); - displ_global += mt.getTranslation(); - } - else - { - obj->setRotation(obj->getRotationEdit() * new_rot); - } - } - else - { - update_success = false; - } - } - - if (update_position) - { - // establish if object can be moved or not - enable_pos = perm_move && !obj->isAttachment() - && (perm_mod || noedit_linked_parts); - - if (enable_pos) - { - obj->setPosition(obj->getPositionEdit() + displ_global); - } - else - { - update_success = false; - } - } - - if (enable_pos && enable_rot && obj->mDrawable.notNull()) - { - gPipeline.markMoved(obj->mDrawable, true); - } - } - - if (update_position && update_success && obj_count > 1) - { - updateSelectionCenter(); - } - - return update_success; -} - -void LLSelectMgr::sendSelectionMove() -{ - LLSelectNode *node = mSelectedObjects->getFirstRootNode(); - if (node == NULL) - { - return; - } - - //saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); - - U32 update_type = UPD_POSITION | UPD_ROTATION; - LLViewerRegion *last_region, *curr_region = node->getObject()->getRegion(); - S32 objects_in_this_packet = 0; - - // apply to linked objects if unable to select their individual parts - if (!gSavedSettings.getBOOL("EditLinkedParts") && !getTEMode()) - { - // tell simulator to apply to whole linked sets - update_type |= UPD_LINKED_SETS; - } - - // prepare first bulk message - gMessageSystem->newMessage("MultipleObjectUpdate"); - packAgentAndSessionID(&update_type); - - LLViewerObject *obj = NULL; - for (LLObjectSelection::root_iterator it = getSelection()->root_begin(); - it != getSelection()->root_end(); ++it) - { - obj = (*it)->getObject(); - - // note: following code adapted from sendListToRegions() (@3924) - last_region = curr_region; - curr_region = obj->getRegion(); - - // if not simulator or message too big - if (curr_region != last_region - || gMessageSystem->isSendFull(NULL) - || objects_in_this_packet >= MAX_OBJECTS_PER_PACKET) - { - // send sim the current message and start new one - gMessageSystem->sendReliable(last_region->getHost()); - objects_in_this_packet = 0; - gMessageSystem->newMessage("MultipleObjectUpdate"); - packAgentAndSessionID(&update_type); - } - - // add another instance of the body of data - packMultipleUpdate(*it, &update_type); - ++objects_in_this_packet; - } - - // flush remaining messages - if (gMessageSystem->getCurrentSendTotal() > 0) - { - gMessageSystem->sendReliable(curr_region->getHost()); - } - else - { - gMessageSystem->clearMessage(); - } - - //saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); -} - -template<> -bool LLCheckIdenticalFunctor::same(const F32& a, const F32& b, const F32& tolerance) -{ - F32 delta = (a - b); - F32 abs_delta = fabs(delta); - return abs_delta <= tolerance; -} - -#define DEF_DUMMY_CHECK_FUNCTOR(T) \ -template<> \ -bool LLCheckIdenticalFunctor::same(const T& a, const T& b, const T& tolerance) \ -{ \ - (void)tolerance; \ - return a == b; \ -} - -DEF_DUMMY_CHECK_FUNCTOR(LLUUID) -DEF_DUMMY_CHECK_FUNCTOR(LLGLenum) -DEF_DUMMY_CHECK_FUNCTOR(LLTextureEntry) -DEF_DUMMY_CHECK_FUNCTOR(LLTextureEntry::e_texgen) -DEF_DUMMY_CHECK_FUNCTOR(bool) -DEF_DUMMY_CHECK_FUNCTOR(U8) -DEF_DUMMY_CHECK_FUNCTOR(int) -DEF_DUMMY_CHECK_FUNCTOR(LLColor4) -DEF_DUMMY_CHECK_FUNCTOR(LLMediaEntry) -DEF_DUMMY_CHECK_FUNCTOR(LLPointer) -DEF_DUMMY_CHECK_FUNCTOR(LLPointer) -DEF_DUMMY_CHECK_FUNCTOR(std::string) -DEF_DUMMY_CHECK_FUNCTOR(std::vector) - -template<> -bool LLCheckIdenticalFunctor::same(class LLFace* const & a, class LLFace* const & b, class LLFace* const & tolerance) \ -{ \ - (void)tolerance; \ - return a == b; \ -} - +/** + * @file llselectmgr.cpp + * @brief A manager for selected objects and faces. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +// file include +#define LLSELECTMGR_CPP +#include "llselectmgr.h" +#include "llmaterialmgr.h" + +// library includes +#include "llcachename.h" +#include "llavatarnamecache.h" +#include "lldbstrings.h" +#include "llgl.h" +#include "llmediaentry.h" +#include "llrender.h" +#include "llnotifications.h" +#include "llpermissions.h" +#include "llpermissionsflags.h" +#include "lltrans.h" +#include "llundo.h" +#include "lluuid.h" +#include "llvolume.h" +#include "llcontrolavatar.h" +#include "message.h" +#include "object_flags.h" +#include "llquaternion.h" + +// viewer includes +#include "llagent.h" +#include "llagentcamera.h" +#include "llattachmentsmgr.h" +#include "llviewerwindow.h" +#include "lldrawable.h" +#include "llfloaterinspect.h" +#include "llfloaterreporter.h" +#include "llfloaterreg.h" +#include "llfloatertools.h" +#include "llframetimer.h" +#include "llfocusmgr.h" +#include "llgltfmateriallist.h" +#include "llhudeffecttrail.h" +#include "llhudmanager.h" +#include "llinventorymodel.h" +#include "llmenugl.h" +#include "llmeshrepository.h" +#include "llmutelist.h" +#include "llnotificationsutil.h" +#include "llsidepaneltaskinfo.h" +#include "llslurl.h" +#include "llstatusbar.h" +#include "llsurface.h" +#include "lltool.h" +#include "lltooldraganddrop.h" +#include "lltoolmgr.h" +#include "lltoolpie.h" +#include "llui.h" +#include "llviewercamera.h" +#include "llviewercontrol.h" +#include "llviewertexturelist.h" +#include "llviewermedia.h" +#include "llviewermediafocus.h" +#include "llviewermenu.h" +#include "llviewerobject.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llviewerstats.h" +#include "llvoavatarself.h" +#include "llvovolume.h" +#include "pipeline.h" +#include "llviewershadermgr.h" +#include "llpanelface.h" +#include "llglheaders.h" +#include "llinventoryobserver.h" + +LLViewerObject* getSelectedParentObject(LLViewerObject *object) ; +// +// Consts +// + +const F32 SILHOUETTE_UPDATE_THRESHOLD_SQUARED = 0.02f; +const S32 MAX_SILS_PER_FRAME = 50; +const S32 MAX_OBJECTS_PER_PACKET = 254; +// For linked sets +const S32 MAX_CHILDREN_PER_TASK = 255; + +// +// Globals +// + +//bool gDebugSelectMgr = false; + +//bool gHideSelectedObjects = false; +//bool gAllowSelectAvatar = false; + +bool LLSelectMgr::sRectSelectInclusive = true; +bool LLSelectMgr::sRenderHiddenSelections = true; +bool LLSelectMgr::sRenderLightRadius = false; +F32 LLSelectMgr::sHighlightThickness = 0.f; +F32 LLSelectMgr::sHighlightUScale = 0.f; +F32 LLSelectMgr::sHighlightVScale = 0.f; +F32 LLSelectMgr::sHighlightAlpha = 0.f; +F32 LLSelectMgr::sHighlightAlphaTest = 0.f; +F32 LLSelectMgr::sHighlightUAnim = 0.f; +F32 LLSelectMgr::sHighlightVAnim = 0.f; +LLColor4 LLSelectMgr::sSilhouetteParentColor; +LLColor4 LLSelectMgr::sSilhouetteChildColor; +LLColor4 LLSelectMgr::sHighlightInspectColor; +LLColor4 LLSelectMgr::sHighlightParentColor; +LLColor4 LLSelectMgr::sHighlightChildColor; +LLColor4 LLSelectMgr::sContextSilhouetteColor; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// struct LLDeRezInfo +// +// Used to keep track of important derez info. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +struct LLDeRezInfo +{ + EDeRezDestination mDestination; + LLUUID mDestinationID; + LLDeRezInfo(EDeRezDestination dest, const LLUUID& dest_id) : + mDestination(dest), mDestinationID(dest_id) {} +}; + +// +// Imports +// + +//----------------------------------------------------------------------------- +// ~LLSelectionCallbackData() +//----------------------------------------------------------------------------- + +LLSelectionCallbackData::LLSelectionCallbackData() +{ + LLSelectMgr *instance = LLSelectMgr::getInstance(); + LLObjectSelectionHandle selection = instance->getSelection(); + if (!selection->getNumNodes()) + { + return; + } + mSelectedObjects = new LLObjectSelection(); + + for (LLObjectSelection::iterator iter = selection->begin(); + iter != selection->end();) + { + LLObjectSelection::iterator curiter = iter++; + + LLSelectNode *nodep = *curiter; + LLViewerObject* objectp = nodep->getObject(); + + if (!objectp) + { + mSelectedObjects->mSelectType = SELECT_TYPE_WORLD; + } + else + { + LLSelectNode* new_nodep = new LLSelectNode(*nodep); + mSelectedObjects->addNode(new_nodep); + + if (objectp->isHUDAttachment()) + { + mSelectedObjects->mSelectType = SELECT_TYPE_HUD; + } + else if (objectp->isAttachment()) + { + mSelectedObjects->mSelectType = SELECT_TYPE_ATTACHMENT; + } + else + { + mSelectedObjects->mSelectType = SELECT_TYPE_WORLD; + } + } + } +} + + +// +// Functions +// + +void LLSelectMgr::cleanupGlobals() +{ + LLSelectMgr::getInstance()->clearSelections(); +} + +//----------------------------------------------------------------------------- +// LLSelectMgr() +//----------------------------------------------------------------------------- +LLSelectMgr::LLSelectMgr() + : mHideSelectedObjects(LLCachedControl(gSavedSettings, "HideSelectedObjects", false)), + mRenderHighlightSelections(LLCachedControl(gSavedSettings, "RenderHighlightSelections", true)), + mAllowSelectAvatar( LLCachedControl(gSavedSettings, "AllowSelectAvatar", false)), + mDebugSelectMgr(LLCachedControl(gSavedSettings, "DebugSelectMgr", false)) +{ + mTEMode = false; + mTextureChannel = LLRender::DIFFUSE_MAP; + mLastCameraPos.clearVec(); + + sHighlightThickness = gSavedSettings.getF32("SelectionHighlightThickness"); + sHighlightUScale = gSavedSettings.getF32("SelectionHighlightUScale"); + sHighlightVScale = gSavedSettings.getF32("SelectionHighlightVScale"); + sHighlightAlpha = gSavedSettings.getF32("SelectionHighlightAlpha") * 2; + sHighlightAlphaTest = gSavedSettings.getF32("SelectionHighlightAlphaTest"); + sHighlightUAnim = gSavedSettings.getF32("SelectionHighlightUAnim"); + sHighlightVAnim = gSavedSettings.getF32("SelectionHighlightVAnim"); + + sSilhouetteParentColor =LLUIColorTable::instance().getColor("SilhouetteParentColor"); + sSilhouetteChildColor = LLUIColorTable::instance().getColor("SilhouetteChildColor"); + sHighlightParentColor = LLUIColorTable::instance().getColor("HighlightParentColor"); + sHighlightChildColor = LLUIColorTable::instance().getColor("HighlightChildColor"); + sHighlightInspectColor = LLUIColorTable::instance().getColor("HighlightInspectColor"); + sContextSilhouetteColor = LLUIColorTable::instance().getColor("ContextSilhouetteColor")*0.5f; + + sRenderLightRadius = gSavedSettings.getBOOL("RenderLightRadius"); + + mRenderSilhouettes = true; + + mGridMode = GRID_MODE_WORLD; + gSavedSettings.setS32("GridMode", (S32)GRID_MODE_WORLD); + + mSelectedObjects = new LLObjectSelection(); + mHoverObjects = new LLObjectSelection(); + mHighlightedObjects = new LLObjectSelection(); + + mForceSelection = false; + mShowSelection = false; +} + + +//----------------------------------------------------------------------------- +// ~LLSelectMgr() +//----------------------------------------------------------------------------- +LLSelectMgr::~LLSelectMgr() +{ + clearSelections(); +} + +void LLSelectMgr::clearSelections() +{ + mHoverObjects->deleteAllNodes(); + mSelectedObjects->deleteAllNodes(); + mHighlightedObjects->deleteAllNodes(); + mRectSelectedObjects.clear(); + mGridObjects.deleteAllNodes(); + + LLPipeline::setRenderHighlightTextureChannel(LLRender::DIFFUSE_MAP); +} + +void LLSelectMgr::update() +{ + mSelectedObjects->cleanupNodes(); +} + +void LLSelectMgr::updateEffects() +{ + //keep reference grid objects active + struct f : public LLSelectedObjectFunctor + { + virtual bool apply(LLViewerObject* object) + { + LLDrawable* drawable = object->mDrawable; + if (drawable) + { + gPipeline.markMoved(drawable); + } + return true; + } + } func; + mGridObjects.applyToObjects(&func); + + if (mEffectsTimer.getElapsedTimeF32() > 1.f) + { + mSelectedObjects->updateEffects(); + mEffectsTimer.reset(); + } +} + +void LLSelectMgr::resetObjectOverrides() +{ + resetObjectOverrides(getSelection()); +} + +void LLSelectMgr::resetObjectOverrides(LLObjectSelectionHandle selected_handle) +{ + struct f : public LLSelectedNodeFunctor + { + f(bool a, LLSelectMgr* p) : mAvatarOverridesPersist(a), mManager(p) {} + bool mAvatarOverridesPersist; + LLSelectMgr* mManager; + virtual bool apply(LLSelectNode* node) + { + if (mAvatarOverridesPersist) + { + LLViewerObject* object = node->getObject(); + if (object && !object->getParent()) + { + LLVOAvatar* avatar = object->asAvatar(); + if (avatar) + { + mManager->mAvatarOverridesMap.emplace(avatar->getID(), AvatarPositionOverride(node->mLastPositionLocal, node->mLastRotation, object)); + } + } + } + node->mLastPositionLocal.setVec(0, 0, 0); + node->mLastRotation = LLQuaternion(); + node->mLastScale.setVec(0, 0, 0); + return true; + } + } func(mAllowSelectAvatar, this); + + selected_handle->applyToNodes(&func); +} + +void LLSelectMgr::overrideObjectUpdates() +{ + //override any position updates from simulator on objects being edited + struct f : public LLSelectedNodeFunctor + { + virtual bool apply(LLSelectNode* selectNode) + { + LLViewerObject* object = selectNode->getObject(); + if (object && object->permMove() && !object->isPermanentEnforced()) + { + if (!selectNode->mLastPositionLocal.isExactlyZero()) + { + object->setPosition(selectNode->mLastPositionLocal); + } + if (selectNode->mLastRotation != LLQuaternion()) + { + object->setRotation(selectNode->mLastRotation); + } + if (!selectNode->mLastScale.isExactlyZero()) + { + object->setScale(selectNode->mLastScale); + } + } + return true; + } + } func; + getSelection()->applyToNodes(&func); +} + +void LLSelectMgr::resetAvatarOverrides() +{ + mAvatarOverridesMap.clear(); +} + +void LLSelectMgr::overrideAvatarUpdates() +{ + if (mAvatarOverridesMap.size() == 0) + { + return; + } + + if (!mAllowSelectAvatar || !gFloaterTools) + { + resetAvatarOverrides(); + return; + } + + if (!gFloaterTools->getVisible() && getSelection()->isEmpty()) + { + // when user switches selection, floater is invisible and selection is empty + LLToolset *toolset = LLToolMgr::getInstance()->getCurrentToolset(); + if (toolset->isShowFloaterTools() + && toolset->isToolSelected(0)) // Pie tool + { + resetAvatarOverrides(); + return; + } + } + + // remove selected avatars from this list, + // but set object overrides to make sure avatar won't snap back + struct f : public LLSelectedNodeFunctor + { + f(LLSelectMgr* p) : mManager(p) {} + LLSelectMgr* mManager; + virtual bool apply(LLSelectNode* selectNode) + { + LLViewerObject* object = selectNode->getObject(); + if (object && !object->getParent()) + { + LLVOAvatar* avatar = object->asAvatar(); + if (avatar) + { + uuid_av_override_map_t::iterator iter = mManager->mAvatarOverridesMap.find(avatar->getID()); + if (iter != mManager->mAvatarOverridesMap.end()) + { + if (selectNode->mLastPositionLocal.isExactlyZero()) + { + selectNode->mLastPositionLocal = iter->second.mLastPositionLocal; + } + if (selectNode->mLastRotation == LLQuaternion()) + { + selectNode->mLastRotation = iter->second.mLastRotation; + } + mManager->mAvatarOverridesMap.erase(iter); + } + } + } + return true; + } + } func(this); + getSelection()->applyToNodes(&func); + + // Override avatar positions + uuid_av_override_map_t::iterator it = mAvatarOverridesMap.begin(); + while (it != mAvatarOverridesMap.end()) + { + if (it->second.mObject->isDead()) + { + it = mAvatarOverridesMap.erase(it); + } + else + { + if (!it->second.mLastPositionLocal.isExactlyZero()) + { + it->second.mObject->setPosition(it->second.mLastPositionLocal); + } + if (it->second.mLastRotation != LLQuaternion()) + { + it->second.mObject->setRotation(it->second.mLastRotation); + } + it++; + } + } +} + +//----------------------------------------------------------------------------- +// Select just the object, not any other group members. +//----------------------------------------------------------------------------- +LLObjectSelectionHandle LLSelectMgr::selectObjectOnly(LLViewerObject* object, S32 face) +{ + llassert( object ); + + //remember primary object + mSelectedObjects->mPrimaryObject = object; + + // Don't add an object that is already in the list + if (object->isSelected() ) { + // make sure point at position is updated + updatePointAt(); + gEditMenuHandler = this; + return NULL; + } + + if (!canSelectObject(object)) + { + //make_ui_sound("UISndInvalidOp"); + return NULL; + } + + // LL_INFOS() << "Adding object to selected object list" << LL_ENDL; + + // Place it in the list and tag it. + // This will refresh dialogs. + addAsIndividual(object, face); + + // Stop the object from moving (this anticipates changes on the + // simulator in LLTask::userSelect) + // *FIX: shouldn't zero out these either + object->setVelocity(LLVector3::zero); + object->setAcceleration(LLVector3::zero); + //object->setAngularVelocity(LLVector3::zero); + object->resetRot(); + + // Always send to simulator, so you get a copy of the + // permissions structure back. + gMessageSystem->newMessageFast(_PREHASH_ObjectSelect); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + gMessageSystem->nextBlockFast(_PREHASH_ObjectData); + gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, object->getLocalID() ); + LLViewerRegion* regionp = object->getRegion(); + gMessageSystem->sendReliable( regionp->getHost()); + + updatePointAt(); + updateSelectionCenter(); + saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); + + // have selection manager handle edit menu immediately after + // user selects an object + if (mSelectedObjects->getObjectCount()) + { + gEditMenuHandler = this; + } + + return mSelectedObjects; +} + +//----------------------------------------------------------------------------- +// Select the object, parents and children. +//----------------------------------------------------------------------------- +LLObjectSelectionHandle LLSelectMgr::selectObjectAndFamily(LLViewerObject* obj, bool add_to_end, bool ignore_select_owned) +{ + llassert( obj ); + + //remember primary object + mSelectedObjects->mPrimaryObject = obj; + + // This may be incorrect if things weren't family selected before... - djs 07/08/02 + // Don't add an object that is already in the list + if (obj->isSelected() ) + { + // make sure pointat position is updated + updatePointAt(); + gEditMenuHandler = this; + return NULL; + } + + if (!canSelectObject(obj,ignore_select_owned)) + { + //make_ui_sound("UISndInvalidOp"); + return NULL; + } + + // Since we're selecting a family, start at the root, but + // don't include an avatar. + LLViewerObject* root = obj; + + while(!root->isAvatar() && root->getParent()) + { + LLViewerObject* parent = (LLViewerObject*)root->getParent(); + if (parent->isAvatar()) + { + break; + } + root = parent; + } + + // Collect all of the objects + std::vector objects; + + root->addThisAndNonJointChildren(objects); + addAsFamily(objects, add_to_end); + + updateSelectionCenter(); + saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); + updatePointAt(); + + dialog_refresh_all(); + + // Always send to simulator, so you get a copy of the permissions + // structure back. + sendSelect(); + + // Stop the object from moving (this anticipates changes on the + // simulator in LLTask::userSelect) + root->setVelocity(LLVector3::zero); + root->setAcceleration(LLVector3::zero); + //root->setAngularVelocity(LLVector3::zero); + root->resetRot(); + + // leave component mode + if (gSavedSettings.getBOOL("EditLinkedParts")) + { + gSavedSettings.setBOOL("EditLinkedParts", false); + promoteSelectionToRoot(); + } + + // have selection manager handle edit menu immediately after + // user selects an object + if (mSelectedObjects->getObjectCount()) + { + gEditMenuHandler = this; + } + + return mSelectedObjects; +} + +//----------------------------------------------------------------------------- +// Select the object, parents and children. +//----------------------------------------------------------------------------- +LLObjectSelectionHandle LLSelectMgr::selectObjectAndFamily(const std::vector& object_list, + bool send_to_sim) +{ + // Collect all of the objects, children included + std::vector objects; + + //clear primary object (no primary object) + mSelectedObjects->mPrimaryObject = NULL; + + if (object_list.size() < 1) + { + return NULL; + } + + // NOTE -- we add the objects in REVERSE ORDER + // to preserve the order in the mSelectedObjects list + for (std::vector::const_reverse_iterator riter = object_list.rbegin(); + riter != object_list.rend(); ++riter) + { + LLViewerObject *object = *riter; + + llassert( object ); + + if (!canSelectObject(object)) continue; + + object->addThisAndNonJointChildren(objects); + addAsFamily(objects); + + // Stop the object from moving (this anticipates changes on the + // simulator in LLTask::userSelect) + object->setVelocity(LLVector3::zero); + object->setAcceleration(LLVector3::zero); + //object->setAngularVelocity(LLVector3::zero); + object->resetRot(); + } + + updateSelectionCenter(); + saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); + updatePointAt(); + dialog_refresh_all(); + + // Almost always send to simulator, so you get a copy of the permissions + // structure back. + // JC: The one case where you don't want to do this is if you're selecting + // all the objects on a sim. + if (send_to_sim) + { + sendSelect(); + } + + // leave component mode + if (gSavedSettings.getBOOL("EditLinkedParts")) + { + gSavedSettings.setBOOL("EditLinkedParts", false); + promoteSelectionToRoot(); + } + + // have selection manager handle edit menu immediately after + // user selects an object + if (mSelectedObjects->getObjectCount()) + { + gEditMenuHandler = this; + } + + return mSelectedObjects; +} + +// Use for when the simulator kills an object. This version also +// handles informing the current tool of the object's deletion. +// +// Caller needs to call dialog_refresh_all if necessary. +bool LLSelectMgr::removeObjectFromSelections(const LLUUID &id) +{ + bool object_found = false; + LLTool *tool = NULL; + + tool = LLToolMgr::getInstance()->getCurrentTool(); + + // It's possible that the tool is editing an object that is not selected + LLViewerObject* tool_editing_object = tool->getEditingObject(); + if( tool_editing_object && tool_editing_object->mID == id) + { + tool->stopEditing(); + object_found = true; + } + + // Iterate through selected objects list and kill the object + if( !object_found ) + { + for (LLObjectSelection::iterator iter = getSelection()->begin(); + iter != getSelection()->end(); ) + { + LLObjectSelection::iterator curiter = iter++; + LLViewerObject* object = (*curiter)->getObject(); + if (object->mID == id) + { + if (tool) + { + tool->stopEditing(); + } + + // lose the selection, don't tell simulator, it knows + deselectObjectAndFamily(object, false); + object_found = true; + break; // must break here, may have removed multiple objects from list + } + else if (object->isAvatar() && object->getParent() && ((LLViewerObject*)object->getParent())->mID == id) + { + // It's possible the item being removed has an avatar sitting on it + // So remove the avatar that is sitting on the object. + deselectObjectAndFamily(object, false); + break; // must break here, may have removed multiple objects from list + } + } + } + + return object_found; +} + +bool LLSelectMgr::linkObjects() +{ + if (!LLSelectMgr::getInstance()->selectGetAllRootsValid()) + { + LLNotificationsUtil::add("UnableToLinkWhileDownloading"); + return true; + } + + S32 object_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount(); + if (object_count > MAX_CHILDREN_PER_TASK + 1) + { + LLSD args; + args["COUNT"] = llformat("%d", object_count); + int max = MAX_CHILDREN_PER_TASK+1; + args["MAX"] = llformat("%d", max); + LLNotificationsUtil::add("UnableToLinkObjects", args); + return true; + } + + if (LLSelectMgr::getInstance()->getSelection()->getRootObjectCount() < 2) + { + LLNotificationsUtil::add("CannotLinkIncompleteSet"); + return true; + } + + if (!LLSelectMgr::getInstance()->selectGetRootsModify()) + { + LLNotificationsUtil::add("CannotLinkModify"); + return true; + } + + if (!LLSelectMgr::getInstance()->selectGetRootsNonPermanentEnforced()) + { + LLNotificationsUtil::add("CannotLinkPermanent"); + return true; + } + + LLUUID owner_id; + std::string owner_name; + if (!LLSelectMgr::getInstance()->selectGetOwner(owner_id, owner_name)) + { + // we don't actually care if you're the owner, but novices are + // the most likely to be stumped by this one, so offer the + // easiest and most likely solution. + LLNotificationsUtil::add("CannotLinkDifferentOwners"); + return true; + } + + if (!LLSelectMgr::getInstance()->selectGetSameRegion()) + { + LLNotificationsUtil::add("CannotLinkAcrossRegions"); + return true; + } + + LLSelectMgr::getInstance()->sendLink(); + + return true; +} + +bool LLSelectMgr::unlinkObjects() +{ + S32 min_objects_for_confirm = gSavedSettings.getS32("MinObjectsForUnlinkConfirm"); + S32 unlink_object_count = mSelectedObjects->getObjectCount(); // clears out nodes with NULL objects + if (unlink_object_count >= min_objects_for_confirm + && unlink_object_count > mSelectedObjects->getRootObjectCount()) + { + // total count > root count means that there are childer inside and that there are linksets that will be unlinked + LLNotificationsUtil::add("ConfirmUnlink", LLSD(), LLSD(), boost::bind(&LLSelectMgr::confirmUnlinkObjects, this, _1, _2)); + return true; + } + + LLSelectMgr::getInstance()->sendDelink(); + return true; +} + +void LLSelectMgr::confirmUnlinkObjects(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + // if Cancel pressed + if (option == 1) + { + return; + } + + LLSelectMgr::getInstance()->sendDelink(); + return; +} + +// in order to link, all objects must have the same owner, and the +// agent must have the ability to modify all of the objects. However, +// we're not answering that question with this method. The question +// we're answering is: does the user have a reasonable expectation +// that a link operation should work? If so, return true, false +// otherwise. this allows the handle_link method to more finely check +// the selection and give an error message when the uer has a +// reasonable expectation for the link to work, but it will fail. +// +// For animated objects, there's additional check that if the +// selection includes at least one animated object, the total mesh +// triangle count cannot exceed the designated limit. +bool LLSelectMgr::enableLinkObjects() +{ + bool new_value = false; + // check if there are at least 2 objects selected, and that the + // user can modify at least one of the selected objects. + + // in component mode, can't link + if (!gSavedSettings.getBOOL("EditLinkedParts")) + { + if(LLSelectMgr::getInstance()->selectGetAllRootsValid() && LLSelectMgr::getInstance()->getSelection()->getRootObjectCount() >= 2) + { + struct f : public LLSelectedObjectFunctor + { + virtual bool apply(LLViewerObject* object) + { + LLViewerObject *root_object = (object == NULL) ? NULL : object->getRootEdit(); + return object->permModify() && !object->isPermanentEnforced() && + ((root_object == NULL) || !root_object->isPermanentEnforced()); + } + } func; + const bool firstonly = true; + new_value = LLSelectMgr::getInstance()->getSelection()->applyToRootObjects(&func, firstonly); + } + } + if (!LLSelectMgr::getInstance()->getSelection()->checkAnimatedObjectLinkable()) + { + new_value = false; + } + return new_value; +} + +bool LLSelectMgr::enableUnlinkObjects() +{ + LLViewerObject* first_editable_object = LLSelectMgr::getInstance()->getSelection()->getFirstEditableObject(); + LLViewerObject *root_object = (first_editable_object == NULL) ? NULL : first_editable_object->getRootEdit(); + + bool new_value = LLSelectMgr::getInstance()->selectGetAllRootsValid() && + first_editable_object && + !first_editable_object->isAttachment() && !first_editable_object->isPermanentEnforced() && + ((root_object == NULL) || !root_object->isPermanentEnforced()); + + return new_value; +} + +void LLSelectMgr::deselectObjectAndFamily(LLViewerObject* object, bool send_to_sim, bool include_entire_object) +{ + // bail if nothing selected or if object wasn't selected in the first place + if(!object) return; + if(!object->isSelected()) return; + + // Collect all of the objects, and remove them + std::vector objects; + + if (include_entire_object) + { + // Since we're selecting a family, start at the root, but + // don't include an avatar. + LLViewerObject* root = object; + + while(!root->isAvatar() && root->getParent()) + { + LLViewerObject* parent = (LLViewerObject*)root->getParent(); + if (parent->isAvatar()) + { + break; + } + root = parent; + } + + object = root; + } + else + { + object = (LLViewerObject*)object->getRoot(); + } + + object->addThisAndAllChildren(objects); + remove(objects); + + if (!send_to_sim) return; + + //----------------------------------------------------------- + // Inform simulator of deselection + //----------------------------------------------------------- + LLViewerRegion* regionp = object->getRegion(); + + bool start_new_message = true; + S32 select_count = 0; + + LLMessageSystem* msg = gMessageSystem; + for (U32 i = 0; i < objects.size(); i++) + { + if (start_new_message) + { + msg->newMessageFast(_PREHASH_ObjectDeselect); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + select_count++; + start_new_message = false; + } + + msg->nextBlockFast(_PREHASH_ObjectData); + msg->addU32Fast(_PREHASH_ObjectLocalID, (objects[i])->getLocalID()); + select_count++; + + // Zap the angular velocity, as the sim will set it to zero + objects[i]->setAngularVelocity( 0,0,0 ); + objects[i]->setVelocity( 0,0,0 ); + + if(msg->isSendFull(NULL) || select_count >= MAX_OBJECTS_PER_PACKET) + { + msg->sendReliable(regionp->getHost() ); + select_count = 0; + start_new_message = true; + } + } + + if (!start_new_message) + { + msg->sendReliable(regionp->getHost() ); + } + + updatePointAt(); + updateSelectionCenter(); +} + +void LLSelectMgr::deselectObjectOnly(LLViewerObject* object, bool send_to_sim) +{ + // bail if nothing selected or if object wasn't selected in the first place + if (!object) return; + if (!object->isSelected() ) return; + + // Zap the angular velocity, as the sim will set it to zero + object->setAngularVelocity( 0,0,0 ); + object->setVelocity( 0,0,0 ); + + if (send_to_sim) + { + LLViewerRegion* region = object->getRegion(); + gMessageSystem->newMessageFast(_PREHASH_ObjectDeselect); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + gMessageSystem->nextBlockFast(_PREHASH_ObjectData); + gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, object->getLocalID() ); + gMessageSystem->sendReliable(region->getHost()); + } + + // This will refresh dialogs. + remove( object ); + + updatePointAt(); + updateSelectionCenter(); +} + + +//----------------------------------------------------------------------------- +// addAsFamily +//----------------------------------------------------------------------------- + +void LLSelectMgr::addAsFamily(std::vector& objects, bool add_to_end) +{ + for (std::vector::iterator iter = objects.begin(); + iter != objects.end(); ++iter) + { + LLViewerObject* objectp = *iter; + + // Can't select yourself + if (objectp->mID == gAgentID + && !mAllowSelectAvatar) + { + continue; + } + + if (!objectp->isSelected()) + { + LLSelectNode *nodep = new LLSelectNode(objectp, true); + if (add_to_end) + { + mSelectedObjects->addNodeAtEnd(nodep); + } + else + { + mSelectedObjects->addNode(nodep); + } + objectp->setSelected(true); + + if (objectp->getNumTEs() > 0) + { + nodep->selectAllTEs(true); + objectp->setAllTESelected(true); + } + else + { + // object has no faces, so don't mess with faces + } + } + else + { + // we want this object to be selected for real + // so clear transient flag + LLSelectNode* select_node = mSelectedObjects->findNode(objectp); + if (select_node) + { + select_node->setTransient(false); + } + } + } + saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); +} + +//----------------------------------------------------------------------------- +// addAsIndividual() - a single object, face, etc +//----------------------------------------------------------------------------- +void LLSelectMgr::addAsIndividual(LLViewerObject *objectp, S32 face, bool undoable) +{ + // check to see if object is already in list + LLSelectNode *nodep = mSelectedObjects->findNode(objectp); + + // if not in list, add it + if (!nodep) + { + nodep = new LLSelectNode(objectp, true); + mSelectedObjects->addNode(nodep); + llassert_always(nodep->getObject()); + } + else + { + // make this a full-fledged selection + nodep->setTransient(false); + // Move it to the front of the list + mSelectedObjects->moveNodeToFront(nodep); + } + + // Make sure the object is tagged as selected + objectp->setSelected( true ); + + // And make sure we don't consider it as part of a family + nodep->mIndividualSelection = true; + + // Handle face selection + if (objectp->getNumTEs() <= 0) + { + // object has no faces, so don't do anything + } + else if (face == SELECT_ALL_TES) + { + nodep->selectAllTEs(true); + objectp->setAllTESelected(true); + } + else if (0 <= face && face < SELECT_MAX_TES) + { + nodep->selectTE(face, true); + objectp->setTESelected(face, true); + } + else + { + LL_ERRS() << "LLSelectMgr::add face " << face << " out-of-range" << LL_ENDL; + return; + } + + saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); + updateSelectionCenter(); + dialog_refresh_all(); +} + + +LLObjectSelectionHandle LLSelectMgr::setHoverObject(LLViewerObject *objectp, S32 face) +{ + if (!objectp) + { + mHoverObjects->deleteAllNodes(); + return NULL; + } + + // Can't select yourself + if (objectp->mID == gAgentID) + { + mHoverObjects->deleteAllNodes(); + return NULL; + } + + // Can't select land + if (objectp->getPCode() == LLViewerObject::LL_VO_SURFACE_PATCH) + { + mHoverObjects->deleteAllNodes(); + return NULL; + } + + mHoverObjects->mPrimaryObject = objectp; + + objectp = objectp->getRootEdit(); + + // is the requested object the same as the existing hover object root? + // NOTE: there is only ever one linked set in mHoverObjects + if (mHoverObjects->getFirstRootObject() != objectp) + { + + // Collect all of the objects + std::vector objects; + objectp = objectp->getRootEdit(); + objectp->addThisAndNonJointChildren(objects); + + mHoverObjects->deleteAllNodes(); + for (std::vector::iterator iter = objects.begin(); + iter != objects.end(); ++iter) + { + LLViewerObject* cur_objectp = *iter; + if(!cur_objectp || cur_objectp->isDead()) + { + continue; + } + LLSelectNode* nodep = new LLSelectNode(cur_objectp, false); + nodep->selectTE(face, true); + mHoverObjects->addNodeAtEnd(nodep); + } + + requestObjectPropertiesFamily(objectp); + } + + return mHoverObjects; +} + +LLSelectNode *LLSelectMgr::getHoverNode() +{ + return mHoverObjects->getFirstRootNode(); +} + +LLSelectNode *LLSelectMgr::getPrimaryHoverNode() +{ + return mHoverObjects->mSelectNodeMap[mHoverObjects->mPrimaryObject]; +} + +void LLSelectMgr::highlightObjectOnly(LLViewerObject* objectp) +{ + if (!objectp) + { + return; + } + + if (objectp->getPCode() != LL_PCODE_VOLUME) + { + return; + } + + if ((gSavedSettings.getBOOL("SelectOwnedOnly") && !objectp->permYouOwner()) + || (gSavedSettings.getBOOL("SelectMovableOnly") && (!objectp->permMove() || objectp->isPermanentEnforced()))) + { + // only select my own objects + return; + } + + mRectSelectedObjects.insert(objectp); +} + +void LLSelectMgr::highlightObjectAndFamily(LLViewerObject* objectp) +{ + if (!objectp) + { + return; + } + + LLViewerObject* root_obj = (LLViewerObject*)objectp->getRoot(); + + highlightObjectOnly(root_obj); + + LLViewerObject::const_child_list_t& child_list = root_obj->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); iter++) + { + LLViewerObject* child = *iter; + highlightObjectOnly(child); + } +} + +// Note that this ignores the "select owned only" flag +// It's also more efficient than calling the single-object version over and over. +void LLSelectMgr::highlightObjectAndFamily(const std::vector& objects) +{ + for (std::vector::const_iterator iter1 = objects.begin(); + iter1 != objects.end(); ++iter1) + { + LLViewerObject* object = *iter1; + + if (!object) + { + continue; + } + if (object->getPCode() != LL_PCODE_VOLUME) + { + continue; + } + + LLViewerObject* root = (LLViewerObject*)object->getRoot(); + mRectSelectedObjects.insert(root); + + LLViewerObject::const_child_list_t& child_list = root->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter2 = child_list.begin(); + iter2 != child_list.end(); iter2++) + { + LLViewerObject* child = *iter2; + mRectSelectedObjects.insert(child); + } + } +} + +void LLSelectMgr::unhighlightObjectOnly(LLViewerObject* objectp) +{ + if (!objectp || (objectp->getPCode() != LL_PCODE_VOLUME)) + { + return; + } + + mRectSelectedObjects.erase(objectp); +} + +void LLSelectMgr::unhighlightObjectAndFamily(LLViewerObject* objectp) +{ + if (!objectp) + { + return; + } + + LLViewerObject* root_obj = (LLViewerObject*)objectp->getRoot(); + + unhighlightObjectOnly(root_obj); + + LLViewerObject::const_child_list_t& child_list = root_obj->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); iter++) + { + LLViewerObject* child = *iter; + unhighlightObjectOnly(child); + } +} + + +void LLSelectMgr::unhighlightAll() +{ + mRectSelectedObjects.clear(); + mHighlightedObjects->deleteAllNodes(); +} + +LLObjectSelectionHandle LLSelectMgr::selectHighlightedObjects() +{ + if (!mHighlightedObjects->getNumNodes()) + { + return NULL; + } + + //clear primary object + mSelectedObjects->mPrimaryObject = NULL; + + for (LLObjectSelection::iterator iter = getHighlightedObjects()->begin(); + iter != getHighlightedObjects()->end(); ) + { + LLObjectSelection::iterator curiter = iter++; + + LLSelectNode *nodep = *curiter; + LLViewerObject* objectp = nodep->getObject(); + + if (!canSelectObject(objectp)) + { + continue; + } + + // already selected + if (objectp->isSelected()) + { + continue; + } + + LLSelectNode* new_nodep = new LLSelectNode(*nodep); + mSelectedObjects->addNode(new_nodep); + + // flag this object as selected + objectp->setSelected(true); + objectp->setAllTESelected(true); + + mSelectedObjects->mSelectType = getSelectTypeForObject(objectp); + + // request properties on root objects + if (objectp->isRootEdit()) + { + requestObjectPropertiesFamily(objectp); + } + } + + // pack up messages to let sim know these objects are selected + sendSelect(); + unhighlightAll(); + updateSelectionCenter(); + saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); + updatePointAt(); + + if (mSelectedObjects->getObjectCount()) + { + gEditMenuHandler = this; + } + + return mSelectedObjects; +} + +void LLSelectMgr::deselectHighlightedObjects() +{ + bool select_linked_set = !gSavedSettings.getBOOL("EditLinkedParts"); + for (std::set >::iterator iter = mRectSelectedObjects.begin(); + iter != mRectSelectedObjects.end(); iter++) + { + LLViewerObject *objectp = *iter; + if (!select_linked_set) + { + deselectObjectOnly(objectp); + } + else + { + LLViewerObject* root_object = (LLViewerObject*)objectp->getRoot(); + if (root_object->isSelected()) + { + deselectObjectAndFamily(root_object); + } + } + } + + unhighlightAll(); +} + +void LLSelectMgr::addGridObject(LLViewerObject* objectp) +{ + LLSelectNode* nodep = new LLSelectNode(objectp, false); + mGridObjects.addNodeAtEnd(nodep); + + LLViewerObject::const_child_list_t& child_list = objectp->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); iter++) + { + LLViewerObject* child = *iter; + nodep = new LLSelectNode(child, false); + mGridObjects.addNodeAtEnd(nodep); + } +} + +void LLSelectMgr::clearGridObjects() +{ + mGridObjects.deleteAllNodes(); +} + +void LLSelectMgr::setGridMode(EGridMode mode) +{ + mGridMode = mode; + gSavedSettings.setS32("GridMode", mode); + updateSelectionCenter(); +} + +void LLSelectMgr::getGrid(LLVector3& origin, LLQuaternion &rotation, LLVector3 &scale, bool for_snap_guides) +{ + mGridObjects.cleanupNodes(); + + LLViewerObject* first_grid_object = mGridObjects.getFirstObject(); + + if (mGridMode == GRID_MODE_LOCAL && mSelectedObjects->getObjectCount()) + { + //LLViewerObject* root = getSelectedParentObject(mSelectedObjects->getFirstObject()); + mGridOrigin = mSavedSelectionBBox.getCenterAgent(); + mGridScale = mSavedSelectionBBox.getExtentLocal() * 0.5f; + + // DEV-12570 Just taking the saved selection box rotation prevents + // wild rotations of linked sets while in local grid mode + //if(mSelectedObjects->getObjectCount() < 2 || !root || root->mDrawable.isNull()) + { + mGridRotation = mSavedSelectionBBox.getRotation(); + } + /*else //set to the root object + { + mGridRotation = root->getRenderRotation(); + }*/ + } + else if (mGridMode == GRID_MODE_REF_OBJECT && first_grid_object && first_grid_object->mDrawable.notNull()) + { + LLSelectNode *node = mSelectedObjects->findNode(first_grid_object); + if (!for_snap_guides && node) + { + mGridRotation = node->mSavedRotation; + } + else + { + mGridRotation = first_grid_object->getRenderRotation(); + } + + LLVector4a min_extents(F32_MAX); + LLVector4a max_extents(-F32_MAX); + bool grid_changed = false; + for (LLObjectSelection::iterator iter = mGridObjects.begin(); + iter != mGridObjects.end(); ++iter) + { + LLViewerObject* object = (*iter)->getObject(); + LLDrawable* drawable = object->mDrawable; + if (drawable) + { + const LLVector4a* ext = drawable->getSpatialExtents(); + update_min_max(min_extents, max_extents, ext[0]); + update_min_max(min_extents, max_extents, ext[1]); + grid_changed = true; + } + } + if (grid_changed) + { + LLVector4a center, size; + center.setAdd(min_extents, max_extents); + center.mul(0.5f); + size.setSub(max_extents, min_extents); + size.mul(0.5f); + + mGridOrigin.set(center.getF32ptr()); + LLDrawable* drawable = first_grid_object->mDrawable; + if (drawable && drawable->isActive()) + { + mGridOrigin = mGridOrigin * first_grid_object->getRenderMatrix(); + } + mGridScale.set(size.getF32ptr()); + } + } + else // GRID_MODE_WORLD or just plain default + { + const bool non_root_ok = true; + LLViewerObject* first_object = mSelectedObjects->getFirstRootObject(non_root_ok); + + mGridOrigin.clearVec(); + mGridRotation.loadIdentity(); + + mSelectedObjects->mSelectType = getSelectTypeForObject( first_object ); + + switch (mSelectedObjects->mSelectType) + { + case SELECT_TYPE_ATTACHMENT: + if (first_object && first_object->getRootEdit()->mDrawable.notNull()) + { + // this means this object *has* to be an attachment + LLXform* attachment_point_xform = first_object->getRootEdit()->mDrawable->mXform.getParent(); + mGridOrigin = attachment_point_xform->getWorldPosition(); + mGridRotation = attachment_point_xform->getWorldRotation(); + mGridScale = LLVector3(1.f, 1.f, 1.f) * gSavedSettings.getF32("GridResolution"); + } + break; + case SELECT_TYPE_HUD: + mGridScale = LLVector3(1.f, 1.f, 1.f) * llmin(gSavedSettings.getF32("GridResolution"), 0.5f); + break; + case SELECT_TYPE_WORLD: + mGridScale = LLVector3(1.f, 1.f, 1.f) * gSavedSettings.getF32("GridResolution"); + break; + } + } + llassert(mGridOrigin.isFinite()); + + origin = mGridOrigin; + rotation = mGridRotation; + scale = mGridScale; +} + +//----------------------------------------------------------------------------- +// remove() - an array of objects +//----------------------------------------------------------------------------- + +void LLSelectMgr::remove(std::vector& objects) +{ + for (std::vector::iterator iter = objects.begin(); + iter != objects.end(); ++iter) + { + LLViewerObject* objectp = *iter; + LLSelectNode* nodep = mSelectedObjects->findNode(objectp); + if (nodep) + { + objectp->setSelected(false); + mSelectedObjects->removeNode(nodep); + nodep = NULL; + } + } + updateSelectionCenter(); + dialog_refresh_all(); +} + + +//----------------------------------------------------------------------------- +// remove() - a single object +//----------------------------------------------------------------------------- +void LLSelectMgr::remove(LLViewerObject *objectp, S32 te, bool undoable) +{ + // get object node (and verify it is in the selected list) + LLSelectNode *nodep = mSelectedObjects->findNode(objectp); + if (!nodep) + { + return; + } + + // if face = all, remove object from list + if ((objectp->getNumTEs() <= 0) || (te == SELECT_ALL_TES)) + { + // Remove all faces (or the object doesn't have faces) so remove the node + mSelectedObjects->removeNode(nodep); + nodep = NULL; + objectp->setSelected( false ); + } + else if (0 <= te && te < SELECT_MAX_TES) + { + // ...valid face, check to see if it was on + if (nodep->isTESelected(te)) + { + nodep->selectTE(te, false); + objectp->setTESelected(te, false); + } + else + { + LL_ERRS() << "LLSelectMgr::remove - tried to remove TE " << te << " that wasn't selected" << LL_ENDL; + return; + } + + // ...check to see if this operation turned off all faces + bool found = false; + for (S32 i = 0; i < nodep->getObject()->getNumTEs(); i++) + { + found = found || nodep->isTESelected(i); + } + + // ...all faces now turned off, so remove + if (!found) + { + mSelectedObjects->removeNode(nodep); + nodep = NULL; + objectp->setSelected( false ); + // *FIXME: Doesn't update simulator that object is no longer selected + } + } + else + { + // ...out of range face + LL_ERRS() << "LLSelectMgr::remove - TE " << te << " out of range" << LL_ENDL; + } + + updateSelectionCenter(); + dialog_refresh_all(); +} + + +//----------------------------------------------------------------------------- +// removeAll() +//----------------------------------------------------------------------------- +void LLSelectMgr::removeAll() +{ + for (LLObjectSelection::iterator iter = mSelectedObjects->begin(); + iter != mSelectedObjects->end(); iter++ ) + { + LLViewerObject *objectp = (*iter)->getObject(); + objectp->setSelected( false ); + } + + mSelectedObjects->deleteAllNodes(); + + updateSelectionCenter(); + dialog_refresh_all(); +} + +//----------------------------------------------------------------------------- +// promoteSelectionToRoot() +//----------------------------------------------------------------------------- +void LLSelectMgr::promoteSelectionToRoot() +{ + std::set selection_set; + + bool selection_changed = false; + + for (LLObjectSelection::iterator iter = getSelection()->begin(); + iter != getSelection()->end(); ) + { + LLObjectSelection::iterator curiter = iter++; + LLSelectNode* nodep = *curiter; + LLViewerObject* object = nodep->getObject(); + + if (nodep->mIndividualSelection) + { + selection_changed = true; + } + + LLViewerObject* parentp = object; + while(parentp->getParent() && !(parentp->isRootEdit())) + { + parentp = (LLViewerObject*)parentp->getParent(); + } + + selection_set.insert(parentp); + } + + if (selection_changed) + { + deselectAll(); + + std::set::iterator set_iter; + for (set_iter = selection_set.begin(); set_iter != selection_set.end(); ++set_iter) + { + selectObjectAndFamily(*set_iter); + } + } +} + +//----------------------------------------------------------------------------- +// demoteSelectionToIndividuals() +//----------------------------------------------------------------------------- +void LLSelectMgr::demoteSelectionToIndividuals() +{ + std::vector objects; + + for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); + iter != getSelection()->root_end(); iter++) + { + LLViewerObject* object = (*iter)->getObject(); + object->addThisAndNonJointChildren(objects); + } + + if (!objects.empty()) + { + deselectAll(); + for (std::vector::iterator iter = objects.begin(); + iter != objects.end(); ++iter) + { + LLViewerObject* objectp = *iter; + selectObjectOnly(objectp); + } + } +} + +//----------------------------------------------------------------------------- +// dump() +//----------------------------------------------------------------------------- +void LLSelectMgr::dump() +{ + LL_INFOS() << "Selection Manager: " << mSelectedObjects->getNumNodes() << " items" << LL_ENDL; + + LL_INFOS() << "TE mode " << mTEMode << LL_ENDL; + + S32 count = 0; + for (LLObjectSelection::iterator iter = getSelection()->begin(); + iter != getSelection()->end(); iter++ ) + { + LLViewerObject* objectp = (*iter)->getObject(); + LL_INFOS() << "Object " << count << " type " << LLPrimitive::pCodeToString(objectp->getPCode()) << LL_ENDL; + LL_INFOS() << " hasLSL " << objectp->flagScripted() << LL_ENDL; + LL_INFOS() << " hasTouch " << objectp->flagHandleTouch() << LL_ENDL; + LL_INFOS() << " hasMoney " << objectp->flagTakesMoney() << LL_ENDL; + LL_INFOS() << " getposition " << objectp->getPosition() << LL_ENDL; + LL_INFOS() << " getpositionAgent " << objectp->getPositionAgent() << LL_ENDL; + LL_INFOS() << " getpositionRegion " << objectp->getPositionRegion() << LL_ENDL; + LL_INFOS() << " getpositionGlobal " << objectp->getPositionGlobal() << LL_ENDL; + LLDrawable* drawablep = objectp->mDrawable; + LL_INFOS() << " " << (drawablep&& drawablep->isVisible() ? "visible" : "invisible") << LL_ENDL; + LL_INFOS() << " " << (drawablep&& drawablep->isState(LLDrawable::FORCE_INVISIBLE) ? "force_invisible" : "") << LL_ENDL; + count++; + } + + // Face iterator + for (LLObjectSelection::iterator iter = getSelection()->begin(); + iter != getSelection()->end(); iter++ ) + { + LLSelectNode* node = *iter; + LLViewerObject* objectp = node->getObject(); + if (!objectp) + continue; + for (S32 te = 0; te < objectp->getNumTEs(); ++te ) + { + if (node->isTESelected(te)) + { + LL_INFOS() << "Object " << objectp << " te " << te << LL_ENDL; + } + } + } + + LL_INFOS() << mHighlightedObjects->getNumNodes() << " objects currently highlighted." << LL_ENDL; + + LL_INFOS() << "Center global " << mSelectionCenterGlobal << LL_ENDL; +} + +//----------------------------------------------------------------------------- +// cleanup() +//----------------------------------------------------------------------------- +void LLSelectMgr::cleanup() +{ + mSilhouetteImagep = NULL; +} + + +//--------------------------------------------------------------------------- +// Manipulate properties of selected objects +//--------------------------------------------------------------------------- + +struct LLSelectMgrSendFunctor : public LLSelectedObjectFunctor +{ + virtual bool apply(LLViewerObject* object) + { + if (object->permModify()) + { + object->sendTEUpdate(); + } + return true; + } +}; + +void LLObjectSelection::applyNoCopyTextureToTEs(LLViewerInventoryItem* item) +{ + if (!item) + { + return; + } + LLViewerTexture* image = LLViewerTextureManager::getFetchedTexture(item->getAssetUUID()); + + for (iterator iter = begin(); iter != end(); ++iter) + { + LLSelectNode* node = *iter; + LLViewerObject* object = (*iter)->getObject(); + if (!object) + { + continue; + } + + S32 num_tes = llmin((S32)object->getNumTEs(), (S32)object->getNumFaces()); + bool texture_copied = false; + bool updated = false; + for (S32 te = 0; te < num_tes; ++te) + { + if (node->isTESelected(te)) + { + //(no-copy) textures must be moved to the object's inventory only once + // without making any copies + if (!texture_copied) + { + LLToolDragAndDrop::handleDropMaterialProtections(object, item, LLToolDragAndDrop::SOURCE_AGENT, LLUUID::null); + texture_copied = true; + } + + // apply texture for the selected faces + add(LLStatViewer::EDIT_TEXTURE, 1); + object->setTEImage(te, image); + updated = true; + } + } + + if (updated) // not nessesary? sendTEUpdate update supposed to be done by sendfunc + { + dialog_refresh_all(); + + // send the update to the simulator + object->sendTEUpdate(); + } + } +} + +bool LLObjectSelection::applyRestrictedPbrMaterialToTEs(LLViewerInventoryItem* item) +{ + if (!item) + { + return false; + } + + LLUUID asset_id = item->getAssetUUID(); + if (asset_id.isNull()) + { + asset_id = LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID; + } + + bool material_copied_all_faces = true; + + for (iterator iter = begin(); iter != end(); ++iter) + { + LLSelectNode* node = *iter; + LLViewerObject* object = (*iter)->getObject(); + if (!object) + { + continue; + } + + S32 num_tes = llmin((S32)object->getNumTEs(), (S32)object->getNumFaces()); + bool material_copied = false; + for (S32 te = 0; te < num_tes; ++te) + { + if (node->isTESelected(te)) + { + //(no-copy), (no-modify), and (no-transfer) materials must be moved to the object's inventory only once + // without making any copies + if (!material_copied && asset_id.notNull()) + { + material_copied = (bool)LLToolDragAndDrop::handleDropMaterialProtections(object, item, LLToolDragAndDrop::SOURCE_AGENT, LLUUID::null); + } + if (!material_copied) + { + // Applying the material is not possible for this object given the current inventory + material_copied_all_faces = false; + break; + } + + // apply texture for the selected faces + // blank out most override data on the server + //add(LLStatViewer::EDIT_TEXTURE, 1); + object->setRenderMaterialID(te, asset_id); + } + } + } + + LLGLTFMaterialList::flushUpdates(); + + return material_copied_all_faces; +} + + +//----------------------------------------------------------------------------- +// selectionSetImage() +//----------------------------------------------------------------------------- +// *TODO: re-arch texture applying out of lltooldraganddrop +bool LLSelectMgr::selectionSetImage(const LLUUID& imageid) +{ + // First for (no copy) textures and multiple object selection + LLViewerInventoryItem* item = gInventory.getItem(imageid); + if(item + && !item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID()) + && (mSelectedObjects->getNumNodes() > 1) ) + { + LL_DEBUGS() << "Attempted to apply no-copy texture " << imageid + << " to multiple objects" << LL_ENDL; + + LLNotificationsUtil::add("FailedToApplyTextureNoCopyToMultiple"); + return false; + } + + struct f : public LLSelectedTEFunctor + { + LLViewerInventoryItem* mItem; + LLUUID mImageID; + f(LLViewerInventoryItem* item, const LLUUID& id) : mItem(item), mImageID(id) {} + bool apply(LLViewerObject* objectp, S32 te) + { + if(!objectp || !objectp->permModify()) + { + return false; + } + + // Might be better to run willObjectAcceptInventory + if (mItem && objectp->isAttachment()) + { + const LLPermissions& perm = mItem->getPermissions(); + bool unrestricted = (perm.getMaskBase() & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED; + if (!unrestricted) + { + // Attachments are in world and in inventory simultaneously, + // at the moment server doesn't support such a situation. + return false; + } + } + + if (mItem) + { + LLToolDragAndDrop::dropTextureOneFace(objectp, + te, + mItem, + LLToolDragAndDrop::SOURCE_AGENT, + LLUUID::null, + false); + } + else // not an inventory item + { + // Texture picker defaults aren't inventory items + // * Don't need to worry about permissions for them + // * Can just apply the texture and be done with it. + objectp->setTEImage(te, LLViewerTextureManager::getFetchedTexture(mImageID, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE)); + } + + return true; + } + }; + + if (item && !item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID())) + { + getSelection()->applyNoCopyTextureToTEs(item); + } + else + { + f setfunc(item, imageid); + getSelection()->applyToTEs(&setfunc); + } + + + struct g : public LLSelectedObjectFunctor + { + LLViewerInventoryItem* mItem; + g(LLViewerInventoryItem* item) : mItem(item) {} + virtual bool apply(LLViewerObject* object) + { + if (!mItem) + { + object->sendTEUpdate(); + // 1 particle effect per object + LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM, true); + effectp->setSourceObject(gAgentAvatarp); + effectp->setTargetObject(object); + effectp->setDuration(LL_HUD_DUR_SHORT); + effectp->setColor(LLColor4U(gAgent.getEffectColor())); + } + return true; + } + } sendfunc(item); + getSelection()->applyToObjects(&sendfunc); + + return true; +} + +//----------------------------------------------------------------------------- +// selectionSetGLTFMaterial() +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectionSetGLTFMaterial(const LLUUID& mat_id) +{ + // First for (no copy) textures and multiple object selection + LLViewerInventoryItem* item = gInventory.getItem(mat_id); + if (item + && !item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID()) + && (mSelectedObjects->getNumNodes() > 1)) + { + LL_DEBUGS() << "Attempted to apply no-copy material " << mat_id + << "to multiple objects" << LL_ENDL; + + LLNotificationsUtil::add("FailedToApplyGLTFNoCopyToMultiple"); + return false; + } + + struct f : public LLSelectedTEFunctor + { + LLViewerInventoryItem* mItem; + LLUUID mMatId; + bool material_copied_any_face = false; + bool material_copied_all_faces = true; + f(LLViewerInventoryItem* item, const LLUUID& id) : mItem(item), mMatId(id) {} + bool apply(LLViewerObject* objectp, S32 te) + { + if (!objectp || !objectp->permModify()) + { + return false; + } + LLUUID asset_id = mMatId; + if (mItem) + { + const LLPermissions& perm = mItem->getPermissions(); + bool from_library = perm.getOwner() == ALEXANDRIA_LINDEN_ID; + if (objectp->isAttachment()) + { + bool unrestricted = (perm.getMaskBase() & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED; + + if (!unrestricted && !from_library) + { + // Attachments are in world and in inventory simultaneously, + // at the moment server doesn't support such a situation. + return false; + } + } + + if (!from_library + // Check if item may be copied into the object's inventory + && !LLToolDragAndDrop::handleDropMaterialProtections(objectp, mItem, LLToolDragAndDrop::SOURCE_AGENT, LLUUID::null)) + { + return false; + } + + asset_id = mItem->getAssetUUID(); + if (asset_id.isNull()) + { + asset_id = LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID; + } + } + + // Blank out most override data on the object and send to server + objectp->setRenderMaterialID(te, asset_id); + + return true; + } + }; + + bool success = true; + if (item + && (!item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID()) || + !item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID()) || + !item->getPermissions().allowOperationBy(PERM_MODIFY, gAgent.getID()) + ) + && item->getPermissions().getOwner() != ALEXANDRIA_LINDEN_ID + ) + { + success = success && getSelection()->applyRestrictedPbrMaterialToTEs(item); + } + else + { + f setfunc(item, mat_id); + success = success && getSelection()->applyToTEs(&setfunc); + } + + struct g : public LLSelectedObjectFunctor + { + LLViewerInventoryItem* mItem; + g(LLViewerInventoryItem* item) : mItem(item) {} + virtual bool apply(LLViewerObject* object) + { + if (object && !object->permModify()) + { + return false; + } + + if (!mItem) + { + // 1 particle effect per object + LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM, true); + effectp->setSourceObject(gAgentAvatarp); + effectp->setTargetObject(object); + effectp->setDuration(LL_HUD_DUR_SHORT); + effectp->setColor(LLColor4U(gAgent.getEffectColor())); + } + + dialog_refresh_all(); + object->sendTEUpdate(); + return true; + } + } sendfunc(item); + success = success && getSelection()->applyToObjects(&sendfunc); + + LLGLTFMaterialList::flushUpdates(); + + return success; +} + +//----------------------------------------------------------------------------- +// selectionSetColor() +//----------------------------------------------------------------------------- +void LLSelectMgr::selectionSetColor(const LLColor4 &color) +{ + struct f : public LLSelectedTEFunctor + { + LLColor4 mColor; + f(const LLColor4& c) : mColor(c) {} + bool apply(LLViewerObject* object, S32 te) + { + if (object->permModify()) + { + object->setTEColor(te, mColor); + } + return true; + } + } setfunc(color); + getSelection()->applyToTEs(&setfunc); + + LLSelectMgrSendFunctor sendfunc; + getSelection()->applyToObjects(&sendfunc); +} + +//----------------------------------------------------------------------------- +// selectionSetColorOnly() +//----------------------------------------------------------------------------- +void LLSelectMgr::selectionSetColorOnly(const LLColor4 &color) +{ + struct f : public LLSelectedTEFunctor + { + LLColor4 mColor; + f(const LLColor4& c) : mColor(c) {} + bool apply(LLViewerObject* object, S32 te) + { + if (object->permModify()) + { + LLColor4 prev_color = object->getTE(te)->getColor(); + mColor.mV[VALPHA] = prev_color.mV[VALPHA]; + // update viewer side color in anticipation of update from simulator + object->setTEColor(te, mColor); + } + return true; + } + } setfunc(color); + getSelection()->applyToTEs(&setfunc); + + LLSelectMgrSendFunctor sendfunc; + getSelection()->applyToObjects(&sendfunc); +} + +//----------------------------------------------------------------------------- +// selectionSetAlphaOnly() +//----------------------------------------------------------------------------- +void LLSelectMgr::selectionSetAlphaOnly(const F32 alpha) +{ + struct f : public LLSelectedTEFunctor + { + F32 mAlpha; + f(const F32& a) : mAlpha(a) {} + bool apply(LLViewerObject* object, S32 te) + { + if (object->permModify()) + { + LLColor4 prev_color = object->getTE(te)->getColor(); + prev_color.mV[VALPHA] = mAlpha; + // update viewer side color in anticipation of update from simulator + object->setTEColor(te, prev_color); + } + return true; + } + } setfunc(alpha); + getSelection()->applyToTEs(&setfunc); + + LLSelectMgrSendFunctor sendfunc; + getSelection()->applyToObjects(&sendfunc); +} + +void LLSelectMgr::selectionRevertColors() +{ + struct f : public LLSelectedTEFunctor + { + LLObjectSelectionHandle mSelectedObjects; + f(LLObjectSelectionHandle sel) : mSelectedObjects(sel) {} + bool apply(LLViewerObject* object, S32 te) + { + if (object->permModify()) + { + LLSelectNode* nodep = mSelectedObjects->findNode(object); + if (nodep && te < (S32)nodep->mSavedColors.size()) + { + LLColor4 color = nodep->mSavedColors[te]; + // update viewer side color in anticipation of update from simulator + object->setTEColor(te, color); + } + } + return true; + } + } setfunc(mSelectedObjects); + getSelection()->applyToTEs(&setfunc); + + LLSelectMgrSendFunctor sendfunc; + getSelection()->applyToObjects(&sendfunc); +} + +void LLSelectMgr::selectionRevertShinyColors() +{ + struct f : public LLSelectedTEFunctor + { + LLObjectSelectionHandle mSelectedObjects; + f(LLObjectSelectionHandle sel) : mSelectedObjects(sel) {} + bool apply(LLViewerObject* object, S32 te) + { + if (object->permModify()) + { + LLSelectNode* nodep = mSelectedObjects->findNode(object); + if (nodep && te < (S32)nodep->mSavedShinyColors.size()) + { + LLColor4 color = nodep->mSavedShinyColors[te]; + // update viewer side color in anticipation of update from simulator + LLMaterialPtr old_mat = object->getTE(te)->getMaterialParams(); + if (!old_mat.isNull()) + { + LLMaterialPtr new_mat = gFloaterTools->getPanelFace()->createDefaultMaterial(old_mat); + new_mat->setSpecularLightColor(color); + object->getTE(te)->setMaterialParams(new_mat); + LLMaterialMgr::getInstance()->put(object->getID(), te, *new_mat); + } + } + } + return true; + } + } setfunc(mSelectedObjects); + getSelection()->applyToTEs(&setfunc); + + LLSelectMgrSendFunctor sendfunc; + getSelection()->applyToObjects(&sendfunc); +} + +bool LLSelectMgr::selectionRevertTextures() +{ + struct f : public LLSelectedTEFunctor + { + LLObjectSelectionHandle mSelectedObjects; + f(LLObjectSelectionHandle sel) : mSelectedObjects(sel) {} + bool apply(LLViewerObject* object, S32 te) + { + if (object->permModify()) + { + LLSelectNode* nodep = mSelectedObjects->findNode(object); + if (nodep && te < (S32)nodep->mSavedTextures.size()) + { + LLUUID id = nodep->mSavedTextures[te]; + // update textures on viewer side + if (id.isNull()) + { + // this was probably a no-copy texture, leave image as-is + return false; + } + else + { + object->setTEImage(te, LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE)); + + } + } + } + return true; + } + } setfunc(mSelectedObjects); + bool revert_successful = getSelection()->applyToTEs(&setfunc); + + LLSelectMgrSendFunctor sendfunc; + getSelection()->applyToObjects(&sendfunc); + + return revert_successful; +} + +void LLSelectMgr::selectionRevertGLTFMaterials() +{ + struct f : public LLSelectedTEFunctor + { + LLObjectSelectionHandle mSelectedObjects; + f(LLObjectSelectionHandle sel) : mSelectedObjects(sel) {} + bool apply(LLViewerObject* objectp, S32 te) + { + if (objectp && !objectp->permModify()) + { + return false; + } + + LLSelectNode* nodep = mSelectedObjects->findNode(objectp); + if (nodep && te < (S32)nodep->mSavedGLTFMaterialIds.size()) + { + // Restore base material + LLUUID asset_id = nodep->mSavedGLTFMaterialIds[te]; + + // Update material locally + objectp->setRenderMaterialID(te, asset_id, false /*wait for LLGLTFMaterialList update*/); + objectp->setTEGLTFMaterialOverride(te, nodep->mSavedGLTFOverrideMaterials[te]); + + // Enqueue update to server + if (asset_id.notNull()) + { + // Restore overrides and base material + LLGLTFMaterialList::queueApply(objectp, te, asset_id, nodep->mSavedGLTFOverrideMaterials[te]); + } + else + { + //blank override out + LLGLTFMaterialList::queueApply(objectp, te, asset_id); + } + + } + return true; + } + } setfunc(mSelectedObjects); + getSelection()->applyToTEs(&setfunc); +} + +void LLSelectMgr::selectionSetBumpmap(U8 bumpmap, const LLUUID &image_id) +{ + struct f : public LLSelectedTEFunctor + { + U8 mBump; + f(const U8& b) : mBump(b) {} + bool apply(LLViewerObject* object, S32 te) + { + if (object->permModify()) + { + // update viewer side color in anticipation of update from simulator + object->setTEBumpmap(te, mBump); + } + return true; + } + } setfunc(bumpmap); + + LLViewerInventoryItem* item = gInventory.getItem(image_id); + if(item + && !item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID()) + && (mSelectedObjects->getNumNodes() > 1) ) + { + LL_WARNS() << "Attempted to apply no-copy texture to multiple objects" << LL_ENDL; + return; + } + + if (item && !item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID())) + { + LLViewerObject *object = mSelectedObjects->getFirstRootObject(); + if (!object) + { + return; + } + const LLPermissions& perm = item->getPermissions(); + bool unrestricted = (perm.getMaskBase() & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED; + bool attached = object->isAttachment(); + if (attached && !unrestricted) + { + // Attachments are in world and in inventory simultaneously, + // at the moment server doesn't support such a situation. + return; + } + LLToolDragAndDrop::handleDropMaterialProtections(object, item, LLToolDragAndDrop::SOURCE_AGENT, LLUUID::null); + } + getSelection()->applyToTEs(&setfunc); + + LLSelectMgrSendFunctor sendfunc; + getSelection()->applyToObjects(&sendfunc); +} + +void LLSelectMgr::selectionSetTexGen(U8 texgen) +{ + struct f : public LLSelectedTEFunctor + { + U8 mTexgen; + f(const U8& t) : mTexgen(t) {} + bool apply(LLViewerObject* object, S32 te) + { + if (object->permModify()) + { + // update viewer side color in anticipation of update from simulator + object->setTETexGen(te, mTexgen); + } + return true; + } + } setfunc(texgen); + getSelection()->applyToTEs(&setfunc); + + LLSelectMgrSendFunctor sendfunc; + getSelection()->applyToObjects(&sendfunc); +} + + +void LLSelectMgr::selectionSetShiny(U8 shiny, const LLUUID &image_id) +{ + struct f : public LLSelectedTEFunctor + { + U8 mShiny; + f(const U8& t) : mShiny(t) {} + bool apply(LLViewerObject* object, S32 te) + { + if (object->permModify()) + { + // update viewer side color in anticipation of update from simulator + object->setTEShiny(te, mShiny); + } + return true; + } + } setfunc(shiny); + + LLViewerInventoryItem* item = gInventory.getItem(image_id); + if(item + && !item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID()) + && (mSelectedObjects->getNumNodes() > 1) ) + { + LL_WARNS() << "Attempted to apply no-copy texture to multiple objects" << LL_ENDL; + return; + } + + if (item && !item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID())) + { + LLViewerObject *object = mSelectedObjects->getFirstRootObject(); + if (!object) + { + return; + } + const LLPermissions& perm = item->getPermissions(); + bool unrestricted = (perm.getMaskBase() & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED; + bool attached = object->isAttachment(); + if (attached && !unrestricted) + { + // Attachments are in world and in inventory simultaneously, + // at the moment server doesn't support such a situation. + return; + } + LLToolDragAndDrop::handleDropMaterialProtections(object, item, LLToolDragAndDrop::SOURCE_AGENT, LLUUID::null); + } + getSelection()->applyToTEs(&setfunc); + + LLSelectMgrSendFunctor sendfunc; + getSelection()->applyToObjects(&sendfunc); +} + +void LLSelectMgr::selectionSetFullbright(U8 fullbright) +{ + struct f : public LLSelectedTEFunctor + { + U8 mFullbright; + f(const U8& t) : mFullbright(t) {} + bool apply(LLViewerObject* object, S32 te) + { + if (object->permModify()) + { + // update viewer side color in anticipation of update from simulator + object->setTEFullbright(te, mFullbright); + } + return true; + } + } setfunc(fullbright); + getSelection()->applyToTEs(&setfunc); + + struct g : public LLSelectedObjectFunctor + { + U8 mFullbright; + g(const U8& t) : mFullbright(t) {} + virtual bool apply(LLViewerObject* object) + { + if (object->permModify()) + { + object->sendTEUpdate(); + if (mFullbright) + { + U8 material = object->getMaterial(); + U8 mcode = material & LL_MCODE_MASK; + if (mcode == LL_MCODE_LIGHT) + { + mcode = LL_MCODE_GLASS; + material = (material & ~LL_MCODE_MASK) | mcode; + object->setMaterial(material); + object->sendMaterialUpdate(); + } + } + } + return true; + } + } sendfunc(fullbright); + getSelection()->applyToObjects(&sendfunc); +} + +// This function expects media_data to be a map containing relevant +// media data name/value pairs (e.g. home_url, etc.) +void LLSelectMgr::selectionSetMedia(U8 media_type, const LLSD &media_data) +{ + struct f : public LLSelectedTEFunctor + { + U8 mMediaFlags; + const LLSD &mMediaData; + f(const U8& t, const LLSD& d) : mMediaFlags(t), mMediaData(d) {} + bool apply(LLViewerObject* object, S32 te) + { + if (object->permModify()) + { + // If we are adding media, then check the current state of the + // media data on this face. + // - If it does not have media, AND we are NOT setting the HOME URL, then do NOT add media to this + // face. + // - If it does not have media, and we ARE setting the HOME URL, add media to this face. + // - If it does already have media, add/update media to/on this face + // If we are removing media, just do it (ignore the passed-in LLSD). + if (mMediaFlags & LLTextureEntry::MF_HAS_MEDIA) + { + llassert(mMediaData.isMap()); + const LLTextureEntry *texture_entry = object->getTE(te); + if (!mMediaData.isMap() || + ((NULL != texture_entry) && !texture_entry->hasMedia() && !mMediaData.has(LLMediaEntry::HOME_URL_KEY))) + { + // skip adding/updating media + } + else { + // Add/update media + object->setTEMediaFlags(te, mMediaFlags); + LLVOVolume *vo = dynamic_cast(object); + llassert(NULL != vo); + if (NULL != vo) + { + vo->syncMediaData(te, mMediaData, true/*merge*/, true/*ignore_agent*/); + } + } + } + else + { + // delete media (or just set the flags) + object->setTEMediaFlags(te, mMediaFlags); + } + } + return true; + } + } setfunc(media_type, media_data); + getSelection()->applyToTEs(&setfunc); + + struct f2 : public LLSelectedObjectFunctor + { + virtual bool apply(LLViewerObject* object) + { + if (object->permModify()) + { + object->sendTEUpdate(); + LLVOVolume *vo = dynamic_cast(object); + llassert(NULL != vo); + // It's okay to skip this object if hasMedia() is false... + // the sendTEUpdate() above would remove all media data if it were + // there. + if (NULL != vo && vo->hasMedia()) + { + // Send updated media data FOR THE ENTIRE OBJECT + vo->sendMediaDataUpdate(); + } + } + return true; + } + } func2; + mSelectedObjects->applyToObjects( &func2 ); +} + +void LLSelectMgr::selectionSetGlow(F32 glow) +{ + struct f1 : public LLSelectedTEFunctor + { + F32 mGlow; + f1(F32 glow) : mGlow(glow) {}; + bool apply(LLViewerObject* object, S32 face) + { + if (object->permModify()) + { + // update viewer side color in anticipation of update from simulator + object->setTEGlow(face, mGlow); + } + return true; + } + } func1(glow); + mSelectedObjects->applyToTEs( &func1 ); + + struct f2 : public LLSelectedObjectFunctor + { + virtual bool apply(LLViewerObject* object) + { + if (object->permModify()) + { + object->sendTEUpdate(); + } + return true; + } + } func2; + mSelectedObjects->applyToObjects( &func2 ); +} + +void LLSelectMgr::selectionSetMaterialParams(LLSelectedTEMaterialFunctor* material_func, int te) +{ + struct f1 : public LLSelectedTEFunctor + { + LLMaterialPtr mMaterial; + f1(LLSelectedTEMaterialFunctor* material_func, int te) : _material_func(material_func), _specific_te(te) {} + + bool apply(LLViewerObject* object, S32 te) + { + if (_specific_te == -1 || (te == _specific_te)) + { + if (object && object->permModify() && _material_func) + { + LLTextureEntry* tep = object->getTE(te); + if (tep) + { + LLMaterialPtr current_material = tep->getMaterialParams(); + _material_func->apply(object, te, tep, current_material); + } + } + } + return true; + } + + LLSelectedTEMaterialFunctor* _material_func; + int _specific_te; + } func1(material_func, te); + mSelectedObjects->applyToTEs( &func1 ); + + struct f2 : public LLSelectedObjectFunctor + { + virtual bool apply(LLViewerObject* object) + { + if (object->permModify()) + { + object->sendTEUpdate(); + } + return true; + } + } func2; + mSelectedObjects->applyToObjects( &func2 ); +} + +void LLSelectMgr::selectionRemoveMaterial() +{ + struct f1 : public LLSelectedTEFunctor + { + bool apply(LLViewerObject* object, S32 face) + { + if (object->permModify()) + { + LL_DEBUGS("Materials") << "Removing material from object " << object->getID() << " face " << face << LL_ENDL; + LLMaterialMgr::getInstance()->remove(object->getID(),face); + object->setTEMaterialParams(face, NULL); + } + return true; + } + } func1; + mSelectedObjects->applyToTEs( &func1 ); + + struct f2 : public LLSelectedObjectFunctor + { + virtual bool apply(LLViewerObject* object) + { + if (object->permModify()) + { + object->sendTEUpdate(); + } + return true; + } + } func2; + mSelectedObjects->applyToObjects( &func2 ); +} + + +//----------------------------------------------------------------------------- +// findObjectPermissions() +//----------------------------------------------------------------------------- +LLPermissions* LLSelectMgr::findObjectPermissions(const LLViewerObject* object) +{ + for (LLObjectSelection::valid_iterator iter = getSelection()->valid_begin(); + iter != getSelection()->valid_end(); iter++ ) + { + LLSelectNode* nodep = *iter; + if (nodep->getObject() == object) + { + return nodep->mPermissions; + } + } + + return NULL; +} + + +//----------------------------------------------------------------------------- +// selectionGetGlow() +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectionGetGlow(F32 *glow) +{ + bool identical; + F32 lglow = 0.f; + struct f1 : public LLSelectedTEGetFunctor + { + F32 get(LLViewerObject* object, S32 face) + { + return object->getTE(face)->getGlow(); + } + } func; + identical = mSelectedObjects->getSelectedTEValue( &func, lglow ); + + *glow = lglow; + return identical; +} + + +void LLSelectMgr::selectionSetPhysicsType(U8 type) +{ + struct f : public LLSelectedObjectFunctor + { + U8 mType; + f(const U8& t) : mType(t) {} + virtual bool apply(LLViewerObject* object) + { + if (object->permModify()) + { + object->setPhysicsShapeType(mType); + object->updateFlags(true); + } + return true; + } + } sendfunc(type); + getSelection()->applyToObjects(&sendfunc); +} + +void LLSelectMgr::selectionSetFriction(F32 friction) +{ + struct f : public LLSelectedObjectFunctor + { + F32 mFriction; + f(const F32& friction) : mFriction(friction) {} + virtual bool apply(LLViewerObject* object) + { + if (object->permModify()) + { + object->setPhysicsFriction(mFriction); + object->updateFlags(true); + } + return true; + } + } sendfunc(friction); + getSelection()->applyToObjects(&sendfunc); +} + +void LLSelectMgr::selectionSetGravity(F32 gravity ) +{ + struct f : public LLSelectedObjectFunctor + { + F32 mGravity; + f(const F32& gravity) : mGravity(gravity) {} + virtual bool apply(LLViewerObject* object) + { + if (object->permModify()) + { + object->setPhysicsGravity(mGravity); + object->updateFlags(true); + } + return true; + } + } sendfunc(gravity); + getSelection()->applyToObjects(&sendfunc); +} + +void LLSelectMgr::selectionSetDensity(F32 density ) +{ + struct f : public LLSelectedObjectFunctor + { + F32 mDensity; + f(const F32& density ) : mDensity(density) {} + virtual bool apply(LLViewerObject* object) + { + if (object->permModify()) + { + object->setPhysicsDensity(mDensity); + object->updateFlags(true); + } + return true; + } + } sendfunc(density); + getSelection()->applyToObjects(&sendfunc); +} + +void LLSelectMgr::selectionSetRestitution(F32 restitution) +{ + struct f : public LLSelectedObjectFunctor + { + F32 mRestitution; + f(const F32& restitution ) : mRestitution(restitution) {} + virtual bool apply(LLViewerObject* object) + { + if (object->permModify()) + { + object->setPhysicsRestitution(mRestitution); + object->updateFlags(true); + } + return true; + } + } sendfunc(restitution); + getSelection()->applyToObjects(&sendfunc); +} + + +//----------------------------------------------------------------------------- +// selectionSetMaterial() +//----------------------------------------------------------------------------- +void LLSelectMgr::selectionSetMaterial(U8 material) +{ + struct f : public LLSelectedObjectFunctor + { + U8 mMaterial; + f(const U8& t) : mMaterial(t) {} + virtual bool apply(LLViewerObject* object) + { + if (object->permModify()) + { + U8 cur_material = object->getMaterial(); + U8 material = mMaterial | (cur_material & ~LL_MCODE_MASK); + object->setMaterial(material); + object->sendMaterialUpdate(); + } + return true; + } + } sendfunc(material); + getSelection()->applyToObjects(&sendfunc); +} + +// true if all selected objects have this PCode +bool LLSelectMgr::selectionAllPCode(LLPCode code) +{ + struct f : public LLSelectedObjectFunctor + { + LLPCode mCode; + f(const LLPCode& t) : mCode(t) {} + virtual bool apply(LLViewerObject* object) + { + if (object->getPCode() != mCode) + { + return false; + } + return true; + } + } func(code); + bool res = getSelection()->applyToObjects(&func); + return res; +} + +bool LLSelectMgr::selectionGetIncludeInSearch(bool* include_in_search_out) +{ + LLViewerObject *object = mSelectedObjects->getFirstRootObject(); + if (!object) return false; + + bool include_in_search = object->getIncludeInSearch(); + + bool identical = true; + + for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); + iter != getSelection()->root_end(); iter++) + { + LLViewerObject* object = (*iter)->getObject(); + + if ( include_in_search != object->getIncludeInSearch()) + { + identical = false; + break; + } + } + + *include_in_search_out = include_in_search; + return identical; +} + +void LLSelectMgr::selectionSetIncludeInSearch(bool include_in_search) +{ + LLViewerObject* object = NULL; + for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); + iter != getSelection()->root_end(); iter++) + { + object = (*iter)->getObject(); + object->setIncludeInSearch(include_in_search); + } + sendListToRegions( + "ObjectIncludeInSearch", + packAgentAndSessionID, + packObjectIncludeInSearch, + logNoOp, + &include_in_search, + SEND_ONLY_ROOTS); +} + +bool LLSelectMgr::selectionGetClickAction(U8 *out_action) +{ + LLViewerObject *object = mSelectedObjects->getFirstObject(); + if (!object) + { + return false; + } + + U8 action = object->getClickAction(); + *out_action = action; + + struct f : public LLSelectedObjectFunctor + { + U8 mAction; + f(const U8& t) : mAction(t) {} + virtual bool apply(LLViewerObject* object) + { + if ( mAction != object->getClickAction()) + { + return false; + } + return true; + } + } func(action); + bool res = getSelection()->applyToObjects(&func); + return res; +} + +void LLSelectMgr::selectionSetClickAction(U8 action) +{ + struct f : public LLSelectedObjectFunctor + { + U8 mAction; + f(const U8& t) : mAction(t) {} + virtual bool apply(LLViewerObject* object) + { + object->setClickAction(mAction); + return true; + } + } func(action); + getSelection()->applyToObjects(&func); + + sendListToRegions("ObjectClickAction", + packAgentAndSessionID, + packObjectClickAction, + logNoOp, + &action, + SEND_INDIVIDUALS); +} + + +//----------------------------------------------------------------------------- +// godlike requests +//----------------------------------------------------------------------------- + +typedef std::pair godlike_request_t; + +void LLSelectMgr::sendGodlikeRequest(const std::string& request, const std::string& param) +{ + // If the agent is neither godlike nor an estate owner, the server + // will reject the request. + std::string message_type; + if (gAgent.isGodlike()) + { + message_type = "GodlikeMessage"; + } + else + { + message_type = "EstateOwnerMessage"; + } + + godlike_request_t data(request, param); + if(!mSelectedObjects->getRootObjectCount()) + { + LLMessageSystem* msg = gMessageSystem; + msg->newMessage(message_type.c_str()); + LLSelectMgr::packGodlikeHead(&data); + gAgent.sendReliableMessage(); + } + else + { + sendListToRegions(message_type, packGodlikeHead, packObjectIDAsParam, logNoOp, &data, SEND_ONLY_ROOTS); + } +} + +void LLSelectMgr::packGodlikeHead(void* user_data) +{ + LLMessageSystem* msg = gMessageSystem; + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addUUID("TransactionID", LLUUID::null); + godlike_request_t* data = (godlike_request_t*)user_data; + msg->nextBlock("MethodData"); + msg->addString("Method", data->first); + msg->addUUID("Invoice", LLUUID::null); + + // The parameters used to be restricted to either string or + // integer. This mimics that behavior under the new 'string-only' + // parameter list by not packing a string if there wasn't one + // specified. The object ids will be packed in the + // packObjectIDAsParam() method. + if(data->second.size() > 0) + { + msg->nextBlock("ParamList"); + msg->addString("Parameter", data->second); + } +} + +// static +void LLSelectMgr::logNoOp(LLSelectNode* node, void *) +{ +} + +// static +void LLSelectMgr::logAttachmentRequest(LLSelectNode* node, void *) +{ + LLAttachmentsMgr::instance().onAttachmentRequested(node->mItemID); +} + +// static +void LLSelectMgr::logDetachRequest(LLSelectNode* node, void *) +{ + LLAttachmentsMgr::instance().onDetachRequested(node->mItemID); +} + +// static +void LLSelectMgr::packObjectIDAsParam(LLSelectNode* node, void *) +{ + std::string buf = llformat("%u", node->getObject()->getLocalID()); + gMessageSystem->nextBlock("ParamList"); + gMessageSystem->addString("Parameter", buf); +} + +//----------------------------------------------------------------------------- +// selectionTexScaleAutofit() +//----------------------------------------------------------------------------- +void LLSelectMgr::selectionTexScaleAutofit(F32 repeats_per_meter) +{ + struct f : public LLSelectedTEFunctor + { + F32 mRepeatsPerMeter; + f(const F32& t) : mRepeatsPerMeter(t) {} + bool apply(LLViewerObject* object, S32 te) + { + + if (object->permModify()) + { + // Compute S,T to axis mapping + U32 s_axis, t_axis; + if (!LLPrimitive::getTESTAxes(te, &s_axis, &t_axis)) + { + return true; + } + + F32 new_s = object->getScale().mV[s_axis] * mRepeatsPerMeter; + F32 new_t = object->getScale().mV[t_axis] * mRepeatsPerMeter; + + object->setTEScale(te, new_s, new_t); + } + return true; + } + } setfunc(repeats_per_meter); + getSelection()->applyToTEs(&setfunc); + + LLSelectMgrSendFunctor sendfunc; + getSelection()->applyToObjects(&sendfunc); +} + + + +// Called at the end of a scale operation, this adjusts the textures to attempt to +// maintain a constant repeats per meter. +// BUG: Only works for flex boxes. +//----------------------------------------------------------------------------- +// adjustTexturesByScale() +//----------------------------------------------------------------------------- +void LLSelectMgr::adjustTexturesByScale(bool send_to_sim, bool stretch) +{ + for (LLObjectSelection::iterator iter = getSelection()->begin(); + iter != getSelection()->end(); iter++) + { + LLSelectNode* selectNode = *iter; + LLViewerObject* object = selectNode->getObject(); + + if (!object) + { + continue; + } + + if (!object->permModify()) + { + continue; + } + + if (object->getNumTEs() == 0) + { + continue; + } + + bool send = false; + + for (U8 te_num = 0; te_num < object->getNumTEs(); te_num++) + { + const LLTextureEntry* tep = object->getTE(te_num); + + bool planar = tep->getTexGen() == LLTextureEntry::TEX_GEN_PLANAR; + if (planar == stretch) + { + // Figure out how S,T changed with scale operation + U32 s_axis, t_axis; + if (!LLPrimitive::getTESTAxes(te_num, &s_axis, &t_axis)) + { + continue; + } + + LLVector3 object_scale = object->getScale(); + LLVector3 diffuse_scale_ratio = selectNode->mTextureScaleRatios[te_num]; + + // We like these to track together. NORSPEC-96 + // + LLVector3 normal_scale_ratio = diffuse_scale_ratio; + LLVector3 specular_scale_ratio = diffuse_scale_ratio; + + // Apply new scale to face + if (planar) + { + F32 diffuse_scale_s = diffuse_scale_ratio.mV[s_axis]/object_scale.mV[s_axis]; + F32 diffuse_scale_t = diffuse_scale_ratio.mV[t_axis]/object_scale.mV[t_axis]; + + F32 normal_scale_s = normal_scale_ratio.mV[s_axis]/object_scale.mV[s_axis]; + F32 normal_scale_t = normal_scale_ratio.mV[t_axis]/object_scale.mV[t_axis]; + + F32 specular_scale_s = specular_scale_ratio.mV[s_axis]/object_scale.mV[s_axis]; + F32 specular_scale_t = specular_scale_ratio.mV[t_axis]/object_scale.mV[t_axis]; + + object->setTEScale(te_num, diffuse_scale_s, diffuse_scale_t); + + LLTextureEntry* tep = object->getTE(te_num); + + if (tep && !tep->getMaterialParams().isNull()) + { + LLMaterialPtr orig = tep->getMaterialParams(); + LLMaterialPtr p = gFloaterTools->getPanelFace()->createDefaultMaterial(orig); + p->setNormalRepeat(normal_scale_s, normal_scale_t); + p->setSpecularRepeat(specular_scale_s, specular_scale_t); + + LLMaterialMgr::getInstance()->put(object->getID(), te_num, *p); + } + } + else + { + + F32 diffuse_scale_s = diffuse_scale_ratio.mV[s_axis]*object_scale.mV[s_axis]; + F32 diffuse_scale_t = diffuse_scale_ratio.mV[t_axis]*object_scale.mV[t_axis]; + + F32 normal_scale_s = normal_scale_ratio.mV[s_axis]*object_scale.mV[s_axis]; + F32 normal_scale_t = normal_scale_ratio.mV[t_axis]*object_scale.mV[t_axis]; + + F32 specular_scale_s = specular_scale_ratio.mV[s_axis]*object_scale.mV[s_axis]; + F32 specular_scale_t = specular_scale_ratio.mV[t_axis]*object_scale.mV[t_axis]; + + object->setTEScale(te_num, diffuse_scale_s,diffuse_scale_t); + + LLTextureEntry* tep = object->getTE(te_num); + + if (tep && !tep->getMaterialParams().isNull()) + { + LLMaterialPtr orig = tep->getMaterialParams(); + LLMaterialPtr p = gFloaterTools->getPanelFace()->createDefaultMaterial(orig); + + p->setNormalRepeat(normal_scale_s, normal_scale_t); + p->setSpecularRepeat(specular_scale_s, specular_scale_t); + + LLMaterialMgr::getInstance()->put(object->getID(), te_num, *p); + } + } + send = send_to_sim; + } + } + + if (send) + { + object->sendTEUpdate(); + } + } +} + +//----------------------------------------------------------------------------- +// selectGetAllRootsValid() +// Returns true if the viewer has information on all selected objects +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectGetAllRootsValid() +{ + for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); + iter != getSelection()->root_end(); ++iter ) + { + LLSelectNode* node = *iter; + if( !node->mValid ) + { + return false; + } + } + return true; +} + + +//----------------------------------------------------------------------------- +// selectGetAllValid() +// Returns true if the viewer has information on all selected objects +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectGetAllValid() +{ + for (LLObjectSelection::iterator iter = getSelection()->begin(); + iter != getSelection()->end(); ++iter ) + { + LLSelectNode* node = *iter; + if( !node->mValid ) + { + return false; + } + } + return true; +} + +//----------------------------------------------------------------------------- +// selectGetAllValidAndObjectsFound() - return true if selections are +// valid and objects are found. +// +// For EXT-3114 - same as selectGetModify() without the modify check. +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectGetAllValidAndObjectsFound() +{ + for (LLObjectSelection::iterator iter = getSelection()->begin(); + iter != getSelection()->end(); iter++ ) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + if( !object || !node->mValid ) + { + return false; + } + } + return true; +} + +//----------------------------------------------------------------------------- +// selectGetModify() - return true if current agent can modify all +// selected objects. +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectGetModify() +{ + for (LLObjectSelection::iterator iter = getSelection()->begin(); + iter != getSelection()->end(); iter++ ) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + if( !object || !node->mValid ) + { + return false; + } + if( !object->permModify() ) + { + return false; + } + } + return true; +} + +//----------------------------------------------------------------------------- +// selectGetRootsModify() - return true if current agent can modify all +// selected root objects. +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectGetRootsModify() +{ + for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); + iter != getSelection()->root_end(); iter++ ) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + if( !node->mValid ) + { + return false; + } + if( !object->permModify() ) + { + return false; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// selectGetSameRegion() - return true if all objects are in same region +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectGetSameRegion() +{ + if (getSelection()->isEmpty()) + { + return true; + } + LLViewerObject* object = getSelection()->getFirstObject(); + if (!object) + { + return false; + } + LLViewerRegion* current_region = object->getRegion(); + + for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); + iter != getSelection()->root_end(); iter++) + { + LLSelectNode* node = *iter; + object = node->getObject(); + if (!node->mValid || !object || current_region != object->getRegion()) + { + return false; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// selectGetNonPermanentEnforced() - return true if all objects are not +// permanent enforced +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectGetNonPermanentEnforced() +{ + for (LLObjectSelection::iterator iter = getSelection()->begin(); + iter != getSelection()->end(); iter++ ) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + if( !object || !node->mValid ) + { + return false; + } + if( object->isPermanentEnforced()) + { + return false; + } + } + return true; +} + +//----------------------------------------------------------------------------- +// selectGetRootsNonPermanentEnforced() - return true if all root objects are +// not permanent enforced +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectGetRootsNonPermanentEnforced() +{ + for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); + iter != getSelection()->root_end(); iter++ ) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + if( !node->mValid ) + { + return false; + } + if( object->isPermanentEnforced()) + { + return false; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// selectGetPermanent() - return true if all objects are permanent +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectGetPermanent() +{ + for (LLObjectSelection::iterator iter = getSelection()->begin(); + iter != getSelection()->end(); iter++ ) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + if( !object || !node->mValid ) + { + return false; + } + if( !object->flagObjectPermanent()) + { + return false; + } + } + return true; +} + +//----------------------------------------------------------------------------- +// selectGetRootsPermanent() - return true if all root objects are +// permanent +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectGetRootsPermanent() +{ + for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); + iter != getSelection()->root_end(); iter++ ) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + if( !node->mValid ) + { + return false; + } + if( !object->flagObjectPermanent()) + { + return false; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// selectGetCharacter() - return true if all objects are character +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectGetCharacter() +{ + for (LLObjectSelection::iterator iter = getSelection()->begin(); + iter != getSelection()->end(); iter++ ) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + if( !object || !node->mValid ) + { + return false; + } + if( !object->flagCharacter()) + { + return false; + } + } + return true; +} + +//----------------------------------------------------------------------------- +// selectGetRootsCharacter() - return true if all root objects are +// character +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectGetRootsCharacter() +{ + for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); + iter != getSelection()->root_end(); iter++ ) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + if( !node->mValid ) + { + return false; + } + if( !object->flagCharacter()) + { + return false; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// selectGetNonPathfinding() - return true if all objects are not pathfinding +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectGetNonPathfinding() +{ + for (LLObjectSelection::iterator iter = getSelection()->begin(); + iter != getSelection()->end(); iter++ ) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + if( !object || !node->mValid ) + { + return false; + } + if( object->flagObjectPermanent() || object->flagCharacter()) + { + return false; + } + } + return true; +} + +//----------------------------------------------------------------------------- +// selectGetRootsNonPathfinding() - return true if all root objects are not +// pathfinding +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectGetRootsNonPathfinding() +{ + for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); + iter != getSelection()->root_end(); iter++ ) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + if( !node->mValid ) + { + return false; + } + if( object->flagObjectPermanent() || object->flagCharacter()) + { + return false; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// selectGetNonPermanent() - return true if all objects are not permanent +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectGetNonPermanent() +{ + for (LLObjectSelection::iterator iter = getSelection()->begin(); + iter != getSelection()->end(); iter++ ) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + if( !object || !node->mValid ) + { + return false; + } + if( object->flagObjectPermanent()) + { + return false; + } + } + return true; +} + +//----------------------------------------------------------------------------- +// selectGetRootsNonPermanent() - return true if all root objects are not +// permanent +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectGetRootsNonPermanent() +{ + for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); + iter != getSelection()->root_end(); iter++ ) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + if( !node->mValid ) + { + return false; + } + if( object->flagObjectPermanent()) + { + return false; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// selectGetNonCharacter() - return true if all objects are not character +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectGetNonCharacter() +{ + for (LLObjectSelection::iterator iter = getSelection()->begin(); + iter != getSelection()->end(); iter++ ) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + if( !object || !node->mValid ) + { + return false; + } + if( object->flagCharacter()) + { + return false; + } + } + return true; +} + +//----------------------------------------------------------------------------- +// selectGetRootsNonCharacter() - return true if all root objects are not +// character +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectGetRootsNonCharacter() +{ + for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); + iter != getSelection()->root_end(); iter++ ) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + if( !node->mValid ) + { + return false; + } + if( object->flagCharacter()) + { + return false; + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// selectGetEditableLinksets() - return true if all objects are editable +// pathfinding linksets +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectGetEditableLinksets() +{ + for (LLObjectSelection::iterator iter = getSelection()->begin(); + iter != getSelection()->end(); iter++ ) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + if( !object || !node->mValid ) + { + return false; + } + if (object->flagUsePhysics() || + object->flagTemporaryOnRez() || + object->flagCharacter() || + object->flagVolumeDetect() || + object->flagAnimSource() || + (object->getRegion() != gAgent.getRegion()) || + (!gAgent.isGodlike() && + !gAgent.canManageEstate() && + !object->permYouOwner() && + !object->permMove())) + { + return false; + } + } + return true; +} + +//----------------------------------------------------------------------------- +// selectGetViewableCharacters() - return true if all objects are characters +// viewable within the pathfinding characters floater +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectGetViewableCharacters() +{ + for (LLObjectSelection::iterator iter = getSelection()->begin(); + iter != getSelection()->end(); iter++ ) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + if( !object || !node->mValid ) + { + return false; + } + if( !object->flagCharacter() || + (object->getRegion() != gAgent.getRegion())) + { + return false; + } + } + return true; +} + +//----------------------------------------------------------------------------- +// selectGetRootsTransfer() - return true if current agent can transfer all +// selected root objects. +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectGetRootsTransfer() +{ + for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); + iter != getSelection()->root_end(); iter++ ) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + if( !node->mValid ) + { + return false; + } + if(!object->permTransfer()) + { + return false; + } + } + return true; +} + +//----------------------------------------------------------------------------- +// selectGetRootsCopy() - return true if current agent can copy all +// selected root objects. +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectGetRootsCopy() +{ + for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); + iter != getSelection()->root_end(); iter++ ) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + if( !node->mValid ) + { + return false; + } + if(!object->permCopy()) + { + return false; + } + } + return true; +} + +struct LLSelectGetFirstTest +{ + LLSelectGetFirstTest() : mIdentical(true), mFirst(true) { } + virtual ~LLSelectGetFirstTest() { } + + // returns false to break out of the iteration. + bool checkMatchingNode(LLSelectNode* node) + { + if (!node || !node->mValid) + { + return false; + } + + if (mFirst) + { + mFirstValue = getValueFromNode(node); + mFirst = false; + } + else + { + if ( mFirstValue != getValueFromNode(node) ) + { + mIdentical = false; + // stop testing once we know not all selected are identical. + return false; + } + } + // continue testing. + return true; + } + + bool mIdentical; + LLUUID mFirstValue; + +protected: + virtual const LLUUID& getValueFromNode(LLSelectNode* node) = 0; + +private: + bool mFirst; +}; + +void LLSelectMgr::getFirst(LLSelectGetFirstTest* test) +{ + if (gSavedSettings.getBOOL("EditLinkedParts")) + { + for (LLObjectSelection::valid_iterator iter = getSelection()->valid_begin(); + iter != getSelection()->valid_end(); ++iter ) + { + if (!test->checkMatchingNode(*iter)) + { + break; + } + } + } + else + { + for (LLObjectSelection::root_object_iterator iter = getSelection()->root_object_begin(); + iter != getSelection()->root_object_end(); ++iter ) + { + if (!test->checkMatchingNode(*iter)) + { + break; + } + } + } +} + +//----------------------------------------------------------------------------- +// selectGetCreator() +// Creator information only applies to roots unless editing linked parts. +//----------------------------------------------------------------------------- +struct LLSelectGetFirstCreator : public LLSelectGetFirstTest +{ +protected: + virtual const LLUUID& getValueFromNode(LLSelectNode* node) + { + return node->mPermissions->getCreator(); + } +}; + +bool LLSelectMgr::selectGetCreator(LLUUID& result_id, std::string& name) +{ + LLSelectGetFirstCreator test; + getFirst(&test); + + if (test.mFirstValue.isNull()) + { + name = LLTrans::getString("AvatarNameNobody"); + return false; + } + + result_id = test.mFirstValue; + + if (test.mIdentical) + { + name = LLSLURL("agent", test.mFirstValue, "inspect").getSLURLString(); + } + else + { + name = LLTrans::getString("AvatarNameMultiple"); + } + + return test.mIdentical; +} + +//----------------------------------------------------------------------------- +// selectGetOwner() +// Owner information only applies to roots unless editing linked parts. +//----------------------------------------------------------------------------- +struct LLSelectGetFirstOwner : public LLSelectGetFirstTest +{ +protected: + virtual const LLUUID& getValueFromNode(LLSelectNode* node) + { + // Don't use 'getOwnership' since we return a reference, not a copy. + // Will return LLUUID::null if unowned (which is not allowed and should never happen.) + return node->mPermissions->isGroupOwned() ? node->mPermissions->getGroup() : node->mPermissions->getOwner(); + } +}; + +bool LLSelectMgr::selectGetOwner(LLUUID& result_id, std::string& name) +{ + LLSelectGetFirstOwner test; + getFirst(&test); + + if (test.mFirstValue.isNull()) + { + return false; + } + + result_id = test.mFirstValue; + + if (test.mIdentical) + { + bool group_owned = selectIsGroupOwned(); + if (group_owned) + { + name = LLSLURL("group", test.mFirstValue, "inspect").getSLURLString(); + } + else + { + name = LLSLURL("agent", test.mFirstValue, "inspect").getSLURLString(); + } + } + else + { + name = LLTrans::getString("AvatarNameMultiple"); + } + + return test.mIdentical; +} + +//----------------------------------------------------------------------------- +// selectGetLastOwner() +// Owner information only applies to roots unless editing linked parts. +//----------------------------------------------------------------------------- +struct LLSelectGetFirstLastOwner : public LLSelectGetFirstTest +{ +protected: + virtual const LLUUID& getValueFromNode(LLSelectNode* node) + { + return node->mPermissions->getLastOwner(); + } +}; + +bool LLSelectMgr::selectGetLastOwner(LLUUID& result_id, std::string& name) +{ + LLSelectGetFirstLastOwner test; + getFirst(&test); + + if (test.mFirstValue.isNull()) + { + return false; + } + + result_id = test.mFirstValue; + + if (test.mIdentical) + { + name = LLSLURL("agent", test.mFirstValue, "inspect").getSLURLString(); + } + else + { + name.assign( "" ); + } + + return test.mIdentical; +} + +//----------------------------------------------------------------------------- +// selectGetGroup() +// Group information only applies to roots unless editing linked parts. +//----------------------------------------------------------------------------- +struct LLSelectGetFirstGroup : public LLSelectGetFirstTest +{ +protected: + virtual const LLUUID& getValueFromNode(LLSelectNode* node) + { + return node->mPermissions->getGroup(); + } +}; + +bool LLSelectMgr::selectGetGroup(LLUUID& result_id) +{ + LLSelectGetFirstGroup test; + getFirst(&test); + + result_id = test.mFirstValue; + return test.mIdentical; +} + +//----------------------------------------------------------------------------- +// selectIsGroupOwned() +// Only operates on root nodes unless editing linked parts. +// Returns true if the first selected is group owned. +//----------------------------------------------------------------------------- +struct LLSelectGetFirstGroupOwner : public LLSelectGetFirstTest +{ +protected: + virtual const LLUUID& getValueFromNode(LLSelectNode* node) + { + if (node->mPermissions->isGroupOwned()) + { + return node->mPermissions->getGroup(); + } + return LLUUID::null; + } +}; + +bool LLSelectMgr::selectIsGroupOwned() +{ + LLSelectGetFirstGroupOwner test; + getFirst(&test); + + return test.mFirstValue.notNull(); +} + +//----------------------------------------------------------------------------- +// selectGetPerm() +// Only operates on root nodes. +// Returns true if all have valid data. +// mask_on has bits set to true where all permissions are true +// mask_off has bits set to true where all permissions are false +// if a bit is off both in mask_on and mask_off, the values differ within +// the selection. +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectGetPerm(U8 which_perm, U32* mask_on, U32* mask_off) +{ + U32 mask; + U32 mask_and = 0xffffffff; + U32 mask_or = 0x00000000; + bool all_valid = false; + + for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); + iter != getSelection()->root_end(); iter++) + { + LLSelectNode* node = *iter; + + if (!node->mValid) + { + all_valid = false; + break; + } + + all_valid = true; + + switch( which_perm ) + { + case PERM_BASE: + mask = node->mPermissions->getMaskBase(); + break; + case PERM_OWNER: + mask = node->mPermissions->getMaskOwner(); + break; + case PERM_GROUP: + mask = node->mPermissions->getMaskGroup(); + break; + case PERM_EVERYONE: + mask = node->mPermissions->getMaskEveryone(); + break; + case PERM_NEXT_OWNER: + mask = node->mPermissions->getMaskNextOwner(); + break; + default: + mask = 0x0; + break; + } + mask_and &= mask; + mask_or |= mask; + } + + if (all_valid) + { + // ...true through all ANDs means all true + *mask_on = mask_and; + + // ...false through all ORs means all false + *mask_off = ~mask_or; + return true; + } + else + { + *mask_on = 0; + *mask_off = 0; + return false; + } +} + + + +bool LLSelectMgr::selectGetPermissions(LLPermissions& result_perm) +{ + bool first = true; + LLPermissions perm; + for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); + iter != getSelection()->root_end(); iter++ ) + { + LLSelectNode* node = *iter; + if (!node->mValid) + { + return false; + } + + if (first) + { + perm = *(node->mPermissions); + first = false; + } + else + { + perm.accumulate(*(node->mPermissions)); + } + } + + result_perm = perm; + + return true; +} + + +void LLSelectMgr::selectDelete() +{ + S32 deleteable_count = 0; + + bool locked_but_deleteable_object = false; + bool no_copy_but_deleteable_object = false; + bool all_owned_by_you = true; + + for (LLObjectSelection::iterator iter = getSelection()->begin(); + iter != getSelection()->end(); iter++) + { + LLViewerObject* obj = (*iter)->getObject(); + + if( obj->isAttachment() ) + { + continue; + } + + deleteable_count++; + + // Check to see if you can delete objects which are locked. + if(!obj->permMove()) + { + locked_but_deleteable_object = true; + } + if(!obj->permCopy()) + { + no_copy_but_deleteable_object = true; + } + if(!obj->permYouOwner()) + { + all_owned_by_you = false; + } + } + + if( 0 == deleteable_count ) + { + make_ui_sound("UISndInvalidOp"); + return; + } + + LLNotification::Params params("ConfirmObjectDeleteLock"); + params.functor.function(boost::bind(&LLSelectMgr::confirmDelete, _1, _2, getSelection())); + + if(locked_but_deleteable_object || + no_copy_but_deleteable_object || + !all_owned_by_you) + { + // convert any transient pie-menu selections to full selection so this operation + // has some context + // NOTE: if user cancels delete operation, this will potentially leave objects selected outside of build mode + // but this is ok, if not ideal + convertTransient(); + + //This is messy, but needed to get all english our of the UI. + if(locked_but_deleteable_object && !no_copy_but_deleteable_object && all_owned_by_you) + { + //Locked only + params.name("ConfirmObjectDeleteLock"); + } + else if(!locked_but_deleteable_object && no_copy_but_deleteable_object && all_owned_by_you) + { + //No Copy only + params.name("ConfirmObjectDeleteNoCopy"); + } + else if(!locked_but_deleteable_object && !no_copy_but_deleteable_object && !all_owned_by_you) + { + //not owned only + params.name("ConfirmObjectDeleteNoOwn"); + } + else if(locked_but_deleteable_object && no_copy_but_deleteable_object && all_owned_by_you) + { + //locked and no copy + params.name("ConfirmObjectDeleteLockNoCopy"); + } + else if(locked_but_deleteable_object && !no_copy_but_deleteable_object && !all_owned_by_you) + { + //locked and not owned + params.name("ConfirmObjectDeleteLockNoOwn"); + } + else if(!locked_but_deleteable_object && no_copy_but_deleteable_object && !all_owned_by_you) + { + //no copy and not owned + params.name("ConfirmObjectDeleteNoCopyNoOwn"); + } + else + { + //locked, no copy and not owned + params.name("ConfirmObjectDeleteLockNoCopyNoOwn"); + } + + LLNotifications::instance().add(params); + } + else + { + LLNotifications::instance().forceResponse(params, 0); + } +} + +// static +bool LLSelectMgr::confirmDelete(const LLSD& notification, const LLSD& response, LLObjectSelectionHandle handle) +{ + S32 option = LLNotification::getSelectedOption(notification, response); + if (!handle->getObjectCount()) + { + LL_WARNS() << "Nothing to delete!" << LL_ENDL; + return false; + } + + switch(option) + { + case 0: + { + // TODO: Make sure you have delete permissions on all of them. + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + // attempt to derez into the trash. + LLDeRezInfo info(DRD_TRASH, trash_id); + LLSelectMgr::getInstance()->sendListToRegions("DeRezObject", + packDeRezHeader, + packObjectLocalID, + logNoOp, + (void*) &info, + SEND_ONLY_ROOTS); + // VEFFECT: Delete Object - one effect for all deletes + if (LLSelectMgr::getInstance()->mSelectedObjects->mSelectType != SELECT_TYPE_HUD) + { + LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_POINT, true); + effectp->setPositionGlobal( LLSelectMgr::getInstance()->getSelectionCenterGlobal() ); + effectp->setColor(LLColor4U(gAgent.getEffectColor())); + F32 duration = 0.5f; + duration += LLSelectMgr::getInstance()->mSelectedObjects->getObjectCount() / 64.f; + effectp->setDuration(duration); + } + + gAgentCamera.setLookAt(LOOKAT_TARGET_CLEAR); + + // Keep track of how many objects have been deleted. + add(LLStatViewer::DELETE_OBJECT, LLSelectMgr::getInstance()->mSelectedObjects->getObjectCount()); + } + break; + case 1: + default: + break; + } + return false; +} + + +void LLSelectMgr::selectForceDelete() +{ + sendListToRegions( + "ObjectDelete", + packDeleteHeader, + packObjectLocalID, + logNoOp, + (void*)true, + SEND_ONLY_ROOTS); +} + +bool LLSelectMgr::selectGetEditMoveLinksetPermissions(bool &move, bool &modify) +{ + move = true; + modify = true; + bool selecting_linked_set = !gSavedSettings.getBOOL("EditLinkedParts"); + + for (LLObjectSelection::iterator iter = getSelection()->begin(); + iter != getSelection()->end(); iter++) + { + LLSelectNode* nodep = *iter; + LLViewerObject* object = nodep->getObject(); + if (!object || !nodep->mValid) + { + move = false; + modify = false; + return false; + } + + LLViewerObject *root_object = object->getRootEdit(); + bool this_object_movable = false; + if (object->permMove() && !object->isPermanentEnforced() && + ((root_object == NULL) || !root_object->isPermanentEnforced()) && + (object->permModify() || selecting_linked_set)) + { + this_object_movable = true; + } + move = move && this_object_movable; + modify = modify && object->permModify(); + } + + return true; +} + +void LLSelectMgr::selectGetAggregateSaleInfo(U32 &num_for_sale, + bool &is_for_sale_mixed, + bool &is_sale_price_mixed, + S32 &total_sale_price, + S32 &individual_sale_price) +{ + num_for_sale = 0; + is_for_sale_mixed = false; + is_sale_price_mixed = false; + total_sale_price = 0; + individual_sale_price = 0; + + + // Empty set. + if (getSelection()->root_begin() == getSelection()->root_end()) + return; + + LLSelectNode *node = *(getSelection()->root_begin()); + const bool first_node_for_sale = node->mSaleInfo.isForSale(); + const S32 first_node_sale_price = node->mSaleInfo.getSalePrice(); + + for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); + iter != getSelection()->root_end(); iter++) + { + LLSelectNode* node = *iter; + const bool node_for_sale = node->mSaleInfo.isForSale(); + const S32 node_sale_price = node->mSaleInfo.getSalePrice(); + + // Set mixed if the fields don't match the first node's fields. + if (node_for_sale != first_node_for_sale) + is_for_sale_mixed = true; + if (node_sale_price != first_node_sale_price) + is_sale_price_mixed = true; + + if (node_for_sale) + { + total_sale_price += node_sale_price; + num_for_sale ++; + } + } + + individual_sale_price = first_node_sale_price; + if (is_for_sale_mixed) + { + is_sale_price_mixed = true; + individual_sale_price = 0; + } +} + +// returns true if all nodes are valid. method also stores an +// accumulated sale info. +bool LLSelectMgr::selectGetSaleInfo(LLSaleInfo& result_sale_info) +{ + bool first = true; + LLSaleInfo sale_info; + for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); + iter != getSelection()->root_end(); iter++ ) + { + LLSelectNode* node = *iter; + if (!node->mValid) + { + return false; + } + + if (first) + { + sale_info = node->mSaleInfo; + first = false; + } + else + { + sale_info.accumulate(node->mSaleInfo); + } + } + + result_sale_info = sale_info; + + return true; +} + +bool LLSelectMgr::selectGetAggregatePermissions(LLAggregatePermissions& result_perm) +{ + bool first = true; + LLAggregatePermissions perm; + for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); + iter != getSelection()->root_end(); iter++ ) + { + LLSelectNode* node = *iter; + if (!node->mValid) + { + return false; + } + + if (first) + { + perm = node->mAggregatePerm; + first = false; + } + else + { + perm.aggregate(node->mAggregatePerm); + } + } + + result_perm = perm; + + return true; +} + +bool LLSelectMgr::selectGetAggregateTexturePermissions(LLAggregatePermissions& result_perm) +{ + bool first = true; + LLAggregatePermissions perm; + for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); + iter != getSelection()->root_end(); iter++ ) + { + LLSelectNode* node = *iter; + if (!node->mValid) + { + return false; + } + + LLAggregatePermissions t_perm = node->getObject()->permYouOwner() ? node->mAggregateTexturePermOwner : node->mAggregateTexturePerm; + if (first) + { + perm = t_perm; + first = false; + } + else + { + perm.aggregate(t_perm); + } + } + + result_perm = perm; + + return true; +} + +bool LLSelectMgr::isMovableAvatarSelected() +{ + if (mAllowSelectAvatar && getSelection()->getObjectCount() == 1) + { + // nothing but avatar should be selected, so check that + // there is only one selected object and it is a root + LLViewerObject* obj = getSelection()->getFirstRootObject(); + return obj && obj->isAvatar() && getSelection()->getFirstMoveableNode(true); + } + return false; +} + +//-------------------------------------------------------------------- +// Duplicate objects +//-------------------------------------------------------------------- + +// JC - If this doesn't work right, duplicate the selection list +// before doing anything, do a deselect, then send the duplicate +// messages. +struct LLDuplicateData +{ + LLVector3 offset; + U32 flags; +}; + +void LLSelectMgr::selectDuplicate(const LLVector3& offset, bool select_copy) +{ + if (mSelectedObjects->isAttachment()) + { + //RN: do not duplicate attachments + make_ui_sound("UISndInvalidOp"); + return; + } + if (!canDuplicate()) + { + LLSelectNode* node = getSelection()->getFirstRootNode(NULL, true); + if (node) + { + LLSD args; + args["OBJ_NAME"] = node->mName; + LLNotificationsUtil::add("NoCopyPermsNoObject", args); + return; + } + } + LLDuplicateData data; + + data.offset = offset; + data.flags = (select_copy ? FLAGS_CREATE_SELECTED : 0x0); + + sendListToRegions("ObjectDuplicate", packDuplicateHeader, packDuplicate, logNoOp, &data, SEND_ONLY_ROOTS); + + if (select_copy) + { + // the new copy will be coming in selected + deselectAll(); + } + else + { + for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); + iter != getSelection()->root_end(); iter++ ) + { + LLSelectNode* node = *iter; + node->mDuplicated = true; + node->mDuplicatePos = node->getObject()->getPositionGlobal(); + node->mDuplicateRot = node->getObject()->getRotation(); + } + } +} + +void LLSelectMgr::repeatDuplicate() +{ + if (mSelectedObjects->isAttachment()) + { + //RN: do not duplicate attachments + make_ui_sound("UISndInvalidOp"); + return; + } + + std::vector non_duplicated_objects; + + for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); + iter != getSelection()->root_end(); iter++ ) + { + LLSelectNode* node = *iter; + if (!node->mDuplicated) + { + non_duplicated_objects.push_back(node->getObject()); + } + } + + // make sure only previously duplicated objects are selected + for (std::vector::iterator iter = non_duplicated_objects.begin(); + iter != non_duplicated_objects.end(); ++iter) + { + LLViewerObject* objectp = *iter; + deselectObjectAndFamily(objectp); + } + + // duplicate objects in place + LLDuplicateData data; + + data.offset = LLVector3::zero; + data.flags = 0x0; + + sendListToRegions("ObjectDuplicate", packDuplicateHeader, packDuplicate, logNoOp, &data, SEND_ONLY_ROOTS); + + // move current selection based on delta from duplication position and update duplication position + for (LLObjectSelection::root_iterator iter = getSelection()->root_begin(); + iter != getSelection()->root_end(); iter++ ) + { + LLSelectNode* node = *iter; + if (node->mDuplicated) + { + LLQuaternion cur_rot = node->getObject()->getRotation(); + LLQuaternion rot_delta = (~node->mDuplicateRot * cur_rot); + LLQuaternion new_rot = cur_rot * rot_delta; + LLVector3d cur_pos = node->getObject()->getPositionGlobal(); + LLVector3d new_pos = cur_pos + ((cur_pos - node->mDuplicatePos) * rot_delta); + + node->mDuplicatePos = node->getObject()->getPositionGlobal(); + node->mDuplicateRot = node->getObject()->getRotation(); + node->getObject()->setPositionGlobal(new_pos); + node->getObject()->setRotation(new_rot); + } + } + + sendMultipleUpdate(UPD_ROTATION | UPD_POSITION); +} + +// static +void LLSelectMgr::packDuplicate( LLSelectNode* node, void *duplicate_data ) +{ + gMessageSystem->nextBlockFast(_PREHASH_ObjectData); + gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, node->getObject()->getLocalID()); +} + + +//-------------------------------------------------------------------- +// Duplicate On Ray +//-------------------------------------------------------------------- + +// Duplicates the selected objects, but places the copy along a cast +// ray. +struct LLDuplicateOnRayData +{ + LLVector3 mRayStartRegion; + LLVector3 mRayEndRegion; + bool mBypassRaycast; + bool mRayEndIsIntersection; + LLUUID mRayTargetID; + bool mCopyCenters; + bool mCopyRotates; + U32 mFlags; +}; + +void LLSelectMgr::selectDuplicateOnRay(const LLVector3 &ray_start_region, + const LLVector3 &ray_end_region, + bool bypass_raycast, + bool ray_end_is_intersection, + const LLUUID &ray_target_id, + bool copy_centers, + bool copy_rotates, + bool select_copy) +{ + if (mSelectedObjects->isAttachment()) + { + // do not duplicate attachments + make_ui_sound("UISndInvalidOp"); + return; + } + + LLDuplicateOnRayData data; + + data.mRayStartRegion = ray_start_region; + data.mRayEndRegion = ray_end_region; + data.mBypassRaycast = bypass_raycast; + data.mRayEndIsIntersection = ray_end_is_intersection; + data.mRayTargetID = ray_target_id; + data.mCopyCenters = copy_centers; + data.mCopyRotates = copy_rotates; + data.mFlags = (select_copy ? FLAGS_CREATE_SELECTED : 0x0); + + sendListToRegions("ObjectDuplicateOnRay", + packDuplicateOnRayHead, packObjectLocalID, logNoOp, &data, SEND_ONLY_ROOTS); + + if (select_copy) + { + // the new copy will be coming in selected + deselectAll(); + } +} + +// static +void LLSelectMgr::packDuplicateOnRayHead(void *user_data) +{ + LLMessageSystem *msg = gMessageSystem; + LLDuplicateOnRayData *data = (LLDuplicateOnRayData *)user_data; + + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); + msg->addUUIDFast(_PREHASH_GroupID, gAgent.getGroupID() ); + msg->addVector3Fast(_PREHASH_RayStart, data->mRayStartRegion ); + msg->addVector3Fast(_PREHASH_RayEnd, data->mRayEndRegion ); + msg->addBOOLFast(_PREHASH_BypassRaycast, data->mBypassRaycast ); + msg->addBOOLFast(_PREHASH_RayEndIsIntersection, data->mRayEndIsIntersection ); + msg->addBOOLFast(_PREHASH_CopyCenters, data->mCopyCenters ); + msg->addBOOLFast(_PREHASH_CopyRotates, data->mCopyRotates ); + msg->addUUIDFast(_PREHASH_RayTargetID, data->mRayTargetID ); + msg->addU32Fast(_PREHASH_DuplicateFlags, data->mFlags ); +} + + + +//------------------------------------------------------------------------ +// Object position, scale, rotation update, all-in-one +//------------------------------------------------------------------------ + +void LLSelectMgr::sendMultipleUpdate(U32 type) +{ + if (type == UPD_NONE) return; + // send individual updates when selecting textures or individual objects + ESendType send_type = (!gSavedSettings.getBOOL("EditLinkedParts") && !getTEMode()) ? SEND_ONLY_ROOTS : SEND_ROOTS_FIRST; + if (send_type == SEND_ONLY_ROOTS) + { + // tell simulator to apply to whole linked sets + type |= UPD_LINKED_SETS; + } + + sendListToRegions( + "MultipleObjectUpdate", + packAgentAndSessionID, + packMultipleUpdate, + logNoOp, + &type, + send_type); +} + +// static +void LLSelectMgr::packMultipleUpdate(LLSelectNode* node, void *user_data) +{ + LLViewerObject* object = node->getObject(); + U32 *type32 = (U32 *)user_data; + U8 type = (U8)*type32; + U8 data[256]; + + gMessageSystem->nextBlockFast(_PREHASH_ObjectData); + gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, object->getLocalID() ); + gMessageSystem->addU8Fast(_PREHASH_Type, type ); + + S32 offset = 0; + + // JC: You MUST pack the data in this order. The receiving + // routine process_multiple_update_message on simulator will + // extract them in this order. + + if (type & UPD_POSITION) + { + htolememcpy(&data[offset], &(object->getPosition().mV), MVT_LLVector3, 12); + offset += 12; + } + if (type & UPD_ROTATION) + { + LLQuaternion quat = object->getRotation(); + LLVector3 vec = quat.packToVector3(); + htolememcpy(&data[offset], &(vec.mV), MVT_LLQuaternion, 12); + offset += 12; + } + if (type & UPD_SCALE) + { + //LL_INFOS() << "Sending object scale " << object->getScale() << LL_ENDL; + htolememcpy(&data[offset], &(object->getScale().mV), MVT_LLVector3, 12); + offset += 12; + } + gMessageSystem->addBinaryDataFast(_PREHASH_Data, data, offset); +} + +//------------------------------------------------------------------------ +// Ownership +//------------------------------------------------------------------------ +struct LLOwnerData +{ + LLUUID owner_id; + LLUUID group_id; + bool override; +}; + +void LLSelectMgr::sendOwner(const LLUUID& owner_id, + const LLUUID& group_id, + bool override) +{ + LLOwnerData data; + + data.owner_id = owner_id; + data.group_id = group_id; + data.override = override; + + sendListToRegions("ObjectOwner", packOwnerHead, packObjectLocalID, logNoOp, &data, SEND_ONLY_ROOTS); +} + +// static +void LLSelectMgr::packOwnerHead(void *user_data) +{ + LLOwnerData *data = (LLOwnerData *)user_data; + + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); + gMessageSystem->nextBlockFast(_PREHASH_HeaderData); + gMessageSystem->addBOOLFast(_PREHASH_Override, data->override); + gMessageSystem->addUUIDFast(_PREHASH_OwnerID, data->owner_id); + gMessageSystem->addUUIDFast(_PREHASH_GroupID, data->group_id); +} + +//------------------------------------------------------------------------ +// Group +//------------------------------------------------------------------------ + +void LLSelectMgr::sendGroup(const LLUUID& group_id) +{ + LLUUID local_group_id(group_id); + sendListToRegions("ObjectGroup", packAgentAndSessionAndGroupID, packObjectLocalID, logNoOp, &local_group_id, SEND_ONLY_ROOTS); +} + + +//------------------------------------------------------------------------ +// Buy +//------------------------------------------------------------------------ + +struct LLBuyData +{ + std::vector mObjectsSent; + LLUUID mCategoryID; + LLSaleInfo mSaleInfo; +}; + +// *NOTE: does not work for multiple object buy, which UI does not +// currently support sale info is used for verification only, if it +// doesn't match region info then sale is canceled Need to get sale +// info -as displayed in the UI- for every item. +void LLSelectMgr::sendBuy(const LLUUID& buyer_id, const LLUUID& category_id, const LLSaleInfo sale_info) +{ + LLBuyData buy; + buy.mCategoryID = category_id; + buy.mSaleInfo = sale_info; + sendListToRegions("ObjectBuy", packAgentGroupAndCatID, packBuyObjectIDs, logNoOp, &buy, SEND_ONLY_ROOTS); +} + +// static +void LLSelectMgr::packBuyObjectIDs(LLSelectNode* node, void* data) +{ + LLBuyData* buy = (LLBuyData*)data; + + LLViewerObject* object = node->getObject(); + if (std::find(buy->mObjectsSent.begin(), buy->mObjectsSent.end(), object) == buy->mObjectsSent.end()) + { + buy->mObjectsSent.push_back(object); + gMessageSystem->nextBlockFast(_PREHASH_ObjectData); + gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, object->getLocalID() ); + gMessageSystem->addU8Fast(_PREHASH_SaleType, buy->mSaleInfo.getSaleType()); + gMessageSystem->addS32Fast(_PREHASH_SalePrice, buy->mSaleInfo.getSalePrice()); + } +} + +//------------------------------------------------------------------------ +// Permissions +//------------------------------------------------------------------------ + +struct LLPermData +{ + U8 mField; + bool mSet; + U32 mMask; + bool mOverride; +}; + +// TODO: Make this able to fail elegantly. +void LLSelectMgr::selectionSetObjectPermissions(U8 field, + bool set, + U32 mask, + bool override) +{ + LLPermData data; + + data.mField = field; + data.mSet = set; + data.mMask = mask; + data.mOverride = override; + + sendListToRegions("ObjectPermissions", packPermissionsHead, packPermissions, logNoOp, &data, SEND_ONLY_ROOTS); +} + +void LLSelectMgr::packPermissionsHead(void* user_data) +{ + LLPermData* data = (LLPermData*)user_data; + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + gMessageSystem->nextBlockFast(_PREHASH_HeaderData); + gMessageSystem->addBOOLFast(_PREHASH_Override, data->mOverride); +} + + +// Now that you've added a bunch of objects, send a select message +// on the entire list for efficiency. +/* +void LLSelectMgr::sendSelect() +{ + LL_ERRS() << "Not implemented" << LL_ENDL; +} +*/ + +void LLSelectMgr::deselectAll() +{ + if (!mSelectedObjects->getNumNodes()) + { + return; + } + + // Zap the angular velocity, as the sim will set it to zero + for (LLObjectSelection::iterator iter = mSelectedObjects->begin(); + iter != mSelectedObjects->end(); iter++ ) + { + LLViewerObject *objectp = (*iter)->getObject(); + objectp->setAngularVelocity( 0,0,0 ); + objectp->setVelocity( 0,0,0 ); + } + + sendListToRegions( + "ObjectDeselect", + packAgentAndSessionID, + packObjectLocalID, + logNoOp, + NULL, + SEND_INDIVIDUALS); + + removeAll(); + + mLastSentSelectionCenterGlobal.clearVec(); + + updatePointAt(); +} + +void LLSelectMgr::deselectAllForStandingUp() +{ + /* + This function is similar deselectAll() except for the first if statement + which was removed. This is needed as a workaround for DEV-2854 + */ + + // Zap the angular velocity, as the sim will set it to zero + for (LLObjectSelection::iterator iter = mSelectedObjects->begin(); + iter != mSelectedObjects->end(); iter++ ) + { + LLViewerObject *objectp = (*iter)->getObject(); + objectp->setAngularVelocity( 0,0,0 ); + objectp->setVelocity( 0,0,0 ); + } + + sendListToRegions( + "ObjectDeselect", + packAgentAndSessionID, + packObjectLocalID, + logNoOp, + NULL, + SEND_INDIVIDUALS); + + removeAll(); + + mLastSentSelectionCenterGlobal.clearVec(); + + updatePointAt(); +} + +void LLSelectMgr::deselectUnused() +{ + // no more outstanding references to this selection + if (mSelectedObjects->getNumRefs() == 1) + { + deselectAll(); + } +} + +void LLSelectMgr::convertTransient() +{ + LLObjectSelection::iterator node_it; + for (node_it = mSelectedObjects->begin(); node_it != mSelectedObjects->end(); ++node_it) + { + LLSelectNode *nodep = *node_it; + nodep->setTransient(false); + } +} + +void LLSelectMgr::deselectAllIfTooFar() +{ + if (mSelectedObjects->isEmpty() || mSelectedObjects->mSelectType == SELECT_TYPE_HUD) + { + return; + } + + // HACK: Don't deselect when we're navigating to rate an object's + // owner or creator. JC + if (gMenuObject->getVisible()) + { + return; + } + + LLVector3d selectionCenter = getSelectionCenterGlobal(); + if (gSavedSettings.getBOOL("LimitSelectDistance") + && (!mSelectedObjects->getPrimaryObject() || !mSelectedObjects->getPrimaryObject()->isAvatar()) + && (mSelectedObjects->getPrimaryObject() != LLViewerMediaFocus::getInstance()->getFocusedObject()) + && !mSelectedObjects->isAttachment() + && !selectionCenter.isExactlyZero()) + { + F32 deselect_dist = gSavedSettings.getF32("MaxSelectDistance"); + F32 deselect_dist_sq = deselect_dist * deselect_dist; + + LLVector3d select_delta = gAgent.getPositionGlobal() - selectionCenter; + F32 select_dist_sq = (F32) select_delta.magVecSquared(); + + if (select_dist_sq > deselect_dist_sq) + { + if (mDebugSelectMgr) + { + LL_INFOS() << "Selection manager: auto-deselecting, select_dist = " << (F32) sqrt(select_dist_sq) << LL_ENDL; + LL_INFOS() << "agent pos global = " << gAgent.getPositionGlobal() << LL_ENDL; + LL_INFOS() << "selection pos global = " << selectionCenter << LL_ENDL; + } + + deselectAll(); + } + } +} + + +void LLSelectMgr::selectionSetObjectName(const std::string& name) +{ + std::string name_copy(name); + + // we only work correctly if 1 object is selected. + if(mSelectedObjects->getRootObjectCount() == 1) + { + sendListToRegions("ObjectName", + packAgentAndSessionID, + packObjectName, + logNoOp, + (void*)(&name_copy), + SEND_ONLY_ROOTS); + } + else if(mSelectedObjects->getObjectCount() == 1) + { + sendListToRegions("ObjectName", + packAgentAndSessionID, + packObjectName, + logNoOp, + (void*)(&name_copy), + SEND_INDIVIDUALS); + } +} + +void LLSelectMgr::selectionSetObjectDescription(const std::string& desc) +{ + std::string desc_copy(desc); + + // we only work correctly if 1 object is selected. + if(mSelectedObjects->getRootObjectCount() == 1) + { + sendListToRegions("ObjectDescription", + packAgentAndSessionID, + packObjectDescription, + logNoOp, + (void*)(&desc_copy), + SEND_ONLY_ROOTS); + } + else if(mSelectedObjects->getObjectCount() == 1) + { + sendListToRegions("ObjectDescription", + packAgentAndSessionID, + packObjectDescription, + logNoOp, + (void*)(&desc_copy), + SEND_INDIVIDUALS); + } +} + +void LLSelectMgr::selectionSetObjectCategory(const LLCategory& category) +{ + // for now, we only want to be able to set one root category at + // a time. + if(mSelectedObjects->getRootObjectCount() != 1) return; + sendListToRegions("ObjectCategory", + packAgentAndSessionID, + packObjectCategory, + logNoOp, + (void*)(&category), + SEND_ONLY_ROOTS); +} + +void LLSelectMgr::selectionSetObjectSaleInfo(const LLSaleInfo& sale_info) +{ + sendListToRegions("ObjectSaleInfo", + packAgentAndSessionID, + packObjectSaleInfo, + logNoOp, + (void*)(&sale_info), + SEND_ONLY_ROOTS); +} + +//---------------------------------------------------------------------- +// Attachments +//---------------------------------------------------------------------- + +void LLSelectMgr::sendAttach(U8 attachment_point, bool replace) +{ + sendAttach(mSelectedObjects, attachment_point, replace); +} + +void LLSelectMgr::sendAttach(LLObjectSelectionHandle selection_handle, U8 attachment_point, bool replace) +{ + if (selection_handle.isNull()) + { + return; + } + + LLViewerObject* attach_object = selection_handle->getFirstRootObject(); + + if (!attach_object || !isAgentAvatarValid() || selection_handle->mSelectType != SELECT_TYPE_WORLD) + { + return; + } + + bool build_mode = LLToolMgr::getInstance()->inEdit(); + // Special case: Attach to default location for this object. + if (0 == attachment_point || + get_if_there(gAgentAvatarp->mAttachmentPoints, (S32)attachment_point, (LLViewerJointAttachment*)NULL)) + { + if (!replace || attachment_point != 0) + { + // If we know the attachment point then we got here by clicking an + // "Attach to..." context menu item, so we should add, not replace. + attachment_point |= ATTACHMENT_ADD; + } + + sendListToRegions( + selection_handle, + "ObjectAttach", + packAgentIDAndSessionAndAttachment, + packObjectIDAndRotation, + logAttachmentRequest, + &attachment_point, + SEND_ONLY_ROOTS ); + if (!build_mode) + { + // After "ObjectAttach" server will unsubscribe us from properties updates + // so either deselect objects or resend selection after attach packet reaches server + // In case of build_mode LLPanelObjectInventory::refresh() will deal with selection + // Still unsubscribe even in case selection_handle is not current selection + deselectAll(); + } + } +} + +void LLSelectMgr::sendDetach() +{ + if (!mSelectedObjects->getNumNodes() || mSelectedObjects->mSelectType == SELECT_TYPE_WORLD) + { + return; + } + + sendListToRegions( + "ObjectDetach", + packAgentAndSessionID, + packObjectLocalID, + logDetachRequest, + NULL, + SEND_ONLY_ROOTS ); +} + + +void LLSelectMgr::sendDropAttachment() +{ + if (!mSelectedObjects->getNumNodes() || mSelectedObjects->mSelectType == SELECT_TYPE_WORLD) + { + return; + } + + sendListToRegions( + "ObjectDrop", + packAgentAndSessionID, + packObjectLocalID, + logDetachRequest, + NULL, + SEND_ONLY_ROOTS); +} + +//---------------------------------------------------------------------- +// Links +//---------------------------------------------------------------------- + +void LLSelectMgr::sendLink() +{ + if (!mSelectedObjects->getNumNodes()) + { + return; + } + + sendListToRegions( + "ObjectLink", + packAgentAndSessionID, + packObjectLocalID, + logNoOp, + NULL, + SEND_ONLY_ROOTS); +} + +void LLSelectMgr::sendDelink() +{ + if (!mSelectedObjects->getNumNodes()) + { + return; + } + + struct f : public LLSelectedObjectFunctor + { //on delink, any modifyable object should + f() {} + + virtual bool apply(LLViewerObject* object) + { + if (object->permModify()) + { + if (object->getPhysicsShapeType() == LLViewerObject::PHYSICS_SHAPE_NONE) + { + object->setPhysicsShapeType(LLViewerObject::PHYSICS_SHAPE_CONVEX_HULL); + object->updateFlags(); + } + } + return true; + } + } sendfunc; + getSelection()->applyToObjects(&sendfunc); + + + // Delink needs to send individuals so you can unlink a single object from + // a linked set. + sendListToRegions( + "ObjectDelink", + packAgentAndSessionID, + packObjectLocalID, + logNoOp, + NULL, + SEND_INDIVIDUALS); +} + + +//---------------------------------------------------------------------- +// Hinges +//---------------------------------------------------------------------- + +/* +void LLSelectMgr::sendHinge(U8 type) +{ + if (!mSelectedObjects->getNumNodes()) + { + return; + } + + sendListToRegions( + "ObjectHinge", + packHingeHead, + packObjectLocalID, + &type, + SEND_ONLY_ROOTS); +} + + +void LLSelectMgr::sendDehinge() +{ + if (!mSelectedObjects->getNumNodes()) + { + return; + } + + sendListToRegions( + "ObjectDehinge", + packAgentAndSessionID, + packObjectLocalID, + NULL, + SEND_ONLY_ROOTS); +}*/ + +void LLSelectMgr::sendSelect() +{ + if (!mSelectedObjects->getNumNodes()) + { + return; + } + + sendListToRegions( + "ObjectSelect", + packAgentAndSessionID, + packObjectLocalID, + logNoOp, + NULL, + SEND_INDIVIDUALS); +} + +// static +void LLSelectMgr::packHingeHead(void *user_data) +{ + U8 *type = (U8 *)user_data; + + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); + gMessageSystem->nextBlockFast(_PREHASH_JointType); + gMessageSystem->addU8Fast(_PREHASH_Type, *type ); +} + + +void LLSelectMgr::selectionDump() +{ + struct f : public LLSelectedObjectFunctor + { + virtual bool apply(LLViewerObject* object) + { + object->dump(); + return true; + } + } func; + getSelection()->applyToObjects(&func); +} + +void LLSelectMgr::saveSelectedObjectColors() +{ + struct f : public LLSelectedNodeFunctor + { + virtual bool apply(LLSelectNode* node) + { + node->saveColors(); + return true; + } + } func; + getSelection()->applyToNodes(&func); +} + +void LLSelectMgr::saveSelectedShinyColors() +{ + struct f : public LLSelectedNodeFunctor + { + virtual bool apply(LLSelectNode* node) + { + node->saveShinyColors(); + return true; + } + } func; + getSelection()->applyToNodes(&func); +} + +void LLSelectMgr::saveSelectedObjectTextures() +{ + // invalidate current selection so we update saved textures + struct f : public LLSelectedNodeFunctor + { + virtual bool apply(LLSelectNode* node) + { + node->mValid = false; + return true; + } + } func; + getSelection()->applyToNodes(&func); + + // request object properties message to get updated permissions data + sendSelect(); +} + + +// This routine should be called whenever a drag is initiated. +// also need to know to which simulator to send update message +void LLSelectMgr::saveSelectedObjectTransform(EActionType action_type) +{ + if (mSelectedObjects->isEmpty()) + { + // nothing selected, so nothing to save + return; + } + + struct f : public LLSelectedNodeFunctor + { + EActionType mActionType; + LLSelectMgr* mManager; + f(EActionType a, LLSelectMgr* p) : mActionType(a), mManager(p) {} + virtual bool apply(LLSelectNode* selectNode) + { + LLViewerObject* object = selectNode->getObject(); + if (!object) + { + return true; // skip + } + selectNode->mSavedPositionLocal = object->getPosition(); + if (object->isAttachment()) + { + if (object->isRootEdit()) + { + LLXform* parent_xform = object->mDrawable->getXform()->getParent(); + if (parent_xform) + { + selectNode->mSavedPositionGlobal = gAgent.getPosGlobalFromAgent((object->getPosition() * parent_xform->getWorldRotation()) + parent_xform->getWorldPosition()); + } + else + { + selectNode->mSavedPositionGlobal = object->getPositionGlobal(); + } + } + else + { + LLViewerObject* attachment_root = (LLViewerObject*)object->getParent(); + LLXform* parent_xform = attachment_root ? attachment_root->mDrawable->getXform()->getParent() : NULL; + if (parent_xform) + { + LLVector3 root_pos = (attachment_root->getPosition() * parent_xform->getWorldRotation()) + parent_xform->getWorldPosition(); + LLQuaternion root_rot = (attachment_root->getRotation() * parent_xform->getWorldRotation()); + selectNode->mSavedPositionGlobal = gAgent.getPosGlobalFromAgent((object->getPosition() * root_rot) + root_pos); + } + else + { + selectNode->mSavedPositionGlobal = object->getPositionGlobal(); + } + } + selectNode->mSavedRotation = object->getRenderRotation(); + } + else + { + selectNode->mSavedPositionGlobal = object->getPositionGlobal(); + selectNode->mSavedRotation = object->getRotationRegion(); + } + + selectNode->mSavedScale = object->getScale(); + selectNode->saveTextureScaleRatios(mManager->mTextureChannel); + return true; + } + } func(action_type, this); + getSelection()->applyToNodes(&func); + + mSavedSelectionBBox = getBBoxOfSelection(); +} + +struct LLSelectMgrApplyFlags : public LLSelectedObjectFunctor +{ + LLSelectMgrApplyFlags(U32 flags, bool state) : mFlags(flags), mState(state) {} + U32 mFlags; + bool mState; + virtual bool apply(LLViewerObject* object) + { + if ( object->permModify()) + { + if (object->isRoot()) // don't send for child objects + { + object->setFlags( mFlags, mState); + } + else if (FLAGS_WORLD & mFlags && ((LLViewerObject*)object->getRoot())->isSelected()) + { + // FLAGS_WORLD are shared by all items in linkset + object->setFlagsWithoutUpdate(FLAGS_WORLD & mFlags, mState); + } + }; + return true; + } +}; + +void LLSelectMgr::selectionUpdatePhysics(bool physics) +{ + LLSelectMgrApplyFlags func( FLAGS_USE_PHYSICS, physics); + getSelection()->applyToObjects(&func); +} + +void LLSelectMgr::selectionUpdateTemporary(bool is_temporary) +{ + LLSelectMgrApplyFlags func( FLAGS_TEMPORARY_ON_REZ, is_temporary); + getSelection()->applyToObjects(&func); +} + +void LLSelectMgr::selectionUpdatePhantom(bool is_phantom) +{ + LLSelectMgrApplyFlags func( FLAGS_PHANTOM, is_phantom); + getSelection()->applyToObjects(&func); +} + +//---------------------------------------------------------------------- +// Helpful packing functions for sendObjectMessage() +//---------------------------------------------------------------------- + +// static +void LLSelectMgr::packAgentIDAndSessionAndAttachment( void *user_data) +{ + U8 *attachment_point = (U8*)user_data; + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + gMessageSystem->addU8Fast(_PREHASH_AttachmentPoint, *attachment_point); +} + +// static +void LLSelectMgr::packAgentID( void *user_data) +{ + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); +} + +// static +void LLSelectMgr::packAgentAndSessionID(void* user_data) +{ + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); +} + +// static +void LLSelectMgr::packAgentAndGroupID(void* user_data) +{ + LLOwnerData *data = (LLOwnerData *)user_data; + + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, data->owner_id ); + gMessageSystem->addUUIDFast(_PREHASH_GroupID, data->group_id ); +} + +// static +void LLSelectMgr::packAgentAndSessionAndGroupID(void* user_data) +{ + LLUUID* group_idp = (LLUUID*) user_data; + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + gMessageSystem->addUUIDFast(_PREHASH_GroupID, *group_idp); +} + +// static +void LLSelectMgr::packDuplicateHeader(void* data) +{ + LLUUID group_id(gAgent.getGroupID()); + packAgentAndSessionAndGroupID(&group_id); + + LLDuplicateData* dup_data = (LLDuplicateData*) data; + + gMessageSystem->nextBlockFast(_PREHASH_SharedData); + gMessageSystem->addVector3Fast(_PREHASH_Offset, dup_data->offset); + gMessageSystem->addU32Fast(_PREHASH_DuplicateFlags, dup_data->flags); +} + +// static +void LLSelectMgr::packDeleteHeader(void* userdata) +{ + bool force = (bool)(intptr_t)userdata; + + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + gMessageSystem->addBOOLFast(_PREHASH_Force, force); +} + +// static +void LLSelectMgr::packAgentGroupAndCatID(void* user_data) +{ + LLBuyData* buy = (LLBuyData*)user_data; + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + gMessageSystem->addUUIDFast(_PREHASH_GroupID, gAgent.getGroupID()); + gMessageSystem->addUUIDFast(_PREHASH_CategoryID, buy->mCategoryID); +} + +//static +void LLSelectMgr::packDeRezHeader(void* user_data) +{ + LLDeRezInfo* info = (LLDeRezInfo*)user_data; + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + gMessageSystem->nextBlockFast(_PREHASH_AgentBlock); + gMessageSystem->addUUIDFast(_PREHASH_GroupID, gAgent.getGroupID()); + gMessageSystem->addU8Fast(_PREHASH_Destination, (U8)info->mDestination); + gMessageSystem->addUUIDFast(_PREHASH_DestinationID, info->mDestinationID); + LLUUID tid; + tid.generate(); + gMessageSystem->addUUIDFast(_PREHASH_TransactionID, tid); + const U8 PACKET = 1; + gMessageSystem->addU8Fast(_PREHASH_PacketCount, PACKET); + gMessageSystem->addU8Fast(_PREHASH_PacketNumber, PACKET); +} + +// static +void LLSelectMgr::packObjectID(LLSelectNode* node, void *user_data) +{ + gMessageSystem->nextBlockFast(_PREHASH_ObjectData); + gMessageSystem->addUUIDFast(_PREHASH_ObjectID, node->getObject()->mID ); +} + +void LLSelectMgr::packObjectIDAndRotation(LLSelectNode* node, void *user_data) +{ + gMessageSystem->nextBlockFast(_PREHASH_ObjectData); + gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, node->getObject()->getLocalID() ); + gMessageSystem->addQuatFast(_PREHASH_Rotation, node->getObject()->getRotation()); +} + +void LLSelectMgr::packObjectClickAction(LLSelectNode* node, void *user_data) +{ + gMessageSystem->nextBlockFast(_PREHASH_ObjectData); + gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, node->getObject()->getLocalID() ); + gMessageSystem->addU8("ClickAction", node->getObject()->getClickAction()); +} + +void LLSelectMgr::packObjectIncludeInSearch(LLSelectNode* node, void *user_data) +{ + gMessageSystem->nextBlockFast(_PREHASH_ObjectData); + gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, node->getObject()->getLocalID() ); + gMessageSystem->addBOOL("IncludeInSearch", node->getObject()->getIncludeInSearch()); +} + +// static +void LLSelectMgr::packObjectLocalID(LLSelectNode* node, void *) +{ + gMessageSystem->nextBlockFast(_PREHASH_ObjectData); + gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, node->getObject()->getLocalID()); +} + +// static +void LLSelectMgr::packObjectName(LLSelectNode* node, void* user_data) +{ + const std::string* name = (const std::string*)user_data; + if(!name->empty()) + { + gMessageSystem->nextBlockFast(_PREHASH_ObjectData); + gMessageSystem->addU32Fast(_PREHASH_LocalID, node->getObject()->getLocalID()); + gMessageSystem->addStringFast(_PREHASH_Name, *name); + } +} + +// static +void LLSelectMgr::packObjectDescription(LLSelectNode* node, void* user_data) +{ + const std::string* desc = (const std::string*)user_data; + if(desc) + { // Empty (non-null, but zero length) descriptions are OK + gMessageSystem->nextBlockFast(_PREHASH_ObjectData); + gMessageSystem->addU32Fast(_PREHASH_LocalID, node->getObject()->getLocalID()); + gMessageSystem->addStringFast(_PREHASH_Description, *desc); + } +} + +// static +void LLSelectMgr::packObjectCategory(LLSelectNode* node, void* user_data) +{ + LLCategory* category = (LLCategory*)user_data; + if(!category) return; + gMessageSystem->nextBlockFast(_PREHASH_ObjectData); + gMessageSystem->addU32Fast(_PREHASH_LocalID, node->getObject()->getLocalID()); + category->packMessage(gMessageSystem); +} + +// static +void LLSelectMgr::packObjectSaleInfo(LLSelectNode* node, void* user_data) +{ + LLSaleInfo* sale_info = (LLSaleInfo*)user_data; + if(!sale_info) return; + gMessageSystem->nextBlockFast(_PREHASH_ObjectData); + gMessageSystem->addU32Fast(_PREHASH_LocalID, node->getObject()->getLocalID()); + sale_info->packMessage(gMessageSystem); +} + +// static +void LLSelectMgr::packPhysics(LLSelectNode* node, void *user_data) +{ +} + +// static +void LLSelectMgr::packShape(LLSelectNode* node, void *user_data) +{ +} + +// static +void LLSelectMgr::packPermissions(LLSelectNode* node, void *user_data) +{ + LLPermData *data = (LLPermData *)user_data; + + gMessageSystem->nextBlockFast(_PREHASH_ObjectData); + gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, node->getObject()->getLocalID()); + + gMessageSystem->addU8Fast(_PREHASH_Field, data->mField); + gMessageSystem->addBOOLFast(_PREHASH_Set, data->mSet); + gMessageSystem->addU32Fast(_PREHASH_Mask, data->mMask); +} + +// Utility function to send some information to every region containing +// an object on the selection list. We want to do this to reduce the total +// number of packets sent by the viewer. +void LLSelectMgr::sendListToRegions(const std::string& message_name, + void (*pack_header)(void *user_data), + void (*pack_body)(LLSelectNode* node, void *user_data), + void (*log_func)(LLSelectNode* node, void *user_data), + void *user_data, + ESendType send_type) +{ + sendListToRegions(mSelectedObjects, message_name, pack_header, pack_body, log_func, user_data, send_type); +} +void LLSelectMgr::sendListToRegions(LLObjectSelectionHandle selected_handle, + const std::string& message_name, + void (*pack_header)(void *user_data), + void (*pack_body)(LLSelectNode* node, void *user_data), + void (*log_func)(LLSelectNode* node, void *user_data), + void *user_data, + ESendType send_type) +{ + LLSelectNode* node; + LLSelectNode* linkset_root = NULL; + LLViewerRegion* last_region; + LLViewerRegion* current_region; + S32 objects_in_this_packet = 0; + + bool link_operation = message_name == "ObjectLink"; + + if (mAllowSelectAvatar) + { + if (selected_handle->getObjectCount() == 1 + && selected_handle->getFirstObject() != NULL + && selected_handle->getFirstObject()->isAvatar()) + { + // Server doesn't move avatars at the moment, it is a local debug feature, + // but server does update position regularly, so do not drop mLastPositionLocal + // Position override for avatar gets reset in LLAgentCamera::resetView(). + } + else + { + // drop mLastPositionLocal (allow next update through) + resetObjectOverrides(selected_handle); + } + } + else + { + //clear update override data (allow next update through) + resetObjectOverrides(selected_handle); + } + + std::queue nodes_to_send; + + struct push_all : public LLSelectedNodeFunctor + { + std::queue& nodes_to_send; + push_all(std::queue& n) : nodes_to_send(n) {} + virtual bool apply(LLSelectNode* node) + { + if (node->getObject()) + { + nodes_to_send.push(node); + } + return true; + } + }; + struct push_some : public LLSelectedNodeFunctor + { + std::queue& nodes_to_send; + bool mRoots; + push_some(std::queue& n, bool roots) : nodes_to_send(n), mRoots(roots) {} + virtual bool apply(LLSelectNode* node) + { + if (node->getObject()) + { + bool is_root = node->getObject()->isRootEdit(); + if ((mRoots && is_root) || (!mRoots && !is_root)) + { + nodes_to_send.push(node); + } + } + return true; + } + }; + struct push_all pushall(nodes_to_send); + struct push_some pushroots(nodes_to_send, true); + struct push_some pushnonroots(nodes_to_send, false); + + switch(send_type) + { + case SEND_ONLY_ROOTS: + if(message_name == "ObjectBuy") + selected_handle->applyToRootNodes(&pushroots); + else + selected_handle->applyToRootNodes(&pushall); + + break; + case SEND_INDIVIDUALS: + selected_handle->applyToNodes(&pushall); + break; + case SEND_ROOTS_FIRST: + // first roots... + selected_handle->applyToNodes(&pushroots); + // then children... + selected_handle->applyToNodes(&pushnonroots); + break; + case SEND_CHILDREN_FIRST: + // first children... + selected_handle->applyToNodes(&pushnonroots); + // then roots... + selected_handle->applyToNodes(&pushroots); + break; + + default: + LL_ERRS() << "Bad send type " << send_type << " passed to SendListToRegions()" << LL_ENDL; + } + + // bail if nothing selected + if (nodes_to_send.empty()) + { + return; + } + + node = nodes_to_send.front(); + nodes_to_send.pop(); + + // cache last region information + current_region = node->getObject()->getRegion(); + + // Start duplicate message + // CRO: this isn't + gMessageSystem->newMessage(message_name.c_str()); + (*pack_header)(user_data); + + // For each object + while (node != NULL) + { + // remember the last region, look up the current one + last_region = current_region; + current_region = node->getObject()->getRegion(); + + // if to same simulator and message not too big + if ((current_region == last_region) + && (! gMessageSystem->isSendFull(NULL)) + && (objects_in_this_packet < MAX_OBJECTS_PER_PACKET)) + { + if (link_operation && linkset_root == NULL) + { + // linksets over 254 will be split into multiple messages, + // but we need to provide same root for all messages or we will get separate linksets + linkset_root = node; + } + // add another instance of the body of the data + (*pack_body)(node, user_data); + // do any related logging + (*log_func)(node, user_data); + ++objects_in_this_packet; + + // and on to the next object + if(nodes_to_send.empty()) + { + node = NULL; + } + else + { + node = nodes_to_send.front(); + nodes_to_send.pop(); + } + } + else + { + // otherwise send current message and start new one + gMessageSystem->sendReliable( last_region->getHost()); + objects_in_this_packet = 0; + + gMessageSystem->newMessage(message_name.c_str()); + (*pack_header)(user_data); + + if (linkset_root != NULL) + { + if (current_region != last_region) + { + // root should be in one region with the child, reset it + linkset_root = NULL; + } + else + { + // add root instance into new message + (*pack_body)(linkset_root, user_data); + ++objects_in_this_packet; + } + } + + // don't move to the next object, we still need to add the + // body data. + } + } + + // flush messages + if (gMessageSystem->getCurrentSendTotal() > 0) + { + gMessageSystem->sendReliable( current_region->getHost()); + } + else + { + gMessageSystem->clearMessage(); + } + + // LL_INFOS() << "sendListToRegions " << message_name << " obj " << objects_sent << " pkt " << packets_sent << LL_ENDL; +} + + +// +// Network communications +// + +void LLSelectMgr::requestObjectPropertiesFamily(LLViewerObject* object) +{ + LLMessageSystem* msg = gMessageSystem; + + msg->newMessageFast(_PREHASH_RequestObjectPropertiesFamily); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ObjectData); + msg->addU32Fast(_PREHASH_RequestFlags, 0x0 ); + msg->addUUIDFast(_PREHASH_ObjectID, object->mID ); + + LLViewerRegion* regionp = object->getRegion(); + msg->sendReliable( regionp->getHost() ); +} + + +// static +void LLSelectMgr::processObjectProperties(LLMessageSystem* msg, void** user_data) +{ + S32 i; + S32 count = msg->getNumberOfBlocksFast(_PREHASH_ObjectData); + for (i = 0; i < count; i++) + { + LLUUID id; + msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_ObjectID, id, i); + + LLUUID creator_id; + LLUUID owner_id; + LLUUID group_id; + LLUUID last_owner_id; + U64 creation_date; + LLUUID extra_id; + U32 base_mask, owner_mask, group_mask, everyone_mask, next_owner_mask; + LLSaleInfo sale_info; + LLCategory category; + LLAggregatePermissions ag_perms; + LLAggregatePermissions ag_texture_perms; + LLAggregatePermissions ag_texture_perms_owner; + + msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_CreatorID, creator_id, i); + msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_OwnerID, owner_id, i); + msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_GroupID, group_id, i); + msg->getU64Fast(_PREHASH_ObjectData, _PREHASH_CreationDate, creation_date, i); + msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_BaseMask, base_mask, i); + msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_OwnerMask, owner_mask, i); + msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_GroupMask, group_mask, i); + msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_EveryoneMask, everyone_mask, i); + msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_NextOwnerMask, next_owner_mask, i); + sale_info.unpackMultiMessage(msg, _PREHASH_ObjectData, i); + + ag_perms.unpackMessage(msg, _PREHASH_ObjectData, _PREHASH_AggregatePerms, i); + ag_texture_perms.unpackMessage(msg, _PREHASH_ObjectData, _PREHASH_AggregatePermTextures, i); + ag_texture_perms_owner.unpackMessage(msg, _PREHASH_ObjectData, _PREHASH_AggregatePermTexturesOwner, i); + category.unpackMultiMessage(msg, _PREHASH_ObjectData, i); + + S16 inv_serial = 0; + msg->getS16Fast(_PREHASH_ObjectData, _PREHASH_InventorySerial, inv_serial, i); + + LLUUID item_id; + msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_ItemID, item_id, i); + LLUUID folder_id; + msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_FolderID, folder_id, i); + LLUUID from_task_id; + msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_FromTaskID, from_task_id, i); + + msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_LastOwnerID, last_owner_id, i); + + std::string name; + msg->getStringFast(_PREHASH_ObjectData, _PREHASH_Name, name, i); + std::string desc; + msg->getStringFast(_PREHASH_ObjectData, _PREHASH_Description, desc, i); + + std::string touch_name; + msg->getStringFast(_PREHASH_ObjectData, _PREHASH_TouchName, touch_name, i); + std::string sit_name; + msg->getStringFast(_PREHASH_ObjectData, _PREHASH_SitName, sit_name, i); + + //unpack TE IDs + uuid_vec_t texture_ids; + S32 size = msg->getSizeFast(_PREHASH_ObjectData, i, _PREHASH_TextureID); + if (size > 0) + { + S8 packed_buffer[SELECT_MAX_TES * UUID_BYTES]; + msg->getBinaryDataFast(_PREHASH_ObjectData, _PREHASH_TextureID, packed_buffer, 0, i, SELECT_MAX_TES * UUID_BYTES); + + for (S32 buf_offset = 0; buf_offset < size; buf_offset += UUID_BYTES) + { + LLUUID tid; + memcpy(tid.mData, packed_buffer + buf_offset, UUID_BYTES); /* Flawfinder: ignore */ + texture_ids.push_back(tid); + } + } + + + // Iterate through nodes at end, since it can be on both the regular AND hover list + struct f : public LLSelectedNodeFunctor + { + LLUUID mID; + f(const LLUUID& id) : mID(id) {} + virtual bool apply(LLSelectNode* node) + { + return (node->getObject() && node->getObject()->mID == mID); + } + } func(id); + LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode(&func); + + if (!node) + { + LL_WARNS() << "Couldn't find object " << id << " selected." << LL_ENDL; + } + else + { + // save texture data as soon as we get texture perms first time + bool save_textures = !node->mValid; + if (node->mInventorySerial != inv_serial && node->getObject()) + { + node->getObject()->dirtyInventory(); + + // Even if this isn't object's first udpate, inventory changed + // and some of the applied textures might have been in inventory + // so update texture list. + save_textures = true; + } + + if (save_textures) + { + bool can_copy = false; + bool can_transfer = false; + + LLAggregatePermissions::EValue value = LLAggregatePermissions::AP_NONE; + if(node->getObject()->permYouOwner()) + { + value = ag_texture_perms_owner.getValue(PERM_COPY); + if (value == LLAggregatePermissions::AP_EMPTY || value == LLAggregatePermissions::AP_ALL) + { + can_copy = true; + } + value = ag_texture_perms_owner.getValue(PERM_TRANSFER); + if (value == LLAggregatePermissions::AP_EMPTY || value == LLAggregatePermissions::AP_ALL) + { + can_transfer = true; + } + } + else + { + value = ag_texture_perms.getValue(PERM_COPY); + if (value == LLAggregatePermissions::AP_EMPTY || value == LLAggregatePermissions::AP_ALL) + { + can_copy = true; + } + value = ag_texture_perms.getValue(PERM_TRANSFER); + if (value == LLAggregatePermissions::AP_EMPTY || value == LLAggregatePermissions::AP_ALL) + { + can_transfer = true; + } + } + + if (can_copy && can_transfer) + { + node->saveTextures(texture_ids); + } + + if (can_copy && can_transfer && node->getObject()->getVolume()) + { + uuid_vec_t material_ids; + gltf_materials_vec_t override_materials; + LLVOVolume* vobjp = (LLVOVolume*)node->getObject(); + for (int i = 0; i < vobjp->getNumTEs(); ++i) + { + material_ids.push_back(vobjp->getRenderMaterialID(i)); + + // Make a copy to ensure we won't affect live material + // with any potential changes nor live changes will be + // reflected in a saved copy. + // Like changes from local material (reuses pointer) or + // from live editor (revert mechanics might modify this) + LLGLTFMaterial* old_override = node->getObject()->getTE(i)->getGLTFMaterialOverride(); + if (old_override) + { + LLPointer mat = new LLGLTFMaterial(*old_override); + override_materials.push_back(mat); + } + else + { + override_materials.push_back(nullptr); + } + } + // processObjectProperties does not include overrides so this + // might need to be moved to LLGLTFMaterialOverrideDispatchHandler + node->saveGLTFMaterials(material_ids, override_materials); + } + } + + node->mValid = true; + node->mPermissions->init(creator_id, owner_id, + last_owner_id, group_id); + node->mPermissions->initMasks(base_mask, owner_mask, everyone_mask, group_mask, next_owner_mask); + node->mCreationDate = creation_date; + node->mItemID = item_id; + node->mFolderID = folder_id; + node->mFromTaskID = from_task_id; + node->mName.assign(name); + node->mDescription.assign(desc); + node->mSaleInfo = sale_info; + node->mAggregatePerm = ag_perms; + node->mAggregateTexturePerm = ag_texture_perms; + node->mAggregateTexturePermOwner = ag_texture_perms_owner; + node->mCategory = category; + node->mInventorySerial = inv_serial; + node->mSitName.assign(sit_name); + node->mTouchName.assign(touch_name); + } + } + + dialog_refresh_all(); + + // hack for left-click buy object + LLToolPie::selectionPropertiesReceived(); +} + +// static +void LLSelectMgr::processObjectPropertiesFamily(LLMessageSystem* msg, void** user_data) +{ + LLUUID id; + + U32 request_flags; + LLUUID creator_id; + LLUUID owner_id; + LLUUID group_id; + LLUUID extra_id; + U32 base_mask, owner_mask, group_mask, everyone_mask, next_owner_mask; + LLSaleInfo sale_info; + LLCategory category; + + msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_RequestFlags, request_flags ); + msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_ObjectID, id ); + msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_OwnerID, owner_id ); + msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_GroupID, group_id ); + msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_BaseMask, base_mask ); + msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_OwnerMask, owner_mask ); + msg->getU32Fast(_PREHASH_ObjectData,_PREHASH_GroupMask, group_mask ); + msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_EveryoneMask, everyone_mask ); + msg->getU32Fast(_PREHASH_ObjectData, _PREHASH_NextOwnerMask, next_owner_mask); + sale_info.unpackMessage(msg, _PREHASH_ObjectData); + category.unpackMessage(msg, _PREHASH_ObjectData); + + LLUUID last_owner_id; + msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_LastOwnerID, last_owner_id ); + + // unpack name & desc + std::string name; + msg->getStringFast(_PREHASH_ObjectData, _PREHASH_Name, name); + + std::string desc; + msg->getStringFast(_PREHASH_ObjectData, _PREHASH_Description, desc); + + // the reporter widget askes the server for info about picked objects + if (request_flags & COMPLAINT_REPORT_REQUEST ) + { + LLFloaterReporter *reporterp = LLFloaterReg::findTypedInstance("reporter"); + if (reporterp) + { + LLAvatarName av_name; + LLAvatarNameCache::get(owner_id, &av_name); + reporterp->setPickedObjectProperties(name, av_name.getUserName(), owner_id); + } + } + else if (request_flags & OBJECT_PAY_REQUEST) + { + // check if the owner of the paid object is muted + LLMuteList::getInstance()->autoRemove(owner_id, LLMuteList::AR_MONEY); + } + + // Now look through all of the hovered nodes + struct f : public LLSelectedNodeFunctor + { + LLUUID mID; + f(const LLUUID& id) : mID(id) {} + virtual bool apply(LLSelectNode* node) + { + return (node->getObject() && node->getObject()->mID == mID); + } + } func(id); + LLSelectNode* node = LLSelectMgr::getInstance()->mHoverObjects->getFirstNode(&func); + + if (node) + { + node->mValid = true; + node->mPermissions->init(LLUUID::null, owner_id, + last_owner_id, group_id); + node->mPermissions->initMasks(base_mask, owner_mask, everyone_mask, group_mask, next_owner_mask); + node->mSaleInfo = sale_info; + node->mCategory = category; + node->mName.assign(name); + node->mDescription.assign(desc); + } + + dialog_refresh_all(); +} + + +// static +void LLSelectMgr::processForceObjectSelect(LLMessageSystem* msg, void**) +{ + bool reset_list; + msg->getBOOL("Header", "ResetList", reset_list); + + if (reset_list) + { + LLSelectMgr::getInstance()->deselectAll(); + } + + LLUUID full_id; + S32 local_id; + LLViewerObject* object; + std::vector objects; + S32 i; + S32 block_count = msg->getNumberOfBlocks("Data"); + + for (i = 0; i < block_count; i++) + { + msg->getS32("Data", "LocalID", local_id, i); + + gObjectList.getUUIDFromLocal(full_id, + local_id, + msg->getSenderIP(), + msg->getSenderPort()); + object = gObjectList.findObject(full_id); + if (object) + { + objects.push_back(object); + } + } + + // Don't select, just highlight + LLSelectMgr::getInstance()->highlightObjectAndFamily(objects); +} + +extern F32 gGLModelView[16]; + +void LLSelectMgr::updateSilhouettes() +{ + S32 num_sils_genned = 0; + + LLVector3d cameraPos = gAgentCamera.getCameraPositionGlobal(); + F32 currentCameraZoom = gAgentCamera.getCurrentCameraBuildOffset(); + + if (!mSilhouetteImagep) + { + mSilhouetteImagep = LLViewerTextureManager::getFetchedTextureFromFile("silhouette.j2c", FTT_LOCAL_FILE, true, LLGLTexture::BOOST_UI); + } + + mHighlightedObjects->cleanupNodes(); + + if((cameraPos - mLastCameraPos).magVecSquared() > SILHOUETTE_UPDATE_THRESHOLD_SQUARED * currentCameraZoom * currentCameraZoom) + { + struct f : public LLSelectedObjectFunctor + { + virtual bool apply(LLViewerObject* object) + { + object->setChanged(LLXform::SILHOUETTE); + return true; + } + } func; + getSelection()->applyToObjects(&func); + + mLastCameraPos = gAgentCamera.getCameraPositionGlobal(); + } + + std::vector changed_objects; + + updateSelectionSilhouette(mSelectedObjects, num_sils_genned, changed_objects); + if (mRectSelectedObjects.size() > 0) + { + //gGLSPipelineSelection.set(); + + //mSilhouetteImagep->bindTexture(); + //glAlphaFunc(GL_GREATER, sHighlightAlphaTest); + + std::set roots; + + // sync mHighlightedObjects with mRectSelectedObjects since the latter is rebuilt every frame and former + // persists from frame to frame to avoid regenerating object silhouettes + // mHighlightedObjects includes all siblings of rect selected objects + + bool select_linked_set = !gSavedSettings.getBOOL("EditLinkedParts"); + + // generate list of roots from current object selection + for (std::set >::iterator iter = mRectSelectedObjects.begin(); + iter != mRectSelectedObjects.end(); iter++) + { + LLViewerObject *objectp = *iter; + if (select_linked_set) + { + LLViewerObject *rootp = (LLViewerObject*)objectp->getRoot(); + roots.insert(rootp); + } + else + { + roots.insert(objectp); + } + } + + // remove highlight nodes not in roots list + std::vector remove_these_nodes; + std::vector remove_these_roots; + + for (LLObjectSelection::iterator iter = mHighlightedObjects->begin(); + iter != mHighlightedObjects->end(); iter++) + { + LLSelectNode* node = *iter; + LLViewerObject* objectp = node->getObject(); + if (!objectp) + continue; + if (objectp->isRoot() || !select_linked_set) + { + if (roots.count(objectp) == 0) + { + remove_these_nodes.push_back(node); + } + else + { + remove_these_roots.push_back(objectp); + } + } + else + { + LLViewerObject* rootp = (LLViewerObject*)objectp->getRoot(); + + if (roots.count(rootp) == 0) + { + remove_these_nodes.push_back(node); + } + } + } + + // remove all highlight nodes no longer in rectangle selection + for (std::vector::iterator iter = remove_these_nodes.begin(); + iter != remove_these_nodes.end(); ++iter) + { + LLSelectNode* nodep = *iter; + mHighlightedObjects->removeNode(nodep); + } + + // remove all root objects already being highlighted + for (std::vector::iterator iter = remove_these_roots.begin(); + iter != remove_these_roots.end(); ++iter) + { + LLViewerObject* objectp = *iter; + roots.erase(objectp); + } + + // add all new objects in rectangle selection + for (std::set::iterator iter = roots.begin(); + iter != roots.end(); iter++) + { + LLViewerObject* objectp = *iter; + if (!canSelectObject(objectp)) + { + continue; + } + + LLSelectNode* rect_select_root_node = new LLSelectNode(objectp, true); + rect_select_root_node->selectAllTEs(true); + + if (!select_linked_set) + { + rect_select_root_node->mIndividualSelection = true; + } + else + { + LLViewerObject::const_child_list_t& child_list = objectp->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); iter++) + { + LLViewerObject* child_objectp = *iter; + + if (!canSelectObject(child_objectp)) + { + continue; + } + + LLSelectNode* rect_select_node = new LLSelectNode(child_objectp, true); + rect_select_node->selectAllTEs(true); + mHighlightedObjects->addNodeAtEnd(rect_select_node); + } + } + + // Add the root last, to preserve order for link operations. + mHighlightedObjects->addNodeAtEnd(rect_select_root_node); + } + + num_sils_genned = 0; + + // render silhouettes for highlighted objects + //bool subtracting_from_selection = (gKeyboard->currentMask(true) == MASK_CONTROL); + for (S32 pass = 0; pass < 2; pass++) + { + for (LLObjectSelection::iterator iter = mHighlightedObjects->begin(); + iter != mHighlightedObjects->end(); iter++) + { + LLSelectNode* node = *iter; + LLViewerObject* objectp = node->getObject(); + if (!objectp) + continue; + + // do roots first, then children so that root flags are cleared ASAP + bool roots_only = (pass == 0); + bool is_root = objectp->isRootEdit(); + if (roots_only != is_root) + { + continue; + } + + if (!node->mSilhouetteExists + || objectp->isChanged(LLXform::SILHOUETTE) + || (objectp->getParent() && objectp->getParent()->isChanged(LLXform::SILHOUETTE))) + { + if (num_sils_genned++ < MAX_SILS_PER_FRAME) + { + generateSilhouette(node, LLViewerCamera::getInstance()->getOrigin()); + changed_objects.push_back(objectp); + } + else if (objectp->isAttachment() && objectp->getRootEdit()->mDrawable.notNull()) + { + //RN: hack for orthogonal projection of HUD attachments + LLViewerJointAttachment* attachment_pt = (LLViewerJointAttachment*)objectp->getRootEdit()->mDrawable->getParent(); + if (attachment_pt && attachment_pt->getIsHUDAttachment()) + { + LLVector3 camera_pos = LLVector3(-10000.f, 0.f, 0.f); + generateSilhouette(node, camera_pos); + } + } + } + //LLColor4 highlight_color; + // + //if (subtracting_from_selection) + //{ + // node->renderOneSilhouette(LLColor4::red); + //} + //else if (!objectp->isSelected()) + //{ + // highlight_color = objectp->isRoot() ? sHighlightParentColor : sHighlightChildColor; + // node->renderOneSilhouette(highlight_color); + //} + } + } + //mSilhouetteImagep->unbindTexture(0, GL_TEXTURE_2D); + } + else + { + mHighlightedObjects->deleteAllNodes(); + } + + for (std::vector::iterator iter = changed_objects.begin(); + iter != changed_objects.end(); ++iter) + { + // clear flags after traversing node list (as child objects need to refer to parent flags, etc) + LLViewerObject* objectp = *iter; + objectp->clearChanged(LLXform::MOVED | LLXform::SILHOUETTE); + } +} + +void LLSelectMgr::updateSelectionSilhouette(LLObjectSelectionHandle object_handle, S32& num_sils_genned, std::vector& changed_objects) +{ + if (object_handle->getNumNodes()) + { + //gGLSPipelineSelection.set(); + + //mSilhouetteImagep->bindTexture(); + //glAlphaFunc(GL_GREATER, sHighlightAlphaTest); + + for (S32 pass = 0; pass < 2; pass++) + { + for (LLObjectSelection::iterator iter = object_handle->begin(); + iter != object_handle->end(); iter++) + { + LLSelectNode* node = *iter; + LLViewerObject* objectp = node->getObject(); + if (!objectp) + continue; + // do roots first, then children so that root flags are cleared ASAP + bool roots_only = (pass == 0); + bool is_root = (objectp->isRootEdit()); + if (roots_only != is_root || objectp->mDrawable.isNull()) + { + continue; + } + + if (!node->mSilhouetteExists + || objectp->isChanged(LLXform::SILHOUETTE) + || (objectp->getParent() && objectp->getParent()->isChanged(LLXform::SILHOUETTE))) + { + if (num_sils_genned++ < MAX_SILS_PER_FRAME)// && objectp->mDrawable->isVisible()) + { + generateSilhouette(node, LLViewerCamera::getInstance()->getOrigin()); + changed_objects.push_back(objectp); + } + else if (objectp->isAttachment()) + { + //RN: hack for orthogonal projection of HUD attachments + LLViewerJointAttachment* attachment_pt = (LLViewerJointAttachment*)objectp->getRootEdit()->mDrawable->getParent(); + if (attachment_pt && attachment_pt->getIsHUDAttachment()) + { + LLVector3 camera_pos = LLVector3(-10000.f, 0.f, 0.f); + generateSilhouette(node, camera_pos); + } + } + } + } + } + } +} +void LLSelectMgr::renderSilhouettes(bool for_hud) +{ + if (!mRenderSilhouettes || !mRenderHighlightSelections) + { + return; + } + + gGL.getTexUnit(0)->bind(mSilhouetteImagep); + LLGLSPipelineSelection gls_select; + LLGLEnable blend(GL_BLEND); + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); + + if (isAgentAvatarValid() && for_hud) + { + LLBBox hud_bbox = gAgentAvatarp->getHUDBBox(); + + F32 cur_zoom = gAgentCamera.mHUDCurZoom; + + // set up transform to encompass bounding box of HUD + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.pushMatrix(); + gGL.loadIdentity(); + F32 depth = llmax(1.f, hud_bbox.getExtentLocal().mV[VX] * 1.1f); + gGL.ortho(-0.5f * LLViewerCamera::getInstance()->getAspect(), 0.5f * LLViewerCamera::getInstance()->getAspect(), -0.5f, 0.5f, 0.f, depth); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + gGL.pushUIMatrix(); + gGL.loadUIIdentity(); + gGL.loadIdentity(); + gGL.loadMatrix(OGL_TO_CFR_ROTATION); // Load Cory's favorite reference frame + gGL.translatef(-hud_bbox.getCenterLocal().mV[VX] + (depth *0.5f), 0.f, 0.f); + gGL.scalef(cur_zoom, cur_zoom, cur_zoom); + } + + bool wireframe_selection = (gFloaterTools && gFloaterTools->getVisible()) || LLSelectMgr::sRenderHiddenSelections; + F32 fogCfx = (F32)llclamp((LLSelectMgr::getInstance()->getSelectionCenterGlobal() - gAgentCamera.getCameraPositionGlobal()).magVec() / (LLSelectMgr::getInstance()->getBBoxOfSelection().getExtentLocal().magVec() * 4), 0.0, 1.0); + + static LLColor4 sParentColor = LLColor4(sSilhouetteParentColor[VRED], sSilhouetteParentColor[VGREEN], sSilhouetteParentColor[VBLUE], LLSelectMgr::sHighlightAlpha); + static LLColor4 sChildColor = LLColor4(sSilhouetteChildColor[VRED], sSilhouetteChildColor[VGREEN], sSilhouetteChildColor[VBLUE], LLSelectMgr::sHighlightAlpha); + + auto renderMeshSelection_f = [fogCfx, wireframe_selection](LLSelectNode* node, LLViewerObject* objectp, LLColor4 hlColor) + { + LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; + + if (shader) + { + gDebugProgram.bind(); + } + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + + bool is_hud_object = objectp->isHUDAttachment(); + + if (!is_hud_object) + { + gGL.loadIdentity(); + gGL.multMatrix(gGLModelView); + } + + if (objectp->mDrawable->isActive()) + { + gGL.multMatrix((F32*)objectp->getRenderMatrix().mMatrix); + } + else if (!is_hud_object) + { + LLVector3 trans = objectp->getRegion()->getOriginAgent(); + gGL.translatef(trans.mV[0], trans.mV[1], trans.mV[2]); + } + + bool bRenderHidenSelection = node->isTransient() ? false : LLSelectMgr::sRenderHiddenSelections; + + + LLVOVolume* vobj = objectp->mDrawable->getVOVolume(); + if (vobj) + { + LLVertexBuffer::unbind(); + gGL.pushMatrix(); + gGL.multMatrix((F32*)vobj->getRelativeXform().mMatrix); + + if (objectp->mDrawable->isState(LLDrawable::RIGGED)) + { + vobj->updateRiggedVolume(true); + } + } + + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + + S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces()); // avatars have TEs but no faces + for (S32 te = 0; te < num_tes; ++te) + { + if (node->isTESelected(te)) + { + objectp->mDrawable->getFace(te)->renderOneWireframe(hlColor, fogCfx, wireframe_selection, bRenderHidenSelection, nullptr != shader); + } + } + + gGL.popMatrix(); + gGL.popMatrix(); + + glLineWidth(1.f); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + + if (shader) + { + shader->bind(); + } + }; + + if (mSelectedObjects->getNumNodes()) + { + LLUUID inspect_item_id= LLUUID::null; + LLFloaterInspect* inspect_instance = LLFloaterReg::getTypedInstance("inspect"); + if(inspect_instance && inspect_instance->getVisible()) + { + inspect_item_id = inspect_instance->getSelectedUUID(); + } + else + { + LLSidepanelTaskInfo *panel_task_info = LLSidepanelTaskInfo::getActivePanel(); + if (panel_task_info) + { + inspect_item_id = panel_task_info->getSelectedUUID(); + } + } + + LLUUID focus_item_id = LLViewerMediaFocus::getInstance()->getFocusedObjectID(); + for (S32 pass = 0; pass < 2; pass++) + { + for (LLObjectSelection::iterator iter = mSelectedObjects->begin(); + iter != mSelectedObjects->end(); iter++) + { + LLSelectNode* node = *iter; + + if (getTEMode() && !node->hasSelectedTE()) + continue; + + LLViewerObject* objectp = node->getObject(); + if (!objectp) + continue; + + if (objectp->mDrawable + && objectp->mDrawable->getVOVolume() + && objectp->mDrawable->getVOVolume()->isMesh()) + { + LLColor4 hlColor = objectp->isRootEdit() ? sParentColor : sChildColor; + if (objectp->getID() == inspect_item_id) + { + hlColor = sHighlightInspectColor; + } + else if (node->isTransient()) + { + hlColor = sContextSilhouetteColor; + } + renderMeshSelection_f(node, objectp, hlColor); + } + else + { + if (objectp->isHUDAttachment() != for_hud) + { + continue; + } + if (objectp->getID() == focus_item_id) + { + node->renderOneSilhouette(gFocusMgr.getFocusColor()); + } + else if (objectp->getID() == inspect_item_id) + { + node->renderOneSilhouette(sHighlightInspectColor); + } + else if (node->isTransient()) + { + bool oldHidden = LLSelectMgr::sRenderHiddenSelections; + LLSelectMgr::sRenderHiddenSelections = false; + node->renderOneSilhouette(sContextSilhouetteColor); + LLSelectMgr::sRenderHiddenSelections = oldHidden; + } + else if (objectp->isRootEdit()) + { + node->renderOneSilhouette(sSilhouetteParentColor); + } + else + { + node->renderOneSilhouette(sSilhouetteChildColor); + } + } + } //for all selected node's + } //for pass + } + + if (mHighlightedObjects->getNumNodes()) + { + // render silhouettes for highlighted objects + bool subtracting_from_selection = (gKeyboard->currentMask(true) == MASK_CONTROL); + for (S32 pass = 0; pass < 2; pass++) + { + for (LLObjectSelection::iterator iter = mHighlightedObjects->begin(); + iter != mHighlightedObjects->end(); iter++) + { + LLSelectNode* node = *iter; + LLViewerObject* objectp = node->getObject(); + if (!objectp) + continue; + if (objectp->isHUDAttachment() != for_hud) + { + continue; + } + + LLColor4 highlight_color = objectp->isRoot() ? sHighlightParentColor : sHighlightChildColor; + if (objectp->mDrawable + && objectp->mDrawable->getVOVolume() + && objectp->mDrawable->getVOVolume()->isMesh()) + { + renderMeshSelection_f(node, objectp, subtracting_from_selection ? LLColor4::red : highlight_color); + } + else if (subtracting_from_selection) + { + node->renderOneSilhouette(LLColor4::red); + } + else if (!objectp->isSelected()) + { + node->renderOneSilhouette(highlight_color); + } + } + } + } + + if (isAgentAvatarValid() && for_hud) + { + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.popMatrix(); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.popMatrix(); + gGL.popUIMatrix(); + stop_glerror(); + } + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); +} + +void LLSelectMgr::generateSilhouette(LLSelectNode* nodep, const LLVector3& view_point) +{ + LLViewerObject* objectp = nodep->getObject(); + + if (objectp && objectp->getPCode() == LL_PCODE_VOLUME) + { + ((LLVOVolume*)objectp)->generateSilhouette(nodep, view_point); + } +} + +// +// Utility classes +// +LLSelectNode::LLSelectNode(LLViewerObject* object, bool glow) +: mObject(object), + mIndividualSelection(false), + mTransient(false), + mValid(false), + mPermissions(new LLPermissions()), + mInventorySerial(0), + mSilhouetteExists(false), + mDuplicated(false), + mTESelectMask(0), + mLastTESelected(0), + mName(LLStringUtil::null), + mDescription(LLStringUtil::null), + mTouchName(LLStringUtil::null), + mSitName(LLStringUtil::null), + mCreationDate(0) +{ + saveColors(); + saveShinyColors(); +} + +LLSelectNode::LLSelectNode(const LLSelectNode& nodep) +{ + mTESelectMask = nodep.mTESelectMask; + mLastTESelected = nodep.mLastTESelected; + + mIndividualSelection = nodep.mIndividualSelection; + + mValid = nodep.mValid; + mTransient = nodep.mTransient; + mPermissions = new LLPermissions(*nodep.mPermissions); + mSaleInfo = nodep.mSaleInfo;; + mAggregatePerm = nodep.mAggregatePerm; + mAggregateTexturePerm = nodep.mAggregateTexturePerm; + mAggregateTexturePermOwner = nodep.mAggregateTexturePermOwner; + mName = nodep.mName; + mDescription = nodep.mDescription; + mCategory = nodep.mCategory; + mInventorySerial = 0; + mSavedPositionLocal = nodep.mSavedPositionLocal; + mSavedPositionGlobal = nodep.mSavedPositionGlobal; + mSavedScale = nodep.mSavedScale; + mSavedRotation = nodep.mSavedRotation; + mDuplicated = nodep.mDuplicated; + mDuplicatePos = nodep.mDuplicatePos; + mDuplicateRot = nodep.mDuplicateRot; + mItemID = nodep.mItemID; + mFolderID = nodep.mFolderID; + mFromTaskID = nodep.mFromTaskID; + mTouchName = nodep.mTouchName; + mSitName = nodep.mSitName; + mCreationDate = nodep.mCreationDate; + + mSilhouetteVertices = nodep.mSilhouetteVertices; + mSilhouetteNormals = nodep.mSilhouetteNormals; + mSilhouetteExists = nodep.mSilhouetteExists; + mObject = nodep.mObject; + + std::vector::const_iterator color_iter; + mSavedColors.clear(); + for (color_iter = nodep.mSavedColors.begin(); color_iter != nodep.mSavedColors.end(); ++color_iter) + { + mSavedColors.push_back(*color_iter); + } + mSavedShinyColors.clear(); + for (color_iter = nodep.mSavedShinyColors.begin(); color_iter != nodep.mSavedShinyColors.end(); ++color_iter) + { + mSavedShinyColors.push_back(*color_iter); + } + + saveTextures(nodep.mSavedTextures); + saveGLTFMaterials(nodep.mSavedGLTFMaterialIds, nodep.mSavedGLTFOverrideMaterials); +} + +LLSelectNode::~LLSelectNode() +{ + LLSelectMgr *manager = LLSelectMgr::getInstance(); + if (manager->mAllowSelectAvatar + && (!mLastPositionLocal.isExactlyZero() + || mLastRotation != LLQuaternion())) + { + LLViewerObject* object = getObject(); //isDead() check + if (object && !object->getParent()) + { + LLVOAvatar* avatar = object->asAvatar(); + if (avatar) + { + // Avatar was moved and needs to stay that way + manager->mAvatarOverridesMap.emplace(avatar->getID(), LLSelectMgr::AvatarPositionOverride(mLastPositionLocal, mLastRotation, object)); + } + } + } + + + delete mPermissions; + mPermissions = NULL; +} + +void LLSelectNode::selectAllTEs(bool b) +{ + mTESelectMask = b ? TE_SELECT_MASK_ALL : 0x0; + mLastTESelected = 0; +} + +void LLSelectNode::selectTE(S32 te_index, bool selected) +{ + if (te_index < 0 || te_index >= SELECT_MAX_TES) + { + return; + } + S32 mask = 0x1 << te_index; + if(selected) + { + mTESelectMask |= mask; + } + else + { + mTESelectMask &= ~mask; + } + mLastTESelected = te_index; +} + +bool LLSelectNode::isTESelected(S32 te_index) const +{ + if (te_index < 0 || te_index >= mObject->getNumTEs()) + { + return false; + } + return (mTESelectMask & (0x1 << te_index)) != 0; +} + +S32 LLSelectNode::getLastSelectedTE() const +{ + if (!isTESelected(mLastTESelected)) + { + return -1; + } + return mLastTESelected; +} + +LLViewerObject* LLSelectNode::getObject() +{ + if (!mObject) + { + return NULL; + } + else if (mObject->isDead()) + { + mObject = NULL; + } + return mObject; +} + +void LLSelectNode::setObject(LLViewerObject* object) +{ + mObject = object; +} + +void LLSelectNode::saveColors() +{ + if (mObject.notNull()) + { + mSavedColors.clear(); + for (S32 i = 0; i < mObject->getNumTEs(); i++) + { + const LLTextureEntry* tep = mObject->getTE(i); + mSavedColors.push_back(tep->getColor()); + } + } +} + +void LLSelectNode::saveShinyColors() +{ + if (mObject.notNull()) + { + mSavedShinyColors.clear(); + for (S32 i = 0; i < mObject->getNumTEs(); i++) + { + const LLMaterialPtr mat = mObject->getTE(i)->getMaterialParams(); + if (!mat.isNull()) + { + mSavedShinyColors.push_back(mat->getSpecularLightColor()); + } + else + { + mSavedShinyColors.push_back(LLColor4::white); + } + } + } +} + +void LLSelectNode::saveTextures(const uuid_vec_t& textures) +{ + if (mObject.notNull()) + { + mSavedTextures.clear(); + + for (uuid_vec_t::const_iterator texture_it = textures.begin(); + texture_it != textures.end(); ++texture_it) + { + mSavedTextures.push_back(*texture_it); + } + } +} + +void LLSelectNode::saveGLTFMaterials(const uuid_vec_t& materials, const gltf_materials_vec_t& override_materials) +{ + if (mObject.notNull()) + { + mSavedGLTFMaterialIds.clear(); + mSavedGLTFOverrideMaterials.clear(); + + for (uuid_vec_t::const_iterator materials_it = materials.begin(); + materials_it != materials.end(); ++materials_it) + { + mSavedGLTFMaterialIds.push_back(*materials_it); + } + + for (gltf_materials_vec_t::const_iterator mat_it = override_materials.begin(); + mat_it != override_materials.end(); ++mat_it) + { + mSavedGLTFOverrideMaterials.push_back(*mat_it); + } + } +} + +void LLSelectNode::saveTextureScaleRatios(LLRender::eTexIndex index_to_query) +{ + mTextureScaleRatios.clear(); + + if (mObject.notNull()) + { + + LLVector3 scale = mObject->getScale(); + + for (U8 i = 0; i < mObject->getNumTEs(); i++) + { + F32 diffuse_s = 1.0f; + F32 diffuse_t = 1.0f; + + LLVector3 v; + const LLTextureEntry* tep = mObject->getTE(i); + if (!tep) + continue; + + U32 s_axis = VX; + U32 t_axis = VY; + LLPrimitive::getTESTAxes(i, &s_axis, &t_axis); + + tep->getScale(&diffuse_s,&diffuse_t); + + if (tep->getTexGen() == LLTextureEntry::TEX_GEN_PLANAR) + { + v.mV[s_axis] = diffuse_s*scale.mV[s_axis]; + v.mV[t_axis] = diffuse_t*scale.mV[t_axis]; + mTextureScaleRatios.push_back(v); + } + else + { + v.mV[s_axis] = diffuse_s/scale.mV[s_axis]; + v.mV[t_axis] = diffuse_t/scale.mV[t_axis]; + mTextureScaleRatios.push_back(v); + } + } + } +} + + +// This implementation should be similar to LLTask::allowOperationOnTask +bool LLSelectNode::allowOperationOnNode(PermissionBit op, U64 group_proxy_power) const +{ + // Extract ownership. + bool object_is_group_owned = false; + LLUUID object_owner_id; + mPermissions->getOwnership(object_owner_id, object_is_group_owned); + + // Operations on invalid or public objects is not allowed. + if (!mObject || (mObject->isDead()) || !mPermissions->isOwned()) + { + return false; + } + + // The transfer permissions can never be given through proxy. + if (PERM_TRANSFER == op) + { + // The owner of an agent-owned object can transfer to themselves. + if ( !object_is_group_owned + && (gAgent.getID() == object_owner_id) ) + { + return true; + } + else + { + // Otherwise check aggregate permissions. + return mObject->permTransfer(); + } + } + + if (PERM_MOVE == op + || PERM_MODIFY == op) + { + // only owners can move or modify their attachments + // no proxy allowed. + if (mObject->isAttachment() && object_owner_id != gAgent.getID()) + { + return false; + } + } + + // Calculate proxy_agent_id and group_id to use for permissions checks. + // proxy_agent_id may be set to the object owner through group powers. + // group_id can only be set to the object's group, if the agent is in that group. + LLUUID group_id = LLUUID::null; + LLUUID proxy_agent_id = gAgent.getID(); + + // Gods can always operate. + if (gAgent.isGodlike()) + { + return true; + } + + // Check if the agent is in the same group as the object. + LLUUID object_group_id = mPermissions->getGroup(); + if (object_group_id.notNull() && + gAgent.isInGroup(object_group_id)) + { + // Assume the object's group during this operation. + group_id = object_group_id; + } + + // Only allow proxy powers for PERM_COPY if the actual agent can + // receive the item (ie has PERM_TRANSFER permissions). + // NOTE: op == PERM_TRANSFER has already been handled, but if + // that ever changes we need to BLOCK proxy powers for PERM_TRANSFER. DK 03/28/06 + if (PERM_COPY != op || mPermissions->allowTransferTo(gAgent.getID())) + { + // Check if the agent can assume ownership through group proxy or agent-granted proxy. + if ( ( object_is_group_owned + && gAgent.hasPowerInGroup(object_owner_id, group_proxy_power)) + // Only allow proxy for move, modify, and copy. + || ( (PERM_MOVE == op || PERM_MODIFY == op || PERM_COPY == op) + && (!object_is_group_owned + && gAgent.isGrantedProxy(*mPermissions)))) + { + // This agent is able to assume the ownership role for this operation. + proxy_agent_id = object_owner_id; + } + } + + // We now have max ownership information. + if (PERM_OWNER == op) + { + // This this was just a check for ownership, we can now return the answer. + return proxy_agent_id == object_owner_id; + } + + // check permissions to see if the agent can operate + return (mPermissions->allowOperationBy(op, proxy_agent_id, group_id)); +} + +//----------------------------------------------------------------------------- +// renderOneSilhouette() +//----------------------------------------------------------------------------- +void LLSelectNode::renderOneSilhouette(const LLColor4 &color) +{ + LLViewerObject* objectp = getObject(); + if (!objectp) + { + return; + } + + LLDrawable* drawable = objectp->mDrawable; + if(!drawable) + { + return; + } + + LLVOVolume* vobj = drawable->getVOVolume(); + if (vobj && vobj->isMesh()) + { + //This check (if(...)) with assert here just for ensure that this situation will not happens, and can be removed later. For example on the next release. + llassert(!"renderOneWireframe() was removed SL-10194"); + return; + } + + if (!mSilhouetteExists) + { + return; + } + + bool is_hud_object = objectp->isHUDAttachment(); + + if (mSilhouetteVertices.size() == 0 || mSilhouetteNormals.size() != mSilhouetteVertices.size()) + { + return; + } + + + LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; + + if (shader) + { //use UI program for selection highlights (texture color modulated by vertex color) + gUIProgram.bind(); + } + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + gGL.pushUIMatrix(); + gGL.loadUIIdentity(); + + if (!is_hud_object) + { + gGL.loadIdentity(); + gGL.multMatrix(gGLModelView); + } + + + if (drawable->isActive()) + { + gGL.multMatrix((F32*) objectp->getRenderMatrix().mMatrix); + } + + LLVolume *volume = objectp->getVolume(); + if (volume) + { + F32 silhouette_thickness; + if (isAgentAvatarValid() && is_hud_object) + { + silhouette_thickness = LLSelectMgr::sHighlightThickness / gAgentCamera.mHUDCurZoom; + } + else + { + LLVector3 view_vector = LLViewerCamera::getInstance()->getOrigin() - objectp->getRenderPosition(); + silhouette_thickness = view_vector.magVec() * LLSelectMgr::sHighlightThickness * (LLViewerCamera::getInstance()->getView() / LLViewerCamera::getInstance()->getDefaultFOV()); + } + F32 animationTime = (F32)LLFrameTimer::getElapsedSeconds(); + + F32 u_coord = fmod(animationTime * LLSelectMgr::sHighlightUAnim, 1.f); + F32 v_coord = 1.f - fmod(animationTime * LLSelectMgr::sHighlightVAnim, 1.f); + F32 u_divisor = 1.f / ((F32)(mSilhouetteVertices.size() - 1)); + + if (LLSelectMgr::sRenderHiddenSelections) // && gFloaterTools && gFloaterTools->getVisible()) + { + gGL.flush(); + gGL.blendFunc(LLRender::BF_SOURCE_COLOR, LLRender::BF_ONE); + + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE, GL_GEQUAL); + gGL.flush(); + gGL.begin(LLRender::LINES); + { + gGL.color4f(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], 0.4f); + + for(S32 i = 0; i < mSilhouetteVertices.size(); i += 2) + { + u_coord += u_divisor * LLSelectMgr::sHighlightUScale; + gGL.texCoord2f( u_coord, v_coord ); + gGL.vertex3fv( mSilhouetteVertices[i].mV); + u_coord += u_divisor * LLSelectMgr::sHighlightUScale; + gGL.texCoord2f( u_coord, v_coord ); + gGL.vertex3fv(mSilhouetteVertices[i+1].mV); + } + } + gGL.end(); + u_coord = fmod(animationTime * LLSelectMgr::sHighlightUAnim, 1.f); + } + + gGL.flush(); + gGL.setSceneBlendType(LLRender::BT_ALPHA); + gGL.begin(LLRender::TRIANGLES); + { + for(S32 i = 0; i < mSilhouetteVertices.size(); i+=2) + { + if (!mSilhouetteNormals[i].isFinite() || + !mSilhouetteNormals[i+1].isFinite()) + { //skip skewed segments + continue; + } + + LLVector3 v[4]; + LLVector2 tc[4]; + v[0] = mSilhouetteVertices[i] + (mSilhouetteNormals[i] * silhouette_thickness); + tc[0].set(u_coord, v_coord + LLSelectMgr::sHighlightVScale); + + v[1] = mSilhouetteVertices[i]; + tc[1].set(u_coord, v_coord); + + u_coord += u_divisor * LLSelectMgr::sHighlightUScale; + + v[2] = mSilhouetteVertices[i+1] + (mSilhouetteNormals[i+1] * silhouette_thickness); + tc[2].set(u_coord, v_coord + LLSelectMgr::sHighlightVScale); + + v[3] = mSilhouetteVertices[i+1]; + tc[3].set(u_coord,v_coord); + + gGL.color4f(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], 0.0f); //LLSelectMgr::sHighlightAlpha); + gGL.texCoord2fv(tc[0].mV); + gGL.vertex3fv( v[0].mV ); + + gGL.color4f(color.mV[VRED]*2, color.mV[VGREEN]*2, color.mV[VBLUE]*2, LLSelectMgr::sHighlightAlpha); + gGL.texCoord2fv( tc[1].mV ); + gGL.vertex3fv( v[1].mV ); + + gGL.color4f(color.mV[VRED], color.mV[VGREEN], color.mV[VBLUE], 0.0f); //LLSelectMgr::sHighlightAlpha); + gGL.texCoord2fv( tc[2].mV ); + gGL.vertex3fv( v[2].mV ); + + gGL.vertex3fv( v[2].mV ); + + gGL.color4f(color.mV[VRED]*2, color.mV[VGREEN]*2, color.mV[VBLUE]*2, LLSelectMgr::sHighlightAlpha); + gGL.texCoord2fv( tc[1].mV ); + gGL.vertex3fv( v[1].mV ); + + gGL.texCoord2fv( tc[3].mV ); + gGL.vertex3fv( v[3].mV ); + } + } + gGL.end(); + gGL.flush(); + } + gGL.popMatrix(); + gGL.popUIMatrix(); + + if (shader) + { + shader->bind(); + } +} + +// +// Utility Functions +// + +// *DEPRECATED: See header comment. +void dialog_refresh_all() +{ + // This is the easiest place to fire the update signal, as it will + // make cleaning up the functions below easier. Also, sometimes entities + // outside the selection manager change properties of selected objects + // and call into this function. Yuck. + LLSelectMgr::getInstance()->mUpdateSignal(); + + // *TODO: Eliminate all calls into outside classes below, make those + // objects register with the update signal. + + gFloaterTools->dirty(); + + gMenuObject->needsArrange(); + + if( gMenuAttachmentSelf->getVisible() ) + { + gMenuAttachmentSelf->arrange(); + } + if( gMenuAttachmentOther->getVisible() ) + { + gMenuAttachmentOther->arrange(); + } + + LLFloaterInspect* inspect_instance = LLFloaterReg::getTypedInstance("inspect"); + if(inspect_instance) + { + inspect_instance->dirty(); + } + + LLSidepanelTaskInfo *panel_task_info = LLSidepanelTaskInfo::getActivePanel(); + if (panel_task_info) + { + panel_task_info->dirty(); + } +} + +S32 get_family_count(LLViewerObject *parent) +{ + if (!parent) + { + LL_WARNS() << "Trying to get_family_count on null parent!" << LL_ENDL; + } + S32 count = 1; // for this object + LLViewerObject::const_child_list_t& child_list = parent->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); iter++) + { + LLViewerObject* child = *iter; + + if (!child) + { + LL_WARNS() << "Family object has NULL child! Show Doug." << LL_ENDL; + } + else if (child->isDead()) + { + LL_WARNS() << "Family object has dead child object. Show Doug." << LL_ENDL; + } + else + { + if (LLSelectMgr::getInstance()->canSelectObject(child)) + { + count += get_family_count( child ); + } + } + } + return count; +} + +//----------------------------------------------------------------------------- +// updateSelectionCenter +// +// FIXME this is a grab bag of functionality only some of which has to do +// with the selection center +// ----------------------------------------------------------------------------- +void LLSelectMgr::updateSelectionCenter() +{ + const F32 MOVE_SELECTION_THRESHOLD = 1.f; // Movement threshold in meters for updating selection + // center (tractor beam) + + // override any avatar updates received + // Works only if avatar was repositioned + // and edit floater is visible + overrideAvatarUpdates(); + //override any object updates received + //for selected objects + overrideObjectUpdates(); + + LLViewerObject* object = mSelectedObjects->getFirstObject(); + if (!object) + { + // nothing selected, probably grabbing + // Ignore by setting to avatar origin. + mSelectionCenterGlobal.clearVec(); + mShowSelection = false; + mSelectionBBox = LLBBox(); + resetAgentHUDZoom(); + } + else + { + mSelectedObjects->mSelectType = getSelectTypeForObject(object); + + if (mSelectedObjects->mSelectType != SELECT_TYPE_HUD && isAgentAvatarValid()) + { + // reset hud ZOOM + resetAgentHUDZoom(); + } + + mShowSelection = false; + LLBBox bbox; + + // have stuff selected + LLVector3d select_center; + // keep a list of jointed objects for showing the joint HUDEffects + + // Initialize the bounding box to the root prim, so the BBox orientation + // matches the root prim's (affecting the orientation of the manipulators). + bbox.addBBoxAgent( (mSelectedObjects->getFirstRootObject(true))->getBoundingBoxAgent() ); + + for (LLObjectSelection::iterator iter = mSelectedObjects->begin(); + iter != mSelectedObjects->end(); iter++) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + if (!object) + continue; + + LLViewerObject *root = object->getRootEdit(); + if (mSelectedObjects->mSelectType == SELECT_TYPE_WORLD && // not an attachment + !root->isChild(gAgentAvatarp) && // not the object you're sitting on + !object->isAvatar()) // not another avatar + { + mShowSelection = true; + } + + bbox.addBBoxAgent( object->getBoundingBoxAgent() ); + } + + LLVector3 bbox_center_agent = bbox.getCenterAgent(); + mSelectionCenterGlobal = gAgent.getPosGlobalFromAgent(bbox_center_agent); + mSelectionBBox = bbox; + + } + + if ( !(gAgentID == LLUUID::null)) + { + LLTool *tool = LLToolMgr::getInstance()->getCurrentTool(); + if (mShowSelection) + { + LLVector3d select_center_global; + + if( tool->isEditing() ) + { + select_center_global = tool->getEditingPointGlobal(); + } + else + { + select_center_global = mSelectionCenterGlobal; + } + + // Send selection center if moved beyond threshold (used to animate tractor beam) + LLVector3d diff; + diff = select_center_global - mLastSentSelectionCenterGlobal; + + if ( diff.magVecSquared() > MOVE_SELECTION_THRESHOLD*MOVE_SELECTION_THRESHOLD ) + { + // Transmit updated selection center + mLastSentSelectionCenterGlobal = select_center_global; + } + } + } + + // give up edit menu if no objects selected + if (gEditMenuHandler == this && mSelectedObjects->getObjectCount() == 0) + { + gEditMenuHandler = NULL; + } + + pauseAssociatedAvatars(); +} + +//----------------------------------------------------------------------------- +// pauseAssociatedAvatars +// +// If the selection includes an attachment or an animated object, the +// associated avatars should pause their animations until they are no +// longer selected. +//----------------------------------------------------------------------------- +void LLSelectMgr::pauseAssociatedAvatars() +{ + mPauseRequests.clear(); + + for (LLObjectSelection::iterator iter = mSelectedObjects->begin(); + iter != mSelectedObjects->end(); iter++) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + if (!object) + continue; + + mSelectedObjects->mSelectType = getSelectTypeForObject(object); + + LLVOAvatar* parent_av = NULL; + if (mSelectedObjects->mSelectType == SELECT_TYPE_ATTACHMENT) + { + // Selection can be obsolete, confirm that this is an attachment + // and find parent avatar + parent_av = object->getAvatarAncestor(); + } + + // Can be both an attachment and animated object + if (parent_av) + { + // It's an attachment. Pause the avatar it's attached to. + mPauseRequests.push_back(parent_av->requestPause()); + } + + if (object->isAnimatedObject() && object->getControlAvatar()) + { + // It's an animated object. Pause the control avatar. + mPauseRequests.push_back(object->getControlAvatar()->requestPause()); + } + } +} + +void LLSelectMgr::updatePointAt() +{ + if (mShowSelection) + { + if (mSelectedObjects->getObjectCount()) + { + LLVector3 select_offset; + const LLPickInfo& pick = gViewerWindow->getLastPick(); + LLViewerObject *click_object = pick.getObject(); + if (click_object && click_object->isSelected()) + { + // clicked on another object in our selection group, use that as target + select_offset.setVec(pick.mObjectOffset); + select_offset.rotVec(~click_object->getRenderRotation()); + + gAgentCamera.setPointAt(POINTAT_TARGET_SELECT, click_object, select_offset); + gAgentCamera.setLookAt(LOOKAT_TARGET_SELECT, click_object, select_offset); + } + else + { + // didn't click on an object this time, revert to pointing at center of first object + gAgentCamera.setPointAt(POINTAT_TARGET_SELECT, mSelectedObjects->getFirstObject()); + gAgentCamera.setLookAt(LOOKAT_TARGET_SELECT, mSelectedObjects->getFirstObject()); + } + } + else + { + gAgentCamera.setPointAt(POINTAT_TARGET_CLEAR); + gAgentCamera.setLookAt(LOOKAT_TARGET_CLEAR); + } + } + else + { + gAgentCamera.setPointAt(POINTAT_TARGET_CLEAR); + gAgentCamera.setLookAt(LOOKAT_TARGET_CLEAR); + } +} + +//----------------------------------------------------------------------------- +// getBBoxOfSelection() +//----------------------------------------------------------------------------- +LLBBox LLSelectMgr::getBBoxOfSelection() const +{ + return mSelectionBBox; +} + + +//----------------------------------------------------------------------------- +// canUndo() +//----------------------------------------------------------------------------- +bool LLSelectMgr::canUndo() const +{ + // Can edit or can move + return const_cast(this)->mSelectedObjects->getFirstUndoEnabledObject() != NULL; // HACK: casting away constness - MG; +} + +//----------------------------------------------------------------------------- +// undo() +//----------------------------------------------------------------------------- +void LLSelectMgr::undo() +{ + bool select_linked_set = !gSavedSettings.getBOOL("EditLinkedParts"); + LLUUID group_id(gAgent.getGroupID()); + sendListToRegions("Undo", packAgentAndSessionAndGroupID, packObjectID, logNoOp, &group_id, select_linked_set ? SEND_ONLY_ROOTS : SEND_CHILDREN_FIRST); +} + +//----------------------------------------------------------------------------- +// canRedo() +//----------------------------------------------------------------------------- +bool LLSelectMgr::canRedo() const +{ + return const_cast(this)->mSelectedObjects->getFirstEditableObject() != NULL; // HACK: casting away constness - MG +} + +//----------------------------------------------------------------------------- +// redo() +//----------------------------------------------------------------------------- +void LLSelectMgr::redo() +{ + bool select_linked_set = !gSavedSettings.getBOOL("EditLinkedParts"); + LLUUID group_id(gAgent.getGroupID()); + sendListToRegions("Redo", packAgentAndSessionAndGroupID, packObjectID, logNoOp, &group_id, select_linked_set ? SEND_ONLY_ROOTS : SEND_CHILDREN_FIRST); +} + +//----------------------------------------------------------------------------- +// canDoDelete() +//----------------------------------------------------------------------------- +bool LLSelectMgr::canDoDelete() const +{ + bool can_delete = false; + // This function is "logically const" - it does not change state in + // a way visible outside the selection manager. + LLSelectMgr* self = const_cast(this); + LLViewerObject* obj = self->mSelectedObjects->getFirstDeleteableObject(); + // Note: Can only delete root objects (see getFirstDeleteableObject() for more info) + if (obj!= NULL) + { + // all the faces needs to be selected + if(self->mSelectedObjects->contains(obj,SELECT_ALL_TES )) + { + can_delete = true; + } + } + + return can_delete; +} + +//----------------------------------------------------------------------------- +// doDelete() +//----------------------------------------------------------------------------- +void LLSelectMgr::doDelete() +{ + selectDelete(); +} + +//----------------------------------------------------------------------------- +// canDeselect() +//----------------------------------------------------------------------------- +bool LLSelectMgr::canDeselect() const +{ + return !mSelectedObjects->isEmpty(); +} + +//----------------------------------------------------------------------------- +// deselect() +//----------------------------------------------------------------------------- +void LLSelectMgr::deselect() +{ + deselectAll(); +} +//----------------------------------------------------------------------------- +// canDuplicate() +//----------------------------------------------------------------------------- +bool LLSelectMgr::canDuplicate() const +{ + return const_cast(this)->mSelectedObjects->getFirstCopyableObject() != NULL; // HACK: casting away constness - MG +} +//----------------------------------------------------------------------------- +// duplicate() +//----------------------------------------------------------------------------- +void LLSelectMgr::duplicate() +{ + LLVector3 offset(0.5f, 0.5f, 0.f); + selectDuplicate(offset, true); +} + +ESelectType LLSelectMgr::getSelectTypeForObject(LLViewerObject* object) +{ + if (!object) + { + return SELECT_TYPE_WORLD; + } + if (object->isHUDAttachment()) + { + return SELECT_TYPE_HUD; + } + else if (object->isAttachment()) + { + return SELECT_TYPE_ATTACHMENT; + } + else + { + return SELECT_TYPE_WORLD; + } +} + +void LLSelectMgr::validateSelection() +{ + struct f : public LLSelectedObjectFunctor + { + virtual bool apply(LLViewerObject* object) + { + if (!LLSelectMgr::getInstance()->canSelectObject(object)) + { + LLSelectMgr::getInstance()->deselectObjectOnly(object); + } + return true; + } + } func; + getSelection()->applyToObjects(&func); +} + +bool LLSelectMgr::canSelectObject(LLViewerObject* object, bool ignore_select_owned) +{ + // Never select dead objects + if (!object || object->isDead()) + { + return false; + } + + if (mForceSelection) + { + return true; + } + + if(!ignore_select_owned) + { + if ((gSavedSettings.getBOOL("SelectOwnedOnly") && !object->permYouOwner()) || + (gSavedSettings.getBOOL("SelectMovableOnly") && (!object->permMove() || object->isPermanentEnforced()))) + { + // only select my own objects + return false; + } + } + + // Can't select orphans + if (object->isOrphaned()) return false; + + // Can't select avatars + if (object->isAvatar()) return false; + + // Can't select land + if (object->getPCode() == LLViewerObject::LL_VO_SURFACE_PATCH) return false; + + ESelectType selection_type = getSelectTypeForObject(object); + if (mSelectedObjects->getObjectCount() > 0 && mSelectedObjects->mSelectType != selection_type) return false; + + return true; +} + +bool LLSelectMgr::setForceSelection(bool force) +{ + std::swap(mForceSelection,force); + return force; +} + +void LLSelectMgr::resetAgentHUDZoom() +{ + if (gAgentCamera.mHUDTargetZoom != 1) + { + gAgentCamera.mHUDTargetZoom = 1.f; + gAgentCamera.mHUDCurZoom = 1.f; + } +} + +void LLSelectMgr::getAgentHUDZoom(F32 &target_zoom, F32 ¤t_zoom) const +{ + target_zoom = gAgentCamera.mHUDTargetZoom; + current_zoom = gAgentCamera.mHUDCurZoom; +} + +void LLSelectMgr::setAgentHUDZoom(F32 target_zoom, F32 current_zoom) +{ + gAgentCamera.mHUDTargetZoom = target_zoom; + gAgentCamera.mHUDCurZoom = current_zoom; +} + +///////////////////////////////////////////////////////////////////////////// +// Object selection iterator helpers +///////////////////////////////////////////////////////////////////////////// +bool LLObjectSelection::is_root::operator()(LLSelectNode *node) +{ + LLViewerObject* object = node->getObject(); + return (object != NULL) && !node->mIndividualSelection && (object->isRootEdit()); +} + +bool LLObjectSelection::is_valid_root::operator()(LLSelectNode *node) +{ + LLViewerObject* object = node->getObject(); + return (object != NULL) && node->mValid && !node->mIndividualSelection && (object->isRootEdit()); +} + +bool LLObjectSelection::is_root_object::operator()(LLSelectNode *node) +{ + LLViewerObject* object = node->getObject(); + return (object != NULL) && (object->isRootEdit()); +} + +LLObjectSelection::LLObjectSelection() : + LLRefCount(), + mSelectType(SELECT_TYPE_WORLD) +{ +} + +LLObjectSelection::~LLObjectSelection() +{ + deleteAllNodes(); +} + +void LLObjectSelection::cleanupNodes() +{ + for (list_t::iterator iter = mList.begin(); iter != mList.end(); ) + { + list_t::iterator curiter = iter++; + LLSelectNode* node = *curiter; + if (node->getObject() == NULL || node->getObject()->isDead()) + { + mList.erase(curiter); + delete node; + } + } +} + +void LLObjectSelection::updateEffects() +{ +} + +S32 LLObjectSelection::getNumNodes() +{ + return mList.size(); +} + +void LLObjectSelection::addNode(LLSelectNode *nodep) +{ + llassert_always(nodep->getObject() && !nodep->getObject()->isDead()); + mList.push_front(nodep); + mSelectNodeMap[nodep->getObject()] = nodep; +} + +void LLObjectSelection::addNodeAtEnd(LLSelectNode *nodep) +{ + llassert_always(nodep->getObject() && !nodep->getObject()->isDead()); + mList.push_back(nodep); + mSelectNodeMap[nodep->getObject()] = nodep; +} + +void LLObjectSelection::moveNodeToFront(LLSelectNode *nodep) +{ + mList.remove(nodep); + mList.push_front(nodep); +} + +void LLObjectSelection::removeNode(LLSelectNode *nodep) +{ + mSelectNodeMap.erase(nodep->getObject()); + if (nodep->getObject() == mPrimaryObject) + { + mPrimaryObject = NULL; + } + nodep->setObject(NULL); // Will get erased in cleanupNodes() + mList.remove(nodep); +} + +void LLObjectSelection::deleteAllNodes() +{ + std::for_each(mList.begin(), mList.end(), DeletePointer()); + mList.clear(); + mSelectNodeMap.clear(); + mPrimaryObject = NULL; +} + +LLSelectNode* LLObjectSelection::findNode(LLViewerObject* objectp) +{ + std::map, LLSelectNode*>::iterator found_it = mSelectNodeMap.find(objectp); + if (found_it != mSelectNodeMap.end()) + { + return found_it->second; + } + return NULL; +} + +//----------------------------------------------------------------------------- +// isEmpty() +//----------------------------------------------------------------------------- +bool LLObjectSelection::isEmpty() const +{ + return (mList.size() == 0); +} + + +//----------------------------------------------------------------------------- +// getObjectCount() - returns number of non null objects +//----------------------------------------------------------------------------- +S32 LLObjectSelection::getObjectCount() +{ + cleanupNodes(); + S32 count = mList.size(); + + return count; +} + +F32 LLObjectSelection::getSelectedObjectCost() +{ + cleanupNodes(); + F32 cost = 0.f; + + for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + + if (object) + { + cost += object->getObjectCost(); + } + } + + return cost; +} + +F32 LLObjectSelection::getSelectedLinksetCost() +{ + cleanupNodes(); + F32 cost = 0.f; + + std::set me_roots; + + for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + + if (object && !object->isAttachment()) + { + LLViewerObject* root = static_cast(object->getRoot()); + if (root) + { + if (me_roots.find(root) == me_roots.end()) + { + me_roots.insert(root); + cost += root->getLinksetCost(); + } + } + } + } + + return cost; +} + +F32 LLObjectSelection::getSelectedPhysicsCost() +{ + cleanupNodes(); + F32 cost = 0.f; + + for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + + if (object) + { + cost += object->getPhysicsCost(); + } + } + + return cost; +} + +F32 LLObjectSelection::getSelectedLinksetPhysicsCost() +{ + cleanupNodes(); + F32 cost = 0.f; + + std::set me_roots; + + for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + + if (object) + { + LLViewerObject* root = static_cast(object->getRoot()); + if (root) + { + if (me_roots.find(root) == me_roots.end()) + { + me_roots.insert(root); + cost += root->getLinksetPhysicsCost(); + } + } + } + } + + return cost; +} + +F32 LLObjectSelection::getSelectedObjectStreamingCost(S32* total_bytes, S32* visible_bytes) +{ + F32 cost = 0.f; + for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + + if (object) + { + cost += object->getStreamingCost(); + + S32 bytes = 0; + S32 visible = 0; + LLMeshCostData costs; + if (object->getCostData(costs)) + { + bytes = costs.getSizeTotal(); + visible = costs.getSizeByLOD(object->getLOD()); + } + if (total_bytes) + { + *total_bytes += bytes; + } + + if (visible_bytes) + { + *visible_bytes += visible; + } + } + } + + return cost; +} + +U32 LLObjectSelection::getSelectedObjectTriangleCount(S32* vcount) +{ + U32 count = 0; + for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + + if (object) + { + S32 vt = 0; + count += object->getTriangleCount(&vt); + *vcount += vt; + } + } + + return count; +} + +S32 LLObjectSelection::getSelectedObjectRenderCost() +{ + S32 cost = 0; + LLVOVolume::texture_cost_t textures; + typedef std::set uuid_list_t; + uuid_list_t computed_objects; + + typedef std::list > child_list_t; + typedef const child_list_t const_child_list_t; + + // add render cost of complete linksets first, to get accurate texture counts + for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter) + { + LLSelectNode* node = *iter; + + LLVOVolume* object = (LLVOVolume*)node->getObject(); + + if (object && object->isRootEdit()) + { + cost += object->getRenderCost(textures); + computed_objects.insert(object->getID()); + + const_child_list_t children = object->getChildren(); + for (const_child_list_t::const_iterator child_iter = children.begin(); + child_iter != children.end(); + ++child_iter) + { + LLViewerObject* child_obj = *child_iter; + LLVOVolume *child = dynamic_cast( child_obj ); + if (child) + { + cost += child->getRenderCost(textures); + computed_objects.insert(child->getID()); + } + } + + for (LLVOVolume::texture_cost_t::iterator iter = textures.begin(); iter != textures.end(); ++iter) + { + // add the cost of each individual texture in the linkset + cost += LLVOVolume::getTextureCost(*iter); + } + + textures.clear(); + } + } + + // add any partial linkset objects, texture cost may be slightly misleading + for (list_t::iterator iter = mList.begin(); iter != mList.end(); ++iter) + { + LLSelectNode* node = *iter; + LLVOVolume* object = (LLVOVolume*)node->getObject(); + + if (object && computed_objects.find(object->getID()) == computed_objects.end() ) + { + cost += object->getRenderCost(textures); + computed_objects.insert(object->getID()); + } + + for (LLVOVolume::texture_cost_t::iterator iter = textures.begin(); iter != textures.end(); ++iter) + { + // add the cost of each individual texture in the linkset + cost += LLVOVolume::getTextureCost(*iter); + } + + textures.clear(); + } + + return cost; +} + +//----------------------------------------------------------------------------- +// getTECount() +//----------------------------------------------------------------------------- +S32 LLObjectSelection::getTECount() +{ + S32 count = 0; + for (LLObjectSelection::iterator iter = begin(); iter != end(); iter++) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + if (!object) + continue; + S32 num_tes = object->getNumTEs(); + for (S32 te = 0; te < num_tes; te++) + { + if (node->isTESelected(te)) + { + ++count; + } + } + } + return count; +} + +//----------------------------------------------------------------------------- +// getRootObjectCount() +//----------------------------------------------------------------------------- +S32 LLObjectSelection::getRootObjectCount() +{ + S32 count = 0; + for (LLObjectSelection::root_iterator iter = root_begin(); iter != root_end(); iter++) + { + ++count; + } + return count; +} + +bool LLObjectSelection::applyToObjects(LLSelectedObjectFunctor* func) +{ + bool result = true; + for (iterator iter = begin(); iter != end(); ) + { + iterator nextiter = iter++; + LLViewerObject* object = (*nextiter)->getObject(); + if (!object) + continue; + bool r = func->apply(object); + result = result && r; + } + return result; +} + +bool LLObjectSelection::checkAnimatedObjectEstTris() +{ + F32 est_tris = 0; + F32 max_tris = 0; + S32 anim_count = 0; + for (root_iterator iter = root_begin(); iter != root_end(); ++iter) + { + LLViewerObject* object = (*iter)->getObject(); + if (!object) + continue; + if (object->isAnimatedObject()) + { + anim_count++; + } + est_tris += object->recursiveGetEstTrianglesMax(); + max_tris = llmax((F32)max_tris,(F32)object->getAnimatedObjectMaxTris()); + } + return anim_count==0 || est_tris <= max_tris; +} + +bool LLObjectSelection::checkAnimatedObjectLinkable() +{ + return checkAnimatedObjectEstTris(); +} + +bool LLObjectSelection::applyToRootObjects(LLSelectedObjectFunctor* func, bool firstonly) +{ + bool result = !firstonly; + for (root_iterator iter = root_begin(); iter != root_end(); ) + { + root_iterator nextiter = iter++; + LLViewerObject* object = (*nextiter)->getObject(); + if (!object) + continue; + bool r = func->apply(object); + if (firstonly && r) + return true; + else + result = result && r; + } + return result; +} + +bool LLObjectSelection::applyToTEs(LLSelectedTEFunctor* func, bool firstonly) +{ + bool result = !firstonly; + for (iterator iter = begin(); iter != end(); ) + { + iterator nextiter = iter++; + LLSelectNode* node = *nextiter; + LLViewerObject* object = (*nextiter)->getObject(); + if (!object) + continue; + S32 num_tes = llmin((S32)object->getNumTEs(), (S32)object->getNumFaces()); // avatars have TEs but no faces + for (S32 te = 0; te < num_tes; ++te) + { + if (node->isTESelected(te)) + { + bool r = func->apply(object, te); + if (firstonly && r) + return true; + else + result = result && r; + } + } + } + return result; +} + +bool LLObjectSelection::applyToNodes(LLSelectedNodeFunctor *func, bool firstonly) +{ + bool result = !firstonly; + for (iterator iter = begin(); iter != end(); ) + { + iterator nextiter = iter++; + LLSelectNode* node = *nextiter; + bool r = func->apply(node); + if (firstonly && r) + return true; + else + result = result && r; + } + return result; +} + +bool LLObjectSelection::applyToRootNodes(LLSelectedNodeFunctor *func, bool firstonly) +{ + bool result = !firstonly; + for (root_iterator iter = root_begin(); iter != root_end(); ) + { + root_iterator nextiter = iter++; + LLSelectNode* node = *nextiter; + bool r = func->apply(node); + if (firstonly && r) + return true; + else + result = result && r; + } + return result; +} + +bool LLObjectSelection::isMultipleTESelected() +{ + bool te_selected = false; + // ...all faces + for (LLObjectSelection::iterator iter = begin(); + iter != end(); iter++) + { + LLSelectNode* nodep = *iter; + for (S32 i = 0; i < SELECT_MAX_TES; i++) + { + if(nodep->isTESelected(i)) + { + if(te_selected) + { + return true; + } + te_selected = true; + } + } + } + return false; +} + +//----------------------------------------------------------------------------- +// contains() +//----------------------------------------------------------------------------- +bool LLObjectSelection::contains(LLViewerObject* object) +{ + return findNode(object) != NULL; +} + + +//----------------------------------------------------------------------------- +// contains() +//----------------------------------------------------------------------------- +bool LLObjectSelection::contains(LLViewerObject* object, S32 te) +{ + if (te == SELECT_ALL_TES) + { + // ...all faces + for (LLObjectSelection::iterator iter = begin(); + iter != end(); iter++) + { + LLSelectNode* nodep = *iter; + if (nodep->getObject() == object) + { + // Optimization + if (nodep->getTESelectMask() == TE_SELECT_MASK_ALL) + { + return true; + } + + bool all_selected = true; + for (S32 i = 0; i < object->getNumTEs(); i++) + { + all_selected = all_selected && nodep->isTESelected(i); + } + return all_selected; + } + } + return false; + } + else + { + // ...one face + for (LLObjectSelection::iterator iter = begin(); iter != end(); iter++) + { + LLSelectNode* nodep = *iter; + if (nodep->getObject() == object && nodep->isTESelected(te)) + { + return true; + } + } + return false; + } +} + +// returns true is any node is currenly worn as an attachment +bool LLObjectSelection::isAttachment() +{ + return (mSelectType == SELECT_TYPE_ATTACHMENT || mSelectType == SELECT_TYPE_HUD); +} + +//----------------------------------------------------------------------------- +// getSelectedParentObject() +//----------------------------------------------------------------------------- +LLViewerObject* getSelectedParentObject(LLViewerObject *object) +{ + LLViewerObject *parent; + while (object && (parent = (LLViewerObject*)object->getParent())) + { + if (parent->isSelected()) + { + object = parent; + } + else + { + break; + } + } + return object; +} + +//----------------------------------------------------------------------------- +// getFirstNode +//----------------------------------------------------------------------------- +LLSelectNode* LLObjectSelection::getFirstNode(LLSelectedNodeFunctor* func) +{ + for (iterator iter = begin(); iter != end(); ++iter) + { + LLSelectNode* node = *iter; + if (func == NULL || func->apply(node)) + { + return node; + } + } + return NULL; +} + +LLSelectNode* LLObjectSelection::getFirstRootNode(LLSelectedNodeFunctor* func, bool non_root_ok) +{ + for (root_iterator iter = root_begin(); iter != root_end(); ++iter) + { + LLSelectNode* node = *iter; + if (func == NULL || func->apply(node)) + { + return node; + } + } + if (non_root_ok) + { + // Get non root + return getFirstNode(func); + } + return NULL; +} + + +//----------------------------------------------------------------------------- +// getFirstSelectedObject +//----------------------------------------------------------------------------- +LLViewerObject* LLObjectSelection::getFirstSelectedObject(LLSelectedNodeFunctor* func, bool get_parent) +{ + LLSelectNode* res = getFirstNode(func); + if (res && get_parent) + { + return getSelectedParentObject(res->getObject()); + } + else if (res) + { + return res->getObject(); + } + return NULL; +} + +//----------------------------------------------------------------------------- +// getFirstObject() +//----------------------------------------------------------------------------- +LLViewerObject* LLObjectSelection::getFirstObject() +{ + LLSelectNode* res = getFirstNode(NULL); + return res ? res->getObject() : NULL; +} + +//----------------------------------------------------------------------------- +// getFirstRootObject() +//----------------------------------------------------------------------------- +LLViewerObject* LLObjectSelection::getFirstRootObject(bool non_root_ok) +{ + LLSelectNode* res = getFirstRootNode(NULL, non_root_ok); + return res ? res->getObject() : NULL; +} + +//----------------------------------------------------------------------------- +// getFirstMoveableNode() +//----------------------------------------------------------------------------- +LLSelectNode* LLObjectSelection::getFirstMoveableNode(bool get_root_first) +{ + struct f : public LLSelectedNodeFunctor + { + bool apply(LLSelectNode* node) + { + LLViewerObject* obj = node->getObject(); + return obj && obj->permMove() && !obj->isPermanentEnforced(); + } + } func; + LLSelectNode* res = get_root_first ? getFirstRootNode(&func, true) : getFirstNode(&func); + return res; +} + +//----------------------------------------------------------------------------- +// getFirstCopyableObject() +//----------------------------------------------------------------------------- +LLViewerObject* LLObjectSelection::getFirstCopyableObject(bool get_parent) +{ + struct f : public LLSelectedNodeFunctor + { + bool apply(LLSelectNode* node) + { + LLViewerObject* obj = node->getObject(); + return obj && obj->permCopy() && !obj->isAttachment(); + } + } func; + return getFirstSelectedObject(&func, get_parent); +} + +//----------------------------------------------------------------------------- +// getFirstDeleteableObject() +//----------------------------------------------------------------------------- +LLViewerObject* LLObjectSelection::getFirstDeleteableObject() +{ + //RN: don't currently support deletion of child objects, as that requires separating them first + // then derezzing to trash + + struct f : public LLSelectedNodeFunctor + { + bool apply(LLSelectNode* node) + { + LLViewerObject* obj = node->getObject(); + // you can delete an object if you are the owner + // or you have permission to modify it. + if( obj && !obj->isPermanentEnforced() && + ( (obj->permModify()) || + (obj->permYouOwner()) || + (!obj->permAnyOwner()) )) // public + { + if( !obj->isAttachment() ) + { + return true; + } + } + return false; + } + } func; + LLSelectNode* node = getFirstNode(&func); + return node ? node->getObject() : NULL; +} + +//----------------------------------------------------------------------------- +// getFirstEditableObject() +//----------------------------------------------------------------------------- +LLViewerObject* LLObjectSelection::getFirstEditableObject(bool get_parent) +{ + struct f : public LLSelectedNodeFunctor + { + bool apply(LLSelectNode* node) + { + LLViewerObject* obj = node->getObject(); + return obj && obj->permModify(); + } + } func; + return getFirstSelectedObject(&func, get_parent); +} + +//----------------------------------------------------------------------------- +// getFirstMoveableObject() +//----------------------------------------------------------------------------- +LLViewerObject* LLObjectSelection::getFirstMoveableObject(bool get_parent) +{ + struct f : public LLSelectedNodeFunctor + { + bool apply(LLSelectNode* node) + { + LLViewerObject* obj = node->getObject(); + return obj && obj->permMove() && !obj->isPermanentEnforced(); + } + } func; + return getFirstSelectedObject(&func, get_parent); +} + +//----------------------------------------------------------------------------- +// getFirstUndoEnabledObject() +//----------------------------------------------------------------------------- +LLViewerObject* LLObjectSelection::getFirstUndoEnabledObject(bool get_parent) +{ + struct f : public LLSelectedNodeFunctor + { + bool apply(LLSelectNode* node) + { + LLViewerObject* obj = node->getObject(); + return obj && (obj->permModify() || (obj->permMove() && !obj->isPermanentEnforced())); + } + } func; + return getFirstSelectedObject(&func, get_parent); +} + +//----------------------------------------------------------------------------- +// Position + Rotation update methods called from LLViewerJoystick +//----------------------------------------------------------------------------- +bool LLSelectMgr::selectionMove(const LLVector3& displ, + F32 roll, F32 pitch, F32 yaw, U32 update_type) +{ + if (update_type == UPD_NONE) + { + return false; + } + + LLVector3 displ_global; + bool update_success = true; + bool update_position = update_type & UPD_POSITION; + bool update_rotation = update_type & UPD_ROTATION; + const bool noedit_linked_parts = !gSavedSettings.getBOOL("EditLinkedParts"); + + if (update_position) + { + // calculate the distance of the object closest to the camera origin + F32 min_dist_squared = F32_MAX; // value will be overridden in the loop + + LLVector3 obj_pos; + for (LLObjectSelection::root_iterator it = getSelection()->root_begin(); + it != getSelection()->root_end(); ++it) + { + obj_pos = (*it)->getObject()->getPositionEdit(); + + F32 obj_dist_squared = dist_vec_squared(obj_pos, LLViewerCamera::getInstance()->getOrigin()); + if (obj_dist_squared < min_dist_squared) + { + min_dist_squared = obj_dist_squared; + } + } + + // factor the distance into the displacement vector. This will get us + // equally visible movements for both close and far away selections. + F32 min_dist = sqrt((F32) sqrtf(min_dist_squared)) / 2; + displ_global.setVec(displ.mV[0] * min_dist, + displ.mV[1] * min_dist, + displ.mV[2] * min_dist); + + // equates to: Displ_global = Displ * M_cam_axes_in_global_frame + displ_global = LLViewerCamera::getInstance()->rotateToAbsolute(displ_global); + } + + LLQuaternion new_rot; + if (update_rotation) + { + // let's calculate the rotation around each camera axes + LLQuaternion qx(roll, LLViewerCamera::getInstance()->getAtAxis()); + LLQuaternion qy(pitch, LLViewerCamera::getInstance()->getLeftAxis()); + LLQuaternion qz(yaw, LLViewerCamera::getInstance()->getUpAxis()); + new_rot.setQuat(qx * qy * qz); + } + + LLViewerObject *obj; + S32 obj_count = getSelection()->getObjectCount(); + for (LLObjectSelection::root_iterator it = getSelection()->root_begin(); + it != getSelection()->root_end(); ++it ) + { + obj = (*it)->getObject(); + bool enable_pos = false, enable_rot = false; + bool perm_move = obj->permMove() && !obj->isPermanentEnforced(); + bool perm_mod = obj->permModify(); + + LLVector3d sel_center(getSelectionCenterGlobal()); + + if (update_rotation) + { + enable_rot = perm_move + && ((perm_mod && !obj->isAttachment()) || noedit_linked_parts); + + if (enable_rot) + { + int children_count = obj->getChildren().size(); + if (obj_count > 1 && children_count > 0) + { + // for linked sets, rotate around the group center + const LLVector3 t(obj->getPositionGlobal() - sel_center); + + // Ra = T x R x T^-1 + LLMatrix4 mt; mt.setTranslation(t); + const LLMatrix4 mnew_rot(new_rot); + LLMatrix4 mt_1; mt_1.setTranslation(-t); + mt *= mnew_rot; + mt *= mt_1; + + // Rfin = Rcur * Ra + obj->setRotation(obj->getRotationEdit() * mt.quaternion()); + displ_global += mt.getTranslation(); + } + else + { + obj->setRotation(obj->getRotationEdit() * new_rot); + } + } + else + { + update_success = false; + } + } + + if (update_position) + { + // establish if object can be moved or not + enable_pos = perm_move && !obj->isAttachment() + && (perm_mod || noedit_linked_parts); + + if (enable_pos) + { + obj->setPosition(obj->getPositionEdit() + displ_global); + } + else + { + update_success = false; + } + } + + if (enable_pos && enable_rot && obj->mDrawable.notNull()) + { + gPipeline.markMoved(obj->mDrawable, true); + } + } + + if (update_position && update_success && obj_count > 1) + { + updateSelectionCenter(); + } + + return update_success; +} + +void LLSelectMgr::sendSelectionMove() +{ + LLSelectNode *node = mSelectedObjects->getFirstRootNode(); + if (node == NULL) + { + return; + } + + //saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); + + U32 update_type = UPD_POSITION | UPD_ROTATION; + LLViewerRegion *last_region, *curr_region = node->getObject()->getRegion(); + S32 objects_in_this_packet = 0; + + // apply to linked objects if unable to select their individual parts + if (!gSavedSettings.getBOOL("EditLinkedParts") && !getTEMode()) + { + // tell simulator to apply to whole linked sets + update_type |= UPD_LINKED_SETS; + } + + // prepare first bulk message + gMessageSystem->newMessage("MultipleObjectUpdate"); + packAgentAndSessionID(&update_type); + + LLViewerObject *obj = NULL; + for (LLObjectSelection::root_iterator it = getSelection()->root_begin(); + it != getSelection()->root_end(); ++it) + { + obj = (*it)->getObject(); + + // note: following code adapted from sendListToRegions() (@3924) + last_region = curr_region; + curr_region = obj->getRegion(); + + // if not simulator or message too big + if (curr_region != last_region + || gMessageSystem->isSendFull(NULL) + || objects_in_this_packet >= MAX_OBJECTS_PER_PACKET) + { + // send sim the current message and start new one + gMessageSystem->sendReliable(last_region->getHost()); + objects_in_this_packet = 0; + gMessageSystem->newMessage("MultipleObjectUpdate"); + packAgentAndSessionID(&update_type); + } + + // add another instance of the body of data + packMultipleUpdate(*it, &update_type); + ++objects_in_this_packet; + } + + // flush remaining messages + if (gMessageSystem->getCurrentSendTotal() > 0) + { + gMessageSystem->sendReliable(curr_region->getHost()); + } + else + { + gMessageSystem->clearMessage(); + } + + //saveSelectedObjectTransform(SELECT_ACTION_TYPE_PICK); +} + +template<> +bool LLCheckIdenticalFunctor::same(const F32& a, const F32& b, const F32& tolerance) +{ + F32 delta = (a - b); + F32 abs_delta = fabs(delta); + return abs_delta <= tolerance; +} + +#define DEF_DUMMY_CHECK_FUNCTOR(T) \ +template<> \ +bool LLCheckIdenticalFunctor::same(const T& a, const T& b, const T& tolerance) \ +{ \ + (void)tolerance; \ + return a == b; \ +} + +DEF_DUMMY_CHECK_FUNCTOR(LLUUID) +DEF_DUMMY_CHECK_FUNCTOR(LLGLenum) +DEF_DUMMY_CHECK_FUNCTOR(LLTextureEntry) +DEF_DUMMY_CHECK_FUNCTOR(LLTextureEntry::e_texgen) +DEF_DUMMY_CHECK_FUNCTOR(bool) +DEF_DUMMY_CHECK_FUNCTOR(U8) +DEF_DUMMY_CHECK_FUNCTOR(int) +DEF_DUMMY_CHECK_FUNCTOR(LLColor4) +DEF_DUMMY_CHECK_FUNCTOR(LLMediaEntry) +DEF_DUMMY_CHECK_FUNCTOR(LLPointer) +DEF_DUMMY_CHECK_FUNCTOR(LLPointer) +DEF_DUMMY_CHECK_FUNCTOR(std::string) +DEF_DUMMY_CHECK_FUNCTOR(std::vector) + +template<> +bool LLCheckIdenticalFunctor::same(class LLFace* const & a, class LLFace* const & b, class LLFace* const & tolerance) \ +{ \ + (void)tolerance; \ + return a == b; \ +} + diff --git a/indra/newview/llselectmgr.h b/indra/newview/llselectmgr.h index 78f51d0aec..bbc6d566cb 100644 --- a/indra/newview/llselectmgr.h +++ b/indra/newview/llselectmgr.h @@ -1,1058 +1,1058 @@ -/** - * @file llselectmgr.h - * @brief A manager for selected objects and TEs. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLSELECTMGR_H -#define LL_LLSELECTMGR_H - -#include "llcharacter.h" -#include "lleditmenuhandler.h" -#include "llundo.h" -#include "lluuid.h" -#include "llpointer.h" -#include "llsafehandle.h" -#include "llsaleinfo.h" -#include "llcategory.h" -#include "v3dmath.h" -#include "llquaternion.h" -#include "llcoord.h" -#include "llframetimer.h" -#include "llbbox.h" -#include "llpermissions.h" -#include "llcontrol.h" -#include "llviewerobject.h" // LLObjectSelection::getSelectedTEValue template -#include "llmaterial.h" - -#include -#include -#include -#include // boost::make_shared - -class LLMessageSystem; -class LLViewerTexture; -class LLColor4; -class LLVector3; -class LLSelectNode; - -const U8 UPD_NONE = 0x00; -const U8 UPD_POSITION = 0x01; -const U8 UPD_ROTATION = 0x02; -const U8 UPD_SCALE = 0x04; -const U8 UPD_LINKED_SETS = 0x08; -const U8 UPD_UNIFORM = 0x10; // used with UPD_SCALE - -// This is used by the DeRezObject message to determine where to put -// derezed tasks. -enum EDeRezDestination -{ - DRD_SAVE_INTO_AGENT_INVENTORY = 0, - DRD_ACQUIRE_TO_AGENT_INVENTORY = 1, // try to leave copy in world - DRD_SAVE_INTO_TASK_INVENTORY = 2, - DRD_ATTACHMENT = 3, - DRD_TAKE_INTO_AGENT_INVENTORY = 4, // delete from world - DRD_FORCE_TO_GOD_INVENTORY = 5, // force take copy - DRD_TRASH = 6, - DRD_ATTACHMENT_TO_INV = 7, - DRD_ATTACHMENT_EXISTS = 8, - DRD_RETURN_TO_OWNER = 9, // back to owner's inventory - DRD_RETURN_TO_LAST_OWNER = 10, // deeded object back to last owner's inventory - - DRD_COUNT = 11 -}; - - -const S32 SELECT_ALL_TES = -1; -const S32 SELECT_MAX_TES = 32; - -// Do something to all objects in the selection manager. -// The bool return value can be used to indicate if all -// objects are identical (gathering information) or if -// the operation was successful. -struct LLSelectedObjectFunctor -{ - virtual ~LLSelectedObjectFunctor() {}; - virtual bool apply(LLViewerObject* object) = 0; -}; - -// Do something to all select nodes in the selection manager. -// The bool return value can be used to indicate if all -// objects are identical (gathering information) or if -// the operation was successful. -struct LLSelectedNodeFunctor -{ - virtual ~LLSelectedNodeFunctor() {}; - virtual bool apply(LLSelectNode* node) = 0; -}; - -struct LLSelectedTEFunctor -{ - virtual ~LLSelectedTEFunctor() {}; - virtual bool apply(LLViewerObject* object, S32 face) = 0; -}; - -struct LLSelectedTEMaterialFunctor -{ - virtual ~LLSelectedTEMaterialFunctor() {}; - virtual LLMaterialPtr apply(LLViewerObject* object, S32 face, LLTextureEntry* tep, LLMaterialPtr& current_material) = 0; -}; - -template struct LLSelectedTEGetFunctor -{ - virtual ~LLSelectedTEGetFunctor() {}; - virtual T get(LLViewerObject* object, S32 te) = 0; -}; - -template struct LLCheckIdenticalFunctor -{ - static bool same(const T& a, const T& b, const T& tolerance); -}; - -typedef enum e_send_type -{ - SEND_ONLY_ROOTS, - SEND_INDIVIDUALS, - SEND_ROOTS_FIRST, // useful for serial undos on linked sets - SEND_CHILDREN_FIRST // useful for serial transforms of linked sets -} ESendType; - -typedef enum e_grid_mode -{ - GRID_MODE_WORLD, - GRID_MODE_LOCAL, - GRID_MODE_REF_OBJECT -} EGridMode; - -typedef enum e_action_type -{ - SELECT_ACTION_TYPE_BEGIN, - SELECT_ACTION_TYPE_PICK, - SELECT_ACTION_TYPE_MOVE, - SELECT_ACTION_TYPE_ROTATE, - SELECT_ACTION_TYPE_SCALE, - NUM_ACTION_TYPES -}EActionType; - -typedef enum e_selection_type -{ - SELECT_TYPE_WORLD, - SELECT_TYPE_ATTACHMENT, - SELECT_TYPE_HUD -}ESelectType; - -typedef std::vector > gltf_materials_vec_t; - -const S32 TE_SELECT_MASK_ALL = 0xFFFFFFFF; - -// Contains information about a selected object, particularly which TEs are selected. -class LLSelectNode -{ -public: - LLSelectNode(LLViewerObject* object, bool do_glow); - LLSelectNode(const LLSelectNode& nodep); - ~LLSelectNode(); - - void selectAllTEs(bool b); - void selectTE(S32 te_index, bool selected); - bool isTESelected(S32 te_index) const; - bool hasSelectedTE() const { return TE_SELECT_MASK_ALL & mTESelectMask; } - S32 getLastSelectedTE() const; - S32 getLastOperatedTE() const { return mLastTESelected; } - S32 getTESelectMask() { return mTESelectMask; } - void renderOneSilhouette(const LLColor4 &color); - void setTransient(bool transient) { mTransient = transient; } - bool isTransient() const { return mTransient; } - LLViewerObject* getObject(); - void setObject(LLViewerObject* object); - // *NOTE: invalidate stored textures and colors when # faces change - // Used by tools floater's color/texture pickers to restore changes - void saveColors(); - void saveShinyColors(); - void saveTextures(const uuid_vec_t& textures); - void saveTextureScaleRatios(LLRender::eTexIndex index_to_query); - - // GLTF materials are applied to objects by ids, - // overrides get applied on top of materials resulting in - // final gltf material that users see. - // Ids get applied and restored by tools floater, - // overrides get applied in live material editor - void saveGLTFMaterials(const uuid_vec_t& materials, const gltf_materials_vec_t& override_materials); - - bool allowOperationOnNode(PermissionBit op, U64 group_proxy_power) const; - -public: - bool mIndividualSelection; // For root objects and objects individually selected - - bool mTransient; - bool mValid; // is extra information valid? - LLPermissions* mPermissions; - LLSaleInfo mSaleInfo; - LLAggregatePermissions mAggregatePerm; - LLAggregatePermissions mAggregateTexturePerm; - LLAggregatePermissions mAggregateTexturePermOwner; - std::string mName; - std::string mDescription; - LLCategory mCategory; - S16 mInventorySerial; - LLVector3 mSavedPositionLocal; // for interactively modifying object position - LLVector3 mLastPositionLocal; - LLVector3d mSavedPositionGlobal; // for interactively modifying object position - LLVector3 mSavedScale; // for interactively modifying object scale - LLVector3 mLastScale; - LLQuaternion mSavedRotation; // for interactively modifying object rotation - LLQuaternion mLastRotation; - bool mDuplicated; - LLVector3d mDuplicatePos; - LLQuaternion mDuplicateRot; - LLUUID mItemID; - LLUUID mFolderID; - LLUUID mFromTaskID; - std::string mTouchName; - std::string mSitName; - U64 mCreationDate; - std::vector mSavedColors; - std::vector mSavedShinyColors; - uuid_vec_t mSavedTextures; - uuid_vec_t mSavedGLTFMaterialIds; - gltf_materials_vec_t mSavedGLTFOverrideMaterials; - std::vector mTextureScaleRatios; - std::vector mSilhouetteVertices; // array of vertices to render silhouette of object - std::vector mSilhouetteNormals; // array of normals to render silhouette of object - bool mSilhouetteExists; // need to generate silhouette? - -protected: - LLPointer mObject; - S32 mTESelectMask; - S32 mLastTESelected; -}; - -class LLObjectSelection : public LLRefCount -{ - friend class LLSelectMgr; - friend class LLSafeHandle; - friend class LLSelectionCallbackData; - -protected: - ~LLObjectSelection(); - -public: - typedef std::list list_t; - - // Iterators - struct is_non_null - { - bool operator()(LLSelectNode* node) - { - return (node->getObject() != NULL); - } - }; - typedef boost::filter_iterator iterator; - iterator begin() { return iterator(mList.begin(), mList.end()); } - iterator end() { return iterator(mList.end(), mList.end()); } - - struct is_valid - { - bool operator()(LLSelectNode* node) - { - return (node->getObject() != NULL) && node->mValid; - } - }; - typedef boost::filter_iterator valid_iterator; - valid_iterator valid_begin() { return valid_iterator(mList.begin(), mList.end()); } - valid_iterator valid_end() { return valid_iterator(mList.end(), mList.end()); } - - struct is_root - { - bool operator()(LLSelectNode* node); - }; - typedef boost::filter_iterator root_iterator; - root_iterator root_begin() { return root_iterator(mList.begin(), mList.end()); } - root_iterator root_end() { return root_iterator(mList.end(), mList.end()); } - - struct is_valid_root - { - bool operator()(LLSelectNode* node); - }; - typedef boost::filter_iterator valid_root_iterator; - valid_root_iterator valid_root_begin() { return valid_root_iterator(mList.begin(), mList.end()); } - valid_root_iterator valid_root_end() { return valid_root_iterator(mList.end(), mList.end()); } - - struct is_root_object - { - bool operator()(LLSelectNode* node); - }; - typedef boost::filter_iterator root_object_iterator; - root_object_iterator root_object_begin() { return root_object_iterator(mList.begin(), mList.end()); } - root_object_iterator root_object_end() { return root_object_iterator(mList.end(), mList.end()); } - -public: - LLObjectSelection(); - - void updateEffects(); - - bool isEmpty() const; - - LLSelectNode* getFirstNode(LLSelectedNodeFunctor* func = NULL); - LLSelectNode* getFirstRootNode(LLSelectedNodeFunctor* func = NULL, bool non_root_ok = false); - LLViewerObject* getFirstSelectedObject(LLSelectedNodeFunctor* func, bool get_parent = false); - LLViewerObject* getFirstObject(); - LLViewerObject* getFirstRootObject(bool non_root_ok = false); - - LLSelectNode* getFirstMoveableNode(bool get_root_first = false); - - LLViewerObject* getFirstEditableObject(bool get_parent = false); - LLViewerObject* getFirstCopyableObject(bool get_parent = false); - LLViewerObject* getFirstDeleteableObject(); - LLViewerObject* getFirstMoveableObject(bool get_parent = false); - LLViewerObject* getFirstUndoEnabledObject(bool get_parent = false); - - /// Return the object that lead to this selection, possible a child - LLViewerObject* getPrimaryObject() { return mPrimaryObject; } - - // iterate through texture entries - template bool getSelectedTEValue(LLSelectedTEGetFunctor* func, T& res, bool has_tolerance = false, T tolerance = T()); - template bool isMultipleTEValue(LLSelectedTEGetFunctor* func, const T& ignore_value); - - S32 getNumNodes(); - LLSelectNode* findNode(LLViewerObject* objectp); - - // count members - S32 getObjectCount(); - F32 getSelectedObjectCost(); - F32 getSelectedLinksetCost(); - F32 getSelectedPhysicsCost(); - F32 getSelectedLinksetPhysicsCost(); - S32 getSelectedObjectRenderCost(); - - F32 getSelectedObjectStreamingCost(S32* total_bytes = NULL, S32* visible_bytes = NULL); - U32 getSelectedObjectTriangleCount(S32* vcount = NULL); - - S32 getTECount(); - S32 getRootObjectCount(); - - bool isMultipleTESelected(); - bool contains(LLViewerObject* object); - bool contains(LLViewerObject* object, S32 te); - - // returns true is any node is currenly worn as an attachment - bool isAttachment(); - - bool checkAnimatedObjectEstTris(); - bool checkAnimatedObjectLinkable(); - - // Apply functors to various subsets of the selected objects - // If firstonly is false, returns the AND of all apply() calls. - // Else returns true immediately if any apply() call succeeds (i.e. OR with early exit) - bool applyToRootObjects(LLSelectedObjectFunctor* func, bool firstonly = false); - bool applyToObjects(LLSelectedObjectFunctor* func); - bool applyToTEs(LLSelectedTEFunctor* func, bool firstonly = false); - bool applyToRootNodes(LLSelectedNodeFunctor* func, bool firstonly = false); - bool applyToNodes(LLSelectedNodeFunctor* func, bool firstonly = false); - - /* - * Used to apply (no-copy) textures to the selected object or - * selected face/faces of the object. - * This method moves (no-copy) texture to the object's inventory - * and doesn't make copy of the texture for each face. - * Then this only texture is used for all selected faces. - */ - void applyNoCopyTextureToTEs(LLViewerInventoryItem* item); - /* - * Multi-purpose function for applying PBR materials to the - * selected object or faces, any combination of copy/mod/transfer - * permission restrictions. This method moves the restricted - * material to the object's inventory and doesn't make a copy of the - * material for each face. Then this only material is used for - * all selected faces. - * Returns false if applying the material failed on one or more selected - * faces. - */ - bool applyRestrictedPbrMaterialToTEs(LLViewerInventoryItem* item); - - ESelectType getSelectType() const { return mSelectType; } - -private: - void addNode(LLSelectNode *nodep); - void addNodeAtEnd(LLSelectNode *nodep); - void moveNodeToFront(LLSelectNode *nodep); - void removeNode(LLSelectNode *nodep); - void deleteAllNodes(); - void cleanupNodes(); - - -private: - list_t mList; - const LLObjectSelection &operator=(const LLObjectSelection &); - - LLPointer mPrimaryObject; - std::map, LLSelectNode*> mSelectNodeMap; - ESelectType mSelectType; -}; - -typedef LLSafeHandle LLObjectSelectionHandle; - -// Build time optimization, generate this once in .cpp file -#ifndef LLSELECTMGR_CPP -extern template class LLSelectMgr* LLSingleton::getInstance(); -#endif - -// For use with getFirstTest() -struct LLSelectGetFirstTest; - -// temporary storage, Ex: to attach objects after autopilot -class LLSelectionCallbackData -{ -public: - LLSelectionCallbackData(); - LLObjectSelectionHandle getSelection() { return mSelectedObjects; } -private: - LLObjectSelectionHandle mSelectedObjects; -}; - -class LLSelectMgr : public LLEditMenuHandler, public LLSimpleton -{ -public: - static bool sRectSelectInclusive; // do we need to surround an object to pick it? - static bool sRenderHiddenSelections; // do we show selection silhouettes that are occluded? - static bool sRenderLightRadius; // do we show the radius of selected lights? - - static F32 sHighlightThickness; - static F32 sHighlightUScale; - static F32 sHighlightVScale; - static F32 sHighlightAlpha; - static F32 sHighlightAlphaTest; - static F32 sHighlightUAnim; - static F32 sHighlightVAnim; - static LLColor4 sSilhouetteParentColor; - static LLColor4 sSilhouetteChildColor; - static LLColor4 sHighlightParentColor; - static LLColor4 sHighlightChildColor; - static LLColor4 sHighlightInspectColor; - static LLColor4 sContextSilhouetteColor; - - LLCachedControl mHideSelectedObjects; - LLCachedControl mRenderHighlightSelections; - LLCachedControl mAllowSelectAvatar; - LLCachedControl mDebugSelectMgr; - -public: - LLSelectMgr(); - ~LLSelectMgr(); - - static void cleanupGlobals(); - - // LLEditMenuHandler interface - virtual bool canUndo() const; - virtual void undo(); - - virtual bool canRedo() const; - virtual void redo(); - - virtual bool canDoDelete() const; - virtual void doDelete(); - - virtual void deselect(); - virtual bool canDeselect() const; - - virtual void duplicate(); - virtual bool canDuplicate() const; - - void clearSelections(); - void update(); - void updateEffects(); // Update HUD effects - - // When we edit object's position/rotation/scale we set local - // overrides and ignore any updates (override received valeus). - // When we send data to server, we send local values and reset - // overrides - void resetObjectOverrides(); - void resetObjectOverrides(LLObjectSelectionHandle selected_handle); - void overrideObjectUpdates(); - - void resetAvatarOverrides(); - void overrideAvatarUpdates(); - - struct AvatarPositionOverride - { - AvatarPositionOverride(); - AvatarPositionOverride(LLVector3 &vec, LLQuaternion &quat, LLViewerObject *obj) : - mLastPositionLocal(vec), - mLastRotation(quat), - mObject(obj) - { - } - LLVector3 mLastPositionLocal; - LLQuaternion mLastRotation; - LLPointer mObject; - }; - - // Avatar overrides should persist even after selection - // was removed as long as edit floater is up - typedef std::map uuid_av_override_map_t; - uuid_av_override_map_t mAvatarOverridesMap; -public: - - - // Returns the previous value of mForceSelection - bool setForceSelection(bool force); - - //////////////////////////////////////////////////////////////// - // Selection methods - //////////////////////////////////////////////////////////////// - - //////////////////////////////////////////////////////////////// - // Add - //////////////////////////////////////////////////////////////// - - // This method is meant to select an object, and then select all - // of the ancestors and descendants. This should be the normal behavior. - // - // *NOTE: You must hold on to the object selection handle, otherwise - // the objects will be automatically deselected in 1 frame. - LLObjectSelectionHandle selectObjectAndFamily(LLViewerObject* object, bool add_to_end = false, bool ignore_select_owned = false); - - // For when you want just a child object. - LLObjectSelectionHandle selectObjectOnly(LLViewerObject* object, S32 face = SELECT_ALL_TES); - - // Same as above, but takes a list of objects. Used by rectangle select. - LLObjectSelectionHandle selectObjectAndFamily(const std::vector& object_list, bool send_to_sim = true); - - // converts all objects currently highlighted to a selection, and returns it - LLObjectSelectionHandle selectHighlightedObjects(); - - LLObjectSelectionHandle setHoverObject(LLViewerObject *objectp, S32 face = -1); - LLSelectNode *getHoverNode(); - LLSelectNode *getPrimaryHoverNode(); - - void highlightObjectOnly(LLViewerObject *objectp); - void highlightObjectAndFamily(LLViewerObject *objectp); - void highlightObjectAndFamily(const std::vector& list); - - //////////////////////////////////////////////////////////////// - // Remove - //////////////////////////////////////////////////////////////// - - void deselectObjectOnly(LLViewerObject* object, bool send_to_sim = true); - void deselectObjectAndFamily(LLViewerObject* object, bool send_to_sim = true, bool include_entire_object = false); - - // Send deselect messages to simulator, then clear the list - void deselectAll(); - void deselectAllForStandingUp(); - - // deselect only if nothing else currently referencing the selection - void deselectUnused(); - - // Deselect if the selection center is too far away from the agent. - void deselectAllIfTooFar(); - - // Removes all highlighted objects from current selection - void deselectHighlightedObjects(); - - void unhighlightObjectOnly(LLViewerObject *objectp); - void unhighlightObjectAndFamily(LLViewerObject *objectp); - void unhighlightAll(); - - bool removeObjectFromSelections(const LLUUID &id); - - //////////////////////////////////////////////////////////////// - // Selection editing - //////////////////////////////////////////////////////////////// - bool linkObjects(); - - bool unlinkObjects(); - - void confirmUnlinkObjects(const LLSD& notification, const LLSD& response); - - bool enableLinkObjects(); - - bool enableUnlinkObjects(); - - //////////////////////////////////////////////////////////////// - // Selection accessors - //////////////////////////////////////////////////////////////// - LLObjectSelectionHandle getSelection() { return mSelectedObjects; } - // right now this just renders the selection with root/child colors instead of a single color - LLObjectSelectionHandle getEditSelection() { convertTransient(); return mSelectedObjects; } - LLObjectSelectionHandle getHighlightedObjects() { return mHighlightedObjects; } - - //////////////////////////////////////////////////////////////// - // Grid manipulation - //////////////////////////////////////////////////////////////// - void addGridObject(LLViewerObject* objectp); - void clearGridObjects(); - void setGridMode(EGridMode mode); - EGridMode getGridMode() { return mGridMode; } - void getGrid(LLVector3& origin, LLQuaternion& rotation, LLVector3 &scale, bool for_snap_guides = false); - - bool getTEMode() const { return mTEMode; } - void setTEMode(bool b) { mTEMode = b; } - - bool shouldShowSelection() const { return mShowSelection; } - - LLBBox getBBoxOfSelection() const; - LLBBox getSavedBBoxOfSelection() const { return mSavedSelectionBBox; } - - void dump(); - void cleanup(); - - void updateSilhouettes(); - void renderSilhouettes(bool for_hud); - void enableSilhouette(bool enable) { mRenderSilhouettes = enable; } - - //////////////////////////////////////////////////////////////// - // Utility functions that operate on the current selection - //////////////////////////////////////////////////////////////// - void saveSelectedObjectTransform(EActionType action_type); - void saveSelectedObjectColors(); - void saveSelectedShinyColors(); - void saveSelectedObjectTextures(); - - void selectionUpdatePhysics(bool use_physics); - void selectionUpdateTemporary(bool is_temporary); - void selectionUpdatePhantom(bool is_ghost); - void selectionDump(); - - bool selectionAllPCode(LLPCode code); // all objects have this PCode - bool selectionGetClickAction(U8 *out_action); - bool selectionGetIncludeInSearch(bool* include_in_search_out); // true if all selected objects have same - bool selectionGetGlow(F32 *glow); - - void selectionSetPhysicsType(U8 type); - void selectionSetGravity(F32 gravity); - void selectionSetFriction(F32 friction); - void selectionSetDensity(F32 density); - void selectionSetRestitution(F32 restitution); - void selectionSetMaterial(U8 material); - bool selectionSetImage(const LLUUID& imageid); // could be item or asset id - bool selectionSetGLTFMaterial(const LLUUID& mat_id); // material id only - void selectionSetColor(const LLColor4 &color); - void selectionSetColorOnly(const LLColor4 &color); // Set only the RGB channels - void selectionSetAlphaOnly(const F32 alpha); // Set only the alpha channel - void selectionRevertColors(); - void selectionRevertShinyColors(); - bool selectionRevertTextures(); - void selectionRevertGLTFMaterials(); - void selectionSetBumpmap( U8 bumpmap, const LLUUID &image_id ); - void selectionSetTexGen( U8 texgen ); - void selectionSetShiny( U8 shiny, const LLUUID &image_id ); - void selectionSetFullbright( U8 fullbright ); - void selectionSetMedia( U8 media_type, const LLSD &media_data ); - void selectionSetClickAction(U8 action); - void selectionSetIncludeInSearch(bool include_in_search); - void selectionSetGlow(const F32 glow); - void selectionSetMaterialParams(LLSelectedTEMaterialFunctor* material_func, int specific_te = -1); - void selectionRemoveMaterial(); - - void selectionSetObjectPermissions(U8 perm_field, bool set, U32 perm_mask, bool override = false); - void selectionSetObjectName(const std::string& name); - void selectionSetObjectDescription(const std::string& desc); - void selectionSetObjectCategory(const LLCategory& category); - void selectionSetObjectSaleInfo(const LLSaleInfo& sale_info); - - void selectionTexScaleAutofit(F32 repeats_per_meter); - void adjustTexturesByScale(bool send_to_sim, bool stretch); - - bool selectionMove(const LLVector3& displ, F32 rx, F32 ry, F32 rz, - U32 update_type); - void sendSelectionMove(); - - void sendGodlikeRequest(const std::string& request, const std::string& parameter); - - - // will make sure all selected object meet current criteria, or deselect them otherwise - void validateSelection(); - - // returns true if it is possible to select this object - bool canSelectObject(LLViewerObject* object, bool ignore_select_owned = false); - - // Returns true if the viewer has information on all selected objects - bool selectGetAllRootsValid(); - bool selectGetAllValid(); - bool selectGetAllValidAndObjectsFound(); - - // returns true if you can modify all selected objects. - bool selectGetRootsModify(); - bool selectGetModify(); - - // returns true if all objects are in same region - bool selectGetSameRegion(); - - // returns true if is all objects are non-permanent-enforced - bool selectGetRootsNonPermanentEnforced(); - bool selectGetNonPermanentEnforced(); - - // returns true if is all objects are permanent - bool selectGetRootsPermanent(); - bool selectGetPermanent(); - - // returns true if is all objects are character - bool selectGetRootsCharacter(); - bool selectGetCharacter(); - - // returns true if is all objects are not permanent - bool selectGetRootsNonPathfinding(); - bool selectGetNonPathfinding(); - - // returns true if is all objects are not permanent - bool selectGetRootsNonPermanent(); - bool selectGetNonPermanent(); - - // returns true if is all objects are not character - bool selectGetRootsNonCharacter(); - bool selectGetNonCharacter(); - - bool selectGetEditableLinksets(); - bool selectGetViewableCharacters(); - - // returns true if selected objects can be transferred. - bool selectGetRootsTransfer(); - - // returns true if selected objects can be copied. - bool selectGetRootsCopy(); - - bool selectGetCreator(LLUUID& id, std::string& name); // true if all have same creator, returns id - bool selectGetOwner(LLUUID& id, std::string& name); // true if all objects have same owner, returns id - bool selectGetLastOwner(LLUUID& id, std::string& name); // true if all objects have same owner, returns id - - // returns true if all are the same. id is stuffed with - // the value found if available. - bool selectGetGroup(LLUUID& id); - bool selectGetPerm( U8 which_perm, U32* mask_on, U32* mask_off); // true if all have data, returns two masks, each indicating which bits are all on and all off - - bool selectIsGroupOwned(); // true if all root objects have valid data and are group owned. - - // returns true if all the nodes are valid. Accumulates - // permissions in the parameter. - bool selectGetPermissions(LLPermissions& perm); - - // returns true if all the nodes are valid. Depends onto "edit linked" state - // Children in linksets are a bit special - they require not only move permission - // but also modify if "edit linked" is set, since you move them relative to parent - bool selectGetEditMoveLinksetPermissions(bool &move, bool &modify); - - // Get a bunch of useful sale information for the object(s) selected. - // "_mixed" is true if not all objects have the same setting. - void selectGetAggregateSaleInfo(U32 &num_for_sale, - bool &is_for_sale_mixed, - bool &is_sale_price_mixed, - S32 &total_sale_price, - S32 &individual_sale_price); - - // returns true if all nodes are valid. - bool selectGetCategory(LLCategory& category); - - // returns true if all nodes are valid. method also stores an - // accumulated sale info. - bool selectGetSaleInfo(LLSaleInfo& sale_info); - - // returns true if all nodes are valid. fills passed in object - // with the aggregate permissions of the selection. - bool selectGetAggregatePermissions(LLAggregatePermissions& ag_perm); - - // returns true if all nodes are valid. fills passed in object - // with the aggregate permissions for texture inventory items of the selection. - bool selectGetAggregateTexturePermissions(LLAggregatePermissions& ag_perm); - - LLPermissions* findObjectPermissions(const LLViewerObject* object); - - bool isMovableAvatarSelected(); - - void selectDelete(); // Delete on simulator - void selectForceDelete(); // just delete, no into trash - void selectDuplicate(const LLVector3& offset, bool select_copy); // Duplicate on simulator - void repeatDuplicate(); - void selectDuplicateOnRay(const LLVector3 &ray_start_region, - const LLVector3 &ray_end_region, - bool bypass_raycast, - bool ray_end_is_intersection, - const LLUUID &ray_target_id, - bool copy_centers, - bool copy_rotates, - bool select_copy); - - void sendMultipleUpdate(U32 type); // Position, rotation, scale all in one - void sendOwner(const LLUUID& owner_id, const LLUUID& group_id, bool override = false); - void sendGroup(const LLUUID& group_id); - - // Category ID is the UUID of the folder you want to contain the purchase. - // *NOTE: sale_info check doesn't work for multiple object buy, - // which UI does not currently support sale info is used for - // verification only, if it doesn't match region info then sale is - // canceled - void sendBuy(const LLUUID& buyer_id, const LLUUID& category_id, const LLSaleInfo sale_info); - void sendAttach(U8 attachment_point, bool replace); - void sendAttach(LLObjectSelectionHandle selection_handle, U8 attachment_point, bool replace); - void sendDetach(); - void sendDropAttachment(); - void sendLink(); - void sendDelink(); - //void sendHinge(U8 type); - //void sendDehinge(); - void sendSelect(); - - void requestObjectPropertiesFamily(LLViewerObject* object); // asks sim for creator, permissions, resources, etc. - static void processObjectProperties(LLMessageSystem *mesgsys, void **user_data); - static void processObjectPropertiesFamily(LLMessageSystem *mesgsys, void **user_data); - static void processForceObjectSelect(LLMessageSystem* msg, void**); - - void requestGodInfo(); - - LLVector3d getSelectionCenterGlobal() const { return mSelectionCenterGlobal; } - void updateSelectionCenter(); - - void pauseAssociatedAvatars(); - - void resetAgentHUDZoom(); - void setAgentHUDZoom(F32 target_zoom, F32 current_zoom); - void getAgentHUDZoom(F32 &target_zoom, F32 ¤t_zoom) const; - - void updatePointAt(); - - // Internal list maintenance functions. TODO: Make these private! - void remove(std::vector& objects); - void remove(LLViewerObject* object, S32 te = SELECT_ALL_TES, bool undoable = true); - void removeAll(); - void addAsIndividual(LLViewerObject* object, S32 te = SELECT_ALL_TES, bool undoable = true); - void promoteSelectionToRoot(); - void demoteSelectionToIndividuals(); - -private: - void convertTransient(); // converts temporarily selected objects to full-fledged selections - ESelectType getSelectTypeForObject(LLViewerObject* object); - void addAsFamily(std::vector& objects, bool add_to_end = false); - void generateSilhouette(LLSelectNode *nodep, const LLVector3& view_point); - void updateSelectionSilhouette(LLObjectSelectionHandle object_handle, S32& num_sils_genned, std::vector& changed_objects); - // Send one message to each region containing an object on selection list. - void sendListToRegions( const std::string& message_name, - void (*pack_header)(void *user_data), - void (*pack_body)(LLSelectNode* node, void *user_data), - void (*log_func)(LLSelectNode* node, void *user_data), - void *user_data, - ESendType send_type); - void sendListToRegions( LLObjectSelectionHandle selected_handle, - const std::string& message_name, - void (*pack_header)(void *user_data), - void (*pack_body)(LLSelectNode* node, void *user_data), - void (*log_func)(LLSelectNode* node, void *user_data), - void *user_data, - ESendType send_type); - - - static void packAgentID( void *); - static void packAgentAndSessionID(void* user_data); - static void packAgentAndGroupID(void* user_data); - static void packAgentAndSessionAndGroupID(void* user_data); - static void packAgentIDAndSessionAndAttachment(void*); - static void packAgentGroupAndCatID(void*); - static void packDeleteHeader(void* userdata); - static void packDeRezHeader(void* user_data); - static void packObjectID( LLSelectNode* node, void *); - static void packObjectIDAsParam(LLSelectNode* node, void *); - static void packObjectIDAndRotation(LLSelectNode* node, void *); - static void packObjectLocalID(LLSelectNode* node, void *); - static void packObjectClickAction(LLSelectNode* node, void* data); - static void packObjectIncludeInSearch(LLSelectNode* node, void* data); - static void packObjectName(LLSelectNode* node, void* user_data); - static void packObjectDescription(LLSelectNode* node, void* user_data); - static void packObjectCategory(LLSelectNode* node, void* user_data); - static void packObjectSaleInfo(LLSelectNode* node, void* user_data); - static void packBuyObjectIDs(LLSelectNode* node, void* user_data); - static void packDuplicate( LLSelectNode* node, void *duplicate_data); - static void packDuplicateHeader(void*); - static void packDuplicateOnRayHead(void *user_data); - static void packPermissions(LLSelectNode* node, void *user_data); - static void packDeselect( LLSelectNode* node, void *user_data); - static void packMultipleUpdate(LLSelectNode* node, void *user_data); - static void packPhysics(LLSelectNode* node, void *user_data); - static void packShape(LLSelectNode* node, void *user_data); - static void packOwnerHead(void *user_data); - static void packHingeHead(void *user_data); - static void packPermissionsHead(void* user_data); - static void packGodlikeHead(void* user_data); - static void logNoOp(LLSelectNode* node, void *user_data); - static void logAttachmentRequest(LLSelectNode* node, void *user_data); - static void logDetachRequest(LLSelectNode* node, void *user_data); - static bool confirmDelete(const LLSD& notification, const LLSD& response, LLObjectSelectionHandle handle); - - // Get the first ID that matches test and whether or not all ids are identical in selected objects. - void getFirst(LLSelectGetFirstTest* test); - -public: - // Observer/callback support for when object selection changes or - // properties are received/updated - typedef boost::signals2::signal< void ()> update_signal_t; - update_signal_t mUpdateSignal; - -private: - LLPointer mSilhouetteImagep; - LLObjectSelectionHandle mSelectedObjects; - LLObjectSelectionHandle mHoverObjects; - LLObjectSelectionHandle mHighlightedObjects; - std::set > mRectSelectedObjects; - - LLObjectSelection mGridObjects; - LLQuaternion mGridRotation; - LLVector3 mGridOrigin; - LLVector3 mGridScale; - EGridMode mGridMode; - - bool mTEMode; // render te - LLRender::eTexIndex mTextureChannel; // diff, norm, or spec, depending on UI editing mode - LLVector3d mSelectionCenterGlobal; - LLBBox mSelectionBBox; - - LLVector3d mLastSentSelectionCenterGlobal; - bool mShowSelection; // do we send the selection center name value and do we animate this selection? - LLVector3d mLastCameraPos; // camera position from last generation of selection silhouette - bool mRenderSilhouettes; // do we render the silhouette - LLBBox mSavedSelectionBBox; - - LLFrameTimer mEffectsTimer; - bool mForceSelection; - - std::vector mPauseRequests; -}; - -// *DEPRECATED: For callbacks or observers, use -// LLSelectMgr::getInstance()->mUpdateSignal.connect( callback ) -// Update subscribers to the selection list -void dialog_refresh_all(); - -// Templates -//----------------------------------------------------------------------------- -// getSelectedTEValue -//----------------------------------------------------------------------------- -template bool LLObjectSelection::getSelectedTEValue(LLSelectedTEGetFunctor* func, T& res, bool has_tolerance, T tolerance) -{ - bool have_first = false; - bool have_selected = false; - T selected_value = T(); - - // Now iterate through all TEs to test for sameness - bool identical = true; - for (iterator iter = begin(); iter != end(); iter++) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - S32 selected_te = -1; - if (object == getPrimaryObject()) - { - selected_te = node->getLastSelectedTE(); - } - for (S32 te = 0; te < object->getNumTEs(); ++te) - { - if (!node->isTESelected(te)) - { - continue; - } - T value = func->get(object, te); - if (!have_first) - { - have_first = true; - if (!have_selected) - { - selected_value = value; - } - } - else - { - if ( value != selected_value ) - { - if (!has_tolerance) - { - identical = false; - } - else if (!LLCheckIdenticalFunctor::same(value, selected_value, tolerance)) - { - identical = false; - } - } - if (te == selected_te) - { - selected_value = value; - have_selected = true; - } - } - } - if (!identical && have_selected) - { - break; - } - } - if (have_first || have_selected) - { - res = selected_value; - } - return identical; -} - -// Templates -//----------------------------------------------------------------------------- -// isMultipleTEValue iterate through all TEs and test for uniqueness -// with certain return value ignored when performing the test. -// e.g. when testing if the selection has a unique non-empty homeurl : -// you can set ignore_value = "" and it will only compare among the non-empty -// homeUrls and ignore the empty ones. -//----------------------------------------------------------------------------- -template bool LLObjectSelection::isMultipleTEValue(LLSelectedTEGetFunctor* func, const T& ignore_value) -{ - bool have_first = false; - T selected_value = T(); - - // Now iterate through all TEs to test for sameness - bool unique = true; - for (iterator iter = begin(); iter != end(); iter++) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - for (S32 te = 0; te < object->getNumTEs(); ++te) - { - if (!node->isTESelected(te)) - { - continue; - } - T value = func->get(object, te); - if(value == ignore_value) - { - continue; - } - if (!have_first) - { - have_first = true; - } - else - { - if (value !=selected_value ) - { - unique = false; - return !unique; - } - } - } - } - return !unique; -} - - -#endif +/** + * @file llselectmgr.h + * @brief A manager for selected objects and TEs. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSELECTMGR_H +#define LL_LLSELECTMGR_H + +#include "llcharacter.h" +#include "lleditmenuhandler.h" +#include "llundo.h" +#include "lluuid.h" +#include "llpointer.h" +#include "llsafehandle.h" +#include "llsaleinfo.h" +#include "llcategory.h" +#include "v3dmath.h" +#include "llquaternion.h" +#include "llcoord.h" +#include "llframetimer.h" +#include "llbbox.h" +#include "llpermissions.h" +#include "llcontrol.h" +#include "llviewerobject.h" // LLObjectSelection::getSelectedTEValue template +#include "llmaterial.h" + +#include +#include +#include +#include // boost::make_shared + +class LLMessageSystem; +class LLViewerTexture; +class LLColor4; +class LLVector3; +class LLSelectNode; + +const U8 UPD_NONE = 0x00; +const U8 UPD_POSITION = 0x01; +const U8 UPD_ROTATION = 0x02; +const U8 UPD_SCALE = 0x04; +const U8 UPD_LINKED_SETS = 0x08; +const U8 UPD_UNIFORM = 0x10; // used with UPD_SCALE + +// This is used by the DeRezObject message to determine where to put +// derezed tasks. +enum EDeRezDestination +{ + DRD_SAVE_INTO_AGENT_INVENTORY = 0, + DRD_ACQUIRE_TO_AGENT_INVENTORY = 1, // try to leave copy in world + DRD_SAVE_INTO_TASK_INVENTORY = 2, + DRD_ATTACHMENT = 3, + DRD_TAKE_INTO_AGENT_INVENTORY = 4, // delete from world + DRD_FORCE_TO_GOD_INVENTORY = 5, // force take copy + DRD_TRASH = 6, + DRD_ATTACHMENT_TO_INV = 7, + DRD_ATTACHMENT_EXISTS = 8, + DRD_RETURN_TO_OWNER = 9, // back to owner's inventory + DRD_RETURN_TO_LAST_OWNER = 10, // deeded object back to last owner's inventory + + DRD_COUNT = 11 +}; + + +const S32 SELECT_ALL_TES = -1; +const S32 SELECT_MAX_TES = 32; + +// Do something to all objects in the selection manager. +// The bool return value can be used to indicate if all +// objects are identical (gathering information) or if +// the operation was successful. +struct LLSelectedObjectFunctor +{ + virtual ~LLSelectedObjectFunctor() {}; + virtual bool apply(LLViewerObject* object) = 0; +}; + +// Do something to all select nodes in the selection manager. +// The bool return value can be used to indicate if all +// objects are identical (gathering information) or if +// the operation was successful. +struct LLSelectedNodeFunctor +{ + virtual ~LLSelectedNodeFunctor() {}; + virtual bool apply(LLSelectNode* node) = 0; +}; + +struct LLSelectedTEFunctor +{ + virtual ~LLSelectedTEFunctor() {}; + virtual bool apply(LLViewerObject* object, S32 face) = 0; +}; + +struct LLSelectedTEMaterialFunctor +{ + virtual ~LLSelectedTEMaterialFunctor() {}; + virtual LLMaterialPtr apply(LLViewerObject* object, S32 face, LLTextureEntry* tep, LLMaterialPtr& current_material) = 0; +}; + +template struct LLSelectedTEGetFunctor +{ + virtual ~LLSelectedTEGetFunctor() {}; + virtual T get(LLViewerObject* object, S32 te) = 0; +}; + +template struct LLCheckIdenticalFunctor +{ + static bool same(const T& a, const T& b, const T& tolerance); +}; + +typedef enum e_send_type +{ + SEND_ONLY_ROOTS, + SEND_INDIVIDUALS, + SEND_ROOTS_FIRST, // useful for serial undos on linked sets + SEND_CHILDREN_FIRST // useful for serial transforms of linked sets +} ESendType; + +typedef enum e_grid_mode +{ + GRID_MODE_WORLD, + GRID_MODE_LOCAL, + GRID_MODE_REF_OBJECT +} EGridMode; + +typedef enum e_action_type +{ + SELECT_ACTION_TYPE_BEGIN, + SELECT_ACTION_TYPE_PICK, + SELECT_ACTION_TYPE_MOVE, + SELECT_ACTION_TYPE_ROTATE, + SELECT_ACTION_TYPE_SCALE, + NUM_ACTION_TYPES +}EActionType; + +typedef enum e_selection_type +{ + SELECT_TYPE_WORLD, + SELECT_TYPE_ATTACHMENT, + SELECT_TYPE_HUD +}ESelectType; + +typedef std::vector > gltf_materials_vec_t; + +const S32 TE_SELECT_MASK_ALL = 0xFFFFFFFF; + +// Contains information about a selected object, particularly which TEs are selected. +class LLSelectNode +{ +public: + LLSelectNode(LLViewerObject* object, bool do_glow); + LLSelectNode(const LLSelectNode& nodep); + ~LLSelectNode(); + + void selectAllTEs(bool b); + void selectTE(S32 te_index, bool selected); + bool isTESelected(S32 te_index) const; + bool hasSelectedTE() const { return TE_SELECT_MASK_ALL & mTESelectMask; } + S32 getLastSelectedTE() const; + S32 getLastOperatedTE() const { return mLastTESelected; } + S32 getTESelectMask() { return mTESelectMask; } + void renderOneSilhouette(const LLColor4 &color); + void setTransient(bool transient) { mTransient = transient; } + bool isTransient() const { return mTransient; } + LLViewerObject* getObject(); + void setObject(LLViewerObject* object); + // *NOTE: invalidate stored textures and colors when # faces change + // Used by tools floater's color/texture pickers to restore changes + void saveColors(); + void saveShinyColors(); + void saveTextures(const uuid_vec_t& textures); + void saveTextureScaleRatios(LLRender::eTexIndex index_to_query); + + // GLTF materials are applied to objects by ids, + // overrides get applied on top of materials resulting in + // final gltf material that users see. + // Ids get applied and restored by tools floater, + // overrides get applied in live material editor + void saveGLTFMaterials(const uuid_vec_t& materials, const gltf_materials_vec_t& override_materials); + + bool allowOperationOnNode(PermissionBit op, U64 group_proxy_power) const; + +public: + bool mIndividualSelection; // For root objects and objects individually selected + + bool mTransient; + bool mValid; // is extra information valid? + LLPermissions* mPermissions; + LLSaleInfo mSaleInfo; + LLAggregatePermissions mAggregatePerm; + LLAggregatePermissions mAggregateTexturePerm; + LLAggregatePermissions mAggregateTexturePermOwner; + std::string mName; + std::string mDescription; + LLCategory mCategory; + S16 mInventorySerial; + LLVector3 mSavedPositionLocal; // for interactively modifying object position + LLVector3 mLastPositionLocal; + LLVector3d mSavedPositionGlobal; // for interactively modifying object position + LLVector3 mSavedScale; // for interactively modifying object scale + LLVector3 mLastScale; + LLQuaternion mSavedRotation; // for interactively modifying object rotation + LLQuaternion mLastRotation; + bool mDuplicated; + LLVector3d mDuplicatePos; + LLQuaternion mDuplicateRot; + LLUUID mItemID; + LLUUID mFolderID; + LLUUID mFromTaskID; + std::string mTouchName; + std::string mSitName; + U64 mCreationDate; + std::vector mSavedColors; + std::vector mSavedShinyColors; + uuid_vec_t mSavedTextures; + uuid_vec_t mSavedGLTFMaterialIds; + gltf_materials_vec_t mSavedGLTFOverrideMaterials; + std::vector mTextureScaleRatios; + std::vector mSilhouetteVertices; // array of vertices to render silhouette of object + std::vector mSilhouetteNormals; // array of normals to render silhouette of object + bool mSilhouetteExists; // need to generate silhouette? + +protected: + LLPointer mObject; + S32 mTESelectMask; + S32 mLastTESelected; +}; + +class LLObjectSelection : public LLRefCount +{ + friend class LLSelectMgr; + friend class LLSafeHandle; + friend class LLSelectionCallbackData; + +protected: + ~LLObjectSelection(); + +public: + typedef std::list list_t; + + // Iterators + struct is_non_null + { + bool operator()(LLSelectNode* node) + { + return (node->getObject() != NULL); + } + }; + typedef boost::filter_iterator iterator; + iterator begin() { return iterator(mList.begin(), mList.end()); } + iterator end() { return iterator(mList.end(), mList.end()); } + + struct is_valid + { + bool operator()(LLSelectNode* node) + { + return (node->getObject() != NULL) && node->mValid; + } + }; + typedef boost::filter_iterator valid_iterator; + valid_iterator valid_begin() { return valid_iterator(mList.begin(), mList.end()); } + valid_iterator valid_end() { return valid_iterator(mList.end(), mList.end()); } + + struct is_root + { + bool operator()(LLSelectNode* node); + }; + typedef boost::filter_iterator root_iterator; + root_iterator root_begin() { return root_iterator(mList.begin(), mList.end()); } + root_iterator root_end() { return root_iterator(mList.end(), mList.end()); } + + struct is_valid_root + { + bool operator()(LLSelectNode* node); + }; + typedef boost::filter_iterator valid_root_iterator; + valid_root_iterator valid_root_begin() { return valid_root_iterator(mList.begin(), mList.end()); } + valid_root_iterator valid_root_end() { return valid_root_iterator(mList.end(), mList.end()); } + + struct is_root_object + { + bool operator()(LLSelectNode* node); + }; + typedef boost::filter_iterator root_object_iterator; + root_object_iterator root_object_begin() { return root_object_iterator(mList.begin(), mList.end()); } + root_object_iterator root_object_end() { return root_object_iterator(mList.end(), mList.end()); } + +public: + LLObjectSelection(); + + void updateEffects(); + + bool isEmpty() const; + + LLSelectNode* getFirstNode(LLSelectedNodeFunctor* func = NULL); + LLSelectNode* getFirstRootNode(LLSelectedNodeFunctor* func = NULL, bool non_root_ok = false); + LLViewerObject* getFirstSelectedObject(LLSelectedNodeFunctor* func, bool get_parent = false); + LLViewerObject* getFirstObject(); + LLViewerObject* getFirstRootObject(bool non_root_ok = false); + + LLSelectNode* getFirstMoveableNode(bool get_root_first = false); + + LLViewerObject* getFirstEditableObject(bool get_parent = false); + LLViewerObject* getFirstCopyableObject(bool get_parent = false); + LLViewerObject* getFirstDeleteableObject(); + LLViewerObject* getFirstMoveableObject(bool get_parent = false); + LLViewerObject* getFirstUndoEnabledObject(bool get_parent = false); + + /// Return the object that lead to this selection, possible a child + LLViewerObject* getPrimaryObject() { return mPrimaryObject; } + + // iterate through texture entries + template bool getSelectedTEValue(LLSelectedTEGetFunctor* func, T& res, bool has_tolerance = false, T tolerance = T()); + template bool isMultipleTEValue(LLSelectedTEGetFunctor* func, const T& ignore_value); + + S32 getNumNodes(); + LLSelectNode* findNode(LLViewerObject* objectp); + + // count members + S32 getObjectCount(); + F32 getSelectedObjectCost(); + F32 getSelectedLinksetCost(); + F32 getSelectedPhysicsCost(); + F32 getSelectedLinksetPhysicsCost(); + S32 getSelectedObjectRenderCost(); + + F32 getSelectedObjectStreamingCost(S32* total_bytes = NULL, S32* visible_bytes = NULL); + U32 getSelectedObjectTriangleCount(S32* vcount = NULL); + + S32 getTECount(); + S32 getRootObjectCount(); + + bool isMultipleTESelected(); + bool contains(LLViewerObject* object); + bool contains(LLViewerObject* object, S32 te); + + // returns true is any node is currenly worn as an attachment + bool isAttachment(); + + bool checkAnimatedObjectEstTris(); + bool checkAnimatedObjectLinkable(); + + // Apply functors to various subsets of the selected objects + // If firstonly is false, returns the AND of all apply() calls. + // Else returns true immediately if any apply() call succeeds (i.e. OR with early exit) + bool applyToRootObjects(LLSelectedObjectFunctor* func, bool firstonly = false); + bool applyToObjects(LLSelectedObjectFunctor* func); + bool applyToTEs(LLSelectedTEFunctor* func, bool firstonly = false); + bool applyToRootNodes(LLSelectedNodeFunctor* func, bool firstonly = false); + bool applyToNodes(LLSelectedNodeFunctor* func, bool firstonly = false); + + /* + * Used to apply (no-copy) textures to the selected object or + * selected face/faces of the object. + * This method moves (no-copy) texture to the object's inventory + * and doesn't make copy of the texture for each face. + * Then this only texture is used for all selected faces. + */ + void applyNoCopyTextureToTEs(LLViewerInventoryItem* item); + /* + * Multi-purpose function for applying PBR materials to the + * selected object or faces, any combination of copy/mod/transfer + * permission restrictions. This method moves the restricted + * material to the object's inventory and doesn't make a copy of the + * material for each face. Then this only material is used for + * all selected faces. + * Returns false if applying the material failed on one or more selected + * faces. + */ + bool applyRestrictedPbrMaterialToTEs(LLViewerInventoryItem* item); + + ESelectType getSelectType() const { return mSelectType; } + +private: + void addNode(LLSelectNode *nodep); + void addNodeAtEnd(LLSelectNode *nodep); + void moveNodeToFront(LLSelectNode *nodep); + void removeNode(LLSelectNode *nodep); + void deleteAllNodes(); + void cleanupNodes(); + + +private: + list_t mList; + const LLObjectSelection &operator=(const LLObjectSelection &); + + LLPointer mPrimaryObject; + std::map, LLSelectNode*> mSelectNodeMap; + ESelectType mSelectType; +}; + +typedef LLSafeHandle LLObjectSelectionHandle; + +// Build time optimization, generate this once in .cpp file +#ifndef LLSELECTMGR_CPP +extern template class LLSelectMgr* LLSingleton::getInstance(); +#endif + +// For use with getFirstTest() +struct LLSelectGetFirstTest; + +// temporary storage, Ex: to attach objects after autopilot +class LLSelectionCallbackData +{ +public: + LLSelectionCallbackData(); + LLObjectSelectionHandle getSelection() { return mSelectedObjects; } +private: + LLObjectSelectionHandle mSelectedObjects; +}; + +class LLSelectMgr : public LLEditMenuHandler, public LLSimpleton +{ +public: + static bool sRectSelectInclusive; // do we need to surround an object to pick it? + static bool sRenderHiddenSelections; // do we show selection silhouettes that are occluded? + static bool sRenderLightRadius; // do we show the radius of selected lights? + + static F32 sHighlightThickness; + static F32 sHighlightUScale; + static F32 sHighlightVScale; + static F32 sHighlightAlpha; + static F32 sHighlightAlphaTest; + static F32 sHighlightUAnim; + static F32 sHighlightVAnim; + static LLColor4 sSilhouetteParentColor; + static LLColor4 sSilhouetteChildColor; + static LLColor4 sHighlightParentColor; + static LLColor4 sHighlightChildColor; + static LLColor4 sHighlightInspectColor; + static LLColor4 sContextSilhouetteColor; + + LLCachedControl mHideSelectedObjects; + LLCachedControl mRenderHighlightSelections; + LLCachedControl mAllowSelectAvatar; + LLCachedControl mDebugSelectMgr; + +public: + LLSelectMgr(); + ~LLSelectMgr(); + + static void cleanupGlobals(); + + // LLEditMenuHandler interface + virtual bool canUndo() const; + virtual void undo(); + + virtual bool canRedo() const; + virtual void redo(); + + virtual bool canDoDelete() const; + virtual void doDelete(); + + virtual void deselect(); + virtual bool canDeselect() const; + + virtual void duplicate(); + virtual bool canDuplicate() const; + + void clearSelections(); + void update(); + void updateEffects(); // Update HUD effects + + // When we edit object's position/rotation/scale we set local + // overrides and ignore any updates (override received valeus). + // When we send data to server, we send local values and reset + // overrides + void resetObjectOverrides(); + void resetObjectOverrides(LLObjectSelectionHandle selected_handle); + void overrideObjectUpdates(); + + void resetAvatarOverrides(); + void overrideAvatarUpdates(); + + struct AvatarPositionOverride + { + AvatarPositionOverride(); + AvatarPositionOverride(LLVector3 &vec, LLQuaternion &quat, LLViewerObject *obj) : + mLastPositionLocal(vec), + mLastRotation(quat), + mObject(obj) + { + } + LLVector3 mLastPositionLocal; + LLQuaternion mLastRotation; + LLPointer mObject; + }; + + // Avatar overrides should persist even after selection + // was removed as long as edit floater is up + typedef std::map uuid_av_override_map_t; + uuid_av_override_map_t mAvatarOverridesMap; +public: + + + // Returns the previous value of mForceSelection + bool setForceSelection(bool force); + + //////////////////////////////////////////////////////////////// + // Selection methods + //////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////// + // Add + //////////////////////////////////////////////////////////////// + + // This method is meant to select an object, and then select all + // of the ancestors and descendants. This should be the normal behavior. + // + // *NOTE: You must hold on to the object selection handle, otherwise + // the objects will be automatically deselected in 1 frame. + LLObjectSelectionHandle selectObjectAndFamily(LLViewerObject* object, bool add_to_end = false, bool ignore_select_owned = false); + + // For when you want just a child object. + LLObjectSelectionHandle selectObjectOnly(LLViewerObject* object, S32 face = SELECT_ALL_TES); + + // Same as above, but takes a list of objects. Used by rectangle select. + LLObjectSelectionHandle selectObjectAndFamily(const std::vector& object_list, bool send_to_sim = true); + + // converts all objects currently highlighted to a selection, and returns it + LLObjectSelectionHandle selectHighlightedObjects(); + + LLObjectSelectionHandle setHoverObject(LLViewerObject *objectp, S32 face = -1); + LLSelectNode *getHoverNode(); + LLSelectNode *getPrimaryHoverNode(); + + void highlightObjectOnly(LLViewerObject *objectp); + void highlightObjectAndFamily(LLViewerObject *objectp); + void highlightObjectAndFamily(const std::vector& list); + + //////////////////////////////////////////////////////////////// + // Remove + //////////////////////////////////////////////////////////////// + + void deselectObjectOnly(LLViewerObject* object, bool send_to_sim = true); + void deselectObjectAndFamily(LLViewerObject* object, bool send_to_sim = true, bool include_entire_object = false); + + // Send deselect messages to simulator, then clear the list + void deselectAll(); + void deselectAllForStandingUp(); + + // deselect only if nothing else currently referencing the selection + void deselectUnused(); + + // Deselect if the selection center is too far away from the agent. + void deselectAllIfTooFar(); + + // Removes all highlighted objects from current selection + void deselectHighlightedObjects(); + + void unhighlightObjectOnly(LLViewerObject *objectp); + void unhighlightObjectAndFamily(LLViewerObject *objectp); + void unhighlightAll(); + + bool removeObjectFromSelections(const LLUUID &id); + + //////////////////////////////////////////////////////////////// + // Selection editing + //////////////////////////////////////////////////////////////// + bool linkObjects(); + + bool unlinkObjects(); + + void confirmUnlinkObjects(const LLSD& notification, const LLSD& response); + + bool enableLinkObjects(); + + bool enableUnlinkObjects(); + + //////////////////////////////////////////////////////////////// + // Selection accessors + //////////////////////////////////////////////////////////////// + LLObjectSelectionHandle getSelection() { return mSelectedObjects; } + // right now this just renders the selection with root/child colors instead of a single color + LLObjectSelectionHandle getEditSelection() { convertTransient(); return mSelectedObjects; } + LLObjectSelectionHandle getHighlightedObjects() { return mHighlightedObjects; } + + //////////////////////////////////////////////////////////////// + // Grid manipulation + //////////////////////////////////////////////////////////////// + void addGridObject(LLViewerObject* objectp); + void clearGridObjects(); + void setGridMode(EGridMode mode); + EGridMode getGridMode() { return mGridMode; } + void getGrid(LLVector3& origin, LLQuaternion& rotation, LLVector3 &scale, bool for_snap_guides = false); + + bool getTEMode() const { return mTEMode; } + void setTEMode(bool b) { mTEMode = b; } + + bool shouldShowSelection() const { return mShowSelection; } + + LLBBox getBBoxOfSelection() const; + LLBBox getSavedBBoxOfSelection() const { return mSavedSelectionBBox; } + + void dump(); + void cleanup(); + + void updateSilhouettes(); + void renderSilhouettes(bool for_hud); + void enableSilhouette(bool enable) { mRenderSilhouettes = enable; } + + //////////////////////////////////////////////////////////////// + // Utility functions that operate on the current selection + //////////////////////////////////////////////////////////////// + void saveSelectedObjectTransform(EActionType action_type); + void saveSelectedObjectColors(); + void saveSelectedShinyColors(); + void saveSelectedObjectTextures(); + + void selectionUpdatePhysics(bool use_physics); + void selectionUpdateTemporary(bool is_temporary); + void selectionUpdatePhantom(bool is_ghost); + void selectionDump(); + + bool selectionAllPCode(LLPCode code); // all objects have this PCode + bool selectionGetClickAction(U8 *out_action); + bool selectionGetIncludeInSearch(bool* include_in_search_out); // true if all selected objects have same + bool selectionGetGlow(F32 *glow); + + void selectionSetPhysicsType(U8 type); + void selectionSetGravity(F32 gravity); + void selectionSetFriction(F32 friction); + void selectionSetDensity(F32 density); + void selectionSetRestitution(F32 restitution); + void selectionSetMaterial(U8 material); + bool selectionSetImage(const LLUUID& imageid); // could be item or asset id + bool selectionSetGLTFMaterial(const LLUUID& mat_id); // material id only + void selectionSetColor(const LLColor4 &color); + void selectionSetColorOnly(const LLColor4 &color); // Set only the RGB channels + void selectionSetAlphaOnly(const F32 alpha); // Set only the alpha channel + void selectionRevertColors(); + void selectionRevertShinyColors(); + bool selectionRevertTextures(); + void selectionRevertGLTFMaterials(); + void selectionSetBumpmap( U8 bumpmap, const LLUUID &image_id ); + void selectionSetTexGen( U8 texgen ); + void selectionSetShiny( U8 shiny, const LLUUID &image_id ); + void selectionSetFullbright( U8 fullbright ); + void selectionSetMedia( U8 media_type, const LLSD &media_data ); + void selectionSetClickAction(U8 action); + void selectionSetIncludeInSearch(bool include_in_search); + void selectionSetGlow(const F32 glow); + void selectionSetMaterialParams(LLSelectedTEMaterialFunctor* material_func, int specific_te = -1); + void selectionRemoveMaterial(); + + void selectionSetObjectPermissions(U8 perm_field, bool set, U32 perm_mask, bool override = false); + void selectionSetObjectName(const std::string& name); + void selectionSetObjectDescription(const std::string& desc); + void selectionSetObjectCategory(const LLCategory& category); + void selectionSetObjectSaleInfo(const LLSaleInfo& sale_info); + + void selectionTexScaleAutofit(F32 repeats_per_meter); + void adjustTexturesByScale(bool send_to_sim, bool stretch); + + bool selectionMove(const LLVector3& displ, F32 rx, F32 ry, F32 rz, + U32 update_type); + void sendSelectionMove(); + + void sendGodlikeRequest(const std::string& request, const std::string& parameter); + + + // will make sure all selected object meet current criteria, or deselect them otherwise + void validateSelection(); + + // returns true if it is possible to select this object + bool canSelectObject(LLViewerObject* object, bool ignore_select_owned = false); + + // Returns true if the viewer has information on all selected objects + bool selectGetAllRootsValid(); + bool selectGetAllValid(); + bool selectGetAllValidAndObjectsFound(); + + // returns true if you can modify all selected objects. + bool selectGetRootsModify(); + bool selectGetModify(); + + // returns true if all objects are in same region + bool selectGetSameRegion(); + + // returns true if is all objects are non-permanent-enforced + bool selectGetRootsNonPermanentEnforced(); + bool selectGetNonPermanentEnforced(); + + // returns true if is all objects are permanent + bool selectGetRootsPermanent(); + bool selectGetPermanent(); + + // returns true if is all objects are character + bool selectGetRootsCharacter(); + bool selectGetCharacter(); + + // returns true if is all objects are not permanent + bool selectGetRootsNonPathfinding(); + bool selectGetNonPathfinding(); + + // returns true if is all objects are not permanent + bool selectGetRootsNonPermanent(); + bool selectGetNonPermanent(); + + // returns true if is all objects are not character + bool selectGetRootsNonCharacter(); + bool selectGetNonCharacter(); + + bool selectGetEditableLinksets(); + bool selectGetViewableCharacters(); + + // returns true if selected objects can be transferred. + bool selectGetRootsTransfer(); + + // returns true if selected objects can be copied. + bool selectGetRootsCopy(); + + bool selectGetCreator(LLUUID& id, std::string& name); // true if all have same creator, returns id + bool selectGetOwner(LLUUID& id, std::string& name); // true if all objects have same owner, returns id + bool selectGetLastOwner(LLUUID& id, std::string& name); // true if all objects have same owner, returns id + + // returns true if all are the same. id is stuffed with + // the value found if available. + bool selectGetGroup(LLUUID& id); + bool selectGetPerm( U8 which_perm, U32* mask_on, U32* mask_off); // true if all have data, returns two masks, each indicating which bits are all on and all off + + bool selectIsGroupOwned(); // true if all root objects have valid data and are group owned. + + // returns true if all the nodes are valid. Accumulates + // permissions in the parameter. + bool selectGetPermissions(LLPermissions& perm); + + // returns true if all the nodes are valid. Depends onto "edit linked" state + // Children in linksets are a bit special - they require not only move permission + // but also modify if "edit linked" is set, since you move them relative to parent + bool selectGetEditMoveLinksetPermissions(bool &move, bool &modify); + + // Get a bunch of useful sale information for the object(s) selected. + // "_mixed" is true if not all objects have the same setting. + void selectGetAggregateSaleInfo(U32 &num_for_sale, + bool &is_for_sale_mixed, + bool &is_sale_price_mixed, + S32 &total_sale_price, + S32 &individual_sale_price); + + // returns true if all nodes are valid. + bool selectGetCategory(LLCategory& category); + + // returns true if all nodes are valid. method also stores an + // accumulated sale info. + bool selectGetSaleInfo(LLSaleInfo& sale_info); + + // returns true if all nodes are valid. fills passed in object + // with the aggregate permissions of the selection. + bool selectGetAggregatePermissions(LLAggregatePermissions& ag_perm); + + // returns true if all nodes are valid. fills passed in object + // with the aggregate permissions for texture inventory items of the selection. + bool selectGetAggregateTexturePermissions(LLAggregatePermissions& ag_perm); + + LLPermissions* findObjectPermissions(const LLViewerObject* object); + + bool isMovableAvatarSelected(); + + void selectDelete(); // Delete on simulator + void selectForceDelete(); // just delete, no into trash + void selectDuplicate(const LLVector3& offset, bool select_copy); // Duplicate on simulator + void repeatDuplicate(); + void selectDuplicateOnRay(const LLVector3 &ray_start_region, + const LLVector3 &ray_end_region, + bool bypass_raycast, + bool ray_end_is_intersection, + const LLUUID &ray_target_id, + bool copy_centers, + bool copy_rotates, + bool select_copy); + + void sendMultipleUpdate(U32 type); // Position, rotation, scale all in one + void sendOwner(const LLUUID& owner_id, const LLUUID& group_id, bool override = false); + void sendGroup(const LLUUID& group_id); + + // Category ID is the UUID of the folder you want to contain the purchase. + // *NOTE: sale_info check doesn't work for multiple object buy, + // which UI does not currently support sale info is used for + // verification only, if it doesn't match region info then sale is + // canceled + void sendBuy(const LLUUID& buyer_id, const LLUUID& category_id, const LLSaleInfo sale_info); + void sendAttach(U8 attachment_point, bool replace); + void sendAttach(LLObjectSelectionHandle selection_handle, U8 attachment_point, bool replace); + void sendDetach(); + void sendDropAttachment(); + void sendLink(); + void sendDelink(); + //void sendHinge(U8 type); + //void sendDehinge(); + void sendSelect(); + + void requestObjectPropertiesFamily(LLViewerObject* object); // asks sim for creator, permissions, resources, etc. + static void processObjectProperties(LLMessageSystem *mesgsys, void **user_data); + static void processObjectPropertiesFamily(LLMessageSystem *mesgsys, void **user_data); + static void processForceObjectSelect(LLMessageSystem* msg, void**); + + void requestGodInfo(); + + LLVector3d getSelectionCenterGlobal() const { return mSelectionCenterGlobal; } + void updateSelectionCenter(); + + void pauseAssociatedAvatars(); + + void resetAgentHUDZoom(); + void setAgentHUDZoom(F32 target_zoom, F32 current_zoom); + void getAgentHUDZoom(F32 &target_zoom, F32 ¤t_zoom) const; + + void updatePointAt(); + + // Internal list maintenance functions. TODO: Make these private! + void remove(std::vector& objects); + void remove(LLViewerObject* object, S32 te = SELECT_ALL_TES, bool undoable = true); + void removeAll(); + void addAsIndividual(LLViewerObject* object, S32 te = SELECT_ALL_TES, bool undoable = true); + void promoteSelectionToRoot(); + void demoteSelectionToIndividuals(); + +private: + void convertTransient(); // converts temporarily selected objects to full-fledged selections + ESelectType getSelectTypeForObject(LLViewerObject* object); + void addAsFamily(std::vector& objects, bool add_to_end = false); + void generateSilhouette(LLSelectNode *nodep, const LLVector3& view_point); + void updateSelectionSilhouette(LLObjectSelectionHandle object_handle, S32& num_sils_genned, std::vector& changed_objects); + // Send one message to each region containing an object on selection list. + void sendListToRegions( const std::string& message_name, + void (*pack_header)(void *user_data), + void (*pack_body)(LLSelectNode* node, void *user_data), + void (*log_func)(LLSelectNode* node, void *user_data), + void *user_data, + ESendType send_type); + void sendListToRegions( LLObjectSelectionHandle selected_handle, + const std::string& message_name, + void (*pack_header)(void *user_data), + void (*pack_body)(LLSelectNode* node, void *user_data), + void (*log_func)(LLSelectNode* node, void *user_data), + void *user_data, + ESendType send_type); + + + static void packAgentID( void *); + static void packAgentAndSessionID(void* user_data); + static void packAgentAndGroupID(void* user_data); + static void packAgentAndSessionAndGroupID(void* user_data); + static void packAgentIDAndSessionAndAttachment(void*); + static void packAgentGroupAndCatID(void*); + static void packDeleteHeader(void* userdata); + static void packDeRezHeader(void* user_data); + static void packObjectID( LLSelectNode* node, void *); + static void packObjectIDAsParam(LLSelectNode* node, void *); + static void packObjectIDAndRotation(LLSelectNode* node, void *); + static void packObjectLocalID(LLSelectNode* node, void *); + static void packObjectClickAction(LLSelectNode* node, void* data); + static void packObjectIncludeInSearch(LLSelectNode* node, void* data); + static void packObjectName(LLSelectNode* node, void* user_data); + static void packObjectDescription(LLSelectNode* node, void* user_data); + static void packObjectCategory(LLSelectNode* node, void* user_data); + static void packObjectSaleInfo(LLSelectNode* node, void* user_data); + static void packBuyObjectIDs(LLSelectNode* node, void* user_data); + static void packDuplicate( LLSelectNode* node, void *duplicate_data); + static void packDuplicateHeader(void*); + static void packDuplicateOnRayHead(void *user_data); + static void packPermissions(LLSelectNode* node, void *user_data); + static void packDeselect( LLSelectNode* node, void *user_data); + static void packMultipleUpdate(LLSelectNode* node, void *user_data); + static void packPhysics(LLSelectNode* node, void *user_data); + static void packShape(LLSelectNode* node, void *user_data); + static void packOwnerHead(void *user_data); + static void packHingeHead(void *user_data); + static void packPermissionsHead(void* user_data); + static void packGodlikeHead(void* user_data); + static void logNoOp(LLSelectNode* node, void *user_data); + static void logAttachmentRequest(LLSelectNode* node, void *user_data); + static void logDetachRequest(LLSelectNode* node, void *user_data); + static bool confirmDelete(const LLSD& notification, const LLSD& response, LLObjectSelectionHandle handle); + + // Get the first ID that matches test and whether or not all ids are identical in selected objects. + void getFirst(LLSelectGetFirstTest* test); + +public: + // Observer/callback support for when object selection changes or + // properties are received/updated + typedef boost::signals2::signal< void ()> update_signal_t; + update_signal_t mUpdateSignal; + +private: + LLPointer mSilhouetteImagep; + LLObjectSelectionHandle mSelectedObjects; + LLObjectSelectionHandle mHoverObjects; + LLObjectSelectionHandle mHighlightedObjects; + std::set > mRectSelectedObjects; + + LLObjectSelection mGridObjects; + LLQuaternion mGridRotation; + LLVector3 mGridOrigin; + LLVector3 mGridScale; + EGridMode mGridMode; + + bool mTEMode; // render te + LLRender::eTexIndex mTextureChannel; // diff, norm, or spec, depending on UI editing mode + LLVector3d mSelectionCenterGlobal; + LLBBox mSelectionBBox; + + LLVector3d mLastSentSelectionCenterGlobal; + bool mShowSelection; // do we send the selection center name value and do we animate this selection? + LLVector3d mLastCameraPos; // camera position from last generation of selection silhouette + bool mRenderSilhouettes; // do we render the silhouette + LLBBox mSavedSelectionBBox; + + LLFrameTimer mEffectsTimer; + bool mForceSelection; + + std::vector mPauseRequests; +}; + +// *DEPRECATED: For callbacks or observers, use +// LLSelectMgr::getInstance()->mUpdateSignal.connect( callback ) +// Update subscribers to the selection list +void dialog_refresh_all(); + +// Templates +//----------------------------------------------------------------------------- +// getSelectedTEValue +//----------------------------------------------------------------------------- +template bool LLObjectSelection::getSelectedTEValue(LLSelectedTEGetFunctor* func, T& res, bool has_tolerance, T tolerance) +{ + bool have_first = false; + bool have_selected = false; + T selected_value = T(); + + // Now iterate through all TEs to test for sameness + bool identical = true; + for (iterator iter = begin(); iter != end(); iter++) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + S32 selected_te = -1; + if (object == getPrimaryObject()) + { + selected_te = node->getLastSelectedTE(); + } + for (S32 te = 0; te < object->getNumTEs(); ++te) + { + if (!node->isTESelected(te)) + { + continue; + } + T value = func->get(object, te); + if (!have_first) + { + have_first = true; + if (!have_selected) + { + selected_value = value; + } + } + else + { + if ( value != selected_value ) + { + if (!has_tolerance) + { + identical = false; + } + else if (!LLCheckIdenticalFunctor::same(value, selected_value, tolerance)) + { + identical = false; + } + } + if (te == selected_te) + { + selected_value = value; + have_selected = true; + } + } + } + if (!identical && have_selected) + { + break; + } + } + if (have_first || have_selected) + { + res = selected_value; + } + return identical; +} + +// Templates +//----------------------------------------------------------------------------- +// isMultipleTEValue iterate through all TEs and test for uniqueness +// with certain return value ignored when performing the test. +// e.g. when testing if the selection has a unique non-empty homeurl : +// you can set ignore_value = "" and it will only compare among the non-empty +// homeUrls and ignore the empty ones. +//----------------------------------------------------------------------------- +template bool LLObjectSelection::isMultipleTEValue(LLSelectedTEGetFunctor* func, const T& ignore_value) +{ + bool have_first = false; + T selected_value = T(); + + // Now iterate through all TEs to test for sameness + bool unique = true; + for (iterator iter = begin(); iter != end(); iter++) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + for (S32 te = 0; te < object->getNumTEs(); ++te) + { + if (!node->isTESelected(te)) + { + continue; + } + T value = func->get(object, te); + if(value == ignore_value) + { + continue; + } + if (!have_first) + { + have_first = true; + } + else + { + if (value !=selected_value ) + { + unique = false; + return !unique; + } + } + } + } + return !unique; +} + + +#endif diff --git a/indra/newview/llsettingspicker.h b/indra/newview/llsettingspicker.h index a6fa307a63..29827dfb94 100644 --- a/indra/newview/llsettingspicker.h +++ b/indra/newview/llsettingspicker.h @@ -1,137 +1,137 @@ -/** - * @file llsettingspicker.h - * @author Rider Linden - * @brief LLSettingsPicker class header file including related functions - * - * $LicenseInfo:firstyear=2018&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2018, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_SETTINGSPICKER_H -#define LL_SETTINGSPICKER_H - -#include "llinventorysettings.h" -#include "llfloater.h" -#include "llpermissionsflags.h" -#include "llfolderview.h" -#include "llinventory.h" -#include "llsettingsdaycycle.h" - -#include - -//========================================================================= -class LLFilterEditor; -class LLInventoryPanel; - -//========================================================================= -class LLFloaterSettingsPicker : public LLFloater -{ -public: - enum ETrackMode - { - TRACK_NONE, - TRACK_WATER, - TRACK_SKY - }; - typedef std::function close_callback_t; - typedef std::function id_changed_callback_t; - - LLFloaterSettingsPicker(LLView * owner, LLUUID setting_item_id, const LLSD ¶ms = LLSD()); - - virtual ~LLFloaterSettingsPicker() override; - - void setActive(bool active); - - virtual bool postBuild() override; - virtual void onClose(bool app_quitting) override; - virtual void draw() override; - - void setSettingsItemId(const LLUUID &settings_id, bool set_selection = true); - LLUUID getSettingsItemId() const { return mSettingItemID; } - - void setSettingsFilter(LLSettingsType::type_e type); - LLSettingsType::type_e getSettingsFilter() const { return mSettingsType; } - - // Only for day cycle - void setTrackMode(ETrackMode mode); - void setTrackWater() { mTrackMode = TRACK_WATER; } - void setTrackSky() { mTrackMode = TRACK_SKY; } - - // Takes a UUID, wraps get/setImageAssetID - virtual void setValue(const LLSD& value) override; - virtual LLSD getValue() const override; - - static LLUUID findItemID(const LLUUID& asset_id, bool copyable_only, bool ignore_library = false) - { - LLInventoryItem *pitem = findItem(asset_id, copyable_only, ignore_library); - if (pitem) - return pitem->getUUID(); - return LLUUID::null; - } - - static std::string findItemName(const LLUUID& asset_id, bool copyable_only, bool ignore_library = false) - { - LLInventoryItem *pitem = findItem(asset_id, copyable_only, ignore_library); - if (pitem) - return pitem->getName(); - return std::string(); - } - - static LLInventoryItem * findItem(const LLUUID& asset_id, bool copyable_only, bool ignore_library); - - -private: - typedef std::deque itemlist_t; - - void onFilterEdit(const std::string& search_string); - void onSelectionChange(const itemlist_t &items, bool user_action); - static void onAssetLoadedCb(LLHandle handle, LLUUID item_id, LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status); - void onAssetLoaded(LLUUID asset_id, LLSettingsBase::ptr_t settings); - void onButtonCancel(); - void onButtonSelect(); - virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; - bool handleKeyHere(KEY key, MASK mask) override; - void onFocusLost() override; - - - LLHandle mOwnerHandle; - LLUUID mSettingItemID; - LLUUID mSettingAssetID; - ETrackMode mTrackMode; - - LLFilterEditor * mFilterEdit; - LLInventoryPanel * mInventoryPanel; - LLSettingsType::type_e mSettingsType; - - F32 mContextConeOpacity; - PermissionMask mImmediateFilterPermMask; - - bool mActive; - bool mNoCopySettingsSelected; - - LLSaveFolderState mSavedFolderState; - -// boost::signals2::signal mCommitSignal; - boost::signals2::signal mCloseSignal; - boost::signals2::signal mChangeIDSignal; -}; - -#endif // LL_LLTEXTURECTRL_H +/** + * @file llsettingspicker.h + * @author Rider Linden + * @brief LLSettingsPicker class header file including related functions + * + * $LicenseInfo:firstyear=2018&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2018, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_SETTINGSPICKER_H +#define LL_SETTINGSPICKER_H + +#include "llinventorysettings.h" +#include "llfloater.h" +#include "llpermissionsflags.h" +#include "llfolderview.h" +#include "llinventory.h" +#include "llsettingsdaycycle.h" + +#include + +//========================================================================= +class LLFilterEditor; +class LLInventoryPanel; + +//========================================================================= +class LLFloaterSettingsPicker : public LLFloater +{ +public: + enum ETrackMode + { + TRACK_NONE, + TRACK_WATER, + TRACK_SKY + }; + typedef std::function close_callback_t; + typedef std::function id_changed_callback_t; + + LLFloaterSettingsPicker(LLView * owner, LLUUID setting_item_id, const LLSD ¶ms = LLSD()); + + virtual ~LLFloaterSettingsPicker() override; + + void setActive(bool active); + + virtual bool postBuild() override; + virtual void onClose(bool app_quitting) override; + virtual void draw() override; + + void setSettingsItemId(const LLUUID &settings_id, bool set_selection = true); + LLUUID getSettingsItemId() const { return mSettingItemID; } + + void setSettingsFilter(LLSettingsType::type_e type); + LLSettingsType::type_e getSettingsFilter() const { return mSettingsType; } + + // Only for day cycle + void setTrackMode(ETrackMode mode); + void setTrackWater() { mTrackMode = TRACK_WATER; } + void setTrackSky() { mTrackMode = TRACK_SKY; } + + // Takes a UUID, wraps get/setImageAssetID + virtual void setValue(const LLSD& value) override; + virtual LLSD getValue() const override; + + static LLUUID findItemID(const LLUUID& asset_id, bool copyable_only, bool ignore_library = false) + { + LLInventoryItem *pitem = findItem(asset_id, copyable_only, ignore_library); + if (pitem) + return pitem->getUUID(); + return LLUUID::null; + } + + static std::string findItemName(const LLUUID& asset_id, bool copyable_only, bool ignore_library = false) + { + LLInventoryItem *pitem = findItem(asset_id, copyable_only, ignore_library); + if (pitem) + return pitem->getName(); + return std::string(); + } + + static LLInventoryItem * findItem(const LLUUID& asset_id, bool copyable_only, bool ignore_library); + + +private: + typedef std::deque itemlist_t; + + void onFilterEdit(const std::string& search_string); + void onSelectionChange(const itemlist_t &items, bool user_action); + static void onAssetLoadedCb(LLHandle handle, LLUUID item_id, LLUUID asset_id, LLSettingsBase::ptr_t settings, S32 status); + void onAssetLoaded(LLUUID asset_id, LLSettingsBase::ptr_t settings); + void onButtonCancel(); + void onButtonSelect(); + virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; + bool handleKeyHere(KEY key, MASK mask) override; + void onFocusLost() override; + + + LLHandle mOwnerHandle; + LLUUID mSettingItemID; + LLUUID mSettingAssetID; + ETrackMode mTrackMode; + + LLFilterEditor * mFilterEdit; + LLInventoryPanel * mInventoryPanel; + LLSettingsType::type_e mSettingsType; + + F32 mContextConeOpacity; + PermissionMask mImmediateFilterPermMask; + + bool mActive; + bool mNoCopySettingsSelected; + + LLSaveFolderState mSavedFolderState; + +// boost::signals2::signal mCommitSignal; + boost::signals2::signal mCloseSignal; + boost::signals2::signal mChangeIDSignal; +}; + +#endif // LL_LLTEXTURECTRL_H diff --git a/indra/newview/llsettingsvo.cpp b/indra/newview/llsettingsvo.cpp index 5a318a4092..645c42cbee 100644 --- a/indra/newview/llsettingsvo.cpp +++ b/indra/newview/llsettingsvo.cpp @@ -1,1569 +1,1569 @@ -/** -* @file llsettingsvo.cpp -* @author Rider Linden -* @brief Subclasses for viewer specific settings behaviors. -* -* $LicenseInfo:2011&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2017, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#include "llviewerprecompiledheaders.h" -#include "llviewercontrol.h" -#include "llsettingsvo.h" - -#include "pipeline.h" - -#include -#include -#include -#include "lltrace.h" -#include "llfasttimer.h" -#include "v3colorutil.h" - -#include "llglslshader.h" -#include "llviewershadermgr.h" - -#include "llagent.h" -#include "llassettype.h" -#include "llfloaterperms.h" -#include "llnotificationsutil.h" - -#include "llviewerregion.h" -#include "llviewerassetupload.h" -#include "llviewerinventory.h" - -#include "llenvironment.h" -#include "llsky.h" - -#include "llpermissions.h" - -#include "llinventorymodel.h" -#include "llassetstorage.h" -#include "llfilesystem.h" -#include "lldrawpoolwater.h" - -#include -#include "llinventoryobserver.h" -#include "llinventorydefines.h" -#include "llworld.h" - -#include "lltrans.h" - -#undef VERIFY_LEGACY_CONVERSION - -extern bool gCubeSnapshot; - -//========================================================================= -namespace -{ - LLSD ensure_array_4(LLSD in, F32 fill); - LLSD read_legacy_preset_data(const std::string &name, const std::string& path, LLSD &messages); - - //------------------------------------------------------------------------- - class LLSettingsInventoryCB : public LLInventoryCallback - { - public: - typedef std::function callback_t; - - LLSettingsInventoryCB(callback_t cbfn) : - mCbfn(cbfn) - { } - - void fire(const LLUUID& inv_item) override { if (mCbfn) mCbfn(inv_item); } - - private: - callback_t mCbfn; - }; - - //------------------------------------------------------------------------- -} - - -//========================================================================= -void LLSettingsVOBase::createNewInventoryItem(LLSettingsType::type_e stype, const LLUUID& parent_id, std::function created_cb) -{ - inventory_result_fn cb = NULL; - - if (created_cb != NULL) - { - cb = [created_cb](LLUUID asset_id, LLUUID inventory_id, LLUUID object_id, LLSD results) - { - created_cb(inventory_id); - }; - } - createNewInventoryItem(stype, parent_id, cb); -} - -void LLSettingsVOBase::createNewInventoryItem(LLSettingsType::type_e stype, const LLUUID &parent_id, inventory_result_fn callback) -{ - LLTransactionID tid; - U32 nextOwnerPerm = LLFloaterPerms::getNextOwnerPerms("Settings"); - nextOwnerPerm |= PERM_COPY; - - if (!LLEnvironment::instance().isInventoryEnabled()) - { - LL_WARNS("SETTINGS") << "Region does not support settings inventory objects." << LL_ENDL; - LLNotificationsUtil::add("SettingsUnsuported"); - return; - } - - tid.generate(); - - LLPointer cb = new LLSettingsInventoryCB([callback](const LLUUID &inventoryId) { - LLSettingsVOBase::onInventoryItemCreated(inventoryId, LLSettingsBase::ptr_t(), callback); - }); - - create_inventory_settings(gAgent.getID(), gAgent.getSessionID(), - parent_id, LLTransactionID::tnull, - LLSettingsType::getDefaultName(stype), "", - stype, nextOwnerPerm, cb); -} - - -void LLSettingsVOBase::createInventoryItem(const LLSettingsBase::ptr_t &settings, const LLUUID &parent_id, std::string settings_name, inventory_result_fn callback) -{ - U32 nextOwnerPerm = LLFloaterPerms::getNextOwnerPerms("Settings"); - createInventoryItem(settings, nextOwnerPerm, parent_id, settings_name, callback); -} - -void LLSettingsVOBase::createInventoryItem(const LLSettingsBase::ptr_t &settings, U32 next_owner_perm, const LLUUID &parent_id, std::string settings_name, inventory_result_fn callback) -{ - LLTransactionID tid; - - if (!LLEnvironment::instance().isInventoryEnabled()) - { - LL_WARNS("SETTINGS") << "Region does not support settings inventory objects." << LL_ENDL; - LLNotificationsUtil::add("SettingsUnsuported"); - return; - } - - tid.generate(); - - LLPointer cb = new LLSettingsInventoryCB([settings, callback](const LLUUID &inventoryId) { - LLSettingsVOBase::onInventoryItemCreated(inventoryId, settings, callback); - }); - - if (settings_name.empty()) - { - settings_name = settings->getName(); - } - create_inventory_settings(gAgent.getID(), gAgent.getSessionID(), - parent_id, tid, - settings_name, "", - settings->getSettingsTypeValue(), next_owner_perm, cb); -} - -void LLSettingsVOBase::onInventoryItemCreated(const LLUUID &inventoryId, LLSettingsBase::ptr_t settings, inventory_result_fn callback) -{ - LLViewerInventoryItem *pitem = gInventory.getItem(inventoryId); - if (pitem) - { - LLPermissions perm = pitem->getPermissions(); - if (perm.getMaskEveryone() != PERM_COPY) - { - perm.setMaskEveryone(PERM_COPY); - pitem->setPermissions(perm); - pitem->updateServer(false); - } - } - if (!settings) - { // The item was created as new with no settings passed in. Simulator should have given it the default for the type... check ID, - // no need to upload asset. - LLUUID asset_id; - if (pitem) - { - asset_id = pitem->getAssetUUID(); - } - if (callback) - callback(asset_id, inventoryId, LLUUID::null, LLSD()); - return; - } - // We need to update some inventory stuff here.... maybe. - updateInventoryItem(settings, inventoryId, callback, false); -} - -void LLSettingsVOBase::updateInventoryItem(const LLSettingsBase::ptr_t &settings, LLUUID inv_item_id, inventory_result_fn callback, bool update_name) -{ - const LLViewerRegion* region = gAgent.getRegion(); - if (!region) - { - LL_WARNS("SETTINGS") << "Not connected to a region, cannot save setting." << LL_ENDL; - return; - } - - std::string agent_url(region->getCapability("UpdateSettingsAgentInventory")); - - if (!LLEnvironment::instance().isInventoryEnabled()) - { - LL_WARNS("SETTINGS") << "Region does not support settings inventory objects." << LL_ENDL; - LLNotificationsUtil::add("SettingsUnsuported"); - return; - } - - LLViewerInventoryItem *inv_item = gInventory.getItem(inv_item_id); - if (inv_item) - { - bool need_update(false); - LLPointer new_item = new LLViewerInventoryItem(inv_item); - - if (settings->getFlag(LLSettingsBase::FLAG_NOTRANS) && new_item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID())) - { - LLPermissions perm(inv_item->getPermissions()); - perm.setBaseBits(LLUUID::null, false, PERM_TRANSFER); - perm.setOwnerBits(LLUUID::null, false, PERM_TRANSFER); - new_item->setPermissions(perm); - need_update |= true; - } - if (update_name && (settings->getName() != new_item->getName())) - { - new_item->rename(settings->getName()); - settings->setName(new_item->getName()); // account for corrections - need_update |= true; - } - if (need_update) - { - new_item->updateServer(false); - gInventory.updateItem(new_item); - gInventory.notifyObservers(); - } - } - - std::stringstream buffer; - LLSD settingdata(settings->getSettings()); - LLSDSerialize::serialize(settingdata, buffer, LLSDSerialize::LLSD_NOTATION); - - LLResourceUploadInfo::ptr_t uploadInfo = std::make_shared(inv_item_id, LLAssetType::AT_SETTINGS, buffer.str(), - [settings, callback](LLUUID itemId, LLUUID newAssetId, LLUUID newItemId, LLSD response) - { - LLSettingsVOBase::onAgentAssetUploadComplete(itemId, newAssetId, newItemId, response, settings, callback); - }, - nullptr); - - LLViewerAssetUpload::EnqueueInventoryUpload(agent_url, uploadInfo); -} - -void LLSettingsVOBase::updateInventoryItem(const LLSettingsBase::ptr_t &settings, LLUUID object_id, LLUUID inv_item_id, inventory_result_fn callback) -{ - const LLViewerRegion* region = gAgent.getRegion(); - if (!region) - { - LL_WARNS("SETTINGS") << "Not connected to a region, cannot save setting." << LL_ENDL; - return; - } - - std::string agent_url(region->getCapability("UpdateSettingsAgentInventory")); - - if (!LLEnvironment::instance().isInventoryEnabled()) - { - LL_WARNS("SETTINGS") << "Region does not support settings inventory objects." << LL_ENDL; - LLNotificationsUtil::add("SettingsUnsuported"); - return; - } - - std::stringstream buffer; - LLSD settingdata(settings->getSettings()); - - LLSDSerialize::serialize(settingdata, buffer, LLSDSerialize::LLSD_NOTATION); - - LLResourceUploadInfo::ptr_t uploadInfo = std::make_shared(object_id, inv_item_id, LLAssetType::AT_SETTINGS, buffer.str(), - [settings, callback](LLUUID itemId, LLUUID taskId, LLUUID newAssetId, LLSD response) - { - LLSettingsVOBase::onTaskAssetUploadComplete(itemId, taskId, newAssetId, response, settings, callback); - }, - nullptr); - - LLViewerAssetUpload::EnqueueInventoryUpload(agent_url, uploadInfo); -} - -void LLSettingsVOBase::onAgentAssetUploadComplete(LLUUID itemId, LLUUID newAssetId, LLUUID newItemId, LLSD response, LLSettingsBase::ptr_t psettings, inventory_result_fn callback) -{ - LL_INFOS("SETTINGS") << "itemId:" << itemId << " newAssetId:" << newAssetId << " newItemId:" << newItemId << " response:" << response << LL_ENDL; - psettings->setAssetId(newAssetId); - if (callback) - callback( newAssetId, itemId, LLUUID::null, response ); -} - -void LLSettingsVOBase::onTaskAssetUploadComplete(LLUUID itemId, LLUUID taskId, LLUUID newAssetId, LLSD response, LLSettingsBase::ptr_t psettings, inventory_result_fn callback) -{ - LL_INFOS("SETTINGS") << "Upload to task complete!" << LL_ENDL; - psettings->setAssetId(newAssetId); - if (callback) - callback(newAssetId, itemId, taskId, response); -} - - -void LLSettingsVOBase::getSettingsAsset(const LLUUID &assetId, LLSettingsVOBase::asset_download_fn callback) -{ - gAssetStorage->getAssetData(assetId, LLAssetType::AT_SETTINGS, - [callback](const LLUUID &asset_id, LLAssetType::EType, void *, S32 status, LLExtStat ext_status) - { onAssetDownloadComplete(asset_id, status, ext_status, callback); }, - nullptr, true); - -} - -void LLSettingsVOBase::onAssetDownloadComplete(const LLUUID &asset_id, S32 status, LLExtStat ext_status, LLSettingsVOBase::asset_download_fn callback) -{ - LLSettingsBase::ptr_t settings; - if (!status) - { - LLFileSystem file(asset_id, LLAssetType::AT_SETTINGS, LLFileSystem::READ); - S32 size = file.getSize(); - - std::string buffer(size + 1, '\0'); - file.read((U8 *)buffer.data(), size); - - std::stringstream llsdstream(buffer); - LLSD llsdsettings; - - if (LLSDSerialize::deserialize(llsdsettings, llsdstream, LLSDSerialize::SIZE_UNLIMITED)) - { - settings = createFromLLSD(llsdsettings); - } - - if (!settings) - { - status = 1; - LL_WARNS("SETTINGS") << "Unable to create settings object." << LL_ENDL; - } - else - { - settings->setAssetId(asset_id); - } - } - else - { - LL_WARNS("SETTINGS") << "Error retrieving asset " << asset_id << ". Status code=" << status << "(" << LLAssetStorage::getErrorString(status) << ") ext_status=" << (U32)ext_status << LL_ENDL; - } - if (callback) - callback(asset_id, settings, status, ext_status); -} - -void LLSettingsVOBase::getSettingsInventory(const LLUUID &inventoryId, inventory_download_fn callback) -{ - -} - -bool LLSettingsVOBase::exportFile(const LLSettingsBase::ptr_t &settings, const std::string &filename, LLSDSerialize::ELLSD_Serialize format) -{ - try - { - std::ofstream file(filename, std::ios::out | std::ios::trunc); - file.exceptions(std::ios_base::failbit | std::ios_base::badbit); - - if (!file) - { - LL_WARNS("SETTINGS") << "Unable to open '" << filename << "' for writing." << LL_ENDL; - return false; - } - - LLSDSerialize::serialize(settings->getSettings(), file, format); - } - catch (const std::ios_base::failure &e) - { - LL_WARNS("SETTINGS") << "Unable to save settings to file '" << filename << "': " << e.what() << LL_ENDL; - return false; - } - - return true; -} - -LLSettingsBase::ptr_t LLSettingsVOBase::importFile(const std::string &filename) -{ - LLSD settings; - - try - { - std::ifstream file(filename, std::ios::in); - file.exceptions(std::ios_base::failbit | std::ios_base::badbit); - - if (!file) - { - LL_WARNS("SETTINGS") << "Unable to open '" << filename << "' for reading." << LL_ENDL; - return LLSettingsBase::ptr_t(); - } - - if (!LLSDSerialize::deserialize(settings, file, LLSDSerialize::SIZE_UNLIMITED)) - { - LL_WARNS("SETTINGS") << "Unable to deserialize settings from '" << filename << "'" << LL_ENDL; - return LLSettingsBase::ptr_t(); - } - } - catch (const std::ios_base::failure &e) - { - LL_WARNS("SETTINGS") << "Unable to save settings to file '" << filename << "': " << e.what() << LL_ENDL; - return LLSettingsBase::ptr_t(); - } - - return createFromLLSD(settings); -} - -LLSettingsBase::ptr_t LLSettingsVOBase::createFromLLSD(const LLSD &settings) -{ - if (!settings.has(SETTING_TYPE)) - { - LL_WARNS("SETTINGS") << "No settings type in LLSD" << LL_ENDL; - return LLSettingsBase::ptr_t(); - } - - std::string settingtype = settings[SETTING_TYPE].asString(); - - LLSettingsBase::ptr_t psetting; - - if (settingtype == "water") - { - return LLSettingsVOWater::buildWater(settings); - } - else if (settingtype == "sky") - { - return LLSettingsVOSky::buildSky(settings); - } - else if (settingtype == "daycycle") - { - return LLSettingsVODay::buildDay(settings); - } - - LL_WARNS("SETTINGS") << "Unable to determine settings type for '" << settingtype << "'." << LL_ENDL; - return LLSettingsBase::ptr_t(); - -} - -//========================================================================= -LLSettingsVOSky::LLSettingsVOSky(const LLSD &data, bool isAdvanced) -: LLSettingsSky(data) -, m_isAdvanced(isAdvanced) -{ -} - -LLSettingsVOSky::LLSettingsVOSky() -: LLSettingsSky() -, m_isAdvanced(false) -{ -} - -//------------------------------------------------------------------------- -LLSettingsSky::ptr_t LLSettingsVOSky::buildSky(LLSD settings) -{ - LLSettingsSky::validation_list_t validations = LLSettingsSky::validationList(); - - LLSD results = LLSettingsBase::settingValidation(settings, validations); - - if (!results["success"].asBoolean()) - { - LL_WARNS("SETTINGS") << "Sky setting validation failed!\n" << results << LL_ENDL; - LLSettingsSky::ptr_t(); - } - - return std::make_shared(settings, true); -} - - -LLSettingsSky::ptr_t LLSettingsVOSky::buildFromLegacyPreset(const std::string &name, const LLSD &oldsettings, LLSD &messages) -{ - - LLSD newsettings = LLSettingsSky::translateLegacySettings(oldsettings); - if (newsettings.isUndefined()) - { - messages["REASONS"] = LLTrans::getString("SettingTranslateError", LLSDMap("NAME", name)); - return LLSettingsSky::ptr_t(); - } - - newsettings[SETTING_NAME] = name; - - LLSettingsSky::validation_list_t validations = LLSettingsSky::validationList(); - LLSD results = LLSettingsBase::settingValidation(newsettings, validations); - if (!results["success"].asBoolean()) - { - messages["REASONS"] = LLTrans::getString("SettingValidationError", LLSDMap("NAME", name)); - LL_WARNS("SETTINGS") << "Sky setting validation failed!\n" << results << LL_ENDL; - LLSettingsSky::ptr_t(); - } - - LLSettingsSky::ptr_t skyp = std::make_shared(newsettings); - -#ifdef VERIFY_LEGACY_CONVERSION - LLSD oldsettings = LLSettingsVOSky::convertToLegacy(skyp, isAdvanced()); - - if (!llsd_equals(oldsettings, oldsettings)) - { - LL_WARNS("SKY") << "Conversion to/from legacy does not match!\n" - << "Old: " << oldsettings - << "new: " << oldsettings << LL_ENDL; - } - -#endif - - return skyp; -} - -LLSettingsSky::ptr_t LLSettingsVOSky::buildFromLegacyPresetFile(const std::string &name, const std::string &path, LLSD &messages) -{ - LLSD legacy_data = read_legacy_preset_data(name, path, messages); - - if (!legacy_data) - { // messages filled in by read_legacy_preset_data - LL_WARNS("SETTINGS") << "Could not load legacy Windlight \"" << name << "\" from " << path << LL_ENDL; - return ptr_t(); - } - - return buildFromLegacyPreset(LLURI::unescape(name), legacy_data, messages); -} - - -LLSettingsSky::ptr_t LLSettingsVOSky::buildDefaultSky() -{ - static LLSD default_settings; - - if (!default_settings.size()) - { - default_settings = LLSettingsSky::defaults(); - - default_settings[SETTING_NAME] = DEFAULT_SETTINGS_NAME; - - LLSettingsSky::validation_list_t validations = LLSettingsSky::validationList(); - LLSD results = LLSettingsBase::settingValidation(default_settings, validations); - if (!results["success"].asBoolean()) - { - LL_WARNS("SETTINGS") << "Sky setting validation failed!\n" << results << LL_ENDL; - LLSettingsSky::ptr_t(); - } - } - - LLSettingsSky::ptr_t skyp = std::make_shared(default_settings); - return skyp; -} - -LLSettingsSky::ptr_t LLSettingsVOSky::buildClone() const -{ - LLSD settings = cloneSettings(); - U32 flags = getFlags(); - - LLSettingsSky::validation_list_t validations = LLSettingsSky::validationList(); - LLSD results = LLSettingsBase::settingValidation(settings, validations); - if (!results["success"].asBoolean()) - { - LL_WARNS("SETTINGS") << "Sky setting validation failed!\n" << results << LL_ENDL; - LLSettingsSky::ptr_t(); - } - - LLSettingsSky::ptr_t skyp = std::make_shared(settings); - skyp->setFlags(flags); - return skyp; -} - -void LLSettingsVOSky::convertAtmosphericsToLegacy(LLSD& legacy, LLSD& settings) -{ - // These may need to be inferred from new settings' density profiles - // if the legacy settings values are not available. - if (settings.has(SETTING_LEGACY_HAZE)) - { - LLSD legacyhaze = settings[SETTING_LEGACY_HAZE]; - - // work-around for setter formerly putting ambient values in wrong loc... - if (legacyhaze.has(SETTING_AMBIENT)) - { - legacy[SETTING_AMBIENT] = ensure_array_4(legacyhaze[SETTING_AMBIENT], 1.0f); - } - else if (settings.has(SETTING_AMBIENT)) - { - legacy[SETTING_AMBIENT] = ensure_array_4(settings[SETTING_AMBIENT], 1.0f); - } - - legacy[SETTING_BLUE_DENSITY] = ensure_array_4(legacyhaze[SETTING_BLUE_DENSITY], 1.0); - legacy[SETTING_BLUE_HORIZON] = ensure_array_4(legacyhaze[SETTING_BLUE_HORIZON], 1.0); - - legacy[SETTING_DENSITY_MULTIPLIER] = llsd::array(legacyhaze[SETTING_DENSITY_MULTIPLIER].asReal(), 0.0f, 0.0f, 1.0f); - legacy[SETTING_DISTANCE_MULTIPLIER] = llsd::array(legacyhaze[SETTING_DISTANCE_MULTIPLIER].asReal(), 0.0f, 0.0f, 1.0f); - - legacy[SETTING_HAZE_DENSITY] = llsd::array(legacyhaze[SETTING_HAZE_DENSITY], 0.0f, 0.0f, 1.0f); - legacy[SETTING_HAZE_HORIZON] = llsd::array(legacyhaze[SETTING_HAZE_HORIZON], 0.0f, 0.0f, 1.0f); - } -} - -LLSD LLSettingsVOSky::convertToLegacy(const LLSettingsSky::ptr_t &psky, bool isAdvanced) -{ - LLSD legacy(LLSD::emptyMap()); - LLSD settings = psky->getSettings(); - - convertAtmosphericsToLegacy(legacy, settings); - - legacy[SETTING_CLOUD_COLOR] = ensure_array_4(settings[SETTING_CLOUD_COLOR], 1.0); - legacy[SETTING_CLOUD_POS_DENSITY1] = ensure_array_4(settings[SETTING_CLOUD_POS_DENSITY1], 1.0); - legacy[SETTING_CLOUD_POS_DENSITY2] = ensure_array_4(settings[SETTING_CLOUD_POS_DENSITY2], 1.0); - legacy[SETTING_CLOUD_SCALE] = llsd::array(settings[SETTING_CLOUD_SCALE], LLSD::Real(0.0), LLSD::Real(0.0), LLSD::Real(1.0)); - legacy[SETTING_CLOUD_SCROLL_RATE] = settings[SETTING_CLOUD_SCROLL_RATE]; - legacy[SETTING_LEGACY_ENABLE_CLOUD_SCROLL] = llsd::array(LLSD::Boolean(!is_approx_zero(settings[SETTING_CLOUD_SCROLL_RATE][0].asReal())), - LLSD::Boolean(!is_approx_zero(settings[SETTING_CLOUD_SCROLL_RATE][1].asReal()))); - legacy[SETTING_CLOUD_SHADOW] = llsd::array(settings[SETTING_CLOUD_SHADOW].asReal(), 0.0f, 0.0f, 1.0f); - legacy[SETTING_GAMMA] = llsd::array(settings[SETTING_GAMMA], 0.0f, 0.0f, 1.0f); - legacy[SETTING_GLOW] = ensure_array_4(settings[SETTING_GLOW], 1.0); - legacy[SETTING_LIGHT_NORMAL] = ensure_array_4(psky->getLightDirection().getValue(), 0.0f); - legacy[SETTING_MAX_Y] = llsd::array(settings[SETTING_MAX_Y], 0.0f, 0.0f, 1.0f); - legacy[SETTING_STAR_BRIGHTNESS] = settings[SETTING_STAR_BRIGHTNESS].asReal() / 250.0f; // convert from 0-500 -> 0-2 ala pre-FS-compat changes - legacy[SETTING_SUNLIGHT_COLOR] = ensure_array_4(settings[SETTING_SUNLIGHT_COLOR], 1.0f); - - LLVector3 dir = psky->getLightDirection(); - - F32 phi = asin(dir.mV[2]); - F32 cos_phi = cosf(phi); - F32 theta = (cos_phi != 0) ? asin(dir.mV[1] / cos_phi) : 0.0f; - - theta = -theta; - - // get angles back into valid ranges for legacy viewer... - // - while (theta < 0) - { - theta += F_PI * 2; - } - - if (theta > 4 * F_PI) - { - theta = fmod(theta, 2 * F_PI); - } - - while (phi < -F_PI) - { - phi += 2 * F_PI; - } - - if (phi > 3 * F_PI) - { - phi = F_PI + fmod(phi - F_PI, 2 * F_PI); - } - - legacy[SETTING_LEGACY_EAST_ANGLE] = theta; - legacy[SETTING_LEGACY_SUN_ANGLE] = phi; - - return legacy; -} - -//------------------------------------------------------------------------- -void LLSettingsVOSky::updateSettings() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT; - LLSettingsSky::updateSettings(); - LLVector3 sun_direction = getSunDirection(); - LLVector3 moon_direction = getMoonDirection(); - - // Want the dot prod of sun w/ high noon vector (0,0,1), which is just the z component - F32 dp = llmax(sun_direction[2], 0.0f); // clamped to 0 when sun is down - - // Since WL scales everything by 2, there should always be at least a 2:1 brightness ratio - // between sunlight and point lights in windlight to normalize point lights. - // - // After some A/B comparison of relesae vs EEP, tweak to allow strength to fall below 2 - // at night, for better match. (mSceneLightStrength is a divisor, so lower value means brighter - // local lights) - F32 sun_dynamic_range = llmax(gSavedSettings.getF32("RenderSunDynamicRange"), 0.0001f); - mSceneLightStrength = 2.0f * (0.75f + sun_dynamic_range * dp); - - gSky.setSunAndMoonDirectionsCFR(sun_direction, moon_direction); - gSky.setSunTextures(getSunTextureId(), getNextSunTextureId()); - gSky.setMoonTextures(getMoonTextureId(), getNextMoonTextureId()); - gSky.setCloudNoiseTextures(getCloudNoiseTextureId(), getNextCloudNoiseTextureId()); - gSky.setBloomTextures(getBloomTextureId(), getNextBloomTextureId()); - - gSky.setSunScale(getSunScale()); - gSky.setMoonScale(getMoonScale()); -} - -void LLSettingsVOSky::applySpecial(void *ptarget, bool force) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_SHADER; - LLVector3 light_direction = LLVector3(LLEnvironment::instance().getClampedLightNorm().mV); - - bool irradiance_pass = gCubeSnapshot && !gPipeline.mReflectionMapManager.isRadiancePass(); - - LLShaderUniforms* shader = &((LLShaderUniforms*)ptarget)[LLGLSLShader::SG_DEFAULT]; - { - shader->uniform3fv(LLViewerShaderMgr::LIGHTNORM, light_direction); - shader->uniform3fv(LLShaderMgr::WL_CAMPOSLOCAL, LLViewerCamera::getInstance()->getOrigin()); - } - - shader = &((LLShaderUniforms*)ptarget)[LLGLSLShader::SG_SKY]; - - shader->uniform3fv(LLViewerShaderMgr::LIGHTNORM, light_direction); - - // Legacy? SETTING_CLOUD_SCROLL_RATE("cloud_scroll_rate") - LLVector4 vect_c_p_d1(mSettings[SETTING_CLOUD_POS_DENSITY1]); - LLVector4 cloud_scroll( LLEnvironment::instance().getCloudScrollDelta() ); - - // SL-13084 EEP added support for custom cloud textures -- flip them horizontally to match the preview of Clouds > Cloud Scroll - // Keep in Sync! - // * indra\newview\llsettingsvo.cpp - // * indra\newview\app_settings\shaders\class2\windlight\cloudsV.glsl - // * indra\newview\app_settings\shaders\class1\deferred\cloudsV.glsl - cloud_scroll[0] = -cloud_scroll[0]; - vect_c_p_d1 += cloud_scroll; - shader->uniform3fv(LLShaderMgr::CLOUD_POS_DENSITY1, LLVector3(vect_c_p_d1.mV)); - - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - - // TODO -- make these getters return vec3s - LLVector3 sunDiffuse = LLVector3(psky->getSunlightColor().mV); - LLVector3 moonDiffuse = LLVector3(psky->getMoonlightColor().mV); - - shader->uniform3fv(LLShaderMgr::SUNLIGHT_COLOR, sunDiffuse); - shader->uniform3fv(LLShaderMgr::MOONLIGHT_COLOR, moonDiffuse); - - shader->uniform3fv(LLShaderMgr::CLOUD_COLOR, LLVector3(psky->getCloudColor().mV)); - - shader = &((LLShaderUniforms*)ptarget)[LLGLSLShader::SG_ANY]; - shader->uniform1f(LLShaderMgr::SCENE_LIGHT_STRENGTH, mSceneLightStrength); - - LLColor3 ambient(getTotalAmbient()); - - F32 g = getGamma(); - - static LLCachedControl should_auto_adjust(gSavedSettings, "RenderSkyAutoAdjustLegacy", true); - static LLCachedControl auto_adjust_ambient_scale(gSavedSettings, "RenderSkyAutoAdjustAmbientScale", 0.75f); - static LLCachedControl auto_adjust_hdr_scale(gSavedSettings, "RenderSkyAutoAdjustHDRScale", 2.f); - static LLCachedControl auto_adjust_blue_horizon_scale(gSavedSettings, "RenderSkyAutoAdjustBlueHorizonScale", 1.f); - static LLCachedControl auto_adjust_blue_density_scale(gSavedSettings, "RenderSkyAutoAdjustBlueDensityScale", 1.f); - static LLCachedControl auto_adjust_sun_color_scale(gSavedSettings, "RenderSkyAutoAdjustSunColorScale", 1.f); - static LLCachedControl sunlight_scale(gSavedSettings, "RenderSkySunlightScale", 1.5f); - static LLCachedControl ambient_scale(gSavedSettings, "RenderSkyAmbientScale", 1.5f); - - shader->uniform1f(LLShaderMgr::SKY_SUNLIGHT_SCALE, sunlight_scale); - shader->uniform1f(LLShaderMgr::SKY_AMBIENT_SCALE, ambient_scale); - - static LLCachedControl cloud_shadow_scale(gSavedSettings, "RenderCloudShadowAmbianceFactor", 0.125f); - F32 probe_ambiance = getTotalReflectionProbeAmbiance(cloud_shadow_scale); - - if (irradiance_pass) - { // during an irradiance map update, disable ambient lighting (direct lighting only) and desaturate sky color (avoid tinting the world blue) - shader->uniform3fv(LLShaderMgr::AMBIENT, LLVector3::zero.mV); - } - else - { - if (psky->getReflectionProbeAmbiance() != 0.f) - { - shader->uniform3fv(LLShaderMgr::AMBIENT, LLVector3(ambient.mV)); - shader->uniform1f(LLShaderMgr::SKY_HDR_SCALE, sqrtf(g)*2.0); // use a modifier here so 1.0 maps to the "most desirable" default and the maximum value doesn't go off the rails - } - else if (psky->canAutoAdjust() && should_auto_adjust) - { // auto-adjust legacy sky to take advantage of probe ambiance - shader->uniform3fv(LLShaderMgr::AMBIENT, (ambient * auto_adjust_ambient_scale).mV); - shader->uniform1f(LLShaderMgr::SKY_HDR_SCALE, auto_adjust_hdr_scale); - LLColor3 blue_horizon = getBlueHorizon() * auto_adjust_blue_horizon_scale; - LLColor3 blue_density = getBlueDensity() * auto_adjust_blue_density_scale; - LLColor3 sun_diffuse = getSunDiffuse() * auto_adjust_sun_color_scale; - - shader->uniform3fv(LLShaderMgr::SUNLIGHT_COLOR, sun_diffuse.mV); - shader->uniform3fv(LLShaderMgr::BLUE_DENSITY, blue_density.mV); - shader->uniform3fv(LLShaderMgr::BLUE_HORIZON, blue_horizon.mV); - - probe_ambiance = sAutoAdjustProbeAmbiance; - } - else - { - shader->uniform1f(LLShaderMgr::SKY_HDR_SCALE, 1.f); - shader->uniform3fv(LLShaderMgr::AMBIENT, LLVector3(ambient.mV)); - } - } - - shader->uniform1f(LLShaderMgr::REFLECTION_PROBE_AMBIANCE, probe_ambiance); - - shader->uniform1i(LLShaderMgr::SUN_UP_FACTOR, getIsSunUp() ? 1 : 0); - shader->uniform1f(LLShaderMgr::SUN_MOON_GLOW_FACTOR, getSunMoonGlowFactor()); - shader->uniform1f(LLShaderMgr::DENSITY_MULTIPLIER, getDensityMultiplier()); - shader->uniform1f(LLShaderMgr::DISTANCE_MULTIPLIER, getDistanceMultiplier()); - - shader->uniform1f(LLShaderMgr::GAMMA, g); -} - -LLSettingsSky::parammapping_t LLSettingsVOSky::getParameterMap() const -{ - static parammapping_t param_map; - - if (param_map.empty()) - { -// LEGACY_ATMOSPHERICS - - // Todo: default 'legacy' values duplicate the ones from functions like getBlueDensity() find a better home for them - // There is LLSettingsSky::defaults(), but it doesn't contain everything since it is geared towards creating new settings. - param_map[SETTING_AMBIENT] = DefaultParam(LLShaderMgr::AMBIENT, LLColor3(0.25f, 0.25f, 0.25f).getValue()); - param_map[SETTING_BLUE_DENSITY] = DefaultParam(LLShaderMgr::BLUE_DENSITY, LLColor3(0.2447f, 0.4487f, 0.7599f).getValue()); - param_map[SETTING_BLUE_HORIZON] = DefaultParam(LLShaderMgr::BLUE_HORIZON, LLColor3(0.4954f, 0.4954f, 0.6399f).getValue()); - param_map[SETTING_HAZE_DENSITY] = DefaultParam(LLShaderMgr::HAZE_DENSITY, LLSD(0.7f)); - param_map[SETTING_HAZE_HORIZON] = DefaultParam(LLShaderMgr::HAZE_HORIZON, LLSD(0.19f)); - param_map[SETTING_DENSITY_MULTIPLIER] = DefaultParam(LLShaderMgr::DENSITY_MULTIPLIER, LLSD(0.0001f)); - param_map[SETTING_DISTANCE_MULTIPLIER] = DefaultParam(LLShaderMgr::DISTANCE_MULTIPLIER, LLSD(0.8f)); - - // Following values are always present, so we can just zero these ones, but used values from defaults() - LLSD sky_defaults = LLSettingsSky::defaults(); - - param_map[SETTING_CLOUD_POS_DENSITY2] = DefaultParam(LLShaderMgr::CLOUD_POS_DENSITY2, sky_defaults[SETTING_CLOUD_POS_DENSITY2]); - param_map[SETTING_CLOUD_SCALE] = DefaultParam(LLShaderMgr::CLOUD_SCALE, sky_defaults[SETTING_CLOUD_SCALE]); - param_map[SETTING_CLOUD_SHADOW] = DefaultParam(LLShaderMgr::CLOUD_SHADOW, sky_defaults[SETTING_CLOUD_SHADOW]); - param_map[SETTING_CLOUD_VARIANCE] = DefaultParam(LLShaderMgr::CLOUD_VARIANCE, sky_defaults[SETTING_CLOUD_VARIANCE]); - param_map[SETTING_GLOW] = DefaultParam(LLShaderMgr::GLOW, sky_defaults[SETTING_GLOW]); - param_map[SETTING_MAX_Y] = DefaultParam(LLShaderMgr::MAX_Y, sky_defaults[SETTING_MAX_Y]); - - //param_map[SETTING_SUNLIGHT_COLOR] = DefaultParam(LLShaderMgr::SUNLIGHT_COLOR, sky_defaults[SETTING_SUNLIGHT_COLOR]); - //param_map[SETTING_CLOUD_COLOR] = DefaultParam(LLShaderMgr::CLOUD_COLOR, sky_defaults[SETTING_CLOUD_COLOR]); - - param_map[SETTING_MOON_BRIGHTNESS] = DefaultParam(LLShaderMgr::MOON_BRIGHTNESS, sky_defaults[SETTING_MOON_BRIGHTNESS]); - param_map[SETTING_SKY_MOISTURE_LEVEL] = DefaultParam(LLShaderMgr::MOISTURE_LEVEL, sky_defaults[SETTING_SKY_MOISTURE_LEVEL]); - param_map[SETTING_SKY_DROPLET_RADIUS] = DefaultParam(LLShaderMgr::DROPLET_RADIUS, sky_defaults[SETTING_SKY_DROPLET_RADIUS]); - param_map[SETTING_SKY_ICE_LEVEL] = DefaultParam(LLShaderMgr::ICE_LEVEL, sky_defaults[SETTING_SKY_ICE_LEVEL]); - - param_map[SETTING_REFLECTION_PROBE_AMBIANCE] = DefaultParam(LLShaderMgr::REFLECTION_PROBE_AMBIANCE, sky_defaults[SETTING_REFLECTION_PROBE_AMBIANCE]); -// AdvancedAtmospherics TODO -// Provide mappings for new shader params here - } - - return param_map; -} - -//========================================================================= -const F32 LLSettingsVOWater::WATER_FOG_LIGHT_CLAMP(0.3f); - -//------------------------------------------------------------------------- -LLSettingsVOWater::LLSettingsVOWater(const LLSD &data) : - LLSettingsWater(data) -{ - -} - -LLSettingsVOWater::LLSettingsVOWater() : - LLSettingsWater() -{ - -} - -LLSettingsWater::ptr_t LLSettingsVOWater::buildWater(LLSD settings) -{ - LLSettingsWater::validation_list_t validations = LLSettingsWater::validationList(); - LLSD results = LLSettingsWater::settingValidation(settings, validations); - if (!results["success"].asBoolean()) - { - LL_WARNS("SETTINGS") << "Water setting validation failed!\n" << results << LL_ENDL; - LLSettingsWater::ptr_t(); - } - - return std::make_shared(settings); -} - -//------------------------------------------------------------------------- -LLSettingsWater::ptr_t LLSettingsVOWater::buildFromLegacyPreset(const std::string &name, const LLSD &oldsettings, LLSD &messages) -{ - LLSD newsettings(LLSettingsWater::translateLegacySettings(oldsettings)); - if (newsettings.isUndefined()) - { - messages["REASONS"] = LLTrans::getString("SettingTranslateError", LLSDMap("NAME", name)); - return LLSettingsWater::ptr_t(); - } - - newsettings[SETTING_NAME] = name; - LLSettingsWater::validation_list_t validations = LLSettingsWater::validationList(); - LLSD results = LLSettingsWater::settingValidation(newsettings, validations); - if (!results["success"].asBoolean()) - { - messages["REASONS"] = LLTrans::getString("SettingValidationError", name); - LL_WARNS("SETTINGS") << "Water setting validation failed!: " << results << LL_ENDL; - return LLSettingsWater::ptr_t(); - } - - LLSettingsWater::ptr_t waterp = std::make_shared(newsettings); - -#ifdef VERIFY_LEGACY_CONVERSION - LLSD oldsettings = LLSettingsVOWater::convertToLegacy(waterp); - - if (!llsd_equals(oldsettings, oldsettings)) - { - LL_WARNS("WATER") << "Conversion to/from legacy does not match!\n" - << "Old: " << oldsettings - << "new: " << oldsettings << LL_ENDL; - } - -#endif - return waterp; -} - -LLSettingsWater::ptr_t LLSettingsVOWater::buildFromLegacyPresetFile(const std::string &name, const std::string &path, LLSD &messages) -{ - LLSD legacy_data = read_legacy_preset_data(name, path, messages); - - if (!legacy_data) - { // messages filled in by read_legacy_preset_data - LL_WARNS("SETTINGS") << "Could not load legacy Windlight \"" << name << "\" from " << path << LL_ENDL; - return ptr_t(); - } - - return buildFromLegacyPreset(LLURI::unescape(name), legacy_data, messages); -} - - -LLSettingsWater::ptr_t LLSettingsVOWater::buildDefaultWater() -{ - static LLSD default_settings; - - if (!default_settings.size()) - { - default_settings = LLSettingsWater::defaults(); - - default_settings[SETTING_NAME] = DEFAULT_SETTINGS_NAME; - - LLSettingsWater::validation_list_t validations = LLSettingsWater::validationList(); - LLSD results = LLSettingsWater::settingValidation(default_settings, validations); - if (!results["success"].asBoolean()) - { - LL_WARNS("SETTINGS") << "Water setting validation failed!: " << results << LL_ENDL; - return LLSettingsWater::ptr_t(); - } - } - - LLSettingsWater::ptr_t waterp = std::make_shared(default_settings); - - return waterp; -} - -LLSettingsWater::ptr_t LLSettingsVOWater::buildClone() const -{ - LLSD settings = cloneSettings(); - U32 flags = getFlags(); - LLSettingsWater::validation_list_t validations = LLSettingsWater::validationList(); - LLSD results = LLSettingsWater::settingValidation(settings, validations); - if (!results["success"].asBoolean()) - { - LL_WARNS("SETTINGS") << "Water setting validation failed!: " << results << LL_ENDL; - return LLSettingsWater::ptr_t(); - } - - LLSettingsWater::ptr_t waterp = std::make_shared(settings); - waterp->setFlags(flags); - return waterp; -} - -LLSD LLSettingsVOWater::convertToLegacy(const LLSettingsWater::ptr_t &pwater) -{ - LLSD legacy(LLSD::emptyMap()); - LLSD settings = pwater->getSettings(); - - legacy[SETTING_LEGACY_BLUR_MULTIPLIER] = settings[SETTING_BLUR_MULTIPLIER]; - legacy[SETTING_LEGACY_FOG_COLOR] = ensure_array_4(settings[SETTING_FOG_COLOR], 1.0f); - legacy[SETTING_LEGACY_FOG_DENSITY] = settings[SETTING_FOG_DENSITY]; - legacy[SETTING_LEGACY_FOG_MOD] = settings[SETTING_FOG_MOD]; - legacy[SETTING_LEGACY_FRESNEL_OFFSET] = settings[SETTING_FRESNEL_OFFSET]; - legacy[SETTING_LEGACY_FRESNEL_SCALE] = settings[SETTING_FRESNEL_SCALE]; - legacy[SETTING_LEGACY_NORMAL_MAP] = settings[SETTING_NORMAL_MAP]; - legacy[SETTING_LEGACY_NORMAL_SCALE] = settings[SETTING_NORMAL_SCALE]; - legacy[SETTING_LEGACY_SCALE_ABOVE] = settings[SETTING_SCALE_ABOVE]; - legacy[SETTING_LEGACY_SCALE_BELOW] = settings[SETTING_SCALE_BELOW]; - legacy[SETTING_LEGACY_WAVE1_DIR] = settings[SETTING_WAVE1_DIR]; - legacy[SETTING_LEGACY_WAVE2_DIR] = settings[SETTING_WAVE2_DIR]; - - return legacy; -} -//------------------------------------------------------------------------- -//------------------------------------------------------------------------- -void LLSettingsVOWater::applySpecial(void *ptarget, bool force) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_SHADER; - - LLEnvironment& env = LLEnvironment::instance(); - - auto group = LLGLSLShader::SG_ANY; - LLShaderUniforms* shader = &((LLShaderUniforms*)ptarget)[group]; - - { - F32 water_height = env.getWaterHeight(); - - if (LLViewerCamera::instance().cameraUnderWater()) - { // when the camera is under water, use the water height at the camera position - LLViewerRegion* region = LLWorld::instance().getRegionFromPosAgent(LLViewerCamera::instance().getOrigin()); - if (region) - { - water_height = region->getWaterHeight(); - } - } - - //transform water plane to eye space - glh::vec3f norm(0.f, 0.f, 1.f); - glh::vec3f p(0.f, 0.f, water_height); - - F32 modelView[16]; - for (U32 i = 0; i < 16; i++) - { - modelView[i] = (F32)gGLModelView[i]; - } - - glh::matrix4f mat(modelView); - glh::matrix4f invtrans = mat.inverse().transpose(); - glh::vec3f enorm; - glh::vec3f ep; - invtrans.mult_matrix_vec(norm, enorm); - enorm.normalize(); - mat.mult_matrix_vec(p, ep); - - LLVector4 waterPlane(enorm.v[0], enorm.v[1], enorm.v[2], -ep.dot(enorm)); - - LLDrawPoolAlpha::sWaterPlane = waterPlane; - - shader->uniform4fv(LLShaderMgr::WATER_WATERPLANE, waterPlane.mV); - - LLVector4 light_direction = env.getClampedLightNorm(); - - F32 waterFogKS = 1.f / llmax(light_direction.mV[2], WATER_FOG_LIGHT_CLAMP); - - shader->uniform1f(LLShaderMgr::WATER_FOGKS, waterFogKS); - - F32 eyedepth = LLViewerCamera::getInstance()->getOrigin().mV[2] - water_height; - bool underwater = (eyedepth <= 0.0f); - - F32 waterFogDensity = env.getCurrentWater()->getModifiedWaterFogDensity(underwater); - shader->uniform1f(LLShaderMgr::WATER_FOGDENSITY, waterFogDensity); - - LLColor4 fog_color(env.getCurrentWater()->getWaterFogColor()); - shader->uniform4fv(LLShaderMgr::WATER_FOGCOLOR, fog_color.mV); - - shader->uniform3fv(LLShaderMgr::WATER_FOGCOLOR_LINEAR, linearColor3(fog_color).mV); - - F32 blend_factor = env.getCurrentWater()->getBlendFactor(); - shader->uniform1f(LLShaderMgr::BLEND_FACTOR, blend_factor); - - // update to normal lightnorm, water shader itself will use rotated lightnorm as necessary - shader->uniform3fv(LLShaderMgr::LIGHTNORM, light_direction.mV); - } -} - -void LLSettingsVOWater::updateSettings() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - // base class clears dirty flag so as to not trigger recursive update - LLSettingsBase::updateSettings(); - - LLDrawPoolWater* pwaterpool = (LLDrawPoolWater*)gPipeline.getPool(LLDrawPool::POOL_WATER); - if (pwaterpool) - { - pwaterpool->setTransparentTextures(getTransparentTextureID(), getNextTransparentTextureID()); - pwaterpool->setOpaqueTexture(GetDefaultOpaqueTextureAssetId()); - pwaterpool->setNormalMaps(getNormalMapID(), getNextNormalMapID()); - } -} - -LLSettingsWater::parammapping_t LLSettingsVOWater::getParameterMap() const -{ - static parammapping_t param_map; - - return param_map; -} - -//========================================================================= -LLSettingsVODay::LLSettingsVODay(const LLSD &data): - LLSettingsDay(data) -{} - -LLSettingsVODay::LLSettingsVODay(): - LLSettingsDay() -{} - -LLSettingsDay::ptr_t LLSettingsVODay::buildDay(LLSD settings) -{ - LLSettingsDay::validation_list_t validations = LLSettingsDay::validationList(); - LLSD results = LLSettingsDay::settingValidation(settings, validations); - if (!results["success"].asBoolean()) - { - LL_WARNS("SETTINGS") << "Day setting validation failed!\n" << results << LL_ENDL; - LLSettingsDay::ptr_t(); - } - - LLSettingsDay::ptr_t pday = std::make_shared(settings); - if (pday) - pday->initialize(); - - return pday; -} - -//------------------------------------------------------------------------- -LLSettingsDay::ptr_t LLSettingsVODay::buildFromLegacyPreset(const std::string &name, const std::string &path, const LLSD &oldsettings, LLSD &messages) -{ - LLSD newsettings(defaults()); - std::set framenames; - std::set notfound; - - // expected and correct folder sctructure is to have - // three folders in widnlight's root: days, water, skies - std::string base_path(gDirUtilp->getDirName(path)); - std::string water_path(base_path); - std::string sky_path(base_path); - std::string day_path(base_path); - - gDirUtilp->append(water_path, "water"); - gDirUtilp->append(sky_path, "skies"); - gDirUtilp->append(day_path, "days"); - - if (!gDirUtilp->fileExists(day_path)) - { - LL_WARNS("SETTINGS") << "File " << name << ".xml is not in \"days\" folder." << LL_ENDL; - } - - if (!gDirUtilp->fileExists(water_path)) - { - LL_WARNS("SETTINGS") << "Failed to find accompaniying water folder for file " << name - << ".xml. Falling back to using default folder" << LL_ENDL; - - water_path = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "windlight"); - gDirUtilp->append(water_path, "water"); - } - - if (!gDirUtilp->fileExists(sky_path)) - { - LL_WARNS("SETTINGS") << "Failed to find accompaniying skies folder for file " << name - << ".xml. Falling back to using default folder" << LL_ENDL; - - sky_path = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "windlight"); - gDirUtilp->append(sky_path, "skies"); - } - - newsettings[SETTING_NAME] = name; - - LLSD watertrack = llsd::array( - LLSDMap(SETTING_KEYKFRAME, LLSD::Real(0.0f)) - (SETTING_KEYNAME, "water:Default")); - - LLSD skytrack = LLSD::emptyArray(); - - for (LLSD::array_const_iterator it = oldsettings.beginArray(); it != oldsettings.endArray(); ++it) - { - std::string framename = (*it)[1].asString(); - LLSD entry = LLSDMap(SETTING_KEYKFRAME, (*it)[0].asReal()) - (SETTING_KEYNAME, "sky:" + framename); - framenames.insert(framename); - skytrack.append(entry); - } - - newsettings[SETTING_TRACKS] = llsd::array(watertrack, skytrack); - - LLSD frames(LLSD::emptyMap()); - - { - LLSettingsWater::ptr_t pwater = LLSettingsVOWater::buildFromLegacyPresetFile("Default", water_path, messages); - if (!pwater) - { // messages filled in by buildFromLegacyPresetFile - return LLSettingsDay::ptr_t(); - } - frames["water:Default"] = pwater->getSettings(); - } - - for (std::set::iterator itn = framenames.begin(); itn != framenames.end(); ++itn) - { - LLSettingsSky::ptr_t psky = LLSettingsVOSky::buildFromLegacyPresetFile((*itn), sky_path, messages); - if (!psky) - { // messages filled in by buildFromLegacyPresetFile - return LLSettingsDay::ptr_t(); - } - frames["sky:" + (*itn)] = psky->getSettings(); - } - - newsettings[SETTING_FRAMES] = frames; - - LLSettingsDay::validation_list_t validations = LLSettingsDay::validationList(); - LLSD results = LLSettingsDay::settingValidation(newsettings, validations); - if (!results["success"].asBoolean()) - { - messages["REASONS"] = LLTrans::getString("SettingValidationError", LLSDMap("NAME", name)); - LL_WARNS("SETTINGS") << "Day setting validation failed!: " << results << LL_ENDL; - return LLSettingsDay::ptr_t(); - } - - LLSettingsDay::ptr_t dayp = std::make_shared(newsettings); - -#ifdef VERIFY_LEGACY_CONVERSION - LLSD testsettings = LLSettingsVODay::convertToLegacy(dayp); - - if (!llsd_equals(oldsettings, testsettings)) - { - LL_WARNS("DAYCYCLE") << "Conversion to/from legacy does not match!\n" - << "Old: " << oldsettings - << "new: " << testsettings << LL_ENDL; - } - -#endif - - dayp->initialize(); - - return dayp; -} - -LLSettingsDay::ptr_t LLSettingsVODay::buildFromLegacyPresetFile(const std::string &name, const std::string &path, LLSD &messages) -{ - LLSD legacy_data = read_legacy_preset_data(name, path, messages); - - if (!legacy_data) - { // messages filled in by read_legacy_preset_data - LL_WARNS("SETTINGS") << "Could not load legacy Windlight \"" << name << "\" from " << path << LL_ENDL; - return ptr_t(); - } - // Name for LLSettingsDay only, path to get related files from filesystem - return buildFromLegacyPreset(LLURI::unescape(name), path, legacy_data, messages); -} - - - -LLSettingsDay::ptr_t LLSettingsVODay::buildFromLegacyMessage(const LLUUID ®ionId, LLSD daycycle, LLSD skydefs, LLSD waterdef) -{ - LLSD frames(LLSD::emptyMap()); - - for (LLSD::map_iterator itm = skydefs.beginMap(); itm != skydefs.endMap(); ++itm) - { - std::string newname = "sky:" + (*itm).first; - LLSD newsettings = LLSettingsSky::translateLegacySettings((*itm).second); - - newsettings[SETTING_NAME] = newname; - frames[newname] = newsettings; - - LL_WARNS("SETTINGS") << "created region sky '" << newname << "'" << LL_ENDL; - } - - LLSD watersettings = LLSettingsWater::translateLegacySettings(waterdef); - std::string watername = "water:"+ watersettings[SETTING_NAME].asString(); - watersettings[SETTING_NAME] = watername; - frames[watername] = watersettings; - - LLSD watertrack = llsd::array( - LLSDMap(SETTING_KEYKFRAME, LLSD::Real(0.0f)) - (SETTING_KEYNAME, watername)); - - LLSD skytrack(LLSD::emptyArray()); - for (LLSD::array_const_iterator it = daycycle.beginArray(); it != daycycle.endArray(); ++it) - { - LLSD entry = LLSDMap(SETTING_KEYKFRAME, (*it)[0].asReal()) - (SETTING_KEYNAME, "sky:" + (*it)[1].asString()); - skytrack.append(entry); - } - - LLSD newsettings = LLSDMap - ( SETTING_NAME, "Region (legacy)" ) - ( SETTING_TRACKS, llsd::array(watertrack, skytrack)) - ( SETTING_FRAMES, frames ) - ( SETTING_TYPE, "daycycle" ); - - LLSettingsSky::validation_list_t validations = LLSettingsDay::validationList(); - LLSD results = LLSettingsDay::settingValidation(newsettings, validations); - if (!results["success"].asBoolean()) - { - LL_WARNS("SETTINGS") << "Day setting validation failed!:" << results << LL_ENDL; - return LLSettingsDay::ptr_t(); - } - - LLSettingsDay::ptr_t dayp = std::make_shared(newsettings); - - if (dayp) - { - // true for validation - either validate here, or when cloning for floater. - dayp->initialize(true); - } - return dayp; -} - - - -LLSettingsDay::ptr_t LLSettingsVODay::buildDefaultDayCycle() -{ - static LLSD default_settings; - - if (!default_settings.size()) - { - default_settings = LLSettingsDay::defaults(); - default_settings[SETTING_NAME] = DEFAULT_SETTINGS_NAME; - - LLSettingsDay::validation_list_t validations = LLSettingsDay::validationList(); - LLSD results = LLSettingsDay::settingValidation(default_settings, validations); - if (!results["success"].asBoolean()) - { - LL_WARNS("SETTINGS") << "Day setting validation failed!\n" << results << LL_ENDL; - LLSettingsDay::ptr_t(); - } - } - - LLSettingsDay::ptr_t dayp = std::make_shared(default_settings); - - dayp->initialize(); - return dayp; -} - -LLSettingsDay::ptr_t LLSettingsVODay::buildFromEnvironmentMessage(LLSD settings) -{ - LLSettingsDay::validation_list_t validations = LLSettingsDay::validationList(); - LLSD results = LLSettingsDay::settingValidation(settings, validations); - if (!results["success"].asBoolean()) - { - LL_WARNS("SETTINGS") << "Day setting validation failed!\n" << results << LL_ENDL; - LLSettingsDay::ptr_t(); - } - - LLSettingsDay::ptr_t dayp = std::make_shared(settings); - - dayp->initialize(); - return dayp; -} - - -void LLSettingsVODay::buildFromOtherSetting(LLSettingsBase::ptr_t settings, LLSettingsVODay::asset_built_fn cb) -{ - if (settings->getSettingsType() == "daycycle") - { - if (cb) - cb(std::static_pointer_cast(settings)); - } - else - { - LLSettingsVOBase::getSettingsAsset(LLSettingsDay::GetDefaultAssetId(), - [settings, cb](LLUUID, LLSettingsBase::ptr_t pday, S32, LLExtStat){ combineIntoDayCycle(std::static_pointer_cast(pday), settings, cb); }); - } -} - -void LLSettingsVODay::combineIntoDayCycle(LLSettingsDay::ptr_t pday, LLSettingsBase::ptr_t settings, asset_built_fn cb) -{ - if (settings->getSettingsType() == "sky") - { - pday->setName("sky: " + settings->getName()); - pday->clearCycleTrack(1); - pday->setSettingsAtKeyframe(settings, 0.0, 1); - } - else if (settings->getSettingsType() == "water") - { - pday->setName("water: " + settings->getName()); - pday->clearCycleTrack(0); - pday->setSettingsAtKeyframe(settings, 0.0, 0); - } - else - { - pday.reset(); - } - - if (cb) - cb(pday); -} - - -LLSettingsDay::ptr_t LLSettingsVODay::buildClone() const -{ - LLSD settings = cloneSettings(); - - LLSettingsDay::validation_list_t validations = LLSettingsDay::validationList(); - LLSD results = LLSettingsDay::settingValidation(settings, validations); - if (!results["success"].asBoolean()) - { - LL_WARNS("SETTINGS") << "Day setting validation failed!\n" << results << LL_ENDL; - LLSettingsDay::ptr_t(); - } - - LLSettingsDay::ptr_t dayp = std::make_shared(settings); - - U32 flags = getFlags(); - if (flags) - dayp->setFlags(flags); - - dayp->initialize(); - return dayp; -} - -LLSettingsDay::ptr_t LLSettingsVODay::buildDeepCloneAndUncompress() const -{ - // no need for SETTING_TRACKS or SETTING_FRAMES, so take base LLSD - LLSD settings = llsd_clone(mSettings); - - U32 flags = getFlags(); - LLSettingsDay::ptr_t day_clone = std::make_shared(settings); - - for (S32 i = 0; i < LLSettingsDay::TRACK_MAX; ++i) - { - const LLSettingsDay::CycleTrack_t& track = getCycleTrackConst(i); - LLSettingsDay::CycleTrack_t::const_iterator iter = track.begin(); - while (iter != track.end()) - { - // 'Unpack', usually for editing - // - frames 'share' settings multiple times - // - settings can reuse LLSDs they were initialized from - // We do not want for edited frame to change multiple frames in same track, so do a clone - day_clone->setSettingsAtKeyframe(iter->second->buildDerivedClone(), iter->first, i); - iter++; - } - } - day_clone->setFlags(flags); - return day_clone; -} - -LLSD LLSettingsVODay::convertToLegacy(const LLSettingsVODay::ptr_t &pday) -{ - CycleTrack_t &trackwater = pday->getCycleTrack(TRACK_WATER); - - LLSettingsWater::ptr_t pwater; - if (!trackwater.empty()) - { - pwater = std::static_pointer_cast((*trackwater.begin()).second); - } - - if (!pwater) - pwater = LLSettingsVOWater::buildDefaultWater(); - - LLSD llsdwater = LLSettingsVOWater::convertToLegacy(pwater); - - CycleTrack_t &tracksky = pday->getCycleTrack(1); // first sky track - std::map skys; - - LLSD llsdcycle(LLSD::emptyArray()); - - for(CycleTrack_t::iterator it = tracksky.begin(); it != tracksky.end(); ++it) - { - size_t hash = (*it).second->getHash(); - std::stringstream name; - - name << hash; - - skys[name.str()] = std::static_pointer_cast((*it).second); - - F32 frame = ((tracksky.size() == 1) && (it == tracksky.begin())) ? -1.0f : (*it).first; - llsdcycle.append( llsd::array(LLSD::Real(frame), name.str()) ); - } - - LLSD llsdskylist(LLSD::emptyMap()); - - for (std::map::iterator its = skys.begin(); its != skys.end(); ++its) - { - LLSD llsdsky = LLSettingsVOSky::convertToLegacy((*its).second, false); - llsdsky[SETTING_NAME] = (*its).first; - - llsdskylist[(*its).first] = llsdsky; - } - - return llsd::array(LLSD::emptyMap(), llsdcycle, llsdskylist, llsdwater); -} - -LLSettingsSkyPtr_t LLSettingsVODay::getDefaultSky() const -{ - return LLSettingsVOSky::buildDefaultSky(); -} - -LLSettingsWaterPtr_t LLSettingsVODay::getDefaultWater() const -{ - return LLSettingsVOWater::buildDefaultWater(); -} - -LLSettingsSkyPtr_t LLSettingsVODay::buildSky(LLSD settings) const -{ - LLSettingsSky::ptr_t skyp = std::make_shared(settings); - - if (skyp->validate()) - return skyp; - - return LLSettingsSky::ptr_t(); -} - -LLSettingsWaterPtr_t LLSettingsVODay::buildWater(LLSD settings) const -{ - LLSettingsWater::ptr_t waterp = std::make_shared(settings); - - if (waterp->validate()) - return waterp; - - return LLSettingsWater::ptr_t(); -} - -//========================================================================= -namespace -{ - LLSD ensure_array_4(LLSD in, F32 fill) - { - if (in.size() >= 4) - return in; - - LLSD out(LLSD::emptyArray()); - - for (S32 idx = 0; idx < in.size(); ++idx) - { - out.append(in[idx]); - } - - while (out.size() < 4) - { - out.append(LLSD::Real(fill)); - } - return out; - } - - // This is a disturbing hack - std::string legacy_name_to_filename(const std::string &name, bool convertdash = false) - { - std::string fixedname(LLURI::escape(name)); - - if (convertdash) - boost::algorithm::replace_all(fixedname, "-", "%2D"); - - return fixedname; - } - - //--------------------------------------------------------------------- - LLSD read_legacy_preset_data(const std::string &name, const std::string& path, LLSD &messages) - { - llifstream xml_file; - - std::string full_path(path); - std::string full_name(name); - full_name += ".xml"; - gDirUtilp->append(full_path, full_name); - - xml_file.open(full_path.c_str()); - if (!xml_file) - { - std::string bad_path(full_path); - full_path = path; - full_name = legacy_name_to_filename(name); - full_name += ".xml"; - gDirUtilp->append(full_path, full_name); - - LL_INFOS("LEGACYSETTING") << "Could not open \"" << bad_path << "\" trying escaped \"" << full_path << "\"" << LL_ENDL; - - xml_file.open(full_path.c_str()); - if (!xml_file) - { - LL_WARNS("LEGACYSETTING") << "Unable to open legacy windlight \"" << name << "\" from " << path << LL_ENDL; - - full_path = path; - full_name = legacy_name_to_filename(name, true); - full_name += ".xml"; - gDirUtilp->append(full_path, full_name); - xml_file.open(full_path.c_str()); - if (!xml_file) - { - messages["REASONS"] = LLTrans::getString("SettingImportFileError", LLSDMap("FILE", bad_path)); - LL_WARNS("LEGACYSETTING") << "Unable to open legacy windlight \"" << name << "\" from " << path << LL_ENDL; - return LLSD(); - } - } - } - - LLSD params_data; - LLPointer parser = new LLSDXMLParser(); - if (parser->parse(xml_file, params_data, LLSDSerialize::SIZE_UNLIMITED) == LLSDParser::PARSE_FAILURE) - { - xml_file.close(); - messages["REASONS"] = LLTrans::getString("SettingParseFileError", LLSDMap("FILE", full_path)); - return LLSD(); - } - xml_file.close(); - - return params_data; - } -} +/** +* @file llsettingsvo.cpp +* @author Rider Linden +* @brief Subclasses for viewer specific settings behaviors. +* +* $LicenseInfo:2011&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2017, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" +#include "llviewercontrol.h" +#include "llsettingsvo.h" + +#include "pipeline.h" + +#include +#include +#include +#include "lltrace.h" +#include "llfasttimer.h" +#include "v3colorutil.h" + +#include "llglslshader.h" +#include "llviewershadermgr.h" + +#include "llagent.h" +#include "llassettype.h" +#include "llfloaterperms.h" +#include "llnotificationsutil.h" + +#include "llviewerregion.h" +#include "llviewerassetupload.h" +#include "llviewerinventory.h" + +#include "llenvironment.h" +#include "llsky.h" + +#include "llpermissions.h" + +#include "llinventorymodel.h" +#include "llassetstorage.h" +#include "llfilesystem.h" +#include "lldrawpoolwater.h" + +#include +#include "llinventoryobserver.h" +#include "llinventorydefines.h" +#include "llworld.h" + +#include "lltrans.h" + +#undef VERIFY_LEGACY_CONVERSION + +extern bool gCubeSnapshot; + +//========================================================================= +namespace +{ + LLSD ensure_array_4(LLSD in, F32 fill); + LLSD read_legacy_preset_data(const std::string &name, const std::string& path, LLSD &messages); + + //------------------------------------------------------------------------- + class LLSettingsInventoryCB : public LLInventoryCallback + { + public: + typedef std::function callback_t; + + LLSettingsInventoryCB(callback_t cbfn) : + mCbfn(cbfn) + { } + + void fire(const LLUUID& inv_item) override { if (mCbfn) mCbfn(inv_item); } + + private: + callback_t mCbfn; + }; + + //------------------------------------------------------------------------- +} + + +//========================================================================= +void LLSettingsVOBase::createNewInventoryItem(LLSettingsType::type_e stype, const LLUUID& parent_id, std::function created_cb) +{ + inventory_result_fn cb = NULL; + + if (created_cb != NULL) + { + cb = [created_cb](LLUUID asset_id, LLUUID inventory_id, LLUUID object_id, LLSD results) + { + created_cb(inventory_id); + }; + } + createNewInventoryItem(stype, parent_id, cb); +} + +void LLSettingsVOBase::createNewInventoryItem(LLSettingsType::type_e stype, const LLUUID &parent_id, inventory_result_fn callback) +{ + LLTransactionID tid; + U32 nextOwnerPerm = LLFloaterPerms::getNextOwnerPerms("Settings"); + nextOwnerPerm |= PERM_COPY; + + if (!LLEnvironment::instance().isInventoryEnabled()) + { + LL_WARNS("SETTINGS") << "Region does not support settings inventory objects." << LL_ENDL; + LLNotificationsUtil::add("SettingsUnsuported"); + return; + } + + tid.generate(); + + LLPointer cb = new LLSettingsInventoryCB([callback](const LLUUID &inventoryId) { + LLSettingsVOBase::onInventoryItemCreated(inventoryId, LLSettingsBase::ptr_t(), callback); + }); + + create_inventory_settings(gAgent.getID(), gAgent.getSessionID(), + parent_id, LLTransactionID::tnull, + LLSettingsType::getDefaultName(stype), "", + stype, nextOwnerPerm, cb); +} + + +void LLSettingsVOBase::createInventoryItem(const LLSettingsBase::ptr_t &settings, const LLUUID &parent_id, std::string settings_name, inventory_result_fn callback) +{ + U32 nextOwnerPerm = LLFloaterPerms::getNextOwnerPerms("Settings"); + createInventoryItem(settings, nextOwnerPerm, parent_id, settings_name, callback); +} + +void LLSettingsVOBase::createInventoryItem(const LLSettingsBase::ptr_t &settings, U32 next_owner_perm, const LLUUID &parent_id, std::string settings_name, inventory_result_fn callback) +{ + LLTransactionID tid; + + if (!LLEnvironment::instance().isInventoryEnabled()) + { + LL_WARNS("SETTINGS") << "Region does not support settings inventory objects." << LL_ENDL; + LLNotificationsUtil::add("SettingsUnsuported"); + return; + } + + tid.generate(); + + LLPointer cb = new LLSettingsInventoryCB([settings, callback](const LLUUID &inventoryId) { + LLSettingsVOBase::onInventoryItemCreated(inventoryId, settings, callback); + }); + + if (settings_name.empty()) + { + settings_name = settings->getName(); + } + create_inventory_settings(gAgent.getID(), gAgent.getSessionID(), + parent_id, tid, + settings_name, "", + settings->getSettingsTypeValue(), next_owner_perm, cb); +} + +void LLSettingsVOBase::onInventoryItemCreated(const LLUUID &inventoryId, LLSettingsBase::ptr_t settings, inventory_result_fn callback) +{ + LLViewerInventoryItem *pitem = gInventory.getItem(inventoryId); + if (pitem) + { + LLPermissions perm = pitem->getPermissions(); + if (perm.getMaskEveryone() != PERM_COPY) + { + perm.setMaskEveryone(PERM_COPY); + pitem->setPermissions(perm); + pitem->updateServer(false); + } + } + if (!settings) + { // The item was created as new with no settings passed in. Simulator should have given it the default for the type... check ID, + // no need to upload asset. + LLUUID asset_id; + if (pitem) + { + asset_id = pitem->getAssetUUID(); + } + if (callback) + callback(asset_id, inventoryId, LLUUID::null, LLSD()); + return; + } + // We need to update some inventory stuff here.... maybe. + updateInventoryItem(settings, inventoryId, callback, false); +} + +void LLSettingsVOBase::updateInventoryItem(const LLSettingsBase::ptr_t &settings, LLUUID inv_item_id, inventory_result_fn callback, bool update_name) +{ + const LLViewerRegion* region = gAgent.getRegion(); + if (!region) + { + LL_WARNS("SETTINGS") << "Not connected to a region, cannot save setting." << LL_ENDL; + return; + } + + std::string agent_url(region->getCapability("UpdateSettingsAgentInventory")); + + if (!LLEnvironment::instance().isInventoryEnabled()) + { + LL_WARNS("SETTINGS") << "Region does not support settings inventory objects." << LL_ENDL; + LLNotificationsUtil::add("SettingsUnsuported"); + return; + } + + LLViewerInventoryItem *inv_item = gInventory.getItem(inv_item_id); + if (inv_item) + { + bool need_update(false); + LLPointer new_item = new LLViewerInventoryItem(inv_item); + + if (settings->getFlag(LLSettingsBase::FLAG_NOTRANS) && new_item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID())) + { + LLPermissions perm(inv_item->getPermissions()); + perm.setBaseBits(LLUUID::null, false, PERM_TRANSFER); + perm.setOwnerBits(LLUUID::null, false, PERM_TRANSFER); + new_item->setPermissions(perm); + need_update |= true; + } + if (update_name && (settings->getName() != new_item->getName())) + { + new_item->rename(settings->getName()); + settings->setName(new_item->getName()); // account for corrections + need_update |= true; + } + if (need_update) + { + new_item->updateServer(false); + gInventory.updateItem(new_item); + gInventory.notifyObservers(); + } + } + + std::stringstream buffer; + LLSD settingdata(settings->getSettings()); + LLSDSerialize::serialize(settingdata, buffer, LLSDSerialize::LLSD_NOTATION); + + LLResourceUploadInfo::ptr_t uploadInfo = std::make_shared(inv_item_id, LLAssetType::AT_SETTINGS, buffer.str(), + [settings, callback](LLUUID itemId, LLUUID newAssetId, LLUUID newItemId, LLSD response) + { + LLSettingsVOBase::onAgentAssetUploadComplete(itemId, newAssetId, newItemId, response, settings, callback); + }, + nullptr); + + LLViewerAssetUpload::EnqueueInventoryUpload(agent_url, uploadInfo); +} + +void LLSettingsVOBase::updateInventoryItem(const LLSettingsBase::ptr_t &settings, LLUUID object_id, LLUUID inv_item_id, inventory_result_fn callback) +{ + const LLViewerRegion* region = gAgent.getRegion(); + if (!region) + { + LL_WARNS("SETTINGS") << "Not connected to a region, cannot save setting." << LL_ENDL; + return; + } + + std::string agent_url(region->getCapability("UpdateSettingsAgentInventory")); + + if (!LLEnvironment::instance().isInventoryEnabled()) + { + LL_WARNS("SETTINGS") << "Region does not support settings inventory objects." << LL_ENDL; + LLNotificationsUtil::add("SettingsUnsuported"); + return; + } + + std::stringstream buffer; + LLSD settingdata(settings->getSettings()); + + LLSDSerialize::serialize(settingdata, buffer, LLSDSerialize::LLSD_NOTATION); + + LLResourceUploadInfo::ptr_t uploadInfo = std::make_shared(object_id, inv_item_id, LLAssetType::AT_SETTINGS, buffer.str(), + [settings, callback](LLUUID itemId, LLUUID taskId, LLUUID newAssetId, LLSD response) + { + LLSettingsVOBase::onTaskAssetUploadComplete(itemId, taskId, newAssetId, response, settings, callback); + }, + nullptr); + + LLViewerAssetUpload::EnqueueInventoryUpload(agent_url, uploadInfo); +} + +void LLSettingsVOBase::onAgentAssetUploadComplete(LLUUID itemId, LLUUID newAssetId, LLUUID newItemId, LLSD response, LLSettingsBase::ptr_t psettings, inventory_result_fn callback) +{ + LL_INFOS("SETTINGS") << "itemId:" << itemId << " newAssetId:" << newAssetId << " newItemId:" << newItemId << " response:" << response << LL_ENDL; + psettings->setAssetId(newAssetId); + if (callback) + callback( newAssetId, itemId, LLUUID::null, response ); +} + +void LLSettingsVOBase::onTaskAssetUploadComplete(LLUUID itemId, LLUUID taskId, LLUUID newAssetId, LLSD response, LLSettingsBase::ptr_t psettings, inventory_result_fn callback) +{ + LL_INFOS("SETTINGS") << "Upload to task complete!" << LL_ENDL; + psettings->setAssetId(newAssetId); + if (callback) + callback(newAssetId, itemId, taskId, response); +} + + +void LLSettingsVOBase::getSettingsAsset(const LLUUID &assetId, LLSettingsVOBase::asset_download_fn callback) +{ + gAssetStorage->getAssetData(assetId, LLAssetType::AT_SETTINGS, + [callback](const LLUUID &asset_id, LLAssetType::EType, void *, S32 status, LLExtStat ext_status) + { onAssetDownloadComplete(asset_id, status, ext_status, callback); }, + nullptr, true); + +} + +void LLSettingsVOBase::onAssetDownloadComplete(const LLUUID &asset_id, S32 status, LLExtStat ext_status, LLSettingsVOBase::asset_download_fn callback) +{ + LLSettingsBase::ptr_t settings; + if (!status) + { + LLFileSystem file(asset_id, LLAssetType::AT_SETTINGS, LLFileSystem::READ); + S32 size = file.getSize(); + + std::string buffer(size + 1, '\0'); + file.read((U8 *)buffer.data(), size); + + std::stringstream llsdstream(buffer); + LLSD llsdsettings; + + if (LLSDSerialize::deserialize(llsdsettings, llsdstream, LLSDSerialize::SIZE_UNLIMITED)) + { + settings = createFromLLSD(llsdsettings); + } + + if (!settings) + { + status = 1; + LL_WARNS("SETTINGS") << "Unable to create settings object." << LL_ENDL; + } + else + { + settings->setAssetId(asset_id); + } + } + else + { + LL_WARNS("SETTINGS") << "Error retrieving asset " << asset_id << ". Status code=" << status << "(" << LLAssetStorage::getErrorString(status) << ") ext_status=" << (U32)ext_status << LL_ENDL; + } + if (callback) + callback(asset_id, settings, status, ext_status); +} + +void LLSettingsVOBase::getSettingsInventory(const LLUUID &inventoryId, inventory_download_fn callback) +{ + +} + +bool LLSettingsVOBase::exportFile(const LLSettingsBase::ptr_t &settings, const std::string &filename, LLSDSerialize::ELLSD_Serialize format) +{ + try + { + std::ofstream file(filename, std::ios::out | std::ios::trunc); + file.exceptions(std::ios_base::failbit | std::ios_base::badbit); + + if (!file) + { + LL_WARNS("SETTINGS") << "Unable to open '" << filename << "' for writing." << LL_ENDL; + return false; + } + + LLSDSerialize::serialize(settings->getSettings(), file, format); + } + catch (const std::ios_base::failure &e) + { + LL_WARNS("SETTINGS") << "Unable to save settings to file '" << filename << "': " << e.what() << LL_ENDL; + return false; + } + + return true; +} + +LLSettingsBase::ptr_t LLSettingsVOBase::importFile(const std::string &filename) +{ + LLSD settings; + + try + { + std::ifstream file(filename, std::ios::in); + file.exceptions(std::ios_base::failbit | std::ios_base::badbit); + + if (!file) + { + LL_WARNS("SETTINGS") << "Unable to open '" << filename << "' for reading." << LL_ENDL; + return LLSettingsBase::ptr_t(); + } + + if (!LLSDSerialize::deserialize(settings, file, LLSDSerialize::SIZE_UNLIMITED)) + { + LL_WARNS("SETTINGS") << "Unable to deserialize settings from '" << filename << "'" << LL_ENDL; + return LLSettingsBase::ptr_t(); + } + } + catch (const std::ios_base::failure &e) + { + LL_WARNS("SETTINGS") << "Unable to save settings to file '" << filename << "': " << e.what() << LL_ENDL; + return LLSettingsBase::ptr_t(); + } + + return createFromLLSD(settings); +} + +LLSettingsBase::ptr_t LLSettingsVOBase::createFromLLSD(const LLSD &settings) +{ + if (!settings.has(SETTING_TYPE)) + { + LL_WARNS("SETTINGS") << "No settings type in LLSD" << LL_ENDL; + return LLSettingsBase::ptr_t(); + } + + std::string settingtype = settings[SETTING_TYPE].asString(); + + LLSettingsBase::ptr_t psetting; + + if (settingtype == "water") + { + return LLSettingsVOWater::buildWater(settings); + } + else if (settingtype == "sky") + { + return LLSettingsVOSky::buildSky(settings); + } + else if (settingtype == "daycycle") + { + return LLSettingsVODay::buildDay(settings); + } + + LL_WARNS("SETTINGS") << "Unable to determine settings type for '" << settingtype << "'." << LL_ENDL; + return LLSettingsBase::ptr_t(); + +} + +//========================================================================= +LLSettingsVOSky::LLSettingsVOSky(const LLSD &data, bool isAdvanced) +: LLSettingsSky(data) +, m_isAdvanced(isAdvanced) +{ +} + +LLSettingsVOSky::LLSettingsVOSky() +: LLSettingsSky() +, m_isAdvanced(false) +{ +} + +//------------------------------------------------------------------------- +LLSettingsSky::ptr_t LLSettingsVOSky::buildSky(LLSD settings) +{ + LLSettingsSky::validation_list_t validations = LLSettingsSky::validationList(); + + LLSD results = LLSettingsBase::settingValidation(settings, validations); + + if (!results["success"].asBoolean()) + { + LL_WARNS("SETTINGS") << "Sky setting validation failed!\n" << results << LL_ENDL; + LLSettingsSky::ptr_t(); + } + + return std::make_shared(settings, true); +} + + +LLSettingsSky::ptr_t LLSettingsVOSky::buildFromLegacyPreset(const std::string &name, const LLSD &oldsettings, LLSD &messages) +{ + + LLSD newsettings = LLSettingsSky::translateLegacySettings(oldsettings); + if (newsettings.isUndefined()) + { + messages["REASONS"] = LLTrans::getString("SettingTranslateError", LLSDMap("NAME", name)); + return LLSettingsSky::ptr_t(); + } + + newsettings[SETTING_NAME] = name; + + LLSettingsSky::validation_list_t validations = LLSettingsSky::validationList(); + LLSD results = LLSettingsBase::settingValidation(newsettings, validations); + if (!results["success"].asBoolean()) + { + messages["REASONS"] = LLTrans::getString("SettingValidationError", LLSDMap("NAME", name)); + LL_WARNS("SETTINGS") << "Sky setting validation failed!\n" << results << LL_ENDL; + LLSettingsSky::ptr_t(); + } + + LLSettingsSky::ptr_t skyp = std::make_shared(newsettings); + +#ifdef VERIFY_LEGACY_CONVERSION + LLSD oldsettings = LLSettingsVOSky::convertToLegacy(skyp, isAdvanced()); + + if (!llsd_equals(oldsettings, oldsettings)) + { + LL_WARNS("SKY") << "Conversion to/from legacy does not match!\n" + << "Old: " << oldsettings + << "new: " << oldsettings << LL_ENDL; + } + +#endif + + return skyp; +} + +LLSettingsSky::ptr_t LLSettingsVOSky::buildFromLegacyPresetFile(const std::string &name, const std::string &path, LLSD &messages) +{ + LLSD legacy_data = read_legacy_preset_data(name, path, messages); + + if (!legacy_data) + { // messages filled in by read_legacy_preset_data + LL_WARNS("SETTINGS") << "Could not load legacy Windlight \"" << name << "\" from " << path << LL_ENDL; + return ptr_t(); + } + + return buildFromLegacyPreset(LLURI::unescape(name), legacy_data, messages); +} + + +LLSettingsSky::ptr_t LLSettingsVOSky::buildDefaultSky() +{ + static LLSD default_settings; + + if (!default_settings.size()) + { + default_settings = LLSettingsSky::defaults(); + + default_settings[SETTING_NAME] = DEFAULT_SETTINGS_NAME; + + LLSettingsSky::validation_list_t validations = LLSettingsSky::validationList(); + LLSD results = LLSettingsBase::settingValidation(default_settings, validations); + if (!results["success"].asBoolean()) + { + LL_WARNS("SETTINGS") << "Sky setting validation failed!\n" << results << LL_ENDL; + LLSettingsSky::ptr_t(); + } + } + + LLSettingsSky::ptr_t skyp = std::make_shared(default_settings); + return skyp; +} + +LLSettingsSky::ptr_t LLSettingsVOSky::buildClone() const +{ + LLSD settings = cloneSettings(); + U32 flags = getFlags(); + + LLSettingsSky::validation_list_t validations = LLSettingsSky::validationList(); + LLSD results = LLSettingsBase::settingValidation(settings, validations); + if (!results["success"].asBoolean()) + { + LL_WARNS("SETTINGS") << "Sky setting validation failed!\n" << results << LL_ENDL; + LLSettingsSky::ptr_t(); + } + + LLSettingsSky::ptr_t skyp = std::make_shared(settings); + skyp->setFlags(flags); + return skyp; +} + +void LLSettingsVOSky::convertAtmosphericsToLegacy(LLSD& legacy, LLSD& settings) +{ + // These may need to be inferred from new settings' density profiles + // if the legacy settings values are not available. + if (settings.has(SETTING_LEGACY_HAZE)) + { + LLSD legacyhaze = settings[SETTING_LEGACY_HAZE]; + + // work-around for setter formerly putting ambient values in wrong loc... + if (legacyhaze.has(SETTING_AMBIENT)) + { + legacy[SETTING_AMBIENT] = ensure_array_4(legacyhaze[SETTING_AMBIENT], 1.0f); + } + else if (settings.has(SETTING_AMBIENT)) + { + legacy[SETTING_AMBIENT] = ensure_array_4(settings[SETTING_AMBIENT], 1.0f); + } + + legacy[SETTING_BLUE_DENSITY] = ensure_array_4(legacyhaze[SETTING_BLUE_DENSITY], 1.0); + legacy[SETTING_BLUE_HORIZON] = ensure_array_4(legacyhaze[SETTING_BLUE_HORIZON], 1.0); + + legacy[SETTING_DENSITY_MULTIPLIER] = llsd::array(legacyhaze[SETTING_DENSITY_MULTIPLIER].asReal(), 0.0f, 0.0f, 1.0f); + legacy[SETTING_DISTANCE_MULTIPLIER] = llsd::array(legacyhaze[SETTING_DISTANCE_MULTIPLIER].asReal(), 0.0f, 0.0f, 1.0f); + + legacy[SETTING_HAZE_DENSITY] = llsd::array(legacyhaze[SETTING_HAZE_DENSITY], 0.0f, 0.0f, 1.0f); + legacy[SETTING_HAZE_HORIZON] = llsd::array(legacyhaze[SETTING_HAZE_HORIZON], 0.0f, 0.0f, 1.0f); + } +} + +LLSD LLSettingsVOSky::convertToLegacy(const LLSettingsSky::ptr_t &psky, bool isAdvanced) +{ + LLSD legacy(LLSD::emptyMap()); + LLSD settings = psky->getSettings(); + + convertAtmosphericsToLegacy(legacy, settings); + + legacy[SETTING_CLOUD_COLOR] = ensure_array_4(settings[SETTING_CLOUD_COLOR], 1.0); + legacy[SETTING_CLOUD_POS_DENSITY1] = ensure_array_4(settings[SETTING_CLOUD_POS_DENSITY1], 1.0); + legacy[SETTING_CLOUD_POS_DENSITY2] = ensure_array_4(settings[SETTING_CLOUD_POS_DENSITY2], 1.0); + legacy[SETTING_CLOUD_SCALE] = llsd::array(settings[SETTING_CLOUD_SCALE], LLSD::Real(0.0), LLSD::Real(0.0), LLSD::Real(1.0)); + legacy[SETTING_CLOUD_SCROLL_RATE] = settings[SETTING_CLOUD_SCROLL_RATE]; + legacy[SETTING_LEGACY_ENABLE_CLOUD_SCROLL] = llsd::array(LLSD::Boolean(!is_approx_zero(settings[SETTING_CLOUD_SCROLL_RATE][0].asReal())), + LLSD::Boolean(!is_approx_zero(settings[SETTING_CLOUD_SCROLL_RATE][1].asReal()))); + legacy[SETTING_CLOUD_SHADOW] = llsd::array(settings[SETTING_CLOUD_SHADOW].asReal(), 0.0f, 0.0f, 1.0f); + legacy[SETTING_GAMMA] = llsd::array(settings[SETTING_GAMMA], 0.0f, 0.0f, 1.0f); + legacy[SETTING_GLOW] = ensure_array_4(settings[SETTING_GLOW], 1.0); + legacy[SETTING_LIGHT_NORMAL] = ensure_array_4(psky->getLightDirection().getValue(), 0.0f); + legacy[SETTING_MAX_Y] = llsd::array(settings[SETTING_MAX_Y], 0.0f, 0.0f, 1.0f); + legacy[SETTING_STAR_BRIGHTNESS] = settings[SETTING_STAR_BRIGHTNESS].asReal() / 250.0f; // convert from 0-500 -> 0-2 ala pre-FS-compat changes + legacy[SETTING_SUNLIGHT_COLOR] = ensure_array_4(settings[SETTING_SUNLIGHT_COLOR], 1.0f); + + LLVector3 dir = psky->getLightDirection(); + + F32 phi = asin(dir.mV[2]); + F32 cos_phi = cosf(phi); + F32 theta = (cos_phi != 0) ? asin(dir.mV[1] / cos_phi) : 0.0f; + + theta = -theta; + + // get angles back into valid ranges for legacy viewer... + // + while (theta < 0) + { + theta += F_PI * 2; + } + + if (theta > 4 * F_PI) + { + theta = fmod(theta, 2 * F_PI); + } + + while (phi < -F_PI) + { + phi += 2 * F_PI; + } + + if (phi > 3 * F_PI) + { + phi = F_PI + fmod(phi - F_PI, 2 * F_PI); + } + + legacy[SETTING_LEGACY_EAST_ANGLE] = theta; + legacy[SETTING_LEGACY_SUN_ANGLE] = phi; + + return legacy; +} + +//------------------------------------------------------------------------- +void LLSettingsVOSky::updateSettings() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT; + LLSettingsSky::updateSettings(); + LLVector3 sun_direction = getSunDirection(); + LLVector3 moon_direction = getMoonDirection(); + + // Want the dot prod of sun w/ high noon vector (0,0,1), which is just the z component + F32 dp = llmax(sun_direction[2], 0.0f); // clamped to 0 when sun is down + + // Since WL scales everything by 2, there should always be at least a 2:1 brightness ratio + // between sunlight and point lights in windlight to normalize point lights. + // + // After some A/B comparison of relesae vs EEP, tweak to allow strength to fall below 2 + // at night, for better match. (mSceneLightStrength is a divisor, so lower value means brighter + // local lights) + F32 sun_dynamic_range = llmax(gSavedSettings.getF32("RenderSunDynamicRange"), 0.0001f); + mSceneLightStrength = 2.0f * (0.75f + sun_dynamic_range * dp); + + gSky.setSunAndMoonDirectionsCFR(sun_direction, moon_direction); + gSky.setSunTextures(getSunTextureId(), getNextSunTextureId()); + gSky.setMoonTextures(getMoonTextureId(), getNextMoonTextureId()); + gSky.setCloudNoiseTextures(getCloudNoiseTextureId(), getNextCloudNoiseTextureId()); + gSky.setBloomTextures(getBloomTextureId(), getNextBloomTextureId()); + + gSky.setSunScale(getSunScale()); + gSky.setMoonScale(getMoonScale()); +} + +void LLSettingsVOSky::applySpecial(void *ptarget, bool force) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_SHADER; + LLVector3 light_direction = LLVector3(LLEnvironment::instance().getClampedLightNorm().mV); + + bool irradiance_pass = gCubeSnapshot && !gPipeline.mReflectionMapManager.isRadiancePass(); + + LLShaderUniforms* shader = &((LLShaderUniforms*)ptarget)[LLGLSLShader::SG_DEFAULT]; + { + shader->uniform3fv(LLViewerShaderMgr::LIGHTNORM, light_direction); + shader->uniform3fv(LLShaderMgr::WL_CAMPOSLOCAL, LLViewerCamera::getInstance()->getOrigin()); + } + + shader = &((LLShaderUniforms*)ptarget)[LLGLSLShader::SG_SKY]; + + shader->uniform3fv(LLViewerShaderMgr::LIGHTNORM, light_direction); + + // Legacy? SETTING_CLOUD_SCROLL_RATE("cloud_scroll_rate") + LLVector4 vect_c_p_d1(mSettings[SETTING_CLOUD_POS_DENSITY1]); + LLVector4 cloud_scroll( LLEnvironment::instance().getCloudScrollDelta() ); + + // SL-13084 EEP added support for custom cloud textures -- flip them horizontally to match the preview of Clouds > Cloud Scroll + // Keep in Sync! + // * indra\newview\llsettingsvo.cpp + // * indra\newview\app_settings\shaders\class2\windlight\cloudsV.glsl + // * indra\newview\app_settings\shaders\class1\deferred\cloudsV.glsl + cloud_scroll[0] = -cloud_scroll[0]; + vect_c_p_d1 += cloud_scroll; + shader->uniform3fv(LLShaderMgr::CLOUD_POS_DENSITY1, LLVector3(vect_c_p_d1.mV)); + + LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); + + // TODO -- make these getters return vec3s + LLVector3 sunDiffuse = LLVector3(psky->getSunlightColor().mV); + LLVector3 moonDiffuse = LLVector3(psky->getMoonlightColor().mV); + + shader->uniform3fv(LLShaderMgr::SUNLIGHT_COLOR, sunDiffuse); + shader->uniform3fv(LLShaderMgr::MOONLIGHT_COLOR, moonDiffuse); + + shader->uniform3fv(LLShaderMgr::CLOUD_COLOR, LLVector3(psky->getCloudColor().mV)); + + shader = &((LLShaderUniforms*)ptarget)[LLGLSLShader::SG_ANY]; + shader->uniform1f(LLShaderMgr::SCENE_LIGHT_STRENGTH, mSceneLightStrength); + + LLColor3 ambient(getTotalAmbient()); + + F32 g = getGamma(); + + static LLCachedControl should_auto_adjust(gSavedSettings, "RenderSkyAutoAdjustLegacy", true); + static LLCachedControl auto_adjust_ambient_scale(gSavedSettings, "RenderSkyAutoAdjustAmbientScale", 0.75f); + static LLCachedControl auto_adjust_hdr_scale(gSavedSettings, "RenderSkyAutoAdjustHDRScale", 2.f); + static LLCachedControl auto_adjust_blue_horizon_scale(gSavedSettings, "RenderSkyAutoAdjustBlueHorizonScale", 1.f); + static LLCachedControl auto_adjust_blue_density_scale(gSavedSettings, "RenderSkyAutoAdjustBlueDensityScale", 1.f); + static LLCachedControl auto_adjust_sun_color_scale(gSavedSettings, "RenderSkyAutoAdjustSunColorScale", 1.f); + static LLCachedControl sunlight_scale(gSavedSettings, "RenderSkySunlightScale", 1.5f); + static LLCachedControl ambient_scale(gSavedSettings, "RenderSkyAmbientScale", 1.5f); + + shader->uniform1f(LLShaderMgr::SKY_SUNLIGHT_SCALE, sunlight_scale); + shader->uniform1f(LLShaderMgr::SKY_AMBIENT_SCALE, ambient_scale); + + static LLCachedControl cloud_shadow_scale(gSavedSettings, "RenderCloudShadowAmbianceFactor", 0.125f); + F32 probe_ambiance = getTotalReflectionProbeAmbiance(cloud_shadow_scale); + + if (irradiance_pass) + { // during an irradiance map update, disable ambient lighting (direct lighting only) and desaturate sky color (avoid tinting the world blue) + shader->uniform3fv(LLShaderMgr::AMBIENT, LLVector3::zero.mV); + } + else + { + if (psky->getReflectionProbeAmbiance() != 0.f) + { + shader->uniform3fv(LLShaderMgr::AMBIENT, LLVector3(ambient.mV)); + shader->uniform1f(LLShaderMgr::SKY_HDR_SCALE, sqrtf(g)*2.0); // use a modifier here so 1.0 maps to the "most desirable" default and the maximum value doesn't go off the rails + } + else if (psky->canAutoAdjust() && should_auto_adjust) + { // auto-adjust legacy sky to take advantage of probe ambiance + shader->uniform3fv(LLShaderMgr::AMBIENT, (ambient * auto_adjust_ambient_scale).mV); + shader->uniform1f(LLShaderMgr::SKY_HDR_SCALE, auto_adjust_hdr_scale); + LLColor3 blue_horizon = getBlueHorizon() * auto_adjust_blue_horizon_scale; + LLColor3 blue_density = getBlueDensity() * auto_adjust_blue_density_scale; + LLColor3 sun_diffuse = getSunDiffuse() * auto_adjust_sun_color_scale; + + shader->uniform3fv(LLShaderMgr::SUNLIGHT_COLOR, sun_diffuse.mV); + shader->uniform3fv(LLShaderMgr::BLUE_DENSITY, blue_density.mV); + shader->uniform3fv(LLShaderMgr::BLUE_HORIZON, blue_horizon.mV); + + probe_ambiance = sAutoAdjustProbeAmbiance; + } + else + { + shader->uniform1f(LLShaderMgr::SKY_HDR_SCALE, 1.f); + shader->uniform3fv(LLShaderMgr::AMBIENT, LLVector3(ambient.mV)); + } + } + + shader->uniform1f(LLShaderMgr::REFLECTION_PROBE_AMBIANCE, probe_ambiance); + + shader->uniform1i(LLShaderMgr::SUN_UP_FACTOR, getIsSunUp() ? 1 : 0); + shader->uniform1f(LLShaderMgr::SUN_MOON_GLOW_FACTOR, getSunMoonGlowFactor()); + shader->uniform1f(LLShaderMgr::DENSITY_MULTIPLIER, getDensityMultiplier()); + shader->uniform1f(LLShaderMgr::DISTANCE_MULTIPLIER, getDistanceMultiplier()); + + shader->uniform1f(LLShaderMgr::GAMMA, g); +} + +LLSettingsSky::parammapping_t LLSettingsVOSky::getParameterMap() const +{ + static parammapping_t param_map; + + if (param_map.empty()) + { +// LEGACY_ATMOSPHERICS + + // Todo: default 'legacy' values duplicate the ones from functions like getBlueDensity() find a better home for them + // There is LLSettingsSky::defaults(), but it doesn't contain everything since it is geared towards creating new settings. + param_map[SETTING_AMBIENT] = DefaultParam(LLShaderMgr::AMBIENT, LLColor3(0.25f, 0.25f, 0.25f).getValue()); + param_map[SETTING_BLUE_DENSITY] = DefaultParam(LLShaderMgr::BLUE_DENSITY, LLColor3(0.2447f, 0.4487f, 0.7599f).getValue()); + param_map[SETTING_BLUE_HORIZON] = DefaultParam(LLShaderMgr::BLUE_HORIZON, LLColor3(0.4954f, 0.4954f, 0.6399f).getValue()); + param_map[SETTING_HAZE_DENSITY] = DefaultParam(LLShaderMgr::HAZE_DENSITY, LLSD(0.7f)); + param_map[SETTING_HAZE_HORIZON] = DefaultParam(LLShaderMgr::HAZE_HORIZON, LLSD(0.19f)); + param_map[SETTING_DENSITY_MULTIPLIER] = DefaultParam(LLShaderMgr::DENSITY_MULTIPLIER, LLSD(0.0001f)); + param_map[SETTING_DISTANCE_MULTIPLIER] = DefaultParam(LLShaderMgr::DISTANCE_MULTIPLIER, LLSD(0.8f)); + + // Following values are always present, so we can just zero these ones, but used values from defaults() + LLSD sky_defaults = LLSettingsSky::defaults(); + + param_map[SETTING_CLOUD_POS_DENSITY2] = DefaultParam(LLShaderMgr::CLOUD_POS_DENSITY2, sky_defaults[SETTING_CLOUD_POS_DENSITY2]); + param_map[SETTING_CLOUD_SCALE] = DefaultParam(LLShaderMgr::CLOUD_SCALE, sky_defaults[SETTING_CLOUD_SCALE]); + param_map[SETTING_CLOUD_SHADOW] = DefaultParam(LLShaderMgr::CLOUD_SHADOW, sky_defaults[SETTING_CLOUD_SHADOW]); + param_map[SETTING_CLOUD_VARIANCE] = DefaultParam(LLShaderMgr::CLOUD_VARIANCE, sky_defaults[SETTING_CLOUD_VARIANCE]); + param_map[SETTING_GLOW] = DefaultParam(LLShaderMgr::GLOW, sky_defaults[SETTING_GLOW]); + param_map[SETTING_MAX_Y] = DefaultParam(LLShaderMgr::MAX_Y, sky_defaults[SETTING_MAX_Y]); + + //param_map[SETTING_SUNLIGHT_COLOR] = DefaultParam(LLShaderMgr::SUNLIGHT_COLOR, sky_defaults[SETTING_SUNLIGHT_COLOR]); + //param_map[SETTING_CLOUD_COLOR] = DefaultParam(LLShaderMgr::CLOUD_COLOR, sky_defaults[SETTING_CLOUD_COLOR]); + + param_map[SETTING_MOON_BRIGHTNESS] = DefaultParam(LLShaderMgr::MOON_BRIGHTNESS, sky_defaults[SETTING_MOON_BRIGHTNESS]); + param_map[SETTING_SKY_MOISTURE_LEVEL] = DefaultParam(LLShaderMgr::MOISTURE_LEVEL, sky_defaults[SETTING_SKY_MOISTURE_LEVEL]); + param_map[SETTING_SKY_DROPLET_RADIUS] = DefaultParam(LLShaderMgr::DROPLET_RADIUS, sky_defaults[SETTING_SKY_DROPLET_RADIUS]); + param_map[SETTING_SKY_ICE_LEVEL] = DefaultParam(LLShaderMgr::ICE_LEVEL, sky_defaults[SETTING_SKY_ICE_LEVEL]); + + param_map[SETTING_REFLECTION_PROBE_AMBIANCE] = DefaultParam(LLShaderMgr::REFLECTION_PROBE_AMBIANCE, sky_defaults[SETTING_REFLECTION_PROBE_AMBIANCE]); +// AdvancedAtmospherics TODO +// Provide mappings for new shader params here + } + + return param_map; +} + +//========================================================================= +const F32 LLSettingsVOWater::WATER_FOG_LIGHT_CLAMP(0.3f); + +//------------------------------------------------------------------------- +LLSettingsVOWater::LLSettingsVOWater(const LLSD &data) : + LLSettingsWater(data) +{ + +} + +LLSettingsVOWater::LLSettingsVOWater() : + LLSettingsWater() +{ + +} + +LLSettingsWater::ptr_t LLSettingsVOWater::buildWater(LLSD settings) +{ + LLSettingsWater::validation_list_t validations = LLSettingsWater::validationList(); + LLSD results = LLSettingsWater::settingValidation(settings, validations); + if (!results["success"].asBoolean()) + { + LL_WARNS("SETTINGS") << "Water setting validation failed!\n" << results << LL_ENDL; + LLSettingsWater::ptr_t(); + } + + return std::make_shared(settings); +} + +//------------------------------------------------------------------------- +LLSettingsWater::ptr_t LLSettingsVOWater::buildFromLegacyPreset(const std::string &name, const LLSD &oldsettings, LLSD &messages) +{ + LLSD newsettings(LLSettingsWater::translateLegacySettings(oldsettings)); + if (newsettings.isUndefined()) + { + messages["REASONS"] = LLTrans::getString("SettingTranslateError", LLSDMap("NAME", name)); + return LLSettingsWater::ptr_t(); + } + + newsettings[SETTING_NAME] = name; + LLSettingsWater::validation_list_t validations = LLSettingsWater::validationList(); + LLSD results = LLSettingsWater::settingValidation(newsettings, validations); + if (!results["success"].asBoolean()) + { + messages["REASONS"] = LLTrans::getString("SettingValidationError", name); + LL_WARNS("SETTINGS") << "Water setting validation failed!: " << results << LL_ENDL; + return LLSettingsWater::ptr_t(); + } + + LLSettingsWater::ptr_t waterp = std::make_shared(newsettings); + +#ifdef VERIFY_LEGACY_CONVERSION + LLSD oldsettings = LLSettingsVOWater::convertToLegacy(waterp); + + if (!llsd_equals(oldsettings, oldsettings)) + { + LL_WARNS("WATER") << "Conversion to/from legacy does not match!\n" + << "Old: " << oldsettings + << "new: " << oldsettings << LL_ENDL; + } + +#endif + return waterp; +} + +LLSettingsWater::ptr_t LLSettingsVOWater::buildFromLegacyPresetFile(const std::string &name, const std::string &path, LLSD &messages) +{ + LLSD legacy_data = read_legacy_preset_data(name, path, messages); + + if (!legacy_data) + { // messages filled in by read_legacy_preset_data + LL_WARNS("SETTINGS") << "Could not load legacy Windlight \"" << name << "\" from " << path << LL_ENDL; + return ptr_t(); + } + + return buildFromLegacyPreset(LLURI::unescape(name), legacy_data, messages); +} + + +LLSettingsWater::ptr_t LLSettingsVOWater::buildDefaultWater() +{ + static LLSD default_settings; + + if (!default_settings.size()) + { + default_settings = LLSettingsWater::defaults(); + + default_settings[SETTING_NAME] = DEFAULT_SETTINGS_NAME; + + LLSettingsWater::validation_list_t validations = LLSettingsWater::validationList(); + LLSD results = LLSettingsWater::settingValidation(default_settings, validations); + if (!results["success"].asBoolean()) + { + LL_WARNS("SETTINGS") << "Water setting validation failed!: " << results << LL_ENDL; + return LLSettingsWater::ptr_t(); + } + } + + LLSettingsWater::ptr_t waterp = std::make_shared(default_settings); + + return waterp; +} + +LLSettingsWater::ptr_t LLSettingsVOWater::buildClone() const +{ + LLSD settings = cloneSettings(); + U32 flags = getFlags(); + LLSettingsWater::validation_list_t validations = LLSettingsWater::validationList(); + LLSD results = LLSettingsWater::settingValidation(settings, validations); + if (!results["success"].asBoolean()) + { + LL_WARNS("SETTINGS") << "Water setting validation failed!: " << results << LL_ENDL; + return LLSettingsWater::ptr_t(); + } + + LLSettingsWater::ptr_t waterp = std::make_shared(settings); + waterp->setFlags(flags); + return waterp; +} + +LLSD LLSettingsVOWater::convertToLegacy(const LLSettingsWater::ptr_t &pwater) +{ + LLSD legacy(LLSD::emptyMap()); + LLSD settings = pwater->getSettings(); + + legacy[SETTING_LEGACY_BLUR_MULTIPLIER] = settings[SETTING_BLUR_MULTIPLIER]; + legacy[SETTING_LEGACY_FOG_COLOR] = ensure_array_4(settings[SETTING_FOG_COLOR], 1.0f); + legacy[SETTING_LEGACY_FOG_DENSITY] = settings[SETTING_FOG_DENSITY]; + legacy[SETTING_LEGACY_FOG_MOD] = settings[SETTING_FOG_MOD]; + legacy[SETTING_LEGACY_FRESNEL_OFFSET] = settings[SETTING_FRESNEL_OFFSET]; + legacy[SETTING_LEGACY_FRESNEL_SCALE] = settings[SETTING_FRESNEL_SCALE]; + legacy[SETTING_LEGACY_NORMAL_MAP] = settings[SETTING_NORMAL_MAP]; + legacy[SETTING_LEGACY_NORMAL_SCALE] = settings[SETTING_NORMAL_SCALE]; + legacy[SETTING_LEGACY_SCALE_ABOVE] = settings[SETTING_SCALE_ABOVE]; + legacy[SETTING_LEGACY_SCALE_BELOW] = settings[SETTING_SCALE_BELOW]; + legacy[SETTING_LEGACY_WAVE1_DIR] = settings[SETTING_WAVE1_DIR]; + legacy[SETTING_LEGACY_WAVE2_DIR] = settings[SETTING_WAVE2_DIR]; + + return legacy; +} +//------------------------------------------------------------------------- +//------------------------------------------------------------------------- +void LLSettingsVOWater::applySpecial(void *ptarget, bool force) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_SHADER; + + LLEnvironment& env = LLEnvironment::instance(); + + auto group = LLGLSLShader::SG_ANY; + LLShaderUniforms* shader = &((LLShaderUniforms*)ptarget)[group]; + + { + F32 water_height = env.getWaterHeight(); + + if (LLViewerCamera::instance().cameraUnderWater()) + { // when the camera is under water, use the water height at the camera position + LLViewerRegion* region = LLWorld::instance().getRegionFromPosAgent(LLViewerCamera::instance().getOrigin()); + if (region) + { + water_height = region->getWaterHeight(); + } + } + + //transform water plane to eye space + glh::vec3f norm(0.f, 0.f, 1.f); + glh::vec3f p(0.f, 0.f, water_height); + + F32 modelView[16]; + for (U32 i = 0; i < 16; i++) + { + modelView[i] = (F32)gGLModelView[i]; + } + + glh::matrix4f mat(modelView); + glh::matrix4f invtrans = mat.inverse().transpose(); + glh::vec3f enorm; + glh::vec3f ep; + invtrans.mult_matrix_vec(norm, enorm); + enorm.normalize(); + mat.mult_matrix_vec(p, ep); + + LLVector4 waterPlane(enorm.v[0], enorm.v[1], enorm.v[2], -ep.dot(enorm)); + + LLDrawPoolAlpha::sWaterPlane = waterPlane; + + shader->uniform4fv(LLShaderMgr::WATER_WATERPLANE, waterPlane.mV); + + LLVector4 light_direction = env.getClampedLightNorm(); + + F32 waterFogKS = 1.f / llmax(light_direction.mV[2], WATER_FOG_LIGHT_CLAMP); + + shader->uniform1f(LLShaderMgr::WATER_FOGKS, waterFogKS); + + F32 eyedepth = LLViewerCamera::getInstance()->getOrigin().mV[2] - water_height; + bool underwater = (eyedepth <= 0.0f); + + F32 waterFogDensity = env.getCurrentWater()->getModifiedWaterFogDensity(underwater); + shader->uniform1f(LLShaderMgr::WATER_FOGDENSITY, waterFogDensity); + + LLColor4 fog_color(env.getCurrentWater()->getWaterFogColor()); + shader->uniform4fv(LLShaderMgr::WATER_FOGCOLOR, fog_color.mV); + + shader->uniform3fv(LLShaderMgr::WATER_FOGCOLOR_LINEAR, linearColor3(fog_color).mV); + + F32 blend_factor = env.getCurrentWater()->getBlendFactor(); + shader->uniform1f(LLShaderMgr::BLEND_FACTOR, blend_factor); + + // update to normal lightnorm, water shader itself will use rotated lightnorm as necessary + shader->uniform3fv(LLShaderMgr::LIGHTNORM, light_direction.mV); + } +} + +void LLSettingsVOWater::updateSettings() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + // base class clears dirty flag so as to not trigger recursive update + LLSettingsBase::updateSettings(); + + LLDrawPoolWater* pwaterpool = (LLDrawPoolWater*)gPipeline.getPool(LLDrawPool::POOL_WATER); + if (pwaterpool) + { + pwaterpool->setTransparentTextures(getTransparentTextureID(), getNextTransparentTextureID()); + pwaterpool->setOpaqueTexture(GetDefaultOpaqueTextureAssetId()); + pwaterpool->setNormalMaps(getNormalMapID(), getNextNormalMapID()); + } +} + +LLSettingsWater::parammapping_t LLSettingsVOWater::getParameterMap() const +{ + static parammapping_t param_map; + + return param_map; +} + +//========================================================================= +LLSettingsVODay::LLSettingsVODay(const LLSD &data): + LLSettingsDay(data) +{} + +LLSettingsVODay::LLSettingsVODay(): + LLSettingsDay() +{} + +LLSettingsDay::ptr_t LLSettingsVODay::buildDay(LLSD settings) +{ + LLSettingsDay::validation_list_t validations = LLSettingsDay::validationList(); + LLSD results = LLSettingsDay::settingValidation(settings, validations); + if (!results["success"].asBoolean()) + { + LL_WARNS("SETTINGS") << "Day setting validation failed!\n" << results << LL_ENDL; + LLSettingsDay::ptr_t(); + } + + LLSettingsDay::ptr_t pday = std::make_shared(settings); + if (pday) + pday->initialize(); + + return pday; +} + +//------------------------------------------------------------------------- +LLSettingsDay::ptr_t LLSettingsVODay::buildFromLegacyPreset(const std::string &name, const std::string &path, const LLSD &oldsettings, LLSD &messages) +{ + LLSD newsettings(defaults()); + std::set framenames; + std::set notfound; + + // expected and correct folder sctructure is to have + // three folders in widnlight's root: days, water, skies + std::string base_path(gDirUtilp->getDirName(path)); + std::string water_path(base_path); + std::string sky_path(base_path); + std::string day_path(base_path); + + gDirUtilp->append(water_path, "water"); + gDirUtilp->append(sky_path, "skies"); + gDirUtilp->append(day_path, "days"); + + if (!gDirUtilp->fileExists(day_path)) + { + LL_WARNS("SETTINGS") << "File " << name << ".xml is not in \"days\" folder." << LL_ENDL; + } + + if (!gDirUtilp->fileExists(water_path)) + { + LL_WARNS("SETTINGS") << "Failed to find accompaniying water folder for file " << name + << ".xml. Falling back to using default folder" << LL_ENDL; + + water_path = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "windlight"); + gDirUtilp->append(water_path, "water"); + } + + if (!gDirUtilp->fileExists(sky_path)) + { + LL_WARNS("SETTINGS") << "Failed to find accompaniying skies folder for file " << name + << ".xml. Falling back to using default folder" << LL_ENDL; + + sky_path = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "windlight"); + gDirUtilp->append(sky_path, "skies"); + } + + newsettings[SETTING_NAME] = name; + + LLSD watertrack = llsd::array( + LLSDMap(SETTING_KEYKFRAME, LLSD::Real(0.0f)) + (SETTING_KEYNAME, "water:Default")); + + LLSD skytrack = LLSD::emptyArray(); + + for (LLSD::array_const_iterator it = oldsettings.beginArray(); it != oldsettings.endArray(); ++it) + { + std::string framename = (*it)[1].asString(); + LLSD entry = LLSDMap(SETTING_KEYKFRAME, (*it)[0].asReal()) + (SETTING_KEYNAME, "sky:" + framename); + framenames.insert(framename); + skytrack.append(entry); + } + + newsettings[SETTING_TRACKS] = llsd::array(watertrack, skytrack); + + LLSD frames(LLSD::emptyMap()); + + { + LLSettingsWater::ptr_t pwater = LLSettingsVOWater::buildFromLegacyPresetFile("Default", water_path, messages); + if (!pwater) + { // messages filled in by buildFromLegacyPresetFile + return LLSettingsDay::ptr_t(); + } + frames["water:Default"] = pwater->getSettings(); + } + + for (std::set::iterator itn = framenames.begin(); itn != framenames.end(); ++itn) + { + LLSettingsSky::ptr_t psky = LLSettingsVOSky::buildFromLegacyPresetFile((*itn), sky_path, messages); + if (!psky) + { // messages filled in by buildFromLegacyPresetFile + return LLSettingsDay::ptr_t(); + } + frames["sky:" + (*itn)] = psky->getSettings(); + } + + newsettings[SETTING_FRAMES] = frames; + + LLSettingsDay::validation_list_t validations = LLSettingsDay::validationList(); + LLSD results = LLSettingsDay::settingValidation(newsettings, validations); + if (!results["success"].asBoolean()) + { + messages["REASONS"] = LLTrans::getString("SettingValidationError", LLSDMap("NAME", name)); + LL_WARNS("SETTINGS") << "Day setting validation failed!: " << results << LL_ENDL; + return LLSettingsDay::ptr_t(); + } + + LLSettingsDay::ptr_t dayp = std::make_shared(newsettings); + +#ifdef VERIFY_LEGACY_CONVERSION + LLSD testsettings = LLSettingsVODay::convertToLegacy(dayp); + + if (!llsd_equals(oldsettings, testsettings)) + { + LL_WARNS("DAYCYCLE") << "Conversion to/from legacy does not match!\n" + << "Old: " << oldsettings + << "new: " << testsettings << LL_ENDL; + } + +#endif + + dayp->initialize(); + + return dayp; +} + +LLSettingsDay::ptr_t LLSettingsVODay::buildFromLegacyPresetFile(const std::string &name, const std::string &path, LLSD &messages) +{ + LLSD legacy_data = read_legacy_preset_data(name, path, messages); + + if (!legacy_data) + { // messages filled in by read_legacy_preset_data + LL_WARNS("SETTINGS") << "Could not load legacy Windlight \"" << name << "\" from " << path << LL_ENDL; + return ptr_t(); + } + // Name for LLSettingsDay only, path to get related files from filesystem + return buildFromLegacyPreset(LLURI::unescape(name), path, legacy_data, messages); +} + + + +LLSettingsDay::ptr_t LLSettingsVODay::buildFromLegacyMessage(const LLUUID ®ionId, LLSD daycycle, LLSD skydefs, LLSD waterdef) +{ + LLSD frames(LLSD::emptyMap()); + + for (LLSD::map_iterator itm = skydefs.beginMap(); itm != skydefs.endMap(); ++itm) + { + std::string newname = "sky:" + (*itm).first; + LLSD newsettings = LLSettingsSky::translateLegacySettings((*itm).second); + + newsettings[SETTING_NAME] = newname; + frames[newname] = newsettings; + + LL_WARNS("SETTINGS") << "created region sky '" << newname << "'" << LL_ENDL; + } + + LLSD watersettings = LLSettingsWater::translateLegacySettings(waterdef); + std::string watername = "water:"+ watersettings[SETTING_NAME].asString(); + watersettings[SETTING_NAME] = watername; + frames[watername] = watersettings; + + LLSD watertrack = llsd::array( + LLSDMap(SETTING_KEYKFRAME, LLSD::Real(0.0f)) + (SETTING_KEYNAME, watername)); + + LLSD skytrack(LLSD::emptyArray()); + for (LLSD::array_const_iterator it = daycycle.beginArray(); it != daycycle.endArray(); ++it) + { + LLSD entry = LLSDMap(SETTING_KEYKFRAME, (*it)[0].asReal()) + (SETTING_KEYNAME, "sky:" + (*it)[1].asString()); + skytrack.append(entry); + } + + LLSD newsettings = LLSDMap + ( SETTING_NAME, "Region (legacy)" ) + ( SETTING_TRACKS, llsd::array(watertrack, skytrack)) + ( SETTING_FRAMES, frames ) + ( SETTING_TYPE, "daycycle" ); + + LLSettingsSky::validation_list_t validations = LLSettingsDay::validationList(); + LLSD results = LLSettingsDay::settingValidation(newsettings, validations); + if (!results["success"].asBoolean()) + { + LL_WARNS("SETTINGS") << "Day setting validation failed!:" << results << LL_ENDL; + return LLSettingsDay::ptr_t(); + } + + LLSettingsDay::ptr_t dayp = std::make_shared(newsettings); + + if (dayp) + { + // true for validation - either validate here, or when cloning for floater. + dayp->initialize(true); + } + return dayp; +} + + + +LLSettingsDay::ptr_t LLSettingsVODay::buildDefaultDayCycle() +{ + static LLSD default_settings; + + if (!default_settings.size()) + { + default_settings = LLSettingsDay::defaults(); + default_settings[SETTING_NAME] = DEFAULT_SETTINGS_NAME; + + LLSettingsDay::validation_list_t validations = LLSettingsDay::validationList(); + LLSD results = LLSettingsDay::settingValidation(default_settings, validations); + if (!results["success"].asBoolean()) + { + LL_WARNS("SETTINGS") << "Day setting validation failed!\n" << results << LL_ENDL; + LLSettingsDay::ptr_t(); + } + } + + LLSettingsDay::ptr_t dayp = std::make_shared(default_settings); + + dayp->initialize(); + return dayp; +} + +LLSettingsDay::ptr_t LLSettingsVODay::buildFromEnvironmentMessage(LLSD settings) +{ + LLSettingsDay::validation_list_t validations = LLSettingsDay::validationList(); + LLSD results = LLSettingsDay::settingValidation(settings, validations); + if (!results["success"].asBoolean()) + { + LL_WARNS("SETTINGS") << "Day setting validation failed!\n" << results << LL_ENDL; + LLSettingsDay::ptr_t(); + } + + LLSettingsDay::ptr_t dayp = std::make_shared(settings); + + dayp->initialize(); + return dayp; +} + + +void LLSettingsVODay::buildFromOtherSetting(LLSettingsBase::ptr_t settings, LLSettingsVODay::asset_built_fn cb) +{ + if (settings->getSettingsType() == "daycycle") + { + if (cb) + cb(std::static_pointer_cast(settings)); + } + else + { + LLSettingsVOBase::getSettingsAsset(LLSettingsDay::GetDefaultAssetId(), + [settings, cb](LLUUID, LLSettingsBase::ptr_t pday, S32, LLExtStat){ combineIntoDayCycle(std::static_pointer_cast(pday), settings, cb); }); + } +} + +void LLSettingsVODay::combineIntoDayCycle(LLSettingsDay::ptr_t pday, LLSettingsBase::ptr_t settings, asset_built_fn cb) +{ + if (settings->getSettingsType() == "sky") + { + pday->setName("sky: " + settings->getName()); + pday->clearCycleTrack(1); + pday->setSettingsAtKeyframe(settings, 0.0, 1); + } + else if (settings->getSettingsType() == "water") + { + pday->setName("water: " + settings->getName()); + pday->clearCycleTrack(0); + pday->setSettingsAtKeyframe(settings, 0.0, 0); + } + else + { + pday.reset(); + } + + if (cb) + cb(pday); +} + + +LLSettingsDay::ptr_t LLSettingsVODay::buildClone() const +{ + LLSD settings = cloneSettings(); + + LLSettingsDay::validation_list_t validations = LLSettingsDay::validationList(); + LLSD results = LLSettingsDay::settingValidation(settings, validations); + if (!results["success"].asBoolean()) + { + LL_WARNS("SETTINGS") << "Day setting validation failed!\n" << results << LL_ENDL; + LLSettingsDay::ptr_t(); + } + + LLSettingsDay::ptr_t dayp = std::make_shared(settings); + + U32 flags = getFlags(); + if (flags) + dayp->setFlags(flags); + + dayp->initialize(); + return dayp; +} + +LLSettingsDay::ptr_t LLSettingsVODay::buildDeepCloneAndUncompress() const +{ + // no need for SETTING_TRACKS or SETTING_FRAMES, so take base LLSD + LLSD settings = llsd_clone(mSettings); + + U32 flags = getFlags(); + LLSettingsDay::ptr_t day_clone = std::make_shared(settings); + + for (S32 i = 0; i < LLSettingsDay::TRACK_MAX; ++i) + { + const LLSettingsDay::CycleTrack_t& track = getCycleTrackConst(i); + LLSettingsDay::CycleTrack_t::const_iterator iter = track.begin(); + while (iter != track.end()) + { + // 'Unpack', usually for editing + // - frames 'share' settings multiple times + // - settings can reuse LLSDs they were initialized from + // We do not want for edited frame to change multiple frames in same track, so do a clone + day_clone->setSettingsAtKeyframe(iter->second->buildDerivedClone(), iter->first, i); + iter++; + } + } + day_clone->setFlags(flags); + return day_clone; +} + +LLSD LLSettingsVODay::convertToLegacy(const LLSettingsVODay::ptr_t &pday) +{ + CycleTrack_t &trackwater = pday->getCycleTrack(TRACK_WATER); + + LLSettingsWater::ptr_t pwater; + if (!trackwater.empty()) + { + pwater = std::static_pointer_cast((*trackwater.begin()).second); + } + + if (!pwater) + pwater = LLSettingsVOWater::buildDefaultWater(); + + LLSD llsdwater = LLSettingsVOWater::convertToLegacy(pwater); + + CycleTrack_t &tracksky = pday->getCycleTrack(1); // first sky track + std::map skys; + + LLSD llsdcycle(LLSD::emptyArray()); + + for(CycleTrack_t::iterator it = tracksky.begin(); it != tracksky.end(); ++it) + { + size_t hash = (*it).second->getHash(); + std::stringstream name; + + name << hash; + + skys[name.str()] = std::static_pointer_cast((*it).second); + + F32 frame = ((tracksky.size() == 1) && (it == tracksky.begin())) ? -1.0f : (*it).first; + llsdcycle.append( llsd::array(LLSD::Real(frame), name.str()) ); + } + + LLSD llsdskylist(LLSD::emptyMap()); + + for (std::map::iterator its = skys.begin(); its != skys.end(); ++its) + { + LLSD llsdsky = LLSettingsVOSky::convertToLegacy((*its).second, false); + llsdsky[SETTING_NAME] = (*its).first; + + llsdskylist[(*its).first] = llsdsky; + } + + return llsd::array(LLSD::emptyMap(), llsdcycle, llsdskylist, llsdwater); +} + +LLSettingsSkyPtr_t LLSettingsVODay::getDefaultSky() const +{ + return LLSettingsVOSky::buildDefaultSky(); +} + +LLSettingsWaterPtr_t LLSettingsVODay::getDefaultWater() const +{ + return LLSettingsVOWater::buildDefaultWater(); +} + +LLSettingsSkyPtr_t LLSettingsVODay::buildSky(LLSD settings) const +{ + LLSettingsSky::ptr_t skyp = std::make_shared(settings); + + if (skyp->validate()) + return skyp; + + return LLSettingsSky::ptr_t(); +} + +LLSettingsWaterPtr_t LLSettingsVODay::buildWater(LLSD settings) const +{ + LLSettingsWater::ptr_t waterp = std::make_shared(settings); + + if (waterp->validate()) + return waterp; + + return LLSettingsWater::ptr_t(); +} + +//========================================================================= +namespace +{ + LLSD ensure_array_4(LLSD in, F32 fill) + { + if (in.size() >= 4) + return in; + + LLSD out(LLSD::emptyArray()); + + for (S32 idx = 0; idx < in.size(); ++idx) + { + out.append(in[idx]); + } + + while (out.size() < 4) + { + out.append(LLSD::Real(fill)); + } + return out; + } + + // This is a disturbing hack + std::string legacy_name_to_filename(const std::string &name, bool convertdash = false) + { + std::string fixedname(LLURI::escape(name)); + + if (convertdash) + boost::algorithm::replace_all(fixedname, "-", "%2D"); + + return fixedname; + } + + //--------------------------------------------------------------------- + LLSD read_legacy_preset_data(const std::string &name, const std::string& path, LLSD &messages) + { + llifstream xml_file; + + std::string full_path(path); + std::string full_name(name); + full_name += ".xml"; + gDirUtilp->append(full_path, full_name); + + xml_file.open(full_path.c_str()); + if (!xml_file) + { + std::string bad_path(full_path); + full_path = path; + full_name = legacy_name_to_filename(name); + full_name += ".xml"; + gDirUtilp->append(full_path, full_name); + + LL_INFOS("LEGACYSETTING") << "Could not open \"" << bad_path << "\" trying escaped \"" << full_path << "\"" << LL_ENDL; + + xml_file.open(full_path.c_str()); + if (!xml_file) + { + LL_WARNS("LEGACYSETTING") << "Unable to open legacy windlight \"" << name << "\" from " << path << LL_ENDL; + + full_path = path; + full_name = legacy_name_to_filename(name, true); + full_name += ".xml"; + gDirUtilp->append(full_path, full_name); + xml_file.open(full_path.c_str()); + if (!xml_file) + { + messages["REASONS"] = LLTrans::getString("SettingImportFileError", LLSDMap("FILE", bad_path)); + LL_WARNS("LEGACYSETTING") << "Unable to open legacy windlight \"" << name << "\" from " << path << LL_ENDL; + return LLSD(); + } + } + } + + LLSD params_data; + LLPointer parser = new LLSDXMLParser(); + if (parser->parse(xml_file, params_data, LLSDSerialize::SIZE_UNLIMITED) == LLSDParser::PARSE_FAILURE) + { + xml_file.close(); + messages["REASONS"] = LLTrans::getString("SettingParseFileError", LLSDMap("FILE", full_path)); + return LLSD(); + } + xml_file.close(); + + return params_data; + } +} diff --git a/indra/newview/llshareavatarhandler.cpp b/indra/newview/llshareavatarhandler.cpp index 77badea302..2ec47ed456 100644 --- a/indra/newview/llshareavatarhandler.cpp +++ b/indra/newview/llshareavatarhandler.cpp @@ -1,67 +1,67 @@ -/** - * @file llshareavatarhandler.cpp - * @brief slapp to handle sharing with an avatar - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llcommandhandler.h" -#include "llavataractions.h" -#include "llnotificationsutil.h" -#include "llui.h" - -class LLShareWithAvatarHandler : public LLCommandHandler -{ -public: - // requires trusted browser to trigger - LLShareWithAvatarHandler() : LLCommandHandler("sharewithavatar", UNTRUSTED_THROTTLE) - { - } - - bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) - { - if (!LLUI::getInstance()->mSettingGroups["config"]->getBOOL("EnableAvatarShare")) - { - LLNotificationsUtil::add("NoAvatarShare", LLSD(), LLSD(), std::string("SwitchToStandardSkinAndQuit")); - return true; - } - - //Make sure we have some parameters - if (params.size() == 0) - { - return false; - } - - //Get the ID - LLUUID id; - if (!id.set( params[0], false )) - { - return false; - } - - //instigate share with this avatar - LLAvatarActions::share( id ); - return true; - } -}; -LLShareWithAvatarHandler gShareWithAvatar; +/** + * @file llshareavatarhandler.cpp + * @brief slapp to handle sharing with an avatar + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llcommandhandler.h" +#include "llavataractions.h" +#include "llnotificationsutil.h" +#include "llui.h" + +class LLShareWithAvatarHandler : public LLCommandHandler +{ +public: + // requires trusted browser to trigger + LLShareWithAvatarHandler() : LLCommandHandler("sharewithavatar", UNTRUSTED_THROTTLE) + { + } + + bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) + { + if (!LLUI::getInstance()->mSettingGroups["config"]->getBOOL("EnableAvatarShare")) + { + LLNotificationsUtil::add("NoAvatarShare", LLSD(), LLSD(), std::string("SwitchToStandardSkinAndQuit")); + return true; + } + + //Make sure we have some parameters + if (params.size() == 0) + { + return false; + } + + //Get the ID + LLUUID id; + if (!id.set( params[0], false )) + { + return false; + } + + //instigate share with this avatar + LLAvatarActions::share( id ); + return true; + } +}; +LLShareWithAvatarHandler gShareWithAvatar; diff --git a/indra/newview/llsidepanelappearance.cpp b/indra/newview/llsidepanelappearance.cpp index 7b842188d5..35d07d1ac8 100644 --- a/indra/newview/llsidepanelappearance.cpp +++ b/indra/newview/llsidepanelappearance.cpp @@ -1,568 +1,568 @@ -/** - * @file llsidepanelappearance.cpp - * @brief Side Bar "Appearance" panel - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llsidepanelappearance.h" - -#include "llaccordionctrltab.h" -#include "llagent.h" -#include "llagentcamera.h" -#include "llagentwearables.h" -#include "llappearancemgr.h" -#include "llfloatersidepanelcontainer.h" -#include "llfolderview.h" -#include "llinventorypanel.h" -#include "llfiltereditor.h" -#include "llfloaterreg.h" -#include "llfloaterworldmap.h" -#include "llfolderviewmodel.h" -#include "lloutfitobserver.h" -#include "llpaneleditwearable.h" -#include "llpaneloutfitsinventory.h" -#include "lltextbox.h" -#include "lluictrlfactory.h" -#include "llviewercontrol.h" -#include "llviewerregion.h" -#include "llvoavatarself.h" -#include "llviewerwearable.h" - -static LLPanelInjector t_appearance("sidepanel_appearance"); - -class LLCurrentlyWornFetchObserver : public LLInventoryFetchItemsObserver -{ -public: - LLCurrentlyWornFetchObserver(const uuid_vec_t &ids, - LLSidepanelAppearance *panel) : - LLInventoryFetchItemsObserver(ids), - mPanel(panel) - {} - ~LLCurrentlyWornFetchObserver() {} - virtual void done() - { - mPanel->inventoryFetched(); - gInventory.removeObserver(this); - delete this; - } -private: - LLSidepanelAppearance *mPanel; -}; - -LLSidepanelAppearance::LLSidepanelAppearance() : - LLPanel(), - mFilterSubString(LLStringUtil::null), - mFilterEditor(NULL), - mOutfitEdit(NULL), - mCurrOutfitPanel(NULL), - mOpened(false) -{ - LLOutfitObserver& outfit_observer = LLOutfitObserver::instance(); - outfit_observer.addBOFReplacedCallback(boost::bind(&LLSidepanelAppearance::refreshCurrentOutfitName, this, "")); - outfit_observer.addBOFChangedCallback(boost::bind(&LLSidepanelAppearance::refreshCurrentOutfitName, this, "")); - outfit_observer.addCOFChangedCallback(boost::bind(&LLSidepanelAppearance::refreshCurrentOutfitName, this, "")); - - gAgentWearables.addLoadingStartedCallback(boost::bind(&LLSidepanelAppearance::setWearablesLoading, this, true)); - gAgentWearables.addLoadedCallback(boost::bind(&LLSidepanelAppearance::setWearablesLoading, this, false)); -} - -LLSidepanelAppearance::~LLSidepanelAppearance() -{ -} - -// virtual -bool LLSidepanelAppearance::postBuild() -{ - mOpenOutfitBtn = getChild("openoutfit_btn"); - mOpenOutfitBtn->setClickedCallback(boost::bind(&LLSidepanelAppearance::onOpenOutfitButtonClicked, this)); - - mEditAppearanceBtn = getChild("editappearance_btn"); - mEditAppearanceBtn->setClickedCallback(boost::bind(&LLSidepanelAppearance::onEditAppearanceButtonClicked, this)); - - childSetAction("edit_outfit_btn", boost::bind(&LLSidepanelAppearance::showOutfitEditPanel, this)); - - mFilterEditor = getChild("Filter"); - if (mFilterEditor) - { - mFilterEditor->setCommitCallback(boost::bind(&LLSidepanelAppearance::onFilterEdit, this, _2)); - } - - mPanelOutfitsInventory = dynamic_cast(getChild("panel_outfits_inventory")); - - mOutfitEdit = dynamic_cast(getChild("panel_outfit_edit")); - if (mOutfitEdit) - { - LLButton* back_btn = mOutfitEdit->getChild("back_btn"); - if (back_btn) - { - back_btn->setClickedCallback(boost::bind(&LLSidepanelAppearance::showOutfitsInventoryPanel, this)); - } - - } - - mEditWearable = dynamic_cast(getChild("panel_edit_wearable")); - if (mEditWearable) - { - LLButton* edit_wearable_back_btn = mEditWearable->getChild("back_btn"); - if (edit_wearable_back_btn) - { - edit_wearable_back_btn->setClickedCallback(boost::bind(&LLSidepanelAppearance::showOutfitEditPanel, this)); - } - } - - mCurrentLookName = getChild("currentlook_name"); - - mOutfitStatus = getChild("currentlook_status"); - - mCurrOutfitPanel = getChild("panel_currentlook"); - - - setVisibleCallback(boost::bind(&LLSidepanelAppearance::onVisibilityChanged,this,_2)); - - setWearablesLoading(gAgentWearables.isCOFChangeInProgress()); - - return true; -} - -// virtual -void LLSidepanelAppearance::onOpen(const LLSD& key) -{ - if (!key.has("type")) - { - // No specific panel requested. - // If we're opened for the first time then show My Outfits. - // Else do nothing. - if (!mOpened) - { - showOutfitsInventoryPanel(); - } - } - else - { - // Switch to the requested panel. - std::string type = key["type"].asString(); - if (type == "my_outfits") - { - showOutfitsInventoryPanel("outfitslist_tab"); - } - else if (type == "now_wearing") - { - showOutfitsInventoryPanel("cof_tab"); - } - else if (type == "edit_outfit") - { - showOutfitEditPanel(); - } - else if (type == "edit_shape") - { - showWearableEditPanel(); - } - } - - mOpened = true; -} - -void LLSidepanelAppearance::onVisibilityChanged(const LLSD &new_visibility) -{ - LLSD visibility; - visibility["visible"] = new_visibility.asBoolean(); - visibility["reset_accordion"] = false; - updateToVisibility(visibility); -} - -void LLSidepanelAppearance::updateToVisibility(const LLSD &new_visibility) -{ - if (new_visibility["visible"].asBoolean()) - { - const bool is_outfit_edit_visible = mOutfitEdit && mOutfitEdit->getVisible(); - const bool is_wearable_edit_visible = mEditWearable && mEditWearable->getVisible(); - - if (is_outfit_edit_visible || is_wearable_edit_visible) - { - const LLViewerWearable *wearable_ptr = mEditWearable ? mEditWearable->getWearable() : NULL; - if (!wearable_ptr) - { - LL_WARNS() << "Visibility change to invalid wearable" << LL_ENDL; - return; - } - // Disable camera switch is currently just for WT_PHYSICS type since we don't want to freeze the avatar - // when editing its physics. - if (!gAgentCamera.cameraCustomizeAvatar()) - { - LLVOAvatarSelf::onCustomizeStart(LLWearableType::getInstance()->getDisableCameraSwitch(wearable_ptr->getType())); - } - if (is_wearable_edit_visible) - { - U32 index; - if (!gAgentWearables.getWearableIndex(wearable_ptr,index)) - { - // we're no longer wearing the wearable we were last editing, switch back to outfit editor - showOutfitEditPanel(); - } - } - - if (is_outfit_edit_visible && new_visibility["reset_accordion"].asBoolean()) - { - mOutfitEdit->resetAccordionState(); - } - } - } - else - { - if (gAgentCamera.cameraCustomizeAvatar() && gSavedSettings.getBOOL("AppearanceCameraMovement")) - { - gAgentCamera.changeCameraToDefault(); - gAgentCamera.resetView(); - } - } -} - -void LLSidepanelAppearance::onFilterEdit(const std::string& search_string) -{ - if (mFilterSubString != search_string) - { - mFilterSubString = search_string; - - // Searches are case-insensitive - // but we don't convert the typed string to upper-case so that it can be fed to the web search as-is. - - mPanelOutfitsInventory->onSearchEdit(mFilterSubString); - } -} - -void LLSidepanelAppearance::onOpenOutfitButtonClicked() -{ - const LLViewerInventoryItem *outfit_link = LLAppearanceMgr::getInstance()->getBaseOutfitLink(); - if (!outfit_link) - return; - if (!outfit_link->getIsLinkType()) - return; - - LLAccordionCtrlTab* tab_outfits = mPanelOutfitsInventory->findChild("tab_outfits"); - if (tab_outfits) - { - tab_outfits->changeOpenClose(false); - LLInventoryPanel *inventory_panel = tab_outfits->findChild("outfitslist_tab"); - if (inventory_panel) - { - LLFolderView* root = inventory_panel->getRootFolder(); - LLFolderViewItem *outfit_folder = inventory_panel->getItemByID(outfit_link->getLinkedUUID()); - if (outfit_folder) - { - outfit_folder->setOpen(!outfit_folder->isOpen()); - root->setSelection(outfit_folder,true); - root->scrollToShowSelection(); - } - } - } -} - -// *TODO: obsolete? -void LLSidepanelAppearance::onEditAppearanceButtonClicked() -{ - if (gAgentWearables.areWearablesLoaded()) - { - LLVOAvatarSelf::onCustomizeStart(); - } -} - -void LLSidepanelAppearance::showOutfitsInventoryPanel() -{ - toggleWearableEditPanel(false); - toggleOutfitEditPanel(false); - toggleMyOutfitsPanel(true, ""); -} - -void LLSidepanelAppearance::showOutfitsInventoryPanel(const std::string &tab_name) -{ - toggleWearableEditPanel(false); - toggleOutfitEditPanel(false); - toggleMyOutfitsPanel(true, tab_name); -} - -void LLSidepanelAppearance::showOutfitEditPanel() -{ - if (mOutfitEdit && mOutfitEdit->getVisible()) return; - - // Accordion's state must be reset in all cases except the one when user - // is returning back to the mOutfitEdit panel from the mEditWearable panel. - // The simplest way to control this is to check the visibility state of the mEditWearable - // BEFORE it is changed by the call to the toggleWearableEditPanel(false, NULL, true). - if (mEditWearable != NULL && !mEditWearable->getVisible() && mOutfitEdit != NULL) - { - mOutfitEdit->resetAccordionState(); - } - - // If we're exiting the edit wearable view, and the camera was not focused on the avatar - // (e.g. such as if we were editing a physics param), then skip the outfits edit mode since - // otherwise this would trigger the camera focus mode. - if (mEditWearable != NULL && mEditWearable->getVisible() && !gAgentCamera.cameraCustomizeAvatar()) - { - showOutfitsInventoryPanel(); - return; - } - - toggleMyOutfitsPanel(false, ""); - toggleWearableEditPanel(false, NULL, true); // don't switch out of edit appearance mode - toggleOutfitEditPanel(true); -} - -void LLSidepanelAppearance::showWearableEditPanel(LLViewerWearable *wearable /* = NULL*/, bool disable_camera_switch) -{ - toggleMyOutfitsPanel(false, ""); - toggleOutfitEditPanel(false, true); // don't switch out of edit appearance mode - toggleWearableEditPanel(true, wearable, disable_camera_switch); -} - -void LLSidepanelAppearance::toggleMyOutfitsPanel(bool visible, const std::string& tab_name) -{ - if (!mPanelOutfitsInventory - || (mPanelOutfitsInventory->getVisible() == visible && tab_name.empty())) - { - // visibility isn't changing, hence nothing to do - return; - } - - mPanelOutfitsInventory->setVisible(visible); - - // *TODO: Move these controls to panel_outfits_inventory.xml - // so that we don't need to toggle them explicitly. - mFilterEditor->setVisible(visible); - mCurrOutfitPanel->setVisible(visible); - - if (visible) - { - mPanelOutfitsInventory->onOpen(LLSD()); - if (!tab_name.empty()) - { - mPanelOutfitsInventory->openApearanceTab(tab_name); - } - } -} - -bool LLSidepanelAppearance::isCOFPanelVisible() -{ - if (mPanelOutfitsInventory && mPanelOutfitsInventory->getVisible()) - { - return mPanelOutfitsInventory->isCOFPanelActive(); - } - return false; -} - -void LLSidepanelAppearance::toggleOutfitEditPanel(bool visible, bool disable_camera_switch) -{ - if (!mOutfitEdit || mOutfitEdit->getVisible() == visible) - { - // visibility isn't changing, hence nothing to do - return; - } - - mOutfitEdit->setVisible(visible); - - if (visible) - { - mOutfitEdit->onOpen(LLSD()); - LLVOAvatarSelf::onCustomizeStart(disable_camera_switch); - } - else - { - if (!disable_camera_switch) // if we're just switching between outfit and wearable editing, don't end customization. - { - LLVOAvatarSelf::onCustomizeEnd(disable_camera_switch); - LLAppearanceMgr::getInstance()->updateIsDirty(); - } - } -} - -void LLSidepanelAppearance::toggleWearableEditPanel(bool visible, LLViewerWearable *wearable, bool disable_camera_switch) -{ - if (!mEditWearable) - { - return; - } - - if (mEditWearable->getVisible() == visible && (!visible || mEditWearable->getWearable() == wearable)) - { - // visibility isn't changing and panel doesn't need an update, hence nothing to do - return; - } - - // If we're just switching between outfit and wearable editing or updating item, - // don't end customization and don't switch camera - // Don't end customization and don't switch camera without visibility change - bool change_state = !disable_camera_switch && mEditWearable->getVisible() != visible; - - if (!wearable) - { - wearable = gAgentWearables.getViewerWearable(LLWearableType::WT_SHAPE, 0); - } - if (!wearable) - { - return; - } - - // Toggle panel visibility. - mEditWearable->setVisible(visible); - - if (visible) - { - LLVOAvatarSelf::onCustomizeStart(!change_state); - mEditWearable->setWearable(wearable, !change_state); - mEditWearable->onOpen(LLSD()); // currently no-op, just for consistency - } - else - { - // Save changes if closing. - mEditWearable->saveChanges(); - mEditWearable->setWearable(NULL); - LLAppearanceMgr::getInstance()->updateIsDirty(); - if (change_state) - { - LLVOAvatarSelf::onCustomizeEnd(!change_state); - } - } -} - -void LLSidepanelAppearance::refreshCurrentOutfitName(const std::string& name) -{ - // Set current outfit status (wearing/unsaved). - bool dirty = LLAppearanceMgr::getInstance()->isOutfitDirty(); - std::string cof_status_str = getString(dirty ? "Unsaved Changes" : "Now Wearing"); - mOutfitStatus->setText(cof_status_str); - - if (name == "") - { - std::string outfit_name; - if (LLAppearanceMgr::getInstance()->getBaseOutfitName(outfit_name)) - { - mCurrentLookName->setText(outfit_name); - return; - } - - std::string string_name = gAgentWearables.isCOFChangeInProgress() ? "Changing outfits" : "No Outfit"; - mCurrentLookName->setText(getString(string_name)); - mOpenOutfitBtn->setEnabled(false); - } - else - { - mCurrentLookName->setText(name); - // Can't just call update verbs since the folder link may not have been created yet. - mOpenOutfitBtn->setEnabled(true); - } -} - -//static -void LLSidepanelAppearance::editWearable(LLViewerWearable *wearable, LLView *data, bool disable_camera_switch) -{ - LLFloaterSidePanelContainer::showPanel("appearance", LLSD()); - LLSidepanelAppearance *panel = dynamic_cast(data); - if (panel) - { - panel->showWearableEditPanel(wearable, disable_camera_switch); - } -} - -// Fetch currently worn items and only enable the New Look button after everything's been -// fetched. Alternatively, we could stuff this logic into llagentwearables::makeNewOutfitLinks. -void LLSidepanelAppearance::fetchInventory() -{ - uuid_vec_t ids; - LLUUID item_id; - for(S32 type = (S32)LLWearableType::WT_SHAPE; type < (S32)LLWearableType::WT_COUNT; ++type) - { - for (U32 index = 0; index < gAgentWearables.getWearableCount((LLWearableType::EType)type); ++index) - { - item_id = gAgentWearables.getWearableItemID((LLWearableType::EType)type, index); - if(item_id.notNull()) - { - ids.push_back(item_id); - } - } - } - - if (isAgentAvatarValid()) - { - for (LLVOAvatar::attachment_map_t::const_iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); - iter != gAgentAvatarp->mAttachmentPoints.end(); ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - if (!attachment) continue; - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - LLViewerObject* attached_object = attachment_iter->get(); - if (!attached_object) continue; - const LLUUID& item_id = attached_object->getAttachmentItemID(); - if (item_id.isNull()) continue; - ids.push_back(item_id); - } - } - } - - LLCurrentlyWornFetchObserver *fetch_worn = new LLCurrentlyWornFetchObserver(ids, this); - fetch_worn->startFetch(); - // If no items to be fetched, done will never be triggered. - // TODO: Change LLInventoryFetchItemsObserver::fetchItems to trigger done() on this condition. - if (fetch_worn->isFinished()) - { - fetch_worn->done(); - } - else - { - gInventory.addObserver(fetch_worn); - } -} - -void LLSidepanelAppearance::inventoryFetched() -{ -} - -void LLSidepanelAppearance::setWearablesLoading(bool val) -{ - getChildView("wearables_loading_indicator")->setVisible( val); - getChildView("edit_outfit_btn")->setVisible( !val); - - if (!val) - { - // refresh outfit name when COF is already changed. - refreshCurrentOutfitName(); - } -} - -void LLSidepanelAppearance::showDefaultSubpart() -{ - if (mEditWearable->getVisible()) - { - mEditWearable->showDefaultSubpart(); - } -} - -void LLSidepanelAppearance::updateScrollingPanelList() -{ - if (mEditWearable->getVisible()) - { - mEditWearable->updateScrollingPanelList(); - } -} +/** + * @file llsidepanelappearance.cpp + * @brief Side Bar "Appearance" panel + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llsidepanelappearance.h" + +#include "llaccordionctrltab.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llagentwearables.h" +#include "llappearancemgr.h" +#include "llfloatersidepanelcontainer.h" +#include "llfolderview.h" +#include "llinventorypanel.h" +#include "llfiltereditor.h" +#include "llfloaterreg.h" +#include "llfloaterworldmap.h" +#include "llfolderviewmodel.h" +#include "lloutfitobserver.h" +#include "llpaneleditwearable.h" +#include "llpaneloutfitsinventory.h" +#include "lltextbox.h" +#include "lluictrlfactory.h" +#include "llviewercontrol.h" +#include "llviewerregion.h" +#include "llvoavatarself.h" +#include "llviewerwearable.h" + +static LLPanelInjector t_appearance("sidepanel_appearance"); + +class LLCurrentlyWornFetchObserver : public LLInventoryFetchItemsObserver +{ +public: + LLCurrentlyWornFetchObserver(const uuid_vec_t &ids, + LLSidepanelAppearance *panel) : + LLInventoryFetchItemsObserver(ids), + mPanel(panel) + {} + ~LLCurrentlyWornFetchObserver() {} + virtual void done() + { + mPanel->inventoryFetched(); + gInventory.removeObserver(this); + delete this; + } +private: + LLSidepanelAppearance *mPanel; +}; + +LLSidepanelAppearance::LLSidepanelAppearance() : + LLPanel(), + mFilterSubString(LLStringUtil::null), + mFilterEditor(NULL), + mOutfitEdit(NULL), + mCurrOutfitPanel(NULL), + mOpened(false) +{ + LLOutfitObserver& outfit_observer = LLOutfitObserver::instance(); + outfit_observer.addBOFReplacedCallback(boost::bind(&LLSidepanelAppearance::refreshCurrentOutfitName, this, "")); + outfit_observer.addBOFChangedCallback(boost::bind(&LLSidepanelAppearance::refreshCurrentOutfitName, this, "")); + outfit_observer.addCOFChangedCallback(boost::bind(&LLSidepanelAppearance::refreshCurrentOutfitName, this, "")); + + gAgentWearables.addLoadingStartedCallback(boost::bind(&LLSidepanelAppearance::setWearablesLoading, this, true)); + gAgentWearables.addLoadedCallback(boost::bind(&LLSidepanelAppearance::setWearablesLoading, this, false)); +} + +LLSidepanelAppearance::~LLSidepanelAppearance() +{ +} + +// virtual +bool LLSidepanelAppearance::postBuild() +{ + mOpenOutfitBtn = getChild("openoutfit_btn"); + mOpenOutfitBtn->setClickedCallback(boost::bind(&LLSidepanelAppearance::onOpenOutfitButtonClicked, this)); + + mEditAppearanceBtn = getChild("editappearance_btn"); + mEditAppearanceBtn->setClickedCallback(boost::bind(&LLSidepanelAppearance::onEditAppearanceButtonClicked, this)); + + childSetAction("edit_outfit_btn", boost::bind(&LLSidepanelAppearance::showOutfitEditPanel, this)); + + mFilterEditor = getChild("Filter"); + if (mFilterEditor) + { + mFilterEditor->setCommitCallback(boost::bind(&LLSidepanelAppearance::onFilterEdit, this, _2)); + } + + mPanelOutfitsInventory = dynamic_cast(getChild("panel_outfits_inventory")); + + mOutfitEdit = dynamic_cast(getChild("panel_outfit_edit")); + if (mOutfitEdit) + { + LLButton* back_btn = mOutfitEdit->getChild("back_btn"); + if (back_btn) + { + back_btn->setClickedCallback(boost::bind(&LLSidepanelAppearance::showOutfitsInventoryPanel, this)); + } + + } + + mEditWearable = dynamic_cast(getChild("panel_edit_wearable")); + if (mEditWearable) + { + LLButton* edit_wearable_back_btn = mEditWearable->getChild("back_btn"); + if (edit_wearable_back_btn) + { + edit_wearable_back_btn->setClickedCallback(boost::bind(&LLSidepanelAppearance::showOutfitEditPanel, this)); + } + } + + mCurrentLookName = getChild("currentlook_name"); + + mOutfitStatus = getChild("currentlook_status"); + + mCurrOutfitPanel = getChild("panel_currentlook"); + + + setVisibleCallback(boost::bind(&LLSidepanelAppearance::onVisibilityChanged,this,_2)); + + setWearablesLoading(gAgentWearables.isCOFChangeInProgress()); + + return true; +} + +// virtual +void LLSidepanelAppearance::onOpen(const LLSD& key) +{ + if (!key.has("type")) + { + // No specific panel requested. + // If we're opened for the first time then show My Outfits. + // Else do nothing. + if (!mOpened) + { + showOutfitsInventoryPanel(); + } + } + else + { + // Switch to the requested panel. + std::string type = key["type"].asString(); + if (type == "my_outfits") + { + showOutfitsInventoryPanel("outfitslist_tab"); + } + else if (type == "now_wearing") + { + showOutfitsInventoryPanel("cof_tab"); + } + else if (type == "edit_outfit") + { + showOutfitEditPanel(); + } + else if (type == "edit_shape") + { + showWearableEditPanel(); + } + } + + mOpened = true; +} + +void LLSidepanelAppearance::onVisibilityChanged(const LLSD &new_visibility) +{ + LLSD visibility; + visibility["visible"] = new_visibility.asBoolean(); + visibility["reset_accordion"] = false; + updateToVisibility(visibility); +} + +void LLSidepanelAppearance::updateToVisibility(const LLSD &new_visibility) +{ + if (new_visibility["visible"].asBoolean()) + { + const bool is_outfit_edit_visible = mOutfitEdit && mOutfitEdit->getVisible(); + const bool is_wearable_edit_visible = mEditWearable && mEditWearable->getVisible(); + + if (is_outfit_edit_visible || is_wearable_edit_visible) + { + const LLViewerWearable *wearable_ptr = mEditWearable ? mEditWearable->getWearable() : NULL; + if (!wearable_ptr) + { + LL_WARNS() << "Visibility change to invalid wearable" << LL_ENDL; + return; + } + // Disable camera switch is currently just for WT_PHYSICS type since we don't want to freeze the avatar + // when editing its physics. + if (!gAgentCamera.cameraCustomizeAvatar()) + { + LLVOAvatarSelf::onCustomizeStart(LLWearableType::getInstance()->getDisableCameraSwitch(wearable_ptr->getType())); + } + if (is_wearable_edit_visible) + { + U32 index; + if (!gAgentWearables.getWearableIndex(wearable_ptr,index)) + { + // we're no longer wearing the wearable we were last editing, switch back to outfit editor + showOutfitEditPanel(); + } + } + + if (is_outfit_edit_visible && new_visibility["reset_accordion"].asBoolean()) + { + mOutfitEdit->resetAccordionState(); + } + } + } + else + { + if (gAgentCamera.cameraCustomizeAvatar() && gSavedSettings.getBOOL("AppearanceCameraMovement")) + { + gAgentCamera.changeCameraToDefault(); + gAgentCamera.resetView(); + } + } +} + +void LLSidepanelAppearance::onFilterEdit(const std::string& search_string) +{ + if (mFilterSubString != search_string) + { + mFilterSubString = search_string; + + // Searches are case-insensitive + // but we don't convert the typed string to upper-case so that it can be fed to the web search as-is. + + mPanelOutfitsInventory->onSearchEdit(mFilterSubString); + } +} + +void LLSidepanelAppearance::onOpenOutfitButtonClicked() +{ + const LLViewerInventoryItem *outfit_link = LLAppearanceMgr::getInstance()->getBaseOutfitLink(); + if (!outfit_link) + return; + if (!outfit_link->getIsLinkType()) + return; + + LLAccordionCtrlTab* tab_outfits = mPanelOutfitsInventory->findChild("tab_outfits"); + if (tab_outfits) + { + tab_outfits->changeOpenClose(false); + LLInventoryPanel *inventory_panel = tab_outfits->findChild("outfitslist_tab"); + if (inventory_panel) + { + LLFolderView* root = inventory_panel->getRootFolder(); + LLFolderViewItem *outfit_folder = inventory_panel->getItemByID(outfit_link->getLinkedUUID()); + if (outfit_folder) + { + outfit_folder->setOpen(!outfit_folder->isOpen()); + root->setSelection(outfit_folder,true); + root->scrollToShowSelection(); + } + } + } +} + +// *TODO: obsolete? +void LLSidepanelAppearance::onEditAppearanceButtonClicked() +{ + if (gAgentWearables.areWearablesLoaded()) + { + LLVOAvatarSelf::onCustomizeStart(); + } +} + +void LLSidepanelAppearance::showOutfitsInventoryPanel() +{ + toggleWearableEditPanel(false); + toggleOutfitEditPanel(false); + toggleMyOutfitsPanel(true, ""); +} + +void LLSidepanelAppearance::showOutfitsInventoryPanel(const std::string &tab_name) +{ + toggleWearableEditPanel(false); + toggleOutfitEditPanel(false); + toggleMyOutfitsPanel(true, tab_name); +} + +void LLSidepanelAppearance::showOutfitEditPanel() +{ + if (mOutfitEdit && mOutfitEdit->getVisible()) return; + + // Accordion's state must be reset in all cases except the one when user + // is returning back to the mOutfitEdit panel from the mEditWearable panel. + // The simplest way to control this is to check the visibility state of the mEditWearable + // BEFORE it is changed by the call to the toggleWearableEditPanel(false, NULL, true). + if (mEditWearable != NULL && !mEditWearable->getVisible() && mOutfitEdit != NULL) + { + mOutfitEdit->resetAccordionState(); + } + + // If we're exiting the edit wearable view, and the camera was not focused on the avatar + // (e.g. such as if we were editing a physics param), then skip the outfits edit mode since + // otherwise this would trigger the camera focus mode. + if (mEditWearable != NULL && mEditWearable->getVisible() && !gAgentCamera.cameraCustomizeAvatar()) + { + showOutfitsInventoryPanel(); + return; + } + + toggleMyOutfitsPanel(false, ""); + toggleWearableEditPanel(false, NULL, true); // don't switch out of edit appearance mode + toggleOutfitEditPanel(true); +} + +void LLSidepanelAppearance::showWearableEditPanel(LLViewerWearable *wearable /* = NULL*/, bool disable_camera_switch) +{ + toggleMyOutfitsPanel(false, ""); + toggleOutfitEditPanel(false, true); // don't switch out of edit appearance mode + toggleWearableEditPanel(true, wearable, disable_camera_switch); +} + +void LLSidepanelAppearance::toggleMyOutfitsPanel(bool visible, const std::string& tab_name) +{ + if (!mPanelOutfitsInventory + || (mPanelOutfitsInventory->getVisible() == visible && tab_name.empty())) + { + // visibility isn't changing, hence nothing to do + return; + } + + mPanelOutfitsInventory->setVisible(visible); + + // *TODO: Move these controls to panel_outfits_inventory.xml + // so that we don't need to toggle them explicitly. + mFilterEditor->setVisible(visible); + mCurrOutfitPanel->setVisible(visible); + + if (visible) + { + mPanelOutfitsInventory->onOpen(LLSD()); + if (!tab_name.empty()) + { + mPanelOutfitsInventory->openApearanceTab(tab_name); + } + } +} + +bool LLSidepanelAppearance::isCOFPanelVisible() +{ + if (mPanelOutfitsInventory && mPanelOutfitsInventory->getVisible()) + { + return mPanelOutfitsInventory->isCOFPanelActive(); + } + return false; +} + +void LLSidepanelAppearance::toggleOutfitEditPanel(bool visible, bool disable_camera_switch) +{ + if (!mOutfitEdit || mOutfitEdit->getVisible() == visible) + { + // visibility isn't changing, hence nothing to do + return; + } + + mOutfitEdit->setVisible(visible); + + if (visible) + { + mOutfitEdit->onOpen(LLSD()); + LLVOAvatarSelf::onCustomizeStart(disable_camera_switch); + } + else + { + if (!disable_camera_switch) // if we're just switching between outfit and wearable editing, don't end customization. + { + LLVOAvatarSelf::onCustomizeEnd(disable_camera_switch); + LLAppearanceMgr::getInstance()->updateIsDirty(); + } + } +} + +void LLSidepanelAppearance::toggleWearableEditPanel(bool visible, LLViewerWearable *wearable, bool disable_camera_switch) +{ + if (!mEditWearable) + { + return; + } + + if (mEditWearable->getVisible() == visible && (!visible || mEditWearable->getWearable() == wearable)) + { + // visibility isn't changing and panel doesn't need an update, hence nothing to do + return; + } + + // If we're just switching between outfit and wearable editing or updating item, + // don't end customization and don't switch camera + // Don't end customization and don't switch camera without visibility change + bool change_state = !disable_camera_switch && mEditWearable->getVisible() != visible; + + if (!wearable) + { + wearable = gAgentWearables.getViewerWearable(LLWearableType::WT_SHAPE, 0); + } + if (!wearable) + { + return; + } + + // Toggle panel visibility. + mEditWearable->setVisible(visible); + + if (visible) + { + LLVOAvatarSelf::onCustomizeStart(!change_state); + mEditWearable->setWearable(wearable, !change_state); + mEditWearable->onOpen(LLSD()); // currently no-op, just for consistency + } + else + { + // Save changes if closing. + mEditWearable->saveChanges(); + mEditWearable->setWearable(NULL); + LLAppearanceMgr::getInstance()->updateIsDirty(); + if (change_state) + { + LLVOAvatarSelf::onCustomizeEnd(!change_state); + } + } +} + +void LLSidepanelAppearance::refreshCurrentOutfitName(const std::string& name) +{ + // Set current outfit status (wearing/unsaved). + bool dirty = LLAppearanceMgr::getInstance()->isOutfitDirty(); + std::string cof_status_str = getString(dirty ? "Unsaved Changes" : "Now Wearing"); + mOutfitStatus->setText(cof_status_str); + + if (name == "") + { + std::string outfit_name; + if (LLAppearanceMgr::getInstance()->getBaseOutfitName(outfit_name)) + { + mCurrentLookName->setText(outfit_name); + return; + } + + std::string string_name = gAgentWearables.isCOFChangeInProgress() ? "Changing outfits" : "No Outfit"; + mCurrentLookName->setText(getString(string_name)); + mOpenOutfitBtn->setEnabled(false); + } + else + { + mCurrentLookName->setText(name); + // Can't just call update verbs since the folder link may not have been created yet. + mOpenOutfitBtn->setEnabled(true); + } +} + +//static +void LLSidepanelAppearance::editWearable(LLViewerWearable *wearable, LLView *data, bool disable_camera_switch) +{ + LLFloaterSidePanelContainer::showPanel("appearance", LLSD()); + LLSidepanelAppearance *panel = dynamic_cast(data); + if (panel) + { + panel->showWearableEditPanel(wearable, disable_camera_switch); + } +} + +// Fetch currently worn items and only enable the New Look button after everything's been +// fetched. Alternatively, we could stuff this logic into llagentwearables::makeNewOutfitLinks. +void LLSidepanelAppearance::fetchInventory() +{ + uuid_vec_t ids; + LLUUID item_id; + for(S32 type = (S32)LLWearableType::WT_SHAPE; type < (S32)LLWearableType::WT_COUNT; ++type) + { + for (U32 index = 0; index < gAgentWearables.getWearableCount((LLWearableType::EType)type); ++index) + { + item_id = gAgentWearables.getWearableItemID((LLWearableType::EType)type, index); + if(item_id.notNull()) + { + ids.push_back(item_id); + } + } + } + + if (isAgentAvatarValid()) + { + for (LLVOAvatar::attachment_map_t::const_iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); + iter != gAgentAvatarp->mAttachmentPoints.end(); ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + if (!attachment) continue; + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + LLViewerObject* attached_object = attachment_iter->get(); + if (!attached_object) continue; + const LLUUID& item_id = attached_object->getAttachmentItemID(); + if (item_id.isNull()) continue; + ids.push_back(item_id); + } + } + } + + LLCurrentlyWornFetchObserver *fetch_worn = new LLCurrentlyWornFetchObserver(ids, this); + fetch_worn->startFetch(); + // If no items to be fetched, done will never be triggered. + // TODO: Change LLInventoryFetchItemsObserver::fetchItems to trigger done() on this condition. + if (fetch_worn->isFinished()) + { + fetch_worn->done(); + } + else + { + gInventory.addObserver(fetch_worn); + } +} + +void LLSidepanelAppearance::inventoryFetched() +{ +} + +void LLSidepanelAppearance::setWearablesLoading(bool val) +{ + getChildView("wearables_loading_indicator")->setVisible( val); + getChildView("edit_outfit_btn")->setVisible( !val); + + if (!val) + { + // refresh outfit name when COF is already changed. + refreshCurrentOutfitName(); + } +} + +void LLSidepanelAppearance::showDefaultSubpart() +{ + if (mEditWearable->getVisible()) + { + mEditWearable->showDefaultSubpart(); + } +} + +void LLSidepanelAppearance::updateScrollingPanelList() +{ + if (mEditWearable->getVisible()) + { + mEditWearable->updateScrollingPanelList(); + } +} diff --git a/indra/newview/llsidepanelappearance.h b/indra/newview/llsidepanelappearance.h index fee4e2a5fd..f3d34a857c 100644 --- a/indra/newview/llsidepanelappearance.h +++ b/indra/newview/llsidepanelappearance.h @@ -1,102 +1,102 @@ -/** - * @file llsidepanelappearance.h - * @brief Side Bar "Appearance" panel - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLSIDEPANELAPPEARANCE_H -#define LL_LLSIDEPANELAPPEARANCE_H - -#include "llpanel.h" -#include "llinventoryobserver.h" - -#include "llinventory.h" -#include "llpaneloutfitedit.h" - -class LLFilterEditor; -class LLCurrentlyWornFetchObserver; -class LLPanelEditWearable; -class LLViewerWearable; -class LLPanelOutfitsInventory; - -class LLSidepanelAppearance : public LLPanel -{ - LOG_CLASS(LLSidepanelAppearance); -public: - LLSidepanelAppearance(); - virtual ~LLSidepanelAppearance(); - - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); - - void refreshCurrentOutfitName(const std::string& name = ""); - - static void editWearable(LLViewerWearable *wearable, LLView *data, bool disable_camera_switch = false); - - void fetchInventory(); - void inventoryFetched(); - - void showOutfitsInventoryPanel(); // last selected - void showOutfitsInventoryPanel(const std::string& tab_name); - void showOutfitEditPanel(); - void showWearableEditPanel(LLViewerWearable *wearable = NULL, bool disable_camera_switch = false); - void setWearablesLoading(bool val); - void showDefaultSubpart(); - void updateScrollingPanelList(); - void updateToVisibility( const LLSD& new_visibility ); - LLPanelEditWearable* getWearable(){ return mEditWearable; } - - bool isCOFPanelVisible(); - -private: - void onFilterEdit(const std::string& search_string); - void onVisibilityChanged ( const LLSD& new_visibility ); - - void onOpenOutfitButtonClicked(); - void onEditAppearanceButtonClicked(); - - void toggleMyOutfitsPanel(bool visible, const std::string& tab_name); - void toggleOutfitEditPanel(bool visible, bool disable_camera_switch = false); - void toggleWearableEditPanel(bool visible, LLViewerWearable* wearable = nullptr, bool disable_camera_switch = false); - - LLFilterEditor* mFilterEditor; - LLPanelOutfitsInventory* mPanelOutfitsInventory; - LLPanelOutfitEdit* mOutfitEdit; - LLPanelEditWearable* mEditWearable; - - LLButton* mOpenOutfitBtn; - LLButton* mEditAppearanceBtn; - LLPanel* mCurrOutfitPanel; - - LLTextBox* mCurrentLookName; - LLTextBox* mOutfitStatus; - - // Search string for filtering landmarks and teleport - // history locations - std::string mFilterSubString; - - // Gets set to true when we're opened for the first time. - bool mOpened; -}; - -#endif //LL_LLSIDEPANELAPPEARANCE_H +/** + * @file llsidepanelappearance.h + * @brief Side Bar "Appearance" panel + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSIDEPANELAPPEARANCE_H +#define LL_LLSIDEPANELAPPEARANCE_H + +#include "llpanel.h" +#include "llinventoryobserver.h" + +#include "llinventory.h" +#include "llpaneloutfitedit.h" + +class LLFilterEditor; +class LLCurrentlyWornFetchObserver; +class LLPanelEditWearable; +class LLViewerWearable; +class LLPanelOutfitsInventory; + +class LLSidepanelAppearance : public LLPanel +{ + LOG_CLASS(LLSidepanelAppearance); +public: + LLSidepanelAppearance(); + virtual ~LLSidepanelAppearance(); + + /*virtual*/ bool postBuild(); + /*virtual*/ void onOpen(const LLSD& key); + + void refreshCurrentOutfitName(const std::string& name = ""); + + static void editWearable(LLViewerWearable *wearable, LLView *data, bool disable_camera_switch = false); + + void fetchInventory(); + void inventoryFetched(); + + void showOutfitsInventoryPanel(); // last selected + void showOutfitsInventoryPanel(const std::string& tab_name); + void showOutfitEditPanel(); + void showWearableEditPanel(LLViewerWearable *wearable = NULL, bool disable_camera_switch = false); + void setWearablesLoading(bool val); + void showDefaultSubpart(); + void updateScrollingPanelList(); + void updateToVisibility( const LLSD& new_visibility ); + LLPanelEditWearable* getWearable(){ return mEditWearable; } + + bool isCOFPanelVisible(); + +private: + void onFilterEdit(const std::string& search_string); + void onVisibilityChanged ( const LLSD& new_visibility ); + + void onOpenOutfitButtonClicked(); + void onEditAppearanceButtonClicked(); + + void toggleMyOutfitsPanel(bool visible, const std::string& tab_name); + void toggleOutfitEditPanel(bool visible, bool disable_camera_switch = false); + void toggleWearableEditPanel(bool visible, LLViewerWearable* wearable = nullptr, bool disable_camera_switch = false); + + LLFilterEditor* mFilterEditor; + LLPanelOutfitsInventory* mPanelOutfitsInventory; + LLPanelOutfitEdit* mOutfitEdit; + LLPanelEditWearable* mEditWearable; + + LLButton* mOpenOutfitBtn; + LLButton* mEditAppearanceBtn; + LLPanel* mCurrOutfitPanel; + + LLTextBox* mCurrentLookName; + LLTextBox* mOutfitStatus; + + // Search string for filtering landmarks and teleport + // history locations + std::string mFilterSubString; + + // Gets set to true when we're opened for the first time. + bool mOpened; +}; + +#endif //LL_LLSIDEPANELAPPEARANCE_H diff --git a/indra/newview/llsidepanelinventory.cpp b/indra/newview/llsidepanelinventory.cpp index 021cdd7945..c07d4b7e56 100644 --- a/indra/newview/llsidepanelinventory.cpp +++ b/indra/newview/llsidepanelinventory.cpp @@ -1,595 +1,595 @@ -/** - * @file LLSidepanelInventory.cpp - * @brief Side Bar "Inventory" panel - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llsidepanelinventory.h" - -#include "llagent.h" -#include "llappearancemgr.h" -#include "llappviewer.h" -#include "llavataractions.h" -#include "llbutton.h" -#include "lldate.h" -#include "llfirstuse.h" -#include "llfloaterreg.h" -#include "llfloatersidepanelcontainer.h" -#include "llfoldertype.h" -#include "llfolderview.h" -#include "llinventorybridge.h" -#include "llinventoryfunctions.h" -#include "llinventorymodel.h" -#include "llinventorymodelbackgroundfetch.h" -#include "llinventoryobserver.h" -#include "llinventorypanel.h" -#include "lllayoutstack.h" -#include "lloutfitobserver.h" -#include "llpanelmaininventory.h" -#include "llpanelmarketplaceinbox.h" -#include "llselectmgr.h" -#include "llsidepaneliteminfo.h" -#include "llsidepaneltaskinfo.h" -#include "llstring.h" -#include "lltabcontainer.h" -#include "lltextbox.h" -#include "lltrans.h" -#include "llviewermedia.h" -#include "llviewernetwork.h" -#include "llweb.h" - -static LLPanelInjector t_inventory("sidepanel_inventory"); - -// -// Constants -// - -// No longer want the inbox panel to auto-expand since it creates issues with the "new" tag time stamp -#define AUTO_EXPAND_INBOX 0 - -static const char * const INBOX_BUTTON_NAME = "inbox_btn"; -static const char * const INBOX_LAYOUT_PANEL_NAME = "inbox_layout_panel"; -static const char * const INVENTORY_LAYOUT_STACK_NAME = "inventory_layout_stack"; -static const char * const MARKETPLACE_INBOX_PANEL = "marketplace_inbox"; - -static bool sLoginCompleted = false; - -// -// Helpers -// -class LLInboxAddedObserver : public LLInventoryCategoryAddedObserver -{ -public: - LLInboxAddedObserver(LLSidepanelInventory * sidepanelInventory) - : LLInventoryCategoryAddedObserver() - , mSidepanelInventory(sidepanelInventory) - { - } - - void done() - { - for (cat_vec_t::iterator it = mAddedCategories.begin(); it != mAddedCategories.end(); ++it) - { - LLViewerInventoryCategory* added_category = *it; - - LLFolderType::EType added_category_type = added_category->getPreferredType(); - - switch (added_category_type) - { - case LLFolderType::FT_INBOX: - mSidepanelInventory->enableInbox(true); - mSidepanelInventory->observeInboxModifications(added_category->getUUID()); - break; - default: - break; - } - } - } - -private: - LLSidepanelInventory * mSidepanelInventory; -}; - -// -// Implementation -// - -LLSidepanelInventory::LLSidepanelInventory() - : LLPanel() - , mPanelMainInventory(NULL) - , mInboxEnabled(false) - , mCategoriesObserver(NULL) - , mInboxAddedObserver(NULL) - , mInboxLayoutPanel(NULL) -{ - //buildFromFile( "panel_inventory.xml"); // Called from LLRegisterPanelClass::defaultPanelClassBuilder() -} - -LLSidepanelInventory::~LLSidepanelInventory() -{ - // Save the InventoryMainPanelHeight in settings per account - gSavedPerAccountSettings.setS32("InventoryInboxHeight", mInboxLayoutPanel->getTargetDim()); - - if (mCategoriesObserver && gInventory.containsObserver(mCategoriesObserver)) - { - gInventory.removeObserver(mCategoriesObserver); - } - delete mCategoriesObserver; - - if (mInboxAddedObserver && gInventory.containsObserver(mInboxAddedObserver)) - { - gInventory.removeObserver(mInboxAddedObserver); - } - delete mInboxAddedObserver; -} - -void handleInventoryDisplayInboxChanged() -{ - LLSidepanelInventory* sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); - if (sidepanel_inventory) - { - sidepanel_inventory->enableInbox(gSavedSettings.getBOOL("InventoryDisplayInbox")); - } -} - -bool LLSidepanelInventory::postBuild() -{ - // UI elements from inventory panel - { - mInventoryPanel = getChild("sidepanel_inventory_panel"); - - mPanelMainInventory = mInventoryPanel->getChild("panel_main_inventory"); - mPanelMainInventory->setSelectCallback(boost::bind(&LLSidepanelInventory::onSelectionChange, this, _1, _2)); - //LLTabContainer* tabs = mPanelMainInventory->getChild("inventory filter tabs"); - //tabs->setCommitCallback(boost::bind(&LLSidepanelInventory::updateVerbs, this)); - - /* - EXT-4846 : "Can we suppress the "Landmarks" and "My Favorites" folder since they have their own Task Panel?" - Deferring this until 2.1. - LLInventoryPanel *my_inventory_panel = mPanelMainInventory->getChild("All Items"); - my_inventory_panel->addHideFolderType(LLFolderType::FT_LANDMARK); - my_inventory_panel->addHideFolderType(LLFolderType::FT_FAVORITE); - */ - - //LLOutfitObserver::instance().addCOFChangedCallback(boost::bind(&LLSidepanelInventory::updateVerbs, this)); - } - - // Received items inbox setup - { - LLLayoutStack* inv_stack = getChild(INVENTORY_LAYOUT_STACK_NAME); - - // Set up button states and callbacks - LLButton * inbox_button = getChild(INBOX_BUTTON_NAME); - - inbox_button->setCommitCallback(boost::bind(&LLSidepanelInventory::onToggleInboxBtn, this)); - - // For main Inventory floater: Get the previous inbox state from "InventoryInboxToggleState" setting. - // For additional Inventory floaters: Collapsed state is default. - bool is_inbox_collapsed = !inbox_button->getToggleState() || sLoginCompleted; - - // Restore the collapsed inbox panel state - mInboxLayoutPanel = getChild(INBOX_LAYOUT_PANEL_NAME); - inv_stack->collapsePanel(mInboxLayoutPanel, is_inbox_collapsed); - if (!is_inbox_collapsed) - { - mInboxLayoutPanel->setTargetDim(gSavedPerAccountSettings.getS32("InventoryInboxHeight")); - } - - if (sLoginCompleted) - { - //save the state of Inbox panel only for main Inventory floater - inbox_button->removeControlVariable(); - inbox_button->setToggleState(false); - updateInbox(); - } - else - { - // Trigger callback for after login so we can setup to track inbox changes after initial inventory load - LLAppViewer::instance()->setOnLoginCompletedCallback(boost::bind(&LLSidepanelInventory::updateInbox, this)); - } - } - - gSavedSettings.getControl("InventoryDisplayInbox")->getCommitSignal()->connect(boost::bind(&handleInventoryDisplayInboxChanged)); - - LLFloater *floater = dynamic_cast(getParent()); - if (floater && floater->getKey().isUndefined() && !sLoginCompleted) - { - // Prefill inventory for primary inventory floater - // Other floaters should fill on visibility change - // - // see get_instance_num(); - // Primary inventory floater will have undefined key - initInventoryViews(); - } - - return true; -} - -void LLSidepanelInventory::updateInbox() -{ - sLoginCompleted = true; - // - // Track inbox folder changes - // - const LLUUID inbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX); - - // Set up observer to listen for creation of inbox if it doesn't exist - if (inbox_id.isNull()) - { - observeInboxCreation(); - } - // Set up observer for inbox changes, if we have an inbox already - else - { - // Consolidate Received items - // We shouldn't have to do that but with a client/server system relying on a "well known folder" convention, - // things can get messy and conventions broken. This call puts everything back together in its right place. - gInventory.consolidateForType(inbox_id, LLFolderType::FT_INBOX); - - // Enable the display of the inbox if it exists - enableInbox(true); - - observeInboxModifications(inbox_id); - } -} - -void LLSidepanelInventory::observeInboxCreation() -{ - // - // Set up observer to track inbox folder creation - // - - if (mInboxAddedObserver == NULL) - { - mInboxAddedObserver = new LLInboxAddedObserver(this); - - gInventory.addObserver(mInboxAddedObserver); - } -} - -void LLSidepanelInventory::observeInboxModifications(const LLUUID& inboxID) -{ - // - // Silently do nothing if we already have an inbox inventory panel set up - // (this can happen multiple times on the initial session that creates the inbox) - // - - if (mInventoryPanelInbox.get() != NULL) - { - return; - } - - // - // Track inbox folder changes - // - - if (inboxID.isNull()) - { - LL_WARNS() << "Attempting to track modifications to non-existent inbox" << LL_ENDL; - return; - } - - if (mCategoriesObserver == NULL) - { - mCategoriesObserver = new LLInventoryCategoriesObserver(); - gInventory.addObserver(mCategoriesObserver); - } - - mCategoriesObserver->addCategory(inboxID, boost::bind(&LLSidepanelInventory::onInboxChanged, this, inboxID)); - - // - // Trigger a load for the entire contents of the Inbox - // - - LLInventoryModelBackgroundFetch::instance().start(inboxID); - - // - // Set up the inbox inventory view - // - - LLPanelMarketplaceInbox * inbox = getChild(MARKETPLACE_INBOX_PANEL); - LLInventoryPanel* inventory_panel = inbox->setupInventoryPanel(); - mInventoryPanelInbox = inventory_panel->getInventoryPanelHandle(); -} - -void LLSidepanelInventory::enableInbox(bool enabled) -{ - mInboxEnabled = enabled; - - if(!enabled || !mPanelMainInventory->isSingleFolderMode()) - { - toggleInbox(); - } -} - -void LLSidepanelInventory::hideInbox() -{ - mInboxLayoutPanel->setVisible(false); -} - -void LLSidepanelInventory::toggleInbox() -{ - mInboxLayoutPanel->setVisible(mInboxEnabled); -} - -void LLSidepanelInventory::openInbox() -{ - if (mInboxEnabled) - { - getChild(INBOX_BUTTON_NAME)->setToggleState(true); - onToggleInboxBtn(); - } -} - -void LLSidepanelInventory::onInboxChanged(const LLUUID& inbox_id) -{ - // Trigger a load of the entire inbox so we always know the contents and their creation dates for sorting - LLInventoryModelBackgroundFetch::instance().start(inbox_id); - -#if AUTO_EXPAND_INBOX - // Expand the inbox since we have fresh items - if (mInboxEnabled) - { - getChild(INBOX_BUTTON_NAME)->setToggleState(true); - onToggleInboxBtn(); - } -#endif -} - -void LLSidepanelInventory::onToggleInboxBtn() -{ - LLButton* inboxButton = getChild(INBOX_BUTTON_NAME); - LLLayoutStack* inv_stack = getChild(INVENTORY_LAYOUT_STACK_NAME); - - const bool inbox_expanded = inboxButton->getToggleState(); - - // Expand/collapse the indicated panel - inv_stack->collapsePanel(mInboxLayoutPanel, !inbox_expanded); - - if (inbox_expanded) - { - mInboxLayoutPanel->setTargetDim(gSavedPerAccountSettings.getS32("InventoryInboxHeight")); - if (mInboxLayoutPanel->isInVisibleChain()) - { - gSavedPerAccountSettings.setU32("LastInventoryInboxActivity", time_corrected()); - } -} - else - { - gSavedPerAccountSettings.setS32("InventoryInboxHeight", mInboxLayoutPanel->getTargetDim()); - } - -} - -void LLSidepanelInventory::onOpen(const LLSD& key) -{ - LLFirstUse::newInventory(false); - mPanelMainInventory->setFocusFilterEditor(); -#if AUTO_EXPAND_INBOX - // Expand the inbox if we have fresh items - LLPanelMarketplaceInbox * inbox = findChild(MARKETPLACE_INBOX_PANEL); - if (inbox && (inbox->getFreshItemCount() > 0)) - { - getChild(INBOX_BUTTON_NAME)->setToggleState(true); - onToggleInboxBtn(); - } -#else - if (mInboxEnabled && getChild(INBOX_BUTTON_NAME)->getToggleState()) - { - gSavedPerAccountSettings.setU32("LastInventoryInboxActivity", time_corrected()); - } -#endif - - gAgent.showLatestFeatureNotification("inventory"); -} - -void LLSidepanelInventory::performActionOnSelection(const std::string &action) -{ - LLFolderViewItem* current_item = mPanelMainInventory->getActivePanel()->getRootFolder()->getCurSelectedItem(); - if (!current_item) - { - if (mInventoryPanelInbox.get() && mInventoryPanelInbox.get()->getRootFolder()) - { - current_item = mInventoryPanelInbox.get()->getRootFolder()->getCurSelectedItem(); - } - - if (!current_item) - { - return; - } - } - - static_cast(current_item->getViewModelItem())->performAction(mPanelMainInventory->getActivePanel()->getModel(), action); -} - -void LLSidepanelInventory::onBackButtonClicked() -{ - showInventoryPanel(); -} - -void LLSidepanelInventory::onSelectionChange(const std::deque &items, bool user_action) -{ - -} - -void LLSidepanelInventory::showInventoryPanel() -{ - mInventoryPanel->setVisible(true); -} - -void LLSidepanelInventory::initInventoryViews() -{ - mPanelMainInventory->initInventoryViews(); -} - -bool LLSidepanelInventory::canShare() -{ - LLInventoryPanel* inbox = mInventoryPanelInbox.get(); - - // Avoid flicker in the Recent tab while inventory is being loaded. - if ( (!inbox || !inbox->getRootFolder() || inbox->getRootFolder()->getSelectionList().empty()) - && (mPanelMainInventory && !mPanelMainInventory->getActivePanel()->getRootFolder()->hasVisibleChildren()) ) - { - return false; - } - - return ( (mPanelMainInventory ? LLAvatarActions::canShareSelectedItems(mPanelMainInventory->getActivePanel()) : false) - || (inbox ? LLAvatarActions::canShareSelectedItems(inbox) : false) ); -} - - -bool LLSidepanelInventory::canWearSelected() -{ - - std::set selected_uuids = LLAvatarActions::getInventorySelectedUUIDs(); - - if (selected_uuids.empty()) - return false; - - for (std::set::const_iterator it = selected_uuids.begin(); - it != selected_uuids.end(); - ++it) - { - if (!get_can_item_be_worn(*it)) return false; - } - - return true; -} - -LLInventoryItem *LLSidepanelInventory::getSelectedItem() -{ - LLFolderView* root = mPanelMainInventory->getActivePanel()->getRootFolder(); - if (!root) - { - return NULL; - } - LLFolderViewItem* current_item = root->getCurSelectedItem(); - - if (!current_item) - { - if (mInventoryPanelInbox.get() && mInventoryPanelInbox.get()->getRootFolder()) - { - current_item = mInventoryPanelInbox.get()->getRootFolder()->getCurSelectedItem(); - } - - if (!current_item) - { - return NULL; - } - } - const LLUUID &item_id = static_cast(current_item->getViewModelItem())->getUUID(); - LLInventoryItem *item = gInventory.getItem(item_id); - return item; -} - -U32 LLSidepanelInventory::getSelectedCount() -{ - int count = 0; - - std::set selection_list = mPanelMainInventory->getActivePanel()->getRootFolder()->getSelectionList(); - count += selection_list.size(); - - if ((count == 0) && mInboxEnabled && mInventoryPanelInbox.get() && mInventoryPanelInbox.get()->getRootFolder()) - { - selection_list = mInventoryPanelInbox.get()->getRootFolder()->getSelectionList(); - - count += selection_list.size(); - } - - return count; -} - -LLInventoryPanel *LLSidepanelInventory::getActivePanel() -{ - if (!getVisible()) - { - return NULL; - } - if (mInventoryPanel->getVisible()) - { - return mPanelMainInventory->getActivePanel(); - } - return NULL; -} - -void LLSidepanelInventory::selectAllItemsPanel() -{ - if (!getVisible()) - { - return; - } - if (mInventoryPanel->getVisible()) - { - mPanelMainInventory->selectAllItemsPanel(); - } - -} - -bool LLSidepanelInventory::isMainInventoryPanelActive() const -{ - return mInventoryPanel->getVisible(); -} - -void LLSidepanelInventory::clearSelections(bool clearMain, bool clearInbox) -{ - if (clearMain) - { - LLInventoryPanel * inv_panel = getActivePanel(); - - if (inv_panel) - { - inv_panel->getRootFolder()->clearSelection(); - } - } - - if (clearInbox && mInboxEnabled && !mInventoryPanelInbox.isDead()) - { - mInventoryPanelInbox.get()->getRootFolder()->clearSelection(); - } -} - -std::set LLSidepanelInventory::getInboxSelectionList() -{ - std::set inventory_selected_uuids; - - if (mInboxEnabled && mInventoryPanelInbox.get() && mInventoryPanelInbox.get()->getRootFolder()) - { - inventory_selected_uuids = mInventoryPanelInbox.get()->getRootFolder()->getSelectionList(); - } - - return inventory_selected_uuids; -} - -void LLSidepanelInventory::cleanup() -{ - LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("inventory"); - for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); iter != inst_list.end();) - { - LLFloaterSidePanelContainer* iv = dynamic_cast(*iter++); - if (iv) - { - iv->cleanup(); - } - } -} +/** + * @file LLSidepanelInventory.cpp + * @brief Side Bar "Inventory" panel + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llsidepanelinventory.h" + +#include "llagent.h" +#include "llappearancemgr.h" +#include "llappviewer.h" +#include "llavataractions.h" +#include "llbutton.h" +#include "lldate.h" +#include "llfirstuse.h" +#include "llfloaterreg.h" +#include "llfloatersidepanelcontainer.h" +#include "llfoldertype.h" +#include "llfolderview.h" +#include "llinventorybridge.h" +#include "llinventoryfunctions.h" +#include "llinventorymodel.h" +#include "llinventorymodelbackgroundfetch.h" +#include "llinventoryobserver.h" +#include "llinventorypanel.h" +#include "lllayoutstack.h" +#include "lloutfitobserver.h" +#include "llpanelmaininventory.h" +#include "llpanelmarketplaceinbox.h" +#include "llselectmgr.h" +#include "llsidepaneliteminfo.h" +#include "llsidepaneltaskinfo.h" +#include "llstring.h" +#include "lltabcontainer.h" +#include "lltextbox.h" +#include "lltrans.h" +#include "llviewermedia.h" +#include "llviewernetwork.h" +#include "llweb.h" + +static LLPanelInjector t_inventory("sidepanel_inventory"); + +// +// Constants +// + +// No longer want the inbox panel to auto-expand since it creates issues with the "new" tag time stamp +#define AUTO_EXPAND_INBOX 0 + +static const char * const INBOX_BUTTON_NAME = "inbox_btn"; +static const char * const INBOX_LAYOUT_PANEL_NAME = "inbox_layout_panel"; +static const char * const INVENTORY_LAYOUT_STACK_NAME = "inventory_layout_stack"; +static const char * const MARKETPLACE_INBOX_PANEL = "marketplace_inbox"; + +static bool sLoginCompleted = false; + +// +// Helpers +// +class LLInboxAddedObserver : public LLInventoryCategoryAddedObserver +{ +public: + LLInboxAddedObserver(LLSidepanelInventory * sidepanelInventory) + : LLInventoryCategoryAddedObserver() + , mSidepanelInventory(sidepanelInventory) + { + } + + void done() + { + for (cat_vec_t::iterator it = mAddedCategories.begin(); it != mAddedCategories.end(); ++it) + { + LLViewerInventoryCategory* added_category = *it; + + LLFolderType::EType added_category_type = added_category->getPreferredType(); + + switch (added_category_type) + { + case LLFolderType::FT_INBOX: + mSidepanelInventory->enableInbox(true); + mSidepanelInventory->observeInboxModifications(added_category->getUUID()); + break; + default: + break; + } + } + } + +private: + LLSidepanelInventory * mSidepanelInventory; +}; + +// +// Implementation +// + +LLSidepanelInventory::LLSidepanelInventory() + : LLPanel() + , mPanelMainInventory(NULL) + , mInboxEnabled(false) + , mCategoriesObserver(NULL) + , mInboxAddedObserver(NULL) + , mInboxLayoutPanel(NULL) +{ + //buildFromFile( "panel_inventory.xml"); // Called from LLRegisterPanelClass::defaultPanelClassBuilder() +} + +LLSidepanelInventory::~LLSidepanelInventory() +{ + // Save the InventoryMainPanelHeight in settings per account + gSavedPerAccountSettings.setS32("InventoryInboxHeight", mInboxLayoutPanel->getTargetDim()); + + if (mCategoriesObserver && gInventory.containsObserver(mCategoriesObserver)) + { + gInventory.removeObserver(mCategoriesObserver); + } + delete mCategoriesObserver; + + if (mInboxAddedObserver && gInventory.containsObserver(mInboxAddedObserver)) + { + gInventory.removeObserver(mInboxAddedObserver); + } + delete mInboxAddedObserver; +} + +void handleInventoryDisplayInboxChanged() +{ + LLSidepanelInventory* sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); + if (sidepanel_inventory) + { + sidepanel_inventory->enableInbox(gSavedSettings.getBOOL("InventoryDisplayInbox")); + } +} + +bool LLSidepanelInventory::postBuild() +{ + // UI elements from inventory panel + { + mInventoryPanel = getChild("sidepanel_inventory_panel"); + + mPanelMainInventory = mInventoryPanel->getChild("panel_main_inventory"); + mPanelMainInventory->setSelectCallback(boost::bind(&LLSidepanelInventory::onSelectionChange, this, _1, _2)); + //LLTabContainer* tabs = mPanelMainInventory->getChild("inventory filter tabs"); + //tabs->setCommitCallback(boost::bind(&LLSidepanelInventory::updateVerbs, this)); + + /* + EXT-4846 : "Can we suppress the "Landmarks" and "My Favorites" folder since they have their own Task Panel?" + Deferring this until 2.1. + LLInventoryPanel *my_inventory_panel = mPanelMainInventory->getChild("All Items"); + my_inventory_panel->addHideFolderType(LLFolderType::FT_LANDMARK); + my_inventory_panel->addHideFolderType(LLFolderType::FT_FAVORITE); + */ + + //LLOutfitObserver::instance().addCOFChangedCallback(boost::bind(&LLSidepanelInventory::updateVerbs, this)); + } + + // Received items inbox setup + { + LLLayoutStack* inv_stack = getChild(INVENTORY_LAYOUT_STACK_NAME); + + // Set up button states and callbacks + LLButton * inbox_button = getChild(INBOX_BUTTON_NAME); + + inbox_button->setCommitCallback(boost::bind(&LLSidepanelInventory::onToggleInboxBtn, this)); + + // For main Inventory floater: Get the previous inbox state from "InventoryInboxToggleState" setting. + // For additional Inventory floaters: Collapsed state is default. + bool is_inbox_collapsed = !inbox_button->getToggleState() || sLoginCompleted; + + // Restore the collapsed inbox panel state + mInboxLayoutPanel = getChild(INBOX_LAYOUT_PANEL_NAME); + inv_stack->collapsePanel(mInboxLayoutPanel, is_inbox_collapsed); + if (!is_inbox_collapsed) + { + mInboxLayoutPanel->setTargetDim(gSavedPerAccountSettings.getS32("InventoryInboxHeight")); + } + + if (sLoginCompleted) + { + //save the state of Inbox panel only for main Inventory floater + inbox_button->removeControlVariable(); + inbox_button->setToggleState(false); + updateInbox(); + } + else + { + // Trigger callback for after login so we can setup to track inbox changes after initial inventory load + LLAppViewer::instance()->setOnLoginCompletedCallback(boost::bind(&LLSidepanelInventory::updateInbox, this)); + } + } + + gSavedSettings.getControl("InventoryDisplayInbox")->getCommitSignal()->connect(boost::bind(&handleInventoryDisplayInboxChanged)); + + LLFloater *floater = dynamic_cast(getParent()); + if (floater && floater->getKey().isUndefined() && !sLoginCompleted) + { + // Prefill inventory for primary inventory floater + // Other floaters should fill on visibility change + // + // see get_instance_num(); + // Primary inventory floater will have undefined key + initInventoryViews(); + } + + return true; +} + +void LLSidepanelInventory::updateInbox() +{ + sLoginCompleted = true; + // + // Track inbox folder changes + // + const LLUUID inbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX); + + // Set up observer to listen for creation of inbox if it doesn't exist + if (inbox_id.isNull()) + { + observeInboxCreation(); + } + // Set up observer for inbox changes, if we have an inbox already + else + { + // Consolidate Received items + // We shouldn't have to do that but with a client/server system relying on a "well known folder" convention, + // things can get messy and conventions broken. This call puts everything back together in its right place. + gInventory.consolidateForType(inbox_id, LLFolderType::FT_INBOX); + + // Enable the display of the inbox if it exists + enableInbox(true); + + observeInboxModifications(inbox_id); + } +} + +void LLSidepanelInventory::observeInboxCreation() +{ + // + // Set up observer to track inbox folder creation + // + + if (mInboxAddedObserver == NULL) + { + mInboxAddedObserver = new LLInboxAddedObserver(this); + + gInventory.addObserver(mInboxAddedObserver); + } +} + +void LLSidepanelInventory::observeInboxModifications(const LLUUID& inboxID) +{ + // + // Silently do nothing if we already have an inbox inventory panel set up + // (this can happen multiple times on the initial session that creates the inbox) + // + + if (mInventoryPanelInbox.get() != NULL) + { + return; + } + + // + // Track inbox folder changes + // + + if (inboxID.isNull()) + { + LL_WARNS() << "Attempting to track modifications to non-existent inbox" << LL_ENDL; + return; + } + + if (mCategoriesObserver == NULL) + { + mCategoriesObserver = new LLInventoryCategoriesObserver(); + gInventory.addObserver(mCategoriesObserver); + } + + mCategoriesObserver->addCategory(inboxID, boost::bind(&LLSidepanelInventory::onInboxChanged, this, inboxID)); + + // + // Trigger a load for the entire contents of the Inbox + // + + LLInventoryModelBackgroundFetch::instance().start(inboxID); + + // + // Set up the inbox inventory view + // + + LLPanelMarketplaceInbox * inbox = getChild(MARKETPLACE_INBOX_PANEL); + LLInventoryPanel* inventory_panel = inbox->setupInventoryPanel(); + mInventoryPanelInbox = inventory_panel->getInventoryPanelHandle(); +} + +void LLSidepanelInventory::enableInbox(bool enabled) +{ + mInboxEnabled = enabled; + + if(!enabled || !mPanelMainInventory->isSingleFolderMode()) + { + toggleInbox(); + } +} + +void LLSidepanelInventory::hideInbox() +{ + mInboxLayoutPanel->setVisible(false); +} + +void LLSidepanelInventory::toggleInbox() +{ + mInboxLayoutPanel->setVisible(mInboxEnabled); +} + +void LLSidepanelInventory::openInbox() +{ + if (mInboxEnabled) + { + getChild(INBOX_BUTTON_NAME)->setToggleState(true); + onToggleInboxBtn(); + } +} + +void LLSidepanelInventory::onInboxChanged(const LLUUID& inbox_id) +{ + // Trigger a load of the entire inbox so we always know the contents and their creation dates for sorting + LLInventoryModelBackgroundFetch::instance().start(inbox_id); + +#if AUTO_EXPAND_INBOX + // Expand the inbox since we have fresh items + if (mInboxEnabled) + { + getChild(INBOX_BUTTON_NAME)->setToggleState(true); + onToggleInboxBtn(); + } +#endif +} + +void LLSidepanelInventory::onToggleInboxBtn() +{ + LLButton* inboxButton = getChild(INBOX_BUTTON_NAME); + LLLayoutStack* inv_stack = getChild(INVENTORY_LAYOUT_STACK_NAME); + + const bool inbox_expanded = inboxButton->getToggleState(); + + // Expand/collapse the indicated panel + inv_stack->collapsePanel(mInboxLayoutPanel, !inbox_expanded); + + if (inbox_expanded) + { + mInboxLayoutPanel->setTargetDim(gSavedPerAccountSettings.getS32("InventoryInboxHeight")); + if (mInboxLayoutPanel->isInVisibleChain()) + { + gSavedPerAccountSettings.setU32("LastInventoryInboxActivity", time_corrected()); + } +} + else + { + gSavedPerAccountSettings.setS32("InventoryInboxHeight", mInboxLayoutPanel->getTargetDim()); + } + +} + +void LLSidepanelInventory::onOpen(const LLSD& key) +{ + LLFirstUse::newInventory(false); + mPanelMainInventory->setFocusFilterEditor(); +#if AUTO_EXPAND_INBOX + // Expand the inbox if we have fresh items + LLPanelMarketplaceInbox * inbox = findChild(MARKETPLACE_INBOX_PANEL); + if (inbox && (inbox->getFreshItemCount() > 0)) + { + getChild(INBOX_BUTTON_NAME)->setToggleState(true); + onToggleInboxBtn(); + } +#else + if (mInboxEnabled && getChild(INBOX_BUTTON_NAME)->getToggleState()) + { + gSavedPerAccountSettings.setU32("LastInventoryInboxActivity", time_corrected()); + } +#endif + + gAgent.showLatestFeatureNotification("inventory"); +} + +void LLSidepanelInventory::performActionOnSelection(const std::string &action) +{ + LLFolderViewItem* current_item = mPanelMainInventory->getActivePanel()->getRootFolder()->getCurSelectedItem(); + if (!current_item) + { + if (mInventoryPanelInbox.get() && mInventoryPanelInbox.get()->getRootFolder()) + { + current_item = mInventoryPanelInbox.get()->getRootFolder()->getCurSelectedItem(); + } + + if (!current_item) + { + return; + } + } + + static_cast(current_item->getViewModelItem())->performAction(mPanelMainInventory->getActivePanel()->getModel(), action); +} + +void LLSidepanelInventory::onBackButtonClicked() +{ + showInventoryPanel(); +} + +void LLSidepanelInventory::onSelectionChange(const std::deque &items, bool user_action) +{ + +} + +void LLSidepanelInventory::showInventoryPanel() +{ + mInventoryPanel->setVisible(true); +} + +void LLSidepanelInventory::initInventoryViews() +{ + mPanelMainInventory->initInventoryViews(); +} + +bool LLSidepanelInventory::canShare() +{ + LLInventoryPanel* inbox = mInventoryPanelInbox.get(); + + // Avoid flicker in the Recent tab while inventory is being loaded. + if ( (!inbox || !inbox->getRootFolder() || inbox->getRootFolder()->getSelectionList().empty()) + && (mPanelMainInventory && !mPanelMainInventory->getActivePanel()->getRootFolder()->hasVisibleChildren()) ) + { + return false; + } + + return ( (mPanelMainInventory ? LLAvatarActions::canShareSelectedItems(mPanelMainInventory->getActivePanel()) : false) + || (inbox ? LLAvatarActions::canShareSelectedItems(inbox) : false) ); +} + + +bool LLSidepanelInventory::canWearSelected() +{ + + std::set selected_uuids = LLAvatarActions::getInventorySelectedUUIDs(); + + if (selected_uuids.empty()) + return false; + + for (std::set::const_iterator it = selected_uuids.begin(); + it != selected_uuids.end(); + ++it) + { + if (!get_can_item_be_worn(*it)) return false; + } + + return true; +} + +LLInventoryItem *LLSidepanelInventory::getSelectedItem() +{ + LLFolderView* root = mPanelMainInventory->getActivePanel()->getRootFolder(); + if (!root) + { + return NULL; + } + LLFolderViewItem* current_item = root->getCurSelectedItem(); + + if (!current_item) + { + if (mInventoryPanelInbox.get() && mInventoryPanelInbox.get()->getRootFolder()) + { + current_item = mInventoryPanelInbox.get()->getRootFolder()->getCurSelectedItem(); + } + + if (!current_item) + { + return NULL; + } + } + const LLUUID &item_id = static_cast(current_item->getViewModelItem())->getUUID(); + LLInventoryItem *item = gInventory.getItem(item_id); + return item; +} + +U32 LLSidepanelInventory::getSelectedCount() +{ + int count = 0; + + std::set selection_list = mPanelMainInventory->getActivePanel()->getRootFolder()->getSelectionList(); + count += selection_list.size(); + + if ((count == 0) && mInboxEnabled && mInventoryPanelInbox.get() && mInventoryPanelInbox.get()->getRootFolder()) + { + selection_list = mInventoryPanelInbox.get()->getRootFolder()->getSelectionList(); + + count += selection_list.size(); + } + + return count; +} + +LLInventoryPanel *LLSidepanelInventory::getActivePanel() +{ + if (!getVisible()) + { + return NULL; + } + if (mInventoryPanel->getVisible()) + { + return mPanelMainInventory->getActivePanel(); + } + return NULL; +} + +void LLSidepanelInventory::selectAllItemsPanel() +{ + if (!getVisible()) + { + return; + } + if (mInventoryPanel->getVisible()) + { + mPanelMainInventory->selectAllItemsPanel(); + } + +} + +bool LLSidepanelInventory::isMainInventoryPanelActive() const +{ + return mInventoryPanel->getVisible(); +} + +void LLSidepanelInventory::clearSelections(bool clearMain, bool clearInbox) +{ + if (clearMain) + { + LLInventoryPanel * inv_panel = getActivePanel(); + + if (inv_panel) + { + inv_panel->getRootFolder()->clearSelection(); + } + } + + if (clearInbox && mInboxEnabled && !mInventoryPanelInbox.isDead()) + { + mInventoryPanelInbox.get()->getRootFolder()->clearSelection(); + } +} + +std::set LLSidepanelInventory::getInboxSelectionList() +{ + std::set inventory_selected_uuids; + + if (mInboxEnabled && mInventoryPanelInbox.get() && mInventoryPanelInbox.get()->getRootFolder()) + { + inventory_selected_uuids = mInventoryPanelInbox.get()->getRootFolder()->getSelectionList(); + } + + return inventory_selected_uuids; +} + +void LLSidepanelInventory::cleanup() +{ + LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("inventory"); + for (LLFloaterReg::const_instance_list_t::const_iterator iter = inst_list.begin(); iter != inst_list.end();) + { + LLFloaterSidePanelContainer* iv = dynamic_cast(*iter++); + if (iv) + { + iv->cleanup(); + } + } +} diff --git a/indra/newview/llsidepanelinventory.h b/indra/newview/llsidepanelinventory.h index 148fdd0a7d..b3b3ce4c50 100644 --- a/indra/newview/llsidepanelinventory.h +++ b/indra/newview/llsidepanelinventory.h @@ -1,119 +1,119 @@ -/** - * @file LLSidepanelInventory.h - * @brief Side Bar "Inventory" panel - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLSIDEPANELINVENTORY_H -#define LL_LLSIDEPANELINVENTORY_H - -#include "llpanel.h" - -class LLButton; -class LLFolderViewItem; -class LLInboxAddedObserver; -class LLInventoryCategoriesObserver; -class LLInventoryItem; -class LLInventoryPanel; -class LLLayoutPanel; -class LLPanelMainInventory; -class LLSidepanelItemInfo; -class LLSidepanelTaskInfo; - -class LLSidepanelInventory : public LLPanel -{ -public: - LLSidepanelInventory(); - virtual ~LLSidepanelInventory(); - -private: - void updateInbox(); - -public: - void observeInboxCreation(); - void observeInboxModifications(const LLUUID& inboxID); - - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); - - LLInventoryPanel* getActivePanel(); // Returns an active inventory panel, if any. - void selectAllItemsPanel(); - LLInventoryPanel* getInboxPanel() const { return mInventoryPanelInbox.get(); } - - LLPanelMainInventory* getMainInventoryPanel() const { return mPanelMainInventory; } - bool isMainInventoryPanelActive() const; - - void clearSelections(bool clearMain, bool clearInbox); - std::set getInboxSelectionList(); - - void showInventoryPanel(); - void initInventoryViews(); - - // checks can share selected item(s) - bool canShare(); - - void onToggleInboxBtn(); - - void enableInbox(bool enabled); - void toggleInbox(); - void hideInbox(); - - void openInbox(); - - bool isInboxEnabled() const { return mInboxEnabled; } - - static void cleanup(); - -protected: - // Tracks highlighted (selected) item in inventory panel. - LLInventoryItem *getSelectedItem(); - U32 getSelectedCount(); - void onSelectionChange(const std::deque &items, bool user_action); - // "wear", "teleport", etc. - void performActionOnSelection(const std::string &action); - - bool canWearSelected(); // check whether selected items can be worn - - void onInboxChanged(const LLUUID& inbox_id); - - // - // UI Elements - // -private: - LLPanel* mInventoryPanel; // Main inventory view - LLHandle mInventoryPanelInbox; - LLPanelMainInventory* mPanelMainInventory; - - LLLayoutPanel* mInboxLayoutPanel; - -protected: - void onBackButtonClicked(); - -private: - bool mInboxEnabled; - - LLInventoryCategoriesObserver* mCategoriesObserver; - LLInboxAddedObserver* mInboxAddedObserver; -}; - -#endif //LL_LLSIDEPANELINVENTORY_H +/** + * @file LLSidepanelInventory.h + * @brief Side Bar "Inventory" panel + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSIDEPANELINVENTORY_H +#define LL_LLSIDEPANELINVENTORY_H + +#include "llpanel.h" + +class LLButton; +class LLFolderViewItem; +class LLInboxAddedObserver; +class LLInventoryCategoriesObserver; +class LLInventoryItem; +class LLInventoryPanel; +class LLLayoutPanel; +class LLPanelMainInventory; +class LLSidepanelItemInfo; +class LLSidepanelTaskInfo; + +class LLSidepanelInventory : public LLPanel +{ +public: + LLSidepanelInventory(); + virtual ~LLSidepanelInventory(); + +private: + void updateInbox(); + +public: + void observeInboxCreation(); + void observeInboxModifications(const LLUUID& inboxID); + + /*virtual*/ bool postBuild(); + /*virtual*/ void onOpen(const LLSD& key); + + LLInventoryPanel* getActivePanel(); // Returns an active inventory panel, if any. + void selectAllItemsPanel(); + LLInventoryPanel* getInboxPanel() const { return mInventoryPanelInbox.get(); } + + LLPanelMainInventory* getMainInventoryPanel() const { return mPanelMainInventory; } + bool isMainInventoryPanelActive() const; + + void clearSelections(bool clearMain, bool clearInbox); + std::set getInboxSelectionList(); + + void showInventoryPanel(); + void initInventoryViews(); + + // checks can share selected item(s) + bool canShare(); + + void onToggleInboxBtn(); + + void enableInbox(bool enabled); + void toggleInbox(); + void hideInbox(); + + void openInbox(); + + bool isInboxEnabled() const { return mInboxEnabled; } + + static void cleanup(); + +protected: + // Tracks highlighted (selected) item in inventory panel. + LLInventoryItem *getSelectedItem(); + U32 getSelectedCount(); + void onSelectionChange(const std::deque &items, bool user_action); + // "wear", "teleport", etc. + void performActionOnSelection(const std::string &action); + + bool canWearSelected(); // check whether selected items can be worn + + void onInboxChanged(const LLUUID& inbox_id); + + // + // UI Elements + // +private: + LLPanel* mInventoryPanel; // Main inventory view + LLHandle mInventoryPanelInbox; + LLPanelMainInventory* mPanelMainInventory; + + LLLayoutPanel* mInboxLayoutPanel; + +protected: + void onBackButtonClicked(); + +private: + bool mInboxEnabled; + + LLInventoryCategoriesObserver* mCategoriesObserver; + LLInboxAddedObserver* mInboxAddedObserver; +}; + +#endif //LL_LLSIDEPANELINVENTORY_H diff --git a/indra/newview/llsidepanelinventorysubpanel.cpp b/indra/newview/llsidepanelinventorysubpanel.cpp index c11d24895d..820ceed296 100644 --- a/indra/newview/llsidepanelinventorysubpanel.cpp +++ b/indra/newview/llsidepanelinventorysubpanel.cpp @@ -1,139 +1,139 @@ -/** - * @file llsidepanelinventorysubpanel.cpp - * @brief A floater which shows an inventory item's properties. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llsidepanelinventorysubpanel.h" - -#include "roles_constants.h" - -#include "llagent.h" -#include "llavataractions.h" -#include "llbutton.h" -#include "llfloaterreg.h" -#include "llgroupactions.h" -#include "llinventorymodel.h" -#include "lllineeditor.h" -#include "llradiogroup.h" -#include "llviewercontrol.h" -#include "llviewerobjectlist.h" - - -///---------------------------------------------------------------------------- -/// Class LLSidepanelInventorySubpanel -///---------------------------------------------------------------------------- - -// Default constructor -LLSidepanelInventorySubpanel::LLSidepanelInventorySubpanel(const LLPanel::Params& p) - : LLPanel(p), - mIsDirty(true), - mIsEditing(false), - mCancelBtn(NULL) -{ -} - -// Destroys the object -LLSidepanelInventorySubpanel::~LLSidepanelInventorySubpanel() -{ -} - -// virtual -bool LLSidepanelInventorySubpanel::postBuild() -{ - - mCancelBtn = findChild("cancel_btn"); - if (mCancelBtn) - { - mCancelBtn->setClickedCallback(boost::bind(&LLSidepanelInventorySubpanel::onCancelButtonClicked, this)); - } - return true; -} - -void LLSidepanelInventorySubpanel::setVisible(bool visible) -{ - if (visible) - { - dirty(); - } - LLPanel::setVisible(visible); -} - -void LLSidepanelInventorySubpanel::setIsEditing(bool edit) -{ - mIsEditing = edit; - mIsDirty = true; -} - -bool LLSidepanelInventorySubpanel::getIsEditing() const -{ - - return true; // Default everything to edit mode since we're not using an edit button anymore. - // return mIsEditing; -} - -void LLSidepanelInventorySubpanel::reset() -{ - mIsDirty = true; -} - -void LLSidepanelInventorySubpanel::draw() -{ - if (mIsDirty) - { - refresh(); - updateVerbs(); - mIsDirty = false; - } - - LLPanel::draw(); -} - -void LLSidepanelInventorySubpanel::dirty() -{ - mIsDirty = true; - setIsEditing(false); -} - -void LLSidepanelInventorySubpanel::updateVerbs() -{ - if (mCancelBtn) - { - mCancelBtn->setVisible(mIsEditing); - } -} - -void LLSidepanelInventorySubpanel::onEditButtonClicked() -{ - setIsEditing(true); - refresh(); - updateVerbs(); -} - -void LLSidepanelInventorySubpanel::onCancelButtonClicked() -{ - setIsEditing(false); - refresh(); - updateVerbs(); -} +/** + * @file llsidepanelinventorysubpanel.cpp + * @brief A floater which shows an inventory item's properties. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llsidepanelinventorysubpanel.h" + +#include "roles_constants.h" + +#include "llagent.h" +#include "llavataractions.h" +#include "llbutton.h" +#include "llfloaterreg.h" +#include "llgroupactions.h" +#include "llinventorymodel.h" +#include "lllineeditor.h" +#include "llradiogroup.h" +#include "llviewercontrol.h" +#include "llviewerobjectlist.h" + + +///---------------------------------------------------------------------------- +/// Class LLSidepanelInventorySubpanel +///---------------------------------------------------------------------------- + +// Default constructor +LLSidepanelInventorySubpanel::LLSidepanelInventorySubpanel(const LLPanel::Params& p) + : LLPanel(p), + mIsDirty(true), + mIsEditing(false), + mCancelBtn(NULL) +{ +} + +// Destroys the object +LLSidepanelInventorySubpanel::~LLSidepanelInventorySubpanel() +{ +} + +// virtual +bool LLSidepanelInventorySubpanel::postBuild() +{ + + mCancelBtn = findChild("cancel_btn"); + if (mCancelBtn) + { + mCancelBtn->setClickedCallback(boost::bind(&LLSidepanelInventorySubpanel::onCancelButtonClicked, this)); + } + return true; +} + +void LLSidepanelInventorySubpanel::setVisible(bool visible) +{ + if (visible) + { + dirty(); + } + LLPanel::setVisible(visible); +} + +void LLSidepanelInventorySubpanel::setIsEditing(bool edit) +{ + mIsEditing = edit; + mIsDirty = true; +} + +bool LLSidepanelInventorySubpanel::getIsEditing() const +{ + + return true; // Default everything to edit mode since we're not using an edit button anymore. + // return mIsEditing; +} + +void LLSidepanelInventorySubpanel::reset() +{ + mIsDirty = true; +} + +void LLSidepanelInventorySubpanel::draw() +{ + if (mIsDirty) + { + refresh(); + updateVerbs(); + mIsDirty = false; + } + + LLPanel::draw(); +} + +void LLSidepanelInventorySubpanel::dirty() +{ + mIsDirty = true; + setIsEditing(false); +} + +void LLSidepanelInventorySubpanel::updateVerbs() +{ + if (mCancelBtn) + { + mCancelBtn->setVisible(mIsEditing); + } +} + +void LLSidepanelInventorySubpanel::onEditButtonClicked() +{ + setIsEditing(true); + refresh(); + updateVerbs(); +} + +void LLSidepanelInventorySubpanel::onCancelButtonClicked() +{ + setIsEditing(false); + refresh(); + updateVerbs(); +} diff --git a/indra/newview/llsidepanelinventorysubpanel.h b/indra/newview/llsidepanelinventorysubpanel.h index cf4a2399c2..a11c91561e 100644 --- a/indra/newview/llsidepanelinventorysubpanel.h +++ b/indra/newview/llsidepanelinventorysubpanel.h @@ -1,73 +1,73 @@ -/** - * @file llsidepanelinventorysubpanel.h - * @brief A panel which shows an inventory item's properties. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLSIDEPANELINVENTORYSUBPANEL_H -#define LL_LLSIDEPANELINVENTORYSUBPANEL_H - -#include "llpanel.h" - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLSidepanelInventorySubpanel -// Base class for inventory sidepanel panels (e.g. item info, task info). -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLButton; -class LLInventoryItem; - -class LLSidepanelInventorySubpanel : public LLPanel -{ -public: - LLSidepanelInventorySubpanel(const LLPanel::Params& p = getDefaultParams()); - virtual ~LLSidepanelInventorySubpanel(); - - /*virtual*/ void setVisible(bool visible); - virtual bool postBuild(); - virtual void draw(); - virtual void reset(); - - void dirty(); - void setIsEditing(bool edit); -protected: - virtual void refresh() = 0; - virtual void save() = 0; - virtual void updateVerbs(); - - bool getIsEditing() const; - - // - // UI Elements - // -protected: - void onEditButtonClicked(); - void onCancelButtonClicked(); - LLButton* mCancelBtn; - -private: - bool mIsDirty; // item properties need to be updated - bool mIsEditing; // if we're in edit mode -}; - -#endif // LL_LLSIDEPANELINVENTORYSUBPANEL_H +/** + * @file llsidepanelinventorysubpanel.h + * @brief A panel which shows an inventory item's properties. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSIDEPANELINVENTORYSUBPANEL_H +#define LL_LLSIDEPANELINVENTORYSUBPANEL_H + +#include "llpanel.h" + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLSidepanelInventorySubpanel +// Base class for inventory sidepanel panels (e.g. item info, task info). +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLButton; +class LLInventoryItem; + +class LLSidepanelInventorySubpanel : public LLPanel +{ +public: + LLSidepanelInventorySubpanel(const LLPanel::Params& p = getDefaultParams()); + virtual ~LLSidepanelInventorySubpanel(); + + /*virtual*/ void setVisible(bool visible); + virtual bool postBuild(); + virtual void draw(); + virtual void reset(); + + void dirty(); + void setIsEditing(bool edit); +protected: + virtual void refresh() = 0; + virtual void save() = 0; + virtual void updateVerbs(); + + bool getIsEditing() const; + + // + // UI Elements + // +protected: + void onEditButtonClicked(); + void onCancelButtonClicked(); + LLButton* mCancelBtn; + +private: + bool mIsDirty; // item properties need to be updated + bool mIsEditing; // if we're in edit mode +}; + +#endif // LL_LLSIDEPANELINVENTORYSUBPANEL_H diff --git a/indra/newview/llsidepaneliteminfo.cpp b/indra/newview/llsidepaneliteminfo.cpp index d9b4673204..7775e3d9f6 100644 --- a/indra/newview/llsidepaneliteminfo.cpp +++ b/indra/newview/llsidepaneliteminfo.cpp @@ -1,1199 +1,1199 @@ -/** - * @file llsidepaneliteminfo.cpp - * @brief A floater which shows an inventory item's properties. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llsidepaneliteminfo.h" - -#include "roles_constants.h" - -#include "llagent.h" -#include "llavataractions.h" -#include "llavatarnamecache.h" -#include "llbutton.h" -#include "llcallbacklist.h" -#include "llcombobox.h" -#include "llfloater.h" -#include "llfloaterreg.h" -#include "llgroupactions.h" -#include "llgroupmgr.h" -#include "lliconctrl.h" -#include "llinventorydefines.h" -#include "llinventoryicon.h" -#include "llinventorymodel.h" -#include "llinventoryobserver.h" -#include "lllineeditor.h" -#include "llradiogroup.h" -#include "llslurl.h" -#include "lltexteditor.h" -#include "llviewercontrol.h" -#include "llviewerinventory.h" -#include "llviewerobjectlist.h" -#include "llexperiencecache.h" -#include "lltrans.h" -#include "llviewerregion.h" - - -class PropertiesChangedCallback : public LLInventoryCallback -{ -public: - PropertiesChangedCallback(LLHandle sidepanel_handle, LLUUID &item_id, S32 id) - : mHandle(sidepanel_handle), mItemId(item_id), mId(id) - {} - - void fire(const LLUUID &inv_item) - { - // inv_item can be null for some reason - LLSidepanelItemInfo* sidepanel = dynamic_cast(mHandle.get()); - if (sidepanel) - { - // sidepanel waits only for most recent update - sidepanel->onUpdateCallback(mItemId, mId); - } - } -private: - LLHandle mHandle; - LLUUID mItemId; - S32 mId; -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLObjectInventoryObserver -// -// Helper class to watch for changes in an object inventory. -// Used to update item properties in LLSidepanelItemInfo. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLObjectInventoryObserver : public LLVOInventoryListener -{ -public: - LLObjectInventoryObserver(LLSidepanelItemInfo* floater, LLViewerObject* object) - : mFloater(floater) - { - registerVOInventoryListener(object, NULL); - } - virtual ~LLObjectInventoryObserver() - { - removeVOInventoryListener(); - } - /*virtual*/ void inventoryChanged(LLViewerObject* object, - LLInventoryObject::object_list_t* inventory, - S32 serial_num, - void* user_data); -private: - LLSidepanelItemInfo* mFloater; // Not a handle because LLSidepanelItemInfo is managing LLObjectInventoryObserver -}; - -/*virtual*/ -void LLObjectInventoryObserver::inventoryChanged(LLViewerObject* object, - LLInventoryObject::object_list_t* inventory, - S32 serial_num, - void* user_data) -{ - mFloater->dirty(); -} - -///---------------------------------------------------------------------------- -/// Class LLSidepanelItemInfo -///---------------------------------------------------------------------------- - -static LLPanelInjector t_item_info("sidepanel_item_info"); - -// Default constructor -LLSidepanelItemInfo::LLSidepanelItemInfo(const LLPanel::Params& p) - : LLPanel(p) - , mItemID(LLUUID::null) - , mObjectInventoryObserver(NULL) - , mUpdatePendingId(-1) - , mIsDirty(false) /*Not ready*/ - , mParentFloater(NULL) -{ - gInventory.addObserver(this); - gIdleCallbacks.addFunction(&LLSidepanelItemInfo::onIdle, (void*)this); -} - -// Destroys the object -LLSidepanelItemInfo::~LLSidepanelItemInfo() -{ - gInventory.removeObserver(this); - gIdleCallbacks.deleteFunction(&LLSidepanelItemInfo::onIdle, (void*)this); - - stopObjectInventoryObserver(); - - if (mOwnerCacheConnection.connected()) - { - mOwnerCacheConnection.disconnect(); - } - if (mCreatorCacheConnection.connected()) - { - mCreatorCacheConnection.disconnect(); - } -} - -// virtual -bool LLSidepanelItemInfo::postBuild() -{ - mChangeThumbnailBtn = getChild("change_thumbnail_btn"); - mItemTypeIcon = getChild("item_type_icon"); - mLabelOwnerName = getChild("LabelOwnerName"); - mLabelCreatorName = getChild("LabelCreatorName"); - - getChild("LabelItemName")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); - getChild("LabelItemName")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitName,this)); - getChild("LabelItemDesc")->setCommitCallback(boost::bind(&LLSidepanelItemInfo:: onCommitDescription, this)); - // Thumnail edition - mChangeThumbnailBtn->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onEditThumbnail, this)); - // acquired date - // owner permissions - // Permissions debug text - // group permissions - getChild("CheckShareWithGroup")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitPermissions, this, _1)); - // everyone permissions - getChild("CheckEveryoneCopy")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitPermissions, this, _1)); - // next owner permissions - getChild("CheckNextOwnerModify")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitPermissions, this, _1)); - getChild("CheckNextOwnerCopy")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitPermissions, this, _1)); - getChild("CheckNextOwnerTransfer")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitPermissions, this, _1)); - // Mark for sale or not, and sale info - getChild("CheckPurchase")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitSaleInfo, this, _1)); - // Change sale type, and sale info - getChild("ComboBoxSaleType")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitSaleInfo, this, _1)); - // "Price" label for edit - getChild("Edit Cost")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitSaleInfo, this, _1)); - refresh(); - return true; -} - -void LLSidepanelItemInfo::setObjectID(const LLUUID& object_id) -{ - mObjectID = object_id; - - // Start monitoring changes in the object inventory to update - // selected inventory item properties in Item Profile panel. See STORM-148. - startObjectInventoryObserver(); - mUpdatePendingId = -1; -} - -void LLSidepanelItemInfo::setItemID(const LLUUID& item_id) -{ - if (mItemID != item_id) - { - mItemID = item_id; - mUpdatePendingId = -1; - } - dirty(); -} - -void LLSidepanelItemInfo::setParentFloater(LLFloater* parent) -{ - mParentFloater = parent; -} - -const LLUUID& LLSidepanelItemInfo::getObjectID() const -{ - return mObjectID; -} - -const LLUUID& LLSidepanelItemInfo::getItemID() const -{ - return mItemID; -} - -void LLSidepanelItemInfo::onUpdateCallback(const LLUUID& item_id, S32 received_update_id) -{ - if (mItemID == item_id && mUpdatePendingId == received_update_id) - { - mUpdatePendingId = -1; - refresh(); - } -} - -void LLSidepanelItemInfo::reset() -{ - mObjectID = LLUUID::null; - mItemID = LLUUID::null; - - stopObjectInventoryObserver(); - dirty(); -} - -void LLSidepanelItemInfo::refresh() -{ - LLViewerInventoryItem* item = findItem(); - if(item) - { - const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - bool in_trash = (item->getUUID() == trash_id) || gInventory.isObjectDescendentOf(item->getUUID(), trash_id); - if (in_trash && mParentFloater) - { - // Close properties when moving to trash - // Aren't supposed to view properties from trash - mParentFloater->closeFloater(); - } - else - { - refreshFromItem(item); - } - return; - } - - if (mObjectID.notNull()) - { - LLViewerObject* object = gObjectList.findObject(mObjectID); - if (object) - { - // Object exists, but object's content is not nessesary - // loaded, so assume item exists as well - return; - } - } - - if (mParentFloater) - { - // if we failed to get item, it likely no longer exists - mParentFloater->closeFloater(); - } -} - -void LLSidepanelItemInfo::refreshFromItem(LLViewerInventoryItem* item) -{ - //////////////////////// - // PERMISSIONS LOOKUP // - //////////////////////// - - llassert(item); - if (!item) return; - - if (mUpdatePendingId != -1) - { - return; - } - - // do not enable the UI for incomplete items. - bool is_complete = item->isFinished(); - const bool cannot_restrict_permissions = LLInventoryType::cannotRestrictPermissions(item->getInventoryType()); - const bool is_calling_card = (item->getInventoryType() == LLInventoryType::IT_CALLINGCARD); - const bool is_settings = (item->getInventoryType() == LLInventoryType::IT_SETTINGS); - const LLPermissions& perm = item->getPermissions(); - const bool can_agent_manipulate = gAgent.allowOperation(PERM_OWNER, perm, - GP_OBJECT_MANIPULATE); - const bool can_agent_sell = gAgent.allowOperation(PERM_OWNER, perm, - GP_OBJECT_SET_SALE) && - !cannot_restrict_permissions; - const bool is_link = item->getIsLinkType(); - - const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - bool not_in_trash = (item->getUUID() != trash_id) && !gInventory.isObjectDescendentOf(item->getUUID(), trash_id); - - // You need permission to modify the object to modify an inventory - // item in it. - LLViewerObject* object = NULL; - if(!mObjectID.isNull()) object = gObjectList.findObject(mObjectID); - bool is_obj_modify = true; - if(object) - { - is_obj_modify = object->permOwnerModify(); - } - - if(item->getInventoryType() == LLInventoryType::IT_LSL) - { - getChildView("LabelItemExperienceTitle")->setVisible(true); - LLTextBox* tb = getChild("LabelItemExperience"); - tb->setText(getString("loading_experience")); - tb->setVisible(true); - std::string url = std::string(); - if(object && object->getRegion()) - { - url = object->getRegion()->getCapability("GetMetadata"); - } - LLExperienceCache::instance().fetchAssociatedExperience(item->getParentUUID(), item->getUUID(), url, - boost::bind(&LLSidepanelItemInfo::setAssociatedExperience, getDerivedHandle(), _1)); - } - - ////////////////////// - // ITEM NAME & DESC // - ////////////////////// - bool is_modifiable = gAgent.allowOperation(PERM_MODIFY, perm, - GP_OBJECT_MANIPULATE) - && is_obj_modify && is_complete && not_in_trash; - - getChildView("LabelItemNameTitle")->setEnabled(true); - getChildView("LabelItemName")->setEnabled(is_modifiable && !is_calling_card); // for now, don't allow rename of calling cards - getChild("LabelItemName")->setValue(item->getName()); - getChildView("LabelItemDescTitle")->setEnabled(true); - getChildView("LabelItemDesc")->setEnabled(is_modifiable); - getChild("LabelItemDesc")->setValue(item->getDescription()); - getChild("item_thumbnail")->setValue(item->getThumbnailUUID()); - - LLUIImagePtr icon_img = LLInventoryIcon::getIcon(item->getType(), item->getInventoryType(), item->getFlags(), false); - mItemTypeIcon->setImage(icon_img); - - // Style for creator and owner links - LLStyle::Params style_params; - LLColor4 link_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); - style_params.color = link_color; - style_params.readonly_color = link_color; - style_params.is_link = true; // link will be added later - const LLFontGL* fontp = mLabelCreatorName->getFont(); - style_params.font.name = LLFontGL::nameFromFont(fontp); - style_params.font.size = LLFontGL::sizeFromFont(fontp); - style_params.font.style = "UNDERLINE"; - - ////////////////// - // CREATOR NAME // - ////////////////// - if(!gCacheName) return; - if(!gAgent.getRegion()) return; - - if (item->getCreatorUUID().notNull()) - { - LLUUID creator_id = item->getCreatorUUID(); - std::string slurl = - LLSLURL("agent", creator_id, "inspect").getSLURLString(); - - style_params.link_href = slurl; - - LLAvatarName av_name; - if (LLAvatarNameCache::get(creator_id, &av_name)) - { - updateCreatorName(creator_id, av_name, style_params); - } - else - { - if (mCreatorCacheConnection.connected()) - { - mCreatorCacheConnection.disconnect(); - } - mLabelCreatorName->setText(LLTrans::getString("None")); - mCreatorCacheConnection = LLAvatarNameCache::get(creator_id, boost::bind(&LLSidepanelItemInfo::updateCreatorName, this, _1, _2, style_params)); - } - - getChildView("LabelCreatorTitle")->setEnabled(true); - mLabelCreatorName->setEnabled(true); - } - else - { - getChildView("LabelCreatorTitle")->setEnabled(false); - mLabelCreatorName->setEnabled(false); - mLabelCreatorName->setValue(getString("unknown_multiple")); - } - - //////////////// - // OWNER NAME // - //////////////// - if(perm.isOwned()) - { - std::string slurl; - if (perm.isGroupOwned()) - { - LLGroupMgrGroupData* group_data = LLGroupMgr::getInstance()->getGroupData(perm.getGroup()); - - slurl = LLSLURL("group", perm.getGroup(), "inspect").getSLURLString(); - style_params.link_href = slurl; - if (group_data && group_data->isGroupPropertiesDataComplete()) - { - mLabelOwnerName->setText(group_data->mName, style_params); - } - else - { - // Triggers refresh - LLGroupMgr::getInstance()->sendGroupPropertiesRequest(perm.getGroup()); - - std::string name; - gCacheName->getGroupName(perm.getGroup(), name); - mLabelOwnerName->setText(name, style_params); - } - } - else - { - LLUUID owner_id = perm.getOwner(); - slurl = LLSLURL("agent", owner_id, "inspect").getSLURLString(); - - style_params.link_href = slurl; - LLAvatarName av_name; - if (LLAvatarNameCache::get(owner_id, &av_name)) - { - updateOwnerName(owner_id, av_name, style_params); - } - else - { - if (mOwnerCacheConnection.connected()) - { - mOwnerCacheConnection.disconnect(); - } - mLabelOwnerName->setText(LLTrans::getString("None")); - mOwnerCacheConnection = LLAvatarNameCache::get(owner_id, boost::bind(&LLSidepanelItemInfo::updateOwnerName, this, _1, _2, style_params)); - } - } - getChildView("LabelOwnerTitle")->setEnabled(true); - mLabelOwnerName->setEnabled(true); - } - else - { - getChildView("LabelOwnerTitle")->setEnabled(false); - mLabelOwnerName->setEnabled(false); - mLabelOwnerName->setValue(getString("public")); - } - - // Not yet supported for task inventories - mChangeThumbnailBtn->setEnabled(mObjectID.isNull() && ALEXANDRIA_LINDEN_ID != perm.getOwner()); - - //////////// - // ORIGIN // - //////////// - - if (object) - { - getChild("origin")->setValue(getString("origin_inworld")); - } - else - { - getChild("origin")->setValue(getString("origin_inventory")); - } - - ////////////////// - // ACQUIRE DATE // - ////////////////// - - time_t time_utc = item->getCreationDate(); - if (0 == time_utc) - { - getChild("LabelAcquiredDate")->setValue(getString("unknown")); - } - else - { - std::string timeStr = getString("acquiredDate"); - LLSD substitution; - substitution["datetime"] = (S32) time_utc; - LLStringUtil::format (timeStr, substitution); - getChild("LabelAcquiredDate")->setValue(timeStr); - } - - ////////////////////////////////////// - // PERMISSIONS AND SALE ITEM HIDING // - ////////////////////////////////////// - - const std::string perm_and_sale_items[]={ - "perms_inv", - "perm_modify", - "CheckOwnerModify", - "CheckOwnerCopy", - "CheckOwnerTransfer", - "GroupLabel", - "CheckShareWithGroup", - "AnyoneLabel", - "CheckEveryoneCopy", - "NextOwnerLabel", - "CheckNextOwnerModify", - "CheckNextOwnerCopy", - "CheckNextOwnerTransfer", - "CheckPurchase", - "ComboBoxSaleType", - "Edit Cost" - }; - - const std::string debug_items[]={ - "BaseMaskDebug", - "OwnerMaskDebug", - "GroupMaskDebug", - "EveryoneMaskDebug", - "NextMaskDebug" - }; - - // Hide permissions checkboxes and labels and for sale info if in the trash - // or ui elements don't apply to these objects and return from function - if (!not_in_trash || cannot_restrict_permissions) - { - for(size_t t=0; tsetVisible(false); - } - - for(size_t t=0; tsetVisible(false); - } - return; - } - else // Make sure perms and sale ui elements are visible - { - for(size_t t=0; tsetVisible(true); - } - } - - /////////////////////// - // OWNER PERMISSIONS // - /////////////////////// - - U32 base_mask = perm.getMaskBase(); - U32 owner_mask = perm.getMaskOwner(); - U32 group_mask = perm.getMaskGroup(); - U32 everyone_mask = perm.getMaskEveryone(); - U32 next_owner_mask = perm.getMaskNextOwner(); - - getChildView("CheckOwnerModify")->setEnabled(false); - getChild("CheckOwnerModify")->setValue(LLSD((bool)(owner_mask & PERM_MODIFY))); - getChildView("CheckOwnerCopy")->setEnabled(false); - getChild("CheckOwnerCopy")->setValue(LLSD((bool)(owner_mask & PERM_COPY))); - getChildView("CheckOwnerTransfer")->setEnabled(false); - getChild("CheckOwnerTransfer")->setValue(LLSD((bool)(owner_mask & PERM_TRANSFER))); - - /////////////////////// - // DEBUG PERMISSIONS // - /////////////////////// - - if( gSavedSettings.getBOOL("DebugPermissions") ) - { - childSetVisible("layout_debug_permissions", true); - - bool slam_perm = false; - bool overwrite_group = false; - bool overwrite_everyone = false; - - if (item->getType() == LLAssetType::AT_OBJECT) - { - U32 flags = item->getFlags(); - slam_perm = flags & LLInventoryItemFlags::II_FLAGS_OBJECT_SLAM_PERM; - overwrite_everyone = flags & LLInventoryItemFlags::II_FLAGS_OBJECT_PERM_OVERWRITE_EVERYONE; - overwrite_group = flags & LLInventoryItemFlags::II_FLAGS_OBJECT_PERM_OVERWRITE_GROUP; - } - - std::string perm_string; - - perm_string = "B: "; - perm_string += mask_to_string(base_mask); - getChild("BaseMaskDebug")->setValue(perm_string); - - perm_string = "O: "; - perm_string += mask_to_string(owner_mask); - getChild("OwnerMaskDebug")->setValue(perm_string); - - perm_string = "G"; - perm_string += overwrite_group ? "*: " : ": "; - perm_string += mask_to_string(group_mask); - getChild("GroupMaskDebug")->setValue(perm_string); - - perm_string = "E"; - perm_string += overwrite_everyone ? "*: " : ": "; - perm_string += mask_to_string(everyone_mask); - getChild("EveryoneMaskDebug")->setValue(perm_string); - - perm_string = "N"; - perm_string += slam_perm ? "*: " : ": "; - perm_string += mask_to_string(next_owner_mask); - getChild("NextMaskDebug")->setValue(perm_string); - } - else - { - childSetVisible("layout_debug_permissions", false); - } - - ///////////// - // SHARING // - ///////////// - - // Check for ability to change values. - if (is_link || cannot_restrict_permissions) - { - getChildView("CheckShareWithGroup")->setEnabled(false); - getChildView("CheckEveryoneCopy")->setEnabled(false); - } - else if (is_obj_modify && can_agent_manipulate) - { - getChildView("CheckShareWithGroup")->setEnabled(true); - getChildView("CheckEveryoneCopy")->setEnabled((owner_mask & PERM_COPY) && (owner_mask & PERM_TRANSFER)); - } - else - { - getChildView("CheckShareWithGroup")->setEnabled(false); - getChildView("CheckEveryoneCopy")->setEnabled(false); - } - - // Set values. - bool is_group_copy = group_mask & PERM_COPY; - bool is_group_modify = group_mask & PERM_MODIFY; - bool is_group_move = group_mask & PERM_MOVE; - - if (is_group_copy && is_group_modify && is_group_move) - { - getChild("CheckShareWithGroup")->setValue(LLSD((bool)true)); - if (LLCheckBoxCtrl* ctl = getChild("CheckShareWithGroup")) - { - ctl->setTentative(false); - } - } - else if (!is_group_copy && !is_group_modify && !is_group_move) - { - getChild("CheckShareWithGroup")->setValue(LLSD((bool)false)); - if (LLCheckBoxCtrl* ctl = getChild("CheckShareWithGroup")) - { - ctl->setTentative(false); - } - } - else - { - if (LLCheckBoxCtrl* ctl = getChild("CheckShareWithGroup")) - { - ctl->setTentative(!ctl->getEnabled()); - ctl->set(true); - } - } - - getChild("CheckEveryoneCopy")->setValue(LLSD((bool)(everyone_mask & PERM_COPY))); - - /////////////// - // SALE INFO // - /////////////// - - const LLSaleInfo& sale_info = item->getSaleInfo(); - bool is_for_sale = sale_info.isForSale(); - LLComboBox* combo_sale_type = getChild("ComboBoxSaleType"); - LLUICtrl* edit_cost = getChild("Edit Cost"); - - // Check for ability to change values. - if (is_obj_modify && can_agent_sell - && gAgent.allowOperation(PERM_TRANSFER, perm, GP_OBJECT_MANIPULATE)) - { - getChildView("CheckPurchase")->setEnabled(is_complete); - - getChildView("NextOwnerLabel")->setEnabled(true); - getChildView("CheckNextOwnerModify")->setEnabled((base_mask & PERM_MODIFY) && !cannot_restrict_permissions); - getChildView("CheckNextOwnerCopy")->setEnabled((base_mask & PERM_COPY) && !cannot_restrict_permissions && !is_settings); - getChildView("CheckNextOwnerTransfer")->setEnabled((next_owner_mask & PERM_COPY) && !cannot_restrict_permissions); - - combo_sale_type->setEnabled(is_complete && is_for_sale); - edit_cost->setEnabled(is_complete && is_for_sale); - } - else - { - getChildView("CheckPurchase")->setEnabled(false); - - getChildView("NextOwnerLabel")->setEnabled(false); - getChildView("CheckNextOwnerModify")->setEnabled(false); - getChildView("CheckNextOwnerCopy")->setEnabled(false); - getChildView("CheckNextOwnerTransfer")->setEnabled(false); - - combo_sale_type->setEnabled(false); - edit_cost->setEnabled(false); - } - - // Hide any properties that are not relevant to settings - if (is_settings) - { - getChild("GroupLabel")->setEnabled(false); - getChild("GroupLabel")->setVisible(false); - getChild("CheckShareWithGroup")->setEnabled(false); - getChild("CheckShareWithGroup")->setVisible(false); - getChild("AnyoneLabel")->setEnabled(false); - getChild("AnyoneLabel")->setVisible(false); - getChild("CheckEveryoneCopy")->setEnabled(false); - getChild("CheckEveryoneCopy")->setVisible(false); - getChild("CheckPurchase")->setEnabled(false); - getChild("CheckPurchase")->setVisible(false); - getChild("ComboBoxSaleType")->setEnabled(false); - getChild("ComboBoxSaleType")->setVisible(false); - getChild("Edit Cost")->setEnabled(false); - getChild("Edit Cost")->setVisible(false); - } - - // Set values. - getChild("CheckPurchase")->setValue(is_for_sale); - getChild("CheckNextOwnerModify")->setValue(LLSD(bool(next_owner_mask & PERM_MODIFY))); - getChild("CheckNextOwnerCopy")->setValue(LLSD(bool(next_owner_mask & PERM_COPY))); - getChild("CheckNextOwnerTransfer")->setValue(LLSD(bool(next_owner_mask & PERM_TRANSFER))); - - if (is_for_sale) - { - S32 numerical_price; - numerical_price = sale_info.getSalePrice(); - edit_cost->setValue(llformat("%d",numerical_price)); - combo_sale_type->setValue(sale_info.getSaleType()); - } - else - { - edit_cost->setValue(llformat("%d",0)); - combo_sale_type->setValue(LLSaleInfo::FS_COPY); - } -} - -void LLSidepanelItemInfo::updateCreatorName(const LLUUID& creator_id, const LLAvatarName& creator_name, const LLStyle::Params& style_params) -{ - if (mCreatorCacheConnection.connected()) - { - mCreatorCacheConnection.disconnect(); - } - std::string name = creator_name.getCompleteName(); - mLabelCreatorName->setText(name, style_params); -} - -void LLSidepanelItemInfo::updateOwnerName(const LLUUID& owner_id, const LLAvatarName& owner_name, const LLStyle::Params& style_params) -{ - if (mOwnerCacheConnection.connected()) - { - mOwnerCacheConnection.disconnect(); - } - std::string name = owner_name.getCompleteName(); - mLabelOwnerName->setText(name, style_params); -} - -void LLSidepanelItemInfo::changed(U32 mask) -{ - const LLUUID& item_id = getItemID(); - if (getObjectID().notNull() || item_id.isNull()) - { - // Task inventory or not set up yet - return; - } - - const std::set& mChangedItemIDs = gInventory.getChangedIDs(); - std::set::const_iterator it; - - for (it = mChangedItemIDs.begin(); it != mChangedItemIDs.end(); it++) - { - // set dirty for 'item profile panel' only if changed item is the item for which 'item profile panel' is shown (STORM-288) - if (*it == item_id) - { - // if there's a change we're interested in. - if((mask & (LLInventoryObserver::LABEL | LLInventoryObserver::INTERNAL | LLInventoryObserver::REMOVE)) != 0) - { - dirty(); - } - } - } -} - -void LLSidepanelItemInfo::dirty() -{ - mIsDirty = true; -} - -// static -void LLSidepanelItemInfo::onIdle( void* user_data ) -{ - LLSidepanelItemInfo* self = reinterpret_cast(user_data); - - if( self->mIsDirty ) - { - self->refresh(); - self->mIsDirty = false; - } -} - -void LLSidepanelItemInfo::setAssociatedExperience( LLHandle hInfo, const LLSD& experience ) -{ - LLSidepanelItemInfo* info = hInfo.get(); - if(info) - { - LLUUID id; - if(experience.has(LLExperienceCache::EXPERIENCE_ID)) - { - id=experience[LLExperienceCache::EXPERIENCE_ID].asUUID(); - } - if(id.notNull()) - { - info->getChild("LabelItemExperience")->setText(LLSLURL("experience", id, "profile").getSLURLString()); - } - else - { - info->getChild("LabelItemExperience")->setText(LLTrans::getString("ExperienceNameNull")); - } - } -} - - -void LLSidepanelItemInfo::startObjectInventoryObserver() -{ - if (!mObjectInventoryObserver) - { - stopObjectInventoryObserver(); - - // Previous object observer should be removed before starting to observe a new object. - llassert(mObjectInventoryObserver == NULL); - } - - if (mObjectID.isNull()) - { - LL_WARNS() << "Empty object id passed to inventory observer" << LL_ENDL; - return; - } - - LLViewerObject* object = gObjectList.findObject(mObjectID); - - mObjectInventoryObserver = new LLObjectInventoryObserver(this, object); -} - -void LLSidepanelItemInfo::stopObjectInventoryObserver() -{ - delete mObjectInventoryObserver; - mObjectInventoryObserver = NULL; -} - -void LLSidepanelItemInfo::setPropertiesFieldsEnabled(bool enabled) -{ - const std::string fields[] = { - "CheckOwnerModify", - "CheckOwnerCopy", - "CheckOwnerTransfer", - "CheckShareWithGroup", - "CheckEveryoneCopy", - "CheckNextOwnerModify", - "CheckNextOwnerCopy", - "CheckNextOwnerTransfer", - "CheckPurchase", - "Edit Cost" - }; - for (size_t t = 0; tsetEnabled(false); - } -} - -void LLSidepanelItemInfo::onClickCreator() -{ - LLViewerInventoryItem* item = findItem(); - if(!item) return; - if(!item->getCreatorUUID().isNull()) - { - LLAvatarActions::showProfile(item->getCreatorUUID()); - } -} - -// static -void LLSidepanelItemInfo::onClickOwner() -{ - LLViewerInventoryItem* item = findItem(); - if(!item) return; - if(item->getPermissions().isGroupOwned()) - { - LLGroupActions::show(item->getPermissions().getGroup()); - } - else - { - LLAvatarActions::showProfile(item->getPermissions().getOwner()); - } -} - -// static -void LLSidepanelItemInfo::onCommitName() -{ - //LL_INFOS() << "LLSidepanelItemInfo::onCommitName()" << LL_ENDL; - LLViewerInventoryItem* item = findItem(); - if(!item) - { - return; - } - LLLineEditor* labelItemName = getChild("LabelItemName"); - - if(labelItemName&& - (item->getName() != labelItemName->getText()) && - (gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), GP_OBJECT_MANIPULATE)) ) - { - LLPointer new_item = new LLViewerInventoryItem(item); - new_item->rename(labelItemName->getText()); - onCommitChanges(new_item); - } -} - -void LLSidepanelItemInfo::onCommitDescription() -{ - //LL_INFOS() << "LLSidepanelItemInfo::onCommitDescription()" << LL_ENDL; - LLViewerInventoryItem* item = findItem(); - if(!item) return; - - LLTextEditor* labelItemDesc = getChild("LabelItemDesc"); - if(!labelItemDesc) - { - return; - } - if((item->getDescription() != labelItemDesc->getText()) && - (gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), GP_OBJECT_MANIPULATE))) - { - LLPointer new_item = new LLViewerInventoryItem(item); - - new_item->setDescription(labelItemDesc->getText()); - onCommitChanges(new_item); - } -} - -void LLSidepanelItemInfo::onCommitPermissions(LLUICtrl* ctrl) -{ - if (ctrl) - { - // will be enabled by response from server - ctrl->setEnabled(false); - } - updatePermissions(); -} - -void LLSidepanelItemInfo::updatePermissions() -{ - LLViewerInventoryItem* item = findItem(); - if(!item) return; - - bool is_group_owned; - LLUUID owner_id; - LLUUID group_id; - LLPermissions perm(item->getPermissions()); - perm.getOwnership(owner_id, is_group_owned); - - if (is_group_owned && gAgent.hasPowerInGroup(owner_id, GP_OBJECT_MANIPULATE)) - { - group_id = owner_id; - } - - LLCheckBoxCtrl* CheckShareWithGroup = getChild("CheckShareWithGroup"); - - if(CheckShareWithGroup) - { - perm.setGroupBits(gAgent.getID(), group_id, - CheckShareWithGroup->get(), - PERM_MODIFY | PERM_MOVE | PERM_COPY); - } - LLCheckBoxCtrl* CheckEveryoneCopy = getChild("CheckEveryoneCopy"); - if(CheckEveryoneCopy) - { - perm.setEveryoneBits(gAgent.getID(), group_id, - CheckEveryoneCopy->get(), PERM_COPY); - } - - LLCheckBoxCtrl* CheckNextOwnerModify = getChild("CheckNextOwnerModify"); - if(CheckNextOwnerModify) - { - perm.setNextOwnerBits(gAgent.getID(), group_id, - CheckNextOwnerModify->get(), PERM_MODIFY); - } - LLCheckBoxCtrl* CheckNextOwnerCopy = getChild("CheckNextOwnerCopy"); - if(CheckNextOwnerCopy) - { - perm.setNextOwnerBits(gAgent.getID(), group_id, - CheckNextOwnerCopy->get(), PERM_COPY); - } - LLCheckBoxCtrl* CheckNextOwnerTransfer = getChild("CheckNextOwnerTransfer"); - if(CheckNextOwnerTransfer) - { - perm.setNextOwnerBits(gAgent.getID(), group_id, - CheckNextOwnerTransfer->get(), PERM_TRANSFER); - } - if(perm != item->getPermissions() - && item->isFinished()) - { - LLPointer new_item = new LLViewerInventoryItem(item); - new_item->setPermissions(perm); - U32 flags = new_item->getFlags(); - // If next owner permissions have changed (and this is an object) - // then set the slam permissions flag so that they are applied on rez. - if((perm.getMaskNextOwner()!=item->getPermissions().getMaskNextOwner()) - && (item->getType() == LLAssetType::AT_OBJECT)) - { - flags |= LLInventoryItemFlags::II_FLAGS_OBJECT_SLAM_PERM; - } - // If everyone permissions have changed (and this is an object) - // then set the overwrite everyone permissions flag so they - // are applied on rez. - if ((perm.getMaskEveryone()!=item->getPermissions().getMaskEveryone()) - && (item->getType() == LLAssetType::AT_OBJECT)) - { - flags |= LLInventoryItemFlags::II_FLAGS_OBJECT_PERM_OVERWRITE_EVERYONE; - } - // If group permissions have changed (and this is an object) - // then set the overwrite group permissions flag so they - // are applied on rez. - if ((perm.getMaskGroup()!=item->getPermissions().getMaskGroup()) - && (item->getType() == LLAssetType::AT_OBJECT)) - { - flags |= LLInventoryItemFlags::II_FLAGS_OBJECT_PERM_OVERWRITE_GROUP; - } - new_item->setFlags(flags); - onCommitChanges(new_item); - } - else - { - // need to make sure we don't just follow the click - refresh(); - } -} - -void LLSidepanelItemInfo::onEditThumbnail() -{ - LLSD data; - data["task_id"] = mObjectID; - data["item_id"] = mItemID; - LLFloaterReg::showInstance("change_item_thumbnail", data); -} - -void LLSidepanelItemInfo::onCommitSaleInfo(LLUICtrl* ctrl) -{ - if (ctrl) - { - // will be enabled by response from server - ctrl->setEnabled(false); - } - //LL_INFOS() << "LLSidepanelItemInfo::onCommitSaleInfo()" << LL_ENDL; - updateSaleInfo(); -} - -void LLSidepanelItemInfo::updateSaleInfo() -{ - LLViewerInventoryItem* item = findItem(); - if(!item) return; - LLSaleInfo sale_info(item->getSaleInfo()); - if(!gAgent.allowOperation(PERM_TRANSFER, item->getPermissions(), GP_OBJECT_SET_SALE)) - { - getChild("CheckPurchase")->setValue(LLSD((bool)false)); - } - - if((bool)getChild("CheckPurchase")->getValue()) - { - // turn on sale info - LLSaleInfo::EForSale sale_type = LLSaleInfo::FS_COPY; - - LLComboBox* combo_sale_type = getChild("ComboBoxSaleType"); - if (combo_sale_type) - { - sale_type = static_cast(combo_sale_type->getValue().asInteger()); - } - - if (sale_type == LLSaleInfo::FS_COPY - && !gAgent.allowOperation(PERM_COPY, item->getPermissions(), - GP_OBJECT_SET_SALE)) - { - sale_type = LLSaleInfo::FS_ORIGINAL; - } - - - - S32 price = -1; - price = getChild("Edit Cost")->getValue().asInteger();; - - // Invalid data - turn off the sale - if (price < 0) - { - sale_type = LLSaleInfo::FS_NOT; - price = 0; - } - - sale_info.setSaleType(sale_type); - sale_info.setSalePrice(price); - } - else - { - sale_info.setSaleType(LLSaleInfo::FS_NOT); - } - if(sale_info != item->getSaleInfo() - && item->isFinished()) - { - LLPointer new_item = new LLViewerInventoryItem(item); - - // Force an update on the sale price at rez - if (item->getType() == LLAssetType::AT_OBJECT) - { - U32 flags = new_item->getFlags(); - flags |= LLInventoryItemFlags::II_FLAGS_OBJECT_SLAM_SALE; - new_item->setFlags(flags); - } - - new_item->setSaleInfo(sale_info); - onCommitChanges(new_item); - } - else - { - // need to make sure we don't just follow the click - refresh(); - } -} - -void LLSidepanelItemInfo::onCommitChanges(LLPointer item) -{ - if (item.isNull()) - { - return; - } - - if (mObjectID.isNull()) - { - // This is in the agent's inventory. - // Mark update as pending and wait only for most recent one in case user requested for couple - // Once update arrives or any of ids change drop pending id. - mUpdatePendingId++; - LLPointer callback = new PropertiesChangedCallback(getHandle(), mItemID, mUpdatePendingId); - update_inventory_item(item.get(), callback); - //item->updateServer(false); - gInventory.updateItem(item); - gInventory.notifyObservers(); - } - else - { - // This is in an object's contents. - LLViewerObject* object = gObjectList.findObject(mObjectID); - if (object) - { - object->updateInventory( - item, - TASK_INVENTORY_ITEM_KEY, - false); - - if (object->isSelected()) - { - // Since object is selected (build floater is open) object will - // receive properties update, detect serial mismatch, dirty and - // reload inventory, meanwhile some other updates will refresh it. - // So mark dirty early, this will prevent unnecessary changes - // and download will be triggered by LLPanelObjectInventory - it - // prevents flashing in content tab and some duplicated request. - object->dirtyInventory(); - } - setPropertiesFieldsEnabled(false); - } - } -} - -LLViewerInventoryItem* LLSidepanelItemInfo::findItem() const -{ - LLViewerInventoryItem* item = NULL; - if(mObjectID.isNull()) - { - // it is in agent inventory - item = gInventory.getItem(mItemID); - } - else - { - LLViewerObject* object = gObjectList.findObject(mObjectID); - if(object) - { - item = static_cast(object->getInventoryObject(mItemID)); - } - } - return item; -} - -// virtual -void LLSidepanelItemInfo::save() -{ - onCommitName(); - onCommitDescription(); - updatePermissions(); - updateSaleInfo(); -} +/** + * @file llsidepaneliteminfo.cpp + * @brief A floater which shows an inventory item's properties. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llsidepaneliteminfo.h" + +#include "roles_constants.h" + +#include "llagent.h" +#include "llavataractions.h" +#include "llavatarnamecache.h" +#include "llbutton.h" +#include "llcallbacklist.h" +#include "llcombobox.h" +#include "llfloater.h" +#include "llfloaterreg.h" +#include "llgroupactions.h" +#include "llgroupmgr.h" +#include "lliconctrl.h" +#include "llinventorydefines.h" +#include "llinventoryicon.h" +#include "llinventorymodel.h" +#include "llinventoryobserver.h" +#include "lllineeditor.h" +#include "llradiogroup.h" +#include "llslurl.h" +#include "lltexteditor.h" +#include "llviewercontrol.h" +#include "llviewerinventory.h" +#include "llviewerobjectlist.h" +#include "llexperiencecache.h" +#include "lltrans.h" +#include "llviewerregion.h" + + +class PropertiesChangedCallback : public LLInventoryCallback +{ +public: + PropertiesChangedCallback(LLHandle sidepanel_handle, LLUUID &item_id, S32 id) + : mHandle(sidepanel_handle), mItemId(item_id), mId(id) + {} + + void fire(const LLUUID &inv_item) + { + // inv_item can be null for some reason + LLSidepanelItemInfo* sidepanel = dynamic_cast(mHandle.get()); + if (sidepanel) + { + // sidepanel waits only for most recent update + sidepanel->onUpdateCallback(mItemId, mId); + } + } +private: + LLHandle mHandle; + LLUUID mItemId; + S32 mId; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLObjectInventoryObserver +// +// Helper class to watch for changes in an object inventory. +// Used to update item properties in LLSidepanelItemInfo. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLObjectInventoryObserver : public LLVOInventoryListener +{ +public: + LLObjectInventoryObserver(LLSidepanelItemInfo* floater, LLViewerObject* object) + : mFloater(floater) + { + registerVOInventoryListener(object, NULL); + } + virtual ~LLObjectInventoryObserver() + { + removeVOInventoryListener(); + } + /*virtual*/ void inventoryChanged(LLViewerObject* object, + LLInventoryObject::object_list_t* inventory, + S32 serial_num, + void* user_data); +private: + LLSidepanelItemInfo* mFloater; // Not a handle because LLSidepanelItemInfo is managing LLObjectInventoryObserver +}; + +/*virtual*/ +void LLObjectInventoryObserver::inventoryChanged(LLViewerObject* object, + LLInventoryObject::object_list_t* inventory, + S32 serial_num, + void* user_data) +{ + mFloater->dirty(); +} + +///---------------------------------------------------------------------------- +/// Class LLSidepanelItemInfo +///---------------------------------------------------------------------------- + +static LLPanelInjector t_item_info("sidepanel_item_info"); + +// Default constructor +LLSidepanelItemInfo::LLSidepanelItemInfo(const LLPanel::Params& p) + : LLPanel(p) + , mItemID(LLUUID::null) + , mObjectInventoryObserver(NULL) + , mUpdatePendingId(-1) + , mIsDirty(false) /*Not ready*/ + , mParentFloater(NULL) +{ + gInventory.addObserver(this); + gIdleCallbacks.addFunction(&LLSidepanelItemInfo::onIdle, (void*)this); +} + +// Destroys the object +LLSidepanelItemInfo::~LLSidepanelItemInfo() +{ + gInventory.removeObserver(this); + gIdleCallbacks.deleteFunction(&LLSidepanelItemInfo::onIdle, (void*)this); + + stopObjectInventoryObserver(); + + if (mOwnerCacheConnection.connected()) + { + mOwnerCacheConnection.disconnect(); + } + if (mCreatorCacheConnection.connected()) + { + mCreatorCacheConnection.disconnect(); + } +} + +// virtual +bool LLSidepanelItemInfo::postBuild() +{ + mChangeThumbnailBtn = getChild("change_thumbnail_btn"); + mItemTypeIcon = getChild("item_type_icon"); + mLabelOwnerName = getChild("LabelOwnerName"); + mLabelCreatorName = getChild("LabelCreatorName"); + + getChild("LabelItemName")->setPrevalidate(&LLTextValidate::validateASCIIPrintableNoPipe); + getChild("LabelItemName")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitName,this)); + getChild("LabelItemDesc")->setCommitCallback(boost::bind(&LLSidepanelItemInfo:: onCommitDescription, this)); + // Thumnail edition + mChangeThumbnailBtn->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onEditThumbnail, this)); + // acquired date + // owner permissions + // Permissions debug text + // group permissions + getChild("CheckShareWithGroup")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitPermissions, this, _1)); + // everyone permissions + getChild("CheckEveryoneCopy")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitPermissions, this, _1)); + // next owner permissions + getChild("CheckNextOwnerModify")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitPermissions, this, _1)); + getChild("CheckNextOwnerCopy")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitPermissions, this, _1)); + getChild("CheckNextOwnerTransfer")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitPermissions, this, _1)); + // Mark for sale or not, and sale info + getChild("CheckPurchase")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitSaleInfo, this, _1)); + // Change sale type, and sale info + getChild("ComboBoxSaleType")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitSaleInfo, this, _1)); + // "Price" label for edit + getChild("Edit Cost")->setCommitCallback(boost::bind(&LLSidepanelItemInfo::onCommitSaleInfo, this, _1)); + refresh(); + return true; +} + +void LLSidepanelItemInfo::setObjectID(const LLUUID& object_id) +{ + mObjectID = object_id; + + // Start monitoring changes in the object inventory to update + // selected inventory item properties in Item Profile panel. See STORM-148. + startObjectInventoryObserver(); + mUpdatePendingId = -1; +} + +void LLSidepanelItemInfo::setItemID(const LLUUID& item_id) +{ + if (mItemID != item_id) + { + mItemID = item_id; + mUpdatePendingId = -1; + } + dirty(); +} + +void LLSidepanelItemInfo::setParentFloater(LLFloater* parent) +{ + mParentFloater = parent; +} + +const LLUUID& LLSidepanelItemInfo::getObjectID() const +{ + return mObjectID; +} + +const LLUUID& LLSidepanelItemInfo::getItemID() const +{ + return mItemID; +} + +void LLSidepanelItemInfo::onUpdateCallback(const LLUUID& item_id, S32 received_update_id) +{ + if (mItemID == item_id && mUpdatePendingId == received_update_id) + { + mUpdatePendingId = -1; + refresh(); + } +} + +void LLSidepanelItemInfo::reset() +{ + mObjectID = LLUUID::null; + mItemID = LLUUID::null; + + stopObjectInventoryObserver(); + dirty(); +} + +void LLSidepanelItemInfo::refresh() +{ + LLViewerInventoryItem* item = findItem(); + if(item) + { + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + bool in_trash = (item->getUUID() == trash_id) || gInventory.isObjectDescendentOf(item->getUUID(), trash_id); + if (in_trash && mParentFloater) + { + // Close properties when moving to trash + // Aren't supposed to view properties from trash + mParentFloater->closeFloater(); + } + else + { + refreshFromItem(item); + } + return; + } + + if (mObjectID.notNull()) + { + LLViewerObject* object = gObjectList.findObject(mObjectID); + if (object) + { + // Object exists, but object's content is not nessesary + // loaded, so assume item exists as well + return; + } + } + + if (mParentFloater) + { + // if we failed to get item, it likely no longer exists + mParentFloater->closeFloater(); + } +} + +void LLSidepanelItemInfo::refreshFromItem(LLViewerInventoryItem* item) +{ + //////////////////////// + // PERMISSIONS LOOKUP // + //////////////////////// + + llassert(item); + if (!item) return; + + if (mUpdatePendingId != -1) + { + return; + } + + // do not enable the UI for incomplete items. + bool is_complete = item->isFinished(); + const bool cannot_restrict_permissions = LLInventoryType::cannotRestrictPermissions(item->getInventoryType()); + const bool is_calling_card = (item->getInventoryType() == LLInventoryType::IT_CALLINGCARD); + const bool is_settings = (item->getInventoryType() == LLInventoryType::IT_SETTINGS); + const LLPermissions& perm = item->getPermissions(); + const bool can_agent_manipulate = gAgent.allowOperation(PERM_OWNER, perm, + GP_OBJECT_MANIPULATE); + const bool can_agent_sell = gAgent.allowOperation(PERM_OWNER, perm, + GP_OBJECT_SET_SALE) && + !cannot_restrict_permissions; + const bool is_link = item->getIsLinkType(); + + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + bool not_in_trash = (item->getUUID() != trash_id) && !gInventory.isObjectDescendentOf(item->getUUID(), trash_id); + + // You need permission to modify the object to modify an inventory + // item in it. + LLViewerObject* object = NULL; + if(!mObjectID.isNull()) object = gObjectList.findObject(mObjectID); + bool is_obj_modify = true; + if(object) + { + is_obj_modify = object->permOwnerModify(); + } + + if(item->getInventoryType() == LLInventoryType::IT_LSL) + { + getChildView("LabelItemExperienceTitle")->setVisible(true); + LLTextBox* tb = getChild("LabelItemExperience"); + tb->setText(getString("loading_experience")); + tb->setVisible(true); + std::string url = std::string(); + if(object && object->getRegion()) + { + url = object->getRegion()->getCapability("GetMetadata"); + } + LLExperienceCache::instance().fetchAssociatedExperience(item->getParentUUID(), item->getUUID(), url, + boost::bind(&LLSidepanelItemInfo::setAssociatedExperience, getDerivedHandle(), _1)); + } + + ////////////////////// + // ITEM NAME & DESC // + ////////////////////// + bool is_modifiable = gAgent.allowOperation(PERM_MODIFY, perm, + GP_OBJECT_MANIPULATE) + && is_obj_modify && is_complete && not_in_trash; + + getChildView("LabelItemNameTitle")->setEnabled(true); + getChildView("LabelItemName")->setEnabled(is_modifiable && !is_calling_card); // for now, don't allow rename of calling cards + getChild("LabelItemName")->setValue(item->getName()); + getChildView("LabelItemDescTitle")->setEnabled(true); + getChildView("LabelItemDesc")->setEnabled(is_modifiable); + getChild("LabelItemDesc")->setValue(item->getDescription()); + getChild("item_thumbnail")->setValue(item->getThumbnailUUID()); + + LLUIImagePtr icon_img = LLInventoryIcon::getIcon(item->getType(), item->getInventoryType(), item->getFlags(), false); + mItemTypeIcon->setImage(icon_img); + + // Style for creator and owner links + LLStyle::Params style_params; + LLColor4 link_color = LLUIColorTable::instance().getColor("HTMLLinkColor"); + style_params.color = link_color; + style_params.readonly_color = link_color; + style_params.is_link = true; // link will be added later + const LLFontGL* fontp = mLabelCreatorName->getFont(); + style_params.font.name = LLFontGL::nameFromFont(fontp); + style_params.font.size = LLFontGL::sizeFromFont(fontp); + style_params.font.style = "UNDERLINE"; + + ////////////////// + // CREATOR NAME // + ////////////////// + if(!gCacheName) return; + if(!gAgent.getRegion()) return; + + if (item->getCreatorUUID().notNull()) + { + LLUUID creator_id = item->getCreatorUUID(); + std::string slurl = + LLSLURL("agent", creator_id, "inspect").getSLURLString(); + + style_params.link_href = slurl; + + LLAvatarName av_name; + if (LLAvatarNameCache::get(creator_id, &av_name)) + { + updateCreatorName(creator_id, av_name, style_params); + } + else + { + if (mCreatorCacheConnection.connected()) + { + mCreatorCacheConnection.disconnect(); + } + mLabelCreatorName->setText(LLTrans::getString("None")); + mCreatorCacheConnection = LLAvatarNameCache::get(creator_id, boost::bind(&LLSidepanelItemInfo::updateCreatorName, this, _1, _2, style_params)); + } + + getChildView("LabelCreatorTitle")->setEnabled(true); + mLabelCreatorName->setEnabled(true); + } + else + { + getChildView("LabelCreatorTitle")->setEnabled(false); + mLabelCreatorName->setEnabled(false); + mLabelCreatorName->setValue(getString("unknown_multiple")); + } + + //////////////// + // OWNER NAME // + //////////////// + if(perm.isOwned()) + { + std::string slurl; + if (perm.isGroupOwned()) + { + LLGroupMgrGroupData* group_data = LLGroupMgr::getInstance()->getGroupData(perm.getGroup()); + + slurl = LLSLURL("group", perm.getGroup(), "inspect").getSLURLString(); + style_params.link_href = slurl; + if (group_data && group_data->isGroupPropertiesDataComplete()) + { + mLabelOwnerName->setText(group_data->mName, style_params); + } + else + { + // Triggers refresh + LLGroupMgr::getInstance()->sendGroupPropertiesRequest(perm.getGroup()); + + std::string name; + gCacheName->getGroupName(perm.getGroup(), name); + mLabelOwnerName->setText(name, style_params); + } + } + else + { + LLUUID owner_id = perm.getOwner(); + slurl = LLSLURL("agent", owner_id, "inspect").getSLURLString(); + + style_params.link_href = slurl; + LLAvatarName av_name; + if (LLAvatarNameCache::get(owner_id, &av_name)) + { + updateOwnerName(owner_id, av_name, style_params); + } + else + { + if (mOwnerCacheConnection.connected()) + { + mOwnerCacheConnection.disconnect(); + } + mLabelOwnerName->setText(LLTrans::getString("None")); + mOwnerCacheConnection = LLAvatarNameCache::get(owner_id, boost::bind(&LLSidepanelItemInfo::updateOwnerName, this, _1, _2, style_params)); + } + } + getChildView("LabelOwnerTitle")->setEnabled(true); + mLabelOwnerName->setEnabled(true); + } + else + { + getChildView("LabelOwnerTitle")->setEnabled(false); + mLabelOwnerName->setEnabled(false); + mLabelOwnerName->setValue(getString("public")); + } + + // Not yet supported for task inventories + mChangeThumbnailBtn->setEnabled(mObjectID.isNull() && ALEXANDRIA_LINDEN_ID != perm.getOwner()); + + //////////// + // ORIGIN // + //////////// + + if (object) + { + getChild("origin")->setValue(getString("origin_inworld")); + } + else + { + getChild("origin")->setValue(getString("origin_inventory")); + } + + ////////////////// + // ACQUIRE DATE // + ////////////////// + + time_t time_utc = item->getCreationDate(); + if (0 == time_utc) + { + getChild("LabelAcquiredDate")->setValue(getString("unknown")); + } + else + { + std::string timeStr = getString("acquiredDate"); + LLSD substitution; + substitution["datetime"] = (S32) time_utc; + LLStringUtil::format (timeStr, substitution); + getChild("LabelAcquiredDate")->setValue(timeStr); + } + + ////////////////////////////////////// + // PERMISSIONS AND SALE ITEM HIDING // + ////////////////////////////////////// + + const std::string perm_and_sale_items[]={ + "perms_inv", + "perm_modify", + "CheckOwnerModify", + "CheckOwnerCopy", + "CheckOwnerTransfer", + "GroupLabel", + "CheckShareWithGroup", + "AnyoneLabel", + "CheckEveryoneCopy", + "NextOwnerLabel", + "CheckNextOwnerModify", + "CheckNextOwnerCopy", + "CheckNextOwnerTransfer", + "CheckPurchase", + "ComboBoxSaleType", + "Edit Cost" + }; + + const std::string debug_items[]={ + "BaseMaskDebug", + "OwnerMaskDebug", + "GroupMaskDebug", + "EveryoneMaskDebug", + "NextMaskDebug" + }; + + // Hide permissions checkboxes and labels and for sale info if in the trash + // or ui elements don't apply to these objects and return from function + if (!not_in_trash || cannot_restrict_permissions) + { + for(size_t t=0; tsetVisible(false); + } + + for(size_t t=0; tsetVisible(false); + } + return; + } + else // Make sure perms and sale ui elements are visible + { + for(size_t t=0; tsetVisible(true); + } + } + + /////////////////////// + // OWNER PERMISSIONS // + /////////////////////// + + U32 base_mask = perm.getMaskBase(); + U32 owner_mask = perm.getMaskOwner(); + U32 group_mask = perm.getMaskGroup(); + U32 everyone_mask = perm.getMaskEveryone(); + U32 next_owner_mask = perm.getMaskNextOwner(); + + getChildView("CheckOwnerModify")->setEnabled(false); + getChild("CheckOwnerModify")->setValue(LLSD((bool)(owner_mask & PERM_MODIFY))); + getChildView("CheckOwnerCopy")->setEnabled(false); + getChild("CheckOwnerCopy")->setValue(LLSD((bool)(owner_mask & PERM_COPY))); + getChildView("CheckOwnerTransfer")->setEnabled(false); + getChild("CheckOwnerTransfer")->setValue(LLSD((bool)(owner_mask & PERM_TRANSFER))); + + /////////////////////// + // DEBUG PERMISSIONS // + /////////////////////// + + if( gSavedSettings.getBOOL("DebugPermissions") ) + { + childSetVisible("layout_debug_permissions", true); + + bool slam_perm = false; + bool overwrite_group = false; + bool overwrite_everyone = false; + + if (item->getType() == LLAssetType::AT_OBJECT) + { + U32 flags = item->getFlags(); + slam_perm = flags & LLInventoryItemFlags::II_FLAGS_OBJECT_SLAM_PERM; + overwrite_everyone = flags & LLInventoryItemFlags::II_FLAGS_OBJECT_PERM_OVERWRITE_EVERYONE; + overwrite_group = flags & LLInventoryItemFlags::II_FLAGS_OBJECT_PERM_OVERWRITE_GROUP; + } + + std::string perm_string; + + perm_string = "B: "; + perm_string += mask_to_string(base_mask); + getChild("BaseMaskDebug")->setValue(perm_string); + + perm_string = "O: "; + perm_string += mask_to_string(owner_mask); + getChild("OwnerMaskDebug")->setValue(perm_string); + + perm_string = "G"; + perm_string += overwrite_group ? "*: " : ": "; + perm_string += mask_to_string(group_mask); + getChild("GroupMaskDebug")->setValue(perm_string); + + perm_string = "E"; + perm_string += overwrite_everyone ? "*: " : ": "; + perm_string += mask_to_string(everyone_mask); + getChild("EveryoneMaskDebug")->setValue(perm_string); + + perm_string = "N"; + perm_string += slam_perm ? "*: " : ": "; + perm_string += mask_to_string(next_owner_mask); + getChild("NextMaskDebug")->setValue(perm_string); + } + else + { + childSetVisible("layout_debug_permissions", false); + } + + ///////////// + // SHARING // + ///////////// + + // Check for ability to change values. + if (is_link || cannot_restrict_permissions) + { + getChildView("CheckShareWithGroup")->setEnabled(false); + getChildView("CheckEveryoneCopy")->setEnabled(false); + } + else if (is_obj_modify && can_agent_manipulate) + { + getChildView("CheckShareWithGroup")->setEnabled(true); + getChildView("CheckEveryoneCopy")->setEnabled((owner_mask & PERM_COPY) && (owner_mask & PERM_TRANSFER)); + } + else + { + getChildView("CheckShareWithGroup")->setEnabled(false); + getChildView("CheckEveryoneCopy")->setEnabled(false); + } + + // Set values. + bool is_group_copy = group_mask & PERM_COPY; + bool is_group_modify = group_mask & PERM_MODIFY; + bool is_group_move = group_mask & PERM_MOVE; + + if (is_group_copy && is_group_modify && is_group_move) + { + getChild("CheckShareWithGroup")->setValue(LLSD((bool)true)); + if (LLCheckBoxCtrl* ctl = getChild("CheckShareWithGroup")) + { + ctl->setTentative(false); + } + } + else if (!is_group_copy && !is_group_modify && !is_group_move) + { + getChild("CheckShareWithGroup")->setValue(LLSD((bool)false)); + if (LLCheckBoxCtrl* ctl = getChild("CheckShareWithGroup")) + { + ctl->setTentative(false); + } + } + else + { + if (LLCheckBoxCtrl* ctl = getChild("CheckShareWithGroup")) + { + ctl->setTentative(!ctl->getEnabled()); + ctl->set(true); + } + } + + getChild("CheckEveryoneCopy")->setValue(LLSD((bool)(everyone_mask & PERM_COPY))); + + /////////////// + // SALE INFO // + /////////////// + + const LLSaleInfo& sale_info = item->getSaleInfo(); + bool is_for_sale = sale_info.isForSale(); + LLComboBox* combo_sale_type = getChild("ComboBoxSaleType"); + LLUICtrl* edit_cost = getChild("Edit Cost"); + + // Check for ability to change values. + if (is_obj_modify && can_agent_sell + && gAgent.allowOperation(PERM_TRANSFER, perm, GP_OBJECT_MANIPULATE)) + { + getChildView("CheckPurchase")->setEnabled(is_complete); + + getChildView("NextOwnerLabel")->setEnabled(true); + getChildView("CheckNextOwnerModify")->setEnabled((base_mask & PERM_MODIFY) && !cannot_restrict_permissions); + getChildView("CheckNextOwnerCopy")->setEnabled((base_mask & PERM_COPY) && !cannot_restrict_permissions && !is_settings); + getChildView("CheckNextOwnerTransfer")->setEnabled((next_owner_mask & PERM_COPY) && !cannot_restrict_permissions); + + combo_sale_type->setEnabled(is_complete && is_for_sale); + edit_cost->setEnabled(is_complete && is_for_sale); + } + else + { + getChildView("CheckPurchase")->setEnabled(false); + + getChildView("NextOwnerLabel")->setEnabled(false); + getChildView("CheckNextOwnerModify")->setEnabled(false); + getChildView("CheckNextOwnerCopy")->setEnabled(false); + getChildView("CheckNextOwnerTransfer")->setEnabled(false); + + combo_sale_type->setEnabled(false); + edit_cost->setEnabled(false); + } + + // Hide any properties that are not relevant to settings + if (is_settings) + { + getChild("GroupLabel")->setEnabled(false); + getChild("GroupLabel")->setVisible(false); + getChild("CheckShareWithGroup")->setEnabled(false); + getChild("CheckShareWithGroup")->setVisible(false); + getChild("AnyoneLabel")->setEnabled(false); + getChild("AnyoneLabel")->setVisible(false); + getChild("CheckEveryoneCopy")->setEnabled(false); + getChild("CheckEveryoneCopy")->setVisible(false); + getChild("CheckPurchase")->setEnabled(false); + getChild("CheckPurchase")->setVisible(false); + getChild("ComboBoxSaleType")->setEnabled(false); + getChild("ComboBoxSaleType")->setVisible(false); + getChild("Edit Cost")->setEnabled(false); + getChild("Edit Cost")->setVisible(false); + } + + // Set values. + getChild("CheckPurchase")->setValue(is_for_sale); + getChild("CheckNextOwnerModify")->setValue(LLSD(bool(next_owner_mask & PERM_MODIFY))); + getChild("CheckNextOwnerCopy")->setValue(LLSD(bool(next_owner_mask & PERM_COPY))); + getChild("CheckNextOwnerTransfer")->setValue(LLSD(bool(next_owner_mask & PERM_TRANSFER))); + + if (is_for_sale) + { + S32 numerical_price; + numerical_price = sale_info.getSalePrice(); + edit_cost->setValue(llformat("%d",numerical_price)); + combo_sale_type->setValue(sale_info.getSaleType()); + } + else + { + edit_cost->setValue(llformat("%d",0)); + combo_sale_type->setValue(LLSaleInfo::FS_COPY); + } +} + +void LLSidepanelItemInfo::updateCreatorName(const LLUUID& creator_id, const LLAvatarName& creator_name, const LLStyle::Params& style_params) +{ + if (mCreatorCacheConnection.connected()) + { + mCreatorCacheConnection.disconnect(); + } + std::string name = creator_name.getCompleteName(); + mLabelCreatorName->setText(name, style_params); +} + +void LLSidepanelItemInfo::updateOwnerName(const LLUUID& owner_id, const LLAvatarName& owner_name, const LLStyle::Params& style_params) +{ + if (mOwnerCacheConnection.connected()) + { + mOwnerCacheConnection.disconnect(); + } + std::string name = owner_name.getCompleteName(); + mLabelOwnerName->setText(name, style_params); +} + +void LLSidepanelItemInfo::changed(U32 mask) +{ + const LLUUID& item_id = getItemID(); + if (getObjectID().notNull() || item_id.isNull()) + { + // Task inventory or not set up yet + return; + } + + const std::set& mChangedItemIDs = gInventory.getChangedIDs(); + std::set::const_iterator it; + + for (it = mChangedItemIDs.begin(); it != mChangedItemIDs.end(); it++) + { + // set dirty for 'item profile panel' only if changed item is the item for which 'item profile panel' is shown (STORM-288) + if (*it == item_id) + { + // if there's a change we're interested in. + if((mask & (LLInventoryObserver::LABEL | LLInventoryObserver::INTERNAL | LLInventoryObserver::REMOVE)) != 0) + { + dirty(); + } + } + } +} + +void LLSidepanelItemInfo::dirty() +{ + mIsDirty = true; +} + +// static +void LLSidepanelItemInfo::onIdle( void* user_data ) +{ + LLSidepanelItemInfo* self = reinterpret_cast(user_data); + + if( self->mIsDirty ) + { + self->refresh(); + self->mIsDirty = false; + } +} + +void LLSidepanelItemInfo::setAssociatedExperience( LLHandle hInfo, const LLSD& experience ) +{ + LLSidepanelItemInfo* info = hInfo.get(); + if(info) + { + LLUUID id; + if(experience.has(LLExperienceCache::EXPERIENCE_ID)) + { + id=experience[LLExperienceCache::EXPERIENCE_ID].asUUID(); + } + if(id.notNull()) + { + info->getChild("LabelItemExperience")->setText(LLSLURL("experience", id, "profile").getSLURLString()); + } + else + { + info->getChild("LabelItemExperience")->setText(LLTrans::getString("ExperienceNameNull")); + } + } +} + + +void LLSidepanelItemInfo::startObjectInventoryObserver() +{ + if (!mObjectInventoryObserver) + { + stopObjectInventoryObserver(); + + // Previous object observer should be removed before starting to observe a new object. + llassert(mObjectInventoryObserver == NULL); + } + + if (mObjectID.isNull()) + { + LL_WARNS() << "Empty object id passed to inventory observer" << LL_ENDL; + return; + } + + LLViewerObject* object = gObjectList.findObject(mObjectID); + + mObjectInventoryObserver = new LLObjectInventoryObserver(this, object); +} + +void LLSidepanelItemInfo::stopObjectInventoryObserver() +{ + delete mObjectInventoryObserver; + mObjectInventoryObserver = NULL; +} + +void LLSidepanelItemInfo::setPropertiesFieldsEnabled(bool enabled) +{ + const std::string fields[] = { + "CheckOwnerModify", + "CheckOwnerCopy", + "CheckOwnerTransfer", + "CheckShareWithGroup", + "CheckEveryoneCopy", + "CheckNextOwnerModify", + "CheckNextOwnerCopy", + "CheckNextOwnerTransfer", + "CheckPurchase", + "Edit Cost" + }; + for (size_t t = 0; tsetEnabled(false); + } +} + +void LLSidepanelItemInfo::onClickCreator() +{ + LLViewerInventoryItem* item = findItem(); + if(!item) return; + if(!item->getCreatorUUID().isNull()) + { + LLAvatarActions::showProfile(item->getCreatorUUID()); + } +} + +// static +void LLSidepanelItemInfo::onClickOwner() +{ + LLViewerInventoryItem* item = findItem(); + if(!item) return; + if(item->getPermissions().isGroupOwned()) + { + LLGroupActions::show(item->getPermissions().getGroup()); + } + else + { + LLAvatarActions::showProfile(item->getPermissions().getOwner()); + } +} + +// static +void LLSidepanelItemInfo::onCommitName() +{ + //LL_INFOS() << "LLSidepanelItemInfo::onCommitName()" << LL_ENDL; + LLViewerInventoryItem* item = findItem(); + if(!item) + { + return; + } + LLLineEditor* labelItemName = getChild("LabelItemName"); + + if(labelItemName&& + (item->getName() != labelItemName->getText()) && + (gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), GP_OBJECT_MANIPULATE)) ) + { + LLPointer new_item = new LLViewerInventoryItem(item); + new_item->rename(labelItemName->getText()); + onCommitChanges(new_item); + } +} + +void LLSidepanelItemInfo::onCommitDescription() +{ + //LL_INFOS() << "LLSidepanelItemInfo::onCommitDescription()" << LL_ENDL; + LLViewerInventoryItem* item = findItem(); + if(!item) return; + + LLTextEditor* labelItemDesc = getChild("LabelItemDesc"); + if(!labelItemDesc) + { + return; + } + if((item->getDescription() != labelItemDesc->getText()) && + (gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), GP_OBJECT_MANIPULATE))) + { + LLPointer new_item = new LLViewerInventoryItem(item); + + new_item->setDescription(labelItemDesc->getText()); + onCommitChanges(new_item); + } +} + +void LLSidepanelItemInfo::onCommitPermissions(LLUICtrl* ctrl) +{ + if (ctrl) + { + // will be enabled by response from server + ctrl->setEnabled(false); + } + updatePermissions(); +} + +void LLSidepanelItemInfo::updatePermissions() +{ + LLViewerInventoryItem* item = findItem(); + if(!item) return; + + bool is_group_owned; + LLUUID owner_id; + LLUUID group_id; + LLPermissions perm(item->getPermissions()); + perm.getOwnership(owner_id, is_group_owned); + + if (is_group_owned && gAgent.hasPowerInGroup(owner_id, GP_OBJECT_MANIPULATE)) + { + group_id = owner_id; + } + + LLCheckBoxCtrl* CheckShareWithGroup = getChild("CheckShareWithGroup"); + + if(CheckShareWithGroup) + { + perm.setGroupBits(gAgent.getID(), group_id, + CheckShareWithGroup->get(), + PERM_MODIFY | PERM_MOVE | PERM_COPY); + } + LLCheckBoxCtrl* CheckEveryoneCopy = getChild("CheckEveryoneCopy"); + if(CheckEveryoneCopy) + { + perm.setEveryoneBits(gAgent.getID(), group_id, + CheckEveryoneCopy->get(), PERM_COPY); + } + + LLCheckBoxCtrl* CheckNextOwnerModify = getChild("CheckNextOwnerModify"); + if(CheckNextOwnerModify) + { + perm.setNextOwnerBits(gAgent.getID(), group_id, + CheckNextOwnerModify->get(), PERM_MODIFY); + } + LLCheckBoxCtrl* CheckNextOwnerCopy = getChild("CheckNextOwnerCopy"); + if(CheckNextOwnerCopy) + { + perm.setNextOwnerBits(gAgent.getID(), group_id, + CheckNextOwnerCopy->get(), PERM_COPY); + } + LLCheckBoxCtrl* CheckNextOwnerTransfer = getChild("CheckNextOwnerTransfer"); + if(CheckNextOwnerTransfer) + { + perm.setNextOwnerBits(gAgent.getID(), group_id, + CheckNextOwnerTransfer->get(), PERM_TRANSFER); + } + if(perm != item->getPermissions() + && item->isFinished()) + { + LLPointer new_item = new LLViewerInventoryItem(item); + new_item->setPermissions(perm); + U32 flags = new_item->getFlags(); + // If next owner permissions have changed (and this is an object) + // then set the slam permissions flag so that they are applied on rez. + if((perm.getMaskNextOwner()!=item->getPermissions().getMaskNextOwner()) + && (item->getType() == LLAssetType::AT_OBJECT)) + { + flags |= LLInventoryItemFlags::II_FLAGS_OBJECT_SLAM_PERM; + } + // If everyone permissions have changed (and this is an object) + // then set the overwrite everyone permissions flag so they + // are applied on rez. + if ((perm.getMaskEveryone()!=item->getPermissions().getMaskEveryone()) + && (item->getType() == LLAssetType::AT_OBJECT)) + { + flags |= LLInventoryItemFlags::II_FLAGS_OBJECT_PERM_OVERWRITE_EVERYONE; + } + // If group permissions have changed (and this is an object) + // then set the overwrite group permissions flag so they + // are applied on rez. + if ((perm.getMaskGroup()!=item->getPermissions().getMaskGroup()) + && (item->getType() == LLAssetType::AT_OBJECT)) + { + flags |= LLInventoryItemFlags::II_FLAGS_OBJECT_PERM_OVERWRITE_GROUP; + } + new_item->setFlags(flags); + onCommitChanges(new_item); + } + else + { + // need to make sure we don't just follow the click + refresh(); + } +} + +void LLSidepanelItemInfo::onEditThumbnail() +{ + LLSD data; + data["task_id"] = mObjectID; + data["item_id"] = mItemID; + LLFloaterReg::showInstance("change_item_thumbnail", data); +} + +void LLSidepanelItemInfo::onCommitSaleInfo(LLUICtrl* ctrl) +{ + if (ctrl) + { + // will be enabled by response from server + ctrl->setEnabled(false); + } + //LL_INFOS() << "LLSidepanelItemInfo::onCommitSaleInfo()" << LL_ENDL; + updateSaleInfo(); +} + +void LLSidepanelItemInfo::updateSaleInfo() +{ + LLViewerInventoryItem* item = findItem(); + if(!item) return; + LLSaleInfo sale_info(item->getSaleInfo()); + if(!gAgent.allowOperation(PERM_TRANSFER, item->getPermissions(), GP_OBJECT_SET_SALE)) + { + getChild("CheckPurchase")->setValue(LLSD((bool)false)); + } + + if((bool)getChild("CheckPurchase")->getValue()) + { + // turn on sale info + LLSaleInfo::EForSale sale_type = LLSaleInfo::FS_COPY; + + LLComboBox* combo_sale_type = getChild("ComboBoxSaleType"); + if (combo_sale_type) + { + sale_type = static_cast(combo_sale_type->getValue().asInteger()); + } + + if (sale_type == LLSaleInfo::FS_COPY + && !gAgent.allowOperation(PERM_COPY, item->getPermissions(), + GP_OBJECT_SET_SALE)) + { + sale_type = LLSaleInfo::FS_ORIGINAL; + } + + + + S32 price = -1; + price = getChild("Edit Cost")->getValue().asInteger();; + + // Invalid data - turn off the sale + if (price < 0) + { + sale_type = LLSaleInfo::FS_NOT; + price = 0; + } + + sale_info.setSaleType(sale_type); + sale_info.setSalePrice(price); + } + else + { + sale_info.setSaleType(LLSaleInfo::FS_NOT); + } + if(sale_info != item->getSaleInfo() + && item->isFinished()) + { + LLPointer new_item = new LLViewerInventoryItem(item); + + // Force an update on the sale price at rez + if (item->getType() == LLAssetType::AT_OBJECT) + { + U32 flags = new_item->getFlags(); + flags |= LLInventoryItemFlags::II_FLAGS_OBJECT_SLAM_SALE; + new_item->setFlags(flags); + } + + new_item->setSaleInfo(sale_info); + onCommitChanges(new_item); + } + else + { + // need to make sure we don't just follow the click + refresh(); + } +} + +void LLSidepanelItemInfo::onCommitChanges(LLPointer item) +{ + if (item.isNull()) + { + return; + } + + if (mObjectID.isNull()) + { + // This is in the agent's inventory. + // Mark update as pending and wait only for most recent one in case user requested for couple + // Once update arrives or any of ids change drop pending id. + mUpdatePendingId++; + LLPointer callback = new PropertiesChangedCallback(getHandle(), mItemID, mUpdatePendingId); + update_inventory_item(item.get(), callback); + //item->updateServer(false); + gInventory.updateItem(item); + gInventory.notifyObservers(); + } + else + { + // This is in an object's contents. + LLViewerObject* object = gObjectList.findObject(mObjectID); + if (object) + { + object->updateInventory( + item, + TASK_INVENTORY_ITEM_KEY, + false); + + if (object->isSelected()) + { + // Since object is selected (build floater is open) object will + // receive properties update, detect serial mismatch, dirty and + // reload inventory, meanwhile some other updates will refresh it. + // So mark dirty early, this will prevent unnecessary changes + // and download will be triggered by LLPanelObjectInventory - it + // prevents flashing in content tab and some duplicated request. + object->dirtyInventory(); + } + setPropertiesFieldsEnabled(false); + } + } +} + +LLViewerInventoryItem* LLSidepanelItemInfo::findItem() const +{ + LLViewerInventoryItem* item = NULL; + if(mObjectID.isNull()) + { + // it is in agent inventory + item = gInventory.getItem(mItemID); + } + else + { + LLViewerObject* object = gObjectList.findObject(mObjectID); + if(object) + { + item = static_cast(object->getInventoryObject(mItemID)); + } + } + return item; +} + +// virtual +void LLSidepanelItemInfo::save() +{ + onCommitName(); + onCommitDescription(); + updatePermissions(); + updateSaleInfo(); +} diff --git a/indra/newview/llsidepaneliteminfo.h b/indra/newview/llsidepaneliteminfo.h index 6b34321a55..718edc79d6 100644 --- a/indra/newview/llsidepaneliteminfo.h +++ b/indra/newview/llsidepaneliteminfo.h @@ -1,125 +1,125 @@ -/** - * @file llsidepaneliteminfo.h - * @brief A panel which shows an inventory item's properties. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLSIDEPANELITEMINFO_H -#define LL_LLSIDEPANELITEMINFO_H - -#include "llinventoryobserver.h" -#include "llpanel.h" -#include "llstyle.h" - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLSidepanelItemInfo -// Object properties for inventory side panel. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLAvatarName; -class LLButton; -class LLFloater; -class LLIconCtrl; -class LLViewerInventoryItem; -class LLItemPropertiesObserver; -class LLObjectInventoryObserver; -class LLViewerObject; -class LLPermissions; -class LLTextBox; - -class LLSidepanelItemInfo : public LLPanel, public LLInventoryObserver -{ -public: - LLSidepanelItemInfo(const LLPanel::Params& p = getDefaultParams()); - virtual ~LLSidepanelItemInfo(); - - /*virtual*/ bool postBuild() override; - /*virtual*/ void reset(); - - void setObjectID(const LLUUID& object_id); - void setItemID(const LLUUID& item_id); - void setParentFloater(LLFloater* parent); // For simplicity - - const LLUUID& getObjectID() const; - const LLUUID& getItemID() const; - - // if received update and item id (from callback) matches internal ones, update UI - void onUpdateCallback(const LLUUID& item_id, S32 received_update_id); - - void changed(U32 mask) override; - void dirty(); - - static void onIdle( void* user_data ); - void updateOwnerName(const LLUUID& owner_id, const LLAvatarName& owner_name, const LLStyle::Params& style_params); - void updateCreatorName(const LLUUID& creator_id, const LLAvatarName& creator_name, const LLStyle::Params& style_params); - -protected: - void refresh() override; - void save(); - - LLViewerInventoryItem* findItem() const; - LLViewerObject* findObject() const; - - void refreshFromItem(LLViewerInventoryItem* item); - -private: - static void setAssociatedExperience( LLHandle hInfo, const LLSD& experience ); - - void startObjectInventoryObserver(); - void stopObjectInventoryObserver(); - void setPropertiesFieldsEnabled(bool enabled); - - boost::signals2::connection mOwnerCacheConnection; - boost::signals2::connection mCreatorCacheConnection; - - LLUUID mItemID; // inventory UUID for the inventory item. - LLUUID mObjectID; // in-world task UUID, or null if in agent inventory. - LLObjectInventoryObserver* mObjectInventoryObserver; // for syncing changes to items inside an object - - // We can send multiple properties updates simultaneously, make sure only last response counts and there won't be a race condition. - S32 mUpdatePendingId; - bool mIsDirty; // item properties need to be updated - LLFloater* mParentFloater; - - LLUICtrl* mChangeThumbnailBtn; - LLIconCtrl* mItemTypeIcon; - LLTextBox* mLabelOwnerName; - LLTextBox* mLabelCreatorName; - - // - // UI Elements - // -protected: - void onClickCreator(); - void onClickOwner(); - void onCommitName(); - void onCommitDescription(); - void onCommitPermissions(LLUICtrl* ctrl); - void updatePermissions(); - void onEditThumbnail(); - void onCommitSaleInfo(LLUICtrl* ctrl); - void updateSaleInfo(); - void onCommitChanges(LLPointer item); -}; - -#endif // LL_LLSIDEPANELITEMINFO_H +/** + * @file llsidepaneliteminfo.h + * @brief A panel which shows an inventory item's properties. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSIDEPANELITEMINFO_H +#define LL_LLSIDEPANELITEMINFO_H + +#include "llinventoryobserver.h" +#include "llpanel.h" +#include "llstyle.h" + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLSidepanelItemInfo +// Object properties for inventory side panel. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLAvatarName; +class LLButton; +class LLFloater; +class LLIconCtrl; +class LLViewerInventoryItem; +class LLItemPropertiesObserver; +class LLObjectInventoryObserver; +class LLViewerObject; +class LLPermissions; +class LLTextBox; + +class LLSidepanelItemInfo : public LLPanel, public LLInventoryObserver +{ +public: + LLSidepanelItemInfo(const LLPanel::Params& p = getDefaultParams()); + virtual ~LLSidepanelItemInfo(); + + /*virtual*/ bool postBuild() override; + /*virtual*/ void reset(); + + void setObjectID(const LLUUID& object_id); + void setItemID(const LLUUID& item_id); + void setParentFloater(LLFloater* parent); // For simplicity + + const LLUUID& getObjectID() const; + const LLUUID& getItemID() const; + + // if received update and item id (from callback) matches internal ones, update UI + void onUpdateCallback(const LLUUID& item_id, S32 received_update_id); + + void changed(U32 mask) override; + void dirty(); + + static void onIdle( void* user_data ); + void updateOwnerName(const LLUUID& owner_id, const LLAvatarName& owner_name, const LLStyle::Params& style_params); + void updateCreatorName(const LLUUID& creator_id, const LLAvatarName& creator_name, const LLStyle::Params& style_params); + +protected: + void refresh() override; + void save(); + + LLViewerInventoryItem* findItem() const; + LLViewerObject* findObject() const; + + void refreshFromItem(LLViewerInventoryItem* item); + +private: + static void setAssociatedExperience( LLHandle hInfo, const LLSD& experience ); + + void startObjectInventoryObserver(); + void stopObjectInventoryObserver(); + void setPropertiesFieldsEnabled(bool enabled); + + boost::signals2::connection mOwnerCacheConnection; + boost::signals2::connection mCreatorCacheConnection; + + LLUUID mItemID; // inventory UUID for the inventory item. + LLUUID mObjectID; // in-world task UUID, or null if in agent inventory. + LLObjectInventoryObserver* mObjectInventoryObserver; // for syncing changes to items inside an object + + // We can send multiple properties updates simultaneously, make sure only last response counts and there won't be a race condition. + S32 mUpdatePendingId; + bool mIsDirty; // item properties need to be updated + LLFloater* mParentFloater; + + LLUICtrl* mChangeThumbnailBtn; + LLIconCtrl* mItemTypeIcon; + LLTextBox* mLabelOwnerName; + LLTextBox* mLabelCreatorName; + + // + // UI Elements + // +protected: + void onClickCreator(); + void onClickOwner(); + void onCommitName(); + void onCommitDescription(); + void onCommitPermissions(LLUICtrl* ctrl); + void updatePermissions(); + void onEditThumbnail(); + void onCommitSaleInfo(LLUICtrl* ctrl); + void updateSaleInfo(); + void onCommitChanges(LLPointer item); +}; + +#endif // LL_LLSIDEPANELITEMINFO_H diff --git a/indra/newview/llsidepaneltaskinfo.cpp b/indra/newview/llsidepaneltaskinfo.cpp index 4bb9369813..4ce45608df 100644 --- a/indra/newview/llsidepaneltaskinfo.cpp +++ b/indra/newview/llsidepaneltaskinfo.cpp @@ -1,1365 +1,1365 @@ -/** - * @file llsidepaneltaskinfo.cpp - * @brief LLSidepanelTaskInfo class implementation - * This class represents the panel in the build view for - * viewing/editing object names, owners, permissions, etc. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llsidepaneltaskinfo.h" - -#include "lluuid.h" -#include "llpermissions.h" -#include "llcategory.h" -#include "llclickaction.h" -#include "llfocusmgr.h" -#include "llnotificationsutil.h" -#include "llstring.h" - -#include "llviewerwindow.h" -#include "llresmgr.h" -#include "lltextbox.h" -#include "llbutton.h" -#include "llcallbacklist.h" -#include "llcheckboxctrl.h" -#include "llviewerobject.h" -#include "llselectmgr.h" -#include "llagent.h" -#include "llstatusbar.h" // for getBalance() -#include "lllineeditor.h" -#include "llcombobox.h" -#include "lluiconstants.h" -#include "lldbstrings.h" -#include "llfloatergroups.h" -#include "llfloaterreg.h" -#include "llavataractions.h" -#include "llnamebox.h" -#include "llviewercontrol.h" -#include "llviewermenu.h" -#include "lluictrlfactory.h" -#include "llspinctrl.h" -#include "roles_constants.h" -#include "llgroupactions.h" -#include "lltextbase.h" -#include "llstring.h" -#include "lltrans.h" - -///---------------------------------------------------------------------------- -/// Class llsidepaneltaskinfo -///---------------------------------------------------------------------------- - -LLSidepanelTaskInfo* LLSidepanelTaskInfo::sActivePanel = NULL; - -static LLPanelInjector t_task_info("sidepanel_task_info"); - -static std::string click_action_to_string_value(U8 click_action) -{ - switch (click_action) - { - case CLICK_ACTION_TOUCH: - return "Touch"; - case CLICK_ACTION_SIT: - return "Sit"; - case CLICK_ACTION_BUY: - return "Buy"; - case CLICK_ACTION_PAY: - return "Pay"; - case CLICK_ACTION_OPEN: - return "Open"; - case CLICK_ACTION_ZOOM: - return "Zoom"; - case CLICK_ACTION_DISABLED: - return "None"; - case CLICK_ACTION_IGNORE: - return "Ignore"; - default: - return "Touch"; - } - return "Touch"; -} - -// Default constructor -LLSidepanelTaskInfo::LLSidepanelTaskInfo() - : mVisibleDebugPermissions(true) // space was allocated by default -{ - setMouseOpaque(false); - mSelectionUpdateSlot = LLSelectMgr::instance().mUpdateSignal.connect(boost::bind(&LLSidepanelTaskInfo::refreshAll, this)); - gIdleCallbacks.addFunction(&LLSidepanelTaskInfo::onIdle, (void*)this); -} - - -LLSidepanelTaskInfo::~LLSidepanelTaskInfo() -{ - if (sActivePanel == this) - sActivePanel = NULL; - gIdleCallbacks.deleteFunction(&LLSidepanelTaskInfo::onIdle, (void*)this); - - if (mSelectionUpdateSlot.connected()) - { - mSelectionUpdateSlot.disconnect(); - } -} - -// virtual -bool LLSidepanelTaskInfo::postBuild() -{ - mOpenBtn = getChild("open_btn"); - mOpenBtn->setClickedCallback(boost::bind(&LLSidepanelTaskInfo::onOpenButtonClicked, this)); - mPayBtn = getChild("pay_btn"); - mPayBtn->setClickedCallback(boost::bind(&LLSidepanelTaskInfo::onPayButtonClicked, this)); - mBuyBtn = getChild("buy_btn"); - mBuyBtn->setClickedCallback(boost::bind(&handle_buy)); - mDetailsBtn = getChild("details_btn"); - mDetailsBtn->setClickedCallback(boost::bind(&LLSidepanelTaskInfo::onDetailsButtonClicked, this)); - - mDeedBtn = getChild("button deed"); - - mLabelGroupName = getChild("Group Name Proxy"); - - childSetCommitCallback("Object Name", LLSidepanelTaskInfo::onCommitName,this); - getChild("Object Name")->setPrevalidate(LLTextValidate::validateASCIIPrintableNoPipe); - childSetCommitCallback("Object Description", LLSidepanelTaskInfo::onCommitDesc,this); - getChild("Object Description")->setPrevalidate(LLTextValidate::validateASCIIPrintableNoPipe); - getChild("button set group")->setCommitCallback(boost::bind(&LLSidepanelTaskInfo::onClickGroup,this)); - childSetCommitCallback("checkbox share with group", &LLSidepanelTaskInfo::onCommitGroupShare,this); - childSetAction("button deed", &LLSidepanelTaskInfo::onClickDeedToGroup,this); - childSetCommitCallback("checkbox allow everyone move", &LLSidepanelTaskInfo::onCommitEveryoneMove,this); - childSetCommitCallback("checkbox allow everyone copy", &LLSidepanelTaskInfo::onCommitEveryoneCopy,this); - childSetCommitCallback("checkbox for sale", &LLSidepanelTaskInfo::onCommitSaleInfo,this); - childSetCommitCallback("sale type", &LLSidepanelTaskInfo::onCommitSaleType,this); - childSetCommitCallback("Edit Cost", &LLSidepanelTaskInfo::onCommitSaleInfo, this); - childSetCommitCallback("checkbox next owner can modify", &LLSidepanelTaskInfo::onCommitNextOwnerModify,this); - childSetCommitCallback("checkbox next owner can copy", &LLSidepanelTaskInfo::onCommitNextOwnerCopy,this); - childSetCommitCallback("checkbox next owner can transfer", &LLSidepanelTaskInfo::onCommitNextOwnerTransfer,this); - childSetCommitCallback("clickaction", &LLSidepanelTaskInfo::onCommitClickAction,this); - childSetCommitCallback("search_check", &LLSidepanelTaskInfo::onCommitIncludeInSearch,this); - - mDAPermModify = getChild("perm_modify"); - mDACreatorName = getChild("Creator Name"); - mDAOwner = getChildView("Owner:"); - mDAOwnerName = getChild("Owner Name"); - mDAButtonSetGroup = getChildView("button set group"); - mDAObjectName = getChild("Object Name"); - mDAName = getChildView("Name:"); - mDADescription = getChildView("Description:"); - mDAObjectDescription = getChild("Object Description"); - mDACheckboxShareWithGroup = getChild("checkbox share with group"); - mDAButtonDeed = getChildView("button deed"); - mDACheckboxAllowEveryoneMove = getChild("checkbox allow everyone move"); - mDACheckboxAllowEveryoneCopy = getChild("checkbox allow everyone copy"); - mDACheckboxNextOwnerCanModify = getChild("checkbox next owner can modify"); - mDACheckboxNextOwnerCanCopy = getChild("checkbox next owner can copy"); - mDACheckboxNextOwnerCanTransfer = getChild("checkbox next owner can transfer"); - mDACheckboxForSale = getChild("checkbox for sale"); - mDASearchCheck = getChild("search_check"); - mDAComboSaleType = getChild("sale type"); - mDAEditCost = getChild("Edit Cost"); - mDALabelClickAction = getChildView("label click action"); - mDAComboClickAction = getChild("clickaction"); - mDAPathfindingAttributes = getChild("pathfinding_attributes_value"); - mDAB = getChild("B:"); - mDAO = getChild("O:"); - mDAG = getChild("G:"); - mDAE = getChild("E:"); - mDAN = getChild("N:"); - mDAF = getChild("F:"); - - return true; -} - -/*virtual*/ void LLSidepanelTaskInfo::onVisibilityChange(bool visible) -{ - if (visible) - { - sActivePanel = this; - mObject = getFirstSelectedObject(); - } - else - { - sActivePanel = NULL; - // drop selection reference - mObjectSelection = NULL; - } -} - - -void LLSidepanelTaskInfo::disableAll() -{ - mDACreatorName->setValue(LLStringUtil::null); - mDACreatorName->setEnabled(false); - - mDAOwner->setEnabled(false); - mDAOwnerName->setValue(LLStringUtil::null); - mDAOwnerName->setEnabled(false); - - mDAObjectName->setValue(LLStringUtil::null); - mDAObjectName->setEnabled(false); - mDAName->setEnabled(false); - mDADescription->setEnabled(false); - mDAObjectDescription->setValue(LLStringUtil::null); - mDAObjectDescription->setEnabled(false); - - mDAPathfindingAttributes->setEnabled(false); - mDAPathfindingAttributes->setValue(LLStringUtil::null); - - mDAButtonSetGroup->setEnabled(false); - mDAButtonDeed->setEnabled(false); - - mDAPermModify->setEnabled(false); - mDAPermModify->setValue(LLStringUtil::null); - mDAEditCost->setValue(LLStringUtil::null); - mDAComboSaleType->setValue(LLSaleInfo::FS_COPY); - - disablePermissions(); - - if (mVisibleDebugPermissions) - { - mDAB->setVisible(false); - mDAO->setVisible(false); - mDAG->setVisible(false); - mDAE->setVisible(false); - mDAN->setVisible(false); - mDAF->setVisible(false); - - LLFloater* parent_floater = gFloaterView->getParentFloater(this); - LLRect parent_rect = parent_floater->getRect(); - LLRect debug_rect = mDAB->getRect(); - // use double the debug rect for padding (since it isn't trivial to extract top_pad) - parent_floater->reshape(parent_rect.getWidth(), parent_rect.getHeight() - (debug_rect.getHeight() * 2)); - mVisibleDebugPermissions = false; - } - - mOpenBtn->setEnabled(false); - mPayBtn->setEnabled(false); - mBuyBtn->setEnabled(false); -} - -void LLSidepanelTaskInfo::disablePermissions() -{ - mDACheckboxShareWithGroup->setValue(false); - mDACheckboxShareWithGroup->setEnabled(false); - - mDACheckboxAllowEveryoneMove->setValue(false); - mDACheckboxAllowEveryoneMove->setEnabled(false); - mDACheckboxAllowEveryoneCopy->setValue(false); - mDACheckboxAllowEveryoneCopy->setEnabled(false); - - //Next owner can: - mDACheckboxNextOwnerCanModify->setValue(false); - mDACheckboxNextOwnerCanModify->setEnabled(false); - mDACheckboxNextOwnerCanCopy->setValue(false); - mDACheckboxNextOwnerCanCopy->setEnabled(false); - mDACheckboxNextOwnerCanTransfer->setValue(false); - mDACheckboxNextOwnerCanTransfer->setEnabled(false); - - //checkbox for sale - mDACheckboxForSale->setValue(false); - mDACheckboxForSale->setEnabled(false); - - //checkbox include in search - mDASearchCheck->setValue(false); - mDASearchCheck->setEnabled(false); - - mDAComboSaleType->setEnabled(false); - - mDAEditCost->setEnabled(false); - - mDALabelClickAction->setEnabled(false); - if (mDAComboClickAction) - { - mDAComboClickAction->setEnabled(false); - mDAComboClickAction->clear(); - } -} - -void LLSidepanelTaskInfo::refresh() -{ - mIsDirty = false; - - LLButton* btn_deed_to_group = mDeedBtn; - if (btn_deed_to_group) - { - std::string deedText; - if (gWarningSettings.getBOOL("DeedObject")) - { - deedText = getString("text deed continued"); - } - else - { - deedText = getString("text deed"); - } - btn_deed_to_group->setLabelSelected(deedText); - btn_deed_to_group->setLabelUnselected(deedText); - } - - bool root_selected = true; - LLSelectNode* nodep = mObjectSelection->getFirstRootNode(); - S32 object_count = mObjectSelection->getRootObjectCount(); - if (!nodep || (object_count == 0)) - { - nodep = mObjectSelection->getFirstNode(); - object_count = mObjectSelection->getObjectCount(); - root_selected = false; - } - - LLViewerObject* objectp = NULL; - if (nodep) - { - objectp = nodep->getObject(); - } - - // ...nothing selected - if (!nodep || !objectp) - { - disableAll(); - return; - } - - // figure out a few variables - const bool is_one_object = (object_count == 1); - - // BUG: fails if a root and non-root are both single-selected. - const bool is_perm_modify = (mObjectSelection->getFirstRootNode() && LLSelectMgr::getInstance()->selectGetRootsModify()) || - LLSelectMgr::getInstance()->selectGetModify(); - const bool is_nonpermanent_enforced = (mObjectSelection->getFirstRootNode() && LLSelectMgr::getInstance()->selectGetRootsNonPermanentEnforced()) || - LLSelectMgr::getInstance()->selectGetNonPermanentEnforced(); - - S32 string_index = 0; - std::string MODIFY_INFO_STRINGS[] = - { - getString("text modify info 1"), - getString("text modify info 2"), - getString("text modify info 3"), - getString("text modify info 4"), - getString("text modify info 5"), - getString("text modify info 6") - }; - if (!is_perm_modify) - { - string_index += 2; - } - else if (!is_nonpermanent_enforced) - { - string_index += 4; - } - if (!is_one_object) - { - ++string_index; - } - getChildView("perm_modify")->setEnabled(true); - getChild("perm_modify")->setValue(MODIFY_INFO_STRINGS[string_index]); - - std::string pfAttrName; - - if ((mObjectSelection->getFirstRootNode() - && LLSelectMgr::getInstance()->selectGetRootsNonPathfinding()) - || LLSelectMgr::getInstance()->selectGetNonPathfinding()) - { - pfAttrName = "Pathfinding_Object_Attr_None"; - } - else if ((mObjectSelection->getFirstRootNode() - && LLSelectMgr::getInstance()->selectGetRootsPermanent()) - || LLSelectMgr::getInstance()->selectGetPermanent()) - { - pfAttrName = "Pathfinding_Object_Attr_Permanent"; - } - else if ((mObjectSelection->getFirstRootNode() - && LLSelectMgr::getInstance()->selectGetRootsCharacter()) - || LLSelectMgr::getInstance()->selectGetCharacter()) - { - pfAttrName = "Pathfinding_Object_Attr_Character"; - } - else - { - pfAttrName = "Pathfinding_Object_Attr_MultiSelect"; - } - - mDAPathfindingAttributes->setEnabled(true); - mDAPathfindingAttributes->setValue(LLTrans::getString(pfAttrName)); - - // Update creator text field - getChildView("Creator:")->setEnabled(true); - - std::string creator_name; - LLUUID creator_id; - LLSelectMgr::getInstance()->selectGetCreator(creator_id, creator_name); - - if(creator_id != mCreatorID ) - { - mDACreatorName->setValue(creator_name); - mCreatorID = creator_id; - } - if(mDACreatorName->getValue().asString() == LLStringUtil::null) - { - mDACreatorName->setValue(creator_name); - } - mDACreatorName->setEnabled(true); - - // Update owner text field - getChildView("Owner:")->setEnabled(true); - - std::string owner_name; - LLUUID owner_id; - const bool owners_identical = LLSelectMgr::getInstance()->selectGetOwner(owner_id, owner_name); - if (owner_id.isNull()) - { - if (LLSelectMgr::getInstance()->selectIsGroupOwned()) - { - // Group owned already displayed by selectGetOwner - } - else - { - // Display last owner if public - std::string last_owner_name; - LLSelectMgr::getInstance()->selectGetLastOwner(mLastOwnerID, last_owner_name); - - // It should never happen that the last owner is null and the owner - // is null, but it seems to be a bug in the simulator right now. JC - if (!mLastOwnerID.isNull() && !last_owner_name.empty()) - { - owner_name.append(", last "); - owner_name.append(last_owner_name); - } - } - } - - if(owner_id.isNull() || (owner_id != mOwnerID)) - { - mDAOwnerName->setValue(owner_name); - mOwnerID = owner_id; - } - if(mDAOwnerName->getValue().asString() == LLStringUtil::null) - { - mDAOwnerName->setValue(owner_name); - } - - getChildView("Owner Name")->setEnabled(true); - - // update group text field - getChildView("Group:")->setEnabled(true); - getChild("Group Name")->setValue(LLStringUtil::null); - LLUUID group_id; - bool groups_identical = LLSelectMgr::getInstance()->selectGetGroup(group_id); - if (groups_identical) - { - if (mLabelGroupName) - { - mLabelGroupName->setNameID(group_id,true); - mLabelGroupName->setEnabled(true); - } - } - else - { - if (mLabelGroupName) - { - mLabelGroupName->setNameID(LLUUID::null, true); - mLabelGroupName->refresh(LLUUID::null, std::string(), true); - mLabelGroupName->setEnabled(false); - } - } - - getChildView("button set group")->setEnabled(owners_identical && (mOwnerID == gAgent.getID()) && is_nonpermanent_enforced); - - getChildView("Name:")->setEnabled(true); - LLLineEditor* LineEditorObjectName = getChild("Object Name"); - getChildView("Description:")->setEnabled(true); - LLLineEditor* LineEditorObjectDesc = getChild("Object Description"); - - if (is_one_object) - { - if (!LineEditorObjectName->hasFocus()) - { - getChild("Object Name")->setValue(nodep->mName); - } - - if (LineEditorObjectDesc) - { - if (!LineEditorObjectDesc->hasFocus()) - { - LineEditorObjectDesc->setText(nodep->mDescription); - } - } - } - else - { - getChild("Object Name")->setValue(LLStringUtil::null); - LineEditorObjectDesc->setText(LLStringUtil::null); - } - - // figure out the contents of the name, description, & category - bool edit_name_desc = false; - if (is_one_object && objectp->permModify() && !objectp->isPermanentEnforced()) - { - edit_name_desc = true; - } - if (edit_name_desc) - { - getChildView("Object Name")->setEnabled(true); - getChildView("Object Description")->setEnabled(true); - } - else - { - getChildView("Object Name")->setEnabled(false); - getChildView("Object Description")->setEnabled(false); - } - - S32 total_sale_price = 0; - S32 individual_sale_price = 0; - bool is_for_sale_mixed = false; - bool is_sale_price_mixed = false; - U32 num_for_sale = false; - LLSelectMgr::getInstance()->selectGetAggregateSaleInfo(num_for_sale, - is_for_sale_mixed, - is_sale_price_mixed, - total_sale_price, - individual_sale_price); - - const bool self_owned = (gAgent.getID() == mOwnerID); - const bool group_owned = LLSelectMgr::getInstance()->selectIsGroupOwned() ; - const bool public_owned = (mOwnerID.isNull() && !LLSelectMgr::getInstance()->selectIsGroupOwned()); - const bool can_transfer = LLSelectMgr::getInstance()->selectGetRootsTransfer(); - const bool can_copy = LLSelectMgr::getInstance()->selectGetRootsCopy(); - - if (!owners_identical) - { - getChildView("Cost")->setEnabled(false); - getChild("Edit Cost")->setValue(LLStringUtil::null); - getChildView("Edit Cost")->setEnabled(false); - } - // You own these objects. - else if (self_owned || (group_owned && gAgent.hasPowerInGroup(group_id,GP_OBJECT_SET_SALE))) - { - LLSpinCtrl *edit_price = getChild("Edit Cost"); - - // If there are multiple items for sale then set text to PRICE PER UNIT. - if (num_for_sale > 1) - { - std::string label_text = is_sale_price_mixed? "Cost Mixed" :"Cost Per Unit"; - edit_price->setLabel(getString(label_text)); - } - else - { - edit_price->setLabel(getString("Cost Default")); - } - - if (!edit_price->hasFocus()) - { - // If the sale price is mixed then set the cost to MIXED, otherwise - // set to the actual cost. - if ((num_for_sale > 0) && is_for_sale_mixed) - { - edit_price->setTentative(true); - } - else if ((num_for_sale > 0) && is_sale_price_mixed) - { - edit_price->setTentative(true); - } - else - { - edit_price->setValue(individual_sale_price); - } - } - // The edit fields are only enabled if you can sell this object - // and the sale price is not mixed. - bool enable_edit = (num_for_sale && can_transfer) ? !is_for_sale_mixed : false; - getChildView("Cost")->setEnabled(enable_edit); - getChildView("Edit Cost")->setEnabled(enable_edit); - } - // Someone, not you, owns these objects. - else if (!public_owned) - { - getChildView("Cost")->setEnabled(false); - getChildView("Edit Cost")->setEnabled(false); - - // Don't show a price if none of the items are for sale. - if (num_for_sale) - getChild("Edit Cost")->setValue(llformat("%d",total_sale_price)); - else - getChild("Edit Cost")->setValue(LLStringUtil::null); - - // If multiple items are for sale, set text to TOTAL PRICE. - if (num_for_sale > 1) - getChild("Edit Cost")->setLabel(getString("Cost Total")); - else - getChild("Edit Cost")->setLabel(getString("Cost Default")); - } - // This is a public object. - else - { - getChildView("Cost")->setEnabled(false); - getChild("Edit Cost")->setLabel(getString("Cost Default")); - getChild("Edit Cost")->setValue(LLStringUtil::null); - getChildView("Edit Cost")->setEnabled(false); - } - - // Enable and disable the permissions checkboxes - // based on who owns the object. - // TODO: Creator permissions - - U32 base_mask_on = 0; - U32 base_mask_off = 0; - U32 owner_mask_off = 0; - U32 owner_mask_on = 0; - U32 group_mask_on = 0; - U32 group_mask_off = 0; - U32 everyone_mask_on = 0; - U32 everyone_mask_off = 0; - U32 next_owner_mask_on = 0; - U32 next_owner_mask_off = 0; - - bool valid_base_perms = LLSelectMgr::getInstance()->selectGetPerm(PERM_BASE, - &base_mask_on, - &base_mask_off); - //bool valid_owner_perms =// - LLSelectMgr::getInstance()->selectGetPerm(PERM_OWNER, - &owner_mask_on, - &owner_mask_off); - bool valid_group_perms = LLSelectMgr::getInstance()->selectGetPerm(PERM_GROUP, - &group_mask_on, - &group_mask_off); - - bool valid_everyone_perms = LLSelectMgr::getInstance()->selectGetPerm(PERM_EVERYONE, - &everyone_mask_on, - &everyone_mask_off); - - bool valid_next_perms = LLSelectMgr::getInstance()->selectGetPerm(PERM_NEXT_OWNER, - &next_owner_mask_on, - &next_owner_mask_off); - - - if (gSavedSettings.getBOOL("DebugPermissions") ) - { - if (valid_base_perms) - { - mDAB->setValue("B: " + mask_to_string(base_mask_on)); - mDAB->setVisible( true); - - mDAO->setValue("O: " + mask_to_string(owner_mask_on)); - mDAO->setVisible( true); - - mDAG->setValue("G: " + mask_to_string(group_mask_on)); - mDAG->setVisible( true); - - mDAE->setValue("E: " + mask_to_string(everyone_mask_on)); - mDAE->setVisible( true); - - mDAN->setValue("N: " + mask_to_string(next_owner_mask_on)); - mDAN->setVisible( true); - } - - U32 flag_mask = 0x0; - if (objectp->permMove()) flag_mask |= PERM_MOVE; - if (objectp->permModify()) flag_mask |= PERM_MODIFY; - if (objectp->permCopy()) flag_mask |= PERM_COPY; - if (objectp->permTransfer()) flag_mask |= PERM_TRANSFER; - - mDAF->setValue("F:" + mask_to_string(flag_mask)); - mDAF->setVisible(true); - - if (!mVisibleDebugPermissions) - { - LLFloater* parent_floater = gFloaterView->getParentFloater(this); - LLRect parent_rect = parent_floater->getRect(); - LLRect debug_rect = mDAB->getRect(); - // use double the debug rect for padding (since it isn't trivial to extract top_pad) - parent_floater->reshape(parent_rect.getWidth(), parent_rect.getHeight() + (debug_rect.getHeight() * 2)); - mVisibleDebugPermissions = true; - } - } - else if (mVisibleDebugPermissions) - { - mDAB->setVisible(false); - mDAO->setVisible(false); - mDAG->setVisible(false); - mDAE->setVisible(false); - mDAN->setVisible(false); - mDAF->setVisible(false); - - LLFloater* parent_floater = gFloaterView->getParentFloater(this); - LLRect parent_rect = parent_floater->getRect(); - LLRect debug_rect = mDAB->getRect(); - // use double the debug rect for padding (since it isn't trivial to extract top_pad) - parent_floater->reshape(parent_rect.getWidth(), parent_rect.getHeight() - (debug_rect.getHeight() * 2)); - mVisibleDebugPermissions = false; - } - - bool has_change_perm_ability = false; - bool has_change_sale_ability = false; - - if (valid_base_perms && is_nonpermanent_enforced && - (self_owned || (group_owned && gAgent.hasPowerInGroup(group_id, GP_OBJECT_MANIPULATE)))) - { - has_change_perm_ability = true; - } - if (valid_base_perms && is_nonpermanent_enforced && - (self_owned || (group_owned && gAgent.hasPowerInGroup(group_id, GP_OBJECT_SET_SALE)))) - { - has_change_sale_ability = true; - } - - if (!has_change_perm_ability && !has_change_sale_ability && !root_selected) - { - // ...must select root to choose permissions - getChild("perm_modify")->setValue(getString("text modify warning")); - } - - if (has_change_perm_ability) - { - getChildView("checkbox share with group")->setEnabled(true); - getChildView("checkbox allow everyone move")->setEnabled(owner_mask_on & PERM_MOVE); - getChildView("checkbox allow everyone copy")->setEnabled(owner_mask_on & PERM_COPY && owner_mask_on & PERM_TRANSFER); - } - else - { - getChildView("checkbox share with group")->setEnabled(false); - getChildView("checkbox allow everyone move")->setEnabled(false); - getChildView("checkbox allow everyone copy")->setEnabled(false); - } - - if (has_change_sale_ability && (owner_mask_on & PERM_TRANSFER)) - { - getChildView("checkbox for sale")->setEnabled(can_transfer || (!can_transfer && num_for_sale)); - // Set the checkbox to tentative if the prices of each object selected - // are not the same. - getChild("checkbox for sale")->setTentative( is_for_sale_mixed); - getChildView("sale type")->setEnabled(num_for_sale && can_transfer && !is_sale_price_mixed); - - getChildView("checkbox next owner can modify")->setEnabled(base_mask_on & PERM_MODIFY); - getChildView("checkbox next owner can copy")->setEnabled(base_mask_on & PERM_COPY); - getChildView("checkbox next owner can transfer")->setEnabled(next_owner_mask_on & PERM_COPY); - } - else - { - getChildView("checkbox for sale")->setEnabled(false); - getChildView("sale type")->setEnabled(false); - - getChildView("checkbox next owner can modify")->setEnabled(false); - getChildView("checkbox next owner can copy")->setEnabled(false); - getChildView("checkbox next owner can transfer")->setEnabled(false); - } - - if (valid_group_perms) - { - if ((group_mask_on & PERM_COPY) && (group_mask_on & PERM_MODIFY) && (group_mask_on & PERM_MOVE)) - { - getChild("checkbox share with group")->setValue(true); - getChild("checkbox share with group")->setTentative( false); - getChildView("button deed")->setEnabled(gAgent.hasPowerInGroup(group_id, GP_OBJECT_DEED) && (owner_mask_on & PERM_TRANSFER) && !group_owned && can_transfer); - } - else if ((group_mask_off & PERM_COPY) && (group_mask_off & PERM_MODIFY) && (group_mask_off & PERM_MOVE)) - { - getChild("checkbox share with group")->setValue(false); - getChild("checkbox share with group")->setTentative( false); - getChildView("button deed")->setEnabled(false); - } - else - { - getChild("checkbox share with group")->setValue(true); - getChild("checkbox share with group")->setTentative( true); - getChildView("button deed")->setEnabled(gAgent.hasPowerInGroup(group_id, GP_OBJECT_DEED) && (group_mask_on & PERM_MOVE) && (owner_mask_on & PERM_TRANSFER) && !group_owned && can_transfer); - } - } - - if (valid_everyone_perms) - { - // Move - if (everyone_mask_on & PERM_MOVE) - { - getChild("checkbox allow everyone move")->setValue(true); - getChild("checkbox allow everyone move")->setTentative( false); - } - else if (everyone_mask_off & PERM_MOVE) - { - getChild("checkbox allow everyone move")->setValue(false); - getChild("checkbox allow everyone move")->setTentative( false); - } - else - { - getChild("checkbox allow everyone move")->setValue(true); - getChild("checkbox allow everyone move")->setTentative( true); - } - - // Copy == everyone can't copy - if (everyone_mask_on & PERM_COPY) - { - getChild("checkbox allow everyone copy")->setValue(true); - getChild("checkbox allow everyone copy")->setTentative( !can_copy || !can_transfer); - } - else if (everyone_mask_off & PERM_COPY) - { - getChild("checkbox allow everyone copy")->setValue(false); - getChild("checkbox allow everyone copy")->setTentative( false); - } - else - { - getChild("checkbox allow everyone copy")->setValue(true); - getChild("checkbox allow everyone copy")->setTentative( true); - } - } - - if (valid_next_perms) - { - // Modify == next owner canot modify - if (next_owner_mask_on & PERM_MODIFY) - { - getChild("checkbox next owner can modify")->setValue(true); - getChild("checkbox next owner can modify")->setTentative( false); - } - else if (next_owner_mask_off & PERM_MODIFY) - { - getChild("checkbox next owner can modify")->setValue(false); - getChild("checkbox next owner can modify")->setTentative( false); - } - else - { - getChild("checkbox next owner can modify")->setValue(true); - getChild("checkbox next owner can modify")->setTentative( true); - } - - // Copy == next owner cannot copy - if (next_owner_mask_on & PERM_COPY) - { - getChild("checkbox next owner can copy")->setValue(true); - getChild("checkbox next owner can copy")->setTentative( !can_copy); - } - else if (next_owner_mask_off & PERM_COPY) - { - getChild("checkbox next owner can copy")->setValue(false); - getChild("checkbox next owner can copy")->setTentative( false); - } - else - { - getChild("checkbox next owner can copy")->setValue(true); - getChild("checkbox next owner can copy")->setTentative( true); - } - - // Transfer == next owner cannot transfer - if (next_owner_mask_on & PERM_TRANSFER) - { - getChild("checkbox next owner can transfer")->setValue(true); - getChild("checkbox next owner can transfer")->setTentative( !can_transfer); - } - else if (next_owner_mask_off & PERM_TRANSFER) - { - getChild("checkbox next owner can transfer")->setValue(false); - getChild("checkbox next owner can transfer")->setTentative( false); - } - else - { - getChild("checkbox next owner can transfer")->setValue(true); - getChild("checkbox next owner can transfer")->setTentative( true); - } - } - - // reflect sale information - LLSaleInfo sale_info; - bool valid_sale_info = LLSelectMgr::getInstance()->selectGetSaleInfo(sale_info); - LLSaleInfo::EForSale sale_type = sale_info.getSaleType(); - - LLComboBox* combo_sale_type = getChild("sale type"); - if (valid_sale_info) - { - combo_sale_type->setValue( sale_type == LLSaleInfo::FS_NOT ? LLSaleInfo::FS_COPY : sale_type); - combo_sale_type->setTentative( false); // unfortunately this doesn't do anything at the moment. - } - else - { - // default option is sell copy, determined to be safest - combo_sale_type->setValue( LLSaleInfo::FS_COPY); - combo_sale_type->setTentative( true); // unfortunately this doesn't do anything at the moment. - } - - getChild("checkbox for sale")->setValue((num_for_sale != 0)); - - // HACK: There are some old objects in world that are set for sale, - // but are no-transfer. We need to let users turn for-sale off, but only - // if for-sale is set. - bool cannot_actually_sell = !can_transfer || (!can_copy && sale_type == LLSaleInfo::FS_COPY); - if (cannot_actually_sell) - { - if (num_for_sale && has_change_sale_ability) - { - getChildView("checkbox for sale")->setEnabled(true); - } - } - - // Check search status of objects - const bool all_volume = LLSelectMgr::getInstance()->selectionAllPCode( LL_PCODE_VOLUME ); - bool include_in_search; - const bool all_include_in_search = LLSelectMgr::getInstance()->selectionGetIncludeInSearch(&include_in_search); - getChildView("search_check")->setEnabled(has_change_sale_ability && all_volume); - getChild("search_check")->setValue(include_in_search); - getChild("search_check")->setTentative(!all_include_in_search); - - // Click action (touch, sit, buy) - U8 click_action = 0; - if (LLSelectMgr::getInstance()->selectionGetClickAction(&click_action)) - { - getChild("clickaction")->setValue(click_action_to_string_value(click_action)); - } - getChildView("label click action")->setEnabled(is_perm_modify && is_nonpermanent_enforced && all_volume); - getChildView("clickaction")->setEnabled(is_perm_modify && is_nonpermanent_enforced && all_volume); - - updateVerbs(); -} - - -// static -void LLSidepanelTaskInfo::onClickClaim(void*) -{ - // try to claim ownership - LLSelectMgr::getInstance()->sendOwner(gAgent.getID(), gAgent.getGroupID()); -} - -// static -void LLSidepanelTaskInfo::onClickRelease(void*) -{ - // try to release ownership - LLSelectMgr::getInstance()->sendOwner(LLUUID::null, LLUUID::null); -} - -void LLSidepanelTaskInfo::onClickGroup() -{ - LLUUID owner_id; - std::string name; - bool owners_identical = LLSelectMgr::getInstance()->selectGetOwner(owner_id, name); - LLFloater* parent_floater = gFloaterView->getParentFloater(this); - - if (owners_identical && (owner_id == gAgent.getID())) - { - LLFloaterGroupPicker* fg = LLFloaterReg::showTypedInstance("group_picker", LLSD(gAgent.getID())); - if (fg) - { - fg->setSelectGroupCallback( boost::bind(&LLSidepanelTaskInfo::cbGroupID, this, _1) ); - if (parent_floater) - { - LLRect new_rect = gFloaterView->findNeighboringPosition(parent_floater, fg); - fg->setOrigin(new_rect.mLeft, new_rect.mBottom); - parent_floater->addDependentFloater(fg); - } - } - } -} - -void LLSidepanelTaskInfo::cbGroupID(LLUUID group_id) -{ - if (mLabelGroupName) - { - mLabelGroupName->setNameID(group_id, true); - } - LLSelectMgr::getInstance()->sendGroup(group_id); -} - -static bool callback_deed_to_group(const LLSD& notification, const LLSD& response) -{ - const S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) - { - LLUUID group_id; - const bool groups_identical = LLSelectMgr::getInstance()->selectGetGroup(group_id); - if (group_id.notNull() && groups_identical && (gAgent.hasPowerInGroup(group_id, GP_OBJECT_DEED))) - { - LLSelectMgr::getInstance()->sendOwner(LLUUID::null, group_id, false); - } - } - return false; -} - -void LLSidepanelTaskInfo::onClickDeedToGroup(void *data) -{ - LLNotificationsUtil::add("DeedObjectToGroup", LLSD(), LLSD(), callback_deed_to_group); -} - -///---------------------------------------------------------------------------- -/// Permissions checkboxes -///---------------------------------------------------------------------------- - -// static -void LLSidepanelTaskInfo::onCommitPerm(LLUICtrl *ctrl, void *data, U8 field, U32 perm) -{ - LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getFirstRootObject(); - if(!object) return; - - // Checkbox will have toggled itself - // LLSidepanelTaskInfo* self = (LLSidepanelTaskInfo*)data; - LLCheckBoxCtrl *check = (LLCheckBoxCtrl *)ctrl; - bool new_state = check->get(); - - LLSelectMgr::getInstance()->selectionSetObjectPermissions(field, new_state, perm); - - LLSidepanelTaskInfo* self = (LLSidepanelTaskInfo*)data; - if (self) - { - self->disablePermissions(); - } -} - -// static -void LLSidepanelTaskInfo::onCommitGroupShare(LLUICtrl *ctrl, void *data) -{ - onCommitPerm(ctrl, data, PERM_GROUP, PERM_MODIFY | PERM_MOVE | PERM_COPY); -} - -// static -void LLSidepanelTaskInfo::onCommitEveryoneMove(LLUICtrl *ctrl, void *data) -{ - onCommitPerm(ctrl, data, PERM_EVERYONE, PERM_MOVE); -} - - -// static -void LLSidepanelTaskInfo::onCommitEveryoneCopy(LLUICtrl *ctrl, void *data) -{ - onCommitPerm(ctrl, data, PERM_EVERYONE, PERM_COPY); -} - -// static -void LLSidepanelTaskInfo::onCommitNextOwnerModify(LLUICtrl* ctrl, void* data) -{ - //LL_INFOS() << "LLSidepanelTaskInfo::onCommitNextOwnerModify" << LL_ENDL; - onCommitPerm(ctrl, data, PERM_NEXT_OWNER, PERM_MODIFY); -} - -// static -void LLSidepanelTaskInfo::onCommitNextOwnerCopy(LLUICtrl* ctrl, void* data) -{ - //LL_INFOS() << "LLSidepanelTaskInfo::onCommitNextOwnerCopy" << LL_ENDL; - onCommitPerm(ctrl, data, PERM_NEXT_OWNER, PERM_COPY); -} - -// static -void LLSidepanelTaskInfo::onCommitNextOwnerTransfer(LLUICtrl* ctrl, void* data) -{ - //LL_INFOS() << "LLSidepanelTaskInfo::onCommitNextOwnerTransfer" << LL_ENDL; - onCommitPerm(ctrl, data, PERM_NEXT_OWNER, PERM_TRANSFER); -} - -// static -void LLSidepanelTaskInfo::onCommitName(LLUICtrl*, void* data) -{ - //LL_INFOS() << "LLSidepanelTaskInfo::onCommitName()" << LL_ENDL; - LLSidepanelTaskInfo* self = (LLSidepanelTaskInfo*)data; - LLLineEditor* tb = self->getChild("Object Name"); - if(tb) - { - LLSelectMgr::getInstance()->selectionSetObjectName(tb->getText()); -// LLSelectMgr::getInstance()->selectionSetObjectName(self->mLabelObjectName->getText()); - } -} - - -// static -void LLSidepanelTaskInfo::onCommitDesc(LLUICtrl*, void* data) -{ - //LL_INFOS() << "LLSidepanelTaskInfo::onCommitDesc()" << LL_ENDL; - LLSidepanelTaskInfo* self = (LLSidepanelTaskInfo*)data; - LLLineEditor* le = self->getChild("Object Description"); - if(le) - { - LLSelectMgr::getInstance()->selectionSetObjectDescription(le->getText()); - } -} - -// static -void LLSidepanelTaskInfo::onCommitSaleInfo(LLUICtrl*, void* data) -{ - LLSidepanelTaskInfo* self = (LLSidepanelTaskInfo*)data; - self->setAllSaleInfo(); -} - -// static -void LLSidepanelTaskInfo::onCommitSaleType(LLUICtrl*, void* data) -{ - LLSidepanelTaskInfo* self = (LLSidepanelTaskInfo*)data; - self->setAllSaleInfo(); -} - - -void LLSidepanelTaskInfo::setAllSaleInfo() -{ - LLSaleInfo::EForSale sale_type = LLSaleInfo::FS_NOT; - - LLCheckBoxCtrl *checkPurchase = getChild("checkbox for sale"); - - // Set the sale type if the object(s) are for sale. - if(checkPurchase && checkPurchase->get()) - { - sale_type = static_cast(getChild("sale type")->getValue().asInteger()); - } - - S32 price = -1; - - LLSpinCtrl *edit_price = getChild("Edit Cost"); - price = (edit_price->getTentative()) ? DEFAULT_PRICE : edit_price->getValue().asInteger(); - - // If somehow an invalid price, turn the sale off. - if (price < 0) - sale_type = LLSaleInfo::FS_NOT; - - LLSaleInfo old_sale_info; - LLSelectMgr::getInstance()->selectGetSaleInfo(old_sale_info); - - LLSaleInfo new_sale_info(sale_type, price); - LLSelectMgr::getInstance()->selectionSetObjectSaleInfo(new_sale_info); - - U8 old_click_action = 0; - LLSelectMgr::getInstance()->selectionGetClickAction(&old_click_action); - - if (old_sale_info.isForSale() - && !new_sale_info.isForSale() - && old_click_action == CLICK_ACTION_BUY) - { - // If turned off for-sale, make sure click-action buy is turned - // off as well - LLSelectMgr::getInstance()-> - selectionSetClickAction(CLICK_ACTION_TOUCH); - } - else if (new_sale_info.isForSale() - && !old_sale_info.isForSale() - && old_click_action == CLICK_ACTION_TOUCH) - { - // If just turning on for-sale, preemptively turn on one-click buy - // unless user have a different click action set - LLSelectMgr::getInstance()-> - selectionSetClickAction(CLICK_ACTION_BUY); - } -} - -struct LLSelectionPayable : public LLSelectedObjectFunctor -{ - virtual bool apply(LLViewerObject* obj) - { - // can pay if you or your parent has money() event in script - LLViewerObject* parent = (LLViewerObject*)obj->getParent(); - return (obj->flagTakesMoney() || - (parent && parent->flagTakesMoney())); - } -}; - -static U8 string_value_to_click_action(std::string p_value) -{ - if (p_value == "Touch") - return CLICK_ACTION_TOUCH; - if (p_value == "Sit") - return CLICK_ACTION_SIT; - if (p_value == "Buy") - return CLICK_ACTION_BUY; - if (p_value == "Pay") - return CLICK_ACTION_PAY; - if (p_value == "Open") - return CLICK_ACTION_OPEN; - if (p_value == "Zoom") - return CLICK_ACTION_ZOOM; - if (p_value == "None") - return CLICK_ACTION_DISABLED; - if (p_value == "Ignore") - return CLICK_ACTION_IGNORE; - return CLICK_ACTION_TOUCH; -} - -// static -void LLSidepanelTaskInfo::onCommitClickAction(LLUICtrl* ctrl, void*) -{ - LLComboBox* box = (LLComboBox*)ctrl; - if (!box) - return; - std::string value = box->getValue().asString(); - U8 click_action = string_value_to_click_action(value); - doClickAction(click_action); -} - -// static -void LLSidepanelTaskInfo::doClickAction(U8 click_action) -{ - if (click_action == CLICK_ACTION_BUY) - { - LLSaleInfo sale_info; - LLSelectMgr::getInstance()->selectGetSaleInfo(sale_info); - if (!sale_info.isForSale()) - { - LLNotificationsUtil::add("CantSetBuyObject"); - - // Set click action back to its old value - U8 click_action = 0; - LLSelectMgr::getInstance()->selectionGetClickAction(&click_action); - return; - } - } - else if (click_action == CLICK_ACTION_PAY) - { - // Verify object has script with money() handler - LLSelectionPayable payable; - bool can_pay = LLSelectMgr::getInstance()->getSelection()->applyToObjects(&payable); - if (!can_pay) - { - // Warn, but do it anyway. - LLNotificationsUtil::add("ClickActionNotPayable"); - } - else - { - handle_give_money_dialog(); - } - } - LLSelectMgr::getInstance()->selectionSetClickAction(click_action); -} - -// static -void LLSidepanelTaskInfo::onCommitIncludeInSearch(LLUICtrl* ctrl, void* data) -{ - LLCheckBoxCtrl* box = (LLCheckBoxCtrl*)ctrl; - llassert(box); - LLSelectMgr::getInstance()->selectionSetIncludeInSearch(box->get()); -} - -// virtual -void LLSidepanelTaskInfo::updateVerbs() -{ - LLSafeHandle object_selection = LLSelectMgr::getInstance()->getSelection(); - const bool any_selected = (object_selection->getNumNodes() > 0); - - mOpenBtn->setVisible(true); - mPayBtn->setVisible(true); - mBuyBtn->setVisible(true); - mDetailsBtn->setVisible(true); - - mOpenBtn->setEnabled(enable_object_open()); - mPayBtn->setEnabled(enable_pay_object()); - mBuyBtn->setEnabled(enable_buy_object()); - mDetailsBtn->setEnabled(any_selected); -} - -void LLSidepanelTaskInfo::onOpenButtonClicked() -{ - if (enable_object_open()) - { - handle_object_open(); - } -} - -void LLSidepanelTaskInfo::onPayButtonClicked() -{ - doClickAction(CLICK_ACTION_PAY); -} - -void LLSidepanelTaskInfo::onBuyButtonClicked() -{ - doClickAction(CLICK_ACTION_BUY); -} - -void LLSidepanelTaskInfo::onDetailsButtonClicked() -{ - LLFloaterReg::showInstance("inspect", LLSD()); -} - -// virtual -void LLSidepanelTaskInfo::save() -{ - onCommitGroupShare(getChild("checkbox share with group"), this); - onCommitEveryoneMove(getChild("checkbox allow everyone move"), this); - onCommitEveryoneCopy(getChild("checkbox allow everyone copy"), this); - onCommitNextOwnerModify(getChild("checkbox next owner can modify"), this); - onCommitNextOwnerCopy(getChild("checkbox next owner can copy"), this); - onCommitNextOwnerTransfer(getChild("checkbox next owner can transfer"), this); - onCommitName(getChild("Object Name"), this); - onCommitDesc(getChild("Object Description"), this); - onCommitSaleInfo(NULL, this); - onCommitSaleType(NULL, this); - onCommitIncludeInSearch(getChild("search_check"), this); -} - -// removes keyboard focus so that all fields can be updated -// and then restored focus -void LLSidepanelTaskInfo::refreshAll() -{ - // update UI as soon as we have an object - // but remove keyboard focus first so fields are free to update - LLFocusableElement* focus = NULL; - if (hasFocus()) - { - focus = gFocusMgr.getKeyboardFocus(); - setFocus(false); - } - refresh(); - if (focus) - { - focus->setFocus(true); - } -} - - -void LLSidepanelTaskInfo::setObjectSelection(LLObjectSelectionHandle selection) -{ - mObjectSelection = selection; - refreshAll(); -} - -LLSidepanelTaskInfo* LLSidepanelTaskInfo::getActivePanel() -{ - return sActivePanel; -} - -void LLSidepanelTaskInfo::dirty() -{ - mIsDirty = true; -} - -// static -void LLSidepanelTaskInfo::onIdle( void* user_data ) -{ - LLSidepanelTaskInfo* self = reinterpret_cast(user_data); - - if( self->mIsDirty ) - { - self->refresh(); - self->mIsDirty = false; - } -} - -LLViewerObject* LLSidepanelTaskInfo::getObject() -{ - if (!mObject->isDead()) - return mObject; - return NULL; -} - -LLViewerObject* LLSidepanelTaskInfo::getFirstSelectedObject() -{ - LLSelectNode *node = mObjectSelection->getFirstRootNode(); - if (node) - { - return node->getObject(); - } - return NULL; -} - -const LLUUID& LLSidepanelTaskInfo::getSelectedUUID() -{ - const LLViewerObject* obj = getFirstSelectedObject(); - if (obj) - { - return obj->getID(); - } - return LLUUID::null; -} +/** + * @file llsidepaneltaskinfo.cpp + * @brief LLSidepanelTaskInfo class implementation + * This class represents the panel in the build view for + * viewing/editing object names, owners, permissions, etc. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llsidepaneltaskinfo.h" + +#include "lluuid.h" +#include "llpermissions.h" +#include "llcategory.h" +#include "llclickaction.h" +#include "llfocusmgr.h" +#include "llnotificationsutil.h" +#include "llstring.h" + +#include "llviewerwindow.h" +#include "llresmgr.h" +#include "lltextbox.h" +#include "llbutton.h" +#include "llcallbacklist.h" +#include "llcheckboxctrl.h" +#include "llviewerobject.h" +#include "llselectmgr.h" +#include "llagent.h" +#include "llstatusbar.h" // for getBalance() +#include "lllineeditor.h" +#include "llcombobox.h" +#include "lluiconstants.h" +#include "lldbstrings.h" +#include "llfloatergroups.h" +#include "llfloaterreg.h" +#include "llavataractions.h" +#include "llnamebox.h" +#include "llviewercontrol.h" +#include "llviewermenu.h" +#include "lluictrlfactory.h" +#include "llspinctrl.h" +#include "roles_constants.h" +#include "llgroupactions.h" +#include "lltextbase.h" +#include "llstring.h" +#include "lltrans.h" + +///---------------------------------------------------------------------------- +/// Class llsidepaneltaskinfo +///---------------------------------------------------------------------------- + +LLSidepanelTaskInfo* LLSidepanelTaskInfo::sActivePanel = NULL; + +static LLPanelInjector t_task_info("sidepanel_task_info"); + +static std::string click_action_to_string_value(U8 click_action) +{ + switch (click_action) + { + case CLICK_ACTION_TOUCH: + return "Touch"; + case CLICK_ACTION_SIT: + return "Sit"; + case CLICK_ACTION_BUY: + return "Buy"; + case CLICK_ACTION_PAY: + return "Pay"; + case CLICK_ACTION_OPEN: + return "Open"; + case CLICK_ACTION_ZOOM: + return "Zoom"; + case CLICK_ACTION_DISABLED: + return "None"; + case CLICK_ACTION_IGNORE: + return "Ignore"; + default: + return "Touch"; + } + return "Touch"; +} + +// Default constructor +LLSidepanelTaskInfo::LLSidepanelTaskInfo() + : mVisibleDebugPermissions(true) // space was allocated by default +{ + setMouseOpaque(false); + mSelectionUpdateSlot = LLSelectMgr::instance().mUpdateSignal.connect(boost::bind(&LLSidepanelTaskInfo::refreshAll, this)); + gIdleCallbacks.addFunction(&LLSidepanelTaskInfo::onIdle, (void*)this); +} + + +LLSidepanelTaskInfo::~LLSidepanelTaskInfo() +{ + if (sActivePanel == this) + sActivePanel = NULL; + gIdleCallbacks.deleteFunction(&LLSidepanelTaskInfo::onIdle, (void*)this); + + if (mSelectionUpdateSlot.connected()) + { + mSelectionUpdateSlot.disconnect(); + } +} + +// virtual +bool LLSidepanelTaskInfo::postBuild() +{ + mOpenBtn = getChild("open_btn"); + mOpenBtn->setClickedCallback(boost::bind(&LLSidepanelTaskInfo::onOpenButtonClicked, this)); + mPayBtn = getChild("pay_btn"); + mPayBtn->setClickedCallback(boost::bind(&LLSidepanelTaskInfo::onPayButtonClicked, this)); + mBuyBtn = getChild("buy_btn"); + mBuyBtn->setClickedCallback(boost::bind(&handle_buy)); + mDetailsBtn = getChild("details_btn"); + mDetailsBtn->setClickedCallback(boost::bind(&LLSidepanelTaskInfo::onDetailsButtonClicked, this)); + + mDeedBtn = getChild("button deed"); + + mLabelGroupName = getChild("Group Name Proxy"); + + childSetCommitCallback("Object Name", LLSidepanelTaskInfo::onCommitName,this); + getChild("Object Name")->setPrevalidate(LLTextValidate::validateASCIIPrintableNoPipe); + childSetCommitCallback("Object Description", LLSidepanelTaskInfo::onCommitDesc,this); + getChild("Object Description")->setPrevalidate(LLTextValidate::validateASCIIPrintableNoPipe); + getChild("button set group")->setCommitCallback(boost::bind(&LLSidepanelTaskInfo::onClickGroup,this)); + childSetCommitCallback("checkbox share with group", &LLSidepanelTaskInfo::onCommitGroupShare,this); + childSetAction("button deed", &LLSidepanelTaskInfo::onClickDeedToGroup,this); + childSetCommitCallback("checkbox allow everyone move", &LLSidepanelTaskInfo::onCommitEveryoneMove,this); + childSetCommitCallback("checkbox allow everyone copy", &LLSidepanelTaskInfo::onCommitEveryoneCopy,this); + childSetCommitCallback("checkbox for sale", &LLSidepanelTaskInfo::onCommitSaleInfo,this); + childSetCommitCallback("sale type", &LLSidepanelTaskInfo::onCommitSaleType,this); + childSetCommitCallback("Edit Cost", &LLSidepanelTaskInfo::onCommitSaleInfo, this); + childSetCommitCallback("checkbox next owner can modify", &LLSidepanelTaskInfo::onCommitNextOwnerModify,this); + childSetCommitCallback("checkbox next owner can copy", &LLSidepanelTaskInfo::onCommitNextOwnerCopy,this); + childSetCommitCallback("checkbox next owner can transfer", &LLSidepanelTaskInfo::onCommitNextOwnerTransfer,this); + childSetCommitCallback("clickaction", &LLSidepanelTaskInfo::onCommitClickAction,this); + childSetCommitCallback("search_check", &LLSidepanelTaskInfo::onCommitIncludeInSearch,this); + + mDAPermModify = getChild("perm_modify"); + mDACreatorName = getChild("Creator Name"); + mDAOwner = getChildView("Owner:"); + mDAOwnerName = getChild("Owner Name"); + mDAButtonSetGroup = getChildView("button set group"); + mDAObjectName = getChild("Object Name"); + mDAName = getChildView("Name:"); + mDADescription = getChildView("Description:"); + mDAObjectDescription = getChild("Object Description"); + mDACheckboxShareWithGroup = getChild("checkbox share with group"); + mDAButtonDeed = getChildView("button deed"); + mDACheckboxAllowEveryoneMove = getChild("checkbox allow everyone move"); + mDACheckboxAllowEveryoneCopy = getChild("checkbox allow everyone copy"); + mDACheckboxNextOwnerCanModify = getChild("checkbox next owner can modify"); + mDACheckboxNextOwnerCanCopy = getChild("checkbox next owner can copy"); + mDACheckboxNextOwnerCanTransfer = getChild("checkbox next owner can transfer"); + mDACheckboxForSale = getChild("checkbox for sale"); + mDASearchCheck = getChild("search_check"); + mDAComboSaleType = getChild("sale type"); + mDAEditCost = getChild("Edit Cost"); + mDALabelClickAction = getChildView("label click action"); + mDAComboClickAction = getChild("clickaction"); + mDAPathfindingAttributes = getChild("pathfinding_attributes_value"); + mDAB = getChild("B:"); + mDAO = getChild("O:"); + mDAG = getChild("G:"); + mDAE = getChild("E:"); + mDAN = getChild("N:"); + mDAF = getChild("F:"); + + return true; +} + +/*virtual*/ void LLSidepanelTaskInfo::onVisibilityChange(bool visible) +{ + if (visible) + { + sActivePanel = this; + mObject = getFirstSelectedObject(); + } + else + { + sActivePanel = NULL; + // drop selection reference + mObjectSelection = NULL; + } +} + + +void LLSidepanelTaskInfo::disableAll() +{ + mDACreatorName->setValue(LLStringUtil::null); + mDACreatorName->setEnabled(false); + + mDAOwner->setEnabled(false); + mDAOwnerName->setValue(LLStringUtil::null); + mDAOwnerName->setEnabled(false); + + mDAObjectName->setValue(LLStringUtil::null); + mDAObjectName->setEnabled(false); + mDAName->setEnabled(false); + mDADescription->setEnabled(false); + mDAObjectDescription->setValue(LLStringUtil::null); + mDAObjectDescription->setEnabled(false); + + mDAPathfindingAttributes->setEnabled(false); + mDAPathfindingAttributes->setValue(LLStringUtil::null); + + mDAButtonSetGroup->setEnabled(false); + mDAButtonDeed->setEnabled(false); + + mDAPermModify->setEnabled(false); + mDAPermModify->setValue(LLStringUtil::null); + mDAEditCost->setValue(LLStringUtil::null); + mDAComboSaleType->setValue(LLSaleInfo::FS_COPY); + + disablePermissions(); + + if (mVisibleDebugPermissions) + { + mDAB->setVisible(false); + mDAO->setVisible(false); + mDAG->setVisible(false); + mDAE->setVisible(false); + mDAN->setVisible(false); + mDAF->setVisible(false); + + LLFloater* parent_floater = gFloaterView->getParentFloater(this); + LLRect parent_rect = parent_floater->getRect(); + LLRect debug_rect = mDAB->getRect(); + // use double the debug rect for padding (since it isn't trivial to extract top_pad) + parent_floater->reshape(parent_rect.getWidth(), parent_rect.getHeight() - (debug_rect.getHeight() * 2)); + mVisibleDebugPermissions = false; + } + + mOpenBtn->setEnabled(false); + mPayBtn->setEnabled(false); + mBuyBtn->setEnabled(false); +} + +void LLSidepanelTaskInfo::disablePermissions() +{ + mDACheckboxShareWithGroup->setValue(false); + mDACheckboxShareWithGroup->setEnabled(false); + + mDACheckboxAllowEveryoneMove->setValue(false); + mDACheckboxAllowEveryoneMove->setEnabled(false); + mDACheckboxAllowEveryoneCopy->setValue(false); + mDACheckboxAllowEveryoneCopy->setEnabled(false); + + //Next owner can: + mDACheckboxNextOwnerCanModify->setValue(false); + mDACheckboxNextOwnerCanModify->setEnabled(false); + mDACheckboxNextOwnerCanCopy->setValue(false); + mDACheckboxNextOwnerCanCopy->setEnabled(false); + mDACheckboxNextOwnerCanTransfer->setValue(false); + mDACheckboxNextOwnerCanTransfer->setEnabled(false); + + //checkbox for sale + mDACheckboxForSale->setValue(false); + mDACheckboxForSale->setEnabled(false); + + //checkbox include in search + mDASearchCheck->setValue(false); + mDASearchCheck->setEnabled(false); + + mDAComboSaleType->setEnabled(false); + + mDAEditCost->setEnabled(false); + + mDALabelClickAction->setEnabled(false); + if (mDAComboClickAction) + { + mDAComboClickAction->setEnabled(false); + mDAComboClickAction->clear(); + } +} + +void LLSidepanelTaskInfo::refresh() +{ + mIsDirty = false; + + LLButton* btn_deed_to_group = mDeedBtn; + if (btn_deed_to_group) + { + std::string deedText; + if (gWarningSettings.getBOOL("DeedObject")) + { + deedText = getString("text deed continued"); + } + else + { + deedText = getString("text deed"); + } + btn_deed_to_group->setLabelSelected(deedText); + btn_deed_to_group->setLabelUnselected(deedText); + } + + bool root_selected = true; + LLSelectNode* nodep = mObjectSelection->getFirstRootNode(); + S32 object_count = mObjectSelection->getRootObjectCount(); + if (!nodep || (object_count == 0)) + { + nodep = mObjectSelection->getFirstNode(); + object_count = mObjectSelection->getObjectCount(); + root_selected = false; + } + + LLViewerObject* objectp = NULL; + if (nodep) + { + objectp = nodep->getObject(); + } + + // ...nothing selected + if (!nodep || !objectp) + { + disableAll(); + return; + } + + // figure out a few variables + const bool is_one_object = (object_count == 1); + + // BUG: fails if a root and non-root are both single-selected. + const bool is_perm_modify = (mObjectSelection->getFirstRootNode() && LLSelectMgr::getInstance()->selectGetRootsModify()) || + LLSelectMgr::getInstance()->selectGetModify(); + const bool is_nonpermanent_enforced = (mObjectSelection->getFirstRootNode() && LLSelectMgr::getInstance()->selectGetRootsNonPermanentEnforced()) || + LLSelectMgr::getInstance()->selectGetNonPermanentEnforced(); + + S32 string_index = 0; + std::string MODIFY_INFO_STRINGS[] = + { + getString("text modify info 1"), + getString("text modify info 2"), + getString("text modify info 3"), + getString("text modify info 4"), + getString("text modify info 5"), + getString("text modify info 6") + }; + if (!is_perm_modify) + { + string_index += 2; + } + else if (!is_nonpermanent_enforced) + { + string_index += 4; + } + if (!is_one_object) + { + ++string_index; + } + getChildView("perm_modify")->setEnabled(true); + getChild("perm_modify")->setValue(MODIFY_INFO_STRINGS[string_index]); + + std::string pfAttrName; + + if ((mObjectSelection->getFirstRootNode() + && LLSelectMgr::getInstance()->selectGetRootsNonPathfinding()) + || LLSelectMgr::getInstance()->selectGetNonPathfinding()) + { + pfAttrName = "Pathfinding_Object_Attr_None"; + } + else if ((mObjectSelection->getFirstRootNode() + && LLSelectMgr::getInstance()->selectGetRootsPermanent()) + || LLSelectMgr::getInstance()->selectGetPermanent()) + { + pfAttrName = "Pathfinding_Object_Attr_Permanent"; + } + else if ((mObjectSelection->getFirstRootNode() + && LLSelectMgr::getInstance()->selectGetRootsCharacter()) + || LLSelectMgr::getInstance()->selectGetCharacter()) + { + pfAttrName = "Pathfinding_Object_Attr_Character"; + } + else + { + pfAttrName = "Pathfinding_Object_Attr_MultiSelect"; + } + + mDAPathfindingAttributes->setEnabled(true); + mDAPathfindingAttributes->setValue(LLTrans::getString(pfAttrName)); + + // Update creator text field + getChildView("Creator:")->setEnabled(true); + + std::string creator_name; + LLUUID creator_id; + LLSelectMgr::getInstance()->selectGetCreator(creator_id, creator_name); + + if(creator_id != mCreatorID ) + { + mDACreatorName->setValue(creator_name); + mCreatorID = creator_id; + } + if(mDACreatorName->getValue().asString() == LLStringUtil::null) + { + mDACreatorName->setValue(creator_name); + } + mDACreatorName->setEnabled(true); + + // Update owner text field + getChildView("Owner:")->setEnabled(true); + + std::string owner_name; + LLUUID owner_id; + const bool owners_identical = LLSelectMgr::getInstance()->selectGetOwner(owner_id, owner_name); + if (owner_id.isNull()) + { + if (LLSelectMgr::getInstance()->selectIsGroupOwned()) + { + // Group owned already displayed by selectGetOwner + } + else + { + // Display last owner if public + std::string last_owner_name; + LLSelectMgr::getInstance()->selectGetLastOwner(mLastOwnerID, last_owner_name); + + // It should never happen that the last owner is null and the owner + // is null, but it seems to be a bug in the simulator right now. JC + if (!mLastOwnerID.isNull() && !last_owner_name.empty()) + { + owner_name.append(", last "); + owner_name.append(last_owner_name); + } + } + } + + if(owner_id.isNull() || (owner_id != mOwnerID)) + { + mDAOwnerName->setValue(owner_name); + mOwnerID = owner_id; + } + if(mDAOwnerName->getValue().asString() == LLStringUtil::null) + { + mDAOwnerName->setValue(owner_name); + } + + getChildView("Owner Name")->setEnabled(true); + + // update group text field + getChildView("Group:")->setEnabled(true); + getChild("Group Name")->setValue(LLStringUtil::null); + LLUUID group_id; + bool groups_identical = LLSelectMgr::getInstance()->selectGetGroup(group_id); + if (groups_identical) + { + if (mLabelGroupName) + { + mLabelGroupName->setNameID(group_id,true); + mLabelGroupName->setEnabled(true); + } + } + else + { + if (mLabelGroupName) + { + mLabelGroupName->setNameID(LLUUID::null, true); + mLabelGroupName->refresh(LLUUID::null, std::string(), true); + mLabelGroupName->setEnabled(false); + } + } + + getChildView("button set group")->setEnabled(owners_identical && (mOwnerID == gAgent.getID()) && is_nonpermanent_enforced); + + getChildView("Name:")->setEnabled(true); + LLLineEditor* LineEditorObjectName = getChild("Object Name"); + getChildView("Description:")->setEnabled(true); + LLLineEditor* LineEditorObjectDesc = getChild("Object Description"); + + if (is_one_object) + { + if (!LineEditorObjectName->hasFocus()) + { + getChild("Object Name")->setValue(nodep->mName); + } + + if (LineEditorObjectDesc) + { + if (!LineEditorObjectDesc->hasFocus()) + { + LineEditorObjectDesc->setText(nodep->mDescription); + } + } + } + else + { + getChild("Object Name")->setValue(LLStringUtil::null); + LineEditorObjectDesc->setText(LLStringUtil::null); + } + + // figure out the contents of the name, description, & category + bool edit_name_desc = false; + if (is_one_object && objectp->permModify() && !objectp->isPermanentEnforced()) + { + edit_name_desc = true; + } + if (edit_name_desc) + { + getChildView("Object Name")->setEnabled(true); + getChildView("Object Description")->setEnabled(true); + } + else + { + getChildView("Object Name")->setEnabled(false); + getChildView("Object Description")->setEnabled(false); + } + + S32 total_sale_price = 0; + S32 individual_sale_price = 0; + bool is_for_sale_mixed = false; + bool is_sale_price_mixed = false; + U32 num_for_sale = false; + LLSelectMgr::getInstance()->selectGetAggregateSaleInfo(num_for_sale, + is_for_sale_mixed, + is_sale_price_mixed, + total_sale_price, + individual_sale_price); + + const bool self_owned = (gAgent.getID() == mOwnerID); + const bool group_owned = LLSelectMgr::getInstance()->selectIsGroupOwned() ; + const bool public_owned = (mOwnerID.isNull() && !LLSelectMgr::getInstance()->selectIsGroupOwned()); + const bool can_transfer = LLSelectMgr::getInstance()->selectGetRootsTransfer(); + const bool can_copy = LLSelectMgr::getInstance()->selectGetRootsCopy(); + + if (!owners_identical) + { + getChildView("Cost")->setEnabled(false); + getChild("Edit Cost")->setValue(LLStringUtil::null); + getChildView("Edit Cost")->setEnabled(false); + } + // You own these objects. + else if (self_owned || (group_owned && gAgent.hasPowerInGroup(group_id,GP_OBJECT_SET_SALE))) + { + LLSpinCtrl *edit_price = getChild("Edit Cost"); + + // If there are multiple items for sale then set text to PRICE PER UNIT. + if (num_for_sale > 1) + { + std::string label_text = is_sale_price_mixed? "Cost Mixed" :"Cost Per Unit"; + edit_price->setLabel(getString(label_text)); + } + else + { + edit_price->setLabel(getString("Cost Default")); + } + + if (!edit_price->hasFocus()) + { + // If the sale price is mixed then set the cost to MIXED, otherwise + // set to the actual cost. + if ((num_for_sale > 0) && is_for_sale_mixed) + { + edit_price->setTentative(true); + } + else if ((num_for_sale > 0) && is_sale_price_mixed) + { + edit_price->setTentative(true); + } + else + { + edit_price->setValue(individual_sale_price); + } + } + // The edit fields are only enabled if you can sell this object + // and the sale price is not mixed. + bool enable_edit = (num_for_sale && can_transfer) ? !is_for_sale_mixed : false; + getChildView("Cost")->setEnabled(enable_edit); + getChildView("Edit Cost")->setEnabled(enable_edit); + } + // Someone, not you, owns these objects. + else if (!public_owned) + { + getChildView("Cost")->setEnabled(false); + getChildView("Edit Cost")->setEnabled(false); + + // Don't show a price if none of the items are for sale. + if (num_for_sale) + getChild("Edit Cost")->setValue(llformat("%d",total_sale_price)); + else + getChild("Edit Cost")->setValue(LLStringUtil::null); + + // If multiple items are for sale, set text to TOTAL PRICE. + if (num_for_sale > 1) + getChild("Edit Cost")->setLabel(getString("Cost Total")); + else + getChild("Edit Cost")->setLabel(getString("Cost Default")); + } + // This is a public object. + else + { + getChildView("Cost")->setEnabled(false); + getChild("Edit Cost")->setLabel(getString("Cost Default")); + getChild("Edit Cost")->setValue(LLStringUtil::null); + getChildView("Edit Cost")->setEnabled(false); + } + + // Enable and disable the permissions checkboxes + // based on who owns the object. + // TODO: Creator permissions + + U32 base_mask_on = 0; + U32 base_mask_off = 0; + U32 owner_mask_off = 0; + U32 owner_mask_on = 0; + U32 group_mask_on = 0; + U32 group_mask_off = 0; + U32 everyone_mask_on = 0; + U32 everyone_mask_off = 0; + U32 next_owner_mask_on = 0; + U32 next_owner_mask_off = 0; + + bool valid_base_perms = LLSelectMgr::getInstance()->selectGetPerm(PERM_BASE, + &base_mask_on, + &base_mask_off); + //bool valid_owner_perms =// + LLSelectMgr::getInstance()->selectGetPerm(PERM_OWNER, + &owner_mask_on, + &owner_mask_off); + bool valid_group_perms = LLSelectMgr::getInstance()->selectGetPerm(PERM_GROUP, + &group_mask_on, + &group_mask_off); + + bool valid_everyone_perms = LLSelectMgr::getInstance()->selectGetPerm(PERM_EVERYONE, + &everyone_mask_on, + &everyone_mask_off); + + bool valid_next_perms = LLSelectMgr::getInstance()->selectGetPerm(PERM_NEXT_OWNER, + &next_owner_mask_on, + &next_owner_mask_off); + + + if (gSavedSettings.getBOOL("DebugPermissions") ) + { + if (valid_base_perms) + { + mDAB->setValue("B: " + mask_to_string(base_mask_on)); + mDAB->setVisible( true); + + mDAO->setValue("O: " + mask_to_string(owner_mask_on)); + mDAO->setVisible( true); + + mDAG->setValue("G: " + mask_to_string(group_mask_on)); + mDAG->setVisible( true); + + mDAE->setValue("E: " + mask_to_string(everyone_mask_on)); + mDAE->setVisible( true); + + mDAN->setValue("N: " + mask_to_string(next_owner_mask_on)); + mDAN->setVisible( true); + } + + U32 flag_mask = 0x0; + if (objectp->permMove()) flag_mask |= PERM_MOVE; + if (objectp->permModify()) flag_mask |= PERM_MODIFY; + if (objectp->permCopy()) flag_mask |= PERM_COPY; + if (objectp->permTransfer()) flag_mask |= PERM_TRANSFER; + + mDAF->setValue("F:" + mask_to_string(flag_mask)); + mDAF->setVisible(true); + + if (!mVisibleDebugPermissions) + { + LLFloater* parent_floater = gFloaterView->getParentFloater(this); + LLRect parent_rect = parent_floater->getRect(); + LLRect debug_rect = mDAB->getRect(); + // use double the debug rect for padding (since it isn't trivial to extract top_pad) + parent_floater->reshape(parent_rect.getWidth(), parent_rect.getHeight() + (debug_rect.getHeight() * 2)); + mVisibleDebugPermissions = true; + } + } + else if (mVisibleDebugPermissions) + { + mDAB->setVisible(false); + mDAO->setVisible(false); + mDAG->setVisible(false); + mDAE->setVisible(false); + mDAN->setVisible(false); + mDAF->setVisible(false); + + LLFloater* parent_floater = gFloaterView->getParentFloater(this); + LLRect parent_rect = parent_floater->getRect(); + LLRect debug_rect = mDAB->getRect(); + // use double the debug rect for padding (since it isn't trivial to extract top_pad) + parent_floater->reshape(parent_rect.getWidth(), parent_rect.getHeight() - (debug_rect.getHeight() * 2)); + mVisibleDebugPermissions = false; + } + + bool has_change_perm_ability = false; + bool has_change_sale_ability = false; + + if (valid_base_perms && is_nonpermanent_enforced && + (self_owned || (group_owned && gAgent.hasPowerInGroup(group_id, GP_OBJECT_MANIPULATE)))) + { + has_change_perm_ability = true; + } + if (valid_base_perms && is_nonpermanent_enforced && + (self_owned || (group_owned && gAgent.hasPowerInGroup(group_id, GP_OBJECT_SET_SALE)))) + { + has_change_sale_ability = true; + } + + if (!has_change_perm_ability && !has_change_sale_ability && !root_selected) + { + // ...must select root to choose permissions + getChild("perm_modify")->setValue(getString("text modify warning")); + } + + if (has_change_perm_ability) + { + getChildView("checkbox share with group")->setEnabled(true); + getChildView("checkbox allow everyone move")->setEnabled(owner_mask_on & PERM_MOVE); + getChildView("checkbox allow everyone copy")->setEnabled(owner_mask_on & PERM_COPY && owner_mask_on & PERM_TRANSFER); + } + else + { + getChildView("checkbox share with group")->setEnabled(false); + getChildView("checkbox allow everyone move")->setEnabled(false); + getChildView("checkbox allow everyone copy")->setEnabled(false); + } + + if (has_change_sale_ability && (owner_mask_on & PERM_TRANSFER)) + { + getChildView("checkbox for sale")->setEnabled(can_transfer || (!can_transfer && num_for_sale)); + // Set the checkbox to tentative if the prices of each object selected + // are not the same. + getChild("checkbox for sale")->setTentative( is_for_sale_mixed); + getChildView("sale type")->setEnabled(num_for_sale && can_transfer && !is_sale_price_mixed); + + getChildView("checkbox next owner can modify")->setEnabled(base_mask_on & PERM_MODIFY); + getChildView("checkbox next owner can copy")->setEnabled(base_mask_on & PERM_COPY); + getChildView("checkbox next owner can transfer")->setEnabled(next_owner_mask_on & PERM_COPY); + } + else + { + getChildView("checkbox for sale")->setEnabled(false); + getChildView("sale type")->setEnabled(false); + + getChildView("checkbox next owner can modify")->setEnabled(false); + getChildView("checkbox next owner can copy")->setEnabled(false); + getChildView("checkbox next owner can transfer")->setEnabled(false); + } + + if (valid_group_perms) + { + if ((group_mask_on & PERM_COPY) && (group_mask_on & PERM_MODIFY) && (group_mask_on & PERM_MOVE)) + { + getChild("checkbox share with group")->setValue(true); + getChild("checkbox share with group")->setTentative( false); + getChildView("button deed")->setEnabled(gAgent.hasPowerInGroup(group_id, GP_OBJECT_DEED) && (owner_mask_on & PERM_TRANSFER) && !group_owned && can_transfer); + } + else if ((group_mask_off & PERM_COPY) && (group_mask_off & PERM_MODIFY) && (group_mask_off & PERM_MOVE)) + { + getChild("checkbox share with group")->setValue(false); + getChild("checkbox share with group")->setTentative( false); + getChildView("button deed")->setEnabled(false); + } + else + { + getChild("checkbox share with group")->setValue(true); + getChild("checkbox share with group")->setTentative( true); + getChildView("button deed")->setEnabled(gAgent.hasPowerInGroup(group_id, GP_OBJECT_DEED) && (group_mask_on & PERM_MOVE) && (owner_mask_on & PERM_TRANSFER) && !group_owned && can_transfer); + } + } + + if (valid_everyone_perms) + { + // Move + if (everyone_mask_on & PERM_MOVE) + { + getChild("checkbox allow everyone move")->setValue(true); + getChild("checkbox allow everyone move")->setTentative( false); + } + else if (everyone_mask_off & PERM_MOVE) + { + getChild("checkbox allow everyone move")->setValue(false); + getChild("checkbox allow everyone move")->setTentative( false); + } + else + { + getChild("checkbox allow everyone move")->setValue(true); + getChild("checkbox allow everyone move")->setTentative( true); + } + + // Copy == everyone can't copy + if (everyone_mask_on & PERM_COPY) + { + getChild("checkbox allow everyone copy")->setValue(true); + getChild("checkbox allow everyone copy")->setTentative( !can_copy || !can_transfer); + } + else if (everyone_mask_off & PERM_COPY) + { + getChild("checkbox allow everyone copy")->setValue(false); + getChild("checkbox allow everyone copy")->setTentative( false); + } + else + { + getChild("checkbox allow everyone copy")->setValue(true); + getChild("checkbox allow everyone copy")->setTentative( true); + } + } + + if (valid_next_perms) + { + // Modify == next owner canot modify + if (next_owner_mask_on & PERM_MODIFY) + { + getChild("checkbox next owner can modify")->setValue(true); + getChild("checkbox next owner can modify")->setTentative( false); + } + else if (next_owner_mask_off & PERM_MODIFY) + { + getChild("checkbox next owner can modify")->setValue(false); + getChild("checkbox next owner can modify")->setTentative( false); + } + else + { + getChild("checkbox next owner can modify")->setValue(true); + getChild("checkbox next owner can modify")->setTentative( true); + } + + // Copy == next owner cannot copy + if (next_owner_mask_on & PERM_COPY) + { + getChild("checkbox next owner can copy")->setValue(true); + getChild("checkbox next owner can copy")->setTentative( !can_copy); + } + else if (next_owner_mask_off & PERM_COPY) + { + getChild("checkbox next owner can copy")->setValue(false); + getChild("checkbox next owner can copy")->setTentative( false); + } + else + { + getChild("checkbox next owner can copy")->setValue(true); + getChild("checkbox next owner can copy")->setTentative( true); + } + + // Transfer == next owner cannot transfer + if (next_owner_mask_on & PERM_TRANSFER) + { + getChild("checkbox next owner can transfer")->setValue(true); + getChild("checkbox next owner can transfer")->setTentative( !can_transfer); + } + else if (next_owner_mask_off & PERM_TRANSFER) + { + getChild("checkbox next owner can transfer")->setValue(false); + getChild("checkbox next owner can transfer")->setTentative( false); + } + else + { + getChild("checkbox next owner can transfer")->setValue(true); + getChild("checkbox next owner can transfer")->setTentative( true); + } + } + + // reflect sale information + LLSaleInfo sale_info; + bool valid_sale_info = LLSelectMgr::getInstance()->selectGetSaleInfo(sale_info); + LLSaleInfo::EForSale sale_type = sale_info.getSaleType(); + + LLComboBox* combo_sale_type = getChild("sale type"); + if (valid_sale_info) + { + combo_sale_type->setValue( sale_type == LLSaleInfo::FS_NOT ? LLSaleInfo::FS_COPY : sale_type); + combo_sale_type->setTentative( false); // unfortunately this doesn't do anything at the moment. + } + else + { + // default option is sell copy, determined to be safest + combo_sale_type->setValue( LLSaleInfo::FS_COPY); + combo_sale_type->setTentative( true); // unfortunately this doesn't do anything at the moment. + } + + getChild("checkbox for sale")->setValue((num_for_sale != 0)); + + // HACK: There are some old objects in world that are set for sale, + // but are no-transfer. We need to let users turn for-sale off, but only + // if for-sale is set. + bool cannot_actually_sell = !can_transfer || (!can_copy && sale_type == LLSaleInfo::FS_COPY); + if (cannot_actually_sell) + { + if (num_for_sale && has_change_sale_ability) + { + getChildView("checkbox for sale")->setEnabled(true); + } + } + + // Check search status of objects + const bool all_volume = LLSelectMgr::getInstance()->selectionAllPCode( LL_PCODE_VOLUME ); + bool include_in_search; + const bool all_include_in_search = LLSelectMgr::getInstance()->selectionGetIncludeInSearch(&include_in_search); + getChildView("search_check")->setEnabled(has_change_sale_ability && all_volume); + getChild("search_check")->setValue(include_in_search); + getChild("search_check")->setTentative(!all_include_in_search); + + // Click action (touch, sit, buy) + U8 click_action = 0; + if (LLSelectMgr::getInstance()->selectionGetClickAction(&click_action)) + { + getChild("clickaction")->setValue(click_action_to_string_value(click_action)); + } + getChildView("label click action")->setEnabled(is_perm_modify && is_nonpermanent_enforced && all_volume); + getChildView("clickaction")->setEnabled(is_perm_modify && is_nonpermanent_enforced && all_volume); + + updateVerbs(); +} + + +// static +void LLSidepanelTaskInfo::onClickClaim(void*) +{ + // try to claim ownership + LLSelectMgr::getInstance()->sendOwner(gAgent.getID(), gAgent.getGroupID()); +} + +// static +void LLSidepanelTaskInfo::onClickRelease(void*) +{ + // try to release ownership + LLSelectMgr::getInstance()->sendOwner(LLUUID::null, LLUUID::null); +} + +void LLSidepanelTaskInfo::onClickGroup() +{ + LLUUID owner_id; + std::string name; + bool owners_identical = LLSelectMgr::getInstance()->selectGetOwner(owner_id, name); + LLFloater* parent_floater = gFloaterView->getParentFloater(this); + + if (owners_identical && (owner_id == gAgent.getID())) + { + LLFloaterGroupPicker* fg = LLFloaterReg::showTypedInstance("group_picker", LLSD(gAgent.getID())); + if (fg) + { + fg->setSelectGroupCallback( boost::bind(&LLSidepanelTaskInfo::cbGroupID, this, _1) ); + if (parent_floater) + { + LLRect new_rect = gFloaterView->findNeighboringPosition(parent_floater, fg); + fg->setOrigin(new_rect.mLeft, new_rect.mBottom); + parent_floater->addDependentFloater(fg); + } + } + } +} + +void LLSidepanelTaskInfo::cbGroupID(LLUUID group_id) +{ + if (mLabelGroupName) + { + mLabelGroupName->setNameID(group_id, true); + } + LLSelectMgr::getInstance()->sendGroup(group_id); +} + +static bool callback_deed_to_group(const LLSD& notification, const LLSD& response) +{ + const S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) + { + LLUUID group_id; + const bool groups_identical = LLSelectMgr::getInstance()->selectGetGroup(group_id); + if (group_id.notNull() && groups_identical && (gAgent.hasPowerInGroup(group_id, GP_OBJECT_DEED))) + { + LLSelectMgr::getInstance()->sendOwner(LLUUID::null, group_id, false); + } + } + return false; +} + +void LLSidepanelTaskInfo::onClickDeedToGroup(void *data) +{ + LLNotificationsUtil::add("DeedObjectToGroup", LLSD(), LLSD(), callback_deed_to_group); +} + +///---------------------------------------------------------------------------- +/// Permissions checkboxes +///---------------------------------------------------------------------------- + +// static +void LLSidepanelTaskInfo::onCommitPerm(LLUICtrl *ctrl, void *data, U8 field, U32 perm) +{ + LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getFirstRootObject(); + if(!object) return; + + // Checkbox will have toggled itself + // LLSidepanelTaskInfo* self = (LLSidepanelTaskInfo*)data; + LLCheckBoxCtrl *check = (LLCheckBoxCtrl *)ctrl; + bool new_state = check->get(); + + LLSelectMgr::getInstance()->selectionSetObjectPermissions(field, new_state, perm); + + LLSidepanelTaskInfo* self = (LLSidepanelTaskInfo*)data; + if (self) + { + self->disablePermissions(); + } +} + +// static +void LLSidepanelTaskInfo::onCommitGroupShare(LLUICtrl *ctrl, void *data) +{ + onCommitPerm(ctrl, data, PERM_GROUP, PERM_MODIFY | PERM_MOVE | PERM_COPY); +} + +// static +void LLSidepanelTaskInfo::onCommitEveryoneMove(LLUICtrl *ctrl, void *data) +{ + onCommitPerm(ctrl, data, PERM_EVERYONE, PERM_MOVE); +} + + +// static +void LLSidepanelTaskInfo::onCommitEveryoneCopy(LLUICtrl *ctrl, void *data) +{ + onCommitPerm(ctrl, data, PERM_EVERYONE, PERM_COPY); +} + +// static +void LLSidepanelTaskInfo::onCommitNextOwnerModify(LLUICtrl* ctrl, void* data) +{ + //LL_INFOS() << "LLSidepanelTaskInfo::onCommitNextOwnerModify" << LL_ENDL; + onCommitPerm(ctrl, data, PERM_NEXT_OWNER, PERM_MODIFY); +} + +// static +void LLSidepanelTaskInfo::onCommitNextOwnerCopy(LLUICtrl* ctrl, void* data) +{ + //LL_INFOS() << "LLSidepanelTaskInfo::onCommitNextOwnerCopy" << LL_ENDL; + onCommitPerm(ctrl, data, PERM_NEXT_OWNER, PERM_COPY); +} + +// static +void LLSidepanelTaskInfo::onCommitNextOwnerTransfer(LLUICtrl* ctrl, void* data) +{ + //LL_INFOS() << "LLSidepanelTaskInfo::onCommitNextOwnerTransfer" << LL_ENDL; + onCommitPerm(ctrl, data, PERM_NEXT_OWNER, PERM_TRANSFER); +} + +// static +void LLSidepanelTaskInfo::onCommitName(LLUICtrl*, void* data) +{ + //LL_INFOS() << "LLSidepanelTaskInfo::onCommitName()" << LL_ENDL; + LLSidepanelTaskInfo* self = (LLSidepanelTaskInfo*)data; + LLLineEditor* tb = self->getChild("Object Name"); + if(tb) + { + LLSelectMgr::getInstance()->selectionSetObjectName(tb->getText()); +// LLSelectMgr::getInstance()->selectionSetObjectName(self->mLabelObjectName->getText()); + } +} + + +// static +void LLSidepanelTaskInfo::onCommitDesc(LLUICtrl*, void* data) +{ + //LL_INFOS() << "LLSidepanelTaskInfo::onCommitDesc()" << LL_ENDL; + LLSidepanelTaskInfo* self = (LLSidepanelTaskInfo*)data; + LLLineEditor* le = self->getChild("Object Description"); + if(le) + { + LLSelectMgr::getInstance()->selectionSetObjectDescription(le->getText()); + } +} + +// static +void LLSidepanelTaskInfo::onCommitSaleInfo(LLUICtrl*, void* data) +{ + LLSidepanelTaskInfo* self = (LLSidepanelTaskInfo*)data; + self->setAllSaleInfo(); +} + +// static +void LLSidepanelTaskInfo::onCommitSaleType(LLUICtrl*, void* data) +{ + LLSidepanelTaskInfo* self = (LLSidepanelTaskInfo*)data; + self->setAllSaleInfo(); +} + + +void LLSidepanelTaskInfo::setAllSaleInfo() +{ + LLSaleInfo::EForSale sale_type = LLSaleInfo::FS_NOT; + + LLCheckBoxCtrl *checkPurchase = getChild("checkbox for sale"); + + // Set the sale type if the object(s) are for sale. + if(checkPurchase && checkPurchase->get()) + { + sale_type = static_cast(getChild("sale type")->getValue().asInteger()); + } + + S32 price = -1; + + LLSpinCtrl *edit_price = getChild("Edit Cost"); + price = (edit_price->getTentative()) ? DEFAULT_PRICE : edit_price->getValue().asInteger(); + + // If somehow an invalid price, turn the sale off. + if (price < 0) + sale_type = LLSaleInfo::FS_NOT; + + LLSaleInfo old_sale_info; + LLSelectMgr::getInstance()->selectGetSaleInfo(old_sale_info); + + LLSaleInfo new_sale_info(sale_type, price); + LLSelectMgr::getInstance()->selectionSetObjectSaleInfo(new_sale_info); + + U8 old_click_action = 0; + LLSelectMgr::getInstance()->selectionGetClickAction(&old_click_action); + + if (old_sale_info.isForSale() + && !new_sale_info.isForSale() + && old_click_action == CLICK_ACTION_BUY) + { + // If turned off for-sale, make sure click-action buy is turned + // off as well + LLSelectMgr::getInstance()-> + selectionSetClickAction(CLICK_ACTION_TOUCH); + } + else if (new_sale_info.isForSale() + && !old_sale_info.isForSale() + && old_click_action == CLICK_ACTION_TOUCH) + { + // If just turning on for-sale, preemptively turn on one-click buy + // unless user have a different click action set + LLSelectMgr::getInstance()-> + selectionSetClickAction(CLICK_ACTION_BUY); + } +} + +struct LLSelectionPayable : public LLSelectedObjectFunctor +{ + virtual bool apply(LLViewerObject* obj) + { + // can pay if you or your parent has money() event in script + LLViewerObject* parent = (LLViewerObject*)obj->getParent(); + return (obj->flagTakesMoney() || + (parent && parent->flagTakesMoney())); + } +}; + +static U8 string_value_to_click_action(std::string p_value) +{ + if (p_value == "Touch") + return CLICK_ACTION_TOUCH; + if (p_value == "Sit") + return CLICK_ACTION_SIT; + if (p_value == "Buy") + return CLICK_ACTION_BUY; + if (p_value == "Pay") + return CLICK_ACTION_PAY; + if (p_value == "Open") + return CLICK_ACTION_OPEN; + if (p_value == "Zoom") + return CLICK_ACTION_ZOOM; + if (p_value == "None") + return CLICK_ACTION_DISABLED; + if (p_value == "Ignore") + return CLICK_ACTION_IGNORE; + return CLICK_ACTION_TOUCH; +} + +// static +void LLSidepanelTaskInfo::onCommitClickAction(LLUICtrl* ctrl, void*) +{ + LLComboBox* box = (LLComboBox*)ctrl; + if (!box) + return; + std::string value = box->getValue().asString(); + U8 click_action = string_value_to_click_action(value); + doClickAction(click_action); +} + +// static +void LLSidepanelTaskInfo::doClickAction(U8 click_action) +{ + if (click_action == CLICK_ACTION_BUY) + { + LLSaleInfo sale_info; + LLSelectMgr::getInstance()->selectGetSaleInfo(sale_info); + if (!sale_info.isForSale()) + { + LLNotificationsUtil::add("CantSetBuyObject"); + + // Set click action back to its old value + U8 click_action = 0; + LLSelectMgr::getInstance()->selectionGetClickAction(&click_action); + return; + } + } + else if (click_action == CLICK_ACTION_PAY) + { + // Verify object has script with money() handler + LLSelectionPayable payable; + bool can_pay = LLSelectMgr::getInstance()->getSelection()->applyToObjects(&payable); + if (!can_pay) + { + // Warn, but do it anyway. + LLNotificationsUtil::add("ClickActionNotPayable"); + } + else + { + handle_give_money_dialog(); + } + } + LLSelectMgr::getInstance()->selectionSetClickAction(click_action); +} + +// static +void LLSidepanelTaskInfo::onCommitIncludeInSearch(LLUICtrl* ctrl, void* data) +{ + LLCheckBoxCtrl* box = (LLCheckBoxCtrl*)ctrl; + llassert(box); + LLSelectMgr::getInstance()->selectionSetIncludeInSearch(box->get()); +} + +// virtual +void LLSidepanelTaskInfo::updateVerbs() +{ + LLSafeHandle object_selection = LLSelectMgr::getInstance()->getSelection(); + const bool any_selected = (object_selection->getNumNodes() > 0); + + mOpenBtn->setVisible(true); + mPayBtn->setVisible(true); + mBuyBtn->setVisible(true); + mDetailsBtn->setVisible(true); + + mOpenBtn->setEnabled(enable_object_open()); + mPayBtn->setEnabled(enable_pay_object()); + mBuyBtn->setEnabled(enable_buy_object()); + mDetailsBtn->setEnabled(any_selected); +} + +void LLSidepanelTaskInfo::onOpenButtonClicked() +{ + if (enable_object_open()) + { + handle_object_open(); + } +} + +void LLSidepanelTaskInfo::onPayButtonClicked() +{ + doClickAction(CLICK_ACTION_PAY); +} + +void LLSidepanelTaskInfo::onBuyButtonClicked() +{ + doClickAction(CLICK_ACTION_BUY); +} + +void LLSidepanelTaskInfo::onDetailsButtonClicked() +{ + LLFloaterReg::showInstance("inspect", LLSD()); +} + +// virtual +void LLSidepanelTaskInfo::save() +{ + onCommitGroupShare(getChild("checkbox share with group"), this); + onCommitEveryoneMove(getChild("checkbox allow everyone move"), this); + onCommitEveryoneCopy(getChild("checkbox allow everyone copy"), this); + onCommitNextOwnerModify(getChild("checkbox next owner can modify"), this); + onCommitNextOwnerCopy(getChild("checkbox next owner can copy"), this); + onCommitNextOwnerTransfer(getChild("checkbox next owner can transfer"), this); + onCommitName(getChild("Object Name"), this); + onCommitDesc(getChild("Object Description"), this); + onCommitSaleInfo(NULL, this); + onCommitSaleType(NULL, this); + onCommitIncludeInSearch(getChild("search_check"), this); +} + +// removes keyboard focus so that all fields can be updated +// and then restored focus +void LLSidepanelTaskInfo::refreshAll() +{ + // update UI as soon as we have an object + // but remove keyboard focus first so fields are free to update + LLFocusableElement* focus = NULL; + if (hasFocus()) + { + focus = gFocusMgr.getKeyboardFocus(); + setFocus(false); + } + refresh(); + if (focus) + { + focus->setFocus(true); + } +} + + +void LLSidepanelTaskInfo::setObjectSelection(LLObjectSelectionHandle selection) +{ + mObjectSelection = selection; + refreshAll(); +} + +LLSidepanelTaskInfo* LLSidepanelTaskInfo::getActivePanel() +{ + return sActivePanel; +} + +void LLSidepanelTaskInfo::dirty() +{ + mIsDirty = true; +} + +// static +void LLSidepanelTaskInfo::onIdle( void* user_data ) +{ + LLSidepanelTaskInfo* self = reinterpret_cast(user_data); + + if( self->mIsDirty ) + { + self->refresh(); + self->mIsDirty = false; + } +} + +LLViewerObject* LLSidepanelTaskInfo::getObject() +{ + if (!mObject->isDead()) + return mObject; + return NULL; +} + +LLViewerObject* LLSidepanelTaskInfo::getFirstSelectedObject() +{ + LLSelectNode *node = mObjectSelection->getFirstRootNode(); + if (node) + { + return node->getObject(); + } + return NULL; +} + +const LLUUID& LLSidepanelTaskInfo::getSelectedUUID() +{ + const LLViewerObject* obj = getFirstSelectedObject(); + if (obj) + { + return obj->getID(); + } + return LLUUID::null; +} diff --git a/indra/newview/llsidepaneltaskinfo.h b/indra/newview/llsidepaneltaskinfo.h index 441283da99..3816bb8381 100644 --- a/indra/newview/llsidepaneltaskinfo.h +++ b/indra/newview/llsidepaneltaskinfo.h @@ -1,170 +1,170 @@ -/** - * @file llsidepaneltaskinfo.h - * @brief LLSidepanelTaskInfo class header file - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLSIDEPANELTASKINFO_H -#define LL_LLSIDEPANELTASKINFO_H - -#include "lluuid.h" -#include "llpanel.h" -#include "llselectmgr.h" - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLSidepanelTaskInfo -// -// Panel for permissions of an object. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLCheckBoxCtrl; -class LLComboBox; -class LLNameBox; -class LLViewerObject; -class LLTextBase; - -class LLSidepanelTaskInfo : public LLPanel -{ -public: - LLSidepanelTaskInfo(); - virtual ~LLSidepanelTaskInfo(); - - bool postBuild() override; - void onVisibilityChange (bool new_visibility) override; - - void setObjectSelection(LLObjectSelectionHandle selection); - - const LLUUID& getSelectedUUID(); - LLViewerObject* getFirstSelectedObject(); - - static LLSidepanelTaskInfo *getActivePanel(); - void dirty(); - static void onIdle( void* user_data ); -protected: - void refresh() override; // refresh all labels as needed - void save(); - void updateVerbs(); - - void refreshAll(); // ignore current keyboard focus and update all fields - - // statics - static void onClickClaim(void*); - static void onClickRelease(void*); - void onClickGroup(); - void cbGroupID(LLUUID group_id); - static void onClickDeedToGroup(void*); - - static void onCommitPerm(LLUICtrl *ctrl, void *data, U8 field, U32 perm); - - static void onCommitGroupShare(LLUICtrl *ctrl, void *data); - - static void onCommitEveryoneMove(LLUICtrl *ctrl, void *data); - static void onCommitEveryoneCopy(LLUICtrl *ctrl, void *data); - - static void onCommitNextOwnerModify(LLUICtrl* ctrl, void* data); - static void onCommitNextOwnerCopy(LLUICtrl* ctrl, void* data); - static void onCommitNextOwnerTransfer(LLUICtrl* ctrl, void* data); - - static void onCommitName(LLUICtrl* ctrl, void* data); - static void onCommitDesc(LLUICtrl* ctrl, void* data); - - static void onCommitSaleInfo(LLUICtrl* ctrl, void* data); - static void onCommitSaleType(LLUICtrl* ctrl, void* data); - void setAllSaleInfo(); - - static void onCommitClickAction(LLUICtrl* ctrl, void* data); - static void onCommitIncludeInSearch(LLUICtrl* ctrl, void*); - - static void doClickAction(U8 click_action); - void disableAll(); - void disablePermissions(); - -private: - LLNameBox* mLabelGroupName; // group name - - LLUUID mCreatorID; - LLUUID mOwnerID; - LLUUID mLastOwnerID; - - bool mIsDirty; - -protected: - void onOpenButtonClicked(); - void onPayButtonClicked(); - void onBuyButtonClicked(); - void onDetailsButtonClicked(); -private: - LLButton* mOpenBtn; - LLButton* mPayBtn; - LLButton* mBuyBtn; - LLButton* mDetailsBtn; - LLButton* mDeedBtn; - -protected: - LLViewerObject* getObject(); -private: - LLPointer mObject; - LLObjectSelectionHandle mObjectSelection; - - // mVisibleDebugPermissions doesn't nessesarily matche state - // of viewes and is primarily for floater resize - bool mVisibleDebugPermissions; - static LLSidepanelTaskInfo* sActivePanel; - -private: - // Pointers cached here to speed up the "disableAll" function which gets called on idle - LLUICtrl* mDAPermModify; - LLUICtrl* mDACreatorName; - LLView* mDAOwner; - LLUICtrl* mDAOwnerName; - LLView* mDAButtonSetGroup; - LLUICtrl* mDAObjectName; - LLView* mDAName; - LLView* mDADescription; - LLUICtrl* mDAObjectDescription; - LLUICtrl* mDACheckboxShareWithGroup; - LLView* mDAButtonDeed; - LLUICtrl* mDACheckboxAllowEveryoneMove; - LLUICtrl* mDACheckboxAllowEveryoneCopy; - LLUICtrl* mDACheckboxNextOwnerCanModify; - LLUICtrl* mDACheckboxNextOwnerCanCopy; - LLUICtrl* mDACheckboxNextOwnerCanTransfer; - LLUICtrl* mDACheckboxForSale; - LLUICtrl* mDASearchCheck; - LLComboBox* mDAComboSaleType; - LLUICtrl* mDAEditCost; - LLView* mDALabelClickAction; - LLComboBox* mDAComboClickAction; - LLTextBase* mDAPathfindingAttributes; - LLUICtrl* mDAB; - LLUICtrl* mDAO; - LLUICtrl* mDAG; - LLUICtrl* mDAE; - LLUICtrl* mDAN; - LLUICtrl* mDAF; - - boost::signals2::connection mSelectionUpdateSlot; -}; - - -#endif // LL_LLSIDEPANELTASKINFO_H +/** + * @file llsidepaneltaskinfo.h + * @brief LLSidepanelTaskInfo class header file + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSIDEPANELTASKINFO_H +#define LL_LLSIDEPANELTASKINFO_H + +#include "lluuid.h" +#include "llpanel.h" +#include "llselectmgr.h" + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLSidepanelTaskInfo +// +// Panel for permissions of an object. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLCheckBoxCtrl; +class LLComboBox; +class LLNameBox; +class LLViewerObject; +class LLTextBase; + +class LLSidepanelTaskInfo : public LLPanel +{ +public: + LLSidepanelTaskInfo(); + virtual ~LLSidepanelTaskInfo(); + + bool postBuild() override; + void onVisibilityChange (bool new_visibility) override; + + void setObjectSelection(LLObjectSelectionHandle selection); + + const LLUUID& getSelectedUUID(); + LLViewerObject* getFirstSelectedObject(); + + static LLSidepanelTaskInfo *getActivePanel(); + void dirty(); + static void onIdle( void* user_data ); +protected: + void refresh() override; // refresh all labels as needed + void save(); + void updateVerbs(); + + void refreshAll(); // ignore current keyboard focus and update all fields + + // statics + static void onClickClaim(void*); + static void onClickRelease(void*); + void onClickGroup(); + void cbGroupID(LLUUID group_id); + static void onClickDeedToGroup(void*); + + static void onCommitPerm(LLUICtrl *ctrl, void *data, U8 field, U32 perm); + + static void onCommitGroupShare(LLUICtrl *ctrl, void *data); + + static void onCommitEveryoneMove(LLUICtrl *ctrl, void *data); + static void onCommitEveryoneCopy(LLUICtrl *ctrl, void *data); + + static void onCommitNextOwnerModify(LLUICtrl* ctrl, void* data); + static void onCommitNextOwnerCopy(LLUICtrl* ctrl, void* data); + static void onCommitNextOwnerTransfer(LLUICtrl* ctrl, void* data); + + static void onCommitName(LLUICtrl* ctrl, void* data); + static void onCommitDesc(LLUICtrl* ctrl, void* data); + + static void onCommitSaleInfo(LLUICtrl* ctrl, void* data); + static void onCommitSaleType(LLUICtrl* ctrl, void* data); + void setAllSaleInfo(); + + static void onCommitClickAction(LLUICtrl* ctrl, void* data); + static void onCommitIncludeInSearch(LLUICtrl* ctrl, void*); + + static void doClickAction(U8 click_action); + void disableAll(); + void disablePermissions(); + +private: + LLNameBox* mLabelGroupName; // group name + + LLUUID mCreatorID; + LLUUID mOwnerID; + LLUUID mLastOwnerID; + + bool mIsDirty; + +protected: + void onOpenButtonClicked(); + void onPayButtonClicked(); + void onBuyButtonClicked(); + void onDetailsButtonClicked(); +private: + LLButton* mOpenBtn; + LLButton* mPayBtn; + LLButton* mBuyBtn; + LLButton* mDetailsBtn; + LLButton* mDeedBtn; + +protected: + LLViewerObject* getObject(); +private: + LLPointer mObject; + LLObjectSelectionHandle mObjectSelection; + + // mVisibleDebugPermissions doesn't nessesarily matche state + // of viewes and is primarily for floater resize + bool mVisibleDebugPermissions; + static LLSidepanelTaskInfo* sActivePanel; + +private: + // Pointers cached here to speed up the "disableAll" function which gets called on idle + LLUICtrl* mDAPermModify; + LLUICtrl* mDACreatorName; + LLView* mDAOwner; + LLUICtrl* mDAOwnerName; + LLView* mDAButtonSetGroup; + LLUICtrl* mDAObjectName; + LLView* mDAName; + LLView* mDADescription; + LLUICtrl* mDAObjectDescription; + LLUICtrl* mDACheckboxShareWithGroup; + LLView* mDAButtonDeed; + LLUICtrl* mDACheckboxAllowEveryoneMove; + LLUICtrl* mDACheckboxAllowEveryoneCopy; + LLUICtrl* mDACheckboxNextOwnerCanModify; + LLUICtrl* mDACheckboxNextOwnerCanCopy; + LLUICtrl* mDACheckboxNextOwnerCanTransfer; + LLUICtrl* mDACheckboxForSale; + LLUICtrl* mDASearchCheck; + LLComboBox* mDAComboSaleType; + LLUICtrl* mDAEditCost; + LLView* mDALabelClickAction; + LLComboBox* mDAComboClickAction; + LLTextBase* mDAPathfindingAttributes; + LLUICtrl* mDAB; + LLUICtrl* mDAO; + LLUICtrl* mDAG; + LLUICtrl* mDAE; + LLUICtrl* mDAN; + LLUICtrl* mDAF; + + boost::signals2::connection mSelectionUpdateSlot; +}; + + +#endif // LL_LLSIDEPANELTASKINFO_H diff --git a/indra/newview/llsidetraypanelcontainer.cpp b/indra/newview/llsidetraypanelcontainer.cpp index 6f29889b06..eb8e05ec27 100644 --- a/indra/newview/llsidetraypanelcontainer.cpp +++ b/indra/newview/llsidetraypanelcontainer.cpp @@ -1,93 +1,93 @@ -/** -* @file llsidetraypanelcontainer.cpp -* @brief LLSideTrayPanelContainer implementation -* -* $LicenseInfo:firstyear=2001&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2010, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#include "llviewerprecompiledheaders.h" -#include "llsidetraypanelcontainer.h" - -static LLDefaultChildRegistry::Register r2("panel_container"); - -std::string LLSideTrayPanelContainer::PARAM_SUB_PANEL_NAME = "sub_panel_name"; - -LLSideTrayPanelContainer::Params::Params() -: default_panel_name("default_panel_name") -{ - // Always hide tabs. - changeDefault(hide_tabs, true); -} - -LLSideTrayPanelContainer::LLSideTrayPanelContainer(const Params& p) - : LLTabContainer(p) - , mDefaultPanelName(p.default_panel_name) -{ -} - -void LLSideTrayPanelContainer::onOpen(const LLSD& key) -{ - // Select specified panel and save navigation history. - if(key.has(PARAM_SUB_PANEL_NAME)) - { - //*NOTE dzaporozhan - // Navigation history is not used after fix for EXT-3186, - // openPreviousPanel() always opens default panel - - // Save panel navigation history - std::string panel_name = key[PARAM_SUB_PANEL_NAME]; - - selectTabByName(panel_name); - } - // Will reopen current panel if no panel name was passed. - getCurrentPanel()->onOpen(key); -} - -void LLSideTrayPanelContainer::openPanel(const std::string& panel_name, const LLSD& key) -{ - LLSD combined_key = key; - combined_key[PARAM_SUB_PANEL_NAME] = panel_name; - onOpen(combined_key); -} - -void LLSideTrayPanelContainer::openPreviousPanel() -{ - if(!mDefaultPanelName.empty()) - { - selectTabByName(mDefaultPanelName); - } - else - { - selectTab(0); - } -} - -bool LLSideTrayPanelContainer::handleKeyHere(KEY key, MASK mask) -{ - // No key press handling code for Panel Container - this disables - // Tab Container's Alt + Left/Right Button tab switching. - - // Let default handler process key presses, don't simply return true or false - // as this may brake some functionality as it did with Copy/Paste for - // text_editor (ticket EXT-642). - return LLPanel::handleKeyHere(key, mask); -} +/** +* @file llsidetraypanelcontainer.cpp +* @brief LLSideTrayPanelContainer implementation +* +* $LicenseInfo:firstyear=2001&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2010, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" +#include "llsidetraypanelcontainer.h" + +static LLDefaultChildRegistry::Register r2("panel_container"); + +std::string LLSideTrayPanelContainer::PARAM_SUB_PANEL_NAME = "sub_panel_name"; + +LLSideTrayPanelContainer::Params::Params() +: default_panel_name("default_panel_name") +{ + // Always hide tabs. + changeDefault(hide_tabs, true); +} + +LLSideTrayPanelContainer::LLSideTrayPanelContainer(const Params& p) + : LLTabContainer(p) + , mDefaultPanelName(p.default_panel_name) +{ +} + +void LLSideTrayPanelContainer::onOpen(const LLSD& key) +{ + // Select specified panel and save navigation history. + if(key.has(PARAM_SUB_PANEL_NAME)) + { + //*NOTE dzaporozhan + // Navigation history is not used after fix for EXT-3186, + // openPreviousPanel() always opens default panel + + // Save panel navigation history + std::string panel_name = key[PARAM_SUB_PANEL_NAME]; + + selectTabByName(panel_name); + } + // Will reopen current panel if no panel name was passed. + getCurrentPanel()->onOpen(key); +} + +void LLSideTrayPanelContainer::openPanel(const std::string& panel_name, const LLSD& key) +{ + LLSD combined_key = key; + combined_key[PARAM_SUB_PANEL_NAME] = panel_name; + onOpen(combined_key); +} + +void LLSideTrayPanelContainer::openPreviousPanel() +{ + if(!mDefaultPanelName.empty()) + { + selectTabByName(mDefaultPanelName); + } + else + { + selectTab(0); + } +} + +bool LLSideTrayPanelContainer::handleKeyHere(KEY key, MASK mask) +{ + // No key press handling code for Panel Container - this disables + // Tab Container's Alt + Left/Right Button tab switching. + + // Let default handler process key presses, don't simply return true or false + // as this may brake some functionality as it did with Copy/Paste for + // text_editor (ticket EXT-642). + return LLPanel::handleKeyHere(key, mask); +} diff --git a/indra/newview/llsidetraypanelcontainer.h b/indra/newview/llsidetraypanelcontainer.h index f35aa08042..5dfd7f2d83 100644 --- a/indra/newview/llsidetraypanelcontainer.h +++ b/indra/newview/llsidetraypanelcontainer.h @@ -1,96 +1,96 @@ -/** -* @file llsidetraypanelcontainer.h -* @brief LLSideTrayPanelContainer class declaration -* -* $LicenseInfo:firstyear=2009&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2010, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#ifndef LL_LLSIDETRAY_PANEL_CONTAINER_H -#define LL_LLSIDETRAY_PANEL_CONTAINER_H - -#include "lltabcontainer.h" - -/** -* LLSideTrayPanelContainer class acts like LLTabContainer with invisible tabs. -* It is designed to make panel switching easier, avoid setVisible(true) setVisible(false) -* calls and related workarounds. -* use onOpen to open sub panel, pass the name of panel to open -* in key[PARAM_SUB_PANEL_NAME]. -* LLSideTrayPanelContainer also implements panel navigation history - it allows to -* open previous or next panel if navigation history is available(after user -* has opened two or more panels). *NOTE - only back navigation is implemented so far. -*/ -class LLSideTrayPanelContainer : public LLTabContainer -{ -public: - - struct Params : public LLInitParam::Block - { - Optional default_panel_name; - Params(); - }; - - /** - * Opens sub panel - * @param key - params to be passed to panel, use key[PARAM_SUB_PANEL_NAME] - * to specify panel name to be opened. - */ - /*virtual*/ void onOpen(const LLSD& key); - - /** - * Opens given subpanel. - */ - void openPanel(const std::string& panel_name, const LLSD& key = LLSD::emptyMap()); - - /** - * Opens previous panel from panel navigation history. - */ - void openPreviousPanel(); - - /** - * Overrides LLTabContainer::handleKeyHere to disable panel switch on - * Alt + Left/Right button press. - */ - bool handleKeyHere(KEY key, MASK mask); - - /** - * Name of parameter that stores panel name to open. - */ - static std::string PARAM_SUB_PANEL_NAME; - -protected: - LLSideTrayPanelContainer(const Params& p); - friend class LLUICtrlFactory; - - /** - * std::string - name of panel - * S32 - index of previous panel - * *NOTE - no forward navigation implemented yet - */ - typedef std::map panel_navigation_history_t; - - // Navigation history - panel_navigation_history_t mPanelHistory; - std::string mDefaultPanelName; -}; - -#endif //LL_LLSIDETRAY_PANEL_CONTAINER_H +/** +* @file llsidetraypanelcontainer.h +* @brief LLSideTrayPanelContainer class declaration +* +* $LicenseInfo:firstyear=2009&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2010, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#ifndef LL_LLSIDETRAY_PANEL_CONTAINER_H +#define LL_LLSIDETRAY_PANEL_CONTAINER_H + +#include "lltabcontainer.h" + +/** +* LLSideTrayPanelContainer class acts like LLTabContainer with invisible tabs. +* It is designed to make panel switching easier, avoid setVisible(true) setVisible(false) +* calls and related workarounds. +* use onOpen to open sub panel, pass the name of panel to open +* in key[PARAM_SUB_PANEL_NAME]. +* LLSideTrayPanelContainer also implements panel navigation history - it allows to +* open previous or next panel if navigation history is available(after user +* has opened two or more panels). *NOTE - only back navigation is implemented so far. +*/ +class LLSideTrayPanelContainer : public LLTabContainer +{ +public: + + struct Params : public LLInitParam::Block + { + Optional default_panel_name; + Params(); + }; + + /** + * Opens sub panel + * @param key - params to be passed to panel, use key[PARAM_SUB_PANEL_NAME] + * to specify panel name to be opened. + */ + /*virtual*/ void onOpen(const LLSD& key); + + /** + * Opens given subpanel. + */ + void openPanel(const std::string& panel_name, const LLSD& key = LLSD::emptyMap()); + + /** + * Opens previous panel from panel navigation history. + */ + void openPreviousPanel(); + + /** + * Overrides LLTabContainer::handleKeyHere to disable panel switch on + * Alt + Left/Right button press. + */ + bool handleKeyHere(KEY key, MASK mask); + + /** + * Name of parameter that stores panel name to open. + */ + static std::string PARAM_SUB_PANEL_NAME; + +protected: + LLSideTrayPanelContainer(const Params& p); + friend class LLUICtrlFactory; + + /** + * std::string - name of panel + * S32 - index of previous panel + * *NOTE - no forward navigation implemented yet + */ + typedef std::map panel_navigation_history_t; + + // Navigation history + panel_navigation_history_t mPanelHistory; + std::string mDefaultPanelName; +}; + +#endif //LL_LLSIDETRAY_PANEL_CONTAINER_H diff --git a/indra/newview/llsky.cpp b/indra/newview/llsky.cpp index 2e73e47e05..82caa14433 100644 --- a/indra/newview/llsky.cpp +++ b/indra/newview/llsky.cpp @@ -1,333 +1,333 @@ -/** - * @file llsky.cpp - * @brief IndraWorld sky class - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -// Ideas: -// -haze should be controlled by global query from sims -// -need secondary optical effects on sun (flare) -// -stars should be brought down from sims -// -star intensity should be driven by global ambient level from sims, -// so that eclipses, etc can be easily done. -// - -#include "llviewerprecompiledheaders.h" - -#include "llsky.h" - -// linden library includes -#include "llerror.h" -#include "llmath.h" -#include "math.h" -#include "v4color.h" - -#include "llviewerobjectlist.h" -#include "llviewerobject.h" -#include "llviewercamera.h" -#include "pipeline.h" -#include "lldrawpool.h" - -#include "llvosky.h" -#include "llcubemap.h" -#include "llviewercontrol.h" -#include "llenvironment.h" -#include "llvoavatarself.h" -#include "llvowlsky.h" - -F32 azimuth_from_vector(const LLVector3 &v); -F32 elevation_from_vector(const LLVector3 &v); - -LLSky gSky; - -// ---------------- LLSky ---------------- -////////////////////////////////////////////////////////////////////// -// Construction/Destruction -////////////////////////////////////////////////////////////////////// - -LLSky::LLSky() -{ - // Set initial clear color to black - // Set fog color - mFogColor.mV[VRED] = mFogColor.mV[VGREEN] = mFogColor.mV[VBLUE] = 0.5f; - mFogColor.mV[VALPHA] = 0.0f; - - mLightingGeneration = 0; - mUpdatedThisFrame = true; -} - - -LLSky::~LLSky() -{ -} - -void LLSky::cleanup() -{ - mVOSkyp = NULL; - mVOWLSkyp = NULL; -} - -void LLSky::destroyGL() -{ - if (!mVOSkyp.isNull() && mVOSkyp->getCubeMap()) - { - mVOSkyp->cleanupGL(); - } - if (mVOWLSkyp.notNull()) - { - mVOWLSkyp->cleanupGL(); - } -} - -void LLSky::restoreGL() -{ - if (mVOSkyp) - { - mVOSkyp->restoreGL(); - } - if (mVOWLSkyp) - { - mVOWLSkyp->restoreGL(); - } -} - -void LLSky::resetVertexBuffers() -{ - if (gSky.mVOSkyp.notNull()) - { - gPipeline.resetVertexBuffers(gSky.mVOSkyp->mDrawable); - gPipeline.markRebuild(gSky.mVOSkyp->mDrawable, LLDrawable::REBUILD_ALL); - } - if (gSky.mVOWLSkyp.notNull()) - { - gSky.mVOWLSkyp->resetVertexBuffers(); - gPipeline.resetVertexBuffers(gSky.mVOWLSkyp->mDrawable); - gPipeline.markRebuild(gSky.mVOWLSkyp->mDrawable, LLDrawable::REBUILD_ALL); - } -} - -void LLSky::setSunScale(F32 sun_scale) -{ - if(mVOSkyp.notNull()) - { - mVOSkyp->setSunScale(sun_scale); - } -} - -void LLSky::setMoonScale(F32 moon_scale) -{ - if(mVOSkyp.notNull()) - { - mVOSkyp->setMoonScale(moon_scale); - } -} - -void LLSky::setSunTextures(const LLUUID& sun_texture, const LLUUID& sun_texture_next) -{ - if(mVOSkyp.notNull()) { - mVOSkyp->setSunTextures(sun_texture, sun_texture_next); - } -} - -void LLSky::setMoonTextures(const LLUUID& moon_texture, const LLUUID& moon_texture_next) -{ - if(mVOSkyp.notNull()) { - mVOSkyp->setMoonTextures(moon_texture, moon_texture_next); - } -} - -void LLSky::setCloudNoiseTextures(const LLUUID& cloud_noise_texture, const LLUUID& cloud_noise_texture_next) -{ - if(mVOSkyp.notNull()) { - mVOSkyp->setCloudNoiseTextures(cloud_noise_texture, cloud_noise_texture_next); - } -} - -void LLSky::setBloomTextures(const LLUUID& bloom_texture, const LLUUID& bloom_texture_next) -{ - if(mVOSkyp.notNull()) { - mVOSkyp->setBloomTextures(bloom_texture, bloom_texture_next); - } -} - -void LLSky::setSunAndMoonDirectionsCFR(const LLVector3 &sun_direction, const LLVector3 &moon_direction) -{ - if(mVOSkyp.notNull()) { - mVOSkyp->setSunAndMoonDirectionsCFR(sun_direction, moon_direction); - } -} - -void LLSky::setSunDirectionCFR(const LLVector3 &sun_direction) -{ - if(mVOSkyp.notNull()) { - mVOSkyp->setSunDirectionCFR(sun_direction); - } -} - -void LLSky::setMoonDirectionCFR(const LLVector3 &moon_direction) -{ - if(mVOSkyp.notNull()) { - mVOSkyp->setMoonDirectionCFR(moon_direction); - } -} - -////////////////////////////////////////////////////////////////////// -// Public Methods -////////////////////////////////////////////////////////////////////// - -void LLSky::init() -{ - mVOWLSkyp = static_cast(gObjectList.createObjectViewer(LLViewerObject::LL_VO_WL_SKY, NULL)); - mVOWLSkyp->init(); - gPipeline.createObject(mVOWLSkyp.get()); - - mVOSkyp = (LLVOSky *)gObjectList.createObjectViewer(LLViewerObject::LL_VO_SKY, NULL); - mVOSkyp->init(); - gPipeline.createObject(mVOSkyp.get()); - - gSky.setFogRatio(gSavedSettings.getF32("RenderFogRatio")); - - mUpdatedThisFrame = true; -} - - -void LLSky::setCloudDensityAtAgent(F32 cloud_density) -{ - if (mVOSkyp) - { - mVOSkyp->setCloudDensity(cloud_density); - } -} - - -void LLSky::setWind(const LLVector3& average_wind) -{ - if (mVOSkyp) - { - mVOSkyp->setWind(average_wind); - } -} - -void LLSky::addSunMoonBeacons() -{ - if (!gAgentAvatarp || !mVOSkyp) return; - - static LLUICachedControl show_sun_beacon("sunbeacon", false); - static LLUICachedControl show_moon_beacon("moonbeacon", false); - - if (show_sun_beacon) - { - renderSunMoonBeacons(gAgentAvatarp->getPositionAgent(), mVOSkyp->getSun().getDirection(), LLColor4(1.f, 0.5f, 0.f, 0.5f)); - } - if (show_moon_beacon) - { - renderSunMoonBeacons(gAgentAvatarp->getPositionAgent(), mVOSkyp->getMoon().getDirection(), LLColor4(1.f, 0.f, 0.8f, 0.5f)); - } -} - -////////////////////////////////////////////////////////////////////// -// Private Methods -////////////////////////////////////////////////////////////////////// - - -LLColor4 LLSky::getSkyFogColor() const -{ - if (mVOSkyp) - { - return mVOSkyp->getSkyFogColor(); - } - - return LLColor4(1.f, 1.f, 1.f, 1.f); -} - -void LLSky::updateFog(const F32 distance) -{ - if (mVOSkyp) - { - mVOSkyp->updateFog(distance); - } -} - -void LLSky::updateCull() -{ - // *TODO: do culling for wl sky properly -Brad -} - -void LLSky::updateSky() -{ - if (!gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_SKY)) - { - return; - } - if (mVOSkyp) - { - mVOSkyp->updateSky(); - } -} - - -void LLSky::setFogRatio(const F32 fog_ratio) -{ - if (mVOSkyp) - { - mVOSkyp->setFogRatio(fog_ratio); - } -} - - -F32 LLSky::getFogRatio() const -{ - if (mVOSkyp) - { - return mVOSkyp->getFogRatio(); - } - else - { - return 0.f; - } -} - - -// Returns angle (DEGREES) between the horizontal plane and "v", -// where the angle is negative when v.mV[VZ] < 0.0f -F32 elevation_from_vector(const LLVector3 &v) -{ - F32 elevation = 0.0f; - F32 xy_component = (F32) sqrt(v.mV[VX] * v.mV[VX] + v.mV[VY] * v.mV[VY]); - if (xy_component != 0.0f) - { - elevation = RAD_TO_DEG * (F32) atan(v.mV[VZ]/xy_component); - } - else - { - if (v.mV[VZ] > 0.f) - { - elevation = 90.f; - } - else - { - elevation = -90.f; - } - } - return elevation; -} +/** + * @file llsky.cpp + * @brief IndraWorld sky class + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Ideas: +// -haze should be controlled by global query from sims +// -need secondary optical effects on sun (flare) +// -stars should be brought down from sims +// -star intensity should be driven by global ambient level from sims, +// so that eclipses, etc can be easily done. +// + +#include "llviewerprecompiledheaders.h" + +#include "llsky.h" + +// linden library includes +#include "llerror.h" +#include "llmath.h" +#include "math.h" +#include "v4color.h" + +#include "llviewerobjectlist.h" +#include "llviewerobject.h" +#include "llviewercamera.h" +#include "pipeline.h" +#include "lldrawpool.h" + +#include "llvosky.h" +#include "llcubemap.h" +#include "llviewercontrol.h" +#include "llenvironment.h" +#include "llvoavatarself.h" +#include "llvowlsky.h" + +F32 azimuth_from_vector(const LLVector3 &v); +F32 elevation_from_vector(const LLVector3 &v); + +LLSky gSky; + +// ---------------- LLSky ---------------- +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +LLSky::LLSky() +{ + // Set initial clear color to black + // Set fog color + mFogColor.mV[VRED] = mFogColor.mV[VGREEN] = mFogColor.mV[VBLUE] = 0.5f; + mFogColor.mV[VALPHA] = 0.0f; + + mLightingGeneration = 0; + mUpdatedThisFrame = true; +} + + +LLSky::~LLSky() +{ +} + +void LLSky::cleanup() +{ + mVOSkyp = NULL; + mVOWLSkyp = NULL; +} + +void LLSky::destroyGL() +{ + if (!mVOSkyp.isNull() && mVOSkyp->getCubeMap()) + { + mVOSkyp->cleanupGL(); + } + if (mVOWLSkyp.notNull()) + { + mVOWLSkyp->cleanupGL(); + } +} + +void LLSky::restoreGL() +{ + if (mVOSkyp) + { + mVOSkyp->restoreGL(); + } + if (mVOWLSkyp) + { + mVOWLSkyp->restoreGL(); + } +} + +void LLSky::resetVertexBuffers() +{ + if (gSky.mVOSkyp.notNull()) + { + gPipeline.resetVertexBuffers(gSky.mVOSkyp->mDrawable); + gPipeline.markRebuild(gSky.mVOSkyp->mDrawable, LLDrawable::REBUILD_ALL); + } + if (gSky.mVOWLSkyp.notNull()) + { + gSky.mVOWLSkyp->resetVertexBuffers(); + gPipeline.resetVertexBuffers(gSky.mVOWLSkyp->mDrawable); + gPipeline.markRebuild(gSky.mVOWLSkyp->mDrawable, LLDrawable::REBUILD_ALL); + } +} + +void LLSky::setSunScale(F32 sun_scale) +{ + if(mVOSkyp.notNull()) + { + mVOSkyp->setSunScale(sun_scale); + } +} + +void LLSky::setMoonScale(F32 moon_scale) +{ + if(mVOSkyp.notNull()) + { + mVOSkyp->setMoonScale(moon_scale); + } +} + +void LLSky::setSunTextures(const LLUUID& sun_texture, const LLUUID& sun_texture_next) +{ + if(mVOSkyp.notNull()) { + mVOSkyp->setSunTextures(sun_texture, sun_texture_next); + } +} + +void LLSky::setMoonTextures(const LLUUID& moon_texture, const LLUUID& moon_texture_next) +{ + if(mVOSkyp.notNull()) { + mVOSkyp->setMoonTextures(moon_texture, moon_texture_next); + } +} + +void LLSky::setCloudNoiseTextures(const LLUUID& cloud_noise_texture, const LLUUID& cloud_noise_texture_next) +{ + if(mVOSkyp.notNull()) { + mVOSkyp->setCloudNoiseTextures(cloud_noise_texture, cloud_noise_texture_next); + } +} + +void LLSky::setBloomTextures(const LLUUID& bloom_texture, const LLUUID& bloom_texture_next) +{ + if(mVOSkyp.notNull()) { + mVOSkyp->setBloomTextures(bloom_texture, bloom_texture_next); + } +} + +void LLSky::setSunAndMoonDirectionsCFR(const LLVector3 &sun_direction, const LLVector3 &moon_direction) +{ + if(mVOSkyp.notNull()) { + mVOSkyp->setSunAndMoonDirectionsCFR(sun_direction, moon_direction); + } +} + +void LLSky::setSunDirectionCFR(const LLVector3 &sun_direction) +{ + if(mVOSkyp.notNull()) { + mVOSkyp->setSunDirectionCFR(sun_direction); + } +} + +void LLSky::setMoonDirectionCFR(const LLVector3 &moon_direction) +{ + if(mVOSkyp.notNull()) { + mVOSkyp->setMoonDirectionCFR(moon_direction); + } +} + +////////////////////////////////////////////////////////////////////// +// Public Methods +////////////////////////////////////////////////////////////////////// + +void LLSky::init() +{ + mVOWLSkyp = static_cast(gObjectList.createObjectViewer(LLViewerObject::LL_VO_WL_SKY, NULL)); + mVOWLSkyp->init(); + gPipeline.createObject(mVOWLSkyp.get()); + + mVOSkyp = (LLVOSky *)gObjectList.createObjectViewer(LLViewerObject::LL_VO_SKY, NULL); + mVOSkyp->init(); + gPipeline.createObject(mVOSkyp.get()); + + gSky.setFogRatio(gSavedSettings.getF32("RenderFogRatio")); + + mUpdatedThisFrame = true; +} + + +void LLSky::setCloudDensityAtAgent(F32 cloud_density) +{ + if (mVOSkyp) + { + mVOSkyp->setCloudDensity(cloud_density); + } +} + + +void LLSky::setWind(const LLVector3& average_wind) +{ + if (mVOSkyp) + { + mVOSkyp->setWind(average_wind); + } +} + +void LLSky::addSunMoonBeacons() +{ + if (!gAgentAvatarp || !mVOSkyp) return; + + static LLUICachedControl show_sun_beacon("sunbeacon", false); + static LLUICachedControl show_moon_beacon("moonbeacon", false); + + if (show_sun_beacon) + { + renderSunMoonBeacons(gAgentAvatarp->getPositionAgent(), mVOSkyp->getSun().getDirection(), LLColor4(1.f, 0.5f, 0.f, 0.5f)); + } + if (show_moon_beacon) + { + renderSunMoonBeacons(gAgentAvatarp->getPositionAgent(), mVOSkyp->getMoon().getDirection(), LLColor4(1.f, 0.f, 0.8f, 0.5f)); + } +} + +////////////////////////////////////////////////////////////////////// +// Private Methods +////////////////////////////////////////////////////////////////////// + + +LLColor4 LLSky::getSkyFogColor() const +{ + if (mVOSkyp) + { + return mVOSkyp->getSkyFogColor(); + } + + return LLColor4(1.f, 1.f, 1.f, 1.f); +} + +void LLSky::updateFog(const F32 distance) +{ + if (mVOSkyp) + { + mVOSkyp->updateFog(distance); + } +} + +void LLSky::updateCull() +{ + // *TODO: do culling for wl sky properly -Brad +} + +void LLSky::updateSky() +{ + if (!gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_SKY)) + { + return; + } + if (mVOSkyp) + { + mVOSkyp->updateSky(); + } +} + + +void LLSky::setFogRatio(const F32 fog_ratio) +{ + if (mVOSkyp) + { + mVOSkyp->setFogRatio(fog_ratio); + } +} + + +F32 LLSky::getFogRatio() const +{ + if (mVOSkyp) + { + return mVOSkyp->getFogRatio(); + } + else + { + return 0.f; + } +} + + +// Returns angle (DEGREES) between the horizontal plane and "v", +// where the angle is negative when v.mV[VZ] < 0.0f +F32 elevation_from_vector(const LLVector3 &v) +{ + F32 elevation = 0.0f; + F32 xy_component = (F32) sqrt(v.mV[VX] * v.mV[VX] + v.mV[VY] * v.mV[VY]); + if (xy_component != 0.0f) + { + elevation = RAD_TO_DEG * (F32) atan(v.mV[VZ]/xy_component); + } + else + { + if (v.mV[VZ] > 0.f) + { + elevation = 90.f; + } + else + { + elevation = -90.f; + } + } + return elevation; +} diff --git a/indra/newview/llsky.h b/indra/newview/llsky.h index f0b8d69b08..32599dcee2 100644 --- a/indra/newview/llsky.h +++ b/indra/newview/llsky.h @@ -1,97 +1,97 @@ -/** - * @file llsky.h - * @brief It's, uh, the sky! - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLSKY_H -#define LL_LLSKY_H - -#include "llmath.h" -//#include "vmath.h" -#include "v3math.h" -#include "v4math.h" -#include "v4color.h" -#include "v4coloru.h" -#include "llvosky.h" - -class LLViewerCamera; - -class LLVOWLSky; - - -class LLSky -{ -public: - LLSky(); - ~LLSky(); - - void init(); - void cleanup(); - - // These directions should be in CFR coord sys (+x at, +z up, +y right) - void setSunAndMoonDirectionsCFR(const LLVector3 &sun_direction, const LLVector3 &moon_direction); - void setSunDirectionCFR(const LLVector3 &sun_direction); - void setMoonDirectionCFR(const LLVector3 &moon_direction); - - void setSunTextures(const LLUUID& sun_texture, const LLUUID& sun_texture_next); - void setMoonTextures(const LLUUID& moon_texture, const LLUUID& moon_texture_next); - void setCloudNoiseTextures(const LLUUID& cloud_noise_texture, const LLUUID& cloud_noise_texture_next); - void setBloomTextures(const LLUUID& bloom_texture, const LLUUID& bloom_texture_next); - - void setSunScale(F32 sun_scale); - void setMoonScale(F32 moon_scale); - - LLColor4 getSkyFogColor() const; - - void setCloudDensityAtAgent(F32 cloud_density); - void setWind(const LLVector3& wind); - - void updateFog(const F32 distance); - void updateCull(); - void updateSky(); - - S32 mLightingGeneration; - bool mUpdatedThisFrame; - - void setFogRatio(const F32 fog_ratio); // Fog distance as fraction of cull distance. - F32 getFogRatio() const; - LLColor4U getFadeColor() const; - - void destroyGL(); - void restoreGL(); - void resetVertexBuffers(); - - void addSunMoonBeacons(); - void renderSunMoonBeacons(const LLVector3& pos_agent, const LLVector3& direction, LLColor4 color); - -public: - LLPointer mVOSkyp; // Pointer to the LLVOSky object (only one, ever!) - LLPointer mVOWLSkyp; - -protected: - LLColor4 mFogColor; -}; - -extern LLSky gSky; -#endif +/** + * @file llsky.h + * @brief It's, uh, the sky! + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSKY_H +#define LL_LLSKY_H + +#include "llmath.h" +//#include "vmath.h" +#include "v3math.h" +#include "v4math.h" +#include "v4color.h" +#include "v4coloru.h" +#include "llvosky.h" + +class LLViewerCamera; + +class LLVOWLSky; + + +class LLSky +{ +public: + LLSky(); + ~LLSky(); + + void init(); + void cleanup(); + + // These directions should be in CFR coord sys (+x at, +z up, +y right) + void setSunAndMoonDirectionsCFR(const LLVector3 &sun_direction, const LLVector3 &moon_direction); + void setSunDirectionCFR(const LLVector3 &sun_direction); + void setMoonDirectionCFR(const LLVector3 &moon_direction); + + void setSunTextures(const LLUUID& sun_texture, const LLUUID& sun_texture_next); + void setMoonTextures(const LLUUID& moon_texture, const LLUUID& moon_texture_next); + void setCloudNoiseTextures(const LLUUID& cloud_noise_texture, const LLUUID& cloud_noise_texture_next); + void setBloomTextures(const LLUUID& bloom_texture, const LLUUID& bloom_texture_next); + + void setSunScale(F32 sun_scale); + void setMoonScale(F32 moon_scale); + + LLColor4 getSkyFogColor() const; + + void setCloudDensityAtAgent(F32 cloud_density); + void setWind(const LLVector3& wind); + + void updateFog(const F32 distance); + void updateCull(); + void updateSky(); + + S32 mLightingGeneration; + bool mUpdatedThisFrame; + + void setFogRatio(const F32 fog_ratio); // Fog distance as fraction of cull distance. + F32 getFogRatio() const; + LLColor4U getFadeColor() const; + + void destroyGL(); + void restoreGL(); + void resetVertexBuffers(); + + void addSunMoonBeacons(); + void renderSunMoonBeacons(const LLVector3& pos_agent, const LLVector3& direction, LLColor4 color); + +public: + LLPointer mVOSkyp; // Pointer to the LLVOSky object (only one, ever!) + LLPointer mVOWLSkyp; + +protected: + LLColor4 mFogColor; +}; + +extern LLSky gSky; +#endif diff --git a/indra/newview/llsnapshotlivepreview.cpp b/indra/newview/llsnapshotlivepreview.cpp index f8e3088324..787dd3b667 100644 --- a/indra/newview/llsnapshotlivepreview.cpp +++ b/indra/newview/llsnapshotlivepreview.cpp @@ -1,1069 +1,1069 @@ -/** -* @file llsnapshotlivepreview.cpp -* @brief Implementation of llsnapshotlivepreview -* @author Gilbert@lindenlab.com -* -* $LicenseInfo:firstyear=2013&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2014, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#include "llviewerprecompiledheaders.h" - -#include "llagent.h" -#include "llagentbenefits.h" -#include "llagentcamera.h" -#include "llagentui.h" -#include "llfilesystem.h" -#include "llcombobox.h" -#include "llfloaterperms.h" -#include "llfloaterreg.h" -#include "llimagefilter.h" -#include "llimagefiltersmanager.h" -#include "llimagebmp.h" -#include "llimagej2c.h" -#include "llimagejpeg.h" -#include "llimagepng.h" -#include "lllandmarkactions.h" -#include "lllocalcliprect.h" -#include "llresmgr.h" -#include "llnotificationsutil.h" -#include "llslurl.h" -#include "llsnapshotlivepreview.h" -#include "lltoolfocus.h" -#include "llviewercontrol.h" -#include "llviewermenufile.h" // upload_new_resource() -#include "llviewerstats.h" -#include "llviewertexturelist.h" -#include "llwindow.h" -#include "llworld.h" -#include - -constexpr F32 AUTO_SNAPSHOT_TIME_DELAY = 1.f; - -constexpr F32 SHINE_TIME = 0.5f; -constexpr F32 SHINE_WIDTH = 0.6f; -constexpr F32 SHINE_OPACITY = 0.3f; -constexpr F32 FALL_TIME = 0.6f; -constexpr S32 BORDER_WIDTH = 6; -constexpr S32 TOP_PANEL_HEIGHT = 30; - -constexpr S32 MAX_TEXTURE_SIZE = 512 ; //max upload texture size 512 * 512 - -std::set LLSnapshotLivePreview::sList; -LLPointer LLSnapshotLivePreview::sSaveLocalImage = NULL; - -LLSnapshotLivePreview::LLSnapshotLivePreview (const LLSnapshotLivePreview::Params& p) - : LLView(p), - mColor(1.f, 0.f, 0.f, 0.5f), - mCurImageIndex(0), - mPreviewImage(NULL), - mThumbnailImage(NULL) , - mBigThumbnailImage(NULL) , - mThumbnailWidth(0), - mThumbnailHeight(0), - mThumbnailSubsampled(false), - mPreviewImageEncoded(NULL), - mFormattedImage(NULL), - mShineCountdown(0), - mFlashAlpha(0.f), - mNeedsFlash(true), - mSnapshotQuality(gSavedSettings.getS32("SnapshotQuality")), - mDataSize(0), - mSnapshotType(LLSnapshotModel::SNAPSHOT_POSTCARD), - mSnapshotFormat(LLSnapshotModel::ESnapshotFormat(gSavedSettings.getS32("SnapshotFormat"))), - mSnapshotUpToDate(false), - mCameraPos(LLViewerCamera::getInstance()->getOrigin()), - mCameraRot(LLViewerCamera::getInstance()->getQuaternion()), - mSnapshotActive(false), - mSnapshotBufferType(LLSnapshotModel::SNAPSHOT_TYPE_COLOR), - mFilterName(""), - mAllowRenderUI(true), - mAllowFullScreenPreview(true), - mViewContainer(NULL) -{ - setSnapshotQuality(gSavedSettings.getS32("SnapshotQuality")); - mSnapshotDelayTimer.setTimerExpirySec(0.0f); - mSnapshotDelayTimer.start(); - // gIdleCallbacks.addFunction( &LLSnapshotLivePreview::onIdle, (void*)this ); - sList.insert(this); - setFollowsAll(); - mWidth[0] = gViewerWindow->getWindowWidthRaw(); - mWidth[1] = gViewerWindow->getWindowWidthRaw(); - mHeight[0] = gViewerWindow->getWindowHeightRaw(); - mHeight[1] = gViewerWindow->getWindowHeightRaw(); - mImageScaled[0] = false; - mImageScaled[1] = false; - - mMaxImageSize = MAX_SNAPSHOT_IMAGE_SIZE ; - mKeepAspectRatio = gSavedSettings.getBOOL("KeepAspectForSnapshot") ; - mThumbnailUpdateLock = false ; - mThumbnailUpToDate = false ; - mBigThumbnailUpToDate = false ; - - mForceUpdateSnapshot = false; -} - -LLSnapshotLivePreview::~LLSnapshotLivePreview() -{ - // delete images - mPreviewImage = NULL; - mPreviewImageEncoded = NULL; - mFormattedImage = NULL; - - // gIdleCallbacks.deleteFunction( &LLSnapshotLivePreview::onIdle, (void*)this ); - sList.erase(this); - sSaveLocalImage = NULL; -} - -void LLSnapshotLivePreview::setMaxImageSize(S32 size) -{ - mMaxImageSize = llmin(size,(S32)(MAX_SNAPSHOT_IMAGE_SIZE)); -} - -LLViewerTexture* LLSnapshotLivePreview::getCurrentImage() -{ - return mViewerImage[mCurImageIndex]; -} - -F32 LLSnapshotLivePreview::getImageAspect() -{ - if (!getCurrentImage()) - { - return 0.f; - } - // mKeepAspectRatio) == gSavedSettings.getBOOL("KeepAspectForSnapshot")) - return (mKeepAspectRatio ? ((F32)getRect().getWidth()) / ((F32)getRect().getHeight()) : ((F32)getWidth()) / ((F32)getHeight())); -} - -void LLSnapshotLivePreview::updateSnapshot(bool new_snapshot, bool new_thumbnail, F32 delay) -{ - LL_DEBUGS("Snapshot") << "updateSnapshot: mSnapshotUpToDate = " << getSnapshotUpToDate() << LL_ENDL; - - // Update snapshot if requested. - if (new_snapshot) - { - if (getSnapshotUpToDate()) - { - S32 old_image_index = mCurImageIndex; - mCurImageIndex = (mCurImageIndex + 1) % 2; - setSize(mWidth[old_image_index], mHeight[old_image_index]); - mFallAnimTimer.start(); - } - mSnapshotUpToDate = false; - - // Update snapshot source rect depending on whether we keep the aspect ratio. - LLRect& rect = mImageRect[mCurImageIndex]; - rect.set(0, getRect().getHeight(), getRect().getWidth(), 0); - - F32 image_aspect_ratio = ((F32)getWidth()) / ((F32)getHeight()); - F32 window_aspect_ratio = ((F32)getRect().getWidth()) / ((F32)getRect().getHeight()); - - if (mKeepAspectRatio)//gSavedSettings.getBOOL("KeepAspectForSnapshot")) - { - if (image_aspect_ratio > window_aspect_ratio) - { - // trim off top and bottom - S32 new_height = ll_round((F32)getRect().getWidth() / image_aspect_ratio); - rect.mBottom += (getRect().getHeight() - new_height) / 2; - rect.mTop -= (getRect().getHeight() - new_height) / 2; - } - else if (image_aspect_ratio < window_aspect_ratio) - { - // trim off left and right - S32 new_width = ll_round((F32)getRect().getHeight() * image_aspect_ratio); - rect.mLeft += (getRect().getWidth() - new_width) / 2; - rect.mRight -= (getRect().getWidth() - new_width) / 2; - } - } - - // Stop shining animation. - mShineAnimTimer.stop(); - mSnapshotDelayTimer.start(); - mSnapshotDelayTimer.resetWithExpiry(delay); - - - mPosTakenGlobal = gAgentCamera.getCameraPositionGlobal(); - - // Tell the floater container that the snapshot is in the process of updating itself - if (mViewContainer) - { - mViewContainer->notify(LLSD().with("snapshot-updating", true)); - } - } - - // Update thumbnail if requested. - if (new_thumbnail) - { - mThumbnailUpToDate = false ; - mBigThumbnailUpToDate = false; - } -} - -// Return true if the quality has been changed, false otherwise -bool LLSnapshotLivePreview::setSnapshotQuality(S32 quality, bool set_by_user) -{ - llclamp(quality, 0, 100); - if (quality != mSnapshotQuality) - { - mSnapshotQuality = quality; - if (set_by_user) - { - gSavedSettings.setS32("SnapshotQuality", quality); - } - mFormattedImage = NULL; // Invalidate the already formatted image if any - return true; - } - return false; -} - -void LLSnapshotLivePreview::drawPreviewRect(S32 offset_x, S32 offset_y, LLColor4 alpha_color) -{ - F32 line_width ; - glGetFloatv(GL_LINE_WIDTH, &line_width) ; - glLineWidth(2.0f * line_width) ; - LLColor4 color(0.0f, 0.0f, 0.0f, 1.0f) ; - gl_rect_2d( mPreviewRect.mLeft + offset_x, mPreviewRect.mTop + offset_y, - mPreviewRect.mRight + offset_x, mPreviewRect.mBottom + offset_y, color, false ) ; - glLineWidth(line_width) ; - - //draw four alpha rectangles to cover areas outside of the snapshot image - if(!mKeepAspectRatio) - { - S32 dwl = 0, dwr = 0 ; - if(mThumbnailWidth > mPreviewRect.getWidth()) - { - dwl = (mThumbnailWidth - mPreviewRect.getWidth()) >> 1 ; - dwr = mThumbnailWidth - mPreviewRect.getWidth() - dwl ; - - gl_rect_2d(mPreviewRect.mLeft + offset_x - dwl, mPreviewRect.mTop + offset_y, - mPreviewRect.mLeft + offset_x, mPreviewRect.mBottom + offset_y, alpha_color, true ) ; - gl_rect_2d( mPreviewRect.mRight + offset_x, mPreviewRect.mTop + offset_y, - mPreviewRect.mRight + offset_x + dwr, mPreviewRect.mBottom + offset_y, alpha_color, true ) ; - } - - if(mThumbnailHeight > mPreviewRect.getHeight()) - { - S32 dh = (mThumbnailHeight - mPreviewRect.getHeight()) >> 1 ; - gl_rect_2d(mPreviewRect.mLeft + offset_x - dwl, mPreviewRect.mBottom + offset_y , - mPreviewRect.mRight + offset_x + dwr, mPreviewRect.mBottom + offset_y - dh, alpha_color, true ) ; - - dh = mThumbnailHeight - mPreviewRect.getHeight() - dh ; - gl_rect_2d( mPreviewRect.mLeft + offset_x - dwl, mPreviewRect.mTop + offset_y + dh, - mPreviewRect.mRight + offset_x + dwr, mPreviewRect.mTop + offset_y, alpha_color, true ) ; - } - } -} - -//called when the frame is frozen. -void LLSnapshotLivePreview::draw() -{ - if (getCurrentImage() && - mPreviewImageEncoded.notNull() && - getSnapshotUpToDate()) - { - LLColor4 bg_color(0.f, 0.f, 0.3f, 0.4f); - gl_rect_2d(getRect(), bg_color); - const LLRect& rect = getImageRect(); - LLRect shadow_rect = rect; - shadow_rect.stretch(BORDER_WIDTH); - gl_drop_shadow(shadow_rect.mLeft, shadow_rect.mTop, shadow_rect.mRight, shadow_rect.mBottom, LLColor4(0.f, 0.f, 0.f, mNeedsFlash ? 0.f :0.5f), 10); - - LLColor4 image_color(1.f, 1.f, 1.f, 1.f); - gGL.color4fv(image_color.mV); - gGL.getTexUnit(0)->bind(getCurrentImage()); - // calculate UV scale - F32 uv_width = isImageScaled() ? 1.f : llmin((F32)getWidth() / (F32)getCurrentImage()->getWidth(), 1.f); - F32 uv_height = isImageScaled() ? 1.f : llmin((F32)getHeight() / (F32)getCurrentImage()->getHeight(), 1.f); - gGL.pushMatrix(); - { - gGL.translatef((F32)rect.mLeft, (F32)rect.mBottom + TOP_PANEL_HEIGHT, 0.f); - gGL.begin(LLRender::QUADS); - { - gGL.texCoord2f(uv_width, uv_height); - gGL.vertex2i(rect.getWidth(), rect.getHeight() ); - - gGL.texCoord2f(0.f, uv_height); - gGL.vertex2i(0, rect.getHeight() ); - - gGL.texCoord2f(0.f, 0.f); - gGL.vertex2i(0, 0); - - gGL.texCoord2f(uv_width, 0.f); - gGL.vertex2i(rect.getWidth(), 0); - } - gGL.end(); - } - gGL.popMatrix(); - - gGL.color4f(1.f, 1.f, 1.f, mFlashAlpha); - gl_rect_2d(getRect()); - if (mNeedsFlash) - { - if (mFlashAlpha < 1.f) - { - mFlashAlpha = lerp(mFlashAlpha, 1.f, LLCriticalDamp::getInterpolant(0.02f)); - } - else - { - mNeedsFlash = false; - } - } - else - { - mFlashAlpha = lerp(mFlashAlpha, 0.f, LLCriticalDamp::getInterpolant(0.15f)); - } - - // Draw shining animation if appropriate. - if (mShineCountdown > 0) - { - mShineCountdown--; - if (mShineCountdown == 0) - { - mShineAnimTimer.start(); - } - } - else if (mShineAnimTimer.getStarted()) - { - LL_DEBUGS("Snapshot") << "Drawing shining animation" << LL_ENDL; - F32 shine_interp = llmin(1.f, mShineAnimTimer.getElapsedTimeF32() / SHINE_TIME); - - // draw "shine" effect - LLRect local_rect(0, getRect().getHeight() + TOP_PANEL_HEIGHT, getRect().getWidth(), 0); - LLLocalClipRect clip(local_rect); - { - // draw diagonal stripe with gradient that passes over screen - S32 x1 = gViewerWindow->getWindowWidthScaled() * ll_round((clamp_rescale(shine_interp, 0.f, 1.f, -1.f - SHINE_WIDTH, 1.f))); - S32 x2 = x1 + ll_round(gViewerWindow->getWindowWidthScaled() * SHINE_WIDTH); - S32 x3 = x2 + ll_round(gViewerWindow->getWindowWidthScaled() * SHINE_WIDTH); - S32 y1 = 0; - S32 y2 = gViewerWindow->getWindowHeightScaled() + TOP_PANEL_HEIGHT; - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.begin(LLRender::QUADS); - { - gGL.color4f(1.f, 1.f, 1.f, 0.f); - gGL.vertex2i(x1, y1); - gGL.vertex2i(x1 + gViewerWindow->getWindowWidthScaled(), y2); - gGL.color4f(1.f, 1.f, 1.f, SHINE_OPACITY); - gGL.vertex2i(x2 + gViewerWindow->getWindowWidthScaled(), y2); - gGL.vertex2i(x2, y1); - - gGL.color4f(1.f, 1.f, 1.f, SHINE_OPACITY); - gGL.vertex2i(x2, y1); - gGL.vertex2i(x2 + gViewerWindow->getWindowWidthScaled(), y2); - gGL.color4f(1.f, 1.f, 1.f, 0.f); - gGL.vertex2i(x3 + gViewerWindow->getWindowWidthScaled(), y2); - gGL.vertex2i(x3, y1); - } - gGL.end(); - } - - // if we're at the end of the animation, stop - if (shine_interp >= 1.f) - { - mShineAnimTimer.stop(); - } - } - } - - // draw old image dropping away - if (mFallAnimTimer.getStarted()) - { - S32 old_image_index = (mCurImageIndex + 1) % 2; - if (mViewerImage[old_image_index].notNull() && mFallAnimTimer.getElapsedTimeF32() < FALL_TIME) - { - LL_DEBUGS("Snapshot") << "Drawing fall animation" << LL_ENDL; - F32 fall_interp = mFallAnimTimer.getElapsedTimeF32() / FALL_TIME; - F32 alpha = clamp_rescale(fall_interp, 0.f, 1.f, 0.8f, 0.4f); - LLColor4 image_color(1.f, 1.f, 1.f, alpha); - gGL.color4fv(image_color.mV); - gGL.getTexUnit(0)->bind(mViewerImage[old_image_index]); - // calculate UV scale - // *FIX get this to work with old image - bool rescale = !mImageScaled[old_image_index] && mViewerImage[mCurImageIndex].notNull(); - F32 uv_width = rescale ? llmin((F32)mWidth[old_image_index] / (F32)mViewerImage[mCurImageIndex]->getWidth(), 1.f) : 1.f; - F32 uv_height = rescale ? llmin((F32)mHeight[old_image_index] / (F32)mViewerImage[mCurImageIndex]->getHeight(), 1.f) : 1.f; - gGL.pushMatrix(); - { - LLRect& rect = mImageRect[old_image_index]; - gGL.translatef((F32)rect.mLeft, (F32)rect.mBottom - ll_round(getRect().getHeight() * 2.f * (fall_interp * fall_interp)), 0.f); - gGL.rotatef(-45.f * fall_interp, 0.f, 0.f, 1.f); - gGL.begin(LLRender::QUADS); - { - gGL.texCoord2f(uv_width, uv_height); - gGL.vertex2i(rect.getWidth(), rect.getHeight() ); - - gGL.texCoord2f(0.f, uv_height); - gGL.vertex2i(0, rect.getHeight() ); - - gGL.texCoord2f(0.f, 0.f); - gGL.vertex2i(0, 0); - - gGL.texCoord2f(uv_width, 0.f); - gGL.vertex2i(rect.getWidth(), 0); - } - gGL.end(); - } - gGL.popMatrix(); - } - } -} - -/*virtual*/ -void LLSnapshotLivePreview::reshape(S32 width, S32 height, bool called_from_parent) -{ - LLRect old_rect = getRect(); - LLView::reshape(width, height, called_from_parent); - if (old_rect.getWidth() != width || old_rect.getHeight() != height) - { - LL_DEBUGS("Window", "Snapshot") << "window reshaped, updating thumbnail" << LL_ENDL; - if (mViewContainer && mViewContainer->isInVisibleChain()) - { - // We usually resize only on window reshape, so give it a chance to redraw, assign delay - updateSnapshot( - true, // new snapshot is needed - false, // thumbnail will be updated either way. - AUTO_SNAPSHOT_TIME_DELAY); // shutter delay. - } - } -} - -bool LLSnapshotLivePreview::setThumbnailImageSize() -{ - if (getWidth() < 10 || getHeight() < 10) - { - return false ; - } - S32 width = (mThumbnailSubsampled ? mPreviewImage->getWidth() : gViewerWindow->getWindowWidthRaw()); - S32 height = (mThumbnailSubsampled ? mPreviewImage->getHeight() : gViewerWindow->getWindowHeightRaw()) ; - - F32 aspect_ratio = ((F32)width) / ((F32)height); - - // UI size for thumbnail - S32 max_width = mThumbnailPlaceholderRect.getWidth(); - S32 max_height = mThumbnailPlaceholderRect.getHeight(); - - if (aspect_ratio > (F32)max_width / (F32)max_height) - { - // image too wide, shrink to width - mThumbnailWidth = max_width; - mThumbnailHeight = ll_round((F32)max_width / aspect_ratio); - } - else - { - // image too tall, shrink to height - mThumbnailHeight = max_height; - mThumbnailWidth = ll_round((F32)max_height * aspect_ratio); - } - - if (mThumbnailWidth > width || mThumbnailHeight > height) - { - return false ;//if the window is too small, ignore thumbnail updating. - } - - S32 left = 0 , top = mThumbnailHeight, right = mThumbnailWidth, bottom = 0 ; - if (!mKeepAspectRatio) - { - F32 ratio_x = (F32)getWidth() / width ; - F32 ratio_y = (F32)getHeight() / height ; - - if (ratio_x > ratio_y) - { - top = (S32)(top * ratio_y / ratio_x) ; - } - else - { - right = (S32)(right * ratio_x / ratio_y) ; - } - left = (S32)((mThumbnailWidth - right) * 0.5f) ; - bottom = (S32)((mThumbnailHeight - top) * 0.5f) ; - top += bottom ; - right += left ; - } - mPreviewRect.set(left - 1, top + 1, right + 1, bottom - 1) ; - - return true ; -} - -void LLSnapshotLivePreview::generateThumbnailImage(bool force_update) -{ - if(mThumbnailUpdateLock) //in the process of updating - { - return ; - } - if(getThumbnailUpToDate() && !force_update)//already updated - { - return ; - } - if(getWidth() < 10 || getHeight() < 10) - { - return ; - } - - ////lock updating - mThumbnailUpdateLock = true ; - - if(!setThumbnailImageSize()) - { - mThumbnailUpdateLock = false ; - mThumbnailUpToDate = true ; - return ; - } - - // Invalidate the big thumbnail when we regenerate the small one - mBigThumbnailUpToDate = false; - - if(mThumbnailImage) - { - resetThumbnailImage() ; - } - - LLPointer raw = new LLImageRaw; - - if (mThumbnailSubsampled) - { - // The thumbnail is be a subsampled version of the preview (used in SL Share previews, i.e. Flickr, Twitter) - raw->resize( mPreviewImage->getWidth(), - mPreviewImage->getHeight(), - mPreviewImage->getComponents()); - raw->copy(mPreviewImage); - // Scale to the thumbnail size - if (!raw->scale(mThumbnailWidth, mThumbnailHeight)) - { - raw = NULL ; - } - } - else - { - // The thumbnail is a screen view with screen grab positioning preview - if(!gViewerWindow->thumbnailSnapshot(raw, - mThumbnailWidth, mThumbnailHeight, - mAllowRenderUI && gSavedSettings.getBOOL("RenderUIInSnapshot"), - gSavedSettings.getBOOL("RenderHUDInSnapshot"), - false, - gSavedSettings.getBOOL("RenderSnapshotNoPost"), - mSnapshotBufferType) ) - { - raw = NULL ; - } - } - - if (raw) - { - // Filter the thumbnail - // Note: filtering needs to be done *before* the scaling to power of 2 or the effect is distorted - if (getFilter() != "") - { - std::string filter_path = LLImageFiltersManager::getInstance()->getFilterPath(getFilter()); - if (filter_path != "") - { - LLImageFilter filter(filter_path); - filter.executeFilter(raw); - } - else - { - LL_WARNS("Snapshot") << "Couldn't find a path to the following filter : " << getFilter() << LL_ENDL; - } - } - // Scale to a power of 2 so it can be mapped to a texture - raw->expandToPowerOfTwo(); - mThumbnailImage = LLViewerTextureManager::getLocalTexture(raw.get(), false); - mThumbnailUpToDate = true ; - } - - //unlock updating - mThumbnailUpdateLock = false ; -} - -LLViewerTexture* LLSnapshotLivePreview::getBigThumbnailImage() -{ - if (mThumbnailUpdateLock) //in the process of updating - { - return NULL; - } - if (mBigThumbnailUpToDate && mBigThumbnailImage)//already updated - { - return mBigThumbnailImage; - } - - LLPointer raw = new LLImageRaw; - - if (raw) - { - // The big thumbnail is a new filtered version of the preview (used in SL Share previews, i.e. Flickr, Twitter) - mBigThumbnailWidth = mPreviewImage->getWidth(); - mBigThumbnailHeight = mPreviewImage->getHeight(); - raw->resize( mBigThumbnailWidth, - mBigThumbnailHeight, - mPreviewImage->getComponents()); - raw->copy(mPreviewImage); - - // Filter - // Note: filtering needs to be done *before* the scaling to power of 2 or the effect is distorted - if (getFilter() != "") - { - std::string filter_path = LLImageFiltersManager::getInstance()->getFilterPath(getFilter()); - if (filter_path != "") - { - LLImageFilter filter(filter_path); - filter.executeFilter(raw); - } - else - { - LL_WARNS("Snapshot") << "Couldn't find a path to the following filter : " << getFilter() << LL_ENDL; - } - } - // Scale to a power of 2 so it can be mapped to a texture - raw->expandToPowerOfTwo(); - mBigThumbnailImage = LLViewerTextureManager::getLocalTexture(raw.get(), false); - mBigThumbnailUpToDate = true ; - } - - return mBigThumbnailImage ; -} - -// Called often. Checks whether it's time to grab a new snapshot and if so, does it. -// Returns true if new snapshot generated, false otherwise. -//static -bool LLSnapshotLivePreview::onIdle( void* snapshot_preview ) -{ - LLSnapshotLivePreview* previewp = (LLSnapshotLivePreview*)snapshot_preview; - if (previewp->getWidth() == 0 || previewp->getHeight() == 0) - { - LL_WARNS("Snapshot") << "Incorrect dimensions: " << previewp->getWidth() << "x" << previewp->getHeight() << LL_ENDL; - return false; - } - - if (previewp->mSnapshotDelayTimer.getStarted()) // Wait for a snapshot delay timer - { - if (!previewp->mSnapshotDelayTimer.hasExpired()) - { - return false; - } - previewp->mSnapshotDelayTimer.stop(); - } - - if (LLToolCamera::getInstance()->hasMouseCapture()) // Hide full-screen preview while camming, either don't take snapshots while ALT-zoom active - { - previewp->setVisible(false); - return false; - } - - // If we're in freeze-frame and/or auto update mode and camera has moved, update snapshot. - LLVector3 new_camera_pos = LLViewerCamera::getInstance()->getOrigin(); - LLQuaternion new_camera_rot = LLViewerCamera::getInstance()->getQuaternion(); - if (previewp->mForceUpdateSnapshot || - (((gSavedSettings.getBOOL("AutoSnapshot") && LLView::isAvailable(previewp->mViewContainer)) || - (gSavedSettings.getBOOL("FreezeTime") && previewp->mAllowFullScreenPreview)) && - (new_camera_pos != previewp->mCameraPos || dot(new_camera_rot, previewp->mCameraRot) < 0.995f))) - { - previewp->mCameraPos = new_camera_pos; - previewp->mCameraRot = new_camera_rot; - // request a new snapshot whenever the camera moves, with a time delay - bool new_snapshot = gSavedSettings.getBOOL("AutoSnapshot") || previewp->mForceUpdateSnapshot; - LL_DEBUGS("Snapshot") << "camera moved, updating thumbnail" << LL_ENDL; - previewp->updateSnapshot( - new_snapshot, // whether a new snapshot is needed or merely invalidate the existing one - false, // or if 1st arg is false, whether to produce a new thumbnail image. - new_snapshot ? AUTO_SNAPSHOT_TIME_DELAY : 0.f); // shutter delay if 1st arg is true. - previewp->mForceUpdateSnapshot = false; - } - - if (previewp->getSnapshotUpToDate() && previewp->getThumbnailUpToDate()) - { - return false; - } - - // time to produce a snapshot - if(!previewp->getSnapshotUpToDate()) - { - LL_DEBUGS("Snapshot") << "producing snapshot" << LL_ENDL; - if (!previewp->mPreviewImage) - { - previewp->mPreviewImage = new LLImageRaw; - } - - previewp->mSnapshotActive = true; - - previewp->setVisible(false); - previewp->setEnabled(false); - - previewp->getWindow()->incBusyCount(); - previewp->setImageScaled(false); - - // grab the raw image - if (gViewerWindow->rawSnapshot( - previewp->mPreviewImage, - previewp->getWidth(), - previewp->getHeight(), - previewp->mKeepAspectRatio,//gSavedSettings.getBOOL("KeepAspectForSnapshot"), - previewp->getSnapshotType() == LLSnapshotModel::SNAPSHOT_TEXTURE, - previewp->mAllowRenderUI && gSavedSettings.getBOOL("RenderUIInSnapshot"), - gSavedSettings.getBOOL("RenderHUDInSnapshot"), - false, - gSavedSettings.getBOOL("RenderSnapshotNoPost"), - previewp->mSnapshotBufferType, - previewp->getMaxImageSize())) - { - // Invalidate/delete any existing encoded image - previewp->mPreviewImageEncoded = NULL; - // Invalidate/delete any existing formatted image - previewp->mFormattedImage = NULL; - // Update the data size - previewp->estimateDataSize(); - - // Full size preview is set: get the decoded image result and save it for animation - if (gSavedSettings.getBOOL("UseFreezeFrame") && previewp->mAllowFullScreenPreview) - { - previewp->prepareFreezeFrame(); - } - - // The snapshot is updated now... - previewp->mSnapshotUpToDate = true; - - // We need to update the thumbnail though - previewp->setThumbnailImageSize(); - previewp->generateThumbnailImage(true) ; - } - previewp->getWindow()->decBusyCount(); - previewp->setVisible(gSavedSettings.getBOOL("UseFreezeFrame") && previewp->mAllowFullScreenPreview); // only show fullscreen preview when in freeze frame mode - previewp->mSnapshotActive = false; - LL_DEBUGS("Snapshot") << "done creating snapshot" << LL_ENDL; - } - - if (!previewp->getThumbnailUpToDate()) - { - previewp->generateThumbnailImage() ; - } - - // Tell the floater container that the snapshot is updated now - if (previewp->mViewContainer) - { - previewp->mViewContainer->notify(LLSD().with("snapshot-updated", true)); - } - - return true; -} - -void LLSnapshotLivePreview::prepareFreezeFrame() -{ - // Get the decoded version of the formatted image - getEncodedImage(); - - LLImageDataSharedLock lock(mPreviewImageEncoded); - - // We need to scale that a bit for display... - LLPointer scaled = new LLImageRaw( - mPreviewImageEncoded->getData(), - mPreviewImageEncoded->getWidth(), - mPreviewImageEncoded->getHeight(), - mPreviewImageEncoded->getComponents()); - - if (!scaled->isBufferInvalid()) - { - // leave original image dimensions, just scale up texture buffer - if (mPreviewImageEncoded->getWidth() > 1024 || mPreviewImageEncoded->getHeight() > 1024) - { - // go ahead and shrink image to appropriate power of 2 for display - scaled->biasedScaleToPowerOfTwo(1024); - setImageScaled(true); - } - else - { - // expand image but keep original image data intact - scaled->expandToPowerOfTwo(1024, false); - } - - mViewerImage[mCurImageIndex] = LLViewerTextureManager::getLocalTexture(scaled.get(), false); - LLPointer curr_preview_image = mViewerImage[mCurImageIndex]; - gGL.getTexUnit(0)->bind(curr_preview_image); - curr_preview_image->setFilteringOption(getSnapshotType() == LLSnapshotModel::SNAPSHOT_TEXTURE ? LLTexUnit::TFO_ANISOTROPIC : LLTexUnit::TFO_POINT); - curr_preview_image->setAddressMode(LLTexUnit::TAM_CLAMP); - - - if (gSavedSettings.getBOOL("UseFreezeFrame") && mAllowFullScreenPreview) - { - mShineCountdown = 4; // wait a few frames to avoid animation glitch due to readback this frame - } - } -} - -S32 LLSnapshotLivePreview::getEncodedImageWidth() const -{ - S32 width = getWidth(); - if (getSnapshotType() == LLSnapshotModel::SNAPSHOT_TEXTURE) - { - width = LLImageRaw::biasedDimToPowerOfTwo(width,MAX_TEXTURE_SIZE); - } - return width; -} -S32 LLSnapshotLivePreview::getEncodedImageHeight() const -{ - S32 height = getHeight(); - if (getSnapshotType() == LLSnapshotModel::SNAPSHOT_TEXTURE) - { - height = LLImageRaw::biasedDimToPowerOfTwo(height,MAX_TEXTURE_SIZE); - } - return height; -} - -LLPointer LLSnapshotLivePreview::getEncodedImage() -{ - if (!mPreviewImageEncoded) - { - LLImageDataSharedLock lock(mPreviewImage); - - mPreviewImageEncoded = new LLImageRaw; - - mPreviewImageEncoded->resize( - mPreviewImage->getWidth(), - mPreviewImage->getHeight(), - mPreviewImage->getComponents()); - - if (getSnapshotType() == LLSnapshotModel::SNAPSHOT_TEXTURE) - { - // We don't store the intermediate formatted image in mFormattedImage in the J2C case - LL_DEBUGS("Snapshot") << "Encoding new image of format J2C" << LL_ENDL; - LLPointer formatted = new LLImageJ2C; - // Copy the preview - LLPointer scaled = new LLImageRaw( - mPreviewImage->getData(), - mPreviewImage->getWidth(), - mPreviewImage->getHeight(), - mPreviewImage->getComponents()); - // Scale it as required by J2C - scaled->biasedScaleToPowerOfTwo(MAX_TEXTURE_SIZE); - setImageScaled(true); - // Compress to J2C - if (formatted->encode(scaled, 0.f)) - { - // We can update the data size precisely at that point - mDataSize = formatted->getDataSize(); - // Decompress back - formatted->decode(mPreviewImageEncoded, 0); - } - } - else - { - // Update mFormattedImage if necessary - getFormattedImage(); - if (getSnapshotFormat() == LLSnapshotModel::SNAPSHOT_FORMAT_BMP) - { - // BMP hack : copy instead of decode otherwise decode will crash. - mPreviewImageEncoded->copy(mPreviewImage); - } - else - { - // Decode back - mFormattedImage->decode(mPreviewImageEncoded, 0); - } - } - } - return mPreviewImageEncoded; -} - -bool LLSnapshotLivePreview::createUploadFile(const std::string &out_filename, const S32 max_image_dimentions, const S32 min_image_dimentions) -{ - return LLViewerTextureList::createUploadFile(mPreviewImage, out_filename, max_image_dimentions, min_image_dimentions); -} - -// We actually estimate the data size so that we do not require actual compression when showing the preview -// Note : whenever formatted image is computed, mDataSize will be updated to reflect the true size -void LLSnapshotLivePreview::estimateDataSize() -{ - // Compression ratio - F32 ratio = 1.0; - - if (getSnapshotType() == LLSnapshotModel::SNAPSHOT_TEXTURE) - { - ratio = 8.0; // This is what we shoot for when compressing to J2C - } - else - { - LLSnapshotModel::ESnapshotFormat format = getSnapshotFormat(); - switch (format) - { - case LLSnapshotModel::SNAPSHOT_FORMAT_PNG: - ratio = 3.0; // Average observed PNG compression ratio - break; - case LLSnapshotModel::SNAPSHOT_FORMAT_JPEG: - // Observed from JPG compression tests - ratio = (110 - mSnapshotQuality) / 2; - break; - case LLSnapshotModel::SNAPSHOT_FORMAT_BMP: - ratio = 1.0; // No compression with BMP - break; - } - } - mDataSize = (S32)((F32)mPreviewImage->getDataSize() / ratio); -} - -LLPointer LLSnapshotLivePreview::getFormattedImage() -{ - if (!mFormattedImage) - { - // Apply the filter to mPreviewImage - if (getFilter() != "") - { - std::string filter_path = LLImageFiltersManager::getInstance()->getFilterPath(getFilter()); - if (filter_path != "") - { - LLImageFilter filter(filter_path); - filter.executeFilter(mPreviewImage); - } - else - { - LL_WARNS("Snapshot") << "Couldn't find a path to the following filter : " << getFilter() << LL_ENDL; - } - } - - // Create the new formatted image of the appropriate format. - LLSnapshotModel::ESnapshotFormat format = getSnapshotFormat(); - LL_DEBUGS("Snapshot") << "Encoding new image of format " << format << LL_ENDL; - - switch (format) - { - case LLSnapshotModel::SNAPSHOT_FORMAT_PNG: - mFormattedImage = new LLImagePNG(); - break; - case LLSnapshotModel::SNAPSHOT_FORMAT_JPEG: - mFormattedImage = new LLImageJPEG(mSnapshotQuality); - break; - case LLSnapshotModel::SNAPSHOT_FORMAT_BMP: - mFormattedImage = new LLImageBMP(); - break; - } - if (mFormattedImage->encode(mPreviewImage, 0)) - { - // We can update the data size precisely at that point - mDataSize = mFormattedImage->getDataSize(); - } - } - return mFormattedImage; -} - -void LLSnapshotLivePreview::setSize(S32 w, S32 h) -{ - LL_DEBUGS("Snapshot") << "setSize(" << w << ", " << h << ")" << LL_ENDL; - setWidth(w); - setHeight(h); -} - -void LLSnapshotLivePreview::setSnapshotFormat(LLSnapshotModel::ESnapshotFormat format) -{ - if (mSnapshotFormat != format) - { - mFormattedImage = NULL; // Invalidate the already formatted image if any - mSnapshotFormat = format; - } -} - -void LLSnapshotLivePreview::getSize(S32& w, S32& h) const -{ - w = getWidth(); - h = getHeight(); -} - -void LLSnapshotLivePreview::saveTexture(bool outfit_snapshot, std::string name) -{ - LLImageDataSharedLock lock(mPreviewImage); - - LL_DEBUGS("Snapshot") << "saving texture: " << mPreviewImage->getWidth() << "x" << mPreviewImage->getHeight() << LL_ENDL; - // gen a new uuid for this asset - LLTransactionID tid; - tid.generate(); - LLAssetID new_asset_id = tid.makeAssetID(gAgent.getSecureSessionID()); - - LLPointer formatted = new LLImageJ2C; - LLPointer scaled = new LLImageRaw(mPreviewImage->getData(), - mPreviewImage->getWidth(), - mPreviewImage->getHeight(), - mPreviewImage->getComponents()); - - // Apply the filter to mPreviewImage - if (getFilter() != "") - { - std::string filter_path = LLImageFiltersManager::getInstance()->getFilterPath(getFilter()); - if (filter_path != "") - { - LLImageFilter filter(filter_path); - filter.executeFilter(scaled); - } - else - { - LL_WARNS("Snapshot") << "Couldn't find a path to the following filter : " << getFilter() << LL_ENDL; - } - } - - scaled->biasedScaleToPowerOfTwo(MAX_TEXTURE_SIZE); - LL_DEBUGS("Snapshot") << "scaled texture to " << scaled->getWidth() << "x" << scaled->getHeight() << LL_ENDL; - - if (formatted->encode(scaled, 0.0f)) - { - LLFileSystem fmt_file(new_asset_id, LLAssetType::AT_TEXTURE, LLFileSystem::WRITE); - fmt_file.write(formatted->getData(), formatted->getDataSize()); - std::string pos_string; - LLAgentUI::buildLocationString(pos_string, LLAgentUI::LOCATION_FORMAT_FULL); - std::string who_took_it; - LLAgentUI::buildFullname(who_took_it); - S32 expected_upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost(); - std::string res_name = outfit_snapshot ? name : "Snapshot : " + pos_string; - std::string res_desc = outfit_snapshot ? "" : "Taken by " + who_took_it + " at " + pos_string; - LLFolderType::EType folder_type = outfit_snapshot ? LLFolderType::FT_NONE : LLFolderType::FT_SNAPSHOT_CATEGORY; - LLInventoryType::EType inv_type = outfit_snapshot ? LLInventoryType::IT_NONE : LLInventoryType::IT_SNAPSHOT; - - LLResourceUploadInfo::ptr_t assetUploadInfo(new LLResourceUploadInfo( - tid, LLAssetType::AT_TEXTURE, res_name, res_desc, 0, - folder_type, inv_type, - PERM_ALL, LLFloaterPerms::getGroupPerms("Uploads"), LLFloaterPerms::getEveryonePerms("Uploads"), - expected_upload_cost, !outfit_snapshot)); - - upload_new_resource(assetUploadInfo); - - gViewerWindow->playSnapshotAnimAndSound(); - } - else - { - LLNotificationsUtil::add("ErrorEncodingSnapshot"); - LL_WARNS("Snapshot") << "Error encoding snapshot" << LL_ENDL; - } - - add(LLStatViewer::SNAPSHOT, 1); - - mDataSize = 0; -} - -void LLSnapshotLivePreview::saveLocal(const snapshot_saved_signal_t::slot_type& success_cb, const snapshot_saved_signal_t::slot_type& failure_cb) -{ - // Update mFormattedImage if necessary - getFormattedImage(); - - // Save the formatted image - saveLocal(mFormattedImage, success_cb, failure_cb); -} - -//Check if failed due to insufficient memory -void LLSnapshotLivePreview::saveLocal(LLPointer image, const snapshot_saved_signal_t::slot_type& success_cb, const snapshot_saved_signal_t::slot_type& failure_cb) -{ - sSaveLocalImage = image; - - gViewerWindow->saveImageNumbered(sSaveLocalImage, false, success_cb, failure_cb); -} +/** +* @file llsnapshotlivepreview.cpp +* @brief Implementation of llsnapshotlivepreview +* @author Gilbert@lindenlab.com +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2014, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" + +#include "llagent.h" +#include "llagentbenefits.h" +#include "llagentcamera.h" +#include "llagentui.h" +#include "llfilesystem.h" +#include "llcombobox.h" +#include "llfloaterperms.h" +#include "llfloaterreg.h" +#include "llimagefilter.h" +#include "llimagefiltersmanager.h" +#include "llimagebmp.h" +#include "llimagej2c.h" +#include "llimagejpeg.h" +#include "llimagepng.h" +#include "lllandmarkactions.h" +#include "lllocalcliprect.h" +#include "llresmgr.h" +#include "llnotificationsutil.h" +#include "llslurl.h" +#include "llsnapshotlivepreview.h" +#include "lltoolfocus.h" +#include "llviewercontrol.h" +#include "llviewermenufile.h" // upload_new_resource() +#include "llviewerstats.h" +#include "llviewertexturelist.h" +#include "llwindow.h" +#include "llworld.h" +#include + +constexpr F32 AUTO_SNAPSHOT_TIME_DELAY = 1.f; + +constexpr F32 SHINE_TIME = 0.5f; +constexpr F32 SHINE_WIDTH = 0.6f; +constexpr F32 SHINE_OPACITY = 0.3f; +constexpr F32 FALL_TIME = 0.6f; +constexpr S32 BORDER_WIDTH = 6; +constexpr S32 TOP_PANEL_HEIGHT = 30; + +constexpr S32 MAX_TEXTURE_SIZE = 512 ; //max upload texture size 512 * 512 + +std::set LLSnapshotLivePreview::sList; +LLPointer LLSnapshotLivePreview::sSaveLocalImage = NULL; + +LLSnapshotLivePreview::LLSnapshotLivePreview (const LLSnapshotLivePreview::Params& p) + : LLView(p), + mColor(1.f, 0.f, 0.f, 0.5f), + mCurImageIndex(0), + mPreviewImage(NULL), + mThumbnailImage(NULL) , + mBigThumbnailImage(NULL) , + mThumbnailWidth(0), + mThumbnailHeight(0), + mThumbnailSubsampled(false), + mPreviewImageEncoded(NULL), + mFormattedImage(NULL), + mShineCountdown(0), + mFlashAlpha(0.f), + mNeedsFlash(true), + mSnapshotQuality(gSavedSettings.getS32("SnapshotQuality")), + mDataSize(0), + mSnapshotType(LLSnapshotModel::SNAPSHOT_POSTCARD), + mSnapshotFormat(LLSnapshotModel::ESnapshotFormat(gSavedSettings.getS32("SnapshotFormat"))), + mSnapshotUpToDate(false), + mCameraPos(LLViewerCamera::getInstance()->getOrigin()), + mCameraRot(LLViewerCamera::getInstance()->getQuaternion()), + mSnapshotActive(false), + mSnapshotBufferType(LLSnapshotModel::SNAPSHOT_TYPE_COLOR), + mFilterName(""), + mAllowRenderUI(true), + mAllowFullScreenPreview(true), + mViewContainer(NULL) +{ + setSnapshotQuality(gSavedSettings.getS32("SnapshotQuality")); + mSnapshotDelayTimer.setTimerExpirySec(0.0f); + mSnapshotDelayTimer.start(); + // gIdleCallbacks.addFunction( &LLSnapshotLivePreview::onIdle, (void*)this ); + sList.insert(this); + setFollowsAll(); + mWidth[0] = gViewerWindow->getWindowWidthRaw(); + mWidth[1] = gViewerWindow->getWindowWidthRaw(); + mHeight[0] = gViewerWindow->getWindowHeightRaw(); + mHeight[1] = gViewerWindow->getWindowHeightRaw(); + mImageScaled[0] = false; + mImageScaled[1] = false; + + mMaxImageSize = MAX_SNAPSHOT_IMAGE_SIZE ; + mKeepAspectRatio = gSavedSettings.getBOOL("KeepAspectForSnapshot") ; + mThumbnailUpdateLock = false ; + mThumbnailUpToDate = false ; + mBigThumbnailUpToDate = false ; + + mForceUpdateSnapshot = false; +} + +LLSnapshotLivePreview::~LLSnapshotLivePreview() +{ + // delete images + mPreviewImage = NULL; + mPreviewImageEncoded = NULL; + mFormattedImage = NULL; + + // gIdleCallbacks.deleteFunction( &LLSnapshotLivePreview::onIdle, (void*)this ); + sList.erase(this); + sSaveLocalImage = NULL; +} + +void LLSnapshotLivePreview::setMaxImageSize(S32 size) +{ + mMaxImageSize = llmin(size,(S32)(MAX_SNAPSHOT_IMAGE_SIZE)); +} + +LLViewerTexture* LLSnapshotLivePreview::getCurrentImage() +{ + return mViewerImage[mCurImageIndex]; +} + +F32 LLSnapshotLivePreview::getImageAspect() +{ + if (!getCurrentImage()) + { + return 0.f; + } + // mKeepAspectRatio) == gSavedSettings.getBOOL("KeepAspectForSnapshot")) + return (mKeepAspectRatio ? ((F32)getRect().getWidth()) / ((F32)getRect().getHeight()) : ((F32)getWidth()) / ((F32)getHeight())); +} + +void LLSnapshotLivePreview::updateSnapshot(bool new_snapshot, bool new_thumbnail, F32 delay) +{ + LL_DEBUGS("Snapshot") << "updateSnapshot: mSnapshotUpToDate = " << getSnapshotUpToDate() << LL_ENDL; + + // Update snapshot if requested. + if (new_snapshot) + { + if (getSnapshotUpToDate()) + { + S32 old_image_index = mCurImageIndex; + mCurImageIndex = (mCurImageIndex + 1) % 2; + setSize(mWidth[old_image_index], mHeight[old_image_index]); + mFallAnimTimer.start(); + } + mSnapshotUpToDate = false; + + // Update snapshot source rect depending on whether we keep the aspect ratio. + LLRect& rect = mImageRect[mCurImageIndex]; + rect.set(0, getRect().getHeight(), getRect().getWidth(), 0); + + F32 image_aspect_ratio = ((F32)getWidth()) / ((F32)getHeight()); + F32 window_aspect_ratio = ((F32)getRect().getWidth()) / ((F32)getRect().getHeight()); + + if (mKeepAspectRatio)//gSavedSettings.getBOOL("KeepAspectForSnapshot")) + { + if (image_aspect_ratio > window_aspect_ratio) + { + // trim off top and bottom + S32 new_height = ll_round((F32)getRect().getWidth() / image_aspect_ratio); + rect.mBottom += (getRect().getHeight() - new_height) / 2; + rect.mTop -= (getRect().getHeight() - new_height) / 2; + } + else if (image_aspect_ratio < window_aspect_ratio) + { + // trim off left and right + S32 new_width = ll_round((F32)getRect().getHeight() * image_aspect_ratio); + rect.mLeft += (getRect().getWidth() - new_width) / 2; + rect.mRight -= (getRect().getWidth() - new_width) / 2; + } + } + + // Stop shining animation. + mShineAnimTimer.stop(); + mSnapshotDelayTimer.start(); + mSnapshotDelayTimer.resetWithExpiry(delay); + + + mPosTakenGlobal = gAgentCamera.getCameraPositionGlobal(); + + // Tell the floater container that the snapshot is in the process of updating itself + if (mViewContainer) + { + mViewContainer->notify(LLSD().with("snapshot-updating", true)); + } + } + + // Update thumbnail if requested. + if (new_thumbnail) + { + mThumbnailUpToDate = false ; + mBigThumbnailUpToDate = false; + } +} + +// Return true if the quality has been changed, false otherwise +bool LLSnapshotLivePreview::setSnapshotQuality(S32 quality, bool set_by_user) +{ + llclamp(quality, 0, 100); + if (quality != mSnapshotQuality) + { + mSnapshotQuality = quality; + if (set_by_user) + { + gSavedSettings.setS32("SnapshotQuality", quality); + } + mFormattedImage = NULL; // Invalidate the already formatted image if any + return true; + } + return false; +} + +void LLSnapshotLivePreview::drawPreviewRect(S32 offset_x, S32 offset_y, LLColor4 alpha_color) +{ + F32 line_width ; + glGetFloatv(GL_LINE_WIDTH, &line_width) ; + glLineWidth(2.0f * line_width) ; + LLColor4 color(0.0f, 0.0f, 0.0f, 1.0f) ; + gl_rect_2d( mPreviewRect.mLeft + offset_x, mPreviewRect.mTop + offset_y, + mPreviewRect.mRight + offset_x, mPreviewRect.mBottom + offset_y, color, false ) ; + glLineWidth(line_width) ; + + //draw four alpha rectangles to cover areas outside of the snapshot image + if(!mKeepAspectRatio) + { + S32 dwl = 0, dwr = 0 ; + if(mThumbnailWidth > mPreviewRect.getWidth()) + { + dwl = (mThumbnailWidth - mPreviewRect.getWidth()) >> 1 ; + dwr = mThumbnailWidth - mPreviewRect.getWidth() - dwl ; + + gl_rect_2d(mPreviewRect.mLeft + offset_x - dwl, mPreviewRect.mTop + offset_y, + mPreviewRect.mLeft + offset_x, mPreviewRect.mBottom + offset_y, alpha_color, true ) ; + gl_rect_2d( mPreviewRect.mRight + offset_x, mPreviewRect.mTop + offset_y, + mPreviewRect.mRight + offset_x + dwr, mPreviewRect.mBottom + offset_y, alpha_color, true ) ; + } + + if(mThumbnailHeight > mPreviewRect.getHeight()) + { + S32 dh = (mThumbnailHeight - mPreviewRect.getHeight()) >> 1 ; + gl_rect_2d(mPreviewRect.mLeft + offset_x - dwl, mPreviewRect.mBottom + offset_y , + mPreviewRect.mRight + offset_x + dwr, mPreviewRect.mBottom + offset_y - dh, alpha_color, true ) ; + + dh = mThumbnailHeight - mPreviewRect.getHeight() - dh ; + gl_rect_2d( mPreviewRect.mLeft + offset_x - dwl, mPreviewRect.mTop + offset_y + dh, + mPreviewRect.mRight + offset_x + dwr, mPreviewRect.mTop + offset_y, alpha_color, true ) ; + } + } +} + +//called when the frame is frozen. +void LLSnapshotLivePreview::draw() +{ + if (getCurrentImage() && + mPreviewImageEncoded.notNull() && + getSnapshotUpToDate()) + { + LLColor4 bg_color(0.f, 0.f, 0.3f, 0.4f); + gl_rect_2d(getRect(), bg_color); + const LLRect& rect = getImageRect(); + LLRect shadow_rect = rect; + shadow_rect.stretch(BORDER_WIDTH); + gl_drop_shadow(shadow_rect.mLeft, shadow_rect.mTop, shadow_rect.mRight, shadow_rect.mBottom, LLColor4(0.f, 0.f, 0.f, mNeedsFlash ? 0.f :0.5f), 10); + + LLColor4 image_color(1.f, 1.f, 1.f, 1.f); + gGL.color4fv(image_color.mV); + gGL.getTexUnit(0)->bind(getCurrentImage()); + // calculate UV scale + F32 uv_width = isImageScaled() ? 1.f : llmin((F32)getWidth() / (F32)getCurrentImage()->getWidth(), 1.f); + F32 uv_height = isImageScaled() ? 1.f : llmin((F32)getHeight() / (F32)getCurrentImage()->getHeight(), 1.f); + gGL.pushMatrix(); + { + gGL.translatef((F32)rect.mLeft, (F32)rect.mBottom + TOP_PANEL_HEIGHT, 0.f); + gGL.begin(LLRender::QUADS); + { + gGL.texCoord2f(uv_width, uv_height); + gGL.vertex2i(rect.getWidth(), rect.getHeight() ); + + gGL.texCoord2f(0.f, uv_height); + gGL.vertex2i(0, rect.getHeight() ); + + gGL.texCoord2f(0.f, 0.f); + gGL.vertex2i(0, 0); + + gGL.texCoord2f(uv_width, 0.f); + gGL.vertex2i(rect.getWidth(), 0); + } + gGL.end(); + } + gGL.popMatrix(); + + gGL.color4f(1.f, 1.f, 1.f, mFlashAlpha); + gl_rect_2d(getRect()); + if (mNeedsFlash) + { + if (mFlashAlpha < 1.f) + { + mFlashAlpha = lerp(mFlashAlpha, 1.f, LLCriticalDamp::getInterpolant(0.02f)); + } + else + { + mNeedsFlash = false; + } + } + else + { + mFlashAlpha = lerp(mFlashAlpha, 0.f, LLCriticalDamp::getInterpolant(0.15f)); + } + + // Draw shining animation if appropriate. + if (mShineCountdown > 0) + { + mShineCountdown--; + if (mShineCountdown == 0) + { + mShineAnimTimer.start(); + } + } + else if (mShineAnimTimer.getStarted()) + { + LL_DEBUGS("Snapshot") << "Drawing shining animation" << LL_ENDL; + F32 shine_interp = llmin(1.f, mShineAnimTimer.getElapsedTimeF32() / SHINE_TIME); + + // draw "shine" effect + LLRect local_rect(0, getRect().getHeight() + TOP_PANEL_HEIGHT, getRect().getWidth(), 0); + LLLocalClipRect clip(local_rect); + { + // draw diagonal stripe with gradient that passes over screen + S32 x1 = gViewerWindow->getWindowWidthScaled() * ll_round((clamp_rescale(shine_interp, 0.f, 1.f, -1.f - SHINE_WIDTH, 1.f))); + S32 x2 = x1 + ll_round(gViewerWindow->getWindowWidthScaled() * SHINE_WIDTH); + S32 x3 = x2 + ll_round(gViewerWindow->getWindowWidthScaled() * SHINE_WIDTH); + S32 y1 = 0; + S32 y2 = gViewerWindow->getWindowHeightScaled() + TOP_PANEL_HEIGHT; + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gGL.begin(LLRender::QUADS); + { + gGL.color4f(1.f, 1.f, 1.f, 0.f); + gGL.vertex2i(x1, y1); + gGL.vertex2i(x1 + gViewerWindow->getWindowWidthScaled(), y2); + gGL.color4f(1.f, 1.f, 1.f, SHINE_OPACITY); + gGL.vertex2i(x2 + gViewerWindow->getWindowWidthScaled(), y2); + gGL.vertex2i(x2, y1); + + gGL.color4f(1.f, 1.f, 1.f, SHINE_OPACITY); + gGL.vertex2i(x2, y1); + gGL.vertex2i(x2 + gViewerWindow->getWindowWidthScaled(), y2); + gGL.color4f(1.f, 1.f, 1.f, 0.f); + gGL.vertex2i(x3 + gViewerWindow->getWindowWidthScaled(), y2); + gGL.vertex2i(x3, y1); + } + gGL.end(); + } + + // if we're at the end of the animation, stop + if (shine_interp >= 1.f) + { + mShineAnimTimer.stop(); + } + } + } + + // draw old image dropping away + if (mFallAnimTimer.getStarted()) + { + S32 old_image_index = (mCurImageIndex + 1) % 2; + if (mViewerImage[old_image_index].notNull() && mFallAnimTimer.getElapsedTimeF32() < FALL_TIME) + { + LL_DEBUGS("Snapshot") << "Drawing fall animation" << LL_ENDL; + F32 fall_interp = mFallAnimTimer.getElapsedTimeF32() / FALL_TIME; + F32 alpha = clamp_rescale(fall_interp, 0.f, 1.f, 0.8f, 0.4f); + LLColor4 image_color(1.f, 1.f, 1.f, alpha); + gGL.color4fv(image_color.mV); + gGL.getTexUnit(0)->bind(mViewerImage[old_image_index]); + // calculate UV scale + // *FIX get this to work with old image + bool rescale = !mImageScaled[old_image_index] && mViewerImage[mCurImageIndex].notNull(); + F32 uv_width = rescale ? llmin((F32)mWidth[old_image_index] / (F32)mViewerImage[mCurImageIndex]->getWidth(), 1.f) : 1.f; + F32 uv_height = rescale ? llmin((F32)mHeight[old_image_index] / (F32)mViewerImage[mCurImageIndex]->getHeight(), 1.f) : 1.f; + gGL.pushMatrix(); + { + LLRect& rect = mImageRect[old_image_index]; + gGL.translatef((F32)rect.mLeft, (F32)rect.mBottom - ll_round(getRect().getHeight() * 2.f * (fall_interp * fall_interp)), 0.f); + gGL.rotatef(-45.f * fall_interp, 0.f, 0.f, 1.f); + gGL.begin(LLRender::QUADS); + { + gGL.texCoord2f(uv_width, uv_height); + gGL.vertex2i(rect.getWidth(), rect.getHeight() ); + + gGL.texCoord2f(0.f, uv_height); + gGL.vertex2i(0, rect.getHeight() ); + + gGL.texCoord2f(0.f, 0.f); + gGL.vertex2i(0, 0); + + gGL.texCoord2f(uv_width, 0.f); + gGL.vertex2i(rect.getWidth(), 0); + } + gGL.end(); + } + gGL.popMatrix(); + } + } +} + +/*virtual*/ +void LLSnapshotLivePreview::reshape(S32 width, S32 height, bool called_from_parent) +{ + LLRect old_rect = getRect(); + LLView::reshape(width, height, called_from_parent); + if (old_rect.getWidth() != width || old_rect.getHeight() != height) + { + LL_DEBUGS("Window", "Snapshot") << "window reshaped, updating thumbnail" << LL_ENDL; + if (mViewContainer && mViewContainer->isInVisibleChain()) + { + // We usually resize only on window reshape, so give it a chance to redraw, assign delay + updateSnapshot( + true, // new snapshot is needed + false, // thumbnail will be updated either way. + AUTO_SNAPSHOT_TIME_DELAY); // shutter delay. + } + } +} + +bool LLSnapshotLivePreview::setThumbnailImageSize() +{ + if (getWidth() < 10 || getHeight() < 10) + { + return false ; + } + S32 width = (mThumbnailSubsampled ? mPreviewImage->getWidth() : gViewerWindow->getWindowWidthRaw()); + S32 height = (mThumbnailSubsampled ? mPreviewImage->getHeight() : gViewerWindow->getWindowHeightRaw()) ; + + F32 aspect_ratio = ((F32)width) / ((F32)height); + + // UI size for thumbnail + S32 max_width = mThumbnailPlaceholderRect.getWidth(); + S32 max_height = mThumbnailPlaceholderRect.getHeight(); + + if (aspect_ratio > (F32)max_width / (F32)max_height) + { + // image too wide, shrink to width + mThumbnailWidth = max_width; + mThumbnailHeight = ll_round((F32)max_width / aspect_ratio); + } + else + { + // image too tall, shrink to height + mThumbnailHeight = max_height; + mThumbnailWidth = ll_round((F32)max_height * aspect_ratio); + } + + if (mThumbnailWidth > width || mThumbnailHeight > height) + { + return false ;//if the window is too small, ignore thumbnail updating. + } + + S32 left = 0 , top = mThumbnailHeight, right = mThumbnailWidth, bottom = 0 ; + if (!mKeepAspectRatio) + { + F32 ratio_x = (F32)getWidth() / width ; + F32 ratio_y = (F32)getHeight() / height ; + + if (ratio_x > ratio_y) + { + top = (S32)(top * ratio_y / ratio_x) ; + } + else + { + right = (S32)(right * ratio_x / ratio_y) ; + } + left = (S32)((mThumbnailWidth - right) * 0.5f) ; + bottom = (S32)((mThumbnailHeight - top) * 0.5f) ; + top += bottom ; + right += left ; + } + mPreviewRect.set(left - 1, top + 1, right + 1, bottom - 1) ; + + return true ; +} + +void LLSnapshotLivePreview::generateThumbnailImage(bool force_update) +{ + if(mThumbnailUpdateLock) //in the process of updating + { + return ; + } + if(getThumbnailUpToDate() && !force_update)//already updated + { + return ; + } + if(getWidth() < 10 || getHeight() < 10) + { + return ; + } + + ////lock updating + mThumbnailUpdateLock = true ; + + if(!setThumbnailImageSize()) + { + mThumbnailUpdateLock = false ; + mThumbnailUpToDate = true ; + return ; + } + + // Invalidate the big thumbnail when we regenerate the small one + mBigThumbnailUpToDate = false; + + if(mThumbnailImage) + { + resetThumbnailImage() ; + } + + LLPointer raw = new LLImageRaw; + + if (mThumbnailSubsampled) + { + // The thumbnail is be a subsampled version of the preview (used in SL Share previews, i.e. Flickr, Twitter) + raw->resize( mPreviewImage->getWidth(), + mPreviewImage->getHeight(), + mPreviewImage->getComponents()); + raw->copy(mPreviewImage); + // Scale to the thumbnail size + if (!raw->scale(mThumbnailWidth, mThumbnailHeight)) + { + raw = NULL ; + } + } + else + { + // The thumbnail is a screen view with screen grab positioning preview + if(!gViewerWindow->thumbnailSnapshot(raw, + mThumbnailWidth, mThumbnailHeight, + mAllowRenderUI && gSavedSettings.getBOOL("RenderUIInSnapshot"), + gSavedSettings.getBOOL("RenderHUDInSnapshot"), + false, + gSavedSettings.getBOOL("RenderSnapshotNoPost"), + mSnapshotBufferType) ) + { + raw = NULL ; + } + } + + if (raw) + { + // Filter the thumbnail + // Note: filtering needs to be done *before* the scaling to power of 2 or the effect is distorted + if (getFilter() != "") + { + std::string filter_path = LLImageFiltersManager::getInstance()->getFilterPath(getFilter()); + if (filter_path != "") + { + LLImageFilter filter(filter_path); + filter.executeFilter(raw); + } + else + { + LL_WARNS("Snapshot") << "Couldn't find a path to the following filter : " << getFilter() << LL_ENDL; + } + } + // Scale to a power of 2 so it can be mapped to a texture + raw->expandToPowerOfTwo(); + mThumbnailImage = LLViewerTextureManager::getLocalTexture(raw.get(), false); + mThumbnailUpToDate = true ; + } + + //unlock updating + mThumbnailUpdateLock = false ; +} + +LLViewerTexture* LLSnapshotLivePreview::getBigThumbnailImage() +{ + if (mThumbnailUpdateLock) //in the process of updating + { + return NULL; + } + if (mBigThumbnailUpToDate && mBigThumbnailImage)//already updated + { + return mBigThumbnailImage; + } + + LLPointer raw = new LLImageRaw; + + if (raw) + { + // The big thumbnail is a new filtered version of the preview (used in SL Share previews, i.e. Flickr, Twitter) + mBigThumbnailWidth = mPreviewImage->getWidth(); + mBigThumbnailHeight = mPreviewImage->getHeight(); + raw->resize( mBigThumbnailWidth, + mBigThumbnailHeight, + mPreviewImage->getComponents()); + raw->copy(mPreviewImage); + + // Filter + // Note: filtering needs to be done *before* the scaling to power of 2 or the effect is distorted + if (getFilter() != "") + { + std::string filter_path = LLImageFiltersManager::getInstance()->getFilterPath(getFilter()); + if (filter_path != "") + { + LLImageFilter filter(filter_path); + filter.executeFilter(raw); + } + else + { + LL_WARNS("Snapshot") << "Couldn't find a path to the following filter : " << getFilter() << LL_ENDL; + } + } + // Scale to a power of 2 so it can be mapped to a texture + raw->expandToPowerOfTwo(); + mBigThumbnailImage = LLViewerTextureManager::getLocalTexture(raw.get(), false); + mBigThumbnailUpToDate = true ; + } + + return mBigThumbnailImage ; +} + +// Called often. Checks whether it's time to grab a new snapshot and if so, does it. +// Returns true if new snapshot generated, false otherwise. +//static +bool LLSnapshotLivePreview::onIdle( void* snapshot_preview ) +{ + LLSnapshotLivePreview* previewp = (LLSnapshotLivePreview*)snapshot_preview; + if (previewp->getWidth() == 0 || previewp->getHeight() == 0) + { + LL_WARNS("Snapshot") << "Incorrect dimensions: " << previewp->getWidth() << "x" << previewp->getHeight() << LL_ENDL; + return false; + } + + if (previewp->mSnapshotDelayTimer.getStarted()) // Wait for a snapshot delay timer + { + if (!previewp->mSnapshotDelayTimer.hasExpired()) + { + return false; + } + previewp->mSnapshotDelayTimer.stop(); + } + + if (LLToolCamera::getInstance()->hasMouseCapture()) // Hide full-screen preview while camming, either don't take snapshots while ALT-zoom active + { + previewp->setVisible(false); + return false; + } + + // If we're in freeze-frame and/or auto update mode and camera has moved, update snapshot. + LLVector3 new_camera_pos = LLViewerCamera::getInstance()->getOrigin(); + LLQuaternion new_camera_rot = LLViewerCamera::getInstance()->getQuaternion(); + if (previewp->mForceUpdateSnapshot || + (((gSavedSettings.getBOOL("AutoSnapshot") && LLView::isAvailable(previewp->mViewContainer)) || + (gSavedSettings.getBOOL("FreezeTime") && previewp->mAllowFullScreenPreview)) && + (new_camera_pos != previewp->mCameraPos || dot(new_camera_rot, previewp->mCameraRot) < 0.995f))) + { + previewp->mCameraPos = new_camera_pos; + previewp->mCameraRot = new_camera_rot; + // request a new snapshot whenever the camera moves, with a time delay + bool new_snapshot = gSavedSettings.getBOOL("AutoSnapshot") || previewp->mForceUpdateSnapshot; + LL_DEBUGS("Snapshot") << "camera moved, updating thumbnail" << LL_ENDL; + previewp->updateSnapshot( + new_snapshot, // whether a new snapshot is needed or merely invalidate the existing one + false, // or if 1st arg is false, whether to produce a new thumbnail image. + new_snapshot ? AUTO_SNAPSHOT_TIME_DELAY : 0.f); // shutter delay if 1st arg is true. + previewp->mForceUpdateSnapshot = false; + } + + if (previewp->getSnapshotUpToDate() && previewp->getThumbnailUpToDate()) + { + return false; + } + + // time to produce a snapshot + if(!previewp->getSnapshotUpToDate()) + { + LL_DEBUGS("Snapshot") << "producing snapshot" << LL_ENDL; + if (!previewp->mPreviewImage) + { + previewp->mPreviewImage = new LLImageRaw; + } + + previewp->mSnapshotActive = true; + + previewp->setVisible(false); + previewp->setEnabled(false); + + previewp->getWindow()->incBusyCount(); + previewp->setImageScaled(false); + + // grab the raw image + if (gViewerWindow->rawSnapshot( + previewp->mPreviewImage, + previewp->getWidth(), + previewp->getHeight(), + previewp->mKeepAspectRatio,//gSavedSettings.getBOOL("KeepAspectForSnapshot"), + previewp->getSnapshotType() == LLSnapshotModel::SNAPSHOT_TEXTURE, + previewp->mAllowRenderUI && gSavedSettings.getBOOL("RenderUIInSnapshot"), + gSavedSettings.getBOOL("RenderHUDInSnapshot"), + false, + gSavedSettings.getBOOL("RenderSnapshotNoPost"), + previewp->mSnapshotBufferType, + previewp->getMaxImageSize())) + { + // Invalidate/delete any existing encoded image + previewp->mPreviewImageEncoded = NULL; + // Invalidate/delete any existing formatted image + previewp->mFormattedImage = NULL; + // Update the data size + previewp->estimateDataSize(); + + // Full size preview is set: get the decoded image result and save it for animation + if (gSavedSettings.getBOOL("UseFreezeFrame") && previewp->mAllowFullScreenPreview) + { + previewp->prepareFreezeFrame(); + } + + // The snapshot is updated now... + previewp->mSnapshotUpToDate = true; + + // We need to update the thumbnail though + previewp->setThumbnailImageSize(); + previewp->generateThumbnailImage(true) ; + } + previewp->getWindow()->decBusyCount(); + previewp->setVisible(gSavedSettings.getBOOL("UseFreezeFrame") && previewp->mAllowFullScreenPreview); // only show fullscreen preview when in freeze frame mode + previewp->mSnapshotActive = false; + LL_DEBUGS("Snapshot") << "done creating snapshot" << LL_ENDL; + } + + if (!previewp->getThumbnailUpToDate()) + { + previewp->generateThumbnailImage() ; + } + + // Tell the floater container that the snapshot is updated now + if (previewp->mViewContainer) + { + previewp->mViewContainer->notify(LLSD().with("snapshot-updated", true)); + } + + return true; +} + +void LLSnapshotLivePreview::prepareFreezeFrame() +{ + // Get the decoded version of the formatted image + getEncodedImage(); + + LLImageDataSharedLock lock(mPreviewImageEncoded); + + // We need to scale that a bit for display... + LLPointer scaled = new LLImageRaw( + mPreviewImageEncoded->getData(), + mPreviewImageEncoded->getWidth(), + mPreviewImageEncoded->getHeight(), + mPreviewImageEncoded->getComponents()); + + if (!scaled->isBufferInvalid()) + { + // leave original image dimensions, just scale up texture buffer + if (mPreviewImageEncoded->getWidth() > 1024 || mPreviewImageEncoded->getHeight() > 1024) + { + // go ahead and shrink image to appropriate power of 2 for display + scaled->biasedScaleToPowerOfTwo(1024); + setImageScaled(true); + } + else + { + // expand image but keep original image data intact + scaled->expandToPowerOfTwo(1024, false); + } + + mViewerImage[mCurImageIndex] = LLViewerTextureManager::getLocalTexture(scaled.get(), false); + LLPointer curr_preview_image = mViewerImage[mCurImageIndex]; + gGL.getTexUnit(0)->bind(curr_preview_image); + curr_preview_image->setFilteringOption(getSnapshotType() == LLSnapshotModel::SNAPSHOT_TEXTURE ? LLTexUnit::TFO_ANISOTROPIC : LLTexUnit::TFO_POINT); + curr_preview_image->setAddressMode(LLTexUnit::TAM_CLAMP); + + + if (gSavedSettings.getBOOL("UseFreezeFrame") && mAllowFullScreenPreview) + { + mShineCountdown = 4; // wait a few frames to avoid animation glitch due to readback this frame + } + } +} + +S32 LLSnapshotLivePreview::getEncodedImageWidth() const +{ + S32 width = getWidth(); + if (getSnapshotType() == LLSnapshotModel::SNAPSHOT_TEXTURE) + { + width = LLImageRaw::biasedDimToPowerOfTwo(width,MAX_TEXTURE_SIZE); + } + return width; +} +S32 LLSnapshotLivePreview::getEncodedImageHeight() const +{ + S32 height = getHeight(); + if (getSnapshotType() == LLSnapshotModel::SNAPSHOT_TEXTURE) + { + height = LLImageRaw::biasedDimToPowerOfTwo(height,MAX_TEXTURE_SIZE); + } + return height; +} + +LLPointer LLSnapshotLivePreview::getEncodedImage() +{ + if (!mPreviewImageEncoded) + { + LLImageDataSharedLock lock(mPreviewImage); + + mPreviewImageEncoded = new LLImageRaw; + + mPreviewImageEncoded->resize( + mPreviewImage->getWidth(), + mPreviewImage->getHeight(), + mPreviewImage->getComponents()); + + if (getSnapshotType() == LLSnapshotModel::SNAPSHOT_TEXTURE) + { + // We don't store the intermediate formatted image in mFormattedImage in the J2C case + LL_DEBUGS("Snapshot") << "Encoding new image of format J2C" << LL_ENDL; + LLPointer formatted = new LLImageJ2C; + // Copy the preview + LLPointer scaled = new LLImageRaw( + mPreviewImage->getData(), + mPreviewImage->getWidth(), + mPreviewImage->getHeight(), + mPreviewImage->getComponents()); + // Scale it as required by J2C + scaled->biasedScaleToPowerOfTwo(MAX_TEXTURE_SIZE); + setImageScaled(true); + // Compress to J2C + if (formatted->encode(scaled, 0.f)) + { + // We can update the data size precisely at that point + mDataSize = formatted->getDataSize(); + // Decompress back + formatted->decode(mPreviewImageEncoded, 0); + } + } + else + { + // Update mFormattedImage if necessary + getFormattedImage(); + if (getSnapshotFormat() == LLSnapshotModel::SNAPSHOT_FORMAT_BMP) + { + // BMP hack : copy instead of decode otherwise decode will crash. + mPreviewImageEncoded->copy(mPreviewImage); + } + else + { + // Decode back + mFormattedImage->decode(mPreviewImageEncoded, 0); + } + } + } + return mPreviewImageEncoded; +} + +bool LLSnapshotLivePreview::createUploadFile(const std::string &out_filename, const S32 max_image_dimentions, const S32 min_image_dimentions) +{ + return LLViewerTextureList::createUploadFile(mPreviewImage, out_filename, max_image_dimentions, min_image_dimentions); +} + +// We actually estimate the data size so that we do not require actual compression when showing the preview +// Note : whenever formatted image is computed, mDataSize will be updated to reflect the true size +void LLSnapshotLivePreview::estimateDataSize() +{ + // Compression ratio + F32 ratio = 1.0; + + if (getSnapshotType() == LLSnapshotModel::SNAPSHOT_TEXTURE) + { + ratio = 8.0; // This is what we shoot for when compressing to J2C + } + else + { + LLSnapshotModel::ESnapshotFormat format = getSnapshotFormat(); + switch (format) + { + case LLSnapshotModel::SNAPSHOT_FORMAT_PNG: + ratio = 3.0; // Average observed PNG compression ratio + break; + case LLSnapshotModel::SNAPSHOT_FORMAT_JPEG: + // Observed from JPG compression tests + ratio = (110 - mSnapshotQuality) / 2; + break; + case LLSnapshotModel::SNAPSHOT_FORMAT_BMP: + ratio = 1.0; // No compression with BMP + break; + } + } + mDataSize = (S32)((F32)mPreviewImage->getDataSize() / ratio); +} + +LLPointer LLSnapshotLivePreview::getFormattedImage() +{ + if (!mFormattedImage) + { + // Apply the filter to mPreviewImage + if (getFilter() != "") + { + std::string filter_path = LLImageFiltersManager::getInstance()->getFilterPath(getFilter()); + if (filter_path != "") + { + LLImageFilter filter(filter_path); + filter.executeFilter(mPreviewImage); + } + else + { + LL_WARNS("Snapshot") << "Couldn't find a path to the following filter : " << getFilter() << LL_ENDL; + } + } + + // Create the new formatted image of the appropriate format. + LLSnapshotModel::ESnapshotFormat format = getSnapshotFormat(); + LL_DEBUGS("Snapshot") << "Encoding new image of format " << format << LL_ENDL; + + switch (format) + { + case LLSnapshotModel::SNAPSHOT_FORMAT_PNG: + mFormattedImage = new LLImagePNG(); + break; + case LLSnapshotModel::SNAPSHOT_FORMAT_JPEG: + mFormattedImage = new LLImageJPEG(mSnapshotQuality); + break; + case LLSnapshotModel::SNAPSHOT_FORMAT_BMP: + mFormattedImage = new LLImageBMP(); + break; + } + if (mFormattedImage->encode(mPreviewImage, 0)) + { + // We can update the data size precisely at that point + mDataSize = mFormattedImage->getDataSize(); + } + } + return mFormattedImage; +} + +void LLSnapshotLivePreview::setSize(S32 w, S32 h) +{ + LL_DEBUGS("Snapshot") << "setSize(" << w << ", " << h << ")" << LL_ENDL; + setWidth(w); + setHeight(h); +} + +void LLSnapshotLivePreview::setSnapshotFormat(LLSnapshotModel::ESnapshotFormat format) +{ + if (mSnapshotFormat != format) + { + mFormattedImage = NULL; // Invalidate the already formatted image if any + mSnapshotFormat = format; + } +} + +void LLSnapshotLivePreview::getSize(S32& w, S32& h) const +{ + w = getWidth(); + h = getHeight(); +} + +void LLSnapshotLivePreview::saveTexture(bool outfit_snapshot, std::string name) +{ + LLImageDataSharedLock lock(mPreviewImage); + + LL_DEBUGS("Snapshot") << "saving texture: " << mPreviewImage->getWidth() << "x" << mPreviewImage->getHeight() << LL_ENDL; + // gen a new uuid for this asset + LLTransactionID tid; + tid.generate(); + LLAssetID new_asset_id = tid.makeAssetID(gAgent.getSecureSessionID()); + + LLPointer formatted = new LLImageJ2C; + LLPointer scaled = new LLImageRaw(mPreviewImage->getData(), + mPreviewImage->getWidth(), + mPreviewImage->getHeight(), + mPreviewImage->getComponents()); + + // Apply the filter to mPreviewImage + if (getFilter() != "") + { + std::string filter_path = LLImageFiltersManager::getInstance()->getFilterPath(getFilter()); + if (filter_path != "") + { + LLImageFilter filter(filter_path); + filter.executeFilter(scaled); + } + else + { + LL_WARNS("Snapshot") << "Couldn't find a path to the following filter : " << getFilter() << LL_ENDL; + } + } + + scaled->biasedScaleToPowerOfTwo(MAX_TEXTURE_SIZE); + LL_DEBUGS("Snapshot") << "scaled texture to " << scaled->getWidth() << "x" << scaled->getHeight() << LL_ENDL; + + if (formatted->encode(scaled, 0.0f)) + { + LLFileSystem fmt_file(new_asset_id, LLAssetType::AT_TEXTURE, LLFileSystem::WRITE); + fmt_file.write(formatted->getData(), formatted->getDataSize()); + std::string pos_string; + LLAgentUI::buildLocationString(pos_string, LLAgentUI::LOCATION_FORMAT_FULL); + std::string who_took_it; + LLAgentUI::buildFullname(who_took_it); + S32 expected_upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost(); + std::string res_name = outfit_snapshot ? name : "Snapshot : " + pos_string; + std::string res_desc = outfit_snapshot ? "" : "Taken by " + who_took_it + " at " + pos_string; + LLFolderType::EType folder_type = outfit_snapshot ? LLFolderType::FT_NONE : LLFolderType::FT_SNAPSHOT_CATEGORY; + LLInventoryType::EType inv_type = outfit_snapshot ? LLInventoryType::IT_NONE : LLInventoryType::IT_SNAPSHOT; + + LLResourceUploadInfo::ptr_t assetUploadInfo(new LLResourceUploadInfo( + tid, LLAssetType::AT_TEXTURE, res_name, res_desc, 0, + folder_type, inv_type, + PERM_ALL, LLFloaterPerms::getGroupPerms("Uploads"), LLFloaterPerms::getEveryonePerms("Uploads"), + expected_upload_cost, !outfit_snapshot)); + + upload_new_resource(assetUploadInfo); + + gViewerWindow->playSnapshotAnimAndSound(); + } + else + { + LLNotificationsUtil::add("ErrorEncodingSnapshot"); + LL_WARNS("Snapshot") << "Error encoding snapshot" << LL_ENDL; + } + + add(LLStatViewer::SNAPSHOT, 1); + + mDataSize = 0; +} + +void LLSnapshotLivePreview::saveLocal(const snapshot_saved_signal_t::slot_type& success_cb, const snapshot_saved_signal_t::slot_type& failure_cb) +{ + // Update mFormattedImage if necessary + getFormattedImage(); + + // Save the formatted image + saveLocal(mFormattedImage, success_cb, failure_cb); +} + +//Check if failed due to insufficient memory +void LLSnapshotLivePreview::saveLocal(LLPointer image, const snapshot_saved_signal_t::slot_type& success_cb, const snapshot_saved_signal_t::slot_type& failure_cb) +{ + sSaveLocalImage = image; + + gViewerWindow->saveImageNumbered(sSaveLocalImage, false, success_cb, failure_cb); +} diff --git a/indra/newview/llsnapshotlivepreview.h b/indra/newview/llsnapshotlivepreview.h index 06bbaccd22..c41c21c120 100644 --- a/indra/newview/llsnapshotlivepreview.h +++ b/indra/newview/llsnapshotlivepreview.h @@ -1,186 +1,186 @@ -/** -* @file llsnapshotlivepreview.h -* @brief Header file for llsnapshotlivepreview -* @author Gilbert@lindenlab.com -* -* $LicenseInfo:firstyear=2013&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2013, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ -#ifndef LL_LLSNAPSHOTLIVEPREVIEW_H -#define LL_LLSNAPSHOTLIVEPREVIEW_H - -#include "llsnapshotmodel.h" -#include "llviewertexture.h" -#include "llviewerwindow.h" - -class LLImageJPEG; - -///---------------------------------------------------------------------------- -/// Class LLSnapshotLivePreview -///---------------------------------------------------------------------------- -class LLSnapshotLivePreview : public LLView -{ - LOG_CLASS(LLSnapshotLivePreview); -public: - typedef boost::signals2::signal snapshot_saved_signal_t; - - static void saveLocal(LLPointer image, const snapshot_saved_signal_t::slot_type& success_cb = snapshot_saved_signal_t(), const snapshot_saved_signal_t::slot_type& failure_cb = snapshot_saved_signal_t()); - struct Params : public LLInitParam::Block - { - Params() - { - name = "snapshot_live_preview"; - mouse_opaque = false; - } - }; - - - LLSnapshotLivePreview(const LLSnapshotLivePreview::Params& p); - ~LLSnapshotLivePreview(); - - void setContainer(LLView* container) { mViewContainer = container; } - - /*virtual*/ void draw(); - /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent); - - void setSize(S32 w, S32 h); - void setWidth(S32 w) { mWidth[mCurImageIndex] = w; } - void setHeight(S32 h) { mHeight[mCurImageIndex] = h; } - void getSize(S32& w, S32& h) const; - S32 getWidth() const { return mWidth[mCurImageIndex]; } - S32 getHeight() const { return mHeight[mCurImageIndex]; } - S32 getEncodedImageWidth() const; - S32 getEncodedImageHeight() const; - void estimateDataSize(); - S32 getDataSize() const { return mDataSize; } - void setMaxImageSize(S32 size) ; - S32 getMaxImageSize() {return mMaxImageSize ;} - - LLSnapshotModel::ESnapshotType getSnapshotType() const { return mSnapshotType; } - LLSnapshotModel::ESnapshotFormat getSnapshotFormat() const { return mSnapshotFormat; } - bool getSnapshotUpToDate() const { return mSnapshotUpToDate; } - bool isSnapshotActive() { return mSnapshotActive; } - LLViewerTexture* getThumbnailImage() const { return mThumbnailImage ; } - S32 getThumbnailWidth() const { return mThumbnailWidth ; } - S32 getThumbnailHeight() const { return mThumbnailHeight ; } - bool getThumbnailLock() const { return mThumbnailUpdateLock ; } - bool getThumbnailUpToDate() const { return mThumbnailUpToDate ;} - void setThumbnailSubsampled(bool subsampled) { mThumbnailSubsampled = subsampled; } - - LLViewerTexture* getCurrentImage(); - F32 getImageAspect(); - const LLRect& getImageRect() const { return mImageRect[mCurImageIndex]; } - bool isImageScaled() const { return mImageScaled[mCurImageIndex]; } - void setImageScaled(bool scaled) { mImageScaled[mCurImageIndex] = scaled; } - const LLVector3d& getPosTakenGlobal() const { return mPosTakenGlobal; } - - void setSnapshotType(LLSnapshotModel::ESnapshotType type) { mSnapshotType = type; } - void setSnapshotFormat(LLSnapshotModel::ESnapshotFormat format); - bool setSnapshotQuality(S32 quality, bool set_by_user = true); - void setSnapshotBufferType(LLSnapshotModel::ESnapshotLayerType type) { mSnapshotBufferType = type; } - void setAllowRenderUI(bool allow) { mAllowRenderUI = allow; } - void setAllowFullScreenPreview(bool allow) { mAllowFullScreenPreview = allow; } - void setFilter(std::string filter_name) { mFilterName = filter_name; } - std::string getFilter() const { return mFilterName; } - void updateSnapshot(bool new_snapshot, bool new_thumbnail = false, F32 delay = 0.f); - void saveTexture(bool outfit_snapshot = false, std::string name = ""); - void saveLocal(const snapshot_saved_signal_t::slot_type& success_cb, const snapshot_saved_signal_t::slot_type& failure_cb); - - LLPointer getFormattedImage(); - LLPointer getEncodedImage(); - bool createUploadFile(const std::string &out_file, const S32 max_image_dimentions, const S32 min_image_dimentions); - - /// Sets size of preview thumbnail image and the surrounding rect. - void setThumbnailPlaceholderRect(const LLRect& rect) {mThumbnailPlaceholderRect = rect; } - bool setThumbnailImageSize() ; - void generateThumbnailImage(bool force_update = false) ; - void resetThumbnailImage() { mThumbnailImage = NULL ; } - void drawPreviewRect(S32 offset_x, S32 offset_y, LLColor4 alpha_color = LLColor4(0.5f, 0.5f, 0.5f, 0.8f)); - void prepareFreezeFrame(); - - LLViewerTexture* getBigThumbnailImage(); - S32 getBigThumbnailWidth() const { return mBigThumbnailWidth ; } - S32 getBigThumbnailHeight() const { return mBigThumbnailHeight ; } - - // Returns true when snapshot generated, false otherwise. - static bool onIdle( void* snapshot_preview ); - -private: - LLView* mViewContainer; - - LLColor4 mColor; - LLPointer mViewerImage[2]; //used to represent the scene when the frame is frozen. - LLRect mImageRect[2]; - S32 mWidth[2]; - S32 mHeight[2]; - bool mImageScaled[2]; - S32 mMaxImageSize ; - - //thumbnail image - LLPointer mThumbnailImage ; - S32 mThumbnailWidth ; - S32 mThumbnailHeight ; - LLRect mPreviewRect ; - bool mThumbnailUpdateLock ; - bool mThumbnailUpToDate ; - LLRect mThumbnailPlaceholderRect; - bool mThumbnailSubsampled; // true if the thumbnail is a subsampled version of the mPreviewImage - - LLPointer mBigThumbnailImage ; - S32 mBigThumbnailWidth; - S32 mBigThumbnailHeight; - bool mBigThumbnailUpToDate; - - S32 mCurImageIndex; - // The logic is mPreviewImage (raw frame) -> mFormattedImage (formatted / filtered) -> mPreviewImageEncoded (decoded back, to show artifacts) - LLPointer mPreviewImage; - LLPointer mPreviewImageEncoded; - LLPointer mFormattedImage; - bool mAllowRenderUI; - bool mAllowFullScreenPreview; - LLFrameTimer mSnapshotDelayTimer; - S32 mShineCountdown; - LLFrameTimer mShineAnimTimer; - F32 mFlashAlpha; - bool mNeedsFlash; - LLVector3d mPosTakenGlobal; - S32 mSnapshotQuality; - S32 mDataSize; - LLSnapshotModel::ESnapshotType mSnapshotType; - LLSnapshotModel::ESnapshotFormat mSnapshotFormat; - bool mSnapshotUpToDate; - LLFrameTimer mFallAnimTimer; - LLVector3 mCameraPos; - LLQuaternion mCameraRot; - bool mSnapshotActive; - LLSnapshotModel::ESnapshotLayerType mSnapshotBufferType; - std::string mFilterName; - - static LLPointer sSaveLocalImage; - -public: - static std::set sList; - bool mKeepAspectRatio ; - bool mForceUpdateSnapshot; -}; - -#endif // LL_LLSNAPSHOTLIVEPREVIEW_H - +/** +* @file llsnapshotlivepreview.h +* @brief Header file for llsnapshotlivepreview +* @author Gilbert@lindenlab.com +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2013, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ +#ifndef LL_LLSNAPSHOTLIVEPREVIEW_H +#define LL_LLSNAPSHOTLIVEPREVIEW_H + +#include "llsnapshotmodel.h" +#include "llviewertexture.h" +#include "llviewerwindow.h" + +class LLImageJPEG; + +///---------------------------------------------------------------------------- +/// Class LLSnapshotLivePreview +///---------------------------------------------------------------------------- +class LLSnapshotLivePreview : public LLView +{ + LOG_CLASS(LLSnapshotLivePreview); +public: + typedef boost::signals2::signal snapshot_saved_signal_t; + + static void saveLocal(LLPointer image, const snapshot_saved_signal_t::slot_type& success_cb = snapshot_saved_signal_t(), const snapshot_saved_signal_t::slot_type& failure_cb = snapshot_saved_signal_t()); + struct Params : public LLInitParam::Block + { + Params() + { + name = "snapshot_live_preview"; + mouse_opaque = false; + } + }; + + + LLSnapshotLivePreview(const LLSnapshotLivePreview::Params& p); + ~LLSnapshotLivePreview(); + + void setContainer(LLView* container) { mViewContainer = container; } + + /*virtual*/ void draw(); + /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent); + + void setSize(S32 w, S32 h); + void setWidth(S32 w) { mWidth[mCurImageIndex] = w; } + void setHeight(S32 h) { mHeight[mCurImageIndex] = h; } + void getSize(S32& w, S32& h) const; + S32 getWidth() const { return mWidth[mCurImageIndex]; } + S32 getHeight() const { return mHeight[mCurImageIndex]; } + S32 getEncodedImageWidth() const; + S32 getEncodedImageHeight() const; + void estimateDataSize(); + S32 getDataSize() const { return mDataSize; } + void setMaxImageSize(S32 size) ; + S32 getMaxImageSize() {return mMaxImageSize ;} + + LLSnapshotModel::ESnapshotType getSnapshotType() const { return mSnapshotType; } + LLSnapshotModel::ESnapshotFormat getSnapshotFormat() const { return mSnapshotFormat; } + bool getSnapshotUpToDate() const { return mSnapshotUpToDate; } + bool isSnapshotActive() { return mSnapshotActive; } + LLViewerTexture* getThumbnailImage() const { return mThumbnailImage ; } + S32 getThumbnailWidth() const { return mThumbnailWidth ; } + S32 getThumbnailHeight() const { return mThumbnailHeight ; } + bool getThumbnailLock() const { return mThumbnailUpdateLock ; } + bool getThumbnailUpToDate() const { return mThumbnailUpToDate ;} + void setThumbnailSubsampled(bool subsampled) { mThumbnailSubsampled = subsampled; } + + LLViewerTexture* getCurrentImage(); + F32 getImageAspect(); + const LLRect& getImageRect() const { return mImageRect[mCurImageIndex]; } + bool isImageScaled() const { return mImageScaled[mCurImageIndex]; } + void setImageScaled(bool scaled) { mImageScaled[mCurImageIndex] = scaled; } + const LLVector3d& getPosTakenGlobal() const { return mPosTakenGlobal; } + + void setSnapshotType(LLSnapshotModel::ESnapshotType type) { mSnapshotType = type; } + void setSnapshotFormat(LLSnapshotModel::ESnapshotFormat format); + bool setSnapshotQuality(S32 quality, bool set_by_user = true); + void setSnapshotBufferType(LLSnapshotModel::ESnapshotLayerType type) { mSnapshotBufferType = type; } + void setAllowRenderUI(bool allow) { mAllowRenderUI = allow; } + void setAllowFullScreenPreview(bool allow) { mAllowFullScreenPreview = allow; } + void setFilter(std::string filter_name) { mFilterName = filter_name; } + std::string getFilter() const { return mFilterName; } + void updateSnapshot(bool new_snapshot, bool new_thumbnail = false, F32 delay = 0.f); + void saveTexture(bool outfit_snapshot = false, std::string name = ""); + void saveLocal(const snapshot_saved_signal_t::slot_type& success_cb, const snapshot_saved_signal_t::slot_type& failure_cb); + + LLPointer getFormattedImage(); + LLPointer getEncodedImage(); + bool createUploadFile(const std::string &out_file, const S32 max_image_dimentions, const S32 min_image_dimentions); + + /// Sets size of preview thumbnail image and the surrounding rect. + void setThumbnailPlaceholderRect(const LLRect& rect) {mThumbnailPlaceholderRect = rect; } + bool setThumbnailImageSize() ; + void generateThumbnailImage(bool force_update = false) ; + void resetThumbnailImage() { mThumbnailImage = NULL ; } + void drawPreviewRect(S32 offset_x, S32 offset_y, LLColor4 alpha_color = LLColor4(0.5f, 0.5f, 0.5f, 0.8f)); + void prepareFreezeFrame(); + + LLViewerTexture* getBigThumbnailImage(); + S32 getBigThumbnailWidth() const { return mBigThumbnailWidth ; } + S32 getBigThumbnailHeight() const { return mBigThumbnailHeight ; } + + // Returns true when snapshot generated, false otherwise. + static bool onIdle( void* snapshot_preview ); + +private: + LLView* mViewContainer; + + LLColor4 mColor; + LLPointer mViewerImage[2]; //used to represent the scene when the frame is frozen. + LLRect mImageRect[2]; + S32 mWidth[2]; + S32 mHeight[2]; + bool mImageScaled[2]; + S32 mMaxImageSize ; + + //thumbnail image + LLPointer mThumbnailImage ; + S32 mThumbnailWidth ; + S32 mThumbnailHeight ; + LLRect mPreviewRect ; + bool mThumbnailUpdateLock ; + bool mThumbnailUpToDate ; + LLRect mThumbnailPlaceholderRect; + bool mThumbnailSubsampled; // true if the thumbnail is a subsampled version of the mPreviewImage + + LLPointer mBigThumbnailImage ; + S32 mBigThumbnailWidth; + S32 mBigThumbnailHeight; + bool mBigThumbnailUpToDate; + + S32 mCurImageIndex; + // The logic is mPreviewImage (raw frame) -> mFormattedImage (formatted / filtered) -> mPreviewImageEncoded (decoded back, to show artifacts) + LLPointer mPreviewImage; + LLPointer mPreviewImageEncoded; + LLPointer mFormattedImage; + bool mAllowRenderUI; + bool mAllowFullScreenPreview; + LLFrameTimer mSnapshotDelayTimer; + S32 mShineCountdown; + LLFrameTimer mShineAnimTimer; + F32 mFlashAlpha; + bool mNeedsFlash; + LLVector3d mPosTakenGlobal; + S32 mSnapshotQuality; + S32 mDataSize; + LLSnapshotModel::ESnapshotType mSnapshotType; + LLSnapshotModel::ESnapshotFormat mSnapshotFormat; + bool mSnapshotUpToDate; + LLFrameTimer mFallAnimTimer; + LLVector3 mCameraPos; + LLQuaternion mCameraRot; + bool mSnapshotActive; + LLSnapshotModel::ESnapshotLayerType mSnapshotBufferType; + std::string mFilterName; + + static LLPointer sSaveLocalImage; + +public: + static std::set sList; + bool mKeepAspectRatio ; + bool mForceUpdateSnapshot; +}; + +#endif // LL_LLSNAPSHOTLIVEPREVIEW_H + diff --git a/indra/newview/llspatialpartition.cpp b/indra/newview/llspatialpartition.cpp index 38d7bcbbf5..939673b15e 100644 --- a/indra/newview/llspatialpartition.cpp +++ b/indra/newview/llspatialpartition.cpp @@ -1,4129 +1,4129 @@ -/** - * @file llspatialpartition.cpp - * @brief LLSpatialGroup class implementation and supporting functions - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llspatialpartition.h" - -#include "llappviewer.h" -#include "llcallstack.h" -#include "lltexturecache.h" -#include "lltexturefetch.h" -#include "llimageworker.h" -#include "llviewerwindow.h" -#include "llviewerobjectlist.h" -#include "llvovolume.h" -#include "llvolume.h" -#include "llvolumeoctree.h" -#include "llviewercamera.h" -#include "llface.h" -#include "llfloatertools.h" -#include "llviewercontrol.h" -#include "llviewerregion.h" -#include "llcamera.h" -#include "pipeline.h" -#include "llmeshrepository.h" -#include "llrender.h" -#include "lloctree.h" -#include "llphysicsshapebuilderutil.h" -#include "llvoavatar.h" -#include "llvolumemgr.h" -#include "llviewershadermgr.h" -#include "llcontrolavatar.h" - -extern bool gShiftFrame; - -static U32 sZombieGroups = 0; -U32 LLSpatialGroup::sNodeCount = 0; - -bool LLSpatialGroup::sNoDelete = false; - -static F32 sLastMaxTexPriority = 1.f; -static F32 sCurMaxTexPriority = 1.f; - -// enable expensive sanity checks around redundant drawable and group insertion to LLCullResult -#define LL_DEBUG_CULL_RESULT 0 - -//static counter for frame to switch LOD on - -void sg_assert(bool expr) -{ -#if LL_OCTREE_PARANOIA_CHECK - if (!expr) - { - LL_ERRS() << "Octree invalid!" << LL_ENDL; - } -#endif -} - -//returns: -// 0 if sphere and AABB are not intersecting -// 1 if they are -// 2 if AABB is entirely inside sphere - -S32 LLSphereAABB(const LLVector3& center, const LLVector3& size, const LLVector3& pos, const F32 &rad) -{ - S32 ret = 2; - - LLVector3 min = center - size; - LLVector3 max = center + size; - for (U32 i = 0; i < 3; i++) - { - if (min.mV[i] > pos.mV[i] + rad || - max.mV[i] < pos.mV[i] - rad) - { //totally outside - return 0; - } - - if (min.mV[i] < pos.mV[i] - rad || - max.mV[i] > pos.mV[i] + rad) - { //intersecting - ret = 1; - } - } - - return ret; -} - -LLSpatialGroup::~LLSpatialGroup() -{ - /*if (sNoDelete) - { - LL_ERRS() << "Illegal deletion of LLSpatialGroup!" << LL_ENDL; - }*/ - - if (gDebugGL) - { - gPipeline.checkReferences(this); - } - - if (hasState(DEAD)) - { - sZombieGroups--; - } - - sNodeCount--; - - clearDrawMap(); -} - -void LLSpatialGroup::clearDrawMap() -{ - mDrawMap.clear(); -} - -bool LLSpatialGroup::isHUDGroup() -{ - return getSpatialPartition() && getSpatialPartition()->isHUDPartition() ; -} - -void LLSpatialGroup::validate() -{ - ll_assert_aligned(this,64); -#if LL_OCTREE_PARANOIA_CHECK - - sg_assert(!isState(DIRTY)); - sg_assert(!isDead()); - - LLVector4a myMin; - myMin.setSub(mBounds[0], mBounds[1]); - LLVector4a myMax; - myMax.setAdd(mBounds[0], mBounds[1]); - - validateDrawMap(); - - for (element_iter i = getDataBegin(); i != getDataEnd(); ++i) - { - LLDrawable* drawable = *i; - sg_assert(drawable->getSpatialGroup() == this); - if (drawable->getSpatialBridge()) - { - sg_assert(drawable->getSpatialBridge() == getSpatialPartition()->asBridge()); - } - - /*if (drawable->isSpatialBridge()) - { - LLSpatialPartition* part = drawable->asPartition(); - if (!part) - { - LL_ERRS() << "Drawable reports it is a spatial bridge but not a partition." << LL_ENDL; - } - LLSpatialGroup* group = (LLSpatialGroup*) part->mOctree->getListener(0); - group->validate(); - }*/ - } - - for (U32 i = 0; i < mOctreeNode->getChildCount(); ++i) - { - LLSpatialGroup* group = (LLSpatialGroup*) mOctreeNode->getChild(i)->getListener(0); - - group->validate(); - - //ensure all children are enclosed in this node - LLVector4a center = group->mBounds[0]; - LLVector4a size = group->mBounds[1]; - - LLVector4a min; - min.setSub(center, size); - LLVector4a max; - max.setAdd(center, size); - - for (U32 j = 0; j < 3; j++) - { - sg_assert(min[j] >= myMin[j]-0.02f); - sg_assert(max[j] <= myMax[j]+0.02f); - } - } - -#endif -} - -void LLSpatialGroup::validateDrawMap() -{ -#if LL_OCTREE_PARANOIA_CHECK - for (draw_map_t::iterator i = mDrawMap.begin(); i != mDrawMap.end(); ++i) - { - LLSpatialGroup::drawmap_elem_t& draw_vec = i->second; - for (drawmap_elem_t::iterator j = draw_vec.begin(); j != draw_vec.end(); ++j) - { - LLDrawInfo& params = **j; - - params.validate(); - } - } -#endif -} - -bool LLSpatialGroup::updateInGroup(LLDrawable *drawablep, bool immediate) -{ - LL_PROFILE_ZONE_SCOPED; - drawablep->updateSpatialExtents(); - - OctreeNode* parent = mOctreeNode->getOctParent(); - - if (mOctreeNode->isInside(drawablep->getPositionGroup()) && - (mOctreeNode->contains(drawablep->getEntry()) || - (drawablep->getBinRadius() > mOctreeNode->getSize()[0] && - parent && parent->getElementCount() >= gOctreeMaxCapacity))) - { - unbound(); - setState(OBJECT_DIRTY); - //setState(GEOM_DIRTY); - return true; - } - - return false; -} - -void LLSpatialGroup::expandExtents(const LLVector4a* addingExtents, const LLXformMatrix& currentTransform) -{ - // Get coordinates of the adding extents - const LLVector4a& min = addingExtents[0]; - const LLVector4a& max = addingExtents[1]; - - // Get coordinates of all corners of the bounding box - LLVector3 corners[] = - { - LLVector3(min[0], min[1], min[2]), - LLVector3(min[0], min[1], max[2]), - LLVector3(min[0], max[1], min[2]), - LLVector3(min[0], max[1], max[2]), - LLVector3(max[0], min[1], min[2]), - LLVector3(max[0], min[1], max[2]), - LLVector3(max[0], max[1], min[2]), - LLVector3(max[0], max[1], max[2]) - }; - - // New extents (to be expanded) - LLVector3 extents[] = - { - LLVector3(mExtents[0].getF32ptr()), - LLVector3(mExtents[1].getF32ptr()) - }; - - LLQuaternion backwardRotation = ~currentTransform.getWorldRotation(); - for (LLVector3& corner : corners) - { - // Make coordinates relative to the current position - corner -= currentTransform.getWorldPosition(); - // Rotate coordinates backward to the current rotation - corner.rotVec(backwardRotation); - // Expand root extents on the current corner - for (int j = 0; j < 3; ++j) - { - if (corner[j] < extents[0][j]) - extents[0][j] = corner[j]; - if (corner[j] > extents[1][j]) - extents[1][j] = corner[j]; - } - } - - // Set new expanded extents - mExtents[0].load3(extents[0].mV); - mExtents[1].load3(extents[1].mV); - - // Calculate new center and size - mBounds[0].setAdd(mExtents[0], mExtents[1]); - mBounds[0].mul(0.5f); - mBounds[1].setSub(mExtents[0], mExtents[1]); - mBounds[1].mul(0.5f); -} - -bool LLSpatialGroup::addObject(LLDrawable *drawablep) -{ - if(!drawablep) - { - return false; - } - { - drawablep->setGroup(this); - setState(OBJECT_DIRTY | GEOM_DIRTY); - setOcclusionState(LLSpatialGroup::DISCARD_QUERY, LLSpatialGroup::STATE_MODE_ALL_CAMERAS); - gPipeline.markRebuild(this); - if (drawablep->isSpatialBridge()) - { - mBridgeList.push_back((LLSpatialBridge*) drawablep); - } - if (drawablep->getRadius() > 1.f) - { - setState(IMAGE_DIRTY); - } - } - - return true; -} - -void LLSpatialGroup::rebuildGeom() -{ - if (!isDead()) - { - getSpatialPartition()->rebuildGeom(this); - - if (hasState(LLSpatialGroup::MESH_DIRTY)) - { - gPipeline.markMeshDirty(this); - } - } -} - -void LLSpatialGroup::rebuildMesh() -{ - if (!isDead()) - { - getSpatialPartition()->rebuildMesh(this); - } -} - -void LLSpatialPartition::rebuildGeom(LLSpatialGroup* group) -{ - if (group->isDead() || !group->hasState(LLSpatialGroup::GEOM_DIRTY)) - { - return; - } - - if (group->changeLOD()) - { - group->mLastUpdateDistance = group->mDistance; - group->mLastUpdateViewAngle = group->mViewAngle; - } - - LL_PROFILE_ZONE_SCOPED_CATEGORY_SPATIAL; - - group->clearDrawMap(); - - //get geometry count - U32 index_count = 0; - U32 vertex_count = 0; - - addGeometryCount(group, vertex_count, index_count); - - if (vertex_count > 0 && index_count > 0) - { //create vertex buffer containing volume geometry for this node - { - group->mBuilt = 1.f; - if (group->mVertexBuffer.isNull() || - group->mVertexBuffer->getNumVerts() != vertex_count || - group->mVertexBuffer->getNumVerts() != index_count) - { - group->mVertexBuffer = new LLVertexBuffer(mVertexDataMask); - if (!group->mVertexBuffer->allocateBuffer(vertex_count, index_count)) - { - LL_WARNS() << "Failed to allocate Vertex Buffer on rebuild to " - << vertex_count << " vertices and " - << index_count << " indices" << LL_ENDL; - group->mVertexBuffer = NULL; - group->mBufferMap.clear(); - } - } - } - - if (group->mVertexBuffer) - { - getGeometry(group); - } - } - else - { - group->mVertexBuffer = NULL; - group->mBufferMap.clear(); - } - - group->mLastUpdateTime = gFrameTimeSeconds; - group->clearState(LLSpatialGroup::GEOM_DIRTY); -} - - -void LLSpatialPartition::rebuildMesh(LLSpatialGroup* group) -{ - -} - -LLSpatialGroup* LLSpatialGroup::getParent() -{ - return (LLSpatialGroup*)LLViewerOctreeGroup::getParent(); - } - -bool LLSpatialGroup::removeObject(LLDrawable *drawablep, bool from_octree) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_SPATIAL - - if(!drawablep) - { - return false; - } - - unbound(); - if (mOctreeNode && !from_octree) - { - drawablep->setGroup(NULL); - } - else - { - drawablep->setGroup(NULL); - setState(GEOM_DIRTY); - gPipeline.markRebuild(this); - - if (drawablep->isSpatialBridge()) - { - for (bridge_list_t::iterator i = mBridgeList.begin(); i != mBridgeList.end(); ++i) - { - if (*i == drawablep) - { - mBridgeList.erase(i); - break; - } - } - } - - if (getElementCount() == 0) - { //delete draw map on last element removal since a rebuild might never happen - clearDrawMap(); - } - } - return true; -} - -void LLSpatialGroup::shift(const LLVector4a &offset) -{ - LLVector4a t = mOctreeNode->getCenter(); - t.add(offset); - mOctreeNode->setCenter(t); - mOctreeNode->updateMinMax(); - mBounds[0].add(offset); - mExtents[0].add(offset); - mExtents[1].add(offset); - mObjectBounds[0].add(offset); - mObjectExtents[0].add(offset); - mObjectExtents[1].add(offset); - - if (!getSpatialPartition()->mRenderByGroup && - getSpatialPartition()->mPartitionType != LLViewerRegion::PARTITION_TREE && - getSpatialPartition()->mPartitionType != LLViewerRegion::PARTITION_TERRAIN && - getSpatialPartition()->mPartitionType != LLViewerRegion::PARTITION_BRIDGE && - getSpatialPartition()->mPartitionType != LLViewerRegion::PARTITION_AVATAR && - getSpatialPartition()->mPartitionType != LLViewerRegion::PARTITION_CONTROL_AV) - { - setState(GEOM_DIRTY); - gPipeline.markRebuild(this); - } -} - -class LLSpatialSetState : public OctreeTraveler -{ -public: - U32 mState; - LLSpatialSetState(U32 state) : mState(state) { } - virtual void visit(const OctreeNode* branch) { ((LLSpatialGroup*) branch->getListener(0))->setState(mState); } -}; - -class LLSpatialSetStateDiff : public LLSpatialSetState -{ -public: - LLSpatialSetStateDiff(U32 state) : LLSpatialSetState(state) { } - - virtual void traverse(const OctreeNode* n) - { - LLSpatialGroup* group = (LLSpatialGroup*) n->getListener(0); - - if (!group->hasState(mState)) - { - OctreeTraveler::traverse(n); - } - } -}; - -void LLSpatialGroup::setState(U32 state, S32 mode) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_SPATIAL - - llassert(state <= LLSpatialGroup::STATE_MASK); - - if (mode > STATE_MODE_SINGLE) - { - if (mode == STATE_MODE_DIFF) - { - LLSpatialSetStateDiff setter(state); - setter.traverse(mOctreeNode); - } - else - { - LLSpatialSetState setter(state); - setter.traverse(mOctreeNode); - } - } - else - { - mState |= state; - } -} - -class LLSpatialClearState : public OctreeTraveler -{ -public: - U32 mState; - LLSpatialClearState(U32 state) : mState(state) { } - virtual void visit(const OctreeNode* branch) { ((LLSpatialGroup*) branch->getListener(0))->clearState(mState); } -}; - -class LLSpatialClearStateDiff : public LLSpatialClearState -{ -public: - LLSpatialClearStateDiff(U32 state) : LLSpatialClearState(state) { } - - virtual void traverse(const OctreeNode* n) - { - LLSpatialGroup* group = (LLSpatialGroup*) n->getListener(0); - - if (group->hasState(mState)) - { - OctreeTraveler::traverse(n); - } - } -}; - -void LLSpatialGroup::clearState(U32 state, S32 mode) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_SPATIAL - - llassert(state <= LLSpatialGroup::STATE_MASK); - - if (mode > STATE_MODE_SINGLE) - { - if (mode == STATE_MODE_DIFF) - { - LLSpatialClearStateDiff clearer(state); - clearer.traverse(mOctreeNode); - } - else - { - LLSpatialClearState clearer(state); - clearer.traverse(mOctreeNode); - } - } - else - { - mState &= ~state; - } -} - -//====================================== -// Octree Listener Implementation -//====================================== - -LLSpatialGroup::LLSpatialGroup(OctreeNode* node, LLSpatialPartition* part) : LLOcclusionCullingGroup(node, part), - mObjectBoxSize(1.f), - mGeometryBytes(0), - mSurfaceArea(0.f), - mBuilt(0.f), - mVertexBuffer(NULL), - mDistance(0.f), - mDepth(0.f), - mLastUpdateDistance(-1.f), - mLastUpdateTime(gFrameTimeSeconds) -{ - ll_assert_aligned(this,16); - - sNodeCount++; - - mViewAngle.splat(0.f); - mLastUpdateViewAngle.splat(-1.f); - - sg_assert(mOctreeNode->getListenerCount() == 0); - setState(SG_INITIAL_STATE_MASK); - gPipeline.markRebuild(this); - - // let the reflection map manager know about this spatial group - mReflectionProbe = gPipeline.mReflectionMapManager.registerSpatialGroup(this); - - mRadius = 1; - mPixelArea = 1024.f; -} - -void LLSpatialGroup::updateDistance(LLCamera &camera) -{ - if (LLViewerCamera::sCurCameraID != LLViewerCamera::CAMERA_WORLD) - { - LL_WARNS() << "Attempted to update distance for camera other than world camera!" << LL_ENDL; - llassert(false); - return; - } - - if (gShiftFrame) - { - return; - } - -#if !LL_RELEASE_FOR_DOWNLOAD - if (hasState(LLSpatialGroup::OBJECT_DIRTY)) - { - LL_ERRS() << "Spatial group dirty on distance update." << LL_ENDL; - } -#endif - if (!isEmpty()) - { - mRadius = getSpatialPartition()->mRenderByGroup ? mObjectBounds[1].getLength3().getF32() : - (F32) mOctreeNode->getSize().getLength3().getF32(); - mDistance = getSpatialPartition()->calcDistance(this, camera); - mPixelArea = getSpatialPartition()->calcPixelArea(this, camera); - } -} - -F32 LLSpatialPartition::calcDistance(LLSpatialGroup* group, LLCamera& camera) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_SPATIAL - - LLVector4a eye; - LLVector4a origin; - origin.load3(camera.getOrigin().mV); - - eye.setSub(group->mObjectBounds[0], origin); - - F32 dist = 0.f; - - if (group->mDrawMap.find(LLRenderPass::PASS_ALPHA) != group->mDrawMap.end()) - { - LLVector4a v = eye; - - dist = eye.getLength3().getF32(); - eye.normalize3fast(); - - if (!group->hasState(LLSpatialGroup::ALPHA_DIRTY)) - { - if (!group->getSpatialPartition()->isBridge()) - { - LLVector4a view_angle = eye; - - LLVector4a diff; - diff.setSub(view_angle, group->mLastUpdateViewAngle); - - if (diff.getLength3().getF32() > 0.64f) - { - group->mViewAngle = view_angle; - group->mLastUpdateViewAngle = view_angle; - //for occasional alpha sorting within the group - //NOTE: If there is a trivial way to detect that alpha sorting here would not change the render order, - //not setting this node to dirty would be a very good thing - group->setState(LLSpatialGroup::ALPHA_DIRTY); - gPipeline.markRebuild(group); - } - } - } - - //calculate depth of node for alpha sorting - - LLVector3 at = camera.getAtAxis(); - - LLVector4a ata; - ata.load3(at.mV); - - LLVector4a t = ata; - //front of bounding box - t.mul(0.25f); - t.mul(group->mObjectBounds[1]); - v.sub(t); - - group->mDepth = v.dot3(ata).getF32(); - } - else - { - dist = eye.getLength3().getF32(); - } - -#if !LL_RELEASE - LL_DEBUGS("RiggedBox") << "calcDistance, group " << group << " camera " << origin << " obj bounds " - << group->mObjectBounds[0] << ", " << group->mObjectBounds[1] - << " dist " << dist << " radius " << group->mRadius << LL_ENDL; -#endif - - if (dist < 16.f) - { - dist /= 16.f; - dist *= dist; - dist *= 16.f; - } - - return dist; -} - -F32 LLSpatialPartition::calcPixelArea(LLSpatialGroup* group, LLCamera& camera) -{ - return LLPipeline::calcPixelArea(group->mObjectBounds[0], group->mObjectBounds[1], camera); -} - -F32 LLSpatialGroup::getUpdateUrgency() const -{ - if (!isVisible()) - { - return 0.f; - } - else - { - F32 time = gFrameTimeSeconds-mLastUpdateTime+4.f; - return time + (mObjectBounds[1].dot3(mObjectBounds[1]).getF32()+1.f)/mDistance; - } -} - -bool LLSpatialGroup::changeLOD() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_SPATIAL - - if (hasState(ALPHA_DIRTY | OBJECT_DIRTY)) - { - //a rebuild is going to happen, update distance and LoD - return true; - } - - if (getSpatialPartition()->mSlopRatio > 0.f) - { - F32 ratio = (mDistance - mLastUpdateDistance)/(llmax(mLastUpdateDistance, mRadius)); - - // MAINT-8264 - this check is not robust if it needs to work - // for bounding boxes much larger than the actual enclosed - // objects, and using distance to box center is also - // problematic. Consider the case that you have a large box - // where the enclosed object is in one corner. As you zoom in - // on the corner, the object gets much closer to the camera, - // but the distance to the box center changes very little, and - // an LOD change will not trigger, so object LOD gets "stuck" - // at a too-low value. In the case of the above JIRA, the box - // was large only due to another error, so this logic did not - // need to be changed. - - if (fabsf(ratio) >= getSpatialPartition()->mSlopRatio) - { - LL_DEBUGS("RiggedBox") << "changeLOD true because of ratio compare " - << fabsf(ratio) << " " << getSpatialPartition()->mSlopRatio << LL_ENDL; - LL_DEBUGS("RiggedBox") << "sg " << this << "\nmDistance " << mDistance - << " mLastUpdateDistance " << mLastUpdateDistance - << " mRadius " << mRadius - << " fab ratio " << fabsf(ratio) - << " slop " << getSpatialPartition()->mSlopRatio << LL_ENDL; - - return true; - } - } - - if (needsUpdate()) - { - return true; - } - - return false; -} - -void LLSpatialGroup::handleInsertion(const TreeNode* node, LLViewerOctreeEntry* entry) -{ - addObject((LLDrawable*)entry->getDrawable()); - unbound(); - setState(OBJECT_DIRTY); -} - -void LLSpatialGroup::handleRemoval(const TreeNode* node, LLViewerOctreeEntry* entry) -{ - removeObject((LLDrawable*)entry->getDrawable(), true); - LLViewerOctreeGroup::handleRemoval(node, entry); -} - -void LLSpatialGroup::handleDestruction(const TreeNode* node) -{ - if(isDead()) - { - return; - } - setState(DEAD); - - for (element_iter i = getDataBegin(); i != getDataEnd(); ++i) - { - LLViewerOctreeEntry* entry = *i; - - if (entry->getGroup() == this) - { - if(entry->hasDrawable()) - { - ((LLDrawable*)entry->getDrawable())->setGroup(NULL); - } - } - } - - clearDrawMap(); - mVertexBuffer = NULL; - mBufferMap.clear(); - sZombieGroups++; - mOctreeNode = NULL; -} - -void LLSpatialGroup::handleChildAddition(const OctreeNode* parent, OctreeNode* child) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_SPATIAL - - if (child->getListenerCount() == 0) - { - new LLSpatialGroup(child, getSpatialPartition()); - } - else - { - OCT_ERRS << "LLSpatialGroup redundancy detected." << LL_ENDL; - } - - unbound(); - - assert_states_valid(this); -} - -//virtual -void LLSpatialGroup::rebound() -{ - if (!isDirty()) - return; - - super::rebound(); - - if (mSpatialPartition->mDrawableType == LLPipeline::RENDER_TYPE_CONTROL_AV) - { - llassert(mSpatialPartition->mPartitionType == LLViewerRegion::PARTITION_CONTROL_AV); - - LLSpatialBridge* bridge = getSpatialPartition()->asBridge(); - if (bridge && - bridge->mDrawable && - bridge->mDrawable->getVObj() && - bridge->mDrawable->getVObj()->isRoot()) - { - LLControlAvatar* controlAvatar = bridge->mDrawable->getVObj()->getControlAvatar(); - if (controlAvatar && - controlAvatar->mDrawable && - controlAvatar->mControlAVBridge && - controlAvatar->mControlAVBridge->mOctree) - { - LLSpatialGroup* root = (LLSpatialGroup*)controlAvatar->mControlAVBridge->mOctree->getListener(0); - if (this == root) - { - const LLVector4a* addingExtents = controlAvatar->mDrawable->getSpatialExtents(); - const LLXformMatrix* currentTransform = bridge->mDrawable->getXform(); - expandExtents(addingExtents, *currentTransform); - } - } - } - } -} - -void LLSpatialGroup::destroyGLState(bool keep_occlusion) -{ - setState(LLSpatialGroup::GEOM_DIRTY | LLSpatialGroup::IMAGE_DIRTY); - - if (!keep_occlusion) - { //going to need a rebuild - gPipeline.markRebuild(this); - } - - mLastUpdateTime = gFrameTimeSeconds; - mVertexBuffer = NULL; - mBufferMap.clear(); - - clearDrawMap(); - - if (!keep_occlusion) - { - releaseOcclusionQueryObjectNames(); - } - - - for (LLSpatialGroup::element_iter i = getDataBegin(); i != getDataEnd(); ++i) - { - LLDrawable* drawable = (LLDrawable*)(*i)->getDrawable(); - if(!drawable) - { - continue; - } - for (S32 j = 0; j < drawable->getNumFaces(); j++) - { - LLFace* facep = drawable->getFace(j); - if (facep) - { - facep->clearVertexBuffer(); - } - } - } -} - -//============================================== - -LLSpatialPartition::LLSpatialPartition(U32 data_mask, bool render_by_group, LLViewerRegion* regionp) -: mRenderByGroup(render_by_group), mBridge(NULL) -{ - mRegionp = regionp; - mPartitionType = LLViewerRegion::PARTITION_NONE; - mVertexDataMask = data_mask; - mDepthMask = false; - mSlopRatio = 0.25f; - mInfiniteFarClip = false; - - new LLSpatialGroup(mOctree, this); -} - - -LLSpatialPartition::~LLSpatialPartition() -{ - cleanup(); -} - -LLSpatialGroup *LLSpatialPartition::put(LLDrawable *drawablep, bool was_visible) -{ - LL_PROFILE_ZONE_SCOPED; - drawablep->updateSpatialExtents(); - - //keep drawable from being garbage collected - LLPointer ptr = drawablep; - - if(!drawablep->getGroup()) - { - assert_octree_valid(mOctree); - mOctree->insert(drawablep->getEntry()); - assert_octree_valid(mOctree); - } - - LLSpatialGroup* group = drawablep->getSpatialGroup(); - //llassert(group != NULL); - - if (group && was_visible && group->isOcclusionState(LLSpatialGroup::QUERY_PENDING)) - { - group->setOcclusionState(LLSpatialGroup::DISCARD_QUERY, LLSpatialGroup::STATE_MODE_ALL_CAMERAS); - } - - return group; -} - -bool LLSpatialPartition::remove(LLDrawable *drawablep, LLSpatialGroup *curp) -{ - LL_PROFILE_ZONE_SCOPED; - if (!curp->removeObject(drawablep)) - { - OCT_ERRS << "Failed to remove drawable from octree!" << LL_ENDL; - } - else - { - drawablep->setGroup(NULL); - } - - assert_octree_valid(mOctree); - - return true; -} - -void LLSpatialPartition::move(LLDrawable *drawablep, LLSpatialGroup *curp, bool immediate) -{ - LL_PROFILE_ZONE_SCOPED; - // sanity check submitted by open source user bushing Spatula - // who was seeing crashing here. (See VWR-424 reported by Bunny Mayne) - if (!drawablep) - { - OCT_ERRS << "LLSpatialPartition::move was passed a bad drawable." << LL_ENDL; - return; - } - - bool was_visible = curp ? curp->isVisible() : false; - - if (curp && curp->getSpatialPartition() != this) - { - //keep drawable from being garbage collected - LLPointer ptr = drawablep; - if (curp->getSpatialPartition()->remove(drawablep, curp)) - { - put(drawablep, was_visible); - return; - } - else - { - OCT_ERRS << "Drawable lost between spatial partitions on outbound transition." << LL_ENDL; - } - } - - if (curp && curp->updateInGroup(drawablep, immediate)) - { - // Already updated, don't need to do anything - assert_octree_valid(mOctree); - return; - } - - //keep drawable from being garbage collected - LLPointer ptr = drawablep; - if (curp && !remove(drawablep, curp)) - { - OCT_ERRS << "Move couldn't find existing spatial group!" << LL_ENDL; - } - - put(drawablep, was_visible); -} - -class LLSpatialShift : public OctreeTraveler -{ -public: - const LLVector4a& mOffset; - - LLSpatialShift(const LLVector4a& offset) : mOffset(offset) { } - virtual void visit(const OctreeNode* branch) - { - ((LLSpatialGroup*) branch->getListener(0))->shift(mOffset); - } -}; - -void LLSpatialPartition::shift(const LLVector4a &offset) -{ //shift octree node bounding boxes by offset - LLSpatialShift shifter(offset); - shifter.traverse(mOctree); -} - -class LLOctreeCull : public LLViewerOctreeCull -{ -public: - LLOctreeCull(LLCamera* camera) : LLViewerOctreeCull(camera) {} - - virtual bool earlyFail(LLViewerOctreeGroup* base_group) - { - if (LLPipeline::sReflectionRender) - { - return false; - } - - LLSpatialGroup* group = (LLSpatialGroup*)base_group; - group->checkOcclusion(); - - if (group->getOctreeNode()->getParent() && //never occlusion cull the root node - LLPipeline::sUseOcclusion && //ignore occlusion if disabled - group->isOcclusionState(LLSpatialGroup::OCCLUDED)) - { - gPipeline.markOccluder(group); - return true; - } - - return false; - } - - virtual S32 frustumCheck(const LLViewerOctreeGroup* group) - { - S32 res = AABBInFrustumNoFarClipGroupBounds(group); - if (res != 0) - { - res = llmin(res, AABBSphereIntersectGroupExtents(group)); - } - return res; - } - - virtual S32 frustumCheckObjects(const LLViewerOctreeGroup* group) - { - S32 res = AABBInFrustumNoFarClipObjectBounds(group); - if (res != 0) - { - res = llmin(res, AABBSphereIntersectObjectExtents(group)); - } - return res; - } - - virtual void processGroup(LLViewerOctreeGroup* base_group) - { - LLSpatialGroup* group = (LLSpatialGroup*)base_group; - /*if (group->needsUpdate() || - group->getVisible(LLViewerCamera::sCurCameraID) < LLDrawable::getCurrentFrame() - 1) - { - group->doOcclusion(mCamera); - }*/ - gPipeline.markNotCulled(group, *mCamera); - } -}; - -class LLOctreeCullNoFarClip : public LLOctreeCull -{ -public: - LLOctreeCullNoFarClip(LLCamera* camera) - : LLOctreeCull(camera) { } - - virtual S32 frustumCheck(const LLViewerOctreeGroup* group) - { - return AABBInFrustumNoFarClipGroupBounds(group); - } - - virtual S32 frustumCheckObjects(const LLViewerOctreeGroup* group) - { - S32 res = AABBInFrustumNoFarClipObjectBounds(group); - return res; - } -}; - -class LLOctreeCullShadow : public LLOctreeCull -{ -public: - LLOctreeCullShadow(LLCamera* camera) - : LLOctreeCull(camera) { } - - virtual S32 frustumCheck(const LLViewerOctreeGroup* group) - { - return AABBInFrustumGroupBounds(group); - } - - virtual S32 frustumCheckObjects(const LLViewerOctreeGroup* group) - { - return AABBInFrustumObjectBounds(group); - } -}; - -class LLOctreeCullVisExtents: public LLOctreeCullShadow -{ -public: - LLOctreeCullVisExtents(LLCamera* camera, LLVector4a& min, LLVector4a& max) - : LLOctreeCullShadow(camera), mMin(min), mMax(max), mEmpty(true) { } - - virtual bool earlyFail(LLViewerOctreeGroup* base_group) - { - LLSpatialGroup* group = (LLSpatialGroup*)base_group; - - if (group->getOctreeNode()->getParent() && //never occlusion cull the root node - LLPipeline::sUseOcclusion && //ignore occlusion if disabled - group->isOcclusionState(LLSpatialGroup::OCCLUDED)) - { - return true; - } - - return false; - } - - virtual void traverse(const OctreeNode* n) - { - LLSpatialGroup* group = (LLSpatialGroup*) n->getListener(0); - - if (earlyFail(group)) - { - return; - } - - if ((mRes && group->hasState(LLSpatialGroup::SKIP_FRUSTUM_CHECK)) || - mRes == 2) - { //don't need to do frustum check - OctreeTraveler::traverse(n); - } - else - { - mRes = frustumCheck(group); - - if (mRes) - { //at least partially in, run on down - OctreeTraveler::traverse(n); - } - - mRes = 0; - } - } - - virtual void processGroup(LLViewerOctreeGroup* base_group) - { - LLSpatialGroup* group = (LLSpatialGroup*)base_group; - - llassert(!group->hasState(LLSpatialGroup::DIRTY) && !group->isEmpty()); - - if (mRes < 2) - { - if (AABBInFrustumObjectBounds(group) > 0) - { - mEmpty = false; - const LLVector4a* exts = group->getObjectExtents(); - update_min_max(mMin, mMax, exts[0]); - update_min_max(mMin, mMax, exts[1]); - } - } - else - { - mEmpty = false; - const LLVector4a* exts = group->getExtents(); - update_min_max(mMin, mMax, exts[0]); - update_min_max(mMin, mMax, exts[1]); - } - } - - bool mEmpty; - LLVector4a& mMin; - LLVector4a& mMax; -}; - -class LLOctreeCullDetectVisible: public LLOctreeCullShadow -{ -public: - LLOctreeCullDetectVisible(LLCamera* camera) - : LLOctreeCullShadow(camera), mResult(false) { } - - virtual bool earlyFail(LLViewerOctreeGroup* base_group) - { - LLSpatialGroup* group = (LLSpatialGroup*)base_group; - - if (mResult || //already found a node, don't check any more - (group->getOctreeNode()->getParent() && //never occlusion cull the root node - LLPipeline::sUseOcclusion && //ignore occlusion if disabled - group->isOcclusionState(LLSpatialGroup::OCCLUDED))) - { - return true; - } - - return false; - } - - virtual void processGroup(LLViewerOctreeGroup* base_group) - { - if (base_group->isVisible()) - { - mResult = true; - } - } - - bool mResult; -}; - -class LLOctreeSelect : public LLOctreeCull -{ -public: - LLOctreeSelect(LLCamera* camera, std::vector* results) - : LLOctreeCull(camera), mResults(results) { } - - virtual bool earlyFail(LLViewerOctreeGroup* group) { return false; } - virtual void preprocess(LLViewerOctreeGroup* group) { } - - virtual void processGroup(LLViewerOctreeGroup* base_group) - { - LLSpatialGroup* group = (LLSpatialGroup*)base_group; - OctreeNode* branch = group->getOctreeNode(); - - for (OctreeNode::const_element_iter i = branch->getDataBegin(); i != branch->getDataEnd(); ++i) - { - LLDrawable* drawable = (LLDrawable*)(*i)->getDrawable(); - if(!drawable) - { - continue; - } - if (!drawable->isDead()) - { - if (drawable->isSpatialBridge()) - { - drawable->setVisible(*mCamera, mResults, true); - } - else - { - mResults->push_back(drawable); - } - } - } - } - - std::vector* mResults; -}; - -void drawBox(const LLVector3& c, const LLVector3& r) -{ - LLVertexBuffer::unbind(); - - gGL.begin(LLRender::TRIANGLE_STRIP); - //left front - gGL.vertex3fv((c+r.scaledVec(LLVector3(-1,1,-1))).mV); - gGL.vertex3fv((c+r.scaledVec(LLVector3(-1,1,1))).mV); - //right front - gGL.vertex3fv((c+r.scaledVec(LLVector3(1,1,-1))).mV); - gGL.vertex3fv((c+r.scaledVec(LLVector3(1,1,1))).mV); - //right back - gGL.vertex3fv((c+r.scaledVec(LLVector3(1,-1,-1))).mV); - gGL.vertex3fv((c+r.scaledVec(LLVector3(1,-1,1))).mV); - //left back - gGL.vertex3fv((c+r.scaledVec(LLVector3(-1,-1,-1))).mV); - gGL.vertex3fv((c+r.scaledVec(LLVector3(-1,-1,1))).mV); - //left front - gGL.vertex3fv((c+r.scaledVec(LLVector3(-1,1,-1))).mV); - gGL.vertex3fv((c+r.scaledVec(LLVector3(-1,1,1))).mV); - gGL.end(); - - //bottom - gGL.begin(LLRender::TRIANGLE_STRIP); - gGL.vertex3fv((c+r.scaledVec(LLVector3(1,1,-1))).mV); - gGL.vertex3fv((c+r.scaledVec(LLVector3(1,-1,-1))).mV); - gGL.vertex3fv((c+r.scaledVec(LLVector3(-1,1,-1))).mV); - gGL.vertex3fv((c+r.scaledVec(LLVector3(-1,-1,-1))).mV); - gGL.end(); - - //top - gGL.begin(LLRender::TRIANGLE_STRIP); - gGL.vertex3fv((c+r.scaledVec(LLVector3(1,1,1))).mV); - gGL.vertex3fv((c+r.scaledVec(LLVector3(-1,1,1))).mV); - gGL.vertex3fv((c+r.scaledVec(LLVector3(1,-1,1))).mV); - gGL.vertex3fv((c+r.scaledVec(LLVector3(-1,-1,1))).mV); - gGL.end(); -} - -void drawBox(const LLVector4a& c, const LLVector4a& r) -{ - drawBox(reinterpret_cast(c), reinterpret_cast(r)); -} - -void drawBoxOutline(const LLVector3& pos, const LLVector3& size) -{ - if (!pos.isFinite() || !size.isFinite()) - return; - - LLVector3 v1 = size.scaledVec(LLVector3( 1, 1,1)); - LLVector3 v2 = size.scaledVec(LLVector3(-1, 1,1)); - LLVector3 v3 = size.scaledVec(LLVector3(-1,-1,1)); - LLVector3 v4 = size.scaledVec(LLVector3( 1,-1,1)); - - gGL.begin(LLRender::LINES); - - //top - gGL.vertex3fv((pos+v1).mV); - gGL.vertex3fv((pos+v2).mV); - gGL.vertex3fv((pos+v2).mV); - gGL.vertex3fv((pos+v3).mV); - gGL.vertex3fv((pos+v3).mV); - gGL.vertex3fv((pos+v4).mV); - gGL.vertex3fv((pos+v4).mV); - gGL.vertex3fv((pos+v1).mV); - - //bottom - gGL.vertex3fv((pos-v1).mV); - gGL.vertex3fv((pos-v2).mV); - gGL.vertex3fv((pos-v2).mV); - gGL.vertex3fv((pos-v3).mV); - gGL.vertex3fv((pos-v3).mV); - gGL.vertex3fv((pos-v4).mV); - gGL.vertex3fv((pos-v4).mV); - gGL.vertex3fv((pos-v1).mV); - - //right - gGL.vertex3fv((pos+v1).mV); - gGL.vertex3fv((pos-v3).mV); - - gGL.vertex3fv((pos+v4).mV); - gGL.vertex3fv((pos-v2).mV); - - //left - gGL.vertex3fv((pos+v2).mV); - gGL.vertex3fv((pos-v4).mV); - - gGL.vertex3fv((pos+v3).mV); - gGL.vertex3fv((pos-v1).mV); - - gGL.end(); -} - -void drawBoxOutline(const LLVector4a& pos, const LLVector4a& size) -{ - drawBoxOutline(reinterpret_cast(pos), reinterpret_cast(size)); -} - - -void LLSpatialPartition::restoreGL() -{ -} - -bool LLSpatialPartition::getVisibleExtents(LLCamera& camera, LLVector3& visMin, LLVector3& visMax) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_SPATIAL; - LLVector4a visMina, visMaxa; - visMina.load3(visMin.mV); - visMaxa.load3(visMax.mV); - - { - LLSpatialGroup* group = (LLSpatialGroup*) mOctree->getListener(0); - group->rebound(); - } - - LLOctreeCullVisExtents vis(&camera, visMina, visMaxa); - vis.traverse(mOctree); - - visMin.set(visMina.getF32ptr()); - visMax.set(visMaxa.getF32ptr()); - return vis.mEmpty; -} - -bool LLSpatialPartition::visibleObjectsInFrustum(LLCamera& camera) -{ - LLOctreeCullDetectVisible vis(&camera); - vis.traverse(mOctree); - return vis.mResult; -} - -S32 LLSpatialPartition::cull(LLCamera &camera, std::vector* results, bool for_select) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_SPATIAL; -#if LL_OCTREE_PARANOIA_CHECK - ((LLSpatialGroup*)mOctree->getListener(0))->checkStates(); -#endif - { - LLSpatialGroup* group = (LLSpatialGroup*) mOctree->getListener(0); - group->rebound(); - } - -#if LL_OCTREE_PARANOIA_CHECK - ((LLSpatialGroup*)mOctree->getListener(0))->validate(); -#endif - - LLOctreeSelect selecter(&camera, results); - selecter.traverse(mOctree); - - return 0; -} - -extern bool gCubeSnapshot; - -S32 LLSpatialPartition::cull(LLCamera &camera, bool do_occlusion) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_SPATIAL; -#if LL_OCTREE_PARANOIA_CHECK - ((LLSpatialGroup*)mOctree->getListener(0))->checkStates(); -#endif - LLSpatialGroup* group = (LLSpatialGroup*) mOctree->getListener(0); - group->rebound(); - -#if LL_OCTREE_PARANOIA_CHECK - ((LLSpatialGroup*)mOctree->getListener(0))->validate(); -#endif - - if (LLPipeline::sShadowRender) - { - LLOctreeCullShadow culler(&camera); - culler.traverse(mOctree); - } - else if (mInfiniteFarClip || (!LLPipeline::sUseFarClip && !gCubeSnapshot)) - { - LLOctreeCullNoFarClip culler(&camera); - culler.traverse(mOctree); - } - else - { - LLOctreeCull culler(&camera); - culler.traverse(mOctree); - } - - return 0; -} - -void pushVerts(LLDrawInfo* params) -{ - LLRenderPass::applyModelMatrix(*params); - params->mVertexBuffer->setBuffer(); - params->mVertexBuffer->drawRange(LLRender::TRIANGLES, - params->mStart, params->mEnd, params->mCount, params->mOffset); -} - -void pushVerts(LLSpatialGroup* group) -{ - LLDrawInfo* params = NULL; - - for (LLSpatialGroup::draw_map_t::iterator i = group->mDrawMap.begin(); i != group->mDrawMap.end(); ++i) - { - for (LLSpatialGroup::drawmap_elem_t::iterator j = i->second.begin(); j != i->second.end(); ++j) - { - params = *j; - pushVerts(params); - } - } -} - -void pushVerts(LLFace* face) -{ - if (face) - { - llassert(face->verify()); - face->renderIndexed(); - } -} - -void pushVerts(LLDrawable* drawable) -{ - for (S32 i = 0; i < drawable->getNumFaces(); ++i) - { - pushVerts(drawable->getFace(i)); - } -} - -void pushVerts(LLVolume* volume) -{ - LLVertexBuffer::unbind(); - for (S32 i = 0; i < volume->getNumVolumeFaces(); ++i) - { - const LLVolumeFace& face = volume->getVolumeFace(i); - LLVertexBuffer::drawElements(LLRender::TRIANGLES, face.mPositions, NULL, face.mNumIndices, face.mIndices); - } -} - -void pushBufferVerts(LLVertexBuffer* buffer) -{ - if (buffer) - { - buffer->setBuffer(); - buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts()-1, buffer->getNumIndices(), 0); - } -} - -void pushBufferVerts(LLSpatialGroup* group, bool push_alpha = true) -{ - if (group->getSpatialPartition()->mRenderByGroup) - { - if (!group->mDrawMap.empty()) - { - LLDrawInfo* params = *(group->mDrawMap.begin()->second.begin()); - LLRenderPass::applyModelMatrix(*params); - - if (push_alpha) - { - pushBufferVerts(group->mVertexBuffer); - } - - for (LLSpatialGroup::buffer_map_t::iterator i = group->mBufferMap.begin(); i != group->mBufferMap.end(); ++i) - { - for (LLSpatialGroup::buffer_texture_map_t::iterator j = i->second.begin(); j != i->second.end(); ++j) - { - for (LLSpatialGroup::buffer_list_t::iterator k = j->second.begin(); k != j->second.end(); ++k) - { - pushBufferVerts(*k); - } - } - } - } - } - /*else - { - //const LLVector4a* bounds = group->getBounds(); - //drawBox(bounds[0], bounds[1]); - }*/ -} - -void pushVertsColorCoded(LLSpatialGroup* group) -{ - LLDrawInfo* params = NULL; - - static const LLColor4 colors[] = { - LLColor4::green, - LLColor4::green1, - LLColor4::green2, - LLColor4::green3, - LLColor4::green4, - LLColor4::green5, - LLColor4::green6 - }; - - static const U32 col_count = LL_ARRAY_SIZE(colors); - - U32 col = 0; - - for (LLSpatialGroup::draw_map_t::iterator i = group->mDrawMap.begin(); i != group->mDrawMap.end(); ++i) - { - for (LLSpatialGroup::drawmap_elem_t::iterator j = i->second.begin(); j != i->second.end(); ++j) - { - params = *j; - LLRenderPass::applyModelMatrix(*params); - gGL.diffuseColor4f(colors[col].mV[0], colors[col].mV[1], colors[col].mV[2], 0.5f); - params->mVertexBuffer->setBuffer(); - params->mVertexBuffer->drawRange(LLRender::TRIANGLES, - params->mStart, params->mEnd, params->mCount, params->mOffset); - col = (col+1)%col_count; - } - } -} - -// return false if drawable is rigged and: -// - a linked rigged drawable has a different spatial group -// - a linked rigged drawable face has the wrong draw order index -bool check_rigged_group(LLDrawable* drawable) -{ -#if 0 - if (drawable->isState(LLDrawable::RIGGED)) - { - LLSpatialGroup* group = drawable->getSpatialGroup(); - LLDrawable* root = drawable->getRoot(); - - if (root->isState(LLDrawable::RIGGED) && root->getSpatialGroup() != group) - { - LL_WARNS() << "[root->isState(LLDrawable::RIGGED) and root->getSpatialGroup() != group] is true" - " (" << root->getSpatialGroup() << " != " << group << ")" << LL_ENDL; - llassert(false); - return false; - } - - S32 last_draw_index = -1; - if (root->isState(LLDrawable::RIGGED)) - { - for (auto& face : root->getFaces()) - { - if ((S32) face->getDrawOrderIndex() <= last_draw_index) - { - LL_WARNS() << "[(S32)face->getDrawOrderIndex() <= last_draw_index] is true" - " (" << (S32)face->getDrawOrderIndex() << " <= " << last_draw_index << ")" << LL_ENDL; - llassert(false); - return false; - } - last_draw_index = face->getDrawOrderIndex(); - } - } - - for (auto& child : root->getVObj()->getChildren()) - { - if (child->mDrawable->isState(LLDrawable::RIGGED)) - { - for (auto& face : child->mDrawable->getFaces()) - { - if ((S32) face->getDrawOrderIndex() <= last_draw_index) - { - LL_WARNS() << "[(S32)face->getDrawOrderIndex() <= last_draw_index] is true" - " (" << (S32)face->getDrawOrderIndex() << " <= " << last_draw_index << ")" << LL_ENDL; - llassert(false); - return false; - } - last_draw_index = face->getDrawOrderIndex(); - } - } - - if (child->mDrawable->getSpatialGroup() != group) - { - LL_WARNS() << "[child->mDrawable->getSpatialGroup() != group] is true" - " (" << child->mDrawable->getSpatialGroup() << " != " << group << ")" << LL_ENDL; - llassert(false); - return false; - } - } - } -#endif - return true; -} - -void renderOctree(LLSpatialGroup* group) -{ - //render solid object bounding box, color - //coded by buffer usage and activity - gGL.setSceneBlendType(LLRender::BT_ADD_WITH_ALPHA); - LLVector4 col; - if (group->mBuilt > 0.f) - { - group->mBuilt -= 2.f * gFrameIntervalSeconds.value(); - col.setVec(0.1f,0.1f,1,0.1f); - - { - LLGLDepthTest gl_depth(false, false); - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - - gGL.diffuseColor4f(1,0,0,group->mBuilt); - gGL.flush(); - glLineWidth(5.f); - - const LLVector4a* bounds = group->getObjectBounds(); - drawBoxOutline(bounds[0], bounds[1]); - gGL.flush(); - glLineWidth(1.f); - gGL.flush(); - - LLVOAvatar* lastAvatar = nullptr; - U64 lastMeshId = 0; - - for (LLSpatialGroup::element_iter i = group->getDataBegin(); i != group->getDataEnd(); ++i) - { - LLDrawable* drawable = (LLDrawable*)(*i)->getDrawable(); - if(!drawable || drawable->getNumFaces() == 0) - { - continue; - } - - llassert(check_rigged_group(drawable)); - - if (!group->getSpatialPartition()->isBridge()) - { - gGL.pushMatrix(); - LLVector3 trans = drawable->getRegion()->getOriginAgent(); - gGL.translatef(trans.mV[0], trans.mV[1], trans.mV[2]); - } - - LLFace* face = drawable->getFace(0); - bool rigged = face->isState(LLFace::RIGGED); - gDebugProgram.bind(rigged); - - gGL.diffuseColor4f(1, 0, 0, 1); - - if (rigged) - { - gGL.pushMatrix(); - gGL.loadMatrix(gGLModelView); - if (lastAvatar != face->mAvatar || - lastMeshId != face->mSkinInfo->mHash) - { - if (!LLRenderPass::uploadMatrixPalette(face->mAvatar, face->mSkinInfo)) - { - continue; - } - lastAvatar = face->mAvatar; - lastMeshId = face->mSkinInfo->mHash; - } - } - for (S32 j = 0; j < drawable->getNumFaces(); j++) - { - LLFace* face = drawable->getFace(j); - if (face && face->getVertexBuffer()) - { - LLVOVolume* vol = drawable->getVOVolume(); - - if (gFrameTimeSeconds - face->mLastUpdateTime < 0.5f) - { - if (vol && vol->isShrinkWrapped()) - { - gGL.diffuseColor4f(0, 1, 1, group->mBuilt); - } - else - { - gGL.diffuseColor4f(0, 1, 0, group->mBuilt); - } - } - else if (gFrameTimeSeconds - face->mLastMoveTime < 0.5f) - { - if (vol && vol->isShrinkWrapped()) - { - gGL.diffuseColor4f(1, 1, 0, group->mBuilt); - } - else - { - gGL.diffuseColor4f(1, 0, 0, group->mBuilt); - } - } - else - { - continue; - } - - face->getVertexBuffer()->setBuffer(); - face->getVertexBuffer()->draw(LLRender::TRIANGLES, face->getIndicesCount(), face->getIndicesStart()); - } - } - - if (rigged) - { - gGL.popMatrix(); - } - - if (!group->getSpatialPartition()->isBridge()) - { - gGL.popMatrix(); - } - } - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - gDebugProgram.bind(); // make sure non-rigged variant is bound - gGL.diffuseColor4f(1,1,1,1); - } - } - - gGL.diffuseColor4fv(col.mV); - LLVector4a fudge; - fudge.splat(0.001f); - - gGL.setSceneBlendType(LLRender::BT_ALPHA); - - { - //draw opaque outline - gGL.diffuseColor4f(0,1,1,1); - - const LLVector4a* bounds = group->getBounds(); - drawBoxOutline(bounds[0], bounds[1]); - } -} - -std::set visible_selected_groups; - - - -void renderXRay(LLSpatialGroup* group, LLCamera* camera) -{ - bool render_objects = (!LLPipeline::sUseOcclusion || !group->isOcclusionState(LLSpatialGroup::OCCLUDED)) && group->isVisible() && - !group->isEmpty(); - - if (render_objects) - { - pushBufferVerts(group, false); - - bool selected = false; - - for (LLSpatialGroup::element_iter iter = group->getDataBegin(); iter != group->getDataEnd(); ++iter) - { - LLDrawable* drawable = (LLDrawable*)(*iter)->getDrawable(); - if (drawable->getVObj().notNull() && drawable->getVObj()->isSelected()) - { - selected = true; - break; - } - } - - if (selected) - { //store for rendering occlusion volume as overlay - - if (!group->getSpatialPartition()->isBridge()) - { - visible_selected_groups.insert(group); - } - else - { - visible_selected_groups.insert(group->getSpatialPartition()->asBridge()->getSpatialGroup()); - } - } - } -} - -void renderCrossHairs(LLVector3 position, F32 size, LLColor4 color) -{ - gGL.color4fv(color.mV); - gGL.begin(LLRender::LINES); - { - gGL.vertex3fv((position - LLVector3(size, 0.f, 0.f)).mV); - gGL.vertex3fv((position + LLVector3(size, 0.f, 0.f)).mV); - gGL.vertex3fv((position - LLVector3(0.f, size, 0.f)).mV); - gGL.vertex3fv((position + LLVector3(0.f, size, 0.f)).mV); - gGL.vertex3fv((position - LLVector3(0.f, 0.f, size)).mV); - gGL.vertex3fv((position + LLVector3(0.f, 0.f, size)).mV); - } - gGL.end(); -} - -void renderUpdateType(LLDrawable* drawablep) -{ - LLViewerObject* vobj = drawablep->getVObj(); - if (!vobj || OUT_UNKNOWN == vobj->getLastUpdateType()) - { - return; - } - LLGLEnable blend(GL_BLEND); - switch (vobj->getLastUpdateType()) - { - case OUT_FULL: - gGL.diffuseColor4f(0,1,0,0.5f); - break; - case OUT_TERSE_IMPROVED: - gGL.diffuseColor4f(0,1,1,0.5f); - break; - case OUT_FULL_COMPRESSED: - if (vobj->getLastUpdateCached()) - { - gGL.diffuseColor4f(1,0,0,0.5f); - } - else - { - gGL.diffuseColor4f(1,1,0,0.5f); - } - break; - case OUT_FULL_CACHED: - gGL.diffuseColor4f(0,0,1,0.5f); - break; - default: - LL_WARNS() << "Unknown update_type " << vobj->getLastUpdateType() << LL_ENDL; - break; - }; - S32 num_faces = drawablep->getNumFaces(); - if (num_faces) - { - for (S32 i = 0; i < num_faces; ++i) - { - pushVerts(drawablep->getFace(i)); - } - } -} - -void renderBoundingBox(LLDrawable* drawable, bool set_color = true) -{ - if (set_color) - { - if (drawable->isSpatialBridge()) - { - gGL.diffuseColor4f(1,0.5f,0,1); // orange - } - else if (drawable->getVOVolume()) - { - if (drawable->isRoot()) - { - gGL.diffuseColor4f(1,1,0,1); // yellow - } - else - { - gGL.diffuseColor4f(0,1,0,1); // green - } - } - else if (drawable->getVObj()) - { - switch (drawable->getVObj()->getPCode()) - { - case LLViewerObject::LL_VO_SURFACE_PATCH: - gGL.diffuseColor4f(0,1,1,1); // cyan - break; - case LLViewerObject::LL_VO_CLOUDS: - // no longer used - break; - case LLViewerObject::LL_VO_PART_GROUP: - case LLViewerObject::LL_VO_HUD_PART_GROUP: - gGL.diffuseColor4f(0,0,1,1); // blue - break; - case LLViewerObject::LL_VO_VOID_WATER: - case LLViewerObject::LL_VO_WATER: - gGL.diffuseColor4f(0,0.5f,1,1); // medium blue - break; - case LL_PCODE_LEGACY_TREE: - gGL.diffuseColor4f(0,0.5f,0,1); // dark green - break; - default: - LLControlAvatar *cav = dynamic_cast(drawable->getVObj()->asAvatar()); - if (cav) - { - bool has_pos_constraint = (cav->mPositionConstraintFixup != LLVector3()); - bool has_scale_constraint = (cav->mScaleConstraintFixup != 1.0f); - if (has_pos_constraint || has_scale_constraint) - { - gGL.diffuseColor4f(1,0,0,1); - } - else - { - gGL.diffuseColor4f(0,1,0.5,1); - } - } - else - { - gGL.diffuseColor4f(1,0,1,1); // magenta - } - break; - } - } - else - { - gGL.diffuseColor4f(1,0,0,1); - } - } - - const LLVector4a* ext; - LLVector4a pos, size; - - if (drawable->getVOVolume()) - { - //render face bounding boxes - for (S32 i = 0; i < drawable->getNumFaces(); i++) - { - LLFace* facep = drawable->getFace(i); - if (facep) - { - ext = facep->mExtents; - - pos.setAdd(ext[0], ext[1]); - pos.mul(0.5f); - size.setSub(ext[1], ext[0]); - size.mul(0.5f); - - drawBoxOutline(pos,size); - } - } - } - - //render drawable bounding box - ext = drawable->getSpatialExtents(); - - pos.setAdd(ext[0], ext[1]); - pos.mul(0.5f); - size.setSub(ext[1], ext[0]); - size.mul(0.5f); - - LLViewerObject* vobj = drawable->getVObj(); - if (vobj && vobj->onActiveList()) - { - gGL.flush(); - glLineWidth(llmax(4.f*sinf(gFrameTimeSeconds*2.f)+1.f, 1.f)); - //glLineWidth(4.f*(sinf(gFrameTimeSeconds*2.f)*0.25f+0.75f)); - stop_glerror(); - drawBoxOutline(pos,size); - gGL.flush(); - glLineWidth(1.f); - } - else - { - drawBoxOutline(pos,size); - } -} - -void renderNormals(LLDrawable *drawablep) -{ - if (!drawablep->isVisible()) - return; - - LLVertexBuffer::unbind(); - - LLVOVolume *vol = drawablep->getVOVolume(); - - if (vol) - { - LLVolume *volume = vol->getVolume(); - - // Drawable's normals & tangents are stored in model space, i.e. before any scaling is applied. - // - // SL-13490, using pos + normal to compute the 2nd vertex of a normal line segment doesn't - // work when there's a non-uniform scale in the mix. Normals require MVP-inverse-transpose - // transform. We get that effect here by pre-applying the inverse scale (twice, because - // one forward scale will be re-applied via the MVP in the vertex shader) - - LLVector3 scale_v3 = vol->getScale(); - float scale_len = scale_v3.length(); - LLVector4a obj_scale(scale_v3.mV[VX], scale_v3.mV[VY], scale_v3.mV[VZ]); - obj_scale.normalize3(); - - // Normals &tangent line segments get scaled along with the object. Divide by scale length - // to keep the as-viewed lengths (relatively) constant with the debug setting length - float draw_length = gSavedSettings.getF32("RenderDebugNormalScale") / scale_len; - - // Create inverse-scale vector for normals - LLVector4a inv_scale(1.0 / scale_v3.mV[VX], 1.0 / scale_v3.mV[VY], 1.0 / scale_v3.mV[VZ]); - inv_scale.mul(inv_scale); // Squared, to apply inverse scale twice - inv_scale.normalize3fast(); - - gGL.pushMatrix(); - gGL.multMatrix((F32 *) vol->getRelativeXform().mMatrix); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - for (S32 i = 0; i < volume->getNumVolumeFaces(); ++i) - { - const LLVolumeFace &face = volume->getVolumeFace(i); - - gGL.flush(); - gGL.diffuseColor4f(1, 1, 0, 1); - gGL.begin(LLRender::LINES); - for (S32 j = 0; j < face.mNumVertices; ++j) - { - LLVector4a n, p; - - n.setMul(face.mNormals[j], 1.0); - n.mul(inv_scale); // Pre-scale normal, so it's left with an inverse-transpose xform after MVP - n.normalize3fast(); - n.mul(draw_length); - p.setAdd(face.mPositions[j], n); - - gGL.vertex3fv(face.mPositions[j].getF32ptr()); - gGL.vertex3fv(p.getF32ptr()); - } - gGL.end(); - - // Tangents are simple vectors and do not require reorientation via pre-scaling - if (face.mTangents) - { - gGL.flush(); - gGL.diffuseColor4f(0, 1, 1, 1); - gGL.begin(LLRender::LINES); - for (S32 j = 0; j < face.mNumVertices; ++j) - { - LLVector4a t, p; - - t.setMul(face.mTangents[j], 1.0f); - t.normalize3fast(); - t.mul(draw_length); - p.setAdd(face.mPositions[j], t); - - gGL.vertex3fv(face.mPositions[j].getF32ptr()); - gGL.vertex3fv(p.getF32ptr()); - } - gGL.end(); - } - } - - gGL.popMatrix(); - } -} - -S32 get_physics_detail(const LLVolumeParams& volume_params, const LLVector3& scale) -{ - const S32 DEFAULT_DETAIL = 1; - const F32 LARGE_THRESHOLD = 5.f; - const F32 MEGA_THRESHOLD = 25.f; - - S32 detail = DEFAULT_DETAIL; - F32 avg_scale = (scale[0]+scale[1]+scale[2])/3.f; - - if (avg_scale > LARGE_THRESHOLD) - { - detail += 1; - if (avg_scale > MEGA_THRESHOLD) - { - detail += 1; - } - } - - return detail; -} - -void renderMeshBaseHull(LLVOVolume* volume, U32 data_mask, LLColor4& color) -{ - LLUUID mesh_id = volume->getVolume()->getParams().getSculptID(); - LLModel::Decomposition* decomp = gMeshRepo.getDecomposition(mesh_id); - - const LLVector3 center(0,0,0); - const LLVector3 size(0.25f,0.25f,0.25f); - - if (decomp) - { - if (!decomp->mBaseHullMesh.empty()) - { - gGL.diffuseColor4fv(color.mV); - LLVertexBuffer::drawArrays(LLRender::TRIANGLES, decomp->mBaseHullMesh.mPositions); - } - else - { - gMeshRepo.buildPhysicsMesh(*decomp); - gGL.diffuseColor4f(0,1,1,1); - drawBoxOutline(center, size); - } - - } - else - { - gGL.diffuseColor3f(1,0,1); - drawBoxOutline(center, size); - } -} - -void render_hull(LLModel::PhysicsMesh& mesh, const LLColor4& color) -{ - gGL.diffuseColor4fv(color.mV); - LLVertexBuffer::drawArrays(LLRender::TRIANGLES, mesh.mPositions); -} - -void renderPhysicsShape(LLDrawable* drawable, LLVOVolume* volume, bool wireframe) -{ - U8 physics_type = volume->getPhysicsShapeType(); - - if (physics_type == LLViewerObject::PHYSICS_SHAPE_NONE || volume->isFlexible()) - { - return; - } - - //not allowed to return at this point without rendering *something* - - F32 threshold = gSavedSettings.getF32("ObjectCostHighThreshold"); - F32 cost = volume->getObjectCost(); - - LLColor4 low = gSavedSettings.getColor4("ObjectCostLowColor"); - LLColor4 mid = gSavedSettings.getColor4("ObjectCostMidColor"); - LLColor4 high = gSavedSettings.getColor4("ObjectCostHighColor"); - - F32 normalizedCost = 1.f - exp( -(cost / threshold) ); - - LLColor4 color; - if ( normalizedCost <= 0.5f ) - { - color = lerp( low, mid, 2.f * normalizedCost ); - } - else - { - color = lerp( mid, high, 2.f * ( normalizedCost - 0.5f ) ); - } - - if (wireframe) - { - color = color * 0.5f; - } - - U32 data_mask = LLVertexBuffer::MAP_VERTEX; - - LLVolumeParams volume_params = volume->getVolume()->getParams(); - - LLPhysicsVolumeParams physics_params(volume_params, - physics_type == LLViewerObject::PHYSICS_SHAPE_CONVEX_HULL); - - LLPhysicsShapeBuilderUtil::PhysicsShapeSpecification physics_spec; - LLPhysicsShapeBuilderUtil::determinePhysicsShape(physics_params, volume->getScale(), physics_spec); - - U32 type = physics_spec.getType(); - - LLVector3 center(0,0,0); - LLVector3 size(0.25f,0.25f,0.25f); - - gGL.pushMatrix(); - gGL.multMatrix((F32*) volume->getRelativeXform().mMatrix); - - if (type == LLPhysicsShapeBuilderUtil::PhysicsShapeSpecification::USER_MESH) - { - LLUUID mesh_id = volume->getVolume()->getParams().getSculptID(); - LLModel::Decomposition* decomp = gMeshRepo.getDecomposition(mesh_id); - - if (decomp) - { //render a physics based mesh - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - if (!decomp->mHull.empty()) - { //decomposition exists, use that - - if (decomp->mMesh.empty()) - { - gMeshRepo.buildPhysicsMesh(*decomp); - } - - for (U32 i = 0; i < decomp->mMesh.size(); ++i) - { - render_hull(decomp->mMesh[i], color); - } - } - else if (!decomp->mPhysicsShapeMesh.empty()) - { - //decomp has physics mesh, render that mesh - gGL.diffuseColor4fv(color.mV); - - LLVertexBuffer::drawArrays(LLRender::TRIANGLES, decomp->mPhysicsShapeMesh.mPositions); - } - else - { //no mesh or decomposition, render base hull - renderMeshBaseHull(volume, data_mask, color); - - if (decomp->mPhysicsShapeMesh.empty()) - { - //attempt to fetch physics shape mesh if available - gMeshRepo.fetchPhysicsShape(mesh_id); - } - } - } - else - { - gGL.diffuseColor3f(1,1,0); - drawBoxOutline(center, size); - } - } - else if (type == LLPhysicsShapeBuilderUtil::PhysicsShapeSpecification::USER_CONVEX || - type == LLPhysicsShapeBuilderUtil::PhysicsShapeSpecification::PRIM_CONVEX) - { - if (volume->isMesh()) - { - renderMeshBaseHull(volume, data_mask, color); - } - else - { - LLVolumeParams volume_params = volume->getVolume()->getParams(); - S32 detail = get_physics_detail(volume_params, volume->getScale()); - LLVolume* phys_volume = LLPrimitive::sVolumeManager->refVolume(volume_params, detail); - - if (!phys_volume->mHullPoints) - { //build convex hull - std::vector pos; - std::vector index; - - S32 index_offset = 0; - - for (S32 i = 0; i < phys_volume->getNumVolumeFaces(); ++i) - { - const LLVolumeFace& face = phys_volume->getVolumeFace(i); - if (index_offset + face.mNumVertices > 65535) - { - continue; - } - - for (S32 j = 0; j < face.mNumVertices; ++j) - { - pos.push_back(LLVector3(face.mPositions[j].getF32ptr())); - } - - for (S32 j = 0; j < face.mNumIndices; ++j) - { - index.push_back(face.mIndices[j]+index_offset); - } - - index_offset += face.mNumVertices; - } - - if (!pos.empty() && !index.empty()) - { - LLCDMeshData mesh; - mesh.mIndexBase = &index[0]; - mesh.mVertexBase = pos[0].mV; - mesh.mNumVertices = pos.size(); - mesh.mVertexStrideBytes = 12; - mesh.mIndexStrideBytes = 6; - mesh.mIndexType = LLCDMeshData::INT_16; - - mesh.mNumTriangles = index.size()/3; - - LLCDMeshData res; - - LLConvexDecomposition::getInstance()->generateSingleHullMeshFromMesh( &mesh, &res ); - - //copy res into phys_volume - phys_volume->mHullPoints = (LLVector4a*) ll_aligned_malloc_16(sizeof(LLVector4a)*res.mNumVertices); - phys_volume->mNumHullPoints = res.mNumVertices; - - S32 idx_size = (res.mNumTriangles*3*2+0xF) & ~0xF; - phys_volume->mHullIndices = (U16*) ll_aligned_malloc_16(idx_size); - phys_volume->mNumHullIndices = res.mNumTriangles*3; - - const F32* v = res.mVertexBase; - - for (S32 i = 0; i < res.mNumVertices; ++i) - { - F32* p = (F32*) ((U8*)v+i*res.mVertexStrideBytes); - phys_volume->mHullPoints[i].load3(p); - } - - if (res.mIndexType == LLCDMeshData::INT_16) - { - for (S32 i = 0; i < res.mNumTriangles; ++i) - { - U16* idx = (U16*) (((U8*)res.mIndexBase)+i*res.mIndexStrideBytes); - - phys_volume->mHullIndices[i*3+0] = idx[0]; - phys_volume->mHullIndices[i*3+1] = idx[1]; - phys_volume->mHullIndices[i*3+2] = idx[2]; - } - } - else - { - for (S32 i = 0; i < res.mNumTriangles; ++i) - { - U32* idx = (U32*) (((U8*)res.mIndexBase)+i*res.mIndexStrideBytes); - - phys_volume->mHullIndices[i*3+0] = (U16) idx[0]; - phys_volume->mHullIndices[i*3+1] = (U16) idx[1]; - phys_volume->mHullIndices[i*3+2] = (U16) idx[2]; - } - } - } - } - - if (phys_volume->mHullPoints) - { - //render hull - gGL.diffuseColor4fv(color.mV); - - LLVertexBuffer::unbind(); - LLVertexBuffer::drawElements(LLRender::TRIANGLES, phys_volume->mHullPoints, NULL, phys_volume->mNumHullIndices, phys_volume->mHullIndices); - } - else - { - gGL.diffuseColor4f(1,0,1,1); - drawBoxOutline(center, size); - } - - LLPrimitive::sVolumeManager->unrefVolume(phys_volume); - } - } - else if (type == LLPhysicsShapeBuilderUtil::PhysicsShapeSpecification::BOX) - { - if (!wireframe) - { - LLVector3 center = physics_spec.getCenter(); - LLVector3 scale = physics_spec.getScale(); - LLVector3 vscale = volume->getScale() * 2.f; - scale.set(scale[0] / vscale[0], scale[1] / vscale[1], scale[2] / vscale[2]); - - gGL.diffuseColor4fv(color.mV); - drawBox(center, scale); - } - } - else if (type == LLPhysicsShapeBuilderUtil::PhysicsShapeSpecification::SPHERE) - { - if (!wireframe) - { - LLVolumeParams volume_params; - volume_params.setType(LL_PCODE_PROFILE_CIRCLE_HALF, LL_PCODE_PATH_CIRCLE); - volume_params.setBeginAndEndS(0.f, 1.f); - volume_params.setBeginAndEndT(0.f, 1.f); - volume_params.setRatio(1, 1); - volume_params.setShear(0, 0); - LLVolume* sphere = LLPrimitive::sVolumeManager->refVolume(volume_params, 3); - - gGL.diffuseColor4fv(color.mV); - pushVerts(sphere); - LLPrimitive::sVolumeManager->unrefVolume(sphere); - } - } - else if (type == LLPhysicsShapeBuilderUtil::PhysicsShapeSpecification::CYLINDER) - { - if (!wireframe) - { - LLVolumeParams volume_params; - volume_params.setType(LL_PCODE_PROFILE_CIRCLE, LL_PCODE_PATH_LINE); - volume_params.setBeginAndEndS(0.f, 1.f); - volume_params.setBeginAndEndT(0.f, 1.f); - volume_params.setRatio(1, 1); - volume_params.setShear(0, 0); - LLVolume* cylinder = LLPrimitive::sVolumeManager->refVolume(volume_params, 3); - - gGL.diffuseColor4fv(color.mV); - pushVerts(cylinder); - LLPrimitive::sVolumeManager->unrefVolume(cylinder); - } - } - else if (type == LLPhysicsShapeBuilderUtil::PhysicsShapeSpecification::PRIM_MESH) - { - LLVolumeParams volume_params = volume->getVolume()->getParams(); - S32 detail = get_physics_detail(volume_params, volume->getScale()); - - LLVolume* phys_volume = LLPrimitive::sVolumeManager->refVolume(volume_params, detail); - - gGL.diffuseColor4fv(color.mV); - pushVerts(phys_volume); - - LLPrimitive::sVolumeManager->unrefVolume(phys_volume); - } - else if (type == LLPhysicsShapeBuilderUtil::PhysicsShapeSpecification::PRIM_CONVEX) - { - LLVolumeParams volume_params = volume->getVolume()->getParams(); - S32 detail = get_physics_detail(volume_params, volume->getScale()); - - LLVolume* phys_volume = LLPrimitive::sVolumeManager->refVolume(volume_params, detail); - - if (phys_volume->mHullPoints && phys_volume->mHullIndices) - { - - llassert(LLGLSLShader::sCurBoundShader != 0); - LLVertexBuffer::unbind(); - glVertexPointer(3, GL_FLOAT, 16, phys_volume->mHullPoints); - - gGL.diffuseColor4fv(color.mV); - - gGL.syncMatrices(); - glDrawElements(GL_TRIANGLES, phys_volume->mNumHullIndices, GL_UNSIGNED_SHORT, phys_volume->mHullIndices); - } - else - { - gGL.diffuseColor3f(1,0,1); - drawBoxOutline(center, size); - gMeshRepo.buildHull(volume_params, detail); - } - LLPrimitive::sVolumeManager->unrefVolume(phys_volume); - } - else if (type == LLPhysicsShapeBuilderUtil::PhysicsShapeSpecification::SCULPT) - { - //TODO: implement sculpted prim physics display - } - else - { - LL_ERRS() << "Unhandled type" << LL_ENDL; - } - - gGL.popMatrix(); -} - -void renderPhysicsShapes(LLSpatialGroup* group, bool wireframe) -{ - for (OctreeNode::const_element_iter i = group->getDataBegin(); i != group->getDataEnd(); ++i) - { - LLDrawable* drawable = (LLDrawable*)(*i)->getDrawable(); - if(!drawable) - { - continue; - } - - if (drawable->isSpatialBridge()) - { - LLSpatialBridge* bridge = drawable->asPartition()->asBridge(); - - if (bridge) - { - gGL.pushMatrix(); - gGL.multMatrix((F32*)bridge->mDrawable->getRenderMatrix().mMatrix); - bridge->renderPhysicsShapes(wireframe); - gGL.popMatrix(); - } - } - else - { - LLVOVolume* volume = drawable->getVOVolume(); - if (volume && !volume->isAttachment() && volume->getPhysicsShapeType() != LLViewerObject::PHYSICS_SHAPE_NONE ) - { - if (!group->getSpatialPartition()->isBridge()) - { - gGL.pushMatrix(); - LLVector3 trans = drawable->getRegion()->getOriginAgent(); - gGL.translatef(trans.mV[0], trans.mV[1], trans.mV[2]); - renderPhysicsShape(drawable, volume, wireframe); - gGL.popMatrix(); - } - else - { - renderPhysicsShape(drawable, volume, wireframe); - } - } - else - { -#if 0 - LLViewerObject* object = drawable->getVObj(); - if (object && object->getPCode() == LLViewerObject::LL_VO_SURFACE_PATCH) - { - gGL.pushMatrix(); - gGL.multMatrix((F32*) object->getRegion()->mRenderMatrix.mMatrix); - //push face vertices for terrain - for (S32 i = 0; i < drawable->getNumFaces(); ++i) - { - LLFace* face = drawable->getFace(i); - if (face) - { - LLVertexBuffer* buff = face->getVertexBuffer(); - if (buff) - { - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - - buff->setBuffer(); - gGL.diffuseColor4f(0.2f, 0.5f, 0.3f, 0.5f); - buff->draw(LLRender::TRIANGLES, buff->getNumIndices(), 0); - - gGL.diffuseColor4f(0.2f, 1.f, 0.3f, 0.75f); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - buff->draw(LLRender::TRIANGLES, buff->getNumIndices(), 0); - } - } - } - gGL.popMatrix(); - } -#endif - } - } - } -} - -void renderTexturePriority(LLDrawable* drawable) -{ - for (int face=0; facegetNumFaces(); ++face) - { - LLFace *facep = drawable->getFace(face); - - LLVector4 cold(0,0,0.25f); - LLVector4 hot(1,0.25f,0.25f); - - LLVector4 boost_cold(0,0,0,0); - LLVector4 boost_hot(0,1,0,1); - - LLGLDisable blend(GL_BLEND); - - //LLViewerTexture* imagep = facep->getTexture(); - //if (imagep) - if (facep) - { - - //F32 vsize = imagep->mMaxVirtualSize; - F32 vsize = facep->getPixelArea(); - - if (vsize > sCurMaxTexPriority) - { - sCurMaxTexPriority = vsize; - } - - F32 t = vsize/sLastMaxTexPriority; - - LLVector4 col = lerp(cold, hot, t); - gGL.diffuseColor4fv(col.mV); - } - //else - //{ - // gGL.diffuseColor4f(1,0,1,1); - //} - - LLVector4a center; - center.setAdd(facep->mExtents[1],facep->mExtents[0]); - center.mul(0.5f); - LLVector4a size; - size.setSub(facep->mExtents[1],facep->mExtents[0]); - size.mul(0.5f); - size.add(LLVector4a(0.01f)); - drawBox(center, size); - - /*S32 boost = imagep->getBoostLevel(); - if (boost>LLGLTexture::BOOST_NONE) - { - F32 t = (F32) boost / (F32) (LLGLTexture::BOOST_MAX_LEVEL-1); - LLVector4 col = lerp(boost_cold, boost_hot, t); - LLGLEnable blend_on(GL_BLEND); - gGL.blendFunc(GL_SRC_ALPHA, GL_ONE); - gGL.diffuseColor4fv(col.mV); - drawBox(center, size); - gGL.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - }*/ - } -} - -void renderPoints(LLDrawable* drawablep) -{ - LLGLDepthTest depth(GL_FALSE, GL_FALSE); - if (drawablep->getNumFaces()) - { - gGL.begin(LLRender::POINTS); - gGL.diffuseColor3f(1,1,1); - for (S32 i = 0; i < drawablep->getNumFaces(); i++) - { - LLFace * face = drawablep->getFace(i); - if (face) - { - gGL.vertex3fv(face->mCenterLocal.mV); - } - } - gGL.end(); - } -} - -void renderTextureAnim(LLDrawInfo* params) -{ - if (!params->mTextureMatrix) - { - return; - } - - LLGLEnable blend(GL_BLEND); - gGL.diffuseColor4f(1,1,0,0.5f); - pushVerts(params); -} - -void renderBatchSize(LLDrawInfo* params) -{ - LLGLEnable offset(GL_POLYGON_OFFSET_FILL); - glPolygonOffset(-1.f, 1.f); - LLGLSLShader* old_shader = LLGLSLShader::sCurBoundShaderPtr; - bool bind = false; - if (params->mAvatar) - { - gGL.pushMatrix(); - gGL.loadMatrix(gGLModelView); - bind = true; - old_shader->mRiggedVariant->bind(); - LLRenderPass::uploadMatrixPalette(*params); - } - - - gGL.diffuseColor4ubv(params->getDebugColor().mV); - pushVerts(params); - - if (bind) - { - gGL.popMatrix(); - old_shader->bind(); - } -} - -void renderTexelDensity(LLDrawable* drawable) -{ - if (LLViewerTexture::sDebugTexelsMode == LLViewerTexture::DEBUG_TEXELS_OFF - || LLViewerTexture::sCheckerBoardImagep.isNull()) - { - return; - } - - LLGLEnable _(GL_BLEND); - - LLMatrix4 checkerboard_matrix; - S32 discard_level = -1; - - for (S32 f = 0; f < drawable->getNumFaces(); f++) - { - LLFace* facep = drawable->getFace(f); - LLVertexBuffer* buffer = facep->getVertexBuffer(); - LLViewerTexture* texturep = facep->getTexture(); - - if (texturep == NULL) continue; - - switch(LLViewerTexture::sDebugTexelsMode) - { - case LLViewerTexture::DEBUG_TEXELS_CURRENT: - discard_level = -1; - break; - case LLViewerTexture::DEBUG_TEXELS_DESIRED: - { - LLViewerFetchedTexture* fetched_texturep = dynamic_cast(texturep); - discard_level = fetched_texturep ? fetched_texturep->getDesiredDiscardLevel() : -1; - break; - } - default: - case LLViewerTexture::DEBUG_TEXELS_FULL: - discard_level = 0; - break; - } - - checkerboard_matrix.initScale(LLVector3(texturep->getWidth(discard_level) / 8, texturep->getHeight(discard_level) / 8, 1.f)); - - gGL.getTexUnit(0)->bind(LLViewerTexture::sCheckerBoardImagep, true); - gGL.matrixMode(LLRender::MM_TEXTURE); - gGL.loadMatrix((GLfloat*)&checkerboard_matrix.mMatrix); - - if (buffer && (facep->getGeomCount() >= 3)) - { - buffer->setBuffer(); - U16 start = facep->getGeomStart(); - U16 end = start + facep->getGeomCount()-1; - U32 count = facep->getIndicesCount(); - U16 offset = facep->getIndicesStart(); - buffer->drawRange(LLRender::TRIANGLES, start, end, count, offset); - } - - gGL.loadIdentity(); - gGL.matrixMode(LLRender::MM_MODELVIEW); - } - - //S32 num_textures = llmax(1, (S32)params->mTextureList.size()); - - //for (S32 i = 0; i < num_textures; i++) - //{ - // LLViewerTexture* texturep = params->mTextureList.empty() ? params->mTexture.get() : params->mTextureList[i].get(); - // if (texturep == NULL) continue; - - // LLMatrix4 checkboard_matrix; - // S32 discard_level = -1; - // switch(LLViewerTexture::sDebugTexelsMode) - // { - // case LLViewerTexture::DEBUG_TEXELS_CURRENT: - // discard_level = -1; - // break; - // case LLViewerTexture::DEBUG_TEXELS_DESIRED: - // { - // LLViewerFetchedTexture* fetched_texturep = dynamic_cast(texturep); - // discard_level = fetched_texturep ? fetched_texturep->getDesiredDiscardLevel() : -1; - // break; - // } - // default: - // case LLViewerTexture::DEBUG_TEXELS_FULL: - // discard_level = 0; - // break; - // } - - // checkboard_matrix.initScale(LLVector3(texturep->getWidth(discard_level) / 8, texturep->getHeight(discard_level) / 8, 1.f)); - // gGL.getTexUnit(i)->activate(); - - // glMatrixMode(GL_TEXTURE); - // glPushMatrix(); - // glLoadIdentity(); - // //gGL.matrixMode(LLRender::MM_TEXTURE); - // glLoadMatrixf((GLfloat*) checkboard_matrix.mMatrix); - - // gGL.getTexUnit(i)->bind(LLViewerTexture::sCheckerBoardImagep, true); - - // pushVerts(params, LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_COLOR | LLVertexBuffer::MAP_NORMAL ); - - // glPopMatrix(); - // glMatrixMode(GL_MODELVIEW); - // //gGL.matrixMode(LLRender::MM_MODELVIEW); - //} -} - - -void renderLights(LLDrawable* drawablep) -{ - if (!drawablep->isLight()) - { - return; - } - - if (drawablep->getNumFaces()) - { - LLGLEnable blend(GL_BLEND); - gGL.diffuseColor4f(0,1,1,0.5f); - - for (S32 i = 0; i < drawablep->getNumFaces(); i++) - { - LLFace * face = drawablep->getFace(i); - if (face) - { - pushVerts(face); - } - } - - const LLVector4a* ext = drawablep->getSpatialExtents(); - - LLVector4a pos; - pos.setAdd(ext[0], ext[1]); - pos.mul(0.5f); - LLVector4a size; - size.setSub(ext[1], ext[0]); - size.mul(0.5f); - - { - LLGLDepthTest depth(GL_FALSE, GL_TRUE); - gGL.diffuseColor4f(1,1,1,1); - drawBoxOutline(pos, size); - } - - gGL.diffuseColor4f(1,1,0,1); - F32 rad = drawablep->getVOVolume()->getLightRadius(); - drawBoxOutline(pos, LLVector4a(rad)); - } -} - -class LLRenderOctreeRaycast : public LLOctreeTriangleRayIntersect -{ -public: - - - LLRenderOctreeRaycast(const LLVector4a& start, const LLVector4a& dir, F32* closest_t) - : LLOctreeTriangleRayIntersect(start, dir, NULL, closest_t, NULL, NULL, NULL, NULL) - { - - } - - void visit(const LLOctreeNode* branch) - { - LLVolumeOctreeListener* vl = (LLVolumeOctreeListener*) branch->getListener(0); - - LLVector3 center, size; - - if (branch->isEmpty()) - { - gGL.diffuseColor3f(1.f,0.2f,0.f); - center.set(branch->getCenter().getF32ptr()); - size.set(branch->getSize().getF32ptr()); - } - else - { - gGL.diffuseColor3f(0.75f, 1.f, 0.f); - center.set(vl->mBounds[0].getF32ptr()); - size.set(vl->mBounds[1].getF32ptr()); - } - - drawBoxOutline(center, size); - - for (U32 i = 0; i < 2; i++) - { - LLGLDepthTest depth(GL_TRUE, GL_FALSE, i == 1 ? GL_LEQUAL : GL_GREATER); - - if (i == 1) - { - gGL.diffuseColor4f(0,1,1,0.5f); - } - else - { - gGL.diffuseColor4f(0,0.5f,0.5f, 0.25f); - drawBoxOutline(center, size); - } - - if (i == 1) - { - gGL.flush(); - glLineWidth(3.f); - } - - gGL.begin(LLRender::TRIANGLES); - for (LLOctreeNode::const_element_iter iter = branch->getDataBegin(); - iter != branch->getDataEnd(); - ++iter) - { - const LLVolumeTriangle* tri = *iter; - - gGL.vertex3fv(tri->mV[0]->getF32ptr()); - gGL.vertex3fv(tri->mV[1]->getF32ptr()); - gGL.vertex3fv(tri->mV[2]->getF32ptr()); - } - gGL.end(); - - if (i == 1) - { - gGL.flush(); - glLineWidth(1.f); - } - } - } -}; - -void renderRaycast(LLDrawable* drawablep) -{ - if (drawablep->getNumFaces()) - { - LLGLEnable blend(GL_BLEND); - gGL.diffuseColor4f(0,1,1,0.5f); - - LLVOVolume* vobj = drawablep->getVOVolume(); - if (vobj && !vobj->isDead()) - { - //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - //pushVerts(drawablep->getFace(gDebugRaycastFaceHit), LLVertexBuffer::MAP_VERTEX); - //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - - LLVolume* volume = vobj->getVolume(); - - bool transform = true; - if (drawablep->isState(LLDrawable::RIGGED)) - { - volume = vobj->getRiggedVolume(); - transform = false; - } - - if (volume) - { - LLVector3 trans = drawablep->getRegion()->getOriginAgent(); - - for (S32 i = 0; i < volume->getNumVolumeFaces(); ++i) - { - const LLVolumeFace& face = volume->getVolumeFace(i); - - gGL.pushMatrix(); - gGL.translatef(trans.mV[0], trans.mV[1], trans.mV[2]); - gGL.multMatrix((F32*) vobj->getRelativeXform().mMatrix); - - LLVector4a start, end; - if (transform) - { - LLVector3 v_start(gDebugRaycastStart.getF32ptr()); - LLVector3 v_end(gDebugRaycastEnd.getF32ptr()); - - v_start = vobj->agentPositionToVolume(v_start); - v_end = vobj->agentPositionToVolume(v_end); - - start.load3(v_start.mV); - end.load3(v_end.mV); - } - else - { - start = gDebugRaycastStart; - end = gDebugRaycastEnd; - } - - LLVector4a dir; - dir.setSub(end, start); - - gGL.flush(); - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - - { - //render face positions - LLVertexBuffer::unbind(); - gGL.diffuseColor4f(0,1,1,0.5f); - glVertexPointer(3, GL_FLOAT, sizeof(LLVector4a), face.mPositions); - gGL.syncMatrices(); - glDrawElements(GL_TRIANGLES, face.mNumIndices, GL_UNSIGNED_SHORT, face.mIndices); - } - - if (!volume->isUnique()) - { - F32 t = 1.f; - - if (!face.getOctree()) - { - ((LLVolumeFace*) &face)->createOctree(); - } - - LLRenderOctreeRaycast render(start, dir, &t); - - render.traverse(face.getOctree()); - } - - gGL.popMatrix(); - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - } - } - } - else if (drawablep->isAvatar()) - { - if (drawablep->getVObj() == gDebugRaycastObject) - { - LLGLDepthTest depth(GL_FALSE); - LLVOAvatar* av = (LLVOAvatar*) drawablep->getVObj().get(); - av->renderCollisionVolumes(); - } - } - - if (drawablep->getVObj() == gDebugRaycastObject) - { - // draw intersection point - gGL.pushMatrix(); - gGL.loadMatrix(gGLModelView); - LLVector3 translate(gDebugRaycastIntersection.getF32ptr()); - gGL.translatef(translate.mV[0], translate.mV[1], translate.mV[2]); - LLCoordFrame orient; - LLVector4a debug_binormal; - - debug_binormal.setCross3(gDebugRaycastNormal, gDebugRaycastTangent); - debug_binormal.mul(gDebugRaycastTangent.getF32ptr()[3]); - - LLVector3 normal(gDebugRaycastNormal.getF32ptr()); - LLVector3 binormal(debug_binormal.getF32ptr()); - - orient.lookDir(normal, binormal); - LLMatrix4 rotation; - orient.getRotMatrixToParent(rotation); - gGL.multMatrix((float*)rotation.mMatrix); - - gGL.diffuseColor4f(1,0,0,0.5f); - drawBox(LLVector3(0, 0, 0), LLVector3(0.1f, 0.022f, 0.022f)); - gGL.diffuseColor4f(0,1,0,0.5f); - drawBox(LLVector3(0, 0, 0), LLVector3(0.021f, 0.1f, 0.021f)); - gGL.diffuseColor4f(0,0,1,0.5f); - drawBox(LLVector3(0, 0, 0), LLVector3(0.02f, 0.02f, 0.1f)); - gGL.popMatrix(); - - // draw bounding box of prim - const LLVector4a* ext = drawablep->getSpatialExtents(); - - LLVector4a pos; - pos.setAdd(ext[0], ext[1]); - pos.mul(0.5f); - LLVector4a size; - size.setSub(ext[1], ext[0]); - size.mul(0.5f); - - LLGLDepthTest depth(GL_FALSE, GL_TRUE); - gGL.diffuseColor4f(0,0.5f,0.5f,1); - drawBoxOutline(pos, size); - } - } -} - - -void renderAvatarCollisionVolumes(LLVOAvatar* avatar) -{ - avatar->renderCollisionVolumes(); -} - -void renderAvatarBones(LLVOAvatar* avatar) -{ - avatar->renderBones(); -} - -void renderAgentTarget(LLVOAvatar* avatar) -{ - // render these for self only (why, i don't know) - if (avatar->isSelf()) - { - renderCrossHairs(avatar->getPositionAgent(), 0.2f, LLColor4(1, 0, 0, 0.8f)); - renderCrossHairs(avatar->mDrawable->getPositionAgent(), 0.2f, LLColor4(0, 1, 0, 0.8f)); - renderCrossHairs(avatar->mRoot->getWorldPosition(), 0.2f, LLColor4(1, 1, 1, 0.8f)); - renderCrossHairs(avatar->mPelvisp->getWorldPosition(), 0.2f, LLColor4(0, 0, 1, 0.8f)); - } -} - -static void setTextureAreaDebugText(LLDrawable* drawablep) -{ - LLVOVolume* vobjp = drawablep->getVOVolume(); - - if (vobjp) - { - if (drawablep->mDistanceWRTCamera < 32.f) - { - std::ostringstream str; - - //for (S32 i = 0; i < vobjp->getNumTEs(); ++i) - S32 i = 0; - { - if (i < drawablep->getNumFaces()) - { - LLFace* facep = drawablep->getFace(i); - - if (facep) - { - LLViewerTexture* imagep = facep->getTexture(); - - if (imagep) - { - str << llformat("D - %.2f", sqrtf(imagep->getMaxVirtualSize())); - } - - imagep = vobjp->getTENormalMap(i); - - if (imagep && imagep != LLViewerFetchedTexture::sDefaultImagep) - { - str << llformat("\nN - %.2f", sqrtf(imagep->getMaxVirtualSize())); - } - - imagep = vobjp->getTESpecularMap(i); - - if (imagep && imagep != LLViewerFetchedTexture::sDefaultImagep) - { - str << llformat("\nS - %.2f", sqrtf(imagep->getMaxVirtualSize())); - } - - str << "\n\n"; - } - - vobjp->setDebugText(str.str()); - } - } - } - else - { - vobjp->setDebugText("."); - } - } -} - -class LLOctreeRenderNonOccluded : public OctreeTraveler -{ -public: - LLCamera* mCamera; - LLOctreeRenderNonOccluded(LLCamera* camera): mCamera(camera) {} - - virtual void traverse(const OctreeNode* node) - { - LLSpatialGroup* group = (LLSpatialGroup*) node->getListener(0); - - const LLVector4a* bounds = group->getBounds(); - if (!mCamera || mCamera->AABBInFrustumNoFarClip(bounds[0], bounds[1])) - { - node->accept(this); - stop_glerror(); - - for (U32 i = 0; i < node->getChildCount(); i++) - { - traverse(node->getChild(i)); - stop_glerror(); - } - - //draw tight fit bounding boxes for spatial group - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_OCTREE)) - { - group->rebuildGeom(); - group->rebuildMesh(); - - renderOctree(group); - stop_glerror(); - } - } - } - - virtual void visit(const OctreeNode* branch) - { - LLSpatialGroup* group = (LLSpatialGroup*) branch->getListener(0); - const LLVector4a* bounds = group->getBounds(); - if (group->hasState(LLSpatialGroup::GEOM_DIRTY) || (mCamera && !mCamera->AABBInFrustumNoFarClip(bounds[0], bounds[1]))) - { - return; - } - - group->rebuildGeom(); - group->rebuildMesh(); - - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_BBOXES)) - { - if (!group->isEmpty()) - { - gGL.diffuseColor3f(0,0,1); - const LLVector4a* obj_bounds = group->getObjectBounds(); - drawBoxOutline(obj_bounds[0], obj_bounds[1]); - } - } - - for (OctreeNode::const_element_iter i = branch->getDataBegin(); i != branch->getDataEnd(); ++i) - { - LLDrawable* drawable = (LLDrawable*)(*i)->getDrawable(); - if(!drawable || drawable->isDead()) - { - continue; - } - - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_BBOXES)) - { - renderBoundingBox(drawable); - } - - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_NORMALS)) - { - renderNormals(drawable); - } - - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_AREA)) - { - setTextureAreaDebugText(drawable); - } - - /*if (drawable->getVOVolume() && gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_PRIORITY)) - { - renderTexturePriority(drawable); - }*/ - - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_POINTS)) - { - renderPoints(drawable); - } - - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_LIGHTS)) - { - renderLights(drawable); - } - - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_RAYCAST)) - { - renderRaycast(drawable); - } - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_UPDATE_TYPE)) - { - renderUpdateType(drawable); - } - if(gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXEL_DENSITY)) - { - renderTexelDensity(drawable); - } - - LLVOAvatar* avatar = dynamic_cast(drawable->getVObj().get()); - - if (avatar && gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_AVATAR_VOLUME)) - { - renderAvatarCollisionVolumes(avatar); - } - - if (avatar && gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_AVATAR_JOINTS)) - { - renderAvatarBones(avatar); - } - - if (avatar && gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_AGENT_TARGET)) - { - renderAgentTarget(avatar); - } - -#if 0 - if (gDebugGL) - { - for (U32 i = 0; i < drawable->getNumFaces(); ++i) - { - LLFace* facep = drawable->getFace(i); - if (facep) - { - U8 index = facep->getTextureIndex(); - if (facep->mDrawInfo) - { - if (index < FACE_DO_NOT_BATCH_TEXTURES) - { - if (facep->mDrawInfo->mTextureList.size() <= index) - { - LL_ERRS() << "Face texture index out of bounds." << LL_ENDL; - } - else if (facep->mDrawInfo->mTextureList[index] != facep->getTexture()) - { - LL_ERRS() << "Face texture index incorrect." << LL_ENDL; - } - } - } - } - } - } -#endif - } - - for (LLSpatialGroup::draw_map_t::iterator i = group->mDrawMap.begin(); i != group->mDrawMap.end(); ++i) - { - LLSpatialGroup::drawmap_elem_t& draw_vec = i->second; - for (LLSpatialGroup::drawmap_elem_t::iterator j = draw_vec.begin(); j != draw_vec.end(); ++j) - { - LLDrawInfo* draw_info = *j; - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_ANIM)) - { - renderTextureAnim(draw_info); - } - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_BATCH_SIZE)) - { - renderBatchSize(draw_info); - } - } - } - } -}; - -class LLOctreeRenderXRay : public OctreeTraveler -{ -public: - LLCamera* mCamera; - LLOctreeRenderXRay(LLCamera* camera): mCamera(camera) {} - - virtual void traverse(const OctreeNode* node) - { - LLSpatialGroup* group = (LLSpatialGroup*) node->getListener(0); - - const LLVector4a* bounds = group->getBounds(); - if (!mCamera || mCamera->AABBInFrustumNoFarClip(bounds[0], bounds[1])) - { - node->accept(this); - stop_glerror(); - - for (U32 i = 0; i < node->getChildCount(); i++) - { - traverse(node->getChild(i)); - stop_glerror(); - } - - //render visibility wireframe - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_OCCLUSION)) - { - group->rebuildGeom(); - group->rebuildMesh(); - - gGL.flush(); - gGL.pushMatrix(); - gGLLastMatrix = NULL; - gGL.loadMatrix(gGLModelView); - renderXRay(group, mCamera); - stop_glerror(); - gGLLastMatrix = NULL; - gGL.popMatrix(); - } - } - } - - virtual void visit(const OctreeNode* node) {} - -}; - -class LLOctreeRenderPhysicsShapes : public OctreeTraveler -{ -public: - LLCamera* mCamera; - bool mWireframe; - - LLOctreeRenderPhysicsShapes(LLCamera* camera, bool wireframe): mCamera(camera), mWireframe(wireframe) {} - - virtual void traverse(const OctreeNode* node) - { - LLSpatialGroup* group = (LLSpatialGroup*) node->getListener(0); - - const LLVector4a* bounds = group->getBounds(); - if (!mCamera || mCamera->AABBInFrustumNoFarClip(bounds[0], bounds[1])) - { - node->accept(this); - stop_glerror(); - - for (U32 i = 0; i < node->getChildCount(); i++) - { - traverse(node->getChild(i)); - stop_glerror(); - } - - group->rebuildGeom(); - group->rebuildMesh(); - - renderPhysicsShapes(group, mWireframe); - } - } - - virtual void visit(const OctreeNode* branch) - { - - } -}; - -class LLOctreePushBBoxVerts : public OctreeTraveler -{ -public: - LLCamera* mCamera; - LLOctreePushBBoxVerts(LLCamera* camera): mCamera(camera) {} - - virtual void traverse(const OctreeNode* node) - { - LLSpatialGroup* group = (LLSpatialGroup*) node->getListener(0); - - const LLVector4a* bounds = group->getBounds(); - if (!mCamera || mCamera->AABBInFrustum(bounds[0], bounds[1])) - { - node->accept(this); - - for (U32 i = 0; i < node->getChildCount(); i++) - { - traverse(node->getChild(i)); - } - } - } - - virtual void visit(const OctreeNode* branch) - { - LLSpatialGroup* group = (LLSpatialGroup*) branch->getListener(0); - - const LLVector4a* bounds = group->getBounds(); - if (group->hasState(LLSpatialGroup::GEOM_DIRTY) || (mCamera && !mCamera->AABBInFrustumNoFarClip(bounds[0], bounds[1]))) - { - return; - } - - for (OctreeNode::const_element_iter i = branch->getDataBegin(); i != branch->getDataEnd(); ++i) - { - LLDrawable* drawable = (LLDrawable*)(*i)->getDrawable(); - if(!drawable) - { - continue; - } - renderBoundingBox(drawable, false); - } - } -}; - -void LLSpatialPartition::renderIntersectingBBoxes(LLCamera* camera) -{ - LLOctreePushBBoxVerts pusher(camera); - pusher.traverse(mOctree); -} - -class LLOctreeStateCheck : public OctreeTraveler -{ -public: - U32 mInheritedMask[LLViewerCamera::NUM_CAMERAS]; - - LLOctreeStateCheck() - { - for (U32 i = 0; i < LLViewerCamera::NUM_CAMERAS; i++) - { - mInheritedMask[i] = 0; - } - } - - virtual void traverse(const OctreeNode* node) - { - LLSpatialGroup* group = (LLSpatialGroup*) node->getListener(0); - - node->accept(this); - - - U32 temp[LLViewerCamera::NUM_CAMERAS]; - - for (U32 i = 0; i < LLViewerCamera::NUM_CAMERAS; i++) - { - temp[i] = mInheritedMask[i]; - mInheritedMask[i] |= group->mOcclusionState[i] & LLSpatialGroup::OCCLUDED; - } - - for (U32 i = 0; i < node->getChildCount(); i++) - { - traverse(node->getChild(i)); - } - - for (U32 i = 0; i < LLViewerCamera::NUM_CAMERAS; i++) - { - mInheritedMask[i] = temp[i]; - } - } - - - virtual void visit(const OctreeNode* state) - { - LLSpatialGroup* group = (LLSpatialGroup*) state->getListener(0); - - for (U32 i = 0; i < LLViewerCamera::NUM_CAMERAS; i++) - { - if (mInheritedMask[i] && !(group->mOcclusionState[i] & mInheritedMask[i])) - { - LL_ERRS() << "Spatial group failed inherited mask test." << LL_ENDL; - } - } - - if (group->hasState(LLSpatialGroup::DIRTY)) - { - assert_parent_state(group, LLSpatialGroup::DIRTY); - } - } - - void assert_parent_state(LLSpatialGroup* group, U32 state) - { - LLSpatialGroup* parent = group->getParent(); - while (parent) - { - if (!parent->hasState(state)) - { - LL_ERRS() << "Spatial group failed parent state check." << LL_ENDL; - } - parent = parent->getParent(); - } - } -}; - - -void LLSpatialPartition::renderPhysicsShapes(bool wireframe) -{ - LLSpatialBridge* bridge = asBridge(); - LLCamera* camera = LLViewerCamera::getInstance(); - - if (bridge) - { - camera = NULL; - } - - gGL.flush(); - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - LLOctreeRenderPhysicsShapes render_physics(camera, wireframe); - render_physics.traverse(mOctree); - gGL.flush(); -} - -void LLSpatialPartition::renderDebug() -{ - if (!gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_OCTREE | - LLPipeline::RENDER_DEBUG_OCCLUSION | - LLPipeline::RENDER_DEBUG_LIGHTS | - LLPipeline::RENDER_DEBUG_BATCH_SIZE | - LLPipeline::RENDER_DEBUG_UPDATE_TYPE | - LLPipeline::RENDER_DEBUG_BBOXES | - LLPipeline::RENDER_DEBUG_NORMALS | - LLPipeline::RENDER_DEBUG_POINTS | - LLPipeline::RENDER_DEBUG_TEXTURE_AREA | - LLPipeline::RENDER_DEBUG_TEXTURE_ANIM | - LLPipeline::RENDER_DEBUG_RAYCAST | - LLPipeline::RENDER_DEBUG_AVATAR_VOLUME | - LLPipeline::RENDER_DEBUG_AVATAR_JOINTS | - LLPipeline::RENDER_DEBUG_AGENT_TARGET | - LLPipeline::RENDER_DEBUG_SHADOW_FRUSTA | - LLPipeline::RENDER_DEBUG_TEXEL_DENSITY)) - { - return; - } - - gDebugProgram.bind(); - - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_PRIORITY)) - { - //sLastMaxTexPriority = lerp(sLastMaxTexPriority, sCurMaxTexPriority, gFrameIntervalSeconds); - sLastMaxTexPriority = (F32) LLViewerCamera::getInstance()->getScreenPixelArea(); - sCurMaxTexPriority = 0.f; - } - - LLGLDisable cullface(GL_CULL_FACE); - LLGLEnable blend(GL_BLEND); - gGL.setSceneBlendType(LLRender::BT_ALPHA); - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gPipeline.disableLights(); - - LLSpatialBridge* bridge = asBridge(); - LLCamera* camera = LLViewerCamera::getInstance(); - - if (bridge) - { - camera = NULL; - } - - LLOctreeStateCheck checker; - checker.traverse(mOctree); - - LLOctreeRenderNonOccluded render_debug(camera); - render_debug.traverse(mOctree); - - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_OCCLUSION)) - { - { - LLGLEnable cull(GL_CULL_FACE); - - LLGLEnable blend(GL_BLEND); - LLGLDepthTest depth_under(GL_TRUE, GL_FALSE, GL_GREATER); - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - gGL.diffuseColor4f(0.5f, 0.0f, 0, 0.25f); - - LLGLEnable offset(GL_POLYGON_OFFSET_LINE); - glPolygonOffset(-1.f, -1.f); - - LLOctreeRenderXRay xray(camera); - xray.traverse(mOctree); - - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - } - } - gDebugProgram.unbind(); -} - -void LLSpatialGroup::drawObjectBox(LLColor4 col) -{ - gGL.diffuseColor4fv(col.mV); - LLVector4a size; - size = mObjectBounds[1]; - size.mul(1.01f); - size.add(LLVector4a(0.001f)); - drawBox(mObjectBounds[0], size); -} - -bool LLSpatialPartition::isHUDPartition() -{ - return mPartitionType == LLViewerRegion::PARTITION_HUD ; -} - -bool LLSpatialPartition::isVisible(const LLVector3& v) -{ - if (!LLViewerCamera::getInstance()->sphereInFrustum(v, 4.0f)) - { - return false; - } - - return true; -} - -LL_ALIGN_PREFIX(16) -class LLOctreeIntersect : public LLOctreeTraveler> -{ -public: - LL_ALIGN_16(LLVector4a mStart); - LL_ALIGN_16(LLVector4a mEnd); - - S32 *mFaceHit; - LLVector4a *mIntersection; - LLVector2 *mTexCoord; - LLVector4a *mNormal; - LLVector4a *mTangent; - LLDrawable* mHit; - bool mPickTransparent; - bool mPickRigged; - bool mPickUnselectable; - bool mPickReflectionProbe; - - LLOctreeIntersect(const LLVector4a& start, const LLVector4a& end, bool pick_transparent, bool pick_rigged, bool pick_unselectable, bool pick_reflection_probe, - S32* face_hit, LLVector4a* intersection, LLVector2* tex_coord, LLVector4a* normal, LLVector4a* tangent) - : mStart(start), - mEnd(end), - mFaceHit(face_hit), - mIntersection(intersection), - mTexCoord(tex_coord), - mNormal(normal), - mTangent(tangent), - mHit(NULL), - mPickTransparent(pick_transparent), - mPickRigged(pick_rigged), - mPickUnselectable(pick_unselectable), - mPickReflectionProbe(pick_reflection_probe) - { - } - - virtual void visit(const OctreeNode* branch) - { - for (OctreeNode::const_element_iter i = branch->getDataBegin(); i != branch->getDataEnd(); ++i) - { - check(*i); - } - } - - virtual LLDrawable* check(const OctreeNode* node) - { - node->accept(this); - - for (U32 i = 0; i < node->getChildCount(); i++) - { - const OctreeNode* child = node->getChild(i); - LLVector3 res; - - LLSpatialGroup* group = (LLSpatialGroup*) child->getListener(0); - - LLVector4a size; - LLVector4a center; - - const LLVector4a* bounds = group->getBounds(); - size = bounds[1]; - center = bounds[0]; - - LLVector4a local_start = mStart; - LLVector4a local_end = mEnd; - - if (group->getSpatialPartition()->isBridge()) - { - LLMatrix4 local_matrix = group->getSpatialPartition()->asBridge()->mDrawable->getRenderMatrix(); - local_matrix.invert(); - - LLMatrix4a local_matrix4a; - local_matrix4a.loadu(local_matrix); - - local_matrix4a.affineTransform(mStart, local_start); - local_matrix4a.affineTransform(mEnd, local_end); - } - - if (LLLineSegmentBoxIntersect(local_start, local_end, center, size)) - { - check(child); - } - } - - return mHit; - } - - virtual bool check(LLViewerOctreeEntry* entry) - { - LLDrawable* drawable = (LLDrawable*)entry->getDrawable(); - - if (!drawable || !gPipeline.hasRenderType(drawable->getRenderType()) || !drawable->isVisible()) - { - return false; - } - - if (drawable->isSpatialBridge()) - { - LLSpatialPartition *part = drawable->asPartition(); - LLSpatialBridge* bridge = part->asBridge(); - if (bridge && gPipeline.hasRenderType(bridge->mDrawableType)) - { - check(part->mOctree); - } - } - else - { - LLViewerObject* vobj = drawable->getVObj(); - - if (vobj && - (!vobj->isReflectionProbe() || mPickReflectionProbe)) - { - if (vobj->getClickAction() == CLICK_ACTION_IGNORE && !LLFloater::isVisible(gFloaterTools)) - { - return false; - } - - LLVector4a intersection; - bool skip_check = false; - if (vobj->isAvatar()) - { - LLVOAvatar* avatar = (LLVOAvatar*) vobj; - if ((mPickRigged) || ((avatar->isSelf()) && (LLFloater::isVisible(gFloaterTools)))) - { - LLViewerObject* hit = avatar->lineSegmentIntersectRiggedAttachments(mStart, mEnd, -1, mPickTransparent, mPickRigged, mPickUnselectable, mFaceHit, &intersection, mTexCoord, mNormal, mTangent); - if (hit) - { - mEnd = intersection; - if (mIntersection) - { - *mIntersection = intersection; - } - - mHit = hit->mDrawable; - skip_check = true; - } - - } - } - - if (!skip_check && vobj->lineSegmentIntersect(mStart, mEnd, -1, - (mPickReflectionProbe && vobj->isReflectionProbe()) ? true : mPickTransparent, // always pick transparent when picking selection probe - mPickRigged, mPickUnselectable, mFaceHit, &intersection, mTexCoord, mNormal, mTangent)) - { - mEnd = intersection; // shorten ray so we only find CLOSER hits - if (mIntersection) - { - *mIntersection = intersection; - } - - mHit = vobj->mDrawable; - } - } - } - - return false; - } -} LL_ALIGN_POSTFIX(16); - -LLDrawable* LLSpatialPartition::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, - bool pick_transparent, - bool pick_rigged, - bool pick_unselectable, - bool pick_reflection_probe, - S32* face_hit, // return the face hit - LLVector4a* intersection, // return the intersection point - LLVector2* tex_coord, // return the texture coordinates of the intersection point - LLVector4a* normal, // return the surface normal at the intersection point - LLVector4a* tangent // return the surface tangent at the intersection point - ) - -{ - LLOctreeIntersect intersect(start, end, pick_transparent, pick_rigged, pick_unselectable, pick_reflection_probe, face_hit, intersection, tex_coord, normal, tangent); - LLDrawable* drawable = intersect.check(mOctree); - - return drawable; -} - -LLDrawable* LLSpatialGroup::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, - bool pick_transparent, - bool pick_rigged, - bool pick_unselectable, - bool pick_reflection_probe, - S32* face_hit, // return the face hit - LLVector4a* intersection, // return the intersection point - LLVector2* tex_coord, // return the texture coordinates of the intersection point - LLVector4a* normal, // return the surface normal at the intersection point - LLVector4a* tangent // return the surface tangent at the intersection point -) - -{ - LLOctreeIntersect intersect(start, end, pick_transparent, pick_rigged, pick_unselectable, pick_reflection_probe, face_hit, intersection, tex_coord, normal, tangent); - LLDrawable* drawable = intersect.check(getOctreeNode()); - - return drawable; -} - -LLDrawInfo::LLDrawInfo(U16 start, U16 end, U32 count, U32 offset, - LLViewerTexture* texture, LLVertexBuffer* buffer, - bool fullbright, U8 bump) -: mVertexBuffer(buffer), - mTexture(texture), - mStart(start), - mEnd(end), - mCount(count), - mOffset(offset), - mFullbright(fullbright), - mBump(bump), - mBlendFuncSrc(LLRender::BF_SOURCE_ALPHA), - mBlendFuncDst(LLRender::BF_ONE_MINUS_SOURCE_ALPHA), - mHasGlow(false), - mEnvIntensity(0.0f), - mAlphaMaskCutoff(0.5f) -{ - mVertexBuffer->validateRange(mStart, mEnd, mCount, mOffset); -} - -LLDrawInfo::~LLDrawInfo() -{ - if (gDebugGL) - { - gPipeline.checkReferences(this); - } -} - -LLColor4U LLDrawInfo::getDebugColor() const -{ - LLColor4U color; - - LLCRC hash; - hash.update((U8*)this + sizeof(S32), sizeof(LLDrawInfo) - sizeof(S32)); - - *((U32*) color.mV) = hash.getCRC(); - - color.mV[3] = 200; - - return color; -} - -void LLDrawInfo::validate() -{ - mVertexBuffer->validateRange(mStart, mEnd, mCount, mOffset); -} - -U64 LLDrawInfo::getSkinHash() -{ - return mSkinInfo ? mSkinInfo->mHash : 0; -} - -LLCullResult::LLCullResult() -{ - mVisibleGroupsAllocated = 0; - mAlphaGroupsAllocated = 0; - mRiggedAlphaGroupsAllocated = 0; - mOcclusionGroupsAllocated = 0; - mDrawableGroupsAllocated = 0; - mVisibleListAllocated = 0; - mVisibleBridgeAllocated = 0; - - mVisibleGroups.clear(); - mVisibleGroups.push_back(NULL); - mVisibleGroupsEnd = &mVisibleGroups[0]; - mAlphaGroups.clear(); - mAlphaGroups.push_back(NULL); - mAlphaGroupsEnd = &mAlphaGroups[0]; - mRiggedAlphaGroups.clear(); - mRiggedAlphaGroups.push_back(NULL); - mRiggedAlphaGroupsEnd = &mRiggedAlphaGroups[0]; - mOcclusionGroups.clear(); - mOcclusionGroups.push_back(NULL); - mOcclusionGroupsEnd = &mOcclusionGroups[0]; - mDrawableGroups.clear(); - mDrawableGroups.push_back(NULL); - mDrawableGroupsEnd = &mDrawableGroups[0]; - mVisibleList.clear(); - mVisibleList.push_back(NULL); - mVisibleListEnd = &mVisibleList[0]; - mVisibleBridge.clear(); - mVisibleBridge.push_back(NULL); - mVisibleBridgeEnd = &mVisibleBridge[0]; - - for (U32 i = 0; i < LLRenderPass::NUM_RENDER_TYPES; i++) - { - mRenderMap[i].clear(); - mRenderMap[i].push_back(NULL); - mRenderMapEnd[i] = &mRenderMap[i][0]; - mRenderMapAllocated[i] = 0; - } - - clear(); -} - -template -void LLCullResult::pushBack(T& head, U32& count, V* val) -{ - head[count] = val; - head.push_back(NULL); - count++; -} - -void LLCullResult::clear() -{ - mVisibleGroupsSize = 0; - mVisibleGroupsEnd = &mVisibleGroups[0]; - - mAlphaGroupsSize = 0; - mAlphaGroupsEnd = &mAlphaGroups[0]; - - mRiggedAlphaGroupsSize = 0; - mRiggedAlphaGroupsEnd = &mRiggedAlphaGroups[0]; - - mOcclusionGroupsSize = 0; - mOcclusionGroupsEnd = &mOcclusionGroups[0]; - - mDrawableGroupsSize = 0; - mDrawableGroupsEnd = &mDrawableGroups[0]; - - mVisibleListSize = 0; - mVisibleListEnd = &mVisibleList[0]; - - mVisibleBridgeSize = 0; - mVisibleBridgeEnd = &mVisibleBridge[0]; - - - for (U32 i = 0; i < LLRenderPass::NUM_RENDER_TYPES; i++) - { - for (U32 j = 0; j < mRenderMapSize[i]; j++) - { - mRenderMap[i][j] = 0; - } - mRenderMapSize[i] = 0; - mRenderMapEnd[i] = &(mRenderMap[i][0]); - } -} - -LLCullResult::sg_iterator LLCullResult::beginVisibleGroups() -{ - return &mVisibleGroups[0]; -} - -LLCullResult::sg_iterator LLCullResult::endVisibleGroups() -{ - return mVisibleGroupsEnd; -} - -LLCullResult::sg_iterator LLCullResult::beginAlphaGroups() -{ - return &mAlphaGroups[0]; -} - -LLCullResult::sg_iterator LLCullResult::endAlphaGroups() -{ - return mAlphaGroupsEnd; -} - -LLCullResult::sg_iterator LLCullResult::beginRiggedAlphaGroups() -{ - return &mRiggedAlphaGroups[0]; -} - -LLCullResult::sg_iterator LLCullResult::endRiggedAlphaGroups() -{ - return mRiggedAlphaGroupsEnd; -} - -LLCullResult::sg_iterator LLCullResult::beginOcclusionGroups() -{ - return &mOcclusionGroups[0]; -} - -LLCullResult::sg_iterator LLCullResult::endOcclusionGroups() -{ - return mOcclusionGroupsEnd; -} - -LLCullResult::sg_iterator LLCullResult::beginDrawableGroups() -{ - return &mDrawableGroups[0]; -} - -LLCullResult::sg_iterator LLCullResult::endDrawableGroups() -{ - return mDrawableGroupsEnd; -} - -LLCullResult::drawable_iterator LLCullResult::beginVisibleList() -{ - return &mVisibleList[0]; -} - -LLCullResult::drawable_iterator LLCullResult::endVisibleList() -{ - return mVisibleListEnd; -} - -LLCullResult::bridge_iterator LLCullResult::beginVisibleBridge() -{ - return &mVisibleBridge[0]; -} - -LLCullResult::bridge_iterator LLCullResult::endVisibleBridge() -{ - return mVisibleBridgeEnd; -} - -LLCullResult::drawinfo_iterator LLCullResult::beginRenderMap(U32 type) -{ - return &mRenderMap[type][0]; -} - -LLCullResult::drawinfo_iterator LLCullResult::endRenderMap(U32 type) -{ - return mRenderMapEnd[type]; -} - -void LLCullResult::pushVisibleGroup(LLSpatialGroup* group) -{ - if (mVisibleGroupsSize < mVisibleGroupsAllocated) - { - mVisibleGroups[mVisibleGroupsSize] = group; - } - else - { - pushBack(mVisibleGroups, mVisibleGroupsAllocated, group); - } - ++mVisibleGroupsSize; - mVisibleGroupsEnd = &mVisibleGroups[mVisibleGroupsSize]; -} - -void LLCullResult::pushAlphaGroup(LLSpatialGroup* group) -{ - if (mAlphaGroupsSize < mAlphaGroupsAllocated) - { - mAlphaGroups[mAlphaGroupsSize] = group; - } - else - { - pushBack(mAlphaGroups, mAlphaGroupsAllocated, group); - } - ++mAlphaGroupsSize; - mAlphaGroupsEnd = &mAlphaGroups[mAlphaGroupsSize]; -} - -void LLCullResult::pushRiggedAlphaGroup(LLSpatialGroup* group) -{ - if (mRiggedAlphaGroupsSize < mRiggedAlphaGroupsAllocated) - { - mRiggedAlphaGroups[mRiggedAlphaGroupsSize] = group; - } - else - { - pushBack(mRiggedAlphaGroups, mRiggedAlphaGroupsAllocated, group); - } - ++mRiggedAlphaGroupsSize; - mRiggedAlphaGroupsEnd = &mRiggedAlphaGroups[mRiggedAlphaGroupsSize]; -} - -void LLCullResult::pushOcclusionGroup(LLSpatialGroup* group) -{ - if (mOcclusionGroupsSize < mOcclusionGroupsAllocated) - { - mOcclusionGroups[mOcclusionGroupsSize] = group; - } - else - { - pushBack(mOcclusionGroups, mOcclusionGroupsAllocated, group); - } - ++mOcclusionGroupsSize; - mOcclusionGroupsEnd = &mOcclusionGroups[mOcclusionGroupsSize]; -} - -void LLCullResult::pushDrawableGroup(LLSpatialGroup* group) -{ -#if LL_DEBUG_CULL_RESULT - // group must NOT be in the drawble groups list already - llassert(std::find(&mDrawableGroups[0], mDrawableGroupsEnd, group) == mDrawableGroupsEnd); -#endif - if (mDrawableGroupsSize < mDrawableGroupsAllocated) - { - mDrawableGroups[mDrawableGroupsSize] = group; - } - else - { - pushBack(mDrawableGroups, mDrawableGroupsAllocated, group); - } - ++mDrawableGroupsSize; - mDrawableGroupsEnd = &mDrawableGroups[mDrawableGroupsSize]; -} - -void LLCullResult::pushDrawable(LLDrawable* drawable) -{ -#if LL_DEBUG_CULL_RESULT - // drawable must NOT be in the visible list already - llassert(std::find(&mVisibleList[0], mVisibleListEnd, drawable) == mVisibleListEnd); -#endif - if (mVisibleListSize < mVisibleListAllocated) - { - mVisibleList[mVisibleListSize] = drawable; - } - else - { - pushBack(mVisibleList, mVisibleListAllocated, drawable); - } - ++mVisibleListSize; - mVisibleListEnd = &mVisibleList[mVisibleListSize]; -} - -void LLCullResult::pushBridge(LLSpatialBridge* bridge) -{ - if (mVisibleBridgeSize < mVisibleBridgeAllocated) - { - mVisibleBridge[mVisibleBridgeSize] = bridge; - } - else - { - pushBack(mVisibleBridge, mVisibleBridgeAllocated, bridge); - } - ++mVisibleBridgeSize; - mVisibleBridgeEnd = &mVisibleBridge[mVisibleBridgeSize]; -} - -void LLCullResult::pushDrawInfo(U32 type, LLDrawInfo* draw_info) -{ - if (mRenderMapSize[type] < mRenderMapAllocated[type]) - { - mRenderMap[type][mRenderMapSize[type]] = draw_info; - } - else - { - pushBack(mRenderMap[type], mRenderMapAllocated[type], draw_info); - } - ++mRenderMapSize[type]; - mRenderMapEnd[type] = &(mRenderMap[type][mRenderMapSize[type]]); -} - - -void LLCullResult::assertDrawMapsEmpty() -{ - for (U32 i = 0; i < LLRenderPass::NUM_RENDER_TYPES; i++) - { - if (mRenderMapSize[i] != 0) - { - LL_ERRS() << "Stale LLDrawInfo's in LLCullResult!" - << " (mRenderMapSize[" << i << "] = " << mRenderMapSize[i] << ")" << LL_ENDL; - } - } -} - +/** + * @file llspatialpartition.cpp + * @brief LLSpatialGroup class implementation and supporting functions + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llspatialpartition.h" + +#include "llappviewer.h" +#include "llcallstack.h" +#include "lltexturecache.h" +#include "lltexturefetch.h" +#include "llimageworker.h" +#include "llviewerwindow.h" +#include "llviewerobjectlist.h" +#include "llvovolume.h" +#include "llvolume.h" +#include "llvolumeoctree.h" +#include "llviewercamera.h" +#include "llface.h" +#include "llfloatertools.h" +#include "llviewercontrol.h" +#include "llviewerregion.h" +#include "llcamera.h" +#include "pipeline.h" +#include "llmeshrepository.h" +#include "llrender.h" +#include "lloctree.h" +#include "llphysicsshapebuilderutil.h" +#include "llvoavatar.h" +#include "llvolumemgr.h" +#include "llviewershadermgr.h" +#include "llcontrolavatar.h" + +extern bool gShiftFrame; + +static U32 sZombieGroups = 0; +U32 LLSpatialGroup::sNodeCount = 0; + +bool LLSpatialGroup::sNoDelete = false; + +static F32 sLastMaxTexPriority = 1.f; +static F32 sCurMaxTexPriority = 1.f; + +// enable expensive sanity checks around redundant drawable and group insertion to LLCullResult +#define LL_DEBUG_CULL_RESULT 0 + +//static counter for frame to switch LOD on + +void sg_assert(bool expr) +{ +#if LL_OCTREE_PARANOIA_CHECK + if (!expr) + { + LL_ERRS() << "Octree invalid!" << LL_ENDL; + } +#endif +} + +//returns: +// 0 if sphere and AABB are not intersecting +// 1 if they are +// 2 if AABB is entirely inside sphere + +S32 LLSphereAABB(const LLVector3& center, const LLVector3& size, const LLVector3& pos, const F32 &rad) +{ + S32 ret = 2; + + LLVector3 min = center - size; + LLVector3 max = center + size; + for (U32 i = 0; i < 3; i++) + { + if (min.mV[i] > pos.mV[i] + rad || + max.mV[i] < pos.mV[i] - rad) + { //totally outside + return 0; + } + + if (min.mV[i] < pos.mV[i] - rad || + max.mV[i] > pos.mV[i] + rad) + { //intersecting + ret = 1; + } + } + + return ret; +} + +LLSpatialGroup::~LLSpatialGroup() +{ + /*if (sNoDelete) + { + LL_ERRS() << "Illegal deletion of LLSpatialGroup!" << LL_ENDL; + }*/ + + if (gDebugGL) + { + gPipeline.checkReferences(this); + } + + if (hasState(DEAD)) + { + sZombieGroups--; + } + + sNodeCount--; + + clearDrawMap(); +} + +void LLSpatialGroup::clearDrawMap() +{ + mDrawMap.clear(); +} + +bool LLSpatialGroup::isHUDGroup() +{ + return getSpatialPartition() && getSpatialPartition()->isHUDPartition() ; +} + +void LLSpatialGroup::validate() +{ + ll_assert_aligned(this,64); +#if LL_OCTREE_PARANOIA_CHECK + + sg_assert(!isState(DIRTY)); + sg_assert(!isDead()); + + LLVector4a myMin; + myMin.setSub(mBounds[0], mBounds[1]); + LLVector4a myMax; + myMax.setAdd(mBounds[0], mBounds[1]); + + validateDrawMap(); + + for (element_iter i = getDataBegin(); i != getDataEnd(); ++i) + { + LLDrawable* drawable = *i; + sg_assert(drawable->getSpatialGroup() == this); + if (drawable->getSpatialBridge()) + { + sg_assert(drawable->getSpatialBridge() == getSpatialPartition()->asBridge()); + } + + /*if (drawable->isSpatialBridge()) + { + LLSpatialPartition* part = drawable->asPartition(); + if (!part) + { + LL_ERRS() << "Drawable reports it is a spatial bridge but not a partition." << LL_ENDL; + } + LLSpatialGroup* group = (LLSpatialGroup*) part->mOctree->getListener(0); + group->validate(); + }*/ + } + + for (U32 i = 0; i < mOctreeNode->getChildCount(); ++i) + { + LLSpatialGroup* group = (LLSpatialGroup*) mOctreeNode->getChild(i)->getListener(0); + + group->validate(); + + //ensure all children are enclosed in this node + LLVector4a center = group->mBounds[0]; + LLVector4a size = group->mBounds[1]; + + LLVector4a min; + min.setSub(center, size); + LLVector4a max; + max.setAdd(center, size); + + for (U32 j = 0; j < 3; j++) + { + sg_assert(min[j] >= myMin[j]-0.02f); + sg_assert(max[j] <= myMax[j]+0.02f); + } + } + +#endif +} + +void LLSpatialGroup::validateDrawMap() +{ +#if LL_OCTREE_PARANOIA_CHECK + for (draw_map_t::iterator i = mDrawMap.begin(); i != mDrawMap.end(); ++i) + { + LLSpatialGroup::drawmap_elem_t& draw_vec = i->second; + for (drawmap_elem_t::iterator j = draw_vec.begin(); j != draw_vec.end(); ++j) + { + LLDrawInfo& params = **j; + + params.validate(); + } + } +#endif +} + +bool LLSpatialGroup::updateInGroup(LLDrawable *drawablep, bool immediate) +{ + LL_PROFILE_ZONE_SCOPED; + drawablep->updateSpatialExtents(); + + OctreeNode* parent = mOctreeNode->getOctParent(); + + if (mOctreeNode->isInside(drawablep->getPositionGroup()) && + (mOctreeNode->contains(drawablep->getEntry()) || + (drawablep->getBinRadius() > mOctreeNode->getSize()[0] && + parent && parent->getElementCount() >= gOctreeMaxCapacity))) + { + unbound(); + setState(OBJECT_DIRTY); + //setState(GEOM_DIRTY); + return true; + } + + return false; +} + +void LLSpatialGroup::expandExtents(const LLVector4a* addingExtents, const LLXformMatrix& currentTransform) +{ + // Get coordinates of the adding extents + const LLVector4a& min = addingExtents[0]; + const LLVector4a& max = addingExtents[1]; + + // Get coordinates of all corners of the bounding box + LLVector3 corners[] = + { + LLVector3(min[0], min[1], min[2]), + LLVector3(min[0], min[1], max[2]), + LLVector3(min[0], max[1], min[2]), + LLVector3(min[0], max[1], max[2]), + LLVector3(max[0], min[1], min[2]), + LLVector3(max[0], min[1], max[2]), + LLVector3(max[0], max[1], min[2]), + LLVector3(max[0], max[1], max[2]) + }; + + // New extents (to be expanded) + LLVector3 extents[] = + { + LLVector3(mExtents[0].getF32ptr()), + LLVector3(mExtents[1].getF32ptr()) + }; + + LLQuaternion backwardRotation = ~currentTransform.getWorldRotation(); + for (LLVector3& corner : corners) + { + // Make coordinates relative to the current position + corner -= currentTransform.getWorldPosition(); + // Rotate coordinates backward to the current rotation + corner.rotVec(backwardRotation); + // Expand root extents on the current corner + for (int j = 0; j < 3; ++j) + { + if (corner[j] < extents[0][j]) + extents[0][j] = corner[j]; + if (corner[j] > extents[1][j]) + extents[1][j] = corner[j]; + } + } + + // Set new expanded extents + mExtents[0].load3(extents[0].mV); + mExtents[1].load3(extents[1].mV); + + // Calculate new center and size + mBounds[0].setAdd(mExtents[0], mExtents[1]); + mBounds[0].mul(0.5f); + mBounds[1].setSub(mExtents[0], mExtents[1]); + mBounds[1].mul(0.5f); +} + +bool LLSpatialGroup::addObject(LLDrawable *drawablep) +{ + if(!drawablep) + { + return false; + } + { + drawablep->setGroup(this); + setState(OBJECT_DIRTY | GEOM_DIRTY); + setOcclusionState(LLSpatialGroup::DISCARD_QUERY, LLSpatialGroup::STATE_MODE_ALL_CAMERAS); + gPipeline.markRebuild(this); + if (drawablep->isSpatialBridge()) + { + mBridgeList.push_back((LLSpatialBridge*) drawablep); + } + if (drawablep->getRadius() > 1.f) + { + setState(IMAGE_DIRTY); + } + } + + return true; +} + +void LLSpatialGroup::rebuildGeom() +{ + if (!isDead()) + { + getSpatialPartition()->rebuildGeom(this); + + if (hasState(LLSpatialGroup::MESH_DIRTY)) + { + gPipeline.markMeshDirty(this); + } + } +} + +void LLSpatialGroup::rebuildMesh() +{ + if (!isDead()) + { + getSpatialPartition()->rebuildMesh(this); + } +} + +void LLSpatialPartition::rebuildGeom(LLSpatialGroup* group) +{ + if (group->isDead() || !group->hasState(LLSpatialGroup::GEOM_DIRTY)) + { + return; + } + + if (group->changeLOD()) + { + group->mLastUpdateDistance = group->mDistance; + group->mLastUpdateViewAngle = group->mViewAngle; + } + + LL_PROFILE_ZONE_SCOPED_CATEGORY_SPATIAL; + + group->clearDrawMap(); + + //get geometry count + U32 index_count = 0; + U32 vertex_count = 0; + + addGeometryCount(group, vertex_count, index_count); + + if (vertex_count > 0 && index_count > 0) + { //create vertex buffer containing volume geometry for this node + { + group->mBuilt = 1.f; + if (group->mVertexBuffer.isNull() || + group->mVertexBuffer->getNumVerts() != vertex_count || + group->mVertexBuffer->getNumVerts() != index_count) + { + group->mVertexBuffer = new LLVertexBuffer(mVertexDataMask); + if (!group->mVertexBuffer->allocateBuffer(vertex_count, index_count)) + { + LL_WARNS() << "Failed to allocate Vertex Buffer on rebuild to " + << vertex_count << " vertices and " + << index_count << " indices" << LL_ENDL; + group->mVertexBuffer = NULL; + group->mBufferMap.clear(); + } + } + } + + if (group->mVertexBuffer) + { + getGeometry(group); + } + } + else + { + group->mVertexBuffer = NULL; + group->mBufferMap.clear(); + } + + group->mLastUpdateTime = gFrameTimeSeconds; + group->clearState(LLSpatialGroup::GEOM_DIRTY); +} + + +void LLSpatialPartition::rebuildMesh(LLSpatialGroup* group) +{ + +} + +LLSpatialGroup* LLSpatialGroup::getParent() +{ + return (LLSpatialGroup*)LLViewerOctreeGroup::getParent(); + } + +bool LLSpatialGroup::removeObject(LLDrawable *drawablep, bool from_octree) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_SPATIAL + + if(!drawablep) + { + return false; + } + + unbound(); + if (mOctreeNode && !from_octree) + { + drawablep->setGroup(NULL); + } + else + { + drawablep->setGroup(NULL); + setState(GEOM_DIRTY); + gPipeline.markRebuild(this); + + if (drawablep->isSpatialBridge()) + { + for (bridge_list_t::iterator i = mBridgeList.begin(); i != mBridgeList.end(); ++i) + { + if (*i == drawablep) + { + mBridgeList.erase(i); + break; + } + } + } + + if (getElementCount() == 0) + { //delete draw map on last element removal since a rebuild might never happen + clearDrawMap(); + } + } + return true; +} + +void LLSpatialGroup::shift(const LLVector4a &offset) +{ + LLVector4a t = mOctreeNode->getCenter(); + t.add(offset); + mOctreeNode->setCenter(t); + mOctreeNode->updateMinMax(); + mBounds[0].add(offset); + mExtents[0].add(offset); + mExtents[1].add(offset); + mObjectBounds[0].add(offset); + mObjectExtents[0].add(offset); + mObjectExtents[1].add(offset); + + if (!getSpatialPartition()->mRenderByGroup && + getSpatialPartition()->mPartitionType != LLViewerRegion::PARTITION_TREE && + getSpatialPartition()->mPartitionType != LLViewerRegion::PARTITION_TERRAIN && + getSpatialPartition()->mPartitionType != LLViewerRegion::PARTITION_BRIDGE && + getSpatialPartition()->mPartitionType != LLViewerRegion::PARTITION_AVATAR && + getSpatialPartition()->mPartitionType != LLViewerRegion::PARTITION_CONTROL_AV) + { + setState(GEOM_DIRTY); + gPipeline.markRebuild(this); + } +} + +class LLSpatialSetState : public OctreeTraveler +{ +public: + U32 mState; + LLSpatialSetState(U32 state) : mState(state) { } + virtual void visit(const OctreeNode* branch) { ((LLSpatialGroup*) branch->getListener(0))->setState(mState); } +}; + +class LLSpatialSetStateDiff : public LLSpatialSetState +{ +public: + LLSpatialSetStateDiff(U32 state) : LLSpatialSetState(state) { } + + virtual void traverse(const OctreeNode* n) + { + LLSpatialGroup* group = (LLSpatialGroup*) n->getListener(0); + + if (!group->hasState(mState)) + { + OctreeTraveler::traverse(n); + } + } +}; + +void LLSpatialGroup::setState(U32 state, S32 mode) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_SPATIAL + + llassert(state <= LLSpatialGroup::STATE_MASK); + + if (mode > STATE_MODE_SINGLE) + { + if (mode == STATE_MODE_DIFF) + { + LLSpatialSetStateDiff setter(state); + setter.traverse(mOctreeNode); + } + else + { + LLSpatialSetState setter(state); + setter.traverse(mOctreeNode); + } + } + else + { + mState |= state; + } +} + +class LLSpatialClearState : public OctreeTraveler +{ +public: + U32 mState; + LLSpatialClearState(U32 state) : mState(state) { } + virtual void visit(const OctreeNode* branch) { ((LLSpatialGroup*) branch->getListener(0))->clearState(mState); } +}; + +class LLSpatialClearStateDiff : public LLSpatialClearState +{ +public: + LLSpatialClearStateDiff(U32 state) : LLSpatialClearState(state) { } + + virtual void traverse(const OctreeNode* n) + { + LLSpatialGroup* group = (LLSpatialGroup*) n->getListener(0); + + if (group->hasState(mState)) + { + OctreeTraveler::traverse(n); + } + } +}; + +void LLSpatialGroup::clearState(U32 state, S32 mode) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_SPATIAL + + llassert(state <= LLSpatialGroup::STATE_MASK); + + if (mode > STATE_MODE_SINGLE) + { + if (mode == STATE_MODE_DIFF) + { + LLSpatialClearStateDiff clearer(state); + clearer.traverse(mOctreeNode); + } + else + { + LLSpatialClearState clearer(state); + clearer.traverse(mOctreeNode); + } + } + else + { + mState &= ~state; + } +} + +//====================================== +// Octree Listener Implementation +//====================================== + +LLSpatialGroup::LLSpatialGroup(OctreeNode* node, LLSpatialPartition* part) : LLOcclusionCullingGroup(node, part), + mObjectBoxSize(1.f), + mGeometryBytes(0), + mSurfaceArea(0.f), + mBuilt(0.f), + mVertexBuffer(NULL), + mDistance(0.f), + mDepth(0.f), + mLastUpdateDistance(-1.f), + mLastUpdateTime(gFrameTimeSeconds) +{ + ll_assert_aligned(this,16); + + sNodeCount++; + + mViewAngle.splat(0.f); + mLastUpdateViewAngle.splat(-1.f); + + sg_assert(mOctreeNode->getListenerCount() == 0); + setState(SG_INITIAL_STATE_MASK); + gPipeline.markRebuild(this); + + // let the reflection map manager know about this spatial group + mReflectionProbe = gPipeline.mReflectionMapManager.registerSpatialGroup(this); + + mRadius = 1; + mPixelArea = 1024.f; +} + +void LLSpatialGroup::updateDistance(LLCamera &camera) +{ + if (LLViewerCamera::sCurCameraID != LLViewerCamera::CAMERA_WORLD) + { + LL_WARNS() << "Attempted to update distance for camera other than world camera!" << LL_ENDL; + llassert(false); + return; + } + + if (gShiftFrame) + { + return; + } + +#if !LL_RELEASE_FOR_DOWNLOAD + if (hasState(LLSpatialGroup::OBJECT_DIRTY)) + { + LL_ERRS() << "Spatial group dirty on distance update." << LL_ENDL; + } +#endif + if (!isEmpty()) + { + mRadius = getSpatialPartition()->mRenderByGroup ? mObjectBounds[1].getLength3().getF32() : + (F32) mOctreeNode->getSize().getLength3().getF32(); + mDistance = getSpatialPartition()->calcDistance(this, camera); + mPixelArea = getSpatialPartition()->calcPixelArea(this, camera); + } +} + +F32 LLSpatialPartition::calcDistance(LLSpatialGroup* group, LLCamera& camera) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_SPATIAL + + LLVector4a eye; + LLVector4a origin; + origin.load3(camera.getOrigin().mV); + + eye.setSub(group->mObjectBounds[0], origin); + + F32 dist = 0.f; + + if (group->mDrawMap.find(LLRenderPass::PASS_ALPHA) != group->mDrawMap.end()) + { + LLVector4a v = eye; + + dist = eye.getLength3().getF32(); + eye.normalize3fast(); + + if (!group->hasState(LLSpatialGroup::ALPHA_DIRTY)) + { + if (!group->getSpatialPartition()->isBridge()) + { + LLVector4a view_angle = eye; + + LLVector4a diff; + diff.setSub(view_angle, group->mLastUpdateViewAngle); + + if (diff.getLength3().getF32() > 0.64f) + { + group->mViewAngle = view_angle; + group->mLastUpdateViewAngle = view_angle; + //for occasional alpha sorting within the group + //NOTE: If there is a trivial way to detect that alpha sorting here would not change the render order, + //not setting this node to dirty would be a very good thing + group->setState(LLSpatialGroup::ALPHA_DIRTY); + gPipeline.markRebuild(group); + } + } + } + + //calculate depth of node for alpha sorting + + LLVector3 at = camera.getAtAxis(); + + LLVector4a ata; + ata.load3(at.mV); + + LLVector4a t = ata; + //front of bounding box + t.mul(0.25f); + t.mul(group->mObjectBounds[1]); + v.sub(t); + + group->mDepth = v.dot3(ata).getF32(); + } + else + { + dist = eye.getLength3().getF32(); + } + +#if !LL_RELEASE + LL_DEBUGS("RiggedBox") << "calcDistance, group " << group << " camera " << origin << " obj bounds " + << group->mObjectBounds[0] << ", " << group->mObjectBounds[1] + << " dist " << dist << " radius " << group->mRadius << LL_ENDL; +#endif + + if (dist < 16.f) + { + dist /= 16.f; + dist *= dist; + dist *= 16.f; + } + + return dist; +} + +F32 LLSpatialPartition::calcPixelArea(LLSpatialGroup* group, LLCamera& camera) +{ + return LLPipeline::calcPixelArea(group->mObjectBounds[0], group->mObjectBounds[1], camera); +} + +F32 LLSpatialGroup::getUpdateUrgency() const +{ + if (!isVisible()) + { + return 0.f; + } + else + { + F32 time = gFrameTimeSeconds-mLastUpdateTime+4.f; + return time + (mObjectBounds[1].dot3(mObjectBounds[1]).getF32()+1.f)/mDistance; + } +} + +bool LLSpatialGroup::changeLOD() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_SPATIAL + + if (hasState(ALPHA_DIRTY | OBJECT_DIRTY)) + { + //a rebuild is going to happen, update distance and LoD + return true; + } + + if (getSpatialPartition()->mSlopRatio > 0.f) + { + F32 ratio = (mDistance - mLastUpdateDistance)/(llmax(mLastUpdateDistance, mRadius)); + + // MAINT-8264 - this check is not robust if it needs to work + // for bounding boxes much larger than the actual enclosed + // objects, and using distance to box center is also + // problematic. Consider the case that you have a large box + // where the enclosed object is in one corner. As you zoom in + // on the corner, the object gets much closer to the camera, + // but the distance to the box center changes very little, and + // an LOD change will not trigger, so object LOD gets "stuck" + // at a too-low value. In the case of the above JIRA, the box + // was large only due to another error, so this logic did not + // need to be changed. + + if (fabsf(ratio) >= getSpatialPartition()->mSlopRatio) + { + LL_DEBUGS("RiggedBox") << "changeLOD true because of ratio compare " + << fabsf(ratio) << " " << getSpatialPartition()->mSlopRatio << LL_ENDL; + LL_DEBUGS("RiggedBox") << "sg " << this << "\nmDistance " << mDistance + << " mLastUpdateDistance " << mLastUpdateDistance + << " mRadius " << mRadius + << " fab ratio " << fabsf(ratio) + << " slop " << getSpatialPartition()->mSlopRatio << LL_ENDL; + + return true; + } + } + + if (needsUpdate()) + { + return true; + } + + return false; +} + +void LLSpatialGroup::handleInsertion(const TreeNode* node, LLViewerOctreeEntry* entry) +{ + addObject((LLDrawable*)entry->getDrawable()); + unbound(); + setState(OBJECT_DIRTY); +} + +void LLSpatialGroup::handleRemoval(const TreeNode* node, LLViewerOctreeEntry* entry) +{ + removeObject((LLDrawable*)entry->getDrawable(), true); + LLViewerOctreeGroup::handleRemoval(node, entry); +} + +void LLSpatialGroup::handleDestruction(const TreeNode* node) +{ + if(isDead()) + { + return; + } + setState(DEAD); + + for (element_iter i = getDataBegin(); i != getDataEnd(); ++i) + { + LLViewerOctreeEntry* entry = *i; + + if (entry->getGroup() == this) + { + if(entry->hasDrawable()) + { + ((LLDrawable*)entry->getDrawable())->setGroup(NULL); + } + } + } + + clearDrawMap(); + mVertexBuffer = NULL; + mBufferMap.clear(); + sZombieGroups++; + mOctreeNode = NULL; +} + +void LLSpatialGroup::handleChildAddition(const OctreeNode* parent, OctreeNode* child) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_SPATIAL + + if (child->getListenerCount() == 0) + { + new LLSpatialGroup(child, getSpatialPartition()); + } + else + { + OCT_ERRS << "LLSpatialGroup redundancy detected." << LL_ENDL; + } + + unbound(); + + assert_states_valid(this); +} + +//virtual +void LLSpatialGroup::rebound() +{ + if (!isDirty()) + return; + + super::rebound(); + + if (mSpatialPartition->mDrawableType == LLPipeline::RENDER_TYPE_CONTROL_AV) + { + llassert(mSpatialPartition->mPartitionType == LLViewerRegion::PARTITION_CONTROL_AV); + + LLSpatialBridge* bridge = getSpatialPartition()->asBridge(); + if (bridge && + bridge->mDrawable && + bridge->mDrawable->getVObj() && + bridge->mDrawable->getVObj()->isRoot()) + { + LLControlAvatar* controlAvatar = bridge->mDrawable->getVObj()->getControlAvatar(); + if (controlAvatar && + controlAvatar->mDrawable && + controlAvatar->mControlAVBridge && + controlAvatar->mControlAVBridge->mOctree) + { + LLSpatialGroup* root = (LLSpatialGroup*)controlAvatar->mControlAVBridge->mOctree->getListener(0); + if (this == root) + { + const LLVector4a* addingExtents = controlAvatar->mDrawable->getSpatialExtents(); + const LLXformMatrix* currentTransform = bridge->mDrawable->getXform(); + expandExtents(addingExtents, *currentTransform); + } + } + } + } +} + +void LLSpatialGroup::destroyGLState(bool keep_occlusion) +{ + setState(LLSpatialGroup::GEOM_DIRTY | LLSpatialGroup::IMAGE_DIRTY); + + if (!keep_occlusion) + { //going to need a rebuild + gPipeline.markRebuild(this); + } + + mLastUpdateTime = gFrameTimeSeconds; + mVertexBuffer = NULL; + mBufferMap.clear(); + + clearDrawMap(); + + if (!keep_occlusion) + { + releaseOcclusionQueryObjectNames(); + } + + + for (LLSpatialGroup::element_iter i = getDataBegin(); i != getDataEnd(); ++i) + { + LLDrawable* drawable = (LLDrawable*)(*i)->getDrawable(); + if(!drawable) + { + continue; + } + for (S32 j = 0; j < drawable->getNumFaces(); j++) + { + LLFace* facep = drawable->getFace(j); + if (facep) + { + facep->clearVertexBuffer(); + } + } + } +} + +//============================================== + +LLSpatialPartition::LLSpatialPartition(U32 data_mask, bool render_by_group, LLViewerRegion* regionp) +: mRenderByGroup(render_by_group), mBridge(NULL) +{ + mRegionp = regionp; + mPartitionType = LLViewerRegion::PARTITION_NONE; + mVertexDataMask = data_mask; + mDepthMask = false; + mSlopRatio = 0.25f; + mInfiniteFarClip = false; + + new LLSpatialGroup(mOctree, this); +} + + +LLSpatialPartition::~LLSpatialPartition() +{ + cleanup(); +} + +LLSpatialGroup *LLSpatialPartition::put(LLDrawable *drawablep, bool was_visible) +{ + LL_PROFILE_ZONE_SCOPED; + drawablep->updateSpatialExtents(); + + //keep drawable from being garbage collected + LLPointer ptr = drawablep; + + if(!drawablep->getGroup()) + { + assert_octree_valid(mOctree); + mOctree->insert(drawablep->getEntry()); + assert_octree_valid(mOctree); + } + + LLSpatialGroup* group = drawablep->getSpatialGroup(); + //llassert(group != NULL); + + if (group && was_visible && group->isOcclusionState(LLSpatialGroup::QUERY_PENDING)) + { + group->setOcclusionState(LLSpatialGroup::DISCARD_QUERY, LLSpatialGroup::STATE_MODE_ALL_CAMERAS); + } + + return group; +} + +bool LLSpatialPartition::remove(LLDrawable *drawablep, LLSpatialGroup *curp) +{ + LL_PROFILE_ZONE_SCOPED; + if (!curp->removeObject(drawablep)) + { + OCT_ERRS << "Failed to remove drawable from octree!" << LL_ENDL; + } + else + { + drawablep->setGroup(NULL); + } + + assert_octree_valid(mOctree); + + return true; +} + +void LLSpatialPartition::move(LLDrawable *drawablep, LLSpatialGroup *curp, bool immediate) +{ + LL_PROFILE_ZONE_SCOPED; + // sanity check submitted by open source user bushing Spatula + // who was seeing crashing here. (See VWR-424 reported by Bunny Mayne) + if (!drawablep) + { + OCT_ERRS << "LLSpatialPartition::move was passed a bad drawable." << LL_ENDL; + return; + } + + bool was_visible = curp ? curp->isVisible() : false; + + if (curp && curp->getSpatialPartition() != this) + { + //keep drawable from being garbage collected + LLPointer ptr = drawablep; + if (curp->getSpatialPartition()->remove(drawablep, curp)) + { + put(drawablep, was_visible); + return; + } + else + { + OCT_ERRS << "Drawable lost between spatial partitions on outbound transition." << LL_ENDL; + } + } + + if (curp && curp->updateInGroup(drawablep, immediate)) + { + // Already updated, don't need to do anything + assert_octree_valid(mOctree); + return; + } + + //keep drawable from being garbage collected + LLPointer ptr = drawablep; + if (curp && !remove(drawablep, curp)) + { + OCT_ERRS << "Move couldn't find existing spatial group!" << LL_ENDL; + } + + put(drawablep, was_visible); +} + +class LLSpatialShift : public OctreeTraveler +{ +public: + const LLVector4a& mOffset; + + LLSpatialShift(const LLVector4a& offset) : mOffset(offset) { } + virtual void visit(const OctreeNode* branch) + { + ((LLSpatialGroup*) branch->getListener(0))->shift(mOffset); + } +}; + +void LLSpatialPartition::shift(const LLVector4a &offset) +{ //shift octree node bounding boxes by offset + LLSpatialShift shifter(offset); + shifter.traverse(mOctree); +} + +class LLOctreeCull : public LLViewerOctreeCull +{ +public: + LLOctreeCull(LLCamera* camera) : LLViewerOctreeCull(camera) {} + + virtual bool earlyFail(LLViewerOctreeGroup* base_group) + { + if (LLPipeline::sReflectionRender) + { + return false; + } + + LLSpatialGroup* group = (LLSpatialGroup*)base_group; + group->checkOcclusion(); + + if (group->getOctreeNode()->getParent() && //never occlusion cull the root node + LLPipeline::sUseOcclusion && //ignore occlusion if disabled + group->isOcclusionState(LLSpatialGroup::OCCLUDED)) + { + gPipeline.markOccluder(group); + return true; + } + + return false; + } + + virtual S32 frustumCheck(const LLViewerOctreeGroup* group) + { + S32 res = AABBInFrustumNoFarClipGroupBounds(group); + if (res != 0) + { + res = llmin(res, AABBSphereIntersectGroupExtents(group)); + } + return res; + } + + virtual S32 frustumCheckObjects(const LLViewerOctreeGroup* group) + { + S32 res = AABBInFrustumNoFarClipObjectBounds(group); + if (res != 0) + { + res = llmin(res, AABBSphereIntersectObjectExtents(group)); + } + return res; + } + + virtual void processGroup(LLViewerOctreeGroup* base_group) + { + LLSpatialGroup* group = (LLSpatialGroup*)base_group; + /*if (group->needsUpdate() || + group->getVisible(LLViewerCamera::sCurCameraID) < LLDrawable::getCurrentFrame() - 1) + { + group->doOcclusion(mCamera); + }*/ + gPipeline.markNotCulled(group, *mCamera); + } +}; + +class LLOctreeCullNoFarClip : public LLOctreeCull +{ +public: + LLOctreeCullNoFarClip(LLCamera* camera) + : LLOctreeCull(camera) { } + + virtual S32 frustumCheck(const LLViewerOctreeGroup* group) + { + return AABBInFrustumNoFarClipGroupBounds(group); + } + + virtual S32 frustumCheckObjects(const LLViewerOctreeGroup* group) + { + S32 res = AABBInFrustumNoFarClipObjectBounds(group); + return res; + } +}; + +class LLOctreeCullShadow : public LLOctreeCull +{ +public: + LLOctreeCullShadow(LLCamera* camera) + : LLOctreeCull(camera) { } + + virtual S32 frustumCheck(const LLViewerOctreeGroup* group) + { + return AABBInFrustumGroupBounds(group); + } + + virtual S32 frustumCheckObjects(const LLViewerOctreeGroup* group) + { + return AABBInFrustumObjectBounds(group); + } +}; + +class LLOctreeCullVisExtents: public LLOctreeCullShadow +{ +public: + LLOctreeCullVisExtents(LLCamera* camera, LLVector4a& min, LLVector4a& max) + : LLOctreeCullShadow(camera), mMin(min), mMax(max), mEmpty(true) { } + + virtual bool earlyFail(LLViewerOctreeGroup* base_group) + { + LLSpatialGroup* group = (LLSpatialGroup*)base_group; + + if (group->getOctreeNode()->getParent() && //never occlusion cull the root node + LLPipeline::sUseOcclusion && //ignore occlusion if disabled + group->isOcclusionState(LLSpatialGroup::OCCLUDED)) + { + return true; + } + + return false; + } + + virtual void traverse(const OctreeNode* n) + { + LLSpatialGroup* group = (LLSpatialGroup*) n->getListener(0); + + if (earlyFail(group)) + { + return; + } + + if ((mRes && group->hasState(LLSpatialGroup::SKIP_FRUSTUM_CHECK)) || + mRes == 2) + { //don't need to do frustum check + OctreeTraveler::traverse(n); + } + else + { + mRes = frustumCheck(group); + + if (mRes) + { //at least partially in, run on down + OctreeTraveler::traverse(n); + } + + mRes = 0; + } + } + + virtual void processGroup(LLViewerOctreeGroup* base_group) + { + LLSpatialGroup* group = (LLSpatialGroup*)base_group; + + llassert(!group->hasState(LLSpatialGroup::DIRTY) && !group->isEmpty()); + + if (mRes < 2) + { + if (AABBInFrustumObjectBounds(group) > 0) + { + mEmpty = false; + const LLVector4a* exts = group->getObjectExtents(); + update_min_max(mMin, mMax, exts[0]); + update_min_max(mMin, mMax, exts[1]); + } + } + else + { + mEmpty = false; + const LLVector4a* exts = group->getExtents(); + update_min_max(mMin, mMax, exts[0]); + update_min_max(mMin, mMax, exts[1]); + } + } + + bool mEmpty; + LLVector4a& mMin; + LLVector4a& mMax; +}; + +class LLOctreeCullDetectVisible: public LLOctreeCullShadow +{ +public: + LLOctreeCullDetectVisible(LLCamera* camera) + : LLOctreeCullShadow(camera), mResult(false) { } + + virtual bool earlyFail(LLViewerOctreeGroup* base_group) + { + LLSpatialGroup* group = (LLSpatialGroup*)base_group; + + if (mResult || //already found a node, don't check any more + (group->getOctreeNode()->getParent() && //never occlusion cull the root node + LLPipeline::sUseOcclusion && //ignore occlusion if disabled + group->isOcclusionState(LLSpatialGroup::OCCLUDED))) + { + return true; + } + + return false; + } + + virtual void processGroup(LLViewerOctreeGroup* base_group) + { + if (base_group->isVisible()) + { + mResult = true; + } + } + + bool mResult; +}; + +class LLOctreeSelect : public LLOctreeCull +{ +public: + LLOctreeSelect(LLCamera* camera, std::vector* results) + : LLOctreeCull(camera), mResults(results) { } + + virtual bool earlyFail(LLViewerOctreeGroup* group) { return false; } + virtual void preprocess(LLViewerOctreeGroup* group) { } + + virtual void processGroup(LLViewerOctreeGroup* base_group) + { + LLSpatialGroup* group = (LLSpatialGroup*)base_group; + OctreeNode* branch = group->getOctreeNode(); + + for (OctreeNode::const_element_iter i = branch->getDataBegin(); i != branch->getDataEnd(); ++i) + { + LLDrawable* drawable = (LLDrawable*)(*i)->getDrawable(); + if(!drawable) + { + continue; + } + if (!drawable->isDead()) + { + if (drawable->isSpatialBridge()) + { + drawable->setVisible(*mCamera, mResults, true); + } + else + { + mResults->push_back(drawable); + } + } + } + } + + std::vector* mResults; +}; + +void drawBox(const LLVector3& c, const LLVector3& r) +{ + LLVertexBuffer::unbind(); + + gGL.begin(LLRender::TRIANGLE_STRIP); + //left front + gGL.vertex3fv((c+r.scaledVec(LLVector3(-1,1,-1))).mV); + gGL.vertex3fv((c+r.scaledVec(LLVector3(-1,1,1))).mV); + //right front + gGL.vertex3fv((c+r.scaledVec(LLVector3(1,1,-1))).mV); + gGL.vertex3fv((c+r.scaledVec(LLVector3(1,1,1))).mV); + //right back + gGL.vertex3fv((c+r.scaledVec(LLVector3(1,-1,-1))).mV); + gGL.vertex3fv((c+r.scaledVec(LLVector3(1,-1,1))).mV); + //left back + gGL.vertex3fv((c+r.scaledVec(LLVector3(-1,-1,-1))).mV); + gGL.vertex3fv((c+r.scaledVec(LLVector3(-1,-1,1))).mV); + //left front + gGL.vertex3fv((c+r.scaledVec(LLVector3(-1,1,-1))).mV); + gGL.vertex3fv((c+r.scaledVec(LLVector3(-1,1,1))).mV); + gGL.end(); + + //bottom + gGL.begin(LLRender::TRIANGLE_STRIP); + gGL.vertex3fv((c+r.scaledVec(LLVector3(1,1,-1))).mV); + gGL.vertex3fv((c+r.scaledVec(LLVector3(1,-1,-1))).mV); + gGL.vertex3fv((c+r.scaledVec(LLVector3(-1,1,-1))).mV); + gGL.vertex3fv((c+r.scaledVec(LLVector3(-1,-1,-1))).mV); + gGL.end(); + + //top + gGL.begin(LLRender::TRIANGLE_STRIP); + gGL.vertex3fv((c+r.scaledVec(LLVector3(1,1,1))).mV); + gGL.vertex3fv((c+r.scaledVec(LLVector3(-1,1,1))).mV); + gGL.vertex3fv((c+r.scaledVec(LLVector3(1,-1,1))).mV); + gGL.vertex3fv((c+r.scaledVec(LLVector3(-1,-1,1))).mV); + gGL.end(); +} + +void drawBox(const LLVector4a& c, const LLVector4a& r) +{ + drawBox(reinterpret_cast(c), reinterpret_cast(r)); +} + +void drawBoxOutline(const LLVector3& pos, const LLVector3& size) +{ + if (!pos.isFinite() || !size.isFinite()) + return; + + LLVector3 v1 = size.scaledVec(LLVector3( 1, 1,1)); + LLVector3 v2 = size.scaledVec(LLVector3(-1, 1,1)); + LLVector3 v3 = size.scaledVec(LLVector3(-1,-1,1)); + LLVector3 v4 = size.scaledVec(LLVector3( 1,-1,1)); + + gGL.begin(LLRender::LINES); + + //top + gGL.vertex3fv((pos+v1).mV); + gGL.vertex3fv((pos+v2).mV); + gGL.vertex3fv((pos+v2).mV); + gGL.vertex3fv((pos+v3).mV); + gGL.vertex3fv((pos+v3).mV); + gGL.vertex3fv((pos+v4).mV); + gGL.vertex3fv((pos+v4).mV); + gGL.vertex3fv((pos+v1).mV); + + //bottom + gGL.vertex3fv((pos-v1).mV); + gGL.vertex3fv((pos-v2).mV); + gGL.vertex3fv((pos-v2).mV); + gGL.vertex3fv((pos-v3).mV); + gGL.vertex3fv((pos-v3).mV); + gGL.vertex3fv((pos-v4).mV); + gGL.vertex3fv((pos-v4).mV); + gGL.vertex3fv((pos-v1).mV); + + //right + gGL.vertex3fv((pos+v1).mV); + gGL.vertex3fv((pos-v3).mV); + + gGL.vertex3fv((pos+v4).mV); + gGL.vertex3fv((pos-v2).mV); + + //left + gGL.vertex3fv((pos+v2).mV); + gGL.vertex3fv((pos-v4).mV); + + gGL.vertex3fv((pos+v3).mV); + gGL.vertex3fv((pos-v1).mV); + + gGL.end(); +} + +void drawBoxOutline(const LLVector4a& pos, const LLVector4a& size) +{ + drawBoxOutline(reinterpret_cast(pos), reinterpret_cast(size)); +} + + +void LLSpatialPartition::restoreGL() +{ +} + +bool LLSpatialPartition::getVisibleExtents(LLCamera& camera, LLVector3& visMin, LLVector3& visMax) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_SPATIAL; + LLVector4a visMina, visMaxa; + visMina.load3(visMin.mV); + visMaxa.load3(visMax.mV); + + { + LLSpatialGroup* group = (LLSpatialGroup*) mOctree->getListener(0); + group->rebound(); + } + + LLOctreeCullVisExtents vis(&camera, visMina, visMaxa); + vis.traverse(mOctree); + + visMin.set(visMina.getF32ptr()); + visMax.set(visMaxa.getF32ptr()); + return vis.mEmpty; +} + +bool LLSpatialPartition::visibleObjectsInFrustum(LLCamera& camera) +{ + LLOctreeCullDetectVisible vis(&camera); + vis.traverse(mOctree); + return vis.mResult; +} + +S32 LLSpatialPartition::cull(LLCamera &camera, std::vector* results, bool for_select) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_SPATIAL; +#if LL_OCTREE_PARANOIA_CHECK + ((LLSpatialGroup*)mOctree->getListener(0))->checkStates(); +#endif + { + LLSpatialGroup* group = (LLSpatialGroup*) mOctree->getListener(0); + group->rebound(); + } + +#if LL_OCTREE_PARANOIA_CHECK + ((LLSpatialGroup*)mOctree->getListener(0))->validate(); +#endif + + LLOctreeSelect selecter(&camera, results); + selecter.traverse(mOctree); + + return 0; +} + +extern bool gCubeSnapshot; + +S32 LLSpatialPartition::cull(LLCamera &camera, bool do_occlusion) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_SPATIAL; +#if LL_OCTREE_PARANOIA_CHECK + ((LLSpatialGroup*)mOctree->getListener(0))->checkStates(); +#endif + LLSpatialGroup* group = (LLSpatialGroup*) mOctree->getListener(0); + group->rebound(); + +#if LL_OCTREE_PARANOIA_CHECK + ((LLSpatialGroup*)mOctree->getListener(0))->validate(); +#endif + + if (LLPipeline::sShadowRender) + { + LLOctreeCullShadow culler(&camera); + culler.traverse(mOctree); + } + else if (mInfiniteFarClip || (!LLPipeline::sUseFarClip && !gCubeSnapshot)) + { + LLOctreeCullNoFarClip culler(&camera); + culler.traverse(mOctree); + } + else + { + LLOctreeCull culler(&camera); + culler.traverse(mOctree); + } + + return 0; +} + +void pushVerts(LLDrawInfo* params) +{ + LLRenderPass::applyModelMatrix(*params); + params->mVertexBuffer->setBuffer(); + params->mVertexBuffer->drawRange(LLRender::TRIANGLES, + params->mStart, params->mEnd, params->mCount, params->mOffset); +} + +void pushVerts(LLSpatialGroup* group) +{ + LLDrawInfo* params = NULL; + + for (LLSpatialGroup::draw_map_t::iterator i = group->mDrawMap.begin(); i != group->mDrawMap.end(); ++i) + { + for (LLSpatialGroup::drawmap_elem_t::iterator j = i->second.begin(); j != i->second.end(); ++j) + { + params = *j; + pushVerts(params); + } + } +} + +void pushVerts(LLFace* face) +{ + if (face) + { + llassert(face->verify()); + face->renderIndexed(); + } +} + +void pushVerts(LLDrawable* drawable) +{ + for (S32 i = 0; i < drawable->getNumFaces(); ++i) + { + pushVerts(drawable->getFace(i)); + } +} + +void pushVerts(LLVolume* volume) +{ + LLVertexBuffer::unbind(); + for (S32 i = 0; i < volume->getNumVolumeFaces(); ++i) + { + const LLVolumeFace& face = volume->getVolumeFace(i); + LLVertexBuffer::drawElements(LLRender::TRIANGLES, face.mPositions, NULL, face.mNumIndices, face.mIndices); + } +} + +void pushBufferVerts(LLVertexBuffer* buffer) +{ + if (buffer) + { + buffer->setBuffer(); + buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts()-1, buffer->getNumIndices(), 0); + } +} + +void pushBufferVerts(LLSpatialGroup* group, bool push_alpha = true) +{ + if (group->getSpatialPartition()->mRenderByGroup) + { + if (!group->mDrawMap.empty()) + { + LLDrawInfo* params = *(group->mDrawMap.begin()->second.begin()); + LLRenderPass::applyModelMatrix(*params); + + if (push_alpha) + { + pushBufferVerts(group->mVertexBuffer); + } + + for (LLSpatialGroup::buffer_map_t::iterator i = group->mBufferMap.begin(); i != group->mBufferMap.end(); ++i) + { + for (LLSpatialGroup::buffer_texture_map_t::iterator j = i->second.begin(); j != i->second.end(); ++j) + { + for (LLSpatialGroup::buffer_list_t::iterator k = j->second.begin(); k != j->second.end(); ++k) + { + pushBufferVerts(*k); + } + } + } + } + } + /*else + { + //const LLVector4a* bounds = group->getBounds(); + //drawBox(bounds[0], bounds[1]); + }*/ +} + +void pushVertsColorCoded(LLSpatialGroup* group) +{ + LLDrawInfo* params = NULL; + + static const LLColor4 colors[] = { + LLColor4::green, + LLColor4::green1, + LLColor4::green2, + LLColor4::green3, + LLColor4::green4, + LLColor4::green5, + LLColor4::green6 + }; + + static const U32 col_count = LL_ARRAY_SIZE(colors); + + U32 col = 0; + + for (LLSpatialGroup::draw_map_t::iterator i = group->mDrawMap.begin(); i != group->mDrawMap.end(); ++i) + { + for (LLSpatialGroup::drawmap_elem_t::iterator j = i->second.begin(); j != i->second.end(); ++j) + { + params = *j; + LLRenderPass::applyModelMatrix(*params); + gGL.diffuseColor4f(colors[col].mV[0], colors[col].mV[1], colors[col].mV[2], 0.5f); + params->mVertexBuffer->setBuffer(); + params->mVertexBuffer->drawRange(LLRender::TRIANGLES, + params->mStart, params->mEnd, params->mCount, params->mOffset); + col = (col+1)%col_count; + } + } +} + +// return false if drawable is rigged and: +// - a linked rigged drawable has a different spatial group +// - a linked rigged drawable face has the wrong draw order index +bool check_rigged_group(LLDrawable* drawable) +{ +#if 0 + if (drawable->isState(LLDrawable::RIGGED)) + { + LLSpatialGroup* group = drawable->getSpatialGroup(); + LLDrawable* root = drawable->getRoot(); + + if (root->isState(LLDrawable::RIGGED) && root->getSpatialGroup() != group) + { + LL_WARNS() << "[root->isState(LLDrawable::RIGGED) and root->getSpatialGroup() != group] is true" + " (" << root->getSpatialGroup() << " != " << group << ")" << LL_ENDL; + llassert(false); + return false; + } + + S32 last_draw_index = -1; + if (root->isState(LLDrawable::RIGGED)) + { + for (auto& face : root->getFaces()) + { + if ((S32) face->getDrawOrderIndex() <= last_draw_index) + { + LL_WARNS() << "[(S32)face->getDrawOrderIndex() <= last_draw_index] is true" + " (" << (S32)face->getDrawOrderIndex() << " <= " << last_draw_index << ")" << LL_ENDL; + llassert(false); + return false; + } + last_draw_index = face->getDrawOrderIndex(); + } + } + + for (auto& child : root->getVObj()->getChildren()) + { + if (child->mDrawable->isState(LLDrawable::RIGGED)) + { + for (auto& face : child->mDrawable->getFaces()) + { + if ((S32) face->getDrawOrderIndex() <= last_draw_index) + { + LL_WARNS() << "[(S32)face->getDrawOrderIndex() <= last_draw_index] is true" + " (" << (S32)face->getDrawOrderIndex() << " <= " << last_draw_index << ")" << LL_ENDL; + llassert(false); + return false; + } + last_draw_index = face->getDrawOrderIndex(); + } + } + + if (child->mDrawable->getSpatialGroup() != group) + { + LL_WARNS() << "[child->mDrawable->getSpatialGroup() != group] is true" + " (" << child->mDrawable->getSpatialGroup() << " != " << group << ")" << LL_ENDL; + llassert(false); + return false; + } + } + } +#endif + return true; +} + +void renderOctree(LLSpatialGroup* group) +{ + //render solid object bounding box, color + //coded by buffer usage and activity + gGL.setSceneBlendType(LLRender::BT_ADD_WITH_ALPHA); + LLVector4 col; + if (group->mBuilt > 0.f) + { + group->mBuilt -= 2.f * gFrameIntervalSeconds.value(); + col.setVec(0.1f,0.1f,1,0.1f); + + { + LLGLDepthTest gl_depth(false, false); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + + gGL.diffuseColor4f(1,0,0,group->mBuilt); + gGL.flush(); + glLineWidth(5.f); + + const LLVector4a* bounds = group->getObjectBounds(); + drawBoxOutline(bounds[0], bounds[1]); + gGL.flush(); + glLineWidth(1.f); + gGL.flush(); + + LLVOAvatar* lastAvatar = nullptr; + U64 lastMeshId = 0; + + for (LLSpatialGroup::element_iter i = group->getDataBegin(); i != group->getDataEnd(); ++i) + { + LLDrawable* drawable = (LLDrawable*)(*i)->getDrawable(); + if(!drawable || drawable->getNumFaces() == 0) + { + continue; + } + + llassert(check_rigged_group(drawable)); + + if (!group->getSpatialPartition()->isBridge()) + { + gGL.pushMatrix(); + LLVector3 trans = drawable->getRegion()->getOriginAgent(); + gGL.translatef(trans.mV[0], trans.mV[1], trans.mV[2]); + } + + LLFace* face = drawable->getFace(0); + bool rigged = face->isState(LLFace::RIGGED); + gDebugProgram.bind(rigged); + + gGL.diffuseColor4f(1, 0, 0, 1); + + if (rigged) + { + gGL.pushMatrix(); + gGL.loadMatrix(gGLModelView); + if (lastAvatar != face->mAvatar || + lastMeshId != face->mSkinInfo->mHash) + { + if (!LLRenderPass::uploadMatrixPalette(face->mAvatar, face->mSkinInfo)) + { + continue; + } + lastAvatar = face->mAvatar; + lastMeshId = face->mSkinInfo->mHash; + } + } + for (S32 j = 0; j < drawable->getNumFaces(); j++) + { + LLFace* face = drawable->getFace(j); + if (face && face->getVertexBuffer()) + { + LLVOVolume* vol = drawable->getVOVolume(); + + if (gFrameTimeSeconds - face->mLastUpdateTime < 0.5f) + { + if (vol && vol->isShrinkWrapped()) + { + gGL.diffuseColor4f(0, 1, 1, group->mBuilt); + } + else + { + gGL.diffuseColor4f(0, 1, 0, group->mBuilt); + } + } + else if (gFrameTimeSeconds - face->mLastMoveTime < 0.5f) + { + if (vol && vol->isShrinkWrapped()) + { + gGL.diffuseColor4f(1, 1, 0, group->mBuilt); + } + else + { + gGL.diffuseColor4f(1, 0, 0, group->mBuilt); + } + } + else + { + continue; + } + + face->getVertexBuffer()->setBuffer(); + face->getVertexBuffer()->draw(LLRender::TRIANGLES, face->getIndicesCount(), face->getIndicesStart()); + } + } + + if (rigged) + { + gGL.popMatrix(); + } + + if (!group->getSpatialPartition()->isBridge()) + { + gGL.popMatrix(); + } + } + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + gDebugProgram.bind(); // make sure non-rigged variant is bound + gGL.diffuseColor4f(1,1,1,1); + } + } + + gGL.diffuseColor4fv(col.mV); + LLVector4a fudge; + fudge.splat(0.001f); + + gGL.setSceneBlendType(LLRender::BT_ALPHA); + + { + //draw opaque outline + gGL.diffuseColor4f(0,1,1,1); + + const LLVector4a* bounds = group->getBounds(); + drawBoxOutline(bounds[0], bounds[1]); + } +} + +std::set visible_selected_groups; + + + +void renderXRay(LLSpatialGroup* group, LLCamera* camera) +{ + bool render_objects = (!LLPipeline::sUseOcclusion || !group->isOcclusionState(LLSpatialGroup::OCCLUDED)) && group->isVisible() && + !group->isEmpty(); + + if (render_objects) + { + pushBufferVerts(group, false); + + bool selected = false; + + for (LLSpatialGroup::element_iter iter = group->getDataBegin(); iter != group->getDataEnd(); ++iter) + { + LLDrawable* drawable = (LLDrawable*)(*iter)->getDrawable(); + if (drawable->getVObj().notNull() && drawable->getVObj()->isSelected()) + { + selected = true; + break; + } + } + + if (selected) + { //store for rendering occlusion volume as overlay + + if (!group->getSpatialPartition()->isBridge()) + { + visible_selected_groups.insert(group); + } + else + { + visible_selected_groups.insert(group->getSpatialPartition()->asBridge()->getSpatialGroup()); + } + } + } +} + +void renderCrossHairs(LLVector3 position, F32 size, LLColor4 color) +{ + gGL.color4fv(color.mV); + gGL.begin(LLRender::LINES); + { + gGL.vertex3fv((position - LLVector3(size, 0.f, 0.f)).mV); + gGL.vertex3fv((position + LLVector3(size, 0.f, 0.f)).mV); + gGL.vertex3fv((position - LLVector3(0.f, size, 0.f)).mV); + gGL.vertex3fv((position + LLVector3(0.f, size, 0.f)).mV); + gGL.vertex3fv((position - LLVector3(0.f, 0.f, size)).mV); + gGL.vertex3fv((position + LLVector3(0.f, 0.f, size)).mV); + } + gGL.end(); +} + +void renderUpdateType(LLDrawable* drawablep) +{ + LLViewerObject* vobj = drawablep->getVObj(); + if (!vobj || OUT_UNKNOWN == vobj->getLastUpdateType()) + { + return; + } + LLGLEnable blend(GL_BLEND); + switch (vobj->getLastUpdateType()) + { + case OUT_FULL: + gGL.diffuseColor4f(0,1,0,0.5f); + break; + case OUT_TERSE_IMPROVED: + gGL.diffuseColor4f(0,1,1,0.5f); + break; + case OUT_FULL_COMPRESSED: + if (vobj->getLastUpdateCached()) + { + gGL.diffuseColor4f(1,0,0,0.5f); + } + else + { + gGL.diffuseColor4f(1,1,0,0.5f); + } + break; + case OUT_FULL_CACHED: + gGL.diffuseColor4f(0,0,1,0.5f); + break; + default: + LL_WARNS() << "Unknown update_type " << vobj->getLastUpdateType() << LL_ENDL; + break; + }; + S32 num_faces = drawablep->getNumFaces(); + if (num_faces) + { + for (S32 i = 0; i < num_faces; ++i) + { + pushVerts(drawablep->getFace(i)); + } + } +} + +void renderBoundingBox(LLDrawable* drawable, bool set_color = true) +{ + if (set_color) + { + if (drawable->isSpatialBridge()) + { + gGL.diffuseColor4f(1,0.5f,0,1); // orange + } + else if (drawable->getVOVolume()) + { + if (drawable->isRoot()) + { + gGL.diffuseColor4f(1,1,0,1); // yellow + } + else + { + gGL.diffuseColor4f(0,1,0,1); // green + } + } + else if (drawable->getVObj()) + { + switch (drawable->getVObj()->getPCode()) + { + case LLViewerObject::LL_VO_SURFACE_PATCH: + gGL.diffuseColor4f(0,1,1,1); // cyan + break; + case LLViewerObject::LL_VO_CLOUDS: + // no longer used + break; + case LLViewerObject::LL_VO_PART_GROUP: + case LLViewerObject::LL_VO_HUD_PART_GROUP: + gGL.diffuseColor4f(0,0,1,1); // blue + break; + case LLViewerObject::LL_VO_VOID_WATER: + case LLViewerObject::LL_VO_WATER: + gGL.diffuseColor4f(0,0.5f,1,1); // medium blue + break; + case LL_PCODE_LEGACY_TREE: + gGL.diffuseColor4f(0,0.5f,0,1); // dark green + break; + default: + LLControlAvatar *cav = dynamic_cast(drawable->getVObj()->asAvatar()); + if (cav) + { + bool has_pos_constraint = (cav->mPositionConstraintFixup != LLVector3()); + bool has_scale_constraint = (cav->mScaleConstraintFixup != 1.0f); + if (has_pos_constraint || has_scale_constraint) + { + gGL.diffuseColor4f(1,0,0,1); + } + else + { + gGL.diffuseColor4f(0,1,0.5,1); + } + } + else + { + gGL.diffuseColor4f(1,0,1,1); // magenta + } + break; + } + } + else + { + gGL.diffuseColor4f(1,0,0,1); + } + } + + const LLVector4a* ext; + LLVector4a pos, size; + + if (drawable->getVOVolume()) + { + //render face bounding boxes + for (S32 i = 0; i < drawable->getNumFaces(); i++) + { + LLFace* facep = drawable->getFace(i); + if (facep) + { + ext = facep->mExtents; + + pos.setAdd(ext[0], ext[1]); + pos.mul(0.5f); + size.setSub(ext[1], ext[0]); + size.mul(0.5f); + + drawBoxOutline(pos,size); + } + } + } + + //render drawable bounding box + ext = drawable->getSpatialExtents(); + + pos.setAdd(ext[0], ext[1]); + pos.mul(0.5f); + size.setSub(ext[1], ext[0]); + size.mul(0.5f); + + LLViewerObject* vobj = drawable->getVObj(); + if (vobj && vobj->onActiveList()) + { + gGL.flush(); + glLineWidth(llmax(4.f*sinf(gFrameTimeSeconds*2.f)+1.f, 1.f)); + //glLineWidth(4.f*(sinf(gFrameTimeSeconds*2.f)*0.25f+0.75f)); + stop_glerror(); + drawBoxOutline(pos,size); + gGL.flush(); + glLineWidth(1.f); + } + else + { + drawBoxOutline(pos,size); + } +} + +void renderNormals(LLDrawable *drawablep) +{ + if (!drawablep->isVisible()) + return; + + LLVertexBuffer::unbind(); + + LLVOVolume *vol = drawablep->getVOVolume(); + + if (vol) + { + LLVolume *volume = vol->getVolume(); + + // Drawable's normals & tangents are stored in model space, i.e. before any scaling is applied. + // + // SL-13490, using pos + normal to compute the 2nd vertex of a normal line segment doesn't + // work when there's a non-uniform scale in the mix. Normals require MVP-inverse-transpose + // transform. We get that effect here by pre-applying the inverse scale (twice, because + // one forward scale will be re-applied via the MVP in the vertex shader) + + LLVector3 scale_v3 = vol->getScale(); + float scale_len = scale_v3.length(); + LLVector4a obj_scale(scale_v3.mV[VX], scale_v3.mV[VY], scale_v3.mV[VZ]); + obj_scale.normalize3(); + + // Normals &tangent line segments get scaled along with the object. Divide by scale length + // to keep the as-viewed lengths (relatively) constant with the debug setting length + float draw_length = gSavedSettings.getF32("RenderDebugNormalScale") / scale_len; + + // Create inverse-scale vector for normals + LLVector4a inv_scale(1.0 / scale_v3.mV[VX], 1.0 / scale_v3.mV[VY], 1.0 / scale_v3.mV[VZ]); + inv_scale.mul(inv_scale); // Squared, to apply inverse scale twice + inv_scale.normalize3fast(); + + gGL.pushMatrix(); + gGL.multMatrix((F32 *) vol->getRelativeXform().mMatrix); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + for (S32 i = 0; i < volume->getNumVolumeFaces(); ++i) + { + const LLVolumeFace &face = volume->getVolumeFace(i); + + gGL.flush(); + gGL.diffuseColor4f(1, 1, 0, 1); + gGL.begin(LLRender::LINES); + for (S32 j = 0; j < face.mNumVertices; ++j) + { + LLVector4a n, p; + + n.setMul(face.mNormals[j], 1.0); + n.mul(inv_scale); // Pre-scale normal, so it's left with an inverse-transpose xform after MVP + n.normalize3fast(); + n.mul(draw_length); + p.setAdd(face.mPositions[j], n); + + gGL.vertex3fv(face.mPositions[j].getF32ptr()); + gGL.vertex3fv(p.getF32ptr()); + } + gGL.end(); + + // Tangents are simple vectors and do not require reorientation via pre-scaling + if (face.mTangents) + { + gGL.flush(); + gGL.diffuseColor4f(0, 1, 1, 1); + gGL.begin(LLRender::LINES); + for (S32 j = 0; j < face.mNumVertices; ++j) + { + LLVector4a t, p; + + t.setMul(face.mTangents[j], 1.0f); + t.normalize3fast(); + t.mul(draw_length); + p.setAdd(face.mPositions[j], t); + + gGL.vertex3fv(face.mPositions[j].getF32ptr()); + gGL.vertex3fv(p.getF32ptr()); + } + gGL.end(); + } + } + + gGL.popMatrix(); + } +} + +S32 get_physics_detail(const LLVolumeParams& volume_params, const LLVector3& scale) +{ + const S32 DEFAULT_DETAIL = 1; + const F32 LARGE_THRESHOLD = 5.f; + const F32 MEGA_THRESHOLD = 25.f; + + S32 detail = DEFAULT_DETAIL; + F32 avg_scale = (scale[0]+scale[1]+scale[2])/3.f; + + if (avg_scale > LARGE_THRESHOLD) + { + detail += 1; + if (avg_scale > MEGA_THRESHOLD) + { + detail += 1; + } + } + + return detail; +} + +void renderMeshBaseHull(LLVOVolume* volume, U32 data_mask, LLColor4& color) +{ + LLUUID mesh_id = volume->getVolume()->getParams().getSculptID(); + LLModel::Decomposition* decomp = gMeshRepo.getDecomposition(mesh_id); + + const LLVector3 center(0,0,0); + const LLVector3 size(0.25f,0.25f,0.25f); + + if (decomp) + { + if (!decomp->mBaseHullMesh.empty()) + { + gGL.diffuseColor4fv(color.mV); + LLVertexBuffer::drawArrays(LLRender::TRIANGLES, decomp->mBaseHullMesh.mPositions); + } + else + { + gMeshRepo.buildPhysicsMesh(*decomp); + gGL.diffuseColor4f(0,1,1,1); + drawBoxOutline(center, size); + } + + } + else + { + gGL.diffuseColor3f(1,0,1); + drawBoxOutline(center, size); + } +} + +void render_hull(LLModel::PhysicsMesh& mesh, const LLColor4& color) +{ + gGL.diffuseColor4fv(color.mV); + LLVertexBuffer::drawArrays(LLRender::TRIANGLES, mesh.mPositions); +} + +void renderPhysicsShape(LLDrawable* drawable, LLVOVolume* volume, bool wireframe) +{ + U8 physics_type = volume->getPhysicsShapeType(); + + if (physics_type == LLViewerObject::PHYSICS_SHAPE_NONE || volume->isFlexible()) + { + return; + } + + //not allowed to return at this point without rendering *something* + + F32 threshold = gSavedSettings.getF32("ObjectCostHighThreshold"); + F32 cost = volume->getObjectCost(); + + LLColor4 low = gSavedSettings.getColor4("ObjectCostLowColor"); + LLColor4 mid = gSavedSettings.getColor4("ObjectCostMidColor"); + LLColor4 high = gSavedSettings.getColor4("ObjectCostHighColor"); + + F32 normalizedCost = 1.f - exp( -(cost / threshold) ); + + LLColor4 color; + if ( normalizedCost <= 0.5f ) + { + color = lerp( low, mid, 2.f * normalizedCost ); + } + else + { + color = lerp( mid, high, 2.f * ( normalizedCost - 0.5f ) ); + } + + if (wireframe) + { + color = color * 0.5f; + } + + U32 data_mask = LLVertexBuffer::MAP_VERTEX; + + LLVolumeParams volume_params = volume->getVolume()->getParams(); + + LLPhysicsVolumeParams physics_params(volume_params, + physics_type == LLViewerObject::PHYSICS_SHAPE_CONVEX_HULL); + + LLPhysicsShapeBuilderUtil::PhysicsShapeSpecification physics_spec; + LLPhysicsShapeBuilderUtil::determinePhysicsShape(physics_params, volume->getScale(), physics_spec); + + U32 type = physics_spec.getType(); + + LLVector3 center(0,0,0); + LLVector3 size(0.25f,0.25f,0.25f); + + gGL.pushMatrix(); + gGL.multMatrix((F32*) volume->getRelativeXform().mMatrix); + + if (type == LLPhysicsShapeBuilderUtil::PhysicsShapeSpecification::USER_MESH) + { + LLUUID mesh_id = volume->getVolume()->getParams().getSculptID(); + LLModel::Decomposition* decomp = gMeshRepo.getDecomposition(mesh_id); + + if (decomp) + { //render a physics based mesh + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + if (!decomp->mHull.empty()) + { //decomposition exists, use that + + if (decomp->mMesh.empty()) + { + gMeshRepo.buildPhysicsMesh(*decomp); + } + + for (U32 i = 0; i < decomp->mMesh.size(); ++i) + { + render_hull(decomp->mMesh[i], color); + } + } + else if (!decomp->mPhysicsShapeMesh.empty()) + { + //decomp has physics mesh, render that mesh + gGL.diffuseColor4fv(color.mV); + + LLVertexBuffer::drawArrays(LLRender::TRIANGLES, decomp->mPhysicsShapeMesh.mPositions); + } + else + { //no mesh or decomposition, render base hull + renderMeshBaseHull(volume, data_mask, color); + + if (decomp->mPhysicsShapeMesh.empty()) + { + //attempt to fetch physics shape mesh if available + gMeshRepo.fetchPhysicsShape(mesh_id); + } + } + } + else + { + gGL.diffuseColor3f(1,1,0); + drawBoxOutline(center, size); + } + } + else if (type == LLPhysicsShapeBuilderUtil::PhysicsShapeSpecification::USER_CONVEX || + type == LLPhysicsShapeBuilderUtil::PhysicsShapeSpecification::PRIM_CONVEX) + { + if (volume->isMesh()) + { + renderMeshBaseHull(volume, data_mask, color); + } + else + { + LLVolumeParams volume_params = volume->getVolume()->getParams(); + S32 detail = get_physics_detail(volume_params, volume->getScale()); + LLVolume* phys_volume = LLPrimitive::sVolumeManager->refVolume(volume_params, detail); + + if (!phys_volume->mHullPoints) + { //build convex hull + std::vector pos; + std::vector index; + + S32 index_offset = 0; + + for (S32 i = 0; i < phys_volume->getNumVolumeFaces(); ++i) + { + const LLVolumeFace& face = phys_volume->getVolumeFace(i); + if (index_offset + face.mNumVertices > 65535) + { + continue; + } + + for (S32 j = 0; j < face.mNumVertices; ++j) + { + pos.push_back(LLVector3(face.mPositions[j].getF32ptr())); + } + + for (S32 j = 0; j < face.mNumIndices; ++j) + { + index.push_back(face.mIndices[j]+index_offset); + } + + index_offset += face.mNumVertices; + } + + if (!pos.empty() && !index.empty()) + { + LLCDMeshData mesh; + mesh.mIndexBase = &index[0]; + mesh.mVertexBase = pos[0].mV; + mesh.mNumVertices = pos.size(); + mesh.mVertexStrideBytes = 12; + mesh.mIndexStrideBytes = 6; + mesh.mIndexType = LLCDMeshData::INT_16; + + mesh.mNumTriangles = index.size()/3; + + LLCDMeshData res; + + LLConvexDecomposition::getInstance()->generateSingleHullMeshFromMesh( &mesh, &res ); + + //copy res into phys_volume + phys_volume->mHullPoints = (LLVector4a*) ll_aligned_malloc_16(sizeof(LLVector4a)*res.mNumVertices); + phys_volume->mNumHullPoints = res.mNumVertices; + + S32 idx_size = (res.mNumTriangles*3*2+0xF) & ~0xF; + phys_volume->mHullIndices = (U16*) ll_aligned_malloc_16(idx_size); + phys_volume->mNumHullIndices = res.mNumTriangles*3; + + const F32* v = res.mVertexBase; + + for (S32 i = 0; i < res.mNumVertices; ++i) + { + F32* p = (F32*) ((U8*)v+i*res.mVertexStrideBytes); + phys_volume->mHullPoints[i].load3(p); + } + + if (res.mIndexType == LLCDMeshData::INT_16) + { + for (S32 i = 0; i < res.mNumTriangles; ++i) + { + U16* idx = (U16*) (((U8*)res.mIndexBase)+i*res.mIndexStrideBytes); + + phys_volume->mHullIndices[i*3+0] = idx[0]; + phys_volume->mHullIndices[i*3+1] = idx[1]; + phys_volume->mHullIndices[i*3+2] = idx[2]; + } + } + else + { + for (S32 i = 0; i < res.mNumTriangles; ++i) + { + U32* idx = (U32*) (((U8*)res.mIndexBase)+i*res.mIndexStrideBytes); + + phys_volume->mHullIndices[i*3+0] = (U16) idx[0]; + phys_volume->mHullIndices[i*3+1] = (U16) idx[1]; + phys_volume->mHullIndices[i*3+2] = (U16) idx[2]; + } + } + } + } + + if (phys_volume->mHullPoints) + { + //render hull + gGL.diffuseColor4fv(color.mV); + + LLVertexBuffer::unbind(); + LLVertexBuffer::drawElements(LLRender::TRIANGLES, phys_volume->mHullPoints, NULL, phys_volume->mNumHullIndices, phys_volume->mHullIndices); + } + else + { + gGL.diffuseColor4f(1,0,1,1); + drawBoxOutline(center, size); + } + + LLPrimitive::sVolumeManager->unrefVolume(phys_volume); + } + } + else if (type == LLPhysicsShapeBuilderUtil::PhysicsShapeSpecification::BOX) + { + if (!wireframe) + { + LLVector3 center = physics_spec.getCenter(); + LLVector3 scale = physics_spec.getScale(); + LLVector3 vscale = volume->getScale() * 2.f; + scale.set(scale[0] / vscale[0], scale[1] / vscale[1], scale[2] / vscale[2]); + + gGL.diffuseColor4fv(color.mV); + drawBox(center, scale); + } + } + else if (type == LLPhysicsShapeBuilderUtil::PhysicsShapeSpecification::SPHERE) + { + if (!wireframe) + { + LLVolumeParams volume_params; + volume_params.setType(LL_PCODE_PROFILE_CIRCLE_HALF, LL_PCODE_PATH_CIRCLE); + volume_params.setBeginAndEndS(0.f, 1.f); + volume_params.setBeginAndEndT(0.f, 1.f); + volume_params.setRatio(1, 1); + volume_params.setShear(0, 0); + LLVolume* sphere = LLPrimitive::sVolumeManager->refVolume(volume_params, 3); + + gGL.diffuseColor4fv(color.mV); + pushVerts(sphere); + LLPrimitive::sVolumeManager->unrefVolume(sphere); + } + } + else if (type == LLPhysicsShapeBuilderUtil::PhysicsShapeSpecification::CYLINDER) + { + if (!wireframe) + { + LLVolumeParams volume_params; + volume_params.setType(LL_PCODE_PROFILE_CIRCLE, LL_PCODE_PATH_LINE); + volume_params.setBeginAndEndS(0.f, 1.f); + volume_params.setBeginAndEndT(0.f, 1.f); + volume_params.setRatio(1, 1); + volume_params.setShear(0, 0); + LLVolume* cylinder = LLPrimitive::sVolumeManager->refVolume(volume_params, 3); + + gGL.diffuseColor4fv(color.mV); + pushVerts(cylinder); + LLPrimitive::sVolumeManager->unrefVolume(cylinder); + } + } + else if (type == LLPhysicsShapeBuilderUtil::PhysicsShapeSpecification::PRIM_MESH) + { + LLVolumeParams volume_params = volume->getVolume()->getParams(); + S32 detail = get_physics_detail(volume_params, volume->getScale()); + + LLVolume* phys_volume = LLPrimitive::sVolumeManager->refVolume(volume_params, detail); + + gGL.diffuseColor4fv(color.mV); + pushVerts(phys_volume); + + LLPrimitive::sVolumeManager->unrefVolume(phys_volume); + } + else if (type == LLPhysicsShapeBuilderUtil::PhysicsShapeSpecification::PRIM_CONVEX) + { + LLVolumeParams volume_params = volume->getVolume()->getParams(); + S32 detail = get_physics_detail(volume_params, volume->getScale()); + + LLVolume* phys_volume = LLPrimitive::sVolumeManager->refVolume(volume_params, detail); + + if (phys_volume->mHullPoints && phys_volume->mHullIndices) + { + + llassert(LLGLSLShader::sCurBoundShader != 0); + LLVertexBuffer::unbind(); + glVertexPointer(3, GL_FLOAT, 16, phys_volume->mHullPoints); + + gGL.diffuseColor4fv(color.mV); + + gGL.syncMatrices(); + glDrawElements(GL_TRIANGLES, phys_volume->mNumHullIndices, GL_UNSIGNED_SHORT, phys_volume->mHullIndices); + } + else + { + gGL.diffuseColor3f(1,0,1); + drawBoxOutline(center, size); + gMeshRepo.buildHull(volume_params, detail); + } + LLPrimitive::sVolumeManager->unrefVolume(phys_volume); + } + else if (type == LLPhysicsShapeBuilderUtil::PhysicsShapeSpecification::SCULPT) + { + //TODO: implement sculpted prim physics display + } + else + { + LL_ERRS() << "Unhandled type" << LL_ENDL; + } + + gGL.popMatrix(); +} + +void renderPhysicsShapes(LLSpatialGroup* group, bool wireframe) +{ + for (OctreeNode::const_element_iter i = group->getDataBegin(); i != group->getDataEnd(); ++i) + { + LLDrawable* drawable = (LLDrawable*)(*i)->getDrawable(); + if(!drawable) + { + continue; + } + + if (drawable->isSpatialBridge()) + { + LLSpatialBridge* bridge = drawable->asPartition()->asBridge(); + + if (bridge) + { + gGL.pushMatrix(); + gGL.multMatrix((F32*)bridge->mDrawable->getRenderMatrix().mMatrix); + bridge->renderPhysicsShapes(wireframe); + gGL.popMatrix(); + } + } + else + { + LLVOVolume* volume = drawable->getVOVolume(); + if (volume && !volume->isAttachment() && volume->getPhysicsShapeType() != LLViewerObject::PHYSICS_SHAPE_NONE ) + { + if (!group->getSpatialPartition()->isBridge()) + { + gGL.pushMatrix(); + LLVector3 trans = drawable->getRegion()->getOriginAgent(); + gGL.translatef(trans.mV[0], trans.mV[1], trans.mV[2]); + renderPhysicsShape(drawable, volume, wireframe); + gGL.popMatrix(); + } + else + { + renderPhysicsShape(drawable, volume, wireframe); + } + } + else + { +#if 0 + LLViewerObject* object = drawable->getVObj(); + if (object && object->getPCode() == LLViewerObject::LL_VO_SURFACE_PATCH) + { + gGL.pushMatrix(); + gGL.multMatrix((F32*) object->getRegion()->mRenderMatrix.mMatrix); + //push face vertices for terrain + for (S32 i = 0; i < drawable->getNumFaces(); ++i) + { + LLFace* face = drawable->getFace(i); + if (face) + { + LLVertexBuffer* buff = face->getVertexBuffer(); + if (buff) + { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + + buff->setBuffer(); + gGL.diffuseColor4f(0.2f, 0.5f, 0.3f, 0.5f); + buff->draw(LLRender::TRIANGLES, buff->getNumIndices(), 0); + + gGL.diffuseColor4f(0.2f, 1.f, 0.3f, 0.75f); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + buff->draw(LLRender::TRIANGLES, buff->getNumIndices(), 0); + } + } + } + gGL.popMatrix(); + } +#endif + } + } + } +} + +void renderTexturePriority(LLDrawable* drawable) +{ + for (int face=0; facegetNumFaces(); ++face) + { + LLFace *facep = drawable->getFace(face); + + LLVector4 cold(0,0,0.25f); + LLVector4 hot(1,0.25f,0.25f); + + LLVector4 boost_cold(0,0,0,0); + LLVector4 boost_hot(0,1,0,1); + + LLGLDisable blend(GL_BLEND); + + //LLViewerTexture* imagep = facep->getTexture(); + //if (imagep) + if (facep) + { + + //F32 vsize = imagep->mMaxVirtualSize; + F32 vsize = facep->getPixelArea(); + + if (vsize > sCurMaxTexPriority) + { + sCurMaxTexPriority = vsize; + } + + F32 t = vsize/sLastMaxTexPriority; + + LLVector4 col = lerp(cold, hot, t); + gGL.diffuseColor4fv(col.mV); + } + //else + //{ + // gGL.diffuseColor4f(1,0,1,1); + //} + + LLVector4a center; + center.setAdd(facep->mExtents[1],facep->mExtents[0]); + center.mul(0.5f); + LLVector4a size; + size.setSub(facep->mExtents[1],facep->mExtents[0]); + size.mul(0.5f); + size.add(LLVector4a(0.01f)); + drawBox(center, size); + + /*S32 boost = imagep->getBoostLevel(); + if (boost>LLGLTexture::BOOST_NONE) + { + F32 t = (F32) boost / (F32) (LLGLTexture::BOOST_MAX_LEVEL-1); + LLVector4 col = lerp(boost_cold, boost_hot, t); + LLGLEnable blend_on(GL_BLEND); + gGL.blendFunc(GL_SRC_ALPHA, GL_ONE); + gGL.diffuseColor4fv(col.mV); + drawBox(center, size); + gGL.blendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + }*/ + } +} + +void renderPoints(LLDrawable* drawablep) +{ + LLGLDepthTest depth(GL_FALSE, GL_FALSE); + if (drawablep->getNumFaces()) + { + gGL.begin(LLRender::POINTS); + gGL.diffuseColor3f(1,1,1); + for (S32 i = 0; i < drawablep->getNumFaces(); i++) + { + LLFace * face = drawablep->getFace(i); + if (face) + { + gGL.vertex3fv(face->mCenterLocal.mV); + } + } + gGL.end(); + } +} + +void renderTextureAnim(LLDrawInfo* params) +{ + if (!params->mTextureMatrix) + { + return; + } + + LLGLEnable blend(GL_BLEND); + gGL.diffuseColor4f(1,1,0,0.5f); + pushVerts(params); +} + +void renderBatchSize(LLDrawInfo* params) +{ + LLGLEnable offset(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(-1.f, 1.f); + LLGLSLShader* old_shader = LLGLSLShader::sCurBoundShaderPtr; + bool bind = false; + if (params->mAvatar) + { + gGL.pushMatrix(); + gGL.loadMatrix(gGLModelView); + bind = true; + old_shader->mRiggedVariant->bind(); + LLRenderPass::uploadMatrixPalette(*params); + } + + + gGL.diffuseColor4ubv(params->getDebugColor().mV); + pushVerts(params); + + if (bind) + { + gGL.popMatrix(); + old_shader->bind(); + } +} + +void renderTexelDensity(LLDrawable* drawable) +{ + if (LLViewerTexture::sDebugTexelsMode == LLViewerTexture::DEBUG_TEXELS_OFF + || LLViewerTexture::sCheckerBoardImagep.isNull()) + { + return; + } + + LLGLEnable _(GL_BLEND); + + LLMatrix4 checkerboard_matrix; + S32 discard_level = -1; + + for (S32 f = 0; f < drawable->getNumFaces(); f++) + { + LLFace* facep = drawable->getFace(f); + LLVertexBuffer* buffer = facep->getVertexBuffer(); + LLViewerTexture* texturep = facep->getTexture(); + + if (texturep == NULL) continue; + + switch(LLViewerTexture::sDebugTexelsMode) + { + case LLViewerTexture::DEBUG_TEXELS_CURRENT: + discard_level = -1; + break; + case LLViewerTexture::DEBUG_TEXELS_DESIRED: + { + LLViewerFetchedTexture* fetched_texturep = dynamic_cast(texturep); + discard_level = fetched_texturep ? fetched_texturep->getDesiredDiscardLevel() : -1; + break; + } + default: + case LLViewerTexture::DEBUG_TEXELS_FULL: + discard_level = 0; + break; + } + + checkerboard_matrix.initScale(LLVector3(texturep->getWidth(discard_level) / 8, texturep->getHeight(discard_level) / 8, 1.f)); + + gGL.getTexUnit(0)->bind(LLViewerTexture::sCheckerBoardImagep, true); + gGL.matrixMode(LLRender::MM_TEXTURE); + gGL.loadMatrix((GLfloat*)&checkerboard_matrix.mMatrix); + + if (buffer && (facep->getGeomCount() >= 3)) + { + buffer->setBuffer(); + U16 start = facep->getGeomStart(); + U16 end = start + facep->getGeomCount()-1; + U32 count = facep->getIndicesCount(); + U16 offset = facep->getIndicesStart(); + buffer->drawRange(LLRender::TRIANGLES, start, end, count, offset); + } + + gGL.loadIdentity(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + } + + //S32 num_textures = llmax(1, (S32)params->mTextureList.size()); + + //for (S32 i = 0; i < num_textures; i++) + //{ + // LLViewerTexture* texturep = params->mTextureList.empty() ? params->mTexture.get() : params->mTextureList[i].get(); + // if (texturep == NULL) continue; + + // LLMatrix4 checkboard_matrix; + // S32 discard_level = -1; + // switch(LLViewerTexture::sDebugTexelsMode) + // { + // case LLViewerTexture::DEBUG_TEXELS_CURRENT: + // discard_level = -1; + // break; + // case LLViewerTexture::DEBUG_TEXELS_DESIRED: + // { + // LLViewerFetchedTexture* fetched_texturep = dynamic_cast(texturep); + // discard_level = fetched_texturep ? fetched_texturep->getDesiredDiscardLevel() : -1; + // break; + // } + // default: + // case LLViewerTexture::DEBUG_TEXELS_FULL: + // discard_level = 0; + // break; + // } + + // checkboard_matrix.initScale(LLVector3(texturep->getWidth(discard_level) / 8, texturep->getHeight(discard_level) / 8, 1.f)); + // gGL.getTexUnit(i)->activate(); + + // glMatrixMode(GL_TEXTURE); + // glPushMatrix(); + // glLoadIdentity(); + // //gGL.matrixMode(LLRender::MM_TEXTURE); + // glLoadMatrixf((GLfloat*) checkboard_matrix.mMatrix); + + // gGL.getTexUnit(i)->bind(LLViewerTexture::sCheckerBoardImagep, true); + + // pushVerts(params, LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_COLOR | LLVertexBuffer::MAP_NORMAL ); + + // glPopMatrix(); + // glMatrixMode(GL_MODELVIEW); + // //gGL.matrixMode(LLRender::MM_MODELVIEW); + //} +} + + +void renderLights(LLDrawable* drawablep) +{ + if (!drawablep->isLight()) + { + return; + } + + if (drawablep->getNumFaces()) + { + LLGLEnable blend(GL_BLEND); + gGL.diffuseColor4f(0,1,1,0.5f); + + for (S32 i = 0; i < drawablep->getNumFaces(); i++) + { + LLFace * face = drawablep->getFace(i); + if (face) + { + pushVerts(face); + } + } + + const LLVector4a* ext = drawablep->getSpatialExtents(); + + LLVector4a pos; + pos.setAdd(ext[0], ext[1]); + pos.mul(0.5f); + LLVector4a size; + size.setSub(ext[1], ext[0]); + size.mul(0.5f); + + { + LLGLDepthTest depth(GL_FALSE, GL_TRUE); + gGL.diffuseColor4f(1,1,1,1); + drawBoxOutline(pos, size); + } + + gGL.diffuseColor4f(1,1,0,1); + F32 rad = drawablep->getVOVolume()->getLightRadius(); + drawBoxOutline(pos, LLVector4a(rad)); + } +} + +class LLRenderOctreeRaycast : public LLOctreeTriangleRayIntersect +{ +public: + + + LLRenderOctreeRaycast(const LLVector4a& start, const LLVector4a& dir, F32* closest_t) + : LLOctreeTriangleRayIntersect(start, dir, NULL, closest_t, NULL, NULL, NULL, NULL) + { + + } + + void visit(const LLOctreeNode* branch) + { + LLVolumeOctreeListener* vl = (LLVolumeOctreeListener*) branch->getListener(0); + + LLVector3 center, size; + + if (branch->isEmpty()) + { + gGL.diffuseColor3f(1.f,0.2f,0.f); + center.set(branch->getCenter().getF32ptr()); + size.set(branch->getSize().getF32ptr()); + } + else + { + gGL.diffuseColor3f(0.75f, 1.f, 0.f); + center.set(vl->mBounds[0].getF32ptr()); + size.set(vl->mBounds[1].getF32ptr()); + } + + drawBoxOutline(center, size); + + for (U32 i = 0; i < 2; i++) + { + LLGLDepthTest depth(GL_TRUE, GL_FALSE, i == 1 ? GL_LEQUAL : GL_GREATER); + + if (i == 1) + { + gGL.diffuseColor4f(0,1,1,0.5f); + } + else + { + gGL.diffuseColor4f(0,0.5f,0.5f, 0.25f); + drawBoxOutline(center, size); + } + + if (i == 1) + { + gGL.flush(); + glLineWidth(3.f); + } + + gGL.begin(LLRender::TRIANGLES); + for (LLOctreeNode::const_element_iter iter = branch->getDataBegin(); + iter != branch->getDataEnd(); + ++iter) + { + const LLVolumeTriangle* tri = *iter; + + gGL.vertex3fv(tri->mV[0]->getF32ptr()); + gGL.vertex3fv(tri->mV[1]->getF32ptr()); + gGL.vertex3fv(tri->mV[2]->getF32ptr()); + } + gGL.end(); + + if (i == 1) + { + gGL.flush(); + glLineWidth(1.f); + } + } + } +}; + +void renderRaycast(LLDrawable* drawablep) +{ + if (drawablep->getNumFaces()) + { + LLGLEnable blend(GL_BLEND); + gGL.diffuseColor4f(0,1,1,0.5f); + + LLVOVolume* vobj = drawablep->getVOVolume(); + if (vobj && !vobj->isDead()) + { + //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + //pushVerts(drawablep->getFace(gDebugRaycastFaceHit), LLVertexBuffer::MAP_VERTEX); + //glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + + LLVolume* volume = vobj->getVolume(); + + bool transform = true; + if (drawablep->isState(LLDrawable::RIGGED)) + { + volume = vobj->getRiggedVolume(); + transform = false; + } + + if (volume) + { + LLVector3 trans = drawablep->getRegion()->getOriginAgent(); + + for (S32 i = 0; i < volume->getNumVolumeFaces(); ++i) + { + const LLVolumeFace& face = volume->getVolumeFace(i); + + gGL.pushMatrix(); + gGL.translatef(trans.mV[0], trans.mV[1], trans.mV[2]); + gGL.multMatrix((F32*) vobj->getRelativeXform().mMatrix); + + LLVector4a start, end; + if (transform) + { + LLVector3 v_start(gDebugRaycastStart.getF32ptr()); + LLVector3 v_end(gDebugRaycastEnd.getF32ptr()); + + v_start = vobj->agentPositionToVolume(v_start); + v_end = vobj->agentPositionToVolume(v_end); + + start.load3(v_start.mV); + end.load3(v_end.mV); + } + else + { + start = gDebugRaycastStart; + end = gDebugRaycastEnd; + } + + LLVector4a dir; + dir.setSub(end, start); + + gGL.flush(); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + + { + //render face positions + LLVertexBuffer::unbind(); + gGL.diffuseColor4f(0,1,1,0.5f); + glVertexPointer(3, GL_FLOAT, sizeof(LLVector4a), face.mPositions); + gGL.syncMatrices(); + glDrawElements(GL_TRIANGLES, face.mNumIndices, GL_UNSIGNED_SHORT, face.mIndices); + } + + if (!volume->isUnique()) + { + F32 t = 1.f; + + if (!face.getOctree()) + { + ((LLVolumeFace*) &face)->createOctree(); + } + + LLRenderOctreeRaycast render(start, dir, &t); + + render.traverse(face.getOctree()); + } + + gGL.popMatrix(); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } + } + } + else if (drawablep->isAvatar()) + { + if (drawablep->getVObj() == gDebugRaycastObject) + { + LLGLDepthTest depth(GL_FALSE); + LLVOAvatar* av = (LLVOAvatar*) drawablep->getVObj().get(); + av->renderCollisionVolumes(); + } + } + + if (drawablep->getVObj() == gDebugRaycastObject) + { + // draw intersection point + gGL.pushMatrix(); + gGL.loadMatrix(gGLModelView); + LLVector3 translate(gDebugRaycastIntersection.getF32ptr()); + gGL.translatef(translate.mV[0], translate.mV[1], translate.mV[2]); + LLCoordFrame orient; + LLVector4a debug_binormal; + + debug_binormal.setCross3(gDebugRaycastNormal, gDebugRaycastTangent); + debug_binormal.mul(gDebugRaycastTangent.getF32ptr()[3]); + + LLVector3 normal(gDebugRaycastNormal.getF32ptr()); + LLVector3 binormal(debug_binormal.getF32ptr()); + + orient.lookDir(normal, binormal); + LLMatrix4 rotation; + orient.getRotMatrixToParent(rotation); + gGL.multMatrix((float*)rotation.mMatrix); + + gGL.diffuseColor4f(1,0,0,0.5f); + drawBox(LLVector3(0, 0, 0), LLVector3(0.1f, 0.022f, 0.022f)); + gGL.diffuseColor4f(0,1,0,0.5f); + drawBox(LLVector3(0, 0, 0), LLVector3(0.021f, 0.1f, 0.021f)); + gGL.diffuseColor4f(0,0,1,0.5f); + drawBox(LLVector3(0, 0, 0), LLVector3(0.02f, 0.02f, 0.1f)); + gGL.popMatrix(); + + // draw bounding box of prim + const LLVector4a* ext = drawablep->getSpatialExtents(); + + LLVector4a pos; + pos.setAdd(ext[0], ext[1]); + pos.mul(0.5f); + LLVector4a size; + size.setSub(ext[1], ext[0]); + size.mul(0.5f); + + LLGLDepthTest depth(GL_FALSE, GL_TRUE); + gGL.diffuseColor4f(0,0.5f,0.5f,1); + drawBoxOutline(pos, size); + } + } +} + + +void renderAvatarCollisionVolumes(LLVOAvatar* avatar) +{ + avatar->renderCollisionVolumes(); +} + +void renderAvatarBones(LLVOAvatar* avatar) +{ + avatar->renderBones(); +} + +void renderAgentTarget(LLVOAvatar* avatar) +{ + // render these for self only (why, i don't know) + if (avatar->isSelf()) + { + renderCrossHairs(avatar->getPositionAgent(), 0.2f, LLColor4(1, 0, 0, 0.8f)); + renderCrossHairs(avatar->mDrawable->getPositionAgent(), 0.2f, LLColor4(0, 1, 0, 0.8f)); + renderCrossHairs(avatar->mRoot->getWorldPosition(), 0.2f, LLColor4(1, 1, 1, 0.8f)); + renderCrossHairs(avatar->mPelvisp->getWorldPosition(), 0.2f, LLColor4(0, 0, 1, 0.8f)); + } +} + +static void setTextureAreaDebugText(LLDrawable* drawablep) +{ + LLVOVolume* vobjp = drawablep->getVOVolume(); + + if (vobjp) + { + if (drawablep->mDistanceWRTCamera < 32.f) + { + std::ostringstream str; + + //for (S32 i = 0; i < vobjp->getNumTEs(); ++i) + S32 i = 0; + { + if (i < drawablep->getNumFaces()) + { + LLFace* facep = drawablep->getFace(i); + + if (facep) + { + LLViewerTexture* imagep = facep->getTexture(); + + if (imagep) + { + str << llformat("D - %.2f", sqrtf(imagep->getMaxVirtualSize())); + } + + imagep = vobjp->getTENormalMap(i); + + if (imagep && imagep != LLViewerFetchedTexture::sDefaultImagep) + { + str << llformat("\nN - %.2f", sqrtf(imagep->getMaxVirtualSize())); + } + + imagep = vobjp->getTESpecularMap(i); + + if (imagep && imagep != LLViewerFetchedTexture::sDefaultImagep) + { + str << llformat("\nS - %.2f", sqrtf(imagep->getMaxVirtualSize())); + } + + str << "\n\n"; + } + + vobjp->setDebugText(str.str()); + } + } + } + else + { + vobjp->setDebugText("."); + } + } +} + +class LLOctreeRenderNonOccluded : public OctreeTraveler +{ +public: + LLCamera* mCamera; + LLOctreeRenderNonOccluded(LLCamera* camera): mCamera(camera) {} + + virtual void traverse(const OctreeNode* node) + { + LLSpatialGroup* group = (LLSpatialGroup*) node->getListener(0); + + const LLVector4a* bounds = group->getBounds(); + if (!mCamera || mCamera->AABBInFrustumNoFarClip(bounds[0], bounds[1])) + { + node->accept(this); + stop_glerror(); + + for (U32 i = 0; i < node->getChildCount(); i++) + { + traverse(node->getChild(i)); + stop_glerror(); + } + + //draw tight fit bounding boxes for spatial group + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_OCTREE)) + { + group->rebuildGeom(); + group->rebuildMesh(); + + renderOctree(group); + stop_glerror(); + } + } + } + + virtual void visit(const OctreeNode* branch) + { + LLSpatialGroup* group = (LLSpatialGroup*) branch->getListener(0); + const LLVector4a* bounds = group->getBounds(); + if (group->hasState(LLSpatialGroup::GEOM_DIRTY) || (mCamera && !mCamera->AABBInFrustumNoFarClip(bounds[0], bounds[1]))) + { + return; + } + + group->rebuildGeom(); + group->rebuildMesh(); + + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_BBOXES)) + { + if (!group->isEmpty()) + { + gGL.diffuseColor3f(0,0,1); + const LLVector4a* obj_bounds = group->getObjectBounds(); + drawBoxOutline(obj_bounds[0], obj_bounds[1]); + } + } + + for (OctreeNode::const_element_iter i = branch->getDataBegin(); i != branch->getDataEnd(); ++i) + { + LLDrawable* drawable = (LLDrawable*)(*i)->getDrawable(); + if(!drawable || drawable->isDead()) + { + continue; + } + + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_BBOXES)) + { + renderBoundingBox(drawable); + } + + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_NORMALS)) + { + renderNormals(drawable); + } + + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_AREA)) + { + setTextureAreaDebugText(drawable); + } + + /*if (drawable->getVOVolume() && gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_PRIORITY)) + { + renderTexturePriority(drawable); + }*/ + + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_POINTS)) + { + renderPoints(drawable); + } + + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_LIGHTS)) + { + renderLights(drawable); + } + + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_RAYCAST)) + { + renderRaycast(drawable); + } + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_UPDATE_TYPE)) + { + renderUpdateType(drawable); + } + if(gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXEL_DENSITY)) + { + renderTexelDensity(drawable); + } + + LLVOAvatar* avatar = dynamic_cast(drawable->getVObj().get()); + + if (avatar && gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_AVATAR_VOLUME)) + { + renderAvatarCollisionVolumes(avatar); + } + + if (avatar && gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_AVATAR_JOINTS)) + { + renderAvatarBones(avatar); + } + + if (avatar && gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_AGENT_TARGET)) + { + renderAgentTarget(avatar); + } + +#if 0 + if (gDebugGL) + { + for (U32 i = 0; i < drawable->getNumFaces(); ++i) + { + LLFace* facep = drawable->getFace(i); + if (facep) + { + U8 index = facep->getTextureIndex(); + if (facep->mDrawInfo) + { + if (index < FACE_DO_NOT_BATCH_TEXTURES) + { + if (facep->mDrawInfo->mTextureList.size() <= index) + { + LL_ERRS() << "Face texture index out of bounds." << LL_ENDL; + } + else if (facep->mDrawInfo->mTextureList[index] != facep->getTexture()) + { + LL_ERRS() << "Face texture index incorrect." << LL_ENDL; + } + } + } + } + } + } +#endif + } + + for (LLSpatialGroup::draw_map_t::iterator i = group->mDrawMap.begin(); i != group->mDrawMap.end(); ++i) + { + LLSpatialGroup::drawmap_elem_t& draw_vec = i->second; + for (LLSpatialGroup::drawmap_elem_t::iterator j = draw_vec.begin(); j != draw_vec.end(); ++j) + { + LLDrawInfo* draw_info = *j; + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_ANIM)) + { + renderTextureAnim(draw_info); + } + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_BATCH_SIZE)) + { + renderBatchSize(draw_info); + } + } + } + } +}; + +class LLOctreeRenderXRay : public OctreeTraveler +{ +public: + LLCamera* mCamera; + LLOctreeRenderXRay(LLCamera* camera): mCamera(camera) {} + + virtual void traverse(const OctreeNode* node) + { + LLSpatialGroup* group = (LLSpatialGroup*) node->getListener(0); + + const LLVector4a* bounds = group->getBounds(); + if (!mCamera || mCamera->AABBInFrustumNoFarClip(bounds[0], bounds[1])) + { + node->accept(this); + stop_glerror(); + + for (U32 i = 0; i < node->getChildCount(); i++) + { + traverse(node->getChild(i)); + stop_glerror(); + } + + //render visibility wireframe + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_OCCLUSION)) + { + group->rebuildGeom(); + group->rebuildMesh(); + + gGL.flush(); + gGL.pushMatrix(); + gGLLastMatrix = NULL; + gGL.loadMatrix(gGLModelView); + renderXRay(group, mCamera); + stop_glerror(); + gGLLastMatrix = NULL; + gGL.popMatrix(); + } + } + } + + virtual void visit(const OctreeNode* node) {} + +}; + +class LLOctreeRenderPhysicsShapes : public OctreeTraveler +{ +public: + LLCamera* mCamera; + bool mWireframe; + + LLOctreeRenderPhysicsShapes(LLCamera* camera, bool wireframe): mCamera(camera), mWireframe(wireframe) {} + + virtual void traverse(const OctreeNode* node) + { + LLSpatialGroup* group = (LLSpatialGroup*) node->getListener(0); + + const LLVector4a* bounds = group->getBounds(); + if (!mCamera || mCamera->AABBInFrustumNoFarClip(bounds[0], bounds[1])) + { + node->accept(this); + stop_glerror(); + + for (U32 i = 0; i < node->getChildCount(); i++) + { + traverse(node->getChild(i)); + stop_glerror(); + } + + group->rebuildGeom(); + group->rebuildMesh(); + + renderPhysicsShapes(group, mWireframe); + } + } + + virtual void visit(const OctreeNode* branch) + { + + } +}; + +class LLOctreePushBBoxVerts : public OctreeTraveler +{ +public: + LLCamera* mCamera; + LLOctreePushBBoxVerts(LLCamera* camera): mCamera(camera) {} + + virtual void traverse(const OctreeNode* node) + { + LLSpatialGroup* group = (LLSpatialGroup*) node->getListener(0); + + const LLVector4a* bounds = group->getBounds(); + if (!mCamera || mCamera->AABBInFrustum(bounds[0], bounds[1])) + { + node->accept(this); + + for (U32 i = 0; i < node->getChildCount(); i++) + { + traverse(node->getChild(i)); + } + } + } + + virtual void visit(const OctreeNode* branch) + { + LLSpatialGroup* group = (LLSpatialGroup*) branch->getListener(0); + + const LLVector4a* bounds = group->getBounds(); + if (group->hasState(LLSpatialGroup::GEOM_DIRTY) || (mCamera && !mCamera->AABBInFrustumNoFarClip(bounds[0], bounds[1]))) + { + return; + } + + for (OctreeNode::const_element_iter i = branch->getDataBegin(); i != branch->getDataEnd(); ++i) + { + LLDrawable* drawable = (LLDrawable*)(*i)->getDrawable(); + if(!drawable) + { + continue; + } + renderBoundingBox(drawable, false); + } + } +}; + +void LLSpatialPartition::renderIntersectingBBoxes(LLCamera* camera) +{ + LLOctreePushBBoxVerts pusher(camera); + pusher.traverse(mOctree); +} + +class LLOctreeStateCheck : public OctreeTraveler +{ +public: + U32 mInheritedMask[LLViewerCamera::NUM_CAMERAS]; + + LLOctreeStateCheck() + { + for (U32 i = 0; i < LLViewerCamera::NUM_CAMERAS; i++) + { + mInheritedMask[i] = 0; + } + } + + virtual void traverse(const OctreeNode* node) + { + LLSpatialGroup* group = (LLSpatialGroup*) node->getListener(0); + + node->accept(this); + + + U32 temp[LLViewerCamera::NUM_CAMERAS]; + + for (U32 i = 0; i < LLViewerCamera::NUM_CAMERAS; i++) + { + temp[i] = mInheritedMask[i]; + mInheritedMask[i] |= group->mOcclusionState[i] & LLSpatialGroup::OCCLUDED; + } + + for (U32 i = 0; i < node->getChildCount(); i++) + { + traverse(node->getChild(i)); + } + + for (U32 i = 0; i < LLViewerCamera::NUM_CAMERAS; i++) + { + mInheritedMask[i] = temp[i]; + } + } + + + virtual void visit(const OctreeNode* state) + { + LLSpatialGroup* group = (LLSpatialGroup*) state->getListener(0); + + for (U32 i = 0; i < LLViewerCamera::NUM_CAMERAS; i++) + { + if (mInheritedMask[i] && !(group->mOcclusionState[i] & mInheritedMask[i])) + { + LL_ERRS() << "Spatial group failed inherited mask test." << LL_ENDL; + } + } + + if (group->hasState(LLSpatialGroup::DIRTY)) + { + assert_parent_state(group, LLSpatialGroup::DIRTY); + } + } + + void assert_parent_state(LLSpatialGroup* group, U32 state) + { + LLSpatialGroup* parent = group->getParent(); + while (parent) + { + if (!parent->hasState(state)) + { + LL_ERRS() << "Spatial group failed parent state check." << LL_ENDL; + } + parent = parent->getParent(); + } + } +}; + + +void LLSpatialPartition::renderPhysicsShapes(bool wireframe) +{ + LLSpatialBridge* bridge = asBridge(); + LLCamera* camera = LLViewerCamera::getInstance(); + + if (bridge) + { + camera = NULL; + } + + gGL.flush(); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + LLOctreeRenderPhysicsShapes render_physics(camera, wireframe); + render_physics.traverse(mOctree); + gGL.flush(); +} + +void LLSpatialPartition::renderDebug() +{ + if (!gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_OCTREE | + LLPipeline::RENDER_DEBUG_OCCLUSION | + LLPipeline::RENDER_DEBUG_LIGHTS | + LLPipeline::RENDER_DEBUG_BATCH_SIZE | + LLPipeline::RENDER_DEBUG_UPDATE_TYPE | + LLPipeline::RENDER_DEBUG_BBOXES | + LLPipeline::RENDER_DEBUG_NORMALS | + LLPipeline::RENDER_DEBUG_POINTS | + LLPipeline::RENDER_DEBUG_TEXTURE_AREA | + LLPipeline::RENDER_DEBUG_TEXTURE_ANIM | + LLPipeline::RENDER_DEBUG_RAYCAST | + LLPipeline::RENDER_DEBUG_AVATAR_VOLUME | + LLPipeline::RENDER_DEBUG_AVATAR_JOINTS | + LLPipeline::RENDER_DEBUG_AGENT_TARGET | + LLPipeline::RENDER_DEBUG_SHADOW_FRUSTA | + LLPipeline::RENDER_DEBUG_TEXEL_DENSITY)) + { + return; + } + + gDebugProgram.bind(); + + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_PRIORITY)) + { + //sLastMaxTexPriority = lerp(sLastMaxTexPriority, sCurMaxTexPriority, gFrameIntervalSeconds); + sLastMaxTexPriority = (F32) LLViewerCamera::getInstance()->getScreenPixelArea(); + sCurMaxTexPriority = 0.f; + } + + LLGLDisable cullface(GL_CULL_FACE); + LLGLEnable blend(GL_BLEND); + gGL.setSceneBlendType(LLRender::BT_ALPHA); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gPipeline.disableLights(); + + LLSpatialBridge* bridge = asBridge(); + LLCamera* camera = LLViewerCamera::getInstance(); + + if (bridge) + { + camera = NULL; + } + + LLOctreeStateCheck checker; + checker.traverse(mOctree); + + LLOctreeRenderNonOccluded render_debug(camera); + render_debug.traverse(mOctree); + + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_OCCLUSION)) + { + { + LLGLEnable cull(GL_CULL_FACE); + + LLGLEnable blend(GL_BLEND); + LLGLDepthTest depth_under(GL_TRUE, GL_FALSE, GL_GREATER); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + gGL.diffuseColor4f(0.5f, 0.0f, 0, 0.25f); + + LLGLEnable offset(GL_POLYGON_OFFSET_LINE); + glPolygonOffset(-1.f, -1.f); + + LLOctreeRenderXRay xray(camera); + xray.traverse(mOctree); + + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } + } + gDebugProgram.unbind(); +} + +void LLSpatialGroup::drawObjectBox(LLColor4 col) +{ + gGL.diffuseColor4fv(col.mV); + LLVector4a size; + size = mObjectBounds[1]; + size.mul(1.01f); + size.add(LLVector4a(0.001f)); + drawBox(mObjectBounds[0], size); +} + +bool LLSpatialPartition::isHUDPartition() +{ + return mPartitionType == LLViewerRegion::PARTITION_HUD ; +} + +bool LLSpatialPartition::isVisible(const LLVector3& v) +{ + if (!LLViewerCamera::getInstance()->sphereInFrustum(v, 4.0f)) + { + return false; + } + + return true; +} + +LL_ALIGN_PREFIX(16) +class LLOctreeIntersect : public LLOctreeTraveler> +{ +public: + LL_ALIGN_16(LLVector4a mStart); + LL_ALIGN_16(LLVector4a mEnd); + + S32 *mFaceHit; + LLVector4a *mIntersection; + LLVector2 *mTexCoord; + LLVector4a *mNormal; + LLVector4a *mTangent; + LLDrawable* mHit; + bool mPickTransparent; + bool mPickRigged; + bool mPickUnselectable; + bool mPickReflectionProbe; + + LLOctreeIntersect(const LLVector4a& start, const LLVector4a& end, bool pick_transparent, bool pick_rigged, bool pick_unselectable, bool pick_reflection_probe, + S32* face_hit, LLVector4a* intersection, LLVector2* tex_coord, LLVector4a* normal, LLVector4a* tangent) + : mStart(start), + mEnd(end), + mFaceHit(face_hit), + mIntersection(intersection), + mTexCoord(tex_coord), + mNormal(normal), + mTangent(tangent), + mHit(NULL), + mPickTransparent(pick_transparent), + mPickRigged(pick_rigged), + mPickUnselectable(pick_unselectable), + mPickReflectionProbe(pick_reflection_probe) + { + } + + virtual void visit(const OctreeNode* branch) + { + for (OctreeNode::const_element_iter i = branch->getDataBegin(); i != branch->getDataEnd(); ++i) + { + check(*i); + } + } + + virtual LLDrawable* check(const OctreeNode* node) + { + node->accept(this); + + for (U32 i = 0; i < node->getChildCount(); i++) + { + const OctreeNode* child = node->getChild(i); + LLVector3 res; + + LLSpatialGroup* group = (LLSpatialGroup*) child->getListener(0); + + LLVector4a size; + LLVector4a center; + + const LLVector4a* bounds = group->getBounds(); + size = bounds[1]; + center = bounds[0]; + + LLVector4a local_start = mStart; + LLVector4a local_end = mEnd; + + if (group->getSpatialPartition()->isBridge()) + { + LLMatrix4 local_matrix = group->getSpatialPartition()->asBridge()->mDrawable->getRenderMatrix(); + local_matrix.invert(); + + LLMatrix4a local_matrix4a; + local_matrix4a.loadu(local_matrix); + + local_matrix4a.affineTransform(mStart, local_start); + local_matrix4a.affineTransform(mEnd, local_end); + } + + if (LLLineSegmentBoxIntersect(local_start, local_end, center, size)) + { + check(child); + } + } + + return mHit; + } + + virtual bool check(LLViewerOctreeEntry* entry) + { + LLDrawable* drawable = (LLDrawable*)entry->getDrawable(); + + if (!drawable || !gPipeline.hasRenderType(drawable->getRenderType()) || !drawable->isVisible()) + { + return false; + } + + if (drawable->isSpatialBridge()) + { + LLSpatialPartition *part = drawable->asPartition(); + LLSpatialBridge* bridge = part->asBridge(); + if (bridge && gPipeline.hasRenderType(bridge->mDrawableType)) + { + check(part->mOctree); + } + } + else + { + LLViewerObject* vobj = drawable->getVObj(); + + if (vobj && + (!vobj->isReflectionProbe() || mPickReflectionProbe)) + { + if (vobj->getClickAction() == CLICK_ACTION_IGNORE && !LLFloater::isVisible(gFloaterTools)) + { + return false; + } + + LLVector4a intersection; + bool skip_check = false; + if (vobj->isAvatar()) + { + LLVOAvatar* avatar = (LLVOAvatar*) vobj; + if ((mPickRigged) || ((avatar->isSelf()) && (LLFloater::isVisible(gFloaterTools)))) + { + LLViewerObject* hit = avatar->lineSegmentIntersectRiggedAttachments(mStart, mEnd, -1, mPickTransparent, mPickRigged, mPickUnselectable, mFaceHit, &intersection, mTexCoord, mNormal, mTangent); + if (hit) + { + mEnd = intersection; + if (mIntersection) + { + *mIntersection = intersection; + } + + mHit = hit->mDrawable; + skip_check = true; + } + + } + } + + if (!skip_check && vobj->lineSegmentIntersect(mStart, mEnd, -1, + (mPickReflectionProbe && vobj->isReflectionProbe()) ? true : mPickTransparent, // always pick transparent when picking selection probe + mPickRigged, mPickUnselectable, mFaceHit, &intersection, mTexCoord, mNormal, mTangent)) + { + mEnd = intersection; // shorten ray so we only find CLOSER hits + if (mIntersection) + { + *mIntersection = intersection; + } + + mHit = vobj->mDrawable; + } + } + } + + return false; + } +} LL_ALIGN_POSTFIX(16); + +LLDrawable* LLSpatialPartition::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, + bool pick_transparent, + bool pick_rigged, + bool pick_unselectable, + bool pick_reflection_probe, + S32* face_hit, // return the face hit + LLVector4a* intersection, // return the intersection point + LLVector2* tex_coord, // return the texture coordinates of the intersection point + LLVector4a* normal, // return the surface normal at the intersection point + LLVector4a* tangent // return the surface tangent at the intersection point + ) + +{ + LLOctreeIntersect intersect(start, end, pick_transparent, pick_rigged, pick_unselectable, pick_reflection_probe, face_hit, intersection, tex_coord, normal, tangent); + LLDrawable* drawable = intersect.check(mOctree); + + return drawable; +} + +LLDrawable* LLSpatialGroup::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, + bool pick_transparent, + bool pick_rigged, + bool pick_unselectable, + bool pick_reflection_probe, + S32* face_hit, // return the face hit + LLVector4a* intersection, // return the intersection point + LLVector2* tex_coord, // return the texture coordinates of the intersection point + LLVector4a* normal, // return the surface normal at the intersection point + LLVector4a* tangent // return the surface tangent at the intersection point +) + +{ + LLOctreeIntersect intersect(start, end, pick_transparent, pick_rigged, pick_unselectable, pick_reflection_probe, face_hit, intersection, tex_coord, normal, tangent); + LLDrawable* drawable = intersect.check(getOctreeNode()); + + return drawable; +} + +LLDrawInfo::LLDrawInfo(U16 start, U16 end, U32 count, U32 offset, + LLViewerTexture* texture, LLVertexBuffer* buffer, + bool fullbright, U8 bump) +: mVertexBuffer(buffer), + mTexture(texture), + mStart(start), + mEnd(end), + mCount(count), + mOffset(offset), + mFullbright(fullbright), + mBump(bump), + mBlendFuncSrc(LLRender::BF_SOURCE_ALPHA), + mBlendFuncDst(LLRender::BF_ONE_MINUS_SOURCE_ALPHA), + mHasGlow(false), + mEnvIntensity(0.0f), + mAlphaMaskCutoff(0.5f) +{ + mVertexBuffer->validateRange(mStart, mEnd, mCount, mOffset); +} + +LLDrawInfo::~LLDrawInfo() +{ + if (gDebugGL) + { + gPipeline.checkReferences(this); + } +} + +LLColor4U LLDrawInfo::getDebugColor() const +{ + LLColor4U color; + + LLCRC hash; + hash.update((U8*)this + sizeof(S32), sizeof(LLDrawInfo) - sizeof(S32)); + + *((U32*) color.mV) = hash.getCRC(); + + color.mV[3] = 200; + + return color; +} + +void LLDrawInfo::validate() +{ + mVertexBuffer->validateRange(mStart, mEnd, mCount, mOffset); +} + +U64 LLDrawInfo::getSkinHash() +{ + return mSkinInfo ? mSkinInfo->mHash : 0; +} + +LLCullResult::LLCullResult() +{ + mVisibleGroupsAllocated = 0; + mAlphaGroupsAllocated = 0; + mRiggedAlphaGroupsAllocated = 0; + mOcclusionGroupsAllocated = 0; + mDrawableGroupsAllocated = 0; + mVisibleListAllocated = 0; + mVisibleBridgeAllocated = 0; + + mVisibleGroups.clear(); + mVisibleGroups.push_back(NULL); + mVisibleGroupsEnd = &mVisibleGroups[0]; + mAlphaGroups.clear(); + mAlphaGroups.push_back(NULL); + mAlphaGroupsEnd = &mAlphaGroups[0]; + mRiggedAlphaGroups.clear(); + mRiggedAlphaGroups.push_back(NULL); + mRiggedAlphaGroupsEnd = &mRiggedAlphaGroups[0]; + mOcclusionGroups.clear(); + mOcclusionGroups.push_back(NULL); + mOcclusionGroupsEnd = &mOcclusionGroups[0]; + mDrawableGroups.clear(); + mDrawableGroups.push_back(NULL); + mDrawableGroupsEnd = &mDrawableGroups[0]; + mVisibleList.clear(); + mVisibleList.push_back(NULL); + mVisibleListEnd = &mVisibleList[0]; + mVisibleBridge.clear(); + mVisibleBridge.push_back(NULL); + mVisibleBridgeEnd = &mVisibleBridge[0]; + + for (U32 i = 0; i < LLRenderPass::NUM_RENDER_TYPES; i++) + { + mRenderMap[i].clear(); + mRenderMap[i].push_back(NULL); + mRenderMapEnd[i] = &mRenderMap[i][0]; + mRenderMapAllocated[i] = 0; + } + + clear(); +} + +template +void LLCullResult::pushBack(T& head, U32& count, V* val) +{ + head[count] = val; + head.push_back(NULL); + count++; +} + +void LLCullResult::clear() +{ + mVisibleGroupsSize = 0; + mVisibleGroupsEnd = &mVisibleGroups[0]; + + mAlphaGroupsSize = 0; + mAlphaGroupsEnd = &mAlphaGroups[0]; + + mRiggedAlphaGroupsSize = 0; + mRiggedAlphaGroupsEnd = &mRiggedAlphaGroups[0]; + + mOcclusionGroupsSize = 0; + mOcclusionGroupsEnd = &mOcclusionGroups[0]; + + mDrawableGroupsSize = 0; + mDrawableGroupsEnd = &mDrawableGroups[0]; + + mVisibleListSize = 0; + mVisibleListEnd = &mVisibleList[0]; + + mVisibleBridgeSize = 0; + mVisibleBridgeEnd = &mVisibleBridge[0]; + + + for (U32 i = 0; i < LLRenderPass::NUM_RENDER_TYPES; i++) + { + for (U32 j = 0; j < mRenderMapSize[i]; j++) + { + mRenderMap[i][j] = 0; + } + mRenderMapSize[i] = 0; + mRenderMapEnd[i] = &(mRenderMap[i][0]); + } +} + +LLCullResult::sg_iterator LLCullResult::beginVisibleGroups() +{ + return &mVisibleGroups[0]; +} + +LLCullResult::sg_iterator LLCullResult::endVisibleGroups() +{ + return mVisibleGroupsEnd; +} + +LLCullResult::sg_iterator LLCullResult::beginAlphaGroups() +{ + return &mAlphaGroups[0]; +} + +LLCullResult::sg_iterator LLCullResult::endAlphaGroups() +{ + return mAlphaGroupsEnd; +} + +LLCullResult::sg_iterator LLCullResult::beginRiggedAlphaGroups() +{ + return &mRiggedAlphaGroups[0]; +} + +LLCullResult::sg_iterator LLCullResult::endRiggedAlphaGroups() +{ + return mRiggedAlphaGroupsEnd; +} + +LLCullResult::sg_iterator LLCullResult::beginOcclusionGroups() +{ + return &mOcclusionGroups[0]; +} + +LLCullResult::sg_iterator LLCullResult::endOcclusionGroups() +{ + return mOcclusionGroupsEnd; +} + +LLCullResult::sg_iterator LLCullResult::beginDrawableGroups() +{ + return &mDrawableGroups[0]; +} + +LLCullResult::sg_iterator LLCullResult::endDrawableGroups() +{ + return mDrawableGroupsEnd; +} + +LLCullResult::drawable_iterator LLCullResult::beginVisibleList() +{ + return &mVisibleList[0]; +} + +LLCullResult::drawable_iterator LLCullResult::endVisibleList() +{ + return mVisibleListEnd; +} + +LLCullResult::bridge_iterator LLCullResult::beginVisibleBridge() +{ + return &mVisibleBridge[0]; +} + +LLCullResult::bridge_iterator LLCullResult::endVisibleBridge() +{ + return mVisibleBridgeEnd; +} + +LLCullResult::drawinfo_iterator LLCullResult::beginRenderMap(U32 type) +{ + return &mRenderMap[type][0]; +} + +LLCullResult::drawinfo_iterator LLCullResult::endRenderMap(U32 type) +{ + return mRenderMapEnd[type]; +} + +void LLCullResult::pushVisibleGroup(LLSpatialGroup* group) +{ + if (mVisibleGroupsSize < mVisibleGroupsAllocated) + { + mVisibleGroups[mVisibleGroupsSize] = group; + } + else + { + pushBack(mVisibleGroups, mVisibleGroupsAllocated, group); + } + ++mVisibleGroupsSize; + mVisibleGroupsEnd = &mVisibleGroups[mVisibleGroupsSize]; +} + +void LLCullResult::pushAlphaGroup(LLSpatialGroup* group) +{ + if (mAlphaGroupsSize < mAlphaGroupsAllocated) + { + mAlphaGroups[mAlphaGroupsSize] = group; + } + else + { + pushBack(mAlphaGroups, mAlphaGroupsAllocated, group); + } + ++mAlphaGroupsSize; + mAlphaGroupsEnd = &mAlphaGroups[mAlphaGroupsSize]; +} + +void LLCullResult::pushRiggedAlphaGroup(LLSpatialGroup* group) +{ + if (mRiggedAlphaGroupsSize < mRiggedAlphaGroupsAllocated) + { + mRiggedAlphaGroups[mRiggedAlphaGroupsSize] = group; + } + else + { + pushBack(mRiggedAlphaGroups, mRiggedAlphaGroupsAllocated, group); + } + ++mRiggedAlphaGroupsSize; + mRiggedAlphaGroupsEnd = &mRiggedAlphaGroups[mRiggedAlphaGroupsSize]; +} + +void LLCullResult::pushOcclusionGroup(LLSpatialGroup* group) +{ + if (mOcclusionGroupsSize < mOcclusionGroupsAllocated) + { + mOcclusionGroups[mOcclusionGroupsSize] = group; + } + else + { + pushBack(mOcclusionGroups, mOcclusionGroupsAllocated, group); + } + ++mOcclusionGroupsSize; + mOcclusionGroupsEnd = &mOcclusionGroups[mOcclusionGroupsSize]; +} + +void LLCullResult::pushDrawableGroup(LLSpatialGroup* group) +{ +#if LL_DEBUG_CULL_RESULT + // group must NOT be in the drawble groups list already + llassert(std::find(&mDrawableGroups[0], mDrawableGroupsEnd, group) == mDrawableGroupsEnd); +#endif + if (mDrawableGroupsSize < mDrawableGroupsAllocated) + { + mDrawableGroups[mDrawableGroupsSize] = group; + } + else + { + pushBack(mDrawableGroups, mDrawableGroupsAllocated, group); + } + ++mDrawableGroupsSize; + mDrawableGroupsEnd = &mDrawableGroups[mDrawableGroupsSize]; +} + +void LLCullResult::pushDrawable(LLDrawable* drawable) +{ +#if LL_DEBUG_CULL_RESULT + // drawable must NOT be in the visible list already + llassert(std::find(&mVisibleList[0], mVisibleListEnd, drawable) == mVisibleListEnd); +#endif + if (mVisibleListSize < mVisibleListAllocated) + { + mVisibleList[mVisibleListSize] = drawable; + } + else + { + pushBack(mVisibleList, mVisibleListAllocated, drawable); + } + ++mVisibleListSize; + mVisibleListEnd = &mVisibleList[mVisibleListSize]; +} + +void LLCullResult::pushBridge(LLSpatialBridge* bridge) +{ + if (mVisibleBridgeSize < mVisibleBridgeAllocated) + { + mVisibleBridge[mVisibleBridgeSize] = bridge; + } + else + { + pushBack(mVisibleBridge, mVisibleBridgeAllocated, bridge); + } + ++mVisibleBridgeSize; + mVisibleBridgeEnd = &mVisibleBridge[mVisibleBridgeSize]; +} + +void LLCullResult::pushDrawInfo(U32 type, LLDrawInfo* draw_info) +{ + if (mRenderMapSize[type] < mRenderMapAllocated[type]) + { + mRenderMap[type][mRenderMapSize[type]] = draw_info; + } + else + { + pushBack(mRenderMap[type], mRenderMapAllocated[type], draw_info); + } + ++mRenderMapSize[type]; + mRenderMapEnd[type] = &(mRenderMap[type][mRenderMapSize[type]]); +} + + +void LLCullResult::assertDrawMapsEmpty() +{ + for (U32 i = 0; i < LLRenderPass::NUM_RENDER_TYPES; i++) + { + if (mRenderMapSize[i] != 0) + { + LL_ERRS() << "Stale LLDrawInfo's in LLCullResult!" + << " (mRenderMapSize[" << i << "] = " << mRenderMapSize[i] << ")" << LL_ENDL; + } + } +} + diff --git a/indra/newview/llspatialpartition.h b/indra/newview/llspatialpartition.h index f73e32294a..c074d6a89a 100644 --- a/indra/newview/llspatialpartition.h +++ b/indra/newview/llspatialpartition.h @@ -1,779 +1,779 @@ -/** - * @file llspatialpartition.h - * @brief LLSpatialGroup header file including definitions for supporting functions - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLSPATIALPARTITION_H -#define LL_LLSPATIALPARTITION_H - -#define SG_MIN_DIST_RATIO 0.00001f - -#include "lldrawable.h" -#include "lloctree.h" -#include "llpointer.h" -#include "llrefcount.h" -#include "llvertexbuffer.h" -#include "llgltypes.h" -#include "llcubemap.h" -#include "lldrawpool.h" -#include "llface.h" -#include "llviewercamera.h" -#include "llvector4a.h" -#include "llvoavatar.h" -#include "llfetchedgltfmaterial.h" - -#include -#include - -#define SG_STATE_INHERIT_MASK (OCCLUDED) -#define SG_INITIAL_STATE_MASK (DIRTY | GEOM_DIRTY) - -class LLViewerOctreePartition; -class LLSpatialPartition; -class LLSpatialBridge; -class LLSpatialGroup; -class LLViewerRegion; -class LLReflectionMap; - -void pushVerts(LLFace* face); - -/* - Class that represents a single Draw Call - - Make every effort to keep size minimal. - Member ordering is important for cache coherency -*/ -class LLDrawInfo final : public LLRefCount -{ - LL_ALIGN_NEW; -protected: - ~LLDrawInfo(); - -public: - LLDrawInfo(const LLDrawInfo& rhs) - { - *this = rhs; - } - - const LLDrawInfo& operator=(const LLDrawInfo& rhs) - { - LL_ERRS() << "Illegal operation!" << LL_ENDL; - return *this; - } - - // return a hash of this LLDrawInfo as a debug color - LLColor4U getDebugColor() const; - - LLDrawInfo(U16 start, U16 end, U32 count, U32 offset, - LLViewerTexture* image, LLVertexBuffer* buffer, - bool fullbright = false, U8 bump = 0); - - - void validate(); - - // return mSkinHash->mHash, or 0 if mSkinHash is null - U64 getSkinHash(); - - LLPointer mVertexBuffer; - U16 mStart = 0; - U16 mEnd = 0; - U32 mCount = 0; - U32 mOffset = 0; - - LLPointer mTexture; - LLPointer mSpecularMap; - LLPointer mNormalMap; - - const LLMatrix4* mSpecularMapMatrix = nullptr; - const LLMatrix4* mNormalMapMatrix = nullptr; - const LLMatrix4* mTextureMatrix = nullptr; - const LLMatrix4* mModelMatrix = nullptr; - - LLPointer mAvatar = nullptr; - LLMeshSkinInfo* mSkinInfo = nullptr; - - // Material pointer here is likely for debugging only and are immaterial (zing!) - LLPointer mMaterial; - - // PBR material parameters - LLPointer mGLTFMaterial; - - LLVector4 mSpecColor = LLVector4(1.f, 1.f, 1.f, 0.5f); // XYZ = Specular RGB, W = Specular Exponent - - std::vector > mTextureList; - - LLUUID mMaterialID; // id of LLGLTFMaterial or LLMaterial applied to this draw info - - U32 mShaderMask = 0; - F32 mEnvIntensity = 0.f; - F32 mAlphaMaskCutoff = 0.5f; - - LLRender::eBlendFactor mBlendFuncSrc = LLRender::BF_SOURCE_ALPHA; - LLRender::eBlendFactor mBlendFuncDst = LLRender::BF_ONE_MINUS_SOURCE_ALPHA; - U8 mDiffuseAlphaMode = 0; - U8 mBump = 0; - U8 mShiny = 0; - bool mFullbright = false; - bool mHasGlow = false; - - struct CompareTexture - { - bool operator()(const LLDrawInfo& lhs, const LLDrawInfo& rhs) - { - return lhs.mTexture > rhs.mTexture; - } - }; - - struct CompareTexturePtr - { //sort by texture - bool operator()(const LLPointer& lhs, const LLPointer& rhs) - { - // sort by pointer, sort NULL down to the end - return lhs.get() != rhs.get() - && (lhs.isNull() || (rhs.notNull() && lhs->mTexture.get() > rhs->mTexture.get())); - } - }; - - struct CompareVertexBuffer - { //sort by texture - bool operator()(const LLPointer& lhs, const LLPointer& rhs) - { - // sort by pointer, sort NULL down to the end - return lhs.get() != rhs.get() - && (lhs.isNull() || (rhs.notNull() && lhs->mVertexBuffer.get() > rhs->mVertexBuffer.get())); - } - }; - - struct CompareTexturePtrMatrix - { - bool operator()(const LLPointer& lhs, const LLPointer& rhs) - { - return lhs.get() != rhs.get() - && (lhs.isNull() || (rhs.notNull() && (lhs->mTexture.get() > rhs->mTexture.get() || - (lhs->mTexture.get() == rhs->mTexture.get() && lhs->mModelMatrix > rhs->mModelMatrix)))); - } - - }; - - struct CompareMatrixTexturePtr - { - bool operator()(const LLPointer& lhs, const LLPointer& rhs) - { - return lhs.get() != rhs.get() - && (lhs.isNull() || (rhs.notNull() && (lhs->mModelMatrix > rhs->mModelMatrix || - (lhs->mModelMatrix == rhs->mModelMatrix && lhs->mTexture.get() > rhs->mTexture.get())))); - } - - }; - - struct CompareBump - { - bool operator()(const LLPointer& lhs, const LLPointer& rhs) - { - // sort by mBump value, sort NULL down to the end - return lhs.get() != rhs.get() - && (lhs.isNull() || (rhs.notNull() && lhs->mBump > rhs->mBump)); - } - }; -}; - -LL_ALIGN_PREFIX(16) -class LLSpatialGroup : public LLOcclusionCullingGroup -{ - using super = LLOcclusionCullingGroup; - friend class LLSpatialPartition; - friend class LLOctreeStateCheck; -public: - - LLSpatialGroup(const LLSpatialGroup& rhs) : LLOcclusionCullingGroup(rhs) - { - *this = rhs; - } - - const LLSpatialGroup& operator=(const LLSpatialGroup& rhs) - { - LL_ERRS() << "Illegal operation!" << LL_ENDL; - return *this; - } - - static U32 sNodeCount; - static bool sNoDelete; //deletion of spatial groups and draw info not allowed if true - - typedef std::vector > sg_vector_t; - typedef std::vector > bridge_list_t; - typedef std::vector > drawmap_elem_t; - typedef std::unordered_map draw_map_t; - typedef std::vector > buffer_list_t; - typedef std::unordered_map buffer_texture_map_t; - typedef std::unordered_map buffer_map_t; - - struct CompareDistanceGreater - { - bool operator()(const LLSpatialGroup* const& lhs, const LLSpatialGroup* const& rhs) - { - return lhs->mDistance > rhs->mDistance; - } - }; - - struct CompareUpdateUrgency - { - bool operator()(const LLPointer lhs, const LLPointer rhs) - { - return lhs->getUpdateUrgency() > rhs->getUpdateUrgency(); - } - }; - - struct CompareDepthGreater - { - bool operator()(const LLSpatialGroup* const& lhs, const LLSpatialGroup* const& rhs) - { - return lhs->mDepth > rhs->mDepth; - } - }; - - struct CompareRenderOrder - { - bool operator()(const LLSpatialGroup* const& lhs, const LLSpatialGroup* const& rhs) - { - if (lhs->mAvatarp != rhs->mAvatarp) - { - return lhs->mAvatarp < rhs->mAvatarp; - } - - return lhs->mRenderOrder > rhs->mRenderOrder; - } - }; - - typedef enum - { - GEOM_DIRTY = LLViewerOctreeGroup::INVALID_STATE, - ALPHA_DIRTY = (GEOM_DIRTY << 1), - IN_IMAGE_QUEUE = (ALPHA_DIRTY << 1), - IMAGE_DIRTY = (IN_IMAGE_QUEUE << 1), - MESH_DIRTY = (IMAGE_DIRTY << 1), - NEW_DRAWINFO = (MESH_DIRTY << 1), - IN_BUILD_Q1 = (NEW_DRAWINFO << 1), - IN_BUILD_Q2 = (IN_BUILD_Q1 << 1), - STATE_MASK = 0x0000FFFF, - } eSpatialState; - - LLSpatialGroup(OctreeNode* node, LLSpatialPartition* part); - - bool isHUDGroup() ; - - void clearDrawMap(); - void validate(); - void validateDrawMap(); - - void setState(U32 state, S32 mode); - void clearState(U32 state, S32 mode); - void clearState(U32 state) {mState &= ~state;} - - LLSpatialGroup* getParent(); - - bool addObject(LLDrawable *drawablep); - bool removeObject(LLDrawable *drawablep, bool from_octree = false); - bool updateInGroup(LLDrawable *drawablep, bool immediate = false); // Update position if it's in the group - void expandExtents(const LLVector4a* addingExtents, const LLXformMatrix& currentTransform); - void shift(const LLVector4a &offset); - - // TODO: this no longer appears to be called, figure out if it's important and if not remove it - void destroyGLState(bool keep_occlusion = false); - - void updateDistance(LLCamera& camera); - F32 getUpdateUrgency() const; - bool changeLOD(); - void rebuildGeom(); - void rebuildMesh(); - - void setState(U32 state) {mState |= state;} - void dirtyGeom() { setState(GEOM_DIRTY); } - void dirtyMesh() { setState(MESH_DIRTY); } - - void drawObjectBox(LLColor4 col); - - LLDrawable* lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, - bool pick_transparent, - bool pick_rigged, - bool pick_unselectable, - bool pick_reflection_probe, - S32* face_hit, // return the face hit - LLVector4a* intersection = NULL, // return the intersection point - LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point - LLVector4a* normal = NULL, // return the surface normal at the intersection point - LLVector4a* tangent = NULL // return the surface tangent at the intersection point - ); - - - LLSpatialPartition* getSpatialPartition() {return (LLSpatialPartition*)mSpatialPartition;} - - //LISTENER FUNCTIONS - virtual void handleInsertion(const TreeNode* node, LLViewerOctreeEntry* face); - virtual void handleRemoval(const TreeNode* node, LLViewerOctreeEntry* face); - virtual void handleDestruction(const TreeNode* node); - virtual void handleChildAddition(const OctreeNode* parent, OctreeNode* child); - - // LLViewerOctreeGroup - virtual void rebound(); - -public: - LL_ALIGN_16(LLVector4a mViewAngle); - LL_ALIGN_16(LLVector4a mLastUpdateViewAngle); - -protected: - virtual ~LLSpatialGroup(); - -public: - LLPointer mVertexBuffer; - draw_map_t mDrawMap; - - bridge_list_t mBridgeList; - buffer_map_t mBufferMap; //used by volume buffers to attempt to reuse vertex buffers - - F32 mObjectBoxSize; //cached mObjectBounds[1].getLength3() - U32 mGeometryBytes; //used by volumes to track how many bytes of geometry data are in this node - F32 mSurfaceArea; //used by volumes to track estimated surface area of geometry in this node - F32 mBuilt; - - F32 mDistance; - F32 mDepth; - F32 mLastUpdateDistance; - F32 mLastUpdateTime; - - F32 mPixelArea; - F32 mRadius; - - //used by LLVOAVatar to set render order in alpha draw pool to preserve legacy render order behavior - LLVOAvatar* mAvatarp = nullptr; - U32 mRenderOrder = 0; - // Reflection Probe associated with this node (if any) - LLPointer mReflectionProbe = nullptr; -} LL_ALIGN_POSTFIX(16); - -class LLGeometryManager -{ -public: - std::vector mFaceList; - virtual ~LLGeometryManager() { } - virtual void rebuildGeom(LLSpatialGroup* group) = 0; - virtual void rebuildMesh(LLSpatialGroup* group) = 0; - virtual void getGeometry(LLSpatialGroup* group) = 0; - virtual void addGeometryCount(LLSpatialGroup* group, U32 &vertex_count, U32 &index_count); -}; - -class LLSpatialPartition: public LLViewerOctreePartition, public LLGeometryManager -{ -public: - LLSpatialPartition(U32 data_mask, bool render_by_group, LLViewerRegion* regionp); - virtual ~LLSpatialPartition(); - - LLSpatialGroup *put(LLDrawable *drawablep, bool was_visible = false); - bool remove(LLDrawable *drawablep, LLSpatialGroup *curp); - - LLDrawable* lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, - bool pick_transparent, - bool pick_rigged, - bool pick_unselectable, - bool pick_reflection_probe, - S32* face_hit, // return the face hit - LLVector4a* intersection = NULL, // return the intersection point - LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point - LLVector4a* normal = NULL, // return the surface normal at the intersection point - LLVector4a* tangent = NULL // return the surface tangent at the intersection point - ); - - - // If the drawable moves, move it here. - virtual void move(LLDrawable *drawablep, LLSpatialGroup *curp, bool immediate = false); - virtual void shift(const LLVector4a &offset); - - virtual F32 calcDistance(LLSpatialGroup* group, LLCamera& camera); - virtual F32 calcPixelArea(LLSpatialGroup* group, LLCamera& camera); - - virtual void rebuildGeom(LLSpatialGroup* group); - virtual void rebuildMesh(LLSpatialGroup* group); - - bool visibleObjectsInFrustum(LLCamera& camera); - /*virtual*/ S32 cull(LLCamera &camera, bool do_occlusion=false); // Cull on arbitrary frustum - S32 cull(LLCamera &camera, std::vector* results, bool for_select); // Cull on arbitrary frustum - - bool isVisible(const LLVector3& v); - bool isHUDPartition() ; - - LLSpatialBridge* asBridge() { return mBridge; } - bool isBridge() { return asBridge() != NULL; } - - void renderPhysicsShapes(bool depth_only); - void renderDebug(); - void renderIntersectingBBoxes(LLCamera* camera); - void restoreGL(); - - bool getVisibleExtents(LLCamera& camera, LLVector3& visMin, LLVector3& visMax); - -public: - LLSpatialBridge* mBridge; // NULL for non-LLSpatialBridge instances, otherwise, mBridge == this - // use a pointer instead of making "isBridge" and "asBridge" virtual so it's safe - // to call asBridge() from the destructor - - bool mInfiniteFarClip; // if true, frustum culling ignores far clip plane - const bool mRenderByGroup; - U32 mVertexDataMask; - F32 mSlopRatio; //percentage distance must change before drawables receive LOD update (default is 0.25); - bool mDepthMask; //if true, objects in this partition will be written to depth during alpha rendering -}; - -// class for creating bridges between spatial partitions -class LLSpatialBridge : public LLDrawable, public LLSpatialPartition -{ -protected: - ~LLSpatialBridge(); - -public: - typedef std::vector > bridge_vector_t; - - LLSpatialBridge(LLDrawable* root, bool render_by_group, U32 data_mask, LLViewerRegion* regionp); - - void destroyTree(); - - virtual bool isSpatialBridge() const { return true; } - virtual void updateSpatialExtents(); - virtual void updateBinRadius(); - virtual void setVisible(LLCamera& camera_in, std::vector* results = NULL, bool for_select = false); - virtual void updateDistance(LLCamera& camera_in, bool force_update); - virtual void makeActive(); - virtual void move(LLDrawable *drawablep, LLSpatialGroup *curp, bool immediate = false); - virtual bool updateMove(); - virtual void shiftPos(const LLVector4a& vec); - virtual void cleanupReferences(); - virtual LLSpatialPartition* asPartition() { return this; } - - //transform agent space camera into this Spatial Bridge's coordinate frame - virtual LLCamera transformCamera(LLCamera& camera); - - //transform agent space bounding box into this Spatial Bridge's coordinate frame - void transformExtents(const LLVector4a* src, LLVector4a* dst); - LLDrawable* mDrawable; -}; - -class LLCullResult -{ -public: - LLCullResult(); - - typedef std::vector sg_list_t; - typedef std::vector drawable_list_t; - typedef std::vector bridge_list_t; - typedef std::vector drawinfo_list_t; - - typedef LLSpatialGroup** sg_iterator; - typedef LLSpatialBridge** bridge_iterator; - typedef LLDrawInfo** drawinfo_iterator; - typedef LLDrawable** drawable_iterator; - - // Helper function for taking advantage of _mm_prefetch when iterating over cull results - static inline void increment_iterator(LLCullResult::drawinfo_iterator& i, const LLCullResult::drawinfo_iterator& end) - { - ++i; - - if (i != end) - { - _mm_prefetch((char*)(*i)->mVertexBuffer.get(), _MM_HINT_NTA); - - auto* ni = i + 1; - if (ni != end) - { - _mm_prefetch((char*)*ni, _MM_HINT_NTA); - } - } - } - - void clear(); - - sg_iterator beginVisibleGroups(); - sg_iterator endVisibleGroups(); - - sg_iterator beginAlphaGroups(); - sg_iterator endAlphaGroups(); - - sg_iterator beginRiggedAlphaGroups(); - sg_iterator endRiggedAlphaGroups(); - - bool hasOcclusionGroups() { return mOcclusionGroupsSize > 0; } - sg_iterator beginOcclusionGroups(); - sg_iterator endOcclusionGroups(); - - sg_iterator beginDrawableGroups(); - sg_iterator endDrawableGroups(); - - drawable_iterator beginVisibleList(); - drawable_iterator endVisibleList(); - - bridge_iterator beginVisibleBridge(); - bridge_iterator endVisibleBridge(); - - drawinfo_iterator beginRenderMap(U32 type); - drawinfo_iterator endRenderMap(U32 type); - - void pushVisibleGroup(LLSpatialGroup* group); - void pushAlphaGroup(LLSpatialGroup* group); - void pushRiggedAlphaGroup(LLSpatialGroup* group); - void pushOcclusionGroup(LLSpatialGroup* group); - void pushDrawableGroup(LLSpatialGroup* group); - void pushDrawable(LLDrawable* drawable); - void pushBridge(LLSpatialBridge* bridge); - void pushDrawInfo(U32 type, LLDrawInfo* draw_info); - - U32 getVisibleGroupsSize() { return mVisibleGroupsSize; } - U32 getAlphaGroupsSize() { return mAlphaGroupsSize; } - U32 getRiggedAlphaGroupsSize() { return mRiggedAlphaGroupsSize; } - U32 getDrawableGroupsSize() { return mDrawableGroupsSize; } - U32 getVisibleListSize() { return mVisibleListSize; } - U32 getVisibleBridgeSize() { return mVisibleBridgeSize; } - U32 getRenderMapSize(U32 type) { return mRenderMapSize[type]; } - - void assertDrawMapsEmpty(); - -private: - - template void pushBack(T &head, U32& count, V* val); - - U32 mVisibleGroupsSize; - U32 mAlphaGroupsSize; - U32 mRiggedAlphaGroupsSize; - U32 mOcclusionGroupsSize; - U32 mDrawableGroupsSize; - U32 mVisibleListSize; - U32 mVisibleBridgeSize; - - U32 mVisibleGroupsAllocated; - U32 mAlphaGroupsAllocated; - U32 mRiggedAlphaGroupsAllocated; - U32 mOcclusionGroupsAllocated; - U32 mDrawableGroupsAllocated; - U32 mVisibleListAllocated; - U32 mVisibleBridgeAllocated; - - U32 mRenderMapSize[LLRenderPass::NUM_RENDER_TYPES]; - - sg_list_t mVisibleGroups; - sg_iterator mVisibleGroupsEnd; - sg_list_t mAlphaGroups; - sg_iterator mAlphaGroupsEnd; - sg_list_t mRiggedAlphaGroups; - sg_iterator mRiggedAlphaGroupsEnd; - sg_list_t mOcclusionGroups; - sg_iterator mOcclusionGroupsEnd; - sg_list_t mDrawableGroups; - sg_iterator mDrawableGroupsEnd; - drawable_list_t mVisibleList; - drawable_iterator mVisibleListEnd; - bridge_list_t mVisibleBridge; - bridge_iterator mVisibleBridgeEnd; - drawinfo_list_t mRenderMap[LLRenderPass::NUM_RENDER_TYPES]; - U32 mRenderMapAllocated[LLRenderPass::NUM_RENDER_TYPES]; - drawinfo_iterator mRenderMapEnd[LLRenderPass::NUM_RENDER_TYPES]; - -}; - - -//spatial partition for water (implemented in LLVOWater.cpp) -class LLWaterPartition : public LLSpatialPartition -{ -public: - LLWaterPartition(LLViewerRegion* regionp); - virtual void getGeometry(LLSpatialGroup* group) { } - virtual void addGeometryCount(LLSpatialGroup* group, U32 &vertex_count, U32& index_count) { } -}; - -//spatial partition for hole and edge water (implemented in LLVOWater.cpp) -class LLVoidWaterPartition : public LLWaterPartition -{ -public: - LLVoidWaterPartition(LLViewerRegion* regionp); -}; - -//spatial partition for terrain (impelmented in LLVOSurfacePatch.cpp) -class LLTerrainPartition : public LLSpatialPartition -{ -public: - LLTerrainPartition(LLViewerRegion* regionp); - virtual void getGeometry(LLSpatialGroup* group); -}; - -//spatial partition for trees -class LLTreePartition : public LLSpatialPartition -{ -public: - LLTreePartition(LLViewerRegion* regionp); - virtual void getGeometry(LLSpatialGroup* group) { } - virtual void addGeometryCount(LLSpatialGroup* group, U32 &vertex_count, U32& index_count) { } - -}; - -//spatial partition for particles (implemented in LLVOPartGroup.cpp) -class LLParticlePartition : public LLSpatialPartition -{ -public: - LLParticlePartition(LLViewerRegion* regionp); - virtual void rebuildGeom(LLSpatialGroup* group); - virtual void getGeometry(LLSpatialGroup* group); - virtual void addGeometryCount(LLSpatialGroup* group, U32 &vertex_count, U32& index_count); - virtual F32 calcPixelArea(LLSpatialGroup* group, LLCamera& camera); -protected: - U32 mRenderPass; -}; - -class LLHUDParticlePartition : public LLParticlePartition -{ -public: - LLHUDParticlePartition(LLViewerRegion* regionp); -}; - -//spatial partition for grass (implemented in LLVOGrass.cpp) -class LLGrassPartition : public LLSpatialPartition -{ -public: - LLGrassPartition(LLViewerRegion* regionp); - virtual void getGeometry(LLSpatialGroup* group); - virtual void addGeometryCount(LLSpatialGroup* group, U32 &vertex_count, U32& index_count); -protected: - U32 mRenderPass; -}; - -//class for wrangling geometry out of volumes (implemented in LLVOVolume.cpp) -class LLVolumeGeometryManager: public LLGeometryManager -{ - public: - typedef enum - { - NONE = 0, - BATCH_SORT, - DISTANCE_SORT - } eSortType; - - LLVolumeGeometryManager(); - virtual ~LLVolumeGeometryManager(); - virtual void rebuildGeom(LLSpatialGroup* group); - virtual void rebuildMesh(LLSpatialGroup* group); - virtual void getGeometry(LLSpatialGroup* group); - virtual void addGeometryCount(LLSpatialGroup* group, U32& vertex_count, U32& index_count); - U32 genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace** faces, U32 face_count, bool distance_sort = false, bool batch_textures = false, bool rigged = false); - void registerFace(LLSpatialGroup* group, LLFace* facep, U32 type); - -private: - void allocateFaces(U32 pMaxFaceCount); - void freeFaces(); - - static int32_t sInstanceCount; - static LLFace** sFullbrightFaces[2]; - static LLFace** sBumpFaces[2]; - static LLFace** sSimpleFaces[2]; - static LLFace** sNormFaces[2]; - static LLFace** sSpecFaces[2]; - static LLFace** sNormSpecFaces[2]; - static LLFace** sPbrFaces[2]; - static LLFace** sAlphaFaces[2]; -}; - -//spatial partition that uses volume geometry manager (implemented in LLVOVolume.cpp) -class LLVolumePartition : public LLSpatialPartition, public LLVolumeGeometryManager -{ -public: - LLVolumePartition(LLViewerRegion* regionp); - virtual void rebuildGeom(LLSpatialGroup* group) { LLVolumeGeometryManager::rebuildGeom(group); } - virtual void getGeometry(LLSpatialGroup* group) { LLVolumeGeometryManager::getGeometry(group); } - virtual void rebuildMesh(LLSpatialGroup* group) { LLVolumeGeometryManager::rebuildMesh(group); } - virtual void addGeometryCount(LLSpatialGroup* group, U32 &vertex_count, U32& index_count) { LLVolumeGeometryManager::addGeometryCount(group, vertex_count, index_count); } -}; - -//spatial bridge that uses volume geometry manager (implemented in LLVOVolume.cpp) -class LLVolumeBridge : public LLSpatialBridge, public LLVolumeGeometryManager -{ -public: - LLVolumeBridge(LLDrawable* drawable, LLViewerRegion* regionp); - virtual void rebuildGeom(LLSpatialGroup* group) { LLVolumeGeometryManager::rebuildGeom(group); } - virtual void getGeometry(LLSpatialGroup* group) { LLVolumeGeometryManager::getGeometry(group); } - virtual void rebuildMesh(LLSpatialGroup* group) { LLVolumeGeometryManager::rebuildMesh(group); } - virtual void addGeometryCount(LLSpatialGroup* group, U32 &vertex_count, U32& index_count) { LLVolumeGeometryManager::addGeometryCount(group, vertex_count, index_count); } -}; - -class LLAvatarBridge : public LLVolumeBridge -{ -public: - LLAvatarBridge(LLDrawable* drawablep, LLViewerRegion* regionp); -}; - -class LLControlAVBridge : public LLVolumeBridge -{ - using super = LLVolumeBridge; -public: - LLControlAVBridge(LLDrawable* drawablep, LLViewerRegion* regionp); -}; - -class LLHUDBridge : public LLVolumeBridge -{ -public: - LLHUDBridge(LLDrawable* drawablep, LLViewerRegion* regionp); - virtual void shiftPos(const LLVector4a& vec); - virtual F32 calcPixelArea(LLSpatialGroup* group, LLCamera& camera); -}; - -//spatial partition that holds nothing but spatial bridges -class LLBridgePartition : public LLSpatialPartition -{ -public: - LLBridgePartition(LLViewerRegion* regionp); - virtual void getGeometry(LLSpatialGroup* group) { } - virtual void addGeometryCount(LLSpatialGroup* group, U32 &vertex_count, U32& index_count) { } -}; - -class LLAvatarPartition : public LLBridgePartition -{ -public: - LLAvatarPartition(LLViewerRegion* regionp); -}; - -class LLControlAVPartition : public LLBridgePartition -{ -public: - LLControlAVPartition(LLViewerRegion* regionp); -}; - -class LLHUDPartition : public LLBridgePartition -{ -public: - LLHUDPartition(LLViewerRegion* regionp); - virtual void shift(const LLVector4a &offset); -}; - -extern const F32 SG_BOX_SIDE; -extern const F32 SG_BOX_OFFSET; -extern const F32 SG_BOX_RAD; - -extern const F32 SG_OBJ_SIDE; -extern const F32 SG_MAX_OBJ_RAD; - - -#endif //LL_LLSPATIALPARTITION_H - +/** + * @file llspatialpartition.h + * @brief LLSpatialGroup header file including definitions for supporting functions + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSPATIALPARTITION_H +#define LL_LLSPATIALPARTITION_H + +#define SG_MIN_DIST_RATIO 0.00001f + +#include "lldrawable.h" +#include "lloctree.h" +#include "llpointer.h" +#include "llrefcount.h" +#include "llvertexbuffer.h" +#include "llgltypes.h" +#include "llcubemap.h" +#include "lldrawpool.h" +#include "llface.h" +#include "llviewercamera.h" +#include "llvector4a.h" +#include "llvoavatar.h" +#include "llfetchedgltfmaterial.h" + +#include +#include + +#define SG_STATE_INHERIT_MASK (OCCLUDED) +#define SG_INITIAL_STATE_MASK (DIRTY | GEOM_DIRTY) + +class LLViewerOctreePartition; +class LLSpatialPartition; +class LLSpatialBridge; +class LLSpatialGroup; +class LLViewerRegion; +class LLReflectionMap; + +void pushVerts(LLFace* face); + +/* + Class that represents a single Draw Call + + Make every effort to keep size minimal. + Member ordering is important for cache coherency +*/ +class LLDrawInfo final : public LLRefCount +{ + LL_ALIGN_NEW; +protected: + ~LLDrawInfo(); + +public: + LLDrawInfo(const LLDrawInfo& rhs) + { + *this = rhs; + } + + const LLDrawInfo& operator=(const LLDrawInfo& rhs) + { + LL_ERRS() << "Illegal operation!" << LL_ENDL; + return *this; + } + + // return a hash of this LLDrawInfo as a debug color + LLColor4U getDebugColor() const; + + LLDrawInfo(U16 start, U16 end, U32 count, U32 offset, + LLViewerTexture* image, LLVertexBuffer* buffer, + bool fullbright = false, U8 bump = 0); + + + void validate(); + + // return mSkinHash->mHash, or 0 if mSkinHash is null + U64 getSkinHash(); + + LLPointer mVertexBuffer; + U16 mStart = 0; + U16 mEnd = 0; + U32 mCount = 0; + U32 mOffset = 0; + + LLPointer mTexture; + LLPointer mSpecularMap; + LLPointer mNormalMap; + + const LLMatrix4* mSpecularMapMatrix = nullptr; + const LLMatrix4* mNormalMapMatrix = nullptr; + const LLMatrix4* mTextureMatrix = nullptr; + const LLMatrix4* mModelMatrix = nullptr; + + LLPointer mAvatar = nullptr; + LLMeshSkinInfo* mSkinInfo = nullptr; + + // Material pointer here is likely for debugging only and are immaterial (zing!) + LLPointer mMaterial; + + // PBR material parameters + LLPointer mGLTFMaterial; + + LLVector4 mSpecColor = LLVector4(1.f, 1.f, 1.f, 0.5f); // XYZ = Specular RGB, W = Specular Exponent + + std::vector > mTextureList; + + LLUUID mMaterialID; // id of LLGLTFMaterial or LLMaterial applied to this draw info + + U32 mShaderMask = 0; + F32 mEnvIntensity = 0.f; + F32 mAlphaMaskCutoff = 0.5f; + + LLRender::eBlendFactor mBlendFuncSrc = LLRender::BF_SOURCE_ALPHA; + LLRender::eBlendFactor mBlendFuncDst = LLRender::BF_ONE_MINUS_SOURCE_ALPHA; + U8 mDiffuseAlphaMode = 0; + U8 mBump = 0; + U8 mShiny = 0; + bool mFullbright = false; + bool mHasGlow = false; + + struct CompareTexture + { + bool operator()(const LLDrawInfo& lhs, const LLDrawInfo& rhs) + { + return lhs.mTexture > rhs.mTexture; + } + }; + + struct CompareTexturePtr + { //sort by texture + bool operator()(const LLPointer& lhs, const LLPointer& rhs) + { + // sort by pointer, sort NULL down to the end + return lhs.get() != rhs.get() + && (lhs.isNull() || (rhs.notNull() && lhs->mTexture.get() > rhs->mTexture.get())); + } + }; + + struct CompareVertexBuffer + { //sort by texture + bool operator()(const LLPointer& lhs, const LLPointer& rhs) + { + // sort by pointer, sort NULL down to the end + return lhs.get() != rhs.get() + && (lhs.isNull() || (rhs.notNull() && lhs->mVertexBuffer.get() > rhs->mVertexBuffer.get())); + } + }; + + struct CompareTexturePtrMatrix + { + bool operator()(const LLPointer& lhs, const LLPointer& rhs) + { + return lhs.get() != rhs.get() + && (lhs.isNull() || (rhs.notNull() && (lhs->mTexture.get() > rhs->mTexture.get() || + (lhs->mTexture.get() == rhs->mTexture.get() && lhs->mModelMatrix > rhs->mModelMatrix)))); + } + + }; + + struct CompareMatrixTexturePtr + { + bool operator()(const LLPointer& lhs, const LLPointer& rhs) + { + return lhs.get() != rhs.get() + && (lhs.isNull() || (rhs.notNull() && (lhs->mModelMatrix > rhs->mModelMatrix || + (lhs->mModelMatrix == rhs->mModelMatrix && lhs->mTexture.get() > rhs->mTexture.get())))); + } + + }; + + struct CompareBump + { + bool operator()(const LLPointer& lhs, const LLPointer& rhs) + { + // sort by mBump value, sort NULL down to the end + return lhs.get() != rhs.get() + && (lhs.isNull() || (rhs.notNull() && lhs->mBump > rhs->mBump)); + } + }; +}; + +LL_ALIGN_PREFIX(16) +class LLSpatialGroup : public LLOcclusionCullingGroup +{ + using super = LLOcclusionCullingGroup; + friend class LLSpatialPartition; + friend class LLOctreeStateCheck; +public: + + LLSpatialGroup(const LLSpatialGroup& rhs) : LLOcclusionCullingGroup(rhs) + { + *this = rhs; + } + + const LLSpatialGroup& operator=(const LLSpatialGroup& rhs) + { + LL_ERRS() << "Illegal operation!" << LL_ENDL; + return *this; + } + + static U32 sNodeCount; + static bool sNoDelete; //deletion of spatial groups and draw info not allowed if true + + typedef std::vector > sg_vector_t; + typedef std::vector > bridge_list_t; + typedef std::vector > drawmap_elem_t; + typedef std::unordered_map draw_map_t; + typedef std::vector > buffer_list_t; + typedef std::unordered_map buffer_texture_map_t; + typedef std::unordered_map buffer_map_t; + + struct CompareDistanceGreater + { + bool operator()(const LLSpatialGroup* const& lhs, const LLSpatialGroup* const& rhs) + { + return lhs->mDistance > rhs->mDistance; + } + }; + + struct CompareUpdateUrgency + { + bool operator()(const LLPointer lhs, const LLPointer rhs) + { + return lhs->getUpdateUrgency() > rhs->getUpdateUrgency(); + } + }; + + struct CompareDepthGreater + { + bool operator()(const LLSpatialGroup* const& lhs, const LLSpatialGroup* const& rhs) + { + return lhs->mDepth > rhs->mDepth; + } + }; + + struct CompareRenderOrder + { + bool operator()(const LLSpatialGroup* const& lhs, const LLSpatialGroup* const& rhs) + { + if (lhs->mAvatarp != rhs->mAvatarp) + { + return lhs->mAvatarp < rhs->mAvatarp; + } + + return lhs->mRenderOrder > rhs->mRenderOrder; + } + }; + + typedef enum + { + GEOM_DIRTY = LLViewerOctreeGroup::INVALID_STATE, + ALPHA_DIRTY = (GEOM_DIRTY << 1), + IN_IMAGE_QUEUE = (ALPHA_DIRTY << 1), + IMAGE_DIRTY = (IN_IMAGE_QUEUE << 1), + MESH_DIRTY = (IMAGE_DIRTY << 1), + NEW_DRAWINFO = (MESH_DIRTY << 1), + IN_BUILD_Q1 = (NEW_DRAWINFO << 1), + IN_BUILD_Q2 = (IN_BUILD_Q1 << 1), + STATE_MASK = 0x0000FFFF, + } eSpatialState; + + LLSpatialGroup(OctreeNode* node, LLSpatialPartition* part); + + bool isHUDGroup() ; + + void clearDrawMap(); + void validate(); + void validateDrawMap(); + + void setState(U32 state, S32 mode); + void clearState(U32 state, S32 mode); + void clearState(U32 state) {mState &= ~state;} + + LLSpatialGroup* getParent(); + + bool addObject(LLDrawable *drawablep); + bool removeObject(LLDrawable *drawablep, bool from_octree = false); + bool updateInGroup(LLDrawable *drawablep, bool immediate = false); // Update position if it's in the group + void expandExtents(const LLVector4a* addingExtents, const LLXformMatrix& currentTransform); + void shift(const LLVector4a &offset); + + // TODO: this no longer appears to be called, figure out if it's important and if not remove it + void destroyGLState(bool keep_occlusion = false); + + void updateDistance(LLCamera& camera); + F32 getUpdateUrgency() const; + bool changeLOD(); + void rebuildGeom(); + void rebuildMesh(); + + void setState(U32 state) {mState |= state;} + void dirtyGeom() { setState(GEOM_DIRTY); } + void dirtyMesh() { setState(MESH_DIRTY); } + + void drawObjectBox(LLColor4 col); + + LLDrawable* lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, + bool pick_transparent, + bool pick_rigged, + bool pick_unselectable, + bool pick_reflection_probe, + S32* face_hit, // return the face hit + LLVector4a* intersection = NULL, // return the intersection point + LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point + LLVector4a* normal = NULL, // return the surface normal at the intersection point + LLVector4a* tangent = NULL // return the surface tangent at the intersection point + ); + + + LLSpatialPartition* getSpatialPartition() {return (LLSpatialPartition*)mSpatialPartition;} + + //LISTENER FUNCTIONS + virtual void handleInsertion(const TreeNode* node, LLViewerOctreeEntry* face); + virtual void handleRemoval(const TreeNode* node, LLViewerOctreeEntry* face); + virtual void handleDestruction(const TreeNode* node); + virtual void handleChildAddition(const OctreeNode* parent, OctreeNode* child); + + // LLViewerOctreeGroup + virtual void rebound(); + +public: + LL_ALIGN_16(LLVector4a mViewAngle); + LL_ALIGN_16(LLVector4a mLastUpdateViewAngle); + +protected: + virtual ~LLSpatialGroup(); + +public: + LLPointer mVertexBuffer; + draw_map_t mDrawMap; + + bridge_list_t mBridgeList; + buffer_map_t mBufferMap; //used by volume buffers to attempt to reuse vertex buffers + + F32 mObjectBoxSize; //cached mObjectBounds[1].getLength3() + U32 mGeometryBytes; //used by volumes to track how many bytes of geometry data are in this node + F32 mSurfaceArea; //used by volumes to track estimated surface area of geometry in this node + F32 mBuilt; + + F32 mDistance; + F32 mDepth; + F32 mLastUpdateDistance; + F32 mLastUpdateTime; + + F32 mPixelArea; + F32 mRadius; + + //used by LLVOAVatar to set render order in alpha draw pool to preserve legacy render order behavior + LLVOAvatar* mAvatarp = nullptr; + U32 mRenderOrder = 0; + // Reflection Probe associated with this node (if any) + LLPointer mReflectionProbe = nullptr; +} LL_ALIGN_POSTFIX(16); + +class LLGeometryManager +{ +public: + std::vector mFaceList; + virtual ~LLGeometryManager() { } + virtual void rebuildGeom(LLSpatialGroup* group) = 0; + virtual void rebuildMesh(LLSpatialGroup* group) = 0; + virtual void getGeometry(LLSpatialGroup* group) = 0; + virtual void addGeometryCount(LLSpatialGroup* group, U32 &vertex_count, U32 &index_count); +}; + +class LLSpatialPartition: public LLViewerOctreePartition, public LLGeometryManager +{ +public: + LLSpatialPartition(U32 data_mask, bool render_by_group, LLViewerRegion* regionp); + virtual ~LLSpatialPartition(); + + LLSpatialGroup *put(LLDrawable *drawablep, bool was_visible = false); + bool remove(LLDrawable *drawablep, LLSpatialGroup *curp); + + LLDrawable* lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, + bool pick_transparent, + bool pick_rigged, + bool pick_unselectable, + bool pick_reflection_probe, + S32* face_hit, // return the face hit + LLVector4a* intersection = NULL, // return the intersection point + LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point + LLVector4a* normal = NULL, // return the surface normal at the intersection point + LLVector4a* tangent = NULL // return the surface tangent at the intersection point + ); + + + // If the drawable moves, move it here. + virtual void move(LLDrawable *drawablep, LLSpatialGroup *curp, bool immediate = false); + virtual void shift(const LLVector4a &offset); + + virtual F32 calcDistance(LLSpatialGroup* group, LLCamera& camera); + virtual F32 calcPixelArea(LLSpatialGroup* group, LLCamera& camera); + + virtual void rebuildGeom(LLSpatialGroup* group); + virtual void rebuildMesh(LLSpatialGroup* group); + + bool visibleObjectsInFrustum(LLCamera& camera); + /*virtual*/ S32 cull(LLCamera &camera, bool do_occlusion=false); // Cull on arbitrary frustum + S32 cull(LLCamera &camera, std::vector* results, bool for_select); // Cull on arbitrary frustum + + bool isVisible(const LLVector3& v); + bool isHUDPartition() ; + + LLSpatialBridge* asBridge() { return mBridge; } + bool isBridge() { return asBridge() != NULL; } + + void renderPhysicsShapes(bool depth_only); + void renderDebug(); + void renderIntersectingBBoxes(LLCamera* camera); + void restoreGL(); + + bool getVisibleExtents(LLCamera& camera, LLVector3& visMin, LLVector3& visMax); + +public: + LLSpatialBridge* mBridge; // NULL for non-LLSpatialBridge instances, otherwise, mBridge == this + // use a pointer instead of making "isBridge" and "asBridge" virtual so it's safe + // to call asBridge() from the destructor + + bool mInfiniteFarClip; // if true, frustum culling ignores far clip plane + const bool mRenderByGroup; + U32 mVertexDataMask; + F32 mSlopRatio; //percentage distance must change before drawables receive LOD update (default is 0.25); + bool mDepthMask; //if true, objects in this partition will be written to depth during alpha rendering +}; + +// class for creating bridges between spatial partitions +class LLSpatialBridge : public LLDrawable, public LLSpatialPartition +{ +protected: + ~LLSpatialBridge(); + +public: + typedef std::vector > bridge_vector_t; + + LLSpatialBridge(LLDrawable* root, bool render_by_group, U32 data_mask, LLViewerRegion* regionp); + + void destroyTree(); + + virtual bool isSpatialBridge() const { return true; } + virtual void updateSpatialExtents(); + virtual void updateBinRadius(); + virtual void setVisible(LLCamera& camera_in, std::vector* results = NULL, bool for_select = false); + virtual void updateDistance(LLCamera& camera_in, bool force_update); + virtual void makeActive(); + virtual void move(LLDrawable *drawablep, LLSpatialGroup *curp, bool immediate = false); + virtual bool updateMove(); + virtual void shiftPos(const LLVector4a& vec); + virtual void cleanupReferences(); + virtual LLSpatialPartition* asPartition() { return this; } + + //transform agent space camera into this Spatial Bridge's coordinate frame + virtual LLCamera transformCamera(LLCamera& camera); + + //transform agent space bounding box into this Spatial Bridge's coordinate frame + void transformExtents(const LLVector4a* src, LLVector4a* dst); + LLDrawable* mDrawable; +}; + +class LLCullResult +{ +public: + LLCullResult(); + + typedef std::vector sg_list_t; + typedef std::vector drawable_list_t; + typedef std::vector bridge_list_t; + typedef std::vector drawinfo_list_t; + + typedef LLSpatialGroup** sg_iterator; + typedef LLSpatialBridge** bridge_iterator; + typedef LLDrawInfo** drawinfo_iterator; + typedef LLDrawable** drawable_iterator; + + // Helper function for taking advantage of _mm_prefetch when iterating over cull results + static inline void increment_iterator(LLCullResult::drawinfo_iterator& i, const LLCullResult::drawinfo_iterator& end) + { + ++i; + + if (i != end) + { + _mm_prefetch((char*)(*i)->mVertexBuffer.get(), _MM_HINT_NTA); + + auto* ni = i + 1; + if (ni != end) + { + _mm_prefetch((char*)*ni, _MM_HINT_NTA); + } + } + } + + void clear(); + + sg_iterator beginVisibleGroups(); + sg_iterator endVisibleGroups(); + + sg_iterator beginAlphaGroups(); + sg_iterator endAlphaGroups(); + + sg_iterator beginRiggedAlphaGroups(); + sg_iterator endRiggedAlphaGroups(); + + bool hasOcclusionGroups() { return mOcclusionGroupsSize > 0; } + sg_iterator beginOcclusionGroups(); + sg_iterator endOcclusionGroups(); + + sg_iterator beginDrawableGroups(); + sg_iterator endDrawableGroups(); + + drawable_iterator beginVisibleList(); + drawable_iterator endVisibleList(); + + bridge_iterator beginVisibleBridge(); + bridge_iterator endVisibleBridge(); + + drawinfo_iterator beginRenderMap(U32 type); + drawinfo_iterator endRenderMap(U32 type); + + void pushVisibleGroup(LLSpatialGroup* group); + void pushAlphaGroup(LLSpatialGroup* group); + void pushRiggedAlphaGroup(LLSpatialGroup* group); + void pushOcclusionGroup(LLSpatialGroup* group); + void pushDrawableGroup(LLSpatialGroup* group); + void pushDrawable(LLDrawable* drawable); + void pushBridge(LLSpatialBridge* bridge); + void pushDrawInfo(U32 type, LLDrawInfo* draw_info); + + U32 getVisibleGroupsSize() { return mVisibleGroupsSize; } + U32 getAlphaGroupsSize() { return mAlphaGroupsSize; } + U32 getRiggedAlphaGroupsSize() { return mRiggedAlphaGroupsSize; } + U32 getDrawableGroupsSize() { return mDrawableGroupsSize; } + U32 getVisibleListSize() { return mVisibleListSize; } + U32 getVisibleBridgeSize() { return mVisibleBridgeSize; } + U32 getRenderMapSize(U32 type) { return mRenderMapSize[type]; } + + void assertDrawMapsEmpty(); + +private: + + template void pushBack(T &head, U32& count, V* val); + + U32 mVisibleGroupsSize; + U32 mAlphaGroupsSize; + U32 mRiggedAlphaGroupsSize; + U32 mOcclusionGroupsSize; + U32 mDrawableGroupsSize; + U32 mVisibleListSize; + U32 mVisibleBridgeSize; + + U32 mVisibleGroupsAllocated; + U32 mAlphaGroupsAllocated; + U32 mRiggedAlphaGroupsAllocated; + U32 mOcclusionGroupsAllocated; + U32 mDrawableGroupsAllocated; + U32 mVisibleListAllocated; + U32 mVisibleBridgeAllocated; + + U32 mRenderMapSize[LLRenderPass::NUM_RENDER_TYPES]; + + sg_list_t mVisibleGroups; + sg_iterator mVisibleGroupsEnd; + sg_list_t mAlphaGroups; + sg_iterator mAlphaGroupsEnd; + sg_list_t mRiggedAlphaGroups; + sg_iterator mRiggedAlphaGroupsEnd; + sg_list_t mOcclusionGroups; + sg_iterator mOcclusionGroupsEnd; + sg_list_t mDrawableGroups; + sg_iterator mDrawableGroupsEnd; + drawable_list_t mVisibleList; + drawable_iterator mVisibleListEnd; + bridge_list_t mVisibleBridge; + bridge_iterator mVisibleBridgeEnd; + drawinfo_list_t mRenderMap[LLRenderPass::NUM_RENDER_TYPES]; + U32 mRenderMapAllocated[LLRenderPass::NUM_RENDER_TYPES]; + drawinfo_iterator mRenderMapEnd[LLRenderPass::NUM_RENDER_TYPES]; + +}; + + +//spatial partition for water (implemented in LLVOWater.cpp) +class LLWaterPartition : public LLSpatialPartition +{ +public: + LLWaterPartition(LLViewerRegion* regionp); + virtual void getGeometry(LLSpatialGroup* group) { } + virtual void addGeometryCount(LLSpatialGroup* group, U32 &vertex_count, U32& index_count) { } +}; + +//spatial partition for hole and edge water (implemented in LLVOWater.cpp) +class LLVoidWaterPartition : public LLWaterPartition +{ +public: + LLVoidWaterPartition(LLViewerRegion* regionp); +}; + +//spatial partition for terrain (impelmented in LLVOSurfacePatch.cpp) +class LLTerrainPartition : public LLSpatialPartition +{ +public: + LLTerrainPartition(LLViewerRegion* regionp); + virtual void getGeometry(LLSpatialGroup* group); +}; + +//spatial partition for trees +class LLTreePartition : public LLSpatialPartition +{ +public: + LLTreePartition(LLViewerRegion* regionp); + virtual void getGeometry(LLSpatialGroup* group) { } + virtual void addGeometryCount(LLSpatialGroup* group, U32 &vertex_count, U32& index_count) { } + +}; + +//spatial partition for particles (implemented in LLVOPartGroup.cpp) +class LLParticlePartition : public LLSpatialPartition +{ +public: + LLParticlePartition(LLViewerRegion* regionp); + virtual void rebuildGeom(LLSpatialGroup* group); + virtual void getGeometry(LLSpatialGroup* group); + virtual void addGeometryCount(LLSpatialGroup* group, U32 &vertex_count, U32& index_count); + virtual F32 calcPixelArea(LLSpatialGroup* group, LLCamera& camera); +protected: + U32 mRenderPass; +}; + +class LLHUDParticlePartition : public LLParticlePartition +{ +public: + LLHUDParticlePartition(LLViewerRegion* regionp); +}; + +//spatial partition for grass (implemented in LLVOGrass.cpp) +class LLGrassPartition : public LLSpatialPartition +{ +public: + LLGrassPartition(LLViewerRegion* regionp); + virtual void getGeometry(LLSpatialGroup* group); + virtual void addGeometryCount(LLSpatialGroup* group, U32 &vertex_count, U32& index_count); +protected: + U32 mRenderPass; +}; + +//class for wrangling geometry out of volumes (implemented in LLVOVolume.cpp) +class LLVolumeGeometryManager: public LLGeometryManager +{ + public: + typedef enum + { + NONE = 0, + BATCH_SORT, + DISTANCE_SORT + } eSortType; + + LLVolumeGeometryManager(); + virtual ~LLVolumeGeometryManager(); + virtual void rebuildGeom(LLSpatialGroup* group); + virtual void rebuildMesh(LLSpatialGroup* group); + virtual void getGeometry(LLSpatialGroup* group); + virtual void addGeometryCount(LLSpatialGroup* group, U32& vertex_count, U32& index_count); + U32 genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace** faces, U32 face_count, bool distance_sort = false, bool batch_textures = false, bool rigged = false); + void registerFace(LLSpatialGroup* group, LLFace* facep, U32 type); + +private: + void allocateFaces(U32 pMaxFaceCount); + void freeFaces(); + + static int32_t sInstanceCount; + static LLFace** sFullbrightFaces[2]; + static LLFace** sBumpFaces[2]; + static LLFace** sSimpleFaces[2]; + static LLFace** sNormFaces[2]; + static LLFace** sSpecFaces[2]; + static LLFace** sNormSpecFaces[2]; + static LLFace** sPbrFaces[2]; + static LLFace** sAlphaFaces[2]; +}; + +//spatial partition that uses volume geometry manager (implemented in LLVOVolume.cpp) +class LLVolumePartition : public LLSpatialPartition, public LLVolumeGeometryManager +{ +public: + LLVolumePartition(LLViewerRegion* regionp); + virtual void rebuildGeom(LLSpatialGroup* group) { LLVolumeGeometryManager::rebuildGeom(group); } + virtual void getGeometry(LLSpatialGroup* group) { LLVolumeGeometryManager::getGeometry(group); } + virtual void rebuildMesh(LLSpatialGroup* group) { LLVolumeGeometryManager::rebuildMesh(group); } + virtual void addGeometryCount(LLSpatialGroup* group, U32 &vertex_count, U32& index_count) { LLVolumeGeometryManager::addGeometryCount(group, vertex_count, index_count); } +}; + +//spatial bridge that uses volume geometry manager (implemented in LLVOVolume.cpp) +class LLVolumeBridge : public LLSpatialBridge, public LLVolumeGeometryManager +{ +public: + LLVolumeBridge(LLDrawable* drawable, LLViewerRegion* regionp); + virtual void rebuildGeom(LLSpatialGroup* group) { LLVolumeGeometryManager::rebuildGeom(group); } + virtual void getGeometry(LLSpatialGroup* group) { LLVolumeGeometryManager::getGeometry(group); } + virtual void rebuildMesh(LLSpatialGroup* group) { LLVolumeGeometryManager::rebuildMesh(group); } + virtual void addGeometryCount(LLSpatialGroup* group, U32 &vertex_count, U32& index_count) { LLVolumeGeometryManager::addGeometryCount(group, vertex_count, index_count); } +}; + +class LLAvatarBridge : public LLVolumeBridge +{ +public: + LLAvatarBridge(LLDrawable* drawablep, LLViewerRegion* regionp); +}; + +class LLControlAVBridge : public LLVolumeBridge +{ + using super = LLVolumeBridge; +public: + LLControlAVBridge(LLDrawable* drawablep, LLViewerRegion* regionp); +}; + +class LLHUDBridge : public LLVolumeBridge +{ +public: + LLHUDBridge(LLDrawable* drawablep, LLViewerRegion* regionp); + virtual void shiftPos(const LLVector4a& vec); + virtual F32 calcPixelArea(LLSpatialGroup* group, LLCamera& camera); +}; + +//spatial partition that holds nothing but spatial bridges +class LLBridgePartition : public LLSpatialPartition +{ +public: + LLBridgePartition(LLViewerRegion* regionp); + virtual void getGeometry(LLSpatialGroup* group) { } + virtual void addGeometryCount(LLSpatialGroup* group, U32 &vertex_count, U32& index_count) { } +}; + +class LLAvatarPartition : public LLBridgePartition +{ +public: + LLAvatarPartition(LLViewerRegion* regionp); +}; + +class LLControlAVPartition : public LLBridgePartition +{ +public: + LLControlAVPartition(LLViewerRegion* regionp); +}; + +class LLHUDPartition : public LLBridgePartition +{ +public: + LLHUDPartition(LLViewerRegion* regionp); + virtual void shift(const LLVector4a &offset); +}; + +extern const F32 SG_BOX_SIDE; +extern const F32 SG_BOX_OFFSET; +extern const F32 SG_BOX_RAD; + +extern const F32 SG_OBJ_SIDE; +extern const F32 SG_MAX_OBJ_RAD; + + +#endif //LL_LLSPATIALPARTITION_H + diff --git a/indra/newview/llspeakers.cpp b/indra/newview/llspeakers.cpp index 7d71873b68..0d1d2a73ed 100644 --- a/indra/newview/llspeakers.cpp +++ b/indra/newview/llspeakers.cpp @@ -1,1042 +1,1042 @@ -/** - * @file llspeakers.cpp - * @brief Management interface for muting and controlling volume of residents currently speaking - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llspeakers.h" - -#include "llagent.h" -#include "llavatarnamecache.h" -#include "llappviewer.h" -#include "llimview.h" -#include "llgroupmgr.h" -#include "llsdutil.h" -#include "lluicolortable.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llvoavatar.h" -#include "llworld.h" -#include "llcorehttputil.h" - -extern LLControlGroup gSavedSettings; - -const LLColor4 INACTIVE_COLOR(0.3f, 0.3f, 0.3f, 0.5f); -const LLColor4 ACTIVE_COLOR(0.5f, 0.5f, 0.5f, 1.f); - -LLSpeaker::LLSpeaker(const LLUUID& id, const std::string& name, const ESpeakerType type) : - mStatus(LLSpeaker::STATUS_TEXT_ONLY), - mLastSpokeTime(0.f), - mSpeechVolume(0.f), - mHasSpoken(false), - mHasLeftCurrentCall(false), - mDotColor(LLColor4::white), - mID(id), - mTyping(false), - mSortIndex(0), - mType(type), - mIsModerator(false), - mModeratorMutedVoice(false), - mModeratorMutedText(false) -{ - if (name.empty() && type == SPEAKER_AGENT) - { - lookupName(); - } - else - { - mDisplayName = name; - } -} - - -void LLSpeaker::lookupName() -{ - if (mDisplayName.empty()) - { - LLAvatarNameCache::get(mID, boost::bind(&LLSpeaker::onNameCache, this, _1, _2)); // todo: can be group??? - } -} - -void LLSpeaker::onNameCache(const LLUUID& id, const LLAvatarName& av_name) -{ - mDisplayName = av_name.getUserName(); -} - -bool LLSpeaker::isInVoiceChannel() -{ - return mStatus <= LLSpeaker::STATUS_VOICE_ACTIVE || mStatus == LLSpeaker::STATUS_MUTED; -} - -LLSpeakerUpdateSpeakerEvent::LLSpeakerUpdateSpeakerEvent(LLSpeaker* source) -: LLEvent(source, "Speaker update speaker event"), - mSpeakerID (source->mID) -{ -} - -LLSD LLSpeakerUpdateSpeakerEvent::getValue() -{ - LLSD ret; - ret["id"] = mSpeakerID; - return ret; -} - -LLSpeakerUpdateModeratorEvent::LLSpeakerUpdateModeratorEvent(LLSpeaker* source) -: LLEvent(source, "Speaker add moderator event"), - mSpeakerID (source->mID), - mIsModerator (source->mIsModerator) -{ -} - -LLSD LLSpeakerUpdateModeratorEvent::getValue() -{ - LLSD ret; - ret["id"] = mSpeakerID; - ret["is_moderator"] = mIsModerator; - return ret; -} - -LLSpeakerTextModerationEvent::LLSpeakerTextModerationEvent(LLSpeaker* source) -: LLEvent(source, "Speaker text moderation event") -{ -} - -LLSD LLSpeakerTextModerationEvent::getValue() -{ - return std::string("text"); -} - - -LLSpeakerVoiceModerationEvent::LLSpeakerVoiceModerationEvent(LLSpeaker* source) -: LLEvent(source, "Speaker voice moderation event") -{ -} - -LLSD LLSpeakerVoiceModerationEvent::getValue() -{ - return std::string("voice"); -} - -LLSpeakerListChangeEvent::LLSpeakerListChangeEvent(LLSpeakerMgr* source, const LLUUID& speaker_id) -: LLEvent(source, "Speaker added/removed from speaker mgr"), - mSpeakerID(speaker_id) -{ -} - -LLSD LLSpeakerListChangeEvent::getValue() -{ - return mSpeakerID; -} - -// helper sort class -struct LLSortRecentSpeakers -{ - bool operator()(const LLPointer lhs, const LLPointer rhs) const; -}; - -bool LLSortRecentSpeakers::operator()(const LLPointer lhs, const LLPointer rhs) const -{ - // Sort first on status - if (lhs->mStatus != rhs->mStatus) - { - return (lhs->mStatus < rhs->mStatus); - } - - // and then on last speaking time - if(lhs->mLastSpokeTime != rhs->mLastSpokeTime) - { - return (lhs->mLastSpokeTime > rhs->mLastSpokeTime); - } - - // and finally (only if those are both equal), on name. - return( lhs->mDisplayName.compare(rhs->mDisplayName) < 0 ); -} - -LLSpeakerActionTimer::LLSpeakerActionTimer(action_callback_t action_cb, F32 action_period, const LLUUID& speaker_id) -: LLEventTimer(action_period) -, mActionCallback(action_cb) -, mSpeakerId(speaker_id) -{ -} - -bool LLSpeakerActionTimer::tick() -{ - if (mActionCallback) - { - return (bool)mActionCallback(mSpeakerId); - } - return true; -} - -void LLSpeakerActionTimer::unset() -{ - mActionCallback = 0; -} - -LLSpeakersDelayActionsStorage::LLSpeakersDelayActionsStorage(LLSpeakerActionTimer::action_callback_t action_cb, F32 action_delay) -: mActionCallback(action_cb) -, mActionDelay(action_delay) -{ -} - -LLSpeakersDelayActionsStorage::~LLSpeakersDelayActionsStorage() -{ - removeAllTimers(); -} - -void LLSpeakersDelayActionsStorage::setActionTimer(const LLUUID& speaker_id) -{ - bool not_found = true; - if (mActionTimersMap.size() > 0) - { - not_found = mActionTimersMap.find(speaker_id) == mActionTimersMap.end(); - } - - // If there is already a started timer for the passed UUID don't do anything. - if (not_found) - { - // Starting a timer to remove an participant after delay is completed - mActionTimersMap.insert(LLSpeakerActionTimer::action_value_t(speaker_id, - new LLSpeakerActionTimer( - boost::bind(&LLSpeakersDelayActionsStorage::onTimerActionCallback, this, _1), - mActionDelay, speaker_id))); - } -} - -void LLSpeakersDelayActionsStorage::unsetActionTimer(const LLUUID& speaker_id) -{ - if (mActionTimersMap.size() == 0) return; - - LLSpeakerActionTimer::action_timer_iter_t it_speaker = mActionTimersMap.find(speaker_id); - - if (it_speaker != mActionTimersMap.end()) - { - it_speaker->second->unset(); - mActionTimersMap.erase(it_speaker); - } -} - -void LLSpeakersDelayActionsStorage::removeAllTimers() -{ - LLSpeakerActionTimer::action_timer_iter_t iter = mActionTimersMap.begin(); - for (; iter != mActionTimersMap.end(); ++iter) - { - delete iter->second; - } - mActionTimersMap.clear(); -} - -bool LLSpeakersDelayActionsStorage::onTimerActionCallback(const LLUUID& speaker_id) -{ - unsetActionTimer(speaker_id); - - if (mActionCallback) - { - mActionCallback(speaker_id); - } - - return true; -} - -bool LLSpeakersDelayActionsStorage::isTimerStarted(const LLUUID& speaker_id) -{ - return (mActionTimersMap.size() > 0) && (mActionTimersMap.find(speaker_id) != mActionTimersMap.end()); -} - -// -// LLSpeakerMgr -// - -LLSpeakerMgr::LLSpeakerMgr(LLVoiceChannel* channelp) : - mVoiceChannel(channelp), - mVoiceModerated(false), - mModerateModeHandledFirstTime(false), - mSpeakerListUpdated(false) -{ - mGetListTime.reset(); - static LLUICachedControl remove_delay ("SpeakerParticipantRemoveDelay", 10.0); - - mSpeakerDelayRemover = new LLSpeakersDelayActionsStorage(boost::bind(&LLSpeakerMgr::removeSpeaker, this, _1), remove_delay); -} - -LLSpeakerMgr::~LLSpeakerMgr() -{ - delete mSpeakerDelayRemover; -} - -LLPointer LLSpeakerMgr::setSpeaker(const LLUUID& id, const std::string& name, LLSpeaker::ESpeakerStatus status, LLSpeaker::ESpeakerType type) -{ - LLUUID session_id = getSessionID(); - if (id.isNull() || (id == session_id)) - { - return NULL; - } - - LLPointer speakerp; - if (mSpeakers.find(id) == mSpeakers.end()) - { - speakerp = new LLSpeaker(id, name, type); - speakerp->mStatus = status; - mSpeakers.insert(std::make_pair(speakerp->mID, speakerp)); - mSpeakersSorted.push_back(speakerp); - LL_DEBUGS("Speakers") << "Added speaker " << id << LL_ENDL; - fireEvent(new LLSpeakerListChangeEvent(this, speakerp->mID), "add"); - } - else - { - speakerp = findSpeaker(id); - if (speakerp.notNull()) - { - // keep highest priority status (lowest value) instead of overriding current value - speakerp->mStatus = llmin(speakerp->mStatus, status); - // RN: due to a weird behavior where IMs from attached objects come from the wearer's agent_id - // we need to override speakers that we think are objects when we find out they are really - // residents - if (type == LLSpeaker::SPEAKER_AGENT) - { - speakerp->mType = LLSpeaker::SPEAKER_AGENT; - speakerp->lookupName(); - } - } - else - { - LL_WARNS("Speakers") << "Speaker " << id << " not found" << LL_ENDL; - } - } - - mSpeakerDelayRemover->unsetActionTimer(speakerp->mID); - return speakerp; -} - -// *TODO: Once way to request the current voice channel moderation mode is implemented -// this method with related code should be removed. -/* - Initializes "moderate_mode" of voice session on first join. - - This is WORKAROUND because a way to request the current voice channel moderation mode exists - but is not implemented in viewer yet. See EXT-6937. -*/ -void LLSpeakerMgr::initVoiceModerateMode() -{ - if (!mModerateModeHandledFirstTime && (mVoiceChannel && mVoiceChannel->isActive())) - { - LLPointer speakerp; - - if (mSpeakers.find(gAgentID) != mSpeakers.end()) - { - speakerp = mSpeakers[gAgentID]; - } - - if (speakerp.notNull()) - { - mVoiceModerated = speakerp->mModeratorMutedVoice; - mModerateModeHandledFirstTime = true; - } - } -} - -void LLSpeakerMgr::update(bool resort_ok) -{ - if (!LLVoiceClient::getInstance()) - { - return; - } - - LLColor4 speaking_color = LLUIColorTable::instance().getColor("SpeakingColor"); - LLColor4 overdriven_color = LLUIColorTable::instance().getColor("OverdrivenColor"); - - if(resort_ok) // only allow list changes when user is not interacting with it - { - updateSpeakerList(); - } - - // update status of all current speakers - bool voice_channel_active = (!mVoiceChannel && LLVoiceClient::getInstance()->inProximalChannel()) || (mVoiceChannel && mVoiceChannel->isActive()); - for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end(); speaker_it++) - { - LLUUID speaker_id = speaker_it->first; - LLSpeaker* speakerp = speaker_it->second; - - if (voice_channel_active && LLVoiceClient::getInstance()->getVoiceEnabled(speaker_id)) - { - speakerp->mSpeechVolume = LLVoiceClient::getInstance()->getCurrentPower(speaker_id); - bool moderator_muted_voice = LLVoiceClient::getInstance()->getIsModeratorMuted(speaker_id); - if (moderator_muted_voice != speakerp->mModeratorMutedVoice) - { - speakerp->mModeratorMutedVoice = moderator_muted_voice; - LL_DEBUGS("Speakers") << (speakerp->mModeratorMutedVoice? "Muted" : "Umuted") << " speaker " << speaker_id<< LL_ENDL; - speakerp->fireEvent(new LLSpeakerVoiceModerationEvent(speakerp)); - } - - if (LLVoiceClient::getInstance()->getOnMuteList(speaker_id) || speakerp->mModeratorMutedVoice) - { - speakerp->mStatus = LLSpeaker::STATUS_MUTED; - } - else if (LLVoiceClient::getInstance()->getIsSpeaking(speaker_id)) - { - // reset inactivity expiration - if (speakerp->mStatus != LLSpeaker::STATUS_SPEAKING) - { - speakerp->mLastSpokeTime = mSpeechTimer.getElapsedTimeF32(); - speakerp->mHasSpoken = true; - fireEvent(new LLSpeakerUpdateSpeakerEvent(speakerp), "update_speaker"); - } - speakerp->mStatus = LLSpeaker::STATUS_SPEAKING; - // interpolate between active color and full speaking color based on power of speech output - speakerp->mDotColor = speaking_color; - if (speakerp->mSpeechVolume > LLVoiceClient::OVERDRIVEN_POWER_LEVEL) - { - speakerp->mDotColor = overdriven_color; - } - } - else - { - speakerp->mSpeechVolume = 0.f; - speakerp->mDotColor = ACTIVE_COLOR; - - if (speakerp->mHasSpoken) - { - // have spoken once, not currently speaking - speakerp->mStatus = LLSpeaker::STATUS_HAS_SPOKEN; - } - else - { - // default state for being in voice channel - speakerp->mStatus = LLSpeaker::STATUS_VOICE_ACTIVE; - } - } - } - // speaker no longer registered in voice channel, demote to text only - else if (speakerp->mStatus != LLSpeaker::STATUS_NOT_IN_CHANNEL) - { - if(speakerp->mType == LLSpeaker::SPEAKER_EXTERNAL) - { - // external speakers should be timed out when they leave the voice channel (since they only exist via SLVoice) - speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL; - } - else - { - speakerp->mStatus = LLSpeaker::STATUS_TEXT_ONLY; - speakerp->mSpeechVolume = 0.f; - speakerp->mDotColor = ACTIVE_COLOR; - } - } - } - - if(resort_ok) // only allow list changes when user is not interacting with it - { - // sort by status then time last spoken - std::sort(mSpeakersSorted.begin(), mSpeakersSorted.end(), LLSortRecentSpeakers()); - } - - // for recent speakers who are not currently speaking, show "recent" color dot for most recent - // fading to "active" color - - S32 recent_speaker_count = 0; - S32 sort_index = 0; - speaker_list_t::iterator sorted_speaker_it; - for(sorted_speaker_it = mSpeakersSorted.begin(); - sorted_speaker_it != mSpeakersSorted.end(); ++sorted_speaker_it) - { - LLPointer speakerp = *sorted_speaker_it; - - // color code recent speakers who are not currently speaking - if (speakerp->mStatus == LLSpeaker::STATUS_HAS_SPOKEN) - { - speakerp->mDotColor = lerp(speaking_color, ACTIVE_COLOR, clamp_rescale((F32)recent_speaker_count, -2.f, 3.f, 0.f, 1.f)); - recent_speaker_count++; - } - - // stuff sort ordinal into speaker so the ui can sort by this value - speakerp->mSortIndex = sort_index++; - } -} - -void LLSpeakerMgr::updateSpeakerList() -{ - // Are we bound to the currently active voice channel? - if ((!mVoiceChannel && LLVoiceClient::getInstance()->inProximalChannel()) || (mVoiceChannel && mVoiceChannel->isActive())) - { - std::set participants; - LLVoiceClient::getInstance()->getParticipantList(participants); - // If we are, add all voice client participants to our list of known speakers - for (std::set::iterator participant_it = participants.begin(); participant_it != participants.end(); ++participant_it) - { - setSpeaker(*participant_it, - LLVoiceClient::getInstance()->getDisplayName(*participant_it), - LLSpeaker::STATUS_VOICE_ACTIVE, - (LLVoiceClient::getInstance()->isParticipantAvatar(*participant_it)?LLSpeaker::SPEAKER_AGENT:LLSpeaker::SPEAKER_EXTERNAL)); - } - } - else - { - // If not, check if the list is empty, except if it's Nearby Chat (session_id NULL). - LLUUID session_id = getSessionID(); - if (!session_id.isNull() && !mSpeakerListUpdated) - { - // If the list is empty, we update it with whatever we have locally so that it doesn't stay empty too long. - // *TODO: Fix the server side code that sometimes forgets to send back the list of participants after a chat started. - // (IOW, fix why we get no ChatterBoxSessionAgentListUpdates message after the initial ChatterBoxSessionStartReply) - LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(session_id); - if (session->isGroupSessionType() && (mSpeakers.size() <= 1)) - { - // For groups, we need to hit the group manager. - // Note: The session uuid and the group uuid are actually one and the same. If that was to change, this will fail. - LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(session_id); - - if (gdatap && gdatap->isMemberDataComplete() && !gdatap->mMembers.empty()) - { - // Add group members when we get the complete list (note: can take a while before we get that list) - LLGroupMgrGroupData::member_list_t::iterator member_it = gdatap->mMembers.begin(); - const S32 load_group_max_members = gSavedSettings.getS32("ChatLoadGroupMaxMembers"); - S32 updated = 0; - while (member_it != gdatap->mMembers.end()) - { - LLGroupMemberData* member = member_it->second; - LLUUID id = member_it->first; - // Add only members who are online and not already in the list - if ((member->getOnlineStatus() == "Online") && (mSpeakers.find(id) == mSpeakers.end())) - { - LLPointer speakerp = setSpeaker(id, "", LLSpeaker::STATUS_VOICE_ACTIVE, LLSpeaker::SPEAKER_AGENT); - speakerp->mIsModerator = ((member->getAgentPowers() & GP_SESSION_MODERATOR) == GP_SESSION_MODERATOR); - updated++; - } - ++member_it; - // Limit the number of "manually updated" participants to a reasonable number to avoid severe fps drop - // *TODO : solve the perf issue of having several hundreds of widgets in the conversation list - if (updated >= load_group_max_members) - break; - } - mSpeakerListUpdated = true; - } - } - else if (mSpeakers.size() == 0) - { - // For all other session type (ad-hoc, P2P), we use the initial participants targets list - for (uuid_vec_t::iterator it = session->mInitialTargetIDs.begin();it!=session->mInitialTargetIDs.end();++it) - { - // Add buddies if they are on line, add any other avatar. - if (!LLAvatarTracker::instance().isBuddy(*it) || LLAvatarTracker::instance().isBuddyOnline(*it)) - { - setSpeaker(*it, "", LLSpeaker::STATUS_VOICE_ACTIVE, LLSpeaker::SPEAKER_AGENT); - } - } - mSpeakerListUpdated = true; - } - else - { - // The list has been updated the normal way (i.e. by a ChatterBoxSessionAgentListUpdates received from the server) - mSpeakerListUpdated = true; - } - } - } - // Always add the current agent (it has to be there...). Will do nothing if already there. - setSpeaker(gAgentID, "", LLSpeaker::STATUS_VOICE_ACTIVE, LLSpeaker::SPEAKER_AGENT); -} - -void LLSpeakerMgr::setSpeakerNotInChannel(LLPointer speakerp) -{ - if (speakerp.notNull()) - { - speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL; - speakerp->mDotColor = INACTIVE_COLOR; - mSpeakerDelayRemover->setActionTimer(speakerp->mID); - } -} - -bool LLSpeakerMgr::removeSpeaker(const LLUUID& speaker_id) -{ - mSpeakers.erase(speaker_id); - - speaker_list_t::iterator sorted_speaker_it = mSpeakersSorted.begin(); - - for(; sorted_speaker_it != mSpeakersSorted.end(); ++sorted_speaker_it) - { - if (speaker_id == (*sorted_speaker_it)->mID) - { - mSpeakersSorted.erase(sorted_speaker_it); - break; - } - } - - LL_DEBUGS("Speakers") << "Removed speaker " << speaker_id << LL_ENDL; - fireEvent(new LLSpeakerListChangeEvent(this, speaker_id), "remove"); - - update(true); - - return false; -} - -LLPointer LLSpeakerMgr::findSpeaker(const LLUUID& speaker_id) -{ - //In some conditions map causes crash if it is empty(Windows only), adding check (EK) - if (mSpeakers.size() == 0) - return NULL; - speaker_map_t::iterator found_it = mSpeakers.find(speaker_id); - if (found_it == mSpeakers.end()) - { - return NULL; - } - return found_it->second; -} - -void LLSpeakerMgr::getSpeakerList(speaker_list_t* speaker_list, bool include_text) -{ - speaker_list->clear(); - for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end(); ++speaker_it) - { - LLPointer speakerp = speaker_it->second; - // what about text only muted or inactive? - if (include_text || speakerp->mStatus != LLSpeaker::STATUS_TEXT_ONLY) - { - speaker_list->push_back(speakerp); - } - } -} - -const LLUUID LLSpeakerMgr::getSessionID() -{ - return mVoiceChannel->getSessionID(); -} - -bool LLSpeakerMgr::isSpeakerToBeRemoved(const LLUUID& speaker_id) -{ - return mSpeakerDelayRemover && mSpeakerDelayRemover->isTimerStarted(speaker_id); -} - -void LLSpeakerMgr::setSpeakerTyping(const LLUUID& speaker_id, bool typing) -{ - LLPointer speakerp = findSpeaker(speaker_id); - if (speakerp.notNull()) - { - speakerp->mTyping = typing; - } -} - -// speaker has chatted via either text or voice -void LLSpeakerMgr::speakerChatted(const LLUUID& speaker_id) -{ - LLPointer speakerp = findSpeaker(speaker_id); - if (speakerp.notNull()) - { - speakerp->mLastSpokeTime = mSpeechTimer.getElapsedTimeF32(); - speakerp->mHasSpoken = true; - fireEvent(new LLSpeakerUpdateSpeakerEvent(speakerp), "update_speaker"); - } -} - -bool LLSpeakerMgr::isVoiceActive() -{ - // mVoiceChannel = NULL means current voice channel, whatever it is - return LLVoiceClient::getInstance()->voiceEnabled() && mVoiceChannel && mVoiceChannel->isActive(); -} - - -// -// LLIMSpeakerMgr -// -LLIMSpeakerMgr::LLIMSpeakerMgr(LLVoiceChannel* channel) : LLSpeakerMgr(channel) -{ -} - -void LLIMSpeakerMgr::updateSpeakerList() -{ - // don't do normal updates which are pulled from voice channel - // rely on user list reported by sim - - // We need to do this to allow PSTN callers into group chats to show in the list. - LLSpeakerMgr::updateSpeakerList(); - - return; -} - -void LLIMSpeakerMgr::setSpeakers(const LLSD& speakers) -{ - if ( !speakers.isMap() ) return; - - if ( speakers.has("agent_info") && speakers["agent_info"].isMap() ) - { - LLSD::map_const_iterator speaker_it; - for(speaker_it = speakers["agent_info"].beginMap(); - speaker_it != speakers["agent_info"].endMap(); - ++speaker_it) - { - LLUUID agent_id(speaker_it->first); - - LLPointer speakerp = setSpeaker( - agent_id, - LLStringUtil::null, - LLSpeaker::STATUS_TEXT_ONLY); - - if ( speaker_it->second.isMap() ) - { - bool is_moderator = speakerp->mIsModerator; - speakerp->mIsModerator = speaker_it->second["is_moderator"]; - speakerp->mModeratorMutedText = - speaker_it->second["mutes"]["text"]; - // Fire event only if moderator changed - if ( is_moderator != speakerp->mIsModerator ) - { - LL_DEBUGS("Speakers") << "Speaker " << agent_id << (is_moderator ? "is now" : "no longer is") << " a moderator" << LL_ENDL; - fireEvent(new LLSpeakerUpdateModeratorEvent(speakerp), "update_moderator"); - } - } - } - } - else if ( speakers.has("agents" ) && speakers["agents"].isArray() ) - { - //older, more decprecated way. Need here for - //using older version of servers - LLSD::array_const_iterator speaker_it; - for(speaker_it = speakers["agents"].beginArray(); - speaker_it != speakers["agents"].endArray(); - ++speaker_it) - { - const LLUUID agent_id = (*speaker_it).asUUID(); - - LLPointer speakerp = setSpeaker( - agent_id, - LLStringUtil::null, - LLSpeaker::STATUS_TEXT_ONLY); - } - } -} - -void LLIMSpeakerMgr::updateSpeakers(const LLSD& update) -{ - if ( !update.isMap() ) return; - - if ( update.has("agent_updates") && update["agent_updates"].isMap() ) - { - LLSD::map_const_iterator update_it; - for( - update_it = update["agent_updates"].beginMap(); - update_it != update["agent_updates"].endMap(); - ++update_it) - { - LLUUID agent_id(update_it->first); - LLPointer speakerp = findSpeaker(agent_id); - - LLSD agent_data = update_it->second; - - if (agent_data.isMap() && agent_data.has("transition")) - { - if (agent_data["transition"].asString() == "LEAVE") - { - setSpeakerNotInChannel(speakerp); - } - else if (agent_data["transition"].asString() == "ENTER") - { - // add or update speaker - speakerp = setSpeaker(agent_id); - } - else - { - LL_WARNS() << "bad membership list update from 'agent_updates' for agent " << agent_id << ", transition " << ll_print_sd(agent_data["transition"]) << LL_ENDL; - } - } - - if (speakerp.isNull()) continue; - - // should have a valid speaker from this point on - if (agent_data.isMap() && agent_data.has("info")) - { - LLSD agent_info = agent_data["info"]; - - if (agent_info.has("is_moderator")) - { - bool is_moderator = speakerp->mIsModerator; - speakerp->mIsModerator = agent_info["is_moderator"]; - // Fire event only if moderator changed - if ( is_moderator != speakerp->mIsModerator ) - { - LL_DEBUGS("Speakers") << "Speaker " << agent_id << (is_moderator ? "is now" : "no longer is") << " a moderator" << LL_ENDL; - fireEvent(new LLSpeakerUpdateModeratorEvent(speakerp), "update_moderator"); - } - } - - if (agent_info.has("mutes")) - { - speakerp->mModeratorMutedText = agent_info["mutes"]["text"]; - } - } - } - } - else if ( update.has("updates") && update["updates"].isMap() ) - { - LLSD::map_const_iterator update_it; - for ( - update_it = update["updates"].beginMap(); - update_it != update["updates"].endMap(); - ++update_it) - { - LLUUID agent_id(update_it->first); - LLPointer speakerp = findSpeaker(agent_id); - - std::string agent_transition = update_it->second.asString(); - if (agent_transition == "LEAVE") - { - setSpeakerNotInChannel(speakerp); - } - else if ( agent_transition == "ENTER") - { - // add or update speaker - speakerp = setSpeaker(agent_id); - } - else - { - LL_WARNS() << "bad membership list update from 'updates' for agent " << agent_id << ", transition " << agent_transition << LL_ENDL; - } - } - } -} - -void LLIMSpeakerMgr::toggleAllowTextChat(const LLUUID& speaker_id) -{ - LLPointer speakerp = findSpeaker(speaker_id); - if (!speakerp) return; - - std::string url = gAgent.getRegionCapability("ChatSessionRequest"); - LLSD data; - data["method"] = "mute update"; - data["session-id"] = getSessionID(); - data["params"] = LLSD::emptyMap(); - data["params"]["agent_id"] = speaker_id; - data["params"]["mute_info"] = LLSD::emptyMap(); - //current value represents ability to type, so invert - data["params"]["mute_info"]["text"] = !speakerp->mModeratorMutedText; - - LLCoros::instance().launch("LLIMSpeakerMgr::moderationActionCoro", - boost::bind(&LLIMSpeakerMgr::moderationActionCoro, this, url, data)); -} - -void LLIMSpeakerMgr::moderateVoiceParticipant(const LLUUID& avatar_id, bool unmute) -{ - LLPointer speakerp = findSpeaker(avatar_id); - if (!speakerp) return; - - // *NOTE: mantipov: probably this condition will be incorrect when avatar will be blocked for - // text chat via moderation (LLSpeaker::mModeratorMutedText == true) - bool is_in_voice = speakerp->mStatus <= LLSpeaker::STATUS_VOICE_ACTIVE || speakerp->mStatus == LLSpeaker::STATUS_MUTED; - - // do not send voice moderation changes for avatars not in voice channel - if (!is_in_voice) return; - - std::string url = gAgent.getRegionCapability("ChatSessionRequest"); - LLSD data; - data["method"] = "mute update"; - data["session-id"] = getSessionID(); - data["params"] = LLSD::emptyMap(); - data["params"]["agent_id"] = avatar_id; - data["params"]["mute_info"] = LLSD::emptyMap(); - data["params"]["mute_info"]["voice"] = !unmute; - - LLCoros::instance().launch("LLIMSpeakerMgr::moderationActionCoro", - boost::bind(&LLIMSpeakerMgr::moderationActionCoro, this, url, data)); -} - -void LLIMSpeakerMgr::moderationActionCoro(std::string url, LLSD action) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("moderationActionCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); - - httpOpts->setWantHeaders(true); - - LLUUID sessionId = action["session-id"]; - - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, action, httpOpts); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - if (gIMMgr) - { - //403 == you're not a mod - //should be disabled if you're not a moderator - if (status == LLCore::HttpStatus(HTTP_FORBIDDEN)) - { - gIMMgr->showSessionEventError( - "mute", - "not_a_mod_error", - sessionId); - } - else - { - gIMMgr->showSessionEventError( - "mute", - "generic_request_error", - sessionId); - } - } - return; - } -} - -void LLIMSpeakerMgr::moderateVoiceAllParticipants( bool unmute_everyone ) -{ - if (mVoiceModerated == !unmute_everyone) - { - // session already in requested state. Just force participants which do not match it. - forceVoiceModeratedMode(mVoiceModerated); - } - else - { - // otherwise set moderated mode for a whole session. - moderateVoiceSession(getSessionID(), !unmute_everyone); - } -} - -void LLIMSpeakerMgr::processSessionUpdate(const LLSD& session_update) -{ - if (session_update.has("moderated_mode") && - session_update["moderated_mode"].has("voice")) - { - mVoiceModerated = session_update["moderated_mode"]["voice"]; - } -} - -void LLIMSpeakerMgr::moderateVoiceSession(const LLUUID& session_id, bool disallow_voice) -{ - std::string url = gAgent.getRegionCapability("ChatSessionRequest"); - LLSD data; - data["method"] = "session update"; - data["session-id"] = session_id; - data["params"] = LLSD::emptyMap(); - - data["params"]["update_info"] = LLSD::emptyMap(); - - data["params"]["update_info"]["moderated_mode"] = LLSD::emptyMap(); - data["params"]["update_info"]["moderated_mode"]["voice"] = disallow_voice; - - LLCoros::instance().launch("LLIMSpeakerMgr::moderationActionCoro", - boost::bind(&LLIMSpeakerMgr::moderationActionCoro, this, url, data)); -} - -void LLIMSpeakerMgr::forceVoiceModeratedMode(bool should_be_muted) -{ - for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end(); ++speaker_it) - { - LLUUID speaker_id = speaker_it->first; - LLSpeaker* speakerp = speaker_it->second; - - // participant does not match requested state - if (should_be_muted != (bool)speakerp->mModeratorMutedVoice) - { - moderateVoiceParticipant(speaker_id, !should_be_muted); - } - } -} - -// -// LLActiveSpeakerMgr -// - -LLActiveSpeakerMgr::LLActiveSpeakerMgr() : LLSpeakerMgr(NULL) -{ -} - -void LLActiveSpeakerMgr::updateSpeakerList() -{ - // point to whatever the current voice channel is - mVoiceChannel = LLVoiceChannel::getCurrentVoiceChannel(); - - // always populate from active voice channel - if (LLVoiceChannel::getCurrentVoiceChannel() != mVoiceChannel) //MA: seems this is always false - { - LL_DEBUGS("Speakers") << "Removed all speakers" << LL_ENDL; - fireEvent(new LLSpeakerListChangeEvent(this, LLUUID::null), "clear"); - mSpeakers.clear(); - mSpeakersSorted.clear(); - mVoiceChannel = LLVoiceChannel::getCurrentVoiceChannel(); - mSpeakerDelayRemover->removeAllTimers(); - } - LLSpeakerMgr::updateSpeakerList(); - - // clean up text only speakers - for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end(); ++speaker_it) - { - LLSpeaker* speakerp = speaker_it->second; - if (speakerp->mStatus == LLSpeaker::STATUS_TEXT_ONLY) - { - // automatically flag text only speakers for removal - speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL; - } - } - -} - - - -// -// LLLocalSpeakerMgr -// - -LLLocalSpeakerMgr::LLLocalSpeakerMgr() : LLSpeakerMgr(LLVoiceChannelProximal::getInstance()) -{ -} - -LLLocalSpeakerMgr::~LLLocalSpeakerMgr () -{ -} - -void LLLocalSpeakerMgr::updateSpeakerList() -{ - // pull speakers from voice channel - LLSpeakerMgr::updateSpeakerList(); - - if (gDisconnected)//the world is cleared. - { - return ; - } - - // pick up non-voice speakers in chat range - uuid_vec_t avatar_ids; - std::vector positions; - LLWorld::getInstance()->getAvatars(&avatar_ids, &positions, gAgent.getPositionGlobal(), CHAT_NORMAL_RADIUS); - for(U32 i=0; ifirst; - LLPointer speakerp = speaker_it->second; - if (speakerp.notNull() && speakerp->mStatus == LLSpeaker::STATUS_TEXT_ONLY) - { - LLVOAvatar* avatarp = (LLVOAvatar*)gObjectList.findObject(speaker_id); - if (!avatarp || dist_vec_squared(avatarp->getPositionAgent(), gAgent.getPositionAgent()) > CHAT_NORMAL_RADIUS * CHAT_NORMAL_RADIUS) - { - setSpeakerNotInChannel(speakerp); - } - } - } -} +/** + * @file llspeakers.cpp + * @brief Management interface for muting and controlling volume of residents currently speaking + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llspeakers.h" + +#include "llagent.h" +#include "llavatarnamecache.h" +#include "llappviewer.h" +#include "llimview.h" +#include "llgroupmgr.h" +#include "llsdutil.h" +#include "lluicolortable.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llvoavatar.h" +#include "llworld.h" +#include "llcorehttputil.h" + +extern LLControlGroup gSavedSettings; + +const LLColor4 INACTIVE_COLOR(0.3f, 0.3f, 0.3f, 0.5f); +const LLColor4 ACTIVE_COLOR(0.5f, 0.5f, 0.5f, 1.f); + +LLSpeaker::LLSpeaker(const LLUUID& id, const std::string& name, const ESpeakerType type) : + mStatus(LLSpeaker::STATUS_TEXT_ONLY), + mLastSpokeTime(0.f), + mSpeechVolume(0.f), + mHasSpoken(false), + mHasLeftCurrentCall(false), + mDotColor(LLColor4::white), + mID(id), + mTyping(false), + mSortIndex(0), + mType(type), + mIsModerator(false), + mModeratorMutedVoice(false), + mModeratorMutedText(false) +{ + if (name.empty() && type == SPEAKER_AGENT) + { + lookupName(); + } + else + { + mDisplayName = name; + } +} + + +void LLSpeaker::lookupName() +{ + if (mDisplayName.empty()) + { + LLAvatarNameCache::get(mID, boost::bind(&LLSpeaker::onNameCache, this, _1, _2)); // todo: can be group??? + } +} + +void LLSpeaker::onNameCache(const LLUUID& id, const LLAvatarName& av_name) +{ + mDisplayName = av_name.getUserName(); +} + +bool LLSpeaker::isInVoiceChannel() +{ + return mStatus <= LLSpeaker::STATUS_VOICE_ACTIVE || mStatus == LLSpeaker::STATUS_MUTED; +} + +LLSpeakerUpdateSpeakerEvent::LLSpeakerUpdateSpeakerEvent(LLSpeaker* source) +: LLEvent(source, "Speaker update speaker event"), + mSpeakerID (source->mID) +{ +} + +LLSD LLSpeakerUpdateSpeakerEvent::getValue() +{ + LLSD ret; + ret["id"] = mSpeakerID; + return ret; +} + +LLSpeakerUpdateModeratorEvent::LLSpeakerUpdateModeratorEvent(LLSpeaker* source) +: LLEvent(source, "Speaker add moderator event"), + mSpeakerID (source->mID), + mIsModerator (source->mIsModerator) +{ +} + +LLSD LLSpeakerUpdateModeratorEvent::getValue() +{ + LLSD ret; + ret["id"] = mSpeakerID; + ret["is_moderator"] = mIsModerator; + return ret; +} + +LLSpeakerTextModerationEvent::LLSpeakerTextModerationEvent(LLSpeaker* source) +: LLEvent(source, "Speaker text moderation event") +{ +} + +LLSD LLSpeakerTextModerationEvent::getValue() +{ + return std::string("text"); +} + + +LLSpeakerVoiceModerationEvent::LLSpeakerVoiceModerationEvent(LLSpeaker* source) +: LLEvent(source, "Speaker voice moderation event") +{ +} + +LLSD LLSpeakerVoiceModerationEvent::getValue() +{ + return std::string("voice"); +} + +LLSpeakerListChangeEvent::LLSpeakerListChangeEvent(LLSpeakerMgr* source, const LLUUID& speaker_id) +: LLEvent(source, "Speaker added/removed from speaker mgr"), + mSpeakerID(speaker_id) +{ +} + +LLSD LLSpeakerListChangeEvent::getValue() +{ + return mSpeakerID; +} + +// helper sort class +struct LLSortRecentSpeakers +{ + bool operator()(const LLPointer lhs, const LLPointer rhs) const; +}; + +bool LLSortRecentSpeakers::operator()(const LLPointer lhs, const LLPointer rhs) const +{ + // Sort first on status + if (lhs->mStatus != rhs->mStatus) + { + return (lhs->mStatus < rhs->mStatus); + } + + // and then on last speaking time + if(lhs->mLastSpokeTime != rhs->mLastSpokeTime) + { + return (lhs->mLastSpokeTime > rhs->mLastSpokeTime); + } + + // and finally (only if those are both equal), on name. + return( lhs->mDisplayName.compare(rhs->mDisplayName) < 0 ); +} + +LLSpeakerActionTimer::LLSpeakerActionTimer(action_callback_t action_cb, F32 action_period, const LLUUID& speaker_id) +: LLEventTimer(action_period) +, mActionCallback(action_cb) +, mSpeakerId(speaker_id) +{ +} + +bool LLSpeakerActionTimer::tick() +{ + if (mActionCallback) + { + return (bool)mActionCallback(mSpeakerId); + } + return true; +} + +void LLSpeakerActionTimer::unset() +{ + mActionCallback = 0; +} + +LLSpeakersDelayActionsStorage::LLSpeakersDelayActionsStorage(LLSpeakerActionTimer::action_callback_t action_cb, F32 action_delay) +: mActionCallback(action_cb) +, mActionDelay(action_delay) +{ +} + +LLSpeakersDelayActionsStorage::~LLSpeakersDelayActionsStorage() +{ + removeAllTimers(); +} + +void LLSpeakersDelayActionsStorage::setActionTimer(const LLUUID& speaker_id) +{ + bool not_found = true; + if (mActionTimersMap.size() > 0) + { + not_found = mActionTimersMap.find(speaker_id) == mActionTimersMap.end(); + } + + // If there is already a started timer for the passed UUID don't do anything. + if (not_found) + { + // Starting a timer to remove an participant after delay is completed + mActionTimersMap.insert(LLSpeakerActionTimer::action_value_t(speaker_id, + new LLSpeakerActionTimer( + boost::bind(&LLSpeakersDelayActionsStorage::onTimerActionCallback, this, _1), + mActionDelay, speaker_id))); + } +} + +void LLSpeakersDelayActionsStorage::unsetActionTimer(const LLUUID& speaker_id) +{ + if (mActionTimersMap.size() == 0) return; + + LLSpeakerActionTimer::action_timer_iter_t it_speaker = mActionTimersMap.find(speaker_id); + + if (it_speaker != mActionTimersMap.end()) + { + it_speaker->second->unset(); + mActionTimersMap.erase(it_speaker); + } +} + +void LLSpeakersDelayActionsStorage::removeAllTimers() +{ + LLSpeakerActionTimer::action_timer_iter_t iter = mActionTimersMap.begin(); + for (; iter != mActionTimersMap.end(); ++iter) + { + delete iter->second; + } + mActionTimersMap.clear(); +} + +bool LLSpeakersDelayActionsStorage::onTimerActionCallback(const LLUUID& speaker_id) +{ + unsetActionTimer(speaker_id); + + if (mActionCallback) + { + mActionCallback(speaker_id); + } + + return true; +} + +bool LLSpeakersDelayActionsStorage::isTimerStarted(const LLUUID& speaker_id) +{ + return (mActionTimersMap.size() > 0) && (mActionTimersMap.find(speaker_id) != mActionTimersMap.end()); +} + +// +// LLSpeakerMgr +// + +LLSpeakerMgr::LLSpeakerMgr(LLVoiceChannel* channelp) : + mVoiceChannel(channelp), + mVoiceModerated(false), + mModerateModeHandledFirstTime(false), + mSpeakerListUpdated(false) +{ + mGetListTime.reset(); + static LLUICachedControl remove_delay ("SpeakerParticipantRemoveDelay", 10.0); + + mSpeakerDelayRemover = new LLSpeakersDelayActionsStorage(boost::bind(&LLSpeakerMgr::removeSpeaker, this, _1), remove_delay); +} + +LLSpeakerMgr::~LLSpeakerMgr() +{ + delete mSpeakerDelayRemover; +} + +LLPointer LLSpeakerMgr::setSpeaker(const LLUUID& id, const std::string& name, LLSpeaker::ESpeakerStatus status, LLSpeaker::ESpeakerType type) +{ + LLUUID session_id = getSessionID(); + if (id.isNull() || (id == session_id)) + { + return NULL; + } + + LLPointer speakerp; + if (mSpeakers.find(id) == mSpeakers.end()) + { + speakerp = new LLSpeaker(id, name, type); + speakerp->mStatus = status; + mSpeakers.insert(std::make_pair(speakerp->mID, speakerp)); + mSpeakersSorted.push_back(speakerp); + LL_DEBUGS("Speakers") << "Added speaker " << id << LL_ENDL; + fireEvent(new LLSpeakerListChangeEvent(this, speakerp->mID), "add"); + } + else + { + speakerp = findSpeaker(id); + if (speakerp.notNull()) + { + // keep highest priority status (lowest value) instead of overriding current value + speakerp->mStatus = llmin(speakerp->mStatus, status); + // RN: due to a weird behavior where IMs from attached objects come from the wearer's agent_id + // we need to override speakers that we think are objects when we find out they are really + // residents + if (type == LLSpeaker::SPEAKER_AGENT) + { + speakerp->mType = LLSpeaker::SPEAKER_AGENT; + speakerp->lookupName(); + } + } + else + { + LL_WARNS("Speakers") << "Speaker " << id << " not found" << LL_ENDL; + } + } + + mSpeakerDelayRemover->unsetActionTimer(speakerp->mID); + return speakerp; +} + +// *TODO: Once way to request the current voice channel moderation mode is implemented +// this method with related code should be removed. +/* + Initializes "moderate_mode" of voice session on first join. + + This is WORKAROUND because a way to request the current voice channel moderation mode exists + but is not implemented in viewer yet. See EXT-6937. +*/ +void LLSpeakerMgr::initVoiceModerateMode() +{ + if (!mModerateModeHandledFirstTime && (mVoiceChannel && mVoiceChannel->isActive())) + { + LLPointer speakerp; + + if (mSpeakers.find(gAgentID) != mSpeakers.end()) + { + speakerp = mSpeakers[gAgentID]; + } + + if (speakerp.notNull()) + { + mVoiceModerated = speakerp->mModeratorMutedVoice; + mModerateModeHandledFirstTime = true; + } + } +} + +void LLSpeakerMgr::update(bool resort_ok) +{ + if (!LLVoiceClient::getInstance()) + { + return; + } + + LLColor4 speaking_color = LLUIColorTable::instance().getColor("SpeakingColor"); + LLColor4 overdriven_color = LLUIColorTable::instance().getColor("OverdrivenColor"); + + if(resort_ok) // only allow list changes when user is not interacting with it + { + updateSpeakerList(); + } + + // update status of all current speakers + bool voice_channel_active = (!mVoiceChannel && LLVoiceClient::getInstance()->inProximalChannel()) || (mVoiceChannel && mVoiceChannel->isActive()); + for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end(); speaker_it++) + { + LLUUID speaker_id = speaker_it->first; + LLSpeaker* speakerp = speaker_it->second; + + if (voice_channel_active && LLVoiceClient::getInstance()->getVoiceEnabled(speaker_id)) + { + speakerp->mSpeechVolume = LLVoiceClient::getInstance()->getCurrentPower(speaker_id); + bool moderator_muted_voice = LLVoiceClient::getInstance()->getIsModeratorMuted(speaker_id); + if (moderator_muted_voice != speakerp->mModeratorMutedVoice) + { + speakerp->mModeratorMutedVoice = moderator_muted_voice; + LL_DEBUGS("Speakers") << (speakerp->mModeratorMutedVoice? "Muted" : "Umuted") << " speaker " << speaker_id<< LL_ENDL; + speakerp->fireEvent(new LLSpeakerVoiceModerationEvent(speakerp)); + } + + if (LLVoiceClient::getInstance()->getOnMuteList(speaker_id) || speakerp->mModeratorMutedVoice) + { + speakerp->mStatus = LLSpeaker::STATUS_MUTED; + } + else if (LLVoiceClient::getInstance()->getIsSpeaking(speaker_id)) + { + // reset inactivity expiration + if (speakerp->mStatus != LLSpeaker::STATUS_SPEAKING) + { + speakerp->mLastSpokeTime = mSpeechTimer.getElapsedTimeF32(); + speakerp->mHasSpoken = true; + fireEvent(new LLSpeakerUpdateSpeakerEvent(speakerp), "update_speaker"); + } + speakerp->mStatus = LLSpeaker::STATUS_SPEAKING; + // interpolate between active color and full speaking color based on power of speech output + speakerp->mDotColor = speaking_color; + if (speakerp->mSpeechVolume > LLVoiceClient::OVERDRIVEN_POWER_LEVEL) + { + speakerp->mDotColor = overdriven_color; + } + } + else + { + speakerp->mSpeechVolume = 0.f; + speakerp->mDotColor = ACTIVE_COLOR; + + if (speakerp->mHasSpoken) + { + // have spoken once, not currently speaking + speakerp->mStatus = LLSpeaker::STATUS_HAS_SPOKEN; + } + else + { + // default state for being in voice channel + speakerp->mStatus = LLSpeaker::STATUS_VOICE_ACTIVE; + } + } + } + // speaker no longer registered in voice channel, demote to text only + else if (speakerp->mStatus != LLSpeaker::STATUS_NOT_IN_CHANNEL) + { + if(speakerp->mType == LLSpeaker::SPEAKER_EXTERNAL) + { + // external speakers should be timed out when they leave the voice channel (since they only exist via SLVoice) + speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL; + } + else + { + speakerp->mStatus = LLSpeaker::STATUS_TEXT_ONLY; + speakerp->mSpeechVolume = 0.f; + speakerp->mDotColor = ACTIVE_COLOR; + } + } + } + + if(resort_ok) // only allow list changes when user is not interacting with it + { + // sort by status then time last spoken + std::sort(mSpeakersSorted.begin(), mSpeakersSorted.end(), LLSortRecentSpeakers()); + } + + // for recent speakers who are not currently speaking, show "recent" color dot for most recent + // fading to "active" color + + S32 recent_speaker_count = 0; + S32 sort_index = 0; + speaker_list_t::iterator sorted_speaker_it; + for(sorted_speaker_it = mSpeakersSorted.begin(); + sorted_speaker_it != mSpeakersSorted.end(); ++sorted_speaker_it) + { + LLPointer speakerp = *sorted_speaker_it; + + // color code recent speakers who are not currently speaking + if (speakerp->mStatus == LLSpeaker::STATUS_HAS_SPOKEN) + { + speakerp->mDotColor = lerp(speaking_color, ACTIVE_COLOR, clamp_rescale((F32)recent_speaker_count, -2.f, 3.f, 0.f, 1.f)); + recent_speaker_count++; + } + + // stuff sort ordinal into speaker so the ui can sort by this value + speakerp->mSortIndex = sort_index++; + } +} + +void LLSpeakerMgr::updateSpeakerList() +{ + // Are we bound to the currently active voice channel? + if ((!mVoiceChannel && LLVoiceClient::getInstance()->inProximalChannel()) || (mVoiceChannel && mVoiceChannel->isActive())) + { + std::set participants; + LLVoiceClient::getInstance()->getParticipantList(participants); + // If we are, add all voice client participants to our list of known speakers + for (std::set::iterator participant_it = participants.begin(); participant_it != participants.end(); ++participant_it) + { + setSpeaker(*participant_it, + LLVoiceClient::getInstance()->getDisplayName(*participant_it), + LLSpeaker::STATUS_VOICE_ACTIVE, + (LLVoiceClient::getInstance()->isParticipantAvatar(*participant_it)?LLSpeaker::SPEAKER_AGENT:LLSpeaker::SPEAKER_EXTERNAL)); + } + } + else + { + // If not, check if the list is empty, except if it's Nearby Chat (session_id NULL). + LLUUID session_id = getSessionID(); + if (!session_id.isNull() && !mSpeakerListUpdated) + { + // If the list is empty, we update it with whatever we have locally so that it doesn't stay empty too long. + // *TODO: Fix the server side code that sometimes forgets to send back the list of participants after a chat started. + // (IOW, fix why we get no ChatterBoxSessionAgentListUpdates message after the initial ChatterBoxSessionStartReply) + LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(session_id); + if (session->isGroupSessionType() && (mSpeakers.size() <= 1)) + { + // For groups, we need to hit the group manager. + // Note: The session uuid and the group uuid are actually one and the same. If that was to change, this will fail. + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(session_id); + + if (gdatap && gdatap->isMemberDataComplete() && !gdatap->mMembers.empty()) + { + // Add group members when we get the complete list (note: can take a while before we get that list) + LLGroupMgrGroupData::member_list_t::iterator member_it = gdatap->mMembers.begin(); + const S32 load_group_max_members = gSavedSettings.getS32("ChatLoadGroupMaxMembers"); + S32 updated = 0; + while (member_it != gdatap->mMembers.end()) + { + LLGroupMemberData* member = member_it->second; + LLUUID id = member_it->first; + // Add only members who are online and not already in the list + if ((member->getOnlineStatus() == "Online") && (mSpeakers.find(id) == mSpeakers.end())) + { + LLPointer speakerp = setSpeaker(id, "", LLSpeaker::STATUS_VOICE_ACTIVE, LLSpeaker::SPEAKER_AGENT); + speakerp->mIsModerator = ((member->getAgentPowers() & GP_SESSION_MODERATOR) == GP_SESSION_MODERATOR); + updated++; + } + ++member_it; + // Limit the number of "manually updated" participants to a reasonable number to avoid severe fps drop + // *TODO : solve the perf issue of having several hundreds of widgets in the conversation list + if (updated >= load_group_max_members) + break; + } + mSpeakerListUpdated = true; + } + } + else if (mSpeakers.size() == 0) + { + // For all other session type (ad-hoc, P2P), we use the initial participants targets list + for (uuid_vec_t::iterator it = session->mInitialTargetIDs.begin();it!=session->mInitialTargetIDs.end();++it) + { + // Add buddies if they are on line, add any other avatar. + if (!LLAvatarTracker::instance().isBuddy(*it) || LLAvatarTracker::instance().isBuddyOnline(*it)) + { + setSpeaker(*it, "", LLSpeaker::STATUS_VOICE_ACTIVE, LLSpeaker::SPEAKER_AGENT); + } + } + mSpeakerListUpdated = true; + } + else + { + // The list has been updated the normal way (i.e. by a ChatterBoxSessionAgentListUpdates received from the server) + mSpeakerListUpdated = true; + } + } + } + // Always add the current agent (it has to be there...). Will do nothing if already there. + setSpeaker(gAgentID, "", LLSpeaker::STATUS_VOICE_ACTIVE, LLSpeaker::SPEAKER_AGENT); +} + +void LLSpeakerMgr::setSpeakerNotInChannel(LLPointer speakerp) +{ + if (speakerp.notNull()) + { + speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL; + speakerp->mDotColor = INACTIVE_COLOR; + mSpeakerDelayRemover->setActionTimer(speakerp->mID); + } +} + +bool LLSpeakerMgr::removeSpeaker(const LLUUID& speaker_id) +{ + mSpeakers.erase(speaker_id); + + speaker_list_t::iterator sorted_speaker_it = mSpeakersSorted.begin(); + + for(; sorted_speaker_it != mSpeakersSorted.end(); ++sorted_speaker_it) + { + if (speaker_id == (*sorted_speaker_it)->mID) + { + mSpeakersSorted.erase(sorted_speaker_it); + break; + } + } + + LL_DEBUGS("Speakers") << "Removed speaker " << speaker_id << LL_ENDL; + fireEvent(new LLSpeakerListChangeEvent(this, speaker_id), "remove"); + + update(true); + + return false; +} + +LLPointer LLSpeakerMgr::findSpeaker(const LLUUID& speaker_id) +{ + //In some conditions map causes crash if it is empty(Windows only), adding check (EK) + if (mSpeakers.size() == 0) + return NULL; + speaker_map_t::iterator found_it = mSpeakers.find(speaker_id); + if (found_it == mSpeakers.end()) + { + return NULL; + } + return found_it->second; +} + +void LLSpeakerMgr::getSpeakerList(speaker_list_t* speaker_list, bool include_text) +{ + speaker_list->clear(); + for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end(); ++speaker_it) + { + LLPointer speakerp = speaker_it->second; + // what about text only muted or inactive? + if (include_text || speakerp->mStatus != LLSpeaker::STATUS_TEXT_ONLY) + { + speaker_list->push_back(speakerp); + } + } +} + +const LLUUID LLSpeakerMgr::getSessionID() +{ + return mVoiceChannel->getSessionID(); +} + +bool LLSpeakerMgr::isSpeakerToBeRemoved(const LLUUID& speaker_id) +{ + return mSpeakerDelayRemover && mSpeakerDelayRemover->isTimerStarted(speaker_id); +} + +void LLSpeakerMgr::setSpeakerTyping(const LLUUID& speaker_id, bool typing) +{ + LLPointer speakerp = findSpeaker(speaker_id); + if (speakerp.notNull()) + { + speakerp->mTyping = typing; + } +} + +// speaker has chatted via either text or voice +void LLSpeakerMgr::speakerChatted(const LLUUID& speaker_id) +{ + LLPointer speakerp = findSpeaker(speaker_id); + if (speakerp.notNull()) + { + speakerp->mLastSpokeTime = mSpeechTimer.getElapsedTimeF32(); + speakerp->mHasSpoken = true; + fireEvent(new LLSpeakerUpdateSpeakerEvent(speakerp), "update_speaker"); + } +} + +bool LLSpeakerMgr::isVoiceActive() +{ + // mVoiceChannel = NULL means current voice channel, whatever it is + return LLVoiceClient::getInstance()->voiceEnabled() && mVoiceChannel && mVoiceChannel->isActive(); +} + + +// +// LLIMSpeakerMgr +// +LLIMSpeakerMgr::LLIMSpeakerMgr(LLVoiceChannel* channel) : LLSpeakerMgr(channel) +{ +} + +void LLIMSpeakerMgr::updateSpeakerList() +{ + // don't do normal updates which are pulled from voice channel + // rely on user list reported by sim + + // We need to do this to allow PSTN callers into group chats to show in the list. + LLSpeakerMgr::updateSpeakerList(); + + return; +} + +void LLIMSpeakerMgr::setSpeakers(const LLSD& speakers) +{ + if ( !speakers.isMap() ) return; + + if ( speakers.has("agent_info") && speakers["agent_info"].isMap() ) + { + LLSD::map_const_iterator speaker_it; + for(speaker_it = speakers["agent_info"].beginMap(); + speaker_it != speakers["agent_info"].endMap(); + ++speaker_it) + { + LLUUID agent_id(speaker_it->first); + + LLPointer speakerp = setSpeaker( + agent_id, + LLStringUtil::null, + LLSpeaker::STATUS_TEXT_ONLY); + + if ( speaker_it->second.isMap() ) + { + bool is_moderator = speakerp->mIsModerator; + speakerp->mIsModerator = speaker_it->second["is_moderator"]; + speakerp->mModeratorMutedText = + speaker_it->second["mutes"]["text"]; + // Fire event only if moderator changed + if ( is_moderator != speakerp->mIsModerator ) + { + LL_DEBUGS("Speakers") << "Speaker " << agent_id << (is_moderator ? "is now" : "no longer is") << " a moderator" << LL_ENDL; + fireEvent(new LLSpeakerUpdateModeratorEvent(speakerp), "update_moderator"); + } + } + } + } + else if ( speakers.has("agents" ) && speakers["agents"].isArray() ) + { + //older, more decprecated way. Need here for + //using older version of servers + LLSD::array_const_iterator speaker_it; + for(speaker_it = speakers["agents"].beginArray(); + speaker_it != speakers["agents"].endArray(); + ++speaker_it) + { + const LLUUID agent_id = (*speaker_it).asUUID(); + + LLPointer speakerp = setSpeaker( + agent_id, + LLStringUtil::null, + LLSpeaker::STATUS_TEXT_ONLY); + } + } +} + +void LLIMSpeakerMgr::updateSpeakers(const LLSD& update) +{ + if ( !update.isMap() ) return; + + if ( update.has("agent_updates") && update["agent_updates"].isMap() ) + { + LLSD::map_const_iterator update_it; + for( + update_it = update["agent_updates"].beginMap(); + update_it != update["agent_updates"].endMap(); + ++update_it) + { + LLUUID agent_id(update_it->first); + LLPointer speakerp = findSpeaker(agent_id); + + LLSD agent_data = update_it->second; + + if (agent_data.isMap() && agent_data.has("transition")) + { + if (agent_data["transition"].asString() == "LEAVE") + { + setSpeakerNotInChannel(speakerp); + } + else if (agent_data["transition"].asString() == "ENTER") + { + // add or update speaker + speakerp = setSpeaker(agent_id); + } + else + { + LL_WARNS() << "bad membership list update from 'agent_updates' for agent " << agent_id << ", transition " << ll_print_sd(agent_data["transition"]) << LL_ENDL; + } + } + + if (speakerp.isNull()) continue; + + // should have a valid speaker from this point on + if (agent_data.isMap() && agent_data.has("info")) + { + LLSD agent_info = agent_data["info"]; + + if (agent_info.has("is_moderator")) + { + bool is_moderator = speakerp->mIsModerator; + speakerp->mIsModerator = agent_info["is_moderator"]; + // Fire event only if moderator changed + if ( is_moderator != speakerp->mIsModerator ) + { + LL_DEBUGS("Speakers") << "Speaker " << agent_id << (is_moderator ? "is now" : "no longer is") << " a moderator" << LL_ENDL; + fireEvent(new LLSpeakerUpdateModeratorEvent(speakerp), "update_moderator"); + } + } + + if (agent_info.has("mutes")) + { + speakerp->mModeratorMutedText = agent_info["mutes"]["text"]; + } + } + } + } + else if ( update.has("updates") && update["updates"].isMap() ) + { + LLSD::map_const_iterator update_it; + for ( + update_it = update["updates"].beginMap(); + update_it != update["updates"].endMap(); + ++update_it) + { + LLUUID agent_id(update_it->first); + LLPointer speakerp = findSpeaker(agent_id); + + std::string agent_transition = update_it->second.asString(); + if (agent_transition == "LEAVE") + { + setSpeakerNotInChannel(speakerp); + } + else if ( agent_transition == "ENTER") + { + // add or update speaker + speakerp = setSpeaker(agent_id); + } + else + { + LL_WARNS() << "bad membership list update from 'updates' for agent " << agent_id << ", transition " << agent_transition << LL_ENDL; + } + } + } +} + +void LLIMSpeakerMgr::toggleAllowTextChat(const LLUUID& speaker_id) +{ + LLPointer speakerp = findSpeaker(speaker_id); + if (!speakerp) return; + + std::string url = gAgent.getRegionCapability("ChatSessionRequest"); + LLSD data; + data["method"] = "mute update"; + data["session-id"] = getSessionID(); + data["params"] = LLSD::emptyMap(); + data["params"]["agent_id"] = speaker_id; + data["params"]["mute_info"] = LLSD::emptyMap(); + //current value represents ability to type, so invert + data["params"]["mute_info"]["text"] = !speakerp->mModeratorMutedText; + + LLCoros::instance().launch("LLIMSpeakerMgr::moderationActionCoro", + boost::bind(&LLIMSpeakerMgr::moderationActionCoro, this, url, data)); +} + +void LLIMSpeakerMgr::moderateVoiceParticipant(const LLUUID& avatar_id, bool unmute) +{ + LLPointer speakerp = findSpeaker(avatar_id); + if (!speakerp) return; + + // *NOTE: mantipov: probably this condition will be incorrect when avatar will be blocked for + // text chat via moderation (LLSpeaker::mModeratorMutedText == true) + bool is_in_voice = speakerp->mStatus <= LLSpeaker::STATUS_VOICE_ACTIVE || speakerp->mStatus == LLSpeaker::STATUS_MUTED; + + // do not send voice moderation changes for avatars not in voice channel + if (!is_in_voice) return; + + std::string url = gAgent.getRegionCapability("ChatSessionRequest"); + LLSD data; + data["method"] = "mute update"; + data["session-id"] = getSessionID(); + data["params"] = LLSD::emptyMap(); + data["params"]["agent_id"] = avatar_id; + data["params"]["mute_info"] = LLSD::emptyMap(); + data["params"]["mute_info"]["voice"] = !unmute; + + LLCoros::instance().launch("LLIMSpeakerMgr::moderationActionCoro", + boost::bind(&LLIMSpeakerMgr::moderationActionCoro, this, url, data)); +} + +void LLIMSpeakerMgr::moderationActionCoro(std::string url, LLSD action) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("moderationActionCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); + + httpOpts->setWantHeaders(true); + + LLUUID sessionId = action["session-id"]; + + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, action, httpOpts); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + if (gIMMgr) + { + //403 == you're not a mod + //should be disabled if you're not a moderator + if (status == LLCore::HttpStatus(HTTP_FORBIDDEN)) + { + gIMMgr->showSessionEventError( + "mute", + "not_a_mod_error", + sessionId); + } + else + { + gIMMgr->showSessionEventError( + "mute", + "generic_request_error", + sessionId); + } + } + return; + } +} + +void LLIMSpeakerMgr::moderateVoiceAllParticipants( bool unmute_everyone ) +{ + if (mVoiceModerated == !unmute_everyone) + { + // session already in requested state. Just force participants which do not match it. + forceVoiceModeratedMode(mVoiceModerated); + } + else + { + // otherwise set moderated mode for a whole session. + moderateVoiceSession(getSessionID(), !unmute_everyone); + } +} + +void LLIMSpeakerMgr::processSessionUpdate(const LLSD& session_update) +{ + if (session_update.has("moderated_mode") && + session_update["moderated_mode"].has("voice")) + { + mVoiceModerated = session_update["moderated_mode"]["voice"]; + } +} + +void LLIMSpeakerMgr::moderateVoiceSession(const LLUUID& session_id, bool disallow_voice) +{ + std::string url = gAgent.getRegionCapability("ChatSessionRequest"); + LLSD data; + data["method"] = "session update"; + data["session-id"] = session_id; + data["params"] = LLSD::emptyMap(); + + data["params"]["update_info"] = LLSD::emptyMap(); + + data["params"]["update_info"]["moderated_mode"] = LLSD::emptyMap(); + data["params"]["update_info"]["moderated_mode"]["voice"] = disallow_voice; + + LLCoros::instance().launch("LLIMSpeakerMgr::moderationActionCoro", + boost::bind(&LLIMSpeakerMgr::moderationActionCoro, this, url, data)); +} + +void LLIMSpeakerMgr::forceVoiceModeratedMode(bool should_be_muted) +{ + for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end(); ++speaker_it) + { + LLUUID speaker_id = speaker_it->first; + LLSpeaker* speakerp = speaker_it->second; + + // participant does not match requested state + if (should_be_muted != (bool)speakerp->mModeratorMutedVoice) + { + moderateVoiceParticipant(speaker_id, !should_be_muted); + } + } +} + +// +// LLActiveSpeakerMgr +// + +LLActiveSpeakerMgr::LLActiveSpeakerMgr() : LLSpeakerMgr(NULL) +{ +} + +void LLActiveSpeakerMgr::updateSpeakerList() +{ + // point to whatever the current voice channel is + mVoiceChannel = LLVoiceChannel::getCurrentVoiceChannel(); + + // always populate from active voice channel + if (LLVoiceChannel::getCurrentVoiceChannel() != mVoiceChannel) //MA: seems this is always false + { + LL_DEBUGS("Speakers") << "Removed all speakers" << LL_ENDL; + fireEvent(new LLSpeakerListChangeEvent(this, LLUUID::null), "clear"); + mSpeakers.clear(); + mSpeakersSorted.clear(); + mVoiceChannel = LLVoiceChannel::getCurrentVoiceChannel(); + mSpeakerDelayRemover->removeAllTimers(); + } + LLSpeakerMgr::updateSpeakerList(); + + // clean up text only speakers + for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end(); ++speaker_it) + { + LLSpeaker* speakerp = speaker_it->second; + if (speakerp->mStatus == LLSpeaker::STATUS_TEXT_ONLY) + { + // automatically flag text only speakers for removal + speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL; + } + } + +} + + + +// +// LLLocalSpeakerMgr +// + +LLLocalSpeakerMgr::LLLocalSpeakerMgr() : LLSpeakerMgr(LLVoiceChannelProximal::getInstance()) +{ +} + +LLLocalSpeakerMgr::~LLLocalSpeakerMgr () +{ +} + +void LLLocalSpeakerMgr::updateSpeakerList() +{ + // pull speakers from voice channel + LLSpeakerMgr::updateSpeakerList(); + + if (gDisconnected)//the world is cleared. + { + return ; + } + + // pick up non-voice speakers in chat range + uuid_vec_t avatar_ids; + std::vector positions; + LLWorld::getInstance()->getAvatars(&avatar_ids, &positions, gAgent.getPositionGlobal(), CHAT_NORMAL_RADIUS); + for(U32 i=0; ifirst; + LLPointer speakerp = speaker_it->second; + if (speakerp.notNull() && speakerp->mStatus == LLSpeaker::STATUS_TEXT_ONLY) + { + LLVOAvatar* avatarp = (LLVOAvatar*)gObjectList.findObject(speaker_id); + if (!avatarp || dist_vec_squared(avatarp->getPositionAgent(), gAgent.getPositionAgent()) > CHAT_NORMAL_RADIUS * CHAT_NORMAL_RADIUS) + { + setSpeakerNotInChannel(speakerp); + } + } + } +} diff --git a/indra/newview/llspeakers.h b/indra/newview/llspeakers.h index 0846ec9876..4cf7b3aba7 100644 --- a/indra/newview/llspeakers.h +++ b/indra/newview/llspeakers.h @@ -1,353 +1,353 @@ -/** - * @file llspeakers.h - * @brief Management interface for muting and controlling volume of residents currently speaking - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLSPEAKERS_H -#define LL_LLSPEAKERS_H - -#include "llevent.h" -#include "lleventtimer.h" -#include "llvoicechannel.h" -#include "lleventcoro.h" -#include "llcoros.h" - -class LLSpeakerMgr; -class LLAvatarName; - -// data for a given participant in a voice channel -class LLSpeaker : public LLRefCount, public LLOldEvents::LLObservable, public LLHandleProvider, public boost::signals2::trackable -{ -public: - typedef enum e_speaker_type - { - SPEAKER_AGENT, - SPEAKER_OBJECT, - SPEAKER_EXTERNAL // Speaker that doesn't map to an avatar or object (i.e. PSTN caller in a group) - } ESpeakerType; - - typedef enum e_speaker_status - { - STATUS_SPEAKING, - STATUS_HAS_SPOKEN, - STATUS_VOICE_ACTIVE, - STATUS_TEXT_ONLY, - STATUS_NOT_IN_CHANNEL, - STATUS_MUTED - } ESpeakerStatus; - - - LLSpeaker(const LLUUID& id, const std::string& name = LLStringUtil::null, const ESpeakerType type = SPEAKER_AGENT); - ~LLSpeaker() {}; - void lookupName(); - - void onNameCache(const LLUUID& id, const LLAvatarName& full_name); - - bool isInVoiceChannel(); - - ESpeakerStatus mStatus; // current activity status in speech group - F32 mLastSpokeTime; // timestamp when this speaker last spoke - F32 mSpeechVolume; // current speech amplitude (timea average rms amplitude?) - std::string mDisplayName; // cache user name for this speaker - bool mHasSpoken; // has this speaker said anything this session? - bool mHasLeftCurrentCall; // has this speaker left the current voice call? - LLColor4 mDotColor; - LLUUID mID; - bool mTyping; - S32 mSortIndex; - ESpeakerType mType; - bool mIsModerator; - bool mModeratorMutedVoice; - bool mModeratorMutedText; -}; - -class LLSpeakerUpdateSpeakerEvent : public LLOldEvents::LLEvent -{ -public: - LLSpeakerUpdateSpeakerEvent(LLSpeaker* source); - /*virtual*/ LLSD getValue(); -private: - const LLUUID& mSpeakerID; -}; - -class LLSpeakerUpdateModeratorEvent : public LLOldEvents::LLEvent -{ -public: - LLSpeakerUpdateModeratorEvent(LLSpeaker* source); - /*virtual*/ LLSD getValue(); -private: - const LLUUID& mSpeakerID; - bool mIsModerator; -}; - -class LLSpeakerTextModerationEvent : public LLOldEvents::LLEvent -{ -public: - LLSpeakerTextModerationEvent(LLSpeaker* source); - /*virtual*/ LLSD getValue(); -}; - -class LLSpeakerVoiceModerationEvent : public LLOldEvents::LLEvent -{ -public: - LLSpeakerVoiceModerationEvent(LLSpeaker* source); - /*virtual*/ LLSD getValue(); -}; - -class LLSpeakerListChangeEvent : public LLOldEvents::LLEvent -{ -public: - LLSpeakerListChangeEvent(LLSpeakerMgr* source, const LLUUID& speaker_id); - /*virtual*/ LLSD getValue(); - -private: - const LLUUID& mSpeakerID; -}; - -/** - * class LLSpeakerActionTimer - * - * Implements a timer that calls stored callback action for stored speaker after passed period. - * - * Action is called until callback returns "true". - * In this case the timer will be removed via LLEventTimer::updateClass(). - * Otherwise it should be deleted manually in place where it is used. - * If action callback is not set timer will tick only once and deleted. - */ -class LLSpeakerActionTimer : public LLEventTimer -{ -public: - typedef boost::function action_callback_t; - typedef std::map action_timers_map_t; - typedef action_timers_map_t::value_type action_value_t; - typedef action_timers_map_t::const_iterator action_timer_const_iter_t; - typedef action_timers_map_t::iterator action_timer_iter_t; - - /** - * Constructor. - * - * @param action_cb - callback which will be called each time after passed action period. - * @param action_period - time in seconds timer should tick. - * @param speaker_id - LLUUID of speaker which will be passed into action callback. - */ - LLSpeakerActionTimer(action_callback_t action_cb, F32 action_period, const LLUUID& speaker_id); - virtual ~LLSpeakerActionTimer() {}; - - /** - * Implements timer "tick". - * - * If action callback is not specified returns true. Instance will be deleted by LLEventTimer::updateClass(). - */ - virtual bool tick(); - - /** - * Clears the callback. - * - * Use this instead of deleteing this object. - * The next call to tick() will return true and that will destroy this object. - */ - void unset(); -private: - action_callback_t mActionCallback; - LLUUID mSpeakerId; -}; - -/** - * Represents a functionality to store actions for speakers with delay. - * Is based on LLSpeakerActionTimer. - */ -class LLSpeakersDelayActionsStorage -{ -public: - LLSpeakersDelayActionsStorage(LLSpeakerActionTimer::action_callback_t action_cb, F32 action_delay); - ~LLSpeakersDelayActionsStorage(); - - /** - * Sets new LLSpeakerActionTimer with passed speaker UUID. - */ - void setActionTimer(const LLUUID& speaker_id); - - /** - * Removes stored LLSpeakerActionTimer for passed speaker UUID from internal map and optionally deletes it. - * - * @see onTimerActionCallback() - */ - void unsetActionTimer(const LLUUID& speaker_id); - - void removeAllTimers(); - - bool isTimerStarted(const LLUUID& speaker_id); -private: - /** - * Callback of the each instance of LLSpeakerActionTimer. - * - * Unsets an appropriate timer instance and calls action callback for specified speacker_id. - * - * @see unsetActionTimer() - */ - bool onTimerActionCallback(const LLUUID& speaker_id); - - LLSpeakerActionTimer::action_timers_map_t mActionTimersMap; - LLSpeakerActionTimer::action_callback_t mActionCallback; - - /** - * Delay to call action callback for speakers after timer was set. - */ - F32 mActionDelay; - -}; - - -class LLSpeakerMgr : public LLOldEvents::LLObservable -{ - LOG_CLASS(LLSpeakerMgr); - -public: - LLSpeakerMgr(LLVoiceChannel* channelp); - virtual ~LLSpeakerMgr(); - - LLPointer findSpeaker(const LLUUID& avatar_id); - void update(bool resort_ok); - void setSpeakerTyping(const LLUUID& speaker_id, bool typing); - void speakerChatted(const LLUUID& speaker_id); - LLPointer setSpeaker(const LLUUID& id, - const std::string& name = LLStringUtil::null, - LLSpeaker::ESpeakerStatus status = LLSpeaker::STATUS_TEXT_ONLY, - LLSpeaker::ESpeakerType = LLSpeaker::SPEAKER_AGENT); - - bool isVoiceActive(); - - typedef std::vector > speaker_list_t; - void getSpeakerList(speaker_list_t* speaker_list, bool include_text); - LLVoiceChannel* getVoiceChannel() { return mVoiceChannel; } - const LLUUID getSessionID(); - bool isSpeakerToBeRemoved(const LLUUID& speaker_id); - - /** - * Initializes mVoiceModerated depend on LLSpeaker::mModeratorMutedVoice of agent's participant. - * - * Is used only to implement workaround to initialize mVoiceModerated on first join to group chat. See EXT-6937 - */ - void initVoiceModerateMode(); - -protected: - virtual void updateSpeakerList(); - void setSpeakerNotInChannel(LLPointer speackerp); - bool removeSpeaker(const LLUUID& speaker_id); - - typedef std::map > speaker_map_t; - speaker_map_t mSpeakers; - bool mSpeakerListUpdated; - LLTimer mGetListTime; - - speaker_list_t mSpeakersSorted; - LLFrameTimer mSpeechTimer; - LLVoiceChannel* mVoiceChannel; - - /** - * time out speakers when they are not part of current session - */ - LLSpeakersDelayActionsStorage* mSpeakerDelayRemover; - - // *TODO: should be moved back into LLIMSpeakerMgr when a way to request the current voice channel - // moderation mode is implemented: See EXT-6937 - bool mVoiceModerated; - - // *TODO: To be removed when a way to request the current voice channel - // moderation mode is implemented: See EXT-6937 - bool mModerateModeHandledFirstTime; -}; - -class LLIMSpeakerMgr : public LLSpeakerMgr -{ - LOG_CLASS(LLIMSpeakerMgr); - -public: - LLIMSpeakerMgr(LLVoiceChannel* channel); - - void updateSpeakers(const LLSD& update); - void setSpeakers(const LLSD& speakers); - - void toggleAllowTextChat(const LLUUID& speaker_id); - - /** - * Mutes/Unmutes avatar for current group voice chat. - * - * It only marks avatar as muted for session and does not use local Agent's Block list. - * It does not mute Agent itself. - * - * @param[in] avatar_id UUID of avatar to be processed - * @param[in] unmute if false - specified avatar will be muted, otherwise - unmuted. - * - * @see moderateVoiceAllParticipants() - */ - void moderateVoiceParticipant(const LLUUID& avatar_id, bool unmute); - - /** - * Mutes/Unmutes all avatars for current group voice chat. - * - * It only marks avatars as muted for session and does not use local Agent's Block list. - * It calls forceVoiceModeratedMode() in case of session is already in requested state. - * - * @param[in] unmute_everyone if false - avatars will be muted, otherwise - unmuted. - * - * @see moderateVoiceParticipant() - */ - void moderateVoiceAllParticipants(bool unmute_everyone); - - void processSessionUpdate(const LLSD& session_update); - -protected: - virtual void updateSpeakerList(); - - void moderateVoiceSession(const LLUUID& session_id, bool disallow_voice); - - /** - * Process all participants to mute/unmute them according to passed voice session state. - */ - void forceVoiceModeratedMode(bool should_be_muted); - - void moderationActionCoro(std::string url, LLSD action); - -}; - -class LLActiveSpeakerMgr : public LLSpeakerMgr, public LLSingleton -{ - LLSINGLETON(LLActiveSpeakerMgr); - LOG_CLASS(LLActiveSpeakerMgr); - -protected: - virtual void updateSpeakerList() override; -}; - -class LLLocalSpeakerMgr : public LLSpeakerMgr, public LLSingleton -{ - LLSINGLETON(LLLocalSpeakerMgr); - ~LLLocalSpeakerMgr (); - LOG_CLASS(LLLocalSpeakerMgr); -protected: - virtual void updateSpeakerList() override; -}; - -#endif // LL_LLSPEAKERS_H +/** + * @file llspeakers.h + * @brief Management interface for muting and controlling volume of residents currently speaking + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSPEAKERS_H +#define LL_LLSPEAKERS_H + +#include "llevent.h" +#include "lleventtimer.h" +#include "llvoicechannel.h" +#include "lleventcoro.h" +#include "llcoros.h" + +class LLSpeakerMgr; +class LLAvatarName; + +// data for a given participant in a voice channel +class LLSpeaker : public LLRefCount, public LLOldEvents::LLObservable, public LLHandleProvider, public boost::signals2::trackable +{ +public: + typedef enum e_speaker_type + { + SPEAKER_AGENT, + SPEAKER_OBJECT, + SPEAKER_EXTERNAL // Speaker that doesn't map to an avatar or object (i.e. PSTN caller in a group) + } ESpeakerType; + + typedef enum e_speaker_status + { + STATUS_SPEAKING, + STATUS_HAS_SPOKEN, + STATUS_VOICE_ACTIVE, + STATUS_TEXT_ONLY, + STATUS_NOT_IN_CHANNEL, + STATUS_MUTED + } ESpeakerStatus; + + + LLSpeaker(const LLUUID& id, const std::string& name = LLStringUtil::null, const ESpeakerType type = SPEAKER_AGENT); + ~LLSpeaker() {}; + void lookupName(); + + void onNameCache(const LLUUID& id, const LLAvatarName& full_name); + + bool isInVoiceChannel(); + + ESpeakerStatus mStatus; // current activity status in speech group + F32 mLastSpokeTime; // timestamp when this speaker last spoke + F32 mSpeechVolume; // current speech amplitude (timea average rms amplitude?) + std::string mDisplayName; // cache user name for this speaker + bool mHasSpoken; // has this speaker said anything this session? + bool mHasLeftCurrentCall; // has this speaker left the current voice call? + LLColor4 mDotColor; + LLUUID mID; + bool mTyping; + S32 mSortIndex; + ESpeakerType mType; + bool mIsModerator; + bool mModeratorMutedVoice; + bool mModeratorMutedText; +}; + +class LLSpeakerUpdateSpeakerEvent : public LLOldEvents::LLEvent +{ +public: + LLSpeakerUpdateSpeakerEvent(LLSpeaker* source); + /*virtual*/ LLSD getValue(); +private: + const LLUUID& mSpeakerID; +}; + +class LLSpeakerUpdateModeratorEvent : public LLOldEvents::LLEvent +{ +public: + LLSpeakerUpdateModeratorEvent(LLSpeaker* source); + /*virtual*/ LLSD getValue(); +private: + const LLUUID& mSpeakerID; + bool mIsModerator; +}; + +class LLSpeakerTextModerationEvent : public LLOldEvents::LLEvent +{ +public: + LLSpeakerTextModerationEvent(LLSpeaker* source); + /*virtual*/ LLSD getValue(); +}; + +class LLSpeakerVoiceModerationEvent : public LLOldEvents::LLEvent +{ +public: + LLSpeakerVoiceModerationEvent(LLSpeaker* source); + /*virtual*/ LLSD getValue(); +}; + +class LLSpeakerListChangeEvent : public LLOldEvents::LLEvent +{ +public: + LLSpeakerListChangeEvent(LLSpeakerMgr* source, const LLUUID& speaker_id); + /*virtual*/ LLSD getValue(); + +private: + const LLUUID& mSpeakerID; +}; + +/** + * class LLSpeakerActionTimer + * + * Implements a timer that calls stored callback action for stored speaker after passed period. + * + * Action is called until callback returns "true". + * In this case the timer will be removed via LLEventTimer::updateClass(). + * Otherwise it should be deleted manually in place where it is used. + * If action callback is not set timer will tick only once and deleted. + */ +class LLSpeakerActionTimer : public LLEventTimer +{ +public: + typedef boost::function action_callback_t; + typedef std::map action_timers_map_t; + typedef action_timers_map_t::value_type action_value_t; + typedef action_timers_map_t::const_iterator action_timer_const_iter_t; + typedef action_timers_map_t::iterator action_timer_iter_t; + + /** + * Constructor. + * + * @param action_cb - callback which will be called each time after passed action period. + * @param action_period - time in seconds timer should tick. + * @param speaker_id - LLUUID of speaker which will be passed into action callback. + */ + LLSpeakerActionTimer(action_callback_t action_cb, F32 action_period, const LLUUID& speaker_id); + virtual ~LLSpeakerActionTimer() {}; + + /** + * Implements timer "tick". + * + * If action callback is not specified returns true. Instance will be deleted by LLEventTimer::updateClass(). + */ + virtual bool tick(); + + /** + * Clears the callback. + * + * Use this instead of deleteing this object. + * The next call to tick() will return true and that will destroy this object. + */ + void unset(); +private: + action_callback_t mActionCallback; + LLUUID mSpeakerId; +}; + +/** + * Represents a functionality to store actions for speakers with delay. + * Is based on LLSpeakerActionTimer. + */ +class LLSpeakersDelayActionsStorage +{ +public: + LLSpeakersDelayActionsStorage(LLSpeakerActionTimer::action_callback_t action_cb, F32 action_delay); + ~LLSpeakersDelayActionsStorage(); + + /** + * Sets new LLSpeakerActionTimer with passed speaker UUID. + */ + void setActionTimer(const LLUUID& speaker_id); + + /** + * Removes stored LLSpeakerActionTimer for passed speaker UUID from internal map and optionally deletes it. + * + * @see onTimerActionCallback() + */ + void unsetActionTimer(const LLUUID& speaker_id); + + void removeAllTimers(); + + bool isTimerStarted(const LLUUID& speaker_id); +private: + /** + * Callback of the each instance of LLSpeakerActionTimer. + * + * Unsets an appropriate timer instance and calls action callback for specified speacker_id. + * + * @see unsetActionTimer() + */ + bool onTimerActionCallback(const LLUUID& speaker_id); + + LLSpeakerActionTimer::action_timers_map_t mActionTimersMap; + LLSpeakerActionTimer::action_callback_t mActionCallback; + + /** + * Delay to call action callback for speakers after timer was set. + */ + F32 mActionDelay; + +}; + + +class LLSpeakerMgr : public LLOldEvents::LLObservable +{ + LOG_CLASS(LLSpeakerMgr); + +public: + LLSpeakerMgr(LLVoiceChannel* channelp); + virtual ~LLSpeakerMgr(); + + LLPointer findSpeaker(const LLUUID& avatar_id); + void update(bool resort_ok); + void setSpeakerTyping(const LLUUID& speaker_id, bool typing); + void speakerChatted(const LLUUID& speaker_id); + LLPointer setSpeaker(const LLUUID& id, + const std::string& name = LLStringUtil::null, + LLSpeaker::ESpeakerStatus status = LLSpeaker::STATUS_TEXT_ONLY, + LLSpeaker::ESpeakerType = LLSpeaker::SPEAKER_AGENT); + + bool isVoiceActive(); + + typedef std::vector > speaker_list_t; + void getSpeakerList(speaker_list_t* speaker_list, bool include_text); + LLVoiceChannel* getVoiceChannel() { return mVoiceChannel; } + const LLUUID getSessionID(); + bool isSpeakerToBeRemoved(const LLUUID& speaker_id); + + /** + * Initializes mVoiceModerated depend on LLSpeaker::mModeratorMutedVoice of agent's participant. + * + * Is used only to implement workaround to initialize mVoiceModerated on first join to group chat. See EXT-6937 + */ + void initVoiceModerateMode(); + +protected: + virtual void updateSpeakerList(); + void setSpeakerNotInChannel(LLPointer speackerp); + bool removeSpeaker(const LLUUID& speaker_id); + + typedef std::map > speaker_map_t; + speaker_map_t mSpeakers; + bool mSpeakerListUpdated; + LLTimer mGetListTime; + + speaker_list_t mSpeakersSorted; + LLFrameTimer mSpeechTimer; + LLVoiceChannel* mVoiceChannel; + + /** + * time out speakers when they are not part of current session + */ + LLSpeakersDelayActionsStorage* mSpeakerDelayRemover; + + // *TODO: should be moved back into LLIMSpeakerMgr when a way to request the current voice channel + // moderation mode is implemented: See EXT-6937 + bool mVoiceModerated; + + // *TODO: To be removed when a way to request the current voice channel + // moderation mode is implemented: See EXT-6937 + bool mModerateModeHandledFirstTime; +}; + +class LLIMSpeakerMgr : public LLSpeakerMgr +{ + LOG_CLASS(LLIMSpeakerMgr); + +public: + LLIMSpeakerMgr(LLVoiceChannel* channel); + + void updateSpeakers(const LLSD& update); + void setSpeakers(const LLSD& speakers); + + void toggleAllowTextChat(const LLUUID& speaker_id); + + /** + * Mutes/Unmutes avatar for current group voice chat. + * + * It only marks avatar as muted for session and does not use local Agent's Block list. + * It does not mute Agent itself. + * + * @param[in] avatar_id UUID of avatar to be processed + * @param[in] unmute if false - specified avatar will be muted, otherwise - unmuted. + * + * @see moderateVoiceAllParticipants() + */ + void moderateVoiceParticipant(const LLUUID& avatar_id, bool unmute); + + /** + * Mutes/Unmutes all avatars for current group voice chat. + * + * It only marks avatars as muted for session and does not use local Agent's Block list. + * It calls forceVoiceModeratedMode() in case of session is already in requested state. + * + * @param[in] unmute_everyone if false - avatars will be muted, otherwise - unmuted. + * + * @see moderateVoiceParticipant() + */ + void moderateVoiceAllParticipants(bool unmute_everyone); + + void processSessionUpdate(const LLSD& session_update); + +protected: + virtual void updateSpeakerList(); + + void moderateVoiceSession(const LLUUID& session_id, bool disallow_voice); + + /** + * Process all participants to mute/unmute them according to passed voice session state. + */ + void forceVoiceModeratedMode(bool should_be_muted); + + void moderationActionCoro(std::string url, LLSD action); + +}; + +class LLActiveSpeakerMgr : public LLSpeakerMgr, public LLSingleton +{ + LLSINGLETON(LLActiveSpeakerMgr); + LOG_CLASS(LLActiveSpeakerMgr); + +protected: + virtual void updateSpeakerList() override; +}; + +class LLLocalSpeakerMgr : public LLSpeakerMgr, public LLSingleton +{ + LLSINGLETON(LLLocalSpeakerMgr); + ~LLLocalSpeakerMgr (); + LOG_CLASS(LLLocalSpeakerMgr); +protected: + virtual void updateSpeakerList() override; +}; + +#endif // LL_LLSPEAKERS_H diff --git a/indra/newview/llspeakingindicatormanager.cpp b/indra/newview/llspeakingindicatormanager.cpp index 41d471a55a..ca99d351bb 100644 --- a/indra/newview/llspeakingindicatormanager.cpp +++ b/indra/newview/llspeakingindicatormanager.cpp @@ -1,322 +1,322 @@ -/** - * @file llspeakingindicatormanager.cpp - * @author Mike Antipov - * @brief Implementation of SpeackerIndicatorManager class to process registered LLSpeackerIndicator - * depend on avatars are in the same voice channel. - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llspeakingindicatormanager.h" - - -#include "llvoicechannel.h" -#include "llvoiceclient.h" - -/** - * This class intended to control visibility of avatar speaking indicators depend on whether avatars - * are in the same voice channel. - * - * Speaking indicator should be visible for avatars in the same voice channel. See EXT-3976. - * - * It stores passed instances of LLOutputMonitorCtrl in a multimap by avatar LLUUID. - * It observes changing of voice channel and changing of participant list in voice channel. - * When voice channel or voice participant list is changed it updates visibility of an appropriate - * speaking indicator. - * - * Several indicators can be registered for the same avatar. - */ -class SpeakingIndicatorManager : public LLSingleton, LLVoiceClientParticipantObserver -{ - LLSINGLETON(SpeakingIndicatorManager); - ~SpeakingIndicatorManager(); - LOG_CLASS(SpeakingIndicatorManager); - -protected: - void cleanupSingleton() override; - -public: - - /** - * Stores passed speaking indicator to control its visibility. - * - * Registered indicator is set visible if an appropriate avatar is in the same voice channel with Agent. - * It ignores instances of Agent's indicator. - * - * @param speaker_id LLUUID of an avatar whose speaking indicator is registered. - * @param speaking_indicator instance of the speaking indicator to be registered. - * @param session_id session UUID for which indicator should be shown only. - * If this parameter is set registered indicator will be shown only in voice channel - * which has the same session id (EXT-5562). - */ - void registerSpeakingIndicator(const LLUUID& speaker_id, LLSpeakingIndicator* const speaking_indicator, - const LLUUID& session_id = LLUUID::null); - - /** - * Removes passed speaking indicator from observing. - * - * @param speaker_id LLUUID of an avatar whose speaking indicator should be unregistered. - * @param speaking_indicator instance of the speaking indicator to be unregistered. - */ - void unregisterSpeakingIndicator(const LLUUID& speaker_id, const LLSpeakingIndicator* const speaking_indicator); - - /** - * Callback of changing voice participant list (from LLVoiceClientParticipantObserver). - * - * Switches off indicators had been switched on and switches on indicators of current participants list. - * There is only a few indicators in lists should be switched off/on. - * So, method does not calculate difference between these list it only switches off already - * switched on indicators and switches on indicators of voice channel participants - */ - void onParticipantsChanged() override; - -private: - typedef std::set speaker_ids_t; - typedef std::multimap speaking_indicators_mmap_t; - typedef speaking_indicators_mmap_t::value_type speaking_indicator_value_t; - typedef speaking_indicators_mmap_t::const_iterator indicator_const_iterator; - typedef std::pair indicator_range_t; - - /** - * Callback to determine when voice channel is changed. - * - * It switches all registered speaking indicators off. - * To reduce overheads only switched on indicators are processed. - */ - void sOnCurrentChannelChanged(const LLUUID& session_id); - - /** - * Changes state of indicators specified by LLUUIDs - * - * @param speakers_uuids - avatars' LLUUIDs whose speaking indicators should be switched - * @param switch_on - if true specified indicator will be switched on, off otherwise. - */ - void switchSpeakerIndicators(const speaker_ids_t& speakers_uuids, bool switch_on); - - /** - * Ensures that passed instance of Speaking Indicator does not exist among registered ones. - * If yes, it will be removed. - */ - void ensureInstanceDoesNotExist(LLSpeakingIndicator* const speaking_indicator); - - - /** - * Multimap with all registered speaking indicators - */ - speaking_indicators_mmap_t mSpeakingIndicators; - - /** - * LUUIDs of avatar for which we have speaking indicators switched on. - * - * Is used to switch off all previously ON indicators when voice participant list is changed. - * - * @see onChange() - */ - speaker_ids_t mSwitchedIndicatorsOn; -}; - -////////////////////////////////////////////////////////////////////////// -// PUBLIC SECTION -////////////////////////////////////////////////////////////////////////// -void SpeakingIndicatorManager::registerSpeakingIndicator(const LLUUID& speaker_id, LLSpeakingIndicator* const speaking_indicator, - const LLUUID& session_id) -{ - // do not exclude agent's indicators. They should be processed in the same way as others. See EXT-3889. - - LL_DEBUGS("SpeakingIndicator") << "Registering indicator: " << speaker_id << "|"<< speaking_indicator << ", session: " << session_id << LL_ENDL; - - - ensureInstanceDoesNotExist(speaking_indicator); - - speaking_indicator->setTargetSessionID(session_id); - - speaking_indicator_value_t value_type(speaker_id, speaking_indicator); - mSpeakingIndicators.insert(value_type); - - speaker_ids_t speakers_uuids; - bool is_in_same_voice = LLVoiceClient::getInstance()->isParticipant(speaker_id); - - speakers_uuids.insert(speaker_id); - switchSpeakerIndicators(speakers_uuids, is_in_same_voice); -} - -void SpeakingIndicatorManager::unregisterSpeakingIndicator(const LLUUID& speaker_id, const LLSpeakingIndicator* const speaking_indicator) -{ - LL_DEBUGS("SpeakingIndicator") << "Unregistering indicator: " << speaker_id << "|"<< speaking_indicator << LL_ENDL; - speaking_indicators_mmap_t::iterator it; - it = mSpeakingIndicators.find(speaker_id); - for (;it != mSpeakingIndicators.end(); ++it) - { - if (it->second == speaking_indicator) - { - LL_DEBUGS("SpeakingIndicator") << "Unregistered." << LL_ENDL; - mSpeakingIndicators.erase(it); - break; - } - } -} - -////////////////////////////////////////////////////////////////////////// -// PRIVATE SECTION -////////////////////////////////////////////////////////////////////////// -SpeakingIndicatorManager::SpeakingIndicatorManager() -{ - LLVoiceChannel::setCurrentVoiceChannelChangedCallback(boost::bind(&SpeakingIndicatorManager::sOnCurrentChannelChanged, this, _1)); - LLVoiceClient::getInstance()->addObserver(this); -} - -SpeakingIndicatorManager::~SpeakingIndicatorManager() -{ -} - -void SpeakingIndicatorManager::cleanupSingleton() -{ - // Don't use LLVoiceClient::getInstance() here without a check, - // singleton MAY have already been destroyed. - if (LLVoiceClient::instanceExists()) - { - LLVoiceClient::getInstance()->removeObserver(this); - } -} - -void SpeakingIndicatorManager::sOnCurrentChannelChanged(const LLUUID& /*session_id*/) -{ - switchSpeakerIndicators(mSwitchedIndicatorsOn, false); - mSwitchedIndicatorsOn.clear(); -} - -void SpeakingIndicatorManager::onParticipantsChanged() -{ - LL_DEBUGS("SpeakingIndicator") << "Voice participant list was changed, updating indicators" << LL_ENDL; - - speaker_ids_t speakers_uuids; - LLVoiceClient::getInstance()->getParticipantList(speakers_uuids); - - LL_DEBUGS("SpeakingIndicator") << "Switching all OFF, count: " << mSwitchedIndicatorsOn.size() << LL_ENDL; - // switch all indicators off - switchSpeakerIndicators(mSwitchedIndicatorsOn, false); - mSwitchedIndicatorsOn.clear(); - - LL_DEBUGS("SpeakingIndicator") << "Switching all ON, count: " << speakers_uuids.size() << LL_ENDL; - // then switch current voice participants indicators on - switchSpeakerIndicators(speakers_uuids, true); -} - -void SpeakingIndicatorManager::switchSpeakerIndicators(const speaker_ids_t& speakers_uuids, bool switch_on) -{ - LLVoiceChannel* voice_channel = LLVoiceChannel::getCurrentVoiceChannel(); - LLUUID session_id; - if (voice_channel) - { - session_id = voice_channel->getSessionID(); - } - - speaker_ids_t::const_iterator it_uuid = speakers_uuids.begin(); - for (; it_uuid != speakers_uuids.end(); ++it_uuid) - { - LL_DEBUGS("SpeakingIndicator") << "Looking for indicator: " << *it_uuid << LL_ENDL; - indicator_range_t it_range = mSpeakingIndicators.equal_range(*it_uuid); - indicator_const_iterator it_indicator = it_range.first; - bool was_found = false; - bool was_switched_on = false; - for (; it_indicator != it_range.second; ++it_indicator) - { - was_found = true; - LLSpeakingIndicator* indicator = (*it_indicator).second; - was_switched_on = was_switched_on || switch_on; - - indicator->switchIndicator(switch_on); - } - - if (was_found) - { - LL_DEBUGS("SpeakingIndicator") << mSpeakingIndicators.count(*it_uuid) << " indicators were found" << LL_ENDL; - - if (switch_on && !was_switched_on) - { - LL_DEBUGS("SpeakingIndicator") << "but none of them were switched on" << LL_ENDL; - } - - if (was_switched_on) - { - // store switched on indicator to be able switch it off - mSwitchedIndicatorsOn.insert(*it_uuid); - } - } - } -} - -void SpeakingIndicatorManager::ensureInstanceDoesNotExist(LLSpeakingIndicator* const speaking_indicator) -{ - LL_DEBUGS("SpeakingIndicator") << "Searching for an registered indicator instance: " << speaking_indicator << LL_ENDL; - speaking_indicators_mmap_t::iterator it = mSpeakingIndicators.begin(); - for (;it != mSpeakingIndicators.end(); ++it) - { - if (it->second == speaking_indicator) - { - LL_DEBUGS("SpeakingIndicator") << "Found" << LL_ENDL; - break; - } - } - - // It is possible with LLOutputMonitorCtrl the same instance of indicator is registered several - // times with different UUIDs. This leads to crash after instance is destroyed because the - // only one (specified by UUID in unregisterSpeakingIndicator()) is removed from the map. - // So, using stored deleted pointer leads to crash. See EXT-4782. - if (it != mSpeakingIndicators.end()) - { - LL_WARNS() << "The same instance of indicator has already been registered, removing it: " << it->first << "|"<< speaking_indicator << LL_ENDL; - llassert(it == mSpeakingIndicators.end()); - mSpeakingIndicators.erase(it); - } -} - - -/************************************************************************/ -/* LLSpeakingIndicatorManager namespace implementation */ -/************************************************************************/ - -void LLSpeakingIndicatorManager::registerSpeakingIndicator(const LLUUID& speaker_id, LLSpeakingIndicator* const speaking_indicator, - const LLUUID& session_id) -{ - SpeakingIndicatorManager::instance().registerSpeakingIndicator(speaker_id, speaking_indicator, session_id); -} - -void LLSpeakingIndicatorManager::unregisterSpeakingIndicator(const LLUUID& speaker_id, const LLSpeakingIndicator* const speaking_indicator) -{ - if(SpeakingIndicatorManager::instanceExists()) - { - SpeakingIndicatorManager::instance().unregisterSpeakingIndicator(speaker_id, speaking_indicator); - } -} - -void LLSpeakingIndicatorManager::updateSpeakingIndicators() -{ - if(SpeakingIndicatorManager::instanceExists()) - { - SpeakingIndicatorManager::instance().onParticipantsChanged(); - } -} - -// EOF - +/** + * @file llspeakingindicatormanager.cpp + * @author Mike Antipov + * @brief Implementation of SpeackerIndicatorManager class to process registered LLSpeackerIndicator + * depend on avatars are in the same voice channel. + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llspeakingindicatormanager.h" + + +#include "llvoicechannel.h" +#include "llvoiceclient.h" + +/** + * This class intended to control visibility of avatar speaking indicators depend on whether avatars + * are in the same voice channel. + * + * Speaking indicator should be visible for avatars in the same voice channel. See EXT-3976. + * + * It stores passed instances of LLOutputMonitorCtrl in a multimap by avatar LLUUID. + * It observes changing of voice channel and changing of participant list in voice channel. + * When voice channel or voice participant list is changed it updates visibility of an appropriate + * speaking indicator. + * + * Several indicators can be registered for the same avatar. + */ +class SpeakingIndicatorManager : public LLSingleton, LLVoiceClientParticipantObserver +{ + LLSINGLETON(SpeakingIndicatorManager); + ~SpeakingIndicatorManager(); + LOG_CLASS(SpeakingIndicatorManager); + +protected: + void cleanupSingleton() override; + +public: + + /** + * Stores passed speaking indicator to control its visibility. + * + * Registered indicator is set visible if an appropriate avatar is in the same voice channel with Agent. + * It ignores instances of Agent's indicator. + * + * @param speaker_id LLUUID of an avatar whose speaking indicator is registered. + * @param speaking_indicator instance of the speaking indicator to be registered. + * @param session_id session UUID for which indicator should be shown only. + * If this parameter is set registered indicator will be shown only in voice channel + * which has the same session id (EXT-5562). + */ + void registerSpeakingIndicator(const LLUUID& speaker_id, LLSpeakingIndicator* const speaking_indicator, + const LLUUID& session_id = LLUUID::null); + + /** + * Removes passed speaking indicator from observing. + * + * @param speaker_id LLUUID of an avatar whose speaking indicator should be unregistered. + * @param speaking_indicator instance of the speaking indicator to be unregistered. + */ + void unregisterSpeakingIndicator(const LLUUID& speaker_id, const LLSpeakingIndicator* const speaking_indicator); + + /** + * Callback of changing voice participant list (from LLVoiceClientParticipantObserver). + * + * Switches off indicators had been switched on and switches on indicators of current participants list. + * There is only a few indicators in lists should be switched off/on. + * So, method does not calculate difference between these list it only switches off already + * switched on indicators and switches on indicators of voice channel participants + */ + void onParticipantsChanged() override; + +private: + typedef std::set speaker_ids_t; + typedef std::multimap speaking_indicators_mmap_t; + typedef speaking_indicators_mmap_t::value_type speaking_indicator_value_t; + typedef speaking_indicators_mmap_t::const_iterator indicator_const_iterator; + typedef std::pair indicator_range_t; + + /** + * Callback to determine when voice channel is changed. + * + * It switches all registered speaking indicators off. + * To reduce overheads only switched on indicators are processed. + */ + void sOnCurrentChannelChanged(const LLUUID& session_id); + + /** + * Changes state of indicators specified by LLUUIDs + * + * @param speakers_uuids - avatars' LLUUIDs whose speaking indicators should be switched + * @param switch_on - if true specified indicator will be switched on, off otherwise. + */ + void switchSpeakerIndicators(const speaker_ids_t& speakers_uuids, bool switch_on); + + /** + * Ensures that passed instance of Speaking Indicator does not exist among registered ones. + * If yes, it will be removed. + */ + void ensureInstanceDoesNotExist(LLSpeakingIndicator* const speaking_indicator); + + + /** + * Multimap with all registered speaking indicators + */ + speaking_indicators_mmap_t mSpeakingIndicators; + + /** + * LUUIDs of avatar for which we have speaking indicators switched on. + * + * Is used to switch off all previously ON indicators when voice participant list is changed. + * + * @see onChange() + */ + speaker_ids_t mSwitchedIndicatorsOn; +}; + +////////////////////////////////////////////////////////////////////////// +// PUBLIC SECTION +////////////////////////////////////////////////////////////////////////// +void SpeakingIndicatorManager::registerSpeakingIndicator(const LLUUID& speaker_id, LLSpeakingIndicator* const speaking_indicator, + const LLUUID& session_id) +{ + // do not exclude agent's indicators. They should be processed in the same way as others. See EXT-3889. + + LL_DEBUGS("SpeakingIndicator") << "Registering indicator: " << speaker_id << "|"<< speaking_indicator << ", session: " << session_id << LL_ENDL; + + + ensureInstanceDoesNotExist(speaking_indicator); + + speaking_indicator->setTargetSessionID(session_id); + + speaking_indicator_value_t value_type(speaker_id, speaking_indicator); + mSpeakingIndicators.insert(value_type); + + speaker_ids_t speakers_uuids; + bool is_in_same_voice = LLVoiceClient::getInstance()->isParticipant(speaker_id); + + speakers_uuids.insert(speaker_id); + switchSpeakerIndicators(speakers_uuids, is_in_same_voice); +} + +void SpeakingIndicatorManager::unregisterSpeakingIndicator(const LLUUID& speaker_id, const LLSpeakingIndicator* const speaking_indicator) +{ + LL_DEBUGS("SpeakingIndicator") << "Unregistering indicator: " << speaker_id << "|"<< speaking_indicator << LL_ENDL; + speaking_indicators_mmap_t::iterator it; + it = mSpeakingIndicators.find(speaker_id); + for (;it != mSpeakingIndicators.end(); ++it) + { + if (it->second == speaking_indicator) + { + LL_DEBUGS("SpeakingIndicator") << "Unregistered." << LL_ENDL; + mSpeakingIndicators.erase(it); + break; + } + } +} + +////////////////////////////////////////////////////////////////////////// +// PRIVATE SECTION +////////////////////////////////////////////////////////////////////////// +SpeakingIndicatorManager::SpeakingIndicatorManager() +{ + LLVoiceChannel::setCurrentVoiceChannelChangedCallback(boost::bind(&SpeakingIndicatorManager::sOnCurrentChannelChanged, this, _1)); + LLVoiceClient::getInstance()->addObserver(this); +} + +SpeakingIndicatorManager::~SpeakingIndicatorManager() +{ +} + +void SpeakingIndicatorManager::cleanupSingleton() +{ + // Don't use LLVoiceClient::getInstance() here without a check, + // singleton MAY have already been destroyed. + if (LLVoiceClient::instanceExists()) + { + LLVoiceClient::getInstance()->removeObserver(this); + } +} + +void SpeakingIndicatorManager::sOnCurrentChannelChanged(const LLUUID& /*session_id*/) +{ + switchSpeakerIndicators(mSwitchedIndicatorsOn, false); + mSwitchedIndicatorsOn.clear(); +} + +void SpeakingIndicatorManager::onParticipantsChanged() +{ + LL_DEBUGS("SpeakingIndicator") << "Voice participant list was changed, updating indicators" << LL_ENDL; + + speaker_ids_t speakers_uuids; + LLVoiceClient::getInstance()->getParticipantList(speakers_uuids); + + LL_DEBUGS("SpeakingIndicator") << "Switching all OFF, count: " << mSwitchedIndicatorsOn.size() << LL_ENDL; + // switch all indicators off + switchSpeakerIndicators(mSwitchedIndicatorsOn, false); + mSwitchedIndicatorsOn.clear(); + + LL_DEBUGS("SpeakingIndicator") << "Switching all ON, count: " << speakers_uuids.size() << LL_ENDL; + // then switch current voice participants indicators on + switchSpeakerIndicators(speakers_uuids, true); +} + +void SpeakingIndicatorManager::switchSpeakerIndicators(const speaker_ids_t& speakers_uuids, bool switch_on) +{ + LLVoiceChannel* voice_channel = LLVoiceChannel::getCurrentVoiceChannel(); + LLUUID session_id; + if (voice_channel) + { + session_id = voice_channel->getSessionID(); + } + + speaker_ids_t::const_iterator it_uuid = speakers_uuids.begin(); + for (; it_uuid != speakers_uuids.end(); ++it_uuid) + { + LL_DEBUGS("SpeakingIndicator") << "Looking for indicator: " << *it_uuid << LL_ENDL; + indicator_range_t it_range = mSpeakingIndicators.equal_range(*it_uuid); + indicator_const_iterator it_indicator = it_range.first; + bool was_found = false; + bool was_switched_on = false; + for (; it_indicator != it_range.second; ++it_indicator) + { + was_found = true; + LLSpeakingIndicator* indicator = (*it_indicator).second; + was_switched_on = was_switched_on || switch_on; + + indicator->switchIndicator(switch_on); + } + + if (was_found) + { + LL_DEBUGS("SpeakingIndicator") << mSpeakingIndicators.count(*it_uuid) << " indicators were found" << LL_ENDL; + + if (switch_on && !was_switched_on) + { + LL_DEBUGS("SpeakingIndicator") << "but none of them were switched on" << LL_ENDL; + } + + if (was_switched_on) + { + // store switched on indicator to be able switch it off + mSwitchedIndicatorsOn.insert(*it_uuid); + } + } + } +} + +void SpeakingIndicatorManager::ensureInstanceDoesNotExist(LLSpeakingIndicator* const speaking_indicator) +{ + LL_DEBUGS("SpeakingIndicator") << "Searching for an registered indicator instance: " << speaking_indicator << LL_ENDL; + speaking_indicators_mmap_t::iterator it = mSpeakingIndicators.begin(); + for (;it != mSpeakingIndicators.end(); ++it) + { + if (it->second == speaking_indicator) + { + LL_DEBUGS("SpeakingIndicator") << "Found" << LL_ENDL; + break; + } + } + + // It is possible with LLOutputMonitorCtrl the same instance of indicator is registered several + // times with different UUIDs. This leads to crash after instance is destroyed because the + // only one (specified by UUID in unregisterSpeakingIndicator()) is removed from the map. + // So, using stored deleted pointer leads to crash. See EXT-4782. + if (it != mSpeakingIndicators.end()) + { + LL_WARNS() << "The same instance of indicator has already been registered, removing it: " << it->first << "|"<< speaking_indicator << LL_ENDL; + llassert(it == mSpeakingIndicators.end()); + mSpeakingIndicators.erase(it); + } +} + + +/************************************************************************/ +/* LLSpeakingIndicatorManager namespace implementation */ +/************************************************************************/ + +void LLSpeakingIndicatorManager::registerSpeakingIndicator(const LLUUID& speaker_id, LLSpeakingIndicator* const speaking_indicator, + const LLUUID& session_id) +{ + SpeakingIndicatorManager::instance().registerSpeakingIndicator(speaker_id, speaking_indicator, session_id); +} + +void LLSpeakingIndicatorManager::unregisterSpeakingIndicator(const LLUUID& speaker_id, const LLSpeakingIndicator* const speaking_indicator) +{ + if(SpeakingIndicatorManager::instanceExists()) + { + SpeakingIndicatorManager::instance().unregisterSpeakingIndicator(speaker_id, speaking_indicator); + } +} + +void LLSpeakingIndicatorManager::updateSpeakingIndicators() +{ + if(SpeakingIndicatorManager::instanceExists()) + { + SpeakingIndicatorManager::instance().onParticipantsChanged(); + } +} + +// EOF + diff --git a/indra/newview/llsplitbutton.cpp b/indra/newview/llsplitbutton.cpp index 6fb70fe853..77eedeaf2f 100644 --- a/indra/newview/llsplitbutton.cpp +++ b/indra/newview/llsplitbutton.cpp @@ -1,273 +1,273 @@ -/** - * @file llsplitbutton.cpp - * @brief LLSplitButton base class - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -// A control that consolidates several buttons as options - -#include "llviewerprecompiledheaders.h" - -#include "llsplitbutton.h" - -#include "llinitparam.h" -#include "llpanel.h" -#include "llfocusmgr.h" -#include "llviewerwindow.h" -#include "llrootview.h" - - -S32 BUTTON_PAD = 2; //pad between buttons on an items panel - - -static LLDefaultChildRegistry::Register split_button("split_button"); - -void LLSplitButton::ArrowPositionValues::declareValues() -{ - declare("left", LEFT); - declare("right", RIGHT); -} - -LLSplitButton::ItemParams::ItemParams() -{ -} - -LLSplitButton::Params::Params() -: arrow_position("arrow_position", LEFT), - items("item"), - arrow_button("arrow_button"), - items_panel("items_panel") -{ -} - - -void LLSplitButton::onFocusLost() -{ - hideButtons(); - LLUICtrl::onFocusLost(); -} - -void LLSplitButton::setFocus(bool b) -{ - LLUICtrl::setFocus(b); - - if (b) - { - if (mItemsPanel && mItemsPanel->getVisible()) - { - mItemsPanel->setFocus(true); - } - } -} - -void LLSplitButton::setEnabled(bool enabled) -{ - LLView::setEnabled(enabled); - mArrowBtn->setEnabled(enabled); -} - - -void LLSplitButton::onArrowBtnDown() -{ - if (!mItemsPanel->getVisible()) - { - showButtons(); - - setFocus(true); - - if (mArrowBtn->hasMouseCapture() || mShownItem->hasMouseCapture()) - { - gFocusMgr.setMouseCapture(this); - } - } - else - { - hideButtons(); - } -} - -void LLSplitButton::onHeldDownShownButton() -{ - if (!mItemsPanel->getVisible()) onArrowBtnDown(); -} - -void LLSplitButton::onItemSelected(LLUICtrl* ctrl) -{ - if (!ctrl) return; - - hideButtons(); - - // call the callback if it exists - if(!mSelectionCallback.empty()) - { - mSelectionCallback(this, ctrl->getName()); - } - - gFocusMgr.setKeyboardFocus(NULL); -} - -bool LLSplitButton::handleMouseUp(S32 x, S32 y, MASK mask) -{ - gFocusMgr.setMouseCapture(NULL); - - if (mShownItem->parentPointInView(x, y)) - { - onItemSelected(mShownItem); - return true; - } - - for (std::list::const_iterator it = mHidenItems.begin(); it != mHidenItems.end(); ++it) - { - LLButton* item = *it; - - S32 panel_x = 0; - S32 panel_y = 0; - localPointToOtherView(x, y, &panel_x, &panel_y, mItemsPanel); - - if (item->parentPointInView(panel_x, panel_y)) - { - onItemSelected(item); - return true; - } - } - return true; -} - -void LLSplitButton::showButtons() -{ - mItemsPanel->setOrigin(0, getRect().getHeight()); - - // register ourselves as a "top" control - // effectively putting us into a special draw layer - gViewerWindow->addPopup(this); - - mItemsPanel->setFocus(true); - - //push arrow button down and show the item buttons - mArrowBtn->setToggleState(true); - mItemsPanel->setVisible(true); - - setUseBoundingRect(true); -} - -void LLSplitButton::hideButtons() -{ - mItemsPanel->setVisible(false); - mArrowBtn->setToggleState(false); - - setUseBoundingRect(false); - gViewerWindow->removePopup(this); -} - - -// protected/private - -LLSplitButton::LLSplitButton(const LLSplitButton::Params& p) -: LLUICtrl(p), - mArrowBtn(NULL), - mShownItem(NULL), - mItemsPanel(NULL), - mArrowPosition(p.arrow_position) -{ - LLRect rc(p.rect); - - LLButton::Params arrow_params = p.arrow_button; - S32 arrow_width = p.arrow_button.rect.width; - - //Default arrow rect values for LEFT arrow position - S32 arrow_left = 0; - S32 arrow_right = arrow_width; - S32 btn_left = arrow_width; - S32 btn_right = rc.getWidth(); - - if (mArrowPosition == RIGHT) - { - arrow_left = rc.getWidth()- arrow_width; - arrow_right = rc.getWidth(); - btn_left = 0; - btn_right = arrow_left; - } - - arrow_params.rect(LLRect(arrow_left, rc.getHeight(), arrow_right, 0)); - arrow_params.label(""); - arrow_params.mouse_down_callback.function(boost::bind(&LLSplitButton::onArrowBtnDown, this)); - mArrowBtn = LLUICtrlFactory::create(arrow_params); - addChild(mArrowBtn); - - //a panel for hidden item buttons - LLPanel::Params panel_params = p.items_panel; - mItemsPanel= prepareItemsPanel(panel_params, p.items.numValidElements()); - addChild(mItemsPanel); - - - LLInitParam::ParamIterator::const_iterator it = p.items.begin(); - - //processing shown item button - mShownItem = prepareItemButton(*it); - mShownItem->setHeldDownCallback(boost::bind(&LLSplitButton::onHeldDownShownButton, this)); - mShownItem->setMouseUpCallback(boost::bind(&LLSplitButton::onItemSelected, this, _1)); - mShownItem->setRect(LLRect(btn_left, rc.getHeight(), btn_right, 0)); - addChild(mShownItem); - - //processing hidden item buttons - S32 item_top = mItemsPanel->getRect().getHeight(); - for (++it; it != p.items.end(); ++it) - { - LLButton* hidden_button = prepareItemButton(*it); - hidden_button->setRect(LLRect(btn_left, item_top, btn_right, item_top - rc.getHeight())); - hidden_button->setMouseDownCallback(boost::bind(&LLSplitButton::onItemSelected, this, _1)); - mHidenItems.push_back(hidden_button); - mItemsPanel->addChild(hidden_button); - - //calculate next button's top - item_top -= (rc.getHeight() + BUTTON_PAD); - } - - mTopLostSignalConnection = setTopLostCallback(boost::bind(&LLSplitButton::hideButtons, this)); -} - -LLSplitButton::~LLSplitButton() -{ - // explicitly disconect to avoid hideButtons with - // dead pointers being called on destruction - mTopLostSignalConnection.disconnect(); -} - - -LLButton* LLSplitButton::prepareItemButton(LLButton::Params params) -{ - params.label(""); - params.is_toggle(false); - return LLUICtrlFactory::create(params); -} - -LLPanel* LLSplitButton::prepareItemsPanel(LLPanel::Params params, S32 items_count) -{ - S32 num_hiden_btns = items_count - 1; - S32 panel_height = num_hiden_btns * (getRect().getHeight() + BUTTON_PAD); - params.visible(false); - params.rect.width(getRect().getWidth()); - params.rect.height(panel_height); - return LLUICtrlFactory::create(params); -} - +/** + * @file llsplitbutton.cpp + * @brief LLSplitButton base class + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// A control that consolidates several buttons as options + +#include "llviewerprecompiledheaders.h" + +#include "llsplitbutton.h" + +#include "llinitparam.h" +#include "llpanel.h" +#include "llfocusmgr.h" +#include "llviewerwindow.h" +#include "llrootview.h" + + +S32 BUTTON_PAD = 2; //pad between buttons on an items panel + + +static LLDefaultChildRegistry::Register split_button("split_button"); + +void LLSplitButton::ArrowPositionValues::declareValues() +{ + declare("left", LEFT); + declare("right", RIGHT); +} + +LLSplitButton::ItemParams::ItemParams() +{ +} + +LLSplitButton::Params::Params() +: arrow_position("arrow_position", LEFT), + items("item"), + arrow_button("arrow_button"), + items_panel("items_panel") +{ +} + + +void LLSplitButton::onFocusLost() +{ + hideButtons(); + LLUICtrl::onFocusLost(); +} + +void LLSplitButton::setFocus(bool b) +{ + LLUICtrl::setFocus(b); + + if (b) + { + if (mItemsPanel && mItemsPanel->getVisible()) + { + mItemsPanel->setFocus(true); + } + } +} + +void LLSplitButton::setEnabled(bool enabled) +{ + LLView::setEnabled(enabled); + mArrowBtn->setEnabled(enabled); +} + + +void LLSplitButton::onArrowBtnDown() +{ + if (!mItemsPanel->getVisible()) + { + showButtons(); + + setFocus(true); + + if (mArrowBtn->hasMouseCapture() || mShownItem->hasMouseCapture()) + { + gFocusMgr.setMouseCapture(this); + } + } + else + { + hideButtons(); + } +} + +void LLSplitButton::onHeldDownShownButton() +{ + if (!mItemsPanel->getVisible()) onArrowBtnDown(); +} + +void LLSplitButton::onItemSelected(LLUICtrl* ctrl) +{ + if (!ctrl) return; + + hideButtons(); + + // call the callback if it exists + if(!mSelectionCallback.empty()) + { + mSelectionCallback(this, ctrl->getName()); + } + + gFocusMgr.setKeyboardFocus(NULL); +} + +bool LLSplitButton::handleMouseUp(S32 x, S32 y, MASK mask) +{ + gFocusMgr.setMouseCapture(NULL); + + if (mShownItem->parentPointInView(x, y)) + { + onItemSelected(mShownItem); + return true; + } + + for (std::list::const_iterator it = mHidenItems.begin(); it != mHidenItems.end(); ++it) + { + LLButton* item = *it; + + S32 panel_x = 0; + S32 panel_y = 0; + localPointToOtherView(x, y, &panel_x, &panel_y, mItemsPanel); + + if (item->parentPointInView(panel_x, panel_y)) + { + onItemSelected(item); + return true; + } + } + return true; +} + +void LLSplitButton::showButtons() +{ + mItemsPanel->setOrigin(0, getRect().getHeight()); + + // register ourselves as a "top" control + // effectively putting us into a special draw layer + gViewerWindow->addPopup(this); + + mItemsPanel->setFocus(true); + + //push arrow button down and show the item buttons + mArrowBtn->setToggleState(true); + mItemsPanel->setVisible(true); + + setUseBoundingRect(true); +} + +void LLSplitButton::hideButtons() +{ + mItemsPanel->setVisible(false); + mArrowBtn->setToggleState(false); + + setUseBoundingRect(false); + gViewerWindow->removePopup(this); +} + + +// protected/private + +LLSplitButton::LLSplitButton(const LLSplitButton::Params& p) +: LLUICtrl(p), + mArrowBtn(NULL), + mShownItem(NULL), + mItemsPanel(NULL), + mArrowPosition(p.arrow_position) +{ + LLRect rc(p.rect); + + LLButton::Params arrow_params = p.arrow_button; + S32 arrow_width = p.arrow_button.rect.width; + + //Default arrow rect values for LEFT arrow position + S32 arrow_left = 0; + S32 arrow_right = arrow_width; + S32 btn_left = arrow_width; + S32 btn_right = rc.getWidth(); + + if (mArrowPosition == RIGHT) + { + arrow_left = rc.getWidth()- arrow_width; + arrow_right = rc.getWidth(); + btn_left = 0; + btn_right = arrow_left; + } + + arrow_params.rect(LLRect(arrow_left, rc.getHeight(), arrow_right, 0)); + arrow_params.label(""); + arrow_params.mouse_down_callback.function(boost::bind(&LLSplitButton::onArrowBtnDown, this)); + mArrowBtn = LLUICtrlFactory::create(arrow_params); + addChild(mArrowBtn); + + //a panel for hidden item buttons + LLPanel::Params panel_params = p.items_panel; + mItemsPanel= prepareItemsPanel(panel_params, p.items.numValidElements()); + addChild(mItemsPanel); + + + LLInitParam::ParamIterator::const_iterator it = p.items.begin(); + + //processing shown item button + mShownItem = prepareItemButton(*it); + mShownItem->setHeldDownCallback(boost::bind(&LLSplitButton::onHeldDownShownButton, this)); + mShownItem->setMouseUpCallback(boost::bind(&LLSplitButton::onItemSelected, this, _1)); + mShownItem->setRect(LLRect(btn_left, rc.getHeight(), btn_right, 0)); + addChild(mShownItem); + + //processing hidden item buttons + S32 item_top = mItemsPanel->getRect().getHeight(); + for (++it; it != p.items.end(); ++it) + { + LLButton* hidden_button = prepareItemButton(*it); + hidden_button->setRect(LLRect(btn_left, item_top, btn_right, item_top - rc.getHeight())); + hidden_button->setMouseDownCallback(boost::bind(&LLSplitButton::onItemSelected, this, _1)); + mHidenItems.push_back(hidden_button); + mItemsPanel->addChild(hidden_button); + + //calculate next button's top + item_top -= (rc.getHeight() + BUTTON_PAD); + } + + mTopLostSignalConnection = setTopLostCallback(boost::bind(&LLSplitButton::hideButtons, this)); +} + +LLSplitButton::~LLSplitButton() +{ + // explicitly disconect to avoid hideButtons with + // dead pointers being called on destruction + mTopLostSignalConnection.disconnect(); +} + + +LLButton* LLSplitButton::prepareItemButton(LLButton::Params params) +{ + params.label(""); + params.is_toggle(false); + return LLUICtrlFactory::create(params); +} + +LLPanel* LLSplitButton::prepareItemsPanel(LLPanel::Params params, S32 items_count) +{ + S32 num_hiden_btns = items_count - 1; + S32 panel_height = num_hiden_btns * (getRect().getHeight() + BUTTON_PAD); + params.visible(false); + params.rect.width(getRect().getWidth()); + params.rect.height(panel_height); + return LLUICtrlFactory::create(params); +} + diff --git a/indra/newview/llsplitbutton.h b/indra/newview/llsplitbutton.h index 0a5a8abdf6..b6ee47a9c7 100644 --- a/indra/newview/llsplitbutton.h +++ b/indra/newview/llsplitbutton.h @@ -1,108 +1,108 @@ -/** - * @file llsplitbutton.h - * @brief LLSplitButton base class - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -// A control that displays the name of the chosen item, which when clicked -// shows a scrolling box of choices. - - -#include "llbutton.h" -#include "llpanel.h" -#include "lluictrl.h" - - -#ifndef LL_LLSPLITBUTTON_H -#define LL_LLSPLITBUTTON_H - -class LLSplitButton - : public LLUICtrl -{ -public: - typedef enum e_arrow_position - { - LEFT, - RIGHT - } EArrowPosition; - - struct ArrowPositionValues : public LLInitParam::TypeValuesHelper - { - static void declareValues(); - }; - - struct ItemParams : public LLInitParam::Block - { - ItemParams(); - }; - - struct Params : public LLInitParam::Block - { - Optional arrow_position; - Optional arrow_button; - Optional items_panel; - Multiple items; - - Params(); - }; - - - virtual ~LLSplitButton(); - - //Overridden - virtual void onFocusLost(); - virtual void setFocus(bool b); - virtual void setEnabled(bool enabled); - - //Callbacks - void onArrowBtnDown(); - void onHeldDownShownButton(); - void onItemSelected(LLUICtrl* ctrl); - void setSelectionCallback(commit_callback_t cb) { mSelectionCallback = cb; } - - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - - virtual void showButtons(); - virtual void hideButtons(); - - -protected: - friend class LLUICtrlFactory; - LLSplitButton(const LLSplitButton::Params& p); - - LLButton* prepareItemButton(LLButton::Params params); - LLPanel* prepareItemsPanel(LLPanel::Params params, S32 items_count); - - LLPanel* mItemsPanel; - std::list mHidenItems; - LLButton* mArrowBtn; - LLButton* mShownItem; - EArrowPosition mArrowPosition; - - boost::signals2::connection mTopLostSignalConnection; - - commit_callback_t mSelectionCallback; -}; - - -#endif +/** + * @file llsplitbutton.h + * @brief LLSplitButton base class + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// A control that displays the name of the chosen item, which when clicked +// shows a scrolling box of choices. + + +#include "llbutton.h" +#include "llpanel.h" +#include "lluictrl.h" + + +#ifndef LL_LLSPLITBUTTON_H +#define LL_LLSPLITBUTTON_H + +class LLSplitButton + : public LLUICtrl +{ +public: + typedef enum e_arrow_position + { + LEFT, + RIGHT + } EArrowPosition; + + struct ArrowPositionValues : public LLInitParam::TypeValuesHelper + { + static void declareValues(); + }; + + struct ItemParams : public LLInitParam::Block + { + ItemParams(); + }; + + struct Params : public LLInitParam::Block + { + Optional arrow_position; + Optional arrow_button; + Optional items_panel; + Multiple items; + + Params(); + }; + + + virtual ~LLSplitButton(); + + //Overridden + virtual void onFocusLost(); + virtual void setFocus(bool b); + virtual void setEnabled(bool enabled); + + //Callbacks + void onArrowBtnDown(); + void onHeldDownShownButton(); + void onItemSelected(LLUICtrl* ctrl); + void setSelectionCallback(commit_callback_t cb) { mSelectionCallback = cb; } + + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + + virtual void showButtons(); + virtual void hideButtons(); + + +protected: + friend class LLUICtrlFactory; + LLSplitButton(const LLSplitButton::Params& p); + + LLButton* prepareItemButton(LLButton::Params params); + LLPanel* prepareItemsPanel(LLPanel::Params params, S32 items_count); + + LLPanel* mItemsPanel; + std::list mHidenItems; + LLButton* mArrowBtn; + LLButton* mShownItem; + EArrowPosition mArrowPosition; + + boost::signals2::connection mTopLostSignalConnection; + + commit_callback_t mSelectionCallback; +}; + + +#endif diff --git a/indra/newview/llsprite.cpp b/indra/newview/llsprite.cpp index 9973350f47..e51aeb6080 100644 --- a/indra/newview/llsprite.cpp +++ b/indra/newview/llsprite.cpp @@ -1,302 +1,302 @@ -/** - * @file llsprite.cpp - * @brief LLSprite class implementation - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/* -*- c++ -*- - * Notes: - * PR - Should add a creator that can take a pointer rather than handle for streaming - * object textures. - * PR - Need to add support for lit/non-lit conditions, set normals? - */ - -#include "llviewerprecompiledheaders.h" - -#include - -#include "llsprite.h" - -#include "math.h" - -#include "lldrawable.h" -#include "llface.h" -#include "llviewercamera.h" -#include "llviewertexturelist.h" - -LLVector3 LLSprite::sCameraUp(0.0f,0.0f,1.0f); -LLVector3 LLSprite::sCameraRight(1.0f,0.0f,0.0f); -LLVector3 LLSprite::sCameraPosition(0.f, 0.f, 0.f); -LLVector3 LLSprite::sNormal(0.0f,0.0f,0.0f); - -////////////////////////////////////////////////////////////////////// -// Construction/Destruction -////////////////////////////////////////////////////////////////////// - -// A simple initialization -LLSprite::LLSprite(const LLUUID &image_uuid) : - mImageID(image_uuid), - mImagep(NULL), - mPitch(0.f), - mYaw(0.f), - mPosition(0.0f, 0.0f, 0.0f), - mFollow(true), - mUseCameraUp(true), - mColor(0.5f, 0.5f, 0.5f, 1.0f), - mTexMode(GL_REPLACE) -{ - setSize(1.0f, 1.0f); -} - -////////////////////////////////////////////////////////////////////// -LLSprite::~LLSprite() -{ -} - -void LLSprite::updateFace(LLFace &face) -{ - LLViewerCamera &camera = *LLViewerCamera::getInstance(); - - // First, figure out how many vertices/indices we need. - U32 num_vertices, num_indices; - - // Get the total number of vertices and indices - if (mFollow) - { - num_vertices = 4; - num_indices = 6; - } - else - { - num_vertices = 4; - num_indices = 12; - } - - face.setSize(num_vertices, num_indices); - - if (mFollow) - { - sCameraUp = camera.getUpAxis(); - sCameraRight = -camera.getLeftAxis(); - sCameraPosition = camera.getOrigin(); - sNormal = -camera.getAtAxis(); - if (mUseCameraUp) - { - // these need to live here because the height/width may change between render calls - mScaledUp = sCameraUp; - mScaledRight = sCameraRight; - - mScaledUp *= mHeightDiv2; - mScaledRight *= mWidthDiv2; - - mA = mPosition + mScaledRight + mScaledUp; - mB = mPosition - mScaledRight + mScaledUp; - mC = mPosition - mScaledRight - mScaledUp; - mD = mPosition + mScaledRight - mScaledUp; - } - else - { - // The up vector is perpendicular to the camera vector... - LLVector3 camera_vec = mPosition - sCameraPosition; - mScaledRight = camera_vec % LLVector3(0.f, 0.f, 1.f); - mScaledUp = -(camera_vec % mScaledRight); - mScaledUp.normalize(); - mScaledRight.normalize(); - mScaledUp *= mHeightDiv2; - mScaledRight *= mWidthDiv2; - - mA = mPosition + mScaledRight + mScaledUp; - mB = mPosition - mScaledRight + mScaledUp; - mC = mPosition - mScaledRight - mScaledUp; - mD = mPosition + mScaledRight - mScaledUp; - } - } - else - { - // this is equivalent to how it was done before. . . - // we need to establish a way to - // identify the orientation of a particular sprite rather than - // just banging it in on the x,z plane if it's not following the camera. - - LLVector3 x_axis; - LLVector3 y_axis; - - F32 dot = sNormal * LLVector3(0.f, 1.f, 0.f); - if (dot == 1.f || dot == -1.f) - { - x_axis.setVec(1.f, 0.f, 0.f); - y_axis.setVec(0.f, 1.f, 0.f); - } - else - { - x_axis = sNormal % LLVector3(0.f, -1.f, 0.f); - x_axis.normalize(); - - y_axis = sNormal % x_axis; - } - - LLQuaternion yaw_rot(mYaw, sNormal); - - // rotate axes by specified yaw - x_axis = x_axis * yaw_rot; - y_axis = y_axis * yaw_rot; - - // rescale axes by width and height of sprite - x_axis = x_axis * mWidthDiv2; - y_axis = y_axis * mHeightDiv2; - - mA = -x_axis + y_axis; - mB = x_axis + y_axis; - mC = x_axis - y_axis; - mD = -x_axis - y_axis; - - mA += mPosition; - mB += mPosition; - mC += mPosition; - mD += mPosition; - } - - face.setFaceColor(mColor); - - LLStrider verticesp; - LLStrider normalsp; - LLStrider tex_coordsp; - LLStrider indicesp; - U16 index_offset; - - // Setup face - if (!face.getVertexBuffer()) - { - LLVertexBuffer* buff = new LLVertexBuffer(LLVertexBuffer::MAP_VERTEX | - LLVertexBuffer::MAP_TEXCOORD0 ); - buff->allocateBuffer(4, 12); - face.setGeomIndex(0); - face.setIndicesIndex(0); - face.setVertexBuffer(buff); - } - - index_offset = face.getGeometry(verticesp,normalsp,tex_coordsp, indicesp); - - *tex_coordsp = LLVector2(0.f, 0.f); - *verticesp = mC; - tex_coordsp++; - verticesp++; - - *tex_coordsp = LLVector2(0.f, 1.f); - *verticesp = mB; - tex_coordsp++; - verticesp++; - - *tex_coordsp = LLVector2(1.f, 1.f); - *verticesp = mA; - tex_coordsp++; - verticesp++; - - *tex_coordsp = LLVector2(1.f, 0.0f); - *verticesp = mD; - tex_coordsp++; - verticesp++; - - // Generate indices, since they're easy. - // Just a series of quads. - *indicesp++ = index_offset; - *indicesp++ = 2 + index_offset; - *indicesp++ = 1 + index_offset; - - *indicesp++ = index_offset; - *indicesp++ = 3 + index_offset; - *indicesp++ = 2 + index_offset; - - if (!mFollow) - { - *indicesp++ = 0 + index_offset; - *indicesp++ = 1 + index_offset; - *indicesp++ = 2 + index_offset; - *indicesp++ = 0 + index_offset; - *indicesp++ = 2 + index_offset; - *indicesp++ = 3 + index_offset; - } - - face.getVertexBuffer()->unmapBuffer(); - face.mCenterAgent = mPosition; -} - -void LLSprite::setPosition(const LLVector3 &position) -{ - mPosition = position; -} - - -void LLSprite::setPitch(const F32 pitch) -{ - mPitch = pitch; -} - - -void LLSprite::setSize(const F32 width, const F32 height) -{ - mWidth = width; - mHeight = height; - mWidthDiv2 = width/2.0f; - mHeightDiv2 = height/2.0f; -} - -void LLSprite::setYaw(F32 yaw) -{ - mYaw = yaw; -} - -void LLSprite::setFollow(const bool follow) -{ - mFollow = follow; -} - -void LLSprite::setUseCameraUp(const bool use_up) -{ - mUseCameraUp = use_up; -} - -void LLSprite::setTexMode(const LLGLenum mode) -{ - mTexMode = mode; -} - -void LLSprite::setColor(const LLColor4 &color) -{ - mColor = color; -} - -void LLSprite::setColor(const F32 r, const F32 g, const F32 b, const F32 a) -{ - mColor.setVec(r, g, b, a); -} - - - - - - - - - - +/** + * @file llsprite.cpp + * @brief LLSprite class implementation + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/* -*- c++ -*- + * Notes: + * PR - Should add a creator that can take a pointer rather than handle for streaming + * object textures. + * PR - Need to add support for lit/non-lit conditions, set normals? + */ + +#include "llviewerprecompiledheaders.h" + +#include + +#include "llsprite.h" + +#include "math.h" + +#include "lldrawable.h" +#include "llface.h" +#include "llviewercamera.h" +#include "llviewertexturelist.h" + +LLVector3 LLSprite::sCameraUp(0.0f,0.0f,1.0f); +LLVector3 LLSprite::sCameraRight(1.0f,0.0f,0.0f); +LLVector3 LLSprite::sCameraPosition(0.f, 0.f, 0.f); +LLVector3 LLSprite::sNormal(0.0f,0.0f,0.0f); + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +// A simple initialization +LLSprite::LLSprite(const LLUUID &image_uuid) : + mImageID(image_uuid), + mImagep(NULL), + mPitch(0.f), + mYaw(0.f), + mPosition(0.0f, 0.0f, 0.0f), + mFollow(true), + mUseCameraUp(true), + mColor(0.5f, 0.5f, 0.5f, 1.0f), + mTexMode(GL_REPLACE) +{ + setSize(1.0f, 1.0f); +} + +////////////////////////////////////////////////////////////////////// +LLSprite::~LLSprite() +{ +} + +void LLSprite::updateFace(LLFace &face) +{ + LLViewerCamera &camera = *LLViewerCamera::getInstance(); + + // First, figure out how many vertices/indices we need. + U32 num_vertices, num_indices; + + // Get the total number of vertices and indices + if (mFollow) + { + num_vertices = 4; + num_indices = 6; + } + else + { + num_vertices = 4; + num_indices = 12; + } + + face.setSize(num_vertices, num_indices); + + if (mFollow) + { + sCameraUp = camera.getUpAxis(); + sCameraRight = -camera.getLeftAxis(); + sCameraPosition = camera.getOrigin(); + sNormal = -camera.getAtAxis(); + if (mUseCameraUp) + { + // these need to live here because the height/width may change between render calls + mScaledUp = sCameraUp; + mScaledRight = sCameraRight; + + mScaledUp *= mHeightDiv2; + mScaledRight *= mWidthDiv2; + + mA = mPosition + mScaledRight + mScaledUp; + mB = mPosition - mScaledRight + mScaledUp; + mC = mPosition - mScaledRight - mScaledUp; + mD = mPosition + mScaledRight - mScaledUp; + } + else + { + // The up vector is perpendicular to the camera vector... + LLVector3 camera_vec = mPosition - sCameraPosition; + mScaledRight = camera_vec % LLVector3(0.f, 0.f, 1.f); + mScaledUp = -(camera_vec % mScaledRight); + mScaledUp.normalize(); + mScaledRight.normalize(); + mScaledUp *= mHeightDiv2; + mScaledRight *= mWidthDiv2; + + mA = mPosition + mScaledRight + mScaledUp; + mB = mPosition - mScaledRight + mScaledUp; + mC = mPosition - mScaledRight - mScaledUp; + mD = mPosition + mScaledRight - mScaledUp; + } + } + else + { + // this is equivalent to how it was done before. . . + // we need to establish a way to + // identify the orientation of a particular sprite rather than + // just banging it in on the x,z plane if it's not following the camera. + + LLVector3 x_axis; + LLVector3 y_axis; + + F32 dot = sNormal * LLVector3(0.f, 1.f, 0.f); + if (dot == 1.f || dot == -1.f) + { + x_axis.setVec(1.f, 0.f, 0.f); + y_axis.setVec(0.f, 1.f, 0.f); + } + else + { + x_axis = sNormal % LLVector3(0.f, -1.f, 0.f); + x_axis.normalize(); + + y_axis = sNormal % x_axis; + } + + LLQuaternion yaw_rot(mYaw, sNormal); + + // rotate axes by specified yaw + x_axis = x_axis * yaw_rot; + y_axis = y_axis * yaw_rot; + + // rescale axes by width and height of sprite + x_axis = x_axis * mWidthDiv2; + y_axis = y_axis * mHeightDiv2; + + mA = -x_axis + y_axis; + mB = x_axis + y_axis; + mC = x_axis - y_axis; + mD = -x_axis - y_axis; + + mA += mPosition; + mB += mPosition; + mC += mPosition; + mD += mPosition; + } + + face.setFaceColor(mColor); + + LLStrider verticesp; + LLStrider normalsp; + LLStrider tex_coordsp; + LLStrider indicesp; + U16 index_offset; + + // Setup face + if (!face.getVertexBuffer()) + { + LLVertexBuffer* buff = new LLVertexBuffer(LLVertexBuffer::MAP_VERTEX | + LLVertexBuffer::MAP_TEXCOORD0 ); + buff->allocateBuffer(4, 12); + face.setGeomIndex(0); + face.setIndicesIndex(0); + face.setVertexBuffer(buff); + } + + index_offset = face.getGeometry(verticesp,normalsp,tex_coordsp, indicesp); + + *tex_coordsp = LLVector2(0.f, 0.f); + *verticesp = mC; + tex_coordsp++; + verticesp++; + + *tex_coordsp = LLVector2(0.f, 1.f); + *verticesp = mB; + tex_coordsp++; + verticesp++; + + *tex_coordsp = LLVector2(1.f, 1.f); + *verticesp = mA; + tex_coordsp++; + verticesp++; + + *tex_coordsp = LLVector2(1.f, 0.0f); + *verticesp = mD; + tex_coordsp++; + verticesp++; + + // Generate indices, since they're easy. + // Just a series of quads. + *indicesp++ = index_offset; + *indicesp++ = 2 + index_offset; + *indicesp++ = 1 + index_offset; + + *indicesp++ = index_offset; + *indicesp++ = 3 + index_offset; + *indicesp++ = 2 + index_offset; + + if (!mFollow) + { + *indicesp++ = 0 + index_offset; + *indicesp++ = 1 + index_offset; + *indicesp++ = 2 + index_offset; + *indicesp++ = 0 + index_offset; + *indicesp++ = 2 + index_offset; + *indicesp++ = 3 + index_offset; + } + + face.getVertexBuffer()->unmapBuffer(); + face.mCenterAgent = mPosition; +} + +void LLSprite::setPosition(const LLVector3 &position) +{ + mPosition = position; +} + + +void LLSprite::setPitch(const F32 pitch) +{ + mPitch = pitch; +} + + +void LLSprite::setSize(const F32 width, const F32 height) +{ + mWidth = width; + mHeight = height; + mWidthDiv2 = width/2.0f; + mHeightDiv2 = height/2.0f; +} + +void LLSprite::setYaw(F32 yaw) +{ + mYaw = yaw; +} + +void LLSprite::setFollow(const bool follow) +{ + mFollow = follow; +} + +void LLSprite::setUseCameraUp(const bool use_up) +{ + mUseCameraUp = use_up; +} + +void LLSprite::setTexMode(const LLGLenum mode) +{ + mTexMode = mode; +} + +void LLSprite::setColor(const LLColor4 &color) +{ + mColor = color; +} + +void LLSprite::setColor(const F32 r, const F32 g, const F32 b, const F32 a) +{ + mColor.setVec(r, g, b, a); +} + + + + + + + + + + diff --git a/indra/newview/llsprite.h b/indra/newview/llsprite.h index 113a73a815..44439bd30c 100644 --- a/indra/newview/llsprite.h +++ b/indra/newview/llsprite.h @@ -1,106 +1,106 @@ -/** - * @file llsprite.h - * @brief LLSprite class definition - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLSPRITE_H -#define LL_LLSPRITE_H - -////#include "vmath.h" -//#include "llmath.h" -#include "v3math.h" -#include "v4math.h" -#include "v4color.h" -#include "lluuid.h" -#include "llgl.h" -#include "llviewertexture.h" - -class LLViewerCamera; - -class LLFace; - -class LLSprite -{ -public: - LLSprite(const LLUUID &image_uuid); - ~LLSprite(); - - void render(LLViewerCamera * camerap); - - F32 getWidth() const { return mWidth; } - F32 getHeight() const { return mHeight; } - F32 getYaw() const { return mYaw; } - F32 getPitch() const { return mPitch; } - F32 getAlpha() const { return mColor.mV[VALPHA]; } - - LLVector3 getPosition() const { return mPosition; } - LLColor4 getColor() const { return mColor; } - - void setPosition(const LLVector3 &position); - void setPitch(const F32 pitch); - void setSize(const F32 width, const F32 height); - void setYaw(const F32 yaw); - void setFollow(const bool follow); - void setUseCameraUp(const bool use_up); - - void setTexMode(LLGLenum mode); - void setColor(const LLColor4 &color); - void setColor(const F32 r, const F32 g, const F32 b, const F32 a); - void setAlpha(const F32 alpha) { mColor.mV[VALPHA] = alpha; } - void setNormal(const LLVector3 &normal) { sNormal = normal; sNormal.normalize();} - - F32 getAlpha(); - - void updateFace(LLFace &face); - -public: - LLUUID mImageID; - LLPointer mImagep; -private: - F32 mWidth; - F32 mHeight; - F32 mWidthDiv2; - F32 mHeightDiv2; - F32 mPitch; - F32 mYaw; - LLVector3 mPosition; - bool mFollow; - bool mUseCameraUp; - - LLColor4 mColor; - LLGLenum mTexMode; - - // put - LLVector3 mScaledUp; - LLVector3 mScaledRight; - static LLVector3 sCameraUp; - static LLVector3 sCameraRight; - static LLVector3 sCameraPosition; - static LLVector3 sNormal; - LLVector3 mA,mB,mC,mD; // the four corners of a quad - -}; - -#endif - +/** + * @file llsprite.h + * @brief LLSprite class definition + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSPRITE_H +#define LL_LLSPRITE_H + +////#include "vmath.h" +//#include "llmath.h" +#include "v3math.h" +#include "v4math.h" +#include "v4color.h" +#include "lluuid.h" +#include "llgl.h" +#include "llviewertexture.h" + +class LLViewerCamera; + +class LLFace; + +class LLSprite +{ +public: + LLSprite(const LLUUID &image_uuid); + ~LLSprite(); + + void render(LLViewerCamera * camerap); + + F32 getWidth() const { return mWidth; } + F32 getHeight() const { return mHeight; } + F32 getYaw() const { return mYaw; } + F32 getPitch() const { return mPitch; } + F32 getAlpha() const { return mColor.mV[VALPHA]; } + + LLVector3 getPosition() const { return mPosition; } + LLColor4 getColor() const { return mColor; } + + void setPosition(const LLVector3 &position); + void setPitch(const F32 pitch); + void setSize(const F32 width, const F32 height); + void setYaw(const F32 yaw); + void setFollow(const bool follow); + void setUseCameraUp(const bool use_up); + + void setTexMode(LLGLenum mode); + void setColor(const LLColor4 &color); + void setColor(const F32 r, const F32 g, const F32 b, const F32 a); + void setAlpha(const F32 alpha) { mColor.mV[VALPHA] = alpha; } + void setNormal(const LLVector3 &normal) { sNormal = normal; sNormal.normalize();} + + F32 getAlpha(); + + void updateFace(LLFace &face); + +public: + LLUUID mImageID; + LLPointer mImagep; +private: + F32 mWidth; + F32 mHeight; + F32 mWidthDiv2; + F32 mHeightDiv2; + F32 mPitch; + F32 mYaw; + LLVector3 mPosition; + bool mFollow; + bool mUseCameraUp; + + LLColor4 mColor; + LLGLenum mTexMode; + + // put + LLVector3 mScaledUp; + LLVector3 mScaledRight; + static LLVector3 sCameraUp; + static LLVector3 sCameraRight; + static LLVector3 sCameraPosition; + static LLVector3 sNormal; + LLVector3 mA,mB,mC,mD; // the four corners of a quad + +}; + +#endif + diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 0b8cb4163b..384c8caf90 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -1,3876 +1,3876 @@ -/** - * @file llstartup.cpp - * @brief startup routines. - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llappviewer.h" -#include "llstartup.h" -#include "llcallstack.h" - -#if LL_WINDOWS -# include // _spawnl() -#else -# include // mkdir() -#endif -#include // std::unique_ptr - -#include "llviewermedia_streamingaudio.h" -#include "llaudioengine.h" - -#ifdef LL_OPENAL -#include "llaudioengine_openal.h" -#endif - -#include "llavatarnamecache.h" -#include "llexperiencecache.h" -#include "lllandmark.h" -#include "llcachename.h" -#include "lldir.h" -#include "lldonotdisturbnotificationstorage.h" -#include "llerrorcontrol.h" -#include "llfloaterreg.h" -#include "llfocusmgr.h" -#include "llfloatergridstatus.h" -#include "llfloaterimsession.h" -#include "lllocationhistory.h" -#include "llgltfmateriallist.h" -#include "llimageworker.h" - -#include "llloginflags.h" -#include "llmd5.h" -#include "llmemorystream.h" -#include "llmessageconfig.h" -#include "llmoveview.h" -#include "llfloaterimcontainer.h" -#include "llfloaterimnearbychat.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "llpersistentnotificationstorage.h" -#include "llpresetsmanager.h" -#include "llteleporthistory.h" -#include "llregionhandle.h" -#include "llsd.h" -#include "llsdserialize.h" -#include "llsdutil_math.h" -#include "llstring.h" -#include "lluserrelations.h" -#include "llversioninfo.h" -#include "llviewercontrol.h" -#include "llviewerhelp.h" -#include "llxorcipher.h" // saved password, MAC address -#include "llwindow.h" -#include "message.h" -#include "v3math.h" - -#include "llagent.h" -#include "llagentbenefits.h" -#include "llagentcamera.h" -#include "llagentpicksinfo.h" -#include "llagentwearables.h" -#include "llagentpilot.h" -#include "llfloateravatarpicker.h" -#include "llcallbacklist.h" -#include "llcallingcard.h" -#include "llclassifiedinfo.h" -#include "llconsole.h" -#include "llcontainerview.h" -#include "llconversationlog.h" -#include "lldebugview.h" -#include "lldrawable.h" -#include "lleventnotifier.h" -#include "llface.h" -#include "llfeaturemanager.h" -//#include "llfirstuse.h" -#include "llfloaterhud.h" -#include "llfloaterland.h" -#include "llfloatertopobjects.h" -#include "llfloaterworldmap.h" -#include "llgesturemgr.h" -#include "llgroupmgr.h" -#include "llhudeffecttrail.h" -#include "llhudmanager.h" -#include "llimage.h" -#include "llinventorybridge.h" -#include "llinventorymodel.h" -#include "llinventorymodelbackgroundfetch.h" -#include "llkeyboard.h" -#include "llloginhandler.h" // gLoginHandler, SLURL support -#include "lllogininstance.h" // Host the login module. -#include "llpanellogin.h" -#include "llmutelist.h" -#include "llavatarpropertiesprocessor.h" -#include "llpanelgrouplandmoney.h" -#include "llpanelgroupnotices.h" -#include "llparcel.h" -#include "llpreview.h" -#include "llpreviewscript.h" -#include "llproxy.h" -#include "llproductinforequest.h" -#include "llqueryflags.h" -#include "llsecapi.h" -#include "llselectmgr.h" -#include "llsky.h" -#include "llstatview.h" -#include "llstatusbar.h" // sendMoneyBalanceRequest(), owns L$ balance -#include "llsurface.h" -#include "lltexturecache.h" -#include "lltexturefetch.h" -#include "lltoolmgr.h" -#include "lltrans.h" -#include "llui.h" -#include "lluiusage.h" -#include "llurldispatcher.h" -#include "llurlentry.h" -#include "llslurl.h" -#include "llurlhistory.h" -#include "llurlwhitelist.h" -#include "llvieweraudio.h" -#include "llviewerassetstorage.h" -#include "llviewercamera.h" -#include "llviewerdisplay.h" -#include "llviewergenericmessage.h" -#include "llviewergesture.h" -#include "llviewertexturelist.h" -#include "llviewermedia.h" -#include "llviewermenu.h" -#include "llviewermessage.h" -#include "llviewernetwork.h" -#include "llviewerobjectlist.h" -#include "llviewerparcelaskplay.h" -#include "llviewerparcelmedia.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" -#include "llviewerstats.h" -#include "llviewerstatsrecorder.h" -#include "llviewerthrottle.h" -#include "llviewerwindow.h" -#include "llvoavatar.h" -#include "llvoavatarself.h" -#include "llweb.h" -#include "llworld.h" -#include "llworldmapmessage.h" -#include "llxfermanager.h" -#include "pipeline.h" -#include "llappviewer.h" -#include "llfasttimerview.h" -#include "llfloatermap.h" -#include "llweb.h" -#include "llvoiceclient.h" -#include "llnamelistctrl.h" -#include "llnamebox.h" -#include "llnameeditor.h" -#include "llpostprocess.h" -#include "llagentlanguage.h" -#include "llwearable.h" -#include "llinventorybridge.h" -#include "llappearancemgr.h" -#include "llavatariconctrl.h" -#include "llvoicechannel.h" -#include "llpathfindingmanager.h" -#include "llremoteparcelrequest.h" - -#include "lllogin.h" -#include "llevents.h" -#include "llstartuplistener.h" -#include "lltoolbarview.h" -#include "llexperiencelog.h" -#include "llcleanup.h" - -#include "llenvironment.h" - -#include "llstacktrace.h" - -#include "threadpool.h" -#include "llperfstats.h" - - -#if LL_WINDOWS -#include "lldxhardware.h" -#endif - -// -// exported globals -// -bool gAgentMovementCompleted = false; - -const std::string SCREEN_HOME_FILENAME = "screen_home%s.png"; -const std::string SCREEN_LAST_FILENAME = "screen_last%s.png"; - -LLPointer gStartTexture; - -// -// Imported globals -// -extern S32 gStartImageWidth; -extern S32 gStartImageHeight; - -// -// local globals -// -static bool gGotUseCircuitCodeAck = false; -static std::string sInitialOutfit; -static std::string sInitialOutfitGender; // "male" or "female" - -static bool gUseCircuitCallbackCalled = false; - -EStartupState LLStartUp::gStartupState = STATE_FIRST; -LLSLURL LLStartUp::sStartSLURL; - -static LLPointer gUserCredential; -static std::string gDisplayName; -static bool gRememberPassword = true; -static bool gRememberUser = true; - -static U64 gFirstSimHandle = 0; -static LLHost gFirstSim; -static std::string gFirstSimSeedCap; -static LLVector3 gAgentStartLookAt(1.0f, 0.f, 0.f); -static std::string gAgentStartLocation = "safe"; -static bool mLoginStatePastUI = false; -static bool mBenefitsSuccessfullyInit = false; - -const F32 STATE_AGENT_WAIT_TIMEOUT = 240; //seconds -const S32 MAX_SEED_CAP_ATTEMPTS_BEFORE_ABORT = 4; // Give region 4 chances - -std::unique_ptr LLStartUp::sStateWatcher(new LLEventStream("StartupState")); -std::unique_ptr LLStartUp::sListener(new LLStartupListener()); -std::unique_ptr LLStartUp::sPhases(new LLViewerStats::PhaseMap); - -// -// local function declaration -// - -void login_show(); -void login_callback(S32 option, void* userdata); -void show_release_notes_if_required(); -void show_first_run_dialog(); -bool first_run_dialog_callback(const LLSD& notification, const LLSD& response); -void set_startup_status(const F32 frac, const std::string& string, const std::string& msg); -bool login_alert_status(const LLSD& notification, const LLSD& response); -void use_circuit_callback(void**, S32 result); -void register_viewer_callbacks(LLMessageSystem* msg); -void asset_callback_nothing(const LLUUID&, LLAssetType::EType, void*, S32); -bool callback_choose_gender(const LLSD& notification, const LLSD& response); -void release_start_screen(); -void reset_login(); -LLSD transform_cert_args(LLPointer cert); -void general_cert_done(const LLSD& notification, const LLSD& response); -void trust_cert_done(const LLSD& notification, const LLSD& response); -void apply_udp_blacklist(const std::string& csv); -bool process_login_success_response(); -void on_benefits_failed_callback(const LLSD& notification, const LLSD& response); -void transition_back_to_login_panel(const std::string& emsg); - -void callback_cache_name(const LLUUID& id, const std::string& full_name, bool is_group) -{ - LLNameBox::refreshAll(id, full_name, is_group); - LLNameEditor::refreshAll(id, full_name, is_group); - - // TODO: Actually be intelligent about the refresh. - // For now, just brute force refresh the dialogs. - dialog_refresh_all(); -} - -// -// exported functionality -// - -void pump_idle_startup_network(void) -{ - { - LockMessageChecker lmc(gMessageSystem); - while (lmc.checkAllMessages(gFrameCount, gServicePump)) - { - display_startup(); - } - lmc.processAcks(); - } - display_startup(); -} - -// -// local classes -// -void update_texture_fetch() -{ - LLAppViewer::getTextureCache()->update(1); // unpauses the texture cache thread - LLAppViewer::getImageDecodeThread()->update(1); // unpauses the image thread - LLAppViewer::getTextureFetch()->update(1); // unpauses the texture fetch thread - gTextureList.updateImages(0.10f); - - if (LLImageGLThread::sEnabledTextures) - { - std::shared_ptr main_queue = LL::WorkQueue::getInstance("mainloop"); - main_queue->runFor(std::chrono::milliseconds(1)); - } -} - -void set_flags_and_update_appearance() -{ - LLAppearanceMgr::instance().setAttachmentInvLinkEnable(true); - LLAppearanceMgr::instance().updateAppearanceFromCOF(true, true, no_op); - - LLInventoryModelBackgroundFetch::instance().start(); -} - -// Returns false to skip other idle processing. Should only return -// true when all initialization done. -bool idle_startup() -{ - if (gViewerWindow == NULL) - { - // We expect window to be initialized - LL_WARNS_ONCE() << "gViewerWindow is not initialized" << LL_ENDL; - return false; // No world yet - } - - const F32 PRECACHING_DELAY = gSavedSettings.getF32("PrecachingDelay"); - static LLTimer timeout; - - static LLTimer login_time; - - // until this is encapsulated, this little hack for the - // auth/transform loop will do. - static F32 progress = 0.10f; - - static std::string auth_desc; - static std::string auth_message; - - static LLVector3 agent_start_position_region(10.f, 10.f, 10.f); // default for when no space server - - // last location by default - static S32 agent_location_id = START_LOCATION_ID_LAST; - - static bool show_connect_box = true; - - //static bool stipend_since_login = false; - - // HACK: These are things from the main loop that usually aren't done - // until initialization is complete, but need to be done here for things - // to work. - gIdleCallbacks.callFunctions(); - gViewerWindow->updateUI(); - - LLMortician::updateClass(); - - const std::string delims (" "); - std::string system; - int begIdx, endIdx; - std::string osString = LLOSInfo::instance().getOSStringSimple(); - - begIdx = osString.find_first_not_of (delims); - endIdx = osString.find_first_of (delims, begIdx); - system = osString.substr (begIdx, endIdx - begIdx); - system += "Locale"; - - LLStringUtil::setLocale (LLTrans::getString(system)); - - //note: Removing this line will cause incorrect button size in the login screen. -- bao. - gTextureList.updateImages(0.01f) ; - - if ( STATE_FIRST == LLStartUp::getStartupState() ) - { - static bool first_call = true; - if (first_call) - { - // Other phases get handled when startup state changes, - // need to capture the initial state as well. - LLStartUp::getPhases().startPhase(LLStartUp::getStartupStateString()); - first_call = false; - } - - gViewerWindow->showCursor(); - gViewerWindow->getWindow()->setCursor(UI_CURSOR_WAIT); - - ///////////////////////////////////////////////// - // - // Initialize stuff that doesn't need data from simulators - // - std::string lastGPU = gSavedSettings.getString("LastGPUString"); - std::string thisGPU = LLFeatureManager::getInstance()->getGPUString(); - - if (LLFeatureManager::getInstance()->isSafe()) - { - LLNotificationsUtil::add("DisplaySetToSafe"); - } - else if ((gSavedSettings.getS32("LastFeatureVersion") < LLFeatureManager::getInstance()->getVersion()) && - (gSavedSettings.getS32("LastFeatureVersion") != 0)) - { - LLNotificationsUtil::add("DisplaySetToRecommendedFeatureChange"); - } - else if ( ! lastGPU.empty() && (lastGPU != thisGPU)) - { - LLSD subs; - subs["LAST_GPU"] = lastGPU; - subs["THIS_GPU"] = thisGPU; - LLNotificationsUtil::add("DisplaySetToRecommendedGPUChange", subs); - } - else if (!gViewerWindow->getInitAlert().empty()) - { - LLNotificationsUtil::add(gViewerWindow->getInitAlert()); - } - - //------------------------------------------------- - // Init the SOCKS 5 proxy if the user has configured - // one. We need to do this early in case the user - // is using SOCKS for HTTP so we get the login - // screen and HTTP tables via SOCKS. - //------------------------------------------------- - LLStartUp::startLLProxy(); - - gSavedSettings.setS32("LastFeatureVersion", LLFeatureManager::getInstance()->getVersion()); - gSavedSettings.setString("LastGPUString", thisGPU); - - - std::string xml_file = LLUI::locateSkin("xui_version.xml"); - LLXMLNodePtr root; - bool xml_ok = false; - if (LLXMLNode::parseFile(xml_file, root, NULL)) - { - if( (root->hasName("xui_version") ) ) - { - std::string value = root->getValue(); - F32 version = 0.0f; - LLStringUtil::convertToF32(value, version); - if (version >= 1.0f) - { - xml_ok = true; - } - } - } - if (!xml_ok) - { - // If XML is bad, there's a good possibility that notifications.xml is ALSO bad. - // If that's so, then we'll get a fatal error on attempting to load it, - // which will display a nontranslatable error message that says so. - // Otherwise, we'll display a reasonable error message that IS translatable. - LLAppViewer::instance()->earlyExit("BadInstallation"); - } - // - // Statistics stuff - // - - // Load autopilot and stats stuff - gAgentPilot.load(); - - //gErrorStream.setTime(gSavedSettings.getBOOL("LogTimestamps")); - - // Load the throttle settings - gViewerThrottle.load(); - - // - // Initialize messaging system - // - LL_DEBUGS("AppInit") << "Initializing messaging system..." << LL_ENDL; - - std::string message_template_path = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS,"message_template.msg"); - - LLFILE* found_template = NULL; - found_template = LLFile::fopen(message_template_path, "r"); /* Flawfinder: ignore */ - - #if LL_WINDOWS - // On the windows dev builds, unpackaged, the message_template.msg - // file will be located in: - // build-vc**/newview//app_settings - if (!found_template) - { - message_template_path = gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, "app_settings", "message_template.msg"); - found_template = LLFile::fopen(message_template_path.c_str(), "r"); /* Flawfinder: ignore */ - } - #elif LL_DARWIN - // On Mac dev builds, message_template.msg lives in: - // indra/build-*/newview//Second Life/Contents/Resources/app_settings - if (!found_template) - { - message_template_path = - gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, - "message_template.msg"); - found_template = LLFile::fopen(message_template_path.c_str(), "r"); /* Flawfinder: ignore */ - } - #endif - - if (found_template) - { - fclose(found_template); - - U32 port = gSavedSettings.getU32("UserConnectionPort"); - - if ((NET_USE_OS_ASSIGNED_PORT == port) && // if nothing specified on command line (-port) - (gSavedSettings.getBOOL("ConnectionPortEnabled"))) - { - port = gSavedSettings.getU32("ConnectionPort"); - } - - // TODO parameterize - const F32 circuit_heartbeat_interval = 5; - const F32 circuit_timeout = 100; - - const LLUseCircuitCodeResponder* responder = NULL; - bool failure_is_fatal = true; - - if(!start_messaging_system( - message_template_path, - port, - LLVersionInfo::instance().getMajor(), - LLVersionInfo::instance().getMinor(), - LLVersionInfo::instance().getPatch(), - false, - std::string(), - responder, - failure_is_fatal, - circuit_heartbeat_interval, - circuit_timeout)) - { - std::string diagnostic = llformat(" Error: %d", gMessageSystem->getErrorCode()); - LL_WARNS("AppInit") << diagnostic << LL_ENDL; - LLAppViewer::instance()->earlyExit("LoginFailedNoNetwork", LLSD().with("DIAGNOSTIC", diagnostic)); - } - - #if LL_WINDOWS - // On the windows dev builds, unpackaged, the message.xml file will - // be located in indra/build-vc**/newview//app_settings. - std::string message_path = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS,"message.xml"); - - if (!LLFile::isfile(message_path.c_str())) - { - LLMessageConfig::initClass("viewer", gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, "app_settings", "")); - } - else - { - LLMessageConfig::initClass("viewer", gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "")); - } - #else - LLMessageConfig::initClass("viewer", gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "")); - #endif - - } - else - { - LLAppViewer::instance()->earlyExit("MessageTemplateNotFound", LLSD().with("PATH", message_template_path)); - } - - if(gMessageSystem && gMessageSystem->isOK()) - { - // Initialize all of the callbacks in case of bad message - // system data - LLMessageSystem* msg = gMessageSystem; - msg->setExceptionFunc(MX_UNREGISTERED_MESSAGE, - invalid_message_callback, - NULL); - msg->setExceptionFunc(MX_PACKET_TOO_SHORT, - invalid_message_callback, - NULL); - - // running off end of a packet is now valid in the case - // when a reader has a newer message template than - // the sender - /*msg->setExceptionFunc(MX_RAN_OFF_END_OF_PACKET, - invalid_message_callback, - NULL);*/ - msg->setExceptionFunc(MX_WROTE_PAST_BUFFER_SIZE, - invalid_message_callback, - NULL); - - if (gSavedSettings.getBOOL("LogMessages")) - { - LL_DEBUGS("AppInit") << "Message logging activated!" << LL_ENDL; - msg->startLogging(); - } - - // start the xfer system. by default, choke the downloads - // a lot... - const S32 VIEWER_MAX_XFER = 3; - start_xfer_manager(); - gXferManager->setMaxIncomingXfers(VIEWER_MAX_XFER); - F32 xfer_throttle_bps = gSavedSettings.getF32("XferThrottle"); - if (xfer_throttle_bps > 1.f) - { - gXferManager->setUseAckThrottling(true); - gXferManager->setAckThrottleBPS(xfer_throttle_bps); - } - gAssetStorage = new LLViewerAssetStorage(msg, gXferManager); - - - F32 dropPercent = gSavedSettings.getF32("PacketDropPercentage"); - msg->mPacketRing.setDropPercentage(dropPercent); - - F32 inBandwidth = gSavedSettings.getF32("InBandwidth"); - F32 outBandwidth = gSavedSettings.getF32("OutBandwidth"); - if (inBandwidth != 0.f) - { - LL_DEBUGS("AppInit") << "Setting packetring incoming bandwidth to " << inBandwidth << LL_ENDL; - msg->mPacketRing.setUseInThrottle(true); - msg->mPacketRing.setInBandwidth(inBandwidth); - } - if (outBandwidth != 0.f) - { - LL_DEBUGS("AppInit") << "Setting packetring outgoing bandwidth to " << outBandwidth << LL_ENDL; - msg->mPacketRing.setUseOutThrottle(true); - msg->mPacketRing.setOutBandwidth(outBandwidth); - } - } - - LL_INFOS("AppInit") << "Message System Initialized." << LL_ENDL; - - //------------------------------------------------- - // Init audio, which may be needed for prefs dialog - // or audio cues in connection UI. - //------------------------------------------------- - - if (false == gSavedSettings.getBOOL("NoAudio")) - { - delete gAudiop; - gAudiop = NULL; - -#ifdef LL_OPENAL -#if !LL_WINDOWS - if (NULL == getenv("LL_BAD_OPENAL_DRIVER")) -#endif // !LL_WINDOWS - { - gAudiop = (LLAudioEngine *) new LLAudioEngine_OpenAL(); - } -#endif - - if (gAudiop) - { -#if LL_WINDOWS - // FMOD Ex on Windows needs the window handle to stop playing audio - // when window is minimized. JC - void* window_handle = (HWND)gViewerWindow->getPlatformWindow(); -#else - void* window_handle = NULL; -#endif - if (gAudiop->init(window_handle, LLAppViewer::instance()->getSecondLifeTitle())) - { - LL_INFOS("AppInit") << "Using media plugins to render streaming audio" << LL_ENDL; - gAudiop->setStreamingAudioImpl(new LLStreamingAudio_MediaPlugins()); - - gAudiop->setMuted(true); - } - else - { - LL_WARNS("AppInit") << "Unable to initialize audio engine" << LL_ENDL; - delete gAudiop; - gAudiop = NULL; - } - } - } - - LL_INFOS("AppInit") << "Audio Engine Initialized." << LL_ENDL; - - if (LLTimer::knownBadTimer()) - { - LL_WARNS("AppInit") << "Unreliable timers detected (may be bad PCI chipset)!!" << LL_ENDL; - } - - // - // Log on to system - // - if (gUserCredential.isNull()) - { - gUserCredential = gLoginHandler.initializeLoginInfo(); - } - // Previous initializeLoginInfo may have generated user credentials. Re-check them. - if (gUserCredential.isNull()) - { - show_connect_box = true; - } - else if (gSavedSettings.getBOOL("AutoLogin")) - { - // Log into last account - gRememberPassword = true; - gRememberUser = true; - gSavedSettings.setBOOL("RememberPassword", true); - gSavedSettings.setBOOL("RememberUser", true); - show_connect_box = false; - } - else if (gSavedSettings.getLLSD("UserLoginInfo").size() == 3) - { - // Console provided login&password - gRememberPassword = gSavedSettings.getBOOL("RememberPassword"); - gRememberUser = gSavedSettings.getBOOL("RememberUser"); - show_connect_box = false; - } - else - { - gRememberPassword = gSavedSettings.getBOOL("RememberPassword"); - gRememberUser = gSavedSettings.getBOOL("RememberUser"); - show_connect_box = true; - } - - //setup map of datetime strings to codes and slt & local time offset from utc - // *TODO: Does this need to be here? - LLStringOps::setupDatetimeInfo(false); - - // Go to the next startup state - LLStartUp::setStartupState( STATE_BROWSER_INIT ); - return false; - } - - - if (STATE_BROWSER_INIT == LLStartUp::getStartupState()) - { - LL_DEBUGS("AppInit") << "STATE_BROWSER_INIT" << LL_ENDL; - std::string msg = LLTrans::getString("LoginInitializingBrowser"); - set_startup_status(0.03f, msg.c_str(), gAgent.mMOTD.c_str()); - display_startup(); - // LLViewerMedia::initBrowser(); - LLStartUp::setStartupState( STATE_LOGIN_SHOW ); - return false; - } - - - if (STATE_LOGIN_SHOW == LLStartUp::getStartupState()) - { - LL_DEBUGS("AppInit") << "Initializing Window, show_connect_box = " - << show_connect_box << LL_ENDL; - - // if we've gone backwards in the login state machine, to this state where we show the UI - // AND the debug setting to exit in this case is true, then go ahead and bail quickly - if ( mLoginStatePastUI && gSavedSettings.getBOOL("QuitOnLoginActivated") ) - { - LL_DEBUGS("AppInit") << "taking QuitOnLoginActivated exit" << LL_ENDL; - // no requirement for notification here - just exit - LLAppViewer::instance()->earlyExitNoNotify(); - } - - gViewerWindow->getWindow()->setCursor(UI_CURSOR_ARROW); - - // Login screen needs menus for preferences, but we can enter - // this startup phase more than once. - if (gLoginMenuBarView == NULL) - { - LL_DEBUGS("AppInit") << "initializing menu bar" << LL_ENDL; - initialize_spellcheck_menu(); - init_menus(); - } - show_release_notes_if_required(); - - if (show_connect_box) - { - LL_DEBUGS("AppInit") << "show_connect_box on" << LL_ENDL; - // Load all the name information out of the login view - // NOTE: Hits "Attempted getFields with no login view shown" warning, since we don't - // show the login view until login_show() is called below. - if (gUserCredential.isNull()) - { - LL_DEBUGS("AppInit") << "loading credentials from gLoginHandler" << LL_ENDL; - gUserCredential = gLoginHandler.initializeLoginInfo(); - } - // Make sure the process dialog doesn't hide things - gViewerWindow->setShowProgress(false); - // Show the login dialog - login_show(); - // connect dialog is already shown, so fill in the names - LLPanelLogin::populateFields( gUserCredential, gRememberUser, gRememberPassword); - LLPanelLogin::giveFocus(); - - // MAINT-3231 Show first run dialog only for Desura viewer - if (gSavedSettings.getString("sourceid") == "1208_desura") - { - if (gSavedSettings.getBOOL("FirstLoginThisInstall")) - { - LL_INFOS("AppInit") << "FirstLoginThisInstall, calling show_first_run_dialog()" << LL_ENDL; - show_first_run_dialog(); - } - else - { - LL_DEBUGS("AppInit") << "FirstLoginThisInstall off" << LL_ENDL; - } - } - display_startup(); - LLStartUp::setStartupState( STATE_LOGIN_WAIT ); // Wait for user input - } - else - { - LL_DEBUGS("AppInit") << "show_connect_box off, skipping to STATE_LOGIN_CLEANUP" << LL_ENDL; - // skip directly to message template verification - LLStartUp::setStartupState( STATE_LOGIN_CLEANUP ); - } - - gViewerWindow->setNormalControlsVisible( false ); - gLoginMenuBarView->setVisible( true ); - gLoginMenuBarView->setEnabled( true ); - show_debug_menus(); - - // Hide the splash screen - LL_DEBUGS("AppInit") << "Hide the splash screen and show window" << LL_ENDL; - LLSplashScreen::hide(); - // Push our window frontmost - gViewerWindow->getWindow()->show(); - - // DEV-16927. The following code removes errant keystrokes that happen while the window is being - // first made visible. -#ifdef _WIN32 - LL_DEBUGS("AppInit") << "Processing PeekMessage" << LL_ENDL; - MSG msg; - while( PeekMessage( &msg, /*All hWnds owned by this thread */ NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE ) ) - { - } - LL_DEBUGS("AppInit") << "PeekMessage processed" << LL_ENDL; -#endif - display_startup(); - timeout.reset(); - return false; - } - - if (STATE_LOGIN_WAIT == LLStartUp::getStartupState()) - { - // when we get to this state, we've already been past the login UI - // (possiblely automatically) - flag this so we can test in the - // STATE_LOGIN_SHOW state if we've gone backwards - mLoginStatePastUI = true; - - // Don't do anything. Wait for the login view to call the login_callback, - // which will push us to the next state. - - // display() function will be the one to run display_startup() - // Sleep so we don't spin the CPU - ms_sleep(1); - return false; - } - - if (STATE_LOGIN_CLEANUP == LLStartUp::getStartupState()) - { - // Post login screen, we should see if any settings have changed that may - // require us to either start/stop or change the socks proxy. As various communications - // past this point may require the proxy to be up. - if (!LLStartUp::startLLProxy()) - { - // Proxy start up failed, we should now bail the state machine - // startLLProxy() will have reported an error to the user - // already, so we just go back to the login screen. The user - // could then change the preferences to fix the issue. - - LLStartUp::setStartupState(STATE_LOGIN_SHOW); - return false; - } - - // reset the values that could have come in from a slurl - // DEV-42215: Make sure they're not empty -- gUserCredential - // might already have been set from gSavedSettings, and it's too bad - // to overwrite valid values with empty strings. - - if (show_connect_box) - { - // TODO if not use viewer auth - // Load all the name information out of the login view - LLPanelLogin::getFields(gUserCredential, gRememberUser, gRememberPassword); - // end TODO - - // HACK: Try to make not jump on login - gKeyboard->resetKeys(); - } - - // when we get to this state, we've already been past the login UI - // (possiblely automatically) - flag this so we can test in the - // STATE_LOGIN_SHOW state if we've gone backwards - mLoginStatePastUI = true; - - // save the credentials - std::string userid = "unknown"; - if (gUserCredential.notNull()) - { - userid = gUserCredential->userID(); - if (gRememberUser) - { - gSecAPIHandler->addToCredentialMap("login_list", gUserCredential, gRememberPassword); - // Legacy viewers use this method to store user credentials, newer viewers - // reuse it to be compatible and to remember last session - gSecAPIHandler->saveCredential(gUserCredential, gRememberPassword); - } - } - gSavedSettings.setBOOL("RememberPassword", gRememberPassword); - gSavedSettings.setBOOL("RememberUser", gRememberUser); - LL_INFOS("AppInit") << "Attempting login as: " << userid << LL_ENDL; - gDebugInfo["LoginName"] = userid; - - // create necessary directories - // *FIX: these mkdir's should error check - gDirUtilp->setLindenUserDir(userid); - LLFile::mkdir(gDirUtilp->getLindenUserDir()); - - // As soon as directories are ready initialize notification storages - if (!LLPersistentNotificationStorage::instanceExists()) - { - // check existance since this part of code can be reached - // twice due to login failures - LLPersistentNotificationStorage::initParamSingleton(); - LLDoNotDisturbNotificationStorage::initParamSingleton(); - } - else - { - // reinitialize paths in case user switched grids or accounts - LLPersistentNotificationStorage::getInstance()->reset(); - LLDoNotDisturbNotificationStorage::getInstance()->reset(); - } - - // Set PerAccountSettingsFile to the default value. - std::string settings_per_account = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, LLAppViewer::instance()->getSettingsFilename("Default", "PerAccount")); - gSavedSettings.setString("PerAccountSettingsFile", settings_per_account); - gDebugInfo["PerAccountSettingsFilename"] = settings_per_account; - - // Note: can't store warnings files per account because some come up before login - - // Overwrite default user settings with user settings - LLAppViewer::instance()->loadSettingsFromDirectory("Account"); - - // Convert 'LogInstantMessages' into 'KeepConversationLogTranscripts' for backward compatibility (CHUI-743). - LLControlVariablePtr logInstantMessagesControl = gSavedPerAccountSettings.getControl("LogInstantMessages"); - if (logInstantMessagesControl.notNull()) - { - gSavedPerAccountSettings.setS32("KeepConversationLogTranscripts", logInstantMessagesControl->getValue() ? 2 : 1); - } - - // Need to set the LastLogoff time here if we don't have one. LastLogoff is used for "Recent Items" calculation - // and startup time is close enough if we don't have a real value. - if (gSavedPerAccountSettings.getU32("LastLogoff") == 0) - { - gSavedPerAccountSettings.setU32("LastLogoff", time_corrected()); - } - - //Default the path if one isn't set. - // *NOTE: unable to check variable differ from "InstantMessageLogPath" because it was - // provided in pre 2.0 viewer. See EXT-6661 - if (gSavedPerAccountSettings.getString("InstantMessageLogPath").empty()) - { - gDirUtilp->setChatLogsDir(gDirUtilp->getOSUserAppDir()); - gSavedPerAccountSettings.setString("InstantMessageLogPath", gDirUtilp->getChatLogsDir()); - } - else - { - gDirUtilp->setChatLogsDir(gSavedPerAccountSettings.getString("InstantMessageLogPath")); - } - gDirUtilp->setPerAccountChatLogsDir(userid); - - LLFile::mkdir(gDirUtilp->getChatLogsDir()); - LLFile::mkdir(gDirUtilp->getPerAccountChatLogsDir()); - - if (show_connect_box) - { - LLSLURL slurl; - //LLPanelLogin::closePanel(); - } - - - // Load URL History File - LLURLHistory::loadFile("url_history.xml"); - // Load location history - LLLocationHistory::getInstance()->load(); - - // Load Avatars icons cache - LLAvatarIconIDCache::getInstance()->load(); - - LLRenderMuteList::getInstance()->loadFromFile(); - - //------------------------------------------------- - // Handle startup progress screen - //------------------------------------------------- - - // on startup the user can request to go to their home, - // their last location, or some URL "-url //sim/x/y[/z]" - // All accounts have both a home and a last location, and we don't support - // more locations than that. Choose the appropriate one. JC - switch (LLStartUp::getStartSLURL().getType()) - { - case LLSLURL::LOCATION: - agent_location_id = START_LOCATION_ID_URL; - break; - case LLSLURL::LAST_LOCATION: - agent_location_id = START_LOCATION_ID_LAST; - break; - default: - agent_location_id = START_LOCATION_ID_HOME; - break; - } - - gViewerWindow->getWindow()->setCursor(UI_CURSOR_WAIT); - - // Display the startup progress bar. - gViewerWindow->initTextures(agent_location_id); - gViewerWindow->setShowProgress(true); - gViewerWindow->setProgressCancelButtonVisible(true, LLTrans::getString("Quit")); - - gViewerWindow->revealIntroPanel(); - - LLStartUp::setStartupState( STATE_LOGIN_AUTH_INIT ); - - return false; - } - - if(STATE_LOGIN_AUTH_INIT == LLStartUp::getStartupState()) - { - gDebugInfo["GridName"] = LLGridManager::getInstance()->getGridId(); - - // Update progress status and the display loop. - auth_desc = LLTrans::getString("LoginInProgress"); - set_startup_status(progress, auth_desc, auth_message); - progress += 0.02f; - display_startup(); - - // Setting initial values... - LLLoginInstance* login = LLLoginInstance::getInstance(); - login->setNotificationsInterface(LLNotifications::getInstance()); - - login->setSerialNumber(LLAppViewer::instance()->getSerialNumber()); - login->setLastExecEvent(gLastExecEvent); - login->setLastExecDuration(gLastExecDuration); - - // This call to LLLoginInstance::connect() starts the - // authentication process. - login->connect(gUserCredential); - - LLStartUp::setStartupState( STATE_LOGIN_CURL_UNSTUCK ); - return false; - } - - if(STATE_LOGIN_CURL_UNSTUCK == LLStartUp::getStartupState()) - { - // If we get here we have gotten past the potential stall - // in curl, so take "may appear frozen" out of progress bar. JC - auth_desc = LLTrans::getString("LoginInProgressNoFrozen"); - set_startup_status(progress, auth_desc, auth_message); - - LLStartUp::setStartupState( STATE_LOGIN_PROCESS_RESPONSE ); - return false; - } - - if(STATE_LOGIN_PROCESS_RESPONSE == LLStartUp::getStartupState()) - { - // Generic failure message - std::ostringstream emsg; - emsg << LLTrans::getString("LoginFailedHeader") << "\n"; - if(LLLoginInstance::getInstance()->authFailure()) - { - LL_INFOS("LLStartup") << "Login failed, LLLoginInstance::getResponse(): " - << LLLoginInstance::getInstance()->getResponse() << LL_ENDL; - LLSD response = LLLoginInstance::getInstance()->getResponse(); - // Still have error conditions that may need some - // sort of handling - dig up specific message - std::string reason_response = response["reason"]; - std::string message_response = response["message"]; - std::string message_id = response["message_id"]; - std::string message; // actual string to show the user - - bool localized_by_id = false; - if(!message_id.empty()) - { - LLSD message_args = response["message_args"]; - if (message_args.has("TIME") - && (message_id == "LoginFailedAcountSuspended" - || message_id == "LoginFailedAccountMaintenance")) - { - LLDate date; - std::string time_string; - if (date.fromString(message_args["TIME"].asString())) - { - LLSD args; - args["datetime"] = (S32)date.secondsSinceEpoch(); - LLTrans::findString(time_string, "LocalTime", args); - } - else - { - time_string = message_args["TIME"].asString() + " " + LLTrans::getString("PacificTime"); - } - - message_args["TIME"] = time_string; - } - // message will be filled in with the template and arguments - if (LLTrans::findString(message, message_id, message_args)) - { - localized_by_id = true; - } - } - - if(!localized_by_id && !message_response.empty()) - { - // *HACK: "no_inventory_host" sent as the message itself. - // Remove this clause when server is sending message_id as well. - message = LLAgent::sTeleportErrorMessages[ message_response ]; - } - - if (message.empty()) - { - // Fallback to server-supplied string; necessary since server - // may add strings that this viewer is not yet aware of - message = message_response; - } - - emsg << message; - - - if(reason_response == "key") - { - // Couldn't login because user/password is wrong - // Clear the credential - gUserCredential->clearAuthenticator(); - } - - if(reason_response == "update" - || reason_response == "optional") - { - // In the case of a needed update, quit. - // Its either downloading or declined. - // If optional was skipped this case shouldn't - // be reached. - - LL_INFOS("LLStartup") << "Forcing a quit due to update." << LL_ENDL; - LLLoginInstance::getInstance()->disconnect(); - LLAppViewer::instance()->forceQuit(); - } - else - { - if (reason_response != "tos" && reason_response != "mfa_challenge") - { - // Don't pop up a notification in the TOS or MFA cases because - // the specialized floater has already scolded the user. - std::string error_code; - if(response.has("errorcode")) - { - error_code = response["errorcode"].asString(); - } - if ((reason_response == "CURLError") && - (error_code == "SSL_CACERT" || error_code == "SSL_PEER_CERTIFICATE") && - response.has("certificate")) - { - // This was a certificate error, so grab the certificate - // and throw up the appropriate dialog. - LLPointer certificate; - try - { - certificate = gSecAPIHandler->getCertificate(response["certificate"]); - } - catch (LLCertException &cert_exception) - { - LL_WARNS("LLStartup", "SECAPI") << "Caught " << cert_exception.what() << " certificate expception on getCertificate("<< response["certificate"] << ")" << LL_ENDL; - LLSD args; - args["REASON"] = LLTrans::getString(cert_exception.what()); - - LLNotificationsUtil::add("GeneralCertificateErrorShort", args, response, - general_cert_done); - - reset_login(); - gSavedSettings.setBOOL("AutoLogin", false); - show_connect_box = true; - } - if(certificate) - { - LLSD args = transform_cert_args(certificate); - - if(error_code == "SSL_CACERT") - { - // if we are handling an untrusted CA, throw up the dialog - // with the 'trust this CA' button. - LLNotificationsUtil::add("TrustCertificateError", args, response, - trust_cert_done); - - show_connect_box = true; - } - else - { - // the certificate exception returns a unique string for each type of exception. - // we grab this string via the LLUserAuth object, and use that to grab the localized - // string. - args["REASON"] = LLTrans::getString(message_response); - - LLNotificationsUtil::add("GeneralCertificateError", args, response, - general_cert_done); - - reset_login(); - gSavedSettings.setBOOL("AutoLogin", false); - show_connect_box = true; - - } - - } - } - else if (reason_response == "BadType") - { - LLNotificationsUtil::add("LoginFailedToParse", LLSD(), LLSD(), login_alert_done); - } - else if (!message.empty()) - { - // This wasn't a certificate error, so throw up the normal - // notificatioin message. - LLSD args; - args["ERROR_MESSAGE"] = emsg.str(); - LL_INFOS("LLStartup") << "Notification: " << args << LL_ENDL; - LLNotificationsUtil::add("ErrorMessage", args, LLSD(), login_alert_done); - } - } - transition_back_to_login_panel(emsg.str()); - show_connect_box = true; - } - } - else if(LLLoginInstance::getInstance()->authSuccess()) - { - if(process_login_success_response()) - { - // Pass the user information to the voice chat server interface. - LLVoiceClient::getInstance()->userAuthorized(gUserCredential->userID(), gAgentID); - // create the default proximal channel - LLVoiceChannel::initClass(); - LLStartUp::setStartupState( STATE_WORLD_INIT); - LLTrace::get_frame_recording().reset(); - } - else - { - LLSD args; - args["ERROR_MESSAGE"] = emsg.str(); - LL_INFOS("LLStartup") << "Notification: " << args << LL_ENDL; - LLNotificationsUtil::add("ErrorMessage", args, LLSD(), login_alert_done); - transition_back_to_login_panel(emsg.str()); - show_connect_box = true; - return false; - } - } - return false; - } - - //--------------------------------------------------------------------- - // World Init - //--------------------------------------------------------------------- - if (STATE_WORLD_INIT == LLStartUp::getStartupState()) - { - set_startup_status(0.30f, LLTrans::getString("LoginInitializingWorld"), gAgent.mMOTD); - display_startup(); - // We should have an agent id by this point. - llassert(!(gAgentID == LLUUID::null)); - - // Finish agent initialization. (Requires gSavedSettings, builds camera) - gAgent.init(); - display_startup(); - gAgentCamera.init(); - display_startup(); - display_startup(); - - // Since we connected, save off the settings so the user doesn't have to - // type the name/password again if we crash. - gSavedSettings.saveToFile(gSavedSettings.getString("ClientSettingsFile"), true); - LLUIColorTable::instance().saveUserSettings(); - - display_startup(); - - // - // Initialize classes w/graphics stuff. - // - LLViewerStatsRecorder::instance(); // Since textures work in threads - LLSurface::initClasses(); - display_startup(); - - display_startup(); - - LLDrawable::initClass(); - display_startup(); - - // init the shader managers - LLPostProcess::initClass(); - display_startup(); - - LLAvatarAppearance::initClass("avatar_lad.xml","avatar_skeleton.xml"); - display_startup(); - - LLViewerObject::initVOClasses(); - display_startup(); - - // Initialize all our tools. Must be done after saved settings loaded. - // NOTE: This also is where gToolMgr used to be instantiated before being turned into a singleton. - LLToolMgr::getInstance()->initTools(); - display_startup(); - - // Pre-load floaters, like the world map, that are slow to spawn - // due to XML complexity. - gViewerWindow->initWorldUI(); - - display_startup(); - - // This is where we used to initialize gWorldp. Original comment said: - // World initialization must be done after above window init - - // User might have overridden far clip - LLWorld::getInstance()->setLandFarClip(gAgentCamera.mDrawDistance); - display_startup(); - // Before we create the first region, we need to set the agent's mOriginGlobal - // This is necessary because creating objects before this is set will result in a - // bad mPositionAgent cache. - - gAgent.initOriginGlobal(from_region_handle(gFirstSimHandle)); - display_startup(); - - LLWorld::getInstance()->addRegion(gFirstSimHandle, gFirstSim); - display_startup(); - - LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle(gFirstSimHandle); - LL_INFOS("AppInit") << "Adding initial simulator " << regionp->getOriginGlobal() << LL_ENDL; - - LL_DEBUGS("CrossingCaps") << "Calling setSeedCapability from init_idle(). Seed cap == " - << gFirstSimSeedCap << LL_ENDL; - regionp->setSeedCapability(gFirstSimSeedCap); - LL_DEBUGS("AppInit") << "Waiting for seed grant ...." << LL_ENDL; - display_startup(); - // Set agent's initial region to be the one we just created. - gAgent.setRegion(regionp); - display_startup(); - // Set agent's initial position, which will be read by LLVOAvatar when the avatar - // object is created. I think this must be done after setting the region. JC - gAgent.setPositionAgent(agent_start_position_region); - - display_startup(); - LLStartUp::initExperiences(); - - display_startup(); - - // If logging should be enebled, turns it on and loads history from disk - // Note: does not happen on init of singleton because preferences can use - // this instance without logging in - LLConversationLog::getInstance()->initLoggingState(); - - LLStartUp::setStartupState( STATE_MULTIMEDIA_INIT ); - - return false; - } - - - //--------------------------------------------------------------------- - // Load QuickTime/GStreamer and other multimedia engines, can be slow. - // Do it while we're waiting on the network for our seed capability. JC - //--------------------------------------------------------------------- - if (STATE_MULTIMEDIA_INIT == LLStartUp::getStartupState()) - { - LLStartUp::multimediaInit(); - LLStartUp::setStartupState( STATE_FONT_INIT ); - display_startup(); - return false; - } - - // Loading fonts takes several seconds - if (STATE_FONT_INIT == LLStartUp::getStartupState()) - { - LLStartUp::fontInit(); - LLStartUp::setStartupState( STATE_SEED_GRANTED_WAIT ); - display_startup(); - return false; - } - - //--------------------------------------------------------------------- - // Wait for Seed Cap Grant - //--------------------------------------------------------------------- - if(STATE_SEED_GRANTED_WAIT == LLStartUp::getStartupState()) - { - LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle(gFirstSimHandle); - if (regionp->capabilitiesReceived()) - { - LLStartUp::setStartupState( STATE_SEED_CAP_GRANTED ); - } - else if (regionp->capabilitiesError()) - { - LL_WARNS("AppInit") << "Failed to get capabilities. Backing up to login screen!" << LL_ENDL; - if (gRememberPassword) - { - LLNotificationsUtil::add("LoginPacketNeverReceived", LLSD(), LLSD(), login_alert_status); - } - else - { - LLNotificationsUtil::add("LoginPacketNeverReceivedNoTP", LLSD(), LLSD(), login_alert_status); - } - reset_login(); - } - else - { - U32 num_retries = regionp->getNumSeedCapRetries(); - if (num_retries > MAX_SEED_CAP_ATTEMPTS_BEFORE_ABORT) - { - LL_WARNS("AppInit") << "Failed to get capabilities. Backing up to login screen!" << LL_ENDL; - if (gRememberPassword) - { - LLNotificationsUtil::add("LoginPacketNeverReceived", LLSD(), LLSD(), login_alert_status); - } - else - { - LLNotificationsUtil::add("LoginPacketNeverReceivedNoTP", LLSD(), LLSD(), login_alert_status); - } - reset_login(); - } - else if (num_retries > 0) - { - LLStringUtil::format_map_t args; - args["[NUMBER]"] = llformat("%d", num_retries + 1); - set_startup_status(0.4f, LLTrans::getString("LoginRetrySeedCapGrant", args), gAgent.mMOTD.c_str()); - } - else - { - set_startup_status(0.4f, LLTrans::getString("LoginRequestSeedCapGrant"), gAgent.mMOTD.c_str()); - } - } - display_startup(); - return false; - } - - - //--------------------------------------------------------------------- - // Seed Capability Granted - // no newMessage calls should happen before this point - //--------------------------------------------------------------------- - if (STATE_SEED_CAP_GRANTED == LLStartUp::getStartupState()) - { - display_startup(); - - // These textures are not warrantied to be cached, so needs - // to hapen with caps granted - gTextureList.doPrefetchImages(); - - // will init images, should be done with caps, but before gSky.init() - LLEnvironment::getInstance()->initSingleton(); - - display_startup(); - update_texture_fetch(); - display_startup(); - - if ( gViewerWindow != NULL) - { // This isn't the first logon attempt, so show the UI - gViewerWindow->setNormalControlsVisible( true ); - } - gLoginMenuBarView->setVisible( false ); - gLoginMenuBarView->setEnabled( false ); - display_startup(); - - // direct logging to the debug console's line buffer - LLError::logToFixedBuffer(gDebugView->mDebugConsolep); - display_startup(); - - // set initial visibility of debug console - gDebugView->mDebugConsolep->setVisible(gSavedSettings.getBOOL("ShowDebugConsole")); - display_startup(); - - // - // Set message handlers - // - LL_INFOS("AppInit") << "Initializing communications..." << LL_ENDL; - - // register callbacks for messages. . . do this after initial handshake to make sure that we don't catch any unwanted - register_viewer_callbacks(gMessageSystem); - display_startup(); - - // Debugging info parameters - gMessageSystem->setMaxMessageTime( 0.5f ); // Spam if decoding all msgs takes more than 500 ms - display_startup(); - - #ifndef LL_RELEASE_FOR_DOWNLOAD - gMessageSystem->setTimeDecodes( true ); // Time the decode of each msg - gMessageSystem->setTimeDecodesSpamThreshold( 0.05f ); // Spam if a single msg takes over 50ms to decode - #endif - display_startup(); - - gXferManager->registerCallbacks(gMessageSystem); - display_startup(); - - LLStartUp::initNameCache(); - display_startup(); - - // update the voice settings *after* gCacheName initialization - // so that we can construct voice UI that relies on the name cache - if (LLVoiceClient::instanceExists()) - { - LLVoiceClient::getInstance()->updateSettings(); - } - display_startup(); - - // create a container's instance for start a controlling conversation windows - // by the voice's events - LLFloaterIMContainer::getInstance(); - if (gSavedSettings.getS32("ParcelMediaAutoPlayEnable") == 2) - { - LLViewerParcelAskPlay::getInstance()->loadSettings(); - } - - gAgent.addRegionChangedCallback(boost::bind(&LLPerfStats::StatsRecorder::clearStats)); - - // *Note: this is where gWorldMap used to be initialized. - - // register null callbacks for audio until the audio system is initialized - gMessageSystem->setHandlerFuncFast(_PREHASH_SoundTrigger, null_message_callback, NULL); - gMessageSystem->setHandlerFuncFast(_PREHASH_AttachedSound, null_message_callback, NULL); - display_startup(); - - //reset statistics - LLViewerStats::instance().resetStats(); - - display_startup(); - // - // Set up region and surface defaults - // - - - // Sets up the parameters for the first simulator - - LL_DEBUGS("AppInit") << "Initializing camera..." << LL_ENDL; - gFrameTime = totalTime(); - F32Seconds last_time = gFrameTimeSeconds; - gFrameTimeSeconds = (gFrameTime - gStartTime); - - gFrameIntervalSeconds = gFrameTimeSeconds - last_time; - if (gFrameIntervalSeconds < 0.f) - { - gFrameIntervalSeconds = 0.f; - } - - // Make sure agent knows correct aspect ratio - // FOV limits depend upon aspect ratio so this needs to happen before initializing the FOV below - LLViewerCamera::getInstance()->setViewHeightInPixels(gViewerWindow->getWorldViewHeightRaw()); - LLViewerCamera::getInstance()->setAspect(gViewerWindow->getWorldViewAspectRatio()); - // Initialize FOV - LLViewerCamera::getInstance()->setDefaultFOV(gSavedSettings.getF32("CameraAngle")); - display_startup(); - - // Move agent to starting location. The position handed to us by - // the space server is in global coordinates, but the agent frame - // is in region local coordinates. Therefore, we need to adjust - // the coordinates handed to us to fit in the local region. - - gAgent.setPositionAgent(agent_start_position_region); - gAgent.resetAxes(gAgentStartLookAt); - gAgentCamera.stopCameraAnimation(); - gAgentCamera.resetCamera(); - display_startup(); - - // Initialize global class data needed for surfaces (i.e. textures) - LL_DEBUGS("AppInit") << "Initializing sky..." << LL_ENDL; - // Initialize all of the viewer object classes for the first time (doing things like texture fetches. - LLGLState::checkStates(); - - gSky.init(); - - LLGLState::checkStates(); - - display_startup(); - - LL_DEBUGS("AppInit") << "Decoding images..." << LL_ENDL; - // For all images pre-loaded into viewer cache, init - // priorities and fetching using decodeAllImages. - // Most of the fetching and decoding likely to be done - // by update_texture_fetch() later, while viewer waits. - // - // Need to do this AFTER we init the sky - const S32 DECODE_TIME_SEC = 2; - for (int i = 0; i < DECODE_TIME_SEC; i++) - { - F32 frac = (F32)i / (F32)DECODE_TIME_SEC; - set_startup_status(0.45f + frac*0.1f, LLTrans::getString("LoginDecodingImages"), gAgent.mMOTD); - display_startup(); - gTextureList.decodeAllImages(1.f); - } - LLStartUp::setStartupState( STATE_WORLD_WAIT ); - - display_startup(); - - // JC - Do this as late as possible to increase likelihood Purify - // will run. - LLMessageSystem* msg = gMessageSystem; - if (!msg->mOurCircuitCode) - { - LL_WARNS("AppInit") << "Attempting to connect to simulator with a zero circuit code!" << LL_ENDL; - } - - gUseCircuitCallbackCalled = false; - - msg->enableCircuit(gFirstSim, true); - // now, use the circuit info to tell simulator about us! - LL_INFOS("AppInit") << "viewer: UserLoginLocationReply() Enabling " << gFirstSim << " with code " << msg->mOurCircuitCode << LL_ENDL; - msg->newMessageFast(_PREHASH_UseCircuitCode); - msg->nextBlockFast(_PREHASH_CircuitCode); - msg->addU32Fast(_PREHASH_Code, msg->mOurCircuitCode); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addUUIDFast(_PREHASH_ID, gAgent.getID()); - msg->sendReliable( - gFirstSim, - gSavedSettings.getS32("UseCircuitCodeMaxRetries"), - false, - (F32Seconds)gSavedSettings.getF32("UseCircuitCodeTimeout"), - use_circuit_callback, - NULL); - - timeout.reset(); - display_startup(); - - return false; - } - - //--------------------------------------------------------------------- - // World Wait - //--------------------------------------------------------------------- - if(STATE_WORLD_WAIT == LLStartUp::getStartupState()) - { - LL_DEBUGS("AppInit") << "Waiting for simulator ack...." << LL_ENDL; - set_startup_status(0.59f, LLTrans::getString("LoginWaitingForRegionHandshake"), gAgent.mMOTD); - if(gGotUseCircuitCodeAck) - { - LLStartUp::setStartupState( STATE_AGENT_SEND ); - } - pump_idle_startup_network(); - return false; - } - - //--------------------------------------------------------------------- - // Agent Send - //--------------------------------------------------------------------- - if (STATE_AGENT_SEND == LLStartUp::getStartupState()) - { - LL_DEBUGS("AppInit") << "Connecting to region..." << LL_ENDL; - set_startup_status(0.60f, LLTrans::getString("LoginConnectingToRegion"), gAgent.mMOTD); - display_startup(); - // register with the message system so it knows we're - // expecting this message - LLMessageSystem* msg = gMessageSystem; - msg->setHandlerFuncFast( - _PREHASH_AgentMovementComplete, - process_agent_movement_complete); - LLViewerRegion* regionp = gAgent.getRegion(); - if(regionp) - { - send_complete_agent_movement(regionp->getHost()); - gAssetStorage->setUpstream(regionp->getHost()); - gCacheName->setUpstream(regionp->getHost()); - } - display_startup(); - - // Create login effect - // But not on first login, because you can't see your avatar then - if (!gAgent.isFirstLogin()) - { - LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_POINT, true); - effectp->setPositionGlobal(gAgent.getPositionGlobal()); - effectp->setColor(LLColor4U(gAgent.getEffectColor())); - LLHUDManager::getInstance()->sendEffects(); - } - - LLStartUp::setStartupState( STATE_AGENT_WAIT ); // Go to STATE_AGENT_WAIT - - timeout.reset(); - display_startup(); - return false; - } - - //--------------------------------------------------------------------- - // Agent Wait - //--------------------------------------------------------------------- - if (STATE_AGENT_WAIT == LLStartUp::getStartupState()) - { - { - LockMessageChecker lmc(gMessageSystem); - while (lmc.checkAllMessages(gFrameCount, gServicePump)) - { - if (gAgentMovementCompleted) - { - // Sometimes we have more than one message in the - // queue. break out of this loop and continue - // processing. If we don't, then this could skip one - // or more login steps. - break; - } - else - { - LL_DEBUGS("AppInit") << "Awaiting AvatarInitComplete, got " - << gMessageSystem->getMessageName() << LL_ENDL; - } - display_startup(); - } - lmc.processAcks(); - } - - display_startup(); - - if (gAgentMovementCompleted) - { - LLStartUp::setStartupState( STATE_INVENTORY_SEND ); - } - display_startup(); - - if (!gAgentMovementCompleted && timeout.getElapsedTimeF32() > STATE_AGENT_WAIT_TIMEOUT) - { - LL_WARNS("AppInit") << "Backing up to login screen!" << LL_ENDL; - if (gRememberPassword) - { - LLNotificationsUtil::add("LoginPacketNeverReceived", LLSD(), LLSD(), login_alert_status); - } - else - { - LLNotificationsUtil::add("LoginPacketNeverReceivedNoTP", LLSD(), LLSD(), login_alert_status); - } - reset_login(); - } - return false; - } - - //--------------------------------------------------------------------- - // Inventory Send - //--------------------------------------------------------------------- - if (STATE_INVENTORY_SEND == LLStartUp::getStartupState()) - { - LL_PROFILE_ZONE_NAMED("State inventory send") - display_startup(); - - // request mute list - LL_INFOS() << "Requesting Mute List" << LL_ENDL; - LLMuteList::getInstance()->requestFromServer(gAgent.getID()); - - // Get L$ and ownership credit information - LL_INFOS() << "Requesting Money Balance" << LL_ENDL; - LLStatusBar::sendMoneyBalanceRequest(); - - display_startup(); - - // Inform simulator of our language preference - LLAgentLanguage::update(); - - display_startup(); - // unpack thin inventory - LLSD response = LLLoginInstance::getInstance()->getResponse(); - //bool dump_buffer = false; - - LLSD inv_lib_root = response["inventory-lib-root"]; - if(inv_lib_root.isDefined()) - { - // should only be one - LLSD id = inv_lib_root[0]["folder_id"]; - if(id.isDefined()) - { - gInventory.setLibraryRootFolderID(id.asUUID()); - } - } - display_startup(); - - LLSD inv_lib_owner = response["inventory-lib-owner"]; - if(inv_lib_owner.isDefined()) - { - // should only be one - LLSD id = inv_lib_owner[0]["agent_id"]; - if(id.isDefined()) - { - gInventory.setLibraryOwnerID(LLUUID(id.asUUID())); - } - } - display_startup(); - LLStartUp::setStartupState(STATE_INVENTORY_SKEL); - display_startup(); - return false; - } - - if (STATE_INVENTORY_SKEL == LLStartUp::getStartupState()) - { - LL_PROFILE_ZONE_NAMED("State inventory load skeleton") - - LLSD response = LLLoginInstance::getInstance()->getResponse(); - - LLSD inv_skel_lib = response["inventory-skel-lib"]; - if (inv_skel_lib.isDefined() && gInventory.getLibraryOwnerID().notNull()) - { - LL_PROFILE_ZONE_NAMED("load library inv") - if (!gInventory.loadSkeleton(inv_skel_lib, gInventory.getLibraryOwnerID())) - { - LL_WARNS("AppInit") << "Problem loading inventory-skel-lib" << LL_ENDL; - } - } - display_startup(); - - LLSD inv_skeleton = response["inventory-skeleton"]; - if (inv_skeleton.isDefined()) - { - LL_PROFILE_ZONE_NAMED("load personal inv") - if (!gInventory.loadSkeleton(inv_skeleton, gAgent.getID())) - { - LL_WARNS("AppInit") << "Problem loading inventory-skel-targets" << LL_ENDL; - } - } - display_startup(); - LLStartUp::setStartupState(STATE_INVENTORY_SEND2); - display_startup(); - return false; - } - - if (STATE_INVENTORY_SEND2 == LLStartUp::getStartupState()) - { - LL_PROFILE_ZONE_NAMED("State inventory send2") - - LLSD response = LLLoginInstance::getInstance()->getResponse(); - - LLSD inv_basic = response["inventory-basic"]; - if(inv_basic.isDefined()) - { - LL_INFOS() << "Basic inventory root folder id is " << inv_basic["folder_id"] << LL_ENDL; - } - - LLSD buddy_list = response["buddy-list"]; - if(buddy_list.isDefined()) - { - LLAvatarTracker::buddy_map_t list; - LLUUID agent_id; - S32 has_rights = 0, given_rights = 0; - for(LLSD::array_const_iterator it = buddy_list.beginArray(), - end = buddy_list.endArray(); it != end; ++it) - { - LLSD buddy_id = (*it)["buddy_id"]; - if(buddy_id.isDefined()) - { - agent_id = buddy_id.asUUID(); - } - - LLSD buddy_rights_has = (*it)["buddy_rights_has"]; - if(buddy_rights_has.isDefined()) - { - has_rights = buddy_rights_has.asInteger(); - } - - LLSD buddy_rights_given = (*it)["buddy_rights_given"]; - if(buddy_rights_given.isDefined()) - { - given_rights = buddy_rights_given.asInteger(); - } - - list[agent_id] = new LLRelationship(given_rights, has_rights, false); - } - LLAvatarTracker::instance().addBuddyList(list); - display_startup(); - } - - bool show_hud = false; - LLSD tutorial_setting = response["tutorial_setting"]; - if(tutorial_setting.isDefined()) - { - for(LLSD::array_const_iterator it = tutorial_setting.beginArray(), - end = tutorial_setting.endArray(); it != end; ++it) - { - LLSD tutorial_url = (*it)["tutorial_url"]; - if(tutorial_url.isDefined()) - { - // Tutorial floater will append language code - gSavedSettings.setString("TutorialURL", tutorial_url.asString()); - } - - // For Viewer 2.0 we are not using the web-based tutorial - // If we reverse that decision, put this code back and use - // login.cgi to send a different URL with content that matches - // the Viewer 2.0 UI. - //LLSD use_tutorial = (*it)["use_tutorial"]; - //if(use_tutorial.asString() == "true") - //{ - // show_hud = true; - //} - } - } - display_startup(); - - // Either we want to show tutorial because this is the first login - // to a Linden Help Island or the user quit with the tutorial - // visible. JC - if (show_hud || gSavedSettings.getBOOL("ShowTutorial")) - { - LLFloaterReg::showInstance("hud", LLSD(), false); - } - display_startup(); - - LLSD event_notifications = response["event_notifications"]; - if(event_notifications.isDefined()) - { - gEventNotifier.load(event_notifications); - } - display_startup(); - - LLSD classified_categories = response["classified_categories"]; - if(classified_categories.isDefined()) - { - LLClassifiedInfo::loadCategories(classified_categories); - } - display_startup(); - - // This method MUST be called before gInventory.findCategoryUUIDForType because of - // gInventory.mIsAgentInvUsable is set to true in the gInventory.buildParentChildMap. - gInventory.buildParentChildMap(); - - // If buildParentChildMap succeeded, inventory will now be in - // a usable state and gInventory.isInventoryUsable() will be - // true. - - // if inventory is unusable, show warning. - if (!gInventory.isInventoryUsable()) - { - LLNotificationsUtil::add("InventoryUnusable"); - } - - LLInventoryModelBackgroundFetch::instance().start(); - gInventory.createCommonSystemCategories(); - LLStartUp::setStartupState(STATE_INVENTORY_CALLBACKS ); - display_startup(); - - return false; - } - - //--------------------------------------------------------------------- - // STATE_INVENTORY_CALLBACKS - //--------------------------------------------------------------------- - if (STATE_INVENTORY_CALLBACKS == LLStartUp::getStartupState()) - { - if (!LLInventoryModel::isSysFoldersReady()) - { - display_startup(); - return false; - } - - LLInventoryModelBackgroundFetch::instance().start(); - LLAppearanceMgr::instance().initCOFID(); - LLUUID cof_id = LLAppearanceMgr::instance().getCOF(); - LLViewerInventoryCategory* cof = gInventory.getCategory(cof_id); - if (cof - && cof->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) - { - // Special case, dupplicate request prevention. - // Cof folder will be requested via FetchCOF - // in appearance manager, prevent recursive fetch - cof->setFetching(LLViewerInventoryCategory::FETCH_RECURSIVE); - } - - - // It's debatable whether this flag is a good idea - sets all - // bits, and in general it isn't true that inventory - // initialization generates all types of changes. Maybe add an - // INITIALIZE mask bit instead? - gInventory.addChangedMask(LLInventoryObserver::ALL, LLUUID::null); - gInventory.notifyObservers(); - - display_startup(); - - // set up callbacks - LL_INFOS() << "Registering Callbacks" << LL_ENDL; - LLMessageSystem* msg = gMessageSystem; - LL_INFOS() << " Inventory" << LL_ENDL; - LLInventoryModel::registerCallbacks(msg); - LL_INFOS() << " AvatarTracker" << LL_ENDL; - LLAvatarTracker::instance().registerCallbacks(msg); - LL_INFOS() << " Landmark" << LL_ENDL; - LLLandmark::registerCallbacks(msg); - display_startup(); - - // request all group information - LL_INFOS() << "Requesting Agent Data" << LL_ENDL; - gAgent.sendAgentDataUpdateRequest(); - display_startup(); - // Create the inventory views - LL_INFOS() << "Creating Inventory Views" << LL_ENDL; - LLFloaterReg::getInstance("inventory"); - display_startup(); - LLStartUp::setStartupState( STATE_MISC ); - display_startup(); - - return false; - } - - - //--------------------------------------------------------------------- - // Misc - //--------------------------------------------------------------------- - if (STATE_MISC == LLStartUp::getStartupState()) - { - // We have a region, and just did a big inventory download. - // We can estimate the user's connection speed, and set their - // max bandwidth accordingly. JC - if (gSavedSettings.getBOOL("FirstLoginThisInstall")) - { - // This is actually a pessimistic computation, because TCP may not have enough - // time to ramp up on the (small) default inventory file to truly measure max - // bandwidth. JC - F64 rate_bps = LLLoginInstance::getInstance()->getLastTransferRateBPS(); - const F32 FAST_RATE_BPS = 600.f * 1024.f; - const F32 FASTER_RATE_BPS = 750.f * 1024.f; - F32 max_bandwidth = gViewerThrottle.getMaxBandwidth(); - if (rate_bps > FASTER_RATE_BPS - && rate_bps > max_bandwidth) - { - LL_DEBUGS("AppInit") << "Fast network connection, increasing max bandwidth to " - << FASTER_RATE_BPS/1024.f - << " kbps" << LL_ENDL; - gViewerThrottle.setMaxBandwidth(FASTER_RATE_BPS / 1024.f); - } - else if (rate_bps > FAST_RATE_BPS - && rate_bps > max_bandwidth) - { - LL_DEBUGS("AppInit") << "Fast network connection, increasing max bandwidth to " - << FAST_RATE_BPS/1024.f - << " kbps" << LL_ENDL; - gViewerThrottle.setMaxBandwidth(FAST_RATE_BPS / 1024.f); - } - - if (gSavedSettings.getBOOL("ShowHelpOnFirstLogin")) - { - gSavedSettings.setBOOL("HelpFloaterOpen", true); - } - - // Set the show start location to true, now that the user has logged - // on with this install. - gSavedSettings.setBOOL("ShowStartLocation", true); - } - - display_startup(); - - // Load stored local environment if needed. - LLEnvironment::instance().loadFromSettings(); - - // *TODO : Uncomment that line once the whole grid migrated to SLM and suppress it from LLAgent::handleTeleportFinished() (llagent.cpp) - //check_merchant_status(); - - display_startup(); - - if (gSavedSettings.getBOOL("HelpFloaterOpen")) - { - // show default topic - LLViewerHelp::instance().showTopic(""); - } - - display_startup(); - - // We're successfully logged in. - gSavedSettings.setBOOL("FirstLoginThisInstall", false); - - LLFloaterReg::showInitialVisibleInstances(); - - LLFloaterGridStatus::getInstance()->startGridStatusTimer(); - - display_startup(); - - display_startup(); - // JC: Initializing audio requests many sounds for download. - init_audio(); - display_startup(); - - // JC: Initialize "active" gestures. This may also trigger - // many gesture downloads, if this is the user's first - // time on this machine or -purge has been run. - LLSD gesture_options - = LLLoginInstance::getInstance()->getResponse("gestures"); - if (gesture_options.isDefined()) - { - LL_DEBUGS("AppInit") << "Gesture Manager loading " << gesture_options.size() - << LL_ENDL; - uuid_vec_t item_ids; - for(LLSD::array_const_iterator resp_it = gesture_options.beginArray(), - end = gesture_options.endArray(); resp_it != end; ++resp_it) - { - // If the id is not specifed in the LLSD, - // the LLSD operator[]() will return a null LLUUID. - LLUUID item_id = (*resp_it)["item_id"]; - LLUUID asset_id = (*resp_it)["asset_id"]; - - if (item_id.notNull() && asset_id.notNull()) - { - // Could schedule and delay these for later. - const bool no_inform_server = false; - const bool no_deactivate_similar = false; - LLGestureMgr::instance().activateGestureWithAsset(item_id, asset_id, - no_inform_server, - no_deactivate_similar); - // We need to fetch the inventory items for these gestures - // so we have the names to populate the UI. - item_ids.push_back(item_id); - } - } - // no need to add gesture to inventory observer, it's already made in constructor - LLGestureMgr::instance().setFetchIDs(item_ids); - LLGestureMgr::instance().startFetch(); - } - gDisplaySwapBuffers = true; - display_startup(); - - LLMessageSystem* msg = gMessageSystem; - msg->setHandlerFuncFast(_PREHASH_SoundTrigger, process_sound_trigger); - msg->setHandlerFuncFast(_PREHASH_PreloadSound, process_preload_sound); - msg->setHandlerFuncFast(_PREHASH_AttachedSound, process_attached_sound); - msg->setHandlerFuncFast(_PREHASH_AttachedSoundGainChange, process_attached_sound_gain_change); - - LL_DEBUGS("AppInit") << "Initialization complete" << LL_ENDL; - - LL_DEBUGS("SceneLoadTiming", "Start") << "Scene Load Started " << LL_ENDL; - gRenderStartTime.reset(); - gForegroundTime.reset(); - - // HACK: Inform simulator of window size. - // Do this here so it's less likely to race with RegisterNewAgent. - // TODO: Put this into RegisterNewAgent - // JC - 7/20/2002 - gViewerWindow->sendShapeToSim(); - - LLPresetsManager::getInstance()->createMissingDefault(PRESETS_CAMERA); - - // The reason we show the alert is because we want to - // reduce confusion for when you log in and your provided - // location is not your expected location. So, if this is - // your first login, then you do not have an expectation, - // thus, do not show this alert. - if (!gAgent.isFirstLogin()) - { - LL_INFOS() << "gAgentStartLocation : " << gAgentStartLocation << LL_ENDL; - LLSLURL start_slurl = LLStartUp::getStartSLURL(); - LL_DEBUGS("AppInit") << "start slurl "< 1.f) && isAgentAvatarValid()) - { - LLStartUp::setStartupState( STATE_WEARABLES_WAIT ); - } - else if (timeout_frac > 10.f) - { - // If we exceed the wait above while isAgentAvatarValid is - // not true yet, we will change startup state and - // eventually (once avatar does get created) wind up at - // the gender chooser. This should occur only in very - // unusual circumstances, so set the timeout fairly high - // to minimize mistaken hits here. - LL_WARNS() << "Wait for valid avatar state exceeded " - << timeout.getElapsedTimeF32() << " will invoke gender chooser" << LL_ENDL; - LLStartUp::setStartupState( STATE_WEARABLES_WAIT ); - } - else - { - update_texture_fetch(); - set_startup_status(0.60f + 0.30f * timeout_frac, - LLTrans::getString("LoginPrecaching"), - gAgent.mMOTD.c_str()); - display_startup(); - } - - return true; - } - - if (STATE_WEARABLES_WAIT == LLStartUp::getStartupState()) - { - static LLFrameTimer wearables_timer; - - const F32 wearables_time = wearables_timer.getElapsedTimeF32(); - const F32 MAX_WEARABLES_TIME = 10.f; - - if (!gAgent.isOutfitChosen() && isAgentAvatarValid()) - { - // No point in waiting for clothing, we don't even know - // what outfit we want. Pop up a gender chooser dialog to - // ask and proceed to draw the world. JC - // - // *NOTE: We might hit this case even if we have an - // initial outfit, but if the load hasn't started - // already then something is wrong so fall back - // to generic outfits. JC - LLNotificationsUtil::add("WelcomeChooseSex", LLSD(), LLSD(), - callback_choose_gender); - LLStartUp::setStartupState( STATE_CLEANUP ); - } - - display_startup(); - - if (gAgent.isOutfitChosen() && (wearables_time > MAX_WEARABLES_TIME)) - { - if (gInventory.isInventoryUsable()) - { - LLNotificationsUtil::add("ClothingLoading"); - } - record(LLStatViewer::LOADING_WEARABLES_LONG_DELAY, wearables_time); - LLStartUp::setStartupState( STATE_CLEANUP ); - } - else if (gAgent.isFirstLogin() - && isAgentAvatarValid() - && gAgentAvatarp->isFullyLoaded()) - { - // wait for avatar to be completely loaded - if (isAgentAvatarValid() - && gAgentAvatarp->isFullyLoaded()) - { - LL_DEBUGS("Avatar") << "avatar fully loaded" << LL_ENDL; - LLStartUp::setStartupState( STATE_CLEANUP ); - return true; - } - } - else - { - // OK to just get the wearables - if ( gAgentWearables.areWearablesLoaded() ) - { - // We have our clothing, proceed. - LL_DEBUGS("Avatar") << "wearables loaded" << LL_ENDL; - LLStartUp::setStartupState( STATE_CLEANUP ); - return true; - } - } - //fall through this frame to STATE_CLEANUP - } - - if (STATE_CLEANUP == LLStartUp::getStartupState()) - { - set_startup_status(1.0, "", ""); - display_startup(); - - if (!mBenefitsSuccessfullyInit) - { - LLNotificationsUtil::add("FailedToGetBenefits", LLSD(), LLSD(), boost::bind(on_benefits_failed_callback, _1, _2)); - } - - // Let the map know about the inventory. - LLFloaterWorldMap* floater_world_map = LLFloaterWorldMap::getInstance(); - if(floater_world_map) - { - floater_world_map->observeInventory(&gInventory); - floater_world_map->observeFriends(); - } - gViewerWindow->showCursor(); - gViewerWindow->getWindow()->resetBusyCount(); - gViewerWindow->getWindow()->setCursor(UI_CURSOR_ARROW); - LL_DEBUGS("AppInit") << "Done releasing bitmap" << LL_ENDL; - //gViewerWindow->revealIntroPanel(); - gViewerWindow->setStartupComplete(); - gViewerWindow->setProgressCancelButtonVisible(false); - display_startup(); - - // We're not away from keyboard, even though login might have taken - // a while. JC - gAgent.clearAFK(); - - // Have the agent start watching the friends list so we can update proxies - gAgent.observeFriends(); - - // Start automatic replay if the flag is set. - if (gSavedSettings.getBOOL("StatsAutoRun") || gAgentPilot.getReplaySession()) - { - LL_DEBUGS("AppInit") << "Starting automatic playback" << LL_ENDL; - gAgentPilot.startPlayback(); - } - - show_debug_menus(); // Debug menu visiblity and First Use trigger - - // If we've got a startup URL, dispatch it - //LLStartUp::dispatchURL(); - - // Retrieve information about the land data - // (just accessing this the first time will fetch it, - // then the data is cached for the viewer's lifetime) - LLProductInfoRequestManager::instance(); - - // *FIX:Mani - What do I do here? - // Need we really clear the Auth response data? - // Clean up the userauth stuff. - // LLUserAuth::getInstance()->reset(); - - LLStartUp::setStartupState( STATE_STARTED ); - display_startup(); - - // Unmute audio if desired and setup volumes. - // This is a not-uncommon crash site, so surround it with - // LL_INFOS() output to aid diagnosis. - LL_INFOS("AppInit") << "Doing first audio_update_volume..." << LL_ENDL; - audio_update_volume(); - LL_INFOS("AppInit") << "Done first audio_update_volume." << LL_ENDL; - - // reset keyboard focus to sane state of pointing at world - gFocusMgr.setKeyboardFocus(NULL); - - LLAppViewer::instance()->handleLoginComplete(); - - LLAgentPicksInfo::getInstance()->requestNumberOfPicks(); - - display_startup(); - - llassert(LLPathfindingManager::getInstance() != NULL); - LLPathfindingManager::getInstance()->initSystem(); - - gAgentAvatarp->sendHoverHeight(); - - // look for parcels we own - send_places_query(LLUUID::null, - LLUUID::null, - "", - DFQ_AGENT_OWNED, - LLParcel::C_ANY, - ""); - - LLUIUsage::instance().clear(); - - LLPerfStats::StatsRecorder::setAutotuneInit(); - - return true; - } - - return true; -} - -// -// local function definition -// - -void login_show() -{ - LL_INFOS("AppInit") << "Initializing Login Screen" << LL_ENDL; - - // Hide the toolbars: may happen to come back here if login fails after login agent but before login in region - if (gToolBarView) - { - gToolBarView->setVisible(false); - } - - LLPanelLogin::show( gViewerWindow->getWindowRectScaled(), login_callback, NULL ); -} - -// Callback for when login screen is closed. Option 0 = connect, option 1 = quit. -void login_callback(S32 option, void *userdata) -{ - const S32 CONNECT_OPTION = 0; - const S32 QUIT_OPTION = 1; - - if (CONNECT_OPTION == option) - { - LLStartUp::setStartupState( STATE_LOGIN_CLEANUP ); - return; - } - else if (QUIT_OPTION == option) // *TODO: THIS CODE SEEMS TO BE UNREACHABLE!!!!! login_callback is never called with option equal to QUIT_OPTION - { - if (!gSavedSettings.getBOOL("RememberPassword")) - { - // turn off the setting and write out to disk - gSavedSettings.saveToFile( gSavedSettings.getString("ClientSettingsFile") , true ); - LLUIColorTable::instance().saveUserSettings(); - } - - // Next iteration through main loop should shut down the app cleanly. - LLAppViewer::instance()->userQuit(); - - if (LLAppViewer::instance()->quitRequested()) - { - LLPanelLogin::closePanel(); - } - return; - } - else - { - LL_WARNS("AppInit") << "Unknown login button clicked" << LL_ENDL; - } -} - -void release_notes_coro(const std::string url) -{ - if (url.empty()) - { - return; - } - - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("releaseNotesCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); - - httpOpts->setHeadersOnly(true); // only making sure it isn't 404 or something like that - - LLSD result = httpAdapter->getAndSuspend(httpRequest, url, httpOpts); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - return; - } - - LLWeb::loadURLInternal(url); -} - -/** -* Check if user is running a new version of the viewer. -* Display the Release Notes if it's not overriden by the "UpdaterShowReleaseNotes" setting. -*/ -void show_release_notes_if_required() -{ - static bool release_notes_shown = false; - // We happen to know that instantiating LLVersionInfo implicitly - // instantiates the LLEventMailDrop named "relnotes", which we (might) use - // below. If viewer release notes stop working, might be because that - // LLEventMailDrop got moved out of LLVersionInfo and hasn't yet been - // instantiated. - if (!release_notes_shown && (LLVersionInfo::instance().getChannelAndVersion() != gLastRunVersion) - && LLVersionInfo::instance().getViewerMaturity() != LLVersionInfo::TEST_VIEWER // don't show Release Notes for the test builds - && gSavedSettings.getBOOL("UpdaterShowReleaseNotes") - && !gSavedSettings.getBOOL("FirstLoginThisInstall")) - { - -#if LL_RELEASE_FOR_DOWNLOAD - if (!gSavedSettings.getBOOL("CmdLineSkipUpdater") - && !LLAppViewer::instance()->isUpdaterMissing()) - { - // Instantiate a "relnotes" listener which assumes any arriving event - // is the release notes URL string. Since "relnotes" is an - // LLEventMailDrop, this listener will be invoked whether or not the - // URL has already been posted. If so, it will fire immediately; - // otherwise it will fire whenever the URL is (later) posted. Either - // way, it will display the release notes as soon as the URL becomes - // available. - LLEventPumps::instance().obtain("relnotes").listen( - "showrelnotes", - [](const LLSD& url) { - LLCoros::instance().launch("releaseNotesCoro", - boost::bind(&release_notes_coro, url.asString())); - return false; - }); - } - else -#endif // LL_RELEASE_FOR_DOWNLOAD - { - LLSD info(LLAppViewer::instance()->getViewerInfo()); - std::string url = info["VIEWER_RELEASE_NOTES_URL"].asString(); - LLCoros::instance().launch("releaseNotesCoro", - boost::bind(&release_notes_coro, url)); - } - release_notes_shown = true; - } -} - -void show_first_run_dialog() -{ - LLNotificationsUtil::add("FirstRun", LLSD(), LLSD(), first_run_dialog_callback); -} - -bool first_run_dialog_callback(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (0 == option) - { - LL_DEBUGS("AppInit") << "First run dialog cancelling" << LL_ENDL; - LLWeb::loadURLExternal(LLTrans::getString("create_account_url") ); - } - - LLPanelLogin::giveFocus(); - return false; -} - - - -void set_startup_status(const F32 frac, const std::string& string, const std::string& msg) -{ - gViewerWindow->setProgressPercent(frac*100); - gViewerWindow->setProgressString(string); - - gViewerWindow->setProgressMessage(msg); -} - -bool login_alert_status(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - // Buttons - switch( option ) - { - case 0: // OK - break; - // case 1: // Help - // LLWeb::loadURL(LLNotifications::instance().getGlobalString("SUPPORT_URL") ); - // break; - case 2: // Teleport - // Restart the login process, starting at our home locaton - LLStartUp::setStartSLURL(LLSLURL(LLSLURL::SIM_LOCATION_HOME)); - LLStartUp::setStartupState( STATE_LOGIN_CLEANUP ); - break; - default: - LL_WARNS("AppInit") << "Missing case in login_alert_status switch" << LL_ENDL; - } - - LLPanelLogin::giveFocus(); - return false; -} - - -void use_circuit_callback(void**, S32 result) -{ - // bail if we're quitting. - if(LLApp::isExiting()) return; - if( !gUseCircuitCallbackCalled ) - { - gUseCircuitCallbackCalled = true; - if (result) - { - // Make sure user knows something bad happened. JC - LL_WARNS("AppInit") << "Backing up to login screen!" << LL_ENDL; - if (gRememberPassword) - { - LLNotificationsUtil::add("LoginPacketNeverReceived", LLSD(), LLSD(), login_alert_status); - } - else - { - LLNotificationsUtil::add("LoginPacketNeverReceivedNoTP", LLSD(), LLSD(), login_alert_status); - } - reset_login(); - } - else - { - gGotUseCircuitCodeAck = true; - } - } -} - -void register_viewer_callbacks(LLMessageSystem* msg) -{ - msg->setHandlerFuncFast(_PREHASH_LayerData, process_layer_data ); - msg->setHandlerFuncFast(_PREHASH_ObjectUpdate, process_object_update ); - msg->setHandlerFunc("ObjectUpdateCompressed", process_compressed_object_update ); - msg->setHandlerFunc("ObjectUpdateCached", process_cached_object_update ); - msg->setHandlerFuncFast(_PREHASH_ImprovedTerseObjectUpdate, process_terse_object_update_improved ); - msg->setHandlerFunc("SimStats", process_sim_stats); - msg->setHandlerFuncFast(_PREHASH_HealthMessage, process_health_message ); - msg->setHandlerFuncFast(_PREHASH_EconomyData, process_economy_data); - msg->setHandlerFunc("RegionInfo", LLViewerRegion::processRegionInfo); - - msg->setHandlerFuncFast(_PREHASH_ChatFromSimulator, process_chat_from_simulator); - msg->setHandlerFuncFast(_PREHASH_KillObject, process_kill_object, NULL); - msg->setHandlerFuncFast(_PREHASH_SimulatorViewerTimeMessage, process_time_synch, NULL); - msg->setHandlerFuncFast(_PREHASH_EnableSimulator, process_enable_simulator); - msg->setHandlerFuncFast(_PREHASH_DisableSimulator, process_disable_simulator); - msg->setHandlerFuncFast(_PREHASH_KickUser, process_kick_user, NULL); - - msg->setHandlerFunc("CrossedRegion", process_crossed_region); - msg->setHandlerFuncFast(_PREHASH_TeleportFinish, process_teleport_finish); - - msg->setHandlerFuncFast(_PREHASH_AlertMessage, process_alert_message); - msg->setHandlerFunc("AgentAlertMessage", process_agent_alert_message); - msg->setHandlerFuncFast(_PREHASH_MeanCollisionAlert, process_mean_collision_alert_message, NULL); - msg->setHandlerFunc("ViewerFrozenMessage", process_frozen_message); - - msg->setHandlerFuncFast(_PREHASH_NameValuePair, process_name_value); - msg->setHandlerFuncFast(_PREHASH_RemoveNameValuePair, process_remove_name_value); - msg->setHandlerFuncFast(_PREHASH_AvatarAnimation, process_avatar_animation); - msg->setHandlerFuncFast(_PREHASH_ObjectAnimation, process_object_animation); - msg->setHandlerFuncFast(_PREHASH_AvatarAppearance, process_avatar_appearance); - msg->setHandlerFuncFast(_PREHASH_CameraConstraint, process_camera_constraint); - msg->setHandlerFuncFast(_PREHASH_AvatarSitResponse, process_avatar_sit_response); - msg->setHandlerFunc("SetFollowCamProperties", process_set_follow_cam_properties); - msg->setHandlerFunc("ClearFollowCamProperties", process_clear_follow_cam_properties); - - msg->setHandlerFuncFast(_PREHASH_ImprovedInstantMessage, process_improved_im); - msg->setHandlerFuncFast(_PREHASH_ScriptQuestion, process_script_question); - msg->setHandlerFuncFast(_PREHASH_ObjectProperties, LLSelectMgr::processObjectProperties, NULL); - msg->setHandlerFuncFast(_PREHASH_ObjectPropertiesFamily, LLSelectMgr::processObjectPropertiesFamily, NULL); - msg->setHandlerFunc("ForceObjectSelect", LLSelectMgr::processForceObjectSelect); - - msg->setHandlerFuncFast(_PREHASH_MoneyBalanceReply, process_money_balance_reply, NULL); - msg->setHandlerFuncFast(_PREHASH_CoarseLocationUpdate, LLWorld::processCoarseUpdate, NULL); - msg->setHandlerFuncFast(_PREHASH_ReplyTaskInventory, LLViewerObject::processTaskInv, NULL); - msg->setHandlerFuncFast(_PREHASH_DerezContainer, process_derez_container, NULL); - msg->setHandlerFuncFast(_PREHASH_ScriptRunningReply, - &LLLiveLSLEditor::processScriptRunningReply); - - msg->setHandlerFuncFast(_PREHASH_DeRezAck, process_derez_ack); - - msg->setHandlerFunc("LogoutReply", process_logout_reply); - - //msg->setHandlerFuncFast(_PREHASH_AddModifyAbility, - // &LLAgent::processAddModifyAbility); - //msg->setHandlerFuncFast(_PREHASH_RemoveModifyAbility, - // &LLAgent::processRemoveModifyAbility); - msg->setHandlerFuncFast(_PREHASH_AgentDataUpdate, - &LLAgent::processAgentDataUpdate); - msg->setHandlerFuncFast(_PREHASH_AgentGroupDataUpdate, - &LLAgent::processAgentGroupDataUpdate); - msg->setHandlerFunc("AgentDropGroup", - &LLAgent::processAgentDropGroup); - // land ownership messages - msg->setHandlerFuncFast(_PREHASH_ParcelOverlay, - LLViewerParcelMgr::processParcelOverlay); - msg->setHandlerFuncFast(_PREHASH_ParcelProperties, - LLViewerParcelMgr::processParcelProperties); - msg->setHandlerFunc("ParcelAccessListReply", - LLViewerParcelMgr::processParcelAccessListReply); - msg->setHandlerFunc("ParcelDwellReply", - LLViewerParcelMgr::processParcelDwellReply); - - msg->setHandlerFunc("AvatarPropertiesReply", - &LLAvatarPropertiesProcessor::processAvatarLegacyPropertiesReply); - msg->setHandlerFunc("AvatarInterestsReply", - &LLAvatarPropertiesProcessor::processAvatarInterestsReply); - msg->setHandlerFunc("AvatarGroupsReply", - &LLAvatarPropertiesProcessor::processAvatarGroupsReply); - msg->setHandlerFunc("AvatarNotesReply", - &LLAvatarPropertiesProcessor::processAvatarNotesReply); - msg->setHandlerFunc("AvatarPicksReply", - &LLAvatarPropertiesProcessor::processAvatarPicksReply); - msg->setHandlerFunc("AvatarClassifiedReply", - &LLAvatarPropertiesProcessor::processAvatarClassifiedsReply); - - msg->setHandlerFuncFast(_PREHASH_CreateGroupReply, - LLGroupMgr::processCreateGroupReply); - msg->setHandlerFuncFast(_PREHASH_JoinGroupReply, - LLGroupMgr::processJoinGroupReply); - msg->setHandlerFuncFast(_PREHASH_EjectGroupMemberReply, - LLGroupMgr::processEjectGroupMemberReply); - msg->setHandlerFuncFast(_PREHASH_LeaveGroupReply, - LLGroupMgr::processLeaveGroupReply); - msg->setHandlerFuncFast(_PREHASH_GroupProfileReply, - LLGroupMgr::processGroupPropertiesReply); - - // ratings deprecated - // msg->setHandlerFuncFast(_PREHASH_ReputationIndividualReply, - // LLFloaterRate::processReputationIndividualReply); - - msg->setHandlerFunc("ScriptControlChange", - LLAgent::processScriptControlChange ); - - msg->setHandlerFuncFast(_PREHASH_ViewerEffect, LLHUDManager::processViewerEffect); - - msg->setHandlerFuncFast(_PREHASH_GrantGodlikePowers, process_grant_godlike_powers); - - msg->setHandlerFuncFast(_PREHASH_GroupAccountSummaryReply, - LLPanelGroupLandMoney::processGroupAccountSummaryReply); - msg->setHandlerFuncFast(_PREHASH_GroupAccountDetailsReply, - LLPanelGroupLandMoney::processGroupAccountDetailsReply); - msg->setHandlerFuncFast(_PREHASH_GroupAccountTransactionsReply, - LLPanelGroupLandMoney::processGroupAccountTransactionsReply); - - msg->setHandlerFuncFast(_PREHASH_UserInfoReply, - process_user_info_reply); - - msg->setHandlerFunc("RegionHandshake", process_region_handshake, NULL); - - msg->setHandlerFunc("TeleportStart", process_teleport_start ); - msg->setHandlerFunc("TeleportProgress", process_teleport_progress); - msg->setHandlerFunc("TeleportFailed", process_teleport_failed, NULL); - msg->setHandlerFunc("TeleportLocal", process_teleport_local, NULL); - - msg->setHandlerFunc("ImageNotInDatabase", LLViewerTextureList::processImageNotInDatabase, NULL); - - msg->setHandlerFuncFast(_PREHASH_GroupMembersReply, - LLGroupMgr::processGroupMembersReply); - msg->setHandlerFunc("GroupRoleDataReply", - LLGroupMgr::processGroupRoleDataReply); - msg->setHandlerFunc("GroupRoleMembersReply", - LLGroupMgr::processGroupRoleMembersReply); - msg->setHandlerFunc("GroupTitlesReply", - LLGroupMgr::processGroupTitlesReply); - // Special handler as this message is sometimes used for group land. - msg->setHandlerFunc("PlacesReply", process_places_reply); - msg->setHandlerFunc("GroupNoticesListReply", LLPanelGroupNotices::processGroupNoticesListReply); - - msg->setHandlerFunc("AvatarPickerReply", LLFloaterAvatarPicker::processAvatarPickerReply); - - msg->setHandlerFunc("MapBlockReply", LLWorldMapMessage::processMapBlockReply); - msg->setHandlerFunc("MapItemReply", LLWorldMapMessage::processMapItemReply); - msg->setHandlerFunc("EventInfoReply", LLEventNotifier::processEventInfoReply); - - msg->setHandlerFunc("PickInfoReply", &LLAvatarPropertiesProcessor::processPickInfoReply); - msg->setHandlerFunc("ClassifiedInfoReply", LLAvatarPropertiesProcessor::processClassifiedInfoReply); - msg->setHandlerFunc("ParcelInfoReply", LLRemoteParcelInfoProcessor::processParcelInfoReply); - msg->setHandlerFunc("ScriptDialog", process_script_dialog); - msg->setHandlerFunc("LoadURL", process_load_url); - msg->setHandlerFunc("ScriptTeleportRequest", process_script_teleport_request); - msg->setHandlerFunc("EstateCovenantReply", process_covenant_reply); - - // calling cards - msg->setHandlerFunc("OfferCallingCard", process_offer_callingcard); - msg->setHandlerFunc("AcceptCallingCard", process_accept_callingcard); - msg->setHandlerFunc("DeclineCallingCard", process_decline_callingcard); - - msg->setHandlerFunc("ParcelObjectOwnersReply", LLPanelLandObjects::processParcelObjectOwnersReply); - - msg->setHandlerFunc("InitiateDownload", process_initiate_download); - msg->setHandlerFunc("LandStatReply", LLFloaterTopObjects::handle_land_reply); - msg->setHandlerFunc("GenericMessage", process_generic_message); - msg->setHandlerFunc("GenericStreamingMessage", process_generic_streaming_message); - msg->setHandlerFunc("LargeGenericMessage", process_large_generic_message); - - msg->setHandlerFuncFast(_PREHASH_FeatureDisabled, process_feature_disabled_message); -} - -void asset_callback_nothing(const LLUUID&, LLAssetType::EType, void*, S32) -{ - // nothing -} - -const S32 OPT_CLOSED_WINDOW = -1; -const S32 OPT_MALE = 0; -const S32 OPT_FEMALE = 1; -const S32 OPT_TRUST_CERT = 0; -const S32 OPT_CANCEL_TRUST = 1; - -bool callback_choose_gender(const LLSD& notification, const LLSD& response) -{ - - // These defaults are returned from the server on login. They are set in login.xml. - // If no default is returned from the server, they are retrieved from settings.xml. - - S32 option = LLNotification::getSelectedOption(notification, response); - switch(option) - { - case OPT_MALE: - LLStartUp::loadInitialOutfit( gSavedSettings.getString("DefaultMaleAvatar"), "male" ); - break; - - case OPT_FEMALE: - case OPT_CLOSED_WINDOW: - default: - LLStartUp::loadInitialOutfit( gSavedSettings.getString("DefaultFemaleAvatar"), "female" ); - break; - } - return false; -} - -std::string get_screen_filename(const std::string& pattern) -{ - if (LLGridManager::getInstance()->isInProductionGrid()) - { - return llformat(pattern.c_str(), ""); - } - else - { - const std::string& grid_id_str = LLGridManager::getInstance()->getGridId(); - const std::string& grid_id_lower = utf8str_tolower(grid_id_str); - std::string grid = "." + grid_id_lower; - return llformat(pattern.c_str(), grid.c_str()); - } -} - -//static -std::string LLStartUp::getScreenLastFilename() -{ - return get_screen_filename(SCREEN_LAST_FILENAME); -} - -//static -std::string LLStartUp::getScreenHomeFilename() -{ - return get_screen_filename(SCREEN_HOME_FILENAME); -} - -//static -void LLStartUp::loadInitialOutfit( const std::string& outfit_folder_name, - const std::string& gender_name ) -{ - LL_DEBUGS() << "starting" << LL_ENDL; - - // Not going through the processAgentInitialWearables path, so need to set this here. - LLAppearanceMgr::instance().setAttachmentInvLinkEnable(true); - // Initiate creation of COF, since we're also bypassing that. - gInventory.ensureCategoryForTypeExists(LLFolderType::FT_CURRENT_OUTFIT); - LLAppearanceMgr::getInstance()->initCOFID(); - - ESex gender; - if (gender_name == "male") - { - LL_DEBUGS() << "male" << LL_ENDL; - gender = SEX_MALE; - } - else - { - LL_DEBUGS() << "female" << LL_ENDL; - gender = SEX_FEMALE; - } - - if (!isAgentAvatarValid()) - { - LL_WARNS() << "Trying to load an initial outfit for an invalid agent avatar" << LL_ENDL; - return; - } - - gAgentAvatarp->setSex(gender); - - // try to find the requested outfit or folder - - // -- check for existing outfit in My Outfits - bool do_copy = false; - LLUUID cat_id = findDescendentCategoryIDByName( - gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS), - outfit_folder_name); - - // -- check for existing folder in Library - if (cat_id.isNull()) - { - cat_id = findDescendentCategoryIDByName( - gInventory.getLibraryRootFolderID(), - outfit_folder_name); - if (!cat_id.isNull()) - { - do_copy = true; - } - } - - if (cat_id.isNull()) - { - // -- final fallback: create standard wearables - LL_DEBUGS() << "standard wearables" << LL_ENDL; - gAgentWearables.createStandardWearables(); - } - else - { - bool do_append = false; - LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id); - // Need to fetch cof contents before we can wear. - if (do_copy) - { - callAfterCOFFetch(boost::bind(&LLAppearanceMgr::wearInventoryCategory, LLAppearanceMgr::getInstance(), cat, do_copy, do_append)); - } - else - { - callAfterCategoryLinksFetch(cat_id, boost::bind(&LLAppearanceMgr::wearInventoryCategory, LLAppearanceMgr::getInstance(), cat, do_copy, do_append)); - } - LL_DEBUGS() << "initial outfit category id: " << cat_id << LL_ENDL; - } - - gAgent.setOutfitChosen(true); - gAgentWearables.sendDummyAgentWearablesUpdate(); -} - -std::string& LLStartUp::getInitialOutfitName() -{ - return sInitialOutfit; -} - -std::string LLStartUp::getUserId() -{ - if (gUserCredential.isNull()) - { - return ""; - } - return gUserCredential->userID(); -} - - -// frees the bitmap -void release_start_screen() -{ - LL_DEBUGS("AppInit") << "Releasing bitmap..." << LL_ENDL; - gStartTexture = NULL; -} - - -// static -std::string LLStartUp::startupStateToString(EStartupState state) -{ -#define RTNENUM(E) case E: return #E - switch(state){ - RTNENUM( STATE_FIRST ); - RTNENUM( STATE_BROWSER_INIT ); - RTNENUM( STATE_LOGIN_SHOW ); - RTNENUM( STATE_LOGIN_WAIT ); - RTNENUM( STATE_LOGIN_CLEANUP ); - RTNENUM( STATE_LOGIN_AUTH_INIT ); - RTNENUM( STATE_LOGIN_CURL_UNSTUCK ); - RTNENUM( STATE_LOGIN_PROCESS_RESPONSE ); - RTNENUM( STATE_WORLD_INIT ); - RTNENUM( STATE_MULTIMEDIA_INIT ); - RTNENUM( STATE_FONT_INIT ); - RTNENUM( STATE_SEED_GRANTED_WAIT ); - RTNENUM( STATE_SEED_CAP_GRANTED ); - RTNENUM( STATE_WORLD_WAIT ); - RTNENUM( STATE_AGENT_SEND ); - RTNENUM( STATE_AGENT_WAIT ); - RTNENUM( STATE_INVENTORY_SEND ); - RTNENUM(STATE_INVENTORY_CALLBACKS ); - RTNENUM( STATE_INVENTORY_SKEL ); - RTNENUM( STATE_INVENTORY_SEND2 ); - RTNENUM( STATE_MISC ); - RTNENUM( STATE_PRECACHE ); - RTNENUM( STATE_WEARABLES_WAIT ); - RTNENUM( STATE_CLEANUP ); - RTNENUM( STATE_STARTED ); - default: - return llformat("(state #%d)", state); - } -#undef RTNENUM -} - -// static -void LLStartUp::setStartupState( EStartupState state ) -{ - LL_INFOS("AppInit") << "Startup state changing from " << - getStartupStateString() << " to " << - startupStateToString(state) << LL_ENDL; - - getPhases().stopPhase(getStartupStateString()); - gStartupState = state; - getPhases().startPhase(getStartupStateString()); - - postStartupState(); -} - -void LLStartUp::postStartupState() -{ - LLSD stateInfo; - stateInfo["str"] = getStartupStateString(); - stateInfo["enum"] = gStartupState; - sStateWatcher->post(stateInfo); - gDebugInfo["StartupState"] = getStartupStateString(); -} - - -void reset_login() -{ - gAgentWearables.cleanup(); - gAgentCamera.cleanup(); - gAgent.cleanup(); - gSky.cleanup(); // mVOSkyp is an inworld object. - LLWorld::getInstance()->resetClass(); - LLAppearanceMgr::getInstance()->cleanup(); - - if ( gViewerWindow ) - { // Hide menus and normal buttons - gViewerWindow->setNormalControlsVisible( false ); - gLoginMenuBarView->setVisible( true ); - gLoginMenuBarView->setEnabled( true ); - } - - // Hide any other stuff - LLFloaterReg::hideVisibleInstances(); - - if (LLStartUp::getStartupState() > STATE_WORLD_INIT) - { - gViewerWindow->resetStatusBarContainer(); - } - LLStartUp::setStartupState( STATE_BROWSER_INIT ); - - if (LLVoiceClient::instanceExists()) - { - LLVoiceClient::getInstance()->terminate(); - } - - // Clear any verified certs and verify them again on next login - // to ensure cert matches server instead of just getting reused - LLPointer store = gSecAPIHandler->getCertificateStore(""); - store->clearSertCache(); -} - -//--------------------------------------------------------------------------- - -// Initialize all plug-ins except the web browser (which was initialized -// early, before the login screen). JC -void LLStartUp::multimediaInit() -{ - LL_DEBUGS("AppInit") << "Initializing Multimedia...." << LL_ENDL; - std::string msg = LLTrans::getString("LoginInitializingMultimedia"); - set_startup_status(0.42f, msg.c_str(), gAgent.mMOTD.c_str()); - display_startup(); -} - -void LLStartUp::fontInit() -{ - LL_DEBUGS("AppInit") << "Initializing fonts...." << LL_ENDL; - std::string msg = LLTrans::getString("LoginInitializingFonts"); - set_startup_status(0.45f, msg.c_str(), gAgent.mMOTD.c_str()); - display_startup(); - - LLFontGL::loadDefaultFonts(); -} - -void LLStartUp::initNameCache() -{ - // Can be called multiple times - if ( gCacheName ) return; - - gCacheName = new LLCacheName(gMessageSystem); - gCacheName->addObserver(&callback_cache_name); - gCacheName->localizeCacheName("waiting", LLTrans::getString("AvatarNameWaiting")); - gCacheName->localizeCacheName("nobody", LLTrans::getString("AvatarNameNobody")); - gCacheName->localizeCacheName("none", LLTrans::getString("GroupNameNone")); - // Load stored cache if possible - LLAppViewer::instance()->loadNameCache(); - - // Start cache in not-running state until we figure out if we have - // capabilities for display name lookup - LLAvatarNameCache* cache_inst = LLAvatarNameCache::getInstance(); - cache_inst->setUsePeopleAPI(gSavedSettings.getBOOL("UsePeopleAPI")); - cache_inst->setUseDisplayNames(gSavedSettings.getBOOL("UseDisplayNames")); - cache_inst->setUseUsernames(gSavedSettings.getBOOL("NameTagShowUsernames")); -} - - -void LLStartUp::initExperiences() -{ - // Should trigger loading the cache. - LLExperienceCache::instance().setCapabilityQuery( - boost::bind(&LLAgent::getRegionCapability, &gAgent, _1)); - - LLExperienceLog::instance().initialize(); -} - -void LLStartUp::cleanupNameCache() -{ - delete gCacheName; - gCacheName = NULL; -} - -bool LLStartUp::dispatchURL() -{ - // ok, if we've gotten this far and have a startup URL - if (!getStartSLURL().isValid()) - { - return false; - } - if(getStartSLURL().getType() != LLSLURL::APP) - { - - // If we started with a location, but we're already - // at that location, don't pop dialogs open. - LLVector3 pos = gAgent.getPositionAgent(); - LLVector3 slurlpos = getStartSLURL().getPosition(); - F32 dx = pos.mV[VX] - slurlpos.mV[VX]; - F32 dy = pos.mV[VY] - slurlpos.mV[VY]; - const F32 SLOP = 2.f; // meters - - if( getStartSLURL().getRegion() != gAgent.getRegion()->getName() - || (dx*dx > SLOP*SLOP) - || (dy*dy > SLOP*SLOP) ) - { - LLURLDispatcher::dispatch(getStartSLURL().getSLURLString(), LLCommandHandler::NAV_TYPE_CLICKED, - NULL, false); - } - return true; - } - return false; -} - -void LLStartUp::setStartSLURL(const LLSLURL& slurl) -{ - LL_DEBUGS("AppInit")< socks_cred = gSecAPIHandler->loadCredential("SOCKS5"); - std::string socks_user = socks_cred->getIdentifier()["username"].asString(); - std::string socks_password = socks_cred->getAuthenticator()["creds"].asString(); - - bool ok = LLProxy::getInstance()->setAuthPassword(socks_user, socks_password); - - if (!ok) - { - LLNotificationsUtil::add("SOCKS_BAD_CREDS"); - proxy_ok = false; - } - } - else if (auth_type.compare("None") == 0) - { - LLProxy::getInstance()->setAuthNone(); - } - else - { - LL_WARNS("Proxy") << "Invalid SOCKS 5 authentication type."<< LL_ENDL; - - // Unknown or missing setting. - gSavedSettings.setString("Socks5AuthType", "None"); - - // Clear the SOCKS credentials. - LLPointer socks_cred = new LLCredential("SOCKS5"); - gSecAPIHandler->deleteCredential(socks_cred); - - LLProxy::getInstance()->setAuthNone(); - } - - if (proxy_ok) - { - // Start the proxy and check for errors - // If status != SOCKS_OK, stopSOCKSProxy() will already have been called when startSOCKSProxy() returns. - LLHost socks_host; - socks_host.setHostByName(gSavedSettings.getString("Socks5ProxyHost")); - socks_host.setPort(gSavedSettings.getU32("Socks5ProxyPort")); - int status = LLProxy::getInstance()->startSOCKSProxy(socks_host); - - if (status != SOCKS_OK) - { - LLSD subs; - subs["HOST"] = gSavedSettings.getString("Socks5ProxyHost"); - subs["PORT"] = (S32)gSavedSettings.getU32("Socks5ProxyPort"); - - std::string error_string; - - switch(status) - { - case SOCKS_CONNECT_ERROR: // TCP Fail - error_string = "SOCKS_CONNECT_ERROR"; - break; - - case SOCKS_NOT_PERMITTED: // SOCKS 5 server rule set refused connection - error_string = "SOCKS_NOT_PERMITTED"; - break; - - case SOCKS_NOT_ACCEPTABLE: // Selected authentication is not acceptable to server - error_string = "SOCKS_NOT_ACCEPTABLE"; - break; - - case SOCKS_AUTH_FAIL: // Authentication failed - error_string = "SOCKS_AUTH_FAIL"; - break; - - case SOCKS_UDP_FWD_NOT_GRANTED: // UDP forward request failed - error_string = "SOCKS_UDP_FWD_NOT_GRANTED"; - break; - - case SOCKS_HOST_CONNECT_FAILED: // Failed to open a TCP channel to the socks server - error_string = "SOCKS_HOST_CONNECT_FAILED"; - break; - - case SOCKS_INVALID_HOST: // Improperly formatted host address or port. - error_string = "SOCKS_INVALID_HOST"; - break; - - default: - error_string = "SOCKS_UNKNOWN_STATUS"; // Something strange happened, - LL_WARNS("Proxy") << "Unknown return from LLProxy::startProxy(): " << status << LL_ENDL; - break; - } - - LLNotificationsUtil::add(error_string, subs); - proxy_ok = false; - } - } - } - else - { - LLProxy::getInstance()->stopSOCKSProxy(); // ensure no UDP proxy is running and it's all cleaned up - } - - if (proxy_ok) - { - // Determine the HTTP proxy type (if any) - if ((httpProxyType.compare("Web") == 0) && gSavedSettings.getBOOL("BrowserProxyEnabled")) - { - LLHost http_host; - http_host.setHostByName(gSavedSettings.getString("BrowserProxyAddress")); - http_host.setPort(gSavedSettings.getS32("BrowserProxyPort")); - if (!LLProxy::getInstance()->enableHTTPProxy(http_host, LLPROXY_HTTP)) - { - LLSD subs; - subs["HOST"] = http_host.getIPString(); - subs["PORT"] = (S32)http_host.getPort(); - LLNotificationsUtil::add("PROXY_INVALID_HTTP_HOST", subs); - proxy_ok = false; - } - } - else if ((httpProxyType.compare("Socks") == 0) && gSavedSettings.getBOOL("Socks5ProxyEnabled")) - { - LLHost socks_host; - socks_host.setHostByName(gSavedSettings.getString("Socks5ProxyHost")); - socks_host.setPort(gSavedSettings.getU32("Socks5ProxyPort")); - if (!LLProxy::getInstance()->enableHTTPProxy(socks_host, LLPROXY_SOCKS)) - { - LLSD subs; - subs["HOST"] = socks_host.getIPString(); - subs["PORT"] = (S32)socks_host.getPort(); - LLNotificationsUtil::add("PROXY_INVALID_SOCKS_HOST", subs); - proxy_ok = false; - } - } - else if (httpProxyType.compare("None") == 0) - { - LLProxy::getInstance()->disableHTTPProxy(); - } - else - { - LL_WARNS("Proxy") << "Invalid other HTTP proxy configuration: " << httpProxyType << LL_ENDL; - - // Set the missing or wrong configuration back to something valid. - gSavedSettings.setString("HttpProxyType", "None"); - LLProxy::getInstance()->disableHTTPProxy(); - - // Leave proxy_ok alone, since this isn't necessarily fatal. - } - } - - return proxy_ok; -} - -bool login_alert_done(const LLSD& notification, const LLSD& response) -{ - LLPanelLogin::giveFocus(); - return false; -} - -// parse the certificate information into args for the -// certificate notifications -LLSD transform_cert_args(LLPointer cert) -{ - LLSD args = LLSD::emptyMap(); - std::string value; - LLSD cert_info; - cert->getLLSD(cert_info); - // convert all of the elements in the cert into - // args for the xml dialog, so we have flexability to - // display various parts of the cert by only modifying - // the cert alert dialog xml. - for(LLSD::map_iterator iter = cert_info.beginMap(); - iter != cert_info.endMap(); - iter++) - { - // key usage and extended key usage - // are actually arrays, and we want to format them as comma separated - // strings, so special case those. - LLSDSerialize::toXML(cert_info[iter->first], std::cout); - if((iter->first == std::string(CERT_KEY_USAGE)) || - (iter->first == std::string(CERT_EXTENDED_KEY_USAGE))) - { - value = ""; - LLSD usage = cert_info[iter->first]; - for (LLSD::array_iterator usage_iter = usage.beginArray(); - usage_iter != usage.endArray(); - usage_iter++) - { - - if(usage_iter != usage.beginArray()) - { - value += ", "; - } - - value += (*usage_iter).asString(); - } - - } - else - { - value = iter->second.asString(); - } - - std::string name = iter->first; - std::transform(name.begin(), name.end(), name.begin(), - (int(*)(int))toupper); - args[name.c_str()] = value; - } - return args; -} - - -// when we handle a cert error, give focus back to the login panel -void general_cert_done(const LLSD& notification, const LLSD& response) -{ - LLStartUp::setStartupState( STATE_LOGIN_SHOW ); - LLPanelLogin::giveFocus(); -} - -// check to see if the user wants to trust the cert. -// if they do, add it to the cert store and -void trust_cert_done(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotification::getSelectedOption(notification, response); - switch(option) - { - case OPT_TRUST_CERT: - { - LLPointer cert = gSecAPIHandler->getCertificate(notification["payload"]["certificate"]); - LLPointer store = gSecAPIHandler->getCertificateStore(gSavedSettings.getString("CertStore")); - store->add(cert); - store->save(); - LLStartUp::setStartupState( STATE_LOGIN_CLEANUP ); - break; - } - case OPT_CANCEL_TRUST: - reset_login(); - gSavedSettings.setBOOL("AutoLogin", false); - LLStartUp::setStartupState( STATE_LOGIN_SHOW ); - default: - LLPanelLogin::giveFocus(); - break; - } - -} - -void apply_udp_blacklist(const std::string& csv) -{ - - std::string::size_type start = 0; - std::string::size_type comma = 0; - do - { - comma = csv.find(",", start); - if (comma == std::string::npos) - { - comma = csv.length(); - } - std::string item(csv, start, comma-start); - - LL_DEBUGS() << "udp_blacklist " << item << LL_ENDL; - gMessageSystem->banUdpMessage(item); - - start = comma + 1; - - } - while(comma < csv.length()); - -} - -void on_benefits_failed_callback(const LLSD& notification, const LLSD& response) -{ - LL_WARNS("Benefits") << "Failed to load benefits information" << LL_ENDL; -} - -bool init_benefits(LLSD& response) -{ - bool succ = true; - - std::string package_name = response["account_type"].asString(); - const LLSD& benefits_sd = response["account_level_benefits"]; - if (!LLAgentBenefitsMgr::init(package_name, benefits_sd) || - !LLAgentBenefitsMgr::initCurrent(package_name, benefits_sd)) - { - succ = false; - } - else - { - LL_DEBUGS("Benefits") << "Initialized current benefits, level " << package_name << " from " << benefits_sd << LL_ENDL; - } - const LLSD& packages_sd = response["premium_packages"]; - for(LLSD::map_const_iterator package_iter = packages_sd.beginMap(); - package_iter != packages_sd.endMap(); - ++package_iter) - { - std::string package_name = package_iter->first; - const LLSD& benefits_sd = package_iter->second["benefits"]; - if (LLAgentBenefitsMgr::init(package_name, benefits_sd)) - { - LL_DEBUGS("Benefits") << "Initialized benefits for package " << package_name << " from " << benefits_sd << LL_ENDL; - } - else - { - LL_WARNS("Benefits") << "Failed init for package " << package_name << " from " << benefits_sd << LL_ENDL; - succ = false; - } - } - - if (!LLAgentBenefitsMgr::has("Base")) - { - LL_WARNS("Benefits") << "Benefits info did not include required package Base" << LL_ENDL; - succ = false; - } - if (!LLAgentBenefitsMgr::has("Premium")) - { - LL_WARNS("Benefits") << "Benefits info did not include required package Premium" << LL_ENDL; - succ = false; - } - - return succ; -} - -bool process_login_success_response() -{ - LLSD response = LLLoginInstance::getInstance()->getResponse(); - - mBenefitsSuccessfullyInit = init_benefits(response); - - std::string text(response["udp_blacklist"]); - if(!text.empty()) - { - apply_udp_blacklist(text); - } - - // unpack login data needed by the application - text = response["agent_id"].asString(); - if(!text.empty()) gAgentID.set(text); - gDebugInfo["AgentID"] = text; - - LLPerfStats::StatsRecorder::setEnabled(gSavedSettings.getBOOL("PerfStatsCaptureEnabled")); - LLPerfStats::StatsRecorder::setFocusAv(gAgentID); - - // Agent id needed for parcel info request in LLUrlEntryParcel - // to resolve parcel name. - LLUrlEntryParcel::setAgentID(gAgentID); - - text = response["session_id"].asString(); - if(!text.empty()) gAgentSessionID.set(text); - gDebugInfo["SessionID"] = text; - - // Session id needed for parcel info request in LLUrlEntryParcel - // to resolve parcel name. - LLUrlEntryParcel::setSessionID(gAgentSessionID); - - text = response["secure_session_id"].asString(); - if(!text.empty()) gAgent.mSecureSessionID.set(text); - - // if the response contains a display name, use that, - // otherwise if the response contains a first and/or last name, - // use those. Otherwise use the credential identifier - - gDisplayName = ""; - if (response.has("display_name")) - { - gDisplayName.assign(response["display_name"].asString()); - if(!gDisplayName.empty()) - { - // Remove quotes from string. Login.cgi sends these to force - // names that look like numbers into strings. - LLStringUtil::replaceChar(gDisplayName, '"', ' '); - LLStringUtil::trim(gDisplayName); - } - } - std::string first_name; - if(response.has("first_name")) - { - first_name = response["first_name"].asString(); - LLStringUtil::replaceChar(first_name, '"', ' '); - LLStringUtil::trim(first_name); - gAgentUsername = first_name; - } - - if(response.has("last_name") && !gAgentUsername.empty()) - { - std::string last_name = response["last_name"].asString(); - if (last_name != "Resident") - { - LLStringUtil::replaceChar(last_name, '"', ' '); - LLStringUtil::trim(last_name); - gAgentUsername = gAgentUsername + " " + last_name; - } - } - - if(gDisplayName.empty()) - { - if(response.has("first_name")) - { - gDisplayName.assign(response["first_name"].asString()); - LLStringUtil::replaceChar(gDisplayName, '"', ' '); - LLStringUtil::trim(gDisplayName); - } - if(response.has("last_name")) - { - text.assign(response["last_name"].asString()); - LLStringUtil::replaceChar(text, '"', ' '); - LLStringUtil::trim(text); - if(!gDisplayName.empty()) - { - gDisplayName += " "; - } - gDisplayName += text; - } - } - - if(gDisplayName.empty()) - { - gDisplayName.assign(gUserCredential->asString()); - } - - // this is their actual ability to access content - text = response["agent_access_max"].asString(); - if (!text.empty()) - { - // agent_access can be 'A', 'M', and 'PG'. - gAgent.setMaturity(text[0]); - } - - // this is the value of their preference setting for that content - // which will always be <= agent_access_max - text = response["agent_region_access"].asString(); - if (!text.empty()) - { - U32 preferredMaturity = (U32)LLAgent::convertTextToMaturity(text[0]); - - gSavedSettings.setU32("PreferredMaturity", preferredMaturity); - } - - text = response["start_location"].asString(); - if(!text.empty()) - { - gAgentStartLocation.assign(text); - } - - text = response["circuit_code"].asString(); - if(!text.empty()) - { - gMessageSystem->mOurCircuitCode = strtoul(text.c_str(), NULL, 10); - } - std::string sim_ip_str = response["sim_ip"]; - std::string sim_port_str = response["sim_port"]; - if(!sim_ip_str.empty() && !sim_port_str.empty()) - { - U32 sim_port = strtoul(sim_port_str.c_str(), NULL, 10); - gFirstSim.set(sim_ip_str, sim_port); - if (gFirstSim.isOk()) - { - gMessageSystem->enableCircuit(gFirstSim, true); - } - } - std::string region_x_str = response["region_x"]; - std::string region_y_str = response["region_y"]; - if(!region_x_str.empty() && !region_y_str.empty()) - { - U32 region_x = strtoul(region_x_str.c_str(), NULL, 10); - U32 region_y = strtoul(region_y_str.c_str(), NULL, 10); - gFirstSimHandle = to_region_handle(region_x, region_y); - } - - const std::string look_at_str = response["look_at"]; - if (!look_at_str.empty()) - { - size_t len = look_at_str.size(); - LLMemoryStream mstr((U8*)look_at_str.c_str(), len); - LLSD sd = LLSDSerialize::fromNotation(mstr, len); - gAgentStartLookAt = ll_vector3_from_sd(sd); - } - - text = response["seed_capability"].asString(); - if (!text.empty()) gFirstSimSeedCap = text; - - text = response["seconds_since_epoch"].asString(); - if(!text.empty()) - { - U32 server_utc_time = strtoul(text.c_str(), NULL, 10); - if(server_utc_time) - { - time_t now = time(NULL); - gUTCOffset = (server_utc_time - now); - - // Print server timestamp - LLSD substitution; - substitution["datetime"] = (S32)server_utc_time; - std::string timeStr = "[month, datetime, slt] [day, datetime, slt] [year, datetime, slt] [hour, datetime, slt]:[min, datetime, slt]:[second, datetime, slt]"; - LLStringUtil::format(timeStr, substitution); - LL_INFOS("AppInit") << "Server SLT timestamp: " << timeStr << ". Server-viewer time offset before correction: " << gUTCOffset << "s" << LL_ENDL; - } - } - - // this is the base used to construct help URLs - text = response["help_url_format"].asString(); - if (!text.empty()) - { - // replace the default help URL format - gSavedSettings.setString("HelpURLFormat",text); - } - - std::string home_location = response["home"]; - if(!home_location.empty()) - { - size_t len = home_location.size(); - LLMemoryStream mstr((U8*)home_location.c_str(), len); - LLSD sd = LLSDSerialize::fromNotation(mstr, len); - S32 region_x = sd["region_handle"][0].asInteger(); - S32 region_y = sd["region_handle"][1].asInteger(); - U64 region_handle = to_region_handle(region_x, region_y); - LLVector3 position = ll_vector3_from_sd(sd["position"]); - gAgent.setHomePosRegion(region_handle, position); - } - - gAgent.mMOTD.assign(response["message"]); - - // Options... - // Each 'option' is an array of submaps. - // It appears that we only ever use the first element of the array. - LLUUID inv_root_folder_id = response["inventory-root"][0]["folder_id"]; - if(inv_root_folder_id.notNull()) - { - gInventory.setRootFolderID(inv_root_folder_id); - //gInventory.mock(gAgent.getInventoryRootID()); - } - - LLSD login_flags = response["login-flags"][0]; - if(login_flags.size()) - { - std::string flag = login_flags["ever_logged_in"]; - if(!flag.empty()) - { - gAgent.setFirstLogin(flag == "N"); - } - - /* Flag is currently ignored by the viewer. - flag = login_flags["stipend_since_login"]; - if(flag == "Y") - { - stipend_since_login = true; - } - */ - - flag = login_flags["gendered"].asString(); - if(flag == "Y") - { - // We don't care about this flag anymore; now base whether - // outfit is chosen on COF contents, initial outfit - // requested and available, etc. - - //gAgent.setGenderChosen(true); - } - - bool pacific_daylight_time = false; - flag = login_flags["daylight_savings"].asString(); - if(flag == "Y") - { - pacific_daylight_time = (flag == "Y"); - } - - //setup map of datetime strings to codes and slt & local time offset from utc - LLStringOps::setupDatetimeInfo(pacific_daylight_time); - } - - // set up the voice configuration. Ultimately, we should pass this up as part of each voice - // channel if we need to move to multiple voice servers per grid. - LLSD voice_config_info = response["voice-config"]; - if(voice_config_info.has("VoiceServerType")) - { - gSavedSettings.setString("VoiceServerType", voice_config_info["VoiceServerType"].asString()); - } - - // Request the map server url - std::string map_server_url = response["map-server-url"]; - if(!map_server_url.empty()) - { - // We got an answer from the grid -> use that for map for the current session - gSavedSettings.setString("CurrentMapServerURL", map_server_url); - LL_INFOS("LLStartup") << "map-server-url : we got an answer from the grid : " << map_server_url << LL_ENDL; - } - else - { - // No answer from the grid -> use the default setting for current session - map_server_url = gSavedSettings.getString("MapServerURL"); - gSavedSettings.setString("CurrentMapServerURL", map_server_url); - LL_INFOS("LLStartup") << "map-server-url : no map-server-url answer, we use the default setting for the map : " << map_server_url << LL_ENDL; - } - - // Default male and female avatars allowing the user to choose their avatar on first login. - // These may be passed up by SLE to allow choice of enterprise avatars instead of the standard - // "new ruth." Not to be confused with 'initial-outfit' below - LLSD newuser_config = response["newuser-config"][0]; - if(newuser_config.has("DefaultFemaleAvatar")) - { - gSavedSettings.setString("DefaultFemaleAvatar", newuser_config["DefaultFemaleAvatar"].asString()); - } - if(newuser_config.has("DefaultMaleAvatar")) - { - gSavedSettings.setString("DefaultMaleAvatar", newuser_config["DefaultMaleAvatar"].asString()); - } - - // Initial outfit for the user. - LLSD initial_outfit = response["initial-outfit"][0]; - if(initial_outfit.size()) - { - std::string flag = initial_outfit["folder_name"]; - if(!flag.empty()) - { - // Initial outfit is a folder in your inventory, - // must be an exact folder-name match. - sInitialOutfit = flag; - } - - flag = initial_outfit["gender"].asString(); - if(!flag.empty()) - { - sInitialOutfitGender = flag; - } - } - - std::string fake_initial_outfit_name = gSavedSettings.getString("FakeInitialOutfitName"); - if (!fake_initial_outfit_name.empty()) - { - gAgent.setFirstLogin(true); - sInitialOutfit = fake_initial_outfit_name; - if (sInitialOutfitGender.empty()) - { - sInitialOutfitGender = "female"; // just guess, will get overridden when outfit is worn anyway. - } - - LL_WARNS() << "Faking first-time login with initial outfit " << sInitialOutfit << LL_ENDL; - } - - // set the location of the Agent Appearance service, from which we can request - // avatar baked textures if they are supported by the current region - std::string agent_appearance_url = response["agent_appearance_service"]; - if (!agent_appearance_url.empty()) - { - LLAppearanceMgr::instance().setAppearanceServiceURL(agent_appearance_url); - } - - // Set the location of the snapshot sharing config endpoint - std::string snapshot_config_url = response["snapshot_config_url"]; - if(!snapshot_config_url.empty()) - { - gSavedSettings.setString("SnapshotConfigURL", snapshot_config_url); - } - - // Start the process of fetching the OpenID session cookie for this user login - std::string openid_url = response["openid_url"]; - if(!openid_url.empty()) - { - std::string openid_token = response["openid_token"]; - LLViewerMedia::getInstance()->openIDSetup(openid_url, openid_token); - } - - - // Only save mfa_hash for future logins if the user wants their info remembered. - if(response.has("mfa_hash") - && gSavedSettings.getBOOL("RememberUser") - && LLLoginInstance::getInstance()->saveMFA()) - { - std::string grid(LLGridManager::getInstance()->getGridId()); - std::string user_id(gUserCredential->userID()); - gSecAPIHandler->addToProtectedMap("mfa_hash", grid, user_id, response["mfa_hash"]); - // TODO(brad) - related to SL-17223 consider building a better interface that sync's automatically - gSecAPIHandler->syncProtectedMap(); - } - else if (!LLLoginInstance::getInstance()->saveMFA()) - { - std::string grid(LLGridManager::getInstance()->getGridId()); - std::string user_id(gUserCredential->userID()); - gSecAPIHandler->removeFromProtectedMap("mfa_hash", grid, user_id); - gSecAPIHandler->syncProtectedMap(); - } - - bool success = false; - // JC: gesture loading done below, when we have an asset system - // in place. Don't delete/clear gUserCredentials until then. - if(gAgentID.notNull() - && gAgentSessionID.notNull() - && gMessageSystem->mOurCircuitCode - && gFirstSim.isOk() - && gInventory.getRootFolderID().notNull()) - { - success = true; - } - LLAppViewer* pApp = LLAppViewer::instance(); - pApp->writeDebugInfo(); //Write our static data now that we have username, session_id, etc. - return success; -} - -void transition_back_to_login_panel(const std::string& emsg) -{ - // Bounce back to the login screen. - reset_login(); // calls LLStartUp::setStartupState( STATE_LOGIN_SHOW ); - gSavedSettings.setBOOL("AutoLogin", false); -} - +/** + * @file llstartup.cpp + * @brief startup routines. + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llappviewer.h" +#include "llstartup.h" +#include "llcallstack.h" + +#if LL_WINDOWS +# include // _spawnl() +#else +# include // mkdir() +#endif +#include // std::unique_ptr + +#include "llviewermedia_streamingaudio.h" +#include "llaudioengine.h" + +#ifdef LL_OPENAL +#include "llaudioengine_openal.h" +#endif + +#include "llavatarnamecache.h" +#include "llexperiencecache.h" +#include "lllandmark.h" +#include "llcachename.h" +#include "lldir.h" +#include "lldonotdisturbnotificationstorage.h" +#include "llerrorcontrol.h" +#include "llfloaterreg.h" +#include "llfocusmgr.h" +#include "llfloatergridstatus.h" +#include "llfloaterimsession.h" +#include "lllocationhistory.h" +#include "llgltfmateriallist.h" +#include "llimageworker.h" + +#include "llloginflags.h" +#include "llmd5.h" +#include "llmemorystream.h" +#include "llmessageconfig.h" +#include "llmoveview.h" +#include "llfloaterimcontainer.h" +#include "llfloaterimnearbychat.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "llpersistentnotificationstorage.h" +#include "llpresetsmanager.h" +#include "llteleporthistory.h" +#include "llregionhandle.h" +#include "llsd.h" +#include "llsdserialize.h" +#include "llsdutil_math.h" +#include "llstring.h" +#include "lluserrelations.h" +#include "llversioninfo.h" +#include "llviewercontrol.h" +#include "llviewerhelp.h" +#include "llxorcipher.h" // saved password, MAC address +#include "llwindow.h" +#include "message.h" +#include "v3math.h" + +#include "llagent.h" +#include "llagentbenefits.h" +#include "llagentcamera.h" +#include "llagentpicksinfo.h" +#include "llagentwearables.h" +#include "llagentpilot.h" +#include "llfloateravatarpicker.h" +#include "llcallbacklist.h" +#include "llcallingcard.h" +#include "llclassifiedinfo.h" +#include "llconsole.h" +#include "llcontainerview.h" +#include "llconversationlog.h" +#include "lldebugview.h" +#include "lldrawable.h" +#include "lleventnotifier.h" +#include "llface.h" +#include "llfeaturemanager.h" +//#include "llfirstuse.h" +#include "llfloaterhud.h" +#include "llfloaterland.h" +#include "llfloatertopobjects.h" +#include "llfloaterworldmap.h" +#include "llgesturemgr.h" +#include "llgroupmgr.h" +#include "llhudeffecttrail.h" +#include "llhudmanager.h" +#include "llimage.h" +#include "llinventorybridge.h" +#include "llinventorymodel.h" +#include "llinventorymodelbackgroundfetch.h" +#include "llkeyboard.h" +#include "llloginhandler.h" // gLoginHandler, SLURL support +#include "lllogininstance.h" // Host the login module. +#include "llpanellogin.h" +#include "llmutelist.h" +#include "llavatarpropertiesprocessor.h" +#include "llpanelgrouplandmoney.h" +#include "llpanelgroupnotices.h" +#include "llparcel.h" +#include "llpreview.h" +#include "llpreviewscript.h" +#include "llproxy.h" +#include "llproductinforequest.h" +#include "llqueryflags.h" +#include "llsecapi.h" +#include "llselectmgr.h" +#include "llsky.h" +#include "llstatview.h" +#include "llstatusbar.h" // sendMoneyBalanceRequest(), owns L$ balance +#include "llsurface.h" +#include "lltexturecache.h" +#include "lltexturefetch.h" +#include "lltoolmgr.h" +#include "lltrans.h" +#include "llui.h" +#include "lluiusage.h" +#include "llurldispatcher.h" +#include "llurlentry.h" +#include "llslurl.h" +#include "llurlhistory.h" +#include "llurlwhitelist.h" +#include "llvieweraudio.h" +#include "llviewerassetstorage.h" +#include "llviewercamera.h" +#include "llviewerdisplay.h" +#include "llviewergenericmessage.h" +#include "llviewergesture.h" +#include "llviewertexturelist.h" +#include "llviewermedia.h" +#include "llviewermenu.h" +#include "llviewermessage.h" +#include "llviewernetwork.h" +#include "llviewerobjectlist.h" +#include "llviewerparcelaskplay.h" +#include "llviewerparcelmedia.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "llviewerstats.h" +#include "llviewerstatsrecorder.h" +#include "llviewerthrottle.h" +#include "llviewerwindow.h" +#include "llvoavatar.h" +#include "llvoavatarself.h" +#include "llweb.h" +#include "llworld.h" +#include "llworldmapmessage.h" +#include "llxfermanager.h" +#include "pipeline.h" +#include "llappviewer.h" +#include "llfasttimerview.h" +#include "llfloatermap.h" +#include "llweb.h" +#include "llvoiceclient.h" +#include "llnamelistctrl.h" +#include "llnamebox.h" +#include "llnameeditor.h" +#include "llpostprocess.h" +#include "llagentlanguage.h" +#include "llwearable.h" +#include "llinventorybridge.h" +#include "llappearancemgr.h" +#include "llavatariconctrl.h" +#include "llvoicechannel.h" +#include "llpathfindingmanager.h" +#include "llremoteparcelrequest.h" + +#include "lllogin.h" +#include "llevents.h" +#include "llstartuplistener.h" +#include "lltoolbarview.h" +#include "llexperiencelog.h" +#include "llcleanup.h" + +#include "llenvironment.h" + +#include "llstacktrace.h" + +#include "threadpool.h" +#include "llperfstats.h" + + +#if LL_WINDOWS +#include "lldxhardware.h" +#endif + +// +// exported globals +// +bool gAgentMovementCompleted = false; + +const std::string SCREEN_HOME_FILENAME = "screen_home%s.png"; +const std::string SCREEN_LAST_FILENAME = "screen_last%s.png"; + +LLPointer gStartTexture; + +// +// Imported globals +// +extern S32 gStartImageWidth; +extern S32 gStartImageHeight; + +// +// local globals +// +static bool gGotUseCircuitCodeAck = false; +static std::string sInitialOutfit; +static std::string sInitialOutfitGender; // "male" or "female" + +static bool gUseCircuitCallbackCalled = false; + +EStartupState LLStartUp::gStartupState = STATE_FIRST; +LLSLURL LLStartUp::sStartSLURL; + +static LLPointer gUserCredential; +static std::string gDisplayName; +static bool gRememberPassword = true; +static bool gRememberUser = true; + +static U64 gFirstSimHandle = 0; +static LLHost gFirstSim; +static std::string gFirstSimSeedCap; +static LLVector3 gAgentStartLookAt(1.0f, 0.f, 0.f); +static std::string gAgentStartLocation = "safe"; +static bool mLoginStatePastUI = false; +static bool mBenefitsSuccessfullyInit = false; + +const F32 STATE_AGENT_WAIT_TIMEOUT = 240; //seconds +const S32 MAX_SEED_CAP_ATTEMPTS_BEFORE_ABORT = 4; // Give region 4 chances + +std::unique_ptr LLStartUp::sStateWatcher(new LLEventStream("StartupState")); +std::unique_ptr LLStartUp::sListener(new LLStartupListener()); +std::unique_ptr LLStartUp::sPhases(new LLViewerStats::PhaseMap); + +// +// local function declaration +// + +void login_show(); +void login_callback(S32 option, void* userdata); +void show_release_notes_if_required(); +void show_first_run_dialog(); +bool first_run_dialog_callback(const LLSD& notification, const LLSD& response); +void set_startup_status(const F32 frac, const std::string& string, const std::string& msg); +bool login_alert_status(const LLSD& notification, const LLSD& response); +void use_circuit_callback(void**, S32 result); +void register_viewer_callbacks(LLMessageSystem* msg); +void asset_callback_nothing(const LLUUID&, LLAssetType::EType, void*, S32); +bool callback_choose_gender(const LLSD& notification, const LLSD& response); +void release_start_screen(); +void reset_login(); +LLSD transform_cert_args(LLPointer cert); +void general_cert_done(const LLSD& notification, const LLSD& response); +void trust_cert_done(const LLSD& notification, const LLSD& response); +void apply_udp_blacklist(const std::string& csv); +bool process_login_success_response(); +void on_benefits_failed_callback(const LLSD& notification, const LLSD& response); +void transition_back_to_login_panel(const std::string& emsg); + +void callback_cache_name(const LLUUID& id, const std::string& full_name, bool is_group) +{ + LLNameBox::refreshAll(id, full_name, is_group); + LLNameEditor::refreshAll(id, full_name, is_group); + + // TODO: Actually be intelligent about the refresh. + // For now, just brute force refresh the dialogs. + dialog_refresh_all(); +} + +// +// exported functionality +// + +void pump_idle_startup_network(void) +{ + { + LockMessageChecker lmc(gMessageSystem); + while (lmc.checkAllMessages(gFrameCount, gServicePump)) + { + display_startup(); + } + lmc.processAcks(); + } + display_startup(); +} + +// +// local classes +// +void update_texture_fetch() +{ + LLAppViewer::getTextureCache()->update(1); // unpauses the texture cache thread + LLAppViewer::getImageDecodeThread()->update(1); // unpauses the image thread + LLAppViewer::getTextureFetch()->update(1); // unpauses the texture fetch thread + gTextureList.updateImages(0.10f); + + if (LLImageGLThread::sEnabledTextures) + { + std::shared_ptr main_queue = LL::WorkQueue::getInstance("mainloop"); + main_queue->runFor(std::chrono::milliseconds(1)); + } +} + +void set_flags_and_update_appearance() +{ + LLAppearanceMgr::instance().setAttachmentInvLinkEnable(true); + LLAppearanceMgr::instance().updateAppearanceFromCOF(true, true, no_op); + + LLInventoryModelBackgroundFetch::instance().start(); +} + +// Returns false to skip other idle processing. Should only return +// true when all initialization done. +bool idle_startup() +{ + if (gViewerWindow == NULL) + { + // We expect window to be initialized + LL_WARNS_ONCE() << "gViewerWindow is not initialized" << LL_ENDL; + return false; // No world yet + } + + const F32 PRECACHING_DELAY = gSavedSettings.getF32("PrecachingDelay"); + static LLTimer timeout; + + static LLTimer login_time; + + // until this is encapsulated, this little hack for the + // auth/transform loop will do. + static F32 progress = 0.10f; + + static std::string auth_desc; + static std::string auth_message; + + static LLVector3 agent_start_position_region(10.f, 10.f, 10.f); // default for when no space server + + // last location by default + static S32 agent_location_id = START_LOCATION_ID_LAST; + + static bool show_connect_box = true; + + //static bool stipend_since_login = false; + + // HACK: These are things from the main loop that usually aren't done + // until initialization is complete, but need to be done here for things + // to work. + gIdleCallbacks.callFunctions(); + gViewerWindow->updateUI(); + + LLMortician::updateClass(); + + const std::string delims (" "); + std::string system; + int begIdx, endIdx; + std::string osString = LLOSInfo::instance().getOSStringSimple(); + + begIdx = osString.find_first_not_of (delims); + endIdx = osString.find_first_of (delims, begIdx); + system = osString.substr (begIdx, endIdx - begIdx); + system += "Locale"; + + LLStringUtil::setLocale (LLTrans::getString(system)); + + //note: Removing this line will cause incorrect button size in the login screen. -- bao. + gTextureList.updateImages(0.01f) ; + + if ( STATE_FIRST == LLStartUp::getStartupState() ) + { + static bool first_call = true; + if (first_call) + { + // Other phases get handled when startup state changes, + // need to capture the initial state as well. + LLStartUp::getPhases().startPhase(LLStartUp::getStartupStateString()); + first_call = false; + } + + gViewerWindow->showCursor(); + gViewerWindow->getWindow()->setCursor(UI_CURSOR_WAIT); + + ///////////////////////////////////////////////// + // + // Initialize stuff that doesn't need data from simulators + // + std::string lastGPU = gSavedSettings.getString("LastGPUString"); + std::string thisGPU = LLFeatureManager::getInstance()->getGPUString(); + + if (LLFeatureManager::getInstance()->isSafe()) + { + LLNotificationsUtil::add("DisplaySetToSafe"); + } + else if ((gSavedSettings.getS32("LastFeatureVersion") < LLFeatureManager::getInstance()->getVersion()) && + (gSavedSettings.getS32("LastFeatureVersion") != 0)) + { + LLNotificationsUtil::add("DisplaySetToRecommendedFeatureChange"); + } + else if ( ! lastGPU.empty() && (lastGPU != thisGPU)) + { + LLSD subs; + subs["LAST_GPU"] = lastGPU; + subs["THIS_GPU"] = thisGPU; + LLNotificationsUtil::add("DisplaySetToRecommendedGPUChange", subs); + } + else if (!gViewerWindow->getInitAlert().empty()) + { + LLNotificationsUtil::add(gViewerWindow->getInitAlert()); + } + + //------------------------------------------------- + // Init the SOCKS 5 proxy if the user has configured + // one. We need to do this early in case the user + // is using SOCKS for HTTP so we get the login + // screen and HTTP tables via SOCKS. + //------------------------------------------------- + LLStartUp::startLLProxy(); + + gSavedSettings.setS32("LastFeatureVersion", LLFeatureManager::getInstance()->getVersion()); + gSavedSettings.setString("LastGPUString", thisGPU); + + + std::string xml_file = LLUI::locateSkin("xui_version.xml"); + LLXMLNodePtr root; + bool xml_ok = false; + if (LLXMLNode::parseFile(xml_file, root, NULL)) + { + if( (root->hasName("xui_version") ) ) + { + std::string value = root->getValue(); + F32 version = 0.0f; + LLStringUtil::convertToF32(value, version); + if (version >= 1.0f) + { + xml_ok = true; + } + } + } + if (!xml_ok) + { + // If XML is bad, there's a good possibility that notifications.xml is ALSO bad. + // If that's so, then we'll get a fatal error on attempting to load it, + // which will display a nontranslatable error message that says so. + // Otherwise, we'll display a reasonable error message that IS translatable. + LLAppViewer::instance()->earlyExit("BadInstallation"); + } + // + // Statistics stuff + // + + // Load autopilot and stats stuff + gAgentPilot.load(); + + //gErrorStream.setTime(gSavedSettings.getBOOL("LogTimestamps")); + + // Load the throttle settings + gViewerThrottle.load(); + + // + // Initialize messaging system + // + LL_DEBUGS("AppInit") << "Initializing messaging system..." << LL_ENDL; + + std::string message_template_path = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS,"message_template.msg"); + + LLFILE* found_template = NULL; + found_template = LLFile::fopen(message_template_path, "r"); /* Flawfinder: ignore */ + + #if LL_WINDOWS + // On the windows dev builds, unpackaged, the message_template.msg + // file will be located in: + // build-vc**/newview//app_settings + if (!found_template) + { + message_template_path = gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, "app_settings", "message_template.msg"); + found_template = LLFile::fopen(message_template_path.c_str(), "r"); /* Flawfinder: ignore */ + } + #elif LL_DARWIN + // On Mac dev builds, message_template.msg lives in: + // indra/build-*/newview//Second Life/Contents/Resources/app_settings + if (!found_template) + { + message_template_path = + gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, + "message_template.msg"); + found_template = LLFile::fopen(message_template_path.c_str(), "r"); /* Flawfinder: ignore */ + } + #endif + + if (found_template) + { + fclose(found_template); + + U32 port = gSavedSettings.getU32("UserConnectionPort"); + + if ((NET_USE_OS_ASSIGNED_PORT == port) && // if nothing specified on command line (-port) + (gSavedSettings.getBOOL("ConnectionPortEnabled"))) + { + port = gSavedSettings.getU32("ConnectionPort"); + } + + // TODO parameterize + const F32 circuit_heartbeat_interval = 5; + const F32 circuit_timeout = 100; + + const LLUseCircuitCodeResponder* responder = NULL; + bool failure_is_fatal = true; + + if(!start_messaging_system( + message_template_path, + port, + LLVersionInfo::instance().getMajor(), + LLVersionInfo::instance().getMinor(), + LLVersionInfo::instance().getPatch(), + false, + std::string(), + responder, + failure_is_fatal, + circuit_heartbeat_interval, + circuit_timeout)) + { + std::string diagnostic = llformat(" Error: %d", gMessageSystem->getErrorCode()); + LL_WARNS("AppInit") << diagnostic << LL_ENDL; + LLAppViewer::instance()->earlyExit("LoginFailedNoNetwork", LLSD().with("DIAGNOSTIC", diagnostic)); + } + + #if LL_WINDOWS + // On the windows dev builds, unpackaged, the message.xml file will + // be located in indra/build-vc**/newview//app_settings. + std::string message_path = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS,"message.xml"); + + if (!LLFile::isfile(message_path.c_str())) + { + LLMessageConfig::initClass("viewer", gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, "app_settings", "")); + } + else + { + LLMessageConfig::initClass("viewer", gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "")); + } + #else + LLMessageConfig::initClass("viewer", gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "")); + #endif + + } + else + { + LLAppViewer::instance()->earlyExit("MessageTemplateNotFound", LLSD().with("PATH", message_template_path)); + } + + if(gMessageSystem && gMessageSystem->isOK()) + { + // Initialize all of the callbacks in case of bad message + // system data + LLMessageSystem* msg = gMessageSystem; + msg->setExceptionFunc(MX_UNREGISTERED_MESSAGE, + invalid_message_callback, + NULL); + msg->setExceptionFunc(MX_PACKET_TOO_SHORT, + invalid_message_callback, + NULL); + + // running off end of a packet is now valid in the case + // when a reader has a newer message template than + // the sender + /*msg->setExceptionFunc(MX_RAN_OFF_END_OF_PACKET, + invalid_message_callback, + NULL);*/ + msg->setExceptionFunc(MX_WROTE_PAST_BUFFER_SIZE, + invalid_message_callback, + NULL); + + if (gSavedSettings.getBOOL("LogMessages")) + { + LL_DEBUGS("AppInit") << "Message logging activated!" << LL_ENDL; + msg->startLogging(); + } + + // start the xfer system. by default, choke the downloads + // a lot... + const S32 VIEWER_MAX_XFER = 3; + start_xfer_manager(); + gXferManager->setMaxIncomingXfers(VIEWER_MAX_XFER); + F32 xfer_throttle_bps = gSavedSettings.getF32("XferThrottle"); + if (xfer_throttle_bps > 1.f) + { + gXferManager->setUseAckThrottling(true); + gXferManager->setAckThrottleBPS(xfer_throttle_bps); + } + gAssetStorage = new LLViewerAssetStorage(msg, gXferManager); + + + F32 dropPercent = gSavedSettings.getF32("PacketDropPercentage"); + msg->mPacketRing.setDropPercentage(dropPercent); + + F32 inBandwidth = gSavedSettings.getF32("InBandwidth"); + F32 outBandwidth = gSavedSettings.getF32("OutBandwidth"); + if (inBandwidth != 0.f) + { + LL_DEBUGS("AppInit") << "Setting packetring incoming bandwidth to " << inBandwidth << LL_ENDL; + msg->mPacketRing.setUseInThrottle(true); + msg->mPacketRing.setInBandwidth(inBandwidth); + } + if (outBandwidth != 0.f) + { + LL_DEBUGS("AppInit") << "Setting packetring outgoing bandwidth to " << outBandwidth << LL_ENDL; + msg->mPacketRing.setUseOutThrottle(true); + msg->mPacketRing.setOutBandwidth(outBandwidth); + } + } + + LL_INFOS("AppInit") << "Message System Initialized." << LL_ENDL; + + //------------------------------------------------- + // Init audio, which may be needed for prefs dialog + // or audio cues in connection UI. + //------------------------------------------------- + + if (false == gSavedSettings.getBOOL("NoAudio")) + { + delete gAudiop; + gAudiop = NULL; + +#ifdef LL_OPENAL +#if !LL_WINDOWS + if (NULL == getenv("LL_BAD_OPENAL_DRIVER")) +#endif // !LL_WINDOWS + { + gAudiop = (LLAudioEngine *) new LLAudioEngine_OpenAL(); + } +#endif + + if (gAudiop) + { +#if LL_WINDOWS + // FMOD Ex on Windows needs the window handle to stop playing audio + // when window is minimized. JC + void* window_handle = (HWND)gViewerWindow->getPlatformWindow(); +#else + void* window_handle = NULL; +#endif + if (gAudiop->init(window_handle, LLAppViewer::instance()->getSecondLifeTitle())) + { + LL_INFOS("AppInit") << "Using media plugins to render streaming audio" << LL_ENDL; + gAudiop->setStreamingAudioImpl(new LLStreamingAudio_MediaPlugins()); + + gAudiop->setMuted(true); + } + else + { + LL_WARNS("AppInit") << "Unable to initialize audio engine" << LL_ENDL; + delete gAudiop; + gAudiop = NULL; + } + } + } + + LL_INFOS("AppInit") << "Audio Engine Initialized." << LL_ENDL; + + if (LLTimer::knownBadTimer()) + { + LL_WARNS("AppInit") << "Unreliable timers detected (may be bad PCI chipset)!!" << LL_ENDL; + } + + // + // Log on to system + // + if (gUserCredential.isNull()) + { + gUserCredential = gLoginHandler.initializeLoginInfo(); + } + // Previous initializeLoginInfo may have generated user credentials. Re-check them. + if (gUserCredential.isNull()) + { + show_connect_box = true; + } + else if (gSavedSettings.getBOOL("AutoLogin")) + { + // Log into last account + gRememberPassword = true; + gRememberUser = true; + gSavedSettings.setBOOL("RememberPassword", true); + gSavedSettings.setBOOL("RememberUser", true); + show_connect_box = false; + } + else if (gSavedSettings.getLLSD("UserLoginInfo").size() == 3) + { + // Console provided login&password + gRememberPassword = gSavedSettings.getBOOL("RememberPassword"); + gRememberUser = gSavedSettings.getBOOL("RememberUser"); + show_connect_box = false; + } + else + { + gRememberPassword = gSavedSettings.getBOOL("RememberPassword"); + gRememberUser = gSavedSettings.getBOOL("RememberUser"); + show_connect_box = true; + } + + //setup map of datetime strings to codes and slt & local time offset from utc + // *TODO: Does this need to be here? + LLStringOps::setupDatetimeInfo(false); + + // Go to the next startup state + LLStartUp::setStartupState( STATE_BROWSER_INIT ); + return false; + } + + + if (STATE_BROWSER_INIT == LLStartUp::getStartupState()) + { + LL_DEBUGS("AppInit") << "STATE_BROWSER_INIT" << LL_ENDL; + std::string msg = LLTrans::getString("LoginInitializingBrowser"); + set_startup_status(0.03f, msg.c_str(), gAgent.mMOTD.c_str()); + display_startup(); + // LLViewerMedia::initBrowser(); + LLStartUp::setStartupState( STATE_LOGIN_SHOW ); + return false; + } + + + if (STATE_LOGIN_SHOW == LLStartUp::getStartupState()) + { + LL_DEBUGS("AppInit") << "Initializing Window, show_connect_box = " + << show_connect_box << LL_ENDL; + + // if we've gone backwards in the login state machine, to this state where we show the UI + // AND the debug setting to exit in this case is true, then go ahead and bail quickly + if ( mLoginStatePastUI && gSavedSettings.getBOOL("QuitOnLoginActivated") ) + { + LL_DEBUGS("AppInit") << "taking QuitOnLoginActivated exit" << LL_ENDL; + // no requirement for notification here - just exit + LLAppViewer::instance()->earlyExitNoNotify(); + } + + gViewerWindow->getWindow()->setCursor(UI_CURSOR_ARROW); + + // Login screen needs menus for preferences, but we can enter + // this startup phase more than once. + if (gLoginMenuBarView == NULL) + { + LL_DEBUGS("AppInit") << "initializing menu bar" << LL_ENDL; + initialize_spellcheck_menu(); + init_menus(); + } + show_release_notes_if_required(); + + if (show_connect_box) + { + LL_DEBUGS("AppInit") << "show_connect_box on" << LL_ENDL; + // Load all the name information out of the login view + // NOTE: Hits "Attempted getFields with no login view shown" warning, since we don't + // show the login view until login_show() is called below. + if (gUserCredential.isNull()) + { + LL_DEBUGS("AppInit") << "loading credentials from gLoginHandler" << LL_ENDL; + gUserCredential = gLoginHandler.initializeLoginInfo(); + } + // Make sure the process dialog doesn't hide things + gViewerWindow->setShowProgress(false); + // Show the login dialog + login_show(); + // connect dialog is already shown, so fill in the names + LLPanelLogin::populateFields( gUserCredential, gRememberUser, gRememberPassword); + LLPanelLogin::giveFocus(); + + // MAINT-3231 Show first run dialog only for Desura viewer + if (gSavedSettings.getString("sourceid") == "1208_desura") + { + if (gSavedSettings.getBOOL("FirstLoginThisInstall")) + { + LL_INFOS("AppInit") << "FirstLoginThisInstall, calling show_first_run_dialog()" << LL_ENDL; + show_first_run_dialog(); + } + else + { + LL_DEBUGS("AppInit") << "FirstLoginThisInstall off" << LL_ENDL; + } + } + display_startup(); + LLStartUp::setStartupState( STATE_LOGIN_WAIT ); // Wait for user input + } + else + { + LL_DEBUGS("AppInit") << "show_connect_box off, skipping to STATE_LOGIN_CLEANUP" << LL_ENDL; + // skip directly to message template verification + LLStartUp::setStartupState( STATE_LOGIN_CLEANUP ); + } + + gViewerWindow->setNormalControlsVisible( false ); + gLoginMenuBarView->setVisible( true ); + gLoginMenuBarView->setEnabled( true ); + show_debug_menus(); + + // Hide the splash screen + LL_DEBUGS("AppInit") << "Hide the splash screen and show window" << LL_ENDL; + LLSplashScreen::hide(); + // Push our window frontmost + gViewerWindow->getWindow()->show(); + + // DEV-16927. The following code removes errant keystrokes that happen while the window is being + // first made visible. +#ifdef _WIN32 + LL_DEBUGS("AppInit") << "Processing PeekMessage" << LL_ENDL; + MSG msg; + while( PeekMessage( &msg, /*All hWnds owned by this thread */ NULL, WM_KEYFIRST, WM_KEYLAST, PM_REMOVE ) ) + { + } + LL_DEBUGS("AppInit") << "PeekMessage processed" << LL_ENDL; +#endif + display_startup(); + timeout.reset(); + return false; + } + + if (STATE_LOGIN_WAIT == LLStartUp::getStartupState()) + { + // when we get to this state, we've already been past the login UI + // (possiblely automatically) - flag this so we can test in the + // STATE_LOGIN_SHOW state if we've gone backwards + mLoginStatePastUI = true; + + // Don't do anything. Wait for the login view to call the login_callback, + // which will push us to the next state. + + // display() function will be the one to run display_startup() + // Sleep so we don't spin the CPU + ms_sleep(1); + return false; + } + + if (STATE_LOGIN_CLEANUP == LLStartUp::getStartupState()) + { + // Post login screen, we should see if any settings have changed that may + // require us to either start/stop or change the socks proxy. As various communications + // past this point may require the proxy to be up. + if (!LLStartUp::startLLProxy()) + { + // Proxy start up failed, we should now bail the state machine + // startLLProxy() will have reported an error to the user + // already, so we just go back to the login screen. The user + // could then change the preferences to fix the issue. + + LLStartUp::setStartupState(STATE_LOGIN_SHOW); + return false; + } + + // reset the values that could have come in from a slurl + // DEV-42215: Make sure they're not empty -- gUserCredential + // might already have been set from gSavedSettings, and it's too bad + // to overwrite valid values with empty strings. + + if (show_connect_box) + { + // TODO if not use viewer auth + // Load all the name information out of the login view + LLPanelLogin::getFields(gUserCredential, gRememberUser, gRememberPassword); + // end TODO + + // HACK: Try to make not jump on login + gKeyboard->resetKeys(); + } + + // when we get to this state, we've already been past the login UI + // (possiblely automatically) - flag this so we can test in the + // STATE_LOGIN_SHOW state if we've gone backwards + mLoginStatePastUI = true; + + // save the credentials + std::string userid = "unknown"; + if (gUserCredential.notNull()) + { + userid = gUserCredential->userID(); + if (gRememberUser) + { + gSecAPIHandler->addToCredentialMap("login_list", gUserCredential, gRememberPassword); + // Legacy viewers use this method to store user credentials, newer viewers + // reuse it to be compatible and to remember last session + gSecAPIHandler->saveCredential(gUserCredential, gRememberPassword); + } + } + gSavedSettings.setBOOL("RememberPassword", gRememberPassword); + gSavedSettings.setBOOL("RememberUser", gRememberUser); + LL_INFOS("AppInit") << "Attempting login as: " << userid << LL_ENDL; + gDebugInfo["LoginName"] = userid; + + // create necessary directories + // *FIX: these mkdir's should error check + gDirUtilp->setLindenUserDir(userid); + LLFile::mkdir(gDirUtilp->getLindenUserDir()); + + // As soon as directories are ready initialize notification storages + if (!LLPersistentNotificationStorage::instanceExists()) + { + // check existance since this part of code can be reached + // twice due to login failures + LLPersistentNotificationStorage::initParamSingleton(); + LLDoNotDisturbNotificationStorage::initParamSingleton(); + } + else + { + // reinitialize paths in case user switched grids or accounts + LLPersistentNotificationStorage::getInstance()->reset(); + LLDoNotDisturbNotificationStorage::getInstance()->reset(); + } + + // Set PerAccountSettingsFile to the default value. + std::string settings_per_account = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, LLAppViewer::instance()->getSettingsFilename("Default", "PerAccount")); + gSavedSettings.setString("PerAccountSettingsFile", settings_per_account); + gDebugInfo["PerAccountSettingsFilename"] = settings_per_account; + + // Note: can't store warnings files per account because some come up before login + + // Overwrite default user settings with user settings + LLAppViewer::instance()->loadSettingsFromDirectory("Account"); + + // Convert 'LogInstantMessages' into 'KeepConversationLogTranscripts' for backward compatibility (CHUI-743). + LLControlVariablePtr logInstantMessagesControl = gSavedPerAccountSettings.getControl("LogInstantMessages"); + if (logInstantMessagesControl.notNull()) + { + gSavedPerAccountSettings.setS32("KeepConversationLogTranscripts", logInstantMessagesControl->getValue() ? 2 : 1); + } + + // Need to set the LastLogoff time here if we don't have one. LastLogoff is used for "Recent Items" calculation + // and startup time is close enough if we don't have a real value. + if (gSavedPerAccountSettings.getU32("LastLogoff") == 0) + { + gSavedPerAccountSettings.setU32("LastLogoff", time_corrected()); + } + + //Default the path if one isn't set. + // *NOTE: unable to check variable differ from "InstantMessageLogPath" because it was + // provided in pre 2.0 viewer. See EXT-6661 + if (gSavedPerAccountSettings.getString("InstantMessageLogPath").empty()) + { + gDirUtilp->setChatLogsDir(gDirUtilp->getOSUserAppDir()); + gSavedPerAccountSettings.setString("InstantMessageLogPath", gDirUtilp->getChatLogsDir()); + } + else + { + gDirUtilp->setChatLogsDir(gSavedPerAccountSettings.getString("InstantMessageLogPath")); + } + gDirUtilp->setPerAccountChatLogsDir(userid); + + LLFile::mkdir(gDirUtilp->getChatLogsDir()); + LLFile::mkdir(gDirUtilp->getPerAccountChatLogsDir()); + + if (show_connect_box) + { + LLSLURL slurl; + //LLPanelLogin::closePanel(); + } + + + // Load URL History File + LLURLHistory::loadFile("url_history.xml"); + // Load location history + LLLocationHistory::getInstance()->load(); + + // Load Avatars icons cache + LLAvatarIconIDCache::getInstance()->load(); + + LLRenderMuteList::getInstance()->loadFromFile(); + + //------------------------------------------------- + // Handle startup progress screen + //------------------------------------------------- + + // on startup the user can request to go to their home, + // their last location, or some URL "-url //sim/x/y[/z]" + // All accounts have both a home and a last location, and we don't support + // more locations than that. Choose the appropriate one. JC + switch (LLStartUp::getStartSLURL().getType()) + { + case LLSLURL::LOCATION: + agent_location_id = START_LOCATION_ID_URL; + break; + case LLSLURL::LAST_LOCATION: + agent_location_id = START_LOCATION_ID_LAST; + break; + default: + agent_location_id = START_LOCATION_ID_HOME; + break; + } + + gViewerWindow->getWindow()->setCursor(UI_CURSOR_WAIT); + + // Display the startup progress bar. + gViewerWindow->initTextures(agent_location_id); + gViewerWindow->setShowProgress(true); + gViewerWindow->setProgressCancelButtonVisible(true, LLTrans::getString("Quit")); + + gViewerWindow->revealIntroPanel(); + + LLStartUp::setStartupState( STATE_LOGIN_AUTH_INIT ); + + return false; + } + + if(STATE_LOGIN_AUTH_INIT == LLStartUp::getStartupState()) + { + gDebugInfo["GridName"] = LLGridManager::getInstance()->getGridId(); + + // Update progress status and the display loop. + auth_desc = LLTrans::getString("LoginInProgress"); + set_startup_status(progress, auth_desc, auth_message); + progress += 0.02f; + display_startup(); + + // Setting initial values... + LLLoginInstance* login = LLLoginInstance::getInstance(); + login->setNotificationsInterface(LLNotifications::getInstance()); + + login->setSerialNumber(LLAppViewer::instance()->getSerialNumber()); + login->setLastExecEvent(gLastExecEvent); + login->setLastExecDuration(gLastExecDuration); + + // This call to LLLoginInstance::connect() starts the + // authentication process. + login->connect(gUserCredential); + + LLStartUp::setStartupState( STATE_LOGIN_CURL_UNSTUCK ); + return false; + } + + if(STATE_LOGIN_CURL_UNSTUCK == LLStartUp::getStartupState()) + { + // If we get here we have gotten past the potential stall + // in curl, so take "may appear frozen" out of progress bar. JC + auth_desc = LLTrans::getString("LoginInProgressNoFrozen"); + set_startup_status(progress, auth_desc, auth_message); + + LLStartUp::setStartupState( STATE_LOGIN_PROCESS_RESPONSE ); + return false; + } + + if(STATE_LOGIN_PROCESS_RESPONSE == LLStartUp::getStartupState()) + { + // Generic failure message + std::ostringstream emsg; + emsg << LLTrans::getString("LoginFailedHeader") << "\n"; + if(LLLoginInstance::getInstance()->authFailure()) + { + LL_INFOS("LLStartup") << "Login failed, LLLoginInstance::getResponse(): " + << LLLoginInstance::getInstance()->getResponse() << LL_ENDL; + LLSD response = LLLoginInstance::getInstance()->getResponse(); + // Still have error conditions that may need some + // sort of handling - dig up specific message + std::string reason_response = response["reason"]; + std::string message_response = response["message"]; + std::string message_id = response["message_id"]; + std::string message; // actual string to show the user + + bool localized_by_id = false; + if(!message_id.empty()) + { + LLSD message_args = response["message_args"]; + if (message_args.has("TIME") + && (message_id == "LoginFailedAcountSuspended" + || message_id == "LoginFailedAccountMaintenance")) + { + LLDate date; + std::string time_string; + if (date.fromString(message_args["TIME"].asString())) + { + LLSD args; + args["datetime"] = (S32)date.secondsSinceEpoch(); + LLTrans::findString(time_string, "LocalTime", args); + } + else + { + time_string = message_args["TIME"].asString() + " " + LLTrans::getString("PacificTime"); + } + + message_args["TIME"] = time_string; + } + // message will be filled in with the template and arguments + if (LLTrans::findString(message, message_id, message_args)) + { + localized_by_id = true; + } + } + + if(!localized_by_id && !message_response.empty()) + { + // *HACK: "no_inventory_host" sent as the message itself. + // Remove this clause when server is sending message_id as well. + message = LLAgent::sTeleportErrorMessages[ message_response ]; + } + + if (message.empty()) + { + // Fallback to server-supplied string; necessary since server + // may add strings that this viewer is not yet aware of + message = message_response; + } + + emsg << message; + + + if(reason_response == "key") + { + // Couldn't login because user/password is wrong + // Clear the credential + gUserCredential->clearAuthenticator(); + } + + if(reason_response == "update" + || reason_response == "optional") + { + // In the case of a needed update, quit. + // Its either downloading or declined. + // If optional was skipped this case shouldn't + // be reached. + + LL_INFOS("LLStartup") << "Forcing a quit due to update." << LL_ENDL; + LLLoginInstance::getInstance()->disconnect(); + LLAppViewer::instance()->forceQuit(); + } + else + { + if (reason_response != "tos" && reason_response != "mfa_challenge") + { + // Don't pop up a notification in the TOS or MFA cases because + // the specialized floater has already scolded the user. + std::string error_code; + if(response.has("errorcode")) + { + error_code = response["errorcode"].asString(); + } + if ((reason_response == "CURLError") && + (error_code == "SSL_CACERT" || error_code == "SSL_PEER_CERTIFICATE") && + response.has("certificate")) + { + // This was a certificate error, so grab the certificate + // and throw up the appropriate dialog. + LLPointer certificate; + try + { + certificate = gSecAPIHandler->getCertificate(response["certificate"]); + } + catch (LLCertException &cert_exception) + { + LL_WARNS("LLStartup", "SECAPI") << "Caught " << cert_exception.what() << " certificate expception on getCertificate("<< response["certificate"] << ")" << LL_ENDL; + LLSD args; + args["REASON"] = LLTrans::getString(cert_exception.what()); + + LLNotificationsUtil::add("GeneralCertificateErrorShort", args, response, + general_cert_done); + + reset_login(); + gSavedSettings.setBOOL("AutoLogin", false); + show_connect_box = true; + } + if(certificate) + { + LLSD args = transform_cert_args(certificate); + + if(error_code == "SSL_CACERT") + { + // if we are handling an untrusted CA, throw up the dialog + // with the 'trust this CA' button. + LLNotificationsUtil::add("TrustCertificateError", args, response, + trust_cert_done); + + show_connect_box = true; + } + else + { + // the certificate exception returns a unique string for each type of exception. + // we grab this string via the LLUserAuth object, and use that to grab the localized + // string. + args["REASON"] = LLTrans::getString(message_response); + + LLNotificationsUtil::add("GeneralCertificateError", args, response, + general_cert_done); + + reset_login(); + gSavedSettings.setBOOL("AutoLogin", false); + show_connect_box = true; + + } + + } + } + else if (reason_response == "BadType") + { + LLNotificationsUtil::add("LoginFailedToParse", LLSD(), LLSD(), login_alert_done); + } + else if (!message.empty()) + { + // This wasn't a certificate error, so throw up the normal + // notificatioin message. + LLSD args; + args["ERROR_MESSAGE"] = emsg.str(); + LL_INFOS("LLStartup") << "Notification: " << args << LL_ENDL; + LLNotificationsUtil::add("ErrorMessage", args, LLSD(), login_alert_done); + } + } + transition_back_to_login_panel(emsg.str()); + show_connect_box = true; + } + } + else if(LLLoginInstance::getInstance()->authSuccess()) + { + if(process_login_success_response()) + { + // Pass the user information to the voice chat server interface. + LLVoiceClient::getInstance()->userAuthorized(gUserCredential->userID(), gAgentID); + // create the default proximal channel + LLVoiceChannel::initClass(); + LLStartUp::setStartupState( STATE_WORLD_INIT); + LLTrace::get_frame_recording().reset(); + } + else + { + LLSD args; + args["ERROR_MESSAGE"] = emsg.str(); + LL_INFOS("LLStartup") << "Notification: " << args << LL_ENDL; + LLNotificationsUtil::add("ErrorMessage", args, LLSD(), login_alert_done); + transition_back_to_login_panel(emsg.str()); + show_connect_box = true; + return false; + } + } + return false; + } + + //--------------------------------------------------------------------- + // World Init + //--------------------------------------------------------------------- + if (STATE_WORLD_INIT == LLStartUp::getStartupState()) + { + set_startup_status(0.30f, LLTrans::getString("LoginInitializingWorld"), gAgent.mMOTD); + display_startup(); + // We should have an agent id by this point. + llassert(!(gAgentID == LLUUID::null)); + + // Finish agent initialization. (Requires gSavedSettings, builds camera) + gAgent.init(); + display_startup(); + gAgentCamera.init(); + display_startup(); + display_startup(); + + // Since we connected, save off the settings so the user doesn't have to + // type the name/password again if we crash. + gSavedSettings.saveToFile(gSavedSettings.getString("ClientSettingsFile"), true); + LLUIColorTable::instance().saveUserSettings(); + + display_startup(); + + // + // Initialize classes w/graphics stuff. + // + LLViewerStatsRecorder::instance(); // Since textures work in threads + LLSurface::initClasses(); + display_startup(); + + display_startup(); + + LLDrawable::initClass(); + display_startup(); + + // init the shader managers + LLPostProcess::initClass(); + display_startup(); + + LLAvatarAppearance::initClass("avatar_lad.xml","avatar_skeleton.xml"); + display_startup(); + + LLViewerObject::initVOClasses(); + display_startup(); + + // Initialize all our tools. Must be done after saved settings loaded. + // NOTE: This also is where gToolMgr used to be instantiated before being turned into a singleton. + LLToolMgr::getInstance()->initTools(); + display_startup(); + + // Pre-load floaters, like the world map, that are slow to spawn + // due to XML complexity. + gViewerWindow->initWorldUI(); + + display_startup(); + + // This is where we used to initialize gWorldp. Original comment said: + // World initialization must be done after above window init + + // User might have overridden far clip + LLWorld::getInstance()->setLandFarClip(gAgentCamera.mDrawDistance); + display_startup(); + // Before we create the first region, we need to set the agent's mOriginGlobal + // This is necessary because creating objects before this is set will result in a + // bad mPositionAgent cache. + + gAgent.initOriginGlobal(from_region_handle(gFirstSimHandle)); + display_startup(); + + LLWorld::getInstance()->addRegion(gFirstSimHandle, gFirstSim); + display_startup(); + + LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle(gFirstSimHandle); + LL_INFOS("AppInit") << "Adding initial simulator " << regionp->getOriginGlobal() << LL_ENDL; + + LL_DEBUGS("CrossingCaps") << "Calling setSeedCapability from init_idle(). Seed cap == " + << gFirstSimSeedCap << LL_ENDL; + regionp->setSeedCapability(gFirstSimSeedCap); + LL_DEBUGS("AppInit") << "Waiting for seed grant ...." << LL_ENDL; + display_startup(); + // Set agent's initial region to be the one we just created. + gAgent.setRegion(regionp); + display_startup(); + // Set agent's initial position, which will be read by LLVOAvatar when the avatar + // object is created. I think this must be done after setting the region. JC + gAgent.setPositionAgent(agent_start_position_region); + + display_startup(); + LLStartUp::initExperiences(); + + display_startup(); + + // If logging should be enebled, turns it on and loads history from disk + // Note: does not happen on init of singleton because preferences can use + // this instance without logging in + LLConversationLog::getInstance()->initLoggingState(); + + LLStartUp::setStartupState( STATE_MULTIMEDIA_INIT ); + + return false; + } + + + //--------------------------------------------------------------------- + // Load QuickTime/GStreamer and other multimedia engines, can be slow. + // Do it while we're waiting on the network for our seed capability. JC + //--------------------------------------------------------------------- + if (STATE_MULTIMEDIA_INIT == LLStartUp::getStartupState()) + { + LLStartUp::multimediaInit(); + LLStartUp::setStartupState( STATE_FONT_INIT ); + display_startup(); + return false; + } + + // Loading fonts takes several seconds + if (STATE_FONT_INIT == LLStartUp::getStartupState()) + { + LLStartUp::fontInit(); + LLStartUp::setStartupState( STATE_SEED_GRANTED_WAIT ); + display_startup(); + return false; + } + + //--------------------------------------------------------------------- + // Wait for Seed Cap Grant + //--------------------------------------------------------------------- + if(STATE_SEED_GRANTED_WAIT == LLStartUp::getStartupState()) + { + LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle(gFirstSimHandle); + if (regionp->capabilitiesReceived()) + { + LLStartUp::setStartupState( STATE_SEED_CAP_GRANTED ); + } + else if (regionp->capabilitiesError()) + { + LL_WARNS("AppInit") << "Failed to get capabilities. Backing up to login screen!" << LL_ENDL; + if (gRememberPassword) + { + LLNotificationsUtil::add("LoginPacketNeverReceived", LLSD(), LLSD(), login_alert_status); + } + else + { + LLNotificationsUtil::add("LoginPacketNeverReceivedNoTP", LLSD(), LLSD(), login_alert_status); + } + reset_login(); + } + else + { + U32 num_retries = regionp->getNumSeedCapRetries(); + if (num_retries > MAX_SEED_CAP_ATTEMPTS_BEFORE_ABORT) + { + LL_WARNS("AppInit") << "Failed to get capabilities. Backing up to login screen!" << LL_ENDL; + if (gRememberPassword) + { + LLNotificationsUtil::add("LoginPacketNeverReceived", LLSD(), LLSD(), login_alert_status); + } + else + { + LLNotificationsUtil::add("LoginPacketNeverReceivedNoTP", LLSD(), LLSD(), login_alert_status); + } + reset_login(); + } + else if (num_retries > 0) + { + LLStringUtil::format_map_t args; + args["[NUMBER]"] = llformat("%d", num_retries + 1); + set_startup_status(0.4f, LLTrans::getString("LoginRetrySeedCapGrant", args), gAgent.mMOTD.c_str()); + } + else + { + set_startup_status(0.4f, LLTrans::getString("LoginRequestSeedCapGrant"), gAgent.mMOTD.c_str()); + } + } + display_startup(); + return false; + } + + + //--------------------------------------------------------------------- + // Seed Capability Granted + // no newMessage calls should happen before this point + //--------------------------------------------------------------------- + if (STATE_SEED_CAP_GRANTED == LLStartUp::getStartupState()) + { + display_startup(); + + // These textures are not warrantied to be cached, so needs + // to hapen with caps granted + gTextureList.doPrefetchImages(); + + // will init images, should be done with caps, but before gSky.init() + LLEnvironment::getInstance()->initSingleton(); + + display_startup(); + update_texture_fetch(); + display_startup(); + + if ( gViewerWindow != NULL) + { // This isn't the first logon attempt, so show the UI + gViewerWindow->setNormalControlsVisible( true ); + } + gLoginMenuBarView->setVisible( false ); + gLoginMenuBarView->setEnabled( false ); + display_startup(); + + // direct logging to the debug console's line buffer + LLError::logToFixedBuffer(gDebugView->mDebugConsolep); + display_startup(); + + // set initial visibility of debug console + gDebugView->mDebugConsolep->setVisible(gSavedSettings.getBOOL("ShowDebugConsole")); + display_startup(); + + // + // Set message handlers + // + LL_INFOS("AppInit") << "Initializing communications..." << LL_ENDL; + + // register callbacks for messages. . . do this after initial handshake to make sure that we don't catch any unwanted + register_viewer_callbacks(gMessageSystem); + display_startup(); + + // Debugging info parameters + gMessageSystem->setMaxMessageTime( 0.5f ); // Spam if decoding all msgs takes more than 500 ms + display_startup(); + + #ifndef LL_RELEASE_FOR_DOWNLOAD + gMessageSystem->setTimeDecodes( true ); // Time the decode of each msg + gMessageSystem->setTimeDecodesSpamThreshold( 0.05f ); // Spam if a single msg takes over 50ms to decode + #endif + display_startup(); + + gXferManager->registerCallbacks(gMessageSystem); + display_startup(); + + LLStartUp::initNameCache(); + display_startup(); + + // update the voice settings *after* gCacheName initialization + // so that we can construct voice UI that relies on the name cache + if (LLVoiceClient::instanceExists()) + { + LLVoiceClient::getInstance()->updateSettings(); + } + display_startup(); + + // create a container's instance for start a controlling conversation windows + // by the voice's events + LLFloaterIMContainer::getInstance(); + if (gSavedSettings.getS32("ParcelMediaAutoPlayEnable") == 2) + { + LLViewerParcelAskPlay::getInstance()->loadSettings(); + } + + gAgent.addRegionChangedCallback(boost::bind(&LLPerfStats::StatsRecorder::clearStats)); + + // *Note: this is where gWorldMap used to be initialized. + + // register null callbacks for audio until the audio system is initialized + gMessageSystem->setHandlerFuncFast(_PREHASH_SoundTrigger, null_message_callback, NULL); + gMessageSystem->setHandlerFuncFast(_PREHASH_AttachedSound, null_message_callback, NULL); + display_startup(); + + //reset statistics + LLViewerStats::instance().resetStats(); + + display_startup(); + // + // Set up region and surface defaults + // + + + // Sets up the parameters for the first simulator + + LL_DEBUGS("AppInit") << "Initializing camera..." << LL_ENDL; + gFrameTime = totalTime(); + F32Seconds last_time = gFrameTimeSeconds; + gFrameTimeSeconds = (gFrameTime - gStartTime); + + gFrameIntervalSeconds = gFrameTimeSeconds - last_time; + if (gFrameIntervalSeconds < 0.f) + { + gFrameIntervalSeconds = 0.f; + } + + // Make sure agent knows correct aspect ratio + // FOV limits depend upon aspect ratio so this needs to happen before initializing the FOV below + LLViewerCamera::getInstance()->setViewHeightInPixels(gViewerWindow->getWorldViewHeightRaw()); + LLViewerCamera::getInstance()->setAspect(gViewerWindow->getWorldViewAspectRatio()); + // Initialize FOV + LLViewerCamera::getInstance()->setDefaultFOV(gSavedSettings.getF32("CameraAngle")); + display_startup(); + + // Move agent to starting location. The position handed to us by + // the space server is in global coordinates, but the agent frame + // is in region local coordinates. Therefore, we need to adjust + // the coordinates handed to us to fit in the local region. + + gAgent.setPositionAgent(agent_start_position_region); + gAgent.resetAxes(gAgentStartLookAt); + gAgentCamera.stopCameraAnimation(); + gAgentCamera.resetCamera(); + display_startup(); + + // Initialize global class data needed for surfaces (i.e. textures) + LL_DEBUGS("AppInit") << "Initializing sky..." << LL_ENDL; + // Initialize all of the viewer object classes for the first time (doing things like texture fetches. + LLGLState::checkStates(); + + gSky.init(); + + LLGLState::checkStates(); + + display_startup(); + + LL_DEBUGS("AppInit") << "Decoding images..." << LL_ENDL; + // For all images pre-loaded into viewer cache, init + // priorities and fetching using decodeAllImages. + // Most of the fetching and decoding likely to be done + // by update_texture_fetch() later, while viewer waits. + // + // Need to do this AFTER we init the sky + const S32 DECODE_TIME_SEC = 2; + for (int i = 0; i < DECODE_TIME_SEC; i++) + { + F32 frac = (F32)i / (F32)DECODE_TIME_SEC; + set_startup_status(0.45f + frac*0.1f, LLTrans::getString("LoginDecodingImages"), gAgent.mMOTD); + display_startup(); + gTextureList.decodeAllImages(1.f); + } + LLStartUp::setStartupState( STATE_WORLD_WAIT ); + + display_startup(); + + // JC - Do this as late as possible to increase likelihood Purify + // will run. + LLMessageSystem* msg = gMessageSystem; + if (!msg->mOurCircuitCode) + { + LL_WARNS("AppInit") << "Attempting to connect to simulator with a zero circuit code!" << LL_ENDL; + } + + gUseCircuitCallbackCalled = false; + + msg->enableCircuit(gFirstSim, true); + // now, use the circuit info to tell simulator about us! + LL_INFOS("AppInit") << "viewer: UserLoginLocationReply() Enabling " << gFirstSim << " with code " << msg->mOurCircuitCode << LL_ENDL; + msg->newMessageFast(_PREHASH_UseCircuitCode); + msg->nextBlockFast(_PREHASH_CircuitCode); + msg->addU32Fast(_PREHASH_Code, msg->mOurCircuitCode); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addUUIDFast(_PREHASH_ID, gAgent.getID()); + msg->sendReliable( + gFirstSim, + gSavedSettings.getS32("UseCircuitCodeMaxRetries"), + false, + (F32Seconds)gSavedSettings.getF32("UseCircuitCodeTimeout"), + use_circuit_callback, + NULL); + + timeout.reset(); + display_startup(); + + return false; + } + + //--------------------------------------------------------------------- + // World Wait + //--------------------------------------------------------------------- + if(STATE_WORLD_WAIT == LLStartUp::getStartupState()) + { + LL_DEBUGS("AppInit") << "Waiting for simulator ack...." << LL_ENDL; + set_startup_status(0.59f, LLTrans::getString("LoginWaitingForRegionHandshake"), gAgent.mMOTD); + if(gGotUseCircuitCodeAck) + { + LLStartUp::setStartupState( STATE_AGENT_SEND ); + } + pump_idle_startup_network(); + return false; + } + + //--------------------------------------------------------------------- + // Agent Send + //--------------------------------------------------------------------- + if (STATE_AGENT_SEND == LLStartUp::getStartupState()) + { + LL_DEBUGS("AppInit") << "Connecting to region..." << LL_ENDL; + set_startup_status(0.60f, LLTrans::getString("LoginConnectingToRegion"), gAgent.mMOTD); + display_startup(); + // register with the message system so it knows we're + // expecting this message + LLMessageSystem* msg = gMessageSystem; + msg->setHandlerFuncFast( + _PREHASH_AgentMovementComplete, + process_agent_movement_complete); + LLViewerRegion* regionp = gAgent.getRegion(); + if(regionp) + { + send_complete_agent_movement(regionp->getHost()); + gAssetStorage->setUpstream(regionp->getHost()); + gCacheName->setUpstream(regionp->getHost()); + } + display_startup(); + + // Create login effect + // But not on first login, because you can't see your avatar then + if (!gAgent.isFirstLogin()) + { + LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_POINT, true); + effectp->setPositionGlobal(gAgent.getPositionGlobal()); + effectp->setColor(LLColor4U(gAgent.getEffectColor())); + LLHUDManager::getInstance()->sendEffects(); + } + + LLStartUp::setStartupState( STATE_AGENT_WAIT ); // Go to STATE_AGENT_WAIT + + timeout.reset(); + display_startup(); + return false; + } + + //--------------------------------------------------------------------- + // Agent Wait + //--------------------------------------------------------------------- + if (STATE_AGENT_WAIT == LLStartUp::getStartupState()) + { + { + LockMessageChecker lmc(gMessageSystem); + while (lmc.checkAllMessages(gFrameCount, gServicePump)) + { + if (gAgentMovementCompleted) + { + // Sometimes we have more than one message in the + // queue. break out of this loop and continue + // processing. If we don't, then this could skip one + // or more login steps. + break; + } + else + { + LL_DEBUGS("AppInit") << "Awaiting AvatarInitComplete, got " + << gMessageSystem->getMessageName() << LL_ENDL; + } + display_startup(); + } + lmc.processAcks(); + } + + display_startup(); + + if (gAgentMovementCompleted) + { + LLStartUp::setStartupState( STATE_INVENTORY_SEND ); + } + display_startup(); + + if (!gAgentMovementCompleted && timeout.getElapsedTimeF32() > STATE_AGENT_WAIT_TIMEOUT) + { + LL_WARNS("AppInit") << "Backing up to login screen!" << LL_ENDL; + if (gRememberPassword) + { + LLNotificationsUtil::add("LoginPacketNeverReceived", LLSD(), LLSD(), login_alert_status); + } + else + { + LLNotificationsUtil::add("LoginPacketNeverReceivedNoTP", LLSD(), LLSD(), login_alert_status); + } + reset_login(); + } + return false; + } + + //--------------------------------------------------------------------- + // Inventory Send + //--------------------------------------------------------------------- + if (STATE_INVENTORY_SEND == LLStartUp::getStartupState()) + { + LL_PROFILE_ZONE_NAMED("State inventory send") + display_startup(); + + // request mute list + LL_INFOS() << "Requesting Mute List" << LL_ENDL; + LLMuteList::getInstance()->requestFromServer(gAgent.getID()); + + // Get L$ and ownership credit information + LL_INFOS() << "Requesting Money Balance" << LL_ENDL; + LLStatusBar::sendMoneyBalanceRequest(); + + display_startup(); + + // Inform simulator of our language preference + LLAgentLanguage::update(); + + display_startup(); + // unpack thin inventory + LLSD response = LLLoginInstance::getInstance()->getResponse(); + //bool dump_buffer = false; + + LLSD inv_lib_root = response["inventory-lib-root"]; + if(inv_lib_root.isDefined()) + { + // should only be one + LLSD id = inv_lib_root[0]["folder_id"]; + if(id.isDefined()) + { + gInventory.setLibraryRootFolderID(id.asUUID()); + } + } + display_startup(); + + LLSD inv_lib_owner = response["inventory-lib-owner"]; + if(inv_lib_owner.isDefined()) + { + // should only be one + LLSD id = inv_lib_owner[0]["agent_id"]; + if(id.isDefined()) + { + gInventory.setLibraryOwnerID(LLUUID(id.asUUID())); + } + } + display_startup(); + LLStartUp::setStartupState(STATE_INVENTORY_SKEL); + display_startup(); + return false; + } + + if (STATE_INVENTORY_SKEL == LLStartUp::getStartupState()) + { + LL_PROFILE_ZONE_NAMED("State inventory load skeleton") + + LLSD response = LLLoginInstance::getInstance()->getResponse(); + + LLSD inv_skel_lib = response["inventory-skel-lib"]; + if (inv_skel_lib.isDefined() && gInventory.getLibraryOwnerID().notNull()) + { + LL_PROFILE_ZONE_NAMED("load library inv") + if (!gInventory.loadSkeleton(inv_skel_lib, gInventory.getLibraryOwnerID())) + { + LL_WARNS("AppInit") << "Problem loading inventory-skel-lib" << LL_ENDL; + } + } + display_startup(); + + LLSD inv_skeleton = response["inventory-skeleton"]; + if (inv_skeleton.isDefined()) + { + LL_PROFILE_ZONE_NAMED("load personal inv") + if (!gInventory.loadSkeleton(inv_skeleton, gAgent.getID())) + { + LL_WARNS("AppInit") << "Problem loading inventory-skel-targets" << LL_ENDL; + } + } + display_startup(); + LLStartUp::setStartupState(STATE_INVENTORY_SEND2); + display_startup(); + return false; + } + + if (STATE_INVENTORY_SEND2 == LLStartUp::getStartupState()) + { + LL_PROFILE_ZONE_NAMED("State inventory send2") + + LLSD response = LLLoginInstance::getInstance()->getResponse(); + + LLSD inv_basic = response["inventory-basic"]; + if(inv_basic.isDefined()) + { + LL_INFOS() << "Basic inventory root folder id is " << inv_basic["folder_id"] << LL_ENDL; + } + + LLSD buddy_list = response["buddy-list"]; + if(buddy_list.isDefined()) + { + LLAvatarTracker::buddy_map_t list; + LLUUID agent_id; + S32 has_rights = 0, given_rights = 0; + for(LLSD::array_const_iterator it = buddy_list.beginArray(), + end = buddy_list.endArray(); it != end; ++it) + { + LLSD buddy_id = (*it)["buddy_id"]; + if(buddy_id.isDefined()) + { + agent_id = buddy_id.asUUID(); + } + + LLSD buddy_rights_has = (*it)["buddy_rights_has"]; + if(buddy_rights_has.isDefined()) + { + has_rights = buddy_rights_has.asInteger(); + } + + LLSD buddy_rights_given = (*it)["buddy_rights_given"]; + if(buddy_rights_given.isDefined()) + { + given_rights = buddy_rights_given.asInteger(); + } + + list[agent_id] = new LLRelationship(given_rights, has_rights, false); + } + LLAvatarTracker::instance().addBuddyList(list); + display_startup(); + } + + bool show_hud = false; + LLSD tutorial_setting = response["tutorial_setting"]; + if(tutorial_setting.isDefined()) + { + for(LLSD::array_const_iterator it = tutorial_setting.beginArray(), + end = tutorial_setting.endArray(); it != end; ++it) + { + LLSD tutorial_url = (*it)["tutorial_url"]; + if(tutorial_url.isDefined()) + { + // Tutorial floater will append language code + gSavedSettings.setString("TutorialURL", tutorial_url.asString()); + } + + // For Viewer 2.0 we are not using the web-based tutorial + // If we reverse that decision, put this code back and use + // login.cgi to send a different URL with content that matches + // the Viewer 2.0 UI. + //LLSD use_tutorial = (*it)["use_tutorial"]; + //if(use_tutorial.asString() == "true") + //{ + // show_hud = true; + //} + } + } + display_startup(); + + // Either we want to show tutorial because this is the first login + // to a Linden Help Island or the user quit with the tutorial + // visible. JC + if (show_hud || gSavedSettings.getBOOL("ShowTutorial")) + { + LLFloaterReg::showInstance("hud", LLSD(), false); + } + display_startup(); + + LLSD event_notifications = response["event_notifications"]; + if(event_notifications.isDefined()) + { + gEventNotifier.load(event_notifications); + } + display_startup(); + + LLSD classified_categories = response["classified_categories"]; + if(classified_categories.isDefined()) + { + LLClassifiedInfo::loadCategories(classified_categories); + } + display_startup(); + + // This method MUST be called before gInventory.findCategoryUUIDForType because of + // gInventory.mIsAgentInvUsable is set to true in the gInventory.buildParentChildMap. + gInventory.buildParentChildMap(); + + // If buildParentChildMap succeeded, inventory will now be in + // a usable state and gInventory.isInventoryUsable() will be + // true. + + // if inventory is unusable, show warning. + if (!gInventory.isInventoryUsable()) + { + LLNotificationsUtil::add("InventoryUnusable"); + } + + LLInventoryModelBackgroundFetch::instance().start(); + gInventory.createCommonSystemCategories(); + LLStartUp::setStartupState(STATE_INVENTORY_CALLBACKS ); + display_startup(); + + return false; + } + + //--------------------------------------------------------------------- + // STATE_INVENTORY_CALLBACKS + //--------------------------------------------------------------------- + if (STATE_INVENTORY_CALLBACKS == LLStartUp::getStartupState()) + { + if (!LLInventoryModel::isSysFoldersReady()) + { + display_startup(); + return false; + } + + LLInventoryModelBackgroundFetch::instance().start(); + LLAppearanceMgr::instance().initCOFID(); + LLUUID cof_id = LLAppearanceMgr::instance().getCOF(); + LLViewerInventoryCategory* cof = gInventory.getCategory(cof_id); + if (cof + && cof->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) + { + // Special case, dupplicate request prevention. + // Cof folder will be requested via FetchCOF + // in appearance manager, prevent recursive fetch + cof->setFetching(LLViewerInventoryCategory::FETCH_RECURSIVE); + } + + + // It's debatable whether this flag is a good idea - sets all + // bits, and in general it isn't true that inventory + // initialization generates all types of changes. Maybe add an + // INITIALIZE mask bit instead? + gInventory.addChangedMask(LLInventoryObserver::ALL, LLUUID::null); + gInventory.notifyObservers(); + + display_startup(); + + // set up callbacks + LL_INFOS() << "Registering Callbacks" << LL_ENDL; + LLMessageSystem* msg = gMessageSystem; + LL_INFOS() << " Inventory" << LL_ENDL; + LLInventoryModel::registerCallbacks(msg); + LL_INFOS() << " AvatarTracker" << LL_ENDL; + LLAvatarTracker::instance().registerCallbacks(msg); + LL_INFOS() << " Landmark" << LL_ENDL; + LLLandmark::registerCallbacks(msg); + display_startup(); + + // request all group information + LL_INFOS() << "Requesting Agent Data" << LL_ENDL; + gAgent.sendAgentDataUpdateRequest(); + display_startup(); + // Create the inventory views + LL_INFOS() << "Creating Inventory Views" << LL_ENDL; + LLFloaterReg::getInstance("inventory"); + display_startup(); + LLStartUp::setStartupState( STATE_MISC ); + display_startup(); + + return false; + } + + + //--------------------------------------------------------------------- + // Misc + //--------------------------------------------------------------------- + if (STATE_MISC == LLStartUp::getStartupState()) + { + // We have a region, and just did a big inventory download. + // We can estimate the user's connection speed, and set their + // max bandwidth accordingly. JC + if (gSavedSettings.getBOOL("FirstLoginThisInstall")) + { + // This is actually a pessimistic computation, because TCP may not have enough + // time to ramp up on the (small) default inventory file to truly measure max + // bandwidth. JC + F64 rate_bps = LLLoginInstance::getInstance()->getLastTransferRateBPS(); + const F32 FAST_RATE_BPS = 600.f * 1024.f; + const F32 FASTER_RATE_BPS = 750.f * 1024.f; + F32 max_bandwidth = gViewerThrottle.getMaxBandwidth(); + if (rate_bps > FASTER_RATE_BPS + && rate_bps > max_bandwidth) + { + LL_DEBUGS("AppInit") << "Fast network connection, increasing max bandwidth to " + << FASTER_RATE_BPS/1024.f + << " kbps" << LL_ENDL; + gViewerThrottle.setMaxBandwidth(FASTER_RATE_BPS / 1024.f); + } + else if (rate_bps > FAST_RATE_BPS + && rate_bps > max_bandwidth) + { + LL_DEBUGS("AppInit") << "Fast network connection, increasing max bandwidth to " + << FAST_RATE_BPS/1024.f + << " kbps" << LL_ENDL; + gViewerThrottle.setMaxBandwidth(FAST_RATE_BPS / 1024.f); + } + + if (gSavedSettings.getBOOL("ShowHelpOnFirstLogin")) + { + gSavedSettings.setBOOL("HelpFloaterOpen", true); + } + + // Set the show start location to true, now that the user has logged + // on with this install. + gSavedSettings.setBOOL("ShowStartLocation", true); + } + + display_startup(); + + // Load stored local environment if needed. + LLEnvironment::instance().loadFromSettings(); + + // *TODO : Uncomment that line once the whole grid migrated to SLM and suppress it from LLAgent::handleTeleportFinished() (llagent.cpp) + //check_merchant_status(); + + display_startup(); + + if (gSavedSettings.getBOOL("HelpFloaterOpen")) + { + // show default topic + LLViewerHelp::instance().showTopic(""); + } + + display_startup(); + + // We're successfully logged in. + gSavedSettings.setBOOL("FirstLoginThisInstall", false); + + LLFloaterReg::showInitialVisibleInstances(); + + LLFloaterGridStatus::getInstance()->startGridStatusTimer(); + + display_startup(); + + display_startup(); + // JC: Initializing audio requests many sounds for download. + init_audio(); + display_startup(); + + // JC: Initialize "active" gestures. This may also trigger + // many gesture downloads, if this is the user's first + // time on this machine or -purge has been run. + LLSD gesture_options + = LLLoginInstance::getInstance()->getResponse("gestures"); + if (gesture_options.isDefined()) + { + LL_DEBUGS("AppInit") << "Gesture Manager loading " << gesture_options.size() + << LL_ENDL; + uuid_vec_t item_ids; + for(LLSD::array_const_iterator resp_it = gesture_options.beginArray(), + end = gesture_options.endArray(); resp_it != end; ++resp_it) + { + // If the id is not specifed in the LLSD, + // the LLSD operator[]() will return a null LLUUID. + LLUUID item_id = (*resp_it)["item_id"]; + LLUUID asset_id = (*resp_it)["asset_id"]; + + if (item_id.notNull() && asset_id.notNull()) + { + // Could schedule and delay these for later. + const bool no_inform_server = false; + const bool no_deactivate_similar = false; + LLGestureMgr::instance().activateGestureWithAsset(item_id, asset_id, + no_inform_server, + no_deactivate_similar); + // We need to fetch the inventory items for these gestures + // so we have the names to populate the UI. + item_ids.push_back(item_id); + } + } + // no need to add gesture to inventory observer, it's already made in constructor + LLGestureMgr::instance().setFetchIDs(item_ids); + LLGestureMgr::instance().startFetch(); + } + gDisplaySwapBuffers = true; + display_startup(); + + LLMessageSystem* msg = gMessageSystem; + msg->setHandlerFuncFast(_PREHASH_SoundTrigger, process_sound_trigger); + msg->setHandlerFuncFast(_PREHASH_PreloadSound, process_preload_sound); + msg->setHandlerFuncFast(_PREHASH_AttachedSound, process_attached_sound); + msg->setHandlerFuncFast(_PREHASH_AttachedSoundGainChange, process_attached_sound_gain_change); + + LL_DEBUGS("AppInit") << "Initialization complete" << LL_ENDL; + + LL_DEBUGS("SceneLoadTiming", "Start") << "Scene Load Started " << LL_ENDL; + gRenderStartTime.reset(); + gForegroundTime.reset(); + + // HACK: Inform simulator of window size. + // Do this here so it's less likely to race with RegisterNewAgent. + // TODO: Put this into RegisterNewAgent + // JC - 7/20/2002 + gViewerWindow->sendShapeToSim(); + + LLPresetsManager::getInstance()->createMissingDefault(PRESETS_CAMERA); + + // The reason we show the alert is because we want to + // reduce confusion for when you log in and your provided + // location is not your expected location. So, if this is + // your first login, then you do not have an expectation, + // thus, do not show this alert. + if (!gAgent.isFirstLogin()) + { + LL_INFOS() << "gAgentStartLocation : " << gAgentStartLocation << LL_ENDL; + LLSLURL start_slurl = LLStartUp::getStartSLURL(); + LL_DEBUGS("AppInit") << "start slurl "< 1.f) && isAgentAvatarValid()) + { + LLStartUp::setStartupState( STATE_WEARABLES_WAIT ); + } + else if (timeout_frac > 10.f) + { + // If we exceed the wait above while isAgentAvatarValid is + // not true yet, we will change startup state and + // eventually (once avatar does get created) wind up at + // the gender chooser. This should occur only in very + // unusual circumstances, so set the timeout fairly high + // to minimize mistaken hits here. + LL_WARNS() << "Wait for valid avatar state exceeded " + << timeout.getElapsedTimeF32() << " will invoke gender chooser" << LL_ENDL; + LLStartUp::setStartupState( STATE_WEARABLES_WAIT ); + } + else + { + update_texture_fetch(); + set_startup_status(0.60f + 0.30f * timeout_frac, + LLTrans::getString("LoginPrecaching"), + gAgent.mMOTD.c_str()); + display_startup(); + } + + return true; + } + + if (STATE_WEARABLES_WAIT == LLStartUp::getStartupState()) + { + static LLFrameTimer wearables_timer; + + const F32 wearables_time = wearables_timer.getElapsedTimeF32(); + const F32 MAX_WEARABLES_TIME = 10.f; + + if (!gAgent.isOutfitChosen() && isAgentAvatarValid()) + { + // No point in waiting for clothing, we don't even know + // what outfit we want. Pop up a gender chooser dialog to + // ask and proceed to draw the world. JC + // + // *NOTE: We might hit this case even if we have an + // initial outfit, but if the load hasn't started + // already then something is wrong so fall back + // to generic outfits. JC + LLNotificationsUtil::add("WelcomeChooseSex", LLSD(), LLSD(), + callback_choose_gender); + LLStartUp::setStartupState( STATE_CLEANUP ); + } + + display_startup(); + + if (gAgent.isOutfitChosen() && (wearables_time > MAX_WEARABLES_TIME)) + { + if (gInventory.isInventoryUsable()) + { + LLNotificationsUtil::add("ClothingLoading"); + } + record(LLStatViewer::LOADING_WEARABLES_LONG_DELAY, wearables_time); + LLStartUp::setStartupState( STATE_CLEANUP ); + } + else if (gAgent.isFirstLogin() + && isAgentAvatarValid() + && gAgentAvatarp->isFullyLoaded()) + { + // wait for avatar to be completely loaded + if (isAgentAvatarValid() + && gAgentAvatarp->isFullyLoaded()) + { + LL_DEBUGS("Avatar") << "avatar fully loaded" << LL_ENDL; + LLStartUp::setStartupState( STATE_CLEANUP ); + return true; + } + } + else + { + // OK to just get the wearables + if ( gAgentWearables.areWearablesLoaded() ) + { + // We have our clothing, proceed. + LL_DEBUGS("Avatar") << "wearables loaded" << LL_ENDL; + LLStartUp::setStartupState( STATE_CLEANUP ); + return true; + } + } + //fall through this frame to STATE_CLEANUP + } + + if (STATE_CLEANUP == LLStartUp::getStartupState()) + { + set_startup_status(1.0, "", ""); + display_startup(); + + if (!mBenefitsSuccessfullyInit) + { + LLNotificationsUtil::add("FailedToGetBenefits", LLSD(), LLSD(), boost::bind(on_benefits_failed_callback, _1, _2)); + } + + // Let the map know about the inventory. + LLFloaterWorldMap* floater_world_map = LLFloaterWorldMap::getInstance(); + if(floater_world_map) + { + floater_world_map->observeInventory(&gInventory); + floater_world_map->observeFriends(); + } + gViewerWindow->showCursor(); + gViewerWindow->getWindow()->resetBusyCount(); + gViewerWindow->getWindow()->setCursor(UI_CURSOR_ARROW); + LL_DEBUGS("AppInit") << "Done releasing bitmap" << LL_ENDL; + //gViewerWindow->revealIntroPanel(); + gViewerWindow->setStartupComplete(); + gViewerWindow->setProgressCancelButtonVisible(false); + display_startup(); + + // We're not away from keyboard, even though login might have taken + // a while. JC + gAgent.clearAFK(); + + // Have the agent start watching the friends list so we can update proxies + gAgent.observeFriends(); + + // Start automatic replay if the flag is set. + if (gSavedSettings.getBOOL("StatsAutoRun") || gAgentPilot.getReplaySession()) + { + LL_DEBUGS("AppInit") << "Starting automatic playback" << LL_ENDL; + gAgentPilot.startPlayback(); + } + + show_debug_menus(); // Debug menu visiblity and First Use trigger + + // If we've got a startup URL, dispatch it + //LLStartUp::dispatchURL(); + + // Retrieve information about the land data + // (just accessing this the first time will fetch it, + // then the data is cached for the viewer's lifetime) + LLProductInfoRequestManager::instance(); + + // *FIX:Mani - What do I do here? + // Need we really clear the Auth response data? + // Clean up the userauth stuff. + // LLUserAuth::getInstance()->reset(); + + LLStartUp::setStartupState( STATE_STARTED ); + display_startup(); + + // Unmute audio if desired and setup volumes. + // This is a not-uncommon crash site, so surround it with + // LL_INFOS() output to aid diagnosis. + LL_INFOS("AppInit") << "Doing first audio_update_volume..." << LL_ENDL; + audio_update_volume(); + LL_INFOS("AppInit") << "Done first audio_update_volume." << LL_ENDL; + + // reset keyboard focus to sane state of pointing at world + gFocusMgr.setKeyboardFocus(NULL); + + LLAppViewer::instance()->handleLoginComplete(); + + LLAgentPicksInfo::getInstance()->requestNumberOfPicks(); + + display_startup(); + + llassert(LLPathfindingManager::getInstance() != NULL); + LLPathfindingManager::getInstance()->initSystem(); + + gAgentAvatarp->sendHoverHeight(); + + // look for parcels we own + send_places_query(LLUUID::null, + LLUUID::null, + "", + DFQ_AGENT_OWNED, + LLParcel::C_ANY, + ""); + + LLUIUsage::instance().clear(); + + LLPerfStats::StatsRecorder::setAutotuneInit(); + + return true; + } + + return true; +} + +// +// local function definition +// + +void login_show() +{ + LL_INFOS("AppInit") << "Initializing Login Screen" << LL_ENDL; + + // Hide the toolbars: may happen to come back here if login fails after login agent but before login in region + if (gToolBarView) + { + gToolBarView->setVisible(false); + } + + LLPanelLogin::show( gViewerWindow->getWindowRectScaled(), login_callback, NULL ); +} + +// Callback for when login screen is closed. Option 0 = connect, option 1 = quit. +void login_callback(S32 option, void *userdata) +{ + const S32 CONNECT_OPTION = 0; + const S32 QUIT_OPTION = 1; + + if (CONNECT_OPTION == option) + { + LLStartUp::setStartupState( STATE_LOGIN_CLEANUP ); + return; + } + else if (QUIT_OPTION == option) // *TODO: THIS CODE SEEMS TO BE UNREACHABLE!!!!! login_callback is never called with option equal to QUIT_OPTION + { + if (!gSavedSettings.getBOOL("RememberPassword")) + { + // turn off the setting and write out to disk + gSavedSettings.saveToFile( gSavedSettings.getString("ClientSettingsFile") , true ); + LLUIColorTable::instance().saveUserSettings(); + } + + // Next iteration through main loop should shut down the app cleanly. + LLAppViewer::instance()->userQuit(); + + if (LLAppViewer::instance()->quitRequested()) + { + LLPanelLogin::closePanel(); + } + return; + } + else + { + LL_WARNS("AppInit") << "Unknown login button clicked" << LL_ENDL; + } +} + +void release_notes_coro(const std::string url) +{ + if (url.empty()) + { + return; + } + + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("releaseNotesCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); + + httpOpts->setHeadersOnly(true); // only making sure it isn't 404 or something like that + + LLSD result = httpAdapter->getAndSuspend(httpRequest, url, httpOpts); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + return; + } + + LLWeb::loadURLInternal(url); +} + +/** +* Check if user is running a new version of the viewer. +* Display the Release Notes if it's not overriden by the "UpdaterShowReleaseNotes" setting. +*/ +void show_release_notes_if_required() +{ + static bool release_notes_shown = false; + // We happen to know that instantiating LLVersionInfo implicitly + // instantiates the LLEventMailDrop named "relnotes", which we (might) use + // below. If viewer release notes stop working, might be because that + // LLEventMailDrop got moved out of LLVersionInfo and hasn't yet been + // instantiated. + if (!release_notes_shown && (LLVersionInfo::instance().getChannelAndVersion() != gLastRunVersion) + && LLVersionInfo::instance().getViewerMaturity() != LLVersionInfo::TEST_VIEWER // don't show Release Notes for the test builds + && gSavedSettings.getBOOL("UpdaterShowReleaseNotes") + && !gSavedSettings.getBOOL("FirstLoginThisInstall")) + { + +#if LL_RELEASE_FOR_DOWNLOAD + if (!gSavedSettings.getBOOL("CmdLineSkipUpdater") + && !LLAppViewer::instance()->isUpdaterMissing()) + { + // Instantiate a "relnotes" listener which assumes any arriving event + // is the release notes URL string. Since "relnotes" is an + // LLEventMailDrop, this listener will be invoked whether or not the + // URL has already been posted. If so, it will fire immediately; + // otherwise it will fire whenever the URL is (later) posted. Either + // way, it will display the release notes as soon as the URL becomes + // available. + LLEventPumps::instance().obtain("relnotes").listen( + "showrelnotes", + [](const LLSD& url) { + LLCoros::instance().launch("releaseNotesCoro", + boost::bind(&release_notes_coro, url.asString())); + return false; + }); + } + else +#endif // LL_RELEASE_FOR_DOWNLOAD + { + LLSD info(LLAppViewer::instance()->getViewerInfo()); + std::string url = info["VIEWER_RELEASE_NOTES_URL"].asString(); + LLCoros::instance().launch("releaseNotesCoro", + boost::bind(&release_notes_coro, url)); + } + release_notes_shown = true; + } +} + +void show_first_run_dialog() +{ + LLNotificationsUtil::add("FirstRun", LLSD(), LLSD(), first_run_dialog_callback); +} + +bool first_run_dialog_callback(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (0 == option) + { + LL_DEBUGS("AppInit") << "First run dialog cancelling" << LL_ENDL; + LLWeb::loadURLExternal(LLTrans::getString("create_account_url") ); + } + + LLPanelLogin::giveFocus(); + return false; +} + + + +void set_startup_status(const F32 frac, const std::string& string, const std::string& msg) +{ + gViewerWindow->setProgressPercent(frac*100); + gViewerWindow->setProgressString(string); + + gViewerWindow->setProgressMessage(msg); +} + +bool login_alert_status(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + // Buttons + switch( option ) + { + case 0: // OK + break; + // case 1: // Help + // LLWeb::loadURL(LLNotifications::instance().getGlobalString("SUPPORT_URL") ); + // break; + case 2: // Teleport + // Restart the login process, starting at our home locaton + LLStartUp::setStartSLURL(LLSLURL(LLSLURL::SIM_LOCATION_HOME)); + LLStartUp::setStartupState( STATE_LOGIN_CLEANUP ); + break; + default: + LL_WARNS("AppInit") << "Missing case in login_alert_status switch" << LL_ENDL; + } + + LLPanelLogin::giveFocus(); + return false; +} + + +void use_circuit_callback(void**, S32 result) +{ + // bail if we're quitting. + if(LLApp::isExiting()) return; + if( !gUseCircuitCallbackCalled ) + { + gUseCircuitCallbackCalled = true; + if (result) + { + // Make sure user knows something bad happened. JC + LL_WARNS("AppInit") << "Backing up to login screen!" << LL_ENDL; + if (gRememberPassword) + { + LLNotificationsUtil::add("LoginPacketNeverReceived", LLSD(), LLSD(), login_alert_status); + } + else + { + LLNotificationsUtil::add("LoginPacketNeverReceivedNoTP", LLSD(), LLSD(), login_alert_status); + } + reset_login(); + } + else + { + gGotUseCircuitCodeAck = true; + } + } +} + +void register_viewer_callbacks(LLMessageSystem* msg) +{ + msg->setHandlerFuncFast(_PREHASH_LayerData, process_layer_data ); + msg->setHandlerFuncFast(_PREHASH_ObjectUpdate, process_object_update ); + msg->setHandlerFunc("ObjectUpdateCompressed", process_compressed_object_update ); + msg->setHandlerFunc("ObjectUpdateCached", process_cached_object_update ); + msg->setHandlerFuncFast(_PREHASH_ImprovedTerseObjectUpdate, process_terse_object_update_improved ); + msg->setHandlerFunc("SimStats", process_sim_stats); + msg->setHandlerFuncFast(_PREHASH_HealthMessage, process_health_message ); + msg->setHandlerFuncFast(_PREHASH_EconomyData, process_economy_data); + msg->setHandlerFunc("RegionInfo", LLViewerRegion::processRegionInfo); + + msg->setHandlerFuncFast(_PREHASH_ChatFromSimulator, process_chat_from_simulator); + msg->setHandlerFuncFast(_PREHASH_KillObject, process_kill_object, NULL); + msg->setHandlerFuncFast(_PREHASH_SimulatorViewerTimeMessage, process_time_synch, NULL); + msg->setHandlerFuncFast(_PREHASH_EnableSimulator, process_enable_simulator); + msg->setHandlerFuncFast(_PREHASH_DisableSimulator, process_disable_simulator); + msg->setHandlerFuncFast(_PREHASH_KickUser, process_kick_user, NULL); + + msg->setHandlerFunc("CrossedRegion", process_crossed_region); + msg->setHandlerFuncFast(_PREHASH_TeleportFinish, process_teleport_finish); + + msg->setHandlerFuncFast(_PREHASH_AlertMessage, process_alert_message); + msg->setHandlerFunc("AgentAlertMessage", process_agent_alert_message); + msg->setHandlerFuncFast(_PREHASH_MeanCollisionAlert, process_mean_collision_alert_message, NULL); + msg->setHandlerFunc("ViewerFrozenMessage", process_frozen_message); + + msg->setHandlerFuncFast(_PREHASH_NameValuePair, process_name_value); + msg->setHandlerFuncFast(_PREHASH_RemoveNameValuePair, process_remove_name_value); + msg->setHandlerFuncFast(_PREHASH_AvatarAnimation, process_avatar_animation); + msg->setHandlerFuncFast(_PREHASH_ObjectAnimation, process_object_animation); + msg->setHandlerFuncFast(_PREHASH_AvatarAppearance, process_avatar_appearance); + msg->setHandlerFuncFast(_PREHASH_CameraConstraint, process_camera_constraint); + msg->setHandlerFuncFast(_PREHASH_AvatarSitResponse, process_avatar_sit_response); + msg->setHandlerFunc("SetFollowCamProperties", process_set_follow_cam_properties); + msg->setHandlerFunc("ClearFollowCamProperties", process_clear_follow_cam_properties); + + msg->setHandlerFuncFast(_PREHASH_ImprovedInstantMessage, process_improved_im); + msg->setHandlerFuncFast(_PREHASH_ScriptQuestion, process_script_question); + msg->setHandlerFuncFast(_PREHASH_ObjectProperties, LLSelectMgr::processObjectProperties, NULL); + msg->setHandlerFuncFast(_PREHASH_ObjectPropertiesFamily, LLSelectMgr::processObjectPropertiesFamily, NULL); + msg->setHandlerFunc("ForceObjectSelect", LLSelectMgr::processForceObjectSelect); + + msg->setHandlerFuncFast(_PREHASH_MoneyBalanceReply, process_money_balance_reply, NULL); + msg->setHandlerFuncFast(_PREHASH_CoarseLocationUpdate, LLWorld::processCoarseUpdate, NULL); + msg->setHandlerFuncFast(_PREHASH_ReplyTaskInventory, LLViewerObject::processTaskInv, NULL); + msg->setHandlerFuncFast(_PREHASH_DerezContainer, process_derez_container, NULL); + msg->setHandlerFuncFast(_PREHASH_ScriptRunningReply, + &LLLiveLSLEditor::processScriptRunningReply); + + msg->setHandlerFuncFast(_PREHASH_DeRezAck, process_derez_ack); + + msg->setHandlerFunc("LogoutReply", process_logout_reply); + + //msg->setHandlerFuncFast(_PREHASH_AddModifyAbility, + // &LLAgent::processAddModifyAbility); + //msg->setHandlerFuncFast(_PREHASH_RemoveModifyAbility, + // &LLAgent::processRemoveModifyAbility); + msg->setHandlerFuncFast(_PREHASH_AgentDataUpdate, + &LLAgent::processAgentDataUpdate); + msg->setHandlerFuncFast(_PREHASH_AgentGroupDataUpdate, + &LLAgent::processAgentGroupDataUpdate); + msg->setHandlerFunc("AgentDropGroup", + &LLAgent::processAgentDropGroup); + // land ownership messages + msg->setHandlerFuncFast(_PREHASH_ParcelOverlay, + LLViewerParcelMgr::processParcelOverlay); + msg->setHandlerFuncFast(_PREHASH_ParcelProperties, + LLViewerParcelMgr::processParcelProperties); + msg->setHandlerFunc("ParcelAccessListReply", + LLViewerParcelMgr::processParcelAccessListReply); + msg->setHandlerFunc("ParcelDwellReply", + LLViewerParcelMgr::processParcelDwellReply); + + msg->setHandlerFunc("AvatarPropertiesReply", + &LLAvatarPropertiesProcessor::processAvatarLegacyPropertiesReply); + msg->setHandlerFunc("AvatarInterestsReply", + &LLAvatarPropertiesProcessor::processAvatarInterestsReply); + msg->setHandlerFunc("AvatarGroupsReply", + &LLAvatarPropertiesProcessor::processAvatarGroupsReply); + msg->setHandlerFunc("AvatarNotesReply", + &LLAvatarPropertiesProcessor::processAvatarNotesReply); + msg->setHandlerFunc("AvatarPicksReply", + &LLAvatarPropertiesProcessor::processAvatarPicksReply); + msg->setHandlerFunc("AvatarClassifiedReply", + &LLAvatarPropertiesProcessor::processAvatarClassifiedsReply); + + msg->setHandlerFuncFast(_PREHASH_CreateGroupReply, + LLGroupMgr::processCreateGroupReply); + msg->setHandlerFuncFast(_PREHASH_JoinGroupReply, + LLGroupMgr::processJoinGroupReply); + msg->setHandlerFuncFast(_PREHASH_EjectGroupMemberReply, + LLGroupMgr::processEjectGroupMemberReply); + msg->setHandlerFuncFast(_PREHASH_LeaveGroupReply, + LLGroupMgr::processLeaveGroupReply); + msg->setHandlerFuncFast(_PREHASH_GroupProfileReply, + LLGroupMgr::processGroupPropertiesReply); + + // ratings deprecated + // msg->setHandlerFuncFast(_PREHASH_ReputationIndividualReply, + // LLFloaterRate::processReputationIndividualReply); + + msg->setHandlerFunc("ScriptControlChange", + LLAgent::processScriptControlChange ); + + msg->setHandlerFuncFast(_PREHASH_ViewerEffect, LLHUDManager::processViewerEffect); + + msg->setHandlerFuncFast(_PREHASH_GrantGodlikePowers, process_grant_godlike_powers); + + msg->setHandlerFuncFast(_PREHASH_GroupAccountSummaryReply, + LLPanelGroupLandMoney::processGroupAccountSummaryReply); + msg->setHandlerFuncFast(_PREHASH_GroupAccountDetailsReply, + LLPanelGroupLandMoney::processGroupAccountDetailsReply); + msg->setHandlerFuncFast(_PREHASH_GroupAccountTransactionsReply, + LLPanelGroupLandMoney::processGroupAccountTransactionsReply); + + msg->setHandlerFuncFast(_PREHASH_UserInfoReply, + process_user_info_reply); + + msg->setHandlerFunc("RegionHandshake", process_region_handshake, NULL); + + msg->setHandlerFunc("TeleportStart", process_teleport_start ); + msg->setHandlerFunc("TeleportProgress", process_teleport_progress); + msg->setHandlerFunc("TeleportFailed", process_teleport_failed, NULL); + msg->setHandlerFunc("TeleportLocal", process_teleport_local, NULL); + + msg->setHandlerFunc("ImageNotInDatabase", LLViewerTextureList::processImageNotInDatabase, NULL); + + msg->setHandlerFuncFast(_PREHASH_GroupMembersReply, + LLGroupMgr::processGroupMembersReply); + msg->setHandlerFunc("GroupRoleDataReply", + LLGroupMgr::processGroupRoleDataReply); + msg->setHandlerFunc("GroupRoleMembersReply", + LLGroupMgr::processGroupRoleMembersReply); + msg->setHandlerFunc("GroupTitlesReply", + LLGroupMgr::processGroupTitlesReply); + // Special handler as this message is sometimes used for group land. + msg->setHandlerFunc("PlacesReply", process_places_reply); + msg->setHandlerFunc("GroupNoticesListReply", LLPanelGroupNotices::processGroupNoticesListReply); + + msg->setHandlerFunc("AvatarPickerReply", LLFloaterAvatarPicker::processAvatarPickerReply); + + msg->setHandlerFunc("MapBlockReply", LLWorldMapMessage::processMapBlockReply); + msg->setHandlerFunc("MapItemReply", LLWorldMapMessage::processMapItemReply); + msg->setHandlerFunc("EventInfoReply", LLEventNotifier::processEventInfoReply); + + msg->setHandlerFunc("PickInfoReply", &LLAvatarPropertiesProcessor::processPickInfoReply); + msg->setHandlerFunc("ClassifiedInfoReply", LLAvatarPropertiesProcessor::processClassifiedInfoReply); + msg->setHandlerFunc("ParcelInfoReply", LLRemoteParcelInfoProcessor::processParcelInfoReply); + msg->setHandlerFunc("ScriptDialog", process_script_dialog); + msg->setHandlerFunc("LoadURL", process_load_url); + msg->setHandlerFunc("ScriptTeleportRequest", process_script_teleport_request); + msg->setHandlerFunc("EstateCovenantReply", process_covenant_reply); + + // calling cards + msg->setHandlerFunc("OfferCallingCard", process_offer_callingcard); + msg->setHandlerFunc("AcceptCallingCard", process_accept_callingcard); + msg->setHandlerFunc("DeclineCallingCard", process_decline_callingcard); + + msg->setHandlerFunc("ParcelObjectOwnersReply", LLPanelLandObjects::processParcelObjectOwnersReply); + + msg->setHandlerFunc("InitiateDownload", process_initiate_download); + msg->setHandlerFunc("LandStatReply", LLFloaterTopObjects::handle_land_reply); + msg->setHandlerFunc("GenericMessage", process_generic_message); + msg->setHandlerFunc("GenericStreamingMessage", process_generic_streaming_message); + msg->setHandlerFunc("LargeGenericMessage", process_large_generic_message); + + msg->setHandlerFuncFast(_PREHASH_FeatureDisabled, process_feature_disabled_message); +} + +void asset_callback_nothing(const LLUUID&, LLAssetType::EType, void*, S32) +{ + // nothing +} + +const S32 OPT_CLOSED_WINDOW = -1; +const S32 OPT_MALE = 0; +const S32 OPT_FEMALE = 1; +const S32 OPT_TRUST_CERT = 0; +const S32 OPT_CANCEL_TRUST = 1; + +bool callback_choose_gender(const LLSD& notification, const LLSD& response) +{ + + // These defaults are returned from the server on login. They are set in login.xml. + // If no default is returned from the server, they are retrieved from settings.xml. + + S32 option = LLNotification::getSelectedOption(notification, response); + switch(option) + { + case OPT_MALE: + LLStartUp::loadInitialOutfit( gSavedSettings.getString("DefaultMaleAvatar"), "male" ); + break; + + case OPT_FEMALE: + case OPT_CLOSED_WINDOW: + default: + LLStartUp::loadInitialOutfit( gSavedSettings.getString("DefaultFemaleAvatar"), "female" ); + break; + } + return false; +} + +std::string get_screen_filename(const std::string& pattern) +{ + if (LLGridManager::getInstance()->isInProductionGrid()) + { + return llformat(pattern.c_str(), ""); + } + else + { + const std::string& grid_id_str = LLGridManager::getInstance()->getGridId(); + const std::string& grid_id_lower = utf8str_tolower(grid_id_str); + std::string grid = "." + grid_id_lower; + return llformat(pattern.c_str(), grid.c_str()); + } +} + +//static +std::string LLStartUp::getScreenLastFilename() +{ + return get_screen_filename(SCREEN_LAST_FILENAME); +} + +//static +std::string LLStartUp::getScreenHomeFilename() +{ + return get_screen_filename(SCREEN_HOME_FILENAME); +} + +//static +void LLStartUp::loadInitialOutfit( const std::string& outfit_folder_name, + const std::string& gender_name ) +{ + LL_DEBUGS() << "starting" << LL_ENDL; + + // Not going through the processAgentInitialWearables path, so need to set this here. + LLAppearanceMgr::instance().setAttachmentInvLinkEnable(true); + // Initiate creation of COF, since we're also bypassing that. + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_CURRENT_OUTFIT); + LLAppearanceMgr::getInstance()->initCOFID(); + + ESex gender; + if (gender_name == "male") + { + LL_DEBUGS() << "male" << LL_ENDL; + gender = SEX_MALE; + } + else + { + LL_DEBUGS() << "female" << LL_ENDL; + gender = SEX_FEMALE; + } + + if (!isAgentAvatarValid()) + { + LL_WARNS() << "Trying to load an initial outfit for an invalid agent avatar" << LL_ENDL; + return; + } + + gAgentAvatarp->setSex(gender); + + // try to find the requested outfit or folder + + // -- check for existing outfit in My Outfits + bool do_copy = false; + LLUUID cat_id = findDescendentCategoryIDByName( + gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS), + outfit_folder_name); + + // -- check for existing folder in Library + if (cat_id.isNull()) + { + cat_id = findDescendentCategoryIDByName( + gInventory.getLibraryRootFolderID(), + outfit_folder_name); + if (!cat_id.isNull()) + { + do_copy = true; + } + } + + if (cat_id.isNull()) + { + // -- final fallback: create standard wearables + LL_DEBUGS() << "standard wearables" << LL_ENDL; + gAgentWearables.createStandardWearables(); + } + else + { + bool do_append = false; + LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id); + // Need to fetch cof contents before we can wear. + if (do_copy) + { + callAfterCOFFetch(boost::bind(&LLAppearanceMgr::wearInventoryCategory, LLAppearanceMgr::getInstance(), cat, do_copy, do_append)); + } + else + { + callAfterCategoryLinksFetch(cat_id, boost::bind(&LLAppearanceMgr::wearInventoryCategory, LLAppearanceMgr::getInstance(), cat, do_copy, do_append)); + } + LL_DEBUGS() << "initial outfit category id: " << cat_id << LL_ENDL; + } + + gAgent.setOutfitChosen(true); + gAgentWearables.sendDummyAgentWearablesUpdate(); +} + +std::string& LLStartUp::getInitialOutfitName() +{ + return sInitialOutfit; +} + +std::string LLStartUp::getUserId() +{ + if (gUserCredential.isNull()) + { + return ""; + } + return gUserCredential->userID(); +} + + +// frees the bitmap +void release_start_screen() +{ + LL_DEBUGS("AppInit") << "Releasing bitmap..." << LL_ENDL; + gStartTexture = NULL; +} + + +// static +std::string LLStartUp::startupStateToString(EStartupState state) +{ +#define RTNENUM(E) case E: return #E + switch(state){ + RTNENUM( STATE_FIRST ); + RTNENUM( STATE_BROWSER_INIT ); + RTNENUM( STATE_LOGIN_SHOW ); + RTNENUM( STATE_LOGIN_WAIT ); + RTNENUM( STATE_LOGIN_CLEANUP ); + RTNENUM( STATE_LOGIN_AUTH_INIT ); + RTNENUM( STATE_LOGIN_CURL_UNSTUCK ); + RTNENUM( STATE_LOGIN_PROCESS_RESPONSE ); + RTNENUM( STATE_WORLD_INIT ); + RTNENUM( STATE_MULTIMEDIA_INIT ); + RTNENUM( STATE_FONT_INIT ); + RTNENUM( STATE_SEED_GRANTED_WAIT ); + RTNENUM( STATE_SEED_CAP_GRANTED ); + RTNENUM( STATE_WORLD_WAIT ); + RTNENUM( STATE_AGENT_SEND ); + RTNENUM( STATE_AGENT_WAIT ); + RTNENUM( STATE_INVENTORY_SEND ); + RTNENUM(STATE_INVENTORY_CALLBACKS ); + RTNENUM( STATE_INVENTORY_SKEL ); + RTNENUM( STATE_INVENTORY_SEND2 ); + RTNENUM( STATE_MISC ); + RTNENUM( STATE_PRECACHE ); + RTNENUM( STATE_WEARABLES_WAIT ); + RTNENUM( STATE_CLEANUP ); + RTNENUM( STATE_STARTED ); + default: + return llformat("(state #%d)", state); + } +#undef RTNENUM +} + +// static +void LLStartUp::setStartupState( EStartupState state ) +{ + LL_INFOS("AppInit") << "Startup state changing from " << + getStartupStateString() << " to " << + startupStateToString(state) << LL_ENDL; + + getPhases().stopPhase(getStartupStateString()); + gStartupState = state; + getPhases().startPhase(getStartupStateString()); + + postStartupState(); +} + +void LLStartUp::postStartupState() +{ + LLSD stateInfo; + stateInfo["str"] = getStartupStateString(); + stateInfo["enum"] = gStartupState; + sStateWatcher->post(stateInfo); + gDebugInfo["StartupState"] = getStartupStateString(); +} + + +void reset_login() +{ + gAgentWearables.cleanup(); + gAgentCamera.cleanup(); + gAgent.cleanup(); + gSky.cleanup(); // mVOSkyp is an inworld object. + LLWorld::getInstance()->resetClass(); + LLAppearanceMgr::getInstance()->cleanup(); + + if ( gViewerWindow ) + { // Hide menus and normal buttons + gViewerWindow->setNormalControlsVisible( false ); + gLoginMenuBarView->setVisible( true ); + gLoginMenuBarView->setEnabled( true ); + } + + // Hide any other stuff + LLFloaterReg::hideVisibleInstances(); + + if (LLStartUp::getStartupState() > STATE_WORLD_INIT) + { + gViewerWindow->resetStatusBarContainer(); + } + LLStartUp::setStartupState( STATE_BROWSER_INIT ); + + if (LLVoiceClient::instanceExists()) + { + LLVoiceClient::getInstance()->terminate(); + } + + // Clear any verified certs and verify them again on next login + // to ensure cert matches server instead of just getting reused + LLPointer store = gSecAPIHandler->getCertificateStore(""); + store->clearSertCache(); +} + +//--------------------------------------------------------------------------- + +// Initialize all plug-ins except the web browser (which was initialized +// early, before the login screen). JC +void LLStartUp::multimediaInit() +{ + LL_DEBUGS("AppInit") << "Initializing Multimedia...." << LL_ENDL; + std::string msg = LLTrans::getString("LoginInitializingMultimedia"); + set_startup_status(0.42f, msg.c_str(), gAgent.mMOTD.c_str()); + display_startup(); +} + +void LLStartUp::fontInit() +{ + LL_DEBUGS("AppInit") << "Initializing fonts...." << LL_ENDL; + std::string msg = LLTrans::getString("LoginInitializingFonts"); + set_startup_status(0.45f, msg.c_str(), gAgent.mMOTD.c_str()); + display_startup(); + + LLFontGL::loadDefaultFonts(); +} + +void LLStartUp::initNameCache() +{ + // Can be called multiple times + if ( gCacheName ) return; + + gCacheName = new LLCacheName(gMessageSystem); + gCacheName->addObserver(&callback_cache_name); + gCacheName->localizeCacheName("waiting", LLTrans::getString("AvatarNameWaiting")); + gCacheName->localizeCacheName("nobody", LLTrans::getString("AvatarNameNobody")); + gCacheName->localizeCacheName("none", LLTrans::getString("GroupNameNone")); + // Load stored cache if possible + LLAppViewer::instance()->loadNameCache(); + + // Start cache in not-running state until we figure out if we have + // capabilities for display name lookup + LLAvatarNameCache* cache_inst = LLAvatarNameCache::getInstance(); + cache_inst->setUsePeopleAPI(gSavedSettings.getBOOL("UsePeopleAPI")); + cache_inst->setUseDisplayNames(gSavedSettings.getBOOL("UseDisplayNames")); + cache_inst->setUseUsernames(gSavedSettings.getBOOL("NameTagShowUsernames")); +} + + +void LLStartUp::initExperiences() +{ + // Should trigger loading the cache. + LLExperienceCache::instance().setCapabilityQuery( + boost::bind(&LLAgent::getRegionCapability, &gAgent, _1)); + + LLExperienceLog::instance().initialize(); +} + +void LLStartUp::cleanupNameCache() +{ + delete gCacheName; + gCacheName = NULL; +} + +bool LLStartUp::dispatchURL() +{ + // ok, if we've gotten this far and have a startup URL + if (!getStartSLURL().isValid()) + { + return false; + } + if(getStartSLURL().getType() != LLSLURL::APP) + { + + // If we started with a location, but we're already + // at that location, don't pop dialogs open. + LLVector3 pos = gAgent.getPositionAgent(); + LLVector3 slurlpos = getStartSLURL().getPosition(); + F32 dx = pos.mV[VX] - slurlpos.mV[VX]; + F32 dy = pos.mV[VY] - slurlpos.mV[VY]; + const F32 SLOP = 2.f; // meters + + if( getStartSLURL().getRegion() != gAgent.getRegion()->getName() + || (dx*dx > SLOP*SLOP) + || (dy*dy > SLOP*SLOP) ) + { + LLURLDispatcher::dispatch(getStartSLURL().getSLURLString(), LLCommandHandler::NAV_TYPE_CLICKED, + NULL, false); + } + return true; + } + return false; +} + +void LLStartUp::setStartSLURL(const LLSLURL& slurl) +{ + LL_DEBUGS("AppInit")< socks_cred = gSecAPIHandler->loadCredential("SOCKS5"); + std::string socks_user = socks_cred->getIdentifier()["username"].asString(); + std::string socks_password = socks_cred->getAuthenticator()["creds"].asString(); + + bool ok = LLProxy::getInstance()->setAuthPassword(socks_user, socks_password); + + if (!ok) + { + LLNotificationsUtil::add("SOCKS_BAD_CREDS"); + proxy_ok = false; + } + } + else if (auth_type.compare("None") == 0) + { + LLProxy::getInstance()->setAuthNone(); + } + else + { + LL_WARNS("Proxy") << "Invalid SOCKS 5 authentication type."<< LL_ENDL; + + // Unknown or missing setting. + gSavedSettings.setString("Socks5AuthType", "None"); + + // Clear the SOCKS credentials. + LLPointer socks_cred = new LLCredential("SOCKS5"); + gSecAPIHandler->deleteCredential(socks_cred); + + LLProxy::getInstance()->setAuthNone(); + } + + if (proxy_ok) + { + // Start the proxy and check for errors + // If status != SOCKS_OK, stopSOCKSProxy() will already have been called when startSOCKSProxy() returns. + LLHost socks_host; + socks_host.setHostByName(gSavedSettings.getString("Socks5ProxyHost")); + socks_host.setPort(gSavedSettings.getU32("Socks5ProxyPort")); + int status = LLProxy::getInstance()->startSOCKSProxy(socks_host); + + if (status != SOCKS_OK) + { + LLSD subs; + subs["HOST"] = gSavedSettings.getString("Socks5ProxyHost"); + subs["PORT"] = (S32)gSavedSettings.getU32("Socks5ProxyPort"); + + std::string error_string; + + switch(status) + { + case SOCKS_CONNECT_ERROR: // TCP Fail + error_string = "SOCKS_CONNECT_ERROR"; + break; + + case SOCKS_NOT_PERMITTED: // SOCKS 5 server rule set refused connection + error_string = "SOCKS_NOT_PERMITTED"; + break; + + case SOCKS_NOT_ACCEPTABLE: // Selected authentication is not acceptable to server + error_string = "SOCKS_NOT_ACCEPTABLE"; + break; + + case SOCKS_AUTH_FAIL: // Authentication failed + error_string = "SOCKS_AUTH_FAIL"; + break; + + case SOCKS_UDP_FWD_NOT_GRANTED: // UDP forward request failed + error_string = "SOCKS_UDP_FWD_NOT_GRANTED"; + break; + + case SOCKS_HOST_CONNECT_FAILED: // Failed to open a TCP channel to the socks server + error_string = "SOCKS_HOST_CONNECT_FAILED"; + break; + + case SOCKS_INVALID_HOST: // Improperly formatted host address or port. + error_string = "SOCKS_INVALID_HOST"; + break; + + default: + error_string = "SOCKS_UNKNOWN_STATUS"; // Something strange happened, + LL_WARNS("Proxy") << "Unknown return from LLProxy::startProxy(): " << status << LL_ENDL; + break; + } + + LLNotificationsUtil::add(error_string, subs); + proxy_ok = false; + } + } + } + else + { + LLProxy::getInstance()->stopSOCKSProxy(); // ensure no UDP proxy is running and it's all cleaned up + } + + if (proxy_ok) + { + // Determine the HTTP proxy type (if any) + if ((httpProxyType.compare("Web") == 0) && gSavedSettings.getBOOL("BrowserProxyEnabled")) + { + LLHost http_host; + http_host.setHostByName(gSavedSettings.getString("BrowserProxyAddress")); + http_host.setPort(gSavedSettings.getS32("BrowserProxyPort")); + if (!LLProxy::getInstance()->enableHTTPProxy(http_host, LLPROXY_HTTP)) + { + LLSD subs; + subs["HOST"] = http_host.getIPString(); + subs["PORT"] = (S32)http_host.getPort(); + LLNotificationsUtil::add("PROXY_INVALID_HTTP_HOST", subs); + proxy_ok = false; + } + } + else if ((httpProxyType.compare("Socks") == 0) && gSavedSettings.getBOOL("Socks5ProxyEnabled")) + { + LLHost socks_host; + socks_host.setHostByName(gSavedSettings.getString("Socks5ProxyHost")); + socks_host.setPort(gSavedSettings.getU32("Socks5ProxyPort")); + if (!LLProxy::getInstance()->enableHTTPProxy(socks_host, LLPROXY_SOCKS)) + { + LLSD subs; + subs["HOST"] = socks_host.getIPString(); + subs["PORT"] = (S32)socks_host.getPort(); + LLNotificationsUtil::add("PROXY_INVALID_SOCKS_HOST", subs); + proxy_ok = false; + } + } + else if (httpProxyType.compare("None") == 0) + { + LLProxy::getInstance()->disableHTTPProxy(); + } + else + { + LL_WARNS("Proxy") << "Invalid other HTTP proxy configuration: " << httpProxyType << LL_ENDL; + + // Set the missing or wrong configuration back to something valid. + gSavedSettings.setString("HttpProxyType", "None"); + LLProxy::getInstance()->disableHTTPProxy(); + + // Leave proxy_ok alone, since this isn't necessarily fatal. + } + } + + return proxy_ok; +} + +bool login_alert_done(const LLSD& notification, const LLSD& response) +{ + LLPanelLogin::giveFocus(); + return false; +} + +// parse the certificate information into args for the +// certificate notifications +LLSD transform_cert_args(LLPointer cert) +{ + LLSD args = LLSD::emptyMap(); + std::string value; + LLSD cert_info; + cert->getLLSD(cert_info); + // convert all of the elements in the cert into + // args for the xml dialog, so we have flexability to + // display various parts of the cert by only modifying + // the cert alert dialog xml. + for(LLSD::map_iterator iter = cert_info.beginMap(); + iter != cert_info.endMap(); + iter++) + { + // key usage and extended key usage + // are actually arrays, and we want to format them as comma separated + // strings, so special case those. + LLSDSerialize::toXML(cert_info[iter->first], std::cout); + if((iter->first == std::string(CERT_KEY_USAGE)) || + (iter->first == std::string(CERT_EXTENDED_KEY_USAGE))) + { + value = ""; + LLSD usage = cert_info[iter->first]; + for (LLSD::array_iterator usage_iter = usage.beginArray(); + usage_iter != usage.endArray(); + usage_iter++) + { + + if(usage_iter != usage.beginArray()) + { + value += ", "; + } + + value += (*usage_iter).asString(); + } + + } + else + { + value = iter->second.asString(); + } + + std::string name = iter->first; + std::transform(name.begin(), name.end(), name.begin(), + (int(*)(int))toupper); + args[name.c_str()] = value; + } + return args; +} + + +// when we handle a cert error, give focus back to the login panel +void general_cert_done(const LLSD& notification, const LLSD& response) +{ + LLStartUp::setStartupState( STATE_LOGIN_SHOW ); + LLPanelLogin::giveFocus(); +} + +// check to see if the user wants to trust the cert. +// if they do, add it to the cert store and +void trust_cert_done(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotification::getSelectedOption(notification, response); + switch(option) + { + case OPT_TRUST_CERT: + { + LLPointer cert = gSecAPIHandler->getCertificate(notification["payload"]["certificate"]); + LLPointer store = gSecAPIHandler->getCertificateStore(gSavedSettings.getString("CertStore")); + store->add(cert); + store->save(); + LLStartUp::setStartupState( STATE_LOGIN_CLEANUP ); + break; + } + case OPT_CANCEL_TRUST: + reset_login(); + gSavedSettings.setBOOL("AutoLogin", false); + LLStartUp::setStartupState( STATE_LOGIN_SHOW ); + default: + LLPanelLogin::giveFocus(); + break; + } + +} + +void apply_udp_blacklist(const std::string& csv) +{ + + std::string::size_type start = 0; + std::string::size_type comma = 0; + do + { + comma = csv.find(",", start); + if (comma == std::string::npos) + { + comma = csv.length(); + } + std::string item(csv, start, comma-start); + + LL_DEBUGS() << "udp_blacklist " << item << LL_ENDL; + gMessageSystem->banUdpMessage(item); + + start = comma + 1; + + } + while(comma < csv.length()); + +} + +void on_benefits_failed_callback(const LLSD& notification, const LLSD& response) +{ + LL_WARNS("Benefits") << "Failed to load benefits information" << LL_ENDL; +} + +bool init_benefits(LLSD& response) +{ + bool succ = true; + + std::string package_name = response["account_type"].asString(); + const LLSD& benefits_sd = response["account_level_benefits"]; + if (!LLAgentBenefitsMgr::init(package_name, benefits_sd) || + !LLAgentBenefitsMgr::initCurrent(package_name, benefits_sd)) + { + succ = false; + } + else + { + LL_DEBUGS("Benefits") << "Initialized current benefits, level " << package_name << " from " << benefits_sd << LL_ENDL; + } + const LLSD& packages_sd = response["premium_packages"]; + for(LLSD::map_const_iterator package_iter = packages_sd.beginMap(); + package_iter != packages_sd.endMap(); + ++package_iter) + { + std::string package_name = package_iter->first; + const LLSD& benefits_sd = package_iter->second["benefits"]; + if (LLAgentBenefitsMgr::init(package_name, benefits_sd)) + { + LL_DEBUGS("Benefits") << "Initialized benefits for package " << package_name << " from " << benefits_sd << LL_ENDL; + } + else + { + LL_WARNS("Benefits") << "Failed init for package " << package_name << " from " << benefits_sd << LL_ENDL; + succ = false; + } + } + + if (!LLAgentBenefitsMgr::has("Base")) + { + LL_WARNS("Benefits") << "Benefits info did not include required package Base" << LL_ENDL; + succ = false; + } + if (!LLAgentBenefitsMgr::has("Premium")) + { + LL_WARNS("Benefits") << "Benefits info did not include required package Premium" << LL_ENDL; + succ = false; + } + + return succ; +} + +bool process_login_success_response() +{ + LLSD response = LLLoginInstance::getInstance()->getResponse(); + + mBenefitsSuccessfullyInit = init_benefits(response); + + std::string text(response["udp_blacklist"]); + if(!text.empty()) + { + apply_udp_blacklist(text); + } + + // unpack login data needed by the application + text = response["agent_id"].asString(); + if(!text.empty()) gAgentID.set(text); + gDebugInfo["AgentID"] = text; + + LLPerfStats::StatsRecorder::setEnabled(gSavedSettings.getBOOL("PerfStatsCaptureEnabled")); + LLPerfStats::StatsRecorder::setFocusAv(gAgentID); + + // Agent id needed for parcel info request in LLUrlEntryParcel + // to resolve parcel name. + LLUrlEntryParcel::setAgentID(gAgentID); + + text = response["session_id"].asString(); + if(!text.empty()) gAgentSessionID.set(text); + gDebugInfo["SessionID"] = text; + + // Session id needed for parcel info request in LLUrlEntryParcel + // to resolve parcel name. + LLUrlEntryParcel::setSessionID(gAgentSessionID); + + text = response["secure_session_id"].asString(); + if(!text.empty()) gAgent.mSecureSessionID.set(text); + + // if the response contains a display name, use that, + // otherwise if the response contains a first and/or last name, + // use those. Otherwise use the credential identifier + + gDisplayName = ""; + if (response.has("display_name")) + { + gDisplayName.assign(response["display_name"].asString()); + if(!gDisplayName.empty()) + { + // Remove quotes from string. Login.cgi sends these to force + // names that look like numbers into strings. + LLStringUtil::replaceChar(gDisplayName, '"', ' '); + LLStringUtil::trim(gDisplayName); + } + } + std::string first_name; + if(response.has("first_name")) + { + first_name = response["first_name"].asString(); + LLStringUtil::replaceChar(first_name, '"', ' '); + LLStringUtil::trim(first_name); + gAgentUsername = first_name; + } + + if(response.has("last_name") && !gAgentUsername.empty()) + { + std::string last_name = response["last_name"].asString(); + if (last_name != "Resident") + { + LLStringUtil::replaceChar(last_name, '"', ' '); + LLStringUtil::trim(last_name); + gAgentUsername = gAgentUsername + " " + last_name; + } + } + + if(gDisplayName.empty()) + { + if(response.has("first_name")) + { + gDisplayName.assign(response["first_name"].asString()); + LLStringUtil::replaceChar(gDisplayName, '"', ' '); + LLStringUtil::trim(gDisplayName); + } + if(response.has("last_name")) + { + text.assign(response["last_name"].asString()); + LLStringUtil::replaceChar(text, '"', ' '); + LLStringUtil::trim(text); + if(!gDisplayName.empty()) + { + gDisplayName += " "; + } + gDisplayName += text; + } + } + + if(gDisplayName.empty()) + { + gDisplayName.assign(gUserCredential->asString()); + } + + // this is their actual ability to access content + text = response["agent_access_max"].asString(); + if (!text.empty()) + { + // agent_access can be 'A', 'M', and 'PG'. + gAgent.setMaturity(text[0]); + } + + // this is the value of their preference setting for that content + // which will always be <= agent_access_max + text = response["agent_region_access"].asString(); + if (!text.empty()) + { + U32 preferredMaturity = (U32)LLAgent::convertTextToMaturity(text[0]); + + gSavedSettings.setU32("PreferredMaturity", preferredMaturity); + } + + text = response["start_location"].asString(); + if(!text.empty()) + { + gAgentStartLocation.assign(text); + } + + text = response["circuit_code"].asString(); + if(!text.empty()) + { + gMessageSystem->mOurCircuitCode = strtoul(text.c_str(), NULL, 10); + } + std::string sim_ip_str = response["sim_ip"]; + std::string sim_port_str = response["sim_port"]; + if(!sim_ip_str.empty() && !sim_port_str.empty()) + { + U32 sim_port = strtoul(sim_port_str.c_str(), NULL, 10); + gFirstSim.set(sim_ip_str, sim_port); + if (gFirstSim.isOk()) + { + gMessageSystem->enableCircuit(gFirstSim, true); + } + } + std::string region_x_str = response["region_x"]; + std::string region_y_str = response["region_y"]; + if(!region_x_str.empty() && !region_y_str.empty()) + { + U32 region_x = strtoul(region_x_str.c_str(), NULL, 10); + U32 region_y = strtoul(region_y_str.c_str(), NULL, 10); + gFirstSimHandle = to_region_handle(region_x, region_y); + } + + const std::string look_at_str = response["look_at"]; + if (!look_at_str.empty()) + { + size_t len = look_at_str.size(); + LLMemoryStream mstr((U8*)look_at_str.c_str(), len); + LLSD sd = LLSDSerialize::fromNotation(mstr, len); + gAgentStartLookAt = ll_vector3_from_sd(sd); + } + + text = response["seed_capability"].asString(); + if (!text.empty()) gFirstSimSeedCap = text; + + text = response["seconds_since_epoch"].asString(); + if(!text.empty()) + { + U32 server_utc_time = strtoul(text.c_str(), NULL, 10); + if(server_utc_time) + { + time_t now = time(NULL); + gUTCOffset = (server_utc_time - now); + + // Print server timestamp + LLSD substitution; + substitution["datetime"] = (S32)server_utc_time; + std::string timeStr = "[month, datetime, slt] [day, datetime, slt] [year, datetime, slt] [hour, datetime, slt]:[min, datetime, slt]:[second, datetime, slt]"; + LLStringUtil::format(timeStr, substitution); + LL_INFOS("AppInit") << "Server SLT timestamp: " << timeStr << ". Server-viewer time offset before correction: " << gUTCOffset << "s" << LL_ENDL; + } + } + + // this is the base used to construct help URLs + text = response["help_url_format"].asString(); + if (!text.empty()) + { + // replace the default help URL format + gSavedSettings.setString("HelpURLFormat",text); + } + + std::string home_location = response["home"]; + if(!home_location.empty()) + { + size_t len = home_location.size(); + LLMemoryStream mstr((U8*)home_location.c_str(), len); + LLSD sd = LLSDSerialize::fromNotation(mstr, len); + S32 region_x = sd["region_handle"][0].asInteger(); + S32 region_y = sd["region_handle"][1].asInteger(); + U64 region_handle = to_region_handle(region_x, region_y); + LLVector3 position = ll_vector3_from_sd(sd["position"]); + gAgent.setHomePosRegion(region_handle, position); + } + + gAgent.mMOTD.assign(response["message"]); + + // Options... + // Each 'option' is an array of submaps. + // It appears that we only ever use the first element of the array. + LLUUID inv_root_folder_id = response["inventory-root"][0]["folder_id"]; + if(inv_root_folder_id.notNull()) + { + gInventory.setRootFolderID(inv_root_folder_id); + //gInventory.mock(gAgent.getInventoryRootID()); + } + + LLSD login_flags = response["login-flags"][0]; + if(login_flags.size()) + { + std::string flag = login_flags["ever_logged_in"]; + if(!flag.empty()) + { + gAgent.setFirstLogin(flag == "N"); + } + + /* Flag is currently ignored by the viewer. + flag = login_flags["stipend_since_login"]; + if(flag == "Y") + { + stipend_since_login = true; + } + */ + + flag = login_flags["gendered"].asString(); + if(flag == "Y") + { + // We don't care about this flag anymore; now base whether + // outfit is chosen on COF contents, initial outfit + // requested and available, etc. + + //gAgent.setGenderChosen(true); + } + + bool pacific_daylight_time = false; + flag = login_flags["daylight_savings"].asString(); + if(flag == "Y") + { + pacific_daylight_time = (flag == "Y"); + } + + //setup map of datetime strings to codes and slt & local time offset from utc + LLStringOps::setupDatetimeInfo(pacific_daylight_time); + } + + // set up the voice configuration. Ultimately, we should pass this up as part of each voice + // channel if we need to move to multiple voice servers per grid. + LLSD voice_config_info = response["voice-config"]; + if(voice_config_info.has("VoiceServerType")) + { + gSavedSettings.setString("VoiceServerType", voice_config_info["VoiceServerType"].asString()); + } + + // Request the map server url + std::string map_server_url = response["map-server-url"]; + if(!map_server_url.empty()) + { + // We got an answer from the grid -> use that for map for the current session + gSavedSettings.setString("CurrentMapServerURL", map_server_url); + LL_INFOS("LLStartup") << "map-server-url : we got an answer from the grid : " << map_server_url << LL_ENDL; + } + else + { + // No answer from the grid -> use the default setting for current session + map_server_url = gSavedSettings.getString("MapServerURL"); + gSavedSettings.setString("CurrentMapServerURL", map_server_url); + LL_INFOS("LLStartup") << "map-server-url : no map-server-url answer, we use the default setting for the map : " << map_server_url << LL_ENDL; + } + + // Default male and female avatars allowing the user to choose their avatar on first login. + // These may be passed up by SLE to allow choice of enterprise avatars instead of the standard + // "new ruth." Not to be confused with 'initial-outfit' below + LLSD newuser_config = response["newuser-config"][0]; + if(newuser_config.has("DefaultFemaleAvatar")) + { + gSavedSettings.setString("DefaultFemaleAvatar", newuser_config["DefaultFemaleAvatar"].asString()); + } + if(newuser_config.has("DefaultMaleAvatar")) + { + gSavedSettings.setString("DefaultMaleAvatar", newuser_config["DefaultMaleAvatar"].asString()); + } + + // Initial outfit for the user. + LLSD initial_outfit = response["initial-outfit"][0]; + if(initial_outfit.size()) + { + std::string flag = initial_outfit["folder_name"]; + if(!flag.empty()) + { + // Initial outfit is a folder in your inventory, + // must be an exact folder-name match. + sInitialOutfit = flag; + } + + flag = initial_outfit["gender"].asString(); + if(!flag.empty()) + { + sInitialOutfitGender = flag; + } + } + + std::string fake_initial_outfit_name = gSavedSettings.getString("FakeInitialOutfitName"); + if (!fake_initial_outfit_name.empty()) + { + gAgent.setFirstLogin(true); + sInitialOutfit = fake_initial_outfit_name; + if (sInitialOutfitGender.empty()) + { + sInitialOutfitGender = "female"; // just guess, will get overridden when outfit is worn anyway. + } + + LL_WARNS() << "Faking first-time login with initial outfit " << sInitialOutfit << LL_ENDL; + } + + // set the location of the Agent Appearance service, from which we can request + // avatar baked textures if they are supported by the current region + std::string agent_appearance_url = response["agent_appearance_service"]; + if (!agent_appearance_url.empty()) + { + LLAppearanceMgr::instance().setAppearanceServiceURL(agent_appearance_url); + } + + // Set the location of the snapshot sharing config endpoint + std::string snapshot_config_url = response["snapshot_config_url"]; + if(!snapshot_config_url.empty()) + { + gSavedSettings.setString("SnapshotConfigURL", snapshot_config_url); + } + + // Start the process of fetching the OpenID session cookie for this user login + std::string openid_url = response["openid_url"]; + if(!openid_url.empty()) + { + std::string openid_token = response["openid_token"]; + LLViewerMedia::getInstance()->openIDSetup(openid_url, openid_token); + } + + + // Only save mfa_hash for future logins if the user wants their info remembered. + if(response.has("mfa_hash") + && gSavedSettings.getBOOL("RememberUser") + && LLLoginInstance::getInstance()->saveMFA()) + { + std::string grid(LLGridManager::getInstance()->getGridId()); + std::string user_id(gUserCredential->userID()); + gSecAPIHandler->addToProtectedMap("mfa_hash", grid, user_id, response["mfa_hash"]); + // TODO(brad) - related to SL-17223 consider building a better interface that sync's automatically + gSecAPIHandler->syncProtectedMap(); + } + else if (!LLLoginInstance::getInstance()->saveMFA()) + { + std::string grid(LLGridManager::getInstance()->getGridId()); + std::string user_id(gUserCredential->userID()); + gSecAPIHandler->removeFromProtectedMap("mfa_hash", grid, user_id); + gSecAPIHandler->syncProtectedMap(); + } + + bool success = false; + // JC: gesture loading done below, when we have an asset system + // in place. Don't delete/clear gUserCredentials until then. + if(gAgentID.notNull() + && gAgentSessionID.notNull() + && gMessageSystem->mOurCircuitCode + && gFirstSim.isOk() + && gInventory.getRootFolderID().notNull()) + { + success = true; + } + LLAppViewer* pApp = LLAppViewer::instance(); + pApp->writeDebugInfo(); //Write our static data now that we have username, session_id, etc. + return success; +} + +void transition_back_to_login_panel(const std::string& emsg) +{ + // Bounce back to the login screen. + reset_login(); // calls LLStartUp::setStartupState( STATE_LOGIN_SHOW ); + gSavedSettings.setBOOL("AutoLogin", false); +} + diff --git a/indra/newview/llstatusbar.cpp b/indra/newview/llstatusbar.cpp index 3de6191d18..ecbbc4b2c5 100644 --- a/indra/newview/llstatusbar.cpp +++ b/indra/newview/llstatusbar.cpp @@ -1,761 +1,761 @@ -/** -* @file llstatusbar.cpp -* @brief LLStatusBar class implementation -* -* $LicenseInfo:firstyear=2002&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2010, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#include "llviewerprecompiledheaders.h" - -#include "llstatusbar.h" - -// viewer includes -#include "llagent.h" -#include "llagentcamera.h" -#include "llbutton.h" -#include "llcommandhandler.h" -#include "llfirstuse.h" -#include "llviewercontrol.h" -#include "llfloaterbuycurrency.h" -#include "llbuycurrencyhtml.h" -#include "llpanelnearbymedia.h" -#include "llpanelpresetscamerapulldown.h" -#include "llpanelpresetspulldown.h" -#include "llpanelvolumepulldown.h" -#include "llfloaterregioninfo.h" -#include "llfloaterscriptdebug.h" -#include "llhints.h" -#include "llhudicon.h" -#include "llnavigationbar.h" -#include "llkeyboard.h" -#include "lllineeditor.h" -#include "llmenugl.h" -#include "llrootview.h" -#include "llsd.h" -#include "lltextbox.h" -#include "llui.h" -#include "llviewerparceloverlay.h" -#include "llviewerregion.h" -#include "llviewerstats.h" -#include "llviewerwindow.h" -#include "llframetimer.h" -#include "llvoavatarself.h" -#include "llresmgr.h" -#include "llworld.h" -#include "llstatgraph.h" -#include "llviewermedia.h" -#include "llviewermenu.h" // for gMenuBarView -#include "llviewerparcelmgr.h" -#include "llviewerthrottle.h" -#include "lluictrlfactory.h" - -#include "lltoolmgr.h" -#include "llfocusmgr.h" -#include "llappviewer.h" -#include "lltrans.h" - -// library includes -#include "llfloaterreg.h" -#include "llfontgl.h" -#include "llrect.h" -#include "llerror.h" -#include "llnotificationsutil.h" -#include "llparcel.h" -#include "llstring.h" -#include "message.h" -#include "llsearchableui.h" -#include "llsearcheditor.h" - -// system includes -#include - - -// -// Globals -// -LLStatusBar *gStatusBar = NULL; -S32 STATUS_BAR_HEIGHT = 26; -extern S32 MENU_BAR_HEIGHT; - - -// TODO: these values ought to be in the XML too -const S32 SIM_STAT_WIDTH = 8; -const LLColor4 SIM_OK_COLOR(0.f, 1.f, 0.f, 1.f); -const LLColor4 SIM_WARN_COLOR(1.f, 1.f, 0.f, 1.f); -const LLColor4 SIM_FULL_COLOR(1.f, 0.f, 0.f, 1.f); -const F32 ICON_TIMER_EXPIRY = 3.f; // How long the balance and health icons should flash after a change. - -static void onClickVolume(void*); - -LLStatusBar::LLStatusBar(const LLRect& rect) -: LLPanel(), - mTextTime(NULL), - mSGBandwidth(NULL), - mSGPacketLoss(NULL), - mBtnVolume(NULL), - mBoxBalance(NULL), - mBalance(0), - mHealth(100), - mSquareMetersCredit(0), - mSquareMetersCommitted(0), - mFilterEdit(NULL), // Edit for filtering - mSearchPanel(NULL) // Panel for filtering -{ - setRect(rect); - - // status bar can possible overlay menus? - setMouseOpaque(false); - - mBalanceTimer = new LLFrameTimer(); - mHealthTimer = new LLFrameTimer(); - - buildFromFile("panel_status_bar.xml"); -} - -LLStatusBar::~LLStatusBar() -{ - delete mBalanceTimer; - mBalanceTimer = NULL; - - delete mHealthTimer; - mHealthTimer = NULL; - - // LLView destructor cleans up children -} - -//----------------------------------------------------------------------- -// Overrides -//----------------------------------------------------------------------- - -// virtual -void LLStatusBar::draw() -{ - refresh(); - LLPanel::draw(); -} - -bool LLStatusBar::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - show_navbar_context_menu(this,x,y); - return true; -} - -bool LLStatusBar::postBuild() -{ - gMenuBarView->setRightMouseDownCallback(boost::bind(&show_navbar_context_menu, _1, _2, _3)); - - mTextTime = getChild("TimeText" ); - - getChild("buyL")->setCommitCallback( - boost::bind(&LLStatusBar::onClickBuyCurrency, this)); - - getChild("goShop")->setCommitCallback(boost::bind(&LLWeb::loadURL, gSavedSettings.getString("MarketplaceURL"), LLStringUtil::null, LLStringUtil::null)); - - mBoxBalance = getChild("balance"); - mBoxBalance->setClickedCallback( &LLStatusBar::onClickBalance, this ); - - mIconPresetsCamera = getChild( "presets_icon_camera" ); - mIconPresetsCamera->setMouseEnterCallback(boost::bind(&LLStatusBar::onMouseEnterPresetsCamera, this)); - - mIconPresetsGraphic = getChild( "presets_icon_graphic" ); - mIconPresetsGraphic->setMouseEnterCallback(boost::bind(&LLStatusBar::onMouseEnterPresets, this)); - - mBtnVolume = getChild( "volume_btn" ); - mBtnVolume->setClickedCallback( onClickVolume, this ); - mBtnVolume->setMouseEnterCallback(boost::bind(&LLStatusBar::onMouseEnterVolume, this)); - - mMediaToggle = getChild("media_toggle_btn"); - mMediaToggle->setClickedCallback( &LLStatusBar::onClickMediaToggle, this ); - mMediaToggle->setMouseEnterCallback(boost::bind(&LLStatusBar::onMouseEnterNearbyMedia, this)); - - LLHints::getInstance()->registerHintTarget("linden_balance", getChild("balance_bg")->getHandle()); - - gSavedSettings.getControl("MuteAudio")->getSignal()->connect(boost::bind(&LLStatusBar::onVolumeChanged, this, _2)); - gSavedSettings.getControl("EnableVoiceChat")->getSignal()->connect(boost::bind(&LLStatusBar::onVoiceChanged, this, _2)); - - if (!gSavedSettings.getBOOL("EnableVoiceChat") && LLAppViewer::instance()->isSecondInstance()) - { - // Indicate that second instance started without sound - mBtnVolume->setImageUnselected(LLUI::getUIImage("VoiceMute_Off")); - } - - // Adding Net Stat Graph - S32 x = getRect().getWidth() - 2; - S32 y = 0; - LLRect r; - r.set( x-SIM_STAT_WIDTH, y+MENU_BAR_HEIGHT-1, x, y+1); - LLStatGraph::Params sgp; - sgp.name("BandwidthGraph"); - sgp.rect(r); - sgp.follows.flags(FOLLOWS_BOTTOM | FOLLOWS_RIGHT); - sgp.mouse_opaque(false); - sgp.stat.count_stat_float(&LLStatViewer::ACTIVE_MESSAGE_DATA_RECEIVED); - sgp.units("Kbps"); - sgp.precision(0); - sgp.per_sec(true); - mSGBandwidth = LLUICtrlFactory::create(sgp); - addChild(mSGBandwidth); - x -= SIM_STAT_WIDTH + 2; - - r.set( x-SIM_STAT_WIDTH, y+MENU_BAR_HEIGHT-1, x, y+1); - //these don't seem to like being reused - LLStatGraph::Params pgp; - pgp.name("PacketLossPercent"); - pgp.rect(r); - pgp.follows.flags(FOLLOWS_BOTTOM | FOLLOWS_RIGHT); - pgp.mouse_opaque(false); - pgp.stat.sample_stat_float(&LLStatViewer::PACKETS_LOST_PERCENT); - pgp.units("%"); - pgp.min(0.f); - pgp.max(5.f); - pgp.precision(1); - pgp.per_sec(false); - LLStatGraph::Thresholds thresholds; - thresholds.threshold.add(LLStatGraph::ThresholdParams().value(0.1).color(LLColor4::green)) - .add(LLStatGraph::ThresholdParams().value(0.25f).color(LLColor4::yellow)) - .add(LLStatGraph::ThresholdParams().value(0.6f).color(LLColor4::red)); - - pgp.thresholds(thresholds); - - mSGPacketLoss = LLUICtrlFactory::create(pgp); - addChild(mSGPacketLoss); - - mPanelPresetsCameraPulldown = new LLPanelPresetsCameraPulldown(); - addChild(mPanelPresetsCameraPulldown); - mPanelPresetsCameraPulldown->setFollows(FOLLOWS_TOP|FOLLOWS_RIGHT); - mPanelPresetsCameraPulldown->setVisible(false); - - mPanelPresetsPulldown = new LLPanelPresetsPulldown(); - addChild(mPanelPresetsPulldown); - mPanelPresetsPulldown->setFollows(FOLLOWS_TOP|FOLLOWS_RIGHT); - mPanelPresetsPulldown->setVisible(false); - - mPanelVolumePulldown = new LLPanelVolumePulldown(); - addChild(mPanelVolumePulldown); - mPanelVolumePulldown->setFollows(FOLLOWS_TOP|FOLLOWS_RIGHT); - mPanelVolumePulldown->setVisible(false); - - mPanelNearByMedia = new LLPanelNearByMedia(); - addChild(mPanelNearByMedia); - mPanelNearByMedia->setFollows(FOLLOWS_TOP|FOLLOWS_RIGHT); - mPanelNearByMedia->setVisible(false); - - updateBalancePanelPosition(); - - // Hook up and init for filtering - mFilterEdit = getChild( "search_menu_edit" ); - mSearchPanel = getChild( "menu_search_panel" ); - - bool search_panel_visible = gSavedSettings.getBOOL("MenuSearch"); - mSearchPanel->setVisible(search_panel_visible); - mFilterEdit->setKeystrokeCallback(boost::bind(&LLStatusBar::onUpdateFilterTerm, this)); - mFilterEdit->setCommitCallback(boost::bind(&LLStatusBar::onUpdateFilterTerm, this)); - collectSearchableItems(); - gSavedSettings.getControl("MenuSearch")->getCommitSignal()->connect(boost::bind(&LLStatusBar::updateMenuSearchVisibility, this, _2)); - - if (search_panel_visible) - { - updateMenuSearchPosition(); - } - - return true; -} - -// Per-frame updates of visibility -void LLStatusBar::refresh() -{ - static LLCachedControl show_net_stats(gSavedSettings, "ShowNetStats", false); - bool net_stats_visible = show_net_stats; - - if (net_stats_visible) - { - // Adding Net Stat Meter back in - F32 bwtotal = gViewerThrottle.getMaxBandwidth() / 1000.f; - mSGBandwidth->setMin(0.f); - mSGBandwidth->setMax(bwtotal*1.25f); - //mSGBandwidth->setThreshold(0, bwtotal*0.75f); - //mSGBandwidth->setThreshold(1, bwtotal); - //mSGBandwidth->setThreshold(2, bwtotal); - } - - // update clock every 10 seconds - if(mClockUpdateTimer.getElapsedTimeF32() > 10.f) - { - mClockUpdateTimer.reset(); - - // Get current UTC time, adjusted for the user's clock - // being off. - time_t utc_time; - utc_time = time_corrected(); - - std::string timeStr = getString("time"); - LLSD substitution; - substitution["datetime"] = (S32) utc_time; - LLStringUtil::format (timeStr, substitution); - mTextTime->setText(timeStr); - - // set the tooltip to have the date - std::string dtStr = getString("timeTooltip"); - LLStringUtil::format (dtStr, substitution); - mTextTime->setToolTip (dtStr); - } - - LLRect r; - const S32 MENU_RIGHT = gMenuBarView->getRightmostMenuEdge(); - - // reshape menu bar to its content's width - if (MENU_RIGHT != gMenuBarView->getRect().getWidth()) - { - gMenuBarView->reshape(MENU_RIGHT, gMenuBarView->getRect().getHeight()); - } - - mSGBandwidth->setVisible(net_stats_visible); - mSGPacketLoss->setVisible(net_stats_visible); - - // update the master volume button state - bool mute_audio = LLAppViewer::instance()->getMasterSystemAudioMute(); - mBtnVolume->setToggleState(mute_audio); - - LLViewerMedia* media_inst = LLViewerMedia::getInstance(); - - // Disable media toggle if there's no media, parcel media, and no parcel audio - // (or if media is disabled) - bool button_enabled = (gSavedSettings.getBOOL("AudioStreamingMusic")||gSavedSettings.getBOOL("AudioStreamingMedia")) && - (media_inst->hasInWorldMedia() || media_inst->hasParcelMedia() || media_inst->hasParcelAudio()); - mMediaToggle->setEnabled(button_enabled); - // Note the "sense" of the toggle is opposite whether media is playing or not - bool any_media_playing = (media_inst->isAnyMediaPlaying() || - media_inst->isParcelMediaPlaying() || - media_inst->isParcelAudioPlaying()); - mMediaToggle->setValue(!any_media_playing); -} - -void LLStatusBar::setVisibleForMouselook(bool visible) -{ - mTextTime->setVisible(visible); - getChild("balance_bg")->setVisible(visible); - mBoxBalance->setVisible(visible); - mBtnVolume->setVisible(visible); - mMediaToggle->setVisible(visible); - mSGBandwidth->setVisible(visible); - mSGPacketLoss->setVisible(visible); - mSearchPanel->setVisible(visible && gSavedSettings.getBOOL("MenuSearch")); - setBackgroundVisible(visible); - mIconPresetsCamera->setVisible(visible); - mIconPresetsGraphic->setVisible(visible); -} - -void LLStatusBar::debitBalance(S32 debit) -{ - setBalance(getBalance() - debit); -} - -void LLStatusBar::creditBalance(S32 credit) -{ - setBalance(getBalance() + credit); -} - -void LLStatusBar::setBalance(S32 balance) -{ - if (balance > getBalance() && getBalance() != 0) - { - LLFirstUse::receiveLindens(); - } - - std::string money_str = LLResMgr::getInstance()->getMonetaryString( balance ); - - LLStringUtil::format_map_t string_args; - string_args["[AMT]"] = llformat("%s", money_str.c_str()); - std::string label_str = getString("buycurrencylabel", string_args); - mBoxBalance->setValue(label_str); - - updateBalancePanelPosition(); - - // If the search panel is shown, move this according to the new balance width. Parcel text will reshape itself in setParcelInfoText - if (mSearchPanel && mSearchPanel->getVisible()) - { - updateMenuSearchPosition(); - } - - if (mBalance && (fabs((F32)(mBalance - balance)) > gSavedSettings.getF32("UISndMoneyChangeThreshold"))) - { - if (mBalance > balance) - make_ui_sound("UISndMoneyChangeDown"); - else - make_ui_sound("UISndMoneyChangeUp"); - } - - if( balance != mBalance ) - { - mBalanceTimer->reset(); - mBalanceTimer->setTimerExpirySec( ICON_TIMER_EXPIRY ); - mBalance = balance; - } -} - - -// static -void LLStatusBar::sendMoneyBalanceRequest() -{ - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_MoneyBalanceRequest); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_MoneyData); - msg->addUUIDFast(_PREHASH_TransactionID, LLUUID::null ); - - if (gDisconnected) - { - LL_DEBUGS() << "Trying to send message when disconnected, skipping balance request!" << LL_ENDL; - return; - } - if (!gAgent.getRegion()) - { - LL_DEBUGS() << "LLAgent::sendReliableMessage No region for agent yet, skipping balance request!" << LL_ENDL; - return; - } - // Double amount of retries due to this request initially happening during busy stage - // Ideally this should be turned into a capability - gMessageSystem->sendReliable(gAgent.getRegionHost(), LL_DEFAULT_RELIABLE_RETRIES * 2, true, LL_PING_BASED_TIMEOUT_DUMMY, NULL, NULL); -} - - -void LLStatusBar::setHealth(S32 health) -{ - //LL_INFOS() << "Setting health to: " << buffer << LL_ENDL; - if( mHealth > health ) - { - if (mHealth > (health + gSavedSettings.getF32("UISndHealthReductionThreshold"))) - { - if (isAgentAvatarValid()) - { - if (gAgentAvatarp->getSex() == SEX_FEMALE) - { - make_ui_sound("UISndHealthReductionF"); - } - else - { - make_ui_sound("UISndHealthReductionM"); - } - } - } - - mHealthTimer->reset(); - mHealthTimer->setTimerExpirySec( ICON_TIMER_EXPIRY ); - } - - mHealth = health; -} - -S32 LLStatusBar::getBalance() const -{ - return mBalance; -} - - -S32 LLStatusBar::getHealth() const -{ - return mHealth; -} - -void LLStatusBar::setLandCredit(S32 credit) -{ - mSquareMetersCredit = credit; -} -void LLStatusBar::setLandCommitted(S32 committed) -{ - mSquareMetersCommitted = committed; -} - -bool LLStatusBar::isUserTiered() const -{ - return (mSquareMetersCredit > 0); -} - -S32 LLStatusBar::getSquareMetersCredit() const -{ - return mSquareMetersCredit; -} - -S32 LLStatusBar::getSquareMetersCommitted() const -{ - return mSquareMetersCommitted; -} - -S32 LLStatusBar::getSquareMetersLeft() const -{ - return mSquareMetersCredit - mSquareMetersCommitted; -} - -void LLStatusBar::onClickBuyCurrency() -{ - // open a currency floater - actual one open depends on - // value specified in settings.xml - LLBuyCurrencyHTML::openCurrencyFloater(); - LLFirstUse::receiveLindens(false); -} - -void LLStatusBar::onMouseEnterPresetsCamera() -{ - LLView* popup_holder = gViewerWindow->getRootView()->getChildView("popup_holder"); - LLIconCtrl* icon = getChild( "presets_icon_camera" ); - LLRect icon_rect = icon->getRect(); - LLRect pulldown_rect = mPanelPresetsCameraPulldown->getRect(); - pulldown_rect.setLeftTopAndSize(icon_rect.mLeft - - (pulldown_rect.getWidth() - icon_rect.getWidth()), - icon_rect.mBottom, - pulldown_rect.getWidth(), - pulldown_rect.getHeight()); - - pulldown_rect.translate(popup_holder->getRect().getWidth() - pulldown_rect.mRight, 0); - mPanelPresetsCameraPulldown->setShape(pulldown_rect); - - // show the master presets pull-down - LLUI::getInstance()->clearPopups(); - LLUI::getInstance()->addPopup(mPanelPresetsCameraPulldown); - mPanelNearByMedia->setVisible(false); - mPanelVolumePulldown->setVisible(false); - mPanelPresetsPulldown->setVisible(false); - mPanelPresetsCameraPulldown->setVisible(true); -} - -void LLStatusBar::onMouseEnterPresets() -{ - LLView* popup_holder = gViewerWindow->getRootView()->getChildView("popup_holder"); - LLIconCtrl* icon = getChild( "presets_icon_graphic" ); - LLRect icon_rect = icon->getRect(); - LLRect pulldown_rect = mPanelPresetsPulldown->getRect(); - pulldown_rect.setLeftTopAndSize(icon_rect.mLeft - - (pulldown_rect.getWidth() - icon_rect.getWidth()), - icon_rect.mBottom, - pulldown_rect.getWidth(), - pulldown_rect.getHeight()); - - pulldown_rect.translate(popup_holder->getRect().getWidth() - pulldown_rect.mRight, 0); - mPanelPresetsPulldown->setShape(pulldown_rect); - - // show the master presets pull-down - LLUI::getInstance()->clearPopups(); - LLUI::getInstance()->addPopup(mPanelPresetsPulldown); - mPanelNearByMedia->setVisible(false); - mPanelVolumePulldown->setVisible(false); - mPanelPresetsPulldown->setVisible(true); -} - -void LLStatusBar::onMouseEnterVolume() -{ - LLView* popup_holder = gViewerWindow->getRootView()->getChildView("popup_holder"); - LLButton* volbtn = getChild( "volume_btn" ); - LLRect vol_btn_rect = volbtn->getRect(); - LLRect volume_pulldown_rect = mPanelVolumePulldown->getRect(); - volume_pulldown_rect.setLeftTopAndSize(vol_btn_rect.mLeft - - (volume_pulldown_rect.getWidth() - vol_btn_rect.getWidth()), - vol_btn_rect.mBottom, - volume_pulldown_rect.getWidth(), - volume_pulldown_rect.getHeight()); - - volume_pulldown_rect.translate(popup_holder->getRect().getWidth() - volume_pulldown_rect.mRight, 0); - mPanelVolumePulldown->setShape(volume_pulldown_rect); - - - // show the master volume pull-down - LLUI::getInstance()->clearPopups(); - LLUI::getInstance()->addPopup(mPanelVolumePulldown); - mPanelPresetsCameraPulldown->setVisible(false); - mPanelPresetsPulldown->setVisible(false); - mPanelNearByMedia->setVisible(false); - mPanelVolumePulldown->setVisible(true); -} - -void LLStatusBar::onMouseEnterNearbyMedia() -{ - LLView* popup_holder = gViewerWindow->getRootView()->getChildView("popup_holder"); - LLRect nearby_media_rect = mPanelNearByMedia->getRect(); - LLButton* nearby_media_btn = getChild( "media_toggle_btn" ); - LLRect nearby_media_btn_rect = nearby_media_btn->getRect(); - nearby_media_rect.setLeftTopAndSize(nearby_media_btn_rect.mLeft - - (nearby_media_rect.getWidth() - nearby_media_btn_rect.getWidth())/2, - nearby_media_btn_rect.mBottom, - nearby_media_rect.getWidth(), - nearby_media_rect.getHeight()); - // force onscreen - nearby_media_rect.translate(popup_holder->getRect().getWidth() - nearby_media_rect.mRight, 0); - - // show the master volume pull-down - mPanelNearByMedia->setShape(nearby_media_rect); - LLUI::getInstance()->clearPopups(); - LLUI::getInstance()->addPopup(mPanelNearByMedia); - - mPanelPresetsCameraPulldown->setVisible(false); - mPanelPresetsPulldown->setVisible(false); - mPanelVolumePulldown->setVisible(false); - mPanelNearByMedia->setVisible(true); -} - - -static void onClickVolume(void* data) -{ - // toggle the master mute setting - bool mute_audio = LLAppViewer::instance()->getMasterSystemAudioMute(); - LLAppViewer::instance()->setMasterSystemAudioMute(!mute_audio); -} - -//static -void LLStatusBar::onClickBalance(void* ) -{ - // Force a balance request message: - LLStatusBar::sendMoneyBalanceRequest(); - // The refresh of the display (call to setBalance()) will be done by process_money_balance_reply() -} - -//static -void LLStatusBar::onClickMediaToggle(void* data) -{ - LLStatusBar *status_bar = (LLStatusBar*)data; - // "Selected" means it was showing the "play" icon (so media was playing), and now it shows "pause", so turn off media - bool pause = status_bar->mMediaToggle->getValue(); - LLViewerMedia::getInstance()->setAllMediaPaused(pause); -} - -bool can_afford_transaction(S32 cost) -{ - return((cost <= 0)||((gStatusBar) && (gStatusBar->getBalance() >=cost))); -} - -void LLStatusBar::onVolumeChanged(const LLSD& newvalue) -{ - refresh(); -} - -void LLStatusBar::onVoiceChanged(const LLSD& newvalue) -{ - if (newvalue.asBoolean()) - { - // Second instance starts with "VoiceMute_Off" icon, fix it - mBtnVolume->setImageUnselected(LLUI::getUIImage("Audio_Off")); - } - refresh(); -} - -void LLStatusBar::onUpdateFilterTerm() -{ - LLWString searchValue = utf8str_to_wstring( mFilterEdit->getValue() ); - LLWStringUtil::toLower( searchValue ); - - if( !mSearchData || mSearchData->mLastFilter == searchValue ) - return; - - mSearchData->mLastFilter = searchValue; - - mSearchData->mRootMenu->hightlightAndHide( searchValue ); - gMenuBarView->needsArrange(); -} - -void collectChildren( LLMenuGL *aMenu, ll::statusbar::SearchableItemPtr aParentMenu ) -{ - for( U32 i = 0; i < aMenu->getItemCount(); ++i ) - { - LLMenuItemGL *pMenu = aMenu->getItem( i ); - - ll::statusbar::SearchableItemPtr pItem( new ll::statusbar::SearchableItem ); - pItem->mCtrl = pMenu; - pItem->mMenu = pMenu; - pItem->mLabel = utf8str_to_wstring( pMenu->ll::ui::SearchableControl::getSearchText() ); - LLWStringUtil::toLower( pItem->mLabel ); - aParentMenu->mChildren.push_back( pItem ); - - LLMenuItemBranchGL *pBranch = dynamic_cast< LLMenuItemBranchGL* >( pMenu ); - if( pBranch ) - collectChildren( pBranch->getBranch(), pItem ); - } - -} - -void LLStatusBar::collectSearchableItems() -{ - mSearchData.reset( new ll::statusbar::SearchData ); - ll::statusbar::SearchableItemPtr pItem( new ll::statusbar::SearchableItem ); - mSearchData->mRootMenu = pItem; - collectChildren( gMenuBarView, pItem ); -} - -void LLStatusBar::updateMenuSearchVisibility(const LLSD& data) -{ - bool visible = data.asBoolean(); - mSearchPanel->setVisible(visible); - if (!visible) - { - mFilterEdit->setText(LLStringUtil::null); - onUpdateFilterTerm(); - } - else - { - updateMenuSearchPosition(); - } -} - -void LLStatusBar::updateMenuSearchPosition() -{ - const S32 HPAD = 12; - LLRect balanceRect = getChildView("balance_bg")->getRect(); - LLRect searchRect = mSearchPanel->getRect(); - S32 w = searchRect.getWidth(); - searchRect.mLeft = balanceRect.mLeft - w - HPAD; - searchRect.mRight = searchRect.mLeft + w; - mSearchPanel->setShape( searchRect ); -} - -void LLStatusBar::updateBalancePanelPosition() -{ - // Resize the L$ balance background to be wide enough for your balance plus the buy button - const S32 HPAD = 24; - LLRect balance_rect = mBoxBalance->getTextBoundingRect(); - LLRect buy_rect = getChildView("buyL")->getRect(); - LLRect shop_rect = getChildView("goShop")->getRect(); - LLView* balance_bg_view = getChildView("balance_bg"); - LLRect balance_bg_rect = balance_bg_view->getRect(); - balance_bg_rect.mLeft = balance_bg_rect.mRight - (buy_rect.getWidth() + shop_rect.getWidth() + balance_rect.getWidth() + HPAD); - balance_bg_view->setShape(balance_bg_rect); -} - - -// Implements secondlife:///app/balance/request to request a L$ balance -// update via UDP message system. JC -class LLBalanceHandler : public LLCommandHandler -{ -public: - // Requires "trusted" browser/URL source - LLBalanceHandler() : LLCommandHandler("balance", UNTRUSTED_BLOCK) { } - bool handle(const LLSD& tokens, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) - { - if (tokens.size() == 1 - && tokens[0].asString() == "request") - { - LLStatusBar::sendMoneyBalanceRequest(); - return true; - } - return false; - } -}; -// register with command dispatch system -LLBalanceHandler gBalanceHandler; +/** +* @file llstatusbar.cpp +* @brief LLStatusBar class implementation +* +* $LicenseInfo:firstyear=2002&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2010, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" + +#include "llstatusbar.h" + +// viewer includes +#include "llagent.h" +#include "llagentcamera.h" +#include "llbutton.h" +#include "llcommandhandler.h" +#include "llfirstuse.h" +#include "llviewercontrol.h" +#include "llfloaterbuycurrency.h" +#include "llbuycurrencyhtml.h" +#include "llpanelnearbymedia.h" +#include "llpanelpresetscamerapulldown.h" +#include "llpanelpresetspulldown.h" +#include "llpanelvolumepulldown.h" +#include "llfloaterregioninfo.h" +#include "llfloaterscriptdebug.h" +#include "llhints.h" +#include "llhudicon.h" +#include "llnavigationbar.h" +#include "llkeyboard.h" +#include "lllineeditor.h" +#include "llmenugl.h" +#include "llrootview.h" +#include "llsd.h" +#include "lltextbox.h" +#include "llui.h" +#include "llviewerparceloverlay.h" +#include "llviewerregion.h" +#include "llviewerstats.h" +#include "llviewerwindow.h" +#include "llframetimer.h" +#include "llvoavatarself.h" +#include "llresmgr.h" +#include "llworld.h" +#include "llstatgraph.h" +#include "llviewermedia.h" +#include "llviewermenu.h" // for gMenuBarView +#include "llviewerparcelmgr.h" +#include "llviewerthrottle.h" +#include "lluictrlfactory.h" + +#include "lltoolmgr.h" +#include "llfocusmgr.h" +#include "llappviewer.h" +#include "lltrans.h" + +// library includes +#include "llfloaterreg.h" +#include "llfontgl.h" +#include "llrect.h" +#include "llerror.h" +#include "llnotificationsutil.h" +#include "llparcel.h" +#include "llstring.h" +#include "message.h" +#include "llsearchableui.h" +#include "llsearcheditor.h" + +// system includes +#include + + +// +// Globals +// +LLStatusBar *gStatusBar = NULL; +S32 STATUS_BAR_HEIGHT = 26; +extern S32 MENU_BAR_HEIGHT; + + +// TODO: these values ought to be in the XML too +const S32 SIM_STAT_WIDTH = 8; +const LLColor4 SIM_OK_COLOR(0.f, 1.f, 0.f, 1.f); +const LLColor4 SIM_WARN_COLOR(1.f, 1.f, 0.f, 1.f); +const LLColor4 SIM_FULL_COLOR(1.f, 0.f, 0.f, 1.f); +const F32 ICON_TIMER_EXPIRY = 3.f; // How long the balance and health icons should flash after a change. + +static void onClickVolume(void*); + +LLStatusBar::LLStatusBar(const LLRect& rect) +: LLPanel(), + mTextTime(NULL), + mSGBandwidth(NULL), + mSGPacketLoss(NULL), + mBtnVolume(NULL), + mBoxBalance(NULL), + mBalance(0), + mHealth(100), + mSquareMetersCredit(0), + mSquareMetersCommitted(0), + mFilterEdit(NULL), // Edit for filtering + mSearchPanel(NULL) // Panel for filtering +{ + setRect(rect); + + // status bar can possible overlay menus? + setMouseOpaque(false); + + mBalanceTimer = new LLFrameTimer(); + mHealthTimer = new LLFrameTimer(); + + buildFromFile("panel_status_bar.xml"); +} + +LLStatusBar::~LLStatusBar() +{ + delete mBalanceTimer; + mBalanceTimer = NULL; + + delete mHealthTimer; + mHealthTimer = NULL; + + // LLView destructor cleans up children +} + +//----------------------------------------------------------------------- +// Overrides +//----------------------------------------------------------------------- + +// virtual +void LLStatusBar::draw() +{ + refresh(); + LLPanel::draw(); +} + +bool LLStatusBar::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + show_navbar_context_menu(this,x,y); + return true; +} + +bool LLStatusBar::postBuild() +{ + gMenuBarView->setRightMouseDownCallback(boost::bind(&show_navbar_context_menu, _1, _2, _3)); + + mTextTime = getChild("TimeText" ); + + getChild("buyL")->setCommitCallback( + boost::bind(&LLStatusBar::onClickBuyCurrency, this)); + + getChild("goShop")->setCommitCallback(boost::bind(&LLWeb::loadURL, gSavedSettings.getString("MarketplaceURL"), LLStringUtil::null, LLStringUtil::null)); + + mBoxBalance = getChild("balance"); + mBoxBalance->setClickedCallback( &LLStatusBar::onClickBalance, this ); + + mIconPresetsCamera = getChild( "presets_icon_camera" ); + mIconPresetsCamera->setMouseEnterCallback(boost::bind(&LLStatusBar::onMouseEnterPresetsCamera, this)); + + mIconPresetsGraphic = getChild( "presets_icon_graphic" ); + mIconPresetsGraphic->setMouseEnterCallback(boost::bind(&LLStatusBar::onMouseEnterPresets, this)); + + mBtnVolume = getChild( "volume_btn" ); + mBtnVolume->setClickedCallback( onClickVolume, this ); + mBtnVolume->setMouseEnterCallback(boost::bind(&LLStatusBar::onMouseEnterVolume, this)); + + mMediaToggle = getChild("media_toggle_btn"); + mMediaToggle->setClickedCallback( &LLStatusBar::onClickMediaToggle, this ); + mMediaToggle->setMouseEnterCallback(boost::bind(&LLStatusBar::onMouseEnterNearbyMedia, this)); + + LLHints::getInstance()->registerHintTarget("linden_balance", getChild("balance_bg")->getHandle()); + + gSavedSettings.getControl("MuteAudio")->getSignal()->connect(boost::bind(&LLStatusBar::onVolumeChanged, this, _2)); + gSavedSettings.getControl("EnableVoiceChat")->getSignal()->connect(boost::bind(&LLStatusBar::onVoiceChanged, this, _2)); + + if (!gSavedSettings.getBOOL("EnableVoiceChat") && LLAppViewer::instance()->isSecondInstance()) + { + // Indicate that second instance started without sound + mBtnVolume->setImageUnselected(LLUI::getUIImage("VoiceMute_Off")); + } + + // Adding Net Stat Graph + S32 x = getRect().getWidth() - 2; + S32 y = 0; + LLRect r; + r.set( x-SIM_STAT_WIDTH, y+MENU_BAR_HEIGHT-1, x, y+1); + LLStatGraph::Params sgp; + sgp.name("BandwidthGraph"); + sgp.rect(r); + sgp.follows.flags(FOLLOWS_BOTTOM | FOLLOWS_RIGHT); + sgp.mouse_opaque(false); + sgp.stat.count_stat_float(&LLStatViewer::ACTIVE_MESSAGE_DATA_RECEIVED); + sgp.units("Kbps"); + sgp.precision(0); + sgp.per_sec(true); + mSGBandwidth = LLUICtrlFactory::create(sgp); + addChild(mSGBandwidth); + x -= SIM_STAT_WIDTH + 2; + + r.set( x-SIM_STAT_WIDTH, y+MENU_BAR_HEIGHT-1, x, y+1); + //these don't seem to like being reused + LLStatGraph::Params pgp; + pgp.name("PacketLossPercent"); + pgp.rect(r); + pgp.follows.flags(FOLLOWS_BOTTOM | FOLLOWS_RIGHT); + pgp.mouse_opaque(false); + pgp.stat.sample_stat_float(&LLStatViewer::PACKETS_LOST_PERCENT); + pgp.units("%"); + pgp.min(0.f); + pgp.max(5.f); + pgp.precision(1); + pgp.per_sec(false); + LLStatGraph::Thresholds thresholds; + thresholds.threshold.add(LLStatGraph::ThresholdParams().value(0.1).color(LLColor4::green)) + .add(LLStatGraph::ThresholdParams().value(0.25f).color(LLColor4::yellow)) + .add(LLStatGraph::ThresholdParams().value(0.6f).color(LLColor4::red)); + + pgp.thresholds(thresholds); + + mSGPacketLoss = LLUICtrlFactory::create(pgp); + addChild(mSGPacketLoss); + + mPanelPresetsCameraPulldown = new LLPanelPresetsCameraPulldown(); + addChild(mPanelPresetsCameraPulldown); + mPanelPresetsCameraPulldown->setFollows(FOLLOWS_TOP|FOLLOWS_RIGHT); + mPanelPresetsCameraPulldown->setVisible(false); + + mPanelPresetsPulldown = new LLPanelPresetsPulldown(); + addChild(mPanelPresetsPulldown); + mPanelPresetsPulldown->setFollows(FOLLOWS_TOP|FOLLOWS_RIGHT); + mPanelPresetsPulldown->setVisible(false); + + mPanelVolumePulldown = new LLPanelVolumePulldown(); + addChild(mPanelVolumePulldown); + mPanelVolumePulldown->setFollows(FOLLOWS_TOP|FOLLOWS_RIGHT); + mPanelVolumePulldown->setVisible(false); + + mPanelNearByMedia = new LLPanelNearByMedia(); + addChild(mPanelNearByMedia); + mPanelNearByMedia->setFollows(FOLLOWS_TOP|FOLLOWS_RIGHT); + mPanelNearByMedia->setVisible(false); + + updateBalancePanelPosition(); + + // Hook up and init for filtering + mFilterEdit = getChild( "search_menu_edit" ); + mSearchPanel = getChild( "menu_search_panel" ); + + bool search_panel_visible = gSavedSettings.getBOOL("MenuSearch"); + mSearchPanel->setVisible(search_panel_visible); + mFilterEdit->setKeystrokeCallback(boost::bind(&LLStatusBar::onUpdateFilterTerm, this)); + mFilterEdit->setCommitCallback(boost::bind(&LLStatusBar::onUpdateFilterTerm, this)); + collectSearchableItems(); + gSavedSettings.getControl("MenuSearch")->getCommitSignal()->connect(boost::bind(&LLStatusBar::updateMenuSearchVisibility, this, _2)); + + if (search_panel_visible) + { + updateMenuSearchPosition(); + } + + return true; +} + +// Per-frame updates of visibility +void LLStatusBar::refresh() +{ + static LLCachedControl show_net_stats(gSavedSettings, "ShowNetStats", false); + bool net_stats_visible = show_net_stats; + + if (net_stats_visible) + { + // Adding Net Stat Meter back in + F32 bwtotal = gViewerThrottle.getMaxBandwidth() / 1000.f; + mSGBandwidth->setMin(0.f); + mSGBandwidth->setMax(bwtotal*1.25f); + //mSGBandwidth->setThreshold(0, bwtotal*0.75f); + //mSGBandwidth->setThreshold(1, bwtotal); + //mSGBandwidth->setThreshold(2, bwtotal); + } + + // update clock every 10 seconds + if(mClockUpdateTimer.getElapsedTimeF32() > 10.f) + { + mClockUpdateTimer.reset(); + + // Get current UTC time, adjusted for the user's clock + // being off. + time_t utc_time; + utc_time = time_corrected(); + + std::string timeStr = getString("time"); + LLSD substitution; + substitution["datetime"] = (S32) utc_time; + LLStringUtil::format (timeStr, substitution); + mTextTime->setText(timeStr); + + // set the tooltip to have the date + std::string dtStr = getString("timeTooltip"); + LLStringUtil::format (dtStr, substitution); + mTextTime->setToolTip (dtStr); + } + + LLRect r; + const S32 MENU_RIGHT = gMenuBarView->getRightmostMenuEdge(); + + // reshape menu bar to its content's width + if (MENU_RIGHT != gMenuBarView->getRect().getWidth()) + { + gMenuBarView->reshape(MENU_RIGHT, gMenuBarView->getRect().getHeight()); + } + + mSGBandwidth->setVisible(net_stats_visible); + mSGPacketLoss->setVisible(net_stats_visible); + + // update the master volume button state + bool mute_audio = LLAppViewer::instance()->getMasterSystemAudioMute(); + mBtnVolume->setToggleState(mute_audio); + + LLViewerMedia* media_inst = LLViewerMedia::getInstance(); + + // Disable media toggle if there's no media, parcel media, and no parcel audio + // (or if media is disabled) + bool button_enabled = (gSavedSettings.getBOOL("AudioStreamingMusic")||gSavedSettings.getBOOL("AudioStreamingMedia")) && + (media_inst->hasInWorldMedia() || media_inst->hasParcelMedia() || media_inst->hasParcelAudio()); + mMediaToggle->setEnabled(button_enabled); + // Note the "sense" of the toggle is opposite whether media is playing or not + bool any_media_playing = (media_inst->isAnyMediaPlaying() || + media_inst->isParcelMediaPlaying() || + media_inst->isParcelAudioPlaying()); + mMediaToggle->setValue(!any_media_playing); +} + +void LLStatusBar::setVisibleForMouselook(bool visible) +{ + mTextTime->setVisible(visible); + getChild("balance_bg")->setVisible(visible); + mBoxBalance->setVisible(visible); + mBtnVolume->setVisible(visible); + mMediaToggle->setVisible(visible); + mSGBandwidth->setVisible(visible); + mSGPacketLoss->setVisible(visible); + mSearchPanel->setVisible(visible && gSavedSettings.getBOOL("MenuSearch")); + setBackgroundVisible(visible); + mIconPresetsCamera->setVisible(visible); + mIconPresetsGraphic->setVisible(visible); +} + +void LLStatusBar::debitBalance(S32 debit) +{ + setBalance(getBalance() - debit); +} + +void LLStatusBar::creditBalance(S32 credit) +{ + setBalance(getBalance() + credit); +} + +void LLStatusBar::setBalance(S32 balance) +{ + if (balance > getBalance() && getBalance() != 0) + { + LLFirstUse::receiveLindens(); + } + + std::string money_str = LLResMgr::getInstance()->getMonetaryString( balance ); + + LLStringUtil::format_map_t string_args; + string_args["[AMT]"] = llformat("%s", money_str.c_str()); + std::string label_str = getString("buycurrencylabel", string_args); + mBoxBalance->setValue(label_str); + + updateBalancePanelPosition(); + + // If the search panel is shown, move this according to the new balance width. Parcel text will reshape itself in setParcelInfoText + if (mSearchPanel && mSearchPanel->getVisible()) + { + updateMenuSearchPosition(); + } + + if (mBalance && (fabs((F32)(mBalance - balance)) > gSavedSettings.getF32("UISndMoneyChangeThreshold"))) + { + if (mBalance > balance) + make_ui_sound("UISndMoneyChangeDown"); + else + make_ui_sound("UISndMoneyChangeUp"); + } + + if( balance != mBalance ) + { + mBalanceTimer->reset(); + mBalanceTimer->setTimerExpirySec( ICON_TIMER_EXPIRY ); + mBalance = balance; + } +} + + +// static +void LLStatusBar::sendMoneyBalanceRequest() +{ + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_MoneyBalanceRequest); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_MoneyData); + msg->addUUIDFast(_PREHASH_TransactionID, LLUUID::null ); + + if (gDisconnected) + { + LL_DEBUGS() << "Trying to send message when disconnected, skipping balance request!" << LL_ENDL; + return; + } + if (!gAgent.getRegion()) + { + LL_DEBUGS() << "LLAgent::sendReliableMessage No region for agent yet, skipping balance request!" << LL_ENDL; + return; + } + // Double amount of retries due to this request initially happening during busy stage + // Ideally this should be turned into a capability + gMessageSystem->sendReliable(gAgent.getRegionHost(), LL_DEFAULT_RELIABLE_RETRIES * 2, true, LL_PING_BASED_TIMEOUT_DUMMY, NULL, NULL); +} + + +void LLStatusBar::setHealth(S32 health) +{ + //LL_INFOS() << "Setting health to: " << buffer << LL_ENDL; + if( mHealth > health ) + { + if (mHealth > (health + gSavedSettings.getF32("UISndHealthReductionThreshold"))) + { + if (isAgentAvatarValid()) + { + if (gAgentAvatarp->getSex() == SEX_FEMALE) + { + make_ui_sound("UISndHealthReductionF"); + } + else + { + make_ui_sound("UISndHealthReductionM"); + } + } + } + + mHealthTimer->reset(); + mHealthTimer->setTimerExpirySec( ICON_TIMER_EXPIRY ); + } + + mHealth = health; +} + +S32 LLStatusBar::getBalance() const +{ + return mBalance; +} + + +S32 LLStatusBar::getHealth() const +{ + return mHealth; +} + +void LLStatusBar::setLandCredit(S32 credit) +{ + mSquareMetersCredit = credit; +} +void LLStatusBar::setLandCommitted(S32 committed) +{ + mSquareMetersCommitted = committed; +} + +bool LLStatusBar::isUserTiered() const +{ + return (mSquareMetersCredit > 0); +} + +S32 LLStatusBar::getSquareMetersCredit() const +{ + return mSquareMetersCredit; +} + +S32 LLStatusBar::getSquareMetersCommitted() const +{ + return mSquareMetersCommitted; +} + +S32 LLStatusBar::getSquareMetersLeft() const +{ + return mSquareMetersCredit - mSquareMetersCommitted; +} + +void LLStatusBar::onClickBuyCurrency() +{ + // open a currency floater - actual one open depends on + // value specified in settings.xml + LLBuyCurrencyHTML::openCurrencyFloater(); + LLFirstUse::receiveLindens(false); +} + +void LLStatusBar::onMouseEnterPresetsCamera() +{ + LLView* popup_holder = gViewerWindow->getRootView()->getChildView("popup_holder"); + LLIconCtrl* icon = getChild( "presets_icon_camera" ); + LLRect icon_rect = icon->getRect(); + LLRect pulldown_rect = mPanelPresetsCameraPulldown->getRect(); + pulldown_rect.setLeftTopAndSize(icon_rect.mLeft - + (pulldown_rect.getWidth() - icon_rect.getWidth()), + icon_rect.mBottom, + pulldown_rect.getWidth(), + pulldown_rect.getHeight()); + + pulldown_rect.translate(popup_holder->getRect().getWidth() - pulldown_rect.mRight, 0); + mPanelPresetsCameraPulldown->setShape(pulldown_rect); + + // show the master presets pull-down + LLUI::getInstance()->clearPopups(); + LLUI::getInstance()->addPopup(mPanelPresetsCameraPulldown); + mPanelNearByMedia->setVisible(false); + mPanelVolumePulldown->setVisible(false); + mPanelPresetsPulldown->setVisible(false); + mPanelPresetsCameraPulldown->setVisible(true); +} + +void LLStatusBar::onMouseEnterPresets() +{ + LLView* popup_holder = gViewerWindow->getRootView()->getChildView("popup_holder"); + LLIconCtrl* icon = getChild( "presets_icon_graphic" ); + LLRect icon_rect = icon->getRect(); + LLRect pulldown_rect = mPanelPresetsPulldown->getRect(); + pulldown_rect.setLeftTopAndSize(icon_rect.mLeft - + (pulldown_rect.getWidth() - icon_rect.getWidth()), + icon_rect.mBottom, + pulldown_rect.getWidth(), + pulldown_rect.getHeight()); + + pulldown_rect.translate(popup_holder->getRect().getWidth() - pulldown_rect.mRight, 0); + mPanelPresetsPulldown->setShape(pulldown_rect); + + // show the master presets pull-down + LLUI::getInstance()->clearPopups(); + LLUI::getInstance()->addPopup(mPanelPresetsPulldown); + mPanelNearByMedia->setVisible(false); + mPanelVolumePulldown->setVisible(false); + mPanelPresetsPulldown->setVisible(true); +} + +void LLStatusBar::onMouseEnterVolume() +{ + LLView* popup_holder = gViewerWindow->getRootView()->getChildView("popup_holder"); + LLButton* volbtn = getChild( "volume_btn" ); + LLRect vol_btn_rect = volbtn->getRect(); + LLRect volume_pulldown_rect = mPanelVolumePulldown->getRect(); + volume_pulldown_rect.setLeftTopAndSize(vol_btn_rect.mLeft - + (volume_pulldown_rect.getWidth() - vol_btn_rect.getWidth()), + vol_btn_rect.mBottom, + volume_pulldown_rect.getWidth(), + volume_pulldown_rect.getHeight()); + + volume_pulldown_rect.translate(popup_holder->getRect().getWidth() - volume_pulldown_rect.mRight, 0); + mPanelVolumePulldown->setShape(volume_pulldown_rect); + + + // show the master volume pull-down + LLUI::getInstance()->clearPopups(); + LLUI::getInstance()->addPopup(mPanelVolumePulldown); + mPanelPresetsCameraPulldown->setVisible(false); + mPanelPresetsPulldown->setVisible(false); + mPanelNearByMedia->setVisible(false); + mPanelVolumePulldown->setVisible(true); +} + +void LLStatusBar::onMouseEnterNearbyMedia() +{ + LLView* popup_holder = gViewerWindow->getRootView()->getChildView("popup_holder"); + LLRect nearby_media_rect = mPanelNearByMedia->getRect(); + LLButton* nearby_media_btn = getChild( "media_toggle_btn" ); + LLRect nearby_media_btn_rect = nearby_media_btn->getRect(); + nearby_media_rect.setLeftTopAndSize(nearby_media_btn_rect.mLeft - + (nearby_media_rect.getWidth() - nearby_media_btn_rect.getWidth())/2, + nearby_media_btn_rect.mBottom, + nearby_media_rect.getWidth(), + nearby_media_rect.getHeight()); + // force onscreen + nearby_media_rect.translate(popup_holder->getRect().getWidth() - nearby_media_rect.mRight, 0); + + // show the master volume pull-down + mPanelNearByMedia->setShape(nearby_media_rect); + LLUI::getInstance()->clearPopups(); + LLUI::getInstance()->addPopup(mPanelNearByMedia); + + mPanelPresetsCameraPulldown->setVisible(false); + mPanelPresetsPulldown->setVisible(false); + mPanelVolumePulldown->setVisible(false); + mPanelNearByMedia->setVisible(true); +} + + +static void onClickVolume(void* data) +{ + // toggle the master mute setting + bool mute_audio = LLAppViewer::instance()->getMasterSystemAudioMute(); + LLAppViewer::instance()->setMasterSystemAudioMute(!mute_audio); +} + +//static +void LLStatusBar::onClickBalance(void* ) +{ + // Force a balance request message: + LLStatusBar::sendMoneyBalanceRequest(); + // The refresh of the display (call to setBalance()) will be done by process_money_balance_reply() +} + +//static +void LLStatusBar::onClickMediaToggle(void* data) +{ + LLStatusBar *status_bar = (LLStatusBar*)data; + // "Selected" means it was showing the "play" icon (so media was playing), and now it shows "pause", so turn off media + bool pause = status_bar->mMediaToggle->getValue(); + LLViewerMedia::getInstance()->setAllMediaPaused(pause); +} + +bool can_afford_transaction(S32 cost) +{ + return((cost <= 0)||((gStatusBar) && (gStatusBar->getBalance() >=cost))); +} + +void LLStatusBar::onVolumeChanged(const LLSD& newvalue) +{ + refresh(); +} + +void LLStatusBar::onVoiceChanged(const LLSD& newvalue) +{ + if (newvalue.asBoolean()) + { + // Second instance starts with "VoiceMute_Off" icon, fix it + mBtnVolume->setImageUnselected(LLUI::getUIImage("Audio_Off")); + } + refresh(); +} + +void LLStatusBar::onUpdateFilterTerm() +{ + LLWString searchValue = utf8str_to_wstring( mFilterEdit->getValue() ); + LLWStringUtil::toLower( searchValue ); + + if( !mSearchData || mSearchData->mLastFilter == searchValue ) + return; + + mSearchData->mLastFilter = searchValue; + + mSearchData->mRootMenu->hightlightAndHide( searchValue ); + gMenuBarView->needsArrange(); +} + +void collectChildren( LLMenuGL *aMenu, ll::statusbar::SearchableItemPtr aParentMenu ) +{ + for( U32 i = 0; i < aMenu->getItemCount(); ++i ) + { + LLMenuItemGL *pMenu = aMenu->getItem( i ); + + ll::statusbar::SearchableItemPtr pItem( new ll::statusbar::SearchableItem ); + pItem->mCtrl = pMenu; + pItem->mMenu = pMenu; + pItem->mLabel = utf8str_to_wstring( pMenu->ll::ui::SearchableControl::getSearchText() ); + LLWStringUtil::toLower( pItem->mLabel ); + aParentMenu->mChildren.push_back( pItem ); + + LLMenuItemBranchGL *pBranch = dynamic_cast< LLMenuItemBranchGL* >( pMenu ); + if( pBranch ) + collectChildren( pBranch->getBranch(), pItem ); + } + +} + +void LLStatusBar::collectSearchableItems() +{ + mSearchData.reset( new ll::statusbar::SearchData ); + ll::statusbar::SearchableItemPtr pItem( new ll::statusbar::SearchableItem ); + mSearchData->mRootMenu = pItem; + collectChildren( gMenuBarView, pItem ); +} + +void LLStatusBar::updateMenuSearchVisibility(const LLSD& data) +{ + bool visible = data.asBoolean(); + mSearchPanel->setVisible(visible); + if (!visible) + { + mFilterEdit->setText(LLStringUtil::null); + onUpdateFilterTerm(); + } + else + { + updateMenuSearchPosition(); + } +} + +void LLStatusBar::updateMenuSearchPosition() +{ + const S32 HPAD = 12; + LLRect balanceRect = getChildView("balance_bg")->getRect(); + LLRect searchRect = mSearchPanel->getRect(); + S32 w = searchRect.getWidth(); + searchRect.mLeft = balanceRect.mLeft - w - HPAD; + searchRect.mRight = searchRect.mLeft + w; + mSearchPanel->setShape( searchRect ); +} + +void LLStatusBar::updateBalancePanelPosition() +{ + // Resize the L$ balance background to be wide enough for your balance plus the buy button + const S32 HPAD = 24; + LLRect balance_rect = mBoxBalance->getTextBoundingRect(); + LLRect buy_rect = getChildView("buyL")->getRect(); + LLRect shop_rect = getChildView("goShop")->getRect(); + LLView* balance_bg_view = getChildView("balance_bg"); + LLRect balance_bg_rect = balance_bg_view->getRect(); + balance_bg_rect.mLeft = balance_bg_rect.mRight - (buy_rect.getWidth() + shop_rect.getWidth() + balance_rect.getWidth() + HPAD); + balance_bg_view->setShape(balance_bg_rect); +} + + +// Implements secondlife:///app/balance/request to request a L$ balance +// update via UDP message system. JC +class LLBalanceHandler : public LLCommandHandler +{ +public: + // Requires "trusted" browser/URL source + LLBalanceHandler() : LLCommandHandler("balance", UNTRUSTED_BLOCK) { } + bool handle(const LLSD& tokens, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) + { + if (tokens.size() == 1 + && tokens[0].asString() == "request") + { + LLStatusBar::sendMoneyBalanceRequest(); + return true; + } + return false; + } +}; +// register with command dispatch system +LLBalanceHandler gBalanceHandler; diff --git a/indra/newview/llstatusbar.h b/indra/newview/llstatusbar.h index 71290cde31..4c9d3e0c08 100644 --- a/indra/newview/llstatusbar.h +++ b/indra/newview/llstatusbar.h @@ -1,152 +1,152 @@ -/** - * @file llstatusbar.h - * @brief LLStatusBar class definition - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLSTATUSBAR_H -#define LL_LLSTATUSBAR_H - -#include "llpanel.h" - -// "Constants" loaded from settings.xml at start time -extern S32 STATUS_BAR_HEIGHT; - -class LLButton; -class LLLineEditor; -class LLMessageSystem; -class LLTextBox; -class LLTextEditor; -class LLUICtrl; -class LLUUID; -class LLFrameTimer; -class LLStatGraph; -class LLPanelPresetsCameraPulldown; -class LLPanelPresetsPulldown; -class LLPanelVolumePulldown; -class LLPanelNearByMedia; -class LLIconCtrl; -class LLSearchEditor; - -namespace ll -{ - namespace statusbar - { - struct SearchData; - } -} -class LLStatusBar -: public LLPanel -{ -public: - LLStatusBar(const LLRect& rect ); - /*virtual*/ ~LLStatusBar(); - - /*virtual*/ void draw(); - - /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool postBuild(); - - // MANIPULATORS - void setBalance(S32 balance); - void debitBalance(S32 debit); - void creditBalance(S32 credit); - - // Request the latest currency balance from the server - static void sendMoneyBalanceRequest(); - - void setHealth(S32 percent); - - void setLandCredit(S32 credit); - void setLandCommitted(S32 committed); - - void refresh(); - void setVisibleForMouselook(bool visible); - // some elements should hide in mouselook - - // ACCESSORS - S32 getBalance() const; - S32 getHealth() const; - - bool isUserTiered() const; - S32 getSquareMetersCredit() const; - S32 getSquareMetersCommitted() const; - S32 getSquareMetersLeft() const; - - LLPanelNearByMedia* getNearbyMediaPanel() { return mPanelNearByMedia; } - -private: - - void onClickBuyCurrency(); - void onVolumeChanged(const LLSD& newvalue); - void onVoiceChanged(const LLSD& newvalue); - - void onMouseEnterPresetsCamera(); - void onMouseEnterPresets(); - void onMouseEnterVolume(); - void onMouseEnterNearbyMedia(); - - static void onClickMediaToggle(void* data); - static void onClickBalance(void* data); - - LLSearchEditor *mFilterEdit; - LLPanel *mSearchPanel; - void onUpdateFilterTerm(); - - std::unique_ptr< ll::statusbar::SearchData > mSearchData; - void collectSearchableItems(); - void updateMenuSearchVisibility( const LLSD& data ); - void updateMenuSearchPosition(); // depends onto balance position - void updateBalancePanelPosition(); - -private: - LLTextBox *mTextTime; - - LLStatGraph *mSGBandwidth; - LLStatGraph *mSGPacketLoss; - - LLIconCtrl *mIconPresetsCamera; - LLIconCtrl *mIconPresetsGraphic; - LLButton *mBtnVolume; - LLTextBox *mBoxBalance; - LLButton *mMediaToggle; - LLFrameTimer mClockUpdateTimer; - - S32 mBalance; - S32 mHealth; - S32 mSquareMetersCredit; - S32 mSquareMetersCommitted; - LLFrameTimer* mBalanceTimer; - LLFrameTimer* mHealthTimer; - LLPanelPresetsCameraPulldown* mPanelPresetsCameraPulldown; - LLPanelPresetsPulldown* mPanelPresetsPulldown; - LLPanelVolumePulldown* mPanelVolumePulldown; - LLPanelNearByMedia* mPanelNearByMedia; -}; - -// *HACK: Status bar owns your cached money balance. JC -bool can_afford_transaction(S32 cost); - -extern LLStatusBar *gStatusBar; - -#endif +/** + * @file llstatusbar.h + * @brief LLStatusBar class definition + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSTATUSBAR_H +#define LL_LLSTATUSBAR_H + +#include "llpanel.h" + +// "Constants" loaded from settings.xml at start time +extern S32 STATUS_BAR_HEIGHT; + +class LLButton; +class LLLineEditor; +class LLMessageSystem; +class LLTextBox; +class LLTextEditor; +class LLUICtrl; +class LLUUID; +class LLFrameTimer; +class LLStatGraph; +class LLPanelPresetsCameraPulldown; +class LLPanelPresetsPulldown; +class LLPanelVolumePulldown; +class LLPanelNearByMedia; +class LLIconCtrl; +class LLSearchEditor; + +namespace ll +{ + namespace statusbar + { + struct SearchData; + } +} +class LLStatusBar +: public LLPanel +{ +public: + LLStatusBar(const LLRect& rect ); + /*virtual*/ ~LLStatusBar(); + + /*virtual*/ void draw(); + + /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool postBuild(); + + // MANIPULATORS + void setBalance(S32 balance); + void debitBalance(S32 debit); + void creditBalance(S32 credit); + + // Request the latest currency balance from the server + static void sendMoneyBalanceRequest(); + + void setHealth(S32 percent); + + void setLandCredit(S32 credit); + void setLandCommitted(S32 committed); + + void refresh(); + void setVisibleForMouselook(bool visible); + // some elements should hide in mouselook + + // ACCESSORS + S32 getBalance() const; + S32 getHealth() const; + + bool isUserTiered() const; + S32 getSquareMetersCredit() const; + S32 getSquareMetersCommitted() const; + S32 getSquareMetersLeft() const; + + LLPanelNearByMedia* getNearbyMediaPanel() { return mPanelNearByMedia; } + +private: + + void onClickBuyCurrency(); + void onVolumeChanged(const LLSD& newvalue); + void onVoiceChanged(const LLSD& newvalue); + + void onMouseEnterPresetsCamera(); + void onMouseEnterPresets(); + void onMouseEnterVolume(); + void onMouseEnterNearbyMedia(); + + static void onClickMediaToggle(void* data); + static void onClickBalance(void* data); + + LLSearchEditor *mFilterEdit; + LLPanel *mSearchPanel; + void onUpdateFilterTerm(); + + std::unique_ptr< ll::statusbar::SearchData > mSearchData; + void collectSearchableItems(); + void updateMenuSearchVisibility( const LLSD& data ); + void updateMenuSearchPosition(); // depends onto balance position + void updateBalancePanelPosition(); + +private: + LLTextBox *mTextTime; + + LLStatGraph *mSGBandwidth; + LLStatGraph *mSGPacketLoss; + + LLIconCtrl *mIconPresetsCamera; + LLIconCtrl *mIconPresetsGraphic; + LLButton *mBtnVolume; + LLTextBox *mBoxBalance; + LLButton *mMediaToggle; + LLFrameTimer mClockUpdateTimer; + + S32 mBalance; + S32 mHealth; + S32 mSquareMetersCredit; + S32 mSquareMetersCommitted; + LLFrameTimer* mBalanceTimer; + LLFrameTimer* mHealthTimer; + LLPanelPresetsCameraPulldown* mPanelPresetsCameraPulldown; + LLPanelPresetsPulldown* mPanelPresetsPulldown; + LLPanelVolumePulldown* mPanelVolumePulldown; + LLPanelNearByMedia* mPanelNearByMedia; +}; + +// *HACK: Status bar owns your cached money balance. JC +bool can_afford_transaction(S32 cost); + +extern LLStatusBar *gStatusBar; + +#endif diff --git a/indra/newview/llsurface.cpp b/indra/newview/llsurface.cpp index ce880c09ee..cafeb8db1e 100644 --- a/indra/newview/llsurface.cpp +++ b/indra/newview/llsurface.cpp @@ -1,1314 +1,1314 @@ -/** - * @file llsurface.cpp - * @brief Implementation of LLSurface class - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llsurface.h" - -#include "llrender.h" - -#include "llviewertexturelist.h" -#include "llpatchvertexarray.h" -#include "patch_dct.h" -#include "patch_code.h" -#include "llbitpack.h" -#include "llviewerobjectlist.h" -#include "llregionhandle.h" -#include "llagent.h" -#include "llagentcamera.h" -#include "llappviewer.h" -#include "llworld.h" -#include "llviewercontrol.h" -#include "llviewertexture.h" -#include "llsurfacepatch.h" -#include "llvosurfacepatch.h" -#include "llvowater.h" -#include "pipeline.h" -#include "llviewerregion.h" -#include "llvlcomposition.h" -#include "noise.h" -#include "llviewercamera.h" -#include "llglheaders.h" -#include "lldrawpoolterrain.h" -#include "lldrawable.h" - -extern LLPipeline gPipeline; -extern bool gShiftFrame; - -LLColor4U MAX_WATER_COLOR(0, 48, 96, 240); - - -S32 LLSurface::sTextureSize = 256; - -// ---------------- LLSurface:: Public Members --------------- - -LLSurface::LLSurface(U32 type, LLViewerRegion *regionp) : - mGridsPerEdge(0), - mOOGridsPerEdge(0.f), - mPatchesPerEdge(0), - mNumberOfPatches(0), - mType(type), - mDetailTextureScale(0.f), - mOriginGlobal(0.0, 0.0, 0.0), - mSTexturep(NULL), - mWaterTexturep(NULL), - mGridsPerPatchEdge(0), - mMetersPerGrid(1.0f), - mMetersPerEdge(1.0f), - mRegionp(regionp) -{ - // Surface data - mSurfaceZ = NULL; - mNorm = NULL; - - // Patch data - mPatchList = NULL; - - // One of each for each camera - mVisiblePatchCount = 0; - - mHasZData = false; - // "uninitialized" min/max z - mMinZ = 10000.f; - mMaxZ = -10000.f; - - mWaterObjp = NULL; - - // In here temporarily. - mSurfacePatchUpdateCount = 0; - - for (S32 i = 0; i < 8; i++) - { - mNeighbors[i] = NULL; - } -} - - -LLSurface::~LLSurface() -{ - delete [] mSurfaceZ; - mSurfaceZ = NULL; - - delete [] mNorm; - - mGridsPerEdge = 0; - mGridsPerPatchEdge = 0; - mPatchesPerEdge = 0; - mNumberOfPatches = 0; - destroyPatchData(); - - LLDrawPoolTerrain *poolp = (LLDrawPoolTerrain*) gPipeline.findPool(LLDrawPool::POOL_TERRAIN, mSTexturep); - if (!poolp) - { - LL_WARNS() << "No pool for terrain on destruction!" << LL_ENDL; - } - else if (poolp->mReferences.empty()) - { - gPipeline.removePool(poolp); - // Don't enable this until we blitz the draw pool for it as well. -- djs - if (mSTexturep) - { - mSTexturep = NULL; - } - if (mWaterTexturep) - { - mWaterTexturep = NULL; - } - } - else - { - LL_ERRS() << "Terrain pool not empty!" << LL_ENDL; - } -} - -void LLSurface::initClasses() -{ -} - -void LLSurface::setRegion(LLViewerRegion *regionp) -{ - mRegionp = regionp; - mWaterObjp = NULL; // depends on regionp, needs recreating -} - -// Assumes that arguments are powers of 2, and that -// grids_per_edge / grids_per_patch_edge = power of 2 -void LLSurface::create(const S32 grids_per_edge, - const S32 grids_per_patch_edge, - const LLVector3d &origin_global, - const F32 width) -{ - // Initialize various constants for the surface - mGridsPerEdge = grids_per_edge + 1; // Add 1 for the east and north buffer - mOOGridsPerEdge = 1.f / mGridsPerEdge; - mGridsPerPatchEdge = grids_per_patch_edge; - mPatchesPerEdge = (mGridsPerEdge - 1) / mGridsPerPatchEdge; - mNumberOfPatches = mPatchesPerEdge * mPatchesPerEdge; - mMetersPerGrid = width / ((F32)(mGridsPerEdge - 1)); - mMetersPerEdge = mMetersPerGrid * (mGridsPerEdge - 1); - - mOriginGlobal.setVec(origin_global); - - mPVArray.create(mGridsPerEdge, mGridsPerPatchEdge, LLWorld::getInstance()->getRegionScale()); - - S32 number_of_grids = mGridsPerEdge * mGridsPerEdge; - - ///////////////////////////////////// - // - // Initialize data arrays for surface - /// - mSurfaceZ = new F32[number_of_grids]; - mNorm = new LLVector3[number_of_grids]; - - // Reset the surface to be a flat square grid - for(S32 i=0; i < number_of_grids; i++) - { - // Surface is flat and zero - // Normals all point up - mSurfaceZ[i] = 0.0f; - mNorm[i].setVec(0.f, 0.f, 1.f); - } - - - mVisiblePatchCount = 0; - - - /////////////////////// - // - // Initialize textures - // - - initTextures(); - - // Has to be done after texture initialization - createPatchData(); -} - -LLViewerTexture* LLSurface::getSTexture() -{ - if (mSTexturep.notNull() && !mSTexturep->hasGLTexture()) - { - createSTexture(); - } - return mSTexturep; -} - -LLViewerTexture* LLSurface::getWaterTexture() -{ - if (mWaterTexturep.notNull() && !mWaterTexturep->hasGLTexture()) - { - createWaterTexture(); - } - return mWaterTexturep; -} - -void LLSurface::createSTexture() -{ - if (!mSTexturep) - { - // Fill with dummy gray data. - // GL NOT ACTIVE HERE - LLPointer raw = new LLImageRaw(sTextureSize, sTextureSize, 3); - U8 *default_texture = raw->getData(); - for (S32 i = 0; i < sTextureSize; i++) - { - for (S32 j = 0; j < sTextureSize; j++) - { - *(default_texture + (i*sTextureSize + j)*3) = 128; - *(default_texture + (i*sTextureSize + j)*3 + 1) = 128; - *(default_texture + (i*sTextureSize + j)*3 + 2) = 128; - } - } - - mSTexturep = LLViewerTextureManager::getLocalTexture(raw.get(), false); - mSTexturep->dontDiscard(); - gGL.getTexUnit(0)->bind(mSTexturep); - mSTexturep->setAddressMode(LLTexUnit::TAM_CLAMP); - } -} - -void LLSurface::createWaterTexture() -{ - if (!mWaterTexturep) - { - // Create the water texture - LLPointer raw = new LLImageRaw(sTextureSize/2, sTextureSize/2, 4); - U8 *default_texture = raw->getData(); - for (S32 i = 0; i < sTextureSize/2; i++) - { - for (S32 j = 0; j < sTextureSize/2; j++) - { - *(default_texture + (i*sTextureSize/2 + j)*4) = MAX_WATER_COLOR.mV[0]; - *(default_texture + (i*sTextureSize/2 + j)*4 + 1) = MAX_WATER_COLOR.mV[1]; - *(default_texture + (i*sTextureSize/2 + j)*4 + 2) = MAX_WATER_COLOR.mV[2]; - *(default_texture + (i*sTextureSize/2 + j)*4 + 3) = MAX_WATER_COLOR.mV[3]; - } - } - - mWaterTexturep = LLViewerTextureManager::getLocalTexture(raw.get(), false); - mWaterTexturep->dontDiscard(); - gGL.getTexUnit(0)->bind(mWaterTexturep); - mWaterTexturep->setAddressMode(LLTexUnit::TAM_CLAMP); - } -} - -void LLSurface::initTextures() -{ - /////////////////////// - // - // Main surface texture - // - createSTexture(); - - /////////////////////// - // - // Water texture - // - if (gSavedSettings.getBOOL("RenderWater") ) - { - createWaterTexture(); - mWaterObjp = (LLVOWater *)gObjectList.createObjectViewer(LLViewerObject::LL_VO_WATER, mRegionp); - gPipeline.createObject(mWaterObjp); - LLVector3d water_pos_global = from_region_handle(mRegionp->getHandle()); - water_pos_global += LLVector3d(128.0, 128.0, DEFAULT_WATER_HEIGHT); // region doesn't have a valid water height yet - mWaterObjp->setPositionGlobal(water_pos_global); - } -} - - -void LLSurface::setOriginGlobal(const LLVector3d &origin_global) -{ - LLVector3d new_origin_global; - mOriginGlobal = origin_global; - LLSurfacePatch *patchp; - S32 i, j; - // Need to update the southwest corners of the patches - for (j=0; jgetOriginGlobal(); - - new_origin_global.mdV[0] = mOriginGlobal.mdV[0] + i * mMetersPerGrid * mGridsPerPatchEdge; - new_origin_global.mdV[1] = mOriginGlobal.mdV[1] + j * mMetersPerGrid * mGridsPerPatchEdge; - patchp->setOriginGlobal(new_origin_global); - } - } - - // Hack! - if (mWaterObjp.notNull() && mWaterObjp->mDrawable.notNull()) - { - const F64 x = origin_global.mdV[VX] + 128.0; - const F64 y = origin_global.mdV[VY] + 128.0; - const F64 z = mWaterObjp->getPositionGlobal().mdV[VZ]; - - LLVector3d water_origin_global(x, y, z); - - mWaterObjp->setPositionGlobal(water_origin_global); - } -} - -void LLSurface::getNeighboringRegions( std::vector& uniqueRegions ) -{ - S32 i; - for (i = 0; i < 8; i++) - { - if ( mNeighbors[i] != NULL ) - { - uniqueRegions.push_back( mNeighbors[i]->getRegion() ); - } - } -} - - -void LLSurface::getNeighboringRegionsStatus( std::vector& regions ) -{ - S32 i; - for (i = 0; i < 8; i++) - { - if ( mNeighbors[i] != NULL ) - { - regions.push_back( i ); - } - } -} - -void LLSurface::connectNeighbor(LLSurface *neighborp, U32 direction) -{ - S32 i; - LLSurfacePatch *patchp, *neighbor_patchp; - - mNeighbors[direction] = neighborp; - neighborp->mNeighbors[gDirOpposite[direction]] = this; - - // Connect patches - if (NORTHEAST == direction) - { - patchp = getPatch(mPatchesPerEdge - 1, mPatchesPerEdge - 1); - neighbor_patchp = neighborp->getPatch(0, 0); - - patchp->connectNeighbor(neighbor_patchp, direction); - neighbor_patchp->connectNeighbor(patchp, gDirOpposite[direction]); - - patchp->updateNorthEdge(); // Only update one of north or east. - patchp->dirtyZ(); - } - else if (NORTHWEST == direction) - { - patchp = getPatch(0, mPatchesPerEdge - 1); - neighbor_patchp = neighborp->getPatch(mPatchesPerEdge - 1, 0); - - patchp->connectNeighbor(neighbor_patchp, direction); - neighbor_patchp->connectNeighbor(patchp, gDirOpposite[direction]); - } - else if (SOUTHWEST == direction) - { - patchp = getPatch(0, 0); - neighbor_patchp = neighborp->getPatch(mPatchesPerEdge - 1, mPatchesPerEdge - 1); - - patchp->connectNeighbor(neighbor_patchp, direction); - neighbor_patchp->connectNeighbor(patchp, gDirOpposite[direction]); - - neighbor_patchp->updateNorthEdge(); // Only update one of north or east. - neighbor_patchp->dirtyZ(); - } - else if (SOUTHEAST == direction) - { - patchp = getPatch(mPatchesPerEdge - 1, 0); - neighbor_patchp = neighborp->getPatch(0, mPatchesPerEdge - 1); - - patchp->connectNeighbor(neighbor_patchp, direction); - neighbor_patchp->connectNeighbor(patchp, gDirOpposite[direction]); - } - else if (EAST == direction) - { - // Do east/west connections, first - for (i = 0; i < (S32)mPatchesPerEdge; i++) - { - patchp = getPatch(mPatchesPerEdge - 1, i); - neighbor_patchp = neighborp->getPatch(0, i); - - patchp->connectNeighbor(neighbor_patchp, direction); - neighbor_patchp->connectNeighbor(patchp, gDirOpposite[direction]); - - patchp->updateEastEdge(); - patchp->dirtyZ(); - } - - // Now do northeast/southwest connections - for (i = 0; i < (S32)mPatchesPerEdge - 1; i++) - { - patchp = getPatch(mPatchesPerEdge - 1, i); - neighbor_patchp = neighborp->getPatch(0, i+1); - - patchp->connectNeighbor(neighbor_patchp, NORTHEAST); - neighbor_patchp->connectNeighbor(patchp, SOUTHWEST); - } - // Now do southeast/northwest connections - for (i = 1; i < (S32)mPatchesPerEdge; i++) - { - patchp = getPatch(mPatchesPerEdge - 1, i); - neighbor_patchp = neighborp->getPatch(0, i-1); - - patchp->connectNeighbor(neighbor_patchp, SOUTHEAST); - neighbor_patchp->connectNeighbor(patchp, NORTHWEST); - } - } - else if (NORTH == direction) - { - // Do north/south connections, first - for (i = 0; i < (S32)mPatchesPerEdge; i++) - { - patchp = getPatch(i, mPatchesPerEdge - 1); - neighbor_patchp = neighborp->getPatch(i, 0); - - patchp->connectNeighbor(neighbor_patchp, direction); - neighbor_patchp->connectNeighbor(patchp, gDirOpposite[direction]); - - patchp->updateNorthEdge(); - patchp->dirtyZ(); - } - - // Do northeast/southwest connections - for (i = 0; i < (S32)mPatchesPerEdge - 1; i++) - { - patchp = getPatch(i, mPatchesPerEdge - 1); - neighbor_patchp = neighborp->getPatch(i+1, 0); - - patchp->connectNeighbor(neighbor_patchp, NORTHEAST); - neighbor_patchp->connectNeighbor(patchp, SOUTHWEST); - } - // Do southeast/northwest connections - for (i = 1; i < (S32)mPatchesPerEdge; i++) - { - patchp = getPatch(i, mPatchesPerEdge - 1); - neighbor_patchp = neighborp->getPatch(i-1, 0); - - patchp->connectNeighbor(neighbor_patchp, NORTHWEST); - neighbor_patchp->connectNeighbor(patchp, SOUTHEAST); - } - } - else if (WEST == direction) - { - // Do east/west connections, first - for (i = 0; i < mPatchesPerEdge; i++) - { - patchp = getPatch(0, i); - neighbor_patchp = neighborp->getPatch(mPatchesPerEdge - 1, i); - - patchp->connectNeighbor(neighbor_patchp, direction); - neighbor_patchp->connectNeighbor(patchp, gDirOpposite[direction]); - - neighbor_patchp->updateEastEdge(); - neighbor_patchp->dirtyZ(); - } - - // Now do northeast/southwest connections - for (i = 1; i < mPatchesPerEdge; i++) - { - patchp = getPatch(0, i); - neighbor_patchp = neighborp->getPatch(mPatchesPerEdge - 1, i - 1); - - patchp->connectNeighbor(neighbor_patchp, SOUTHWEST); - neighbor_patchp->connectNeighbor(patchp, NORTHEAST); - } - - // Now do northwest/southeast connections - for (i = 0; i < mPatchesPerEdge - 1; i++) - { - patchp = getPatch(0, i); - neighbor_patchp = neighborp->getPatch(mPatchesPerEdge - 1, i + 1); - - patchp->connectNeighbor(neighbor_patchp, NORTHWEST); - neighbor_patchp->connectNeighbor(patchp, SOUTHEAST); - } - } - else if (SOUTH == direction) - { - // Do north/south connections, first - for (i = 0; i < mPatchesPerEdge; i++) - { - patchp = getPatch(i, 0); - neighbor_patchp = neighborp->getPatch(i, mPatchesPerEdge - 1); - - patchp->connectNeighbor(neighbor_patchp, direction); - neighbor_patchp->connectNeighbor(patchp, gDirOpposite[direction]); - - neighbor_patchp->updateNorthEdge(); - neighbor_patchp->dirtyZ(); - } - - // Now do northeast/southwest connections - for (i = 1; i < mPatchesPerEdge; i++) - { - patchp = getPatch(i, 0); - neighbor_patchp = neighborp->getPatch(i - 1, mPatchesPerEdge - 1); - - patchp->connectNeighbor(neighbor_patchp, SOUTHWEST); - neighbor_patchp->connectNeighbor(patchp, NORTHEAST); - } - // Now do northeast/southwest connections - for (i = 0; i < mPatchesPerEdge - 1; i++) - { - patchp = getPatch(i, 0); - neighbor_patchp = neighborp->getPatch(i + 1, mPatchesPerEdge - 1); - - patchp->connectNeighbor(neighbor_patchp, SOUTHEAST); - neighbor_patchp->connectNeighbor(patchp, NORTHWEST); - } - } -} - -void LLSurface::disconnectNeighbor(LLSurface *surfacep) -{ - S32 i; - for (i = 0; i < 8; i++) - { - if (surfacep == mNeighbors[i]) - { - mNeighbors[i] = NULL; - } - } - - // Iterate through surface patches, removing any connectivity to removed surface. - for (i = 0; i < mNumberOfPatches; i++) - { - (mPatchList + i)->disconnectNeighbor(surfacep); - } -} - - -void LLSurface::disconnectAllNeighbors() -{ - S32 i; - for (i = 0; i < 8; i++) - { - if (mNeighbors[i]) - { - mNeighbors[i]->disconnectNeighbor(this); - mNeighbors[i] = NULL; - } - } -} - - - -const LLVector3d &LLSurface::getOriginGlobal() const -{ - return mOriginGlobal; -} - -LLVector3 LLSurface::getOriginAgent() const -{ - return gAgent.getPosAgentFromGlobal(mOriginGlobal); -} - -F32 LLSurface::getMetersPerGrid() const -{ - return mMetersPerGrid; -} - -S32 LLSurface::getGridsPerEdge() const -{ - return mGridsPerEdge; -} - -S32 LLSurface::getPatchesPerEdge() const -{ - return mPatchesPerEdge; -} - -S32 LLSurface::getGridsPerPatchEdge() const -{ - return mGridsPerPatchEdge; -} - -void LLSurface::moveZ(const S32 x, const S32 y, const F32 delta) -{ - llassert(x >= 0); - llassert(y >= 0); - llassert(x < mGridsPerEdge); - llassert(y < mGridsPerEdge); - mSurfaceZ[x + y*mGridsPerEdge] += delta; -} - - -void LLSurface::updatePatchVisibilities(LLAgent &agent) -{ - if (gShiftFrame) - { - return; - } - - LLVector3 pos_region = mRegionp->getPosRegionFromGlobal(gAgentCamera.getCameraPositionGlobal()); - - LLSurfacePatch *patchp; - - mVisiblePatchCount = 0; - for (S32 i=0; iupdateVisibility(); - if (patchp->getVisible()) - { - mVisiblePatchCount++; - patchp->updateCameraDistanceRegion(pos_region); - } - } -} - -bool LLSurface::idleUpdate(F32 max_update_time) -{ - if (!gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_TERRAIN)) - { - return false; - } - - // Perform idle time update of non-critical stuff. - // In this case, texture and normal updates. - LLTimer update_timer; - bool did_update = false; - - // If the Z height data has changed, we need to rebuild our - // property line vertex arrays. - if (mDirtyPatchList.size() > 0) - { - getRegion()->dirtyHeights(); - } - - // Always call updateNormals() / updateVerticalStats() - // every frame to avoid artifacts - for(std::set::iterator iter = mDirtyPatchList.begin(); - iter != mDirtyPatchList.end(); ) - { - std::set::iterator curiter = iter++; - LLSurfacePatch *patchp = *curiter; - patchp->updateNormals(); - patchp->updateVerticalStats(); - if (max_update_time == 0.f || update_timer.getElapsedTimeF32() < max_update_time) - { - if (patchp->updateTexture()) - { - did_update = true; - patchp->clearDirty(); - mDirtyPatchList.erase(curiter); - } - } - } - - if (did_update) - { - // some patches changed, update region reflection probes - mRegionp->updateReflectionProbes(); - } - - return did_update; -} - -void LLSurface::decompressDCTPatch(LLBitPack &bitpack, LLGroupHeader *gopp, bool b_large_patch) -{ - - LLPatchHeader ph; - S32 j, i; - S32 patch[LARGE_PATCH_SIZE*LARGE_PATCH_SIZE]; - LLSurfacePatch *patchp; - - init_patch_decompressor(gopp->patch_size); - gopp->stride = mGridsPerEdge; - set_group_of_patch_header(gopp); - - while (1) - { - decode_patch_header(bitpack, &ph); - if (ph.quant_wbits == END_OF_PATCHES) - { - break; - } - - i = ph.patchids >> 5; - j = ph.patchids & 0x1F; - - if ((i >= mPatchesPerEdge) || (j >= mPatchesPerEdge)) - { - LL_WARNS() << "Received invalid terrain packet - patch header patch ID incorrect!" - << " patches per edge " << mPatchesPerEdge - << " i " << i - << " j " << j - << " dc_offset " << ph.dc_offset - << " range " << (S32)ph.range - << " quant_wbits " << (S32)ph.quant_wbits - << " patchids " << (S32)ph.patchids - << LL_ENDL; - return; - } - - patchp = &mPatchList[j*mPatchesPerEdge + i]; - - - decode_patch(bitpack, patch); - decompress_patch(patchp->getDataZ(), patch, &ph); - - // Update edges for neighbors. Need to guarantee that this gets done before we generate vertical stats. - patchp->updateNorthEdge(); - patchp->updateEastEdge(); - if (patchp->getNeighborPatch(WEST)) - { - patchp->getNeighborPatch(WEST)->updateEastEdge(); - } - if (patchp->getNeighborPatch(SOUTHWEST)) - { - patchp->getNeighborPatch(SOUTHWEST)->updateEastEdge(); - patchp->getNeighborPatch(SOUTHWEST)->updateNorthEdge(); - } - if (patchp->getNeighborPatch(SOUTH)) - { - patchp->getNeighborPatch(SOUTH)->updateNorthEdge(); - } - - // Dirty patch statistics, and flag that the patch has data. - patchp->dirtyZ(); - patchp->setHasReceivedData(); - } -} - - -// Retrurns true if "position" is within the bounds of surface. -// "position" is region-local -bool LLSurface::containsPosition(const LLVector3 &position) -{ - if (position.mV[VX] < 0.0f || position.mV[VX] > mMetersPerEdge || - position.mV[VY] < 0.0f || position.mV[VY] > mMetersPerEdge) - { - return false; - } - return true; -} - - -F32 LLSurface::resolveHeightRegion(const F32 x, const F32 y) const -{ - F32 height = 0.0f; - F32 oometerspergrid = 1.f/mMetersPerGrid; - - // Check to see if v is actually above surface - // We use (mGridsPerEdge-1) below rather than (mGridsPerEdge) - // becuase of the east and north buffers - - if (x >= 0.f && - x <= mMetersPerEdge && - y >= 0.f && - y <= mMetersPerEdge) - { - const S32 left = llfloor(x * oometerspergrid); - const S32 bottom = llfloor(y * oometerspergrid); - - // Don't walk off the edge of the array! - const S32 right = ( left+1 < (S32)mGridsPerEdge-1 ? left+1 : left ); - const S32 top = ( bottom+1 < (S32)mGridsPerEdge-1 ? bottom+1 : bottom ); - - // Figure out if v is in first or second triangle of the square - // and calculate the slopes accordingly - // | | - // -(i,j+1)---(i+1,j+1)-- - // | 1 / | ^ - // | / 2 | | - // | / | j - // --(i,j)----(i+1,j)-- - // | | - // - // i -> - // where N = mGridsPerEdge - - const F32 left_bottom = getZ( left, bottom ); - const F32 right_bottom = getZ( right, bottom ); - const F32 left_top = getZ( left, top ); - const F32 right_top = getZ( right, top ); - - // dx and dy are incremental steps from (mSurface + k) - F32 dx = x - left * mMetersPerGrid; - F32 dy = y - bottom * mMetersPerGrid; - - if (dy > dx) - { - // triangle 1 - dy *= left_top - left_bottom; - dx *= right_top - left_top; - } - else - { - // triangle 2 - dx *= right_bottom - left_bottom; - dy *= right_top - right_bottom; - } - height = left_bottom + (dx + dy) * oometerspergrid; - } - return height; -} - - -F32 LLSurface::resolveHeightGlobal(const LLVector3d& v) const -{ - if (!mRegionp) - { - return 0.f; - } - - LLVector3 pos_region = mRegionp->getPosRegionFromGlobal(v); - - return resolveHeightRegion(pos_region); -} - - -LLVector3 LLSurface::resolveNormalGlobal(const LLVector3d& pos_global) const -{ - if (!mSurfaceZ) - { - // Hmm. Uninitialized surface! - return LLVector3::z_axis; - } - // - // Returns the vector normal to a surface at location specified by vector v - // - F32 oometerspergrid = 1.f/mMetersPerGrid; - LLVector3 normal; - F32 dzx, dzy; - - if (pos_global.mdV[VX] >= mOriginGlobal.mdV[VX] && - pos_global.mdV[VX] < mOriginGlobal.mdV[VX] + mMetersPerEdge && - pos_global.mdV[VY] >= mOriginGlobal.mdV[VY] && - pos_global.mdV[VY] < mOriginGlobal.mdV[VY] + mMetersPerEdge) - { - U32 i, j, k; - F32 dx, dy; - i = (U32) ((pos_global.mdV[VX] - mOriginGlobal.mdV[VX]) * oometerspergrid); - j = (U32) ((pos_global.mdV[VY] - mOriginGlobal.mdV[VY]) * oometerspergrid ); - k = i + j*mGridsPerEdge; - - // Figure out if v is in first or second triangle of the square - // and calculate the slopes accordingly - // | | - // -(k+N)---(k+1+N)-- - // | 1 / | ^ - // | / 2 | | - // | / | j - // --(k)----(k+1)-- - // | | - // - // i -> - // where N = mGridsPerEdge - - // dx and dy are incremental steps from (mSurface + k) - dx = (F32)(pos_global.mdV[VX] - i*mMetersPerGrid - mOriginGlobal.mdV[VX]); - dy = (F32)(pos_global.mdV[VY] - j*mMetersPerGrid - mOriginGlobal.mdV[VY]); - if (dy > dx) - { // triangle 1 - dzx = *(mSurfaceZ + k + 1 + mGridsPerEdge) - *(mSurfaceZ + k + mGridsPerEdge); - dzy = *(mSurfaceZ + k) - *(mSurfaceZ + k + mGridsPerEdge); - normal.setVec(-dzx,dzy,1); - } - else - { // triangle 2 - dzx = *(mSurfaceZ + k) - *(mSurfaceZ + k + 1); - dzy = *(mSurfaceZ + k + 1 + mGridsPerEdge) - *(mSurfaceZ + k + 1); - normal.setVec(dzx,-dzy,1); - } - } - normal.normVec(); - return normal; - - -} - -LLSurfacePatch *LLSurface::resolvePatchRegion(const F32 x, const F32 y) const -{ -// x and y should be region-local coordinates. -// If x and y are outside of the surface, then the returned -// index will be for the nearest boundary patch. -// -// 12 | 13| 14| 15 -// | | | -// +---+---+---+---+ -// | 12| 13| 14| 15| -// ----+---+---+---+---+----- -// 8 | 8 | 9 | 10| 11| 11 -// ----+---+---+---+---+----- -// 4 | 4 | 5 | 6 | 7 | 7 -// ----+---+---+---+---+----- -// | 0 | 1 | 2 | 3 | -// +---+---+---+---+ -// | | | -// 0 | 1 | 2 | 3 -// - -// When x and y are not region-local do the following first - - S32 i, j; - if (x < 0.0f) - { - i = 0; - } - else if (x >= mMetersPerEdge) - { - i = mPatchesPerEdge - 1; - } - else - { - i = (U32) (x / (mMetersPerGrid * mGridsPerPatchEdge)); - } - - if (y < 0.0f) - { - j = 0; - } - else if (y >= mMetersPerEdge) - { - j = mPatchesPerEdge - 1; - } - else - { - j = (U32) (y / (mMetersPerGrid * mGridsPerPatchEdge)); - } - - // *NOTE: Super paranoia code follows. - S32 index = i + j * mPatchesPerEdge; - if((index < 0) || (index >= mNumberOfPatches)) - { - if(0 == mNumberOfPatches) - { - LL_WARNS() << "No patches for current region!" << LL_ENDL; - return NULL; - } - S32 old_index = index; - index = llclamp(old_index, 0, (mNumberOfPatches - 1)); - LL_WARNS() << "Clamping out of range patch index " << old_index - << " to " << index << LL_ENDL; - } - return &(mPatchList[index]); -} - - -LLSurfacePatch *LLSurface::resolvePatchRegion(const LLVector3 &pos_region) const -{ - return resolvePatchRegion(pos_region.mV[VX], pos_region.mV[VY]); -} - - -LLSurfacePatch *LLSurface::resolvePatchGlobal(const LLVector3d &pos_global) const -{ - llassert(mRegionp); - LLVector3 pos_region = mRegionp->getPosRegionFromGlobal(pos_global); - return resolvePatchRegion(pos_region); -} - - -std::ostream& operator<<(std::ostream &s, const LLSurface &S) -{ - s << "{ \n"; - s << " mGridsPerEdge = " << S.mGridsPerEdge - 1 << " + 1\n"; - s << " mGridsPerPatchEdge = " << S.mGridsPerPatchEdge << "\n"; - s << " mPatchesPerEdge = " << S.mPatchesPerEdge << "\n"; - s << " mOriginGlobal = " << S.mOriginGlobal << "\n"; - s << " mMetersPerGrid = " << S.mMetersPerGrid << "\n"; - s << " mVisiblePatchCount = " << S.mVisiblePatchCount << "\n"; - s << "}"; - return s; -} - - -// ---------------- LLSurface:: Protected ---------------- - -void LLSurface::createPatchData() -{ - // Assumes mGridsPerEdge, mGridsPerPatchEdge, and mPatchesPerEdge have been properly set - // TODO -- check for create() called when surface is not empty - S32 i, j; - LLSurfacePatch *patchp; - - // Allocate memory - mPatchList = new LLSurfacePatch[mNumberOfPatches]; - - // One of each for each camera - mVisiblePatchCount = mNumberOfPatches; - - for (j=0; jsetSurface(this); - } - } - - for (j=0; jmHasReceivedData = false; - patchp->mSTexUpdate = true; - - S32 data_offset = i * mGridsPerPatchEdge + j * mGridsPerPatchEdge * mGridsPerEdge; - - patchp->setDataZ(mSurfaceZ + data_offset); - patchp->setDataNorm(mNorm + data_offset); - - - // We make each patch point to its neighbors so we can do resolution checking - // when butting up different resolutions. Patches that don't have neighbors - // somewhere will point to NULL on that side. - if (i < mPatchesPerEdge-1) - { - patchp->setNeighborPatch(EAST,getPatch(i+1, j)); - } - else - { - patchp->setNeighborPatch(EAST, NULL); - } - - if (j < mPatchesPerEdge-1) - { - patchp->setNeighborPatch(NORTH, getPatch(i, j+1)); - } - else - { - patchp->setNeighborPatch(NORTH, NULL); - } - - if (i > 0) - { - patchp->setNeighborPatch(WEST, getPatch(i - 1, j)); - } - else - { - patchp->setNeighborPatch(WEST, NULL); - } - - if (j > 0) - { - patchp->setNeighborPatch(SOUTH, getPatch(i, j-1)); - } - else - { - patchp->setNeighborPatch(SOUTH, NULL); - } - - if (i < (mPatchesPerEdge-1) && j < (mPatchesPerEdge-1)) - { - patchp->setNeighborPatch(NORTHEAST, getPatch(i + 1, j + 1)); - } - else - { - patchp->setNeighborPatch(NORTHEAST, NULL); - } - - if (i > 0 && j < (mPatchesPerEdge-1)) - { - patchp->setNeighborPatch(NORTHWEST, getPatch(i - 1, j + 1)); - } - else - { - patchp->setNeighborPatch(NORTHWEST, NULL); - } - - if (i > 0 && j > 0) - { - patchp->setNeighborPatch(SOUTHWEST, getPatch(i - 1, j - 1)); - } - else - { - patchp->setNeighborPatch(SOUTHWEST, NULL); - } - - if (i < (mPatchesPerEdge-1) && j > 0) - { - patchp->setNeighborPatch(SOUTHEAST, getPatch(i + 1, j - 1)); - } - else - { - patchp->setNeighborPatch(SOUTHEAST, NULL); - } - - LLVector3d origin_global; - origin_global.mdV[0] = mOriginGlobal.mdV[0] + i * mMetersPerGrid * mGridsPerPatchEdge; - origin_global.mdV[1] = mOriginGlobal.mdV[0] + j * mMetersPerGrid * mGridsPerPatchEdge; - origin_global.mdV[2] = 0.f; - patchp->setOriginGlobal(origin_global); - } - } -} - - -void LLSurface::destroyPatchData() -{ - // Delete all of the cached patch data for these patches. - - delete [] mPatchList; - mPatchList = NULL; - mVisiblePatchCount = 0; -} - - -void LLSurface::setTextureSize(const S32 texture_size) -{ - sTextureSize = texture_size; -} - - -U32 LLSurface::getRenderLevel(const U32 render_stride) const -{ - return mPVArray.mRenderLevelp[render_stride]; -} - - -U32 LLSurface::getRenderStride(const U32 render_level) const -{ - return mPVArray.mRenderStridep[render_level]; -} - - -LLSurfacePatch *LLSurface::getPatch(const S32 x, const S32 y) const -{ - if ((x < 0) || (x >= mPatchesPerEdge)) - { - LL_ERRS() << "Asking for patch out of bounds" << LL_ENDL; - return NULL; - } - if ((y < 0) || (y >= mPatchesPerEdge)) - { - LL_ERRS() << "Asking for patch out of bounds" << LL_ENDL; - return NULL; - } - - return mPatchList + x + y*mPatchesPerEdge; -} - - -void LLSurface::dirtyAllPatches() -{ - S32 i; - for (i = 0; i < mNumberOfPatches; i++) - { - mPatchList[i].dirtyZ(); - } -} - -void LLSurface::dirtySurfacePatch(LLSurfacePatch *patchp) -{ - // Put surface patch on dirty surface patch list - mDirtyPatchList.insert(patchp); -} - - -void LLSurface::setWaterHeight(F32 height) -{ - if (!mWaterObjp.isNull()) - { - LLVector3 water_pos_region = mWaterObjp->getPositionRegion(); - bool changed = water_pos_region.mV[VZ] != height; - water_pos_region.mV[VZ] = height; - mWaterObjp->setPositionRegion(water_pos_region); - if (changed) - { - LLWorld::getInstance()->updateWaterObjects(); - } - } - else - { - LL_WARNS() << "LLSurface::setWaterHeight with no water object!" << LL_ENDL; - } -} - -F32 LLSurface::getWaterHeight() const -{ - if (!mWaterObjp.isNull()) - { - // we have a water object, the usual case - return mWaterObjp->getPositionRegion().mV[VZ]; - } - else - { - return DEFAULT_WATER_HEIGHT; - } -} - - -bool LLSurface::generateWaterTexture(const F32 x, const F32 y, - const F32 width, const F32 height) -{ - LL_PROFILE_ZONE_SCOPED - if (!getWaterTexture()) - { - return false; - } - - S32 tex_width = mWaterTexturep->getWidth(); - S32 tex_height = mWaterTexturep->getHeight(); - S32 tex_comps = mWaterTexturep->getComponents(); - S32 tex_stride = tex_width * tex_comps; - LLPointer raw = new LLImageRaw(tex_width, tex_height, tex_comps); - U8 *rawp = raw->getData(); - - F32 scale = 256.f * getMetersPerGrid() / (F32)tex_width; - F32 scale_inv = 1.f / scale; - - S32 x_begin, y_begin, x_end, y_end; - - x_begin = ll_round(x * scale_inv); - y_begin = ll_round(y * scale_inv); - x_end = ll_round((x + width) * scale_inv); - y_end = ll_round((y + width) * scale_inv); - - if (x_end > tex_width) - { - x_end = tex_width; - } - if (y_end > tex_width) - { - y_end = tex_width; - } - - // OK, for now, just have the composition value equal the height at the point. - LLVector3 location; - LLColor4U coloru; - - const F32 WATER_HEIGHT = getWaterHeight(); - - S32 i, j, offset; - for (j = y_begin; j < y_end; j++) - { - for (i = x_begin; i < x_end; i++) - { - //F32 nv[2]; - //nv[0] = i/256.f; - //nv[1] = j/256.f; - // const S32 modulation = noise2(nv)*40; - offset = j*tex_stride + i*tex_comps; - location.mV[VX] = i*scale; - location.mV[VY] = j*scale; - - // Sample multiple points - const F32 height = resolveHeightRegion(location); - - if (height > WATER_HEIGHT) - { - // Above water... - coloru = MAX_WATER_COLOR; - coloru.mV[3] = ABOVE_WATERLINE_ALPHA; - *(rawp + offset++) = coloru.mV[0]; - *(rawp + offset++) = coloru.mV[1]; - *(rawp + offset++) = coloru.mV[2]; - *(rawp + offset++) = coloru.mV[3]; - } - else - { - // Want non-linear curve for transparency gradient - coloru = MAX_WATER_COLOR; - const F32 frac = 1.f - 2.f/(2.f - (height - WATER_HEIGHT)); - S32 alpha = 64 + ll_round((255-64)*frac); - - alpha = llmin(ll_round((F32)MAX_WATER_COLOR.mV[3]), alpha); - alpha = llmax(64, alpha); - - coloru.mV[3] = alpha; - *(rawp + offset++) = coloru.mV[0]; - *(rawp + offset++) = coloru.mV[1]; - *(rawp + offset++) = coloru.mV[2]; - *(rawp + offset++) = coloru.mV[3]; - } - } - } - - if (!mWaterTexturep->hasGLTexture()) - { - mWaterTexturep->createGLTexture(0, raw); - } - - mWaterTexturep->setSubImage(raw, x_begin, y_begin, x_end - x_begin, y_end - y_begin); - return true; -} +/** + * @file llsurface.cpp + * @brief Implementation of LLSurface class + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llsurface.h" + +#include "llrender.h" + +#include "llviewertexturelist.h" +#include "llpatchvertexarray.h" +#include "patch_dct.h" +#include "patch_code.h" +#include "llbitpack.h" +#include "llviewerobjectlist.h" +#include "llregionhandle.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llappviewer.h" +#include "llworld.h" +#include "llviewercontrol.h" +#include "llviewertexture.h" +#include "llsurfacepatch.h" +#include "llvosurfacepatch.h" +#include "llvowater.h" +#include "pipeline.h" +#include "llviewerregion.h" +#include "llvlcomposition.h" +#include "noise.h" +#include "llviewercamera.h" +#include "llglheaders.h" +#include "lldrawpoolterrain.h" +#include "lldrawable.h" + +extern LLPipeline gPipeline; +extern bool gShiftFrame; + +LLColor4U MAX_WATER_COLOR(0, 48, 96, 240); + + +S32 LLSurface::sTextureSize = 256; + +// ---------------- LLSurface:: Public Members --------------- + +LLSurface::LLSurface(U32 type, LLViewerRegion *regionp) : + mGridsPerEdge(0), + mOOGridsPerEdge(0.f), + mPatchesPerEdge(0), + mNumberOfPatches(0), + mType(type), + mDetailTextureScale(0.f), + mOriginGlobal(0.0, 0.0, 0.0), + mSTexturep(NULL), + mWaterTexturep(NULL), + mGridsPerPatchEdge(0), + mMetersPerGrid(1.0f), + mMetersPerEdge(1.0f), + mRegionp(regionp) +{ + // Surface data + mSurfaceZ = NULL; + mNorm = NULL; + + // Patch data + mPatchList = NULL; + + // One of each for each camera + mVisiblePatchCount = 0; + + mHasZData = false; + // "uninitialized" min/max z + mMinZ = 10000.f; + mMaxZ = -10000.f; + + mWaterObjp = NULL; + + // In here temporarily. + mSurfacePatchUpdateCount = 0; + + for (S32 i = 0; i < 8; i++) + { + mNeighbors[i] = NULL; + } +} + + +LLSurface::~LLSurface() +{ + delete [] mSurfaceZ; + mSurfaceZ = NULL; + + delete [] mNorm; + + mGridsPerEdge = 0; + mGridsPerPatchEdge = 0; + mPatchesPerEdge = 0; + mNumberOfPatches = 0; + destroyPatchData(); + + LLDrawPoolTerrain *poolp = (LLDrawPoolTerrain*) gPipeline.findPool(LLDrawPool::POOL_TERRAIN, mSTexturep); + if (!poolp) + { + LL_WARNS() << "No pool for terrain on destruction!" << LL_ENDL; + } + else if (poolp->mReferences.empty()) + { + gPipeline.removePool(poolp); + // Don't enable this until we blitz the draw pool for it as well. -- djs + if (mSTexturep) + { + mSTexturep = NULL; + } + if (mWaterTexturep) + { + mWaterTexturep = NULL; + } + } + else + { + LL_ERRS() << "Terrain pool not empty!" << LL_ENDL; + } +} + +void LLSurface::initClasses() +{ +} + +void LLSurface::setRegion(LLViewerRegion *regionp) +{ + mRegionp = regionp; + mWaterObjp = NULL; // depends on regionp, needs recreating +} + +// Assumes that arguments are powers of 2, and that +// grids_per_edge / grids_per_patch_edge = power of 2 +void LLSurface::create(const S32 grids_per_edge, + const S32 grids_per_patch_edge, + const LLVector3d &origin_global, + const F32 width) +{ + // Initialize various constants for the surface + mGridsPerEdge = grids_per_edge + 1; // Add 1 for the east and north buffer + mOOGridsPerEdge = 1.f / mGridsPerEdge; + mGridsPerPatchEdge = grids_per_patch_edge; + mPatchesPerEdge = (mGridsPerEdge - 1) / mGridsPerPatchEdge; + mNumberOfPatches = mPatchesPerEdge * mPatchesPerEdge; + mMetersPerGrid = width / ((F32)(mGridsPerEdge - 1)); + mMetersPerEdge = mMetersPerGrid * (mGridsPerEdge - 1); + + mOriginGlobal.setVec(origin_global); + + mPVArray.create(mGridsPerEdge, mGridsPerPatchEdge, LLWorld::getInstance()->getRegionScale()); + + S32 number_of_grids = mGridsPerEdge * mGridsPerEdge; + + ///////////////////////////////////// + // + // Initialize data arrays for surface + /// + mSurfaceZ = new F32[number_of_grids]; + mNorm = new LLVector3[number_of_grids]; + + // Reset the surface to be a flat square grid + for(S32 i=0; i < number_of_grids; i++) + { + // Surface is flat and zero + // Normals all point up + mSurfaceZ[i] = 0.0f; + mNorm[i].setVec(0.f, 0.f, 1.f); + } + + + mVisiblePatchCount = 0; + + + /////////////////////// + // + // Initialize textures + // + + initTextures(); + + // Has to be done after texture initialization + createPatchData(); +} + +LLViewerTexture* LLSurface::getSTexture() +{ + if (mSTexturep.notNull() && !mSTexturep->hasGLTexture()) + { + createSTexture(); + } + return mSTexturep; +} + +LLViewerTexture* LLSurface::getWaterTexture() +{ + if (mWaterTexturep.notNull() && !mWaterTexturep->hasGLTexture()) + { + createWaterTexture(); + } + return mWaterTexturep; +} + +void LLSurface::createSTexture() +{ + if (!mSTexturep) + { + // Fill with dummy gray data. + // GL NOT ACTIVE HERE + LLPointer raw = new LLImageRaw(sTextureSize, sTextureSize, 3); + U8 *default_texture = raw->getData(); + for (S32 i = 0; i < sTextureSize; i++) + { + for (S32 j = 0; j < sTextureSize; j++) + { + *(default_texture + (i*sTextureSize + j)*3) = 128; + *(default_texture + (i*sTextureSize + j)*3 + 1) = 128; + *(default_texture + (i*sTextureSize + j)*3 + 2) = 128; + } + } + + mSTexturep = LLViewerTextureManager::getLocalTexture(raw.get(), false); + mSTexturep->dontDiscard(); + gGL.getTexUnit(0)->bind(mSTexturep); + mSTexturep->setAddressMode(LLTexUnit::TAM_CLAMP); + } +} + +void LLSurface::createWaterTexture() +{ + if (!mWaterTexturep) + { + // Create the water texture + LLPointer raw = new LLImageRaw(sTextureSize/2, sTextureSize/2, 4); + U8 *default_texture = raw->getData(); + for (S32 i = 0; i < sTextureSize/2; i++) + { + for (S32 j = 0; j < sTextureSize/2; j++) + { + *(default_texture + (i*sTextureSize/2 + j)*4) = MAX_WATER_COLOR.mV[0]; + *(default_texture + (i*sTextureSize/2 + j)*4 + 1) = MAX_WATER_COLOR.mV[1]; + *(default_texture + (i*sTextureSize/2 + j)*4 + 2) = MAX_WATER_COLOR.mV[2]; + *(default_texture + (i*sTextureSize/2 + j)*4 + 3) = MAX_WATER_COLOR.mV[3]; + } + } + + mWaterTexturep = LLViewerTextureManager::getLocalTexture(raw.get(), false); + mWaterTexturep->dontDiscard(); + gGL.getTexUnit(0)->bind(mWaterTexturep); + mWaterTexturep->setAddressMode(LLTexUnit::TAM_CLAMP); + } +} + +void LLSurface::initTextures() +{ + /////////////////////// + // + // Main surface texture + // + createSTexture(); + + /////////////////////// + // + // Water texture + // + if (gSavedSettings.getBOOL("RenderWater") ) + { + createWaterTexture(); + mWaterObjp = (LLVOWater *)gObjectList.createObjectViewer(LLViewerObject::LL_VO_WATER, mRegionp); + gPipeline.createObject(mWaterObjp); + LLVector3d water_pos_global = from_region_handle(mRegionp->getHandle()); + water_pos_global += LLVector3d(128.0, 128.0, DEFAULT_WATER_HEIGHT); // region doesn't have a valid water height yet + mWaterObjp->setPositionGlobal(water_pos_global); + } +} + + +void LLSurface::setOriginGlobal(const LLVector3d &origin_global) +{ + LLVector3d new_origin_global; + mOriginGlobal = origin_global; + LLSurfacePatch *patchp; + S32 i, j; + // Need to update the southwest corners of the patches + for (j=0; jgetOriginGlobal(); + + new_origin_global.mdV[0] = mOriginGlobal.mdV[0] + i * mMetersPerGrid * mGridsPerPatchEdge; + new_origin_global.mdV[1] = mOriginGlobal.mdV[1] + j * mMetersPerGrid * mGridsPerPatchEdge; + patchp->setOriginGlobal(new_origin_global); + } + } + + // Hack! + if (mWaterObjp.notNull() && mWaterObjp->mDrawable.notNull()) + { + const F64 x = origin_global.mdV[VX] + 128.0; + const F64 y = origin_global.mdV[VY] + 128.0; + const F64 z = mWaterObjp->getPositionGlobal().mdV[VZ]; + + LLVector3d water_origin_global(x, y, z); + + mWaterObjp->setPositionGlobal(water_origin_global); + } +} + +void LLSurface::getNeighboringRegions( std::vector& uniqueRegions ) +{ + S32 i; + for (i = 0; i < 8; i++) + { + if ( mNeighbors[i] != NULL ) + { + uniqueRegions.push_back( mNeighbors[i]->getRegion() ); + } + } +} + + +void LLSurface::getNeighboringRegionsStatus( std::vector& regions ) +{ + S32 i; + for (i = 0; i < 8; i++) + { + if ( mNeighbors[i] != NULL ) + { + regions.push_back( i ); + } + } +} + +void LLSurface::connectNeighbor(LLSurface *neighborp, U32 direction) +{ + S32 i; + LLSurfacePatch *patchp, *neighbor_patchp; + + mNeighbors[direction] = neighborp; + neighborp->mNeighbors[gDirOpposite[direction]] = this; + + // Connect patches + if (NORTHEAST == direction) + { + patchp = getPatch(mPatchesPerEdge - 1, mPatchesPerEdge - 1); + neighbor_patchp = neighborp->getPatch(0, 0); + + patchp->connectNeighbor(neighbor_patchp, direction); + neighbor_patchp->connectNeighbor(patchp, gDirOpposite[direction]); + + patchp->updateNorthEdge(); // Only update one of north or east. + patchp->dirtyZ(); + } + else if (NORTHWEST == direction) + { + patchp = getPatch(0, mPatchesPerEdge - 1); + neighbor_patchp = neighborp->getPatch(mPatchesPerEdge - 1, 0); + + patchp->connectNeighbor(neighbor_patchp, direction); + neighbor_patchp->connectNeighbor(patchp, gDirOpposite[direction]); + } + else if (SOUTHWEST == direction) + { + patchp = getPatch(0, 0); + neighbor_patchp = neighborp->getPatch(mPatchesPerEdge - 1, mPatchesPerEdge - 1); + + patchp->connectNeighbor(neighbor_patchp, direction); + neighbor_patchp->connectNeighbor(patchp, gDirOpposite[direction]); + + neighbor_patchp->updateNorthEdge(); // Only update one of north or east. + neighbor_patchp->dirtyZ(); + } + else if (SOUTHEAST == direction) + { + patchp = getPatch(mPatchesPerEdge - 1, 0); + neighbor_patchp = neighborp->getPatch(0, mPatchesPerEdge - 1); + + patchp->connectNeighbor(neighbor_patchp, direction); + neighbor_patchp->connectNeighbor(patchp, gDirOpposite[direction]); + } + else if (EAST == direction) + { + // Do east/west connections, first + for (i = 0; i < (S32)mPatchesPerEdge; i++) + { + patchp = getPatch(mPatchesPerEdge - 1, i); + neighbor_patchp = neighborp->getPatch(0, i); + + patchp->connectNeighbor(neighbor_patchp, direction); + neighbor_patchp->connectNeighbor(patchp, gDirOpposite[direction]); + + patchp->updateEastEdge(); + patchp->dirtyZ(); + } + + // Now do northeast/southwest connections + for (i = 0; i < (S32)mPatchesPerEdge - 1; i++) + { + patchp = getPatch(mPatchesPerEdge - 1, i); + neighbor_patchp = neighborp->getPatch(0, i+1); + + patchp->connectNeighbor(neighbor_patchp, NORTHEAST); + neighbor_patchp->connectNeighbor(patchp, SOUTHWEST); + } + // Now do southeast/northwest connections + for (i = 1; i < (S32)mPatchesPerEdge; i++) + { + patchp = getPatch(mPatchesPerEdge - 1, i); + neighbor_patchp = neighborp->getPatch(0, i-1); + + patchp->connectNeighbor(neighbor_patchp, SOUTHEAST); + neighbor_patchp->connectNeighbor(patchp, NORTHWEST); + } + } + else if (NORTH == direction) + { + // Do north/south connections, first + for (i = 0; i < (S32)mPatchesPerEdge; i++) + { + patchp = getPatch(i, mPatchesPerEdge - 1); + neighbor_patchp = neighborp->getPatch(i, 0); + + patchp->connectNeighbor(neighbor_patchp, direction); + neighbor_patchp->connectNeighbor(patchp, gDirOpposite[direction]); + + patchp->updateNorthEdge(); + patchp->dirtyZ(); + } + + // Do northeast/southwest connections + for (i = 0; i < (S32)mPatchesPerEdge - 1; i++) + { + patchp = getPatch(i, mPatchesPerEdge - 1); + neighbor_patchp = neighborp->getPatch(i+1, 0); + + patchp->connectNeighbor(neighbor_patchp, NORTHEAST); + neighbor_patchp->connectNeighbor(patchp, SOUTHWEST); + } + // Do southeast/northwest connections + for (i = 1; i < (S32)mPatchesPerEdge; i++) + { + patchp = getPatch(i, mPatchesPerEdge - 1); + neighbor_patchp = neighborp->getPatch(i-1, 0); + + patchp->connectNeighbor(neighbor_patchp, NORTHWEST); + neighbor_patchp->connectNeighbor(patchp, SOUTHEAST); + } + } + else if (WEST == direction) + { + // Do east/west connections, first + for (i = 0; i < mPatchesPerEdge; i++) + { + patchp = getPatch(0, i); + neighbor_patchp = neighborp->getPatch(mPatchesPerEdge - 1, i); + + patchp->connectNeighbor(neighbor_patchp, direction); + neighbor_patchp->connectNeighbor(patchp, gDirOpposite[direction]); + + neighbor_patchp->updateEastEdge(); + neighbor_patchp->dirtyZ(); + } + + // Now do northeast/southwest connections + for (i = 1; i < mPatchesPerEdge; i++) + { + patchp = getPatch(0, i); + neighbor_patchp = neighborp->getPatch(mPatchesPerEdge - 1, i - 1); + + patchp->connectNeighbor(neighbor_patchp, SOUTHWEST); + neighbor_patchp->connectNeighbor(patchp, NORTHEAST); + } + + // Now do northwest/southeast connections + for (i = 0; i < mPatchesPerEdge - 1; i++) + { + patchp = getPatch(0, i); + neighbor_patchp = neighborp->getPatch(mPatchesPerEdge - 1, i + 1); + + patchp->connectNeighbor(neighbor_patchp, NORTHWEST); + neighbor_patchp->connectNeighbor(patchp, SOUTHEAST); + } + } + else if (SOUTH == direction) + { + // Do north/south connections, first + for (i = 0; i < mPatchesPerEdge; i++) + { + patchp = getPatch(i, 0); + neighbor_patchp = neighborp->getPatch(i, mPatchesPerEdge - 1); + + patchp->connectNeighbor(neighbor_patchp, direction); + neighbor_patchp->connectNeighbor(patchp, gDirOpposite[direction]); + + neighbor_patchp->updateNorthEdge(); + neighbor_patchp->dirtyZ(); + } + + // Now do northeast/southwest connections + for (i = 1; i < mPatchesPerEdge; i++) + { + patchp = getPatch(i, 0); + neighbor_patchp = neighborp->getPatch(i - 1, mPatchesPerEdge - 1); + + patchp->connectNeighbor(neighbor_patchp, SOUTHWEST); + neighbor_patchp->connectNeighbor(patchp, NORTHEAST); + } + // Now do northeast/southwest connections + for (i = 0; i < mPatchesPerEdge - 1; i++) + { + patchp = getPatch(i, 0); + neighbor_patchp = neighborp->getPatch(i + 1, mPatchesPerEdge - 1); + + patchp->connectNeighbor(neighbor_patchp, SOUTHEAST); + neighbor_patchp->connectNeighbor(patchp, NORTHWEST); + } + } +} + +void LLSurface::disconnectNeighbor(LLSurface *surfacep) +{ + S32 i; + for (i = 0; i < 8; i++) + { + if (surfacep == mNeighbors[i]) + { + mNeighbors[i] = NULL; + } + } + + // Iterate through surface patches, removing any connectivity to removed surface. + for (i = 0; i < mNumberOfPatches; i++) + { + (mPatchList + i)->disconnectNeighbor(surfacep); + } +} + + +void LLSurface::disconnectAllNeighbors() +{ + S32 i; + for (i = 0; i < 8; i++) + { + if (mNeighbors[i]) + { + mNeighbors[i]->disconnectNeighbor(this); + mNeighbors[i] = NULL; + } + } +} + + + +const LLVector3d &LLSurface::getOriginGlobal() const +{ + return mOriginGlobal; +} + +LLVector3 LLSurface::getOriginAgent() const +{ + return gAgent.getPosAgentFromGlobal(mOriginGlobal); +} + +F32 LLSurface::getMetersPerGrid() const +{ + return mMetersPerGrid; +} + +S32 LLSurface::getGridsPerEdge() const +{ + return mGridsPerEdge; +} + +S32 LLSurface::getPatchesPerEdge() const +{ + return mPatchesPerEdge; +} + +S32 LLSurface::getGridsPerPatchEdge() const +{ + return mGridsPerPatchEdge; +} + +void LLSurface::moveZ(const S32 x, const S32 y, const F32 delta) +{ + llassert(x >= 0); + llassert(y >= 0); + llassert(x < mGridsPerEdge); + llassert(y < mGridsPerEdge); + mSurfaceZ[x + y*mGridsPerEdge] += delta; +} + + +void LLSurface::updatePatchVisibilities(LLAgent &agent) +{ + if (gShiftFrame) + { + return; + } + + LLVector3 pos_region = mRegionp->getPosRegionFromGlobal(gAgentCamera.getCameraPositionGlobal()); + + LLSurfacePatch *patchp; + + mVisiblePatchCount = 0; + for (S32 i=0; iupdateVisibility(); + if (patchp->getVisible()) + { + mVisiblePatchCount++; + patchp->updateCameraDistanceRegion(pos_region); + } + } +} + +bool LLSurface::idleUpdate(F32 max_update_time) +{ + if (!gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_TERRAIN)) + { + return false; + } + + // Perform idle time update of non-critical stuff. + // In this case, texture and normal updates. + LLTimer update_timer; + bool did_update = false; + + // If the Z height data has changed, we need to rebuild our + // property line vertex arrays. + if (mDirtyPatchList.size() > 0) + { + getRegion()->dirtyHeights(); + } + + // Always call updateNormals() / updateVerticalStats() + // every frame to avoid artifacts + for(std::set::iterator iter = mDirtyPatchList.begin(); + iter != mDirtyPatchList.end(); ) + { + std::set::iterator curiter = iter++; + LLSurfacePatch *patchp = *curiter; + patchp->updateNormals(); + patchp->updateVerticalStats(); + if (max_update_time == 0.f || update_timer.getElapsedTimeF32() < max_update_time) + { + if (patchp->updateTexture()) + { + did_update = true; + patchp->clearDirty(); + mDirtyPatchList.erase(curiter); + } + } + } + + if (did_update) + { + // some patches changed, update region reflection probes + mRegionp->updateReflectionProbes(); + } + + return did_update; +} + +void LLSurface::decompressDCTPatch(LLBitPack &bitpack, LLGroupHeader *gopp, bool b_large_patch) +{ + + LLPatchHeader ph; + S32 j, i; + S32 patch[LARGE_PATCH_SIZE*LARGE_PATCH_SIZE]; + LLSurfacePatch *patchp; + + init_patch_decompressor(gopp->patch_size); + gopp->stride = mGridsPerEdge; + set_group_of_patch_header(gopp); + + while (1) + { + decode_patch_header(bitpack, &ph); + if (ph.quant_wbits == END_OF_PATCHES) + { + break; + } + + i = ph.patchids >> 5; + j = ph.patchids & 0x1F; + + if ((i >= mPatchesPerEdge) || (j >= mPatchesPerEdge)) + { + LL_WARNS() << "Received invalid terrain packet - patch header patch ID incorrect!" + << " patches per edge " << mPatchesPerEdge + << " i " << i + << " j " << j + << " dc_offset " << ph.dc_offset + << " range " << (S32)ph.range + << " quant_wbits " << (S32)ph.quant_wbits + << " patchids " << (S32)ph.patchids + << LL_ENDL; + return; + } + + patchp = &mPatchList[j*mPatchesPerEdge + i]; + + + decode_patch(bitpack, patch); + decompress_patch(patchp->getDataZ(), patch, &ph); + + // Update edges for neighbors. Need to guarantee that this gets done before we generate vertical stats. + patchp->updateNorthEdge(); + patchp->updateEastEdge(); + if (patchp->getNeighborPatch(WEST)) + { + patchp->getNeighborPatch(WEST)->updateEastEdge(); + } + if (patchp->getNeighborPatch(SOUTHWEST)) + { + patchp->getNeighborPatch(SOUTHWEST)->updateEastEdge(); + patchp->getNeighborPatch(SOUTHWEST)->updateNorthEdge(); + } + if (patchp->getNeighborPatch(SOUTH)) + { + patchp->getNeighborPatch(SOUTH)->updateNorthEdge(); + } + + // Dirty patch statistics, and flag that the patch has data. + patchp->dirtyZ(); + patchp->setHasReceivedData(); + } +} + + +// Retrurns true if "position" is within the bounds of surface. +// "position" is region-local +bool LLSurface::containsPosition(const LLVector3 &position) +{ + if (position.mV[VX] < 0.0f || position.mV[VX] > mMetersPerEdge || + position.mV[VY] < 0.0f || position.mV[VY] > mMetersPerEdge) + { + return false; + } + return true; +} + + +F32 LLSurface::resolveHeightRegion(const F32 x, const F32 y) const +{ + F32 height = 0.0f; + F32 oometerspergrid = 1.f/mMetersPerGrid; + + // Check to see if v is actually above surface + // We use (mGridsPerEdge-1) below rather than (mGridsPerEdge) + // becuase of the east and north buffers + + if (x >= 0.f && + x <= mMetersPerEdge && + y >= 0.f && + y <= mMetersPerEdge) + { + const S32 left = llfloor(x * oometerspergrid); + const S32 bottom = llfloor(y * oometerspergrid); + + // Don't walk off the edge of the array! + const S32 right = ( left+1 < (S32)mGridsPerEdge-1 ? left+1 : left ); + const S32 top = ( bottom+1 < (S32)mGridsPerEdge-1 ? bottom+1 : bottom ); + + // Figure out if v is in first or second triangle of the square + // and calculate the slopes accordingly + // | | + // -(i,j+1)---(i+1,j+1)-- + // | 1 / | ^ + // | / 2 | | + // | / | j + // --(i,j)----(i+1,j)-- + // | | + // + // i -> + // where N = mGridsPerEdge + + const F32 left_bottom = getZ( left, bottom ); + const F32 right_bottom = getZ( right, bottom ); + const F32 left_top = getZ( left, top ); + const F32 right_top = getZ( right, top ); + + // dx and dy are incremental steps from (mSurface + k) + F32 dx = x - left * mMetersPerGrid; + F32 dy = y - bottom * mMetersPerGrid; + + if (dy > dx) + { + // triangle 1 + dy *= left_top - left_bottom; + dx *= right_top - left_top; + } + else + { + // triangle 2 + dx *= right_bottom - left_bottom; + dy *= right_top - right_bottom; + } + height = left_bottom + (dx + dy) * oometerspergrid; + } + return height; +} + + +F32 LLSurface::resolveHeightGlobal(const LLVector3d& v) const +{ + if (!mRegionp) + { + return 0.f; + } + + LLVector3 pos_region = mRegionp->getPosRegionFromGlobal(v); + + return resolveHeightRegion(pos_region); +} + + +LLVector3 LLSurface::resolveNormalGlobal(const LLVector3d& pos_global) const +{ + if (!mSurfaceZ) + { + // Hmm. Uninitialized surface! + return LLVector3::z_axis; + } + // + // Returns the vector normal to a surface at location specified by vector v + // + F32 oometerspergrid = 1.f/mMetersPerGrid; + LLVector3 normal; + F32 dzx, dzy; + + if (pos_global.mdV[VX] >= mOriginGlobal.mdV[VX] && + pos_global.mdV[VX] < mOriginGlobal.mdV[VX] + mMetersPerEdge && + pos_global.mdV[VY] >= mOriginGlobal.mdV[VY] && + pos_global.mdV[VY] < mOriginGlobal.mdV[VY] + mMetersPerEdge) + { + U32 i, j, k; + F32 dx, dy; + i = (U32) ((pos_global.mdV[VX] - mOriginGlobal.mdV[VX]) * oometerspergrid); + j = (U32) ((pos_global.mdV[VY] - mOriginGlobal.mdV[VY]) * oometerspergrid ); + k = i + j*mGridsPerEdge; + + // Figure out if v is in first or second triangle of the square + // and calculate the slopes accordingly + // | | + // -(k+N)---(k+1+N)-- + // | 1 / | ^ + // | / 2 | | + // | / | j + // --(k)----(k+1)-- + // | | + // + // i -> + // where N = mGridsPerEdge + + // dx and dy are incremental steps from (mSurface + k) + dx = (F32)(pos_global.mdV[VX] - i*mMetersPerGrid - mOriginGlobal.mdV[VX]); + dy = (F32)(pos_global.mdV[VY] - j*mMetersPerGrid - mOriginGlobal.mdV[VY]); + if (dy > dx) + { // triangle 1 + dzx = *(mSurfaceZ + k + 1 + mGridsPerEdge) - *(mSurfaceZ + k + mGridsPerEdge); + dzy = *(mSurfaceZ + k) - *(mSurfaceZ + k + mGridsPerEdge); + normal.setVec(-dzx,dzy,1); + } + else + { // triangle 2 + dzx = *(mSurfaceZ + k) - *(mSurfaceZ + k + 1); + dzy = *(mSurfaceZ + k + 1 + mGridsPerEdge) - *(mSurfaceZ + k + 1); + normal.setVec(dzx,-dzy,1); + } + } + normal.normVec(); + return normal; + + +} + +LLSurfacePatch *LLSurface::resolvePatchRegion(const F32 x, const F32 y) const +{ +// x and y should be region-local coordinates. +// If x and y are outside of the surface, then the returned +// index will be for the nearest boundary patch. +// +// 12 | 13| 14| 15 +// | | | +// +---+---+---+---+ +// | 12| 13| 14| 15| +// ----+---+---+---+---+----- +// 8 | 8 | 9 | 10| 11| 11 +// ----+---+---+---+---+----- +// 4 | 4 | 5 | 6 | 7 | 7 +// ----+---+---+---+---+----- +// | 0 | 1 | 2 | 3 | +// +---+---+---+---+ +// | | | +// 0 | 1 | 2 | 3 +// + +// When x and y are not region-local do the following first + + S32 i, j; + if (x < 0.0f) + { + i = 0; + } + else if (x >= mMetersPerEdge) + { + i = mPatchesPerEdge - 1; + } + else + { + i = (U32) (x / (mMetersPerGrid * mGridsPerPatchEdge)); + } + + if (y < 0.0f) + { + j = 0; + } + else if (y >= mMetersPerEdge) + { + j = mPatchesPerEdge - 1; + } + else + { + j = (U32) (y / (mMetersPerGrid * mGridsPerPatchEdge)); + } + + // *NOTE: Super paranoia code follows. + S32 index = i + j * mPatchesPerEdge; + if((index < 0) || (index >= mNumberOfPatches)) + { + if(0 == mNumberOfPatches) + { + LL_WARNS() << "No patches for current region!" << LL_ENDL; + return NULL; + } + S32 old_index = index; + index = llclamp(old_index, 0, (mNumberOfPatches - 1)); + LL_WARNS() << "Clamping out of range patch index " << old_index + << " to " << index << LL_ENDL; + } + return &(mPatchList[index]); +} + + +LLSurfacePatch *LLSurface::resolvePatchRegion(const LLVector3 &pos_region) const +{ + return resolvePatchRegion(pos_region.mV[VX], pos_region.mV[VY]); +} + + +LLSurfacePatch *LLSurface::resolvePatchGlobal(const LLVector3d &pos_global) const +{ + llassert(mRegionp); + LLVector3 pos_region = mRegionp->getPosRegionFromGlobal(pos_global); + return resolvePatchRegion(pos_region); +} + + +std::ostream& operator<<(std::ostream &s, const LLSurface &S) +{ + s << "{ \n"; + s << " mGridsPerEdge = " << S.mGridsPerEdge - 1 << " + 1\n"; + s << " mGridsPerPatchEdge = " << S.mGridsPerPatchEdge << "\n"; + s << " mPatchesPerEdge = " << S.mPatchesPerEdge << "\n"; + s << " mOriginGlobal = " << S.mOriginGlobal << "\n"; + s << " mMetersPerGrid = " << S.mMetersPerGrid << "\n"; + s << " mVisiblePatchCount = " << S.mVisiblePatchCount << "\n"; + s << "}"; + return s; +} + + +// ---------------- LLSurface:: Protected ---------------- + +void LLSurface::createPatchData() +{ + // Assumes mGridsPerEdge, mGridsPerPatchEdge, and mPatchesPerEdge have been properly set + // TODO -- check for create() called when surface is not empty + S32 i, j; + LLSurfacePatch *patchp; + + // Allocate memory + mPatchList = new LLSurfacePatch[mNumberOfPatches]; + + // One of each for each camera + mVisiblePatchCount = mNumberOfPatches; + + for (j=0; jsetSurface(this); + } + } + + for (j=0; jmHasReceivedData = false; + patchp->mSTexUpdate = true; + + S32 data_offset = i * mGridsPerPatchEdge + j * mGridsPerPatchEdge * mGridsPerEdge; + + patchp->setDataZ(mSurfaceZ + data_offset); + patchp->setDataNorm(mNorm + data_offset); + + + // We make each patch point to its neighbors so we can do resolution checking + // when butting up different resolutions. Patches that don't have neighbors + // somewhere will point to NULL on that side. + if (i < mPatchesPerEdge-1) + { + patchp->setNeighborPatch(EAST,getPatch(i+1, j)); + } + else + { + patchp->setNeighborPatch(EAST, NULL); + } + + if (j < mPatchesPerEdge-1) + { + patchp->setNeighborPatch(NORTH, getPatch(i, j+1)); + } + else + { + patchp->setNeighborPatch(NORTH, NULL); + } + + if (i > 0) + { + patchp->setNeighborPatch(WEST, getPatch(i - 1, j)); + } + else + { + patchp->setNeighborPatch(WEST, NULL); + } + + if (j > 0) + { + patchp->setNeighborPatch(SOUTH, getPatch(i, j-1)); + } + else + { + patchp->setNeighborPatch(SOUTH, NULL); + } + + if (i < (mPatchesPerEdge-1) && j < (mPatchesPerEdge-1)) + { + patchp->setNeighborPatch(NORTHEAST, getPatch(i + 1, j + 1)); + } + else + { + patchp->setNeighborPatch(NORTHEAST, NULL); + } + + if (i > 0 && j < (mPatchesPerEdge-1)) + { + patchp->setNeighborPatch(NORTHWEST, getPatch(i - 1, j + 1)); + } + else + { + patchp->setNeighborPatch(NORTHWEST, NULL); + } + + if (i > 0 && j > 0) + { + patchp->setNeighborPatch(SOUTHWEST, getPatch(i - 1, j - 1)); + } + else + { + patchp->setNeighborPatch(SOUTHWEST, NULL); + } + + if (i < (mPatchesPerEdge-1) && j > 0) + { + patchp->setNeighborPatch(SOUTHEAST, getPatch(i + 1, j - 1)); + } + else + { + patchp->setNeighborPatch(SOUTHEAST, NULL); + } + + LLVector3d origin_global; + origin_global.mdV[0] = mOriginGlobal.mdV[0] + i * mMetersPerGrid * mGridsPerPatchEdge; + origin_global.mdV[1] = mOriginGlobal.mdV[0] + j * mMetersPerGrid * mGridsPerPatchEdge; + origin_global.mdV[2] = 0.f; + patchp->setOriginGlobal(origin_global); + } + } +} + + +void LLSurface::destroyPatchData() +{ + // Delete all of the cached patch data for these patches. + + delete [] mPatchList; + mPatchList = NULL; + mVisiblePatchCount = 0; +} + + +void LLSurface::setTextureSize(const S32 texture_size) +{ + sTextureSize = texture_size; +} + + +U32 LLSurface::getRenderLevel(const U32 render_stride) const +{ + return mPVArray.mRenderLevelp[render_stride]; +} + + +U32 LLSurface::getRenderStride(const U32 render_level) const +{ + return mPVArray.mRenderStridep[render_level]; +} + + +LLSurfacePatch *LLSurface::getPatch(const S32 x, const S32 y) const +{ + if ((x < 0) || (x >= mPatchesPerEdge)) + { + LL_ERRS() << "Asking for patch out of bounds" << LL_ENDL; + return NULL; + } + if ((y < 0) || (y >= mPatchesPerEdge)) + { + LL_ERRS() << "Asking for patch out of bounds" << LL_ENDL; + return NULL; + } + + return mPatchList + x + y*mPatchesPerEdge; +} + + +void LLSurface::dirtyAllPatches() +{ + S32 i; + for (i = 0; i < mNumberOfPatches; i++) + { + mPatchList[i].dirtyZ(); + } +} + +void LLSurface::dirtySurfacePatch(LLSurfacePatch *patchp) +{ + // Put surface patch on dirty surface patch list + mDirtyPatchList.insert(patchp); +} + + +void LLSurface::setWaterHeight(F32 height) +{ + if (!mWaterObjp.isNull()) + { + LLVector3 water_pos_region = mWaterObjp->getPositionRegion(); + bool changed = water_pos_region.mV[VZ] != height; + water_pos_region.mV[VZ] = height; + mWaterObjp->setPositionRegion(water_pos_region); + if (changed) + { + LLWorld::getInstance()->updateWaterObjects(); + } + } + else + { + LL_WARNS() << "LLSurface::setWaterHeight with no water object!" << LL_ENDL; + } +} + +F32 LLSurface::getWaterHeight() const +{ + if (!mWaterObjp.isNull()) + { + // we have a water object, the usual case + return mWaterObjp->getPositionRegion().mV[VZ]; + } + else + { + return DEFAULT_WATER_HEIGHT; + } +} + + +bool LLSurface::generateWaterTexture(const F32 x, const F32 y, + const F32 width, const F32 height) +{ + LL_PROFILE_ZONE_SCOPED + if (!getWaterTexture()) + { + return false; + } + + S32 tex_width = mWaterTexturep->getWidth(); + S32 tex_height = mWaterTexturep->getHeight(); + S32 tex_comps = mWaterTexturep->getComponents(); + S32 tex_stride = tex_width * tex_comps; + LLPointer raw = new LLImageRaw(tex_width, tex_height, tex_comps); + U8 *rawp = raw->getData(); + + F32 scale = 256.f * getMetersPerGrid() / (F32)tex_width; + F32 scale_inv = 1.f / scale; + + S32 x_begin, y_begin, x_end, y_end; + + x_begin = ll_round(x * scale_inv); + y_begin = ll_round(y * scale_inv); + x_end = ll_round((x + width) * scale_inv); + y_end = ll_round((y + width) * scale_inv); + + if (x_end > tex_width) + { + x_end = tex_width; + } + if (y_end > tex_width) + { + y_end = tex_width; + } + + // OK, for now, just have the composition value equal the height at the point. + LLVector3 location; + LLColor4U coloru; + + const F32 WATER_HEIGHT = getWaterHeight(); + + S32 i, j, offset; + for (j = y_begin; j < y_end; j++) + { + for (i = x_begin; i < x_end; i++) + { + //F32 nv[2]; + //nv[0] = i/256.f; + //nv[1] = j/256.f; + // const S32 modulation = noise2(nv)*40; + offset = j*tex_stride + i*tex_comps; + location.mV[VX] = i*scale; + location.mV[VY] = j*scale; + + // Sample multiple points + const F32 height = resolveHeightRegion(location); + + if (height > WATER_HEIGHT) + { + // Above water... + coloru = MAX_WATER_COLOR; + coloru.mV[3] = ABOVE_WATERLINE_ALPHA; + *(rawp + offset++) = coloru.mV[0]; + *(rawp + offset++) = coloru.mV[1]; + *(rawp + offset++) = coloru.mV[2]; + *(rawp + offset++) = coloru.mV[3]; + } + else + { + // Want non-linear curve for transparency gradient + coloru = MAX_WATER_COLOR; + const F32 frac = 1.f - 2.f/(2.f - (height - WATER_HEIGHT)); + S32 alpha = 64 + ll_round((255-64)*frac); + + alpha = llmin(ll_round((F32)MAX_WATER_COLOR.mV[3]), alpha); + alpha = llmax(64, alpha); + + coloru.mV[3] = alpha; + *(rawp + offset++) = coloru.mV[0]; + *(rawp + offset++) = coloru.mV[1]; + *(rawp + offset++) = coloru.mV[2]; + *(rawp + offset++) = coloru.mV[3]; + } + } + } + + if (!mWaterTexturep->hasGLTexture()) + { + mWaterTexturep->createGLTexture(0, raw); + } + + mWaterTexturep->setSubImage(raw, x_begin, y_begin, x_end - x_begin, y_end - y_begin); + return true; +} diff --git a/indra/newview/llsurface.h b/indra/newview/llsurface.h index 8acf9e79dc..d759389585 100644 --- a/indra/newview/llsurface.h +++ b/indra/newview/llsurface.h @@ -1,260 +1,260 @@ -/** - * @file llsurface.h - * @brief Description of LLSurface class - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLSURFACE_H -#define LL_LLSURFACE_H - -//#include "vmath.h" -#include "v3math.h" -#include "v3dmath.h" -#include "v4math.h" -#include "m3math.h" -#include "m4math.h" -#include "llquaternion.h" - -#include "v4coloru.h" -#include "v4color.h" - -#include "llvowater.h" -#include "llpatchvertexarray.h" -#include "llviewertexture.h" - -class LLTimer; -class LLUUID; -class LLAgent; - -static const U8 NO_EDGE = 0x00; -static const U8 EAST_EDGE = 0x01; -static const U8 NORTH_EDGE = 0x02; -static const U8 WEST_EDGE = 0x04; -static const U8 SOUTH_EDGE = 0x08; - -static const S32 ONE_MORE_THAN_NEIGHBOR = 1; -static const S32 EQUAL_TO_NEIGHBOR = 0; -static const S32 ONE_LESS_THAN_NEIGHBOR = -1; - -const S32 ABOVE_WATERLINE_ALPHA = 32; // The alpha of water when the land elevation is above the waterline. - -class LLViewerRegion; -class LLSurfacePatch; -class LLBitPack; -class LLGroupHeader; - -class LLSurface -{ -public: - LLSurface(U32 type, LLViewerRegion *regionp = NULL); - virtual ~LLSurface(); - - static void initClasses(); // Do class initialization for LLSurface and its child classes. - - void create(const S32 surface_grid_width, - const S32 surface_patch_width, - const LLVector3d &origin_global, - const F32 width); // Allocates and initializes surface - - void setRegion(LLViewerRegion *regionp); - - void setOriginGlobal(const LLVector3d &origin_global); - - void connectNeighbor(LLSurface *neighborp, U32 direction); - void disconnectNeighbor(LLSurface *neighborp); - void disconnectAllNeighbors(); - - virtual void decompressDCTPatch(LLBitPack &bitpack, LLGroupHeader *gopp, bool b_large_patch); - virtual void updatePatchVisibilities(LLAgent &agent); - - inline F32 getZ(const U32 k) const { return mSurfaceZ[k]; } - inline F32 getZ(const S32 i, const S32 j) const { return mSurfaceZ[i + j*mGridsPerEdge]; } - - LLVector3 getOriginAgent() const; - const LLVector3d &getOriginGlobal() const; - F32 getMetersPerGrid() const; - S32 getGridsPerEdge() const; - S32 getPatchesPerEdge() const; - S32 getGridsPerPatchEdge() const; - U32 getRenderStride(const U32 render_level) const; - U32 getRenderLevel(const U32 render_stride) const; - - // Returns the height of the surface immediately above (or below) location, - // or if location is not above surface returns zero. - F32 resolveHeightRegion(const F32 x, const F32 y) const; - F32 resolveHeightRegion(const LLVector3 &location) const - { return resolveHeightRegion( location.mV[VX], location.mV[VY] ); } - F32 resolveHeightGlobal(const LLVector3d &position_global) const; - LLVector3 resolveNormalGlobal(const LLVector3d& v) const; // Returns normal to surface - - LLSurfacePatch *resolvePatchRegion(const F32 x, const F32 y) const; - LLSurfacePatch *resolvePatchRegion(const LLVector3 &position_region) const; - LLSurfacePatch *resolvePatchGlobal(const LLVector3d &position_global) const; - - // Update methods (called during idle, normally) - bool idleUpdate(F32 max_update_time); - - bool containsPosition(const LLVector3 &position); - - void moveZ(const S32 x, const S32 y, const F32 delta); - - LLViewerRegion *getRegion() const { return mRegionp; } - - F32 getMinZ() const { return mMinZ; } - F32 getMaxZ() const { return mMaxZ; } - - void setWaterHeight(F32 height); - F32 getWaterHeight() const; - - LLViewerTexture *getSTexture(); - LLViewerTexture *getWaterTexture(); - bool hasZData() const { return mHasZData; } - - void dirtyAllPatches(); // Use this to dirty all patches when changing terrain parameters - - void dirtySurfacePatch(LLSurfacePatch *patchp); - LLVOWater *getWaterObj() { return mWaterObjp; } - - static void setTextureSize(const S32 texture_size); - - friend class LLSurfacePatch; - friend std::ostream& operator<<(std::ostream &s, const LLSurface &S); - - void getNeighboringRegions( std::vector& uniqueRegions ); - void getNeighboringRegionsStatus( std::vector& regions ); - -public: - // Number of grid points on one side of a region, including +1 buffer for - // north and east edge. - S32 mGridsPerEdge; - - F32 mOOGridsPerEdge; // Inverse of grids per edge - - S32 mPatchesPerEdge; // Number of patches on one side of a region - S32 mNumberOfPatches; // Total number of patches - - - // Each surface points at 8 neighbors (or NULL) - // +---+---+---+ - // |NW | N | NE| - // +---+---+---+ - // | W | 0 | E | - // +---+---+---+ - // |SW | S | SE| - // +---+---+---+ - LLSurface *mNeighbors[8]; // Adjacent patches - - U32 mType; // Useful for identifying derived classes - - F32 mDetailTextureScale; // Number of times to repeat detail texture across this surface - -protected: - void createSTexture(); - void createWaterTexture(); - void initTextures(); - void initWater(); - - - void createPatchData(); // Allocates memory for patches. - void destroyPatchData(); // Deallocates memory for patches. - - bool generateWaterTexture(const F32 x, const F32 y, - const F32 width, const F32 height); // Generate texture from composition values. - - //F32 updateTexture(LLSurfacePatch *ppatch); - - LLSurfacePatch *getPatch(const S32 x, const S32 y) const; - -protected: - LLVector3d mOriginGlobal; // In absolute frame - LLSurfacePatch *mPatchList; // Array of all patches - - // Array of grid data, mGridsPerEdge * mGridsPerEdge - F32 *mSurfaceZ; - - // Array of grid normals, mGridsPerEdge * mGridsPerEdge - LLVector3 *mNorm; - - std::set mDirtyPatchList; - - - // The textures should never be directly initialized - use the setter methods! - LLPointer mSTexturep; // Texture for surface - LLPointer mWaterTexturep; // Water texture - - LLPointer mWaterObjp; - - // When we want multiple cameras we'll need one of each these for each camera - S32 mVisiblePatchCount; - - U32 mGridsPerPatchEdge; // Number of grid points on a side of a patch - F32 mMetersPerGrid; // Converts (i,j) indecies to distance - F32 mMetersPerEdge; // = mMetersPerGrid * (mGridsPerEdge-1) - - LLPatchVertexArray mPVArray; - - bool mHasZData; // We've received any patch data for this surface. - F32 mMinZ; // min z for this region (during the session) - F32 mMaxZ; // max z for this region (during the session) - - S32 mSurfacePatchUpdateCount; // Number of frames since last update. - -private: - LLViewerRegion *mRegionp; // Patch whose coordinate system this surface is using. - static S32 sTextureSize; // Size of the surface texture -}; - - - -// . __. -// Z /|\ /| Y North -// | / -// | / |<----------------- mGridsPerSurfaceEdge --------------->| -// | / __________________________________________________________ -// |/______\ X /_______________________________________________________ / -// / / / / / / / /M*M-2 /M*M-1 / / -// /______/______/______/______/______/______/______/______/ / -// / / / / / / / / / / -// /______/______/______/______/______/______/______/______/ / -// / / / / / / / / / / -// /______/______/______/______/______/______/______/______/ / -// West / / / / / / / / / / -// /______/______/______/______/______/______/______/______/ / East -// /... / / / / / / / / / -// /______/______/______/______/______/______/______/______/ / -// _. / 2M / / / / / / / / / -// /| /______/______/______/______/______/______/______/______/ / -// / / M / M+1 / M+2 / ... / / / / 2M-1 / / -// j /______/______/______/______/______/______/______/______/ / -// / 0 / 1 / 2 / ... / / / / M-1 / / -// /______/______/______/______/______/______/______/______/_/ -// South |<-L->| -// i --> -// -// where M = mSurfPatchWidth -// and L = mPatchGridWidth -// -// Notice that mGridsPerSurfaceEdge = a power of two + 1 -// This provides a buffer on the east and north edges that will allow us to -// fill the cracks between adjacent surfaces when rendering. -#endif +/** + * @file llsurface.h + * @brief Description of LLSurface class + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSURFACE_H +#define LL_LLSURFACE_H + +//#include "vmath.h" +#include "v3math.h" +#include "v3dmath.h" +#include "v4math.h" +#include "m3math.h" +#include "m4math.h" +#include "llquaternion.h" + +#include "v4coloru.h" +#include "v4color.h" + +#include "llvowater.h" +#include "llpatchvertexarray.h" +#include "llviewertexture.h" + +class LLTimer; +class LLUUID; +class LLAgent; + +static const U8 NO_EDGE = 0x00; +static const U8 EAST_EDGE = 0x01; +static const U8 NORTH_EDGE = 0x02; +static const U8 WEST_EDGE = 0x04; +static const U8 SOUTH_EDGE = 0x08; + +static const S32 ONE_MORE_THAN_NEIGHBOR = 1; +static const S32 EQUAL_TO_NEIGHBOR = 0; +static const S32 ONE_LESS_THAN_NEIGHBOR = -1; + +const S32 ABOVE_WATERLINE_ALPHA = 32; // The alpha of water when the land elevation is above the waterline. + +class LLViewerRegion; +class LLSurfacePatch; +class LLBitPack; +class LLGroupHeader; + +class LLSurface +{ +public: + LLSurface(U32 type, LLViewerRegion *regionp = NULL); + virtual ~LLSurface(); + + static void initClasses(); // Do class initialization for LLSurface and its child classes. + + void create(const S32 surface_grid_width, + const S32 surface_patch_width, + const LLVector3d &origin_global, + const F32 width); // Allocates and initializes surface + + void setRegion(LLViewerRegion *regionp); + + void setOriginGlobal(const LLVector3d &origin_global); + + void connectNeighbor(LLSurface *neighborp, U32 direction); + void disconnectNeighbor(LLSurface *neighborp); + void disconnectAllNeighbors(); + + virtual void decompressDCTPatch(LLBitPack &bitpack, LLGroupHeader *gopp, bool b_large_patch); + virtual void updatePatchVisibilities(LLAgent &agent); + + inline F32 getZ(const U32 k) const { return mSurfaceZ[k]; } + inline F32 getZ(const S32 i, const S32 j) const { return mSurfaceZ[i + j*mGridsPerEdge]; } + + LLVector3 getOriginAgent() const; + const LLVector3d &getOriginGlobal() const; + F32 getMetersPerGrid() const; + S32 getGridsPerEdge() const; + S32 getPatchesPerEdge() const; + S32 getGridsPerPatchEdge() const; + U32 getRenderStride(const U32 render_level) const; + U32 getRenderLevel(const U32 render_stride) const; + + // Returns the height of the surface immediately above (or below) location, + // or if location is not above surface returns zero. + F32 resolveHeightRegion(const F32 x, const F32 y) const; + F32 resolveHeightRegion(const LLVector3 &location) const + { return resolveHeightRegion( location.mV[VX], location.mV[VY] ); } + F32 resolveHeightGlobal(const LLVector3d &position_global) const; + LLVector3 resolveNormalGlobal(const LLVector3d& v) const; // Returns normal to surface + + LLSurfacePatch *resolvePatchRegion(const F32 x, const F32 y) const; + LLSurfacePatch *resolvePatchRegion(const LLVector3 &position_region) const; + LLSurfacePatch *resolvePatchGlobal(const LLVector3d &position_global) const; + + // Update methods (called during idle, normally) + bool idleUpdate(F32 max_update_time); + + bool containsPosition(const LLVector3 &position); + + void moveZ(const S32 x, const S32 y, const F32 delta); + + LLViewerRegion *getRegion() const { return mRegionp; } + + F32 getMinZ() const { return mMinZ; } + F32 getMaxZ() const { return mMaxZ; } + + void setWaterHeight(F32 height); + F32 getWaterHeight() const; + + LLViewerTexture *getSTexture(); + LLViewerTexture *getWaterTexture(); + bool hasZData() const { return mHasZData; } + + void dirtyAllPatches(); // Use this to dirty all patches when changing terrain parameters + + void dirtySurfacePatch(LLSurfacePatch *patchp); + LLVOWater *getWaterObj() { return mWaterObjp; } + + static void setTextureSize(const S32 texture_size); + + friend class LLSurfacePatch; + friend std::ostream& operator<<(std::ostream &s, const LLSurface &S); + + void getNeighboringRegions( std::vector& uniqueRegions ); + void getNeighboringRegionsStatus( std::vector& regions ); + +public: + // Number of grid points on one side of a region, including +1 buffer for + // north and east edge. + S32 mGridsPerEdge; + + F32 mOOGridsPerEdge; // Inverse of grids per edge + + S32 mPatchesPerEdge; // Number of patches on one side of a region + S32 mNumberOfPatches; // Total number of patches + + + // Each surface points at 8 neighbors (or NULL) + // +---+---+---+ + // |NW | N | NE| + // +---+---+---+ + // | W | 0 | E | + // +---+---+---+ + // |SW | S | SE| + // +---+---+---+ + LLSurface *mNeighbors[8]; // Adjacent patches + + U32 mType; // Useful for identifying derived classes + + F32 mDetailTextureScale; // Number of times to repeat detail texture across this surface + +protected: + void createSTexture(); + void createWaterTexture(); + void initTextures(); + void initWater(); + + + void createPatchData(); // Allocates memory for patches. + void destroyPatchData(); // Deallocates memory for patches. + + bool generateWaterTexture(const F32 x, const F32 y, + const F32 width, const F32 height); // Generate texture from composition values. + + //F32 updateTexture(LLSurfacePatch *ppatch); + + LLSurfacePatch *getPatch(const S32 x, const S32 y) const; + +protected: + LLVector3d mOriginGlobal; // In absolute frame + LLSurfacePatch *mPatchList; // Array of all patches + + // Array of grid data, mGridsPerEdge * mGridsPerEdge + F32 *mSurfaceZ; + + // Array of grid normals, mGridsPerEdge * mGridsPerEdge + LLVector3 *mNorm; + + std::set mDirtyPatchList; + + + // The textures should never be directly initialized - use the setter methods! + LLPointer mSTexturep; // Texture for surface + LLPointer mWaterTexturep; // Water texture + + LLPointer mWaterObjp; + + // When we want multiple cameras we'll need one of each these for each camera + S32 mVisiblePatchCount; + + U32 mGridsPerPatchEdge; // Number of grid points on a side of a patch + F32 mMetersPerGrid; // Converts (i,j) indecies to distance + F32 mMetersPerEdge; // = mMetersPerGrid * (mGridsPerEdge-1) + + LLPatchVertexArray mPVArray; + + bool mHasZData; // We've received any patch data for this surface. + F32 mMinZ; // min z for this region (during the session) + F32 mMaxZ; // max z for this region (during the session) + + S32 mSurfacePatchUpdateCount; // Number of frames since last update. + +private: + LLViewerRegion *mRegionp; // Patch whose coordinate system this surface is using. + static S32 sTextureSize; // Size of the surface texture +}; + + + +// . __. +// Z /|\ /| Y North +// | / +// | / |<----------------- mGridsPerSurfaceEdge --------------->| +// | / __________________________________________________________ +// |/______\ X /_______________________________________________________ / +// / / / / / / / /M*M-2 /M*M-1 / / +// /______/______/______/______/______/______/______/______/ / +// / / / / / / / / / / +// /______/______/______/______/______/______/______/______/ / +// / / / / / / / / / / +// /______/______/______/______/______/______/______/______/ / +// West / / / / / / / / / / +// /______/______/______/______/______/______/______/______/ / East +// /... / / / / / / / / / +// /______/______/______/______/______/______/______/______/ / +// _. / 2M / / / / / / / / / +// /| /______/______/______/______/______/______/______/______/ / +// / / M / M+1 / M+2 / ... / / / / 2M-1 / / +// j /______/______/______/______/______/______/______/______/ / +// / 0 / 1 / 2 / ... / / / / M-1 / / +// /______/______/______/______/______/______/______/______/_/ +// South |<-L->| +// i --> +// +// where M = mSurfPatchWidth +// and L = mPatchGridWidth +// +// Notice that mGridsPerSurfaceEdge = a power of two + 1 +// This provides a buffer on the east and north edges that will allow us to +// fill the cracks between adjacent surfaces when rendering. +#endif diff --git a/indra/newview/llsurfacepatch.cpp b/indra/newview/llsurfacepatch.cpp index 13215d771e..d2379da7b3 100644 --- a/indra/newview/llsurfacepatch.cpp +++ b/indra/newview/llsurfacepatch.cpp @@ -1,1032 +1,1032 @@ -/** - * @file llsurfacepatch.cpp - * @brief LLSurfacePatch class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llsurfacepatch.h" -#include "llpatchvertexarray.h" -#include "llviewerobjectlist.h" -#include "llvosurfacepatch.h" -#include "llsurface.h" -#include "pipeline.h" -#include "llagent.h" -#include "llsky.h" -#include "llviewercamera.h" - -// For getting composition values -#include "llviewerregion.h" -#include "llvlcomposition.h" -#include "lldrawpool.h" -#include "noise.h" - -extern bool gShiftFrame; -extern U64MicrosecondsImplicit gFrameTime; -extern LLPipeline gPipeline; - -LLSurfacePatch::LLSurfacePatch() -: mHasReceivedData(false), - mSTexUpdate(false), - mDirty(false), - mDirtyZStats(true), - mHeightsGenerated(false), - mDataOffset(0), - mDataZ(NULL), - mDataNorm(NULL), - mVObjp(NULL), - mOriginRegion(0.f, 0.f, 0.f), - mCenterRegion(0.f, 0.f, 0.f), - mMinZ(0.f), - mMaxZ(0.f), - mMeanZ(0.f), - mRadius(0.f), - mMinComposition(0.f), - mMaxComposition(0.f), - mMeanComposition(0.f), - // This flag is used to communicate between adjacent surfaces and is - // set to non-zero values by higher classes. - mConnectedEdge(NO_EDGE), - mLastUpdateTime(0), - mSurfacep(NULL) -{ - S32 i; - for (i = 0; i < 8; i++) - { - setNeighborPatch(i, NULL); - } - for (i = 0; i < 9; i++) - { - mNormalsInvalid[i] = true; - } -} - - -LLSurfacePatch::~LLSurfacePatch() -{ - mVObjp = NULL; -} - - -void LLSurfacePatch::dirty() -{ - // These are outside of the loop in case we're still waiting for a dirty from the - // texture being updated... - if (mVObjp) - { - mVObjp->dirtyGeom(); - } - else - { - LL_WARNS("Terrain") << "No viewer object for this surface patch!" << LL_ENDL; - } - - mDirtyZStats = true; - mHeightsGenerated = false; - - if (!mDirty) - { - mDirty = true; - mSurfacep->dirtySurfacePatch(this); - } -} - - -void LLSurfacePatch::setSurface(LLSurface *surfacep) -{ - mSurfacep = surfacep; - if (mVObjp == (LLVOSurfacePatch *)NULL) - { - llassert(mSurfacep->mType == 'l'); - - mVObjp = (LLVOSurfacePatch *)gObjectList.createObjectViewer(LLViewerObject::LL_VO_SURFACE_PATCH, mSurfacep->getRegion()); - mVObjp->setPatch(this); - mVObjp->setPositionRegion(mCenterRegion); - gPipeline.createObject(mVObjp); - } -} - -void LLSurfacePatch::disconnectNeighbor(LLSurface *surfacep) -{ - U32 i; - for (i = 0; i < 8; i++) - { - if (getNeighborPatch(i)) - { - if (getNeighborPatch(i)->mSurfacep == surfacep) - { - setNeighborPatch(i, NULL); - mNormalsInvalid[i] = true; - } - } - } - - // Clean up connected edges - if (getNeighborPatch(EAST)) - { - if (getNeighborPatch(EAST)->mSurfacep == surfacep) - { - mConnectedEdge &= ~EAST_EDGE; - } - } - if (getNeighborPatch(NORTH)) - { - if (getNeighborPatch(NORTH)->mSurfacep == surfacep) - { - mConnectedEdge &= ~NORTH_EDGE; - } - } - if (getNeighborPatch(WEST)) - { - if (getNeighborPatch(WEST)->mSurfacep == surfacep) - { - mConnectedEdge &= ~WEST_EDGE; - } - } - if (getNeighborPatch(SOUTH)) - { - if (getNeighborPatch(SOUTH)->mSurfacep == surfacep) - { - mConnectedEdge &= ~SOUTH_EDGE; - } - } -} - -LLVector3 LLSurfacePatch::getPointAgent(const U32 x, const U32 y) const -{ - U32 surface_stride = mSurfacep->getGridsPerEdge(); - U32 point_offset = x + y*surface_stride; - LLVector3 pos; - pos = getOriginAgent(); - pos.mV[VX] += x * mSurfacep->getMetersPerGrid(); - pos.mV[VY] += y * mSurfacep->getMetersPerGrid(); - pos.mV[VZ] = *(mDataZ + point_offset); - return pos; -} - -LLVector2 LLSurfacePatch::getTexCoords(const U32 x, const U32 y) const -{ - U32 surface_stride = mSurfacep->getGridsPerEdge(); - U32 point_offset = x + y*surface_stride; - LLVector3 pos, rel_pos; - pos = getOriginAgent(); - pos.mV[VX] += x * mSurfacep->getMetersPerGrid(); - pos.mV[VY] += y * mSurfacep->getMetersPerGrid(); - pos.mV[VZ] = *(mDataZ + point_offset); - rel_pos = pos - mSurfacep->getOriginAgent(); - rel_pos *= 1.f/surface_stride; - return LLVector2(rel_pos.mV[VX], rel_pos.mV[VY]); -} - - -void LLSurfacePatch::eval(const U32 x, const U32 y, const U32 stride, LLVector3 *vertex, LLVector3 *normal, - LLVector2 *tex0, LLVector2 *tex1) -{ - if (!mSurfacep || !mSurfacep->getRegion() || !mSurfacep->getGridsPerEdge() || !mVObjp) - { - return; // failsafe - } - llassert_always(vertex && normal && tex0 && tex1); - - U32 surface_stride = mSurfacep->getGridsPerEdge(); - U32 point_offset = x + y*surface_stride; - - *normal = getNormal(x, y); - - LLVector3 pos_agent = getOriginAgent(); - pos_agent.mV[VX] += x * mSurfacep->getMetersPerGrid(); - pos_agent.mV[VY] += y * mSurfacep->getMetersPerGrid(); - pos_agent.mV[VZ] = *(mDataZ + point_offset); - *vertex = pos_agent-mVObjp->getRegion()->getOriginAgent(); - - LLVector3 rel_pos = pos_agent - mSurfacep->getOriginAgent(); - LLVector3 tex_pos = rel_pos * (1.f/surface_stride); - tex0->mV[0] = tex_pos.mV[0]; - tex0->mV[1] = tex_pos.mV[1]; - tex1->mV[0] = mSurfacep->getRegion()->getCompositionXY(llfloor(mOriginRegion.mV[0])+x, llfloor(mOriginRegion.mV[1])+y); - - const F32 xyScale = 4.9215f*7.f; //0.93284f; - const F32 xyScaleInv = (1.f / xyScale)*(0.2222222222f); - - F32 vec[3] = { - (F32)fmod((F32)(mOriginGlobal.mdV[0] + x)*xyScaleInv, 256.f), - (F32)fmod((F32)(mOriginGlobal.mdV[1] + y)*xyScaleInv, 256.f), - 0.f - }; - F32 rand_val = llclamp(noise2(vec)* 0.75f + 0.5f, 0.f, 1.f); - tex1->mV[1] = rand_val; - - -} - - -void LLSurfacePatch::calcNormal(const U32 x, const U32 y, const U32 stride) -{ - U32 patch_width = mSurfacep->mPVArray.mPatchWidth; - U32 surface_stride = mSurfacep->getGridsPerEdge(); - - const F32 mpg = mSurfacep->getMetersPerGrid() * stride; - - S32 poffsets[2][2][2]; - poffsets[0][0][0] = x - stride; - poffsets[0][0][1] = y - stride; - - poffsets[0][1][0] = x - stride; - poffsets[0][1][1] = y + stride; - - poffsets[1][0][0] = x + stride; - poffsets[1][0][1] = y - stride; - - poffsets[1][1][0] = x + stride; - poffsets[1][1][1] = y + stride; - - const LLSurfacePatch *ppatches[2][2]; - - // LLVector3 p1, p2, p3, p4; - - ppatches[0][0] = this; - ppatches[0][1] = this; - ppatches[1][0] = this; - ppatches[1][1] = this; - - U32 i, j; - for (i = 0; i < 2; i++) - { - for (j = 0; j < 2; j++) - { - if (poffsets[i][j][0] < 0) - { - if (!ppatches[i][j]->getNeighborPatch(WEST)) - { - poffsets[i][j][0] = 0; - } - else - { - poffsets[i][j][0] += patch_width; - ppatches[i][j] = ppatches[i][j]->getNeighborPatch(WEST); - } - } - if (poffsets[i][j][1] < 0) - { - if (!ppatches[i][j]->getNeighborPatch(SOUTH)) - { - poffsets[i][j][1] = 0; - } - else - { - poffsets[i][j][1] += patch_width; - ppatches[i][j] = ppatches[i][j]->getNeighborPatch(SOUTH); - } - } - if (poffsets[i][j][0] >= (S32)patch_width) - { - if (!ppatches[i][j]->getNeighborPatch(EAST)) - { - poffsets[i][j][0] = patch_width - 1; - } - else - { - poffsets[i][j][0] -= patch_width; - ppatches[i][j] = ppatches[i][j]->getNeighborPatch(EAST); - } - } - if (poffsets[i][j][1] >= (S32)patch_width) - { - if (!ppatches[i][j]->getNeighborPatch(NORTH)) - { - poffsets[i][j][1] = patch_width - 1; - } - else - { - poffsets[i][j][1] -= patch_width; - ppatches[i][j] = ppatches[i][j]->getNeighborPatch(NORTH); - } - } - } - } - - LLVector3 p00(-mpg,-mpg, - *(ppatches[0][0]->mDataZ - + poffsets[0][0][0] - + poffsets[0][0][1]*surface_stride)); - LLVector3 p01(-mpg,+mpg, - *(ppatches[0][1]->mDataZ - + poffsets[0][1][0] - + poffsets[0][1][1]*surface_stride)); - LLVector3 p10(+mpg,-mpg, - *(ppatches[1][0]->mDataZ - + poffsets[1][0][0] - + poffsets[1][0][1]*surface_stride)); - LLVector3 p11(+mpg,+mpg, - *(ppatches[1][1]->mDataZ - + poffsets[1][1][0] - + poffsets[1][1][1]*surface_stride)); - - LLVector3 c1 = p11 - p00; - LLVector3 c2 = p01 - p10; - - LLVector3 normal = c1; - normal %= c2; - normal.normVec(); - - llassert(mDataNorm); - *(mDataNorm + surface_stride * y + x) = normal; -} - -const LLVector3 &LLSurfacePatch::getNormal(const U32 x, const U32 y) const -{ - U32 surface_stride = mSurfacep->getGridsPerEdge(); - llassert(mDataNorm); - return *(mDataNorm + surface_stride * y + x); -} - - -void LLSurfacePatch::updateCameraDistanceRegion(const LLVector3 &pos_region) -{ - if (LLPipeline::sDynamicLOD) - { - if (!gShiftFrame) - { - LLVector3 dv = pos_region; - dv -= mCenterRegion; - mVisInfo.mDistance = llmax(0.f, (F32)(dv.magVec() - mRadius))/ - llmax(LLVOSurfacePatch::sLODFactor, 0.1f); - } - } - else - { - mVisInfo.mDistance = 0.f; - } -} - -F32 LLSurfacePatch::getDistance() const -{ - return mVisInfo.mDistance; -} - - -// Called when a patch has changed its height field -// data. -void LLSurfacePatch::updateVerticalStats() -{ - if (!mDirtyZStats) - { - return; - } - - U32 grids_per_patch_edge = mSurfacep->getGridsPerPatchEdge(); - U32 grids_per_edge = mSurfacep->getGridsPerEdge(); - F32 meters_per_grid = mSurfacep->getMetersPerGrid(); - - U32 i, j, k; - F32 z, total; - - llassert(mDataZ); - z = *(mDataZ); - - mMinZ = z; - mMaxZ = z; - - k = 0; - total = 0.0f; - - // Iterate to +1 because we need to do the edges correctly. - for (j=0; j<(grids_per_patch_edge+1); j++) - { - for (i=0; i<(grids_per_patch_edge+1); i++) - { - z = *(mDataZ + i + j*grids_per_edge); - - if (z < mMinZ) - { - mMinZ = z; - } - if (z > mMaxZ) - { - mMaxZ = z; - } - total += z; - k++; - } - } - mMeanZ = total / (F32) k; - mCenterRegion.mV[VZ] = 0.5f * (mMinZ + mMaxZ); - - LLVector3 diam_vec(meters_per_grid*grids_per_patch_edge, - meters_per_grid*grids_per_patch_edge, - mMaxZ - mMinZ); - mRadius = diam_vec.magVec() * 0.5f; - - mSurfacep->mMaxZ = llmax(mMaxZ, mSurfacep->mMaxZ); - mSurfacep->mMinZ = llmin(mMinZ, mSurfacep->mMinZ); - mSurfacep->mHasZData = true; - mSurfacep->getRegion()->calculateCenterGlobal(); - - if (mVObjp) - { - mVObjp->dirtyPatch(); - } - mDirtyZStats = false; -} - - -void LLSurfacePatch::updateNormals() -{ - if (mSurfacep->mType == 'w') - { - return; - } - U32 grids_per_patch_edge = mSurfacep->getGridsPerPatchEdge(); - U32 grids_per_edge = mSurfacep->getGridsPerEdge(); - - bool dirty_patch = false; - - U32 i, j; - // update the east edge - if (mNormalsInvalid[EAST] || mNormalsInvalid[NORTHEAST] || mNormalsInvalid[SOUTHEAST]) - { - for (j = 0; j <= grids_per_patch_edge; j++) - { - calcNormal(grids_per_patch_edge, j, 2); - calcNormal(grids_per_patch_edge - 1, j, 2); - calcNormal(grids_per_patch_edge - 2, j, 2); - } - - dirty_patch = true; - } - - // update the north edge - if (mNormalsInvalid[NORTHEAST] || mNormalsInvalid[NORTH] || mNormalsInvalid[NORTHWEST]) - { - for (i = 0; i <= grids_per_patch_edge; i++) - { - calcNormal(i, grids_per_patch_edge, 2); - calcNormal(i, grids_per_patch_edge - 1, 2); - calcNormal(i, grids_per_patch_edge - 2, 2); - } - - dirty_patch = true; - } - - // update the west edge - if (mNormalsInvalid[NORTHWEST] || mNormalsInvalid[WEST] || mNormalsInvalid[SOUTHWEST]) - { - for (j = 0; j < grids_per_patch_edge; j++) - { - calcNormal(0, j, 2); - calcNormal(1, j, 2); - } - dirty_patch = true; - } - - // update the south edge - if (mNormalsInvalid[SOUTHWEST] || mNormalsInvalid[SOUTH] || mNormalsInvalid[SOUTHEAST]) - { - for (i = 0; i < grids_per_patch_edge; i++) - { - calcNormal(i, 0, 2); - calcNormal(i, 1, 2); - } - dirty_patch = true; - } - - // Invalidating the northeast corner is different, because depending on what the adjacent neighbors are, - // we'll want to do different things. - if (mNormalsInvalid[NORTHEAST]) - { - if (!getNeighborPatch(NORTHEAST)) - { - if (!getNeighborPatch(NORTH)) - { - if (!getNeighborPatch(EAST)) - { - // No north or east neighbors. Pull from the diagonal in your own patch. - *(mDataZ + grids_per_patch_edge + grids_per_patch_edge*grids_per_edge) = - *(mDataZ + grids_per_patch_edge - 1 + (grids_per_patch_edge - 1)*grids_per_edge); - } - else - { - if (getNeighborPatch(EAST)->getHasReceivedData()) - { - // East, but not north. Pull from your east neighbor's northwest point. - *(mDataZ + grids_per_patch_edge + grids_per_patch_edge*grids_per_edge) = - *(getNeighborPatch(EAST)->mDataZ + (grids_per_patch_edge - 1)*grids_per_edge); - } - else - { - *(mDataZ + grids_per_patch_edge + grids_per_patch_edge*grids_per_edge) = - *(mDataZ + grids_per_patch_edge - 1 + (grids_per_patch_edge - 1)*grids_per_edge); - } - } - } - else - { - // We have a north. - if (getNeighborPatch(EAST)) - { - // North and east neighbors, but not northeast. - // Pull from diagonal in your own patch. - *(mDataZ + grids_per_patch_edge + grids_per_patch_edge*grids_per_edge) = - *(mDataZ + grids_per_patch_edge - 1 + (grids_per_patch_edge - 1)*grids_per_edge); - } - else - { - if (getNeighborPatch(NORTH)->getHasReceivedData()) - { - // North, but not east. Pull from your north neighbor's southeast corner. - *(mDataZ + grids_per_patch_edge + grids_per_patch_edge*grids_per_edge) = - *(getNeighborPatch(NORTH)->mDataZ + (grids_per_patch_edge - 1)); - } - else - { - *(mDataZ + grids_per_patch_edge + grids_per_patch_edge*grids_per_edge) = - *(mDataZ + grids_per_patch_edge - 1 + (grids_per_patch_edge - 1)*grids_per_edge); - } - } - } - } - else if (getNeighborPatch(NORTHEAST)->mSurfacep != mSurfacep) - { - if ( - (!getNeighborPatch(NORTH) || (getNeighborPatch(NORTH)->mSurfacep != mSurfacep)) - && - (!getNeighborPatch(EAST) || (getNeighborPatch(EAST)->mSurfacep != mSurfacep))) - { - *(mDataZ + grids_per_patch_edge + grids_per_patch_edge*grids_per_edge) = - *(getNeighborPatch(NORTHEAST)->mDataZ); - } - } - else - { - // We've got a northeast patch in the same surface. - // The z and normals will be handled by that patch. - } - calcNormal(grids_per_patch_edge, grids_per_patch_edge, 2); - calcNormal(grids_per_patch_edge, grids_per_patch_edge - 1, 2); - calcNormal(grids_per_patch_edge - 1, grids_per_patch_edge, 2); - calcNormal(grids_per_patch_edge - 1, grids_per_patch_edge - 1, 2); - dirty_patch = true; - } - - // update the middle normals - if (mNormalsInvalid[MIDDLE]) - { - for (j=2; j < grids_per_patch_edge - 2; j++) - { - for (i=2; i < grids_per_patch_edge - 2; i++) - { - calcNormal(i, j, 2); - } - } - dirty_patch = true; - } - - if (dirty_patch) - { - mSurfacep->dirtySurfacePatch(this); - } - - for (i = 0; i < 9; i++) - { - mNormalsInvalid[i] = false; - } -} - -void LLSurfacePatch::updateEastEdge() -{ - U32 grids_per_patch_edge = mSurfacep->getGridsPerPatchEdge(); - U32 grids_per_edge = mSurfacep->getGridsPerEdge(); - - U32 j, k; - F32 *west_surface, *east_surface; - - if (!getNeighborPatch(EAST)) - { - west_surface = mDataZ + grids_per_patch_edge; - east_surface = mDataZ + grids_per_patch_edge - 1; - } - else if (mConnectedEdge & EAST_EDGE) - { - west_surface = mDataZ + grids_per_patch_edge; - east_surface = getNeighborPatch(EAST)->mDataZ; - } - else - { - return; - } - - // If patchp is on the east edge of its surface, then we update the east - // side buffer - for (j=0; j < grids_per_patch_edge; j++) - { - k = j * grids_per_edge; - *(west_surface + k) = *(east_surface + k); // update buffer Z - } -} - - -void LLSurfacePatch::updateNorthEdge() -{ - U32 grids_per_patch_edge = mSurfacep->getGridsPerPatchEdge(); - U32 grids_per_edge = mSurfacep->getGridsPerEdge(); - - U32 i; - F32 *south_surface, *north_surface; - - if (!getNeighborPatch(NORTH)) - { - south_surface = mDataZ + grids_per_patch_edge*grids_per_edge; - north_surface = mDataZ + (grids_per_patch_edge - 1) * grids_per_edge; - } - else if (mConnectedEdge & NORTH_EDGE) - { - south_surface = mDataZ + grids_per_patch_edge*grids_per_edge; - north_surface = getNeighborPatch(NORTH)->mDataZ; - } - else - { - return; - } - - // Update patchp's north edge ... - for (i=0; igetMetersPerGrid(); - F32 grids_per_patch_edge = (F32)getSurface()->getGridsPerPatchEdge(); - - if ((!getNeighborPatch(EAST) || getNeighborPatch(EAST)->getHasReceivedData()) - && (!getNeighborPatch(WEST) || getNeighborPatch(WEST)->getHasReceivedData()) - && (!getNeighborPatch(SOUTH) || getNeighborPatch(SOUTH)->getHasReceivedData()) - && (!getNeighborPatch(NORTH) || getNeighborPatch(NORTH)->getHasReceivedData())) - { - LLViewerRegion *regionp = getSurface()->getRegion(); - LLVector3d origin_region = getOriginGlobal() - getSurface()->getOriginGlobal(); - - // Have to figure out a better way to deal with these edge conditions... - LLVLComposition* comp = regionp->getComposition(); - if (!mHeightsGenerated) - { - F32 patch_size = meters_per_grid*(grids_per_patch_edge+1); - if (comp->generateHeights((F32)origin_region[VX], (F32)origin_region[VY], - patch_size, patch_size)) - { - mHeightsGenerated = true; - } - else - { - return false; - } - } - - if (comp->generateComposition()) - { - if (mVObjp) - { - mVObjp->dirtyGeom(); - gPipeline.markGLRebuild(mVObjp); - return !mSTexUpdate; - } - } - } - return false; - } - else - { - return true; - } -} - -void LLSurfacePatch::updateGL() -{ - LL_PROFILE_ZONE_SCOPED - F32 meters_per_grid = getSurface()->getMetersPerGrid(); - F32 grids_per_patch_edge = (F32)getSurface()->getGridsPerPatchEdge(); - - LLViewerRegion *regionp = getSurface()->getRegion(); - LLVector3d origin_region = getOriginGlobal() - getSurface()->getOriginGlobal(); - - LLVLComposition* comp = regionp->getComposition(); - - updateCompositionStats(); - F32 tex_patch_size = meters_per_grid*grids_per_patch_edge; - if (comp->generateTexture((F32)origin_region[VX], (F32)origin_region[VY], - tex_patch_size, tex_patch_size)) - { - mSTexUpdate = false; - - // Also generate the water texture - mSurfacep->generateWaterTexture((F32)origin_region.mdV[VX], (F32)origin_region.mdV[VY], - tex_patch_size, tex_patch_size); - } -} - -void LLSurfacePatch::dirtyZ() -{ - mSTexUpdate = true; - - // Invalidate all normals in this patch - U32 i; - for (i = 0; i < 9; i++) - { - mNormalsInvalid[i] = true; - } - - // Invalidate normals in this and neighboring patches - for (i = 0; i < 8; i++) - { - if (getNeighborPatch(i)) - { - getNeighborPatch(i)->mNormalsInvalid[gDirOpposite[i]] = true; - getNeighborPatch(i)->dirty(); - if (i < 4) - { - getNeighborPatch(i)->mNormalsInvalid[gDirAdjacent[gDirOpposite[i]][0]] = true; - getNeighborPatch(i)->mNormalsInvalid[gDirAdjacent[gDirOpposite[i]][1]] = true; - } - } - } - - dirty(); - mLastUpdateTime = gFrameTime; -} - - -const U64 &LLSurfacePatch::getLastUpdateTime() const -{ - return mLastUpdateTime; -} - -F32 LLSurfacePatch::getMaxZ() const -{ - return mMaxZ; -} - -F32 LLSurfacePatch::getMinZ() const -{ - return mMinZ; -} - -void LLSurfacePatch::setOriginGlobal(const LLVector3d &origin_global) -{ - mOriginGlobal = origin_global; - - LLVector3 origin_region; - origin_region.setVec(mOriginGlobal - mSurfacep->getOriginGlobal()); - - mOriginRegion = origin_region; - mCenterRegion.mV[VX] = origin_region.mV[VX] + 0.5f*mSurfacep->getGridsPerPatchEdge()*mSurfacep->getMetersPerGrid(); - mCenterRegion.mV[VY] = origin_region.mV[VY] + 0.5f*mSurfacep->getGridsPerPatchEdge()*mSurfacep->getMetersPerGrid(); - - mVisInfo.mbIsVisible = false; - mVisInfo.mDistance = 512.0f; - mVisInfo.mRenderLevel = 0; - mVisInfo.mRenderStride = mSurfacep->getGridsPerPatchEdge(); - -} - -void LLSurfacePatch::connectNeighbor(LLSurfacePatch *neighbor_patchp, const U32 direction) -{ - llassert(neighbor_patchp); - mNormalsInvalid[direction] = true; - neighbor_patchp->mNormalsInvalid[gDirOpposite[direction]] = true; - - setNeighborPatch(direction, neighbor_patchp); - neighbor_patchp->setNeighborPatch(gDirOpposite[direction], this); - - if (EAST == direction) - { - mConnectedEdge |= EAST_EDGE; - neighbor_patchp->mConnectedEdge |= WEST_EDGE; - } - else if (NORTH == direction) - { - mConnectedEdge |= NORTH_EDGE; - neighbor_patchp->mConnectedEdge |= SOUTH_EDGE; - } - else if (WEST == direction) - { - mConnectedEdge |= WEST_EDGE; - neighbor_patchp->mConnectedEdge |= EAST_EDGE; - } - else if (SOUTH == direction) - { - mConnectedEdge |= SOUTH_EDGE; - neighbor_patchp->mConnectedEdge |= NORTH_EDGE; - } -} - -void LLSurfacePatch::updateVisibility() -{ - if (mVObjp.isNull()) - { - return; - } - - const F32 DEFAULT_DELTA_ANGLE = (0.15f); - U32 old_render_stride, max_render_stride; - U32 new_render_level; - F32 stride_per_distance = DEFAULT_DELTA_ANGLE / mSurfacep->getMetersPerGrid(); - U32 grids_per_patch_edge = mSurfacep->getGridsPerPatchEdge(); - - LLVector4a center; - center.load3( (mCenterRegion + mSurfacep->getOriginAgent()).mV); - LLVector4a radius; - radius.splat(mRadius); - - // sphere in frustum on global coordinates - if (LLViewerCamera::getInstance()->AABBInFrustumNoFarClip(center, radius)) - { - // We now need to calculate the render stride based on patchp's distance - // from LLCamera render_stride is governed by a relation something like this... - // - // delta_angle * patch.distance - // render_stride <= ---------------------------------------- - // mMetersPerGrid - // - // where 'delta_angle' is the desired solid angle of the average polgon on a patch. - // - // Any render_stride smaller than the RHS would be 'satisfactory'. Smaller - // strides give more resolution, but efficiency suggests that we use the largest - // of the render_strides that obey the relation. Flexibility is achieved by - // modulating 'delta_angle' until we have an acceptable number of triangles. - - old_render_stride = mVisInfo.mRenderStride; - - // Calculate the render_stride using information in agent - max_render_stride = lltrunc(mVisInfo.mDistance * stride_per_distance); - max_render_stride = llmin(max_render_stride , 2*grids_per_patch_edge); - - // We only use render_strides that are powers of two, so we use look-up tables to figure out - // the render_level and corresponding render_stride - new_render_level = mVisInfo.mRenderLevel = mSurfacep->getRenderLevel(max_render_stride); - mVisInfo.mRenderStride = mSurfacep->getRenderStride(new_render_level); - - if ((mVisInfo.mRenderStride != old_render_stride)) - // The reason we check !mbIsVisible is because non-visible patches normals - // are not updated when their data is changed. When this changes we can get - // rid of mbIsVisible altogether. - { - if (mVObjp) - { - mVObjp->dirtyGeom(); - if (getNeighborPatch(WEST)) - { - getNeighborPatch(WEST)->mVObjp->dirtyGeom(); - } - if (getNeighborPatch(SOUTH)) - { - getNeighborPatch(SOUTH)->mVObjp->dirtyGeom(); - } - } - } - mVisInfo.mbIsVisible = true; - } - else - { - mVisInfo.mbIsVisible = false; - } -} - - -const LLVector3d &LLSurfacePatch::getOriginGlobal() const -{ - return mOriginGlobal; -} - -LLVector3 LLSurfacePatch::getOriginAgent() const -{ - return gAgent.getPosAgentFromGlobal(mOriginGlobal); -} - -bool LLSurfacePatch::getVisible() const -{ - return mVisInfo.mbIsVisible; -} - -U32 LLSurfacePatch::getRenderStride() const -{ - return mVisInfo.mRenderStride; -} - -S32 LLSurfacePatch::getRenderLevel() const -{ - return mVisInfo.mRenderLevel; -} - -void LLSurfacePatch::setHasReceivedData() -{ - mHasReceivedData = true; -} - -bool LLSurfacePatch::getHasReceivedData() const -{ - return mHasReceivedData; -} - -const LLVector3 &LLSurfacePatch::getCenterRegion() const -{ - return mCenterRegion; -} - - -void LLSurfacePatch::updateCompositionStats() -{ - LLViewerLayer *vlp = mSurfacep->getRegion()->getComposition(); - - F32 x, y, width, height, mpg, min, mean, max; - - LLVector3 origin = getOriginAgent() - mSurfacep->getOriginAgent(); - mpg = mSurfacep->getMetersPerGrid(); - x = origin.mV[VX]; - y = origin.mV[VY]; - width = mpg*(mSurfacep->getGridsPerPatchEdge()+1); - height = mpg*(mSurfacep->getGridsPerPatchEdge()+1); - - mean = 0.f; - min = vlp->getValueScaled(x, y); - max= min; - U32 count = 0; - F32 i, j; - for (j = 0; j < height; j += mpg) - { - for (i = 0; i < width; i += mpg) - { - F32 comp = vlp->getValueScaled(x + i, y + j); - mean += comp; - min = llmin(min, comp); - max = llmax(max, comp); - count++; - } - } - mean /= count; - - mMinComposition = min; - mMeanComposition = mean; - mMaxComposition = max; -} - -F32 LLSurfacePatch::getMeanComposition() const -{ - return mMeanComposition; -} - -F32 LLSurfacePatch::getMinComposition() const -{ - return mMinComposition; -} - -F32 LLSurfacePatch::getMaxComposition() const -{ - return mMaxComposition; -} - -void LLSurfacePatch::setNeighborPatch(const U32 direction, LLSurfacePatch *neighborp) -{ - mNeighborPatches[direction] = neighborp; - mNormalsInvalid[direction] = true; - if (direction < 4) - { - mNormalsInvalid[gDirAdjacent[direction][0]] = true; - mNormalsInvalid[gDirAdjacent[direction][1]] = true; - } -} - -LLSurfacePatch *LLSurfacePatch::getNeighborPatch(const U32 direction) const -{ - return mNeighborPatches[direction]; -} - -void LLSurfacePatch::clearVObj() -{ - mVObjp = NULL; -} +/** + * @file llsurfacepatch.cpp + * @brief LLSurfacePatch class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llsurfacepatch.h" +#include "llpatchvertexarray.h" +#include "llviewerobjectlist.h" +#include "llvosurfacepatch.h" +#include "llsurface.h" +#include "pipeline.h" +#include "llagent.h" +#include "llsky.h" +#include "llviewercamera.h" + +// For getting composition values +#include "llviewerregion.h" +#include "llvlcomposition.h" +#include "lldrawpool.h" +#include "noise.h" + +extern bool gShiftFrame; +extern U64MicrosecondsImplicit gFrameTime; +extern LLPipeline gPipeline; + +LLSurfacePatch::LLSurfacePatch() +: mHasReceivedData(false), + mSTexUpdate(false), + mDirty(false), + mDirtyZStats(true), + mHeightsGenerated(false), + mDataOffset(0), + mDataZ(NULL), + mDataNorm(NULL), + mVObjp(NULL), + mOriginRegion(0.f, 0.f, 0.f), + mCenterRegion(0.f, 0.f, 0.f), + mMinZ(0.f), + mMaxZ(0.f), + mMeanZ(0.f), + mRadius(0.f), + mMinComposition(0.f), + mMaxComposition(0.f), + mMeanComposition(0.f), + // This flag is used to communicate between adjacent surfaces and is + // set to non-zero values by higher classes. + mConnectedEdge(NO_EDGE), + mLastUpdateTime(0), + mSurfacep(NULL) +{ + S32 i; + for (i = 0; i < 8; i++) + { + setNeighborPatch(i, NULL); + } + for (i = 0; i < 9; i++) + { + mNormalsInvalid[i] = true; + } +} + + +LLSurfacePatch::~LLSurfacePatch() +{ + mVObjp = NULL; +} + + +void LLSurfacePatch::dirty() +{ + // These are outside of the loop in case we're still waiting for a dirty from the + // texture being updated... + if (mVObjp) + { + mVObjp->dirtyGeom(); + } + else + { + LL_WARNS("Terrain") << "No viewer object for this surface patch!" << LL_ENDL; + } + + mDirtyZStats = true; + mHeightsGenerated = false; + + if (!mDirty) + { + mDirty = true; + mSurfacep->dirtySurfacePatch(this); + } +} + + +void LLSurfacePatch::setSurface(LLSurface *surfacep) +{ + mSurfacep = surfacep; + if (mVObjp == (LLVOSurfacePatch *)NULL) + { + llassert(mSurfacep->mType == 'l'); + + mVObjp = (LLVOSurfacePatch *)gObjectList.createObjectViewer(LLViewerObject::LL_VO_SURFACE_PATCH, mSurfacep->getRegion()); + mVObjp->setPatch(this); + mVObjp->setPositionRegion(mCenterRegion); + gPipeline.createObject(mVObjp); + } +} + +void LLSurfacePatch::disconnectNeighbor(LLSurface *surfacep) +{ + U32 i; + for (i = 0; i < 8; i++) + { + if (getNeighborPatch(i)) + { + if (getNeighborPatch(i)->mSurfacep == surfacep) + { + setNeighborPatch(i, NULL); + mNormalsInvalid[i] = true; + } + } + } + + // Clean up connected edges + if (getNeighborPatch(EAST)) + { + if (getNeighborPatch(EAST)->mSurfacep == surfacep) + { + mConnectedEdge &= ~EAST_EDGE; + } + } + if (getNeighborPatch(NORTH)) + { + if (getNeighborPatch(NORTH)->mSurfacep == surfacep) + { + mConnectedEdge &= ~NORTH_EDGE; + } + } + if (getNeighborPatch(WEST)) + { + if (getNeighborPatch(WEST)->mSurfacep == surfacep) + { + mConnectedEdge &= ~WEST_EDGE; + } + } + if (getNeighborPatch(SOUTH)) + { + if (getNeighborPatch(SOUTH)->mSurfacep == surfacep) + { + mConnectedEdge &= ~SOUTH_EDGE; + } + } +} + +LLVector3 LLSurfacePatch::getPointAgent(const U32 x, const U32 y) const +{ + U32 surface_stride = mSurfacep->getGridsPerEdge(); + U32 point_offset = x + y*surface_stride; + LLVector3 pos; + pos = getOriginAgent(); + pos.mV[VX] += x * mSurfacep->getMetersPerGrid(); + pos.mV[VY] += y * mSurfacep->getMetersPerGrid(); + pos.mV[VZ] = *(mDataZ + point_offset); + return pos; +} + +LLVector2 LLSurfacePatch::getTexCoords(const U32 x, const U32 y) const +{ + U32 surface_stride = mSurfacep->getGridsPerEdge(); + U32 point_offset = x + y*surface_stride; + LLVector3 pos, rel_pos; + pos = getOriginAgent(); + pos.mV[VX] += x * mSurfacep->getMetersPerGrid(); + pos.mV[VY] += y * mSurfacep->getMetersPerGrid(); + pos.mV[VZ] = *(mDataZ + point_offset); + rel_pos = pos - mSurfacep->getOriginAgent(); + rel_pos *= 1.f/surface_stride; + return LLVector2(rel_pos.mV[VX], rel_pos.mV[VY]); +} + + +void LLSurfacePatch::eval(const U32 x, const U32 y, const U32 stride, LLVector3 *vertex, LLVector3 *normal, + LLVector2 *tex0, LLVector2 *tex1) +{ + if (!mSurfacep || !mSurfacep->getRegion() || !mSurfacep->getGridsPerEdge() || !mVObjp) + { + return; // failsafe + } + llassert_always(vertex && normal && tex0 && tex1); + + U32 surface_stride = mSurfacep->getGridsPerEdge(); + U32 point_offset = x + y*surface_stride; + + *normal = getNormal(x, y); + + LLVector3 pos_agent = getOriginAgent(); + pos_agent.mV[VX] += x * mSurfacep->getMetersPerGrid(); + pos_agent.mV[VY] += y * mSurfacep->getMetersPerGrid(); + pos_agent.mV[VZ] = *(mDataZ + point_offset); + *vertex = pos_agent-mVObjp->getRegion()->getOriginAgent(); + + LLVector3 rel_pos = pos_agent - mSurfacep->getOriginAgent(); + LLVector3 tex_pos = rel_pos * (1.f/surface_stride); + tex0->mV[0] = tex_pos.mV[0]; + tex0->mV[1] = tex_pos.mV[1]; + tex1->mV[0] = mSurfacep->getRegion()->getCompositionXY(llfloor(mOriginRegion.mV[0])+x, llfloor(mOriginRegion.mV[1])+y); + + const F32 xyScale = 4.9215f*7.f; //0.93284f; + const F32 xyScaleInv = (1.f / xyScale)*(0.2222222222f); + + F32 vec[3] = { + (F32)fmod((F32)(mOriginGlobal.mdV[0] + x)*xyScaleInv, 256.f), + (F32)fmod((F32)(mOriginGlobal.mdV[1] + y)*xyScaleInv, 256.f), + 0.f + }; + F32 rand_val = llclamp(noise2(vec)* 0.75f + 0.5f, 0.f, 1.f); + tex1->mV[1] = rand_val; + + +} + + +void LLSurfacePatch::calcNormal(const U32 x, const U32 y, const U32 stride) +{ + U32 patch_width = mSurfacep->mPVArray.mPatchWidth; + U32 surface_stride = mSurfacep->getGridsPerEdge(); + + const F32 mpg = mSurfacep->getMetersPerGrid() * stride; + + S32 poffsets[2][2][2]; + poffsets[0][0][0] = x - stride; + poffsets[0][0][1] = y - stride; + + poffsets[0][1][0] = x - stride; + poffsets[0][1][1] = y + stride; + + poffsets[1][0][0] = x + stride; + poffsets[1][0][1] = y - stride; + + poffsets[1][1][0] = x + stride; + poffsets[1][1][1] = y + stride; + + const LLSurfacePatch *ppatches[2][2]; + + // LLVector3 p1, p2, p3, p4; + + ppatches[0][0] = this; + ppatches[0][1] = this; + ppatches[1][0] = this; + ppatches[1][1] = this; + + U32 i, j; + for (i = 0; i < 2; i++) + { + for (j = 0; j < 2; j++) + { + if (poffsets[i][j][0] < 0) + { + if (!ppatches[i][j]->getNeighborPatch(WEST)) + { + poffsets[i][j][0] = 0; + } + else + { + poffsets[i][j][0] += patch_width; + ppatches[i][j] = ppatches[i][j]->getNeighborPatch(WEST); + } + } + if (poffsets[i][j][1] < 0) + { + if (!ppatches[i][j]->getNeighborPatch(SOUTH)) + { + poffsets[i][j][1] = 0; + } + else + { + poffsets[i][j][1] += patch_width; + ppatches[i][j] = ppatches[i][j]->getNeighborPatch(SOUTH); + } + } + if (poffsets[i][j][0] >= (S32)patch_width) + { + if (!ppatches[i][j]->getNeighborPatch(EAST)) + { + poffsets[i][j][0] = patch_width - 1; + } + else + { + poffsets[i][j][0] -= patch_width; + ppatches[i][j] = ppatches[i][j]->getNeighborPatch(EAST); + } + } + if (poffsets[i][j][1] >= (S32)patch_width) + { + if (!ppatches[i][j]->getNeighborPatch(NORTH)) + { + poffsets[i][j][1] = patch_width - 1; + } + else + { + poffsets[i][j][1] -= patch_width; + ppatches[i][j] = ppatches[i][j]->getNeighborPatch(NORTH); + } + } + } + } + + LLVector3 p00(-mpg,-mpg, + *(ppatches[0][0]->mDataZ + + poffsets[0][0][0] + + poffsets[0][0][1]*surface_stride)); + LLVector3 p01(-mpg,+mpg, + *(ppatches[0][1]->mDataZ + + poffsets[0][1][0] + + poffsets[0][1][1]*surface_stride)); + LLVector3 p10(+mpg,-mpg, + *(ppatches[1][0]->mDataZ + + poffsets[1][0][0] + + poffsets[1][0][1]*surface_stride)); + LLVector3 p11(+mpg,+mpg, + *(ppatches[1][1]->mDataZ + + poffsets[1][1][0] + + poffsets[1][1][1]*surface_stride)); + + LLVector3 c1 = p11 - p00; + LLVector3 c2 = p01 - p10; + + LLVector3 normal = c1; + normal %= c2; + normal.normVec(); + + llassert(mDataNorm); + *(mDataNorm + surface_stride * y + x) = normal; +} + +const LLVector3 &LLSurfacePatch::getNormal(const U32 x, const U32 y) const +{ + U32 surface_stride = mSurfacep->getGridsPerEdge(); + llassert(mDataNorm); + return *(mDataNorm + surface_stride * y + x); +} + + +void LLSurfacePatch::updateCameraDistanceRegion(const LLVector3 &pos_region) +{ + if (LLPipeline::sDynamicLOD) + { + if (!gShiftFrame) + { + LLVector3 dv = pos_region; + dv -= mCenterRegion; + mVisInfo.mDistance = llmax(0.f, (F32)(dv.magVec() - mRadius))/ + llmax(LLVOSurfacePatch::sLODFactor, 0.1f); + } + } + else + { + mVisInfo.mDistance = 0.f; + } +} + +F32 LLSurfacePatch::getDistance() const +{ + return mVisInfo.mDistance; +} + + +// Called when a patch has changed its height field +// data. +void LLSurfacePatch::updateVerticalStats() +{ + if (!mDirtyZStats) + { + return; + } + + U32 grids_per_patch_edge = mSurfacep->getGridsPerPatchEdge(); + U32 grids_per_edge = mSurfacep->getGridsPerEdge(); + F32 meters_per_grid = mSurfacep->getMetersPerGrid(); + + U32 i, j, k; + F32 z, total; + + llassert(mDataZ); + z = *(mDataZ); + + mMinZ = z; + mMaxZ = z; + + k = 0; + total = 0.0f; + + // Iterate to +1 because we need to do the edges correctly. + for (j=0; j<(grids_per_patch_edge+1); j++) + { + for (i=0; i<(grids_per_patch_edge+1); i++) + { + z = *(mDataZ + i + j*grids_per_edge); + + if (z < mMinZ) + { + mMinZ = z; + } + if (z > mMaxZ) + { + mMaxZ = z; + } + total += z; + k++; + } + } + mMeanZ = total / (F32) k; + mCenterRegion.mV[VZ] = 0.5f * (mMinZ + mMaxZ); + + LLVector3 diam_vec(meters_per_grid*grids_per_patch_edge, + meters_per_grid*grids_per_patch_edge, + mMaxZ - mMinZ); + mRadius = diam_vec.magVec() * 0.5f; + + mSurfacep->mMaxZ = llmax(mMaxZ, mSurfacep->mMaxZ); + mSurfacep->mMinZ = llmin(mMinZ, mSurfacep->mMinZ); + mSurfacep->mHasZData = true; + mSurfacep->getRegion()->calculateCenterGlobal(); + + if (mVObjp) + { + mVObjp->dirtyPatch(); + } + mDirtyZStats = false; +} + + +void LLSurfacePatch::updateNormals() +{ + if (mSurfacep->mType == 'w') + { + return; + } + U32 grids_per_patch_edge = mSurfacep->getGridsPerPatchEdge(); + U32 grids_per_edge = mSurfacep->getGridsPerEdge(); + + bool dirty_patch = false; + + U32 i, j; + // update the east edge + if (mNormalsInvalid[EAST] || mNormalsInvalid[NORTHEAST] || mNormalsInvalid[SOUTHEAST]) + { + for (j = 0; j <= grids_per_patch_edge; j++) + { + calcNormal(grids_per_patch_edge, j, 2); + calcNormal(grids_per_patch_edge - 1, j, 2); + calcNormal(grids_per_patch_edge - 2, j, 2); + } + + dirty_patch = true; + } + + // update the north edge + if (mNormalsInvalid[NORTHEAST] || mNormalsInvalid[NORTH] || mNormalsInvalid[NORTHWEST]) + { + for (i = 0; i <= grids_per_patch_edge; i++) + { + calcNormal(i, grids_per_patch_edge, 2); + calcNormal(i, grids_per_patch_edge - 1, 2); + calcNormal(i, grids_per_patch_edge - 2, 2); + } + + dirty_patch = true; + } + + // update the west edge + if (mNormalsInvalid[NORTHWEST] || mNormalsInvalid[WEST] || mNormalsInvalid[SOUTHWEST]) + { + for (j = 0; j < grids_per_patch_edge; j++) + { + calcNormal(0, j, 2); + calcNormal(1, j, 2); + } + dirty_patch = true; + } + + // update the south edge + if (mNormalsInvalid[SOUTHWEST] || mNormalsInvalid[SOUTH] || mNormalsInvalid[SOUTHEAST]) + { + for (i = 0; i < grids_per_patch_edge; i++) + { + calcNormal(i, 0, 2); + calcNormal(i, 1, 2); + } + dirty_patch = true; + } + + // Invalidating the northeast corner is different, because depending on what the adjacent neighbors are, + // we'll want to do different things. + if (mNormalsInvalid[NORTHEAST]) + { + if (!getNeighborPatch(NORTHEAST)) + { + if (!getNeighborPatch(NORTH)) + { + if (!getNeighborPatch(EAST)) + { + // No north or east neighbors. Pull from the diagonal in your own patch. + *(mDataZ + grids_per_patch_edge + grids_per_patch_edge*grids_per_edge) = + *(mDataZ + grids_per_patch_edge - 1 + (grids_per_patch_edge - 1)*grids_per_edge); + } + else + { + if (getNeighborPatch(EAST)->getHasReceivedData()) + { + // East, but not north. Pull from your east neighbor's northwest point. + *(mDataZ + grids_per_patch_edge + grids_per_patch_edge*grids_per_edge) = + *(getNeighborPatch(EAST)->mDataZ + (grids_per_patch_edge - 1)*grids_per_edge); + } + else + { + *(mDataZ + grids_per_patch_edge + grids_per_patch_edge*grids_per_edge) = + *(mDataZ + grids_per_patch_edge - 1 + (grids_per_patch_edge - 1)*grids_per_edge); + } + } + } + else + { + // We have a north. + if (getNeighborPatch(EAST)) + { + // North and east neighbors, but not northeast. + // Pull from diagonal in your own patch. + *(mDataZ + grids_per_patch_edge + grids_per_patch_edge*grids_per_edge) = + *(mDataZ + grids_per_patch_edge - 1 + (grids_per_patch_edge - 1)*grids_per_edge); + } + else + { + if (getNeighborPatch(NORTH)->getHasReceivedData()) + { + // North, but not east. Pull from your north neighbor's southeast corner. + *(mDataZ + grids_per_patch_edge + grids_per_patch_edge*grids_per_edge) = + *(getNeighborPatch(NORTH)->mDataZ + (grids_per_patch_edge - 1)); + } + else + { + *(mDataZ + grids_per_patch_edge + grids_per_patch_edge*grids_per_edge) = + *(mDataZ + grids_per_patch_edge - 1 + (grids_per_patch_edge - 1)*grids_per_edge); + } + } + } + } + else if (getNeighborPatch(NORTHEAST)->mSurfacep != mSurfacep) + { + if ( + (!getNeighborPatch(NORTH) || (getNeighborPatch(NORTH)->mSurfacep != mSurfacep)) + && + (!getNeighborPatch(EAST) || (getNeighborPatch(EAST)->mSurfacep != mSurfacep))) + { + *(mDataZ + grids_per_patch_edge + grids_per_patch_edge*grids_per_edge) = + *(getNeighborPatch(NORTHEAST)->mDataZ); + } + } + else + { + // We've got a northeast patch in the same surface. + // The z and normals will be handled by that patch. + } + calcNormal(grids_per_patch_edge, grids_per_patch_edge, 2); + calcNormal(grids_per_patch_edge, grids_per_patch_edge - 1, 2); + calcNormal(grids_per_patch_edge - 1, grids_per_patch_edge, 2); + calcNormal(grids_per_patch_edge - 1, grids_per_patch_edge - 1, 2); + dirty_patch = true; + } + + // update the middle normals + if (mNormalsInvalid[MIDDLE]) + { + for (j=2; j < grids_per_patch_edge - 2; j++) + { + for (i=2; i < grids_per_patch_edge - 2; i++) + { + calcNormal(i, j, 2); + } + } + dirty_patch = true; + } + + if (dirty_patch) + { + mSurfacep->dirtySurfacePatch(this); + } + + for (i = 0; i < 9; i++) + { + mNormalsInvalid[i] = false; + } +} + +void LLSurfacePatch::updateEastEdge() +{ + U32 grids_per_patch_edge = mSurfacep->getGridsPerPatchEdge(); + U32 grids_per_edge = mSurfacep->getGridsPerEdge(); + + U32 j, k; + F32 *west_surface, *east_surface; + + if (!getNeighborPatch(EAST)) + { + west_surface = mDataZ + grids_per_patch_edge; + east_surface = mDataZ + grids_per_patch_edge - 1; + } + else if (mConnectedEdge & EAST_EDGE) + { + west_surface = mDataZ + grids_per_patch_edge; + east_surface = getNeighborPatch(EAST)->mDataZ; + } + else + { + return; + } + + // If patchp is on the east edge of its surface, then we update the east + // side buffer + for (j=0; j < grids_per_patch_edge; j++) + { + k = j * grids_per_edge; + *(west_surface + k) = *(east_surface + k); // update buffer Z + } +} + + +void LLSurfacePatch::updateNorthEdge() +{ + U32 grids_per_patch_edge = mSurfacep->getGridsPerPatchEdge(); + U32 grids_per_edge = mSurfacep->getGridsPerEdge(); + + U32 i; + F32 *south_surface, *north_surface; + + if (!getNeighborPatch(NORTH)) + { + south_surface = mDataZ + grids_per_patch_edge*grids_per_edge; + north_surface = mDataZ + (grids_per_patch_edge - 1) * grids_per_edge; + } + else if (mConnectedEdge & NORTH_EDGE) + { + south_surface = mDataZ + grids_per_patch_edge*grids_per_edge; + north_surface = getNeighborPatch(NORTH)->mDataZ; + } + else + { + return; + } + + // Update patchp's north edge ... + for (i=0; igetMetersPerGrid(); + F32 grids_per_patch_edge = (F32)getSurface()->getGridsPerPatchEdge(); + + if ((!getNeighborPatch(EAST) || getNeighborPatch(EAST)->getHasReceivedData()) + && (!getNeighborPatch(WEST) || getNeighborPatch(WEST)->getHasReceivedData()) + && (!getNeighborPatch(SOUTH) || getNeighborPatch(SOUTH)->getHasReceivedData()) + && (!getNeighborPatch(NORTH) || getNeighborPatch(NORTH)->getHasReceivedData())) + { + LLViewerRegion *regionp = getSurface()->getRegion(); + LLVector3d origin_region = getOriginGlobal() - getSurface()->getOriginGlobal(); + + // Have to figure out a better way to deal with these edge conditions... + LLVLComposition* comp = regionp->getComposition(); + if (!mHeightsGenerated) + { + F32 patch_size = meters_per_grid*(grids_per_patch_edge+1); + if (comp->generateHeights((F32)origin_region[VX], (F32)origin_region[VY], + patch_size, patch_size)) + { + mHeightsGenerated = true; + } + else + { + return false; + } + } + + if (comp->generateComposition()) + { + if (mVObjp) + { + mVObjp->dirtyGeom(); + gPipeline.markGLRebuild(mVObjp); + return !mSTexUpdate; + } + } + } + return false; + } + else + { + return true; + } +} + +void LLSurfacePatch::updateGL() +{ + LL_PROFILE_ZONE_SCOPED + F32 meters_per_grid = getSurface()->getMetersPerGrid(); + F32 grids_per_patch_edge = (F32)getSurface()->getGridsPerPatchEdge(); + + LLViewerRegion *regionp = getSurface()->getRegion(); + LLVector3d origin_region = getOriginGlobal() - getSurface()->getOriginGlobal(); + + LLVLComposition* comp = regionp->getComposition(); + + updateCompositionStats(); + F32 tex_patch_size = meters_per_grid*grids_per_patch_edge; + if (comp->generateTexture((F32)origin_region[VX], (F32)origin_region[VY], + tex_patch_size, tex_patch_size)) + { + mSTexUpdate = false; + + // Also generate the water texture + mSurfacep->generateWaterTexture((F32)origin_region.mdV[VX], (F32)origin_region.mdV[VY], + tex_patch_size, tex_patch_size); + } +} + +void LLSurfacePatch::dirtyZ() +{ + mSTexUpdate = true; + + // Invalidate all normals in this patch + U32 i; + for (i = 0; i < 9; i++) + { + mNormalsInvalid[i] = true; + } + + // Invalidate normals in this and neighboring patches + for (i = 0; i < 8; i++) + { + if (getNeighborPatch(i)) + { + getNeighborPatch(i)->mNormalsInvalid[gDirOpposite[i]] = true; + getNeighborPatch(i)->dirty(); + if (i < 4) + { + getNeighborPatch(i)->mNormalsInvalid[gDirAdjacent[gDirOpposite[i]][0]] = true; + getNeighborPatch(i)->mNormalsInvalid[gDirAdjacent[gDirOpposite[i]][1]] = true; + } + } + } + + dirty(); + mLastUpdateTime = gFrameTime; +} + + +const U64 &LLSurfacePatch::getLastUpdateTime() const +{ + return mLastUpdateTime; +} + +F32 LLSurfacePatch::getMaxZ() const +{ + return mMaxZ; +} + +F32 LLSurfacePatch::getMinZ() const +{ + return mMinZ; +} + +void LLSurfacePatch::setOriginGlobal(const LLVector3d &origin_global) +{ + mOriginGlobal = origin_global; + + LLVector3 origin_region; + origin_region.setVec(mOriginGlobal - mSurfacep->getOriginGlobal()); + + mOriginRegion = origin_region; + mCenterRegion.mV[VX] = origin_region.mV[VX] + 0.5f*mSurfacep->getGridsPerPatchEdge()*mSurfacep->getMetersPerGrid(); + mCenterRegion.mV[VY] = origin_region.mV[VY] + 0.5f*mSurfacep->getGridsPerPatchEdge()*mSurfacep->getMetersPerGrid(); + + mVisInfo.mbIsVisible = false; + mVisInfo.mDistance = 512.0f; + mVisInfo.mRenderLevel = 0; + mVisInfo.mRenderStride = mSurfacep->getGridsPerPatchEdge(); + +} + +void LLSurfacePatch::connectNeighbor(LLSurfacePatch *neighbor_patchp, const U32 direction) +{ + llassert(neighbor_patchp); + mNormalsInvalid[direction] = true; + neighbor_patchp->mNormalsInvalid[gDirOpposite[direction]] = true; + + setNeighborPatch(direction, neighbor_patchp); + neighbor_patchp->setNeighborPatch(gDirOpposite[direction], this); + + if (EAST == direction) + { + mConnectedEdge |= EAST_EDGE; + neighbor_patchp->mConnectedEdge |= WEST_EDGE; + } + else if (NORTH == direction) + { + mConnectedEdge |= NORTH_EDGE; + neighbor_patchp->mConnectedEdge |= SOUTH_EDGE; + } + else if (WEST == direction) + { + mConnectedEdge |= WEST_EDGE; + neighbor_patchp->mConnectedEdge |= EAST_EDGE; + } + else if (SOUTH == direction) + { + mConnectedEdge |= SOUTH_EDGE; + neighbor_patchp->mConnectedEdge |= NORTH_EDGE; + } +} + +void LLSurfacePatch::updateVisibility() +{ + if (mVObjp.isNull()) + { + return; + } + + const F32 DEFAULT_DELTA_ANGLE = (0.15f); + U32 old_render_stride, max_render_stride; + U32 new_render_level; + F32 stride_per_distance = DEFAULT_DELTA_ANGLE / mSurfacep->getMetersPerGrid(); + U32 grids_per_patch_edge = mSurfacep->getGridsPerPatchEdge(); + + LLVector4a center; + center.load3( (mCenterRegion + mSurfacep->getOriginAgent()).mV); + LLVector4a radius; + radius.splat(mRadius); + + // sphere in frustum on global coordinates + if (LLViewerCamera::getInstance()->AABBInFrustumNoFarClip(center, radius)) + { + // We now need to calculate the render stride based on patchp's distance + // from LLCamera render_stride is governed by a relation something like this... + // + // delta_angle * patch.distance + // render_stride <= ---------------------------------------- + // mMetersPerGrid + // + // where 'delta_angle' is the desired solid angle of the average polgon on a patch. + // + // Any render_stride smaller than the RHS would be 'satisfactory'. Smaller + // strides give more resolution, but efficiency suggests that we use the largest + // of the render_strides that obey the relation. Flexibility is achieved by + // modulating 'delta_angle' until we have an acceptable number of triangles. + + old_render_stride = mVisInfo.mRenderStride; + + // Calculate the render_stride using information in agent + max_render_stride = lltrunc(mVisInfo.mDistance * stride_per_distance); + max_render_stride = llmin(max_render_stride , 2*grids_per_patch_edge); + + // We only use render_strides that are powers of two, so we use look-up tables to figure out + // the render_level and corresponding render_stride + new_render_level = mVisInfo.mRenderLevel = mSurfacep->getRenderLevel(max_render_stride); + mVisInfo.mRenderStride = mSurfacep->getRenderStride(new_render_level); + + if ((mVisInfo.mRenderStride != old_render_stride)) + // The reason we check !mbIsVisible is because non-visible patches normals + // are not updated when their data is changed. When this changes we can get + // rid of mbIsVisible altogether. + { + if (mVObjp) + { + mVObjp->dirtyGeom(); + if (getNeighborPatch(WEST)) + { + getNeighborPatch(WEST)->mVObjp->dirtyGeom(); + } + if (getNeighborPatch(SOUTH)) + { + getNeighborPatch(SOUTH)->mVObjp->dirtyGeom(); + } + } + } + mVisInfo.mbIsVisible = true; + } + else + { + mVisInfo.mbIsVisible = false; + } +} + + +const LLVector3d &LLSurfacePatch::getOriginGlobal() const +{ + return mOriginGlobal; +} + +LLVector3 LLSurfacePatch::getOriginAgent() const +{ + return gAgent.getPosAgentFromGlobal(mOriginGlobal); +} + +bool LLSurfacePatch::getVisible() const +{ + return mVisInfo.mbIsVisible; +} + +U32 LLSurfacePatch::getRenderStride() const +{ + return mVisInfo.mRenderStride; +} + +S32 LLSurfacePatch::getRenderLevel() const +{ + return mVisInfo.mRenderLevel; +} + +void LLSurfacePatch::setHasReceivedData() +{ + mHasReceivedData = true; +} + +bool LLSurfacePatch::getHasReceivedData() const +{ + return mHasReceivedData; +} + +const LLVector3 &LLSurfacePatch::getCenterRegion() const +{ + return mCenterRegion; +} + + +void LLSurfacePatch::updateCompositionStats() +{ + LLViewerLayer *vlp = mSurfacep->getRegion()->getComposition(); + + F32 x, y, width, height, mpg, min, mean, max; + + LLVector3 origin = getOriginAgent() - mSurfacep->getOriginAgent(); + mpg = mSurfacep->getMetersPerGrid(); + x = origin.mV[VX]; + y = origin.mV[VY]; + width = mpg*(mSurfacep->getGridsPerPatchEdge()+1); + height = mpg*(mSurfacep->getGridsPerPatchEdge()+1); + + mean = 0.f; + min = vlp->getValueScaled(x, y); + max= min; + U32 count = 0; + F32 i, j; + for (j = 0; j < height; j += mpg) + { + for (i = 0; i < width; i += mpg) + { + F32 comp = vlp->getValueScaled(x + i, y + j); + mean += comp; + min = llmin(min, comp); + max = llmax(max, comp); + count++; + } + } + mean /= count; + + mMinComposition = min; + mMeanComposition = mean; + mMaxComposition = max; +} + +F32 LLSurfacePatch::getMeanComposition() const +{ + return mMeanComposition; +} + +F32 LLSurfacePatch::getMinComposition() const +{ + return mMinComposition; +} + +F32 LLSurfacePatch::getMaxComposition() const +{ + return mMaxComposition; +} + +void LLSurfacePatch::setNeighborPatch(const U32 direction, LLSurfacePatch *neighborp) +{ + mNeighborPatches[direction] = neighborp; + mNormalsInvalid[direction] = true; + if (direction < 4) + { + mNormalsInvalid[gDirAdjacent[direction][0]] = true; + mNormalsInvalid[gDirAdjacent[direction][1]] = true; + } +} + +LLSurfacePatch *LLSurfacePatch::getNeighborPatch(const U32 direction) const +{ + return mNeighborPatches[direction]; +} + +void LLSurfacePatch::clearVObj() +{ + mVObjp = NULL; +} diff --git a/indra/newview/llsurfacepatch.h b/indra/newview/llsurfacepatch.h index 25ad9bbfd6..82a09c152b 100644 --- a/indra/newview/llsurfacepatch.h +++ b/indra/newview/llsurfacepatch.h @@ -1,185 +1,185 @@ -/** - * @file llsurfacepatch.h - * @brief LLSurfacePatch class definition - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLSURFACEPATCH_H -#define LL_LLSURFACEPATCH_H - -#include "v3math.h" -#include "v3dmath.h" -#include "llpointer.h" - -class LLSurface; -class LLVOSurfacePatch; -class LLVector2; -class LLColor4U; -class LLAgent; - -// A patch shouldn't know about its visibility since that really depends on the -// camera that is looking (or not looking) at it. So, anything about a patch -// that is specific to a camera should be in the class below. -class LLPatchVisibilityInfo -{ -public: - LLPatchVisibilityInfo() : - mbIsVisible(false), - mDistance(0.f), - mRenderLevel(0), - mRenderStride(0) { }; - ~LLPatchVisibilityInfo() { }; - - bool mbIsVisible; - F32 mDistance; // Distance from camera - S32 mRenderLevel; - U32 mRenderStride; -}; - - - -class LLSurfacePatch -{ -public: - LLSurfacePatch(); - ~LLSurfacePatch(); - - void reset(const U32 id); - void connectNeighbor(LLSurfacePatch *neighborp, const U32 direction); - void disconnectNeighbor(LLSurface *surfacep); - - void setNeighborPatch(const U32 direction, LLSurfacePatch *neighborp); - LLSurfacePatch *getNeighborPatch(const U32 direction) const; - - void colorPatch(const U8 r, const U8 g, const U8 b); - - bool updateTexture(); - - void updateVerticalStats(); - void updateCompositionStats(); - void updateNormals(); - - void updateEastEdge(); - void updateNorthEdge(); - - void updateCameraDistanceRegion( const LLVector3 &pos_region); - void updateVisibility(); - void updateGL(); - - void dirtyZ(); // Dirty the z values of this patch - void setHasReceivedData(); - bool getHasReceivedData() const; - - F32 getDistance() const; - F32 getMaxZ() const; - F32 getMinZ() const; - F32 getMeanComposition() const; - F32 getMinComposition() const; - F32 getMaxComposition() const; - const LLVector3 &getCenterRegion() const; - const U64 &getLastUpdateTime() const; - LLSurface *getSurface() const { return mSurfacep; } - LLVector3 getPointAgent(const U32 x, const U32 y) const; // get the point at the offset. - LLVector2 getTexCoords(const U32 x, const U32 y) const; - - void calcNormal(const U32 x, const U32 y, const U32 stride); - const LLVector3 &getNormal(const U32 x, const U32 y) const; - - void eval(const U32 x, const U32 y, const U32 stride, - LLVector3 *vertex, LLVector3 *normal, LLVector2 *tex0, LLVector2 *tex1); - - - - LLVector3 getOriginAgent() const; - const LLVector3d &getOriginGlobal() const; - void setOriginGlobal(const LLVector3d &origin_global); - - // connectivity -- each LLPatch points at 5 neighbors (or NULL) - // +---+---+---+ - // | | 2 | 5 | - // +---+---+---+ - // | 3 | 0 | 1 | - // +---+---+---+ - // | 6 | 4 | | - // +---+---+---+ - - - bool getVisible() const; - U32 getRenderStride() const; - S32 getRenderLevel() const; - - void setSurface(LLSurface *surfacep); - void setDataZ(F32 *data_z) { mDataZ = data_z; } - void setDataNorm(LLVector3 *data_norm) { mDataNorm = data_norm; } - F32 *getDataZ() const { return mDataZ; } - - void dirty(); // Mark this surface patch as dirty... - void clearDirty() { mDirty = false; } - - void clearVObj(); - -public: - bool mHasReceivedData; // has the patch EVER received height data? - bool mSTexUpdate; // Does the surface texture need to be updated? - -protected: - LLSurfacePatch *mNeighborPatches[8]; // Adjacent patches - bool mNormalsInvalid[9]; // Which normals are invalid - - bool mDirty; - bool mDirtyZStats; - bool mHeightsGenerated; - - U32 mDataOffset; - F32 *mDataZ; - LLVector3 *mDataNorm; - - // Pointer to the LLVOSurfacePatch object which is used in the new renderer. - LLPointer mVObjp; - - // All of the camera-dependent stuff should be in its own class... - LLPatchVisibilityInfo mVisInfo; - - // pointers to beginnings of patch data fields - LLVector3d mOriginGlobal; - LLVector3 mOriginRegion; - - - // height field stats - LLVector3 mCenterRegion; // Center in region-local coords - F32 mMinZ, mMaxZ, mMeanZ; - F32 mRadius; - - F32 mMinComposition; - F32 mMaxComposition; - F32 mMeanComposition; - - U8 mConnectedEdge; // This flag is non-zero iff patch is on at least one edge - // of LLSurface that is "connected" to another LLSurface - U64 mLastUpdateTime; // Time patch was last updated - - LLSurface *mSurfacep; // Pointer to "parent" surface -}; - - -#endif // LL_LLSURFACEPATCH_H +/** + * @file llsurfacepatch.h + * @brief LLSurfacePatch class definition + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSURFACEPATCH_H +#define LL_LLSURFACEPATCH_H + +#include "v3math.h" +#include "v3dmath.h" +#include "llpointer.h" + +class LLSurface; +class LLVOSurfacePatch; +class LLVector2; +class LLColor4U; +class LLAgent; + +// A patch shouldn't know about its visibility since that really depends on the +// camera that is looking (or not looking) at it. So, anything about a patch +// that is specific to a camera should be in the class below. +class LLPatchVisibilityInfo +{ +public: + LLPatchVisibilityInfo() : + mbIsVisible(false), + mDistance(0.f), + mRenderLevel(0), + mRenderStride(0) { }; + ~LLPatchVisibilityInfo() { }; + + bool mbIsVisible; + F32 mDistance; // Distance from camera + S32 mRenderLevel; + U32 mRenderStride; +}; + + + +class LLSurfacePatch +{ +public: + LLSurfacePatch(); + ~LLSurfacePatch(); + + void reset(const U32 id); + void connectNeighbor(LLSurfacePatch *neighborp, const U32 direction); + void disconnectNeighbor(LLSurface *surfacep); + + void setNeighborPatch(const U32 direction, LLSurfacePatch *neighborp); + LLSurfacePatch *getNeighborPatch(const U32 direction) const; + + void colorPatch(const U8 r, const U8 g, const U8 b); + + bool updateTexture(); + + void updateVerticalStats(); + void updateCompositionStats(); + void updateNormals(); + + void updateEastEdge(); + void updateNorthEdge(); + + void updateCameraDistanceRegion( const LLVector3 &pos_region); + void updateVisibility(); + void updateGL(); + + void dirtyZ(); // Dirty the z values of this patch + void setHasReceivedData(); + bool getHasReceivedData() const; + + F32 getDistance() const; + F32 getMaxZ() const; + F32 getMinZ() const; + F32 getMeanComposition() const; + F32 getMinComposition() const; + F32 getMaxComposition() const; + const LLVector3 &getCenterRegion() const; + const U64 &getLastUpdateTime() const; + LLSurface *getSurface() const { return mSurfacep; } + LLVector3 getPointAgent(const U32 x, const U32 y) const; // get the point at the offset. + LLVector2 getTexCoords(const U32 x, const U32 y) const; + + void calcNormal(const U32 x, const U32 y, const U32 stride); + const LLVector3 &getNormal(const U32 x, const U32 y) const; + + void eval(const U32 x, const U32 y, const U32 stride, + LLVector3 *vertex, LLVector3 *normal, LLVector2 *tex0, LLVector2 *tex1); + + + + LLVector3 getOriginAgent() const; + const LLVector3d &getOriginGlobal() const; + void setOriginGlobal(const LLVector3d &origin_global); + + // connectivity -- each LLPatch points at 5 neighbors (or NULL) + // +---+---+---+ + // | | 2 | 5 | + // +---+---+---+ + // | 3 | 0 | 1 | + // +---+---+---+ + // | 6 | 4 | | + // +---+---+---+ + + + bool getVisible() const; + U32 getRenderStride() const; + S32 getRenderLevel() const; + + void setSurface(LLSurface *surfacep); + void setDataZ(F32 *data_z) { mDataZ = data_z; } + void setDataNorm(LLVector3 *data_norm) { mDataNorm = data_norm; } + F32 *getDataZ() const { return mDataZ; } + + void dirty(); // Mark this surface patch as dirty... + void clearDirty() { mDirty = false; } + + void clearVObj(); + +public: + bool mHasReceivedData; // has the patch EVER received height data? + bool mSTexUpdate; // Does the surface texture need to be updated? + +protected: + LLSurfacePatch *mNeighborPatches[8]; // Adjacent patches + bool mNormalsInvalid[9]; // Which normals are invalid + + bool mDirty; + bool mDirtyZStats; + bool mHeightsGenerated; + + U32 mDataOffset; + F32 *mDataZ; + LLVector3 *mDataNorm; + + // Pointer to the LLVOSurfacePatch object which is used in the new renderer. + LLPointer mVObjp; + + // All of the camera-dependent stuff should be in its own class... + LLPatchVisibilityInfo mVisInfo; + + // pointers to beginnings of patch data fields + LLVector3d mOriginGlobal; + LLVector3 mOriginRegion; + + + // height field stats + LLVector3 mCenterRegion; // Center in region-local coords + F32 mMinZ, mMaxZ, mMeanZ; + F32 mRadius; + + F32 mMinComposition; + F32 mMaxComposition; + F32 mMeanComposition; + + U8 mConnectedEdge; // This flag is non-zero iff patch is on at least one edge + // of LLSurface that is "connected" to another LLSurface + U64 mLastUpdateTime; // Time patch was last updated + + LLSurface *mSurfacep; // Pointer to "parent" surface +}; + + +#endif // LL_LLSURFACEPATCH_H diff --git a/indra/newview/llsyswellitem.cpp b/indra/newview/llsyswellitem.cpp index 27ee3dfa84..6da55b0a0a 100644 --- a/indra/newview/llsyswellitem.cpp +++ b/indra/newview/llsyswellitem.cpp @@ -1,94 +1,94 @@ -/** - * @file llsyswellitem.cpp - * @brief // TODO - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#include "llviewerprecompiledheaders.h" // must be first include - -#include "llsyswellitem.h" - -#include "llwindow.h" -#include "v4color.h" -#include "lluicolortable.h" - -//--------------------------------------------------------------------------------- -LLSysWellItem::LLSysWellItem(const Params& p) : LLPanel(p), - mTitle(NULL), - mCloseBtn(NULL) -{ - buildFromFile( "panel_sys_well_item.xml"); - - mTitle = getChild("title"); - mCloseBtn = getChild("close_btn"); - - mTitle->setContentTrusted(false); - mTitle->setValue(p.title); - mCloseBtn->setClickedCallback(boost::bind(&LLSysWellItem::onClickCloseBtn,this)); - - mID = p.notification_id; -} - -//--------------------------------------------------------------------------------- -LLSysWellItem::~LLSysWellItem() -{ -} - -//--------------------------------------------------------------------------------- -void LLSysWellItem::setTitle( std::string title ) -{ - mTitle->setValue(title); -} - -//--------------------------------------------------------------------------------- -void LLSysWellItem::onClickCloseBtn() -{ - mOnItemClose(this); -} - -//--------------------------------------------------------------------------------- -bool LLSysWellItem::handleMouseDown(S32 x, S32 y, MASK mask) -{ - bool res = LLPanel::handleMouseDown(x, y, mask); - if(!mCloseBtn->getRect().pointInRect(x, y)) - mOnItemClick(this); - - return res; -} - -//--------------------------------------------------------------------------------- -void LLSysWellItem::onMouseEnter(S32 x, S32 y, MASK mask) -{ - setTransparentColor(LLUIColorTable::instance().getColor( "SysWellItemSelected" )); -} - -//--------------------------------------------------------------------------------- -void LLSysWellItem::onMouseLeave(S32 x, S32 y, MASK mask) -{ - setTransparentColor(LLUIColorTable::instance().getColor( "SysWellItemUnselected" )); -} - -//--------------------------------------------------------------------------------- - - +/** + * @file llsyswellitem.cpp + * @brief // TODO + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#include "llviewerprecompiledheaders.h" // must be first include + +#include "llsyswellitem.h" + +#include "llwindow.h" +#include "v4color.h" +#include "lluicolortable.h" + +//--------------------------------------------------------------------------------- +LLSysWellItem::LLSysWellItem(const Params& p) : LLPanel(p), + mTitle(NULL), + mCloseBtn(NULL) +{ + buildFromFile( "panel_sys_well_item.xml"); + + mTitle = getChild("title"); + mCloseBtn = getChild("close_btn"); + + mTitle->setContentTrusted(false); + mTitle->setValue(p.title); + mCloseBtn->setClickedCallback(boost::bind(&LLSysWellItem::onClickCloseBtn,this)); + + mID = p.notification_id; +} + +//--------------------------------------------------------------------------------- +LLSysWellItem::~LLSysWellItem() +{ +} + +//--------------------------------------------------------------------------------- +void LLSysWellItem::setTitle( std::string title ) +{ + mTitle->setValue(title); +} + +//--------------------------------------------------------------------------------- +void LLSysWellItem::onClickCloseBtn() +{ + mOnItemClose(this); +} + +//--------------------------------------------------------------------------------- +bool LLSysWellItem::handleMouseDown(S32 x, S32 y, MASK mask) +{ + bool res = LLPanel::handleMouseDown(x, y, mask); + if(!mCloseBtn->getRect().pointInRect(x, y)) + mOnItemClick(this); + + return res; +} + +//--------------------------------------------------------------------------------- +void LLSysWellItem::onMouseEnter(S32 x, S32 y, MASK mask) +{ + setTransparentColor(LLUIColorTable::instance().getColor( "SysWellItemSelected" )); +} + +//--------------------------------------------------------------------------------- +void LLSysWellItem::onMouseLeave(S32 x, S32 y, MASK mask) +{ + setTransparentColor(LLUIColorTable::instance().getColor( "SysWellItemUnselected" )); +} + +//--------------------------------------------------------------------------------- + + diff --git a/indra/newview/llsyswellitem.h b/indra/newview/llsyswellitem.h index ce9c8aa46d..0a4e26cb98 100644 --- a/indra/newview/llsyswellitem.h +++ b/indra/newview/llsyswellitem.h @@ -1,81 +1,81 @@ -/** - * @file llsyswellitem.h - * @brief // TODO - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLSYSWELLITEM_H -#define LL_LLSYSWELLITEM_H - -#include "llpanel.h" -#include "lltextbox.h" -#include "llbutton.h" -#include "lliconctrl.h" - -#include - -class LLSysWellItem : public LLPanel -{ -public: - struct Params : public LLInitParam::Block - { - LLUUID notification_id; - std::string title; - Params() {}; - }; - - - LLSysWellItem(const Params& p); - virtual ~LLSysWellItem(); - - // title - void setTitle( std::string title ); - - // get item's ID - LLUUID getID() { return mID; } - - // handlers - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual void onMouseEnter(S32 x, S32 y, MASK mask); - virtual void onMouseLeave(S32 x, S32 y, MASK mask); - - //callbacks - typedef boost::function syswell_item_callback_t; - typedef boost::signals2::signal syswell_item_signal_t; - syswell_item_signal_t mOnItemClose; - syswell_item_signal_t mOnItemClick; - boost::signals2::connection setOnItemCloseCallback(syswell_item_callback_t cb) { return mOnItemClose.connect(cb); } - boost::signals2::connection setOnItemClickCallback(syswell_item_callback_t cb) { return mOnItemClick.connect(cb); } - -private: - - void onClickCloseBtn(); - - LLTextBox* mTitle; - LLButton* mCloseBtn; - LLUUID mID; -}; - -#endif // LL_LLSYSWELLITEM_H - - +/** + * @file llsyswellitem.h + * @brief // TODO + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSYSWELLITEM_H +#define LL_LLSYSWELLITEM_H + +#include "llpanel.h" +#include "lltextbox.h" +#include "llbutton.h" +#include "lliconctrl.h" + +#include + +class LLSysWellItem : public LLPanel +{ +public: + struct Params : public LLInitParam::Block + { + LLUUID notification_id; + std::string title; + Params() {}; + }; + + + LLSysWellItem(const Params& p); + virtual ~LLSysWellItem(); + + // title + void setTitle( std::string title ); + + // get item's ID + LLUUID getID() { return mID; } + + // handlers + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual void onMouseEnter(S32 x, S32 y, MASK mask); + virtual void onMouseLeave(S32 x, S32 y, MASK mask); + + //callbacks + typedef boost::function syswell_item_callback_t; + typedef boost::signals2::signal syswell_item_signal_t; + syswell_item_signal_t mOnItemClose; + syswell_item_signal_t mOnItemClick; + boost::signals2::connection setOnItemCloseCallback(syswell_item_callback_t cb) { return mOnItemClose.connect(cb); } + boost::signals2::connection setOnItemClickCallback(syswell_item_callback_t cb) { return mOnItemClick.connect(cb); } + +private: + + void onClickCloseBtn(); + + LLTextBox* mTitle; + LLButton* mCloseBtn; + LLUUID mID; +}; + +#endif // LL_LLSYSWELLITEM_H + + diff --git a/indra/newview/llsyswellwindow.cpp b/indra/newview/llsyswellwindow.cpp index 2d629c2983..30142ad601 100644 --- a/indra/newview/llsyswellwindow.cpp +++ b/indra/newview/llsyswellwindow.cpp @@ -1,450 +1,450 @@ -/** - * @file llsyswellwindow.cpp - * @brief // TODO - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" // must be first include -#include "llsyswellwindow.h" - -#include "llchiclet.h" -#include "llchicletbar.h" -#include "llflatlistview.h" -#include "llfloaterreg.h" -#include "llnotificationmanager.h" -#include "llnotificationsutil.h" -#include "llscriptfloater.h" -#include "llspeakers.h" -#include "lltoastpanel.h" - -//--------------------------------------------------------------------------------- -LLSysWellWindow::LLSysWellWindow(const LLSD& key) : LLTransientDockableFloater(NULL, true, key), - mChannel(NULL), - mMessageList(NULL), - mSysWellChiclet(NULL), - NOTIFICATION_WELL_ANCHOR_NAME("notification_well_panel"), - IM_WELL_ANCHOR_NAME("im_well_panel"), - mIsReshapedByUser(false) - -{ - setOverlapsScreenChannel(true); -} - -//--------------------------------------------------------------------------------- -bool LLSysWellWindow::postBuild() -{ - mMessageList = getChild("notification_list"); - - // get a corresponding channel - initChannel(); - - return LLTransientDockableFloater::postBuild(); -} - -//--------------------------------------------------------------------------------- -void LLSysWellWindow::setMinimized(bool minimize) -{ - LLTransientDockableFloater::setMinimized(minimize); -} - -//--------------------------------------------------------------------------------- -void LLSysWellWindow::handleReshape(const LLRect& rect, bool by_user) -{ - mIsReshapedByUser |= by_user; // mark floater that it is reshaped by user - LLTransientDockableFloater::handleReshape(rect, by_user); -} - -//--------------------------------------------------------------------------------- -void LLSysWellWindow::onStartUpToastClick(S32 x, S32 y, MASK mask) -{ - // just set floater visible. Screen channels will be cleared. - setVisible(true); -} - -void LLSysWellWindow::setSysWellChiclet(LLSysWellChiclet* chiclet) -{ - mSysWellChiclet = chiclet; - if(NULL != mSysWellChiclet) - { - mSysWellChiclet->updateWidget(isWindowEmpty()); - } -} - -//--------------------------------------------------------------------------------- -LLSysWellWindow::~LLSysWellWindow() -{ -} - -//--------------------------------------------------------------------------------- -void LLSysWellWindow::removeItemByID(const LLUUID& id) -{ - if(mMessageList->removeItemByValue(id)) - { - if (NULL != mSysWellChiclet) - { - mSysWellChiclet->updateWidget(isWindowEmpty()); - } - reshapeWindow(); - } - else - { - LL_WARNS() << "Unable to remove notification from the list, ID: " << id - << LL_ENDL; - } - - // hide chiclet window if there are no items left - if(isWindowEmpty()) - { - setVisible(false); - } -} - - LLPanel * LLSysWellWindow::findItemByID(const LLUUID& id) -{ - return mMessageList->getItemByValue(id); -} - -//--------------------------------------------------------------------------------- -//--------------------------------------------------------------------------------- -void LLSysWellWindow::initChannel() -{ - LLNotificationsUI::LLScreenChannelBase* channel = LLNotificationsUI::LLChannelManager::getInstance()->findChannelByID( - LLNotificationsUI::NOTIFICATION_CHANNEL_UUID); - mChannel = dynamic_cast(channel); - if(NULL == mChannel) - { - LL_WARNS() << "LLSysWellWindow::initChannel() - could not get a requested screen channel" << LL_ENDL; - } -} - -//--------------------------------------------------------------------------------- -void LLSysWellWindow::setVisible(bool visible) -{ - if (visible) - { - if (NULL == getDockControl() && getDockTongue().notNull()) - { - setDockControl(new LLDockControl( - LLChicletBar::getInstance()->getChild(getAnchorViewName()), this, - getDockTongue(), LLDockControl::BOTTOM)); - } - } - - // do not show empty window - if (NULL == mMessageList || isWindowEmpty()) visible = false; - - LLTransientDockableFloater::setVisible(visible); - - // update notification channel state - initChannel(); // make sure the channel still exists - if(mChannel) - { - mChannel->updateShowToastsState(); - mChannel->redrawToasts(); - } -} - -//--------------------------------------------------------------------------------- -void LLSysWellWindow::setDocked(bool docked, bool pop_on_undock) -{ - LLTransientDockableFloater::setDocked(docked, pop_on_undock); - - // update notification channel state - if(mChannel) - { - mChannel->updateShowToastsState(); - mChannel->redrawToasts(); - } -} - -//--------------------------------------------------------------------------------- -void LLSysWellWindow::reshapeWindow() -{ - // save difference between floater height and the list height to take it into account while calculating new window height - // it includes height from floater top to list top and from floater bottom and list bottom - static S32 parent_list_delta_height = getRect().getHeight() - mMessageList->getRect().getHeight(); - - if (!mIsReshapedByUser) // Don't reshape Well window, if it ever was reshaped by user. See EXT-5715. - { - S32 notif_list_height = mMessageList->getItemsRect().getHeight() + 2 * mMessageList->getBorderWidth(); - - LLRect curRect = getRect(); - - S32 new_window_height = notif_list_height + parent_list_delta_height; - - if (new_window_height > MAX_WINDOW_HEIGHT) - { - new_window_height = MAX_WINDOW_HEIGHT; - } - S32 newWidth = curRect.getWidth() < MIN_WINDOW_WIDTH ? MIN_WINDOW_WIDTH : curRect.getWidth(); - - curRect.setLeftTopAndSize(curRect.mLeft, curRect.mTop, newWidth, new_window_height); - reshape(curRect.getWidth(), curRect.getHeight(), true); - setRect(curRect); - } - - // update notification channel state - // update on a window reshape is important only when a window is visible and docked - if(mChannel && getVisible() && isDocked()) - { - mChannel->updateShowToastsState(); - } -} - -//--------------------------------------------------------------------------------- -bool LLSysWellWindow::isWindowEmpty() -{ - return mMessageList->size() == 0; -} - -/************************************************************************/ -/* ObjectRowPanel implementation */ -/************************************************************************/ - -LLIMWellWindow::ObjectRowPanel::ObjectRowPanel(const LLUUID& notification_id, bool new_message/* = false*/) - : LLPanel() - , mChiclet(NULL) -{ - buildFromFile( "panel_active_object_row.xml"); - - initChiclet(notification_id); - - LLTextBox* obj_name = getChild("object_name"); - obj_name->setValue(LLScriptFloaterManager::getObjectName(notification_id)); - - mCloseBtn = getChild("hide_btn"); - mCloseBtn->setCommitCallback(boost::bind(&LLIMWellWindow::ObjectRowPanel::onClosePanel, this)); -} - -//--------------------------------------------------------------------------------- -LLIMWellWindow::ObjectRowPanel::~ObjectRowPanel() -{ -} - -//--------------------------------------------------------------------------------- -void LLIMWellWindow::ObjectRowPanel::onClosePanel() -{ - LLScriptFloaterManager::getInstance()->removeNotification(mChiclet->getSessionId()); -} - -void LLIMWellWindow::ObjectRowPanel::initChiclet(const LLUUID& notification_id, bool new_message/* = false*/) -{ - // Choose which of the pre-created chiclets to use. - switch(LLScriptFloaterManager::getObjectType(notification_id)) - { - case LLScriptFloaterManager::OBJ_GIVE_INVENTORY: - mChiclet = getChild("inv_offer_chiclet"); - break; - default: - mChiclet = getChild("object_chiclet"); - break; - } - - mChiclet->setVisible(true); - mChiclet->setSessionId(notification_id); -} - -//--------------------------------------------------------------------------------- -void LLIMWellWindow::ObjectRowPanel::onMouseEnter(S32 x, S32 y, MASK mask) -{ - setTransparentColor(LLUIColorTable::instance().getColor("SysWellItemSelected")); -} - -//--------------------------------------------------------------------------------- -void LLIMWellWindow::ObjectRowPanel::onMouseLeave(S32 x, S32 y, MASK mask) -{ - setTransparentColor(LLUIColorTable::instance().getColor("SysWellItemUnselected")); -} - -//--------------------------------------------------------------------------------- -// virtual -bool LLIMWellWindow::ObjectRowPanel::handleMouseDown(S32 x, S32 y, MASK mask) -{ - // Pass the mouse down event to the chiclet (EXT-596). - if (!mChiclet->pointInView(x, y) && !mCloseBtn->getRect().pointInRect(x, y)) // prevent double call of LLIMChiclet::onMouseDown() - { - mChiclet->onMouseDown(); - return true; - } - - return LLPanel::handleMouseDown(x, y, mask); -} - -// virtual -bool LLIMWellWindow::ObjectRowPanel::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - return mChiclet->handleRightMouseDown(x, y, mask); -} - -/************************************************************************/ -/* LLIMWellWindow implementation */ -/************************************************************************/ - -////////////////////////////////////////////////////////////////////////// -// PUBLIC METHODS -LLIMWellWindow::LLIMWellWindow(const LLSD& key) -: LLSysWellWindow(key) -{ -} - -LLIMWellWindow::~LLIMWellWindow() -{ -} - -// static -LLIMWellWindow* LLIMWellWindow::getInstance(const LLSD& key /*= LLSD()*/) -{ - return LLFloaterReg::getTypedInstance("im_well_window", key); -} - - -// static -LLIMWellWindow* LLIMWellWindow::findInstance(const LLSD& key /*= LLSD()*/) -{ - return LLFloaterReg::findTypedInstance("im_well_window", key); -} - -bool LLIMWellWindow::postBuild() -{ - bool rv = LLSysWellWindow::postBuild(); - setTitle(getString("title_im_well_window")); - - LLIMChiclet::sFindChicletsSignal.connect(boost::bind(&LLIMWellWindow::findObjectChiclet, this, _1)); - - return rv; -} - -LLChiclet* LLIMWellWindow::findObjectChiclet(const LLUUID& notification_id) -{ - if (!mMessageList) return NULL; - - LLChiclet* res = NULL; - ObjectRowPanel* panel = mMessageList->getTypedItemByValue(notification_id); - if (panel != NULL) - { - res = panel->mChiclet; - } - - return res; -} - -////////////////////////////////////////////////////////////////////////// -// PRIVATE METHODS - -void LLIMWellWindow::addObjectRow(const LLUUID& notification_id, bool new_message/* = false*/) -{ - if (mMessageList->getItemByValue(notification_id) == NULL) - { - ObjectRowPanel* item = new ObjectRowPanel(notification_id, new_message); - if (!mMessageList->addItem(item, notification_id)) - { - LL_WARNS() << "Unable to add Object Row into the list, notificationID: " << notification_id << LL_ENDL; - item->die(); - } - reshapeWindow(); - } -} - -void LLIMWellWindow::removeObjectRow(const LLUUID& notification_id) -{ - if (!mMessageList->removeItemByValue(notification_id)) - { - LL_WARNS() << "Unable to remove Object Row from the list, notificationID: " << notification_id << LL_ENDL; - } - - reshapeWindow(); - // hide chiclet window if there are no items left - if(isWindowEmpty()) - { - setVisible(false); - } -} - -void LLIMWellWindow::closeAll() -{ - // Generate an ignorable alert dialog if there is an active voice IM sesion - bool need_confirmation = false; - const LLIMModel& im_model = LLIMModel::instance(); - std::vector values; - mMessageList->getValues(values); - for (std::vector::iterator - iter = values.begin(), - iter_end = values.end(); - iter != iter_end; ++iter) - { - LLIMSpeakerMgr* speaker_mgr = im_model.getSpeakerManager(*iter); - if (speaker_mgr && speaker_mgr->isVoiceActive()) - { - need_confirmation = true; - break; - } - } - if ( need_confirmation ) - { - //Bring up a confirmation dialog - LLNotificationsUtil::add - ("ConfirmCloseAll", LLSD(), LLSD(), - boost::bind(&LLIMWellWindow::confirmCloseAll, this, _1, _2)); - } - else - { - closeAllImpl(); - } -} - -void LLIMWellWindow::closeAllImpl() -{ - std::vector values; - mMessageList->getValues(values); - - for (std::vector::iterator - iter = values.begin(), - iter_end = values.end(); - iter != iter_end; ++iter) - { - LLPanel* panel = mMessageList->getItemByValue(*iter); - - ObjectRowPanel* obj_panel = dynamic_cast (panel); - if (obj_panel) - { - LLScriptFloaterManager::instance().removeNotification(*iter); - } - } -} - -bool LLIMWellWindow::confirmCloseAll(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - switch(option) - { - case 0: - { - closeAllImpl(); - return true; - } - default: - break; - } - return false; -} - - +/** + * @file llsyswellwindow.cpp + * @brief // TODO + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" // must be first include +#include "llsyswellwindow.h" + +#include "llchiclet.h" +#include "llchicletbar.h" +#include "llflatlistview.h" +#include "llfloaterreg.h" +#include "llnotificationmanager.h" +#include "llnotificationsutil.h" +#include "llscriptfloater.h" +#include "llspeakers.h" +#include "lltoastpanel.h" + +//--------------------------------------------------------------------------------- +LLSysWellWindow::LLSysWellWindow(const LLSD& key) : LLTransientDockableFloater(NULL, true, key), + mChannel(NULL), + mMessageList(NULL), + mSysWellChiclet(NULL), + NOTIFICATION_WELL_ANCHOR_NAME("notification_well_panel"), + IM_WELL_ANCHOR_NAME("im_well_panel"), + mIsReshapedByUser(false) + +{ + setOverlapsScreenChannel(true); +} + +//--------------------------------------------------------------------------------- +bool LLSysWellWindow::postBuild() +{ + mMessageList = getChild("notification_list"); + + // get a corresponding channel + initChannel(); + + return LLTransientDockableFloater::postBuild(); +} + +//--------------------------------------------------------------------------------- +void LLSysWellWindow::setMinimized(bool minimize) +{ + LLTransientDockableFloater::setMinimized(minimize); +} + +//--------------------------------------------------------------------------------- +void LLSysWellWindow::handleReshape(const LLRect& rect, bool by_user) +{ + mIsReshapedByUser |= by_user; // mark floater that it is reshaped by user + LLTransientDockableFloater::handleReshape(rect, by_user); +} + +//--------------------------------------------------------------------------------- +void LLSysWellWindow::onStartUpToastClick(S32 x, S32 y, MASK mask) +{ + // just set floater visible. Screen channels will be cleared. + setVisible(true); +} + +void LLSysWellWindow::setSysWellChiclet(LLSysWellChiclet* chiclet) +{ + mSysWellChiclet = chiclet; + if(NULL != mSysWellChiclet) + { + mSysWellChiclet->updateWidget(isWindowEmpty()); + } +} + +//--------------------------------------------------------------------------------- +LLSysWellWindow::~LLSysWellWindow() +{ +} + +//--------------------------------------------------------------------------------- +void LLSysWellWindow::removeItemByID(const LLUUID& id) +{ + if(mMessageList->removeItemByValue(id)) + { + if (NULL != mSysWellChiclet) + { + mSysWellChiclet->updateWidget(isWindowEmpty()); + } + reshapeWindow(); + } + else + { + LL_WARNS() << "Unable to remove notification from the list, ID: " << id + << LL_ENDL; + } + + // hide chiclet window if there are no items left + if(isWindowEmpty()) + { + setVisible(false); + } +} + + LLPanel * LLSysWellWindow::findItemByID(const LLUUID& id) +{ + return mMessageList->getItemByValue(id); +} + +//--------------------------------------------------------------------------------- +//--------------------------------------------------------------------------------- +void LLSysWellWindow::initChannel() +{ + LLNotificationsUI::LLScreenChannelBase* channel = LLNotificationsUI::LLChannelManager::getInstance()->findChannelByID( + LLNotificationsUI::NOTIFICATION_CHANNEL_UUID); + mChannel = dynamic_cast(channel); + if(NULL == mChannel) + { + LL_WARNS() << "LLSysWellWindow::initChannel() - could not get a requested screen channel" << LL_ENDL; + } +} + +//--------------------------------------------------------------------------------- +void LLSysWellWindow::setVisible(bool visible) +{ + if (visible) + { + if (NULL == getDockControl() && getDockTongue().notNull()) + { + setDockControl(new LLDockControl( + LLChicletBar::getInstance()->getChild(getAnchorViewName()), this, + getDockTongue(), LLDockControl::BOTTOM)); + } + } + + // do not show empty window + if (NULL == mMessageList || isWindowEmpty()) visible = false; + + LLTransientDockableFloater::setVisible(visible); + + // update notification channel state + initChannel(); // make sure the channel still exists + if(mChannel) + { + mChannel->updateShowToastsState(); + mChannel->redrawToasts(); + } +} + +//--------------------------------------------------------------------------------- +void LLSysWellWindow::setDocked(bool docked, bool pop_on_undock) +{ + LLTransientDockableFloater::setDocked(docked, pop_on_undock); + + // update notification channel state + if(mChannel) + { + mChannel->updateShowToastsState(); + mChannel->redrawToasts(); + } +} + +//--------------------------------------------------------------------------------- +void LLSysWellWindow::reshapeWindow() +{ + // save difference between floater height and the list height to take it into account while calculating new window height + // it includes height from floater top to list top and from floater bottom and list bottom + static S32 parent_list_delta_height = getRect().getHeight() - mMessageList->getRect().getHeight(); + + if (!mIsReshapedByUser) // Don't reshape Well window, if it ever was reshaped by user. See EXT-5715. + { + S32 notif_list_height = mMessageList->getItemsRect().getHeight() + 2 * mMessageList->getBorderWidth(); + + LLRect curRect = getRect(); + + S32 new_window_height = notif_list_height + parent_list_delta_height; + + if (new_window_height > MAX_WINDOW_HEIGHT) + { + new_window_height = MAX_WINDOW_HEIGHT; + } + S32 newWidth = curRect.getWidth() < MIN_WINDOW_WIDTH ? MIN_WINDOW_WIDTH : curRect.getWidth(); + + curRect.setLeftTopAndSize(curRect.mLeft, curRect.mTop, newWidth, new_window_height); + reshape(curRect.getWidth(), curRect.getHeight(), true); + setRect(curRect); + } + + // update notification channel state + // update on a window reshape is important only when a window is visible and docked + if(mChannel && getVisible() && isDocked()) + { + mChannel->updateShowToastsState(); + } +} + +//--------------------------------------------------------------------------------- +bool LLSysWellWindow::isWindowEmpty() +{ + return mMessageList->size() == 0; +} + +/************************************************************************/ +/* ObjectRowPanel implementation */ +/************************************************************************/ + +LLIMWellWindow::ObjectRowPanel::ObjectRowPanel(const LLUUID& notification_id, bool new_message/* = false*/) + : LLPanel() + , mChiclet(NULL) +{ + buildFromFile( "panel_active_object_row.xml"); + + initChiclet(notification_id); + + LLTextBox* obj_name = getChild("object_name"); + obj_name->setValue(LLScriptFloaterManager::getObjectName(notification_id)); + + mCloseBtn = getChild("hide_btn"); + mCloseBtn->setCommitCallback(boost::bind(&LLIMWellWindow::ObjectRowPanel::onClosePanel, this)); +} + +//--------------------------------------------------------------------------------- +LLIMWellWindow::ObjectRowPanel::~ObjectRowPanel() +{ +} + +//--------------------------------------------------------------------------------- +void LLIMWellWindow::ObjectRowPanel::onClosePanel() +{ + LLScriptFloaterManager::getInstance()->removeNotification(mChiclet->getSessionId()); +} + +void LLIMWellWindow::ObjectRowPanel::initChiclet(const LLUUID& notification_id, bool new_message/* = false*/) +{ + // Choose which of the pre-created chiclets to use. + switch(LLScriptFloaterManager::getObjectType(notification_id)) + { + case LLScriptFloaterManager::OBJ_GIVE_INVENTORY: + mChiclet = getChild("inv_offer_chiclet"); + break; + default: + mChiclet = getChild("object_chiclet"); + break; + } + + mChiclet->setVisible(true); + mChiclet->setSessionId(notification_id); +} + +//--------------------------------------------------------------------------------- +void LLIMWellWindow::ObjectRowPanel::onMouseEnter(S32 x, S32 y, MASK mask) +{ + setTransparentColor(LLUIColorTable::instance().getColor("SysWellItemSelected")); +} + +//--------------------------------------------------------------------------------- +void LLIMWellWindow::ObjectRowPanel::onMouseLeave(S32 x, S32 y, MASK mask) +{ + setTransparentColor(LLUIColorTable::instance().getColor("SysWellItemUnselected")); +} + +//--------------------------------------------------------------------------------- +// virtual +bool LLIMWellWindow::ObjectRowPanel::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // Pass the mouse down event to the chiclet (EXT-596). + if (!mChiclet->pointInView(x, y) && !mCloseBtn->getRect().pointInRect(x, y)) // prevent double call of LLIMChiclet::onMouseDown() + { + mChiclet->onMouseDown(); + return true; + } + + return LLPanel::handleMouseDown(x, y, mask); +} + +// virtual +bool LLIMWellWindow::ObjectRowPanel::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + return mChiclet->handleRightMouseDown(x, y, mask); +} + +/************************************************************************/ +/* LLIMWellWindow implementation */ +/************************************************************************/ + +////////////////////////////////////////////////////////////////////////// +// PUBLIC METHODS +LLIMWellWindow::LLIMWellWindow(const LLSD& key) +: LLSysWellWindow(key) +{ +} + +LLIMWellWindow::~LLIMWellWindow() +{ +} + +// static +LLIMWellWindow* LLIMWellWindow::getInstance(const LLSD& key /*= LLSD()*/) +{ + return LLFloaterReg::getTypedInstance("im_well_window", key); +} + + +// static +LLIMWellWindow* LLIMWellWindow::findInstance(const LLSD& key /*= LLSD()*/) +{ + return LLFloaterReg::findTypedInstance("im_well_window", key); +} + +bool LLIMWellWindow::postBuild() +{ + bool rv = LLSysWellWindow::postBuild(); + setTitle(getString("title_im_well_window")); + + LLIMChiclet::sFindChicletsSignal.connect(boost::bind(&LLIMWellWindow::findObjectChiclet, this, _1)); + + return rv; +} + +LLChiclet* LLIMWellWindow::findObjectChiclet(const LLUUID& notification_id) +{ + if (!mMessageList) return NULL; + + LLChiclet* res = NULL; + ObjectRowPanel* panel = mMessageList->getTypedItemByValue(notification_id); + if (panel != NULL) + { + res = panel->mChiclet; + } + + return res; +} + +////////////////////////////////////////////////////////////////////////// +// PRIVATE METHODS + +void LLIMWellWindow::addObjectRow(const LLUUID& notification_id, bool new_message/* = false*/) +{ + if (mMessageList->getItemByValue(notification_id) == NULL) + { + ObjectRowPanel* item = new ObjectRowPanel(notification_id, new_message); + if (!mMessageList->addItem(item, notification_id)) + { + LL_WARNS() << "Unable to add Object Row into the list, notificationID: " << notification_id << LL_ENDL; + item->die(); + } + reshapeWindow(); + } +} + +void LLIMWellWindow::removeObjectRow(const LLUUID& notification_id) +{ + if (!mMessageList->removeItemByValue(notification_id)) + { + LL_WARNS() << "Unable to remove Object Row from the list, notificationID: " << notification_id << LL_ENDL; + } + + reshapeWindow(); + // hide chiclet window if there are no items left + if(isWindowEmpty()) + { + setVisible(false); + } +} + +void LLIMWellWindow::closeAll() +{ + // Generate an ignorable alert dialog if there is an active voice IM sesion + bool need_confirmation = false; + const LLIMModel& im_model = LLIMModel::instance(); + std::vector values; + mMessageList->getValues(values); + for (std::vector::iterator + iter = values.begin(), + iter_end = values.end(); + iter != iter_end; ++iter) + { + LLIMSpeakerMgr* speaker_mgr = im_model.getSpeakerManager(*iter); + if (speaker_mgr && speaker_mgr->isVoiceActive()) + { + need_confirmation = true; + break; + } + } + if ( need_confirmation ) + { + //Bring up a confirmation dialog + LLNotificationsUtil::add + ("ConfirmCloseAll", LLSD(), LLSD(), + boost::bind(&LLIMWellWindow::confirmCloseAll, this, _1, _2)); + } + else + { + closeAllImpl(); + } +} + +void LLIMWellWindow::closeAllImpl() +{ + std::vector values; + mMessageList->getValues(values); + + for (std::vector::iterator + iter = values.begin(), + iter_end = values.end(); + iter != iter_end; ++iter) + { + LLPanel* panel = mMessageList->getItemByValue(*iter); + + ObjectRowPanel* obj_panel = dynamic_cast (panel); + if (obj_panel) + { + LLScriptFloaterManager::instance().removeNotification(*iter); + } + } +} + +bool LLIMWellWindow::confirmCloseAll(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + switch(option) + { + case 0: + { + closeAllImpl(); + return true; + } + default: + break; + } + return false; +} + + diff --git a/indra/newview/llsyswellwindow.h b/indra/newview/llsyswellwindow.h index b40f1a5414..618a0e6812 100644 --- a/indra/newview/llsyswellwindow.h +++ b/indra/newview/llsyswellwindow.h @@ -1,153 +1,153 @@ -/** - * @file llsyswellwindow.h - * @brief // TODO - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLSYSWELLWINDOW_H -#define LL_LLSYSWELLWINDOW_H - -#include "llimview.h" -#include "llnotifications.h" -#include "llscreenchannel.h" -#include "llsyswellitem.h" -#include "lltransientdockablefloater.h" -#include "llinitdestroyclass.h" - -class LLAvatarName; -class LLChiclet; -class LLFlatListView; -class LLIMChiclet; -class LLScriptChiclet; -class LLSysWellChiclet; - -class LLSysWellWindow : public LLTransientDockableFloater -{ -public: - LOG_CLASS(LLSysWellWindow); - - LLSysWellWindow(const LLSD& key); - virtual ~LLSysWellWindow(); - bool postBuild(); - - // other interface functions - // check is window empty - bool isWindowEmpty(); - - // Operating with items - void removeItemByID(const LLUUID& id); - LLPanel * findItemByID(const LLUUID& id); - - // Operating with outfit - virtual void setVisible(bool visible); - void adjustWindowPosition(); - /*virtual*/ void setDocked(bool docked, bool pop_on_undock = true); - // override LLFloater's minimization according to EXT-1216 - /*virtual*/ void setMinimized(bool minimize); - /*virtual*/ void handleReshape(const LLRect& rect, bool by_user); - - void onStartUpToastClick(S32 x, S32 y, MASK mask); - - void setSysWellChiclet(LLSysWellChiclet* chiclet); - - // size constants for the window and for its elements - static constexpr S32 MAX_WINDOW_HEIGHT = 200; - static constexpr S32 MIN_WINDOW_WIDTH = 318; - -protected: - // init Window's channel - virtual void initChannel(); - - const std::string NOTIFICATION_WELL_ANCHOR_NAME; - const std::string IM_WELL_ANCHOR_NAME; - virtual const std::string& getAnchorViewName() = 0; - - void reshapeWindow(); - - // pointer to a corresponding channel's instance - LLNotificationsUI::LLScreenChannel* mChannel; - LLFlatListView* mMessageList; - - /** - * Reference to an appropriate Well chiclet to release "new message" state. EXT-3147 - */ - LLSysWellChiclet* mSysWellChiclet; - - bool mIsReshapedByUser; -}; - -/** - * Class intended to manage incoming messages in IM chats. - * - * It contains a list list of all active IM sessions. - */ -class LLIMWellWindow : public LLSysWellWindow, LLInitClass -{ -public: - LLIMWellWindow(const LLSD& key); - ~LLIMWellWindow(); - - static LLIMWellWindow* getInstance(const LLSD& key = LLSD()); - static LLIMWellWindow* findInstance(const LLSD& key = LLSD()); - static void initClass() { getInstance(); } - - /*virtual*/ bool postBuild(); - - void addObjectRow(const LLUUID& notification_id, bool new_message = false); - void removeObjectRow(const LLUUID& notification_id); - void closeAll(); - -protected: - /*virtual*/ const std::string& getAnchorViewName() { return IM_WELL_ANCHOR_NAME; } - -private: - LLChiclet* findObjectChiclet(const LLUUID& notification_id); - - bool confirmCloseAll(const LLSD& notification, const LLSD& response); - void closeAllImpl(); - - class ObjectRowPanel: public LLPanel - { - public: - ObjectRowPanel(const LLUUID& notification_id, bool new_message = false); - virtual ~ObjectRowPanel(); - /*virtual*/ void onMouseEnter(S32 x, S32 y, MASK mask); - /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); - - private: - void onClosePanel(); - void initChiclet(const LLUUID& notification_id, bool new_message = false); - - public: - LLIMChiclet* mChiclet; - private: - LLButton* mCloseBtn; - }; -}; - -#endif // LL_LLSYSWELLWINDOW_H - - - +/** + * @file llsyswellwindow.h + * @brief // TODO + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLSYSWELLWINDOW_H +#define LL_LLSYSWELLWINDOW_H + +#include "llimview.h" +#include "llnotifications.h" +#include "llscreenchannel.h" +#include "llsyswellitem.h" +#include "lltransientdockablefloater.h" +#include "llinitdestroyclass.h" + +class LLAvatarName; +class LLChiclet; +class LLFlatListView; +class LLIMChiclet; +class LLScriptChiclet; +class LLSysWellChiclet; + +class LLSysWellWindow : public LLTransientDockableFloater +{ +public: + LOG_CLASS(LLSysWellWindow); + + LLSysWellWindow(const LLSD& key); + virtual ~LLSysWellWindow(); + bool postBuild(); + + // other interface functions + // check is window empty + bool isWindowEmpty(); + + // Operating with items + void removeItemByID(const LLUUID& id); + LLPanel * findItemByID(const LLUUID& id); + + // Operating with outfit + virtual void setVisible(bool visible); + void adjustWindowPosition(); + /*virtual*/ void setDocked(bool docked, bool pop_on_undock = true); + // override LLFloater's minimization according to EXT-1216 + /*virtual*/ void setMinimized(bool minimize); + /*virtual*/ void handleReshape(const LLRect& rect, bool by_user); + + void onStartUpToastClick(S32 x, S32 y, MASK mask); + + void setSysWellChiclet(LLSysWellChiclet* chiclet); + + // size constants for the window and for its elements + static constexpr S32 MAX_WINDOW_HEIGHT = 200; + static constexpr S32 MIN_WINDOW_WIDTH = 318; + +protected: + // init Window's channel + virtual void initChannel(); + + const std::string NOTIFICATION_WELL_ANCHOR_NAME; + const std::string IM_WELL_ANCHOR_NAME; + virtual const std::string& getAnchorViewName() = 0; + + void reshapeWindow(); + + // pointer to a corresponding channel's instance + LLNotificationsUI::LLScreenChannel* mChannel; + LLFlatListView* mMessageList; + + /** + * Reference to an appropriate Well chiclet to release "new message" state. EXT-3147 + */ + LLSysWellChiclet* mSysWellChiclet; + + bool mIsReshapedByUser; +}; + +/** + * Class intended to manage incoming messages in IM chats. + * + * It contains a list list of all active IM sessions. + */ +class LLIMWellWindow : public LLSysWellWindow, LLInitClass +{ +public: + LLIMWellWindow(const LLSD& key); + ~LLIMWellWindow(); + + static LLIMWellWindow* getInstance(const LLSD& key = LLSD()); + static LLIMWellWindow* findInstance(const LLSD& key = LLSD()); + static void initClass() { getInstance(); } + + /*virtual*/ bool postBuild(); + + void addObjectRow(const LLUUID& notification_id, bool new_message = false); + void removeObjectRow(const LLUUID& notification_id); + void closeAll(); + +protected: + /*virtual*/ const std::string& getAnchorViewName() { return IM_WELL_ANCHOR_NAME; } + +private: + LLChiclet* findObjectChiclet(const LLUUID& notification_id); + + bool confirmCloseAll(const LLSD& notification, const LLSD& response); + void closeAllImpl(); + + class ObjectRowPanel: public LLPanel + { + public: + ObjectRowPanel(const LLUUID& notification_id, bool new_message = false); + virtual ~ObjectRowPanel(); + /*virtual*/ void onMouseEnter(S32 x, S32 y, MASK mask); + /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask); + + private: + void onClosePanel(); + void initChiclet(const LLUUID& notification_id, bool new_message = false); + + public: + LLIMChiclet* mChiclet; + private: + LLButton* mCloseBtn; + }; +}; + +#endif // LL_LLSYSWELLWINDOW_H + + + diff --git a/indra/newview/lltexturecache.cpp b/indra/newview/lltexturecache.cpp index 941e10b858..cb08421606 100644 --- a/indra/newview/lltexturecache.cpp +++ b/indra/newview/lltexturecache.cpp @@ -1,2308 +1,2308 @@ -/** - * @file lltexturecache.cpp - * @brief Object which handles local texture caching - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lltexturecache.h" - -#include "llapr.h" -#include "lldir.h" -#include "llimage.h" -#include "llimagej2c.h" // for version control -#include "lllfsthread.h" -#include "llviewercontrol.h" - -// Included to allow LLTextureCache::purgeTextures() to pause watchdog timeout -#include "llappviewer.h" -#include "llmemory.h" - -// Cache organization: -// cache/texture.entries -// Unordered array of Entry structs -// cache/texture.cache -// First TEXTURE_CACHE_ENTRY_SIZE bytes of each texture in texture.entries in same order -// cache/textures/[0-F]/UUID.texture -// Actual texture body files - -//note: there is no good to define 1024 for TEXTURE_CACHE_ENTRY_SIZE while FIRST_PACKET_SIZE is 600 on sim side. -const S32 TEXTURE_CACHE_ENTRY_SIZE = FIRST_PACKET_SIZE;//1024; -const F32 TEXTURE_CACHE_PURGE_AMOUNT = .20f; // % amount to reduce the cache by when it exceeds its limit -const F32 TEXTURE_CACHE_LRU_SIZE = .10f; // % amount for LRU list (low overhead to regenerate) -const S32 TEXTURE_FAST_CACHE_ENTRY_OVERHEAD = sizeof(S32) * 4; //w, h, c, level -const S32 TEXTURE_FAST_CACHE_DATA_SIZE = 16 * 16 * 4; -const S32 TEXTURE_FAST_CACHE_ENTRY_SIZE = TEXTURE_FAST_CACHE_DATA_SIZE + TEXTURE_FAST_CACHE_ENTRY_OVERHEAD; -const F32 TEXTURE_LAZY_PURGE_TIME_LIMIT = .004f; // 4ms. Would be better to autoadjust, but there is a major cache rework in progress. -const F32 TEXTURE_PRUNING_MAX_TIME = 15.f; - -class LLTextureCacheWorker : public LLWorkerClass -{ - friend class LLTextureCache; - -private: - class ReadResponder : public LLLFSThread::Responder - { - public: - ReadResponder(LLTextureCache* cache, handle_t handle) : mCache(cache), mHandle(handle) {} - ~ReadResponder() {} - void completed(S32 bytes) - { - mCache->lockWorkers(); - LLTextureCacheWorker* reader = mCache->getReader(mHandle); - if (reader) reader->ioComplete(bytes); - mCache->unlockWorkers(); - } - LLTextureCache* mCache; - LLTextureCacheWorker::handle_t mHandle; - }; - - class WriteResponder : public LLLFSThread::Responder - { - public: - WriteResponder(LLTextureCache* cache, handle_t handle) : mCache(cache), mHandle(handle) {} - ~WriteResponder() {} - void completed(S32 bytes) - { - mCache->lockWorkers(); - LLTextureCacheWorker* writer = mCache->getWriter(mHandle); - if (writer) writer->ioComplete(bytes); - mCache->unlockWorkers(); - } - LLTextureCache* mCache; - LLTextureCacheWorker::handle_t mHandle; - }; - -public: - LLTextureCacheWorker(LLTextureCache* cache, const LLUUID& id, - const U8* data, S32 datasize, S32 offset, - S32 imagesize, // for writes - LLTextureCache::Responder* responder) - : LLWorkerClass(cache, "LLTextureCacheWorker"), - mID(id), - mCache(cache), - mReadData(NULL), - mWriteData(data), - mDataSize(datasize), - mOffset(offset), - mImageSize(imagesize), - mImageFormat(IMG_CODEC_J2C), - mImageLocal(false), - mResponder(responder), - mFileHandle(LLLFSThread::nullHandle()), - mBytesToRead(0), - mBytesRead(0) - { - } - ~LLTextureCacheWorker() - { - llassert_always(!haveWork()); - ll_aligned_free_16(mReadData); - } - - // override this interface - virtual bool doRead() = 0; - virtual bool doWrite() = 0; - - virtual bool doWork(S32 param); // Called from LLWorkerThread::processRequest() - - handle_t read() { addWork(0); return mRequestHandle; } - handle_t write() { addWork(1); return mRequestHandle; } - bool complete() { return checkWork(); } - void ioComplete(S32 bytes) - { - mBytesRead = bytes; - } - -private: - virtual void startWork(S32 param); // called from addWork() (MAIN THREAD) - virtual void finishWork(S32 param, bool completed); // called from finishRequest() (WORK THREAD) - virtual void endWork(S32 param, bool aborted); // called from doWork() (MAIN THREAD) - -protected: - LLTextureCache* mCache; - LLUUID mID; - - U8* mReadData; - const U8* mWriteData; - S32 mDataSize; - S32 mOffset; - S32 mImageSize; - EImageCodec mImageFormat; - bool mImageLocal; - LLPointer mResponder; - LLLFSThread::handle_t mFileHandle; - S32 mBytesToRead; - LLAtomicS32 mBytesRead; -}; - -class LLTextureCacheLocalFileWorker : public LLTextureCacheWorker -{ -public: - LLTextureCacheLocalFileWorker(LLTextureCache* cache, const std::string& filename, const LLUUID& id, - U8* data, S32 datasize, S32 offset, - S32 imagesize, // for writes - LLTextureCache::Responder* responder) - : LLTextureCacheWorker(cache, id, data, datasize, offset, imagesize, responder), - mFileName(filename) - - { - } - - virtual bool doRead(); - virtual bool doWrite(); - -private: - std::string mFileName; -}; - -bool LLTextureCacheLocalFileWorker::doRead() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - S32 local_size = LLAPRFile::size(mFileName, mCache->getLocalAPRFilePool()); - - if (local_size > 0 && mFileName.size() > 4) - { - mDataSize = local_size; // Only a complete file is valid - - std::string extension = mFileName.substr(mFileName.size() - 3, 3); - - mImageFormat = LLImageBase::getCodecFromExtension(extension); - - if (mImageFormat == IMG_CODEC_INVALID) - { -// LL_WARNS() << "Unrecognized file extension " << extension << " for local texture " << mFileName << LL_ENDL; - mDataSize = 0; // no data - return true; - } - } - else - { - // file doesn't exist - mDataSize = 0; // no data - return true; - } - - if (!mDataSize || mDataSize > local_size) - { - mDataSize = local_size; - } - mReadData = (U8*)ll_aligned_malloc_16(mDataSize); - - S32 bytes_read = LLAPRFile::readEx(mFileName, mReadData, mOffset, mDataSize, mCache->getLocalAPRFilePool()); - - if (bytes_read != mDataSize) - { -// LL_WARNS() << "Error reading file from local cache: " << mFileName -// << " Bytes: " << mDataSize << " Offset: " << mOffset -// << " / " << mDataSize << LL_ENDL; - mDataSize = 0; - ll_aligned_free_16(mReadData); - mReadData = NULL; - } - else - { - mImageSize = local_size; - mImageLocal = true; - } - return true; -} - -bool LLTextureCacheLocalFileWorker::doWrite() -{ - // no writes for local files - return false; -} - -class LLTextureCacheRemoteWorker : public LLTextureCacheWorker -{ -public: - LLTextureCacheRemoteWorker(LLTextureCache* cache, const LLUUID& id, - const U8* data, S32 datasize, S32 offset, - S32 imagesize, // for writes - LLPointer raw, S32 discardlevel, - LLTextureCache::Responder* responder) - : LLTextureCacheWorker(cache, id, data, datasize, offset, imagesize, responder), - mState(INIT), - mRawImage(raw), - mRawDiscardLevel(discardlevel) - { - } - - virtual bool doRead(); - virtual bool doWrite(); - -private: - enum e_state - { - INIT = 0, - LOCAL = 1, - CACHE = 2, - HEADER = 3, - BODY = 4 - }; - - e_state mState; - LLPointer mRawImage; - S32 mRawDiscardLevel; -}; - - -//virtual -void LLTextureCacheWorker::startWork(S32 param) -{ -} - -// This is where a texture is read from the cache system (header and body) -// Current assumption are: -// - the whole data are in a raw form, will be stored at mReadData -// - the size of this raw data is mDataSize and can be smaller than TEXTURE_CACHE_ENTRY_SIZE (the size of a record in the header cache) -// - the code supports offset reading but this is actually never exercised in the viewer -bool LLTextureCacheRemoteWorker::doRead() -{ - LL_PROFILE_ZONE_SCOPED; - bool done = false; - S32 idx = -1; - - S32 local_size = 0; - std::string local_filename; - - // First state / stage : find out if the file is local - if (mState == INIT) - { -#if 0 - std::string filename = mCache->getLocalFileName(mID); - // Is it a JPEG2000 file? - { - local_filename = filename + ".j2c"; - local_size = LLAPRFile::size(local_filename, mCache->getLocalAPRFilePool()); - if (local_size > 0) - { - mImageFormat = IMG_CODEC_J2C; - } - } - // If not, is it a jpeg file? - if (local_size == 0) - { - local_filename = filename + ".jpg"; - local_size = LLAPRFile::size(local_filename, mCache->getLocalAPRFilePool()); - if (local_size > 0) - { - mImageFormat = IMG_CODEC_JPEG; - mDataSize = local_size; // Only a complete .jpg file is valid - } - } - // Hmm... What about a targa file? (used for UI texture mostly) - if (local_size == 0) - { - local_filename = filename + ".tga"; - local_size = LLAPRFile::size(local_filename, mCache->getLocalAPRFilePool()); - if (local_size > 0) - { - mImageFormat = IMG_CODEC_TGA; - mDataSize = local_size; // Only a complete .tga file is valid - } - } - // Determine the next stage: if we found a file, then LOCAL else CACHE - mState = (local_size > 0 ? LOCAL : CACHE); - - llassert_always(mState == CACHE) ; -#else - mState = CACHE; -#endif - } - - // Second state / stage : if the file is local, load it and leave - if (!done && (mState == LOCAL)) - { - llassert(local_size != 0); // we're assuming there is a non empty local file here... - if (!mDataSize || mDataSize > local_size) - { - mDataSize = local_size; - } - // Allocate read buffer - mReadData = (U8*)ll_aligned_malloc_16(mDataSize); - - if (mReadData) - { - S32 bytes_read = LLAPRFile::readEx( local_filename, - mReadData, - mOffset, - mDataSize, - mCache->getLocalAPRFilePool()); - - if (bytes_read != mDataSize) - { - LL_WARNS() << "Error reading file from local cache: " << local_filename - << " Bytes: " << mDataSize << " Offset: " << mOffset - << " / " << mDataSize << LL_ENDL; - mDataSize = 0; - ll_aligned_free_16(mReadData); - mReadData = NULL; - } - else - { - mImageSize = local_size; - mImageLocal = true; - } - } - else - { - LL_WARNS() << "Error allocating memory for cache: " << local_filename - << " of size: " << mDataSize << LL_ENDL; - mDataSize = 0; - } - // We're done... - done = true; - } - - // Second state / stage : identify the cache or not... - if (!done && (mState == CACHE)) - { - LLTextureCache::Entry entry ; - idx = mCache->getHeaderCacheEntry(mID, entry); - if (idx < 0) - { - // The texture is *not* cached. We're done here... - mDataSize = 0; // no data - done = true; - } - else - { - mImageSize = entry.mImageSize ; - // If the read offset is bigger than the header cache, we read directly from the body - // Note that currently, we *never* read with offset from the cache, so the result is *always* HEADER - mState = mOffset < TEXTURE_CACHE_ENTRY_SIZE ? HEADER : BODY; - } - } - - // Third state / stage : read data from the header cache (texture.entries) file - if (!done && (mState == HEADER)) - { - llassert_always(idx >= 0); // we need an entry here or reading the header makes no sense - llassert_always(mOffset < TEXTURE_CACHE_ENTRY_SIZE); - S32 offset = idx * TEXTURE_CACHE_ENTRY_SIZE + mOffset; - // Compute the size we need to read (in bytes) - S32 size = TEXTURE_CACHE_ENTRY_SIZE - mOffset; - size = llmin(size, mDataSize); - // Allocate the read buffer - mReadData = (U8*)ll_aligned_malloc_16(size); - if (mReadData) - { - S32 bytes_read = LLAPRFile::readEx(mCache->mHeaderDataFileName, - mReadData, offset, size, mCache->getLocalAPRFilePool()); - if (bytes_read != size) - { - LL_WARNS() << "LLTextureCacheWorker: " << mID - << " incorrect number of bytes read from header: " << bytes_read - << " / " << size << LL_ENDL; - ll_aligned_free_16(mReadData); - mReadData = NULL; - mDataSize = -1; // failed - done = true; - } - // If we already read all we expected, we're actually done - if (mDataSize <= bytes_read) - { - done = true; - } - else - { - mState = BODY; - } - } - else - { - LL_WARNS() << "LLTextureCacheWorker: " << mID - << " failed to allocate memory for reading: " << mDataSize << LL_ENDL; - mReadData = NULL; - mDataSize = -1; // failed - done = true; - } - } - - // Fourth state / stage : read the rest of the data from the UUID based cached file - if (!done && (mState == BODY)) - { - std::string filename = mCache->getTextureFileName(mID); - S32 filesize = LLAPRFile::size(filename, mCache->getLocalAPRFilePool()); - - if (filesize && (filesize + TEXTURE_CACHE_ENTRY_SIZE) > mOffset) - { - S32 max_datasize = TEXTURE_CACHE_ENTRY_SIZE + filesize - mOffset; - mDataSize = llmin(max_datasize, mDataSize); - - S32 data_offset, file_size, file_offset; - - // Reserve the whole data buffer first - U8* data = (U8*)ll_aligned_malloc_16(mDataSize); - if (data) - { - // Set the data file pointers taking the read offset into account. 2 cases: - if (mOffset < TEXTURE_CACHE_ENTRY_SIZE) - { - // Offset within the header record. That means we read something from the header cache. - // Note: most common case is (mOffset = 0), so this is the "normal" code path. - data_offset = TEXTURE_CACHE_ENTRY_SIZE - mOffset; // i.e. TEXTURE_CACHE_ENTRY_SIZE if mOffset nul (common case) - file_offset = 0; - file_size = mDataSize - data_offset; - // Copy the raw data we've been holding from the header cache into the new sized buffer - llassert_always(mReadData); - memcpy(data, mReadData, data_offset); - ll_aligned_free_16(mReadData); - mReadData = NULL; - } - else - { - // Offset bigger than the header record. That means we haven't read anything yet. - data_offset = 0; - file_offset = mOffset - TEXTURE_CACHE_ENTRY_SIZE; - file_size = mDataSize; - // No data from header cache to copy in that case, we skipped it all - } - - // Now use that buffer as the object read buffer - llassert_always(mReadData == NULL); - mReadData = data; - - // Read the data at last - S32 bytes_read = LLAPRFile::readEx(filename, - mReadData + data_offset, - file_offset, file_size, - mCache->getLocalAPRFilePool()); - if (bytes_read != file_size) - { - LL_WARNS() << "LLTextureCacheWorker: " << mID - << " incorrect number of bytes read from body: " << bytes_read - << " / " << file_size << LL_ENDL; - ll_aligned_free_16(mReadData); - mReadData = NULL; - mDataSize = -1; // failed - done = true; - } - } - else - { - LL_WARNS() << "LLTextureCacheWorker: " << mID - << " failed to allocate memory for reading: " << mDataSize << LL_ENDL; - ll_aligned_free_16(mReadData); - mReadData = NULL; - mDataSize = -1; // failed - done = true; - } - } - else - { - // No body, we're done. - mDataSize = llmax(TEXTURE_CACHE_ENTRY_SIZE - mOffset, 0); - LL_DEBUGS() << "No body file for: " << filename << LL_ENDL; - } - // Nothing else to do at that point... - done = true; - } - - // Clean up and exit - return done; -} - -// This is where *everything* about a texture is written down in the cache system (entry map, header and body) -// Current assumption are: -// - the whole data are in a raw form, starting at mWriteData -// - the size of this raw data is mDataSize and can be smaller than TEXTURE_CACHE_ENTRY_SIZE (the size of a record in the header cache) -// - the code *does not* support offset writing so there are no difference between buffer addresses and start of data -bool LLTextureCacheRemoteWorker::doWrite() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - bool done = false; - S32 idx = -1; - - // First state / stage : check that what we're trying to cache is in an OK shape - if (mState == INIT) - { - if ((mOffset != 0) // We currently do not support write offsets - || (mDataSize <= 0) // Things will go badly wrong if mDataSize is nul or negative... - || (mImageSize < mDataSize) - || (mRawDiscardLevel < 0) - || (mRawImage->isBufferInvalid())) // decode failed or malfunctioned, don't write - { - LL_WARNS() << "INIT state check failed for image: " << mID << " Size: " << mImageSize << " DataSize: " << mDataSize << " Discard:" << mRawDiscardLevel << LL_ENDL; - mDataSize = -1; // failed - done = true; - } - else - { - mState = CACHE; - } - } - - // No LOCAL state for write(): because it doesn't make much sense to cache a local file... - - // Second state / stage : set an entry in the headers entry (texture.entries) file - if (!done && (mState == CACHE)) - { - bool alreadyCached = false; - LLTextureCache::Entry entry; - - // Checks if this image is already in the entry list - idx = mCache->getHeaderCacheEntry(mID, entry); - if(idx < 0) - { - idx = mCache->setHeaderCacheEntry(mID, entry, mImageSize, mDataSize); // create the new entry. - if(idx >= 0) - { - // write to the fast cache. - // mRawImage is not entirely safe here since it is a pointer to one owned by cache worker, - // it could have been retrieved via getRequestFinished() and then modified. - // If writeToFastCache crashes, something is wrong around fetch worker. - if(!mCache->writeToFastCache(mID, idx, mRawImage, mRawDiscardLevel)) - { - LL_WARNS() << "writeToFastCache failed" << LL_ENDL; - mDataSize = -1; // failed - done = true; - } - } - } - else - { - alreadyCached = mCache->updateEntry(idx, entry, mImageSize, mDataSize); // update the existing entry. - } - - if (!done) - { - if (idx < 0) - { - LL_WARNS() << "LLTextureCacheWorker: " << mID - << " Unable to create header entry for writing!" << LL_ENDL; - mDataSize = -1; // failed - done = true; - } - else - { - if (alreadyCached && (mDataSize <= TEXTURE_CACHE_ENTRY_SIZE)) - { - // Small texture already cached case: we're done with writing - done = true; - } - else - { - // If the texture has already been cached, we don't resave the header and go directly to the body part - mState = alreadyCached ? BODY : HEADER; - } - } - } - } - - - // Third stage / state : write the header record in the header file (texture.cache) - if (!done && (mState == HEADER)) - { - if (idx < 0) // we need an entry here or storing the header makes no sense - { - LL_WARNS() << "index check failed" << LL_ENDL; - mDataSize = -1; // failed - done = true; - } - else - { - S32 offset = idx * TEXTURE_CACHE_ENTRY_SIZE; // skip to the correct spot in the header file - S32 size = TEXTURE_CACHE_ENTRY_SIZE; // record size is fixed for the header - S32 bytes_written; - - if (mDataSize < TEXTURE_CACHE_ENTRY_SIZE) - { - // We need to write a full record in the header cache so, if the amount of data is smaller - // than a record, we need to transfer the data to a buffer padded with 0 and write that - U8* padBuffer = (U8*)ll_aligned_malloc_16(TEXTURE_CACHE_ENTRY_SIZE); - memset(padBuffer, 0, TEXTURE_CACHE_ENTRY_SIZE); // Init with zeros - memcpy(padBuffer, mWriteData, mDataSize); // Copy the write buffer - bytes_written = LLAPRFile::writeEx(mCache->mHeaderDataFileName, padBuffer, offset, size, mCache->getLocalAPRFilePool()); - ll_aligned_free_16(padBuffer); - } - else - { - // Write the header record (== first TEXTURE_CACHE_ENTRY_SIZE bytes of the raw file) in the header file - bytes_written = LLAPRFile::writeEx(mCache->mHeaderDataFileName, mWriteData, offset, size, mCache->getLocalAPRFilePool()); - } - - if (bytes_written <= 0) - { - LL_WARNS() << "LLTextureCacheWorker: " << mID - << " Unable to write header entry!" << LL_ENDL; - mDataSize = -1; // failed - done = true; - } - - // If we wrote everything (may be more with padding) in the header cache, - // we're done so we don't have a body to store - if (mDataSize <= bytes_written) - { - done = true; - } - else - { - mState = BODY; - } - } - } - - // Fourth stage / state : write the body file, i.e. the rest of the texture in a "UUID" file name - if (!done && (mState == BODY)) - { - if (mDataSize <= TEXTURE_CACHE_ENTRY_SIZE) // wouldn't make sense to be here otherwise... - { - LL_WARNS() << "mDataSize check failed" << LL_ENDL; - mDataSize = -1; // failed - done = true; - } - else - { - S32 file_size = mDataSize - TEXTURE_CACHE_ENTRY_SIZE; - - { - // build the cache file name from the UUID - std::string filename = mCache->getTextureFileName(mID); - // LL_INFOS() << "Writing Body: " << filename << " Bytes: " << file_offset+file_size << LL_ENDL; - S32 bytes_written = LLAPRFile::writeEx(filename, - mWriteData + TEXTURE_CACHE_ENTRY_SIZE, - 0, file_size, - mCache->getLocalAPRFilePool()); - if (bytes_written <= 0) - { - LL_WARNS() << "LLTextureCacheWorker: " << mID - << " incorrect number of bytes written to body: " << bytes_written - << " / " << file_size << LL_ENDL; - mDataSize = -1; // failed - done = true; - } - } - - // Nothing else to do at that point... - done = true; - } - } - mRawImage = NULL; - - // Clean up and exit - return done; -} - -//virtual -bool LLTextureCacheWorker::doWork(S32 param) -{ - LL_PROFILE_ZONE_SCOPED; - bool res = false; - if (param == 0) // read - { - res = doRead(); - } - else if (param == 1) // write - { - res = doWrite(); - } - else - { - llassert_always(0); - } - return res; -} - -//virtual (WORKER THREAD) -void LLTextureCacheWorker::finishWork(S32 param, bool completed) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if (mResponder.notNull()) - { - bool success = (completed && mDataSize > 0); - if (param == 0) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("tcwfw - read"); - // read - if (success) - { - mResponder->setData(mReadData, mDataSize, mImageSize, mImageFormat, mImageLocal); - mReadData = NULL; // responder owns data - mDataSize = 0; - } - else - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("tcwfw - read fail"); - ll_aligned_free_16(mReadData); - mReadData = NULL; - } - } - else - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("tcwfw - write"); - // write - mWriteData = NULL; // we never owned data - mDataSize = 0; - } - mCache->addCompleted(mResponder, success); - } -} - -//virtual (MAIN THREAD) -void LLTextureCacheWorker::endWork(S32 param, bool aborted) -{ - LL_PROFILE_ZONE_SCOPED; - if (aborted) - { - // Let the destructor handle any cleanup - return; - } - switch(param) - { - default: - case 0: // read - case 1: // write - { - if (mDataSize < 0) - { - // failed - mCache->removeFromCache(mID); - } - break; - } - } -} - -////////////////////////////////////////////////////////////////////////////// - -LLTextureCache::LLTextureCache(bool threaded) - : LLWorkerThread("TextureCache", threaded), - mWorkersMutex(), - mHeaderMutex(), - mListMutex(), - mFastCacheMutex(), - mHeaderAPRFile(NULL), - mReadOnly(true), //do not allow to change the texture cache until setReadOnly() is called. - mTexturesSizeTotal(0), - mDoPurge(false), - mFastCachep(NULL), - mFastCachePoolp(NULL), - mFastCachePadBuffer(NULL) -{ - mHeaderAPRFilePoolp = new LLVolatileAPRPool(); // is_local = true, because this pool is for headers, headers are under own mutex -} - -LLTextureCache::~LLTextureCache() -{ - clearDeleteList() ; - writeUpdatedEntries() ; - delete mFastCachep; - delete mFastCachePoolp; - delete mHeaderAPRFilePoolp; - ll_aligned_free_16(mFastCachePadBuffer); -} - -////////////////////////////////////////////////////////////////////////////// - -//virtual -size_t LLTextureCache::update(F32 max_time_ms) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - static LLFrameTimer timer ; - static const F32 MAX_TIME_INTERVAL = 300.f ; //seconds. - - size_t res; - res = LLWorkerThread::update(max_time_ms); - - mListMutex.lock(); - handle_list_t priorty_list = mPrioritizeWriteList; // copy list - mPrioritizeWriteList.clear(); - responder_list_t completed_list = mCompletedList; // copy list - mCompletedList.clear(); - mListMutex.unlock(); - - // call 'completed' with workers list unlocked (may call readComplete() or writeComplete() - for (responder_list_t::iterator iter1 = completed_list.begin(); - iter1 != completed_list.end(); ++iter1) - { - Responder *responder = iter1->first; - bool success = iter1->second; - responder->completed(success); - } - - if(!res && timer.getElapsedTimeF32() > MAX_TIME_INTERVAL) - { - timer.reset() ; - writeUpdatedEntries() ; - } - - return res; -} - -////////////////////////////////////////////////////////////////////////////// -// search for local copy of UUID-based image file -std::string LLTextureCache::getLocalFileName(const LLUUID& id) -{ - // Does not include extension - std::string idstr = id.asString(); - // TODO: should we be storing cached textures in skin directory? - std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_LOCAL_ASSETS, idstr); - return filename; -} - -std::string LLTextureCache::getTextureFileName(const LLUUID& id) -{ - std::string idstr = id.asString(); - std::string delem = gDirUtilp->getDirDelimiter(); - std::string filename = mTexturesDirName + delem + idstr[0] + delem + idstr + ".texture"; - return filename; -} - -//debug -bool LLTextureCache::isInCache(const LLUUID& id) -{ - LLMutexLock lock(&mHeaderMutex); - id_map_t::const_iterator iter = mHeaderIDMap.find(id); - - return (iter != mHeaderIDMap.end()) ; -} - -//debug -bool LLTextureCache::isInLocal(const LLUUID& id) -{ - S32 local_size = 0; - std::string local_filename; - - std::string filename = getLocalFileName(id); - // Is it a JPEG2000 file? - { - local_filename = filename + ".j2c"; - local_size = LLAPRFile::size(local_filename, getLocalAPRFilePool()); - if (local_size > 0) - { - return true ; - } - } - - // If not, is it a jpeg file? - { - local_filename = filename + ".jpg"; - local_size = LLAPRFile::size(local_filename, getLocalAPRFilePool()); - if (local_size > 0) - { - return true ; - } - } - - // Hmm... What about a targa file? (used for UI texture mostly) - { - local_filename = filename + ".tga"; - local_size = LLAPRFile::size(local_filename, getLocalAPRFilePool()); - if (local_size > 0) - { - return true ; - } - } - - return false ; -} -////////////////////////////////////////////////////////////////////////////// - -//static -F32 LLTextureCache::sHeaderCacheVersion = 1.71f; -U32 LLTextureCache::sCacheMaxEntries = 1024 * 1024; //~1 million textures. -S64 LLTextureCache::sCacheMaxTexturesSize = 0; // no limit -std::string LLTextureCache::sHeaderCacheEncoderVersion = LLImageJ2C::getEngineInfo(); - -#if defined(ADDRESS_SIZE) -U32 LLTextureCache::sHeaderCacheAddressSize = ADDRESS_SIZE; -#else -U32 LLTextureCache::sHeaderCacheAddressSize = 32; -#endif - -const char* entries_filename = "texture.entries"; -const char* cache_filename = "texture.cache"; -const char* old_textures_dirname = "textures"; -//change the location of the texture cache to prevent from being deleted by old version viewers. -const char* textures_dirname = "texturecache"; -const char* fast_cache_filename = "FastCache.cache"; - -void LLTextureCache::setDirNames(ELLPath location) -{ - std::string delem = gDirUtilp->getDirDelimiter(); - - mHeaderEntriesFileName = gDirUtilp->getExpandedFilename(location, textures_dirname, entries_filename); - mHeaderDataFileName = gDirUtilp->getExpandedFilename(location, textures_dirname, cache_filename); - mTexturesDirName = gDirUtilp->getExpandedFilename(location, textures_dirname); - mFastCacheFileName = gDirUtilp->getExpandedFilename(location, textures_dirname, fast_cache_filename); -} - -void LLTextureCache::purgeCache(ELLPath location, bool remove_dir) -{ - LLMutexLock lock(&mHeaderMutex); - - if (!mReadOnly) - { - setDirNames(location); - llassert_always(mHeaderAPRFile == NULL); - - //remove the legacy cache if exists - std::string texture_dir = mTexturesDirName ; - mTexturesDirName = gDirUtilp->getExpandedFilename(location, old_textures_dirname); - if(LLFile::isdir(mTexturesDirName)) - { - std::string file_name = gDirUtilp->getExpandedFilename(location, entries_filename); - // mHeaderAPRFilePoolp because we are under header mutex, and can be in main thread - LLAPRFile::remove(file_name, mHeaderAPRFilePoolp); - - file_name = gDirUtilp->getExpandedFilename(location, cache_filename); - LLAPRFile::remove(file_name, mHeaderAPRFilePoolp); - - purgeAllTextures(true); - } - mTexturesDirName = texture_dir ; - } - - //remove the current texture cache. - purgeAllTextures(remove_dir); -} - -//is called in the main thread before initCache(...) is called. -void LLTextureCache::setReadOnly(bool read_only) -{ - mReadOnly = read_only ; -} - -// Called in the main thread. -// Returns the unused amount of max_size if any -S64 LLTextureCache::initCache(ELLPath location, S64 max_size, bool texture_cache_mismatch) -{ - llassert_always(getPending() == 0) ; //should not start accessing the texture cache before initialized. - - S64 entries_size = (max_size * 36) / 100; //0.36 * max_size - S64 max_entries = entries_size / (TEXTURE_CACHE_ENTRY_SIZE + TEXTURE_FAST_CACHE_ENTRY_SIZE); - sCacheMaxEntries = (S32)(llmin((S64)sCacheMaxEntries, max_entries)); - entries_size = sCacheMaxEntries * (TEXTURE_CACHE_ENTRY_SIZE + TEXTURE_FAST_CACHE_ENTRY_SIZE); - max_size -= entries_size; - if (sCacheMaxTexturesSize > 0) - sCacheMaxTexturesSize = llmin(sCacheMaxTexturesSize, max_size); - else - sCacheMaxTexturesSize = max_size; - max_size -= sCacheMaxTexturesSize; - - LL_INFOS("TextureCache") << "Headers: " << sCacheMaxEntries - << " Textures size: " << sCacheMaxTexturesSize / (1024 * 1024) << " MB" << LL_ENDL; - - setDirNames(location); - - if(texture_cache_mismatch) - { - //if readonly, disable the texture cache, - //otherwise wipe out the texture cache. - purgeAllTextures(true); - - if(mReadOnly) - { - return max_size ; - } - } - - if (!mReadOnly) - { - LLFile::mkdir(mTexturesDirName); - - const char* subdirs = "0123456789abcdef"; - for (S32 i=0; i<16; i++) - { - std::string dirname = mTexturesDirName + gDirUtilp->getDirDelimiter() + subdirs[i]; - LLFile::mkdir(dirname); - } - } - readHeaderCache(); - purgeTextures(true); // calc mTexturesSize and make some room in the texture cache if we need it - - llassert_always(getPending() == 0) ; //should not start accessing the texture cache before initialized. - openFastCache(true); - - return max_size; // unused cache space -} - -//---------------------------------------------------------------------------- -// mHeaderMutex must be locked for the following functions! - -LLAPRFile* LLTextureCache::openHeaderEntriesFile(bool readonly, S32 offset) -{ - llassert_always(mHeaderAPRFile == NULL); - apr_int32_t flags = readonly ? APR_READ|APR_BINARY : APR_READ|APR_WRITE|APR_BINARY; - mHeaderAPRFile = new LLAPRFile(mHeaderEntriesFileName, flags, mHeaderAPRFilePoolp); - if(offset > 0) - { - mHeaderAPRFile->seek(APR_SET, offset); - } - return mHeaderAPRFile; -} - -void LLTextureCache::closeHeaderEntriesFile() -{ - if(!mHeaderAPRFile) - { - return ; - } - - delete mHeaderAPRFile; - mHeaderAPRFile = NULL; -} - -void LLTextureCache::readEntriesHeader() -{ - // mHeaderEntriesInfo initializes to default values so safe not to read it - llassert_always(mHeaderAPRFile == NULL); - if (LLAPRFile::isExist(mHeaderEntriesFileName, mHeaderAPRFilePoolp)) - { - LLAPRFile::readEx(mHeaderEntriesFileName, (U8*)&mHeaderEntriesInfo, 0, sizeof(EntriesInfo), - mHeaderAPRFilePoolp); - } - else //create an empty entries header. - { - setEntriesHeader(); - writeEntriesHeader() ; - } -} - -void LLTextureCache::setEntriesHeader() -{ - if (sHeaderEncoderStringSize < sHeaderCacheEncoderVersion.size() + 1) - { - // For simplicity we use predefined size of header, so if version string - // doesn't fit, either getEngineInfo() returned malformed string or - // sHeaderEncoderStringSize need to be increased. - // Also take into accout that c_str() returns additional null character - LL_ERRS() << "Version string doesn't fit in header" << LL_ENDL; - } - - mHeaderEntriesInfo.mVersion = sHeaderCacheVersion; - mHeaderEntriesInfo.mAdressSize = sHeaderCacheAddressSize; - strcpy(mHeaderEntriesInfo.mEncoderVersion, sHeaderCacheEncoderVersion.c_str()); - mHeaderEntriesInfo.mEntries = 0; -} - -void LLTextureCache::writeEntriesHeader() -{ - llassert_always(mHeaderAPRFile == NULL); - if (!mReadOnly) - { - LLAPRFile::writeEx(mHeaderEntriesFileName, (U8*)&mHeaderEntriesInfo, 0, sizeof(EntriesInfo), - mHeaderAPRFilePoolp); - } -} - -//mHeaderMutex is locked before calling this. -S32 LLTextureCache::openAndReadEntry(const LLUUID& id, Entry& entry, bool create) -{ - S32 idx = -1; - - id_map_t::iterator iter1 = mHeaderIDMap.find(id); - if (iter1 != mHeaderIDMap.end()) - { - idx = iter1->second; - } - - if (idx < 0) - { - if (create && !mReadOnly) - { - if (mHeaderEntriesInfo.mEntries < sCacheMaxEntries) - { - // Add an entry to the end of the list - idx = mHeaderEntriesInfo.mEntries++; - - } - else if (!mFreeList.empty()) - { - idx = *(mFreeList.begin()); - mFreeList.erase(mFreeList.begin()); - } - else - { - // Look for a still valid entry in the LRU - for (std::set::iterator iter2 = mLRU.begin(); iter2 != mLRU.end();) - { - std::set::iterator curiter2 = iter2++; - LLUUID oldid = *curiter2; - // Erase entry from LRU regardless - mLRU.erase(curiter2); - // Look up entry and use it if it is valid - id_map_t::iterator iter3 = mHeaderIDMap.find(oldid); - if (iter3 != mHeaderIDMap.end() && iter3->second >= 0) - { - idx = iter3->second; - removeCachedTexture(oldid) ;//remove the existing cached texture to release the entry index. - break; - } - } - // if (idx < 0) at this point, we will rebuild the LRU - // and retry if called from setHeaderCacheEntry(), - // otherwise this shouldn't happen and will trigger an error - } - if (idx >= 0) - { - entry.mID = id ; - entry.mImageSize = -1 ; //mark it is a brand-new entry. - entry.mBodySize = 0 ; - } - } - } - else - { - // Remove this entry from the LRU if it exists - mLRU.erase(id); - // Read the entry - idx_entry_map_t::iterator iter = mUpdatedEntryMap.find(idx) ; - if(iter != mUpdatedEntryMap.end()) - { - entry = iter->second ; - } - else - { - readEntryFromHeaderImmediately(idx, entry) ; - } - if(entry.mImageSize <= entry.mBodySize)//it happens on 64-bit systems, do not know why - { - LL_WARNS() << "corrupted entry: " << id << " entry image size: " << entry.mImageSize << " entry body size: " << entry.mBodySize << LL_ENDL ; - - //erase this entry and the cached texture from the cache. - std::string tex_filename = getTextureFileName(id); - removeEntry(idx, entry, tex_filename) ; - mUpdatedEntryMap.erase(idx) ; - idx = -1 ; - } - } - return idx; -} - -//mHeaderMutex is locked before calling this. -void LLTextureCache::writeEntryToHeaderImmediately(S32& idx, Entry& entry, bool write_header) -{ - LLAPRFile* aprfile ; - S32 bytes_written ; - S32 offset = sizeof(EntriesInfo) + idx * sizeof(Entry); - if(write_header) - { - aprfile = openHeaderEntriesFile(false, 0); - bytes_written = aprfile->write((U8*)&mHeaderEntriesInfo, sizeof(EntriesInfo)) ; - if(bytes_written != sizeof(EntriesInfo)) - { - clearCorruptedCache() ; //clear the cache. - idx = -1 ;//mark the idx invalid. - return ; - } - - mHeaderAPRFile->seek(APR_SET, offset); - } - else - { - aprfile = openHeaderEntriesFile(false, offset); - } - bytes_written = aprfile->write((void*)&entry, (S32)sizeof(Entry)); - if(bytes_written != sizeof(Entry)) - { - clearCorruptedCache() ; //clear the cache. - idx = -1 ;//mark the idx invalid. - - return ; - } - - closeHeaderEntriesFile(); - mUpdatedEntryMap.erase(idx) ; -} - -//mHeaderMutex is locked before calling this. -void LLTextureCache::readEntryFromHeaderImmediately(S32& idx, Entry& entry) -{ - S32 offset = sizeof(EntriesInfo) + idx * sizeof(Entry); - LLAPRFile* aprfile = openHeaderEntriesFile(true, offset); - S32 bytes_read = aprfile->read((void*)&entry, (S32)sizeof(Entry)); - closeHeaderEntriesFile(); - - if(bytes_read != sizeof(Entry)) - { - clearCorruptedCache() ; //clear the cache. - idx = -1 ;//mark the idx invalid. - } -} - -//mHeaderMutex is locked before calling this. -//update an existing entry time stamp, delay writing. -void LLTextureCache::updateEntryTimeStamp(S32 idx, Entry& entry) -{ - static const U32 MAX_ENTRIES_WITHOUT_TIME_STAMP = (U32)(LLTextureCache::sCacheMaxEntries * 0.75f) ; - - if(mHeaderEntriesInfo.mEntries < MAX_ENTRIES_WITHOUT_TIME_STAMP) - { - return ; //there are enough empty entry index space, no need to stamp time. - } - - if (idx >= 0) - { - if (!mReadOnly) - { - entry.mTime = time(NULL); - mUpdatedEntryMap[idx] = entry ; - } - } -} - -//update an existing entry, write to header file immediately. -bool LLTextureCache::updateEntry(S32& idx, Entry& entry, S32 new_image_size, S32 new_data_size) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - S32 new_body_size = llmax(0, new_data_size - TEXTURE_CACHE_ENTRY_SIZE) ; - - if(new_image_size == entry.mImageSize && new_body_size == entry.mBodySize) - { - return true ; //nothing changed. - } - else - { - bool purge = false ; - - lockHeaders() ; - - bool update_header = false ; - if(entry.mImageSize < 0) //is a brand-new entry - { - mHeaderIDMap[entry.mID] = idx; - mTexturesSizeMap[entry.mID] = new_body_size ; - mTexturesSizeTotal += new_body_size ; - - // Update Header - update_header = true ; - } - else if (entry.mBodySize != new_body_size) - { - //already in mHeaderIDMap. - mTexturesSizeMap[entry.mID] = new_body_size ; - mTexturesSizeTotal -= entry.mBodySize ; - mTexturesSizeTotal += new_body_size ; - } - entry.mTime = time(NULL); - entry.mImageSize = new_image_size ; - entry.mBodySize = new_body_size ; - - writeEntryToHeaderImmediately(idx, entry, update_header) ; - - if (mTexturesSizeTotal > sCacheMaxTexturesSize) - { - purge = true; - } - - unlockHeaders() ; - - if (purge) - { - mDoPurge = true; - } - } - - return false ; -} - -U32 LLTextureCache::openAndReadEntries(std::vector& entries) -{ - U32 num_entries = mHeaderEntriesInfo.mEntries; - - mHeaderIDMap.clear(); - mTexturesSizeMap.clear(); - mFreeList.clear(); - mTexturesSizeTotal = 0; - - LLAPRFile* aprfile = NULL; - if(mUpdatedEntryMap.empty()) - { - aprfile = openHeaderEntriesFile(true, (S32)sizeof(EntriesInfo)); - } - else //update the header file first. - { - aprfile = openHeaderEntriesFile(false, 0); - updatedHeaderEntriesFile() ; - if(!aprfile) - { - return 0; - } - aprfile->seek(APR_SET, (S32)sizeof(EntriesInfo)); - } - for (U32 idx=0; idxread((void*)(&entry), (S32)sizeof(Entry)); - if (bytes_read < sizeof(Entry)) - { - LL_WARNS() << "Corrupted header entries, failed at " << idx << " / " << num_entries << LL_ENDL; - closeHeaderEntriesFile(); - purgeAllTextures(false); - return 0; - } - entries.push_back(entry); -// LL_INFOS() << "ENTRY: " << entry.mTime << " TEX: " << entry.mID << " IDX: " << idx << " Size: " << entry.mImageSize << LL_ENDL; - if(entry.mImageSize > entry.mBodySize) - { - mHeaderIDMap[entry.mID] = idx; - mTexturesSizeMap[entry.mID] = entry.mBodySize; - mTexturesSizeTotal += entry.mBodySize; - } - else - { - mFreeList.insert(idx); - } - } - closeHeaderEntriesFile(); - return num_entries; -} - -void LLTextureCache::writeEntriesAndClose(const std::vector& entries) -{ - S32 num_entries = entries.size(); - llassert_always(num_entries == mHeaderEntriesInfo.mEntries); - - if (!mReadOnly) - { - LLAPRFile* aprfile = openHeaderEntriesFile(false, (S32)sizeof(EntriesInfo)); - for (S32 idx=0; idxwrite((void*)(&entries[idx]), (S32)sizeof(Entry)); - if(bytes_written != sizeof(Entry)) - { - clearCorruptedCache() ; //clear the cache. - return ; - } - } - closeHeaderEntriesFile(); - } -} - -void LLTextureCache::writeUpdatedEntries() -{ - lockHeaders() ; - if (!mReadOnly && !mUpdatedEntryMap.empty()) - { - openHeaderEntriesFile(false, 0); - updatedHeaderEntriesFile() ; - closeHeaderEntriesFile(); - } - unlockHeaders() ; -} - -//mHeaderMutex is locked and mHeaderAPRFile is created before calling this. -void LLTextureCache::updatedHeaderEntriesFile() -{ - if (!mReadOnly && !mUpdatedEntryMap.empty() && mHeaderAPRFile) - { - //entriesInfo - mHeaderAPRFile->seek(APR_SET, 0); - S32 bytes_written = mHeaderAPRFile->write((U8*)&mHeaderEntriesInfo, sizeof(EntriesInfo)) ; - if(bytes_written != sizeof(EntriesInfo)) - { - clearCorruptedCache() ; //clear the cache. - return ; - } - - //write each updated entry - S32 entry_size = (S32)sizeof(Entry) ; - S32 prev_idx = -1 ; - S32 delta_idx ; - for (idx_entry_map_t::iterator iter = mUpdatedEntryMap.begin(); iter != mUpdatedEntryMap.end(); ++iter) - { - delta_idx = iter->first - prev_idx - 1; - prev_idx = iter->first ; - if(delta_idx) - { - mHeaderAPRFile->seek(APR_CUR, delta_idx * entry_size); - } - - bytes_written = mHeaderAPRFile->write((void*)(&iter->second), entry_size); - if(bytes_written != entry_size) - { - clearCorruptedCache() ; //clear the cache. - return ; - } - } - mUpdatedEntryMap.clear() ; - } -} -//---------------------------------------------------------------------------- - -// Called from either the main thread or the worker thread -void LLTextureCache::readHeaderCache() -{ - mHeaderMutex.lock(); - - mLRU.clear(); // always clear the LRU - - readEntriesHeader(); - - if (mHeaderEntriesInfo.mVersion != sHeaderCacheVersion - || mHeaderEntriesInfo.mAdressSize != sHeaderCacheAddressSize - || strcmp(mHeaderEntriesInfo.mEncoderVersion, sHeaderCacheEncoderVersion.c_str()) != 0) - { - if (!mReadOnly) - { - LL_INFOS() << "Texture Cache version mismatch, Purging." << LL_ENDL; - purgeAllTextures(false); - } - } - else - { - std::vector entries; - U32 num_entries = openAndReadEntries(entries); - if (num_entries) - { - U32 empty_entries = 0; - typedef std::pair lru_data_t; - std::set lru; - std::set purge_list; - for (U32 i=0; i 0) - { - if (entry.mBodySize > entry.mImageSize) - { - // Shouldn't happen, failsafe only - LL_WARNS() << "Bad entry: " << i << ": " << entry.mID << ": BodySize: " << entry.mBodySize << LL_ENDL; - purge_list.insert(i); - } - } - } - } - if (num_entries - empty_entries > sCacheMaxEntries) - { - // Special case: cache size was reduced, need to remove entries - U32 entries_to_purge = (num_entries - empty_entries) - sCacheMaxEntries; - LL_INFOS() << "Texture Cache Entries: " << num_entries << " Max: " << sCacheMaxEntries << " Empty: " << empty_entries << " Purging: " << entries_to_purge << LL_ENDL; - // We can exit the following loop with the given condition, since if we'd reach the end of the lru set we'd have: - // purge_list.size() = lru.size() = num_entries - empty_entries = entries_to_purge + sCacheMaxEntries >= entries_to_purge - // So, it's certain that iter will never reach lru.end() first. - std::set::iterator iter = lru.begin(); - while (purge_list.size() < entries_to_purge) - { - purge_list.insert(iter->second); - ++iter; - } - } - - { - S32 lru_entries = (S32)((F32)sCacheMaxEntries * TEXTURE_CACHE_LRU_SIZE); - for (std::set::iterator iter = lru.begin(); iter != lru.end(); ++iter) - { - mLRU.insert(entries[iter->second].mID); -// LL_INFOS() << "LRU: " << iter->first << " : " << iter->second << LL_ENDL; - if (--lru_entries <= 0) - break; - } - } - - if (purge_list.size() > 0) - { - LLTimer timer; - for (std::set::iterator iter = purge_list.begin(); iter != purge_list.end(); ++iter) - { - std::string tex_filename = getTextureFileName(entries[*iter].mID); - removeEntry((S32)*iter, entries[*iter], tex_filename); - - //make sure that pruning entries doesn't take too much time - if (timer.getElapsedTimeF32() > TEXTURE_PRUNING_MAX_TIME) - { - break; - } - } - writeEntriesAndClose(entries); - } - else - { - //entries are not changed, nothing here. - } - } - } - mHeaderMutex.unlock(); -} - -////////////////////////////////////////////////////////////////////////////// - -//the header mutex is locked before calling this. -void LLTextureCache::clearCorruptedCache() -{ - LL_WARNS() << "the texture cache is corrupted, need to be cleared." << LL_ENDL ; - - closeHeaderEntriesFile();//close possible file handler - purgeAllTextures(false) ; //clear the cache. - - if (!mReadOnly) //regenerate the directory tree if not exists. - { - LLFile::mkdir(mTexturesDirName); - - const char* subdirs = "0123456789abcdef"; - for (S32 i=0; i<16; i++) - { - std::string dirname = mTexturesDirName + gDirUtilp->getDirDelimiter() + subdirs[i]; - LLFile::mkdir(dirname); - } - } - - return ; -} - -void LLTextureCache::purgeAllTextures(bool purge_directories) -{ - if (!mReadOnly) - { - const char* subdirs = "0123456789abcdef"; - std::string delem = gDirUtilp->getDirDelimiter(); - std::string mask = "*"; - for (S32 i=0; i<16; i++) - { - std::string dirname = mTexturesDirName + delem + subdirs[i]; - LL_INFOS() << "Deleting files in directory: " << dirname << LL_ENDL; - if (purge_directories) - { - gDirUtilp->deleteDirAndContents(dirname); - } - else - { - gDirUtilp->deleteFilesInDir(dirname, mask); - } -#if LL_WINDOWS - // Texture cache can be large and can take a while to remove - // assure OS that processes is alive and not hanging - MSG msg; - PeekMessage(&msg, 0, 0, 0, PM_NOREMOVE | PM_NOYIELD); -#endif - } - gDirUtilp->deleteFilesInDir(mTexturesDirName, mask); // headers, fast cache - if (purge_directories) - { - LLFile::rmdir(mTexturesDirName); - } - } - mHeaderIDMap.clear(); - mTexturesSizeMap.clear(); - mTexturesSizeTotal = 0; - mFreeList.clear(); - mTexturesSizeTotal = 0; - mUpdatedEntryMap.clear(); - - // Info with 0 entries - setEntriesHeader(); - writeEntriesHeader(); - - LL_INFOS() << "The entire texture cache is cleared." << LL_ENDL ; -} - -void LLTextureCache::purgeTexturesLazy(F32 time_limit_sec) -{ - if (mReadOnly) - { - return; - } - - if (!mThreaded) - { - LLAppViewer::instance()->pauseMainloopTimeout(); - } - - // time_limit doesn't account for lock time - LLMutexLock lock(&mHeaderMutex); - - if (mPurgeEntryList.empty()) - { - // Read the entries list and form list of textures to purge - std::vector entries; - U32 num_entries = openAndReadEntries(entries); - if (!num_entries) - { - return; // nothing to purge - } - - // Use mTexturesSizeMap to collect UUIDs of textures with bodies - typedef std::set > time_idx_set_t; - std::set > time_idx_set; - for (size_map_t::iterator iter1 = mTexturesSizeMap.begin(); - iter1 != mTexturesSizeMap.end(); ++iter1) - { - if (iter1->second > 0) - { - id_map_t::iterator iter2 = mHeaderIDMap.find(iter1->first); - if (iter2 != mHeaderIDMap.end()) - { - S32 idx = iter2->second; - time_idx_set.insert(std::make_pair(entries[idx].mTime, idx)); - } - else - { - LL_ERRS("TextureCache") << "mTexturesSizeMap / mHeaderIDMap corrupted." << LL_ENDL; - } - } - } - - S64 cache_size = mTexturesSizeTotal; - S64 purged_cache_size = (llmax(cache_size, sCacheMaxTexturesSize) * (S64)((1.f - TEXTURE_CACHE_PURGE_AMOUNT) * 100)) / 100; - for (time_idx_set_t::iterator iter = time_idx_set.begin(); - iter != time_idx_set.end(); ++iter) - { - S32 idx = iter->second; - if (cache_size >= purged_cache_size) - { - cache_size -= entries[idx].mBodySize; - mPurgeEntryList.push_back(std::pair(idx, entries[idx])); - } - else - { - break; - } - } - LL_DEBUGS("TextureCache") << "Formed Purge list of " << mPurgeEntryList.size() << " entries" << LL_ENDL; - } - else - { - // Remove collected entried - LLTimer timer; - while (!mPurgeEntryList.empty() && timer.getElapsedTimeF32() < time_limit_sec) - { - S32 idx = mPurgeEntryList.back().first; - Entry entry = mPurgeEntryList.back().second; - mPurgeEntryList.pop_back(); - // make sure record is still valid - id_map_t::iterator iter_header = mHeaderIDMap.find(entry.mID); - if (iter_header != mHeaderIDMap.end() && iter_header->second == idx) - { - std::string tex_filename = getTextureFileName(entry.mID); - removeEntry(idx, entry, tex_filename); - writeEntryToHeaderImmediately(idx, entry); - } - } - } -} - -void LLTextureCache::purgeTextures(bool validate) -{ - if (mReadOnly) - { - return; - } - - if (!mThreaded) - { - // *FIX:Mani - watchdog off. - LLAppViewer::instance()->pauseMainloopTimeout(); - } - - LLMutexLock lock(&mHeaderMutex); - - LL_INFOS() << "TEXTURE CACHE: Purging." << LL_ENDL; - - // Read the entries list - std::vector entries; - U32 num_entries = openAndReadEntries(entries); - if (!num_entries) - { - return; // nothing to purge - } - - // Use mTexturesSizeMap to collect UUIDs of textures with bodies - typedef std::set > time_idx_set_t; - std::set > time_idx_set; - for (size_map_t::iterator iter1 = mTexturesSizeMap.begin(); - iter1 != mTexturesSizeMap.end(); ++iter1) - { - if (iter1->second > 0) - { - id_map_t::iterator iter2 = mHeaderIDMap.find(iter1->first); - if (iter2 != mHeaderIDMap.end()) - { - S32 idx = iter2->second; - time_idx_set.insert(std::make_pair(entries[idx].mTime, idx)); -// LL_INFOS() << "TIME: " << entries[idx].mTime << " TEX: " << entries[idx].mID << " IDX: " << idx << " Size: " << entries[idx].mImageSize << LL_ENDL; - } - else - { - LL_ERRS() << "mTexturesSizeMap / mHeaderIDMap corrupted." << LL_ENDL ; - } - } - } - - // Validate 1/256th of the files on startup - U32 validate_idx = 0; - if (validate) - { - validate_idx = gSavedSettings.getU32("CacheValidateCounter"); - U32 next_idx = (validate_idx + 1) % 256; - gSavedSettings.setU32("CacheValidateCounter", next_idx); - LL_DEBUGS("TextureCache") << "TEXTURE CACHE: Validating: " << validate_idx << LL_ENDL; - } - - S64 cache_size = mTexturesSizeTotal; - S64 purged_cache_size = (llmax(cache_size, sCacheMaxTexturesSize) * (S64)((1.f - TEXTURE_CACHE_PURGE_AMOUNT) * 100)) / 100; - S32 purge_count = 0; - for (time_idx_set_t::iterator iter = time_idx_set.begin(); - iter != time_idx_set.end(); ++iter) - { - S32 idx = iter->second; - bool purge_entry = false; - - if (cache_size >= purged_cache_size) - { - purge_entry = true; - } - else if (validate) - { - // make sure file exists and is the correct size - U32 uuididx = entries[idx].mID.mData[0]; - if (uuididx == validate_idx) - { - std::string filename = getTextureFileName(entries[idx].mID); - LL_DEBUGS("TextureCache") << "Validating: " << filename << "Size: " << entries[idx].mBodySize << LL_ENDL; - // mHeaderAPRFilePoolp because this is under header mutex in main thread - S32 bodysize = LLAPRFile::size(filename, mHeaderAPRFilePoolp); - if (bodysize != entries[idx].mBodySize) - { - LL_WARNS("TextureCache") << "TEXTURE CACHE BODY HAS BAD SIZE: " << bodysize << " != " << entries[idx].mBodySize << filename << LL_ENDL; - purge_entry = true; - } - } - } - else - { - break; - } - - if (purge_entry) - { - purge_count++; - std::string filename = getTextureFileName(entries[idx].mID); - LL_DEBUGS("TextureCache") << "PURGING: " << filename << LL_ENDL; - cache_size -= entries[idx].mBodySize; - removeEntry(idx, entries[idx], filename) ; - } - } - - LL_DEBUGS("TextureCache") << "TEXTURE CACHE: Writing Entries: " << num_entries << LL_ENDL; - - writeEntriesAndClose(entries); - - // *FIX:Mani - watchdog back on. - LLAppViewer::instance()->resumeMainloopTimeout(); - - LL_INFOS("TextureCache") << "TEXTURE CACHE:" - << " PURGED: " << purge_count - << " ENTRIES: " << num_entries - << " CACHE SIZE: " << mTexturesSizeTotal / (1024 * 1024) << " MB" - << LL_ENDL; -} - -////////////////////////////////////////////////////////////////////////////// - -// call lockWorkers() first! -LLTextureCacheWorker* LLTextureCache::getReader(handle_t handle) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - LLTextureCacheWorker* res = NULL; - handle_map_t::iterator iter = mReaders.find(handle); - if (iter != mReaders.end()) - { - res = iter->second; - } - return res; -} - -LLTextureCacheWorker* LLTextureCache::getWriter(handle_t handle) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - LLTextureCacheWorker* res = NULL; - handle_map_t::iterator iter = mWriters.find(handle); - if (iter != mWriters.end()) - { - res = iter->second; - } - return res; -} - -////////////////////////////////////////////////////////////////////////////// -// Called from work thread - -// Reads imagesize from the header, updates timestamp -S32 LLTextureCache::getHeaderCacheEntry(const LLUUID& id, Entry& entry) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - LLMutexLock lock(&mHeaderMutex); - S32 idx = openAndReadEntry(id, entry, false); - if (idx >= 0) - { - updateEntryTimeStamp(idx, entry); // updates time - } - return idx; -} - -// Writes imagesize to the header, updates timestamp -S32 LLTextureCache::setHeaderCacheEntry(const LLUUID& id, Entry& entry, S32 imagesize, S32 datasize) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - mHeaderMutex.lock(); - S32 idx = openAndReadEntry(id, entry, true); // read or create - mHeaderMutex.unlock(); - - if(idx < 0) // retry once - { - readHeaderCache(); // We couldn't write an entry, so refresh the LRU - - mHeaderMutex.lock(); - idx = openAndReadEntry(id, entry, true); - mHeaderMutex.unlock(); - } - - if (idx >= 0) - { - updateEntry(idx, entry, imagesize, datasize); - } - else - { - LL_WARNS() << "Failed to set cache entry for image: " << id << LL_ENDL; - // We couldn't write to file, switch to read only mode and clear data - setReadOnly(true); - clearCorruptedCache(); // won't remove files due to "read only" - } - - return idx; -} - -////////////////////////////////////////////////////////////////////////////// - -// Calls from texture pipeline thread (i.e. LLTextureFetch) - -LLTextureCache::handle_t LLTextureCache::readFromCache(const std::string& filename, const LLUUID& id, - S32 offset, S32 size, ReadResponder* responder) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - // Note: checking to see if an entry exists can cause a stall, - // so let the thread handle it - LLMutexLock lock(&mWorkersMutex); - LLTextureCacheWorker* worker = new LLTextureCacheLocalFileWorker(this, filename, id, - NULL, size, offset, 0, - responder); - handle_t handle = worker->read(); - mReaders[handle] = worker; - return handle; -} - -LLTextureCache::handle_t LLTextureCache::readFromCache(const LLUUID& id, - S32 offset, S32 size, ReadResponder* responder) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - // Note: checking to see if an entry exists can cause a stall, - // so let the thread handle it - LLMutexLock lock(&mWorkersMutex); - LLTextureCacheWorker* worker = new LLTextureCacheRemoteWorker(this, id, - NULL, size, offset, - 0, NULL, 0, responder); - handle_t handle = worker->read(); - mReaders[handle] = worker; - return handle; -} - - -bool LLTextureCache::readComplete(handle_t handle, bool abort) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - lockWorkers(); - handle_map_t::iterator iter = mReaders.find(handle); - LLTextureCacheWorker* worker = NULL; - bool complete = false; - if (iter != mReaders.end()) - { - worker = iter->second; - complete = worker->complete(); - - if(!complete && abort) - { - abortRequest(handle, true) ; - } - } - if (worker && (complete || abort)) - { - mReaders.erase(iter); - unlockWorkers(); - worker->scheduleDelete(); - } - else - { - unlockWorkers(); - } - return (complete || abort); -} - -LLTextureCache::handle_t LLTextureCache::writeToCache(const LLUUID& id, - const U8* data, S32 datasize, S32 imagesize, - LLPointer rawimage, S32 discardlevel, - WriteResponder* responder) -{ - if (mReadOnly) - { - delete responder; - return LLWorkerThread::nullHandle(); - } - if (mDoPurge) - { - // NOTE: Needs to be done on the control thread - // (i.e. here) - purgeTexturesLazy(TEXTURE_LAZY_PURGE_TIME_LIMIT); - mDoPurge = !mPurgeEntryList.empty(); - } - LLMutexLock lock(&mWorkersMutex); - LLTextureCacheWorker* worker = new LLTextureCacheRemoteWorker(this, id, - data, datasize, 0, - imagesize, rawimage, discardlevel, responder); - handle_t handle = worker->write(); - mWriters[handle] = worker; - return handle; -} - -//called in the main thread -LLPointer LLTextureCache::readFromFastCache(const LLUUID& id, S32& discardlevel) -{ - U32 offset; - { - LLMutexLock lock(&mHeaderMutex); - id_map_t::const_iterator iter = mHeaderIDMap.find(id); - if(iter == mHeaderIDMap.end()) - { - return NULL; //not in the cache - } - - offset = iter->second; - } - offset *= TEXTURE_FAST_CACHE_ENTRY_SIZE; - - U8* data; - S32 head[4]; - { - LLMutexLock lock(&mFastCacheMutex); - - openFastCache(); - - mFastCachep->seek(APR_SET, offset); - - if(mFastCachep->read(head, TEXTURE_FAST_CACHE_ENTRY_OVERHEAD) != TEXTURE_FAST_CACHE_ENTRY_OVERHEAD) - { - //cache corrupted or under thread race condition - closeFastCache(); - return NULL; - } - - S32 image_size = head[0] * head[1] * head[2]; - if(image_size <= 0 - || image_size > TEXTURE_FAST_CACHE_DATA_SIZE - || head[3] < 0) //invalid - { - closeFastCache(); - return NULL; - } - discardlevel = head[3]; - - data = (U8*)ll_aligned_malloc_16(image_size); - if(mFastCachep->read(data, image_size) != image_size) - { - ll_aligned_free_16(data); - closeFastCache(); - return NULL; - } - - closeFastCache(); - } - LLPointer raw = new LLImageRaw(data, head[0], head[1], head[2], true); - - return raw; -} - -//return the fast cache location -bool LLTextureCache::writeToFastCache(LLUUID image_id, S32 id, LLPointer raw, S32 discardlevel) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - - LLImageDataSharedLock lock(raw); - - //rescale image if needed - if (raw.isNull() || raw->isBufferInvalid() || !raw->getData()) - { - LL_ERRS() << "Attempted to write NULL raw image to fastcache" << LL_ENDL; - return false; - } - - S32 w, h, c; - w = raw->getWidth(); - h = raw->getHeight(); - c = raw->getComponents(); - - S32 i = 0 ; - - // Search for a discard level that will fit into fast cache - while(((w >> i) * (h >> i) * c) > TEXTURE_FAST_CACHE_DATA_SIZE) - { - ++i ; - } - - if(i) - { - w >>= i; - h >>= i; - if(w * h *c > 0) //valid - { - // Make a duplicate to keep the original raw image untouched. - raw = raw->duplicate(); - - if (raw->isBufferInvalid()) - { - LL_WARNS() << "Invalid image duplicate buffer" << LL_ENDL; - return false; - } - - raw->scale(w, h); - - discardlevel += i ; - } - } - - //copy data - memcpy(mFastCachePadBuffer, &w, sizeof(S32)); - memcpy(mFastCachePadBuffer + sizeof(S32), &h, sizeof(S32)); - memcpy(mFastCachePadBuffer + sizeof(S32) * 2, &c, sizeof(S32)); - memcpy(mFastCachePadBuffer + sizeof(S32) * 3, &discardlevel, sizeof(S32)); - - S32 copy_size = w * h * c; - if(copy_size > 0) //valid - { - copy_size = llmin(copy_size, TEXTURE_FAST_CACHE_ENTRY_SIZE - TEXTURE_FAST_CACHE_ENTRY_OVERHEAD); - memcpy(mFastCachePadBuffer + TEXTURE_FAST_CACHE_ENTRY_OVERHEAD, raw->getData(), copy_size); - } - S32 offset = id * TEXTURE_FAST_CACHE_ENTRY_SIZE; - - { - LLMutexLock lock(&mFastCacheMutex); - - openFastCache(); - - mFastCachep->seek(APR_SET, offset); - - //no need to do this assertion check. When it fails, let it fail quietly. - //this failure could happen because other viewer removes the fast cache file when clearing cache. - //--> llassert_always(mFastCachep->write(mFastCachePadBuffer, TEXTURE_FAST_CACHE_ENTRY_SIZE) == TEXTURE_FAST_CACHE_ENTRY_SIZE); - mFastCachep->write(mFastCachePadBuffer, TEXTURE_FAST_CACHE_ENTRY_SIZE); - - closeFastCache(true); - } - - return true; -} - -void LLTextureCache::openFastCache(bool first_time) -{ - if(!mFastCachep) - { - if(first_time) - { - if(!mFastCachePadBuffer) - { - mFastCachePadBuffer = (U8*)ll_aligned_malloc_16(TEXTURE_FAST_CACHE_ENTRY_SIZE); - } - mFastCachePoolp = new LLVolatileAPRPool(); // is_local= true by default, so not thread safe by default - if (LLAPRFile::isExist(mFastCacheFileName, mFastCachePoolp)) - { - mFastCachep = new LLAPRFile(mFastCacheFileName, APR_READ|APR_WRITE|APR_BINARY, mFastCachePoolp) ; - } - else - { - mFastCachep = new LLAPRFile(mFastCacheFileName, APR_CREATE|APR_READ|APR_WRITE|APR_BINARY, mFastCachePoolp) ; - } - } - else - { - mFastCachep = new LLAPRFile(mFastCacheFileName, APR_READ|APR_WRITE|APR_BINARY, mFastCachePoolp) ; - } - - mFastCacheTimer.reset(); - } - return; -} - -void LLTextureCache::closeFastCache(bool forced) -{ - static const F32 timeout = 10.f ; //seconds - - if(!mFastCachep) - { - return ; - } - - if(!forced && mFastCacheTimer.getElapsedTimeF32() < timeout) - { - return ; - } - - delete mFastCachep; - mFastCachep = NULL; - return; -} - -bool LLTextureCache::writeComplete(handle_t handle, bool abort) -{ - lockWorkers(); - handle_map_t::iterator iter = mWriters.find(handle); - llassert(iter != mWriters.end()); - if (iter != mWriters.end()) - { - LLTextureCacheWorker* worker = iter->second; - if (worker->complete() || abort) - { - mWriters.erase(handle); - unlockWorkers(); - worker->scheduleDelete(); - return true; - } - } - unlockWorkers(); - return false; -} - -void LLTextureCache::prioritizeWrite(handle_t handle) -{ - // Don't prioritize yet, we might be working on this now - // which could create a deadlock - LLMutexLock lock(&mListMutex); - mPrioritizeWriteList.push_back(handle); -} - -void LLTextureCache::addCompleted(Responder* responder, bool success) -{ - LLMutexLock lock(&mListMutex); - mCompletedList.push_back(std::make_pair(responder,success)); -} - -////////////////////////////////////////////////////////////////////////////// - -//called after mHeaderMutex is locked. -void LLTextureCache::removeCachedTexture(const LLUUID& id) -{ - if(mTexturesSizeMap.find(id) != mTexturesSizeMap.end()) - { - mTexturesSizeTotal -= mTexturesSizeMap[id] ; - mTexturesSizeMap.erase(id); - } - mHeaderIDMap.erase(id); - // We are inside header's mutex so mHeaderAPRFilePoolp is safe to use, - // but getLocalAPRFilePool() is not safe, it might be in use by worker - LLAPRFile::remove(getTextureFileName(id), mHeaderAPRFilePoolp); -} - -//called after mHeaderMutex is locked. -void LLTextureCache::removeEntry(S32 idx, Entry& entry, std::string& filename) -{ - bool file_maybe_exists = true; // Always attempt to remove when idx is invalid. - - if(idx >= 0) //valid entry - { - if (entry.mBodySize == 0) // Always attempt to remove when mBodySize > 0. - { - // Sanity check. Shouldn't exist when body size is 0. - // We are inside header's mutex so mHeaderAPRFilePoolp is safe to use, - // but getLocalAPRFilePool() is not safe, it might be in use by worker - if (LLAPRFile::isExist(filename, mHeaderAPRFilePoolp)) - { - LL_WARNS("TextureCache") << "Entry has body size of zero but file " << filename << " exists. Deleting this file, too." << LL_ENDL; - } - else - { - file_maybe_exists = false; - } - } - mTexturesSizeTotal -= entry.mBodySize; - - entry.mImageSize = -1; - entry.mBodySize = 0; - mHeaderIDMap.erase(entry.mID); - mTexturesSizeMap.erase(entry.mID); - mFreeList.insert(idx); - } - - if (file_maybe_exists) - { - LLAPRFile::remove(filename, mHeaderAPRFilePoolp); - } -} - -bool LLTextureCache::removeFromCache(const LLUUID& id) -{ - //LL_WARNS() << "Removing texture from cache: " << id << LL_ENDL; - bool ret = false ; - if (!mReadOnly) - { - lockHeaders() ; - - Entry entry; - S32 idx = openAndReadEntry(id, entry, false); - std::string tex_filename = getTextureFileName(id); - removeEntry(idx, entry, tex_filename) ; - if (idx >= 0) - { - writeEntryToHeaderImmediately(idx, entry); - ret = true; - } - - unlockHeaders() ; - } - return ret ; -} - -////////////////////////////////////////////////////////////////////////////// - -LLTextureCache::ReadResponder::ReadResponder() - : mImageSize(0), - mImageLocal(false) -{ -} - -void LLTextureCache::ReadResponder::setData(U8* data, S32 datasize, S32 imagesize, S32 imageformat, bool imagelocal) -{ - if (mFormattedImage.notNull()) - { - llassert_always(mFormattedImage->getCodec() == imageformat); - mFormattedImage->appendData(data, datasize); - } - else - { - mFormattedImage = LLImageFormatted::createFromType(imageformat); - mFormattedImage->setData(data,datasize); - } - mImageSize = imagesize; - mImageLocal = imagelocal; -} - -////////////////////////////////////////////////////////////////////////////// +/** + * @file lltexturecache.cpp + * @brief Object which handles local texture caching + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lltexturecache.h" + +#include "llapr.h" +#include "lldir.h" +#include "llimage.h" +#include "llimagej2c.h" // for version control +#include "lllfsthread.h" +#include "llviewercontrol.h" + +// Included to allow LLTextureCache::purgeTextures() to pause watchdog timeout +#include "llappviewer.h" +#include "llmemory.h" + +// Cache organization: +// cache/texture.entries +// Unordered array of Entry structs +// cache/texture.cache +// First TEXTURE_CACHE_ENTRY_SIZE bytes of each texture in texture.entries in same order +// cache/textures/[0-F]/UUID.texture +// Actual texture body files + +//note: there is no good to define 1024 for TEXTURE_CACHE_ENTRY_SIZE while FIRST_PACKET_SIZE is 600 on sim side. +const S32 TEXTURE_CACHE_ENTRY_SIZE = FIRST_PACKET_SIZE;//1024; +const F32 TEXTURE_CACHE_PURGE_AMOUNT = .20f; // % amount to reduce the cache by when it exceeds its limit +const F32 TEXTURE_CACHE_LRU_SIZE = .10f; // % amount for LRU list (low overhead to regenerate) +const S32 TEXTURE_FAST_CACHE_ENTRY_OVERHEAD = sizeof(S32) * 4; //w, h, c, level +const S32 TEXTURE_FAST_CACHE_DATA_SIZE = 16 * 16 * 4; +const S32 TEXTURE_FAST_CACHE_ENTRY_SIZE = TEXTURE_FAST_CACHE_DATA_SIZE + TEXTURE_FAST_CACHE_ENTRY_OVERHEAD; +const F32 TEXTURE_LAZY_PURGE_TIME_LIMIT = .004f; // 4ms. Would be better to autoadjust, but there is a major cache rework in progress. +const F32 TEXTURE_PRUNING_MAX_TIME = 15.f; + +class LLTextureCacheWorker : public LLWorkerClass +{ + friend class LLTextureCache; + +private: + class ReadResponder : public LLLFSThread::Responder + { + public: + ReadResponder(LLTextureCache* cache, handle_t handle) : mCache(cache), mHandle(handle) {} + ~ReadResponder() {} + void completed(S32 bytes) + { + mCache->lockWorkers(); + LLTextureCacheWorker* reader = mCache->getReader(mHandle); + if (reader) reader->ioComplete(bytes); + mCache->unlockWorkers(); + } + LLTextureCache* mCache; + LLTextureCacheWorker::handle_t mHandle; + }; + + class WriteResponder : public LLLFSThread::Responder + { + public: + WriteResponder(LLTextureCache* cache, handle_t handle) : mCache(cache), mHandle(handle) {} + ~WriteResponder() {} + void completed(S32 bytes) + { + mCache->lockWorkers(); + LLTextureCacheWorker* writer = mCache->getWriter(mHandle); + if (writer) writer->ioComplete(bytes); + mCache->unlockWorkers(); + } + LLTextureCache* mCache; + LLTextureCacheWorker::handle_t mHandle; + }; + +public: + LLTextureCacheWorker(LLTextureCache* cache, const LLUUID& id, + const U8* data, S32 datasize, S32 offset, + S32 imagesize, // for writes + LLTextureCache::Responder* responder) + : LLWorkerClass(cache, "LLTextureCacheWorker"), + mID(id), + mCache(cache), + mReadData(NULL), + mWriteData(data), + mDataSize(datasize), + mOffset(offset), + mImageSize(imagesize), + mImageFormat(IMG_CODEC_J2C), + mImageLocal(false), + mResponder(responder), + mFileHandle(LLLFSThread::nullHandle()), + mBytesToRead(0), + mBytesRead(0) + { + } + ~LLTextureCacheWorker() + { + llassert_always(!haveWork()); + ll_aligned_free_16(mReadData); + } + + // override this interface + virtual bool doRead() = 0; + virtual bool doWrite() = 0; + + virtual bool doWork(S32 param); // Called from LLWorkerThread::processRequest() + + handle_t read() { addWork(0); return mRequestHandle; } + handle_t write() { addWork(1); return mRequestHandle; } + bool complete() { return checkWork(); } + void ioComplete(S32 bytes) + { + mBytesRead = bytes; + } + +private: + virtual void startWork(S32 param); // called from addWork() (MAIN THREAD) + virtual void finishWork(S32 param, bool completed); // called from finishRequest() (WORK THREAD) + virtual void endWork(S32 param, bool aborted); // called from doWork() (MAIN THREAD) + +protected: + LLTextureCache* mCache; + LLUUID mID; + + U8* mReadData; + const U8* mWriteData; + S32 mDataSize; + S32 mOffset; + S32 mImageSize; + EImageCodec mImageFormat; + bool mImageLocal; + LLPointer mResponder; + LLLFSThread::handle_t mFileHandle; + S32 mBytesToRead; + LLAtomicS32 mBytesRead; +}; + +class LLTextureCacheLocalFileWorker : public LLTextureCacheWorker +{ +public: + LLTextureCacheLocalFileWorker(LLTextureCache* cache, const std::string& filename, const LLUUID& id, + U8* data, S32 datasize, S32 offset, + S32 imagesize, // for writes + LLTextureCache::Responder* responder) + : LLTextureCacheWorker(cache, id, data, datasize, offset, imagesize, responder), + mFileName(filename) + + { + } + + virtual bool doRead(); + virtual bool doWrite(); + +private: + std::string mFileName; +}; + +bool LLTextureCacheLocalFileWorker::doRead() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + S32 local_size = LLAPRFile::size(mFileName, mCache->getLocalAPRFilePool()); + + if (local_size > 0 && mFileName.size() > 4) + { + mDataSize = local_size; // Only a complete file is valid + + std::string extension = mFileName.substr(mFileName.size() - 3, 3); + + mImageFormat = LLImageBase::getCodecFromExtension(extension); + + if (mImageFormat == IMG_CODEC_INVALID) + { +// LL_WARNS() << "Unrecognized file extension " << extension << " for local texture " << mFileName << LL_ENDL; + mDataSize = 0; // no data + return true; + } + } + else + { + // file doesn't exist + mDataSize = 0; // no data + return true; + } + + if (!mDataSize || mDataSize > local_size) + { + mDataSize = local_size; + } + mReadData = (U8*)ll_aligned_malloc_16(mDataSize); + + S32 bytes_read = LLAPRFile::readEx(mFileName, mReadData, mOffset, mDataSize, mCache->getLocalAPRFilePool()); + + if (bytes_read != mDataSize) + { +// LL_WARNS() << "Error reading file from local cache: " << mFileName +// << " Bytes: " << mDataSize << " Offset: " << mOffset +// << " / " << mDataSize << LL_ENDL; + mDataSize = 0; + ll_aligned_free_16(mReadData); + mReadData = NULL; + } + else + { + mImageSize = local_size; + mImageLocal = true; + } + return true; +} + +bool LLTextureCacheLocalFileWorker::doWrite() +{ + // no writes for local files + return false; +} + +class LLTextureCacheRemoteWorker : public LLTextureCacheWorker +{ +public: + LLTextureCacheRemoteWorker(LLTextureCache* cache, const LLUUID& id, + const U8* data, S32 datasize, S32 offset, + S32 imagesize, // for writes + LLPointer raw, S32 discardlevel, + LLTextureCache::Responder* responder) + : LLTextureCacheWorker(cache, id, data, datasize, offset, imagesize, responder), + mState(INIT), + mRawImage(raw), + mRawDiscardLevel(discardlevel) + { + } + + virtual bool doRead(); + virtual bool doWrite(); + +private: + enum e_state + { + INIT = 0, + LOCAL = 1, + CACHE = 2, + HEADER = 3, + BODY = 4 + }; + + e_state mState; + LLPointer mRawImage; + S32 mRawDiscardLevel; +}; + + +//virtual +void LLTextureCacheWorker::startWork(S32 param) +{ +} + +// This is where a texture is read from the cache system (header and body) +// Current assumption are: +// - the whole data are in a raw form, will be stored at mReadData +// - the size of this raw data is mDataSize and can be smaller than TEXTURE_CACHE_ENTRY_SIZE (the size of a record in the header cache) +// - the code supports offset reading but this is actually never exercised in the viewer +bool LLTextureCacheRemoteWorker::doRead() +{ + LL_PROFILE_ZONE_SCOPED; + bool done = false; + S32 idx = -1; + + S32 local_size = 0; + std::string local_filename; + + // First state / stage : find out if the file is local + if (mState == INIT) + { +#if 0 + std::string filename = mCache->getLocalFileName(mID); + // Is it a JPEG2000 file? + { + local_filename = filename + ".j2c"; + local_size = LLAPRFile::size(local_filename, mCache->getLocalAPRFilePool()); + if (local_size > 0) + { + mImageFormat = IMG_CODEC_J2C; + } + } + // If not, is it a jpeg file? + if (local_size == 0) + { + local_filename = filename + ".jpg"; + local_size = LLAPRFile::size(local_filename, mCache->getLocalAPRFilePool()); + if (local_size > 0) + { + mImageFormat = IMG_CODEC_JPEG; + mDataSize = local_size; // Only a complete .jpg file is valid + } + } + // Hmm... What about a targa file? (used for UI texture mostly) + if (local_size == 0) + { + local_filename = filename + ".tga"; + local_size = LLAPRFile::size(local_filename, mCache->getLocalAPRFilePool()); + if (local_size > 0) + { + mImageFormat = IMG_CODEC_TGA; + mDataSize = local_size; // Only a complete .tga file is valid + } + } + // Determine the next stage: if we found a file, then LOCAL else CACHE + mState = (local_size > 0 ? LOCAL : CACHE); + + llassert_always(mState == CACHE) ; +#else + mState = CACHE; +#endif + } + + // Second state / stage : if the file is local, load it and leave + if (!done && (mState == LOCAL)) + { + llassert(local_size != 0); // we're assuming there is a non empty local file here... + if (!mDataSize || mDataSize > local_size) + { + mDataSize = local_size; + } + // Allocate read buffer + mReadData = (U8*)ll_aligned_malloc_16(mDataSize); + + if (mReadData) + { + S32 bytes_read = LLAPRFile::readEx( local_filename, + mReadData, + mOffset, + mDataSize, + mCache->getLocalAPRFilePool()); + + if (bytes_read != mDataSize) + { + LL_WARNS() << "Error reading file from local cache: " << local_filename + << " Bytes: " << mDataSize << " Offset: " << mOffset + << " / " << mDataSize << LL_ENDL; + mDataSize = 0; + ll_aligned_free_16(mReadData); + mReadData = NULL; + } + else + { + mImageSize = local_size; + mImageLocal = true; + } + } + else + { + LL_WARNS() << "Error allocating memory for cache: " << local_filename + << " of size: " << mDataSize << LL_ENDL; + mDataSize = 0; + } + // We're done... + done = true; + } + + // Second state / stage : identify the cache or not... + if (!done && (mState == CACHE)) + { + LLTextureCache::Entry entry ; + idx = mCache->getHeaderCacheEntry(mID, entry); + if (idx < 0) + { + // The texture is *not* cached. We're done here... + mDataSize = 0; // no data + done = true; + } + else + { + mImageSize = entry.mImageSize ; + // If the read offset is bigger than the header cache, we read directly from the body + // Note that currently, we *never* read with offset from the cache, so the result is *always* HEADER + mState = mOffset < TEXTURE_CACHE_ENTRY_SIZE ? HEADER : BODY; + } + } + + // Third state / stage : read data from the header cache (texture.entries) file + if (!done && (mState == HEADER)) + { + llassert_always(idx >= 0); // we need an entry here or reading the header makes no sense + llassert_always(mOffset < TEXTURE_CACHE_ENTRY_SIZE); + S32 offset = idx * TEXTURE_CACHE_ENTRY_SIZE + mOffset; + // Compute the size we need to read (in bytes) + S32 size = TEXTURE_CACHE_ENTRY_SIZE - mOffset; + size = llmin(size, mDataSize); + // Allocate the read buffer + mReadData = (U8*)ll_aligned_malloc_16(size); + if (mReadData) + { + S32 bytes_read = LLAPRFile::readEx(mCache->mHeaderDataFileName, + mReadData, offset, size, mCache->getLocalAPRFilePool()); + if (bytes_read != size) + { + LL_WARNS() << "LLTextureCacheWorker: " << mID + << " incorrect number of bytes read from header: " << bytes_read + << " / " << size << LL_ENDL; + ll_aligned_free_16(mReadData); + mReadData = NULL; + mDataSize = -1; // failed + done = true; + } + // If we already read all we expected, we're actually done + if (mDataSize <= bytes_read) + { + done = true; + } + else + { + mState = BODY; + } + } + else + { + LL_WARNS() << "LLTextureCacheWorker: " << mID + << " failed to allocate memory for reading: " << mDataSize << LL_ENDL; + mReadData = NULL; + mDataSize = -1; // failed + done = true; + } + } + + // Fourth state / stage : read the rest of the data from the UUID based cached file + if (!done && (mState == BODY)) + { + std::string filename = mCache->getTextureFileName(mID); + S32 filesize = LLAPRFile::size(filename, mCache->getLocalAPRFilePool()); + + if (filesize && (filesize + TEXTURE_CACHE_ENTRY_SIZE) > mOffset) + { + S32 max_datasize = TEXTURE_CACHE_ENTRY_SIZE + filesize - mOffset; + mDataSize = llmin(max_datasize, mDataSize); + + S32 data_offset, file_size, file_offset; + + // Reserve the whole data buffer first + U8* data = (U8*)ll_aligned_malloc_16(mDataSize); + if (data) + { + // Set the data file pointers taking the read offset into account. 2 cases: + if (mOffset < TEXTURE_CACHE_ENTRY_SIZE) + { + // Offset within the header record. That means we read something from the header cache. + // Note: most common case is (mOffset = 0), so this is the "normal" code path. + data_offset = TEXTURE_CACHE_ENTRY_SIZE - mOffset; // i.e. TEXTURE_CACHE_ENTRY_SIZE if mOffset nul (common case) + file_offset = 0; + file_size = mDataSize - data_offset; + // Copy the raw data we've been holding from the header cache into the new sized buffer + llassert_always(mReadData); + memcpy(data, mReadData, data_offset); + ll_aligned_free_16(mReadData); + mReadData = NULL; + } + else + { + // Offset bigger than the header record. That means we haven't read anything yet. + data_offset = 0; + file_offset = mOffset - TEXTURE_CACHE_ENTRY_SIZE; + file_size = mDataSize; + // No data from header cache to copy in that case, we skipped it all + } + + // Now use that buffer as the object read buffer + llassert_always(mReadData == NULL); + mReadData = data; + + // Read the data at last + S32 bytes_read = LLAPRFile::readEx(filename, + mReadData + data_offset, + file_offset, file_size, + mCache->getLocalAPRFilePool()); + if (bytes_read != file_size) + { + LL_WARNS() << "LLTextureCacheWorker: " << mID + << " incorrect number of bytes read from body: " << bytes_read + << " / " << file_size << LL_ENDL; + ll_aligned_free_16(mReadData); + mReadData = NULL; + mDataSize = -1; // failed + done = true; + } + } + else + { + LL_WARNS() << "LLTextureCacheWorker: " << mID + << " failed to allocate memory for reading: " << mDataSize << LL_ENDL; + ll_aligned_free_16(mReadData); + mReadData = NULL; + mDataSize = -1; // failed + done = true; + } + } + else + { + // No body, we're done. + mDataSize = llmax(TEXTURE_CACHE_ENTRY_SIZE - mOffset, 0); + LL_DEBUGS() << "No body file for: " << filename << LL_ENDL; + } + // Nothing else to do at that point... + done = true; + } + + // Clean up and exit + return done; +} + +// This is where *everything* about a texture is written down in the cache system (entry map, header and body) +// Current assumption are: +// - the whole data are in a raw form, starting at mWriteData +// - the size of this raw data is mDataSize and can be smaller than TEXTURE_CACHE_ENTRY_SIZE (the size of a record in the header cache) +// - the code *does not* support offset writing so there are no difference between buffer addresses and start of data +bool LLTextureCacheRemoteWorker::doWrite() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + bool done = false; + S32 idx = -1; + + // First state / stage : check that what we're trying to cache is in an OK shape + if (mState == INIT) + { + if ((mOffset != 0) // We currently do not support write offsets + || (mDataSize <= 0) // Things will go badly wrong if mDataSize is nul or negative... + || (mImageSize < mDataSize) + || (mRawDiscardLevel < 0) + || (mRawImage->isBufferInvalid())) // decode failed or malfunctioned, don't write + { + LL_WARNS() << "INIT state check failed for image: " << mID << " Size: " << mImageSize << " DataSize: " << mDataSize << " Discard:" << mRawDiscardLevel << LL_ENDL; + mDataSize = -1; // failed + done = true; + } + else + { + mState = CACHE; + } + } + + // No LOCAL state for write(): because it doesn't make much sense to cache a local file... + + // Second state / stage : set an entry in the headers entry (texture.entries) file + if (!done && (mState == CACHE)) + { + bool alreadyCached = false; + LLTextureCache::Entry entry; + + // Checks if this image is already in the entry list + idx = mCache->getHeaderCacheEntry(mID, entry); + if(idx < 0) + { + idx = mCache->setHeaderCacheEntry(mID, entry, mImageSize, mDataSize); // create the new entry. + if(idx >= 0) + { + // write to the fast cache. + // mRawImage is not entirely safe here since it is a pointer to one owned by cache worker, + // it could have been retrieved via getRequestFinished() and then modified. + // If writeToFastCache crashes, something is wrong around fetch worker. + if(!mCache->writeToFastCache(mID, idx, mRawImage, mRawDiscardLevel)) + { + LL_WARNS() << "writeToFastCache failed" << LL_ENDL; + mDataSize = -1; // failed + done = true; + } + } + } + else + { + alreadyCached = mCache->updateEntry(idx, entry, mImageSize, mDataSize); // update the existing entry. + } + + if (!done) + { + if (idx < 0) + { + LL_WARNS() << "LLTextureCacheWorker: " << mID + << " Unable to create header entry for writing!" << LL_ENDL; + mDataSize = -1; // failed + done = true; + } + else + { + if (alreadyCached && (mDataSize <= TEXTURE_CACHE_ENTRY_SIZE)) + { + // Small texture already cached case: we're done with writing + done = true; + } + else + { + // If the texture has already been cached, we don't resave the header and go directly to the body part + mState = alreadyCached ? BODY : HEADER; + } + } + } + } + + + // Third stage / state : write the header record in the header file (texture.cache) + if (!done && (mState == HEADER)) + { + if (idx < 0) // we need an entry here or storing the header makes no sense + { + LL_WARNS() << "index check failed" << LL_ENDL; + mDataSize = -1; // failed + done = true; + } + else + { + S32 offset = idx * TEXTURE_CACHE_ENTRY_SIZE; // skip to the correct spot in the header file + S32 size = TEXTURE_CACHE_ENTRY_SIZE; // record size is fixed for the header + S32 bytes_written; + + if (mDataSize < TEXTURE_CACHE_ENTRY_SIZE) + { + // We need to write a full record in the header cache so, if the amount of data is smaller + // than a record, we need to transfer the data to a buffer padded with 0 and write that + U8* padBuffer = (U8*)ll_aligned_malloc_16(TEXTURE_CACHE_ENTRY_SIZE); + memset(padBuffer, 0, TEXTURE_CACHE_ENTRY_SIZE); // Init with zeros + memcpy(padBuffer, mWriteData, mDataSize); // Copy the write buffer + bytes_written = LLAPRFile::writeEx(mCache->mHeaderDataFileName, padBuffer, offset, size, mCache->getLocalAPRFilePool()); + ll_aligned_free_16(padBuffer); + } + else + { + // Write the header record (== first TEXTURE_CACHE_ENTRY_SIZE bytes of the raw file) in the header file + bytes_written = LLAPRFile::writeEx(mCache->mHeaderDataFileName, mWriteData, offset, size, mCache->getLocalAPRFilePool()); + } + + if (bytes_written <= 0) + { + LL_WARNS() << "LLTextureCacheWorker: " << mID + << " Unable to write header entry!" << LL_ENDL; + mDataSize = -1; // failed + done = true; + } + + // If we wrote everything (may be more with padding) in the header cache, + // we're done so we don't have a body to store + if (mDataSize <= bytes_written) + { + done = true; + } + else + { + mState = BODY; + } + } + } + + // Fourth stage / state : write the body file, i.e. the rest of the texture in a "UUID" file name + if (!done && (mState == BODY)) + { + if (mDataSize <= TEXTURE_CACHE_ENTRY_SIZE) // wouldn't make sense to be here otherwise... + { + LL_WARNS() << "mDataSize check failed" << LL_ENDL; + mDataSize = -1; // failed + done = true; + } + else + { + S32 file_size = mDataSize - TEXTURE_CACHE_ENTRY_SIZE; + + { + // build the cache file name from the UUID + std::string filename = mCache->getTextureFileName(mID); + // LL_INFOS() << "Writing Body: " << filename << " Bytes: " << file_offset+file_size << LL_ENDL; + S32 bytes_written = LLAPRFile::writeEx(filename, + mWriteData + TEXTURE_CACHE_ENTRY_SIZE, + 0, file_size, + mCache->getLocalAPRFilePool()); + if (bytes_written <= 0) + { + LL_WARNS() << "LLTextureCacheWorker: " << mID + << " incorrect number of bytes written to body: " << bytes_written + << " / " << file_size << LL_ENDL; + mDataSize = -1; // failed + done = true; + } + } + + // Nothing else to do at that point... + done = true; + } + } + mRawImage = NULL; + + // Clean up and exit + return done; +} + +//virtual +bool LLTextureCacheWorker::doWork(S32 param) +{ + LL_PROFILE_ZONE_SCOPED; + bool res = false; + if (param == 0) // read + { + res = doRead(); + } + else if (param == 1) // write + { + res = doWrite(); + } + else + { + llassert_always(0); + } + return res; +} + +//virtual (WORKER THREAD) +void LLTextureCacheWorker::finishWork(S32 param, bool completed) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if (mResponder.notNull()) + { + bool success = (completed && mDataSize > 0); + if (param == 0) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("tcwfw - read"); + // read + if (success) + { + mResponder->setData(mReadData, mDataSize, mImageSize, mImageFormat, mImageLocal); + mReadData = NULL; // responder owns data + mDataSize = 0; + } + else + { + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("tcwfw - read fail"); + ll_aligned_free_16(mReadData); + mReadData = NULL; + } + } + else + { + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("tcwfw - write"); + // write + mWriteData = NULL; // we never owned data + mDataSize = 0; + } + mCache->addCompleted(mResponder, success); + } +} + +//virtual (MAIN THREAD) +void LLTextureCacheWorker::endWork(S32 param, bool aborted) +{ + LL_PROFILE_ZONE_SCOPED; + if (aborted) + { + // Let the destructor handle any cleanup + return; + } + switch(param) + { + default: + case 0: // read + case 1: // write + { + if (mDataSize < 0) + { + // failed + mCache->removeFromCache(mID); + } + break; + } + } +} + +////////////////////////////////////////////////////////////////////////////// + +LLTextureCache::LLTextureCache(bool threaded) + : LLWorkerThread("TextureCache", threaded), + mWorkersMutex(), + mHeaderMutex(), + mListMutex(), + mFastCacheMutex(), + mHeaderAPRFile(NULL), + mReadOnly(true), //do not allow to change the texture cache until setReadOnly() is called. + mTexturesSizeTotal(0), + mDoPurge(false), + mFastCachep(NULL), + mFastCachePoolp(NULL), + mFastCachePadBuffer(NULL) +{ + mHeaderAPRFilePoolp = new LLVolatileAPRPool(); // is_local = true, because this pool is for headers, headers are under own mutex +} + +LLTextureCache::~LLTextureCache() +{ + clearDeleteList() ; + writeUpdatedEntries() ; + delete mFastCachep; + delete mFastCachePoolp; + delete mHeaderAPRFilePoolp; + ll_aligned_free_16(mFastCachePadBuffer); +} + +////////////////////////////////////////////////////////////////////////////// + +//virtual +size_t LLTextureCache::update(F32 max_time_ms) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + static LLFrameTimer timer ; + static const F32 MAX_TIME_INTERVAL = 300.f ; //seconds. + + size_t res; + res = LLWorkerThread::update(max_time_ms); + + mListMutex.lock(); + handle_list_t priorty_list = mPrioritizeWriteList; // copy list + mPrioritizeWriteList.clear(); + responder_list_t completed_list = mCompletedList; // copy list + mCompletedList.clear(); + mListMutex.unlock(); + + // call 'completed' with workers list unlocked (may call readComplete() or writeComplete() + for (responder_list_t::iterator iter1 = completed_list.begin(); + iter1 != completed_list.end(); ++iter1) + { + Responder *responder = iter1->first; + bool success = iter1->second; + responder->completed(success); + } + + if(!res && timer.getElapsedTimeF32() > MAX_TIME_INTERVAL) + { + timer.reset() ; + writeUpdatedEntries() ; + } + + return res; +} + +////////////////////////////////////////////////////////////////////////////// +// search for local copy of UUID-based image file +std::string LLTextureCache::getLocalFileName(const LLUUID& id) +{ + // Does not include extension + std::string idstr = id.asString(); + // TODO: should we be storing cached textures in skin directory? + std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_LOCAL_ASSETS, idstr); + return filename; +} + +std::string LLTextureCache::getTextureFileName(const LLUUID& id) +{ + std::string idstr = id.asString(); + std::string delem = gDirUtilp->getDirDelimiter(); + std::string filename = mTexturesDirName + delem + idstr[0] + delem + idstr + ".texture"; + return filename; +} + +//debug +bool LLTextureCache::isInCache(const LLUUID& id) +{ + LLMutexLock lock(&mHeaderMutex); + id_map_t::const_iterator iter = mHeaderIDMap.find(id); + + return (iter != mHeaderIDMap.end()) ; +} + +//debug +bool LLTextureCache::isInLocal(const LLUUID& id) +{ + S32 local_size = 0; + std::string local_filename; + + std::string filename = getLocalFileName(id); + // Is it a JPEG2000 file? + { + local_filename = filename + ".j2c"; + local_size = LLAPRFile::size(local_filename, getLocalAPRFilePool()); + if (local_size > 0) + { + return true ; + } + } + + // If not, is it a jpeg file? + { + local_filename = filename + ".jpg"; + local_size = LLAPRFile::size(local_filename, getLocalAPRFilePool()); + if (local_size > 0) + { + return true ; + } + } + + // Hmm... What about a targa file? (used for UI texture mostly) + { + local_filename = filename + ".tga"; + local_size = LLAPRFile::size(local_filename, getLocalAPRFilePool()); + if (local_size > 0) + { + return true ; + } + } + + return false ; +} +////////////////////////////////////////////////////////////////////////////// + +//static +F32 LLTextureCache::sHeaderCacheVersion = 1.71f; +U32 LLTextureCache::sCacheMaxEntries = 1024 * 1024; //~1 million textures. +S64 LLTextureCache::sCacheMaxTexturesSize = 0; // no limit +std::string LLTextureCache::sHeaderCacheEncoderVersion = LLImageJ2C::getEngineInfo(); + +#if defined(ADDRESS_SIZE) +U32 LLTextureCache::sHeaderCacheAddressSize = ADDRESS_SIZE; +#else +U32 LLTextureCache::sHeaderCacheAddressSize = 32; +#endif + +const char* entries_filename = "texture.entries"; +const char* cache_filename = "texture.cache"; +const char* old_textures_dirname = "textures"; +//change the location of the texture cache to prevent from being deleted by old version viewers. +const char* textures_dirname = "texturecache"; +const char* fast_cache_filename = "FastCache.cache"; + +void LLTextureCache::setDirNames(ELLPath location) +{ + std::string delem = gDirUtilp->getDirDelimiter(); + + mHeaderEntriesFileName = gDirUtilp->getExpandedFilename(location, textures_dirname, entries_filename); + mHeaderDataFileName = gDirUtilp->getExpandedFilename(location, textures_dirname, cache_filename); + mTexturesDirName = gDirUtilp->getExpandedFilename(location, textures_dirname); + mFastCacheFileName = gDirUtilp->getExpandedFilename(location, textures_dirname, fast_cache_filename); +} + +void LLTextureCache::purgeCache(ELLPath location, bool remove_dir) +{ + LLMutexLock lock(&mHeaderMutex); + + if (!mReadOnly) + { + setDirNames(location); + llassert_always(mHeaderAPRFile == NULL); + + //remove the legacy cache if exists + std::string texture_dir = mTexturesDirName ; + mTexturesDirName = gDirUtilp->getExpandedFilename(location, old_textures_dirname); + if(LLFile::isdir(mTexturesDirName)) + { + std::string file_name = gDirUtilp->getExpandedFilename(location, entries_filename); + // mHeaderAPRFilePoolp because we are under header mutex, and can be in main thread + LLAPRFile::remove(file_name, mHeaderAPRFilePoolp); + + file_name = gDirUtilp->getExpandedFilename(location, cache_filename); + LLAPRFile::remove(file_name, mHeaderAPRFilePoolp); + + purgeAllTextures(true); + } + mTexturesDirName = texture_dir ; + } + + //remove the current texture cache. + purgeAllTextures(remove_dir); +} + +//is called in the main thread before initCache(...) is called. +void LLTextureCache::setReadOnly(bool read_only) +{ + mReadOnly = read_only ; +} + +// Called in the main thread. +// Returns the unused amount of max_size if any +S64 LLTextureCache::initCache(ELLPath location, S64 max_size, bool texture_cache_mismatch) +{ + llassert_always(getPending() == 0) ; //should not start accessing the texture cache before initialized. + + S64 entries_size = (max_size * 36) / 100; //0.36 * max_size + S64 max_entries = entries_size / (TEXTURE_CACHE_ENTRY_SIZE + TEXTURE_FAST_CACHE_ENTRY_SIZE); + sCacheMaxEntries = (S32)(llmin((S64)sCacheMaxEntries, max_entries)); + entries_size = sCacheMaxEntries * (TEXTURE_CACHE_ENTRY_SIZE + TEXTURE_FAST_CACHE_ENTRY_SIZE); + max_size -= entries_size; + if (sCacheMaxTexturesSize > 0) + sCacheMaxTexturesSize = llmin(sCacheMaxTexturesSize, max_size); + else + sCacheMaxTexturesSize = max_size; + max_size -= sCacheMaxTexturesSize; + + LL_INFOS("TextureCache") << "Headers: " << sCacheMaxEntries + << " Textures size: " << sCacheMaxTexturesSize / (1024 * 1024) << " MB" << LL_ENDL; + + setDirNames(location); + + if(texture_cache_mismatch) + { + //if readonly, disable the texture cache, + //otherwise wipe out the texture cache. + purgeAllTextures(true); + + if(mReadOnly) + { + return max_size ; + } + } + + if (!mReadOnly) + { + LLFile::mkdir(mTexturesDirName); + + const char* subdirs = "0123456789abcdef"; + for (S32 i=0; i<16; i++) + { + std::string dirname = mTexturesDirName + gDirUtilp->getDirDelimiter() + subdirs[i]; + LLFile::mkdir(dirname); + } + } + readHeaderCache(); + purgeTextures(true); // calc mTexturesSize and make some room in the texture cache if we need it + + llassert_always(getPending() == 0) ; //should not start accessing the texture cache before initialized. + openFastCache(true); + + return max_size; // unused cache space +} + +//---------------------------------------------------------------------------- +// mHeaderMutex must be locked for the following functions! + +LLAPRFile* LLTextureCache::openHeaderEntriesFile(bool readonly, S32 offset) +{ + llassert_always(mHeaderAPRFile == NULL); + apr_int32_t flags = readonly ? APR_READ|APR_BINARY : APR_READ|APR_WRITE|APR_BINARY; + mHeaderAPRFile = new LLAPRFile(mHeaderEntriesFileName, flags, mHeaderAPRFilePoolp); + if(offset > 0) + { + mHeaderAPRFile->seek(APR_SET, offset); + } + return mHeaderAPRFile; +} + +void LLTextureCache::closeHeaderEntriesFile() +{ + if(!mHeaderAPRFile) + { + return ; + } + + delete mHeaderAPRFile; + mHeaderAPRFile = NULL; +} + +void LLTextureCache::readEntriesHeader() +{ + // mHeaderEntriesInfo initializes to default values so safe not to read it + llassert_always(mHeaderAPRFile == NULL); + if (LLAPRFile::isExist(mHeaderEntriesFileName, mHeaderAPRFilePoolp)) + { + LLAPRFile::readEx(mHeaderEntriesFileName, (U8*)&mHeaderEntriesInfo, 0, sizeof(EntriesInfo), + mHeaderAPRFilePoolp); + } + else //create an empty entries header. + { + setEntriesHeader(); + writeEntriesHeader() ; + } +} + +void LLTextureCache::setEntriesHeader() +{ + if (sHeaderEncoderStringSize < sHeaderCacheEncoderVersion.size() + 1) + { + // For simplicity we use predefined size of header, so if version string + // doesn't fit, either getEngineInfo() returned malformed string or + // sHeaderEncoderStringSize need to be increased. + // Also take into accout that c_str() returns additional null character + LL_ERRS() << "Version string doesn't fit in header" << LL_ENDL; + } + + mHeaderEntriesInfo.mVersion = sHeaderCacheVersion; + mHeaderEntriesInfo.mAdressSize = sHeaderCacheAddressSize; + strcpy(mHeaderEntriesInfo.mEncoderVersion, sHeaderCacheEncoderVersion.c_str()); + mHeaderEntriesInfo.mEntries = 0; +} + +void LLTextureCache::writeEntriesHeader() +{ + llassert_always(mHeaderAPRFile == NULL); + if (!mReadOnly) + { + LLAPRFile::writeEx(mHeaderEntriesFileName, (U8*)&mHeaderEntriesInfo, 0, sizeof(EntriesInfo), + mHeaderAPRFilePoolp); + } +} + +//mHeaderMutex is locked before calling this. +S32 LLTextureCache::openAndReadEntry(const LLUUID& id, Entry& entry, bool create) +{ + S32 idx = -1; + + id_map_t::iterator iter1 = mHeaderIDMap.find(id); + if (iter1 != mHeaderIDMap.end()) + { + idx = iter1->second; + } + + if (idx < 0) + { + if (create && !mReadOnly) + { + if (mHeaderEntriesInfo.mEntries < sCacheMaxEntries) + { + // Add an entry to the end of the list + idx = mHeaderEntriesInfo.mEntries++; + + } + else if (!mFreeList.empty()) + { + idx = *(mFreeList.begin()); + mFreeList.erase(mFreeList.begin()); + } + else + { + // Look for a still valid entry in the LRU + for (std::set::iterator iter2 = mLRU.begin(); iter2 != mLRU.end();) + { + std::set::iterator curiter2 = iter2++; + LLUUID oldid = *curiter2; + // Erase entry from LRU regardless + mLRU.erase(curiter2); + // Look up entry and use it if it is valid + id_map_t::iterator iter3 = mHeaderIDMap.find(oldid); + if (iter3 != mHeaderIDMap.end() && iter3->second >= 0) + { + idx = iter3->second; + removeCachedTexture(oldid) ;//remove the existing cached texture to release the entry index. + break; + } + } + // if (idx < 0) at this point, we will rebuild the LRU + // and retry if called from setHeaderCacheEntry(), + // otherwise this shouldn't happen and will trigger an error + } + if (idx >= 0) + { + entry.mID = id ; + entry.mImageSize = -1 ; //mark it is a brand-new entry. + entry.mBodySize = 0 ; + } + } + } + else + { + // Remove this entry from the LRU if it exists + mLRU.erase(id); + // Read the entry + idx_entry_map_t::iterator iter = mUpdatedEntryMap.find(idx) ; + if(iter != mUpdatedEntryMap.end()) + { + entry = iter->second ; + } + else + { + readEntryFromHeaderImmediately(idx, entry) ; + } + if(entry.mImageSize <= entry.mBodySize)//it happens on 64-bit systems, do not know why + { + LL_WARNS() << "corrupted entry: " << id << " entry image size: " << entry.mImageSize << " entry body size: " << entry.mBodySize << LL_ENDL ; + + //erase this entry and the cached texture from the cache. + std::string tex_filename = getTextureFileName(id); + removeEntry(idx, entry, tex_filename) ; + mUpdatedEntryMap.erase(idx) ; + idx = -1 ; + } + } + return idx; +} + +//mHeaderMutex is locked before calling this. +void LLTextureCache::writeEntryToHeaderImmediately(S32& idx, Entry& entry, bool write_header) +{ + LLAPRFile* aprfile ; + S32 bytes_written ; + S32 offset = sizeof(EntriesInfo) + idx * sizeof(Entry); + if(write_header) + { + aprfile = openHeaderEntriesFile(false, 0); + bytes_written = aprfile->write((U8*)&mHeaderEntriesInfo, sizeof(EntriesInfo)) ; + if(bytes_written != sizeof(EntriesInfo)) + { + clearCorruptedCache() ; //clear the cache. + idx = -1 ;//mark the idx invalid. + return ; + } + + mHeaderAPRFile->seek(APR_SET, offset); + } + else + { + aprfile = openHeaderEntriesFile(false, offset); + } + bytes_written = aprfile->write((void*)&entry, (S32)sizeof(Entry)); + if(bytes_written != sizeof(Entry)) + { + clearCorruptedCache() ; //clear the cache. + idx = -1 ;//mark the idx invalid. + + return ; + } + + closeHeaderEntriesFile(); + mUpdatedEntryMap.erase(idx) ; +} + +//mHeaderMutex is locked before calling this. +void LLTextureCache::readEntryFromHeaderImmediately(S32& idx, Entry& entry) +{ + S32 offset = sizeof(EntriesInfo) + idx * sizeof(Entry); + LLAPRFile* aprfile = openHeaderEntriesFile(true, offset); + S32 bytes_read = aprfile->read((void*)&entry, (S32)sizeof(Entry)); + closeHeaderEntriesFile(); + + if(bytes_read != sizeof(Entry)) + { + clearCorruptedCache() ; //clear the cache. + idx = -1 ;//mark the idx invalid. + } +} + +//mHeaderMutex is locked before calling this. +//update an existing entry time stamp, delay writing. +void LLTextureCache::updateEntryTimeStamp(S32 idx, Entry& entry) +{ + static const U32 MAX_ENTRIES_WITHOUT_TIME_STAMP = (U32)(LLTextureCache::sCacheMaxEntries * 0.75f) ; + + if(mHeaderEntriesInfo.mEntries < MAX_ENTRIES_WITHOUT_TIME_STAMP) + { + return ; //there are enough empty entry index space, no need to stamp time. + } + + if (idx >= 0) + { + if (!mReadOnly) + { + entry.mTime = time(NULL); + mUpdatedEntryMap[idx] = entry ; + } + } +} + +//update an existing entry, write to header file immediately. +bool LLTextureCache::updateEntry(S32& idx, Entry& entry, S32 new_image_size, S32 new_data_size) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + S32 new_body_size = llmax(0, new_data_size - TEXTURE_CACHE_ENTRY_SIZE) ; + + if(new_image_size == entry.mImageSize && new_body_size == entry.mBodySize) + { + return true ; //nothing changed. + } + else + { + bool purge = false ; + + lockHeaders() ; + + bool update_header = false ; + if(entry.mImageSize < 0) //is a brand-new entry + { + mHeaderIDMap[entry.mID] = idx; + mTexturesSizeMap[entry.mID] = new_body_size ; + mTexturesSizeTotal += new_body_size ; + + // Update Header + update_header = true ; + } + else if (entry.mBodySize != new_body_size) + { + //already in mHeaderIDMap. + mTexturesSizeMap[entry.mID] = new_body_size ; + mTexturesSizeTotal -= entry.mBodySize ; + mTexturesSizeTotal += new_body_size ; + } + entry.mTime = time(NULL); + entry.mImageSize = new_image_size ; + entry.mBodySize = new_body_size ; + + writeEntryToHeaderImmediately(idx, entry, update_header) ; + + if (mTexturesSizeTotal > sCacheMaxTexturesSize) + { + purge = true; + } + + unlockHeaders() ; + + if (purge) + { + mDoPurge = true; + } + } + + return false ; +} + +U32 LLTextureCache::openAndReadEntries(std::vector& entries) +{ + U32 num_entries = mHeaderEntriesInfo.mEntries; + + mHeaderIDMap.clear(); + mTexturesSizeMap.clear(); + mFreeList.clear(); + mTexturesSizeTotal = 0; + + LLAPRFile* aprfile = NULL; + if(mUpdatedEntryMap.empty()) + { + aprfile = openHeaderEntriesFile(true, (S32)sizeof(EntriesInfo)); + } + else //update the header file first. + { + aprfile = openHeaderEntriesFile(false, 0); + updatedHeaderEntriesFile() ; + if(!aprfile) + { + return 0; + } + aprfile->seek(APR_SET, (S32)sizeof(EntriesInfo)); + } + for (U32 idx=0; idxread((void*)(&entry), (S32)sizeof(Entry)); + if (bytes_read < sizeof(Entry)) + { + LL_WARNS() << "Corrupted header entries, failed at " << idx << " / " << num_entries << LL_ENDL; + closeHeaderEntriesFile(); + purgeAllTextures(false); + return 0; + } + entries.push_back(entry); +// LL_INFOS() << "ENTRY: " << entry.mTime << " TEX: " << entry.mID << " IDX: " << idx << " Size: " << entry.mImageSize << LL_ENDL; + if(entry.mImageSize > entry.mBodySize) + { + mHeaderIDMap[entry.mID] = idx; + mTexturesSizeMap[entry.mID] = entry.mBodySize; + mTexturesSizeTotal += entry.mBodySize; + } + else + { + mFreeList.insert(idx); + } + } + closeHeaderEntriesFile(); + return num_entries; +} + +void LLTextureCache::writeEntriesAndClose(const std::vector& entries) +{ + S32 num_entries = entries.size(); + llassert_always(num_entries == mHeaderEntriesInfo.mEntries); + + if (!mReadOnly) + { + LLAPRFile* aprfile = openHeaderEntriesFile(false, (S32)sizeof(EntriesInfo)); + for (S32 idx=0; idxwrite((void*)(&entries[idx]), (S32)sizeof(Entry)); + if(bytes_written != sizeof(Entry)) + { + clearCorruptedCache() ; //clear the cache. + return ; + } + } + closeHeaderEntriesFile(); + } +} + +void LLTextureCache::writeUpdatedEntries() +{ + lockHeaders() ; + if (!mReadOnly && !mUpdatedEntryMap.empty()) + { + openHeaderEntriesFile(false, 0); + updatedHeaderEntriesFile() ; + closeHeaderEntriesFile(); + } + unlockHeaders() ; +} + +//mHeaderMutex is locked and mHeaderAPRFile is created before calling this. +void LLTextureCache::updatedHeaderEntriesFile() +{ + if (!mReadOnly && !mUpdatedEntryMap.empty() && mHeaderAPRFile) + { + //entriesInfo + mHeaderAPRFile->seek(APR_SET, 0); + S32 bytes_written = mHeaderAPRFile->write((U8*)&mHeaderEntriesInfo, sizeof(EntriesInfo)) ; + if(bytes_written != sizeof(EntriesInfo)) + { + clearCorruptedCache() ; //clear the cache. + return ; + } + + //write each updated entry + S32 entry_size = (S32)sizeof(Entry) ; + S32 prev_idx = -1 ; + S32 delta_idx ; + for (idx_entry_map_t::iterator iter = mUpdatedEntryMap.begin(); iter != mUpdatedEntryMap.end(); ++iter) + { + delta_idx = iter->first - prev_idx - 1; + prev_idx = iter->first ; + if(delta_idx) + { + mHeaderAPRFile->seek(APR_CUR, delta_idx * entry_size); + } + + bytes_written = mHeaderAPRFile->write((void*)(&iter->second), entry_size); + if(bytes_written != entry_size) + { + clearCorruptedCache() ; //clear the cache. + return ; + } + } + mUpdatedEntryMap.clear() ; + } +} +//---------------------------------------------------------------------------- + +// Called from either the main thread or the worker thread +void LLTextureCache::readHeaderCache() +{ + mHeaderMutex.lock(); + + mLRU.clear(); // always clear the LRU + + readEntriesHeader(); + + if (mHeaderEntriesInfo.mVersion != sHeaderCacheVersion + || mHeaderEntriesInfo.mAdressSize != sHeaderCacheAddressSize + || strcmp(mHeaderEntriesInfo.mEncoderVersion, sHeaderCacheEncoderVersion.c_str()) != 0) + { + if (!mReadOnly) + { + LL_INFOS() << "Texture Cache version mismatch, Purging." << LL_ENDL; + purgeAllTextures(false); + } + } + else + { + std::vector entries; + U32 num_entries = openAndReadEntries(entries); + if (num_entries) + { + U32 empty_entries = 0; + typedef std::pair lru_data_t; + std::set lru; + std::set purge_list; + for (U32 i=0; i 0) + { + if (entry.mBodySize > entry.mImageSize) + { + // Shouldn't happen, failsafe only + LL_WARNS() << "Bad entry: " << i << ": " << entry.mID << ": BodySize: " << entry.mBodySize << LL_ENDL; + purge_list.insert(i); + } + } + } + } + if (num_entries - empty_entries > sCacheMaxEntries) + { + // Special case: cache size was reduced, need to remove entries + U32 entries_to_purge = (num_entries - empty_entries) - sCacheMaxEntries; + LL_INFOS() << "Texture Cache Entries: " << num_entries << " Max: " << sCacheMaxEntries << " Empty: " << empty_entries << " Purging: " << entries_to_purge << LL_ENDL; + // We can exit the following loop with the given condition, since if we'd reach the end of the lru set we'd have: + // purge_list.size() = lru.size() = num_entries - empty_entries = entries_to_purge + sCacheMaxEntries >= entries_to_purge + // So, it's certain that iter will never reach lru.end() first. + std::set::iterator iter = lru.begin(); + while (purge_list.size() < entries_to_purge) + { + purge_list.insert(iter->second); + ++iter; + } + } + + { + S32 lru_entries = (S32)((F32)sCacheMaxEntries * TEXTURE_CACHE_LRU_SIZE); + for (std::set::iterator iter = lru.begin(); iter != lru.end(); ++iter) + { + mLRU.insert(entries[iter->second].mID); +// LL_INFOS() << "LRU: " << iter->first << " : " << iter->second << LL_ENDL; + if (--lru_entries <= 0) + break; + } + } + + if (purge_list.size() > 0) + { + LLTimer timer; + for (std::set::iterator iter = purge_list.begin(); iter != purge_list.end(); ++iter) + { + std::string tex_filename = getTextureFileName(entries[*iter].mID); + removeEntry((S32)*iter, entries[*iter], tex_filename); + + //make sure that pruning entries doesn't take too much time + if (timer.getElapsedTimeF32() > TEXTURE_PRUNING_MAX_TIME) + { + break; + } + } + writeEntriesAndClose(entries); + } + else + { + //entries are not changed, nothing here. + } + } + } + mHeaderMutex.unlock(); +} + +////////////////////////////////////////////////////////////////////////////// + +//the header mutex is locked before calling this. +void LLTextureCache::clearCorruptedCache() +{ + LL_WARNS() << "the texture cache is corrupted, need to be cleared." << LL_ENDL ; + + closeHeaderEntriesFile();//close possible file handler + purgeAllTextures(false) ; //clear the cache. + + if (!mReadOnly) //regenerate the directory tree if not exists. + { + LLFile::mkdir(mTexturesDirName); + + const char* subdirs = "0123456789abcdef"; + for (S32 i=0; i<16; i++) + { + std::string dirname = mTexturesDirName + gDirUtilp->getDirDelimiter() + subdirs[i]; + LLFile::mkdir(dirname); + } + } + + return ; +} + +void LLTextureCache::purgeAllTextures(bool purge_directories) +{ + if (!mReadOnly) + { + const char* subdirs = "0123456789abcdef"; + std::string delem = gDirUtilp->getDirDelimiter(); + std::string mask = "*"; + for (S32 i=0; i<16; i++) + { + std::string dirname = mTexturesDirName + delem + subdirs[i]; + LL_INFOS() << "Deleting files in directory: " << dirname << LL_ENDL; + if (purge_directories) + { + gDirUtilp->deleteDirAndContents(dirname); + } + else + { + gDirUtilp->deleteFilesInDir(dirname, mask); + } +#if LL_WINDOWS + // Texture cache can be large and can take a while to remove + // assure OS that processes is alive and not hanging + MSG msg; + PeekMessage(&msg, 0, 0, 0, PM_NOREMOVE | PM_NOYIELD); +#endif + } + gDirUtilp->deleteFilesInDir(mTexturesDirName, mask); // headers, fast cache + if (purge_directories) + { + LLFile::rmdir(mTexturesDirName); + } + } + mHeaderIDMap.clear(); + mTexturesSizeMap.clear(); + mTexturesSizeTotal = 0; + mFreeList.clear(); + mTexturesSizeTotal = 0; + mUpdatedEntryMap.clear(); + + // Info with 0 entries + setEntriesHeader(); + writeEntriesHeader(); + + LL_INFOS() << "The entire texture cache is cleared." << LL_ENDL ; +} + +void LLTextureCache::purgeTexturesLazy(F32 time_limit_sec) +{ + if (mReadOnly) + { + return; + } + + if (!mThreaded) + { + LLAppViewer::instance()->pauseMainloopTimeout(); + } + + // time_limit doesn't account for lock time + LLMutexLock lock(&mHeaderMutex); + + if (mPurgeEntryList.empty()) + { + // Read the entries list and form list of textures to purge + std::vector entries; + U32 num_entries = openAndReadEntries(entries); + if (!num_entries) + { + return; // nothing to purge + } + + // Use mTexturesSizeMap to collect UUIDs of textures with bodies + typedef std::set > time_idx_set_t; + std::set > time_idx_set; + for (size_map_t::iterator iter1 = mTexturesSizeMap.begin(); + iter1 != mTexturesSizeMap.end(); ++iter1) + { + if (iter1->second > 0) + { + id_map_t::iterator iter2 = mHeaderIDMap.find(iter1->first); + if (iter2 != mHeaderIDMap.end()) + { + S32 idx = iter2->second; + time_idx_set.insert(std::make_pair(entries[idx].mTime, idx)); + } + else + { + LL_ERRS("TextureCache") << "mTexturesSizeMap / mHeaderIDMap corrupted." << LL_ENDL; + } + } + } + + S64 cache_size = mTexturesSizeTotal; + S64 purged_cache_size = (llmax(cache_size, sCacheMaxTexturesSize) * (S64)((1.f - TEXTURE_CACHE_PURGE_AMOUNT) * 100)) / 100; + for (time_idx_set_t::iterator iter = time_idx_set.begin(); + iter != time_idx_set.end(); ++iter) + { + S32 idx = iter->second; + if (cache_size >= purged_cache_size) + { + cache_size -= entries[idx].mBodySize; + mPurgeEntryList.push_back(std::pair(idx, entries[idx])); + } + else + { + break; + } + } + LL_DEBUGS("TextureCache") << "Formed Purge list of " << mPurgeEntryList.size() << " entries" << LL_ENDL; + } + else + { + // Remove collected entried + LLTimer timer; + while (!mPurgeEntryList.empty() && timer.getElapsedTimeF32() < time_limit_sec) + { + S32 idx = mPurgeEntryList.back().first; + Entry entry = mPurgeEntryList.back().second; + mPurgeEntryList.pop_back(); + // make sure record is still valid + id_map_t::iterator iter_header = mHeaderIDMap.find(entry.mID); + if (iter_header != mHeaderIDMap.end() && iter_header->second == idx) + { + std::string tex_filename = getTextureFileName(entry.mID); + removeEntry(idx, entry, tex_filename); + writeEntryToHeaderImmediately(idx, entry); + } + } + } +} + +void LLTextureCache::purgeTextures(bool validate) +{ + if (mReadOnly) + { + return; + } + + if (!mThreaded) + { + // *FIX:Mani - watchdog off. + LLAppViewer::instance()->pauseMainloopTimeout(); + } + + LLMutexLock lock(&mHeaderMutex); + + LL_INFOS() << "TEXTURE CACHE: Purging." << LL_ENDL; + + // Read the entries list + std::vector entries; + U32 num_entries = openAndReadEntries(entries); + if (!num_entries) + { + return; // nothing to purge + } + + // Use mTexturesSizeMap to collect UUIDs of textures with bodies + typedef std::set > time_idx_set_t; + std::set > time_idx_set; + for (size_map_t::iterator iter1 = mTexturesSizeMap.begin(); + iter1 != mTexturesSizeMap.end(); ++iter1) + { + if (iter1->second > 0) + { + id_map_t::iterator iter2 = mHeaderIDMap.find(iter1->first); + if (iter2 != mHeaderIDMap.end()) + { + S32 idx = iter2->second; + time_idx_set.insert(std::make_pair(entries[idx].mTime, idx)); +// LL_INFOS() << "TIME: " << entries[idx].mTime << " TEX: " << entries[idx].mID << " IDX: " << idx << " Size: " << entries[idx].mImageSize << LL_ENDL; + } + else + { + LL_ERRS() << "mTexturesSizeMap / mHeaderIDMap corrupted." << LL_ENDL ; + } + } + } + + // Validate 1/256th of the files on startup + U32 validate_idx = 0; + if (validate) + { + validate_idx = gSavedSettings.getU32("CacheValidateCounter"); + U32 next_idx = (validate_idx + 1) % 256; + gSavedSettings.setU32("CacheValidateCounter", next_idx); + LL_DEBUGS("TextureCache") << "TEXTURE CACHE: Validating: " << validate_idx << LL_ENDL; + } + + S64 cache_size = mTexturesSizeTotal; + S64 purged_cache_size = (llmax(cache_size, sCacheMaxTexturesSize) * (S64)((1.f - TEXTURE_CACHE_PURGE_AMOUNT) * 100)) / 100; + S32 purge_count = 0; + for (time_idx_set_t::iterator iter = time_idx_set.begin(); + iter != time_idx_set.end(); ++iter) + { + S32 idx = iter->second; + bool purge_entry = false; + + if (cache_size >= purged_cache_size) + { + purge_entry = true; + } + else if (validate) + { + // make sure file exists and is the correct size + U32 uuididx = entries[idx].mID.mData[0]; + if (uuididx == validate_idx) + { + std::string filename = getTextureFileName(entries[idx].mID); + LL_DEBUGS("TextureCache") << "Validating: " << filename << "Size: " << entries[idx].mBodySize << LL_ENDL; + // mHeaderAPRFilePoolp because this is under header mutex in main thread + S32 bodysize = LLAPRFile::size(filename, mHeaderAPRFilePoolp); + if (bodysize != entries[idx].mBodySize) + { + LL_WARNS("TextureCache") << "TEXTURE CACHE BODY HAS BAD SIZE: " << bodysize << " != " << entries[idx].mBodySize << filename << LL_ENDL; + purge_entry = true; + } + } + } + else + { + break; + } + + if (purge_entry) + { + purge_count++; + std::string filename = getTextureFileName(entries[idx].mID); + LL_DEBUGS("TextureCache") << "PURGING: " << filename << LL_ENDL; + cache_size -= entries[idx].mBodySize; + removeEntry(idx, entries[idx], filename) ; + } + } + + LL_DEBUGS("TextureCache") << "TEXTURE CACHE: Writing Entries: " << num_entries << LL_ENDL; + + writeEntriesAndClose(entries); + + // *FIX:Mani - watchdog back on. + LLAppViewer::instance()->resumeMainloopTimeout(); + + LL_INFOS("TextureCache") << "TEXTURE CACHE:" + << " PURGED: " << purge_count + << " ENTRIES: " << num_entries + << " CACHE SIZE: " << mTexturesSizeTotal / (1024 * 1024) << " MB" + << LL_ENDL; +} + +////////////////////////////////////////////////////////////////////////////// + +// call lockWorkers() first! +LLTextureCacheWorker* LLTextureCache::getReader(handle_t handle) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + LLTextureCacheWorker* res = NULL; + handle_map_t::iterator iter = mReaders.find(handle); + if (iter != mReaders.end()) + { + res = iter->second; + } + return res; +} + +LLTextureCacheWorker* LLTextureCache::getWriter(handle_t handle) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + LLTextureCacheWorker* res = NULL; + handle_map_t::iterator iter = mWriters.find(handle); + if (iter != mWriters.end()) + { + res = iter->second; + } + return res; +} + +////////////////////////////////////////////////////////////////////////////// +// Called from work thread + +// Reads imagesize from the header, updates timestamp +S32 LLTextureCache::getHeaderCacheEntry(const LLUUID& id, Entry& entry) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + LLMutexLock lock(&mHeaderMutex); + S32 idx = openAndReadEntry(id, entry, false); + if (idx >= 0) + { + updateEntryTimeStamp(idx, entry); // updates time + } + return idx; +} + +// Writes imagesize to the header, updates timestamp +S32 LLTextureCache::setHeaderCacheEntry(const LLUUID& id, Entry& entry, S32 imagesize, S32 datasize) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + mHeaderMutex.lock(); + S32 idx = openAndReadEntry(id, entry, true); // read or create + mHeaderMutex.unlock(); + + if(idx < 0) // retry once + { + readHeaderCache(); // We couldn't write an entry, so refresh the LRU + + mHeaderMutex.lock(); + idx = openAndReadEntry(id, entry, true); + mHeaderMutex.unlock(); + } + + if (idx >= 0) + { + updateEntry(idx, entry, imagesize, datasize); + } + else + { + LL_WARNS() << "Failed to set cache entry for image: " << id << LL_ENDL; + // We couldn't write to file, switch to read only mode and clear data + setReadOnly(true); + clearCorruptedCache(); // won't remove files due to "read only" + } + + return idx; +} + +////////////////////////////////////////////////////////////////////////////// + +// Calls from texture pipeline thread (i.e. LLTextureFetch) + +LLTextureCache::handle_t LLTextureCache::readFromCache(const std::string& filename, const LLUUID& id, + S32 offset, S32 size, ReadResponder* responder) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + // Note: checking to see if an entry exists can cause a stall, + // so let the thread handle it + LLMutexLock lock(&mWorkersMutex); + LLTextureCacheWorker* worker = new LLTextureCacheLocalFileWorker(this, filename, id, + NULL, size, offset, 0, + responder); + handle_t handle = worker->read(); + mReaders[handle] = worker; + return handle; +} + +LLTextureCache::handle_t LLTextureCache::readFromCache(const LLUUID& id, + S32 offset, S32 size, ReadResponder* responder) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + // Note: checking to see if an entry exists can cause a stall, + // so let the thread handle it + LLMutexLock lock(&mWorkersMutex); + LLTextureCacheWorker* worker = new LLTextureCacheRemoteWorker(this, id, + NULL, size, offset, + 0, NULL, 0, responder); + handle_t handle = worker->read(); + mReaders[handle] = worker; + return handle; +} + + +bool LLTextureCache::readComplete(handle_t handle, bool abort) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + lockWorkers(); + handle_map_t::iterator iter = mReaders.find(handle); + LLTextureCacheWorker* worker = NULL; + bool complete = false; + if (iter != mReaders.end()) + { + worker = iter->second; + complete = worker->complete(); + + if(!complete && abort) + { + abortRequest(handle, true) ; + } + } + if (worker && (complete || abort)) + { + mReaders.erase(iter); + unlockWorkers(); + worker->scheduleDelete(); + } + else + { + unlockWorkers(); + } + return (complete || abort); +} + +LLTextureCache::handle_t LLTextureCache::writeToCache(const LLUUID& id, + const U8* data, S32 datasize, S32 imagesize, + LLPointer rawimage, S32 discardlevel, + WriteResponder* responder) +{ + if (mReadOnly) + { + delete responder; + return LLWorkerThread::nullHandle(); + } + if (mDoPurge) + { + // NOTE: Needs to be done on the control thread + // (i.e. here) + purgeTexturesLazy(TEXTURE_LAZY_PURGE_TIME_LIMIT); + mDoPurge = !mPurgeEntryList.empty(); + } + LLMutexLock lock(&mWorkersMutex); + LLTextureCacheWorker* worker = new LLTextureCacheRemoteWorker(this, id, + data, datasize, 0, + imagesize, rawimage, discardlevel, responder); + handle_t handle = worker->write(); + mWriters[handle] = worker; + return handle; +} + +//called in the main thread +LLPointer LLTextureCache::readFromFastCache(const LLUUID& id, S32& discardlevel) +{ + U32 offset; + { + LLMutexLock lock(&mHeaderMutex); + id_map_t::const_iterator iter = mHeaderIDMap.find(id); + if(iter == mHeaderIDMap.end()) + { + return NULL; //not in the cache + } + + offset = iter->second; + } + offset *= TEXTURE_FAST_CACHE_ENTRY_SIZE; + + U8* data; + S32 head[4]; + { + LLMutexLock lock(&mFastCacheMutex); + + openFastCache(); + + mFastCachep->seek(APR_SET, offset); + + if(mFastCachep->read(head, TEXTURE_FAST_CACHE_ENTRY_OVERHEAD) != TEXTURE_FAST_CACHE_ENTRY_OVERHEAD) + { + //cache corrupted or under thread race condition + closeFastCache(); + return NULL; + } + + S32 image_size = head[0] * head[1] * head[2]; + if(image_size <= 0 + || image_size > TEXTURE_FAST_CACHE_DATA_SIZE + || head[3] < 0) //invalid + { + closeFastCache(); + return NULL; + } + discardlevel = head[3]; + + data = (U8*)ll_aligned_malloc_16(image_size); + if(mFastCachep->read(data, image_size) != image_size) + { + ll_aligned_free_16(data); + closeFastCache(); + return NULL; + } + + closeFastCache(); + } + LLPointer raw = new LLImageRaw(data, head[0], head[1], head[2], true); + + return raw; +} + +//return the fast cache location +bool LLTextureCache::writeToFastCache(LLUUID image_id, S32 id, LLPointer raw, S32 discardlevel) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + + LLImageDataSharedLock lock(raw); + + //rescale image if needed + if (raw.isNull() || raw->isBufferInvalid() || !raw->getData()) + { + LL_ERRS() << "Attempted to write NULL raw image to fastcache" << LL_ENDL; + return false; + } + + S32 w, h, c; + w = raw->getWidth(); + h = raw->getHeight(); + c = raw->getComponents(); + + S32 i = 0 ; + + // Search for a discard level that will fit into fast cache + while(((w >> i) * (h >> i) * c) > TEXTURE_FAST_CACHE_DATA_SIZE) + { + ++i ; + } + + if(i) + { + w >>= i; + h >>= i; + if(w * h *c > 0) //valid + { + // Make a duplicate to keep the original raw image untouched. + raw = raw->duplicate(); + + if (raw->isBufferInvalid()) + { + LL_WARNS() << "Invalid image duplicate buffer" << LL_ENDL; + return false; + } + + raw->scale(w, h); + + discardlevel += i ; + } + } + + //copy data + memcpy(mFastCachePadBuffer, &w, sizeof(S32)); + memcpy(mFastCachePadBuffer + sizeof(S32), &h, sizeof(S32)); + memcpy(mFastCachePadBuffer + sizeof(S32) * 2, &c, sizeof(S32)); + memcpy(mFastCachePadBuffer + sizeof(S32) * 3, &discardlevel, sizeof(S32)); + + S32 copy_size = w * h * c; + if(copy_size > 0) //valid + { + copy_size = llmin(copy_size, TEXTURE_FAST_CACHE_ENTRY_SIZE - TEXTURE_FAST_CACHE_ENTRY_OVERHEAD); + memcpy(mFastCachePadBuffer + TEXTURE_FAST_CACHE_ENTRY_OVERHEAD, raw->getData(), copy_size); + } + S32 offset = id * TEXTURE_FAST_CACHE_ENTRY_SIZE; + + { + LLMutexLock lock(&mFastCacheMutex); + + openFastCache(); + + mFastCachep->seek(APR_SET, offset); + + //no need to do this assertion check. When it fails, let it fail quietly. + //this failure could happen because other viewer removes the fast cache file when clearing cache. + //--> llassert_always(mFastCachep->write(mFastCachePadBuffer, TEXTURE_FAST_CACHE_ENTRY_SIZE) == TEXTURE_FAST_CACHE_ENTRY_SIZE); + mFastCachep->write(mFastCachePadBuffer, TEXTURE_FAST_CACHE_ENTRY_SIZE); + + closeFastCache(true); + } + + return true; +} + +void LLTextureCache::openFastCache(bool first_time) +{ + if(!mFastCachep) + { + if(first_time) + { + if(!mFastCachePadBuffer) + { + mFastCachePadBuffer = (U8*)ll_aligned_malloc_16(TEXTURE_FAST_CACHE_ENTRY_SIZE); + } + mFastCachePoolp = new LLVolatileAPRPool(); // is_local= true by default, so not thread safe by default + if (LLAPRFile::isExist(mFastCacheFileName, mFastCachePoolp)) + { + mFastCachep = new LLAPRFile(mFastCacheFileName, APR_READ|APR_WRITE|APR_BINARY, mFastCachePoolp) ; + } + else + { + mFastCachep = new LLAPRFile(mFastCacheFileName, APR_CREATE|APR_READ|APR_WRITE|APR_BINARY, mFastCachePoolp) ; + } + } + else + { + mFastCachep = new LLAPRFile(mFastCacheFileName, APR_READ|APR_WRITE|APR_BINARY, mFastCachePoolp) ; + } + + mFastCacheTimer.reset(); + } + return; +} + +void LLTextureCache::closeFastCache(bool forced) +{ + static const F32 timeout = 10.f ; //seconds + + if(!mFastCachep) + { + return ; + } + + if(!forced && mFastCacheTimer.getElapsedTimeF32() < timeout) + { + return ; + } + + delete mFastCachep; + mFastCachep = NULL; + return; +} + +bool LLTextureCache::writeComplete(handle_t handle, bool abort) +{ + lockWorkers(); + handle_map_t::iterator iter = mWriters.find(handle); + llassert(iter != mWriters.end()); + if (iter != mWriters.end()) + { + LLTextureCacheWorker* worker = iter->second; + if (worker->complete() || abort) + { + mWriters.erase(handle); + unlockWorkers(); + worker->scheduleDelete(); + return true; + } + } + unlockWorkers(); + return false; +} + +void LLTextureCache::prioritizeWrite(handle_t handle) +{ + // Don't prioritize yet, we might be working on this now + // which could create a deadlock + LLMutexLock lock(&mListMutex); + mPrioritizeWriteList.push_back(handle); +} + +void LLTextureCache::addCompleted(Responder* responder, bool success) +{ + LLMutexLock lock(&mListMutex); + mCompletedList.push_back(std::make_pair(responder,success)); +} + +////////////////////////////////////////////////////////////////////////////// + +//called after mHeaderMutex is locked. +void LLTextureCache::removeCachedTexture(const LLUUID& id) +{ + if(mTexturesSizeMap.find(id) != mTexturesSizeMap.end()) + { + mTexturesSizeTotal -= mTexturesSizeMap[id] ; + mTexturesSizeMap.erase(id); + } + mHeaderIDMap.erase(id); + // We are inside header's mutex so mHeaderAPRFilePoolp is safe to use, + // but getLocalAPRFilePool() is not safe, it might be in use by worker + LLAPRFile::remove(getTextureFileName(id), mHeaderAPRFilePoolp); +} + +//called after mHeaderMutex is locked. +void LLTextureCache::removeEntry(S32 idx, Entry& entry, std::string& filename) +{ + bool file_maybe_exists = true; // Always attempt to remove when idx is invalid. + + if(idx >= 0) //valid entry + { + if (entry.mBodySize == 0) // Always attempt to remove when mBodySize > 0. + { + // Sanity check. Shouldn't exist when body size is 0. + // We are inside header's mutex so mHeaderAPRFilePoolp is safe to use, + // but getLocalAPRFilePool() is not safe, it might be in use by worker + if (LLAPRFile::isExist(filename, mHeaderAPRFilePoolp)) + { + LL_WARNS("TextureCache") << "Entry has body size of zero but file " << filename << " exists. Deleting this file, too." << LL_ENDL; + } + else + { + file_maybe_exists = false; + } + } + mTexturesSizeTotal -= entry.mBodySize; + + entry.mImageSize = -1; + entry.mBodySize = 0; + mHeaderIDMap.erase(entry.mID); + mTexturesSizeMap.erase(entry.mID); + mFreeList.insert(idx); + } + + if (file_maybe_exists) + { + LLAPRFile::remove(filename, mHeaderAPRFilePoolp); + } +} + +bool LLTextureCache::removeFromCache(const LLUUID& id) +{ + //LL_WARNS() << "Removing texture from cache: " << id << LL_ENDL; + bool ret = false ; + if (!mReadOnly) + { + lockHeaders() ; + + Entry entry; + S32 idx = openAndReadEntry(id, entry, false); + std::string tex_filename = getTextureFileName(id); + removeEntry(idx, entry, tex_filename) ; + if (idx >= 0) + { + writeEntryToHeaderImmediately(idx, entry); + ret = true; + } + + unlockHeaders() ; + } + return ret ; +} + +////////////////////////////////////////////////////////////////////////////// + +LLTextureCache::ReadResponder::ReadResponder() + : mImageSize(0), + mImageLocal(false) +{ +} + +void LLTextureCache::ReadResponder::setData(U8* data, S32 datasize, S32 imagesize, S32 imageformat, bool imagelocal) +{ + if (mFormattedImage.notNull()) + { + llassert_always(mFormattedImage->getCodec() == imageformat); + mFormattedImage->appendData(data, datasize); + } + else + { + mFormattedImage = LLImageFormatted::createFromType(imageformat); + mFormattedImage->setData(data,datasize); + } + mImageSize = imagesize; + mImageLocal = imagelocal; +} + +////////////////////////////////////////////////////////////////////////////// diff --git a/indra/newview/lltexturecache.h b/indra/newview/lltexturecache.h index 5ac0a0428d..70766b0605 100644 --- a/indra/newview/lltexturecache.h +++ b/indra/newview/lltexturecache.h @@ -1,255 +1,255 @@ -/** - * @file lltexturecache.h - * @brief Object for managing texture cachees. - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLTEXTURECACHE_H -#define LL_LLTEXTURECACHE_H - -#include "lldir.h" -#include "llstl.h" -#include "llstring.h" -#include "lluuid.h" - -#include "llworkerthread.h" - -class LLImageFormatted; -class LLTextureCacheWorker; -class LLImageRaw; - -class LLTextureCache : public LLWorkerThread -{ - friend class LLTextureCacheWorker; - friend class LLTextureCacheRemoteWorker; - friend class LLTextureCacheLocalFileWorker; - -private: - -#if LL_WINDOWS -#pragma pack(push,1) -#endif - - // Entries - static const U32 sHeaderEncoderStringSize = 32; - struct EntriesInfo - { - EntriesInfo() : mVersion(0.f), mAdressSize(0), mEntries(0) { memset(mEncoderVersion, 0, sHeaderEncoderStringSize); } - F32 mVersion; - U32 mAdressSize; - char mEncoderVersion[sHeaderEncoderStringSize]; - U32 mEntries; - }; - struct Entry - { - Entry() : - mBodySize(0), - mImageSize(0), - mTime(0) - { - } - Entry(const LLUUID& id, S32 imagesize, S32 bodysize, U32 time) : - mID(id), mImageSize(imagesize), mBodySize(bodysize), mTime(time) {} - void init(const LLUUID& id, U32 time) { mID = id, mImageSize = 0; mBodySize = 0; mTime = time; } - Entry& operator=(const Entry& entry) {mID = entry.mID, mImageSize = entry.mImageSize; mBodySize = entry.mBodySize; mTime = entry.mTime; return *this;} - LLUUID mID; // 16 bytes - S32 mImageSize; // total size of image if known - S32 mBodySize; // size of body file in body cache - U32 mTime; // seconds since 1/1/1970 - }; - -#if LL_WINDOWS -#pragma pack(pop) -#endif - -public: - - class Responder : public LLResponder - { - public: - virtual void setData(U8* data, S32 datasize, S32 imagesize, S32 imageformat, bool imagelocal) = 0; - }; - - class ReadResponder : public Responder - { - public: - ReadResponder(); - void setData(U8* data, S32 datasize, S32 imagesize, S32 imageformat, bool imagelocal); - void setImage(LLImageFormatted* image) { mFormattedImage = image; } - protected: - LLPointer mFormattedImage; - S32 mImageSize; - bool mImageLocal; - }; - - class WriteResponder : public Responder - { - void setData(U8* data, S32 datasize, S32 imagesize, S32 imageformat, bool imagelocal) - { - // not used - } - }; - - LLTextureCache(bool threaded); - ~LLTextureCache(); - - /*virtual*/ size_t update(F32 max_time_ms); - - void purgeCache(ELLPath location, bool remove_dir = true); - void setReadOnly(bool read_only) ; - S64 initCache(ELLPath location, S64 maxsize, bool texture_cache_mismatch); - - handle_t readFromCache(const std::string& local_filename, const LLUUID& id, S32 offset, S32 size, - ReadResponder* responder); - - handle_t readFromCache(const LLUUID& id, S32 offset, S32 size, - ReadResponder* responder); - bool readComplete(handle_t handle, bool abort); - handle_t writeToCache(const LLUUID& id, const U8* data, S32 datasize, S32 imagesize, LLPointer rawimage, S32 discardlevel, - WriteResponder* responder); - LLPointer readFromFastCache(const LLUUID& id, S32& discardlevel); - bool writeComplete(handle_t handle, bool abort = false); - void prioritizeWrite(handle_t handle); - - bool removeFromCache(const LLUUID& id); - - // For LLTextureCacheWorker::Responder - LLTextureCacheWorker* getReader(handle_t handle); - LLTextureCacheWorker* getWriter(handle_t handle); - void lockWorkers() { mWorkersMutex.lock(); } - void unlockWorkers() { mWorkersMutex.unlock(); } - - // debug - S32 getNumReads() { return mReaders.size(); } - S32 getNumWrites() { return mWriters.size(); } - S64Bytes getUsage() { return S64Bytes(mTexturesSizeTotal); } - S64Bytes getMaxUsage() { return S64Bytes(sCacheMaxTexturesSize); } - U32 getEntries() { return mHeaderEntriesInfo.mEntries; } - U32 getMaxEntries() { return sCacheMaxEntries; }; - bool isInCache(const LLUUID& id) ; - bool isInLocal(const LLUUID& id) ; //not thread safe at the moment - -protected: - // Accessed by LLTextureCacheWorker - std::string getLocalFileName(const LLUUID& id); - std::string getTextureFileName(const LLUUID& id); - void addCompleted(Responder* responder, bool success); - -protected: - //void setFileAPRPool(apr_pool_t* pool) { mFileAPRPool = pool ; } - -private: - void setDirNames(ELLPath location); - void readHeaderCache(); - void clearCorruptedCache(); - void purgeAllTextures(bool purge_directories); - void purgeTexturesLazy(F32 time_limit_sec); - void purgeTextures(bool validate); - LLAPRFile* openHeaderEntriesFile(bool readonly, S32 offset); - void closeHeaderEntriesFile(); - void readEntriesHeader(); - void setEntriesHeader(); - void writeEntriesHeader(); - S32 openAndReadEntry(const LLUUID& id, Entry& entry, bool create); - bool updateEntry(S32& idx, Entry& entry, S32 new_image_size, S32 new_body_size); - void updateEntryTimeStamp(S32 idx, Entry& entry) ; - U32 openAndReadEntries(std::vector& entries); - void writeEntriesAndClose(const std::vector& entries); - void readEntryFromHeaderImmediately(S32& idx, Entry& entry) ; - void writeEntryToHeaderImmediately(S32& idx, Entry& entry, bool write_header = false) ; - void removeEntry(S32 idx, Entry& entry, std::string& filename); - void removeCachedTexture(const LLUUID& id) ; - S32 getHeaderCacheEntry(const LLUUID& id, Entry& entry); - S32 setHeaderCacheEntry(const LLUUID& id, Entry& entry, S32 imagesize, S32 datasize); - void writeUpdatedEntries() ; - void updatedHeaderEntriesFile() ; - void lockHeaders() { mHeaderMutex.lock(); } - void unlockHeaders() { mHeaderMutex.unlock(); } - - void openFastCache(bool first_time = false); - void closeFastCache(bool forced = false); - bool writeToFastCache(LLUUID image_id, S32 cache_id, LLPointer raw, S32 discardlevel); - -private: - // Internal - LLMutex mWorkersMutex; - LLMutex mHeaderMutex; - LLMutex mListMutex; - LLMutex mFastCacheMutex; - LLAPRFile* mHeaderAPRFile; - LLVolatileAPRPool* mFastCachePoolp; - - // mLocalAPRFilePoolp is not thread safe and is meant only for workers - // howhever mHeaderEntriesFileName is accessed not from workers' threads - // so it needs own pool (not thread safe by itself, relies onto header's mutex) - LLVolatileAPRPool* mHeaderAPRFilePoolp; - - typedef std::map handle_map_t; - handle_map_t mReaders; - handle_map_t mWriters; - - typedef std::vector handle_list_t; - handle_list_t mPrioritizeWriteList; - - typedef std::vector, bool> > responder_list_t; - responder_list_t mCompletedList; - - bool mReadOnly; - - // HEADERS (Include first mip) - std::string mHeaderEntriesFileName; - std::string mHeaderDataFileName; - std::string mFastCacheFileName; - EntriesInfo mHeaderEntriesInfo; - std::set mFreeList; // deleted entries - std::set mLRU; - typedef std::map id_map_t; - id_map_t mHeaderIDMap; - - LLAPRFile* mFastCachep; - LLFrameTimer mFastCacheTimer; - U8* mFastCachePadBuffer; - - // BODIES (TEXTURES minus headers) - std::string mTexturesDirName; - typedef std::map size_map_t; - size_map_t mTexturesSizeMap; - S64 mTexturesSizeTotal; - LLAtomicBool mDoPurge; - - typedef std::map idx_entry_map_t; - idx_entry_map_t mUpdatedEntryMap; - typedef std::vector > idx_entry_vector_t; - idx_entry_vector_t mPurgeEntryList; - - // Statics - static F32 sHeaderCacheVersion; - static U32 sHeaderCacheAddressSize; - static std::string sHeaderCacheEncoderVersion; - static U32 sCacheMaxEntries; - static S64 sCacheMaxTexturesSize; -}; - -extern const S32 TEXTURE_CACHE_ENTRY_SIZE; - -#endif // LL_LLTEXTURECACHE_H +/** + * @file lltexturecache.h + * @brief Object for managing texture cachees. + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLTEXTURECACHE_H +#define LL_LLTEXTURECACHE_H + +#include "lldir.h" +#include "llstl.h" +#include "llstring.h" +#include "lluuid.h" + +#include "llworkerthread.h" + +class LLImageFormatted; +class LLTextureCacheWorker; +class LLImageRaw; + +class LLTextureCache : public LLWorkerThread +{ + friend class LLTextureCacheWorker; + friend class LLTextureCacheRemoteWorker; + friend class LLTextureCacheLocalFileWorker; + +private: + +#if LL_WINDOWS +#pragma pack(push,1) +#endif + + // Entries + static const U32 sHeaderEncoderStringSize = 32; + struct EntriesInfo + { + EntriesInfo() : mVersion(0.f), mAdressSize(0), mEntries(0) { memset(mEncoderVersion, 0, sHeaderEncoderStringSize); } + F32 mVersion; + U32 mAdressSize; + char mEncoderVersion[sHeaderEncoderStringSize]; + U32 mEntries; + }; + struct Entry + { + Entry() : + mBodySize(0), + mImageSize(0), + mTime(0) + { + } + Entry(const LLUUID& id, S32 imagesize, S32 bodysize, U32 time) : + mID(id), mImageSize(imagesize), mBodySize(bodysize), mTime(time) {} + void init(const LLUUID& id, U32 time) { mID = id, mImageSize = 0; mBodySize = 0; mTime = time; } + Entry& operator=(const Entry& entry) {mID = entry.mID, mImageSize = entry.mImageSize; mBodySize = entry.mBodySize; mTime = entry.mTime; return *this;} + LLUUID mID; // 16 bytes + S32 mImageSize; // total size of image if known + S32 mBodySize; // size of body file in body cache + U32 mTime; // seconds since 1/1/1970 + }; + +#if LL_WINDOWS +#pragma pack(pop) +#endif + +public: + + class Responder : public LLResponder + { + public: + virtual void setData(U8* data, S32 datasize, S32 imagesize, S32 imageformat, bool imagelocal) = 0; + }; + + class ReadResponder : public Responder + { + public: + ReadResponder(); + void setData(U8* data, S32 datasize, S32 imagesize, S32 imageformat, bool imagelocal); + void setImage(LLImageFormatted* image) { mFormattedImage = image; } + protected: + LLPointer mFormattedImage; + S32 mImageSize; + bool mImageLocal; + }; + + class WriteResponder : public Responder + { + void setData(U8* data, S32 datasize, S32 imagesize, S32 imageformat, bool imagelocal) + { + // not used + } + }; + + LLTextureCache(bool threaded); + ~LLTextureCache(); + + /*virtual*/ size_t update(F32 max_time_ms); + + void purgeCache(ELLPath location, bool remove_dir = true); + void setReadOnly(bool read_only) ; + S64 initCache(ELLPath location, S64 maxsize, bool texture_cache_mismatch); + + handle_t readFromCache(const std::string& local_filename, const LLUUID& id, S32 offset, S32 size, + ReadResponder* responder); + + handle_t readFromCache(const LLUUID& id, S32 offset, S32 size, + ReadResponder* responder); + bool readComplete(handle_t handle, bool abort); + handle_t writeToCache(const LLUUID& id, const U8* data, S32 datasize, S32 imagesize, LLPointer rawimage, S32 discardlevel, + WriteResponder* responder); + LLPointer readFromFastCache(const LLUUID& id, S32& discardlevel); + bool writeComplete(handle_t handle, bool abort = false); + void prioritizeWrite(handle_t handle); + + bool removeFromCache(const LLUUID& id); + + // For LLTextureCacheWorker::Responder + LLTextureCacheWorker* getReader(handle_t handle); + LLTextureCacheWorker* getWriter(handle_t handle); + void lockWorkers() { mWorkersMutex.lock(); } + void unlockWorkers() { mWorkersMutex.unlock(); } + + // debug + S32 getNumReads() { return mReaders.size(); } + S32 getNumWrites() { return mWriters.size(); } + S64Bytes getUsage() { return S64Bytes(mTexturesSizeTotal); } + S64Bytes getMaxUsage() { return S64Bytes(sCacheMaxTexturesSize); } + U32 getEntries() { return mHeaderEntriesInfo.mEntries; } + U32 getMaxEntries() { return sCacheMaxEntries; }; + bool isInCache(const LLUUID& id) ; + bool isInLocal(const LLUUID& id) ; //not thread safe at the moment + +protected: + // Accessed by LLTextureCacheWorker + std::string getLocalFileName(const LLUUID& id); + std::string getTextureFileName(const LLUUID& id); + void addCompleted(Responder* responder, bool success); + +protected: + //void setFileAPRPool(apr_pool_t* pool) { mFileAPRPool = pool ; } + +private: + void setDirNames(ELLPath location); + void readHeaderCache(); + void clearCorruptedCache(); + void purgeAllTextures(bool purge_directories); + void purgeTexturesLazy(F32 time_limit_sec); + void purgeTextures(bool validate); + LLAPRFile* openHeaderEntriesFile(bool readonly, S32 offset); + void closeHeaderEntriesFile(); + void readEntriesHeader(); + void setEntriesHeader(); + void writeEntriesHeader(); + S32 openAndReadEntry(const LLUUID& id, Entry& entry, bool create); + bool updateEntry(S32& idx, Entry& entry, S32 new_image_size, S32 new_body_size); + void updateEntryTimeStamp(S32 idx, Entry& entry) ; + U32 openAndReadEntries(std::vector& entries); + void writeEntriesAndClose(const std::vector& entries); + void readEntryFromHeaderImmediately(S32& idx, Entry& entry) ; + void writeEntryToHeaderImmediately(S32& idx, Entry& entry, bool write_header = false) ; + void removeEntry(S32 idx, Entry& entry, std::string& filename); + void removeCachedTexture(const LLUUID& id) ; + S32 getHeaderCacheEntry(const LLUUID& id, Entry& entry); + S32 setHeaderCacheEntry(const LLUUID& id, Entry& entry, S32 imagesize, S32 datasize); + void writeUpdatedEntries() ; + void updatedHeaderEntriesFile() ; + void lockHeaders() { mHeaderMutex.lock(); } + void unlockHeaders() { mHeaderMutex.unlock(); } + + void openFastCache(bool first_time = false); + void closeFastCache(bool forced = false); + bool writeToFastCache(LLUUID image_id, S32 cache_id, LLPointer raw, S32 discardlevel); + +private: + // Internal + LLMutex mWorkersMutex; + LLMutex mHeaderMutex; + LLMutex mListMutex; + LLMutex mFastCacheMutex; + LLAPRFile* mHeaderAPRFile; + LLVolatileAPRPool* mFastCachePoolp; + + // mLocalAPRFilePoolp is not thread safe and is meant only for workers + // howhever mHeaderEntriesFileName is accessed not from workers' threads + // so it needs own pool (not thread safe by itself, relies onto header's mutex) + LLVolatileAPRPool* mHeaderAPRFilePoolp; + + typedef std::map handle_map_t; + handle_map_t mReaders; + handle_map_t mWriters; + + typedef std::vector handle_list_t; + handle_list_t mPrioritizeWriteList; + + typedef std::vector, bool> > responder_list_t; + responder_list_t mCompletedList; + + bool mReadOnly; + + // HEADERS (Include first mip) + std::string mHeaderEntriesFileName; + std::string mHeaderDataFileName; + std::string mFastCacheFileName; + EntriesInfo mHeaderEntriesInfo; + std::set mFreeList; // deleted entries + std::set mLRU; + typedef std::map id_map_t; + id_map_t mHeaderIDMap; + + LLAPRFile* mFastCachep; + LLFrameTimer mFastCacheTimer; + U8* mFastCachePadBuffer; + + // BODIES (TEXTURES minus headers) + std::string mTexturesDirName; + typedef std::map size_map_t; + size_map_t mTexturesSizeMap; + S64 mTexturesSizeTotal; + LLAtomicBool mDoPurge; + + typedef std::map idx_entry_map_t; + idx_entry_map_t mUpdatedEntryMap; + typedef std::vector > idx_entry_vector_t; + idx_entry_vector_t mPurgeEntryList; + + // Statics + static F32 sHeaderCacheVersion; + static U32 sHeaderCacheAddressSize; + static std::string sHeaderCacheEncoderVersion; + static U32 sCacheMaxEntries; + static S64 sCacheMaxTexturesSize; +}; + +extern const S32 TEXTURE_CACHE_ENTRY_SIZE; + +#endif // LL_LLTEXTURECACHE_H diff --git a/indra/newview/lltexturectrl.cpp b/indra/newview/lltexturectrl.cpp index 406c49f325..cb216056de 100644 --- a/indra/newview/lltexturectrl.cpp +++ b/indra/newview/lltexturectrl.cpp @@ -1,2372 +1,2372 @@ -/** - * @file lltexturectrl.cpp - * @author Richard Nelson, James Cook - * @brief LLTextureCtrl class implementation including related functions - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lltexturectrl.h" - -#include "llrender.h" -#include "llagent.h" -#include "llviewertexturelist.h" -#include "llselectmgr.h" -#include "llcheckboxctrl.h" -#include "llcombobox.h" -#include "llbutton.h" -#include "lldraghandle.h" -#include "llfocusmgr.h" -#include "llfolderviewmodel.h" -#include "llinventory.h" -#include "llinventoryfunctions.h" -#include "llinventoryicon.h" -#include "llinventorymodelbackgroundfetch.h" -#include "llinventoryobserver.h" -#include "llinventorypanel.h" -#include "lllineeditor.h" -#include "llmaterialeditor.h" -#include "llui.h" -#include "llviewerinventory.h" -#include "llviewermenufile.h" // LLFilePickerReplyThread -#include "llpermissions.h" -#include "llpreviewtexture.h" -#include "llsaleinfo.h" -#include "llassetstorage.h" -#include "lltextbox.h" -#include "llresizehandle.h" -#include "llscrollcontainer.h" -#include "lltoolmgr.h" -#include "lltoolpipette.h" -#include "llfiltereditor.h" -#include "llwindow.h" - -#include "lltool.h" -#include "llviewerwindow.h" -#include "llviewerobject.h" -#include "llviewercontrol.h" -#include "llglheaders.h" -#include "lluictrlfactory.h" -#include "lltrans.h" - -#include "llradiogroup.h" -#include "llfloaterreg.h" -#include "lllocalbitmaps.h" -#include "lllocalgltfmaterials.h" -#include "llerror.h" - -#include "llavatarappearancedefines.h" - - -//static -bool get_is_predefined_texture(LLUUID asset_id) -{ - if (asset_id == DEFAULT_OBJECT_TEXTURE - || asset_id == DEFAULT_OBJECT_SPECULAR - || asset_id == DEFAULT_OBJECT_NORMAL - || asset_id == BLANK_OBJECT_NORMAL - || asset_id == IMG_WHITE - || asset_id == LLUUID(SCULPT_DEFAULT_TEXTURE)) - { - return true; - } - return false; -} - -LLUUID get_copy_free_item_by_asset_id(LLUUID asset_id, bool no_trans_perm) -{ - LLViewerInventoryCategory::cat_array_t cats; - LLViewerInventoryItem::item_array_t items; - LLAssetIDMatches asset_id_matches(asset_id); - gInventory.collectDescendentsIf(LLUUID::null, - cats, - items, - LLInventoryModel::INCLUDE_TRASH, - asset_id_matches); - - LLUUID res; - if (items.size()) - { - for (S32 i = 0; i < items.size(); i++) - { - LLViewerInventoryItem* itemp = items[i]; - if (itemp) - { - LLPermissions item_permissions = itemp->getPermissions(); - if (item_permissions.allowOperationBy(PERM_COPY, - gAgent.getID(), - gAgent.getGroupID())) - { - bool allow_trans = item_permissions.allowOperationBy(PERM_TRANSFER, gAgent.getID(), gAgent.getGroupID()); - if (allow_trans != no_trans_perm) - { - return itemp->getUUID(); - } - res = itemp->getUUID(); - } - } - } - } - return res; -} - -bool get_can_copy_texture(LLUUID asset_id) -{ - // User is allowed to copy a texture if: - // library asset or default texture, - // or copy perm asset exists in user's inventory - - return get_is_predefined_texture(asset_id) || get_copy_free_item_by_asset_id(asset_id).notNull(); -} - -S32 LLFloaterTexturePicker::sLastPickerMode = 0; - -LLFloaterTexturePicker::LLFloaterTexturePicker( - LLView* owner, - LLUUID image_asset_id, - LLUUID default_image_asset_id, - LLUUID blank_image_asset_id, - bool tentative, - bool allow_no_texture, - const std::string& label, - PermissionMask immediate_filter_perm_mask, - PermissionMask dnd_filter_perm_mask, - bool can_apply_immediately, - LLUIImagePtr fallback_image, - EPickInventoryType pick_type) -: LLFloater(LLSD()), - mOwner( owner ), - mImageAssetID( image_asset_id ), - mOriginalImageAssetID(image_asset_id), - mFallbackImage(fallback_image), - mDefaultImageAssetID(default_image_asset_id), - mBlankImageAssetID(blank_image_asset_id), - mTentative(tentative), - mAllowNoTexture(allow_no_texture), - mLabel(label), - mTentativeLabel(NULL), - mResolutionLabel(NULL), - mActive( true ), - mFilterEdit(NULL), - mImmediateFilterPermMask(immediate_filter_perm_mask), - mDnDFilterPermMask(dnd_filter_perm_mask), - mContextConeOpacity(0.f), - mSelectedItemPinned( false ), - mCanApply(true), - mCanPreview(true), - mLimitsSet(false), - mMaxDim(S32_MAX), - mMinDim(0), - mPreviewSettingChanged(false), - mOnFloaterCommitCallback(NULL), - mOnFloaterCloseCallback(NULL), - mSetImageAssetIDCallback(NULL), - mOnUpdateImageStatsCallback(NULL), - mBakeTextureEnabled(false), - mInventoryPickType(pick_type) -{ - mCanApplyImmediately = can_apply_immediately; - buildFromFile("floater_texture_ctrl.xml"); - setCanMinimize(false); -} - -LLFloaterTexturePicker::~LLFloaterTexturePicker() -{ -} - -void LLFloaterTexturePicker::setImageID(const LLUUID& image_id, bool set_selection /*=true*/) -{ - if( ((mImageAssetID != image_id) || mTentative) && mActive) - { - mNoCopyTextureSelected = false; - mViewModel->setDirty(); // *TODO: shouldn't we be using setValue() here? - mImageAssetID = image_id; - - if (LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(mImageAssetID)) - { - if ( mBakeTextureEnabled && mModeSelector->getValue().asInteger() != 2) - { - mModeSelector->selectByValue(2); - onModeSelect(0,this); - } - } - else - { - if (mModeSelector->getValue().asInteger() == 2) - { - mModeSelector->selectByValue(0); - onModeSelect(0,this); - } - - LLUUID item_id; - LLFolderView* root_folder = mInventoryPanel->getRootFolder(); - if (root_folder && root_folder->getCurSelectedItem()) - { - LLFolderViewItem* last_selected = root_folder->getCurSelectedItem(); - LLFolderViewModelItemInventory* inv_view = static_cast(last_selected->getViewModelItem()); - - LLInventoryItem* itemp = gInventory.getItem(inv_view->getUUID()); - - if (mInventoryPickType == PICK_MATERIAL - && mImageAssetID == LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID - && itemp && itemp->getAssetUUID().isNull()) - { - item_id = inv_view->getUUID(); - } - else if (itemp && itemp->getAssetUUID() == mImageAssetID) - { - item_id = inv_view->getUUID(); - } - } - if (item_id.isNull()) - { - item_id = findItemID(mImageAssetID, false); - } - if (item_id.isNull()) - { - mInventoryPanel->getRootFolder()->clearSelection(); - } - else - { - LLInventoryItem* itemp = gInventory.getItem(item_id); - if (itemp && !itemp->getPermissions().allowCopyBy(gAgent.getID())) - { - // no copy texture - getChild("apply_immediate_check")->setValue(false); - mNoCopyTextureSelected = true; - } - } - - if (set_selection) - { - mInventoryPanel->setSelection(item_id, TAKE_FOCUS_NO); - } - } - } -} - -void LLFloaterTexturePicker::setImageIDFromItem(const LLInventoryItem* itemp, bool set_selection) -{ - LLUUID asset_id = itemp->getAssetUUID(); - if (mInventoryPickType == PICK_MATERIAL && asset_id.isNull()) - { - // If an inventory item has a null asset, consider it a valid blank material(gltf) - asset_id = LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID; - } - setImageID(asset_id, set_selection); -} - -void LLFloaterTexturePicker::setActive( bool active ) -{ - if (!active && getChild("Pipette")->getValue().asBoolean()) - { - stopUsingPipette(); - } - mActive = active; -} - -void LLFloaterTexturePicker::setCanApplyImmediately(bool b) -{ - mCanApplyImmediately = b; - - LLUICtrl *apply_checkbox = getChild("apply_immediate_check"); - apply_checkbox->setValue(mCanApplyImmediately && gSavedSettings.getBOOL("TextureLivePreview")); - apply_checkbox->setEnabled(mCanApplyImmediately); -} - -void LLFloaterTexturePicker::stopUsingPipette() -{ - if (LLToolMgr::getInstance()->getCurrentTool() == LLToolPipette::getInstance()) - { - LLToolMgr::getInstance()->clearTransientTool(); - } -} - -bool LLFloaterTexturePicker::updateImageStats() -{ - bool result = true; - if (mGLTFMaterial.notNull()) - { - S32 width = 0; - S32 height = 0; - - bool has_texture = false; - - if (mGLTFMaterial->mBaseColorTexture) - { - width = llmax(width, mGLTFMaterial->mBaseColorTexture->getFullWidth()); - height = llmax(height, mGLTFMaterial->mBaseColorTexture->getFullHeight()); - has_texture = true; - } - if (mGLTFMaterial->mNormalTexture) - { - width = llmax(width, mGLTFMaterial->mNormalTexture->getFullWidth()); - height = llmax(height, mGLTFMaterial->mNormalTexture->getFullHeight()); - has_texture = true; - } - if (mGLTFMaterial->mMetallicRoughnessTexture) - { - width = llmax(width, mGLTFMaterial->mMetallicRoughnessTexture->getFullWidth()); - height = llmax(height, mGLTFMaterial->mMetallicRoughnessTexture->getFullHeight()); - has_texture = true; - } - if (mGLTFMaterial->mEmissiveTexture) - { - width = llmax(width, mGLTFMaterial->mEmissiveTexture->getFullWidth()); - height = llmax(height, mGLTFMaterial->mEmissiveTexture->getFullHeight()); - has_texture = true; - } - - if (width > 0 && height > 0) - { - std::string formatted_dims = llformat("%d x %d", width, height); - mResolutionLabel->setTextArg("[DIMENSIONS]", formatted_dims); - if (mOnUpdateImageStatsCallback) - { - mOnUpdateImageStatsCallback(mTexturep); - } - } - else if (has_texture) - { - // unknown resolution - mResolutionLabel->setTextArg("[DIMENSIONS]", std::string("[? x ?]")); - } - else - { - // No textures - no applicable resolution (may be show some max value instead?) - mResolutionLabel->setTextArg("[DIMENSIONS]", std::string("")); - } - } - else if (mTexturep.notNull()) - { - //RN: have we received header data for this image? - S32 width = mTexturep->getFullWidth(); - S32 height = mTexturep->getFullHeight(); - if (width > 0 && height > 0) - { - if ((mLimitsSet && (width != height)) - || width < mMinDim - || width > mMaxDim - || height < mMinDim - || height > mMaxDim - ) - { - std::string formatted_dims = llformat("%dx%d", width, height); - mResolutionWarning->setTextArg("[TEXDIM]", formatted_dims); - result = false; - } - else - { - std::string formatted_dims = llformat("%d x %d", width, height); - mResolutionLabel->setTextArg("[DIMENSIONS]", formatted_dims); - } - - if (mOnUpdateImageStatsCallback) - { - mOnUpdateImageStatsCallback(mTexturep); - } - } - else - { - mResolutionLabel->setTextArg("[DIMENSIONS]", std::string("[? x ?]")); - } - } - else - { - mResolutionLabel->setTextArg("[DIMENSIONS]", std::string("")); - } - mResolutionLabel->setVisible(result); - mResolutionWarning->setVisible(!result); - - // Hide buttons and pipete to make space for mResolutionWarning - // Hiding buttons is suboptimal, but at the moment limited to inventory thumbnails - // may be this should be an info/warning icon with a tooltip? - S32 index = mModeSelector->getValue().asInteger(); - if (index == 0) - { - mDefaultBtn->setVisible(result); - mNoneBtn->setVisible(result); - mBlankBtn->setVisible(result); - mPipetteBtn->setVisible(result); - } - return result; -} - -// virtual -bool LLFloaterTexturePicker::handleDragAndDrop( - S32 x, S32 y, MASK mask, - bool drop, - EDragAndDropType cargo_type, void *cargo_data, - EAcceptance *accept, - std::string& tooltip_msg) -{ - bool handled = false; - - bool is_mesh = cargo_type == DAD_MESH; - bool is_texture = cargo_type == DAD_TEXTURE; - bool is_material = cargo_type == DAD_MATERIAL; - - bool allow_dnd = false; - if (mInventoryPickType == PICK_MATERIAL) - { - allow_dnd = is_material; - } - else if (mInventoryPickType == PICK_TEXTURE) - { - allow_dnd = is_texture || is_mesh; - } - else - { - allow_dnd = is_texture || is_mesh || is_material; - } - - if (allow_dnd) - { - LLInventoryItem *item = (LLInventoryItem *)cargo_data; - - bool copy = item->getPermissions().allowCopyBy(gAgent.getID()); - bool mod = item->getPermissions().allowModifyBy(gAgent.getID()); - bool xfer = item->getPermissions().allowOperationBy(PERM_TRANSFER, - gAgent.getID()); - - PermissionMask item_perm_mask = 0; - if (copy) item_perm_mask |= PERM_COPY; - if (mod) item_perm_mask |= PERM_MODIFY; - if (xfer) item_perm_mask |= PERM_TRANSFER; - - PermissionMask filter_perm_mask = mDnDFilterPermMask; - if ( (item_perm_mask & filter_perm_mask) == filter_perm_mask ) - { - if (drop) - { - setImageIDFromItem(item); - commitIfImmediateSet(); - } - - *accept = ACCEPT_YES_SINGLE; - } - else - { - *accept = ACCEPT_NO; - } - } - else - { - *accept = ACCEPT_NO; - } - - handled = true; - LL_DEBUGS("UserInput") << "dragAndDrop handled by LLFloaterTexturePicker " << getName() << LL_ENDL; - - return handled; -} - -bool LLFloaterTexturePicker::handleKeyHere(KEY key, MASK mask) -{ - LLFolderView* root_folder = mInventoryPanel->getRootFolder(); - - if (root_folder && mFilterEdit) - { - if (mFilterEdit->hasFocus() - && (key == KEY_RETURN || key == KEY_DOWN) - && mask == MASK_NONE) - { - if (!root_folder->getCurSelectedItem()) - { - LLFolderViewItem* itemp = mInventoryPanel->getItemByID(gInventory.getRootFolderID()); - if (itemp) - { - root_folder->setSelection(itemp, false, false); - } - } - root_folder->scrollToShowSelection(); - - // move focus to inventory proper - mInventoryPanel->setFocus(true); - - // treat this as a user selection of the first filtered result - commitIfImmediateSet(); - - return true; - } - - if (mInventoryPanel->hasFocus() && key == KEY_UP) - { - mFilterEdit->focusFirstItem(true); - } - } - - return LLFloater::handleKeyHere(key, mask); -} - -void LLFloaterTexturePicker::onOpen(const LLSD& key) -{ - if (sLastPickerMode != 0 - && mModeSelector->selectByValue(sLastPickerMode)) - { - changeMode(); - } -} - -void LLFloaterTexturePicker::onClose(bool app_quitting) -{ - if (mOwner && mOnFloaterCloseCallback) - { - mOnFloaterCloseCallback(); - } - stopUsingPipette(); - sLastPickerMode = mModeSelector->getValue().asInteger(); -} - -// virtual -bool LLFloaterTexturePicker::postBuild() -{ - LLFloater::postBuild(); - - if (!mLabel.empty()) - { - std::string pick = getString("pick title"); - - setTitle(pick + mLabel); - } - mTentativeLabel = getChild("Multiple"); - - mResolutionLabel = getChild("size_lbl"); - mResolutionWarning = getChild("over_limit_lbl"); - - - mDefaultBtn = getChild("Default"); - mNoneBtn = getChild("None"); - mBlankBtn = getChild("Blank"); - mPipetteBtn = getChild("Pipette"); - mSelectBtn = getChild("Select"); - mCancelBtn = getChild("Cancel"); - - mDefaultBtn->setClickedCallback(boost::bind(LLFloaterTexturePicker::onBtnSetToDefault,this)); - mNoneBtn->setClickedCallback(boost::bind(LLFloaterTexturePicker::onBtnNone, this)); - mBlankBtn->setClickedCallback(boost::bind(LLFloaterTexturePicker::onBtnBlank, this)); - mPipetteBtn->setCommitCallback(boost::bind(&LLFloaterTexturePicker::onBtnPipette, this)); - mSelectBtn->setClickedCallback(boost::bind(LLFloaterTexturePicker::onBtnSelect, this)); - mCancelBtn->setClickedCallback(boost::bind(LLFloaterTexturePicker::onBtnCancel, this)); - - mFilterEdit = getChild("inventory search editor"); - mFilterEdit->setCommitCallback(boost::bind(&LLFloaterTexturePicker::onFilterEdit, this, _2)); - - mInventoryPanel = getChild("inventory panel"); - - mModeSelector = getChild("mode_selection"); - mModeSelector->setCommitCallback(onModeSelect, this); - mModeSelector->selectByValue(0); - - if(mInventoryPanel) - { - // to avoid having to make an assumption about which option is - // selected at startup, we call the same function that is triggered - // when a texture/materials/both choice is made and let it take care - // of setting the filters - refreshInventoryFilter(); - - mInventoryPanel->setFilterPermMask(mImmediateFilterPermMask); - mInventoryPanel->setSelectCallback(boost::bind(&LLFloaterTexturePicker::onSelectionChange, this, _1, _2)); - mInventoryPanel->setShowFolderState(LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); - - // Disable auto selecting first filtered item because it takes away - // selection from the item set by LLTextureCtrl owning this floater. - mInventoryPanel->getRootFolder()->setAutoSelectOverride(true); - - // Commented out to scroll to currently selected texture. See EXT-5403. - // // store this filter as the default one - // mInventoryPanel->getRootFolder()->getFilter().markDefault(); - - // Commented out to stop opening all folders with textures - // mInventoryPanel->openDefaultFolderForType(LLFolderType::FT_TEXTURE); - - // don't put keyboard focus on selected item, because the selection callback - // will assume that this was user input - - if(!mImageAssetID.isNull() || mInventoryPickType == PICK_MATERIAL) - { - mInventoryPanel->setSelection(findItemID(mImageAssetID, false), TAKE_FOCUS_NO); - } - } - - childSetAction("l_add_btn", LLFloaterTexturePicker::onBtnAdd, this); - childSetAction("l_rem_btn", LLFloaterTexturePicker::onBtnRemove, this); - childSetAction("l_upl_btn", LLFloaterTexturePicker::onBtnUpload, this); - - mLocalScrollCtrl = getChild("l_name_list"); - mLocalScrollCtrl->setCommitCallback(onLocalScrollCommit, this); - refreshLocalList(); - - mNoCopyTextureSelected = false; - - getChild("apply_immediate_check")->setValue(mCanApplyImmediately && gSavedSettings.getBOOL("TextureLivePreview")); - childSetCommitCallback("apply_immediate_check", onApplyImmediateCheck, this); - getChildView("apply_immediate_check")->setEnabled(mCanApplyImmediately); - - getChild("Pipette")->setCommitCallback( boost::bind(&LLFloaterTexturePicker::onBtnPipette, this)); - childSetAction("Cancel", LLFloaterTexturePicker::onBtnCancel,this); - childSetAction("Select", LLFloaterTexturePicker::onBtnSelect,this); - - mSavedFolderState.setApply(false); - - LLToolPipette::getInstance()->setToolSelectCallback(boost::bind(&LLFloaterTexturePicker::onTextureSelect, this, _1)); - - getChild("l_bake_use_texture_combo_box")->setCommitCallback(onBakeTextureSelect, this); - - setBakeTextureEnabled(true); - return true; -} - -// virtual -void LLFloaterTexturePicker::draw() -{ - static LLCachedControl max_opacity(gSavedSettings, "PickerContextOpacity", 0.4f); - drawConeToOwner(mContextConeOpacity, max_opacity, mOwner); - - // This is going to spam mOnUpdateImageStatsCallback, - // either move elsewhere or fix to cause update once per image - bool valid_dims = updateImageStats(); - - // if we're inactive, gray out "apply immediate" checkbox - getChildView("show_folders_check")->setEnabled(mActive && mCanApplyImmediately && !mNoCopyTextureSelected); - mSelectBtn->setEnabled(mActive && mCanApply && valid_dims); - mPipetteBtn->setEnabled(mActive); - mPipetteBtn->setValue(LLToolMgr::getInstance()->getCurrentTool() == LLToolPipette::getInstance()); - - //bool allow_copy = false; - if( mOwner ) - { - mTexturep = NULL; - mGLTFMaterial = NULL; - if (mImageAssetID.notNull()) - { - if (mInventoryPickType == PICK_MATERIAL) - { - mGLTFMaterial = (LLFetchedGLTFMaterial*) gGLTFMaterialList.getMaterial(mImageAssetID); - llassert(mGLTFMaterial == nullptr || dynamic_cast(gGLTFMaterialList.getMaterial(mImageAssetID)) != nullptr); - } - else - { - LLPointer texture = NULL; - - if (LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(mImageAssetID)) - { - // TODO: Fix this! Picker is not warrantied to be connected to a selection - // LLSelectMgr shouldn't be used in texture picker - LLViewerObject* obj = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); - if (obj) - { - LLViewerTexture* viewerTexture = obj->getBakedTextureForMagicId(mImageAssetID); - texture = viewerTexture ? dynamic_cast(viewerTexture) : NULL; - } - } - - if (texture.isNull()) - { - texture = LLViewerTextureManager::getFetchedTexture(mImageAssetID); - } - - mTexturep = texture; - mTexturep->setBoostLevel(LLGLTexture::BOOST_PREVIEW); - } - } - - if (mTentativeLabel) - { - mTentativeLabel->setVisible( false ); - } - - mDefaultBtn->setEnabled(mImageAssetID != mDefaultImageAssetID || mTentative); - mBlankBtn->setEnabled((mImageAssetID != mBlankImageAssetID && mBlankImageAssetID.notNull()) || mTentative); - mNoneBtn->setEnabled(mAllowNoTexture && (!mImageAssetID.isNull() || mTentative)); - - LLFloater::draw(); - - if( isMinimized() ) - { - return; - } - - // Border - LLRect border = getChildView("preview_widget")->getRect(); - gl_rect_2d( border, LLColor4::black, false ); - - - // Interior - LLRect interior = border; - interior.stretch( -1 ); - - // If the floater is focused, don't apply its alpha to the texture (STORM-677). - const F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); - LLViewerTexture* texture = nullptr; - if (mGLTFMaterial) - { - texture = mGLTFMaterial->getUITexture(); - } - else - { - texture = mTexturep.get(); - } - - if( texture ) - { - if( texture->getComponents() == 4 ) - { - gl_rect_2d_checkerboard( interior, alpha ); - } - - gl_draw_scaled_image( interior.mLeft, interior.mBottom, interior.getWidth(), interior.getHeight(), texture, UI_VERTEX_COLOR % alpha ); - - // Pump the priority - texture->addTextureStats( (F32)(interior.getWidth() * interior.getHeight()) ); - } - else if (!mFallbackImage.isNull()) - { - mFallbackImage->draw(interior, UI_VERTEX_COLOR % alpha); - } - else - { - gl_rect_2d( interior, LLColor4::grey % alpha, true ); - - // Draw X - gl_draw_x(interior, LLColor4::black ); - } - - // Draw Tentative Label over the image - if( mTentative && !mViewModel->isDirty() ) - { - mTentativeLabel->setVisible( true ); - drawChild(mTentativeLabel); - } - - if (mSelectedItemPinned) return; - - LLFolderView* folder_view = mInventoryPanel->getRootFolder(); - if (!folder_view) return; - - LLFolderViewFilter& filter = static_cast(folder_view->getFolderViewModel())->getFilter(); - - bool is_filter_active = folder_view->getViewModelItem()->getLastFilterGeneration() < filter.getCurrentGeneration() && - filter.isNotDefault(); - - // After inventory panel filter is applied we have to update - // constraint rect for the selected item because of folder view - // AutoSelectOverride set to true. We force PinningSelectedItem - // flag to false state and setting filter "dirty" to update - // scroll container to show selected item (see LLFolderView::doIdle()). - if (!is_filter_active && !mSelectedItemPinned) - { - folder_view->setPinningSelectedItem(mSelectedItemPinned); - folder_view->getViewModelItem()->dirtyFilter(); - mSelectedItemPinned = true; - } - } -} - -const LLUUID& LLFloaterTexturePicker::findItemID(const LLUUID& asset_id, bool copyable_only, bool ignore_library) -{ - if (asset_id.isNull()) - { - // null asset id means, no material or texture assigned - return LLUUID::null; - } - - LLUUID loockup_id = asset_id; - if (mInventoryPickType == PICK_MATERIAL && loockup_id == LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID) - { - // default asset id means we are looking for an inventory item with a default asset UUID (null) - loockup_id = LLUUID::null; - } - - LLViewerInventoryCategory::cat_array_t cats; - LLViewerInventoryItem::item_array_t items; - - if (loockup_id.isNull()) - { - // looking for a material with a null id, null id is shared by a lot - // of objects as a default value, so have to filter by type as well - LLAssetIDAndTypeMatches matches(loockup_id, LLAssetType::AT_MATERIAL); - gInventory.collectDescendentsIf(LLUUID::null, - cats, - items, - LLInventoryModel::INCLUDE_TRASH, - matches); - } - else - { - LLAssetIDMatches asset_id_matches(loockup_id); - gInventory.collectDescendentsIf(LLUUID::null, - cats, - items, - LLInventoryModel::INCLUDE_TRASH, - asset_id_matches); - } - - - if (items.size()) - { - // search for copyable version first - for (S32 i = 0; i < items.size(); i++) - { - LLInventoryItem* itemp = items[i]; - LLPermissions item_permissions = itemp->getPermissions(); - if (item_permissions.allowCopyBy(gAgent.getID(), gAgent.getGroupID())) - { - if(!ignore_library || !gInventory.isObjectDescendentOf(itemp->getUUID(),gInventory.getLibraryRootFolderID())) - { - return itemp->getUUID(); - } - } - } - // otherwise just return first instance, unless copyable requested - if (copyable_only) - { - return LLUUID::null; - } - else - { - if(!ignore_library || !gInventory.isObjectDescendentOf(items[0]->getUUID(),gInventory.getLibraryRootFolderID())) - { - return items[0]->getUUID(); - } - } - } - - return LLUUID::null; -} - -void LLFloaterTexturePicker::commitIfImmediateSet() -{ - if (!mNoCopyTextureSelected && mCanApply) - { - commitCallback(LLTextureCtrl::TEXTURE_CHANGE); - } -} - -void LLFloaterTexturePicker::commitCallback(LLTextureCtrl::ETexturePickOp op) -{ - if (!mOnFloaterCommitCallback) - { - return; - } - LLUUID asset_id = mImageAssetID; - LLUUID inventory_id; - LLUUID tracking_id; - LLPickerSource mode = (LLPickerSource)mModeSelector->getValue().asInteger(); - - switch (mode) - { - case PICKER_INVENTORY: - { - LLFolderView* root_folder = mInventoryPanel->getRootFolder(); - if (root_folder && root_folder->getCurSelectedItem()) - { - LLFolderViewItem* last_selected = root_folder->getCurSelectedItem(); - LLFolderViewModelItemInventory* inv_view = static_cast(last_selected->getViewModelItem()); - - LLInventoryItem* itemp = gInventory.getItem(inv_view->getUUID()); - - if (mInventoryPickType == PICK_MATERIAL - && mImageAssetID == LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID - && itemp && itemp->getAssetUUID().isNull()) - { - inventory_id = inv_view->getUUID(); - } - else if (itemp && itemp->getAssetUUID() == mImageAssetID) - { - inventory_id = inv_view->getUUID(); - } - else - { - mode = PICKER_UNKNOWN; // source of id unknown - } - } - else - { - mode = PICKER_UNKNOWN; // source of id unknown - } - break; - } - case PICKER_LOCAL: - { - if (!mLocalScrollCtrl->getAllSelected().empty()) - { - LLSD data = mLocalScrollCtrl->getFirstSelected()->getValue(); - tracking_id = data["id"]; - S32 asset_type = data["type"].asInteger(); - - if (LLAssetType::AT_MATERIAL == asset_type) - { - asset_id = LLLocalGLTFMaterialMgr::getInstance()->getWorldID(tracking_id); - } - else - { - asset_id = LLLocalBitmapMgr::getInstance()->getWorldID(tracking_id); - } - } - else - { - asset_id = mImageAssetID; - mode = PICKER_UNKNOWN; // source of id unknown - } - break; - } - case PICKER_BAKE: - break; - default: - mode = PICKER_UNKNOWN; // source of id unknown - break; - } - - mOnFloaterCommitCallback(op, mode, asset_id, inventory_id, tracking_id); -} -void LLFloaterTexturePicker::commitCancel() -{ - if (!mNoCopyTextureSelected && mOnFloaterCommitCallback && mCanApply) - { - mOnFloaterCommitCallback(LLTextureCtrl::TEXTURE_CANCEL, PICKER_UNKNOWN, mOriginalImageAssetID, LLUUID::null, LLUUID::null); - } -} - -// static -void LLFloaterTexturePicker::onBtnSetToDefault(void* userdata) -{ - LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; - self->setCanApply(true, true); - if (self->mOwner) - { - self->setImageID( self->getDefaultImageAssetID() ); - } - self->commitIfImmediateSet(); -} - -// static -void LLFloaterTexturePicker::onBtnBlank(void* userdata) -{ - LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; - self->setCanApply(true, true); - self->setImageID( self->getBlankImageAssetID() ); - self->commitIfImmediateSet(); -} - - -// static -void LLFloaterTexturePicker::onBtnNone(void* userdata) -{ - LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; - self->setCanApply(true, true); - self->setImageID( LLUUID::null ); - self->commitIfImmediateSet(); -} - -/* -// static -void LLFloaterTexturePicker::onBtnRevert(void* userdata) -{ - LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; - self->setImageID( self->mOriginalImageAssetID ); - // TODO: Change this to tell the owner to cancel. It needs to be - // smart enough to restore multi-texture selections. - self->mOwner->onFloaterCommit(); - self->mViewModel->resetDirty(); -}*/ - -// static -void LLFloaterTexturePicker::onBtnCancel(void* userdata) -{ - LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; - self->setImageID( self->mOriginalImageAssetID ); - if (self->mOnFloaterCommitCallback) - { - self->mOnFloaterCommitCallback(LLTextureCtrl::TEXTURE_CANCEL, PICKER_UNKNOWN, self->mOriginalImageAssetID, LLUUID::null, LLUUID::null); - } - self->mViewModel->resetDirty(); - self->closeFloater(); -} - -// static -void LLFloaterTexturePicker::onBtnSelect(void* userdata) -{ - LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; - if (self->mOnFloaterCommitCallback) - { - self->commitCallback(LLTextureCtrl::TEXTURE_SELECT); - } - self->closeFloater(); -} - -void LLFloaterTexturePicker::onBtnPipette() -{ - bool pipette_active = getChild("Pipette")->getValue().asBoolean(); - pipette_active = !pipette_active; - if (pipette_active) - { - LLToolMgr::getInstance()->setTransientTool(LLToolPipette::getInstance()); - } - else - { - LLToolMgr::getInstance()->clearTransientTool(); - } -} - -void LLFloaterTexturePicker::onSelectionChange(const std::deque &items, bool user_action) -{ - if (items.size()) - { - LLFolderViewItem* first_item = items.front(); - LLInventoryItem* itemp = gInventory.getItem(static_cast(first_item->getViewModelItem())->getUUID()); - mNoCopyTextureSelected = false; - if (itemp) - { - if (!mTextureSelectedCallback.empty()) - { - mTextureSelectedCallback(itemp); - } - if (!itemp->getPermissions().allowCopyBy(gAgent.getID())) - { - mNoCopyTextureSelected = true; - } - setImageIDFromItem(itemp, false); - mViewModel->setDirty(); // *TODO: shouldn't we be using setValue() here? - - if(!mPreviewSettingChanged) - { - mCanPreview = mCanApplyImmediately && gSavedSettings.getBOOL("TextureLivePreview"); - } - else - { - mPreviewSettingChanged = false; - } - - if (user_action && mCanPreview) - { - // only commit intentional selections, not implicit ones - commitIfImmediateSet(); - } - } - } -} - -// static -void LLFloaterTexturePicker::onModeSelect(LLUICtrl* ctrl, void *userdata) -{ - LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; - self->changeMode(); -} - -// static -void LLFloaterTexturePicker::onBtnAdd(void* userdata) -{ - LLFloaterTexturePicker* self = (LLFloaterTexturePicker*)userdata; - - if (self->mInventoryPickType == PICK_TEXTURE_MATERIAL) - { - LLFilePickerReplyThread::startPicker(boost::bind(&onPickerCallback, _1, self->getHandle()), LLFilePicker::FFLOAD_MATERIAL_TEXTURE, true); - } - else if (self->mInventoryPickType == PICK_TEXTURE) - { - LLFilePickerReplyThread::startPicker(boost::bind(&onPickerCallback, _1, self->getHandle()), LLFilePicker::FFLOAD_IMAGE, true); - } - else if (self->mInventoryPickType == PICK_MATERIAL) - { - LLFilePickerReplyThread::startPicker(boost::bind(&onPickerCallback, _1, self->getHandle()), LLFilePicker::FFLOAD_MATERIAL, true); - } -} - -// static -void LLFloaterTexturePicker::onBtnRemove(void* userdata) -{ - LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; - std::vector selected_items = self->mLocalScrollCtrl->getAllSelected(); - - if (!selected_items.empty()) - { - - for(std::vector::iterator iter = selected_items.begin(); - iter != selected_items.end(); iter++) - { - LLScrollListItem* list_item = *iter; - if (list_item) - { - LLSD data = list_item->getValue(); - LLUUID tracking_id = data["id"]; - S32 asset_type = data["type"].asInteger(); - - if (LLAssetType::AT_MATERIAL == asset_type) - { - LLLocalGLTFMaterialMgr::getInstance()->delUnit(tracking_id); - } - else - { - LLLocalBitmapMgr::getInstance()->delUnit(tracking_id); - } - } - } - - self->getChild("l_rem_btn")->setEnabled(false); - self->getChild("l_upl_btn")->setEnabled(false); - self->refreshLocalList(); - } -} - -// static -void LLFloaterTexturePicker::onBtnUpload(void* userdata) -{ - LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; - std::vector selected_items = self->mLocalScrollCtrl->getAllSelected(); - - if (selected_items.empty()) - { - return; - } - - /* currently only allows uploading one by one, picks the first item from the selection list. (not the vector!) - in the future, it might be a good idea to check the vector size and if more than one units is selected - opt for multi-image upload. */ - - LLSD data = self->mLocalScrollCtrl->getFirstSelected()->getValue(); - LLUUID tracking_id = data["id"]; - S32 asset_type = data["type"].asInteger(); - - if (LLAssetType::AT_MATERIAL == asset_type) - { - std::string filename; - S32 index; - LLLocalGLTFMaterialMgr::getInstance()->getFilenameAndIndex(tracking_id, filename, index); - if (!filename.empty()) - { - LLMaterialEditor::loadMaterialFromFile(filename, index); - } - } - else - { - std::string filename = LLLocalBitmapMgr::getInstance()->getFilename(tracking_id); - if (!filename.empty()) - { - LLFloaterReg::showInstance("upload_image", LLSD(filename)); - } - } -} - -//static -void LLFloaterTexturePicker::onLocalScrollCommit(LLUICtrl* ctrl, void* userdata) -{ - LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; - std::vector selected_items = self->mLocalScrollCtrl->getAllSelected(); - bool has_selection = !selected_items.empty(); - - self->getChild("l_rem_btn")->setEnabled(has_selection); - self->getChild("l_upl_btn")->setEnabled(has_selection && (selected_items.size() < 2)); - /* since multiple-localbitmap upload is not implemented, upl button gets disabled if more than one is selected. */ - - if (has_selection) - { - LLSD data = self->mLocalScrollCtrl->getFirstSelected()->getValue(); - LLUUID tracking_id = data["id"]; - S32 asset_type = data["type"].asInteger(); - LLUUID inworld_id; - - if (LLAssetType::AT_MATERIAL == asset_type) - { - inworld_id = LLLocalGLTFMaterialMgr::getInstance()->getWorldID(tracking_id); - } - else - { - inworld_id = LLLocalBitmapMgr::getInstance()->getWorldID(tracking_id); - } - - if (self->mSetImageAssetIDCallback) - { - self->mSetImageAssetIDCallback(inworld_id); - } - - if (self->childGetValue("apply_immediate_check").asBoolean()) - { - if (self->mOnFloaterCommitCallback) - { - self->mOnFloaterCommitCallback(LLTextureCtrl::TEXTURE_CHANGE, PICKER_LOCAL, inworld_id, LLUUID::null, tracking_id); - } - } - } -} - -// static -void LLFloaterTexturePicker::onApplyImmediateCheck(LLUICtrl* ctrl, void *user_data) -{ - LLFloaterTexturePicker* picker = (LLFloaterTexturePicker*)user_data; - - LLCheckBoxCtrl* check_box = (LLCheckBoxCtrl*)ctrl; - gSavedSettings.setBOOL("TextureLivePreview", check_box->get()); - - picker->commitIfImmediateSet(); -} - -//static -void LLFloaterTexturePicker::onBakeTextureSelect(LLUICtrl* ctrl, void *user_data) -{ - LLFloaterTexturePicker* self = (LLFloaterTexturePicker*)user_data; - LLComboBox* combo_box = (LLComboBox*)ctrl; - - S8 type = combo_box->getValue().asInteger(); - - LLUUID imageID = self->mDefaultImageAssetID; - if (type == 0) - { - imageID = IMG_USE_BAKED_HEAD; - } - else if (type == 1) - { - imageID = IMG_USE_BAKED_UPPER; - } - else if (type == 2) - { - imageID = IMG_USE_BAKED_LOWER; - } - else if (type == 3) - { - imageID = IMG_USE_BAKED_EYES; - } - else if (type == 4) - { - imageID = IMG_USE_BAKED_SKIRT; - } - else if (type == 5) - { - imageID = IMG_USE_BAKED_HAIR; - } - else if (type == 6) - { - imageID = IMG_USE_BAKED_LEFTARM; - } - else if (type == 7) - { - imageID = IMG_USE_BAKED_LEFTLEG; - } - else if (type == 8) - { - imageID = IMG_USE_BAKED_AUX1; - } - else if (type == 9) - { - imageID = IMG_USE_BAKED_AUX2; - } - else if (type == 10) - { - imageID = IMG_USE_BAKED_AUX3; - } - - self->setImageID(imageID); - self->mViewModel->setDirty(); // *TODO: shouldn't we be using setValue() here? - - if (!self->mPreviewSettingChanged) - { - self->mCanPreview = self->mCanApplyImmediately && gSavedSettings.getBOOL("TextureLivePreview"); - } - else - { - self->mPreviewSettingChanged = false; - } - - if (self->mCanPreview) - { - // only commit intentional selections, not implicit ones - self->commitIfImmediateSet(); - } -} - -void LLFloaterTexturePicker::setCanApply(bool can_preview, bool can_apply, bool inworld_image) -{ - mSelectBtn->setEnabled(can_apply); - getChildRef("preview_disabled").setVisible(!can_preview && inworld_image); - getChildRef("apply_immediate_check").setVisible(can_preview); - - mCanApply = can_apply; - mCanPreview = can_preview ? (mCanApplyImmediately && gSavedSettings.getBOOL("TextureLivePreview")) : false; - mPreviewSettingChanged = true; -} - -void LLFloaterTexturePicker::setMinDimentionsLimits(S32 min_dim) -{ - mMinDim = min_dim; - mLimitsSet = true; - - std::string formatted_dims = llformat("%dx%d", mMinDim, mMinDim); - mResolutionWarning->setTextArg("[MINTEXDIM]", formatted_dims); -} - -void LLFloaterTexturePicker::onFilterEdit(const std::string& search_string ) -{ - std::string upper_case_search_string = search_string; - LLStringUtil::toUpper(upper_case_search_string); - - if (upper_case_search_string.empty()) - { - if (mInventoryPanel->getFilterSubString().empty()) - { - // current filter and new filter empty, do nothing - return; - } - - mSavedFolderState.setApply(true); - mInventoryPanel->getRootFolder()->applyFunctorRecursively(mSavedFolderState); - // add folder with current item to list of previously opened folders - LLOpenFoldersWithSelection opener; - mInventoryPanel->getRootFolder()->applyFunctorRecursively(opener); - mInventoryPanel->getRootFolder()->scrollToShowSelection(); - - } - else if (mInventoryPanel->getFilterSubString().empty()) - { - // first letter in search term, save existing folder open state - if (!mInventoryPanel->getFilter().isNotDefault()) - { - mSavedFolderState.setApply(false); - mInventoryPanel->getRootFolder()->applyFunctorRecursively(mSavedFolderState); - } - } - - mInventoryPanel->setFilterSubString(search_string); -} - -void LLFloaterTexturePicker::changeMode() -{ - int index = mModeSelector->getValue().asInteger(); - - mDefaultBtn->setVisible(index == PICKER_INVENTORY); - mBlankBtn->setVisible(index == PICKER_INVENTORY); - mNoneBtn->setVisible(index == PICKER_INVENTORY); - mFilterEdit->setVisible(index == PICKER_INVENTORY); - mInventoryPanel->setVisible(index == PICKER_INVENTORY); - - getChild("l_add_btn")->setVisible(index == PICKER_LOCAL); - getChild("l_rem_btn")->setVisible(index == PICKER_LOCAL); - getChild("l_upl_btn")->setVisible(index == PICKER_LOCAL); - getChild("l_name_list")->setVisible(index == PICKER_LOCAL); - - getChild("l_bake_use_texture_combo_box")->setVisible(index == PICKER_BAKE); - getChild("hide_base_mesh_region")->setVisible(false);// index == 2); - - bool pipette_visible = (index == PICKER_INVENTORY) - && (mInventoryPickType != PICK_MATERIAL); - mPipetteBtn->setVisible(pipette_visible); - - if (index == PICKER_BAKE) - { - stopUsingPipette(); - - S8 val = -1; - - LLUUID imageID = mImageAssetID; - if (imageID == IMG_USE_BAKED_HEAD) - { - val = 0; - } - else if (imageID == IMG_USE_BAKED_UPPER) - { - val = 1; - } - else if (imageID == IMG_USE_BAKED_LOWER) - { - val = 2; - } - else if (imageID == IMG_USE_BAKED_EYES) - { - val = 3; - } - else if (imageID == IMG_USE_BAKED_SKIRT) - { - val = 4; - } - else if (imageID == IMG_USE_BAKED_HAIR) - { - val = 5; - } - else if (imageID == IMG_USE_BAKED_LEFTARM) - { - val = 6; - } - else if (imageID == IMG_USE_BAKED_LEFTLEG) - { - val = 7; - } - else if (imageID == IMG_USE_BAKED_AUX1) - { - val = 8; - } - else if (imageID == IMG_USE_BAKED_AUX2) - { - val = 9; - } - else if (imageID == IMG_USE_BAKED_AUX3) - { - val = 10; - } - - getChild("l_bake_use_texture_combo_box")->setSelectedByValue(val, true); - } -} - -void LLFloaterTexturePicker::refreshLocalList() -{ - mLocalScrollCtrl->clearRows(); - - if (mInventoryPickType == PICK_TEXTURE_MATERIAL) - { - LLLocalBitmapMgr::getInstance()->feedScrollList(mLocalScrollCtrl); - LLLocalGLTFMaterialMgr::getInstance()->feedScrollList(mLocalScrollCtrl); - } - else if (mInventoryPickType == PICK_TEXTURE) - { - LLLocalBitmapMgr::getInstance()->feedScrollList(mLocalScrollCtrl); - } - else if (mInventoryPickType == PICK_MATERIAL) - { - LLLocalGLTFMaterialMgr::getInstance()->feedScrollList(mLocalScrollCtrl); - } -} - -void LLFloaterTexturePicker::refreshInventoryFilter() -{ - U32 filter_types = 0x0; - - if (mInventoryPickType == PICK_TEXTURE_MATERIAL) - { - filter_types |= 0x1 << LLInventoryType::IT_TEXTURE; - filter_types |= 0x1 << LLInventoryType::IT_SNAPSHOT; - filter_types |= 0x1 << LLInventoryType::IT_MATERIAL; - } - else if (mInventoryPickType == PICK_TEXTURE) - { - filter_types |= 0x1 << LLInventoryType::IT_TEXTURE; - filter_types |= 0x1 << LLInventoryType::IT_SNAPSHOT; - } - else if (mInventoryPickType == PICK_MATERIAL) - { - filter_types |= 0x1 << LLInventoryType::IT_MATERIAL; - } - - mInventoryPanel->setFilterTypes(filter_types); -} - -void LLFloaterTexturePicker::setLocalTextureEnabled(bool enabled) -{ - mModeSelector->setEnabledByValue(1, enabled); -} - -void LLFloaterTexturePicker::setBakeTextureEnabled(bool enabled) -{ - bool changed = (enabled != mBakeTextureEnabled); - - mBakeTextureEnabled = enabled; - mModeSelector->setEnabledByValue(2, enabled); - - if (!mBakeTextureEnabled && (mModeSelector->getValue().asInteger() == 2)) - { - mModeSelector->selectByValue(0); - } - - if (changed && mBakeTextureEnabled && LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(mImageAssetID)) - { - if (mModeSelector->getValue().asInteger() != 2) - { - mModeSelector->selectByValue(2); - } - } - onModeSelect(0, this); -} - -void LLFloaterTexturePicker::setInventoryPickType(EPickInventoryType type) -{ - mInventoryPickType = type; - refreshLocalList(); - refreshInventoryFilter(); - - if (mInventoryPickType == PICK_MATERIAL) - { - getChild("Pipette")->setVisible(false); - } - else - { - S32 index = mModeSelector->getValue().asInteger(); - getChild("Pipette")->setVisible(index == 0); - } - - if (!mLabel.empty()) - { - std::string pick = getString("pick title"); - - setTitle(pick + mLabel); - } - else if(mInventoryPickType == PICK_MATERIAL) - { - setTitle(getString("pick_material")); - } - else - { - setTitle(getString("pick_texture")); - } - - // refresh selection - if (!mImageAssetID.isNull() || mInventoryPickType == PICK_MATERIAL) - { - mInventoryPanel->setSelection(findItemID(mImageAssetID, false), TAKE_FOCUS_NO); - } -} - -void LLFloaterTexturePicker::setImmediateFilterPermMask(PermissionMask mask) -{ - mImmediateFilterPermMask = mask; - mInventoryPanel->setFilterPermMask(mask); -} - -void LLFloaterTexturePicker::onPickerCallback(const std::vector& filenames, LLHandle handle) -{ - std::vector::const_iterator iter = filenames.begin(); - while (iter != filenames.end()) - { - if (!iter->empty()) - { - std::string temp_exten = gDirUtilp->getExtension(*iter); - if (temp_exten == "gltf" || temp_exten == "glb") - { - LLLocalGLTFMaterialMgr::getInstance()->addUnit(*iter); - } - else - { - LLLocalBitmapMgr::getInstance()->addUnit(*iter); - } - } - iter++; - } - - // Todo: this should referesh all pickers, not just a current one - if (!handle.isDead()) - { - LLFloaterTexturePicker* self = (LLFloaterTexturePicker*)handle.get(); - self->mLocalScrollCtrl->clearRows(); - - if (self->mInventoryPickType == PICK_TEXTURE_MATERIAL) - { - LLLocalBitmapMgr::getInstance()->feedScrollList(self->mLocalScrollCtrl); - LLLocalGLTFMaterialMgr::getInstance()->feedScrollList(self->mLocalScrollCtrl); - } - else if (self->mInventoryPickType == PICK_TEXTURE) - { - LLLocalBitmapMgr::getInstance()->feedScrollList(self->mLocalScrollCtrl); - } - else if (self->mInventoryPickType == PICK_MATERIAL) - { - LLLocalGLTFMaterialMgr::getInstance()->feedScrollList(self->mLocalScrollCtrl); - } - } -} - -void LLFloaterTexturePicker::onTextureSelect( const LLTextureEntry& te ) -{ - LLUUID inventory_item_id = findItemID(te.getID(), true); - if (inventory_item_id.notNull()) - { - LLToolPipette::getInstance()->setResult(true, ""); - if (mInventoryPickType == PICK_MATERIAL) - { - // tes have no data about material ids - // Plus gltf materials are layered with overrides, - // which mean that end result might have no id. - LL_WARNS() << "tes have no data about material ids" << LL_ENDL; - } - else - { - setImageID(te.getID()); - } - - mNoCopyTextureSelected = false; - LLInventoryItem* itemp = gInventory.getItem(inventory_item_id); - - if (itemp && !itemp->getPermissions().allowCopyBy(gAgent.getID())) - { - // no copy texture - mNoCopyTextureSelected = true; - } - - commitIfImmediateSet(); - } - else - { - LLToolPipette::getInstance()->setResult(false, LLTrans::getString("InventoryNoTexture")); - } -} - -/////////////////////////////////////////////////////////////////////// -// LLTextureCtrl - -static LLDefaultChildRegistry::Register r("texture_picker"); - -LLTextureCtrl::LLTextureCtrl(const LLTextureCtrl::Params& p) -: LLUICtrl(p), - mDragCallback(NULL), - mDropCallback(NULL), - mOnCancelCallback(NULL), - mOnCloseCallback(NULL), - mOnSelectCallback(NULL), - mBorderColor( p.border_color() ), - mAllowNoTexture( p.allow_no_texture ), - mAllowLocalTexture( true ), - mImmediateFilterPermMask( PERM_NONE ), - mCanApplyImmediately( false ), - mNeedsRawImageData( false ), - mValid( true ), - mShowLoadingPlaceholder( true ), - mOpenTexPreview(false), - mBakeTextureEnabled(true), - mInventoryPickType(PICK_TEXTURE), - mImageAssetID(p.image_id), - mDefaultImageAssetID(p.default_image_id), - mDefaultImageName(p.default_image_name), - mFallbackImage(p.fallback_image) -{ - - // Default of defaults is white image for diff tex - // - setBlankImageAssetID(IMG_WHITE); - - setAllowNoTexture(p.allow_no_texture); - setCanApplyImmediately(p.can_apply_immediately); - mCommitOnSelection = !p.no_commit_on_selection; - - LLTextBox::Params params(p.caption_text); - params.name(p.label); - params.rect(LLRect( 0, BTN_HEIGHT_SMALL, getRect().getWidth(), 0 )); - params.initial_value(p.label()); - params.follows.flags(FOLLOWS_LEFT | FOLLOWS_RIGHT | FOLLOWS_BOTTOM); - mCaption = LLUICtrlFactory::create (params); - addChild( mCaption ); - - S32 image_top = getRect().getHeight(); - S32 image_bottom = BTN_HEIGHT_SMALL; - S32 image_middle = (image_top + image_bottom) / 2; - S32 line_height = LLFontGL::getFontSansSerifSmall()->getLineHeight(); - - LLTextBox::Params tentative_label_p(p.multiselect_text); - tentative_label_p.name("Multiple"); - tentative_label_p.rect(LLRect (0, image_middle + line_height / 2, getRect().getWidth(), image_middle - line_height / 2 )); - tentative_label_p.follows.flags(FOLLOWS_ALL); - mTentativeLabel = LLUICtrlFactory::create (tentative_label_p); - - // It is no longer possible to associate a style with a textbox, so it has to be done in this fashion - LLStyle::Params style_params; - style_params.color = LLColor4::white; - - mTentativeLabel->setText(LLTrans::getString("multiple_textures"), style_params); - mTentativeLabel->setHAlign(LLFontGL::HCENTER); - addChild( mTentativeLabel ); - - LLRect border_rect = getLocalRect(); - border_rect.mBottom += BTN_HEIGHT_SMALL; - LLViewBorder::Params vbparams(p.border); - vbparams.name("border"); - vbparams.rect(border_rect); - mBorder = LLUICtrlFactory::create (vbparams); - addChild(mBorder); - - mLoadingPlaceholderString = LLTrans::getString("texture_loading"); -} - -LLTextureCtrl::~LLTextureCtrl() -{ - closeDependentFloater(); -} - -void LLTextureCtrl::setShowLoadingPlaceholder(bool showLoadingPlaceholder) -{ - mShowLoadingPlaceholder = showLoadingPlaceholder; -} - -void LLTextureCtrl::setCaption(const std::string& caption) -{ - mCaption->setText( caption ); -} - -void LLTextureCtrl::setCanApplyImmediately(bool b) -{ - mCanApplyImmediately = b; - LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterHandle.get(); - if( floaterp ) - { - floaterp->setCanApplyImmediately(b); - } -} - -void LLTextureCtrl::setCanApply(bool can_preview, bool can_apply) -{ - LLFloaterTexturePicker* floaterp = dynamic_cast(mFloaterHandle.get()); - if( floaterp ) - { - floaterp->setCanApply(can_preview, can_apply); - } -} - -void LLTextureCtrl::setImmediateFilterPermMask(PermissionMask mask) -{ - mImmediateFilterPermMask = mask; - - LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterHandle.get(); - if (floaterp) - { - floaterp->setImmediateFilterPermMask(mask); - } -} - -void LLTextureCtrl::setFilterPermissionMasks(PermissionMask mask) -{ - setImmediateFilterPermMask(mask); - setDnDFilterPermMask(mask); -} - -void LLTextureCtrl::setVisible( bool visible ) -{ - if( !visible ) - { - closeDependentFloater(); - } - LLUICtrl::setVisible( visible ); -} - -void LLTextureCtrl::setEnabled( bool enabled ) -{ - LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterHandle.get(); - if( floaterp ) - { - floaterp->setActive(enabled); - } - if( enabled ) - { - std::string tooltip; - if (floaterp) tooltip = floaterp->getString("choose_picture"); - setToolTip( tooltip ); - } - else - { - setToolTip( std::string() ); - // *TODO: would be better to keep floater open and show - // disabled state. - closeDependentFloater(); - } - - mCaption->setEnabled( enabled ); - - LLView::setEnabled( enabled ); -} - -void LLTextureCtrl::setValid(bool valid ) -{ - mValid = valid; - if (!valid) - { - LLFloaterTexturePicker* pickerp = (LLFloaterTexturePicker*)mFloaterHandle.get(); - if (pickerp) - { - pickerp->setActive(false); - } - } -} - - -// virtual -void LLTextureCtrl::clear() -{ - setImageAssetID(LLUUID::null); -} - -void LLTextureCtrl::setLabel(const std::string& label) -{ - mLabel = label; - mCaption->setText(label); -} - -void LLTextureCtrl::showPicker(bool take_focus) -{ - // show hourglass cursor when loading inventory window - // because inventory construction is slooow - getWindow()->setCursor(UI_CURSOR_WAIT); - LLFloater* floaterp = mFloaterHandle.get(); - - // Show the dialog - if( floaterp ) - { - floaterp->openFloater(); - } - else - { - floaterp = new LLFloaterTexturePicker( - this, - getImageAssetID(), - getDefaultImageAssetID(), - getBlankImageAssetID(), - getTentative(), - getAllowNoTexture(), - mLabel, - mImmediateFilterPermMask, - mDnDFilterPermMask, - mCanApplyImmediately, - mFallbackImage, - mInventoryPickType); - mFloaterHandle = floaterp->getHandle(); - - LLFloaterTexturePicker* texture_floaterp = dynamic_cast(floaterp); - if (texture_floaterp && mOnTextureSelectedCallback) - { - texture_floaterp->setTextureSelectedCallback(mOnTextureSelectedCallback); - } - if (texture_floaterp && mOnCloseCallback) - { - texture_floaterp->setOnFloaterCloseCallback(boost::bind(&LLTextureCtrl::onFloaterClose, this)); - } - if (texture_floaterp) - { - texture_floaterp->setOnFloaterCommitCallback(boost::bind(&LLTextureCtrl::onFloaterCommit, this, _1, _2, _3, _4, _5)); - } - if (texture_floaterp) - { - texture_floaterp->setSetImageAssetIDCallback(boost::bind(&LLTextureCtrl::setImageAssetID, this, _1)); - - texture_floaterp->setBakeTextureEnabled(mBakeTextureEnabled); - } - - LLFloater* root_floater = gFloaterView->getParentFloater(this); - if (root_floater) - root_floater->addDependentFloater(floaterp); - floaterp->openFloater(); - } - - LLFloaterTexturePicker* picker_floater = dynamic_cast(floaterp); - if (picker_floater) - { - picker_floater->setLocalTextureEnabled(mAllowLocalTexture); - } - - if (take_focus) - { - floaterp->setFocus(true); - } -} - - -void LLTextureCtrl::closeDependentFloater() -{ - LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterHandle.get(); - if( floaterp && floaterp->isInVisibleChain()) - { - floaterp->setOwner(NULL); - floaterp->setVisible(false); - floaterp->closeFloater(); - } -} - -// Allow us to download textures quickly when floater is shown -class LLTextureFetchDescendentsObserver : public LLInventoryFetchDescendentsObserver -{ -public: - virtual void done() - { - // We need to find textures in all folders, so get the main - // background download going. - LLInventoryModelBackgroundFetch::instance().start(); - gInventory.removeObserver(this); - delete this; - } -}; - -bool LLTextureCtrl::handleHover(S32 x, S32 y, MASK mask) -{ - getWindow()->setCursor(mBorder->parentPointInView(x,y) ? UI_CURSOR_HAND : UI_CURSOR_ARROW); - return true; -} - - -bool LLTextureCtrl::handleMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = LLUICtrl::handleMouseDown( x, y , mask ); - - if (!handled && mBorder->parentPointInView(x, y)) - { - if (!mOpenTexPreview) - { - showPicker(false); - if (mInventoryPickType == PICK_MATERIAL) - { - //grab materials first... - LLInventoryModelBackgroundFetch::instance().start(gInventory.findCategoryUUIDForType(LLFolderType::FT_MATERIAL)); - } - else - { - //grab textures first... - LLInventoryModelBackgroundFetch::instance().start(gInventory.findCategoryUUIDForType(LLFolderType::FT_TEXTURE)); - } - //...then start full inventory fetch. - if (!LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) - { - LLInventoryModelBackgroundFetch::instance().start(); - } - handled = true; - } - else - { - if (getImageAssetID().notNull()) - { - LLPreviewTexture* preview_texture = LLFloaterReg::showTypedInstance("preview_texture", getValue()); - if (preview_texture && !preview_texture->isDependent()) - { - LLFloater* root_floater = gFloaterView->getParentFloater(this); - if (root_floater) - { - root_floater->addDependentFloater(preview_texture); - preview_texture->hideCtrlButtons(); - } - } - } - } - } - - return handled; -} - -void LLTextureCtrl::onFloaterClose() -{ - LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterHandle.get(); - - if (floaterp) - { - if (mOnCloseCallback) - { - mOnCloseCallback(this,LLSD()); - } - floaterp->setOwner(NULL); - } - - mFloaterHandle.markDead(); -} - -void LLTextureCtrl::onFloaterCommit(ETexturePickOp op, LLPickerSource source, const LLUUID& asset_id, const LLUUID& inv_id, const LLUUID& tracking_id) -{ - LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterHandle.get(); - - if( floaterp && getEnabled()) - { - if (op == TEXTURE_CANCEL) - mViewModel->resetDirty(); - // If the "no_commit_on_selection" parameter is set - // we get dirty only when user presses OK in the picker - // (i.e. op == TEXTURE_SELECT) or texture changes via DnD. - else if (mCommitOnSelection || op == TEXTURE_SELECT) - mViewModel->setDirty(); // *TODO: shouldn't we be using setValue() here? - - if(floaterp->isDirty() || asset_id.notNull()) // mModelView->setDirty does not work. - { - setTentative( false ); - - switch(source) - { - case PICKER_INVENTORY: - mImageItemID = inv_id; - mImageAssetID = asset_id; - mLocalTrackingID.setNull(); - break; - case PICKER_BAKE: - mImageItemID = LLUUID::null; - mImageAssetID = asset_id; - mLocalTrackingID.setNull(); - break; - case PICKER_LOCAL: - mImageItemID = LLUUID::null; - mImageAssetID = asset_id; - mLocalTrackingID = tracking_id; - break; - case PICKER_UNKNOWN: - default: - mImageItemID = floaterp->findItemID(asset_id, false); - mImageAssetID = asset_id; - mLocalTrackingID.setNull(); - break; - } - - LL_DEBUGS() << "mImageAssetID: " << mImageAssetID << ", mImageItemID: " << mImageItemID << LL_ENDL; - - if (op == TEXTURE_SELECT && mOnSelectCallback) - { - mOnSelectCallback(this, LLSD()); - } - else if (op == TEXTURE_CANCEL && mOnCancelCallback) - { - mOnCancelCallback( this, LLSD() ); - } - else - { - // If the "no_commit_on_selection" parameter is set - // we commit only when user presses OK in the picker - // (i.e. op == TEXTURE_SELECT) or texture changes via DnD. - if (mCommitOnSelection || op == TEXTURE_SELECT) - { - onCommit(); - } - } - } - } -} - -void LLTextureCtrl::setOnTextureSelectedCallback(texture_selected_callback cb) -{ - mOnTextureSelectedCallback = cb; - LLFloaterTexturePicker* floaterp = dynamic_cast(mFloaterHandle.get()); - if (floaterp) - { - floaterp->setTextureSelectedCallback(cb); - } -} - -void LLTextureCtrl::setImageAssetName(const std::string& name) -{ - LLPointer imagep = LLUI::getUIImage(name); - if(imagep) - { - LLViewerFetchedTexture* pTexture = dynamic_cast(imagep->getImage().get()); - if(pTexture) - { - LLUUID id = pTexture->getID(); - setImageAssetID(id); - } - } -} - -void LLTextureCtrl::setImageAssetID( const LLUUID& asset_id ) -{ - if( mImageAssetID != asset_id ) - { - mImageItemID.setNull(); - mImageAssetID = asset_id; - mLocalTrackingID.setNull(); - LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterHandle.get(); - if( floaterp && getEnabled() ) - { - floaterp->setImageID( asset_id ); - floaterp->resetDirty(); - } - } -} - -void LLTextureCtrl::setBakeTextureEnabled(bool enabled) -{ - mBakeTextureEnabled = enabled; - LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterHandle.get(); - if (floaterp) - { - floaterp->setBakeTextureEnabled(enabled); - } -} - -void LLTextureCtrl::setInventoryPickType(EPickInventoryType type) -{ - mInventoryPickType = type; - LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterHandle.get(); - if (floaterp) - { - floaterp->setInventoryPickType(type); - } -} - -bool LLTextureCtrl::handleDragAndDrop(S32 x, S32 y, MASK mask, - bool drop, EDragAndDropType cargo_type, void *cargo_data, - EAcceptance *accept, - std::string& tooltip_msg) -{ - bool handled = false; - - // this downcast may be invalid - but if the second test below - // returns true, then the cast was valid, and we can perform - // the third test without problems. - LLInventoryItem* item = (LLInventoryItem*)cargo_data; - - bool is_mesh = cargo_type == DAD_MESH; - bool is_texture = cargo_type == DAD_TEXTURE; - bool is_material = cargo_type == DAD_MATERIAL; - - bool allow_dnd = false; - if (mInventoryPickType == PICK_MATERIAL) - { - allow_dnd = is_material; - } - else if (mInventoryPickType == PICK_TEXTURE) - { - allow_dnd = is_texture || is_mesh; - } - else - { - allow_dnd = is_texture || is_mesh || is_material; - } - - if (getEnabled() && allow_dnd && allowDrop(item, cargo_type, tooltip_msg)) - { - if (drop) - { - if(doDrop(item)) - { - if (!mCommitOnSelection) - mViewModel->setDirty(); - - // This removes the 'Multiple' overlay, since - // there is now only one texture selected. - setTentative( false ); - onCommit(); - } - } - - *accept = ACCEPT_YES_SINGLE; - } - else - { - *accept = ACCEPT_NO; - } - - handled = true; - LL_DEBUGS("UserInput") << "dragAndDrop handled by LLTextureCtrl " << getName() << LL_ENDL; - - return handled; -} - -void LLTextureCtrl::draw() -{ - mBorder->setKeyboardFocusHighlight(hasFocus()); - - if (!mValid) - { - mTexturep = NULL; - } - else if (!mImageAssetID.isNull()) - { - LLPointer texture = NULL; - - if (LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(mImageAssetID)) - { - LLViewerObject* obj = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); - if (obj) - { - LLViewerTexture* viewerTexture = obj->getBakedTextureForMagicId(mImageAssetID); - texture = viewerTexture ? dynamic_cast(viewerTexture) : NULL; - } - - } - - if (texture.isNull()) - { - if (mInventoryPickType == PICK_MATERIAL) - { - LLPointer material = gGLTFMaterialList.getMaterial(mImageAssetID); - if (material) - { - texture = material->getUITexture(); - } - } - else - { - texture = LLViewerTextureManager::getFetchedTexture(mImageAssetID, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); - texture->setBoostLevel(LLGLTexture::BOOST_PREVIEW); - texture->forceToSaveRawImage(0); - } - } - - mTexturep = texture; - } - else//mImageAssetID == LLUUID::null - { - mTexturep = NULL; - } - - // Border - LLRect border( 0, getRect().getHeight(), getRect().getWidth(), BTN_HEIGHT_SMALL ); - gl_rect_2d( border, mBorderColor.get(), false ); - - // Interior - LLRect interior = border; - interior.stretch( -1 ); - - // If we're in a focused floater, don't apply the floater's alpha to the texture (STORM-677). - const F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); - if( mTexturep ) - { - if( mTexturep->getComponents() == 4 ) - { - gl_rect_2d_checkerboard( interior, alpha ); - } - - gl_draw_scaled_image( interior.mLeft, interior.mBottom, interior.getWidth(), interior.getHeight(), mTexturep, UI_VERTEX_COLOR % alpha); - mTexturep->addTextureStats( (F32)(interior.getWidth() * interior.getHeight()) ); - } - else if (!mFallbackImage.isNull()) - { - mFallbackImage->draw(interior, UI_VERTEX_COLOR % alpha); - } - else - { - gl_rect_2d( interior, LLColor4::grey % alpha, true ); - - // Draw X - gl_draw_x( interior, LLColor4::black ); - } - - mTentativeLabel->setVisible( getTentative() ); - - // Show "Loading..." string on the top left corner while this texture is loading. - // Using the discard level, do not show the string if the texture is almost but not - // fully loaded. - if (mTexturep.notNull() && - !mTexturep->isFullyLoaded() && - mShowLoadingPlaceholder) - { - U32 v_offset = 25; - LLFontGL* font = LLFontGL::getFontSansSerif(); - - // Don't show as loaded if the texture is almost fully loaded (i.e. discard1) unless god - if ((mTexturep->getDiscardLevel() > 1) || gAgent.isGodlike()) - { - font->renderUTF8( - mLoadingPlaceholderString, - 0, - llfloor(interior.mLeft+3), - llfloor(interior.mTop-v_offset), - LLColor4::white, - LLFontGL::LEFT, - LLFontGL::BASELINE, - LLFontGL::DROP_SHADOW); - } - - // Optionally show more detailed information. - if (gSavedSettings.getBOOL("DebugAvatarRezTime")) - { - LLFontGL* font = LLFontGL::getFontSansSerif(); - std::string tdesc; - // Show what % the texture has loaded (0 to 100%, 100 is highest), and what level of detail (5 to 0, 0 is best). - - v_offset += 12; - tdesc = llformat(" PK : %d%%", U32(mTexturep->getDownloadProgress()*100.0)); - font->renderUTF8(tdesc, 0, llfloor(interior.mLeft+3), llfloor(interior.mTop-v_offset), - LLColor4::white, LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::DROP_SHADOW); - - v_offset += 12; - tdesc = llformat(" LVL: %d", mTexturep->getDiscardLevel()); - font->renderUTF8(tdesc, 0, llfloor(interior.mLeft+3), llfloor(interior.mTop-v_offset), - LLColor4::white, LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::DROP_SHADOW); - - v_offset += 12; - tdesc = llformat(" ID : %s...", (mImageAssetID.asString().substr(0,7)).c_str()); - font->renderUTF8(tdesc, 0, llfloor(interior.mLeft+3), llfloor(interior.mTop-v_offset), - LLColor4::white, LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::DROP_SHADOW); - } - } - - LLUICtrl::draw(); -} - -bool LLTextureCtrl::allowDrop(LLInventoryItem* item, EDragAndDropType cargo_type, std::string& tooltip_msg) -{ - bool copy = item->getPermissions().allowCopyBy(gAgent.getID()); - bool mod = item->getPermissions().allowModifyBy(gAgent.getID()); - bool xfer = item->getPermissions().allowOperationBy(PERM_TRANSFER, - gAgent.getID()); - - PermissionMask item_perm_mask = 0; - if (copy) item_perm_mask |= PERM_COPY; - if (mod) item_perm_mask |= PERM_MODIFY; - if (xfer) item_perm_mask |= PERM_TRANSFER; - - PermissionMask filter_perm_mask = mImmediateFilterPermMask; - if ( (item_perm_mask & filter_perm_mask) == filter_perm_mask ) - { - if(mDragCallback) - { - return mDragCallback(this, item); - } - else - { - return true; - } - } - else - { - PermissionMask mask = PERM_COPY | PERM_TRANSFER; - if ((filter_perm_mask & mask) == mask - && cargo_type == DAD_TEXTURE) - { - tooltip_msg.assign(LLTrans::getString("TooltipTextureRestrictedDrop")); - } - return false; - } -} - -bool LLTextureCtrl::doDrop(LLInventoryItem* item) -{ - // call the callback if it exists. - if(mDropCallback) - { - // if it returns true, we return true, and therefore the - // commit is called above. - return mDropCallback(this, item); - } - - // no callback installed, so just set the image ids and carry on. - LLUUID asset_id = item->getAssetUUID(); - - if (mInventoryPickType == PICK_MATERIAL && asset_id.isNull()) - { - // If an inventory material has a null asset, consider it a valid blank material(gltf) - asset_id = LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID; - } - - setImageAssetID(asset_id); - mImageItemID = item->getUUID(); - return true; -} - -bool LLTextureCtrl::handleUnicodeCharHere(llwchar uni_char) -{ - if( ' ' == uni_char ) - { - showPicker(true); - return true; - } - return LLUICtrl::handleUnicodeCharHere(uni_char); -} - -void LLTextureCtrl::setValue( const LLSD& value ) -{ - setImageAssetID(value.asUUID()); -} - -LLSD LLTextureCtrl::getValue() const -{ - return LLSD(getImageAssetID()); -} - - - - - +/** + * @file lltexturectrl.cpp + * @author Richard Nelson, James Cook + * @brief LLTextureCtrl class implementation including related functions + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lltexturectrl.h" + +#include "llrender.h" +#include "llagent.h" +#include "llviewertexturelist.h" +#include "llselectmgr.h" +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "llbutton.h" +#include "lldraghandle.h" +#include "llfocusmgr.h" +#include "llfolderviewmodel.h" +#include "llinventory.h" +#include "llinventoryfunctions.h" +#include "llinventoryicon.h" +#include "llinventorymodelbackgroundfetch.h" +#include "llinventoryobserver.h" +#include "llinventorypanel.h" +#include "lllineeditor.h" +#include "llmaterialeditor.h" +#include "llui.h" +#include "llviewerinventory.h" +#include "llviewermenufile.h" // LLFilePickerReplyThread +#include "llpermissions.h" +#include "llpreviewtexture.h" +#include "llsaleinfo.h" +#include "llassetstorage.h" +#include "lltextbox.h" +#include "llresizehandle.h" +#include "llscrollcontainer.h" +#include "lltoolmgr.h" +#include "lltoolpipette.h" +#include "llfiltereditor.h" +#include "llwindow.h" + +#include "lltool.h" +#include "llviewerwindow.h" +#include "llviewerobject.h" +#include "llviewercontrol.h" +#include "llglheaders.h" +#include "lluictrlfactory.h" +#include "lltrans.h" + +#include "llradiogroup.h" +#include "llfloaterreg.h" +#include "lllocalbitmaps.h" +#include "lllocalgltfmaterials.h" +#include "llerror.h" + +#include "llavatarappearancedefines.h" + + +//static +bool get_is_predefined_texture(LLUUID asset_id) +{ + if (asset_id == DEFAULT_OBJECT_TEXTURE + || asset_id == DEFAULT_OBJECT_SPECULAR + || asset_id == DEFAULT_OBJECT_NORMAL + || asset_id == BLANK_OBJECT_NORMAL + || asset_id == IMG_WHITE + || asset_id == LLUUID(SCULPT_DEFAULT_TEXTURE)) + { + return true; + } + return false; +} + +LLUUID get_copy_free_item_by_asset_id(LLUUID asset_id, bool no_trans_perm) +{ + LLViewerInventoryCategory::cat_array_t cats; + LLViewerInventoryItem::item_array_t items; + LLAssetIDMatches asset_id_matches(asset_id); + gInventory.collectDescendentsIf(LLUUID::null, + cats, + items, + LLInventoryModel::INCLUDE_TRASH, + asset_id_matches); + + LLUUID res; + if (items.size()) + { + for (S32 i = 0; i < items.size(); i++) + { + LLViewerInventoryItem* itemp = items[i]; + if (itemp) + { + LLPermissions item_permissions = itemp->getPermissions(); + if (item_permissions.allowOperationBy(PERM_COPY, + gAgent.getID(), + gAgent.getGroupID())) + { + bool allow_trans = item_permissions.allowOperationBy(PERM_TRANSFER, gAgent.getID(), gAgent.getGroupID()); + if (allow_trans != no_trans_perm) + { + return itemp->getUUID(); + } + res = itemp->getUUID(); + } + } + } + } + return res; +} + +bool get_can_copy_texture(LLUUID asset_id) +{ + // User is allowed to copy a texture if: + // library asset or default texture, + // or copy perm asset exists in user's inventory + + return get_is_predefined_texture(asset_id) || get_copy_free_item_by_asset_id(asset_id).notNull(); +} + +S32 LLFloaterTexturePicker::sLastPickerMode = 0; + +LLFloaterTexturePicker::LLFloaterTexturePicker( + LLView* owner, + LLUUID image_asset_id, + LLUUID default_image_asset_id, + LLUUID blank_image_asset_id, + bool tentative, + bool allow_no_texture, + const std::string& label, + PermissionMask immediate_filter_perm_mask, + PermissionMask dnd_filter_perm_mask, + bool can_apply_immediately, + LLUIImagePtr fallback_image, + EPickInventoryType pick_type) +: LLFloater(LLSD()), + mOwner( owner ), + mImageAssetID( image_asset_id ), + mOriginalImageAssetID(image_asset_id), + mFallbackImage(fallback_image), + mDefaultImageAssetID(default_image_asset_id), + mBlankImageAssetID(blank_image_asset_id), + mTentative(tentative), + mAllowNoTexture(allow_no_texture), + mLabel(label), + mTentativeLabel(NULL), + mResolutionLabel(NULL), + mActive( true ), + mFilterEdit(NULL), + mImmediateFilterPermMask(immediate_filter_perm_mask), + mDnDFilterPermMask(dnd_filter_perm_mask), + mContextConeOpacity(0.f), + mSelectedItemPinned( false ), + mCanApply(true), + mCanPreview(true), + mLimitsSet(false), + mMaxDim(S32_MAX), + mMinDim(0), + mPreviewSettingChanged(false), + mOnFloaterCommitCallback(NULL), + mOnFloaterCloseCallback(NULL), + mSetImageAssetIDCallback(NULL), + mOnUpdateImageStatsCallback(NULL), + mBakeTextureEnabled(false), + mInventoryPickType(pick_type) +{ + mCanApplyImmediately = can_apply_immediately; + buildFromFile("floater_texture_ctrl.xml"); + setCanMinimize(false); +} + +LLFloaterTexturePicker::~LLFloaterTexturePicker() +{ +} + +void LLFloaterTexturePicker::setImageID(const LLUUID& image_id, bool set_selection /*=true*/) +{ + if( ((mImageAssetID != image_id) || mTentative) && mActive) + { + mNoCopyTextureSelected = false; + mViewModel->setDirty(); // *TODO: shouldn't we be using setValue() here? + mImageAssetID = image_id; + + if (LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(mImageAssetID)) + { + if ( mBakeTextureEnabled && mModeSelector->getValue().asInteger() != 2) + { + mModeSelector->selectByValue(2); + onModeSelect(0,this); + } + } + else + { + if (mModeSelector->getValue().asInteger() == 2) + { + mModeSelector->selectByValue(0); + onModeSelect(0,this); + } + + LLUUID item_id; + LLFolderView* root_folder = mInventoryPanel->getRootFolder(); + if (root_folder && root_folder->getCurSelectedItem()) + { + LLFolderViewItem* last_selected = root_folder->getCurSelectedItem(); + LLFolderViewModelItemInventory* inv_view = static_cast(last_selected->getViewModelItem()); + + LLInventoryItem* itemp = gInventory.getItem(inv_view->getUUID()); + + if (mInventoryPickType == PICK_MATERIAL + && mImageAssetID == LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID + && itemp && itemp->getAssetUUID().isNull()) + { + item_id = inv_view->getUUID(); + } + else if (itemp && itemp->getAssetUUID() == mImageAssetID) + { + item_id = inv_view->getUUID(); + } + } + if (item_id.isNull()) + { + item_id = findItemID(mImageAssetID, false); + } + if (item_id.isNull()) + { + mInventoryPanel->getRootFolder()->clearSelection(); + } + else + { + LLInventoryItem* itemp = gInventory.getItem(item_id); + if (itemp && !itemp->getPermissions().allowCopyBy(gAgent.getID())) + { + // no copy texture + getChild("apply_immediate_check")->setValue(false); + mNoCopyTextureSelected = true; + } + } + + if (set_selection) + { + mInventoryPanel->setSelection(item_id, TAKE_FOCUS_NO); + } + } + } +} + +void LLFloaterTexturePicker::setImageIDFromItem(const LLInventoryItem* itemp, bool set_selection) +{ + LLUUID asset_id = itemp->getAssetUUID(); + if (mInventoryPickType == PICK_MATERIAL && asset_id.isNull()) + { + // If an inventory item has a null asset, consider it a valid blank material(gltf) + asset_id = LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID; + } + setImageID(asset_id, set_selection); +} + +void LLFloaterTexturePicker::setActive( bool active ) +{ + if (!active && getChild("Pipette")->getValue().asBoolean()) + { + stopUsingPipette(); + } + mActive = active; +} + +void LLFloaterTexturePicker::setCanApplyImmediately(bool b) +{ + mCanApplyImmediately = b; + + LLUICtrl *apply_checkbox = getChild("apply_immediate_check"); + apply_checkbox->setValue(mCanApplyImmediately && gSavedSettings.getBOOL("TextureLivePreview")); + apply_checkbox->setEnabled(mCanApplyImmediately); +} + +void LLFloaterTexturePicker::stopUsingPipette() +{ + if (LLToolMgr::getInstance()->getCurrentTool() == LLToolPipette::getInstance()) + { + LLToolMgr::getInstance()->clearTransientTool(); + } +} + +bool LLFloaterTexturePicker::updateImageStats() +{ + bool result = true; + if (mGLTFMaterial.notNull()) + { + S32 width = 0; + S32 height = 0; + + bool has_texture = false; + + if (mGLTFMaterial->mBaseColorTexture) + { + width = llmax(width, mGLTFMaterial->mBaseColorTexture->getFullWidth()); + height = llmax(height, mGLTFMaterial->mBaseColorTexture->getFullHeight()); + has_texture = true; + } + if (mGLTFMaterial->mNormalTexture) + { + width = llmax(width, mGLTFMaterial->mNormalTexture->getFullWidth()); + height = llmax(height, mGLTFMaterial->mNormalTexture->getFullHeight()); + has_texture = true; + } + if (mGLTFMaterial->mMetallicRoughnessTexture) + { + width = llmax(width, mGLTFMaterial->mMetallicRoughnessTexture->getFullWidth()); + height = llmax(height, mGLTFMaterial->mMetallicRoughnessTexture->getFullHeight()); + has_texture = true; + } + if (mGLTFMaterial->mEmissiveTexture) + { + width = llmax(width, mGLTFMaterial->mEmissiveTexture->getFullWidth()); + height = llmax(height, mGLTFMaterial->mEmissiveTexture->getFullHeight()); + has_texture = true; + } + + if (width > 0 && height > 0) + { + std::string formatted_dims = llformat("%d x %d", width, height); + mResolutionLabel->setTextArg("[DIMENSIONS]", formatted_dims); + if (mOnUpdateImageStatsCallback) + { + mOnUpdateImageStatsCallback(mTexturep); + } + } + else if (has_texture) + { + // unknown resolution + mResolutionLabel->setTextArg("[DIMENSIONS]", std::string("[? x ?]")); + } + else + { + // No textures - no applicable resolution (may be show some max value instead?) + mResolutionLabel->setTextArg("[DIMENSIONS]", std::string("")); + } + } + else if (mTexturep.notNull()) + { + //RN: have we received header data for this image? + S32 width = mTexturep->getFullWidth(); + S32 height = mTexturep->getFullHeight(); + if (width > 0 && height > 0) + { + if ((mLimitsSet && (width != height)) + || width < mMinDim + || width > mMaxDim + || height < mMinDim + || height > mMaxDim + ) + { + std::string formatted_dims = llformat("%dx%d", width, height); + mResolutionWarning->setTextArg("[TEXDIM]", formatted_dims); + result = false; + } + else + { + std::string formatted_dims = llformat("%d x %d", width, height); + mResolutionLabel->setTextArg("[DIMENSIONS]", formatted_dims); + } + + if (mOnUpdateImageStatsCallback) + { + mOnUpdateImageStatsCallback(mTexturep); + } + } + else + { + mResolutionLabel->setTextArg("[DIMENSIONS]", std::string("[? x ?]")); + } + } + else + { + mResolutionLabel->setTextArg("[DIMENSIONS]", std::string("")); + } + mResolutionLabel->setVisible(result); + mResolutionWarning->setVisible(!result); + + // Hide buttons and pipete to make space for mResolutionWarning + // Hiding buttons is suboptimal, but at the moment limited to inventory thumbnails + // may be this should be an info/warning icon with a tooltip? + S32 index = mModeSelector->getValue().asInteger(); + if (index == 0) + { + mDefaultBtn->setVisible(result); + mNoneBtn->setVisible(result); + mBlankBtn->setVisible(result); + mPipetteBtn->setVisible(result); + } + return result; +} + +// virtual +bool LLFloaterTexturePicker::handleDragAndDrop( + S32 x, S32 y, MASK mask, + bool drop, + EDragAndDropType cargo_type, void *cargo_data, + EAcceptance *accept, + std::string& tooltip_msg) +{ + bool handled = false; + + bool is_mesh = cargo_type == DAD_MESH; + bool is_texture = cargo_type == DAD_TEXTURE; + bool is_material = cargo_type == DAD_MATERIAL; + + bool allow_dnd = false; + if (mInventoryPickType == PICK_MATERIAL) + { + allow_dnd = is_material; + } + else if (mInventoryPickType == PICK_TEXTURE) + { + allow_dnd = is_texture || is_mesh; + } + else + { + allow_dnd = is_texture || is_mesh || is_material; + } + + if (allow_dnd) + { + LLInventoryItem *item = (LLInventoryItem *)cargo_data; + + bool copy = item->getPermissions().allowCopyBy(gAgent.getID()); + bool mod = item->getPermissions().allowModifyBy(gAgent.getID()); + bool xfer = item->getPermissions().allowOperationBy(PERM_TRANSFER, + gAgent.getID()); + + PermissionMask item_perm_mask = 0; + if (copy) item_perm_mask |= PERM_COPY; + if (mod) item_perm_mask |= PERM_MODIFY; + if (xfer) item_perm_mask |= PERM_TRANSFER; + + PermissionMask filter_perm_mask = mDnDFilterPermMask; + if ( (item_perm_mask & filter_perm_mask) == filter_perm_mask ) + { + if (drop) + { + setImageIDFromItem(item); + commitIfImmediateSet(); + } + + *accept = ACCEPT_YES_SINGLE; + } + else + { + *accept = ACCEPT_NO; + } + } + else + { + *accept = ACCEPT_NO; + } + + handled = true; + LL_DEBUGS("UserInput") << "dragAndDrop handled by LLFloaterTexturePicker " << getName() << LL_ENDL; + + return handled; +} + +bool LLFloaterTexturePicker::handleKeyHere(KEY key, MASK mask) +{ + LLFolderView* root_folder = mInventoryPanel->getRootFolder(); + + if (root_folder && mFilterEdit) + { + if (mFilterEdit->hasFocus() + && (key == KEY_RETURN || key == KEY_DOWN) + && mask == MASK_NONE) + { + if (!root_folder->getCurSelectedItem()) + { + LLFolderViewItem* itemp = mInventoryPanel->getItemByID(gInventory.getRootFolderID()); + if (itemp) + { + root_folder->setSelection(itemp, false, false); + } + } + root_folder->scrollToShowSelection(); + + // move focus to inventory proper + mInventoryPanel->setFocus(true); + + // treat this as a user selection of the first filtered result + commitIfImmediateSet(); + + return true; + } + + if (mInventoryPanel->hasFocus() && key == KEY_UP) + { + mFilterEdit->focusFirstItem(true); + } + } + + return LLFloater::handleKeyHere(key, mask); +} + +void LLFloaterTexturePicker::onOpen(const LLSD& key) +{ + if (sLastPickerMode != 0 + && mModeSelector->selectByValue(sLastPickerMode)) + { + changeMode(); + } +} + +void LLFloaterTexturePicker::onClose(bool app_quitting) +{ + if (mOwner && mOnFloaterCloseCallback) + { + mOnFloaterCloseCallback(); + } + stopUsingPipette(); + sLastPickerMode = mModeSelector->getValue().asInteger(); +} + +// virtual +bool LLFloaterTexturePicker::postBuild() +{ + LLFloater::postBuild(); + + if (!mLabel.empty()) + { + std::string pick = getString("pick title"); + + setTitle(pick + mLabel); + } + mTentativeLabel = getChild("Multiple"); + + mResolutionLabel = getChild("size_lbl"); + mResolutionWarning = getChild("over_limit_lbl"); + + + mDefaultBtn = getChild("Default"); + mNoneBtn = getChild("None"); + mBlankBtn = getChild("Blank"); + mPipetteBtn = getChild("Pipette"); + mSelectBtn = getChild("Select"); + mCancelBtn = getChild("Cancel"); + + mDefaultBtn->setClickedCallback(boost::bind(LLFloaterTexturePicker::onBtnSetToDefault,this)); + mNoneBtn->setClickedCallback(boost::bind(LLFloaterTexturePicker::onBtnNone, this)); + mBlankBtn->setClickedCallback(boost::bind(LLFloaterTexturePicker::onBtnBlank, this)); + mPipetteBtn->setCommitCallback(boost::bind(&LLFloaterTexturePicker::onBtnPipette, this)); + mSelectBtn->setClickedCallback(boost::bind(LLFloaterTexturePicker::onBtnSelect, this)); + mCancelBtn->setClickedCallback(boost::bind(LLFloaterTexturePicker::onBtnCancel, this)); + + mFilterEdit = getChild("inventory search editor"); + mFilterEdit->setCommitCallback(boost::bind(&LLFloaterTexturePicker::onFilterEdit, this, _2)); + + mInventoryPanel = getChild("inventory panel"); + + mModeSelector = getChild("mode_selection"); + mModeSelector->setCommitCallback(onModeSelect, this); + mModeSelector->selectByValue(0); + + if(mInventoryPanel) + { + // to avoid having to make an assumption about which option is + // selected at startup, we call the same function that is triggered + // when a texture/materials/both choice is made and let it take care + // of setting the filters + refreshInventoryFilter(); + + mInventoryPanel->setFilterPermMask(mImmediateFilterPermMask); + mInventoryPanel->setSelectCallback(boost::bind(&LLFloaterTexturePicker::onSelectionChange, this, _1, _2)); + mInventoryPanel->setShowFolderState(LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); + + // Disable auto selecting first filtered item because it takes away + // selection from the item set by LLTextureCtrl owning this floater. + mInventoryPanel->getRootFolder()->setAutoSelectOverride(true); + + // Commented out to scroll to currently selected texture. See EXT-5403. + // // store this filter as the default one + // mInventoryPanel->getRootFolder()->getFilter().markDefault(); + + // Commented out to stop opening all folders with textures + // mInventoryPanel->openDefaultFolderForType(LLFolderType::FT_TEXTURE); + + // don't put keyboard focus on selected item, because the selection callback + // will assume that this was user input + + if(!mImageAssetID.isNull() || mInventoryPickType == PICK_MATERIAL) + { + mInventoryPanel->setSelection(findItemID(mImageAssetID, false), TAKE_FOCUS_NO); + } + } + + childSetAction("l_add_btn", LLFloaterTexturePicker::onBtnAdd, this); + childSetAction("l_rem_btn", LLFloaterTexturePicker::onBtnRemove, this); + childSetAction("l_upl_btn", LLFloaterTexturePicker::onBtnUpload, this); + + mLocalScrollCtrl = getChild("l_name_list"); + mLocalScrollCtrl->setCommitCallback(onLocalScrollCommit, this); + refreshLocalList(); + + mNoCopyTextureSelected = false; + + getChild("apply_immediate_check")->setValue(mCanApplyImmediately && gSavedSettings.getBOOL("TextureLivePreview")); + childSetCommitCallback("apply_immediate_check", onApplyImmediateCheck, this); + getChildView("apply_immediate_check")->setEnabled(mCanApplyImmediately); + + getChild("Pipette")->setCommitCallback( boost::bind(&LLFloaterTexturePicker::onBtnPipette, this)); + childSetAction("Cancel", LLFloaterTexturePicker::onBtnCancel,this); + childSetAction("Select", LLFloaterTexturePicker::onBtnSelect,this); + + mSavedFolderState.setApply(false); + + LLToolPipette::getInstance()->setToolSelectCallback(boost::bind(&LLFloaterTexturePicker::onTextureSelect, this, _1)); + + getChild("l_bake_use_texture_combo_box")->setCommitCallback(onBakeTextureSelect, this); + + setBakeTextureEnabled(true); + return true; +} + +// virtual +void LLFloaterTexturePicker::draw() +{ + static LLCachedControl max_opacity(gSavedSettings, "PickerContextOpacity", 0.4f); + drawConeToOwner(mContextConeOpacity, max_opacity, mOwner); + + // This is going to spam mOnUpdateImageStatsCallback, + // either move elsewhere or fix to cause update once per image + bool valid_dims = updateImageStats(); + + // if we're inactive, gray out "apply immediate" checkbox + getChildView("show_folders_check")->setEnabled(mActive && mCanApplyImmediately && !mNoCopyTextureSelected); + mSelectBtn->setEnabled(mActive && mCanApply && valid_dims); + mPipetteBtn->setEnabled(mActive); + mPipetteBtn->setValue(LLToolMgr::getInstance()->getCurrentTool() == LLToolPipette::getInstance()); + + //bool allow_copy = false; + if( mOwner ) + { + mTexturep = NULL; + mGLTFMaterial = NULL; + if (mImageAssetID.notNull()) + { + if (mInventoryPickType == PICK_MATERIAL) + { + mGLTFMaterial = (LLFetchedGLTFMaterial*) gGLTFMaterialList.getMaterial(mImageAssetID); + llassert(mGLTFMaterial == nullptr || dynamic_cast(gGLTFMaterialList.getMaterial(mImageAssetID)) != nullptr); + } + else + { + LLPointer texture = NULL; + + if (LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(mImageAssetID)) + { + // TODO: Fix this! Picker is not warrantied to be connected to a selection + // LLSelectMgr shouldn't be used in texture picker + LLViewerObject* obj = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); + if (obj) + { + LLViewerTexture* viewerTexture = obj->getBakedTextureForMagicId(mImageAssetID); + texture = viewerTexture ? dynamic_cast(viewerTexture) : NULL; + } + } + + if (texture.isNull()) + { + texture = LLViewerTextureManager::getFetchedTexture(mImageAssetID); + } + + mTexturep = texture; + mTexturep->setBoostLevel(LLGLTexture::BOOST_PREVIEW); + } + } + + if (mTentativeLabel) + { + mTentativeLabel->setVisible( false ); + } + + mDefaultBtn->setEnabled(mImageAssetID != mDefaultImageAssetID || mTentative); + mBlankBtn->setEnabled((mImageAssetID != mBlankImageAssetID && mBlankImageAssetID.notNull()) || mTentative); + mNoneBtn->setEnabled(mAllowNoTexture && (!mImageAssetID.isNull() || mTentative)); + + LLFloater::draw(); + + if( isMinimized() ) + { + return; + } + + // Border + LLRect border = getChildView("preview_widget")->getRect(); + gl_rect_2d( border, LLColor4::black, false ); + + + // Interior + LLRect interior = border; + interior.stretch( -1 ); + + // If the floater is focused, don't apply its alpha to the texture (STORM-677). + const F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); + LLViewerTexture* texture = nullptr; + if (mGLTFMaterial) + { + texture = mGLTFMaterial->getUITexture(); + } + else + { + texture = mTexturep.get(); + } + + if( texture ) + { + if( texture->getComponents() == 4 ) + { + gl_rect_2d_checkerboard( interior, alpha ); + } + + gl_draw_scaled_image( interior.mLeft, interior.mBottom, interior.getWidth(), interior.getHeight(), texture, UI_VERTEX_COLOR % alpha ); + + // Pump the priority + texture->addTextureStats( (F32)(interior.getWidth() * interior.getHeight()) ); + } + else if (!mFallbackImage.isNull()) + { + mFallbackImage->draw(interior, UI_VERTEX_COLOR % alpha); + } + else + { + gl_rect_2d( interior, LLColor4::grey % alpha, true ); + + // Draw X + gl_draw_x(interior, LLColor4::black ); + } + + // Draw Tentative Label over the image + if( mTentative && !mViewModel->isDirty() ) + { + mTentativeLabel->setVisible( true ); + drawChild(mTentativeLabel); + } + + if (mSelectedItemPinned) return; + + LLFolderView* folder_view = mInventoryPanel->getRootFolder(); + if (!folder_view) return; + + LLFolderViewFilter& filter = static_cast(folder_view->getFolderViewModel())->getFilter(); + + bool is_filter_active = folder_view->getViewModelItem()->getLastFilterGeneration() < filter.getCurrentGeneration() && + filter.isNotDefault(); + + // After inventory panel filter is applied we have to update + // constraint rect for the selected item because of folder view + // AutoSelectOverride set to true. We force PinningSelectedItem + // flag to false state and setting filter "dirty" to update + // scroll container to show selected item (see LLFolderView::doIdle()). + if (!is_filter_active && !mSelectedItemPinned) + { + folder_view->setPinningSelectedItem(mSelectedItemPinned); + folder_view->getViewModelItem()->dirtyFilter(); + mSelectedItemPinned = true; + } + } +} + +const LLUUID& LLFloaterTexturePicker::findItemID(const LLUUID& asset_id, bool copyable_only, bool ignore_library) +{ + if (asset_id.isNull()) + { + // null asset id means, no material or texture assigned + return LLUUID::null; + } + + LLUUID loockup_id = asset_id; + if (mInventoryPickType == PICK_MATERIAL && loockup_id == LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID) + { + // default asset id means we are looking for an inventory item with a default asset UUID (null) + loockup_id = LLUUID::null; + } + + LLViewerInventoryCategory::cat_array_t cats; + LLViewerInventoryItem::item_array_t items; + + if (loockup_id.isNull()) + { + // looking for a material with a null id, null id is shared by a lot + // of objects as a default value, so have to filter by type as well + LLAssetIDAndTypeMatches matches(loockup_id, LLAssetType::AT_MATERIAL); + gInventory.collectDescendentsIf(LLUUID::null, + cats, + items, + LLInventoryModel::INCLUDE_TRASH, + matches); + } + else + { + LLAssetIDMatches asset_id_matches(loockup_id); + gInventory.collectDescendentsIf(LLUUID::null, + cats, + items, + LLInventoryModel::INCLUDE_TRASH, + asset_id_matches); + } + + + if (items.size()) + { + // search for copyable version first + for (S32 i = 0; i < items.size(); i++) + { + LLInventoryItem* itemp = items[i]; + LLPermissions item_permissions = itemp->getPermissions(); + if (item_permissions.allowCopyBy(gAgent.getID(), gAgent.getGroupID())) + { + if(!ignore_library || !gInventory.isObjectDescendentOf(itemp->getUUID(),gInventory.getLibraryRootFolderID())) + { + return itemp->getUUID(); + } + } + } + // otherwise just return first instance, unless copyable requested + if (copyable_only) + { + return LLUUID::null; + } + else + { + if(!ignore_library || !gInventory.isObjectDescendentOf(items[0]->getUUID(),gInventory.getLibraryRootFolderID())) + { + return items[0]->getUUID(); + } + } + } + + return LLUUID::null; +} + +void LLFloaterTexturePicker::commitIfImmediateSet() +{ + if (!mNoCopyTextureSelected && mCanApply) + { + commitCallback(LLTextureCtrl::TEXTURE_CHANGE); + } +} + +void LLFloaterTexturePicker::commitCallback(LLTextureCtrl::ETexturePickOp op) +{ + if (!mOnFloaterCommitCallback) + { + return; + } + LLUUID asset_id = mImageAssetID; + LLUUID inventory_id; + LLUUID tracking_id; + LLPickerSource mode = (LLPickerSource)mModeSelector->getValue().asInteger(); + + switch (mode) + { + case PICKER_INVENTORY: + { + LLFolderView* root_folder = mInventoryPanel->getRootFolder(); + if (root_folder && root_folder->getCurSelectedItem()) + { + LLFolderViewItem* last_selected = root_folder->getCurSelectedItem(); + LLFolderViewModelItemInventory* inv_view = static_cast(last_selected->getViewModelItem()); + + LLInventoryItem* itemp = gInventory.getItem(inv_view->getUUID()); + + if (mInventoryPickType == PICK_MATERIAL + && mImageAssetID == LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID + && itemp && itemp->getAssetUUID().isNull()) + { + inventory_id = inv_view->getUUID(); + } + else if (itemp && itemp->getAssetUUID() == mImageAssetID) + { + inventory_id = inv_view->getUUID(); + } + else + { + mode = PICKER_UNKNOWN; // source of id unknown + } + } + else + { + mode = PICKER_UNKNOWN; // source of id unknown + } + break; + } + case PICKER_LOCAL: + { + if (!mLocalScrollCtrl->getAllSelected().empty()) + { + LLSD data = mLocalScrollCtrl->getFirstSelected()->getValue(); + tracking_id = data["id"]; + S32 asset_type = data["type"].asInteger(); + + if (LLAssetType::AT_MATERIAL == asset_type) + { + asset_id = LLLocalGLTFMaterialMgr::getInstance()->getWorldID(tracking_id); + } + else + { + asset_id = LLLocalBitmapMgr::getInstance()->getWorldID(tracking_id); + } + } + else + { + asset_id = mImageAssetID; + mode = PICKER_UNKNOWN; // source of id unknown + } + break; + } + case PICKER_BAKE: + break; + default: + mode = PICKER_UNKNOWN; // source of id unknown + break; + } + + mOnFloaterCommitCallback(op, mode, asset_id, inventory_id, tracking_id); +} +void LLFloaterTexturePicker::commitCancel() +{ + if (!mNoCopyTextureSelected && mOnFloaterCommitCallback && mCanApply) + { + mOnFloaterCommitCallback(LLTextureCtrl::TEXTURE_CANCEL, PICKER_UNKNOWN, mOriginalImageAssetID, LLUUID::null, LLUUID::null); + } +} + +// static +void LLFloaterTexturePicker::onBtnSetToDefault(void* userdata) +{ + LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; + self->setCanApply(true, true); + if (self->mOwner) + { + self->setImageID( self->getDefaultImageAssetID() ); + } + self->commitIfImmediateSet(); +} + +// static +void LLFloaterTexturePicker::onBtnBlank(void* userdata) +{ + LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; + self->setCanApply(true, true); + self->setImageID( self->getBlankImageAssetID() ); + self->commitIfImmediateSet(); +} + + +// static +void LLFloaterTexturePicker::onBtnNone(void* userdata) +{ + LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; + self->setCanApply(true, true); + self->setImageID( LLUUID::null ); + self->commitIfImmediateSet(); +} + +/* +// static +void LLFloaterTexturePicker::onBtnRevert(void* userdata) +{ + LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; + self->setImageID( self->mOriginalImageAssetID ); + // TODO: Change this to tell the owner to cancel. It needs to be + // smart enough to restore multi-texture selections. + self->mOwner->onFloaterCommit(); + self->mViewModel->resetDirty(); +}*/ + +// static +void LLFloaterTexturePicker::onBtnCancel(void* userdata) +{ + LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; + self->setImageID( self->mOriginalImageAssetID ); + if (self->mOnFloaterCommitCallback) + { + self->mOnFloaterCommitCallback(LLTextureCtrl::TEXTURE_CANCEL, PICKER_UNKNOWN, self->mOriginalImageAssetID, LLUUID::null, LLUUID::null); + } + self->mViewModel->resetDirty(); + self->closeFloater(); +} + +// static +void LLFloaterTexturePicker::onBtnSelect(void* userdata) +{ + LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; + if (self->mOnFloaterCommitCallback) + { + self->commitCallback(LLTextureCtrl::TEXTURE_SELECT); + } + self->closeFloater(); +} + +void LLFloaterTexturePicker::onBtnPipette() +{ + bool pipette_active = getChild("Pipette")->getValue().asBoolean(); + pipette_active = !pipette_active; + if (pipette_active) + { + LLToolMgr::getInstance()->setTransientTool(LLToolPipette::getInstance()); + } + else + { + LLToolMgr::getInstance()->clearTransientTool(); + } +} + +void LLFloaterTexturePicker::onSelectionChange(const std::deque &items, bool user_action) +{ + if (items.size()) + { + LLFolderViewItem* first_item = items.front(); + LLInventoryItem* itemp = gInventory.getItem(static_cast(first_item->getViewModelItem())->getUUID()); + mNoCopyTextureSelected = false; + if (itemp) + { + if (!mTextureSelectedCallback.empty()) + { + mTextureSelectedCallback(itemp); + } + if (!itemp->getPermissions().allowCopyBy(gAgent.getID())) + { + mNoCopyTextureSelected = true; + } + setImageIDFromItem(itemp, false); + mViewModel->setDirty(); // *TODO: shouldn't we be using setValue() here? + + if(!mPreviewSettingChanged) + { + mCanPreview = mCanApplyImmediately && gSavedSettings.getBOOL("TextureLivePreview"); + } + else + { + mPreviewSettingChanged = false; + } + + if (user_action && mCanPreview) + { + // only commit intentional selections, not implicit ones + commitIfImmediateSet(); + } + } + } +} + +// static +void LLFloaterTexturePicker::onModeSelect(LLUICtrl* ctrl, void *userdata) +{ + LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; + self->changeMode(); +} + +// static +void LLFloaterTexturePicker::onBtnAdd(void* userdata) +{ + LLFloaterTexturePicker* self = (LLFloaterTexturePicker*)userdata; + + if (self->mInventoryPickType == PICK_TEXTURE_MATERIAL) + { + LLFilePickerReplyThread::startPicker(boost::bind(&onPickerCallback, _1, self->getHandle()), LLFilePicker::FFLOAD_MATERIAL_TEXTURE, true); + } + else if (self->mInventoryPickType == PICK_TEXTURE) + { + LLFilePickerReplyThread::startPicker(boost::bind(&onPickerCallback, _1, self->getHandle()), LLFilePicker::FFLOAD_IMAGE, true); + } + else if (self->mInventoryPickType == PICK_MATERIAL) + { + LLFilePickerReplyThread::startPicker(boost::bind(&onPickerCallback, _1, self->getHandle()), LLFilePicker::FFLOAD_MATERIAL, true); + } +} + +// static +void LLFloaterTexturePicker::onBtnRemove(void* userdata) +{ + LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; + std::vector selected_items = self->mLocalScrollCtrl->getAllSelected(); + + if (!selected_items.empty()) + { + + for(std::vector::iterator iter = selected_items.begin(); + iter != selected_items.end(); iter++) + { + LLScrollListItem* list_item = *iter; + if (list_item) + { + LLSD data = list_item->getValue(); + LLUUID tracking_id = data["id"]; + S32 asset_type = data["type"].asInteger(); + + if (LLAssetType::AT_MATERIAL == asset_type) + { + LLLocalGLTFMaterialMgr::getInstance()->delUnit(tracking_id); + } + else + { + LLLocalBitmapMgr::getInstance()->delUnit(tracking_id); + } + } + } + + self->getChild("l_rem_btn")->setEnabled(false); + self->getChild("l_upl_btn")->setEnabled(false); + self->refreshLocalList(); + } +} + +// static +void LLFloaterTexturePicker::onBtnUpload(void* userdata) +{ + LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; + std::vector selected_items = self->mLocalScrollCtrl->getAllSelected(); + + if (selected_items.empty()) + { + return; + } + + /* currently only allows uploading one by one, picks the first item from the selection list. (not the vector!) + in the future, it might be a good idea to check the vector size and if more than one units is selected - opt for multi-image upload. */ + + LLSD data = self->mLocalScrollCtrl->getFirstSelected()->getValue(); + LLUUID tracking_id = data["id"]; + S32 asset_type = data["type"].asInteger(); + + if (LLAssetType::AT_MATERIAL == asset_type) + { + std::string filename; + S32 index; + LLLocalGLTFMaterialMgr::getInstance()->getFilenameAndIndex(tracking_id, filename, index); + if (!filename.empty()) + { + LLMaterialEditor::loadMaterialFromFile(filename, index); + } + } + else + { + std::string filename = LLLocalBitmapMgr::getInstance()->getFilename(tracking_id); + if (!filename.empty()) + { + LLFloaterReg::showInstance("upload_image", LLSD(filename)); + } + } +} + +//static +void LLFloaterTexturePicker::onLocalScrollCommit(LLUICtrl* ctrl, void* userdata) +{ + LLFloaterTexturePicker* self = (LLFloaterTexturePicker*) userdata; + std::vector selected_items = self->mLocalScrollCtrl->getAllSelected(); + bool has_selection = !selected_items.empty(); + + self->getChild("l_rem_btn")->setEnabled(has_selection); + self->getChild("l_upl_btn")->setEnabled(has_selection && (selected_items.size() < 2)); + /* since multiple-localbitmap upload is not implemented, upl button gets disabled if more than one is selected. */ + + if (has_selection) + { + LLSD data = self->mLocalScrollCtrl->getFirstSelected()->getValue(); + LLUUID tracking_id = data["id"]; + S32 asset_type = data["type"].asInteger(); + LLUUID inworld_id; + + if (LLAssetType::AT_MATERIAL == asset_type) + { + inworld_id = LLLocalGLTFMaterialMgr::getInstance()->getWorldID(tracking_id); + } + else + { + inworld_id = LLLocalBitmapMgr::getInstance()->getWorldID(tracking_id); + } + + if (self->mSetImageAssetIDCallback) + { + self->mSetImageAssetIDCallback(inworld_id); + } + + if (self->childGetValue("apply_immediate_check").asBoolean()) + { + if (self->mOnFloaterCommitCallback) + { + self->mOnFloaterCommitCallback(LLTextureCtrl::TEXTURE_CHANGE, PICKER_LOCAL, inworld_id, LLUUID::null, tracking_id); + } + } + } +} + +// static +void LLFloaterTexturePicker::onApplyImmediateCheck(LLUICtrl* ctrl, void *user_data) +{ + LLFloaterTexturePicker* picker = (LLFloaterTexturePicker*)user_data; + + LLCheckBoxCtrl* check_box = (LLCheckBoxCtrl*)ctrl; + gSavedSettings.setBOOL("TextureLivePreview", check_box->get()); + + picker->commitIfImmediateSet(); +} + +//static +void LLFloaterTexturePicker::onBakeTextureSelect(LLUICtrl* ctrl, void *user_data) +{ + LLFloaterTexturePicker* self = (LLFloaterTexturePicker*)user_data; + LLComboBox* combo_box = (LLComboBox*)ctrl; + + S8 type = combo_box->getValue().asInteger(); + + LLUUID imageID = self->mDefaultImageAssetID; + if (type == 0) + { + imageID = IMG_USE_BAKED_HEAD; + } + else if (type == 1) + { + imageID = IMG_USE_BAKED_UPPER; + } + else if (type == 2) + { + imageID = IMG_USE_BAKED_LOWER; + } + else if (type == 3) + { + imageID = IMG_USE_BAKED_EYES; + } + else if (type == 4) + { + imageID = IMG_USE_BAKED_SKIRT; + } + else if (type == 5) + { + imageID = IMG_USE_BAKED_HAIR; + } + else if (type == 6) + { + imageID = IMG_USE_BAKED_LEFTARM; + } + else if (type == 7) + { + imageID = IMG_USE_BAKED_LEFTLEG; + } + else if (type == 8) + { + imageID = IMG_USE_BAKED_AUX1; + } + else if (type == 9) + { + imageID = IMG_USE_BAKED_AUX2; + } + else if (type == 10) + { + imageID = IMG_USE_BAKED_AUX3; + } + + self->setImageID(imageID); + self->mViewModel->setDirty(); // *TODO: shouldn't we be using setValue() here? + + if (!self->mPreviewSettingChanged) + { + self->mCanPreview = self->mCanApplyImmediately && gSavedSettings.getBOOL("TextureLivePreview"); + } + else + { + self->mPreviewSettingChanged = false; + } + + if (self->mCanPreview) + { + // only commit intentional selections, not implicit ones + self->commitIfImmediateSet(); + } +} + +void LLFloaterTexturePicker::setCanApply(bool can_preview, bool can_apply, bool inworld_image) +{ + mSelectBtn->setEnabled(can_apply); + getChildRef("preview_disabled").setVisible(!can_preview && inworld_image); + getChildRef("apply_immediate_check").setVisible(can_preview); + + mCanApply = can_apply; + mCanPreview = can_preview ? (mCanApplyImmediately && gSavedSettings.getBOOL("TextureLivePreview")) : false; + mPreviewSettingChanged = true; +} + +void LLFloaterTexturePicker::setMinDimentionsLimits(S32 min_dim) +{ + mMinDim = min_dim; + mLimitsSet = true; + + std::string formatted_dims = llformat("%dx%d", mMinDim, mMinDim); + mResolutionWarning->setTextArg("[MINTEXDIM]", formatted_dims); +} + +void LLFloaterTexturePicker::onFilterEdit(const std::string& search_string ) +{ + std::string upper_case_search_string = search_string; + LLStringUtil::toUpper(upper_case_search_string); + + if (upper_case_search_string.empty()) + { + if (mInventoryPanel->getFilterSubString().empty()) + { + // current filter and new filter empty, do nothing + return; + } + + mSavedFolderState.setApply(true); + mInventoryPanel->getRootFolder()->applyFunctorRecursively(mSavedFolderState); + // add folder with current item to list of previously opened folders + LLOpenFoldersWithSelection opener; + mInventoryPanel->getRootFolder()->applyFunctorRecursively(opener); + mInventoryPanel->getRootFolder()->scrollToShowSelection(); + + } + else if (mInventoryPanel->getFilterSubString().empty()) + { + // first letter in search term, save existing folder open state + if (!mInventoryPanel->getFilter().isNotDefault()) + { + mSavedFolderState.setApply(false); + mInventoryPanel->getRootFolder()->applyFunctorRecursively(mSavedFolderState); + } + } + + mInventoryPanel->setFilterSubString(search_string); +} + +void LLFloaterTexturePicker::changeMode() +{ + int index = mModeSelector->getValue().asInteger(); + + mDefaultBtn->setVisible(index == PICKER_INVENTORY); + mBlankBtn->setVisible(index == PICKER_INVENTORY); + mNoneBtn->setVisible(index == PICKER_INVENTORY); + mFilterEdit->setVisible(index == PICKER_INVENTORY); + mInventoryPanel->setVisible(index == PICKER_INVENTORY); + + getChild("l_add_btn")->setVisible(index == PICKER_LOCAL); + getChild("l_rem_btn")->setVisible(index == PICKER_LOCAL); + getChild("l_upl_btn")->setVisible(index == PICKER_LOCAL); + getChild("l_name_list")->setVisible(index == PICKER_LOCAL); + + getChild("l_bake_use_texture_combo_box")->setVisible(index == PICKER_BAKE); + getChild("hide_base_mesh_region")->setVisible(false);// index == 2); + + bool pipette_visible = (index == PICKER_INVENTORY) + && (mInventoryPickType != PICK_MATERIAL); + mPipetteBtn->setVisible(pipette_visible); + + if (index == PICKER_BAKE) + { + stopUsingPipette(); + + S8 val = -1; + + LLUUID imageID = mImageAssetID; + if (imageID == IMG_USE_BAKED_HEAD) + { + val = 0; + } + else if (imageID == IMG_USE_BAKED_UPPER) + { + val = 1; + } + else if (imageID == IMG_USE_BAKED_LOWER) + { + val = 2; + } + else if (imageID == IMG_USE_BAKED_EYES) + { + val = 3; + } + else if (imageID == IMG_USE_BAKED_SKIRT) + { + val = 4; + } + else if (imageID == IMG_USE_BAKED_HAIR) + { + val = 5; + } + else if (imageID == IMG_USE_BAKED_LEFTARM) + { + val = 6; + } + else if (imageID == IMG_USE_BAKED_LEFTLEG) + { + val = 7; + } + else if (imageID == IMG_USE_BAKED_AUX1) + { + val = 8; + } + else if (imageID == IMG_USE_BAKED_AUX2) + { + val = 9; + } + else if (imageID == IMG_USE_BAKED_AUX3) + { + val = 10; + } + + getChild("l_bake_use_texture_combo_box")->setSelectedByValue(val, true); + } +} + +void LLFloaterTexturePicker::refreshLocalList() +{ + mLocalScrollCtrl->clearRows(); + + if (mInventoryPickType == PICK_TEXTURE_MATERIAL) + { + LLLocalBitmapMgr::getInstance()->feedScrollList(mLocalScrollCtrl); + LLLocalGLTFMaterialMgr::getInstance()->feedScrollList(mLocalScrollCtrl); + } + else if (mInventoryPickType == PICK_TEXTURE) + { + LLLocalBitmapMgr::getInstance()->feedScrollList(mLocalScrollCtrl); + } + else if (mInventoryPickType == PICK_MATERIAL) + { + LLLocalGLTFMaterialMgr::getInstance()->feedScrollList(mLocalScrollCtrl); + } +} + +void LLFloaterTexturePicker::refreshInventoryFilter() +{ + U32 filter_types = 0x0; + + if (mInventoryPickType == PICK_TEXTURE_MATERIAL) + { + filter_types |= 0x1 << LLInventoryType::IT_TEXTURE; + filter_types |= 0x1 << LLInventoryType::IT_SNAPSHOT; + filter_types |= 0x1 << LLInventoryType::IT_MATERIAL; + } + else if (mInventoryPickType == PICK_TEXTURE) + { + filter_types |= 0x1 << LLInventoryType::IT_TEXTURE; + filter_types |= 0x1 << LLInventoryType::IT_SNAPSHOT; + } + else if (mInventoryPickType == PICK_MATERIAL) + { + filter_types |= 0x1 << LLInventoryType::IT_MATERIAL; + } + + mInventoryPanel->setFilterTypes(filter_types); +} + +void LLFloaterTexturePicker::setLocalTextureEnabled(bool enabled) +{ + mModeSelector->setEnabledByValue(1, enabled); +} + +void LLFloaterTexturePicker::setBakeTextureEnabled(bool enabled) +{ + bool changed = (enabled != mBakeTextureEnabled); + + mBakeTextureEnabled = enabled; + mModeSelector->setEnabledByValue(2, enabled); + + if (!mBakeTextureEnabled && (mModeSelector->getValue().asInteger() == 2)) + { + mModeSelector->selectByValue(0); + } + + if (changed && mBakeTextureEnabled && LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(mImageAssetID)) + { + if (mModeSelector->getValue().asInteger() != 2) + { + mModeSelector->selectByValue(2); + } + } + onModeSelect(0, this); +} + +void LLFloaterTexturePicker::setInventoryPickType(EPickInventoryType type) +{ + mInventoryPickType = type; + refreshLocalList(); + refreshInventoryFilter(); + + if (mInventoryPickType == PICK_MATERIAL) + { + getChild("Pipette")->setVisible(false); + } + else + { + S32 index = mModeSelector->getValue().asInteger(); + getChild("Pipette")->setVisible(index == 0); + } + + if (!mLabel.empty()) + { + std::string pick = getString("pick title"); + + setTitle(pick + mLabel); + } + else if(mInventoryPickType == PICK_MATERIAL) + { + setTitle(getString("pick_material")); + } + else + { + setTitle(getString("pick_texture")); + } + + // refresh selection + if (!mImageAssetID.isNull() || mInventoryPickType == PICK_MATERIAL) + { + mInventoryPanel->setSelection(findItemID(mImageAssetID, false), TAKE_FOCUS_NO); + } +} + +void LLFloaterTexturePicker::setImmediateFilterPermMask(PermissionMask mask) +{ + mImmediateFilterPermMask = mask; + mInventoryPanel->setFilterPermMask(mask); +} + +void LLFloaterTexturePicker::onPickerCallback(const std::vector& filenames, LLHandle handle) +{ + std::vector::const_iterator iter = filenames.begin(); + while (iter != filenames.end()) + { + if (!iter->empty()) + { + std::string temp_exten = gDirUtilp->getExtension(*iter); + if (temp_exten == "gltf" || temp_exten == "glb") + { + LLLocalGLTFMaterialMgr::getInstance()->addUnit(*iter); + } + else + { + LLLocalBitmapMgr::getInstance()->addUnit(*iter); + } + } + iter++; + } + + // Todo: this should referesh all pickers, not just a current one + if (!handle.isDead()) + { + LLFloaterTexturePicker* self = (LLFloaterTexturePicker*)handle.get(); + self->mLocalScrollCtrl->clearRows(); + + if (self->mInventoryPickType == PICK_TEXTURE_MATERIAL) + { + LLLocalBitmapMgr::getInstance()->feedScrollList(self->mLocalScrollCtrl); + LLLocalGLTFMaterialMgr::getInstance()->feedScrollList(self->mLocalScrollCtrl); + } + else if (self->mInventoryPickType == PICK_TEXTURE) + { + LLLocalBitmapMgr::getInstance()->feedScrollList(self->mLocalScrollCtrl); + } + else if (self->mInventoryPickType == PICK_MATERIAL) + { + LLLocalGLTFMaterialMgr::getInstance()->feedScrollList(self->mLocalScrollCtrl); + } + } +} + +void LLFloaterTexturePicker::onTextureSelect( const LLTextureEntry& te ) +{ + LLUUID inventory_item_id = findItemID(te.getID(), true); + if (inventory_item_id.notNull()) + { + LLToolPipette::getInstance()->setResult(true, ""); + if (mInventoryPickType == PICK_MATERIAL) + { + // tes have no data about material ids + // Plus gltf materials are layered with overrides, + // which mean that end result might have no id. + LL_WARNS() << "tes have no data about material ids" << LL_ENDL; + } + else + { + setImageID(te.getID()); + } + + mNoCopyTextureSelected = false; + LLInventoryItem* itemp = gInventory.getItem(inventory_item_id); + + if (itemp && !itemp->getPermissions().allowCopyBy(gAgent.getID())) + { + // no copy texture + mNoCopyTextureSelected = true; + } + + commitIfImmediateSet(); + } + else + { + LLToolPipette::getInstance()->setResult(false, LLTrans::getString("InventoryNoTexture")); + } +} + +/////////////////////////////////////////////////////////////////////// +// LLTextureCtrl + +static LLDefaultChildRegistry::Register r("texture_picker"); + +LLTextureCtrl::LLTextureCtrl(const LLTextureCtrl::Params& p) +: LLUICtrl(p), + mDragCallback(NULL), + mDropCallback(NULL), + mOnCancelCallback(NULL), + mOnCloseCallback(NULL), + mOnSelectCallback(NULL), + mBorderColor( p.border_color() ), + mAllowNoTexture( p.allow_no_texture ), + mAllowLocalTexture( true ), + mImmediateFilterPermMask( PERM_NONE ), + mCanApplyImmediately( false ), + mNeedsRawImageData( false ), + mValid( true ), + mShowLoadingPlaceholder( true ), + mOpenTexPreview(false), + mBakeTextureEnabled(true), + mInventoryPickType(PICK_TEXTURE), + mImageAssetID(p.image_id), + mDefaultImageAssetID(p.default_image_id), + mDefaultImageName(p.default_image_name), + mFallbackImage(p.fallback_image) +{ + + // Default of defaults is white image for diff tex + // + setBlankImageAssetID(IMG_WHITE); + + setAllowNoTexture(p.allow_no_texture); + setCanApplyImmediately(p.can_apply_immediately); + mCommitOnSelection = !p.no_commit_on_selection; + + LLTextBox::Params params(p.caption_text); + params.name(p.label); + params.rect(LLRect( 0, BTN_HEIGHT_SMALL, getRect().getWidth(), 0 )); + params.initial_value(p.label()); + params.follows.flags(FOLLOWS_LEFT | FOLLOWS_RIGHT | FOLLOWS_BOTTOM); + mCaption = LLUICtrlFactory::create (params); + addChild( mCaption ); + + S32 image_top = getRect().getHeight(); + S32 image_bottom = BTN_HEIGHT_SMALL; + S32 image_middle = (image_top + image_bottom) / 2; + S32 line_height = LLFontGL::getFontSansSerifSmall()->getLineHeight(); + + LLTextBox::Params tentative_label_p(p.multiselect_text); + tentative_label_p.name("Multiple"); + tentative_label_p.rect(LLRect (0, image_middle + line_height / 2, getRect().getWidth(), image_middle - line_height / 2 )); + tentative_label_p.follows.flags(FOLLOWS_ALL); + mTentativeLabel = LLUICtrlFactory::create (tentative_label_p); + + // It is no longer possible to associate a style with a textbox, so it has to be done in this fashion + LLStyle::Params style_params; + style_params.color = LLColor4::white; + + mTentativeLabel->setText(LLTrans::getString("multiple_textures"), style_params); + mTentativeLabel->setHAlign(LLFontGL::HCENTER); + addChild( mTentativeLabel ); + + LLRect border_rect = getLocalRect(); + border_rect.mBottom += BTN_HEIGHT_SMALL; + LLViewBorder::Params vbparams(p.border); + vbparams.name("border"); + vbparams.rect(border_rect); + mBorder = LLUICtrlFactory::create (vbparams); + addChild(mBorder); + + mLoadingPlaceholderString = LLTrans::getString("texture_loading"); +} + +LLTextureCtrl::~LLTextureCtrl() +{ + closeDependentFloater(); +} + +void LLTextureCtrl::setShowLoadingPlaceholder(bool showLoadingPlaceholder) +{ + mShowLoadingPlaceholder = showLoadingPlaceholder; +} + +void LLTextureCtrl::setCaption(const std::string& caption) +{ + mCaption->setText( caption ); +} + +void LLTextureCtrl::setCanApplyImmediately(bool b) +{ + mCanApplyImmediately = b; + LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterHandle.get(); + if( floaterp ) + { + floaterp->setCanApplyImmediately(b); + } +} + +void LLTextureCtrl::setCanApply(bool can_preview, bool can_apply) +{ + LLFloaterTexturePicker* floaterp = dynamic_cast(mFloaterHandle.get()); + if( floaterp ) + { + floaterp->setCanApply(can_preview, can_apply); + } +} + +void LLTextureCtrl::setImmediateFilterPermMask(PermissionMask mask) +{ + mImmediateFilterPermMask = mask; + + LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterHandle.get(); + if (floaterp) + { + floaterp->setImmediateFilterPermMask(mask); + } +} + +void LLTextureCtrl::setFilterPermissionMasks(PermissionMask mask) +{ + setImmediateFilterPermMask(mask); + setDnDFilterPermMask(mask); +} + +void LLTextureCtrl::setVisible( bool visible ) +{ + if( !visible ) + { + closeDependentFloater(); + } + LLUICtrl::setVisible( visible ); +} + +void LLTextureCtrl::setEnabled( bool enabled ) +{ + LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterHandle.get(); + if( floaterp ) + { + floaterp->setActive(enabled); + } + if( enabled ) + { + std::string tooltip; + if (floaterp) tooltip = floaterp->getString("choose_picture"); + setToolTip( tooltip ); + } + else + { + setToolTip( std::string() ); + // *TODO: would be better to keep floater open and show + // disabled state. + closeDependentFloater(); + } + + mCaption->setEnabled( enabled ); + + LLView::setEnabled( enabled ); +} + +void LLTextureCtrl::setValid(bool valid ) +{ + mValid = valid; + if (!valid) + { + LLFloaterTexturePicker* pickerp = (LLFloaterTexturePicker*)mFloaterHandle.get(); + if (pickerp) + { + pickerp->setActive(false); + } + } +} + + +// virtual +void LLTextureCtrl::clear() +{ + setImageAssetID(LLUUID::null); +} + +void LLTextureCtrl::setLabel(const std::string& label) +{ + mLabel = label; + mCaption->setText(label); +} + +void LLTextureCtrl::showPicker(bool take_focus) +{ + // show hourglass cursor when loading inventory window + // because inventory construction is slooow + getWindow()->setCursor(UI_CURSOR_WAIT); + LLFloater* floaterp = mFloaterHandle.get(); + + // Show the dialog + if( floaterp ) + { + floaterp->openFloater(); + } + else + { + floaterp = new LLFloaterTexturePicker( + this, + getImageAssetID(), + getDefaultImageAssetID(), + getBlankImageAssetID(), + getTentative(), + getAllowNoTexture(), + mLabel, + mImmediateFilterPermMask, + mDnDFilterPermMask, + mCanApplyImmediately, + mFallbackImage, + mInventoryPickType); + mFloaterHandle = floaterp->getHandle(); + + LLFloaterTexturePicker* texture_floaterp = dynamic_cast(floaterp); + if (texture_floaterp && mOnTextureSelectedCallback) + { + texture_floaterp->setTextureSelectedCallback(mOnTextureSelectedCallback); + } + if (texture_floaterp && mOnCloseCallback) + { + texture_floaterp->setOnFloaterCloseCallback(boost::bind(&LLTextureCtrl::onFloaterClose, this)); + } + if (texture_floaterp) + { + texture_floaterp->setOnFloaterCommitCallback(boost::bind(&LLTextureCtrl::onFloaterCommit, this, _1, _2, _3, _4, _5)); + } + if (texture_floaterp) + { + texture_floaterp->setSetImageAssetIDCallback(boost::bind(&LLTextureCtrl::setImageAssetID, this, _1)); + + texture_floaterp->setBakeTextureEnabled(mBakeTextureEnabled); + } + + LLFloater* root_floater = gFloaterView->getParentFloater(this); + if (root_floater) + root_floater->addDependentFloater(floaterp); + floaterp->openFloater(); + } + + LLFloaterTexturePicker* picker_floater = dynamic_cast(floaterp); + if (picker_floater) + { + picker_floater->setLocalTextureEnabled(mAllowLocalTexture); + } + + if (take_focus) + { + floaterp->setFocus(true); + } +} + + +void LLTextureCtrl::closeDependentFloater() +{ + LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterHandle.get(); + if( floaterp && floaterp->isInVisibleChain()) + { + floaterp->setOwner(NULL); + floaterp->setVisible(false); + floaterp->closeFloater(); + } +} + +// Allow us to download textures quickly when floater is shown +class LLTextureFetchDescendentsObserver : public LLInventoryFetchDescendentsObserver +{ +public: + virtual void done() + { + // We need to find textures in all folders, so get the main + // background download going. + LLInventoryModelBackgroundFetch::instance().start(); + gInventory.removeObserver(this); + delete this; + } +}; + +bool LLTextureCtrl::handleHover(S32 x, S32 y, MASK mask) +{ + getWindow()->setCursor(mBorder->parentPointInView(x,y) ? UI_CURSOR_HAND : UI_CURSOR_ARROW); + return true; +} + + +bool LLTextureCtrl::handleMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = LLUICtrl::handleMouseDown( x, y , mask ); + + if (!handled && mBorder->parentPointInView(x, y)) + { + if (!mOpenTexPreview) + { + showPicker(false); + if (mInventoryPickType == PICK_MATERIAL) + { + //grab materials first... + LLInventoryModelBackgroundFetch::instance().start(gInventory.findCategoryUUIDForType(LLFolderType::FT_MATERIAL)); + } + else + { + //grab textures first... + LLInventoryModelBackgroundFetch::instance().start(gInventory.findCategoryUUIDForType(LLFolderType::FT_TEXTURE)); + } + //...then start full inventory fetch. + if (!LLInventoryModelBackgroundFetch::instance().inventoryFetchStarted()) + { + LLInventoryModelBackgroundFetch::instance().start(); + } + handled = true; + } + else + { + if (getImageAssetID().notNull()) + { + LLPreviewTexture* preview_texture = LLFloaterReg::showTypedInstance("preview_texture", getValue()); + if (preview_texture && !preview_texture->isDependent()) + { + LLFloater* root_floater = gFloaterView->getParentFloater(this); + if (root_floater) + { + root_floater->addDependentFloater(preview_texture); + preview_texture->hideCtrlButtons(); + } + } + } + } + } + + return handled; +} + +void LLTextureCtrl::onFloaterClose() +{ + LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterHandle.get(); + + if (floaterp) + { + if (mOnCloseCallback) + { + mOnCloseCallback(this,LLSD()); + } + floaterp->setOwner(NULL); + } + + mFloaterHandle.markDead(); +} + +void LLTextureCtrl::onFloaterCommit(ETexturePickOp op, LLPickerSource source, const LLUUID& asset_id, const LLUUID& inv_id, const LLUUID& tracking_id) +{ + LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterHandle.get(); + + if( floaterp && getEnabled()) + { + if (op == TEXTURE_CANCEL) + mViewModel->resetDirty(); + // If the "no_commit_on_selection" parameter is set + // we get dirty only when user presses OK in the picker + // (i.e. op == TEXTURE_SELECT) or texture changes via DnD. + else if (mCommitOnSelection || op == TEXTURE_SELECT) + mViewModel->setDirty(); // *TODO: shouldn't we be using setValue() here? + + if(floaterp->isDirty() || asset_id.notNull()) // mModelView->setDirty does not work. + { + setTentative( false ); + + switch(source) + { + case PICKER_INVENTORY: + mImageItemID = inv_id; + mImageAssetID = asset_id; + mLocalTrackingID.setNull(); + break; + case PICKER_BAKE: + mImageItemID = LLUUID::null; + mImageAssetID = asset_id; + mLocalTrackingID.setNull(); + break; + case PICKER_LOCAL: + mImageItemID = LLUUID::null; + mImageAssetID = asset_id; + mLocalTrackingID = tracking_id; + break; + case PICKER_UNKNOWN: + default: + mImageItemID = floaterp->findItemID(asset_id, false); + mImageAssetID = asset_id; + mLocalTrackingID.setNull(); + break; + } + + LL_DEBUGS() << "mImageAssetID: " << mImageAssetID << ", mImageItemID: " << mImageItemID << LL_ENDL; + + if (op == TEXTURE_SELECT && mOnSelectCallback) + { + mOnSelectCallback(this, LLSD()); + } + else if (op == TEXTURE_CANCEL && mOnCancelCallback) + { + mOnCancelCallback( this, LLSD() ); + } + else + { + // If the "no_commit_on_selection" parameter is set + // we commit only when user presses OK in the picker + // (i.e. op == TEXTURE_SELECT) or texture changes via DnD. + if (mCommitOnSelection || op == TEXTURE_SELECT) + { + onCommit(); + } + } + } + } +} + +void LLTextureCtrl::setOnTextureSelectedCallback(texture_selected_callback cb) +{ + mOnTextureSelectedCallback = cb; + LLFloaterTexturePicker* floaterp = dynamic_cast(mFloaterHandle.get()); + if (floaterp) + { + floaterp->setTextureSelectedCallback(cb); + } +} + +void LLTextureCtrl::setImageAssetName(const std::string& name) +{ + LLPointer imagep = LLUI::getUIImage(name); + if(imagep) + { + LLViewerFetchedTexture* pTexture = dynamic_cast(imagep->getImage().get()); + if(pTexture) + { + LLUUID id = pTexture->getID(); + setImageAssetID(id); + } + } +} + +void LLTextureCtrl::setImageAssetID( const LLUUID& asset_id ) +{ + if( mImageAssetID != asset_id ) + { + mImageItemID.setNull(); + mImageAssetID = asset_id; + mLocalTrackingID.setNull(); + LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterHandle.get(); + if( floaterp && getEnabled() ) + { + floaterp->setImageID( asset_id ); + floaterp->resetDirty(); + } + } +} + +void LLTextureCtrl::setBakeTextureEnabled(bool enabled) +{ + mBakeTextureEnabled = enabled; + LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterHandle.get(); + if (floaterp) + { + floaterp->setBakeTextureEnabled(enabled); + } +} + +void LLTextureCtrl::setInventoryPickType(EPickInventoryType type) +{ + mInventoryPickType = type; + LLFloaterTexturePicker* floaterp = (LLFloaterTexturePicker*)mFloaterHandle.get(); + if (floaterp) + { + floaterp->setInventoryPickType(type); + } +} + +bool LLTextureCtrl::handleDragAndDrop(S32 x, S32 y, MASK mask, + bool drop, EDragAndDropType cargo_type, void *cargo_data, + EAcceptance *accept, + std::string& tooltip_msg) +{ + bool handled = false; + + // this downcast may be invalid - but if the second test below + // returns true, then the cast was valid, and we can perform + // the third test without problems. + LLInventoryItem* item = (LLInventoryItem*)cargo_data; + + bool is_mesh = cargo_type == DAD_MESH; + bool is_texture = cargo_type == DAD_TEXTURE; + bool is_material = cargo_type == DAD_MATERIAL; + + bool allow_dnd = false; + if (mInventoryPickType == PICK_MATERIAL) + { + allow_dnd = is_material; + } + else if (mInventoryPickType == PICK_TEXTURE) + { + allow_dnd = is_texture || is_mesh; + } + else + { + allow_dnd = is_texture || is_mesh || is_material; + } + + if (getEnabled() && allow_dnd && allowDrop(item, cargo_type, tooltip_msg)) + { + if (drop) + { + if(doDrop(item)) + { + if (!mCommitOnSelection) + mViewModel->setDirty(); + + // This removes the 'Multiple' overlay, since + // there is now only one texture selected. + setTentative( false ); + onCommit(); + } + } + + *accept = ACCEPT_YES_SINGLE; + } + else + { + *accept = ACCEPT_NO; + } + + handled = true; + LL_DEBUGS("UserInput") << "dragAndDrop handled by LLTextureCtrl " << getName() << LL_ENDL; + + return handled; +} + +void LLTextureCtrl::draw() +{ + mBorder->setKeyboardFocusHighlight(hasFocus()); + + if (!mValid) + { + mTexturep = NULL; + } + else if (!mImageAssetID.isNull()) + { + LLPointer texture = NULL; + + if (LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(mImageAssetID)) + { + LLViewerObject* obj = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); + if (obj) + { + LLViewerTexture* viewerTexture = obj->getBakedTextureForMagicId(mImageAssetID); + texture = viewerTexture ? dynamic_cast(viewerTexture) : NULL; + } + + } + + if (texture.isNull()) + { + if (mInventoryPickType == PICK_MATERIAL) + { + LLPointer material = gGLTFMaterialList.getMaterial(mImageAssetID); + if (material) + { + texture = material->getUITexture(); + } + } + else + { + texture = LLViewerTextureManager::getFetchedTexture(mImageAssetID, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + texture->setBoostLevel(LLGLTexture::BOOST_PREVIEW); + texture->forceToSaveRawImage(0); + } + } + + mTexturep = texture; + } + else//mImageAssetID == LLUUID::null + { + mTexturep = NULL; + } + + // Border + LLRect border( 0, getRect().getHeight(), getRect().getWidth(), BTN_HEIGHT_SMALL ); + gl_rect_2d( border, mBorderColor.get(), false ); + + // Interior + LLRect interior = border; + interior.stretch( -1 ); + + // If we're in a focused floater, don't apply the floater's alpha to the texture (STORM-677). + const F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); + if( mTexturep ) + { + if( mTexturep->getComponents() == 4 ) + { + gl_rect_2d_checkerboard( interior, alpha ); + } + + gl_draw_scaled_image( interior.mLeft, interior.mBottom, interior.getWidth(), interior.getHeight(), mTexturep, UI_VERTEX_COLOR % alpha); + mTexturep->addTextureStats( (F32)(interior.getWidth() * interior.getHeight()) ); + } + else if (!mFallbackImage.isNull()) + { + mFallbackImage->draw(interior, UI_VERTEX_COLOR % alpha); + } + else + { + gl_rect_2d( interior, LLColor4::grey % alpha, true ); + + // Draw X + gl_draw_x( interior, LLColor4::black ); + } + + mTentativeLabel->setVisible( getTentative() ); + + // Show "Loading..." string on the top left corner while this texture is loading. + // Using the discard level, do not show the string if the texture is almost but not + // fully loaded. + if (mTexturep.notNull() && + !mTexturep->isFullyLoaded() && + mShowLoadingPlaceholder) + { + U32 v_offset = 25; + LLFontGL* font = LLFontGL::getFontSansSerif(); + + // Don't show as loaded if the texture is almost fully loaded (i.e. discard1) unless god + if ((mTexturep->getDiscardLevel() > 1) || gAgent.isGodlike()) + { + font->renderUTF8( + mLoadingPlaceholderString, + 0, + llfloor(interior.mLeft+3), + llfloor(interior.mTop-v_offset), + LLColor4::white, + LLFontGL::LEFT, + LLFontGL::BASELINE, + LLFontGL::DROP_SHADOW); + } + + // Optionally show more detailed information. + if (gSavedSettings.getBOOL("DebugAvatarRezTime")) + { + LLFontGL* font = LLFontGL::getFontSansSerif(); + std::string tdesc; + // Show what % the texture has loaded (0 to 100%, 100 is highest), and what level of detail (5 to 0, 0 is best). + + v_offset += 12; + tdesc = llformat(" PK : %d%%", U32(mTexturep->getDownloadProgress()*100.0)); + font->renderUTF8(tdesc, 0, llfloor(interior.mLeft+3), llfloor(interior.mTop-v_offset), + LLColor4::white, LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::DROP_SHADOW); + + v_offset += 12; + tdesc = llformat(" LVL: %d", mTexturep->getDiscardLevel()); + font->renderUTF8(tdesc, 0, llfloor(interior.mLeft+3), llfloor(interior.mTop-v_offset), + LLColor4::white, LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::DROP_SHADOW); + + v_offset += 12; + tdesc = llformat(" ID : %s...", (mImageAssetID.asString().substr(0,7)).c_str()); + font->renderUTF8(tdesc, 0, llfloor(interior.mLeft+3), llfloor(interior.mTop-v_offset), + LLColor4::white, LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::DROP_SHADOW); + } + } + + LLUICtrl::draw(); +} + +bool LLTextureCtrl::allowDrop(LLInventoryItem* item, EDragAndDropType cargo_type, std::string& tooltip_msg) +{ + bool copy = item->getPermissions().allowCopyBy(gAgent.getID()); + bool mod = item->getPermissions().allowModifyBy(gAgent.getID()); + bool xfer = item->getPermissions().allowOperationBy(PERM_TRANSFER, + gAgent.getID()); + + PermissionMask item_perm_mask = 0; + if (copy) item_perm_mask |= PERM_COPY; + if (mod) item_perm_mask |= PERM_MODIFY; + if (xfer) item_perm_mask |= PERM_TRANSFER; + + PermissionMask filter_perm_mask = mImmediateFilterPermMask; + if ( (item_perm_mask & filter_perm_mask) == filter_perm_mask ) + { + if(mDragCallback) + { + return mDragCallback(this, item); + } + else + { + return true; + } + } + else + { + PermissionMask mask = PERM_COPY | PERM_TRANSFER; + if ((filter_perm_mask & mask) == mask + && cargo_type == DAD_TEXTURE) + { + tooltip_msg.assign(LLTrans::getString("TooltipTextureRestrictedDrop")); + } + return false; + } +} + +bool LLTextureCtrl::doDrop(LLInventoryItem* item) +{ + // call the callback if it exists. + if(mDropCallback) + { + // if it returns true, we return true, and therefore the + // commit is called above. + return mDropCallback(this, item); + } + + // no callback installed, so just set the image ids and carry on. + LLUUID asset_id = item->getAssetUUID(); + + if (mInventoryPickType == PICK_MATERIAL && asset_id.isNull()) + { + // If an inventory material has a null asset, consider it a valid blank material(gltf) + asset_id = LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID; + } + + setImageAssetID(asset_id); + mImageItemID = item->getUUID(); + return true; +} + +bool LLTextureCtrl::handleUnicodeCharHere(llwchar uni_char) +{ + if( ' ' == uni_char ) + { + showPicker(true); + return true; + } + return LLUICtrl::handleUnicodeCharHere(uni_char); +} + +void LLTextureCtrl::setValue( const LLSD& value ) +{ + setImageAssetID(value.asUUID()); +} + +LLSD LLTextureCtrl::getValue() const +{ + return LLSD(getImageAssetID()); +} + + + + + diff --git a/indra/newview/lltexturectrl.h b/indra/newview/lltexturectrl.h index 2f82a28af4..daa879ca62 100644 --- a/indra/newview/lltexturectrl.h +++ b/indra/newview/lltexturectrl.h @@ -1,445 +1,445 @@ -/** - * @file lltexturectrl.h - * @author Richard Nelson, James Cook - * @brief LLTextureCtrl class header file including related functions - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLTEXTURECTRL_H -#define LL_LLTEXTURECTRL_H - -#include "llcoord.h" -#include "llfiltereditor.h" -#include "llfloater.h" -#include "llfolderview.h" -#include "lllocalbitmaps.h" -#include "llstring.h" -#include "lluictrl.h" -#include "llpermissionsflags.h" -#include "lltextbox.h" // for params -#include "llviewerinventory.h" -#include "llviewborder.h" // for params -#include "llviewerobject.h" -#include "llviewertexture.h" -#include "llwindow.h" - -class LLComboBox; -class LLFloaterTexturePicker; -class LLInventoryItem; -class LLViewerFetchedTexture; -class LLFetchedGLTFMaterial; - -// used for setting drag & drop callbacks. -typedef boost::function drag_n_drop_callback; -typedef boost::function texture_selected_callback; - -// Helper functions for UI that work with picker -bool get_is_predefined_texture(LLUUID asset_id); - -// texture picker works by asset ids since objects normaly do -// not retain inventory ids as result these functions are looking -// for textures in inventory by asset ids -// This search can be performance unfriendly and doesn't warranty -// that the texture is original source of asset -LLUUID get_copy_free_item_by_asset_id(LLUUID image_id, bool no_trans_perm = false); -bool get_can_copy_texture(LLUUID image_id); - -enum LLPickerSource -{ - PICKER_INVENTORY, - PICKER_LOCAL, - PICKER_BAKE, - PICKER_UNKNOWN, // on cancel, default ids -}; - -typedef enum e_pick_inventory_type -{ - PICK_TEXTURE_MATERIAL = 0, - PICK_TEXTURE = 1, - PICK_MATERIAL = 2, -} EPickInventoryType; - -////////////////////////////////////////////////////////////////////////////////////////// -// LLTextureCtrl - - -class LLTextureCtrl -: public LLUICtrl -{ -public: - typedef enum e_texture_pick_op - { - TEXTURE_CHANGE, - TEXTURE_SELECT, - TEXTURE_CANCEL - } ETexturePickOp; - -public: - struct Params : public LLInitParam::Block - { - Optional image_id; - Optional default_image_id; - Optional default_image_name; - Optional allow_no_texture; - Optional can_apply_immediately; - Optional no_commit_on_selection; // alternative mode: commit occurs and the widget gets dirty - // only on DnD or when OK is pressed in the picker - Optional label_width; - Optional border_color; - Optional fallback_image; - - Optional multiselect_text, - caption_text; - - Optional border; - - Params() - : image_id("image"), - default_image_id("default_image_id"), - default_image_name("default_image_name"), - allow_no_texture("allow_no_texture", false), - can_apply_immediately("can_apply_immediately"), - no_commit_on_selection("no_commit_on_selection", false), - label_width("label_width", -1), - border_color("border_color"), - fallback_image("fallback_image"), - multiselect_text("multiselect_text"), - caption_text("caption_text"), - border("border") - {} - }; -protected: - LLTextureCtrl(const Params&); - friend class LLUICtrlFactory; -public: - virtual ~LLTextureCtrl(); - - // LLView interface - - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, - bool drop, EDragAndDropType cargo_type, void *cargo_data, - EAcceptance *accept, - std::string& tooltip_msg); - virtual bool handleHover(S32 x, S32 y, MASK mask); - virtual bool handleUnicodeCharHere(llwchar uni_char); - - virtual void draw(); - virtual void setVisible( bool visible ); - virtual void setEnabled( bool enabled ); - - void setValid(bool valid); - - // LLUICtrl interface - virtual void clear(); - - // Takes a UUID, wraps get/setImageAssetID - virtual void setValue(const LLSD& value); - virtual LLSD getValue() const; - - // LLTextureCtrl interface - void showPicker(bool take_focus); - bool isPickerShown() { return !mFloaterHandle.isDead(); } - void setLabel(const std::string& label); - void setLabelWidth(S32 label_width) {mLabelWidth =label_width;} - const std::string& getLabel() const { return mLabel; } - - void setAllowNoTexture( bool b ) { mAllowNoTexture = b; } - bool getAllowNoTexture() const { return mAllowNoTexture; } - - void setAllowLocalTexture(bool b) { mAllowLocalTexture = b; } - bool getAllowLocalTexture() const { return mAllowLocalTexture; } - - const LLUUID& getImageItemID() { return mImageItemID; } - - virtual void setImageAssetName(const std::string& name); - - void setImageAssetID(const LLUUID &image_asset_id); - const LLUUID& getImageAssetID() const { return mImageAssetID; } - - void setDefaultImageAssetID( const LLUUID& id ) { mDefaultImageAssetID = id; } - const LLUUID& getDefaultImageAssetID() const { return mDefaultImageAssetID; } - - const std::string& getDefaultImageName() const { return mDefaultImageName; } - - void setBlankImageAssetID( const LLUUID& id ) { mBlankImageAssetID = id; } - const LLUUID& getBlankImageAssetID() const { return mBlankImageAssetID; } - - void setOpenTexPreview(bool open_preview) { mOpenTexPreview = open_preview; } - - void setCaption(const std::string& caption); - void setCanApplyImmediately(bool b); - - void setCanApply(bool can_preview, bool can_apply); - - void setImmediateFilterPermMask(PermissionMask mask); - void setDnDFilterPermMask(PermissionMask mask) - { mDnDFilterPermMask = mask; } - PermissionMask getImmediateFilterPermMask() { return mImmediateFilterPermMask; } - void setFilterPermissionMasks(PermissionMask mask); - - void closeDependentFloater(); - - void onFloaterClose(); - void onFloaterCommit(ETexturePickOp op, - LLPickerSource source, - const LLUUID& local_id, - const LLUUID& inv_id, - const LLUUID& tracking_id); - - // This call is returned when a drag is detected. Your callback - // should return true if the drag is acceptable. - void setDragCallback(drag_n_drop_callback cb) { mDragCallback = cb; } - - // This callback is called when the drop happens. Return true if - // the drop happened - resulting in an on commit callback, but not - // necessariliy any other change. - void setDropCallback(drag_n_drop_callback cb) { mDropCallback = cb; } - - void setOnCancelCallback(commit_callback_t cb) { mOnCancelCallback = cb; } - void setOnCloseCallback(commit_callback_t cb) { mOnCloseCallback = cb; } - void setOnSelectCallback(commit_callback_t cb) { mOnSelectCallback = cb; } - - /* - * callback for changing texture selection in inventory list of texture floater - */ - void setOnTextureSelectedCallback(texture_selected_callback cb); - - void setShowLoadingPlaceholder(bool showLoadingPlaceholder); - - LLViewerFetchedTexture* getTexture() { return mTexturep; } - - void setBakeTextureEnabled(bool enabled); - bool getBakeTextureEnabled() const { return mBakeTextureEnabled; } - - void setInventoryPickType(EPickInventoryType type); - EPickInventoryType getInventoryPickType() { return mInventoryPickType; }; - - bool isImageLocal() { return mLocalTrackingID.notNull(); } - LLUUID getLocalTrackingID() { return mLocalTrackingID; } - -private: - bool allowDrop(LLInventoryItem* item, EDragAndDropType cargo_type, std::string& tooltip_msg); - bool doDrop(LLInventoryItem* item); - -private: - drag_n_drop_callback mDragCallback; - drag_n_drop_callback mDropCallback; - commit_callback_t mOnCancelCallback; - commit_callback_t mOnSelectCallback; - commit_callback_t mOnCloseCallback; - texture_selected_callback mOnTextureSelectedCallback; - LLPointer mTexturep; - LLUIColor mBorderColor; - LLUUID mImageItemID; - LLUUID mImageAssetID; - LLUUID mDefaultImageAssetID; - LLUUID mBlankImageAssetID; - LLUUID mLocalTrackingID; - LLUIImagePtr mFallbackImage; - std::string mDefaultImageName; - LLHandle mFloaterHandle; - LLTextBox* mTentativeLabel; - LLTextBox* mCaption; - std::string mLabel; - bool mAllowNoTexture; // If true, the user can select "none" as an option - bool mAllowLocalTexture; - PermissionMask mImmediateFilterPermMask; - PermissionMask mDnDFilterPermMask; - bool mCanApplyImmediately; - bool mCommitOnSelection; - bool mNeedsRawImageData; - LLViewBorder* mBorder; - bool mValid; - bool mShowLoadingPlaceholder; - std::string mLoadingPlaceholderString; - S32 mLabelWidth; - bool mOpenTexPreview; - bool mBakeTextureEnabled; - EPickInventoryType mInventoryPickType; -}; - -////////////////////////////////////////////////////////////////////////////////////////// -// LLFloaterTexturePicker -typedef boost::function floater_commit_callback; -typedef boost::function floater_close_callback; -typedef boost::function set_image_asset_id_callback; -typedef boost::function texture)> set_on_update_image_stats_callback; - -class LLFloaterTexturePicker : public LLFloater -{ -public: - LLFloaterTexturePicker( - LLView* owner, - LLUUID image_asset_id, - LLUUID default_image_asset_id, - LLUUID blank_image_asset_id, - bool tentative, - bool allow_no_texture, - const std::string& label, - PermissionMask immediate_filter_perm_mask, - PermissionMask dnd_filter_perm_mask, - bool can_apply_immediately, - LLUIImagePtr fallback_image_name, - EPickInventoryType pick_type); - - virtual ~LLFloaterTexturePicker(); - - // LLView overrides - /*virtual*/ bool handleDragAndDrop(S32 x, S32 y, MASK mask, - bool drop, EDragAndDropType cargo_type, void *cargo_data, - EAcceptance *accept, - std::string& tooltip_msg); - /*virtual*/ void draw(); - /*virtual*/ bool handleKeyHere(KEY key, MASK mask); - - // LLFloater overrides - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); - /*virtual*/ void onClose(bool app_settings); - - // New functions - void setImageID(const LLUUID& image_asset_id, bool set_selection = true); - bool updateImageStats(); // true if within limits - const LLUUID& getAssetID() { return mImageAssetID; } - const LLUUID& findItemID(const LLUUID& asset_id, bool copyable_only, bool ignore_library = false); - void setCanApplyImmediately(bool b); - - void setActive(bool active); - - LLView* getOwner() const { return mOwner; } - void setOwner(LLView* owner) { mOwner = owner; } - void stopUsingPipette(); - - void commitIfImmediateSet(); - void commitCallback(LLTextureCtrl::ETexturePickOp op); - void commitCancel(); - - void onFilterEdit(const std::string& search_string); - - void setCanApply(bool can_preview, bool can_apply, bool inworld_image = true); - void setMinDimentionsLimits(S32 min_dim); - void setTextureSelectedCallback(const texture_selected_callback& cb) { mTextureSelectedCallback = cb; } - void setOnFloaterCloseCallback(const floater_close_callback& cb) { mOnFloaterCloseCallback = cb; } - void setOnFloaterCommitCallback(const floater_commit_callback& cb) { mOnFloaterCommitCallback = cb; } - void setSetImageAssetIDCallback(const set_image_asset_id_callback& cb) { mSetImageAssetIDCallback = cb; } - void setOnUpdateImageStatsCallback(const set_on_update_image_stats_callback& cb) { mOnUpdateImageStatsCallback = cb; } - const LLUUID& getDefaultImageAssetID() { return mDefaultImageAssetID; } - const LLUUID& getBlankImageAssetID() { return mBlankImageAssetID; } - - static void onBtnSetToDefault(void* userdata); - static void onBtnSelect(void* userdata); - static void onBtnCancel(void* userdata); - void onBtnPipette(); - //static void onBtnRevert( void* userdata ); - static void onBtnBlank(void* userdata); - static void onBtnNone(void* userdata); - void onSelectionChange(const std::deque &items, bool user_action); - static void onApplyImmediateCheck(LLUICtrl* ctrl, void* userdata); - void onTextureSelect(const LLTextureEntry& te); - - static void onModeSelect(LLUICtrl* ctrl, void *userdata); - static void onBtnAdd(void* userdata); - static void onBtnRemove(void* userdata); - static void onBtnUpload(void* userdata); - static void onLocalScrollCommit(LLUICtrl* ctrl, void* userdata); - - static void onBakeTextureSelect(LLUICtrl* ctrl, void *userdata); - - void setLocalTextureEnabled(bool enabled); - void setBakeTextureEnabled(bool enabled); - - void setInventoryPickType(EPickInventoryType type); - void setImmediateFilterPermMask(PermissionMask mask); - - static void onPickerCallback(const std::vector& filenames, LLHandle handle); - -protected: - void changeMode(); - void refreshLocalList(); - void refreshInventoryFilter(); - void setImageIDFromItem(const LLInventoryItem* itemp, bool set_selection = true); - - LLPointer mTexturep; - LLPointer mGLTFMaterial; - LLView* mOwner; - - LLUUID mImageAssetID; // Currently selected texture - LLUIImagePtr mFallbackImage; // What to show if currently selected texture is null. - LLUUID mDefaultImageAssetID; - LLUUID mBlankImageAssetID; - bool mTentative; - bool mAllowNoTexture; - LLUUID mSpecialCurrentImageAssetID; // Used when the asset id has no corresponding texture in the user's inventory. - LLUUID mOriginalImageAssetID; - - std::string mLabel; - - LLTextBox* mTentativeLabel; - LLTextBox* mResolutionLabel; - LLTextBox* mResolutionWarning; - - std::string mPendingName; - bool mActive; - - LLFilterEditor* mFilterEdit; - LLInventoryPanel* mInventoryPanel; - PermissionMask mImmediateFilterPermMask; - PermissionMask mDnDFilterPermMask; - bool mCanApplyImmediately; - bool mNoCopyTextureSelected; - F32 mContextConeOpacity; - LLSaveFolderState mSavedFolderState; - bool mSelectedItemPinned; - - LLComboBox* mModeSelector; - LLScrollListCtrl* mLocalScrollCtrl; - LLButton* mDefaultBtn; - LLButton* mNoneBtn; - LLButton* mBlankBtn; - LLButton* mPipetteBtn; - LLButton* mSelectBtn; - LLButton* mCancelBtn; - -private: - bool mCanApply; - bool mCanPreview; - bool mPreviewSettingChanged; - bool mLimitsSet; - S32 mMaxDim; - S32 mMinDim; - EPickInventoryType mInventoryPickType; - - - texture_selected_callback mTextureSelectedCallback; - floater_close_callback mOnFloaterCloseCallback; - floater_commit_callback mOnFloaterCommitCallback; - set_image_asset_id_callback mSetImageAssetIDCallback; - set_on_update_image_stats_callback mOnUpdateImageStatsCallback; - - bool mBakeTextureEnabled; - - static S32 sLastPickerMode; -}; - -#endif // LL_LLTEXTURECTRL_H +/** + * @file lltexturectrl.h + * @author Richard Nelson, James Cook + * @brief LLTextureCtrl class header file including related functions + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLTEXTURECTRL_H +#define LL_LLTEXTURECTRL_H + +#include "llcoord.h" +#include "llfiltereditor.h" +#include "llfloater.h" +#include "llfolderview.h" +#include "lllocalbitmaps.h" +#include "llstring.h" +#include "lluictrl.h" +#include "llpermissionsflags.h" +#include "lltextbox.h" // for params +#include "llviewerinventory.h" +#include "llviewborder.h" // for params +#include "llviewerobject.h" +#include "llviewertexture.h" +#include "llwindow.h" + +class LLComboBox; +class LLFloaterTexturePicker; +class LLInventoryItem; +class LLViewerFetchedTexture; +class LLFetchedGLTFMaterial; + +// used for setting drag & drop callbacks. +typedef boost::function drag_n_drop_callback; +typedef boost::function texture_selected_callback; + +// Helper functions for UI that work with picker +bool get_is_predefined_texture(LLUUID asset_id); + +// texture picker works by asset ids since objects normaly do +// not retain inventory ids as result these functions are looking +// for textures in inventory by asset ids +// This search can be performance unfriendly and doesn't warranty +// that the texture is original source of asset +LLUUID get_copy_free_item_by_asset_id(LLUUID image_id, bool no_trans_perm = false); +bool get_can_copy_texture(LLUUID image_id); + +enum LLPickerSource +{ + PICKER_INVENTORY, + PICKER_LOCAL, + PICKER_BAKE, + PICKER_UNKNOWN, // on cancel, default ids +}; + +typedef enum e_pick_inventory_type +{ + PICK_TEXTURE_MATERIAL = 0, + PICK_TEXTURE = 1, + PICK_MATERIAL = 2, +} EPickInventoryType; + +////////////////////////////////////////////////////////////////////////////////////////// +// LLTextureCtrl + + +class LLTextureCtrl +: public LLUICtrl +{ +public: + typedef enum e_texture_pick_op + { + TEXTURE_CHANGE, + TEXTURE_SELECT, + TEXTURE_CANCEL + } ETexturePickOp; + +public: + struct Params : public LLInitParam::Block + { + Optional image_id; + Optional default_image_id; + Optional default_image_name; + Optional allow_no_texture; + Optional can_apply_immediately; + Optional no_commit_on_selection; // alternative mode: commit occurs and the widget gets dirty + // only on DnD or when OK is pressed in the picker + Optional label_width; + Optional border_color; + Optional fallback_image; + + Optional multiselect_text, + caption_text; + + Optional border; + + Params() + : image_id("image"), + default_image_id("default_image_id"), + default_image_name("default_image_name"), + allow_no_texture("allow_no_texture", false), + can_apply_immediately("can_apply_immediately"), + no_commit_on_selection("no_commit_on_selection", false), + label_width("label_width", -1), + border_color("border_color"), + fallback_image("fallback_image"), + multiselect_text("multiselect_text"), + caption_text("caption_text"), + border("border") + {} + }; +protected: + LLTextureCtrl(const Params&); + friend class LLUICtrlFactory; +public: + virtual ~LLTextureCtrl(); + + // LLView interface + + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, + bool drop, EDragAndDropType cargo_type, void *cargo_data, + EAcceptance *accept, + std::string& tooltip_msg); + virtual bool handleHover(S32 x, S32 y, MASK mask); + virtual bool handleUnicodeCharHere(llwchar uni_char); + + virtual void draw(); + virtual void setVisible( bool visible ); + virtual void setEnabled( bool enabled ); + + void setValid(bool valid); + + // LLUICtrl interface + virtual void clear(); + + // Takes a UUID, wraps get/setImageAssetID + virtual void setValue(const LLSD& value); + virtual LLSD getValue() const; + + // LLTextureCtrl interface + void showPicker(bool take_focus); + bool isPickerShown() { return !mFloaterHandle.isDead(); } + void setLabel(const std::string& label); + void setLabelWidth(S32 label_width) {mLabelWidth =label_width;} + const std::string& getLabel() const { return mLabel; } + + void setAllowNoTexture( bool b ) { mAllowNoTexture = b; } + bool getAllowNoTexture() const { return mAllowNoTexture; } + + void setAllowLocalTexture(bool b) { mAllowLocalTexture = b; } + bool getAllowLocalTexture() const { return mAllowLocalTexture; } + + const LLUUID& getImageItemID() { return mImageItemID; } + + virtual void setImageAssetName(const std::string& name); + + void setImageAssetID(const LLUUID &image_asset_id); + const LLUUID& getImageAssetID() const { return mImageAssetID; } + + void setDefaultImageAssetID( const LLUUID& id ) { mDefaultImageAssetID = id; } + const LLUUID& getDefaultImageAssetID() const { return mDefaultImageAssetID; } + + const std::string& getDefaultImageName() const { return mDefaultImageName; } + + void setBlankImageAssetID( const LLUUID& id ) { mBlankImageAssetID = id; } + const LLUUID& getBlankImageAssetID() const { return mBlankImageAssetID; } + + void setOpenTexPreview(bool open_preview) { mOpenTexPreview = open_preview; } + + void setCaption(const std::string& caption); + void setCanApplyImmediately(bool b); + + void setCanApply(bool can_preview, bool can_apply); + + void setImmediateFilterPermMask(PermissionMask mask); + void setDnDFilterPermMask(PermissionMask mask) + { mDnDFilterPermMask = mask; } + PermissionMask getImmediateFilterPermMask() { return mImmediateFilterPermMask; } + void setFilterPermissionMasks(PermissionMask mask); + + void closeDependentFloater(); + + void onFloaterClose(); + void onFloaterCommit(ETexturePickOp op, + LLPickerSource source, + const LLUUID& local_id, + const LLUUID& inv_id, + const LLUUID& tracking_id); + + // This call is returned when a drag is detected. Your callback + // should return true if the drag is acceptable. + void setDragCallback(drag_n_drop_callback cb) { mDragCallback = cb; } + + // This callback is called when the drop happens. Return true if + // the drop happened - resulting in an on commit callback, but not + // necessariliy any other change. + void setDropCallback(drag_n_drop_callback cb) { mDropCallback = cb; } + + void setOnCancelCallback(commit_callback_t cb) { mOnCancelCallback = cb; } + void setOnCloseCallback(commit_callback_t cb) { mOnCloseCallback = cb; } + void setOnSelectCallback(commit_callback_t cb) { mOnSelectCallback = cb; } + + /* + * callback for changing texture selection in inventory list of texture floater + */ + void setOnTextureSelectedCallback(texture_selected_callback cb); + + void setShowLoadingPlaceholder(bool showLoadingPlaceholder); + + LLViewerFetchedTexture* getTexture() { return mTexturep; } + + void setBakeTextureEnabled(bool enabled); + bool getBakeTextureEnabled() const { return mBakeTextureEnabled; } + + void setInventoryPickType(EPickInventoryType type); + EPickInventoryType getInventoryPickType() { return mInventoryPickType; }; + + bool isImageLocal() { return mLocalTrackingID.notNull(); } + LLUUID getLocalTrackingID() { return mLocalTrackingID; } + +private: + bool allowDrop(LLInventoryItem* item, EDragAndDropType cargo_type, std::string& tooltip_msg); + bool doDrop(LLInventoryItem* item); + +private: + drag_n_drop_callback mDragCallback; + drag_n_drop_callback mDropCallback; + commit_callback_t mOnCancelCallback; + commit_callback_t mOnSelectCallback; + commit_callback_t mOnCloseCallback; + texture_selected_callback mOnTextureSelectedCallback; + LLPointer mTexturep; + LLUIColor mBorderColor; + LLUUID mImageItemID; + LLUUID mImageAssetID; + LLUUID mDefaultImageAssetID; + LLUUID mBlankImageAssetID; + LLUUID mLocalTrackingID; + LLUIImagePtr mFallbackImage; + std::string mDefaultImageName; + LLHandle mFloaterHandle; + LLTextBox* mTentativeLabel; + LLTextBox* mCaption; + std::string mLabel; + bool mAllowNoTexture; // If true, the user can select "none" as an option + bool mAllowLocalTexture; + PermissionMask mImmediateFilterPermMask; + PermissionMask mDnDFilterPermMask; + bool mCanApplyImmediately; + bool mCommitOnSelection; + bool mNeedsRawImageData; + LLViewBorder* mBorder; + bool mValid; + bool mShowLoadingPlaceholder; + std::string mLoadingPlaceholderString; + S32 mLabelWidth; + bool mOpenTexPreview; + bool mBakeTextureEnabled; + EPickInventoryType mInventoryPickType; +}; + +////////////////////////////////////////////////////////////////////////////////////////// +// LLFloaterTexturePicker +typedef boost::function floater_commit_callback; +typedef boost::function floater_close_callback; +typedef boost::function set_image_asset_id_callback; +typedef boost::function texture)> set_on_update_image_stats_callback; + +class LLFloaterTexturePicker : public LLFloater +{ +public: + LLFloaterTexturePicker( + LLView* owner, + LLUUID image_asset_id, + LLUUID default_image_asset_id, + LLUUID blank_image_asset_id, + bool tentative, + bool allow_no_texture, + const std::string& label, + PermissionMask immediate_filter_perm_mask, + PermissionMask dnd_filter_perm_mask, + bool can_apply_immediately, + LLUIImagePtr fallback_image_name, + EPickInventoryType pick_type); + + virtual ~LLFloaterTexturePicker(); + + // LLView overrides + /*virtual*/ bool handleDragAndDrop(S32 x, S32 y, MASK mask, + bool drop, EDragAndDropType cargo_type, void *cargo_data, + EAcceptance *accept, + std::string& tooltip_msg); + /*virtual*/ void draw(); + /*virtual*/ bool handleKeyHere(KEY key, MASK mask); + + // LLFloater overrides + /*virtual*/ bool postBuild(); + /*virtual*/ void onOpen(const LLSD& key); + /*virtual*/ void onClose(bool app_settings); + + // New functions + void setImageID(const LLUUID& image_asset_id, bool set_selection = true); + bool updateImageStats(); // true if within limits + const LLUUID& getAssetID() { return mImageAssetID; } + const LLUUID& findItemID(const LLUUID& asset_id, bool copyable_only, bool ignore_library = false); + void setCanApplyImmediately(bool b); + + void setActive(bool active); + + LLView* getOwner() const { return mOwner; } + void setOwner(LLView* owner) { mOwner = owner; } + void stopUsingPipette(); + + void commitIfImmediateSet(); + void commitCallback(LLTextureCtrl::ETexturePickOp op); + void commitCancel(); + + void onFilterEdit(const std::string& search_string); + + void setCanApply(bool can_preview, bool can_apply, bool inworld_image = true); + void setMinDimentionsLimits(S32 min_dim); + void setTextureSelectedCallback(const texture_selected_callback& cb) { mTextureSelectedCallback = cb; } + void setOnFloaterCloseCallback(const floater_close_callback& cb) { mOnFloaterCloseCallback = cb; } + void setOnFloaterCommitCallback(const floater_commit_callback& cb) { mOnFloaterCommitCallback = cb; } + void setSetImageAssetIDCallback(const set_image_asset_id_callback& cb) { mSetImageAssetIDCallback = cb; } + void setOnUpdateImageStatsCallback(const set_on_update_image_stats_callback& cb) { mOnUpdateImageStatsCallback = cb; } + const LLUUID& getDefaultImageAssetID() { return mDefaultImageAssetID; } + const LLUUID& getBlankImageAssetID() { return mBlankImageAssetID; } + + static void onBtnSetToDefault(void* userdata); + static void onBtnSelect(void* userdata); + static void onBtnCancel(void* userdata); + void onBtnPipette(); + //static void onBtnRevert( void* userdata ); + static void onBtnBlank(void* userdata); + static void onBtnNone(void* userdata); + void onSelectionChange(const std::deque &items, bool user_action); + static void onApplyImmediateCheck(LLUICtrl* ctrl, void* userdata); + void onTextureSelect(const LLTextureEntry& te); + + static void onModeSelect(LLUICtrl* ctrl, void *userdata); + static void onBtnAdd(void* userdata); + static void onBtnRemove(void* userdata); + static void onBtnUpload(void* userdata); + static void onLocalScrollCommit(LLUICtrl* ctrl, void* userdata); + + static void onBakeTextureSelect(LLUICtrl* ctrl, void *userdata); + + void setLocalTextureEnabled(bool enabled); + void setBakeTextureEnabled(bool enabled); + + void setInventoryPickType(EPickInventoryType type); + void setImmediateFilterPermMask(PermissionMask mask); + + static void onPickerCallback(const std::vector& filenames, LLHandle handle); + +protected: + void changeMode(); + void refreshLocalList(); + void refreshInventoryFilter(); + void setImageIDFromItem(const LLInventoryItem* itemp, bool set_selection = true); + + LLPointer mTexturep; + LLPointer mGLTFMaterial; + LLView* mOwner; + + LLUUID mImageAssetID; // Currently selected texture + LLUIImagePtr mFallbackImage; // What to show if currently selected texture is null. + LLUUID mDefaultImageAssetID; + LLUUID mBlankImageAssetID; + bool mTentative; + bool mAllowNoTexture; + LLUUID mSpecialCurrentImageAssetID; // Used when the asset id has no corresponding texture in the user's inventory. + LLUUID mOriginalImageAssetID; + + std::string mLabel; + + LLTextBox* mTentativeLabel; + LLTextBox* mResolutionLabel; + LLTextBox* mResolutionWarning; + + std::string mPendingName; + bool mActive; + + LLFilterEditor* mFilterEdit; + LLInventoryPanel* mInventoryPanel; + PermissionMask mImmediateFilterPermMask; + PermissionMask mDnDFilterPermMask; + bool mCanApplyImmediately; + bool mNoCopyTextureSelected; + F32 mContextConeOpacity; + LLSaveFolderState mSavedFolderState; + bool mSelectedItemPinned; + + LLComboBox* mModeSelector; + LLScrollListCtrl* mLocalScrollCtrl; + LLButton* mDefaultBtn; + LLButton* mNoneBtn; + LLButton* mBlankBtn; + LLButton* mPipetteBtn; + LLButton* mSelectBtn; + LLButton* mCancelBtn; + +private: + bool mCanApply; + bool mCanPreview; + bool mPreviewSettingChanged; + bool mLimitsSet; + S32 mMaxDim; + S32 mMinDim; + EPickInventoryType mInventoryPickType; + + + texture_selected_callback mTextureSelectedCallback; + floater_close_callback mOnFloaterCloseCallback; + floater_commit_callback mOnFloaterCommitCallback; + set_image_asset_id_callback mSetImageAssetIDCallback; + set_on_update_image_stats_callback mOnUpdateImageStatsCallback; + + bool mBakeTextureEnabled; + + static S32 sLastPickerMode; +}; + +#endif // LL_LLTEXTURECTRL_H diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp index ea04d08980..56d0480be4 100644 --- a/indra/newview/lltexturefetch.cpp +++ b/indra/newview/lltexturefetch.cpp @@ -1,3738 +1,3738 @@ -/** - * @file lltexturefetch.cpp - * @brief Object which fetches textures from the cache and/or network - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012-2014, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include -#include -#include - -#include "lltexturefetch.h" - -#include "lldir.h" -#include "llhttpconstants.h" -#include "llimage.h" -#include "llimagej2c.h" -#include "llimageworker.h" -#include "llworkerthread.h" -#include "message.h" - -#include "llagent.h" -#include "lltexturecache.h" -#include "llviewercontrol.h" -#include "llviewertexturelist.h" -#include "llviewertexture.h" -#include "llviewerregion.h" -#include "llviewerstats.h" -#include "llviewerstatsrecorder.h" -#include "llviewerassetstats.h" -#include "llworld.h" -#include "llsdparam.h" -#include "llsdutil.h" -#include "llstartup.h" - -#include "httprequest.h" -#include "httphandler.h" -#include "httpresponse.h" -#include "bufferarray.h" -#include "bufferstream.h" -#include "llcorehttputil.h" -#include "llhttpretrypolicy.h" - -LLTrace::CountStatHandle LLTextureFetch::sCacheHit("texture_cache_hit"); -LLTrace::CountStatHandle LLTextureFetch::sCacheAttempt("texture_cache_attempt"); -LLTrace::EventStatHandle > LLTextureFetch::sCacheHitRate("texture_cache_hits"); - -LLTrace::SampleStatHandle LLTextureFetch::sCacheReadLatency("texture_cache_read_latency"); -LLTrace::SampleStatHandle LLTextureFetch::sTexDecodeLatency("texture_decode_latency"); -LLTrace::SampleStatHandle LLTextureFetch::sCacheWriteLatency("texture_write_latency"); -LLTrace::SampleStatHandle LLTextureFetch::sTexFetchLatency("texture_fetch_latency"); - -LLTextureFetchTester* LLTextureFetch::sTesterp = NULL ; -const std::string sTesterName("TextureFetchTester"); - -////////////////////////////////////////////////////////////////////////////// -// -// Introduction -// -// This is an attempt to document what's going on in here after-the-fact. -// It's a sincere attempt to be accurate but there will be mistakes. -// -// -// Purpose -// -// What is this module trying to do? It accepts requests to load textures -// at a given priority and discard level and notifies the caller when done -// (successfully or not). Additional constraints are: -// -// * Support a local texture cache. Don't hit network when possible -// to avoid it. -// * Use UDP or HTTP as directed or as fallback. HTTP is tried when -// not disabled and a URL is available. UDP when a URL isn't -// available or HTTP attempts fail. -// * Asynchronous (using threads). Main thread is not to be blocked or -// burdened. -// * High concurrency. Many requests need to be in-flight and at various -// stages of completion. -// * Tolerate frequent re-prioritizations of requests. Priority is -// a reflection of a camera's viewpoint and as that viewpoint changes, -// objects and textures become more and less relevant and that is -// expressed at this level by priority changes and request cancelations. -// -// The caller interfaces that fall out of the above and shape the -// implementation are: -// * createRequest - Load j2c image via UDP or HTTP at given discard level and priority -// * deleteRequest - Request removal of prior request -// * getRequestFinished - Test if request is finished returning data to caller -// * updateRequestPriority - Change priority of existing request -// * getFetchState - Retrieve progress on existing request -// -// Everything else in here is mostly plumbing, metrics and debug. -// -// -// The Work Queue -// -// The two central classes are LLTextureFetch and LLTextureFetchWorker. -// LLTextureFetch combines threading with a priority queue of work -// requests. The priority queue is sorted by a U32 priority derived -// from the F32 priority in the APIs. The *only* work request that -// receives service time by this thread is the highest priority -// request. All others wait until it is complete or a dynamic priority -// change has re-ordered work. -// -// LLTextureFetchWorker implements the work request and is 1:1 with -// texture fetch requests. Embedded in each is a state machine that -// walks it through the cache, HTTP, UDP, image decode and retry -// steps of texture acquisition. -// -// -// Threads -// -// Several threads are actively invoking code in this module. They -// include: -// -// 1. Tmain Main thread of execution -// 2. Ttf LLTextureFetch's worker thread provided by LLQueuedThread -// 3. Tcurl LLCurl's worker thread (should disappear over time) -// 4. Ttc LLTextureCache's worker thread -// 5. Tid Image decoder's worker thread -// 6. Thl HTTP library's worker thread -// -// -// Mutexes/Condition Variables -// -// 1. Mt Mutex defined for LLThread's condition variable (base class of -// LLTextureFetch) -// 2. Ct Condition variable for LLThread and used by lock/unlockData(). -// 3. Mwtd Special LLWorkerThread mutex used for request deletion -// operations (base class of LLTextureFetch) -// 4. Mfq LLTextureFetch's mutex covering request and command queue -// data. -// 5. Mfnq LLTextureFetch's mutex covering udp and http request -// queue data. -// 6. Mwc Mutex covering LLWorkerClass's members (base class of -// LLTextureFetchWorker). One per request. -// 7. Mw LLTextureFetchWorker's mutex. One per request. -// -// -// Lock Ordering Rules -// -// Not an exhaustive list but shows the order of lock acquisition -// needed to prevent deadlocks. 'A < B' means acquire 'A' before -// acquiring 'B'. -// -// 1. Mw < Mfnq -// (there are many more...) -// -// -// Method and Member Definitions -// -// With the above, we'll try to document what threads can call what -// methods (using T* for any), what locks must be held on entry and -// are taken out during execution and what data is covered by which -// lock (if any). This latter category will be especially prone to -// error so be skeptical. -// -// A line like: "// Locks: M" indicates a method that must -// be invoked by a caller holding the 'M' lock. Similarly, -// "// Threads: T" means that a caller should be running in -// the indicated thread. -// -// For data members, a trailing comment like "// M" means that -// the data member is covered by the specified lock. Absence of a -// comment can mean the member is unlocked or that I didn't bother -// to do the archaeology. In the case of LLTextureFetchWorker, -// most data members added by the leaf class are actually covered -// by the Mw lock. You may also see "// T" which means that -// the member's usage is restricted to one thread (except for -// perhaps construction and destruction) and so explicit locking -// isn't used. -// -// In code, a trailing comment like "// [-+]M" indicates a -// lock acquision or release point. -// -// -// Worker Lifecycle -// -// The threading and responder model makes it very likely that -// other components are holding on to a pointer to a worker request. -// So, uncoordinated deletions of requests is a guarantee of memory -// corruption in a short time. So destroying a request involves -// invocations's of LLQueuedThread/LLWorkerThread's abort/stop -// logic that removes workers and puts them ona delete queue for -// 2-phase destruction. That second phase is deferrable by calls -// to deleteOK() which only allow final destruction (via dtor) -// once deleteOK has determined that the request is in a safe -// state. -// -// -// Worker State Machine -// -// "doWork" will be executed for a given worker on its respective -// LLQueuedThread. If doWork returns true, the worker is treated -// as completed. If doWork returns false, the worker will be -// put on the back of the work queue at the start of the next iteration -// of the mainloop. If a worker is waiting on a resource, it should -// return false as soon as possible and not block to avoid starving -// other workers of cpu cycles. -// - - - -////////////////////////////////////////////////////////////////////////////// - -// Tuning/Parameterization Constants - -static const S32 HTTP_PIPE_REQUESTS_HIGH_WATER = 100; // Maximum requests to have active in HTTP (pipelined) -static const S32 HTTP_PIPE_REQUESTS_LOW_WATER = 50; // Active level at which to refill -static const S32 HTTP_NONPIPE_REQUESTS_HIGH_WATER = 40; -static const S32 HTTP_NONPIPE_REQUESTS_LOW_WATER = 20; - -// BUG-3323/SH-4375 -// *NOTE: This is a heuristic value. Texture fetches have a habit of using a -// value of 32MB to indicate 'get the rest of the image'. Certain ISPs and -// network equipment get confused when they see this in a Range: header. So, -// if the request end is beyond this value, we issue an open-ended Range: -// request (e.g. 'Range: -') which seems to fix the problem. -static const S32 HTTP_REQUESTS_RANGE_END_MAX = 20000000; - -// stop after 720 seconds, might be overkill, but cap request can keep going forever. -static const S32 MAX_CAP_MISSING_RETRIES = 720; -static const S32 CAP_MISSING_EXPIRATION_DELAY = 1; // seconds - -////////////////////////////////////////////////////////////////////////////// -namespace -{ - // The NoOpDeletor is used when passing certain objects (the LLTextureFetchWorker) - // in a smart pointer below for passage into - // the LLCore::Http libararies. When the smart pointer is destroyed, no - // action will be taken since we do not in these cases want the object to - // be destroyed at the end of the call. - // - // *NOTE$: Yes! It is "Deletor" - // http://english.stackexchange.com/questions/4733/what-s-the-rule-for-adding-er-vs-or-when-nouning-a-verb - // "delete" derives from Latin "deletus" - void NoOpDeletor(LLCore::HttpHandler *) - { /*NoOp*/ } -} - -static const char* e_state_name[] = -{ - "INVALID", - "INIT", - "LOAD_FROM_TEXTURE_CACHE", - "CACHE_POST", - "LOAD_FROM_NETWORK", - "WAIT_HTTP_RESOURCE", - "WAIT_HTTP_RESOURCE2", - "SEND_HTTP_REQ", - "WAIT_HTTP_REQ", - "DECODE_IMAGE", - "DECODE_IMAGE_UPDATE", - "WRITE_TO_CACHE", - "WAIT_ON_WRITE", - "DONE" -}; - -// Log scope -static const char * const LOG_TXT = "Texture"; - -class LLTextureFetchWorker : public LLWorkerClass, public LLCore::HttpHandler - -{ - friend class LLTextureFetch; - -private: - class CacheReadResponder : public LLTextureCache::ReadResponder - { - public: - - // Threads: Ttf - CacheReadResponder(LLTextureFetch* fetcher, const LLUUID& id, LLImageFormatted* image) - : mFetcher(fetcher), mID(id) - { - setImage(image); - } - - // Threads: Ttc - virtual void completed(bool success) - { - LL_PROFILE_ZONE_SCOPED; - LLTextureFetchWorker* worker = mFetcher->getWorker(mID); - if (worker) - { - worker->callbackCacheRead(success, mFormattedImage, mImageSize, mImageLocal); - } - } - private: - LLTextureFetch* mFetcher; - LLUUID mID; - }; - - class CacheWriteResponder : public LLTextureCache::WriteResponder - { - public: - - // Threads: Ttf - CacheWriteResponder(LLTextureFetch* fetcher, const LLUUID& id) - : mFetcher(fetcher), mID(id) - { - } - - // Threads: Ttc - virtual void completed(bool success) - { - LL_PROFILE_ZONE_SCOPED; - LLTextureFetchWorker* worker = mFetcher->getWorker(mID); - if (worker) - { - worker->callbackCacheWrite(success); - } - } - private: - LLTextureFetch* mFetcher; - LLUUID mID; - }; - - class DecodeResponder : public LLImageDecodeThread::Responder - { - public: - - // Threads: Ttf - DecodeResponder(LLTextureFetch* fetcher, const LLUUID& id, LLTextureFetchWorker* worker) - : mFetcher(fetcher), mID(id) - { - } - - // Threads: Tid - virtual void completed(bool success, const std::string& error_message, LLImageRaw* raw, LLImageRaw* aux, U32 request_id) - { - LL_PROFILE_ZONE_SCOPED; - LLTextureFetchWorker* worker = mFetcher->getWorker(mID); - if (worker) - { - worker->callbackDecoded(success, error_message, raw, aux, request_id); - } - } - private: - LLTextureFetch* mFetcher; - LLUUID mID; - }; - - struct Compare - { - // lhs < rhs - bool operator()(const LLTextureFetchWorker* lhs, const LLTextureFetchWorker* rhs) const - { - // greater priority is "less" - return lhs->mImagePriority > rhs->mImagePriority; - } - }; - -public: - - // Threads: Ttf - /*virtual*/ bool doWork(S32 param); // Called from LLWorkerThread::processRequest() - - // Threads: Ttf - /*virtual*/ void finishWork(S32 param, bool completed); // called from finishRequest() (WORK THREAD) - - // Threads: Tmain - /*virtual*/ bool deleteOK(); // called from update() - - ~LLTextureFetchWorker(); - - // Threads: Ttf - // Locks: Mw - S32 callbackHttpGet(LLCore::HttpResponse * response, - bool partial, bool success); - - // Threads: Ttc - void callbackCacheRead(bool success, LLImageFormatted* image, - S32 imagesize, bool islocal); - - // Threads: Ttc - void callbackCacheWrite(bool success); - - // Threads: Tid - void callbackDecoded(bool success, const std::string& error_message, LLImageRaw* raw, LLImageRaw* aux, S32 decode_id); - - // Threads: T* - void setGetStatus(LLCore::HttpStatus status, const std::string& reason) - { - LLMutexLock lock(&mWorkMutex); - - mGetStatus = status; - mGetReason = reason; - } - - void setCanUseHTTP(bool can_use_http) { mCanUseHTTP = can_use_http; } - bool getCanUseHTTP() const { return mCanUseHTTP; } - - void setUrl(const std::string& url) { mUrl = url; } - - LLTextureFetch & getFetcher() { return *mFetcher; } - - // Inherited from LLCore::HttpHandler - // Threads: Ttf - virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); - - enum e_state // mState - { - // *NOTE: Do not change the order/value of state variables, some code - // depends upon specific ordering/adjacency. - - // NOTE: Affects LLTextureBar::draw in lltextureview.cpp (debug hack) - INVALID = 0, - INIT, - LOAD_FROM_TEXTURE_CACHE, - CACHE_POST, - LOAD_FROM_NETWORK, - WAIT_HTTP_RESOURCE, // Waiting for HTTP resources - WAIT_HTTP_RESOURCE2, // Waiting for HTTP resources - SEND_HTTP_REQ, // Commit to sending as HTTP - WAIT_HTTP_REQ, // Request sent, wait for completion - DECODE_IMAGE, - DECODE_IMAGE_UPDATE, - WRITE_TO_CACHE, - WAIT_ON_WRITE, - DONE - }; - -protected: - LLTextureFetchWorker(LLTextureFetch* fetcher, FTType f_type, - const std::string& url, const LLUUID& id, const LLHost& host, - F32 priority, S32 discard, S32 size); - -private: - - // Threads: Tmain - /*virtual*/ void startWork(S32 param); // called from addWork() (MAIN THREAD) - - // Threads: Tmain - /*virtual*/ void endWork(S32 param, bool aborted); // called from doWork() (MAIN THREAD) - - // Locks: Mw - void resetFormattedData(); - - // get the relative priority of this worker (should map to max virtual size) - F32 getImagePriority() const; - - // Locks: Mw - void setImagePriority(F32 priority); - - // Locks: Mw (ctor invokes without lock) - void setDesiredDiscard(S32 discard, S32 size); - - // Threads: T* - // Locks: Mw - bool insertPacket(S32 index, U8* data, S32 size); - - // Locks: Mw - void clearPackets(); - - - // Locks: Mw - void removeFromCache(); - - // Threads: Ttf - bool writeToCacheComplete(); - - // Threads: Ttf - void recordTextureStart(bool is_http); - - // Threads: Ttf - void recordTextureDone(bool is_http, F64 byte_count); - - void lockWorkMutex() { mWorkMutex.lock(); } - void unlockWorkMutex() { mWorkMutex.unlock(); } - - // Threads: Ttf - // Locks: Mw - bool acquireHttpSemaphore() - { - llassert(! mHttpHasResource); - if (mFetcher->mHttpSemaphore >= mFetcher->mHttpHighWater) - { - return false; - } - mHttpHasResource = true; - mFetcher->mHttpSemaphore++; - return true; - } - - // Threads: Ttf - // Locks: Mw - void releaseHttpSemaphore() - { - llassert(mHttpHasResource); - mHttpHasResource = false; - mFetcher->mHttpSemaphore--; - llassert_always(mFetcher->mHttpSemaphore >= 0); - } - -private: - enum e_request_state // mSentRequest - { - UNSENT = 0, - QUEUED = 1, - SENT_SIM = 2 - }; - enum e_write_to_cache_state //mWriteToCacheState - { - NOT_WRITE = 0, - CAN_WRITE = 1, - SHOULD_WRITE = 2 - }; - - e_state mState; - void setState(e_state new_state); - LLViewerRegion* getRegion(); - - e_write_to_cache_state mWriteToCacheState; - LLTextureFetch* mFetcher; - LLPointer mFormattedImage; - LLPointer mRawImage, - mAuxImage; - FTType mFTType; - LLUUID mID; - LLHost mHost; - std::string mUrl; - U8 mType; - F32 mImagePriority; // should map to max virtual size - F32 mRequestedPriority; - S32 mDesiredDiscard; - S32 mSimRequestedDiscard; - S32 mRequestedDiscard; - S32 mLoadedDiscard; - S32 mDecodedDiscard; - LLFrameTimer mRequestedDeltaTimer; - LLFrameTimer mFetchDeltaTimer; - LLTimer mCacheReadTimer; - LLTimer mDecodeTimer; - LLTimer mCacheWriteTimer; - LLTimer mFetchTimer; - LLTimer mStateTimer; - F32 mCacheReadTime; // time for cache read only - F32 mDecodeTime; // time for decode only - F32 mCacheWriteTime; - F32 mFetchTime; // total time from req to finished fetch - std::map mStateTimersMap; - F32 mSkippedStatesTime; - LLTextureCache::handle_t mCacheReadHandle, - mCacheWriteHandle; - S32 mRequestedSize, - mRequestedOffset, - mDesiredSize, - mFileSize, - mCachedSize; - e_request_state mSentRequest; - handle_t mDecodeHandle; - bool mLoaded; - bool mDecoded; - bool mWritten; - bool mNeedsAux; - bool mHaveAllData; - bool mInLocalCache; - bool mInCache; - bool mCanUseHTTP; - S32 mRetryAttempt; - S32 mActiveCount; - LLCore::HttpStatus mGetStatus; - std::string mGetReason; - LLAdaptiveRetryPolicy mFetchRetryPolicy; - bool mCanUseCapability; - LLTimer mRegionRetryTimer; - S32 mRegionRetryAttempt; - LLUUID mLastRegionId; - - - // Work Data - LLMutex mWorkMutex; - struct PacketData - { - PacketData(U8* data, S32 size) - : mData(data), mSize(size) - {} - ~PacketData() { clearData(); } - void clearData() { delete[] mData; mData = NULL; } - - U8* mData; - U32 mSize; - }; - std::vector mPackets; - S32 mFirstPacket; - S32 mLastPacket; - U16 mTotalPackets; - U8 mImageCodec; - - LLViewerAssetStats::duration_t mMetricsStartTime; - - LLCore::HttpHandle mHttpHandle; // Handle of any active request - LLCore::BufferArray * mHttpBufferArray; // Refcounted pointer to response data - S32 mHttpPolicyClass; - bool mHttpActive; // Active request to http library - U32 mHttpReplySize, // Actual received data size - mHttpReplyOffset; // Actual received data offset - bool mHttpHasResource; // Counts against Fetcher's mHttpSemaphore - - // State history - U32 mCacheReadCount, - mCacheWriteCount, - mResourceWaitCount; // Requests entering WAIT_HTTP_RESOURCE2 -}; - -////////////////////////////////////////////////////////////////////////////// - -// Cross-thread messaging for asset metrics. - -/** - * @brief Base class for cross-thread requests made of the fetcher - * - * I believe the intent of the LLQueuedThread class was to - * have these operations derived from LLQueuedThread::QueuedRequest - * but the texture fetcher has elected to manage the queue - * in its own manner. So these are free-standing objects which are - * managed in simple FIFO order on the mCommands queue of the - * LLTextureFetch object. - * - * What each represents is a simple command sent from an - * outside thread into the TextureFetch thread to be processed - * in order and in a timely fashion (though not an absolute - * higher priority than other operations of the thread). - * Each operation derives a new class from the base customizing - * members, constructors and the doWork() method to effect - * the command. - * - * The flow is one-directional. There are two global instances - * of the LLViewerAssetStats collector, one for the main program's - * thread pointed to by gViewerAssetStatsMain and one for the - * TextureFetch thread pointed to by gViewerAssetStatsThread1. - * Common operations has each thread recording metrics events - * into the respective collector unconcerned with locking and - * the state of any other thread. But when the agent moves into - * a different region or the metrics timer expires and a report - * needs to be sent back to the grid, messaging across threads - * is required to distribute data and perform global actions. - * In pseudo-UML, it looks like: - * - * @verbatim - * Main Thread1 - * . . - * . . - * +-----+ . - * | AM | . - * +--+--+ . - * +-------+ | . - * | Main | +--+--+ . - * | | | SRE |---. . - * | Stats | +-----+ \ . - * | | | \ (uuid) +-----+ - * | Coll. | +--+--+ `-------->| SR | - * +-------+ | MSC | +--+--+ - * | ^ +-----+ | - * | | (uuid) / . +-----+ (uuid) - * | `--------' . | MSC |---------. - * | . +-----+ | - * | +-----+ . v - * | | TE | . +-------+ - * | +--+--+ . | Thd1 | - * | | . | | - * | +-----+ . | Stats | - * `--------->| RSC | . | | - * +--+--+ . | Coll. | - * | . +-------+ - * +--+--+ . | - * | SME |---. . | - * +-----+ \ . | - * . \ (clone) +-----+ | - * . `-------->| SM | | - * . +--+--+ | - * . | | - * . +-----+ | - * . | RSC |<--------' - * . +-----+ - * . | - * . +-----+ - * . | CP |--> HTTP POST - * . +-----+ - * . . - * . . - * - * Key: - * - * SRE - Set Region Enqueued. Enqueue a 'Set Region' command in - * the other thread providing the new UUID of the region. - * TFReqSetRegion carries the data. - * SR - Set Region. New region UUID is sent to the thread-local - * collector. - * SME - Send Metrics Enqueued. Enqueue a 'Send Metrics' command - * including an ownership transfer of a cloned LLViewerAssetStats. - * TFReqSendMetrics carries the data. - * SM - Send Metrics. Global metrics reporting operation. Takes - * the cloned stats from the command, merges it with the - * thread's local stats, converts to LLSD and sends it on - * to the grid. - * AM - Agent Moved. Agent has completed some sort of move to a - * new region. - * TE - Timer Expired. Metrics timer has expired (on the order - * of 10 minutes). - * CP - CURL Post - * MSC - Modify Stats Collector. State change in the thread-local - * collector. Typically a region change which affects the - * global pointers used to find the 'current stats'. - * RSC - Read Stats Collector. Extract collector data cloning it - * (i.e. deep copy) when necessary. - * @endverbatim - * - */ -class LLTextureFetch::TFRequest // : public LLQueuedThread::QueuedRequest -{ -public: - // Default ctors and assignment operator are correct. - - virtual ~TFRequest() - {} - - // Patterned after QueuedRequest's method but expected behavior - // is different. Always expected to complete on the first call - // and work dispatcher will assume the same and delete the - // request after invocation. - virtual bool doWork(LLTextureFetch * fetcher) = 0; -}; - -namespace -{ - -/** - * @brief Implements a 'Set Region' cross-thread command. - * - * When an agent moves to a new region, subsequent metrics need - * to be binned into a new or existing stats collection in 1:1 - * relationship with the region. We communicate this region - * change across the threads involved in the communication with - * this message. - * - * Corresponds to LLTextureFetch::commandSetRegion() - */ -class TFReqSetRegion : public LLTextureFetch::TFRequest -{ -public: - TFReqSetRegion(U64 region_handle) - : LLTextureFetch::TFRequest(), - mRegionHandle(region_handle) - {} - TFReqSetRegion & operator=(const TFReqSetRegion &); // Not defined - - virtual ~TFReqSetRegion() - {} - - virtual bool doWork(LLTextureFetch * fetcher); - -public: - const U64 mRegionHandle; -}; - - -/** - * @brief Implements a 'Send Metrics' cross-thread command. - * - * This is the big operation. The main thread gathers metrics - * for a period of minutes into LLViewerAssetStats and other - * objects then makes a snapshot of the data by cloning the - * collector. This command transfers the clone, along with a few - * additional arguments (UUIDs), handing ownership to the - * TextureFetch thread. It then merges its own data into the - * cloned copy, converts to LLSD and kicks off an HTTP POST of - * the resulting data to the currently active metrics collector. - * - * Corresponds to LLTextureFetch::commandSendMetrics() - */ -class TFReqSendMetrics : public LLTextureFetch::TFRequest -{ -public: - /** - * Construct the 'Send Metrics' command to have the TextureFetch - * thread add and log metrics data. - * - * @param caps_url URL of a "ViewerMetrics" Caps target - * to receive the data. Does not have to - * be associated with a particular region. - * - * @param session_id UUID of the agent's session. - * - * @param agent_id UUID of the agent. (Being pure here...) - * - * @param main_stats Pointer to a clone of the main thread's - * LLViewerAssetStats data. Thread1 takes - * ownership of the copy and disposes of it - * when done. - */ - TFReqSendMetrics(const std::string & caps_url, - const LLUUID & session_id, - const LLUUID & agent_id, - LLSD& stats_sd); - TFReqSendMetrics & operator=(const TFReqSendMetrics &); // Not defined - - virtual ~TFReqSendMetrics(); - - virtual bool doWork(LLTextureFetch * fetcher); - -public: - const std::string mCapsURL; - const LLUUID mSessionID; - const LLUUID mAgentID; - LLSD mStatsSD; - -private: - LLCore::HttpHandler::ptr_t mHandler; -}; - -/* - * Examines the merged viewer metrics report and if found to be too long, - * will attempt to truncate it in some reasonable fashion. - * - * @param max_regions Limit of regions allowed in report. - * - * @param metrics Full, merged viewer metrics report. - * - * @returns If data was truncated, returns true. - */ -bool truncate_viewer_metrics(int max_regions, LLSD & metrics); - -} // end of anonymous namespace - - -////////////////////////////////////////////////////////////////////////////// - -const char* sStateDescs[] = { - "INVALID", - "INIT", - "LOAD_FROM_TEXTURE_CACHE", - "CACHE_POST", - "LOAD_FROM_NETWORK", - "WAIT_HTTP_RESOURCE", - "WAIT_HTTP_RESOURCE2", - "SEND_HTTP_REQ", - "WAIT_HTTP_REQ", - "DECODE_IMAGE", - "DECODE_IMAGE_UPDATE", - "WRITE_TO_CACHE", - "WAIT_ON_WRITE", - "DONE" -}; - -const std::set LOGGED_STATES = { LLTextureFetchWorker::LOAD_FROM_TEXTURE_CACHE, LLTextureFetchWorker::LOAD_FROM_NETWORK, - LLTextureFetchWorker::WAIT_HTTP_REQ, LLTextureFetchWorker::DECODE_IMAGE_UPDATE, LLTextureFetchWorker::WAIT_ON_WRITE }; - -// static -volatile bool LLTextureFetch::svMetricsDataBreak(true); // Start with a data break - -// called from MAIN THREAD - -LLTextureFetchWorker::LLTextureFetchWorker(LLTextureFetch* fetcher, - FTType f_type, // Fetched image type - const std::string& url, // Optional URL - const LLUUID& id, // Image UUID - const LLHost& host, // Simulator host - F32 priority, // Priority - S32 discard, // Desired discard - S32 size) // Desired size - : LLWorkerClass(fetcher, "TextureFetch"), - LLCore::HttpHandler(), - mState(INIT), - mWriteToCacheState(NOT_WRITE), - mFetcher(fetcher), - mFTType(f_type), - mID(id), - mHost(host), - mUrl(url), - mImagePriority(priority), - mRequestedPriority(0.f), - mDesiredDiscard(-1), - mSimRequestedDiscard(-1), - mRequestedDiscard(-1), - mLoadedDiscard(-1), - mDecodedDiscard(-1), - mCacheReadTime(0.f), - mCacheWriteTime(0.f), - mDecodeTime(0.f), - mFetchTime(0.f), - mCacheReadHandle(LLTextureCache::nullHandle()), - mCacheWriteHandle(LLTextureCache::nullHandle()), - mRequestedSize(0), - mRequestedOffset(0), - mDesiredSize(TEXTURE_CACHE_ENTRY_SIZE), - mFileSize(0), - mSkippedStatesTime(0), - mCachedSize(0), - mLoaded(false), - mSentRequest(UNSENT), - mDecodeHandle(0), - mDecoded(false), - mWritten(false), - mNeedsAux(false), - mHaveAllData(false), - mInLocalCache(false), - mInCache(false), - mCanUseHTTP(true), - mRetryAttempt(0), - mActiveCount(0), - mWorkMutex(), - mFirstPacket(0), - mLastPacket(-1), - mTotalPackets(0), - mImageCodec(IMG_CODEC_INVALID), - mMetricsStartTime(0), - mHttpHandle(LLCORE_HTTP_HANDLE_INVALID), - mHttpBufferArray(NULL), - mHttpPolicyClass(mFetcher->mHttpPolicyClass), - mHttpActive(false), - mHttpReplySize(0U), - mHttpReplyOffset(0U), - mHttpHasResource(false), - mCacheReadCount(0U), - mCacheWriteCount(0U), - mResourceWaitCount(0U), - mFetchRetryPolicy(10.f,3600.f,2.f,10), - mCanUseCapability(true), - mRegionRetryAttempt(0) -{ - mType = host.isOk() ? LLImageBase::TYPE_AVATAR_BAKE : LLImageBase::TYPE_NORMAL; -// LL_INFOS(LOG_TXT) << "Create: " << mID << " mHost:" << host << " Discard=" << discard << LL_ENDL; - if (!mFetcher->mDebugPause) - { - addWork(0); - } - setDesiredDiscard(discard, size); -} - -LLTextureFetchWorker::~LLTextureFetchWorker() -{ -// LL_INFOS(LOG_TXT) << "Destroy: " << mID -// << " Decoded=" << mDecodedDiscard -// << " Requested=" << mRequestedDiscard -// << " Desired=" << mDesiredDiscard << LL_ENDL; - llassert_always(!haveWork()); - - lockWorkMutex(); // +Mw (should be useless) - if (mHttpHasResource) - { - // Last-chance catchall to recover the resource. Using an - // atomic datatype solely because this can be running in - // another thread. - releaseHttpSemaphore(); - } - if (mHttpActive) - { - // Issue a cancel on a live request... - mFetcher->getHttpRequest().requestCancel(mHttpHandle, LLCore::HttpHandler::ptr_t()); - } - if (mCacheReadHandle != LLTextureCache::nullHandle() && mFetcher->mTextureCache) - { - mFetcher->mTextureCache->readComplete(mCacheReadHandle, true); - } - if (mCacheWriteHandle != LLTextureCache::nullHandle() && mFetcher->mTextureCache) - { - mFetcher->mTextureCache->writeComplete(mCacheWriteHandle, true); - } - mFormattedImage = NULL; - clearPackets(); - if (mHttpBufferArray) - { - mHttpBufferArray->release(); - mHttpBufferArray = NULL; - } - unlockWorkMutex(); // -Mw - mFetcher->removeFromHTTPQueue(mID, (S32Bytes)0); - mFetcher->removeHttpWaiter(mID); - mFetcher->updateStateStats(mCacheReadCount, mCacheWriteCount, mResourceWaitCount); -} - -// Locks: Mw -void LLTextureFetchWorker::clearPackets() -{ - for_each(mPackets.begin(), mPackets.end(), DeletePointer()); - mPackets.clear(); - mTotalPackets = 0; - mLastPacket = -1; - mFirstPacket = 0; -} - -// Locks: Mw (ctor invokes without lock) -void LLTextureFetchWorker::setDesiredDiscard(S32 discard, S32 size) -{ - bool prioritize = false; - if (mDesiredDiscard != discard) - { - if (!haveWork()) - { - if (!mFetcher->mDebugPause) - { - addWork(0); - } - } - else if (mDesiredDiscard < discard) - { - prioritize = true; - } - mDesiredDiscard = discard; - mDesiredSize = size; - } - else if (size > mDesiredSize) - { - mDesiredSize = size; - prioritize = true; - } - mDesiredSize = llmax(mDesiredSize, TEXTURE_CACHE_ENTRY_SIZE); - if ((prioritize && mState == INIT) || mState == DONE) - { - setState(INIT); - } -} - -// Locks: Mw -void LLTextureFetchWorker::setImagePriority(F32 priority) -{ - mImagePriority = priority; //should map to max virtual size, abort if zero -} - -// Locks: Mw -void LLTextureFetchWorker::resetFormattedData() -{ - if (mHttpBufferArray) - { - mHttpBufferArray->release(); - mHttpBufferArray = NULL; - } - if (mFormattedImage.notNull()) - { - mFormattedImage->deleteData(); - } - mHttpReplySize = 0; - mHttpReplyOffset = 0; - mHaveAllData = false; -} - -F32 LLTextureFetchWorker::getImagePriority() const -{ - return mImagePriority; -} - -// Threads: Tmain -void LLTextureFetchWorker::startWork(S32 param) -{ - llassert(mFormattedImage.isNull()); -} - -// Threads: Ttf -bool LLTextureFetchWorker::doWork(S32 param) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; - if (gNonInteractive) - { - return true; - } - static const LLCore::HttpStatus http_not_found(HTTP_NOT_FOUND); // 404 - static const LLCore::HttpStatus http_service_unavail(HTTP_SERVICE_UNAVAILABLE); // 503 - static const LLCore::HttpStatus http_not_sat(HTTP_REQUESTED_RANGE_NOT_SATISFIABLE); // 416; - - LLMutexLock lock(&mWorkMutex); // +Mw - - if ((mFetcher->isQuitting() || getFlags(LLWorkerClass::WCF_DELETE_REQUESTED))) - { - if (mState < DECODE_IMAGE) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - state < decode"); - return true; // abort - } - } - - if (mImagePriority < F_ALMOST_ZERO) - { - if (mState == INIT || mState == LOAD_FROM_NETWORK) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - priority < 0"); - LL_DEBUGS(LOG_TXT) << mID << " abort: mImagePriority < F_ALMOST_ZERO" << LL_ENDL; - return true; // abort - } - } - if (mState > CACHE_POST && !mCanUseCapability && mCanUseHTTP) - { - if (mRegionRetryAttempt > MAX_CAP_MISSING_RETRIES) - { - mCanUseHTTP = false; - } - else if (!mRegionRetryTimer.hasExpired()) - { - return false; - } - // else retry - } - if(mState > CACHE_POST && !mCanUseHTTP) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - state > cache_post"); - //nowhere to get data, abort. - LL_WARNS(LOG_TXT) << mID << " abort, nowhere to get data" << LL_ENDL; - return true ; - } - - if (mFetcher->mDebugPause) - { - return false; // debug: don't do any work - } - if (mID == mFetcher->mDebugID) - { - mFetcher->mDebugCount++; // for setting breakpoints - } - - if (mState != DONE) - { - mFetchDeltaTimer.reset(); - } - - if (mState == INIT) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - INIT"); - mStateTimer.reset(); - mFetchTimer.reset(); - for(auto i : LOGGED_STATES) - { - mStateTimersMap[i] = 0; - } - mSkippedStatesTime = 0; - mRawImage = NULL ; - mRequestedDiscard = -1; - mLoadedDiscard = -1; - mDecodedDiscard = -1; - mRequestedSize = 0; - mRequestedOffset = 0; - mFileSize = 0; - mCachedSize = 0; - mLoaded = false; - mSentRequest = UNSENT; - mDecoded = false; - mWritten = false; - if (mHttpBufferArray) - { - mHttpBufferArray->release(); - mHttpBufferArray = NULL; - } - mHttpReplySize = 0; - mHttpReplyOffset = 0; - mHaveAllData = false; - clearPackets(); // TODO: Shouldn't be necessary - mCacheReadHandle = LLTextureCache::nullHandle(); - mCacheWriteHandle = LLTextureCache::nullHandle(); - setState(LOAD_FROM_TEXTURE_CACHE); - mInCache = false; - mDesiredSize = llmax(mDesiredSize, TEXTURE_CACHE_ENTRY_SIZE); // min desired size is TEXTURE_CACHE_ENTRY_SIZE - LL_DEBUGS(LOG_TXT) << mID << ": Priority: " << llformat("%8.0f",mImagePriority) - << " Desired Discard: " << mDesiredDiscard << " Desired Size: " << mDesiredSize << LL_ENDL; - - // fall through - } - - if (mState == LOAD_FROM_TEXTURE_CACHE) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - LOAD_FROM_TEXTURE_CACHE"); - if (mCacheReadHandle == LLTextureCache::nullHandle()) - { - S32 offset = mFormattedImage.notNull() ? mFormattedImage->getDataSize() : 0; - S32 size = mDesiredSize - offset; - if (size <= 0) - { - setState(CACHE_POST); - return doWork(param); - // return false; - } - mFileSize = 0; - mLoaded = false; - - add(LLTextureFetch::sCacheAttempt, 1.0); - - if (mUrl.compare(0, 7, "file://") == 0) - { - // read file from local disk - ++mCacheReadCount; - std::string filename = mUrl.substr(7, std::string::npos); - CacheReadResponder* responder = new CacheReadResponder(mFetcher, mID, mFormattedImage); - mCacheReadTimer.reset(); - mCacheReadHandle = mFetcher->mTextureCache->readFromCache(filename, mID, offset, size, responder); - - } - else if ((mUrl.empty() || mFTType==FTT_SERVER_BAKE) && mFetcher->canLoadFromCache()) - { - ++mCacheReadCount; - CacheReadResponder* responder = new CacheReadResponder(mFetcher, mID, mFormattedImage); - mCacheReadTimer.reset(); - mCacheReadHandle = mFetcher->mTextureCache->readFromCache(mID, - offset, size, responder);; - } - else if(!mUrl.empty() && mCanUseHTTP) - { - setState(WAIT_HTTP_RESOURCE); - } - else - { - setState(LOAD_FROM_NETWORK); - } - } - - if (mLoaded) - { - // Make sure request is complete. *TODO: make this auto-complete - if (mFetcher->mTextureCache->readComplete(mCacheReadHandle, false)) - { - mCacheReadHandle = LLTextureCache::nullHandle(); - setState(CACHE_POST); - add(LLTextureFetch::sCacheHit, 1.0); - mCacheReadTime = mCacheReadTimer.getElapsedTimeF32(); - // fall through - } - else - { - // - //This should never happen - // - LL_DEBUGS(LOG_TXT) << mID << " this should never happen" << LL_ENDL; - return false; - } - } - else - { - return false; - } - } - - if (mState == CACHE_POST) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - CACHE_POST"); - mCachedSize = mFormattedImage.notNull() ? mFormattedImage->getDataSize() : 0; - // Successfully loaded - if ((mCachedSize >= mDesiredSize) || mHaveAllData) - { - // we have enough data, decode it - llassert_always(mFormattedImage->getDataSize() > 0); - mLoadedDiscard = mDesiredDiscard; - if (mLoadedDiscard < 0) - { - LL_WARNS(LOG_TXT) << mID << " mLoadedDiscard is " << mLoadedDiscard - << ", should be >=0" << LL_ENDL; - } - setState(DECODE_IMAGE); - mInCache = true; - mWriteToCacheState = NOT_WRITE ; - LL_DEBUGS(LOG_TXT) << mID << ": Cached. Bytes: " << mFormattedImage->getDataSize() - << " Size: " << llformat("%dx%d",mFormattedImage->getWidth(),mFormattedImage->getHeight()) - << " Desired Discard: " << mDesiredDiscard << " Desired Size: " << mDesiredSize << LL_ENDL; - record(LLTextureFetch::sCacheHitRate, LLUnits::Ratio::fromValue(1)); - } - else - { - if (mUrl.compare(0, 7, "file://") == 0) - { - // failed to load local file, we're done. - LL_WARNS(LOG_TXT) << mID << ": abort, failed to load local file " << mUrl << LL_ENDL; - return true; - } - // need more data - else - { - LL_DEBUGS(LOG_TXT) << mID << ": Not in Cache" << LL_ENDL; - setState(LOAD_FROM_NETWORK); - } - record(LLTextureFetch::sCacheHitRate, LLUnits::Ratio::fromValue(0)); - // fall through - } - } - - if (mState == LOAD_FROM_NETWORK) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - LOAD_FROM_NETWORK"); - // Check for retries to previous server failures. - F32 wait_seconds; - if (mFetchRetryPolicy.shouldRetry(wait_seconds)) - { - if (wait_seconds <= 0.0) - { - LL_INFOS(LOG_TXT) << mID << " retrying now" << LL_ENDL; - } - else - { - //LL_INFOS(LOG_TXT) << mID << " waiting to retry for " << wait_seconds << " seconds" << LL_ENDL; - return false; - } - } - - static LLCachedControl use_http(gSavedSettings, "ImagePipelineUseHTTP", true); - -// if (mHost.isInvalid()) get_url = false; - if ( use_http && mCanUseHTTP && mUrl.empty())//get http url. - { - LLViewerRegion* region = getRegion(); - if (region) - { - std::string http_url = region->getViewerAssetUrl(); - if (!http_url.empty()) - { - if (mFTType != FTT_DEFAULT) - { - LL_WARNS(LOG_TXT) << "Trying to fetch a texture of non-default type by UUID. This probably won't work!" << LL_ENDL; - } - setUrl(http_url + "/?texture_id=" + mID.asString().c_str()); - LL_DEBUGS(LOG_TXT) << "Texture URL: " << mUrl << LL_ENDL; - mWriteToCacheState = CAN_WRITE ; //because this texture has a fixed texture id. - mCanUseCapability = true; - mRegionRetryAttempt = 0; - mLastRegionId = region->getRegionID(); - } - else - { - mCanUseCapability = false; - mRegionRetryAttempt++; - mRegionRetryTimer.setTimerExpirySec(CAP_MISSING_EXPIRATION_DELAY); - // ex: waiting for caps - LL_INFOS_ONCE(LOG_TXT) << "Texture not available via HTTP: empty URL." << LL_ENDL; - } - } - else - { - mCanUseCapability = false; - mRegionRetryAttempt++; - mRegionRetryTimer.setTimerExpirySec(CAP_MISSING_EXPIRATION_DELAY); - // This will happen if not logged in or if a region deoes not have HTTP Texture enabled - //LL_WARNS(LOG_TXT) << "Region not found for host: " << mHost << LL_ENDL; - LL_INFOS_ONCE(LOG_TXT) << "Texture not available via HTTP: no region " << mUrl << LL_ENDL; - } - } - else if (mFTType == FTT_SERVER_BAKE) - { - mWriteToCacheState = CAN_WRITE; - } - - if (mCanUseCapability && mCanUseHTTP && !mUrl.empty()) - { - setState(WAIT_HTTP_RESOURCE); - if(mWriteToCacheState != NOT_WRITE) - { - mWriteToCacheState = CAN_WRITE ; - } - // don't return, fall through to next state - } - else - { - return false; - } - } - - if (mState == WAIT_HTTP_RESOURCE) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - WAIT_HTTP_RESOURCE"); - // NOTE: - // control the number of the http requests issued for: - // 1, not openning too many file descriptors at the same time; - // 2, control the traffic of http so udp gets bandwidth. - // - // If it looks like we're busy, keep this request here. - // Otherwise, advance into the HTTP states. - - if (!mHttpHasResource && // sometimes we get into this state when we already have an http resource, go ahead and send the request in that case - (mFetcher->getHttpWaitersCount() || ! acquireHttpSemaphore())) - { - setState(WAIT_HTTP_RESOURCE2); - mFetcher->addHttpWaiter(this->mID); - ++mResourceWaitCount; - return false; - } - - setState(SEND_HTTP_REQ); - // *NOTE: You must invoke releaseHttpSemaphore() if you transition - // to a state other than SEND_HTTP_REQ or WAIT_HTTP_REQ or abort - // the request. - } - - if (mState == WAIT_HTTP_RESOURCE2) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - WAIT_HTTP_RESOURCE2"); - // Just idle it if we make it to the head... - return false; - } - - if (mState == SEND_HTTP_REQ) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - SEND_HTTP_REQ"); - // Also used in llmeshrepository - static LLCachedControl disable_range_req(gSavedSettings, "HttpRangeRequestsDisable", false); - - if (! mCanUseHTTP) - { - releaseHttpSemaphore(); - LL_WARNS(LOG_TXT) << mID << " abort: SEND_HTTP_REQ but !mCanUseHTTP" << LL_ENDL; - return true; // abort - } - - S32 cur_size = 0; - if (mFormattedImage.notNull()) - { - cur_size = mFormattedImage->getDataSize(); // amount of data we already have - if (mFormattedImage->getDiscardLevel() == 0) - { - if (cur_size > 0) - { - // We already have all the data, just decode it - mLoadedDiscard = mFormattedImage->getDiscardLevel(); - if (mLoadedDiscard < 0) - { - LL_WARNS(LOG_TXT) << mID << " mLoadedDiscard is " << mLoadedDiscard - << ", should be >=0" << LL_ENDL; - } - setState(DECODE_IMAGE); - releaseHttpSemaphore(); - //return false; - return doWork(param); - } - else - { - releaseHttpSemaphore(); - LL_WARNS(LOG_TXT) << mID << " SEND_HTTP_REQ abort: cur_size " << cur_size << " <=0" << LL_ENDL; - return true; // abort. - } - } - } - mRequestedSize = mDesiredSize; - mRequestedDiscard = mDesiredDiscard; - mRequestedSize -= cur_size; - mRequestedOffset = cur_size; - if (mRequestedOffset) - { - // Texture fetching often issues 'speculative' loads that - // start beyond the end of the actual asset. Some cache/web - // systems, e.g. Varnish, will respond to this not with a - // 416 but with a 200 and the entire asset in the response - // body. By ensuring that we always have a partially - // satisfiable Range request, we avoid that hit to the network. - // We just have to deal with the overlapping data which is made - // somewhat harder by the fact that grid services don't necessarily - // return the Content-Range header on 206 responses. *Sigh* - mRequestedOffset -= 1; - mRequestedSize += 1; - } - mHttpHandle = LLCORE_HTTP_HANDLE_INVALID; - - if (mUrl.empty()) - { - // *FIXME: This should not be reachable except it has become - // so after some recent 'work'. Need to track this down - // and illuminate the unenlightened. - LL_WARNS(LOG_TXT) << "HTTP GET request failed for " << mID - << " on empty URL." << LL_ENDL; - resetFormattedData(); - releaseHttpSemaphore(); - return true; // failed - } - - mRequestedDeltaTimer.reset(); - mLoaded = false; - mGetStatus = LLCore::HttpStatus(); - mGetReason.clear(); - LL_DEBUGS(LOG_TXT) << "HTTP GET: " << mID << " Offset: " << mRequestedOffset - << " Bytes: " << mRequestedSize - << " Bandwidth(kbps): " << mFetcher->getTextureBandwidth() << "/" << mFetcher->mMaxBandwidth - << LL_ENDL; - - // Will call callbackHttpGet when curl request completes - // Only server bake images use the returned headers currently, for getting retry-after field. - LLCore::HttpOptions::ptr_t options = (mFTType == FTT_SERVER_BAKE) ? mFetcher->mHttpOptionsWithHeaders: mFetcher->mHttpOptions; - if (disable_range_req) - { - // 'Range:' requests may be disabled in which case all HTTP - // texture fetches result in full fetches. This can be used - // by people with questionable ISPs or networking gear that - // doesn't handle these well. - mHttpHandle = mFetcher->mHttpRequest->requestGet(mHttpPolicyClass, - mUrl, - options, - mFetcher->mHttpHeaders, - LLCore::HttpHandler::ptr_t(this, &NoOpDeletor)); - } - else - { - mHttpHandle = mFetcher->mHttpRequest->requestGetByteRange(mHttpPolicyClass, - mUrl, - mRequestedOffset, - (mRequestedOffset + mRequestedSize) > HTTP_REQUESTS_RANGE_END_MAX - ? 0 - : mRequestedSize, - options, - mFetcher->mHttpHeaders, - LLCore::HttpHandler::ptr_t(this, &NoOpDeletor)); - } - if (LLCORE_HTTP_HANDLE_INVALID == mHttpHandle) - { - LLCore::HttpStatus status(mFetcher->mHttpRequest->getStatus()); - LL_WARNS(LOG_TXT) << "HTTP GET request failed for " << mID - << ", Status: " << status.toTerseString() - << " Reason: '" << status.toString() << "'" - << LL_ENDL; - resetFormattedData(); - releaseHttpSemaphore(); - return true; // failed - } - - mHttpActive = true; - mFetcher->addToHTTPQueue(mID); - recordTextureStart(true); - setState(WAIT_HTTP_REQ); - - // fall through - } - - if (mState == WAIT_HTTP_REQ) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - WAIT_HTTP_REQ"); - // *NOTE: As stated above, all transitions out of this state should - // call releaseHttpSemaphore(). - if (mLoaded) - { - S32 cur_size = mFormattedImage.notNull() ? mFormattedImage->getDataSize() : 0; - if (mRequestedSize < 0) - { - if (http_not_found == mGetStatus) - { - if (mFTType != FTT_MAP_TILE) - { - LL_WARNS(LOG_TXT) << "Texture missing from server (404): " << mUrl << LL_ENDL; - } - - if(mWriteToCacheState == NOT_WRITE) //map tiles or server bakes - { - setState(DONE); - releaseHttpSemaphore(); - if (mFTType != FTT_MAP_TILE) - { - LL_WARNS(LOG_TXT) << mID << " abort: WAIT_HTTP_REQ not found" << LL_ENDL; - } - return true; - } - - if (mCanUseHTTP && !mUrl.empty() && cur_size <= 0) - { - LLViewerRegion* region = getRegion(); - if (!region || mLastRegionId != region->getRegionID()) - { - // cap failure? try on new region. - mUrl.clear(); - ++mRetryAttempt; - mLastRegionId.setNull(); - setState(INIT); - return false; - } - } - } - else if (http_service_unavail == mGetStatus) - { - LL_INFOS_ONCE(LOG_TXT) << "Texture server busy (503): " << mUrl << LL_ENDL; - if (mCanUseHTTP && !mUrl.empty() && cur_size <= 0) - { - LLViewerRegion* region = getRegion(); - if (!region || mLastRegionId != region->getRegionID()) - { - // try on new region. - mUrl.clear(); - ++mRetryAttempt; - mLastRegionId.setNull(); - setState(INIT); - return false; - } - } - } - else if (http_not_sat == mGetStatus) - { - // Allowed, we'll accept whatever data we have as complete. - mHaveAllData = true; - } - else - { - LL_INFOS(LOG_TXT) << "HTTP GET failed for: " << mUrl - << " Status: " << mGetStatus.toTerseString() - << " Reason: '" << mGetReason << "'" - << LL_ENDL; - } - - if (mFTType != FTT_SERVER_BAKE && mFTType != FTT_MAP_TILE) - { - mUrl.clear(); - } - if (cur_size > 0) - { - // Use available data - mLoadedDiscard = mFormattedImage->getDiscardLevel(); - if (mLoadedDiscard < 0) - { - LL_WARNS(LOG_TXT) << mID << " mLoadedDiscard is " << mLoadedDiscard - << ", should be >=0" << LL_ENDL; - } - setState(DECODE_IMAGE); - releaseHttpSemaphore(); - //return false; - return doWork(param); - } - - // Fail harder - resetFormattedData(); - setState(DONE); - releaseHttpSemaphore(); - LL_WARNS(LOG_TXT) << mID << " abort: fail harder" << LL_ENDL; - return true; // failed - } - - // Clear the url since we're done with the fetch - // Note: mUrl is used to check is fetching is required so failure to clear it will force an http fetch - // next time the texture is requested, even if the data have already been fetched. - if(mWriteToCacheState != NOT_WRITE && mFTType != FTT_SERVER_BAKE) - { - // Why do we want to keep url if NOT_WRITE - is this a proxy for map tiles? - mUrl.clear(); - } - - if (! mHttpBufferArray || ! mHttpBufferArray->size()) - { - // no data received. - if (mHttpBufferArray) - { - mHttpBufferArray->release(); - mHttpBufferArray = NULL; - } - - // abort. - setState(DONE); - LL_WARNS(LOG_TXT) << mID << " abort: no data received" << LL_ENDL; - releaseHttpSemaphore(); - return true; - } - - S32 append_size(mHttpBufferArray->size()); - S32 total_size(cur_size + append_size); - S32 src_offset(0); - llassert_always(append_size == mRequestedSize); - if (mHttpReplyOffset && mHttpReplyOffset != cur_size) - { - // In case of a partial response, our offset may - // not be trivially contiguous with the data we have. - // Get back into alignment. - if ( (mHttpReplyOffset > cur_size) || (cur_size > mHttpReplyOffset + append_size)) - { - LL_WARNS(LOG_TXT) << "Partial HTTP response produces break in image data for texture " - << mID << ". Aborting load." << LL_ENDL; - setState(DONE); - releaseHttpSemaphore(); - return true; - } - src_offset = cur_size - mHttpReplyOffset; - append_size -= src_offset; - total_size -= src_offset; - mRequestedSize -= src_offset; // Make requested values reflect useful part - mRequestedOffset += src_offset; - } - - U8 * buffer = (U8 *)ll_aligned_malloc_16(total_size); - if (!buffer) - { - // abort. If we have no space for packet, we have not enough space to decode image - setState(DONE); - LL_WARNS(LOG_TXT) << mID << " abort: out of memory" << LL_ENDL; - releaseHttpSemaphore(); - return true; - } - - if (mFormattedImage.isNull()) - { - // For now, create formatted image based on extension - std::string extension = gDirUtilp->getExtension(mUrl); - mFormattedImage = LLImageFormatted::createFromType(LLImageBase::getCodecFromExtension(extension)); - if (mFormattedImage.isNull()) - { - mFormattedImage = new LLImageJ2C; // default - } - } - - LLImageDataLock lock(mFormattedImage); - - if (mHaveAllData) //the image file is fully loaded. - { - mFileSize = total_size; - } - else //the file size is unknown. - { - mFileSize = total_size + 1 ; //flag the file is not fully loaded. - } - - if (cur_size > 0) - { - // Copy previously collected data into buffer - memcpy(buffer, mFormattedImage->getData(), cur_size); - } - mHttpBufferArray->read(src_offset, (char *) buffer + cur_size, append_size); - - // NOTE: setData releases current data and owns new data (buffer) - mFormattedImage->setData(buffer, total_size); - - // Done with buffer array - mHttpBufferArray->release(); - mHttpBufferArray = NULL; - mHttpReplySize = 0; - mHttpReplyOffset = 0; - - mLoadedDiscard = mRequestedDiscard; - if (mLoadedDiscard < 0) - { - LL_WARNS(LOG_TXT) << mID << " mLoadedDiscard is " << mLoadedDiscard - << ", should be >=0" << LL_ENDL; - } - setState(DECODE_IMAGE); - if (mWriteToCacheState != NOT_WRITE) - { - mWriteToCacheState = SHOULD_WRITE ; - } - releaseHttpSemaphore(); - //return false; - return doWork(param); - } - else - { - // *HISTORY: There was a texture timeout test here originally that - // would cancel a request that was over 120 seconds old. That's - // probably not a good idea. Particularly rich regions can take - // an enormous amount of time to load textures. We'll revisit the - // various possible timeout components (total request time, connection - // time, I/O time, with and without retries, etc.) in the future. - - return false; - } - } - - if (mState == DECODE_IMAGE) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - DECODE_IMAGE"); - static LLCachedControl textures_decode_disabled(gSavedSettings, "TextureDecodeDisabled", false); - - if (textures_decode_disabled) - { - // for debug use, don't decode - setState(DONE); - return true; - } - - if (mDesiredDiscard < 0) - { - // We aborted, don't decode - setState(DONE); - LL_DEBUGS(LOG_TXT) << mID << " DECODE_IMAGE abort: desired discard " << mDesiredDiscard << "<0" << LL_ENDL; - return true; - } - - if (mFormattedImage->getDataSize() <= 0) - { - LL_WARNS(LOG_TXT) << "Decode entered with invalid mFormattedImage. ID = " << mID << LL_ENDL; - - //abort, don't decode - setState(DONE); - LL_DEBUGS(LOG_TXT) << mID << " DECODE_IMAGE abort: (mFormattedImage->getDataSize() <= 0)" << LL_ENDL; - return true; - } - if (mLoadedDiscard < 0) - { - LL_WARNS(LOG_TXT) << "Decode entered with invalid mLoadedDiscard. ID = " << mID << LL_ENDL; - - //abort, don't decode - setState(DONE); - LL_DEBUGS(LOG_TXT) << mID << " DECODE_IMAGE abort: mLoadedDiscard < 0" << LL_ENDL; - return true; - } - mDecodeTimer.reset(); - mRawImage = NULL; - mAuxImage = NULL; - llassert_always(mFormattedImage.notNull()); - S32 discard = mHaveAllData ? 0 : mLoadedDiscard; - mDecoded = false; - setState(DECODE_IMAGE_UPDATE); - LL_DEBUGS(LOG_TXT) << mID << ": Decoding. Bytes: " << mFormattedImage->getDataSize() << " Discard: " << discard - << " All Data: " << mHaveAllData << LL_ENDL; - - // In case worked manages to request decode, be shut down, - // then init and request decode again with first decode - // still in progress, assign a sufficiently unique id - mDecodeHandle = LLAppViewer::getImageDecodeThread()->decodeImage(mFormattedImage, - discard, - mNeedsAux, - new DecodeResponder(mFetcher, mID, this)); - if (mDecodeHandle == 0) - { - // Abort, failed to put into queue. - // Happens if viewer is shutting down - setState(DONE); - LL_DEBUGS(LOG_TXT) << mID << " DECODE_IMAGE abort: failed to post for decoding" << LL_ENDL; - return true; - } - // fall though - } - - if (mState == DECODE_IMAGE_UPDATE) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - DECODE_IMAGE_UPDATE"); - if (mDecoded) - { - mDecodeTime = mDecodeTimer.getElapsedTimeF32(); - - if (mDecodedDiscard < 0) - { - if (mCachedSize > 0 && !mInLocalCache && mRetryAttempt == 0) - { - // Cache file should be deleted, try again - LL_DEBUGS(LOG_TXT) << mID << ": Decode of cached file failed (removed), retrying" << LL_ENDL; - llassert_always(mDecodeHandle == 0); - mFormattedImage = NULL; - ++mRetryAttempt; - setState(INIT); - //return false; - return doWork(param); - } - else - { - LL_DEBUGS(LOG_TXT) << "Failed to Decode image " << mID << " after " << mRetryAttempt << " retries" << LL_ENDL; - setState(DONE); // failed - } - } - else - { - llassert_always(mRawImage.notNull()); - LL_DEBUGS(LOG_TXT) << mID << ": Decoded. Discard: " << mDecodedDiscard - << " Raw Image: " << llformat("%dx%d",mRawImage->getWidth(),mRawImage->getHeight()) << LL_ENDL; - setState(WRITE_TO_CACHE); - } - // fall through - } - else - { - return false; - } - } - - if (mState == WRITE_TO_CACHE) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - WRITE_TO_CACHE"); - if (mWriteToCacheState != SHOULD_WRITE || mFormattedImage.isNull()) - { - // If we're in a local cache or we didn't actually receive any new data, - // or we failed to load anything, skip - setState(DONE); - //return false; - return doWork(param); - } - - LLImageDataSharedLock lock(mFormattedImage); - - S32 datasize = mFormattedImage->getDataSize(); - if(mFileSize < datasize)//This could happen when http fetching and sim fetching mixed. - { - if(mHaveAllData) - { - mFileSize = datasize ; - } - else - { - mFileSize = datasize + 1 ; //flag not fully loaded. - } - } - llassert_always(datasize); - mWritten = false; - setState(WAIT_ON_WRITE); - ++mCacheWriteCount; - CacheWriteResponder* responder = new CacheWriteResponder(mFetcher, mID); - // This call might be under work mutex, but mRawImage is not nessesary safe here. - // If something retrieves it via getRequestFinished() and modifies, image won't - // be protected by work mutex and won't be safe to use here nor in cache worker. - // So make sure users of getRequestFinished() does not attempt to modify image while - // fetcher is working - mCacheWriteTimer.reset(); - mCacheWriteHandle = mFetcher->mTextureCache->writeToCache(mID, - mFormattedImage->getData(), datasize, - mFileSize, mRawImage, mDecodedDiscard, responder); - // fall through - } - - if (mState == WAIT_ON_WRITE) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - WAIT_ON_WRITE"); - if (writeToCacheComplete()) - { - mCacheWriteTime = mCacheWriteTimer.getElapsedTimeF32(); - setState(DONE); - // fall through - } - else - { - if (mDesiredDiscard < mDecodedDiscard) - { - // We're waiting for this write to complete before we can receive more data - // (we can't touch mFormattedImage until the write completes) - // Prioritize the write - mFetcher->mTextureCache->prioritizeWrite(mCacheWriteHandle); - } - return false; - } - } - - if (mState == DONE) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - DONE"); - if (mDecodedDiscard >= 0 && mDesiredDiscard < mDecodedDiscard) - { - // More data was requested, return to INIT - setState(INIT); - LL_DEBUGS(LOG_TXT) << mID << " more data requested, returning to INIT: " - << " mDecodedDiscard " << mDecodedDiscard << ">= 0 && mDesiredDiscard " << mDesiredDiscard - << "<" << " mDecodedDiscard " << mDecodedDiscard << LL_ENDL; - // return false; - return doWork(param); - } - else - { - mFetchTime = mFetchTimer.getElapsedTimeF32(); - return true; - } - } - - return false; -} // -Mw - -// Threads: Ttf -// virtual -void LLTextureFetchWorker::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) -{ - LL_PROFILE_ZONE_SCOPED; - static LLCachedControl log_to_viewer_log(gSavedSettings, "LogTextureDownloadsToViewerLog", false); - static LLCachedControl log_to_sim(gSavedSettings, "LogTextureDownloadsToSimulator", false); - static LLCachedControl log_texture_traffic(gSavedSettings, "LogTextureNetworkTraffic", false) ; - - LLMutexLock lock(&mWorkMutex); // +Mw - - mHttpActive = false; - - if (log_to_viewer_log || log_to_sim) - { - mFetcher->mTextureInfo.setRequestStartTime(mID, mMetricsStartTime.value()); - mFetcher->mTextureInfo.setRequestType(mID, LLTextureInfoDetails::REQUEST_TYPE_HTTP); - mFetcher->mTextureInfo.setRequestSize(mID, mRequestedSize); - mFetcher->mTextureInfo.setRequestOffset(mID, mRequestedOffset); - mFetcher->mTextureInfo.setRequestCompleteTimeAndLog(mID, LLTimer::getTotalTime()); - } - - static LLCachedControl fake_failure_rate(gSavedSettings, "TextureFetchFakeFailureRate", 0.0f); - F32 rand_val = ll_frand(); - F32 rate = fake_failure_rate; - if (mFTType == FTT_SERVER_BAKE && (fake_failure_rate > 0.0) && (rand_val < fake_failure_rate)) - { - LL_WARNS(LOG_TXT) << mID << " for debugging, setting fake failure status for texture " << mID - << " (rand was " << rand_val << "/" << rate << ")" << LL_ENDL; - response->setStatus(LLCore::HttpStatus(503)); - } - bool success = true; - bool partial = false; - LLCore::HttpStatus status(response->getStatus()); - if (!status && (mFTType == FTT_SERVER_BAKE)) - { - LL_INFOS(LOG_TXT) << mID << " state " << e_state_name[mState] << LL_ENDL; - mFetchRetryPolicy.onFailure(response); - F32 retry_after; - if (mFetchRetryPolicy.shouldRetry(retry_after)) - { - LL_INFOS(LOG_TXT) << mID << " will retry after " << retry_after << " seconds, resetting state to LOAD_FROM_NETWORK" << LL_ENDL; - mFetcher->removeFromHTTPQueue(mID, S32Bytes(0)); - std::string reason(status.toString()); - setGetStatus(status, reason); - releaseHttpSemaphore(); - setState(LOAD_FROM_NETWORK); - return; - } - else - { - LL_INFOS(LOG_TXT) << mID << " will not retry" << LL_ENDL; - } - } - else - { - mFetchRetryPolicy.onSuccess(); - } - - std::string reason(status.toString()); - setGetStatus(status, reason); - LL_DEBUGS(LOG_TXT) << "HTTP COMPLETE: " << mID - << " status: " << status.toTerseString() - << " '" << reason << "'" - << LL_ENDL; - - if (! status) - { - success = false; - if (mFTType != FTT_MAP_TILE) // missing map tiles are normal, don't complain about them. - { - LL_WARNS(LOG_TXT) << "CURL GET FAILED, status: " << status.toTerseString() - << " reason: " << reason << LL_ENDL; - } - } - else - { - // A warning about partial (HTTP 206) data. Some grid services - // do *not* return a 'Content-Range' header in the response to - // Range requests with a 206 status. We're forced to assume - // we get what we asked for in these cases until we can fix - // the services. - static const LLCore::HttpStatus par_status(HTTP_PARTIAL_CONTENT); - - partial = (par_status == status); - } - - S32BytesImplicit data_size = callbackHttpGet(response, partial, success); - - if (log_texture_traffic && data_size > 0) - { - // one worker per multiple textures - std::vector textures; - LLViewerTextureManager::findTextures(mID, textures); - std::vector::iterator iter = textures.begin(); - while (iter != textures.end()) - { - LLViewerTexture* tex = *iter++; - if (tex) - { - gTotalTextureBytesPerBoostLevel[tex->getBoostLevel()] += data_size; - } - } - } - - mFetcher->removeFromHTTPQueue(mID, data_size); - - recordTextureDone(true, data_size); -} // -Mw - - -// Threads: Tmain -void LLTextureFetchWorker::endWork(S32 param, bool aborted) -{ - LL_PROFILE_ZONE_SCOPED; - if (mDecodeHandle != 0) - { - // LL::ThreadPool has no operation to cancel a particular work item - mDecodeHandle = 0; - } - mFormattedImage = NULL; -} - -////////////////////////////////////////////////////////////////////////////// - -// Threads: Ttf - -// virtual -void LLTextureFetchWorker::finishWork(S32 param, bool completed) -{ - LL_PROFILE_ZONE_SCOPED; - // The following are required in case the work was aborted - if (mCacheReadHandle != LLTextureCache::nullHandle()) - { - mFetcher->mTextureCache->readComplete(mCacheReadHandle, true); - mCacheReadHandle = LLTextureCache::nullHandle(); - } - if (mCacheWriteHandle != LLTextureCache::nullHandle()) - { - mFetcher->mTextureCache->writeComplete(mCacheWriteHandle, true); - mCacheWriteHandle = LLTextureCache::nullHandle(); - } -} - -// LLQueuedThread's update() method is asking if it's okay to -// delete this worker. You'll notice we're not locking in here -// which is a slight concern. Caller is expected to have made -// this request 'quiet' by whatever means... -// -// Threads: Tmain - -// virtual -bool LLTextureFetchWorker::deleteOK() -{ - bool delete_ok = true; - - if (mHttpActive) - { - // HTTP library has a pointer to this worker - // and will dereference it to do notification. - delete_ok = false; - } - - if (WAIT_HTTP_RESOURCE2 == mState) - { - if (mFetcher->isHttpWaiter(mID)) - { - // Don't delete the worker out from under the releaseHttpWaiters() - // method. Keep the pointers valid, clean up after that method - // has recognized the cancelation and removed the UUID from the - // waiter list. - delete_ok = false; - } - } - - // Allow any pending reads or writes to complete - if (mCacheReadHandle != LLTextureCache::nullHandle()) - { - if (mFetcher->mTextureCache->readComplete(mCacheReadHandle, true)) - { - mCacheReadHandle = LLTextureCache::nullHandle(); - } - else - { - delete_ok = false; - } - } - if (mCacheWriteHandle != LLTextureCache::nullHandle()) - { - if (mFetcher->mTextureCache->writeComplete(mCacheWriteHandle)) - { - mCacheWriteHandle = LLTextureCache::nullHandle(); - } - else - { - delete_ok = false; - } - } - - if ((haveWork() && - // not ok to delete from these states - ((mState >= WRITE_TO_CACHE && mState <= WAIT_ON_WRITE)))) - { - delete_ok = false; - } - - return delete_ok; -} - -// Threads: Ttf -void LLTextureFetchWorker::removeFromCache() -{ - if (!mInLocalCache) - { - mFetcher->mTextureCache->removeFromCache(mID); - } -} - - -////////////////////////////////////////////////////////////////////////////// - -// Threads: Ttf -// Locks: Mw -S32 LLTextureFetchWorker::callbackHttpGet(LLCore::HttpResponse * response, - bool partial, bool success) -{ - S32 data_size = 0 ; - - if (mState != WAIT_HTTP_REQ) - { - LL_WARNS(LOG_TXT) << "callbackHttpGet for unrequested fetch worker: " << mID - << " req=" << mSentRequest << " state= " << mState << LL_ENDL; - return data_size; - } - if (mLoaded) - { - LL_WARNS(LOG_TXT) << "Duplicate callback for " << mID.asString() << LL_ENDL; - return data_size ; // ignore duplicate callback - } - if (success) - { - // get length of stream: - LLCore::BufferArray * body(response->getBody()); - data_size = body ? body->size() : 0; - - LL_DEBUGS(LOG_TXT) << "HTTP RECEIVED: " << mID.asString() << " Bytes: " << data_size << LL_ENDL; - if (data_size > 0) - { - // *TODO: set the formatted image data here directly to avoid the copy - - // Hold on to body for later copy - llassert_always(NULL == mHttpBufferArray); - body->addRef(); - mHttpBufferArray = body; - - if (partial) - { - unsigned int offset(0), length(0), full_length(0); - response->getRange(&offset, &length, &full_length); - if (! offset && ! length) - { - // This is the case where we receive a 206 status but - // there wasn't a useful Content-Range header in the response. - // This could be because it was badly formatted but is more - // likely due to capabilities services which scrub headers - // from responses. Assume we got what we asked for... - mHttpReplySize = data_size; - mHttpReplyOffset = mRequestedOffset; - } - else - { - mHttpReplySize = length; - mHttpReplyOffset = offset; - } - } - - if (! partial) - { - // Response indicates this is the entire asset regardless - // of our asking for a byte range. Mark it so and drop - // any partial data we might have so that the current - // response body becomes the entire dataset. - if (data_size <= mRequestedOffset) - { - LL_WARNS(LOG_TXT) << "Fetched entire texture " << mID - << " when it was expected to be marked complete. mImageSize: " - << mFileSize << " datasize: " << mFormattedImage->getDataSize() - << LL_ENDL; - } - mHaveAllData = true; - llassert_always(mDecodeHandle == 0); - mFormattedImage = NULL; // discard any previous data we had - } - else if (data_size < mRequestedSize) - { - mHaveAllData = true; - } - else if (data_size > mRequestedSize) - { - // *TODO: This shouldn't be happening any more (REALLY don't expect this anymore) - LL_WARNS(LOG_TXT) << "data_size = " << data_size << " > requested: " << mRequestedSize << LL_ENDL; - mHaveAllData = true; - llassert_always(mDecodeHandle == 0); - mFormattedImage = NULL; // discard any previous data we had - } - } - else - { - // We requested data but received none (and no error), - // so presumably we have all of it - mHaveAllData = true; - } - mRequestedSize = data_size; - - if (mHaveAllData) - { - LLViewerStatsRecorder::instance().textureFetch(); - } - - // *TODO: set the formatted image data here directly to avoid the copy - } - else - { - mRequestedSize = -1; // error - } - - mLoaded = true; - - return data_size ; -} - -////////////////////////////////////////////////////////////////////////////// - -// Threads: Ttc -void LLTextureFetchWorker::callbackCacheRead(bool success, LLImageFormatted* image, - S32 imagesize, bool islocal) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - LLMutexLock lock(&mWorkMutex); // +Mw - if (mState != LOAD_FROM_TEXTURE_CACHE) - { -// LL_WARNS(LOG_TXT) << "Read callback for " << mID << " with state = " << mState << LL_ENDL; - return; - } - if (success) - { - llassert_always(imagesize >= 0); - mFileSize = imagesize; - mFormattedImage = image; - mImageCodec = image->getCodec(); - mInLocalCache = islocal; - if (mFileSize != 0 && mFormattedImage->getDataSize() >= mFileSize) - { - mHaveAllData = true; - } - } - mLoaded = true; -} // -Mw - -// Threads: Ttc -void LLTextureFetchWorker::callbackCacheWrite(bool success) -{ - LLMutexLock lock(&mWorkMutex); // +Mw - if (mState != WAIT_ON_WRITE) - { -// LL_WARNS(LOG_TXT) << "Write callback for " << mID << " with state = " << mState << LL_ENDL; - return; - } - mWritten = true; -} // -Mw - -////////////////////////////////////////////////////////////////////////////// - -// Threads: Tid -void LLTextureFetchWorker::callbackDecoded(bool success, const std::string &error_message, LLImageRaw* raw, LLImageRaw* aux, S32 decode_id) -{ - LLMutexLock lock(&mWorkMutex); // +Mw - if (mDecodeHandle == 0) - { - return; // aborted, ignore - } - if (mDecodeHandle != decode_id) - { - // Queue doesn't support canceling old requests. - // This shouldn't normally happen, but in case it's possible that a worked - // will request decode, be aborted, reinited then start a new decode - LL_DEBUGS(LOG_TXT) << mID << " received obsolete decode's callback" << LL_ENDL; - return; // ignore - } - if (mState != DECODE_IMAGE_UPDATE) - { - LL_DEBUGS(LOG_TXT) << "Decode callback for " << mID << " with state = " << mState << LL_ENDL; - mDecodeHandle = 0; - return; - } - llassert_always(mFormattedImage.notNull()); - - mDecodeHandle = 0; - if (success) - { - llassert_always(raw); - mRawImage = raw; - mAuxImage = aux; - mDecodedDiscard = mFormattedImage->getDiscardLevel(); - LL_DEBUGS(LOG_TXT) << mID << ": Decode Finished. Discard: " << mDecodedDiscard - << " Raw Image: " << llformat("%dx%d",mRawImage->getWidth(),mRawImage->getHeight()) << LL_ENDL; - } - else - { - LL_WARNS(LOG_TXT) << "DECODE FAILED: " << mID << " Discard: " << (S32)mFormattedImage->getDiscardLevel() << ", reason: " << error_message << LL_ENDL; - removeFromCache(); - mDecodedDiscard = -1; // Redundant, here for clarity and paranoia - } - mDecoded = true; -// LL_INFOS(LOG_TXT) << mID << " : DECODE COMPLETE " << LL_ENDL; -} // -Mw - -////////////////////////////////////////////////////////////////////////////// - -// Threads: Ttf -bool LLTextureFetchWorker::writeToCacheComplete() -{ - // Complete write to cache - if (mCacheWriteHandle != LLTextureCache::nullHandle()) - { - if (!mWritten) - { - return false; - } - if (mFetcher->mTextureCache->writeComplete(mCacheWriteHandle)) - { - mCacheWriteHandle = LLTextureCache::nullHandle(); - } - else - { - return false; - } - } - return true; -} - - -// Threads: Ttf -void LLTextureFetchWorker::recordTextureStart(bool is_http) -{ - if (! mMetricsStartTime.value()) - { - mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp(); - } - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_TEXTURE, - is_http, - LLImageBase::TYPE_AVATAR_BAKE == mType); -} - - -// Threads: Ttf -void LLTextureFetchWorker::recordTextureDone(bool is_http, F64 byte_count) -{ - if (mMetricsStartTime.value()) - { - LLViewerAssetStatsFF::record_response(LLViewerAssetType::AT_TEXTURE, - is_http, - LLImageBase::TYPE_AVATAR_BAKE == mType, - LLViewerAssetStatsFF::get_timestamp() - mMetricsStartTime, - byte_count); - mMetricsStartTime = (U32Seconds)0; - } - LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_TEXTURE, - is_http, - LLImageBase::TYPE_AVATAR_BAKE == mType); -} - - -////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////// -// public - -std::string LLTextureFetch::getStateString(S32 state) -{ - if (state < 0 || state > sizeof(e_state_name) / sizeof(char*)) - { - return llformat("%d", state); - } - - return e_state_name[state]; -} - -LLTextureFetch::LLTextureFetch(LLTextureCache* cache, bool threaded, bool qa_mode) - : LLWorkerThread("TextureFetch", threaded, true), - mDebugCount(0), - mDebugPause(false), - mPacketCount(0), - mBadPacketCount(0), - mQueueMutex(), - mNetworkQueueMutex(), - mTextureCache(cache), - mTextureBandwidth(0), - mHTTPTextureBits(0), - mTotalHTTPRequests(0), - mQAMode(qa_mode), - mHttpRequest(NULL), - mHttpOptions(), - mHttpOptionsWithHeaders(), - mHttpHeaders(), - mHttpPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), - mHttpMetricsHeaders(), - mHttpMetricsPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), - mTotalCacheReadCount(0U), - mTotalCacheWriteCount(0U), - mTotalResourceWaitCount(0U), - mFetchSource(LLTextureFetch::FROM_ALL), - mOriginFetchSource(LLTextureFetch::FROM_ALL), - mTextureInfoMainThread(false) -{ - mMaxBandwidth = gSavedSettings.getF32("ThrottleBandwidthKBPS"); - mTextureInfo.setLogging(true); - - LLAppCoreHttp & app_core_http(LLAppViewer::instance()->getAppCoreHttp()); - mHttpRequest = new LLCore::HttpRequest; - mHttpOptions = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); - mHttpOptionsWithHeaders = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); - mHttpOptionsWithHeaders->setWantHeaders(true); - mHttpHeaders = LLCore::HttpHeaders::ptr_t(new LLCore::HttpHeaders); - mHttpHeaders->append(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_IMAGE_X_J2C); - mHttpPolicyClass = app_core_http.getPolicy(LLAppCoreHttp::AP_TEXTURE); - mHttpMetricsHeaders = LLCore::HttpHeaders::ptr_t(new LLCore::HttpHeaders); - mHttpMetricsHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_LLSD_XML); - mHttpMetricsPolicyClass = app_core_http.getPolicy(LLAppCoreHttp::AP_REPORTING); - mHttpHighWater = HTTP_NONPIPE_REQUESTS_HIGH_WATER; - mHttpLowWater = HTTP_NONPIPE_REQUESTS_LOW_WATER; - mHttpSemaphore = 0; - - // If that test log has ben requested but not yet created, create it - if (LLMetricPerformanceTesterBasic::isMetricLogRequested(sTesterName) && !LLMetricPerformanceTesterBasic::getTester(sTesterName)) - { - sTesterp = new LLTextureFetchTester() ; - if (!sTesterp->isValid()) - { - delete sTesterp; - sTesterp = NULL; - } - } -} - -LLTextureFetch::~LLTextureFetch() -{ - clearDeleteList(); - - while (! mCommands.empty()) - { - TFRequest * req(mCommands.front()); - mCommands.erase(mCommands.begin()); - delete req; - } - - mHttpWaitResource.clear(); - - delete mHttpRequest; - mHttpRequest = NULL; - - // ~LLQueuedThread() called here -} - -S32 LLTextureFetch::createRequest(FTType f_type, const std::string& url, const LLUUID& id, const LLHost& host, F32 priority, - S32 w, S32 h, S32 c, S32 desired_discard, bool needs_aux, bool can_use_http) -{ - LL_PROFILE_ZONE_SCOPED; - if (mDebugPause) - { - return -1; - } - - if (f_type == FTT_SERVER_BAKE) - { - LL_DEBUGS("Avatar") << " requesting " << id << " " << w << "x" << h << " discard " << desired_discard << " type " << f_type << LL_ENDL; - } - LLTextureFetchWorker* worker = getWorker(id) ; - if (worker) - { - if (worker->mHost != host) - { - LL_WARNS(LOG_TXT) << "LLTextureFetch::createRequest " << id << " called with multiple hosts: " - << host << " != " << worker->mHost << LL_ENDL; - removeRequest(worker, true); - worker = NULL; - return -1; - } - } - - S32 desired_size; - std::string exten = gDirUtilp->getExtension(url); - //if (f_type == FTT_SERVER_BAKE) - if ((f_type == FTT_SERVER_BAKE) && !url.empty() && !exten.empty() && (LLImageBase::getCodecFromExtension(exten) != IMG_CODEC_J2C)) - { - // SH-4030: This case should be redundant with the following one, just - // breaking it out here to clarify that it's intended behavior. - llassert(!url.empty() && (!exten.empty() && LLImageBase::getCodecFromExtension(exten) != IMG_CODEC_J2C)); - - // Do full requests for baked textures to reduce interim blurring. - LL_DEBUGS(LOG_TXT) << "full request for " << id << " texture is FTT_SERVER_BAKE" << LL_ENDL; - desired_size = MAX_IMAGE_DATA_SIZE; - desired_discard = 0; - } - else if (!url.empty() && (!exten.empty() && LLImageBase::getCodecFromExtension(exten) != IMG_CODEC_J2C)) - { - LL_DEBUGS(LOG_TXT) << "full request for " << id << " exten is not J2C: " << exten << LL_ENDL; - // Only do partial requests for J2C at the moment - desired_size = MAX_IMAGE_DATA_SIZE; - desired_discard = 0; - } - else if (desired_discard == 0) - { - // if we want the entire image, and we know its size, then get it all - // (calcDataSizeJ2C() below makes assumptions about how the image - // was compressed - this code ensures that when we request the entire image, - // we really do get it.) - desired_size = MAX_IMAGE_DATA_SIZE; - } - else if (w*h*c > 0) - { - // If the requester knows the dimensions of the image, - // this will calculate how much data we need without having to parse the header - - desired_size = LLImageJ2C::calcDataSizeJ2C(w, h, c, desired_discard); - } - else - { - // If the requester knows nothing about the file, we fetch the smallest - // amount of data at the lowest resolution (highest discard level) possible. - desired_size = TEXTURE_CACHE_ENTRY_SIZE; - desired_discard = MAX_DISCARD_LEVEL; - } - - - if (worker) - { - if (worker->wasAborted()) - { - return -1; // need to wait for previous aborted request to complete - } - worker->lockWorkMutex(); // +Mw - if (worker->mState == LLTextureFetchWorker::DONE && worker->mDesiredSize == llmax(desired_size, TEXTURE_CACHE_ENTRY_SIZE) && worker->mDesiredDiscard == desired_discard) { - worker->unlockWorkMutex(); // -Mw - - return -1; // similar request has failed or is in a transitional state - } - worker->mActiveCount++; - worker->mNeedsAux = needs_aux; - worker->setImagePriority(priority); - worker->setDesiredDiscard(desired_discard, desired_size); - worker->setCanUseHTTP(can_use_http); - - //MAINT-4184 url is always empty. Do not set with it. - - if (!worker->haveWork()) - { - worker->setState(LLTextureFetchWorker::INIT); - worker->unlockWorkMutex(); // -Mw - - worker->addWork(0); - } - else - { - worker->unlockWorkMutex(); // -Mw - } - } - else - { - worker = new LLTextureFetchWorker(this, f_type, url, id, host, priority, desired_discard, desired_size); - lockQueue(); // +Mfq - mRequestMap[id] = worker; - unlockQueue(); // -Mfq - - worker->lockWorkMutex(); // +Mw - worker->mActiveCount++; - worker->mNeedsAux = needs_aux; - worker->setCanUseHTTP(can_use_http) ; - worker->unlockWorkMutex(); // -Mw - } - - LL_DEBUGS(LOG_TXT) << "REQUESTED: " << id << " f_type " << fttype_to_string(f_type) - << " Discard: " << desired_discard << " size " << desired_size << LL_ENDL; - return desired_discard; -} -// Threads: T* -// -// protected -void LLTextureFetch::addToHTTPQueue(const LLUUID& id) -{ - LL_PROFILE_ZONE_SCOPED; - LLMutexLock lock(&mNetworkQueueMutex); // +Mfnq - mHTTPTextureQueue.insert(id); - mTotalHTTPRequests++; -} // -Mfnq - -// Threads: T* -void LLTextureFetch::removeFromHTTPQueue(const LLUUID& id, S32Bytes received_size) -{ - LL_PROFILE_ZONE_SCOPED; - LLMutexLock lock(&mNetworkQueueMutex); // +Mfnq - mHTTPTextureQueue.erase(id); - mHTTPTextureBits += received_size; // Approximate - does not include header bits -} // -Mfnq - -// NB: If you change deleteRequest() you should probably make -// parallel changes in removeRequest(). They're functionally -// identical with only argument variations. -// -// Threads: T* -void LLTextureFetch::deleteRequest(const LLUUID& id, bool cancel) -{ - LL_PROFILE_ZONE_SCOPED; - lockQueue(); // +Mfq - LLTextureFetchWorker* worker = getWorkerAfterLock(id); - if (worker) - { - size_t erased_1 = mRequestMap.erase(worker->mID); - unlockQueue(); // -Mfq - - llassert_always(erased_1 > 0) ; - llassert_always(!(worker->getFlags(LLWorkerClass::WCF_DELETE_REQUESTED))) ; - - worker->scheduleDelete(); - } - else - { - unlockQueue(); // -Mfq - } -} - -// NB: If you change removeRequest() you should probably make -// parallel changes in deleteRequest(). They're functionally -// identical with only argument variations. -// -// Threads: T* -void LLTextureFetch::removeRequest(LLTextureFetchWorker* worker, bool cancel) -{ - LL_PROFILE_ZONE_SCOPED; - if(!worker) - { - return; - } - - lockQueue(); // +Mfq - size_t erased_1 = mRequestMap.erase(worker->mID); - unlockQueue(); // -Mfq - - llassert_always(erased_1 > 0) ; - llassert_always(!(worker->getFlags(LLWorkerClass::WCF_DELETE_REQUESTED))) ; - - worker->scheduleDelete(); -} - -void LLTextureFetch::deleteAllRequests() -{ - while(1) - { - lockQueue(); - if(mRequestMap.empty()) - { - unlockQueue() ; - break; - } - - LLTextureFetchWorker* worker = mRequestMap.begin()->second; - unlockQueue() ; - - removeRequest(worker, true); - } -} - -// Threads: T* -S32 LLTextureFetch::getNumRequests() -{ - lockQueue(); // +Mfq - S32 size = (S32)mRequestMap.size(); - unlockQueue(); // -Mfq - - return size; -} - -// Threads: T* -S32 LLTextureFetch::getNumHTTPRequests() -{ - mNetworkQueueMutex.lock(); // +Mfq - S32 size = (S32)mHTTPTextureQueue.size(); - mNetworkQueueMutex.unlock(); // -Mfq - - return size; -} - -// Threads: T* -U32 LLTextureFetch::getTotalNumHTTPRequests() -{ - mNetworkQueueMutex.lock(); // +Mfq - U32 size = mTotalHTTPRequests; - mNetworkQueueMutex.unlock(); // -Mfq - - return size; -} - -// call lockQueue() first! -// Threads: T* -// Locks: Mfq -LLTextureFetchWorker* LLTextureFetch::getWorkerAfterLock(const LLUUID& id) -{ - LL_PROFILE_ZONE_SCOPED; - LLTextureFetchWorker* res = NULL; - map_t::iterator iter = mRequestMap.find(id); - if (iter != mRequestMap.end()) - { - res = iter->second; - } - return res; -} - -// Threads: T* -LLTextureFetchWorker* LLTextureFetch::getWorker(const LLUUID& id) -{ - LLMutexLock lock(&mQueueMutex); // +Mfq - - return getWorkerAfterLock(id); -} // -Mfq - - -// Threads: T* -bool LLTextureFetch::getRequestFinished(const LLUUID& id, S32& discard_level, - LLPointer& raw, LLPointer& aux, - LLCore::HttpStatus& last_http_get_status) -{ - LL_PROFILE_ZONE_SCOPED; - bool res = false; - LLTextureFetchWorker* worker = getWorker(id); - if (worker) - { - if (worker->wasAborted()) - { - res = true; - } - else if (!worker->haveWork()) - { - // Should only happen if we set mDebugPause... - if (!mDebugPause) - { -// LL_WARNS(LOG_TXT) << "Adding work for inactive worker: " << id << LL_ENDL; - worker->addWork(0); - } - } - else if (worker->checkWork()) - { - F32 decode_time; - F32 fetch_time; - F32 cache_read_time; - F32 cache_write_time; - S32 file_size; - std::map logged_state_timers; - F32 skipped_states_time; - worker->lockWorkMutex(); // +Mw - last_http_get_status = worker->mGetStatus; - discard_level = worker->mDecodedDiscard; - raw = worker->mRawImage; - aux = worker->mAuxImage; - - decode_time = worker->mDecodeTime; - fetch_time = worker->mFetchTime; - cache_read_time = worker->mCacheReadTime; - cache_write_time = worker->mCacheWriteTime; - file_size = worker->mFileSize; - worker->mCacheReadTimer.reset(); - worker->mDecodeTimer.reset(); - worker->mCacheWriteTimer.reset(); - worker->mFetchTimer.reset(); - logged_state_timers = worker->mStateTimersMap; - skipped_states_time = worker->mSkippedStatesTime; - worker->mStateTimer.reset(); - res = true; - LL_DEBUGS(LOG_TXT) << id << ": Request Finished. State: " << worker->mState << " Discard: " << discard_level << LL_ENDL; - worker->unlockWorkMutex(); // -Mw - - sample(sTexDecodeLatency, decode_time); - sample(sTexFetchLatency, fetch_time); - sample(sCacheReadLatency, cache_read_time); - sample(sCacheWriteLatency, cache_write_time); - - static LLCachedControl min_time_to_log(gSavedSettings, "TextureFetchMinTimeToLog", 2.f); - if (fetch_time > min_time_to_log) - { - //LL_INFOS() << "fetch_time: " << fetch_time << " cache_read_time: " << cache_read_time << " decode_time: " << decode_time << " cache_write_time: " << cache_write_time << LL_ENDL; - - LLTextureFetchTester* tester = (LLTextureFetchTester*)LLMetricPerformanceTesterBasic::getTester(sTesterName); - if (tester) - { - tester->updateStats(logged_state_timers, fetch_time, skipped_states_time, file_size) ; - } - } - } - else - { - worker->lockWorkMutex(); // +Mw - if ((worker->mDecodedDiscard >= 0) && - (worker->mDecodedDiscard < discard_level || discard_level < 0) && - (worker->mState >= LLTextureFetchWorker::WAIT_ON_WRITE)) - { - // Not finished, but data is ready - discard_level = worker->mDecodedDiscard; - raw = worker->mRawImage; - aux = worker->mAuxImage; - } - worker->unlockWorkMutex(); // -Mw - } - } - else - { - res = true; - } - return res; -} - -// Threads: T* -bool LLTextureFetch::updateRequestPriority(const LLUUID& id, F32 priority) -{ - LL_PROFILE_ZONE_SCOPED; - mRequestQueue.tryPost([=]() - { - LLTextureFetchWorker* worker = getWorker(id); - if (worker) - { - worker->lockWorkMutex(); // +Mw - worker->setImagePriority(priority); - worker->unlockWorkMutex(); // -Mw - } - }); - - return true; -} - -// Replicates and expands upon the base class's -// getPending() implementation. getPending() and -// runCondition() replicate one another's logic to -// an extent and are sometimes used for the same -// function (deciding whether or not to sleep/pause -// a thread). So the implementations need to stay -// in step, at least until this can be refactored and -// the redundancy eliminated. -// -// Threads: T* - -//virtual -size_t LLTextureFetch::getPending() -{ - LL_PROFILE_ZONE_SCOPED; - size_t res; - lockData(); // +Ct - { - LLMutexLock lock(&mQueueMutex); // +Mfq - - res = mRequestQueue.size(); - res += mCommands.size(); - } // -Mfq - unlockData(); // -Ct - return res; -} - -// Locks: Ct -// virtual -bool LLTextureFetch::runCondition() -{ - // Caller is holding the lock on LLThread's condition variable. - - // LLQueuedThread, unlike its base class LLThread, makes this a - // private method which is unfortunate. I want to use it directly - // but I'm going to have to re-implement the logic here (or change - // declarations, which I don't want to do right now). - // - // Changes here may need to be reflected in getPending(). - - bool have_no_commands(false); - { - LLMutexLock lock(&mQueueMutex); // +Mfq - - have_no_commands = mCommands.empty(); - } // -Mfq - - return ! (have_no_commands - && (mRequestQueue.size() == 0 && mIdleThread)); // From base class -} - -////////////////////////////////////////////////////////////////////////////// - -// Threads: Ttf -void LLTextureFetch::commonUpdate() -{ - LL_PROFILE_ZONE_SCOPED; - // Update low/high water levels based on pipelining. We pick - // up setting eventually, so the semaphore/request level can - // fall outside the [0..HIGH_WATER] range. Expect that. - if (LLAppViewer::instance()->getAppCoreHttp().isPipelined(LLAppCoreHttp::AP_TEXTURE)) - { - mHttpHighWater = HTTP_PIPE_REQUESTS_HIGH_WATER; - mHttpLowWater = HTTP_PIPE_REQUESTS_LOW_WATER; - } - else - { - mHttpHighWater = HTTP_NONPIPE_REQUESTS_HIGH_WATER; - mHttpLowWater = HTTP_NONPIPE_REQUESTS_LOW_WATER; - } - - // Release waiters - releaseHttpWaiters(); - - // Run a cross-thread command, if any. - cmdDoWork(); - - // Deliver all completion notifications - LLCore::HttpStatus status = mHttpRequest->update(0); - if (! status) - { - LL_INFOS_ONCE(LOG_TXT) << "Problem during HTTP servicing. Reason: " - << status.toString() - << LL_ENDL; - } -} - - -// Threads: Tmain - -//virtual -size_t LLTextureFetch::update(F32 max_time_ms) -{ - LL_PROFILE_ZONE_SCOPED; - static LLCachedControl band_width(gSavedSettings,"ThrottleBandwidthKBPS", 3000.0); - - { - mNetworkQueueMutex.lock(); // +Mfnq - mMaxBandwidth = band_width(); - - add(LLStatViewer::TEXTURE_NETWORK_DATA_RECEIVED, mHTTPTextureBits); - mHTTPTextureBits = (U32Bits)0; - - mNetworkQueueMutex.unlock(); // -Mfnq - } - - size_t res = LLWorkerThread::update(max_time_ms); - - if (!mThreaded) - { - commonUpdate(); - } - - return res; -} - -// called in the MAIN thread after the TextureCacheThread shuts down. -// -// Threads: Tmain -void LLTextureFetch::shutDownTextureCacheThread() -{ - if(mTextureCache) - { - llassert_always(mTextureCache->isQuitting() || mTextureCache->isStopped()) ; - mTextureCache = NULL ; - } -} - -// Threads: Ttf -void LLTextureFetch::startThread() -{ - mTextureInfo.startRecording(); -} - -// Threads: Ttf -void LLTextureFetch::endThread() -{ - LL_INFOS(LOG_TXT) << "CacheReads: " << mTotalCacheReadCount - << ", CacheWrites: " << mTotalCacheWriteCount - << ", ResWaits: " << mTotalResourceWaitCount - << ", TotalHTTPReq: " << getTotalNumHTTPRequests() - << LL_ENDL; - - mTextureInfo.stopRecording(); -} - -// Threads: Ttf -void LLTextureFetch::threadedUpdate() -{ - LL_PROFILE_ZONE_SCOPED; - llassert_always(mHttpRequest); - -#if 0 - // Limit update frequency - const F32 PROCESS_TIME = 0.05f; - static LLFrameTimer process_timer; - if (process_timer.getElapsedTimeF32() < PROCESS_TIME) - { - return; - } - process_timer.reset(); -#endif - - commonUpdate(); - -#if 0 - const F32 INFO_TIME = 1.0f; - static LLFrameTimer info_timer; - if (info_timer.getElapsedTimeF32() >= INFO_TIME) - { - S32 q = mCurlGetRequest->getQueued(); - if (q > 0) - { - LL_INFOS(LOG_TXT) << "Queued gets: " << q << LL_ENDL; - info_timer.reset(); - } - } -#endif -} - -////////////////////////////////////////////////////////////////////////////// - -// Threads: T* -// Locks: Mw -bool LLTextureFetchWorker::insertPacket(S32 index, U8* data, S32 size) -{ - LL_PROFILE_ZONE_SCOPED; - mRequestedDeltaTimer.reset(); - if (index >= mTotalPackets) - { -// LL_WARNS(LOG_TXT) << "Received Image Packet " << index << " > max: " << mTotalPackets << " for image: " << mID << LL_ENDL; - return false; - } - if (index > 0 && index < mTotalPackets-1 && size != MAX_IMG_PACKET_SIZE) - { -// LL_WARNS(LOG_TXT) << "Received bad sized packet: " << index << ", " << size << " != " << MAX_IMG_PACKET_SIZE << " for image: " << mID << LL_ENDL; - return false; - } - - if (index >= (S32)mPackets.size()) - { - mPackets.resize(index+1, (PacketData*)NULL); // initializes v to NULL pointers - } - else if (mPackets[index] != NULL) - { -// LL_WARNS(LOG_TXT) << "Received duplicate packet: " << index << " for image: " << mID << LL_ENDL; - return false; - } - - mPackets[index] = new PacketData(data, size); - while (mLastPacket+1 < (S32)mPackets.size() && mPackets[mLastPacket+1] != NULL) - { - ++mLastPacket; - } - return true; -} - -void LLTextureFetchWorker::setState(e_state new_state) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if (mFTType == FTT_SERVER_BAKE) - { - // NOTE: turning on these log statements is a reliable way to get - // blurry images fairly frequently. Presumably this is an - // indication of some subtle timing or locking issue. - -// LL_INFOS(LOG_TXT) << "id: " << mID << " FTType: " << mFTType << " disc: " << mDesiredDiscard << " sz: " << mDesiredSize << " state: " << e_state_name[mState] << " => " << e_state_name[new_state] << LL_ENDL; - } - - F32 d_time = mStateTimer.getElapsedTimeF32(); - if (d_time >= 0.0001F) - { - if (LOGGED_STATES.count(mState)) - { - mStateTimersMap[mState] = d_time; - } - else - { - mSkippedStatesTime += d_time; - } - } - - mStateTimer.reset(); - mState = new_state; -} - -LLViewerRegion* LLTextureFetchWorker::getRegion() -{ - LLViewerRegion* region = NULL; - if (mHost.isInvalid()) - { - region = gAgent.getRegion(); - } - else if (LLWorld::instanceExists()) - { - region = LLWorld::getInstance()->getRegion(mHost); - } - return region; -} - -////////////////////////////////////////////////////////////////////////////// - -// Threads: T* -bool LLTextureFetch::isFromLocalCache(const LLUUID& id) -{ - bool from_cache = false ; - - LLTextureFetchWorker* worker = getWorker(id); - if (worker) - { - worker->lockWorkMutex(); // +Mw - from_cache = worker->mInLocalCache; - worker->unlockWorkMutex(); // -Mw - } - - return from_cache ; -} - -S32 LLTextureFetch::getFetchState(const LLUUID& id) -{ - S32 state = LLTextureFetchWorker::INVALID; - LLTextureFetchWorker* worker = getWorker(id); - if (worker && worker->haveWork()) - { - state = worker->mState; - } - - return state; -} - -// Threads: T* -S32 LLTextureFetch::getFetchState(const LLUUID& id, F32& data_progress_p, F32& requested_priority_p, - U32& fetch_priority_p, F32& fetch_dtime_p, F32& request_dtime_p, bool& can_use_http) -{ - LL_PROFILE_ZONE_SCOPED; - S32 state = LLTextureFetchWorker::INVALID; - F32 data_progress = 0.0f; - F32 requested_priority = 0.0f; - F32 fetch_dtime = 999999.f; - F32 request_dtime = 999999.f; - U32 fetch_priority = 0; - - LLTextureFetchWorker* worker = getWorker(id); - if (worker && worker->haveWork()) - { - worker->lockWorkMutex(); // +Mw - state = worker->mState; - fetch_dtime = worker->mFetchDeltaTimer.getElapsedTimeF32(); - request_dtime = worker->mRequestedDeltaTimer.getElapsedTimeF32(); - if (worker->mFileSize > 0) - { - if (worker->mFormattedImage.notNull()) - { - data_progress = (F32)worker->mFormattedImage->getDataSize() / (F32)worker->mFileSize; - } - } - if (state >= LLTextureFetchWorker::LOAD_FROM_NETWORK && state <= LLTextureFetchWorker::WAIT_HTTP_REQ) - { - requested_priority = worker->mRequestedPriority; - } - else - { - requested_priority = worker->mImagePriority; - } - fetch_priority = worker->getImagePriority(); - can_use_http = worker->getCanUseHTTP() ; - worker->unlockWorkMutex(); // -Mw - } - data_progress_p = data_progress; - requested_priority_p = requested_priority; - fetch_priority_p = fetch_priority; - fetch_dtime_p = fetch_dtime; - request_dtime_p = request_dtime; - return state; -} - -void LLTextureFetch::dump() -{ - LL_INFOS(LOG_TXT) << "LLTextureFetch ACTIVE_HTTP:" << LL_ENDL; - for (queue_t::const_iterator iter(mHTTPTextureQueue.begin()); - mHTTPTextureQueue.end() != iter; - ++iter) - { - LL_INFOS(LOG_TXT) << " ID: " << (*iter) << LL_ENDL; - } - - LL_INFOS(LOG_TXT) << "LLTextureFetch WAIT_HTTP_RESOURCE:" << LL_ENDL; - for (wait_http_res_queue_t::const_iterator iter(mHttpWaitResource.begin()); - mHttpWaitResource.end() != iter; - ++iter) - { - LL_INFOS(LOG_TXT) << " ID: " << (*iter) << LL_ENDL; - } -} - -////////////////////////////////////////////////////////////////////////////// - -// HTTP Resource Waiting Methods - -// Threads: Ttf -void LLTextureFetch::addHttpWaiter(const LLUUID & tid) -{ - mNetworkQueueMutex.lock(); // +Mfnq - mHttpWaitResource.insert(tid); - mNetworkQueueMutex.unlock(); // -Mfnq -} - -// Threads: Ttf -void LLTextureFetch::removeHttpWaiter(const LLUUID & tid) -{ - mNetworkQueueMutex.lock(); // +Mfnq - wait_http_res_queue_t::iterator iter(mHttpWaitResource.find(tid)); - if (mHttpWaitResource.end() != iter) - { - mHttpWaitResource.erase(iter); - } - mNetworkQueueMutex.unlock(); // -Mfnq -} - -// Threads: T* -bool LLTextureFetch::isHttpWaiter(const LLUUID & tid) -{ - mNetworkQueueMutex.lock(); // +Mfnq - wait_http_res_queue_t::iterator iter(mHttpWaitResource.find(tid)); - const bool ret(mHttpWaitResource.end() != iter); - mNetworkQueueMutex.unlock(); // -Mfnq - return ret; -} - -// Release as many requests as permitted from the WAIT_HTTP_RESOURCE2 -// state to the SEND_HTTP_REQ state based on their current priority. -// -// This data structures and code associated with this looks a bit -// indirect and naive but it's done in the name of safety. An -// ordered container may become invalid from time to time due to -// priority changes caused by actions in other threads. State itself -// could also suffer the same fate with canceled operations. Even -// done this way, I'm not fully trusting we're truly safe. This -// module is due for a major refactoring and we'll deal with it then. -// -// Threads: Ttf -// Locks: -Mw (must not hold any worker when called) -void LLTextureFetch::releaseHttpWaiters() -{ - LL_PROFILE_ZONE_SCOPED; - // Use mHttpSemaphore rather than mHTTPTextureQueue.size() - // to avoid a lock. - if (mHttpSemaphore >= mHttpLowWater) - return; - S32 needed(mHttpHighWater - mHttpSemaphore); - if (needed <= 0) - { - // Would only happen if High/LowWater were changed behind - // our back. In that case, defer fill until usage falls within - // limits. - return; - } - - // Quickly make a copy of all the LLUIDs. Get off the - // mutex as early as possible. - typedef std::vector uuid_vec_t; - uuid_vec_t tids; - - { - LLMutexLock lock(&mNetworkQueueMutex); // +Mfnq - - if (mHttpWaitResource.empty()) - return; - tids.reserve(mHttpWaitResource.size()); - tids.assign(mHttpWaitResource.begin(), mHttpWaitResource.end()); - } // -Mfnq - - // Now lookup the UUUIDs to find valid requests and sort - // them in priority order, highest to lowest. We're going - // to modify priority later as a side-effect of releasing - // these objects. That, in turn, would violate the partial - // ordering assumption of std::set, std::map, etc. so we - // don't use those containers. We use a vector and an explicit - // sort to keep the containers valid later. - typedef std::vector worker_list_t; - worker_list_t tids2; - - tids2.reserve(tids.size()); - for (uuid_vec_t::iterator iter(tids.begin()); - tids.end() != iter; - ++iter) - { - LLTextureFetchWorker * worker(getWorker(* iter)); - if (worker) - { - tids2.push_back(worker); - } - else - { - // If worker isn't found, this should be due to a request - // for deletion. We signal our recognition that this - // uuid shouldn't be used for resource waiting anymore by - // erasing it from the resource waiter list. That allows - // deleteOK to do final deletion on the worker. - removeHttpWaiter(* iter); - } - } - tids.clear(); - - // Sort into priority order, if necessary and only as much as needed - if (tids2.size() > needed) - { - LLTextureFetchWorker::Compare compare; - std::partial_sort(tids2.begin(), tids2.begin() + needed, tids2.end(), compare); - } - - // Release workers up to the high water mark. Since we aren't - // holding any locks at this point, we can be in competition - // with other callers. Do defensive things like getting - // refreshed counts of requests and checking if someone else - // has moved any worker state around.... - for (worker_list_t::iterator iter2(tids2.begin()); tids2.end() != iter2; ++iter2) - { - LLTextureFetchWorker * worker(* iter2); - - worker->lockWorkMutex(); // +Mw - if (LLTextureFetchWorker::WAIT_HTTP_RESOURCE2 != worker->mState) - { - // Not in expected state, remove it, try the next one - worker->unlockWorkMutex(); // -Mw - LL_WARNS(LOG_TXT) << "Resource-waited texture " << worker->mID - << " in unexpected state: " << worker->mState - << ". Removing from wait list." - << LL_ENDL; - removeHttpWaiter(worker->mID); - continue; - } - - if (! worker->acquireHttpSemaphore()) - { - // Out of active slots, quit - worker->unlockWorkMutex(); // -Mw - break; - } - - worker->setState(LLTextureFetchWorker::SEND_HTTP_REQ); - worker->unlockWorkMutex(); // -Mw - - removeHttpWaiter(worker->mID); - } -} - -// Threads: T* -void LLTextureFetch::cancelHttpWaiters() -{ - mNetworkQueueMutex.lock(); // +Mfnq - mHttpWaitResource.clear(); - mNetworkQueueMutex.unlock(); // -Mfnq -} - -// Threads: T* -int LLTextureFetch::getHttpWaitersCount() -{ - mNetworkQueueMutex.lock(); // +Mfnq - int ret(mHttpWaitResource.size()); - mNetworkQueueMutex.unlock(); // -Mfnq - return ret; -} - - -// Threads: T* -void LLTextureFetch::updateStateStats(U32 cache_read, U32 cache_write, U32 res_wait) -{ - LLMutexLock lock(&mQueueMutex); // +Mfq - - mTotalCacheReadCount += cache_read; - mTotalCacheWriteCount += cache_write; - mTotalResourceWaitCount += res_wait; -} // -Mfq - - -// Threads: T* -void LLTextureFetch::getStateStats(U32 * cache_read, U32 * cache_write, U32 * res_wait) -{ - U32 ret1(0U), ret2(0U), ret3(0U); - - { - LLMutexLock lock(&mQueueMutex); // +Mfq - ret1 = mTotalCacheReadCount; - ret2 = mTotalCacheWriteCount; - ret3 = mTotalResourceWaitCount; - } // -Mfq - - *cache_read = ret1; - *cache_write = ret2; - *res_wait = ret3; -} - -////////////////////////////////////////////////////////////////////////////// - -// cross-thread command methods - -// Threads: T* -void LLTextureFetch::commandSetRegion(U64 region_handle) -{ - TFReqSetRegion * req = new TFReqSetRegion(region_handle); - - cmdEnqueue(req); -} - -// Threads: T* -void LLTextureFetch::commandSendMetrics(const std::string & caps_url, - const LLUUID & session_id, - const LLUUID & agent_id, - LLSD& stats_sd) -{ - TFReqSendMetrics * req = new TFReqSendMetrics(caps_url, session_id, agent_id, stats_sd); - - cmdEnqueue(req); -} - -// Threads: T* -void LLTextureFetch::commandDataBreak() -{ - // The pedantically correct way to implement this is to create a command - // request object in the above fashion and enqueue it. However, this is - // simple data of an advisorial not operational nature and this case - // of shared-write access is tolerable. - - LLTextureFetch::svMetricsDataBreak = true; -} - -// Threads: T* -void LLTextureFetch::cmdEnqueue(TFRequest * req) -{ - LL_PROFILE_ZONE_SCOPED; - lockQueue(); // +Mfq - mCommands.push_back(req); - unlockQueue(); // -Mfq - - unpause(); -} - -// Threads: T* -LLTextureFetch::TFRequest * LLTextureFetch::cmdDequeue() -{ - LL_PROFILE_ZONE_SCOPED; - TFRequest * ret = 0; - - lockQueue(); // +Mfq - if (! mCommands.empty()) - { - ret = mCommands.front(); - mCommands.erase(mCommands.begin()); - } - unlockQueue(); // -Mfq - - return ret; -} - -// Threads: Ttf -void LLTextureFetch::cmdDoWork() -{ - LL_PROFILE_ZONE_SCOPED; - if (mDebugPause) - { - return; // debug: don't do any work - } - - TFRequest * req = cmdDequeue(); - if (req) - { - // One request per pass should really be enough for this. - req->doWork(this); - delete req; - } -} - -////////////////////////////////////////////////////////////////////////////// - -// Private (anonymous) class methods implementing the command scheme. - -namespace -{ - - -// Example of a simple notification handler for metrics -// delivery notification. Earlier versions of the code used -// a Responder that tried harder to detect delivery breaks -// but it really isn't that important. If someone wants to -// revisit that effort, here is a place to start. -class AssetReportHandler : public LLCore::HttpHandler -{ -public: - - // Threads: Ttf - virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) - { - LLCore::HttpStatus status(response->getStatus()); - - if (status) - { - LL_DEBUGS(LOG_TXT) << "Successfully delivered asset metrics to grid." - << LL_ENDL; - } - else - { - LL_WARNS(LOG_TXT) << "Error delivering asset metrics to grid. Status: " - << status.toTerseString() - << ", Reason: " << status.toString() << LL_ENDL; - } - } -}; // end class AssetReportHandler - -/** - * Implements the 'Set Region' command. - * - * Thread: Thread1 (TextureFetch) - */ -bool -TFReqSetRegion::doWork(LLTextureFetch *) -{ - LLViewerAssetStatsFF::set_region(mRegionHandle); - - return true; -} - -TFReqSendMetrics::TFReqSendMetrics(const std::string & caps_url, - const LLUUID & session_id, - const LLUUID & agent_id, - LLSD& stats_sd): - LLTextureFetch::TFRequest(), - mCapsURL(caps_url), - mSessionID(session_id), - mAgentID(agent_id), - mStatsSD(stats_sd), - mHandler(new AssetReportHandler) -{} - - -TFReqSendMetrics::~TFReqSendMetrics() -{ -} - - -/** - * Implements the 'Send Metrics' command. Takes over - * ownership of the passed LLViewerAssetStats pointer. - * - * Thread: Thread1 (TextureFetch) - */ -bool -TFReqSendMetrics::doWork(LLTextureFetch * fetcher) -{ - LL_PROFILE_ZONE_SCOPED; - - //if (! gViewerAssetStatsThread1) - // return true; - - static volatile bool reporting_started(false); - static volatile S32 report_sequence(0); - - // In mStatsSD, we have a copy we own of the LLSD representation - // of the asset stats. Add some additional fields and ship it off. - - static const S32 metrics_data_version = 2; - - bool initial_report = !reporting_started; - mStatsSD["session_id"] = mSessionID; - mStatsSD["agent_id"] = mAgentID; - mStatsSD["message"] = "ViewerAssetMetrics"; - mStatsSD["sequence"] = report_sequence; - mStatsSD["initial"] = initial_report; - mStatsSD["version"] = metrics_data_version; - mStatsSD["break"] = static_cast(LLTextureFetch::svMetricsDataBreak); - - // Update sequence number - if (S32_MAX == ++report_sequence) - { - report_sequence = 0; - } - reporting_started = true; - - // Limit the size of the stats report if necessary. - - mStatsSD["truncated"] = truncate_viewer_metrics(10, mStatsSD); - - if (gSavedSettings.getBOOL("QAModeMetrics")) - { - dump_sequential_xml("metric_asset_stats",mStatsSD); - } - - if (! mCapsURL.empty()) - { - // Don't care about handle, this is a fire-and-forget operation. - LLCoreHttpUtil::requestPostWithLLSD(&fetcher->getHttpRequest(), - fetcher->getMetricsPolicyClass(), - mCapsURL, - mStatsSD, - LLCore::HttpOptions::ptr_t(), - fetcher->getMetricsHeaders(), - mHandler); - LLTextureFetch::svMetricsDataBreak = false; - } - else - { - LLTextureFetch::svMetricsDataBreak = true; - } - - // In QA mode, Metrics submode, log the result for ease of testing - if (fetcher->isQAMode()) - { - LL_INFOS(LOG_TXT) << "ViewerAssetMetrics as submitted\n" << ll_pretty_print_sd(mStatsSD) << LL_ENDL; - } - - return true; -} - - -bool -truncate_viewer_metrics(int max_regions, LLSD & metrics) -{ - static const LLSD::String reg_tag("regions"); - static const LLSD::String duration_tag("duration"); - - LLSD & reg_map(metrics[reg_tag]); - if (reg_map.size() <= max_regions) - { - return false; - } - - // Build map of region hashes ordered by duration - typedef std::multimap reg_ordered_list_t; - reg_ordered_list_t regions_by_duration; - - int ind(0); - LLSD::array_const_iterator it_end(reg_map.endArray()); - for (LLSD::array_const_iterator it(reg_map.beginArray()); it_end != it; ++it, ++ind) - { - LLSD::Real duration = (*it)[duration_tag].asReal(); - regions_by_duration.insert(reg_ordered_list_t::value_type(duration, ind)); - } - - // Build a replacement regions array with the longest-persistence regions - LLSD new_region(LLSD::emptyArray()); - reg_ordered_list_t::const_reverse_iterator it2_end(regions_by_duration.rend()); - reg_ordered_list_t::const_reverse_iterator it2(regions_by_duration.rbegin()); - for (int i(0); i < max_regions && it2_end != it2; ++i, ++it2) - { - new_region.append(reg_map[it2->second]); - } - reg_map = new_region; - - return true; -} - -} // end of anonymous namespace - -LLTextureFetchTester::LLTextureFetchTester() : LLMetricPerformanceTesterBasic(sTesterName) -{ - mTextureFetchTime = 0; - mSkippedStatesTime = 0; - mFileSize = 0; -} - -LLTextureFetchTester::~LLTextureFetchTester() -{ - outputTestResults(); - LLTextureFetch::sTesterp = NULL; -} - -//virtual -void LLTextureFetchTester::outputTestRecord(LLSD *sd) -{ - std::string currentLabel = getCurrentLabelName(); - - (*sd)[currentLabel]["Texture Fetch Time"] = (LLSD::Real)mTextureFetchTime; - (*sd)[currentLabel]["File Size"] = (LLSD::Integer)mFileSize; - (*sd)[currentLabel]["Skipped States Time"] = (LLSD::String)llformat("%.6f", mSkippedStatesTime); - - for(auto i : LOGGED_STATES) - { - (*sd)[currentLabel][sStateDescs[i]] = mStateTimersMap[i]; - } -} - -void LLTextureFetchTester::updateStats(const std::map state_timers, const F32 fetch_time, const F32 skipped_states_time, const S32 file_size) -{ - mTextureFetchTime = fetch_time; - mStateTimersMap = state_timers; - mFileSize = file_size; - mSkippedStatesTime = skipped_states_time; - outputTestResults(); -} - +/** + * @file lltexturefetch.cpp + * @brief Object which fetches textures from the cache and/or network + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012-2014, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include +#include +#include + +#include "lltexturefetch.h" + +#include "lldir.h" +#include "llhttpconstants.h" +#include "llimage.h" +#include "llimagej2c.h" +#include "llimageworker.h" +#include "llworkerthread.h" +#include "message.h" + +#include "llagent.h" +#include "lltexturecache.h" +#include "llviewercontrol.h" +#include "llviewertexturelist.h" +#include "llviewertexture.h" +#include "llviewerregion.h" +#include "llviewerstats.h" +#include "llviewerstatsrecorder.h" +#include "llviewerassetstats.h" +#include "llworld.h" +#include "llsdparam.h" +#include "llsdutil.h" +#include "llstartup.h" + +#include "httprequest.h" +#include "httphandler.h" +#include "httpresponse.h" +#include "bufferarray.h" +#include "bufferstream.h" +#include "llcorehttputil.h" +#include "llhttpretrypolicy.h" + +LLTrace::CountStatHandle LLTextureFetch::sCacheHit("texture_cache_hit"); +LLTrace::CountStatHandle LLTextureFetch::sCacheAttempt("texture_cache_attempt"); +LLTrace::EventStatHandle > LLTextureFetch::sCacheHitRate("texture_cache_hits"); + +LLTrace::SampleStatHandle LLTextureFetch::sCacheReadLatency("texture_cache_read_latency"); +LLTrace::SampleStatHandle LLTextureFetch::sTexDecodeLatency("texture_decode_latency"); +LLTrace::SampleStatHandle LLTextureFetch::sCacheWriteLatency("texture_write_latency"); +LLTrace::SampleStatHandle LLTextureFetch::sTexFetchLatency("texture_fetch_latency"); + +LLTextureFetchTester* LLTextureFetch::sTesterp = NULL ; +const std::string sTesterName("TextureFetchTester"); + +////////////////////////////////////////////////////////////////////////////// +// +// Introduction +// +// This is an attempt to document what's going on in here after-the-fact. +// It's a sincere attempt to be accurate but there will be mistakes. +// +// +// Purpose +// +// What is this module trying to do? It accepts requests to load textures +// at a given priority and discard level and notifies the caller when done +// (successfully or not). Additional constraints are: +// +// * Support a local texture cache. Don't hit network when possible +// to avoid it. +// * Use UDP or HTTP as directed or as fallback. HTTP is tried when +// not disabled and a URL is available. UDP when a URL isn't +// available or HTTP attempts fail. +// * Asynchronous (using threads). Main thread is not to be blocked or +// burdened. +// * High concurrency. Many requests need to be in-flight and at various +// stages of completion. +// * Tolerate frequent re-prioritizations of requests. Priority is +// a reflection of a camera's viewpoint and as that viewpoint changes, +// objects and textures become more and less relevant and that is +// expressed at this level by priority changes and request cancelations. +// +// The caller interfaces that fall out of the above and shape the +// implementation are: +// * createRequest - Load j2c image via UDP or HTTP at given discard level and priority +// * deleteRequest - Request removal of prior request +// * getRequestFinished - Test if request is finished returning data to caller +// * updateRequestPriority - Change priority of existing request +// * getFetchState - Retrieve progress on existing request +// +// Everything else in here is mostly plumbing, metrics and debug. +// +// +// The Work Queue +// +// The two central classes are LLTextureFetch and LLTextureFetchWorker. +// LLTextureFetch combines threading with a priority queue of work +// requests. The priority queue is sorted by a U32 priority derived +// from the F32 priority in the APIs. The *only* work request that +// receives service time by this thread is the highest priority +// request. All others wait until it is complete or a dynamic priority +// change has re-ordered work. +// +// LLTextureFetchWorker implements the work request and is 1:1 with +// texture fetch requests. Embedded in each is a state machine that +// walks it through the cache, HTTP, UDP, image decode and retry +// steps of texture acquisition. +// +// +// Threads +// +// Several threads are actively invoking code in this module. They +// include: +// +// 1. Tmain Main thread of execution +// 2. Ttf LLTextureFetch's worker thread provided by LLQueuedThread +// 3. Tcurl LLCurl's worker thread (should disappear over time) +// 4. Ttc LLTextureCache's worker thread +// 5. Tid Image decoder's worker thread +// 6. Thl HTTP library's worker thread +// +// +// Mutexes/Condition Variables +// +// 1. Mt Mutex defined for LLThread's condition variable (base class of +// LLTextureFetch) +// 2. Ct Condition variable for LLThread and used by lock/unlockData(). +// 3. Mwtd Special LLWorkerThread mutex used for request deletion +// operations (base class of LLTextureFetch) +// 4. Mfq LLTextureFetch's mutex covering request and command queue +// data. +// 5. Mfnq LLTextureFetch's mutex covering udp and http request +// queue data. +// 6. Mwc Mutex covering LLWorkerClass's members (base class of +// LLTextureFetchWorker). One per request. +// 7. Mw LLTextureFetchWorker's mutex. One per request. +// +// +// Lock Ordering Rules +// +// Not an exhaustive list but shows the order of lock acquisition +// needed to prevent deadlocks. 'A < B' means acquire 'A' before +// acquiring 'B'. +// +// 1. Mw < Mfnq +// (there are many more...) +// +// +// Method and Member Definitions +// +// With the above, we'll try to document what threads can call what +// methods (using T* for any), what locks must be held on entry and +// are taken out during execution and what data is covered by which +// lock (if any). This latter category will be especially prone to +// error so be skeptical. +// +// A line like: "// Locks: M" indicates a method that must +// be invoked by a caller holding the 'M' lock. Similarly, +// "// Threads: T" means that a caller should be running in +// the indicated thread. +// +// For data members, a trailing comment like "// M" means that +// the data member is covered by the specified lock. Absence of a +// comment can mean the member is unlocked or that I didn't bother +// to do the archaeology. In the case of LLTextureFetchWorker, +// most data members added by the leaf class are actually covered +// by the Mw lock. You may also see "// T" which means that +// the member's usage is restricted to one thread (except for +// perhaps construction and destruction) and so explicit locking +// isn't used. +// +// In code, a trailing comment like "// [-+]M" indicates a +// lock acquision or release point. +// +// +// Worker Lifecycle +// +// The threading and responder model makes it very likely that +// other components are holding on to a pointer to a worker request. +// So, uncoordinated deletions of requests is a guarantee of memory +// corruption in a short time. So destroying a request involves +// invocations's of LLQueuedThread/LLWorkerThread's abort/stop +// logic that removes workers and puts them ona delete queue for +// 2-phase destruction. That second phase is deferrable by calls +// to deleteOK() which only allow final destruction (via dtor) +// once deleteOK has determined that the request is in a safe +// state. +// +// +// Worker State Machine +// +// "doWork" will be executed for a given worker on its respective +// LLQueuedThread. If doWork returns true, the worker is treated +// as completed. If doWork returns false, the worker will be +// put on the back of the work queue at the start of the next iteration +// of the mainloop. If a worker is waiting on a resource, it should +// return false as soon as possible and not block to avoid starving +// other workers of cpu cycles. +// + + + +////////////////////////////////////////////////////////////////////////////// + +// Tuning/Parameterization Constants + +static const S32 HTTP_PIPE_REQUESTS_HIGH_WATER = 100; // Maximum requests to have active in HTTP (pipelined) +static const S32 HTTP_PIPE_REQUESTS_LOW_WATER = 50; // Active level at which to refill +static const S32 HTTP_NONPIPE_REQUESTS_HIGH_WATER = 40; +static const S32 HTTP_NONPIPE_REQUESTS_LOW_WATER = 20; + +// BUG-3323/SH-4375 +// *NOTE: This is a heuristic value. Texture fetches have a habit of using a +// value of 32MB to indicate 'get the rest of the image'. Certain ISPs and +// network equipment get confused when they see this in a Range: header. So, +// if the request end is beyond this value, we issue an open-ended Range: +// request (e.g. 'Range: -') which seems to fix the problem. +static const S32 HTTP_REQUESTS_RANGE_END_MAX = 20000000; + +// stop after 720 seconds, might be overkill, but cap request can keep going forever. +static const S32 MAX_CAP_MISSING_RETRIES = 720; +static const S32 CAP_MISSING_EXPIRATION_DELAY = 1; // seconds + +////////////////////////////////////////////////////////////////////////////// +namespace +{ + // The NoOpDeletor is used when passing certain objects (the LLTextureFetchWorker) + // in a smart pointer below for passage into + // the LLCore::Http libararies. When the smart pointer is destroyed, no + // action will be taken since we do not in these cases want the object to + // be destroyed at the end of the call. + // + // *NOTE$: Yes! It is "Deletor" + // http://english.stackexchange.com/questions/4733/what-s-the-rule-for-adding-er-vs-or-when-nouning-a-verb + // "delete" derives from Latin "deletus" + void NoOpDeletor(LLCore::HttpHandler *) + { /*NoOp*/ } +} + +static const char* e_state_name[] = +{ + "INVALID", + "INIT", + "LOAD_FROM_TEXTURE_CACHE", + "CACHE_POST", + "LOAD_FROM_NETWORK", + "WAIT_HTTP_RESOURCE", + "WAIT_HTTP_RESOURCE2", + "SEND_HTTP_REQ", + "WAIT_HTTP_REQ", + "DECODE_IMAGE", + "DECODE_IMAGE_UPDATE", + "WRITE_TO_CACHE", + "WAIT_ON_WRITE", + "DONE" +}; + +// Log scope +static const char * const LOG_TXT = "Texture"; + +class LLTextureFetchWorker : public LLWorkerClass, public LLCore::HttpHandler + +{ + friend class LLTextureFetch; + +private: + class CacheReadResponder : public LLTextureCache::ReadResponder + { + public: + + // Threads: Ttf + CacheReadResponder(LLTextureFetch* fetcher, const LLUUID& id, LLImageFormatted* image) + : mFetcher(fetcher), mID(id) + { + setImage(image); + } + + // Threads: Ttc + virtual void completed(bool success) + { + LL_PROFILE_ZONE_SCOPED; + LLTextureFetchWorker* worker = mFetcher->getWorker(mID); + if (worker) + { + worker->callbackCacheRead(success, mFormattedImage, mImageSize, mImageLocal); + } + } + private: + LLTextureFetch* mFetcher; + LLUUID mID; + }; + + class CacheWriteResponder : public LLTextureCache::WriteResponder + { + public: + + // Threads: Ttf + CacheWriteResponder(LLTextureFetch* fetcher, const LLUUID& id) + : mFetcher(fetcher), mID(id) + { + } + + // Threads: Ttc + virtual void completed(bool success) + { + LL_PROFILE_ZONE_SCOPED; + LLTextureFetchWorker* worker = mFetcher->getWorker(mID); + if (worker) + { + worker->callbackCacheWrite(success); + } + } + private: + LLTextureFetch* mFetcher; + LLUUID mID; + }; + + class DecodeResponder : public LLImageDecodeThread::Responder + { + public: + + // Threads: Ttf + DecodeResponder(LLTextureFetch* fetcher, const LLUUID& id, LLTextureFetchWorker* worker) + : mFetcher(fetcher), mID(id) + { + } + + // Threads: Tid + virtual void completed(bool success, const std::string& error_message, LLImageRaw* raw, LLImageRaw* aux, U32 request_id) + { + LL_PROFILE_ZONE_SCOPED; + LLTextureFetchWorker* worker = mFetcher->getWorker(mID); + if (worker) + { + worker->callbackDecoded(success, error_message, raw, aux, request_id); + } + } + private: + LLTextureFetch* mFetcher; + LLUUID mID; + }; + + struct Compare + { + // lhs < rhs + bool operator()(const LLTextureFetchWorker* lhs, const LLTextureFetchWorker* rhs) const + { + // greater priority is "less" + return lhs->mImagePriority > rhs->mImagePriority; + } + }; + +public: + + // Threads: Ttf + /*virtual*/ bool doWork(S32 param); // Called from LLWorkerThread::processRequest() + + // Threads: Ttf + /*virtual*/ void finishWork(S32 param, bool completed); // called from finishRequest() (WORK THREAD) + + // Threads: Tmain + /*virtual*/ bool deleteOK(); // called from update() + + ~LLTextureFetchWorker(); + + // Threads: Ttf + // Locks: Mw + S32 callbackHttpGet(LLCore::HttpResponse * response, + bool partial, bool success); + + // Threads: Ttc + void callbackCacheRead(bool success, LLImageFormatted* image, + S32 imagesize, bool islocal); + + // Threads: Ttc + void callbackCacheWrite(bool success); + + // Threads: Tid + void callbackDecoded(bool success, const std::string& error_message, LLImageRaw* raw, LLImageRaw* aux, S32 decode_id); + + // Threads: T* + void setGetStatus(LLCore::HttpStatus status, const std::string& reason) + { + LLMutexLock lock(&mWorkMutex); + + mGetStatus = status; + mGetReason = reason; + } + + void setCanUseHTTP(bool can_use_http) { mCanUseHTTP = can_use_http; } + bool getCanUseHTTP() const { return mCanUseHTTP; } + + void setUrl(const std::string& url) { mUrl = url; } + + LLTextureFetch & getFetcher() { return *mFetcher; } + + // Inherited from LLCore::HttpHandler + // Threads: Ttf + virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response); + + enum e_state // mState + { + // *NOTE: Do not change the order/value of state variables, some code + // depends upon specific ordering/adjacency. + + // NOTE: Affects LLTextureBar::draw in lltextureview.cpp (debug hack) + INVALID = 0, + INIT, + LOAD_FROM_TEXTURE_CACHE, + CACHE_POST, + LOAD_FROM_NETWORK, + WAIT_HTTP_RESOURCE, // Waiting for HTTP resources + WAIT_HTTP_RESOURCE2, // Waiting for HTTP resources + SEND_HTTP_REQ, // Commit to sending as HTTP + WAIT_HTTP_REQ, // Request sent, wait for completion + DECODE_IMAGE, + DECODE_IMAGE_UPDATE, + WRITE_TO_CACHE, + WAIT_ON_WRITE, + DONE + }; + +protected: + LLTextureFetchWorker(LLTextureFetch* fetcher, FTType f_type, + const std::string& url, const LLUUID& id, const LLHost& host, + F32 priority, S32 discard, S32 size); + +private: + + // Threads: Tmain + /*virtual*/ void startWork(S32 param); // called from addWork() (MAIN THREAD) + + // Threads: Tmain + /*virtual*/ void endWork(S32 param, bool aborted); // called from doWork() (MAIN THREAD) + + // Locks: Mw + void resetFormattedData(); + + // get the relative priority of this worker (should map to max virtual size) + F32 getImagePriority() const; + + // Locks: Mw + void setImagePriority(F32 priority); + + // Locks: Mw (ctor invokes without lock) + void setDesiredDiscard(S32 discard, S32 size); + + // Threads: T* + // Locks: Mw + bool insertPacket(S32 index, U8* data, S32 size); + + // Locks: Mw + void clearPackets(); + + + // Locks: Mw + void removeFromCache(); + + // Threads: Ttf + bool writeToCacheComplete(); + + // Threads: Ttf + void recordTextureStart(bool is_http); + + // Threads: Ttf + void recordTextureDone(bool is_http, F64 byte_count); + + void lockWorkMutex() { mWorkMutex.lock(); } + void unlockWorkMutex() { mWorkMutex.unlock(); } + + // Threads: Ttf + // Locks: Mw + bool acquireHttpSemaphore() + { + llassert(! mHttpHasResource); + if (mFetcher->mHttpSemaphore >= mFetcher->mHttpHighWater) + { + return false; + } + mHttpHasResource = true; + mFetcher->mHttpSemaphore++; + return true; + } + + // Threads: Ttf + // Locks: Mw + void releaseHttpSemaphore() + { + llassert(mHttpHasResource); + mHttpHasResource = false; + mFetcher->mHttpSemaphore--; + llassert_always(mFetcher->mHttpSemaphore >= 0); + } + +private: + enum e_request_state // mSentRequest + { + UNSENT = 0, + QUEUED = 1, + SENT_SIM = 2 + }; + enum e_write_to_cache_state //mWriteToCacheState + { + NOT_WRITE = 0, + CAN_WRITE = 1, + SHOULD_WRITE = 2 + }; + + e_state mState; + void setState(e_state new_state); + LLViewerRegion* getRegion(); + + e_write_to_cache_state mWriteToCacheState; + LLTextureFetch* mFetcher; + LLPointer mFormattedImage; + LLPointer mRawImage, + mAuxImage; + FTType mFTType; + LLUUID mID; + LLHost mHost; + std::string mUrl; + U8 mType; + F32 mImagePriority; // should map to max virtual size + F32 mRequestedPriority; + S32 mDesiredDiscard; + S32 mSimRequestedDiscard; + S32 mRequestedDiscard; + S32 mLoadedDiscard; + S32 mDecodedDiscard; + LLFrameTimer mRequestedDeltaTimer; + LLFrameTimer mFetchDeltaTimer; + LLTimer mCacheReadTimer; + LLTimer mDecodeTimer; + LLTimer mCacheWriteTimer; + LLTimer mFetchTimer; + LLTimer mStateTimer; + F32 mCacheReadTime; // time for cache read only + F32 mDecodeTime; // time for decode only + F32 mCacheWriteTime; + F32 mFetchTime; // total time from req to finished fetch + std::map mStateTimersMap; + F32 mSkippedStatesTime; + LLTextureCache::handle_t mCacheReadHandle, + mCacheWriteHandle; + S32 mRequestedSize, + mRequestedOffset, + mDesiredSize, + mFileSize, + mCachedSize; + e_request_state mSentRequest; + handle_t mDecodeHandle; + bool mLoaded; + bool mDecoded; + bool mWritten; + bool mNeedsAux; + bool mHaveAllData; + bool mInLocalCache; + bool mInCache; + bool mCanUseHTTP; + S32 mRetryAttempt; + S32 mActiveCount; + LLCore::HttpStatus mGetStatus; + std::string mGetReason; + LLAdaptiveRetryPolicy mFetchRetryPolicy; + bool mCanUseCapability; + LLTimer mRegionRetryTimer; + S32 mRegionRetryAttempt; + LLUUID mLastRegionId; + + + // Work Data + LLMutex mWorkMutex; + struct PacketData + { + PacketData(U8* data, S32 size) + : mData(data), mSize(size) + {} + ~PacketData() { clearData(); } + void clearData() { delete[] mData; mData = NULL; } + + U8* mData; + U32 mSize; + }; + std::vector mPackets; + S32 mFirstPacket; + S32 mLastPacket; + U16 mTotalPackets; + U8 mImageCodec; + + LLViewerAssetStats::duration_t mMetricsStartTime; + + LLCore::HttpHandle mHttpHandle; // Handle of any active request + LLCore::BufferArray * mHttpBufferArray; // Refcounted pointer to response data + S32 mHttpPolicyClass; + bool mHttpActive; // Active request to http library + U32 mHttpReplySize, // Actual received data size + mHttpReplyOffset; // Actual received data offset + bool mHttpHasResource; // Counts against Fetcher's mHttpSemaphore + + // State history + U32 mCacheReadCount, + mCacheWriteCount, + mResourceWaitCount; // Requests entering WAIT_HTTP_RESOURCE2 +}; + +////////////////////////////////////////////////////////////////////////////// + +// Cross-thread messaging for asset metrics. + +/** + * @brief Base class for cross-thread requests made of the fetcher + * + * I believe the intent of the LLQueuedThread class was to + * have these operations derived from LLQueuedThread::QueuedRequest + * but the texture fetcher has elected to manage the queue + * in its own manner. So these are free-standing objects which are + * managed in simple FIFO order on the mCommands queue of the + * LLTextureFetch object. + * + * What each represents is a simple command sent from an + * outside thread into the TextureFetch thread to be processed + * in order and in a timely fashion (though not an absolute + * higher priority than other operations of the thread). + * Each operation derives a new class from the base customizing + * members, constructors and the doWork() method to effect + * the command. + * + * The flow is one-directional. There are two global instances + * of the LLViewerAssetStats collector, one for the main program's + * thread pointed to by gViewerAssetStatsMain and one for the + * TextureFetch thread pointed to by gViewerAssetStatsThread1. + * Common operations has each thread recording metrics events + * into the respective collector unconcerned with locking and + * the state of any other thread. But when the agent moves into + * a different region or the metrics timer expires and a report + * needs to be sent back to the grid, messaging across threads + * is required to distribute data and perform global actions. + * In pseudo-UML, it looks like: + * + * @verbatim + * Main Thread1 + * . . + * . . + * +-----+ . + * | AM | . + * +--+--+ . + * +-------+ | . + * | Main | +--+--+ . + * | | | SRE |---. . + * | Stats | +-----+ \ . + * | | | \ (uuid) +-----+ + * | Coll. | +--+--+ `-------->| SR | + * +-------+ | MSC | +--+--+ + * | ^ +-----+ | + * | | (uuid) / . +-----+ (uuid) + * | `--------' . | MSC |---------. + * | . +-----+ | + * | +-----+ . v + * | | TE | . +-------+ + * | +--+--+ . | Thd1 | + * | | . | | + * | +-----+ . | Stats | + * `--------->| RSC | . | | + * +--+--+ . | Coll. | + * | . +-------+ + * +--+--+ . | + * | SME |---. . | + * +-----+ \ . | + * . \ (clone) +-----+ | + * . `-------->| SM | | + * . +--+--+ | + * . | | + * . +-----+ | + * . | RSC |<--------' + * . +-----+ + * . | + * . +-----+ + * . | CP |--> HTTP POST + * . +-----+ + * . . + * . . + * + * Key: + * + * SRE - Set Region Enqueued. Enqueue a 'Set Region' command in + * the other thread providing the new UUID of the region. + * TFReqSetRegion carries the data. + * SR - Set Region. New region UUID is sent to the thread-local + * collector. + * SME - Send Metrics Enqueued. Enqueue a 'Send Metrics' command + * including an ownership transfer of a cloned LLViewerAssetStats. + * TFReqSendMetrics carries the data. + * SM - Send Metrics. Global metrics reporting operation. Takes + * the cloned stats from the command, merges it with the + * thread's local stats, converts to LLSD and sends it on + * to the grid. + * AM - Agent Moved. Agent has completed some sort of move to a + * new region. + * TE - Timer Expired. Metrics timer has expired (on the order + * of 10 minutes). + * CP - CURL Post + * MSC - Modify Stats Collector. State change in the thread-local + * collector. Typically a region change which affects the + * global pointers used to find the 'current stats'. + * RSC - Read Stats Collector. Extract collector data cloning it + * (i.e. deep copy) when necessary. + * @endverbatim + * + */ +class LLTextureFetch::TFRequest // : public LLQueuedThread::QueuedRequest +{ +public: + // Default ctors and assignment operator are correct. + + virtual ~TFRequest() + {} + + // Patterned after QueuedRequest's method but expected behavior + // is different. Always expected to complete on the first call + // and work dispatcher will assume the same and delete the + // request after invocation. + virtual bool doWork(LLTextureFetch * fetcher) = 0; +}; + +namespace +{ + +/** + * @brief Implements a 'Set Region' cross-thread command. + * + * When an agent moves to a new region, subsequent metrics need + * to be binned into a new or existing stats collection in 1:1 + * relationship with the region. We communicate this region + * change across the threads involved in the communication with + * this message. + * + * Corresponds to LLTextureFetch::commandSetRegion() + */ +class TFReqSetRegion : public LLTextureFetch::TFRequest +{ +public: + TFReqSetRegion(U64 region_handle) + : LLTextureFetch::TFRequest(), + mRegionHandle(region_handle) + {} + TFReqSetRegion & operator=(const TFReqSetRegion &); // Not defined + + virtual ~TFReqSetRegion() + {} + + virtual bool doWork(LLTextureFetch * fetcher); + +public: + const U64 mRegionHandle; +}; + + +/** + * @brief Implements a 'Send Metrics' cross-thread command. + * + * This is the big operation. The main thread gathers metrics + * for a period of minutes into LLViewerAssetStats and other + * objects then makes a snapshot of the data by cloning the + * collector. This command transfers the clone, along with a few + * additional arguments (UUIDs), handing ownership to the + * TextureFetch thread. It then merges its own data into the + * cloned copy, converts to LLSD and kicks off an HTTP POST of + * the resulting data to the currently active metrics collector. + * + * Corresponds to LLTextureFetch::commandSendMetrics() + */ +class TFReqSendMetrics : public LLTextureFetch::TFRequest +{ +public: + /** + * Construct the 'Send Metrics' command to have the TextureFetch + * thread add and log metrics data. + * + * @param caps_url URL of a "ViewerMetrics" Caps target + * to receive the data. Does not have to + * be associated with a particular region. + * + * @param session_id UUID of the agent's session. + * + * @param agent_id UUID of the agent. (Being pure here...) + * + * @param main_stats Pointer to a clone of the main thread's + * LLViewerAssetStats data. Thread1 takes + * ownership of the copy and disposes of it + * when done. + */ + TFReqSendMetrics(const std::string & caps_url, + const LLUUID & session_id, + const LLUUID & agent_id, + LLSD& stats_sd); + TFReqSendMetrics & operator=(const TFReqSendMetrics &); // Not defined + + virtual ~TFReqSendMetrics(); + + virtual bool doWork(LLTextureFetch * fetcher); + +public: + const std::string mCapsURL; + const LLUUID mSessionID; + const LLUUID mAgentID; + LLSD mStatsSD; + +private: + LLCore::HttpHandler::ptr_t mHandler; +}; + +/* + * Examines the merged viewer metrics report and if found to be too long, + * will attempt to truncate it in some reasonable fashion. + * + * @param max_regions Limit of regions allowed in report. + * + * @param metrics Full, merged viewer metrics report. + * + * @returns If data was truncated, returns true. + */ +bool truncate_viewer_metrics(int max_regions, LLSD & metrics); + +} // end of anonymous namespace + + +////////////////////////////////////////////////////////////////////////////// + +const char* sStateDescs[] = { + "INVALID", + "INIT", + "LOAD_FROM_TEXTURE_CACHE", + "CACHE_POST", + "LOAD_FROM_NETWORK", + "WAIT_HTTP_RESOURCE", + "WAIT_HTTP_RESOURCE2", + "SEND_HTTP_REQ", + "WAIT_HTTP_REQ", + "DECODE_IMAGE", + "DECODE_IMAGE_UPDATE", + "WRITE_TO_CACHE", + "WAIT_ON_WRITE", + "DONE" +}; + +const std::set LOGGED_STATES = { LLTextureFetchWorker::LOAD_FROM_TEXTURE_CACHE, LLTextureFetchWorker::LOAD_FROM_NETWORK, + LLTextureFetchWorker::WAIT_HTTP_REQ, LLTextureFetchWorker::DECODE_IMAGE_UPDATE, LLTextureFetchWorker::WAIT_ON_WRITE }; + +// static +volatile bool LLTextureFetch::svMetricsDataBreak(true); // Start with a data break + +// called from MAIN THREAD + +LLTextureFetchWorker::LLTextureFetchWorker(LLTextureFetch* fetcher, + FTType f_type, // Fetched image type + const std::string& url, // Optional URL + const LLUUID& id, // Image UUID + const LLHost& host, // Simulator host + F32 priority, // Priority + S32 discard, // Desired discard + S32 size) // Desired size + : LLWorkerClass(fetcher, "TextureFetch"), + LLCore::HttpHandler(), + mState(INIT), + mWriteToCacheState(NOT_WRITE), + mFetcher(fetcher), + mFTType(f_type), + mID(id), + mHost(host), + mUrl(url), + mImagePriority(priority), + mRequestedPriority(0.f), + mDesiredDiscard(-1), + mSimRequestedDiscard(-1), + mRequestedDiscard(-1), + mLoadedDiscard(-1), + mDecodedDiscard(-1), + mCacheReadTime(0.f), + mCacheWriteTime(0.f), + mDecodeTime(0.f), + mFetchTime(0.f), + mCacheReadHandle(LLTextureCache::nullHandle()), + mCacheWriteHandle(LLTextureCache::nullHandle()), + mRequestedSize(0), + mRequestedOffset(0), + mDesiredSize(TEXTURE_CACHE_ENTRY_SIZE), + mFileSize(0), + mSkippedStatesTime(0), + mCachedSize(0), + mLoaded(false), + mSentRequest(UNSENT), + mDecodeHandle(0), + mDecoded(false), + mWritten(false), + mNeedsAux(false), + mHaveAllData(false), + mInLocalCache(false), + mInCache(false), + mCanUseHTTP(true), + mRetryAttempt(0), + mActiveCount(0), + mWorkMutex(), + mFirstPacket(0), + mLastPacket(-1), + mTotalPackets(0), + mImageCodec(IMG_CODEC_INVALID), + mMetricsStartTime(0), + mHttpHandle(LLCORE_HTTP_HANDLE_INVALID), + mHttpBufferArray(NULL), + mHttpPolicyClass(mFetcher->mHttpPolicyClass), + mHttpActive(false), + mHttpReplySize(0U), + mHttpReplyOffset(0U), + mHttpHasResource(false), + mCacheReadCount(0U), + mCacheWriteCount(0U), + mResourceWaitCount(0U), + mFetchRetryPolicy(10.f,3600.f,2.f,10), + mCanUseCapability(true), + mRegionRetryAttempt(0) +{ + mType = host.isOk() ? LLImageBase::TYPE_AVATAR_BAKE : LLImageBase::TYPE_NORMAL; +// LL_INFOS(LOG_TXT) << "Create: " << mID << " mHost:" << host << " Discard=" << discard << LL_ENDL; + if (!mFetcher->mDebugPause) + { + addWork(0); + } + setDesiredDiscard(discard, size); +} + +LLTextureFetchWorker::~LLTextureFetchWorker() +{ +// LL_INFOS(LOG_TXT) << "Destroy: " << mID +// << " Decoded=" << mDecodedDiscard +// << " Requested=" << mRequestedDiscard +// << " Desired=" << mDesiredDiscard << LL_ENDL; + llassert_always(!haveWork()); + + lockWorkMutex(); // +Mw (should be useless) + if (mHttpHasResource) + { + // Last-chance catchall to recover the resource. Using an + // atomic datatype solely because this can be running in + // another thread. + releaseHttpSemaphore(); + } + if (mHttpActive) + { + // Issue a cancel on a live request... + mFetcher->getHttpRequest().requestCancel(mHttpHandle, LLCore::HttpHandler::ptr_t()); + } + if (mCacheReadHandle != LLTextureCache::nullHandle() && mFetcher->mTextureCache) + { + mFetcher->mTextureCache->readComplete(mCacheReadHandle, true); + } + if (mCacheWriteHandle != LLTextureCache::nullHandle() && mFetcher->mTextureCache) + { + mFetcher->mTextureCache->writeComplete(mCacheWriteHandle, true); + } + mFormattedImage = NULL; + clearPackets(); + if (mHttpBufferArray) + { + mHttpBufferArray->release(); + mHttpBufferArray = NULL; + } + unlockWorkMutex(); // -Mw + mFetcher->removeFromHTTPQueue(mID, (S32Bytes)0); + mFetcher->removeHttpWaiter(mID); + mFetcher->updateStateStats(mCacheReadCount, mCacheWriteCount, mResourceWaitCount); +} + +// Locks: Mw +void LLTextureFetchWorker::clearPackets() +{ + for_each(mPackets.begin(), mPackets.end(), DeletePointer()); + mPackets.clear(); + mTotalPackets = 0; + mLastPacket = -1; + mFirstPacket = 0; +} + +// Locks: Mw (ctor invokes without lock) +void LLTextureFetchWorker::setDesiredDiscard(S32 discard, S32 size) +{ + bool prioritize = false; + if (mDesiredDiscard != discard) + { + if (!haveWork()) + { + if (!mFetcher->mDebugPause) + { + addWork(0); + } + } + else if (mDesiredDiscard < discard) + { + prioritize = true; + } + mDesiredDiscard = discard; + mDesiredSize = size; + } + else if (size > mDesiredSize) + { + mDesiredSize = size; + prioritize = true; + } + mDesiredSize = llmax(mDesiredSize, TEXTURE_CACHE_ENTRY_SIZE); + if ((prioritize && mState == INIT) || mState == DONE) + { + setState(INIT); + } +} + +// Locks: Mw +void LLTextureFetchWorker::setImagePriority(F32 priority) +{ + mImagePriority = priority; //should map to max virtual size, abort if zero +} + +// Locks: Mw +void LLTextureFetchWorker::resetFormattedData() +{ + if (mHttpBufferArray) + { + mHttpBufferArray->release(); + mHttpBufferArray = NULL; + } + if (mFormattedImage.notNull()) + { + mFormattedImage->deleteData(); + } + mHttpReplySize = 0; + mHttpReplyOffset = 0; + mHaveAllData = false; +} + +F32 LLTextureFetchWorker::getImagePriority() const +{ + return mImagePriority; +} + +// Threads: Tmain +void LLTextureFetchWorker::startWork(S32 param) +{ + llassert(mFormattedImage.isNull()); +} + +// Threads: Ttf +bool LLTextureFetchWorker::doWork(S32 param) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_THREAD; + if (gNonInteractive) + { + return true; + } + static const LLCore::HttpStatus http_not_found(HTTP_NOT_FOUND); // 404 + static const LLCore::HttpStatus http_service_unavail(HTTP_SERVICE_UNAVAILABLE); // 503 + static const LLCore::HttpStatus http_not_sat(HTTP_REQUESTED_RANGE_NOT_SATISFIABLE); // 416; + + LLMutexLock lock(&mWorkMutex); // +Mw + + if ((mFetcher->isQuitting() || getFlags(LLWorkerClass::WCF_DELETE_REQUESTED))) + { + if (mState < DECODE_IMAGE) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - state < decode"); + return true; // abort + } + } + + if (mImagePriority < F_ALMOST_ZERO) + { + if (mState == INIT || mState == LOAD_FROM_NETWORK) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - priority < 0"); + LL_DEBUGS(LOG_TXT) << mID << " abort: mImagePriority < F_ALMOST_ZERO" << LL_ENDL; + return true; // abort + } + } + if (mState > CACHE_POST && !mCanUseCapability && mCanUseHTTP) + { + if (mRegionRetryAttempt > MAX_CAP_MISSING_RETRIES) + { + mCanUseHTTP = false; + } + else if (!mRegionRetryTimer.hasExpired()) + { + return false; + } + // else retry + } + if(mState > CACHE_POST && !mCanUseHTTP) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - state > cache_post"); + //nowhere to get data, abort. + LL_WARNS(LOG_TXT) << mID << " abort, nowhere to get data" << LL_ENDL; + return true ; + } + + if (mFetcher->mDebugPause) + { + return false; // debug: don't do any work + } + if (mID == mFetcher->mDebugID) + { + mFetcher->mDebugCount++; // for setting breakpoints + } + + if (mState != DONE) + { + mFetchDeltaTimer.reset(); + } + + if (mState == INIT) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - INIT"); + mStateTimer.reset(); + mFetchTimer.reset(); + for(auto i : LOGGED_STATES) + { + mStateTimersMap[i] = 0; + } + mSkippedStatesTime = 0; + mRawImage = NULL ; + mRequestedDiscard = -1; + mLoadedDiscard = -1; + mDecodedDiscard = -1; + mRequestedSize = 0; + mRequestedOffset = 0; + mFileSize = 0; + mCachedSize = 0; + mLoaded = false; + mSentRequest = UNSENT; + mDecoded = false; + mWritten = false; + if (mHttpBufferArray) + { + mHttpBufferArray->release(); + mHttpBufferArray = NULL; + } + mHttpReplySize = 0; + mHttpReplyOffset = 0; + mHaveAllData = false; + clearPackets(); // TODO: Shouldn't be necessary + mCacheReadHandle = LLTextureCache::nullHandle(); + mCacheWriteHandle = LLTextureCache::nullHandle(); + setState(LOAD_FROM_TEXTURE_CACHE); + mInCache = false; + mDesiredSize = llmax(mDesiredSize, TEXTURE_CACHE_ENTRY_SIZE); // min desired size is TEXTURE_CACHE_ENTRY_SIZE + LL_DEBUGS(LOG_TXT) << mID << ": Priority: " << llformat("%8.0f",mImagePriority) + << " Desired Discard: " << mDesiredDiscard << " Desired Size: " << mDesiredSize << LL_ENDL; + + // fall through + } + + if (mState == LOAD_FROM_TEXTURE_CACHE) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - LOAD_FROM_TEXTURE_CACHE"); + if (mCacheReadHandle == LLTextureCache::nullHandle()) + { + S32 offset = mFormattedImage.notNull() ? mFormattedImage->getDataSize() : 0; + S32 size = mDesiredSize - offset; + if (size <= 0) + { + setState(CACHE_POST); + return doWork(param); + // return false; + } + mFileSize = 0; + mLoaded = false; + + add(LLTextureFetch::sCacheAttempt, 1.0); + + if (mUrl.compare(0, 7, "file://") == 0) + { + // read file from local disk + ++mCacheReadCount; + std::string filename = mUrl.substr(7, std::string::npos); + CacheReadResponder* responder = new CacheReadResponder(mFetcher, mID, mFormattedImage); + mCacheReadTimer.reset(); + mCacheReadHandle = mFetcher->mTextureCache->readFromCache(filename, mID, offset, size, responder); + + } + else if ((mUrl.empty() || mFTType==FTT_SERVER_BAKE) && mFetcher->canLoadFromCache()) + { + ++mCacheReadCount; + CacheReadResponder* responder = new CacheReadResponder(mFetcher, mID, mFormattedImage); + mCacheReadTimer.reset(); + mCacheReadHandle = mFetcher->mTextureCache->readFromCache(mID, + offset, size, responder);; + } + else if(!mUrl.empty() && mCanUseHTTP) + { + setState(WAIT_HTTP_RESOURCE); + } + else + { + setState(LOAD_FROM_NETWORK); + } + } + + if (mLoaded) + { + // Make sure request is complete. *TODO: make this auto-complete + if (mFetcher->mTextureCache->readComplete(mCacheReadHandle, false)) + { + mCacheReadHandle = LLTextureCache::nullHandle(); + setState(CACHE_POST); + add(LLTextureFetch::sCacheHit, 1.0); + mCacheReadTime = mCacheReadTimer.getElapsedTimeF32(); + // fall through + } + else + { + // + //This should never happen + // + LL_DEBUGS(LOG_TXT) << mID << " this should never happen" << LL_ENDL; + return false; + } + } + else + { + return false; + } + } + + if (mState == CACHE_POST) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - CACHE_POST"); + mCachedSize = mFormattedImage.notNull() ? mFormattedImage->getDataSize() : 0; + // Successfully loaded + if ((mCachedSize >= mDesiredSize) || mHaveAllData) + { + // we have enough data, decode it + llassert_always(mFormattedImage->getDataSize() > 0); + mLoadedDiscard = mDesiredDiscard; + if (mLoadedDiscard < 0) + { + LL_WARNS(LOG_TXT) << mID << " mLoadedDiscard is " << mLoadedDiscard + << ", should be >=0" << LL_ENDL; + } + setState(DECODE_IMAGE); + mInCache = true; + mWriteToCacheState = NOT_WRITE ; + LL_DEBUGS(LOG_TXT) << mID << ": Cached. Bytes: " << mFormattedImage->getDataSize() + << " Size: " << llformat("%dx%d",mFormattedImage->getWidth(),mFormattedImage->getHeight()) + << " Desired Discard: " << mDesiredDiscard << " Desired Size: " << mDesiredSize << LL_ENDL; + record(LLTextureFetch::sCacheHitRate, LLUnits::Ratio::fromValue(1)); + } + else + { + if (mUrl.compare(0, 7, "file://") == 0) + { + // failed to load local file, we're done. + LL_WARNS(LOG_TXT) << mID << ": abort, failed to load local file " << mUrl << LL_ENDL; + return true; + } + // need more data + else + { + LL_DEBUGS(LOG_TXT) << mID << ": Not in Cache" << LL_ENDL; + setState(LOAD_FROM_NETWORK); + } + record(LLTextureFetch::sCacheHitRate, LLUnits::Ratio::fromValue(0)); + // fall through + } + } + + if (mState == LOAD_FROM_NETWORK) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - LOAD_FROM_NETWORK"); + // Check for retries to previous server failures. + F32 wait_seconds; + if (mFetchRetryPolicy.shouldRetry(wait_seconds)) + { + if (wait_seconds <= 0.0) + { + LL_INFOS(LOG_TXT) << mID << " retrying now" << LL_ENDL; + } + else + { + //LL_INFOS(LOG_TXT) << mID << " waiting to retry for " << wait_seconds << " seconds" << LL_ENDL; + return false; + } + } + + static LLCachedControl use_http(gSavedSettings, "ImagePipelineUseHTTP", true); + +// if (mHost.isInvalid()) get_url = false; + if ( use_http && mCanUseHTTP && mUrl.empty())//get http url. + { + LLViewerRegion* region = getRegion(); + if (region) + { + std::string http_url = region->getViewerAssetUrl(); + if (!http_url.empty()) + { + if (mFTType != FTT_DEFAULT) + { + LL_WARNS(LOG_TXT) << "Trying to fetch a texture of non-default type by UUID. This probably won't work!" << LL_ENDL; + } + setUrl(http_url + "/?texture_id=" + mID.asString().c_str()); + LL_DEBUGS(LOG_TXT) << "Texture URL: " << mUrl << LL_ENDL; + mWriteToCacheState = CAN_WRITE ; //because this texture has a fixed texture id. + mCanUseCapability = true; + mRegionRetryAttempt = 0; + mLastRegionId = region->getRegionID(); + } + else + { + mCanUseCapability = false; + mRegionRetryAttempt++; + mRegionRetryTimer.setTimerExpirySec(CAP_MISSING_EXPIRATION_DELAY); + // ex: waiting for caps + LL_INFOS_ONCE(LOG_TXT) << "Texture not available via HTTP: empty URL." << LL_ENDL; + } + } + else + { + mCanUseCapability = false; + mRegionRetryAttempt++; + mRegionRetryTimer.setTimerExpirySec(CAP_MISSING_EXPIRATION_DELAY); + // This will happen if not logged in or if a region deoes not have HTTP Texture enabled + //LL_WARNS(LOG_TXT) << "Region not found for host: " << mHost << LL_ENDL; + LL_INFOS_ONCE(LOG_TXT) << "Texture not available via HTTP: no region " << mUrl << LL_ENDL; + } + } + else if (mFTType == FTT_SERVER_BAKE) + { + mWriteToCacheState = CAN_WRITE; + } + + if (mCanUseCapability && mCanUseHTTP && !mUrl.empty()) + { + setState(WAIT_HTTP_RESOURCE); + if(mWriteToCacheState != NOT_WRITE) + { + mWriteToCacheState = CAN_WRITE ; + } + // don't return, fall through to next state + } + else + { + return false; + } + } + + if (mState == WAIT_HTTP_RESOURCE) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - WAIT_HTTP_RESOURCE"); + // NOTE: + // control the number of the http requests issued for: + // 1, not openning too many file descriptors at the same time; + // 2, control the traffic of http so udp gets bandwidth. + // + // If it looks like we're busy, keep this request here. + // Otherwise, advance into the HTTP states. + + if (!mHttpHasResource && // sometimes we get into this state when we already have an http resource, go ahead and send the request in that case + (mFetcher->getHttpWaitersCount() || ! acquireHttpSemaphore())) + { + setState(WAIT_HTTP_RESOURCE2); + mFetcher->addHttpWaiter(this->mID); + ++mResourceWaitCount; + return false; + } + + setState(SEND_HTTP_REQ); + // *NOTE: You must invoke releaseHttpSemaphore() if you transition + // to a state other than SEND_HTTP_REQ or WAIT_HTTP_REQ or abort + // the request. + } + + if (mState == WAIT_HTTP_RESOURCE2) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - WAIT_HTTP_RESOURCE2"); + // Just idle it if we make it to the head... + return false; + } + + if (mState == SEND_HTTP_REQ) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - SEND_HTTP_REQ"); + // Also used in llmeshrepository + static LLCachedControl disable_range_req(gSavedSettings, "HttpRangeRequestsDisable", false); + + if (! mCanUseHTTP) + { + releaseHttpSemaphore(); + LL_WARNS(LOG_TXT) << mID << " abort: SEND_HTTP_REQ but !mCanUseHTTP" << LL_ENDL; + return true; // abort + } + + S32 cur_size = 0; + if (mFormattedImage.notNull()) + { + cur_size = mFormattedImage->getDataSize(); // amount of data we already have + if (mFormattedImage->getDiscardLevel() == 0) + { + if (cur_size > 0) + { + // We already have all the data, just decode it + mLoadedDiscard = mFormattedImage->getDiscardLevel(); + if (mLoadedDiscard < 0) + { + LL_WARNS(LOG_TXT) << mID << " mLoadedDiscard is " << mLoadedDiscard + << ", should be >=0" << LL_ENDL; + } + setState(DECODE_IMAGE); + releaseHttpSemaphore(); + //return false; + return doWork(param); + } + else + { + releaseHttpSemaphore(); + LL_WARNS(LOG_TXT) << mID << " SEND_HTTP_REQ abort: cur_size " << cur_size << " <=0" << LL_ENDL; + return true; // abort. + } + } + } + mRequestedSize = mDesiredSize; + mRequestedDiscard = mDesiredDiscard; + mRequestedSize -= cur_size; + mRequestedOffset = cur_size; + if (mRequestedOffset) + { + // Texture fetching often issues 'speculative' loads that + // start beyond the end of the actual asset. Some cache/web + // systems, e.g. Varnish, will respond to this not with a + // 416 but with a 200 and the entire asset in the response + // body. By ensuring that we always have a partially + // satisfiable Range request, we avoid that hit to the network. + // We just have to deal with the overlapping data which is made + // somewhat harder by the fact that grid services don't necessarily + // return the Content-Range header on 206 responses. *Sigh* + mRequestedOffset -= 1; + mRequestedSize += 1; + } + mHttpHandle = LLCORE_HTTP_HANDLE_INVALID; + + if (mUrl.empty()) + { + // *FIXME: This should not be reachable except it has become + // so after some recent 'work'. Need to track this down + // and illuminate the unenlightened. + LL_WARNS(LOG_TXT) << "HTTP GET request failed for " << mID + << " on empty URL." << LL_ENDL; + resetFormattedData(); + releaseHttpSemaphore(); + return true; // failed + } + + mRequestedDeltaTimer.reset(); + mLoaded = false; + mGetStatus = LLCore::HttpStatus(); + mGetReason.clear(); + LL_DEBUGS(LOG_TXT) << "HTTP GET: " << mID << " Offset: " << mRequestedOffset + << " Bytes: " << mRequestedSize + << " Bandwidth(kbps): " << mFetcher->getTextureBandwidth() << "/" << mFetcher->mMaxBandwidth + << LL_ENDL; + + // Will call callbackHttpGet when curl request completes + // Only server bake images use the returned headers currently, for getting retry-after field. + LLCore::HttpOptions::ptr_t options = (mFTType == FTT_SERVER_BAKE) ? mFetcher->mHttpOptionsWithHeaders: mFetcher->mHttpOptions; + if (disable_range_req) + { + // 'Range:' requests may be disabled in which case all HTTP + // texture fetches result in full fetches. This can be used + // by people with questionable ISPs or networking gear that + // doesn't handle these well. + mHttpHandle = mFetcher->mHttpRequest->requestGet(mHttpPolicyClass, + mUrl, + options, + mFetcher->mHttpHeaders, + LLCore::HttpHandler::ptr_t(this, &NoOpDeletor)); + } + else + { + mHttpHandle = mFetcher->mHttpRequest->requestGetByteRange(mHttpPolicyClass, + mUrl, + mRequestedOffset, + (mRequestedOffset + mRequestedSize) > HTTP_REQUESTS_RANGE_END_MAX + ? 0 + : mRequestedSize, + options, + mFetcher->mHttpHeaders, + LLCore::HttpHandler::ptr_t(this, &NoOpDeletor)); + } + if (LLCORE_HTTP_HANDLE_INVALID == mHttpHandle) + { + LLCore::HttpStatus status(mFetcher->mHttpRequest->getStatus()); + LL_WARNS(LOG_TXT) << "HTTP GET request failed for " << mID + << ", Status: " << status.toTerseString() + << " Reason: '" << status.toString() << "'" + << LL_ENDL; + resetFormattedData(); + releaseHttpSemaphore(); + return true; // failed + } + + mHttpActive = true; + mFetcher->addToHTTPQueue(mID); + recordTextureStart(true); + setState(WAIT_HTTP_REQ); + + // fall through + } + + if (mState == WAIT_HTTP_REQ) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - WAIT_HTTP_REQ"); + // *NOTE: As stated above, all transitions out of this state should + // call releaseHttpSemaphore(). + if (mLoaded) + { + S32 cur_size = mFormattedImage.notNull() ? mFormattedImage->getDataSize() : 0; + if (mRequestedSize < 0) + { + if (http_not_found == mGetStatus) + { + if (mFTType != FTT_MAP_TILE) + { + LL_WARNS(LOG_TXT) << "Texture missing from server (404): " << mUrl << LL_ENDL; + } + + if(mWriteToCacheState == NOT_WRITE) //map tiles or server bakes + { + setState(DONE); + releaseHttpSemaphore(); + if (mFTType != FTT_MAP_TILE) + { + LL_WARNS(LOG_TXT) << mID << " abort: WAIT_HTTP_REQ not found" << LL_ENDL; + } + return true; + } + + if (mCanUseHTTP && !mUrl.empty() && cur_size <= 0) + { + LLViewerRegion* region = getRegion(); + if (!region || mLastRegionId != region->getRegionID()) + { + // cap failure? try on new region. + mUrl.clear(); + ++mRetryAttempt; + mLastRegionId.setNull(); + setState(INIT); + return false; + } + } + } + else if (http_service_unavail == mGetStatus) + { + LL_INFOS_ONCE(LOG_TXT) << "Texture server busy (503): " << mUrl << LL_ENDL; + if (mCanUseHTTP && !mUrl.empty() && cur_size <= 0) + { + LLViewerRegion* region = getRegion(); + if (!region || mLastRegionId != region->getRegionID()) + { + // try on new region. + mUrl.clear(); + ++mRetryAttempt; + mLastRegionId.setNull(); + setState(INIT); + return false; + } + } + } + else if (http_not_sat == mGetStatus) + { + // Allowed, we'll accept whatever data we have as complete. + mHaveAllData = true; + } + else + { + LL_INFOS(LOG_TXT) << "HTTP GET failed for: " << mUrl + << " Status: " << mGetStatus.toTerseString() + << " Reason: '" << mGetReason << "'" + << LL_ENDL; + } + + if (mFTType != FTT_SERVER_BAKE && mFTType != FTT_MAP_TILE) + { + mUrl.clear(); + } + if (cur_size > 0) + { + // Use available data + mLoadedDiscard = mFormattedImage->getDiscardLevel(); + if (mLoadedDiscard < 0) + { + LL_WARNS(LOG_TXT) << mID << " mLoadedDiscard is " << mLoadedDiscard + << ", should be >=0" << LL_ENDL; + } + setState(DECODE_IMAGE); + releaseHttpSemaphore(); + //return false; + return doWork(param); + } + + // Fail harder + resetFormattedData(); + setState(DONE); + releaseHttpSemaphore(); + LL_WARNS(LOG_TXT) << mID << " abort: fail harder" << LL_ENDL; + return true; // failed + } + + // Clear the url since we're done with the fetch + // Note: mUrl is used to check is fetching is required so failure to clear it will force an http fetch + // next time the texture is requested, even if the data have already been fetched. + if(mWriteToCacheState != NOT_WRITE && mFTType != FTT_SERVER_BAKE) + { + // Why do we want to keep url if NOT_WRITE - is this a proxy for map tiles? + mUrl.clear(); + } + + if (! mHttpBufferArray || ! mHttpBufferArray->size()) + { + // no data received. + if (mHttpBufferArray) + { + mHttpBufferArray->release(); + mHttpBufferArray = NULL; + } + + // abort. + setState(DONE); + LL_WARNS(LOG_TXT) << mID << " abort: no data received" << LL_ENDL; + releaseHttpSemaphore(); + return true; + } + + S32 append_size(mHttpBufferArray->size()); + S32 total_size(cur_size + append_size); + S32 src_offset(0); + llassert_always(append_size == mRequestedSize); + if (mHttpReplyOffset && mHttpReplyOffset != cur_size) + { + // In case of a partial response, our offset may + // not be trivially contiguous with the data we have. + // Get back into alignment. + if ( (mHttpReplyOffset > cur_size) || (cur_size > mHttpReplyOffset + append_size)) + { + LL_WARNS(LOG_TXT) << "Partial HTTP response produces break in image data for texture " + << mID << ". Aborting load." << LL_ENDL; + setState(DONE); + releaseHttpSemaphore(); + return true; + } + src_offset = cur_size - mHttpReplyOffset; + append_size -= src_offset; + total_size -= src_offset; + mRequestedSize -= src_offset; // Make requested values reflect useful part + mRequestedOffset += src_offset; + } + + U8 * buffer = (U8 *)ll_aligned_malloc_16(total_size); + if (!buffer) + { + // abort. If we have no space for packet, we have not enough space to decode image + setState(DONE); + LL_WARNS(LOG_TXT) << mID << " abort: out of memory" << LL_ENDL; + releaseHttpSemaphore(); + return true; + } + + if (mFormattedImage.isNull()) + { + // For now, create formatted image based on extension + std::string extension = gDirUtilp->getExtension(mUrl); + mFormattedImage = LLImageFormatted::createFromType(LLImageBase::getCodecFromExtension(extension)); + if (mFormattedImage.isNull()) + { + mFormattedImage = new LLImageJ2C; // default + } + } + + LLImageDataLock lock(mFormattedImage); + + if (mHaveAllData) //the image file is fully loaded. + { + mFileSize = total_size; + } + else //the file size is unknown. + { + mFileSize = total_size + 1 ; //flag the file is not fully loaded. + } + + if (cur_size > 0) + { + // Copy previously collected data into buffer + memcpy(buffer, mFormattedImage->getData(), cur_size); + } + mHttpBufferArray->read(src_offset, (char *) buffer + cur_size, append_size); + + // NOTE: setData releases current data and owns new data (buffer) + mFormattedImage->setData(buffer, total_size); + + // Done with buffer array + mHttpBufferArray->release(); + mHttpBufferArray = NULL; + mHttpReplySize = 0; + mHttpReplyOffset = 0; + + mLoadedDiscard = mRequestedDiscard; + if (mLoadedDiscard < 0) + { + LL_WARNS(LOG_TXT) << mID << " mLoadedDiscard is " << mLoadedDiscard + << ", should be >=0" << LL_ENDL; + } + setState(DECODE_IMAGE); + if (mWriteToCacheState != NOT_WRITE) + { + mWriteToCacheState = SHOULD_WRITE ; + } + releaseHttpSemaphore(); + //return false; + return doWork(param); + } + else + { + // *HISTORY: There was a texture timeout test here originally that + // would cancel a request that was over 120 seconds old. That's + // probably not a good idea. Particularly rich regions can take + // an enormous amount of time to load textures. We'll revisit the + // various possible timeout components (total request time, connection + // time, I/O time, with and without retries, etc.) in the future. + + return false; + } + } + + if (mState == DECODE_IMAGE) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - DECODE_IMAGE"); + static LLCachedControl textures_decode_disabled(gSavedSettings, "TextureDecodeDisabled", false); + + if (textures_decode_disabled) + { + // for debug use, don't decode + setState(DONE); + return true; + } + + if (mDesiredDiscard < 0) + { + // We aborted, don't decode + setState(DONE); + LL_DEBUGS(LOG_TXT) << mID << " DECODE_IMAGE abort: desired discard " << mDesiredDiscard << "<0" << LL_ENDL; + return true; + } + + if (mFormattedImage->getDataSize() <= 0) + { + LL_WARNS(LOG_TXT) << "Decode entered with invalid mFormattedImage. ID = " << mID << LL_ENDL; + + //abort, don't decode + setState(DONE); + LL_DEBUGS(LOG_TXT) << mID << " DECODE_IMAGE abort: (mFormattedImage->getDataSize() <= 0)" << LL_ENDL; + return true; + } + if (mLoadedDiscard < 0) + { + LL_WARNS(LOG_TXT) << "Decode entered with invalid mLoadedDiscard. ID = " << mID << LL_ENDL; + + //abort, don't decode + setState(DONE); + LL_DEBUGS(LOG_TXT) << mID << " DECODE_IMAGE abort: mLoadedDiscard < 0" << LL_ENDL; + return true; + } + mDecodeTimer.reset(); + mRawImage = NULL; + mAuxImage = NULL; + llassert_always(mFormattedImage.notNull()); + S32 discard = mHaveAllData ? 0 : mLoadedDiscard; + mDecoded = false; + setState(DECODE_IMAGE_UPDATE); + LL_DEBUGS(LOG_TXT) << mID << ": Decoding. Bytes: " << mFormattedImage->getDataSize() << " Discard: " << discard + << " All Data: " << mHaveAllData << LL_ENDL; + + // In case worked manages to request decode, be shut down, + // then init and request decode again with first decode + // still in progress, assign a sufficiently unique id + mDecodeHandle = LLAppViewer::getImageDecodeThread()->decodeImage(mFormattedImage, + discard, + mNeedsAux, + new DecodeResponder(mFetcher, mID, this)); + if (mDecodeHandle == 0) + { + // Abort, failed to put into queue. + // Happens if viewer is shutting down + setState(DONE); + LL_DEBUGS(LOG_TXT) << mID << " DECODE_IMAGE abort: failed to post for decoding" << LL_ENDL; + return true; + } + // fall though + } + + if (mState == DECODE_IMAGE_UPDATE) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - DECODE_IMAGE_UPDATE"); + if (mDecoded) + { + mDecodeTime = mDecodeTimer.getElapsedTimeF32(); + + if (mDecodedDiscard < 0) + { + if (mCachedSize > 0 && !mInLocalCache && mRetryAttempt == 0) + { + // Cache file should be deleted, try again + LL_DEBUGS(LOG_TXT) << mID << ": Decode of cached file failed (removed), retrying" << LL_ENDL; + llassert_always(mDecodeHandle == 0); + mFormattedImage = NULL; + ++mRetryAttempt; + setState(INIT); + //return false; + return doWork(param); + } + else + { + LL_DEBUGS(LOG_TXT) << "Failed to Decode image " << mID << " after " << mRetryAttempt << " retries" << LL_ENDL; + setState(DONE); // failed + } + } + else + { + llassert_always(mRawImage.notNull()); + LL_DEBUGS(LOG_TXT) << mID << ": Decoded. Discard: " << mDecodedDiscard + << " Raw Image: " << llformat("%dx%d",mRawImage->getWidth(),mRawImage->getHeight()) << LL_ENDL; + setState(WRITE_TO_CACHE); + } + // fall through + } + else + { + return false; + } + } + + if (mState == WRITE_TO_CACHE) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - WRITE_TO_CACHE"); + if (mWriteToCacheState != SHOULD_WRITE || mFormattedImage.isNull()) + { + // If we're in a local cache or we didn't actually receive any new data, + // or we failed to load anything, skip + setState(DONE); + //return false; + return doWork(param); + } + + LLImageDataSharedLock lock(mFormattedImage); + + S32 datasize = mFormattedImage->getDataSize(); + if(mFileSize < datasize)//This could happen when http fetching and sim fetching mixed. + { + if(mHaveAllData) + { + mFileSize = datasize ; + } + else + { + mFileSize = datasize + 1 ; //flag not fully loaded. + } + } + llassert_always(datasize); + mWritten = false; + setState(WAIT_ON_WRITE); + ++mCacheWriteCount; + CacheWriteResponder* responder = new CacheWriteResponder(mFetcher, mID); + // This call might be under work mutex, but mRawImage is not nessesary safe here. + // If something retrieves it via getRequestFinished() and modifies, image won't + // be protected by work mutex and won't be safe to use here nor in cache worker. + // So make sure users of getRequestFinished() does not attempt to modify image while + // fetcher is working + mCacheWriteTimer.reset(); + mCacheWriteHandle = mFetcher->mTextureCache->writeToCache(mID, + mFormattedImage->getData(), datasize, + mFileSize, mRawImage, mDecodedDiscard, responder); + // fall through + } + + if (mState == WAIT_ON_WRITE) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - WAIT_ON_WRITE"); + if (writeToCacheComplete()) + { + mCacheWriteTime = mCacheWriteTimer.getElapsedTimeF32(); + setState(DONE); + // fall through + } + else + { + if (mDesiredDiscard < mDecodedDiscard) + { + // We're waiting for this write to complete before we can receive more data + // (we can't touch mFormattedImage until the write completes) + // Prioritize the write + mFetcher->mTextureCache->prioritizeWrite(mCacheWriteHandle); + } + return false; + } + } + + if (mState == DONE) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_THREAD("tfwdw - DONE"); + if (mDecodedDiscard >= 0 && mDesiredDiscard < mDecodedDiscard) + { + // More data was requested, return to INIT + setState(INIT); + LL_DEBUGS(LOG_TXT) << mID << " more data requested, returning to INIT: " + << " mDecodedDiscard " << mDecodedDiscard << ">= 0 && mDesiredDiscard " << mDesiredDiscard + << "<" << " mDecodedDiscard " << mDecodedDiscard << LL_ENDL; + // return false; + return doWork(param); + } + else + { + mFetchTime = mFetchTimer.getElapsedTimeF32(); + return true; + } + } + + return false; +} // -Mw + +// Threads: Ttf +// virtual +void LLTextureFetchWorker::onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) +{ + LL_PROFILE_ZONE_SCOPED; + static LLCachedControl log_to_viewer_log(gSavedSettings, "LogTextureDownloadsToViewerLog", false); + static LLCachedControl log_to_sim(gSavedSettings, "LogTextureDownloadsToSimulator", false); + static LLCachedControl log_texture_traffic(gSavedSettings, "LogTextureNetworkTraffic", false) ; + + LLMutexLock lock(&mWorkMutex); // +Mw + + mHttpActive = false; + + if (log_to_viewer_log || log_to_sim) + { + mFetcher->mTextureInfo.setRequestStartTime(mID, mMetricsStartTime.value()); + mFetcher->mTextureInfo.setRequestType(mID, LLTextureInfoDetails::REQUEST_TYPE_HTTP); + mFetcher->mTextureInfo.setRequestSize(mID, mRequestedSize); + mFetcher->mTextureInfo.setRequestOffset(mID, mRequestedOffset); + mFetcher->mTextureInfo.setRequestCompleteTimeAndLog(mID, LLTimer::getTotalTime()); + } + + static LLCachedControl fake_failure_rate(gSavedSettings, "TextureFetchFakeFailureRate", 0.0f); + F32 rand_val = ll_frand(); + F32 rate = fake_failure_rate; + if (mFTType == FTT_SERVER_BAKE && (fake_failure_rate > 0.0) && (rand_val < fake_failure_rate)) + { + LL_WARNS(LOG_TXT) << mID << " for debugging, setting fake failure status for texture " << mID + << " (rand was " << rand_val << "/" << rate << ")" << LL_ENDL; + response->setStatus(LLCore::HttpStatus(503)); + } + bool success = true; + bool partial = false; + LLCore::HttpStatus status(response->getStatus()); + if (!status && (mFTType == FTT_SERVER_BAKE)) + { + LL_INFOS(LOG_TXT) << mID << " state " << e_state_name[mState] << LL_ENDL; + mFetchRetryPolicy.onFailure(response); + F32 retry_after; + if (mFetchRetryPolicy.shouldRetry(retry_after)) + { + LL_INFOS(LOG_TXT) << mID << " will retry after " << retry_after << " seconds, resetting state to LOAD_FROM_NETWORK" << LL_ENDL; + mFetcher->removeFromHTTPQueue(mID, S32Bytes(0)); + std::string reason(status.toString()); + setGetStatus(status, reason); + releaseHttpSemaphore(); + setState(LOAD_FROM_NETWORK); + return; + } + else + { + LL_INFOS(LOG_TXT) << mID << " will not retry" << LL_ENDL; + } + } + else + { + mFetchRetryPolicy.onSuccess(); + } + + std::string reason(status.toString()); + setGetStatus(status, reason); + LL_DEBUGS(LOG_TXT) << "HTTP COMPLETE: " << mID + << " status: " << status.toTerseString() + << " '" << reason << "'" + << LL_ENDL; + + if (! status) + { + success = false; + if (mFTType != FTT_MAP_TILE) // missing map tiles are normal, don't complain about them. + { + LL_WARNS(LOG_TXT) << "CURL GET FAILED, status: " << status.toTerseString() + << " reason: " << reason << LL_ENDL; + } + } + else + { + // A warning about partial (HTTP 206) data. Some grid services + // do *not* return a 'Content-Range' header in the response to + // Range requests with a 206 status. We're forced to assume + // we get what we asked for in these cases until we can fix + // the services. + static const LLCore::HttpStatus par_status(HTTP_PARTIAL_CONTENT); + + partial = (par_status == status); + } + + S32BytesImplicit data_size = callbackHttpGet(response, partial, success); + + if (log_texture_traffic && data_size > 0) + { + // one worker per multiple textures + std::vector textures; + LLViewerTextureManager::findTextures(mID, textures); + std::vector::iterator iter = textures.begin(); + while (iter != textures.end()) + { + LLViewerTexture* tex = *iter++; + if (tex) + { + gTotalTextureBytesPerBoostLevel[tex->getBoostLevel()] += data_size; + } + } + } + + mFetcher->removeFromHTTPQueue(mID, data_size); + + recordTextureDone(true, data_size); +} // -Mw + + +// Threads: Tmain +void LLTextureFetchWorker::endWork(S32 param, bool aborted) +{ + LL_PROFILE_ZONE_SCOPED; + if (mDecodeHandle != 0) + { + // LL::ThreadPool has no operation to cancel a particular work item + mDecodeHandle = 0; + } + mFormattedImage = NULL; +} + +////////////////////////////////////////////////////////////////////////////// + +// Threads: Ttf + +// virtual +void LLTextureFetchWorker::finishWork(S32 param, bool completed) +{ + LL_PROFILE_ZONE_SCOPED; + // The following are required in case the work was aborted + if (mCacheReadHandle != LLTextureCache::nullHandle()) + { + mFetcher->mTextureCache->readComplete(mCacheReadHandle, true); + mCacheReadHandle = LLTextureCache::nullHandle(); + } + if (mCacheWriteHandle != LLTextureCache::nullHandle()) + { + mFetcher->mTextureCache->writeComplete(mCacheWriteHandle, true); + mCacheWriteHandle = LLTextureCache::nullHandle(); + } +} + +// LLQueuedThread's update() method is asking if it's okay to +// delete this worker. You'll notice we're not locking in here +// which is a slight concern. Caller is expected to have made +// this request 'quiet' by whatever means... +// +// Threads: Tmain + +// virtual +bool LLTextureFetchWorker::deleteOK() +{ + bool delete_ok = true; + + if (mHttpActive) + { + // HTTP library has a pointer to this worker + // and will dereference it to do notification. + delete_ok = false; + } + + if (WAIT_HTTP_RESOURCE2 == mState) + { + if (mFetcher->isHttpWaiter(mID)) + { + // Don't delete the worker out from under the releaseHttpWaiters() + // method. Keep the pointers valid, clean up after that method + // has recognized the cancelation and removed the UUID from the + // waiter list. + delete_ok = false; + } + } + + // Allow any pending reads or writes to complete + if (mCacheReadHandle != LLTextureCache::nullHandle()) + { + if (mFetcher->mTextureCache->readComplete(mCacheReadHandle, true)) + { + mCacheReadHandle = LLTextureCache::nullHandle(); + } + else + { + delete_ok = false; + } + } + if (mCacheWriteHandle != LLTextureCache::nullHandle()) + { + if (mFetcher->mTextureCache->writeComplete(mCacheWriteHandle)) + { + mCacheWriteHandle = LLTextureCache::nullHandle(); + } + else + { + delete_ok = false; + } + } + + if ((haveWork() && + // not ok to delete from these states + ((mState >= WRITE_TO_CACHE && mState <= WAIT_ON_WRITE)))) + { + delete_ok = false; + } + + return delete_ok; +} + +// Threads: Ttf +void LLTextureFetchWorker::removeFromCache() +{ + if (!mInLocalCache) + { + mFetcher->mTextureCache->removeFromCache(mID); + } +} + + +////////////////////////////////////////////////////////////////////////////// + +// Threads: Ttf +// Locks: Mw +S32 LLTextureFetchWorker::callbackHttpGet(LLCore::HttpResponse * response, + bool partial, bool success) +{ + S32 data_size = 0 ; + + if (mState != WAIT_HTTP_REQ) + { + LL_WARNS(LOG_TXT) << "callbackHttpGet for unrequested fetch worker: " << mID + << " req=" << mSentRequest << " state= " << mState << LL_ENDL; + return data_size; + } + if (mLoaded) + { + LL_WARNS(LOG_TXT) << "Duplicate callback for " << mID.asString() << LL_ENDL; + return data_size ; // ignore duplicate callback + } + if (success) + { + // get length of stream: + LLCore::BufferArray * body(response->getBody()); + data_size = body ? body->size() : 0; + + LL_DEBUGS(LOG_TXT) << "HTTP RECEIVED: " << mID.asString() << " Bytes: " << data_size << LL_ENDL; + if (data_size > 0) + { + // *TODO: set the formatted image data here directly to avoid the copy + + // Hold on to body for later copy + llassert_always(NULL == mHttpBufferArray); + body->addRef(); + mHttpBufferArray = body; + + if (partial) + { + unsigned int offset(0), length(0), full_length(0); + response->getRange(&offset, &length, &full_length); + if (! offset && ! length) + { + // This is the case where we receive a 206 status but + // there wasn't a useful Content-Range header in the response. + // This could be because it was badly formatted but is more + // likely due to capabilities services which scrub headers + // from responses. Assume we got what we asked for... + mHttpReplySize = data_size; + mHttpReplyOffset = mRequestedOffset; + } + else + { + mHttpReplySize = length; + mHttpReplyOffset = offset; + } + } + + if (! partial) + { + // Response indicates this is the entire asset regardless + // of our asking for a byte range. Mark it so and drop + // any partial data we might have so that the current + // response body becomes the entire dataset. + if (data_size <= mRequestedOffset) + { + LL_WARNS(LOG_TXT) << "Fetched entire texture " << mID + << " when it was expected to be marked complete. mImageSize: " + << mFileSize << " datasize: " << mFormattedImage->getDataSize() + << LL_ENDL; + } + mHaveAllData = true; + llassert_always(mDecodeHandle == 0); + mFormattedImage = NULL; // discard any previous data we had + } + else if (data_size < mRequestedSize) + { + mHaveAllData = true; + } + else if (data_size > mRequestedSize) + { + // *TODO: This shouldn't be happening any more (REALLY don't expect this anymore) + LL_WARNS(LOG_TXT) << "data_size = " << data_size << " > requested: " << mRequestedSize << LL_ENDL; + mHaveAllData = true; + llassert_always(mDecodeHandle == 0); + mFormattedImage = NULL; // discard any previous data we had + } + } + else + { + // We requested data but received none (and no error), + // so presumably we have all of it + mHaveAllData = true; + } + mRequestedSize = data_size; + + if (mHaveAllData) + { + LLViewerStatsRecorder::instance().textureFetch(); + } + + // *TODO: set the formatted image data here directly to avoid the copy + } + else + { + mRequestedSize = -1; // error + } + + mLoaded = true; + + return data_size ; +} + +////////////////////////////////////////////////////////////////////////////// + +// Threads: Ttc +void LLTextureFetchWorker::callbackCacheRead(bool success, LLImageFormatted* image, + S32 imagesize, bool islocal) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + LLMutexLock lock(&mWorkMutex); // +Mw + if (mState != LOAD_FROM_TEXTURE_CACHE) + { +// LL_WARNS(LOG_TXT) << "Read callback for " << mID << " with state = " << mState << LL_ENDL; + return; + } + if (success) + { + llassert_always(imagesize >= 0); + mFileSize = imagesize; + mFormattedImage = image; + mImageCodec = image->getCodec(); + mInLocalCache = islocal; + if (mFileSize != 0 && mFormattedImage->getDataSize() >= mFileSize) + { + mHaveAllData = true; + } + } + mLoaded = true; +} // -Mw + +// Threads: Ttc +void LLTextureFetchWorker::callbackCacheWrite(bool success) +{ + LLMutexLock lock(&mWorkMutex); // +Mw + if (mState != WAIT_ON_WRITE) + { +// LL_WARNS(LOG_TXT) << "Write callback for " << mID << " with state = " << mState << LL_ENDL; + return; + } + mWritten = true; +} // -Mw + +////////////////////////////////////////////////////////////////////////////// + +// Threads: Tid +void LLTextureFetchWorker::callbackDecoded(bool success, const std::string &error_message, LLImageRaw* raw, LLImageRaw* aux, S32 decode_id) +{ + LLMutexLock lock(&mWorkMutex); // +Mw + if (mDecodeHandle == 0) + { + return; // aborted, ignore + } + if (mDecodeHandle != decode_id) + { + // Queue doesn't support canceling old requests. + // This shouldn't normally happen, but in case it's possible that a worked + // will request decode, be aborted, reinited then start a new decode + LL_DEBUGS(LOG_TXT) << mID << " received obsolete decode's callback" << LL_ENDL; + return; // ignore + } + if (mState != DECODE_IMAGE_UPDATE) + { + LL_DEBUGS(LOG_TXT) << "Decode callback for " << mID << " with state = " << mState << LL_ENDL; + mDecodeHandle = 0; + return; + } + llassert_always(mFormattedImage.notNull()); + + mDecodeHandle = 0; + if (success) + { + llassert_always(raw); + mRawImage = raw; + mAuxImage = aux; + mDecodedDiscard = mFormattedImage->getDiscardLevel(); + LL_DEBUGS(LOG_TXT) << mID << ": Decode Finished. Discard: " << mDecodedDiscard + << " Raw Image: " << llformat("%dx%d",mRawImage->getWidth(),mRawImage->getHeight()) << LL_ENDL; + } + else + { + LL_WARNS(LOG_TXT) << "DECODE FAILED: " << mID << " Discard: " << (S32)mFormattedImage->getDiscardLevel() << ", reason: " << error_message << LL_ENDL; + removeFromCache(); + mDecodedDiscard = -1; // Redundant, here for clarity and paranoia + } + mDecoded = true; +// LL_INFOS(LOG_TXT) << mID << " : DECODE COMPLETE " << LL_ENDL; +} // -Mw + +////////////////////////////////////////////////////////////////////////////// + +// Threads: Ttf +bool LLTextureFetchWorker::writeToCacheComplete() +{ + // Complete write to cache + if (mCacheWriteHandle != LLTextureCache::nullHandle()) + { + if (!mWritten) + { + return false; + } + if (mFetcher->mTextureCache->writeComplete(mCacheWriteHandle)) + { + mCacheWriteHandle = LLTextureCache::nullHandle(); + } + else + { + return false; + } + } + return true; +} + + +// Threads: Ttf +void LLTextureFetchWorker::recordTextureStart(bool is_http) +{ + if (! mMetricsStartTime.value()) + { + mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp(); + } + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_TEXTURE, + is_http, + LLImageBase::TYPE_AVATAR_BAKE == mType); +} + + +// Threads: Ttf +void LLTextureFetchWorker::recordTextureDone(bool is_http, F64 byte_count) +{ + if (mMetricsStartTime.value()) + { + LLViewerAssetStatsFF::record_response(LLViewerAssetType::AT_TEXTURE, + is_http, + LLImageBase::TYPE_AVATAR_BAKE == mType, + LLViewerAssetStatsFF::get_timestamp() - mMetricsStartTime, + byte_count); + mMetricsStartTime = (U32Seconds)0; + } + LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_TEXTURE, + is_http, + LLImageBase::TYPE_AVATAR_BAKE == mType); +} + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +// public + +std::string LLTextureFetch::getStateString(S32 state) +{ + if (state < 0 || state > sizeof(e_state_name) / sizeof(char*)) + { + return llformat("%d", state); + } + + return e_state_name[state]; +} + +LLTextureFetch::LLTextureFetch(LLTextureCache* cache, bool threaded, bool qa_mode) + : LLWorkerThread("TextureFetch", threaded, true), + mDebugCount(0), + mDebugPause(false), + mPacketCount(0), + mBadPacketCount(0), + mQueueMutex(), + mNetworkQueueMutex(), + mTextureCache(cache), + mTextureBandwidth(0), + mHTTPTextureBits(0), + mTotalHTTPRequests(0), + mQAMode(qa_mode), + mHttpRequest(NULL), + mHttpOptions(), + mHttpOptionsWithHeaders(), + mHttpHeaders(), + mHttpPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), + mHttpMetricsHeaders(), + mHttpMetricsPolicyClass(LLCore::HttpRequest::DEFAULT_POLICY_ID), + mTotalCacheReadCount(0U), + mTotalCacheWriteCount(0U), + mTotalResourceWaitCount(0U), + mFetchSource(LLTextureFetch::FROM_ALL), + mOriginFetchSource(LLTextureFetch::FROM_ALL), + mTextureInfoMainThread(false) +{ + mMaxBandwidth = gSavedSettings.getF32("ThrottleBandwidthKBPS"); + mTextureInfo.setLogging(true); + + LLAppCoreHttp & app_core_http(LLAppViewer::instance()->getAppCoreHttp()); + mHttpRequest = new LLCore::HttpRequest; + mHttpOptions = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); + mHttpOptionsWithHeaders = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); + mHttpOptionsWithHeaders->setWantHeaders(true); + mHttpHeaders = LLCore::HttpHeaders::ptr_t(new LLCore::HttpHeaders); + mHttpHeaders->append(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_IMAGE_X_J2C); + mHttpPolicyClass = app_core_http.getPolicy(LLAppCoreHttp::AP_TEXTURE); + mHttpMetricsHeaders = LLCore::HttpHeaders::ptr_t(new LLCore::HttpHeaders); + mHttpMetricsHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_LLSD_XML); + mHttpMetricsPolicyClass = app_core_http.getPolicy(LLAppCoreHttp::AP_REPORTING); + mHttpHighWater = HTTP_NONPIPE_REQUESTS_HIGH_WATER; + mHttpLowWater = HTTP_NONPIPE_REQUESTS_LOW_WATER; + mHttpSemaphore = 0; + + // If that test log has ben requested but not yet created, create it + if (LLMetricPerformanceTesterBasic::isMetricLogRequested(sTesterName) && !LLMetricPerformanceTesterBasic::getTester(sTesterName)) + { + sTesterp = new LLTextureFetchTester() ; + if (!sTesterp->isValid()) + { + delete sTesterp; + sTesterp = NULL; + } + } +} + +LLTextureFetch::~LLTextureFetch() +{ + clearDeleteList(); + + while (! mCommands.empty()) + { + TFRequest * req(mCommands.front()); + mCommands.erase(mCommands.begin()); + delete req; + } + + mHttpWaitResource.clear(); + + delete mHttpRequest; + mHttpRequest = NULL; + + // ~LLQueuedThread() called here +} + +S32 LLTextureFetch::createRequest(FTType f_type, const std::string& url, const LLUUID& id, const LLHost& host, F32 priority, + S32 w, S32 h, S32 c, S32 desired_discard, bool needs_aux, bool can_use_http) +{ + LL_PROFILE_ZONE_SCOPED; + if (mDebugPause) + { + return -1; + } + + if (f_type == FTT_SERVER_BAKE) + { + LL_DEBUGS("Avatar") << " requesting " << id << " " << w << "x" << h << " discard " << desired_discard << " type " << f_type << LL_ENDL; + } + LLTextureFetchWorker* worker = getWorker(id) ; + if (worker) + { + if (worker->mHost != host) + { + LL_WARNS(LOG_TXT) << "LLTextureFetch::createRequest " << id << " called with multiple hosts: " + << host << " != " << worker->mHost << LL_ENDL; + removeRequest(worker, true); + worker = NULL; + return -1; + } + } + + S32 desired_size; + std::string exten = gDirUtilp->getExtension(url); + //if (f_type == FTT_SERVER_BAKE) + if ((f_type == FTT_SERVER_BAKE) && !url.empty() && !exten.empty() && (LLImageBase::getCodecFromExtension(exten) != IMG_CODEC_J2C)) + { + // SH-4030: This case should be redundant with the following one, just + // breaking it out here to clarify that it's intended behavior. + llassert(!url.empty() && (!exten.empty() && LLImageBase::getCodecFromExtension(exten) != IMG_CODEC_J2C)); + + // Do full requests for baked textures to reduce interim blurring. + LL_DEBUGS(LOG_TXT) << "full request for " << id << " texture is FTT_SERVER_BAKE" << LL_ENDL; + desired_size = MAX_IMAGE_DATA_SIZE; + desired_discard = 0; + } + else if (!url.empty() && (!exten.empty() && LLImageBase::getCodecFromExtension(exten) != IMG_CODEC_J2C)) + { + LL_DEBUGS(LOG_TXT) << "full request for " << id << " exten is not J2C: " << exten << LL_ENDL; + // Only do partial requests for J2C at the moment + desired_size = MAX_IMAGE_DATA_SIZE; + desired_discard = 0; + } + else if (desired_discard == 0) + { + // if we want the entire image, and we know its size, then get it all + // (calcDataSizeJ2C() below makes assumptions about how the image + // was compressed - this code ensures that when we request the entire image, + // we really do get it.) + desired_size = MAX_IMAGE_DATA_SIZE; + } + else if (w*h*c > 0) + { + // If the requester knows the dimensions of the image, + // this will calculate how much data we need without having to parse the header + + desired_size = LLImageJ2C::calcDataSizeJ2C(w, h, c, desired_discard); + } + else + { + // If the requester knows nothing about the file, we fetch the smallest + // amount of data at the lowest resolution (highest discard level) possible. + desired_size = TEXTURE_CACHE_ENTRY_SIZE; + desired_discard = MAX_DISCARD_LEVEL; + } + + + if (worker) + { + if (worker->wasAborted()) + { + return -1; // need to wait for previous aborted request to complete + } + worker->lockWorkMutex(); // +Mw + if (worker->mState == LLTextureFetchWorker::DONE && worker->mDesiredSize == llmax(desired_size, TEXTURE_CACHE_ENTRY_SIZE) && worker->mDesiredDiscard == desired_discard) { + worker->unlockWorkMutex(); // -Mw + + return -1; // similar request has failed or is in a transitional state + } + worker->mActiveCount++; + worker->mNeedsAux = needs_aux; + worker->setImagePriority(priority); + worker->setDesiredDiscard(desired_discard, desired_size); + worker->setCanUseHTTP(can_use_http); + + //MAINT-4184 url is always empty. Do not set with it. + + if (!worker->haveWork()) + { + worker->setState(LLTextureFetchWorker::INIT); + worker->unlockWorkMutex(); // -Mw + + worker->addWork(0); + } + else + { + worker->unlockWorkMutex(); // -Mw + } + } + else + { + worker = new LLTextureFetchWorker(this, f_type, url, id, host, priority, desired_discard, desired_size); + lockQueue(); // +Mfq + mRequestMap[id] = worker; + unlockQueue(); // -Mfq + + worker->lockWorkMutex(); // +Mw + worker->mActiveCount++; + worker->mNeedsAux = needs_aux; + worker->setCanUseHTTP(can_use_http) ; + worker->unlockWorkMutex(); // -Mw + } + + LL_DEBUGS(LOG_TXT) << "REQUESTED: " << id << " f_type " << fttype_to_string(f_type) + << " Discard: " << desired_discard << " size " << desired_size << LL_ENDL; + return desired_discard; +} +// Threads: T* +// +// protected +void LLTextureFetch::addToHTTPQueue(const LLUUID& id) +{ + LL_PROFILE_ZONE_SCOPED; + LLMutexLock lock(&mNetworkQueueMutex); // +Mfnq + mHTTPTextureQueue.insert(id); + mTotalHTTPRequests++; +} // -Mfnq + +// Threads: T* +void LLTextureFetch::removeFromHTTPQueue(const LLUUID& id, S32Bytes received_size) +{ + LL_PROFILE_ZONE_SCOPED; + LLMutexLock lock(&mNetworkQueueMutex); // +Mfnq + mHTTPTextureQueue.erase(id); + mHTTPTextureBits += received_size; // Approximate - does not include header bits +} // -Mfnq + +// NB: If you change deleteRequest() you should probably make +// parallel changes in removeRequest(). They're functionally +// identical with only argument variations. +// +// Threads: T* +void LLTextureFetch::deleteRequest(const LLUUID& id, bool cancel) +{ + LL_PROFILE_ZONE_SCOPED; + lockQueue(); // +Mfq + LLTextureFetchWorker* worker = getWorkerAfterLock(id); + if (worker) + { + size_t erased_1 = mRequestMap.erase(worker->mID); + unlockQueue(); // -Mfq + + llassert_always(erased_1 > 0) ; + llassert_always(!(worker->getFlags(LLWorkerClass::WCF_DELETE_REQUESTED))) ; + + worker->scheduleDelete(); + } + else + { + unlockQueue(); // -Mfq + } +} + +// NB: If you change removeRequest() you should probably make +// parallel changes in deleteRequest(). They're functionally +// identical with only argument variations. +// +// Threads: T* +void LLTextureFetch::removeRequest(LLTextureFetchWorker* worker, bool cancel) +{ + LL_PROFILE_ZONE_SCOPED; + if(!worker) + { + return; + } + + lockQueue(); // +Mfq + size_t erased_1 = mRequestMap.erase(worker->mID); + unlockQueue(); // -Mfq + + llassert_always(erased_1 > 0) ; + llassert_always(!(worker->getFlags(LLWorkerClass::WCF_DELETE_REQUESTED))) ; + + worker->scheduleDelete(); +} + +void LLTextureFetch::deleteAllRequests() +{ + while(1) + { + lockQueue(); + if(mRequestMap.empty()) + { + unlockQueue() ; + break; + } + + LLTextureFetchWorker* worker = mRequestMap.begin()->second; + unlockQueue() ; + + removeRequest(worker, true); + } +} + +// Threads: T* +S32 LLTextureFetch::getNumRequests() +{ + lockQueue(); // +Mfq + S32 size = (S32)mRequestMap.size(); + unlockQueue(); // -Mfq + + return size; +} + +// Threads: T* +S32 LLTextureFetch::getNumHTTPRequests() +{ + mNetworkQueueMutex.lock(); // +Mfq + S32 size = (S32)mHTTPTextureQueue.size(); + mNetworkQueueMutex.unlock(); // -Mfq + + return size; +} + +// Threads: T* +U32 LLTextureFetch::getTotalNumHTTPRequests() +{ + mNetworkQueueMutex.lock(); // +Mfq + U32 size = mTotalHTTPRequests; + mNetworkQueueMutex.unlock(); // -Mfq + + return size; +} + +// call lockQueue() first! +// Threads: T* +// Locks: Mfq +LLTextureFetchWorker* LLTextureFetch::getWorkerAfterLock(const LLUUID& id) +{ + LL_PROFILE_ZONE_SCOPED; + LLTextureFetchWorker* res = NULL; + map_t::iterator iter = mRequestMap.find(id); + if (iter != mRequestMap.end()) + { + res = iter->second; + } + return res; +} + +// Threads: T* +LLTextureFetchWorker* LLTextureFetch::getWorker(const LLUUID& id) +{ + LLMutexLock lock(&mQueueMutex); // +Mfq + + return getWorkerAfterLock(id); +} // -Mfq + + +// Threads: T* +bool LLTextureFetch::getRequestFinished(const LLUUID& id, S32& discard_level, + LLPointer& raw, LLPointer& aux, + LLCore::HttpStatus& last_http_get_status) +{ + LL_PROFILE_ZONE_SCOPED; + bool res = false; + LLTextureFetchWorker* worker = getWorker(id); + if (worker) + { + if (worker->wasAborted()) + { + res = true; + } + else if (!worker->haveWork()) + { + // Should only happen if we set mDebugPause... + if (!mDebugPause) + { +// LL_WARNS(LOG_TXT) << "Adding work for inactive worker: " << id << LL_ENDL; + worker->addWork(0); + } + } + else if (worker->checkWork()) + { + F32 decode_time; + F32 fetch_time; + F32 cache_read_time; + F32 cache_write_time; + S32 file_size; + std::map logged_state_timers; + F32 skipped_states_time; + worker->lockWorkMutex(); // +Mw + last_http_get_status = worker->mGetStatus; + discard_level = worker->mDecodedDiscard; + raw = worker->mRawImage; + aux = worker->mAuxImage; + + decode_time = worker->mDecodeTime; + fetch_time = worker->mFetchTime; + cache_read_time = worker->mCacheReadTime; + cache_write_time = worker->mCacheWriteTime; + file_size = worker->mFileSize; + worker->mCacheReadTimer.reset(); + worker->mDecodeTimer.reset(); + worker->mCacheWriteTimer.reset(); + worker->mFetchTimer.reset(); + logged_state_timers = worker->mStateTimersMap; + skipped_states_time = worker->mSkippedStatesTime; + worker->mStateTimer.reset(); + res = true; + LL_DEBUGS(LOG_TXT) << id << ": Request Finished. State: " << worker->mState << " Discard: " << discard_level << LL_ENDL; + worker->unlockWorkMutex(); // -Mw + + sample(sTexDecodeLatency, decode_time); + sample(sTexFetchLatency, fetch_time); + sample(sCacheReadLatency, cache_read_time); + sample(sCacheWriteLatency, cache_write_time); + + static LLCachedControl min_time_to_log(gSavedSettings, "TextureFetchMinTimeToLog", 2.f); + if (fetch_time > min_time_to_log) + { + //LL_INFOS() << "fetch_time: " << fetch_time << " cache_read_time: " << cache_read_time << " decode_time: " << decode_time << " cache_write_time: " << cache_write_time << LL_ENDL; + + LLTextureFetchTester* tester = (LLTextureFetchTester*)LLMetricPerformanceTesterBasic::getTester(sTesterName); + if (tester) + { + tester->updateStats(logged_state_timers, fetch_time, skipped_states_time, file_size) ; + } + } + } + else + { + worker->lockWorkMutex(); // +Mw + if ((worker->mDecodedDiscard >= 0) && + (worker->mDecodedDiscard < discard_level || discard_level < 0) && + (worker->mState >= LLTextureFetchWorker::WAIT_ON_WRITE)) + { + // Not finished, but data is ready + discard_level = worker->mDecodedDiscard; + raw = worker->mRawImage; + aux = worker->mAuxImage; + } + worker->unlockWorkMutex(); // -Mw + } + } + else + { + res = true; + } + return res; +} + +// Threads: T* +bool LLTextureFetch::updateRequestPriority(const LLUUID& id, F32 priority) +{ + LL_PROFILE_ZONE_SCOPED; + mRequestQueue.tryPost([=]() + { + LLTextureFetchWorker* worker = getWorker(id); + if (worker) + { + worker->lockWorkMutex(); // +Mw + worker->setImagePriority(priority); + worker->unlockWorkMutex(); // -Mw + } + }); + + return true; +} + +// Replicates and expands upon the base class's +// getPending() implementation. getPending() and +// runCondition() replicate one another's logic to +// an extent and are sometimes used for the same +// function (deciding whether or not to sleep/pause +// a thread). So the implementations need to stay +// in step, at least until this can be refactored and +// the redundancy eliminated. +// +// Threads: T* + +//virtual +size_t LLTextureFetch::getPending() +{ + LL_PROFILE_ZONE_SCOPED; + size_t res; + lockData(); // +Ct + { + LLMutexLock lock(&mQueueMutex); // +Mfq + + res = mRequestQueue.size(); + res += mCommands.size(); + } // -Mfq + unlockData(); // -Ct + return res; +} + +// Locks: Ct +// virtual +bool LLTextureFetch::runCondition() +{ + // Caller is holding the lock on LLThread's condition variable. + + // LLQueuedThread, unlike its base class LLThread, makes this a + // private method which is unfortunate. I want to use it directly + // but I'm going to have to re-implement the logic here (or change + // declarations, which I don't want to do right now). + // + // Changes here may need to be reflected in getPending(). + + bool have_no_commands(false); + { + LLMutexLock lock(&mQueueMutex); // +Mfq + + have_no_commands = mCommands.empty(); + } // -Mfq + + return ! (have_no_commands + && (mRequestQueue.size() == 0 && mIdleThread)); // From base class +} + +////////////////////////////////////////////////////////////////////////////// + +// Threads: Ttf +void LLTextureFetch::commonUpdate() +{ + LL_PROFILE_ZONE_SCOPED; + // Update low/high water levels based on pipelining. We pick + // up setting eventually, so the semaphore/request level can + // fall outside the [0..HIGH_WATER] range. Expect that. + if (LLAppViewer::instance()->getAppCoreHttp().isPipelined(LLAppCoreHttp::AP_TEXTURE)) + { + mHttpHighWater = HTTP_PIPE_REQUESTS_HIGH_WATER; + mHttpLowWater = HTTP_PIPE_REQUESTS_LOW_WATER; + } + else + { + mHttpHighWater = HTTP_NONPIPE_REQUESTS_HIGH_WATER; + mHttpLowWater = HTTP_NONPIPE_REQUESTS_LOW_WATER; + } + + // Release waiters + releaseHttpWaiters(); + + // Run a cross-thread command, if any. + cmdDoWork(); + + // Deliver all completion notifications + LLCore::HttpStatus status = mHttpRequest->update(0); + if (! status) + { + LL_INFOS_ONCE(LOG_TXT) << "Problem during HTTP servicing. Reason: " + << status.toString() + << LL_ENDL; + } +} + + +// Threads: Tmain + +//virtual +size_t LLTextureFetch::update(F32 max_time_ms) +{ + LL_PROFILE_ZONE_SCOPED; + static LLCachedControl band_width(gSavedSettings,"ThrottleBandwidthKBPS", 3000.0); + + { + mNetworkQueueMutex.lock(); // +Mfnq + mMaxBandwidth = band_width(); + + add(LLStatViewer::TEXTURE_NETWORK_DATA_RECEIVED, mHTTPTextureBits); + mHTTPTextureBits = (U32Bits)0; + + mNetworkQueueMutex.unlock(); // -Mfnq + } + + size_t res = LLWorkerThread::update(max_time_ms); + + if (!mThreaded) + { + commonUpdate(); + } + + return res; +} + +// called in the MAIN thread after the TextureCacheThread shuts down. +// +// Threads: Tmain +void LLTextureFetch::shutDownTextureCacheThread() +{ + if(mTextureCache) + { + llassert_always(mTextureCache->isQuitting() || mTextureCache->isStopped()) ; + mTextureCache = NULL ; + } +} + +// Threads: Ttf +void LLTextureFetch::startThread() +{ + mTextureInfo.startRecording(); +} + +// Threads: Ttf +void LLTextureFetch::endThread() +{ + LL_INFOS(LOG_TXT) << "CacheReads: " << mTotalCacheReadCount + << ", CacheWrites: " << mTotalCacheWriteCount + << ", ResWaits: " << mTotalResourceWaitCount + << ", TotalHTTPReq: " << getTotalNumHTTPRequests() + << LL_ENDL; + + mTextureInfo.stopRecording(); +} + +// Threads: Ttf +void LLTextureFetch::threadedUpdate() +{ + LL_PROFILE_ZONE_SCOPED; + llassert_always(mHttpRequest); + +#if 0 + // Limit update frequency + const F32 PROCESS_TIME = 0.05f; + static LLFrameTimer process_timer; + if (process_timer.getElapsedTimeF32() < PROCESS_TIME) + { + return; + } + process_timer.reset(); +#endif + + commonUpdate(); + +#if 0 + const F32 INFO_TIME = 1.0f; + static LLFrameTimer info_timer; + if (info_timer.getElapsedTimeF32() >= INFO_TIME) + { + S32 q = mCurlGetRequest->getQueued(); + if (q > 0) + { + LL_INFOS(LOG_TXT) << "Queued gets: " << q << LL_ENDL; + info_timer.reset(); + } + } +#endif +} + +////////////////////////////////////////////////////////////////////////////// + +// Threads: T* +// Locks: Mw +bool LLTextureFetchWorker::insertPacket(S32 index, U8* data, S32 size) +{ + LL_PROFILE_ZONE_SCOPED; + mRequestedDeltaTimer.reset(); + if (index >= mTotalPackets) + { +// LL_WARNS(LOG_TXT) << "Received Image Packet " << index << " > max: " << mTotalPackets << " for image: " << mID << LL_ENDL; + return false; + } + if (index > 0 && index < mTotalPackets-1 && size != MAX_IMG_PACKET_SIZE) + { +// LL_WARNS(LOG_TXT) << "Received bad sized packet: " << index << ", " << size << " != " << MAX_IMG_PACKET_SIZE << " for image: " << mID << LL_ENDL; + return false; + } + + if (index >= (S32)mPackets.size()) + { + mPackets.resize(index+1, (PacketData*)NULL); // initializes v to NULL pointers + } + else if (mPackets[index] != NULL) + { +// LL_WARNS(LOG_TXT) << "Received duplicate packet: " << index << " for image: " << mID << LL_ENDL; + return false; + } + + mPackets[index] = new PacketData(data, size); + while (mLastPacket+1 < (S32)mPackets.size() && mPackets[mLastPacket+1] != NULL) + { + ++mLastPacket; + } + return true; +} + +void LLTextureFetchWorker::setState(e_state new_state) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if (mFTType == FTT_SERVER_BAKE) + { + // NOTE: turning on these log statements is a reliable way to get + // blurry images fairly frequently. Presumably this is an + // indication of some subtle timing or locking issue. + +// LL_INFOS(LOG_TXT) << "id: " << mID << " FTType: " << mFTType << " disc: " << mDesiredDiscard << " sz: " << mDesiredSize << " state: " << e_state_name[mState] << " => " << e_state_name[new_state] << LL_ENDL; + } + + F32 d_time = mStateTimer.getElapsedTimeF32(); + if (d_time >= 0.0001F) + { + if (LOGGED_STATES.count(mState)) + { + mStateTimersMap[mState] = d_time; + } + else + { + mSkippedStatesTime += d_time; + } + } + + mStateTimer.reset(); + mState = new_state; +} + +LLViewerRegion* LLTextureFetchWorker::getRegion() +{ + LLViewerRegion* region = NULL; + if (mHost.isInvalid()) + { + region = gAgent.getRegion(); + } + else if (LLWorld::instanceExists()) + { + region = LLWorld::getInstance()->getRegion(mHost); + } + return region; +} + +////////////////////////////////////////////////////////////////////////////// + +// Threads: T* +bool LLTextureFetch::isFromLocalCache(const LLUUID& id) +{ + bool from_cache = false ; + + LLTextureFetchWorker* worker = getWorker(id); + if (worker) + { + worker->lockWorkMutex(); // +Mw + from_cache = worker->mInLocalCache; + worker->unlockWorkMutex(); // -Mw + } + + return from_cache ; +} + +S32 LLTextureFetch::getFetchState(const LLUUID& id) +{ + S32 state = LLTextureFetchWorker::INVALID; + LLTextureFetchWorker* worker = getWorker(id); + if (worker && worker->haveWork()) + { + state = worker->mState; + } + + return state; +} + +// Threads: T* +S32 LLTextureFetch::getFetchState(const LLUUID& id, F32& data_progress_p, F32& requested_priority_p, + U32& fetch_priority_p, F32& fetch_dtime_p, F32& request_dtime_p, bool& can_use_http) +{ + LL_PROFILE_ZONE_SCOPED; + S32 state = LLTextureFetchWorker::INVALID; + F32 data_progress = 0.0f; + F32 requested_priority = 0.0f; + F32 fetch_dtime = 999999.f; + F32 request_dtime = 999999.f; + U32 fetch_priority = 0; + + LLTextureFetchWorker* worker = getWorker(id); + if (worker && worker->haveWork()) + { + worker->lockWorkMutex(); // +Mw + state = worker->mState; + fetch_dtime = worker->mFetchDeltaTimer.getElapsedTimeF32(); + request_dtime = worker->mRequestedDeltaTimer.getElapsedTimeF32(); + if (worker->mFileSize > 0) + { + if (worker->mFormattedImage.notNull()) + { + data_progress = (F32)worker->mFormattedImage->getDataSize() / (F32)worker->mFileSize; + } + } + if (state >= LLTextureFetchWorker::LOAD_FROM_NETWORK && state <= LLTextureFetchWorker::WAIT_HTTP_REQ) + { + requested_priority = worker->mRequestedPriority; + } + else + { + requested_priority = worker->mImagePriority; + } + fetch_priority = worker->getImagePriority(); + can_use_http = worker->getCanUseHTTP() ; + worker->unlockWorkMutex(); // -Mw + } + data_progress_p = data_progress; + requested_priority_p = requested_priority; + fetch_priority_p = fetch_priority; + fetch_dtime_p = fetch_dtime; + request_dtime_p = request_dtime; + return state; +} + +void LLTextureFetch::dump() +{ + LL_INFOS(LOG_TXT) << "LLTextureFetch ACTIVE_HTTP:" << LL_ENDL; + for (queue_t::const_iterator iter(mHTTPTextureQueue.begin()); + mHTTPTextureQueue.end() != iter; + ++iter) + { + LL_INFOS(LOG_TXT) << " ID: " << (*iter) << LL_ENDL; + } + + LL_INFOS(LOG_TXT) << "LLTextureFetch WAIT_HTTP_RESOURCE:" << LL_ENDL; + for (wait_http_res_queue_t::const_iterator iter(mHttpWaitResource.begin()); + mHttpWaitResource.end() != iter; + ++iter) + { + LL_INFOS(LOG_TXT) << " ID: " << (*iter) << LL_ENDL; + } +} + +////////////////////////////////////////////////////////////////////////////// + +// HTTP Resource Waiting Methods + +// Threads: Ttf +void LLTextureFetch::addHttpWaiter(const LLUUID & tid) +{ + mNetworkQueueMutex.lock(); // +Mfnq + mHttpWaitResource.insert(tid); + mNetworkQueueMutex.unlock(); // -Mfnq +} + +// Threads: Ttf +void LLTextureFetch::removeHttpWaiter(const LLUUID & tid) +{ + mNetworkQueueMutex.lock(); // +Mfnq + wait_http_res_queue_t::iterator iter(mHttpWaitResource.find(tid)); + if (mHttpWaitResource.end() != iter) + { + mHttpWaitResource.erase(iter); + } + mNetworkQueueMutex.unlock(); // -Mfnq +} + +// Threads: T* +bool LLTextureFetch::isHttpWaiter(const LLUUID & tid) +{ + mNetworkQueueMutex.lock(); // +Mfnq + wait_http_res_queue_t::iterator iter(mHttpWaitResource.find(tid)); + const bool ret(mHttpWaitResource.end() != iter); + mNetworkQueueMutex.unlock(); // -Mfnq + return ret; +} + +// Release as many requests as permitted from the WAIT_HTTP_RESOURCE2 +// state to the SEND_HTTP_REQ state based on their current priority. +// +// This data structures and code associated with this looks a bit +// indirect and naive but it's done in the name of safety. An +// ordered container may become invalid from time to time due to +// priority changes caused by actions in other threads. State itself +// could also suffer the same fate with canceled operations. Even +// done this way, I'm not fully trusting we're truly safe. This +// module is due for a major refactoring and we'll deal with it then. +// +// Threads: Ttf +// Locks: -Mw (must not hold any worker when called) +void LLTextureFetch::releaseHttpWaiters() +{ + LL_PROFILE_ZONE_SCOPED; + // Use mHttpSemaphore rather than mHTTPTextureQueue.size() + // to avoid a lock. + if (mHttpSemaphore >= mHttpLowWater) + return; + S32 needed(mHttpHighWater - mHttpSemaphore); + if (needed <= 0) + { + // Would only happen if High/LowWater were changed behind + // our back. In that case, defer fill until usage falls within + // limits. + return; + } + + // Quickly make a copy of all the LLUIDs. Get off the + // mutex as early as possible. + typedef std::vector uuid_vec_t; + uuid_vec_t tids; + + { + LLMutexLock lock(&mNetworkQueueMutex); // +Mfnq + + if (mHttpWaitResource.empty()) + return; + tids.reserve(mHttpWaitResource.size()); + tids.assign(mHttpWaitResource.begin(), mHttpWaitResource.end()); + } // -Mfnq + + // Now lookup the UUUIDs to find valid requests and sort + // them in priority order, highest to lowest. We're going + // to modify priority later as a side-effect of releasing + // these objects. That, in turn, would violate the partial + // ordering assumption of std::set, std::map, etc. so we + // don't use those containers. We use a vector and an explicit + // sort to keep the containers valid later. + typedef std::vector worker_list_t; + worker_list_t tids2; + + tids2.reserve(tids.size()); + for (uuid_vec_t::iterator iter(tids.begin()); + tids.end() != iter; + ++iter) + { + LLTextureFetchWorker * worker(getWorker(* iter)); + if (worker) + { + tids2.push_back(worker); + } + else + { + // If worker isn't found, this should be due to a request + // for deletion. We signal our recognition that this + // uuid shouldn't be used for resource waiting anymore by + // erasing it from the resource waiter list. That allows + // deleteOK to do final deletion on the worker. + removeHttpWaiter(* iter); + } + } + tids.clear(); + + // Sort into priority order, if necessary and only as much as needed + if (tids2.size() > needed) + { + LLTextureFetchWorker::Compare compare; + std::partial_sort(tids2.begin(), tids2.begin() + needed, tids2.end(), compare); + } + + // Release workers up to the high water mark. Since we aren't + // holding any locks at this point, we can be in competition + // with other callers. Do defensive things like getting + // refreshed counts of requests and checking if someone else + // has moved any worker state around.... + for (worker_list_t::iterator iter2(tids2.begin()); tids2.end() != iter2; ++iter2) + { + LLTextureFetchWorker * worker(* iter2); + + worker->lockWorkMutex(); // +Mw + if (LLTextureFetchWorker::WAIT_HTTP_RESOURCE2 != worker->mState) + { + // Not in expected state, remove it, try the next one + worker->unlockWorkMutex(); // -Mw + LL_WARNS(LOG_TXT) << "Resource-waited texture " << worker->mID + << " in unexpected state: " << worker->mState + << ". Removing from wait list." + << LL_ENDL; + removeHttpWaiter(worker->mID); + continue; + } + + if (! worker->acquireHttpSemaphore()) + { + // Out of active slots, quit + worker->unlockWorkMutex(); // -Mw + break; + } + + worker->setState(LLTextureFetchWorker::SEND_HTTP_REQ); + worker->unlockWorkMutex(); // -Mw + + removeHttpWaiter(worker->mID); + } +} + +// Threads: T* +void LLTextureFetch::cancelHttpWaiters() +{ + mNetworkQueueMutex.lock(); // +Mfnq + mHttpWaitResource.clear(); + mNetworkQueueMutex.unlock(); // -Mfnq +} + +// Threads: T* +int LLTextureFetch::getHttpWaitersCount() +{ + mNetworkQueueMutex.lock(); // +Mfnq + int ret(mHttpWaitResource.size()); + mNetworkQueueMutex.unlock(); // -Mfnq + return ret; +} + + +// Threads: T* +void LLTextureFetch::updateStateStats(U32 cache_read, U32 cache_write, U32 res_wait) +{ + LLMutexLock lock(&mQueueMutex); // +Mfq + + mTotalCacheReadCount += cache_read; + mTotalCacheWriteCount += cache_write; + mTotalResourceWaitCount += res_wait; +} // -Mfq + + +// Threads: T* +void LLTextureFetch::getStateStats(U32 * cache_read, U32 * cache_write, U32 * res_wait) +{ + U32 ret1(0U), ret2(0U), ret3(0U); + + { + LLMutexLock lock(&mQueueMutex); // +Mfq + ret1 = mTotalCacheReadCount; + ret2 = mTotalCacheWriteCount; + ret3 = mTotalResourceWaitCount; + } // -Mfq + + *cache_read = ret1; + *cache_write = ret2; + *res_wait = ret3; +} + +////////////////////////////////////////////////////////////////////////////// + +// cross-thread command methods + +// Threads: T* +void LLTextureFetch::commandSetRegion(U64 region_handle) +{ + TFReqSetRegion * req = new TFReqSetRegion(region_handle); + + cmdEnqueue(req); +} + +// Threads: T* +void LLTextureFetch::commandSendMetrics(const std::string & caps_url, + const LLUUID & session_id, + const LLUUID & agent_id, + LLSD& stats_sd) +{ + TFReqSendMetrics * req = new TFReqSendMetrics(caps_url, session_id, agent_id, stats_sd); + + cmdEnqueue(req); +} + +// Threads: T* +void LLTextureFetch::commandDataBreak() +{ + // The pedantically correct way to implement this is to create a command + // request object in the above fashion and enqueue it. However, this is + // simple data of an advisorial not operational nature and this case + // of shared-write access is tolerable. + + LLTextureFetch::svMetricsDataBreak = true; +} + +// Threads: T* +void LLTextureFetch::cmdEnqueue(TFRequest * req) +{ + LL_PROFILE_ZONE_SCOPED; + lockQueue(); // +Mfq + mCommands.push_back(req); + unlockQueue(); // -Mfq + + unpause(); +} + +// Threads: T* +LLTextureFetch::TFRequest * LLTextureFetch::cmdDequeue() +{ + LL_PROFILE_ZONE_SCOPED; + TFRequest * ret = 0; + + lockQueue(); // +Mfq + if (! mCommands.empty()) + { + ret = mCommands.front(); + mCommands.erase(mCommands.begin()); + } + unlockQueue(); // -Mfq + + return ret; +} + +// Threads: Ttf +void LLTextureFetch::cmdDoWork() +{ + LL_PROFILE_ZONE_SCOPED; + if (mDebugPause) + { + return; // debug: don't do any work + } + + TFRequest * req = cmdDequeue(); + if (req) + { + // One request per pass should really be enough for this. + req->doWork(this); + delete req; + } +} + +////////////////////////////////////////////////////////////////////////////// + +// Private (anonymous) class methods implementing the command scheme. + +namespace +{ + + +// Example of a simple notification handler for metrics +// delivery notification. Earlier versions of the code used +// a Responder that tried harder to detect delivery breaks +// but it really isn't that important. If someone wants to +// revisit that effort, here is a place to start. +class AssetReportHandler : public LLCore::HttpHandler +{ +public: + + // Threads: Ttf + virtual void onCompleted(LLCore::HttpHandle handle, LLCore::HttpResponse * response) + { + LLCore::HttpStatus status(response->getStatus()); + + if (status) + { + LL_DEBUGS(LOG_TXT) << "Successfully delivered asset metrics to grid." + << LL_ENDL; + } + else + { + LL_WARNS(LOG_TXT) << "Error delivering asset metrics to grid. Status: " + << status.toTerseString() + << ", Reason: " << status.toString() << LL_ENDL; + } + } +}; // end class AssetReportHandler + +/** + * Implements the 'Set Region' command. + * + * Thread: Thread1 (TextureFetch) + */ +bool +TFReqSetRegion::doWork(LLTextureFetch *) +{ + LLViewerAssetStatsFF::set_region(mRegionHandle); + + return true; +} + +TFReqSendMetrics::TFReqSendMetrics(const std::string & caps_url, + const LLUUID & session_id, + const LLUUID & agent_id, + LLSD& stats_sd): + LLTextureFetch::TFRequest(), + mCapsURL(caps_url), + mSessionID(session_id), + mAgentID(agent_id), + mStatsSD(stats_sd), + mHandler(new AssetReportHandler) +{} + + +TFReqSendMetrics::~TFReqSendMetrics() +{ +} + + +/** + * Implements the 'Send Metrics' command. Takes over + * ownership of the passed LLViewerAssetStats pointer. + * + * Thread: Thread1 (TextureFetch) + */ +bool +TFReqSendMetrics::doWork(LLTextureFetch * fetcher) +{ + LL_PROFILE_ZONE_SCOPED; + + //if (! gViewerAssetStatsThread1) + // return true; + + static volatile bool reporting_started(false); + static volatile S32 report_sequence(0); + + // In mStatsSD, we have a copy we own of the LLSD representation + // of the asset stats. Add some additional fields and ship it off. + + static const S32 metrics_data_version = 2; + + bool initial_report = !reporting_started; + mStatsSD["session_id"] = mSessionID; + mStatsSD["agent_id"] = mAgentID; + mStatsSD["message"] = "ViewerAssetMetrics"; + mStatsSD["sequence"] = report_sequence; + mStatsSD["initial"] = initial_report; + mStatsSD["version"] = metrics_data_version; + mStatsSD["break"] = static_cast(LLTextureFetch::svMetricsDataBreak); + + // Update sequence number + if (S32_MAX == ++report_sequence) + { + report_sequence = 0; + } + reporting_started = true; + + // Limit the size of the stats report if necessary. + + mStatsSD["truncated"] = truncate_viewer_metrics(10, mStatsSD); + + if (gSavedSettings.getBOOL("QAModeMetrics")) + { + dump_sequential_xml("metric_asset_stats",mStatsSD); + } + + if (! mCapsURL.empty()) + { + // Don't care about handle, this is a fire-and-forget operation. + LLCoreHttpUtil::requestPostWithLLSD(&fetcher->getHttpRequest(), + fetcher->getMetricsPolicyClass(), + mCapsURL, + mStatsSD, + LLCore::HttpOptions::ptr_t(), + fetcher->getMetricsHeaders(), + mHandler); + LLTextureFetch::svMetricsDataBreak = false; + } + else + { + LLTextureFetch::svMetricsDataBreak = true; + } + + // In QA mode, Metrics submode, log the result for ease of testing + if (fetcher->isQAMode()) + { + LL_INFOS(LOG_TXT) << "ViewerAssetMetrics as submitted\n" << ll_pretty_print_sd(mStatsSD) << LL_ENDL; + } + + return true; +} + + +bool +truncate_viewer_metrics(int max_regions, LLSD & metrics) +{ + static const LLSD::String reg_tag("regions"); + static const LLSD::String duration_tag("duration"); + + LLSD & reg_map(metrics[reg_tag]); + if (reg_map.size() <= max_regions) + { + return false; + } + + // Build map of region hashes ordered by duration + typedef std::multimap reg_ordered_list_t; + reg_ordered_list_t regions_by_duration; + + int ind(0); + LLSD::array_const_iterator it_end(reg_map.endArray()); + for (LLSD::array_const_iterator it(reg_map.beginArray()); it_end != it; ++it, ++ind) + { + LLSD::Real duration = (*it)[duration_tag].asReal(); + regions_by_duration.insert(reg_ordered_list_t::value_type(duration, ind)); + } + + // Build a replacement regions array with the longest-persistence regions + LLSD new_region(LLSD::emptyArray()); + reg_ordered_list_t::const_reverse_iterator it2_end(regions_by_duration.rend()); + reg_ordered_list_t::const_reverse_iterator it2(regions_by_duration.rbegin()); + for (int i(0); i < max_regions && it2_end != it2; ++i, ++it2) + { + new_region.append(reg_map[it2->second]); + } + reg_map = new_region; + + return true; +} + +} // end of anonymous namespace + +LLTextureFetchTester::LLTextureFetchTester() : LLMetricPerformanceTesterBasic(sTesterName) +{ + mTextureFetchTime = 0; + mSkippedStatesTime = 0; + mFileSize = 0; +} + +LLTextureFetchTester::~LLTextureFetchTester() +{ + outputTestResults(); + LLTextureFetch::sTesterp = NULL; +} + +//virtual +void LLTextureFetchTester::outputTestRecord(LLSD *sd) +{ + std::string currentLabel = getCurrentLabelName(); + + (*sd)[currentLabel]["Texture Fetch Time"] = (LLSD::Real)mTextureFetchTime; + (*sd)[currentLabel]["File Size"] = (LLSD::Integer)mFileSize; + (*sd)[currentLabel]["Skipped States Time"] = (LLSD::String)llformat("%.6f", mSkippedStatesTime); + + for(auto i : LOGGED_STATES) + { + (*sd)[currentLabel][sStateDescs[i]] = mStateTimersMap[i]; + } +} + +void LLTextureFetchTester::updateStats(const std::map state_timers, const F32 fetch_time, const F32 skipped_states_time, const S32 file_size) +{ + mTextureFetchTime = fetch_time; + mStateTimersMap = state_timers; + mFileSize = file_size; + mSkippedStatesTime = skipped_states_time; + outputTestResults(); +} + diff --git a/indra/newview/lltexturefetch.h b/indra/newview/lltexturefetch.h index 9d653173b5..aebd2f8f95 100644 --- a/indra/newview/lltexturefetch.h +++ b/indra/newview/lltexturefetch.h @@ -1,431 +1,431 @@ -/** - * @file lltexturefetch.h - * @brief Object for managing texture fetches. - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012-2013, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLTEXTUREFETCH_H -#define LL_LLTEXTUREFETCH_H - -#include -#include - -#include "lldir.h" -#include "llimage.h" -#include "lluuid.h" -#include "llworkerthread.h" -#include "lltextureinfo.h" -#include "llimageworker.h" -#include "httprequest.h" -#include "httpoptions.h" -#include "httpheaders.h" -#include "httphandler.h" -#include "lltrace.h" -#include "llviewertexture.h" - -class LLViewerTexture; -class LLTextureFetchWorker; -class LLImageDecodeThread; -class LLHost; -class LLViewerAssetStats; -class LLTextureCache; -class LLTextureFetchTester; - -// Interface class - -class LLTextureFetch : public LLWorkerThread -{ - friend class LLTextureFetchWorker; - -public: - static std::string getStateString(S32 state); - - LLTextureFetch(LLTextureCache* cache, bool threaded, bool qa_mode); - ~LLTextureFetch(); - - class TFRequest; - - // Threads: Tmain - /*virtual*/ size_t update(F32 max_time_ms); - - // called in the main thread after the TextureCacheThread shuts down. - // Threads: Tmain - void shutDownTextureCacheThread(); - - //called in the main thread after the ImageDecodeThread shuts down. - // Threads: Tmain - void shutDownImageDecodeThread(); - - // Threads: T* (but Tmain mostly) - S32 createRequest(FTType f_type, const std::string& url, const LLUUID& id, const LLHost& host, F32 priority, - S32 w, S32 h, S32 c, S32 discard, bool needs_aux, bool can_use_http); - - // Requests that a fetch operation be deleted from the queue. - // If @cancel is true, also stops any I/O operations pending. - // Actual delete will be scheduled and performed later. - // - // Note: This *looks* like an override/variant of the - // base class's deleteRequest() but is functionally quite - // different. - // - // Threads: T* - void deleteRequest(const LLUUID& id, bool cancel); - - void deleteAllRequests(); - - // Threads: T* - // keep in mind that if fetcher isn't done, it still might need original raw image - bool getRequestFinished(const LLUUID& id, S32& discard_level, - LLPointer& raw, LLPointer& aux, - LLCore::HttpStatus& last_http_get_status); - - // Threads: T* - bool updateRequestPriority(const LLUUID& id, F32 priority); - - // Threads: T* (but not safe) - void setTextureBandwidth(F32 bandwidth) { mTextureBandwidth = bandwidth; } - - // Threads: T* (but not safe) - F32 getTextureBandwidth() { return mTextureBandwidth; } - - // Threads: T* - bool isFromLocalCache(const LLUUID& id); - - // get the current fetch state, if any, from the given UUID - S32 getFetchState(const LLUUID& id); - - // @return Fetch state of given image and associates statistics - // See also getStateString - // Threads: T* - S32 getFetchState(const LLUUID& id, F32& decode_progress_p, F32& requested_priority_p, - U32& fetch_priority_p, F32& fetch_dtime_p, F32& request_dtime_p, bool& can_use_http); - - // Debug utility - generally not safe - void dump(); - - // Threads: T* - S32 getNumRequests(); - - // Threads: T* - S32 getNumHTTPRequests(); - - // Threads: T* - U32 getTotalNumHTTPRequests(); - - // Threads: T* - size_t getPending(); - - // Threads: T* - void lockQueue() { mQueueMutex.lock(); } - - // Threads: T* - void unlockQueue() { mQueueMutex.unlock(); } - - // Threads: T* - LLTextureFetchWorker* getWorker(const LLUUID& id); - - // Threads: T* - // Locks: Mfq - LLTextureFetchWorker* getWorkerAfterLock(const LLUUID& id); - - // Commands available to other threads to control metrics gathering operations. - - // Threads: T* - void commandSetRegion(U64 region_handle); - - // Threads: T* - void commandSendMetrics(const std::string & caps_url, - const LLUUID & session_id, - const LLUUID & agent_id, - LLSD& stats_sd); - - // Threads: T* - void commandDataBreak(); - - // Threads: T* - LLCore::HttpRequest & getHttpRequest() { return *mHttpRequest; } - - // Threads: T* - LLCore::HttpRequest::policy_t getPolicyClass() const { return mHttpPolicyClass; } - - // Return a pointer to the shared metrics headers definition. - // Does not increment the reference count, caller is required - // to do that to hold a reference for any length of time. - // - // Threads: T* - LLCore::HttpHeaders::ptr_t getMetricsHeaders() const { return mHttpMetricsHeaders; } - - // Threads: T* - LLCore::HttpRequest::policy_t getMetricsPolicyClass() const { return mHttpMetricsPolicyClass; } - - bool isQAMode() const { return mQAMode; } - - // ---------------------------------- - // HTTP resource waiting methods - - // Threads: T* - void addHttpWaiter(const LLUUID & tid); - - // Threads: T* - void removeHttpWaiter(const LLUUID & tid); - - // Threads: T* - bool isHttpWaiter(const LLUUID & tid); - - // If there are slots, release one or more LLTextureFetchWorker - // requests from resource wait state (WAIT_HTTP_RESOURCE) to - // active (SEND_HTTP_REQ). - // - // Because this will modify state of many workers, you may not - // hold any Mw lock while calling. This makes it a little - // inconvenient to use but that's the rule. - // - // Threads: T* - // Locks: -Mw (must not hold any worker when called) - void releaseHttpWaiters(); - - // Threads: T* - void cancelHttpWaiters(); - - // Threads: T* - int getHttpWaitersCount(); - // ---------------------------------- - // Stats management - - // Add given counts to the global totals for the states/requests - // Threads: T* - void updateStateStats(U32 cache_read, U32 cache_write, U32 res_wait); - - // Return the global counts - // Threads: T* - void getStateStats(U32 * cache_read, U32 * cache_write, U32 * res_wait); - - // ---------------------------------- - -protected: - // Threads: T* - void addToHTTPQueue(const LLUUID& id); - - // XXX possible delete - // Threads: T* - void removeFromHTTPQueue(const LLUUID& id, S32Bytes received_size); - - // Identical to @deleteRequest but with different arguments - // (caller already has the worker pointer). - // - // Threads: T* - void removeRequest(LLTextureFetchWorker* worker, bool cancel); - - // Overrides from the LLThread tree - // Locks: Ct - bool runCondition(); - -private: - // Threads: Ttf - /*virtual*/ void startThread(void); - - // Threads: Ttf - /*virtual*/ void endThread(void); - - // Threads: Ttf - /*virtual*/ void threadedUpdate(void); - - // Threads: Ttf - void commonUpdate(); - - // Metrics command helpers - /** - * Enqueues a command request at the end of the command queue - * and wakes up the thread as needed. - * - * Takes ownership of the TFRequest object. - * - * Method locks the command queue. - * - * Threads: T* - */ - void cmdEnqueue(TFRequest *); - - /** - * Returns the first TFRequest object in the command queue or - * NULL if none is present. - * - * Caller acquires ownership of the object and must dispose of it. - * - * Method locks the command queue. - * - * Threads: T* - */ - TFRequest * cmdDequeue(); - - /** - * Processes the first command in the queue disposing of the - * request on completion. Successive calls are needed to perform - * additional commands. - * - * Method locks the command queue. - * - * Threads: Ttf - */ - void cmdDoWork(); - -public: - LLUUID mDebugID; - S32 mDebugCount; - bool mDebugPause; - S32 mPacketCount; - S32 mBadPacketCount; - - static LLTrace::CountStatHandle sCacheHit; - static LLTrace::CountStatHandle sCacheAttempt; - static LLTrace::SampleStatHandle sCacheReadLatency; - static LLTrace::SampleStatHandle sTexDecodeLatency; - static LLTrace::SampleStatHandle sCacheWriteLatency; - static LLTrace::SampleStatHandle sTexFetchLatency; - static LLTrace::EventStatHandle > sCacheHitRate; - -private: - LLMutex mQueueMutex; //to protect mRequestMap and mCommands only - LLMutex mNetworkQueueMutex; //to protect mHTTPTextureQueue - - LLTextureCache* mTextureCache; - - // Map of all requests by UUID - typedef std::map map_t; - map_t mRequestMap; // Mfq - - // Set of requests that require network data - typedef std::set queue_t; - queue_t mHTTPTextureQueue; // Mfnq - typedef std::map > cancel_queue_t; - F32 mTextureBandwidth; // - F32 mMaxBandwidth; // Mfnq - LLTextureInfo mTextureInfo; - LLTextureInfo mTextureInfoMainThread; - - // XXX possible delete - U32Bits mHTTPTextureBits; // Mfnq - - // XXX possible delete - //debug use - U32 mTotalHTTPRequests; - - // Out-of-band cross-thread command queue. This command queue - // is logically tied to LLQueuedThread's list of - // QueuedRequest instances and so must be covered by the - // same locks. - typedef std::vector command_queue_t; - command_queue_t mCommands; // Mfq - - // If true, modifies some behaviors that help with QA tasks. - const bool mQAMode; - - // Interfaces and objects into the core http library used - // to make our HTTP requests. These replace the various - // LLCurl interfaces used in the past. - LLCore::HttpRequest * mHttpRequest; // Ttf - LLCore::HttpOptions::ptr_t mHttpOptions; // Ttf - LLCore::HttpOptions::ptr_t mHttpOptionsWithHeaders; // Ttf - LLCore::HttpHeaders::ptr_t mHttpHeaders; // Ttf - LLCore::HttpRequest::policy_t mHttpPolicyClass; // T* - LLCore::HttpHeaders::ptr_t mHttpMetricsHeaders; // Ttf - LLCore::HttpRequest::policy_t mHttpMetricsPolicyClass; // T* - S32 mHttpHighWater; // Ttf - S32 mHttpLowWater; // Ttf - - // We use a resource semaphore to keep HTTP requests in - // WAIT_HTTP_RESOURCE2 if there aren't sufficient slots in the - // transport. This keeps them near where they can be cheaply - // reprioritized rather than dumping them all across a thread - // where it's more expensive to get at them. Requests in either - // SEND_HTTP_REQ or WAIT_HTTP_REQ charge against the semaphore - // and tracking state transitions is critical to liveness. - // - // Originally implemented as a traditional semaphore (heading towards - // zero), it now is an outstanding request count that is allowed to - // exceed the high water level (but not go below zero). - LLAtomicS32 mHttpSemaphore; // Ttf - - typedef std::set wait_http_res_queue_t; - wait_http_res_queue_t mHttpWaitResource; // Mfnq - - // Cumulative stats on the states/requests issued by - // textures running through here. - U32 mTotalCacheReadCount; // Mfq - U32 mTotalCacheWriteCount; // Mfq - U32 mTotalResourceWaitCount; // Mfq - -public: - // A probabilistically-correct indicator that the current - // attempt to log metrics follows a break in the metrics stream - // reporting due to either startup or a problem POSTing data. - static volatile bool svMetricsDataBreak; - -public: - //debug use - enum e_tex_source - { - FROM_ALL = 0, - FROM_HTTP_ONLY, - INVALID_SOURCE - }; - - static LLTextureFetchTester* sTesterp; - -private: - e_tex_source mFetchSource; - e_tex_source mOriginFetchSource; - - // Retry logic - //LLAdaptiveRetryPolicy mFetchRetryPolicy; - -public: - void setLoadSource(e_tex_source source) {mFetchSource = source;} - void resetLoadSource() {mFetchSource = mOriginFetchSource;} - bool canLoadFromCache() { return mFetchSource != FROM_HTTP_ONLY;} -}; - -//debug use -class LLViewerFetchedTexture; - -class LLTextureFetchTester : public LLMetricPerformanceTesterBasic -{ -public: - LLTextureFetchTester(); - ~LLTextureFetchTester(); - - void updateStats(const std::map states_timers, const F32 fetch_time, const F32 other_states_time, const S32 file_size); - -protected: - /*virtual*/ void outputTestRecord(LLSD* sd); - -private: - - F32 mTextureFetchTime; - F32 mSkippedStatesTime; - S32 mFileSize; - - std::map mStateTimersMap; -}; -#endif // LL_LLTEXTUREFETCH_H - +/** + * @file lltexturefetch.h + * @brief Object for managing texture fetches. + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012-2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLTEXTUREFETCH_H +#define LL_LLTEXTUREFETCH_H + +#include +#include + +#include "lldir.h" +#include "llimage.h" +#include "lluuid.h" +#include "llworkerthread.h" +#include "lltextureinfo.h" +#include "llimageworker.h" +#include "httprequest.h" +#include "httpoptions.h" +#include "httpheaders.h" +#include "httphandler.h" +#include "lltrace.h" +#include "llviewertexture.h" + +class LLViewerTexture; +class LLTextureFetchWorker; +class LLImageDecodeThread; +class LLHost; +class LLViewerAssetStats; +class LLTextureCache; +class LLTextureFetchTester; + +// Interface class + +class LLTextureFetch : public LLWorkerThread +{ + friend class LLTextureFetchWorker; + +public: + static std::string getStateString(S32 state); + + LLTextureFetch(LLTextureCache* cache, bool threaded, bool qa_mode); + ~LLTextureFetch(); + + class TFRequest; + + // Threads: Tmain + /*virtual*/ size_t update(F32 max_time_ms); + + // called in the main thread after the TextureCacheThread shuts down. + // Threads: Tmain + void shutDownTextureCacheThread(); + + //called in the main thread after the ImageDecodeThread shuts down. + // Threads: Tmain + void shutDownImageDecodeThread(); + + // Threads: T* (but Tmain mostly) + S32 createRequest(FTType f_type, const std::string& url, const LLUUID& id, const LLHost& host, F32 priority, + S32 w, S32 h, S32 c, S32 discard, bool needs_aux, bool can_use_http); + + // Requests that a fetch operation be deleted from the queue. + // If @cancel is true, also stops any I/O operations pending. + // Actual delete will be scheduled and performed later. + // + // Note: This *looks* like an override/variant of the + // base class's deleteRequest() but is functionally quite + // different. + // + // Threads: T* + void deleteRequest(const LLUUID& id, bool cancel); + + void deleteAllRequests(); + + // Threads: T* + // keep in mind that if fetcher isn't done, it still might need original raw image + bool getRequestFinished(const LLUUID& id, S32& discard_level, + LLPointer& raw, LLPointer& aux, + LLCore::HttpStatus& last_http_get_status); + + // Threads: T* + bool updateRequestPriority(const LLUUID& id, F32 priority); + + // Threads: T* (but not safe) + void setTextureBandwidth(F32 bandwidth) { mTextureBandwidth = bandwidth; } + + // Threads: T* (but not safe) + F32 getTextureBandwidth() { return mTextureBandwidth; } + + // Threads: T* + bool isFromLocalCache(const LLUUID& id); + + // get the current fetch state, if any, from the given UUID + S32 getFetchState(const LLUUID& id); + + // @return Fetch state of given image and associates statistics + // See also getStateString + // Threads: T* + S32 getFetchState(const LLUUID& id, F32& decode_progress_p, F32& requested_priority_p, + U32& fetch_priority_p, F32& fetch_dtime_p, F32& request_dtime_p, bool& can_use_http); + + // Debug utility - generally not safe + void dump(); + + // Threads: T* + S32 getNumRequests(); + + // Threads: T* + S32 getNumHTTPRequests(); + + // Threads: T* + U32 getTotalNumHTTPRequests(); + + // Threads: T* + size_t getPending(); + + // Threads: T* + void lockQueue() { mQueueMutex.lock(); } + + // Threads: T* + void unlockQueue() { mQueueMutex.unlock(); } + + // Threads: T* + LLTextureFetchWorker* getWorker(const LLUUID& id); + + // Threads: T* + // Locks: Mfq + LLTextureFetchWorker* getWorkerAfterLock(const LLUUID& id); + + // Commands available to other threads to control metrics gathering operations. + + // Threads: T* + void commandSetRegion(U64 region_handle); + + // Threads: T* + void commandSendMetrics(const std::string & caps_url, + const LLUUID & session_id, + const LLUUID & agent_id, + LLSD& stats_sd); + + // Threads: T* + void commandDataBreak(); + + // Threads: T* + LLCore::HttpRequest & getHttpRequest() { return *mHttpRequest; } + + // Threads: T* + LLCore::HttpRequest::policy_t getPolicyClass() const { return mHttpPolicyClass; } + + // Return a pointer to the shared metrics headers definition. + // Does not increment the reference count, caller is required + // to do that to hold a reference for any length of time. + // + // Threads: T* + LLCore::HttpHeaders::ptr_t getMetricsHeaders() const { return mHttpMetricsHeaders; } + + // Threads: T* + LLCore::HttpRequest::policy_t getMetricsPolicyClass() const { return mHttpMetricsPolicyClass; } + + bool isQAMode() const { return mQAMode; } + + // ---------------------------------- + // HTTP resource waiting methods + + // Threads: T* + void addHttpWaiter(const LLUUID & tid); + + // Threads: T* + void removeHttpWaiter(const LLUUID & tid); + + // Threads: T* + bool isHttpWaiter(const LLUUID & tid); + + // If there are slots, release one or more LLTextureFetchWorker + // requests from resource wait state (WAIT_HTTP_RESOURCE) to + // active (SEND_HTTP_REQ). + // + // Because this will modify state of many workers, you may not + // hold any Mw lock while calling. This makes it a little + // inconvenient to use but that's the rule. + // + // Threads: T* + // Locks: -Mw (must not hold any worker when called) + void releaseHttpWaiters(); + + // Threads: T* + void cancelHttpWaiters(); + + // Threads: T* + int getHttpWaitersCount(); + // ---------------------------------- + // Stats management + + // Add given counts to the global totals for the states/requests + // Threads: T* + void updateStateStats(U32 cache_read, U32 cache_write, U32 res_wait); + + // Return the global counts + // Threads: T* + void getStateStats(U32 * cache_read, U32 * cache_write, U32 * res_wait); + + // ---------------------------------- + +protected: + // Threads: T* + void addToHTTPQueue(const LLUUID& id); + + // XXX possible delete + // Threads: T* + void removeFromHTTPQueue(const LLUUID& id, S32Bytes received_size); + + // Identical to @deleteRequest but with different arguments + // (caller already has the worker pointer). + // + // Threads: T* + void removeRequest(LLTextureFetchWorker* worker, bool cancel); + + // Overrides from the LLThread tree + // Locks: Ct + bool runCondition(); + +private: + // Threads: Ttf + /*virtual*/ void startThread(void); + + // Threads: Ttf + /*virtual*/ void endThread(void); + + // Threads: Ttf + /*virtual*/ void threadedUpdate(void); + + // Threads: Ttf + void commonUpdate(); + + // Metrics command helpers + /** + * Enqueues a command request at the end of the command queue + * and wakes up the thread as needed. + * + * Takes ownership of the TFRequest object. + * + * Method locks the command queue. + * + * Threads: T* + */ + void cmdEnqueue(TFRequest *); + + /** + * Returns the first TFRequest object in the command queue or + * NULL if none is present. + * + * Caller acquires ownership of the object and must dispose of it. + * + * Method locks the command queue. + * + * Threads: T* + */ + TFRequest * cmdDequeue(); + + /** + * Processes the first command in the queue disposing of the + * request on completion. Successive calls are needed to perform + * additional commands. + * + * Method locks the command queue. + * + * Threads: Ttf + */ + void cmdDoWork(); + +public: + LLUUID mDebugID; + S32 mDebugCount; + bool mDebugPause; + S32 mPacketCount; + S32 mBadPacketCount; + + static LLTrace::CountStatHandle sCacheHit; + static LLTrace::CountStatHandle sCacheAttempt; + static LLTrace::SampleStatHandle sCacheReadLatency; + static LLTrace::SampleStatHandle sTexDecodeLatency; + static LLTrace::SampleStatHandle sCacheWriteLatency; + static LLTrace::SampleStatHandle sTexFetchLatency; + static LLTrace::EventStatHandle > sCacheHitRate; + +private: + LLMutex mQueueMutex; //to protect mRequestMap and mCommands only + LLMutex mNetworkQueueMutex; //to protect mHTTPTextureQueue + + LLTextureCache* mTextureCache; + + // Map of all requests by UUID + typedef std::map map_t; + map_t mRequestMap; // Mfq + + // Set of requests that require network data + typedef std::set queue_t; + queue_t mHTTPTextureQueue; // Mfnq + typedef std::map > cancel_queue_t; + F32 mTextureBandwidth; // + F32 mMaxBandwidth; // Mfnq + LLTextureInfo mTextureInfo; + LLTextureInfo mTextureInfoMainThread; + + // XXX possible delete + U32Bits mHTTPTextureBits; // Mfnq + + // XXX possible delete + //debug use + U32 mTotalHTTPRequests; + + // Out-of-band cross-thread command queue. This command queue + // is logically tied to LLQueuedThread's list of + // QueuedRequest instances and so must be covered by the + // same locks. + typedef std::vector command_queue_t; + command_queue_t mCommands; // Mfq + + // If true, modifies some behaviors that help with QA tasks. + const bool mQAMode; + + // Interfaces and objects into the core http library used + // to make our HTTP requests. These replace the various + // LLCurl interfaces used in the past. + LLCore::HttpRequest * mHttpRequest; // Ttf + LLCore::HttpOptions::ptr_t mHttpOptions; // Ttf + LLCore::HttpOptions::ptr_t mHttpOptionsWithHeaders; // Ttf + LLCore::HttpHeaders::ptr_t mHttpHeaders; // Ttf + LLCore::HttpRequest::policy_t mHttpPolicyClass; // T* + LLCore::HttpHeaders::ptr_t mHttpMetricsHeaders; // Ttf + LLCore::HttpRequest::policy_t mHttpMetricsPolicyClass; // T* + S32 mHttpHighWater; // Ttf + S32 mHttpLowWater; // Ttf + + // We use a resource semaphore to keep HTTP requests in + // WAIT_HTTP_RESOURCE2 if there aren't sufficient slots in the + // transport. This keeps them near where they can be cheaply + // reprioritized rather than dumping them all across a thread + // where it's more expensive to get at them. Requests in either + // SEND_HTTP_REQ or WAIT_HTTP_REQ charge against the semaphore + // and tracking state transitions is critical to liveness. + // + // Originally implemented as a traditional semaphore (heading towards + // zero), it now is an outstanding request count that is allowed to + // exceed the high water level (but not go below zero). + LLAtomicS32 mHttpSemaphore; // Ttf + + typedef std::set wait_http_res_queue_t; + wait_http_res_queue_t mHttpWaitResource; // Mfnq + + // Cumulative stats on the states/requests issued by + // textures running through here. + U32 mTotalCacheReadCount; // Mfq + U32 mTotalCacheWriteCount; // Mfq + U32 mTotalResourceWaitCount; // Mfq + +public: + // A probabilistically-correct indicator that the current + // attempt to log metrics follows a break in the metrics stream + // reporting due to either startup or a problem POSTing data. + static volatile bool svMetricsDataBreak; + +public: + //debug use + enum e_tex_source + { + FROM_ALL = 0, + FROM_HTTP_ONLY, + INVALID_SOURCE + }; + + static LLTextureFetchTester* sTesterp; + +private: + e_tex_source mFetchSource; + e_tex_source mOriginFetchSource; + + // Retry logic + //LLAdaptiveRetryPolicy mFetchRetryPolicy; + +public: + void setLoadSource(e_tex_source source) {mFetchSource = source;} + void resetLoadSource() {mFetchSource = mOriginFetchSource;} + bool canLoadFromCache() { return mFetchSource != FROM_HTTP_ONLY;} +}; + +//debug use +class LLViewerFetchedTexture; + +class LLTextureFetchTester : public LLMetricPerformanceTesterBasic +{ +public: + LLTextureFetchTester(); + ~LLTextureFetchTester(); + + void updateStats(const std::map states_timers, const F32 fetch_time, const F32 other_states_time, const S32 file_size); + +protected: + /*virtual*/ void outputTestRecord(LLSD* sd); + +private: + + F32 mTextureFetchTime; + F32 mSkippedStatesTime; + S32 mFileSize; + + std::map mStateTimersMap; +}; +#endif // LL_LLTEXTUREFETCH_H + diff --git a/indra/newview/lltextureview.cpp b/indra/newview/lltextureview.cpp index a7da404105..f521293b96 100644 --- a/indra/newview/lltextureview.cpp +++ b/indra/newview/lltextureview.cpp @@ -1,1029 +1,1029 @@ -/** - * @file lltextureview.cpp - * @brief LLTextureView class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012-2013, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include - -#include "lltextureview.h" - -#include "llrect.h" -#include "llerror.h" -#include "lllfsthread.h" -#include "llui.h" -#include "llimageworker.h" -#include "llrender.h" - -#include "lltooltip.h" -#include "llappviewer.h" -#include "llmeshrepository.h" -#include "llselectmgr.h" -#include "llviewertexlayer.h" -#include "lltexturecache.h" -#include "lltexturefetch.h" -#include "llviewercontrol.h" -#include "llviewerobject.h" -#include "llviewerobjectlist.h" -#include "llviewertexture.h" -#include "llviewertexturelist.h" -#include "llviewerwindow.h" -#include "llwindow.h" -#include "llvovolume.h" -#include "llviewerstats.h" -#include "llworld.h" - -// For avatar texture view -#include "llvoavatarself.h" -#include "lltexlayer.h" - -extern F32 texmem_lower_bound_scale; - -LLTextureView *gTextureView = NULL; - -#define HIGH_PRIORITY 100000000.f - -//static -std::set LLTextureView::sDebugImages; - -//////////////////////////////////////////////////////////////////////////// - -static std::string title_string1a("Tex UUID Area DDis(Req) DecodePri(Fetch) [download] pk/max"); -static std::string title_string1b("Tex UUID Area DDis(Req) Fetch(DecodePri) [download] pk/max"); -static std::string title_string2("State"); -static std::string title_string3("Pkt Bnd"); -static std::string title_string4(" W x H (Dis) Mem"); - -static S32 title_x1 = 0; -static S32 title_x2 = 460; -static S32 title_x3 = title_x2 + 40; -static S32 title_x4 = title_x3 + 46; -static S32 texture_bar_height = 8; - -//////////////////////////////////////////////////////////////////////////// - -class LLTextureBar : public LLView -{ -public: - LLPointer mImagep; - S32 mHilite; - -public: - struct Params : public LLInitParam::Block - { - Mandatory texture_view; - Params() - : texture_view("texture_view") - { - changeDefault(mouse_opaque, false); - } - }; - LLTextureBar(const Params& p) - : LLView(p), - mHilite(0), - mTextureView(p.texture_view) - {} - - virtual void draw(); - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual LLRect getRequiredRect(); // Return the height of this object, given the set options. - -// Used for sorting - struct sort - { - bool operator()(const LLView* i1, const LLView* i2) - { - LLTextureBar* bar1p = (LLTextureBar*)i1; - LLTextureBar* bar2p = (LLTextureBar*)i2; - LLViewerFetchedTexture *i1p = bar1p->mImagep; - LLViewerFetchedTexture *i2p = bar2p->mImagep; - F32 pri1 = i1p->getMaxVirtualSize(); - F32 pri2 = i2p->getMaxVirtualSize(); - if (pri1 > pri2) - return true; - else if (pri2 > pri1) - return false; - else - return i1p->getID() < i2p->getID(); - } - }; - - struct sort_fetch - { - bool operator()(const LLView* i1, const LLView* i2) - { - LLTextureBar* bar1p = (LLTextureBar*)i1; - LLTextureBar* bar2p = (LLTextureBar*)i2; - LLViewerFetchedTexture *i1p = bar1p->mImagep; - LLViewerFetchedTexture *i2p = bar2p->mImagep; - U32 pri1 = i1p->getFetchPriority() ; - U32 pri2 = i2p->getFetchPriority() ; - if (pri1 > pri2) - return true; - else if (pri2 > pri1) - return false; - else - return i1p->getID() < i2p->getID(); - } - }; -private: - LLTextureView* mTextureView; -}; - -void LLTextureBar::draw() -{ - if (!mImagep) - { - return; - } - - LLColor4 color; - if (mImagep->getID() == LLAppViewer::getTextureFetch()->mDebugID) - { - color = LLColor4::cyan2; - } - else if (mHilite) - { - S32 idx = llclamp(mHilite,1,3); - if (idx==1) color = LLColor4::orange; - else if (idx==2) color = LLColor4::yellow; - else color = LLColor4::pink2; - } - else if (mImagep->mDontDiscard) - { - color = LLColor4::green4; - } - else if (mImagep->getMaxVirtualSize() <= 0.0f) - { - color = LLColor4::grey; color[VALPHA] = .7f; - } - else - { - color = LLColor4::white; color[VALPHA] = .7f; - } - - // We need to draw: - // The texture UUID or name - // The progress bar for the texture, highlighted if it's being download - // Various numerical stats. - std::string tex_str; - S32 left, right; - S32 top = 0; - S32 bottom = top + 6; - LLColor4 clr; - - LLGLSUIDefault gls_ui; - - // Name, pixel_area, requested pixel area, decode priority - std::string uuid_str; - mImagep->mID.toString(uuid_str); - uuid_str = uuid_str.substr(0,7); - - tex_str = llformat("%s %7.0f %d(%d)", - uuid_str.c_str(), - mImagep->mMaxVirtualSize, - mImagep->mDesiredDiscardLevel, - mImagep->mRequestedDiscardLevel); - - - LLFontGL::getFontMonospace()->renderUTF8(tex_str, 0, title_x1, getRect().getHeight(), - color, LLFontGL::LEFT, LLFontGL::TOP); - - // State - // Hack: mirrored from lltexturefetch.cpp - struct { const std::string desc; LLColor4 color; } fetch_state_desc[] = { - { "---", LLColor4::red }, // INVALID - { "INI", LLColor4::white }, // INIT - { "DSK", LLColor4::cyan }, // LOAD_FROM_TEXTURE_CACHE - { "DSK", LLColor4::blue }, // CACHE_POST - { "NET", LLColor4::green }, // LOAD_FROM_NETWORK - { "HTW", LLColor4::green }, // WAIT_HTTP_RESOURCE - { "HTW", LLColor4::green }, // WAIT_HTTP_RESOURCE2 - { "REQ", LLColor4::yellow },// SEND_HTTP_REQ - { "HTP", LLColor4::green }, // WAIT_HTTP_REQ - { "DEC", LLColor4::yellow },// DECODE_IMAGE - { "DEU", LLColor4::green }, // DECODE_IMAGE_UPDATE - { "WRT", LLColor4::purple },// WRITE_TO_CACHE - { "WWT", LLColor4::orange },// WAIT_ON_WRITE - { "END", LLColor4::red }, // DONE -#define LAST_STATE 14 - { "CRE", LLColor4::magenta }, // LAST_STATE+1 - { "FUL", LLColor4::green }, // LAST_STATE+2 - { "BAD", LLColor4::red }, // LAST_STATE+3 - { "MIS", LLColor4::red }, // LAST_STATE+4 - { "---", LLColor4::white }, // LAST_STATE+5 - }; - const S32 fetch_state_desc_size = (S32)LL_ARRAY_SIZE(fetch_state_desc); - S32 state = - mImagep->mNeedsCreateTexture ? LAST_STATE+1 : - mImagep->mFullyLoaded ? LAST_STATE+2 : - mImagep->mMinDiscardLevel > 0 ? LAST_STATE+3 : - mImagep->mIsMissingAsset ? LAST_STATE+4 : - !mImagep->mIsFetching ? LAST_STATE+5 : - mImagep->mFetchState; - state = llclamp(state,0,fetch_state_desc_size-1); - - LLFontGL::getFontMonospace()->renderUTF8(fetch_state_desc[state].desc, 0, title_x2, getRect().getHeight(), - fetch_state_desc[state].color, - LLFontGL::LEFT, LLFontGL::TOP); - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - // Draw the progress bar. - S32 bar_width = 100; - S32 bar_left = 260; - left = bar_left; - right = left + bar_width; - - gGL.color4f(0.f, 0.f, 0.f, 0.75f); - gl_rect_2d(left, top, right, bottom); - - F32 data_progress = mImagep->mDownloadProgress; - - if (data_progress > 0.0f) - { - // Downloaded bytes - right = left + llfloor(data_progress * (F32)bar_width); - if (right > left) - { - gGL.color4f(0.f, 0.f, 1.f, 0.75f); - gl_rect_2d(left, top, right, bottom); - } - } - - S32 pip_width = 6; - S32 pip_space = 14; - S32 pip_x = title_x3 + pip_space/2; - - // Draw the packet pip - const F32 pip_max_time = 5.f; - F32 last_event = mImagep->mLastPacketTimer.getElapsedTimeF32(); - if (last_event < pip_max_time) - { - clr = LLColor4::white; - } - else - { - last_event = mImagep->mRequestDeltaTime; - if (last_event < pip_max_time) - { - clr = LLColor4::green; - } - else - { - last_event = mImagep->mFetchDeltaTime; - if (last_event < pip_max_time) - { - clr = LLColor4::yellow; - } - } - } - if (last_event < pip_max_time) - { - clr.setAlpha(1.f - last_event/pip_max_time); - gGL.color4fv(clr.mV); - gl_rect_2d(pip_x, top, pip_x + pip_width, bottom); - } - pip_x += pip_width + pip_space; - - // we don't want to show bind/resident pips for textures using the default texture - if (mImagep->hasGLTexture()) - { - // Draw the bound pip - last_event = mImagep->getTimePassedSinceLastBound(); - if (last_event < 1.f) - { - clr = mImagep->getMissed() ? LLColor4::red : LLColor4::magenta1; - clr.setAlpha(1.f - last_event); - gGL.color4fv(clr.mV); - gl_rect_2d(pip_x, top, pip_x + pip_width, bottom); - } - } - pip_x += pip_width + pip_space; - - - { - LLGLSUIDefault gls_ui; - // draw the packet data -// { -// std::string num_str = llformat("%3d/%3d", mImagep->mLastPacket+1, mImagep->mPackets); -// LLFontGL::getFontMonospace()->renderUTF8(num_str, 0, bar_left + 100, getRect().getHeight(), color, -// LLFontGL::LEFT, LLFontGL::TOP); -// } - - // draw the image size at the end - { - std::string num_str = llformat("%3dx%3d (%2d) %7d", mImagep->getWidth(), mImagep->getHeight(), - mImagep->getDiscardLevel(), mImagep->hasGLTexture() ? mImagep->getTextureMemory().value() : 0); - LLFontGL::getFontMonospace()->renderUTF8(num_str, 0, title_x4, getRect().getHeight(), color, - LLFontGL::LEFT, LLFontGL::TOP); - } - } - -} - -bool LLTextureBar::handleMouseDown(S32 x, S32 y, MASK mask) -{ - if ((mask & (MASK_CONTROL|MASK_SHIFT|MASK_ALT)) == MASK_ALT) - { - LLAppViewer::getTextureFetch()->mDebugID = mImagep->getID(); - return true; - } - return LLView::handleMouseDown(x,y,mask); -} - -LLRect LLTextureBar::getRequiredRect() -{ - LLRect rect; - - rect.mTop = texture_bar_height; - - return rect; -} - -//////////////////////////////////////////////////////////////////////////// - -class LLAvatarTexBar : public LLView -{ -public: - struct Params : public LLInitParam::Block - { - Mandatory texture_view; - Params() - : texture_view("texture_view") - { - S32 line_height = LLFontGL::getFontMonospace()->getLineHeight(); - changeDefault(rect, LLRect(0,0,100,line_height * 4)); - } - }; - - LLAvatarTexBar(const Params& p) - : LLView(p), - mTextureView(p.texture_view) - {} - - virtual void draw(); - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual LLRect getRequiredRect(); // Return the height of this object, given the set options. - -private: - LLTextureView* mTextureView; -}; - -void LLAvatarTexBar::draw() -{ - if (!gSavedSettings.getBOOL("DebugAvatarRezTime")) return; - - LLVOAvatarSelf* avatarp = gAgentAvatarp; - if (!avatarp) return; - - const S32 line_height = LLFontGL::getFontMonospace()->getLineHeight(); - const S32 v_offset = 0; - const S32 l_offset = 3; - - //---------------------------------------------------------------------------- - LLGLSUIDefault gls_ui; - LLColor4 color; - - U32 line_num = 1; - for (LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::BakedTextures::const_iterator baked_iter = LLAvatarAppearance::getDictionary()->getBakedTextures().begin(); - baked_iter != LLAvatarAppearance::getDictionary()->getBakedTextures().end(); - ++baked_iter) - { - const LLAvatarAppearanceDefines::EBakedTextureIndex baked_index = baked_iter->first; - const LLViewerTexLayerSet *layerset = avatarp->debugGetLayerSet(baked_index); - if (!layerset) continue; - const LLViewerTexLayerSetBuffer *layerset_buffer = layerset->getViewerComposite(); - if (!layerset_buffer) continue; - - LLColor4 text_color = LLColor4::white; - - std::string text = layerset_buffer->dumpTextureInfo(); - LLFontGL::getFontMonospace()->renderUTF8(text, 0, l_offset, v_offset + line_height*line_num, - text_color, LLFontGL::LEFT, LLFontGL::TOP); //, LLFontGL::BOLD, LLFontGL::DROP_SHADOW_SOFT); - line_num++; - } - const U32 override_tex_discard_level = gSavedSettings.getU32("TextureDiscardLevel"); - - LLColor4 header_color(1.f, 1.f, 1.f, 0.9f); - - const std::string override_tex_discard_level_str = override_tex_discard_level ? llformat("%d",override_tex_discard_level) : "Disabled"; - std::string header_text = llformat("[ Timeout:60 ] [ LOD_Override('TextureDiscardLevel'):%s ]", override_tex_discard_level_str.c_str()); - LLFontGL::getFontMonospace()->renderUTF8(header_text, 0, l_offset, v_offset + line_height*line_num, - header_color, LLFontGL::LEFT, LLFontGL::TOP); //, LLFontGL::BOLD, LLFontGL::DROP_SHADOW_SOFT); - line_num++; - std::string section_text = "Avatar Textures Information:"; - LLFontGL::getFontMonospace()->renderUTF8(section_text, 0, 0, v_offset + line_height*line_num, - header_color, LLFontGL::LEFT, LLFontGL::TOP, LLFontGL::BOLD, LLFontGL::DROP_SHADOW_SOFT); -} - -bool LLAvatarTexBar::handleMouseDown(S32 x, S32 y, MASK mask) -{ - return false; -} - -LLRect LLAvatarTexBar::getRequiredRect() -{ - LLRect rect; - rect.mTop = 100; - if (!gSavedSettings.getBOOL("DebugAvatarRezTime")) rect.mTop = 0; - return rect; -} - -//////////////////////////////////////////////////////////////////////////// - -class LLGLTexMemBar : public LLView -{ -public: - struct Params : public LLInitParam::Block - { - Mandatory texture_view; - Params() - : texture_view("texture_view") - { - S32 line_height = LLFontGL::getFontMonospace()->getLineHeight(); - changeDefault(rect, LLRect(0,0,100,line_height * 4)); - } - }; - - LLGLTexMemBar(const Params& p) - : LLView(p), - mTextureView(p.texture_view) - {} - - virtual void draw(); - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual LLRect getRequiredRect(); // Return the height of this object, given the set options. - -private: - LLTextureView* mTextureView; -}; - -void LLGLTexMemBar::draw() -{ - F32 discard_bias = LLViewerTexture::sDesiredDiscardBias; - F32 cache_usage = LLAppViewer::getTextureCache()->getUsage().valueInUnits(); - F32 cache_max_usage = LLAppViewer::getTextureCache()->getMaxUsage().valueInUnits(); - S32 line_height = LLFontGL::getFontMonospace()->getLineHeight(); - S32 v_offset = 0;//(S32)((texture_bar_height + 2.2f) * mTextureView->mNumTextureBars + 2.0f); - F32Bytes total_texture_downloaded = gTotalTextureData; - F32Bytes total_object_downloaded = gTotalObjectData; - U32 total_http_requests = LLAppViewer::getTextureFetch()->getTotalNumHTTPRequests(); - U32 total_active_cached_objects = LLWorld::getInstance()->getNumOfActiveCachedObjects(); - U32 total_objects = gObjectList.getNumObjects(); - F32 x_right = 0.0; - - //---------------------------------------------------------------------------- - LLGLSUIDefault gls_ui; - LLColor4 text_color(1.f, 1.f, 1.f, 0.75f); - LLColor4 color; - - // Gray background using completely magic numbers - gGL.color4f(0.f, 0.f, 0.f, 0.25f); - // const LLRect & rect(getRect()); - // gl_rect_2d(-4, v_offset, rect.mRight - rect.mLeft + 2, v_offset + line_height*4); - - std::string text = ""; - LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*6, - text_color, LLFontGL::LEFT, LLFontGL::TOP); - - LLTrace::Recording& recording = LLViewerStats::instance().getRecording(); - - F64 cacheHits = recording.getSampleCount(LLTextureFetch::sCacheHit); - F64 cacheAttempts = recording.getSampleCount(LLTextureFetch::sCacheAttempt); - - F32 cacheHitRate = (cacheAttempts > 0.0) ? F32((cacheHits / cacheAttempts) * 100.0f) : 0.0f; - - U32 cacheReadLatMin = U32(recording.getMin(LLTextureFetch::sCacheReadLatency).value() * 1000.0f); - U32 cacheReadLatMed = U32(recording.getMean(LLTextureFetch::sCacheReadLatency).value() * 1000.0f); - U32 cacheReadLatMax = U32(recording.getMax(LLTextureFetch::sCacheReadLatency).value() * 1000.0f); - - U32 texDecodeLatMin = U32(recording.getMin(LLTextureFetch::sTexDecodeLatency).value() * 1000.0f); - U32 texDecodeLatMed = U32(recording.getMean(LLTextureFetch::sTexDecodeLatency).value() * 1000.0f); - U32 texDecodeLatMax = U32(recording.getMax(LLTextureFetch::sTexDecodeLatency).value() * 1000.0f); - - U32 texFetchLatMin = U32(recording.getMin(LLTextureFetch::sTexFetchLatency).value() * 1000.0f); - U32 texFetchLatMed = U32(recording.getMean(LLTextureFetch::sTexFetchLatency).value() * 1000.0f); - U32 texFetchLatMax = U32(recording.getMax(LLTextureFetch::sTexFetchLatency).value() * 1000.0f); - - text = llformat("Est. Free: %d MB Sys Free: %d MB FBO: %d MB Bias: %.2f Cache: %.1f/%.1f MB", - (S32)LLViewerTexture::sFreeVRAMMegabytes, - LLMemory::getAvailableMemKB()/1024, - LLRenderTarget::sBytesAllocated/(1024*1024), - discard_bias, - cache_usage, - cache_max_usage); - //, cache_entries, cache_max_entries - - LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*6, - text_color, LLFontGL::LEFT, LLFontGL::TOP); - - U32 cache_read(0U), cache_write(0U), res_wait(0U); - LLAppViewer::getTextureFetch()->getStateStats(&cache_read, &cache_write, &res_wait); - - text = llformat("Net Tot Tex: %.1f MB Tot Obj: %.1f MB #Objs/#Cached: %d/%d Tot Htp: %d Cread: %u Cwrite: %u Rwait: %u", - total_texture_downloaded.valueInUnits(), - total_object_downloaded.valueInUnits(), - total_objects, - total_active_cached_objects, - total_http_requests, - cache_read, - cache_write, - res_wait); - - LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*5, - text_color, LLFontGL::LEFT, LLFontGL::TOP); - - text = llformat("CacheHitRate: %3.2f Read: %d/%d/%d Decode: %d/%d/%d Fetch: %d/%d/%d", - cacheHitRate, - cacheReadLatMin, - cacheReadLatMed, - cacheReadLatMax, - texDecodeLatMin, - texDecodeLatMed, - texDecodeLatMax, - texFetchLatMin, - texFetchLatMed, - texFetchLatMax); - - LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*4, - text_color, LLFontGL::LEFT, LLFontGL::TOP); - - //---------------------------------------------------------------------------- - - text = llformat("Textures: %d Fetch: %d(%d) Pkts:%d(%d) Cache R/W: %d/%d LFS:%d RAW:%d HTP:%d DEC:%d CRE:%d ", - gTextureList.getNumImages(), - LLAppViewer::getTextureFetch()->getNumRequests(), LLAppViewer::getTextureFetch()->getNumDeletes(), - LLAppViewer::getTextureFetch()->mPacketCount, LLAppViewer::getTextureFetch()->mBadPacketCount, - LLAppViewer::getTextureCache()->getNumReads(), LLAppViewer::getTextureCache()->getNumWrites(), - LLLFSThread::sLocal->getPending(), - LLImageRaw::sRawImageCount, - LLAppViewer::getTextureFetch()->getNumHTTPRequests(), - LLAppViewer::getImageDecodeThread()->getPending(), - gTextureList.mCreateTextureList.size()); - - x_right = 550.0; - LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*3, - text_color, LLFontGL::LEFT, LLFontGL::TOP, - LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, &x_right); - - F32Kilobits bandwidth(LLAppViewer::getTextureFetch()->getTextureBandwidth()); - F32Kilobits max_bandwidth(gSavedSettings.getF32("ThrottleBandwidthKBPS")); - color = bandwidth > max_bandwidth ? LLColor4::red : bandwidth > max_bandwidth*.75f ? LLColor4::yellow : text_color; - color[VALPHA] = text_color[VALPHA]; - text = llformat("BW:%.0f/%.0f",bandwidth.value(), max_bandwidth.value()); - LLFontGL::getFontMonospace()->renderUTF8(text, 0, x_right, v_offset + line_height*3, - color, LLFontGL::LEFT, LLFontGL::TOP); - - // Mesh status line - text = llformat("Mesh: Reqs(Tot/Htp/Big): %u/%u/%u Rtr/Err: %u/%u Cread/Cwrite: %u/%u Low/At/High: %d/%d/%d", - LLMeshRepository::sMeshRequestCount, LLMeshRepository::sHTTPRequestCount, LLMeshRepository::sHTTPLargeRequestCount, - LLMeshRepository::sHTTPRetryCount, LLMeshRepository::sHTTPErrorCount, - LLMeshRepository::sCacheReads, LLMeshRepository::sCacheWrites, - LLMeshRepoThread::sRequestLowWater, LLMeshRepoThread::sRequestWaterLevel, LLMeshRepoThread::sRequestHighWater); - LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*2, - text_color, LLFontGL::LEFT, LLFontGL::TOP); - - // Header for texture table columns - S32 dx1 = 0; - if (LLAppViewer::getTextureFetch()->mDebugPause) - { - LLFontGL::getFontMonospace()->renderUTF8(std::string("!"), 0, title_x1, v_offset + line_height, - text_color, LLFontGL::LEFT, LLFontGL::TOP); - dx1 += 8; - } - if (mTextureView->mFreezeView) - { - LLFontGL::getFontMonospace()->renderUTF8(std::string("*"), 0, title_x1, v_offset + line_height, - text_color, LLFontGL::LEFT, LLFontGL::TOP); - dx1 += 8; - } - if (mTextureView->mOrderFetch) - { - LLFontGL::getFontMonospace()->renderUTF8(title_string1b, 0, title_x1+dx1, v_offset + line_height, - text_color, LLFontGL::LEFT, LLFontGL::TOP); - } - else - { - LLFontGL::getFontMonospace()->renderUTF8(title_string1a, 0, title_x1+dx1, v_offset + line_height, - text_color, LLFontGL::LEFT, LLFontGL::TOP); - } - - LLFontGL::getFontMonospace()->renderUTF8(title_string2, 0, title_x2, v_offset + line_height, - text_color, LLFontGL::LEFT, LLFontGL::TOP); - - LLFontGL::getFontMonospace()->renderUTF8(title_string3, 0, title_x3, v_offset + line_height, - text_color, LLFontGL::LEFT, LLFontGL::TOP); - - LLFontGL::getFontMonospace()->renderUTF8(title_string4, 0, title_x4, v_offset + line_height, - text_color, LLFontGL::LEFT, LLFontGL::TOP); -} - -bool LLGLTexMemBar::handleMouseDown(S32 x, S32 y, MASK mask) -{ - return false; -} - -LLRect LLGLTexMemBar::getRequiredRect() -{ - LLRect rect; - rect.mTop = 78; //LLFontGL::getFontMonospace()->getLineHeight() * 6; - return rect; -} - -//////////////////////////////////////////////////////////////////////////// -class LLGLTexSizeBar -{ -public: - LLGLTexSizeBar(S32 index, S32 left, S32 bottom, S32 right, S32 line_height) - { - mIndex = index ; - mLeft = left ; - mBottom = bottom ; - mRight = right ; - mLineHeight = line_height ; - mTopLoaded = 0 ; - mTopBound = 0 ; - mScale = 1.0f ; - } - - void setTop(S32 loaded, S32 bound, F32 scale) {mTopLoaded = loaded ; mTopBound = bound; mScale = scale ;} - - void draw(); - bool handleHover(S32 x, S32 y, MASK mask, bool set_pick_size) ; - -private: - S32 mIndex ; - S32 mLeft ; - S32 mBottom ; - S32 mRight ; - S32 mTopLoaded ; - S32 mTopBound ; - S32 mLineHeight ; - F32 mScale ; -}; - -bool LLGLTexSizeBar::handleHover(S32 x, S32 y, MASK mask, bool set_pick_size) -{ - if(y > mBottom && (y < mBottom + (S32)(mTopLoaded * mScale) || y < mBottom + (S32)(mTopBound * mScale))) - { - LLImageGL::setCurTexSizebar(mIndex, set_pick_size); - } - return true ; -} -void LLGLTexSizeBar::draw() -{ - LLGLSUIDefault gls_ui; - - if(LLImageGL::sCurTexSizeBar == mIndex) - { - LLColor4 text_color(1.f, 1.f, 1.f, 0.75f); - std::string text; - - text = llformat("%d", mTopLoaded) ; - LLFontGL::getFontMonospace()->renderUTF8(text, 0, mLeft, mBottom + (S32)(mTopLoaded * mScale) + mLineHeight, - text_color, LLFontGL::LEFT, LLFontGL::TOP); - - text = llformat("%d", mTopBound) ; - LLFontGL::getFontMonospace()->renderUTF8(text, 0, (mLeft + mRight) / 2, mBottom + (S32)(mTopBound * mScale) + mLineHeight, - text_color, LLFontGL::LEFT, LLFontGL::TOP); - } - - LLColor4 loaded_color(1.0f, 0.0f, 0.0f, 0.75f); - LLColor4 bound_color(1.0f, 1.0f, 0.0f, 0.75f); - gl_rect_2d(mLeft, mBottom + (S32)(mTopLoaded * mScale), (mLeft + mRight) / 2, mBottom, loaded_color) ; - gl_rect_2d((mLeft + mRight) / 2, mBottom + (S32)(mTopBound * mScale), mRight, mBottom, bound_color) ; -} -//////////////////////////////////////////////////////////////////////////// - -LLTextureView::LLTextureView(const LLTextureView::Params& p) - : LLContainerView(p), - mFreezeView(false), - mOrderFetch(false), - mPrintList(false), - mNumTextureBars(0) -{ - setVisible(false); - - setDisplayChildren(true); - mGLTexMemBar = 0; - mAvatarTexBar = 0; -} - -LLTextureView::~LLTextureView() -{ - // Children all cleaned up by default view destructor. - delete mGLTexMemBar; - mGLTexMemBar = 0; - - delete mAvatarTexBar; - mAvatarTexBar = 0; -} - -typedef std::pair decode_pair_t; -struct compare_decode_pair -{ - bool operator()(const decode_pair_t& a, const decode_pair_t& b) const - { - return a.first > b.first; - } -}; - -struct KillView -{ - void operator()(LLView* viewp) - { - viewp->getParent()->removeChild(viewp); - viewp->die(); - } -}; - -void LLTextureView::draw() -{ - if (!mFreezeView) - { -// LLViewerObject *objectp; -// S32 te; - - for_each(mTextureBars.begin(), mTextureBars.end(), KillView()); - mTextureBars.clear(); - - if (mGLTexMemBar) - { - removeChild(mGLTexMemBar); - mGLTexMemBar->die(); - mGLTexMemBar = 0; - } - - if (mAvatarTexBar) - { - removeChild(mAvatarTexBar); - mAvatarTexBar->die(); - mAvatarTexBar = 0; - } - - typedef std::multiset display_list_t; - display_list_t display_image_list; - - if (mPrintList) - { - LL_INFOS() << "ID\tMEM\tBOOST\tPRI\tWIDTH\tHEIGHT\tDISCARD" << LL_ENDL; - } - - for (LLViewerTextureList::image_priority_list_t::iterator iter = gTextureList.mImageList.begin(); - iter != gTextureList.mImageList.end(); ) - { - LLPointer imagep = *iter++; - if(!imagep->hasFetcher()) - { - continue ; - } - - S32 cur_discard = imagep->getDiscardLevel(); - S32 desired_discard = imagep->mDesiredDiscardLevel; - - if (mPrintList) - { - S32 tex_mem = imagep->hasGLTexture() ? imagep->getTextureMemory().value() : 0 ; - LL_INFOS() << imagep->getID() - << "\t" << tex_mem - << "\t" << imagep->getBoostLevel() - << "\t" << imagep->getMaxVirtualSize() - << "\t" << imagep->getWidth() - << "\t" << imagep->getHeight() - << "\t" << cur_discard - << LL_ENDL; - } - - if (imagep->getID() == LLAppViewer::getTextureFetch()->mDebugID) - { -// static S32 debug_count = 0; -// ++debug_count; // for breakpoints - } - - F32 pri; - if (mOrderFetch) - { - pri = ((F32)imagep->mFetchPriority)/256.f; - } - else - { - pri = imagep->getMaxVirtualSize(); - } - pri = llclamp(pri, 0.0f, HIGH_PRIORITY-1.f); - - if (sDebugImages.find(imagep) != sDebugImages.end()) - { - pri += 4*HIGH_PRIORITY; - } - - if (!mOrderFetch) - { - if (pri < HIGH_PRIORITY && LLSelectMgr::getInstance()) - { - struct f : public LLSelectedTEFunctor - { - LLViewerFetchedTexture* mImage; - f(LLViewerFetchedTexture* image) : mImage(image) {} - virtual bool apply(LLViewerObject* object, S32 te) - { - return (mImage == object->getTEImage(te)); - } - } func(imagep); - const bool firstonly = true; - bool match = LLSelectMgr::getInstance()->getSelection()->applyToTEs(&func, firstonly); - if (match) - { - pri += 3*HIGH_PRIORITY; - } - } - - if (pri < HIGH_PRIORITY && (cur_discard< 0 || desired_discard < cur_discard)) - { - LLSelectNode* hover_node = LLSelectMgr::instance().getHoverNode(); - if (hover_node) - { - LLViewerObject *objectp = hover_node->getObject(); - if (objectp) - { - S32 tex_count = objectp->getNumTEs(); - for (S32 i = 0; i < tex_count; i++) - { - if (imagep == objectp->getTEImage(i)) - { - pri += 2*HIGH_PRIORITY; - break; - } - } - } - } - } - - if (pri > 0.f && pri < HIGH_PRIORITY) - { - if (imagep->mLastPacketTimer.getElapsedTimeF32() < 1.f || - imagep->mFetchDeltaTime < 0.25f) - { - pri += 1*HIGH_PRIORITY; - } - } - } - - if (pri > 0.0f) - { - display_image_list.insert(std::make_pair(pri, imagep)); - } - } - - if (mPrintList) - { - mPrintList = false; - } - - static S32 max_count = 50; - S32 count = 0; - mNumTextureBars = 0 ; - for (display_list_t::iterator iter = display_image_list.begin(); - iter != display_image_list.end(); iter++) - { - LLViewerFetchedTexture* imagep = iter->second; - S32 hilite = 0; - F32 pri = iter->first; - if (pri >= 1 * HIGH_PRIORITY) - { - hilite = (S32)((pri+1) / HIGH_PRIORITY) - 1; - } - if ((hilite || count < max_count-10) && (count < max_count)) - { - if (addBar(imagep, hilite)) - { - count++; - } - } - } - - if (mOrderFetch) - sortChildren(LLTextureBar::sort_fetch()); - else - sortChildren(LLTextureBar::sort()); - - LLGLTexMemBar::Params tmbp; - LLRect tmbr; - tmbp.name("gl texmem bar"); - tmbp.rect(tmbr); - tmbp.follows.flags = FOLLOWS_LEFT|FOLLOWS_TOP; - tmbp.texture_view(this); - mGLTexMemBar = LLUICtrlFactory::create(tmbp); - addChild(mGLTexMemBar); - sendChildToFront(mGLTexMemBar); - - LLAvatarTexBar::Params atbp; - LLRect atbr; - atbp.name("gl avatartex bar"); - atbp.texture_view(this); - atbp.rect(atbr); - mAvatarTexBar = LLUICtrlFactory::create(atbp); - addChild(mAvatarTexBar); - sendChildToFront(mAvatarTexBar); - - reshape(getRect().getWidth(), getRect().getHeight(), true); - - LLUI::popMatrix(); - LLUI::pushMatrix(); - LLUI::translate((F32)getRect().mLeft, (F32)getRect().mBottom); - - for (child_list_const_iter_t child_iter = getChildList()->begin(); - child_iter != getChildList()->end(); ++child_iter) - { - LLView *viewp = *child_iter; - if (viewp->getRect().mBottom < 0) - { - viewp->setVisible(false); - } - } - } - - LLContainerView::draw(); - -} - -bool LLTextureView::addBar(LLViewerFetchedTexture *imagep, S32 hilite) -{ - llassert(imagep); - - LLTextureBar *barp; - LLRect r; - - mNumTextureBars++; - - LLTextureBar::Params tbp; - tbp.name("texture bar"); - tbp.rect(r); - tbp.texture_view(this); - barp = LLUICtrlFactory::create(tbp); - barp->mImagep = imagep; - barp->mHilite = hilite; - - addChild(barp); - mTextureBars.push_back(barp); - - return true; -} - -bool LLTextureView::handleMouseDown(S32 x, S32 y, MASK mask) -{ - if ((mask & (MASK_CONTROL|MASK_SHIFT|MASK_ALT)) == (MASK_ALT|MASK_SHIFT)) - { - mPrintList = true; - return true; - } - if ((mask & (MASK_CONTROL|MASK_SHIFT|MASK_ALT)) == (MASK_CONTROL|MASK_SHIFT)) - { - LLAppViewer::getTextureFetch()->mDebugPause = !LLAppViewer::getTextureFetch()->mDebugPause; - return true; - } - if (mask & MASK_SHIFT) - { - mFreezeView = !mFreezeView; - return true; - } - if (mask & MASK_CONTROL) - { - mOrderFetch = !mOrderFetch; - return true; - } - return LLView::handleMouseDown(x,y,mask); -} - -bool LLTextureView::handleMouseUp(S32 x, S32 y, MASK mask) -{ - return false; -} - -bool LLTextureView::handleKey(KEY key, MASK mask, bool called_from_parent) -{ - return false; -} - - +/** + * @file lltextureview.cpp + * @brief LLTextureView class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012-2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include + +#include "lltextureview.h" + +#include "llrect.h" +#include "llerror.h" +#include "lllfsthread.h" +#include "llui.h" +#include "llimageworker.h" +#include "llrender.h" + +#include "lltooltip.h" +#include "llappviewer.h" +#include "llmeshrepository.h" +#include "llselectmgr.h" +#include "llviewertexlayer.h" +#include "lltexturecache.h" +#include "lltexturefetch.h" +#include "llviewercontrol.h" +#include "llviewerobject.h" +#include "llviewerobjectlist.h" +#include "llviewertexture.h" +#include "llviewertexturelist.h" +#include "llviewerwindow.h" +#include "llwindow.h" +#include "llvovolume.h" +#include "llviewerstats.h" +#include "llworld.h" + +// For avatar texture view +#include "llvoavatarself.h" +#include "lltexlayer.h" + +extern F32 texmem_lower_bound_scale; + +LLTextureView *gTextureView = NULL; + +#define HIGH_PRIORITY 100000000.f + +//static +std::set LLTextureView::sDebugImages; + +//////////////////////////////////////////////////////////////////////////// + +static std::string title_string1a("Tex UUID Area DDis(Req) DecodePri(Fetch) [download] pk/max"); +static std::string title_string1b("Tex UUID Area DDis(Req) Fetch(DecodePri) [download] pk/max"); +static std::string title_string2("State"); +static std::string title_string3("Pkt Bnd"); +static std::string title_string4(" W x H (Dis) Mem"); + +static S32 title_x1 = 0; +static S32 title_x2 = 460; +static S32 title_x3 = title_x2 + 40; +static S32 title_x4 = title_x3 + 46; +static S32 texture_bar_height = 8; + +//////////////////////////////////////////////////////////////////////////// + +class LLTextureBar : public LLView +{ +public: + LLPointer mImagep; + S32 mHilite; + +public: + struct Params : public LLInitParam::Block + { + Mandatory texture_view; + Params() + : texture_view("texture_view") + { + changeDefault(mouse_opaque, false); + } + }; + LLTextureBar(const Params& p) + : LLView(p), + mHilite(0), + mTextureView(p.texture_view) + {} + + virtual void draw(); + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual LLRect getRequiredRect(); // Return the height of this object, given the set options. + +// Used for sorting + struct sort + { + bool operator()(const LLView* i1, const LLView* i2) + { + LLTextureBar* bar1p = (LLTextureBar*)i1; + LLTextureBar* bar2p = (LLTextureBar*)i2; + LLViewerFetchedTexture *i1p = bar1p->mImagep; + LLViewerFetchedTexture *i2p = bar2p->mImagep; + F32 pri1 = i1p->getMaxVirtualSize(); + F32 pri2 = i2p->getMaxVirtualSize(); + if (pri1 > pri2) + return true; + else if (pri2 > pri1) + return false; + else + return i1p->getID() < i2p->getID(); + } + }; + + struct sort_fetch + { + bool operator()(const LLView* i1, const LLView* i2) + { + LLTextureBar* bar1p = (LLTextureBar*)i1; + LLTextureBar* bar2p = (LLTextureBar*)i2; + LLViewerFetchedTexture *i1p = bar1p->mImagep; + LLViewerFetchedTexture *i2p = bar2p->mImagep; + U32 pri1 = i1p->getFetchPriority() ; + U32 pri2 = i2p->getFetchPriority() ; + if (pri1 > pri2) + return true; + else if (pri2 > pri1) + return false; + else + return i1p->getID() < i2p->getID(); + } + }; +private: + LLTextureView* mTextureView; +}; + +void LLTextureBar::draw() +{ + if (!mImagep) + { + return; + } + + LLColor4 color; + if (mImagep->getID() == LLAppViewer::getTextureFetch()->mDebugID) + { + color = LLColor4::cyan2; + } + else if (mHilite) + { + S32 idx = llclamp(mHilite,1,3); + if (idx==1) color = LLColor4::orange; + else if (idx==2) color = LLColor4::yellow; + else color = LLColor4::pink2; + } + else if (mImagep->mDontDiscard) + { + color = LLColor4::green4; + } + else if (mImagep->getMaxVirtualSize() <= 0.0f) + { + color = LLColor4::grey; color[VALPHA] = .7f; + } + else + { + color = LLColor4::white; color[VALPHA] = .7f; + } + + // We need to draw: + // The texture UUID or name + // The progress bar for the texture, highlighted if it's being download + // Various numerical stats. + std::string tex_str; + S32 left, right; + S32 top = 0; + S32 bottom = top + 6; + LLColor4 clr; + + LLGLSUIDefault gls_ui; + + // Name, pixel_area, requested pixel area, decode priority + std::string uuid_str; + mImagep->mID.toString(uuid_str); + uuid_str = uuid_str.substr(0,7); + + tex_str = llformat("%s %7.0f %d(%d)", + uuid_str.c_str(), + mImagep->mMaxVirtualSize, + mImagep->mDesiredDiscardLevel, + mImagep->mRequestedDiscardLevel); + + + LLFontGL::getFontMonospace()->renderUTF8(tex_str, 0, title_x1, getRect().getHeight(), + color, LLFontGL::LEFT, LLFontGL::TOP); + + // State + // Hack: mirrored from lltexturefetch.cpp + struct { const std::string desc; LLColor4 color; } fetch_state_desc[] = { + { "---", LLColor4::red }, // INVALID + { "INI", LLColor4::white }, // INIT + { "DSK", LLColor4::cyan }, // LOAD_FROM_TEXTURE_CACHE + { "DSK", LLColor4::blue }, // CACHE_POST + { "NET", LLColor4::green }, // LOAD_FROM_NETWORK + { "HTW", LLColor4::green }, // WAIT_HTTP_RESOURCE + { "HTW", LLColor4::green }, // WAIT_HTTP_RESOURCE2 + { "REQ", LLColor4::yellow },// SEND_HTTP_REQ + { "HTP", LLColor4::green }, // WAIT_HTTP_REQ + { "DEC", LLColor4::yellow },// DECODE_IMAGE + { "DEU", LLColor4::green }, // DECODE_IMAGE_UPDATE + { "WRT", LLColor4::purple },// WRITE_TO_CACHE + { "WWT", LLColor4::orange },// WAIT_ON_WRITE + { "END", LLColor4::red }, // DONE +#define LAST_STATE 14 + { "CRE", LLColor4::magenta }, // LAST_STATE+1 + { "FUL", LLColor4::green }, // LAST_STATE+2 + { "BAD", LLColor4::red }, // LAST_STATE+3 + { "MIS", LLColor4::red }, // LAST_STATE+4 + { "---", LLColor4::white }, // LAST_STATE+5 + }; + const S32 fetch_state_desc_size = (S32)LL_ARRAY_SIZE(fetch_state_desc); + S32 state = + mImagep->mNeedsCreateTexture ? LAST_STATE+1 : + mImagep->mFullyLoaded ? LAST_STATE+2 : + mImagep->mMinDiscardLevel > 0 ? LAST_STATE+3 : + mImagep->mIsMissingAsset ? LAST_STATE+4 : + !mImagep->mIsFetching ? LAST_STATE+5 : + mImagep->mFetchState; + state = llclamp(state,0,fetch_state_desc_size-1); + + LLFontGL::getFontMonospace()->renderUTF8(fetch_state_desc[state].desc, 0, title_x2, getRect().getHeight(), + fetch_state_desc[state].color, + LLFontGL::LEFT, LLFontGL::TOP); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + // Draw the progress bar. + S32 bar_width = 100; + S32 bar_left = 260; + left = bar_left; + right = left + bar_width; + + gGL.color4f(0.f, 0.f, 0.f, 0.75f); + gl_rect_2d(left, top, right, bottom); + + F32 data_progress = mImagep->mDownloadProgress; + + if (data_progress > 0.0f) + { + // Downloaded bytes + right = left + llfloor(data_progress * (F32)bar_width); + if (right > left) + { + gGL.color4f(0.f, 0.f, 1.f, 0.75f); + gl_rect_2d(left, top, right, bottom); + } + } + + S32 pip_width = 6; + S32 pip_space = 14; + S32 pip_x = title_x3 + pip_space/2; + + // Draw the packet pip + const F32 pip_max_time = 5.f; + F32 last_event = mImagep->mLastPacketTimer.getElapsedTimeF32(); + if (last_event < pip_max_time) + { + clr = LLColor4::white; + } + else + { + last_event = mImagep->mRequestDeltaTime; + if (last_event < pip_max_time) + { + clr = LLColor4::green; + } + else + { + last_event = mImagep->mFetchDeltaTime; + if (last_event < pip_max_time) + { + clr = LLColor4::yellow; + } + } + } + if (last_event < pip_max_time) + { + clr.setAlpha(1.f - last_event/pip_max_time); + gGL.color4fv(clr.mV); + gl_rect_2d(pip_x, top, pip_x + pip_width, bottom); + } + pip_x += pip_width + pip_space; + + // we don't want to show bind/resident pips for textures using the default texture + if (mImagep->hasGLTexture()) + { + // Draw the bound pip + last_event = mImagep->getTimePassedSinceLastBound(); + if (last_event < 1.f) + { + clr = mImagep->getMissed() ? LLColor4::red : LLColor4::magenta1; + clr.setAlpha(1.f - last_event); + gGL.color4fv(clr.mV); + gl_rect_2d(pip_x, top, pip_x + pip_width, bottom); + } + } + pip_x += pip_width + pip_space; + + + { + LLGLSUIDefault gls_ui; + // draw the packet data +// { +// std::string num_str = llformat("%3d/%3d", mImagep->mLastPacket+1, mImagep->mPackets); +// LLFontGL::getFontMonospace()->renderUTF8(num_str, 0, bar_left + 100, getRect().getHeight(), color, +// LLFontGL::LEFT, LLFontGL::TOP); +// } + + // draw the image size at the end + { + std::string num_str = llformat("%3dx%3d (%2d) %7d", mImagep->getWidth(), mImagep->getHeight(), + mImagep->getDiscardLevel(), mImagep->hasGLTexture() ? mImagep->getTextureMemory().value() : 0); + LLFontGL::getFontMonospace()->renderUTF8(num_str, 0, title_x4, getRect().getHeight(), color, + LLFontGL::LEFT, LLFontGL::TOP); + } + } + +} + +bool LLTextureBar::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if ((mask & (MASK_CONTROL|MASK_SHIFT|MASK_ALT)) == MASK_ALT) + { + LLAppViewer::getTextureFetch()->mDebugID = mImagep->getID(); + return true; + } + return LLView::handleMouseDown(x,y,mask); +} + +LLRect LLTextureBar::getRequiredRect() +{ + LLRect rect; + + rect.mTop = texture_bar_height; + + return rect; +} + +//////////////////////////////////////////////////////////////////////////// + +class LLAvatarTexBar : public LLView +{ +public: + struct Params : public LLInitParam::Block + { + Mandatory texture_view; + Params() + : texture_view("texture_view") + { + S32 line_height = LLFontGL::getFontMonospace()->getLineHeight(); + changeDefault(rect, LLRect(0,0,100,line_height * 4)); + } + }; + + LLAvatarTexBar(const Params& p) + : LLView(p), + mTextureView(p.texture_view) + {} + + virtual void draw(); + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual LLRect getRequiredRect(); // Return the height of this object, given the set options. + +private: + LLTextureView* mTextureView; +}; + +void LLAvatarTexBar::draw() +{ + if (!gSavedSettings.getBOOL("DebugAvatarRezTime")) return; + + LLVOAvatarSelf* avatarp = gAgentAvatarp; + if (!avatarp) return; + + const S32 line_height = LLFontGL::getFontMonospace()->getLineHeight(); + const S32 v_offset = 0; + const S32 l_offset = 3; + + //---------------------------------------------------------------------------- + LLGLSUIDefault gls_ui; + LLColor4 color; + + U32 line_num = 1; + for (LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::BakedTextures::const_iterator baked_iter = LLAvatarAppearance::getDictionary()->getBakedTextures().begin(); + baked_iter != LLAvatarAppearance::getDictionary()->getBakedTextures().end(); + ++baked_iter) + { + const LLAvatarAppearanceDefines::EBakedTextureIndex baked_index = baked_iter->first; + const LLViewerTexLayerSet *layerset = avatarp->debugGetLayerSet(baked_index); + if (!layerset) continue; + const LLViewerTexLayerSetBuffer *layerset_buffer = layerset->getViewerComposite(); + if (!layerset_buffer) continue; + + LLColor4 text_color = LLColor4::white; + + std::string text = layerset_buffer->dumpTextureInfo(); + LLFontGL::getFontMonospace()->renderUTF8(text, 0, l_offset, v_offset + line_height*line_num, + text_color, LLFontGL::LEFT, LLFontGL::TOP); //, LLFontGL::BOLD, LLFontGL::DROP_SHADOW_SOFT); + line_num++; + } + const U32 override_tex_discard_level = gSavedSettings.getU32("TextureDiscardLevel"); + + LLColor4 header_color(1.f, 1.f, 1.f, 0.9f); + + const std::string override_tex_discard_level_str = override_tex_discard_level ? llformat("%d",override_tex_discard_level) : "Disabled"; + std::string header_text = llformat("[ Timeout:60 ] [ LOD_Override('TextureDiscardLevel'):%s ]", override_tex_discard_level_str.c_str()); + LLFontGL::getFontMonospace()->renderUTF8(header_text, 0, l_offset, v_offset + line_height*line_num, + header_color, LLFontGL::LEFT, LLFontGL::TOP); //, LLFontGL::BOLD, LLFontGL::DROP_SHADOW_SOFT); + line_num++; + std::string section_text = "Avatar Textures Information:"; + LLFontGL::getFontMonospace()->renderUTF8(section_text, 0, 0, v_offset + line_height*line_num, + header_color, LLFontGL::LEFT, LLFontGL::TOP, LLFontGL::BOLD, LLFontGL::DROP_SHADOW_SOFT); +} + +bool LLAvatarTexBar::handleMouseDown(S32 x, S32 y, MASK mask) +{ + return false; +} + +LLRect LLAvatarTexBar::getRequiredRect() +{ + LLRect rect; + rect.mTop = 100; + if (!gSavedSettings.getBOOL("DebugAvatarRezTime")) rect.mTop = 0; + return rect; +} + +//////////////////////////////////////////////////////////////////////////// + +class LLGLTexMemBar : public LLView +{ +public: + struct Params : public LLInitParam::Block + { + Mandatory texture_view; + Params() + : texture_view("texture_view") + { + S32 line_height = LLFontGL::getFontMonospace()->getLineHeight(); + changeDefault(rect, LLRect(0,0,100,line_height * 4)); + } + }; + + LLGLTexMemBar(const Params& p) + : LLView(p), + mTextureView(p.texture_view) + {} + + virtual void draw(); + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual LLRect getRequiredRect(); // Return the height of this object, given the set options. + +private: + LLTextureView* mTextureView; +}; + +void LLGLTexMemBar::draw() +{ + F32 discard_bias = LLViewerTexture::sDesiredDiscardBias; + F32 cache_usage = LLAppViewer::getTextureCache()->getUsage().valueInUnits(); + F32 cache_max_usage = LLAppViewer::getTextureCache()->getMaxUsage().valueInUnits(); + S32 line_height = LLFontGL::getFontMonospace()->getLineHeight(); + S32 v_offset = 0;//(S32)((texture_bar_height + 2.2f) * mTextureView->mNumTextureBars + 2.0f); + F32Bytes total_texture_downloaded = gTotalTextureData; + F32Bytes total_object_downloaded = gTotalObjectData; + U32 total_http_requests = LLAppViewer::getTextureFetch()->getTotalNumHTTPRequests(); + U32 total_active_cached_objects = LLWorld::getInstance()->getNumOfActiveCachedObjects(); + U32 total_objects = gObjectList.getNumObjects(); + F32 x_right = 0.0; + + //---------------------------------------------------------------------------- + LLGLSUIDefault gls_ui; + LLColor4 text_color(1.f, 1.f, 1.f, 0.75f); + LLColor4 color; + + // Gray background using completely magic numbers + gGL.color4f(0.f, 0.f, 0.f, 0.25f); + // const LLRect & rect(getRect()); + // gl_rect_2d(-4, v_offset, rect.mRight - rect.mLeft + 2, v_offset + line_height*4); + + std::string text = ""; + LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*6, + text_color, LLFontGL::LEFT, LLFontGL::TOP); + + LLTrace::Recording& recording = LLViewerStats::instance().getRecording(); + + F64 cacheHits = recording.getSampleCount(LLTextureFetch::sCacheHit); + F64 cacheAttempts = recording.getSampleCount(LLTextureFetch::sCacheAttempt); + + F32 cacheHitRate = (cacheAttempts > 0.0) ? F32((cacheHits / cacheAttempts) * 100.0f) : 0.0f; + + U32 cacheReadLatMin = U32(recording.getMin(LLTextureFetch::sCacheReadLatency).value() * 1000.0f); + U32 cacheReadLatMed = U32(recording.getMean(LLTextureFetch::sCacheReadLatency).value() * 1000.0f); + U32 cacheReadLatMax = U32(recording.getMax(LLTextureFetch::sCacheReadLatency).value() * 1000.0f); + + U32 texDecodeLatMin = U32(recording.getMin(LLTextureFetch::sTexDecodeLatency).value() * 1000.0f); + U32 texDecodeLatMed = U32(recording.getMean(LLTextureFetch::sTexDecodeLatency).value() * 1000.0f); + U32 texDecodeLatMax = U32(recording.getMax(LLTextureFetch::sTexDecodeLatency).value() * 1000.0f); + + U32 texFetchLatMin = U32(recording.getMin(LLTextureFetch::sTexFetchLatency).value() * 1000.0f); + U32 texFetchLatMed = U32(recording.getMean(LLTextureFetch::sTexFetchLatency).value() * 1000.0f); + U32 texFetchLatMax = U32(recording.getMax(LLTextureFetch::sTexFetchLatency).value() * 1000.0f); + + text = llformat("Est. Free: %d MB Sys Free: %d MB FBO: %d MB Bias: %.2f Cache: %.1f/%.1f MB", + (S32)LLViewerTexture::sFreeVRAMMegabytes, + LLMemory::getAvailableMemKB()/1024, + LLRenderTarget::sBytesAllocated/(1024*1024), + discard_bias, + cache_usage, + cache_max_usage); + //, cache_entries, cache_max_entries + + LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*6, + text_color, LLFontGL::LEFT, LLFontGL::TOP); + + U32 cache_read(0U), cache_write(0U), res_wait(0U); + LLAppViewer::getTextureFetch()->getStateStats(&cache_read, &cache_write, &res_wait); + + text = llformat("Net Tot Tex: %.1f MB Tot Obj: %.1f MB #Objs/#Cached: %d/%d Tot Htp: %d Cread: %u Cwrite: %u Rwait: %u", + total_texture_downloaded.valueInUnits(), + total_object_downloaded.valueInUnits(), + total_objects, + total_active_cached_objects, + total_http_requests, + cache_read, + cache_write, + res_wait); + + LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*5, + text_color, LLFontGL::LEFT, LLFontGL::TOP); + + text = llformat("CacheHitRate: %3.2f Read: %d/%d/%d Decode: %d/%d/%d Fetch: %d/%d/%d", + cacheHitRate, + cacheReadLatMin, + cacheReadLatMed, + cacheReadLatMax, + texDecodeLatMin, + texDecodeLatMed, + texDecodeLatMax, + texFetchLatMin, + texFetchLatMed, + texFetchLatMax); + + LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*4, + text_color, LLFontGL::LEFT, LLFontGL::TOP); + + //---------------------------------------------------------------------------- + + text = llformat("Textures: %d Fetch: %d(%d) Pkts:%d(%d) Cache R/W: %d/%d LFS:%d RAW:%d HTP:%d DEC:%d CRE:%d ", + gTextureList.getNumImages(), + LLAppViewer::getTextureFetch()->getNumRequests(), LLAppViewer::getTextureFetch()->getNumDeletes(), + LLAppViewer::getTextureFetch()->mPacketCount, LLAppViewer::getTextureFetch()->mBadPacketCount, + LLAppViewer::getTextureCache()->getNumReads(), LLAppViewer::getTextureCache()->getNumWrites(), + LLLFSThread::sLocal->getPending(), + LLImageRaw::sRawImageCount, + LLAppViewer::getTextureFetch()->getNumHTTPRequests(), + LLAppViewer::getImageDecodeThread()->getPending(), + gTextureList.mCreateTextureList.size()); + + x_right = 550.0; + LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*3, + text_color, LLFontGL::LEFT, LLFontGL::TOP, + LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, &x_right); + + F32Kilobits bandwidth(LLAppViewer::getTextureFetch()->getTextureBandwidth()); + F32Kilobits max_bandwidth(gSavedSettings.getF32("ThrottleBandwidthKBPS")); + color = bandwidth > max_bandwidth ? LLColor4::red : bandwidth > max_bandwidth*.75f ? LLColor4::yellow : text_color; + color[VALPHA] = text_color[VALPHA]; + text = llformat("BW:%.0f/%.0f",bandwidth.value(), max_bandwidth.value()); + LLFontGL::getFontMonospace()->renderUTF8(text, 0, x_right, v_offset + line_height*3, + color, LLFontGL::LEFT, LLFontGL::TOP); + + // Mesh status line + text = llformat("Mesh: Reqs(Tot/Htp/Big): %u/%u/%u Rtr/Err: %u/%u Cread/Cwrite: %u/%u Low/At/High: %d/%d/%d", + LLMeshRepository::sMeshRequestCount, LLMeshRepository::sHTTPRequestCount, LLMeshRepository::sHTTPLargeRequestCount, + LLMeshRepository::sHTTPRetryCount, LLMeshRepository::sHTTPErrorCount, + LLMeshRepository::sCacheReads, LLMeshRepository::sCacheWrites, + LLMeshRepoThread::sRequestLowWater, LLMeshRepoThread::sRequestWaterLevel, LLMeshRepoThread::sRequestHighWater); + LLFontGL::getFontMonospace()->renderUTF8(text, 0, 0, v_offset + line_height*2, + text_color, LLFontGL::LEFT, LLFontGL::TOP); + + // Header for texture table columns + S32 dx1 = 0; + if (LLAppViewer::getTextureFetch()->mDebugPause) + { + LLFontGL::getFontMonospace()->renderUTF8(std::string("!"), 0, title_x1, v_offset + line_height, + text_color, LLFontGL::LEFT, LLFontGL::TOP); + dx1 += 8; + } + if (mTextureView->mFreezeView) + { + LLFontGL::getFontMonospace()->renderUTF8(std::string("*"), 0, title_x1, v_offset + line_height, + text_color, LLFontGL::LEFT, LLFontGL::TOP); + dx1 += 8; + } + if (mTextureView->mOrderFetch) + { + LLFontGL::getFontMonospace()->renderUTF8(title_string1b, 0, title_x1+dx1, v_offset + line_height, + text_color, LLFontGL::LEFT, LLFontGL::TOP); + } + else + { + LLFontGL::getFontMonospace()->renderUTF8(title_string1a, 0, title_x1+dx1, v_offset + line_height, + text_color, LLFontGL::LEFT, LLFontGL::TOP); + } + + LLFontGL::getFontMonospace()->renderUTF8(title_string2, 0, title_x2, v_offset + line_height, + text_color, LLFontGL::LEFT, LLFontGL::TOP); + + LLFontGL::getFontMonospace()->renderUTF8(title_string3, 0, title_x3, v_offset + line_height, + text_color, LLFontGL::LEFT, LLFontGL::TOP); + + LLFontGL::getFontMonospace()->renderUTF8(title_string4, 0, title_x4, v_offset + line_height, + text_color, LLFontGL::LEFT, LLFontGL::TOP); +} + +bool LLGLTexMemBar::handleMouseDown(S32 x, S32 y, MASK mask) +{ + return false; +} + +LLRect LLGLTexMemBar::getRequiredRect() +{ + LLRect rect; + rect.mTop = 78; //LLFontGL::getFontMonospace()->getLineHeight() * 6; + return rect; +} + +//////////////////////////////////////////////////////////////////////////// +class LLGLTexSizeBar +{ +public: + LLGLTexSizeBar(S32 index, S32 left, S32 bottom, S32 right, S32 line_height) + { + mIndex = index ; + mLeft = left ; + mBottom = bottom ; + mRight = right ; + mLineHeight = line_height ; + mTopLoaded = 0 ; + mTopBound = 0 ; + mScale = 1.0f ; + } + + void setTop(S32 loaded, S32 bound, F32 scale) {mTopLoaded = loaded ; mTopBound = bound; mScale = scale ;} + + void draw(); + bool handleHover(S32 x, S32 y, MASK mask, bool set_pick_size) ; + +private: + S32 mIndex ; + S32 mLeft ; + S32 mBottom ; + S32 mRight ; + S32 mTopLoaded ; + S32 mTopBound ; + S32 mLineHeight ; + F32 mScale ; +}; + +bool LLGLTexSizeBar::handleHover(S32 x, S32 y, MASK mask, bool set_pick_size) +{ + if(y > mBottom && (y < mBottom + (S32)(mTopLoaded * mScale) || y < mBottom + (S32)(mTopBound * mScale))) + { + LLImageGL::setCurTexSizebar(mIndex, set_pick_size); + } + return true ; +} +void LLGLTexSizeBar::draw() +{ + LLGLSUIDefault gls_ui; + + if(LLImageGL::sCurTexSizeBar == mIndex) + { + LLColor4 text_color(1.f, 1.f, 1.f, 0.75f); + std::string text; + + text = llformat("%d", mTopLoaded) ; + LLFontGL::getFontMonospace()->renderUTF8(text, 0, mLeft, mBottom + (S32)(mTopLoaded * mScale) + mLineHeight, + text_color, LLFontGL::LEFT, LLFontGL::TOP); + + text = llformat("%d", mTopBound) ; + LLFontGL::getFontMonospace()->renderUTF8(text, 0, (mLeft + mRight) / 2, mBottom + (S32)(mTopBound * mScale) + mLineHeight, + text_color, LLFontGL::LEFT, LLFontGL::TOP); + } + + LLColor4 loaded_color(1.0f, 0.0f, 0.0f, 0.75f); + LLColor4 bound_color(1.0f, 1.0f, 0.0f, 0.75f); + gl_rect_2d(mLeft, mBottom + (S32)(mTopLoaded * mScale), (mLeft + mRight) / 2, mBottom, loaded_color) ; + gl_rect_2d((mLeft + mRight) / 2, mBottom + (S32)(mTopBound * mScale), mRight, mBottom, bound_color) ; +} +//////////////////////////////////////////////////////////////////////////// + +LLTextureView::LLTextureView(const LLTextureView::Params& p) + : LLContainerView(p), + mFreezeView(false), + mOrderFetch(false), + mPrintList(false), + mNumTextureBars(0) +{ + setVisible(false); + + setDisplayChildren(true); + mGLTexMemBar = 0; + mAvatarTexBar = 0; +} + +LLTextureView::~LLTextureView() +{ + // Children all cleaned up by default view destructor. + delete mGLTexMemBar; + mGLTexMemBar = 0; + + delete mAvatarTexBar; + mAvatarTexBar = 0; +} + +typedef std::pair decode_pair_t; +struct compare_decode_pair +{ + bool operator()(const decode_pair_t& a, const decode_pair_t& b) const + { + return a.first > b.first; + } +}; + +struct KillView +{ + void operator()(LLView* viewp) + { + viewp->getParent()->removeChild(viewp); + viewp->die(); + } +}; + +void LLTextureView::draw() +{ + if (!mFreezeView) + { +// LLViewerObject *objectp; +// S32 te; + + for_each(mTextureBars.begin(), mTextureBars.end(), KillView()); + mTextureBars.clear(); + + if (mGLTexMemBar) + { + removeChild(mGLTexMemBar); + mGLTexMemBar->die(); + mGLTexMemBar = 0; + } + + if (mAvatarTexBar) + { + removeChild(mAvatarTexBar); + mAvatarTexBar->die(); + mAvatarTexBar = 0; + } + + typedef std::multiset display_list_t; + display_list_t display_image_list; + + if (mPrintList) + { + LL_INFOS() << "ID\tMEM\tBOOST\tPRI\tWIDTH\tHEIGHT\tDISCARD" << LL_ENDL; + } + + for (LLViewerTextureList::image_priority_list_t::iterator iter = gTextureList.mImageList.begin(); + iter != gTextureList.mImageList.end(); ) + { + LLPointer imagep = *iter++; + if(!imagep->hasFetcher()) + { + continue ; + } + + S32 cur_discard = imagep->getDiscardLevel(); + S32 desired_discard = imagep->mDesiredDiscardLevel; + + if (mPrintList) + { + S32 tex_mem = imagep->hasGLTexture() ? imagep->getTextureMemory().value() : 0 ; + LL_INFOS() << imagep->getID() + << "\t" << tex_mem + << "\t" << imagep->getBoostLevel() + << "\t" << imagep->getMaxVirtualSize() + << "\t" << imagep->getWidth() + << "\t" << imagep->getHeight() + << "\t" << cur_discard + << LL_ENDL; + } + + if (imagep->getID() == LLAppViewer::getTextureFetch()->mDebugID) + { +// static S32 debug_count = 0; +// ++debug_count; // for breakpoints + } + + F32 pri; + if (mOrderFetch) + { + pri = ((F32)imagep->mFetchPriority)/256.f; + } + else + { + pri = imagep->getMaxVirtualSize(); + } + pri = llclamp(pri, 0.0f, HIGH_PRIORITY-1.f); + + if (sDebugImages.find(imagep) != sDebugImages.end()) + { + pri += 4*HIGH_PRIORITY; + } + + if (!mOrderFetch) + { + if (pri < HIGH_PRIORITY && LLSelectMgr::getInstance()) + { + struct f : public LLSelectedTEFunctor + { + LLViewerFetchedTexture* mImage; + f(LLViewerFetchedTexture* image) : mImage(image) {} + virtual bool apply(LLViewerObject* object, S32 te) + { + return (mImage == object->getTEImage(te)); + } + } func(imagep); + const bool firstonly = true; + bool match = LLSelectMgr::getInstance()->getSelection()->applyToTEs(&func, firstonly); + if (match) + { + pri += 3*HIGH_PRIORITY; + } + } + + if (pri < HIGH_PRIORITY && (cur_discard< 0 || desired_discard < cur_discard)) + { + LLSelectNode* hover_node = LLSelectMgr::instance().getHoverNode(); + if (hover_node) + { + LLViewerObject *objectp = hover_node->getObject(); + if (objectp) + { + S32 tex_count = objectp->getNumTEs(); + for (S32 i = 0; i < tex_count; i++) + { + if (imagep == objectp->getTEImage(i)) + { + pri += 2*HIGH_PRIORITY; + break; + } + } + } + } + } + + if (pri > 0.f && pri < HIGH_PRIORITY) + { + if (imagep->mLastPacketTimer.getElapsedTimeF32() < 1.f || + imagep->mFetchDeltaTime < 0.25f) + { + pri += 1*HIGH_PRIORITY; + } + } + } + + if (pri > 0.0f) + { + display_image_list.insert(std::make_pair(pri, imagep)); + } + } + + if (mPrintList) + { + mPrintList = false; + } + + static S32 max_count = 50; + S32 count = 0; + mNumTextureBars = 0 ; + for (display_list_t::iterator iter = display_image_list.begin(); + iter != display_image_list.end(); iter++) + { + LLViewerFetchedTexture* imagep = iter->second; + S32 hilite = 0; + F32 pri = iter->first; + if (pri >= 1 * HIGH_PRIORITY) + { + hilite = (S32)((pri+1) / HIGH_PRIORITY) - 1; + } + if ((hilite || count < max_count-10) && (count < max_count)) + { + if (addBar(imagep, hilite)) + { + count++; + } + } + } + + if (mOrderFetch) + sortChildren(LLTextureBar::sort_fetch()); + else + sortChildren(LLTextureBar::sort()); + + LLGLTexMemBar::Params tmbp; + LLRect tmbr; + tmbp.name("gl texmem bar"); + tmbp.rect(tmbr); + tmbp.follows.flags = FOLLOWS_LEFT|FOLLOWS_TOP; + tmbp.texture_view(this); + mGLTexMemBar = LLUICtrlFactory::create(tmbp); + addChild(mGLTexMemBar); + sendChildToFront(mGLTexMemBar); + + LLAvatarTexBar::Params atbp; + LLRect atbr; + atbp.name("gl avatartex bar"); + atbp.texture_view(this); + atbp.rect(atbr); + mAvatarTexBar = LLUICtrlFactory::create(atbp); + addChild(mAvatarTexBar); + sendChildToFront(mAvatarTexBar); + + reshape(getRect().getWidth(), getRect().getHeight(), true); + + LLUI::popMatrix(); + LLUI::pushMatrix(); + LLUI::translate((F32)getRect().mLeft, (F32)getRect().mBottom); + + for (child_list_const_iter_t child_iter = getChildList()->begin(); + child_iter != getChildList()->end(); ++child_iter) + { + LLView *viewp = *child_iter; + if (viewp->getRect().mBottom < 0) + { + viewp->setVisible(false); + } + } + } + + LLContainerView::draw(); + +} + +bool LLTextureView::addBar(LLViewerFetchedTexture *imagep, S32 hilite) +{ + llassert(imagep); + + LLTextureBar *barp; + LLRect r; + + mNumTextureBars++; + + LLTextureBar::Params tbp; + tbp.name("texture bar"); + tbp.rect(r); + tbp.texture_view(this); + barp = LLUICtrlFactory::create(tbp); + barp->mImagep = imagep; + barp->mHilite = hilite; + + addChild(barp); + mTextureBars.push_back(barp); + + return true; +} + +bool LLTextureView::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if ((mask & (MASK_CONTROL|MASK_SHIFT|MASK_ALT)) == (MASK_ALT|MASK_SHIFT)) + { + mPrintList = true; + return true; + } + if ((mask & (MASK_CONTROL|MASK_SHIFT|MASK_ALT)) == (MASK_CONTROL|MASK_SHIFT)) + { + LLAppViewer::getTextureFetch()->mDebugPause = !LLAppViewer::getTextureFetch()->mDebugPause; + return true; + } + if (mask & MASK_SHIFT) + { + mFreezeView = !mFreezeView; + return true; + } + if (mask & MASK_CONTROL) + { + mOrderFetch = !mOrderFetch; + return true; + } + return LLView::handleMouseDown(x,y,mask); +} + +bool LLTextureView::handleMouseUp(S32 x, S32 y, MASK mask) +{ + return false; +} + +bool LLTextureView::handleKey(KEY key, MASK mask, bool called_from_parent) +{ + return false; +} + + diff --git a/indra/newview/lltextureview.h b/indra/newview/lltextureview.h index 79dbb30d82..77ffe7d809 100644 --- a/indra/newview/lltextureview.h +++ b/indra/newview/lltextureview.h @@ -1,79 +1,79 @@ -/** - * @file lltextureview.h - * @brief LLTextureView class header file - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLTEXTUREVIEW_H -#define LL_LLTEXTUREVIEW_H - -#include "llcontainerview.h" - -class LLViewerFetchedTexture; -class LLTextureBar; -class LLGLTexMemBar; -class LLAvatarTexBar; - -class LLTextureView : public LLContainerView -{ - friend class LLTextureBar; - friend class LLGLTexMemBar; - friend class LLAvatarTexBar; -protected: - LLTextureView(const Params&); - friend class LLUICtrlFactory; -public: - ~LLTextureView(); - - void draw() override; - bool handleMouseDown(S32 x, S32 y, MASK mask) override; - bool handleMouseUp(S32 x, S32 y, MASK mask) override; - bool handleKey(KEY key, MASK mask, bool called_from_parent) override; - - static void addDebugImage(LLViewerFetchedTexture* image) { sDebugImages.insert(image); } - static void removeDebugImage(LLViewerFetchedTexture* image) { sDebugImages.insert(image); } - static void clearDebugImages() { sDebugImages.clear(); } - -private: - bool addBar(LLViewerFetchedTexture *image, S32 hilight = 0); - -private: - bool mFreezeView; - bool mOrderFetch; - bool mPrintList; - - LLTextBox *mInfoTextp; - - std::vector mTextureBars; - U32 mNumTextureBars; - - LLGLTexMemBar* mGLTexMemBar; - LLAvatarTexBar* mAvatarTexBar; -public: - static std::set sDebugImages; -}; - -class LLGLTexSizeBar; - -extern LLTextureView *gTextureView; -#endif // LL_TEXTURE_VIEW_H +/** + * @file lltextureview.h + * @brief LLTextureView class header file + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLTEXTUREVIEW_H +#define LL_LLTEXTUREVIEW_H + +#include "llcontainerview.h" + +class LLViewerFetchedTexture; +class LLTextureBar; +class LLGLTexMemBar; +class LLAvatarTexBar; + +class LLTextureView : public LLContainerView +{ + friend class LLTextureBar; + friend class LLGLTexMemBar; + friend class LLAvatarTexBar; +protected: + LLTextureView(const Params&); + friend class LLUICtrlFactory; +public: + ~LLTextureView(); + + void draw() override; + bool handleMouseDown(S32 x, S32 y, MASK mask) override; + bool handleMouseUp(S32 x, S32 y, MASK mask) override; + bool handleKey(KEY key, MASK mask, bool called_from_parent) override; + + static void addDebugImage(LLViewerFetchedTexture* image) { sDebugImages.insert(image); } + static void removeDebugImage(LLViewerFetchedTexture* image) { sDebugImages.insert(image); } + static void clearDebugImages() { sDebugImages.clear(); } + +private: + bool addBar(LLViewerFetchedTexture *image, S32 hilight = 0); + +private: + bool mFreezeView; + bool mOrderFetch; + bool mPrintList; + + LLTextBox *mInfoTextp; + + std::vector mTextureBars; + U32 mNumTextureBars; + + LLGLTexMemBar* mGLTexMemBar; + LLAvatarTexBar* mAvatarTexBar; +public: + static std::set sDebugImages; +}; + +class LLGLTexSizeBar; + +extern LLTextureView *gTextureView; +#endif // LL_TEXTURE_VIEW_H diff --git a/indra/newview/llthumbnailctrl.cpp b/indra/newview/llthumbnailctrl.cpp index c86ce7ba4d..d26ad2f060 100644 --- a/indra/newview/llthumbnailctrl.cpp +++ b/indra/newview/llthumbnailctrl.cpp @@ -1,271 +1,271 @@ -/** - * @file llthumbnailctrl.cpp - * @brief LLThumbnailCtrl base class - * - * $LicenseInfo:firstyear=2023&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2023, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llthumbnailctrl.h" - -#include "linden_common.h" -#include "llagent.h" -#include "lluictrlfactory.h" -#include "lluuid.h" -#include "lltrans.h" -#include "llviewborder.h" -#include "llviewertexture.h" -#include "llviewertexturelist.h" -#include "llwindow.h" - -static LLDefaultChildRegistry::Register r("thumbnail"); - -LLThumbnailCtrl::Params::Params() -: border("border") -, border_color("border_color") -, fallback_image("fallback_image") -, image_name("image_name") -, border_visible("show_visible", false) -, interactable("interactable", false) -, show_loading("show_loading", true) -{} - -LLThumbnailCtrl::LLThumbnailCtrl(const LLThumbnailCtrl::Params& p) -: LLUICtrl(p) -, mBorderColor(p.border_color()) -, mBorderVisible(p.border_visible()) -, mFallbackImagep(p.fallback_image) -, mInteractable(p.interactable()) -, mShowLoadingPlaceholder(p.show_loading()) -, mInited(false) -, mInitImmediately(true) -{ - mLoadingPlaceholderString = LLTrans::getString("texture_loading"); - - LLRect border_rect = getLocalRect(); - LLViewBorder::Params vbparams(p.border); - vbparams.name("border"); - vbparams.rect(border_rect); - mBorder = LLUICtrlFactory::create (vbparams); - addChild(mBorder); - - if (p.image_name.isProvided()) - { - setValue(p.image_name()); - } -} - -LLThumbnailCtrl::~LLThumbnailCtrl() -{ - mTexturep = nullptr; - mImagep = nullptr; - mFallbackImagep = nullptr; -} - - -void LLThumbnailCtrl::draw() -{ - if (!mInited) - { - initImage(); - } - LLRect draw_rect = getLocalRect(); - - if (mBorderVisible) - { - mBorder->setKeyboardFocusHighlight(hasFocus()); - - gl_rect_2d( draw_rect, mBorderColor.get(), false ); - draw_rect.stretch( -1 ); - } - - // If we're in a focused floater, don't apply the floater's alpha to the texture. - const F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); - if( mTexturep ) - { - if( mTexturep->getComponents() == 4 ) - { - const LLColor4 color(.098f, .098f, .098f); - gl_rect_2d( draw_rect, color, true); - } - - gl_draw_scaled_image( draw_rect.mLeft, draw_rect.mBottom, draw_rect.getWidth(), draw_rect.getHeight(), mTexturep, UI_VERTEX_COLOR % alpha); - - mTexturep->setKnownDrawSize(draw_rect.getWidth(), draw_rect.getHeight()); - } - else if( mImagep.notNull() ) - { - mImagep->draw(draw_rect, UI_VERTEX_COLOR % alpha ); - } - else if (mFallbackImagep.notNull()) - { - if (draw_rect.getWidth() > mFallbackImagep->getWidth() - && draw_rect.getHeight() > mFallbackImagep->getHeight()) - { - S32 img_width = mFallbackImagep->getWidth(); - S32 img_height = mFallbackImagep->getHeight(); - S32 rect_width = draw_rect.getWidth(); - S32 rect_height = draw_rect.getHeight(); - - LLRect fallback_rect; - fallback_rect.mLeft = draw_rect.mLeft + (rect_width - img_width) / 2; - fallback_rect.mRight = fallback_rect.mLeft + img_width; - fallback_rect.mBottom = draw_rect.mBottom + (rect_height - img_height) / 2; - fallback_rect.mTop = fallback_rect.mBottom + img_height; - - mFallbackImagep->draw(fallback_rect, UI_VERTEX_COLOR % alpha); - } - else - { - mFallbackImagep->draw(draw_rect, UI_VERTEX_COLOR % alpha); - } - } - else - { - gl_rect_2d( draw_rect, LLColor4::grey % alpha, true ); - - // Draw X - gl_draw_x( draw_rect, LLColor4::black ); - } - - // Show "Loading..." string on the top left corner while this texture is loading. - // Using the discard level, do not show the string if the texture is almost but not - // fully loaded. - if (mTexturep.notNull() - && mShowLoadingPlaceholder - && !mTexturep->isFullyLoaded()) - { - U32 v_offset = 25; - LLFontGL* font = LLFontGL::getFontSansSerif(); - - // Don't show as loaded if the texture is almost fully loaded (i.e. discard1) unless god - if ((mTexturep->getDiscardLevel() > 1) || gAgent.isGodlike()) - { - font->renderUTF8( - mLoadingPlaceholderString, - 0, - llfloor(draw_rect.mLeft+3), - llfloor(draw_rect.mTop-v_offset), - LLColor4::white, - LLFontGL::LEFT, - LLFontGL::BASELINE, - LLFontGL::DROP_SHADOW); - } - } - - LLUICtrl::draw(); -} - -void LLThumbnailCtrl::setVisible(bool visible) -{ - if (!visible && mInited) - { - unloadImage(); - } - LLUICtrl::setVisible(visible); -} - -void LLThumbnailCtrl::clearTexture() -{ - setValue(LLSD()); - mInited = true; // nothing to do -} - -// virtual -// value might be a string or a UUID -void LLThumbnailCtrl::setValue(const LLSD& value) -{ - LLSD tvalue(value); - if (value.isString() && LLUUID::validate(value.asString())) - { - //RN: support UUIDs masquerading as strings - tvalue = LLSD(LLUUID(value.asString())); - } - - LLUICtrl::setValue(tvalue); - - unloadImage(); - - if (mInitImmediately) - { - initImage(); - } -} - -bool LLThumbnailCtrl::handleHover(S32 x, S32 y, MASK mask) -{ - if (mInteractable && getEnabled()) - { - getWindow()->setCursor(UI_CURSOR_HAND); - return true; - } - return LLUICtrl::handleHover(x, y, mask); -} - -void LLThumbnailCtrl::initImage() -{ - if (mInited) - { - return; - } - mInited = true; - LLSD tvalue = getValue(); - - if (tvalue.isUUID()) - { - mImageAssetID = tvalue.asUUID(); - if (mImageAssetID.notNull()) - { - // Should it support baked textures? - mTexturep = LLViewerTextureManager::getFetchedTexture(mImageAssetID, FTT_DEFAULT, MIPMAP_YES, LLGLTexture::BOOST_THUMBNAIL); - - mTexturep->forceToSaveRawImage(0); - - S32 desired_draw_width = MAX_IMAGE_SIZE; - S32 desired_draw_height = MAX_IMAGE_SIZE; - mTexturep->setKnownDrawSize(desired_draw_width, desired_draw_height); - } - } - else if (tvalue.isString()) - { - mImagep = LLUI::getUIImage(tvalue.asString(), LLGLTexture::BOOST_UI); - if (mImagep) - { - LLViewerFetchedTexture* texture = dynamic_cast(mImagep->getImage().get()); - if (texture) - { - mImageAssetID = texture->getID(); - } - } - } -} - -void LLThumbnailCtrl::unloadImage() -{ - mImageAssetID = LLUUID::null; - mTexturep = nullptr; - mImagep = nullptr; - mInited = false; -} - - +/** + * @file llthumbnailctrl.cpp + * @brief LLThumbnailCtrl base class + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llthumbnailctrl.h" + +#include "linden_common.h" +#include "llagent.h" +#include "lluictrlfactory.h" +#include "lluuid.h" +#include "lltrans.h" +#include "llviewborder.h" +#include "llviewertexture.h" +#include "llviewertexturelist.h" +#include "llwindow.h" + +static LLDefaultChildRegistry::Register r("thumbnail"); + +LLThumbnailCtrl::Params::Params() +: border("border") +, border_color("border_color") +, fallback_image("fallback_image") +, image_name("image_name") +, border_visible("show_visible", false) +, interactable("interactable", false) +, show_loading("show_loading", true) +{} + +LLThumbnailCtrl::LLThumbnailCtrl(const LLThumbnailCtrl::Params& p) +: LLUICtrl(p) +, mBorderColor(p.border_color()) +, mBorderVisible(p.border_visible()) +, mFallbackImagep(p.fallback_image) +, mInteractable(p.interactable()) +, mShowLoadingPlaceholder(p.show_loading()) +, mInited(false) +, mInitImmediately(true) +{ + mLoadingPlaceholderString = LLTrans::getString("texture_loading"); + + LLRect border_rect = getLocalRect(); + LLViewBorder::Params vbparams(p.border); + vbparams.name("border"); + vbparams.rect(border_rect); + mBorder = LLUICtrlFactory::create (vbparams); + addChild(mBorder); + + if (p.image_name.isProvided()) + { + setValue(p.image_name()); + } +} + +LLThumbnailCtrl::~LLThumbnailCtrl() +{ + mTexturep = nullptr; + mImagep = nullptr; + mFallbackImagep = nullptr; +} + + +void LLThumbnailCtrl::draw() +{ + if (!mInited) + { + initImage(); + } + LLRect draw_rect = getLocalRect(); + + if (mBorderVisible) + { + mBorder->setKeyboardFocusHighlight(hasFocus()); + + gl_rect_2d( draw_rect, mBorderColor.get(), false ); + draw_rect.stretch( -1 ); + } + + // If we're in a focused floater, don't apply the floater's alpha to the texture. + const F32 alpha = getTransparencyType() == TT_ACTIVE ? 1.0f : getCurrentTransparency(); + if( mTexturep ) + { + if( mTexturep->getComponents() == 4 ) + { + const LLColor4 color(.098f, .098f, .098f); + gl_rect_2d( draw_rect, color, true); + } + + gl_draw_scaled_image( draw_rect.mLeft, draw_rect.mBottom, draw_rect.getWidth(), draw_rect.getHeight(), mTexturep, UI_VERTEX_COLOR % alpha); + + mTexturep->setKnownDrawSize(draw_rect.getWidth(), draw_rect.getHeight()); + } + else if( mImagep.notNull() ) + { + mImagep->draw(draw_rect, UI_VERTEX_COLOR % alpha ); + } + else if (mFallbackImagep.notNull()) + { + if (draw_rect.getWidth() > mFallbackImagep->getWidth() + && draw_rect.getHeight() > mFallbackImagep->getHeight()) + { + S32 img_width = mFallbackImagep->getWidth(); + S32 img_height = mFallbackImagep->getHeight(); + S32 rect_width = draw_rect.getWidth(); + S32 rect_height = draw_rect.getHeight(); + + LLRect fallback_rect; + fallback_rect.mLeft = draw_rect.mLeft + (rect_width - img_width) / 2; + fallback_rect.mRight = fallback_rect.mLeft + img_width; + fallback_rect.mBottom = draw_rect.mBottom + (rect_height - img_height) / 2; + fallback_rect.mTop = fallback_rect.mBottom + img_height; + + mFallbackImagep->draw(fallback_rect, UI_VERTEX_COLOR % alpha); + } + else + { + mFallbackImagep->draw(draw_rect, UI_VERTEX_COLOR % alpha); + } + } + else + { + gl_rect_2d( draw_rect, LLColor4::grey % alpha, true ); + + // Draw X + gl_draw_x( draw_rect, LLColor4::black ); + } + + // Show "Loading..." string on the top left corner while this texture is loading. + // Using the discard level, do not show the string if the texture is almost but not + // fully loaded. + if (mTexturep.notNull() + && mShowLoadingPlaceholder + && !mTexturep->isFullyLoaded()) + { + U32 v_offset = 25; + LLFontGL* font = LLFontGL::getFontSansSerif(); + + // Don't show as loaded if the texture is almost fully loaded (i.e. discard1) unless god + if ((mTexturep->getDiscardLevel() > 1) || gAgent.isGodlike()) + { + font->renderUTF8( + mLoadingPlaceholderString, + 0, + llfloor(draw_rect.mLeft+3), + llfloor(draw_rect.mTop-v_offset), + LLColor4::white, + LLFontGL::LEFT, + LLFontGL::BASELINE, + LLFontGL::DROP_SHADOW); + } + } + + LLUICtrl::draw(); +} + +void LLThumbnailCtrl::setVisible(bool visible) +{ + if (!visible && mInited) + { + unloadImage(); + } + LLUICtrl::setVisible(visible); +} + +void LLThumbnailCtrl::clearTexture() +{ + setValue(LLSD()); + mInited = true; // nothing to do +} + +// virtual +// value might be a string or a UUID +void LLThumbnailCtrl::setValue(const LLSD& value) +{ + LLSD tvalue(value); + if (value.isString() && LLUUID::validate(value.asString())) + { + //RN: support UUIDs masquerading as strings + tvalue = LLSD(LLUUID(value.asString())); + } + + LLUICtrl::setValue(tvalue); + + unloadImage(); + + if (mInitImmediately) + { + initImage(); + } +} + +bool LLThumbnailCtrl::handleHover(S32 x, S32 y, MASK mask) +{ + if (mInteractable && getEnabled()) + { + getWindow()->setCursor(UI_CURSOR_HAND); + return true; + } + return LLUICtrl::handleHover(x, y, mask); +} + +void LLThumbnailCtrl::initImage() +{ + if (mInited) + { + return; + } + mInited = true; + LLSD tvalue = getValue(); + + if (tvalue.isUUID()) + { + mImageAssetID = tvalue.asUUID(); + if (mImageAssetID.notNull()) + { + // Should it support baked textures? + mTexturep = LLViewerTextureManager::getFetchedTexture(mImageAssetID, FTT_DEFAULT, MIPMAP_YES, LLGLTexture::BOOST_THUMBNAIL); + + mTexturep->forceToSaveRawImage(0); + + S32 desired_draw_width = MAX_IMAGE_SIZE; + S32 desired_draw_height = MAX_IMAGE_SIZE; + mTexturep->setKnownDrawSize(desired_draw_width, desired_draw_height); + } + } + else if (tvalue.isString()) + { + mImagep = LLUI::getUIImage(tvalue.asString(), LLGLTexture::BOOST_UI); + if (mImagep) + { + LLViewerFetchedTexture* texture = dynamic_cast(mImagep->getImage().get()); + if (texture) + { + mImageAssetID = texture->getID(); + } + } + } +} + +void LLThumbnailCtrl::unloadImage() +{ + mImageAssetID = LLUUID::null; + mTexturep = nullptr; + mImagep = nullptr; + mInited = false; +} + + diff --git a/indra/newview/llthumbnailctrl.h b/indra/newview/llthumbnailctrl.h index d6285e6897..1927001bfd 100644 --- a/indra/newview/llthumbnailctrl.h +++ b/indra/newview/llthumbnailctrl.h @@ -1,95 +1,95 @@ -/** - * @file llthumbnailctrl.h - * @brief LLThumbnailCtrl base class - * - * $LicenseInfo:firstyear=2023&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2023 Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLTHUMBNAILCTRL_H -#define LL_LLTHUMBNAILCTRL_H - -#include "llui.h" -#include "lluictrl.h" -#include "llviewborder.h" // for params - -class LLUICtrlFactory; -class LLUUID; -class LLViewerFetchedTexture; - -// -// Classes -// - -// -class LLThumbnailCtrl -: public LLUICtrl -{ -public: - struct Params : public LLInitParam::Block - { - Optional border; - Optional border_color; - Optional image_name; - Optional fallback_image; - Optional border_visible; - Optional interactable; - Optional show_loading; - - Params(); - }; -protected: - LLThumbnailCtrl(const Params&); - friend class LLUICtrlFactory; - -public: - virtual ~LLThumbnailCtrl(); - - virtual void draw() override; - void setVisible(bool visible) override; - - virtual void setValue(const LLSD& value ) override; - void setInitImmediately(bool val) { mInitImmediately = val; } - void clearTexture(); - - virtual bool handleHover(S32 x, S32 y, MASK mask) override; - -protected: - void initImage(); - void unloadImage(); - -private: - bool mBorderVisible; - bool mInteractable; - bool mShowLoadingPlaceholder; - bool mInited; - bool mInitImmediately; - std::string mLoadingPlaceholderString; - LLUUID mImageAssetID; - LLViewBorder* mBorder; - LLUIColor mBorderColor; - - LLPointer mTexturep; - LLPointer mImagep; - LLPointer mFallbackImagep; -}; - -#endif +/** + * @file llthumbnailctrl.h + * @brief LLThumbnailCtrl base class + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023 Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLTHUMBNAILCTRL_H +#define LL_LLTHUMBNAILCTRL_H + +#include "llui.h" +#include "lluictrl.h" +#include "llviewborder.h" // for params + +class LLUICtrlFactory; +class LLUUID; +class LLViewerFetchedTexture; + +// +// Classes +// + +// +class LLThumbnailCtrl +: public LLUICtrl +{ +public: + struct Params : public LLInitParam::Block + { + Optional border; + Optional border_color; + Optional image_name; + Optional fallback_image; + Optional border_visible; + Optional interactable; + Optional show_loading; + + Params(); + }; +protected: + LLThumbnailCtrl(const Params&); + friend class LLUICtrlFactory; + +public: + virtual ~LLThumbnailCtrl(); + + virtual void draw() override; + void setVisible(bool visible) override; + + virtual void setValue(const LLSD& value ) override; + void setInitImmediately(bool val) { mInitImmediately = val; } + void clearTexture(); + + virtual bool handleHover(S32 x, S32 y, MASK mask) override; + +protected: + void initImage(); + void unloadImage(); + +private: + bool mBorderVisible; + bool mInteractable; + bool mShowLoadingPlaceholder; + bool mInited; + bool mInitImmediately; + std::string mLoadingPlaceholderString; + LLUUID mImageAssetID; + LLViewBorder* mBorder; + LLUIColor mBorderColor; + + LLPointer mTexturep; + LLPointer mImagep; + LLPointer mFallbackImagep; +}; + +#endif diff --git a/indra/newview/lltoast.cpp b/indra/newview/lltoast.cpp index b99514e742..638a01a080 100644 --- a/indra/newview/lltoast.cpp +++ b/indra/newview/lltoast.cpp @@ -1,633 +1,633 @@ -/** - * @file lltoast.cpp - * @brief This class implements a placeholder for any notification panel. - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" // must be first include - -#include "lltoast.h" - -#include "llbutton.h" -#include "llfocusmgr.h" -#include "llnotifications.h" -#include "llviewercontrol.h" - -using namespace LLNotificationsUI; -std::list LLToast::sModalToastsList; - -//-------------------------------------------------------------------------- -LLToastLifeTimer::LLToastLifeTimer(LLToast* toast, F32 period) - : mToast(toast), - LLEventTimer(period) -{ -} - -/*virtual*/ -bool LLToastLifeTimer::tick() -{ - if (mEventTimer.hasExpired()) - { - mToast->expire(); - } - return false; -} - -void LLToastLifeTimer::stop() -{ - mEventTimer.stop(); -} - -void LLToastLifeTimer::start() -{ - mEventTimer.start(); -} - -void LLToastLifeTimer::restart() -{ - mEventTimer.reset(); -} - -bool LLToastLifeTimer::getStarted() -{ - return mEventTimer.getStarted(); -} - -void LLToastLifeTimer::setPeriod(F32 period) -{ - mPeriod = period; -} - -F32 LLToastLifeTimer::getRemainingTimeF32() -{ - F32 et = mEventTimer.getElapsedTimeF32(); - if (!getStarted() || et > mPeriod) return 0.0f; - return mPeriod - et; -} - -//-------------------------------------------------------------------------- -LLToast::Params::Params() -: can_fade("can_fade", true), - can_be_stored("can_be_stored", true), - is_modal("is_modal", false), - is_tip("is_tip", false), - enable_hide_btn("enable_hide_btn", true), - force_show("force_show", false), - force_store("force_store", false), - fading_time_secs("fading_time_secs", gSavedSettings.getS32("ToastFadingTime")), - lifetime_secs("lifetime_secs", gSavedSettings.getS32("NotificationToastLifeTime")) -{}; - -LLToast::LLToast(const LLToast::Params& p) -: LLModalDialog(LLSD(), p.is_modal), - mToastLifetime(p.lifetime_secs), - mToastFadingTime(p.fading_time_secs), - mNotificationID(p.notif_id), - mSessionID(p.session_id), - mCanFade(p.can_fade), - mCanBeStored(p.can_be_stored), - mHideBtnEnabled(p.enable_hide_btn), - mHideBtn(NULL), - mPanel(NULL), - mNotification(p.notification), - mIsHidden(false), - mHideBtnPressed(false), - mIsTip(p.is_tip), - mWrapperPanel(NULL), - mIsFading(false), - mIsHovered(false) -{ - mTimer.reset(new LLToastLifeTimer(this, p.lifetime_secs)); - - buildFromFile("panel_toast.xml"); - - setCanDrag(false); - - mWrapperPanel = getChild("wrapper_panel"); - - setBackgroundOpaque(true); // *TODO: obsolete - updateTransparency(); - - if(p.panel()) - { - insertPanel(p.panel); - } - - if(mHideBtnEnabled) - { - mHideBtn = getChild("hide_btn"); - mHideBtn->setClickedCallback(boost::bind(&LLToast::hide,this)); - } - - // init callbacks if present - if(!p.on_delete_toast().empty()) - { - mOnDeleteToastSignal.connect(p.on_delete_toast()); - } - - if (isModal()) - { - sModalToastsList.push_front(this); - } -} - -void LLToast::reshape(S32 width, S32 height, bool called_from_parent) -{ - // We shouldn't use reshape from LLModalDialog since it changes toasts position. - // Toasts position should be controlled only by toast screen channel, see LLScreenChannelBase. - // see EXT-8044 - LLFloater::reshape(width, height, called_from_parent); -} - -//-------------------------------------------------------------------------- -bool LLToast::postBuild() -{ - if(!mCanFade) - { - mTimer->stop(); - } - - return true; -} - -//-------------------------------------------------------------------------- -void LLToast::setHideButtonEnabled(bool enabled) -{ - if(mHideBtn) - mHideBtn->setEnabled(enabled); -} - -//-------------------------------------------------------------------------- -LLToast::~LLToast() -{ - if(LLApp::isQuitting()) - { - mOnFadeSignal.disconnect_all_slots(); - mOnDeleteToastSignal.disconnect_all_slots(); - mOnToastDestroyedSignal.disconnect_all_slots(); - mOnToastHoverSignal.disconnect_all_slots(); - mToastMouseEnterSignal.disconnect_all_slots(); - mToastMouseLeaveSignal.disconnect_all_slots(); - } - else - { - mOnToastDestroyedSignal(this); - } - - if (isModal()) - { - std::list::iterator iter = std::find(sModalToastsList.begin(), sModalToastsList.end(), this); - if (iter != sModalToastsList.end()) - { - sModalToastsList.erase(iter); - } - } -} - -//-------------------------------------------------------------------------- -void LLToast::hide() -{ - if (!mIsHidden) - { - setVisible(false); - setFading(false); - mTimer->stop(); - mIsHidden = true; - mOnFadeSignal(this); - } -} - -/*virtual*/ -void LLToast::setFocus(bool b) -{ - if (b - && !hasFocus() - && mPanel - && mWrapperPanel - && !mWrapperPanel->getChildList()->empty()) - { - LLModalDialog::setFocus(true); - // mostly for buttons - mPanel->setFocus(true); - } - else - { - LLModalDialog::setFocus(b); - } -} - -void LLToast::onFocusLost() -{ - if(mWrapperPanel && !isBackgroundVisible()) - { - // Lets make wrapper panel behave like a floater - updateTransparency(); - } -} - -void LLToast::onFocusReceived() -{ - if(mWrapperPanel && !isBackgroundVisible()) - { - // Lets make wrapper panel behave like a floater - updateTransparency(); - } -} - -void LLToast::setLifetime(S32 seconds) -{ - mToastLifetime = seconds; -} - -void LLToast::setFadingTime(S32 seconds) -{ - mToastFadingTime = seconds; -} - -void LLToast::closeToast() -{ - mOnDeleteToastSignal(this); - - setSoundFlags(SILENT); - - closeFloater(); -} - -S32 LLToast::getTopPad() -{ - if(mWrapperPanel) - { - return getRect().getHeight() - mWrapperPanel->getRect().getHeight(); - } - return 0; -} - -S32 LLToast::getRightPad() -{ - if(mWrapperPanel) - { - return getRect().getWidth() - mWrapperPanel->getRect().getWidth(); - } - return 0; -} - -//-------------------------------------------------------------------------- -void LLToast::setCanFade(bool can_fade) -{ - mCanFade = can_fade; - if(!mCanFade) - { - mTimer->stop(); - } -} - -//-------------------------------------------------------------------------- -void LLToast::expire() -{ - if (mCanFade) - { - if (mIsFading) - { - // Fade timer expired. Time to hide. - hide(); - } - else - { - // "Life" time has ended. Time to fade. - setFading(true); - mTimer->restart(); - } - } -} - -void LLToast::setFading(bool transparent) -{ - mIsFading = transparent; - updateTransparency(); - - if (transparent) - { - mTimer->setPeriod(mToastFadingTime); - } - else - { - mTimer->setPeriod(mToastLifetime); - } -} - -F32 LLToast::getTimeLeftToLive() -{ - F32 time_to_live = mTimer->getRemainingTimeF32(); - - if (!mIsFading) - { - time_to_live += mToastFadingTime; - } - - return time_to_live; -} -//-------------------------------------------------------------------------- - -void LLToast::reshapeToPanel() -{ - LLPanel* panel = getPanel(); - if(!panel) - return; - - LLRect panel_rect = panel->getLocalRect(); - panel->setShape(panel_rect); - - LLRect toast_rect = getRect(); - - toast_rect.setLeftTopAndSize(toast_rect.mLeft, toast_rect.mTop, - panel_rect.getWidth() + getRightPad(), panel_rect.getHeight() + getTopPad()); - setShape(toast_rect); -} - -void LLToast::insertPanel(LLPanel* panel) -{ - mPanel = panel; - mWrapperPanel->addChild(panel); - reshapeToPanel(); -} - -//-------------------------------------------------------------------------- -void LLToast::draw() -{ - LLFloater::draw(); - - if(!isBackgroundVisible()) - { - // Floater background is invisible, lets make wrapper panel look like a - // floater - draw shadow. - drawShadow(mWrapperPanel); - - // Shadow will probably overlap close button, lets redraw the button - if(mHideBtn) - { - drawChild(mHideBtn); - } - } -} - -//-------------------------------------------------------------------------- -void LLToast::setVisible(bool show) -{ - if(mIsHidden) - { - // this toast is invisible after fade until its ScreenChannel will allow it - // - // (EXT-1849) according to this bug a toast can be resurrected from - // invisible state if it faded during a teleportation - // then it fades a second time and causes a crash - return; - } - - if (show && getVisible()) - { - return; - } - - if(show) - { - if(!mTimer->getStarted() && mCanFade) - { - mTimer->start(); - } - } - else - { - //hide "hide" button in case toast was hidden without mouse_leave - if(mHideBtn) - mHideBtn->setVisible(show); - } - LLFloater::setVisible(show); - if (mPanel - && !mPanel->isDead() - && mWrapperPanel - && !mWrapperPanel->getChildList()->empty() - // LLInspectToast can take over, but LLToast still appears to act like a data storage - && mPanel->getParent() == mWrapperPanel - ) - { - mPanel->setVisible(show); - } -} - -void LLToast::updateHoveredState() -{ - S32 x, y; - LLUI::getInstance()->getMousePositionScreen(&x, &y); - - LLRect panel_rc = mWrapperPanel->calcScreenRect(); - LLRect button_rc; - if(mHideBtn) - { - button_rc = mHideBtn->calcScreenRect(); - } - - if (!panel_rc.pointInRect(x, y) && !button_rc.pointInRect(x, y)) - { - // mouse is not over this toast - mIsHovered = false; - } - else - { - bool is_overlapped_by_other_floater = false; - - const child_list_t* child_list = gFloaterView->getChildList(); - - // find this toast in gFloaterView child list to check whether any floater - // with higher Z-order is visible under the mouse pointer overlapping this toast - child_list_const_reverse_iter_t r_iter = std::find(child_list->rbegin(), child_list->rend(), this); - if (r_iter != child_list->rend()) - { - // skip this toast and proceed to views above in Z-order - for (++r_iter; r_iter != child_list->rend(); ++r_iter) - { - LLView* view = *r_iter; - is_overlapped_by_other_floater = view->isInVisibleChain() && view->calcScreenRect().pointInRect(x, y); - if (is_overlapped_by_other_floater) - { - break; - } - } - } - - mIsHovered = !is_overlapped_by_other_floater; - } - - LLToastLifeTimer* timer = getTimer(); - - if (timer) - { - // Started timer means the mouse had left the toast previously. - // If toast is hovered in the current frame we should handle - // a mouse enter event. - if(timer->getStarted() && mIsHovered) - { - mOnToastHoverSignal(this, MOUSE_ENTER); - - updateTransparency(); - - //toasts fading is management by Screen Channel - - sendChildToFront(mHideBtn); - if(mHideBtn && mHideBtn->getEnabled()) - { - mHideBtn->setVisible(true); - } - - mToastMouseEnterSignal(this, getValue()); - } - // Stopped timer means the mouse had entered the toast previously. - // If the toast is not hovered in the current frame we should handle - // a mouse leave event. - else if(!timer->getStarted() && !mIsHovered) - { - mOnToastHoverSignal(this, MOUSE_LEAVE); - - updateTransparency(); - - //toasts fading is management by Screen Channel - - if(mHideBtn && mHideBtn->getEnabled()) - { - if( mHideBtnPressed ) - { - mHideBtnPressed = false; - return; - } - mHideBtn->setVisible(false); - } - - mToastMouseLeaveSignal(this, getValue()); - } - } -} - -void LLToast::setBackgroundOpaque(bool b) -{ - if(mWrapperPanel && !isBackgroundVisible()) - { - mWrapperPanel->setBackgroundOpaque(b); - } - else - { - LLModalDialog::setBackgroundOpaque(b); - } -} - -void LLToast::updateTransparency() -{ - ETypeTransparency transparency_type; - - if (mCanFade) - { - // Notification toasts (including IM/chat toasts) change their transparency on hover. - if (isHovered()) - { - transparency_type = TT_ACTIVE; - } - else - { - transparency_type = mIsFading ? TT_FADING : TT_INACTIVE; - } - } - else - { - // Transparency of alert toasts depends on focus. - transparency_type = hasFocus() ? TT_ACTIVE : TT_INACTIVE; - } - - LLFloater::updateTransparency(transparency_type); -} - -void LLNotificationsUI::LLToast::stopTimer() -{ - if(mCanFade) - { - setFading(false); - mTimer->stop(); - } -} - -void LLNotificationsUI::LLToast::startTimer() -{ - if(mCanFade) - { - setFading(false); - mTimer->start(); - } -} - -//-------------------------------------------------------------------------- - -bool LLToast::handleMouseDown(S32 x, S32 y, MASK mask) -{ - if(mHideBtn && mHideBtn->getEnabled()) - { - mHideBtnPressed = mHideBtn->getRect().pointInRect(x, y); - } - - return LLModalDialog::handleMouseDown(x, y, mask); -} - -//-------------------------------------------------------------------------- -bool LLToast::isNotificationValid() -{ - if(mNotification) - { - return !mNotification->isCancelled(); - } - return false; -} - -//-------------------------------------------------------------------------- - -S32 LLToast::notifyParent(const LLSD& info) -{ - if (info.has("action") && "hide_toast" == info["action"].asString()) - { - hide(); - return 1; - } - - return LLModalDialog::notifyParent(info); -} - -//static -void LLToast::updateClass() -{ - for (auto& toast : LLInstanceTracker::instance_snapshot()) - { - toast.updateHoveredState(); - } -} - -// static -void LLToast::cleanupToasts() -{ - LLInstanceTracker::instance_snapshot().deleteAll(); -} - +/** + * @file lltoast.cpp + * @brief This class implements a placeholder for any notification panel. + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" // must be first include + +#include "lltoast.h" + +#include "llbutton.h" +#include "llfocusmgr.h" +#include "llnotifications.h" +#include "llviewercontrol.h" + +using namespace LLNotificationsUI; +std::list LLToast::sModalToastsList; + +//-------------------------------------------------------------------------- +LLToastLifeTimer::LLToastLifeTimer(LLToast* toast, F32 period) + : mToast(toast), + LLEventTimer(period) +{ +} + +/*virtual*/ +bool LLToastLifeTimer::tick() +{ + if (mEventTimer.hasExpired()) + { + mToast->expire(); + } + return false; +} + +void LLToastLifeTimer::stop() +{ + mEventTimer.stop(); +} + +void LLToastLifeTimer::start() +{ + mEventTimer.start(); +} + +void LLToastLifeTimer::restart() +{ + mEventTimer.reset(); +} + +bool LLToastLifeTimer::getStarted() +{ + return mEventTimer.getStarted(); +} + +void LLToastLifeTimer::setPeriod(F32 period) +{ + mPeriod = period; +} + +F32 LLToastLifeTimer::getRemainingTimeF32() +{ + F32 et = mEventTimer.getElapsedTimeF32(); + if (!getStarted() || et > mPeriod) return 0.0f; + return mPeriod - et; +} + +//-------------------------------------------------------------------------- +LLToast::Params::Params() +: can_fade("can_fade", true), + can_be_stored("can_be_stored", true), + is_modal("is_modal", false), + is_tip("is_tip", false), + enable_hide_btn("enable_hide_btn", true), + force_show("force_show", false), + force_store("force_store", false), + fading_time_secs("fading_time_secs", gSavedSettings.getS32("ToastFadingTime")), + lifetime_secs("lifetime_secs", gSavedSettings.getS32("NotificationToastLifeTime")) +{}; + +LLToast::LLToast(const LLToast::Params& p) +: LLModalDialog(LLSD(), p.is_modal), + mToastLifetime(p.lifetime_secs), + mToastFadingTime(p.fading_time_secs), + mNotificationID(p.notif_id), + mSessionID(p.session_id), + mCanFade(p.can_fade), + mCanBeStored(p.can_be_stored), + mHideBtnEnabled(p.enable_hide_btn), + mHideBtn(NULL), + mPanel(NULL), + mNotification(p.notification), + mIsHidden(false), + mHideBtnPressed(false), + mIsTip(p.is_tip), + mWrapperPanel(NULL), + mIsFading(false), + mIsHovered(false) +{ + mTimer.reset(new LLToastLifeTimer(this, p.lifetime_secs)); + + buildFromFile("panel_toast.xml"); + + setCanDrag(false); + + mWrapperPanel = getChild("wrapper_panel"); + + setBackgroundOpaque(true); // *TODO: obsolete + updateTransparency(); + + if(p.panel()) + { + insertPanel(p.panel); + } + + if(mHideBtnEnabled) + { + mHideBtn = getChild("hide_btn"); + mHideBtn->setClickedCallback(boost::bind(&LLToast::hide,this)); + } + + // init callbacks if present + if(!p.on_delete_toast().empty()) + { + mOnDeleteToastSignal.connect(p.on_delete_toast()); + } + + if (isModal()) + { + sModalToastsList.push_front(this); + } +} + +void LLToast::reshape(S32 width, S32 height, bool called_from_parent) +{ + // We shouldn't use reshape from LLModalDialog since it changes toasts position. + // Toasts position should be controlled only by toast screen channel, see LLScreenChannelBase. + // see EXT-8044 + LLFloater::reshape(width, height, called_from_parent); +} + +//-------------------------------------------------------------------------- +bool LLToast::postBuild() +{ + if(!mCanFade) + { + mTimer->stop(); + } + + return true; +} + +//-------------------------------------------------------------------------- +void LLToast::setHideButtonEnabled(bool enabled) +{ + if(mHideBtn) + mHideBtn->setEnabled(enabled); +} + +//-------------------------------------------------------------------------- +LLToast::~LLToast() +{ + if(LLApp::isQuitting()) + { + mOnFadeSignal.disconnect_all_slots(); + mOnDeleteToastSignal.disconnect_all_slots(); + mOnToastDestroyedSignal.disconnect_all_slots(); + mOnToastHoverSignal.disconnect_all_slots(); + mToastMouseEnterSignal.disconnect_all_slots(); + mToastMouseLeaveSignal.disconnect_all_slots(); + } + else + { + mOnToastDestroyedSignal(this); + } + + if (isModal()) + { + std::list::iterator iter = std::find(sModalToastsList.begin(), sModalToastsList.end(), this); + if (iter != sModalToastsList.end()) + { + sModalToastsList.erase(iter); + } + } +} + +//-------------------------------------------------------------------------- +void LLToast::hide() +{ + if (!mIsHidden) + { + setVisible(false); + setFading(false); + mTimer->stop(); + mIsHidden = true; + mOnFadeSignal(this); + } +} + +/*virtual*/ +void LLToast::setFocus(bool b) +{ + if (b + && !hasFocus() + && mPanel + && mWrapperPanel + && !mWrapperPanel->getChildList()->empty()) + { + LLModalDialog::setFocus(true); + // mostly for buttons + mPanel->setFocus(true); + } + else + { + LLModalDialog::setFocus(b); + } +} + +void LLToast::onFocusLost() +{ + if(mWrapperPanel && !isBackgroundVisible()) + { + // Lets make wrapper panel behave like a floater + updateTransparency(); + } +} + +void LLToast::onFocusReceived() +{ + if(mWrapperPanel && !isBackgroundVisible()) + { + // Lets make wrapper panel behave like a floater + updateTransparency(); + } +} + +void LLToast::setLifetime(S32 seconds) +{ + mToastLifetime = seconds; +} + +void LLToast::setFadingTime(S32 seconds) +{ + mToastFadingTime = seconds; +} + +void LLToast::closeToast() +{ + mOnDeleteToastSignal(this); + + setSoundFlags(SILENT); + + closeFloater(); +} + +S32 LLToast::getTopPad() +{ + if(mWrapperPanel) + { + return getRect().getHeight() - mWrapperPanel->getRect().getHeight(); + } + return 0; +} + +S32 LLToast::getRightPad() +{ + if(mWrapperPanel) + { + return getRect().getWidth() - mWrapperPanel->getRect().getWidth(); + } + return 0; +} + +//-------------------------------------------------------------------------- +void LLToast::setCanFade(bool can_fade) +{ + mCanFade = can_fade; + if(!mCanFade) + { + mTimer->stop(); + } +} + +//-------------------------------------------------------------------------- +void LLToast::expire() +{ + if (mCanFade) + { + if (mIsFading) + { + // Fade timer expired. Time to hide. + hide(); + } + else + { + // "Life" time has ended. Time to fade. + setFading(true); + mTimer->restart(); + } + } +} + +void LLToast::setFading(bool transparent) +{ + mIsFading = transparent; + updateTransparency(); + + if (transparent) + { + mTimer->setPeriod(mToastFadingTime); + } + else + { + mTimer->setPeriod(mToastLifetime); + } +} + +F32 LLToast::getTimeLeftToLive() +{ + F32 time_to_live = mTimer->getRemainingTimeF32(); + + if (!mIsFading) + { + time_to_live += mToastFadingTime; + } + + return time_to_live; +} +//-------------------------------------------------------------------------- + +void LLToast::reshapeToPanel() +{ + LLPanel* panel = getPanel(); + if(!panel) + return; + + LLRect panel_rect = panel->getLocalRect(); + panel->setShape(panel_rect); + + LLRect toast_rect = getRect(); + + toast_rect.setLeftTopAndSize(toast_rect.mLeft, toast_rect.mTop, + panel_rect.getWidth() + getRightPad(), panel_rect.getHeight() + getTopPad()); + setShape(toast_rect); +} + +void LLToast::insertPanel(LLPanel* panel) +{ + mPanel = panel; + mWrapperPanel->addChild(panel); + reshapeToPanel(); +} + +//-------------------------------------------------------------------------- +void LLToast::draw() +{ + LLFloater::draw(); + + if(!isBackgroundVisible()) + { + // Floater background is invisible, lets make wrapper panel look like a + // floater - draw shadow. + drawShadow(mWrapperPanel); + + // Shadow will probably overlap close button, lets redraw the button + if(mHideBtn) + { + drawChild(mHideBtn); + } + } +} + +//-------------------------------------------------------------------------- +void LLToast::setVisible(bool show) +{ + if(mIsHidden) + { + // this toast is invisible after fade until its ScreenChannel will allow it + // + // (EXT-1849) according to this bug a toast can be resurrected from + // invisible state if it faded during a teleportation + // then it fades a second time and causes a crash + return; + } + + if (show && getVisible()) + { + return; + } + + if(show) + { + if(!mTimer->getStarted() && mCanFade) + { + mTimer->start(); + } + } + else + { + //hide "hide" button in case toast was hidden without mouse_leave + if(mHideBtn) + mHideBtn->setVisible(show); + } + LLFloater::setVisible(show); + if (mPanel + && !mPanel->isDead() + && mWrapperPanel + && !mWrapperPanel->getChildList()->empty() + // LLInspectToast can take over, but LLToast still appears to act like a data storage + && mPanel->getParent() == mWrapperPanel + ) + { + mPanel->setVisible(show); + } +} + +void LLToast::updateHoveredState() +{ + S32 x, y; + LLUI::getInstance()->getMousePositionScreen(&x, &y); + + LLRect panel_rc = mWrapperPanel->calcScreenRect(); + LLRect button_rc; + if(mHideBtn) + { + button_rc = mHideBtn->calcScreenRect(); + } + + if (!panel_rc.pointInRect(x, y) && !button_rc.pointInRect(x, y)) + { + // mouse is not over this toast + mIsHovered = false; + } + else + { + bool is_overlapped_by_other_floater = false; + + const child_list_t* child_list = gFloaterView->getChildList(); + + // find this toast in gFloaterView child list to check whether any floater + // with higher Z-order is visible under the mouse pointer overlapping this toast + child_list_const_reverse_iter_t r_iter = std::find(child_list->rbegin(), child_list->rend(), this); + if (r_iter != child_list->rend()) + { + // skip this toast and proceed to views above in Z-order + for (++r_iter; r_iter != child_list->rend(); ++r_iter) + { + LLView* view = *r_iter; + is_overlapped_by_other_floater = view->isInVisibleChain() && view->calcScreenRect().pointInRect(x, y); + if (is_overlapped_by_other_floater) + { + break; + } + } + } + + mIsHovered = !is_overlapped_by_other_floater; + } + + LLToastLifeTimer* timer = getTimer(); + + if (timer) + { + // Started timer means the mouse had left the toast previously. + // If toast is hovered in the current frame we should handle + // a mouse enter event. + if(timer->getStarted() && mIsHovered) + { + mOnToastHoverSignal(this, MOUSE_ENTER); + + updateTransparency(); + + //toasts fading is management by Screen Channel + + sendChildToFront(mHideBtn); + if(mHideBtn && mHideBtn->getEnabled()) + { + mHideBtn->setVisible(true); + } + + mToastMouseEnterSignal(this, getValue()); + } + // Stopped timer means the mouse had entered the toast previously. + // If the toast is not hovered in the current frame we should handle + // a mouse leave event. + else if(!timer->getStarted() && !mIsHovered) + { + mOnToastHoverSignal(this, MOUSE_LEAVE); + + updateTransparency(); + + //toasts fading is management by Screen Channel + + if(mHideBtn && mHideBtn->getEnabled()) + { + if( mHideBtnPressed ) + { + mHideBtnPressed = false; + return; + } + mHideBtn->setVisible(false); + } + + mToastMouseLeaveSignal(this, getValue()); + } + } +} + +void LLToast::setBackgroundOpaque(bool b) +{ + if(mWrapperPanel && !isBackgroundVisible()) + { + mWrapperPanel->setBackgroundOpaque(b); + } + else + { + LLModalDialog::setBackgroundOpaque(b); + } +} + +void LLToast::updateTransparency() +{ + ETypeTransparency transparency_type; + + if (mCanFade) + { + // Notification toasts (including IM/chat toasts) change their transparency on hover. + if (isHovered()) + { + transparency_type = TT_ACTIVE; + } + else + { + transparency_type = mIsFading ? TT_FADING : TT_INACTIVE; + } + } + else + { + // Transparency of alert toasts depends on focus. + transparency_type = hasFocus() ? TT_ACTIVE : TT_INACTIVE; + } + + LLFloater::updateTransparency(transparency_type); +} + +void LLNotificationsUI::LLToast::stopTimer() +{ + if(mCanFade) + { + setFading(false); + mTimer->stop(); + } +} + +void LLNotificationsUI::LLToast::startTimer() +{ + if(mCanFade) + { + setFading(false); + mTimer->start(); + } +} + +//-------------------------------------------------------------------------- + +bool LLToast::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if(mHideBtn && mHideBtn->getEnabled()) + { + mHideBtnPressed = mHideBtn->getRect().pointInRect(x, y); + } + + return LLModalDialog::handleMouseDown(x, y, mask); +} + +//-------------------------------------------------------------------------- +bool LLToast::isNotificationValid() +{ + if(mNotification) + { + return !mNotification->isCancelled(); + } + return false; +} + +//-------------------------------------------------------------------------- + +S32 LLToast::notifyParent(const LLSD& info) +{ + if (info.has("action") && "hide_toast" == info["action"].asString()) + { + hide(); + return 1; + } + + return LLModalDialog::notifyParent(info); +} + +//static +void LLToast::updateClass() +{ + for (auto& toast : LLInstanceTracker::instance_snapshot()) + { + toast.updateHoveredState(); + } +} + +// static +void LLToast::cleanupToasts() +{ + LLInstanceTracker::instance_snapshot().deleteAll(); +} + diff --git a/indra/newview/lltoast.h b/indra/newview/lltoast.h index 46aac3859e..cf116bfadf 100644 --- a/indra/newview/lltoast.h +++ b/indra/newview/lltoast.h @@ -1,256 +1,256 @@ -/** - * @file lltoast.h - * @brief This class implements a placeholder for any notification panel. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLTOAST_H -#define LL_LLTOAST_H - -#include "llinstancetracker.h" -#include "llpanel.h" -#include "llmodaldialog.h" -#include "lleventtimer.h" -#include "llnotificationptr.h" - -#include "llviewercontrol.h" -#include "lltexteditor.h" -#include - -#define MOUSE_LEAVE false -#define MOUSE_ENTER true - -namespace LLNotificationsUI -{ - -class LLToast; -/** - * Timer for toasts. - */ -class LLToastLifeTimer: public LLEventTimer -{ -public: - LLToastLifeTimer(LLToast* toast, F32 period); - - /*virtual*/ - bool tick(); - void stop(); - void start(); - void restart(); - bool getStarted(); - void setPeriod(F32 period); - F32 getRemainingTimeF32(); - - LLTimer& getEventTimer() { return mEventTimer;} -private : - LLToast* mToast; -}; - -/** - * Represents toast pop-up. - * This is a parent view for all toast panels. - */ -class LLToast : public LLModalDialog, public LLInstanceTracker -{ - friend class LLToastLifeTimer; -public: - - typedef boost::function toast_callback_t; - typedef boost::signals2::signal toast_signal_t; - typedef boost::signals2::signal toast_hover_check_signal_t; - - struct Params : public LLInitParam::Block - { - Mandatory panel; - Optional notif_id, //notification ID - session_id; //im session ID - Optional notification; - - //NOTE: Life time of a toast (i.e. period of time from the moment toast was shown - //till the moment when toast was hidden) is the sum of lifetime_secs and fading_time_secs. - - Optional lifetime_secs, // Number of seconds while a toast is non-transparent - fading_time_secs; // Number of seconds while a toast is transparent - - - Optional on_delete_toast; - Optional can_fade, - can_be_stored, - enable_hide_btn, - is_modal, - is_tip, - force_show, - force_store; - - - Params(); - }; - - static void updateClass(); - static void cleanupToasts(); - - static bool isAlertToastShown() { return sModalToastsList.size() > 0; } - - LLToast(const LLToast::Params& p); - virtual ~LLToast(); - bool postBuild(); - - /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); - - // Toast handlers - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - - //Fading - - /** Stop lifetime/fading timer */ - virtual void stopTimer(); - - /** Start lifetime/fading timer */ - virtual void startTimer(); - - bool isHovered() { return mIsHovered; } - - // Operating with toasts - // insert a panel to a toast - void insertPanel(LLPanel* panel); - - void reshapeToPanel(); - - // get toast's panel - LLPanel* getPanel() const { return mPanel; } - // enable/disable Toast's Hide button - void setHideButtonEnabled(bool enabled); - // - F32 getTimeLeftToLive(); - // - LLToastLifeTimer* getTimer() { return mTimer.get();} - // - virtual void draw(); - // - virtual void setVisible(bool show); - - /*virtual*/ void setBackgroundOpaque(bool b); - // - virtual void hide(); - - /*virtual*/ void setFocus(bool b); - - /*virtual*/ void onFocusLost(); - - /*virtual*/ void onFocusReceived(); - - void setLifetime(S32 seconds); - - void setFadingTime(S32 seconds); - - void closeToast(); - - /** - * Returns padding between floater top and wrapper_panel top. - * This padding should be taken into account when positioning or reshaping toasts - */ - S32 getTopPad(); - - S32 getRightPad(); - - // get/set Toast's flags or states - // get information whether the notification corresponding to the toast is valid or not - bool isNotificationValid(); - - // get toast's Notification ID - const LLUUID getNotificationID() const { return mNotificationID;} - // get toast's Session ID - const LLUUID getSessionID() const { return mSessionID;} - // - void setCanFade(bool can_fade); - // - void setCanBeStored(bool can_be_stored) { mCanBeStored = can_be_stored; } - // - bool getCanBeStored() { return mCanBeStored; } - // set whether this toast considered as hidden or not - void setIsHidden( bool is_toast_hidden ) { mIsHidden = is_toast_hidden; } - - const LLNotificationPtr& getNotification() const { return mNotification;} - - // Registers signals/callbacks for events - boost::signals2::connection setOnFadeCallback(const toast_signal_t::slot_type& cb) { return mOnFadeSignal.connect(cb); } - boost::signals2::connection setOnToastDestroyedCallback(const toast_signal_t::slot_type& cb) { return mOnToastDestroyedSignal.connect(cb); } - boost::signals2::connection setOnToastHoverCallback(const toast_hover_check_signal_t::slot_type& cb) { return mOnToastHoverSignal.connect(cb); } - - boost::signals2::connection setMouseEnterCallback( const commit_signal_t::slot_type& cb ) { return mToastMouseEnterSignal.connect(cb); }; - boost::signals2::connection setMouseLeaveCallback( const commit_signal_t::slot_type& cb ) { return mToastMouseLeaveSignal.connect(cb); }; - - virtual S32 notifyParent(const LLSD& info); - - LLHandle getHandle() const { return getDerivedHandle(); } - -protected: - void updateTransparency(); - -private: - void updateHoveredState(); - - void expire(); - - void setFading(bool fading); - - LLUUID mNotificationID; - LLUUID mSessionID; - LLNotificationPtr mNotification; - - //LLRootHandle mHandle; - - LLPanel* mWrapperPanel; - - // timer counts a lifetime of a toast - std::unique_ptr mTimer; - - F32 mToastLifetime; // in seconds - F32 mToastFadingTime; // in seconds - - LLPanel* mPanel; - LLButton* mHideBtn; - - LLColor4 mBgColor; - bool mCanFade; - bool mCanBeStored; - bool mHideBtnEnabled; - bool mHideBtnPressed; - bool mIsHidden; // this flag is true when a toast has faded or was hidden with (x) button (EXT-1849) - bool mIsTip; - bool mIsFading; - bool mIsHovered; - - toast_signal_t mOnFadeSignal; - toast_signal_t mOnDeleteToastSignal; - toast_signal_t mOnToastDestroyedSignal; - toast_hover_check_signal_t mOnToastHoverSignal; - - commit_signal_t mToastMouseEnterSignal; - commit_signal_t mToastMouseLeaveSignal; - - static std::list sModalToastsList; -}; - -} -#endif +/** + * @file lltoast.h + * @brief This class implements a placeholder for any notification panel. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLTOAST_H +#define LL_LLTOAST_H + +#include "llinstancetracker.h" +#include "llpanel.h" +#include "llmodaldialog.h" +#include "lleventtimer.h" +#include "llnotificationptr.h" + +#include "llviewercontrol.h" +#include "lltexteditor.h" +#include + +#define MOUSE_LEAVE false +#define MOUSE_ENTER true + +namespace LLNotificationsUI +{ + +class LLToast; +/** + * Timer for toasts. + */ +class LLToastLifeTimer: public LLEventTimer +{ +public: + LLToastLifeTimer(LLToast* toast, F32 period); + + /*virtual*/ + bool tick(); + void stop(); + void start(); + void restart(); + bool getStarted(); + void setPeriod(F32 period); + F32 getRemainingTimeF32(); + + LLTimer& getEventTimer() { return mEventTimer;} +private : + LLToast* mToast; +}; + +/** + * Represents toast pop-up. + * This is a parent view for all toast panels. + */ +class LLToast : public LLModalDialog, public LLInstanceTracker +{ + friend class LLToastLifeTimer; +public: + + typedef boost::function toast_callback_t; + typedef boost::signals2::signal toast_signal_t; + typedef boost::signals2::signal toast_hover_check_signal_t; + + struct Params : public LLInitParam::Block + { + Mandatory panel; + Optional notif_id, //notification ID + session_id; //im session ID + Optional notification; + + //NOTE: Life time of a toast (i.e. period of time from the moment toast was shown + //till the moment when toast was hidden) is the sum of lifetime_secs and fading_time_secs. + + Optional lifetime_secs, // Number of seconds while a toast is non-transparent + fading_time_secs; // Number of seconds while a toast is transparent + + + Optional on_delete_toast; + Optional can_fade, + can_be_stored, + enable_hide_btn, + is_modal, + is_tip, + force_show, + force_store; + + + Params(); + }; + + static void updateClass(); + static void cleanupToasts(); + + static bool isAlertToastShown() { return sModalToastsList.size() > 0; } + + LLToast(const LLToast::Params& p); + virtual ~LLToast(); + bool postBuild(); + + /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); + + // Toast handlers + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + + //Fading + + /** Stop lifetime/fading timer */ + virtual void stopTimer(); + + /** Start lifetime/fading timer */ + virtual void startTimer(); + + bool isHovered() { return mIsHovered; } + + // Operating with toasts + // insert a panel to a toast + void insertPanel(LLPanel* panel); + + void reshapeToPanel(); + + // get toast's panel + LLPanel* getPanel() const { return mPanel; } + // enable/disable Toast's Hide button + void setHideButtonEnabled(bool enabled); + // + F32 getTimeLeftToLive(); + // + LLToastLifeTimer* getTimer() { return mTimer.get();} + // + virtual void draw(); + // + virtual void setVisible(bool show); + + /*virtual*/ void setBackgroundOpaque(bool b); + // + virtual void hide(); + + /*virtual*/ void setFocus(bool b); + + /*virtual*/ void onFocusLost(); + + /*virtual*/ void onFocusReceived(); + + void setLifetime(S32 seconds); + + void setFadingTime(S32 seconds); + + void closeToast(); + + /** + * Returns padding between floater top and wrapper_panel top. + * This padding should be taken into account when positioning or reshaping toasts + */ + S32 getTopPad(); + + S32 getRightPad(); + + // get/set Toast's flags or states + // get information whether the notification corresponding to the toast is valid or not + bool isNotificationValid(); + + // get toast's Notification ID + const LLUUID getNotificationID() const { return mNotificationID;} + // get toast's Session ID + const LLUUID getSessionID() const { return mSessionID;} + // + void setCanFade(bool can_fade); + // + void setCanBeStored(bool can_be_stored) { mCanBeStored = can_be_stored; } + // + bool getCanBeStored() { return mCanBeStored; } + // set whether this toast considered as hidden or not + void setIsHidden( bool is_toast_hidden ) { mIsHidden = is_toast_hidden; } + + const LLNotificationPtr& getNotification() const { return mNotification;} + + // Registers signals/callbacks for events + boost::signals2::connection setOnFadeCallback(const toast_signal_t::slot_type& cb) { return mOnFadeSignal.connect(cb); } + boost::signals2::connection setOnToastDestroyedCallback(const toast_signal_t::slot_type& cb) { return mOnToastDestroyedSignal.connect(cb); } + boost::signals2::connection setOnToastHoverCallback(const toast_hover_check_signal_t::slot_type& cb) { return mOnToastHoverSignal.connect(cb); } + + boost::signals2::connection setMouseEnterCallback( const commit_signal_t::slot_type& cb ) { return mToastMouseEnterSignal.connect(cb); }; + boost::signals2::connection setMouseLeaveCallback( const commit_signal_t::slot_type& cb ) { return mToastMouseLeaveSignal.connect(cb); }; + + virtual S32 notifyParent(const LLSD& info); + + LLHandle getHandle() const { return getDerivedHandle(); } + +protected: + void updateTransparency(); + +private: + void updateHoveredState(); + + void expire(); + + void setFading(bool fading); + + LLUUID mNotificationID; + LLUUID mSessionID; + LLNotificationPtr mNotification; + + //LLRootHandle mHandle; + + LLPanel* mWrapperPanel; + + // timer counts a lifetime of a toast + std::unique_ptr mTimer; + + F32 mToastLifetime; // in seconds + F32 mToastFadingTime; // in seconds + + LLPanel* mPanel; + LLButton* mHideBtn; + + LLColor4 mBgColor; + bool mCanFade; + bool mCanBeStored; + bool mHideBtnEnabled; + bool mHideBtnPressed; + bool mIsHidden; // this flag is true when a toast has faded or was hidden with (x) button (EXT-1849) + bool mIsTip; + bool mIsFading; + bool mIsHovered; + + toast_signal_t mOnFadeSignal; + toast_signal_t mOnDeleteToastSignal; + toast_signal_t mOnToastDestroyedSignal; + toast_hover_check_signal_t mOnToastHoverSignal; + + commit_signal_t mToastMouseEnterSignal; + commit_signal_t mToastMouseLeaveSignal; + + static std::list sModalToastsList; +}; + +} +#endif diff --git a/indra/newview/lltoastalertpanel.cpp b/indra/newview/lltoastalertpanel.cpp index 0ecfa3c8a7..c8502d78ca 100644 --- a/indra/newview/lltoastalertpanel.cpp +++ b/indra/newview/lltoastalertpanel.cpp @@ -1,552 +1,552 @@ -/** - * @file lltoastalertpanel.cpp - * @brief Panel for alert toasts. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" // must be first include - -#include "linden_common.h" - -#include "llboost.h" - -#include "lltoastalertpanel.h" -#include "llfontgl.h" -#include "lltextbox.h" -#include "llbutton.h" -#include "llkeyboard.h" -#include "llfocusmgr.h" -#include "lliconctrl.h" -#include "llui.h" -#include "lllineeditor.h" -#include "lluictrlfactory.h" -#include "llnotifications.h" -#include "llrootview.h" -#include "lltransientfloatermgr.h" -#include "llviewercontrol.h" // for gSavedSettings -#include "llweb.h" - -#include - -const S32 MAX_ALLOWED_MSG_WIDTH = 400; -const F32 DEFAULT_BUTTON_DELAY = 0.5f; - -/*static*/ LLControlGroup* LLToastAlertPanel::sSettings = NULL; - -//----------------------------------------------------------------------------- -// Private methods - -static const S32 VPAD = 16; -static const S32 HPAD = 25; -static const S32 BTN_HPAD = 8; - -LLToastAlertPanel::LLToastAlertPanel( LLNotificationPtr notification, bool modal) - : LLCheckBoxToastPanel(notification), - mDefaultOption( 0 ), - mCaution(notification->getPriority() >= NOTIFICATION_PRIORITY_HIGH), - mLabel(notification->getName()), - mLineEditor(NULL) -{ - // EXP-1822 - // save currently focused view, so that return focus to it - // on destroying this toast. - LLView* current_selection = dynamic_cast(gFocusMgr.getKeyboardFocus()); - while(current_selection) - { - if (current_selection->isFocusRoot()) - { - mPreviouslyFocusedView = current_selection->getHandle(); - break; - } - current_selection = current_selection->getParent(); - } - - const LLFontGL* font = LLFontGL::getFontSansSerif(); - const S32 LINE_HEIGHT = font->getLineHeight(); - const S32 EDITOR_HEIGHT = 20; - - LLNotificationFormPtr form = mNotification->getForm(); - std::string edit_text_name; - std::string edit_text_contents; - S32 edit_text_max_chars = 0; - bool is_password = false; - bool allow_emoji = false; - - LLToastPanel::setBackgroundVisible(false); - LLToastPanel::setBackgroundOpaque(true); - - - typedef std::vector > options_t; - options_t supplied_options; - - // for now, get LLSD to iterator over form elements - LLSD form_sd = form->asLLSD(); - - S32 option_index = 0; - for (LLSD::array_const_iterator it = form_sd.beginArray(); it != form_sd.endArray(); ++it) - { - std::string type = (*it)["type"].asString(); - if (type == "button") - { - if((*it)["default"]) - { - mDefaultOption = option_index; - } - - supplied_options.push_back(std::make_pair((*it)["name"].asString(), (*it)["text"].asString())); - - ButtonData data; - if (option_index == mNotification->getURLOption()) - { - data.mURL = mNotification->getURL(); - data.mURLExternal = mNotification->getURLOpenExternally(); - } - - if((*it).has("width")) - { - data.mWidth = (*it)["width"].asInteger(); - } - - mButtonData.push_back(data); - option_index++; - } - else if (type == "text") - { - edit_text_contents = (*it)["value"].asString(); - edit_text_name = (*it)["name"].asString(); - edit_text_max_chars = (*it)["max_length_chars"].asInteger(); - allow_emoji = (*it)["allow_emoji"].asBoolean(); - } - else if (type == "password") - { - edit_text_contents = (*it)["value"].asString(); - edit_text_name = (*it)["name"].asString(); - is_password = true; - } - } - - // Buttons - options_t options; - if (supplied_options.empty()) - { - options.push_back(std::make_pair(std::string("close"), LLNotifications::instance().getGlobalString("implicitclosebutton"))); - - // add data for ok button. - ButtonData ok_button; - mButtonData.push_back(ok_button); - mDefaultOption = 0; - } - else - { - options = supplied_options; - } - - S32 num_options = options.size(); - - // Calc total width of buttons - S32 button_width = 0; - S32 sp = font->getWidth(std::string("OO")); - S32 btn_total_width = 0; - S32 default_size_btns = 0; - for( S32 i = 0; i < num_options; i++ ) - { - S32 w = S32(font->getWidth( options[i].second ) + 0.99f) + sp + 2 * LLBUTTON_H_PAD; - if (mButtonData[i].mWidth > w) - { - btn_total_width += mButtonData[i].mWidth; - } - else - { - button_width = llmax(w, button_width); - default_size_btns++; - } - } - - if( num_options > 1 ) - { - btn_total_width = btn_total_width + (button_width * default_size_btns) + ((num_options - 1) * BTN_HPAD); - } - else - { - btn_total_width = llmax(btn_total_width, button_width); - } - - // Message: create text box using raw string, as text has been structure deliberately - // Use size of created text box to generate dialog box size - std::string msg = mNotification->getMessage(); - LL_WARNS() << "Alert: " << msg << LL_ENDL; - LLTextBox::Params params; - params.name("Alert message"); - params.font(font); - params.tab_stop(false); - params.wrap(true); - params.follows.flags(FOLLOWS_LEFT | FOLLOWS_TOP); - params.allow_scroll(true); - params.force_urls_external(mNotification->getForceUrlsExternal()); - - LLTextBox * msg_box = LLUICtrlFactory::create (params); - // Compute max allowable height for the dialog text, so we can allocate - // space before wrapping the text to fit. - S32 max_allowed_msg_height = - gFloaterView->getRect().getHeight() - - LINE_HEIGHT // title bar - - 3*VPAD - BTN_HEIGHT; - // reshape to calculate real text width and height - msg_box->reshape( MAX_ALLOWED_MSG_WIDTH, max_allowed_msg_height ); - - if ("GroupLimitInfo" == mNotification->getName() || "GroupLimitInfoPlus" == mNotification->getName()) - { - msg_box->setSkipLinkUnderline(true); - } - msg_box->setValue(msg); - - S32 pixel_width = msg_box->getTextPixelWidth(); - S32 pixel_height = msg_box->getTextPixelHeight(); - - // We should use some space to prevent set textbox's scroller visible when it is unnecessary. - msg_box->reshape( llmin(MAX_ALLOWED_MSG_WIDTH,pixel_width + 2 * msg_box->getHPad() + HPAD), - llmin(max_allowed_msg_height,pixel_height + 2 * msg_box->getVPad()) ) ; - - const LLRect& text_rect = msg_box->getRect(); - S32 dialog_width = llmax( btn_total_width, text_rect.getWidth() ) + 2 * HPAD; - S32 dialog_height = text_rect.getHeight() + 3 * VPAD + BTN_HEIGHT; - - if (hasTitleBar()) - { - dialog_height += LINE_HEIGHT; // room for title bar - } - - // 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 - if (!edit_text_name.empty()) - { - dialog_height += EDITOR_HEIGHT + VPAD; - dialog_width = llmax(dialog_width, (S32)(font->getWidth( edit_text_contents ) + 0.99f)); - } - - if (mCaution) - { - // Make room for the caution icon. - dialog_width += 32 + HPAD; - } - - LLToastPanel::reshape( dialog_width, dialog_height, false ); - - S32 msg_y = LLToastPanel::getRect().getHeight() - VPAD; - S32 msg_x = HPAD; - if (hasTitleBar()) - { - msg_y -= LINE_HEIGHT; // room for title - } - - static LLUIColor alert_caution_text_color = LLUIColorTable::instance().getColor("AlertCautionTextColor"); - if (mCaution) - { - LLIconCtrl* icon = LLUICtrlFactory::getInstance()->createFromFile("alert_icon.xml", this, LLPanel::child_registry_t::instance()); - if(icon) - { - icon->setRect(LLRect(msg_x, msg_y, msg_x+32, msg_y-32)); - LLToastPanel::addChild(icon); - } - - msg_x += 32 + HPAD; - msg_box->setColor( alert_caution_text_color ); - } - - LLRect rect; - rect.setLeftTopAndSize( msg_x, msg_y, text_rect.getWidth(), text_rect.getHeight() ); - msg_box->setRect( rect ); - LLToastPanel::addChild(msg_box); - - // (Optional) Edit Box - if (!edit_text_name.empty()) - { - S32 y = VPAD + BTN_HEIGHT + VPAD/2; - if (form->getIgnoreType() != LLNotificationForm::IGNORE_NO) - { - y += EDITOR_HEIGHT; - } - mLineEditor = LLUICtrlFactory::getInstance()->createFromFile("alert_line_editor.xml", this, LLPanel::child_registry_t::instance()); - - if (mLineEditor) - { - LLRect leditor_rect = LLRect( HPAD, y+EDITOR_HEIGHT, dialog_width-HPAD, y); - mLineEditor->setName(edit_text_name); - mLineEditor->reshape(leditor_rect.getWidth(), leditor_rect.getHeight()); - mLineEditor->setRect(leditor_rect); - mLineEditor->setMaxTextChars(edit_text_max_chars); - mLineEditor->setAllowEmoji(allow_emoji); - mLineEditor->setText(edit_text_contents); - - std::string notif_name = mNotification->getName(); - if (("SaveOutfitAs" == notif_name) || ("SaveSettingAs" == notif_name) || ("CreateLandmarkFolder" == notif_name) || - ("CreateSubfolder" == notif_name) || ("SaveMaterialAs" == notif_name)) - { - mLineEditor->setPrevalidate(&LLTextValidate::validateASCII); - } - - // decrease limit of line editor of teleport offer dialog to avoid truncation of - // location URL in invitation message, see EXT-6891 - if ("OfferTeleport" == notif_name) - { - mLineEditor->setMaxTextLength(gSavedSettings.getS32( - "teleport_offer_invitation_max_length")); - } - else - { - mLineEditor->setMaxTextLength(STD_STRING_STR_LEN - 1); - } - - LLToastPanel::addChild(mLineEditor); - - mLineEditor->setDrawAsterixes(is_password); - - setEditTextArgs(notification->getSubstitutions()); - - mLineEditor->setFollowsLeft(); - mLineEditor->setFollowsRight(); - - // find form text input field - LLSD form_text; - for (LLSD::array_const_iterator it = form_sd.beginArray(); it != form_sd.endArray(); ++it) - { - std::string type = (*it)["type"].asString(); - if (type == "text") - { - form_text = (*it); - } - } - - // if form text input field has width attribute - if (form_text.has("width")) - { - // adjust floater width to fit line editor - S32 editor_width = form_text["width"]; - LLRect editor_rect = mLineEditor->getRect(); - U32 width_delta = editor_width - editor_rect.getWidth(); - LLRect toast_rect = getRect(); - reshape(toast_rect.getWidth() + width_delta, toast_rect.getHeight()); - } - } - } - - // Buttons - S32 button_left = (LLToastPanel::getRect().getWidth() - btn_total_width) / 2; - - for( S32 i = 0; i < num_options; i++ ) - { - LLRect button_rect; - - LLButton* btn = LLUICtrlFactory::getInstance()->createFromFile("alert_button.xml", this, LLPanel::child_registry_t::instance()); - if(btn) - { - btn->setName(options[i].first); - btn->setRect(button_rect.setOriginAndSize( button_left, VPAD, (mButtonData[i].mWidth == 0) ? button_width : mButtonData[i].mWidth, BTN_HEIGHT )); - btn->setLabel(options[i].second); - btn->setFont(font); - - btn->setClickedCallback(boost::bind(&LLToastAlertPanel::onButtonPressed, this, _2, i)); - - mButtonData[i].mButton = btn; - - LLToastPanel::addChild(btn); - - if( i == mDefaultOption ) - { - btn->setFocus(true); - } - } - button_left += ((mButtonData[i].mWidth == 0) ? button_width : mButtonData[i].mWidth) + BTN_HPAD; - } - - setCheckBoxes(HPAD, VPAD); - - // *TODO: check necessity of this code - //gFloaterView->adjustToFitScreen(this, false); - if (mLineEditor) - { - mLineEditor->selectAll(); - mLineEditor->setFocus(true); - } - if(mDefaultOption >= 0) - { - // delay before enabling default button - mDefaultBtnTimer.start(); - mDefaultBtnTimer.setTimerExpirySec(DEFAULT_BUTTON_DELAY); - } - - LLTransientFloaterMgr::instance().addControlView( - LLTransientFloaterMgr::GLOBAL, this); -} - -void LLToastAlertPanel::setVisible( bool visible ) -{ - // only make the "ding" sound if it's newly visible - if( visible && !LLToastPanel::getVisible() ) - { - make_ui_sound("UISndAlert"); - } - - LLToastPanel::setVisible( visible ); - -} - -LLToastAlertPanel::~LLToastAlertPanel() -{ - LLTransientFloaterMgr::instance().removeControlView( - LLTransientFloaterMgr::GLOBAL, this); - - // EXP-1822 - // return focus to the previously focused view if the viewer is not exiting - if (mPreviouslyFocusedView.get() && !LLApp::isExiting()) - { - LLView* current_selection = dynamic_cast(gFocusMgr.getKeyboardFocus()); - while(current_selection) - { - if (current_selection->isFocusRoot()) - { - break; - } - current_selection = current_selection->getParent(); - } - if (current_selection) - { - // If the focus moved to some other view though, move the focus there - current_selection->setFocus(true); - } - else - { - mPreviouslyFocusedView.get()->setFocus(true); - } - } -} - -bool LLToastAlertPanel::hasTitleBar() const -{ - // *TODO: check necessity of this code - /* - return (getCurrentTitle() != "" && getCurrentTitle() != " ") // has title - || isMinimizeable() - || isCloseable(); - */ - return false; -} - -bool LLToastAlertPanel::handleKeyHere(KEY key, MASK mask ) -{ - if( KEY_RETURN == key && mask == MASK_NONE ) - { - LLButton* defaultBtn = getDefaultButton(); - if(defaultBtn && defaultBtn->getVisible() && defaultBtn->getEnabled()) - { - // If we have a default button, click it when return is pressed - defaultBtn->onCommit(); - } - return true; - } - else if (KEY_RIGHT == key) - { - LLToastPanel::focusNextItem(false); - return true; - } - else if (KEY_LEFT == key) - { - LLToastPanel::focusPrevItem(false); - return true; - } - else if (KEY_TAB == key && mask == MASK_NONE) - { - LLToastPanel::focusNextItem(false); - return true; - } - else if (KEY_TAB == key && mask == MASK_SHIFT) - { - LLToastPanel::focusPrevItem(false); - return true; - } - else - { - return true; - } -} - -// virtual -void LLToastAlertPanel::draw() -{ - // if the default button timer has just expired, activate the default button - if(mDefaultBtnTimer.hasExpired() && mDefaultBtnTimer.getStarted()) - { - mDefaultBtnTimer.stop(); // prevent this block from being run more than once - LLToastPanel::setDefaultBtn(mButtonData[mDefaultOption].mButton); - } - - static LLUIColor shadow_color = LLUIColorTable::instance().getColor("ColorDropShadow"); - - gl_drop_shadow( 0, LLToastPanel::getRect().getHeight(), LLToastPanel::getRect().getWidth(), 0, - shadow_color, DROP_SHADOW_FLOATER); - - LLToastPanel::draw(); -} - -void LLToastAlertPanel::setEditTextArgs(const LLSD& edit_args) -{ - if (mLineEditor) - { - std::string msg = mLineEditor->getText(); - mLineEditor->setText(msg); - } - else - { - LL_WARNS() << "LLToastAlertPanel::setEditTextArgs called on dialog with no line editor" << LL_ENDL; - } -} - -void LLToastAlertPanel::onButtonPressed( const LLSD& data, S32 button ) -{ - ButtonData* button_data = &mButtonData[button]; - - LLSD response = mNotification->getResponseTemplate(); - if (mLineEditor) - { - response[mLineEditor->getName()] = mLineEditor->getValue(); - } - if (mNotification->getForm()->getIgnoreType() != LLNotificationForm::IGNORE_NO) - { - response["ignore"] = mNotification->isIgnored(); - } - response[button_data->mButton->getName()] = true; - - // If we declared a URL and chose the URL option, go to the url - if (!button_data->mURL.empty()) - { - if (button_data->mURLExternal) - { - LLWeb::loadURLExternal(button_data->mURL); - } - else - { - LLWeb::loadURL(button_data->mURL); - } - } - - mNotification->respond(response); // new notification reponse -} +/** + * @file lltoastalertpanel.cpp + * @brief Panel for alert toasts. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" // must be first include + +#include "linden_common.h" + +#include "llboost.h" + +#include "lltoastalertpanel.h" +#include "llfontgl.h" +#include "lltextbox.h" +#include "llbutton.h" +#include "llkeyboard.h" +#include "llfocusmgr.h" +#include "lliconctrl.h" +#include "llui.h" +#include "lllineeditor.h" +#include "lluictrlfactory.h" +#include "llnotifications.h" +#include "llrootview.h" +#include "lltransientfloatermgr.h" +#include "llviewercontrol.h" // for gSavedSettings +#include "llweb.h" + +#include + +const S32 MAX_ALLOWED_MSG_WIDTH = 400; +const F32 DEFAULT_BUTTON_DELAY = 0.5f; + +/*static*/ LLControlGroup* LLToastAlertPanel::sSettings = NULL; + +//----------------------------------------------------------------------------- +// Private methods + +static const S32 VPAD = 16; +static const S32 HPAD = 25; +static const S32 BTN_HPAD = 8; + +LLToastAlertPanel::LLToastAlertPanel( LLNotificationPtr notification, bool modal) + : LLCheckBoxToastPanel(notification), + mDefaultOption( 0 ), + mCaution(notification->getPriority() >= NOTIFICATION_PRIORITY_HIGH), + mLabel(notification->getName()), + mLineEditor(NULL) +{ + // EXP-1822 + // save currently focused view, so that return focus to it + // on destroying this toast. + LLView* current_selection = dynamic_cast(gFocusMgr.getKeyboardFocus()); + while(current_selection) + { + if (current_selection->isFocusRoot()) + { + mPreviouslyFocusedView = current_selection->getHandle(); + break; + } + current_selection = current_selection->getParent(); + } + + const LLFontGL* font = LLFontGL::getFontSansSerif(); + const S32 LINE_HEIGHT = font->getLineHeight(); + const S32 EDITOR_HEIGHT = 20; + + LLNotificationFormPtr form = mNotification->getForm(); + std::string edit_text_name; + std::string edit_text_contents; + S32 edit_text_max_chars = 0; + bool is_password = false; + bool allow_emoji = false; + + LLToastPanel::setBackgroundVisible(false); + LLToastPanel::setBackgroundOpaque(true); + + + typedef std::vector > options_t; + options_t supplied_options; + + // for now, get LLSD to iterator over form elements + LLSD form_sd = form->asLLSD(); + + S32 option_index = 0; + for (LLSD::array_const_iterator it = form_sd.beginArray(); it != form_sd.endArray(); ++it) + { + std::string type = (*it)["type"].asString(); + if (type == "button") + { + if((*it)["default"]) + { + mDefaultOption = option_index; + } + + supplied_options.push_back(std::make_pair((*it)["name"].asString(), (*it)["text"].asString())); + + ButtonData data; + if (option_index == mNotification->getURLOption()) + { + data.mURL = mNotification->getURL(); + data.mURLExternal = mNotification->getURLOpenExternally(); + } + + if((*it).has("width")) + { + data.mWidth = (*it)["width"].asInteger(); + } + + mButtonData.push_back(data); + option_index++; + } + else if (type == "text") + { + edit_text_contents = (*it)["value"].asString(); + edit_text_name = (*it)["name"].asString(); + edit_text_max_chars = (*it)["max_length_chars"].asInteger(); + allow_emoji = (*it)["allow_emoji"].asBoolean(); + } + else if (type == "password") + { + edit_text_contents = (*it)["value"].asString(); + edit_text_name = (*it)["name"].asString(); + is_password = true; + } + } + + // Buttons + options_t options; + if (supplied_options.empty()) + { + options.push_back(std::make_pair(std::string("close"), LLNotifications::instance().getGlobalString("implicitclosebutton"))); + + // add data for ok button. + ButtonData ok_button; + mButtonData.push_back(ok_button); + mDefaultOption = 0; + } + else + { + options = supplied_options; + } + + S32 num_options = options.size(); + + // Calc total width of buttons + S32 button_width = 0; + S32 sp = font->getWidth(std::string("OO")); + S32 btn_total_width = 0; + S32 default_size_btns = 0; + for( S32 i = 0; i < num_options; i++ ) + { + S32 w = S32(font->getWidth( options[i].second ) + 0.99f) + sp + 2 * LLBUTTON_H_PAD; + if (mButtonData[i].mWidth > w) + { + btn_total_width += mButtonData[i].mWidth; + } + else + { + button_width = llmax(w, button_width); + default_size_btns++; + } + } + + if( num_options > 1 ) + { + btn_total_width = btn_total_width + (button_width * default_size_btns) + ((num_options - 1) * BTN_HPAD); + } + else + { + btn_total_width = llmax(btn_total_width, button_width); + } + + // Message: create text box using raw string, as text has been structure deliberately + // Use size of created text box to generate dialog box size + std::string msg = mNotification->getMessage(); + LL_WARNS() << "Alert: " << msg << LL_ENDL; + LLTextBox::Params params; + params.name("Alert message"); + params.font(font); + params.tab_stop(false); + params.wrap(true); + params.follows.flags(FOLLOWS_LEFT | FOLLOWS_TOP); + params.allow_scroll(true); + params.force_urls_external(mNotification->getForceUrlsExternal()); + + LLTextBox * msg_box = LLUICtrlFactory::create (params); + // Compute max allowable height for the dialog text, so we can allocate + // space before wrapping the text to fit. + S32 max_allowed_msg_height = + gFloaterView->getRect().getHeight() + - LINE_HEIGHT // title bar + - 3*VPAD - BTN_HEIGHT; + // reshape to calculate real text width and height + msg_box->reshape( MAX_ALLOWED_MSG_WIDTH, max_allowed_msg_height ); + + if ("GroupLimitInfo" == mNotification->getName() || "GroupLimitInfoPlus" == mNotification->getName()) + { + msg_box->setSkipLinkUnderline(true); + } + msg_box->setValue(msg); + + S32 pixel_width = msg_box->getTextPixelWidth(); + S32 pixel_height = msg_box->getTextPixelHeight(); + + // We should use some space to prevent set textbox's scroller visible when it is unnecessary. + msg_box->reshape( llmin(MAX_ALLOWED_MSG_WIDTH,pixel_width + 2 * msg_box->getHPad() + HPAD), + llmin(max_allowed_msg_height,pixel_height + 2 * msg_box->getVPad()) ) ; + + const LLRect& text_rect = msg_box->getRect(); + S32 dialog_width = llmax( btn_total_width, text_rect.getWidth() ) + 2 * HPAD; + S32 dialog_height = text_rect.getHeight() + 3 * VPAD + BTN_HEIGHT; + + if (hasTitleBar()) + { + dialog_height += LINE_HEIGHT; // room for title bar + } + + // 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 + if (!edit_text_name.empty()) + { + dialog_height += EDITOR_HEIGHT + VPAD; + dialog_width = llmax(dialog_width, (S32)(font->getWidth( edit_text_contents ) + 0.99f)); + } + + if (mCaution) + { + // Make room for the caution icon. + dialog_width += 32 + HPAD; + } + + LLToastPanel::reshape( dialog_width, dialog_height, false ); + + S32 msg_y = LLToastPanel::getRect().getHeight() - VPAD; + S32 msg_x = HPAD; + if (hasTitleBar()) + { + msg_y -= LINE_HEIGHT; // room for title + } + + static LLUIColor alert_caution_text_color = LLUIColorTable::instance().getColor("AlertCautionTextColor"); + if (mCaution) + { + LLIconCtrl* icon = LLUICtrlFactory::getInstance()->createFromFile("alert_icon.xml", this, LLPanel::child_registry_t::instance()); + if(icon) + { + icon->setRect(LLRect(msg_x, msg_y, msg_x+32, msg_y-32)); + LLToastPanel::addChild(icon); + } + + msg_x += 32 + HPAD; + msg_box->setColor( alert_caution_text_color ); + } + + LLRect rect; + rect.setLeftTopAndSize( msg_x, msg_y, text_rect.getWidth(), text_rect.getHeight() ); + msg_box->setRect( rect ); + LLToastPanel::addChild(msg_box); + + // (Optional) Edit Box + if (!edit_text_name.empty()) + { + S32 y = VPAD + BTN_HEIGHT + VPAD/2; + if (form->getIgnoreType() != LLNotificationForm::IGNORE_NO) + { + y += EDITOR_HEIGHT; + } + mLineEditor = LLUICtrlFactory::getInstance()->createFromFile("alert_line_editor.xml", this, LLPanel::child_registry_t::instance()); + + if (mLineEditor) + { + LLRect leditor_rect = LLRect( HPAD, y+EDITOR_HEIGHT, dialog_width-HPAD, y); + mLineEditor->setName(edit_text_name); + mLineEditor->reshape(leditor_rect.getWidth(), leditor_rect.getHeight()); + mLineEditor->setRect(leditor_rect); + mLineEditor->setMaxTextChars(edit_text_max_chars); + mLineEditor->setAllowEmoji(allow_emoji); + mLineEditor->setText(edit_text_contents); + + std::string notif_name = mNotification->getName(); + if (("SaveOutfitAs" == notif_name) || ("SaveSettingAs" == notif_name) || ("CreateLandmarkFolder" == notif_name) || + ("CreateSubfolder" == notif_name) || ("SaveMaterialAs" == notif_name)) + { + mLineEditor->setPrevalidate(&LLTextValidate::validateASCII); + } + + // decrease limit of line editor of teleport offer dialog to avoid truncation of + // location URL in invitation message, see EXT-6891 + if ("OfferTeleport" == notif_name) + { + mLineEditor->setMaxTextLength(gSavedSettings.getS32( + "teleport_offer_invitation_max_length")); + } + else + { + mLineEditor->setMaxTextLength(STD_STRING_STR_LEN - 1); + } + + LLToastPanel::addChild(mLineEditor); + + mLineEditor->setDrawAsterixes(is_password); + + setEditTextArgs(notification->getSubstitutions()); + + mLineEditor->setFollowsLeft(); + mLineEditor->setFollowsRight(); + + // find form text input field + LLSD form_text; + for (LLSD::array_const_iterator it = form_sd.beginArray(); it != form_sd.endArray(); ++it) + { + std::string type = (*it)["type"].asString(); + if (type == "text") + { + form_text = (*it); + } + } + + // if form text input field has width attribute + if (form_text.has("width")) + { + // adjust floater width to fit line editor + S32 editor_width = form_text["width"]; + LLRect editor_rect = mLineEditor->getRect(); + U32 width_delta = editor_width - editor_rect.getWidth(); + LLRect toast_rect = getRect(); + reshape(toast_rect.getWidth() + width_delta, toast_rect.getHeight()); + } + } + } + + // Buttons + S32 button_left = (LLToastPanel::getRect().getWidth() - btn_total_width) / 2; + + for( S32 i = 0; i < num_options; i++ ) + { + LLRect button_rect; + + LLButton* btn = LLUICtrlFactory::getInstance()->createFromFile("alert_button.xml", this, LLPanel::child_registry_t::instance()); + if(btn) + { + btn->setName(options[i].first); + btn->setRect(button_rect.setOriginAndSize( button_left, VPAD, (mButtonData[i].mWidth == 0) ? button_width : mButtonData[i].mWidth, BTN_HEIGHT )); + btn->setLabel(options[i].second); + btn->setFont(font); + + btn->setClickedCallback(boost::bind(&LLToastAlertPanel::onButtonPressed, this, _2, i)); + + mButtonData[i].mButton = btn; + + LLToastPanel::addChild(btn); + + if( i == mDefaultOption ) + { + btn->setFocus(true); + } + } + button_left += ((mButtonData[i].mWidth == 0) ? button_width : mButtonData[i].mWidth) + BTN_HPAD; + } + + setCheckBoxes(HPAD, VPAD); + + // *TODO: check necessity of this code + //gFloaterView->adjustToFitScreen(this, false); + if (mLineEditor) + { + mLineEditor->selectAll(); + mLineEditor->setFocus(true); + } + if(mDefaultOption >= 0) + { + // delay before enabling default button + mDefaultBtnTimer.start(); + mDefaultBtnTimer.setTimerExpirySec(DEFAULT_BUTTON_DELAY); + } + + LLTransientFloaterMgr::instance().addControlView( + LLTransientFloaterMgr::GLOBAL, this); +} + +void LLToastAlertPanel::setVisible( bool visible ) +{ + // only make the "ding" sound if it's newly visible + if( visible && !LLToastPanel::getVisible() ) + { + make_ui_sound("UISndAlert"); + } + + LLToastPanel::setVisible( visible ); + +} + +LLToastAlertPanel::~LLToastAlertPanel() +{ + LLTransientFloaterMgr::instance().removeControlView( + LLTransientFloaterMgr::GLOBAL, this); + + // EXP-1822 + // return focus to the previously focused view if the viewer is not exiting + if (mPreviouslyFocusedView.get() && !LLApp::isExiting()) + { + LLView* current_selection = dynamic_cast(gFocusMgr.getKeyboardFocus()); + while(current_selection) + { + if (current_selection->isFocusRoot()) + { + break; + } + current_selection = current_selection->getParent(); + } + if (current_selection) + { + // If the focus moved to some other view though, move the focus there + current_selection->setFocus(true); + } + else + { + mPreviouslyFocusedView.get()->setFocus(true); + } + } +} + +bool LLToastAlertPanel::hasTitleBar() const +{ + // *TODO: check necessity of this code + /* + return (getCurrentTitle() != "" && getCurrentTitle() != " ") // has title + || isMinimizeable() + || isCloseable(); + */ + return false; +} + +bool LLToastAlertPanel::handleKeyHere(KEY key, MASK mask ) +{ + if( KEY_RETURN == key && mask == MASK_NONE ) + { + LLButton* defaultBtn = getDefaultButton(); + if(defaultBtn && defaultBtn->getVisible() && defaultBtn->getEnabled()) + { + // If we have a default button, click it when return is pressed + defaultBtn->onCommit(); + } + return true; + } + else if (KEY_RIGHT == key) + { + LLToastPanel::focusNextItem(false); + return true; + } + else if (KEY_LEFT == key) + { + LLToastPanel::focusPrevItem(false); + return true; + } + else if (KEY_TAB == key && mask == MASK_NONE) + { + LLToastPanel::focusNextItem(false); + return true; + } + else if (KEY_TAB == key && mask == MASK_SHIFT) + { + LLToastPanel::focusPrevItem(false); + return true; + } + else + { + return true; + } +} + +// virtual +void LLToastAlertPanel::draw() +{ + // if the default button timer has just expired, activate the default button + if(mDefaultBtnTimer.hasExpired() && mDefaultBtnTimer.getStarted()) + { + mDefaultBtnTimer.stop(); // prevent this block from being run more than once + LLToastPanel::setDefaultBtn(mButtonData[mDefaultOption].mButton); + } + + static LLUIColor shadow_color = LLUIColorTable::instance().getColor("ColorDropShadow"); + + gl_drop_shadow( 0, LLToastPanel::getRect().getHeight(), LLToastPanel::getRect().getWidth(), 0, + shadow_color, DROP_SHADOW_FLOATER); + + LLToastPanel::draw(); +} + +void LLToastAlertPanel::setEditTextArgs(const LLSD& edit_args) +{ + if (mLineEditor) + { + std::string msg = mLineEditor->getText(); + mLineEditor->setText(msg); + } + else + { + LL_WARNS() << "LLToastAlertPanel::setEditTextArgs called on dialog with no line editor" << LL_ENDL; + } +} + +void LLToastAlertPanel::onButtonPressed( const LLSD& data, S32 button ) +{ + ButtonData* button_data = &mButtonData[button]; + + LLSD response = mNotification->getResponseTemplate(); + if (mLineEditor) + { + response[mLineEditor->getName()] = mLineEditor->getValue(); + } + if (mNotification->getForm()->getIgnoreType() != LLNotificationForm::IGNORE_NO) + { + response["ignore"] = mNotification->isIgnored(); + } + response[button_data->mButton->getName()] = true; + + // If we declared a URL and chose the URL option, go to the url + if (!button_data->mURL.empty()) + { + if (button_data->mURLExternal) + { + LLWeb::loadURLExternal(button_data->mURL); + } + else + { + LLWeb::loadURL(button_data->mURL); + } + } + + mNotification->respond(response); // new notification reponse +} diff --git a/indra/newview/lltoastalertpanel.h b/indra/newview/lltoastalertpanel.h index fa089ba0b2..167a7b2363 100644 --- a/indra/newview/lltoastalertpanel.h +++ b/indra/newview/lltoastalertpanel.h @@ -1,107 +1,107 @@ -/** - * @file lltoastalertpanel.h - * @brief Panel for alert toasts. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_TOASTALERTPANEL_H -#define LL_TOASTALERTPANEL_H - -#include "lltoastpanel.h" -#include "llfloater.h" -#include "llui.h" -#include "llnotificationptr.h" -#include "llerror.h" - -class LLButton; -class LLCheckBoxCtrl; -class LLLineEditor; - -/** - * Toast panel for alert notification. - * Alerts notifications doesn't require user interaction. - * - * Replaces class LLAlertDialog. - * https://wiki.lindenlab.com/mediawiki/index.php?title=LLAlertDialog&oldid=81388 - */ - -class LLToastAlertPanel - : public LLCheckBoxToastPanel -{ - LOG_CLASS(LLToastAlertPanel); -public: - typedef bool (*display_callback_t)(S32 modal); - -public: - // User's responsibility to call show() after creating these. - LLToastAlertPanel( LLNotificationPtr notep, bool is_modal ); - - virtual bool handleKeyHere(KEY key, MASK mask ); - - virtual void draw(); - virtual void setVisible( bool visible ); - - void setCaution(bool val = true) { mCaution = val; } - // If mUnique==true only one copy of this message should exist - void setUnique(bool val = true) { mUnique = val; } - void setEditTextArgs(const LLSD& edit_args); - - void onButtonPressed(const LLSD& data, S32 button); - -private: - static std::map sUniqueActiveMap; - - virtual ~LLToastAlertPanel(); - // No you can't kill it. It can only kill itself. - - // Does it have a readable title label, or minimize or close buttons? - bool hasTitleBar() const; - -private: - static LLControlGroup* sSettings; - - struct ButtonData - { - ButtonData() - : mWidth(0) - {} - - LLButton* mButton; - std::string mURL; - U32 mURLExternal; - S32 mWidth; - }; - std::vector mButtonData; - - S32 mDefaultOption; - bool mCaution; - bool mUnique; - LLUIString mLabel; - LLFrameTimer mDefaultBtnTimer; - // For Dialogs that take a line as text as input: - LLLineEditor* mLineEditor; - LLHandle mPreviouslyFocusedView; - -}; - -#endif // LL_TOASTALERTPANEL_H +/** + * @file lltoastalertpanel.h + * @brief Panel for alert toasts. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_TOASTALERTPANEL_H +#define LL_TOASTALERTPANEL_H + +#include "lltoastpanel.h" +#include "llfloater.h" +#include "llui.h" +#include "llnotificationptr.h" +#include "llerror.h" + +class LLButton; +class LLCheckBoxCtrl; +class LLLineEditor; + +/** + * Toast panel for alert notification. + * Alerts notifications doesn't require user interaction. + * + * Replaces class LLAlertDialog. + * https://wiki.lindenlab.com/mediawiki/index.php?title=LLAlertDialog&oldid=81388 + */ + +class LLToastAlertPanel + : public LLCheckBoxToastPanel +{ + LOG_CLASS(LLToastAlertPanel); +public: + typedef bool (*display_callback_t)(S32 modal); + +public: + // User's responsibility to call show() after creating these. + LLToastAlertPanel( LLNotificationPtr notep, bool is_modal ); + + virtual bool handleKeyHere(KEY key, MASK mask ); + + virtual void draw(); + virtual void setVisible( bool visible ); + + void setCaution(bool val = true) { mCaution = val; } + // If mUnique==true only one copy of this message should exist + void setUnique(bool val = true) { mUnique = val; } + void setEditTextArgs(const LLSD& edit_args); + + void onButtonPressed(const LLSD& data, S32 button); + +private: + static std::map sUniqueActiveMap; + + virtual ~LLToastAlertPanel(); + // No you can't kill it. It can only kill itself. + + // Does it have a readable title label, or minimize or close buttons? + bool hasTitleBar() const; + +private: + static LLControlGroup* sSettings; + + struct ButtonData + { + ButtonData() + : mWidth(0) + {} + + LLButton* mButton; + std::string mURL; + U32 mURLExternal; + S32 mWidth; + }; + std::vector mButtonData; + + S32 mDefaultOption; + bool mCaution; + bool mUnique; + LLUIString mLabel; + LLFrameTimer mDefaultBtnTimer; + // For Dialogs that take a line as text as input: + LLLineEditor* mLineEditor; + LLHandle mPreviouslyFocusedView; + +}; + +#endif // LL_TOASTALERTPANEL_H diff --git a/indra/newview/lltoastgroupnotifypanel.cpp b/indra/newview/lltoastgroupnotifypanel.cpp index b6e912bd84..3c3440d41a 100644 --- a/indra/newview/lltoastgroupnotifypanel.cpp +++ b/indra/newview/lltoastgroupnotifypanel.cpp @@ -1,221 +1,221 @@ -/** - * @file lltoastgroupnotifypanel.cpp - * @brief Panel for group notify toasts. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lltoastgroupnotifypanel.h" - -#include "llfocusmgr.h" - -#include "llbutton.h" -#include "llgroupiconctrl.h" -#include "llinventoryfunctions.h" -#include "llinventoryicon.h" -#include "llnotifications.h" -#include "llviewertexteditor.h" - -#include "llavatarnamecache.h" -#include "lluiconstants.h" -#include "llui.h" -#include "llviewercontrol.h" -#include "lltrans.h" -#include "llstyle.h" - -#include "llglheaders.h" -#include "llagent.h" -#include "llavatariconctrl.h" -#include "llinventorytype.h" - -const S32 LLToastGroupNotifyPanel::DEFAULT_MESSAGE_MAX_LINE_COUNT = 7; - -LLToastGroupNotifyPanel::LLToastGroupNotifyPanel(const LLNotificationPtr& notification) -: LLToastPanel(notification), - mInventoryOffer(NULL) -{ - buildFromFile( "panel_group_notify.xml"); - const LLSD& payload = notification->getPayload(); - LLGroupData groupData; - if (!gAgent.getGroupData(payload["group_id"].asUUID(),groupData)) - { - LL_WARNS() << "Group notice for unknown group: " << payload["group_id"].asUUID() << LL_ENDL; - } - - //group icon - LLGroupIconCtrl* pGroupIcon = getChild("group_icon", true); - - // We should already have this data preloaded, so no sense in setting icon through setValue(group_id) - pGroupIcon->setIconId(groupData.mInsigniaID); - - //header title - std::string from_name = payload["sender_name"].asString(); - from_name = LLCacheName::buildUsername(from_name); - - std::stringstream from; - from << from_name << "/" << groupData.mName; - LLTextBox* pTitleText = getChild("title"); - pTitleText->setValue(from.str()); - pTitleText->setToolTip(from.str()); - - //message subject - const std::string& subject = payload["subject"].asString(); - //message body - const std::string& message = payload["message"].asString(); - - std::string timeStr = "[" + LLTrans::getString("TimeWeek") + "], [" - + LLTrans::getString("TimeMonth") + "]/[" - + LLTrans::getString("TimeDay") + "]/[" - + LLTrans::getString("TimeYear") + "] [" - + LLTrans::getString("TimeHour") + "]:[" - + LLTrans::getString("TimeMin") + "] [" - + LLTrans::getString("TimeTimezone") + "]"; - - const LLDate timeStamp = notification->getDate(); - LLDate notice_date = timeStamp.notNull() ? timeStamp : payload["received_time"].asDate(); - LLSD substitution; - substitution["datetime"] = (S32) notice_date.secondsSinceEpoch(); - LLStringUtil::format(timeStr, substitution); - - LLViewerTextEditor* pMessageText = getChild("message"); - pMessageText->setContentTrusted(false); - pMessageText->clear(); - - LLStyle::Params style; - LLFontGL* subject_font = LLFontGL::getFontByName(getString("subject_font")); - if (subject_font) - style.font = subject_font; - pMessageText->appendText(subject, false, style); - - LLFontGL* date_font = LLFontGL::getFontByName(getString("date_font")); - if (date_font) - style.font = date_font; - pMessageText->appendText(timeStr + "\n", true, style); - - style.font = pMessageText->getFont(); - pMessageText->appendText(message, true, style); - - //attachment - bool hasInventory = payload["inventory_offer"].isDefined(); - - //attachment text - LLTextBox * pAttachLink = getChild("attachment"); - //attachment icon - LLIconCtrl* pAttachIcon = getChild("attachment_icon", true); - - //If attachment is empty let it be invisible and not take place at the panel - pAttachLink->setVisible(hasInventory); - pAttachIcon->setVisible(hasInventory); - if (hasInventory) { - pAttachLink->setValue(payload["inventory_name"]); - - mInventoryOffer = new LLOfferInfo(payload["inventory_offer"]); - getChild("attachment")->setClickedCallback(boost::bind( - &LLToastGroupNotifyPanel::onClickAttachment, this)); - - LLUIImagePtr attachIconImg = LLInventoryIcon::getIcon(mInventoryOffer->mType, - LLInventoryType::IT_TEXTURE); - pAttachIcon->setValue(attachIconImg->getName()); - } - - //ok button - LLButton* pOkBtn = getChild("btn_ok"); - pOkBtn->setClickedCallback((boost::bind(&LLToastGroupNotifyPanel::onClickOk, this))); - setDefaultBtn(pOkBtn); - - S32 maxLinesCount; - std::istringstream ss( getString("message_max_lines_count") ); - if (!(ss >> maxLinesCount)) - { - maxLinesCount = DEFAULT_MESSAGE_MAX_LINE_COUNT; - } - snapToMessageHeight(pMessageText, maxLinesCount); -} - -// virtual -LLToastGroupNotifyPanel::~LLToastGroupNotifyPanel() -{ -} - -void LLToastGroupNotifyPanel::close() -{ - // The group notice dialog may be an inventory offer. - // If it has an inventory save button and that button is still enabled - // Then we need to send the inventory declined message - if(mInventoryOffer != NULL) - { - mInventoryOffer->forceResponse(IOR_DECLINE); - mInventoryOffer = NULL; - } - - die(); -} - -void LLToastGroupNotifyPanel::onClickOk() -{ - LLSD response = mNotification->getResponseTemplate(); - mNotification->respond(response); - close(); -} - -void LLToastGroupNotifyPanel::onClickAttachment() -{ - if (mInventoryOffer != NULL) { - mInventoryOffer->forceResponse(IOR_ACCEPT); - - LLTextBox * pAttachLink = getChild ("attachment"); - static const LLUIColor textColor = LLUIColorTable::instance().getColor( - "GroupNotifyDimmedTextColor"); - pAttachLink->setColor(textColor); - - LLIconCtrl* pAttachIcon = - getChild ("attachment_icon", true); - pAttachIcon->setEnabled(false); - - //if attachment isn't openable - notify about saving - if (!isAttachmentOpenable(mInventoryOffer->mType)) { - LLNotifications::instance().add("AttachmentSaved", LLSD(), LLSD()); - } - - mInventoryOffer = NULL; - } -} - -//static -bool LLToastGroupNotifyPanel::isAttachmentOpenable(LLAssetType::EType type) -{ - switch(type) - { - case LLAssetType::AT_LANDMARK: - case LLAssetType::AT_NOTECARD: - case LLAssetType::AT_IMAGE_JPEG: - case LLAssetType::AT_IMAGE_TGA: - case LLAssetType::AT_TEXTURE: - case LLAssetType::AT_TEXTURE_TGA: - return true; - default: - return false; - } -} - +/** + * @file lltoastgroupnotifypanel.cpp + * @brief Panel for group notify toasts. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lltoastgroupnotifypanel.h" + +#include "llfocusmgr.h" + +#include "llbutton.h" +#include "llgroupiconctrl.h" +#include "llinventoryfunctions.h" +#include "llinventoryicon.h" +#include "llnotifications.h" +#include "llviewertexteditor.h" + +#include "llavatarnamecache.h" +#include "lluiconstants.h" +#include "llui.h" +#include "llviewercontrol.h" +#include "lltrans.h" +#include "llstyle.h" + +#include "llglheaders.h" +#include "llagent.h" +#include "llavatariconctrl.h" +#include "llinventorytype.h" + +const S32 LLToastGroupNotifyPanel::DEFAULT_MESSAGE_MAX_LINE_COUNT = 7; + +LLToastGroupNotifyPanel::LLToastGroupNotifyPanel(const LLNotificationPtr& notification) +: LLToastPanel(notification), + mInventoryOffer(NULL) +{ + buildFromFile( "panel_group_notify.xml"); + const LLSD& payload = notification->getPayload(); + LLGroupData groupData; + if (!gAgent.getGroupData(payload["group_id"].asUUID(),groupData)) + { + LL_WARNS() << "Group notice for unknown group: " << payload["group_id"].asUUID() << LL_ENDL; + } + + //group icon + LLGroupIconCtrl* pGroupIcon = getChild("group_icon", true); + + // We should already have this data preloaded, so no sense in setting icon through setValue(group_id) + pGroupIcon->setIconId(groupData.mInsigniaID); + + //header title + std::string from_name = payload["sender_name"].asString(); + from_name = LLCacheName::buildUsername(from_name); + + std::stringstream from; + from << from_name << "/" << groupData.mName; + LLTextBox* pTitleText = getChild("title"); + pTitleText->setValue(from.str()); + pTitleText->setToolTip(from.str()); + + //message subject + const std::string& subject = payload["subject"].asString(); + //message body + const std::string& message = payload["message"].asString(); + + std::string timeStr = "[" + LLTrans::getString("TimeWeek") + "], [" + + LLTrans::getString("TimeMonth") + "]/[" + + LLTrans::getString("TimeDay") + "]/[" + + LLTrans::getString("TimeYear") + "] [" + + LLTrans::getString("TimeHour") + "]:[" + + LLTrans::getString("TimeMin") + "] [" + + LLTrans::getString("TimeTimezone") + "]"; + + const LLDate timeStamp = notification->getDate(); + LLDate notice_date = timeStamp.notNull() ? timeStamp : payload["received_time"].asDate(); + LLSD substitution; + substitution["datetime"] = (S32) notice_date.secondsSinceEpoch(); + LLStringUtil::format(timeStr, substitution); + + LLViewerTextEditor* pMessageText = getChild("message"); + pMessageText->setContentTrusted(false); + pMessageText->clear(); + + LLStyle::Params style; + LLFontGL* subject_font = LLFontGL::getFontByName(getString("subject_font")); + if (subject_font) + style.font = subject_font; + pMessageText->appendText(subject, false, style); + + LLFontGL* date_font = LLFontGL::getFontByName(getString("date_font")); + if (date_font) + style.font = date_font; + pMessageText->appendText(timeStr + "\n", true, style); + + style.font = pMessageText->getFont(); + pMessageText->appendText(message, true, style); + + //attachment + bool hasInventory = payload["inventory_offer"].isDefined(); + + //attachment text + LLTextBox * pAttachLink = getChild("attachment"); + //attachment icon + LLIconCtrl* pAttachIcon = getChild("attachment_icon", true); + + //If attachment is empty let it be invisible and not take place at the panel + pAttachLink->setVisible(hasInventory); + pAttachIcon->setVisible(hasInventory); + if (hasInventory) { + pAttachLink->setValue(payload["inventory_name"]); + + mInventoryOffer = new LLOfferInfo(payload["inventory_offer"]); + getChild("attachment")->setClickedCallback(boost::bind( + &LLToastGroupNotifyPanel::onClickAttachment, this)); + + LLUIImagePtr attachIconImg = LLInventoryIcon::getIcon(mInventoryOffer->mType, + LLInventoryType::IT_TEXTURE); + pAttachIcon->setValue(attachIconImg->getName()); + } + + //ok button + LLButton* pOkBtn = getChild("btn_ok"); + pOkBtn->setClickedCallback((boost::bind(&LLToastGroupNotifyPanel::onClickOk, this))); + setDefaultBtn(pOkBtn); + + S32 maxLinesCount; + std::istringstream ss( getString("message_max_lines_count") ); + if (!(ss >> maxLinesCount)) + { + maxLinesCount = DEFAULT_MESSAGE_MAX_LINE_COUNT; + } + snapToMessageHeight(pMessageText, maxLinesCount); +} + +// virtual +LLToastGroupNotifyPanel::~LLToastGroupNotifyPanel() +{ +} + +void LLToastGroupNotifyPanel::close() +{ + // The group notice dialog may be an inventory offer. + // If it has an inventory save button and that button is still enabled + // Then we need to send the inventory declined message + if(mInventoryOffer != NULL) + { + mInventoryOffer->forceResponse(IOR_DECLINE); + mInventoryOffer = NULL; + } + + die(); +} + +void LLToastGroupNotifyPanel::onClickOk() +{ + LLSD response = mNotification->getResponseTemplate(); + mNotification->respond(response); + close(); +} + +void LLToastGroupNotifyPanel::onClickAttachment() +{ + if (mInventoryOffer != NULL) { + mInventoryOffer->forceResponse(IOR_ACCEPT); + + LLTextBox * pAttachLink = getChild ("attachment"); + static const LLUIColor textColor = LLUIColorTable::instance().getColor( + "GroupNotifyDimmedTextColor"); + pAttachLink->setColor(textColor); + + LLIconCtrl* pAttachIcon = + getChild ("attachment_icon", true); + pAttachIcon->setEnabled(false); + + //if attachment isn't openable - notify about saving + if (!isAttachmentOpenable(mInventoryOffer->mType)) { + LLNotifications::instance().add("AttachmentSaved", LLSD(), LLSD()); + } + + mInventoryOffer = NULL; + } +} + +//static +bool LLToastGroupNotifyPanel::isAttachmentOpenable(LLAssetType::EType type) +{ + switch(type) + { + case LLAssetType::AT_LANDMARK: + case LLAssetType::AT_NOTECARD: + case LLAssetType::AT_IMAGE_JPEG: + case LLAssetType::AT_IMAGE_TGA: + case LLAssetType::AT_TEXTURE: + case LLAssetType::AT_TEXTURE_TGA: + return true; + default: + return false; + } +} + diff --git a/indra/newview/lltoastimpanel.cpp b/indra/newview/lltoastimpanel.cpp index 81dd402c31..42ee30409d 100644 --- a/indra/newview/lltoastimpanel.cpp +++ b/indra/newview/lltoastimpanel.cpp @@ -1,249 +1,249 @@ -/** - * @file lltoastimpanel.cpp - * @brief Panel for IM toasts. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "lltoastimpanel.h" - -#include "llagent.h" -#include "llavatarnamecache.h" -#include "llfloaterreg.h" -#include "llgroupactions.h" -#include "llgroupiconctrl.h" -#include "llimview.h" -#include "llnotifications.h" -#include "llinstantmessage.h" -#include "lltooltip.h" - -#include "llviewerchat.h" - -const S32 LLToastIMPanel::DEFAULT_MESSAGE_MAX_LINE_COUNT = 6; - -//-------------------------------------------------------------------------- -LLToastIMPanel::LLToastIMPanel(LLToastIMPanel::Params &p) : LLToastPanel(p.notification), - mAvatarIcon(NULL), mAvatarName(NULL), - mTime(NULL), mMessage(NULL), mGroupIcon(NULL) -{ - buildFromFile( "panel_instant_message.xml"); - - mGroupIcon = getChild("group_icon"); - mAvatarIcon = getChild("avatar_icon"); - mAdhocIcon = getChild("adhoc_icon"); - mAvatarName = getChild("user_name"); - mTime = getChild("time_box"); - mMessage = getChild("message"); - mMessage->setContentTrusted(false); - - LLStyle::Params style_params; - LLFontGL* fontp = LLViewerChat::getChatFont(); - std::string font_name = LLFontGL::nameFromFont(fontp); - std::string font_size = LLFontGL::sizeFromFont(fontp); - style_params.font.name(font_name); - style_params.font.size(font_size); - - LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(p.session_id); - mIsGroupMsg = (im_session && im_session->mSessionType == LLIMModel::LLIMSession::GROUP_SESSION); - std::string title = mIsGroupMsg ? im_session->mName : p.from; - mAvatarName->setValue(title); - - //Handle IRC styled /me messages. - std::string prefix = p.message.substr(0, 4); - if (prefix == "/me " || prefix == "/me'") - { - //style_params.font.style = "UNDERLINE"; - mMessage->clear(); - - style_params.font.style ="ITALIC"; - mMessage->appendText(p.from, false, style_params); - - style_params.font.style = "ITALIC"; - mMessage->appendText(p.message.substr(3), false, style_params); - } - else - { - if (mIsGroupMsg) - { - LLAvatarName avatar_name; - LLAvatarNameCache::get(p.avatar_id, &avatar_name); - p.message = "[From " + avatar_name.getDisplayName() + "]\n" + p.message; - } - style_params.font.style = "NORMAL"; - mMessage->setText(p.message, style_params); - } - - mTime->setValue(p.time); - mSessionID = p.session_id; - mAvatarID = p.avatar_id; - mNotification = p.notification; - - - - initIcon(); - - S32 maxLinesCount; - std::istringstream ss( getString("message_max_lines_count") ); - if (!(ss >> maxLinesCount)) - { - maxLinesCount = DEFAULT_MESSAGE_MAX_LINE_COUNT; - } - snapToMessageHeight(mMessage, maxLinesCount); -} - -//-------------------------------------------------------------------------- -LLToastIMPanel::~LLToastIMPanel() -{ -} - -//virtual -bool LLToastIMPanel::handleMouseUp(S32 x, S32 y, MASK mask) -{ - if (!LLPanel::handleMouseUp(x, y, mask)) - { - mNotification->respond(mNotification->getResponseTemplate()); - } - - return true; -} - -//virtual -bool LLToastIMPanel::handleToolTip(S32 x, S32 y, MASK mask) -{ - // It's not our direct child, so parentPointInView() doesn't work. - LLRect ctrl_rect; - - mAvatarName->localRectToOtherView(mAvatarName->getLocalRect(), &ctrl_rect, this); - if (ctrl_rect.pointInRect(x, y)) - { - spawnNameToolTip(); - return true; - } - - mGroupIcon->localRectToOtherView(mGroupIcon->getLocalRect(), &ctrl_rect, this); - if(mGroupIcon->getVisible() && ctrl_rect.pointInRect(x, y)) - { - spawnGroupIconToolTip(); - return true; - } - - return LLToastPanel::handleToolTip(x, y, mask); -} - -void LLToastIMPanel::spawnNameToolTip() -{ - // Spawn at right side of the name textbox. - LLRect sticky_rect = mAvatarName->calcScreenRect(); - S32 icon_x = - llmin(sticky_rect.mLeft + mAvatarName->getTextPixelWidth() + 3, sticky_rect.mRight); - LLCoordGL pos(icon_x, sticky_rect.mTop); - - LLToolTip::Params params; - params.background_visible(false); - if(!mIsGroupMsg) - { - params.click_callback(boost::bind(&LLFloaterReg::showInstance, "inspect_avatar", LLSD().with("avatar_id", mAvatarID), false)); - } - else - { - params.click_callback(boost::bind(&LLFloaterReg::showInstance, "inspect_group", LLSD().with("group_id", mSessionID), false)); - } - params.delay_time(0.0f); // spawn instantly on hover - params.image(LLUI::getUIImage("Info_Small")); - params.message(""); - params.padding(0); - params.pos(pos); - params.sticky_rect(sticky_rect); - - LLToolTipMgr::getInstance()->show(params); -} - -void LLToastIMPanel::spawnGroupIconToolTip() -{ - // Spawn at right bottom side of group icon. - LLRect sticky_rect = mGroupIcon->calcScreenRect(); - LLCoordGL pos(sticky_rect.mRight, sticky_rect.mBottom); - - LLGroupData g_data; - if(!gAgent.getGroupData(mSessionID, g_data)) - { - LL_WARNS() << "Error getting group data" << LL_ENDL; - } - - LLInspector::Params params; - params.fillFrom(LLUICtrlFactory::instance().getDefaultParams()); - params.click_callback(boost::bind(&LLFloaterReg::showInstance, "inspect_group", LLSD().with("group_id", mSessionID), false)); - params.delay_time(0.100f); - params.image(LLUI::getUIImage("Info_Small")); - params.message(g_data.mName); - params.padding(3); - params.pos(pos); - params.max_width(300); - - LLToolTipMgr::getInstance()->show(params); -} - -void LLToastIMPanel::initIcon() -{ - mAvatarIcon->setVisible(false); - mGroupIcon->setVisible(false); - mAdhocIcon->setVisible(false); - - if(mAvatarName->getValue().asString() == SYSTEM_FROM) - { - // "sys_msg_icon" was disabled by Erica in the changeset: 5109 (85181bc92cbe) - // and "dummy widget" warnings appeared in log. - // It does not make sense to have such image with empty name. Removed for EXT-5057. - } - else - { - LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(mSessionID); - if(!im_session) - { - LL_WARNS() << "Invalid IM session" << LL_ENDL; - return; - } - - switch(im_session->mSessionType) - { - case LLIMModel::LLIMSession::P2P_SESSION: - mAvatarIcon->setVisible(true); - mAvatarIcon->setValue(mAvatarID); - break; - case LLIMModel::LLIMSession::GROUP_SESSION: - mGroupIcon->setVisible(true); - mGroupIcon->setValue(mSessionID); - break; - case LLIMModel::LLIMSession::ADHOC_SESSION: - mAdhocIcon->setVisible(true); - mAdhocIcon->setValue(im_session->mOtherParticipantID); - mAdhocIcon->setToolTip(im_session->mName); - break; - default: - LL_WARNS() << "Unknown IM session type" << LL_ENDL; - break; - } - } -} - -// EOF +/** + * @file lltoastimpanel.cpp + * @brief Panel for IM toasts. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "lltoastimpanel.h" + +#include "llagent.h" +#include "llavatarnamecache.h" +#include "llfloaterreg.h" +#include "llgroupactions.h" +#include "llgroupiconctrl.h" +#include "llimview.h" +#include "llnotifications.h" +#include "llinstantmessage.h" +#include "lltooltip.h" + +#include "llviewerchat.h" + +const S32 LLToastIMPanel::DEFAULT_MESSAGE_MAX_LINE_COUNT = 6; + +//-------------------------------------------------------------------------- +LLToastIMPanel::LLToastIMPanel(LLToastIMPanel::Params &p) : LLToastPanel(p.notification), + mAvatarIcon(NULL), mAvatarName(NULL), + mTime(NULL), mMessage(NULL), mGroupIcon(NULL) +{ + buildFromFile( "panel_instant_message.xml"); + + mGroupIcon = getChild("group_icon"); + mAvatarIcon = getChild("avatar_icon"); + mAdhocIcon = getChild("adhoc_icon"); + mAvatarName = getChild("user_name"); + mTime = getChild("time_box"); + mMessage = getChild("message"); + mMessage->setContentTrusted(false); + + LLStyle::Params style_params; + LLFontGL* fontp = LLViewerChat::getChatFont(); + std::string font_name = LLFontGL::nameFromFont(fontp); + std::string font_size = LLFontGL::sizeFromFont(fontp); + style_params.font.name(font_name); + style_params.font.size(font_size); + + LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(p.session_id); + mIsGroupMsg = (im_session && im_session->mSessionType == LLIMModel::LLIMSession::GROUP_SESSION); + std::string title = mIsGroupMsg ? im_session->mName : p.from; + mAvatarName->setValue(title); + + //Handle IRC styled /me messages. + std::string prefix = p.message.substr(0, 4); + if (prefix == "/me " || prefix == "/me'") + { + //style_params.font.style = "UNDERLINE"; + mMessage->clear(); + + style_params.font.style ="ITALIC"; + mMessage->appendText(p.from, false, style_params); + + style_params.font.style = "ITALIC"; + mMessage->appendText(p.message.substr(3), false, style_params); + } + else + { + if (mIsGroupMsg) + { + LLAvatarName avatar_name; + LLAvatarNameCache::get(p.avatar_id, &avatar_name); + p.message = "[From " + avatar_name.getDisplayName() + "]\n" + p.message; + } + style_params.font.style = "NORMAL"; + mMessage->setText(p.message, style_params); + } + + mTime->setValue(p.time); + mSessionID = p.session_id; + mAvatarID = p.avatar_id; + mNotification = p.notification; + + + + initIcon(); + + S32 maxLinesCount; + std::istringstream ss( getString("message_max_lines_count") ); + if (!(ss >> maxLinesCount)) + { + maxLinesCount = DEFAULT_MESSAGE_MAX_LINE_COUNT; + } + snapToMessageHeight(mMessage, maxLinesCount); +} + +//-------------------------------------------------------------------------- +LLToastIMPanel::~LLToastIMPanel() +{ +} + +//virtual +bool LLToastIMPanel::handleMouseUp(S32 x, S32 y, MASK mask) +{ + if (!LLPanel::handleMouseUp(x, y, mask)) + { + mNotification->respond(mNotification->getResponseTemplate()); + } + + return true; +} + +//virtual +bool LLToastIMPanel::handleToolTip(S32 x, S32 y, MASK mask) +{ + // It's not our direct child, so parentPointInView() doesn't work. + LLRect ctrl_rect; + + mAvatarName->localRectToOtherView(mAvatarName->getLocalRect(), &ctrl_rect, this); + if (ctrl_rect.pointInRect(x, y)) + { + spawnNameToolTip(); + return true; + } + + mGroupIcon->localRectToOtherView(mGroupIcon->getLocalRect(), &ctrl_rect, this); + if(mGroupIcon->getVisible() && ctrl_rect.pointInRect(x, y)) + { + spawnGroupIconToolTip(); + return true; + } + + return LLToastPanel::handleToolTip(x, y, mask); +} + +void LLToastIMPanel::spawnNameToolTip() +{ + // Spawn at right side of the name textbox. + LLRect sticky_rect = mAvatarName->calcScreenRect(); + S32 icon_x = + llmin(sticky_rect.mLeft + mAvatarName->getTextPixelWidth() + 3, sticky_rect.mRight); + LLCoordGL pos(icon_x, sticky_rect.mTop); + + LLToolTip::Params params; + params.background_visible(false); + if(!mIsGroupMsg) + { + params.click_callback(boost::bind(&LLFloaterReg::showInstance, "inspect_avatar", LLSD().with("avatar_id", mAvatarID), false)); + } + else + { + params.click_callback(boost::bind(&LLFloaterReg::showInstance, "inspect_group", LLSD().with("group_id", mSessionID), false)); + } + params.delay_time(0.0f); // spawn instantly on hover + params.image(LLUI::getUIImage("Info_Small")); + params.message(""); + params.padding(0); + params.pos(pos); + params.sticky_rect(sticky_rect); + + LLToolTipMgr::getInstance()->show(params); +} + +void LLToastIMPanel::spawnGroupIconToolTip() +{ + // Spawn at right bottom side of group icon. + LLRect sticky_rect = mGroupIcon->calcScreenRect(); + LLCoordGL pos(sticky_rect.mRight, sticky_rect.mBottom); + + LLGroupData g_data; + if(!gAgent.getGroupData(mSessionID, g_data)) + { + LL_WARNS() << "Error getting group data" << LL_ENDL; + } + + LLInspector::Params params; + params.fillFrom(LLUICtrlFactory::instance().getDefaultParams()); + params.click_callback(boost::bind(&LLFloaterReg::showInstance, "inspect_group", LLSD().with("group_id", mSessionID), false)); + params.delay_time(0.100f); + params.image(LLUI::getUIImage("Info_Small")); + params.message(g_data.mName); + params.padding(3); + params.pos(pos); + params.max_width(300); + + LLToolTipMgr::getInstance()->show(params); +} + +void LLToastIMPanel::initIcon() +{ + mAvatarIcon->setVisible(false); + mGroupIcon->setVisible(false); + mAdhocIcon->setVisible(false); + + if(mAvatarName->getValue().asString() == SYSTEM_FROM) + { + // "sys_msg_icon" was disabled by Erica in the changeset: 5109 (85181bc92cbe) + // and "dummy widget" warnings appeared in log. + // It does not make sense to have such image with empty name. Removed for EXT-5057. + } + else + { + LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(mSessionID); + if(!im_session) + { + LL_WARNS() << "Invalid IM session" << LL_ENDL; + return; + } + + switch(im_session->mSessionType) + { + case LLIMModel::LLIMSession::P2P_SESSION: + mAvatarIcon->setVisible(true); + mAvatarIcon->setValue(mAvatarID); + break; + case LLIMModel::LLIMSession::GROUP_SESSION: + mGroupIcon->setVisible(true); + mGroupIcon->setValue(mSessionID); + break; + case LLIMModel::LLIMSession::ADHOC_SESSION: + mAdhocIcon->setVisible(true); + mAdhocIcon->setValue(im_session->mOtherParticipantID); + mAdhocIcon->setToolTip(im_session->mName); + break; + default: + LL_WARNS() << "Unknown IM session type" << LL_ENDL; + break; + } + } +} + +// EOF diff --git a/indra/newview/lltoastimpanel.h b/indra/newview/lltoastimpanel.h index 42d15c6238..dfb6eea780 100644 --- a/indra/newview/lltoastimpanel.h +++ b/indra/newview/lltoastimpanel.h @@ -1,82 +1,82 @@ -/** - * @file lltoastimpanel.h - * @brief Panel for IM toasts. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLTOASTIMPANEL_H_ -#define LLTOASTIMPANEL_H_ - - -#include "lltoastpanel.h" -#include "lltextbox.h" -#include "llbutton.h" -#include "llavatariconctrl.h" - -class LLGroupIconCtrl; - -class LLToastIMPanel: public LLToastPanel -{ -public: - struct Params - { - LLNotificationPtr notification; - LLUUID avatar_id, - session_id; - std::string from, - time, - message; - - Params() {} - }; - - LLToastIMPanel(LLToastIMPanel::Params &p); - virtual ~LLToastIMPanel(); - /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); -private: - void showInspector(); - - void spawnNameToolTip(); - void spawnGroupIconToolTip(); - - void initIcon(); - - static const S32 DEFAULT_MESSAGE_MAX_LINE_COUNT; - - LLNotificationPtr mNotification; - LLUUID mSessionID; - LLUUID mAvatarID; - LLAvatarIconCtrl* mAvatarIcon; - LLGroupIconCtrl* mGroupIcon; - LLAvatarIconCtrl* mAdhocIcon; - LLTextBox* mAvatarName; - LLTextBox* mTime; - LLTextBox* mMessage; - - bool mIsGroupMsg; -}; - -#endif // LLTOASTIMPANEL_H_ - - +/** + * @file lltoastimpanel.h + * @brief Panel for IM toasts. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLTOASTIMPANEL_H_ +#define LLTOASTIMPANEL_H_ + + +#include "lltoastpanel.h" +#include "lltextbox.h" +#include "llbutton.h" +#include "llavatariconctrl.h" + +class LLGroupIconCtrl; + +class LLToastIMPanel: public LLToastPanel +{ +public: + struct Params + { + LLNotificationPtr notification; + LLUUID avatar_id, + session_id; + std::string from, + time, + message; + + Params() {} + }; + + LLToastIMPanel(LLToastIMPanel::Params &p); + virtual ~LLToastIMPanel(); + /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); +private: + void showInspector(); + + void spawnNameToolTip(); + void spawnGroupIconToolTip(); + + void initIcon(); + + static const S32 DEFAULT_MESSAGE_MAX_LINE_COUNT; + + LLNotificationPtr mNotification; + LLUUID mSessionID; + LLUUID mAvatarID; + LLAvatarIconCtrl* mAvatarIcon; + LLGroupIconCtrl* mGroupIcon; + LLAvatarIconCtrl* mAdhocIcon; + LLTextBox* mAvatarName; + LLTextBox* mTime; + LLTextBox* mMessage; + + bool mIsGroupMsg; +}; + +#endif // LLTOASTIMPANEL_H_ + + diff --git a/indra/newview/lltoastnotifypanel.cpp b/indra/newview/lltoastnotifypanel.cpp index db15282214..6c0b3bfa13 100644 --- a/indra/newview/lltoastnotifypanel.cpp +++ b/indra/newview/lltoastnotifypanel.cpp @@ -1,561 +1,561 @@ -/** - * @file lltoastnotifypanel.cpp - * @brief Panel for notify toasts. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lltoastnotifypanel.h" - -// project includes -#include "llviewercontrol.h" - -// library includes -#include "lldbstrings.h" -#include "llcheckboxctrl.h" -#include "lllslconstants.h" -#include "llnotifications.h" -#include "lluiconstants.h" -#include "llrect.h" -#include "lltrans.h" -#include "llnotificationsutil.h" -#include "llviewermessage.h" -#include "llfloaterimsession.h" -#include "llavataractions.h" - -const S32 BOTTOM_PAD = VPAD * 3; -const S32 IGNORE_BTN_TOP_DELTA = 3*VPAD;//additional ignore_btn padding -S32 BUTTON_WIDTH = 90; - - -//static -const std::string LLToastNotifyPanel::sFontDefault("Emoji"); -const std::string LLToastNotifyPanel::sFontScript("SansSerif"); - -LLToastNotifyPanel::button_click_signal_t LLToastNotifyPanel::sButtonClickSignal; - -LLToastNotifyPanel::LLToastNotifyPanel(const LLNotificationPtr& notification, const LLRect& rect, bool show_images) -: LLCheckBoxToastPanel(notification) -, LLInstanceTracker(notification->getID()) -, mTextBox(NULL) -{ - init(rect, show_images); -} -void LLToastNotifyPanel::addDefaultButton() -{ - LLSD form_element; - form_element.with("name", "OK").with("text", LLTrans::getString("ok")).with("default", true); - LLButton* ok_btn = createButton(form_element, false); - LLRect new_btn_rect(ok_btn->getRect()); - - new_btn_rect.setOriginAndSize(llabs(getRect().getWidth() - BUTTON_WIDTH)/ 2, BOTTOM_PAD, - //auto_size for ok button makes it very small, so let's make it wider - BUTTON_WIDTH, new_btn_rect.getHeight()); - ok_btn->setRect(new_btn_rect); - addChild(ok_btn, -1); - mNumButtons = 1; - mAddedDefaultBtn = true; -} -LLButton* LLToastNotifyPanel::createButton(const LLSD& form_element, bool is_option) -{ - InstanceAndS32* userdata = new InstanceAndS32; - userdata->mSelf = this; - userdata->mButtonName = is_option ? form_element["name"].asString() : ""; - - mBtnCallbackData.push_back(userdata); - - LLButton::Params p; - S32 index = form_element["index"].asInteger(); - std::string name = form_element["name"].asString(); - std::string text = form_element["text"].asString(); - bool make_small_btn = index == -1 || index == -2; // for block and ignore buttons in script dialog - const LLFontGL* font = LLFontGL::getFont(LLFontDescriptor( - mIsScriptDialog ? sFontScript : sFontDefault, make_small_btn ? "Small" : "Medium", 0)); - p.name = name; - p.label = text; - p.tool_tip = text; - p.font = font; - p.rect.height = BTN_HEIGHT; - p.click_callback.function(boost::bind(&LLToastNotifyPanel::onClickButton, userdata)); - p.rect.width = BUTTON_WIDTH; - p.auto_resize = false; - p.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM); - p.enabled = !form_element.has("enabled") || form_element["enabled"].asBoolean(); - if (mIsCaution) - { - p.image_color(LLUIColorTable::instance().getColor("ButtonCautionImageColor")); - p.image_color_disabled(LLUIColorTable::instance().getColor("ButtonCautionImageColor")); - } - // for the scriptdialog buttons we use fixed button size. This is a limit! - if (!mIsScriptDialog && font->getWidth(form_element["text"].asString()) > (BUTTON_WIDTH-2*HPAD)) - { - p.rect.width = 1; - p.auto_resize = true; - } - else if (mIsScriptDialog && make_small_btn) - { - // this is ignore button, make it smaller - p.rect.height = BTN_HEIGHT_SMALL; - p.rect.width = 1; - p.auto_resize = true; - } - LLButton* btn = LLUICtrlFactory::create(p); - mNumButtons++; - btn->autoResize(); - if (form_element["default"].asBoolean()) - { - setDefaultBtn(btn); - } - - return btn; -} - -LLToastNotifyPanel::~LLToastNotifyPanel() -{ - mButtonClickConnection.disconnect(); - - std::for_each(mBtnCallbackData.begin(), mBtnCallbackData.end(), DeletePointer()); - mBtnCallbackData.clear(); - if (mIsTip) - { - LLNotifications::getInstance()->cancel(mNotification); - } - } - -void LLToastNotifyPanel::updateButtonsLayout(const std::vector& buttons, S32 h_pad) -{ - S32 left = 0; - //reserve place for ignore button - S32 bottom_offset = mIsScriptDialog ? (BTN_HEIGHT + IGNORE_BTN_TOP_DELTA + BOTTOM_PAD) : BOTTOM_PAD; - S32 max_width = mControlPanel->getRect().getWidth(); - LLButton* ignore_btn = NULL; - LLButton* mute_btn = NULL; - for (std::vector::const_iterator it = buttons.begin(); it != buttons.end(); it++) - { - if (-2 == it->first) - { - mute_btn = it->second; - continue; - } - if (it->first == -1) - { - ignore_btn = it->second; - continue; - } - LLButton* btn = it->second; - LLRect btn_rect(btn->getRect()); - if (buttons.size() == 1) // for the one-button forms, center that button - { - left = (max_width - btn_rect.getWidth()) / 2; - } - else if (left == 0 && buttons.size() == 2) - { - // Note: this and "size() == 1" shouldn't be inside the cycle, might be good idea to refactor whole placing process - left = (max_width - (btn_rect.getWidth() * 2) - h_pad) / 2; - } - else if (left + btn_rect.getWidth() > max_width)// whether there is still some place for button+h_pad in the mControlPanel - { - // looks like we need to add button to the next row - left = 0; - bottom_offset += (BTN_HEIGHT + VPAD); - } - //we arrange buttons from bottom to top for backward support of old script - btn_rect.setOriginAndSize(left, bottom_offset, btn_rect.getWidth(), btn_rect.getHeight()); - btn->setRect(btn_rect); - left = btn_rect.mLeft + btn_rect.getWidth() + h_pad; - mControlPanel->addChild(btn, -1); - } - - U32 ignore_btn_width = 0; - U32 mute_btn_pad = 0; - if (mIsScriptDialog && ignore_btn != NULL) - { - LLRect ignore_btn_rect(ignore_btn->getRect()); - S32 ignore_btn_left = max_width - ignore_btn_rect.getWidth(); - ignore_btn_rect.setOriginAndSize(ignore_btn_left, BOTTOM_PAD,// always move ignore button at the bottom - ignore_btn_rect.getWidth(), ignore_btn_rect.getHeight()); - ignore_btn->setRect(ignore_btn_rect); - ignore_btn_width = ignore_btn_rect.getWidth(); - mControlPanel->addChild(ignore_btn, -1); - mute_btn_pad = 4 * HPAD; //only use a 4 * HPAD padding if an ignore button exists - } - - if (mIsScriptDialog && mute_btn != NULL) - { - LLRect mute_btn_rect(mute_btn->getRect()); - // Place mute (Block) button to the left of the ignore button. - S32 mute_btn_left = max_width - mute_btn_rect.getWidth() - ignore_btn_width - mute_btn_pad; - mute_btn_rect.setOriginAndSize(mute_btn_left, BOTTOM_PAD,// always move mute button at the bottom - mute_btn_rect.getWidth(), mute_btn_rect.getHeight()); - mute_btn->setRect(mute_btn_rect); - mControlPanel->addChild(mute_btn); - } -} - -void LLToastNotifyPanel::adjustPanelForScriptNotice(S32 button_panel_width, S32 button_panel_height) -{ - //adjust layout - // we need to keep min width and max height to make visible all buttons, because width of the toast can not be changed - reshape(getRect().getWidth(), mInfoPanel->getRect().getHeight() + button_panel_height + VPAD); - mControlPanel->reshape( button_panel_width, button_panel_height); -} - -void LLToastNotifyPanel::adjustPanelForTipNotice() -{ - //we don't need display ControlPanel for tips because they doesn't contain any buttons. - mControlPanel->setVisible(false); - reshape(getRect().getWidth(), mInfoPanel->getRect().getHeight()); - - if (mNotification->getPayload().has("respond_on_mousedown") - && mNotification->getPayload()["respond_on_mousedown"] ) - { - mInfoPanel->setMouseDownCallback( - boost::bind(&LLNotification::respond, - mNotification, - mNotification->getResponseTemplate())); - } -} - -// static -void LLToastNotifyPanel::onClickButton(void* data) -{ - InstanceAndS32* self_and_button = (InstanceAndS32*)data; - LLToastNotifyPanel* self = self_and_button->mSelf; - std::string button_name = self_and_button->mButtonName; - - LLSD response = self->mNotification->getResponseTemplate(); - if (!self->mAddedDefaultBtn && !button_name.empty()) - { - response[button_name] = true; - } - - // disable all buttons - self->mControlPanel->setEnabled(false); - - // this might repost notification with new form data/enabled buttons - self->mNotification->respond(response); -} - -void LLToastNotifyPanel::init( LLRect rect, bool show_images ) -{ - deleteAllChildren(); - - LLRect current_rect = getRect(); - - setXMLFilename(""); - buildFromFile("panel_notification.xml"); - - if(rect != LLRect::null) - { - this->setShape(rect); - } - mInfoPanel = getChild("info_panel"); - - mControlPanel = getChild("control_panel"); - BUTTON_WIDTH = gSavedSettings.getS32("ToastButtonWidth"); - // customize panel's attributes - // is it intended for displaying a tip? - mIsTip = mNotification->getType() == "notifytip"; - - std::string notif_name = mNotification->getName(); - // is it a script dialog? - mIsScriptDialog = (notif_name == "ScriptDialog" || notif_name == "ScriptDialogGroup"); - - bool is_content_trusted = (notif_name != "LoadWebPage"); - // is it a caution? - // - // caution flag can be set explicitly by specifying it in the notification payload, or it can be set implicitly if the - // notify xml template specifies that it is a caution - // tip-style notification handle 'caution' differently -they display the tip in a different color - mIsCaution = mNotification->getPriority() >= NOTIFICATION_PRIORITY_HIGH; - - // setup parameters - // get a notification message - mMessage = mNotification->getMessage(); - // initialize - setFocusRoot(!mIsTip); - // get a form for the notification - LLNotificationFormPtr form(mNotification->getForm()); - // get number of elements - mNumOptions = form->getNumElements(); - - // customize panel's outfit - // preliminary adjust panel's layout - //move to the end - //mIsTip ? adjustPanelForTipNotice() : adjustPanelForScriptNotice(form); - - // adjust text options according to the notification type - // add a caution textbox at the top of a caution notification - if (mIsCaution && !mIsTip) - { - mTextBox = getChild("caution_text_box"); - mTextBox->setFont(LLFontGL::getFont(LLFontDescriptor(mIsScriptDialog ? sFontScript : sFontDefault, "Medium", LLFontGL::BOLD))); - } - else - { - mTextBox = getChild("text_editor_box"); - mTextBox->setFont(LLFontGL::getFont(LLFontDescriptor(mIsScriptDialog ? sFontScript : sFontDefault, "Medium", 0))); - } - - mTextBox->setMaxTextLength(LLToastPanel::MAX_TEXT_LENGTH); - mTextBox->setVisible(true); - mTextBox->setPlainText(!show_images); - mTextBox->setUseEmoji(!mIsScriptDialog); - mTextBox->setContentTrusted(is_content_trusted); - mTextBox->setValue(mNotification->getMessage()); - mTextBox->setIsFriendCallback(LLAvatarActions::isFriend); - mTextBox->setIsObjectBlockedCallback(boost::bind(&LLMuteList::isMuted, LLMuteList::getInstance(), _1, _2, 0)); - - // add buttons for a script notification - if (mIsTip) - { - adjustPanelForTipNotice(); - } - else - { - std::vector buttons; - buttons.reserve(mNumOptions); - S32 buttons_width = 0; - // create all buttons and accumulate they total width to reshape mControlPanel - for (S32 i = 0; i < mNumOptions; i++) - { - LLSD form_element = form->getElement(i); - if (form_element["type"].asString() != "button") - { - // not a button. - continue; - } - if (form_element["name"].asString() == TEXTBOX_MAGIC_TOKEN) - { - // a textbox pretending to be a button. - continue; - } - LLButton* new_button = createButton(form_element, true); - buttons_width += new_button->getRect().getWidth(); - S32 index = form_element["index"].asInteger(); - buttons.push_back(index_button_pair_t(index,new_button)); - } - if (buttons.empty()) - { - addDefaultButton(); - } - else - { - const S32 button_panel_width = mControlPanel->getRect().getWidth();// do not change width of the panel - S32 button_panel_height = mControlPanel->getRect().getHeight(); - //try get an average h_pad to spread out buttons - S32 h_pad = (button_panel_width - buttons_width) / (S32(buttons.size())); - if(h_pad < 2*HPAD) - { - /* - * Probably it is a scriptdialog toast - * for a scriptdialog toast h_pad can be < 2*HPAD if we have a lot of buttons. - * In last case set default h_pad to avoid heaping of buttons - */ - S32 button_per_row = button_panel_width / BUTTON_WIDTH; - h_pad = (button_panel_width % BUTTON_WIDTH) / (button_per_row - 1);// -1 because we do not need space after last button in a row - if(h_pad < 2*HPAD) // still not enough space between buttons ? - { - h_pad = 2*HPAD; - } - } - if (mIsScriptDialog) - { - // we are using default width for script buttons so we can determinate button_rows - // to get a number of rows we divide the required width of the buttons to button_panel_width - // buttons.size() is reduced by -2 due to presence of ignore button which is calculated independently a bit lower - S32 button_rows = llceil(F32(buttons.size() - 2) * (BUTTON_WIDTH + h_pad) / (button_panel_width + h_pad)); - //reserve one row for the ignore_btn - button_rows++; - //calculate required panel height for scripdialog notification. - button_panel_height = button_rows * (BTN_HEIGHT + VPAD) + IGNORE_BTN_TOP_DELTA + BOTTOM_PAD; - } - else - { - // in common case buttons can have different widths so we need to calculate button_rows according to buttons_width - //S32 button_rows = llceil(F32(buttons.size()) * (buttons_width + h_pad) / button_panel_width); - S32 button_rows = llceil(F32((buttons.size() - 1) * h_pad + buttons_width) / button_panel_width); - //calculate required panel height - button_panel_height = button_rows * (BTN_HEIGHT + VPAD) + BOTTOM_PAD; - } - - // we need to keep min width and max height to make visible all buttons, because width of the toast can not be changed - adjustPanelForScriptNotice(button_panel_width, button_panel_height); - updateButtonsLayout(buttons, h_pad); - // save buttons for later use in disableButtons() - //mButtons.assign(buttons.begin(), buttons.end()); - } - } - - //.xml file intially makes info panel only follow left/right/top. This is so that when control buttons are added the info panel - //can shift upward making room for the buttons inside mControlPanel. After the buttons are added, the info panel can then be set to follow 'all'. - mInfoPanel->setFollowsAll(); - - // Add checkbox (one of couple types) if nessesary. - setCheckBoxes(HPAD * 2, 0, mInfoPanel); - if (mCheck) - { - mCheck->setFollows(FOLLOWS_BOTTOM | FOLLOWS_LEFT); - } - // Snap to message, then to checkbox if present - snapToMessageHeight(mTextBox, LLToastPanel::MAX_TEXT_LENGTH); - if (mCheck) - { - S32 new_panel_height = mCheck->getRect().getHeight() + getRect().getHeight() + VPAD; - reshape(getRect().getWidth(), new_panel_height); - } - - // reshape the panel to its previous size - if (current_rect.notEmpty()) - { - reshape(current_rect.getWidth(), current_rect.getHeight()); - } -} - -void LLToastNotifyPanel::deleteAllChildren() -{ - // some visibility changes, re-init and reshape will attempt to - // use mTextBox or other variables. Reset to avoid crashes - // and other issues. - mTextBox = NULL; - mInfoPanel = NULL; - mControlPanel = NULL; - mNumOptions = 0; - mNumButtons = 0; - mAddedDefaultBtn = false; - - LLCheckBoxToastPanel::deleteAllChildren(); -} - -bool LLToastNotifyPanel::isControlPanelEnabled() const -{ - bool cp_enabled = mControlPanel->getEnabled(); - bool some_buttons_enabled = false; - if (cp_enabled) - { - LLView::child_list_const_iter_t child_it = mControlPanel->beginChild(); - LLView::child_list_const_iter_t child_it_end = mControlPanel->endChild(); - for(; child_it != child_it_end; ++child_it) - { - LLButton * buttonp = dynamic_cast(*child_it); - if (buttonp && buttonp->getEnabled()) - { - some_buttons_enabled = true; - break; - } - } - } - - return cp_enabled && some_buttons_enabled; -} - -////////////////////////////////////////////////////////////////////////// - -LLIMToastNotifyPanel::LLIMToastNotifyPanel(LLNotificationPtr& pNotification, const LLUUID& session_id, const LLRect& rect /* = LLRect::null */, - bool show_images /* = true */, LLTextBase* parent_text) -: mSessionID(session_id), LLToastNotifyPanel(pNotification, rect, show_images), - mParentText(parent_text) -{ - compactButtons(); -} - -LLIMToastNotifyPanel::~LLIMToastNotifyPanel() -{ -} - -void LLIMToastNotifyPanel::reshape(S32 width, S32 height, bool called_from_parent /* = true */) -{ - LLToastPanel::reshape(width, height, called_from_parent); - snapToMessageHeight(); -} - -void LLIMToastNotifyPanel::snapToMessageHeight() -{ - if(!mTextBox) - { - return; - } - - //Add message height if it is visible - if (mTextBox->getVisible()) - { - S32 new_panel_height = computeSnappedToMessageHeight(mTextBox, LLToastPanel::MAX_TEXT_LENGTH); - - //reshape the panel with new height - if (new_panel_height != getRect().getHeight()) - { - LLToastNotifyPanel::reshape( getRect().getWidth(), new_panel_height); - } - } -} - -void LLIMToastNotifyPanel::compactButtons() -{ - //we can't set follows in xml since it broke toasts behavior - setFollows(FOLLOWS_LEFT|FOLLOWS_RIGHT|FOLLOWS_TOP); - - const child_list_t* children = getControlPanel()->getChildList(); - S32 offset = 0; - // Children were added by addChild() which uses push_front to insert them into list, - // so to get buttons in correct order reverse iterator is used (EXT-5906) - for (child_list_t::const_reverse_iterator it = children->rbegin(); it != children->rend(); it++) - { - LLButton * button = dynamic_cast (*it); - if (button != NULL) - { - button->setOrigin( offset,button->getRect().mBottom); - button->setLeftHPad(2 * HPAD); - button->setRightHPad(2 * HPAD); - // set zero width before perform autoResize() - button->setRect(LLRect(button->getRect().mLeft, - button->getRect().mTop, - button->getRect().mLeft, - button->getRect().mBottom)); - button->setAutoResize(true); - button->autoResize(); - offset += HPAD + button->getRect().getWidth(); - button->setFollowsNone(); - } - } - - if (mParentText) - { - mParentText->needsReflow(); - } -} - -void LLIMToastNotifyPanel::updateNotification() - { - init(LLRect(), true); - } - -void LLIMToastNotifyPanel::init( LLRect rect, bool show_images ) -{ - LLToastNotifyPanel::init(LLRect(), show_images); - - compactButtons(); -} - -// EOF - +/** + * @file lltoastnotifypanel.cpp + * @brief Panel for notify toasts. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lltoastnotifypanel.h" + +// project includes +#include "llviewercontrol.h" + +// library includes +#include "lldbstrings.h" +#include "llcheckboxctrl.h" +#include "lllslconstants.h" +#include "llnotifications.h" +#include "lluiconstants.h" +#include "llrect.h" +#include "lltrans.h" +#include "llnotificationsutil.h" +#include "llviewermessage.h" +#include "llfloaterimsession.h" +#include "llavataractions.h" + +const S32 BOTTOM_PAD = VPAD * 3; +const S32 IGNORE_BTN_TOP_DELTA = 3*VPAD;//additional ignore_btn padding +S32 BUTTON_WIDTH = 90; + + +//static +const std::string LLToastNotifyPanel::sFontDefault("Emoji"); +const std::string LLToastNotifyPanel::sFontScript("SansSerif"); + +LLToastNotifyPanel::button_click_signal_t LLToastNotifyPanel::sButtonClickSignal; + +LLToastNotifyPanel::LLToastNotifyPanel(const LLNotificationPtr& notification, const LLRect& rect, bool show_images) +: LLCheckBoxToastPanel(notification) +, LLInstanceTracker(notification->getID()) +, mTextBox(NULL) +{ + init(rect, show_images); +} +void LLToastNotifyPanel::addDefaultButton() +{ + LLSD form_element; + form_element.with("name", "OK").with("text", LLTrans::getString("ok")).with("default", true); + LLButton* ok_btn = createButton(form_element, false); + LLRect new_btn_rect(ok_btn->getRect()); + + new_btn_rect.setOriginAndSize(llabs(getRect().getWidth() - BUTTON_WIDTH)/ 2, BOTTOM_PAD, + //auto_size for ok button makes it very small, so let's make it wider + BUTTON_WIDTH, new_btn_rect.getHeight()); + ok_btn->setRect(new_btn_rect); + addChild(ok_btn, -1); + mNumButtons = 1; + mAddedDefaultBtn = true; +} +LLButton* LLToastNotifyPanel::createButton(const LLSD& form_element, bool is_option) +{ + InstanceAndS32* userdata = new InstanceAndS32; + userdata->mSelf = this; + userdata->mButtonName = is_option ? form_element["name"].asString() : ""; + + mBtnCallbackData.push_back(userdata); + + LLButton::Params p; + S32 index = form_element["index"].asInteger(); + std::string name = form_element["name"].asString(); + std::string text = form_element["text"].asString(); + bool make_small_btn = index == -1 || index == -2; // for block and ignore buttons in script dialog + const LLFontGL* font = LLFontGL::getFont(LLFontDescriptor( + mIsScriptDialog ? sFontScript : sFontDefault, make_small_btn ? "Small" : "Medium", 0)); + p.name = name; + p.label = text; + p.tool_tip = text; + p.font = font; + p.rect.height = BTN_HEIGHT; + p.click_callback.function(boost::bind(&LLToastNotifyPanel::onClickButton, userdata)); + p.rect.width = BUTTON_WIDTH; + p.auto_resize = false; + p.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM); + p.enabled = !form_element.has("enabled") || form_element["enabled"].asBoolean(); + if (mIsCaution) + { + p.image_color(LLUIColorTable::instance().getColor("ButtonCautionImageColor")); + p.image_color_disabled(LLUIColorTable::instance().getColor("ButtonCautionImageColor")); + } + // for the scriptdialog buttons we use fixed button size. This is a limit! + if (!mIsScriptDialog && font->getWidth(form_element["text"].asString()) > (BUTTON_WIDTH-2*HPAD)) + { + p.rect.width = 1; + p.auto_resize = true; + } + else if (mIsScriptDialog && make_small_btn) + { + // this is ignore button, make it smaller + p.rect.height = BTN_HEIGHT_SMALL; + p.rect.width = 1; + p.auto_resize = true; + } + LLButton* btn = LLUICtrlFactory::create(p); + mNumButtons++; + btn->autoResize(); + if (form_element["default"].asBoolean()) + { + setDefaultBtn(btn); + } + + return btn; +} + +LLToastNotifyPanel::~LLToastNotifyPanel() +{ + mButtonClickConnection.disconnect(); + + std::for_each(mBtnCallbackData.begin(), mBtnCallbackData.end(), DeletePointer()); + mBtnCallbackData.clear(); + if (mIsTip) + { + LLNotifications::getInstance()->cancel(mNotification); + } + } + +void LLToastNotifyPanel::updateButtonsLayout(const std::vector& buttons, S32 h_pad) +{ + S32 left = 0; + //reserve place for ignore button + S32 bottom_offset = mIsScriptDialog ? (BTN_HEIGHT + IGNORE_BTN_TOP_DELTA + BOTTOM_PAD) : BOTTOM_PAD; + S32 max_width = mControlPanel->getRect().getWidth(); + LLButton* ignore_btn = NULL; + LLButton* mute_btn = NULL; + for (std::vector::const_iterator it = buttons.begin(); it != buttons.end(); it++) + { + if (-2 == it->first) + { + mute_btn = it->second; + continue; + } + if (it->first == -1) + { + ignore_btn = it->second; + continue; + } + LLButton* btn = it->second; + LLRect btn_rect(btn->getRect()); + if (buttons.size() == 1) // for the one-button forms, center that button + { + left = (max_width - btn_rect.getWidth()) / 2; + } + else if (left == 0 && buttons.size() == 2) + { + // Note: this and "size() == 1" shouldn't be inside the cycle, might be good idea to refactor whole placing process + left = (max_width - (btn_rect.getWidth() * 2) - h_pad) / 2; + } + else if (left + btn_rect.getWidth() > max_width)// whether there is still some place for button+h_pad in the mControlPanel + { + // looks like we need to add button to the next row + left = 0; + bottom_offset += (BTN_HEIGHT + VPAD); + } + //we arrange buttons from bottom to top for backward support of old script + btn_rect.setOriginAndSize(left, bottom_offset, btn_rect.getWidth(), btn_rect.getHeight()); + btn->setRect(btn_rect); + left = btn_rect.mLeft + btn_rect.getWidth() + h_pad; + mControlPanel->addChild(btn, -1); + } + + U32 ignore_btn_width = 0; + U32 mute_btn_pad = 0; + if (mIsScriptDialog && ignore_btn != NULL) + { + LLRect ignore_btn_rect(ignore_btn->getRect()); + S32 ignore_btn_left = max_width - ignore_btn_rect.getWidth(); + ignore_btn_rect.setOriginAndSize(ignore_btn_left, BOTTOM_PAD,// always move ignore button at the bottom + ignore_btn_rect.getWidth(), ignore_btn_rect.getHeight()); + ignore_btn->setRect(ignore_btn_rect); + ignore_btn_width = ignore_btn_rect.getWidth(); + mControlPanel->addChild(ignore_btn, -1); + mute_btn_pad = 4 * HPAD; //only use a 4 * HPAD padding if an ignore button exists + } + + if (mIsScriptDialog && mute_btn != NULL) + { + LLRect mute_btn_rect(mute_btn->getRect()); + // Place mute (Block) button to the left of the ignore button. + S32 mute_btn_left = max_width - mute_btn_rect.getWidth() - ignore_btn_width - mute_btn_pad; + mute_btn_rect.setOriginAndSize(mute_btn_left, BOTTOM_PAD,// always move mute button at the bottom + mute_btn_rect.getWidth(), mute_btn_rect.getHeight()); + mute_btn->setRect(mute_btn_rect); + mControlPanel->addChild(mute_btn); + } +} + +void LLToastNotifyPanel::adjustPanelForScriptNotice(S32 button_panel_width, S32 button_panel_height) +{ + //adjust layout + // we need to keep min width and max height to make visible all buttons, because width of the toast can not be changed + reshape(getRect().getWidth(), mInfoPanel->getRect().getHeight() + button_panel_height + VPAD); + mControlPanel->reshape( button_panel_width, button_panel_height); +} + +void LLToastNotifyPanel::adjustPanelForTipNotice() +{ + //we don't need display ControlPanel for tips because they doesn't contain any buttons. + mControlPanel->setVisible(false); + reshape(getRect().getWidth(), mInfoPanel->getRect().getHeight()); + + if (mNotification->getPayload().has("respond_on_mousedown") + && mNotification->getPayload()["respond_on_mousedown"] ) + { + mInfoPanel->setMouseDownCallback( + boost::bind(&LLNotification::respond, + mNotification, + mNotification->getResponseTemplate())); + } +} + +// static +void LLToastNotifyPanel::onClickButton(void* data) +{ + InstanceAndS32* self_and_button = (InstanceAndS32*)data; + LLToastNotifyPanel* self = self_and_button->mSelf; + std::string button_name = self_and_button->mButtonName; + + LLSD response = self->mNotification->getResponseTemplate(); + if (!self->mAddedDefaultBtn && !button_name.empty()) + { + response[button_name] = true; + } + + // disable all buttons + self->mControlPanel->setEnabled(false); + + // this might repost notification with new form data/enabled buttons + self->mNotification->respond(response); +} + +void LLToastNotifyPanel::init( LLRect rect, bool show_images ) +{ + deleteAllChildren(); + + LLRect current_rect = getRect(); + + setXMLFilename(""); + buildFromFile("panel_notification.xml"); + + if(rect != LLRect::null) + { + this->setShape(rect); + } + mInfoPanel = getChild("info_panel"); + + mControlPanel = getChild("control_panel"); + BUTTON_WIDTH = gSavedSettings.getS32("ToastButtonWidth"); + // customize panel's attributes + // is it intended for displaying a tip? + mIsTip = mNotification->getType() == "notifytip"; + + std::string notif_name = mNotification->getName(); + // is it a script dialog? + mIsScriptDialog = (notif_name == "ScriptDialog" || notif_name == "ScriptDialogGroup"); + + bool is_content_trusted = (notif_name != "LoadWebPage"); + // is it a caution? + // + // caution flag can be set explicitly by specifying it in the notification payload, or it can be set implicitly if the + // notify xml template specifies that it is a caution + // tip-style notification handle 'caution' differently -they display the tip in a different color + mIsCaution = mNotification->getPriority() >= NOTIFICATION_PRIORITY_HIGH; + + // setup parameters + // get a notification message + mMessage = mNotification->getMessage(); + // initialize + setFocusRoot(!mIsTip); + // get a form for the notification + LLNotificationFormPtr form(mNotification->getForm()); + // get number of elements + mNumOptions = form->getNumElements(); + + // customize panel's outfit + // preliminary adjust panel's layout + //move to the end + //mIsTip ? adjustPanelForTipNotice() : adjustPanelForScriptNotice(form); + + // adjust text options according to the notification type + // add a caution textbox at the top of a caution notification + if (mIsCaution && !mIsTip) + { + mTextBox = getChild("caution_text_box"); + mTextBox->setFont(LLFontGL::getFont(LLFontDescriptor(mIsScriptDialog ? sFontScript : sFontDefault, "Medium", LLFontGL::BOLD))); + } + else + { + mTextBox = getChild("text_editor_box"); + mTextBox->setFont(LLFontGL::getFont(LLFontDescriptor(mIsScriptDialog ? sFontScript : sFontDefault, "Medium", 0))); + } + + mTextBox->setMaxTextLength(LLToastPanel::MAX_TEXT_LENGTH); + mTextBox->setVisible(true); + mTextBox->setPlainText(!show_images); + mTextBox->setUseEmoji(!mIsScriptDialog); + mTextBox->setContentTrusted(is_content_trusted); + mTextBox->setValue(mNotification->getMessage()); + mTextBox->setIsFriendCallback(LLAvatarActions::isFriend); + mTextBox->setIsObjectBlockedCallback(boost::bind(&LLMuteList::isMuted, LLMuteList::getInstance(), _1, _2, 0)); + + // add buttons for a script notification + if (mIsTip) + { + adjustPanelForTipNotice(); + } + else + { + std::vector buttons; + buttons.reserve(mNumOptions); + S32 buttons_width = 0; + // create all buttons and accumulate they total width to reshape mControlPanel + for (S32 i = 0; i < mNumOptions; i++) + { + LLSD form_element = form->getElement(i); + if (form_element["type"].asString() != "button") + { + // not a button. + continue; + } + if (form_element["name"].asString() == TEXTBOX_MAGIC_TOKEN) + { + // a textbox pretending to be a button. + continue; + } + LLButton* new_button = createButton(form_element, true); + buttons_width += new_button->getRect().getWidth(); + S32 index = form_element["index"].asInteger(); + buttons.push_back(index_button_pair_t(index,new_button)); + } + if (buttons.empty()) + { + addDefaultButton(); + } + else + { + const S32 button_panel_width = mControlPanel->getRect().getWidth();// do not change width of the panel + S32 button_panel_height = mControlPanel->getRect().getHeight(); + //try get an average h_pad to spread out buttons + S32 h_pad = (button_panel_width - buttons_width) / (S32(buttons.size())); + if(h_pad < 2*HPAD) + { + /* + * Probably it is a scriptdialog toast + * for a scriptdialog toast h_pad can be < 2*HPAD if we have a lot of buttons. + * In last case set default h_pad to avoid heaping of buttons + */ + S32 button_per_row = button_panel_width / BUTTON_WIDTH; + h_pad = (button_panel_width % BUTTON_WIDTH) / (button_per_row - 1);// -1 because we do not need space after last button in a row + if(h_pad < 2*HPAD) // still not enough space between buttons ? + { + h_pad = 2*HPAD; + } + } + if (mIsScriptDialog) + { + // we are using default width for script buttons so we can determinate button_rows + // to get a number of rows we divide the required width of the buttons to button_panel_width + // buttons.size() is reduced by -2 due to presence of ignore button which is calculated independently a bit lower + S32 button_rows = llceil(F32(buttons.size() - 2) * (BUTTON_WIDTH + h_pad) / (button_panel_width + h_pad)); + //reserve one row for the ignore_btn + button_rows++; + //calculate required panel height for scripdialog notification. + button_panel_height = button_rows * (BTN_HEIGHT + VPAD) + IGNORE_BTN_TOP_DELTA + BOTTOM_PAD; + } + else + { + // in common case buttons can have different widths so we need to calculate button_rows according to buttons_width + //S32 button_rows = llceil(F32(buttons.size()) * (buttons_width + h_pad) / button_panel_width); + S32 button_rows = llceil(F32((buttons.size() - 1) * h_pad + buttons_width) / button_panel_width); + //calculate required panel height + button_panel_height = button_rows * (BTN_HEIGHT + VPAD) + BOTTOM_PAD; + } + + // we need to keep min width and max height to make visible all buttons, because width of the toast can not be changed + adjustPanelForScriptNotice(button_panel_width, button_panel_height); + updateButtonsLayout(buttons, h_pad); + // save buttons for later use in disableButtons() + //mButtons.assign(buttons.begin(), buttons.end()); + } + } + + //.xml file intially makes info panel only follow left/right/top. This is so that when control buttons are added the info panel + //can shift upward making room for the buttons inside mControlPanel. After the buttons are added, the info panel can then be set to follow 'all'. + mInfoPanel->setFollowsAll(); + + // Add checkbox (one of couple types) if nessesary. + setCheckBoxes(HPAD * 2, 0, mInfoPanel); + if (mCheck) + { + mCheck->setFollows(FOLLOWS_BOTTOM | FOLLOWS_LEFT); + } + // Snap to message, then to checkbox if present + snapToMessageHeight(mTextBox, LLToastPanel::MAX_TEXT_LENGTH); + if (mCheck) + { + S32 new_panel_height = mCheck->getRect().getHeight() + getRect().getHeight() + VPAD; + reshape(getRect().getWidth(), new_panel_height); + } + + // reshape the panel to its previous size + if (current_rect.notEmpty()) + { + reshape(current_rect.getWidth(), current_rect.getHeight()); + } +} + +void LLToastNotifyPanel::deleteAllChildren() +{ + // some visibility changes, re-init and reshape will attempt to + // use mTextBox or other variables. Reset to avoid crashes + // and other issues. + mTextBox = NULL; + mInfoPanel = NULL; + mControlPanel = NULL; + mNumOptions = 0; + mNumButtons = 0; + mAddedDefaultBtn = false; + + LLCheckBoxToastPanel::deleteAllChildren(); +} + +bool LLToastNotifyPanel::isControlPanelEnabled() const +{ + bool cp_enabled = mControlPanel->getEnabled(); + bool some_buttons_enabled = false; + if (cp_enabled) + { + LLView::child_list_const_iter_t child_it = mControlPanel->beginChild(); + LLView::child_list_const_iter_t child_it_end = mControlPanel->endChild(); + for(; child_it != child_it_end; ++child_it) + { + LLButton * buttonp = dynamic_cast(*child_it); + if (buttonp && buttonp->getEnabled()) + { + some_buttons_enabled = true; + break; + } + } + } + + return cp_enabled && some_buttons_enabled; +} + +////////////////////////////////////////////////////////////////////////// + +LLIMToastNotifyPanel::LLIMToastNotifyPanel(LLNotificationPtr& pNotification, const LLUUID& session_id, const LLRect& rect /* = LLRect::null */, + bool show_images /* = true */, LLTextBase* parent_text) +: mSessionID(session_id), LLToastNotifyPanel(pNotification, rect, show_images), + mParentText(parent_text) +{ + compactButtons(); +} + +LLIMToastNotifyPanel::~LLIMToastNotifyPanel() +{ +} + +void LLIMToastNotifyPanel::reshape(S32 width, S32 height, bool called_from_parent /* = true */) +{ + LLToastPanel::reshape(width, height, called_from_parent); + snapToMessageHeight(); +} + +void LLIMToastNotifyPanel::snapToMessageHeight() +{ + if(!mTextBox) + { + return; + } + + //Add message height if it is visible + if (mTextBox->getVisible()) + { + S32 new_panel_height = computeSnappedToMessageHeight(mTextBox, LLToastPanel::MAX_TEXT_LENGTH); + + //reshape the panel with new height + if (new_panel_height != getRect().getHeight()) + { + LLToastNotifyPanel::reshape( getRect().getWidth(), new_panel_height); + } + } +} + +void LLIMToastNotifyPanel::compactButtons() +{ + //we can't set follows in xml since it broke toasts behavior + setFollows(FOLLOWS_LEFT|FOLLOWS_RIGHT|FOLLOWS_TOP); + + const child_list_t* children = getControlPanel()->getChildList(); + S32 offset = 0; + // Children were added by addChild() which uses push_front to insert them into list, + // so to get buttons in correct order reverse iterator is used (EXT-5906) + for (child_list_t::const_reverse_iterator it = children->rbegin(); it != children->rend(); it++) + { + LLButton * button = dynamic_cast (*it); + if (button != NULL) + { + button->setOrigin( offset,button->getRect().mBottom); + button->setLeftHPad(2 * HPAD); + button->setRightHPad(2 * HPAD); + // set zero width before perform autoResize() + button->setRect(LLRect(button->getRect().mLeft, + button->getRect().mTop, + button->getRect().mLeft, + button->getRect().mBottom)); + button->setAutoResize(true); + button->autoResize(); + offset += HPAD + button->getRect().getWidth(); + button->setFollowsNone(); + } + } + + if (mParentText) + { + mParentText->needsReflow(); + } +} + +void LLIMToastNotifyPanel::updateNotification() + { + init(LLRect(), true); + } + +void LLIMToastNotifyPanel::init( LLRect rect, bool show_images ) +{ + LLToastNotifyPanel::init(LLRect(), show_images); + + compactButtons(); +} + +// EOF + diff --git a/indra/newview/lltoastnotifypanel.h b/indra/newview/lltoastnotifypanel.h index b2dfc591fe..d694513aba 100644 --- a/indra/newview/lltoastnotifypanel.h +++ b/indra/newview/lltoastnotifypanel.h @@ -1,168 +1,168 @@ -/** - * @file lltoastnotifypanel.h - * @brief Panel for notify toasts. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLTOASTNOTIFYPANEL_H_ -#define LLTOASTNOTIFYPANEL_H_ - -#include "llpanel.h" -#include "llfontgl.h" -#include "llnotificationptr.h" -#include "llbutton.h" -#include "lltoastpanel.h" -#include "lliconctrl.h" -#include "lltexteditor.h" -#include "lltextbox.h" - -class LLNotificationForm; - -/** - * Toast panel for notification. - * Notification panel should be used for notifications that require a response from the user. - * - * Replaces class LLNotifyBox. - * - * @deprecated this class will be removed after all toast panel types are - * implemented in separate classes. - */ -class LLToastNotifyPanel: public LLCheckBoxToastPanel, public LLInstanceTracker -{ -public: - /** - * Constructor for LLToastNotifyPanel. - * - * @param pNotification a shared pointer to LLNotification - * @param rect an initial rectangle of the toast panel. - * If it is null then a loaded from xml rectangle will be used. - * @see LLNotification - * @deprecated if you intend to instantiate LLToastNotifyPanel - it's point to - * implement right class for desired toast panel. @see LLGenericTipPanel as example. - */ - LLToastNotifyPanel(const LLNotificationPtr& pNotification, const LLRect& rect = LLRect::null, bool show_images = true); - - virtual void init( LLRect rect, bool show_images ); - virtual void deleteAllChildren(); - - virtual ~LLToastNotifyPanel(); - LLPanel * getControlPanel() { return mControlPanel; } - - virtual void updateNotification() {} - - bool isControlPanelEnabled() const; - -protected: - LLButton* createButton(const LLSD& form_element, bool is_option); - - // Used for callbacks - struct InstanceAndS32 - { - LLToastNotifyPanel* mSelf; - std::string mButtonName; - }; - std::vector mBtnCallbackData; - - typedef std::pair index_button_pair_t; - void adjustPanelForScriptNotice(S32 max_width, S32 max_height); - void adjustPanelForTipNotice(); - void addDefaultButton(); - /* - * It lays out buttons of the notification in mControlPanel. - * Buttons will be placed from BOTTOM to TOP. - * @param h_pad horizontal space between buttons. It is depend on number of buttons. - * @param buttons vector of button to be added. - */ - void updateButtonsLayout(const std::vector& buttons, S32 h_pad); - - /** - * Disable specific button(s) based on notification name and clicked button - */ - //void disableButtons(const std::string& notification_name, const std::string& selected_button); - - //std::vector mButtons; - - // panel elements - LLTextBase* mTextBox { nullptr }; - LLPanel* mInfoPanel { nullptr }; // panel for text information - LLPanel* mControlPanel { nullptr }; // panel for buttons (if present) - - // internal handler for button being clicked - static void onClickButton(void* data); - - typedef boost::signals2::signal - button_click_signal_t; - static button_click_signal_t sButtonClickSignal; - boost::signals2::connection mButtonClickConnection; - - /** - * handle sButtonClickSignal (to disable buttons) across all panels with given notification_id - */ - void onToastPanelButtonClicked(const LLUUID& notification_id, const std::string btn_name); - - /** - * Process response data. Will disable selected options - */ - //void disableRespondedOptions(const LLNotificationPtr& notification); - - bool mIsTip { false }; - bool mAddedDefaultBtn { false }; - bool mIsScriptDialog { false }; - bool mIsCaution { false }; - - std::string mMessage; - S32 mNumOptions { 0 }; - S32 mNumButtons { 0 }; - - static const std::string sFontDefault; - static const std::string sFontScript; -}; - -class LLIMToastNotifyPanel : public LLToastNotifyPanel -{ -public: - - LLIMToastNotifyPanel(LLNotificationPtr& pNotification, - const LLUUID& session_id, - const LLRect& rect = LLRect::null, - bool show_images = true, - LLTextBase* parent_text = NULL); - - void compactButtons(); - - virtual void updateNotification(); - virtual void init( LLRect rect, bool show_images ); - - ~LLIMToastNotifyPanel(); - - /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); - -protected: - LLTextBase* mParentText; - LLUUID mSessionID; - -private: - void snapToMessageHeight(); -}; - -#endif /* LLTOASTNOTIFYPANEL_H_ */ +/** + * @file lltoastnotifypanel.h + * @brief Panel for notify toasts. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLTOASTNOTIFYPANEL_H_ +#define LLTOASTNOTIFYPANEL_H_ + +#include "llpanel.h" +#include "llfontgl.h" +#include "llnotificationptr.h" +#include "llbutton.h" +#include "lltoastpanel.h" +#include "lliconctrl.h" +#include "lltexteditor.h" +#include "lltextbox.h" + +class LLNotificationForm; + +/** + * Toast panel for notification. + * Notification panel should be used for notifications that require a response from the user. + * + * Replaces class LLNotifyBox. + * + * @deprecated this class will be removed after all toast panel types are + * implemented in separate classes. + */ +class LLToastNotifyPanel: public LLCheckBoxToastPanel, public LLInstanceTracker +{ +public: + /** + * Constructor for LLToastNotifyPanel. + * + * @param pNotification a shared pointer to LLNotification + * @param rect an initial rectangle of the toast panel. + * If it is null then a loaded from xml rectangle will be used. + * @see LLNotification + * @deprecated if you intend to instantiate LLToastNotifyPanel - it's point to + * implement right class for desired toast panel. @see LLGenericTipPanel as example. + */ + LLToastNotifyPanel(const LLNotificationPtr& pNotification, const LLRect& rect = LLRect::null, bool show_images = true); + + virtual void init( LLRect rect, bool show_images ); + virtual void deleteAllChildren(); + + virtual ~LLToastNotifyPanel(); + LLPanel * getControlPanel() { return mControlPanel; } + + virtual void updateNotification() {} + + bool isControlPanelEnabled() const; + +protected: + LLButton* createButton(const LLSD& form_element, bool is_option); + + // Used for callbacks + struct InstanceAndS32 + { + LLToastNotifyPanel* mSelf; + std::string mButtonName; + }; + std::vector mBtnCallbackData; + + typedef std::pair index_button_pair_t; + void adjustPanelForScriptNotice(S32 max_width, S32 max_height); + void adjustPanelForTipNotice(); + void addDefaultButton(); + /* + * It lays out buttons of the notification in mControlPanel. + * Buttons will be placed from BOTTOM to TOP. + * @param h_pad horizontal space between buttons. It is depend on number of buttons. + * @param buttons vector of button to be added. + */ + void updateButtonsLayout(const std::vector& buttons, S32 h_pad); + + /** + * Disable specific button(s) based on notification name and clicked button + */ + //void disableButtons(const std::string& notification_name, const std::string& selected_button); + + //std::vector mButtons; + + // panel elements + LLTextBase* mTextBox { nullptr }; + LLPanel* mInfoPanel { nullptr }; // panel for text information + LLPanel* mControlPanel { nullptr }; // panel for buttons (if present) + + // internal handler for button being clicked + static void onClickButton(void* data); + + typedef boost::signals2::signal + button_click_signal_t; + static button_click_signal_t sButtonClickSignal; + boost::signals2::connection mButtonClickConnection; + + /** + * handle sButtonClickSignal (to disable buttons) across all panels with given notification_id + */ + void onToastPanelButtonClicked(const LLUUID& notification_id, const std::string btn_name); + + /** + * Process response data. Will disable selected options + */ + //void disableRespondedOptions(const LLNotificationPtr& notification); + + bool mIsTip { false }; + bool mAddedDefaultBtn { false }; + bool mIsScriptDialog { false }; + bool mIsCaution { false }; + + std::string mMessage; + S32 mNumOptions { 0 }; + S32 mNumButtons { 0 }; + + static const std::string sFontDefault; + static const std::string sFontScript; +}; + +class LLIMToastNotifyPanel : public LLToastNotifyPanel +{ +public: + + LLIMToastNotifyPanel(LLNotificationPtr& pNotification, + const LLUUID& session_id, + const LLRect& rect = LLRect::null, + bool show_images = true, + LLTextBase* parent_text = NULL); + + void compactButtons(); + + virtual void updateNotification(); + virtual void init( LLRect rect, bool show_images ); + + ~LLIMToastNotifyPanel(); + + /*virtual*/ void reshape(S32 width, S32 height, bool called_from_parent = true); + +protected: + LLTextBase* mParentText; + LLUUID mSessionID; + +private: + void snapToMessageHeight(); +}; + +#endif /* LLTOASTNOTIFYPANEL_H_ */ diff --git a/indra/newview/lltoastpanel.cpp b/indra/newview/lltoastpanel.cpp index a0d0d6590a..b50f38354c 100644 --- a/indra/newview/lltoastpanel.cpp +++ b/indra/newview/lltoastpanel.cpp @@ -1,256 +1,256 @@ -/** - * @file lltoastpanel.cpp - * @brief Creates a panel of a specific kind for a toast - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lldbstrings.h" -#include "llcheckboxctrl.h" -#include "llpanelgenerictip.h" -#include "llpanelonlinestatus.h" -#include "llnotifications.h" -#include "lltoastnotifypanel.h" -#include "lltoastpanel.h" -#include "lltoastscriptquestion.h" - -#include - -//static -const S32 LLToastPanel::MIN_PANEL_HEIGHT = 40; // VPAD(4)*2 + ICON_HEIGHT(32) -// 'magic numbers', consider initializing (512+20) part from xml/notifications -const S32 LLToastPanel::MAX_TEXT_LENGTH = 512 + 20 + DB_FIRST_NAME_BUF_SIZE + DB_LAST_NAME_BUF_SIZE + DB_INV_ITEM_NAME_BUF_SIZE; - -LLToastPanel::LLToastPanel(const LLNotificationPtr& notification) -{ - mNotification = notification; -} - -LLToastPanel::~LLToastPanel() -{ -} - -//virtual -std::string LLToastPanel::getTitle() -{ - // *TODO: create Title and localize it. If it will be required. - return mNotification->getMessage(); -} - -//virtual -const std::string& LLToastPanel::getNotificationName() -{ - return mNotification->getName(); -} - -//virtual -const LLUUID& LLToastPanel::getID() -{ - return mNotification->id(); -} - -S32 LLToastPanel::computeSnappedToMessageHeight(LLTextBase* message, S32 maxLineCount) -{ - S32 heightDelta = 0; - S32 maxTextHeight = message->getFont()->getLineHeight() * maxLineCount; - - LLRect messageRect = message->getRect(); - S32 oldTextHeight = messageRect.getHeight(); - - //Knowing the height is set to max allowed, getTextPixelHeight returns needed text height - //Perhaps we need to pass maxLineCount as parameter to getTextPixelHeight to avoid previous reshape. - S32 requiredTextHeight = message->getTextBoundingRect().getHeight(); - S32 newTextHeight = llmin(requiredTextHeight, maxTextHeight); - - heightDelta = newTextHeight - oldTextHeight; - S32 new_panel_height = llmax(getRect().getHeight() + heightDelta, MIN_PANEL_HEIGHT); - - return new_panel_height; -} - -//snap to the message height if it is visible -void LLToastPanel::snapToMessageHeight(LLTextBase* message, S32 maxLineCount) -{ - if(!message) - { - return; - } - - //Add message height if it is visible - if (message->getVisible()) - { - S32 new_panel_height = computeSnappedToMessageHeight(message, maxLineCount); - - //reshape the panel with new height - if (new_panel_height != getRect().getHeight()) - { - reshape( getRect().getWidth(), new_panel_height); - } - } -} - -// static -LLToastPanel* LLToastPanel::buidPanelFromNotification( - const LLNotificationPtr& notification) -{ - LL_PROFILE_ZONE_SCOPED - LLToastPanel* res = NULL; - - //process tip toast panels - if ("notifytip" == notification->getType()) - { - // if it is online/offline notification - if ("FriendOnlineOffline" == notification->getName()) - { - res = new LLPanelOnlineStatus(notification); - } - // in all other case we use generic tip panel - else - { - res = new LLPanelGenericTip(notification); - } - } - else if("notify" == notification->getType()) - { - if (notification->getPriority() == NOTIFICATION_PRIORITY_CRITICAL) - { - res = new LLToastScriptQuestion(notification); - } - else - { - res = new LLToastNotifyPanel(notification); - } - } - /* - else if(...) - create all other specific non-public toast panel - */ - - return res; -} - -LLCheckBoxToastPanel::LLCheckBoxToastPanel(const LLNotificationPtr& p_ntf) -: LLToastPanel(p_ntf), -mCheck(NULL) -{ - -} - -void LLCheckBoxToastPanel::setCheckBoxes(const S32 &h_pad, const S32 &v_pad, LLView *parent_view) -{ - std::string ignore_label; - LLNotificationFormPtr form = mNotification->getForm(); - - if (form->getIgnoreType() == LLNotificationForm::IGNORE_CHECKBOX_ONLY) - { - // Normally text is only used to describe notification in preferences, - // but this one is not displayed in preferences and works on case by case - // basis. - // Display text if present, display 'always chose' if not. - std::string ignore_message = form->getIgnoreMessage(); - if (ignore_message.empty()) - { - ignore_message = LLNotifications::instance().getGlobalString("alwayschoose"); - } - setCheckBox(ignore_message, ignore_label, boost::bind(&LLCheckBoxToastPanel::onCommitCheckbox, this, _1), h_pad, v_pad, parent_view); - } - else if (form->getIgnoreType() == LLNotificationForm::IGNORE_WITH_DEFAULT_RESPONSE) - { - setCheckBox(LLNotifications::instance().getGlobalString("skipnexttime"), ignore_label, boost::bind(&LLCheckBoxToastPanel::onCommitCheckbox, this, _1), h_pad, v_pad, parent_view); - } - if (form->getIgnoreType() == LLNotificationForm::IGNORE_WITH_DEFAULT_RESPONSE_SESSION_ONLY) - { - setCheckBox(LLNotifications::instance().getGlobalString("skipnexttimesessiononly"), ignore_label, boost::bind(&LLCheckBoxToastPanel::onCommitCheckbox, this, _1), h_pad, v_pad, parent_view); - } - else if (form->getIgnoreType() == LLNotificationForm::IGNORE_WITH_LAST_RESPONSE) - { - setCheckBox(LLNotifications::instance().getGlobalString("alwayschoose"), ignore_label, boost::bind(&LLCheckBoxToastPanel::onCommitCheckbox, this, _1), h_pad, v_pad, parent_view); - } -} - -bool LLCheckBoxToastPanel::setCheckBox(const std::string& check_title, - const std::string& check_control, - const commit_signal_t::slot_type& cb, - const S32 &h_pad, - const S32 &v_pad, - LLView *parent_view) -{ - mCheck = LLUICtrlFactory::getInstance()->createFromFile("alert_check_box.xml", this, LLPanel::child_registry_t::instance()); - - if (!mCheck) - { - return false; - } - - const LLFontGL* font = mCheck->getFont(); - const S32 LINE_HEIGHT = font->getLineHeight(); - - std::vector lines; - boost::split(lines, check_title, boost::is_any_of("\n")); - - // Extend dialog for "check next time" - S32 max_msg_width = LLToastPanel::getRect().getWidth() - 2 * h_pad; - S32 check_width = S32(font->getWidth(lines[0]) + 0.99f) + 16; // use width of the first line - max_msg_width = llmax(max_msg_width, check_width); - S32 dialog_width = max_msg_width + 2 * h_pad; - - S32 dialog_height = LLToastPanel::getRect().getHeight(); - dialog_height += LINE_HEIGHT * lines.size(); - dialog_height += LINE_HEIGHT / 2; - - LLToastPanel::reshape(dialog_width, dialog_height, false); - - S32 msg_x = (LLToastPanel::getRect().getWidth() - max_msg_width) / 2; - - // set check_box's attributes - LLRect check_rect; - // if we are part of the toast, we need to leave space for buttons - S32 msg_y = v_pad + (parent_view ? 0 : (BTN_HEIGHT + LINE_HEIGHT / 2)); - mCheck->setRect(check_rect.setOriginAndSize(msg_x, msg_y, max_msg_width, LINE_HEIGHT*lines.size())); - mCheck->setLabel(check_title); - mCheck->setCommitCallback(cb); - - if (parent_view) - { - // assume that width and height autoadjusts to toast - parent_view->addChild(mCheck); - } - else - { - LLToastPanel::addChild(mCheck); - } - - return true; -} - -void LLCheckBoxToastPanel::onCommitCheckbox(LLUICtrl* ctrl) -{ - bool check = ctrl->getValue().asBoolean(); - if (mNotification->getForm()->getIgnoreType() == LLNotificationForm::IGNORE_SHOW_AGAIN) - { - // question was "show again" so invert value to get "ignore" - check = !check; - } - mNotification->setIgnored(check); -} +/** + * @file lltoastpanel.cpp + * @brief Creates a panel of a specific kind for a toast + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lldbstrings.h" +#include "llcheckboxctrl.h" +#include "llpanelgenerictip.h" +#include "llpanelonlinestatus.h" +#include "llnotifications.h" +#include "lltoastnotifypanel.h" +#include "lltoastpanel.h" +#include "lltoastscriptquestion.h" + +#include + +//static +const S32 LLToastPanel::MIN_PANEL_HEIGHT = 40; // VPAD(4)*2 + ICON_HEIGHT(32) +// 'magic numbers', consider initializing (512+20) part from xml/notifications +const S32 LLToastPanel::MAX_TEXT_LENGTH = 512 + 20 + DB_FIRST_NAME_BUF_SIZE + DB_LAST_NAME_BUF_SIZE + DB_INV_ITEM_NAME_BUF_SIZE; + +LLToastPanel::LLToastPanel(const LLNotificationPtr& notification) +{ + mNotification = notification; +} + +LLToastPanel::~LLToastPanel() +{ +} + +//virtual +std::string LLToastPanel::getTitle() +{ + // *TODO: create Title and localize it. If it will be required. + return mNotification->getMessage(); +} + +//virtual +const std::string& LLToastPanel::getNotificationName() +{ + return mNotification->getName(); +} + +//virtual +const LLUUID& LLToastPanel::getID() +{ + return mNotification->id(); +} + +S32 LLToastPanel::computeSnappedToMessageHeight(LLTextBase* message, S32 maxLineCount) +{ + S32 heightDelta = 0; + S32 maxTextHeight = message->getFont()->getLineHeight() * maxLineCount; + + LLRect messageRect = message->getRect(); + S32 oldTextHeight = messageRect.getHeight(); + + //Knowing the height is set to max allowed, getTextPixelHeight returns needed text height + //Perhaps we need to pass maxLineCount as parameter to getTextPixelHeight to avoid previous reshape. + S32 requiredTextHeight = message->getTextBoundingRect().getHeight(); + S32 newTextHeight = llmin(requiredTextHeight, maxTextHeight); + + heightDelta = newTextHeight - oldTextHeight; + S32 new_panel_height = llmax(getRect().getHeight() + heightDelta, MIN_PANEL_HEIGHT); + + return new_panel_height; +} + +//snap to the message height if it is visible +void LLToastPanel::snapToMessageHeight(LLTextBase* message, S32 maxLineCount) +{ + if(!message) + { + return; + } + + //Add message height if it is visible + if (message->getVisible()) + { + S32 new_panel_height = computeSnappedToMessageHeight(message, maxLineCount); + + //reshape the panel with new height + if (new_panel_height != getRect().getHeight()) + { + reshape( getRect().getWidth(), new_panel_height); + } + } +} + +// static +LLToastPanel* LLToastPanel::buidPanelFromNotification( + const LLNotificationPtr& notification) +{ + LL_PROFILE_ZONE_SCOPED + LLToastPanel* res = NULL; + + //process tip toast panels + if ("notifytip" == notification->getType()) + { + // if it is online/offline notification + if ("FriendOnlineOffline" == notification->getName()) + { + res = new LLPanelOnlineStatus(notification); + } + // in all other case we use generic tip panel + else + { + res = new LLPanelGenericTip(notification); + } + } + else if("notify" == notification->getType()) + { + if (notification->getPriority() == NOTIFICATION_PRIORITY_CRITICAL) + { + res = new LLToastScriptQuestion(notification); + } + else + { + res = new LLToastNotifyPanel(notification); + } + } + /* + else if(...) + create all other specific non-public toast panel + */ + + return res; +} + +LLCheckBoxToastPanel::LLCheckBoxToastPanel(const LLNotificationPtr& p_ntf) +: LLToastPanel(p_ntf), +mCheck(NULL) +{ + +} + +void LLCheckBoxToastPanel::setCheckBoxes(const S32 &h_pad, const S32 &v_pad, LLView *parent_view) +{ + std::string ignore_label; + LLNotificationFormPtr form = mNotification->getForm(); + + if (form->getIgnoreType() == LLNotificationForm::IGNORE_CHECKBOX_ONLY) + { + // Normally text is only used to describe notification in preferences, + // but this one is not displayed in preferences and works on case by case + // basis. + // Display text if present, display 'always chose' if not. + std::string ignore_message = form->getIgnoreMessage(); + if (ignore_message.empty()) + { + ignore_message = LLNotifications::instance().getGlobalString("alwayschoose"); + } + setCheckBox(ignore_message, ignore_label, boost::bind(&LLCheckBoxToastPanel::onCommitCheckbox, this, _1), h_pad, v_pad, parent_view); + } + else if (form->getIgnoreType() == LLNotificationForm::IGNORE_WITH_DEFAULT_RESPONSE) + { + setCheckBox(LLNotifications::instance().getGlobalString("skipnexttime"), ignore_label, boost::bind(&LLCheckBoxToastPanel::onCommitCheckbox, this, _1), h_pad, v_pad, parent_view); + } + if (form->getIgnoreType() == LLNotificationForm::IGNORE_WITH_DEFAULT_RESPONSE_SESSION_ONLY) + { + setCheckBox(LLNotifications::instance().getGlobalString("skipnexttimesessiononly"), ignore_label, boost::bind(&LLCheckBoxToastPanel::onCommitCheckbox, this, _1), h_pad, v_pad, parent_view); + } + else if (form->getIgnoreType() == LLNotificationForm::IGNORE_WITH_LAST_RESPONSE) + { + setCheckBox(LLNotifications::instance().getGlobalString("alwayschoose"), ignore_label, boost::bind(&LLCheckBoxToastPanel::onCommitCheckbox, this, _1), h_pad, v_pad, parent_view); + } +} + +bool LLCheckBoxToastPanel::setCheckBox(const std::string& check_title, + const std::string& check_control, + const commit_signal_t::slot_type& cb, + const S32 &h_pad, + const S32 &v_pad, + LLView *parent_view) +{ + mCheck = LLUICtrlFactory::getInstance()->createFromFile("alert_check_box.xml", this, LLPanel::child_registry_t::instance()); + + if (!mCheck) + { + return false; + } + + const LLFontGL* font = mCheck->getFont(); + const S32 LINE_HEIGHT = font->getLineHeight(); + + std::vector lines; + boost::split(lines, check_title, boost::is_any_of("\n")); + + // Extend dialog for "check next time" + S32 max_msg_width = LLToastPanel::getRect().getWidth() - 2 * h_pad; + S32 check_width = S32(font->getWidth(lines[0]) + 0.99f) + 16; // use width of the first line + max_msg_width = llmax(max_msg_width, check_width); + S32 dialog_width = max_msg_width + 2 * h_pad; + + S32 dialog_height = LLToastPanel::getRect().getHeight(); + dialog_height += LINE_HEIGHT * lines.size(); + dialog_height += LINE_HEIGHT / 2; + + LLToastPanel::reshape(dialog_width, dialog_height, false); + + S32 msg_x = (LLToastPanel::getRect().getWidth() - max_msg_width) / 2; + + // set check_box's attributes + LLRect check_rect; + // if we are part of the toast, we need to leave space for buttons + S32 msg_y = v_pad + (parent_view ? 0 : (BTN_HEIGHT + LINE_HEIGHT / 2)); + mCheck->setRect(check_rect.setOriginAndSize(msg_x, msg_y, max_msg_width, LINE_HEIGHT*lines.size())); + mCheck->setLabel(check_title); + mCheck->setCommitCallback(cb); + + if (parent_view) + { + // assume that width and height autoadjusts to toast + parent_view->addChild(mCheck); + } + else + { + LLToastPanel::addChild(mCheck); + } + + return true; +} + +void LLCheckBoxToastPanel::onCommitCheckbox(LLUICtrl* ctrl) +{ + bool check = ctrl->getValue().asBoolean(); + if (mNotification->getForm()->getIgnoreType() == LLNotificationForm::IGNORE_SHOW_AGAIN) + { + // question was "show again" so invert value to get "ignore" + check = !check; + } + mNotification->setIgnored(check); +} diff --git a/indra/newview/lltoastscriptquestion.cpp b/indra/newview/lltoastscriptquestion.cpp index 35d32b85cf..25dc0982b8 100644 --- a/indra/newview/lltoastscriptquestion.cpp +++ b/indra/newview/lltoastscriptquestion.cpp @@ -1,149 +1,149 @@ -/** - * @file lltoastscriptquestion.cpp - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llbutton.h" -#include "llnotifications.h" -#include "lltoastscriptquestion.h" - -const int LEFT_PAD = 10; -const int BUTTON_HEIGHT = 27; -const int MAX_LINES_COUNT = 50; - -LLToastScriptQuestion::LLToastScriptQuestion(const LLNotificationPtr& notification) -: -LLToastPanel(notification) -{ - buildFromFile("panel_script_question_toast.xml"); -} - -bool LLToastScriptQuestion::postBuild() -{ - createButtons(); - - LLTextBox* mMessage = getChild("top_info_message"); - LLTextBox* mFooter = getChild("bottom_info_message"); - - mMessage->setValue(mNotification->getMessage()); - mFooter->setValue(mNotification->getFooter()); - - snapToMessageHeight(); - - return true; -} - -// virtual -void LLToastScriptQuestion::setFocus(bool b) -{ - LLToastPanel::setFocus(b); - // toast can fade out and disappear with focus ON, so reset to default anyway - LLButton* dfbutton = getDefaultButton(); - if (dfbutton && dfbutton->getVisible() && dfbutton->getEnabled()) - { - dfbutton->setFocus(b); - } -} - -void LLToastScriptQuestion::snapToMessageHeight() -{ - LLTextBox* mMessage = getChild("top_info_message"); - LLTextBox* mFooter = getChild("bottom_info_message"); - if (!mMessage || !mFooter) - { - return; - } - - if (mMessage->getVisible() && mFooter->getVisible()) - { - S32 heightDelta = 0; - S32 maxTextHeight = (mMessage->getFont()->getLineHeight() * MAX_LINES_COUNT) - + (mFooter->getFont()->getLineHeight() * MAX_LINES_COUNT); - - LLRect messageRect = mMessage->getRect(); - LLRect footerRect = mFooter->getRect(); - - S32 oldTextHeight = messageRect.getHeight() + footerRect.getHeight(); - - S32 requiredTextHeight = mMessage->getTextBoundingRect().getHeight() + mFooter->getTextBoundingRect().getHeight(); - S32 newTextHeight = llmin(requiredTextHeight, maxTextHeight); - - heightDelta = newTextHeight - oldTextHeight - heightDelta; - - reshape( getRect().getWidth(), llmax(getRect().getHeight() + heightDelta, MIN_PANEL_HEIGHT)); - } -} - -void LLToastScriptQuestion::createButtons() -{ - LLNotificationFormPtr form = mNotification->getForm(); - int num_elements = form->getNumElements(); - int buttons_width = 0; - - for (int i = 0; i < num_elements; ++i) - { - LLSD form_element = form->getElement(i); - if ("button" == form_element["type"].asString()) - { - LLButton::Params p; - const LLFontGL* font = LLFontGL::getFontSansSerif(); - p.name(form_element["name"].asString()); - p.label(form_element["text"].asString()); - p.layout("topleft"); - p.font(font); - p.rect.height(BUTTON_HEIGHT); - p.click_callback.function(boost::bind(&LLToastScriptQuestion::onButtonClicked, this, form_element["name"].asString())); - p.rect.left = LEFT_PAD; - p.rect.width = font->getWidth(form_element["text"].asString()); - p.auto_resize = true; - p.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM); - p.image_color(LLUIColorTable::instance().getColor("ButtonCautionImageColor")); - p.image_color_disabled(LLUIColorTable::instance().getColor("ButtonCautionImageColor")); - - LLButton* button = LLUICtrlFactory::create(p); - button->autoResize(); - getChild("buttons_panel")->addChild(button); - - LLRect rect = button->getRect(); - rect.setLeftTopAndSize(buttons_width, rect.mTop, rect.getWidth(), rect.getHeight()); - button->setRect(rect); - - buttons_width += rect.getWidth() + LEFT_PAD; - - if (form_element.has("default") && form_element["default"].asBoolean()) - { - button->setFocus(true); - setDefaultBtn(button); - } - } - } -} - -void LLToastScriptQuestion::onButtonClicked(std::string btn_name) -{ - LLSD response = mNotification->getResponseTemplate(); - response[btn_name] = true; - mNotification->respond(response); -} +/** + * @file lltoastscriptquestion.cpp + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llbutton.h" +#include "llnotifications.h" +#include "lltoastscriptquestion.h" + +const int LEFT_PAD = 10; +const int BUTTON_HEIGHT = 27; +const int MAX_LINES_COUNT = 50; + +LLToastScriptQuestion::LLToastScriptQuestion(const LLNotificationPtr& notification) +: +LLToastPanel(notification) +{ + buildFromFile("panel_script_question_toast.xml"); +} + +bool LLToastScriptQuestion::postBuild() +{ + createButtons(); + + LLTextBox* mMessage = getChild("top_info_message"); + LLTextBox* mFooter = getChild("bottom_info_message"); + + mMessage->setValue(mNotification->getMessage()); + mFooter->setValue(mNotification->getFooter()); + + snapToMessageHeight(); + + return true; +} + +// virtual +void LLToastScriptQuestion::setFocus(bool b) +{ + LLToastPanel::setFocus(b); + // toast can fade out and disappear with focus ON, so reset to default anyway + LLButton* dfbutton = getDefaultButton(); + if (dfbutton && dfbutton->getVisible() && dfbutton->getEnabled()) + { + dfbutton->setFocus(b); + } +} + +void LLToastScriptQuestion::snapToMessageHeight() +{ + LLTextBox* mMessage = getChild("top_info_message"); + LLTextBox* mFooter = getChild("bottom_info_message"); + if (!mMessage || !mFooter) + { + return; + } + + if (mMessage->getVisible() && mFooter->getVisible()) + { + S32 heightDelta = 0; + S32 maxTextHeight = (mMessage->getFont()->getLineHeight() * MAX_LINES_COUNT) + + (mFooter->getFont()->getLineHeight() * MAX_LINES_COUNT); + + LLRect messageRect = mMessage->getRect(); + LLRect footerRect = mFooter->getRect(); + + S32 oldTextHeight = messageRect.getHeight() + footerRect.getHeight(); + + S32 requiredTextHeight = mMessage->getTextBoundingRect().getHeight() + mFooter->getTextBoundingRect().getHeight(); + S32 newTextHeight = llmin(requiredTextHeight, maxTextHeight); + + heightDelta = newTextHeight - oldTextHeight - heightDelta; + + reshape( getRect().getWidth(), llmax(getRect().getHeight() + heightDelta, MIN_PANEL_HEIGHT)); + } +} + +void LLToastScriptQuestion::createButtons() +{ + LLNotificationFormPtr form = mNotification->getForm(); + int num_elements = form->getNumElements(); + int buttons_width = 0; + + for (int i = 0; i < num_elements; ++i) + { + LLSD form_element = form->getElement(i); + if ("button" == form_element["type"].asString()) + { + LLButton::Params p; + const LLFontGL* font = LLFontGL::getFontSansSerif(); + p.name(form_element["name"].asString()); + p.label(form_element["text"].asString()); + p.layout("topleft"); + p.font(font); + p.rect.height(BUTTON_HEIGHT); + p.click_callback.function(boost::bind(&LLToastScriptQuestion::onButtonClicked, this, form_element["name"].asString())); + p.rect.left = LEFT_PAD; + p.rect.width = font->getWidth(form_element["text"].asString()); + p.auto_resize = true; + p.follows.flags(FOLLOWS_LEFT | FOLLOWS_BOTTOM); + p.image_color(LLUIColorTable::instance().getColor("ButtonCautionImageColor")); + p.image_color_disabled(LLUIColorTable::instance().getColor("ButtonCautionImageColor")); + + LLButton* button = LLUICtrlFactory::create(p); + button->autoResize(); + getChild("buttons_panel")->addChild(button); + + LLRect rect = button->getRect(); + rect.setLeftTopAndSize(buttons_width, rect.mTop, rect.getWidth(), rect.getHeight()); + button->setRect(rect); + + buttons_width += rect.getWidth() + LEFT_PAD; + + if (form_element.has("default") && form_element["default"].asBoolean()) + { + button->setFocus(true); + setDefaultBtn(button); + } + } + } +} + +void LLToastScriptQuestion::onButtonClicked(std::string btn_name) +{ + LLSD response = mNotification->getResponseTemplate(); + response[btn_name] = true; + mNotification->respond(response); +} diff --git a/indra/newview/lltoastscriptquestion.h b/indra/newview/lltoastscriptquestion.h index 8ee88b71a6..41680df5c9 100644 --- a/indra/newview/lltoastscriptquestion.h +++ b/indra/newview/lltoastscriptquestion.h @@ -1,51 +1,51 @@ -/** - * @file lltoastscriptquestion.h - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lltoastpanel.h" - -#ifndef LLTOASTSCRIPTQUESTION_H_ -#define LLTOASTSCRIPTQUESTION_H_ - -class LLToastScriptQuestion : public LLToastPanel -{ - LOG_CLASS(LLToastScriptQuestion); - -public: - LLToastScriptQuestion(const LLNotificationPtr& notification); - bool postBuild() override; - virtual ~LLToastScriptQuestion(){}; - - void setFocus(bool b) override; - -private: - void snapToMessageHeight(); - - void createButtons(); - void onButtonClicked(std::string btn_name); -}; - -#endif /* LLTOASTSCRIPTQUESTION_H_ */ +/** + * @file lltoastscriptquestion.h + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2012, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lltoastpanel.h" + +#ifndef LLTOASTSCRIPTQUESTION_H_ +#define LLTOASTSCRIPTQUESTION_H_ + +class LLToastScriptQuestion : public LLToastPanel +{ + LOG_CLASS(LLToastScriptQuestion); + +public: + LLToastScriptQuestion(const LLNotificationPtr& notification); + bool postBuild() override; + virtual ~LLToastScriptQuestion(){}; + + void setFocus(bool b) override; + +private: + void snapToMessageHeight(); + + void createButtons(); + void onButtonClicked(std::string btn_name); +}; + +#endif /* LLTOASTSCRIPTQUESTION_H_ */ diff --git a/indra/newview/lltoastscripttextbox.cpp b/indra/newview/lltoastscripttextbox.cpp index 9956962e49..de555ca895 100644 --- a/indra/newview/lltoastscripttextbox.cpp +++ b/indra/newview/lltoastscripttextbox.cpp @@ -1,132 +1,132 @@ -/** - * @file lltoastscripttextbox.cpp - * @brief Panel for script llTextBox dialogs - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lltoastscripttextbox.h" - -#include "lldbstrings.h" -#include "lllslconstants.h" -#include "llnotifications.h" -#include "llstyle.h" -#include "lluiconstants.h" -#include "llviewertexteditor.h" - -const S32 LLToastScriptTextbox::DEFAULT_MESSAGE_MAX_LINE_COUNT= 14; - -LLToastScriptTextbox::LLToastScriptTextbox(const LLNotificationPtr& notification) -: LLToastPanel(notification) -{ - buildFromFile( "panel_notify_textbox.xml"); - - mInfoText = getChild("text_editor_box"); - mInfoText->setMaxTextLength(LLToastPanel::MAX_TEXT_LENGTH); - mInfoText->setValue(notification->getMessage()); - - getChild("ignore_btn")->setClickedCallback(boost::bind(&LLToastScriptTextbox::onClickIgnore, this)); - - const LLSD& payload = notification->getPayload(); - - //message body - const std::string& message = payload["message"].asString(); - - LLViewerTextEditor* pMessageText = getChild("message"); - pMessageText->clear(); - - LLStyle::Params style; - style.font = pMessageText->getFont(); - pMessageText->appendText(message, true, style); - - //submit button - LLButton* pSubmitBtn = getChild("btn_submit"); - pSubmitBtn->setClickedCallback((boost::bind(&LLToastScriptTextbox::onClickSubmit, this))); - setDefaultBtn(pSubmitBtn); - - snapToMessageHeight(); -} - -// virtual -LLToastScriptTextbox::~LLToastScriptTextbox() -{ -} - -void LLToastScriptTextbox::close() -{ - die(); -} - -void LLToastScriptTextbox::onClickSubmit() -{ - LLViewerTextEditor* pMessageText = getChild("message"); - - if (pMessageText) - { - LLSD response = mNotification->getResponseTemplate(); - response[TEXTBOX_MAGIC_TOKEN] = pMessageText->getText(); - if (response[TEXTBOX_MAGIC_TOKEN].asString().empty()) - { - // so we can distinguish between a successfully - // submitted blank textbox, and an ignored toast - response[TEXTBOX_MAGIC_TOKEN] = true; - } - mNotification->respond(response); - close(); - LL_WARNS() << response << LL_ENDL; - } -} - -void LLToastScriptTextbox::onClickIgnore() -{ - LLSD response = mNotification->getResponseTemplate(); - mNotification->respond(response); - close(); -} - -void LLToastScriptTextbox::snapToMessageHeight() -{ - LLPanel* info_pan = getChild("info_panel"); - if (!info_pan) - { - return; - } - - S32 maxLinesCount; - std::istringstream ss( getString("message_max_lines_count") ); - if (!(ss >> maxLinesCount)) - { - maxLinesCount = DEFAULT_MESSAGE_MAX_LINE_COUNT; - } - - - S32 maxTextHeight = (mInfoText->getFont()->getLineHeight() * maxLinesCount); - S32 oldTextHeight = mInfoText->getRect().getHeight(); - S32 newTextHeight = llmin(mInfoText->getTextBoundingRect().getHeight(), maxTextHeight); - - S32 heightDelta = newTextHeight - oldTextHeight; - - reshape( getRect().getWidth(), llmax(getRect().getHeight() + heightDelta, MIN_PANEL_HEIGHT)); - info_pan->reshape(info_pan->getRect().getWidth(),newTextHeight); -} +/** + * @file lltoastscripttextbox.cpp + * @brief Panel for script llTextBox dialogs + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lltoastscripttextbox.h" + +#include "lldbstrings.h" +#include "lllslconstants.h" +#include "llnotifications.h" +#include "llstyle.h" +#include "lluiconstants.h" +#include "llviewertexteditor.h" + +const S32 LLToastScriptTextbox::DEFAULT_MESSAGE_MAX_LINE_COUNT= 14; + +LLToastScriptTextbox::LLToastScriptTextbox(const LLNotificationPtr& notification) +: LLToastPanel(notification) +{ + buildFromFile( "panel_notify_textbox.xml"); + + mInfoText = getChild("text_editor_box"); + mInfoText->setMaxTextLength(LLToastPanel::MAX_TEXT_LENGTH); + mInfoText->setValue(notification->getMessage()); + + getChild("ignore_btn")->setClickedCallback(boost::bind(&LLToastScriptTextbox::onClickIgnore, this)); + + const LLSD& payload = notification->getPayload(); + + //message body + const std::string& message = payload["message"].asString(); + + LLViewerTextEditor* pMessageText = getChild("message"); + pMessageText->clear(); + + LLStyle::Params style; + style.font = pMessageText->getFont(); + pMessageText->appendText(message, true, style); + + //submit button + LLButton* pSubmitBtn = getChild("btn_submit"); + pSubmitBtn->setClickedCallback((boost::bind(&LLToastScriptTextbox::onClickSubmit, this))); + setDefaultBtn(pSubmitBtn); + + snapToMessageHeight(); +} + +// virtual +LLToastScriptTextbox::~LLToastScriptTextbox() +{ +} + +void LLToastScriptTextbox::close() +{ + die(); +} + +void LLToastScriptTextbox::onClickSubmit() +{ + LLViewerTextEditor* pMessageText = getChild("message"); + + if (pMessageText) + { + LLSD response = mNotification->getResponseTemplate(); + response[TEXTBOX_MAGIC_TOKEN] = pMessageText->getText(); + if (response[TEXTBOX_MAGIC_TOKEN].asString().empty()) + { + // so we can distinguish between a successfully + // submitted blank textbox, and an ignored toast + response[TEXTBOX_MAGIC_TOKEN] = true; + } + mNotification->respond(response); + close(); + LL_WARNS() << response << LL_ENDL; + } +} + +void LLToastScriptTextbox::onClickIgnore() +{ + LLSD response = mNotification->getResponseTemplate(); + mNotification->respond(response); + close(); +} + +void LLToastScriptTextbox::snapToMessageHeight() +{ + LLPanel* info_pan = getChild("info_panel"); + if (!info_pan) + { + return; + } + + S32 maxLinesCount; + std::istringstream ss( getString("message_max_lines_count") ); + if (!(ss >> maxLinesCount)) + { + maxLinesCount = DEFAULT_MESSAGE_MAX_LINE_COUNT; + } + + + S32 maxTextHeight = (mInfoText->getFont()->getLineHeight() * maxLinesCount); + S32 oldTextHeight = mInfoText->getRect().getHeight(); + S32 newTextHeight = llmin(mInfoText->getTextBoundingRect().getHeight(), maxTextHeight); + + S32 heightDelta = newTextHeight - oldTextHeight; + + reshape( getRect().getWidth(), llmax(getRect().getHeight() + heightDelta, MIN_PANEL_HEIGHT)); + info_pan->reshape(info_pan->getRect().getWidth(),newTextHeight); +} diff --git a/indra/newview/lltool.cpp b/indra/newview/lltool.cpp index 5618e64dd4..c595fc42ca 100644 --- a/indra/newview/lltool.cpp +++ b/indra/newview/lltool.cpp @@ -1,210 +1,210 @@ -/** - * @file lltool.cpp - * @brief LLTool class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lltool.h" - -#include "indra_constants.h" -#include "llerror.h" -#include "llview.h" - -#include "llviewerwindow.h" -#include "llviewercontrol.h" -#include "lltoolcomp.h" -#include "lltoolfocus.h" -#include "llfocusmgr.h" -#include "llagent.h" -#include "llviewerjoystick.h" - -extern bool gDebugClicks; - -//static -const std::string LLTool::sNameNull("null"); - -LLTool::LLTool( const std::string& name, LLToolComposite* composite ) : - mComposite( composite ), - mName(name) -{ -} - -LLTool::~LLTool() -{ - if( hasMouseCapture() ) - { - LL_WARNS() << "Tool deleted holding mouse capture. Mouse capture removed." << LL_ENDL; - gFocusMgr.removeMouseCaptureWithoutCallback( this ); - } -} - -bool LLTool::handleAnyMouseClick(S32 x, S32 y, MASK mask, EMouseClickType clicktype, bool down) -{ - bool result = LLMouseHandler::handleAnyMouseClick(x, y, mask, clicktype, down); - - // This behavior was moved here from LLViewerWindow::handleAnyMouseClick, so it can be selectively overridden by LLTool subclasses. - if(down && result) - { - // This is necessary to force clicks in the world to cause edit - // boxes that might have keyboard focus to relinquish it, and hence - // cause a commit to update their value. JC - gFocusMgr.setKeyboardFocus(NULL); - } - - return result; -} - -bool LLTool::handleMouseDown(S32 x, S32 y, MASK mask) -{ - if (gDebugClicks) - { - LL_INFOS() << "LLTool left mouse down" << LL_ENDL; - } - // by default, didn't handle it - // AGENT_CONTROL_LBUTTON_DOWN is handled by scanMouse() and scanKey() - // LL_INFOS() << "LLTool::handleMouseDown" << LL_ENDL; - return false; -} - -bool LLTool::handleMouseUp(S32 x, S32 y, MASK mask) -{ - if (gDebugClicks) - { - LL_INFOS() << "LLTool left mouse up" << LL_ENDL; - } - // by default, didn't handle it - // AGENT_CONTROL_LBUTTON_UP is handled by scanMouse() and scanKey() - // LL_INFOS() << "LLTool::handleMouseUp" << LL_ENDL; - return true; -} - -bool LLTool::handleHover(S32 x, S32 y, MASK mask) -{ - gViewerWindow->setCursor(UI_CURSOR_ARROW); - LL_DEBUGS("UserInput") << "hover handled by a tool" << LL_ENDL; - // by default, do nothing, say we handled it - return true; -} - -bool LLTool::handleScrollWheel(S32 x, S32 y, S32 clicks) -{ - // by default, didn't handle it - // LL_INFOS() << "LLTool::handleScrollWheel" << LL_ENDL; - return false; -} - -bool LLTool::handleScrollHWheel(S32 x, S32 y, S32 clicks) -{ - // by default, didn't handle it - return false; -} - -bool LLTool::handleDoubleClick(S32 x,S32 y,MASK mask) -{ - // LL_INFOS() << "LLTool::handleDoubleClick" << LL_ENDL; - // by default, pretend it's a left click - return false; -} - -bool LLTool::handleRightMouseDown(S32 x,S32 y,MASK mask) -{ - // by default, didn't handle it - // LL_INFOS() << "LLTool::handleRightMouseDown" << LL_ENDL; - return false; -} - -bool LLTool::handleRightMouseUp(S32 x, S32 y, MASK mask) -{ - // by default, didn't handle it - // LL_INFOS() << "LLTool::handleRightMouseDown" << LL_ENDL; - return false; -} - -bool LLTool::handleMiddleMouseDown(S32 x,S32 y,MASK mask) -{ - // by default, didn't handle it - // LL_INFOS() << "LLTool::handleMiddleMouseDown" << LL_ENDL; - return false; -} - -bool LLTool::handleMiddleMouseUp(S32 x, S32 y, MASK mask) -{ - // by default, didn't handle it - // LL_INFOS() << "LLTool::handleMiddleMouseUp" << LL_ENDL; - return false; -} - -bool LLTool::handleToolTip(S32 x, S32 y, MASK mask) -{ - // by default, didn't handle it - // LL_INFOS() << "LLTool::handleToolTip" << LL_ENDL; - return false; -} - -void LLTool::setMouseCapture( bool b ) -{ - if( b ) - { - gFocusMgr.setMouseCapture(mComposite ? mComposite : this ); - } - else - if( hasMouseCapture() ) - { - gFocusMgr.setMouseCapture( NULL ); - } -} - -// virtual -void LLTool::draw() -{ } - -bool LLTool::hasMouseCapture() -{ - return gFocusMgr.getMouseCapture() == (mComposite ? mComposite : this); -} - -bool LLTool::handleKey(KEY key, MASK mask) -{ - return false; -} - -LLTool* LLTool::getOverrideTool(MASK mask) -{ - // NOTE: if in flycam mode, ALT-ZOOM camera should be disabled - if (LLViewerJoystick::getInstance()->getOverrideCamera()) - { - return NULL; - } - - static LLCachedControl alt_zoom(gSavedSettings, "EnableAltZoom", true); - if (alt_zoom) - { - if (mask & MASK_ALT) - { - return LLToolCamera::getInstance(); - } - } - return NULL; -} +/** + * @file lltool.cpp + * @brief LLTool class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lltool.h" + +#include "indra_constants.h" +#include "llerror.h" +#include "llview.h" + +#include "llviewerwindow.h" +#include "llviewercontrol.h" +#include "lltoolcomp.h" +#include "lltoolfocus.h" +#include "llfocusmgr.h" +#include "llagent.h" +#include "llviewerjoystick.h" + +extern bool gDebugClicks; + +//static +const std::string LLTool::sNameNull("null"); + +LLTool::LLTool( const std::string& name, LLToolComposite* composite ) : + mComposite( composite ), + mName(name) +{ +} + +LLTool::~LLTool() +{ + if( hasMouseCapture() ) + { + LL_WARNS() << "Tool deleted holding mouse capture. Mouse capture removed." << LL_ENDL; + gFocusMgr.removeMouseCaptureWithoutCallback( this ); + } +} + +bool LLTool::handleAnyMouseClick(S32 x, S32 y, MASK mask, EMouseClickType clicktype, bool down) +{ + bool result = LLMouseHandler::handleAnyMouseClick(x, y, mask, clicktype, down); + + // This behavior was moved here from LLViewerWindow::handleAnyMouseClick, so it can be selectively overridden by LLTool subclasses. + if(down && result) + { + // This is necessary to force clicks in the world to cause edit + // boxes that might have keyboard focus to relinquish it, and hence + // cause a commit to update their value. JC + gFocusMgr.setKeyboardFocus(NULL); + } + + return result; +} + +bool LLTool::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (gDebugClicks) + { + LL_INFOS() << "LLTool left mouse down" << LL_ENDL; + } + // by default, didn't handle it + // AGENT_CONTROL_LBUTTON_DOWN is handled by scanMouse() and scanKey() + // LL_INFOS() << "LLTool::handleMouseDown" << LL_ENDL; + return false; +} + +bool LLTool::handleMouseUp(S32 x, S32 y, MASK mask) +{ + if (gDebugClicks) + { + LL_INFOS() << "LLTool left mouse up" << LL_ENDL; + } + // by default, didn't handle it + // AGENT_CONTROL_LBUTTON_UP is handled by scanMouse() and scanKey() + // LL_INFOS() << "LLTool::handleMouseUp" << LL_ENDL; + return true; +} + +bool LLTool::handleHover(S32 x, S32 y, MASK mask) +{ + gViewerWindow->setCursor(UI_CURSOR_ARROW); + LL_DEBUGS("UserInput") << "hover handled by a tool" << LL_ENDL; + // by default, do nothing, say we handled it + return true; +} + +bool LLTool::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + // by default, didn't handle it + // LL_INFOS() << "LLTool::handleScrollWheel" << LL_ENDL; + return false; +} + +bool LLTool::handleScrollHWheel(S32 x, S32 y, S32 clicks) +{ + // by default, didn't handle it + return false; +} + +bool LLTool::handleDoubleClick(S32 x,S32 y,MASK mask) +{ + // LL_INFOS() << "LLTool::handleDoubleClick" << LL_ENDL; + // by default, pretend it's a left click + return false; +} + +bool LLTool::handleRightMouseDown(S32 x,S32 y,MASK mask) +{ + // by default, didn't handle it + // LL_INFOS() << "LLTool::handleRightMouseDown" << LL_ENDL; + return false; +} + +bool LLTool::handleRightMouseUp(S32 x, S32 y, MASK mask) +{ + // by default, didn't handle it + // LL_INFOS() << "LLTool::handleRightMouseDown" << LL_ENDL; + return false; +} + +bool LLTool::handleMiddleMouseDown(S32 x,S32 y,MASK mask) +{ + // by default, didn't handle it + // LL_INFOS() << "LLTool::handleMiddleMouseDown" << LL_ENDL; + return false; +} + +bool LLTool::handleMiddleMouseUp(S32 x, S32 y, MASK mask) +{ + // by default, didn't handle it + // LL_INFOS() << "LLTool::handleMiddleMouseUp" << LL_ENDL; + return false; +} + +bool LLTool::handleToolTip(S32 x, S32 y, MASK mask) +{ + // by default, didn't handle it + // LL_INFOS() << "LLTool::handleToolTip" << LL_ENDL; + return false; +} + +void LLTool::setMouseCapture( bool b ) +{ + if( b ) + { + gFocusMgr.setMouseCapture(mComposite ? mComposite : this ); + } + else + if( hasMouseCapture() ) + { + gFocusMgr.setMouseCapture( NULL ); + } +} + +// virtual +void LLTool::draw() +{ } + +bool LLTool::hasMouseCapture() +{ + return gFocusMgr.getMouseCapture() == (mComposite ? mComposite : this); +} + +bool LLTool::handleKey(KEY key, MASK mask) +{ + return false; +} + +LLTool* LLTool::getOverrideTool(MASK mask) +{ + // NOTE: if in flycam mode, ALT-ZOOM camera should be disabled + if (LLViewerJoystick::getInstance()->getOverrideCamera()) + { + return NULL; + } + + static LLCachedControl alt_zoom(gSavedSettings, "EnableAltZoom", true); + if (alt_zoom) + { + if (mask & MASK_ALT) + { + return LLToolCamera::getInstance(); + } + } + return NULL; +} diff --git a/indra/newview/lltool.h b/indra/newview/lltool.h index f94fe27ac7..10af156395 100644 --- a/indra/newview/lltool.h +++ b/indra/newview/lltool.h @@ -1,109 +1,109 @@ -/** - * @file lltool.h - * @brief LLTool class header file - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLTOOL_H -#define LL_LLTOOL_H - -#include "llkeyboard.h" -#include "llmousehandler.h" -#include "llcoord.h" -#include "v3math.h" -#include "v3dmath.h" - -class LLViewerObject; -class LLToolComposite; -class LLView; -class LLPanel; - -class LLTool -: public LLMouseHandler, public LLThreadSafeRefCount -{ -public: - LLTool( const std::string& name, LLToolComposite* composite = NULL ); - virtual ~LLTool(); - - // Hack to support LLFocusMgr - virtual bool isView() const { return false; } - - // Virtual functions inherited from LLMouseHandler - virtual bool handleAnyMouseClick(S32 x, S32 y, MASK mask, EMouseClickType clicktype, bool down); - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - virtual bool handleMiddleMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleMiddleMouseUp(S32 x, S32 y, MASK mask); - - virtual bool handleHover(S32 x, S32 y, MASK mask); - virtual bool handleScrollWheel(S32 x, S32 y, S32 clicks); - virtual bool handleScrollHWheel(S32 x, S32 y, S32 clicks); - virtual bool handleDoubleClick(S32 x, S32 y, MASK mask); - virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleRightMouseUp(S32 x, S32 y, MASK mask); - virtual bool handleToolTip(S32 x, S32 y, MASK mask); - - // Return false to allow context menu to be shown. - virtual void screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const - { *local_x = screen_x; *local_y = screen_y; } - virtual void localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const - { *screen_x = local_x; *screen_y = local_y; } - - virtual const std::string& getName() const { return mName; } - - // New virtual functions - virtual LLViewerObject* getEditingObject() { return NULL; } - virtual LLVector3d getEditingPointGlobal() { return LLVector3d(); } - virtual bool isEditing() { return (getEditingObject() != NULL); } - virtual void stopEditing() {} - - virtual bool clipMouseWhenDown() { return true; } - - virtual void handleSelect() { } // do stuff when your tool is selected - virtual void handleDeselect() { } // clean up when your tool is deselected - - virtual LLTool* getOverrideTool(MASK mask); - - // isAlwaysRendered() - return true if this is a tool that should - // always be rendered regardless of selection. - virtual bool isAlwaysRendered() { return false; } - - virtual void render() {} // draw tool specific 3D content in world - virtual void draw(); // draw tool specific 2D overlay - - virtual bool handleKey(KEY key, MASK mask); - - // Note: NOT virtual. Subclasses should call this version. - void setMouseCapture(bool b); - bool hasMouseCapture(); - virtual void onMouseCaptureLost() {} // override this one as needed. - -protected: - LLToolComposite* mComposite; // Composite will handle mouse captures. - std::string mName; - -public: - static const std::string sNameNull; -}; - -#endif +/** + * @file lltool.h + * @brief LLTool class header file + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLTOOL_H +#define LL_LLTOOL_H + +#include "llkeyboard.h" +#include "llmousehandler.h" +#include "llcoord.h" +#include "v3math.h" +#include "v3dmath.h" + +class LLViewerObject; +class LLToolComposite; +class LLView; +class LLPanel; + +class LLTool +: public LLMouseHandler, public LLThreadSafeRefCount +{ +public: + LLTool( const std::string& name, LLToolComposite* composite = NULL ); + virtual ~LLTool(); + + // Hack to support LLFocusMgr + virtual bool isView() const { return false; } + + // Virtual functions inherited from LLMouseHandler + virtual bool handleAnyMouseClick(S32 x, S32 y, MASK mask, EMouseClickType clicktype, bool down); + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + virtual bool handleMiddleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleMiddleMouseUp(S32 x, S32 y, MASK mask); + + virtual bool handleHover(S32 x, S32 y, MASK mask); + virtual bool handleScrollWheel(S32 x, S32 y, S32 clicks); + virtual bool handleScrollHWheel(S32 x, S32 y, S32 clicks); + virtual bool handleDoubleClick(S32 x, S32 y, MASK mask); + virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleRightMouseUp(S32 x, S32 y, MASK mask); + virtual bool handleToolTip(S32 x, S32 y, MASK mask); + + // Return false to allow context menu to be shown. + virtual void screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const + { *local_x = screen_x; *local_y = screen_y; } + virtual void localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const + { *screen_x = local_x; *screen_y = local_y; } + + virtual const std::string& getName() const { return mName; } + + // New virtual functions + virtual LLViewerObject* getEditingObject() { return NULL; } + virtual LLVector3d getEditingPointGlobal() { return LLVector3d(); } + virtual bool isEditing() { return (getEditingObject() != NULL); } + virtual void stopEditing() {} + + virtual bool clipMouseWhenDown() { return true; } + + virtual void handleSelect() { } // do stuff when your tool is selected + virtual void handleDeselect() { } // clean up when your tool is deselected + + virtual LLTool* getOverrideTool(MASK mask); + + // isAlwaysRendered() - return true if this is a tool that should + // always be rendered regardless of selection. + virtual bool isAlwaysRendered() { return false; } + + virtual void render() {} // draw tool specific 3D content in world + virtual void draw(); // draw tool specific 2D overlay + + virtual bool handleKey(KEY key, MASK mask); + + // Note: NOT virtual. Subclasses should call this version. + void setMouseCapture(bool b); + bool hasMouseCapture(); + virtual void onMouseCaptureLost() {} // override this one as needed. + +protected: + LLToolComposite* mComposite; // Composite will handle mouse captures. + std::string mName; + +public: + static const std::string sNameNull; +}; + +#endif diff --git a/indra/newview/lltoolbarview.cpp b/indra/newview/lltoolbarview.cpp index aca258f0e8..5180b1808c 100644 --- a/indra/newview/lltoolbarview.cpp +++ b/indra/newview/lltoolbarview.cpp @@ -1,718 +1,718 @@ -/** - * @file lltoolbarview.cpp - * @author Merov Linden - * @brief User customizable toolbar class - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lltoolbarview.h" - -#include "llappviewer.h" -#include "llbutton.h" -#include "llclipboard.h" -#include "lldir.h" -#include "lldockablefloater.h" -#include "lldockcontrol.h" -#include "llimview.h" -#include "lltransientfloatermgr.h" -#include "lltoolbar.h" -#include "lltooldraganddrop.h" -#include "llxmlnode.h" - -#include "llagent.h" // HACK for destinations guide on startup -#include "llfloaterreg.h" // HACK for destinations guide on startup -#include "llviewercontrol.h" // HACK for destinations guide on startup -#include "llinventorymodel.h" // HACK to disable starter avatars button for NUX - -LLToolBarView* gToolBarView = NULL; - -static LLDefaultChildRegistry::Register r("toolbar_view"); - -bool isToolDragged() -{ - return (LLToolDragAndDrop::getInstance()->getSource() == LLToolDragAndDrop::SOURCE_VIEWER); -} - -LLToolBarView::Toolbar::Toolbar() -: button_display_mode("button_display_mode"), - commands("command") -{} - -LLToolBarView::ToolbarSet::ToolbarSet() -: left_toolbar("left_toolbar"), - right_toolbar("right_toolbar"), - bottom_toolbar("bottom_toolbar") -{} - - -LLToolBarView::LLToolBarView(const LLToolBarView::Params& p) -: LLUICtrl(p), - mDragStarted(false), - mShowToolbars(true), - mDragToolbarButton(NULL), - mDragItem(NULL), - mToolbarsLoaded(false), - mBottomToolbarPanel(NULL) -{ - for (S32 i = 0; i < LLToolBarEnums::TOOLBAR_COUNT; i++) - { - mToolbars[i] = NULL; - } -} - -void LLToolBarView::initFromParams(const LLToolBarView::Params& p) -{ - // Initialize the base object - LLUICtrl::initFromParams(p); -} - -LLToolBarView::~LLToolBarView() -{ - saveToolbars(); -} - -bool LLToolBarView::postBuild() -{ - mToolbars[LLToolBarEnums::TOOLBAR_LEFT] = getChild("toolbar_left"); - mToolbars[LLToolBarEnums::TOOLBAR_LEFT]->getCenterLayoutPanel()->setLocationId(LLToolBarEnums::TOOLBAR_LEFT); - - mToolbars[LLToolBarEnums::TOOLBAR_RIGHT] = getChild("toolbar_right"); - mToolbars[LLToolBarEnums::TOOLBAR_RIGHT]->getCenterLayoutPanel()->setLocationId(LLToolBarEnums::TOOLBAR_RIGHT); - - mToolbars[LLToolBarEnums::TOOLBAR_BOTTOM] = getChild("toolbar_bottom"); - mToolbars[LLToolBarEnums::TOOLBAR_BOTTOM]->getCenterLayoutPanel()->setLocationId(LLToolBarEnums::TOOLBAR_BOTTOM); - - mBottomToolbarPanel = getChild("bottom_toolbar_panel"); - - for (int i = LLToolBarEnums::TOOLBAR_FIRST; i <= LLToolBarEnums::TOOLBAR_LAST; i++) - { - mToolbars[i]->setStartDragCallback(boost::bind(LLToolBarView::startDragTool,_1,_2,_3)); - mToolbars[i]->setHandleDragCallback(boost::bind(LLToolBarView::handleDragTool,_1,_2,_3,_4)); - mToolbars[i]->setHandleDropCallback(boost::bind(LLToolBarView::handleDropTool,_1,_2,_3,_4)); - mToolbars[i]->setButtonAddCallback(boost::bind(LLToolBarView::onToolBarButtonAdded,_1)); - mToolbars[i]->setButtonRemoveCallback(boost::bind(LLToolBarView::onToolBarButtonRemoved,_1)); - } - - return true; -} - -S32 LLToolBarView::hasCommand(const LLCommandId& commandId) const -{ - S32 command_location = LLToolBarEnums::TOOLBAR_NONE; - - for (S32 loc = LLToolBarEnums::TOOLBAR_FIRST; loc <= LLToolBarEnums::TOOLBAR_LAST; loc++) - { - if (mToolbars[loc]->hasCommand(commandId)) - { - command_location = loc; - break; - } - } - - return command_location; -} - -S32 LLToolBarView::addCommand(const LLCommandId& commandId, LLToolBarEnums::EToolBarLocation toolbar, int rank) -{ - int old_rank; - removeCommand(commandId, old_rank); - - S32 command_location = mToolbars[toolbar]->addCommand(commandId, rank); - - return command_location; -} - -S32 LLToolBarView::removeCommand(const LLCommandId& commandId, int& rank) -{ - S32 command_location = hasCommand(commandId); - rank = LLToolBar::RANK_NONE; - - if (command_location != LLToolBarEnums::TOOLBAR_NONE) - { - rank = mToolbars[command_location]->removeCommand(commandId); - } - - return command_location; -} - -S32 LLToolBarView::enableCommand(const LLCommandId& commandId, bool enabled) -{ - S32 command_location = hasCommand(commandId); - - if (command_location != LLToolBarEnums::TOOLBAR_NONE) - { - mToolbars[command_location]->enableCommand(commandId, enabled); - } - - return command_location; -} - -S32 LLToolBarView::stopCommandInProgress(const LLCommandId& commandId) -{ - S32 command_location = hasCommand(commandId); - - if (command_location != LLToolBarEnums::TOOLBAR_NONE) - { - mToolbars[command_location]->stopCommandInProgress(commandId); - } - - return command_location; -} - -S32 LLToolBarView::flashCommand(const LLCommandId& commandId, bool flash, bool force_flashing/* = false */) -{ - S32 command_location = hasCommand(commandId); - - if (command_location != LLToolBarEnums::TOOLBAR_NONE) - { - mToolbars[command_location]->flashCommand(commandId, flash, force_flashing); - } - - return command_location; -} - -bool LLToolBarView::addCommandInternal(const LLCommandId& command, LLToolBar* toolbar) -{ - LLCommandManager& mgr = LLCommandManager::instance(); - if (mgr.getCommand(command)) - { - toolbar->addCommand(command); - } - else - { - LL_WARNS() << "Toolbars creation : the command with id " << command.uuid().asString() << " cannot be found in the command manager" << LL_ENDL; - return false; - } - return true; -} - -bool LLToolBarView::loadToolbars(bool force_default) -{ - LLToolBarView::ToolbarSet toolbar_set; - bool err = false; - - // Load the toolbars.xml file - std::string toolbar_file = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "toolbars.xml"); - if (force_default) - { - toolbar_file = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "toolbars.xml"); - } - else if (!gDirUtilp->fileExists(toolbar_file)) - { - LL_WARNS() << "User toolbars def not found -> use default" << LL_ENDL; - toolbar_file = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "toolbars.xml"); - } - - LLXMLNodePtr root; - if(!LLXMLNode::parseFile(toolbar_file, root, NULL)) - { - LL_WARNS() << "Unable to load toolbars from file: " << toolbar_file << LL_ENDL; - err = true; - } - - if (!err && !root->hasName("toolbars")) - { - LL_WARNS() << toolbar_file << " is not a valid toolbars definition file" << LL_ENDL; - err = true; - } - - // Parse the toolbar settings - LLXUIParser parser; - if (!err) - { - parser.readXUI(root, toolbar_set, toolbar_file); - } - - if (!err && !toolbar_set.validateBlock()) - { - LL_WARNS() << "Unable to validate toolbars from file: " << toolbar_file << LL_ENDL; - err = true; - } - - if (err) - { - if (force_default) - { - LL_ERRS() << "Unable to load toolbars from default file : " << toolbar_file << LL_ENDL; - return false; - } - - // Try to load the default toolbars - return loadToolbars(true); - } - - // Clear the toolbars now before adding the loaded commands and settings - for (S32 i = LLToolBarEnums::TOOLBAR_FIRST; i <= LLToolBarEnums::TOOLBAR_LAST; i++) - { - if (mToolbars[i]) - { - mToolbars[i]->clearCommandsList(); - } - } - - // Add commands to each toolbar - if (toolbar_set.left_toolbar.isProvided() && mToolbars[LLToolBarEnums::TOOLBAR_LEFT]) - { - if (toolbar_set.left_toolbar.button_display_mode.isProvided()) - { - LLToolBarEnums::ButtonType button_type = toolbar_set.left_toolbar.button_display_mode; - mToolbars[LLToolBarEnums::TOOLBAR_LEFT]->setButtonType(button_type); - } - for (const LLCommandId::Params& command_params : toolbar_set.left_toolbar.commands) - { - if (!addCommandInternal(LLCommandId(command_params), mToolbars[LLToolBarEnums::TOOLBAR_LEFT])) - { - LL_WARNS() << "Error adding command '" << command_params.name() << "' to left toolbar." << LL_ENDL; - } - } - } - if (toolbar_set.right_toolbar.isProvided() && mToolbars[LLToolBarEnums::TOOLBAR_RIGHT]) - { - if (toolbar_set.right_toolbar.button_display_mode.isProvided()) - { - LLToolBarEnums::ButtonType button_type = toolbar_set.right_toolbar.button_display_mode; - mToolbars[LLToolBarEnums::TOOLBAR_RIGHT]->setButtonType(button_type); - } - for (const LLCommandId::Params& command_params : toolbar_set.right_toolbar.commands) - { - if (!addCommandInternal(LLCommandId(command_params), mToolbars[LLToolBarEnums::TOOLBAR_RIGHT])) - { - LL_WARNS() << "Error adding command '" << command_params.name() << "' to right toolbar." << LL_ENDL; - } - } - } - if (toolbar_set.bottom_toolbar.isProvided() && mToolbars[LLToolBarEnums::TOOLBAR_BOTTOM]) - { - if (toolbar_set.bottom_toolbar.button_display_mode.isProvided()) - { - LLToolBarEnums::ButtonType button_type = toolbar_set.bottom_toolbar.button_display_mode; - mToolbars[LLToolBarEnums::TOOLBAR_BOTTOM]->setButtonType(button_type); - } - for (const LLCommandId::Params& command_params : toolbar_set.bottom_toolbar.commands) - { - if (!addCommandInternal(LLCommandId(command_params), mToolbars[LLToolBarEnums::TOOLBAR_BOTTOM])) - { - LL_WARNS() << "Error adding command '" << command_params.name() << "' to bottom toolbar." << LL_ENDL; - } - } - } - - // SL-18581: Don't show the starter avatar toolbar button for NUX users - if (gAgent.isFirstLogin()) - { - LLViewerInventoryCategory* my_outfits_cat = gInventory.getCategory(gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS)); - LL_WARNS() << "First login: checking for NUX user." << LL_ENDL; - if (my_outfits_cat != NULL && my_outfits_cat->getDescendentCount() > 0) - { - LL_WARNS() << "First login: My Outfits folder is not empty, removing the avatar picker button." << LL_ENDL; - for (S32 i = LLToolBarEnums::TOOLBAR_FIRST; i <= LLToolBarEnums::TOOLBAR_LAST; i++) - { - if (mToolbars[i]) - { - mToolbars[i]->removeCommand(LLCommandId("avatar")); - } - } - } - } - - mToolbarsLoaded = true; - return true; -} - -bool LLToolBarView::clearToolbars() -{ - for (S32 i = LLToolBarEnums::TOOLBAR_FIRST; i <= LLToolBarEnums::TOOLBAR_LAST; i++) - { - if (mToolbars[i]) - { - mToolbars[i]->clearCommandsList(); - } - } - - return true; -} - -//static -bool LLToolBarView::loadDefaultToolbars() -{ - bool retval = false; - - if (gToolBarView) - { - retval = gToolBarView->loadToolbars(true); - if (retval) - { - gToolBarView->saveToolbars(); - } - } - - return retval; -} - -//static -bool LLToolBarView::clearAllToolbars() -{ - bool retval = false; - - if (gToolBarView) - { - retval = gToolBarView->clearToolbars(); - if (retval) - { - gToolBarView->saveToolbars(); - } - } - - return retval; -} - -void LLToolBarView::saveToolbars() const -{ - if (!mToolbarsLoaded) - return; - - // Build the parameter tree from the toolbar data - LLToolBarView::ToolbarSet toolbar_set; - if (mToolbars[LLToolBarEnums::TOOLBAR_LEFT]) - { - toolbar_set.left_toolbar.button_display_mode = mToolbars[LLToolBarEnums::TOOLBAR_LEFT]->getButtonType(); - addToToolset(mToolbars[LLToolBarEnums::TOOLBAR_LEFT]->getCommandsList(), toolbar_set.left_toolbar); - } - if (mToolbars[LLToolBarEnums::TOOLBAR_RIGHT]) - { - toolbar_set.right_toolbar.button_display_mode = mToolbars[LLToolBarEnums::TOOLBAR_RIGHT]->getButtonType(); - addToToolset(mToolbars[LLToolBarEnums::TOOLBAR_RIGHT]->getCommandsList(), toolbar_set.right_toolbar); - } - if (mToolbars[LLToolBarEnums::TOOLBAR_BOTTOM]) - { - toolbar_set.bottom_toolbar.button_display_mode = mToolbars[LLToolBarEnums::TOOLBAR_BOTTOM]->getButtonType(); - addToToolset(mToolbars[LLToolBarEnums::TOOLBAR_BOTTOM]->getCommandsList(), toolbar_set.bottom_toolbar); - } - - // Serialize the parameter tree - LLXMLNodePtr output_node = new LLXMLNode("toolbars", false); - LLXUIParser parser; - parser.writeXUI(output_node, toolbar_set); - - // Write the resulting XML to file - if(!output_node->isNull()) - { - const std::string& filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "toolbars.xml"); - LLFILE *fp = LLFile::fopen(filename, "w"); - if (fp != NULL) - { - LLXMLNode::writeHeaderToFile(fp); - output_node->writeToFile(fp); - fclose(fp); - } - } -} - -// Enumerate the commands in command_list and add them as Params to the toolbar -void LLToolBarView::addToToolset(command_id_list_t& command_list, Toolbar& toolbar) const -{ - LLCommandManager& mgr = LLCommandManager::instance(); - - for (command_id_list_t::const_iterator it = command_list.begin(); - it != command_list.end(); - ++it) - { - LLCommand* command = mgr.getCommand(*it); - if (command) - { - LLCommandId::Params command_name_param; - command_name_param.name = command->name(); - toolbar.commands.add(command_name_param); - } - } -} - -void LLToolBarView::onToolBarButtonAdded(LLView* button) -{ - llassert(button); - - if (button->getName() == "speak") - { - // Add the "Speak" button as a control view in LLTransientFloaterMgr - // to prevent hiding the transient IM floater upon pressing "Speak". - LLTransientFloaterMgr::getInstance()->addControlView(button); - - // Redock incoming and/or outgoing call windows, if applicable - - LLFloater* incoming_floater = LLFloaterReg::getLastFloaterInGroup("incoming_call"); - LLFloater* outgoing_floater = LLFloaterReg::getLastFloaterInGroup("outgoing_call"); - - if (incoming_floater && incoming_floater->isShown()) - { - LLCallDialog* incoming = dynamic_cast(incoming_floater); - llassert(incoming); - - LLDockControl* dock_control = incoming->getDockControl(); - if (dock_control->getDock() == NULL) - { - incoming->dockToToolbarButton("speak"); - } - } - - if (outgoing_floater && outgoing_floater->isShown()) - { - LLCallDialog* outgoing = dynamic_cast(outgoing_floater); - llassert(outgoing); - - LLDockControl* dock_control = outgoing->getDockControl(); - if (dock_control->getDock() == NULL) - { - outgoing->dockToToolbarButton("speak"); - } - } - } - else if (button->getName() == "voice") - { - // Add the "Voice controls" button as a control view in LLTransientFloaterMgr - // to prevent hiding the transient IM floater upon pressing "Voice controls". - LLTransientFloaterMgr::getInstance()->addControlView(button); - } -} - -void LLToolBarView::onToolBarButtonRemoved(LLView* button) -{ - llassert(button); - - if (button->getName() == "speak") - { - LLTransientFloaterMgr::getInstance()->removeControlView(button); - - // Undock incoming and/or outgoing call windows - - LLFloater* incoming_floater = LLFloaterReg::getLastFloaterInGroup("incoming_call"); - LLFloater* outgoing_floater = LLFloaterReg::getLastFloaterInGroup("outgoing_call"); - - if (incoming_floater && incoming_floater->isShown()) - { - LLDockableFloater* incoming = dynamic_cast(incoming_floater); - llassert(incoming); - - LLDockControl* dock_control = incoming->getDockControl(); - dock_control->setDock(NULL); - } - - if (outgoing_floater && outgoing_floater->isShown()) - { - LLDockableFloater* outgoing = dynamic_cast(outgoing_floater); - llassert(outgoing); - - LLDockControl* dock_control = outgoing->getDockControl(); - dock_control->setDock(NULL); - } - } - else if (button->getName() == "voice") - { - LLTransientFloaterMgr::getInstance()->removeControlView(button); - } -} - -void LLToolBarView::draw() -{ - LLRect toolbar_rects[LLToolBarEnums::TOOLBAR_COUNT]; - - for (S32 i = LLToolBarEnums::TOOLBAR_FIRST; i <= LLToolBarEnums::TOOLBAR_LAST; i++) - { - if (mToolbars[i]) - { - LLView::EOrientation orientation = LLToolBarEnums::getOrientation(mToolbars[i]->getSideType()); - - if (orientation == LLLayoutStack::HORIZONTAL) - { - mToolbars[i]->getParent()->reshape(mToolbars[i]->getParent()->getRect().getWidth(), mToolbars[i]->getRect().getHeight()); - } - else - { - mToolbars[i]->getParent()->reshape(mToolbars[i]->getRect().getWidth(), mToolbars[i]->getParent()->getRect().getHeight()); - } - - mToolbars[i]->localRectToOtherView(mToolbars[i]->getLocalRect(), &toolbar_rects[i], this); - } - } - - for (S32 i = LLToolBarEnums::TOOLBAR_FIRST; i <= LLToolBarEnums::TOOLBAR_LAST; i++) - { - mToolbars[i]->getParent()->setVisible(mShowToolbars - && (mToolbars[i]->hasButtons() - || isToolDragged())); - } - - // Draw drop zones if drop of a tool is active - if (isToolDragged()) - { - LLColor4 drop_color = LLUIColorTable::instance().getColor( "ToolbarDropZoneColor" ); - - for (S32 i = LLToolBarEnums::TOOLBAR_FIRST; i <= LLToolBarEnums::TOOLBAR_LAST; i++) - { - gl_rect_2d(toolbar_rects[i], drop_color, true); - } - } - - LLUICtrl::draw(); -} - - -// ---------------------------------------- -// Drag and Drop Handling -// ---------------------------------------- - - -void LLToolBarView::startDragTool(S32 x, S32 y, LLToolBarButton* toolbarButton) -{ - resetDragTool(toolbarButton); - - // Flag the tool dragging but don't start it yet - LLToolDragAndDrop::getInstance()->setDragStart( x, y ); -} - -bool LLToolBarView::handleDragTool( S32 x, S32 y, const LLUUID& uuid, LLAssetType::EType type) -{ - if (LLToolDragAndDrop::getInstance()->isOverThreshold( x, y )) - { - if (!gToolBarView->mDragStarted) - { - // Start the tool dragging: - - // First, create the global drag and drop object - std::vector types; - uuid_vec_t cargo_ids; - types.push_back(DAD_WIDGET); - cargo_ids.push_back(uuid); - LLToolDragAndDrop::ESource src = LLToolDragAndDrop::SOURCE_VIEWER; - LLUUID srcID; - LLToolDragAndDrop::getInstance()->beginMultiDrag(types, cargo_ids, src, srcID); - - // Second, stop the command if it is in progress and requires stopping! - LLCommandId command_id = LLCommandId(uuid); - gToolBarView->stopCommandInProgress(command_id); - - gToolBarView->mDragStarted = true; - return true; - } - else - { - MASK mask = 0; - return LLToolDragAndDrop::getInstance()->handleHover( x, y, mask ); - } - } - return false; -} - -bool LLToolBarView::handleDropTool( void* cargo_data, S32 x, S32 y, LLToolBar* toolbar) -{ - bool handled = false; - LLInventoryObject* inv_item = static_cast(cargo_data); - - LLAssetType::EType type = inv_item->getType(); - if (type == LLAssetType::AT_WIDGET) - { - handled = true; - // Get the command from its uuid - LLCommandManager& mgr = LLCommandManager::instance(); - LLCommandId command_id(inv_item->getUUID()); - LLCommand* command = mgr.getCommand(command_id); - if (command) - { - // Suppress the command from the toolbars (including the one it's dropped in, - // this will handle move position). - S32 old_toolbar_loc = gToolBarView->hasCommand(command_id); - LLToolBar* old_toolbar = NULL; - - if (old_toolbar_loc != LLToolBarEnums::TOOLBAR_NONE) - { - llassert(gToolBarView->mDragToolbarButton); - old_toolbar = gToolBarView->mDragToolbarButton->getParentByType(); - if (old_toolbar->isReadOnly() && toolbar->isReadOnly()) - { - // do nothing - } - else - { - int old_rank = LLToolBar::RANK_NONE; - gToolBarView->removeCommand(command_id, old_rank); - } - } - - // Convert the (x,y) position in rank in toolbar - if (!toolbar->isReadOnly()) - { - int new_rank = toolbar->getRankFromPosition(x,y); - toolbar->addCommand(command_id, new_rank); - } - - // Save the new toolbars configuration - gToolBarView->saveToolbars(); - } - else - { - LL_WARNS() << "Command couldn't be found in command manager" << LL_ENDL; - } - } - - resetDragTool(NULL); - return handled; -} - -void LLToolBarView::resetDragTool(LLToolBarButton* toolbarButton) -{ - // Clear the saved command, toolbar and rank - gToolBarView->mDragStarted = false; - gToolBarView->mDragToolbarButton = toolbarButton; -} - -// Provide a handle on a free standing inventory item containing references to the tool. -// This might be used by Drag and Drop to move around references to tool items. -LLInventoryObject* LLToolBarView::getDragItem() -{ - if (mDragToolbarButton) - { - LLUUID item_uuid = mDragToolbarButton->getCommandId().uuid(); - mDragItem = new LLInventoryObject (item_uuid, LLUUID::null, LLAssetType::AT_WIDGET, ""); - } - return mDragItem; -} - -void LLToolBarView::setToolBarsVisible(bool visible) -{ - mShowToolbars = visible; -} - -bool LLToolBarView::isModified() const -{ - bool modified = false; - - for (S32 i = LLToolBarEnums::TOOLBAR_FIRST; i <= LLToolBarEnums::TOOLBAR_LAST; i++) - { - modified |= mToolbars[i]->isModified(); - } - - return modified; -} - - +/** + * @file lltoolbarview.cpp + * @author Merov Linden + * @brief User customizable toolbar class + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lltoolbarview.h" + +#include "llappviewer.h" +#include "llbutton.h" +#include "llclipboard.h" +#include "lldir.h" +#include "lldockablefloater.h" +#include "lldockcontrol.h" +#include "llimview.h" +#include "lltransientfloatermgr.h" +#include "lltoolbar.h" +#include "lltooldraganddrop.h" +#include "llxmlnode.h" + +#include "llagent.h" // HACK for destinations guide on startup +#include "llfloaterreg.h" // HACK for destinations guide on startup +#include "llviewercontrol.h" // HACK for destinations guide on startup +#include "llinventorymodel.h" // HACK to disable starter avatars button for NUX + +LLToolBarView* gToolBarView = NULL; + +static LLDefaultChildRegistry::Register r("toolbar_view"); + +bool isToolDragged() +{ + return (LLToolDragAndDrop::getInstance()->getSource() == LLToolDragAndDrop::SOURCE_VIEWER); +} + +LLToolBarView::Toolbar::Toolbar() +: button_display_mode("button_display_mode"), + commands("command") +{} + +LLToolBarView::ToolbarSet::ToolbarSet() +: left_toolbar("left_toolbar"), + right_toolbar("right_toolbar"), + bottom_toolbar("bottom_toolbar") +{} + + +LLToolBarView::LLToolBarView(const LLToolBarView::Params& p) +: LLUICtrl(p), + mDragStarted(false), + mShowToolbars(true), + mDragToolbarButton(NULL), + mDragItem(NULL), + mToolbarsLoaded(false), + mBottomToolbarPanel(NULL) +{ + for (S32 i = 0; i < LLToolBarEnums::TOOLBAR_COUNT; i++) + { + mToolbars[i] = NULL; + } +} + +void LLToolBarView::initFromParams(const LLToolBarView::Params& p) +{ + // Initialize the base object + LLUICtrl::initFromParams(p); +} + +LLToolBarView::~LLToolBarView() +{ + saveToolbars(); +} + +bool LLToolBarView::postBuild() +{ + mToolbars[LLToolBarEnums::TOOLBAR_LEFT] = getChild("toolbar_left"); + mToolbars[LLToolBarEnums::TOOLBAR_LEFT]->getCenterLayoutPanel()->setLocationId(LLToolBarEnums::TOOLBAR_LEFT); + + mToolbars[LLToolBarEnums::TOOLBAR_RIGHT] = getChild("toolbar_right"); + mToolbars[LLToolBarEnums::TOOLBAR_RIGHT]->getCenterLayoutPanel()->setLocationId(LLToolBarEnums::TOOLBAR_RIGHT); + + mToolbars[LLToolBarEnums::TOOLBAR_BOTTOM] = getChild("toolbar_bottom"); + mToolbars[LLToolBarEnums::TOOLBAR_BOTTOM]->getCenterLayoutPanel()->setLocationId(LLToolBarEnums::TOOLBAR_BOTTOM); + + mBottomToolbarPanel = getChild("bottom_toolbar_panel"); + + for (int i = LLToolBarEnums::TOOLBAR_FIRST; i <= LLToolBarEnums::TOOLBAR_LAST; i++) + { + mToolbars[i]->setStartDragCallback(boost::bind(LLToolBarView::startDragTool,_1,_2,_3)); + mToolbars[i]->setHandleDragCallback(boost::bind(LLToolBarView::handleDragTool,_1,_2,_3,_4)); + mToolbars[i]->setHandleDropCallback(boost::bind(LLToolBarView::handleDropTool,_1,_2,_3,_4)); + mToolbars[i]->setButtonAddCallback(boost::bind(LLToolBarView::onToolBarButtonAdded,_1)); + mToolbars[i]->setButtonRemoveCallback(boost::bind(LLToolBarView::onToolBarButtonRemoved,_1)); + } + + return true; +} + +S32 LLToolBarView::hasCommand(const LLCommandId& commandId) const +{ + S32 command_location = LLToolBarEnums::TOOLBAR_NONE; + + for (S32 loc = LLToolBarEnums::TOOLBAR_FIRST; loc <= LLToolBarEnums::TOOLBAR_LAST; loc++) + { + if (mToolbars[loc]->hasCommand(commandId)) + { + command_location = loc; + break; + } + } + + return command_location; +} + +S32 LLToolBarView::addCommand(const LLCommandId& commandId, LLToolBarEnums::EToolBarLocation toolbar, int rank) +{ + int old_rank; + removeCommand(commandId, old_rank); + + S32 command_location = mToolbars[toolbar]->addCommand(commandId, rank); + + return command_location; +} + +S32 LLToolBarView::removeCommand(const LLCommandId& commandId, int& rank) +{ + S32 command_location = hasCommand(commandId); + rank = LLToolBar::RANK_NONE; + + if (command_location != LLToolBarEnums::TOOLBAR_NONE) + { + rank = mToolbars[command_location]->removeCommand(commandId); + } + + return command_location; +} + +S32 LLToolBarView::enableCommand(const LLCommandId& commandId, bool enabled) +{ + S32 command_location = hasCommand(commandId); + + if (command_location != LLToolBarEnums::TOOLBAR_NONE) + { + mToolbars[command_location]->enableCommand(commandId, enabled); + } + + return command_location; +} + +S32 LLToolBarView::stopCommandInProgress(const LLCommandId& commandId) +{ + S32 command_location = hasCommand(commandId); + + if (command_location != LLToolBarEnums::TOOLBAR_NONE) + { + mToolbars[command_location]->stopCommandInProgress(commandId); + } + + return command_location; +} + +S32 LLToolBarView::flashCommand(const LLCommandId& commandId, bool flash, bool force_flashing/* = false */) +{ + S32 command_location = hasCommand(commandId); + + if (command_location != LLToolBarEnums::TOOLBAR_NONE) + { + mToolbars[command_location]->flashCommand(commandId, flash, force_flashing); + } + + return command_location; +} + +bool LLToolBarView::addCommandInternal(const LLCommandId& command, LLToolBar* toolbar) +{ + LLCommandManager& mgr = LLCommandManager::instance(); + if (mgr.getCommand(command)) + { + toolbar->addCommand(command); + } + else + { + LL_WARNS() << "Toolbars creation : the command with id " << command.uuid().asString() << " cannot be found in the command manager" << LL_ENDL; + return false; + } + return true; +} + +bool LLToolBarView::loadToolbars(bool force_default) +{ + LLToolBarView::ToolbarSet toolbar_set; + bool err = false; + + // Load the toolbars.xml file + std::string toolbar_file = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "toolbars.xml"); + if (force_default) + { + toolbar_file = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "toolbars.xml"); + } + else if (!gDirUtilp->fileExists(toolbar_file)) + { + LL_WARNS() << "User toolbars def not found -> use default" << LL_ENDL; + toolbar_file = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "toolbars.xml"); + } + + LLXMLNodePtr root; + if(!LLXMLNode::parseFile(toolbar_file, root, NULL)) + { + LL_WARNS() << "Unable to load toolbars from file: " << toolbar_file << LL_ENDL; + err = true; + } + + if (!err && !root->hasName("toolbars")) + { + LL_WARNS() << toolbar_file << " is not a valid toolbars definition file" << LL_ENDL; + err = true; + } + + // Parse the toolbar settings + LLXUIParser parser; + if (!err) + { + parser.readXUI(root, toolbar_set, toolbar_file); + } + + if (!err && !toolbar_set.validateBlock()) + { + LL_WARNS() << "Unable to validate toolbars from file: " << toolbar_file << LL_ENDL; + err = true; + } + + if (err) + { + if (force_default) + { + LL_ERRS() << "Unable to load toolbars from default file : " << toolbar_file << LL_ENDL; + return false; + } + + // Try to load the default toolbars + return loadToolbars(true); + } + + // Clear the toolbars now before adding the loaded commands and settings + for (S32 i = LLToolBarEnums::TOOLBAR_FIRST; i <= LLToolBarEnums::TOOLBAR_LAST; i++) + { + if (mToolbars[i]) + { + mToolbars[i]->clearCommandsList(); + } + } + + // Add commands to each toolbar + if (toolbar_set.left_toolbar.isProvided() && mToolbars[LLToolBarEnums::TOOLBAR_LEFT]) + { + if (toolbar_set.left_toolbar.button_display_mode.isProvided()) + { + LLToolBarEnums::ButtonType button_type = toolbar_set.left_toolbar.button_display_mode; + mToolbars[LLToolBarEnums::TOOLBAR_LEFT]->setButtonType(button_type); + } + for (const LLCommandId::Params& command_params : toolbar_set.left_toolbar.commands) + { + if (!addCommandInternal(LLCommandId(command_params), mToolbars[LLToolBarEnums::TOOLBAR_LEFT])) + { + LL_WARNS() << "Error adding command '" << command_params.name() << "' to left toolbar." << LL_ENDL; + } + } + } + if (toolbar_set.right_toolbar.isProvided() && mToolbars[LLToolBarEnums::TOOLBAR_RIGHT]) + { + if (toolbar_set.right_toolbar.button_display_mode.isProvided()) + { + LLToolBarEnums::ButtonType button_type = toolbar_set.right_toolbar.button_display_mode; + mToolbars[LLToolBarEnums::TOOLBAR_RIGHT]->setButtonType(button_type); + } + for (const LLCommandId::Params& command_params : toolbar_set.right_toolbar.commands) + { + if (!addCommandInternal(LLCommandId(command_params), mToolbars[LLToolBarEnums::TOOLBAR_RIGHT])) + { + LL_WARNS() << "Error adding command '" << command_params.name() << "' to right toolbar." << LL_ENDL; + } + } + } + if (toolbar_set.bottom_toolbar.isProvided() && mToolbars[LLToolBarEnums::TOOLBAR_BOTTOM]) + { + if (toolbar_set.bottom_toolbar.button_display_mode.isProvided()) + { + LLToolBarEnums::ButtonType button_type = toolbar_set.bottom_toolbar.button_display_mode; + mToolbars[LLToolBarEnums::TOOLBAR_BOTTOM]->setButtonType(button_type); + } + for (const LLCommandId::Params& command_params : toolbar_set.bottom_toolbar.commands) + { + if (!addCommandInternal(LLCommandId(command_params), mToolbars[LLToolBarEnums::TOOLBAR_BOTTOM])) + { + LL_WARNS() << "Error adding command '" << command_params.name() << "' to bottom toolbar." << LL_ENDL; + } + } + } + + // SL-18581: Don't show the starter avatar toolbar button for NUX users + if (gAgent.isFirstLogin()) + { + LLViewerInventoryCategory* my_outfits_cat = gInventory.getCategory(gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS)); + LL_WARNS() << "First login: checking for NUX user." << LL_ENDL; + if (my_outfits_cat != NULL && my_outfits_cat->getDescendentCount() > 0) + { + LL_WARNS() << "First login: My Outfits folder is not empty, removing the avatar picker button." << LL_ENDL; + for (S32 i = LLToolBarEnums::TOOLBAR_FIRST; i <= LLToolBarEnums::TOOLBAR_LAST; i++) + { + if (mToolbars[i]) + { + mToolbars[i]->removeCommand(LLCommandId("avatar")); + } + } + } + } + + mToolbarsLoaded = true; + return true; +} + +bool LLToolBarView::clearToolbars() +{ + for (S32 i = LLToolBarEnums::TOOLBAR_FIRST; i <= LLToolBarEnums::TOOLBAR_LAST; i++) + { + if (mToolbars[i]) + { + mToolbars[i]->clearCommandsList(); + } + } + + return true; +} + +//static +bool LLToolBarView::loadDefaultToolbars() +{ + bool retval = false; + + if (gToolBarView) + { + retval = gToolBarView->loadToolbars(true); + if (retval) + { + gToolBarView->saveToolbars(); + } + } + + return retval; +} + +//static +bool LLToolBarView::clearAllToolbars() +{ + bool retval = false; + + if (gToolBarView) + { + retval = gToolBarView->clearToolbars(); + if (retval) + { + gToolBarView->saveToolbars(); + } + } + + return retval; +} + +void LLToolBarView::saveToolbars() const +{ + if (!mToolbarsLoaded) + return; + + // Build the parameter tree from the toolbar data + LLToolBarView::ToolbarSet toolbar_set; + if (mToolbars[LLToolBarEnums::TOOLBAR_LEFT]) + { + toolbar_set.left_toolbar.button_display_mode = mToolbars[LLToolBarEnums::TOOLBAR_LEFT]->getButtonType(); + addToToolset(mToolbars[LLToolBarEnums::TOOLBAR_LEFT]->getCommandsList(), toolbar_set.left_toolbar); + } + if (mToolbars[LLToolBarEnums::TOOLBAR_RIGHT]) + { + toolbar_set.right_toolbar.button_display_mode = mToolbars[LLToolBarEnums::TOOLBAR_RIGHT]->getButtonType(); + addToToolset(mToolbars[LLToolBarEnums::TOOLBAR_RIGHT]->getCommandsList(), toolbar_set.right_toolbar); + } + if (mToolbars[LLToolBarEnums::TOOLBAR_BOTTOM]) + { + toolbar_set.bottom_toolbar.button_display_mode = mToolbars[LLToolBarEnums::TOOLBAR_BOTTOM]->getButtonType(); + addToToolset(mToolbars[LLToolBarEnums::TOOLBAR_BOTTOM]->getCommandsList(), toolbar_set.bottom_toolbar); + } + + // Serialize the parameter tree + LLXMLNodePtr output_node = new LLXMLNode("toolbars", false); + LLXUIParser parser; + parser.writeXUI(output_node, toolbar_set); + + // Write the resulting XML to file + if(!output_node->isNull()) + { + const std::string& filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "toolbars.xml"); + LLFILE *fp = LLFile::fopen(filename, "w"); + if (fp != NULL) + { + LLXMLNode::writeHeaderToFile(fp); + output_node->writeToFile(fp); + fclose(fp); + } + } +} + +// Enumerate the commands in command_list and add them as Params to the toolbar +void LLToolBarView::addToToolset(command_id_list_t& command_list, Toolbar& toolbar) const +{ + LLCommandManager& mgr = LLCommandManager::instance(); + + for (command_id_list_t::const_iterator it = command_list.begin(); + it != command_list.end(); + ++it) + { + LLCommand* command = mgr.getCommand(*it); + if (command) + { + LLCommandId::Params command_name_param; + command_name_param.name = command->name(); + toolbar.commands.add(command_name_param); + } + } +} + +void LLToolBarView::onToolBarButtonAdded(LLView* button) +{ + llassert(button); + + if (button->getName() == "speak") + { + // Add the "Speak" button as a control view in LLTransientFloaterMgr + // to prevent hiding the transient IM floater upon pressing "Speak". + LLTransientFloaterMgr::getInstance()->addControlView(button); + + // Redock incoming and/or outgoing call windows, if applicable + + LLFloater* incoming_floater = LLFloaterReg::getLastFloaterInGroup("incoming_call"); + LLFloater* outgoing_floater = LLFloaterReg::getLastFloaterInGroup("outgoing_call"); + + if (incoming_floater && incoming_floater->isShown()) + { + LLCallDialog* incoming = dynamic_cast(incoming_floater); + llassert(incoming); + + LLDockControl* dock_control = incoming->getDockControl(); + if (dock_control->getDock() == NULL) + { + incoming->dockToToolbarButton("speak"); + } + } + + if (outgoing_floater && outgoing_floater->isShown()) + { + LLCallDialog* outgoing = dynamic_cast(outgoing_floater); + llassert(outgoing); + + LLDockControl* dock_control = outgoing->getDockControl(); + if (dock_control->getDock() == NULL) + { + outgoing->dockToToolbarButton("speak"); + } + } + } + else if (button->getName() == "voice") + { + // Add the "Voice controls" button as a control view in LLTransientFloaterMgr + // to prevent hiding the transient IM floater upon pressing "Voice controls". + LLTransientFloaterMgr::getInstance()->addControlView(button); + } +} + +void LLToolBarView::onToolBarButtonRemoved(LLView* button) +{ + llassert(button); + + if (button->getName() == "speak") + { + LLTransientFloaterMgr::getInstance()->removeControlView(button); + + // Undock incoming and/or outgoing call windows + + LLFloater* incoming_floater = LLFloaterReg::getLastFloaterInGroup("incoming_call"); + LLFloater* outgoing_floater = LLFloaterReg::getLastFloaterInGroup("outgoing_call"); + + if (incoming_floater && incoming_floater->isShown()) + { + LLDockableFloater* incoming = dynamic_cast(incoming_floater); + llassert(incoming); + + LLDockControl* dock_control = incoming->getDockControl(); + dock_control->setDock(NULL); + } + + if (outgoing_floater && outgoing_floater->isShown()) + { + LLDockableFloater* outgoing = dynamic_cast(outgoing_floater); + llassert(outgoing); + + LLDockControl* dock_control = outgoing->getDockControl(); + dock_control->setDock(NULL); + } + } + else if (button->getName() == "voice") + { + LLTransientFloaterMgr::getInstance()->removeControlView(button); + } +} + +void LLToolBarView::draw() +{ + LLRect toolbar_rects[LLToolBarEnums::TOOLBAR_COUNT]; + + for (S32 i = LLToolBarEnums::TOOLBAR_FIRST; i <= LLToolBarEnums::TOOLBAR_LAST; i++) + { + if (mToolbars[i]) + { + LLView::EOrientation orientation = LLToolBarEnums::getOrientation(mToolbars[i]->getSideType()); + + if (orientation == LLLayoutStack::HORIZONTAL) + { + mToolbars[i]->getParent()->reshape(mToolbars[i]->getParent()->getRect().getWidth(), mToolbars[i]->getRect().getHeight()); + } + else + { + mToolbars[i]->getParent()->reshape(mToolbars[i]->getRect().getWidth(), mToolbars[i]->getParent()->getRect().getHeight()); + } + + mToolbars[i]->localRectToOtherView(mToolbars[i]->getLocalRect(), &toolbar_rects[i], this); + } + } + + for (S32 i = LLToolBarEnums::TOOLBAR_FIRST; i <= LLToolBarEnums::TOOLBAR_LAST; i++) + { + mToolbars[i]->getParent()->setVisible(mShowToolbars + && (mToolbars[i]->hasButtons() + || isToolDragged())); + } + + // Draw drop zones if drop of a tool is active + if (isToolDragged()) + { + LLColor4 drop_color = LLUIColorTable::instance().getColor( "ToolbarDropZoneColor" ); + + for (S32 i = LLToolBarEnums::TOOLBAR_FIRST; i <= LLToolBarEnums::TOOLBAR_LAST; i++) + { + gl_rect_2d(toolbar_rects[i], drop_color, true); + } + } + + LLUICtrl::draw(); +} + + +// ---------------------------------------- +// Drag and Drop Handling +// ---------------------------------------- + + +void LLToolBarView::startDragTool(S32 x, S32 y, LLToolBarButton* toolbarButton) +{ + resetDragTool(toolbarButton); + + // Flag the tool dragging but don't start it yet + LLToolDragAndDrop::getInstance()->setDragStart( x, y ); +} + +bool LLToolBarView::handleDragTool( S32 x, S32 y, const LLUUID& uuid, LLAssetType::EType type) +{ + if (LLToolDragAndDrop::getInstance()->isOverThreshold( x, y )) + { + if (!gToolBarView->mDragStarted) + { + // Start the tool dragging: + + // First, create the global drag and drop object + std::vector types; + uuid_vec_t cargo_ids; + types.push_back(DAD_WIDGET); + cargo_ids.push_back(uuid); + LLToolDragAndDrop::ESource src = LLToolDragAndDrop::SOURCE_VIEWER; + LLUUID srcID; + LLToolDragAndDrop::getInstance()->beginMultiDrag(types, cargo_ids, src, srcID); + + // Second, stop the command if it is in progress and requires stopping! + LLCommandId command_id = LLCommandId(uuid); + gToolBarView->stopCommandInProgress(command_id); + + gToolBarView->mDragStarted = true; + return true; + } + else + { + MASK mask = 0; + return LLToolDragAndDrop::getInstance()->handleHover( x, y, mask ); + } + } + return false; +} + +bool LLToolBarView::handleDropTool( void* cargo_data, S32 x, S32 y, LLToolBar* toolbar) +{ + bool handled = false; + LLInventoryObject* inv_item = static_cast(cargo_data); + + LLAssetType::EType type = inv_item->getType(); + if (type == LLAssetType::AT_WIDGET) + { + handled = true; + // Get the command from its uuid + LLCommandManager& mgr = LLCommandManager::instance(); + LLCommandId command_id(inv_item->getUUID()); + LLCommand* command = mgr.getCommand(command_id); + if (command) + { + // Suppress the command from the toolbars (including the one it's dropped in, + // this will handle move position). + S32 old_toolbar_loc = gToolBarView->hasCommand(command_id); + LLToolBar* old_toolbar = NULL; + + if (old_toolbar_loc != LLToolBarEnums::TOOLBAR_NONE) + { + llassert(gToolBarView->mDragToolbarButton); + old_toolbar = gToolBarView->mDragToolbarButton->getParentByType(); + if (old_toolbar->isReadOnly() && toolbar->isReadOnly()) + { + // do nothing + } + else + { + int old_rank = LLToolBar::RANK_NONE; + gToolBarView->removeCommand(command_id, old_rank); + } + } + + // Convert the (x,y) position in rank in toolbar + if (!toolbar->isReadOnly()) + { + int new_rank = toolbar->getRankFromPosition(x,y); + toolbar->addCommand(command_id, new_rank); + } + + // Save the new toolbars configuration + gToolBarView->saveToolbars(); + } + else + { + LL_WARNS() << "Command couldn't be found in command manager" << LL_ENDL; + } + } + + resetDragTool(NULL); + return handled; +} + +void LLToolBarView::resetDragTool(LLToolBarButton* toolbarButton) +{ + // Clear the saved command, toolbar and rank + gToolBarView->mDragStarted = false; + gToolBarView->mDragToolbarButton = toolbarButton; +} + +// Provide a handle on a free standing inventory item containing references to the tool. +// This might be used by Drag and Drop to move around references to tool items. +LLInventoryObject* LLToolBarView::getDragItem() +{ + if (mDragToolbarButton) + { + LLUUID item_uuid = mDragToolbarButton->getCommandId().uuid(); + mDragItem = new LLInventoryObject (item_uuid, LLUUID::null, LLAssetType::AT_WIDGET, ""); + } + return mDragItem; +} + +void LLToolBarView::setToolBarsVisible(bool visible) +{ + mShowToolbars = visible; +} + +bool LLToolBarView::isModified() const +{ + bool modified = false; + + for (S32 i = LLToolBarEnums::TOOLBAR_FIRST; i <= LLToolBarEnums::TOOLBAR_LAST; i++) + { + modified |= mToolbars[i]->isModified(); + } + + return modified; +} + + diff --git a/indra/newview/lltoolbarview.h b/indra/newview/lltoolbarview.h index 0a568c1928..7cecd81052 100644 --- a/indra/newview/lltoolbarview.h +++ b/indra/newview/lltoolbarview.h @@ -1,129 +1,129 @@ -/** - * @file lltoolbarview.h - * @author Merov Linden - * @brief User customizable toolbar class - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLTOOLBARVIEW_H -#define LL_LLTOOLBARVIEW_H - -#include "lluictrl.h" -#include "lltoolbar.h" -#include "llcommandmanager.h" -#include "llinventory.h" - -class LLUICtrlFactory; - -// Parent of all LLToolBar - -class LLToolBarView : public LLUICtrl -{ -public: - // Xui structure of the toolbar panel - struct Params : public LLInitParam::Block {}; - - // Note: valid children for LLToolBarView are stored in this registry - typedef LLDefaultChildRegistry child_registry_t; - - // Xml structure of the toolbars.xml setting - // Those live in a toolbars.xml found in app_settings (for the default) and in - // the user folder for the user specific (saved) settings - struct Toolbar : public LLInitParam::Block - { - Mandatory button_display_mode; - Multiple commands; - - Toolbar(); - }; - struct ToolbarSet : public LLInitParam::Block - { - Optional left_toolbar, - right_toolbar, - bottom_toolbar; - - ToolbarSet(); - }; - - // Derived methods - virtual ~LLToolBarView(); - bool postBuild() override; - void draw() override; - - // Toolbar view interface with the rest of the world - // Checks if the commandId is being used somewhere in one of the toolbars, returns LLToolBarEnums::EToolBarLocation - S32 hasCommand(const LLCommandId& commandId) const; - S32 addCommand(const LLCommandId& commandId, LLToolBarEnums::EToolBarLocation toolbar, int rank = LLToolBar::RANK_NONE); - S32 removeCommand(const LLCommandId& commandId, int& rank); // Sets the rank the removed command was at, RANK_NONE if not found - S32 enableCommand(const LLCommandId& commandId, bool enabled); - S32 stopCommandInProgress(const LLCommandId& commandId); - S32 flashCommand(const LLCommandId& commandId, bool flash, bool force_flashing = false); - - // Loads the toolbars from the existing user or default settings - bool loadToolbars(bool force_default = false); // return false if load fails - - // Clears all buttons off the toolbars - bool clearToolbars(); - - void setToolBarsVisible(bool visible); - - static bool loadDefaultToolbars(); - static bool clearAllToolbars(); - - static void startDragTool(S32 x, S32 y, LLToolBarButton* toolbarButton); - static bool handleDragTool(S32 x, S32 y, const LLUUID& uuid, LLAssetType::EType type); - static bool handleDropTool(void* cargo_data, S32 x, S32 y, LLToolBar* toolbar); - static void resetDragTool(LLToolBarButton* toolbarButton); - LLInventoryObject* getDragItem(); - LLView* getBottomToolbar() { return mBottomToolbarPanel; } - LLToolBar* getToolbar(LLToolBarEnums::EToolBarLocation toolbar) { return mToolbars[toolbar]; } - bool isModified() const; - -protected: - friend class LLUICtrlFactory; - LLToolBarView(const Params&); - - void initFromParams(const Params&); - -private: - void saveToolbars() const; - bool addCommandInternal(const LLCommandId& commandId, LLToolBar* toolbar); - void addToToolset(command_id_list_t& command_list, Toolbar& toolbar) const; - - static void onToolBarButtonAdded(LLView* button); - static void onToolBarButtonRemoved(LLView* button); - - // Pointers to the toolbars handled by the toolbar view - LLToolBar* mToolbars[LLToolBarEnums::TOOLBAR_COUNT]; - bool mToolbarsLoaded; - - bool mDragStarted; - LLToolBarButton* mDragToolbarButton; - LLInventoryObject* mDragItem; - bool mShowToolbars; - LLView* mBottomToolbarPanel; -}; - -extern LLToolBarView* gToolBarView; - -#endif // LL_LLTOOLBARVIEW_H +/** + * @file lltoolbarview.h + * @author Merov Linden + * @brief User customizable toolbar class + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLTOOLBARVIEW_H +#define LL_LLTOOLBARVIEW_H + +#include "lluictrl.h" +#include "lltoolbar.h" +#include "llcommandmanager.h" +#include "llinventory.h" + +class LLUICtrlFactory; + +// Parent of all LLToolBar + +class LLToolBarView : public LLUICtrl +{ +public: + // Xui structure of the toolbar panel + struct Params : public LLInitParam::Block {}; + + // Note: valid children for LLToolBarView are stored in this registry + typedef LLDefaultChildRegistry child_registry_t; + + // Xml structure of the toolbars.xml setting + // Those live in a toolbars.xml found in app_settings (for the default) and in + // the user folder for the user specific (saved) settings + struct Toolbar : public LLInitParam::Block + { + Mandatory button_display_mode; + Multiple commands; + + Toolbar(); + }; + struct ToolbarSet : public LLInitParam::Block + { + Optional left_toolbar, + right_toolbar, + bottom_toolbar; + + ToolbarSet(); + }; + + // Derived methods + virtual ~LLToolBarView(); + bool postBuild() override; + void draw() override; + + // Toolbar view interface with the rest of the world + // Checks if the commandId is being used somewhere in one of the toolbars, returns LLToolBarEnums::EToolBarLocation + S32 hasCommand(const LLCommandId& commandId) const; + S32 addCommand(const LLCommandId& commandId, LLToolBarEnums::EToolBarLocation toolbar, int rank = LLToolBar::RANK_NONE); + S32 removeCommand(const LLCommandId& commandId, int& rank); // Sets the rank the removed command was at, RANK_NONE if not found + S32 enableCommand(const LLCommandId& commandId, bool enabled); + S32 stopCommandInProgress(const LLCommandId& commandId); + S32 flashCommand(const LLCommandId& commandId, bool flash, bool force_flashing = false); + + // Loads the toolbars from the existing user or default settings + bool loadToolbars(bool force_default = false); // return false if load fails + + // Clears all buttons off the toolbars + bool clearToolbars(); + + void setToolBarsVisible(bool visible); + + static bool loadDefaultToolbars(); + static bool clearAllToolbars(); + + static void startDragTool(S32 x, S32 y, LLToolBarButton* toolbarButton); + static bool handleDragTool(S32 x, S32 y, const LLUUID& uuid, LLAssetType::EType type); + static bool handleDropTool(void* cargo_data, S32 x, S32 y, LLToolBar* toolbar); + static void resetDragTool(LLToolBarButton* toolbarButton); + LLInventoryObject* getDragItem(); + LLView* getBottomToolbar() { return mBottomToolbarPanel; } + LLToolBar* getToolbar(LLToolBarEnums::EToolBarLocation toolbar) { return mToolbars[toolbar]; } + bool isModified() const; + +protected: + friend class LLUICtrlFactory; + LLToolBarView(const Params&); + + void initFromParams(const Params&); + +private: + void saveToolbars() const; + bool addCommandInternal(const LLCommandId& commandId, LLToolBar* toolbar); + void addToToolset(command_id_list_t& command_list, Toolbar& toolbar) const; + + static void onToolBarButtonAdded(LLView* button); + static void onToolBarButtonRemoved(LLView* button); + + // Pointers to the toolbars handled by the toolbar view + LLToolBar* mToolbars[LLToolBarEnums::TOOLBAR_COUNT]; + bool mToolbarsLoaded; + + bool mDragStarted; + LLToolBarButton* mDragToolbarButton; + LLInventoryObject* mDragItem; + bool mShowToolbars; + LLView* mBottomToolbarPanel; +}; + +extern LLToolBarView* gToolBarView; + +#endif // LL_LLTOOLBARVIEW_H diff --git a/indra/newview/lltoolbrush.cpp b/indra/newview/lltoolbrush.cpp index 86acb1963e..e2b6924aeb 100644 --- a/indra/newview/lltoolbrush.cpp +++ b/indra/newview/lltoolbrush.cpp @@ -1,718 +1,718 @@ -/** - * @file lltoolbrush.cpp - * @brief Implementation of the toolbrushes - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lltoolbrush.h" -#include "lltoolselectland.h" - -// library headers -#include "llgl.h" -#include "llnotificationsutil.h" -#include "llrender.h" -#include "message.h" - -#include "llagent.h" -#include "llcallbacklist.h" -#include "llviewercontrol.h" -#include "llfloatertools.h" -#include "llregionposition.h" -#include "llstatusbar.h" -#include "llsurface.h" -#include "llsurfacepatch.h" -#include "lltoolmgr.h" -#include "llui.h" -#include "llviewerparcelmgr.h" -#include "llviewerparceloverlay.h" -#include "llviewerregion.h" -#include "llviewerwindow.h" -#include "llworld.h" -#include "llappviewer.h" -#include "llparcel.h" -#include "roles_constants.h" -#include "llglheaders.h" - -const std::string REGION_BLOCKS_TERRAFORM_MSG = "This region does not allow terraforming.\n" - "You will need to buy land in another part of the world to terraform it."; - - -///============================================================================ -/// Local function declarations, constants, enums, and typedefs -///============================================================================ - -const S32 LAND_BRUSH_SIZE_COUNT = 3; -const F32 LAND_BRUSH_SIZE[LAND_BRUSH_SIZE_COUNT] = {1.0f, 2.0f, 4.0f}; - -enum -{ - E_LAND_LEVEL = 0, - E_LAND_RAISE = 1, - E_LAND_LOWER = 2, - E_LAND_SMOOTH = 3, - E_LAND_NOISE = 4, - E_LAND_REVERT = 5, - E_LAND_INVALID = 6, -}; -const LLColor4 OVERLAY_COLOR(1.0f, 1.0f, 1.0f, 1.0f); - -///============================================================================ -/// Class LLToolBrushLand -///============================================================================ - -// constructor -LLToolBrushLand::LLToolBrushLand() -: LLTool(std::string("Land")), - mStartingZ( 0.0f ), - mMouseX( 0 ), - mMouseY(0), - mGotHover(false), - mBrushSelected(false) -{ - mBrushSize = gSavedSettings.getF32("LandBrushSize"); -} - - -U8 LLToolBrushLand::getBrushIndex() -{ - // find the best index for desired size - // (compatibility with old sims, brush_index is now depricated - DEV-8252) - U8 index = 0; - for (U8 i = 0; i < LAND_BRUSH_SIZE_COUNT; i++) - { - if (mBrushSize > LAND_BRUSH_SIZE[i]) - { - index = i; - } - } - - return index; -} - -void LLToolBrushLand::modifyLandAtPointGlobal(const LLVector3d &pos_global, - MASK mask) -{ - S32 radioAction = gSavedSettings.getS32("RadioLandBrushAction"); - - mLastAffectedRegions.clear(); - determineAffectedRegions(mLastAffectedRegions, pos_global); - for(region_list_t::iterator iter = mLastAffectedRegions.begin(); - iter != mLastAffectedRegions.end(); ++iter) - { - LLViewerRegion* regionp = *iter; - //bool is_changed = false; - LLVector3 pos_region = regionp->getPosRegionFromGlobal(pos_global); - LLSurface &land = regionp->getLand(); - char action = E_LAND_LEVEL; - switch (radioAction) - { - case 0: - // // average toward mStartingZ - action = E_LAND_LEVEL; - break; - case 1: - action = E_LAND_RAISE; - break; - case 2: - action = E_LAND_LOWER; - break; - case 3: - action = E_LAND_SMOOTH; - break; - case 4: - action = E_LAND_NOISE; - break; - case 5: - action = E_LAND_REVERT; - break; - default: - action = E_LAND_INVALID; - break; - } - - // Don't send a message to the region if nothing changed. - //if(!is_changed) continue; - - // Now to update the patch information so it will redraw correctly. - LLSurfacePatch *patchp= land.resolvePatchRegion(pos_region); - if (patchp) - { - patchp->dirtyZ(); - } - - // Also force the property lines to update, normals to recompute, etc. - regionp->forceUpdate(); - - // tell the simulator what we've done - F32 seconds = (1.0f / gFPSClamped) * gSavedSettings.getF32("LandBrushForce"); - F32 x_pos = (F32)pos_region.mV[VX]; - F32 y_pos = (F32)pos_region.mV[VY]; - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_ModifyLand); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ModifyBlock); - msg->addU8Fast(_PREHASH_Action, (U8)action); - msg->addU8Fast(_PREHASH_BrushSize, getBrushIndex()); - msg->addF32Fast(_PREHASH_Seconds, seconds); - msg->addF32Fast(_PREHASH_Height, mStartingZ); - msg->nextBlockFast(_PREHASH_ParcelData); - msg->addS32Fast(_PREHASH_LocalID, -1); - msg->addF32Fast(_PREHASH_West, x_pos ); - msg->addF32Fast(_PREHASH_South, y_pos ); - msg->addF32Fast(_PREHASH_East, x_pos ); - msg->addF32Fast(_PREHASH_North, y_pos ); - msg->nextBlock("ModifyBlockExtended"); - msg->addF32("BrushSize", mBrushSize); - msg->sendMessage(regionp->getHost()); - } -} - -void LLToolBrushLand::modifyLandInSelectionGlobal() -{ - if (LLViewerParcelMgr::getInstance()->selectionEmpty()) - { - return; - } - - if (LLToolMgr::getInstance()->getCurrentTool() == LLToolSelectLand::getInstance()) - { - // selecting land, don't do anything - return; - } - - LLVector3d min; - LLVector3d max; - - LLViewerParcelMgr::getInstance()->getSelection(min, max); - - S32 radioAction = gSavedSettings.getS32("RadioLandBrushAction"); - - mLastAffectedRegions.clear(); - - determineAffectedRegions(mLastAffectedRegions, LLVector3d(min.mdV[VX], min.mdV[VY], 0)); - determineAffectedRegions(mLastAffectedRegions, LLVector3d(min.mdV[VX], max.mdV[VY], 0)); - determineAffectedRegions(mLastAffectedRegions, LLVector3d(max.mdV[VX], min.mdV[VY], 0)); - determineAffectedRegions(mLastAffectedRegions, LLVector3d(max.mdV[VX], max.mdV[VY], 0)); - - LLRegionPosition mid_point_region((min + max) * 0.5); - LLViewerRegion* center_region = mid_point_region.getRegion(); - if (center_region) - { - LLVector3 pos_region = mid_point_region.getPositionRegion(); - U32 grids = center_region->getLand().mGridsPerEdge; - S32 i = llclamp( (S32)pos_region.mV[VX], 0, (S32)grids ); - S32 j = llclamp( (S32)pos_region.mV[VY], 0, (S32)grids ); - mStartingZ = center_region->getLand().getZ(i+j*grids); - } - else - { - mStartingZ = 0.f; - } - - // Stop if our selection include a no-terraform region - for(region_list_t::iterator iter = mLastAffectedRegions.begin(); - iter != mLastAffectedRegions.end(); ++iter) - { - LLViewerRegion* regionp = *iter; - if (!canTerraformRegion(regionp)) - { - alertNoTerraformRegion(regionp); - return; - } - } - - for(region_list_t::iterator iter = mLastAffectedRegions.begin(); - iter != mLastAffectedRegions.end(); ++iter) - { - LLViewerRegion* regionp = *iter; - //bool is_changed = false; - LLVector3 min_region = regionp->getPosRegionFromGlobal(min); - LLVector3 max_region = regionp->getPosRegionFromGlobal(max); - - min_region.clamp(0.f, regionp->getWidth()); - max_region.clamp(0.f, regionp->getWidth()); - F32 seconds = gSavedSettings.getF32("LandBrushForce"); - - LLSurface &land = regionp->getLand(); - char action = E_LAND_LEVEL; - switch (radioAction) - { - case 0: - // // average toward mStartingZ - action = E_LAND_LEVEL; - seconds *= 0.25f; - break; - case 1: - action = E_LAND_RAISE; - seconds *= 0.25f; - break; - case 2: - action = E_LAND_LOWER; - seconds *= 0.25f; - break; - case 3: - action = E_LAND_SMOOTH; - seconds *= 5.0f; - break; - case 4: - action = E_LAND_NOISE; - seconds *= 0.5f; - break; - case 5: - action = E_LAND_REVERT; - seconds = 0.5f; - break; - default: - //action = E_LAND_INVALID; - //seconds = 0.0f; - return; - break; - } - - // Don't send a message to the region if nothing changed. - //if(!is_changed) continue; - - // Now to update the patch information so it will redraw correctly. - LLSurfacePatch *patchp= land.resolvePatchRegion(min_region); - if (patchp) - { - patchp->dirtyZ(); - } - - // Also force the property lines to update, normals to recompute, etc. - regionp->forceUpdate(); - - // tell the simulator what we've done - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_ModifyLand); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ModifyBlock); - msg->addU8Fast(_PREHASH_Action, (U8)action); - msg->addU8Fast(_PREHASH_BrushSize, getBrushIndex()); - msg->addF32Fast(_PREHASH_Seconds, seconds); - msg->addF32Fast(_PREHASH_Height, mStartingZ); - - bool parcel_selected = LLViewerParcelMgr::getInstance()->getParcelSelection()->getWholeParcelSelected(); - LLParcel* selected_parcel = LLViewerParcelMgr::getInstance()->getParcelSelection()->getParcel(); - - if (parcel_selected && selected_parcel) - { - msg->nextBlockFast(_PREHASH_ParcelData); - msg->addS32Fast(_PREHASH_LocalID, selected_parcel->getLocalID()); - msg->addF32Fast(_PREHASH_West, min_region.mV[VX] ); - msg->addF32Fast(_PREHASH_South, min_region.mV[VY] ); - msg->addF32Fast(_PREHASH_East, max_region.mV[VX] ); - msg->addF32Fast(_PREHASH_North, max_region.mV[VY] ); - } - else - { - msg->nextBlockFast(_PREHASH_ParcelData); - msg->addS32Fast(_PREHASH_LocalID, -1); - msg->addF32Fast(_PREHASH_West, min_region.mV[VX] ); - msg->addF32Fast(_PREHASH_South, min_region.mV[VY] ); - msg->addF32Fast(_PREHASH_East, max_region.mV[VX] ); - msg->addF32Fast(_PREHASH_North, max_region.mV[VY] ); - } - - msg->nextBlock("ModifyBlockExtended"); - msg->addF32("BrushSize", mBrushSize); - - msg->sendMessage(regionp->getHost()); - } -} - -void LLToolBrushLand::brush( void ) -{ - LLVector3d spot; - if( gViewerWindow->mousePointOnLandGlobal( mMouseX, mMouseY, &spot ) ) - { - // Round to nearest X,Y grid - spot.mdV[VX] = floor( spot.mdV[VX] + 0.5 ); - spot.mdV[VY] = floor( spot.mdV[VY] + 0.5 ); - - modifyLandAtPointGlobal(spot, gKeyboard->currentMask(true)); - } -} - -bool LLToolBrushLand::handleMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - // Find the z value of the initial click. - LLVector3d spot; - if( gViewerWindow->mousePointOnLandGlobal( x, y, &spot ) ) - { - // Round to nearest X,Y grid - spot.mdV[VX] = floor( spot.mdV[VX] + 0.5 ); - spot.mdV[VY] = floor( spot.mdV[VY] + 0.5 ); - - LLRegionPosition region_position( spot ); - LLViewerRegion* regionp = region_position.getRegion(); - - if (!canTerraformRegion(regionp)) - { - alertNoTerraformRegion(regionp); - return true; - } - - if (!canTerraformParcel(regionp)) - { - alertNoTerraformParcel(); - } - - LLVector3 pos_region = region_position.getPositionRegion(); - U32 grids = regionp->getLand().mGridsPerEdge; - S32 i = llclamp( (S32)pos_region.mV[VX], 0, (S32)grids ); - S32 j = llclamp( (S32)pos_region.mV[VY], 0, (S32)grids ); - mStartingZ = regionp->getLand().getZ(i+j*grids); - mMouseX = x; - mMouseY = y; - gIdleCallbacks.addFunction( &LLToolBrushLand::onIdle, (void*)this ); - setMouseCapture( true ); - - LLViewerParcelMgr::getInstance()->setSelectionVisible(false); - handled = true; - } - - return handled; -} - -bool LLToolBrushLand::handleHover( S32 x, S32 y, MASK mask ) -{ - LL_DEBUGS("UserInput") << "hover handled by LLToolBrushLand (" - << (hasMouseCapture() ? "active":"inactive") - << ")" << LL_ENDL; - mMouseX = x; - mMouseY = y; - mGotHover = true; - gViewerWindow->setCursor(UI_CURSOR_TOOLLAND); - - LLVector3d spot; - if( gViewerWindow->mousePointOnLandGlobal( mMouseX, mMouseY, &spot ) ) - { - - spot.mdV[VX] = floor( spot.mdV[VX] + 0.5 ); - spot.mdV[VY] = floor( spot.mdV[VY] + 0.5 ); - - LLViewerParcelMgr::getInstance()->setHoverParcel(spot); - } - return true; -} - -bool LLToolBrushLand::handleMouseUp(S32 x, S32 y, MASK mask) -{ - bool handled = false; - mLastAffectedRegions.clear(); - if( hasMouseCapture() ) - { - // Release the mouse - setMouseCapture( false ); - - LLViewerParcelMgr::getInstance()->setSelectionVisible(true); - - gIdleCallbacks.deleteFunction( &LLToolBrushLand::onIdle, (void*)this ); - handled = true; - } - - return handled; -} - -void LLToolBrushLand::handleSelect() -{ - gEditMenuHandler = this; - - gFloaterTools->setStatusText("modifyland"); -// if (!mBrushSelected) - { - mBrushSelected = true; - } -} - - -void LLToolBrushLand::handleDeselect() -{ - if( gEditMenuHandler == this ) - { - gEditMenuHandler = NULL; - } - LLViewerParcelMgr::getInstance()->setSelectionVisible(true); - mBrushSelected = false; -} - -// Draw the area that will be affected. -void LLToolBrushLand::render() -{ - if(mGotHover) - { - //LL_INFOS() << "LLToolBrushLand::render()" << LL_ENDL; - LLVector3d spot; - if(gViewerWindow->mousePointOnLandGlobal(mMouseX, mMouseY, &spot)) - { - spot.mdV[VX] = floor( spot.mdV[VX] + 0.5 ); - spot.mdV[VY] = floor( spot.mdV[VY] + 0.5 ); - - mBrushSize = gSavedSettings.getF32("LandBrushSize"); - - region_list_t regions; - determineAffectedRegions(regions, spot); - - // Now, for each region, render the overlay - LLVector3 pos_world = gAgent.getRegion()->getPosRegionFromGlobal(spot); - for(region_list_t::iterator iter = regions.begin(); - iter != regions.end(); ++iter) - { - LLViewerRegion* region = *iter; - renderOverlay(region->getLand(), - region->getPosRegionFromGlobal(spot), - pos_world); - } - } - mGotHover = false; - } -} - -/* - * Draw vertical lines from each vertex straight up in world space - * with lengths indicating the current "strength" slider. - * Decorate the tops and bottoms of the lines like this: - * - * Raise Revert - * /|\ ___ - * | | - * | | - * - * Rough Smooth - * /|\ ___ - * | | - * | | - * \|/..........._|_ - * - * Lower Flatten - * | | - * | | - * \|/..........._|_ - */ -void LLToolBrushLand::renderOverlay(LLSurface& land, const LLVector3& pos_region, - const LLVector3& pos_world) -{ - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - LLGLDepthTest mDepthTest(GL_TRUE); - gGL.pushMatrix(); - gGL.color4fv(OVERLAY_COLOR.mV); - gGL.translatef(0.0f, 0.0f, 1.0f); - - S32 i = (S32) pos_region.mV[VX]; - S32 j = (S32) pos_region.mV[VY]; - S32 half_edge = llfloor(mBrushSize); - S32 radioAction = gSavedSettings.getS32("RadioLandBrushAction"); - F32 force = gSavedSettings.getF32("LandBrushForce"); // .1 to 100? - - gGL.begin(LLRender::LINES); - for(S32 di = -half_edge; di <= half_edge; di++) - { - if((i+di) < 0 || (i+di) >= (S32)land.mGridsPerEdge) continue; - for(S32 dj = -half_edge; dj <= half_edge; dj++) - { - if( (j+dj) < 0 || (j+dj) >= (S32)land.mGridsPerEdge ) continue; - const F32 - wx = pos_world.mV[VX] + di, - wy = pos_world.mV[VY] + dj, - wz = land.getZ((i+di)+(j+dj)*land.mGridsPerEdge), - norm_dist = sqrt((float)di*di + dj*dj) / half_edge, - force_scale = sqrt(2.f) - norm_dist, // 1 at center, 0 at corner - wz2 = wz + .2 + (.2 + force/100) * force_scale, // top vertex - tic = .075f; // arrowhead size - // vertical line - gGL.vertex3f(wx, wy, wz); - gGL.vertex3f(wx, wy, wz2); - if(radioAction == E_LAND_RAISE || radioAction == E_LAND_NOISE) // up arrow - { - gGL.vertex3f(wx, wy, wz2); - gGL.vertex3f(wx+tic, wy, wz2-tic); - gGL.vertex3f(wx, wy, wz2); - gGL.vertex3f(wx-tic, wy, wz2-tic); - } - if(radioAction == E_LAND_LOWER || radioAction == E_LAND_NOISE) // down arrow - { - gGL.vertex3f(wx, wy, wz); - gGL.vertex3f(wx+tic, wy, wz+tic); - gGL.vertex3f(wx, wy, wz); - gGL.vertex3f(wx-tic, wy, wz+tic); - } - if(radioAction == E_LAND_REVERT || radioAction == E_LAND_SMOOTH) // flat top - { - gGL.vertex3f(wx-tic, wy, wz2); - gGL.vertex3f(wx+tic, wy, wz2); - } - if(radioAction == E_LAND_LEVEL || radioAction == E_LAND_SMOOTH) // flat bottom - { - gGL.vertex3f(wx-tic, wy, wz); - gGL.vertex3f(wx+tic, wy, wz); - } - } - } - gGL.end(); - - gGL.popMatrix(); -} - -void LLToolBrushLand::determineAffectedRegions(region_list_t& regions, - const LLVector3d& spot ) const -{ - LLVector3d corner(spot); - corner.mdV[VX] -= (mBrushSize / 2); - corner.mdV[VY] -= (mBrushSize / 2); - LLViewerRegion* region = NULL; - region = LLWorld::getInstance()->getRegionFromPosGlobal(corner); - if(region && regions.find(region) == regions.end()) - { - regions.insert(region); - } - corner.mdV[VY] += mBrushSize; - region = LLWorld::getInstance()->getRegionFromPosGlobal(corner); - if(region && regions.find(region) == regions.end()) - { - regions.insert(region); - } - corner.mdV[VX] += mBrushSize; - region = LLWorld::getInstance()->getRegionFromPosGlobal(corner); - if(region && regions.find(region) == regions.end()) - { - regions.insert(region); - } - corner.mdV[VY] -= mBrushSize; - region = LLWorld::getInstance()->getRegionFromPosGlobal(corner); - if(region && regions.find(region) == regions.end()) - { - regions.insert(region); - } -} - -// static -void LLToolBrushLand::onIdle( void* brush_tool ) -{ - LLToolBrushLand* self = reinterpret_cast(brush_tool); - - if( LLToolMgr::getInstance()->getCurrentTool() == self ) - { - self->brush(); - } - else - { - gIdleCallbacks.deleteFunction( &LLToolBrushLand::onIdle, self ); - } -} - -void LLToolBrushLand::onMouseCaptureLost() -{ - gIdleCallbacks.deleteFunction(&LLToolBrushLand::onIdle, this); -} - -// static -void LLToolBrushLand::undo() -{ - for(region_list_t::iterator iter = mLastAffectedRegions.begin(); - iter != mLastAffectedRegions.end(); ++iter) - { - LLViewerRegion* regionp = *iter; - gMessageSystem->newMessageFast(_PREHASH_UndoLand); - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - gMessageSystem->sendMessage(regionp->getHost()); - } -} - -// static -/* -void LLToolBrushLand::redo() -{ - for(region_list_t::iterator iter = mLastAffectedRegions.begin(); - iter != mLastAffectedRegions.end(); ++iter) - { - LLViewerRegion* regionp = *iter; - gMessageSystem->newMessageFast(_PREHASH_RedoLand); - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - gMessageSystem->sendMessage(regionp->getHost()); - } -}*/ - -// static -bool LLToolBrushLand::canTerraformRegion(LLViewerRegion* regionp) const -{ - if (!regionp) return false; - if (regionp->canManageEstate()) return true; - return !regionp->getRegionFlag(REGION_FLAGS_BLOCK_TERRAFORM); -} - -// static -bool LLToolBrushLand::canTerraformParcel(LLViewerRegion* regionp) const -{ - LLParcel* selected_parcel = LLViewerParcelMgr::getInstance()->getHoverParcel(); - bool is_terraform_allowed = false; - if (selected_parcel) - { - bool owner_release = LLViewerParcelMgr::isParcelOwnedByAgent(selected_parcel, GP_LAND_ALLOW_EDIT_LAND); - is_terraform_allowed = ( gAgent.canManageEstate() || (selected_parcel->getOwnerID() == regionp->getOwner()) || owner_release); - } - - return is_terraform_allowed; -} - - -// static -void LLToolBrushLand::alertNoTerraformRegion(LLViewerRegion* regionp) -{ - if (!regionp) return; - - LLSD args; - args["REGION"] = regionp->getName(); - LLNotificationsUtil::add("RegionNoTerraforming", args); - -} - -// static -void LLToolBrushLand::alertNoTerraformParcel() -{ - LLParcel* selected_parcel = LLViewerParcelMgr::getInstance()->getHoverParcel(); - if (selected_parcel) - { - LLSD args; - args["PARCEL"] = selected_parcel->getName(); - LLNotificationsUtil::add("ParcelNoTerraforming", args); - } - -} - -///============================================================================ -/// Local function definitions -///============================================================================ +/** + * @file lltoolbrush.cpp + * @brief Implementation of the toolbrushes + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lltoolbrush.h" +#include "lltoolselectland.h" + +// library headers +#include "llgl.h" +#include "llnotificationsutil.h" +#include "llrender.h" +#include "message.h" + +#include "llagent.h" +#include "llcallbacklist.h" +#include "llviewercontrol.h" +#include "llfloatertools.h" +#include "llregionposition.h" +#include "llstatusbar.h" +#include "llsurface.h" +#include "llsurfacepatch.h" +#include "lltoolmgr.h" +#include "llui.h" +#include "llviewerparcelmgr.h" +#include "llviewerparceloverlay.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" +#include "llworld.h" +#include "llappviewer.h" +#include "llparcel.h" +#include "roles_constants.h" +#include "llglheaders.h" + +const std::string REGION_BLOCKS_TERRAFORM_MSG = "This region does not allow terraforming.\n" + "You will need to buy land in another part of the world to terraform it."; + + +///============================================================================ +/// Local function declarations, constants, enums, and typedefs +///============================================================================ + +const S32 LAND_BRUSH_SIZE_COUNT = 3; +const F32 LAND_BRUSH_SIZE[LAND_BRUSH_SIZE_COUNT] = {1.0f, 2.0f, 4.0f}; + +enum +{ + E_LAND_LEVEL = 0, + E_LAND_RAISE = 1, + E_LAND_LOWER = 2, + E_LAND_SMOOTH = 3, + E_LAND_NOISE = 4, + E_LAND_REVERT = 5, + E_LAND_INVALID = 6, +}; +const LLColor4 OVERLAY_COLOR(1.0f, 1.0f, 1.0f, 1.0f); + +///============================================================================ +/// Class LLToolBrushLand +///============================================================================ + +// constructor +LLToolBrushLand::LLToolBrushLand() +: LLTool(std::string("Land")), + mStartingZ( 0.0f ), + mMouseX( 0 ), + mMouseY(0), + mGotHover(false), + mBrushSelected(false) +{ + mBrushSize = gSavedSettings.getF32("LandBrushSize"); +} + + +U8 LLToolBrushLand::getBrushIndex() +{ + // find the best index for desired size + // (compatibility with old sims, brush_index is now depricated - DEV-8252) + U8 index = 0; + for (U8 i = 0; i < LAND_BRUSH_SIZE_COUNT; i++) + { + if (mBrushSize > LAND_BRUSH_SIZE[i]) + { + index = i; + } + } + + return index; +} + +void LLToolBrushLand::modifyLandAtPointGlobal(const LLVector3d &pos_global, + MASK mask) +{ + S32 radioAction = gSavedSettings.getS32("RadioLandBrushAction"); + + mLastAffectedRegions.clear(); + determineAffectedRegions(mLastAffectedRegions, pos_global); + for(region_list_t::iterator iter = mLastAffectedRegions.begin(); + iter != mLastAffectedRegions.end(); ++iter) + { + LLViewerRegion* regionp = *iter; + //bool is_changed = false; + LLVector3 pos_region = regionp->getPosRegionFromGlobal(pos_global); + LLSurface &land = regionp->getLand(); + char action = E_LAND_LEVEL; + switch (radioAction) + { + case 0: + // // average toward mStartingZ + action = E_LAND_LEVEL; + break; + case 1: + action = E_LAND_RAISE; + break; + case 2: + action = E_LAND_LOWER; + break; + case 3: + action = E_LAND_SMOOTH; + break; + case 4: + action = E_LAND_NOISE; + break; + case 5: + action = E_LAND_REVERT; + break; + default: + action = E_LAND_INVALID; + break; + } + + // Don't send a message to the region if nothing changed. + //if(!is_changed) continue; + + // Now to update the patch information so it will redraw correctly. + LLSurfacePatch *patchp= land.resolvePatchRegion(pos_region); + if (patchp) + { + patchp->dirtyZ(); + } + + // Also force the property lines to update, normals to recompute, etc. + regionp->forceUpdate(); + + // tell the simulator what we've done + F32 seconds = (1.0f / gFPSClamped) * gSavedSettings.getF32("LandBrushForce"); + F32 x_pos = (F32)pos_region.mV[VX]; + F32 y_pos = (F32)pos_region.mV[VY]; + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_ModifyLand); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ModifyBlock); + msg->addU8Fast(_PREHASH_Action, (U8)action); + msg->addU8Fast(_PREHASH_BrushSize, getBrushIndex()); + msg->addF32Fast(_PREHASH_Seconds, seconds); + msg->addF32Fast(_PREHASH_Height, mStartingZ); + msg->nextBlockFast(_PREHASH_ParcelData); + msg->addS32Fast(_PREHASH_LocalID, -1); + msg->addF32Fast(_PREHASH_West, x_pos ); + msg->addF32Fast(_PREHASH_South, y_pos ); + msg->addF32Fast(_PREHASH_East, x_pos ); + msg->addF32Fast(_PREHASH_North, y_pos ); + msg->nextBlock("ModifyBlockExtended"); + msg->addF32("BrushSize", mBrushSize); + msg->sendMessage(regionp->getHost()); + } +} + +void LLToolBrushLand::modifyLandInSelectionGlobal() +{ + if (LLViewerParcelMgr::getInstance()->selectionEmpty()) + { + return; + } + + if (LLToolMgr::getInstance()->getCurrentTool() == LLToolSelectLand::getInstance()) + { + // selecting land, don't do anything + return; + } + + LLVector3d min; + LLVector3d max; + + LLViewerParcelMgr::getInstance()->getSelection(min, max); + + S32 radioAction = gSavedSettings.getS32("RadioLandBrushAction"); + + mLastAffectedRegions.clear(); + + determineAffectedRegions(mLastAffectedRegions, LLVector3d(min.mdV[VX], min.mdV[VY], 0)); + determineAffectedRegions(mLastAffectedRegions, LLVector3d(min.mdV[VX], max.mdV[VY], 0)); + determineAffectedRegions(mLastAffectedRegions, LLVector3d(max.mdV[VX], min.mdV[VY], 0)); + determineAffectedRegions(mLastAffectedRegions, LLVector3d(max.mdV[VX], max.mdV[VY], 0)); + + LLRegionPosition mid_point_region((min + max) * 0.5); + LLViewerRegion* center_region = mid_point_region.getRegion(); + if (center_region) + { + LLVector3 pos_region = mid_point_region.getPositionRegion(); + U32 grids = center_region->getLand().mGridsPerEdge; + S32 i = llclamp( (S32)pos_region.mV[VX], 0, (S32)grids ); + S32 j = llclamp( (S32)pos_region.mV[VY], 0, (S32)grids ); + mStartingZ = center_region->getLand().getZ(i+j*grids); + } + else + { + mStartingZ = 0.f; + } + + // Stop if our selection include a no-terraform region + for(region_list_t::iterator iter = mLastAffectedRegions.begin(); + iter != mLastAffectedRegions.end(); ++iter) + { + LLViewerRegion* regionp = *iter; + if (!canTerraformRegion(regionp)) + { + alertNoTerraformRegion(regionp); + return; + } + } + + for(region_list_t::iterator iter = mLastAffectedRegions.begin(); + iter != mLastAffectedRegions.end(); ++iter) + { + LLViewerRegion* regionp = *iter; + //bool is_changed = false; + LLVector3 min_region = regionp->getPosRegionFromGlobal(min); + LLVector3 max_region = regionp->getPosRegionFromGlobal(max); + + min_region.clamp(0.f, regionp->getWidth()); + max_region.clamp(0.f, regionp->getWidth()); + F32 seconds = gSavedSettings.getF32("LandBrushForce"); + + LLSurface &land = regionp->getLand(); + char action = E_LAND_LEVEL; + switch (radioAction) + { + case 0: + // // average toward mStartingZ + action = E_LAND_LEVEL; + seconds *= 0.25f; + break; + case 1: + action = E_LAND_RAISE; + seconds *= 0.25f; + break; + case 2: + action = E_LAND_LOWER; + seconds *= 0.25f; + break; + case 3: + action = E_LAND_SMOOTH; + seconds *= 5.0f; + break; + case 4: + action = E_LAND_NOISE; + seconds *= 0.5f; + break; + case 5: + action = E_LAND_REVERT; + seconds = 0.5f; + break; + default: + //action = E_LAND_INVALID; + //seconds = 0.0f; + return; + break; + } + + // Don't send a message to the region if nothing changed. + //if(!is_changed) continue; + + // Now to update the patch information so it will redraw correctly. + LLSurfacePatch *patchp= land.resolvePatchRegion(min_region); + if (patchp) + { + patchp->dirtyZ(); + } + + // Also force the property lines to update, normals to recompute, etc. + regionp->forceUpdate(); + + // tell the simulator what we've done + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_ModifyLand); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ModifyBlock); + msg->addU8Fast(_PREHASH_Action, (U8)action); + msg->addU8Fast(_PREHASH_BrushSize, getBrushIndex()); + msg->addF32Fast(_PREHASH_Seconds, seconds); + msg->addF32Fast(_PREHASH_Height, mStartingZ); + + bool parcel_selected = LLViewerParcelMgr::getInstance()->getParcelSelection()->getWholeParcelSelected(); + LLParcel* selected_parcel = LLViewerParcelMgr::getInstance()->getParcelSelection()->getParcel(); + + if (parcel_selected && selected_parcel) + { + msg->nextBlockFast(_PREHASH_ParcelData); + msg->addS32Fast(_PREHASH_LocalID, selected_parcel->getLocalID()); + msg->addF32Fast(_PREHASH_West, min_region.mV[VX] ); + msg->addF32Fast(_PREHASH_South, min_region.mV[VY] ); + msg->addF32Fast(_PREHASH_East, max_region.mV[VX] ); + msg->addF32Fast(_PREHASH_North, max_region.mV[VY] ); + } + else + { + msg->nextBlockFast(_PREHASH_ParcelData); + msg->addS32Fast(_PREHASH_LocalID, -1); + msg->addF32Fast(_PREHASH_West, min_region.mV[VX] ); + msg->addF32Fast(_PREHASH_South, min_region.mV[VY] ); + msg->addF32Fast(_PREHASH_East, max_region.mV[VX] ); + msg->addF32Fast(_PREHASH_North, max_region.mV[VY] ); + } + + msg->nextBlock("ModifyBlockExtended"); + msg->addF32("BrushSize", mBrushSize); + + msg->sendMessage(regionp->getHost()); + } +} + +void LLToolBrushLand::brush( void ) +{ + LLVector3d spot; + if( gViewerWindow->mousePointOnLandGlobal( mMouseX, mMouseY, &spot ) ) + { + // Round to nearest X,Y grid + spot.mdV[VX] = floor( spot.mdV[VX] + 0.5 ); + spot.mdV[VY] = floor( spot.mdV[VY] + 0.5 ); + + modifyLandAtPointGlobal(spot, gKeyboard->currentMask(true)); + } +} + +bool LLToolBrushLand::handleMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + // Find the z value of the initial click. + LLVector3d spot; + if( gViewerWindow->mousePointOnLandGlobal( x, y, &spot ) ) + { + // Round to nearest X,Y grid + spot.mdV[VX] = floor( spot.mdV[VX] + 0.5 ); + spot.mdV[VY] = floor( spot.mdV[VY] + 0.5 ); + + LLRegionPosition region_position( spot ); + LLViewerRegion* regionp = region_position.getRegion(); + + if (!canTerraformRegion(regionp)) + { + alertNoTerraformRegion(regionp); + return true; + } + + if (!canTerraformParcel(regionp)) + { + alertNoTerraformParcel(); + } + + LLVector3 pos_region = region_position.getPositionRegion(); + U32 grids = regionp->getLand().mGridsPerEdge; + S32 i = llclamp( (S32)pos_region.mV[VX], 0, (S32)grids ); + S32 j = llclamp( (S32)pos_region.mV[VY], 0, (S32)grids ); + mStartingZ = regionp->getLand().getZ(i+j*grids); + mMouseX = x; + mMouseY = y; + gIdleCallbacks.addFunction( &LLToolBrushLand::onIdle, (void*)this ); + setMouseCapture( true ); + + LLViewerParcelMgr::getInstance()->setSelectionVisible(false); + handled = true; + } + + return handled; +} + +bool LLToolBrushLand::handleHover( S32 x, S32 y, MASK mask ) +{ + LL_DEBUGS("UserInput") << "hover handled by LLToolBrushLand (" + << (hasMouseCapture() ? "active":"inactive") + << ")" << LL_ENDL; + mMouseX = x; + mMouseY = y; + mGotHover = true; + gViewerWindow->setCursor(UI_CURSOR_TOOLLAND); + + LLVector3d spot; + if( gViewerWindow->mousePointOnLandGlobal( mMouseX, mMouseY, &spot ) ) + { + + spot.mdV[VX] = floor( spot.mdV[VX] + 0.5 ); + spot.mdV[VY] = floor( spot.mdV[VY] + 0.5 ); + + LLViewerParcelMgr::getInstance()->setHoverParcel(spot); + } + return true; +} + +bool LLToolBrushLand::handleMouseUp(S32 x, S32 y, MASK mask) +{ + bool handled = false; + mLastAffectedRegions.clear(); + if( hasMouseCapture() ) + { + // Release the mouse + setMouseCapture( false ); + + LLViewerParcelMgr::getInstance()->setSelectionVisible(true); + + gIdleCallbacks.deleteFunction( &LLToolBrushLand::onIdle, (void*)this ); + handled = true; + } + + return handled; +} + +void LLToolBrushLand::handleSelect() +{ + gEditMenuHandler = this; + + gFloaterTools->setStatusText("modifyland"); +// if (!mBrushSelected) + { + mBrushSelected = true; + } +} + + +void LLToolBrushLand::handleDeselect() +{ + if( gEditMenuHandler == this ) + { + gEditMenuHandler = NULL; + } + LLViewerParcelMgr::getInstance()->setSelectionVisible(true); + mBrushSelected = false; +} + +// Draw the area that will be affected. +void LLToolBrushLand::render() +{ + if(mGotHover) + { + //LL_INFOS() << "LLToolBrushLand::render()" << LL_ENDL; + LLVector3d spot; + if(gViewerWindow->mousePointOnLandGlobal(mMouseX, mMouseY, &spot)) + { + spot.mdV[VX] = floor( spot.mdV[VX] + 0.5 ); + spot.mdV[VY] = floor( spot.mdV[VY] + 0.5 ); + + mBrushSize = gSavedSettings.getF32("LandBrushSize"); + + region_list_t regions; + determineAffectedRegions(regions, spot); + + // Now, for each region, render the overlay + LLVector3 pos_world = gAgent.getRegion()->getPosRegionFromGlobal(spot); + for(region_list_t::iterator iter = regions.begin(); + iter != regions.end(); ++iter) + { + LLViewerRegion* region = *iter; + renderOverlay(region->getLand(), + region->getPosRegionFromGlobal(spot), + pos_world); + } + } + mGotHover = false; + } +} + +/* + * Draw vertical lines from each vertex straight up in world space + * with lengths indicating the current "strength" slider. + * Decorate the tops and bottoms of the lines like this: + * + * Raise Revert + * /|\ ___ + * | | + * | | + * + * Rough Smooth + * /|\ ___ + * | | + * | | + * \|/..........._|_ + * + * Lower Flatten + * | | + * | | + * \|/..........._|_ + */ +void LLToolBrushLand::renderOverlay(LLSurface& land, const LLVector3& pos_region, + const LLVector3& pos_world) +{ + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + LLGLDepthTest mDepthTest(GL_TRUE); + gGL.pushMatrix(); + gGL.color4fv(OVERLAY_COLOR.mV); + gGL.translatef(0.0f, 0.0f, 1.0f); + + S32 i = (S32) pos_region.mV[VX]; + S32 j = (S32) pos_region.mV[VY]; + S32 half_edge = llfloor(mBrushSize); + S32 radioAction = gSavedSettings.getS32("RadioLandBrushAction"); + F32 force = gSavedSettings.getF32("LandBrushForce"); // .1 to 100? + + gGL.begin(LLRender::LINES); + for(S32 di = -half_edge; di <= half_edge; di++) + { + if((i+di) < 0 || (i+di) >= (S32)land.mGridsPerEdge) continue; + for(S32 dj = -half_edge; dj <= half_edge; dj++) + { + if( (j+dj) < 0 || (j+dj) >= (S32)land.mGridsPerEdge ) continue; + const F32 + wx = pos_world.mV[VX] + di, + wy = pos_world.mV[VY] + dj, + wz = land.getZ((i+di)+(j+dj)*land.mGridsPerEdge), + norm_dist = sqrt((float)di*di + dj*dj) / half_edge, + force_scale = sqrt(2.f) - norm_dist, // 1 at center, 0 at corner + wz2 = wz + .2 + (.2 + force/100) * force_scale, // top vertex + tic = .075f; // arrowhead size + // vertical line + gGL.vertex3f(wx, wy, wz); + gGL.vertex3f(wx, wy, wz2); + if(radioAction == E_LAND_RAISE || radioAction == E_LAND_NOISE) // up arrow + { + gGL.vertex3f(wx, wy, wz2); + gGL.vertex3f(wx+tic, wy, wz2-tic); + gGL.vertex3f(wx, wy, wz2); + gGL.vertex3f(wx-tic, wy, wz2-tic); + } + if(radioAction == E_LAND_LOWER || radioAction == E_LAND_NOISE) // down arrow + { + gGL.vertex3f(wx, wy, wz); + gGL.vertex3f(wx+tic, wy, wz+tic); + gGL.vertex3f(wx, wy, wz); + gGL.vertex3f(wx-tic, wy, wz+tic); + } + if(radioAction == E_LAND_REVERT || radioAction == E_LAND_SMOOTH) // flat top + { + gGL.vertex3f(wx-tic, wy, wz2); + gGL.vertex3f(wx+tic, wy, wz2); + } + if(radioAction == E_LAND_LEVEL || radioAction == E_LAND_SMOOTH) // flat bottom + { + gGL.vertex3f(wx-tic, wy, wz); + gGL.vertex3f(wx+tic, wy, wz); + } + } + } + gGL.end(); + + gGL.popMatrix(); +} + +void LLToolBrushLand::determineAffectedRegions(region_list_t& regions, + const LLVector3d& spot ) const +{ + LLVector3d corner(spot); + corner.mdV[VX] -= (mBrushSize / 2); + corner.mdV[VY] -= (mBrushSize / 2); + LLViewerRegion* region = NULL; + region = LLWorld::getInstance()->getRegionFromPosGlobal(corner); + if(region && regions.find(region) == regions.end()) + { + regions.insert(region); + } + corner.mdV[VY] += mBrushSize; + region = LLWorld::getInstance()->getRegionFromPosGlobal(corner); + if(region && regions.find(region) == regions.end()) + { + regions.insert(region); + } + corner.mdV[VX] += mBrushSize; + region = LLWorld::getInstance()->getRegionFromPosGlobal(corner); + if(region && regions.find(region) == regions.end()) + { + regions.insert(region); + } + corner.mdV[VY] -= mBrushSize; + region = LLWorld::getInstance()->getRegionFromPosGlobal(corner); + if(region && regions.find(region) == regions.end()) + { + regions.insert(region); + } +} + +// static +void LLToolBrushLand::onIdle( void* brush_tool ) +{ + LLToolBrushLand* self = reinterpret_cast(brush_tool); + + if( LLToolMgr::getInstance()->getCurrentTool() == self ) + { + self->brush(); + } + else + { + gIdleCallbacks.deleteFunction( &LLToolBrushLand::onIdle, self ); + } +} + +void LLToolBrushLand::onMouseCaptureLost() +{ + gIdleCallbacks.deleteFunction(&LLToolBrushLand::onIdle, this); +} + +// static +void LLToolBrushLand::undo() +{ + for(region_list_t::iterator iter = mLastAffectedRegions.begin(); + iter != mLastAffectedRegions.end(); ++iter) + { + LLViewerRegion* regionp = *iter; + gMessageSystem->newMessageFast(_PREHASH_UndoLand); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + gMessageSystem->sendMessage(regionp->getHost()); + } +} + +// static +/* +void LLToolBrushLand::redo() +{ + for(region_list_t::iterator iter = mLastAffectedRegions.begin(); + iter != mLastAffectedRegions.end(); ++iter) + { + LLViewerRegion* regionp = *iter; + gMessageSystem->newMessageFast(_PREHASH_RedoLand); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + gMessageSystem->sendMessage(regionp->getHost()); + } +}*/ + +// static +bool LLToolBrushLand::canTerraformRegion(LLViewerRegion* regionp) const +{ + if (!regionp) return false; + if (regionp->canManageEstate()) return true; + return !regionp->getRegionFlag(REGION_FLAGS_BLOCK_TERRAFORM); +} + +// static +bool LLToolBrushLand::canTerraformParcel(LLViewerRegion* regionp) const +{ + LLParcel* selected_parcel = LLViewerParcelMgr::getInstance()->getHoverParcel(); + bool is_terraform_allowed = false; + if (selected_parcel) + { + bool owner_release = LLViewerParcelMgr::isParcelOwnedByAgent(selected_parcel, GP_LAND_ALLOW_EDIT_LAND); + is_terraform_allowed = ( gAgent.canManageEstate() || (selected_parcel->getOwnerID() == regionp->getOwner()) || owner_release); + } + + return is_terraform_allowed; +} + + +// static +void LLToolBrushLand::alertNoTerraformRegion(LLViewerRegion* regionp) +{ + if (!regionp) return; + + LLSD args; + args["REGION"] = regionp->getName(); + LLNotificationsUtil::add("RegionNoTerraforming", args); + +} + +// static +void LLToolBrushLand::alertNoTerraformParcel() +{ + LLParcel* selected_parcel = LLViewerParcelMgr::getInstance()->getHoverParcel(); + if (selected_parcel) + { + LLSD args; + args["PARCEL"] = selected_parcel->getName(); + LLNotificationsUtil::add("ParcelNoTerraforming", args); + } + +} + +///============================================================================ +/// Local function definitions +///============================================================================ diff --git a/indra/newview/lltoolbrush.h b/indra/newview/lltoolbrush.h index 1887a10322..28cad912e5 100644 --- a/indra/newview/lltoolbrush.h +++ b/indra/newview/lltoolbrush.h @@ -1,108 +1,108 @@ -/** - * @file lltoolbrush.h - * @brief toolbrush class header file - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLTOOLBRUSH_H -#define LL_LLTOOLBRUSH_H - -#include "lltool.h" -#include "v3math.h" -#include "lleditmenuhandler.h" - -class LLSurface; -class LLVector3d; -class LLViewerRegion; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLToolBrushLand -// -// A toolbrush that modifies the land. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLToolBrushLand : public LLTool, public LLEditMenuHandler, public LLSingleton -{ - LLSINGLETON(LLToolBrushLand); - typedef std::set region_list_t; - -public: - - // x,y in window coords, 0,0 = left,bot - virtual bool handleMouseDown( S32 x, S32 y, MASK mask ) override; - virtual bool handleMouseUp( S32 x, S32 y, MASK mask ) override; - virtual bool handleHover( S32 x, S32 y, MASK mask ) override; - virtual void handleSelect() override; - virtual void handleDeselect() override; - - // isAlwaysRendered() - return true if this is a tool that should - // always be rendered regardless of selection. - virtual bool isAlwaysRendered() override { return true; } - - // Draw the area that will be affected. - virtual void render() override; - - // on Idle is where the land modification actually occurs - static void onIdle(void* brush_tool); - - void onMouseCaptureLost() override; - - void modifyLandInSelectionGlobal(); - virtual void undo() override; - virtual bool canUndo() const override { return true; } - -protected: - void brush( void ); - void modifyLandAtPointGlobal( const LLVector3d &spot, MASK mask ); - - void determineAffectedRegions(region_list_t& regions, - const LLVector3d& spot) const; - void renderOverlay(LLSurface& land, const LLVector3& pos_region, - const LLVector3& pos_world); - - // Does region allow terraform, or are we a god? - bool canTerraformRegion(LLViewerRegion* regionp) const; - - bool canTerraformParcel(LLViewerRegion* regionp) const; - - // Modal dialog that you can't terraform the region - void alertNoTerraformRegion(LLViewerRegion* regionp); - - void alertNoTerraformParcel(); - -protected: - F32 mStartingZ; - S32 mMouseX; - S32 mMouseY; - F32 mBrushSize; - bool mGotHover; - bool mBrushSelected; - // Order doesn't matter and we do check for existance of regions, so use a set - region_list_t mLastAffectedRegions; - -private: - U8 getBrushIndex(); -}; - - -#endif // LL_LLTOOLBRUSH_H +/** + * @file lltoolbrush.h + * @brief toolbrush class header file + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLTOOLBRUSH_H +#define LL_LLTOOLBRUSH_H + +#include "lltool.h" +#include "v3math.h" +#include "lleditmenuhandler.h" + +class LLSurface; +class LLVector3d; +class LLViewerRegion; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLToolBrushLand +// +// A toolbrush that modifies the land. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLToolBrushLand : public LLTool, public LLEditMenuHandler, public LLSingleton +{ + LLSINGLETON(LLToolBrushLand); + typedef std::set region_list_t; + +public: + + // x,y in window coords, 0,0 = left,bot + virtual bool handleMouseDown( S32 x, S32 y, MASK mask ) override; + virtual bool handleMouseUp( S32 x, S32 y, MASK mask ) override; + virtual bool handleHover( S32 x, S32 y, MASK mask ) override; + virtual void handleSelect() override; + virtual void handleDeselect() override; + + // isAlwaysRendered() - return true if this is a tool that should + // always be rendered regardless of selection. + virtual bool isAlwaysRendered() override { return true; } + + // Draw the area that will be affected. + virtual void render() override; + + // on Idle is where the land modification actually occurs + static void onIdle(void* brush_tool); + + void onMouseCaptureLost() override; + + void modifyLandInSelectionGlobal(); + virtual void undo() override; + virtual bool canUndo() const override { return true; } + +protected: + void brush( void ); + void modifyLandAtPointGlobal( const LLVector3d &spot, MASK mask ); + + void determineAffectedRegions(region_list_t& regions, + const LLVector3d& spot) const; + void renderOverlay(LLSurface& land, const LLVector3& pos_region, + const LLVector3& pos_world); + + // Does region allow terraform, or are we a god? + bool canTerraformRegion(LLViewerRegion* regionp) const; + + bool canTerraformParcel(LLViewerRegion* regionp) const; + + // Modal dialog that you can't terraform the region + void alertNoTerraformRegion(LLViewerRegion* regionp); + + void alertNoTerraformParcel(); + +protected: + F32 mStartingZ; + S32 mMouseX; + S32 mMouseY; + F32 mBrushSize; + bool mGotHover; + bool mBrushSelected; + // Order doesn't matter and we do check for existance of regions, so use a set + region_list_t mLastAffectedRegions; + +private: + U8 getBrushIndex(); +}; + + +#endif // LL_LLTOOLBRUSH_H diff --git a/indra/newview/lltoolcomp.cpp b/indra/newview/lltoolcomp.cpp index e0310e8885..c6e59a81c9 100644 --- a/indra/newview/lltoolcomp.cpp +++ b/indra/newview/lltoolcomp.cpp @@ -1,840 +1,840 @@ -/** - * @file lltoolcomp.cpp - * @brief Composite tools - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lltoolcomp.h" - -#include "llfloaterreg.h" -#include "llgl.h" -#include "indra_constants.h" - -#include "llmanip.h" -#include "llmaniprotate.h" -#include "llmanipscale.h" -#include "llmaniptranslate.h" -#include "llmenugl.h" // for right-click menu hack -#include "llselectmgr.h" -#include "lltoolfocus.h" -#include "lltoolgrab.h" -#include "lltoolgun.h" -#include "lltoolmgr.h" -#include "lltoolselectrect.h" -#include "lltoolplacer.h" -#include "llviewerinput.h" -#include "llviewermenu.h" -#include "llviewerobject.h" -#include "llviewerwindow.h" -#include "llagent.h" -#include "llagentcamera.h" -#include "llfloatertools.h" -#include "llviewercontrol.h" - -extern LLControlGroup gSavedSettings; - -// we use this in various places instead of NULL -static LLPointer sNullTool(new LLTool(std::string("null"), NULL)); - -//----------------------------------------------------------------------- -// LLToolComposite - -//static -void LLToolComposite::setCurrentTool( LLTool* new_tool ) -{ - if( mCur != new_tool ) - { - if( mSelected ) - { - mCur->handleDeselect(); - mCur = new_tool; - mCur->handleSelect(); - } - else - { - mCur = new_tool; - } - } -} - -LLToolComposite::LLToolComposite(const std::string& name) - : LLTool(name), - mCur(sNullTool), - mDefault(sNullTool), - mSelected(false), - mMouseDown(false), mManip(NULL), mSelectRect(NULL) -{ -} - -// Returns to the default tool -bool LLToolComposite::handleMouseUp(S32 x, S32 y, MASK mask) -{ - bool handled = mCur->handleMouseUp( x, y, mask ); - if( handled ) - { - setCurrentTool( mDefault ); - } - return handled; -} - -void LLToolComposite::onMouseCaptureLost() -{ - mCur->onMouseCaptureLost(); - setCurrentTool( mDefault ); -} - -bool LLToolComposite::isSelecting() -{ - return mCur == mSelectRect; -} - -void LLToolComposite::handleSelect() -{ - if (!gSavedSettings.getBOOL("EditLinkedParts")) - { - LLSelectMgr::getInstance()->promoteSelectionToRoot(); - } - mCur = mDefault; - mCur->handleSelect(); - mSelected = true; -} - -void LLToolComposite::handleDeselect() -{ - mCur->handleDeselect(); - mCur = mDefault; - mSelected = false; -} - -//---------------------------------------------------------------------------- -// LLToolCompInspect -//---------------------------------------------------------------------------- - -LLToolCompInspect::LLToolCompInspect() -: LLToolComposite(std::string("Inspect")), - mIsToolCameraActive(false) -{ - mSelectRect = new LLToolSelectRect(this); - mDefault = mSelectRect; -} - - -LLToolCompInspect::~LLToolCompInspect() -{ - delete mSelectRect; - mSelectRect = NULL; -} - -bool LLToolCompInspect::handleMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - if (mCur == LLToolCamera::getInstance()) - { - handled = mCur->handleMouseDown(x, y, mask); - } - else - { - mMouseDown = true; - gViewerWindow->pickAsync(x, y, mask, pickCallback); - handled = true; - } - - return handled; -} - -bool LLToolCompInspect::handleMouseUp(S32 x, S32 y, MASK mask) -{ - bool handled = LLToolComposite::handleMouseUp(x, y, mask); - mIsToolCameraActive = getCurrentTool() == LLToolCamera::getInstance(); - return handled; -} - -void LLToolCompInspect::pickCallback(const LLPickInfo& pick_info) -{ - LLViewerObject* hit_obj = pick_info.getObject(); - LLToolCompInspect * tool_inspectp = LLToolCompInspect::getInstance(); - - if (!tool_inspectp->mMouseDown) - { - // fast click on object, but mouse is already up...just do select - tool_inspectp->mSelectRect->handleObjectSelection(pick_info, gSavedSettings.getBOOL("EditLinkedParts"), false); - return; - } - - LLSelectMgr * mgr_selectp = LLSelectMgr::getInstance(); - if( hit_obj && mgr_selectp->getSelection()->getObjectCount()) { - LLEditMenuHandler::gEditMenuHandler = mgr_selectp; - } - - tool_inspectp->setCurrentTool( tool_inspectp->mSelectRect ); - tool_inspectp->mIsToolCameraActive = false; - tool_inspectp->mSelectRect->handlePick( pick_info ); -} - -bool LLToolCompInspect::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - return true; -} - -bool LLToolCompInspect::handleKey(KEY key, MASK mask) -{ - bool handled = false; - - if(KEY_ALT == key) - { - setCurrentTool(LLToolCamera::getInstance()); - mIsToolCameraActive = true; - handled = true; - } - else - { - handled = LLToolComposite::handleKey(key, mask); - } - - return handled; -} - -void LLToolCompInspect::onMouseCaptureLost() -{ - LLToolComposite::onMouseCaptureLost(); - mIsToolCameraActive = false; -} - -void LLToolCompInspect::keyUp(KEY key, MASK mask) -{ - if (KEY_ALT == key && mCur == LLToolCamera::getInstance()) - { - setCurrentTool(mDefault); - mIsToolCameraActive = false; - } -} - -//---------------------------------------------------------------------------- -// LLToolCompTranslate -//---------------------------------------------------------------------------- - -LLToolCompTranslate::LLToolCompTranslate() - : LLToolComposite(std::string("Move")) -{ - mManip = new LLManipTranslate(this); - mSelectRect = new LLToolSelectRect(this); - - mCur = mManip; - mDefault = mManip; -} - -LLToolCompTranslate::~LLToolCompTranslate() -{ - delete mManip; - mManip = NULL; - - delete mSelectRect; - mSelectRect = NULL; -} - -bool LLToolCompTranslate::handleHover(S32 x, S32 y, MASK mask) -{ - if( !mCur->hasMouseCapture() ) - { - setCurrentTool( mManip ); - } - return mCur->handleHover( x, y, mask ); -} - - -bool LLToolCompTranslate::handleMouseDown(S32 x, S32 y, MASK mask) -{ - mMouseDown = true; - gViewerWindow->pickAsync(x, y, mask, pickCallback, /*bool pick_transparent*/ false, LLFloaterReg::instanceVisible("build"), false, - gSavedSettings.getBOOL("SelectReflectionProbes"));; - return true; -} - -void LLToolCompTranslate::pickCallback(const LLPickInfo& pick_info) -{ - LLViewerObject* hit_obj = pick_info.getObject(); - - LLToolCompTranslate::getInstance()->mManip->highlightManipulators(pick_info.mMousePt.mX, pick_info.mMousePt.mY); - if (!LLToolCompTranslate::getInstance()->mMouseDown) - { - // fast click on object, but mouse is already up...just do select - LLToolCompTranslate::getInstance()->mSelectRect->handleObjectSelection(pick_info, gSavedSettings.getBOOL("EditLinkedParts"), false); - return; - } - - if( hit_obj || LLToolCompTranslate::getInstance()->mManip->getHighlightedPart() != LLManip::LL_NO_PART ) - { - if (LLToolCompTranslate::getInstance()->mManip->getSelection()->getObjectCount()) - { - LLEditMenuHandler::gEditMenuHandler = LLSelectMgr::getInstance(); - } - - bool can_move = LLToolCompTranslate::getInstance()->mManip->canAffectSelection(); - - if( LLManip::LL_NO_PART != LLToolCompTranslate::getInstance()->mManip->getHighlightedPart() && can_move) - { - LLToolCompTranslate::getInstance()->setCurrentTool( LLToolCompTranslate::getInstance()->mManip ); - LLToolCompTranslate::getInstance()->mManip->handleMouseDownOnPart( pick_info.mMousePt.mX, pick_info.mMousePt.mY, pick_info.mKeyMask ); - } - else - { - LLToolCompTranslate::getInstance()->setCurrentTool( LLToolCompTranslate::getInstance()->mSelectRect ); - LLToolCompTranslate::getInstance()->mSelectRect->handlePick( pick_info ); - - // *TODO: add toggle to trigger old click-drag functionality - // LLToolCompTranslate::getInstance()->mManip->handleMouseDownOnPart( XY_part, x, y, mask); - } - } - else - { - LLToolCompTranslate::getInstance()->setCurrentTool( LLToolCompTranslate::getInstance()->mSelectRect ); - LLToolCompTranslate::getInstance()->mSelectRect->handlePick( pick_info ); - } -} - -bool LLToolCompTranslate::handleMouseUp(S32 x, S32 y, MASK mask) -{ - mMouseDown = false; - return LLToolComposite::handleMouseUp(x, y, mask); -} - -LLTool* LLToolCompTranslate::getOverrideTool(MASK mask) -{ - if (mask == MASK_CONTROL) - { - return LLToolCompRotate::getInstance(); - } - else if (mask == (MASK_CONTROL | MASK_SHIFT)) - { - return LLToolCompScale::getInstance(); - } - return LLToolComposite::getOverrideTool(mask); -} - -bool LLToolCompTranslate::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - if (mManip->getSelection()->isEmpty() && mManip->getHighlightedPart() == LLManip::LL_NO_PART) - { - // You should already have an object selected from the mousedown. - // If so, show its properties - LLFloaterReg::showInstance("build", "Content"); - return true; - } - // Nothing selected means the first mouse click was probably - // bad, so try again. - // This also consumes the event to prevent things like double-click - // teleport from triggering. - return handleMouseDown(x, y, mask); -} - - -void LLToolCompTranslate::render() -{ - mCur->render(); // removing this will not draw the RGB arrows and guidelines - - if( mCur != mManip ) - { - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); - mManip->renderGuidelines(); - } -} - - -//----------------------------------------------------------------------- -// LLToolCompScale - -LLToolCompScale::LLToolCompScale() - : LLToolComposite(std::string("Stretch")) -{ - mManip = new LLManipScale(this); - mSelectRect = new LLToolSelectRect(this); - - mCur = mManip; - mDefault = mManip; -} - -LLToolCompScale::~LLToolCompScale() -{ - delete mManip; - delete mSelectRect; -} - -bool LLToolCompScale::handleHover(S32 x, S32 y, MASK mask) -{ - if( !mCur->hasMouseCapture() ) - { - setCurrentTool(mManip ); - } - return mCur->handleHover( x, y, mask ); -} - - -bool LLToolCompScale::handleMouseDown(S32 x, S32 y, MASK mask) -{ - mMouseDown = true; - gViewerWindow->pickAsync(x, y, mask, pickCallback); - return true; -} - -void LLToolCompScale::pickCallback(const LLPickInfo& pick_info) -{ - LLViewerObject* hit_obj = pick_info.getObject(); - - LLToolCompScale::getInstance()->mManip->highlightManipulators(pick_info.mMousePt.mX, pick_info.mMousePt.mY); - if (!LLToolCompScale::getInstance()->mMouseDown) - { - // fast click on object, but mouse is already up...just do select - LLToolCompScale::getInstance()->mSelectRect->handleObjectSelection(pick_info, gSavedSettings.getBOOL("EditLinkedParts"), false); - - return; - } - - if( hit_obj || LLToolCompScale::getInstance()->mManip->getHighlightedPart() != LLManip::LL_NO_PART) - { - if (LLToolCompScale::getInstance()->mManip->getSelection()->getObjectCount()) - { - LLEditMenuHandler::gEditMenuHandler = LLSelectMgr::getInstance(); - } - if( LLManip::LL_NO_PART != LLToolCompScale::getInstance()->mManip->getHighlightedPart() ) - { - LLToolCompScale::getInstance()->setCurrentTool( LLToolCompScale::getInstance()->mManip ); - LLToolCompScale::getInstance()->mManip->handleMouseDownOnPart( pick_info.mMousePt.mX, pick_info.mMousePt.mY, pick_info.mKeyMask ); - } - else - { - LLToolCompScale::getInstance()->setCurrentTool( LLToolCompScale::getInstance()->mSelectRect ); - LLToolCompScale::getInstance()->mSelectRect->handlePick( pick_info ); - } - } - else - { - LLToolCompScale::getInstance()->setCurrentTool( LLToolCompScale::getInstance()->mSelectRect ); - LLToolCompScale::getInstance()->mSelectRect->handlePick( pick_info ); - } -} - -bool LLToolCompScale::handleMouseUp(S32 x, S32 y, MASK mask) -{ - mMouseDown = false; - return LLToolComposite::handleMouseUp(x, y, mask); -} - -LLTool* LLToolCompScale::getOverrideTool(MASK mask) -{ - if (mask == MASK_CONTROL) - { - return LLToolCompRotate::getInstance(); - } - - return LLToolComposite::getOverrideTool(mask); -} - - -bool LLToolCompScale::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - if (!mManip->getSelection()->isEmpty() && mManip->getHighlightedPart() == LLManip::LL_NO_PART) - { - // You should already have an object selected from the mousedown. - // If so, show its properties - LLFloaterReg::showInstance("build", "Content"); - return true; - } - else - { - // Nothing selected means the first mouse click was probably - // bad, so try again. - return handleMouseDown(x, y, mask); - } -} - - -void LLToolCompScale::render() -{ - mCur->render(); - - if( mCur != mManip ) - { - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); - mManip->renderGuidelines(); - } -} - -//----------------------------------------------------------------------- -// LLToolCompCreate - -LLToolCompCreate::LLToolCompCreate() - : LLToolComposite(std::string("Create")) -{ - mPlacer = new LLToolPlacer(); - mSelectRect = new LLToolSelectRect(this); - - mCur = mPlacer; - mDefault = mPlacer; - mObjectPlacedOnMouseDown = false; -} - - -LLToolCompCreate::~LLToolCompCreate() -{ - delete mPlacer; - delete mSelectRect; -} - - -bool LLToolCompCreate::handleMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = false; - mMouseDown = true; - - if ( (mask == MASK_SHIFT) || (mask == MASK_CONTROL) ) - { - gViewerWindow->pickAsync(x, y, mask, pickCallback); - handled = true; - } - else - { - setCurrentTool( mPlacer ); - handled = mPlacer->placeObject( x, y, mask ); - } - - mObjectPlacedOnMouseDown = true; - - return handled; -} - -void LLToolCompCreate::pickCallback(const LLPickInfo& pick_info) -{ - // *NOTE: We mask off shift and control, so you cannot - // multi-select multiple objects with the create tool. - MASK mask = (pick_info.mKeyMask & ~MASK_SHIFT); - mask = (mask & ~MASK_CONTROL); - - LLToolCompCreate::getInstance()->setCurrentTool( LLToolCompCreate::getInstance()->mSelectRect ); - LLToolCompCreate::getInstance()->mSelectRect->handlePick( pick_info ); -} - -bool LLToolCompCreate::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - return handleMouseDown(x, y, mask); -} - -bool LLToolCompCreate::handleMouseUp(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - if ( mMouseDown && !mObjectPlacedOnMouseDown && !(mask == MASK_SHIFT) && !(mask == MASK_CONTROL) ) - { - setCurrentTool( mPlacer ); - handled = mPlacer->placeObject( x, y, mask ); - } - - mObjectPlacedOnMouseDown = false; - mMouseDown = false; - - if (!handled) - { - handled = LLToolComposite::handleMouseUp(x, y, mask); - } - - return handled; -} - -//----------------------------------------------------------------------- -// LLToolCompRotate - -LLToolCompRotate::LLToolCompRotate() - : LLToolComposite(std::string("Rotate")) -{ - mManip = new LLManipRotate(this); - mSelectRect = new LLToolSelectRect(this); - - mCur = mManip; - mDefault = mManip; -} - - -LLToolCompRotate::~LLToolCompRotate() -{ - delete mManip; - delete mSelectRect; -} - -bool LLToolCompRotate::handleHover(S32 x, S32 y, MASK mask) -{ - if( !mCur->hasMouseCapture() ) - { - setCurrentTool( mManip ); - } - return mCur->handleHover( x, y, mask ); -} - - -bool LLToolCompRotate::handleMouseDown(S32 x, S32 y, MASK mask) -{ - mMouseDown = true; - gViewerWindow->pickAsync(x, y, mask, pickCallback); - return true; -} - -void LLToolCompRotate::pickCallback(const LLPickInfo& pick_info) -{ - LLViewerObject* hit_obj = pick_info.getObject(); - - LLToolCompRotate::getInstance()->mManip->highlightManipulators(pick_info.mMousePt.mX, pick_info.mMousePt.mY); - if (!LLToolCompRotate::getInstance()->mMouseDown) - { - // fast click on object, but mouse is already up...just do select - LLToolCompRotate::getInstance()->mSelectRect->handleObjectSelection(pick_info, gSavedSettings.getBOOL("EditLinkedParts"), false); - return; - } - - if( hit_obj || LLToolCompRotate::getInstance()->mManip->getHighlightedPart() != LLManip::LL_NO_PART) - { - if (LLToolCompRotate::getInstance()->mManip->getSelection()->getObjectCount()) - { - LLEditMenuHandler::gEditMenuHandler = LLSelectMgr::getInstance(); - } - if( LLManip::LL_NO_PART != LLToolCompRotate::getInstance()->mManip->getHighlightedPart() ) - { - LLToolCompRotate::getInstance()->setCurrentTool( LLToolCompRotate::getInstance()->mManip ); - LLToolCompRotate::getInstance()->mManip->handleMouseDownOnPart( pick_info.mMousePt.mX, pick_info.mMousePt.mY, pick_info.mKeyMask ); - } - else - { - LLToolCompRotate::getInstance()->setCurrentTool( LLToolCompRotate::getInstance()->mSelectRect ); - LLToolCompRotate::getInstance()->mSelectRect->handlePick( pick_info ); - } - } - else - { - LLToolCompRotate::getInstance()->setCurrentTool( LLToolCompRotate::getInstance()->mSelectRect ); - LLToolCompRotate::getInstance()->mSelectRect->handlePick( pick_info ); - } -} - -bool LLToolCompRotate::handleMouseUp(S32 x, S32 y, MASK mask) -{ - mMouseDown = false; - return LLToolComposite::handleMouseUp(x, y, mask); -} - -LLTool* LLToolCompRotate::getOverrideTool(MASK mask) -{ - if (mask == (MASK_CONTROL | MASK_SHIFT)) - { - return LLToolCompScale::getInstance(); - } - return LLToolComposite::getOverrideTool(mask); -} - -bool LLToolCompRotate::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - if (!mManip->getSelection()->isEmpty() && mManip->getHighlightedPart() == LLManip::LL_NO_PART) - { - // You should already have an object selected from the mousedown. - // If so, show its properties - LLFloaterReg::showInstance("build", "Content"); - return true; - } - else - { - // Nothing selected means the first mouse click was probably - // bad, so try again. - return handleMouseDown(x, y, mask); - } -} - - -void LLToolCompRotate::render() -{ - mCur->render(); - - if( mCur != mManip ) - { - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); - mManip->renderGuidelines(); - } -} - - -//----------------------------------------------------------------------- -// LLToolCompGun - -LLToolCompGun::LLToolCompGun() - : LLToolComposite(std::string("Mouselook")) -{ - mGun = new LLToolGun(this); - mGrab = new LLToolGrabBase(this); - mNull = sNullTool; - - setCurrentTool(mGun); - mDefault = mGun; -} - - -LLToolCompGun::~LLToolCompGun() -{ - delete mGun; - mGun = NULL; - - delete mGrab; - mGrab = NULL; - - // don't delete a static object - // delete mNull; - mNull = NULL; -} - -bool LLToolCompGun::handleHover(S32 x, S32 y, MASK mask) -{ - // *NOTE: This hack is here to make mouselook kick in again after - // item selected from context menu. - if ( mCur == mNull && !gPopupMenuView->getVisible() ) - { - LLSelectMgr::getInstance()->deselectAll(); - setCurrentTool( (LLTool*) mGrab ); - } - - // Note: if the tool changed, we can't delegate the current mouse event - // after the change because tools can modify the mouse during selection and deselection. - // Instead we let the current tool handle the event and then make the change. - // The new tool will take effect on the next frame. - - mCur->handleHover( x, y, mask ); - - // If mouse button not down... - if( !gViewerWindow->getLeftMouseDown()) - { - // let ALT switch from gun to grab - if ( mCur == mGun && (mask & MASK_ALT) ) - { - setCurrentTool( (LLTool*) mGrab ); - } - else if ( mCur == mGrab && !(mask & MASK_ALT) ) - { - setCurrentTool( (LLTool*) mGun ); - setMouseCapture(true); - } - } - - return true; -} - - -bool LLToolCompGun::handleMouseDown(S32 x, S32 y, MASK mask) -{ - // if the left button is grabbed, don't put up the pie menu - if (gAgent.leftButtonGrabbed() && gViewerInput.isLMouseHandlingDefault(MODE_FIRST_PERSON)) - { - gAgent.setControlFlags(AGENT_CONTROL_ML_LBUTTON_DOWN); - return false; - } - - // On mousedown, start grabbing - gGrabTransientTool = this; - LLToolMgr::getInstance()->getCurrentToolset()->selectTool( (LLTool*) mGrab ); - - return LLToolGrab::getInstance()->handleMouseDown(x, y, mask); -} - - -bool LLToolCompGun::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - // if the left button is grabbed, don't put up the pie menu - if (gAgent.leftButtonGrabbed() && gViewerInput.isLMouseHandlingDefault(MODE_FIRST_PERSON)) - { - gAgent.setControlFlags(AGENT_CONTROL_ML_LBUTTON_DOWN); - return false; - } - - // On mousedown, start grabbing - gGrabTransientTool = this; - LLToolMgr::getInstance()->getCurrentToolset()->selectTool( (LLTool*) mGrab ); - - return LLToolGrab::getInstance()->handleDoubleClick(x, y, mask); -} - - -bool LLToolCompGun::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - /* JC - suppress context menu 8/29/2002 - - // On right mouse, go through some convoluted steps to - // make the build menu appear. - setCurrentTool( (LLTool*) mNull ); - - // This should return false, meaning the context menu will - // be shown. - return false; - */ - - // Returning true will suppress the context menu - return true; -} - - -bool LLToolCompGun::handleMouseUp(S32 x, S32 y, MASK mask) -{ - if (gViewerInput.isLMouseHandlingDefault(MODE_FIRST_PERSON)) - { - gAgent.setControlFlags(AGENT_CONTROL_ML_LBUTTON_UP); - } - setCurrentTool( (LLTool*) mGun ); - return true; -} - -void LLToolCompGun::onMouseCaptureLost() -{ - if (mComposite) - { - mComposite->onMouseCaptureLost(); - return; - } - mCur->onMouseCaptureLost(); -} - -void LLToolCompGun::handleSelect() -{ - LLToolComposite::handleSelect(); - setMouseCapture(true); -} - -void LLToolCompGun::handleDeselect() -{ - LLToolComposite::handleDeselect(); - setMouseCapture(false); -} - - -bool LLToolCompGun::handleScrollWheel(S32 x, S32 y, S32 clicks) -{ - if (clicks > 0) - { - gAgentCamera.changeCameraToDefault(); - - } - return true; -} +/** + * @file lltoolcomp.cpp + * @brief Composite tools + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lltoolcomp.h" + +#include "llfloaterreg.h" +#include "llgl.h" +#include "indra_constants.h" + +#include "llmanip.h" +#include "llmaniprotate.h" +#include "llmanipscale.h" +#include "llmaniptranslate.h" +#include "llmenugl.h" // for right-click menu hack +#include "llselectmgr.h" +#include "lltoolfocus.h" +#include "lltoolgrab.h" +#include "lltoolgun.h" +#include "lltoolmgr.h" +#include "lltoolselectrect.h" +#include "lltoolplacer.h" +#include "llviewerinput.h" +#include "llviewermenu.h" +#include "llviewerobject.h" +#include "llviewerwindow.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llfloatertools.h" +#include "llviewercontrol.h" + +extern LLControlGroup gSavedSettings; + +// we use this in various places instead of NULL +static LLPointer sNullTool(new LLTool(std::string("null"), NULL)); + +//----------------------------------------------------------------------- +// LLToolComposite + +//static +void LLToolComposite::setCurrentTool( LLTool* new_tool ) +{ + if( mCur != new_tool ) + { + if( mSelected ) + { + mCur->handleDeselect(); + mCur = new_tool; + mCur->handleSelect(); + } + else + { + mCur = new_tool; + } + } +} + +LLToolComposite::LLToolComposite(const std::string& name) + : LLTool(name), + mCur(sNullTool), + mDefault(sNullTool), + mSelected(false), + mMouseDown(false), mManip(NULL), mSelectRect(NULL) +{ +} + +// Returns to the default tool +bool LLToolComposite::handleMouseUp(S32 x, S32 y, MASK mask) +{ + bool handled = mCur->handleMouseUp( x, y, mask ); + if( handled ) + { + setCurrentTool( mDefault ); + } + return handled; +} + +void LLToolComposite::onMouseCaptureLost() +{ + mCur->onMouseCaptureLost(); + setCurrentTool( mDefault ); +} + +bool LLToolComposite::isSelecting() +{ + return mCur == mSelectRect; +} + +void LLToolComposite::handleSelect() +{ + if (!gSavedSettings.getBOOL("EditLinkedParts")) + { + LLSelectMgr::getInstance()->promoteSelectionToRoot(); + } + mCur = mDefault; + mCur->handleSelect(); + mSelected = true; +} + +void LLToolComposite::handleDeselect() +{ + mCur->handleDeselect(); + mCur = mDefault; + mSelected = false; +} + +//---------------------------------------------------------------------------- +// LLToolCompInspect +//---------------------------------------------------------------------------- + +LLToolCompInspect::LLToolCompInspect() +: LLToolComposite(std::string("Inspect")), + mIsToolCameraActive(false) +{ + mSelectRect = new LLToolSelectRect(this); + mDefault = mSelectRect; +} + + +LLToolCompInspect::~LLToolCompInspect() +{ + delete mSelectRect; + mSelectRect = NULL; +} + +bool LLToolCompInspect::handleMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + if (mCur == LLToolCamera::getInstance()) + { + handled = mCur->handleMouseDown(x, y, mask); + } + else + { + mMouseDown = true; + gViewerWindow->pickAsync(x, y, mask, pickCallback); + handled = true; + } + + return handled; +} + +bool LLToolCompInspect::handleMouseUp(S32 x, S32 y, MASK mask) +{ + bool handled = LLToolComposite::handleMouseUp(x, y, mask); + mIsToolCameraActive = getCurrentTool() == LLToolCamera::getInstance(); + return handled; +} + +void LLToolCompInspect::pickCallback(const LLPickInfo& pick_info) +{ + LLViewerObject* hit_obj = pick_info.getObject(); + LLToolCompInspect * tool_inspectp = LLToolCompInspect::getInstance(); + + if (!tool_inspectp->mMouseDown) + { + // fast click on object, but mouse is already up...just do select + tool_inspectp->mSelectRect->handleObjectSelection(pick_info, gSavedSettings.getBOOL("EditLinkedParts"), false); + return; + } + + LLSelectMgr * mgr_selectp = LLSelectMgr::getInstance(); + if( hit_obj && mgr_selectp->getSelection()->getObjectCount()) { + LLEditMenuHandler::gEditMenuHandler = mgr_selectp; + } + + tool_inspectp->setCurrentTool( tool_inspectp->mSelectRect ); + tool_inspectp->mIsToolCameraActive = false; + tool_inspectp->mSelectRect->handlePick( pick_info ); +} + +bool LLToolCompInspect::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + return true; +} + +bool LLToolCompInspect::handleKey(KEY key, MASK mask) +{ + bool handled = false; + + if(KEY_ALT == key) + { + setCurrentTool(LLToolCamera::getInstance()); + mIsToolCameraActive = true; + handled = true; + } + else + { + handled = LLToolComposite::handleKey(key, mask); + } + + return handled; +} + +void LLToolCompInspect::onMouseCaptureLost() +{ + LLToolComposite::onMouseCaptureLost(); + mIsToolCameraActive = false; +} + +void LLToolCompInspect::keyUp(KEY key, MASK mask) +{ + if (KEY_ALT == key && mCur == LLToolCamera::getInstance()) + { + setCurrentTool(mDefault); + mIsToolCameraActive = false; + } +} + +//---------------------------------------------------------------------------- +// LLToolCompTranslate +//---------------------------------------------------------------------------- + +LLToolCompTranslate::LLToolCompTranslate() + : LLToolComposite(std::string("Move")) +{ + mManip = new LLManipTranslate(this); + mSelectRect = new LLToolSelectRect(this); + + mCur = mManip; + mDefault = mManip; +} + +LLToolCompTranslate::~LLToolCompTranslate() +{ + delete mManip; + mManip = NULL; + + delete mSelectRect; + mSelectRect = NULL; +} + +bool LLToolCompTranslate::handleHover(S32 x, S32 y, MASK mask) +{ + if( !mCur->hasMouseCapture() ) + { + setCurrentTool( mManip ); + } + return mCur->handleHover( x, y, mask ); +} + + +bool LLToolCompTranslate::handleMouseDown(S32 x, S32 y, MASK mask) +{ + mMouseDown = true; + gViewerWindow->pickAsync(x, y, mask, pickCallback, /*bool pick_transparent*/ false, LLFloaterReg::instanceVisible("build"), false, + gSavedSettings.getBOOL("SelectReflectionProbes"));; + return true; +} + +void LLToolCompTranslate::pickCallback(const LLPickInfo& pick_info) +{ + LLViewerObject* hit_obj = pick_info.getObject(); + + LLToolCompTranslate::getInstance()->mManip->highlightManipulators(pick_info.mMousePt.mX, pick_info.mMousePt.mY); + if (!LLToolCompTranslate::getInstance()->mMouseDown) + { + // fast click on object, but mouse is already up...just do select + LLToolCompTranslate::getInstance()->mSelectRect->handleObjectSelection(pick_info, gSavedSettings.getBOOL("EditLinkedParts"), false); + return; + } + + if( hit_obj || LLToolCompTranslate::getInstance()->mManip->getHighlightedPart() != LLManip::LL_NO_PART ) + { + if (LLToolCompTranslate::getInstance()->mManip->getSelection()->getObjectCount()) + { + LLEditMenuHandler::gEditMenuHandler = LLSelectMgr::getInstance(); + } + + bool can_move = LLToolCompTranslate::getInstance()->mManip->canAffectSelection(); + + if( LLManip::LL_NO_PART != LLToolCompTranslate::getInstance()->mManip->getHighlightedPart() && can_move) + { + LLToolCompTranslate::getInstance()->setCurrentTool( LLToolCompTranslate::getInstance()->mManip ); + LLToolCompTranslate::getInstance()->mManip->handleMouseDownOnPart( pick_info.mMousePt.mX, pick_info.mMousePt.mY, pick_info.mKeyMask ); + } + else + { + LLToolCompTranslate::getInstance()->setCurrentTool( LLToolCompTranslate::getInstance()->mSelectRect ); + LLToolCompTranslate::getInstance()->mSelectRect->handlePick( pick_info ); + + // *TODO: add toggle to trigger old click-drag functionality + // LLToolCompTranslate::getInstance()->mManip->handleMouseDownOnPart( XY_part, x, y, mask); + } + } + else + { + LLToolCompTranslate::getInstance()->setCurrentTool( LLToolCompTranslate::getInstance()->mSelectRect ); + LLToolCompTranslate::getInstance()->mSelectRect->handlePick( pick_info ); + } +} + +bool LLToolCompTranslate::handleMouseUp(S32 x, S32 y, MASK mask) +{ + mMouseDown = false; + return LLToolComposite::handleMouseUp(x, y, mask); +} + +LLTool* LLToolCompTranslate::getOverrideTool(MASK mask) +{ + if (mask == MASK_CONTROL) + { + return LLToolCompRotate::getInstance(); + } + else if (mask == (MASK_CONTROL | MASK_SHIFT)) + { + return LLToolCompScale::getInstance(); + } + return LLToolComposite::getOverrideTool(mask); +} + +bool LLToolCompTranslate::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + if (mManip->getSelection()->isEmpty() && mManip->getHighlightedPart() == LLManip::LL_NO_PART) + { + // You should already have an object selected from the mousedown. + // If so, show its properties + LLFloaterReg::showInstance("build", "Content"); + return true; + } + // Nothing selected means the first mouse click was probably + // bad, so try again. + // This also consumes the event to prevent things like double-click + // teleport from triggering. + return handleMouseDown(x, y, mask); +} + + +void LLToolCompTranslate::render() +{ + mCur->render(); // removing this will not draw the RGB arrows and guidelines + + if( mCur != mManip ) + { + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); + mManip->renderGuidelines(); + } +} + + +//----------------------------------------------------------------------- +// LLToolCompScale + +LLToolCompScale::LLToolCompScale() + : LLToolComposite(std::string("Stretch")) +{ + mManip = new LLManipScale(this); + mSelectRect = new LLToolSelectRect(this); + + mCur = mManip; + mDefault = mManip; +} + +LLToolCompScale::~LLToolCompScale() +{ + delete mManip; + delete mSelectRect; +} + +bool LLToolCompScale::handleHover(S32 x, S32 y, MASK mask) +{ + if( !mCur->hasMouseCapture() ) + { + setCurrentTool(mManip ); + } + return mCur->handleHover( x, y, mask ); +} + + +bool LLToolCompScale::handleMouseDown(S32 x, S32 y, MASK mask) +{ + mMouseDown = true; + gViewerWindow->pickAsync(x, y, mask, pickCallback); + return true; +} + +void LLToolCompScale::pickCallback(const LLPickInfo& pick_info) +{ + LLViewerObject* hit_obj = pick_info.getObject(); + + LLToolCompScale::getInstance()->mManip->highlightManipulators(pick_info.mMousePt.mX, pick_info.mMousePt.mY); + if (!LLToolCompScale::getInstance()->mMouseDown) + { + // fast click on object, but mouse is already up...just do select + LLToolCompScale::getInstance()->mSelectRect->handleObjectSelection(pick_info, gSavedSettings.getBOOL("EditLinkedParts"), false); + + return; + } + + if( hit_obj || LLToolCompScale::getInstance()->mManip->getHighlightedPart() != LLManip::LL_NO_PART) + { + if (LLToolCompScale::getInstance()->mManip->getSelection()->getObjectCount()) + { + LLEditMenuHandler::gEditMenuHandler = LLSelectMgr::getInstance(); + } + if( LLManip::LL_NO_PART != LLToolCompScale::getInstance()->mManip->getHighlightedPart() ) + { + LLToolCompScale::getInstance()->setCurrentTool( LLToolCompScale::getInstance()->mManip ); + LLToolCompScale::getInstance()->mManip->handleMouseDownOnPart( pick_info.mMousePt.mX, pick_info.mMousePt.mY, pick_info.mKeyMask ); + } + else + { + LLToolCompScale::getInstance()->setCurrentTool( LLToolCompScale::getInstance()->mSelectRect ); + LLToolCompScale::getInstance()->mSelectRect->handlePick( pick_info ); + } + } + else + { + LLToolCompScale::getInstance()->setCurrentTool( LLToolCompScale::getInstance()->mSelectRect ); + LLToolCompScale::getInstance()->mSelectRect->handlePick( pick_info ); + } +} + +bool LLToolCompScale::handleMouseUp(S32 x, S32 y, MASK mask) +{ + mMouseDown = false; + return LLToolComposite::handleMouseUp(x, y, mask); +} + +LLTool* LLToolCompScale::getOverrideTool(MASK mask) +{ + if (mask == MASK_CONTROL) + { + return LLToolCompRotate::getInstance(); + } + + return LLToolComposite::getOverrideTool(mask); +} + + +bool LLToolCompScale::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + if (!mManip->getSelection()->isEmpty() && mManip->getHighlightedPart() == LLManip::LL_NO_PART) + { + // You should already have an object selected from the mousedown. + // If so, show its properties + LLFloaterReg::showInstance("build", "Content"); + return true; + } + else + { + // Nothing selected means the first mouse click was probably + // bad, so try again. + return handleMouseDown(x, y, mask); + } +} + + +void LLToolCompScale::render() +{ + mCur->render(); + + if( mCur != mManip ) + { + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); + mManip->renderGuidelines(); + } +} + +//----------------------------------------------------------------------- +// LLToolCompCreate + +LLToolCompCreate::LLToolCompCreate() + : LLToolComposite(std::string("Create")) +{ + mPlacer = new LLToolPlacer(); + mSelectRect = new LLToolSelectRect(this); + + mCur = mPlacer; + mDefault = mPlacer; + mObjectPlacedOnMouseDown = false; +} + + +LLToolCompCreate::~LLToolCompCreate() +{ + delete mPlacer; + delete mSelectRect; +} + + +bool LLToolCompCreate::handleMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = false; + mMouseDown = true; + + if ( (mask == MASK_SHIFT) || (mask == MASK_CONTROL) ) + { + gViewerWindow->pickAsync(x, y, mask, pickCallback); + handled = true; + } + else + { + setCurrentTool( mPlacer ); + handled = mPlacer->placeObject( x, y, mask ); + } + + mObjectPlacedOnMouseDown = true; + + return handled; +} + +void LLToolCompCreate::pickCallback(const LLPickInfo& pick_info) +{ + // *NOTE: We mask off shift and control, so you cannot + // multi-select multiple objects with the create tool. + MASK mask = (pick_info.mKeyMask & ~MASK_SHIFT); + mask = (mask & ~MASK_CONTROL); + + LLToolCompCreate::getInstance()->setCurrentTool( LLToolCompCreate::getInstance()->mSelectRect ); + LLToolCompCreate::getInstance()->mSelectRect->handlePick( pick_info ); +} + +bool LLToolCompCreate::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + return handleMouseDown(x, y, mask); +} + +bool LLToolCompCreate::handleMouseUp(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + if ( mMouseDown && !mObjectPlacedOnMouseDown && !(mask == MASK_SHIFT) && !(mask == MASK_CONTROL) ) + { + setCurrentTool( mPlacer ); + handled = mPlacer->placeObject( x, y, mask ); + } + + mObjectPlacedOnMouseDown = false; + mMouseDown = false; + + if (!handled) + { + handled = LLToolComposite::handleMouseUp(x, y, mask); + } + + return handled; +} + +//----------------------------------------------------------------------- +// LLToolCompRotate + +LLToolCompRotate::LLToolCompRotate() + : LLToolComposite(std::string("Rotate")) +{ + mManip = new LLManipRotate(this); + mSelectRect = new LLToolSelectRect(this); + + mCur = mManip; + mDefault = mManip; +} + + +LLToolCompRotate::~LLToolCompRotate() +{ + delete mManip; + delete mSelectRect; +} + +bool LLToolCompRotate::handleHover(S32 x, S32 y, MASK mask) +{ + if( !mCur->hasMouseCapture() ) + { + setCurrentTool( mManip ); + } + return mCur->handleHover( x, y, mask ); +} + + +bool LLToolCompRotate::handleMouseDown(S32 x, S32 y, MASK mask) +{ + mMouseDown = true; + gViewerWindow->pickAsync(x, y, mask, pickCallback); + return true; +} + +void LLToolCompRotate::pickCallback(const LLPickInfo& pick_info) +{ + LLViewerObject* hit_obj = pick_info.getObject(); + + LLToolCompRotate::getInstance()->mManip->highlightManipulators(pick_info.mMousePt.mX, pick_info.mMousePt.mY); + if (!LLToolCompRotate::getInstance()->mMouseDown) + { + // fast click on object, but mouse is already up...just do select + LLToolCompRotate::getInstance()->mSelectRect->handleObjectSelection(pick_info, gSavedSettings.getBOOL("EditLinkedParts"), false); + return; + } + + if( hit_obj || LLToolCompRotate::getInstance()->mManip->getHighlightedPart() != LLManip::LL_NO_PART) + { + if (LLToolCompRotate::getInstance()->mManip->getSelection()->getObjectCount()) + { + LLEditMenuHandler::gEditMenuHandler = LLSelectMgr::getInstance(); + } + if( LLManip::LL_NO_PART != LLToolCompRotate::getInstance()->mManip->getHighlightedPart() ) + { + LLToolCompRotate::getInstance()->setCurrentTool( LLToolCompRotate::getInstance()->mManip ); + LLToolCompRotate::getInstance()->mManip->handleMouseDownOnPart( pick_info.mMousePt.mX, pick_info.mMousePt.mY, pick_info.mKeyMask ); + } + else + { + LLToolCompRotate::getInstance()->setCurrentTool( LLToolCompRotate::getInstance()->mSelectRect ); + LLToolCompRotate::getInstance()->mSelectRect->handlePick( pick_info ); + } + } + else + { + LLToolCompRotate::getInstance()->setCurrentTool( LLToolCompRotate::getInstance()->mSelectRect ); + LLToolCompRotate::getInstance()->mSelectRect->handlePick( pick_info ); + } +} + +bool LLToolCompRotate::handleMouseUp(S32 x, S32 y, MASK mask) +{ + mMouseDown = false; + return LLToolComposite::handleMouseUp(x, y, mask); +} + +LLTool* LLToolCompRotate::getOverrideTool(MASK mask) +{ + if (mask == (MASK_CONTROL | MASK_SHIFT)) + { + return LLToolCompScale::getInstance(); + } + return LLToolComposite::getOverrideTool(mask); +} + +bool LLToolCompRotate::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + if (!mManip->getSelection()->isEmpty() && mManip->getHighlightedPart() == LLManip::LL_NO_PART) + { + // You should already have an object selected from the mousedown. + // If so, show its properties + LLFloaterReg::showInstance("build", "Content"); + return true; + } + else + { + // Nothing selected means the first mouse click was probably + // bad, so try again. + return handleMouseDown(x, y, mask); + } +} + + +void LLToolCompRotate::render() +{ + mCur->render(); + + if( mCur != mManip ) + { + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); + mManip->renderGuidelines(); + } +} + + +//----------------------------------------------------------------------- +// LLToolCompGun + +LLToolCompGun::LLToolCompGun() + : LLToolComposite(std::string("Mouselook")) +{ + mGun = new LLToolGun(this); + mGrab = new LLToolGrabBase(this); + mNull = sNullTool; + + setCurrentTool(mGun); + mDefault = mGun; +} + + +LLToolCompGun::~LLToolCompGun() +{ + delete mGun; + mGun = NULL; + + delete mGrab; + mGrab = NULL; + + // don't delete a static object + // delete mNull; + mNull = NULL; +} + +bool LLToolCompGun::handleHover(S32 x, S32 y, MASK mask) +{ + // *NOTE: This hack is here to make mouselook kick in again after + // item selected from context menu. + if ( mCur == mNull && !gPopupMenuView->getVisible() ) + { + LLSelectMgr::getInstance()->deselectAll(); + setCurrentTool( (LLTool*) mGrab ); + } + + // Note: if the tool changed, we can't delegate the current mouse event + // after the change because tools can modify the mouse during selection and deselection. + // Instead we let the current tool handle the event and then make the change. + // The new tool will take effect on the next frame. + + mCur->handleHover( x, y, mask ); + + // If mouse button not down... + if( !gViewerWindow->getLeftMouseDown()) + { + // let ALT switch from gun to grab + if ( mCur == mGun && (mask & MASK_ALT) ) + { + setCurrentTool( (LLTool*) mGrab ); + } + else if ( mCur == mGrab && !(mask & MASK_ALT) ) + { + setCurrentTool( (LLTool*) mGun ); + setMouseCapture(true); + } + } + + return true; +} + + +bool LLToolCompGun::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // if the left button is grabbed, don't put up the pie menu + if (gAgent.leftButtonGrabbed() && gViewerInput.isLMouseHandlingDefault(MODE_FIRST_PERSON)) + { + gAgent.setControlFlags(AGENT_CONTROL_ML_LBUTTON_DOWN); + return false; + } + + // On mousedown, start grabbing + gGrabTransientTool = this; + LLToolMgr::getInstance()->getCurrentToolset()->selectTool( (LLTool*) mGrab ); + + return LLToolGrab::getInstance()->handleMouseDown(x, y, mask); +} + + +bool LLToolCompGun::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + // if the left button is grabbed, don't put up the pie menu + if (gAgent.leftButtonGrabbed() && gViewerInput.isLMouseHandlingDefault(MODE_FIRST_PERSON)) + { + gAgent.setControlFlags(AGENT_CONTROL_ML_LBUTTON_DOWN); + return false; + } + + // On mousedown, start grabbing + gGrabTransientTool = this; + LLToolMgr::getInstance()->getCurrentToolset()->selectTool( (LLTool*) mGrab ); + + return LLToolGrab::getInstance()->handleDoubleClick(x, y, mask); +} + + +bool LLToolCompGun::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + /* JC - suppress context menu 8/29/2002 + + // On right mouse, go through some convoluted steps to + // make the build menu appear. + setCurrentTool( (LLTool*) mNull ); + + // This should return false, meaning the context menu will + // be shown. + return false; + */ + + // Returning true will suppress the context menu + return true; +} + + +bool LLToolCompGun::handleMouseUp(S32 x, S32 y, MASK mask) +{ + if (gViewerInput.isLMouseHandlingDefault(MODE_FIRST_PERSON)) + { + gAgent.setControlFlags(AGENT_CONTROL_ML_LBUTTON_UP); + } + setCurrentTool( (LLTool*) mGun ); + return true; +} + +void LLToolCompGun::onMouseCaptureLost() +{ + if (mComposite) + { + mComposite->onMouseCaptureLost(); + return; + } + mCur->onMouseCaptureLost(); +} + +void LLToolCompGun::handleSelect() +{ + LLToolComposite::handleSelect(); + setMouseCapture(true); +} + +void LLToolCompGun::handleDeselect() +{ + LLToolComposite::handleDeselect(); + setMouseCapture(false); +} + + +bool LLToolCompGun::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + if (clicks > 0) + { + gAgentCamera.changeCameraToDefault(); + + } + return true; +} diff --git a/indra/newview/lltoolcomp.h b/indra/newview/lltoolcomp.h index 0ea8afde28..4b945967d1 100644 --- a/indra/newview/lltoolcomp.h +++ b/indra/newview/lltoolcomp.h @@ -1,245 +1,245 @@ -/** - * @file lltoolcomp.h - * @brief Composite tools - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_TOOLCOMP_H -#define LL_TOOLCOMP_H - -#include "lltool.h" - -class LLManip; -class LLToolSelectRect; -class LLToolPlacer; -class LLPickInfo; - -class LLView; -class LLTextBox; - -//----------------------------------------------------------------------- -// LLToolComposite - -class LLToolComposite : public LLTool -{ -public: - LLToolComposite(const std::string& name); - - virtual bool handleMouseDown(S32 x, S32 y, MASK mask) = 0; // Sets the current tool - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); // Returns to the default tool - virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) = 0; - - // Map virtual functions to the currently active internal tool - virtual bool handleHover(S32 x, S32 y, MASK mask) { return mCur->handleHover( x, y, mask ); } - virtual bool handleScrollWheel(S32 x, S32 y, S32 clicks) { return mCur->handleScrollWheel( x, y, clicks ); } - virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask) { return mCur->handleRightMouseDown( x, y, mask ); } - - virtual LLViewerObject* getEditingObject() { return mCur->getEditingObject(); } - virtual LLVector3d getEditingPointGlobal() { return mCur->getEditingPointGlobal(); } - virtual bool isEditing() { return mCur->isEditing(); } - virtual void stopEditing() { mCur->stopEditing(); mCur = mDefault; } - - virtual bool clipMouseWhenDown() { return mCur->clipMouseWhenDown(); } - - virtual void handleSelect(); - virtual void handleDeselect(); - - virtual void render() { mCur->render(); } - virtual void draw() { mCur->draw(); } - - virtual bool handleKey(KEY key, MASK mask) { return mCur->handleKey( key, mask ); } - - virtual void onMouseCaptureLost(); - - virtual void screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const - { mCur->screenPointToLocal(screen_x, screen_y, local_x, local_y); } - - virtual void localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const - { mCur->localPointToScreen(local_x, local_y, screen_x, screen_y); } - - bool isSelecting(); - LLTool* getCurrentTool() { return mCur; } - -protected: - void setCurrentTool( LLTool* new_tool ); - // In hover handler, call this to auto-switch tools - void setToolFromMask( MASK mask, LLTool *normal ); - -protected: - LLTool* mCur; // The tool to which we're delegating. - LLTool* mDefault; - bool mSelected; - bool mMouseDown; - LLManip* mManip; - LLToolSelectRect* mSelectRect; - -public: - static const std::string sNameComp; -}; - - -//----------------------------------------------------------------------- -// LLToolCompTranslate - -class LLToolCompInspect : public LLToolComposite, public LLSingleton -{ - LLSINGLETON(LLToolCompInspect); - virtual ~LLToolCompInspect(); -public: - - // Overridden from LLToolComposite - virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; - virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; - virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; - virtual bool handleKey(KEY key, MASK mask) override; - virtual void onMouseCaptureLost() override; - void keyUp(KEY key, MASK mask); - - static void pickCallback(const LLPickInfo& pick_info); - - bool isToolCameraActive() const { return mIsToolCameraActive; } - -private: - bool mIsToolCameraActive; -}; - -//----------------------------------------------------------------------- -// LLToolCompTranslate - -class LLToolCompTranslate : public LLToolComposite, public LLSingleton -{ - LLSINGLETON(LLToolCompTranslate); - virtual ~LLToolCompTranslate(); -public: - - // Overridden from LLToolComposite - virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; - virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; - virtual bool handleHover(S32 x, S32 y, MASK mask) override; - virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; // Returns to the default tool - virtual void render() override; - - virtual LLTool* getOverrideTool(MASK mask) override; - - static void pickCallback(const LLPickInfo& pick_info); -}; - -//----------------------------------------------------------------------- -// LLToolCompScale - -class LLToolCompScale : public LLToolComposite, public LLSingleton -{ - LLSINGLETON(LLToolCompScale); - virtual ~LLToolCompScale(); -public: - - // Overridden from LLToolComposite - virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; - virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; - virtual bool handleHover(S32 x, S32 y, MASK mask) override; - virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; // Returns to the default tool - virtual void render() override; - - virtual LLTool* getOverrideTool(MASK mask) override; - - static void pickCallback(const LLPickInfo& pick_info); -}; - - -//----------------------------------------------------------------------- -// LLToolCompRotate - -class LLToolCompRotate : public LLToolComposite, public LLSingleton -{ - LLSINGLETON(LLToolCompRotate); - virtual ~LLToolCompRotate(); -public: - - // Overridden from LLToolComposite - virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; - virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; - virtual bool handleHover(S32 x, S32 y, MASK mask) override; - virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; - virtual void render() override; - - virtual LLTool* getOverrideTool(MASK mask) override; - - static void pickCallback(const LLPickInfo& pick_info); - -protected: -}; - -//----------------------------------------------------------------------- -// LLToolCompCreate - -class LLToolCompCreate : public LLToolComposite, public LLSingleton -{ - LLSINGLETON(LLToolCompCreate); - virtual ~LLToolCompCreate(); -public: - - // Overridden from LLToolComposite - virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; - virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; - virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; - - static void pickCallback(const LLPickInfo& pick_info); -protected: - LLToolPlacer* mPlacer; - bool mObjectPlacedOnMouseDown; -}; - - -//----------------------------------------------------------------------- -// LLToolCompGun - -class LLToolGun; -class LLToolGrabBase; -class LLToolSelect; - -class LLToolCompGun : public LLToolComposite, public LLSingleton -{ - LLSINGLETON(LLToolCompGun); - virtual ~LLToolCompGun(); -public: - - // Overridden from LLToolComposite - virtual bool handleHover(S32 x, S32 y, MASK mask) override; - virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; - virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; - virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask) override; - virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; - virtual bool handleScrollWheel(S32 x, S32 y, S32 clicks) override; - virtual void onMouseCaptureLost() override; - virtual void handleSelect() override; - virtual void handleDeselect() override; - virtual LLTool* getOverrideTool(MASK mask) override { return NULL; } - -protected: - LLToolGun* mGun; - LLToolGrabBase* mGrab; - LLTool* mNull; -}; - - -#endif // LL_TOOLCOMP_H +/** + * @file lltoolcomp.h + * @brief Composite tools + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_TOOLCOMP_H +#define LL_TOOLCOMP_H + +#include "lltool.h" + +class LLManip; +class LLToolSelectRect; +class LLToolPlacer; +class LLPickInfo; + +class LLView; +class LLTextBox; + +//----------------------------------------------------------------------- +// LLToolComposite + +class LLToolComposite : public LLTool +{ +public: + LLToolComposite(const std::string& name); + + virtual bool handleMouseDown(S32 x, S32 y, MASK mask) = 0; // Sets the current tool + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); // Returns to the default tool + virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) = 0; + + // Map virtual functions to the currently active internal tool + virtual bool handleHover(S32 x, S32 y, MASK mask) { return mCur->handleHover( x, y, mask ); } + virtual bool handleScrollWheel(S32 x, S32 y, S32 clicks) { return mCur->handleScrollWheel( x, y, clicks ); } + virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask) { return mCur->handleRightMouseDown( x, y, mask ); } + + virtual LLViewerObject* getEditingObject() { return mCur->getEditingObject(); } + virtual LLVector3d getEditingPointGlobal() { return mCur->getEditingPointGlobal(); } + virtual bool isEditing() { return mCur->isEditing(); } + virtual void stopEditing() { mCur->stopEditing(); mCur = mDefault; } + + virtual bool clipMouseWhenDown() { return mCur->clipMouseWhenDown(); } + + virtual void handleSelect(); + virtual void handleDeselect(); + + virtual void render() { mCur->render(); } + virtual void draw() { mCur->draw(); } + + virtual bool handleKey(KEY key, MASK mask) { return mCur->handleKey( key, mask ); } + + virtual void onMouseCaptureLost(); + + virtual void screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const + { mCur->screenPointToLocal(screen_x, screen_y, local_x, local_y); } + + virtual void localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const + { mCur->localPointToScreen(local_x, local_y, screen_x, screen_y); } + + bool isSelecting(); + LLTool* getCurrentTool() { return mCur; } + +protected: + void setCurrentTool( LLTool* new_tool ); + // In hover handler, call this to auto-switch tools + void setToolFromMask( MASK mask, LLTool *normal ); + +protected: + LLTool* mCur; // The tool to which we're delegating. + LLTool* mDefault; + bool mSelected; + bool mMouseDown; + LLManip* mManip; + LLToolSelectRect* mSelectRect; + +public: + static const std::string sNameComp; +}; + + +//----------------------------------------------------------------------- +// LLToolCompTranslate + +class LLToolCompInspect : public LLToolComposite, public LLSingleton +{ + LLSINGLETON(LLToolCompInspect); + virtual ~LLToolCompInspect(); +public: + + // Overridden from LLToolComposite + virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; + virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; + virtual bool handleKey(KEY key, MASK mask) override; + virtual void onMouseCaptureLost() override; + void keyUp(KEY key, MASK mask); + + static void pickCallback(const LLPickInfo& pick_info); + + bool isToolCameraActive() const { return mIsToolCameraActive; } + +private: + bool mIsToolCameraActive; +}; + +//----------------------------------------------------------------------- +// LLToolCompTranslate + +class LLToolCompTranslate : public LLToolComposite, public LLSingleton +{ + LLSINGLETON(LLToolCompTranslate); + virtual ~LLToolCompTranslate(); +public: + + // Overridden from LLToolComposite + virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; + virtual bool handleHover(S32 x, S32 y, MASK mask) override; + virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; // Returns to the default tool + virtual void render() override; + + virtual LLTool* getOverrideTool(MASK mask) override; + + static void pickCallback(const LLPickInfo& pick_info); +}; + +//----------------------------------------------------------------------- +// LLToolCompScale + +class LLToolCompScale : public LLToolComposite, public LLSingleton +{ + LLSINGLETON(LLToolCompScale); + virtual ~LLToolCompScale(); +public: + + // Overridden from LLToolComposite + virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; + virtual bool handleHover(S32 x, S32 y, MASK mask) override; + virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; // Returns to the default tool + virtual void render() override; + + virtual LLTool* getOverrideTool(MASK mask) override; + + static void pickCallback(const LLPickInfo& pick_info); +}; + + +//----------------------------------------------------------------------- +// LLToolCompRotate + +class LLToolCompRotate : public LLToolComposite, public LLSingleton +{ + LLSINGLETON(LLToolCompRotate); + virtual ~LLToolCompRotate(); +public: + + // Overridden from LLToolComposite + virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; + virtual bool handleHover(S32 x, S32 y, MASK mask) override; + virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; + virtual void render() override; + + virtual LLTool* getOverrideTool(MASK mask) override; + + static void pickCallback(const LLPickInfo& pick_info); + +protected: +}; + +//----------------------------------------------------------------------- +// LLToolCompCreate + +class LLToolCompCreate : public LLToolComposite, public LLSingleton +{ + LLSINGLETON(LLToolCompCreate); + virtual ~LLToolCompCreate(); +public: + + // Overridden from LLToolComposite + virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; + virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; + + static void pickCallback(const LLPickInfo& pick_info); +protected: + LLToolPlacer* mPlacer; + bool mObjectPlacedOnMouseDown; +}; + + +//----------------------------------------------------------------------- +// LLToolCompGun + +class LLToolGun; +class LLToolGrabBase; +class LLToolSelect; + +class LLToolCompGun : public LLToolComposite, public LLSingleton +{ + LLSINGLETON(LLToolCompGun); + virtual ~LLToolCompGun(); +public: + + // Overridden from LLToolComposite + virtual bool handleHover(S32 x, S32 y, MASK mask) override; + virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; + virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask) override; + virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; + virtual bool handleScrollWheel(S32 x, S32 y, S32 clicks) override; + virtual void onMouseCaptureLost() override; + virtual void handleSelect() override; + virtual void handleDeselect() override; + virtual LLTool* getOverrideTool(MASK mask) override { return NULL; } + +protected: + LLToolGun* mGun; + LLToolGrabBase* mGrab; + LLTool* mNull; +}; + + +#endif // LL_TOOLCOMP_H diff --git a/indra/newview/lltooldraganddrop.cpp b/indra/newview/lltooldraganddrop.cpp index 02a757ab3c..c561f43ca0 100644 --- a/indra/newview/lltooldraganddrop.cpp +++ b/indra/newview/lltooldraganddrop.cpp @@ -1,3172 +1,3172 @@ -/** - * @file lltooldraganddrop.cpp - * @brief LLToolDragAndDrop class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "lltooldraganddrop.h" - -// library headers -#include "llnotificationsutil.h" -// project headers -#include "llagent.h" -#include "llagentcamera.h" -#include "llagentwearables.h" -#include "llappearancemgr.h" -#include "llavatarnamecache.h" -#include "lldictionary.h" -#include "llfloaterreg.h" -#include "llfloatertools.h" -#include "llgesturemgr.h" -#include "llgiveinventory.h" -#include "llgltfmateriallist.h" -#include "llhudmanager.h" -#include "llhudeffecttrail.h" -#include "llimview.h" -#include "llinventorybridge.h" -#include "llinventorydefines.h" -#include "llinventoryfunctions.h" -#include "llinventorymodelbackgroundfetch.h" -#include "llpreviewnotecard.h" -#include "llrootview.h" -#include "llselectmgr.h" -#include "lltoolbarview.h" -#include "lltoolmgr.h" -#include "lltooltip.h" -#include "lltrans.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llviewerstats.h" -#include "llviewerwindow.h" -#include "llvoavatarself.h" -#include "llworld.h" -#include "llpanelface.h" -#include "lluiusage.h" - -// syntactic sugar -#define callMemberFunction(object,ptrToMember) ((object).*(ptrToMember)) - -class LLNoPreferredType : public LLInventoryCollectFunctor -{ -public: - LLNoPreferredType() {} - virtual ~LLNoPreferredType() {} - virtual bool operator()(LLInventoryCategory* cat, - LLInventoryItem* item) - { - if (cat && (cat->getPreferredType() == LLFolderType::FT_NONE)) - { - return true; - } - return false; - } -}; - -class LLNoPreferredTypeOrItem : public LLInventoryCollectFunctor -{ -public: - LLNoPreferredTypeOrItem() {} - virtual ~LLNoPreferredTypeOrItem() {} - virtual bool operator()(LLInventoryCategory* cat, - LLInventoryItem* item) - { - if (item) return true; - if (cat && (cat->getPreferredType() == LLFolderType::FT_NONE)) - { - return true; - } - return false; - } -}; - -class LLDroppableItem : public LLInventoryCollectFunctor -{ -public: - LLDroppableItem(bool is_transfer) : - mCountLosing(0), mIsTransfer(is_transfer) {} - virtual ~LLDroppableItem() {} - virtual bool operator()(LLInventoryCategory* cat, - LLInventoryItem* item); - S32 countNoCopy() const { return mCountLosing; } - -protected: - S32 mCountLosing; - bool mIsTransfer; -}; - -bool LLDroppableItem::operator()(LLInventoryCategory* cat, - LLInventoryItem* item) -{ - bool allowed = false; - if (item) - { - allowed = itemTransferCommonlyAllowed(item); - - if (allowed - && mIsTransfer - && !item->getPermissions().allowOperationBy(PERM_TRANSFER, - gAgent.getID())) - { - allowed = false; - } - if (allowed && !item->getPermissions().allowCopyBy(gAgent.getID())) - { - ++mCountLosing; - } - } - return allowed; -} - -class LLDropCopyableItems : public LLInventoryCollectFunctor -{ -public: - LLDropCopyableItems() {} - virtual ~LLDropCopyableItems() {} - virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); -}; - - -bool LLDropCopyableItems::operator()( - LLInventoryCategory* cat, - LLInventoryItem* item) -{ - bool allowed = false; - if (item) - { - allowed = itemTransferCommonlyAllowed(item); - if (allowed && - !item->getPermissions().allowCopyBy(gAgent.getID())) - { - // whoops, can't copy it - don't allow it. - allowed = false; - } - } - return allowed; -} - -// Starts a fetch on folders and items. This is really not used -// as an observer in the traditional sense; we're just using it to -// request a fetch and we don't care about when/if the response arrives. -class LLCategoryFireAndForget : public LLInventoryFetchComboObserver -{ -public: - LLCategoryFireAndForget(const uuid_vec_t& folder_ids, - const uuid_vec_t& item_ids) : - LLInventoryFetchComboObserver(folder_ids, item_ids) - {} - ~LLCategoryFireAndForget() {} - virtual void done() - { - /* no-op: it's fire n forget right? */ - LL_DEBUGS() << "LLCategoryFireAndForget::done()" << LL_ENDL; - } -}; - -class LLCategoryDropObserver : public LLInventoryFetchItemsObserver -{ -public: - LLCategoryDropObserver( - const uuid_vec_t& ids, - const LLUUID& obj_id, LLToolDragAndDrop::ESource src) : - LLInventoryFetchItemsObserver(ids), - mObjectID(obj_id), - mSource(src) - {} - ~LLCategoryDropObserver() {} - virtual void done(); - -protected: - LLUUID mObjectID; - LLToolDragAndDrop::ESource mSource; -}; - -void LLCategoryDropObserver::done() -{ - gInventory.removeObserver(this); - LLViewerObject* dst_obj = gObjectList.findObject(mObjectID); - if (dst_obj) - { - // *FIX: coalesce these... - LLInventoryItem* item = NULL; - uuid_vec_t::iterator it = mComplete.begin(); - uuid_vec_t::iterator end = mComplete.end(); - for(; it < end; ++it) - { - item = gInventory.getItem(*it); - if (item) - { - LLToolDragAndDrop::dropInventory( - dst_obj, - item, - mSource, - LLUUID::null); - } - } - } - delete this; -} - -S32 LLToolDragAndDrop::sOperationId = 0; - -LLToolDragAndDrop::DragAndDropEntry::DragAndDropEntry(dragOrDrop3dImpl f_none, - dragOrDrop3dImpl f_self, - dragOrDrop3dImpl f_avatar, - dragOrDrop3dImpl f_object, - dragOrDrop3dImpl f_land) : - LLDictionaryEntry("") -{ - mFunctions[DT_NONE] = f_none; - mFunctions[DT_SELF] = f_self; - mFunctions[DT_AVATAR] = f_avatar; - mFunctions[DT_OBJECT] = f_object; - mFunctions[DT_LAND] = f_land; -} - -LLToolDragAndDrop::dragOrDrop3dImpl LLToolDragAndDrop::LLDragAndDropDictionary::get(EDragAndDropType dad_type, LLToolDragAndDrop::EDropTarget drop_target) -{ - const DragAndDropEntry *entry = lookup(dad_type); - if (entry) - { - return (entry->mFunctions[(U8)drop_target]); - } - return &LLToolDragAndDrop::dad3dNULL; -} - -LLToolDragAndDrop::LLDragAndDropDictionary::LLDragAndDropDictionary() -{ - // DT_NONE DT_SELF DT_AVATAR DT_OBJECT DT_LAND - // |-------------------------------|----------------------------------------------|-----------------------------------------------|---------------------------------------------------|--------------------------------| - addEntry(DAD_NONE, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL)); - addEntry(DAD_TEXTURE, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dTextureObject, &LLToolDragAndDrop::dad3dNULL)); - addEntry(DAD_MATERIAL, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dMaterialObject, &LLToolDragAndDrop::dad3dNULL)); - addEntry(DAD_SOUND, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dUpdateInventory, &LLToolDragAndDrop::dad3dNULL)); - addEntry(DAD_CALLINGCARD, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dUpdateInventory, &LLToolDragAndDrop::dad3dNULL)); - addEntry(DAD_LANDMARK, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dUpdateInventory, &LLToolDragAndDrop::dad3dNULL)); - addEntry(DAD_SCRIPT, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dRezScript, &LLToolDragAndDrop::dad3dNULL)); - addEntry(DAD_CLOTHING, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dWearItem, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dUpdateInventory, &LLToolDragAndDrop::dad3dNULL)); - addEntry(DAD_OBJECT, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dRezAttachmentFromInv, &LLToolDragAndDrop::dad3dGiveInventoryObject, &LLToolDragAndDrop::dad3dRezObjectOnObject, &LLToolDragAndDrop::dad3dRezObjectOnLand)); - addEntry(DAD_NOTECARD, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dUpdateInventory, &LLToolDragAndDrop::dad3dNULL)); - addEntry(DAD_CATEGORY, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dWearCategory, &LLToolDragAndDrop::dad3dGiveInventoryCategory, &LLToolDragAndDrop::dad3dRezCategoryOnObject, &LLToolDragAndDrop::dad3dNULL)); - addEntry(DAD_ROOT_CATEGORY, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL)); - addEntry(DAD_BODYPART, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dWearItem, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dUpdateInventory, &LLToolDragAndDrop::dad3dNULL)); - addEntry(DAD_ANIMATION, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dUpdateInventory, &LLToolDragAndDrop::dad3dNULL)); - addEntry(DAD_GESTURE, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dActivateGesture, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dUpdateInventory, &LLToolDragAndDrop::dad3dNULL)); - addEntry(DAD_LINK, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL)); - addEntry(DAD_MESH, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dMeshObject, &LLToolDragAndDrop::dad3dNULL)); - addEntry(DAD_SETTINGS, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dUpdateInventory, &LLToolDragAndDrop::dad3dNULL)); - - // TODO: animation on self could play it? edit it? - // TODO: gesture on self could play it? edit it? -}; - -LLToolDragAndDrop::LLToolDragAndDrop() -: LLTool(std::string("draganddrop"), NULL), - mCargoCount(0), - mDragStartX(0), - mDragStartY(0), - mSource(SOURCE_AGENT), - mCursor(UI_CURSOR_NO), - mLastAccept(ACCEPT_NO), - mDrop(false), - mCurItemIndex(0) -{ - -} - -void LLToolDragAndDrop::setDragStart(S32 x, S32 y) -{ - mDragStartX = x; - mDragStartY = y; -} - -bool LLToolDragAndDrop::isOverThreshold(S32 x,S32 y) -{ - S32 mouse_delta_x = x - mDragStartX; - S32 mouse_delta_y = y - mDragStartY; - - return (mouse_delta_x * mouse_delta_x) + (mouse_delta_y * mouse_delta_y) > DRAG_N_DROP_DISTANCE_THRESHOLD * DRAG_N_DROP_DISTANCE_THRESHOLD; -} - -void LLToolDragAndDrop::beginDrag(EDragAndDropType type, - const LLUUID& cargo_id, - ESource source, - const LLUUID& source_id, - const LLUUID& object_id) -{ - if (type == DAD_NONE) - { - LL_WARNS() << "Attempted to start drag without a cargo type" << LL_ENDL; - return; - } - - if (type != DAD_CATEGORY) - { - LLViewerInventoryItem* item = gInventory.getItem(cargo_id); - if (item && !item->isFinished()) - { - LLInventoryModelBackgroundFetch::instance().start(item->getUUID(), false); - } - } - - mCargoTypes.clear(); - mCargoTypes.push_back(type); - mCargoIDs.clear(); - mCargoIDs.push_back(cargo_id); - mSource = source; - mSourceID = source_id; - mObjectID = object_id; - - setMouseCapture( true ); - LLToolMgr::getInstance()->setTransientTool( this ); - mCursor = UI_CURSOR_NO; - if ((mCargoTypes[0] == DAD_CATEGORY) - && ((mSource == SOURCE_AGENT) || (mSource == SOURCE_LIBRARY))) - { - LLInventoryCategory* cat = gInventory.getCategory(cargo_id); - // go ahead and fire & forget the descendents if we are not - // dragging a protected folder. - if (cat) - { - LLViewerInventoryCategory::cat_array_t cats; - LLViewerInventoryItem::item_array_t items; - LLNoPreferredTypeOrItem is_not_preferred; - uuid_vec_t folder_ids; - uuid_vec_t item_ids; - if (is_not_preferred(cat, NULL)) - { - folder_ids.push_back(cargo_id); - } - gInventory.collectDescendentsIf( - cargo_id, - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - is_not_preferred); - S32 count = cats.size(); - S32 i; - for(i = 0; i < count; ++i) - { - folder_ids.push_back(cats.at(i)->getUUID()); - } - count = items.size(); - for(i = 0; i < count; ++i) - { - item_ids.push_back(items.at(i)->getUUID()); - } - if (!folder_ids.empty() || !item_ids.empty()) - { - LLCategoryFireAndForget *fetcher = new LLCategoryFireAndForget(folder_ids, item_ids); - fetcher->startFetch(); - delete fetcher; - } - } - } -} - -void LLToolDragAndDrop::beginMultiDrag( - const std::vector types, - const uuid_vec_t& cargo_ids, - ESource source, - const LLUUID& source_id) -{ - // assert on public api is evil - //llassert( type != DAD_NONE ); - - std::vector::const_iterator types_it; - for (types_it = types.begin(); types_it != types.end(); ++types_it) - { - if (DAD_NONE == *types_it) - { - LL_WARNS() << "Attempted to start drag without a cargo type" << LL_ENDL; - return; - } - } - mCargoTypes = types; - mCargoIDs = cargo_ids; - mSource = source; - mSourceID = source_id; - - setMouseCapture( true ); - LLToolMgr::getInstance()->setTransientTool( this ); - mCursor = UI_CURSOR_NO; - if ((mSource == SOURCE_AGENT) || (mSource == SOURCE_LIBRARY)) - { - // find categories (i.e. inventory folders) in the cargo. - LLInventoryCategory* cat = NULL; - S32 count = llmin(cargo_ids.size(), types.size()); - std::set cat_ids; - for(S32 i = 0; i < count; ++i) - { - cat = gInventory.getCategory(cargo_ids[i]); - if (cat) - { - LLViewerInventoryCategory::cat_array_t cats; - LLViewerInventoryItem::item_array_t items; - LLNoPreferredType is_not_preferred; - if (is_not_preferred(cat, NULL)) - { - cat_ids.insert(cat->getUUID()); - } - gInventory.collectDescendentsIf( - cat->getUUID(), - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - is_not_preferred); - S32 cat_count = cats.size(); - for(S32 i = 0; i < cat_count; ++i) - { - cat_ids.insert(cat->getUUID()); - } - } - } - if (!cat_ids.empty()) - { - uuid_vec_t folder_ids; - uuid_vec_t item_ids; - std::back_insert_iterator copier(folder_ids); - std::copy(cat_ids.begin(), cat_ids.end(), copier); - LLCategoryFireAndForget fetcher(folder_ids, item_ids); - } - } -} - -void LLToolDragAndDrop::endDrag() -{ - mEndDragSignal(); - LLSelectMgr::getInstance()->unhighlightAll(); - setMouseCapture(false); -} - -void LLToolDragAndDrop::onMouseCaptureLost() -{ - // Called whenever the drag ends or if mouse capture is simply lost - LLToolMgr::getInstance()->clearTransientTool(); - mCargoTypes.clear(); - mCargoIDs.clear(); - mSource = SOURCE_AGENT; - mSourceID.setNull(); - mObjectID.setNull(); - mCustomMsg.clear(); -} - -bool LLToolDragAndDrop::handleMouseUp( S32 x, S32 y, MASK mask ) -{ - if (hasMouseCapture()) - { - EAcceptance acceptance = ACCEPT_NO; - dragOrDrop( x, y, mask, true, &acceptance ); - endDrag(); - } - return true; -} - -ECursorType LLToolDragAndDrop::acceptanceToCursor( EAcceptance acceptance ) -{ - switch (acceptance) - { - case ACCEPT_YES_MULTI: - if (mCargoIDs.size() > 1) - { - mCursor = UI_CURSOR_ARROWDRAGMULTI; - } - else - { - mCursor = UI_CURSOR_ARROWDRAG; - } - break; - case ACCEPT_YES_SINGLE: - if (mCargoIDs.size() > 1) - { - mToolTipMsg = LLTrans::getString("TooltipMustSingleDrop"); - mCursor = UI_CURSOR_NO; - } - else - { - mCursor = UI_CURSOR_ARROWDRAG; - } - break; - - case ACCEPT_NO_LOCKED: - mCursor = UI_CURSOR_NOLOCKED; - break; - - case ACCEPT_NO_CUSTOM: - mToolTipMsg = mCustomMsg; - mCursor = UI_CURSOR_NO; - break; - - - case ACCEPT_NO: - mCursor = UI_CURSOR_NO; - break; - - case ACCEPT_YES_COPY_MULTI: - if (mCargoIDs.size() > 1) - { - mCursor = UI_CURSOR_ARROWCOPYMULTI; - } - else - { - mCursor = UI_CURSOR_ARROWCOPY; - } - break; - case ACCEPT_YES_COPY_SINGLE: - if (mCargoIDs.size() > 1) - { - mToolTipMsg = LLTrans::getString("TooltipMustSingleDrop"); - mCursor = UI_CURSOR_NO; - } - else - { - mCursor = UI_CURSOR_ARROWCOPY; - } - break; - case ACCEPT_POSTPONED: - break; - default: - llassert( false ); - } - - return mCursor; -} - -bool LLToolDragAndDrop::handleHover( S32 x, S32 y, MASK mask ) -{ - EAcceptance acceptance = ACCEPT_NO; - dragOrDrop( x, y, mask, false, &acceptance ); - - ECursorType cursor = acceptanceToCursor(acceptance); - gViewerWindow->getWindow()->setCursor( cursor ); - - LL_DEBUGS("UserInput") << "hover handled by LLToolDragAndDrop" << LL_ENDL; - return true; -} - -bool LLToolDragAndDrop::handleKey(KEY key, MASK mask) -{ - if (key == KEY_ESCAPE) - { - // cancel drag and drop operation - endDrag(); - return true; - } - - return false; -} - -bool LLToolDragAndDrop::handleToolTip(S32 x, S32 y, MASK mask) -{ - if (!mToolTipMsg.empty()) - { - LLToolTipMgr::instance().unblockToolTips(); - LLToolTipMgr::instance().show(LLToolTip::Params() - .message(mToolTipMsg) - .delay_time(gSavedSettings.getF32( "DragAndDropToolTipDelay" ))); - return true; - } - return false; -} - -void LLToolDragAndDrop::handleDeselect() -{ - mToolTipMsg.clear(); - mCustomMsg.clear(); - - LLToolTipMgr::instance().blockToolTips(); -} - -// protected -void LLToolDragAndDrop::dragOrDrop( S32 x, S32 y, MASK mask, bool drop, - EAcceptance* acceptance) -{ - *acceptance = ACCEPT_YES_MULTI; - - bool handled = false; - - LLView* top_view = gFocusMgr.getTopCtrl(); - LLViewerInventoryItem* item; - LLViewerInventoryCategory* cat; - - mToolTipMsg.clear(); - - // Increment the operation id for every drop - if (drop) - { - sOperationId++; - } - - // For people drag and drop we don't need an actual inventory object, - // instead we need the current cargo id, which should be a person id. - bool is_uuid_dragged = (mSource == SOURCE_PEOPLE); - - if (top_view) - { - handled = true; - - for (mCurItemIndex = 0; mCurItemIndex < (S32)mCargoIDs.size(); mCurItemIndex++) - { - S32 local_x, local_y; - top_view->screenPointToLocal( x, y, &local_x, &local_y ); - EAcceptance item_acceptance = ACCEPT_NO; - - LLInventoryObject* cargo = locateInventory(item, cat); - if (cargo) - { - handled = handled && top_view->handleDragAndDrop(local_x, local_y, mask, false, - mCargoTypes[mCurItemIndex], - (void*)cargo, - &item_acceptance, - mToolTipMsg); - } - else if (is_uuid_dragged) - { - handled = handled && top_view->handleDragAndDrop(local_x, local_y, mask, false, - mCargoTypes[mCurItemIndex], - (void*)&mCargoIDs[mCurItemIndex], - &item_acceptance, - mToolTipMsg); - } - if (handled) - { - // use sort order to determine priority of acceptance - *acceptance = (EAcceptance)llmin((U32)item_acceptance, (U32)*acceptance); - } - } - - // all objects passed, go ahead and perform drop if necessary - if (handled && drop && (U32)*acceptance >= ACCEPT_YES_COPY_SINGLE) - { - if ((U32)*acceptance < ACCEPT_YES_COPY_MULTI && - mCargoIDs.size() > 1) - { - // tried to give multi-cargo to a single-acceptor - refuse and return. - *acceptance = ACCEPT_NO; - return; - } - - for (mCurItemIndex = 0; mCurItemIndex < (S32)mCargoIDs.size(); mCurItemIndex++) - { - S32 local_x, local_y; - EAcceptance item_acceptance; - top_view->screenPointToLocal( x, y, &local_x, &local_y ); - - LLInventoryObject* cargo = locateInventory(item, cat); - if (cargo) - { - handled = handled && top_view->handleDragAndDrop(local_x, local_y, mask, true, - mCargoTypes[mCurItemIndex], - (void*)cargo, - &item_acceptance, - mToolTipMsg); - } - else if (is_uuid_dragged) - { - handled = handled && top_view->handleDragAndDrop(local_x, local_y, mask, false, - mCargoTypes[mCurItemIndex], - (void*)&mCargoIDs[mCurItemIndex], - &item_acceptance, - mToolTipMsg); - } - } - } - if (handled) - { - mLastAccept = (EAcceptance)*acceptance; - } - } - - if (!handled) - { - handled = true; - - LLRootView* root_view = gViewerWindow->getRootView(); - - for (mCurItemIndex = 0; mCurItemIndex < (S32)mCargoIDs.size(); mCurItemIndex++) - { - EAcceptance item_acceptance = ACCEPT_NO; - - LLInventoryObject* cargo = locateInventory(item, cat); - - // fix for EXT-3191 - if (cargo) - { - handled = handled && root_view->handleDragAndDrop(x, y, mask, false, - mCargoTypes[mCurItemIndex], - (void*)cargo, - &item_acceptance, - mToolTipMsg); - } - else if (is_uuid_dragged) - { - handled = handled && root_view->handleDragAndDrop(x, y, mask, false, - mCargoTypes[mCurItemIndex], - (void*)&mCargoIDs[mCurItemIndex], - &item_acceptance, - mToolTipMsg); - } - if (handled) - { - // use sort order to determine priority of acceptance - *acceptance = (EAcceptance)llmin((U32)item_acceptance, (U32)*acceptance); - } - } - // all objects passed, go ahead and perform drop if necessary - if (handled && drop && (U32)*acceptance > ACCEPT_NO_LOCKED) - { - if ((U32)*acceptance < ACCEPT_YES_COPY_MULTI && - mCargoIDs.size() > 1) - { - // tried to give multi-cargo to a single-acceptor - refuse and return. - *acceptance = ACCEPT_NO; - return; - } - - for (mCurItemIndex = 0; mCurItemIndex < (S32)mCargoIDs.size(); mCurItemIndex++) - { - EAcceptance item_acceptance; - - LLInventoryObject* cargo = locateInventory(item, cat); - if (cargo) - { - handled = handled && root_view->handleDragAndDrop(x, y, mask, true, - mCargoTypes[mCurItemIndex], - (void*)cargo, - &item_acceptance, - mToolTipMsg); - } - else if (is_uuid_dragged) - { - handled = handled && root_view->handleDragAndDrop(x, y, mask, true, - mCargoTypes[mCurItemIndex], - (void*)&mCargoIDs[mCurItemIndex], - &item_acceptance, - mToolTipMsg); - } - } - } - - if (handled) - { - mLastAccept = (EAcceptance)*acceptance; - } - } - - if (!handled) - { - // Disallow drag and drop to 3D from the marketplace - const LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - if (marketplacelistings_id.notNull()) - { - for (S32 item_index = 0; item_index < (S32)mCargoIDs.size(); item_index++) - { - if (gInventory.isObjectDescendentOf(mCargoIDs[item_index], marketplacelistings_id)) - { - *acceptance = ACCEPT_NO; - mToolTipMsg = LLTrans::getString("TooltipOutboxDragToWorld"); - return; - } - } - } - - dragOrDrop3D( x, y, mask, drop, acceptance ); - } -} - -void LLToolDragAndDrop::dragOrDrop3D( S32 x, S32 y, MASK mask, bool drop, EAcceptance* acceptance ) -{ - mDrop = drop; - if (mDrop) - { - // don't allow drag and drop onto rigged or transparent objects - pick(gViewerWindow->pickImmediate(x, y, false, false)); - } - else - { - // don't allow drag and drop onto transparent objects - gViewerWindow->pickAsync(x, y, mask, pickCallback, false, false); - } - - *acceptance = mLastAccept; -} - -void LLToolDragAndDrop::pickCallback(const LLPickInfo& pick_info) -{ - if (getInstance() != NULL) - { - getInstance()->pick(pick_info); - } -} - -void LLToolDragAndDrop::pick(const LLPickInfo& pick_info) -{ - EDropTarget target = DT_NONE; - S32 hit_face = -1; - - LLViewerObject* hit_obj = pick_info.getObject(); - LLSelectMgr::getInstance()->unhighlightAll(); - bool highlight_object = false; - // Treat attachments as part of the avatar they are attached to. - if (hit_obj != NULL) - { - // don't allow drag and drop on grass, trees, etc. - if (pick_info.mPickType == LLPickInfo::PICK_FLORA) - { - mCursor = UI_CURSOR_NO; - gViewerWindow->getWindow()->setCursor( mCursor ); - return; - } - - if (hit_obj->isAttachment() && !hit_obj->isHUDAttachment()) - { - LLVOAvatar* avatar = LLVOAvatar::findAvatarFromAttachment( hit_obj ); - if (!avatar) - { - mLastAccept = ACCEPT_NO; - mCursor = UI_CURSOR_NO; - gViewerWindow->getWindow()->setCursor( mCursor ); - return; - } - hit_obj = avatar; - } - - if (hit_obj->isAvatar()) - { - if (((LLVOAvatar*) hit_obj)->isSelf()) - { - target = DT_SELF; - hit_face = -1; - } - else - { - target = DT_AVATAR; - hit_face = -1; - } - } - else - { - target = DT_OBJECT; - hit_face = pick_info.mObjectFace; - highlight_object = true; - } - } - else if (pick_info.mPickType == LLPickInfo::PICK_LAND) - { - target = DT_LAND; - hit_face = -1; - } - - mLastAccept = ACCEPT_YES_MULTI; - - for (mCurItemIndex = 0; mCurItemIndex < (S32)mCargoIDs.size(); mCurItemIndex++) - { - const S32 item_index = mCurItemIndex; - const EDragAndDropType dad_type = mCargoTypes[item_index]; - // Call the right implementation function - mLastAccept = (EAcceptance)llmin( - (U32)mLastAccept, - (U32)callMemberFunction(*this, - LLDragAndDropDictionary::instance().get(dad_type, target)) - (hit_obj, hit_face, pick_info.mKeyMask, false)); - } - - if (mDrop && ((U32)mLastAccept >= ACCEPT_YES_COPY_SINGLE)) - { - // if target allows multi-drop or there is only one item being dropped, go ahead - if ((mLastAccept >= ACCEPT_YES_COPY_MULTI) || (mCargoIDs.size() == 1)) - { - // Target accepts multi, or cargo is a single-drop - for (mCurItemIndex = 0; mCurItemIndex < (S32)mCargoIDs.size(); mCurItemIndex++) - { - const S32 item_index = mCurItemIndex; - const EDragAndDropType dad_type = mCargoTypes[item_index]; - // Call the right implementation function - callMemberFunction(*this, LLDragAndDropDictionary::instance().get(dad_type, target)) - (hit_obj, hit_face, pick_info.mKeyMask, true); - } - } - else - { - // Target does not accept multi, but cargo is multi - mLastAccept = ACCEPT_NO; - } - } - - if (highlight_object && mLastAccept > ACCEPT_NO_LOCKED) - { - // if any item being dragged will be applied to the object under our cursor - // highlight that object - for (S32 i = 0; i < (S32)mCargoIDs.size(); i++) - { - if (mCargoTypes[i] != DAD_OBJECT || (pick_info.mKeyMask & MASK_CONTROL)) - { - LLSelectMgr::getInstance()->highlightObjectAndFamily(hit_obj); - break; - } - } - } - ECursorType cursor = acceptanceToCursor( mLastAccept ); - gViewerWindow->getWindow()->setCursor( cursor ); - - mLastHitPos = pick_info.mPosGlobal; - mLastCameraPos = gAgentCamera.getCameraPositionGlobal(); -} - -// static -bool LLToolDragAndDrop::handleDropMaterialProtections(LLViewerObject* hit_obj, - LLInventoryItem* item, - LLToolDragAndDrop::ESource source, - const LLUUID& src_id) -{ - if (!item) return false; - - // Always succeed if.... - // material is from the library - // or already in the contents of the object - if (SOURCE_LIBRARY == source) - { - // dropping a material from the library always just works. - return true; - } - - // In case the inventory has not been loaded (e.g. due to some recent operation - // causing a dirty inventory) and we can do an update, stall the user - // while fetching the inventory. - // - // Fetch if inventory is dirty and listener is present (otherwise we will not receive update) - if (hit_obj->isInventoryDirty() && hit_obj->hasInventoryListeners()) - { - hit_obj->requestInventory(); - LLSD args; - if (LLAssetType::AT_MATERIAL == item->getType()) - { - args["ERROR_MESSAGE"] = "Unable to add material.\nPlease wait a few seconds and try again."; - } - else - { - args["ERROR_MESSAGE"] = "Unable to add texture.\nPlease wait a few seconds and try again."; - } - LLNotificationsUtil::add("ErrorMessage", args); - return false; - } - // Make sure to verify both id and type since 'null' - // is a shared default for some asset types. - if (hit_obj->getInventoryItemByAsset(item->getAssetUUID(), item->getType())) - { - // if the asset is already in the object's inventory - // then it can always be added to a side. - // This saves some work if the task's inventory is already loaded - // and ensures that the asset item is only added once. - return true; - } - - LLPointer new_item = new LLViewerInventoryItem(item); - if (!item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID())) - { - // Check that we can add the material as inventory to the object - if (willObjectAcceptInventory(hit_obj,item) < ACCEPT_YES_COPY_SINGLE ) - { - return false; - } - // make sure the object has the material in it's inventory. - if (SOURCE_AGENT == source) - { - // Remove the material from local inventory. The server - // will actually remove the item from agent inventory. - gInventory.deleteObject(item->getUUID()); - gInventory.notifyObservers(); - } - else if (SOURCE_WORLD == source) - { - // *FIX: if the objects are in different regions, and the - // source region has crashed, you can bypass these - // permissions. - LLViewerObject* src_obj = gObjectList.findObject(src_id); - if (src_obj) - { - src_obj->removeInventory(item->getUUID()); - } - else - { - LL_WARNS() << "Unable to find source object." << LL_ENDL; - return false; - } - } - // Add the asset item to the target object's inventory. - if (LLAssetType::AT_TEXTURE == new_item->getType() - || LLAssetType::AT_MATERIAL == new_item->getType()) - { - hit_obj->updateMaterialInventory(new_item, TASK_INVENTORY_ITEM_KEY, true); - } - else - { - hit_obj->updateInventory(new_item, TASK_INVENTORY_ITEM_KEY, true); - } - // Force the object to update and refetch its inventory so it has this asset. - hit_obj->dirtyInventory(); - hit_obj->requestInventory(); - // TODO: Check to see if adding the item was successful; if not, then - // we should return false here. This will requre a separate listener - // since without listener, we have no way to receive update - } - else if (!item->getPermissions().allowOperationBy(PERM_TRANSFER, - gAgent.getID())) - { - // Check that we can add the asset as inventory to the object - if (willObjectAcceptInventory(hit_obj,item) < ACCEPT_YES_COPY_SINGLE ) - { - return false; - } - // *FIX: may want to make sure agent can paint hit_obj. - - // Add the asset item to the target object's inventory. - if (LLAssetType::AT_TEXTURE == new_item->getType() - || LLAssetType::AT_MATERIAL == new_item->getType()) - { - hit_obj->updateMaterialInventory(new_item, TASK_INVENTORY_ITEM_KEY, true); - } - else - { - hit_obj->updateInventory(new_item, TASK_INVENTORY_ITEM_KEY, true); - } - // Force the object to update and refetch its inventory so it has this asset. - hit_obj->dirtyInventory(); - hit_obj->requestInventory(); - // TODO: Check to see if adding the item was successful; if not, then - // we should return false here. This will requre a separate listener - // since without listener, we have no way to receive update - } - else if (LLAssetType::AT_MATERIAL == new_item->getType() && - !item->getPermissions().allowOperationBy(PERM_MODIFY, gAgent.getID())) - { - // Check that we can add the material as inventory to the object - if (willObjectAcceptInventory(hit_obj,item) < ACCEPT_YES_COPY_SINGLE ) - { - return false; - } - // *FIX: may want to make sure agent can paint hit_obj. - - // Add the material item to the target object's inventory. - hit_obj->updateMaterialInventory(new_item, TASK_INVENTORY_ITEM_KEY, true); - - // Force the object to update and refetch its inventory so it has this material. - hit_obj->dirtyInventory(); - hit_obj->requestInventory(); - // TODO: Check to see if adding the item was successful; if not, then - // we should return false here. This will requre a separate listener - // since without listener, we have no way to receive update - } - return true; -} - -void set_texture_to_material(LLViewerObject* hit_obj, - S32 hit_face, - const LLUUID& asset_id, - LLGLTFMaterial::TextureInfo drop_channel) -{ - LLTextureEntry* te = hit_obj->getTE(hit_face); - if (te) - { - LLPointer material = te->getGLTFMaterialOverride(); - - // make a copy to not invalidate existing - // material for multiple objects - if (material.isNull()) - { - // Start with a material override which does not make any changes - material = new LLGLTFMaterial(); - } - else - { - material = new LLGLTFMaterial(*material); - } - - switch (drop_channel) - { - case LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR: - default: - { - material->setBaseColorId(asset_id); - } - break; - - case LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS: - { - material->setOcclusionRoughnessMetallicId(asset_id); - } - break; - - case LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE: - { - material->setEmissiveId(asset_id); - } - break; - - case LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL: - { - material->setNormalId(asset_id); - } - break; - } - LLGLTFMaterialList::queueModify(hit_obj, hit_face, material); - } -} - -void LLToolDragAndDrop::dropTextureAllFaces(LLViewerObject* hit_obj, - LLInventoryItem* item, - LLToolDragAndDrop::ESource source, - const LLUUID& src_id, - bool remove_pbr) -{ - if (!item) - { - LL_WARNS() << "LLToolDragAndDrop::dropTextureAllFaces no texture item." << LL_ENDL; - return; - } - S32 num_faces = hit_obj->getNumTEs(); - bool has_non_pbr_faces = false; - for (S32 face = 0; face < num_faces; face++) - { - if (hit_obj->getRenderMaterialID(face).isNull()) - { - has_non_pbr_faces = true; - break; - } - } - - if (has_non_pbr_faces || remove_pbr) - { - bool res = handleDropMaterialProtections(hit_obj, item, source, src_id); - if (!res) - { - return; - } - } - LLUUID asset_id = item->getAssetUUID(); - - // Overrides require textures to be copy and transfer free - LLPermissions item_permissions = item->getPermissions(); - bool allow_adding_to_override = item_permissions.allowOperationBy(PERM_COPY, gAgent.getID()); - allow_adding_to_override &= item_permissions.allowOperationBy(PERM_TRANSFER, gAgent.getID()); - - LLViewerTexture* image = LLViewerTextureManager::getFetchedTexture(asset_id); - add(LLStatViewer::EDIT_TEXTURE, 1); - for( S32 face = 0; face < num_faces; face++ ) - { - if (remove_pbr) - { - hit_obj->setRenderMaterialID(face, LLUUID::null); - hit_obj->setTEImage(face, image); - dialog_refresh_all(); - } - else if (hit_obj->getRenderMaterialID(face).isNull()) - { - // update viewer side - hit_obj->setTEImage(face, image); - dialog_refresh_all(); - } - else if (allow_adding_to_override) - { - set_texture_to_material(hit_obj, face, asset_id, LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR); - } - } - - // send the update to the simulator - LLGLTFMaterialList::flushUpdates(nullptr); - hit_obj->sendTEUpdate(); -} - -void LLToolDragAndDrop::dropMaterial(LLViewerObject* hit_obj, - S32 hit_face, - LLInventoryItem* item, - LLToolDragAndDrop::ESource source, - const LLUUID& src_id, - bool all_faces) -{ - LLSelectNode* nodep = nullptr; - if (hit_obj->isSelected()) - { - // update object's saved materials - nodep = LLSelectMgr::getInstance()->getSelection()->findNode(hit_obj); - } - - // If user dropped a material onto face it implies - // applying texture now without cancel, save to selection - if (all_faces) - { - dropMaterialAllFaces(hit_obj, item, source, src_id); - - if (nodep) - { - uuid_vec_t material_ids; - gltf_materials_vec_t override_materials; - S32 num_faces = hit_obj->getNumTEs(); - for (S32 face = 0; face < num_faces; face++) - { - material_ids.push_back(hit_obj->getRenderMaterialID(face)); - override_materials.push_back(nullptr); - } - nodep->saveGLTFMaterials(material_ids, override_materials); - } - } - else - { - dropMaterialOneFace(hit_obj, hit_face, item, source, src_id); - - // If user dropped a material onto face it implies - // applying texture now without cancel, save to selection - if (nodep - && gFloaterTools->getVisible() - && nodep->mSavedGLTFMaterialIds.size() > hit_face) - { - nodep->mSavedGLTFMaterialIds[hit_face] = hit_obj->getRenderMaterialID(hit_face); - nodep->mSavedGLTFOverrideMaterials[hit_face] = nullptr; - } - } -} - -void LLToolDragAndDrop::dropMaterialOneFace(LLViewerObject* hit_obj, - S32 hit_face, - LLInventoryItem* item, - LLToolDragAndDrop::ESource source, - const LLUUID& src_id) -{ - if (hit_face == -1) return; - if (!item || item->getInventoryType() != LLInventoryType::IT_MATERIAL) - { - LL_WARNS() << "LLToolDragAndDrop::dropTextureOneFace no material item." << LL_ENDL; - return; - } - - // SL-20013 must save asset_id before handleDropMaterialProtections since our item instance - // may be deleted if it is moved into task inventory - LLUUID asset_id = item->getAssetUUID(); - bool success = handleDropMaterialProtections(hit_obj, item, source, src_id); - if (!success) - { - return; - } - - if (asset_id.isNull()) - { - // use blank material - asset_id = LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID; - } - - hit_obj->setRenderMaterialID(hit_face, asset_id); - - dialog_refresh_all(); - - // send the update to the simulator - hit_obj->sendTEUpdate(); -} - - -void LLToolDragAndDrop::dropMaterialAllFaces(LLViewerObject* hit_obj, - LLInventoryItem* item, - LLToolDragAndDrop::ESource source, - const LLUUID& src_id) -{ - if (!item || item->getInventoryType() != LLInventoryType::IT_MATERIAL) - { - LL_WARNS() << "LLToolDragAndDrop::dropTextureAllFaces no material item." << LL_ENDL; - return; - } - - // SL-20013 must save asset_id before handleDropMaterialProtections since our item instance - // may be deleted if it is moved into task inventory - LLUUID asset_id = item->getAssetUUID(); - bool success = handleDropMaterialProtections(hit_obj, item, source, src_id); - - if (!success) - { - return; - } - - if (asset_id.isNull()) - { - // use blank material - asset_id = LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID; - } - - hit_obj->setRenderMaterialIDs(asset_id); - dialog_refresh_all(); - // send the update to the simulator - hit_obj->sendTEUpdate(); -} - - -void LLToolDragAndDrop::dropMesh(LLViewerObject* hit_obj, - LLInventoryItem* item, - LLToolDragAndDrop::ESource source, - const LLUUID& src_id) -{ - if (!item) - { - LL_WARNS() << "no inventory item." << LL_ENDL; - return; - } - LLUUID asset_id = item->getAssetUUID(); - bool success = handleDropMaterialProtections(hit_obj, item, source, src_id); - if(!success) - { - return; - } - - LLSculptParams sculpt_params; - sculpt_params.setSculptTexture(asset_id, LL_SCULPT_TYPE_MESH); - hit_obj->setParameterEntry(LLNetworkData::PARAMS_SCULPT, sculpt_params, true); - - dialog_refresh_all(); -} - -void LLToolDragAndDrop::dropTexture(LLViewerObject* hit_obj, - S32 hit_face, - LLInventoryItem* item, - ESource source, - const LLUUID& src_id, - bool all_faces, - bool remove_pbr, - S32 tex_channel) -{ - LLSelectNode* nodep = nullptr; - if (hit_obj->isSelected()) - { - // update object's saved textures - nodep = LLSelectMgr::getInstance()->getSelection()->findNode(hit_obj); - } - - if (all_faces) - { - dropTextureAllFaces(hit_obj, item, source, src_id, remove_pbr); - - // If user dropped a texture onto face it implies - // applying texture now without cancel, save to selection - if (nodep) - { - uuid_vec_t texture_ids; - uuid_vec_t material_ids; - gltf_materials_vec_t override_materials; - S32 num_faces = hit_obj->getNumTEs(); - for (S32 face = 0; face < num_faces; face++) - { - LLViewerTexture* tex = hit_obj->getTEImage(face); - if (tex != nullptr) - { - texture_ids.push_back(tex->getID()); - } - else - { - texture_ids.push_back(LLUUID::null); - } - - // either removed or modified materials - if (remove_pbr) - { - material_ids.push_back(LLUUID::null); - } - else - { - material_ids.push_back(hit_obj->getRenderMaterialID(face)); - } - - LLTextureEntry* te = hit_obj->getTE(hit_face); - if (te && !remove_pbr) - { - override_materials.push_back(te->getGLTFMaterialOverride()); - } - else - { - override_materials.push_back(nullptr); - } - } - - nodep->saveTextures(texture_ids); - nodep->saveGLTFMaterials(material_ids, override_materials); - } - } - else - { - dropTextureOneFace(hit_obj, hit_face, item, source, src_id, remove_pbr, tex_channel); - - // If user dropped a texture onto face it implies - // applying texture now without cancel, save to selection - LLPanelFace* panel_face = gFloaterTools->getPanelFace(); - if (nodep - && gFloaterTools->getVisible() - && panel_face - && panel_face->getTextureDropChannel() == 0 /*texture*/ - && nodep->mSavedTextures.size() > hit_face) - { - LLViewerTexture* tex = hit_obj->getTEImage(hit_face); - if (tex != nullptr) - { - nodep->mSavedTextures[hit_face] = tex->getID(); - } - else - { - nodep->mSavedTextures[hit_face] = LLUUID::null; - } - - LLTextureEntry* te = hit_obj->getTE(hit_face); - if (te && !remove_pbr) - { - nodep->mSavedGLTFOverrideMaterials[hit_face] = te->getGLTFMaterialOverride(); - } - else - { - nodep->mSavedGLTFOverrideMaterials[hit_face] = nullptr; - } - } - } -} - -void LLToolDragAndDrop::dropTextureOneFace(LLViewerObject* hit_obj, - S32 hit_face, - LLInventoryItem* item, - LLToolDragAndDrop::ESource source, - const LLUUID& src_id, - bool remove_pbr, - S32 tex_channel) -{ - if (hit_face == -1) return; - if (!item) - { - LL_WARNS() << "LLToolDragAndDrop::dropTextureOneFace no texture item." << LL_ENDL; - return; - } - - LLUUID asset_id = item->getAssetUUID(); - - if (hit_obj->getRenderMaterialID(hit_face).notNull() && !remove_pbr) - { - // Overrides require textures to be copy and transfer free - LLPermissions item_permissions = item->getPermissions(); - bool allow_adding_to_override = item_permissions.allowOperationBy(PERM_COPY, gAgent.getID()); - allow_adding_to_override &= item_permissions.allowOperationBy(PERM_TRANSFER, gAgent.getID()); - - if (allow_adding_to_override) - { - LLGLTFMaterial::TextureInfo drop_channel = LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR; - LLPanelFace* panel_face = gFloaterTools->getPanelFace(); - if (gFloaterTools->getVisible() && panel_face) - { - drop_channel = panel_face->getPBRDropChannel(); - } - set_texture_to_material(hit_obj, hit_face, asset_id, drop_channel); - LLGLTFMaterialList::flushUpdates(nullptr); - } - return; - } - bool success = handleDropMaterialProtections(hit_obj, item, source, src_id); - if (!success) - { - return; - } - if (remove_pbr) - { - hit_obj->setRenderMaterialID(hit_face, LLUUID::null); - } - - // update viewer side image in anticipation of update from simulator - LLViewerTexture* image = LLViewerTextureManager::getFetchedTexture(asset_id); - add(LLStatViewer::EDIT_TEXTURE, 1); - - LLTextureEntry* tep = hit_obj->getTE(hit_face); - - LLPanelFace* panel_face = gFloaterTools->getPanelFace(); - - if (gFloaterTools->getVisible() && panel_face) - { - tex_channel = (tex_channel > -1) ? tex_channel : panel_face->getTextureDropChannel(); - switch (tex_channel) - { - - case 0: - default: - { - hit_obj->setTEImage(hit_face, image); - } - break; - - case 1: - if (tep) - { - LLMaterialPtr old_mat = tep->getMaterialParams(); - LLMaterialPtr new_mat = panel_face->createDefaultMaterial(old_mat); - new_mat->setNormalID(asset_id); - tep->setMaterialParams(new_mat); - hit_obj->setTENormalMap(hit_face, asset_id); - LLMaterialMgr::getInstance()->put(hit_obj->getID(), hit_face, *new_mat); - } - break; - - case 2: - if (tep) - { - LLMaterialPtr old_mat = tep->getMaterialParams(); - LLMaterialPtr new_mat = panel_face->createDefaultMaterial(old_mat); - new_mat->setSpecularID(asset_id); - tep->setMaterialParams(new_mat); - hit_obj->setTESpecularMap(hit_face, asset_id); - LLMaterialMgr::getInstance()->put(hit_obj->getID(), hit_face, *new_mat); - } - break; - } - } - else - { - hit_obj->setTEImage(hit_face, image); - } - - dialog_refresh_all(); - - // send the update to the simulator - hit_obj->sendTEUpdate(); -} - - -void LLToolDragAndDrop::dropScript(LLViewerObject* hit_obj, - LLInventoryItem* item, - bool active, - ESource source, - const LLUUID& src_id) -{ - // *HACK: In order to resolve SL-22177, we need to block drags - // from notecards and objects onto other objects. - if ((SOURCE_WORLD == LLToolDragAndDrop::getInstance()->mSource) - || (SOURCE_NOTECARD == LLToolDragAndDrop::getInstance()->mSource)) - { - LL_WARNS() << "Call to LLToolDragAndDrop::dropScript() from world" - << " or notecard." << LL_ENDL; - return; - } - if (hit_obj && item) - { - LLPointer new_script = new LLViewerInventoryItem(item); - if (!item->getPermissions().allowCopyBy(gAgent.getID())) - { - if (SOURCE_AGENT == source) - { - // Remove the script from local inventory. The server - // will actually remove the item from agent inventory. - gInventory.deleteObject(item->getUUID()); - gInventory.notifyObservers(); - } - else if (SOURCE_WORLD == source) - { - // *FIX: if the objects are in different regions, and - // the source region has crashed, you can bypass - // these permissions. - LLViewerObject* src_obj = gObjectList.findObject(src_id); - if (src_obj) - { - src_obj->removeInventory(item->getUUID()); - } - else - { - LL_WARNS() << "Unable to find source object." << LL_ENDL; - return; - } - } - } - hit_obj->saveScript(new_script, active, true); - gFloaterTools->dirty(); - - // VEFFECT: SetScript - LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM, true); - effectp->setSourceObject(gAgentAvatarp); - effectp->setTargetObject(hit_obj); - effectp->setDuration(LL_HUD_DUR_SHORT); - effectp->setColor(LLColor4U(gAgent.getEffectColor())); - } -} - -void LLToolDragAndDrop::dropObject(LLViewerObject* raycast_target, - bool bypass_sim_raycast, - bool from_task_inventory, - bool remove_from_inventory) -{ - LLViewerRegion* regionp = LLWorld::getInstance()->getRegionFromPosGlobal(mLastHitPos); - if (!regionp) - { - LL_WARNS() << "Couldn't find region to rez object" << LL_ENDL; - return; - } - - //LL_INFOS() << "Rezzing object" << LL_ENDL; - make_ui_sound("UISndObjectRezIn"); - LLViewerInventoryItem* item; - LLViewerInventoryCategory* cat; - locateInventory(item, cat); - if (!item || !item->isFinished()) return; - - //if (regionp - // && (regionp->getRegionFlag(REGION_FLAGS_SANDBOX))) - //{ - // LLFirstUse::useSandbox(); - //} - // check if it cannot be copied, and mark as remove if it is - - // this will remove the object from inventory after rez. Only - // bother with this check if we would not normally remove from - // inventory. - if (!remove_from_inventory - && !item->getPermissions().allowCopyBy(gAgent.getID())) - { - remove_from_inventory = true; - } - - // Limit raycast to a single object. - // Speeds up server raycast + avoid problems with server ray - // hitting objects that were clipped by the near plane or culled - // on the viewer. - LLUUID ray_target_id; - if (raycast_target) - { - ray_target_id = raycast_target->getID(); - } - else - { - ray_target_id.setNull(); - } - - // Check if it's in the trash. - bool is_in_trash = false; - const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - if (gInventory.isObjectDescendentOf(item->getUUID(), trash_id)) - { - is_in_trash = true; - } - - LLUUID source_id = from_task_inventory ? mSourceID : LLUUID::null; - - // Select the object only if we're editing. - bool rez_selected = LLToolMgr::getInstance()->inEdit(); - - - LLVector3 ray_start = regionp->getPosRegionFromGlobal(mLastCameraPos); - LLVector3 ray_end = regionp->getPosRegionFromGlobal(mLastHitPos); - // currently the ray's end point is an approximation, - // and is sometimes too short (causing failure.) so we - // double the ray's length: - if (!bypass_sim_raycast) - { - LLVector3 ray_direction = ray_start - ray_end; - ray_end = ray_end - ray_direction; - } - - - // Message packing code should be it's own uninterrupted block - LLMessageSystem* msg = gMessageSystem; - if (mSource == SOURCE_NOTECARD) - { - LLUIUsage::instance().logCommand("Object.RezObjectFromNotecard"); - msg->newMessageFast(_PREHASH_RezObjectFromNotecard); - } - else - { - LLUIUsage::instance().logCommand("Object.RezObject"); - msg->newMessageFast(_PREHASH_RezObject); - } - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addUUIDFast(_PREHASH_GroupID, gAgent.getGroupID()); - - msg->nextBlock("RezData"); - // if it's being rezzed from task inventory, we need to enable - // saving it back into the task inventory. - // *FIX: We can probably compress this to a single byte, since I - // think folderid == mSourceID. This will be a later - // optimization. - msg->addUUIDFast(_PREHASH_FromTaskID, source_id); - msg->addU8Fast(_PREHASH_BypassRaycast, (U8) bypass_sim_raycast); - msg->addVector3Fast(_PREHASH_RayStart, ray_start); - msg->addVector3Fast(_PREHASH_RayEnd, ray_end); - msg->addUUIDFast(_PREHASH_RayTargetID, ray_target_id ); - msg->addBOOLFast(_PREHASH_RayEndIsIntersection, false); - msg->addBOOLFast(_PREHASH_RezSelected, rez_selected); - msg->addBOOLFast(_PREHASH_RemoveItem, remove_from_inventory); - - // deal with permissions slam logic - pack_permissions_slam(msg, item->getFlags(), item->getPermissions()); - - LLUUID folder_id = item->getParentUUID(); - if ((SOURCE_LIBRARY == mSource) || (is_in_trash)) - { - // since it's coming from the library or trash, we want to not - // 'take' it back to the same place. - item->setParent(LLUUID::null); - // *TODO this code isn't working - the parent (FolderID) is still - // set when the object is "taken". so code on the "take" side is - // checking for trash and library as well (llviewermenu.cpp) - } - if (mSource == SOURCE_NOTECARD) - { - msg->nextBlockFast(_PREHASH_NotecardData); - msg->addUUIDFast(_PREHASH_NotecardItemID, mSourceID); - msg->addUUIDFast(_PREHASH_ObjectID, mObjectID); - msg->nextBlockFast(_PREHASH_InventoryData); - msg->addUUIDFast(_PREHASH_ItemID, item->getUUID()); - } - else - { - msg->nextBlockFast(_PREHASH_InventoryData); - item->packMessage(msg); - } - msg->sendReliable(regionp->getHost()); - // back out the change. no actual internal changes take place. - item->setParent(folder_id); - - // If we're going to select it, get ready for the incoming - // selected object. - if (rez_selected) - { - LLSelectMgr::getInstance()->deselectAll(); - gViewerWindow->getWindow()->incBusyCount(); - } - - if (remove_from_inventory) - { - // Delete it from inventory immediately so that users cannot - // easily bypass copy protection in laggy situations. If the - // rez fails, we will put it back on the server. - gInventory.deleteObject(item->getUUID()); - gInventory.notifyObservers(); - } - - // VEFFECT: DropObject - LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM, true); - effectp->setSourceObject(gAgentAvatarp); - effectp->setPositionGlobal(mLastHitPos); - effectp->setDuration(LL_HUD_DUR_SHORT); - effectp->setColor(LLColor4U(gAgent.getEffectColor())); - - add(LLStatViewer::OBJECT_REZ, 1); -} - -void LLToolDragAndDrop::dropInventory(LLViewerObject* hit_obj, - LLInventoryItem* item, - LLToolDragAndDrop::ESource source, - const LLUUID& src_id) -{ - // *HACK: In order to resolve SL-22177, we need to block drags - // from notecards and objects onto other objects. - if ((SOURCE_WORLD == LLToolDragAndDrop::getInstance()->mSource) - || (SOURCE_NOTECARD == LLToolDragAndDrop::getInstance()->mSource)) - { - LL_WARNS() << "Call to LLToolDragAndDrop::dropInventory() from world" - << " or notecard." << LL_ENDL; - return; - } - - LLPointer new_item = new LLViewerInventoryItem(item); - time_t creation_date = time_corrected(); - new_item->setCreationDate(creation_date); - - if (!item->getPermissions().allowCopyBy(gAgent.getID())) - { - if (SOURCE_AGENT == source) - { - // Remove the inventory item from local inventory. The - // server will actually remove the item from agent - // inventory. - gInventory.deleteObject(item->getUUID()); - gInventory.notifyObservers(); - } - else if (SOURCE_WORLD == source) - { - // *FIX: if the objects are in different regions, and the - // source region has crashed, you can bypass these - // permissions. - LLViewerObject* src_obj = gObjectList.findObject(src_id); - if (src_obj) - { - src_obj->removeInventory(item->getUUID()); - } - else - { - LL_WARNS() << "Unable to find source object." << LL_ENDL; - return; - } - } - } - hit_obj->updateInventory(new_item, TASK_INVENTORY_ITEM_KEY, true); - if (LLFloaterReg::instanceVisible("build")) - { - // *FIX: only show this if panel not expanded? - LLFloaterReg::showInstance("build", "Content"); - } - - // VEFFECT: AddToInventory - LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM, true); - effectp->setSourceObject(gAgentAvatarp); - effectp->setTargetObject(hit_obj); - effectp->setDuration(LL_HUD_DUR_SHORT); - effectp->setColor(LLColor4U(gAgent.getEffectColor())); - gFloaterTools->dirty(); -} - -// accessor that looks at permissions, copyability, and names of -// inventory items to determine if a drop would be ok. -EAcceptance LLToolDragAndDrop::willObjectAcceptInventory(LLViewerObject* obj, LLInventoryItem* item, EDragAndDropType type) -{ - // check the basics - if (!item || !obj) - return ACCEPT_NO; - - // HACK: downcast - LLViewerInventoryItem* vitem = (LLViewerInventoryItem*)item; - if (!vitem->isFinished() && (type != DAD_CATEGORY)) - { - // Note: for DAD_CATEGORY we assume that folder version check passed and folder - // is complete, meaning that items inside are up to date. - // (isFinished() == false) at the moment shows that item was loaded from cache. - // Library or agent inventory only. - return ACCEPT_NO; - } - if (vitem->getIsLinkType()) - return ACCEPT_NO; // No giving away links - - // deny attempts to drop from an object onto itself. This is to - // help make sure that drops that are from an object to an object - // don't have to worry about order of evaluation. Think of this - // like check for self in assignment. - if (obj->getID() == item->getParentUUID()) - { - return ACCEPT_NO; - } - - //bool copy = (perm.allowCopyBy(gAgent.getID(), - // gAgent.getGroupID()) - // && (obj->mPermModify || obj->mFlagAllowInventoryAdd)); - bool worn = false; - LLVOAvatarSelf* my_avatar = NULL; - switch (item->getType()) - { - case LLAssetType::AT_OBJECT: - my_avatar = gAgentAvatarp; - if(my_avatar && my_avatar->isWearingAttachment(item->getUUID())) - { - worn = true; - } - break; - case LLAssetType::AT_BODYPART: - case LLAssetType::AT_CLOTHING: - if (gAgentWearables.isWearingItem(item->getUUID())) - { - worn = true; - } - break; - case LLAssetType::AT_CALLINGCARD: - // Calling Cards in object are disabled for now - // because of incomplete LSL support. See STORM-1117. - return ACCEPT_NO; - default: - break; - } - const LLPermissions& perm = item->getPermissions(); - bool modify = (obj->permModify() || obj->flagAllowInventoryAdd()); - bool transfer = false; - if ((obj->permYouOwner() && (perm.getOwner() == gAgent.getID())) - || perm.allowOperationBy(PERM_TRANSFER, gAgent.getID())) - { - transfer = true; - } - bool volume = (LL_PCODE_VOLUME == obj->getPCode()); - bool attached = obj->isAttachment(); - bool unrestricted = (perm.getMaskBase() & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED; - - if (attached && !unrestricted) - { - // Attachments are in world and in inventory simultaneously, - // at the moment server doesn't support such a situation. - return ACCEPT_NO_LOCKED; - } - - if (modify && transfer && volume && !worn) - { - return ACCEPT_YES_MULTI; - } - - if (!modify) - { - return ACCEPT_NO_LOCKED; - } - - return ACCEPT_NO; -} - - -static void give_inventory_cb(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - // if Cancel pressed - if (option == 1) - { - return; - } - - LLSD payload = notification["payload"]; - const LLUUID& session_id = payload["session_id"]; - const LLUUID& agent_id = payload["agent_id"]; - LLViewerInventoryItem * inv_item = gInventory.getItem(payload["item_id"]); - LLViewerInventoryCategory * inv_cat = gInventory.getCategory(payload["item_id"]); - if (NULL == inv_item && NULL == inv_cat) - { - llassert( false ); - return; - } - bool successfully_shared; - if (inv_item) - { - successfully_shared = LLGiveInventory::doGiveInventoryItem(agent_id, inv_item, session_id); - } - else - { - successfully_shared = LLGiveInventory::doGiveInventoryCategory(agent_id, inv_cat, session_id); - } - if (successfully_shared) - { - if ("avatarpicker" == payload["d&d_dest"].asString()) - { - LLFloaterReg::hideInstance("avatar_picker"); - } - LLNotificationsUtil::add("ItemsShared"); - } -} - -static void show_object_sharing_confirmation(const std::string name, - LLInventoryObject* inv_item, - const LLSD& dest, - const LLUUID& dest_agent, - const LLUUID& session_id = LLUUID::null) -{ - if (!inv_item) - { - llassert(NULL != inv_item); - return; - } - LLSD substitutions; - substitutions["RESIDENTS"] = name; - substitutions["ITEMS"] = inv_item->getName(); - LLSD payload; - payload["agent_id"] = dest_agent; - payload["item_id"] = inv_item->getUUID(); - payload["session_id"] = session_id; - payload["d&d_dest"] = dest.asString(); - LLNotificationsUtil::add("ShareItemsConfirmation", substitutions, payload, &give_inventory_cb); -} - -static void get_name_cb(const LLUUID& id, - const LLAvatarName& av_name, - LLInventoryObject* inv_obj, - const LLSD& dest, - const LLUUID& dest_agent) -{ - show_object_sharing_confirmation(av_name.getUserName(), - inv_obj, - dest, - id, - LLUUID::null); -} - -// function used as drag-and-drop handler for simple agent give inventory requests -//static -bool LLToolDragAndDrop::handleGiveDragAndDrop(LLUUID dest_agent, LLUUID session_id, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - const LLSD& dest) -{ - // check the type - switch(cargo_type) - { - case DAD_TEXTURE: - case DAD_SOUND: - case DAD_LANDMARK: - case DAD_SCRIPT: - case DAD_OBJECT: - case DAD_NOTECARD: - case DAD_CLOTHING: - case DAD_BODYPART: - case DAD_ANIMATION: - case DAD_GESTURE: - case DAD_CALLINGCARD: - case DAD_MESH: - case DAD_CATEGORY: - case DAD_SETTINGS: - case DAD_MATERIAL: - { - LLInventoryObject* inv_obj = (LLInventoryObject*)cargo_data; - if(gInventory.getCategory(inv_obj->getUUID()) || (gInventory.getItem(inv_obj->getUUID()) - && LLGiveInventory::isInventoryGiveAcceptable(dynamic_cast(inv_obj)))) - { - // *TODO: get multiple object transfers working - *accept = ACCEPT_YES_COPY_SINGLE; - if(drop) - { - LLIMModel::LLIMSession * session = LLIMModel::instance().findIMSession(session_id); - - // If no IM session found get the destination agent's name by id. - if (NULL == session) - { - LLAvatarName av_name; - - // If destination agent's name is found in cash proceed to showing the confirmation dialog. - // Otherwise set up a callback to show the dialog when the name arrives. - if (LLAvatarNameCache::get(dest_agent, &av_name)) - { - show_object_sharing_confirmation(av_name.getUserName(), inv_obj, dest, dest_agent, LLUUID::null); - } - else - { - LLAvatarNameCache::get(dest_agent, boost::bind(&get_name_cb, _1, _2, inv_obj, dest, dest_agent)); - } - - return true; - } - std::string dest_name = session->mName; - LLAvatarName av_name; - if(LLAvatarNameCache::get(dest_agent, &av_name)) - { - dest_name = av_name.getCompleteName(); - } - // If an IM session with destination agent is found item offer will be logged in this session. - show_object_sharing_confirmation(dest_name, inv_obj, dest, dest_agent, session_id); - } - } - else - { - // It's not in the user's inventory (it's probably - // in an object's contents), so disallow dragging - // it here. You can't give something you don't - // yet have. - *accept = ACCEPT_NO; - } - break; - } - default: - *accept = ACCEPT_NO; - break; - } - - return true; -} - - - -/// -/// Methods called in the drag & drop array -/// - -EAcceptance LLToolDragAndDrop::dad3dNULL( - LLViewerObject*, S32, MASK, bool) -{ - LL_DEBUGS() << "LLToolDragAndDrop::dad3dNULL()" << LL_ENDL; - return ACCEPT_NO; -} - -EAcceptance LLToolDragAndDrop::dad3dRezAttachmentFromInv( - LLViewerObject* obj, S32 face, MASK mask, bool drop) -{ - LL_DEBUGS() << "LLToolDragAndDrop::dad3dRezAttachmentFromInv()" << LL_ENDL; - // must be in the user's inventory - if(mSource != SOURCE_AGENT && mSource != SOURCE_LIBRARY) - { - return ACCEPT_NO; - } - - LLViewerInventoryItem* item; - LLViewerInventoryCategory* cat; - locateInventory(item, cat); - if (!item || !item->isFinished()) return ACCEPT_NO; - - // must not be in the trash - const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - if( gInventory.isObjectDescendentOf( item->getUUID(), trash_id ) ) - { - return ACCEPT_NO; - } - - // must not be already wearing it - LLVOAvatarSelf* avatar = gAgentAvatarp; - if( !avatar || avatar->isWearingAttachment(item->getUUID()) ) - { - return ACCEPT_NO; - } - - const LLUUID &outbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_OUTBOX); - if(outbox_id.notNull() && gInventory.isObjectDescendentOf(item->getUUID(), outbox_id)) - { - // Legacy - return ACCEPT_NO; - } - - - if( drop ) - { - if(mSource == SOURCE_LIBRARY) - { - LLPointer cb = new LLBoostFuncInventoryCallback(boost::bind(rez_attachment_cb, _1, (LLViewerJointAttachment*)0)); - copy_inventory_item( - gAgent.getID(), - item->getPermissions().getOwner(), - item->getUUID(), - LLUUID::null, - std::string(), - cb); - } - else - { - rez_attachment(item, 0); - } - } - return ACCEPT_YES_SINGLE; -} - - -EAcceptance LLToolDragAndDrop::dad3dRezObjectOnLand( - LLViewerObject* obj, S32 face, MASK mask, bool drop) -{ - if (mSource == SOURCE_WORLD) - { - return dad3dRezFromObjectOnLand(obj, face, mask, drop); - } - - LL_DEBUGS() << "LLToolDragAndDrop::dad3dRezObjectOnLand()" << LL_ENDL; - LLViewerInventoryItem* item; - LLViewerInventoryCategory* cat; - locateInventory(item, cat); - if (!item || !item->isFinished()) return ACCEPT_NO; - - LLVOAvatarSelf* my_avatar = gAgentAvatarp; - if( !my_avatar || my_avatar->isWearingAttachment( item->getUUID() ) ) - { - return ACCEPT_NO; - } - - EAcceptance accept; - bool remove_inventory; - - // Get initial settings based on shift key - if (mask & MASK_SHIFT) - { - // For now, always make copy - //accept = ACCEPT_YES_SINGLE; - //remove_inventory = true; - accept = ACCEPT_YES_COPY_SINGLE; - remove_inventory = false; - } - else - { - accept = ACCEPT_YES_COPY_SINGLE; - remove_inventory = false; - } - - // check if the item can be copied. If not, send that to the sim - // which will remove the inventory item. - if(!item->getPermissions().allowCopyBy(gAgent.getID())) - { - accept = ACCEPT_YES_SINGLE; - remove_inventory = true; - } - - // Check if it's in the trash. - const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - if(gInventory.isObjectDescendentOf(item->getUUID(), trash_id)) - { - accept = ACCEPT_YES_SINGLE; - } - - if(drop) - { - dropObject(obj, true, false, remove_inventory); - } - - return accept; -} - -EAcceptance LLToolDragAndDrop::dad3dRezObjectOnObject( - LLViewerObject* obj, S32 face, MASK mask, bool drop) -{ - // handle objects coming from object inventory - if (mSource == SOURCE_WORLD) - { - return dad3dRezFromObjectOnObject(obj, face, mask, drop); - } - - LL_DEBUGS() << "LLToolDragAndDrop::dad3dRezObjectOnObject()" << LL_ENDL; - LLViewerInventoryItem* item; - LLViewerInventoryCategory* cat; - locateInventory(item, cat); - if (!item || !item->isFinished()) return ACCEPT_NO; - LLVOAvatarSelf* my_avatar = gAgentAvatarp; - if( !my_avatar || my_avatar->isWearingAttachment( item->getUUID() ) ) - { - return ACCEPT_NO; - } - - if((mask & MASK_CONTROL)) - { - // *HACK: In order to resolve SL-22177, we need to block drags - // from notecards and objects onto other objects. - if(mSource == SOURCE_NOTECARD) - { - return ACCEPT_NO; - } - - EAcceptance rv = willObjectAcceptInventory(obj, item); - if(drop && (ACCEPT_YES_SINGLE <= rv)) - { - dropInventory(obj, item, mSource, mSourceID); - } - return rv; - } - - EAcceptance accept; - bool remove_inventory; - - if (mask & MASK_SHIFT) - { - // For now, always make copy - //accept = ACCEPT_YES_SINGLE; - //remove_inventory = true; - accept = ACCEPT_YES_COPY_SINGLE; - remove_inventory = false; - } - else - { - accept = ACCEPT_YES_COPY_SINGLE; - remove_inventory = false; - } - - // check if the item can be copied. If not, send that to the sim - // which will remove the inventory item. - if(!item->getPermissions().allowCopyBy(gAgent.getID())) - { - accept = ACCEPT_YES_SINGLE; - remove_inventory = true; - } - - // Check if it's in the trash. - const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - if(gInventory.isObjectDescendentOf(item->getUUID(), trash_id)) - { - accept = ACCEPT_YES_SINGLE; - remove_inventory = true; - } - - if(drop) - { - dropObject(obj, false, false, remove_inventory); - } - - return accept; -} - -EAcceptance LLToolDragAndDrop::dad3dRezScript( - LLViewerObject* obj, S32 face, MASK mask, bool drop) -{ - LL_DEBUGS() << "LLToolDragAndDrop::dad3dRezScript()" << LL_ENDL; - - // *HACK: In order to resolve SL-22177, we need to block drags - // from notecards and objects onto other objects. - if((SOURCE_WORLD == mSource) || (SOURCE_NOTECARD == mSource)) - { - return ACCEPT_NO; - } - - LLViewerInventoryItem* item; - LLViewerInventoryCategory* cat; - locateInventory(item, cat); - if (!item || !item->isFinished()) return ACCEPT_NO; - EAcceptance rv = willObjectAcceptInventory(obj, item); - if(drop && (ACCEPT_YES_SINGLE <= rv)) - { - // rez in the script active by default, rez in inactive if the - // control key is being held down. - bool active = ((mask & MASK_CONTROL) == 0); - - LLViewerObject* root_object = obj; - if (obj && obj->getParent()) - { - LLViewerObject* parent_obj = (LLViewerObject*)obj->getParent(); - if (!parent_obj->isAvatar()) - { - root_object = parent_obj; - } - } - - dropScript(root_object, item, active, mSource, mSourceID); - } - return rv; -} - -EAcceptance LLToolDragAndDrop::dad3dApplyToObject( - LLViewerObject* obj, S32 face, MASK mask, bool drop, EDragAndDropType cargo_type) -{ - LL_DEBUGS() << "LLToolDragAndDrop::dad3dApplyToObject()" << LL_ENDL; - - // *HACK: In order to resolve SL-22177, we need to block drags - // from notecards and objects onto other objects. - if((SOURCE_WORLD == mSource) || (SOURCE_NOTECARD == mSource)) - { - return ACCEPT_NO; - } - - LLViewerInventoryItem* item; - LLViewerInventoryCategory* cat; - locateInventory(item, cat); - if (!item || !item->isFinished()) return ACCEPT_NO; - LLPermissions item_permissions = item->getPermissions(); - EAcceptance rv = willObjectAcceptInventory(obj, item); - if((mask & MASK_CONTROL)) - { - if((ACCEPT_YES_SINGLE <= rv) && drop) - { - dropInventory(obj, item, mSource, mSourceID); - } - return rv; - } - if(!obj->permModify()) - { - return ACCEPT_NO_LOCKED; - } - - if (cargo_type == DAD_TEXTURE && (mask & MASK_ALT) == 0) - { - bool has_non_pbr_faces = false; - if ((mask & MASK_SHIFT)) - { - S32 num_faces = obj->getNumTEs(); - for (S32 face = 0; face < num_faces; face++) - { - if (obj->getRenderMaterialID(face).isNull()) - { - has_non_pbr_faces = true; - break; - } - } - } - else - { - has_non_pbr_faces = obj->getRenderMaterialID(face).isNull(); - } - - if (!has_non_pbr_faces) - { - // Only pbr faces selected, texture will be added to an override - // Overrides require textures to be copy and transfer free - bool allow_adding_to_override = item_permissions.allowOperationBy(PERM_COPY, gAgent.getID()); - allow_adding_to_override &= item_permissions.allowOperationBy(PERM_TRANSFER, gAgent.getID()); - if (!allow_adding_to_override) return ACCEPT_NO; - } - } - - if(drop && (ACCEPT_YES_SINGLE <= rv)) - { - if (cargo_type == DAD_TEXTURE) - { - bool all_faces = mask & MASK_SHIFT; - bool remove_pbr = mask & MASK_ALT; - if (item_permissions.allowOperationBy(PERM_COPY, gAgent.getID())) - { - dropTexture(obj, face, item, mSource, mSourceID, all_faces, remove_pbr); - } - else - { - ESource source = mSource; - LLUUID source_id = mSourceID; - LLNotificationsUtil::add("ApplyInventoryToObject", LLSD(), LLSD(), [obj, face, item, source, source_id, all_faces, remove_pbr](const LLSD& notification, const LLSD& response) - { - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - // if Cancel pressed - if (option == 1) - { - return; - } - dropTexture(obj, face, item, source, source_id, all_faces, remove_pbr); - }); - } - } - else if (cargo_type == DAD_MATERIAL) - { - bool all_faces = mask & MASK_SHIFT; - if (item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID())) - { - dropMaterial(obj, face, item, mSource, mSourceID, all_faces); - } - else - { - ESource source = mSource; - LLUUID source_id = mSourceID; - LLNotificationsUtil::add("ApplyInventoryToObject", LLSD(), LLSD(), [obj, face, item, source, source_id, all_faces](const LLSD& notification, const LLSD& response) - { - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - // if Cancel pressed - if (option == 1) - { - return; - } - dropMaterial(obj, face, item, source, source_id, all_faces); - }); - } - } - else if (cargo_type == DAD_MESH) - { - dropMesh(obj, item, mSource, mSourceID); - } - else - { - LL_WARNS() << "unsupported asset type" << LL_ENDL; - } - - // VEFFECT: SetTexture - LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM, true); - effectp->setSourceObject(gAgentAvatarp); - effectp->setTargetObject(obj); - effectp->setDuration(LL_HUD_DUR_SHORT); - effectp->setColor(LLColor4U(gAgent.getEffectColor())); - } - - // enable multi-drop, although last texture will win - return ACCEPT_YES_MULTI; -} - - -EAcceptance LLToolDragAndDrop::dad3dTextureObject( - LLViewerObject* obj, S32 face, MASK mask, bool drop) -{ - return dad3dApplyToObject(obj, face, mask, drop, DAD_TEXTURE); -} - -EAcceptance LLToolDragAndDrop::dad3dMaterialObject( - LLViewerObject* obj, S32 face, MASK mask, bool drop) -{ - return dad3dApplyToObject(obj, face, mask, drop, DAD_MATERIAL); -} - -EAcceptance LLToolDragAndDrop::dad3dMeshObject( - LLViewerObject* obj, S32 face, MASK mask, bool drop) -{ - return dad3dApplyToObject(obj, face, mask, drop, DAD_MESH); -} - - -/* -EAcceptance LLToolDragAndDrop::dad3dTextureSelf( - LLViewerObject* obj, S32 face, MASK mask, bool drop) -{ - LL_DEBUGS() << "LLToolDragAndDrop::dad3dTextureAvatar()" << LL_ENDL; - if(drop) - { - if( !(mask & MASK_SHIFT) ) - { - dropTextureOneFaceAvatar( (LLVOAvatar*)obj, face, (LLInventoryItem*)mCargoData); - } - } - return (mask & MASK_SHIFT) ? ACCEPT_NO : ACCEPT_YES_SINGLE; -} -*/ - -EAcceptance LLToolDragAndDrop::dad3dWearItem( - LLViewerObject* obj, S32 face, MASK mask, bool drop) -{ - LL_DEBUGS() << "LLToolDragAndDrop::dad3dWearItem()" << LL_ENDL; - LLViewerInventoryItem* item; - LLViewerInventoryCategory* cat; - locateInventory(item, cat); - if (!item || !item->isFinished()) return ACCEPT_NO; - - if(mSource == SOURCE_AGENT || mSource == SOURCE_LIBRARY) - { - // it's in the agent inventory - const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - if( gInventory.isObjectDescendentOf( item->getUUID(), trash_id ) ) - { - return ACCEPT_NO; - } - - if( drop ) - { - // TODO: investigate wearables may not be loaded at this point EXT-8231 - - LLAppearanceMgr::instance().wearItemOnAvatar(item->getUUID(),true, !(mask & MASK_CONTROL)); - } - return ACCEPT_YES_MULTI; - } - else - { - // TODO: copy/move item to avatar's inventory and then wear it. - return ACCEPT_NO; - } -} - -EAcceptance LLToolDragAndDrop::dad3dActivateGesture( - LLViewerObject* obj, S32 face, MASK mask, bool drop) -{ - LL_DEBUGS() << "LLToolDragAndDrop::dad3dActivateGesture()" << LL_ENDL; - LLViewerInventoryItem* item; - LLViewerInventoryCategory* cat; - locateInventory(item, cat); - if (!item || !item->isFinished()) return ACCEPT_NO; - - if(mSource == SOURCE_AGENT || mSource == SOURCE_LIBRARY) - { - // it's in the agent inventory - const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - if( gInventory.isObjectDescendentOf( item->getUUID(), trash_id ) ) - { - return ACCEPT_NO; - } - - if( drop ) - { - LLUUID item_id; - if(mSource == SOURCE_LIBRARY) - { - // create item based on that one, and put it on if that - // was a success. - LLPointer cb = new LLBoostFuncInventoryCallback(activate_gesture_cb); - copy_inventory_item( - gAgent.getID(), - item->getPermissions().getOwner(), - item->getUUID(), - LLUUID::null, - std::string(), - cb); - } - else - { - LLGestureMgr::instance().activateGesture(item->getUUID()); - gInventory.updateItem(item); - gInventory.notifyObservers(); - } - } - return ACCEPT_YES_MULTI; - } - else - { - return ACCEPT_NO; - } -} - -EAcceptance LLToolDragAndDrop::dad3dWearCategory( - LLViewerObject* obj, S32 face, MASK mask, bool drop) -{ - LL_DEBUGS() << "LLToolDragAndDrop::dad3dWearCategory()" << LL_ENDL; - LLViewerInventoryItem* item; - LLViewerInventoryCategory* category; - locateInventory(item, category); - if(!category) return ACCEPT_NO; - - if (drop) - { - // TODO: investigate wearables may not be loaded at this point EXT-8231 - } - - U32 max_items = gSavedSettings.getU32("WearFolderLimit"); - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - LLFindWearablesEx not_worn(/*is_worn=*/ false, /*include_body_parts=*/ false); - gInventory.collectDescendentsIf(category->getUUID(), - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - not_worn); - if (items.size() > max_items) - { - LLStringUtil::format_map_t args; - args["AMOUNT"] = llformat("%d", max_items); - mCustomMsg = LLTrans::getString("TooltipTooManyWearables",args); - return ACCEPT_NO_CUSTOM; - } - - if (mSource == SOURCE_AGENT) - { - const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - if (gInventory.isObjectDescendentOf(category->getUUID(), trash_id)) - { - return ACCEPT_NO; - } - - const LLUUID &outbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_OUTBOX); - if (outbox_id.notNull() && gInventory.isObjectDescendentOf(category->getUUID(), outbox_id)) - { - // Legacy - return ACCEPT_NO; - } - - if (drop) - { - LLAppearanceMgr::instance().wearInventoryCategory(category, false, mask & MASK_SHIFT); - } - - return ACCEPT_YES_MULTI; - } - - if (mSource == SOURCE_LIBRARY) - { - if (drop) - { - LLAppearanceMgr::instance().wearInventoryCategory(category, true, false); - } - - return ACCEPT_YES_MULTI; - } - - // TODO: copy/move category to avatar's inventory and then wear it. - return ACCEPT_NO; -} - - -EAcceptance LLToolDragAndDrop::dad3dUpdateInventory( - LLViewerObject* obj, S32 face, MASK mask, bool drop) -{ - LL_DEBUGS() << "LLToolDragAndDrop::dadUpdateInventory()" << LL_ENDL; - - // *HACK: In order to resolve SL-22177, we need to block drags - // from notecards and objects onto other objects. - if((SOURCE_WORLD == mSource) || (SOURCE_NOTECARD == mSource)) - { - return ACCEPT_NO; - } - - LLViewerInventoryItem* item; - LLViewerInventoryCategory* cat; - locateInventory(item, cat); - if (!item || !item->isFinished()) - return ACCEPT_NO; - - LLViewerObject* root_object = obj; - if (obj && obj->getParent()) - { - LLViewerObject* parent_obj = (LLViewerObject*)obj->getParent(); - if (!parent_obj->isAvatar()) - { - root_object = parent_obj; - } - } - - EAcceptance rv = willObjectAcceptInventory(root_object, item); - if (root_object && drop && (ACCEPT_YES_COPY_SINGLE <= rv)) - { - dropInventory(root_object, item, mSource, mSourceID); - } - - return rv; -} - -bool LLToolDragAndDrop::dadUpdateInventory(LLViewerObject* obj, bool drop) -{ - EAcceptance rv = dad3dUpdateInventory(obj, -1, MASK_NONE, drop); - return rv >= ACCEPT_YES_COPY_SINGLE; -} - -EAcceptance LLToolDragAndDrop::dad3dUpdateInventoryCategory( - LLViewerObject* obj, S32 face, MASK mask, bool drop) -{ - LL_DEBUGS() << "LLToolDragAndDrop::dad3dUpdateInventoryCategory()" << LL_ENDL; - if (obj == NULL) - { - LL_WARNS() << "obj is NULL; aborting func with ACCEPT_NO" << LL_ENDL; - return ACCEPT_NO; - } - - if ((mSource != SOURCE_AGENT) && (mSource != SOURCE_LIBRARY)) - { - return ACCEPT_NO; - } - if (obj->isAttachment()) - { - return ACCEPT_NO_LOCKED; - } - - LLViewerInventoryItem* item = NULL; - LLViewerInventoryCategory* cat = NULL; - locateInventory(item, cat); - if (!cat) - { - return ACCEPT_NO; - } - - // Find all the items in the category - LLDroppableItem droppable(!obj->permYouOwner()); - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - gInventory.collectDescendentsIf(cat->getUUID(), - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - droppable); - cats.push_back(cat); - if (droppable.countNoCopy() > 0) - { - LL_WARNS() << "*** Need to confirm this step" << LL_ENDL; - } - LLViewerObject* root_object = obj; - if (obj->getParent()) - { - LLViewerObject* parent_obj = (LLViewerObject*)obj->getParent(); - if (!parent_obj->isAvatar()) - { - root_object = parent_obj; - } - } - - EAcceptance rv = ACCEPT_NO; - - // Check for accept - for (LLInventoryModel::cat_array_t::const_iterator cat_iter = cats.begin(); - cat_iter != cats.end(); - ++cat_iter) - { - const LLViewerInventoryCategory *cat = (*cat_iter); - rv = gInventory.isCategoryComplete(cat->getUUID()) ? ACCEPT_YES_MULTI : ACCEPT_NO; - if(rv < ACCEPT_YES_SINGLE) - { - LL_DEBUGS() << "Category " << cat->getUUID() << "is not complete." << LL_ENDL; - break; - } - } - if (ACCEPT_YES_COPY_SINGLE <= rv) - { - for (LLInventoryModel::item_array_t::const_iterator item_iter = items.begin(); - item_iter != items.end(); - ++item_iter) - { - LLViewerInventoryItem *item = (*item_iter); - /* - // Pass the base objects, not the links. - if (item && item->getIsLinkType()) - { - item = item->getLinkedItem(); - (*item_iter) = item; - } - */ - rv = willObjectAcceptInventory(root_object, item, DAD_CATEGORY); - if (rv < ACCEPT_YES_COPY_SINGLE) - { - LL_DEBUGS() << "Object will not accept " << item->getUUID() << LL_ENDL; - break; - } - } - } - - // If every item is accepted, send it on - if (drop && (ACCEPT_YES_COPY_SINGLE <= rv)) - { - uuid_vec_t ids; - for (LLInventoryModel::item_array_t::const_iterator item_iter = items.begin(); - item_iter != items.end(); - ++item_iter) - { - const LLViewerInventoryItem *item = (*item_iter); - ids.push_back(item->getUUID()); - } - LLCategoryDropObserver* dropper = new LLCategoryDropObserver(ids, obj->getID(), mSource); - dropper->startFetch(); - if (dropper->isFinished()) - { - dropper->done(); - } - else - { - gInventory.addObserver(dropper); - } - } - return rv; -} - - -EAcceptance LLToolDragAndDrop::dad3dRezCategoryOnObject( - LLViewerObject* obj, S32 face, MASK mask, bool drop) -{ - if ((mask & MASK_CONTROL)) - { - return dad3dUpdateInventoryCategory(obj, face, mask, drop); - } - else - { - return ACCEPT_NO; - } -} - - -bool LLToolDragAndDrop::dadUpdateInventoryCategory(LLViewerObject* obj, - bool drop) -{ - EAcceptance rv = dad3dUpdateInventoryCategory(obj, -1, MASK_NONE, drop); - return (rv >= ACCEPT_YES_COPY_SINGLE); -} - -EAcceptance LLToolDragAndDrop::dad3dGiveInventoryObject( - LLViewerObject* obj, S32 face, MASK mask, bool drop) -{ - LL_DEBUGS() << "LLToolDragAndDrop::dad3dGiveInventoryObject()" << LL_ENDL; - - // item has to be in agent inventory. - if(mSource != SOURCE_AGENT) return ACCEPT_NO; - - // find the item now. - LLViewerInventoryItem* item; - LLViewerInventoryCategory* cat; - locateInventory(item, cat); - if (!item || !item->isFinished()) return ACCEPT_NO; - if(!item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID())) - { - // cannot give away no-transfer objects - return ACCEPT_NO; - } - LLVOAvatarSelf* avatar = gAgentAvatarp; - if(avatar && avatar->isWearingAttachment( item->getUUID() ) ) - { - // You can't give objects that are attached to you - return ACCEPT_NO; - } - if( obj && avatar ) - { - if(drop) - { - LLGiveInventory::doGiveInventoryItem(obj->getID(), item ); - } - // *TODO: deal with all the issues surrounding multi-object - // inventory transfers. - return ACCEPT_YES_SINGLE; - } - return ACCEPT_NO; -} - - -EAcceptance LLToolDragAndDrop::dad3dGiveInventory( - LLViewerObject* obj, S32 face, MASK mask, bool drop) -{ - LL_DEBUGS() << "LLToolDragAndDrop::dad3dGiveInventory()" << LL_ENDL; - // item has to be in agent inventory. - if(mSource != SOURCE_AGENT) return ACCEPT_NO; - LLViewerInventoryItem* item; - LLViewerInventoryCategory* cat; - locateInventory(item, cat); - if (!item || !item->isFinished()) return ACCEPT_NO; - if (!LLGiveInventory::isInventoryGiveAcceptable(item)) - { - return ACCEPT_NO; - } - if (drop && obj) - { - LLGiveInventory::doGiveInventoryItem(obj->getID(), item); - } - // *TODO: deal with all the issues surrounding multi-object - // inventory transfers. - return ACCEPT_YES_SINGLE; -} - -EAcceptance LLToolDragAndDrop::dad3dGiveInventoryCategory( - LLViewerObject* obj, S32 face, MASK mask, bool drop) -{ - LL_DEBUGS() << "LLToolDragAndDrop::dad3dGiveInventoryCategory()" << LL_ENDL; - if(drop && obj) - { - LLViewerInventoryItem* item; - LLViewerInventoryCategory* cat; - locateInventory(item, cat); - if(!cat) return ACCEPT_NO; - LLGiveInventory::doGiveInventoryCategory(obj->getID(), cat); - } - // *TODO: deal with all the issues surrounding multi-object - // inventory transfers. - return ACCEPT_YES_SINGLE; -} - - -EAcceptance LLToolDragAndDrop::dad3dRezFromObjectOnLand( - LLViewerObject* obj, S32 face, MASK mask, bool drop) -{ - LL_DEBUGS() << "LLToolDragAndDrop::dad3dRezFromObjectOnLand()" << LL_ENDL; - LLViewerInventoryItem* item = NULL; - LLViewerInventoryCategory* cat = NULL; - locateInventory(item, cat); - if (!item || !item->isFinished()) return ACCEPT_NO; - - if(!gAgent.allowOperation(PERM_COPY, item->getPermissions()) - || !item->getPermissions().allowTransferTo(LLUUID::null)) - { - return ACCEPT_NO_LOCKED; - } - if(drop) - { - dropObject(obj, true, true, false); - } - return ACCEPT_YES_SINGLE; -} - -EAcceptance LLToolDragAndDrop::dad3dRezFromObjectOnObject( - LLViewerObject* obj, S32 face, MASK mask, bool drop) -{ - LL_DEBUGS() << "LLToolDragAndDrop::dad3dRezFromObjectOnObject()" << LL_ENDL; - LLViewerInventoryItem* item; - LLViewerInventoryCategory* cat; - locateInventory(item, cat); - if (!item || !item->isFinished()) return ACCEPT_NO; - if((mask & MASK_CONTROL)) - { - // *HACK: In order to resolve SL-22177, we need to block drags - // from notecards and objects onto other objects. - return ACCEPT_NO; - - // *HACK: uncomment this when appropriate - //EAcceptance rv = willObjectAcceptInventory(obj, item); - //if(drop && (ACCEPT_YES_SINGLE <= rv)) - //{ - // dropInventory(obj, item, mSource, mSourceID); - //} - //return rv; - } - if(!item->getPermissions().allowCopyBy(gAgent.getID(), - gAgent.getGroupID()) - || !item->getPermissions().allowTransferTo(LLUUID::null)) - { - return ACCEPT_NO_LOCKED; - } - if(drop) - { - dropObject(obj, false, true, false); - } - return ACCEPT_YES_SINGLE; -} - -EAcceptance LLToolDragAndDrop::dad3dCategoryOnLand( - LLViewerObject *obj, S32 face, MASK mask, bool drop) -{ - return ACCEPT_NO; - /* - LL_DEBUGS() << "LLToolDragAndDrop::dad3dCategoryOnLand()" << LL_ENDL; - LLInventoryItem* item; - LLInventoryCategory* cat; - locateInventory(item, cat); - if(!cat) return ACCEPT_NO; - EAcceptance rv = ACCEPT_NO; - - // find all the items in the category - LLViewerInventoryCategory::cat_array_t cats; - LLViewerInventoryItem::item_array_t items; - LLDropCopyableItems droppable; - gInventory.collectDescendentsIf(cat->getUUID(), - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - droppable); - if(items.size() > 0) - { - rv = ACCEPT_YES_SINGLE; - } - if((rv >= ACCEPT_YES_COPY_SINGLE) && drop) - { - createContainer(items, cat->getName()); - return ACCEPT_NO; - } - return rv; - */ -} - - -// This is based on ALOT of copied, special-cased code -// This shortcuts alot of steps to make a basic object -// w/ an inventory and a special permissions set -EAcceptance LLToolDragAndDrop::dad3dAssetOnLand( - LLViewerObject *obj, S32 face, MASK mask, bool drop) -{ - return ACCEPT_NO; - /* - LL_DEBUGS() << "LLToolDragAndDrop::dad3dAssetOnLand()" << LL_ENDL; - LLViewerInventoryCategory::cat_array_t cats; - LLViewerInventoryItem::item_array_t items; - LLViewerInventoryItem::item_array_t copyable_items; - locateMultipleInventory(items, cats); - if(!items.size()) return ACCEPT_NO; - EAcceptance rv = ACCEPT_NO; - for (S32 i = 0; i < items.size(); i++) - { - LLInventoryItem* item = items[i]; - if(item->getPermissions().allowCopyBy(gAgent.getID())) - { - copyable_items.push_back(item); - rv = ACCEPT_YES_SINGLE; - } - } - - if((rv >= ACCEPT_YES_COPY_SINGLE) && drop) - { - createContainer(copyable_items, NULL); - } - - return rv; - */ -} - -LLInventoryObject* LLToolDragAndDrop::locateInventory( - LLViewerInventoryItem*& item, - LLViewerInventoryCategory*& cat) -{ - item = NULL; - cat = NULL; - - if (mCargoIDs.empty() - || (mSource == SOURCE_PEOPLE)) ///< There is no inventory item for people drag and drop. - { - return NULL; - } - - if((mSource == SOURCE_AGENT) || (mSource == SOURCE_LIBRARY)) - { - // The object should be in user inventory. - item = (LLViewerInventoryItem*)gInventory.getItem(mCargoIDs[mCurItemIndex]); - cat = (LLViewerInventoryCategory*)gInventory.getCategory(mCargoIDs[mCurItemIndex]); - } - else if(mSource == SOURCE_WORLD) - { - // This object is in some task inventory somewhere. - LLViewerObject* obj = gObjectList.findObject(mSourceID); - if(obj) - { - if((mCargoTypes[mCurItemIndex] == DAD_CATEGORY) - || (mCargoTypes[mCurItemIndex] == DAD_ROOT_CATEGORY)) - { - cat = (LLViewerInventoryCategory*)obj->getInventoryObject(mCargoIDs[mCurItemIndex]); - } - else - { - item = (LLViewerInventoryItem*)obj->getInventoryObject(mCargoIDs[mCurItemIndex]); - } - } - } - else if(mSource == SOURCE_NOTECARD) - { - LLPreviewNotecard* preview = LLFloaterReg::findTypedInstance("preview_notecard", mSourceID); - if(preview) - { - item = (LLViewerInventoryItem*)preview->getDragItem(); - } - } - else if(mSource == SOURCE_VIEWER) - { - item = (LLViewerInventoryItem*)gToolBarView->getDragItem(); - } - - if(item) return item; - if(cat) return cat; - return NULL; -} - -/* -LLInventoryObject* LLToolDragAndDrop::locateMultipleInventory(LLViewerInventoryCategory::cat_array_t& cats, - LLViewerInventoryItem::item_array_t& items) -{ - if(mCargoIDs.size() == 0) return NULL; - if((mSource == SOURCE_AGENT) || (mSource == SOURCE_LIBRARY)) - { - // The object should be in user inventory. - for (S32 i = 0; i < mCargoIDs.size(); i++) - { - LLInventoryItem* item = gInventory.getItem(mCargoIDs[i]); - if (item) - { - items.push_back(item); - } - LLInventoryCategory* category = gInventory.getCategory(mCargoIDs[i]); - if (category) - { - cats.push_back(category); - } - } - } - else if(mSource == SOURCE_WORLD) - { - // This object is in some task inventory somewhere. - LLViewerObject* obj = gObjectList.findObject(mSourceID); - if(obj) - { - if((mCargoType == DAD_CATEGORY) - || (mCargoType == DAD_ROOT_CATEGORY)) - { - // The object should be in user inventory. - for (S32 i = 0; i < mCargoIDs.size(); i++) - { - LLInventoryCategory* category = (LLInventoryCategory*)obj->getInventoryObject(mCargoIDs[i]); - if (category) - { - cats.push_back(category); - } - } - } - else - { - for (S32 i = 0; i < mCargoIDs.size(); i++) - { - LLInventoryItem* item = (LLInventoryItem*)obj->getInventoryObject(mCargoIDs[i]); - if (item) - { - items.push_back(item); - } - } - } - } - } - else if(mSource == SOURCE_NOTECARD) - { - LLPreviewNotecard* card; - card = (LLPreviewNotecard*)LLPreview::find(mSourceID); - if(card) - { - items.push_back((LLInventoryItem*)card->getDragItem()); - } - } - if(items.size()) return items[0]; - if(cats.size()) return cats[0]; - return NULL; -} -*/ - -// void LLToolDragAndDrop::createContainer(LLViewerInventoryItem::item_array_t &items, const char* preferred_name ) -// { -// LL_WARNS() << "LLToolDragAndDrop::createContainer()" << LL_ENDL; -// return; -// } - - -// utility functions - -void pack_permissions_slam(LLMessageSystem* msg, U32 flags, const LLPermissions& perms) -{ - // CRUFT -- the server no longer pays attention to this data - U32 group_mask = perms.getMaskGroup(); - U32 everyone_mask = perms.getMaskEveryone(); - U32 next_owner_mask = perms.getMaskNextOwner(); - - msg->addU32Fast(_PREHASH_ItemFlags, flags); - msg->addU32Fast(_PREHASH_GroupMask, group_mask); - msg->addU32Fast(_PREHASH_EveryoneMask, everyone_mask); - msg->addU32Fast(_PREHASH_NextOwnerMask, next_owner_mask); -} +/** + * @file lltooldraganddrop.cpp + * @brief LLToolDragAndDrop class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "lltooldraganddrop.h" + +// library headers +#include "llnotificationsutil.h" +// project headers +#include "llagent.h" +#include "llagentcamera.h" +#include "llagentwearables.h" +#include "llappearancemgr.h" +#include "llavatarnamecache.h" +#include "lldictionary.h" +#include "llfloaterreg.h" +#include "llfloatertools.h" +#include "llgesturemgr.h" +#include "llgiveinventory.h" +#include "llgltfmateriallist.h" +#include "llhudmanager.h" +#include "llhudeffecttrail.h" +#include "llimview.h" +#include "llinventorybridge.h" +#include "llinventorydefines.h" +#include "llinventoryfunctions.h" +#include "llinventorymodelbackgroundfetch.h" +#include "llpreviewnotecard.h" +#include "llrootview.h" +#include "llselectmgr.h" +#include "lltoolbarview.h" +#include "lltoolmgr.h" +#include "lltooltip.h" +#include "lltrans.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llviewerstats.h" +#include "llviewerwindow.h" +#include "llvoavatarself.h" +#include "llworld.h" +#include "llpanelface.h" +#include "lluiusage.h" + +// syntactic sugar +#define callMemberFunction(object,ptrToMember) ((object).*(ptrToMember)) + +class LLNoPreferredType : public LLInventoryCollectFunctor +{ +public: + LLNoPreferredType() {} + virtual ~LLNoPreferredType() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item) + { + if (cat && (cat->getPreferredType() == LLFolderType::FT_NONE)) + { + return true; + } + return false; + } +}; + +class LLNoPreferredTypeOrItem : public LLInventoryCollectFunctor +{ +public: + LLNoPreferredTypeOrItem() {} + virtual ~LLNoPreferredTypeOrItem() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item) + { + if (item) return true; + if (cat && (cat->getPreferredType() == LLFolderType::FT_NONE)) + { + return true; + } + return false; + } +}; + +class LLDroppableItem : public LLInventoryCollectFunctor +{ +public: + LLDroppableItem(bool is_transfer) : + mCountLosing(0), mIsTransfer(is_transfer) {} + virtual ~LLDroppableItem() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); + S32 countNoCopy() const { return mCountLosing; } + +protected: + S32 mCountLosing; + bool mIsTransfer; +}; + +bool LLDroppableItem::operator()(LLInventoryCategory* cat, + LLInventoryItem* item) +{ + bool allowed = false; + if (item) + { + allowed = itemTransferCommonlyAllowed(item); + + if (allowed + && mIsTransfer + && !item->getPermissions().allowOperationBy(PERM_TRANSFER, + gAgent.getID())) + { + allowed = false; + } + if (allowed && !item->getPermissions().allowCopyBy(gAgent.getID())) + { + ++mCountLosing; + } + } + return allowed; +} + +class LLDropCopyableItems : public LLInventoryCollectFunctor +{ +public: + LLDropCopyableItems() {} + virtual ~LLDropCopyableItems() {} + virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); +}; + + +bool LLDropCopyableItems::operator()( + LLInventoryCategory* cat, + LLInventoryItem* item) +{ + bool allowed = false; + if (item) + { + allowed = itemTransferCommonlyAllowed(item); + if (allowed && + !item->getPermissions().allowCopyBy(gAgent.getID())) + { + // whoops, can't copy it - don't allow it. + allowed = false; + } + } + return allowed; +} + +// Starts a fetch on folders and items. This is really not used +// as an observer in the traditional sense; we're just using it to +// request a fetch and we don't care about when/if the response arrives. +class LLCategoryFireAndForget : public LLInventoryFetchComboObserver +{ +public: + LLCategoryFireAndForget(const uuid_vec_t& folder_ids, + const uuid_vec_t& item_ids) : + LLInventoryFetchComboObserver(folder_ids, item_ids) + {} + ~LLCategoryFireAndForget() {} + virtual void done() + { + /* no-op: it's fire n forget right? */ + LL_DEBUGS() << "LLCategoryFireAndForget::done()" << LL_ENDL; + } +}; + +class LLCategoryDropObserver : public LLInventoryFetchItemsObserver +{ +public: + LLCategoryDropObserver( + const uuid_vec_t& ids, + const LLUUID& obj_id, LLToolDragAndDrop::ESource src) : + LLInventoryFetchItemsObserver(ids), + mObjectID(obj_id), + mSource(src) + {} + ~LLCategoryDropObserver() {} + virtual void done(); + +protected: + LLUUID mObjectID; + LLToolDragAndDrop::ESource mSource; +}; + +void LLCategoryDropObserver::done() +{ + gInventory.removeObserver(this); + LLViewerObject* dst_obj = gObjectList.findObject(mObjectID); + if (dst_obj) + { + // *FIX: coalesce these... + LLInventoryItem* item = NULL; + uuid_vec_t::iterator it = mComplete.begin(); + uuid_vec_t::iterator end = mComplete.end(); + for(; it < end; ++it) + { + item = gInventory.getItem(*it); + if (item) + { + LLToolDragAndDrop::dropInventory( + dst_obj, + item, + mSource, + LLUUID::null); + } + } + } + delete this; +} + +S32 LLToolDragAndDrop::sOperationId = 0; + +LLToolDragAndDrop::DragAndDropEntry::DragAndDropEntry(dragOrDrop3dImpl f_none, + dragOrDrop3dImpl f_self, + dragOrDrop3dImpl f_avatar, + dragOrDrop3dImpl f_object, + dragOrDrop3dImpl f_land) : + LLDictionaryEntry("") +{ + mFunctions[DT_NONE] = f_none; + mFunctions[DT_SELF] = f_self; + mFunctions[DT_AVATAR] = f_avatar; + mFunctions[DT_OBJECT] = f_object; + mFunctions[DT_LAND] = f_land; +} + +LLToolDragAndDrop::dragOrDrop3dImpl LLToolDragAndDrop::LLDragAndDropDictionary::get(EDragAndDropType dad_type, LLToolDragAndDrop::EDropTarget drop_target) +{ + const DragAndDropEntry *entry = lookup(dad_type); + if (entry) + { + return (entry->mFunctions[(U8)drop_target]); + } + return &LLToolDragAndDrop::dad3dNULL; +} + +LLToolDragAndDrop::LLDragAndDropDictionary::LLDragAndDropDictionary() +{ + // DT_NONE DT_SELF DT_AVATAR DT_OBJECT DT_LAND + // |-------------------------------|----------------------------------------------|-----------------------------------------------|---------------------------------------------------|--------------------------------| + addEntry(DAD_NONE, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL)); + addEntry(DAD_TEXTURE, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dTextureObject, &LLToolDragAndDrop::dad3dNULL)); + addEntry(DAD_MATERIAL, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dMaterialObject, &LLToolDragAndDrop::dad3dNULL)); + addEntry(DAD_SOUND, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dUpdateInventory, &LLToolDragAndDrop::dad3dNULL)); + addEntry(DAD_CALLINGCARD, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dUpdateInventory, &LLToolDragAndDrop::dad3dNULL)); + addEntry(DAD_LANDMARK, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dUpdateInventory, &LLToolDragAndDrop::dad3dNULL)); + addEntry(DAD_SCRIPT, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dRezScript, &LLToolDragAndDrop::dad3dNULL)); + addEntry(DAD_CLOTHING, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dWearItem, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dUpdateInventory, &LLToolDragAndDrop::dad3dNULL)); + addEntry(DAD_OBJECT, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dRezAttachmentFromInv, &LLToolDragAndDrop::dad3dGiveInventoryObject, &LLToolDragAndDrop::dad3dRezObjectOnObject, &LLToolDragAndDrop::dad3dRezObjectOnLand)); + addEntry(DAD_NOTECARD, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dUpdateInventory, &LLToolDragAndDrop::dad3dNULL)); + addEntry(DAD_CATEGORY, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dWearCategory, &LLToolDragAndDrop::dad3dGiveInventoryCategory, &LLToolDragAndDrop::dad3dRezCategoryOnObject, &LLToolDragAndDrop::dad3dNULL)); + addEntry(DAD_ROOT_CATEGORY, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL)); + addEntry(DAD_BODYPART, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dWearItem, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dUpdateInventory, &LLToolDragAndDrop::dad3dNULL)); + addEntry(DAD_ANIMATION, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dUpdateInventory, &LLToolDragAndDrop::dad3dNULL)); + addEntry(DAD_GESTURE, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dActivateGesture, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dUpdateInventory, &LLToolDragAndDrop::dad3dNULL)); + addEntry(DAD_LINK, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL)); + addEntry(DAD_MESH, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dMeshObject, &LLToolDragAndDrop::dad3dNULL)); + addEntry(DAD_SETTINGS, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dUpdateInventory, &LLToolDragAndDrop::dad3dNULL)); + + // TODO: animation on self could play it? edit it? + // TODO: gesture on self could play it? edit it? +}; + +LLToolDragAndDrop::LLToolDragAndDrop() +: LLTool(std::string("draganddrop"), NULL), + mCargoCount(0), + mDragStartX(0), + mDragStartY(0), + mSource(SOURCE_AGENT), + mCursor(UI_CURSOR_NO), + mLastAccept(ACCEPT_NO), + mDrop(false), + mCurItemIndex(0) +{ + +} + +void LLToolDragAndDrop::setDragStart(S32 x, S32 y) +{ + mDragStartX = x; + mDragStartY = y; +} + +bool LLToolDragAndDrop::isOverThreshold(S32 x,S32 y) +{ + S32 mouse_delta_x = x - mDragStartX; + S32 mouse_delta_y = y - mDragStartY; + + return (mouse_delta_x * mouse_delta_x) + (mouse_delta_y * mouse_delta_y) > DRAG_N_DROP_DISTANCE_THRESHOLD * DRAG_N_DROP_DISTANCE_THRESHOLD; +} + +void LLToolDragAndDrop::beginDrag(EDragAndDropType type, + const LLUUID& cargo_id, + ESource source, + const LLUUID& source_id, + const LLUUID& object_id) +{ + if (type == DAD_NONE) + { + LL_WARNS() << "Attempted to start drag without a cargo type" << LL_ENDL; + return; + } + + if (type != DAD_CATEGORY) + { + LLViewerInventoryItem* item = gInventory.getItem(cargo_id); + if (item && !item->isFinished()) + { + LLInventoryModelBackgroundFetch::instance().start(item->getUUID(), false); + } + } + + mCargoTypes.clear(); + mCargoTypes.push_back(type); + mCargoIDs.clear(); + mCargoIDs.push_back(cargo_id); + mSource = source; + mSourceID = source_id; + mObjectID = object_id; + + setMouseCapture( true ); + LLToolMgr::getInstance()->setTransientTool( this ); + mCursor = UI_CURSOR_NO; + if ((mCargoTypes[0] == DAD_CATEGORY) + && ((mSource == SOURCE_AGENT) || (mSource == SOURCE_LIBRARY))) + { + LLInventoryCategory* cat = gInventory.getCategory(cargo_id); + // go ahead and fire & forget the descendents if we are not + // dragging a protected folder. + if (cat) + { + LLViewerInventoryCategory::cat_array_t cats; + LLViewerInventoryItem::item_array_t items; + LLNoPreferredTypeOrItem is_not_preferred; + uuid_vec_t folder_ids; + uuid_vec_t item_ids; + if (is_not_preferred(cat, NULL)) + { + folder_ids.push_back(cargo_id); + } + gInventory.collectDescendentsIf( + cargo_id, + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + is_not_preferred); + S32 count = cats.size(); + S32 i; + for(i = 0; i < count; ++i) + { + folder_ids.push_back(cats.at(i)->getUUID()); + } + count = items.size(); + for(i = 0; i < count; ++i) + { + item_ids.push_back(items.at(i)->getUUID()); + } + if (!folder_ids.empty() || !item_ids.empty()) + { + LLCategoryFireAndForget *fetcher = new LLCategoryFireAndForget(folder_ids, item_ids); + fetcher->startFetch(); + delete fetcher; + } + } + } +} + +void LLToolDragAndDrop::beginMultiDrag( + const std::vector types, + const uuid_vec_t& cargo_ids, + ESource source, + const LLUUID& source_id) +{ + // assert on public api is evil + //llassert( type != DAD_NONE ); + + std::vector::const_iterator types_it; + for (types_it = types.begin(); types_it != types.end(); ++types_it) + { + if (DAD_NONE == *types_it) + { + LL_WARNS() << "Attempted to start drag without a cargo type" << LL_ENDL; + return; + } + } + mCargoTypes = types; + mCargoIDs = cargo_ids; + mSource = source; + mSourceID = source_id; + + setMouseCapture( true ); + LLToolMgr::getInstance()->setTransientTool( this ); + mCursor = UI_CURSOR_NO; + if ((mSource == SOURCE_AGENT) || (mSource == SOURCE_LIBRARY)) + { + // find categories (i.e. inventory folders) in the cargo. + LLInventoryCategory* cat = NULL; + S32 count = llmin(cargo_ids.size(), types.size()); + std::set cat_ids; + for(S32 i = 0; i < count; ++i) + { + cat = gInventory.getCategory(cargo_ids[i]); + if (cat) + { + LLViewerInventoryCategory::cat_array_t cats; + LLViewerInventoryItem::item_array_t items; + LLNoPreferredType is_not_preferred; + if (is_not_preferred(cat, NULL)) + { + cat_ids.insert(cat->getUUID()); + } + gInventory.collectDescendentsIf( + cat->getUUID(), + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + is_not_preferred); + S32 cat_count = cats.size(); + for(S32 i = 0; i < cat_count; ++i) + { + cat_ids.insert(cat->getUUID()); + } + } + } + if (!cat_ids.empty()) + { + uuid_vec_t folder_ids; + uuid_vec_t item_ids; + std::back_insert_iterator copier(folder_ids); + std::copy(cat_ids.begin(), cat_ids.end(), copier); + LLCategoryFireAndForget fetcher(folder_ids, item_ids); + } + } +} + +void LLToolDragAndDrop::endDrag() +{ + mEndDragSignal(); + LLSelectMgr::getInstance()->unhighlightAll(); + setMouseCapture(false); +} + +void LLToolDragAndDrop::onMouseCaptureLost() +{ + // Called whenever the drag ends or if mouse capture is simply lost + LLToolMgr::getInstance()->clearTransientTool(); + mCargoTypes.clear(); + mCargoIDs.clear(); + mSource = SOURCE_AGENT; + mSourceID.setNull(); + mObjectID.setNull(); + mCustomMsg.clear(); +} + +bool LLToolDragAndDrop::handleMouseUp( S32 x, S32 y, MASK mask ) +{ + if (hasMouseCapture()) + { + EAcceptance acceptance = ACCEPT_NO; + dragOrDrop( x, y, mask, true, &acceptance ); + endDrag(); + } + return true; +} + +ECursorType LLToolDragAndDrop::acceptanceToCursor( EAcceptance acceptance ) +{ + switch (acceptance) + { + case ACCEPT_YES_MULTI: + if (mCargoIDs.size() > 1) + { + mCursor = UI_CURSOR_ARROWDRAGMULTI; + } + else + { + mCursor = UI_CURSOR_ARROWDRAG; + } + break; + case ACCEPT_YES_SINGLE: + if (mCargoIDs.size() > 1) + { + mToolTipMsg = LLTrans::getString("TooltipMustSingleDrop"); + mCursor = UI_CURSOR_NO; + } + else + { + mCursor = UI_CURSOR_ARROWDRAG; + } + break; + + case ACCEPT_NO_LOCKED: + mCursor = UI_CURSOR_NOLOCKED; + break; + + case ACCEPT_NO_CUSTOM: + mToolTipMsg = mCustomMsg; + mCursor = UI_CURSOR_NO; + break; + + + case ACCEPT_NO: + mCursor = UI_CURSOR_NO; + break; + + case ACCEPT_YES_COPY_MULTI: + if (mCargoIDs.size() > 1) + { + mCursor = UI_CURSOR_ARROWCOPYMULTI; + } + else + { + mCursor = UI_CURSOR_ARROWCOPY; + } + break; + case ACCEPT_YES_COPY_SINGLE: + if (mCargoIDs.size() > 1) + { + mToolTipMsg = LLTrans::getString("TooltipMustSingleDrop"); + mCursor = UI_CURSOR_NO; + } + else + { + mCursor = UI_CURSOR_ARROWCOPY; + } + break; + case ACCEPT_POSTPONED: + break; + default: + llassert( false ); + } + + return mCursor; +} + +bool LLToolDragAndDrop::handleHover( S32 x, S32 y, MASK mask ) +{ + EAcceptance acceptance = ACCEPT_NO; + dragOrDrop( x, y, mask, false, &acceptance ); + + ECursorType cursor = acceptanceToCursor(acceptance); + gViewerWindow->getWindow()->setCursor( cursor ); + + LL_DEBUGS("UserInput") << "hover handled by LLToolDragAndDrop" << LL_ENDL; + return true; +} + +bool LLToolDragAndDrop::handleKey(KEY key, MASK mask) +{ + if (key == KEY_ESCAPE) + { + // cancel drag and drop operation + endDrag(); + return true; + } + + return false; +} + +bool LLToolDragAndDrop::handleToolTip(S32 x, S32 y, MASK mask) +{ + if (!mToolTipMsg.empty()) + { + LLToolTipMgr::instance().unblockToolTips(); + LLToolTipMgr::instance().show(LLToolTip::Params() + .message(mToolTipMsg) + .delay_time(gSavedSettings.getF32( "DragAndDropToolTipDelay" ))); + return true; + } + return false; +} + +void LLToolDragAndDrop::handleDeselect() +{ + mToolTipMsg.clear(); + mCustomMsg.clear(); + + LLToolTipMgr::instance().blockToolTips(); +} + +// protected +void LLToolDragAndDrop::dragOrDrop( S32 x, S32 y, MASK mask, bool drop, + EAcceptance* acceptance) +{ + *acceptance = ACCEPT_YES_MULTI; + + bool handled = false; + + LLView* top_view = gFocusMgr.getTopCtrl(); + LLViewerInventoryItem* item; + LLViewerInventoryCategory* cat; + + mToolTipMsg.clear(); + + // Increment the operation id for every drop + if (drop) + { + sOperationId++; + } + + // For people drag and drop we don't need an actual inventory object, + // instead we need the current cargo id, which should be a person id. + bool is_uuid_dragged = (mSource == SOURCE_PEOPLE); + + if (top_view) + { + handled = true; + + for (mCurItemIndex = 0; mCurItemIndex < (S32)mCargoIDs.size(); mCurItemIndex++) + { + S32 local_x, local_y; + top_view->screenPointToLocal( x, y, &local_x, &local_y ); + EAcceptance item_acceptance = ACCEPT_NO; + + LLInventoryObject* cargo = locateInventory(item, cat); + if (cargo) + { + handled = handled && top_view->handleDragAndDrop(local_x, local_y, mask, false, + mCargoTypes[mCurItemIndex], + (void*)cargo, + &item_acceptance, + mToolTipMsg); + } + else if (is_uuid_dragged) + { + handled = handled && top_view->handleDragAndDrop(local_x, local_y, mask, false, + mCargoTypes[mCurItemIndex], + (void*)&mCargoIDs[mCurItemIndex], + &item_acceptance, + mToolTipMsg); + } + if (handled) + { + // use sort order to determine priority of acceptance + *acceptance = (EAcceptance)llmin((U32)item_acceptance, (U32)*acceptance); + } + } + + // all objects passed, go ahead and perform drop if necessary + if (handled && drop && (U32)*acceptance >= ACCEPT_YES_COPY_SINGLE) + { + if ((U32)*acceptance < ACCEPT_YES_COPY_MULTI && + mCargoIDs.size() > 1) + { + // tried to give multi-cargo to a single-acceptor - refuse and return. + *acceptance = ACCEPT_NO; + return; + } + + for (mCurItemIndex = 0; mCurItemIndex < (S32)mCargoIDs.size(); mCurItemIndex++) + { + S32 local_x, local_y; + EAcceptance item_acceptance; + top_view->screenPointToLocal( x, y, &local_x, &local_y ); + + LLInventoryObject* cargo = locateInventory(item, cat); + if (cargo) + { + handled = handled && top_view->handleDragAndDrop(local_x, local_y, mask, true, + mCargoTypes[mCurItemIndex], + (void*)cargo, + &item_acceptance, + mToolTipMsg); + } + else if (is_uuid_dragged) + { + handled = handled && top_view->handleDragAndDrop(local_x, local_y, mask, false, + mCargoTypes[mCurItemIndex], + (void*)&mCargoIDs[mCurItemIndex], + &item_acceptance, + mToolTipMsg); + } + } + } + if (handled) + { + mLastAccept = (EAcceptance)*acceptance; + } + } + + if (!handled) + { + handled = true; + + LLRootView* root_view = gViewerWindow->getRootView(); + + for (mCurItemIndex = 0; mCurItemIndex < (S32)mCargoIDs.size(); mCurItemIndex++) + { + EAcceptance item_acceptance = ACCEPT_NO; + + LLInventoryObject* cargo = locateInventory(item, cat); + + // fix for EXT-3191 + if (cargo) + { + handled = handled && root_view->handleDragAndDrop(x, y, mask, false, + mCargoTypes[mCurItemIndex], + (void*)cargo, + &item_acceptance, + mToolTipMsg); + } + else if (is_uuid_dragged) + { + handled = handled && root_view->handleDragAndDrop(x, y, mask, false, + mCargoTypes[mCurItemIndex], + (void*)&mCargoIDs[mCurItemIndex], + &item_acceptance, + mToolTipMsg); + } + if (handled) + { + // use sort order to determine priority of acceptance + *acceptance = (EAcceptance)llmin((U32)item_acceptance, (U32)*acceptance); + } + } + // all objects passed, go ahead and perform drop if necessary + if (handled && drop && (U32)*acceptance > ACCEPT_NO_LOCKED) + { + if ((U32)*acceptance < ACCEPT_YES_COPY_MULTI && + mCargoIDs.size() > 1) + { + // tried to give multi-cargo to a single-acceptor - refuse and return. + *acceptance = ACCEPT_NO; + return; + } + + for (mCurItemIndex = 0; mCurItemIndex < (S32)mCargoIDs.size(); mCurItemIndex++) + { + EAcceptance item_acceptance; + + LLInventoryObject* cargo = locateInventory(item, cat); + if (cargo) + { + handled = handled && root_view->handleDragAndDrop(x, y, mask, true, + mCargoTypes[mCurItemIndex], + (void*)cargo, + &item_acceptance, + mToolTipMsg); + } + else if (is_uuid_dragged) + { + handled = handled && root_view->handleDragAndDrop(x, y, mask, true, + mCargoTypes[mCurItemIndex], + (void*)&mCargoIDs[mCurItemIndex], + &item_acceptance, + mToolTipMsg); + } + } + } + + if (handled) + { + mLastAccept = (EAcceptance)*acceptance; + } + } + + if (!handled) + { + // Disallow drag and drop to 3D from the marketplace + const LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + if (marketplacelistings_id.notNull()) + { + for (S32 item_index = 0; item_index < (S32)mCargoIDs.size(); item_index++) + { + if (gInventory.isObjectDescendentOf(mCargoIDs[item_index], marketplacelistings_id)) + { + *acceptance = ACCEPT_NO; + mToolTipMsg = LLTrans::getString("TooltipOutboxDragToWorld"); + return; + } + } + } + + dragOrDrop3D( x, y, mask, drop, acceptance ); + } +} + +void LLToolDragAndDrop::dragOrDrop3D( S32 x, S32 y, MASK mask, bool drop, EAcceptance* acceptance ) +{ + mDrop = drop; + if (mDrop) + { + // don't allow drag and drop onto rigged or transparent objects + pick(gViewerWindow->pickImmediate(x, y, false, false)); + } + else + { + // don't allow drag and drop onto transparent objects + gViewerWindow->pickAsync(x, y, mask, pickCallback, false, false); + } + + *acceptance = mLastAccept; +} + +void LLToolDragAndDrop::pickCallback(const LLPickInfo& pick_info) +{ + if (getInstance() != NULL) + { + getInstance()->pick(pick_info); + } +} + +void LLToolDragAndDrop::pick(const LLPickInfo& pick_info) +{ + EDropTarget target = DT_NONE; + S32 hit_face = -1; + + LLViewerObject* hit_obj = pick_info.getObject(); + LLSelectMgr::getInstance()->unhighlightAll(); + bool highlight_object = false; + // Treat attachments as part of the avatar they are attached to. + if (hit_obj != NULL) + { + // don't allow drag and drop on grass, trees, etc. + if (pick_info.mPickType == LLPickInfo::PICK_FLORA) + { + mCursor = UI_CURSOR_NO; + gViewerWindow->getWindow()->setCursor( mCursor ); + return; + } + + if (hit_obj->isAttachment() && !hit_obj->isHUDAttachment()) + { + LLVOAvatar* avatar = LLVOAvatar::findAvatarFromAttachment( hit_obj ); + if (!avatar) + { + mLastAccept = ACCEPT_NO; + mCursor = UI_CURSOR_NO; + gViewerWindow->getWindow()->setCursor( mCursor ); + return; + } + hit_obj = avatar; + } + + if (hit_obj->isAvatar()) + { + if (((LLVOAvatar*) hit_obj)->isSelf()) + { + target = DT_SELF; + hit_face = -1; + } + else + { + target = DT_AVATAR; + hit_face = -1; + } + } + else + { + target = DT_OBJECT; + hit_face = pick_info.mObjectFace; + highlight_object = true; + } + } + else if (pick_info.mPickType == LLPickInfo::PICK_LAND) + { + target = DT_LAND; + hit_face = -1; + } + + mLastAccept = ACCEPT_YES_MULTI; + + for (mCurItemIndex = 0; mCurItemIndex < (S32)mCargoIDs.size(); mCurItemIndex++) + { + const S32 item_index = mCurItemIndex; + const EDragAndDropType dad_type = mCargoTypes[item_index]; + // Call the right implementation function + mLastAccept = (EAcceptance)llmin( + (U32)mLastAccept, + (U32)callMemberFunction(*this, + LLDragAndDropDictionary::instance().get(dad_type, target)) + (hit_obj, hit_face, pick_info.mKeyMask, false)); + } + + if (mDrop && ((U32)mLastAccept >= ACCEPT_YES_COPY_SINGLE)) + { + // if target allows multi-drop or there is only one item being dropped, go ahead + if ((mLastAccept >= ACCEPT_YES_COPY_MULTI) || (mCargoIDs.size() == 1)) + { + // Target accepts multi, or cargo is a single-drop + for (mCurItemIndex = 0; mCurItemIndex < (S32)mCargoIDs.size(); mCurItemIndex++) + { + const S32 item_index = mCurItemIndex; + const EDragAndDropType dad_type = mCargoTypes[item_index]; + // Call the right implementation function + callMemberFunction(*this, LLDragAndDropDictionary::instance().get(dad_type, target)) + (hit_obj, hit_face, pick_info.mKeyMask, true); + } + } + else + { + // Target does not accept multi, but cargo is multi + mLastAccept = ACCEPT_NO; + } + } + + if (highlight_object && mLastAccept > ACCEPT_NO_LOCKED) + { + // if any item being dragged will be applied to the object under our cursor + // highlight that object + for (S32 i = 0; i < (S32)mCargoIDs.size(); i++) + { + if (mCargoTypes[i] != DAD_OBJECT || (pick_info.mKeyMask & MASK_CONTROL)) + { + LLSelectMgr::getInstance()->highlightObjectAndFamily(hit_obj); + break; + } + } + } + ECursorType cursor = acceptanceToCursor( mLastAccept ); + gViewerWindow->getWindow()->setCursor( cursor ); + + mLastHitPos = pick_info.mPosGlobal; + mLastCameraPos = gAgentCamera.getCameraPositionGlobal(); +} + +// static +bool LLToolDragAndDrop::handleDropMaterialProtections(LLViewerObject* hit_obj, + LLInventoryItem* item, + LLToolDragAndDrop::ESource source, + const LLUUID& src_id) +{ + if (!item) return false; + + // Always succeed if.... + // material is from the library + // or already in the contents of the object + if (SOURCE_LIBRARY == source) + { + // dropping a material from the library always just works. + return true; + } + + // In case the inventory has not been loaded (e.g. due to some recent operation + // causing a dirty inventory) and we can do an update, stall the user + // while fetching the inventory. + // + // Fetch if inventory is dirty and listener is present (otherwise we will not receive update) + if (hit_obj->isInventoryDirty() && hit_obj->hasInventoryListeners()) + { + hit_obj->requestInventory(); + LLSD args; + if (LLAssetType::AT_MATERIAL == item->getType()) + { + args["ERROR_MESSAGE"] = "Unable to add material.\nPlease wait a few seconds and try again."; + } + else + { + args["ERROR_MESSAGE"] = "Unable to add texture.\nPlease wait a few seconds and try again."; + } + LLNotificationsUtil::add("ErrorMessage", args); + return false; + } + // Make sure to verify both id and type since 'null' + // is a shared default for some asset types. + if (hit_obj->getInventoryItemByAsset(item->getAssetUUID(), item->getType())) + { + // if the asset is already in the object's inventory + // then it can always be added to a side. + // This saves some work if the task's inventory is already loaded + // and ensures that the asset item is only added once. + return true; + } + + LLPointer new_item = new LLViewerInventoryItem(item); + if (!item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID())) + { + // Check that we can add the material as inventory to the object + if (willObjectAcceptInventory(hit_obj,item) < ACCEPT_YES_COPY_SINGLE ) + { + return false; + } + // make sure the object has the material in it's inventory. + if (SOURCE_AGENT == source) + { + // Remove the material from local inventory. The server + // will actually remove the item from agent inventory. + gInventory.deleteObject(item->getUUID()); + gInventory.notifyObservers(); + } + else if (SOURCE_WORLD == source) + { + // *FIX: if the objects are in different regions, and the + // source region has crashed, you can bypass these + // permissions. + LLViewerObject* src_obj = gObjectList.findObject(src_id); + if (src_obj) + { + src_obj->removeInventory(item->getUUID()); + } + else + { + LL_WARNS() << "Unable to find source object." << LL_ENDL; + return false; + } + } + // Add the asset item to the target object's inventory. + if (LLAssetType::AT_TEXTURE == new_item->getType() + || LLAssetType::AT_MATERIAL == new_item->getType()) + { + hit_obj->updateMaterialInventory(new_item, TASK_INVENTORY_ITEM_KEY, true); + } + else + { + hit_obj->updateInventory(new_item, TASK_INVENTORY_ITEM_KEY, true); + } + // Force the object to update and refetch its inventory so it has this asset. + hit_obj->dirtyInventory(); + hit_obj->requestInventory(); + // TODO: Check to see if adding the item was successful; if not, then + // we should return false here. This will requre a separate listener + // since without listener, we have no way to receive update + } + else if (!item->getPermissions().allowOperationBy(PERM_TRANSFER, + gAgent.getID())) + { + // Check that we can add the asset as inventory to the object + if (willObjectAcceptInventory(hit_obj,item) < ACCEPT_YES_COPY_SINGLE ) + { + return false; + } + // *FIX: may want to make sure agent can paint hit_obj. + + // Add the asset item to the target object's inventory. + if (LLAssetType::AT_TEXTURE == new_item->getType() + || LLAssetType::AT_MATERIAL == new_item->getType()) + { + hit_obj->updateMaterialInventory(new_item, TASK_INVENTORY_ITEM_KEY, true); + } + else + { + hit_obj->updateInventory(new_item, TASK_INVENTORY_ITEM_KEY, true); + } + // Force the object to update and refetch its inventory so it has this asset. + hit_obj->dirtyInventory(); + hit_obj->requestInventory(); + // TODO: Check to see if adding the item was successful; if not, then + // we should return false here. This will requre a separate listener + // since without listener, we have no way to receive update + } + else if (LLAssetType::AT_MATERIAL == new_item->getType() && + !item->getPermissions().allowOperationBy(PERM_MODIFY, gAgent.getID())) + { + // Check that we can add the material as inventory to the object + if (willObjectAcceptInventory(hit_obj,item) < ACCEPT_YES_COPY_SINGLE ) + { + return false; + } + // *FIX: may want to make sure agent can paint hit_obj. + + // Add the material item to the target object's inventory. + hit_obj->updateMaterialInventory(new_item, TASK_INVENTORY_ITEM_KEY, true); + + // Force the object to update and refetch its inventory so it has this material. + hit_obj->dirtyInventory(); + hit_obj->requestInventory(); + // TODO: Check to see if adding the item was successful; if not, then + // we should return false here. This will requre a separate listener + // since without listener, we have no way to receive update + } + return true; +} + +void set_texture_to_material(LLViewerObject* hit_obj, + S32 hit_face, + const LLUUID& asset_id, + LLGLTFMaterial::TextureInfo drop_channel) +{ + LLTextureEntry* te = hit_obj->getTE(hit_face); + if (te) + { + LLPointer material = te->getGLTFMaterialOverride(); + + // make a copy to not invalidate existing + // material for multiple objects + if (material.isNull()) + { + // Start with a material override which does not make any changes + material = new LLGLTFMaterial(); + } + else + { + material = new LLGLTFMaterial(*material); + } + + switch (drop_channel) + { + case LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR: + default: + { + material->setBaseColorId(asset_id); + } + break; + + case LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS: + { + material->setOcclusionRoughnessMetallicId(asset_id); + } + break; + + case LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE: + { + material->setEmissiveId(asset_id); + } + break; + + case LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL: + { + material->setNormalId(asset_id); + } + break; + } + LLGLTFMaterialList::queueModify(hit_obj, hit_face, material); + } +} + +void LLToolDragAndDrop::dropTextureAllFaces(LLViewerObject* hit_obj, + LLInventoryItem* item, + LLToolDragAndDrop::ESource source, + const LLUUID& src_id, + bool remove_pbr) +{ + if (!item) + { + LL_WARNS() << "LLToolDragAndDrop::dropTextureAllFaces no texture item." << LL_ENDL; + return; + } + S32 num_faces = hit_obj->getNumTEs(); + bool has_non_pbr_faces = false; + for (S32 face = 0; face < num_faces; face++) + { + if (hit_obj->getRenderMaterialID(face).isNull()) + { + has_non_pbr_faces = true; + break; + } + } + + if (has_non_pbr_faces || remove_pbr) + { + bool res = handleDropMaterialProtections(hit_obj, item, source, src_id); + if (!res) + { + return; + } + } + LLUUID asset_id = item->getAssetUUID(); + + // Overrides require textures to be copy and transfer free + LLPermissions item_permissions = item->getPermissions(); + bool allow_adding_to_override = item_permissions.allowOperationBy(PERM_COPY, gAgent.getID()); + allow_adding_to_override &= item_permissions.allowOperationBy(PERM_TRANSFER, gAgent.getID()); + + LLViewerTexture* image = LLViewerTextureManager::getFetchedTexture(asset_id); + add(LLStatViewer::EDIT_TEXTURE, 1); + for( S32 face = 0; face < num_faces; face++ ) + { + if (remove_pbr) + { + hit_obj->setRenderMaterialID(face, LLUUID::null); + hit_obj->setTEImage(face, image); + dialog_refresh_all(); + } + else if (hit_obj->getRenderMaterialID(face).isNull()) + { + // update viewer side + hit_obj->setTEImage(face, image); + dialog_refresh_all(); + } + else if (allow_adding_to_override) + { + set_texture_to_material(hit_obj, face, asset_id, LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR); + } + } + + // send the update to the simulator + LLGLTFMaterialList::flushUpdates(nullptr); + hit_obj->sendTEUpdate(); +} + +void LLToolDragAndDrop::dropMaterial(LLViewerObject* hit_obj, + S32 hit_face, + LLInventoryItem* item, + LLToolDragAndDrop::ESource source, + const LLUUID& src_id, + bool all_faces) +{ + LLSelectNode* nodep = nullptr; + if (hit_obj->isSelected()) + { + // update object's saved materials + nodep = LLSelectMgr::getInstance()->getSelection()->findNode(hit_obj); + } + + // If user dropped a material onto face it implies + // applying texture now without cancel, save to selection + if (all_faces) + { + dropMaterialAllFaces(hit_obj, item, source, src_id); + + if (nodep) + { + uuid_vec_t material_ids; + gltf_materials_vec_t override_materials; + S32 num_faces = hit_obj->getNumTEs(); + for (S32 face = 0; face < num_faces; face++) + { + material_ids.push_back(hit_obj->getRenderMaterialID(face)); + override_materials.push_back(nullptr); + } + nodep->saveGLTFMaterials(material_ids, override_materials); + } + } + else + { + dropMaterialOneFace(hit_obj, hit_face, item, source, src_id); + + // If user dropped a material onto face it implies + // applying texture now without cancel, save to selection + if (nodep + && gFloaterTools->getVisible() + && nodep->mSavedGLTFMaterialIds.size() > hit_face) + { + nodep->mSavedGLTFMaterialIds[hit_face] = hit_obj->getRenderMaterialID(hit_face); + nodep->mSavedGLTFOverrideMaterials[hit_face] = nullptr; + } + } +} + +void LLToolDragAndDrop::dropMaterialOneFace(LLViewerObject* hit_obj, + S32 hit_face, + LLInventoryItem* item, + LLToolDragAndDrop::ESource source, + const LLUUID& src_id) +{ + if (hit_face == -1) return; + if (!item || item->getInventoryType() != LLInventoryType::IT_MATERIAL) + { + LL_WARNS() << "LLToolDragAndDrop::dropTextureOneFace no material item." << LL_ENDL; + return; + } + + // SL-20013 must save asset_id before handleDropMaterialProtections since our item instance + // may be deleted if it is moved into task inventory + LLUUID asset_id = item->getAssetUUID(); + bool success = handleDropMaterialProtections(hit_obj, item, source, src_id); + if (!success) + { + return; + } + + if (asset_id.isNull()) + { + // use blank material + asset_id = LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID; + } + + hit_obj->setRenderMaterialID(hit_face, asset_id); + + dialog_refresh_all(); + + // send the update to the simulator + hit_obj->sendTEUpdate(); +} + + +void LLToolDragAndDrop::dropMaterialAllFaces(LLViewerObject* hit_obj, + LLInventoryItem* item, + LLToolDragAndDrop::ESource source, + const LLUUID& src_id) +{ + if (!item || item->getInventoryType() != LLInventoryType::IT_MATERIAL) + { + LL_WARNS() << "LLToolDragAndDrop::dropTextureAllFaces no material item." << LL_ENDL; + return; + } + + // SL-20013 must save asset_id before handleDropMaterialProtections since our item instance + // may be deleted if it is moved into task inventory + LLUUID asset_id = item->getAssetUUID(); + bool success = handleDropMaterialProtections(hit_obj, item, source, src_id); + + if (!success) + { + return; + } + + if (asset_id.isNull()) + { + // use blank material + asset_id = LLGLTFMaterialList::BLANK_MATERIAL_ASSET_ID; + } + + hit_obj->setRenderMaterialIDs(asset_id); + dialog_refresh_all(); + // send the update to the simulator + hit_obj->sendTEUpdate(); +} + + +void LLToolDragAndDrop::dropMesh(LLViewerObject* hit_obj, + LLInventoryItem* item, + LLToolDragAndDrop::ESource source, + const LLUUID& src_id) +{ + if (!item) + { + LL_WARNS() << "no inventory item." << LL_ENDL; + return; + } + LLUUID asset_id = item->getAssetUUID(); + bool success = handleDropMaterialProtections(hit_obj, item, source, src_id); + if(!success) + { + return; + } + + LLSculptParams sculpt_params; + sculpt_params.setSculptTexture(asset_id, LL_SCULPT_TYPE_MESH); + hit_obj->setParameterEntry(LLNetworkData::PARAMS_SCULPT, sculpt_params, true); + + dialog_refresh_all(); +} + +void LLToolDragAndDrop::dropTexture(LLViewerObject* hit_obj, + S32 hit_face, + LLInventoryItem* item, + ESource source, + const LLUUID& src_id, + bool all_faces, + bool remove_pbr, + S32 tex_channel) +{ + LLSelectNode* nodep = nullptr; + if (hit_obj->isSelected()) + { + // update object's saved textures + nodep = LLSelectMgr::getInstance()->getSelection()->findNode(hit_obj); + } + + if (all_faces) + { + dropTextureAllFaces(hit_obj, item, source, src_id, remove_pbr); + + // If user dropped a texture onto face it implies + // applying texture now without cancel, save to selection + if (nodep) + { + uuid_vec_t texture_ids; + uuid_vec_t material_ids; + gltf_materials_vec_t override_materials; + S32 num_faces = hit_obj->getNumTEs(); + for (S32 face = 0; face < num_faces; face++) + { + LLViewerTexture* tex = hit_obj->getTEImage(face); + if (tex != nullptr) + { + texture_ids.push_back(tex->getID()); + } + else + { + texture_ids.push_back(LLUUID::null); + } + + // either removed or modified materials + if (remove_pbr) + { + material_ids.push_back(LLUUID::null); + } + else + { + material_ids.push_back(hit_obj->getRenderMaterialID(face)); + } + + LLTextureEntry* te = hit_obj->getTE(hit_face); + if (te && !remove_pbr) + { + override_materials.push_back(te->getGLTFMaterialOverride()); + } + else + { + override_materials.push_back(nullptr); + } + } + + nodep->saveTextures(texture_ids); + nodep->saveGLTFMaterials(material_ids, override_materials); + } + } + else + { + dropTextureOneFace(hit_obj, hit_face, item, source, src_id, remove_pbr, tex_channel); + + // If user dropped a texture onto face it implies + // applying texture now without cancel, save to selection + LLPanelFace* panel_face = gFloaterTools->getPanelFace(); + if (nodep + && gFloaterTools->getVisible() + && panel_face + && panel_face->getTextureDropChannel() == 0 /*texture*/ + && nodep->mSavedTextures.size() > hit_face) + { + LLViewerTexture* tex = hit_obj->getTEImage(hit_face); + if (tex != nullptr) + { + nodep->mSavedTextures[hit_face] = tex->getID(); + } + else + { + nodep->mSavedTextures[hit_face] = LLUUID::null; + } + + LLTextureEntry* te = hit_obj->getTE(hit_face); + if (te && !remove_pbr) + { + nodep->mSavedGLTFOverrideMaterials[hit_face] = te->getGLTFMaterialOverride(); + } + else + { + nodep->mSavedGLTFOverrideMaterials[hit_face] = nullptr; + } + } + } +} + +void LLToolDragAndDrop::dropTextureOneFace(LLViewerObject* hit_obj, + S32 hit_face, + LLInventoryItem* item, + LLToolDragAndDrop::ESource source, + const LLUUID& src_id, + bool remove_pbr, + S32 tex_channel) +{ + if (hit_face == -1) return; + if (!item) + { + LL_WARNS() << "LLToolDragAndDrop::dropTextureOneFace no texture item." << LL_ENDL; + return; + } + + LLUUID asset_id = item->getAssetUUID(); + + if (hit_obj->getRenderMaterialID(hit_face).notNull() && !remove_pbr) + { + // Overrides require textures to be copy and transfer free + LLPermissions item_permissions = item->getPermissions(); + bool allow_adding_to_override = item_permissions.allowOperationBy(PERM_COPY, gAgent.getID()); + allow_adding_to_override &= item_permissions.allowOperationBy(PERM_TRANSFER, gAgent.getID()); + + if (allow_adding_to_override) + { + LLGLTFMaterial::TextureInfo drop_channel = LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR; + LLPanelFace* panel_face = gFloaterTools->getPanelFace(); + if (gFloaterTools->getVisible() && panel_face) + { + drop_channel = panel_face->getPBRDropChannel(); + } + set_texture_to_material(hit_obj, hit_face, asset_id, drop_channel); + LLGLTFMaterialList::flushUpdates(nullptr); + } + return; + } + bool success = handleDropMaterialProtections(hit_obj, item, source, src_id); + if (!success) + { + return; + } + if (remove_pbr) + { + hit_obj->setRenderMaterialID(hit_face, LLUUID::null); + } + + // update viewer side image in anticipation of update from simulator + LLViewerTexture* image = LLViewerTextureManager::getFetchedTexture(asset_id); + add(LLStatViewer::EDIT_TEXTURE, 1); + + LLTextureEntry* tep = hit_obj->getTE(hit_face); + + LLPanelFace* panel_face = gFloaterTools->getPanelFace(); + + if (gFloaterTools->getVisible() && panel_face) + { + tex_channel = (tex_channel > -1) ? tex_channel : panel_face->getTextureDropChannel(); + switch (tex_channel) + { + + case 0: + default: + { + hit_obj->setTEImage(hit_face, image); + } + break; + + case 1: + if (tep) + { + LLMaterialPtr old_mat = tep->getMaterialParams(); + LLMaterialPtr new_mat = panel_face->createDefaultMaterial(old_mat); + new_mat->setNormalID(asset_id); + tep->setMaterialParams(new_mat); + hit_obj->setTENormalMap(hit_face, asset_id); + LLMaterialMgr::getInstance()->put(hit_obj->getID(), hit_face, *new_mat); + } + break; + + case 2: + if (tep) + { + LLMaterialPtr old_mat = tep->getMaterialParams(); + LLMaterialPtr new_mat = panel_face->createDefaultMaterial(old_mat); + new_mat->setSpecularID(asset_id); + tep->setMaterialParams(new_mat); + hit_obj->setTESpecularMap(hit_face, asset_id); + LLMaterialMgr::getInstance()->put(hit_obj->getID(), hit_face, *new_mat); + } + break; + } + } + else + { + hit_obj->setTEImage(hit_face, image); + } + + dialog_refresh_all(); + + // send the update to the simulator + hit_obj->sendTEUpdate(); +} + + +void LLToolDragAndDrop::dropScript(LLViewerObject* hit_obj, + LLInventoryItem* item, + bool active, + ESource source, + const LLUUID& src_id) +{ + // *HACK: In order to resolve SL-22177, we need to block drags + // from notecards and objects onto other objects. + if ((SOURCE_WORLD == LLToolDragAndDrop::getInstance()->mSource) + || (SOURCE_NOTECARD == LLToolDragAndDrop::getInstance()->mSource)) + { + LL_WARNS() << "Call to LLToolDragAndDrop::dropScript() from world" + << " or notecard." << LL_ENDL; + return; + } + if (hit_obj && item) + { + LLPointer new_script = new LLViewerInventoryItem(item); + if (!item->getPermissions().allowCopyBy(gAgent.getID())) + { + if (SOURCE_AGENT == source) + { + // Remove the script from local inventory. The server + // will actually remove the item from agent inventory. + gInventory.deleteObject(item->getUUID()); + gInventory.notifyObservers(); + } + else if (SOURCE_WORLD == source) + { + // *FIX: if the objects are in different regions, and + // the source region has crashed, you can bypass + // these permissions. + LLViewerObject* src_obj = gObjectList.findObject(src_id); + if (src_obj) + { + src_obj->removeInventory(item->getUUID()); + } + else + { + LL_WARNS() << "Unable to find source object." << LL_ENDL; + return; + } + } + } + hit_obj->saveScript(new_script, active, true); + gFloaterTools->dirty(); + + // VEFFECT: SetScript + LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM, true); + effectp->setSourceObject(gAgentAvatarp); + effectp->setTargetObject(hit_obj); + effectp->setDuration(LL_HUD_DUR_SHORT); + effectp->setColor(LLColor4U(gAgent.getEffectColor())); + } +} + +void LLToolDragAndDrop::dropObject(LLViewerObject* raycast_target, + bool bypass_sim_raycast, + bool from_task_inventory, + bool remove_from_inventory) +{ + LLViewerRegion* regionp = LLWorld::getInstance()->getRegionFromPosGlobal(mLastHitPos); + if (!regionp) + { + LL_WARNS() << "Couldn't find region to rez object" << LL_ENDL; + return; + } + + //LL_INFOS() << "Rezzing object" << LL_ENDL; + make_ui_sound("UISndObjectRezIn"); + LLViewerInventoryItem* item; + LLViewerInventoryCategory* cat; + locateInventory(item, cat); + if (!item || !item->isFinished()) return; + + //if (regionp + // && (regionp->getRegionFlag(REGION_FLAGS_SANDBOX))) + //{ + // LLFirstUse::useSandbox(); + //} + // check if it cannot be copied, and mark as remove if it is - + // this will remove the object from inventory after rez. Only + // bother with this check if we would not normally remove from + // inventory. + if (!remove_from_inventory + && !item->getPermissions().allowCopyBy(gAgent.getID())) + { + remove_from_inventory = true; + } + + // Limit raycast to a single object. + // Speeds up server raycast + avoid problems with server ray + // hitting objects that were clipped by the near plane or culled + // on the viewer. + LLUUID ray_target_id; + if (raycast_target) + { + ray_target_id = raycast_target->getID(); + } + else + { + ray_target_id.setNull(); + } + + // Check if it's in the trash. + bool is_in_trash = false; + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + if (gInventory.isObjectDescendentOf(item->getUUID(), trash_id)) + { + is_in_trash = true; + } + + LLUUID source_id = from_task_inventory ? mSourceID : LLUUID::null; + + // Select the object only if we're editing. + bool rez_selected = LLToolMgr::getInstance()->inEdit(); + + + LLVector3 ray_start = regionp->getPosRegionFromGlobal(mLastCameraPos); + LLVector3 ray_end = regionp->getPosRegionFromGlobal(mLastHitPos); + // currently the ray's end point is an approximation, + // and is sometimes too short (causing failure.) so we + // double the ray's length: + if (!bypass_sim_raycast) + { + LLVector3 ray_direction = ray_start - ray_end; + ray_end = ray_end - ray_direction; + } + + + // Message packing code should be it's own uninterrupted block + LLMessageSystem* msg = gMessageSystem; + if (mSource == SOURCE_NOTECARD) + { + LLUIUsage::instance().logCommand("Object.RezObjectFromNotecard"); + msg->newMessageFast(_PREHASH_RezObjectFromNotecard); + } + else + { + LLUIUsage::instance().logCommand("Object.RezObject"); + msg->newMessageFast(_PREHASH_RezObject); + } + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addUUIDFast(_PREHASH_GroupID, gAgent.getGroupID()); + + msg->nextBlock("RezData"); + // if it's being rezzed from task inventory, we need to enable + // saving it back into the task inventory. + // *FIX: We can probably compress this to a single byte, since I + // think folderid == mSourceID. This will be a later + // optimization. + msg->addUUIDFast(_PREHASH_FromTaskID, source_id); + msg->addU8Fast(_PREHASH_BypassRaycast, (U8) bypass_sim_raycast); + msg->addVector3Fast(_PREHASH_RayStart, ray_start); + msg->addVector3Fast(_PREHASH_RayEnd, ray_end); + msg->addUUIDFast(_PREHASH_RayTargetID, ray_target_id ); + msg->addBOOLFast(_PREHASH_RayEndIsIntersection, false); + msg->addBOOLFast(_PREHASH_RezSelected, rez_selected); + msg->addBOOLFast(_PREHASH_RemoveItem, remove_from_inventory); + + // deal with permissions slam logic + pack_permissions_slam(msg, item->getFlags(), item->getPermissions()); + + LLUUID folder_id = item->getParentUUID(); + if ((SOURCE_LIBRARY == mSource) || (is_in_trash)) + { + // since it's coming from the library or trash, we want to not + // 'take' it back to the same place. + item->setParent(LLUUID::null); + // *TODO this code isn't working - the parent (FolderID) is still + // set when the object is "taken". so code on the "take" side is + // checking for trash and library as well (llviewermenu.cpp) + } + if (mSource == SOURCE_NOTECARD) + { + msg->nextBlockFast(_PREHASH_NotecardData); + msg->addUUIDFast(_PREHASH_NotecardItemID, mSourceID); + msg->addUUIDFast(_PREHASH_ObjectID, mObjectID); + msg->nextBlockFast(_PREHASH_InventoryData); + msg->addUUIDFast(_PREHASH_ItemID, item->getUUID()); + } + else + { + msg->nextBlockFast(_PREHASH_InventoryData); + item->packMessage(msg); + } + msg->sendReliable(regionp->getHost()); + // back out the change. no actual internal changes take place. + item->setParent(folder_id); + + // If we're going to select it, get ready for the incoming + // selected object. + if (rez_selected) + { + LLSelectMgr::getInstance()->deselectAll(); + gViewerWindow->getWindow()->incBusyCount(); + } + + if (remove_from_inventory) + { + // Delete it from inventory immediately so that users cannot + // easily bypass copy protection in laggy situations. If the + // rez fails, we will put it back on the server. + gInventory.deleteObject(item->getUUID()); + gInventory.notifyObservers(); + } + + // VEFFECT: DropObject + LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM, true); + effectp->setSourceObject(gAgentAvatarp); + effectp->setPositionGlobal(mLastHitPos); + effectp->setDuration(LL_HUD_DUR_SHORT); + effectp->setColor(LLColor4U(gAgent.getEffectColor())); + + add(LLStatViewer::OBJECT_REZ, 1); +} + +void LLToolDragAndDrop::dropInventory(LLViewerObject* hit_obj, + LLInventoryItem* item, + LLToolDragAndDrop::ESource source, + const LLUUID& src_id) +{ + // *HACK: In order to resolve SL-22177, we need to block drags + // from notecards and objects onto other objects. + if ((SOURCE_WORLD == LLToolDragAndDrop::getInstance()->mSource) + || (SOURCE_NOTECARD == LLToolDragAndDrop::getInstance()->mSource)) + { + LL_WARNS() << "Call to LLToolDragAndDrop::dropInventory() from world" + << " or notecard." << LL_ENDL; + return; + } + + LLPointer new_item = new LLViewerInventoryItem(item); + time_t creation_date = time_corrected(); + new_item->setCreationDate(creation_date); + + if (!item->getPermissions().allowCopyBy(gAgent.getID())) + { + if (SOURCE_AGENT == source) + { + // Remove the inventory item from local inventory. The + // server will actually remove the item from agent + // inventory. + gInventory.deleteObject(item->getUUID()); + gInventory.notifyObservers(); + } + else if (SOURCE_WORLD == source) + { + // *FIX: if the objects are in different regions, and the + // source region has crashed, you can bypass these + // permissions. + LLViewerObject* src_obj = gObjectList.findObject(src_id); + if (src_obj) + { + src_obj->removeInventory(item->getUUID()); + } + else + { + LL_WARNS() << "Unable to find source object." << LL_ENDL; + return; + } + } + } + hit_obj->updateInventory(new_item, TASK_INVENTORY_ITEM_KEY, true); + if (LLFloaterReg::instanceVisible("build")) + { + // *FIX: only show this if panel not expanded? + LLFloaterReg::showInstance("build", "Content"); + } + + // VEFFECT: AddToInventory + LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM, true); + effectp->setSourceObject(gAgentAvatarp); + effectp->setTargetObject(hit_obj); + effectp->setDuration(LL_HUD_DUR_SHORT); + effectp->setColor(LLColor4U(gAgent.getEffectColor())); + gFloaterTools->dirty(); +} + +// accessor that looks at permissions, copyability, and names of +// inventory items to determine if a drop would be ok. +EAcceptance LLToolDragAndDrop::willObjectAcceptInventory(LLViewerObject* obj, LLInventoryItem* item, EDragAndDropType type) +{ + // check the basics + if (!item || !obj) + return ACCEPT_NO; + + // HACK: downcast + LLViewerInventoryItem* vitem = (LLViewerInventoryItem*)item; + if (!vitem->isFinished() && (type != DAD_CATEGORY)) + { + // Note: for DAD_CATEGORY we assume that folder version check passed and folder + // is complete, meaning that items inside are up to date. + // (isFinished() == false) at the moment shows that item was loaded from cache. + // Library or agent inventory only. + return ACCEPT_NO; + } + if (vitem->getIsLinkType()) + return ACCEPT_NO; // No giving away links + + // deny attempts to drop from an object onto itself. This is to + // help make sure that drops that are from an object to an object + // don't have to worry about order of evaluation. Think of this + // like check for self in assignment. + if (obj->getID() == item->getParentUUID()) + { + return ACCEPT_NO; + } + + //bool copy = (perm.allowCopyBy(gAgent.getID(), + // gAgent.getGroupID()) + // && (obj->mPermModify || obj->mFlagAllowInventoryAdd)); + bool worn = false; + LLVOAvatarSelf* my_avatar = NULL; + switch (item->getType()) + { + case LLAssetType::AT_OBJECT: + my_avatar = gAgentAvatarp; + if(my_avatar && my_avatar->isWearingAttachment(item->getUUID())) + { + worn = true; + } + break; + case LLAssetType::AT_BODYPART: + case LLAssetType::AT_CLOTHING: + if (gAgentWearables.isWearingItem(item->getUUID())) + { + worn = true; + } + break; + case LLAssetType::AT_CALLINGCARD: + // Calling Cards in object are disabled for now + // because of incomplete LSL support. See STORM-1117. + return ACCEPT_NO; + default: + break; + } + const LLPermissions& perm = item->getPermissions(); + bool modify = (obj->permModify() || obj->flagAllowInventoryAdd()); + bool transfer = false; + if ((obj->permYouOwner() && (perm.getOwner() == gAgent.getID())) + || perm.allowOperationBy(PERM_TRANSFER, gAgent.getID())) + { + transfer = true; + } + bool volume = (LL_PCODE_VOLUME == obj->getPCode()); + bool attached = obj->isAttachment(); + bool unrestricted = (perm.getMaskBase() & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED; + + if (attached && !unrestricted) + { + // Attachments are in world and in inventory simultaneously, + // at the moment server doesn't support such a situation. + return ACCEPT_NO_LOCKED; + } + + if (modify && transfer && volume && !worn) + { + return ACCEPT_YES_MULTI; + } + + if (!modify) + { + return ACCEPT_NO_LOCKED; + } + + return ACCEPT_NO; +} + + +static void give_inventory_cb(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + // if Cancel pressed + if (option == 1) + { + return; + } + + LLSD payload = notification["payload"]; + const LLUUID& session_id = payload["session_id"]; + const LLUUID& agent_id = payload["agent_id"]; + LLViewerInventoryItem * inv_item = gInventory.getItem(payload["item_id"]); + LLViewerInventoryCategory * inv_cat = gInventory.getCategory(payload["item_id"]); + if (NULL == inv_item && NULL == inv_cat) + { + llassert( false ); + return; + } + bool successfully_shared; + if (inv_item) + { + successfully_shared = LLGiveInventory::doGiveInventoryItem(agent_id, inv_item, session_id); + } + else + { + successfully_shared = LLGiveInventory::doGiveInventoryCategory(agent_id, inv_cat, session_id); + } + if (successfully_shared) + { + if ("avatarpicker" == payload["d&d_dest"].asString()) + { + LLFloaterReg::hideInstance("avatar_picker"); + } + LLNotificationsUtil::add("ItemsShared"); + } +} + +static void show_object_sharing_confirmation(const std::string name, + LLInventoryObject* inv_item, + const LLSD& dest, + const LLUUID& dest_agent, + const LLUUID& session_id = LLUUID::null) +{ + if (!inv_item) + { + llassert(NULL != inv_item); + return; + } + LLSD substitutions; + substitutions["RESIDENTS"] = name; + substitutions["ITEMS"] = inv_item->getName(); + LLSD payload; + payload["agent_id"] = dest_agent; + payload["item_id"] = inv_item->getUUID(); + payload["session_id"] = session_id; + payload["d&d_dest"] = dest.asString(); + LLNotificationsUtil::add("ShareItemsConfirmation", substitutions, payload, &give_inventory_cb); +} + +static void get_name_cb(const LLUUID& id, + const LLAvatarName& av_name, + LLInventoryObject* inv_obj, + const LLSD& dest, + const LLUUID& dest_agent) +{ + show_object_sharing_confirmation(av_name.getUserName(), + inv_obj, + dest, + id, + LLUUID::null); +} + +// function used as drag-and-drop handler for simple agent give inventory requests +//static +bool LLToolDragAndDrop::handleGiveDragAndDrop(LLUUID dest_agent, LLUUID session_id, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + const LLSD& dest) +{ + // check the type + switch(cargo_type) + { + case DAD_TEXTURE: + case DAD_SOUND: + case DAD_LANDMARK: + case DAD_SCRIPT: + case DAD_OBJECT: + case DAD_NOTECARD: + case DAD_CLOTHING: + case DAD_BODYPART: + case DAD_ANIMATION: + case DAD_GESTURE: + case DAD_CALLINGCARD: + case DAD_MESH: + case DAD_CATEGORY: + case DAD_SETTINGS: + case DAD_MATERIAL: + { + LLInventoryObject* inv_obj = (LLInventoryObject*)cargo_data; + if(gInventory.getCategory(inv_obj->getUUID()) || (gInventory.getItem(inv_obj->getUUID()) + && LLGiveInventory::isInventoryGiveAcceptable(dynamic_cast(inv_obj)))) + { + // *TODO: get multiple object transfers working + *accept = ACCEPT_YES_COPY_SINGLE; + if(drop) + { + LLIMModel::LLIMSession * session = LLIMModel::instance().findIMSession(session_id); + + // If no IM session found get the destination agent's name by id. + if (NULL == session) + { + LLAvatarName av_name; + + // If destination agent's name is found in cash proceed to showing the confirmation dialog. + // Otherwise set up a callback to show the dialog when the name arrives. + if (LLAvatarNameCache::get(dest_agent, &av_name)) + { + show_object_sharing_confirmation(av_name.getUserName(), inv_obj, dest, dest_agent, LLUUID::null); + } + else + { + LLAvatarNameCache::get(dest_agent, boost::bind(&get_name_cb, _1, _2, inv_obj, dest, dest_agent)); + } + + return true; + } + std::string dest_name = session->mName; + LLAvatarName av_name; + if(LLAvatarNameCache::get(dest_agent, &av_name)) + { + dest_name = av_name.getCompleteName(); + } + // If an IM session with destination agent is found item offer will be logged in this session. + show_object_sharing_confirmation(dest_name, inv_obj, dest, dest_agent, session_id); + } + } + else + { + // It's not in the user's inventory (it's probably + // in an object's contents), so disallow dragging + // it here. You can't give something you don't + // yet have. + *accept = ACCEPT_NO; + } + break; + } + default: + *accept = ACCEPT_NO; + break; + } + + return true; +} + + + +/// +/// Methods called in the drag & drop array +/// + +EAcceptance LLToolDragAndDrop::dad3dNULL( + LLViewerObject*, S32, MASK, bool) +{ + LL_DEBUGS() << "LLToolDragAndDrop::dad3dNULL()" << LL_ENDL; + return ACCEPT_NO; +} + +EAcceptance LLToolDragAndDrop::dad3dRezAttachmentFromInv( + LLViewerObject* obj, S32 face, MASK mask, bool drop) +{ + LL_DEBUGS() << "LLToolDragAndDrop::dad3dRezAttachmentFromInv()" << LL_ENDL; + // must be in the user's inventory + if(mSource != SOURCE_AGENT && mSource != SOURCE_LIBRARY) + { + return ACCEPT_NO; + } + + LLViewerInventoryItem* item; + LLViewerInventoryCategory* cat; + locateInventory(item, cat); + if (!item || !item->isFinished()) return ACCEPT_NO; + + // must not be in the trash + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + if( gInventory.isObjectDescendentOf( item->getUUID(), trash_id ) ) + { + return ACCEPT_NO; + } + + // must not be already wearing it + LLVOAvatarSelf* avatar = gAgentAvatarp; + if( !avatar || avatar->isWearingAttachment(item->getUUID()) ) + { + return ACCEPT_NO; + } + + const LLUUID &outbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_OUTBOX); + if(outbox_id.notNull() && gInventory.isObjectDescendentOf(item->getUUID(), outbox_id)) + { + // Legacy + return ACCEPT_NO; + } + + + if( drop ) + { + if(mSource == SOURCE_LIBRARY) + { + LLPointer cb = new LLBoostFuncInventoryCallback(boost::bind(rez_attachment_cb, _1, (LLViewerJointAttachment*)0)); + copy_inventory_item( + gAgent.getID(), + item->getPermissions().getOwner(), + item->getUUID(), + LLUUID::null, + std::string(), + cb); + } + else + { + rez_attachment(item, 0); + } + } + return ACCEPT_YES_SINGLE; +} + + +EAcceptance LLToolDragAndDrop::dad3dRezObjectOnLand( + LLViewerObject* obj, S32 face, MASK mask, bool drop) +{ + if (mSource == SOURCE_WORLD) + { + return dad3dRezFromObjectOnLand(obj, face, mask, drop); + } + + LL_DEBUGS() << "LLToolDragAndDrop::dad3dRezObjectOnLand()" << LL_ENDL; + LLViewerInventoryItem* item; + LLViewerInventoryCategory* cat; + locateInventory(item, cat); + if (!item || !item->isFinished()) return ACCEPT_NO; + + LLVOAvatarSelf* my_avatar = gAgentAvatarp; + if( !my_avatar || my_avatar->isWearingAttachment( item->getUUID() ) ) + { + return ACCEPT_NO; + } + + EAcceptance accept; + bool remove_inventory; + + // Get initial settings based on shift key + if (mask & MASK_SHIFT) + { + // For now, always make copy + //accept = ACCEPT_YES_SINGLE; + //remove_inventory = true; + accept = ACCEPT_YES_COPY_SINGLE; + remove_inventory = false; + } + else + { + accept = ACCEPT_YES_COPY_SINGLE; + remove_inventory = false; + } + + // check if the item can be copied. If not, send that to the sim + // which will remove the inventory item. + if(!item->getPermissions().allowCopyBy(gAgent.getID())) + { + accept = ACCEPT_YES_SINGLE; + remove_inventory = true; + } + + // Check if it's in the trash. + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + if(gInventory.isObjectDescendentOf(item->getUUID(), trash_id)) + { + accept = ACCEPT_YES_SINGLE; + } + + if(drop) + { + dropObject(obj, true, false, remove_inventory); + } + + return accept; +} + +EAcceptance LLToolDragAndDrop::dad3dRezObjectOnObject( + LLViewerObject* obj, S32 face, MASK mask, bool drop) +{ + // handle objects coming from object inventory + if (mSource == SOURCE_WORLD) + { + return dad3dRezFromObjectOnObject(obj, face, mask, drop); + } + + LL_DEBUGS() << "LLToolDragAndDrop::dad3dRezObjectOnObject()" << LL_ENDL; + LLViewerInventoryItem* item; + LLViewerInventoryCategory* cat; + locateInventory(item, cat); + if (!item || !item->isFinished()) return ACCEPT_NO; + LLVOAvatarSelf* my_avatar = gAgentAvatarp; + if( !my_avatar || my_avatar->isWearingAttachment( item->getUUID() ) ) + { + return ACCEPT_NO; + } + + if((mask & MASK_CONTROL)) + { + // *HACK: In order to resolve SL-22177, we need to block drags + // from notecards and objects onto other objects. + if(mSource == SOURCE_NOTECARD) + { + return ACCEPT_NO; + } + + EAcceptance rv = willObjectAcceptInventory(obj, item); + if(drop && (ACCEPT_YES_SINGLE <= rv)) + { + dropInventory(obj, item, mSource, mSourceID); + } + return rv; + } + + EAcceptance accept; + bool remove_inventory; + + if (mask & MASK_SHIFT) + { + // For now, always make copy + //accept = ACCEPT_YES_SINGLE; + //remove_inventory = true; + accept = ACCEPT_YES_COPY_SINGLE; + remove_inventory = false; + } + else + { + accept = ACCEPT_YES_COPY_SINGLE; + remove_inventory = false; + } + + // check if the item can be copied. If not, send that to the sim + // which will remove the inventory item. + if(!item->getPermissions().allowCopyBy(gAgent.getID())) + { + accept = ACCEPT_YES_SINGLE; + remove_inventory = true; + } + + // Check if it's in the trash. + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + if(gInventory.isObjectDescendentOf(item->getUUID(), trash_id)) + { + accept = ACCEPT_YES_SINGLE; + remove_inventory = true; + } + + if(drop) + { + dropObject(obj, false, false, remove_inventory); + } + + return accept; +} + +EAcceptance LLToolDragAndDrop::dad3dRezScript( + LLViewerObject* obj, S32 face, MASK mask, bool drop) +{ + LL_DEBUGS() << "LLToolDragAndDrop::dad3dRezScript()" << LL_ENDL; + + // *HACK: In order to resolve SL-22177, we need to block drags + // from notecards and objects onto other objects. + if((SOURCE_WORLD == mSource) || (SOURCE_NOTECARD == mSource)) + { + return ACCEPT_NO; + } + + LLViewerInventoryItem* item; + LLViewerInventoryCategory* cat; + locateInventory(item, cat); + if (!item || !item->isFinished()) return ACCEPT_NO; + EAcceptance rv = willObjectAcceptInventory(obj, item); + if(drop && (ACCEPT_YES_SINGLE <= rv)) + { + // rez in the script active by default, rez in inactive if the + // control key is being held down. + bool active = ((mask & MASK_CONTROL) == 0); + + LLViewerObject* root_object = obj; + if (obj && obj->getParent()) + { + LLViewerObject* parent_obj = (LLViewerObject*)obj->getParent(); + if (!parent_obj->isAvatar()) + { + root_object = parent_obj; + } + } + + dropScript(root_object, item, active, mSource, mSourceID); + } + return rv; +} + +EAcceptance LLToolDragAndDrop::dad3dApplyToObject( + LLViewerObject* obj, S32 face, MASK mask, bool drop, EDragAndDropType cargo_type) +{ + LL_DEBUGS() << "LLToolDragAndDrop::dad3dApplyToObject()" << LL_ENDL; + + // *HACK: In order to resolve SL-22177, we need to block drags + // from notecards and objects onto other objects. + if((SOURCE_WORLD == mSource) || (SOURCE_NOTECARD == mSource)) + { + return ACCEPT_NO; + } + + LLViewerInventoryItem* item; + LLViewerInventoryCategory* cat; + locateInventory(item, cat); + if (!item || !item->isFinished()) return ACCEPT_NO; + LLPermissions item_permissions = item->getPermissions(); + EAcceptance rv = willObjectAcceptInventory(obj, item); + if((mask & MASK_CONTROL)) + { + if((ACCEPT_YES_SINGLE <= rv) && drop) + { + dropInventory(obj, item, mSource, mSourceID); + } + return rv; + } + if(!obj->permModify()) + { + return ACCEPT_NO_LOCKED; + } + + if (cargo_type == DAD_TEXTURE && (mask & MASK_ALT) == 0) + { + bool has_non_pbr_faces = false; + if ((mask & MASK_SHIFT)) + { + S32 num_faces = obj->getNumTEs(); + for (S32 face = 0; face < num_faces; face++) + { + if (obj->getRenderMaterialID(face).isNull()) + { + has_non_pbr_faces = true; + break; + } + } + } + else + { + has_non_pbr_faces = obj->getRenderMaterialID(face).isNull(); + } + + if (!has_non_pbr_faces) + { + // Only pbr faces selected, texture will be added to an override + // Overrides require textures to be copy and transfer free + bool allow_adding_to_override = item_permissions.allowOperationBy(PERM_COPY, gAgent.getID()); + allow_adding_to_override &= item_permissions.allowOperationBy(PERM_TRANSFER, gAgent.getID()); + if (!allow_adding_to_override) return ACCEPT_NO; + } + } + + if(drop && (ACCEPT_YES_SINGLE <= rv)) + { + if (cargo_type == DAD_TEXTURE) + { + bool all_faces = mask & MASK_SHIFT; + bool remove_pbr = mask & MASK_ALT; + if (item_permissions.allowOperationBy(PERM_COPY, gAgent.getID())) + { + dropTexture(obj, face, item, mSource, mSourceID, all_faces, remove_pbr); + } + else + { + ESource source = mSource; + LLUUID source_id = mSourceID; + LLNotificationsUtil::add("ApplyInventoryToObject", LLSD(), LLSD(), [obj, face, item, source, source_id, all_faces, remove_pbr](const LLSD& notification, const LLSD& response) + { + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + // if Cancel pressed + if (option == 1) + { + return; + } + dropTexture(obj, face, item, source, source_id, all_faces, remove_pbr); + }); + } + } + else if (cargo_type == DAD_MATERIAL) + { + bool all_faces = mask & MASK_SHIFT; + if (item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID())) + { + dropMaterial(obj, face, item, mSource, mSourceID, all_faces); + } + else + { + ESource source = mSource; + LLUUID source_id = mSourceID; + LLNotificationsUtil::add("ApplyInventoryToObject", LLSD(), LLSD(), [obj, face, item, source, source_id, all_faces](const LLSD& notification, const LLSD& response) + { + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + // if Cancel pressed + if (option == 1) + { + return; + } + dropMaterial(obj, face, item, source, source_id, all_faces); + }); + } + } + else if (cargo_type == DAD_MESH) + { + dropMesh(obj, item, mSource, mSourceID); + } + else + { + LL_WARNS() << "unsupported asset type" << LL_ENDL; + } + + // VEFFECT: SetTexture + LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM, true); + effectp->setSourceObject(gAgentAvatarp); + effectp->setTargetObject(obj); + effectp->setDuration(LL_HUD_DUR_SHORT); + effectp->setColor(LLColor4U(gAgent.getEffectColor())); + } + + // enable multi-drop, although last texture will win + return ACCEPT_YES_MULTI; +} + + +EAcceptance LLToolDragAndDrop::dad3dTextureObject( + LLViewerObject* obj, S32 face, MASK mask, bool drop) +{ + return dad3dApplyToObject(obj, face, mask, drop, DAD_TEXTURE); +} + +EAcceptance LLToolDragAndDrop::dad3dMaterialObject( + LLViewerObject* obj, S32 face, MASK mask, bool drop) +{ + return dad3dApplyToObject(obj, face, mask, drop, DAD_MATERIAL); +} + +EAcceptance LLToolDragAndDrop::dad3dMeshObject( + LLViewerObject* obj, S32 face, MASK mask, bool drop) +{ + return dad3dApplyToObject(obj, face, mask, drop, DAD_MESH); +} + + +/* +EAcceptance LLToolDragAndDrop::dad3dTextureSelf( + LLViewerObject* obj, S32 face, MASK mask, bool drop) +{ + LL_DEBUGS() << "LLToolDragAndDrop::dad3dTextureAvatar()" << LL_ENDL; + if(drop) + { + if( !(mask & MASK_SHIFT) ) + { + dropTextureOneFaceAvatar( (LLVOAvatar*)obj, face, (LLInventoryItem*)mCargoData); + } + } + return (mask & MASK_SHIFT) ? ACCEPT_NO : ACCEPT_YES_SINGLE; +} +*/ + +EAcceptance LLToolDragAndDrop::dad3dWearItem( + LLViewerObject* obj, S32 face, MASK mask, bool drop) +{ + LL_DEBUGS() << "LLToolDragAndDrop::dad3dWearItem()" << LL_ENDL; + LLViewerInventoryItem* item; + LLViewerInventoryCategory* cat; + locateInventory(item, cat); + if (!item || !item->isFinished()) return ACCEPT_NO; + + if(mSource == SOURCE_AGENT || mSource == SOURCE_LIBRARY) + { + // it's in the agent inventory + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + if( gInventory.isObjectDescendentOf( item->getUUID(), trash_id ) ) + { + return ACCEPT_NO; + } + + if( drop ) + { + // TODO: investigate wearables may not be loaded at this point EXT-8231 + + LLAppearanceMgr::instance().wearItemOnAvatar(item->getUUID(),true, !(mask & MASK_CONTROL)); + } + return ACCEPT_YES_MULTI; + } + else + { + // TODO: copy/move item to avatar's inventory and then wear it. + return ACCEPT_NO; + } +} + +EAcceptance LLToolDragAndDrop::dad3dActivateGesture( + LLViewerObject* obj, S32 face, MASK mask, bool drop) +{ + LL_DEBUGS() << "LLToolDragAndDrop::dad3dActivateGesture()" << LL_ENDL; + LLViewerInventoryItem* item; + LLViewerInventoryCategory* cat; + locateInventory(item, cat); + if (!item || !item->isFinished()) return ACCEPT_NO; + + if(mSource == SOURCE_AGENT || mSource == SOURCE_LIBRARY) + { + // it's in the agent inventory + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + if( gInventory.isObjectDescendentOf( item->getUUID(), trash_id ) ) + { + return ACCEPT_NO; + } + + if( drop ) + { + LLUUID item_id; + if(mSource == SOURCE_LIBRARY) + { + // create item based on that one, and put it on if that + // was a success. + LLPointer cb = new LLBoostFuncInventoryCallback(activate_gesture_cb); + copy_inventory_item( + gAgent.getID(), + item->getPermissions().getOwner(), + item->getUUID(), + LLUUID::null, + std::string(), + cb); + } + else + { + LLGestureMgr::instance().activateGesture(item->getUUID()); + gInventory.updateItem(item); + gInventory.notifyObservers(); + } + } + return ACCEPT_YES_MULTI; + } + else + { + return ACCEPT_NO; + } +} + +EAcceptance LLToolDragAndDrop::dad3dWearCategory( + LLViewerObject* obj, S32 face, MASK mask, bool drop) +{ + LL_DEBUGS() << "LLToolDragAndDrop::dad3dWearCategory()" << LL_ENDL; + LLViewerInventoryItem* item; + LLViewerInventoryCategory* category; + locateInventory(item, category); + if(!category) return ACCEPT_NO; + + if (drop) + { + // TODO: investigate wearables may not be loaded at this point EXT-8231 + } + + U32 max_items = gSavedSettings.getU32("WearFolderLimit"); + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLFindWearablesEx not_worn(/*is_worn=*/ false, /*include_body_parts=*/ false); + gInventory.collectDescendentsIf(category->getUUID(), + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + not_worn); + if (items.size() > max_items) + { + LLStringUtil::format_map_t args; + args["AMOUNT"] = llformat("%d", max_items); + mCustomMsg = LLTrans::getString("TooltipTooManyWearables",args); + return ACCEPT_NO_CUSTOM; + } + + if (mSource == SOURCE_AGENT) + { + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + if (gInventory.isObjectDescendentOf(category->getUUID(), trash_id)) + { + return ACCEPT_NO; + } + + const LLUUID &outbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_OUTBOX); + if (outbox_id.notNull() && gInventory.isObjectDescendentOf(category->getUUID(), outbox_id)) + { + // Legacy + return ACCEPT_NO; + } + + if (drop) + { + LLAppearanceMgr::instance().wearInventoryCategory(category, false, mask & MASK_SHIFT); + } + + return ACCEPT_YES_MULTI; + } + + if (mSource == SOURCE_LIBRARY) + { + if (drop) + { + LLAppearanceMgr::instance().wearInventoryCategory(category, true, false); + } + + return ACCEPT_YES_MULTI; + } + + // TODO: copy/move category to avatar's inventory and then wear it. + return ACCEPT_NO; +} + + +EAcceptance LLToolDragAndDrop::dad3dUpdateInventory( + LLViewerObject* obj, S32 face, MASK mask, bool drop) +{ + LL_DEBUGS() << "LLToolDragAndDrop::dadUpdateInventory()" << LL_ENDL; + + // *HACK: In order to resolve SL-22177, we need to block drags + // from notecards and objects onto other objects. + if((SOURCE_WORLD == mSource) || (SOURCE_NOTECARD == mSource)) + { + return ACCEPT_NO; + } + + LLViewerInventoryItem* item; + LLViewerInventoryCategory* cat; + locateInventory(item, cat); + if (!item || !item->isFinished()) + return ACCEPT_NO; + + LLViewerObject* root_object = obj; + if (obj && obj->getParent()) + { + LLViewerObject* parent_obj = (LLViewerObject*)obj->getParent(); + if (!parent_obj->isAvatar()) + { + root_object = parent_obj; + } + } + + EAcceptance rv = willObjectAcceptInventory(root_object, item); + if (root_object && drop && (ACCEPT_YES_COPY_SINGLE <= rv)) + { + dropInventory(root_object, item, mSource, mSourceID); + } + + return rv; +} + +bool LLToolDragAndDrop::dadUpdateInventory(LLViewerObject* obj, bool drop) +{ + EAcceptance rv = dad3dUpdateInventory(obj, -1, MASK_NONE, drop); + return rv >= ACCEPT_YES_COPY_SINGLE; +} + +EAcceptance LLToolDragAndDrop::dad3dUpdateInventoryCategory( + LLViewerObject* obj, S32 face, MASK mask, bool drop) +{ + LL_DEBUGS() << "LLToolDragAndDrop::dad3dUpdateInventoryCategory()" << LL_ENDL; + if (obj == NULL) + { + LL_WARNS() << "obj is NULL; aborting func with ACCEPT_NO" << LL_ENDL; + return ACCEPT_NO; + } + + if ((mSource != SOURCE_AGENT) && (mSource != SOURCE_LIBRARY)) + { + return ACCEPT_NO; + } + if (obj->isAttachment()) + { + return ACCEPT_NO_LOCKED; + } + + LLViewerInventoryItem* item = NULL; + LLViewerInventoryCategory* cat = NULL; + locateInventory(item, cat); + if (!cat) + { + return ACCEPT_NO; + } + + // Find all the items in the category + LLDroppableItem droppable(!obj->permYouOwner()); + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + gInventory.collectDescendentsIf(cat->getUUID(), + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + droppable); + cats.push_back(cat); + if (droppable.countNoCopy() > 0) + { + LL_WARNS() << "*** Need to confirm this step" << LL_ENDL; + } + LLViewerObject* root_object = obj; + if (obj->getParent()) + { + LLViewerObject* parent_obj = (LLViewerObject*)obj->getParent(); + if (!parent_obj->isAvatar()) + { + root_object = parent_obj; + } + } + + EAcceptance rv = ACCEPT_NO; + + // Check for accept + for (LLInventoryModel::cat_array_t::const_iterator cat_iter = cats.begin(); + cat_iter != cats.end(); + ++cat_iter) + { + const LLViewerInventoryCategory *cat = (*cat_iter); + rv = gInventory.isCategoryComplete(cat->getUUID()) ? ACCEPT_YES_MULTI : ACCEPT_NO; + if(rv < ACCEPT_YES_SINGLE) + { + LL_DEBUGS() << "Category " << cat->getUUID() << "is not complete." << LL_ENDL; + break; + } + } + if (ACCEPT_YES_COPY_SINGLE <= rv) + { + for (LLInventoryModel::item_array_t::const_iterator item_iter = items.begin(); + item_iter != items.end(); + ++item_iter) + { + LLViewerInventoryItem *item = (*item_iter); + /* + // Pass the base objects, not the links. + if (item && item->getIsLinkType()) + { + item = item->getLinkedItem(); + (*item_iter) = item; + } + */ + rv = willObjectAcceptInventory(root_object, item, DAD_CATEGORY); + if (rv < ACCEPT_YES_COPY_SINGLE) + { + LL_DEBUGS() << "Object will not accept " << item->getUUID() << LL_ENDL; + break; + } + } + } + + // If every item is accepted, send it on + if (drop && (ACCEPT_YES_COPY_SINGLE <= rv)) + { + uuid_vec_t ids; + for (LLInventoryModel::item_array_t::const_iterator item_iter = items.begin(); + item_iter != items.end(); + ++item_iter) + { + const LLViewerInventoryItem *item = (*item_iter); + ids.push_back(item->getUUID()); + } + LLCategoryDropObserver* dropper = new LLCategoryDropObserver(ids, obj->getID(), mSource); + dropper->startFetch(); + if (dropper->isFinished()) + { + dropper->done(); + } + else + { + gInventory.addObserver(dropper); + } + } + return rv; +} + + +EAcceptance LLToolDragAndDrop::dad3dRezCategoryOnObject( + LLViewerObject* obj, S32 face, MASK mask, bool drop) +{ + if ((mask & MASK_CONTROL)) + { + return dad3dUpdateInventoryCategory(obj, face, mask, drop); + } + else + { + return ACCEPT_NO; + } +} + + +bool LLToolDragAndDrop::dadUpdateInventoryCategory(LLViewerObject* obj, + bool drop) +{ + EAcceptance rv = dad3dUpdateInventoryCategory(obj, -1, MASK_NONE, drop); + return (rv >= ACCEPT_YES_COPY_SINGLE); +} + +EAcceptance LLToolDragAndDrop::dad3dGiveInventoryObject( + LLViewerObject* obj, S32 face, MASK mask, bool drop) +{ + LL_DEBUGS() << "LLToolDragAndDrop::dad3dGiveInventoryObject()" << LL_ENDL; + + // item has to be in agent inventory. + if(mSource != SOURCE_AGENT) return ACCEPT_NO; + + // find the item now. + LLViewerInventoryItem* item; + LLViewerInventoryCategory* cat; + locateInventory(item, cat); + if (!item || !item->isFinished()) return ACCEPT_NO; + if(!item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID())) + { + // cannot give away no-transfer objects + return ACCEPT_NO; + } + LLVOAvatarSelf* avatar = gAgentAvatarp; + if(avatar && avatar->isWearingAttachment( item->getUUID() ) ) + { + // You can't give objects that are attached to you + return ACCEPT_NO; + } + if( obj && avatar ) + { + if(drop) + { + LLGiveInventory::doGiveInventoryItem(obj->getID(), item ); + } + // *TODO: deal with all the issues surrounding multi-object + // inventory transfers. + return ACCEPT_YES_SINGLE; + } + return ACCEPT_NO; +} + + +EAcceptance LLToolDragAndDrop::dad3dGiveInventory( + LLViewerObject* obj, S32 face, MASK mask, bool drop) +{ + LL_DEBUGS() << "LLToolDragAndDrop::dad3dGiveInventory()" << LL_ENDL; + // item has to be in agent inventory. + if(mSource != SOURCE_AGENT) return ACCEPT_NO; + LLViewerInventoryItem* item; + LLViewerInventoryCategory* cat; + locateInventory(item, cat); + if (!item || !item->isFinished()) return ACCEPT_NO; + if (!LLGiveInventory::isInventoryGiveAcceptable(item)) + { + return ACCEPT_NO; + } + if (drop && obj) + { + LLGiveInventory::doGiveInventoryItem(obj->getID(), item); + } + // *TODO: deal with all the issues surrounding multi-object + // inventory transfers. + return ACCEPT_YES_SINGLE; +} + +EAcceptance LLToolDragAndDrop::dad3dGiveInventoryCategory( + LLViewerObject* obj, S32 face, MASK mask, bool drop) +{ + LL_DEBUGS() << "LLToolDragAndDrop::dad3dGiveInventoryCategory()" << LL_ENDL; + if(drop && obj) + { + LLViewerInventoryItem* item; + LLViewerInventoryCategory* cat; + locateInventory(item, cat); + if(!cat) return ACCEPT_NO; + LLGiveInventory::doGiveInventoryCategory(obj->getID(), cat); + } + // *TODO: deal with all the issues surrounding multi-object + // inventory transfers. + return ACCEPT_YES_SINGLE; +} + + +EAcceptance LLToolDragAndDrop::dad3dRezFromObjectOnLand( + LLViewerObject* obj, S32 face, MASK mask, bool drop) +{ + LL_DEBUGS() << "LLToolDragAndDrop::dad3dRezFromObjectOnLand()" << LL_ENDL; + LLViewerInventoryItem* item = NULL; + LLViewerInventoryCategory* cat = NULL; + locateInventory(item, cat); + if (!item || !item->isFinished()) return ACCEPT_NO; + + if(!gAgent.allowOperation(PERM_COPY, item->getPermissions()) + || !item->getPermissions().allowTransferTo(LLUUID::null)) + { + return ACCEPT_NO_LOCKED; + } + if(drop) + { + dropObject(obj, true, true, false); + } + return ACCEPT_YES_SINGLE; +} + +EAcceptance LLToolDragAndDrop::dad3dRezFromObjectOnObject( + LLViewerObject* obj, S32 face, MASK mask, bool drop) +{ + LL_DEBUGS() << "LLToolDragAndDrop::dad3dRezFromObjectOnObject()" << LL_ENDL; + LLViewerInventoryItem* item; + LLViewerInventoryCategory* cat; + locateInventory(item, cat); + if (!item || !item->isFinished()) return ACCEPT_NO; + if((mask & MASK_CONTROL)) + { + // *HACK: In order to resolve SL-22177, we need to block drags + // from notecards and objects onto other objects. + return ACCEPT_NO; + + // *HACK: uncomment this when appropriate + //EAcceptance rv = willObjectAcceptInventory(obj, item); + //if(drop && (ACCEPT_YES_SINGLE <= rv)) + //{ + // dropInventory(obj, item, mSource, mSourceID); + //} + //return rv; + } + if(!item->getPermissions().allowCopyBy(gAgent.getID(), + gAgent.getGroupID()) + || !item->getPermissions().allowTransferTo(LLUUID::null)) + { + return ACCEPT_NO_LOCKED; + } + if(drop) + { + dropObject(obj, false, true, false); + } + return ACCEPT_YES_SINGLE; +} + +EAcceptance LLToolDragAndDrop::dad3dCategoryOnLand( + LLViewerObject *obj, S32 face, MASK mask, bool drop) +{ + return ACCEPT_NO; + /* + LL_DEBUGS() << "LLToolDragAndDrop::dad3dCategoryOnLand()" << LL_ENDL; + LLInventoryItem* item; + LLInventoryCategory* cat; + locateInventory(item, cat); + if(!cat) return ACCEPT_NO; + EAcceptance rv = ACCEPT_NO; + + // find all the items in the category + LLViewerInventoryCategory::cat_array_t cats; + LLViewerInventoryItem::item_array_t items; + LLDropCopyableItems droppable; + gInventory.collectDescendentsIf(cat->getUUID(), + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + droppable); + if(items.size() > 0) + { + rv = ACCEPT_YES_SINGLE; + } + if((rv >= ACCEPT_YES_COPY_SINGLE) && drop) + { + createContainer(items, cat->getName()); + return ACCEPT_NO; + } + return rv; + */ +} + + +// This is based on ALOT of copied, special-cased code +// This shortcuts alot of steps to make a basic object +// w/ an inventory and a special permissions set +EAcceptance LLToolDragAndDrop::dad3dAssetOnLand( + LLViewerObject *obj, S32 face, MASK mask, bool drop) +{ + return ACCEPT_NO; + /* + LL_DEBUGS() << "LLToolDragAndDrop::dad3dAssetOnLand()" << LL_ENDL; + LLViewerInventoryCategory::cat_array_t cats; + LLViewerInventoryItem::item_array_t items; + LLViewerInventoryItem::item_array_t copyable_items; + locateMultipleInventory(items, cats); + if(!items.size()) return ACCEPT_NO; + EAcceptance rv = ACCEPT_NO; + for (S32 i = 0; i < items.size(); i++) + { + LLInventoryItem* item = items[i]; + if(item->getPermissions().allowCopyBy(gAgent.getID())) + { + copyable_items.push_back(item); + rv = ACCEPT_YES_SINGLE; + } + } + + if((rv >= ACCEPT_YES_COPY_SINGLE) && drop) + { + createContainer(copyable_items, NULL); + } + + return rv; + */ +} + +LLInventoryObject* LLToolDragAndDrop::locateInventory( + LLViewerInventoryItem*& item, + LLViewerInventoryCategory*& cat) +{ + item = NULL; + cat = NULL; + + if (mCargoIDs.empty() + || (mSource == SOURCE_PEOPLE)) ///< There is no inventory item for people drag and drop. + { + return NULL; + } + + if((mSource == SOURCE_AGENT) || (mSource == SOURCE_LIBRARY)) + { + // The object should be in user inventory. + item = (LLViewerInventoryItem*)gInventory.getItem(mCargoIDs[mCurItemIndex]); + cat = (LLViewerInventoryCategory*)gInventory.getCategory(mCargoIDs[mCurItemIndex]); + } + else if(mSource == SOURCE_WORLD) + { + // This object is in some task inventory somewhere. + LLViewerObject* obj = gObjectList.findObject(mSourceID); + if(obj) + { + if((mCargoTypes[mCurItemIndex] == DAD_CATEGORY) + || (mCargoTypes[mCurItemIndex] == DAD_ROOT_CATEGORY)) + { + cat = (LLViewerInventoryCategory*)obj->getInventoryObject(mCargoIDs[mCurItemIndex]); + } + else + { + item = (LLViewerInventoryItem*)obj->getInventoryObject(mCargoIDs[mCurItemIndex]); + } + } + } + else if(mSource == SOURCE_NOTECARD) + { + LLPreviewNotecard* preview = LLFloaterReg::findTypedInstance("preview_notecard", mSourceID); + if(preview) + { + item = (LLViewerInventoryItem*)preview->getDragItem(); + } + } + else if(mSource == SOURCE_VIEWER) + { + item = (LLViewerInventoryItem*)gToolBarView->getDragItem(); + } + + if(item) return item; + if(cat) return cat; + return NULL; +} + +/* +LLInventoryObject* LLToolDragAndDrop::locateMultipleInventory(LLViewerInventoryCategory::cat_array_t& cats, + LLViewerInventoryItem::item_array_t& items) +{ + if(mCargoIDs.size() == 0) return NULL; + if((mSource == SOURCE_AGENT) || (mSource == SOURCE_LIBRARY)) + { + // The object should be in user inventory. + for (S32 i = 0; i < mCargoIDs.size(); i++) + { + LLInventoryItem* item = gInventory.getItem(mCargoIDs[i]); + if (item) + { + items.push_back(item); + } + LLInventoryCategory* category = gInventory.getCategory(mCargoIDs[i]); + if (category) + { + cats.push_back(category); + } + } + } + else if(mSource == SOURCE_WORLD) + { + // This object is in some task inventory somewhere. + LLViewerObject* obj = gObjectList.findObject(mSourceID); + if(obj) + { + if((mCargoType == DAD_CATEGORY) + || (mCargoType == DAD_ROOT_CATEGORY)) + { + // The object should be in user inventory. + for (S32 i = 0; i < mCargoIDs.size(); i++) + { + LLInventoryCategory* category = (LLInventoryCategory*)obj->getInventoryObject(mCargoIDs[i]); + if (category) + { + cats.push_back(category); + } + } + } + else + { + for (S32 i = 0; i < mCargoIDs.size(); i++) + { + LLInventoryItem* item = (LLInventoryItem*)obj->getInventoryObject(mCargoIDs[i]); + if (item) + { + items.push_back(item); + } + } + } + } + } + else if(mSource == SOURCE_NOTECARD) + { + LLPreviewNotecard* card; + card = (LLPreviewNotecard*)LLPreview::find(mSourceID); + if(card) + { + items.push_back((LLInventoryItem*)card->getDragItem()); + } + } + if(items.size()) return items[0]; + if(cats.size()) return cats[0]; + return NULL; +} +*/ + +// void LLToolDragAndDrop::createContainer(LLViewerInventoryItem::item_array_t &items, const char* preferred_name ) +// { +// LL_WARNS() << "LLToolDragAndDrop::createContainer()" << LL_ENDL; +// return; +// } + + +// utility functions + +void pack_permissions_slam(LLMessageSystem* msg, U32 flags, const LLPermissions& perms) +{ + // CRUFT -- the server no longer pays attention to this data + U32 group_mask = perms.getMaskGroup(); + U32 everyone_mask = perms.getMaskEveryone(); + U32 next_owner_mask = perms.getMaskNextOwner(); + + msg->addU32Fast(_PREHASH_ItemFlags, flags); + msg->addU32Fast(_PREHASH_GroupMask, group_mask); + msg->addU32Fast(_PREHASH_EveryoneMask, everyone_mask); + msg->addU32Fast(_PREHASH_NextOwnerMask, next_owner_mask); +} diff --git a/indra/newview/lltooldraganddrop.h b/indra/newview/lltooldraganddrop.h index 8406928f18..364319dc56 100644 --- a/indra/newview/lltooldraganddrop.h +++ b/indra/newview/lltooldraganddrop.h @@ -1,323 +1,323 @@ -/** - * @file lltooldraganddrop.h - * @brief LLToolDragAndDrop class header file - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_TOOLDRAGANDDROP_H -#define LL_TOOLDRAGANDDROP_H - -#include "lldictionary.h" -#include "lltool.h" -#include "llview.h" -#include "lluuid.h" -#include "llassetstorage.h" -#include "llpermissions.h" -#include "llwindow.h" -#include "llviewerinventory.h" - -class LLToolDragAndDrop; -class LLViewerRegion; -class LLVOAvatar; -class LLPickInfo; - -class LLToolDragAndDrop : public LLTool, public LLSingleton -{ - LLSINGLETON(LLToolDragAndDrop); -public: - typedef boost::signals2::signal enddrag_signal_t; - - // overridden from LLTool - virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; - virtual bool handleHover(S32 x, S32 y, MASK mask) override; - virtual bool handleKey(KEY key, MASK mask) override; - virtual bool handleToolTip(S32 x, S32 y, MASK mask) override; - virtual void onMouseCaptureLost() override; - virtual void handleDeselect() override; - - void setDragStart( S32 x, S32 y ); // In screen space - bool isOverThreshold( S32 x, S32 y ); // In screen space - - enum ESource - { - SOURCE_AGENT, - SOURCE_WORLD, - SOURCE_NOTECARD, - SOURCE_LIBRARY, - SOURCE_VIEWER, - SOURCE_PEOPLE - }; - - void beginDrag(EDragAndDropType type, - const LLUUID& cargo_id, - ESource source, - const LLUUID& source_id = LLUUID::null, - const LLUUID& object_id = LLUUID::null); - void beginMultiDrag(const std::vector types, - const uuid_vec_t& cargo_ids, - ESource source, - const LLUUID& source_id = LLUUID::null); - void endDrag(); - ESource getSource() const { return mSource; } - const LLUUID& getSourceID() const { return mSourceID; } - const LLUUID& getObjectID() const { return mObjectID; } - EAcceptance getLastAccept() { return mLastAccept; } - - boost::signals2::connection setEndDragCallback( const enddrag_signal_t::slot_type& cb ) { return mEndDragSignal.connect(cb); } - - void setCargoCount(U32 count) { mCargoCount = count; } - void resetCargoCount() { mCargoCount = 0; } - U32 getCargoCount() const { return (mCargoCount > 0) ? mCargoCount : mCargoIDs.size(); } - S32 getCargoIndex() const { return mCurItemIndex; } - - static S32 getOperationId() { return sOperationId; } - - // deal with permissions of object, etc. returns true if drop can - // proceed, otherwise false. - static bool handleDropMaterialProtections(LLViewerObject* hit_obj, - LLInventoryItem* item, - LLToolDragAndDrop::ESource source, - const LLUUID& src_id); - -protected: - enum EDropTarget - { - DT_NONE = 0, - DT_SELF = 1, - DT_AVATAR = 2, - DT_OBJECT = 3, - DT_LAND = 4, - DT_COUNT = 5 - }; - -protected: - // dragOrDrop3dImpl points to a member of LLToolDragAndDrop that - // takes parameters (LLViewerObject* obj, S32 face, MASK, bool - // drop) and returns a bool if drop is ok - typedef EAcceptance (LLToolDragAndDrop::*dragOrDrop3dImpl) - (LLViewerObject*, S32, MASK, bool); - - void dragOrDrop(S32 x, S32 y, MASK mask, bool drop, - EAcceptance* acceptance); - void dragOrDrop3D(S32 x, S32 y, MASK mask, bool drop, - EAcceptance* acceptance); - - static void pickCallback(const LLPickInfo& pick_info); - void pick(const LLPickInfo& pick_info); - -protected: - - U32 mCargoCount; - - S32 mDragStartX; - S32 mDragStartY; - - std::vector mCargoTypes; - //void* mCargoData; - uuid_vec_t mCargoIDs; - ESource mSource; - LLUUID mSourceID; - LLUUID mObjectID; - - static S32 sOperationId; - - LLVector3d mLastCameraPos; - LLVector3d mLastHitPos; - - ECursorType mCursor; - EAcceptance mLastAccept; - bool mDrop; - S32 mCurItemIndex; - std::string mToolTipMsg; - std::string mCustomMsg; - - enddrag_signal_t mEndDragSignal; - -protected: - // 3d drop functions. these call down into the static functions - // named drop if drop is true and permissions allow - // that behavior. - EAcceptance dad3dNULL(LLViewerObject*, S32, MASK, bool); - EAcceptance dad3dRezObjectOnLand(LLViewerObject* obj, S32 face, - MASK mask, bool drop); - EAcceptance dad3dRezObjectOnObject(LLViewerObject* obj, S32 face, - MASK mask, bool drop); - EAcceptance dad3dRezCategoryOnObject(LLViewerObject* obj, S32 face, - MASK mask, bool drop); - EAcceptance dad3dRezScript(LLViewerObject* obj, S32 face, - MASK mask, bool drop); - EAcceptance dad3dTextureObject(LLViewerObject* obj, S32 face, - MASK mask, bool drop); - EAcceptance dad3dMaterialObject(LLViewerObject* obj, S32 face, - MASK mask, bool drop); - EAcceptance dad3dMeshObject(LLViewerObject* obj, S32 face, - MASK mask, bool drop); -// EAcceptance dad3dTextureSelf(LLViewerObject* obj, S32 face, -// MASK mask, bool drop); - EAcceptance dad3dWearItem(LLViewerObject* obj, S32 face, - MASK mask, bool drop); - EAcceptance dad3dWearCategory(LLViewerObject* obj, S32 face, - MASK mask, bool drop); - EAcceptance dad3dUpdateInventory(LLViewerObject* obj, S32 face, - MASK mask, bool drop); - EAcceptance dad3dUpdateInventoryCategory(LLViewerObject* obj, - S32 face, - MASK mask, - bool drop); - EAcceptance dad3dGiveInventoryObject(LLViewerObject* obj, S32 face, - MASK mask, bool drop); - EAcceptance dad3dGiveInventory(LLViewerObject* obj, S32 face, - MASK mask, bool drop); - EAcceptance dad3dGiveInventoryCategory(LLViewerObject* obj, S32 face, - MASK mask, bool drop); - EAcceptance dad3dRezFromObjectOnLand(LLViewerObject* obj, S32 face, - MASK mask, bool drop); - EAcceptance dad3dRezFromObjectOnObject(LLViewerObject* obj, S32 face, - MASK mask, bool drop); - EAcceptance dad3dRezAttachmentFromInv(LLViewerObject* obj, S32 face, - MASK mask, bool drop); - EAcceptance dad3dCategoryOnLand(LLViewerObject *obj, S32 face, - MASK mask, bool drop); - EAcceptance dad3dAssetOnLand(LLViewerObject *obj, S32 face, - MASK mask, bool drop); - EAcceptance dad3dActivateGesture(LLViewerObject *obj, S32 face, - MASK mask, bool drop); - - // helper called by methods above to handle "application" of an item - // to an object (texture applied to face, mesh applied to shape, etc.) - EAcceptance dad3dApplyToObject(LLViewerObject* obj, S32 face, MASK mask, bool drop, EDragAndDropType cargo_type); - - - // set the LLToolDragAndDrop's cursor based on the given acceptance - ECursorType acceptanceToCursor( EAcceptance acceptance ); - - // This method converts mCargoID to an inventory item or - // folder. If no item or category is found, both pointers will be - // returned NULL. - LLInventoryObject* locateInventory(LLViewerInventoryItem*& item, - LLViewerInventoryCategory*& cat); - - //LLInventoryObject* locateMultipleInventory( - // LLViewerInventoryCategory::cat_array_t& cats, - // LLViewerInventoryItem::item_array_t& items); - - void dropObject(LLViewerObject* raycast_target, - bool bypass_sim_raycast, - bool from_task_inventory, - bool remove_from_inventory); - - // accessor that looks at permissions, copyability, and names of - // inventory items to determine if a drop would be ok. - static EAcceptance willObjectAcceptInventory(LLViewerObject* obj, LLInventoryItem* item, EDragAndDropType type = DAD_NONE); - -public: - // helper functions - static bool isInventoryDropAcceptable(LLViewerObject* obj, LLInventoryItem* item) { return (ACCEPT_YES_COPY_SINGLE <= willObjectAcceptInventory(obj, item)); } - - bool dadUpdateInventory(LLViewerObject* obj, bool drop); - bool dadUpdateInventoryCategory(LLViewerObject* obj, bool drop); - - // methods that act on the simulator state. - static void dropScript(LLViewerObject* hit_obj, - LLInventoryItem* item, - bool active, - ESource source, - const LLUUID& src_id); - static void dropTexture(LLViewerObject* hit_obj, - S32 hit_face, - LLInventoryItem* item, - ESource source, - const LLUUID& src_id, - bool all_faces, - bool replace_pbr, - S32 tex_channel = -1); - static void dropTextureOneFace(LLViewerObject* hit_obj, - S32 hit_face, - LLInventoryItem* item, - ESource source, - const LLUUID& src_id, - bool remove_pbr, - S32 tex_channel = -1); - static void dropTextureAllFaces(LLViewerObject* hit_obj, - LLInventoryItem* item, - ESource source, - const LLUUID& src_id, - bool remove_pbr); - static void dropMaterial(LLViewerObject* hit_obj, - S32 hit_face, - LLInventoryItem* item, - ESource source, - const LLUUID& src_id, - bool all_faces); - static void dropMaterialOneFace(LLViewerObject* hit_obj, - S32 hit_face, - LLInventoryItem* item, - ESource source, - const LLUUID& src_id); - static void dropMaterialAllFaces(LLViewerObject* hit_obj, - LLInventoryItem* item, - ESource source, - const LLUUID& src_id); - static void dropMesh(LLViewerObject* hit_obj, - LLInventoryItem* item, - ESource source, - const LLUUID& src_id); - - //static void dropTextureOneFaceAvatar(LLVOAvatar* avatar,S32 hit_face, - // LLInventoryItem* item) - - static void dropInventory(LLViewerObject* hit_obj, - LLInventoryItem* item, - ESource source, - const LLUUID& src_id); - - static bool handleGiveDragAndDrop(LLUUID agent, LLUUID session, bool drop, - EDragAndDropType cargo_type, - void* cargo_data, - EAcceptance* accept, - const LLSD& dest = LLSD()); - - // Classes used for determining 3d drag and drop types. -private: - struct DragAndDropEntry : public LLDictionaryEntry - { - DragAndDropEntry(dragOrDrop3dImpl f_none, - dragOrDrop3dImpl f_self, - dragOrDrop3dImpl f_avatar, - dragOrDrop3dImpl f_object, - dragOrDrop3dImpl f_land); - dragOrDrop3dImpl mFunctions[DT_COUNT]; - }; - class LLDragAndDropDictionary : public LLSingleton, - public LLDictionary - { - LLSINGLETON(LLDragAndDropDictionary); - public: - dragOrDrop3dImpl get(EDragAndDropType dad_type, EDropTarget drop_target); - }; -}; - -// utility functions -void pack_permissions_slam(LLMessageSystem* msg, U32 flags, const LLPermissions& perms); - -#endif // LL_TOOLDRAGANDDROP_H +/** + * @file lltooldraganddrop.h + * @brief LLToolDragAndDrop class header file + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_TOOLDRAGANDDROP_H +#define LL_TOOLDRAGANDDROP_H + +#include "lldictionary.h" +#include "lltool.h" +#include "llview.h" +#include "lluuid.h" +#include "llassetstorage.h" +#include "llpermissions.h" +#include "llwindow.h" +#include "llviewerinventory.h" + +class LLToolDragAndDrop; +class LLViewerRegion; +class LLVOAvatar; +class LLPickInfo; + +class LLToolDragAndDrop : public LLTool, public LLSingleton +{ + LLSINGLETON(LLToolDragAndDrop); +public: + typedef boost::signals2::signal enddrag_signal_t; + + // overridden from LLTool + virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; + virtual bool handleHover(S32 x, S32 y, MASK mask) override; + virtual bool handleKey(KEY key, MASK mask) override; + virtual bool handleToolTip(S32 x, S32 y, MASK mask) override; + virtual void onMouseCaptureLost() override; + virtual void handleDeselect() override; + + void setDragStart( S32 x, S32 y ); // In screen space + bool isOverThreshold( S32 x, S32 y ); // In screen space + + enum ESource + { + SOURCE_AGENT, + SOURCE_WORLD, + SOURCE_NOTECARD, + SOURCE_LIBRARY, + SOURCE_VIEWER, + SOURCE_PEOPLE + }; + + void beginDrag(EDragAndDropType type, + const LLUUID& cargo_id, + ESource source, + const LLUUID& source_id = LLUUID::null, + const LLUUID& object_id = LLUUID::null); + void beginMultiDrag(const std::vector types, + const uuid_vec_t& cargo_ids, + ESource source, + const LLUUID& source_id = LLUUID::null); + void endDrag(); + ESource getSource() const { return mSource; } + const LLUUID& getSourceID() const { return mSourceID; } + const LLUUID& getObjectID() const { return mObjectID; } + EAcceptance getLastAccept() { return mLastAccept; } + + boost::signals2::connection setEndDragCallback( const enddrag_signal_t::slot_type& cb ) { return mEndDragSignal.connect(cb); } + + void setCargoCount(U32 count) { mCargoCount = count; } + void resetCargoCount() { mCargoCount = 0; } + U32 getCargoCount() const { return (mCargoCount > 0) ? mCargoCount : mCargoIDs.size(); } + S32 getCargoIndex() const { return mCurItemIndex; } + + static S32 getOperationId() { return sOperationId; } + + // deal with permissions of object, etc. returns true if drop can + // proceed, otherwise false. + static bool handleDropMaterialProtections(LLViewerObject* hit_obj, + LLInventoryItem* item, + LLToolDragAndDrop::ESource source, + const LLUUID& src_id); + +protected: + enum EDropTarget + { + DT_NONE = 0, + DT_SELF = 1, + DT_AVATAR = 2, + DT_OBJECT = 3, + DT_LAND = 4, + DT_COUNT = 5 + }; + +protected: + // dragOrDrop3dImpl points to a member of LLToolDragAndDrop that + // takes parameters (LLViewerObject* obj, S32 face, MASK, bool + // drop) and returns a bool if drop is ok + typedef EAcceptance (LLToolDragAndDrop::*dragOrDrop3dImpl) + (LLViewerObject*, S32, MASK, bool); + + void dragOrDrop(S32 x, S32 y, MASK mask, bool drop, + EAcceptance* acceptance); + void dragOrDrop3D(S32 x, S32 y, MASK mask, bool drop, + EAcceptance* acceptance); + + static void pickCallback(const LLPickInfo& pick_info); + void pick(const LLPickInfo& pick_info); + +protected: + + U32 mCargoCount; + + S32 mDragStartX; + S32 mDragStartY; + + std::vector mCargoTypes; + //void* mCargoData; + uuid_vec_t mCargoIDs; + ESource mSource; + LLUUID mSourceID; + LLUUID mObjectID; + + static S32 sOperationId; + + LLVector3d mLastCameraPos; + LLVector3d mLastHitPos; + + ECursorType mCursor; + EAcceptance mLastAccept; + bool mDrop; + S32 mCurItemIndex; + std::string mToolTipMsg; + std::string mCustomMsg; + + enddrag_signal_t mEndDragSignal; + +protected: + // 3d drop functions. these call down into the static functions + // named drop if drop is true and permissions allow + // that behavior. + EAcceptance dad3dNULL(LLViewerObject*, S32, MASK, bool); + EAcceptance dad3dRezObjectOnLand(LLViewerObject* obj, S32 face, + MASK mask, bool drop); + EAcceptance dad3dRezObjectOnObject(LLViewerObject* obj, S32 face, + MASK mask, bool drop); + EAcceptance dad3dRezCategoryOnObject(LLViewerObject* obj, S32 face, + MASK mask, bool drop); + EAcceptance dad3dRezScript(LLViewerObject* obj, S32 face, + MASK mask, bool drop); + EAcceptance dad3dTextureObject(LLViewerObject* obj, S32 face, + MASK mask, bool drop); + EAcceptance dad3dMaterialObject(LLViewerObject* obj, S32 face, + MASK mask, bool drop); + EAcceptance dad3dMeshObject(LLViewerObject* obj, S32 face, + MASK mask, bool drop); +// EAcceptance dad3dTextureSelf(LLViewerObject* obj, S32 face, +// MASK mask, bool drop); + EAcceptance dad3dWearItem(LLViewerObject* obj, S32 face, + MASK mask, bool drop); + EAcceptance dad3dWearCategory(LLViewerObject* obj, S32 face, + MASK mask, bool drop); + EAcceptance dad3dUpdateInventory(LLViewerObject* obj, S32 face, + MASK mask, bool drop); + EAcceptance dad3dUpdateInventoryCategory(LLViewerObject* obj, + S32 face, + MASK mask, + bool drop); + EAcceptance dad3dGiveInventoryObject(LLViewerObject* obj, S32 face, + MASK mask, bool drop); + EAcceptance dad3dGiveInventory(LLViewerObject* obj, S32 face, + MASK mask, bool drop); + EAcceptance dad3dGiveInventoryCategory(LLViewerObject* obj, S32 face, + MASK mask, bool drop); + EAcceptance dad3dRezFromObjectOnLand(LLViewerObject* obj, S32 face, + MASK mask, bool drop); + EAcceptance dad3dRezFromObjectOnObject(LLViewerObject* obj, S32 face, + MASK mask, bool drop); + EAcceptance dad3dRezAttachmentFromInv(LLViewerObject* obj, S32 face, + MASK mask, bool drop); + EAcceptance dad3dCategoryOnLand(LLViewerObject *obj, S32 face, + MASK mask, bool drop); + EAcceptance dad3dAssetOnLand(LLViewerObject *obj, S32 face, + MASK mask, bool drop); + EAcceptance dad3dActivateGesture(LLViewerObject *obj, S32 face, + MASK mask, bool drop); + + // helper called by methods above to handle "application" of an item + // to an object (texture applied to face, mesh applied to shape, etc.) + EAcceptance dad3dApplyToObject(LLViewerObject* obj, S32 face, MASK mask, bool drop, EDragAndDropType cargo_type); + + + // set the LLToolDragAndDrop's cursor based on the given acceptance + ECursorType acceptanceToCursor( EAcceptance acceptance ); + + // This method converts mCargoID to an inventory item or + // folder. If no item or category is found, both pointers will be + // returned NULL. + LLInventoryObject* locateInventory(LLViewerInventoryItem*& item, + LLViewerInventoryCategory*& cat); + + //LLInventoryObject* locateMultipleInventory( + // LLViewerInventoryCategory::cat_array_t& cats, + // LLViewerInventoryItem::item_array_t& items); + + void dropObject(LLViewerObject* raycast_target, + bool bypass_sim_raycast, + bool from_task_inventory, + bool remove_from_inventory); + + // accessor that looks at permissions, copyability, and names of + // inventory items to determine if a drop would be ok. + static EAcceptance willObjectAcceptInventory(LLViewerObject* obj, LLInventoryItem* item, EDragAndDropType type = DAD_NONE); + +public: + // helper functions + static bool isInventoryDropAcceptable(LLViewerObject* obj, LLInventoryItem* item) { return (ACCEPT_YES_COPY_SINGLE <= willObjectAcceptInventory(obj, item)); } + + bool dadUpdateInventory(LLViewerObject* obj, bool drop); + bool dadUpdateInventoryCategory(LLViewerObject* obj, bool drop); + + // methods that act on the simulator state. + static void dropScript(LLViewerObject* hit_obj, + LLInventoryItem* item, + bool active, + ESource source, + const LLUUID& src_id); + static void dropTexture(LLViewerObject* hit_obj, + S32 hit_face, + LLInventoryItem* item, + ESource source, + const LLUUID& src_id, + bool all_faces, + bool replace_pbr, + S32 tex_channel = -1); + static void dropTextureOneFace(LLViewerObject* hit_obj, + S32 hit_face, + LLInventoryItem* item, + ESource source, + const LLUUID& src_id, + bool remove_pbr, + S32 tex_channel = -1); + static void dropTextureAllFaces(LLViewerObject* hit_obj, + LLInventoryItem* item, + ESource source, + const LLUUID& src_id, + bool remove_pbr); + static void dropMaterial(LLViewerObject* hit_obj, + S32 hit_face, + LLInventoryItem* item, + ESource source, + const LLUUID& src_id, + bool all_faces); + static void dropMaterialOneFace(LLViewerObject* hit_obj, + S32 hit_face, + LLInventoryItem* item, + ESource source, + const LLUUID& src_id); + static void dropMaterialAllFaces(LLViewerObject* hit_obj, + LLInventoryItem* item, + ESource source, + const LLUUID& src_id); + static void dropMesh(LLViewerObject* hit_obj, + LLInventoryItem* item, + ESource source, + const LLUUID& src_id); + + //static void dropTextureOneFaceAvatar(LLVOAvatar* avatar,S32 hit_face, + // LLInventoryItem* item) + + static void dropInventory(LLViewerObject* hit_obj, + LLInventoryItem* item, + ESource source, + const LLUUID& src_id); + + static bool handleGiveDragAndDrop(LLUUID agent, LLUUID session, bool drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + const LLSD& dest = LLSD()); + + // Classes used for determining 3d drag and drop types. +private: + struct DragAndDropEntry : public LLDictionaryEntry + { + DragAndDropEntry(dragOrDrop3dImpl f_none, + dragOrDrop3dImpl f_self, + dragOrDrop3dImpl f_avatar, + dragOrDrop3dImpl f_object, + dragOrDrop3dImpl f_land); + dragOrDrop3dImpl mFunctions[DT_COUNT]; + }; + class LLDragAndDropDictionary : public LLSingleton, + public LLDictionary + { + LLSINGLETON(LLDragAndDropDictionary); + public: + dragOrDrop3dImpl get(EDragAndDropType dad_type, EDropTarget drop_target); + }; +}; + +// utility functions +void pack_permissions_slam(LLMessageSystem* msg, U32 flags, const LLPermissions& perms); + +#endif // LL_TOOLDRAGANDDROP_H diff --git a/indra/newview/lltoolface.cpp b/indra/newview/lltoolface.cpp index e93c3006a5..426f340be1 100644 --- a/indra/newview/lltoolface.cpp +++ b/indra/newview/lltoolface.cpp @@ -1,151 +1,151 @@ -/** - * @file lltoolface.cpp - * @brief A tool to manipulate faces - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -// File includes -#include "lltoolface.h" - -// Library includes -#include "llfloaterreg.h" -#include "v3math.h" - -// Viewer includes -#include "llviewercontrol.h" -#include "llselectmgr.h" -#include "llviewerobject.h" -#include "llviewerwindow.h" -#include "llfloatertools.h" - -// -// Member functions -// - -LLToolFace::LLToolFace() -: LLTool(std::string("Texture")) -{ } - - -LLToolFace::~LLToolFace() -{ } - - -bool LLToolFace::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - if (!LLSelectMgr::getInstance()->getSelection()->isEmpty()) - { - // You should already have an object selected from the mousedown. - // If so, show its properties - LLFloaterReg::showInstance("build", "Texture"); - return true; - } - else - { - // Nothing selected means the first mouse click was probably - // bad, so try again. - return false; - } -} - - -bool LLToolFace::handleMouseDown(S32 x, S32 y, MASK mask) -{ - gViewerWindow->pickAsync(x, y, mask, pickCallback); - return true; -} - -void LLToolFace::pickCallback(const LLPickInfo& pick_info) -{ - LLViewerObject* hit_obj = pick_info.getObject(); - if (hit_obj) - { - S32 hit_face = pick_info.mObjectFace; - - if (hit_obj->isAvatar()) - { - // ...clicked on an avatar, so don't do anything - return; - } - - // ...clicked on a world object, try to pick the appropriate face - - if (pick_info.mKeyMask & MASK_SHIFT) - { - // If object not selected, need to inform sim - if ( !hit_obj->isSelected() ) - { - // object wasn't selected so add the object and face - LLSelectMgr::getInstance()->selectObjectOnly(hit_obj, hit_face); - } - else if (!LLSelectMgr::getInstance()->getSelection()->contains(hit_obj, hit_face) ) - { - // object is selected, but not this face, so add it. - LLSelectMgr::getInstance()->addAsIndividual(hit_obj, hit_face); - } - else - { - // object is selected, as is this face, so remove the face. - LLSelectMgr::getInstance()->remove(hit_obj, hit_face); - - // BUG: If you remove the last face, the simulator won't know about it. - } - } - else - { - // clicked without modifiers, select only - // this face - LLSelectMgr::getInstance()->deselectAll(); - LLSelectMgr::getInstance()->selectObjectOnly(hit_obj, hit_face); - } - } - else - { - if (!(pick_info.mKeyMask == MASK_SHIFT)) - { - LLSelectMgr::getInstance()->deselectAll(); - } - } -} - - -void LLToolFace::handleSelect() -{ - // From now on, draw faces - LLSelectMgr::getInstance()->setTEMode(true); -} - - -void LLToolFace::handleDeselect() -{ - // Stop drawing faces - LLSelectMgr::getInstance()->setTEMode(false); -} - - -void LLToolFace::render() -{ - // for now, do nothing -} +/** + * @file lltoolface.cpp + * @brief A tool to manipulate faces + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +// File includes +#include "lltoolface.h" + +// Library includes +#include "llfloaterreg.h" +#include "v3math.h" + +// Viewer includes +#include "llviewercontrol.h" +#include "llselectmgr.h" +#include "llviewerobject.h" +#include "llviewerwindow.h" +#include "llfloatertools.h" + +// +// Member functions +// + +LLToolFace::LLToolFace() +: LLTool(std::string("Texture")) +{ } + + +LLToolFace::~LLToolFace() +{ } + + +bool LLToolFace::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + if (!LLSelectMgr::getInstance()->getSelection()->isEmpty()) + { + // You should already have an object selected from the mousedown. + // If so, show its properties + LLFloaterReg::showInstance("build", "Texture"); + return true; + } + else + { + // Nothing selected means the first mouse click was probably + // bad, so try again. + return false; + } +} + + +bool LLToolFace::handleMouseDown(S32 x, S32 y, MASK mask) +{ + gViewerWindow->pickAsync(x, y, mask, pickCallback); + return true; +} + +void LLToolFace::pickCallback(const LLPickInfo& pick_info) +{ + LLViewerObject* hit_obj = pick_info.getObject(); + if (hit_obj) + { + S32 hit_face = pick_info.mObjectFace; + + if (hit_obj->isAvatar()) + { + // ...clicked on an avatar, so don't do anything + return; + } + + // ...clicked on a world object, try to pick the appropriate face + + if (pick_info.mKeyMask & MASK_SHIFT) + { + // If object not selected, need to inform sim + if ( !hit_obj->isSelected() ) + { + // object wasn't selected so add the object and face + LLSelectMgr::getInstance()->selectObjectOnly(hit_obj, hit_face); + } + else if (!LLSelectMgr::getInstance()->getSelection()->contains(hit_obj, hit_face) ) + { + // object is selected, but not this face, so add it. + LLSelectMgr::getInstance()->addAsIndividual(hit_obj, hit_face); + } + else + { + // object is selected, as is this face, so remove the face. + LLSelectMgr::getInstance()->remove(hit_obj, hit_face); + + // BUG: If you remove the last face, the simulator won't know about it. + } + } + else + { + // clicked without modifiers, select only + // this face + LLSelectMgr::getInstance()->deselectAll(); + LLSelectMgr::getInstance()->selectObjectOnly(hit_obj, hit_face); + } + } + else + { + if (!(pick_info.mKeyMask == MASK_SHIFT)) + { + LLSelectMgr::getInstance()->deselectAll(); + } + } +} + + +void LLToolFace::handleSelect() +{ + // From now on, draw faces + LLSelectMgr::getInstance()->setTEMode(true); +} + + +void LLToolFace::handleDeselect() +{ + // Stop drawing faces + LLSelectMgr::getInstance()->setTEMode(false); +} + + +void LLToolFace::render() +{ + // for now, do nothing +} diff --git a/indra/newview/lltoolface.h b/indra/newview/lltoolface.h index af4824ab78..18d42da1e1 100644 --- a/indra/newview/lltoolface.h +++ b/indra/newview/lltoolface.h @@ -1,51 +1,51 @@ -/** - * @file lltoolface.h - * @brief A tool to select object faces - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLTOOLFACE_H -#define LL_LLTOOLFACE_H - -#include "lltool.h" - -class LLViewerObject; -class LLPickInfo; - -class LLToolFace -: public LLTool, public LLSingleton -{ - LLSINGLETON(LLToolFace); - virtual ~LLToolFace(); -public: - - virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; - virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; - virtual void handleSelect() override; - virtual void handleDeselect() override; - virtual void render() override; // draw face highlights - - static void pickCallback(const LLPickInfo& pick_info); -}; - -#endif +/** + * @file lltoolface.h + * @brief A tool to select object faces + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLTOOLFACE_H +#define LL_LLTOOLFACE_H + +#include "lltool.h" + +class LLViewerObject; +class LLPickInfo; + +class LLToolFace +: public LLTool, public LLSingleton +{ + LLSINGLETON(LLToolFace); + virtual ~LLToolFace(); +public: + + virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; + virtual void handleSelect() override; + virtual void handleDeselect() override; + virtual void render() override; // draw face highlights + + static void pickCallback(const LLPickInfo& pick_info); +}; + +#endif diff --git a/indra/newview/lltoolfocus.cpp b/indra/newview/lltoolfocus.cpp index 5b2b78d98a..0ba7ae5e84 100644 --- a/indra/newview/lltoolfocus.cpp +++ b/indra/newview/lltoolfocus.cpp @@ -1,478 +1,478 @@ -/** - * @file lltoolfocus.cpp - * @brief A tool to set the build focus point. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -// File includes -#include "lltoolfocus.h" - -// Library includes -#include "v3math.h" -#include "llfontgl.h" -#include "llui.h" - -// Viewer includes -#include "llagent.h" -#include "llagentcamera.h" -#include "llbutton.h" -#include "llviewercontrol.h" -#include "lldrawable.h" -#include "lltooltip.h" -#include "llhudmanager.h" -#include "llfloatertools.h" -#include "llselectmgr.h" -#include "llstatusbar.h" -#include "lltoolmgr.h" -#include "llviewercamera.h" -#include "llviewerobject.h" -#include "llviewerwindow.h" -#include "llvoavatarself.h" -#include "llmorphview.h" -#include "llfloaterreg.h" -#include "llfloatercamera.h" -#include "llmenugl.h" - -// Globals -bool gCameraBtnZoom = true; -bool gCameraBtnOrbit = false; -bool gCameraBtnPan = false; - -const S32 SLOP_RANGE = 4; - -// -// Camera - shared functionality -// - -LLToolCamera::LLToolCamera() -: LLTool(std::string("Camera")), - mAccumX(0), - mAccumY(0), - mMouseDownX(0), - mMouseDownY(0), - mOutsideSlopX(false), - mOutsideSlopY(false), - mValidClickPoint(false), - mClickPickPending(false), - mValidSelection(false), - mMouseSteering(false), - mMouseUpX(0), - mMouseUpY(0), - mMouseUpMask(MASK_NONE) -{ } - - -LLToolCamera::~LLToolCamera() -{ } - -// virtual -void LLToolCamera::handleSelect() -{ - if (gFloaterTools) - { - gFloaterTools->setStatusText("camera"); - // in case we start from tools floater, we count any selection as valid - mValidSelection = gFloaterTools->getVisible(); - } -} - -// virtual -void LLToolCamera::handleDeselect() -{ -// gAgent.setLookingAtAvatar(false); - - // Make sure that temporary selection won't pass anywhere except pie tool. - MASK override_mask = gKeyboard ? gKeyboard->currentMask(true) : 0; - if (!mValidSelection && (override_mask != MASK_NONE || (gFloaterTools && gFloaterTools->getVisible()))) - { - LLMenuGL::sMenuContainer->hideMenus(); - LLSelectMgr::getInstance()->validateSelection(); - } -} - -bool LLToolCamera::handleMouseDown(S32 x, S32 y, MASK mask) -{ - // Ensure a mouseup - setMouseCapture(true); - - // call the base class to propogate info to sim - LLTool::handleMouseDown(x, y, mask); - - mAccumX = 0; - mAccumY = 0; - - mOutsideSlopX = false; - mOutsideSlopY = false; - - mValidClickPoint = false; - - // Sometimes Windows issues down and up events near simultaneously - // without giving async pick a chance to trigged - // Ex: mouse from numlock emulation - mClickPickPending = true; - - // If mouse capture gets ripped away, claim we moused up - // at the point we moused down. JC - mMouseUpX = x; - mMouseUpY = y; - mMouseUpMask = mask; - - gViewerWindow->hideCursor(); - - gViewerWindow->pickAsync(x, y, mask, pickCallback, /*bool pick_transparent*/ false, /*bool pick_rigged*/ false, /*bool pick_unselectable*/ true); - - return true; -} - -void LLToolCamera::pickCallback(const LLPickInfo& pick_info) -{ - LLToolCamera* camera = LLToolCamera::getInstance(); - if (!camera->mClickPickPending) - { - return; - } - camera->mClickPickPending = false; - - camera->mMouseDownX = pick_info.mMousePt.mX; - camera->mMouseDownY = pick_info.mMousePt.mY; - - gViewerWindow->moveCursorToCenter(); - - // Potentially recenter if click outside rectangle - LLViewerObject* hit_obj = pick_info.getObject(); - - // Check for hit the sky, or some other invalid point - if (!hit_obj && pick_info.mPosGlobal.isExactlyZero()) - { - camera->mValidClickPoint = false; - return; - } - - // check for hud attachments - if (hit_obj && hit_obj->isHUDAttachment()) - { - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - if (!selection->getObjectCount() || selection->getSelectType() != SELECT_TYPE_HUD) - { - camera->mValidClickPoint = false; - return; - } - } - - if( CAMERA_MODE_CUSTOMIZE_AVATAR == gAgentCamera.getCameraMode() ) - { - bool good_customize_avatar_hit = false; - if( hit_obj ) - { - if (isAgentAvatarValid() && (hit_obj == gAgentAvatarp)) - { - // It's you - good_customize_avatar_hit = true; - } - else if (hit_obj->isAttachment() && hit_obj->permYouOwner()) - { - // It's an attachment that you're wearing - good_customize_avatar_hit = true; - } - } - - if( !good_customize_avatar_hit ) - { - camera->mValidClickPoint = false; - return; - } - - if( gMorphView ) - { - gMorphView->setCameraDrivenByKeys( false ); - } - } - //RN: check to see if this is mouse-driving as opposed to ALT-zoom or Focus tool - else if (pick_info.mKeyMask & MASK_ALT || - (LLToolMgr::getInstance()->getCurrentTool()->getName() == "Camera")) - { - LLViewerObject* hit_obj = pick_info.getObject(); - if (hit_obj) - { - // ...clicked on a world object, so focus at its position - if (!hit_obj->isHUDAttachment()) - { - gAgentCamera.setFocusOnAvatar(false, ANIMATE); - gAgentCamera.setFocusGlobal(pick_info); - } - } - else if (!pick_info.mPosGlobal.isExactlyZero()) - { - // Hit the ground - gAgentCamera.setFocusOnAvatar(false, ANIMATE); - gAgentCamera.setFocusGlobal(pick_info); - } - - bool zoom_tool = gCameraBtnZoom && (LLToolMgr::getInstance()->getBaseTool() == LLToolCamera::getInstance()); - if (!(pick_info.mKeyMask & MASK_ALT) && - !LLFloaterCamera::inFreeCameraMode() && - !zoom_tool && - gAgentCamera.cameraThirdPerson() && - gViewerWindow->getLeftMouseDown() && - !gSavedSettings.getBOOL("FreezeTime") && - (hit_obj == gAgentAvatarp || - (hit_obj && hit_obj->isAttachment() && LLVOAvatar::findAvatarFromAttachment(hit_obj)->isSelf()))) - { - LLToolCamera::getInstance()->mMouseSteering = true; - } - - } - - camera->mValidClickPoint = true; - - if( CAMERA_MODE_CUSTOMIZE_AVATAR == gAgentCamera.getCameraMode() ) - { - gAgentCamera.setFocusOnAvatar(false, false); - - LLVector3d cam_pos = gAgentCamera.getCameraPositionGlobal(); - - gAgentCamera.setCameraPosAndFocusGlobal( cam_pos, pick_info.mPosGlobal, pick_info.mObjectID); - } -} - - -// "Let go" of the mouse, for example on mouse up or when -// we lose mouse capture. This ensures that cursor becomes visible -// if a modal dialog pops up during Alt-Zoom. JC -void LLToolCamera::releaseMouse() -{ - // Need to tell the sim that the mouse button is up, since this - // tool is no longer working and cursor is visible (despite actual - // mouse button status). - LLTool::handleMouseUp(mMouseUpX, mMouseUpY, mMouseUpMask); - - gViewerWindow->showCursor(); - - //for the situation when left click was performed on the Agent - if (!LLFloaterCamera::inFreeCameraMode()) - { - LLToolMgr::getInstance()->clearTransientTool(); - } - - mMouseSteering = false; - mValidClickPoint = false; - mOutsideSlopX = false; - mOutsideSlopY = false; -} - - -bool LLToolCamera::handleMouseUp(S32 x, S32 y, MASK mask) -{ - // Claim that we're mousing up somewhere - mMouseUpX = x; - mMouseUpY = y; - mMouseUpMask = mask; - - if (hasMouseCapture()) - { - // Do not move camera if we haven't gotten a pick - if (!mClickPickPending) - { - if (mValidClickPoint) - { - if (CAMERA_MODE_CUSTOMIZE_AVATAR == gAgentCamera.getCameraMode()) - { - LLCoordGL mouse_pos; - LLVector3 focus_pos = gAgent.getPosAgentFromGlobal(gAgentCamera.getFocusGlobal()); - bool success = LLViewerCamera::getInstance()->projectPosAgentToScreen(focus_pos, mouse_pos); - if (success) - { - LLUI::getInstance()->setMousePositionScreen(mouse_pos.mX, mouse_pos.mY); - } - } - else if (mMouseSteering) - { - LLUI::getInstance()->setMousePositionScreen(mMouseDownX, mMouseDownY); - } - else - { - gViewerWindow->moveCursorToCenter(); - } - } - else - { - // not a valid zoomable object - LLUI::getInstance()->setMousePositionScreen(mMouseDownX, mMouseDownY); - } - } - - // calls releaseMouse() internally - setMouseCapture(false); - } - else - { - releaseMouse(); - } - - return true; -} - - -bool LLToolCamera::handleHover(S32 x, S32 y, MASK mask) -{ - S32 dx = gViewerWindow->getCurrentMouseDX(); - S32 dy = gViewerWindow->getCurrentMouseDY(); - - if (hasMouseCapture() && mValidClickPoint) - { - mAccumX += llabs(dx); - mAccumY += llabs(dy); - - if (mAccumX >= SLOP_RANGE) - { - mOutsideSlopX = true; - } - - if (mAccumY >= SLOP_RANGE) - { - mOutsideSlopY = true; - } - } - - if (mOutsideSlopX || mOutsideSlopY) - { - if (!mValidClickPoint) - { - LL_DEBUGS("UserInput") << "hover handled by LLToolFocus [invalid point]" << LL_ENDL; - gViewerWindow->setCursor(UI_CURSOR_NO); - gViewerWindow->showCursor(); - return true; - } - - if (gCameraBtnOrbit || - mask == MASK_ORBIT || - mask == (MASK_ALT | MASK_ORBIT)) - { - // Orbit tool - if (hasMouseCapture()) - { - const F32 RADIANS_PER_PIXEL = 360.f * DEG_TO_RAD / gViewerWindow->getWorldViewWidthScaled(); - - if (dx != 0) - { - gAgentCamera.cameraOrbitAround( -dx * RADIANS_PER_PIXEL ); - } - - if (dy != 0) - { - gAgentCamera.cameraOrbitOver( -dy * RADIANS_PER_PIXEL ); - } - - gViewerWindow->moveCursorToCenter(); - } - LL_DEBUGS("UserInput") << "hover handled by LLToolFocus [active]" << LL_ENDL; - } - else if ( gCameraBtnPan || - mask == MASK_PAN || - mask == (MASK_PAN | MASK_ALT) ) - { - // Pan tool - if (hasMouseCapture()) - { - LLVector3d camera_to_focus = gAgentCamera.getCameraPositionGlobal(); - camera_to_focus -= gAgentCamera.getFocusGlobal(); - F32 dist = (F32) camera_to_focus.normVec(); - - // Fudge factor for pan - F32 meters_per_pixel = 3.f * dist / gViewerWindow->getWorldViewWidthScaled(); - - if (dx != 0) - { - gAgentCamera.cameraPanLeft( dx * meters_per_pixel ); - } - - if (dy != 0) - { - gAgentCamera.cameraPanUp( -dy * meters_per_pixel ); - } - - gViewerWindow->moveCursorToCenter(); - } - LL_DEBUGS("UserInput") << "hover handled by LLToolPan" << LL_ENDL; - } - else if (gCameraBtnZoom) - { - // Zoom tool - if (hasMouseCapture()) - { - - const F32 RADIANS_PER_PIXEL = 360.f * DEG_TO_RAD / gViewerWindow->getWorldViewWidthScaled(); - - if (dx != 0) - { - gAgentCamera.cameraOrbitAround( -dx * RADIANS_PER_PIXEL ); - } - - const F32 IN_FACTOR = 0.99f; - - if (dy != 0 && mOutsideSlopY ) - { - if (mMouseSteering) - { - gAgentCamera.cameraOrbitOver( -dy * RADIANS_PER_PIXEL ); - } - else - { - gAgentCamera.cameraZoomIn( pow( IN_FACTOR, dy ) ); - } - } - - gViewerWindow->moveCursorToCenter(); - } - - LL_DEBUGS("UserInput") << "hover handled by LLToolZoom" << LL_ENDL; - } - } - - if (gCameraBtnOrbit || - mask == MASK_ORBIT || - mask == (MASK_ALT | MASK_ORBIT)) - { - gViewerWindow->setCursor(UI_CURSOR_TOOLCAMERA); - } - else if ( gCameraBtnPan || - mask == MASK_PAN || - mask == (MASK_PAN | MASK_ALT) ) - { - gViewerWindow->setCursor(UI_CURSOR_TOOLPAN); - } - else - { - gViewerWindow->setCursor(UI_CURSOR_TOOLZOOMIN); - } - - return true; -} - - -void LLToolCamera::onMouseCaptureLost() -{ - releaseMouse(); -} +/** + * @file lltoolfocus.cpp + * @brief A tool to set the build focus point. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +// File includes +#include "lltoolfocus.h" + +// Library includes +#include "v3math.h" +#include "llfontgl.h" +#include "llui.h" + +// Viewer includes +#include "llagent.h" +#include "llagentcamera.h" +#include "llbutton.h" +#include "llviewercontrol.h" +#include "lldrawable.h" +#include "lltooltip.h" +#include "llhudmanager.h" +#include "llfloatertools.h" +#include "llselectmgr.h" +#include "llstatusbar.h" +#include "lltoolmgr.h" +#include "llviewercamera.h" +#include "llviewerobject.h" +#include "llviewerwindow.h" +#include "llvoavatarself.h" +#include "llmorphview.h" +#include "llfloaterreg.h" +#include "llfloatercamera.h" +#include "llmenugl.h" + +// Globals +bool gCameraBtnZoom = true; +bool gCameraBtnOrbit = false; +bool gCameraBtnPan = false; + +const S32 SLOP_RANGE = 4; + +// +// Camera - shared functionality +// + +LLToolCamera::LLToolCamera() +: LLTool(std::string("Camera")), + mAccumX(0), + mAccumY(0), + mMouseDownX(0), + mMouseDownY(0), + mOutsideSlopX(false), + mOutsideSlopY(false), + mValidClickPoint(false), + mClickPickPending(false), + mValidSelection(false), + mMouseSteering(false), + mMouseUpX(0), + mMouseUpY(0), + mMouseUpMask(MASK_NONE) +{ } + + +LLToolCamera::~LLToolCamera() +{ } + +// virtual +void LLToolCamera::handleSelect() +{ + if (gFloaterTools) + { + gFloaterTools->setStatusText("camera"); + // in case we start from tools floater, we count any selection as valid + mValidSelection = gFloaterTools->getVisible(); + } +} + +// virtual +void LLToolCamera::handleDeselect() +{ +// gAgent.setLookingAtAvatar(false); + + // Make sure that temporary selection won't pass anywhere except pie tool. + MASK override_mask = gKeyboard ? gKeyboard->currentMask(true) : 0; + if (!mValidSelection && (override_mask != MASK_NONE || (gFloaterTools && gFloaterTools->getVisible()))) + { + LLMenuGL::sMenuContainer->hideMenus(); + LLSelectMgr::getInstance()->validateSelection(); + } +} + +bool LLToolCamera::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // Ensure a mouseup + setMouseCapture(true); + + // call the base class to propogate info to sim + LLTool::handleMouseDown(x, y, mask); + + mAccumX = 0; + mAccumY = 0; + + mOutsideSlopX = false; + mOutsideSlopY = false; + + mValidClickPoint = false; + + // Sometimes Windows issues down and up events near simultaneously + // without giving async pick a chance to trigged + // Ex: mouse from numlock emulation + mClickPickPending = true; + + // If mouse capture gets ripped away, claim we moused up + // at the point we moused down. JC + mMouseUpX = x; + mMouseUpY = y; + mMouseUpMask = mask; + + gViewerWindow->hideCursor(); + + gViewerWindow->pickAsync(x, y, mask, pickCallback, /*bool pick_transparent*/ false, /*bool pick_rigged*/ false, /*bool pick_unselectable*/ true); + + return true; +} + +void LLToolCamera::pickCallback(const LLPickInfo& pick_info) +{ + LLToolCamera* camera = LLToolCamera::getInstance(); + if (!camera->mClickPickPending) + { + return; + } + camera->mClickPickPending = false; + + camera->mMouseDownX = pick_info.mMousePt.mX; + camera->mMouseDownY = pick_info.mMousePt.mY; + + gViewerWindow->moveCursorToCenter(); + + // Potentially recenter if click outside rectangle + LLViewerObject* hit_obj = pick_info.getObject(); + + // Check for hit the sky, or some other invalid point + if (!hit_obj && pick_info.mPosGlobal.isExactlyZero()) + { + camera->mValidClickPoint = false; + return; + } + + // check for hud attachments + if (hit_obj && hit_obj->isHUDAttachment()) + { + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + if (!selection->getObjectCount() || selection->getSelectType() != SELECT_TYPE_HUD) + { + camera->mValidClickPoint = false; + return; + } + } + + if( CAMERA_MODE_CUSTOMIZE_AVATAR == gAgentCamera.getCameraMode() ) + { + bool good_customize_avatar_hit = false; + if( hit_obj ) + { + if (isAgentAvatarValid() && (hit_obj == gAgentAvatarp)) + { + // It's you + good_customize_avatar_hit = true; + } + else if (hit_obj->isAttachment() && hit_obj->permYouOwner()) + { + // It's an attachment that you're wearing + good_customize_avatar_hit = true; + } + } + + if( !good_customize_avatar_hit ) + { + camera->mValidClickPoint = false; + return; + } + + if( gMorphView ) + { + gMorphView->setCameraDrivenByKeys( false ); + } + } + //RN: check to see if this is mouse-driving as opposed to ALT-zoom or Focus tool + else if (pick_info.mKeyMask & MASK_ALT || + (LLToolMgr::getInstance()->getCurrentTool()->getName() == "Camera")) + { + LLViewerObject* hit_obj = pick_info.getObject(); + if (hit_obj) + { + // ...clicked on a world object, so focus at its position + if (!hit_obj->isHUDAttachment()) + { + gAgentCamera.setFocusOnAvatar(false, ANIMATE); + gAgentCamera.setFocusGlobal(pick_info); + } + } + else if (!pick_info.mPosGlobal.isExactlyZero()) + { + // Hit the ground + gAgentCamera.setFocusOnAvatar(false, ANIMATE); + gAgentCamera.setFocusGlobal(pick_info); + } + + bool zoom_tool = gCameraBtnZoom && (LLToolMgr::getInstance()->getBaseTool() == LLToolCamera::getInstance()); + if (!(pick_info.mKeyMask & MASK_ALT) && + !LLFloaterCamera::inFreeCameraMode() && + !zoom_tool && + gAgentCamera.cameraThirdPerson() && + gViewerWindow->getLeftMouseDown() && + !gSavedSettings.getBOOL("FreezeTime") && + (hit_obj == gAgentAvatarp || + (hit_obj && hit_obj->isAttachment() && LLVOAvatar::findAvatarFromAttachment(hit_obj)->isSelf()))) + { + LLToolCamera::getInstance()->mMouseSteering = true; + } + + } + + camera->mValidClickPoint = true; + + if( CAMERA_MODE_CUSTOMIZE_AVATAR == gAgentCamera.getCameraMode() ) + { + gAgentCamera.setFocusOnAvatar(false, false); + + LLVector3d cam_pos = gAgentCamera.getCameraPositionGlobal(); + + gAgentCamera.setCameraPosAndFocusGlobal( cam_pos, pick_info.mPosGlobal, pick_info.mObjectID); + } +} + + +// "Let go" of the mouse, for example on mouse up or when +// we lose mouse capture. This ensures that cursor becomes visible +// if a modal dialog pops up during Alt-Zoom. JC +void LLToolCamera::releaseMouse() +{ + // Need to tell the sim that the mouse button is up, since this + // tool is no longer working and cursor is visible (despite actual + // mouse button status). + LLTool::handleMouseUp(mMouseUpX, mMouseUpY, mMouseUpMask); + + gViewerWindow->showCursor(); + + //for the situation when left click was performed on the Agent + if (!LLFloaterCamera::inFreeCameraMode()) + { + LLToolMgr::getInstance()->clearTransientTool(); + } + + mMouseSteering = false; + mValidClickPoint = false; + mOutsideSlopX = false; + mOutsideSlopY = false; +} + + +bool LLToolCamera::handleMouseUp(S32 x, S32 y, MASK mask) +{ + // Claim that we're mousing up somewhere + mMouseUpX = x; + mMouseUpY = y; + mMouseUpMask = mask; + + if (hasMouseCapture()) + { + // Do not move camera if we haven't gotten a pick + if (!mClickPickPending) + { + if (mValidClickPoint) + { + if (CAMERA_MODE_CUSTOMIZE_AVATAR == gAgentCamera.getCameraMode()) + { + LLCoordGL mouse_pos; + LLVector3 focus_pos = gAgent.getPosAgentFromGlobal(gAgentCamera.getFocusGlobal()); + bool success = LLViewerCamera::getInstance()->projectPosAgentToScreen(focus_pos, mouse_pos); + if (success) + { + LLUI::getInstance()->setMousePositionScreen(mouse_pos.mX, mouse_pos.mY); + } + } + else if (mMouseSteering) + { + LLUI::getInstance()->setMousePositionScreen(mMouseDownX, mMouseDownY); + } + else + { + gViewerWindow->moveCursorToCenter(); + } + } + else + { + // not a valid zoomable object + LLUI::getInstance()->setMousePositionScreen(mMouseDownX, mMouseDownY); + } + } + + // calls releaseMouse() internally + setMouseCapture(false); + } + else + { + releaseMouse(); + } + + return true; +} + + +bool LLToolCamera::handleHover(S32 x, S32 y, MASK mask) +{ + S32 dx = gViewerWindow->getCurrentMouseDX(); + S32 dy = gViewerWindow->getCurrentMouseDY(); + + if (hasMouseCapture() && mValidClickPoint) + { + mAccumX += llabs(dx); + mAccumY += llabs(dy); + + if (mAccumX >= SLOP_RANGE) + { + mOutsideSlopX = true; + } + + if (mAccumY >= SLOP_RANGE) + { + mOutsideSlopY = true; + } + } + + if (mOutsideSlopX || mOutsideSlopY) + { + if (!mValidClickPoint) + { + LL_DEBUGS("UserInput") << "hover handled by LLToolFocus [invalid point]" << LL_ENDL; + gViewerWindow->setCursor(UI_CURSOR_NO); + gViewerWindow->showCursor(); + return true; + } + + if (gCameraBtnOrbit || + mask == MASK_ORBIT || + mask == (MASK_ALT | MASK_ORBIT)) + { + // Orbit tool + if (hasMouseCapture()) + { + const F32 RADIANS_PER_PIXEL = 360.f * DEG_TO_RAD / gViewerWindow->getWorldViewWidthScaled(); + + if (dx != 0) + { + gAgentCamera.cameraOrbitAround( -dx * RADIANS_PER_PIXEL ); + } + + if (dy != 0) + { + gAgentCamera.cameraOrbitOver( -dy * RADIANS_PER_PIXEL ); + } + + gViewerWindow->moveCursorToCenter(); + } + LL_DEBUGS("UserInput") << "hover handled by LLToolFocus [active]" << LL_ENDL; + } + else if ( gCameraBtnPan || + mask == MASK_PAN || + mask == (MASK_PAN | MASK_ALT) ) + { + // Pan tool + if (hasMouseCapture()) + { + LLVector3d camera_to_focus = gAgentCamera.getCameraPositionGlobal(); + camera_to_focus -= gAgentCamera.getFocusGlobal(); + F32 dist = (F32) camera_to_focus.normVec(); + + // Fudge factor for pan + F32 meters_per_pixel = 3.f * dist / gViewerWindow->getWorldViewWidthScaled(); + + if (dx != 0) + { + gAgentCamera.cameraPanLeft( dx * meters_per_pixel ); + } + + if (dy != 0) + { + gAgentCamera.cameraPanUp( -dy * meters_per_pixel ); + } + + gViewerWindow->moveCursorToCenter(); + } + LL_DEBUGS("UserInput") << "hover handled by LLToolPan" << LL_ENDL; + } + else if (gCameraBtnZoom) + { + // Zoom tool + if (hasMouseCapture()) + { + + const F32 RADIANS_PER_PIXEL = 360.f * DEG_TO_RAD / gViewerWindow->getWorldViewWidthScaled(); + + if (dx != 0) + { + gAgentCamera.cameraOrbitAround( -dx * RADIANS_PER_PIXEL ); + } + + const F32 IN_FACTOR = 0.99f; + + if (dy != 0 && mOutsideSlopY ) + { + if (mMouseSteering) + { + gAgentCamera.cameraOrbitOver( -dy * RADIANS_PER_PIXEL ); + } + else + { + gAgentCamera.cameraZoomIn( pow( IN_FACTOR, dy ) ); + } + } + + gViewerWindow->moveCursorToCenter(); + } + + LL_DEBUGS("UserInput") << "hover handled by LLToolZoom" << LL_ENDL; + } + } + + if (gCameraBtnOrbit || + mask == MASK_ORBIT || + mask == (MASK_ALT | MASK_ORBIT)) + { + gViewerWindow->setCursor(UI_CURSOR_TOOLCAMERA); + } + else if ( gCameraBtnPan || + mask == MASK_PAN || + mask == (MASK_PAN | MASK_ALT) ) + { + gViewerWindow->setCursor(UI_CURSOR_TOOLPAN); + } + else + { + gViewerWindow->setCursor(UI_CURSOR_TOOLZOOMIN); + } + + return true; +} + + +void LLToolCamera::onMouseCaptureLost() +{ + releaseMouse(); +} diff --git a/indra/newview/lltoolfocus.h b/indra/newview/lltoolfocus.h index e7a78e0468..c2460e7aa4 100644 --- a/indra/newview/lltoolfocus.h +++ b/indra/newview/lltoolfocus.h @@ -1,82 +1,82 @@ -/** - * @file lltoolfocus.h - * @brief A tool to set the build focus point. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLTOOLFOCUS_H -#define LL_LLTOOLFOCUS_H - -#include "lltool.h" - -class LLPickInfo; - -class LLToolCamera -: public LLTool, public LLSingleton -{ - LLSINGLETON(LLToolCamera); - virtual ~LLToolCamera(); -public: - - virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; - virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; - virtual bool handleHover(S32 x, S32 y, MASK mask) override; - - virtual void onMouseCaptureLost() override; - - virtual void handleSelect() override; - virtual void handleDeselect() override; - - virtual LLTool* getOverrideTool(MASK mask) override { return NULL; } - - void setClickPickPending() { mClickPickPending = true; } - static void pickCallback(const LLPickInfo& pick_info); - bool mouseSteerMode() { return mMouseSteering; } - -protected: - // called from handleMouseUp and onMouseCaptureLost to "let go" - // of the mouse and make it visible JC - void releaseMouse(); - -protected: - S32 mAccumX; - S32 mAccumY; - S32 mMouseDownX; - S32 mMouseDownY; - bool mOutsideSlopX; - bool mOutsideSlopY; - bool mValidClickPoint; - bool mClickPickPending; - bool mValidSelection; - bool mMouseSteering; - S32 mMouseUpX; // needed for releaseMouse() - S32 mMouseUpY; - MASK mMouseUpMask; -}; - - -extern bool gCameraBtnOrbit; -extern bool gCameraBtnPan; -extern bool gCameraBtnZoom; - -#endif +/** + * @file lltoolfocus.h + * @brief A tool to set the build focus point. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLTOOLFOCUS_H +#define LL_LLTOOLFOCUS_H + +#include "lltool.h" + +class LLPickInfo; + +class LLToolCamera +: public LLTool, public LLSingleton +{ + LLSINGLETON(LLToolCamera); + virtual ~LLToolCamera(); +public: + + virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; + virtual bool handleHover(S32 x, S32 y, MASK mask) override; + + virtual void onMouseCaptureLost() override; + + virtual void handleSelect() override; + virtual void handleDeselect() override; + + virtual LLTool* getOverrideTool(MASK mask) override { return NULL; } + + void setClickPickPending() { mClickPickPending = true; } + static void pickCallback(const LLPickInfo& pick_info); + bool mouseSteerMode() { return mMouseSteering; } + +protected: + // called from handleMouseUp and onMouseCaptureLost to "let go" + // of the mouse and make it visible JC + void releaseMouse(); + +protected: + S32 mAccumX; + S32 mAccumY; + S32 mMouseDownX; + S32 mMouseDownY; + bool mOutsideSlopX; + bool mOutsideSlopY; + bool mValidClickPoint; + bool mClickPickPending; + bool mValidSelection; + bool mMouseSteering; + S32 mMouseUpX; // needed for releaseMouse() + S32 mMouseUpY; + MASK mMouseUpMask; +}; + + +extern bool gCameraBtnOrbit; +extern bool gCameraBtnPan; +extern bool gCameraBtnZoom; + +#endif diff --git a/indra/newview/lltoolgrab.cpp b/indra/newview/lltoolgrab.cpp index 4aacc47643..f3ec655f00 100644 --- a/indra/newview/lltoolgrab.cpp +++ b/indra/newview/lltoolgrab.cpp @@ -1,1212 +1,1212 @@ -/** - * @file lltoolgrab.cpp - * @brief LLToolGrab class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lltoolgrab.h" - -// library headers -#include "indra_constants.h" // for agent control flags -#include "llviewercontrol.h" -#include "llquaternion.h" -#include "llbox.h" -#include "message.h" -#include "llview.h" -#include "llfontgl.h" -#include "llui.h" - -// newview headers -#include "llagent.h" -#include "llagentcamera.h" -#include "lldrawable.h" -#include "llfloatertools.h" -#include "llhudeffect.h" -#include "llhudmanager.h" -#include "llregionposition.h" -#include "llselectmgr.h" -#include "llstatusbar.h" -#include "lltoolmgr.h" -#include "lltoolpie.h" -#include "llviewercamera.h" -#include "llviewerinput.h" -#include "llviewerobject.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llvoavatarself.h" -#include "llworld.h" -#include "llmenugl.h" - -const S32 SLOP_DIST_SQ = 4; - -// Override modifier key behavior with these buttons -bool gGrabBtnVertical = false; -bool gGrabBtnSpin = false; -LLTool* gGrabTransientTool = NULL; -extern bool gDebugClicks; - -// -// Methods -// -LLToolGrabBase::LLToolGrabBase( LLToolComposite* composite ) -: LLTool( std::string("Grab"), composite ), - mMode( GRAB_INACTIVE ), - mVerticalDragging( false ), - mHitLand(false), - mLastMouseX(0), - mLastMouseY(0), - mAccumDeltaX(0), - mAccumDeltaY(0), - mHasMoved( false ), - mOutsideSlop(false), - mDeselectedThisClick(false), - mLastFace(0), - mSpinGrabbing( false ), - mSpinRotation(), - mClickedInMouselook( false ), - mHideBuildHighlight(false) -{ } - -LLToolGrabBase::~LLToolGrabBase() -{ } - - -// virtual -void LLToolGrabBase::handleSelect() -{ - if(gFloaterTools) - { - // viewer can crash during startup if we don't check. - gFloaterTools->setStatusText("grab"); - // in case we start from tools floater, we count any selection as valid - mValidSelection = gFloaterTools->getVisible(); - } - gGrabBtnVertical = false; - gGrabBtnSpin = false; -} - -void LLToolGrabBase::handleDeselect() -{ - if( hasMouseCapture() ) - { - setMouseCapture( false ); - } - - // Make sure that temporary(invalid) selection won't pass anywhere except pie tool. - MASK override_mask = gKeyboard ? gKeyboard->currentMask(true) : 0; - if (!mValidSelection && (override_mask != MASK_NONE || (gFloaterTools && gFloaterTools->getVisible()))) - { - LLMenuGL::sMenuContainer->hideMenus(); - LLSelectMgr::getInstance()->validateSelection(); - } - -} - -bool LLToolGrabBase::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - if (gDebugClicks) - { - LL_INFOS() << "LLToolGrab handleDoubleClick (becoming mouseDown)" << LL_ENDL; - } - - return false; -} - -bool LLToolGrabBase::handleMouseDown(S32 x, S32 y, MASK mask) -{ - if (gDebugClicks) - { - LL_INFOS() << "LLToolGrab handleMouseDown" << LL_ENDL; - } - - LLTool::handleMouseDown(x, y, mask); - - // leftButtonGrabbed() checks if controls are reserved by scripts, but does not take masks into account - if (!gAgent.leftButtonGrabbed() || ((mask & DEFAULT_GRAB_MASK) != 0 && !gAgentCamera.cameraMouselook())) - { - // can grab transparent objects (how touch event propagates, scripters rely on this) - gViewerWindow->pickAsync(x, y, mask, pickCallback, /*bool pick_transparent*/ true); - } - mClickedInMouselook = gAgentCamera.cameraMouselook(); - - if (mClickedInMouselook && gViewerInput.isLMouseHandlingDefault(MODE_FIRST_PERSON)) - { - // LLToolCompGun::handleMouseDown handles the event if ML controls are grabed, - // but LLToolGrabBase is often the end point for mouselook clicks if ML controls - // are not grabbed and LLToolGrabBase::handleMouseDown consumes the event, - // so send clicks from here. - // We are sending specifically CONTROL_LBUTTON_DOWN instead of _ML_ version. - gAgent.setControlFlags(AGENT_CONTROL_LBUTTON_DOWN); - - // Todo: LLToolGrabBase probably shouldn't consume the event if there is nothing - // to grab in Mouselook, it intercepts handling in scanMouse - } - return true; -} - -void LLToolGrabBase::pickCallback(const LLPickInfo& pick_info) -{ - LLToolGrab::getInstance()->mGrabPick = pick_info; - LLViewerObject *objectp = pick_info.getObject(); - - bool extend_select = (pick_info.mKeyMask & MASK_SHIFT); - - if (!extend_select && !LLSelectMgr::getInstance()->getSelection()->isEmpty()) - { - LLSelectMgr::getInstance()->deselectAll(); - LLToolGrab::getInstance()->mDeselectedThisClick = true; - } - else - { - LLToolGrab::getInstance()->mDeselectedThisClick = false; - } - - // if not over object, do nothing - if (!objectp) - { - LLToolGrab::getInstance()->setMouseCapture(true); - LLToolGrab::getInstance()->mMode = GRAB_NOOBJECT; - LLToolGrab::getInstance()->mGrabPick.mObjectID.setNull(); - } - else - { - LLToolGrab::getInstance()->handleObjectHit(LLToolGrab::getInstance()->mGrabPick); - } -} - -bool LLToolGrabBase::handleObjectHit(const LLPickInfo& info) -{ - mGrabPick = info; - LLViewerObject* objectp = mGrabPick.getObject(); - - if (gDebugClicks) - { - LL_INFOS() << "LLToolGrab handleObjectHit " << info.mMousePt.mX << "," << info.mMousePt.mY << LL_ENDL; - } - - if (NULL == objectp) // unexpected - { - LL_WARNS() << "objectp was NULL; returning false" << LL_ENDL; - return false; - } - - if (objectp->isAvatar()) - { - if (gGrabTransientTool) - { - gBasicToolset->selectTool( gGrabTransientTool ); - gGrabTransientTool = NULL; - } - return true; - } - - setMouseCapture( true ); - - // Grabs always start from the root - // objectp = (LLViewerObject *)objectp->getRoot(); - - LLViewerObject* parent = objectp->getRootEdit(); - bool script_touch = (objectp->flagHandleTouch()) || (parent && parent->flagHandleTouch()); - - // Clicks on scripted or physical objects are temporary grabs, so - // not "Build mode" - mHideBuildHighlight = script_touch || objectp->flagUsePhysics(); - - if (!objectp->flagUsePhysics()) - { - if (script_touch) - { - mMode = GRAB_NONPHYSICAL; // if it has a script, use the non-physical grab - } - else - { - // In mouselook, we shouldn't be able to grab non-physical, - // non-touchable objects. If it has a touch handler, we - // do grab it (so llDetectedGrab works), but movement is - // blocked on the server side. JC - if (gAgentCamera.cameraMouselook()) - { - mMode = GRAB_LOCKED; - gViewerWindow->hideCursor(); - gViewerWindow->moveCursorToCenter(); - } - else if (objectp->permMove() && !objectp->isPermanentEnforced()) - { - mMode = GRAB_ACTIVE_CENTER; - gViewerWindow->hideCursor(); - gViewerWindow->moveCursorToCenter(); - } - else - { - mMode = GRAB_LOCKED; - } - - - } - } - else if( objectp->flagCharacter() || !objectp->permMove() || objectp->isPermanentEnforced()) - { - // if mouse is over a physical object without move permission, show feedback if user tries to move it. - mMode = GRAB_LOCKED; - - // Don't bail out here, go on and grab so buttons can get - // their "touched" event. - } - else - { - // if mouse is over a physical object with move permission, - // select it and enter "grab" mode (hiding cursor, etc.) - - mMode = GRAB_ACTIVE_CENTER; - - gViewerWindow->hideCursor(); - gViewerWindow->moveCursorToCenter(); - } - - // Always send "touched" message - - mLastMouseX = gViewerWindow->getCurrentMouseX(); - mLastMouseY = gViewerWindow->getCurrentMouseY(); - mAccumDeltaX = 0; - mAccumDeltaY = 0; - mHasMoved = false; - mOutsideSlop = false; - - mVerticalDragging = (info.mKeyMask == MASK_VERTICAL) || gGrabBtnVertical; - - startGrab(); - - if ((info.mKeyMask == MASK_SPIN) || gGrabBtnSpin) - { - startSpin(); - } - - LLSelectMgr::getInstance()->updateSelectionCenter(); // update selection beam - - // update point at - LLViewerObject *edit_object = info.getObject(); - if (edit_object && info.mPickType != LLPickInfo::PICK_FLORA) - { - LLVector3 local_edit_point = gAgent.getPosAgentFromGlobal(info.mPosGlobal); - local_edit_point -= edit_object->getPositionAgent(); - local_edit_point = local_edit_point * ~edit_object->getRenderRotation(); - gAgentCamera.setPointAt(POINTAT_TARGET_GRAB, edit_object, local_edit_point ); - gAgentCamera.setLookAt(LOOKAT_TARGET_SELECT, edit_object, local_edit_point ); - } - - // on transient grabs (clicks on world objects), kill the grab immediately - if (!gViewerWindow->getLeftMouseDown() - && gGrabTransientTool - && (mMode == GRAB_NONPHYSICAL || mMode == GRAB_LOCKED)) - { - gBasicToolset->selectTool( gGrabTransientTool ); - gGrabTransientTool = NULL; - } - - return true; -} - - -void LLToolGrabBase::startSpin() -{ - LLViewerObject* objectp = mGrabPick.getObject(); - if (!objectp) - { - return; - } - mSpinGrabbing = true; - - // Was saveSelectedObjectTransform() - LLViewerObject *root = (LLViewerObject *)objectp->getRoot(); - mSpinRotation = root->getRotation(); - - LLMessageSystem *msg = gMessageSystem; - msg->newMessageFast(_PREHASH_ObjectSpinStart); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ObjectData); - msg->addUUIDFast(_PREHASH_ObjectID, mGrabPick.mObjectID ); - msg->sendMessage( objectp->getRegion()->getHost() ); -} - - -void LLToolGrabBase::stopSpin() -{ - mSpinGrabbing = false; - - LLViewerObject* objectp = mGrabPick.getObject(); - if (!objectp) - { - return; - } - - LLMessageSystem *msg = gMessageSystem; - switch(mMode) - { - case GRAB_ACTIVE_CENTER: - case GRAB_NONPHYSICAL: - case GRAB_LOCKED: - msg->newMessageFast(_PREHASH_ObjectSpinStop); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ObjectData); - msg->addUUIDFast(_PREHASH_ObjectID, objectp->getID() ); - msg->sendMessage( objectp->getRegion()->getHost() ); - break; - - case GRAB_NOOBJECT: - case GRAB_INACTIVE: - default: - // do nothing - break; - } -} - - -void LLToolGrabBase::startGrab() -{ - // Compute grab_offset in the OBJECT's root's coordinate frame - // (sometimes root == object) - LLViewerObject* objectp = mGrabPick.getObject(); - if (!objectp) - { - return; - } - - LLViewerObject *root = (LLViewerObject *)objectp->getRoot(); - - // drag from center - LLVector3d grab_start_global = root->getPositionGlobal(); - - // Where the grab starts, relative to the center of the root object of the set. - // JC - This code looks wonky, but I believe it does the right thing. - // Otherwise, when you grab a linked object set, it "pops" on the start - // of the drag. - LLVector3d grab_offsetd = root->getPositionGlobal() - objectp->getPositionGlobal(); - - LLVector3 grab_offset; - grab_offset.setVec(grab_offsetd); - - LLQuaternion rotation = root->getRotation(); - rotation.conjQuat(); - grab_offset = grab_offset * rotation; - - // This planar drag starts at the grab point - mDragStartPointGlobal = grab_start_global; - mDragStartFromCamera = grab_start_global - gAgentCamera.getCameraPositionGlobal(); - - send_ObjectGrab_message(objectp, mGrabPick, grab_offset); - - mGrabOffsetFromCenterInitial = grab_offset; - mGrabHiddenOffsetFromCamera = mDragStartFromCamera; - - mGrabTimer.reset(); - - mLastUVCoords = mGrabPick.mUVCoords; - mLastSTCoords = mGrabPick.mSTCoords; - mLastFace = mGrabPick.mObjectFace; - mLastIntersection = mGrabPick.mIntersection; - mLastNormal = mGrabPick.mNormal; - mLastBinormal = mGrabPick.mBinormal; - mLastGrabPos = LLVector3(-1.f, -1.f, -1.f); -} - - -bool LLToolGrabBase::handleHover(S32 x, S32 y, MASK mask) -{ - if (!gViewerWindow->getLeftMouseDown()) - { - gViewerWindow->setCursor(UI_CURSOR_TOOLGRAB); - setMouseCapture(false); - return true; - } - - // Do the right hover based on mode - switch( mMode ) - { - case GRAB_ACTIVE_CENTER: - handleHoverActive( x, y, mask ); // cursor hidden - break; - - case GRAB_NONPHYSICAL: - handleHoverNonPhysical(x, y, mask); - break; - - case GRAB_INACTIVE: - handleHoverInactive( x, y, mask ); // cursor set here - break; - - case GRAB_NOOBJECT: - case GRAB_LOCKED: - handleHoverFailed( x, y, mask ); - break; - - } - - mLastMouseX = x; - mLastMouseY = y; - - return true; -} - -const F32 GRAB_SENSITIVITY_X = 0.0075f; -const F32 GRAB_SENSITIVITY_Y = 0.0075f; - - - - -// Dragging. -void LLToolGrabBase::handleHoverActive(S32 x, S32 y, MASK mask) -{ - LLViewerObject* objectp = mGrabPick.getObject(); - if (!objectp || !hasMouseCapture() ) return; - if (objectp->isDead()) - { - // Bail out of drag because object has been killed - setMouseCapture(false); - return; - } - - //-------------------------------------------------- - // Determine target mode - //-------------------------------------------------- - bool vertical_dragging = false; - bool spin_grabbing = false; - if ((mask == MASK_VERTICAL) - || (gGrabBtnVertical && (mask != MASK_SPIN))) - { - vertical_dragging = true; - } - else if ((mask == MASK_SPIN) - || (gGrabBtnSpin && (mask != MASK_VERTICAL))) - { - spin_grabbing = true; - } - - //-------------------------------------------------- - // Toggle spinning - //-------------------------------------------------- - if (mSpinGrabbing && !spin_grabbing) - { - // user released or switched mask key(s), stop spinning - stopSpin(); - } - else if (!mSpinGrabbing && spin_grabbing) - { - // user pressed mask key(s), start spinning - startSpin(); - } - mSpinGrabbing = spin_grabbing; - - //-------------------------------------------------- - // Toggle vertical dragging - //-------------------------------------------------- - if (mVerticalDragging && !vertical_dragging) - { - // ...switch to horizontal dragging - mDragStartPointGlobal = gViewerWindow->clickPointInWorldGlobal(x, y, objectp); - mDragStartFromCamera = mDragStartPointGlobal - gAgentCamera.getCameraPositionGlobal(); - } - else if (!mVerticalDragging && vertical_dragging) - { - // ...switch to vertical dragging - mDragStartPointGlobal = gViewerWindow->clickPointInWorldGlobal(x, y, objectp); - mDragStartFromCamera = mDragStartPointGlobal - gAgentCamera.getCameraPositionGlobal(); - } - mVerticalDragging = vertical_dragging; - - const F32 RADIANS_PER_PIXEL_X = 0.01f; - const F32 RADIANS_PER_PIXEL_Y = 0.01f; - - S32 dx = gViewerWindow->getCurrentMouseDX(); - S32 dy = gViewerWindow->getCurrentMouseDY(); - - if (dx != 0 || dy != 0) - { - mAccumDeltaX += dx; - mAccumDeltaY += dy; - S32 dist_sq = mAccumDeltaX * mAccumDeltaX + mAccumDeltaY * mAccumDeltaY; - if (dist_sq > SLOP_DIST_SQ) - { - mOutsideSlop = true; - } - - // mouse has moved outside center - mHasMoved = true; - - if (mSpinGrabbing) - { - //------------------------------------------------------ - // Handle spinning - //------------------------------------------------------ - - // x motion maps to rotation around vertical axis - LLVector3 up(0.f, 0.f, 1.f); - LLQuaternion rotation_around_vertical( dx*RADIANS_PER_PIXEL_X, up ); - - // y motion maps to rotation around left axis - const LLVector3 &agent_left = LLViewerCamera::getInstance()->getLeftAxis(); - LLQuaternion rotation_around_left( dy*RADIANS_PER_PIXEL_Y, agent_left ); - - // compose with current rotation - mSpinRotation = mSpinRotation * rotation_around_vertical; - mSpinRotation = mSpinRotation * rotation_around_left; - - // TODO: Throttle these - LLMessageSystem *msg = gMessageSystem; - msg->newMessageFast(_PREHASH_ObjectSpinUpdate); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ObjectData); - msg->addUUIDFast(_PREHASH_ObjectID, objectp->getID() ); - msg->addQuatFast(_PREHASH_Rotation, mSpinRotation ); - msg->sendMessage( objectp->getRegion()->getHost() ); - } - else - { - //------------------------------------------------------ - // Handle grabbing - //------------------------------------------------------ - - LLVector3d x_part; - x_part.setVec(LLViewerCamera::getInstance()->getLeftAxis()); - x_part.mdV[VZ] = 0.0; - x_part.normVec(); - - LLVector3d y_part; - if( mVerticalDragging ) - { - y_part.setVec(LLViewerCamera::getInstance()->getUpAxis()); - // y_part.setVec(0.f, 0.f, 1.f); - } - else - { - // drag toward camera - y_part = x_part % LLVector3d::z_axis; - y_part.mdV[VZ] = 0.0; - y_part.normVec(); - } - - mGrabHiddenOffsetFromCamera = mGrabHiddenOffsetFromCamera - + (x_part * (-dx * GRAB_SENSITIVITY_X)) - + (y_part * ( dy * GRAB_SENSITIVITY_Y)); - - - // Send the message to the viewer. - F32 dt = mGrabTimer.getElapsedTimeAndResetF32(); - U32 dt_milliseconds = (U32) (1000.f * dt); - - // need to return offset from mGrabStartPoint - LLVector3d grab_point_global; - - grab_point_global = gAgentCamera.getCameraPositionGlobal() + mGrabHiddenOffsetFromCamera; - - /* Snap to grid disabled for grab tool - very confusing - // Handle snapping to grid, but only when the tool is formally selected. - bool snap_on = gSavedSettings.getBOOL("SnapEnabled"); - if (snap_on && !gGrabTransientTool) - { - F64 snap_size = gSavedSettings.getF32("GridResolution"); - U8 snap_dimensions = (mVerticalDragging ? 3 : 2); - - for (U8 i = 0; i < snap_dimensions; i++) - { - grab_point_global.mdV[i] += snap_size / 2; - grab_point_global.mdV[i] -= fmod(grab_point_global.mdV[i], snap_size); - } - } - */ - - // Don't let object centers go underground. - F32 land_height = LLWorld::getInstance()->resolveLandHeightGlobal(grab_point_global); - - if (grab_point_global.mdV[VZ] < land_height) - { - grab_point_global.mdV[VZ] = land_height; - } - - // For safety, cap heights where objects can be dragged - if (grab_point_global.mdV[VZ] > MAX_OBJECT_Z) - { - grab_point_global.mdV[VZ] = MAX_OBJECT_Z; - } - - grab_point_global = LLWorld::getInstance()->clipToVisibleRegions(mDragStartPointGlobal, grab_point_global); - // propagate constrained grab point back to grab offset - mGrabHiddenOffsetFromCamera = grab_point_global - gAgentCamera.getCameraPositionGlobal(); - - // Handle auto-rotation at screen edge. - LLVector3 grab_pos_agent = gAgent.getPosAgentFromGlobal( grab_point_global ); - - LLCoordGL grab_center_gl( gViewerWindow->getWorldViewWidthScaled() / 2, gViewerWindow->getWorldViewHeightScaled() / 2); - LLViewerCamera::getInstance()->projectPosAgentToScreen(grab_pos_agent, grab_center_gl); - - const S32 ROTATE_H_MARGIN = gViewerWindow->getWorldViewWidthScaled() / 20; - const F32 ROTATE_ANGLE_PER_SECOND = 30.f * DEG_TO_RAD; - const F32 rotate_angle = ROTATE_ANGLE_PER_SECOND / gFPSClamped; - // ...build mode moves camera about focus point - if (grab_center_gl.mX < ROTATE_H_MARGIN) - { - if (gAgentCamera.getFocusOnAvatar()) - { - gAgent.yaw(rotate_angle); - } - else - { - gAgentCamera.cameraOrbitAround(rotate_angle); - } - } - else if (grab_center_gl.mX > gViewerWindow->getWorldViewWidthScaled() - ROTATE_H_MARGIN) - { - if (gAgentCamera.getFocusOnAvatar()) - { - gAgent.yaw(-rotate_angle); - } - else - { - gAgentCamera.cameraOrbitAround(-rotate_angle); - } - } - - // Don't move above top of screen or below bottom - if ((grab_center_gl.mY < gViewerWindow->getWorldViewHeightScaled() - 6) - && (grab_center_gl.mY > 24)) - { - // Transmit update to simulator - LLVector3 grab_pos_region = objectp->getRegion()->getPosRegionFromGlobal( grab_point_global ); - - LLMessageSystem *msg = gMessageSystem; - msg->newMessageFast(_PREHASH_ObjectGrabUpdate); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ObjectData); - msg->addUUIDFast(_PREHASH_ObjectID, objectp->getID() ); - msg->addVector3Fast(_PREHASH_GrabOffsetInitial, mGrabOffsetFromCenterInitial ); - msg->addVector3Fast(_PREHASH_GrabPosition, grab_pos_region ); - msg->addU32Fast(_PREHASH_TimeSinceLast, dt_milliseconds ); - msg->nextBlock("SurfaceInfo"); - msg->addVector3("UVCoord", LLVector3(mGrabPick.mUVCoords)); - msg->addVector3("STCoord", LLVector3(mGrabPick.mSTCoords)); - msg->addS32Fast(_PREHASH_FaceIndex, mGrabPick.mObjectFace); - msg->addVector3("Position", mGrabPick.mIntersection); - msg->addVector3("Normal", mGrabPick.mNormal); - msg->addVector3("Binormal", mGrabPick.mBinormal); - - msg->sendMessage( objectp->getRegion()->getHost() ); - } - } - - gViewerWindow->moveCursorToCenter(); - - LLSelectMgr::getInstance()->updateSelectionCenter(); - - } - - // once we've initiated a drag, lock the camera down - if (mHasMoved) - { - if (!gAgentCamera.cameraMouselook() && - !objectp->isHUDAttachment() && - objectp->getRoot() == gAgentAvatarp->getRoot()) - { - // we are essentially editing object position - if (!gSavedSettings.getBOOL("EditCameraMovement")) - { - // force focus to point in space where we were looking previously - // Example of use: follow cam scripts shouldn't affect you when movng objects arouns - gAgentCamera.setFocusGlobal(gAgentCamera.calcFocusPositionTargetGlobal(), LLUUID::null); - gAgentCamera.setFocusOnAvatar(false, ANIMATE); - } - } - else - { - gAgentCamera.clearFocusObject(); - } - } - - // HACK to avoid assert: error checking system makes sure that the cursor is set during every handleHover. This is actually a no-op since the cursor is hidden. - gViewerWindow->setCursor(UI_CURSOR_ARROW); - - LL_DEBUGS("UserInput") << "hover handled by LLToolGrab (active) [cursor hidden]" << LL_ENDL; -} - - -void LLToolGrabBase::handleHoverNonPhysical(S32 x, S32 y, MASK mask) -{ - LLViewerObject* objectp = mGrabPick.getObject(); - if (!objectp || !hasMouseCapture() ) return; - if (objectp->isDead()) - { - // Bail out of drag because object has been killed - setMouseCapture(false); - return; - } - - LLPickInfo pick = mGrabPick; - pick.mMousePt = LLCoordGL(x, y); - pick.getSurfaceInfo(); - - // compute elapsed time - F32 dt = mGrabTimer.getElapsedTimeAndResetF32(); - U32 dt_milliseconds = (U32) (1000.f * dt); - - // i'm not a big fan of the following code - it's been culled from the physical grab case. - // ideally these two would be nicely integrated - but the code in that method is a serious - // mess of spaghetti. so here we go: - - LLVector3 grab_pos_region(0,0,0); - - const bool SUPPORT_LLDETECTED_GRAB = true; - if (SUPPORT_LLDETECTED_GRAB) - { - //-------------------------------------------------- - // Toggle vertical dragging - //-------------------------------------------------- - if (!(mask == MASK_VERTICAL) && !gGrabBtnVertical) - { - mVerticalDragging = false; - } - - else if ((gGrabBtnVertical && (mask != MASK_SPIN)) - || (mask == MASK_VERTICAL)) - { - mVerticalDragging = true; - } - - S32 dx = x - mLastMouseX; - S32 dy = y - mLastMouseY; - - if (dx != 0 || dy != 0) - { - mAccumDeltaX += dx; - mAccumDeltaY += dy; - - S32 dist_sq = mAccumDeltaX * mAccumDeltaX + mAccumDeltaY * mAccumDeltaY; - if (dist_sq > SLOP_DIST_SQ) - { - mOutsideSlop = true; - } - - // mouse has moved - mHasMoved = true; - - //------------------------------------------------------ - // Handle grabbing - //------------------------------------------------------ - - LLVector3d x_part; - x_part.setVec(LLViewerCamera::getInstance()->getLeftAxis()); - x_part.mdV[VZ] = 0.0; - x_part.normVec(); - - LLVector3d y_part; - if( mVerticalDragging ) - { - y_part.setVec(LLViewerCamera::getInstance()->getUpAxis()); - // y_part.setVec(0.f, 0.f, 1.f); - } - else - { - // drag toward camera - y_part = x_part % LLVector3d::z_axis; - y_part.mdV[VZ] = 0.0; - y_part.normVec(); - } - - mGrabHiddenOffsetFromCamera = mGrabHiddenOffsetFromCamera - + (x_part * (-dx * GRAB_SENSITIVITY_X)) - + (y_part * ( dy * GRAB_SENSITIVITY_Y)); - - } - - // need to return offset from mGrabStartPoint - LLVector3d grab_point_global = gAgentCamera.getCameraPositionGlobal() + mGrabHiddenOffsetFromCamera; - grab_pos_region = objectp->getRegion()->getPosRegionFromGlobal( grab_point_global ); - } - - - // only send message if something has changed since last message - - bool changed_since_last_update = false; - - // test if touch data needs to be updated - if ((pick.mObjectFace != mLastFace) || - (pick.mUVCoords != mLastUVCoords) || - (pick.mSTCoords != mLastSTCoords) || - (pick.mIntersection != mLastIntersection) || - (pick.mNormal != mLastNormal) || - (pick.mBinormal != mLastBinormal) || - (grab_pos_region != mLastGrabPos)) - { - changed_since_last_update = true; - } - - if (changed_since_last_update) - { - LLMessageSystem *msg = gMessageSystem; - msg->newMessageFast(_PREHASH_ObjectGrabUpdate); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ObjectData); - msg->addUUIDFast(_PREHASH_ObjectID, objectp->getID() ); - msg->addVector3Fast(_PREHASH_GrabOffsetInitial, mGrabOffsetFromCenterInitial ); - msg->addVector3Fast(_PREHASH_GrabPosition, grab_pos_region ); - msg->addU32Fast(_PREHASH_TimeSinceLast, dt_milliseconds ); - msg->nextBlock("SurfaceInfo"); - msg->addVector3("UVCoord", LLVector3(pick.mUVCoords)); - msg->addVector3("STCoord", LLVector3(pick.mSTCoords)); - msg->addS32Fast(_PREHASH_FaceIndex, pick.mObjectFace); - msg->addVector3("Position", pick.mIntersection); - msg->addVector3("Normal", pick.mNormal); - msg->addVector3("Binormal", pick.mBinormal); - - msg->sendMessage( objectp->getRegion()->getHost() ); - - mLastUVCoords = pick.mUVCoords; - mLastSTCoords = pick.mSTCoords; - mLastFace = pick.mObjectFace; - mLastIntersection = pick.mIntersection; - mLastNormal= pick.mNormal; - mLastBinormal= pick.mBinormal; - mLastGrabPos = grab_pos_region; - } - - // update point-at / look-at - if (pick.mObjectFace != -1) // if the intersection was on the surface of the obejct - { - LLVector3 local_edit_point = pick.mIntersection; - local_edit_point -= objectp->getPositionAgent(); - local_edit_point = local_edit_point * ~objectp->getRenderRotation(); - gAgentCamera.setPointAt(POINTAT_TARGET_GRAB, objectp, local_edit_point ); - gAgentCamera.setLookAt(LOOKAT_TARGET_SELECT, objectp, local_edit_point ); - } - - - - gViewerWindow->setCursor(UI_CURSOR_HAND); -} - - -// Not dragging. Just showing affordances -void LLToolGrabBase::handleHoverInactive(S32 x, S32 y, MASK mask) -{ - // JC - TODO - change cursor based on gGrabBtnVertical, gGrabBtnSpin - LL_DEBUGS("UserInput") << "hover handled by LLToolGrab (inactive-not over editable object)" << LL_ENDL; - gViewerWindow->setCursor(UI_CURSOR_TOOLGRAB); -} - -// User is trying to do something that's not allowed. -void LLToolGrabBase::handleHoverFailed(S32 x, S32 y, MASK mask) -{ - if( GRAB_NOOBJECT == mMode ) - { - gViewerWindow->setCursor(UI_CURSOR_NO); - LL_DEBUGS("UserInput") << "hover handled by LLToolGrab (not on object)" << LL_ENDL; - } - else - { - S32 dist_sq = (x-mGrabPick.mMousePt.mX) * (x-mGrabPick.mMousePt.mX) + (y-mGrabPick.mMousePt.mY) * (y-mGrabPick.mMousePt.mY); - if( mOutsideSlop || dist_sq > SLOP_DIST_SQ ) - { - mOutsideSlop = true; - - switch( mMode ) - { - case GRAB_LOCKED: - gViewerWindow->setCursor(UI_CURSOR_GRABLOCKED); - LL_DEBUGS("UserInput") << "hover handled by LLToolGrab (grab failed, no move permission)" << LL_ENDL; - break; - -// Non physical now handled by handleHoverActive - CRO -// case GRAB_NONPHYSICAL: -// gViewerWindow->setCursor(UI_CURSOR_ARROW); -// LL_DEBUGS("UserInput") << "hover handled by LLToolGrab (grab failed, nonphysical)" << LL_ENDL; -// break; - default: - llassert(0); - } - } - else - { - gViewerWindow->setCursor(UI_CURSOR_ARROW); - LL_DEBUGS("UserInput") << "hover handled by LLToolGrab (grab failed but within slop)" << LL_ENDL; - } - } -} - - - - -bool LLToolGrabBase::handleMouseUp(S32 x, S32 y, MASK mask) -{ - LLTool::handleMouseUp(x, y, mask); - - if (gAgentCamera.cameraMouselook() && gViewerInput.isLMouseHandlingDefault(MODE_FIRST_PERSON)) - { - // LLToolCompGun::handleMouseUp handles the event if ML controls are grabed, - // but LLToolGrabBase is often the end point for mouselook clicks if ML controls - // are not grabbed and LToolGrabBase::handleMouseUp consumes the event, - // so send clicks from here. - // We are sending specifically CONTROL_LBUTTON_UP instead of _ML_ version. - gAgent.setControlFlags(AGENT_CONTROL_LBUTTON_UP); - } - - if( hasMouseCapture() ) - { - setMouseCapture( false ); - } - - mMode = GRAB_INACTIVE; - - if(mClickedInMouselook && !gAgentCamera.cameraMouselook()) - { - mClickedInMouselook = false; - } - else - { - // HACK: Make some grabs temporary - if (gGrabTransientTool) - { - gBasicToolset->selectTool( gGrabTransientTool ); - gGrabTransientTool = NULL; - } - } - - //gAgent.setObjectTracking(gSavedSettings.getBOOL("TrackFocusObject")); - - return true; -} - -void LLToolGrabBase::stopEditing() -{ - if( hasMouseCapture() ) - { - setMouseCapture( false ); - } -} - -void LLToolGrabBase::onMouseCaptureLost() -{ - LLViewerObject* objectp = mGrabPick.getObject(); - if (!objectp) - { - gViewerWindow->showCursor(); - return; - } - // First, fix cursor placement - if( !gAgentCamera.cameraMouselook() - && (GRAB_ACTIVE_CENTER == mMode)) - { - if (objectp->isHUDAttachment()) - { - // ...move cursor "naturally", as if it had moved when hidden - S32 x = mGrabPick.mMousePt.mX + mAccumDeltaX; - S32 y = mGrabPick.mMousePt.mY + mAccumDeltaY; - LLUI::getInstance()->setMousePositionScreen(x, y); - } - else if (mHasMoved) - { - // ...move cursor back to the center of the object - LLVector3 grab_point_agent = objectp->getRenderPosition(); - - LLCoordGL gl_point; - if (LLViewerCamera::getInstance()->projectPosAgentToScreen(grab_point_agent, gl_point)) - { - LLUI::getInstance()->setMousePositionScreen(gl_point.mX, gl_point.mY); - } - } - else - { - // ...move cursor back to click position - LLUI::getInstance()->setMousePositionScreen(mGrabPick.mMousePt.mX, mGrabPick.mMousePt.mY); - } - - gViewerWindow->showCursor(); - } - - stopGrab(); - if (mSpinGrabbing) - stopSpin(); - - mMode = GRAB_INACTIVE; - - mHideBuildHighlight = false; - - mGrabPick.mObjectID.setNull(); - - LLSelectMgr::getInstance()->updateSelectionCenter(); - gAgentCamera.setPointAt(POINTAT_TARGET_CLEAR); - gAgentCamera.setLookAt(LOOKAT_TARGET_CLEAR); - - dialog_refresh_all(); -} - - -void LLToolGrabBase::stopGrab() -{ - LLViewerObject* objectp = mGrabPick.getObject(); - if (!objectp) - { - return; - } - - LLPickInfo pick = mGrabPick; - - if (mMode == GRAB_NONPHYSICAL) - { - // for non-physical (touch) grabs, - // gather surface info for this degrab (mouse-up) - S32 x = gViewerWindow->getCurrentMouseX(); - S32 y = gViewerWindow->getCurrentMouseY(); - pick.mMousePt = LLCoordGL(x, y); - pick.getSurfaceInfo(); - } - - // Next, send messages to simulator - switch(mMode) - { - case GRAB_ACTIVE_CENTER: - case GRAB_NONPHYSICAL: - case GRAB_LOCKED: - send_ObjectDeGrab_message(objectp, pick); - mVerticalDragging = false; - break; - - case GRAB_NOOBJECT: - case GRAB_INACTIVE: - default: - // do nothing - break; - } - - mHideBuildHighlight = false; -} - - -void LLToolGrabBase::draw() -{ } - -void LLToolGrabBase::render() -{ } - -bool LLToolGrabBase::isEditing() -{ - return (mGrabPick.getObject().notNull()); -} - -LLViewerObject* LLToolGrabBase::getEditingObject() -{ - return mGrabPick.getObject(); -} - - -LLVector3d LLToolGrabBase::getEditingPointGlobal() -{ - return getGrabPointGlobal(); -} - -LLVector3d LLToolGrabBase::getGrabPointGlobal() -{ - switch(mMode) - { - case GRAB_ACTIVE_CENTER: - case GRAB_NONPHYSICAL: - case GRAB_LOCKED: - return gAgentCamera.getCameraPositionGlobal() + mGrabHiddenOffsetFromCamera; - - case GRAB_NOOBJECT: - case GRAB_INACTIVE: - default: - return gAgent.getPositionGlobal(); - } -} - - -void send_ObjectGrab_message(LLViewerObject* object, const LLPickInfo & pick, const LLVector3 &grab_offset) -{ - if (!object) return; - - LLMessageSystem *msg = gMessageSystem; - - msg->newMessageFast(_PREHASH_ObjectGrab); - msg->nextBlockFast( _PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast( _PREHASH_ObjectData); - msg->addU32Fast( _PREHASH_LocalID, object->mLocalID); - msg->addVector3Fast(_PREHASH_GrabOffset, grab_offset); - msg->nextBlock("SurfaceInfo"); - msg->addVector3("UVCoord", LLVector3(pick.mUVCoords)); - msg->addVector3("STCoord", LLVector3(pick.mSTCoords)); - msg->addS32Fast(_PREHASH_FaceIndex, pick.mObjectFace); - msg->addVector3("Position", pick.mIntersection); - msg->addVector3("Normal", pick.mNormal); - msg->addVector3("Binormal", pick.mBinormal); - msg->sendMessage( object->getRegion()->getHost()); - - /* Diagnostic code - LL_INFOS() << "mUVCoords: " << pick.mUVCoords - << ", mSTCoords: " << pick.mSTCoords - << ", mObjectFace: " << pick.mObjectFace - << ", mIntersection: " << pick.mIntersection - << ", mNormal: " << pick.mNormal - << ", mBinormal: " << pick.mBinormal - << LL_ENDL; - - LL_INFOS() << "Avatar pos: " << gAgent.getPositionAgent() << LL_ENDL; - LL_INFOS() << "Object pos: " << object->getPosition() << LL_ENDL; - */ -} - - -void send_ObjectDeGrab_message(LLViewerObject* object, const LLPickInfo & pick) -{ - if (!object) return; - - LLMessageSystem *msg = gMessageSystem; - - msg->newMessageFast(_PREHASH_ObjectDeGrab); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ObjectData); - msg->addU32Fast(_PREHASH_LocalID, object->mLocalID); - msg->nextBlock("SurfaceInfo"); - msg->addVector3("UVCoord", LLVector3(pick.mUVCoords)); - msg->addVector3("STCoord", LLVector3(pick.mSTCoords)); - msg->addS32Fast(_PREHASH_FaceIndex, pick.mObjectFace); - msg->addVector3("Position", pick.mIntersection); - msg->addVector3("Normal", pick.mNormal); - msg->addVector3("Binormal", pick.mBinormal); - msg->sendMessage(object->getRegion()->getHost()); -} - - - +/** + * @file lltoolgrab.cpp + * @brief LLToolGrab class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lltoolgrab.h" + +// library headers +#include "indra_constants.h" // for agent control flags +#include "llviewercontrol.h" +#include "llquaternion.h" +#include "llbox.h" +#include "message.h" +#include "llview.h" +#include "llfontgl.h" +#include "llui.h" + +// newview headers +#include "llagent.h" +#include "llagentcamera.h" +#include "lldrawable.h" +#include "llfloatertools.h" +#include "llhudeffect.h" +#include "llhudmanager.h" +#include "llregionposition.h" +#include "llselectmgr.h" +#include "llstatusbar.h" +#include "lltoolmgr.h" +#include "lltoolpie.h" +#include "llviewercamera.h" +#include "llviewerinput.h" +#include "llviewerobject.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llvoavatarself.h" +#include "llworld.h" +#include "llmenugl.h" + +const S32 SLOP_DIST_SQ = 4; + +// Override modifier key behavior with these buttons +bool gGrabBtnVertical = false; +bool gGrabBtnSpin = false; +LLTool* gGrabTransientTool = NULL; +extern bool gDebugClicks; + +// +// Methods +// +LLToolGrabBase::LLToolGrabBase( LLToolComposite* composite ) +: LLTool( std::string("Grab"), composite ), + mMode( GRAB_INACTIVE ), + mVerticalDragging( false ), + mHitLand(false), + mLastMouseX(0), + mLastMouseY(0), + mAccumDeltaX(0), + mAccumDeltaY(0), + mHasMoved( false ), + mOutsideSlop(false), + mDeselectedThisClick(false), + mLastFace(0), + mSpinGrabbing( false ), + mSpinRotation(), + mClickedInMouselook( false ), + mHideBuildHighlight(false) +{ } + +LLToolGrabBase::~LLToolGrabBase() +{ } + + +// virtual +void LLToolGrabBase::handleSelect() +{ + if(gFloaterTools) + { + // viewer can crash during startup if we don't check. + gFloaterTools->setStatusText("grab"); + // in case we start from tools floater, we count any selection as valid + mValidSelection = gFloaterTools->getVisible(); + } + gGrabBtnVertical = false; + gGrabBtnSpin = false; +} + +void LLToolGrabBase::handleDeselect() +{ + if( hasMouseCapture() ) + { + setMouseCapture( false ); + } + + // Make sure that temporary(invalid) selection won't pass anywhere except pie tool. + MASK override_mask = gKeyboard ? gKeyboard->currentMask(true) : 0; + if (!mValidSelection && (override_mask != MASK_NONE || (gFloaterTools && gFloaterTools->getVisible()))) + { + LLMenuGL::sMenuContainer->hideMenus(); + LLSelectMgr::getInstance()->validateSelection(); + } + +} + +bool LLToolGrabBase::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + if (gDebugClicks) + { + LL_INFOS() << "LLToolGrab handleDoubleClick (becoming mouseDown)" << LL_ENDL; + } + + return false; +} + +bool LLToolGrabBase::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (gDebugClicks) + { + LL_INFOS() << "LLToolGrab handleMouseDown" << LL_ENDL; + } + + LLTool::handleMouseDown(x, y, mask); + + // leftButtonGrabbed() checks if controls are reserved by scripts, but does not take masks into account + if (!gAgent.leftButtonGrabbed() || ((mask & DEFAULT_GRAB_MASK) != 0 && !gAgentCamera.cameraMouselook())) + { + // can grab transparent objects (how touch event propagates, scripters rely on this) + gViewerWindow->pickAsync(x, y, mask, pickCallback, /*bool pick_transparent*/ true); + } + mClickedInMouselook = gAgentCamera.cameraMouselook(); + + if (mClickedInMouselook && gViewerInput.isLMouseHandlingDefault(MODE_FIRST_PERSON)) + { + // LLToolCompGun::handleMouseDown handles the event if ML controls are grabed, + // but LLToolGrabBase is often the end point for mouselook clicks if ML controls + // are not grabbed and LLToolGrabBase::handleMouseDown consumes the event, + // so send clicks from here. + // We are sending specifically CONTROL_LBUTTON_DOWN instead of _ML_ version. + gAgent.setControlFlags(AGENT_CONTROL_LBUTTON_DOWN); + + // Todo: LLToolGrabBase probably shouldn't consume the event if there is nothing + // to grab in Mouselook, it intercepts handling in scanMouse + } + return true; +} + +void LLToolGrabBase::pickCallback(const LLPickInfo& pick_info) +{ + LLToolGrab::getInstance()->mGrabPick = pick_info; + LLViewerObject *objectp = pick_info.getObject(); + + bool extend_select = (pick_info.mKeyMask & MASK_SHIFT); + + if (!extend_select && !LLSelectMgr::getInstance()->getSelection()->isEmpty()) + { + LLSelectMgr::getInstance()->deselectAll(); + LLToolGrab::getInstance()->mDeselectedThisClick = true; + } + else + { + LLToolGrab::getInstance()->mDeselectedThisClick = false; + } + + // if not over object, do nothing + if (!objectp) + { + LLToolGrab::getInstance()->setMouseCapture(true); + LLToolGrab::getInstance()->mMode = GRAB_NOOBJECT; + LLToolGrab::getInstance()->mGrabPick.mObjectID.setNull(); + } + else + { + LLToolGrab::getInstance()->handleObjectHit(LLToolGrab::getInstance()->mGrabPick); + } +} + +bool LLToolGrabBase::handleObjectHit(const LLPickInfo& info) +{ + mGrabPick = info; + LLViewerObject* objectp = mGrabPick.getObject(); + + if (gDebugClicks) + { + LL_INFOS() << "LLToolGrab handleObjectHit " << info.mMousePt.mX << "," << info.mMousePt.mY << LL_ENDL; + } + + if (NULL == objectp) // unexpected + { + LL_WARNS() << "objectp was NULL; returning false" << LL_ENDL; + return false; + } + + if (objectp->isAvatar()) + { + if (gGrabTransientTool) + { + gBasicToolset->selectTool( gGrabTransientTool ); + gGrabTransientTool = NULL; + } + return true; + } + + setMouseCapture( true ); + + // Grabs always start from the root + // objectp = (LLViewerObject *)objectp->getRoot(); + + LLViewerObject* parent = objectp->getRootEdit(); + bool script_touch = (objectp->flagHandleTouch()) || (parent && parent->flagHandleTouch()); + + // Clicks on scripted or physical objects are temporary grabs, so + // not "Build mode" + mHideBuildHighlight = script_touch || objectp->flagUsePhysics(); + + if (!objectp->flagUsePhysics()) + { + if (script_touch) + { + mMode = GRAB_NONPHYSICAL; // if it has a script, use the non-physical grab + } + else + { + // In mouselook, we shouldn't be able to grab non-physical, + // non-touchable objects. If it has a touch handler, we + // do grab it (so llDetectedGrab works), but movement is + // blocked on the server side. JC + if (gAgentCamera.cameraMouselook()) + { + mMode = GRAB_LOCKED; + gViewerWindow->hideCursor(); + gViewerWindow->moveCursorToCenter(); + } + else if (objectp->permMove() && !objectp->isPermanentEnforced()) + { + mMode = GRAB_ACTIVE_CENTER; + gViewerWindow->hideCursor(); + gViewerWindow->moveCursorToCenter(); + } + else + { + mMode = GRAB_LOCKED; + } + + + } + } + else if( objectp->flagCharacter() || !objectp->permMove() || objectp->isPermanentEnforced()) + { + // if mouse is over a physical object without move permission, show feedback if user tries to move it. + mMode = GRAB_LOCKED; + + // Don't bail out here, go on and grab so buttons can get + // their "touched" event. + } + else + { + // if mouse is over a physical object with move permission, + // select it and enter "grab" mode (hiding cursor, etc.) + + mMode = GRAB_ACTIVE_CENTER; + + gViewerWindow->hideCursor(); + gViewerWindow->moveCursorToCenter(); + } + + // Always send "touched" message + + mLastMouseX = gViewerWindow->getCurrentMouseX(); + mLastMouseY = gViewerWindow->getCurrentMouseY(); + mAccumDeltaX = 0; + mAccumDeltaY = 0; + mHasMoved = false; + mOutsideSlop = false; + + mVerticalDragging = (info.mKeyMask == MASK_VERTICAL) || gGrabBtnVertical; + + startGrab(); + + if ((info.mKeyMask == MASK_SPIN) || gGrabBtnSpin) + { + startSpin(); + } + + LLSelectMgr::getInstance()->updateSelectionCenter(); // update selection beam + + // update point at + LLViewerObject *edit_object = info.getObject(); + if (edit_object && info.mPickType != LLPickInfo::PICK_FLORA) + { + LLVector3 local_edit_point = gAgent.getPosAgentFromGlobal(info.mPosGlobal); + local_edit_point -= edit_object->getPositionAgent(); + local_edit_point = local_edit_point * ~edit_object->getRenderRotation(); + gAgentCamera.setPointAt(POINTAT_TARGET_GRAB, edit_object, local_edit_point ); + gAgentCamera.setLookAt(LOOKAT_TARGET_SELECT, edit_object, local_edit_point ); + } + + // on transient grabs (clicks on world objects), kill the grab immediately + if (!gViewerWindow->getLeftMouseDown() + && gGrabTransientTool + && (mMode == GRAB_NONPHYSICAL || mMode == GRAB_LOCKED)) + { + gBasicToolset->selectTool( gGrabTransientTool ); + gGrabTransientTool = NULL; + } + + return true; +} + + +void LLToolGrabBase::startSpin() +{ + LLViewerObject* objectp = mGrabPick.getObject(); + if (!objectp) + { + return; + } + mSpinGrabbing = true; + + // Was saveSelectedObjectTransform() + LLViewerObject *root = (LLViewerObject *)objectp->getRoot(); + mSpinRotation = root->getRotation(); + + LLMessageSystem *msg = gMessageSystem; + msg->newMessageFast(_PREHASH_ObjectSpinStart); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ObjectData); + msg->addUUIDFast(_PREHASH_ObjectID, mGrabPick.mObjectID ); + msg->sendMessage( objectp->getRegion()->getHost() ); +} + + +void LLToolGrabBase::stopSpin() +{ + mSpinGrabbing = false; + + LLViewerObject* objectp = mGrabPick.getObject(); + if (!objectp) + { + return; + } + + LLMessageSystem *msg = gMessageSystem; + switch(mMode) + { + case GRAB_ACTIVE_CENTER: + case GRAB_NONPHYSICAL: + case GRAB_LOCKED: + msg->newMessageFast(_PREHASH_ObjectSpinStop); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ObjectData); + msg->addUUIDFast(_PREHASH_ObjectID, objectp->getID() ); + msg->sendMessage( objectp->getRegion()->getHost() ); + break; + + case GRAB_NOOBJECT: + case GRAB_INACTIVE: + default: + // do nothing + break; + } +} + + +void LLToolGrabBase::startGrab() +{ + // Compute grab_offset in the OBJECT's root's coordinate frame + // (sometimes root == object) + LLViewerObject* objectp = mGrabPick.getObject(); + if (!objectp) + { + return; + } + + LLViewerObject *root = (LLViewerObject *)objectp->getRoot(); + + // drag from center + LLVector3d grab_start_global = root->getPositionGlobal(); + + // Where the grab starts, relative to the center of the root object of the set. + // JC - This code looks wonky, but I believe it does the right thing. + // Otherwise, when you grab a linked object set, it "pops" on the start + // of the drag. + LLVector3d grab_offsetd = root->getPositionGlobal() - objectp->getPositionGlobal(); + + LLVector3 grab_offset; + grab_offset.setVec(grab_offsetd); + + LLQuaternion rotation = root->getRotation(); + rotation.conjQuat(); + grab_offset = grab_offset * rotation; + + // This planar drag starts at the grab point + mDragStartPointGlobal = grab_start_global; + mDragStartFromCamera = grab_start_global - gAgentCamera.getCameraPositionGlobal(); + + send_ObjectGrab_message(objectp, mGrabPick, grab_offset); + + mGrabOffsetFromCenterInitial = grab_offset; + mGrabHiddenOffsetFromCamera = mDragStartFromCamera; + + mGrabTimer.reset(); + + mLastUVCoords = mGrabPick.mUVCoords; + mLastSTCoords = mGrabPick.mSTCoords; + mLastFace = mGrabPick.mObjectFace; + mLastIntersection = mGrabPick.mIntersection; + mLastNormal = mGrabPick.mNormal; + mLastBinormal = mGrabPick.mBinormal; + mLastGrabPos = LLVector3(-1.f, -1.f, -1.f); +} + + +bool LLToolGrabBase::handleHover(S32 x, S32 y, MASK mask) +{ + if (!gViewerWindow->getLeftMouseDown()) + { + gViewerWindow->setCursor(UI_CURSOR_TOOLGRAB); + setMouseCapture(false); + return true; + } + + // Do the right hover based on mode + switch( mMode ) + { + case GRAB_ACTIVE_CENTER: + handleHoverActive( x, y, mask ); // cursor hidden + break; + + case GRAB_NONPHYSICAL: + handleHoverNonPhysical(x, y, mask); + break; + + case GRAB_INACTIVE: + handleHoverInactive( x, y, mask ); // cursor set here + break; + + case GRAB_NOOBJECT: + case GRAB_LOCKED: + handleHoverFailed( x, y, mask ); + break; + + } + + mLastMouseX = x; + mLastMouseY = y; + + return true; +} + +const F32 GRAB_SENSITIVITY_X = 0.0075f; +const F32 GRAB_SENSITIVITY_Y = 0.0075f; + + + + +// Dragging. +void LLToolGrabBase::handleHoverActive(S32 x, S32 y, MASK mask) +{ + LLViewerObject* objectp = mGrabPick.getObject(); + if (!objectp || !hasMouseCapture() ) return; + if (objectp->isDead()) + { + // Bail out of drag because object has been killed + setMouseCapture(false); + return; + } + + //-------------------------------------------------- + // Determine target mode + //-------------------------------------------------- + bool vertical_dragging = false; + bool spin_grabbing = false; + if ((mask == MASK_VERTICAL) + || (gGrabBtnVertical && (mask != MASK_SPIN))) + { + vertical_dragging = true; + } + else if ((mask == MASK_SPIN) + || (gGrabBtnSpin && (mask != MASK_VERTICAL))) + { + spin_grabbing = true; + } + + //-------------------------------------------------- + // Toggle spinning + //-------------------------------------------------- + if (mSpinGrabbing && !spin_grabbing) + { + // user released or switched mask key(s), stop spinning + stopSpin(); + } + else if (!mSpinGrabbing && spin_grabbing) + { + // user pressed mask key(s), start spinning + startSpin(); + } + mSpinGrabbing = spin_grabbing; + + //-------------------------------------------------- + // Toggle vertical dragging + //-------------------------------------------------- + if (mVerticalDragging && !vertical_dragging) + { + // ...switch to horizontal dragging + mDragStartPointGlobal = gViewerWindow->clickPointInWorldGlobal(x, y, objectp); + mDragStartFromCamera = mDragStartPointGlobal - gAgentCamera.getCameraPositionGlobal(); + } + else if (!mVerticalDragging && vertical_dragging) + { + // ...switch to vertical dragging + mDragStartPointGlobal = gViewerWindow->clickPointInWorldGlobal(x, y, objectp); + mDragStartFromCamera = mDragStartPointGlobal - gAgentCamera.getCameraPositionGlobal(); + } + mVerticalDragging = vertical_dragging; + + const F32 RADIANS_PER_PIXEL_X = 0.01f; + const F32 RADIANS_PER_PIXEL_Y = 0.01f; + + S32 dx = gViewerWindow->getCurrentMouseDX(); + S32 dy = gViewerWindow->getCurrentMouseDY(); + + if (dx != 0 || dy != 0) + { + mAccumDeltaX += dx; + mAccumDeltaY += dy; + S32 dist_sq = mAccumDeltaX * mAccumDeltaX + mAccumDeltaY * mAccumDeltaY; + if (dist_sq > SLOP_DIST_SQ) + { + mOutsideSlop = true; + } + + // mouse has moved outside center + mHasMoved = true; + + if (mSpinGrabbing) + { + //------------------------------------------------------ + // Handle spinning + //------------------------------------------------------ + + // x motion maps to rotation around vertical axis + LLVector3 up(0.f, 0.f, 1.f); + LLQuaternion rotation_around_vertical( dx*RADIANS_PER_PIXEL_X, up ); + + // y motion maps to rotation around left axis + const LLVector3 &agent_left = LLViewerCamera::getInstance()->getLeftAxis(); + LLQuaternion rotation_around_left( dy*RADIANS_PER_PIXEL_Y, agent_left ); + + // compose with current rotation + mSpinRotation = mSpinRotation * rotation_around_vertical; + mSpinRotation = mSpinRotation * rotation_around_left; + + // TODO: Throttle these + LLMessageSystem *msg = gMessageSystem; + msg->newMessageFast(_PREHASH_ObjectSpinUpdate); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ObjectData); + msg->addUUIDFast(_PREHASH_ObjectID, objectp->getID() ); + msg->addQuatFast(_PREHASH_Rotation, mSpinRotation ); + msg->sendMessage( objectp->getRegion()->getHost() ); + } + else + { + //------------------------------------------------------ + // Handle grabbing + //------------------------------------------------------ + + LLVector3d x_part; + x_part.setVec(LLViewerCamera::getInstance()->getLeftAxis()); + x_part.mdV[VZ] = 0.0; + x_part.normVec(); + + LLVector3d y_part; + if( mVerticalDragging ) + { + y_part.setVec(LLViewerCamera::getInstance()->getUpAxis()); + // y_part.setVec(0.f, 0.f, 1.f); + } + else + { + // drag toward camera + y_part = x_part % LLVector3d::z_axis; + y_part.mdV[VZ] = 0.0; + y_part.normVec(); + } + + mGrabHiddenOffsetFromCamera = mGrabHiddenOffsetFromCamera + + (x_part * (-dx * GRAB_SENSITIVITY_X)) + + (y_part * ( dy * GRAB_SENSITIVITY_Y)); + + + // Send the message to the viewer. + F32 dt = mGrabTimer.getElapsedTimeAndResetF32(); + U32 dt_milliseconds = (U32) (1000.f * dt); + + // need to return offset from mGrabStartPoint + LLVector3d grab_point_global; + + grab_point_global = gAgentCamera.getCameraPositionGlobal() + mGrabHiddenOffsetFromCamera; + + /* Snap to grid disabled for grab tool - very confusing + // Handle snapping to grid, but only when the tool is formally selected. + bool snap_on = gSavedSettings.getBOOL("SnapEnabled"); + if (snap_on && !gGrabTransientTool) + { + F64 snap_size = gSavedSettings.getF32("GridResolution"); + U8 snap_dimensions = (mVerticalDragging ? 3 : 2); + + for (U8 i = 0; i < snap_dimensions; i++) + { + grab_point_global.mdV[i] += snap_size / 2; + grab_point_global.mdV[i] -= fmod(grab_point_global.mdV[i], snap_size); + } + } + */ + + // Don't let object centers go underground. + F32 land_height = LLWorld::getInstance()->resolveLandHeightGlobal(grab_point_global); + + if (grab_point_global.mdV[VZ] < land_height) + { + grab_point_global.mdV[VZ] = land_height; + } + + // For safety, cap heights where objects can be dragged + if (grab_point_global.mdV[VZ] > MAX_OBJECT_Z) + { + grab_point_global.mdV[VZ] = MAX_OBJECT_Z; + } + + grab_point_global = LLWorld::getInstance()->clipToVisibleRegions(mDragStartPointGlobal, grab_point_global); + // propagate constrained grab point back to grab offset + mGrabHiddenOffsetFromCamera = grab_point_global - gAgentCamera.getCameraPositionGlobal(); + + // Handle auto-rotation at screen edge. + LLVector3 grab_pos_agent = gAgent.getPosAgentFromGlobal( grab_point_global ); + + LLCoordGL grab_center_gl( gViewerWindow->getWorldViewWidthScaled() / 2, gViewerWindow->getWorldViewHeightScaled() / 2); + LLViewerCamera::getInstance()->projectPosAgentToScreen(grab_pos_agent, grab_center_gl); + + const S32 ROTATE_H_MARGIN = gViewerWindow->getWorldViewWidthScaled() / 20; + const F32 ROTATE_ANGLE_PER_SECOND = 30.f * DEG_TO_RAD; + const F32 rotate_angle = ROTATE_ANGLE_PER_SECOND / gFPSClamped; + // ...build mode moves camera about focus point + if (grab_center_gl.mX < ROTATE_H_MARGIN) + { + if (gAgentCamera.getFocusOnAvatar()) + { + gAgent.yaw(rotate_angle); + } + else + { + gAgentCamera.cameraOrbitAround(rotate_angle); + } + } + else if (grab_center_gl.mX > gViewerWindow->getWorldViewWidthScaled() - ROTATE_H_MARGIN) + { + if (gAgentCamera.getFocusOnAvatar()) + { + gAgent.yaw(-rotate_angle); + } + else + { + gAgentCamera.cameraOrbitAround(-rotate_angle); + } + } + + // Don't move above top of screen or below bottom + if ((grab_center_gl.mY < gViewerWindow->getWorldViewHeightScaled() - 6) + && (grab_center_gl.mY > 24)) + { + // Transmit update to simulator + LLVector3 grab_pos_region = objectp->getRegion()->getPosRegionFromGlobal( grab_point_global ); + + LLMessageSystem *msg = gMessageSystem; + msg->newMessageFast(_PREHASH_ObjectGrabUpdate); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ObjectData); + msg->addUUIDFast(_PREHASH_ObjectID, objectp->getID() ); + msg->addVector3Fast(_PREHASH_GrabOffsetInitial, mGrabOffsetFromCenterInitial ); + msg->addVector3Fast(_PREHASH_GrabPosition, grab_pos_region ); + msg->addU32Fast(_PREHASH_TimeSinceLast, dt_milliseconds ); + msg->nextBlock("SurfaceInfo"); + msg->addVector3("UVCoord", LLVector3(mGrabPick.mUVCoords)); + msg->addVector3("STCoord", LLVector3(mGrabPick.mSTCoords)); + msg->addS32Fast(_PREHASH_FaceIndex, mGrabPick.mObjectFace); + msg->addVector3("Position", mGrabPick.mIntersection); + msg->addVector3("Normal", mGrabPick.mNormal); + msg->addVector3("Binormal", mGrabPick.mBinormal); + + msg->sendMessage( objectp->getRegion()->getHost() ); + } + } + + gViewerWindow->moveCursorToCenter(); + + LLSelectMgr::getInstance()->updateSelectionCenter(); + + } + + // once we've initiated a drag, lock the camera down + if (mHasMoved) + { + if (!gAgentCamera.cameraMouselook() && + !objectp->isHUDAttachment() && + objectp->getRoot() == gAgentAvatarp->getRoot()) + { + // we are essentially editing object position + if (!gSavedSettings.getBOOL("EditCameraMovement")) + { + // force focus to point in space where we were looking previously + // Example of use: follow cam scripts shouldn't affect you when movng objects arouns + gAgentCamera.setFocusGlobal(gAgentCamera.calcFocusPositionTargetGlobal(), LLUUID::null); + gAgentCamera.setFocusOnAvatar(false, ANIMATE); + } + } + else + { + gAgentCamera.clearFocusObject(); + } + } + + // HACK to avoid assert: error checking system makes sure that the cursor is set during every handleHover. This is actually a no-op since the cursor is hidden. + gViewerWindow->setCursor(UI_CURSOR_ARROW); + + LL_DEBUGS("UserInput") << "hover handled by LLToolGrab (active) [cursor hidden]" << LL_ENDL; +} + + +void LLToolGrabBase::handleHoverNonPhysical(S32 x, S32 y, MASK mask) +{ + LLViewerObject* objectp = mGrabPick.getObject(); + if (!objectp || !hasMouseCapture() ) return; + if (objectp->isDead()) + { + // Bail out of drag because object has been killed + setMouseCapture(false); + return; + } + + LLPickInfo pick = mGrabPick; + pick.mMousePt = LLCoordGL(x, y); + pick.getSurfaceInfo(); + + // compute elapsed time + F32 dt = mGrabTimer.getElapsedTimeAndResetF32(); + U32 dt_milliseconds = (U32) (1000.f * dt); + + // i'm not a big fan of the following code - it's been culled from the physical grab case. + // ideally these two would be nicely integrated - but the code in that method is a serious + // mess of spaghetti. so here we go: + + LLVector3 grab_pos_region(0,0,0); + + const bool SUPPORT_LLDETECTED_GRAB = true; + if (SUPPORT_LLDETECTED_GRAB) + { + //-------------------------------------------------- + // Toggle vertical dragging + //-------------------------------------------------- + if (!(mask == MASK_VERTICAL) && !gGrabBtnVertical) + { + mVerticalDragging = false; + } + + else if ((gGrabBtnVertical && (mask != MASK_SPIN)) + || (mask == MASK_VERTICAL)) + { + mVerticalDragging = true; + } + + S32 dx = x - mLastMouseX; + S32 dy = y - mLastMouseY; + + if (dx != 0 || dy != 0) + { + mAccumDeltaX += dx; + mAccumDeltaY += dy; + + S32 dist_sq = mAccumDeltaX * mAccumDeltaX + mAccumDeltaY * mAccumDeltaY; + if (dist_sq > SLOP_DIST_SQ) + { + mOutsideSlop = true; + } + + // mouse has moved + mHasMoved = true; + + //------------------------------------------------------ + // Handle grabbing + //------------------------------------------------------ + + LLVector3d x_part; + x_part.setVec(LLViewerCamera::getInstance()->getLeftAxis()); + x_part.mdV[VZ] = 0.0; + x_part.normVec(); + + LLVector3d y_part; + if( mVerticalDragging ) + { + y_part.setVec(LLViewerCamera::getInstance()->getUpAxis()); + // y_part.setVec(0.f, 0.f, 1.f); + } + else + { + // drag toward camera + y_part = x_part % LLVector3d::z_axis; + y_part.mdV[VZ] = 0.0; + y_part.normVec(); + } + + mGrabHiddenOffsetFromCamera = mGrabHiddenOffsetFromCamera + + (x_part * (-dx * GRAB_SENSITIVITY_X)) + + (y_part * ( dy * GRAB_SENSITIVITY_Y)); + + } + + // need to return offset from mGrabStartPoint + LLVector3d grab_point_global = gAgentCamera.getCameraPositionGlobal() + mGrabHiddenOffsetFromCamera; + grab_pos_region = objectp->getRegion()->getPosRegionFromGlobal( grab_point_global ); + } + + + // only send message if something has changed since last message + + bool changed_since_last_update = false; + + // test if touch data needs to be updated + if ((pick.mObjectFace != mLastFace) || + (pick.mUVCoords != mLastUVCoords) || + (pick.mSTCoords != mLastSTCoords) || + (pick.mIntersection != mLastIntersection) || + (pick.mNormal != mLastNormal) || + (pick.mBinormal != mLastBinormal) || + (grab_pos_region != mLastGrabPos)) + { + changed_since_last_update = true; + } + + if (changed_since_last_update) + { + LLMessageSystem *msg = gMessageSystem; + msg->newMessageFast(_PREHASH_ObjectGrabUpdate); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ObjectData); + msg->addUUIDFast(_PREHASH_ObjectID, objectp->getID() ); + msg->addVector3Fast(_PREHASH_GrabOffsetInitial, mGrabOffsetFromCenterInitial ); + msg->addVector3Fast(_PREHASH_GrabPosition, grab_pos_region ); + msg->addU32Fast(_PREHASH_TimeSinceLast, dt_milliseconds ); + msg->nextBlock("SurfaceInfo"); + msg->addVector3("UVCoord", LLVector3(pick.mUVCoords)); + msg->addVector3("STCoord", LLVector3(pick.mSTCoords)); + msg->addS32Fast(_PREHASH_FaceIndex, pick.mObjectFace); + msg->addVector3("Position", pick.mIntersection); + msg->addVector3("Normal", pick.mNormal); + msg->addVector3("Binormal", pick.mBinormal); + + msg->sendMessage( objectp->getRegion()->getHost() ); + + mLastUVCoords = pick.mUVCoords; + mLastSTCoords = pick.mSTCoords; + mLastFace = pick.mObjectFace; + mLastIntersection = pick.mIntersection; + mLastNormal= pick.mNormal; + mLastBinormal= pick.mBinormal; + mLastGrabPos = grab_pos_region; + } + + // update point-at / look-at + if (pick.mObjectFace != -1) // if the intersection was on the surface of the obejct + { + LLVector3 local_edit_point = pick.mIntersection; + local_edit_point -= objectp->getPositionAgent(); + local_edit_point = local_edit_point * ~objectp->getRenderRotation(); + gAgentCamera.setPointAt(POINTAT_TARGET_GRAB, objectp, local_edit_point ); + gAgentCamera.setLookAt(LOOKAT_TARGET_SELECT, objectp, local_edit_point ); + } + + + + gViewerWindow->setCursor(UI_CURSOR_HAND); +} + + +// Not dragging. Just showing affordances +void LLToolGrabBase::handleHoverInactive(S32 x, S32 y, MASK mask) +{ + // JC - TODO - change cursor based on gGrabBtnVertical, gGrabBtnSpin + LL_DEBUGS("UserInput") << "hover handled by LLToolGrab (inactive-not over editable object)" << LL_ENDL; + gViewerWindow->setCursor(UI_CURSOR_TOOLGRAB); +} + +// User is trying to do something that's not allowed. +void LLToolGrabBase::handleHoverFailed(S32 x, S32 y, MASK mask) +{ + if( GRAB_NOOBJECT == mMode ) + { + gViewerWindow->setCursor(UI_CURSOR_NO); + LL_DEBUGS("UserInput") << "hover handled by LLToolGrab (not on object)" << LL_ENDL; + } + else + { + S32 dist_sq = (x-mGrabPick.mMousePt.mX) * (x-mGrabPick.mMousePt.mX) + (y-mGrabPick.mMousePt.mY) * (y-mGrabPick.mMousePt.mY); + if( mOutsideSlop || dist_sq > SLOP_DIST_SQ ) + { + mOutsideSlop = true; + + switch( mMode ) + { + case GRAB_LOCKED: + gViewerWindow->setCursor(UI_CURSOR_GRABLOCKED); + LL_DEBUGS("UserInput") << "hover handled by LLToolGrab (grab failed, no move permission)" << LL_ENDL; + break; + +// Non physical now handled by handleHoverActive - CRO +// case GRAB_NONPHYSICAL: +// gViewerWindow->setCursor(UI_CURSOR_ARROW); +// LL_DEBUGS("UserInput") << "hover handled by LLToolGrab (grab failed, nonphysical)" << LL_ENDL; +// break; + default: + llassert(0); + } + } + else + { + gViewerWindow->setCursor(UI_CURSOR_ARROW); + LL_DEBUGS("UserInput") << "hover handled by LLToolGrab (grab failed but within slop)" << LL_ENDL; + } + } +} + + + + +bool LLToolGrabBase::handleMouseUp(S32 x, S32 y, MASK mask) +{ + LLTool::handleMouseUp(x, y, mask); + + if (gAgentCamera.cameraMouselook() && gViewerInput.isLMouseHandlingDefault(MODE_FIRST_PERSON)) + { + // LLToolCompGun::handleMouseUp handles the event if ML controls are grabed, + // but LLToolGrabBase is often the end point for mouselook clicks if ML controls + // are not grabbed and LToolGrabBase::handleMouseUp consumes the event, + // so send clicks from here. + // We are sending specifically CONTROL_LBUTTON_UP instead of _ML_ version. + gAgent.setControlFlags(AGENT_CONTROL_LBUTTON_UP); + } + + if( hasMouseCapture() ) + { + setMouseCapture( false ); + } + + mMode = GRAB_INACTIVE; + + if(mClickedInMouselook && !gAgentCamera.cameraMouselook()) + { + mClickedInMouselook = false; + } + else + { + // HACK: Make some grabs temporary + if (gGrabTransientTool) + { + gBasicToolset->selectTool( gGrabTransientTool ); + gGrabTransientTool = NULL; + } + } + + //gAgent.setObjectTracking(gSavedSettings.getBOOL("TrackFocusObject")); + + return true; +} + +void LLToolGrabBase::stopEditing() +{ + if( hasMouseCapture() ) + { + setMouseCapture( false ); + } +} + +void LLToolGrabBase::onMouseCaptureLost() +{ + LLViewerObject* objectp = mGrabPick.getObject(); + if (!objectp) + { + gViewerWindow->showCursor(); + return; + } + // First, fix cursor placement + if( !gAgentCamera.cameraMouselook() + && (GRAB_ACTIVE_CENTER == mMode)) + { + if (objectp->isHUDAttachment()) + { + // ...move cursor "naturally", as if it had moved when hidden + S32 x = mGrabPick.mMousePt.mX + mAccumDeltaX; + S32 y = mGrabPick.mMousePt.mY + mAccumDeltaY; + LLUI::getInstance()->setMousePositionScreen(x, y); + } + else if (mHasMoved) + { + // ...move cursor back to the center of the object + LLVector3 grab_point_agent = objectp->getRenderPosition(); + + LLCoordGL gl_point; + if (LLViewerCamera::getInstance()->projectPosAgentToScreen(grab_point_agent, gl_point)) + { + LLUI::getInstance()->setMousePositionScreen(gl_point.mX, gl_point.mY); + } + } + else + { + // ...move cursor back to click position + LLUI::getInstance()->setMousePositionScreen(mGrabPick.mMousePt.mX, mGrabPick.mMousePt.mY); + } + + gViewerWindow->showCursor(); + } + + stopGrab(); + if (mSpinGrabbing) + stopSpin(); + + mMode = GRAB_INACTIVE; + + mHideBuildHighlight = false; + + mGrabPick.mObjectID.setNull(); + + LLSelectMgr::getInstance()->updateSelectionCenter(); + gAgentCamera.setPointAt(POINTAT_TARGET_CLEAR); + gAgentCamera.setLookAt(LOOKAT_TARGET_CLEAR); + + dialog_refresh_all(); +} + + +void LLToolGrabBase::stopGrab() +{ + LLViewerObject* objectp = mGrabPick.getObject(); + if (!objectp) + { + return; + } + + LLPickInfo pick = mGrabPick; + + if (mMode == GRAB_NONPHYSICAL) + { + // for non-physical (touch) grabs, + // gather surface info for this degrab (mouse-up) + S32 x = gViewerWindow->getCurrentMouseX(); + S32 y = gViewerWindow->getCurrentMouseY(); + pick.mMousePt = LLCoordGL(x, y); + pick.getSurfaceInfo(); + } + + // Next, send messages to simulator + switch(mMode) + { + case GRAB_ACTIVE_CENTER: + case GRAB_NONPHYSICAL: + case GRAB_LOCKED: + send_ObjectDeGrab_message(objectp, pick); + mVerticalDragging = false; + break; + + case GRAB_NOOBJECT: + case GRAB_INACTIVE: + default: + // do nothing + break; + } + + mHideBuildHighlight = false; +} + + +void LLToolGrabBase::draw() +{ } + +void LLToolGrabBase::render() +{ } + +bool LLToolGrabBase::isEditing() +{ + return (mGrabPick.getObject().notNull()); +} + +LLViewerObject* LLToolGrabBase::getEditingObject() +{ + return mGrabPick.getObject(); +} + + +LLVector3d LLToolGrabBase::getEditingPointGlobal() +{ + return getGrabPointGlobal(); +} + +LLVector3d LLToolGrabBase::getGrabPointGlobal() +{ + switch(mMode) + { + case GRAB_ACTIVE_CENTER: + case GRAB_NONPHYSICAL: + case GRAB_LOCKED: + return gAgentCamera.getCameraPositionGlobal() + mGrabHiddenOffsetFromCamera; + + case GRAB_NOOBJECT: + case GRAB_INACTIVE: + default: + return gAgent.getPositionGlobal(); + } +} + + +void send_ObjectGrab_message(LLViewerObject* object, const LLPickInfo & pick, const LLVector3 &grab_offset) +{ + if (!object) return; + + LLMessageSystem *msg = gMessageSystem; + + msg->newMessageFast(_PREHASH_ObjectGrab); + msg->nextBlockFast( _PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast( _PREHASH_ObjectData); + msg->addU32Fast( _PREHASH_LocalID, object->mLocalID); + msg->addVector3Fast(_PREHASH_GrabOffset, grab_offset); + msg->nextBlock("SurfaceInfo"); + msg->addVector3("UVCoord", LLVector3(pick.mUVCoords)); + msg->addVector3("STCoord", LLVector3(pick.mSTCoords)); + msg->addS32Fast(_PREHASH_FaceIndex, pick.mObjectFace); + msg->addVector3("Position", pick.mIntersection); + msg->addVector3("Normal", pick.mNormal); + msg->addVector3("Binormal", pick.mBinormal); + msg->sendMessage( object->getRegion()->getHost()); + + /* Diagnostic code + LL_INFOS() << "mUVCoords: " << pick.mUVCoords + << ", mSTCoords: " << pick.mSTCoords + << ", mObjectFace: " << pick.mObjectFace + << ", mIntersection: " << pick.mIntersection + << ", mNormal: " << pick.mNormal + << ", mBinormal: " << pick.mBinormal + << LL_ENDL; + + LL_INFOS() << "Avatar pos: " << gAgent.getPositionAgent() << LL_ENDL; + LL_INFOS() << "Object pos: " << object->getPosition() << LL_ENDL; + */ +} + + +void send_ObjectDeGrab_message(LLViewerObject* object, const LLPickInfo & pick) +{ + if (!object) return; + + LLMessageSystem *msg = gMessageSystem; + + msg->newMessageFast(_PREHASH_ObjectDeGrab); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ObjectData); + msg->addU32Fast(_PREHASH_LocalID, object->mLocalID); + msg->nextBlock("SurfaceInfo"); + msg->addVector3("UVCoord", LLVector3(pick.mUVCoords)); + msg->addVector3("STCoord", LLVector3(pick.mSTCoords)); + msg->addS32Fast(_PREHASH_FaceIndex, pick.mObjectFace); + msg->addVector3("Position", pick.mIntersection); + msg->addVector3("Normal", pick.mNormal); + msg->addVector3("Binormal", pick.mBinormal); + msg->sendMessage(object->getRegion()->getHost()); +} + + + diff --git a/indra/newview/lltoolgrab.h b/indra/newview/lltoolgrab.h index 36a8a0a3c5..7806cbc24f 100644 --- a/indra/newview/lltoolgrab.h +++ b/indra/newview/lltoolgrab.h @@ -1,159 +1,159 @@ -/** - * @file lltoolgrab.h - * @brief LLToolGrab class header file - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_TOOLGRAB_H -#define LL_TOOLGRAB_H - -#include "lltool.h" -#include "v3math.h" -#include "llquaternion.h" -#include "llsingleton.h" -#include "lluuid.h" -#include "llviewerwindow.h" // for LLPickInfo - -class LLView; -class LLTextBox; -class LLViewerObject; -class LLPickInfo; - - -// Message utilities -void send_ObjectGrab_message(LLViewerObject* object, const LLPickInfo & pick, const LLVector3 &grab_offset); -void send_ObjectDeGrab_message(LLViewerObject* object, const LLPickInfo & pick); - -const MASK DEFAULT_GRAB_MASK = MASK_CONTROL; - -/** - * LLToolGrabBase contains most of the semantics of LLToolGrab. It's just that - * LLToolGrab is an LLSingleton, but we also explicitly instantiate - * LLToolGrabBase as part of LLToolCompGun. You can't just make an extra - * instance of an LLSingleton! - */ -class LLToolGrabBase : public LLTool -{ -public: - LLToolGrabBase(LLToolComposite* composite=NULL); - ~LLToolGrabBase(); - - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); - /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask); - /*virtual*/ void render(); // 3D elements - /*virtual*/ void draw(); // 2D elements - - virtual void handleSelect(); - virtual void handleDeselect(); - - virtual LLViewerObject* getEditingObject(); - virtual LLVector3d getEditingPointGlobal(); - virtual bool isEditing(); - virtual void stopEditing(); - - virtual void onMouseCaptureLost(); - - bool hasGrabOffset() { return true; } // HACK - LLVector3 getGrabOffset(S32 x, S32 y); // HACK - - // Capture the mouse and start grabbing. - bool handleObjectHit(const LLPickInfo& info); - - // Certain grabs should not highlight the "Build" toolbar button - bool getHideBuildHighlight() { return mHideBuildHighlight; } - - void setClickedInMouselook(bool is_clickedInMouselook) {mClickedInMouselook = is_clickedInMouselook;} - - static void pickCallback(const LLPickInfo& pick_info); -private: - LLVector3d getGrabPointGlobal(); - void startGrab(); - void stopGrab(); - - void startSpin(); - void stopSpin(); - - void handleHoverSpin(S32 x, S32 y, MASK mask); - void handleHoverActive(S32 x, S32 y, MASK mask); - void handleHoverNonPhysical(S32 x, S32 y, MASK mask); - void handleHoverInactive(S32 x, S32 y, MASK mask); - void handleHoverFailed(S32 x, S32 y, MASK mask); - -private: - enum EGrabMode { GRAB_INACTIVE, GRAB_ACTIVE_CENTER, GRAB_NONPHYSICAL, GRAB_LOCKED, GRAB_NOOBJECT }; - - EGrabMode mMode; - - bool mVerticalDragging; - - bool mHitLand; - - LLTimer mGrabTimer; // send simulator time between hover movements - - LLVector3 mGrabOffsetFromCenterInitial; // meters from CG of object - LLVector3d mGrabHiddenOffsetFromCamera; // in cursor hidden drag, how far is grab offset from camera - - LLVector3d mDragStartPointGlobal; // projected into world - LLVector3d mDragStartFromCamera; // drag start relative to camera - - LLPickInfo mGrabPick; - - S32 mLastMouseX; - S32 mLastMouseY; - S32 mAccumDeltaX; // since cursor hidden, how far have you moved? - S32 mAccumDeltaY; - bool mHasMoved; // has mouse moved off center at all? - bool mOutsideSlop; // has mouse moved outside center 5 pixels? - bool mDeselectedThisClick; - bool mValidSelection; - - S32 mLastFace; - LLVector2 mLastUVCoords; - LLVector2 mLastSTCoords; - LLVector3 mLastIntersection; - LLVector3 mLastNormal; - LLVector3 mLastBinormal; - LLVector3 mLastGrabPos; - - - bool mSpinGrabbing; - LLQuaternion mSpinRotation; - - bool mHideBuildHighlight; - - bool mClickedInMouselook; -}; - -/// This is the LLSingleton instance of LLToolGrab. -class LLToolGrab : public LLToolGrabBase, public LLSingleton -{ - LLSINGLETON_EMPTY_CTOR(LLToolGrab); -}; - -extern bool gGrabBtnVertical; -extern bool gGrabBtnSpin; -extern LLTool* gGrabTransientTool; - -#endif // LL_TOOLGRAB_H +/** + * @file lltoolgrab.h + * @brief LLToolGrab class header file + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_TOOLGRAB_H +#define LL_TOOLGRAB_H + +#include "lltool.h" +#include "v3math.h" +#include "llquaternion.h" +#include "llsingleton.h" +#include "lluuid.h" +#include "llviewerwindow.h" // for LLPickInfo + +class LLView; +class LLTextBox; +class LLViewerObject; +class LLPickInfo; + + +// Message utilities +void send_ObjectGrab_message(LLViewerObject* object, const LLPickInfo & pick, const LLVector3 &grab_offset); +void send_ObjectDeGrab_message(LLViewerObject* object, const LLPickInfo & pick); + +const MASK DEFAULT_GRAB_MASK = MASK_CONTROL; + +/** + * LLToolGrabBase contains most of the semantics of LLToolGrab. It's just that + * LLToolGrab is an LLSingleton, but we also explicitly instantiate + * LLToolGrabBase as part of LLToolCompGun. You can't just make an extra + * instance of an LLSingleton! + */ +class LLToolGrabBase : public LLTool +{ +public: + LLToolGrabBase(LLToolComposite* composite=NULL); + ~LLToolGrabBase(); + + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); + /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask); + /*virtual*/ void render(); // 3D elements + /*virtual*/ void draw(); // 2D elements + + virtual void handleSelect(); + virtual void handleDeselect(); + + virtual LLViewerObject* getEditingObject(); + virtual LLVector3d getEditingPointGlobal(); + virtual bool isEditing(); + virtual void stopEditing(); + + virtual void onMouseCaptureLost(); + + bool hasGrabOffset() { return true; } // HACK + LLVector3 getGrabOffset(S32 x, S32 y); // HACK + + // Capture the mouse and start grabbing. + bool handleObjectHit(const LLPickInfo& info); + + // Certain grabs should not highlight the "Build" toolbar button + bool getHideBuildHighlight() { return mHideBuildHighlight; } + + void setClickedInMouselook(bool is_clickedInMouselook) {mClickedInMouselook = is_clickedInMouselook;} + + static void pickCallback(const LLPickInfo& pick_info); +private: + LLVector3d getGrabPointGlobal(); + void startGrab(); + void stopGrab(); + + void startSpin(); + void stopSpin(); + + void handleHoverSpin(S32 x, S32 y, MASK mask); + void handleHoverActive(S32 x, S32 y, MASK mask); + void handleHoverNonPhysical(S32 x, S32 y, MASK mask); + void handleHoverInactive(S32 x, S32 y, MASK mask); + void handleHoverFailed(S32 x, S32 y, MASK mask); + +private: + enum EGrabMode { GRAB_INACTIVE, GRAB_ACTIVE_CENTER, GRAB_NONPHYSICAL, GRAB_LOCKED, GRAB_NOOBJECT }; + + EGrabMode mMode; + + bool mVerticalDragging; + + bool mHitLand; + + LLTimer mGrabTimer; // send simulator time between hover movements + + LLVector3 mGrabOffsetFromCenterInitial; // meters from CG of object + LLVector3d mGrabHiddenOffsetFromCamera; // in cursor hidden drag, how far is grab offset from camera + + LLVector3d mDragStartPointGlobal; // projected into world + LLVector3d mDragStartFromCamera; // drag start relative to camera + + LLPickInfo mGrabPick; + + S32 mLastMouseX; + S32 mLastMouseY; + S32 mAccumDeltaX; // since cursor hidden, how far have you moved? + S32 mAccumDeltaY; + bool mHasMoved; // has mouse moved off center at all? + bool mOutsideSlop; // has mouse moved outside center 5 pixels? + bool mDeselectedThisClick; + bool mValidSelection; + + S32 mLastFace; + LLVector2 mLastUVCoords; + LLVector2 mLastSTCoords; + LLVector3 mLastIntersection; + LLVector3 mLastNormal; + LLVector3 mLastBinormal; + LLVector3 mLastGrabPos; + + + bool mSpinGrabbing; + LLQuaternion mSpinRotation; + + bool mHideBuildHighlight; + + bool mClickedInMouselook; +}; + +/// This is the LLSingleton instance of LLToolGrab. +class LLToolGrab : public LLToolGrabBase, public LLSingleton +{ + LLSINGLETON_EMPTY_CTOR(LLToolGrab); +}; + +extern bool gGrabBtnVertical; +extern bool gGrabBtnSpin; +extern LLTool* gGrabTransientTool; + +#endif // LL_TOOLGRAB_H diff --git a/indra/newview/lltoolgun.cpp b/indra/newview/lltoolgun.cpp index 4613f454ef..b60cb96eb5 100644 --- a/indra/newview/lltoolgun.cpp +++ b/indra/newview/lltoolgun.cpp @@ -1,148 +1,148 @@ -/** - * @file lltoolgun.cpp - * @brief LLToolGun class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lltoolgun.h" - -#include "llviewerwindow.h" -#include "llagent.h" -#include "llagentcamera.h" -#include "llviewercontrol.h" -#include "llsky.h" -#include "llappviewer.h" -#include "llresmgr.h" -#include "llfontgl.h" -#include "llui.h" -#include "llviewertexturelist.h" -#include "llviewercamera.h" -#include "llhudmanager.h" -#include "lltoolmgr.h" -#include "lltoolgrab.h" -#include "lluiimage.h" -// Linden library includes -#include "llwindow.h" // setMouseClipping() - -LLToolGun::LLToolGun( LLToolComposite* composite ) -: LLTool( std::string("gun"), composite ), - mIsSelected(false) -{ -} - -void LLToolGun::handleSelect() -{ - gViewerWindow->hideCursor(); - gViewerWindow->moveCursorToCenter(); - gViewerWindow->getWindow()->setMouseClipping(true); - mIsSelected = true; -} - -void LLToolGun::handleDeselect() -{ - gViewerWindow->moveCursorToCenter(); - gViewerWindow->showCursor(); - gViewerWindow->getWindow()->setMouseClipping(false); - mIsSelected = false; -} - -bool LLToolGun::handleMouseDown(S32 x, S32 y, MASK mask) -{ - gGrabTransientTool = this; - LLToolMgr::getInstance()->getCurrentToolset()->selectTool( LLToolGrab::getInstance() ); - - return LLToolGrab::getInstance()->handleMouseDown(x, y, mask); -} - -bool LLToolGun::handleHover(S32 x, S32 y, MASK mask) -{ - if( gAgentCamera.cameraMouselook() && mIsSelected ) - { - const F32 NOMINAL_MOUSE_SENSITIVITY = 0.0025f; - - F32 mouse_sensitivity = gSavedSettings.getF32("MouseSensitivity"); - mouse_sensitivity = clamp_rescale(mouse_sensitivity, 0.f, 15.f, 0.5f, 2.75f) * NOMINAL_MOUSE_SENSITIVITY; - - // ...move the view with the mouse - - // get mouse movement delta - S32 dx = -gViewerWindow->getCurrentMouseDX(); - S32 dy = -gViewerWindow->getCurrentMouseDY(); - - if (dx != 0 || dy != 0) - { - // ...actually moved off center - if (gSavedSettings.getBOOL("InvertMouse")) - { - gAgent.pitch(mouse_sensitivity * -dy); - } - else - { - gAgent.pitch(mouse_sensitivity * dy); - } - LLVector3 skyward = gAgent.getReferenceUpVector(); - gAgent.rotate(mouse_sensitivity * dx, skyward.mV[VX], skyward.mV[VY], skyward.mV[VZ]); - - if (gSavedSettings.getBOOL("MouseSun")) - { - LLVector3 sunpos = LLViewerCamera::getInstance()->getAtAxis(); - gSky.setSunDirectionCFR(sunpos); - gSavedSettings.setVector3("SkySunDefaultPosition", LLViewerCamera::getInstance()->getAtAxis()); - } - - if (gSavedSettings.getBOOL("MouseMoon")) - { - LLVector3 moonpos = LLViewerCamera::getInstance()->getAtAxis(); - gSky.setMoonDirectionCFR(moonpos); - gSavedSettings.setVector3("SkyMoonDefaultPosition", LLViewerCamera::getInstance()->getAtAxis()); - } - - gViewerWindow->moveCursorToCenter(); - gViewerWindow->hideCursor(); - } - - LL_DEBUGS("UserInput") << "hover handled by LLToolGun (mouselook)" << LL_ENDL; - } - else - { - LL_DEBUGS("UserInput") << "hover handled by LLToolGun (not mouselook)" << LL_ENDL; - } - - // HACK to avoid assert: error checking system makes sure that the cursor is set during every handleHover. This is actually a no-op since the cursor is hidden. - gViewerWindow->setCursor(UI_CURSOR_ARROW); - - return true; -} - -void LLToolGun::draw() -{ - if( gSavedSettings.getBOOL("ShowCrosshairs") ) - { - LLUIImagePtr crosshair = LLUI::getUIImage("crosshairs.tga"); - crosshair->draw( - ( gViewerWindow->getWorldViewRectScaled().getWidth() - crosshair->getWidth() ) / 2, - ( gViewerWindow->getWorldViewRectScaled().getHeight() - crosshair->getHeight() ) / 2); - } -} +/** + * @file lltoolgun.cpp + * @brief LLToolGun class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lltoolgun.h" + +#include "llviewerwindow.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llviewercontrol.h" +#include "llsky.h" +#include "llappviewer.h" +#include "llresmgr.h" +#include "llfontgl.h" +#include "llui.h" +#include "llviewertexturelist.h" +#include "llviewercamera.h" +#include "llhudmanager.h" +#include "lltoolmgr.h" +#include "lltoolgrab.h" +#include "lluiimage.h" +// Linden library includes +#include "llwindow.h" // setMouseClipping() + +LLToolGun::LLToolGun( LLToolComposite* composite ) +: LLTool( std::string("gun"), composite ), + mIsSelected(false) +{ +} + +void LLToolGun::handleSelect() +{ + gViewerWindow->hideCursor(); + gViewerWindow->moveCursorToCenter(); + gViewerWindow->getWindow()->setMouseClipping(true); + mIsSelected = true; +} + +void LLToolGun::handleDeselect() +{ + gViewerWindow->moveCursorToCenter(); + gViewerWindow->showCursor(); + gViewerWindow->getWindow()->setMouseClipping(false); + mIsSelected = false; +} + +bool LLToolGun::handleMouseDown(S32 x, S32 y, MASK mask) +{ + gGrabTransientTool = this; + LLToolMgr::getInstance()->getCurrentToolset()->selectTool( LLToolGrab::getInstance() ); + + return LLToolGrab::getInstance()->handleMouseDown(x, y, mask); +} + +bool LLToolGun::handleHover(S32 x, S32 y, MASK mask) +{ + if( gAgentCamera.cameraMouselook() && mIsSelected ) + { + const F32 NOMINAL_MOUSE_SENSITIVITY = 0.0025f; + + F32 mouse_sensitivity = gSavedSettings.getF32("MouseSensitivity"); + mouse_sensitivity = clamp_rescale(mouse_sensitivity, 0.f, 15.f, 0.5f, 2.75f) * NOMINAL_MOUSE_SENSITIVITY; + + // ...move the view with the mouse + + // get mouse movement delta + S32 dx = -gViewerWindow->getCurrentMouseDX(); + S32 dy = -gViewerWindow->getCurrentMouseDY(); + + if (dx != 0 || dy != 0) + { + // ...actually moved off center + if (gSavedSettings.getBOOL("InvertMouse")) + { + gAgent.pitch(mouse_sensitivity * -dy); + } + else + { + gAgent.pitch(mouse_sensitivity * dy); + } + LLVector3 skyward = gAgent.getReferenceUpVector(); + gAgent.rotate(mouse_sensitivity * dx, skyward.mV[VX], skyward.mV[VY], skyward.mV[VZ]); + + if (gSavedSettings.getBOOL("MouseSun")) + { + LLVector3 sunpos = LLViewerCamera::getInstance()->getAtAxis(); + gSky.setSunDirectionCFR(sunpos); + gSavedSettings.setVector3("SkySunDefaultPosition", LLViewerCamera::getInstance()->getAtAxis()); + } + + if (gSavedSettings.getBOOL("MouseMoon")) + { + LLVector3 moonpos = LLViewerCamera::getInstance()->getAtAxis(); + gSky.setMoonDirectionCFR(moonpos); + gSavedSettings.setVector3("SkyMoonDefaultPosition", LLViewerCamera::getInstance()->getAtAxis()); + } + + gViewerWindow->moveCursorToCenter(); + gViewerWindow->hideCursor(); + } + + LL_DEBUGS("UserInput") << "hover handled by LLToolGun (mouselook)" << LL_ENDL; + } + else + { + LL_DEBUGS("UserInput") << "hover handled by LLToolGun (not mouselook)" << LL_ENDL; + } + + // HACK to avoid assert: error checking system makes sure that the cursor is set during every handleHover. This is actually a no-op since the cursor is hidden. + gViewerWindow->setCursor(UI_CURSOR_ARROW); + + return true; +} + +void LLToolGun::draw() +{ + if( gSavedSettings.getBOOL("ShowCrosshairs") ) + { + LLUIImagePtr crosshair = LLUI::getUIImage("crosshairs.tga"); + crosshair->draw( + ( gViewerWindow->getWorldViewRectScaled().getWidth() - crosshair->getWidth() ) / 2, + ( gViewerWindow->getWorldViewRectScaled().getHeight() - crosshair->getHeight() ) / 2); + } +} diff --git a/indra/newview/lltoolgun.h b/indra/newview/lltoolgun.h index 5fb5dc9d3e..f9bb9bb39a 100644 --- a/indra/newview/lltoolgun.h +++ b/indra/newview/lltoolgun.h @@ -1,53 +1,53 @@ -/** - * @file lltoolgun.h - * @brief LLToolGun class header file - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_TOOLGUN_H -#define LL_TOOLGUN_H - -#include "lltool.h" -#include "llui.h" - - -class LLToolGun : public LLTool -{ -public: - LLToolGun( LLToolComposite* composite=NULL ); - - virtual void draw(); - - virtual void handleSelect(); - virtual void handleDeselect(); - - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleHover(S32 x, S32 y, MASK mask); - - virtual LLTool* getOverrideTool(MASK mask) { return NULL; } - virtual bool clipMouseWhenDown() { return false; } -private: - bool mIsSelected; -}; - -#endif +/** + * @file lltoolgun.h + * @brief LLToolGun class header file + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_TOOLGUN_H +#define LL_TOOLGUN_H + +#include "lltool.h" +#include "llui.h" + + +class LLToolGun : public LLTool +{ +public: + LLToolGun( LLToolComposite* composite=NULL ); + + virtual void draw(); + + virtual void handleSelect(); + virtual void handleDeselect(); + + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleHover(S32 x, S32 y, MASK mask); + + virtual LLTool* getOverrideTool(MASK mask) { return NULL; } + virtual bool clipMouseWhenDown() { return false; } +private: + bool mIsSelected; +}; + +#endif diff --git a/indra/newview/lltoolindividual.cpp b/indra/newview/lltoolindividual.cpp index e1fd1abeb2..8962c55273 100644 --- a/indra/newview/lltoolindividual.cpp +++ b/indra/newview/lltoolindividual.cpp @@ -1,115 +1,115 @@ -/** - * @file lltoolindividual.cpp - * @brief LLToolIndividual class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -//***************************************************************************** -// -// This is a tool for selecting individual object from the -// toolbox. Handy for when you want to deal with child object -// inventory... -// -//***************************************************************************** - -#include "llviewerprecompiledheaders.h" -#include "lltoolindividual.h" - -#include "llfloaterreg.h" -#include "llselectmgr.h" -#include "llviewerobject.h" -#include "llviewerwindow.h" -#include "llfloatertools.h" - -///---------------------------------------------------------------------------- -/// Globals -///---------------------------------------------------------------------------- - - -///---------------------------------------------------------------------------- -/// Local function declarations, constants, enums, and typedefs -///---------------------------------------------------------------------------- - - -///---------------------------------------------------------------------------- -/// Class LLToolIndividual -///---------------------------------------------------------------------------- - -// Default constructor -LLToolIndividual::LLToolIndividual() -: LLTool(std::string("Individual")) -{ -} - -// Destroys the object -LLToolIndividual::~LLToolIndividual() -{ -} - -bool LLToolIndividual::handleMouseDown(S32 x, S32 y, MASK mask) -{ - gViewerWindow->pickAsync(x, y, mask, pickCallback); - return true; -} - -void LLToolIndividual::pickCallback(const LLPickInfo& pick_info) -{ - LLViewerObject* obj = pick_info.getObject(); - LLSelectMgr::getInstance()->deselectAll(); - if(obj) - { - LLSelectMgr::getInstance()->selectObjectOnly(obj); - } -} - -bool LLToolIndividual::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - if(!LLSelectMgr::getInstance()->getSelection()->isEmpty()) - { - // You should already have an object selected from the mousedown. - // If so, show its inventory. - LLFloaterReg::showInstance("build", "Content"); - return true; - } - else - { - // Nothing selected means the first mouse click was probably - // bad, so try again. - return false; - } -} - -void LLToolIndividual::handleSelect() -{ - const bool children_ok = true; - LLViewerObject* obj = LLSelectMgr::getInstance()->getSelection()->getFirstRootObject(children_ok); - LLSelectMgr::getInstance()->deselectAll(); - if(obj) - { - LLSelectMgr::getInstance()->selectObjectOnly(obj); - } -} - -///---------------------------------------------------------------------------- -/// Local function definitions -///---------------------------------------------------------------------------- +/** + * @file lltoolindividual.cpp + * @brief LLToolIndividual class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +//***************************************************************************** +// +// This is a tool for selecting individual object from the +// toolbox. Handy for when you want to deal with child object +// inventory... +// +//***************************************************************************** + +#include "llviewerprecompiledheaders.h" +#include "lltoolindividual.h" + +#include "llfloaterreg.h" +#include "llselectmgr.h" +#include "llviewerobject.h" +#include "llviewerwindow.h" +#include "llfloatertools.h" + +///---------------------------------------------------------------------------- +/// Globals +///---------------------------------------------------------------------------- + + +///---------------------------------------------------------------------------- +/// Local function declarations, constants, enums, and typedefs +///---------------------------------------------------------------------------- + + +///---------------------------------------------------------------------------- +/// Class LLToolIndividual +///---------------------------------------------------------------------------- + +// Default constructor +LLToolIndividual::LLToolIndividual() +: LLTool(std::string("Individual")) +{ +} + +// Destroys the object +LLToolIndividual::~LLToolIndividual() +{ +} + +bool LLToolIndividual::handleMouseDown(S32 x, S32 y, MASK mask) +{ + gViewerWindow->pickAsync(x, y, mask, pickCallback); + return true; +} + +void LLToolIndividual::pickCallback(const LLPickInfo& pick_info) +{ + LLViewerObject* obj = pick_info.getObject(); + LLSelectMgr::getInstance()->deselectAll(); + if(obj) + { + LLSelectMgr::getInstance()->selectObjectOnly(obj); + } +} + +bool LLToolIndividual::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + if(!LLSelectMgr::getInstance()->getSelection()->isEmpty()) + { + // You should already have an object selected from the mousedown. + // If so, show its inventory. + LLFloaterReg::showInstance("build", "Content"); + return true; + } + else + { + // Nothing selected means the first mouse click was probably + // bad, so try again. + return false; + } +} + +void LLToolIndividual::handleSelect() +{ + const bool children_ok = true; + LLViewerObject* obj = LLSelectMgr::getInstance()->getSelection()->getFirstRootObject(children_ok); + LLSelectMgr::getInstance()->deselectAll(); + if(obj) + { + LLSelectMgr::getInstance()->selectObjectOnly(obj); + } +} + +///---------------------------------------------------------------------------- +/// Local function definitions +///---------------------------------------------------------------------------- diff --git a/indra/newview/lltoolindividual.h b/indra/newview/lltoolindividual.h index c4bb3f88fb..baf687d183 100644 --- a/indra/newview/lltoolindividual.h +++ b/indra/newview/lltoolindividual.h @@ -1,57 +1,57 @@ -/** - * @file lltoolindividual.h - * @brief LLToolIndividual class header file - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLTOOLINDIVIDUAL_H -#define LL_LLTOOLINDIVIDUAL_H - -#include "lltool.h" - -class LLPickInfo; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class lltoolindividual -// -// A tool to select individual objects rather than linked sets. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLToolIndividual : public LLTool, public LLSingleton -{ - LLSINGLETON(LLToolIndividual); - virtual ~LLToolIndividual(); -public: - - virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; - virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; - virtual void handleSelect() override; - - static void pickCallback(const LLPickInfo& pick_info); - -protected: - -}; - - -#endif // LL_LLTOOLINDIVIDUAL_H +/** + * @file lltoolindividual.h + * @brief LLToolIndividual class header file + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLTOOLINDIVIDUAL_H +#define LL_LLTOOLINDIVIDUAL_H + +#include "lltool.h" + +class LLPickInfo; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class lltoolindividual +// +// A tool to select individual objects rather than linked sets. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLToolIndividual : public LLTool, public LLSingleton +{ + LLSINGLETON(LLToolIndividual); + virtual ~LLToolIndividual(); +public: + + virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; + virtual void handleSelect() override; + + static void pickCallback(const LLPickInfo& pick_info); + +protected: + +}; + + +#endif // LL_LLTOOLINDIVIDUAL_H diff --git a/indra/newview/lltoolmgr.cpp b/indra/newview/lltoolmgr.cpp index dd247ef0f7..07963a7bed 100644 --- a/indra/newview/lltoolmgr.cpp +++ b/indra/newview/lltoolmgr.cpp @@ -1,530 +1,530 @@ -/** - * @file lltoolmgr.cpp - * @brief LLToolMgr class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lltoolmgr.h" - -#include "lluictrl.h" -#include "llmenugl.h" -#include "llfloaterreg.h" - -//#include "llfirstuse.h" -// tools and manipulators -#include "llfloaterinspect.h" -#include "lltool.h" -#include "llmanipscale.h" -#include "llmarketplacefunctions.h" -#include "llselectmgr.h" -#include "lltoolbrush.h" -#include "lltoolcomp.h" -#include "lltooldraganddrop.h" -#include "lltoolface.h" -#include "lltoolfocus.h" -#include "lltoolgrab.h" -#include "lltoolindividual.h" -#include "lltoolmorph.h" -#include "lltoolpie.h" -#include "lltoolselectland.h" -#include "lltoolobjpicker.h" -#include "lltoolpipette.h" -#include "llagent.h" -#include "llagentcamera.h" -#include "llviewercontrol.h" -#include "llviewerjoystick.h" -#include "llviewermenu.h" -#include "llviewerparcelmgr.h" - - -// Used when app not active to avoid processing hover. -LLTool* gToolNull = NULL; - -LLToolset* gBasicToolset = NULL; -LLToolset* gCameraToolset = NULL; -//LLToolset* gLandToolset = NULL; -LLToolset* gMouselookToolset = NULL; -LLToolset* gFaceEditToolset = NULL; - -///////////////////////////////////////////////////// -// LLToolMgr - -LLToolMgr::LLToolMgr() - : - mBaseTool(NULL), - mSavedTool(NULL), - mTransientTool( NULL ), - mOverrideTool( NULL ), - mSelectedTool( NULL ), - mCurrentToolset( NULL ) -{ - // Not a panel, register these callbacks globally. - LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Build.Active", boost::bind(&LLToolMgr::inEdit, this)); - LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Build.Enabled", boost::bind(&LLToolMgr::canEdit, this)); - LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Build.EnabledOrActive", boost::bind(&LLToolMgr::buildEnabledOrActive, this)); - LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Build.Toggle", boost::bind(&LLToolMgr::toggleBuildMode, this, _2)); - LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Marketplace.Enabled", boost::bind(&LLToolMgr::canAccessMarketplace, this)); - LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Marketplace.Toggle", boost::bind(&LLToolMgr::toggleMarketplace, this, _2)); - - gToolNull = new LLTool(LLStringUtil::null); // Does nothing - setCurrentTool(gToolNull); - - gBasicToolset = new LLToolset(); - gCameraToolset = new LLToolset(); -// gLandToolset = new LLToolset(); - gMouselookToolset = new LLToolset(); - gFaceEditToolset = new LLToolset(); - gMouselookToolset->setShowFloaterTools(false); - gFaceEditToolset->setShowFloaterTools(false); -} - -void LLToolMgr::initTools() -{ - static bool initialized = false; - if(initialized) - { - return; - } - initialized = true; - gBasicToolset->addTool( LLToolPie::getInstance() ); - gBasicToolset->addTool( LLToolCamera::getInstance() ); - gCameraToolset->addTool( LLToolCamera::getInstance() ); - gBasicToolset->addTool( LLToolGrab::getInstance() ); - gBasicToolset->addTool( LLToolCompTranslate::getInstance() ); - gBasicToolset->addTool( LLToolCompCreate::getInstance() ); - gBasicToolset->addTool( LLToolBrushLand::getInstance() ); - gMouselookToolset->addTool( LLToolCompGun::getInstance() ); - gBasicToolset->addTool( LLToolCompInspect::getInstance() ); - gFaceEditToolset->addTool( LLToolCamera::getInstance() ); - - // On startup, use "select" tool - setCurrentToolset(gBasicToolset); - - gBasicToolset->selectTool( LLToolPie::getInstance() ); -} - -LLToolMgr::~LLToolMgr() -{ - delete gBasicToolset; - gBasicToolset = NULL; - - delete gMouselookToolset; - gMouselookToolset = NULL; - - delete gFaceEditToolset; - gFaceEditToolset = NULL; - - delete gCameraToolset; - gCameraToolset = NULL; - - delete gToolNull; - gToolNull = NULL; -} - -bool LLToolMgr::usingTransientTool() -{ - return mTransientTool != nullptr; -} - -void LLToolMgr::setCurrentToolset(LLToolset* current) -{ - if (!current) - return; - - // switching toolsets? - if (current != mCurrentToolset) - { - // deselect current tool - if (mSelectedTool) - { - mSelectedTool->handleDeselect(); - } - mCurrentToolset = current; - // select first tool of new toolset only if toolset changed - mCurrentToolset->selectFirstTool(); - } - - // update current tool based on new toolset - setCurrentTool( mCurrentToolset->getSelectedTool() ); -} - -LLToolset* LLToolMgr::getCurrentToolset() -{ - return mCurrentToolset; -} - -void LLToolMgr::setCurrentTool( LLTool* tool ) -{ - if (mTransientTool) - { - mTransientTool = NULL; - } - - mBaseTool = tool; - updateToolStatus(); - - mSavedTool = NULL; -} - -LLTool* LLToolMgr::getCurrentTool() -{ - MASK override_mask = gKeyboard ? gKeyboard->currentMask(true) : 0; - - LLTool* cur_tool = NULL; - // always use transient tools if available - if (mTransientTool) - { - mOverrideTool = NULL; - cur_tool = mTransientTool; - } - // tools currently grabbing mouse input will stay active - else if (mSelectedTool && mSelectedTool->hasMouseCapture()) - { - cur_tool = mSelectedTool; - } - else - { - mOverrideTool = mBaseTool ? mBaseTool->getOverrideTool(override_mask) : NULL; - - // use override tool if available otherwise drop back to base tool - cur_tool = mOverrideTool ? mOverrideTool : mBaseTool; - } - - LLTool* prev_tool = mSelectedTool; - // Set the selected tool to avoid infinite recursion - mSelectedTool = cur_tool; - - //update tool selection status - if (prev_tool != cur_tool) - { - if (prev_tool) - { - prev_tool->handleDeselect(); - } - if (cur_tool) - { - if ( LLToolCompInspect::getInstance()->isToolCameraActive() - && prev_tool == LLToolCamera::getInstance() - && cur_tool == LLToolPie::getInstance() ) - { - LLFloaterInspect * inspect_instance = LLFloaterReg::getTypedInstance("inspect"); - if(inspect_instance && inspect_instance->getVisible()) - { - setTransientTool(LLToolCompInspect::getInstance()); - } - } - else - { - cur_tool->handleSelect(); - } - } - } - - return mSelectedTool; -} - -LLTool* LLToolMgr::getBaseTool() -{ - return mBaseTool; -} - -void LLToolMgr::updateToolStatus() -{ - // call getcurrenttool() to calculate active tool and call handleSelect() and handleDeselect() immediately - // when active tool changes - getCurrentTool(); -} - -bool LLToolMgr::inEdit() -{ - return mBaseTool != LLToolPie::getInstance() && mBaseTool != gToolNull; -} - -bool LLToolMgr::canEdit() -{ - return LLViewerParcelMgr::getInstance()->allowAgentBuild(); -} - -bool LLToolMgr::buildEnabledOrActive() -{ - return LLFloaterReg::instanceVisible("build") || canEdit(); -} - -void LLToolMgr::toggleBuildMode(const LLSD& sdname) -{ - const std::string& param = sdname.asString(); - - LLFloaterReg::toggleInstanceOrBringToFront("build"); - if (param == "build" && !canEdit()) - { - return; - } - - bool build_visible = LLFloaterReg::instanceVisible("build"); - if (build_visible) - { - ECameraMode camMode = gAgentCamera.getCameraMode(); - if (CAMERA_MODE_MOUSELOOK == camMode || CAMERA_MODE_CUSTOMIZE_AVATAR == camMode) - { - // pull the user out of mouselook or appearance mode when entering build mode - handle_reset_view(); - } - - if (gSavedSettings.getBOOL("EditCameraMovement")) - { - // camera should be set - if (LLViewerJoystick::getInstance()->getOverrideCamera()) - { - handle_toggle_flycam(); - } - - if (gAgentCamera.getFocusOnAvatar()) - { - // zoom in if we're looking at the avatar - gAgentCamera.setFocusOnAvatar(false, ANIMATE); - gAgentCamera.setFocusGlobal(gAgent.getPositionGlobal() + 2.0 * LLVector3d(gAgent.getAtAxis())); - gAgentCamera.cameraZoomIn(0.666f); - gAgentCamera.cameraOrbitOver( 30.f * DEG_TO_RAD ); - } - } - - - setCurrentToolset(gBasicToolset); - getCurrentToolset()->selectTool( LLToolCompCreate::getInstance() ); - - // Could be first use - //LLFirstUse::useBuild(); - - gAgentCamera.resetView(false); - - // avoid spurious avatar movements - LLViewerJoystick::getInstance()->setNeedsReset(); - - } - else - { - if (gSavedSettings.getBOOL("EditCameraMovement")) - { - // just reset the view, will pull us out of edit mode - handle_reset_view(); - } - else - { - // manually disable edit mode, but do not affect the camera - gAgentCamera.resetView(false); - LLFloaterReg::hideInstance("build"); - gViewerWindow->showCursor(); - } - // avoid spurious avatar movements pulling out of edit mode - LLViewerJoystick::getInstance()->setNeedsReset(); - } - -} - -bool LLToolMgr::inBuildMode() -{ - // when entering mouselook inEdit() immediately returns true before - // cameraMouselook() actually starts returning true. Also, appearance edit - // sets build mode to true, so let's exclude that. - bool b=(inEdit() - && !gAgentCamera.cameraMouselook() - && mCurrentToolset != gFaceEditToolset); - - return b; -} - -bool LLToolMgr::canAccessMarketplace() -{ - return (LLMarketplaceData::instance().getSLMStatus() != MarketplaceStatusCodes::MARKET_PLACE_NOT_MIGRATED_MERCHANT); -} - -void LLToolMgr::toggleMarketplace(const LLSD& sdname) -{ - const std::string& param = sdname.asString(); - - if ((param != "marketplace") || !canAccessMarketplace()) - { - return; - } - - LLFloaterReg::toggleInstanceOrBringToFront("marketplace_listings"); -} - -void LLToolMgr::setTransientTool(LLTool* tool) -{ - if (!tool) - { - clearTransientTool(); - } - else - { - if (mTransientTool) - { - mTransientTool = NULL; - } - - mTransientTool = tool; - } - - updateToolStatus(); -} - -void LLToolMgr::clearTransientTool() -{ - if (mTransientTool) - { - mTransientTool = NULL; - if (!mBaseTool) - { - LL_WARNS() << "mBaseTool is NULL" << LL_ENDL; - } - } - updateToolStatus(); -} - - -void LLToolMgr::onAppFocusLost() -{ - if (LLApp::isExiting()) - return; - - if (mSelectedTool) - { - mSelectedTool->handleDeselect(); - } - updateToolStatus(); -} - -void LLToolMgr::onAppFocusGained() -{ - if (mSelectedTool) - { - mSelectedTool->handleSelect(); - } - updateToolStatus(); -} - -void LLToolMgr::clearSavedTool() -{ - mSavedTool = NULL; -} - -///////////////////////////////////////////////////// -// LLToolset - -void LLToolset::addTool(LLTool* tool) -{ - mToolList.push_back( tool ); - if( !mSelectedTool ) - { - mSelectedTool = tool; - } -} - - -void LLToolset::selectTool(LLTool* tool) -{ - mSelectedTool = tool; - LLToolMgr::getInstance()->setCurrentTool( mSelectedTool ); -} - - -void LLToolset::selectToolByIndex( S32 index ) -{ - LLTool *tool = (index >= 0 && index < (S32)mToolList.size()) ? mToolList[index] : NULL; - if (tool) - { - mSelectedTool = tool; - LLToolMgr::getInstance()->setCurrentTool( tool ); - } -} - -bool LLToolset::isToolSelected( S32 index ) -{ - LLTool *tool = (index >= 0 && index < (S32)mToolList.size()) ? mToolList[index] : NULL; - return (tool == mSelectedTool); -} - - -void LLToolset::selectFirstTool() -{ - mSelectedTool = (0 < mToolList.size()) ? mToolList[0] : NULL; - LLToolMgr::getInstance()->setCurrentTool( mSelectedTool ); -} - - -void LLToolset::selectNextTool() -{ - LLTool* next = NULL; - for( tool_list_t::iterator iter = mToolList.begin(); - iter != mToolList.end(); ) - { - LLTool* cur = *iter++; - if( cur == mSelectedTool && iter != mToolList.end() ) - { - next = *iter; - break; - } - } - - if( next ) - { - mSelectedTool = next; - LLToolMgr::getInstance()->setCurrentTool( mSelectedTool ); - } - else - { - selectFirstTool(); - } -} - -void LLToolset::selectPrevTool() -{ - LLTool* prev = NULL; - for( tool_list_t::reverse_iterator iter = mToolList.rbegin(); - iter != mToolList.rend(); ) - { - LLTool* cur = *iter++; - if( cur == mSelectedTool && iter != mToolList.rend() ) - { - prev = *iter; - break; - } - } - - if( prev ) - { - mSelectedTool = prev; - LLToolMgr::getInstance()->setCurrentTool( mSelectedTool ); - } - else if (mToolList.size() > 0) - { - selectToolByIndex((S32)mToolList.size()-1); - } -} - -//////////////////////////////////////////////////////////////////////////// - - +/** + * @file lltoolmgr.cpp + * @brief LLToolMgr class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lltoolmgr.h" + +#include "lluictrl.h" +#include "llmenugl.h" +#include "llfloaterreg.h" + +//#include "llfirstuse.h" +// tools and manipulators +#include "llfloaterinspect.h" +#include "lltool.h" +#include "llmanipscale.h" +#include "llmarketplacefunctions.h" +#include "llselectmgr.h" +#include "lltoolbrush.h" +#include "lltoolcomp.h" +#include "lltooldraganddrop.h" +#include "lltoolface.h" +#include "lltoolfocus.h" +#include "lltoolgrab.h" +#include "lltoolindividual.h" +#include "lltoolmorph.h" +#include "lltoolpie.h" +#include "lltoolselectland.h" +#include "lltoolobjpicker.h" +#include "lltoolpipette.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llviewercontrol.h" +#include "llviewerjoystick.h" +#include "llviewermenu.h" +#include "llviewerparcelmgr.h" + + +// Used when app not active to avoid processing hover. +LLTool* gToolNull = NULL; + +LLToolset* gBasicToolset = NULL; +LLToolset* gCameraToolset = NULL; +//LLToolset* gLandToolset = NULL; +LLToolset* gMouselookToolset = NULL; +LLToolset* gFaceEditToolset = NULL; + +///////////////////////////////////////////////////// +// LLToolMgr + +LLToolMgr::LLToolMgr() + : + mBaseTool(NULL), + mSavedTool(NULL), + mTransientTool( NULL ), + mOverrideTool( NULL ), + mSelectedTool( NULL ), + mCurrentToolset( NULL ) +{ + // Not a panel, register these callbacks globally. + LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Build.Active", boost::bind(&LLToolMgr::inEdit, this)); + LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Build.Enabled", boost::bind(&LLToolMgr::canEdit, this)); + LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Build.EnabledOrActive", boost::bind(&LLToolMgr::buildEnabledOrActive, this)); + LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Build.Toggle", boost::bind(&LLToolMgr::toggleBuildMode, this, _2)); + LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Marketplace.Enabled", boost::bind(&LLToolMgr::canAccessMarketplace, this)); + LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Marketplace.Toggle", boost::bind(&LLToolMgr::toggleMarketplace, this, _2)); + + gToolNull = new LLTool(LLStringUtil::null); // Does nothing + setCurrentTool(gToolNull); + + gBasicToolset = new LLToolset(); + gCameraToolset = new LLToolset(); +// gLandToolset = new LLToolset(); + gMouselookToolset = new LLToolset(); + gFaceEditToolset = new LLToolset(); + gMouselookToolset->setShowFloaterTools(false); + gFaceEditToolset->setShowFloaterTools(false); +} + +void LLToolMgr::initTools() +{ + static bool initialized = false; + if(initialized) + { + return; + } + initialized = true; + gBasicToolset->addTool( LLToolPie::getInstance() ); + gBasicToolset->addTool( LLToolCamera::getInstance() ); + gCameraToolset->addTool( LLToolCamera::getInstance() ); + gBasicToolset->addTool( LLToolGrab::getInstance() ); + gBasicToolset->addTool( LLToolCompTranslate::getInstance() ); + gBasicToolset->addTool( LLToolCompCreate::getInstance() ); + gBasicToolset->addTool( LLToolBrushLand::getInstance() ); + gMouselookToolset->addTool( LLToolCompGun::getInstance() ); + gBasicToolset->addTool( LLToolCompInspect::getInstance() ); + gFaceEditToolset->addTool( LLToolCamera::getInstance() ); + + // On startup, use "select" tool + setCurrentToolset(gBasicToolset); + + gBasicToolset->selectTool( LLToolPie::getInstance() ); +} + +LLToolMgr::~LLToolMgr() +{ + delete gBasicToolset; + gBasicToolset = NULL; + + delete gMouselookToolset; + gMouselookToolset = NULL; + + delete gFaceEditToolset; + gFaceEditToolset = NULL; + + delete gCameraToolset; + gCameraToolset = NULL; + + delete gToolNull; + gToolNull = NULL; +} + +bool LLToolMgr::usingTransientTool() +{ + return mTransientTool != nullptr; +} + +void LLToolMgr::setCurrentToolset(LLToolset* current) +{ + if (!current) + return; + + // switching toolsets? + if (current != mCurrentToolset) + { + // deselect current tool + if (mSelectedTool) + { + mSelectedTool->handleDeselect(); + } + mCurrentToolset = current; + // select first tool of new toolset only if toolset changed + mCurrentToolset->selectFirstTool(); + } + + // update current tool based on new toolset + setCurrentTool( mCurrentToolset->getSelectedTool() ); +} + +LLToolset* LLToolMgr::getCurrentToolset() +{ + return mCurrentToolset; +} + +void LLToolMgr::setCurrentTool( LLTool* tool ) +{ + if (mTransientTool) + { + mTransientTool = NULL; + } + + mBaseTool = tool; + updateToolStatus(); + + mSavedTool = NULL; +} + +LLTool* LLToolMgr::getCurrentTool() +{ + MASK override_mask = gKeyboard ? gKeyboard->currentMask(true) : 0; + + LLTool* cur_tool = NULL; + // always use transient tools if available + if (mTransientTool) + { + mOverrideTool = NULL; + cur_tool = mTransientTool; + } + // tools currently grabbing mouse input will stay active + else if (mSelectedTool && mSelectedTool->hasMouseCapture()) + { + cur_tool = mSelectedTool; + } + else + { + mOverrideTool = mBaseTool ? mBaseTool->getOverrideTool(override_mask) : NULL; + + // use override tool if available otherwise drop back to base tool + cur_tool = mOverrideTool ? mOverrideTool : mBaseTool; + } + + LLTool* prev_tool = mSelectedTool; + // Set the selected tool to avoid infinite recursion + mSelectedTool = cur_tool; + + //update tool selection status + if (prev_tool != cur_tool) + { + if (prev_tool) + { + prev_tool->handleDeselect(); + } + if (cur_tool) + { + if ( LLToolCompInspect::getInstance()->isToolCameraActive() + && prev_tool == LLToolCamera::getInstance() + && cur_tool == LLToolPie::getInstance() ) + { + LLFloaterInspect * inspect_instance = LLFloaterReg::getTypedInstance("inspect"); + if(inspect_instance && inspect_instance->getVisible()) + { + setTransientTool(LLToolCompInspect::getInstance()); + } + } + else + { + cur_tool->handleSelect(); + } + } + } + + return mSelectedTool; +} + +LLTool* LLToolMgr::getBaseTool() +{ + return mBaseTool; +} + +void LLToolMgr::updateToolStatus() +{ + // call getcurrenttool() to calculate active tool and call handleSelect() and handleDeselect() immediately + // when active tool changes + getCurrentTool(); +} + +bool LLToolMgr::inEdit() +{ + return mBaseTool != LLToolPie::getInstance() && mBaseTool != gToolNull; +} + +bool LLToolMgr::canEdit() +{ + return LLViewerParcelMgr::getInstance()->allowAgentBuild(); +} + +bool LLToolMgr::buildEnabledOrActive() +{ + return LLFloaterReg::instanceVisible("build") || canEdit(); +} + +void LLToolMgr::toggleBuildMode(const LLSD& sdname) +{ + const std::string& param = sdname.asString(); + + LLFloaterReg::toggleInstanceOrBringToFront("build"); + if (param == "build" && !canEdit()) + { + return; + } + + bool build_visible = LLFloaterReg::instanceVisible("build"); + if (build_visible) + { + ECameraMode camMode = gAgentCamera.getCameraMode(); + if (CAMERA_MODE_MOUSELOOK == camMode || CAMERA_MODE_CUSTOMIZE_AVATAR == camMode) + { + // pull the user out of mouselook or appearance mode when entering build mode + handle_reset_view(); + } + + if (gSavedSettings.getBOOL("EditCameraMovement")) + { + // camera should be set + if (LLViewerJoystick::getInstance()->getOverrideCamera()) + { + handle_toggle_flycam(); + } + + if (gAgentCamera.getFocusOnAvatar()) + { + // zoom in if we're looking at the avatar + gAgentCamera.setFocusOnAvatar(false, ANIMATE); + gAgentCamera.setFocusGlobal(gAgent.getPositionGlobal() + 2.0 * LLVector3d(gAgent.getAtAxis())); + gAgentCamera.cameraZoomIn(0.666f); + gAgentCamera.cameraOrbitOver( 30.f * DEG_TO_RAD ); + } + } + + + setCurrentToolset(gBasicToolset); + getCurrentToolset()->selectTool( LLToolCompCreate::getInstance() ); + + // Could be first use + //LLFirstUse::useBuild(); + + gAgentCamera.resetView(false); + + // avoid spurious avatar movements + LLViewerJoystick::getInstance()->setNeedsReset(); + + } + else + { + if (gSavedSettings.getBOOL("EditCameraMovement")) + { + // just reset the view, will pull us out of edit mode + handle_reset_view(); + } + else + { + // manually disable edit mode, but do not affect the camera + gAgentCamera.resetView(false); + LLFloaterReg::hideInstance("build"); + gViewerWindow->showCursor(); + } + // avoid spurious avatar movements pulling out of edit mode + LLViewerJoystick::getInstance()->setNeedsReset(); + } + +} + +bool LLToolMgr::inBuildMode() +{ + // when entering mouselook inEdit() immediately returns true before + // cameraMouselook() actually starts returning true. Also, appearance edit + // sets build mode to true, so let's exclude that. + bool b=(inEdit() + && !gAgentCamera.cameraMouselook() + && mCurrentToolset != gFaceEditToolset); + + return b; +} + +bool LLToolMgr::canAccessMarketplace() +{ + return (LLMarketplaceData::instance().getSLMStatus() != MarketplaceStatusCodes::MARKET_PLACE_NOT_MIGRATED_MERCHANT); +} + +void LLToolMgr::toggleMarketplace(const LLSD& sdname) +{ + const std::string& param = sdname.asString(); + + if ((param != "marketplace") || !canAccessMarketplace()) + { + return; + } + + LLFloaterReg::toggleInstanceOrBringToFront("marketplace_listings"); +} + +void LLToolMgr::setTransientTool(LLTool* tool) +{ + if (!tool) + { + clearTransientTool(); + } + else + { + if (mTransientTool) + { + mTransientTool = NULL; + } + + mTransientTool = tool; + } + + updateToolStatus(); +} + +void LLToolMgr::clearTransientTool() +{ + if (mTransientTool) + { + mTransientTool = NULL; + if (!mBaseTool) + { + LL_WARNS() << "mBaseTool is NULL" << LL_ENDL; + } + } + updateToolStatus(); +} + + +void LLToolMgr::onAppFocusLost() +{ + if (LLApp::isExiting()) + return; + + if (mSelectedTool) + { + mSelectedTool->handleDeselect(); + } + updateToolStatus(); +} + +void LLToolMgr::onAppFocusGained() +{ + if (mSelectedTool) + { + mSelectedTool->handleSelect(); + } + updateToolStatus(); +} + +void LLToolMgr::clearSavedTool() +{ + mSavedTool = NULL; +} + +///////////////////////////////////////////////////// +// LLToolset + +void LLToolset::addTool(LLTool* tool) +{ + mToolList.push_back( tool ); + if( !mSelectedTool ) + { + mSelectedTool = tool; + } +} + + +void LLToolset::selectTool(LLTool* tool) +{ + mSelectedTool = tool; + LLToolMgr::getInstance()->setCurrentTool( mSelectedTool ); +} + + +void LLToolset::selectToolByIndex( S32 index ) +{ + LLTool *tool = (index >= 0 && index < (S32)mToolList.size()) ? mToolList[index] : NULL; + if (tool) + { + mSelectedTool = tool; + LLToolMgr::getInstance()->setCurrentTool( tool ); + } +} + +bool LLToolset::isToolSelected( S32 index ) +{ + LLTool *tool = (index >= 0 && index < (S32)mToolList.size()) ? mToolList[index] : NULL; + return (tool == mSelectedTool); +} + + +void LLToolset::selectFirstTool() +{ + mSelectedTool = (0 < mToolList.size()) ? mToolList[0] : NULL; + LLToolMgr::getInstance()->setCurrentTool( mSelectedTool ); +} + + +void LLToolset::selectNextTool() +{ + LLTool* next = NULL; + for( tool_list_t::iterator iter = mToolList.begin(); + iter != mToolList.end(); ) + { + LLTool* cur = *iter++; + if( cur == mSelectedTool && iter != mToolList.end() ) + { + next = *iter; + break; + } + } + + if( next ) + { + mSelectedTool = next; + LLToolMgr::getInstance()->setCurrentTool( mSelectedTool ); + } + else + { + selectFirstTool(); + } +} + +void LLToolset::selectPrevTool() +{ + LLTool* prev = NULL; + for( tool_list_t::reverse_iterator iter = mToolList.rbegin(); + iter != mToolList.rend(); ) + { + LLTool* cur = *iter++; + if( cur == mSelectedTool && iter != mToolList.rend() ) + { + prev = *iter; + break; + } + } + + if( prev ) + { + mSelectedTool = prev; + LLToolMgr::getInstance()->setCurrentTool( mSelectedTool ); + } + else if (mToolList.size() > 0) + { + selectToolByIndex((S32)mToolList.size()-1); + } +} + +//////////////////////////////////////////////////////////////////////////// + + diff --git a/indra/newview/lltoolmgr.h b/indra/newview/lltoolmgr.h index 35edc2377f..6cfdbaac06 100644 --- a/indra/newview/lltoolmgr.h +++ b/indra/newview/lltoolmgr.h @@ -1,131 +1,131 @@ -/** - * @file lltoolmgr.h - * @brief LLToolMgr class header file - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_TOOLMGR_H -#define LL_TOOLMGR_H - -#include "llkeyboard.h" - -class LLTool; -class LLToolset; - -// Key bindings for common operations -const MASK MASK_VERTICAL = MASK_CONTROL; -const MASK MASK_SPIN = MASK_CONTROL | MASK_SHIFT; -const MASK MASK_ZOOM = MASK_NONE; -const MASK MASK_ORBIT = MASK_CONTROL; -const MASK MASK_PAN = MASK_CONTROL | MASK_SHIFT; -const MASK MASK_COPY = MASK_SHIFT; - -class LLToolMgr : public LLSingleton -{ - LLSINGLETON(LLToolMgr); - ~LLToolMgr(); -public: - - // Must be called after gSavedSettings set up. - void initTools(); - - LLTool* getCurrentTool(); // returns active tool, taking into account keyboard state - LLTool* getBaseTool(); // returns active tool when overrides are deactivated - - bool inEdit(); - bool canEdit(); - bool buildEnabledOrActive(); - bool canAccessMarketplace(); - void toggleBuildMode(const LLSD& sdname); - void toggleMarketplace(const LLSD& sdname); - - /* Determines if we are in Build mode or not. */ - bool inBuildMode(); - - void setTransientTool(LLTool* tool); - void clearTransientTool(); - bool usingTransientTool(); - - void setCurrentToolset(LLToolset* current); - LLToolset* getCurrentToolset(); - - void onAppFocusGained(); - void onAppFocusLost(); - - void clearSavedTool(); - -protected: - friend class LLToolset; // to allow access to setCurrentTool(); - void setCurrentTool(LLTool* tool); - void updateToolStatus(); - -protected: - LLTool* mBaseTool; - LLTool* mSavedTool; // The current tool at the time application focus was lost. - LLTool* mTransientTool; - LLTool* mOverrideTool; // Tool triggered by keyboard override - LLTool* mSelectedTool; // last known active tool - LLToolset* mCurrentToolset; -}; - -// Sets of tools for various modes -class LLToolset -{ -public: - LLToolset() : mSelectedTool(NULL), mIsShowFloaterTools(true) {} - - LLTool* getSelectedTool() { return mSelectedTool; } - - void addTool(LLTool* tool); - - void selectTool( LLTool* tool ); - void selectToolByIndex( S32 index ); - void selectFirstTool(); - void selectNextTool(); - void selectPrevTool(); - - void handleScrollWheel(S32 clicks); - - bool isToolSelected( S32 index ); - - void setShowFloaterTools(bool pShowFloaterTools) {mIsShowFloaterTools = pShowFloaterTools;}; - bool isShowFloaterTools() const {return mIsShowFloaterTools;}; - -protected: - LLTool* mSelectedTool; - typedef std::vector tool_list_t; - tool_list_t mToolList; - bool mIsShowFloaterTools; -}; - -// Globals - -extern LLToolset* gBasicToolset; -extern LLToolset *gCameraToolset; -//extern LLToolset *gLandToolset; -extern LLToolset* gMouselookToolset; -extern LLToolset* gFaceEditToolset; - -extern LLTool* gToolNull; - -#endif +/** + * @file lltoolmgr.h + * @brief LLToolMgr class header file + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_TOOLMGR_H +#define LL_TOOLMGR_H + +#include "llkeyboard.h" + +class LLTool; +class LLToolset; + +// Key bindings for common operations +const MASK MASK_VERTICAL = MASK_CONTROL; +const MASK MASK_SPIN = MASK_CONTROL | MASK_SHIFT; +const MASK MASK_ZOOM = MASK_NONE; +const MASK MASK_ORBIT = MASK_CONTROL; +const MASK MASK_PAN = MASK_CONTROL | MASK_SHIFT; +const MASK MASK_COPY = MASK_SHIFT; + +class LLToolMgr : public LLSingleton +{ + LLSINGLETON(LLToolMgr); + ~LLToolMgr(); +public: + + // Must be called after gSavedSettings set up. + void initTools(); + + LLTool* getCurrentTool(); // returns active tool, taking into account keyboard state + LLTool* getBaseTool(); // returns active tool when overrides are deactivated + + bool inEdit(); + bool canEdit(); + bool buildEnabledOrActive(); + bool canAccessMarketplace(); + void toggleBuildMode(const LLSD& sdname); + void toggleMarketplace(const LLSD& sdname); + + /* Determines if we are in Build mode or not. */ + bool inBuildMode(); + + void setTransientTool(LLTool* tool); + void clearTransientTool(); + bool usingTransientTool(); + + void setCurrentToolset(LLToolset* current); + LLToolset* getCurrentToolset(); + + void onAppFocusGained(); + void onAppFocusLost(); + + void clearSavedTool(); + +protected: + friend class LLToolset; // to allow access to setCurrentTool(); + void setCurrentTool(LLTool* tool); + void updateToolStatus(); + +protected: + LLTool* mBaseTool; + LLTool* mSavedTool; // The current tool at the time application focus was lost. + LLTool* mTransientTool; + LLTool* mOverrideTool; // Tool triggered by keyboard override + LLTool* mSelectedTool; // last known active tool + LLToolset* mCurrentToolset; +}; + +// Sets of tools for various modes +class LLToolset +{ +public: + LLToolset() : mSelectedTool(NULL), mIsShowFloaterTools(true) {} + + LLTool* getSelectedTool() { return mSelectedTool; } + + void addTool(LLTool* tool); + + void selectTool( LLTool* tool ); + void selectToolByIndex( S32 index ); + void selectFirstTool(); + void selectNextTool(); + void selectPrevTool(); + + void handleScrollWheel(S32 clicks); + + bool isToolSelected( S32 index ); + + void setShowFloaterTools(bool pShowFloaterTools) {mIsShowFloaterTools = pShowFloaterTools;}; + bool isShowFloaterTools() const {return mIsShowFloaterTools;}; + +protected: + LLTool* mSelectedTool; + typedef std::vector tool_list_t; + tool_list_t mToolList; + bool mIsShowFloaterTools; +}; + +// Globals + +extern LLToolset* gBasicToolset; +extern LLToolset *gCameraToolset; +//extern LLToolset *gLandToolset; +extern LLToolset* gMouselookToolset; +extern LLToolset* gFaceEditToolset; + +extern LLTool* gToolNull; + +#endif diff --git a/indra/newview/lltoolmorph.cpp b/indra/newview/lltoolmorph.cpp index f76566e50a..b3871a6d6c 100644 --- a/indra/newview/lltoolmorph.cpp +++ b/indra/newview/lltoolmorph.cpp @@ -1,331 +1,331 @@ -/** - * @file lltoolmorph.cpp - * @brief A tool to manipulate faces.. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -// File includes -#include "lltoolmorph.h" -#include "llrender.h" - -// Library includes -#include "llaudioengine.h" -#include "llviewercontrol.h" -#include "llfontgl.h" -#include "llwearable.h" -#include "sound_ids.h" -#include "v3math.h" -#include "v3color.h" - -// Viewer includes -#include "llagent.h" -#include "lldrawable.h" -#include "lldrawpoolavatar.h" -#include "llface.h" -#include "llmorphview.h" -#include "llresmgr.h" -#include "llselectmgr.h" -#include "llsky.h" -#include "lltexlayer.h" -#include "lltoolmgr.h" -#include "llui.h" -#include "llviewercamera.h" -#include "llviewertexturelist.h" -#include "llviewerobject.h" -#include "llviewerwearable.h" -#include "llviewerwindow.h" -#include "llvoavatarself.h" -#include "pipeline.h" - - -//static -LLVisualParamHint::instance_list_t LLVisualParamHint::sInstances; -bool LLVisualParamReset::sDirty = false; - -//----------------------------------------------------------------------------- -// LLVisualParamHint() -//----------------------------------------------------------------------------- - -// static -LLVisualParamHint::LLVisualParamHint( - S32 pos_x, S32 pos_y, - S32 width, S32 height, - LLViewerJointMesh *mesh, - LLViewerVisualParam *param, - LLWearable *wearable, - F32 param_weight, - LLJoint* jointp) - : - LLViewerDynamicTexture(width, height, 3, LLViewerDynamicTexture::ORDER_MIDDLE, true ), - mNeedsUpdate( true ), - mIsVisible( false ), - mJointMesh( mesh ), - mVisualParam( param ), - mWearablePtr( wearable ), - mVisualParamWeight( param_weight ), - mAllowsUpdates( true ), - mDelayFrames( 0 ), - mRect( pos_x, pos_y + height, pos_x + width, pos_y ), - mLastParamWeight(0.f), - mCamTargetJoint(jointp) -{ - LLVisualParamHint::sInstances.insert( this ); - mBackgroundp = LLUI::getUIImage("avatar_thumb_bkgrnd.png"); - - llassert(width != 0); - llassert(height != 0); -} - -//----------------------------------------------------------------------------- -// ~LLVisualParamHint() -//----------------------------------------------------------------------------- -LLVisualParamHint::~LLVisualParamHint() -{ - LLVisualParamHint::sInstances.erase( this ); -} - -//virtual -S8 LLVisualParamHint::getType() const -{ - return LLViewerDynamicTexture::LL_VISUAL_PARAM_HINT ; -} - -//----------------------------------------------------------------------------- -// static -// requestHintUpdates() -// Requests updates for all instances (excluding two possible exceptions) Grungy but efficient. -//----------------------------------------------------------------------------- -void LLVisualParamHint::requestHintUpdates( LLVisualParamHint* exception1, LLVisualParamHint* exception2 ) -{ - S32 delay_frames = 0; - for (instance_list_t::iterator iter = sInstances.begin(); - iter != sInstances.end(); ++iter) - { - LLVisualParamHint* instance = *iter; - if( (instance != exception1) && (instance != exception2) ) - { - if( instance->mAllowsUpdates ) - { - instance->mNeedsUpdate = true; - instance->mDelayFrames = delay_frames; - delay_frames++; - } - else - { - instance->mNeedsUpdate = true; - instance->mDelayFrames = 0; - } - } - } -} - -bool LLVisualParamHint::needsRender() -{ - return mNeedsUpdate && mDelayFrames-- <= 0 && !gAgentAvatarp->getIsAppearanceAnimating() && mAllowsUpdates; -} - -void LLVisualParamHint::preRender(bool clear_depth) -{ - LLViewerWearable* wearable = (LLViewerWearable*)mWearablePtr; - if (wearable) - { - wearable->setVolatile(true); - } - mLastParamWeight = mVisualParam->getWeight(); - mWearablePtr->setVisualParamWeight(mVisualParam->getID(), mVisualParamWeight); - gAgentAvatarp->setVisualParamWeight(mVisualParam->getID(), mVisualParamWeight); - gAgentAvatarp->setVisualParamWeight("Blink_Left", 0.f); - gAgentAvatarp->setVisualParamWeight("Blink_Right", 0.f); - gAgentAvatarp->updateComposites(); - // Calling LLCharacter version, as we don't want position/height changes to cause the avatar to jump - // up and down when we're doing preview renders. -Nyx - gAgentAvatarp->LLCharacter::updateVisualParams(); - - if (gAgentAvatarp->mDrawable.notNull()) - { - gAgentAvatarp->updateGeometry(gAgentAvatarp->mDrawable); - gAgentAvatarp->updateLOD(); - } - else - { - LL_WARNS() << "Attempting to update avatar's geometry, but drawable doesn't exist yet" << LL_ENDL; - } - - LLViewerDynamicTexture::preRender(clear_depth); -} - -//----------------------------------------------------------------------------- -// render() -//----------------------------------------------------------------------------- -bool LLVisualParamHint::render() -{ - LLVisualParamReset::sDirty = true; - - gGL.pushUIMatrix(); - gGL.loadUIIdentity(); - - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.pushMatrix(); - gGL.loadIdentity(); - gGL.ortho(0.0f, mFullWidth, 0.0f, mFullHeight, -1.0f, 1.0f); - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - gGL.loadIdentity(); - - gUIProgram.bind(); - - LLGLSUIDefault gls_ui; - //LLGLState::verify(true); - mBackgroundp->draw(0, 0, mFullWidth, mFullHeight); - - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.popMatrix(); - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.popMatrix(); - - mNeedsUpdate = false; - mIsVisible = true; - - LLQuaternion avatar_rotation; - LLJoint* root_joint = gAgentAvatarp->getRootJoint(); - if( root_joint ) - { - avatar_rotation = root_joint->getWorldRotation(); - } - - LLVector3 target_joint_pos = mCamTargetJoint->getWorldPosition(); - - LLVector3 target_offset( 0, 0, mVisualParam->getCameraElevation() ); - LLVector3 target_pos = target_joint_pos + (target_offset * avatar_rotation); - - F32 cam_angle_radians = mVisualParam->getCameraAngle() * DEG_TO_RAD; - - static LLCachedControl auto_camera_position(gSavedSettings, "AppearanceCameraMovement"); - if (!auto_camera_position) - { - cam_angle_radians += F_PI; - } - - LLVector3 camera_snapshot_offset( - mVisualParam->getCameraDistance() * cosf( cam_angle_radians ), - mVisualParam->getCameraDistance() * sinf( cam_angle_radians ), - mVisualParam->getCameraElevation() ); - LLVector3 camera_pos = target_joint_pos + (camera_snapshot_offset * avatar_rotation); - - gGL.flush(); - - LLViewerCamera::getInstance()->setAspect((F32)mFullWidth / (F32)mFullHeight); - LLViewerCamera::getInstance()->setOriginAndLookAt( - camera_pos, // camera - LLVector3::z_axis, // up - target_pos ); // point of interest - - LLViewerCamera::getInstance()->setPerspective(false, mOrigin.mX, mOrigin.mY, mFullWidth, mFullHeight, false); - - if (gAgentAvatarp->mDrawable.notNull()) - { - LLGLDepthTest gls_depth(GL_TRUE, GL_TRUE); - gGL.flush(); - gGL.setSceneBlendType(LLRender::BT_REPLACE); - gPipeline.generateImpostor(gAgentAvatarp, true); - gGL.setSceneBlendType(LLRender::BT_ALPHA); - gGL.flush(); - } - - gAgentAvatarp->setVisualParamWeight(mVisualParam->getID(), mLastParamWeight); - mWearablePtr->setVisualParamWeight(mVisualParam->getID(), mLastParamWeight); - LLViewerWearable* wearable = (LLViewerWearable*)mWearablePtr; - if (wearable) - { - wearable->setVolatile(false); - } - - gAgentAvatarp->updateVisualParams(); - gGL.color4f(1,1,1,1); - mGLTexturep->setGLTextureCreated(true); - gGL.popUIMatrix(); - - return true; -} - - -//----------------------------------------------------------------------------- -// draw() -//----------------------------------------------------------------------------- -void LLVisualParamHint::draw(F32 alpha) -{ - if (!mIsVisible) return; - - gGL.getTexUnit(0)->bind(this); - - gGL.color4f(1.f, 1.f, 1.f, alpha); - - LLGLSUIDefault gls_ui; - gGL.begin(LLRender::QUADS); - { - gGL.texCoord2i(0, 1); - gGL.vertex2i(0, mFullHeight); - gGL.texCoord2i(0, 0); - gGL.vertex2i(0, 0); - gGL.texCoord2i(1, 0); - gGL.vertex2i(mFullWidth, 0); - gGL.texCoord2i(1, 1); - gGL.vertex2i(mFullWidth, mFullHeight); - } - gGL.end(); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); -} - -//----------------------------------------------------------------------------- -// LLVisualParamReset() -//----------------------------------------------------------------------------- -LLVisualParamReset::LLVisualParamReset() : LLViewerDynamicTexture(1, 1, 1, ORDER_RESET, false) -{ -} - -//virtual -S8 LLVisualParamReset::getType() const -{ - return LLViewerDynamicTexture::LL_VISUAL_PARAM_RESET ; -} - -//----------------------------------------------------------------------------- -// render() -//----------------------------------------------------------------------------- -bool LLVisualParamReset::render() -{ - if (sDirty) - { - gAgentAvatarp->updateComposites(); - gAgentAvatarp->updateVisualParams(); - gAgentAvatarp->updateGeometry(gAgentAvatarp->mDrawable); - sDirty = false; - } - - return false; -} +/** + * @file lltoolmorph.cpp + * @brief A tool to manipulate faces.. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +// File includes +#include "lltoolmorph.h" +#include "llrender.h" + +// Library includes +#include "llaudioengine.h" +#include "llviewercontrol.h" +#include "llfontgl.h" +#include "llwearable.h" +#include "sound_ids.h" +#include "v3math.h" +#include "v3color.h" + +// Viewer includes +#include "llagent.h" +#include "lldrawable.h" +#include "lldrawpoolavatar.h" +#include "llface.h" +#include "llmorphview.h" +#include "llresmgr.h" +#include "llselectmgr.h" +#include "llsky.h" +#include "lltexlayer.h" +#include "lltoolmgr.h" +#include "llui.h" +#include "llviewercamera.h" +#include "llviewertexturelist.h" +#include "llviewerobject.h" +#include "llviewerwearable.h" +#include "llviewerwindow.h" +#include "llvoavatarself.h" +#include "pipeline.h" + + +//static +LLVisualParamHint::instance_list_t LLVisualParamHint::sInstances; +bool LLVisualParamReset::sDirty = false; + +//----------------------------------------------------------------------------- +// LLVisualParamHint() +//----------------------------------------------------------------------------- + +// static +LLVisualParamHint::LLVisualParamHint( + S32 pos_x, S32 pos_y, + S32 width, S32 height, + LLViewerJointMesh *mesh, + LLViewerVisualParam *param, + LLWearable *wearable, + F32 param_weight, + LLJoint* jointp) + : + LLViewerDynamicTexture(width, height, 3, LLViewerDynamicTexture::ORDER_MIDDLE, true ), + mNeedsUpdate( true ), + mIsVisible( false ), + mJointMesh( mesh ), + mVisualParam( param ), + mWearablePtr( wearable ), + mVisualParamWeight( param_weight ), + mAllowsUpdates( true ), + mDelayFrames( 0 ), + mRect( pos_x, pos_y + height, pos_x + width, pos_y ), + mLastParamWeight(0.f), + mCamTargetJoint(jointp) +{ + LLVisualParamHint::sInstances.insert( this ); + mBackgroundp = LLUI::getUIImage("avatar_thumb_bkgrnd.png"); + + llassert(width != 0); + llassert(height != 0); +} + +//----------------------------------------------------------------------------- +// ~LLVisualParamHint() +//----------------------------------------------------------------------------- +LLVisualParamHint::~LLVisualParamHint() +{ + LLVisualParamHint::sInstances.erase( this ); +} + +//virtual +S8 LLVisualParamHint::getType() const +{ + return LLViewerDynamicTexture::LL_VISUAL_PARAM_HINT ; +} + +//----------------------------------------------------------------------------- +// static +// requestHintUpdates() +// Requests updates for all instances (excluding two possible exceptions) Grungy but efficient. +//----------------------------------------------------------------------------- +void LLVisualParamHint::requestHintUpdates( LLVisualParamHint* exception1, LLVisualParamHint* exception2 ) +{ + S32 delay_frames = 0; + for (instance_list_t::iterator iter = sInstances.begin(); + iter != sInstances.end(); ++iter) + { + LLVisualParamHint* instance = *iter; + if( (instance != exception1) && (instance != exception2) ) + { + if( instance->mAllowsUpdates ) + { + instance->mNeedsUpdate = true; + instance->mDelayFrames = delay_frames; + delay_frames++; + } + else + { + instance->mNeedsUpdate = true; + instance->mDelayFrames = 0; + } + } + } +} + +bool LLVisualParamHint::needsRender() +{ + return mNeedsUpdate && mDelayFrames-- <= 0 && !gAgentAvatarp->getIsAppearanceAnimating() && mAllowsUpdates; +} + +void LLVisualParamHint::preRender(bool clear_depth) +{ + LLViewerWearable* wearable = (LLViewerWearable*)mWearablePtr; + if (wearable) + { + wearable->setVolatile(true); + } + mLastParamWeight = mVisualParam->getWeight(); + mWearablePtr->setVisualParamWeight(mVisualParam->getID(), mVisualParamWeight); + gAgentAvatarp->setVisualParamWeight(mVisualParam->getID(), mVisualParamWeight); + gAgentAvatarp->setVisualParamWeight("Blink_Left", 0.f); + gAgentAvatarp->setVisualParamWeight("Blink_Right", 0.f); + gAgentAvatarp->updateComposites(); + // Calling LLCharacter version, as we don't want position/height changes to cause the avatar to jump + // up and down when we're doing preview renders. -Nyx + gAgentAvatarp->LLCharacter::updateVisualParams(); + + if (gAgentAvatarp->mDrawable.notNull()) + { + gAgentAvatarp->updateGeometry(gAgentAvatarp->mDrawable); + gAgentAvatarp->updateLOD(); + } + else + { + LL_WARNS() << "Attempting to update avatar's geometry, but drawable doesn't exist yet" << LL_ENDL; + } + + LLViewerDynamicTexture::preRender(clear_depth); +} + +//----------------------------------------------------------------------------- +// render() +//----------------------------------------------------------------------------- +bool LLVisualParamHint::render() +{ + LLVisualParamReset::sDirty = true; + + gGL.pushUIMatrix(); + gGL.loadUIIdentity(); + + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.pushMatrix(); + gGL.loadIdentity(); + gGL.ortho(0.0f, mFullWidth, 0.0f, mFullHeight, -1.0f, 1.0f); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + gGL.loadIdentity(); + + gUIProgram.bind(); + + LLGLSUIDefault gls_ui; + //LLGLState::verify(true); + mBackgroundp->draw(0, 0, mFullWidth, mFullHeight); + + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.popMatrix(); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.popMatrix(); + + mNeedsUpdate = false; + mIsVisible = true; + + LLQuaternion avatar_rotation; + LLJoint* root_joint = gAgentAvatarp->getRootJoint(); + if( root_joint ) + { + avatar_rotation = root_joint->getWorldRotation(); + } + + LLVector3 target_joint_pos = mCamTargetJoint->getWorldPosition(); + + LLVector3 target_offset( 0, 0, mVisualParam->getCameraElevation() ); + LLVector3 target_pos = target_joint_pos + (target_offset * avatar_rotation); + + F32 cam_angle_radians = mVisualParam->getCameraAngle() * DEG_TO_RAD; + + static LLCachedControl auto_camera_position(gSavedSettings, "AppearanceCameraMovement"); + if (!auto_camera_position) + { + cam_angle_radians += F_PI; + } + + LLVector3 camera_snapshot_offset( + mVisualParam->getCameraDistance() * cosf( cam_angle_radians ), + mVisualParam->getCameraDistance() * sinf( cam_angle_radians ), + mVisualParam->getCameraElevation() ); + LLVector3 camera_pos = target_joint_pos + (camera_snapshot_offset * avatar_rotation); + + gGL.flush(); + + LLViewerCamera::getInstance()->setAspect((F32)mFullWidth / (F32)mFullHeight); + LLViewerCamera::getInstance()->setOriginAndLookAt( + camera_pos, // camera + LLVector3::z_axis, // up + target_pos ); // point of interest + + LLViewerCamera::getInstance()->setPerspective(false, mOrigin.mX, mOrigin.mY, mFullWidth, mFullHeight, false); + + if (gAgentAvatarp->mDrawable.notNull()) + { + LLGLDepthTest gls_depth(GL_TRUE, GL_TRUE); + gGL.flush(); + gGL.setSceneBlendType(LLRender::BT_REPLACE); + gPipeline.generateImpostor(gAgentAvatarp, true); + gGL.setSceneBlendType(LLRender::BT_ALPHA); + gGL.flush(); + } + + gAgentAvatarp->setVisualParamWeight(mVisualParam->getID(), mLastParamWeight); + mWearablePtr->setVisualParamWeight(mVisualParam->getID(), mLastParamWeight); + LLViewerWearable* wearable = (LLViewerWearable*)mWearablePtr; + if (wearable) + { + wearable->setVolatile(false); + } + + gAgentAvatarp->updateVisualParams(); + gGL.color4f(1,1,1,1); + mGLTexturep->setGLTextureCreated(true); + gGL.popUIMatrix(); + + return true; +} + + +//----------------------------------------------------------------------------- +// draw() +//----------------------------------------------------------------------------- +void LLVisualParamHint::draw(F32 alpha) +{ + if (!mIsVisible) return; + + gGL.getTexUnit(0)->bind(this); + + gGL.color4f(1.f, 1.f, 1.f, alpha); + + LLGLSUIDefault gls_ui; + gGL.begin(LLRender::QUADS); + { + gGL.texCoord2i(0, 1); + gGL.vertex2i(0, mFullHeight); + gGL.texCoord2i(0, 0); + gGL.vertex2i(0, 0); + gGL.texCoord2i(1, 0); + gGL.vertex2i(mFullWidth, 0); + gGL.texCoord2i(1, 1); + gGL.vertex2i(mFullWidth, mFullHeight); + } + gGL.end(); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); +} + +//----------------------------------------------------------------------------- +// LLVisualParamReset() +//----------------------------------------------------------------------------- +LLVisualParamReset::LLVisualParamReset() : LLViewerDynamicTexture(1, 1, 1, ORDER_RESET, false) +{ +} + +//virtual +S8 LLVisualParamReset::getType() const +{ + return LLViewerDynamicTexture::LL_VISUAL_PARAM_RESET ; +} + +//----------------------------------------------------------------------------- +// render() +//----------------------------------------------------------------------------- +bool LLVisualParamReset::render() +{ + if (sDirty) + { + gAgentAvatarp->updateComposites(); + gAgentAvatarp->updateVisualParams(); + gAgentAvatarp->updateGeometry(gAgentAvatarp->mDrawable); + sDirty = false; + } + + return false; +} diff --git a/indra/newview/lltoolmorph.h b/indra/newview/lltoolmorph.h index 344cd52cb0..fb62ba9bba 100644 --- a/indra/newview/lltoolmorph.h +++ b/indra/newview/lltoolmorph.h @@ -1,117 +1,117 @@ -/** - * @file lltoolmorph.h - * @brief A tool to select object faces. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLTOOLMORPH_H -#define LL_LLTOOLMORPH_H - -#include "lltool.h" -#include "m4math.h" -#include "v2math.h" -#include "lldynamictexture.h" -#include "llundo.h" -#include "lltextbox.h" -#include "llstrider.h" -#include "llviewervisualparam.h" -#include "llframetimer.h" -#include "llviewertexture.h" - -class LLViewerJointMesh; -class LLPolyMesh; -class LLViewerObject; -class LLJoint; - -//----------------------------------------------------------------------------- -// LLVisualParamHint -//----------------------------------------------------------------------------- -class LLVisualParamHint : public LLViewerDynamicTexture -{ -protected: - virtual ~LLVisualParamHint(); - -public: - LLVisualParamHint( - S32 pos_x, S32 pos_y, - S32 width, S32 height, - LLViewerJointMesh *mesh, - LLViewerVisualParam *param, - LLWearable *wearable, - F32 param_weight, - LLJoint* jointp); - - /*virtual*/ S8 getType() const ; - - bool needsRender(); - void preRender(bool clear_depth); - bool render(); - void requestUpdate( S32 delay_frames ) {mNeedsUpdate = true; mDelayFrames = delay_frames; } - void setUpdateDelayFrames( S32 delay_frames ) { mDelayFrames = delay_frames; } - void draw(F32 alpha); - - LLViewerVisualParam* getVisualParam() { return mVisualParam; } - F32 getVisualParamWeight() { return mVisualParamWeight; } - bool getVisible() { return mIsVisible; } - - void setAllowsUpdates( bool b ) { mAllowsUpdates = b; } - - const LLRect& getRect() { return mRect; } - - // Requests updates for all instances (excluding two possible exceptions) Grungy but efficient. - static void requestHintUpdates( LLVisualParamHint* exception1 = NULL, LLVisualParamHint* exception2 = NULL ); - -protected: - bool mNeedsUpdate; // does this texture need to be re-rendered? - bool mIsVisible; // is this distortion hint visible? - LLViewerJointMesh* mJointMesh; // mesh that this distortion applies to - LLViewerVisualParam* mVisualParam; // visual param applied by this hint - LLWearable* mWearablePtr; // wearable we're editing - F32 mVisualParamWeight; // weight for this visual parameter - bool mAllowsUpdates; // updates are blocked unless this is true - S32 mDelayFrames; // updates are blocked for this many frames - LLRect mRect; - F32 mLastParamWeight; - LLJoint* mCamTargetJoint; // joint to target with preview camera - - LLUIImagePtr mBackgroundp; - - typedef std::set< LLVisualParamHint* > instance_list_t; - static instance_list_t sInstances; -}; - -// this class resets avatar data at the end of an update cycle -class LLVisualParamReset : public LLViewerDynamicTexture -{ -protected: - /*virtual */ ~LLVisualParamReset(){} -public: - LLVisualParamReset(); - /*virtual */ bool render(); - /*virtual*/ S8 getType() const ; - - static bool sDirty; -}; - -#endif - +/** + * @file lltoolmorph.h + * @brief A tool to select object faces. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLTOOLMORPH_H +#define LL_LLTOOLMORPH_H + +#include "lltool.h" +#include "m4math.h" +#include "v2math.h" +#include "lldynamictexture.h" +#include "llundo.h" +#include "lltextbox.h" +#include "llstrider.h" +#include "llviewervisualparam.h" +#include "llframetimer.h" +#include "llviewertexture.h" + +class LLViewerJointMesh; +class LLPolyMesh; +class LLViewerObject; +class LLJoint; + +//----------------------------------------------------------------------------- +// LLVisualParamHint +//----------------------------------------------------------------------------- +class LLVisualParamHint : public LLViewerDynamicTexture +{ +protected: + virtual ~LLVisualParamHint(); + +public: + LLVisualParamHint( + S32 pos_x, S32 pos_y, + S32 width, S32 height, + LLViewerJointMesh *mesh, + LLViewerVisualParam *param, + LLWearable *wearable, + F32 param_weight, + LLJoint* jointp); + + /*virtual*/ S8 getType() const ; + + bool needsRender(); + void preRender(bool clear_depth); + bool render(); + void requestUpdate( S32 delay_frames ) {mNeedsUpdate = true; mDelayFrames = delay_frames; } + void setUpdateDelayFrames( S32 delay_frames ) { mDelayFrames = delay_frames; } + void draw(F32 alpha); + + LLViewerVisualParam* getVisualParam() { return mVisualParam; } + F32 getVisualParamWeight() { return mVisualParamWeight; } + bool getVisible() { return mIsVisible; } + + void setAllowsUpdates( bool b ) { mAllowsUpdates = b; } + + const LLRect& getRect() { return mRect; } + + // Requests updates for all instances (excluding two possible exceptions) Grungy but efficient. + static void requestHintUpdates( LLVisualParamHint* exception1 = NULL, LLVisualParamHint* exception2 = NULL ); + +protected: + bool mNeedsUpdate; // does this texture need to be re-rendered? + bool mIsVisible; // is this distortion hint visible? + LLViewerJointMesh* mJointMesh; // mesh that this distortion applies to + LLViewerVisualParam* mVisualParam; // visual param applied by this hint + LLWearable* mWearablePtr; // wearable we're editing + F32 mVisualParamWeight; // weight for this visual parameter + bool mAllowsUpdates; // updates are blocked unless this is true + S32 mDelayFrames; // updates are blocked for this many frames + LLRect mRect; + F32 mLastParamWeight; + LLJoint* mCamTargetJoint; // joint to target with preview camera + + LLUIImagePtr mBackgroundp; + + typedef std::set< LLVisualParamHint* > instance_list_t; + static instance_list_t sInstances; +}; + +// this class resets avatar data at the end of an update cycle +class LLVisualParamReset : public LLViewerDynamicTexture +{ +protected: + /*virtual */ ~LLVisualParamReset(){} +public: + LLVisualParamReset(); + /*virtual */ bool render(); + /*virtual*/ S8 getType() const ; + + static bool sDirty; +}; + +#endif + diff --git a/indra/newview/lltoolobjpicker.cpp b/indra/newview/lltoolobjpicker.cpp index 242bc48832..20089b15a3 100644 --- a/indra/newview/lltoolobjpicker.cpp +++ b/indra/newview/lltoolobjpicker.cpp @@ -1,174 +1,174 @@ -/** - * @file lltoolobjpicker.cpp - * @brief LLToolObjPicker class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -// LLToolObjPicker is a transient tool, useful for a single object pick. - -#include "llviewerprecompiledheaders.h" - -#include "lltoolobjpicker.h" - -#include "llagent.h" -#include "llselectmgr.h" -#include "llworld.h" -#include "llviewercontrol.h" -#include "llmenugl.h" -#include "lltoolmgr.h" -#include "llviewerobject.h" -#include "llviewerobjectlist.h" -#include "llviewermenu.h" -#include "llviewercamera.h" -#include "llviewerwindow.h" -#include "lldrawable.h" -#include "llrootview.h" - - -LLToolObjPicker::LLToolObjPicker() -: LLTool( std::string("ObjPicker"), NULL ), - mPicked( false ), - mHitObjectID( LLUUID::null ), - mExitCallback( NULL ), - mExitCallbackData( NULL ) -{ } - - -// returns true if an object was selected -bool LLToolObjPicker::handleMouseDown(S32 x, S32 y, MASK mask) -{ - LLRootView* viewp = gViewerWindow->getRootView(); - bool handled = viewp->handleMouseDown(x, y, mask); - - mHitObjectID.setNull(); - - if (! handled) - { - // didn't click in any UI object, so must have clicked in the world - gViewerWindow->pickAsync(x, y, mask, pickCallback); - handled = true; - } - else - { - if (hasMouseCapture()) - { - setMouseCapture(false); - } - else - { - LL_WARNS() << "PickerTool doesn't have mouse capture on mouseDown" << LL_ENDL; - } - } - - // Pass mousedown to base class - LLTool::handleMouseDown(x, y, mask); - - return handled; -} - -void LLToolObjPicker::pickCallback(const LLPickInfo& pick_info) -{ - LLToolObjPicker::getInstance()->mHitObjectID = pick_info.mObjectID; - LLToolObjPicker::getInstance()->mPicked = pick_info.mObjectID.notNull(); -} - - -bool LLToolObjPicker::handleMouseUp(S32 x, S32 y, MASK mask) -{ - LLView* viewp = gViewerWindow->getRootView(); - bool handled = viewp->handleHover(x, y, mask); - if (handled) - { - // let UI handle this - } - - LLTool::handleMouseUp(x, y, mask); - if (hasMouseCapture()) - { - setMouseCapture(false); - } - else - { - LL_WARNS() << "PickerTool doesn't have mouse capture on mouseUp" << LL_ENDL; - } - return handled; -} - - -bool LLToolObjPicker::handleHover(S32 x, S32 y, MASK mask) -{ - LLView *viewp = gViewerWindow->getRootView(); - bool handled = viewp->handleHover(x, y, mask); - if (!handled) - { - // Used to do pick on hover. Now we just always display the cursor. - ECursorType cursor = UI_CURSOR_ARROWLOCKED; - - cursor = UI_CURSOR_TOOLPICKOBJECT3; - - gViewerWindow->setCursor(cursor); - } - return handled; -} - - -void LLToolObjPicker::onMouseCaptureLost() -{ - if (mExitCallback) - { - mExitCallback(mExitCallbackData); - - mExitCallback = NULL; - mExitCallbackData = NULL; - } - - mPicked = false; - mHitObjectID.setNull(); -} - -// virtual -void LLToolObjPicker::setExitCallback(void (*callback)(void *), void *callback_data) -{ - mExitCallback = callback; - mExitCallbackData = callback_data; -} - -// virtual -void LLToolObjPicker::handleSelect() -{ - LLTool::handleSelect(); - setMouseCapture(true); -} - -// virtual -void LLToolObjPicker::handleDeselect() -{ - if (hasMouseCapture()) - { - LLTool::handleDeselect(); - setMouseCapture(false); - } -} - - - +/** + * @file lltoolobjpicker.cpp + * @brief LLToolObjPicker class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// LLToolObjPicker is a transient tool, useful for a single object pick. + +#include "llviewerprecompiledheaders.h" + +#include "lltoolobjpicker.h" + +#include "llagent.h" +#include "llselectmgr.h" +#include "llworld.h" +#include "llviewercontrol.h" +#include "llmenugl.h" +#include "lltoolmgr.h" +#include "llviewerobject.h" +#include "llviewerobjectlist.h" +#include "llviewermenu.h" +#include "llviewercamera.h" +#include "llviewerwindow.h" +#include "lldrawable.h" +#include "llrootview.h" + + +LLToolObjPicker::LLToolObjPicker() +: LLTool( std::string("ObjPicker"), NULL ), + mPicked( false ), + mHitObjectID( LLUUID::null ), + mExitCallback( NULL ), + mExitCallbackData( NULL ) +{ } + + +// returns true if an object was selected +bool LLToolObjPicker::handleMouseDown(S32 x, S32 y, MASK mask) +{ + LLRootView* viewp = gViewerWindow->getRootView(); + bool handled = viewp->handleMouseDown(x, y, mask); + + mHitObjectID.setNull(); + + if (! handled) + { + // didn't click in any UI object, so must have clicked in the world + gViewerWindow->pickAsync(x, y, mask, pickCallback); + handled = true; + } + else + { + if (hasMouseCapture()) + { + setMouseCapture(false); + } + else + { + LL_WARNS() << "PickerTool doesn't have mouse capture on mouseDown" << LL_ENDL; + } + } + + // Pass mousedown to base class + LLTool::handleMouseDown(x, y, mask); + + return handled; +} + +void LLToolObjPicker::pickCallback(const LLPickInfo& pick_info) +{ + LLToolObjPicker::getInstance()->mHitObjectID = pick_info.mObjectID; + LLToolObjPicker::getInstance()->mPicked = pick_info.mObjectID.notNull(); +} + + +bool LLToolObjPicker::handleMouseUp(S32 x, S32 y, MASK mask) +{ + LLView* viewp = gViewerWindow->getRootView(); + bool handled = viewp->handleHover(x, y, mask); + if (handled) + { + // let UI handle this + } + + LLTool::handleMouseUp(x, y, mask); + if (hasMouseCapture()) + { + setMouseCapture(false); + } + else + { + LL_WARNS() << "PickerTool doesn't have mouse capture on mouseUp" << LL_ENDL; + } + return handled; +} + + +bool LLToolObjPicker::handleHover(S32 x, S32 y, MASK mask) +{ + LLView *viewp = gViewerWindow->getRootView(); + bool handled = viewp->handleHover(x, y, mask); + if (!handled) + { + // Used to do pick on hover. Now we just always display the cursor. + ECursorType cursor = UI_CURSOR_ARROWLOCKED; + + cursor = UI_CURSOR_TOOLPICKOBJECT3; + + gViewerWindow->setCursor(cursor); + } + return handled; +} + + +void LLToolObjPicker::onMouseCaptureLost() +{ + if (mExitCallback) + { + mExitCallback(mExitCallbackData); + + mExitCallback = NULL; + mExitCallbackData = NULL; + } + + mPicked = false; + mHitObjectID.setNull(); +} + +// virtual +void LLToolObjPicker::setExitCallback(void (*callback)(void *), void *callback_data) +{ + mExitCallback = callback; + mExitCallbackData = callback_data; +} + +// virtual +void LLToolObjPicker::handleSelect() +{ + LLTool::handleSelect(); + setMouseCapture(true); +} + +// virtual +void LLToolObjPicker::handleDeselect() +{ + if (hasMouseCapture()) + { + LLTool::handleDeselect(); + setMouseCapture(false); + } +} + + + diff --git a/indra/newview/lltoolobjpicker.h b/indra/newview/lltoolobjpicker.h index 95a62fbf2e..3420541a31 100644 --- a/indra/newview/lltoolobjpicker.h +++ b/indra/newview/lltoolobjpicker.h @@ -1,64 +1,64 @@ -/** - * @file lltoolobjpicker.h - * @brief LLToolObjPicker class header file - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_TOOLOBJPICKER_H -#define LL_TOOLOBJPICKER_H - -#include "lltool.h" -#include "v3math.h" -#include "lluuid.h" - -class LLPickInfo; - -class LLToolObjPicker : public LLTool, public LLSingleton -{ - LLSINGLETON(LLToolObjPicker); -public: - - virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; - virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; - virtual bool handleHover(S32 x, S32 y, MASK mask) override; - - virtual void handleSelect() override; - virtual void handleDeselect() override; - - virtual void onMouseCaptureLost() override; - - void setExitCallback(void (*callback)(void *), void *callback_data); - - LLUUID getObjectID() const { return mHitObjectID; } - - static void pickCallback(const LLPickInfo& pick_info); - -protected: - bool mPicked; - LLUUID mHitObjectID; - void (*mExitCallback)(void *callback_data); - void *mExitCallbackData; -}; - - -#endif +/** + * @file lltoolobjpicker.h + * @brief LLToolObjPicker class header file + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_TOOLOBJPICKER_H +#define LL_TOOLOBJPICKER_H + +#include "lltool.h" +#include "v3math.h" +#include "lluuid.h" + +class LLPickInfo; + +class LLToolObjPicker : public LLTool, public LLSingleton +{ + LLSINGLETON(LLToolObjPicker); +public: + + virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; + virtual bool handleHover(S32 x, S32 y, MASK mask) override; + + virtual void handleSelect() override; + virtual void handleDeselect() override; + + virtual void onMouseCaptureLost() override; + + void setExitCallback(void (*callback)(void *), void *callback_data); + + LLUUID getObjectID() const { return mHitObjectID; } + + static void pickCallback(const LLPickInfo& pick_info); + +protected: + bool mPicked; + LLUUID mHitObjectID; + void (*mExitCallback)(void *callback_data); + void *mExitCallbackData; +}; + + +#endif diff --git a/indra/newview/lltoolpie.cpp b/indra/newview/lltoolpie.cpp index 7fbdadbf12..531e657a1e 100644 --- a/indra/newview/lltoolpie.cpp +++ b/indra/newview/lltoolpie.cpp @@ -1,2036 +1,2036 @@ -/** - * @file lltoolpie.cpp - * @brief LLToolPie class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lltoolpie.h" - -#include "indra_constants.h" -#include "llclickaction.h" -#include "llparcel.h" - -#include "llagent.h" -#include "llagentcamera.h" -#include "llavatarnamecache.h" -#include "llfocusmgr.h" -#include "llfirstuse.h" -#include "llfloaterland.h" -#include "llfloaterreg.h" -#include "llfloaterscriptdebug.h" -#include "lltooltip.h" -#include "llhudeffecttrail.h" -#include "llhudicon.h" -#include "llhudmanager.h" -#include "llkeyboard.h" -#include "llmediaentry.h" -#include "llmenugl.h" -#include "llmutelist.h" -#include "llresmgr.h" // getMonetaryString -#include "llselectmgr.h" -#include "lltoolfocus.h" -#include "lltoolgrab.h" -#include "lltoolmgr.h" -#include "lltoolselect.h" -#include "lltrans.h" -#include "llviewercamera.h" -#include "llviewerparcelmedia.h" -#include "llviewercontrol.h" -#include "llviewermenu.h" -#include "llviewerobjectlist.h" -#include "llviewerobject.h" -#include "llviewerparcelmgr.h" -#include "llviewerwindow.h" -#include "llviewerinput.h" -#include "llviewermedia.h" -#include "llvoavatarself.h" -#include "llviewermediafocus.h" -#include "llworld.h" -#include "llui.h" -#include "llweb.h" -#include "pipeline.h" // setHighlightObject -#include "lluiusage.h" - -extern bool gDebugClicks; - -static void handle_click_action_play(); -static void handle_click_action_open_media(LLPointer objectp); -static ECursorType cursor_from_parcel_media(U8 click_action); - -LLToolPie::LLToolPie() -: LLTool(std::string("Pie")), - mMouseButtonDown( false ), - mMouseOutsideSlop( false ), - mMouseSteerX(-1), - mMouseSteerY(-1), - mClickAction(0), - mClickActionBuyEnabled( true ), - mClickActionPayEnabled( true ), - mDoubleClickTimer() -{ -} - -bool LLToolPie::handleAnyMouseClick(S32 x, S32 y, MASK mask, EMouseClickType clicktype, bool down) -{ - bool result = LLMouseHandler::handleAnyMouseClick(x, y, mask, clicktype, down); - - // This override DISABLES the keyboard focus reset that LLTool::handleAnyMouseClick adds. - // LLToolPie will do the right thing in its pick callback. - - return result; -} - -bool LLToolPie::handleMouseDown(S32 x, S32 y, MASK mask) -{ - if (mDoubleClickTimer.getStarted()) - { - mDoubleClickTimer.stop(); - } - - mMouseOutsideSlop = false; - mMouseDownX = x; - mMouseDownY = y; - LLTimer pick_timer; - bool pick_rigged = false; //gSavedSettings.getBOOL("AnimatedObjectsAllowLeftClick"); - LLPickInfo transparent_pick = gViewerWindow->pickImmediate(x, y, true /*includes transparent*/, pick_rigged, false, true, false); - LLPickInfo visible_pick = gViewerWindow->pickImmediate(x, y, false, pick_rigged); - LLViewerObject *transp_object = transparent_pick.getObject(); - LLViewerObject *visible_object = visible_pick.getObject(); - - // Current set of priorities - // 1. Transparent attachment pick - // 2. Transparent actionable pick - // 3. Visible attachment pick (e.x we click on attachment under invisible floor) - // 4. Visible actionable pick - // 5. Transparent pick (e.x. movement on transparent object/floor, our default pick) - // left mouse down always picks transparent (but see handleMouseUp). - // Also see LLToolPie::handleHover() - priorities are a bit different there. - // Todo: we need a more consistent set of rules to work with - if (transp_object == visible_object || !visible_object || - !transp_object) // avoid potential for null dereference below, don't make assumptions about behavior of pickImmediate - { - mPick = transparent_pick; - } - else - { - // Select between two non-null picks - LLViewerObject *transp_parent = transp_object->getRootEdit(); - LLViewerObject *visible_parent = visible_object->getRootEdit(); - if (transp_object->isAttachment()) - { - // 1. Transparent attachment - mPick = transparent_pick; - } - else if (transp_object->getClickAction() != CLICK_ACTION_DISABLED - && (useClickAction(mask, transp_object, transp_parent) || transp_object->flagHandleTouch() || (transp_parent && transp_parent->flagHandleTouch()))) - { - // 2. Transparent actionable pick - mPick = transparent_pick; - } - else if (visible_object->isAttachment()) - { - // 3. Visible attachment pick - mPick = visible_pick; - } - else if (visible_object->getClickAction() != CLICK_ACTION_DISABLED - && (useClickAction(mask, visible_object, visible_parent) || visible_object->flagHandleTouch() || (visible_parent && visible_parent->flagHandleTouch()))) - { - // 4. Visible actionable pick - mPick = visible_pick; - } - else - { - // 5. Default: transparent - mPick = transparent_pick; - } - } - LL_INFOS() << "pick_rigged is " << (S32) pick_rigged << " pick time elapsed " << pick_timer.getElapsedTimeF32() << LL_ENDL; - - mPick.mKeyMask = mask; - - mMouseButtonDown = true; - - // If nothing clickable is picked, needs to return - // false for click-to-walk or click-to-teleport to work. - return handleLeftClickPick(); -} - -// Spawn context menus on right mouse down so you can drag over and select -// an item. -bool LLToolPie::handleRightMouseDown(S32 x, S32 y, MASK mask) -{ - bool pick_reflection_probe = gSavedSettings.getBOOL("SelectReflectionProbes"); - - // don't pick transparent so users can't "pay" transparent objects - mPick = gViewerWindow->pickImmediate(x, y, - /*bool pick_transparent*/ false, - /*bool pick_rigged*/ true, - /*bool pick_particle*/ true, - /*bool pick_unselectable*/ true, - pick_reflection_probe); - mPick.mKeyMask = mask; - - // claim not handled so UI focus stays same - if(gAgentCamera.getCameraMode() != CAMERA_MODE_MOUSELOOK) - { - handleRightClickPick(); - } - return false; -} - -bool LLToolPie::handleRightMouseUp(S32 x, S32 y, MASK mask) -{ - LLToolMgr::getInstance()->clearTransientTool(); - return LLTool::handleRightMouseUp(x, y, mask); -} - -bool LLToolPie::handleScrollWheelAny(S32 x, S32 y, S32 clicks_x, S32 clicks_y) -{ - bool res = false; - // mHoverPick should have updated on its own and we should have a face - // in LLViewerMediaFocus in case of media, so just reuse mHoverPick - if (mHoverPick.mUVCoords.mV[VX] >= 0.f && mHoverPick.mUVCoords.mV[VY] >= 0.f) - { - res = LLViewerMediaFocus::getInstance()->handleScrollWheel(mHoverPick.mUVCoords, clicks_x, clicks_y); - } - else - { - // this won't provide correct coordinates in case of object selection - res = LLViewerMediaFocus::getInstance()->handleScrollWheel(x, y, clicks_x, clicks_y); - } - return res; -} - -bool LLToolPie::handleScrollWheel(S32 x, S32 y, S32 clicks) -{ - return handleScrollWheelAny(x, y, 0, clicks); -} - -bool LLToolPie::handleScrollHWheel(S32 x, S32 y, S32 clicks) -{ - return handleScrollWheelAny(x, y, clicks, 0); -} - -// True if you selected an object. -bool LLToolPie::handleLeftClickPick() -{ - S32 x = mPick.mMousePt.mX; - S32 y = mPick.mMousePt.mY; - MASK mask = mPick.mKeyMask; - if (mPick.mPickType == LLPickInfo::PICK_PARCEL_WALL) - { - LLParcel* parcel = LLViewerParcelMgr::getInstance()->getCollisionParcel(); - if (parcel) - { - LLViewerParcelMgr::getInstance()->selectCollisionParcel(); - if (parcel->getParcelFlag(PF_USE_PASS_LIST) - && !LLViewerParcelMgr::getInstance()->isCollisionBanned()) - { - // if selling passes, just buy one - void* deselect_when_done = (void*)true; - LLPanelLandGeneral::onClickBuyPass(deselect_when_done); - } - else - { - // not selling passes, get info - LLFloaterReg::showInstance("about_land"); - } - } - - gFocusMgr.setKeyboardFocus(NULL); - return LLTool::handleMouseDown(x, y, mask); - } - - // didn't click in any UI object, so must have clicked in the world - LLViewerObject *object = mPick.getObject(); - LLViewerObject *parent = NULL; - - if (mPick.mPickType != LLPickInfo::PICK_LAND) - { - LLViewerParcelMgr::getInstance()->deselectLand(); - } - - if (object) - { - parent = object->getRootEdit(); - } - - if (handleMediaClick(mPick)) - { - return true; - } - - // If it's a left-click, and we have a special action, do it. - if (useClickAction(mask, object, parent)) - { - mClickAction = 0; - if (object && object->getClickAction()) - { - mClickAction = object->getClickAction(); - } - else if (parent && parent->getClickAction()) - { - mClickAction = parent->getClickAction(); - } - - switch(mClickAction) - { - case CLICK_ACTION_TOUCH: - // touch behavior down below... - break; - case CLICK_ACTION_SIT: - { - if (isAgentAvatarValid() && !gAgentAvatarp->isSitting()) // agent not already sitting - { - handle_object_sit_or_stand(); - // put focus in world when sitting on an object - gFocusMgr.setKeyboardFocus(NULL); - return true; - } // else nothing (fall through to touch) - } - case CLICK_ACTION_PAY: - if ( mClickActionPayEnabled ) - { - if ((object && object->flagTakesMoney()) - || (parent && parent->flagTakesMoney())) - { - // pay event goes to object actually clicked on - mClickActionObject = object; - mLeftClickSelection = LLToolSelect::handleObjectSelection(mPick, false, true); - if (LLSelectMgr::getInstance()->selectGetAllValid()) - { - // call this right away, since we have all the info we need to continue the action - selectionPropertiesReceived(); - } - return true; - } - } - break; - case CLICK_ACTION_BUY: - if ( mClickActionBuyEnabled ) - { - mClickActionObject = parent; - mLeftClickSelection = LLToolSelect::handleObjectSelection(mPick, false, true, true); - if (LLSelectMgr::getInstance()->selectGetAllValid()) - { - // call this right away, since we have all the info we need to continue the action - selectionPropertiesReceived(); - } - return true; - } - break; - case CLICK_ACTION_OPEN: - if (parent && parent->allowOpen()) - { - mClickActionObject = parent; - mLeftClickSelection = LLToolSelect::handleObjectSelection(mPick, false, true, true); - if (LLSelectMgr::getInstance()->selectGetAllValid()) - { - // call this right away, since we have all the info we need to continue the action - selectionPropertiesReceived(); - } - } - return true; - case CLICK_ACTION_PLAY: - handle_click_action_play(); - return true; - case CLICK_ACTION_OPEN_MEDIA: - // mClickActionObject = object; - handle_click_action_open_media(object); - return true; - case CLICK_ACTION_ZOOM: - { - const F32 PADDING_FACTOR = 2.f; - LLViewerObject* object = gObjectList.findObject(mPick.mObjectID); - - if (object) - { - gAgentCamera.setFocusOnAvatar(false, ANIMATE); - - LLBBox bbox = object->getBoundingBoxAgent() ; - F32 angle_of_view = llmax(0.1f, LLViewerCamera::getInstance()->getAspect() > 1.f ? LLViewerCamera::getInstance()->getView() * LLViewerCamera::getInstance()->getAspect() : LLViewerCamera::getInstance()->getView()); - F32 distance = bbox.getExtentLocal().magVec() * PADDING_FACTOR / atan(angle_of_view); - - LLVector3 obj_to_cam = LLViewerCamera::getInstance()->getOrigin() - bbox.getCenterAgent(); - obj_to_cam.normVec(); - - LLVector3d object_center_global = gAgent.getPosGlobalFromAgent(bbox.getCenterAgent()); - gAgentCamera.setCameraPosAndFocusGlobal(object_center_global + LLVector3d(obj_to_cam * distance), - object_center_global, - mPick.mObjectID ); - } - } - return true; - case CLICK_ACTION_DISABLED: - return true; - default: - // nothing - break; - } - } - - // put focus back "in world" - if (gFocusMgr.getKeyboardFocus()) - { - gFocusMgr.setKeyboardFocus(NULL); - } - - bool touchable = object - && (object->getClickAction() != CLICK_ACTION_DISABLED) - && (object->flagHandleTouch() || (parent && parent->flagHandleTouch())); - - // Switch to grab tool if physical or triggerable - if (object && - !object->isAvatar() && - ((object->flagUsePhysics() || (parent && !parent->isAvatar() && parent->flagUsePhysics())) || touchable) - ) - { - gGrabTransientTool = this; - mMouseButtonDown = false; - LLToolGrab::getInstance()->setClickedInMouselook(gAgentCamera.cameraMouselook()); - LLToolMgr::getInstance()->getCurrentToolset()->selectTool( LLToolGrab::getInstance() ); - return LLToolGrab::getInstance()->handleObjectHit( mPick ); - } - - LLHUDIcon* last_hit_hud_icon = mPick.mHUDIcon; - if (!object && last_hit_hud_icon && last_hit_hud_icon->getSourceObject()) - { - LLFloaterScriptDebug::show(last_hit_hud_icon->getSourceObject()->getID()); - } - - // If left-click never selects or spawns a menu - // Eat the event. - - // mouse already released - if (!mMouseButtonDown) - { - return true; - } - - while (object && object->isAttachment() && !object->flagHandleTouch()) - { - // don't pick avatar through hud attachment - if (object->isHUDAttachment()) - { - break; - } - object = (LLViewerObject*)object->getParent(); - } - if (object && object == gAgentAvatarp) - { - // we left clicked on avatar, switch to focus mode - mMouseButtonDown = false; - LLToolMgr::getInstance()->setTransientTool(LLToolCamera::getInstance()); - gViewerWindow->hideCursor(); - LLToolCamera::getInstance()->setMouseCapture(true); - LLToolCamera::getInstance()->setClickPickPending(); - LLToolCamera::getInstance()->pickCallback(mPick); - gAgentCamera.setFocusOnAvatar(true, true); - - return true; - } - ////////// - // // Could be first left-click on nothing - // LLFirstUse::useLeftClickNoHit(); - ///////// - - return LLTool::handleMouseDown(x, y, mask); -} - -bool LLToolPie::useClickAction(MASK mask, - LLViewerObject* object, - LLViewerObject* parent) -{ - return mask == MASK_NONE - && object - && !object->isAttachment() - && LLPrimitive::isPrimitive(object->getPCode()) - // useClickAction does not handle Touch (0) or Disabled action - && ((object->getClickAction() && object->getClickAction() != CLICK_ACTION_DISABLED) - || (parent && parent->getClickAction() && parent->getClickAction() != CLICK_ACTION_DISABLED)); - -} - -U8 final_click_action(LLViewerObject* obj) -{ - if (!obj) return CLICK_ACTION_NONE; - if (obj->isAttachment()) return CLICK_ACTION_NONE; - - U8 click_action = CLICK_ACTION_TOUCH; - LLViewerObject* parent = obj->getRootEdit(); - U8 object_action = obj->getClickAction(); - U8 parent_action = parent ? parent->getClickAction() : CLICK_ACTION_TOUCH; - if (parent_action == CLICK_ACTION_DISABLED || object_action) - { - // CLICK_ACTION_DISABLED ("None" in UI) is intended for child action to - // override parent's action when assigned to parent or to child - click_action = object_action; - } - else if (parent_action) - { - click_action = parent_action; - } - return click_action; -} - -ECursorType LLToolPie::cursorFromObject(LLViewerObject* object) -{ - LLViewerObject* parent = NULL; - if (object) - { - parent = object->getRootEdit(); - } - U8 click_action = final_click_action(object); - ECursorType cursor = UI_CURSOR_ARROW; - switch(click_action) - { - case CLICK_ACTION_SIT: - { - if (isAgentAvatarValid() && !gAgentAvatarp->isSitting()) // not already sitting? - { - cursor = UI_CURSOR_TOOLSIT; - } - } - break; - case CLICK_ACTION_BUY: - if ( mClickActionBuyEnabled ) - { - LLSelectNode* node = LLSelectMgr::getInstance()->getHoverNode(); - if (!node || node->mSaleInfo.isForSale()) - { - cursor = UI_CURSOR_TOOLBUY; - } - } - break; - case CLICK_ACTION_OPEN: - // Open always opens the parent. - if (parent && parent->allowOpen()) - { - cursor = UI_CURSOR_TOOLOPEN; - } - break; - case CLICK_ACTION_PAY: - if ( mClickActionPayEnabled ) - { - if ((object && object->flagTakesMoney()) - || (parent && parent->flagTakesMoney())) - { - cursor = UI_CURSOR_TOOLBUY; - } - } - break; - case CLICK_ACTION_ZOOM: - cursor = UI_CURSOR_TOOLZOOMIN; - break; - case CLICK_ACTION_PLAY: - case CLICK_ACTION_OPEN_MEDIA: - cursor = cursor_from_parcel_media(click_action); - break; - case CLICK_ACTION_DISABLED: - break; - default: - break; - } - return cursor; -} - -void LLToolPie::resetSelection() -{ - mLeftClickSelection = NULL; - mClickActionObject = NULL; - mClickAction = 0; -} - -bool LLToolPie::walkToClickedLocation() -{ - if (gAgent.getFlying() // don't auto-navigate while flying until that works - || !gAgentAvatarp - || gAgentAvatarp->isSitting()) - { - return false; - } - - LLUIUsage::instance().logCommand("Agent.WalkToClickedLocation"); - - LLPickInfo saved_pick = mPick; - if (gAgentCamera.getCameraMode() != CAMERA_MODE_MOUSELOOK) - { - mPick = gViewerWindow->pickImmediate(mHoverPick.mMousePt.mX, mHoverPick.mMousePt.mY, - false /* ignore transparent */, - false /* ignore rigged */, - false /* ignore particles */); - } - else - { - // We do not handle hover in mouselook as we do in other modes, so - // use croshair's position to do a pick - mPick = gViewerWindow->pickImmediate(gViewerWindow->getWorldViewRectScaled().getWidth() / 2, - gViewerWindow->getWorldViewRectScaled().getHeight() / 2, - false /* ignore transparent */, - false /* ignore rigged */, - false /* ignore particles */); - } - - if (mPick.mPickType == LLPickInfo::PICK_OBJECT) - { - if (mPick.getObject() && mPick.getObject()->isHUDAttachment()) - { - mPick = saved_pick; - return false; - } - } - - LLViewerObject* avatar_object = mPick.getObject(); - - // get pointer to avatar - while (avatar_object && !avatar_object->isAvatar()) - { - avatar_object = (LLViewerObject*)avatar_object->getParent(); - } - - if (avatar_object && ((LLVOAvatar*)avatar_object)->isSelf()) - { - const F64 SELF_CLICK_WALK_DISTANCE = 3.0; - // pretend we picked some point a bit in front of avatar - mPick.mPosGlobal = gAgent.getPositionGlobal() + LLVector3d(LLViewerCamera::instance().getAtAxis()) * SELF_CLICK_WALK_DISTANCE; - } - - if ((mPick.mPickType == LLPickInfo::PICK_LAND && !mPick.mPosGlobal.isExactlyZero()) || - (mPick.mObjectID.notNull() && !mPick.mPosGlobal.isExactlyZero())) - { - gAgentCamera.setFocusOnAvatar(true, true); - - if (mAutoPilotDestination) { mAutoPilotDestination->markDead(); } - mAutoPilotDestination = (LLHUDEffectBlob *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BLOB, false); - mAutoPilotDestination->setPositionGlobal(mPick.mPosGlobal); - mAutoPilotDestination->setPixelSize(5); - mAutoPilotDestination->setColor(LLColor4U(170, 210, 190)); - mAutoPilotDestination->setDuration(3.f); - - LLVector3d pos = LLToolPie::getInstance()->getPick().mPosGlobal; - gAgent.startAutoPilotGlobal(pos, std::string(), NULL, NULL, NULL, 0.f, 0.03f, false); - LLFirstUse::notMoving(false); - showVisualContextMenuEffect(); - return true; - } - else - { - LL_DEBUGS() << "walk target was " - << (mPick.mPosGlobal.isExactlyZero() ? "zero" : "not zero") - << ", pick type was " << (mPick.mPickType == LLPickInfo::PICK_LAND ? "land" : "not land") - << ", pick object was " << mPick.mObjectID - << LL_ENDL; - mPick = saved_pick; - return false; - } -} - -bool LLToolPie::teleportToClickedLocation() -{ - if (gAgentCamera.getCameraMode() == CAMERA_MODE_MOUSELOOK) - { - // We do not handle hover in mouselook as we do in other modes, so - // use croshair's position to do a pick - bool pick_rigged = false; - mHoverPick = gViewerWindow->pickImmediate(gViewerWindow->getWorldViewRectScaled().getWidth() / 2, - gViewerWindow->getWorldViewRectScaled().getHeight() / 2, - false, - pick_rigged); - } - LLViewerObject* objp = mHoverPick.getObject(); - LLViewerObject* parentp = objp ? objp->getRootEdit() : NULL; - - if (objp && (objp->getAvatar() == gAgentAvatarp || objp == gAgentAvatarp)) // ex: nametag - { - // Don't teleport to self, teleporting to other avatars is fine - return false; - } - - bool is_in_world = mHoverPick.mObjectID.notNull() && objp && !objp->isHUDAttachment(); - bool is_land = mHoverPick.mPickType == LLPickInfo::PICK_LAND; - bool pos_non_zero = !mHoverPick.mPosGlobal.isExactlyZero(); - bool has_touch_handler = (objp && objp->flagHandleTouch()) || (parentp && parentp->flagHandleTouch()); - U8 click_action = final_click_action(objp); // default action: 0 - touch - bool has_click_action = (click_action || has_touch_handler) && click_action != CLICK_ACTION_DISABLED; - - if (pos_non_zero && (is_land || (is_in_world && !has_click_action))) - { - LLVector3d pos = mHoverPick.mPosGlobal; - pos.mdV[VZ] += gAgentAvatarp->getPelvisToFoot(); - gAgent.teleportViaLocationLookAt(pos); - mPick = mHoverPick; - showVisualContextMenuEffect(); - return true; - } - return false; -} - -// When we get object properties after left-clicking on an object -// with left-click = buy, if it's the same object, do the buy. - -// static -void LLToolPie::selectionPropertiesReceived() -{ - // Make sure all data has been received. - // This function will be called repeatedly as the data comes in. - if (!LLSelectMgr::getInstance()->selectGetAllValid()) - { - return; - } - - LLObjectSelection* selection = LLToolPie::getInstance()->getLeftClickSelection(); - if (selection) - { - LLViewerObject* selected_object = selection->getPrimaryObject(); - // since we don't currently have a way to lock a selection, it could have changed - // after we initially clicked on the object - if (selected_object == LLToolPie::getInstance()->getClickActionObject()) - { - U8 click_action = LLToolPie::getInstance()->getClickAction(); - switch (click_action) - { - case CLICK_ACTION_BUY: - if ( LLToolPie::getInstance()->mClickActionBuyEnabled ) - { - handle_buy(); - } - break; - case CLICK_ACTION_PAY: - if ( LLToolPie::getInstance()->mClickActionPayEnabled ) - { - handle_give_money_dialog(); - } - break; - case CLICK_ACTION_OPEN: - LLFloaterReg::showInstance("openobject"); - break; - case CLICK_ACTION_DISABLED: - break; - default: - break; - } - } - } - LLToolPie::getInstance()->resetSelection(); -} - -bool LLToolPie::handleHover(S32 x, S32 y, MASK mask) -{ - bool pick_rigged = false; //gSavedSettings.getBOOL("AnimatedObjectsAllowLeftClick"); - mHoverPick = gViewerWindow->pickImmediate(x, y, false, pick_rigged); - LLViewerObject *parent = NULL; - LLViewerObject *object = mHoverPick.getObject(); - LLSelectMgr::getInstance()->setHoverObject(object, mHoverPick.mObjectFace); - if (object) - { - parent = object->getRootEdit(); - } - - if (!handleMediaHover(mHoverPick) - && !mMouseOutsideSlop - && mMouseButtonDown - // disable camera steering if click on land is not used for moving - && gViewerInput.isMouseBindUsed(CLICK_LEFT, MASK_NONE, MODE_THIRD_PERSON)) - { - S32 delta_x = x - mMouseDownX; - S32 delta_y = y - mMouseDownY; - if (delta_x * delta_x + delta_y * delta_y > DRAG_N_DROP_DISTANCE_THRESHOLD * DRAG_N_DROP_DISTANCE_THRESHOLD) - { - startCameraSteering(); - steerCameraWithMouse(x, y); - gViewerWindow->setCursor(UI_CURSOR_TOOLGRAB); - } - else - { - gViewerWindow->setCursor(UI_CURSOR_ARROW); - } - } - else if (inCameraSteerMode()) - { - steerCameraWithMouse(x, y); - gViewerWindow->setCursor(UI_CURSOR_TOOLGRAB); - } - else - { - // perform a separate pick that detects transparent objects since they respond to 1-click actions - LLPickInfo click_action_pick = gViewerWindow->pickImmediate(x, y, false, pick_rigged); - - LLViewerObject* click_action_object = click_action_pick.getObject(); - - if (click_action_object && useClickAction(mask, click_action_object, click_action_object->getRootEdit())) - { - ECursorType cursor = cursorFromObject(click_action_object); - gViewerWindow->setCursor(cursor); - LL_DEBUGS("UserInput") << "hover handled by LLToolPie (inactive)" << LL_ENDL; - } - - else if ((object && !object->isAvatar() && object->flagUsePhysics()) - || (parent && !parent->isAvatar() && parent->flagUsePhysics())) - { - gViewerWindow->setCursor(UI_CURSOR_TOOLGRAB); - LL_DEBUGS("UserInput") << "hover handled by LLToolPie (inactive)" << LL_ENDL; - } - else if ((!object || object->getClickAction() != CLICK_ACTION_DISABLED) - && ((object && object->flagHandleTouch()) || (parent && parent->flagHandleTouch())) - && (!object || !object->isAvatar())) - { - gViewerWindow->setCursor(UI_CURSOR_HAND); - LL_DEBUGS("UserInput") << "hover handled by LLToolPie (inactive)" << LL_ENDL; - } - else - { - gViewerWindow->setCursor(UI_CURSOR_ARROW); - LL_DEBUGS("UserInput") << "hover handled by LLToolPie (inactive)" << LL_ENDL; - } - } - - if(!object) - { - LLViewerMediaFocus::getInstance()->clearHover(); - } - - return true; -} - -bool LLToolPie::handleMouseUp(S32 x, S32 y, MASK mask) -{ - if (!mDoubleClickTimer.getStarted()) - { - mDoubleClickTimer.start(); - } - else - { - mDoubleClickTimer.reset(); - } - LLViewerObject* obj = mPick.getObject(); - - stopCameraSteering(); - mMouseButtonDown = false; - - gViewerWindow->setCursor(UI_CURSOR_ARROW); - if (hasMouseCapture()) - { - setMouseCapture(false); - } - - LLToolMgr::getInstance()->clearTransientTool(); - gAgentCamera.setLookAt(LOOKAT_TARGET_CONVERSATION, obj); // maybe look at object/person clicked on - - return LLTool::handleMouseUp(x, y, mask); -} - -void LLToolPie::stopClickToWalk() -{ - mPick.mPosGlobal = gAgent.getPositionGlobal(); - handle_go_to(); - if(mAutoPilotDestination) - { - mAutoPilotDestination->markDead(); - } -} - -bool LLToolPie::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - if (gDebugClicks) - { - LL_INFOS() << "LLToolPie handleDoubleClick (becoming mouseDown)" << LL_ENDL; - } - - if (handleMediaDblClick(mPick)) - { - return true; - } - - if (!mDoubleClickTimer.getStarted() || (mDoubleClickTimer.getElapsedTimeF32() > 0.3f)) - { - mDoubleClickTimer.stop(); - return false; - } - mDoubleClickTimer.stop(); - - return false; -} - -static bool needs_tooltip(LLSelectNode* nodep) -{ - if (!nodep || !nodep->mValid) - return false; - return true; -} - - -bool LLToolPie::handleTooltipLand(std::string line, std::string tooltip_msg) -{ - // Do not show hover for land unless prefs are set to allow it. - if (!gSavedSettings.getBOOL("ShowLandHoverTip")) return true; - - LLViewerParcelMgr::getInstance()->setHoverParcel( mHoverPick.mPosGlobal ); - - // Didn't hit an object, but since we have a land point we - // must be hovering over land. - - LLParcel* hover_parcel = LLViewerParcelMgr::getInstance()->getHoverParcel(); - LLUUID owner; - - if ( hover_parcel ) - { - owner = hover_parcel->getOwnerID(); - } - - // Line: "Land" - line.clear(); - line.append(LLTrans::getString("TooltipLand")); - if (hover_parcel) - { - line.append(hover_parcel->getName()); - } - tooltip_msg.append(line); - tooltip_msg.push_back('\n'); - - // Line: "Owner: James Linden" - line.clear(); - line.append(LLTrans::getString("TooltipOwner") + " "); - - if ( hover_parcel ) - { - std::string name; - if (LLUUID::null == owner) - { - line.append(LLTrans::getString("TooltipPublic")); - } - else if (hover_parcel->getIsGroupOwned()) - { - if (gCacheName->getGroupName(owner, name)) - { - line.append(name); - line.append(LLTrans::getString("TooltipIsGroup")); - } - else - { - line.append(LLTrans::getString("RetrievingData")); - } - } - else - { - LLAvatarName av_name; - if (LLAvatarNameCache::get(owner, &av_name)) - { - name = av_name.getUserName(); - line.append(name); - } - else - { - line.append(LLTrans::getString("RetrievingData")); - } - } - } - else - { - line.append(LLTrans::getString("RetrievingData")); - } - tooltip_msg.append(line); - tooltip_msg.push_back('\n'); - - // Line: "no fly, not safe, no build" - - // Don't display properties for your land. This is just - // confusing, because you can do anything on your own land. - if ( hover_parcel && owner != gAgent.getID() ) - { - S32 words = 0; - - line.clear(); - // JC - Keep this in the same order as the checkboxes - // on the land info panel - if ( !hover_parcel->getAllowModify() ) - { - if ( hover_parcel->getAllowGroupModify() ) - { - line.append(LLTrans::getString("TooltipFlagGroupBuild")); - } - else - { - line.append(LLTrans::getString("TooltipFlagNoBuild")); - } - words++; - } - - if ( !hover_parcel->getAllowTerraform() ) - { - if (words) line.append(", "); - line.append(LLTrans::getString("TooltipFlagNoEdit")); - words++; - } - - if ( hover_parcel->getAllowDamage() ) - { - if (words) line.append(", "); - line.append(LLTrans::getString("TooltipFlagNotSafe")); - words++; - } - - // Maybe we should reflect the estate's block fly bit here as well? DK 12/1/04 - if ( !hover_parcel->getAllowFly() ) - { - if (words) line.append(", "); - line.append(LLTrans::getString("TooltipFlagNoFly")); - words++; - } - - if ( !hover_parcel->getAllowOtherScripts() ) - { - if (words) line.append(", "); - if ( hover_parcel->getAllowGroupScripts() ) - { - line.append(LLTrans::getString("TooltipFlagGroupScripts")); - } - else - { - line.append(LLTrans::getString("TooltipFlagNoScripts")); - } - - words++; - } - - if (words) - { - tooltip_msg.append(line); - tooltip_msg.push_back('\n'); - } - } - - if (hover_parcel && hover_parcel->getParcelFlag(PF_FOR_SALE)) - { - LLStringUtil::format_map_t args; - S32 price = hover_parcel->getSalePrice(); - args["[AMOUNT]"] = LLResMgr::getInstance()->getMonetaryString(price); - line = LLTrans::getString("TooltipForSaleL$", args); - tooltip_msg.append(line); - tooltip_msg.push_back('\n'); - } - - // trim last newlines - if (!tooltip_msg.empty()) - { - tooltip_msg.erase(tooltip_msg.size() - 1); - LLToolTipMgr::instance().show(tooltip_msg); - } - - return true; -} - -bool LLToolPie::handleTooltipObject( LLViewerObject* hover_object, std::string line, std::string tooltip_msg) -{ - if ( hover_object->isHUDAttachment() ) - { - // no hover tips for HUD elements, since they can obscure - // what the HUD is displaying - return true; - } - - if ( hover_object->isAttachment() ) - { - // get root of attachment then parent, which is avatar - LLViewerObject* root_edit = hover_object->getRootEdit(); - if (!root_edit) - { - // Strange parenting issue, don't show any text - return true; - } - hover_object = (LLViewerObject*)root_edit->getParent(); - if (!hover_object) - { - // another strange parenting issue, bail out - return true; - } - } - - line.clear(); - if (hover_object->isAvatar()) - { - // only show tooltip if same inspector not already open - LLFloater* existing_inspector = LLFloaterReg::findInstance("inspect_avatar"); - if (!existing_inspector - || !existing_inspector->getVisible() - || existing_inspector->getKey()["avatar_id"].asUUID() != hover_object->getID()) - { - // Try to get display name + username - std::string final_name; - LLAvatarName av_name; - if (LLAvatarNameCache::get(hover_object->getID(), &av_name)) - { - final_name = av_name.getCompleteName(); - } - else - { - final_name = LLTrans::getString("TooltipPerson");; - } - - const F32 INSPECTOR_TOOLTIP_DELAY = 0.35f; - - LLInspector::Params p; - p.fillFrom(LLUICtrlFactory::instance().getDefaultParams()); - p.message(final_name); - p.image.name("Inspector_I"); - p.click_callback(boost::bind(showAvatarInspector, hover_object->getID())); - p.visible_time_near(6.f); - p.visible_time_far(3.f); - p.delay_time(INSPECTOR_TOOLTIP_DELAY); - p.wrap(false); - - LLToolTipMgr::instance().show(p); - } - } - else - { - // - // We have hit a regular object (not an avatar or attachment) - // - - // - // Default prefs will suppress display unless the object is interactive - // - bool show_all_object_tips = - (bool)gSavedSettings.getBOOL("ShowAllObjectHoverTip"); - LLSelectNode *nodep = LLSelectMgr::getInstance()->getHoverNode(); - - // only show tooltip if same inspector not already open - LLFloater* existing_inspector = LLFloaterReg::findInstance("inspect_object"); - if (nodep && - (!existing_inspector - || !existing_inspector->getVisible() - || existing_inspector->getKey()["object_id"].asUUID() != hover_object->getID())) - { - - // Add price to tooltip for items on sale - bool for_sale = for_sale_selection(nodep); - if(for_sale) - { - LLStringUtil::format_map_t args; - S32 price = nodep->mSaleInfo.getSalePrice(); - args["[AMOUNT]"] = LLResMgr::getInstance()->getMonetaryString(price); - tooltip_msg.append(LLTrans::getString("TooltipPrice", args) ); - } - - if (nodep->mName.empty()) - { - tooltip_msg.append(LLTrans::getString("TooltipNoName")); - } - else - { - tooltip_msg.append( nodep->mName ); - } - - bool has_media = false; - bool is_time_based_media = false; - bool is_web_based_media = false; - bool is_media_playing = false; - bool is_media_displaying = false; - - // Does this face have media? - const LLTextureEntry* tep = hover_object->getTE(mHoverPick.mObjectFace); - - if(tep) - { - has_media = tep->hasMedia(); - const LLMediaEntry* mep = has_media ? tep->getMediaData() : NULL; - if (mep) - { - viewer_media_t media_impl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mep->getMediaID()); - LLPluginClassMedia* media_plugin = NULL; - - if (media_impl.notNull() && (media_impl->hasMedia())) - { - is_media_displaying = true; - //LLStringUtil::format_map_t args; - - media_plugin = media_impl->getMediaPlugin(); - if(media_plugin) - { - if(media_plugin->pluginSupportsMediaTime()) - { - is_time_based_media = true; - is_web_based_media = false; - //args["[CurrentURL]"] = media_impl->getMediaURL(); - is_media_playing = media_impl->isMediaPlaying(); - } - else - { - is_time_based_media = false; - is_web_based_media = true; - //args["[CurrentURL]"] = media_plugin->getLocation(); - } - //tooltip_msg.append(LLTrans::getString("CurrentURL", args)); - } - } - } - } - - - // Avoid showing tip over media that's displaying unless it's for sale - // also check the primary node since sometimes it can have an action even though - // the root node doesn't - - bool needs_tip = (!is_media_displaying || - for_sale) && - (has_media || - needs_tooltip(nodep) || - needs_tooltip(LLSelectMgr::getInstance()->getPrimaryHoverNode())); - - if (show_all_object_tips || needs_tip) - { - LLInspector::Params p; - p.fillFrom(LLUICtrlFactory::instance().getDefaultParams()); - p.message(tooltip_msg); - p.image.name("Inspector_I"); - p.click_callback(boost::bind(showObjectInspector, hover_object->getID(), mHoverPick.mObjectFace)); - p.time_based_media(is_time_based_media); - p.web_based_media(is_web_based_media); - p.media_playing(is_media_playing); - p.click_playmedia_callback(boost::bind(playCurrentMedia, mHoverPick)); - p.click_homepage_callback(boost::bind(VisitHomePage, mHoverPick)); - p.visible_time_near(6.f); - p.visible_time_far(3.f); - p.delay_time(gSavedSettings.getF32("ObjectInspectorTooltipDelay")); - p.wrap(false); - - LLToolTipMgr::instance().show(p); - } - } - } - - return true; -} - -bool LLToolPie::handleToolTip(S32 local_x, S32 local_y, MASK mask) -{ - static LLCachedControl show_hover_tips(*LLUI::getInstance()->mSettingGroups["config"], "ShowHoverTips", true); - if (!show_hover_tips) return true; - if (!mHoverPick.isValid()) return true; - - LLViewerObject* hover_object = mHoverPick.getObject(); - - // update hover object and hover parcel - LLSelectMgr::getInstance()->setHoverObject(hover_object, mHoverPick.mObjectFace); - - - std::string tooltip_msg; - std::string line; - - if ( hover_object ) - { - handleTooltipObject(hover_object, line, tooltip_msg ); - } - else if (mHoverPick.mPickType == LLPickInfo::PICK_LAND) - { - handleTooltipLand(line, tooltip_msg); - } - - return true; -} - -static void show_inspector(const char* inspector, const char* param, const LLUUID& source_id) -{ - LLSD params; - params[param] = source_id; - if (LLToolTipMgr::instance().toolTipVisible()) - { - LLRect rect = LLToolTipMgr::instance().getToolTipRect(); - params["pos"]["x"] = rect.mLeft; - params["pos"]["y"] = rect.mTop; - } - - LLFloaterReg::showInstance(inspector, params); -} - - -static void show_inspector(const char* inspector, LLSD& params) -{ - if (LLToolTipMgr::instance().toolTipVisible()) - { - LLRect rect = LLToolTipMgr::instance().getToolTipRect(); - params["pos"]["x"] = rect.mLeft; - params["pos"]["y"] = rect.mTop; - } - - LLFloaterReg::showInstance(inspector, params); -} - - -// static -void LLToolPie::showAvatarInspector(const LLUUID& avatar_id) -{ - show_inspector("inspect_avatar", "avatar_id", avatar_id); -} - -// static -void LLToolPie::showObjectInspector(const LLUUID& object_id) -{ - show_inspector("inspect_object", "object_id", object_id); -} - - -// static -void LLToolPie::showObjectInspector(const LLUUID& object_id, const S32& object_face) -{ - LLSD params; - params["object_id"] = object_id; - params["object_face"] = object_face; - show_inspector("inspect_object", params); -} - -// static -void LLToolPie::playCurrentMedia(const LLPickInfo& info) -{ - //FIXME: how do we handle object in different parcel than us? - LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - if (!parcel) return; - - LLPointer objectp = info.getObject(); - - // Early out cases. Must clear media hover. - // did not hit an object or did not hit a valid face - if ( objectp.isNull() || - info.mObjectFace < 0 || - info.mObjectFace >= objectp->getNumTEs() ) - { - return; - } - - // Does this face have media? - const LLTextureEntry* tep = objectp->getTE(info.mObjectFace); - if (!tep) - return; - - const LLMediaEntry* mep = tep->hasMedia() ? tep->getMediaData() : NULL; - if(!mep) - return; - - //TODO: Can you Use it? - - LLPluginClassMedia* media_plugin = NULL; - - viewer_media_t media_impl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mep->getMediaID()); - - if(media_impl.notNull() && media_impl->hasMedia()) - { - media_plugin = media_impl->getMediaPlugin(); - if (media_plugin && media_plugin->pluginSupportsMediaTime()) - { - if(media_impl->isMediaPlaying()) - { - media_impl->pause(); - } - else - { - media_impl->play(); - } - } - } - - -} - -// static -void LLToolPie::VisitHomePage(const LLPickInfo& info) -{ - //FIXME: how do we handle object in different parcel than us? - LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - if (!parcel) return; - - LLPointer objectp = info.getObject(); - - // Early out cases. Must clear media hover. - // did not hit an object or did not hit a valid face - if ( objectp.isNull() || - info.mObjectFace < 0 || - info.mObjectFace >= objectp->getNumTEs() ) - { - return; - } - - // Does this face have media? - const LLTextureEntry* tep = objectp->getTE(info.mObjectFace); - if (!tep) - return; - - const LLMediaEntry* mep = tep->hasMedia() ? tep->getMediaData() : NULL; - if(!mep) - return; - - //TODO: Can you Use it? - - LLPluginClassMedia* media_plugin = NULL; - - viewer_media_t media_impl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mep->getMediaID()); - - if(media_impl.notNull() && media_impl->hasMedia()) - { - media_plugin = media_impl->getMediaPlugin(); - - if (media_plugin && !(media_plugin->pluginSupportsMediaTime())) - { - media_impl->navigateHome(); - } - } -} - -void LLToolPie::handleSelect() -{ - // tool is reselected when app gets focus, etc. -} - -void LLToolPie::handleDeselect() -{ - if( hasMouseCapture() ) - { - setMouseCapture( false ); // Calls onMouseCaptureLost() indirectly - } - // remove temporary selection for pie menu - LLSelectMgr::getInstance()->setHoverObject(NULL); - - // Menu may be still up during transfer to different tool. - // toolfocus and toolgrab should retain menu, they will clear it if needed - MASK override_mask = gKeyboard ? gKeyboard->currentMask(true) : 0; - if (gMenuHolder && (!gMenuHolder->getVisible() || (override_mask & (MASK_ALT | MASK_CONTROL)) == 0)) - { - // in most cases menu is useless without correct selection, so either keep both or discard both - gMenuHolder->hideMenus(); - LLSelectMgr::getInstance()->validateSelection(); - } -} - -LLTool* LLToolPie::getOverrideTool(MASK mask) -{ - if (gSavedSettings.getBOOL("EnableGrab")) - { - if (mask == DEFAULT_GRAB_MASK) - { - return LLToolGrab::getInstance(); - } - else if (mask == (MASK_CONTROL | MASK_SHIFT)) - { - return LLToolGrab::getInstance(); - } - } - return LLTool::getOverrideTool(mask); -} - -void LLToolPie::stopEditing() -{ - if( hasMouseCapture() ) - { - setMouseCapture( false ); // Calls onMouseCaptureLost() indirectly - } -} - -void LLToolPie::onMouseCaptureLost() -{ - stopCameraSteering(); - mMouseButtonDown = false; - handleMediaMouseUp(); -} - -void LLToolPie::stopCameraSteering() -{ - mMouseOutsideSlop = false; -} - -bool LLToolPie::inCameraSteerMode() -{ - return mMouseButtonDown && mMouseOutsideSlop; -} - -// true if x,y outside small box around start_x,start_y -bool LLToolPie::outsideSlop(S32 x, S32 y, S32 start_x, S32 start_y) -{ - S32 dx = x - start_x; - S32 dy = y - start_y; - - return (dx <= -2 || 2 <= dx || dy <= -2 || 2 <= dy); -} - - -void LLToolPie::render() -{ - return; -} - -static void handle_click_action_play() -{ - LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - if (!parcel) return; - - LLViewerMediaImpl::EMediaStatus status = LLViewerParcelMedia::getInstance()->getStatus(); - switch(status) - { - case LLViewerMediaImpl::MEDIA_PLAYING: - LLViewerParcelMedia::getInstance()->pause(); - break; - - case LLViewerMediaImpl::MEDIA_PAUSED: - LLViewerParcelMedia::getInstance()->start(); - break; - - default: - LLViewerParcelMedia::getInstance()->play(parcel); - break; - } -} - -bool LLToolPie::handleMediaClick(const LLPickInfo& pick) -{ - //FIXME: how do we handle object in different parcel than us? - LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - LLPointer objectp = pick.getObject(); - - - if (!parcel || - objectp.isNull() || - pick.mObjectFace < 0 || - pick.mObjectFace >= objectp->getNumTEs()) - { - LLViewerMediaFocus::getInstance()->clearFocus(); - - return false; - } - - // Does this face have media? - const LLTextureEntry* tep = objectp->getTE(pick.mObjectFace); - if (!tep) - return false; - - LLMediaEntry* mep = (tep->hasMedia()) ? tep->getMediaData() : NULL; - if (!mep) - return false; - - viewer_media_t media_impl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mep->getMediaID()); - - if (gSavedSettings.getBOOL("MediaOnAPrimUI")) - { - if (!LLViewerMediaFocus::getInstance()->isFocusedOnFace(pick.getObject(), pick.mObjectFace) || media_impl.isNull()) - { - // It's okay to give this a null impl - LLViewerMediaFocus::getInstance()->setFocusFace(pick.getObject(), pick.mObjectFace, media_impl, pick.mNormal); - } - else - { - // Make sure keyboard focus is set to the media focus object. - gFocusMgr.setKeyboardFocus(LLViewerMediaFocus::getInstance()); - LLEditMenuHandler::gEditMenuHandler = LLViewerMediaFocus::instance().getFocusedMediaImpl(); - - media_impl->mouseDown(pick.mUVCoords, gKeyboard->currentMask(true)); - mMediaMouseCaptureID = mep->getMediaID(); - setMouseCapture(true); // This object will send a mouse-up to the media when it loses capture. - } - - return true; - } - - LLViewerMediaFocus::getInstance()->clearFocus(); - - return false; -} - -bool LLToolPie::handleMediaDblClick(const LLPickInfo& pick) -{ - //FIXME: how do we handle object in different parcel than us? - LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - LLPointer objectp = pick.getObject(); - - - if (!parcel || - objectp.isNull() || - pick.mObjectFace < 0 || - pick.mObjectFace >= objectp->getNumTEs()) - { - LLViewerMediaFocus::getInstance()->clearFocus(); - - return false; - } - - // Does this face have media? - const LLTextureEntry* tep = objectp->getTE(pick.mObjectFace); - if (!tep) - return false; - - LLMediaEntry* mep = (tep->hasMedia()) ? tep->getMediaData() : NULL; - if (!mep) - return false; - - viewer_media_t media_impl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mep->getMediaID()); - - if (gSavedSettings.getBOOL("MediaOnAPrimUI")) - { - if (!LLViewerMediaFocus::getInstance()->isFocusedOnFace(pick.getObject(), pick.mObjectFace) || media_impl.isNull()) - { - // It's okay to give this a null impl - LLViewerMediaFocus::getInstance()->setFocusFace(pick.getObject(), pick.mObjectFace, media_impl, pick.mNormal); - } - else - { - // Make sure keyboard focus is set to the media focus object. - gFocusMgr.setKeyboardFocus(LLViewerMediaFocus::getInstance()); - LLEditMenuHandler::gEditMenuHandler = LLViewerMediaFocus::instance().getFocusedMediaImpl(); - - media_impl->mouseDoubleClick(pick.mUVCoords, gKeyboard->currentMask(true)); - mMediaMouseCaptureID = mep->getMediaID(); - setMouseCapture(true); // This object will send a mouse-up to the media when it loses capture. - } - - return true; - } - - LLViewerMediaFocus::getInstance()->clearFocus(); - - return false; -} - -bool LLToolPie::handleMediaHover(const LLPickInfo& pick) -{ - //FIXME: how do we handle object in different parcel than us? - LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - if (!parcel) return false; - - LLPointer objectp = pick.getObject(); - - // Early out cases. Must clear media hover. - // did not hit an object or did not hit a valid face - if ( objectp.isNull() || - pick.mObjectFace < 0 || - pick.mObjectFace >= objectp->getNumTEs() ) - { - LLViewerMediaFocus::getInstance()->clearHover(); - return false; - } - - // Does this face have media? - const LLTextureEntry* tep = objectp->getTE(pick.mObjectFace); - if(!tep) - return false; - - const LLMediaEntry* mep = tep->hasMedia() ? tep->getMediaData() : NULL; - if (mep - && gSavedSettings.getBOOL("MediaOnAPrimUI")) - { - viewer_media_t media_impl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mep->getMediaID()); - - if(media_impl.notNull()) - { - // Update media hover object - if (!LLViewerMediaFocus::getInstance()->isHoveringOverFace(objectp, pick.mObjectFace)) - { - LLViewerMediaFocus::getInstance()->setHoverFace(objectp, pick.mObjectFace, media_impl, pick.mNormal); - } - - // If this is the focused media face, send mouse move events. - if (LLViewerMediaFocus::getInstance()->isFocusedOnFace(objectp, pick.mObjectFace)) - { - media_impl->mouseMove(pick.mUVCoords, gKeyboard->currentMask(true)); - gViewerWindow->setCursor(media_impl->getLastSetCursor()); - } - else - { - // This is not the focused face -- set the default cursor. - gViewerWindow->setCursor(UI_CURSOR_ARROW); - } - - return true; - } - } - - // In all other cases, clear media hover. - LLViewerMediaFocus::getInstance()->clearHover(); - - return false; -} - -bool LLToolPie::handleMediaMouseUp() -{ - bool result = false; - if(mMediaMouseCaptureID.notNull()) - { - // Face media needs to know the mouse went up. - viewer_media_t media_impl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mMediaMouseCaptureID); - if(media_impl) - { - // This will send a mouseUp event to the plugin using the last known mouse coordinate (from a mouseDown or mouseMove), which is what we want. - media_impl->onMouseCaptureLost(); - } - - mMediaMouseCaptureID.setNull(); - - result = true; - } - - return result; -} - -static void handle_click_action_open_media(LLPointer objectp) -{ - //FIXME: how do we handle object in different parcel than us? - LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - if (!parcel) return; - - // did we hit an object? - if (objectp.isNull()) return; - - // did we hit a valid face on the object? - S32 face = LLToolPie::getInstance()->getPick().mObjectFace; - if( face < 0 || face >= objectp->getNumTEs() ) return; - - // is media playing on this face? - if (LLViewerMedia::getInstance()->getMediaImplFromTextureID(objectp->getTE(face)->getID()) != NULL) - { - handle_click_action_play(); - return; - } - - std::string media_url = std::string ( parcel->getMediaURL () ); - std::string media_type = std::string ( parcel->getMediaType() ); - LLStringUtil::trim(media_url); - - LLWeb::loadURL(media_url); -} - -static ECursorType cursor_from_parcel_media(U8 click_action) -{ - // HACK: This is directly referencing an impl name. BAD! - // This can be removed when we have a truly generic media browser that only - // builds an impl based on the type of url it is passed. - - //FIXME: how do we handle object in different parcel than us? - ECursorType open_cursor = UI_CURSOR_ARROW; - LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - if (!parcel) return open_cursor; - - std::string media_url = std::string ( parcel->getMediaURL () ); - std::string media_type = std::string ( parcel->getMediaType() ); - LLStringUtil::trim(media_url); - - open_cursor = UI_CURSOR_TOOLMEDIAOPEN; - - LLViewerMediaImpl::EMediaStatus status = LLViewerParcelMedia::getInstance()->getStatus(); - switch(status) - { - case LLViewerMediaImpl::MEDIA_PLAYING: - return click_action == CLICK_ACTION_PLAY ? UI_CURSOR_TOOLPAUSE : open_cursor; - default: - return UI_CURSOR_TOOLPLAY; - } -} - - -// True if we handled the event. -bool LLToolPie::handleRightClickPick() -{ - S32 x = mPick.mMousePt.mX; - S32 y = mPick.mMousePt.mY; - MASK mask = mPick.mKeyMask; - - if (mPick.mPickType != LLPickInfo::PICK_LAND) - { - LLViewerParcelMgr::getInstance()->deselectLand(); - } - - // didn't click in any UI object, so must have clicked in the world - LLViewerObject *object = mPick.getObject(); - - // Can't ignore children here. - LLToolSelect::handleObjectSelection(mPick, false, true); - - // Spawn pie menu - if (mPick.mPickType == LLPickInfo::PICK_LAND) - { - LLParcelSelectionHandle selection = LLViewerParcelMgr::getInstance()->selectParcelAt( mPick.mPosGlobal ); - gMenuHolder->setParcelSelection(selection); - gMenuLand->show(x, y); - - showVisualContextMenuEffect(); - - } - else if (mPick.mObjectID == gAgent.getID() ) - { - if(!gMenuAvatarSelf) - { - //either at very early startup stage or at late quitting stage, - //this event is ignored. - return true ; - } - - gMenuAvatarSelf->show(x, y); - } - else if (object) - { - gMenuHolder->setObjectSelection(LLSelectMgr::getInstance()->getSelection()); - - bool is_other_attachment = (object->isAttachment() && !object->isHUDAttachment() && !object->permYouOwner()); - if (object->isAvatar() || is_other_attachment) - { - // Find the attachment's avatar - while( object && object->isAttachment()) - { - object = (LLViewerObject*)object->getParent(); - llassert(object); - } - - if (!object) - { - return true; // unexpected, but escape - } - - // Object is an avatar, so check for mute by id. - LLVOAvatar* avatar = (LLVOAvatar*)object; - std::string name = avatar->getFullname(); - std::string mute_msg; - if (LLMuteList::getInstance()->isMuted(avatar->getID(), avatar->getFullname())) - { - mute_msg = LLTrans::getString("UnmuteAvatar"); - } - else - { - mute_msg = LLTrans::getString("MuteAvatar"); - } - - if (is_other_attachment) - { - gMenuAttachmentOther->getChild("Avatar Mute")->setValue(mute_msg); - gMenuAttachmentOther->show(x, y); - } - else - { - gMenuAvatarOther->getChild("Avatar Mute")->setValue(mute_msg); - gMenuAvatarOther->show(x, y); - } - } - else if (object->isAttachment()) - { - gMenuAttachmentSelf->show(x, y); - } - else - { - // BUG: What about chatting child objects? - std::string name; - LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(); - if (node) - { - name = node->mName; - } - - gMenuObject->show(x, y); - - showVisualContextMenuEffect(); - } - } - else if (mPick.mParticleOwnerID.notNull()) - { - if (gMenuMuteParticle && mPick.mParticleOwnerID != gAgent.getID()) - { - gMenuMuteParticle->show(x,y); - } - } - - // non UI object - put focus back "in world" - if (gFocusMgr.getKeyboardFocus()) - { - gFocusMgr.setKeyboardFocus(NULL); - } - - LLTool::handleRightMouseDown(x, y, mask); - // We handled the event. - return true; -} - -void LLToolPie::showVisualContextMenuEffect() -{ - // VEFFECT: ShowPie - LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_SPHERE, true); - effectp->setPositionGlobal(mPick.mPosGlobal); - effectp->setColor(LLColor4U(gAgent.getEffectColor())); - effectp->setDuration(0.25f); -} - -typedef enum e_near_far -{ - NEAR_INTERSECTION, - FAR_INTERSECTION -} ENearFar; - -bool intersect_ray_with_sphere( const LLVector3& ray_pt, const LLVector3& ray_dir, const LLVector3& sphere_center, F32 sphere_radius, e_near_far near_far, LLVector3& intersection_pt) -{ - // do ray/sphere intersection by solving quadratic equation - LLVector3 sphere_to_ray_start_vec = ray_pt - sphere_center; - F32 B = 2.f * ray_dir * sphere_to_ray_start_vec; - F32 C = sphere_to_ray_start_vec.lengthSquared() - (sphere_radius * sphere_radius); - - F32 discriminant = B*B - 4.f*C; - if (discriminant >= 0.f) - { // intersection detected, now find closest one - F32 t0 = (-B - sqrtf(discriminant)) / 2.f; - - if (t0 > 0.f && near_far == NEAR_INTERSECTION) - { - intersection_pt = ray_pt + ray_dir * t0; - } - else - { - F32 t1 = (-B + sqrtf(discriminant)) / 2.f; - intersection_pt = ray_pt + ray_dir * t1; - } - return true; - } - else - { // no intersection - return false; - } -} - -void LLToolPie::startCameraSteering() -{ - LLFirstUse::notMoving(false); - mMouseOutsideSlop = true; - - if (gAgentCamera.getFocusOnAvatar()) - { - mSteerPick = mPick; - - // handle special cases of steering picks - LLViewerObject* avatar_object = mSteerPick.getObject(); - - // get pointer to avatar - while (avatar_object && !avatar_object->isAvatar()) - { - avatar_object = (LLViewerObject*)avatar_object->getParent(); - } - - // if clicking on own avatar... - if (avatar_object && ((LLVOAvatar*)avatar_object)->isSelf()) - { - // ...project pick point a few meters in front of avatar - mSteerPick.mPosGlobal = gAgent.getPositionGlobal() + LLVector3d(LLViewerCamera::instance().getAtAxis()) * 3.0; - } - - if (!mSteerPick.isValid()) - { - mSteerPick.mPosGlobal = gAgent.getPosGlobalFromAgent( - LLViewerCamera::instance().getOrigin() + gViewerWindow->mouseDirectionGlobal(mSteerPick.mMousePt.mX, mSteerPick.mMousePt.mY) * 100.f); - } - - setMouseCapture(true); - - mMouseSteerX = mMouseDownX; - mMouseSteerY = mMouseDownY; - const LLVector3 camera_to_rotation_center = gAgent.getFrameAgent().getOrigin() - LLViewerCamera::instance().getOrigin(); - const LLVector3 rotation_center_to_pick = gAgent.getPosAgentFromGlobal(mSteerPick.mPosGlobal) - gAgent.getFrameAgent().getOrigin(); - - mClockwise = camera_to_rotation_center * rotation_center_to_pick < 0.f; - if (mMouseSteerGrabPoint) { mMouseSteerGrabPoint->markDead(); } - mMouseSteerGrabPoint = (LLHUDEffectBlob *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BLOB, false); - mMouseSteerGrabPoint->setPositionGlobal(mSteerPick.mPosGlobal); - mMouseSteerGrabPoint->setColor(LLColor4U(170, 210, 190)); - mMouseSteerGrabPoint->setPixelSize(5); - mMouseSteerGrabPoint->setDuration(2.f); - } -} - -void LLToolPie::steerCameraWithMouse(S32 x, S32 y) -{ - const LLViewerCamera& camera = LLViewerCamera::instance(); - const LLCoordFrame& rotation_frame = gAgent.getFrameAgent(); - const LLVector3 pick_pos = gAgent.getPosAgentFromGlobal(mSteerPick.mPosGlobal); - const LLVector3 pick_rotation_center = rotation_frame.getOrigin() + parallel_component(pick_pos - rotation_frame.getOrigin(), rotation_frame.getUpAxis()); - const F32 MIN_ROTATION_RADIUS_FRACTION = 0.2f; - const F32 min_rotation_radius = MIN_ROTATION_RADIUS_FRACTION * dist_vec(pick_rotation_center, camera.getOrigin());; - const F32 pick_distance_from_rotation_center = llclamp(dist_vec(pick_pos, pick_rotation_center), min_rotation_radius, F32_MAX); - const LLVector3 camera_to_rotation_center = pick_rotation_center - camera.getOrigin(); - const LLVector3 adjusted_camera_pos = LLViewerCamera::instance().getOrigin() + projected_vec(camera_to_rotation_center, rotation_frame.getUpAxis()); - const F32 camera_distance_from_rotation_center = dist_vec(adjusted_camera_pos, pick_rotation_center); - - LLVector3 mouse_ray = orthogonal_component(gViewerWindow->mouseDirectionGlobal(x, y), rotation_frame.getUpAxis()); - mouse_ray.normalize(); - - LLVector3 old_mouse_ray = orthogonal_component(gViewerWindow->mouseDirectionGlobal(mMouseSteerX, mMouseSteerY), rotation_frame.getUpAxis()); - old_mouse_ray.normalize(); - - F32 yaw_angle; - F32 old_yaw_angle; - LLVector3 mouse_on_sphere; - LLVector3 old_mouse_on_sphere; - - if (intersect_ray_with_sphere( - adjusted_camera_pos, - mouse_ray, - pick_rotation_center, - pick_distance_from_rotation_center, - FAR_INTERSECTION, - mouse_on_sphere)) - { - LLVector3 mouse_sphere_offset = mouse_on_sphere - pick_rotation_center; - yaw_angle = atan2f(mouse_sphere_offset * rotation_frame.getLeftAxis(), mouse_sphere_offset * rotation_frame.getAtAxis()); - } - else - { - yaw_angle = F_PI_BY_TWO + asinf(pick_distance_from_rotation_center / camera_distance_from_rotation_center); - if (mouse_ray * rotation_frame.getLeftAxis() < 0.f) - { - yaw_angle *= -1.f; - } - } - - if (intersect_ray_with_sphere( - adjusted_camera_pos, - old_mouse_ray, - pick_rotation_center, - pick_distance_from_rotation_center, - FAR_INTERSECTION, - old_mouse_on_sphere)) - { - LLVector3 mouse_sphere_offset = old_mouse_on_sphere - pick_rotation_center; - old_yaw_angle = atan2f(mouse_sphere_offset * rotation_frame.getLeftAxis(), mouse_sphere_offset * rotation_frame.getAtAxis()); - } - else - { - old_yaw_angle = F_PI_BY_TWO + asinf(pick_distance_from_rotation_center / camera_distance_from_rotation_center); - - if (mouse_ray * rotation_frame.getLeftAxis() < 0.f) - { - old_yaw_angle *= -1.f; - } - } - - const F32 delta_angle = yaw_angle - old_yaw_angle; - - if (mClockwise) - { - gAgent.yaw(delta_angle); - } - else - { - gAgent.yaw(-delta_angle); - } - - mMouseSteerX = x; - mMouseSteerY = y; -} +/** + * @file lltoolpie.cpp + * @brief LLToolPie class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lltoolpie.h" + +#include "indra_constants.h" +#include "llclickaction.h" +#include "llparcel.h" + +#include "llagent.h" +#include "llagentcamera.h" +#include "llavatarnamecache.h" +#include "llfocusmgr.h" +#include "llfirstuse.h" +#include "llfloaterland.h" +#include "llfloaterreg.h" +#include "llfloaterscriptdebug.h" +#include "lltooltip.h" +#include "llhudeffecttrail.h" +#include "llhudicon.h" +#include "llhudmanager.h" +#include "llkeyboard.h" +#include "llmediaentry.h" +#include "llmenugl.h" +#include "llmutelist.h" +#include "llresmgr.h" // getMonetaryString +#include "llselectmgr.h" +#include "lltoolfocus.h" +#include "lltoolgrab.h" +#include "lltoolmgr.h" +#include "lltoolselect.h" +#include "lltrans.h" +#include "llviewercamera.h" +#include "llviewerparcelmedia.h" +#include "llviewercontrol.h" +#include "llviewermenu.h" +#include "llviewerobjectlist.h" +#include "llviewerobject.h" +#include "llviewerparcelmgr.h" +#include "llviewerwindow.h" +#include "llviewerinput.h" +#include "llviewermedia.h" +#include "llvoavatarself.h" +#include "llviewermediafocus.h" +#include "llworld.h" +#include "llui.h" +#include "llweb.h" +#include "pipeline.h" // setHighlightObject +#include "lluiusage.h" + +extern bool gDebugClicks; + +static void handle_click_action_play(); +static void handle_click_action_open_media(LLPointer objectp); +static ECursorType cursor_from_parcel_media(U8 click_action); + +LLToolPie::LLToolPie() +: LLTool(std::string("Pie")), + mMouseButtonDown( false ), + mMouseOutsideSlop( false ), + mMouseSteerX(-1), + mMouseSteerY(-1), + mClickAction(0), + mClickActionBuyEnabled( true ), + mClickActionPayEnabled( true ), + mDoubleClickTimer() +{ +} + +bool LLToolPie::handleAnyMouseClick(S32 x, S32 y, MASK mask, EMouseClickType clicktype, bool down) +{ + bool result = LLMouseHandler::handleAnyMouseClick(x, y, mask, clicktype, down); + + // This override DISABLES the keyboard focus reset that LLTool::handleAnyMouseClick adds. + // LLToolPie will do the right thing in its pick callback. + + return result; +} + +bool LLToolPie::handleMouseDown(S32 x, S32 y, MASK mask) +{ + if (mDoubleClickTimer.getStarted()) + { + mDoubleClickTimer.stop(); + } + + mMouseOutsideSlop = false; + mMouseDownX = x; + mMouseDownY = y; + LLTimer pick_timer; + bool pick_rigged = false; //gSavedSettings.getBOOL("AnimatedObjectsAllowLeftClick"); + LLPickInfo transparent_pick = gViewerWindow->pickImmediate(x, y, true /*includes transparent*/, pick_rigged, false, true, false); + LLPickInfo visible_pick = gViewerWindow->pickImmediate(x, y, false, pick_rigged); + LLViewerObject *transp_object = transparent_pick.getObject(); + LLViewerObject *visible_object = visible_pick.getObject(); + + // Current set of priorities + // 1. Transparent attachment pick + // 2. Transparent actionable pick + // 3. Visible attachment pick (e.x we click on attachment under invisible floor) + // 4. Visible actionable pick + // 5. Transparent pick (e.x. movement on transparent object/floor, our default pick) + // left mouse down always picks transparent (but see handleMouseUp). + // Also see LLToolPie::handleHover() - priorities are a bit different there. + // Todo: we need a more consistent set of rules to work with + if (transp_object == visible_object || !visible_object || + !transp_object) // avoid potential for null dereference below, don't make assumptions about behavior of pickImmediate + { + mPick = transparent_pick; + } + else + { + // Select between two non-null picks + LLViewerObject *transp_parent = transp_object->getRootEdit(); + LLViewerObject *visible_parent = visible_object->getRootEdit(); + if (transp_object->isAttachment()) + { + // 1. Transparent attachment + mPick = transparent_pick; + } + else if (transp_object->getClickAction() != CLICK_ACTION_DISABLED + && (useClickAction(mask, transp_object, transp_parent) || transp_object->flagHandleTouch() || (transp_parent && transp_parent->flagHandleTouch()))) + { + // 2. Transparent actionable pick + mPick = transparent_pick; + } + else if (visible_object->isAttachment()) + { + // 3. Visible attachment pick + mPick = visible_pick; + } + else if (visible_object->getClickAction() != CLICK_ACTION_DISABLED + && (useClickAction(mask, visible_object, visible_parent) || visible_object->flagHandleTouch() || (visible_parent && visible_parent->flagHandleTouch()))) + { + // 4. Visible actionable pick + mPick = visible_pick; + } + else + { + // 5. Default: transparent + mPick = transparent_pick; + } + } + LL_INFOS() << "pick_rigged is " << (S32) pick_rigged << " pick time elapsed " << pick_timer.getElapsedTimeF32() << LL_ENDL; + + mPick.mKeyMask = mask; + + mMouseButtonDown = true; + + // If nothing clickable is picked, needs to return + // false for click-to-walk or click-to-teleport to work. + return handleLeftClickPick(); +} + +// Spawn context menus on right mouse down so you can drag over and select +// an item. +bool LLToolPie::handleRightMouseDown(S32 x, S32 y, MASK mask) +{ + bool pick_reflection_probe = gSavedSettings.getBOOL("SelectReflectionProbes"); + + // don't pick transparent so users can't "pay" transparent objects + mPick = gViewerWindow->pickImmediate(x, y, + /*bool pick_transparent*/ false, + /*bool pick_rigged*/ true, + /*bool pick_particle*/ true, + /*bool pick_unselectable*/ true, + pick_reflection_probe); + mPick.mKeyMask = mask; + + // claim not handled so UI focus stays same + if(gAgentCamera.getCameraMode() != CAMERA_MODE_MOUSELOOK) + { + handleRightClickPick(); + } + return false; +} + +bool LLToolPie::handleRightMouseUp(S32 x, S32 y, MASK mask) +{ + LLToolMgr::getInstance()->clearTransientTool(); + return LLTool::handleRightMouseUp(x, y, mask); +} + +bool LLToolPie::handleScrollWheelAny(S32 x, S32 y, S32 clicks_x, S32 clicks_y) +{ + bool res = false; + // mHoverPick should have updated on its own and we should have a face + // in LLViewerMediaFocus in case of media, so just reuse mHoverPick + if (mHoverPick.mUVCoords.mV[VX] >= 0.f && mHoverPick.mUVCoords.mV[VY] >= 0.f) + { + res = LLViewerMediaFocus::getInstance()->handleScrollWheel(mHoverPick.mUVCoords, clicks_x, clicks_y); + } + else + { + // this won't provide correct coordinates in case of object selection + res = LLViewerMediaFocus::getInstance()->handleScrollWheel(x, y, clicks_x, clicks_y); + } + return res; +} + +bool LLToolPie::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + return handleScrollWheelAny(x, y, 0, clicks); +} + +bool LLToolPie::handleScrollHWheel(S32 x, S32 y, S32 clicks) +{ + return handleScrollWheelAny(x, y, clicks, 0); +} + +// True if you selected an object. +bool LLToolPie::handleLeftClickPick() +{ + S32 x = mPick.mMousePt.mX; + S32 y = mPick.mMousePt.mY; + MASK mask = mPick.mKeyMask; + if (mPick.mPickType == LLPickInfo::PICK_PARCEL_WALL) + { + LLParcel* parcel = LLViewerParcelMgr::getInstance()->getCollisionParcel(); + if (parcel) + { + LLViewerParcelMgr::getInstance()->selectCollisionParcel(); + if (parcel->getParcelFlag(PF_USE_PASS_LIST) + && !LLViewerParcelMgr::getInstance()->isCollisionBanned()) + { + // if selling passes, just buy one + void* deselect_when_done = (void*)true; + LLPanelLandGeneral::onClickBuyPass(deselect_when_done); + } + else + { + // not selling passes, get info + LLFloaterReg::showInstance("about_land"); + } + } + + gFocusMgr.setKeyboardFocus(NULL); + return LLTool::handleMouseDown(x, y, mask); + } + + // didn't click in any UI object, so must have clicked in the world + LLViewerObject *object = mPick.getObject(); + LLViewerObject *parent = NULL; + + if (mPick.mPickType != LLPickInfo::PICK_LAND) + { + LLViewerParcelMgr::getInstance()->deselectLand(); + } + + if (object) + { + parent = object->getRootEdit(); + } + + if (handleMediaClick(mPick)) + { + return true; + } + + // If it's a left-click, and we have a special action, do it. + if (useClickAction(mask, object, parent)) + { + mClickAction = 0; + if (object && object->getClickAction()) + { + mClickAction = object->getClickAction(); + } + else if (parent && parent->getClickAction()) + { + mClickAction = parent->getClickAction(); + } + + switch(mClickAction) + { + case CLICK_ACTION_TOUCH: + // touch behavior down below... + break; + case CLICK_ACTION_SIT: + { + if (isAgentAvatarValid() && !gAgentAvatarp->isSitting()) // agent not already sitting + { + handle_object_sit_or_stand(); + // put focus in world when sitting on an object + gFocusMgr.setKeyboardFocus(NULL); + return true; + } // else nothing (fall through to touch) + } + case CLICK_ACTION_PAY: + if ( mClickActionPayEnabled ) + { + if ((object && object->flagTakesMoney()) + || (parent && parent->flagTakesMoney())) + { + // pay event goes to object actually clicked on + mClickActionObject = object; + mLeftClickSelection = LLToolSelect::handleObjectSelection(mPick, false, true); + if (LLSelectMgr::getInstance()->selectGetAllValid()) + { + // call this right away, since we have all the info we need to continue the action + selectionPropertiesReceived(); + } + return true; + } + } + break; + case CLICK_ACTION_BUY: + if ( mClickActionBuyEnabled ) + { + mClickActionObject = parent; + mLeftClickSelection = LLToolSelect::handleObjectSelection(mPick, false, true, true); + if (LLSelectMgr::getInstance()->selectGetAllValid()) + { + // call this right away, since we have all the info we need to continue the action + selectionPropertiesReceived(); + } + return true; + } + break; + case CLICK_ACTION_OPEN: + if (parent && parent->allowOpen()) + { + mClickActionObject = parent; + mLeftClickSelection = LLToolSelect::handleObjectSelection(mPick, false, true, true); + if (LLSelectMgr::getInstance()->selectGetAllValid()) + { + // call this right away, since we have all the info we need to continue the action + selectionPropertiesReceived(); + } + } + return true; + case CLICK_ACTION_PLAY: + handle_click_action_play(); + return true; + case CLICK_ACTION_OPEN_MEDIA: + // mClickActionObject = object; + handle_click_action_open_media(object); + return true; + case CLICK_ACTION_ZOOM: + { + const F32 PADDING_FACTOR = 2.f; + LLViewerObject* object = gObjectList.findObject(mPick.mObjectID); + + if (object) + { + gAgentCamera.setFocusOnAvatar(false, ANIMATE); + + LLBBox bbox = object->getBoundingBoxAgent() ; + F32 angle_of_view = llmax(0.1f, LLViewerCamera::getInstance()->getAspect() > 1.f ? LLViewerCamera::getInstance()->getView() * LLViewerCamera::getInstance()->getAspect() : LLViewerCamera::getInstance()->getView()); + F32 distance = bbox.getExtentLocal().magVec() * PADDING_FACTOR / atan(angle_of_view); + + LLVector3 obj_to_cam = LLViewerCamera::getInstance()->getOrigin() - bbox.getCenterAgent(); + obj_to_cam.normVec(); + + LLVector3d object_center_global = gAgent.getPosGlobalFromAgent(bbox.getCenterAgent()); + gAgentCamera.setCameraPosAndFocusGlobal(object_center_global + LLVector3d(obj_to_cam * distance), + object_center_global, + mPick.mObjectID ); + } + } + return true; + case CLICK_ACTION_DISABLED: + return true; + default: + // nothing + break; + } + } + + // put focus back "in world" + if (gFocusMgr.getKeyboardFocus()) + { + gFocusMgr.setKeyboardFocus(NULL); + } + + bool touchable = object + && (object->getClickAction() != CLICK_ACTION_DISABLED) + && (object->flagHandleTouch() || (parent && parent->flagHandleTouch())); + + // Switch to grab tool if physical or triggerable + if (object && + !object->isAvatar() && + ((object->flagUsePhysics() || (parent && !parent->isAvatar() && parent->flagUsePhysics())) || touchable) + ) + { + gGrabTransientTool = this; + mMouseButtonDown = false; + LLToolGrab::getInstance()->setClickedInMouselook(gAgentCamera.cameraMouselook()); + LLToolMgr::getInstance()->getCurrentToolset()->selectTool( LLToolGrab::getInstance() ); + return LLToolGrab::getInstance()->handleObjectHit( mPick ); + } + + LLHUDIcon* last_hit_hud_icon = mPick.mHUDIcon; + if (!object && last_hit_hud_icon && last_hit_hud_icon->getSourceObject()) + { + LLFloaterScriptDebug::show(last_hit_hud_icon->getSourceObject()->getID()); + } + + // If left-click never selects or spawns a menu + // Eat the event. + + // mouse already released + if (!mMouseButtonDown) + { + return true; + } + + while (object && object->isAttachment() && !object->flagHandleTouch()) + { + // don't pick avatar through hud attachment + if (object->isHUDAttachment()) + { + break; + } + object = (LLViewerObject*)object->getParent(); + } + if (object && object == gAgentAvatarp) + { + // we left clicked on avatar, switch to focus mode + mMouseButtonDown = false; + LLToolMgr::getInstance()->setTransientTool(LLToolCamera::getInstance()); + gViewerWindow->hideCursor(); + LLToolCamera::getInstance()->setMouseCapture(true); + LLToolCamera::getInstance()->setClickPickPending(); + LLToolCamera::getInstance()->pickCallback(mPick); + gAgentCamera.setFocusOnAvatar(true, true); + + return true; + } + ////////// + // // Could be first left-click on nothing + // LLFirstUse::useLeftClickNoHit(); + ///////// + + return LLTool::handleMouseDown(x, y, mask); +} + +bool LLToolPie::useClickAction(MASK mask, + LLViewerObject* object, + LLViewerObject* parent) +{ + return mask == MASK_NONE + && object + && !object->isAttachment() + && LLPrimitive::isPrimitive(object->getPCode()) + // useClickAction does not handle Touch (0) or Disabled action + && ((object->getClickAction() && object->getClickAction() != CLICK_ACTION_DISABLED) + || (parent && parent->getClickAction() && parent->getClickAction() != CLICK_ACTION_DISABLED)); + +} + +U8 final_click_action(LLViewerObject* obj) +{ + if (!obj) return CLICK_ACTION_NONE; + if (obj->isAttachment()) return CLICK_ACTION_NONE; + + U8 click_action = CLICK_ACTION_TOUCH; + LLViewerObject* parent = obj->getRootEdit(); + U8 object_action = obj->getClickAction(); + U8 parent_action = parent ? parent->getClickAction() : CLICK_ACTION_TOUCH; + if (parent_action == CLICK_ACTION_DISABLED || object_action) + { + // CLICK_ACTION_DISABLED ("None" in UI) is intended for child action to + // override parent's action when assigned to parent or to child + click_action = object_action; + } + else if (parent_action) + { + click_action = parent_action; + } + return click_action; +} + +ECursorType LLToolPie::cursorFromObject(LLViewerObject* object) +{ + LLViewerObject* parent = NULL; + if (object) + { + parent = object->getRootEdit(); + } + U8 click_action = final_click_action(object); + ECursorType cursor = UI_CURSOR_ARROW; + switch(click_action) + { + case CLICK_ACTION_SIT: + { + if (isAgentAvatarValid() && !gAgentAvatarp->isSitting()) // not already sitting? + { + cursor = UI_CURSOR_TOOLSIT; + } + } + break; + case CLICK_ACTION_BUY: + if ( mClickActionBuyEnabled ) + { + LLSelectNode* node = LLSelectMgr::getInstance()->getHoverNode(); + if (!node || node->mSaleInfo.isForSale()) + { + cursor = UI_CURSOR_TOOLBUY; + } + } + break; + case CLICK_ACTION_OPEN: + // Open always opens the parent. + if (parent && parent->allowOpen()) + { + cursor = UI_CURSOR_TOOLOPEN; + } + break; + case CLICK_ACTION_PAY: + if ( mClickActionPayEnabled ) + { + if ((object && object->flagTakesMoney()) + || (parent && parent->flagTakesMoney())) + { + cursor = UI_CURSOR_TOOLBUY; + } + } + break; + case CLICK_ACTION_ZOOM: + cursor = UI_CURSOR_TOOLZOOMIN; + break; + case CLICK_ACTION_PLAY: + case CLICK_ACTION_OPEN_MEDIA: + cursor = cursor_from_parcel_media(click_action); + break; + case CLICK_ACTION_DISABLED: + break; + default: + break; + } + return cursor; +} + +void LLToolPie::resetSelection() +{ + mLeftClickSelection = NULL; + mClickActionObject = NULL; + mClickAction = 0; +} + +bool LLToolPie::walkToClickedLocation() +{ + if (gAgent.getFlying() // don't auto-navigate while flying until that works + || !gAgentAvatarp + || gAgentAvatarp->isSitting()) + { + return false; + } + + LLUIUsage::instance().logCommand("Agent.WalkToClickedLocation"); + + LLPickInfo saved_pick = mPick; + if (gAgentCamera.getCameraMode() != CAMERA_MODE_MOUSELOOK) + { + mPick = gViewerWindow->pickImmediate(mHoverPick.mMousePt.mX, mHoverPick.mMousePt.mY, + false /* ignore transparent */, + false /* ignore rigged */, + false /* ignore particles */); + } + else + { + // We do not handle hover in mouselook as we do in other modes, so + // use croshair's position to do a pick + mPick = gViewerWindow->pickImmediate(gViewerWindow->getWorldViewRectScaled().getWidth() / 2, + gViewerWindow->getWorldViewRectScaled().getHeight() / 2, + false /* ignore transparent */, + false /* ignore rigged */, + false /* ignore particles */); + } + + if (mPick.mPickType == LLPickInfo::PICK_OBJECT) + { + if (mPick.getObject() && mPick.getObject()->isHUDAttachment()) + { + mPick = saved_pick; + return false; + } + } + + LLViewerObject* avatar_object = mPick.getObject(); + + // get pointer to avatar + while (avatar_object && !avatar_object->isAvatar()) + { + avatar_object = (LLViewerObject*)avatar_object->getParent(); + } + + if (avatar_object && ((LLVOAvatar*)avatar_object)->isSelf()) + { + const F64 SELF_CLICK_WALK_DISTANCE = 3.0; + // pretend we picked some point a bit in front of avatar + mPick.mPosGlobal = gAgent.getPositionGlobal() + LLVector3d(LLViewerCamera::instance().getAtAxis()) * SELF_CLICK_WALK_DISTANCE; + } + + if ((mPick.mPickType == LLPickInfo::PICK_LAND && !mPick.mPosGlobal.isExactlyZero()) || + (mPick.mObjectID.notNull() && !mPick.mPosGlobal.isExactlyZero())) + { + gAgentCamera.setFocusOnAvatar(true, true); + + if (mAutoPilotDestination) { mAutoPilotDestination->markDead(); } + mAutoPilotDestination = (LLHUDEffectBlob *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BLOB, false); + mAutoPilotDestination->setPositionGlobal(mPick.mPosGlobal); + mAutoPilotDestination->setPixelSize(5); + mAutoPilotDestination->setColor(LLColor4U(170, 210, 190)); + mAutoPilotDestination->setDuration(3.f); + + LLVector3d pos = LLToolPie::getInstance()->getPick().mPosGlobal; + gAgent.startAutoPilotGlobal(pos, std::string(), NULL, NULL, NULL, 0.f, 0.03f, false); + LLFirstUse::notMoving(false); + showVisualContextMenuEffect(); + return true; + } + else + { + LL_DEBUGS() << "walk target was " + << (mPick.mPosGlobal.isExactlyZero() ? "zero" : "not zero") + << ", pick type was " << (mPick.mPickType == LLPickInfo::PICK_LAND ? "land" : "not land") + << ", pick object was " << mPick.mObjectID + << LL_ENDL; + mPick = saved_pick; + return false; + } +} + +bool LLToolPie::teleportToClickedLocation() +{ + if (gAgentCamera.getCameraMode() == CAMERA_MODE_MOUSELOOK) + { + // We do not handle hover in mouselook as we do in other modes, so + // use croshair's position to do a pick + bool pick_rigged = false; + mHoverPick = gViewerWindow->pickImmediate(gViewerWindow->getWorldViewRectScaled().getWidth() / 2, + gViewerWindow->getWorldViewRectScaled().getHeight() / 2, + false, + pick_rigged); + } + LLViewerObject* objp = mHoverPick.getObject(); + LLViewerObject* parentp = objp ? objp->getRootEdit() : NULL; + + if (objp && (objp->getAvatar() == gAgentAvatarp || objp == gAgentAvatarp)) // ex: nametag + { + // Don't teleport to self, teleporting to other avatars is fine + return false; + } + + bool is_in_world = mHoverPick.mObjectID.notNull() && objp && !objp->isHUDAttachment(); + bool is_land = mHoverPick.mPickType == LLPickInfo::PICK_LAND; + bool pos_non_zero = !mHoverPick.mPosGlobal.isExactlyZero(); + bool has_touch_handler = (objp && objp->flagHandleTouch()) || (parentp && parentp->flagHandleTouch()); + U8 click_action = final_click_action(objp); // default action: 0 - touch + bool has_click_action = (click_action || has_touch_handler) && click_action != CLICK_ACTION_DISABLED; + + if (pos_non_zero && (is_land || (is_in_world && !has_click_action))) + { + LLVector3d pos = mHoverPick.mPosGlobal; + pos.mdV[VZ] += gAgentAvatarp->getPelvisToFoot(); + gAgent.teleportViaLocationLookAt(pos); + mPick = mHoverPick; + showVisualContextMenuEffect(); + return true; + } + return false; +} + +// When we get object properties after left-clicking on an object +// with left-click = buy, if it's the same object, do the buy. + +// static +void LLToolPie::selectionPropertiesReceived() +{ + // Make sure all data has been received. + // This function will be called repeatedly as the data comes in. + if (!LLSelectMgr::getInstance()->selectGetAllValid()) + { + return; + } + + LLObjectSelection* selection = LLToolPie::getInstance()->getLeftClickSelection(); + if (selection) + { + LLViewerObject* selected_object = selection->getPrimaryObject(); + // since we don't currently have a way to lock a selection, it could have changed + // after we initially clicked on the object + if (selected_object == LLToolPie::getInstance()->getClickActionObject()) + { + U8 click_action = LLToolPie::getInstance()->getClickAction(); + switch (click_action) + { + case CLICK_ACTION_BUY: + if ( LLToolPie::getInstance()->mClickActionBuyEnabled ) + { + handle_buy(); + } + break; + case CLICK_ACTION_PAY: + if ( LLToolPie::getInstance()->mClickActionPayEnabled ) + { + handle_give_money_dialog(); + } + break; + case CLICK_ACTION_OPEN: + LLFloaterReg::showInstance("openobject"); + break; + case CLICK_ACTION_DISABLED: + break; + default: + break; + } + } + } + LLToolPie::getInstance()->resetSelection(); +} + +bool LLToolPie::handleHover(S32 x, S32 y, MASK mask) +{ + bool pick_rigged = false; //gSavedSettings.getBOOL("AnimatedObjectsAllowLeftClick"); + mHoverPick = gViewerWindow->pickImmediate(x, y, false, pick_rigged); + LLViewerObject *parent = NULL; + LLViewerObject *object = mHoverPick.getObject(); + LLSelectMgr::getInstance()->setHoverObject(object, mHoverPick.mObjectFace); + if (object) + { + parent = object->getRootEdit(); + } + + if (!handleMediaHover(mHoverPick) + && !mMouseOutsideSlop + && mMouseButtonDown + // disable camera steering if click on land is not used for moving + && gViewerInput.isMouseBindUsed(CLICK_LEFT, MASK_NONE, MODE_THIRD_PERSON)) + { + S32 delta_x = x - mMouseDownX; + S32 delta_y = y - mMouseDownY; + if (delta_x * delta_x + delta_y * delta_y > DRAG_N_DROP_DISTANCE_THRESHOLD * DRAG_N_DROP_DISTANCE_THRESHOLD) + { + startCameraSteering(); + steerCameraWithMouse(x, y); + gViewerWindow->setCursor(UI_CURSOR_TOOLGRAB); + } + else + { + gViewerWindow->setCursor(UI_CURSOR_ARROW); + } + } + else if (inCameraSteerMode()) + { + steerCameraWithMouse(x, y); + gViewerWindow->setCursor(UI_CURSOR_TOOLGRAB); + } + else + { + // perform a separate pick that detects transparent objects since they respond to 1-click actions + LLPickInfo click_action_pick = gViewerWindow->pickImmediate(x, y, false, pick_rigged); + + LLViewerObject* click_action_object = click_action_pick.getObject(); + + if (click_action_object && useClickAction(mask, click_action_object, click_action_object->getRootEdit())) + { + ECursorType cursor = cursorFromObject(click_action_object); + gViewerWindow->setCursor(cursor); + LL_DEBUGS("UserInput") << "hover handled by LLToolPie (inactive)" << LL_ENDL; + } + + else if ((object && !object->isAvatar() && object->flagUsePhysics()) + || (parent && !parent->isAvatar() && parent->flagUsePhysics())) + { + gViewerWindow->setCursor(UI_CURSOR_TOOLGRAB); + LL_DEBUGS("UserInput") << "hover handled by LLToolPie (inactive)" << LL_ENDL; + } + else if ((!object || object->getClickAction() != CLICK_ACTION_DISABLED) + && ((object && object->flagHandleTouch()) || (parent && parent->flagHandleTouch())) + && (!object || !object->isAvatar())) + { + gViewerWindow->setCursor(UI_CURSOR_HAND); + LL_DEBUGS("UserInput") << "hover handled by LLToolPie (inactive)" << LL_ENDL; + } + else + { + gViewerWindow->setCursor(UI_CURSOR_ARROW); + LL_DEBUGS("UserInput") << "hover handled by LLToolPie (inactive)" << LL_ENDL; + } + } + + if(!object) + { + LLViewerMediaFocus::getInstance()->clearHover(); + } + + return true; +} + +bool LLToolPie::handleMouseUp(S32 x, S32 y, MASK mask) +{ + if (!mDoubleClickTimer.getStarted()) + { + mDoubleClickTimer.start(); + } + else + { + mDoubleClickTimer.reset(); + } + LLViewerObject* obj = mPick.getObject(); + + stopCameraSteering(); + mMouseButtonDown = false; + + gViewerWindow->setCursor(UI_CURSOR_ARROW); + if (hasMouseCapture()) + { + setMouseCapture(false); + } + + LLToolMgr::getInstance()->clearTransientTool(); + gAgentCamera.setLookAt(LOOKAT_TARGET_CONVERSATION, obj); // maybe look at object/person clicked on + + return LLTool::handleMouseUp(x, y, mask); +} + +void LLToolPie::stopClickToWalk() +{ + mPick.mPosGlobal = gAgent.getPositionGlobal(); + handle_go_to(); + if(mAutoPilotDestination) + { + mAutoPilotDestination->markDead(); + } +} + +bool LLToolPie::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + if (gDebugClicks) + { + LL_INFOS() << "LLToolPie handleDoubleClick (becoming mouseDown)" << LL_ENDL; + } + + if (handleMediaDblClick(mPick)) + { + return true; + } + + if (!mDoubleClickTimer.getStarted() || (mDoubleClickTimer.getElapsedTimeF32() > 0.3f)) + { + mDoubleClickTimer.stop(); + return false; + } + mDoubleClickTimer.stop(); + + return false; +} + +static bool needs_tooltip(LLSelectNode* nodep) +{ + if (!nodep || !nodep->mValid) + return false; + return true; +} + + +bool LLToolPie::handleTooltipLand(std::string line, std::string tooltip_msg) +{ + // Do not show hover for land unless prefs are set to allow it. + if (!gSavedSettings.getBOOL("ShowLandHoverTip")) return true; + + LLViewerParcelMgr::getInstance()->setHoverParcel( mHoverPick.mPosGlobal ); + + // Didn't hit an object, but since we have a land point we + // must be hovering over land. + + LLParcel* hover_parcel = LLViewerParcelMgr::getInstance()->getHoverParcel(); + LLUUID owner; + + if ( hover_parcel ) + { + owner = hover_parcel->getOwnerID(); + } + + // Line: "Land" + line.clear(); + line.append(LLTrans::getString("TooltipLand")); + if (hover_parcel) + { + line.append(hover_parcel->getName()); + } + tooltip_msg.append(line); + tooltip_msg.push_back('\n'); + + // Line: "Owner: James Linden" + line.clear(); + line.append(LLTrans::getString("TooltipOwner") + " "); + + if ( hover_parcel ) + { + std::string name; + if (LLUUID::null == owner) + { + line.append(LLTrans::getString("TooltipPublic")); + } + else if (hover_parcel->getIsGroupOwned()) + { + if (gCacheName->getGroupName(owner, name)) + { + line.append(name); + line.append(LLTrans::getString("TooltipIsGroup")); + } + else + { + line.append(LLTrans::getString("RetrievingData")); + } + } + else + { + LLAvatarName av_name; + if (LLAvatarNameCache::get(owner, &av_name)) + { + name = av_name.getUserName(); + line.append(name); + } + else + { + line.append(LLTrans::getString("RetrievingData")); + } + } + } + else + { + line.append(LLTrans::getString("RetrievingData")); + } + tooltip_msg.append(line); + tooltip_msg.push_back('\n'); + + // Line: "no fly, not safe, no build" + + // Don't display properties for your land. This is just + // confusing, because you can do anything on your own land. + if ( hover_parcel && owner != gAgent.getID() ) + { + S32 words = 0; + + line.clear(); + // JC - Keep this in the same order as the checkboxes + // on the land info panel + if ( !hover_parcel->getAllowModify() ) + { + if ( hover_parcel->getAllowGroupModify() ) + { + line.append(LLTrans::getString("TooltipFlagGroupBuild")); + } + else + { + line.append(LLTrans::getString("TooltipFlagNoBuild")); + } + words++; + } + + if ( !hover_parcel->getAllowTerraform() ) + { + if (words) line.append(", "); + line.append(LLTrans::getString("TooltipFlagNoEdit")); + words++; + } + + if ( hover_parcel->getAllowDamage() ) + { + if (words) line.append(", "); + line.append(LLTrans::getString("TooltipFlagNotSafe")); + words++; + } + + // Maybe we should reflect the estate's block fly bit here as well? DK 12/1/04 + if ( !hover_parcel->getAllowFly() ) + { + if (words) line.append(", "); + line.append(LLTrans::getString("TooltipFlagNoFly")); + words++; + } + + if ( !hover_parcel->getAllowOtherScripts() ) + { + if (words) line.append(", "); + if ( hover_parcel->getAllowGroupScripts() ) + { + line.append(LLTrans::getString("TooltipFlagGroupScripts")); + } + else + { + line.append(LLTrans::getString("TooltipFlagNoScripts")); + } + + words++; + } + + if (words) + { + tooltip_msg.append(line); + tooltip_msg.push_back('\n'); + } + } + + if (hover_parcel && hover_parcel->getParcelFlag(PF_FOR_SALE)) + { + LLStringUtil::format_map_t args; + S32 price = hover_parcel->getSalePrice(); + args["[AMOUNT]"] = LLResMgr::getInstance()->getMonetaryString(price); + line = LLTrans::getString("TooltipForSaleL$", args); + tooltip_msg.append(line); + tooltip_msg.push_back('\n'); + } + + // trim last newlines + if (!tooltip_msg.empty()) + { + tooltip_msg.erase(tooltip_msg.size() - 1); + LLToolTipMgr::instance().show(tooltip_msg); + } + + return true; +} + +bool LLToolPie::handleTooltipObject( LLViewerObject* hover_object, std::string line, std::string tooltip_msg) +{ + if ( hover_object->isHUDAttachment() ) + { + // no hover tips for HUD elements, since they can obscure + // what the HUD is displaying + return true; + } + + if ( hover_object->isAttachment() ) + { + // get root of attachment then parent, which is avatar + LLViewerObject* root_edit = hover_object->getRootEdit(); + if (!root_edit) + { + // Strange parenting issue, don't show any text + return true; + } + hover_object = (LLViewerObject*)root_edit->getParent(); + if (!hover_object) + { + // another strange parenting issue, bail out + return true; + } + } + + line.clear(); + if (hover_object->isAvatar()) + { + // only show tooltip if same inspector not already open + LLFloater* existing_inspector = LLFloaterReg::findInstance("inspect_avatar"); + if (!existing_inspector + || !existing_inspector->getVisible() + || existing_inspector->getKey()["avatar_id"].asUUID() != hover_object->getID()) + { + // Try to get display name + username + std::string final_name; + LLAvatarName av_name; + if (LLAvatarNameCache::get(hover_object->getID(), &av_name)) + { + final_name = av_name.getCompleteName(); + } + else + { + final_name = LLTrans::getString("TooltipPerson");; + } + + const F32 INSPECTOR_TOOLTIP_DELAY = 0.35f; + + LLInspector::Params p; + p.fillFrom(LLUICtrlFactory::instance().getDefaultParams()); + p.message(final_name); + p.image.name("Inspector_I"); + p.click_callback(boost::bind(showAvatarInspector, hover_object->getID())); + p.visible_time_near(6.f); + p.visible_time_far(3.f); + p.delay_time(INSPECTOR_TOOLTIP_DELAY); + p.wrap(false); + + LLToolTipMgr::instance().show(p); + } + } + else + { + // + // We have hit a regular object (not an avatar or attachment) + // + + // + // Default prefs will suppress display unless the object is interactive + // + bool show_all_object_tips = + (bool)gSavedSettings.getBOOL("ShowAllObjectHoverTip"); + LLSelectNode *nodep = LLSelectMgr::getInstance()->getHoverNode(); + + // only show tooltip if same inspector not already open + LLFloater* existing_inspector = LLFloaterReg::findInstance("inspect_object"); + if (nodep && + (!existing_inspector + || !existing_inspector->getVisible() + || existing_inspector->getKey()["object_id"].asUUID() != hover_object->getID())) + { + + // Add price to tooltip for items on sale + bool for_sale = for_sale_selection(nodep); + if(for_sale) + { + LLStringUtil::format_map_t args; + S32 price = nodep->mSaleInfo.getSalePrice(); + args["[AMOUNT]"] = LLResMgr::getInstance()->getMonetaryString(price); + tooltip_msg.append(LLTrans::getString("TooltipPrice", args) ); + } + + if (nodep->mName.empty()) + { + tooltip_msg.append(LLTrans::getString("TooltipNoName")); + } + else + { + tooltip_msg.append( nodep->mName ); + } + + bool has_media = false; + bool is_time_based_media = false; + bool is_web_based_media = false; + bool is_media_playing = false; + bool is_media_displaying = false; + + // Does this face have media? + const LLTextureEntry* tep = hover_object->getTE(mHoverPick.mObjectFace); + + if(tep) + { + has_media = tep->hasMedia(); + const LLMediaEntry* mep = has_media ? tep->getMediaData() : NULL; + if (mep) + { + viewer_media_t media_impl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mep->getMediaID()); + LLPluginClassMedia* media_plugin = NULL; + + if (media_impl.notNull() && (media_impl->hasMedia())) + { + is_media_displaying = true; + //LLStringUtil::format_map_t args; + + media_plugin = media_impl->getMediaPlugin(); + if(media_plugin) + { + if(media_plugin->pluginSupportsMediaTime()) + { + is_time_based_media = true; + is_web_based_media = false; + //args["[CurrentURL]"] = media_impl->getMediaURL(); + is_media_playing = media_impl->isMediaPlaying(); + } + else + { + is_time_based_media = false; + is_web_based_media = true; + //args["[CurrentURL]"] = media_plugin->getLocation(); + } + //tooltip_msg.append(LLTrans::getString("CurrentURL", args)); + } + } + } + } + + + // Avoid showing tip over media that's displaying unless it's for sale + // also check the primary node since sometimes it can have an action even though + // the root node doesn't + + bool needs_tip = (!is_media_displaying || + for_sale) && + (has_media || + needs_tooltip(nodep) || + needs_tooltip(LLSelectMgr::getInstance()->getPrimaryHoverNode())); + + if (show_all_object_tips || needs_tip) + { + LLInspector::Params p; + p.fillFrom(LLUICtrlFactory::instance().getDefaultParams()); + p.message(tooltip_msg); + p.image.name("Inspector_I"); + p.click_callback(boost::bind(showObjectInspector, hover_object->getID(), mHoverPick.mObjectFace)); + p.time_based_media(is_time_based_media); + p.web_based_media(is_web_based_media); + p.media_playing(is_media_playing); + p.click_playmedia_callback(boost::bind(playCurrentMedia, mHoverPick)); + p.click_homepage_callback(boost::bind(VisitHomePage, mHoverPick)); + p.visible_time_near(6.f); + p.visible_time_far(3.f); + p.delay_time(gSavedSettings.getF32("ObjectInspectorTooltipDelay")); + p.wrap(false); + + LLToolTipMgr::instance().show(p); + } + } + } + + return true; +} + +bool LLToolPie::handleToolTip(S32 local_x, S32 local_y, MASK mask) +{ + static LLCachedControl show_hover_tips(*LLUI::getInstance()->mSettingGroups["config"], "ShowHoverTips", true); + if (!show_hover_tips) return true; + if (!mHoverPick.isValid()) return true; + + LLViewerObject* hover_object = mHoverPick.getObject(); + + // update hover object and hover parcel + LLSelectMgr::getInstance()->setHoverObject(hover_object, mHoverPick.mObjectFace); + + + std::string tooltip_msg; + std::string line; + + if ( hover_object ) + { + handleTooltipObject(hover_object, line, tooltip_msg ); + } + else if (mHoverPick.mPickType == LLPickInfo::PICK_LAND) + { + handleTooltipLand(line, tooltip_msg); + } + + return true; +} + +static void show_inspector(const char* inspector, const char* param, const LLUUID& source_id) +{ + LLSD params; + params[param] = source_id; + if (LLToolTipMgr::instance().toolTipVisible()) + { + LLRect rect = LLToolTipMgr::instance().getToolTipRect(); + params["pos"]["x"] = rect.mLeft; + params["pos"]["y"] = rect.mTop; + } + + LLFloaterReg::showInstance(inspector, params); +} + + +static void show_inspector(const char* inspector, LLSD& params) +{ + if (LLToolTipMgr::instance().toolTipVisible()) + { + LLRect rect = LLToolTipMgr::instance().getToolTipRect(); + params["pos"]["x"] = rect.mLeft; + params["pos"]["y"] = rect.mTop; + } + + LLFloaterReg::showInstance(inspector, params); +} + + +// static +void LLToolPie::showAvatarInspector(const LLUUID& avatar_id) +{ + show_inspector("inspect_avatar", "avatar_id", avatar_id); +} + +// static +void LLToolPie::showObjectInspector(const LLUUID& object_id) +{ + show_inspector("inspect_object", "object_id", object_id); +} + + +// static +void LLToolPie::showObjectInspector(const LLUUID& object_id, const S32& object_face) +{ + LLSD params; + params["object_id"] = object_id; + params["object_face"] = object_face; + show_inspector("inspect_object", params); +} + +// static +void LLToolPie::playCurrentMedia(const LLPickInfo& info) +{ + //FIXME: how do we handle object in different parcel than us? + LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if (!parcel) return; + + LLPointer objectp = info.getObject(); + + // Early out cases. Must clear media hover. + // did not hit an object or did not hit a valid face + if ( objectp.isNull() || + info.mObjectFace < 0 || + info.mObjectFace >= objectp->getNumTEs() ) + { + return; + } + + // Does this face have media? + const LLTextureEntry* tep = objectp->getTE(info.mObjectFace); + if (!tep) + return; + + const LLMediaEntry* mep = tep->hasMedia() ? tep->getMediaData() : NULL; + if(!mep) + return; + + //TODO: Can you Use it? + + LLPluginClassMedia* media_plugin = NULL; + + viewer_media_t media_impl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mep->getMediaID()); + + if(media_impl.notNull() && media_impl->hasMedia()) + { + media_plugin = media_impl->getMediaPlugin(); + if (media_plugin && media_plugin->pluginSupportsMediaTime()) + { + if(media_impl->isMediaPlaying()) + { + media_impl->pause(); + } + else + { + media_impl->play(); + } + } + } + + +} + +// static +void LLToolPie::VisitHomePage(const LLPickInfo& info) +{ + //FIXME: how do we handle object in different parcel than us? + LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if (!parcel) return; + + LLPointer objectp = info.getObject(); + + // Early out cases. Must clear media hover. + // did not hit an object or did not hit a valid face + if ( objectp.isNull() || + info.mObjectFace < 0 || + info.mObjectFace >= objectp->getNumTEs() ) + { + return; + } + + // Does this face have media? + const LLTextureEntry* tep = objectp->getTE(info.mObjectFace); + if (!tep) + return; + + const LLMediaEntry* mep = tep->hasMedia() ? tep->getMediaData() : NULL; + if(!mep) + return; + + //TODO: Can you Use it? + + LLPluginClassMedia* media_plugin = NULL; + + viewer_media_t media_impl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mep->getMediaID()); + + if(media_impl.notNull() && media_impl->hasMedia()) + { + media_plugin = media_impl->getMediaPlugin(); + + if (media_plugin && !(media_plugin->pluginSupportsMediaTime())) + { + media_impl->navigateHome(); + } + } +} + +void LLToolPie::handleSelect() +{ + // tool is reselected when app gets focus, etc. +} + +void LLToolPie::handleDeselect() +{ + if( hasMouseCapture() ) + { + setMouseCapture( false ); // Calls onMouseCaptureLost() indirectly + } + // remove temporary selection for pie menu + LLSelectMgr::getInstance()->setHoverObject(NULL); + + // Menu may be still up during transfer to different tool. + // toolfocus and toolgrab should retain menu, they will clear it if needed + MASK override_mask = gKeyboard ? gKeyboard->currentMask(true) : 0; + if (gMenuHolder && (!gMenuHolder->getVisible() || (override_mask & (MASK_ALT | MASK_CONTROL)) == 0)) + { + // in most cases menu is useless without correct selection, so either keep both or discard both + gMenuHolder->hideMenus(); + LLSelectMgr::getInstance()->validateSelection(); + } +} + +LLTool* LLToolPie::getOverrideTool(MASK mask) +{ + if (gSavedSettings.getBOOL("EnableGrab")) + { + if (mask == DEFAULT_GRAB_MASK) + { + return LLToolGrab::getInstance(); + } + else if (mask == (MASK_CONTROL | MASK_SHIFT)) + { + return LLToolGrab::getInstance(); + } + } + return LLTool::getOverrideTool(mask); +} + +void LLToolPie::stopEditing() +{ + if( hasMouseCapture() ) + { + setMouseCapture( false ); // Calls onMouseCaptureLost() indirectly + } +} + +void LLToolPie::onMouseCaptureLost() +{ + stopCameraSteering(); + mMouseButtonDown = false; + handleMediaMouseUp(); +} + +void LLToolPie::stopCameraSteering() +{ + mMouseOutsideSlop = false; +} + +bool LLToolPie::inCameraSteerMode() +{ + return mMouseButtonDown && mMouseOutsideSlop; +} + +// true if x,y outside small box around start_x,start_y +bool LLToolPie::outsideSlop(S32 x, S32 y, S32 start_x, S32 start_y) +{ + S32 dx = x - start_x; + S32 dy = y - start_y; + + return (dx <= -2 || 2 <= dx || dy <= -2 || 2 <= dy); +} + + +void LLToolPie::render() +{ + return; +} + +static void handle_click_action_play() +{ + LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if (!parcel) return; + + LLViewerMediaImpl::EMediaStatus status = LLViewerParcelMedia::getInstance()->getStatus(); + switch(status) + { + case LLViewerMediaImpl::MEDIA_PLAYING: + LLViewerParcelMedia::getInstance()->pause(); + break; + + case LLViewerMediaImpl::MEDIA_PAUSED: + LLViewerParcelMedia::getInstance()->start(); + break; + + default: + LLViewerParcelMedia::getInstance()->play(parcel); + break; + } +} + +bool LLToolPie::handleMediaClick(const LLPickInfo& pick) +{ + //FIXME: how do we handle object in different parcel than us? + LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + LLPointer objectp = pick.getObject(); + + + if (!parcel || + objectp.isNull() || + pick.mObjectFace < 0 || + pick.mObjectFace >= objectp->getNumTEs()) + { + LLViewerMediaFocus::getInstance()->clearFocus(); + + return false; + } + + // Does this face have media? + const LLTextureEntry* tep = objectp->getTE(pick.mObjectFace); + if (!tep) + return false; + + LLMediaEntry* mep = (tep->hasMedia()) ? tep->getMediaData() : NULL; + if (!mep) + return false; + + viewer_media_t media_impl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mep->getMediaID()); + + if (gSavedSettings.getBOOL("MediaOnAPrimUI")) + { + if (!LLViewerMediaFocus::getInstance()->isFocusedOnFace(pick.getObject(), pick.mObjectFace) || media_impl.isNull()) + { + // It's okay to give this a null impl + LLViewerMediaFocus::getInstance()->setFocusFace(pick.getObject(), pick.mObjectFace, media_impl, pick.mNormal); + } + else + { + // Make sure keyboard focus is set to the media focus object. + gFocusMgr.setKeyboardFocus(LLViewerMediaFocus::getInstance()); + LLEditMenuHandler::gEditMenuHandler = LLViewerMediaFocus::instance().getFocusedMediaImpl(); + + media_impl->mouseDown(pick.mUVCoords, gKeyboard->currentMask(true)); + mMediaMouseCaptureID = mep->getMediaID(); + setMouseCapture(true); // This object will send a mouse-up to the media when it loses capture. + } + + return true; + } + + LLViewerMediaFocus::getInstance()->clearFocus(); + + return false; +} + +bool LLToolPie::handleMediaDblClick(const LLPickInfo& pick) +{ + //FIXME: how do we handle object in different parcel than us? + LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + LLPointer objectp = pick.getObject(); + + + if (!parcel || + objectp.isNull() || + pick.mObjectFace < 0 || + pick.mObjectFace >= objectp->getNumTEs()) + { + LLViewerMediaFocus::getInstance()->clearFocus(); + + return false; + } + + // Does this face have media? + const LLTextureEntry* tep = objectp->getTE(pick.mObjectFace); + if (!tep) + return false; + + LLMediaEntry* mep = (tep->hasMedia()) ? tep->getMediaData() : NULL; + if (!mep) + return false; + + viewer_media_t media_impl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mep->getMediaID()); + + if (gSavedSettings.getBOOL("MediaOnAPrimUI")) + { + if (!LLViewerMediaFocus::getInstance()->isFocusedOnFace(pick.getObject(), pick.mObjectFace) || media_impl.isNull()) + { + // It's okay to give this a null impl + LLViewerMediaFocus::getInstance()->setFocusFace(pick.getObject(), pick.mObjectFace, media_impl, pick.mNormal); + } + else + { + // Make sure keyboard focus is set to the media focus object. + gFocusMgr.setKeyboardFocus(LLViewerMediaFocus::getInstance()); + LLEditMenuHandler::gEditMenuHandler = LLViewerMediaFocus::instance().getFocusedMediaImpl(); + + media_impl->mouseDoubleClick(pick.mUVCoords, gKeyboard->currentMask(true)); + mMediaMouseCaptureID = mep->getMediaID(); + setMouseCapture(true); // This object will send a mouse-up to the media when it loses capture. + } + + return true; + } + + LLViewerMediaFocus::getInstance()->clearFocus(); + + return false; +} + +bool LLToolPie::handleMediaHover(const LLPickInfo& pick) +{ + //FIXME: how do we handle object in different parcel than us? + LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if (!parcel) return false; + + LLPointer objectp = pick.getObject(); + + // Early out cases. Must clear media hover. + // did not hit an object or did not hit a valid face + if ( objectp.isNull() || + pick.mObjectFace < 0 || + pick.mObjectFace >= objectp->getNumTEs() ) + { + LLViewerMediaFocus::getInstance()->clearHover(); + return false; + } + + // Does this face have media? + const LLTextureEntry* tep = objectp->getTE(pick.mObjectFace); + if(!tep) + return false; + + const LLMediaEntry* mep = tep->hasMedia() ? tep->getMediaData() : NULL; + if (mep + && gSavedSettings.getBOOL("MediaOnAPrimUI")) + { + viewer_media_t media_impl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mep->getMediaID()); + + if(media_impl.notNull()) + { + // Update media hover object + if (!LLViewerMediaFocus::getInstance()->isHoveringOverFace(objectp, pick.mObjectFace)) + { + LLViewerMediaFocus::getInstance()->setHoverFace(objectp, pick.mObjectFace, media_impl, pick.mNormal); + } + + // If this is the focused media face, send mouse move events. + if (LLViewerMediaFocus::getInstance()->isFocusedOnFace(objectp, pick.mObjectFace)) + { + media_impl->mouseMove(pick.mUVCoords, gKeyboard->currentMask(true)); + gViewerWindow->setCursor(media_impl->getLastSetCursor()); + } + else + { + // This is not the focused face -- set the default cursor. + gViewerWindow->setCursor(UI_CURSOR_ARROW); + } + + return true; + } + } + + // In all other cases, clear media hover. + LLViewerMediaFocus::getInstance()->clearHover(); + + return false; +} + +bool LLToolPie::handleMediaMouseUp() +{ + bool result = false; + if(mMediaMouseCaptureID.notNull()) + { + // Face media needs to know the mouse went up. + viewer_media_t media_impl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mMediaMouseCaptureID); + if(media_impl) + { + // This will send a mouseUp event to the plugin using the last known mouse coordinate (from a mouseDown or mouseMove), which is what we want. + media_impl->onMouseCaptureLost(); + } + + mMediaMouseCaptureID.setNull(); + + result = true; + } + + return result; +} + +static void handle_click_action_open_media(LLPointer objectp) +{ + //FIXME: how do we handle object in different parcel than us? + LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if (!parcel) return; + + // did we hit an object? + if (objectp.isNull()) return; + + // did we hit a valid face on the object? + S32 face = LLToolPie::getInstance()->getPick().mObjectFace; + if( face < 0 || face >= objectp->getNumTEs() ) return; + + // is media playing on this face? + if (LLViewerMedia::getInstance()->getMediaImplFromTextureID(objectp->getTE(face)->getID()) != NULL) + { + handle_click_action_play(); + return; + } + + std::string media_url = std::string ( parcel->getMediaURL () ); + std::string media_type = std::string ( parcel->getMediaType() ); + LLStringUtil::trim(media_url); + + LLWeb::loadURL(media_url); +} + +static ECursorType cursor_from_parcel_media(U8 click_action) +{ + // HACK: This is directly referencing an impl name. BAD! + // This can be removed when we have a truly generic media browser that only + // builds an impl based on the type of url it is passed. + + //FIXME: how do we handle object in different parcel than us? + ECursorType open_cursor = UI_CURSOR_ARROW; + LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if (!parcel) return open_cursor; + + std::string media_url = std::string ( parcel->getMediaURL () ); + std::string media_type = std::string ( parcel->getMediaType() ); + LLStringUtil::trim(media_url); + + open_cursor = UI_CURSOR_TOOLMEDIAOPEN; + + LLViewerMediaImpl::EMediaStatus status = LLViewerParcelMedia::getInstance()->getStatus(); + switch(status) + { + case LLViewerMediaImpl::MEDIA_PLAYING: + return click_action == CLICK_ACTION_PLAY ? UI_CURSOR_TOOLPAUSE : open_cursor; + default: + return UI_CURSOR_TOOLPLAY; + } +} + + +// True if we handled the event. +bool LLToolPie::handleRightClickPick() +{ + S32 x = mPick.mMousePt.mX; + S32 y = mPick.mMousePt.mY; + MASK mask = mPick.mKeyMask; + + if (mPick.mPickType != LLPickInfo::PICK_LAND) + { + LLViewerParcelMgr::getInstance()->deselectLand(); + } + + // didn't click in any UI object, so must have clicked in the world + LLViewerObject *object = mPick.getObject(); + + // Can't ignore children here. + LLToolSelect::handleObjectSelection(mPick, false, true); + + // Spawn pie menu + if (mPick.mPickType == LLPickInfo::PICK_LAND) + { + LLParcelSelectionHandle selection = LLViewerParcelMgr::getInstance()->selectParcelAt( mPick.mPosGlobal ); + gMenuHolder->setParcelSelection(selection); + gMenuLand->show(x, y); + + showVisualContextMenuEffect(); + + } + else if (mPick.mObjectID == gAgent.getID() ) + { + if(!gMenuAvatarSelf) + { + //either at very early startup stage or at late quitting stage, + //this event is ignored. + return true ; + } + + gMenuAvatarSelf->show(x, y); + } + else if (object) + { + gMenuHolder->setObjectSelection(LLSelectMgr::getInstance()->getSelection()); + + bool is_other_attachment = (object->isAttachment() && !object->isHUDAttachment() && !object->permYouOwner()); + if (object->isAvatar() || is_other_attachment) + { + // Find the attachment's avatar + while( object && object->isAttachment()) + { + object = (LLViewerObject*)object->getParent(); + llassert(object); + } + + if (!object) + { + return true; // unexpected, but escape + } + + // Object is an avatar, so check for mute by id. + LLVOAvatar* avatar = (LLVOAvatar*)object; + std::string name = avatar->getFullname(); + std::string mute_msg; + if (LLMuteList::getInstance()->isMuted(avatar->getID(), avatar->getFullname())) + { + mute_msg = LLTrans::getString("UnmuteAvatar"); + } + else + { + mute_msg = LLTrans::getString("MuteAvatar"); + } + + if (is_other_attachment) + { + gMenuAttachmentOther->getChild("Avatar Mute")->setValue(mute_msg); + gMenuAttachmentOther->show(x, y); + } + else + { + gMenuAvatarOther->getChild("Avatar Mute")->setValue(mute_msg); + gMenuAvatarOther->show(x, y); + } + } + else if (object->isAttachment()) + { + gMenuAttachmentSelf->show(x, y); + } + else + { + // BUG: What about chatting child objects? + std::string name; + LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(); + if (node) + { + name = node->mName; + } + + gMenuObject->show(x, y); + + showVisualContextMenuEffect(); + } + } + else if (mPick.mParticleOwnerID.notNull()) + { + if (gMenuMuteParticle && mPick.mParticleOwnerID != gAgent.getID()) + { + gMenuMuteParticle->show(x,y); + } + } + + // non UI object - put focus back "in world" + if (gFocusMgr.getKeyboardFocus()) + { + gFocusMgr.setKeyboardFocus(NULL); + } + + LLTool::handleRightMouseDown(x, y, mask); + // We handled the event. + return true; +} + +void LLToolPie::showVisualContextMenuEffect() +{ + // VEFFECT: ShowPie + LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_SPHERE, true); + effectp->setPositionGlobal(mPick.mPosGlobal); + effectp->setColor(LLColor4U(gAgent.getEffectColor())); + effectp->setDuration(0.25f); +} + +typedef enum e_near_far +{ + NEAR_INTERSECTION, + FAR_INTERSECTION +} ENearFar; + +bool intersect_ray_with_sphere( const LLVector3& ray_pt, const LLVector3& ray_dir, const LLVector3& sphere_center, F32 sphere_radius, e_near_far near_far, LLVector3& intersection_pt) +{ + // do ray/sphere intersection by solving quadratic equation + LLVector3 sphere_to_ray_start_vec = ray_pt - sphere_center; + F32 B = 2.f * ray_dir * sphere_to_ray_start_vec; + F32 C = sphere_to_ray_start_vec.lengthSquared() - (sphere_radius * sphere_radius); + + F32 discriminant = B*B - 4.f*C; + if (discriminant >= 0.f) + { // intersection detected, now find closest one + F32 t0 = (-B - sqrtf(discriminant)) / 2.f; + + if (t0 > 0.f && near_far == NEAR_INTERSECTION) + { + intersection_pt = ray_pt + ray_dir * t0; + } + else + { + F32 t1 = (-B + sqrtf(discriminant)) / 2.f; + intersection_pt = ray_pt + ray_dir * t1; + } + return true; + } + else + { // no intersection + return false; + } +} + +void LLToolPie::startCameraSteering() +{ + LLFirstUse::notMoving(false); + mMouseOutsideSlop = true; + + if (gAgentCamera.getFocusOnAvatar()) + { + mSteerPick = mPick; + + // handle special cases of steering picks + LLViewerObject* avatar_object = mSteerPick.getObject(); + + // get pointer to avatar + while (avatar_object && !avatar_object->isAvatar()) + { + avatar_object = (LLViewerObject*)avatar_object->getParent(); + } + + // if clicking on own avatar... + if (avatar_object && ((LLVOAvatar*)avatar_object)->isSelf()) + { + // ...project pick point a few meters in front of avatar + mSteerPick.mPosGlobal = gAgent.getPositionGlobal() + LLVector3d(LLViewerCamera::instance().getAtAxis()) * 3.0; + } + + if (!mSteerPick.isValid()) + { + mSteerPick.mPosGlobal = gAgent.getPosGlobalFromAgent( + LLViewerCamera::instance().getOrigin() + gViewerWindow->mouseDirectionGlobal(mSteerPick.mMousePt.mX, mSteerPick.mMousePt.mY) * 100.f); + } + + setMouseCapture(true); + + mMouseSteerX = mMouseDownX; + mMouseSteerY = mMouseDownY; + const LLVector3 camera_to_rotation_center = gAgent.getFrameAgent().getOrigin() - LLViewerCamera::instance().getOrigin(); + const LLVector3 rotation_center_to_pick = gAgent.getPosAgentFromGlobal(mSteerPick.mPosGlobal) - gAgent.getFrameAgent().getOrigin(); + + mClockwise = camera_to_rotation_center * rotation_center_to_pick < 0.f; + if (mMouseSteerGrabPoint) { mMouseSteerGrabPoint->markDead(); } + mMouseSteerGrabPoint = (LLHUDEffectBlob *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BLOB, false); + mMouseSteerGrabPoint->setPositionGlobal(mSteerPick.mPosGlobal); + mMouseSteerGrabPoint->setColor(LLColor4U(170, 210, 190)); + mMouseSteerGrabPoint->setPixelSize(5); + mMouseSteerGrabPoint->setDuration(2.f); + } +} + +void LLToolPie::steerCameraWithMouse(S32 x, S32 y) +{ + const LLViewerCamera& camera = LLViewerCamera::instance(); + const LLCoordFrame& rotation_frame = gAgent.getFrameAgent(); + const LLVector3 pick_pos = gAgent.getPosAgentFromGlobal(mSteerPick.mPosGlobal); + const LLVector3 pick_rotation_center = rotation_frame.getOrigin() + parallel_component(pick_pos - rotation_frame.getOrigin(), rotation_frame.getUpAxis()); + const F32 MIN_ROTATION_RADIUS_FRACTION = 0.2f; + const F32 min_rotation_radius = MIN_ROTATION_RADIUS_FRACTION * dist_vec(pick_rotation_center, camera.getOrigin());; + const F32 pick_distance_from_rotation_center = llclamp(dist_vec(pick_pos, pick_rotation_center), min_rotation_radius, F32_MAX); + const LLVector3 camera_to_rotation_center = pick_rotation_center - camera.getOrigin(); + const LLVector3 adjusted_camera_pos = LLViewerCamera::instance().getOrigin() + projected_vec(camera_to_rotation_center, rotation_frame.getUpAxis()); + const F32 camera_distance_from_rotation_center = dist_vec(adjusted_camera_pos, pick_rotation_center); + + LLVector3 mouse_ray = orthogonal_component(gViewerWindow->mouseDirectionGlobal(x, y), rotation_frame.getUpAxis()); + mouse_ray.normalize(); + + LLVector3 old_mouse_ray = orthogonal_component(gViewerWindow->mouseDirectionGlobal(mMouseSteerX, mMouseSteerY), rotation_frame.getUpAxis()); + old_mouse_ray.normalize(); + + F32 yaw_angle; + F32 old_yaw_angle; + LLVector3 mouse_on_sphere; + LLVector3 old_mouse_on_sphere; + + if (intersect_ray_with_sphere( + adjusted_camera_pos, + mouse_ray, + pick_rotation_center, + pick_distance_from_rotation_center, + FAR_INTERSECTION, + mouse_on_sphere)) + { + LLVector3 mouse_sphere_offset = mouse_on_sphere - pick_rotation_center; + yaw_angle = atan2f(mouse_sphere_offset * rotation_frame.getLeftAxis(), mouse_sphere_offset * rotation_frame.getAtAxis()); + } + else + { + yaw_angle = F_PI_BY_TWO + asinf(pick_distance_from_rotation_center / camera_distance_from_rotation_center); + if (mouse_ray * rotation_frame.getLeftAxis() < 0.f) + { + yaw_angle *= -1.f; + } + } + + if (intersect_ray_with_sphere( + adjusted_camera_pos, + old_mouse_ray, + pick_rotation_center, + pick_distance_from_rotation_center, + FAR_INTERSECTION, + old_mouse_on_sphere)) + { + LLVector3 mouse_sphere_offset = old_mouse_on_sphere - pick_rotation_center; + old_yaw_angle = atan2f(mouse_sphere_offset * rotation_frame.getLeftAxis(), mouse_sphere_offset * rotation_frame.getAtAxis()); + } + else + { + old_yaw_angle = F_PI_BY_TWO + asinf(pick_distance_from_rotation_center / camera_distance_from_rotation_center); + + if (mouse_ray * rotation_frame.getLeftAxis() < 0.f) + { + old_yaw_angle *= -1.f; + } + } + + const F32 delta_angle = yaw_angle - old_yaw_angle; + + if (mClockwise) + { + gAgent.yaw(delta_angle); + } + else + { + gAgent.yaw(-delta_angle); + } + + mMouseSteerX = x; + mMouseSteerY = y; +} diff --git a/indra/newview/lltoolpie.h b/indra/newview/lltoolpie.h index 7151f64799..b3884a6bfc 100644 --- a/indra/newview/lltoolpie.h +++ b/indra/newview/lltoolpie.h @@ -1,126 +1,126 @@ -/** - * @file lltoolpie.h - * @brief LLToolPie class header file - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_TOOLPIE_H -#define LL_TOOLPIE_H - -#include "lltool.h" -#include "lluuid.h" -#include "llviewerwindow.h" // for LLPickInfo -#include "llhudeffectblob.h" // for LLPointer, apparently - -class LLViewerObject; -class LLObjectSelection; - -class LLToolPie : public LLTool, public LLSingleton -{ - LLSINGLETON(LLToolPie); - LOG_CLASS(LLToolPie); -public: - - // Virtual functions inherited from LLMouseHandler - virtual bool handleAnyMouseClick(S32 x, S32 y, MASK mask, EMouseClickType clicktype, bool down) override; - virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; - virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask) override; - virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; - virtual bool handleRightMouseUp(S32 x, S32 y, MASK mask) override; - virtual bool handleHover(S32 x, S32 y, MASK mask) override; - virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; - bool handleScrollWheelAny(S32 x, S32 y, S32 clicks_x, S32 clicks_y); - virtual bool handleScrollWheel(S32 x, S32 y, S32 clicks) override; - virtual bool handleScrollHWheel(S32 x, S32 y, S32 clicks) override; - virtual bool handleToolTip(S32 x, S32 y, MASK mask) override; - - virtual void render() override; - - virtual void stopEditing() override; - - virtual void onMouseCaptureLost() override; - virtual void handleSelect() override; - virtual void handleDeselect() override; - virtual LLTool* getOverrideTool(MASK mask) override; - - LLPickInfo& getPick() { return mPick; } - U8 getClickAction() { return mClickAction; } - LLViewerObject* getClickActionObject() { return mClickActionObject; } - LLObjectSelection* getLeftClickSelection() { return (LLObjectSelection*)mLeftClickSelection; } - void resetSelection(); - bool walkToClickedLocation(); - bool teleportToClickedLocation(); - void stopClickToWalk(); - - static void selectionPropertiesReceived(); - - static void showAvatarInspector(const LLUUID& avatar_id); - static void showObjectInspector(const LLUUID& object_id); - static void showObjectInspector(const LLUUID& object_id, const S32& object_face); - static void playCurrentMedia(const LLPickInfo& info); - static void VisitHomePage(const LLPickInfo& info); - -private: - bool outsideSlop (S32 x, S32 y, S32 start_x, S32 start_y); - bool handleLeftClickPick(); - bool handleRightClickPick(); - bool useClickAction (MASK mask, LLViewerObject* object,LLViewerObject* parent); - - void showVisualContextMenuEffect(); - ECursorType cursorFromObject(LLViewerObject* object); - - bool handleMediaClick(const LLPickInfo& info); - bool handleMediaDblClick(const LLPickInfo& info); - bool handleMediaHover(const LLPickInfo& info); - bool handleMediaMouseUp(); - bool handleTooltipLand(std::string line, std::string tooltip_msg); - bool handleTooltipObject( LLViewerObject* hover_object, std::string line, std::string tooltip_msg); - - void steerCameraWithMouse(S32 x, S32 y); - void startCameraSteering(); - void stopCameraSteering(); - bool inCameraSteerMode(); - -private: - bool mMouseButtonDown; - bool mMouseOutsideSlop; // for this drag, has mouse moved outside slop region - S32 mMouseDownX; - S32 mMouseDownY; - S32 mMouseSteerX; - S32 mMouseSteerY; - LLPointer mAutoPilotDestination; - LLPointer mMouseSteerGrabPoint; - bool mClockwise; - LLUUID mMediaMouseCaptureID; - LLPickInfo mPick; - LLPickInfo mHoverPick; - LLPickInfo mSteerPick; - LLPointer mClickActionObject; - U8 mClickAction; - LLSafeHandle mLeftClickSelection; - bool mClickActionBuyEnabled; - bool mClickActionPayEnabled; - LLFrameTimer mDoubleClickTimer; -}; - -#endif +/** + * @file lltoolpie.h + * @brief LLToolPie class header file + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_TOOLPIE_H +#define LL_TOOLPIE_H + +#include "lltool.h" +#include "lluuid.h" +#include "llviewerwindow.h" // for LLPickInfo +#include "llhudeffectblob.h" // for LLPointer, apparently + +class LLViewerObject; +class LLObjectSelection; + +class LLToolPie : public LLTool, public LLSingleton +{ + LLSINGLETON(LLToolPie); + LOG_CLASS(LLToolPie); +public: + + // Virtual functions inherited from LLMouseHandler + virtual bool handleAnyMouseClick(S32 x, S32 y, MASK mask, EMouseClickType clicktype, bool down) override; + virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual bool handleRightMouseDown(S32 x, S32 y, MASK mask) override; + virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; + virtual bool handleRightMouseUp(S32 x, S32 y, MASK mask) override; + virtual bool handleHover(S32 x, S32 y, MASK mask) override; + virtual bool handleDoubleClick(S32 x, S32 y, MASK mask) override; + bool handleScrollWheelAny(S32 x, S32 y, S32 clicks_x, S32 clicks_y); + virtual bool handleScrollWheel(S32 x, S32 y, S32 clicks) override; + virtual bool handleScrollHWheel(S32 x, S32 y, S32 clicks) override; + virtual bool handleToolTip(S32 x, S32 y, MASK mask) override; + + virtual void render() override; + + virtual void stopEditing() override; + + virtual void onMouseCaptureLost() override; + virtual void handleSelect() override; + virtual void handleDeselect() override; + virtual LLTool* getOverrideTool(MASK mask) override; + + LLPickInfo& getPick() { return mPick; } + U8 getClickAction() { return mClickAction; } + LLViewerObject* getClickActionObject() { return mClickActionObject; } + LLObjectSelection* getLeftClickSelection() { return (LLObjectSelection*)mLeftClickSelection; } + void resetSelection(); + bool walkToClickedLocation(); + bool teleportToClickedLocation(); + void stopClickToWalk(); + + static void selectionPropertiesReceived(); + + static void showAvatarInspector(const LLUUID& avatar_id); + static void showObjectInspector(const LLUUID& object_id); + static void showObjectInspector(const LLUUID& object_id, const S32& object_face); + static void playCurrentMedia(const LLPickInfo& info); + static void VisitHomePage(const LLPickInfo& info); + +private: + bool outsideSlop (S32 x, S32 y, S32 start_x, S32 start_y); + bool handleLeftClickPick(); + bool handleRightClickPick(); + bool useClickAction (MASK mask, LLViewerObject* object,LLViewerObject* parent); + + void showVisualContextMenuEffect(); + ECursorType cursorFromObject(LLViewerObject* object); + + bool handleMediaClick(const LLPickInfo& info); + bool handleMediaDblClick(const LLPickInfo& info); + bool handleMediaHover(const LLPickInfo& info); + bool handleMediaMouseUp(); + bool handleTooltipLand(std::string line, std::string tooltip_msg); + bool handleTooltipObject( LLViewerObject* hover_object, std::string line, std::string tooltip_msg); + + void steerCameraWithMouse(S32 x, S32 y); + void startCameraSteering(); + void stopCameraSteering(); + bool inCameraSteerMode(); + +private: + bool mMouseButtonDown; + bool mMouseOutsideSlop; // for this drag, has mouse moved outside slop region + S32 mMouseDownX; + S32 mMouseDownY; + S32 mMouseSteerX; + S32 mMouseSteerY; + LLPointer mAutoPilotDestination; + LLPointer mMouseSteerGrabPoint; + bool mClockwise; + LLUUID mMediaMouseCaptureID; + LLPickInfo mPick; + LLPickInfo mHoverPick; + LLPickInfo mSteerPick; + LLPointer mClickActionObject; + U8 mClickAction; + LLSafeHandle mLeftClickSelection; + bool mClickActionBuyEnabled; + bool mClickActionPayEnabled; + LLFrameTimer mDoubleClickTimer; +}; + +#endif diff --git a/indra/newview/lltoolpipette.cpp b/indra/newview/lltoolpipette.cpp index dfd0dad7ad..9e3d435688 100644 --- a/indra/newview/lltoolpipette.cpp +++ b/indra/newview/lltoolpipette.cpp @@ -1,136 +1,136 @@ -/** - * @file lltoolpipette.cpp - * @brief LLToolPipette class implementation - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -/** - * A tool to pick texture entry infro from objects in world (color/texture) - */ - -#include "llviewerprecompiledheaders.h" - -// File includes -#include "lltoolpipette.h" - -// Library includes -#include "lltooltip.h" - -// Viewer includes -#include "llviewerobjectlist.h" -#include "llviewerwindow.h" -#include "llselectmgr.h" -#include "lltoolmgr.h" - -// -// Member functions -// - -LLToolPipette::LLToolPipette() -: LLTool(std::string("Pipette")), - mSuccess(true) -{ -} - - -LLToolPipette::~LLToolPipette() -{ } - - -bool LLToolPipette::handleMouseDown(S32 x, S32 y, MASK mask) -{ - mSuccess = true; - mTooltipMsg.clear(); - setMouseCapture(true); - gViewerWindow->pickAsync(x, y, mask, pickCallback); - return true; -} - -bool LLToolPipette::handleMouseUp(S32 x, S32 y, MASK mask) -{ - mSuccess = true; - LLSelectMgr::getInstance()->unhighlightAll(); - // *NOTE: This assumes the pipette tool is a transient tool. - LLToolMgr::getInstance()->clearTransientTool(); - setMouseCapture(false); - return true; -} - -bool LLToolPipette::handleHover(S32 x, S32 y, MASK mask) -{ - gViewerWindow->setCursor(mSuccess ? UI_CURSOR_PIPETTE : UI_CURSOR_NO); - if (hasMouseCapture()) // mouse button is down - { - gViewerWindow->pickAsync(x, y, mask, pickCallback); - return true; - } - return false; -} - -bool LLToolPipette::handleToolTip(S32 x, S32 y, MASK mask) -{ - if (mTooltipMsg.empty()) - { - return false; - } - - LLRect sticky_rect; - sticky_rect.setCenterAndSize(x, y, 20, 20); - LLToolTipMgr::instance().show(LLToolTip::Params() - .message(mTooltipMsg) - .sticky_rect(sticky_rect)); - - return true; -} - -void LLToolPipette::setTextureEntry(const LLTextureEntry* entry) -{ - if (entry) - { - mTextureEntry = *entry; - mSignal(mTextureEntry); - } -} - -void LLToolPipette::pickCallback(const LLPickInfo& pick_info) -{ - LLViewerObject* hit_obj = pick_info.getObject(); - LLSelectMgr::getInstance()->unhighlightAll(); - - // if we clicked on a face of a valid prim, save off texture entry data - if (hit_obj && - hit_obj->getPCode() == LL_PCODE_VOLUME && - pick_info.mObjectFace != -1) - { - //TODO: this should highlight the selected face only - LLSelectMgr::getInstance()->highlightObjectOnly(hit_obj); - const LLTextureEntry* entry = hit_obj->getTE(pick_info.mObjectFace); - LLToolPipette::getInstance()->setTextureEntry(entry); - } -} - -void LLToolPipette::setResult(bool success, const std::string& msg) -{ - mTooltipMsg = msg; - mSuccess = success; -} +/** + * @file lltoolpipette.cpp + * @brief LLToolPipette class implementation + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/** + * A tool to pick texture entry infro from objects in world (color/texture) + */ + +#include "llviewerprecompiledheaders.h" + +// File includes +#include "lltoolpipette.h" + +// Library includes +#include "lltooltip.h" + +// Viewer includes +#include "llviewerobjectlist.h" +#include "llviewerwindow.h" +#include "llselectmgr.h" +#include "lltoolmgr.h" + +// +// Member functions +// + +LLToolPipette::LLToolPipette() +: LLTool(std::string("Pipette")), + mSuccess(true) +{ +} + + +LLToolPipette::~LLToolPipette() +{ } + + +bool LLToolPipette::handleMouseDown(S32 x, S32 y, MASK mask) +{ + mSuccess = true; + mTooltipMsg.clear(); + setMouseCapture(true); + gViewerWindow->pickAsync(x, y, mask, pickCallback); + return true; +} + +bool LLToolPipette::handleMouseUp(S32 x, S32 y, MASK mask) +{ + mSuccess = true; + LLSelectMgr::getInstance()->unhighlightAll(); + // *NOTE: This assumes the pipette tool is a transient tool. + LLToolMgr::getInstance()->clearTransientTool(); + setMouseCapture(false); + return true; +} + +bool LLToolPipette::handleHover(S32 x, S32 y, MASK mask) +{ + gViewerWindow->setCursor(mSuccess ? UI_CURSOR_PIPETTE : UI_CURSOR_NO); + if (hasMouseCapture()) // mouse button is down + { + gViewerWindow->pickAsync(x, y, mask, pickCallback); + return true; + } + return false; +} + +bool LLToolPipette::handleToolTip(S32 x, S32 y, MASK mask) +{ + if (mTooltipMsg.empty()) + { + return false; + } + + LLRect sticky_rect; + sticky_rect.setCenterAndSize(x, y, 20, 20); + LLToolTipMgr::instance().show(LLToolTip::Params() + .message(mTooltipMsg) + .sticky_rect(sticky_rect)); + + return true; +} + +void LLToolPipette::setTextureEntry(const LLTextureEntry* entry) +{ + if (entry) + { + mTextureEntry = *entry; + mSignal(mTextureEntry); + } +} + +void LLToolPipette::pickCallback(const LLPickInfo& pick_info) +{ + LLViewerObject* hit_obj = pick_info.getObject(); + LLSelectMgr::getInstance()->unhighlightAll(); + + // if we clicked on a face of a valid prim, save off texture entry data + if (hit_obj && + hit_obj->getPCode() == LL_PCODE_VOLUME && + pick_info.mObjectFace != -1) + { + //TODO: this should highlight the selected face only + LLSelectMgr::getInstance()->highlightObjectOnly(hit_obj); + const LLTextureEntry* entry = hit_obj->getTE(pick_info.mObjectFace); + LLToolPipette::getInstance()->setTextureEntry(entry); + } +} + +void LLToolPipette::setResult(bool success, const std::string& msg) +{ + mTooltipMsg = msg; + mSuccess = success; +} diff --git a/indra/newview/lltoolpipette.h b/indra/newview/lltoolpipette.h index 99cdd387b2..0f1574f2d5 100644 --- a/indra/newview/lltoolpipette.h +++ b/indra/newview/lltoolpipette.h @@ -1,70 +1,70 @@ -/** - * @file lltoolpipette.h - * @brief LLToolPipette class header file - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -// A tool to pick texture entry infro from objects in world (color/texture) -// This tool assumes it is transient in the codebase and must be used -// accordingly. We should probably restructure the way tools are -// managed so that this is handled automatically. - -#ifndef LL_LLTOOLPIPETTE_H -#define LL_LLTOOLPIPETTE_H - -#include "lltool.h" -#include "lltextureentry.h" -#include -#include - -class LLViewerObject; -class LLPickInfo; - -class LLToolPipette -: public LLTool, public LLSingleton -{ - LLSINGLETON(LLToolPipette); - virtual ~LLToolPipette(); - -public: - virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; - virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; - virtual bool handleHover(S32 x, S32 y, MASK mask) override; - virtual bool handleToolTip(S32 x, S32 y, MASK mask) override; - - // Note: Don't return connection; use boost::bind + boost::signals2::trackable to disconnect slots - typedef boost::signals2::signal signal_t; - void setToolSelectCallback(const signal_t::slot_type& cb) { mSignal.connect(cb); } - void setResult(bool success, const std::string& msg); - - void setTextureEntry(const LLTextureEntry* entry); - static void pickCallback(const LLPickInfo& pick_info); - -protected: - LLTextureEntry mTextureEntry; - signal_t mSignal; - bool mSuccess; - std::string mTooltipMsg; -}; - -#endif //LL_LLTOOLPIPETTE_H +/** + * @file lltoolpipette.h + * @brief LLToolPipette class header file + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// A tool to pick texture entry infro from objects in world (color/texture) +// This tool assumes it is transient in the codebase and must be used +// accordingly. We should probably restructure the way tools are +// managed so that this is handled automatically. + +#ifndef LL_LLTOOLPIPETTE_H +#define LL_LLTOOLPIPETTE_H + +#include "lltool.h" +#include "lltextureentry.h" +#include +#include + +class LLViewerObject; +class LLPickInfo; + +class LLToolPipette +: public LLTool, public LLSingleton +{ + LLSINGLETON(LLToolPipette); + virtual ~LLToolPipette(); + +public: + virtual bool handleMouseDown(S32 x, S32 y, MASK mask) override; + virtual bool handleMouseUp(S32 x, S32 y, MASK mask) override; + virtual bool handleHover(S32 x, S32 y, MASK mask) override; + virtual bool handleToolTip(S32 x, S32 y, MASK mask) override; + + // Note: Don't return connection; use boost::bind + boost::signals2::trackable to disconnect slots + typedef boost::signals2::signal signal_t; + void setToolSelectCallback(const signal_t::slot_type& cb) { mSignal.connect(cb); } + void setResult(bool success, const std::string& msg); + + void setTextureEntry(const LLTextureEntry* entry); + static void pickCallback(const LLPickInfo& pick_info); + +protected: + LLTextureEntry mTextureEntry; + signal_t mSignal; + bool mSuccess; + std::string mTooltipMsg; +}; + +#endif //LL_LLTOOLPIPETTE_H diff --git a/indra/newview/lltoolplacer.cpp b/indra/newview/lltoolplacer.cpp index 708f3bb579..b15bb5efd5 100644 --- a/indra/newview/lltoolplacer.cpp +++ b/indra/newview/lltoolplacer.cpp @@ -1,536 +1,536 @@ -/** - * @file lltoolplacer.cpp - * @brief Tool for placing new objects into the world - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -// self header -#include "lltoolplacer.h" - -// viewer headers -#include "llbutton.h" -#include "llviewercontrol.h" -//#include "llfirstuse.h" -#include "llfloatertools.h" -#include "llselectmgr.h" -#include "llstatusbar.h" -#include "lltoolcomp.h" -#include "lltoolmgr.h" -#include "llviewerobject.h" -#include "llviewerregion.h" -#include "llviewerwindow.h" -#include "llworld.h" -#include "llui.h" - -//Headers added for functions moved from viewer.cpp -#include "llvograss.h" -#include "llvotree.h" -#include "llvolumemessage.h" -#include "llhudmanager.h" -#include "llagent.h" -#include "llagentcamera.h" -#include "llaudioengine.h" -#include "llhudeffecttrail.h" -#include "llviewerobjectlist.h" -#include "llviewercamera.h" -#include "llviewerstats.h" -#include "llvoavatarself.h" - -// linden library headers -#include "llprimitive.h" -#include "llwindow.h" // incBusyCount() -#include "material_codes.h" -#include "lluiusage.h" - -const LLVector3 DEFAULT_OBJECT_SCALE(0.5f, 0.5f, 0.5f); - -//static -LLPCode LLToolPlacer::sObjectType = LL_PCODE_CUBE; - -LLToolPlacer::LLToolPlacer() -: LLTool( "Create" ) -{ -} - -bool LLToolPlacer::raycastForNewObjPos( S32 x, S32 y, LLViewerObject** hit_obj, S32* hit_face, - bool* b_hit_land, LLVector3* ray_start_region, LLVector3* ray_end_region, LLViewerRegion** region ) -{ - F32 max_dist_from_camera = gSavedSettings.getF32( "MaxSelectDistance" ) - 1.f; - - // Viewer-side pick to find the right sim to create the object on. - // First find the surface the object will be created on. - LLPickInfo pick = gViewerWindow->pickImmediate(x, y, false, false); - - // Note: use the frontmost non-flora version because (a) plants usually have lots of alpha and (b) pants' Havok - // representations (if any) are NOT the same as their viewer representation. - if (pick.mPickType == LLPickInfo::PICK_FLORA) - { - *hit_obj = NULL; - *hit_face = -1; - } - else - { - *hit_obj = pick.getObject(); - *hit_face = pick.mObjectFace; - } - *b_hit_land = !(*hit_obj) && !pick.mPosGlobal.isExactlyZero(); - LLVector3d land_pos_global = pick.mPosGlobal; - - // Make sure there's a surface to place the new object on. - bool bypass_sim_raycast = false; - LLVector3d surface_pos_global; - if (*b_hit_land) - { - surface_pos_global = land_pos_global; - bypass_sim_raycast = true; - } - else - if (*hit_obj) - { - surface_pos_global = (*hit_obj)->getPositionGlobal(); - } - else - { - return false; - } - - // Make sure the surface isn't too far away. - LLVector3d ray_start_global = gAgentCamera.getCameraPositionGlobal(); - F32 dist_to_surface_sq = (F32)((surface_pos_global - ray_start_global).magVecSquared()); - if( dist_to_surface_sq > (max_dist_from_camera * max_dist_from_camera) ) - { - return false; - } - - // Find the sim where the surface lives. - LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromPosGlobal(surface_pos_global); - if (!regionp) - { - LL_WARNS() << "Trying to add object outside of all known regions!" << LL_ENDL; - return false; - } - - // Find the simulator-side ray that will be used to place the object accurately - LLVector3d mouse_direction; - mouse_direction.setVec( gViewerWindow->mouseDirectionGlobal( x, y ) ); - - *region = regionp; - *ray_start_region = regionp->getPosRegionFromGlobal( ray_start_global ); - F32 near_clip = LLViewerCamera::getInstance()->getNear() + 0.01f; // Include an epsilon to avoid rounding issues. - *ray_start_region += LLViewerCamera::getInstance()->getAtAxis() * near_clip; - - if( bypass_sim_raycast ) - { - // Hack to work around Havok's inability to ray cast onto height fields - *ray_end_region = regionp->getPosRegionFromGlobal( surface_pos_global ); // ray end is the viewer's intersection point - } - else - { - LLVector3d ray_end_global = ray_start_global + (1.f + max_dist_from_camera) * mouse_direction; // add an epsilon to the sim version of the ray to avoid rounding problems. - *ray_end_region = regionp->getPosRegionFromGlobal( ray_end_global ); - } - - return true; -} - - -bool LLToolPlacer::addObject( LLPCode pcode, S32 x, S32 y, U8 use_physics ) -{ - LLVector3 ray_start_region; - LLVector3 ray_end_region; - LLViewerRegion* regionp = NULL; - bool b_hit_land = false; - S32 hit_face = -1; - LLViewerObject* hit_obj = NULL; - U8 state = 0; - bool success = raycastForNewObjPos( x, y, &hit_obj, &hit_face, &b_hit_land, &ray_start_region, &ray_end_region, ®ionp ); - if( !success ) - { - return false; - } - - if( hit_obj && (hit_obj->isAvatar() || hit_obj->isAttachment()) ) - { - // Can't create objects on avatars or attachments - return false; - } - - if (NULL == regionp) - { - LL_WARNS() << "regionp was NULL; aborting function." << LL_ENDL; - return false; - } - - if (regionp->getRegionFlag(REGION_FLAGS_SANDBOX)) - { - //LLFirstUse::useSandbox(); - } - - // Set params for new object based on its PCode. - LLQuaternion rotation; - LLVector3 scale = DEFAULT_OBJECT_SCALE; - U8 material = LL_MCODE_WOOD; - bool create_selected = false; - LLVolumeParams volume_params; - - switch (pcode) - { - case LL_PCODE_LEGACY_GRASS: - // Randomize size of grass patch - scale.setVec(10.f + ll_frand(20.f), 10.f + ll_frand(20.f), 1.f + ll_frand(2.f)); - state = rand() % LLVOGrass::sMaxGrassSpecies; - break; - - - case LL_PCODE_LEGACY_TREE: - case LL_PCODE_TREE_NEW: - state = rand() % LLVOTree::sMaxTreeSpecies; - break; - - case LL_PCODE_SPHERE: - case LL_PCODE_CONE: - case LL_PCODE_CUBE: - case LL_PCODE_CYLINDER: - case LL_PCODE_TORUS: - case LLViewerObject::LL_VO_SQUARE_TORUS: - case LLViewerObject::LL_VO_TRIANGLE_TORUS: - default: - create_selected = true; - break; - } - - // Play creation sound - if (gAudiop) - { - gAudiop->triggerSound( LLUUID(gSavedSettings.getString("UISndObjectCreate")), - gAgent.getID(), 1.0f, LLAudioEngine::AUDIO_TYPE_UI); - } - - LLUIUsage::instance().logCommand("Build.ObjectAdd"); - gMessageSystem->newMessageFast(_PREHASH_ObjectAdd); - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - gMessageSystem->addUUIDFast(_PREHASH_GroupID, gAgent.getGroupID()); - gMessageSystem->nextBlockFast(_PREHASH_ObjectData); - gMessageSystem->addU8Fast(_PREHASH_Material, material); - - U32 flags = 0; // not selected - if (use_physics) - { - flags |= FLAGS_USE_PHYSICS; - } - if (create_selected) - { - flags |= FLAGS_CREATE_SELECTED; - } - gMessageSystem->addU32Fast(_PREHASH_AddFlags, flags ); - - LLPCode volume_pcode; // ...PCODE_VOLUME, or the original on error - switch (pcode) - { - case LL_PCODE_SPHERE: - rotation.setQuat(90.f * DEG_TO_RAD, LLVector3::y_axis); - - volume_params.setType( LL_PCODE_PROFILE_CIRCLE_HALF, LL_PCODE_PATH_CIRCLE ); - volume_params.setBeginAndEndS( 0.f, 1.f ); - volume_params.setBeginAndEndT( 0.f, 1.f ); - volume_params.setRatio ( 1, 1 ); - volume_params.setShear ( 0, 0 ); - LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); - volume_pcode = LL_PCODE_VOLUME; - break; - - case LL_PCODE_TORUS: - rotation.setQuat(90.f * DEG_TO_RAD, LLVector3::y_axis); - - volume_params.setType( LL_PCODE_PROFILE_CIRCLE, LL_PCODE_PATH_CIRCLE ); - volume_params.setBeginAndEndS( 0.f, 1.f ); - volume_params.setBeginAndEndT( 0.f, 1.f ); - volume_params.setRatio ( 1.f, 0.25f ); // "top size" - volume_params.setShear ( 0, 0 ); - LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); - volume_pcode = LL_PCODE_VOLUME; - break; - - case LLViewerObject::LL_VO_SQUARE_TORUS: - rotation.setQuat(90.f * DEG_TO_RAD, LLVector3::y_axis); - - volume_params.setType( LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_CIRCLE ); - volume_params.setBeginAndEndS( 0.f, 1.f ); - volume_params.setBeginAndEndT( 0.f, 1.f ); - volume_params.setRatio ( 1.f, 0.25f ); // "top size" - volume_params.setShear ( 0, 0 ); - LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); - volume_pcode = LL_PCODE_VOLUME; - break; - - case LLViewerObject::LL_VO_TRIANGLE_TORUS: - rotation.setQuat(90.f * DEG_TO_RAD, LLVector3::y_axis); - - volume_params.setType( LL_PCODE_PROFILE_EQUALTRI, LL_PCODE_PATH_CIRCLE ); - volume_params.setBeginAndEndS( 0.f, 1.f ); - volume_params.setBeginAndEndT( 0.f, 1.f ); - volume_params.setRatio ( 1.f, 0.25f ); // "top size" - volume_params.setShear ( 0, 0 ); - LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); - volume_pcode = LL_PCODE_VOLUME; - break; - - case LL_PCODE_SPHERE_HEMI: - volume_params.setType( LL_PCODE_PROFILE_CIRCLE_HALF, LL_PCODE_PATH_CIRCLE ); - //volume_params.setBeginAndEndS( 0.5f, 1.f ); - volume_params.setBeginAndEndT( 0.f, 0.5f ); - volume_params.setRatio ( 1, 1 ); - volume_params.setShear ( 0, 0 ); - LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); - volume_pcode = LL_PCODE_VOLUME; - break; - - case LL_PCODE_CUBE: - volume_params.setType( LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE ); - volume_params.setBeginAndEndS( 0.f, 1.f ); - volume_params.setBeginAndEndT( 0.f, 1.f ); - volume_params.setRatio ( 1, 1 ); - volume_params.setShear ( 0, 0 ); - LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); - volume_pcode = LL_PCODE_VOLUME; - break; - - case LL_PCODE_PRISM: - volume_params.setType( LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE ); - volume_params.setBeginAndEndS( 0.f, 1.f ); - volume_params.setBeginAndEndT( 0.f, 1.f ); - volume_params.setRatio ( 0, 1 ); - volume_params.setShear ( -0.5f, 0 ); - LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); - volume_pcode = LL_PCODE_VOLUME; - break; - - case LL_PCODE_PYRAMID: - volume_params.setType( LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE ); - volume_params.setBeginAndEndS( 0.f, 1.f ); - volume_params.setBeginAndEndT( 0.f, 1.f ); - volume_params.setRatio ( 0, 0 ); - volume_params.setShear ( 0, 0 ); - LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); - volume_pcode = LL_PCODE_VOLUME; - break; - - case LL_PCODE_TETRAHEDRON: - volume_params.setType( LL_PCODE_PROFILE_EQUALTRI, LL_PCODE_PATH_LINE ); - volume_params.setBeginAndEndS( 0.f, 1.f ); - volume_params.setBeginAndEndT( 0.f, 1.f ); - volume_params.setRatio ( 0, 0 ); - volume_params.setShear ( 0, 0 ); - LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); - volume_pcode = LL_PCODE_VOLUME; - break; - - case LL_PCODE_CYLINDER: - volume_params.setType( LL_PCODE_PROFILE_CIRCLE, LL_PCODE_PATH_LINE ); - volume_params.setBeginAndEndS( 0.f, 1.f ); - volume_params.setBeginAndEndT( 0.f, 1.f ); - volume_params.setRatio ( 1, 1 ); - volume_params.setShear ( 0, 0 ); - LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); - volume_pcode = LL_PCODE_VOLUME; - break; - - case LL_PCODE_CYLINDER_HEMI: - volume_params.setType( LL_PCODE_PROFILE_CIRCLE, LL_PCODE_PATH_LINE ); - volume_params.setBeginAndEndS( 0.25f, 0.75f ); - volume_params.setBeginAndEndT( 0.f, 1.f ); - volume_params.setRatio ( 1, 1 ); - volume_params.setShear ( 0, 0 ); - LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); - volume_pcode = LL_PCODE_VOLUME; - break; - - case LL_PCODE_CONE: - volume_params.setType( LL_PCODE_PROFILE_CIRCLE, LL_PCODE_PATH_LINE ); - volume_params.setBeginAndEndS( 0.f, 1.f ); - volume_params.setBeginAndEndT( 0.f, 1.f ); - volume_params.setRatio ( 0, 0 ); - volume_params.setShear ( 0, 0 ); - LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); - volume_pcode = LL_PCODE_VOLUME; - break; - - case LL_PCODE_CONE_HEMI: - volume_params.setType( LL_PCODE_PROFILE_CIRCLE, LL_PCODE_PATH_LINE ); - volume_params.setBeginAndEndS( 0.25f, 0.75f ); - volume_params.setBeginAndEndT( 0.f, 1.f ); - volume_params.setRatio ( 0, 0 ); - volume_params.setShear ( 0, 0 ); - LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); - volume_pcode = LL_PCODE_VOLUME; - break; - - default: - LLVolumeMessage::packVolumeParams(0, gMessageSystem); - volume_pcode = pcode; - break; - } - gMessageSystem->addU8Fast(_PREHASH_PCode, volume_pcode); - - gMessageSystem->addVector3Fast(_PREHASH_Scale, scale ); - gMessageSystem->addQuatFast(_PREHASH_Rotation, rotation ); - gMessageSystem->addVector3Fast(_PREHASH_RayStart, ray_start_region ); - gMessageSystem->addVector3Fast(_PREHASH_RayEnd, ray_end_region ); - gMessageSystem->addU8Fast(_PREHASH_BypassRaycast, (U8)b_hit_land ); - gMessageSystem->addU8Fast(_PREHASH_RayEndIsIntersection, (U8)false ); - gMessageSystem->addU8Fast(_PREHASH_State, state); - - // Limit raycast to a single object. - // Speeds up server raycast + avoid problems with server ray hitting objects - // that were clipped by the near plane or culled on the viewer. - LLUUID ray_target_id; - if( hit_obj ) - { - ray_target_id = hit_obj->getID(); - } - else - { - ray_target_id.setNull(); - } - gMessageSystem->addUUIDFast(_PREHASH_RayTargetID, ray_target_id ); - - // Pack in name value pairs - gMessageSystem->sendReliable(regionp->getHost()); - - // Spawns a message, so must be after above send - if (create_selected) - { - LLSelectMgr::getInstance()->deselectAll(); - gViewerWindow->getWindow()->incBusyCount(); - } - - // VEFFECT: AddObject - LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM, true); - effectp->setSourceObject((LLViewerObject*)gAgentAvatarp); - effectp->setPositionGlobal(regionp->getPosGlobalFromRegion(ray_end_region)); - effectp->setDuration(LL_HUD_DUR_SHORT); - effectp->setColor(LLColor4U(gAgent.getEffectColor())); - - add(LLStatViewer::OBJECT_CREATE, 1); - - return true; -} - -// Used by the placer tool to add copies of the current selection. -// Inspired by add_object(). JC -bool LLToolPlacer::addDuplicate(S32 x, S32 y) -{ - LLVector3 ray_start_region; - LLVector3 ray_end_region; - LLViewerRegion* regionp = NULL; - bool b_hit_land = false; - S32 hit_face = -1; - LLViewerObject* hit_obj = NULL; - bool success = raycastForNewObjPos( x, y, &hit_obj, &hit_face, &b_hit_land, &ray_start_region, &ray_end_region, ®ionp ); - if( !success ) - { - make_ui_sound("UISndInvalidOp"); - return false; - } - if( hit_obj && (hit_obj->isAvatar() || hit_obj->isAttachment()) ) - { - // Can't create objects on avatars or attachments - make_ui_sound("UISndInvalidOp"); - return false; - } - - - // Limit raycast to a single object. - // Speeds up server raycast + avoid problems with server ray hitting objects - // that were clipped by the near plane or culled on the viewer. - LLUUID ray_target_id; - if( hit_obj ) - { - ray_target_id = hit_obj->getID(); - } - else - { - ray_target_id.setNull(); - } - - LLSelectMgr::getInstance()->selectDuplicateOnRay(ray_start_region, - ray_end_region, - b_hit_land, // suppress raycast - false, // intersection - ray_target_id, - gSavedSettings.getBOOL("CreateToolCopyCenters"), - gSavedSettings.getBOOL("CreateToolCopyRotates"), - false); // select copy - - if (regionp - && (regionp->getRegionFlag(REGION_FLAGS_SANDBOX))) - { - //LLFirstUse::useSandbox(); - } - - return true; -} - - -bool LLToolPlacer::placeObject(S32 x, S32 y, MASK mask) -{ - bool added = true; - - if (gSavedSettings.getBOOL("CreateToolCopySelection")) - { - added = addDuplicate(x, y); - } - else - { - added = addObject( sObjectType, x, y, false ); - } - - // ...and go back to the default tool - if (added && !gSavedSettings.getBOOL("CreateToolKeepSelected")) - { - LLToolMgr::getInstance()->getCurrentToolset()->selectTool( LLToolCompTranslate::getInstance() ); - } - - return added; -} - -bool LLToolPlacer::handleHover(S32 x, S32 y, MASK mask) -{ - LL_DEBUGS("UserInput") << "hover handled by LLToolPlacer" << LL_ENDL; - gViewerWindow->setCursor(UI_CURSOR_TOOLCREATE); - return true; -} - -void LLToolPlacer::handleSelect() -{ - gFloaterTools->setStatusText("place"); -} - -void LLToolPlacer::handleDeselect() -{ -} - +/** + * @file lltoolplacer.cpp + * @brief Tool for placing new objects into the world + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +// self header +#include "lltoolplacer.h" + +// viewer headers +#include "llbutton.h" +#include "llviewercontrol.h" +//#include "llfirstuse.h" +#include "llfloatertools.h" +#include "llselectmgr.h" +#include "llstatusbar.h" +#include "lltoolcomp.h" +#include "lltoolmgr.h" +#include "llviewerobject.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" +#include "llworld.h" +#include "llui.h" + +//Headers added for functions moved from viewer.cpp +#include "llvograss.h" +#include "llvotree.h" +#include "llvolumemessage.h" +#include "llhudmanager.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llaudioengine.h" +#include "llhudeffecttrail.h" +#include "llviewerobjectlist.h" +#include "llviewercamera.h" +#include "llviewerstats.h" +#include "llvoavatarself.h" + +// linden library headers +#include "llprimitive.h" +#include "llwindow.h" // incBusyCount() +#include "material_codes.h" +#include "lluiusage.h" + +const LLVector3 DEFAULT_OBJECT_SCALE(0.5f, 0.5f, 0.5f); + +//static +LLPCode LLToolPlacer::sObjectType = LL_PCODE_CUBE; + +LLToolPlacer::LLToolPlacer() +: LLTool( "Create" ) +{ +} + +bool LLToolPlacer::raycastForNewObjPos( S32 x, S32 y, LLViewerObject** hit_obj, S32* hit_face, + bool* b_hit_land, LLVector3* ray_start_region, LLVector3* ray_end_region, LLViewerRegion** region ) +{ + F32 max_dist_from_camera = gSavedSettings.getF32( "MaxSelectDistance" ) - 1.f; + + // Viewer-side pick to find the right sim to create the object on. + // First find the surface the object will be created on. + LLPickInfo pick = gViewerWindow->pickImmediate(x, y, false, false); + + // Note: use the frontmost non-flora version because (a) plants usually have lots of alpha and (b) pants' Havok + // representations (if any) are NOT the same as their viewer representation. + if (pick.mPickType == LLPickInfo::PICK_FLORA) + { + *hit_obj = NULL; + *hit_face = -1; + } + else + { + *hit_obj = pick.getObject(); + *hit_face = pick.mObjectFace; + } + *b_hit_land = !(*hit_obj) && !pick.mPosGlobal.isExactlyZero(); + LLVector3d land_pos_global = pick.mPosGlobal; + + // Make sure there's a surface to place the new object on. + bool bypass_sim_raycast = false; + LLVector3d surface_pos_global; + if (*b_hit_land) + { + surface_pos_global = land_pos_global; + bypass_sim_raycast = true; + } + else + if (*hit_obj) + { + surface_pos_global = (*hit_obj)->getPositionGlobal(); + } + else + { + return false; + } + + // Make sure the surface isn't too far away. + LLVector3d ray_start_global = gAgentCamera.getCameraPositionGlobal(); + F32 dist_to_surface_sq = (F32)((surface_pos_global - ray_start_global).magVecSquared()); + if( dist_to_surface_sq > (max_dist_from_camera * max_dist_from_camera) ) + { + return false; + } + + // Find the sim where the surface lives. + LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromPosGlobal(surface_pos_global); + if (!regionp) + { + LL_WARNS() << "Trying to add object outside of all known regions!" << LL_ENDL; + return false; + } + + // Find the simulator-side ray that will be used to place the object accurately + LLVector3d mouse_direction; + mouse_direction.setVec( gViewerWindow->mouseDirectionGlobal( x, y ) ); + + *region = regionp; + *ray_start_region = regionp->getPosRegionFromGlobal( ray_start_global ); + F32 near_clip = LLViewerCamera::getInstance()->getNear() + 0.01f; // Include an epsilon to avoid rounding issues. + *ray_start_region += LLViewerCamera::getInstance()->getAtAxis() * near_clip; + + if( bypass_sim_raycast ) + { + // Hack to work around Havok's inability to ray cast onto height fields + *ray_end_region = regionp->getPosRegionFromGlobal( surface_pos_global ); // ray end is the viewer's intersection point + } + else + { + LLVector3d ray_end_global = ray_start_global + (1.f + max_dist_from_camera) * mouse_direction; // add an epsilon to the sim version of the ray to avoid rounding problems. + *ray_end_region = regionp->getPosRegionFromGlobal( ray_end_global ); + } + + return true; +} + + +bool LLToolPlacer::addObject( LLPCode pcode, S32 x, S32 y, U8 use_physics ) +{ + LLVector3 ray_start_region; + LLVector3 ray_end_region; + LLViewerRegion* regionp = NULL; + bool b_hit_land = false; + S32 hit_face = -1; + LLViewerObject* hit_obj = NULL; + U8 state = 0; + bool success = raycastForNewObjPos( x, y, &hit_obj, &hit_face, &b_hit_land, &ray_start_region, &ray_end_region, ®ionp ); + if( !success ) + { + return false; + } + + if( hit_obj && (hit_obj->isAvatar() || hit_obj->isAttachment()) ) + { + // Can't create objects on avatars or attachments + return false; + } + + if (NULL == regionp) + { + LL_WARNS() << "regionp was NULL; aborting function." << LL_ENDL; + return false; + } + + if (regionp->getRegionFlag(REGION_FLAGS_SANDBOX)) + { + //LLFirstUse::useSandbox(); + } + + // Set params for new object based on its PCode. + LLQuaternion rotation; + LLVector3 scale = DEFAULT_OBJECT_SCALE; + U8 material = LL_MCODE_WOOD; + bool create_selected = false; + LLVolumeParams volume_params; + + switch (pcode) + { + case LL_PCODE_LEGACY_GRASS: + // Randomize size of grass patch + scale.setVec(10.f + ll_frand(20.f), 10.f + ll_frand(20.f), 1.f + ll_frand(2.f)); + state = rand() % LLVOGrass::sMaxGrassSpecies; + break; + + + case LL_PCODE_LEGACY_TREE: + case LL_PCODE_TREE_NEW: + state = rand() % LLVOTree::sMaxTreeSpecies; + break; + + case LL_PCODE_SPHERE: + case LL_PCODE_CONE: + case LL_PCODE_CUBE: + case LL_PCODE_CYLINDER: + case LL_PCODE_TORUS: + case LLViewerObject::LL_VO_SQUARE_TORUS: + case LLViewerObject::LL_VO_TRIANGLE_TORUS: + default: + create_selected = true; + break; + } + + // Play creation sound + if (gAudiop) + { + gAudiop->triggerSound( LLUUID(gSavedSettings.getString("UISndObjectCreate")), + gAgent.getID(), 1.0f, LLAudioEngine::AUDIO_TYPE_UI); + } + + LLUIUsage::instance().logCommand("Build.ObjectAdd"); + gMessageSystem->newMessageFast(_PREHASH_ObjectAdd); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + gMessageSystem->addUUIDFast(_PREHASH_GroupID, gAgent.getGroupID()); + gMessageSystem->nextBlockFast(_PREHASH_ObjectData); + gMessageSystem->addU8Fast(_PREHASH_Material, material); + + U32 flags = 0; // not selected + if (use_physics) + { + flags |= FLAGS_USE_PHYSICS; + } + if (create_selected) + { + flags |= FLAGS_CREATE_SELECTED; + } + gMessageSystem->addU32Fast(_PREHASH_AddFlags, flags ); + + LLPCode volume_pcode; // ...PCODE_VOLUME, or the original on error + switch (pcode) + { + case LL_PCODE_SPHERE: + rotation.setQuat(90.f * DEG_TO_RAD, LLVector3::y_axis); + + volume_params.setType( LL_PCODE_PROFILE_CIRCLE_HALF, LL_PCODE_PATH_CIRCLE ); + volume_params.setBeginAndEndS( 0.f, 1.f ); + volume_params.setBeginAndEndT( 0.f, 1.f ); + volume_params.setRatio ( 1, 1 ); + volume_params.setShear ( 0, 0 ); + LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); + volume_pcode = LL_PCODE_VOLUME; + break; + + case LL_PCODE_TORUS: + rotation.setQuat(90.f * DEG_TO_RAD, LLVector3::y_axis); + + volume_params.setType( LL_PCODE_PROFILE_CIRCLE, LL_PCODE_PATH_CIRCLE ); + volume_params.setBeginAndEndS( 0.f, 1.f ); + volume_params.setBeginAndEndT( 0.f, 1.f ); + volume_params.setRatio ( 1.f, 0.25f ); // "top size" + volume_params.setShear ( 0, 0 ); + LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); + volume_pcode = LL_PCODE_VOLUME; + break; + + case LLViewerObject::LL_VO_SQUARE_TORUS: + rotation.setQuat(90.f * DEG_TO_RAD, LLVector3::y_axis); + + volume_params.setType( LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_CIRCLE ); + volume_params.setBeginAndEndS( 0.f, 1.f ); + volume_params.setBeginAndEndT( 0.f, 1.f ); + volume_params.setRatio ( 1.f, 0.25f ); // "top size" + volume_params.setShear ( 0, 0 ); + LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); + volume_pcode = LL_PCODE_VOLUME; + break; + + case LLViewerObject::LL_VO_TRIANGLE_TORUS: + rotation.setQuat(90.f * DEG_TO_RAD, LLVector3::y_axis); + + volume_params.setType( LL_PCODE_PROFILE_EQUALTRI, LL_PCODE_PATH_CIRCLE ); + volume_params.setBeginAndEndS( 0.f, 1.f ); + volume_params.setBeginAndEndT( 0.f, 1.f ); + volume_params.setRatio ( 1.f, 0.25f ); // "top size" + volume_params.setShear ( 0, 0 ); + LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); + volume_pcode = LL_PCODE_VOLUME; + break; + + case LL_PCODE_SPHERE_HEMI: + volume_params.setType( LL_PCODE_PROFILE_CIRCLE_HALF, LL_PCODE_PATH_CIRCLE ); + //volume_params.setBeginAndEndS( 0.5f, 1.f ); + volume_params.setBeginAndEndT( 0.f, 0.5f ); + volume_params.setRatio ( 1, 1 ); + volume_params.setShear ( 0, 0 ); + LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); + volume_pcode = LL_PCODE_VOLUME; + break; + + case LL_PCODE_CUBE: + volume_params.setType( LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE ); + volume_params.setBeginAndEndS( 0.f, 1.f ); + volume_params.setBeginAndEndT( 0.f, 1.f ); + volume_params.setRatio ( 1, 1 ); + volume_params.setShear ( 0, 0 ); + LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); + volume_pcode = LL_PCODE_VOLUME; + break; + + case LL_PCODE_PRISM: + volume_params.setType( LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE ); + volume_params.setBeginAndEndS( 0.f, 1.f ); + volume_params.setBeginAndEndT( 0.f, 1.f ); + volume_params.setRatio ( 0, 1 ); + volume_params.setShear ( -0.5f, 0 ); + LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); + volume_pcode = LL_PCODE_VOLUME; + break; + + case LL_PCODE_PYRAMID: + volume_params.setType( LL_PCODE_PROFILE_SQUARE, LL_PCODE_PATH_LINE ); + volume_params.setBeginAndEndS( 0.f, 1.f ); + volume_params.setBeginAndEndT( 0.f, 1.f ); + volume_params.setRatio ( 0, 0 ); + volume_params.setShear ( 0, 0 ); + LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); + volume_pcode = LL_PCODE_VOLUME; + break; + + case LL_PCODE_TETRAHEDRON: + volume_params.setType( LL_PCODE_PROFILE_EQUALTRI, LL_PCODE_PATH_LINE ); + volume_params.setBeginAndEndS( 0.f, 1.f ); + volume_params.setBeginAndEndT( 0.f, 1.f ); + volume_params.setRatio ( 0, 0 ); + volume_params.setShear ( 0, 0 ); + LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); + volume_pcode = LL_PCODE_VOLUME; + break; + + case LL_PCODE_CYLINDER: + volume_params.setType( LL_PCODE_PROFILE_CIRCLE, LL_PCODE_PATH_LINE ); + volume_params.setBeginAndEndS( 0.f, 1.f ); + volume_params.setBeginAndEndT( 0.f, 1.f ); + volume_params.setRatio ( 1, 1 ); + volume_params.setShear ( 0, 0 ); + LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); + volume_pcode = LL_PCODE_VOLUME; + break; + + case LL_PCODE_CYLINDER_HEMI: + volume_params.setType( LL_PCODE_PROFILE_CIRCLE, LL_PCODE_PATH_LINE ); + volume_params.setBeginAndEndS( 0.25f, 0.75f ); + volume_params.setBeginAndEndT( 0.f, 1.f ); + volume_params.setRatio ( 1, 1 ); + volume_params.setShear ( 0, 0 ); + LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); + volume_pcode = LL_PCODE_VOLUME; + break; + + case LL_PCODE_CONE: + volume_params.setType( LL_PCODE_PROFILE_CIRCLE, LL_PCODE_PATH_LINE ); + volume_params.setBeginAndEndS( 0.f, 1.f ); + volume_params.setBeginAndEndT( 0.f, 1.f ); + volume_params.setRatio ( 0, 0 ); + volume_params.setShear ( 0, 0 ); + LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); + volume_pcode = LL_PCODE_VOLUME; + break; + + case LL_PCODE_CONE_HEMI: + volume_params.setType( LL_PCODE_PROFILE_CIRCLE, LL_PCODE_PATH_LINE ); + volume_params.setBeginAndEndS( 0.25f, 0.75f ); + volume_params.setBeginAndEndT( 0.f, 1.f ); + volume_params.setRatio ( 0, 0 ); + volume_params.setShear ( 0, 0 ); + LLVolumeMessage::packVolumeParams(&volume_params, gMessageSystem); + volume_pcode = LL_PCODE_VOLUME; + break; + + default: + LLVolumeMessage::packVolumeParams(0, gMessageSystem); + volume_pcode = pcode; + break; + } + gMessageSystem->addU8Fast(_PREHASH_PCode, volume_pcode); + + gMessageSystem->addVector3Fast(_PREHASH_Scale, scale ); + gMessageSystem->addQuatFast(_PREHASH_Rotation, rotation ); + gMessageSystem->addVector3Fast(_PREHASH_RayStart, ray_start_region ); + gMessageSystem->addVector3Fast(_PREHASH_RayEnd, ray_end_region ); + gMessageSystem->addU8Fast(_PREHASH_BypassRaycast, (U8)b_hit_land ); + gMessageSystem->addU8Fast(_PREHASH_RayEndIsIntersection, (U8)false ); + gMessageSystem->addU8Fast(_PREHASH_State, state); + + // Limit raycast to a single object. + // Speeds up server raycast + avoid problems with server ray hitting objects + // that were clipped by the near plane or culled on the viewer. + LLUUID ray_target_id; + if( hit_obj ) + { + ray_target_id = hit_obj->getID(); + } + else + { + ray_target_id.setNull(); + } + gMessageSystem->addUUIDFast(_PREHASH_RayTargetID, ray_target_id ); + + // Pack in name value pairs + gMessageSystem->sendReliable(regionp->getHost()); + + // Spawns a message, so must be after above send + if (create_selected) + { + LLSelectMgr::getInstance()->deselectAll(); + gViewerWindow->getWindow()->incBusyCount(); + } + + // VEFFECT: AddObject + LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM, true); + effectp->setSourceObject((LLViewerObject*)gAgentAvatarp); + effectp->setPositionGlobal(regionp->getPosGlobalFromRegion(ray_end_region)); + effectp->setDuration(LL_HUD_DUR_SHORT); + effectp->setColor(LLColor4U(gAgent.getEffectColor())); + + add(LLStatViewer::OBJECT_CREATE, 1); + + return true; +} + +// Used by the placer tool to add copies of the current selection. +// Inspired by add_object(). JC +bool LLToolPlacer::addDuplicate(S32 x, S32 y) +{ + LLVector3 ray_start_region; + LLVector3 ray_end_region; + LLViewerRegion* regionp = NULL; + bool b_hit_land = false; + S32 hit_face = -1; + LLViewerObject* hit_obj = NULL; + bool success = raycastForNewObjPos( x, y, &hit_obj, &hit_face, &b_hit_land, &ray_start_region, &ray_end_region, ®ionp ); + if( !success ) + { + make_ui_sound("UISndInvalidOp"); + return false; + } + if( hit_obj && (hit_obj->isAvatar() || hit_obj->isAttachment()) ) + { + // Can't create objects on avatars or attachments + make_ui_sound("UISndInvalidOp"); + return false; + } + + + // Limit raycast to a single object. + // Speeds up server raycast + avoid problems with server ray hitting objects + // that were clipped by the near plane or culled on the viewer. + LLUUID ray_target_id; + if( hit_obj ) + { + ray_target_id = hit_obj->getID(); + } + else + { + ray_target_id.setNull(); + } + + LLSelectMgr::getInstance()->selectDuplicateOnRay(ray_start_region, + ray_end_region, + b_hit_land, // suppress raycast + false, // intersection + ray_target_id, + gSavedSettings.getBOOL("CreateToolCopyCenters"), + gSavedSettings.getBOOL("CreateToolCopyRotates"), + false); // select copy + + if (regionp + && (regionp->getRegionFlag(REGION_FLAGS_SANDBOX))) + { + //LLFirstUse::useSandbox(); + } + + return true; +} + + +bool LLToolPlacer::placeObject(S32 x, S32 y, MASK mask) +{ + bool added = true; + + if (gSavedSettings.getBOOL("CreateToolCopySelection")) + { + added = addDuplicate(x, y); + } + else + { + added = addObject( sObjectType, x, y, false ); + } + + // ...and go back to the default tool + if (added && !gSavedSettings.getBOOL("CreateToolKeepSelected")) + { + LLToolMgr::getInstance()->getCurrentToolset()->selectTool( LLToolCompTranslate::getInstance() ); + } + + return added; +} + +bool LLToolPlacer::handleHover(S32 x, S32 y, MASK mask) +{ + LL_DEBUGS("UserInput") << "hover handled by LLToolPlacer" << LL_ENDL; + gViewerWindow->setCursor(UI_CURSOR_TOOLCREATE); + return true; +} + +void LLToolPlacer::handleSelect() +{ + gFloaterTools->setStatusText("place"); +} + +void LLToolPlacer::handleDeselect() +{ +} + diff --git a/indra/newview/lltoolplacer.h b/indra/newview/lltoolplacer.h index 2d39e70ed5..f9501f83b2 100644 --- a/indra/newview/lltoolplacer.h +++ b/indra/newview/lltoolplacer.h @@ -1,63 +1,63 @@ -/** - * @file lltoolplacer.h - * @brief Tool for placing new objects into the world - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_TOOLPLACER_H -#define LL_TOOLPLACER_H - -#include "llpanel.h" -#include "lltool.h" - -class LLButton; -class LLViewerRegion; - -//////////////////////////////////////////////////// -// LLToolPlacer - -class LLToolPlacer - : public LLTool -{ -public: - LLToolPlacer(); - - virtual bool placeObject(S32 x, S32 y, MASK mask); - virtual bool handleHover(S32 x, S32 y, MASK mask); - virtual void handleSelect(); // do stuff when your tool is selected - virtual void handleDeselect(); // clean up when your tool is deselected - - static void setObjectType( LLPCode type ) { sObjectType = type; } - static LLPCode getObjectType() { return sObjectType; } - -protected: - static LLPCode sObjectType; - -private: - bool addObject( LLPCode pcode, S32 x, S32 y, U8 use_physics ); - bool raycastForNewObjPos( S32 x, S32 y, LLViewerObject** hit_obj, S32* hit_face, - bool* b_hit_land, LLVector3* ray_start_region, LLVector3* ray_end_region, LLViewerRegion** region ); - bool addDuplicate(S32 x, S32 y); -}; - -#endif +/** + * @file lltoolplacer.h + * @brief Tool for placing new objects into the world + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_TOOLPLACER_H +#define LL_TOOLPLACER_H + +#include "llpanel.h" +#include "lltool.h" + +class LLButton; +class LLViewerRegion; + +//////////////////////////////////////////////////// +// LLToolPlacer + +class LLToolPlacer + : public LLTool +{ +public: + LLToolPlacer(); + + virtual bool placeObject(S32 x, S32 y, MASK mask); + virtual bool handleHover(S32 x, S32 y, MASK mask); + virtual void handleSelect(); // do stuff when your tool is selected + virtual void handleDeselect(); // clean up when your tool is deselected + + static void setObjectType( LLPCode type ) { sObjectType = type; } + static LLPCode getObjectType() { return sObjectType; } + +protected: + static LLPCode sObjectType; + +private: + bool addObject( LLPCode pcode, S32 x, S32 y, U8 use_physics ); + bool raycastForNewObjPos( S32 x, S32 y, LLViewerObject** hit_obj, S32* hit_face, + bool* b_hit_land, LLVector3* ray_start_region, LLVector3* ray_end_region, LLViewerRegion** region ); + bool addDuplicate(S32 x, S32 y); +}; + +#endif diff --git a/indra/newview/lltoolselect.cpp b/indra/newview/lltoolselect.cpp index 1b10140be6..9050788ed9 100644 --- a/indra/newview/lltoolselect.cpp +++ b/indra/newview/lltoolselect.cpp @@ -1,291 +1,291 @@ -/** - * @file lltoolselect.cpp - * @brief LLToolSelect class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lltoolselect.h" - -#include "llagent.h" -#include "llagentcamera.h" -#include "llviewercontrol.h" -#include "lldrawable.h" -#include "llhudicon.h" -#include "llmanip.h" -#include "llmenugl.h" -#include "llselectmgr.h" -#include "llviewermediafocus.h" -#include "lltoolmgr.h" -#include "llfloaterscriptdebug.h" -#include "llviewercamera.h" -#include "llviewermenu.h" -#include "llviewerobject.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llviewerwindow.h" -#include "llvoavatarself.h" -#include "llworld.h" - -// Globals -//extern bool gAllowSelectAvatar; - -const F32 SELECTION_ROTATION_TRESHOLD = 0.1f; -const F32 SELECTION_SITTING_ROTATION_TRESHOLD = 3.2f; //radian - -LLToolSelect::LLToolSelect( LLToolComposite* composite ) -: LLTool( std::string("Select"), composite ), - mIgnoreGroup( false ) -{ - } - -// True if you selected an object. -bool LLToolSelect::handleMouseDown(S32 x, S32 y, MASK mask) -{ - // do immediate pick query - bool pick_rigged = false; //gSavedSettings.getBOOL("AnimatedObjectsAllowLeftClick"); - bool pick_transparent = gSavedSettings.getBOOL("SelectInvisibleObjects"); - bool pick_reflection_probe = gSavedSettings.getBOOL("SelectReflectionProbes"); - - mPick = gViewerWindow->pickImmediate(x, y, pick_transparent, pick_rigged, false, true, pick_reflection_probe); - - // Pass mousedown to agent - LLTool::handleMouseDown(x, y, mask); - - return mPick.getObject().notNull(); -} - - -// static -LLObjectSelectionHandle LLToolSelect::handleObjectSelection(const LLPickInfo& pick, bool ignore_group, bool temp_select, bool select_root) -{ - LLViewerObject* object = pick.getObject(); - if (select_root) - { - object = object->getRootEdit(); - } - bool select_owned = gSavedSettings.getBOOL("SelectOwnedOnly"); - bool select_movable = gSavedSettings.getBOOL("SelectMovableOnly"); - - // *NOTE: These settings must be cleaned up at bottom of function. - if (temp_select || LLSelectMgr::getInstance()->mAllowSelectAvatar) - { - gSavedSettings.setBOOL("SelectOwnedOnly", false); - gSavedSettings.setBOOL("SelectMovableOnly", false); - LLSelectMgr::getInstance()->setForceSelection(true); - } - - bool extend_select = (pick.mKeyMask == MASK_SHIFT) || (pick.mKeyMask == MASK_CONTROL); - - // If no object, check for icon, then just deselect - if (!object) - { - LLHUDIcon* last_hit_hud_icon = pick.mHUDIcon; - - if (last_hit_hud_icon && last_hit_hud_icon->getSourceObject()) - { - LLFloaterScriptDebug::show(last_hit_hud_icon->getSourceObject()->getID()); - } - else if (!extend_select) - { - LLSelectMgr::getInstance()->deselectAll(); - } - } - else - { - bool already_selected = object->isSelected(); - - if (already_selected && - object->getNumTEs() > 0 && - !LLSelectMgr::getInstance()->getSelection()->contains(object,SELECT_ALL_TES)) - { - const LLTextureEntry* tep = object->getTE(pick.mObjectFace); - if (tep && !tep->isSelected() && !LLViewerMediaFocus::getInstance()->getFocusedObjectID().isNull()) - { - // we were interacting with media and clicked on non selected face, drop media focus - LLViewerMediaFocus::getInstance()->clearFocus(); - // selection was removed and zoom preserved by clearFocus(), continue with regular selection - already_selected = false; - extend_select = true; - } - } - - if ( extend_select ) - { - if ( already_selected ) - { - if ( ignore_group ) - { - LLSelectMgr::getInstance()->deselectObjectOnly(object); - } - else - { - LLSelectMgr::getInstance()->deselectObjectAndFamily(object, true, true); - } - } - else - { - if ( ignore_group ) - { - LLSelectMgr::getInstance()->selectObjectOnly(object, SELECT_ALL_TES); - } - else - { - LLSelectMgr::getInstance()->selectObjectAndFamily(object); - } - } - } - else - { - // Save the current zoom values because deselect resets them. - F32 target_zoom; - F32 current_zoom; - LLSelectMgr::getInstance()->getAgentHUDZoom(target_zoom, current_zoom); - - // JC - Change behavior to make it easier to select children - // of linked sets. 9/3/2002 - if( !already_selected || ignore_group) - { - // ...lose current selection in favor of just this object - LLSelectMgr::getInstance()->deselectAll(); - } - - if ( ignore_group ) - { - LLSelectMgr::getInstance()->selectObjectOnly(object, SELECT_ALL_TES); - } - else - { - LLSelectMgr::getInstance()->selectObjectAndFamily(object); - } - - // restore the zoom to the previously stored values. - LLSelectMgr::getInstance()->setAgentHUDZoom(target_zoom, current_zoom); - } - - if (!gAgentCamera.getFocusOnAvatar() && // if camera not glued to avatar - LLVOAvatar::findAvatarFromAttachment(object) != gAgentAvatarp && // and it's not one of your attachments - object != gAgentAvatarp) // and it's not you - { - // have avatar turn to face the selected object(s) - LLVector3d selection_center = LLSelectMgr::getInstance()->getSelectionCenterGlobal(); - selection_center = selection_center - gAgent.getPositionGlobal(); - LLVector3 selection_dir; - selection_dir.setVec(selection_center); - selection_dir.mV[VZ] = 0.f; - selection_dir.normVec(); - if (!object->isAvatar() && gAgent.getAtAxis() * selection_dir < 0.6f) - { - LLQuaternion target_rot; - target_rot.shortestArc(LLVector3::x_axis, selection_dir); - gAgent.startAutoPilotGlobal(gAgent.getPositionGlobal(), - "", - &target_rot, - NULL, - NULL, - MAX_FAR_CLIP /*stop_distance, don't care since we are looking, not moving*/, - gAgentAvatarp->isSitting() ? SELECTION_SITTING_ROTATION_TRESHOLD : SELECTION_ROTATION_TRESHOLD); - } - } - - if (temp_select) - { - if (!already_selected) - { - LLViewerObject* root_object = (LLViewerObject*)object->getRootEdit(); - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - - // this is just a temporary selection - LLSelectNode* select_node = selection->findNode(root_object); - if (select_node) - { - select_node->setTransient(true); - } - - LLViewerObject::const_child_list_t& child_list = root_object->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); iter++) - { - LLViewerObject* child = *iter; - select_node = selection->findNode(child); - if (select_node) - { - select_node->setTransient(true); - } - } - - } - } //if(temp_select) - } //if(!object) - - // Cleanup temp select settings above. - if (temp_select ||LLSelectMgr::getInstance()->mAllowSelectAvatar) - { - gSavedSettings.setBOOL("SelectOwnedOnly", select_owned); - gSavedSettings.setBOOL("SelectMovableOnly", select_movable); - LLSelectMgr::getInstance()->setForceSelection(false); - } - - return LLSelectMgr::getInstance()->getSelection(); -} - -bool LLToolSelect::handleMouseUp(S32 x, S32 y, MASK mask) -{ - mIgnoreGroup = gSavedSettings.getBOOL("EditLinkedParts"); - - handleObjectSelection(mPick, mIgnoreGroup, false); - - return LLTool::handleMouseUp(x, y, mask); -} - -void LLToolSelect::handleDeselect() -{ - if( hasMouseCapture() ) - { - setMouseCapture( false ); // Calls onMouseCaptureLost() indirectly - } -} - - -void LLToolSelect::stopEditing() -{ - if( hasMouseCapture() ) - { - setMouseCapture( false ); // Calls onMouseCaptureLost() indirectly - } -} - -void LLToolSelect::onMouseCaptureLost() -{ - // Finish drag - - LLSelectMgr::getInstance()->enableSilhouette(true); - - // Clean up drag-specific variables - mIgnoreGroup = false; -} - - - - +/** + * @file lltoolselect.cpp + * @brief LLToolSelect class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lltoolselect.h" + +#include "llagent.h" +#include "llagentcamera.h" +#include "llviewercontrol.h" +#include "lldrawable.h" +#include "llhudicon.h" +#include "llmanip.h" +#include "llmenugl.h" +#include "llselectmgr.h" +#include "llviewermediafocus.h" +#include "lltoolmgr.h" +#include "llfloaterscriptdebug.h" +#include "llviewercamera.h" +#include "llviewermenu.h" +#include "llviewerobject.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" +#include "llvoavatarself.h" +#include "llworld.h" + +// Globals +//extern bool gAllowSelectAvatar; + +const F32 SELECTION_ROTATION_TRESHOLD = 0.1f; +const F32 SELECTION_SITTING_ROTATION_TRESHOLD = 3.2f; //radian + +LLToolSelect::LLToolSelect( LLToolComposite* composite ) +: LLTool( std::string("Select"), composite ), + mIgnoreGroup( false ) +{ + } + +// True if you selected an object. +bool LLToolSelect::handleMouseDown(S32 x, S32 y, MASK mask) +{ + // do immediate pick query + bool pick_rigged = false; //gSavedSettings.getBOOL("AnimatedObjectsAllowLeftClick"); + bool pick_transparent = gSavedSettings.getBOOL("SelectInvisibleObjects"); + bool pick_reflection_probe = gSavedSettings.getBOOL("SelectReflectionProbes"); + + mPick = gViewerWindow->pickImmediate(x, y, pick_transparent, pick_rigged, false, true, pick_reflection_probe); + + // Pass mousedown to agent + LLTool::handleMouseDown(x, y, mask); + + return mPick.getObject().notNull(); +} + + +// static +LLObjectSelectionHandle LLToolSelect::handleObjectSelection(const LLPickInfo& pick, bool ignore_group, bool temp_select, bool select_root) +{ + LLViewerObject* object = pick.getObject(); + if (select_root) + { + object = object->getRootEdit(); + } + bool select_owned = gSavedSettings.getBOOL("SelectOwnedOnly"); + bool select_movable = gSavedSettings.getBOOL("SelectMovableOnly"); + + // *NOTE: These settings must be cleaned up at bottom of function. + if (temp_select || LLSelectMgr::getInstance()->mAllowSelectAvatar) + { + gSavedSettings.setBOOL("SelectOwnedOnly", false); + gSavedSettings.setBOOL("SelectMovableOnly", false); + LLSelectMgr::getInstance()->setForceSelection(true); + } + + bool extend_select = (pick.mKeyMask == MASK_SHIFT) || (pick.mKeyMask == MASK_CONTROL); + + // If no object, check for icon, then just deselect + if (!object) + { + LLHUDIcon* last_hit_hud_icon = pick.mHUDIcon; + + if (last_hit_hud_icon && last_hit_hud_icon->getSourceObject()) + { + LLFloaterScriptDebug::show(last_hit_hud_icon->getSourceObject()->getID()); + } + else if (!extend_select) + { + LLSelectMgr::getInstance()->deselectAll(); + } + } + else + { + bool already_selected = object->isSelected(); + + if (already_selected && + object->getNumTEs() > 0 && + !LLSelectMgr::getInstance()->getSelection()->contains(object,SELECT_ALL_TES)) + { + const LLTextureEntry* tep = object->getTE(pick.mObjectFace); + if (tep && !tep->isSelected() && !LLViewerMediaFocus::getInstance()->getFocusedObjectID().isNull()) + { + // we were interacting with media and clicked on non selected face, drop media focus + LLViewerMediaFocus::getInstance()->clearFocus(); + // selection was removed and zoom preserved by clearFocus(), continue with regular selection + already_selected = false; + extend_select = true; + } + } + + if ( extend_select ) + { + if ( already_selected ) + { + if ( ignore_group ) + { + LLSelectMgr::getInstance()->deselectObjectOnly(object); + } + else + { + LLSelectMgr::getInstance()->deselectObjectAndFamily(object, true, true); + } + } + else + { + if ( ignore_group ) + { + LLSelectMgr::getInstance()->selectObjectOnly(object, SELECT_ALL_TES); + } + else + { + LLSelectMgr::getInstance()->selectObjectAndFamily(object); + } + } + } + else + { + // Save the current zoom values because deselect resets them. + F32 target_zoom; + F32 current_zoom; + LLSelectMgr::getInstance()->getAgentHUDZoom(target_zoom, current_zoom); + + // JC - Change behavior to make it easier to select children + // of linked sets. 9/3/2002 + if( !already_selected || ignore_group) + { + // ...lose current selection in favor of just this object + LLSelectMgr::getInstance()->deselectAll(); + } + + if ( ignore_group ) + { + LLSelectMgr::getInstance()->selectObjectOnly(object, SELECT_ALL_TES); + } + else + { + LLSelectMgr::getInstance()->selectObjectAndFamily(object); + } + + // restore the zoom to the previously stored values. + LLSelectMgr::getInstance()->setAgentHUDZoom(target_zoom, current_zoom); + } + + if (!gAgentCamera.getFocusOnAvatar() && // if camera not glued to avatar + LLVOAvatar::findAvatarFromAttachment(object) != gAgentAvatarp && // and it's not one of your attachments + object != gAgentAvatarp) // and it's not you + { + // have avatar turn to face the selected object(s) + LLVector3d selection_center = LLSelectMgr::getInstance()->getSelectionCenterGlobal(); + selection_center = selection_center - gAgent.getPositionGlobal(); + LLVector3 selection_dir; + selection_dir.setVec(selection_center); + selection_dir.mV[VZ] = 0.f; + selection_dir.normVec(); + if (!object->isAvatar() && gAgent.getAtAxis() * selection_dir < 0.6f) + { + LLQuaternion target_rot; + target_rot.shortestArc(LLVector3::x_axis, selection_dir); + gAgent.startAutoPilotGlobal(gAgent.getPositionGlobal(), + "", + &target_rot, + NULL, + NULL, + MAX_FAR_CLIP /*stop_distance, don't care since we are looking, not moving*/, + gAgentAvatarp->isSitting() ? SELECTION_SITTING_ROTATION_TRESHOLD : SELECTION_ROTATION_TRESHOLD); + } + } + + if (temp_select) + { + if (!already_selected) + { + LLViewerObject* root_object = (LLViewerObject*)object->getRootEdit(); + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + + // this is just a temporary selection + LLSelectNode* select_node = selection->findNode(root_object); + if (select_node) + { + select_node->setTransient(true); + } + + LLViewerObject::const_child_list_t& child_list = root_object->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); iter++) + { + LLViewerObject* child = *iter; + select_node = selection->findNode(child); + if (select_node) + { + select_node->setTransient(true); + } + } + + } + } //if(temp_select) + } //if(!object) + + // Cleanup temp select settings above. + if (temp_select ||LLSelectMgr::getInstance()->mAllowSelectAvatar) + { + gSavedSettings.setBOOL("SelectOwnedOnly", select_owned); + gSavedSettings.setBOOL("SelectMovableOnly", select_movable); + LLSelectMgr::getInstance()->setForceSelection(false); + } + + return LLSelectMgr::getInstance()->getSelection(); +} + +bool LLToolSelect::handleMouseUp(S32 x, S32 y, MASK mask) +{ + mIgnoreGroup = gSavedSettings.getBOOL("EditLinkedParts"); + + handleObjectSelection(mPick, mIgnoreGroup, false); + + return LLTool::handleMouseUp(x, y, mask); +} + +void LLToolSelect::handleDeselect() +{ + if( hasMouseCapture() ) + { + setMouseCapture( false ); // Calls onMouseCaptureLost() indirectly + } +} + + +void LLToolSelect::stopEditing() +{ + if( hasMouseCapture() ) + { + setMouseCapture( false ); // Calls onMouseCaptureLost() indirectly + } +} + +void LLToolSelect::onMouseCaptureLost() +{ + // Finish drag + + LLSelectMgr::getInstance()->enableSilhouette(true); + + // Clean up drag-specific variables + mIgnoreGroup = false; +} + + + + diff --git a/indra/newview/lltoolselect.h b/indra/newview/lltoolselect.h index 655d9db82f..da2b046641 100644 --- a/indra/newview/lltoolselect.h +++ b/indra/newview/lltoolselect.h @@ -1,59 +1,59 @@ -/** - * @file lltoolselect.h - * @brief LLToolSelect class header file - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_TOOLSELECT_H -#define LL_TOOLSELECT_H - -#include "lltool.h" -#include "v3math.h" -#include "lluuid.h" -#include "llviewerwindow.h" // for LLPickInfo - -class LLObjectSelection; - -class LLToolSelect : public LLTool -{ -public: - LLToolSelect( LLToolComposite* composite ); - - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - - virtual void stopEditing(); - - static LLSafeHandle handleObjectSelection(const LLPickInfo& pick, bool ignore_group, bool temp_select, bool select_root = false); - - virtual void onMouseCaptureLost(); - virtual void handleDeselect(); - -protected: - bool mIgnoreGroup; - LLUUID mSelectObjectID; - LLPickInfo mPick; -}; - - -#endif // LL_TOOLSELECTION_H +/** + * @file lltoolselect.h + * @brief LLToolSelect class header file + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_TOOLSELECT_H +#define LL_TOOLSELECT_H + +#include "lltool.h" +#include "v3math.h" +#include "lluuid.h" +#include "llviewerwindow.h" // for LLPickInfo + +class LLObjectSelection; + +class LLToolSelect : public LLTool +{ +public: + LLToolSelect( LLToolComposite* composite ); + + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + + virtual void stopEditing(); + + static LLSafeHandle handleObjectSelection(const LLPickInfo& pick, bool ignore_group, bool temp_select, bool select_root = false); + + virtual void onMouseCaptureLost(); + virtual void handleDeselect(); + +protected: + bool mIgnoreGroup; + LLUUID mSelectObjectID; + LLPickInfo mPick; +}; + + +#endif // LL_TOOLSELECTION_H diff --git a/indra/newview/lltoolselectland.cpp b/indra/newview/lltoolselectland.cpp index f10c15bc8b..88553c7557 100644 --- a/indra/newview/lltoolselectland.cpp +++ b/indra/newview/lltoolselectland.cpp @@ -1,234 +1,234 @@ -/** - * @file lltoolselectland.cpp - * @brief LLToolSelectLand class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lltoolselectland.h" - -// indra includes -#include "llparcel.h" - -// Viewer includes -#include "llviewercontrol.h" -#include "llfloatertools.h" -#include "llselectmgr.h" -#include "llstatusbar.h" -#include "llviewerparcelmgr.h" -#include "llviewerwindow.h" - -// -// Member functions -// - -LLToolSelectLand::LLToolSelectLand( ) -: LLTool( std::string("Parcel") ), - mDragStartGlobal(), - mDragEndGlobal(), - mDragEndValid(false), - mDragStartX(0), - mDragStartY(0), - mDragEndX(0), - mDragEndY(0), - mMouseOutsideSlop(false), - mWestSouthBottom(), - mEastNorthTop() -{ } - -LLToolSelectLand::~LLToolSelectLand() -{ -} - - -bool LLToolSelectLand::handleMouseDown(S32 x, S32 y, MASK mask) -{ - bool hit_land = gViewerWindow->mousePointOnLandGlobal(x, y, &mDragStartGlobal); - if (hit_land) - { - setMouseCapture( true ); - - mDragStartX = x; - mDragStartY = y; - mDragEndX = x; - mDragEndY = y; - - mDragEndValid = true; - mDragEndGlobal = mDragStartGlobal; - - sanitize_corners(mDragStartGlobal, mDragEndGlobal, mWestSouthBottom, mEastNorthTop); - - mWestSouthBottom -= LLVector3d( PARCEL_GRID_STEP_METERS/2, PARCEL_GRID_STEP_METERS/2, 0 ); - mEastNorthTop += LLVector3d( PARCEL_GRID_STEP_METERS/2, PARCEL_GRID_STEP_METERS/2, 0 ); - - roundXY(mWestSouthBottom); - roundXY(mEastNorthTop); - - mMouseOutsideSlop = true; //false; - - LLViewerParcelMgr::getInstance()->deselectLand(); - } - - return hit_land; -} - - -bool LLToolSelectLand::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - LLVector3d pos_global; - bool hit_land = gViewerWindow->mousePointOnLandGlobal(x, y, &pos_global); - if (hit_land) - { - // Auto-select this parcel - LLViewerParcelMgr::getInstance()->selectParcelAt( pos_global ); - return true; - } - return false; -} - - -bool LLToolSelectLand::handleMouseUp(S32 x, S32 y, MASK mask) -{ - if( hasMouseCapture() ) - { - setMouseCapture( false ); - - if (mMouseOutsideSlop && mDragEndValid) - { - // Take the drag start and end locations, then map the southwest - // point down to the next grid location, and the northeast point up - // to the next grid location. - - sanitize_corners(mDragStartGlobal, mDragEndGlobal, mWestSouthBottom, mEastNorthTop); - - mWestSouthBottom -= LLVector3d( PARCEL_GRID_STEP_METERS/2, PARCEL_GRID_STEP_METERS/2, 0 ); - mEastNorthTop += LLVector3d( PARCEL_GRID_STEP_METERS/2, PARCEL_GRID_STEP_METERS/2, 0 ); - - roundXY(mWestSouthBottom); - roundXY(mEastNorthTop); - - // Don't auto-select entire parcel. - mSelection = LLViewerParcelMgr::getInstance()->selectLand( mWestSouthBottom, mEastNorthTop, false ); - } - - mMouseOutsideSlop = false; - mDragEndValid = false; - - return true; - } - return false; -} - - -bool LLToolSelectLand::handleHover(S32 x, S32 y, MASK mask) -{ - if( hasMouseCapture() ) - { - if (mMouseOutsideSlop || outsideSlop(x, y, mDragStartX, mDragStartY)) - { - mMouseOutsideSlop = true; - - // Must do this every frame, in case the camera moved or the land moved - // since last frame. - - // If doesn't hit land, doesn't change old value - LLVector3d land_global; - bool hit_land = gViewerWindow->mousePointOnLandGlobal(x, y, &land_global); - if (hit_land) - { - mDragEndValid = true; - mDragEndGlobal = land_global; - - sanitize_corners(mDragStartGlobal, mDragEndGlobal, mWestSouthBottom, mEastNorthTop); - - mWestSouthBottom -= LLVector3d( PARCEL_GRID_STEP_METERS/2, PARCEL_GRID_STEP_METERS/2, 0 ); - mEastNorthTop += LLVector3d( PARCEL_GRID_STEP_METERS/2, PARCEL_GRID_STEP_METERS/2, 0 ); - - roundXY(mWestSouthBottom); - roundXY(mEastNorthTop); - - LL_DEBUGS("UserInput") << "hover handled by LLToolSelectLand (active, land)" << LL_ENDL; - gViewerWindow->setCursor(UI_CURSOR_ARROW); - } - else - { - mDragEndValid = false; - LL_DEBUGS("UserInput") << "hover handled by LLToolSelectLand (active, no land)" << LL_ENDL; - gViewerWindow->setCursor(UI_CURSOR_NO); - } - - mDragEndX = x; - mDragEndY = y; - } - else - { - LL_DEBUGS("UserInput") << "hover handled by LLToolSelectLand (active, in slop)" << LL_ENDL; - gViewerWindow->setCursor(UI_CURSOR_ARROW); - } - } - else - { - LL_DEBUGS("UserInput") << "hover handled by LLToolSelectLand (inactive)" << LL_ENDL; - gViewerWindow->setCursor(UI_CURSOR_ARROW); - } - - return true; -} - - -void LLToolSelectLand::render() -{ - if( hasMouseCapture() && /*mMouseOutsideSlop &&*/ mDragEndValid) - { - LLViewerParcelMgr::getInstance()->renderRect( mWestSouthBottom, mEastNorthTop ); - } -} - -void LLToolSelectLand::handleSelect() -{ - gFloaterTools->setStatusText("selectland"); -} - - -void LLToolSelectLand::handleDeselect() -{ - mSelection = NULL; -} - - -void LLToolSelectLand::roundXY(LLVector3d &vec) -{ - vec.mdV[VX] = ll_round( vec.mdV[VX], (F64)PARCEL_GRID_STEP_METERS ); - vec.mdV[VY] = ll_round( vec.mdV[VY], (F64)PARCEL_GRID_STEP_METERS ); -} - - -// true if x,y outside small box around start_x,start_y -bool LLToolSelectLand::outsideSlop(S32 x, S32 y, S32 start_x, S32 start_y) -{ - S32 dx = x - start_x; - S32 dy = y - start_y; - - return (dx <= -2 || 2 <= dx || dy <= -2 || 2 <= dy); -} +/** + * @file lltoolselectland.cpp + * @brief LLToolSelectLand class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lltoolselectland.h" + +// indra includes +#include "llparcel.h" + +// Viewer includes +#include "llviewercontrol.h" +#include "llfloatertools.h" +#include "llselectmgr.h" +#include "llstatusbar.h" +#include "llviewerparcelmgr.h" +#include "llviewerwindow.h" + +// +// Member functions +// + +LLToolSelectLand::LLToolSelectLand( ) +: LLTool( std::string("Parcel") ), + mDragStartGlobal(), + mDragEndGlobal(), + mDragEndValid(false), + mDragStartX(0), + mDragStartY(0), + mDragEndX(0), + mDragEndY(0), + mMouseOutsideSlop(false), + mWestSouthBottom(), + mEastNorthTop() +{ } + +LLToolSelectLand::~LLToolSelectLand() +{ +} + + +bool LLToolSelectLand::handleMouseDown(S32 x, S32 y, MASK mask) +{ + bool hit_land = gViewerWindow->mousePointOnLandGlobal(x, y, &mDragStartGlobal); + if (hit_land) + { + setMouseCapture( true ); + + mDragStartX = x; + mDragStartY = y; + mDragEndX = x; + mDragEndY = y; + + mDragEndValid = true; + mDragEndGlobal = mDragStartGlobal; + + sanitize_corners(mDragStartGlobal, mDragEndGlobal, mWestSouthBottom, mEastNorthTop); + + mWestSouthBottom -= LLVector3d( PARCEL_GRID_STEP_METERS/2, PARCEL_GRID_STEP_METERS/2, 0 ); + mEastNorthTop += LLVector3d( PARCEL_GRID_STEP_METERS/2, PARCEL_GRID_STEP_METERS/2, 0 ); + + roundXY(mWestSouthBottom); + roundXY(mEastNorthTop); + + mMouseOutsideSlop = true; //false; + + LLViewerParcelMgr::getInstance()->deselectLand(); + } + + return hit_land; +} + + +bool LLToolSelectLand::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + LLVector3d pos_global; + bool hit_land = gViewerWindow->mousePointOnLandGlobal(x, y, &pos_global); + if (hit_land) + { + // Auto-select this parcel + LLViewerParcelMgr::getInstance()->selectParcelAt( pos_global ); + return true; + } + return false; +} + + +bool LLToolSelectLand::handleMouseUp(S32 x, S32 y, MASK mask) +{ + if( hasMouseCapture() ) + { + setMouseCapture( false ); + + if (mMouseOutsideSlop && mDragEndValid) + { + // Take the drag start and end locations, then map the southwest + // point down to the next grid location, and the northeast point up + // to the next grid location. + + sanitize_corners(mDragStartGlobal, mDragEndGlobal, mWestSouthBottom, mEastNorthTop); + + mWestSouthBottom -= LLVector3d( PARCEL_GRID_STEP_METERS/2, PARCEL_GRID_STEP_METERS/2, 0 ); + mEastNorthTop += LLVector3d( PARCEL_GRID_STEP_METERS/2, PARCEL_GRID_STEP_METERS/2, 0 ); + + roundXY(mWestSouthBottom); + roundXY(mEastNorthTop); + + // Don't auto-select entire parcel. + mSelection = LLViewerParcelMgr::getInstance()->selectLand( mWestSouthBottom, mEastNorthTop, false ); + } + + mMouseOutsideSlop = false; + mDragEndValid = false; + + return true; + } + return false; +} + + +bool LLToolSelectLand::handleHover(S32 x, S32 y, MASK mask) +{ + if( hasMouseCapture() ) + { + if (mMouseOutsideSlop || outsideSlop(x, y, mDragStartX, mDragStartY)) + { + mMouseOutsideSlop = true; + + // Must do this every frame, in case the camera moved or the land moved + // since last frame. + + // If doesn't hit land, doesn't change old value + LLVector3d land_global; + bool hit_land = gViewerWindow->mousePointOnLandGlobal(x, y, &land_global); + if (hit_land) + { + mDragEndValid = true; + mDragEndGlobal = land_global; + + sanitize_corners(mDragStartGlobal, mDragEndGlobal, mWestSouthBottom, mEastNorthTop); + + mWestSouthBottom -= LLVector3d( PARCEL_GRID_STEP_METERS/2, PARCEL_GRID_STEP_METERS/2, 0 ); + mEastNorthTop += LLVector3d( PARCEL_GRID_STEP_METERS/2, PARCEL_GRID_STEP_METERS/2, 0 ); + + roundXY(mWestSouthBottom); + roundXY(mEastNorthTop); + + LL_DEBUGS("UserInput") << "hover handled by LLToolSelectLand (active, land)" << LL_ENDL; + gViewerWindow->setCursor(UI_CURSOR_ARROW); + } + else + { + mDragEndValid = false; + LL_DEBUGS("UserInput") << "hover handled by LLToolSelectLand (active, no land)" << LL_ENDL; + gViewerWindow->setCursor(UI_CURSOR_NO); + } + + mDragEndX = x; + mDragEndY = y; + } + else + { + LL_DEBUGS("UserInput") << "hover handled by LLToolSelectLand (active, in slop)" << LL_ENDL; + gViewerWindow->setCursor(UI_CURSOR_ARROW); + } + } + else + { + LL_DEBUGS("UserInput") << "hover handled by LLToolSelectLand (inactive)" << LL_ENDL; + gViewerWindow->setCursor(UI_CURSOR_ARROW); + } + + return true; +} + + +void LLToolSelectLand::render() +{ + if( hasMouseCapture() && /*mMouseOutsideSlop &&*/ mDragEndValid) + { + LLViewerParcelMgr::getInstance()->renderRect( mWestSouthBottom, mEastNorthTop ); + } +} + +void LLToolSelectLand::handleSelect() +{ + gFloaterTools->setStatusText("selectland"); +} + + +void LLToolSelectLand::handleDeselect() +{ + mSelection = NULL; +} + + +void LLToolSelectLand::roundXY(LLVector3d &vec) +{ + vec.mdV[VX] = ll_round( vec.mdV[VX], (F64)PARCEL_GRID_STEP_METERS ); + vec.mdV[VY] = ll_round( vec.mdV[VY], (F64)PARCEL_GRID_STEP_METERS ); +} + + +// true if x,y outside small box around start_x,start_y +bool LLToolSelectLand::outsideSlop(S32 x, S32 y, S32 start_x, S32 start_y) +{ + S32 dx = x - start_x; + S32 dy = y - start_y; + + return (dx <= -2 || 2 <= dx || dy <= -2 || 2 <= dy); +} diff --git a/indra/newview/lltoolselectland.h b/indra/newview/lltoolselectland.h index e6184672ff..15672aa7d4 100644 --- a/indra/newview/lltoolselectland.h +++ b/indra/newview/lltoolselectland.h @@ -1,76 +1,76 @@ -/** - * @file lltoolselectland.h - * @brief LLToolSelectLand class header file - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLTOOLSELECTLAND_H -#define LL_LLTOOLSELECTLAND_H - -#include "lltool.h" -#include "v3dmath.h" - -class LLParcelSelection; - -class LLToolSelectLand -: public LLTool, public LLSingleton -{ - LLSINGLETON(LLToolSelectLand); - virtual ~LLToolSelectLand(); - -public: - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask) override; - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask) override; - /*virtual*/ void render() override; // draw the select rectangle - /*virtual*/ bool isAlwaysRendered() override { return true; } - - /*virtual*/ void handleSelect() override; - /*virtual*/ void handleDeselect() override; - -protected: - bool outsideSlop(S32 x, S32 y, S32 start_x, S32 start_y); - void roundXY(LLVector3d& vec); - -protected: - LLVector3d mDragStartGlobal; // global coords - LLVector3d mDragEndGlobal; // global coords - bool mDragEndValid; // is drag end a valid point in the world? - - S32 mDragStartX; // screen coords, from left - S32 mDragStartY; // screen coords, from bottom - - S32 mDragEndX; - S32 mDragEndY; - - bool mMouseOutsideSlop; // has mouse ever gone outside slop region? - - LLVector3d mWestSouthBottom; // global coords, from drag - LLVector3d mEastNorthTop; // global coords, from drag - - LLSafeHandle mSelection; // hold on to a parcel selection -}; - - -#endif +/** + * @file lltoolselectland.h + * @brief LLToolSelectLand class header file + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLTOOLSELECTLAND_H +#define LL_LLTOOLSELECTLAND_H + +#include "lltool.h" +#include "v3dmath.h" + +class LLParcelSelection; + +class LLToolSelectLand +: public LLTool, public LLSingleton +{ + LLSINGLETON(LLToolSelectLand); + virtual ~LLToolSelectLand(); + +public: + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask) override; + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask) override; + /*virtual*/ void render() override; // draw the select rectangle + /*virtual*/ bool isAlwaysRendered() override { return true; } + + /*virtual*/ void handleSelect() override; + /*virtual*/ void handleDeselect() override; + +protected: + bool outsideSlop(S32 x, S32 y, S32 start_x, S32 start_y); + void roundXY(LLVector3d& vec); + +protected: + LLVector3d mDragStartGlobal; // global coords + LLVector3d mDragEndGlobal; // global coords + bool mDragEndValid; // is drag end a valid point in the world? + + S32 mDragStartX; // screen coords, from left + S32 mDragStartY; // screen coords, from bottom + + S32 mDragEndX; + S32 mDragEndY; + + bool mMouseOutsideSlop; // has mouse ever gone outside slop region? + + LLVector3d mWestSouthBottom; // global coords, from drag + LLVector3d mEastNorthTop; // global coords, from drag + + LLSafeHandle mSelection; // hold on to a parcel selection +}; + + +#endif diff --git a/indra/newview/lltoolselectrect.cpp b/indra/newview/lltoolselectrect.cpp index 4e5d853c85..f9bb70b24e 100644 --- a/indra/newview/lltoolselectrect.cpp +++ b/indra/newview/lltoolselectrect.cpp @@ -1,207 +1,207 @@ -/** - * @file lltoolselectrect.cpp - * @brief A tool to select multiple objects with a screen-space rectangle. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -// File includes -#include "lltoolselectrect.h" - -// Library includes -#include "llgl.h" -#include "llrender.h" - -// Viewer includes -#include "llviewercontrol.h" -#include "llui.h" -#include "llselectmgr.h" -#include "lltoolmgr.h" -#include "llviewerobject.h" -#include "llviewerobjectlist.h" -#include "llviewerwindow.h" -#include "llviewercamera.h" - -#include "llglheaders.h" - -// Globals -const S32 SLOP_RADIUS = 5; - - -// -// Member functions -// - -LLToolSelectRect::LLToolSelectRect( LLToolComposite* composite ) - : - LLToolSelect( composite ), - mDragStartX(0), - mDragStartY(0), - mDragEndX(0), - mDragEndY(0), - mDragLastWidth(0), - mDragLastHeight(0), - mMouseOutsideSlop(false) - -{ } - - -void dialog_refresh_all(void); - -bool LLToolSelectRect::handleMouseDown(S32 x, S32 y, MASK mask) -{ - bool pick_rigged = false; //gSavedSettings.getBOOL("AnimatedObjectsAllowLeftClick"); - handlePick(gViewerWindow->pickImmediate(x, y, true /* pick_transparent */, pick_rigged)); - - LLTool::handleMouseDown(x, y, mask); - - return mPick.getObject().notNull(); -} - -void LLToolSelectRect::handlePick(const LLPickInfo& pick) -{ - mPick = pick; - - // start dragging rectangle - setMouseCapture( true ); - - mDragStartX = pick.mMousePt.mX; - mDragStartY = pick.mMousePt.mY; - mDragEndX = pick.mMousePt.mX; - mDragEndY = pick.mMousePt.mY; - - mMouseOutsideSlop = false; -} - - -bool LLToolSelectRect::handleMouseUp(S32 x, S32 y, MASK mask) -{ - setMouseCapture( false ); - - if( mMouseOutsideSlop ) - { - mDragLastWidth = 0; - mDragLastHeight = 0; - - mMouseOutsideSlop = false; - - if (mask == MASK_CONTROL) - { - LLSelectMgr::getInstance()->deselectHighlightedObjects(); - } - else - { - LLSelectMgr::getInstance()->selectHighlightedObjects(); - } - return true; - } - else - { - return LLToolSelect::handleMouseUp(x, y, mask); - } -} - - -bool LLToolSelectRect::handleHover(S32 x, S32 y, MASK mask) -{ - if( hasMouseCapture() ) - { - if (mMouseOutsideSlop || outsideSlop(x, y, mDragStartX, mDragStartY)) - { - if (!mMouseOutsideSlop && !(mask & MASK_SHIFT) && !(mask & MASK_CONTROL)) - { - // just started rect select, and not adding to current selection - LLSelectMgr::getInstance()->deselectAll(); - } - mMouseOutsideSlop = true; - mDragEndX = x; - mDragEndY = y; - - handleRectangleSelection(x, y, mask); - } - else - { - return LLToolSelect::handleHover(x, y, mask); - } - - LL_DEBUGS("UserInput") << "hover handled by LLToolSelectRect (active)" << LL_ENDL; - } - else - { - LL_DEBUGS("UserInput") << "hover handled by LLToolSelectRect (inactive)" << LL_ENDL; - } - - gViewerWindow->setCursor(UI_CURSOR_ARROW); - return true; -} - - -void LLToolSelectRect::draw() -{ - if( hasMouseCapture() && mMouseOutsideSlop) - { - if (gKeyboard->currentMask(true) == MASK_CONTROL) - { - gGL.color4f(1.f, 0.f, 0.f, 1.f); - } - else - { - gGL.color4f(1.f, 1.f, 0.f, 1.f); - } - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gl_rect_2d( - llmin(mDragStartX, mDragEndX), - llmax(mDragStartY, mDragEndY), - llmax(mDragStartX, mDragEndX), - llmin(mDragStartY, mDragEndY), - false); - if (gKeyboard->currentMask(true) == MASK_CONTROL) - { - gGL.color4f(1.f, 0.f, 0.f, 0.1f); - } - else - { - gGL.color4f(1.f, 1.f, 0.f, 0.1f); - } - gl_rect_2d( - llmin(mDragStartX, mDragEndX), - llmax(mDragStartY, mDragEndY), - llmax(mDragStartX, mDragEndX), - llmin(mDragStartY, mDragEndY)); - } -} - -// true if x,y outside small box around start_x,start_y -bool LLToolSelectRect::outsideSlop(S32 x, S32 y, S32 start_x, S32 start_y) -{ - S32 dx = x - start_x; - S32 dy = y - start_y; - - return (dx <= -SLOP_RADIUS || SLOP_RADIUS <= dx || dy <= -SLOP_RADIUS || SLOP_RADIUS <= dy); -} - - -// -// Static functions -// +/** + * @file lltoolselectrect.cpp + * @brief A tool to select multiple objects with a screen-space rectangle. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +// File includes +#include "lltoolselectrect.h" + +// Library includes +#include "llgl.h" +#include "llrender.h" + +// Viewer includes +#include "llviewercontrol.h" +#include "llui.h" +#include "llselectmgr.h" +#include "lltoolmgr.h" +#include "llviewerobject.h" +#include "llviewerobjectlist.h" +#include "llviewerwindow.h" +#include "llviewercamera.h" + +#include "llglheaders.h" + +// Globals +const S32 SLOP_RADIUS = 5; + + +// +// Member functions +// + +LLToolSelectRect::LLToolSelectRect( LLToolComposite* composite ) + : + LLToolSelect( composite ), + mDragStartX(0), + mDragStartY(0), + mDragEndX(0), + mDragEndY(0), + mDragLastWidth(0), + mDragLastHeight(0), + mMouseOutsideSlop(false) + +{ } + + +void dialog_refresh_all(void); + +bool LLToolSelectRect::handleMouseDown(S32 x, S32 y, MASK mask) +{ + bool pick_rigged = false; //gSavedSettings.getBOOL("AnimatedObjectsAllowLeftClick"); + handlePick(gViewerWindow->pickImmediate(x, y, true /* pick_transparent */, pick_rigged)); + + LLTool::handleMouseDown(x, y, mask); + + return mPick.getObject().notNull(); +} + +void LLToolSelectRect::handlePick(const LLPickInfo& pick) +{ + mPick = pick; + + // start dragging rectangle + setMouseCapture( true ); + + mDragStartX = pick.mMousePt.mX; + mDragStartY = pick.mMousePt.mY; + mDragEndX = pick.mMousePt.mX; + mDragEndY = pick.mMousePt.mY; + + mMouseOutsideSlop = false; +} + + +bool LLToolSelectRect::handleMouseUp(S32 x, S32 y, MASK mask) +{ + setMouseCapture( false ); + + if( mMouseOutsideSlop ) + { + mDragLastWidth = 0; + mDragLastHeight = 0; + + mMouseOutsideSlop = false; + + if (mask == MASK_CONTROL) + { + LLSelectMgr::getInstance()->deselectHighlightedObjects(); + } + else + { + LLSelectMgr::getInstance()->selectHighlightedObjects(); + } + return true; + } + else + { + return LLToolSelect::handleMouseUp(x, y, mask); + } +} + + +bool LLToolSelectRect::handleHover(S32 x, S32 y, MASK mask) +{ + if( hasMouseCapture() ) + { + if (mMouseOutsideSlop || outsideSlop(x, y, mDragStartX, mDragStartY)) + { + if (!mMouseOutsideSlop && !(mask & MASK_SHIFT) && !(mask & MASK_CONTROL)) + { + // just started rect select, and not adding to current selection + LLSelectMgr::getInstance()->deselectAll(); + } + mMouseOutsideSlop = true; + mDragEndX = x; + mDragEndY = y; + + handleRectangleSelection(x, y, mask); + } + else + { + return LLToolSelect::handleHover(x, y, mask); + } + + LL_DEBUGS("UserInput") << "hover handled by LLToolSelectRect (active)" << LL_ENDL; + } + else + { + LL_DEBUGS("UserInput") << "hover handled by LLToolSelectRect (inactive)" << LL_ENDL; + } + + gViewerWindow->setCursor(UI_CURSOR_ARROW); + return true; +} + + +void LLToolSelectRect::draw() +{ + if( hasMouseCapture() && mMouseOutsideSlop) + { + if (gKeyboard->currentMask(true) == MASK_CONTROL) + { + gGL.color4f(1.f, 0.f, 0.f, 1.f); + } + else + { + gGL.color4f(1.f, 1.f, 0.f, 1.f); + } + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gl_rect_2d( + llmin(mDragStartX, mDragEndX), + llmax(mDragStartY, mDragEndY), + llmax(mDragStartX, mDragEndX), + llmin(mDragStartY, mDragEndY), + false); + if (gKeyboard->currentMask(true) == MASK_CONTROL) + { + gGL.color4f(1.f, 0.f, 0.f, 0.1f); + } + else + { + gGL.color4f(1.f, 1.f, 0.f, 0.1f); + } + gl_rect_2d( + llmin(mDragStartX, mDragEndX), + llmax(mDragStartY, mDragEndY), + llmax(mDragStartX, mDragEndX), + llmin(mDragStartY, mDragEndY)); + } +} + +// true if x,y outside small box around start_x,start_y +bool LLToolSelectRect::outsideSlop(S32 x, S32 y, S32 start_x, S32 start_y) +{ + S32 dx = x - start_x; + S32 dy = y - start_y; + + return (dx <= -SLOP_RADIUS || SLOP_RADIUS <= dx || dy <= -SLOP_RADIUS || SLOP_RADIUS <= dy); +} + + +// +// Static functions +// diff --git a/indra/newview/lltoolselectrect.h b/indra/newview/lltoolselectrect.h index bf10324081..514e08d252 100644 --- a/indra/newview/lltoolselectrect.h +++ b/indra/newview/lltoolselectrect.h @@ -1,64 +1,64 @@ -/** - * @file lltoolselectrect.h - * @brief A tool to select multiple objects with a screen-space rectangle. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLTOOLSELECTRECT_H -#define LL_LLTOOLSELECTRECT_H - -#include "lltool.h" -#include "lltoolselect.h" - -class LLToolSelectRect -: public LLToolSelect -{ -public: - LLToolSelectRect( LLToolComposite* composite ); - - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - virtual bool handleHover(S32 x, S32 y, MASK mask); - virtual void draw(); // draw the select rectangle - - void handlePick(const LLPickInfo& pick); - -protected: - void handleRectangleSelection(S32 x, S32 y, MASK mask); // true if you selected one - bool outsideSlop(S32 x, S32 y, S32 start_x, S32 start_y); - -protected: - S32 mDragStartX; // screen coords, from left - S32 mDragStartY; // screen coords, from bottom - - S32 mDragEndX; - S32 mDragEndY; - - S32 mDragLastWidth; - S32 mDragLastHeight; - - bool mMouseOutsideSlop; // has mouse ever gone outside slop region? -}; - - -#endif +/** + * @file lltoolselectrect.h + * @brief A tool to select multiple objects with a screen-space rectangle. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLTOOLSELECTRECT_H +#define LL_LLTOOLSELECTRECT_H + +#include "lltool.h" +#include "lltoolselect.h" + +class LLToolSelectRect +: public LLToolSelect +{ +public: + LLToolSelectRect( LLToolComposite* composite ); + + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + virtual bool handleHover(S32 x, S32 y, MASK mask); + virtual void draw(); // draw the select rectangle + + void handlePick(const LLPickInfo& pick); + +protected: + void handleRectangleSelection(S32 x, S32 y, MASK mask); // true if you selected one + bool outsideSlop(S32 x, S32 y, S32 start_x, S32 start_y); + +protected: + S32 mDragStartX; // screen coords, from left + S32 mDragStartY; // screen coords, from bottom + + S32 mDragEndX; + S32 mDragEndY; + + S32 mDragLastWidth; + S32 mDragLastHeight; + + bool mMouseOutsideSlop; // has mouse ever gone outside slop region? +}; + + +#endif diff --git a/indra/newview/lltracker.cpp b/indra/newview/lltracker.cpp index 84069ed851..56263f1afe 100644 --- a/indra/newview/lltracker.cpp +++ b/indra/newview/lltracker.cpp @@ -1,859 +1,859 @@ -/** - * @file lltracker.cpp - * @brief Container for objects user is tracking. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -// library includes -#include "llcoord.h" -#include "llfontgl.h" -#include "llgl.h" -#include "llrender.h" -#include "llinventory.h" -#include "llinventorydefines.h" -#include "llpointer.h" -#include "llstring.h" -#include "lluuid.h" -#include "v3math.h" -#include "v3dmath.h" -#include "v4color.h" - -// viewer includes -#include "llappviewer.h" -#include "lltracker.h" -#include "llagent.h" -#include "llagentcamera.h" -#include "llcallingcard.h" -#include "llfloaterworldmap.h" -#include "llhudtext.h" -#include "llhudview.h" -#include "llinventorymodel.h" -#include "llinventoryobserver.h" -#include "lllandmarklist.h" -#include "llprogressview.h" -#include "llsky.h" -#include "llui.h" -#include "llviewercamera.h" -#include "llviewerinventory.h" -#include "llviewerwindow.h" -#include "llworld.h" -#include "llworldmapview.h" -#include "llviewercontrol.h" - -const F32 DESTINATION_REACHED_RADIUS = 3.0f; -const F32 DESTINATION_VISITED_RADIUS = 6.0f; - -// this last one is useful for when the landmark is -// very close to agent when tracking is turned on -const F32 DESTINATION_UNVISITED_RADIUS = 12.0f; - -const S32 ARROW_OFF_RADIUS_SQRD = 100; - -const S32 HUD_ARROW_SIZE = 32; - -// static -LLTracker *LLTracker::sTrackerp = NULL; -bool LLTracker::sCheesyBeacon = false; - -LLTracker::LLTracker() -: mTrackingStatus(TRACKING_NOTHING), - mTrackingLocationType(LOCATION_NOTHING), - mHUDArrowCenterX(0), - mHUDArrowCenterY(0), - mToolTip( "" ), - mTrackedLandmarkName(""), - mHasReachedLandmark(false), - mHasLandmarkPosition(false), - mLandmarkHasBeenVisited(false), - mTrackedLocationName( "" ), - mIsTrackingLocation(false), - mHasReachedLocation(false) -{ } - - -LLTracker::~LLTracker() -{ - purgeBeaconText(); -} - - -// static -void LLTracker::stopTracking(bool clear_ui) -{ - instance()->stopTrackingAll(clear_ui); -} - - -// static virtual -void LLTracker::drawHUDArrow() -{ - if (!LLWorld::instanceExists()) - { - return; - } - - static LLCachedControl render_beacon(gSavedSettings, "RenderTrackerBeacon", true); - if (!render_beacon) - { - return; - } - - if (gViewerWindow->getProgressView()->getVisible()) return; - - static LLUIColor map_track_color = LLUIColorTable::instance().getColor("MapTrackColor", LLColor4::white); - - /* tracking autopilot destination has been disabled - -- 2004.01.09, Leviathan - // Draw dot for autopilot target - if (gAgent.getAutoPilot()) - { - instance()->drawMarker( gAgent.getAutoPilotTargetGlobal(), map_track_color ); - return; - } - */ - switch (getTrackingStatus()) - { - case TRACKING_AVATAR: - // Tracked avatar - if(LLAvatarTracker::instance().haveTrackingInfo()) - { - instance()->drawMarker( LLAvatarTracker::instance().getGlobalPos(), map_track_color ); - } - break; - - case TRACKING_LANDMARK: - instance()->drawMarker( getTrackedPositionGlobal(), map_track_color ); - break; - - case TRACKING_LOCATION: - // HACK -- try to keep the location just above the terrain -#if 0 - // UNHACKED by CRO - keep location where the location is - instance()->mTrackedPositionGlobal.mdV[VZ] = - 0.9f * instance()->mTrackedPositionGlobal.mdV[VZ] - + 0.1f * (LLWorld::getInstance()->resolveLandHeightGlobal(getTrackedPositionGlobal()) + 1.5f); -#endif - instance()->mTrackedPositionGlobal.mdV[VZ] = llclamp((F32)instance()->mTrackedPositionGlobal.mdV[VZ], LLWorld::getInstance()->resolveLandHeightGlobal(getTrackedPositionGlobal()) + 1.5f, (F32)instance()->getTrackedPositionGlobal().mdV[VZ]); - instance()->drawMarker( getTrackedPositionGlobal(), map_track_color ); - break; - - default: - break; - } -} - - -// static -void LLTracker::render3D() -{ - if (!gFloaterWorldMap || !gSavedSettings.getBOOL("RenderTrackerBeacon")) - { - return; - } - - static LLUIColor map_track_color = LLUIColorTable::instance().getColor("MapTrackColor", LLColor4::white); - static LLUIColor map_track_color_under = LLUIColorTable::instance().getColor("MapTrackColorUnder", LLColor4::white); - - // Arbitary location beacon - if( instance()->mIsTrackingLocation ) - { - if (!instance()->mBeaconText) - { - instance()->mBeaconText = (LLHUDText *)LLHUDObject::addHUDObject(LLHUDObject::LL_HUD_TEXT); - instance()->mBeaconText->setDoFade(false); - } - - LLVector3d pos_global = instance()->mTrackedPositionGlobal; - // (z-attenuation < 1) means compute "shorter" distance in z-axis, - // so cancel tracking even if avatar is a little above or below. - F32 dist = gFloaterWorldMap->getDistanceToDestination(pos_global, 0.5f); - if (dist < DESTINATION_REACHED_RADIUS) - { - instance()->stopTrackingLocation(false,true); - } - else - { - renderBeacon( instance()->mTrackedPositionGlobal, map_track_color, map_track_color_under, - instance()->mBeaconText, instance()->mTrackedLocationName ); - } - } - - // Landmark beacon - else if( !instance()->mTrackedLandmarkAssetID.isNull() ) - { - if (!instance()->mBeaconText) - { - instance()->mBeaconText = (LLHUDText *)LLHUDObject::addHUDObject(LLHUDObject::LL_HUD_TEXT); - instance()->mBeaconText->setDoFade(false); - } - - if (instance()->mHasLandmarkPosition) - { - F32 dist = gFloaterWorldMap->getDistanceToDestination(instance()->mTrackedPositionGlobal, 1.0f); - - if ( !instance()->mLandmarkHasBeenVisited - && dist < DESTINATION_VISITED_RADIUS ) - { - // its close enough ==> flag as visited - instance()->setLandmarkVisited(); - } - - if ( !instance()->mHasReachedLandmark - && dist < DESTINATION_REACHED_RADIUS ) - { - // its VERY CLOSE ==> automatically stop tracking - instance()->stopTrackingLandmark(); - } - else - { - if ( instance()->mHasReachedLandmark - && dist > DESTINATION_UNVISITED_RADIUS ) - { - // this is so that landmark beacons don't immediately - // disappear when they're created only a few meters - // away, yet disappear when the agent wanders away - // and back again - instance()->mHasReachedLandmark = false; - } - renderBeacon( instance()->mTrackedPositionGlobal, map_track_color, map_track_color_under, - instance()->mBeaconText, instance()->mTrackedLandmarkName ); - } - } - else - { - // probably just finished downloading the asset - instance()->cacheLandmarkPosition(); - } - } - else - { - // Avatar beacon - LLAvatarTracker& av_tracker = LLAvatarTracker::instance(); - if(av_tracker.haveTrackingInfo()) - { - if (!instance()->mBeaconText) - { - instance()->mBeaconText = (LLHUDText *)LLHUDObject::addHUDObject(LLHUDObject::LL_HUD_TEXT); - instance()->mBeaconText->setDoFade(false); - } - - F32 dist = gFloaterWorldMap->getDistanceToDestination(instance()->getTrackedPositionGlobal(), 0.0f); - if (dist < DESTINATION_REACHED_RADIUS) - { - instance()->stopTrackingAvatar(); - } - else - { - renderBeacon( av_tracker.getGlobalPos(), map_track_color, map_track_color_under, - instance()->mBeaconText, av_tracker.getName() ); - } - } - else - { - bool stop_tracking = false; - const LLUUID& avatar_id = av_tracker.getAvatarID(); - if(avatar_id.isNull()) - { - stop_tracking = true; - } - else - { - const LLRelationship* buddy = av_tracker.getBuddyInfo(avatar_id); - if(buddy && !buddy->isOnline() && !gAgent.isGodlike()) - { - stop_tracking = true; - } - if(!buddy && !gAgent.isGodlike()) - { - stop_tracking = true; - } - } - if(stop_tracking) - { - instance()->stopTrackingAvatar(); - } - } - } -} - - -// static -void LLTracker::trackAvatar( const LLUUID& avatar_id, const std::string& name ) -{ - instance()->stopTrackingLandmark(); - instance()->stopTrackingLocation(); - - LLAvatarTracker::instance().track( avatar_id, name ); - instance()->mTrackingStatus = TRACKING_AVATAR; - instance()->mLabel = name; - instance()->mToolTip = ""; -} - - -// static -void LLTracker::trackLandmark( const LLUUID& asset_id, const LLUUID& item_id, const std::string& name) -{ - instance()->stopTrackingAvatar(); - instance()->stopTrackingLocation(); - - instance()->mTrackedLandmarkAssetID = asset_id; - instance()->mTrackedLandmarkItemID = item_id; - instance()->mTrackedLandmarkName = name; - instance()->cacheLandmarkPosition(); - instance()->mTrackingStatus = TRACKING_LANDMARK; - instance()->mLabel = name; - instance()->mToolTip = ""; -} - - -// static -void LLTracker::trackLocation(const LLVector3d& pos_global, const std::string& full_name, const std::string& tooltip, ETrackingLocationType location_type) -{ - instance()->stopTrackingAvatar(); - instance()->stopTrackingLandmark(); - - instance()->mTrackedPositionGlobal = pos_global; - instance()->mTrackedLocationName = full_name; - instance()->mIsTrackingLocation = true; - instance()->mTrackingStatus = TRACKING_LOCATION; - instance()->mTrackingLocationType = location_type; - instance()->mLabel = full_name; - instance()->mToolTip = tooltip; -} - - -// static -bool LLTracker::handleMouseDown(S32 x, S32 y) -{ - bool eat_mouse_click = false; - // fortunately, we can always compute the tracking arrow center - S32 dist_sqrd = (x - instance()->mHUDArrowCenterX) * (x - instance()->mHUDArrowCenterX) + - (y - instance()->mHUDArrowCenterY) * (y - instance()->mHUDArrowCenterY); - if (dist_sqrd < ARROW_OFF_RADIUS_SQRD) - { - /* tracking autopilot destination has been disabled - -- 2004.01.09, Leviathan - // turn off tracking - if (gAgent.getAutoPilot()) - { - gAgent.stopAutoPilot(true); // true because cancelled by user - eat_mouse_click = true; - } - */ - if (getTrackingStatus()) - { - instance()->stopTrackingAll(); - eat_mouse_click = true; - } - } - return eat_mouse_click; -} - - -// static -LLVector3d LLTracker::getTrackedPositionGlobal() -{ - LLVector3d pos_global; - switch (getTrackingStatus()) - { - case TRACKING_AVATAR: - { - LLAvatarTracker& av_tracker = LLAvatarTracker::instance(); - if (av_tracker.haveTrackingInfo()) - { - pos_global = av_tracker.getGlobalPos(); } - break; - } - case TRACKING_LANDMARK: - if( instance()->mHasLandmarkPosition ) - { - pos_global = instance()->mTrackedPositionGlobal; - } - break; - case TRACKING_LOCATION: - pos_global = instance()->mTrackedPositionGlobal; - break; - default: - break; - } - return pos_global; -} - - -// static -bool LLTracker::hasLandmarkPosition() -{ - if (!instance()->mHasLandmarkPosition) - { - // maybe we just received the landmark position info - instance()->cacheLandmarkPosition(); - } - return instance()->mHasLandmarkPosition; -} - - -// static -const std::string& LLTracker::getTrackedLocationName() -{ - return instance()->mTrackedLocationName; -} - -F32 pulse_func(F32 t, F32 z, bool tracking_avatar, std::string direction) -{ - if (!LLTracker::sCheesyBeacon) - { - return 0.f; - } - - t *= F_PI; - if ("DOWN" == direction) - { - z += t*64.f - 256.f; - } - else - { - z -= t*64.f - 256.f; - } - - F32 a = cosf(z*F_PI/512.f)*10.0f; - a = llmax(a, 9.9f); - a -= 9.9f; - a *= 10.f; - return a; -} - -void draw_shockwave(F32 center_z, F32 t, S32 steps, LLColor4 color) -{ - if (!LLTracker::sCheesyBeacon) - { - return; - } - - t *= 0.6284f/F_PI; - - t -= (F32) (S32) t; - - t = llmax(t, 0.5f); - t -= 0.5f; - t *= 2.0f; - - F32 radius = t*16536.f; - - // Inexact, but reasonably fast. - F32 delta = F_TWO_PI / steps; - F32 sin_delta = sin( delta ); - F32 cos_delta = cos( delta ); - F32 x = radius; - F32 y = 0.f; - - LLColor4 ccol = LLColor4(1,1,1,(1.f-t)*0.25f); - gGL.begin(LLRender::TRIANGLE_FAN); - gGL.color4fv(ccol.mV); - gGL.vertex3f(0.f, 0.f, center_z); - // make sure circle is complete - steps += 1; - - color.mV[3] = (1.f-t*t); - - gGL.color4fv(color.mV); - while( steps-- ) - { - // Successive rotations - gGL.vertex3f( x, y, center_z ); - F32 x_new = x * cos_delta - y * sin_delta; - y = x * sin_delta + y * cos_delta; - x = x_new; - } - gGL.end(); -} - -void LLTracker::drawBeacon(LLVector3 pos_agent, std::string direction, LLColor4 fogged_color, F32 dist) -{ - const U32 BEACON_VERTS = 256; - F32 step; - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - - if ("DOWN" == direction) - { - gGL.translatef(pos_agent.mV[0], pos_agent.mV[1], pos_agent.mV[2]); - draw_shockwave(1024.f, gRenderStartTime.getElapsedTimeF32(), 32, fogged_color); - step = (5020.0f - pos_agent.mV[2]) / BEACON_VERTS; - } - else - { - gGL.translatef(pos_agent.mV[0], pos_agent.mV[1], 0); - step = pos_agent.mV[2] / BEACON_VERTS; - } - - gGL.color4fv(fogged_color.mV); - - LLVector3 x_axis = LLViewerCamera::getInstance()->getLeftAxis(); - F32 t = gRenderStartTime.getElapsedTimeF32(); - - for (U32 i = 0; i < BEACON_VERTS; i++) - { - F32 x = x_axis.mV[0]; - F32 y = x_axis.mV[1]; - - F32 z = i * step; - F32 z_next = (i+1)*step; - - bool tracking_avatar = getTrackingStatus() == TRACKING_AVATAR; - F32 a = pulse_func(t, z, tracking_avatar, direction); - F32 an = pulse_func(t, z_next, tracking_avatar, direction); - - LLColor4 c_col = fogged_color + LLColor4(a,a,a,a); - LLColor4 col_next = fogged_color + LLColor4(an,an,an,an); - LLColor4 col_edge = fogged_color * LLColor4(a,a,a,0.0f); - LLColor4 col_edge_next = fogged_color * LLColor4(an,an,an,0.0f); - - a *= 2.f; - a += 1.0f; - - an *= 2.f; - an += 1.0f; - - gGL.begin(LLRender::TRIANGLE_STRIP); - gGL.color4fv(col_edge.mV); - gGL.vertex3f(-x*a, -y*a, z); - gGL.color4fv(col_edge_next.mV); - gGL.vertex3f(-x*an, -y*an, z_next); - - gGL.color4fv(c_col.mV); - gGL.vertex3f(0, 0, z); - gGL.color4fv(col_next.mV); - gGL.vertex3f(0, 0, z_next); - - gGL.color4fv(col_edge.mV); - gGL.vertex3f(x*a,y*a,z); - gGL.color4fv(col_edge_next.mV); - gGL.vertex3f(x*an,y*an,z_next); - gGL.end(); - } - gGL.popMatrix(); -} - -// static -void LLTracker::renderBeacon(LLVector3d pos_global, - const LLColor4& color, - const LLColor4& color_under, - LLHUDText* hud_textp, - const std::string& label ) -{ - sCheesyBeacon = gSavedSettings.getBOOL("CheesyBeacon"); - LLVector3d to_vec = pos_global - gAgentCamera.getCameraPositionGlobal(); - - F32 dist = (F32)to_vec.magVec(); - F32 color_frac = 1.f; - if (dist > 0.99f * LLViewerCamera::getInstance()->getFar()) - { - color_frac = 0.4f; - // pos_global = gAgentCamera.getCameraPositionGlobal() + 0.99f*(LLViewerCamera::getInstance()->getFar()/dist)*to_vec; - } - else - { - color_frac = 1.f - 0.6f*(dist/LLViewerCamera::getInstance()->getFar()); - } - - LLColor4 fogged_color = color_frac * color + (1 - color_frac)*gSky.getSkyFogColor(); - LLColor4 fogged_color_under = color_frac * color_under + (1 - color_frac) * gSky.getSkyFogColor(); - - F32 FADE_DIST = 3.f; - fogged_color.mV[3] = llmax(0.2f, llmin(0.5f,(dist-FADE_DIST)/FADE_DIST)); - fogged_color_under.mV[3] = llmax(0.2f, llmin(0.5f,(dist-FADE_DIST)/FADE_DIST)); - - LLVector3 pos_agent = gAgent.getPosAgentFromGlobal(pos_global); - - LLGLSTracker gls_tracker; - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - LLGLDisable cull_face(GL_CULL_FACE); - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); - - LLTracker::drawBeacon(pos_agent, "DOWN", fogged_color, dist); - LLTracker::drawBeacon(pos_agent, "UP", fogged_color_under, dist); - - std::string text; - text = llformat( "%.0f m", to_vec.magVec()); - - std::string str; - str += label; - str += '\n'; - str += text; - - hud_textp->setFont(LLFontGL::getFontSansSerif()); - hud_textp->setZCompare(false); - hud_textp->setColor(LLColor4(1.f, 1.f, 1.f, llmax(0.2f, llmin(1.f,(dist-FADE_DIST)/FADE_DIST)))); - - hud_textp->setString(str); - hud_textp->setVertAlignment(LLHUDText::ALIGN_VERT_CENTER); - hud_textp->setPositionAgent(pos_agent); -} - - -void LLTracker::stopTrackingAll(bool clear_ui) -{ - switch (mTrackingStatus) - { - case TRACKING_AVATAR : - stopTrackingAvatar(clear_ui); - break; - case TRACKING_LANDMARK : - stopTrackingLandmark(clear_ui); - break; - case TRACKING_LOCATION : - stopTrackingLocation(clear_ui); - break; - default: - mTrackingStatus = TRACKING_NOTHING; - break; - } -} - - -void LLTracker::stopTrackingAvatar(bool clear_ui) -{ - LLAvatarTracker& av_tracker = LLAvatarTracker::instance(); - if( !av_tracker.getAvatarID().isNull() ) - { - av_tracker.untrack( av_tracker.getAvatarID() ); - } - - purgeBeaconText(); - gFloaterWorldMap->clearAvatarSelection(clear_ui); - mTrackingStatus = TRACKING_NOTHING; -} - - -void LLTracker::stopTrackingLandmark(bool clear_ui) -{ - purgeBeaconText(); - mTrackedLandmarkAssetID.setNull(); - mTrackedLandmarkItemID.setNull(); - mTrackedLandmarkName.assign(""); - mTrackedPositionGlobal.zeroVec(); - mHasLandmarkPosition = false; - mHasReachedLandmark = false; - mLandmarkHasBeenVisited = true; - gFloaterWorldMap->clearLandmarkSelection(clear_ui); - mTrackingStatus = TRACKING_NOTHING; -} - - -void LLTracker::stopTrackingLocation(bool clear_ui, bool dest_reached) -{ - purgeBeaconText(); - mTrackedLocationName.assign(""); - mIsTrackingLocation = false; - mTrackedPositionGlobal.zeroVec(); - gFloaterWorldMap->clearLocationSelection(clear_ui, dest_reached); - mTrackingStatus = TRACKING_NOTHING; - mTrackingLocationType = LOCATION_NOTHING; -} - -void LLTracker::clearFocus() -{ - instance()->mTrackingStatus = TRACKING_NOTHING; -} - -void LLTracker::drawMarker(const LLVector3d& pos_global, const LLColor4& color) -{ - // get position - LLVector3 pos_local = gAgent.getPosAgentFromGlobal(pos_global); - - // check in frustum - LLCoordGL screen; - S32 x = 0; - S32 y = 0; - const bool CLAMP = true; - - if (LLViewerCamera::getInstance()->projectPosAgentToScreen(pos_local, screen, CLAMP) - || LLViewerCamera::getInstance()->projectPosAgentToScreenEdge(pos_local, screen) ) - { - gHUDView->screenPointToLocal(screen.mX, screen.mY, &x, &y); - - // the center of the rendered position of the arrow obeys - // the following rules: - // (1) it lies on an ellipse centered on the target position - // (2) it lies on the line between the target and the window center - // (3) right now the radii of the ellipse are fixed, but eventually - // they will be a function of the target text - // - // from those rules we can compute the position of the - // lower left corner of the image - LLRect rect = gHUDView->getRect(); - S32 x_center = lltrunc(0.5f * (F32)rect.getWidth()); - S32 y_center = lltrunc(0.5f * (F32)rect.getHeight()); - x = x - x_center; // x and y relative to center - y = y - y_center; - F32 dist = sqrt((F32)(x*x + y*y)); - S32 half_arrow_size = lltrunc(0.5f * HUD_ARROW_SIZE); - if (dist > 0.f) - { - const F32 ARROW_ELLIPSE_RADIUS_X = 2 * HUD_ARROW_SIZE; - const F32 ARROW_ELLIPSE_RADIUS_Y = HUD_ARROW_SIZE; - - // compute where the arrow should be - F32 x_target = (F32)(x + x_center) - (ARROW_ELLIPSE_RADIUS_X * ((F32)x / dist) ); - F32 y_target = (F32)(y + y_center) - (ARROW_ELLIPSE_RADIUS_Y * ((F32)y / dist) ); - - // keep the arrow within the window - F32 x_clamped = llclamp( x_target, (F32)half_arrow_size, (F32)(rect.getWidth() - half_arrow_size)); - F32 y_clamped = llclamp( y_target, (F32)half_arrow_size, (F32)(rect.getHeight() - half_arrow_size)); - - F32 slope = (F32)(y) / (F32)(x); - F32 window_ratio = (F32)(rect.getHeight() - HUD_ARROW_SIZE) / (F32)(rect.getWidth() - HUD_ARROW_SIZE); - - // if the arrow has been clamped on one axis - // then we need to compute the other axis - if (llabs(slope) > window_ratio) - { - if (y_clamped != (F32)y_target) - { - // clamp by y - x_clamped = (y_clamped - (F32)y_center) / slope + (F32)x_center; - } - } - else if (x_clamped != (F32)x_target) - { - // clamp by x - y_clamped = (x_clamped - (F32)x_center) * slope + (F32)y_center; - } - mHUDArrowCenterX = lltrunc(x_clamped); - mHUDArrowCenterY = lltrunc(y_clamped); - } - else - { - // recycle the old values - x = mHUDArrowCenterX - x_center; - y = mHUDArrowCenterY - y_center; - } - - F32 angle = atan2( (F32)y, (F32)x ); - - gl_draw_scaled_rotated_image(mHUDArrowCenterX - half_arrow_size, - mHUDArrowCenterY - half_arrow_size, - HUD_ARROW_SIZE, HUD_ARROW_SIZE, - RAD_TO_DEG * angle, - LLWorldMapView::sTrackArrowImage->getImage(), - color); - } -} - - -void LLTracker::setLandmarkVisited() -{ - // poke the inventory item - if (!mTrackedLandmarkItemID.isNull()) - { - LLInventoryItem* i = gInventory.getItem( mTrackedLandmarkItemID ); - LLViewerInventoryItem* item = (LLViewerInventoryItem*)i; - if ( item - && !(item->getFlags()&LLInventoryItemFlags::II_FLAGS_LANDMARK_VISITED)) - { - U32 flags = item->getFlags(); - flags |= LLInventoryItemFlags::II_FLAGS_LANDMARK_VISITED; - item->setFlags(flags); - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("ChangeInventoryItemFlags"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->nextBlock("InventoryData"); - msg->addUUID("ItemID", mTrackedLandmarkItemID); - msg->addU32("Flags", flags); - gAgent.sendReliableMessage(); - - LLInventoryModel::LLCategoryUpdate up(item->getParentUUID(), 0); - gInventory.accountForUpdate(up); - - // need to communicate that the icon needs to change... - gInventory.addChangedMask(LLInventoryObserver::INTERNAL, item->getUUID()); - gInventory.notifyObservers(); - } - } -} - - -void LLTracker::cacheLandmarkPosition() -{ - // the landmark asset download may have finished, in which case - // we'll now be able to figure out where we're trying to go - bool found_landmark = false; - if( mTrackedLandmarkAssetID == LLFloaterWorldMap::getHomeID()) - { - LLVector3d pos_global; - if ( gAgent.getHomePosGlobal( &mTrackedPositionGlobal )) - { - found_landmark = true; - } - else - { - LL_WARNS() << "LLTracker couldn't find home pos" << LL_ENDL; - mTrackedLandmarkAssetID.setNull(); - mTrackedLandmarkItemID.setNull(); - } - } - else - { - LLLandmark* landmark = gLandmarkList.getAsset(mTrackedLandmarkAssetID); - if(landmark && landmark->getGlobalPos(mTrackedPositionGlobal)) - { - found_landmark = true; - - // cache the object's visitation status - mLandmarkHasBeenVisited = false; - LLInventoryItem* item = gInventory.getItem(mTrackedLandmarkItemID); - if ( item - && item->getFlags()&LLInventoryItemFlags::II_FLAGS_LANDMARK_VISITED) - { - mLandmarkHasBeenVisited = true; - } - } - } - if ( found_landmark && gFloaterWorldMap ) - { - mHasReachedLandmark = false; - F32 dist = gFloaterWorldMap->getDistanceToDestination(mTrackedPositionGlobal, 1.0f); - if ( dist < DESTINATION_UNVISITED_RADIUS ) - { - mHasReachedLandmark = true; - } - mHasLandmarkPosition = true; - } - mHasLandmarkPosition = found_landmark; -} - - -void LLTracker::purgeBeaconText() -{ - if(!mBeaconText.isNull()) - { - mBeaconText->markDead(); - mBeaconText = NULL; - } -} - +/** + * @file lltracker.cpp + * @brief Container for objects user is tracking. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +// library includes +#include "llcoord.h" +#include "llfontgl.h" +#include "llgl.h" +#include "llrender.h" +#include "llinventory.h" +#include "llinventorydefines.h" +#include "llpointer.h" +#include "llstring.h" +#include "lluuid.h" +#include "v3math.h" +#include "v3dmath.h" +#include "v4color.h" + +// viewer includes +#include "llappviewer.h" +#include "lltracker.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llcallingcard.h" +#include "llfloaterworldmap.h" +#include "llhudtext.h" +#include "llhudview.h" +#include "llinventorymodel.h" +#include "llinventoryobserver.h" +#include "lllandmarklist.h" +#include "llprogressview.h" +#include "llsky.h" +#include "llui.h" +#include "llviewercamera.h" +#include "llviewerinventory.h" +#include "llviewerwindow.h" +#include "llworld.h" +#include "llworldmapview.h" +#include "llviewercontrol.h" + +const F32 DESTINATION_REACHED_RADIUS = 3.0f; +const F32 DESTINATION_VISITED_RADIUS = 6.0f; + +// this last one is useful for when the landmark is +// very close to agent when tracking is turned on +const F32 DESTINATION_UNVISITED_RADIUS = 12.0f; + +const S32 ARROW_OFF_RADIUS_SQRD = 100; + +const S32 HUD_ARROW_SIZE = 32; + +// static +LLTracker *LLTracker::sTrackerp = NULL; +bool LLTracker::sCheesyBeacon = false; + +LLTracker::LLTracker() +: mTrackingStatus(TRACKING_NOTHING), + mTrackingLocationType(LOCATION_NOTHING), + mHUDArrowCenterX(0), + mHUDArrowCenterY(0), + mToolTip( "" ), + mTrackedLandmarkName(""), + mHasReachedLandmark(false), + mHasLandmarkPosition(false), + mLandmarkHasBeenVisited(false), + mTrackedLocationName( "" ), + mIsTrackingLocation(false), + mHasReachedLocation(false) +{ } + + +LLTracker::~LLTracker() +{ + purgeBeaconText(); +} + + +// static +void LLTracker::stopTracking(bool clear_ui) +{ + instance()->stopTrackingAll(clear_ui); +} + + +// static virtual +void LLTracker::drawHUDArrow() +{ + if (!LLWorld::instanceExists()) + { + return; + } + + static LLCachedControl render_beacon(gSavedSettings, "RenderTrackerBeacon", true); + if (!render_beacon) + { + return; + } + + if (gViewerWindow->getProgressView()->getVisible()) return; + + static LLUIColor map_track_color = LLUIColorTable::instance().getColor("MapTrackColor", LLColor4::white); + + /* tracking autopilot destination has been disabled + -- 2004.01.09, Leviathan + // Draw dot for autopilot target + if (gAgent.getAutoPilot()) + { + instance()->drawMarker( gAgent.getAutoPilotTargetGlobal(), map_track_color ); + return; + } + */ + switch (getTrackingStatus()) + { + case TRACKING_AVATAR: + // Tracked avatar + if(LLAvatarTracker::instance().haveTrackingInfo()) + { + instance()->drawMarker( LLAvatarTracker::instance().getGlobalPos(), map_track_color ); + } + break; + + case TRACKING_LANDMARK: + instance()->drawMarker( getTrackedPositionGlobal(), map_track_color ); + break; + + case TRACKING_LOCATION: + // HACK -- try to keep the location just above the terrain +#if 0 + // UNHACKED by CRO - keep location where the location is + instance()->mTrackedPositionGlobal.mdV[VZ] = + 0.9f * instance()->mTrackedPositionGlobal.mdV[VZ] + + 0.1f * (LLWorld::getInstance()->resolveLandHeightGlobal(getTrackedPositionGlobal()) + 1.5f); +#endif + instance()->mTrackedPositionGlobal.mdV[VZ] = llclamp((F32)instance()->mTrackedPositionGlobal.mdV[VZ], LLWorld::getInstance()->resolveLandHeightGlobal(getTrackedPositionGlobal()) + 1.5f, (F32)instance()->getTrackedPositionGlobal().mdV[VZ]); + instance()->drawMarker( getTrackedPositionGlobal(), map_track_color ); + break; + + default: + break; + } +} + + +// static +void LLTracker::render3D() +{ + if (!gFloaterWorldMap || !gSavedSettings.getBOOL("RenderTrackerBeacon")) + { + return; + } + + static LLUIColor map_track_color = LLUIColorTable::instance().getColor("MapTrackColor", LLColor4::white); + static LLUIColor map_track_color_under = LLUIColorTable::instance().getColor("MapTrackColorUnder", LLColor4::white); + + // Arbitary location beacon + if( instance()->mIsTrackingLocation ) + { + if (!instance()->mBeaconText) + { + instance()->mBeaconText = (LLHUDText *)LLHUDObject::addHUDObject(LLHUDObject::LL_HUD_TEXT); + instance()->mBeaconText->setDoFade(false); + } + + LLVector3d pos_global = instance()->mTrackedPositionGlobal; + // (z-attenuation < 1) means compute "shorter" distance in z-axis, + // so cancel tracking even if avatar is a little above or below. + F32 dist = gFloaterWorldMap->getDistanceToDestination(pos_global, 0.5f); + if (dist < DESTINATION_REACHED_RADIUS) + { + instance()->stopTrackingLocation(false,true); + } + else + { + renderBeacon( instance()->mTrackedPositionGlobal, map_track_color, map_track_color_under, + instance()->mBeaconText, instance()->mTrackedLocationName ); + } + } + + // Landmark beacon + else if( !instance()->mTrackedLandmarkAssetID.isNull() ) + { + if (!instance()->mBeaconText) + { + instance()->mBeaconText = (LLHUDText *)LLHUDObject::addHUDObject(LLHUDObject::LL_HUD_TEXT); + instance()->mBeaconText->setDoFade(false); + } + + if (instance()->mHasLandmarkPosition) + { + F32 dist = gFloaterWorldMap->getDistanceToDestination(instance()->mTrackedPositionGlobal, 1.0f); + + if ( !instance()->mLandmarkHasBeenVisited + && dist < DESTINATION_VISITED_RADIUS ) + { + // its close enough ==> flag as visited + instance()->setLandmarkVisited(); + } + + if ( !instance()->mHasReachedLandmark + && dist < DESTINATION_REACHED_RADIUS ) + { + // its VERY CLOSE ==> automatically stop tracking + instance()->stopTrackingLandmark(); + } + else + { + if ( instance()->mHasReachedLandmark + && dist > DESTINATION_UNVISITED_RADIUS ) + { + // this is so that landmark beacons don't immediately + // disappear when they're created only a few meters + // away, yet disappear when the agent wanders away + // and back again + instance()->mHasReachedLandmark = false; + } + renderBeacon( instance()->mTrackedPositionGlobal, map_track_color, map_track_color_under, + instance()->mBeaconText, instance()->mTrackedLandmarkName ); + } + } + else + { + // probably just finished downloading the asset + instance()->cacheLandmarkPosition(); + } + } + else + { + // Avatar beacon + LLAvatarTracker& av_tracker = LLAvatarTracker::instance(); + if(av_tracker.haveTrackingInfo()) + { + if (!instance()->mBeaconText) + { + instance()->mBeaconText = (LLHUDText *)LLHUDObject::addHUDObject(LLHUDObject::LL_HUD_TEXT); + instance()->mBeaconText->setDoFade(false); + } + + F32 dist = gFloaterWorldMap->getDistanceToDestination(instance()->getTrackedPositionGlobal(), 0.0f); + if (dist < DESTINATION_REACHED_RADIUS) + { + instance()->stopTrackingAvatar(); + } + else + { + renderBeacon( av_tracker.getGlobalPos(), map_track_color, map_track_color_under, + instance()->mBeaconText, av_tracker.getName() ); + } + } + else + { + bool stop_tracking = false; + const LLUUID& avatar_id = av_tracker.getAvatarID(); + if(avatar_id.isNull()) + { + stop_tracking = true; + } + else + { + const LLRelationship* buddy = av_tracker.getBuddyInfo(avatar_id); + if(buddy && !buddy->isOnline() && !gAgent.isGodlike()) + { + stop_tracking = true; + } + if(!buddy && !gAgent.isGodlike()) + { + stop_tracking = true; + } + } + if(stop_tracking) + { + instance()->stopTrackingAvatar(); + } + } + } +} + + +// static +void LLTracker::trackAvatar( const LLUUID& avatar_id, const std::string& name ) +{ + instance()->stopTrackingLandmark(); + instance()->stopTrackingLocation(); + + LLAvatarTracker::instance().track( avatar_id, name ); + instance()->mTrackingStatus = TRACKING_AVATAR; + instance()->mLabel = name; + instance()->mToolTip = ""; +} + + +// static +void LLTracker::trackLandmark( const LLUUID& asset_id, const LLUUID& item_id, const std::string& name) +{ + instance()->stopTrackingAvatar(); + instance()->stopTrackingLocation(); + + instance()->mTrackedLandmarkAssetID = asset_id; + instance()->mTrackedLandmarkItemID = item_id; + instance()->mTrackedLandmarkName = name; + instance()->cacheLandmarkPosition(); + instance()->mTrackingStatus = TRACKING_LANDMARK; + instance()->mLabel = name; + instance()->mToolTip = ""; +} + + +// static +void LLTracker::trackLocation(const LLVector3d& pos_global, const std::string& full_name, const std::string& tooltip, ETrackingLocationType location_type) +{ + instance()->stopTrackingAvatar(); + instance()->stopTrackingLandmark(); + + instance()->mTrackedPositionGlobal = pos_global; + instance()->mTrackedLocationName = full_name; + instance()->mIsTrackingLocation = true; + instance()->mTrackingStatus = TRACKING_LOCATION; + instance()->mTrackingLocationType = location_type; + instance()->mLabel = full_name; + instance()->mToolTip = tooltip; +} + + +// static +bool LLTracker::handleMouseDown(S32 x, S32 y) +{ + bool eat_mouse_click = false; + // fortunately, we can always compute the tracking arrow center + S32 dist_sqrd = (x - instance()->mHUDArrowCenterX) * (x - instance()->mHUDArrowCenterX) + + (y - instance()->mHUDArrowCenterY) * (y - instance()->mHUDArrowCenterY); + if (dist_sqrd < ARROW_OFF_RADIUS_SQRD) + { + /* tracking autopilot destination has been disabled + -- 2004.01.09, Leviathan + // turn off tracking + if (gAgent.getAutoPilot()) + { + gAgent.stopAutoPilot(true); // true because cancelled by user + eat_mouse_click = true; + } + */ + if (getTrackingStatus()) + { + instance()->stopTrackingAll(); + eat_mouse_click = true; + } + } + return eat_mouse_click; +} + + +// static +LLVector3d LLTracker::getTrackedPositionGlobal() +{ + LLVector3d pos_global; + switch (getTrackingStatus()) + { + case TRACKING_AVATAR: + { + LLAvatarTracker& av_tracker = LLAvatarTracker::instance(); + if (av_tracker.haveTrackingInfo()) + { + pos_global = av_tracker.getGlobalPos(); } + break; + } + case TRACKING_LANDMARK: + if( instance()->mHasLandmarkPosition ) + { + pos_global = instance()->mTrackedPositionGlobal; + } + break; + case TRACKING_LOCATION: + pos_global = instance()->mTrackedPositionGlobal; + break; + default: + break; + } + return pos_global; +} + + +// static +bool LLTracker::hasLandmarkPosition() +{ + if (!instance()->mHasLandmarkPosition) + { + // maybe we just received the landmark position info + instance()->cacheLandmarkPosition(); + } + return instance()->mHasLandmarkPosition; +} + + +// static +const std::string& LLTracker::getTrackedLocationName() +{ + return instance()->mTrackedLocationName; +} + +F32 pulse_func(F32 t, F32 z, bool tracking_avatar, std::string direction) +{ + if (!LLTracker::sCheesyBeacon) + { + return 0.f; + } + + t *= F_PI; + if ("DOWN" == direction) + { + z += t*64.f - 256.f; + } + else + { + z -= t*64.f - 256.f; + } + + F32 a = cosf(z*F_PI/512.f)*10.0f; + a = llmax(a, 9.9f); + a -= 9.9f; + a *= 10.f; + return a; +} + +void draw_shockwave(F32 center_z, F32 t, S32 steps, LLColor4 color) +{ + if (!LLTracker::sCheesyBeacon) + { + return; + } + + t *= 0.6284f/F_PI; + + t -= (F32) (S32) t; + + t = llmax(t, 0.5f); + t -= 0.5f; + t *= 2.0f; + + F32 radius = t*16536.f; + + // Inexact, but reasonably fast. + F32 delta = F_TWO_PI / steps; + F32 sin_delta = sin( delta ); + F32 cos_delta = cos( delta ); + F32 x = radius; + F32 y = 0.f; + + LLColor4 ccol = LLColor4(1,1,1,(1.f-t)*0.25f); + gGL.begin(LLRender::TRIANGLE_FAN); + gGL.color4fv(ccol.mV); + gGL.vertex3f(0.f, 0.f, center_z); + // make sure circle is complete + steps += 1; + + color.mV[3] = (1.f-t*t); + + gGL.color4fv(color.mV); + while( steps-- ) + { + // Successive rotations + gGL.vertex3f( x, y, center_z ); + F32 x_new = x * cos_delta - y * sin_delta; + y = x * sin_delta + y * cos_delta; + x = x_new; + } + gGL.end(); +} + +void LLTracker::drawBeacon(LLVector3 pos_agent, std::string direction, LLColor4 fogged_color, F32 dist) +{ + const U32 BEACON_VERTS = 256; + F32 step; + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + + if ("DOWN" == direction) + { + gGL.translatef(pos_agent.mV[0], pos_agent.mV[1], pos_agent.mV[2]); + draw_shockwave(1024.f, gRenderStartTime.getElapsedTimeF32(), 32, fogged_color); + step = (5020.0f - pos_agent.mV[2]) / BEACON_VERTS; + } + else + { + gGL.translatef(pos_agent.mV[0], pos_agent.mV[1], 0); + step = pos_agent.mV[2] / BEACON_VERTS; + } + + gGL.color4fv(fogged_color.mV); + + LLVector3 x_axis = LLViewerCamera::getInstance()->getLeftAxis(); + F32 t = gRenderStartTime.getElapsedTimeF32(); + + for (U32 i = 0; i < BEACON_VERTS; i++) + { + F32 x = x_axis.mV[0]; + F32 y = x_axis.mV[1]; + + F32 z = i * step; + F32 z_next = (i+1)*step; + + bool tracking_avatar = getTrackingStatus() == TRACKING_AVATAR; + F32 a = pulse_func(t, z, tracking_avatar, direction); + F32 an = pulse_func(t, z_next, tracking_avatar, direction); + + LLColor4 c_col = fogged_color + LLColor4(a,a,a,a); + LLColor4 col_next = fogged_color + LLColor4(an,an,an,an); + LLColor4 col_edge = fogged_color * LLColor4(a,a,a,0.0f); + LLColor4 col_edge_next = fogged_color * LLColor4(an,an,an,0.0f); + + a *= 2.f; + a += 1.0f; + + an *= 2.f; + an += 1.0f; + + gGL.begin(LLRender::TRIANGLE_STRIP); + gGL.color4fv(col_edge.mV); + gGL.vertex3f(-x*a, -y*a, z); + gGL.color4fv(col_edge_next.mV); + gGL.vertex3f(-x*an, -y*an, z_next); + + gGL.color4fv(c_col.mV); + gGL.vertex3f(0, 0, z); + gGL.color4fv(col_next.mV); + gGL.vertex3f(0, 0, z_next); + + gGL.color4fv(col_edge.mV); + gGL.vertex3f(x*a,y*a,z); + gGL.color4fv(col_edge_next.mV); + gGL.vertex3f(x*an,y*an,z_next); + gGL.end(); + } + gGL.popMatrix(); +} + +// static +void LLTracker::renderBeacon(LLVector3d pos_global, + const LLColor4& color, + const LLColor4& color_under, + LLHUDText* hud_textp, + const std::string& label ) +{ + sCheesyBeacon = gSavedSettings.getBOOL("CheesyBeacon"); + LLVector3d to_vec = pos_global - gAgentCamera.getCameraPositionGlobal(); + + F32 dist = (F32)to_vec.magVec(); + F32 color_frac = 1.f; + if (dist > 0.99f * LLViewerCamera::getInstance()->getFar()) + { + color_frac = 0.4f; + // pos_global = gAgentCamera.getCameraPositionGlobal() + 0.99f*(LLViewerCamera::getInstance()->getFar()/dist)*to_vec; + } + else + { + color_frac = 1.f - 0.6f*(dist/LLViewerCamera::getInstance()->getFar()); + } + + LLColor4 fogged_color = color_frac * color + (1 - color_frac)*gSky.getSkyFogColor(); + LLColor4 fogged_color_under = color_frac * color_under + (1 - color_frac) * gSky.getSkyFogColor(); + + F32 FADE_DIST = 3.f; + fogged_color.mV[3] = llmax(0.2f, llmin(0.5f,(dist-FADE_DIST)/FADE_DIST)); + fogged_color_under.mV[3] = llmax(0.2f, llmin(0.5f,(dist-FADE_DIST)/FADE_DIST)); + + LLVector3 pos_agent = gAgent.getPosAgentFromGlobal(pos_global); + + LLGLSTracker gls_tracker; + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + LLGLDisable cull_face(GL_CULL_FACE); + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); + + LLTracker::drawBeacon(pos_agent, "DOWN", fogged_color, dist); + LLTracker::drawBeacon(pos_agent, "UP", fogged_color_under, dist); + + std::string text; + text = llformat( "%.0f m", to_vec.magVec()); + + std::string str; + str += label; + str += '\n'; + str += text; + + hud_textp->setFont(LLFontGL::getFontSansSerif()); + hud_textp->setZCompare(false); + hud_textp->setColor(LLColor4(1.f, 1.f, 1.f, llmax(0.2f, llmin(1.f,(dist-FADE_DIST)/FADE_DIST)))); + + hud_textp->setString(str); + hud_textp->setVertAlignment(LLHUDText::ALIGN_VERT_CENTER); + hud_textp->setPositionAgent(pos_agent); +} + + +void LLTracker::stopTrackingAll(bool clear_ui) +{ + switch (mTrackingStatus) + { + case TRACKING_AVATAR : + stopTrackingAvatar(clear_ui); + break; + case TRACKING_LANDMARK : + stopTrackingLandmark(clear_ui); + break; + case TRACKING_LOCATION : + stopTrackingLocation(clear_ui); + break; + default: + mTrackingStatus = TRACKING_NOTHING; + break; + } +} + + +void LLTracker::stopTrackingAvatar(bool clear_ui) +{ + LLAvatarTracker& av_tracker = LLAvatarTracker::instance(); + if( !av_tracker.getAvatarID().isNull() ) + { + av_tracker.untrack( av_tracker.getAvatarID() ); + } + + purgeBeaconText(); + gFloaterWorldMap->clearAvatarSelection(clear_ui); + mTrackingStatus = TRACKING_NOTHING; +} + + +void LLTracker::stopTrackingLandmark(bool clear_ui) +{ + purgeBeaconText(); + mTrackedLandmarkAssetID.setNull(); + mTrackedLandmarkItemID.setNull(); + mTrackedLandmarkName.assign(""); + mTrackedPositionGlobal.zeroVec(); + mHasLandmarkPosition = false; + mHasReachedLandmark = false; + mLandmarkHasBeenVisited = true; + gFloaterWorldMap->clearLandmarkSelection(clear_ui); + mTrackingStatus = TRACKING_NOTHING; +} + + +void LLTracker::stopTrackingLocation(bool clear_ui, bool dest_reached) +{ + purgeBeaconText(); + mTrackedLocationName.assign(""); + mIsTrackingLocation = false; + mTrackedPositionGlobal.zeroVec(); + gFloaterWorldMap->clearLocationSelection(clear_ui, dest_reached); + mTrackingStatus = TRACKING_NOTHING; + mTrackingLocationType = LOCATION_NOTHING; +} + +void LLTracker::clearFocus() +{ + instance()->mTrackingStatus = TRACKING_NOTHING; +} + +void LLTracker::drawMarker(const LLVector3d& pos_global, const LLColor4& color) +{ + // get position + LLVector3 pos_local = gAgent.getPosAgentFromGlobal(pos_global); + + // check in frustum + LLCoordGL screen; + S32 x = 0; + S32 y = 0; + const bool CLAMP = true; + + if (LLViewerCamera::getInstance()->projectPosAgentToScreen(pos_local, screen, CLAMP) + || LLViewerCamera::getInstance()->projectPosAgentToScreenEdge(pos_local, screen) ) + { + gHUDView->screenPointToLocal(screen.mX, screen.mY, &x, &y); + + // the center of the rendered position of the arrow obeys + // the following rules: + // (1) it lies on an ellipse centered on the target position + // (2) it lies on the line between the target and the window center + // (3) right now the radii of the ellipse are fixed, but eventually + // they will be a function of the target text + // + // from those rules we can compute the position of the + // lower left corner of the image + LLRect rect = gHUDView->getRect(); + S32 x_center = lltrunc(0.5f * (F32)rect.getWidth()); + S32 y_center = lltrunc(0.5f * (F32)rect.getHeight()); + x = x - x_center; // x and y relative to center + y = y - y_center; + F32 dist = sqrt((F32)(x*x + y*y)); + S32 half_arrow_size = lltrunc(0.5f * HUD_ARROW_SIZE); + if (dist > 0.f) + { + const F32 ARROW_ELLIPSE_RADIUS_X = 2 * HUD_ARROW_SIZE; + const F32 ARROW_ELLIPSE_RADIUS_Y = HUD_ARROW_SIZE; + + // compute where the arrow should be + F32 x_target = (F32)(x + x_center) - (ARROW_ELLIPSE_RADIUS_X * ((F32)x / dist) ); + F32 y_target = (F32)(y + y_center) - (ARROW_ELLIPSE_RADIUS_Y * ((F32)y / dist) ); + + // keep the arrow within the window + F32 x_clamped = llclamp( x_target, (F32)half_arrow_size, (F32)(rect.getWidth() - half_arrow_size)); + F32 y_clamped = llclamp( y_target, (F32)half_arrow_size, (F32)(rect.getHeight() - half_arrow_size)); + + F32 slope = (F32)(y) / (F32)(x); + F32 window_ratio = (F32)(rect.getHeight() - HUD_ARROW_SIZE) / (F32)(rect.getWidth() - HUD_ARROW_SIZE); + + // if the arrow has been clamped on one axis + // then we need to compute the other axis + if (llabs(slope) > window_ratio) + { + if (y_clamped != (F32)y_target) + { + // clamp by y + x_clamped = (y_clamped - (F32)y_center) / slope + (F32)x_center; + } + } + else if (x_clamped != (F32)x_target) + { + // clamp by x + y_clamped = (x_clamped - (F32)x_center) * slope + (F32)y_center; + } + mHUDArrowCenterX = lltrunc(x_clamped); + mHUDArrowCenterY = lltrunc(y_clamped); + } + else + { + // recycle the old values + x = mHUDArrowCenterX - x_center; + y = mHUDArrowCenterY - y_center; + } + + F32 angle = atan2( (F32)y, (F32)x ); + + gl_draw_scaled_rotated_image(mHUDArrowCenterX - half_arrow_size, + mHUDArrowCenterY - half_arrow_size, + HUD_ARROW_SIZE, HUD_ARROW_SIZE, + RAD_TO_DEG * angle, + LLWorldMapView::sTrackArrowImage->getImage(), + color); + } +} + + +void LLTracker::setLandmarkVisited() +{ + // poke the inventory item + if (!mTrackedLandmarkItemID.isNull()) + { + LLInventoryItem* i = gInventory.getItem( mTrackedLandmarkItemID ); + LLViewerInventoryItem* item = (LLViewerInventoryItem*)i; + if ( item + && !(item->getFlags()&LLInventoryItemFlags::II_FLAGS_LANDMARK_VISITED)) + { + U32 flags = item->getFlags(); + flags |= LLInventoryItemFlags::II_FLAGS_LANDMARK_VISITED; + item->setFlags(flags); + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("ChangeInventoryItemFlags"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->nextBlock("InventoryData"); + msg->addUUID("ItemID", mTrackedLandmarkItemID); + msg->addU32("Flags", flags); + gAgent.sendReliableMessage(); + + LLInventoryModel::LLCategoryUpdate up(item->getParentUUID(), 0); + gInventory.accountForUpdate(up); + + // need to communicate that the icon needs to change... + gInventory.addChangedMask(LLInventoryObserver::INTERNAL, item->getUUID()); + gInventory.notifyObservers(); + } + } +} + + +void LLTracker::cacheLandmarkPosition() +{ + // the landmark asset download may have finished, in which case + // we'll now be able to figure out where we're trying to go + bool found_landmark = false; + if( mTrackedLandmarkAssetID == LLFloaterWorldMap::getHomeID()) + { + LLVector3d pos_global; + if ( gAgent.getHomePosGlobal( &mTrackedPositionGlobal )) + { + found_landmark = true; + } + else + { + LL_WARNS() << "LLTracker couldn't find home pos" << LL_ENDL; + mTrackedLandmarkAssetID.setNull(); + mTrackedLandmarkItemID.setNull(); + } + } + else + { + LLLandmark* landmark = gLandmarkList.getAsset(mTrackedLandmarkAssetID); + if(landmark && landmark->getGlobalPos(mTrackedPositionGlobal)) + { + found_landmark = true; + + // cache the object's visitation status + mLandmarkHasBeenVisited = false; + LLInventoryItem* item = gInventory.getItem(mTrackedLandmarkItemID); + if ( item + && item->getFlags()&LLInventoryItemFlags::II_FLAGS_LANDMARK_VISITED) + { + mLandmarkHasBeenVisited = true; + } + } + } + if ( found_landmark && gFloaterWorldMap ) + { + mHasReachedLandmark = false; + F32 dist = gFloaterWorldMap->getDistanceToDestination(mTrackedPositionGlobal, 1.0f); + if ( dist < DESTINATION_UNVISITED_RADIUS ) + { + mHasReachedLandmark = true; + } + mHasLandmarkPosition = true; + } + mHasLandmarkPosition = found_landmark; +} + + +void LLTracker::purgeBeaconText() +{ + if(!mBeaconText.isNull()) + { + mBeaconText->markDead(); + mBeaconText = NULL; + } +} + diff --git a/indra/newview/lltracker.h b/indra/newview/lltracker.h index 78579c4c76..bf341216dd 100644 --- a/indra/newview/lltracker.h +++ b/indra/newview/lltracker.h @@ -1,155 +1,155 @@ -/** - * @file lltracker.h - * @brief Container for objects user is tracking. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -// A singleton class for tracking stuff. -// -// TODO -- LLAvatarTracker functionality should probably be moved -// to the LLTracker class. - - -#ifndef LL_LLTRACKER_H -#define LL_LLTRACKER_H - -#include "llpointer.h" -#include "llstring.h" -#include "lluuid.h" -#include "v3dmath.h" - -class LLHUDText; - - -class LLTracker -{ -public: - enum ETrackingStatus - { - TRACKING_NOTHING = 0, - TRACKING_AVATAR = 1, - TRACKING_LANDMARK = 2, - TRACKING_LOCATION = 3, - }; - - enum ETrackingLocationType - { - LOCATION_NOTHING, - LOCATION_EVENT, - LOCATION_ITEM, - }; - - static LLTracker* instance() - { - if (!sTrackerp) - { - sTrackerp = new LLTracker(); - } - return sTrackerp; - } - - static void cleanupInstance() { delete sTrackerp; sTrackerp = NULL; } - - //static void drawTrackerArrow(); - // these are static so that they can be used a callbacks - static ETrackingStatus getTrackingStatus() { return instance()->mTrackingStatus; } - static ETrackingLocationType getTrackedLocationType() { return instance()->mTrackingLocationType; } - static bool isTracking(void*) { return instance()->mTrackingStatus != TRACKING_NOTHING; } - static void stopTracking(bool); - static void clearFocus(); - - static const LLUUID& getTrackedLandmarkAssetID() { return instance()->mTrackedLandmarkAssetID; } - static const LLUUID& getTrackedLandmarkItemID() { return instance()->mTrackedLandmarkItemID; } - - static void trackAvatar( const LLUUID& avatar_id, const std::string& name ); - static void trackLandmark( const LLUUID& landmark_asset_id, const LLUUID& landmark_item_id , const std::string& name); - static void trackLocation(const LLVector3d& pos, const std::string& full_name, const std::string& tooltip, ETrackingLocationType location_type = LOCATION_NOTHING); - - // returns global pos of tracked thing - static LLVector3d getTrackedPositionGlobal(); - - static bool hasLandmarkPosition(); - static const std::string& getTrackedLocationName(); - - static void drawHUDArrow(); - - // Draw in-world 3D tracking stuff - static void render3D(); - - static bool handleMouseDown(S32 x, S32 y); - - static LLTracker* sTrackerp; - static bool sCheesyBeacon; - - static const std::string& getLabel() { return instance()->mLabel; } - static const std::string& getToolTip() { return instance()->mToolTip; } -protected: - LLTracker(); - ~LLTracker(); - - static void drawBeacon(LLVector3 pos_agent, std::string direction, LLColor4 fogged_color, F32 dist); - static void renderBeacon( LLVector3d pos_global, - const LLColor4& color, - const LLColor4& color_under, - LLHUDText* hud_textp, - const std::string& label ); - - void stopTrackingAll(bool clear_ui = false); - void stopTrackingAvatar(bool clear_ui = false); - void stopTrackingLocation(bool clear_ui = false, bool dest_reached = false); - void stopTrackingLandmark(bool clear_ui = false); - - void drawMarker(const LLVector3d& pos_global, const LLColor4& color); - void setLandmarkVisited(); - void cacheLandmarkPosition(); - void purgeBeaconText(); - -protected: - ETrackingStatus mTrackingStatus; - ETrackingLocationType mTrackingLocationType; - LLPointer mBeaconText; - S32 mHUDArrowCenterX; - S32 mHUDArrowCenterY; - - LLVector3d mTrackedPositionGlobal; - - std::string mLabel; - std::string mToolTip; - - std::string mTrackedLandmarkName; - LLUUID mTrackedLandmarkAssetID; - LLUUID mTrackedLandmarkItemID; - std::vector mLandmarkAssetIDList; - std::vector mLandmarkItemIDList; - bool mHasReachedLandmark; - bool mHasLandmarkPosition; - bool mLandmarkHasBeenVisited; - - std::string mTrackedLocationName; - bool mIsTrackingLocation; - bool mHasReachedLocation; -}; - - -#endif - +/** + * @file lltracker.h + * @brief Container for objects user is tracking. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// A singleton class for tracking stuff. +// +// TODO -- LLAvatarTracker functionality should probably be moved +// to the LLTracker class. + + +#ifndef LL_LLTRACKER_H +#define LL_LLTRACKER_H + +#include "llpointer.h" +#include "llstring.h" +#include "lluuid.h" +#include "v3dmath.h" + +class LLHUDText; + + +class LLTracker +{ +public: + enum ETrackingStatus + { + TRACKING_NOTHING = 0, + TRACKING_AVATAR = 1, + TRACKING_LANDMARK = 2, + TRACKING_LOCATION = 3, + }; + + enum ETrackingLocationType + { + LOCATION_NOTHING, + LOCATION_EVENT, + LOCATION_ITEM, + }; + + static LLTracker* instance() + { + if (!sTrackerp) + { + sTrackerp = new LLTracker(); + } + return sTrackerp; + } + + static void cleanupInstance() { delete sTrackerp; sTrackerp = NULL; } + + //static void drawTrackerArrow(); + // these are static so that they can be used a callbacks + static ETrackingStatus getTrackingStatus() { return instance()->mTrackingStatus; } + static ETrackingLocationType getTrackedLocationType() { return instance()->mTrackingLocationType; } + static bool isTracking(void*) { return instance()->mTrackingStatus != TRACKING_NOTHING; } + static void stopTracking(bool); + static void clearFocus(); + + static const LLUUID& getTrackedLandmarkAssetID() { return instance()->mTrackedLandmarkAssetID; } + static const LLUUID& getTrackedLandmarkItemID() { return instance()->mTrackedLandmarkItemID; } + + static void trackAvatar( const LLUUID& avatar_id, const std::string& name ); + static void trackLandmark( const LLUUID& landmark_asset_id, const LLUUID& landmark_item_id , const std::string& name); + static void trackLocation(const LLVector3d& pos, const std::string& full_name, const std::string& tooltip, ETrackingLocationType location_type = LOCATION_NOTHING); + + // returns global pos of tracked thing + static LLVector3d getTrackedPositionGlobal(); + + static bool hasLandmarkPosition(); + static const std::string& getTrackedLocationName(); + + static void drawHUDArrow(); + + // Draw in-world 3D tracking stuff + static void render3D(); + + static bool handleMouseDown(S32 x, S32 y); + + static LLTracker* sTrackerp; + static bool sCheesyBeacon; + + static const std::string& getLabel() { return instance()->mLabel; } + static const std::string& getToolTip() { return instance()->mToolTip; } +protected: + LLTracker(); + ~LLTracker(); + + static void drawBeacon(LLVector3 pos_agent, std::string direction, LLColor4 fogged_color, F32 dist); + static void renderBeacon( LLVector3d pos_global, + const LLColor4& color, + const LLColor4& color_under, + LLHUDText* hud_textp, + const std::string& label ); + + void stopTrackingAll(bool clear_ui = false); + void stopTrackingAvatar(bool clear_ui = false); + void stopTrackingLocation(bool clear_ui = false, bool dest_reached = false); + void stopTrackingLandmark(bool clear_ui = false); + + void drawMarker(const LLVector3d& pos_global, const LLColor4& color); + void setLandmarkVisited(); + void cacheLandmarkPosition(); + void purgeBeaconText(); + +protected: + ETrackingStatus mTrackingStatus; + ETrackingLocationType mTrackingLocationType; + LLPointer mBeaconText; + S32 mHUDArrowCenterX; + S32 mHUDArrowCenterY; + + LLVector3d mTrackedPositionGlobal; + + std::string mLabel; + std::string mToolTip; + + std::string mTrackedLandmarkName; + LLUUID mTrackedLandmarkAssetID; + LLUUID mTrackedLandmarkItemID; + std::vector mLandmarkAssetIDList; + std::vector mLandmarkItemIDList; + bool mHasReachedLandmark; + bool mHasLandmarkPosition; + bool mLandmarkHasBeenVisited; + + std::string mTrackedLocationName; + bool mIsTrackingLocation; + bool mHasReachedLocation; +}; + + +#endif + diff --git a/indra/newview/lltransientdockablefloater.cpp b/indra/newview/lltransientdockablefloater.cpp index 6c282b5981..a3818d5c01 100644 --- a/indra/newview/lltransientdockablefloater.cpp +++ b/indra/newview/lltransientdockablefloater.cpp @@ -1,99 +1,99 @@ -/** - * @file lltransientdockablefloater.cpp - * @brief Creates a panel of a specific kind for a toast - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lltransientfloatermgr.h" -#include "lltransientdockablefloater.h" -#include "llfloaterreg.h" - - -LLTransientDockableFloater::LLTransientDockableFloater(LLDockControl* dockControl, bool uniqueDocking, - const LLSD& key, const Params& params) : - LLDockableFloater(dockControl, uniqueDocking, key, params) -{ - LLTransientFloaterMgr::getInstance()->registerTransientFloater(this); - LLTransientFloater::init(this); -} - -LLTransientDockableFloater::~LLTransientDockableFloater() -{ - LLTransientFloaterMgr::getInstance()->unregisterTransientFloater(this); - LLView* dock = getDockWidget(); - LLTransientFloaterMgr::getInstance()->removeControlView( - LLTransientFloaterMgr::DOCKED, this); - if (dock != NULL) - { - LLTransientFloaterMgr::getInstance()->removeControlView( - LLTransientFloaterMgr::DOCKED, dock); - } -} - -void LLTransientDockableFloater::setVisible(bool visible) -{ - LLView* dock = getDockWidget(); - if(visible && isDocked()) - { - LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::DOCKED, this); - if (dock != NULL) - { - LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::DOCKED, dock); - } - } - else - { - LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::DOCKED, this); - if (dock != NULL) - { - LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::DOCKED, dock); - } - } - - LLDockableFloater::setVisible(visible); -} - -void LLTransientDockableFloater::setDocked(bool docked, bool pop_on_undock) -{ - LLView* dock = getDockWidget(); - if(docked) - { - LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::DOCKED, this); - if (dock != NULL) - { - LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::DOCKED, dock); - } - } - else - { - LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::DOCKED, this); - if (dock != NULL) - { - LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::DOCKED, dock); - } - } - - LLDockableFloater::setDocked(docked, pop_on_undock); -} +/** + * @file lltransientdockablefloater.cpp + * @brief Creates a panel of a specific kind for a toast + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lltransientfloatermgr.h" +#include "lltransientdockablefloater.h" +#include "llfloaterreg.h" + + +LLTransientDockableFloater::LLTransientDockableFloater(LLDockControl* dockControl, bool uniqueDocking, + const LLSD& key, const Params& params) : + LLDockableFloater(dockControl, uniqueDocking, key, params) +{ + LLTransientFloaterMgr::getInstance()->registerTransientFloater(this); + LLTransientFloater::init(this); +} + +LLTransientDockableFloater::~LLTransientDockableFloater() +{ + LLTransientFloaterMgr::getInstance()->unregisterTransientFloater(this); + LLView* dock = getDockWidget(); + LLTransientFloaterMgr::getInstance()->removeControlView( + LLTransientFloaterMgr::DOCKED, this); + if (dock != NULL) + { + LLTransientFloaterMgr::getInstance()->removeControlView( + LLTransientFloaterMgr::DOCKED, dock); + } +} + +void LLTransientDockableFloater::setVisible(bool visible) +{ + LLView* dock = getDockWidget(); + if(visible && isDocked()) + { + LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::DOCKED, this); + if (dock != NULL) + { + LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::DOCKED, dock); + } + } + else + { + LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::DOCKED, this); + if (dock != NULL) + { + LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::DOCKED, dock); + } + } + + LLDockableFloater::setVisible(visible); +} + +void LLTransientDockableFloater::setDocked(bool docked, bool pop_on_undock) +{ + LLView* dock = getDockWidget(); + if(docked) + { + LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::DOCKED, this); + if (dock != NULL) + { + LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::DOCKED, dock); + } + } + else + { + LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::DOCKED, this); + if (dock != NULL) + { + LLTransientFloaterMgr::getInstance()->removeControlView(LLTransientFloaterMgr::DOCKED, dock); + } + } + + LLDockableFloater::setDocked(docked, pop_on_undock); +} diff --git a/indra/newview/lltransientdockablefloater.h b/indra/newview/lltransientdockablefloater.h index 79baee52f1..b3b408f853 100644 --- a/indra/newview/lltransientdockablefloater.h +++ b/indra/newview/lltransientdockablefloater.h @@ -1,53 +1,53 @@ -/** - * @file lltransientdockablefloater.h - * @brief Creates a panel of a specific kind for a toast. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_TRANSIENTDOCKABLEFLOATER_H -#define LL_TRANSIENTDOCKABLEFLOATER_H - -#include "llerror.h" -#include "llfloater.h" -#include "lldockcontrol.h" -#include "lldockablefloater.h" -#include "lltransientfloatermgr.h" - -/** - * Represents floater that can dock and managed by transient floater manager. - * Transient floaters should be hidden if user click anywhere except defined view list. - */ -class LLTransientDockableFloater : public LLDockableFloater, LLTransientFloater -{ -public: - LOG_CLASS(LLTransientDockableFloater); - LLTransientDockableFloater(LLDockControl* dockControl, bool uniqueDocking, - const LLSD& key, const Params& params = getDefaultParams()); - virtual ~LLTransientDockableFloater(); - - void setVisible(bool visible) override; - void setDocked(bool docked, bool pop_on_undock = true) override; - virtual LLTransientFloaterMgr::ETransientGroup getGroup() override { return LLTransientFloaterMgr::GLOBAL; } -}; - -#endif /* LL_TRANSIENTDOCKABLEFLOATER_H */ +/** + * @file lltransientdockablefloater.h + * @brief Creates a panel of a specific kind for a toast. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_TRANSIENTDOCKABLEFLOATER_H +#define LL_TRANSIENTDOCKABLEFLOATER_H + +#include "llerror.h" +#include "llfloater.h" +#include "lldockcontrol.h" +#include "lldockablefloater.h" +#include "lltransientfloatermgr.h" + +/** + * Represents floater that can dock and managed by transient floater manager. + * Transient floaters should be hidden if user click anywhere except defined view list. + */ +class LLTransientDockableFloater : public LLDockableFloater, LLTransientFloater +{ +public: + LOG_CLASS(LLTransientDockableFloater); + LLTransientDockableFloater(LLDockControl* dockControl, bool uniqueDocking, + const LLSD& key, const Params& params = getDefaultParams()); + virtual ~LLTransientDockableFloater(); + + void setVisible(bool visible) override; + void setDocked(bool docked, bool pop_on_undock = true) override; + virtual LLTransientFloaterMgr::ETransientGroup getGroup() override { return LLTransientFloaterMgr::GLOBAL; } +}; + +#endif /* LL_TRANSIENTDOCKABLEFLOATER_H */ diff --git a/indra/newview/lltransientfloatermgr.cpp b/indra/newview/lltransientfloatermgr.cpp index 52a049764c..dc777dbb67 100644 --- a/indra/newview/lltransientfloatermgr.cpp +++ b/indra/newview/lltransientfloatermgr.cpp @@ -1,169 +1,169 @@ -/** - * @file lltransientfloatermgr.cpp - * @brief LLFocusMgr base class - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lltransientfloatermgr.h" -#include "llfocusmgr.h" -#include "llrootview.h" -#include "llviewerwindow.h" -#include "lldockablefloater.h" -#include "llmenugl.h" - - -LLTransientFloaterMgr::LLTransientFloaterMgr() -{ - if(gViewerWindow) - { - gViewerWindow->getRootView()->getChild("popup_holder")->setMouseDownCallback(boost::bind( - &LLTransientFloaterMgr::leftMouseClickCallback, this, _2, _3, _4)); - } - - mGroupControls.insert(std::pair(GLOBAL, controls_set_t())); - mGroupControls.insert(std::pair(DOCKED, controls_set_t())); - mGroupControls.insert(std::pair(IM, controls_set_t())); -} - -void LLTransientFloaterMgr::registerTransientFloater(LLTransientFloater* floater) -{ - mTransSet.insert(floater); -} - -void LLTransientFloaterMgr::unregisterTransientFloater(LLTransientFloater* floater) -{ - mTransSet.erase(floater); -} - -void LLTransientFloaterMgr::addControlView(ETransientGroup group, LLView* view) -{ - if (!view) return; - - mGroupControls.find(group)->second.insert(view->getHandle()); -} - -void LLTransientFloaterMgr::removeControlView(ETransientGroup group, LLView* view) -{ - if (!view) return; - - mGroupControls.find(group)->second.erase(view->getHandle()); -} - -void LLTransientFloaterMgr::addControlView(LLView* view) -{ - addControlView(GLOBAL, view); -} - -void LLTransientFloaterMgr::removeControlView(LLView* view) -{ - // we will still get focus lost callbacks on this view, but that's ok - // since we run sanity checking logic every time - removeControlView(GLOBAL, view); -} - -void LLTransientFloaterMgr::hideTransientFloaters(S32 x, S32 y) -{ - for (std::set::iterator it = mTransSet.begin(); it - != mTransSet.end(); it++) - { - LLTransientFloater* floater = *it; - if (floater->isTransientDocked()) - { - ETransientGroup group = floater->getGroup(); - - bool hide = isControlClicked(group, mGroupControls.find(group)->second, x, y); - if (hide) - { - floater->setTransientVisible(false); - } - } - } -} - -bool LLTransientFloaterMgr::isControlClicked(ETransientGroup group, controls_set_t& set, S32 x, S32 y) -{ - std::list< LLHandle > dead_handles; - - bool res = true; - for (controls_set_t::iterator it = set.begin(); it - != set.end(); it++) - { - LLView* control_view = NULL; - - LLHandle handle = *it; - if (handle.isDead()) - { - dead_handles.push_back(handle); - continue; - } - - control_view = handle.get(); - - if (!control_view->getVisible()) - { - continue; - } - - LLRect rect = control_view->calcScreenRect(); - // if click inside view rect - if (rect.pointInRect(x, y)) - { - res = false; - break; - } - } - - for (std::list< LLHandle >::iterator it = dead_handles.begin(); it != dead_handles.end(); ++it) - { - LLHandle handle = *it; - mGroupControls.find(group)->second.erase(handle); - } - - return res; -} - -void LLTransientFloaterMgr::leftMouseClickCallback(S32 x, S32 y, - MASK mask) -{ - // don't hide transient floater if any context menu opened - if (LLMenuGL::sMenuContainer->getVisibleMenu() != NULL) - { - return; - } - - bool hide = isControlClicked(DOCKED, mGroupControls.find(DOCKED)->second, x, y) - && isControlClicked(GLOBAL, mGroupControls.find(GLOBAL)->second, x, y); - if (hide) - { - hideTransientFloaters(x, y); - } -} - -void LLTransientFloater::init(LLFloater* thiz) -{ - // used since LLTransientFloater(this) can't be used in descendant constructor parameter initialization. - mFloater = thiz; -} - +/** + * @file lltransientfloatermgr.cpp + * @brief LLFocusMgr base class + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lltransientfloatermgr.h" +#include "llfocusmgr.h" +#include "llrootview.h" +#include "llviewerwindow.h" +#include "lldockablefloater.h" +#include "llmenugl.h" + + +LLTransientFloaterMgr::LLTransientFloaterMgr() +{ + if(gViewerWindow) + { + gViewerWindow->getRootView()->getChild("popup_holder")->setMouseDownCallback(boost::bind( + &LLTransientFloaterMgr::leftMouseClickCallback, this, _2, _3, _4)); + } + + mGroupControls.insert(std::pair(GLOBAL, controls_set_t())); + mGroupControls.insert(std::pair(DOCKED, controls_set_t())); + mGroupControls.insert(std::pair(IM, controls_set_t())); +} + +void LLTransientFloaterMgr::registerTransientFloater(LLTransientFloater* floater) +{ + mTransSet.insert(floater); +} + +void LLTransientFloaterMgr::unregisterTransientFloater(LLTransientFloater* floater) +{ + mTransSet.erase(floater); +} + +void LLTransientFloaterMgr::addControlView(ETransientGroup group, LLView* view) +{ + if (!view) return; + + mGroupControls.find(group)->second.insert(view->getHandle()); +} + +void LLTransientFloaterMgr::removeControlView(ETransientGroup group, LLView* view) +{ + if (!view) return; + + mGroupControls.find(group)->second.erase(view->getHandle()); +} + +void LLTransientFloaterMgr::addControlView(LLView* view) +{ + addControlView(GLOBAL, view); +} + +void LLTransientFloaterMgr::removeControlView(LLView* view) +{ + // we will still get focus lost callbacks on this view, but that's ok + // since we run sanity checking logic every time + removeControlView(GLOBAL, view); +} + +void LLTransientFloaterMgr::hideTransientFloaters(S32 x, S32 y) +{ + for (std::set::iterator it = mTransSet.begin(); it + != mTransSet.end(); it++) + { + LLTransientFloater* floater = *it; + if (floater->isTransientDocked()) + { + ETransientGroup group = floater->getGroup(); + + bool hide = isControlClicked(group, mGroupControls.find(group)->second, x, y); + if (hide) + { + floater->setTransientVisible(false); + } + } + } +} + +bool LLTransientFloaterMgr::isControlClicked(ETransientGroup group, controls_set_t& set, S32 x, S32 y) +{ + std::list< LLHandle > dead_handles; + + bool res = true; + for (controls_set_t::iterator it = set.begin(); it + != set.end(); it++) + { + LLView* control_view = NULL; + + LLHandle handle = *it; + if (handle.isDead()) + { + dead_handles.push_back(handle); + continue; + } + + control_view = handle.get(); + + if (!control_view->getVisible()) + { + continue; + } + + LLRect rect = control_view->calcScreenRect(); + // if click inside view rect + if (rect.pointInRect(x, y)) + { + res = false; + break; + } + } + + for (std::list< LLHandle >::iterator it = dead_handles.begin(); it != dead_handles.end(); ++it) + { + LLHandle handle = *it; + mGroupControls.find(group)->second.erase(handle); + } + + return res; +} + +void LLTransientFloaterMgr::leftMouseClickCallback(S32 x, S32 y, + MASK mask) +{ + // don't hide transient floater if any context menu opened + if (LLMenuGL::sMenuContainer->getVisibleMenu() != NULL) + { + return; + } + + bool hide = isControlClicked(DOCKED, mGroupControls.find(DOCKED)->second, x, y) + && isControlClicked(GLOBAL, mGroupControls.find(GLOBAL)->second, x, y); + if (hide) + { + hideTransientFloaters(x, y); + } +} + +void LLTransientFloater::init(LLFloater* thiz) +{ + // used since LLTransientFloater(this) can't be used in descendant constructor parameter initialization. + mFloater = thiz; +} + diff --git a/indra/newview/lltransientfloatermgr.h b/indra/newview/lltransientfloatermgr.h index 9c9d6bdf3f..87a7b9e822 100644 --- a/indra/newview/lltransientfloatermgr.h +++ b/indra/newview/lltransientfloatermgr.h @@ -1,89 +1,89 @@ -/** - * @file lltransientfloatermgr.h - * @brief LLFocusMgr base class - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLTRANSIENTFLOATERMGR_H -#define LL_LLTRANSIENTFLOATERMGR_H - -#include "llui.h" -#include "llsingleton.h" -#include "llfloater.h" - -class LLTransientFloater; - -/** - * Provides functionality to hide transient floaters. - */ -class LLTransientFloaterMgr: public LLSingleton -{ - LLSINGLETON(LLTransientFloaterMgr); - -public: - enum ETransientGroup - { - GLOBAL, DOCKED, IM - }; - - void registerTransientFloater(LLTransientFloater* floater); - void unregisterTransientFloater(LLTransientFloater* floater); - void addControlView(ETransientGroup group, LLView* view); - void removeControlView(ETransientGroup group, LLView* view); - void addControlView(LLView* view); - void removeControlView(LLView* view); - -private: - typedef std::set > controls_set_t; - typedef std::map group_controls_t; - - void hideTransientFloaters(S32 x, S32 y); - void leftMouseClickCallback(S32 x, S32 y, MASK mask); - bool isControlClicked(ETransientGroup group, controls_set_t& set, S32 x, S32 y); - - std::set mTransSet; - - group_controls_t mGroupControls; -}; - -/** - * An abstract class declares transient floater interfaces. - */ -class LLTransientFloater -{ -protected: - /** - * Class initialization method. - * Should be called from descendant constructor. - */ - void init(LLFloater* thiz); -public: - virtual LLTransientFloaterMgr::ETransientGroup getGroup() = 0; - bool isTransientDocked() { return mFloater->isDocked(); }; - void setTransientVisible(bool visible) {mFloater->setVisible(visible); } - -private: - LLFloater* mFloater; -}; - -#endif // LL_LLTRANSIENTFLOATERMGR_H +/** + * @file lltransientfloatermgr.h + * @brief LLFocusMgr base class + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLTRANSIENTFLOATERMGR_H +#define LL_LLTRANSIENTFLOATERMGR_H + +#include "llui.h" +#include "llsingleton.h" +#include "llfloater.h" + +class LLTransientFloater; + +/** + * Provides functionality to hide transient floaters. + */ +class LLTransientFloaterMgr: public LLSingleton +{ + LLSINGLETON(LLTransientFloaterMgr); + +public: + enum ETransientGroup + { + GLOBAL, DOCKED, IM + }; + + void registerTransientFloater(LLTransientFloater* floater); + void unregisterTransientFloater(LLTransientFloater* floater); + void addControlView(ETransientGroup group, LLView* view); + void removeControlView(ETransientGroup group, LLView* view); + void addControlView(LLView* view); + void removeControlView(LLView* view); + +private: + typedef std::set > controls_set_t; + typedef std::map group_controls_t; + + void hideTransientFloaters(S32 x, S32 y); + void leftMouseClickCallback(S32 x, S32 y, MASK mask); + bool isControlClicked(ETransientGroup group, controls_set_t& set, S32 x, S32 y); + + std::set mTransSet; + + group_controls_t mGroupControls; +}; + +/** + * An abstract class declares transient floater interfaces. + */ +class LLTransientFloater +{ +protected: + /** + * Class initialization method. + * Should be called from descendant constructor. + */ + void init(LLFloater* thiz); +public: + virtual LLTransientFloaterMgr::ETransientGroup getGroup() = 0; + bool isTransientDocked() { return mFloater->isDocked(); }; + void setTransientVisible(bool visible) {mFloater->setVisible(visible); } + +private: + LLFloater* mFloater; +}; + +#endif // LL_LLTRANSIENTFLOATERMGR_H diff --git a/indra/newview/lltranslate.cpp b/indra/newview/lltranslate.cpp index 9659922333..ae3bcfdf79 100644 --- a/indra/newview/lltranslate.cpp +++ b/indra/newview/lltranslate.cpp @@ -1,1303 +1,1303 @@ -/** -* @file lltranslate.cpp -* @brief Functions for translating text via Google Translate. -* - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lltranslate.h" - -#include - -#include "llbufferstream.h" -#include "lltrans.h" -#include "llui.h" -#include "llversioninfo.h" -#include "llviewercontrol.h" -#include "llcoros.h" -#include "llcorehttputil.h" -#include "llurlregistry.h" -#include "stringize.h" - -#include - -static const std::string AZURE_NOTRANSLATE_OPENING_TAG("
"); -static const std::string AZURE_NOTRANSLATE_CLOSING_TAG("
"); - -/** -* Handler of an HTTP machine translation service. -* -* Derived classes know the service URL -* and how to parse the translation result. -*/ -class LLTranslationAPIHandler -{ -public: - typedef std::pair LanguagePair_t; - - /** - * Get URL for translation of the given string. - * - * Sending HTTP GET request to the URL will initiate translation. - * - * @param[out] url Place holder for the result. - * @param from_lang Source language. Leave empty for auto-detection. - * @param to_lang Target language. - * @param text Text to translate. - */ - virtual std::string getTranslateURL( - const std::string &from_lang, - const std::string &to_lang, - const std::string &text) const = 0; - - /** - * Get URL to verify the given API key. - * - * Sending request to the URL verifies the key. - * Positive HTTP response (code 200) means that the key is valid. - * - * @param[out] url Place holder for the URL. - * @param[in] key Key to verify. - */ - virtual std::string getKeyVerificationURL( - const LLSD &key) const = 0; - - /** - * Check API verification response. - * - * @param[out] bool true if valid. - * @param[in] response - * @param[in] status - */ - virtual bool checkVerificationResponse( - const LLSD &response, - int status) const = 0; - - /** - * Parse translation response. - * - * @param[in,out] status HTTP status. May be modified while parsing. - * @param body Response text. - * @param[out] translation Translated text. - * @param[out] detected_lang Detected source language. May be empty. - * @param[out] err_msg Error message (in case of error). - */ - virtual bool parseResponse( - const LLSD& http_response, - int& status, - const std::string& body, - std::string& translation, - std::string& detected_lang, - std::string& err_msg) const = 0; - - /** - * @return if the handler is configured to function properly - */ - virtual bool isConfigured() const = 0; - - virtual LLTranslate::EService getCurrentService() = 0; - - virtual void verifyKey(const LLSD &key, LLTranslate::KeyVerificationResult_fn fnc) = 0; - virtual void translateMessage(LanguagePair_t fromTo, std::string msg, LLTranslate::TranslationSuccess_fn success, LLTranslate::TranslationFailure_fn failure); - - - virtual ~LLTranslationAPIHandler() {} - - void verifyKeyCoro(LLTranslate::EService service, LLSD key, LLTranslate::KeyVerificationResult_fn fnc); - void translateMessageCoro(LanguagePair_t fromTo, std::string msg, LLTranslate::TranslationSuccess_fn success, LLTranslate::TranslationFailure_fn failure); - - virtual void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent) const = 0; - virtual void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent, const LLSD &key) const = 0; - virtual LLSD sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string & url, - const std::string & msg, - const std::string& from_lang, - const std::string& to_lang) const = 0; - virtual LLSD verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string & url) const = 0; -}; - -void LLTranslationAPIHandler::translateMessage(LanguagePair_t fromTo, std::string msg, LLTranslate::TranslationSuccess_fn success, LLTranslate::TranslationFailure_fn failure) -{ - LLCoros::instance().launch("Translation", boost::bind(&LLTranslationAPIHandler::translateMessageCoro, - this, fromTo, msg, success, failure)); - -} - -void LLTranslationAPIHandler::verifyKeyCoro(LLTranslate::EService service, LLSD key, LLTranslate::KeyVerificationResult_fn fnc) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); - LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); - - - std::string user_agent = stringize( - LLVersionInfo::instance().getChannel(), ' ', - LLVersionInfo::instance().getMajor(), '.', - LLVersionInfo::instance().getMinor(), '.', - LLVersionInfo::instance().getPatch(), " (", - LLVersionInfo::instance().getBuild(), ')'); - - initHttpHeader(httpHeaders, user_agent, key); - - httpOpts->setFollowRedirects(true); - httpOpts->setSSLVerifyPeer(false); - - std::string url = this->getKeyVerificationURL(key); - if (url.empty()) - { - LL_INFOS("Translate") << "No translation URL" << LL_ENDL; - return; - } - - std::string::size_type delim_pos = url.find("://"); - if (delim_pos == std::string::npos) - { - LL_INFOS("Translate") << "URL is missing a scheme" << LL_ENDL; - return; - } - - LLSD result = verifyAndSuspend(httpAdapter, httpRequest, httpOpts, httpHeaders, url); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - bool bOk = true; - int parseResult = status.getType(); - if (!checkVerificationResponse(httpResults, parseResult)) - { - bOk = false; - } - - if (!fnc.empty()) - { - fnc(service, bOk, parseResult); - } -} - -void LLTranslationAPIHandler::translateMessageCoro(LanguagePair_t fromTo, std::string msg, - LLTranslate::TranslationSuccess_fn success, LLTranslate::TranslationFailure_fn failure) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); - LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); - - - std::string user_agent = stringize( - LLVersionInfo::instance().getChannel(), ' ', - LLVersionInfo::instance().getMajor(), '.', - LLVersionInfo::instance().getMinor(), '.', - LLVersionInfo::instance().getPatch(), " (", - LLVersionInfo::instance().getBuild(), ')'); - - initHttpHeader(httpHeaders, user_agent); - httpOpts->setSSLVerifyPeer(false); - - std::string url = this->getTranslateURL(fromTo.first, fromTo.second, msg); - if (url.empty()) - { - LL_INFOS("Translate") << "No translation URL" << LL_ENDL; - return; - } - - LLSD result = sendMessageAndSuspend(httpAdapter, httpRequest, httpOpts, httpHeaders, url, msg, fromTo.first, fromTo.second); - - if (LLApp::isQuitting()) - { - return; - } - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - std::string translation, err_msg; - std::string detected_lang(fromTo.second); - - int parseResult = status.getType(); - const LLSD::Binary &rawBody = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_RAW].asBinary(); - std::string body(rawBody.begin(), rawBody.end()); - - bool res = false; - - try - { - res = parseResponse(httpResults, parseResult, body, translation, detected_lang, err_msg); - } - catch (std::out_of_range&) - { - LL_WARNS() << "Out of range exception on string " << body << LL_ENDL; - } - catch (...) - { - LOG_UNHANDLED_EXCEPTION( "Exception on string " + body ); - } - - if (res) - { - // Fix up the response - LLStringUtil::replaceString(translation, "<", "<"); - LLStringUtil::replaceString(translation, ">", ">"); - LLStringUtil::replaceString(translation, """, "\""); - LLStringUtil::replaceString(translation, "'", "'"); - LLStringUtil::replaceString(translation, "&", "&"); - LLStringUtil::replaceString(translation, "'", "'"); - - if (!success.empty()) - success(translation, detected_lang); - } - else - { - if (err_msg.empty() && httpResults.has("error_body")) - { - err_msg = httpResults["error_body"].asString(); - } - - if (err_msg.empty()) - { - err_msg = LLTrans::getString("TranslationResponseParseError"); - } - - LL_WARNS() << "Translation request failed: " << err_msg << LL_ENDL; - if (!failure.empty()) - failure(status, err_msg); - } -} - -//========================================================================= -/// Google Translate v2 API handler. -class LLGoogleTranslationHandler : public LLTranslationAPIHandler -{ - LOG_CLASS(LLGoogleTranslationHandler); - -public: - std::string getTranslateURL( - const std::string &from_lang, - const std::string &to_lang, - const std::string &text) const override; - std::string getKeyVerificationURL( - const LLSD &key) const override; - bool checkVerificationResponse( - const LLSD &response, - int status) const override; - bool parseResponse( - const LLSD& http_response, - int& status, - const std::string& body, - std::string& translation, - std::string& detected_lang, - std::string& err_msg) const override; - bool isConfigured() const override; - - LLTranslate::EService getCurrentService() override { return LLTranslate::EService::SERVICE_GOOGLE; } - - void verifyKey(const LLSD &key, LLTranslate::KeyVerificationResult_fn fnc) override; - - void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent) const override; - void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent, const LLSD &key) const override; - LLSD sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string & url, - const std::string & msg, - const std::string& from_lang, - const std::string& to_lang) const override; - - LLSD verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string & url) const override; - -private: - static void parseErrorResponse( - const boost::json::value& root, - int& status, - std::string& err_msg); - static bool parseTranslation( - const boost::json::value& root, - std::string& translation, - std::string& detected_lang); - static std::string getAPIKey(); -}; - -//------------------------------------------------------------------------- -// virtual -std::string LLGoogleTranslationHandler::getTranslateURL( - const std::string &from_lang, - const std::string &to_lang, - const std::string &text) const -{ - std::string url = std::string("https://www.googleapis.com/language/translate/v2?key=") - + getAPIKey() + "&q=" + LLURI::escape(text) + "&target=" + to_lang; - if (!from_lang.empty()) - { - url += "&source=" + from_lang; - } - return url; -} - -// virtual -std::string LLGoogleTranslationHandler::getKeyVerificationURL( - const LLSD& key) const -{ - std::string url = std::string("https://www.googleapis.com/language/translate/v2/languages?key=") - + key.asString() +"&target=en"; - return url; -} - -//virtual -bool LLGoogleTranslationHandler::checkVerificationResponse( - const LLSD &response, - int status) const -{ - return status == HTTP_OK; -} - -// virtual -bool LLGoogleTranslationHandler::parseResponse( - const LLSD& http_response, - int& status, - const std::string& body, - std::string& translation, - std::string& detected_lang, - std::string& err_msg) const -{ - const std::string& text = !body.empty() ? body : http_response["error_body"].asStringRef(); - - boost::json::error_code ec; - boost::json::value root = boost::json::parse(text, ec); - if (ec.failed()) - { - err_msg = ec.what(); - return false; - } - - if (root.is_object()) - { - // Request succeeded, extract translation from the XML body. - if (parseTranslation(root, translation, detected_lang)) - return true; - - // Request failed. Extract error message from the XML body. - parseErrorResponse(root, status, err_msg); - } - - return false; -} - -// virtual -bool LLGoogleTranslationHandler::isConfigured() const -{ - return !getAPIKey().empty(); -} - -// static -void LLGoogleTranslationHandler::parseErrorResponse( - const boost::json::value& root, - int& status, - std::string& err_msg) -{ - boost::json::error_code ec; - auto message = root.find_pointer("/data/message", ec); - auto code = root.find_pointer("/data/code", ec); - if (!message || !code) - { - return; - } - - auto message_val = boost::json::try_value_to(*message); - auto code_val = boost::json::try_value_to(*code); - if (!message_val || !code_val) - { - return; - } - - err_msg = message_val.value(); - status = code_val.value(); -} - -// static -bool LLGoogleTranslationHandler::parseTranslation( - const boost::json::value& root, - std::string& translation, - std::string& detected_lang) -{ - boost::json::error_code ec; - auto translated_text = root.find_pointer("/data/translations/0/translatedText", ec); - if (!translated_text) return false; - - auto text_val = boost::json::try_value_to(*translated_text); - if (!text_val) - { - LL_WARNS() << "Failed to parse translation" << text_val.error() << LL_ENDL; - return false; - } - - translation = text_val.value(); - - auto language = root.find_pointer("/data/translations/0/detectedSourceLanguage", ec); - if (language) - { - auto lang_val = boost::json::try_value_to(*language); - detected_lang = lang_val ? lang_val.value() : ""; - } - - return true; -} - -// static -std::string LLGoogleTranslationHandler::getAPIKey() -{ - static LLCachedControl google_key(gSavedSettings, "GoogleTranslateAPIKey"); - return google_key; -} - -/*virtual*/ -void LLGoogleTranslationHandler::verifyKey(const LLSD &key, LLTranslate::KeyVerificationResult_fn fnc) -{ - LLCoros::instance().launch("Google /Verify Key", boost::bind(&LLTranslationAPIHandler::verifyKeyCoro, - this, LLTranslate::SERVICE_GOOGLE, key, fnc)); -} - -/*virtual*/ -void LLGoogleTranslationHandler::initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent) const -{ - headers->append(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_JSON); - headers->append(HTTP_OUT_HEADER_USER_AGENT, user_agent); -} - -/*virtual*/ -void LLGoogleTranslationHandler::initHttpHeader( - LLCore::HttpHeaders::ptr_t headers, - const std::string& user_agent, - const LLSD &key) const -{ - initHttpHeader(headers, user_agent); -} - -LLSD LLGoogleTranslationHandler::sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string & url, - const std::string & msg, - const std::string& from_lang, - const std::string& to_lang) const -{ - return adapter->getRawAndSuspend(request, url, options, headers); -} - -LLSD LLGoogleTranslationHandler::verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string & url) const -{ - return adapter->getAndSuspend(request, url, options, headers); -} - -//========================================================================= -/// Microsoft Translator v2 API handler. -class LLAzureTranslationHandler : public LLTranslationAPIHandler -{ - LOG_CLASS(LLAzureTranslationHandler); - -public: - std::string getTranslateURL( - const std::string &from_lang, - const std::string &to_lang, - const std::string &text) const override; - std::string getKeyVerificationURL( - const LLSD &key) const override; - bool checkVerificationResponse( - const LLSD &response, - int status) const override; - bool parseResponse( - const LLSD& http_response, - int& status, - const std::string& body, - std::string& translation, - std::string& detected_lang, - std::string& err_msg) const override; - bool isConfigured() const override; - - LLTranslate::EService getCurrentService() override { return LLTranslate::EService::SERVICE_AZURE; } - - void verifyKey(const LLSD &key, LLTranslate::KeyVerificationResult_fn fnc) override; - - void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent) const override; - void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent, const LLSD &key) const override; - LLSD sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string & url, - const std::string & msg, - const std::string& from_lang, - const std::string& to_lang) const override; - - LLSD verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string & url) const override; -private: - static std::string parseErrorResponse( - const std::string& body); - static LLSD getAPIKey(); - static std::string getAPILanguageCode(const std::string& lang); - -}; - -//------------------------------------------------------------------------- -// virtual -std::string LLAzureTranslationHandler::getTranslateURL( - const std::string &from_lang, - const std::string &to_lang, - const std::string &text) const -{ - std::string url; - LLSD key = getAPIKey(); - if (key.isMap()) - { - std::string endpoint = key["endpoint"].asString(); - - if (*endpoint.rbegin() != '/') - { - endpoint += "/"; - } - url = endpoint + std::string("translate?api-version=3.0&to=") - + getAPILanguageCode(to_lang); - } - return url; -} - - -// virtual -std::string LLAzureTranslationHandler::getKeyVerificationURL( - const LLSD& key) const -{ - std::string url; - if (key.isMap()) - { - std::string endpoint = key["endpoint"].asString(); - if (*endpoint.rbegin() != '/') - { - endpoint += "/"; - } - url = endpoint + std::string("translate?api-version=3.0&to=en"); - } - return url; -} - -//virtual -bool LLAzureTranslationHandler::checkVerificationResponse( - const LLSD &response, - int status) const -{ - if (status == HTTP_UNAUTHORIZED) - { - LL_DEBUGS("Translate") << "Key unathorised" << LL_ENDL; - return false; - } - - if (status == HTTP_NOT_FOUND) - { - LL_DEBUGS("Translate") << "Either endpoint doesn't have requested resource" << LL_ENDL; - return false; - } - - if (status != HTTP_BAD_REQUEST) - { - LL_DEBUGS("Translate") << "Unexpected error code" << LL_ENDL; - return false; - } - - if (!response.has("error_body")) - { - LL_DEBUGS("Translate") << "Unexpected response, no error returned" << LL_ENDL; - return false; - } - - // Expected: "{\"error\":{\"code\":400000,\"message\":\"One of the request inputs is not valid.\"}}" - // But for now just verify response is a valid json - - boost::json::error_code ec; - boost::json::value root = boost::json::parse(response["error_body"].asString(), ec); - if (ec.failed()) - { - LL_DEBUGS("Translate") << "Failed to parse error_body:" << ec.what() << LL_ENDL; - return false; - } - - return true; -} - -// virtual -bool LLAzureTranslationHandler::parseResponse( - const LLSD& http_response, - int& status, - const std::string& body, - std::string& translation, - std::string& detected_lang, - std::string& err_msg) const -{ - if (status != HTTP_OK) - { - if (http_response.has("error_body")) - err_msg = parseErrorResponse(http_response["error_body"].asString()); - return false; - } - - //Example: - // "[{\"detectedLanguage\":{\"language\":\"en\",\"score\":1.0},\"translations\":[{\"text\":\"Hello, what is your name?\",\"to\":\"en\"}]}]" - - boost::json::error_code ec; - boost::json::value root = boost::json::parse(body, ec); - if (ec.failed()) - { - err_msg = ec.what(); - return false; - } - auto language = root.find_pointer("/0/detectedLanguage/language", ec); - if (!language) return false; - - auto translated_text = root.find_pointer("/0/translations/0/text", ec); - if (!translated_text) return false; - - auto lang_val = boost::json::try_value_to(*language); - auto text_val = boost::json::try_value_to(*translated_text); - if (!lang_val || !text_val) - { - LL_WARNS() << "Failed to parse translation" << lang_val.error() << text_val.error() << LL_ENDL; - return false; - } - - detected_lang = lang_val.value(); - translation = text_val.value(); - - return true; -} - -// virtual -bool LLAzureTranslationHandler::isConfigured() const -{ - return getAPIKey().isMap(); -} - -//static -std::string LLAzureTranslationHandler::parseErrorResponse( - const std::string& body) -{ - // Expected: "{\"error\":{\"code\":400000,\"message\":\"One of the request inputs is not valid.\"}}" - // But for now just verify response is a valid json with an error - - boost::json::error_code ec; - boost::json::value root = boost::json::parse(body, ec); - if (ec.failed()) - { - return {}; - } - - auto err_msg = root.find_pointer("/error/message", ec); - if (!err_msg) - { - return {}; - } - - auto err_msg_val = boost::json::try_value_to(*err_msg); - if (!err_msg_val) - { - return {}; - } - return err_msg_val.value(); -} - -// static -LLSD LLAzureTranslationHandler::getAPIKey() -{ - static LLCachedControl azure_key(gSavedSettings, "AzureTranslateAPIKey"); - return azure_key; -} - -// static -std::string LLAzureTranslationHandler::getAPILanguageCode(const std::string& lang) -{ - return lang == "zh" ? "zh-CHT" : lang; // treat Chinese as Traditional Chinese -} - -/*virtual*/ -void LLAzureTranslationHandler::verifyKey(const LLSD &key, LLTranslate::KeyVerificationResult_fn fnc) -{ - LLCoros::instance().launch("Azure /Verify Key", boost::bind(&LLTranslationAPIHandler::verifyKeyCoro, - this, LLTranslate::SERVICE_AZURE, key, fnc)); -} -/*virtual*/ -void LLAzureTranslationHandler::initHttpHeader( - LLCore::HttpHeaders::ptr_t headers, - const std::string& user_agent) const -{ - initHttpHeader(headers, user_agent, getAPIKey()); -} - -/*virtual*/ -void LLAzureTranslationHandler::initHttpHeader( - LLCore::HttpHeaders::ptr_t headers, - const std::string& user_agent, - const LLSD &key) const -{ - headers->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_JSON); - headers->append(HTTP_OUT_HEADER_USER_AGENT, user_agent); - - if (key.has("id")) - { - // Token based autorization - headers->append("Ocp-Apim-Subscription-Key", key["id"].asString()); - } - if (key.has("region")) - { - // ex: "westeurope" - headers->append("Ocp-Apim-Subscription-Region", key["region"].asString()); - } -} - -LLSD LLAzureTranslationHandler::sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string & url, - const std::string & msg, - const std::string& from_lang, - const std::string& to_lang) const -{ - LLCore::BufferArray::ptr_t rawbody(new LLCore::BufferArray); - LLCore::BufferArrayStream outs(rawbody.get()); - - static const std::string allowed_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz " - "0123456789" - "-._~"; - - outs << "[{\"text\":\""; - outs << LLURI::escape(msg, allowed_chars); - outs << "\"}]"; - - return adapter->postRawAndSuspend(request, url, rawbody, options, headers); -} - -LLSD LLAzureTranslationHandler::verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string & url) const -{ - LLCore::BufferArray::ptr_t rawbody(new LLCore::BufferArray); - LLCore::BufferArrayStream outs(rawbody.get()); - outs << "[{\"intentionally_invalid_400\"}]"; - - return adapter->postRawAndSuspend(request, url, rawbody, options, headers); -} - -//========================================================================= -/// DeepL Translator API handler. -class LLDeepLTranslationHandler: public LLTranslationAPIHandler -{ - LOG_CLASS(LLDeepLTranslationHandler); - -public: - std::string getTranslateURL( - const std::string& from_lang, - const std::string& to_lang, - const std::string& text) const override; - std::string getKeyVerificationURL( - const LLSD& key) const override; - bool checkVerificationResponse( - const LLSD& response, - int status) const override; - bool parseResponse( - const LLSD& http_response, - int& status, - const std::string& body, - std::string& translation, - std::string& detected_lang, - std::string& err_msg) const override; - bool isConfigured() const override; - - LLTranslate::EService getCurrentService() override - { - return LLTranslate::EService::SERVICE_DEEPL; - } - - void verifyKey(const LLSD& key, LLTranslate::KeyVerificationResult_fn fnc) override; - - void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent) const override; - void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent, const LLSD& key) const override; - LLSD sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string& url, - const std::string& msg, - const std::string& from_lang, - const std::string& to_lang) const override; - - LLSD verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string& url) const override; -private: - static std::string parseErrorResponse( - const std::string& body); - static LLSD getAPIKey(); - static std::string getAPILanguageCode(const std::string& lang); -}; - -//------------------------------------------------------------------------- -// virtual -std::string LLDeepLTranslationHandler::getTranslateURL( - const std::string& from_lang, - const std::string& to_lang, - const std::string& text) const -{ - std::string url; - LLSD key = getAPIKey(); - if (key.isMap()) - { - url = key["domain"].asString(); - - if (*url.rbegin() != '/') - { - url += "/"; - } - url += std::string("v2/translate"); - } - return url; -} - - -// virtual -std::string LLDeepLTranslationHandler::getKeyVerificationURL( - const LLSD& key) const -{ - std::string url; - if (key.isMap()) - { - url = key["domain"].asString(); - - if (*url.rbegin() != '/') - { - url += "/"; - } - url += std::string("v2/translate"); - } - return url; -} - -//virtual -bool LLDeepLTranslationHandler::checkVerificationResponse( - const LLSD& response, - int status) const -{ - // Might need to parse body to make sure we got - // a valid response and not a message - return status == HTTP_OK; -} - -// virtual -bool LLDeepLTranslationHandler::parseResponse( - const LLSD& http_response, - int& status, - const std::string& body, - std::string& translation, - std::string& detected_lang, - std::string& err_msg) const -{ - if (status != HTTP_OK) - { - if (http_response.has("error_body")) - err_msg = parseErrorResponse(http_response["error_body"].asString()); - return false; - } - - //Example: - // "{\"translations\":[{\"detected_source_language\":\"EN\",\"text\":\"test\"}]}" - - boost::json::error_code ec; - boost::json::value root = boost::json::parse(body, ec); - if (ec.failed()) - { - err_msg = ec.message(); - return false; - } - - auto detected_langp = root.find_pointer("/translations/0/detected_source_language", ec); - if (!detected_langp || ec.failed()) // empty response? should not happen - { - err_msg = ec.message(); - return false; - } - - // Request succeeded, extract translation from the response. - auto text_valp = root.find_pointer("/translations/0/text", ec); - if (!text_valp || ec.failed()) - { - err_msg = ec.message(); - return false; - } - - auto lang_result = boost::json::try_value_to(*detected_langp); - auto text_result = boost::json::try_value_to(*text_valp); - if (!lang_result || !text_result) - { - return false; - } - - detected_lang = lang_result.value(); - LLStringUtil::toLower(detected_lang); - translation = text_result.value(); - - return true; -} - -// virtual -bool LLDeepLTranslationHandler::isConfigured() const -{ - return getAPIKey().isMap(); -} - -//static -std::string LLDeepLTranslationHandler::parseErrorResponse( - const std::string& body) -{ - // Example: "{\"message\":\"One of the request inputs is not valid.\"}" - boost::json::error_code ec; - boost::json::value root = boost::json::parse(body, ec); - if (ec.failed()) - { - return {}; - } - - auto message_ptr = root.find_pointer("/message", ec); - if (!message_ptr || ec.failed()) - { - return {}; - } - - auto message_val = boost::json::try_value_to(*message_ptr); - if (!message_val) - return {}; - - return message_val.value(); -} - -// static -LLSD LLDeepLTranslationHandler::getAPIKey() -{ - static LLCachedControl deepl_key(gSavedSettings, "DeepLTranslateAPIKey"); - return deepl_key; -} - -// static -std::string LLDeepLTranslationHandler::getAPILanguageCode(const std::string& lang) -{ - return lang == "zh" ? "zh-CHT" : lang; // treat Chinese as Traditional Chinese -} - -/*virtual*/ -void LLDeepLTranslationHandler::verifyKey(const LLSD& key, LLTranslate::KeyVerificationResult_fn fnc) -{ - LLCoros::instance().launch("DeepL /Verify Key", boost::bind(&LLTranslationAPIHandler::verifyKeyCoro, - this, LLTranslate::SERVICE_DEEPL, key, fnc)); -} -/*virtual*/ -void LLDeepLTranslationHandler::initHttpHeader( - LLCore::HttpHeaders::ptr_t headers, - const std::string& user_agent) const -{ - initHttpHeader(headers, user_agent, getAPIKey()); -} - -/*virtual*/ -void LLDeepLTranslationHandler::initHttpHeader( - LLCore::HttpHeaders::ptr_t headers, - const std::string& user_agent, - const LLSD& key) const -{ - headers->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/x-www-form-urlencoded"); - headers->append(HTTP_OUT_HEADER_USER_AGENT, user_agent); - - if (key.has("id")) - { - std::string authkey = "DeepL-Auth-Key " + key["id"].asString(); - headers->append(HTTP_OUT_HEADER_AUTHORIZATION, authkey); - } -} - -LLSD LLDeepLTranslationHandler::sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string& url, - const std::string& msg, - const std::string& from_lang, - const std::string& to_lang) const -{ - LLCore::BufferArray::ptr_t rawbody(new LLCore::BufferArray); - LLCore::BufferArrayStream outs(rawbody.get()); - outs << "text="; - std::string escaped_string = LLURI::escape(msg); - outs << escaped_string; - outs << "&target_lang="; - std::string lang = to_lang; - LLStringUtil::toUpper(lang); - outs << lang; - - return adapter->postRawAndSuspend(request, url, rawbody, options, headers); -} - -LLSD LLDeepLTranslationHandler::verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, - LLCore::HttpRequest::ptr_t request, - LLCore::HttpOptions::ptr_t options, - LLCore::HttpHeaders::ptr_t headers, - const std::string& url) const -{ - LLCore::BufferArray::ptr_t rawbody(new LLCore::BufferArray); - LLCore::BufferArrayStream outs(rawbody.get()); - outs << "text=&target_lang=EN"; - - return adapter->postRawAndSuspend(request, url, rawbody, options, headers); -} - -//========================================================================= -LLTranslate::LLTranslate(): - mCharsSeen(0), - mCharsSent(0), - mFailureCount(0), - mSuccessCount(0) -{ -} - -LLTranslate::~LLTranslate() -{ -} - -/*static*/ -void LLTranslate::translateMessage(const std::string &from_lang, const std::string &to_lang, - const std::string &mesg, TranslationSuccess_fn success, TranslationFailure_fn failure) -{ - LLTranslationAPIHandler& handler = getPreferredHandler(); - - handler.translateMessage(LLTranslationAPIHandler::LanguagePair_t(from_lang, to_lang), addNoTranslateTags(mesg), success, failure); -} - -std::string LLTranslate::addNoTranslateTags(std::string mesg) -{ - if (getPreferredHandler().getCurrentService() == SERVICE_GOOGLE) - { - return mesg; - } - - if (getPreferredHandler().getCurrentService() == SERVICE_DEEPL) - { - return mesg; - } - - if (getPreferredHandler().getCurrentService() == SERVICE_AZURE) - { - // https://learn.microsoft.com/en-us/azure/cognitive-services/translator/prevent-translation - std::string upd_msg(mesg); - LLUrlMatch match; - S32 dif = 0; - //surround all links (including SLURLs) with 'no-translate' tags to prevent unnecessary translation - while (LLUrlRegistry::instance().findUrl(mesg, match)) - { - upd_msg.insert(dif + match.getStart(), AZURE_NOTRANSLATE_OPENING_TAG); - upd_msg.insert(dif + AZURE_NOTRANSLATE_OPENING_TAG.size() + match.getEnd() + 1, AZURE_NOTRANSLATE_CLOSING_TAG); - mesg.erase(match.getStart(), match.getEnd() - match.getStart()); - dif += match.getEnd() - match.getStart() + AZURE_NOTRANSLATE_OPENING_TAG.size() + AZURE_NOTRANSLATE_CLOSING_TAG.size(); - } - return upd_msg; - } - return mesg; -} - -std::string LLTranslate::removeNoTranslateTags(std::string mesg) -{ - if (getPreferredHandler().getCurrentService() == SERVICE_GOOGLE) - { - return mesg; - } - if (getPreferredHandler().getCurrentService() == SERVICE_DEEPL) - { - return mesg; - } - - if (getPreferredHandler().getCurrentService() == SERVICE_AZURE) - { - std::string upd_msg(mesg); - LLUrlMatch match; - S32 opening_tag_size = AZURE_NOTRANSLATE_OPENING_TAG.size(); - S32 closing_tag_size = AZURE_NOTRANSLATE_CLOSING_TAG.size(); - S32 dif = 0; - //remove 'no-translate' tags we added to the links before - while (LLUrlRegistry::instance().findUrl(mesg, match)) - { - if (upd_msg.substr(dif + match.getStart() - opening_tag_size, opening_tag_size) == AZURE_NOTRANSLATE_OPENING_TAG) - { - upd_msg.erase(dif + match.getStart() - opening_tag_size, opening_tag_size); - dif -= opening_tag_size; - - if (upd_msg.substr(dif + match.getEnd() + 1, closing_tag_size) == AZURE_NOTRANSLATE_CLOSING_TAG) - { - upd_msg.replace(dif + match.getEnd() + 1, closing_tag_size, " "); - dif -= closing_tag_size - 1; - } - } - mesg.erase(match.getStart(), match.getUrl().size()); - dif += match.getUrl().size(); - } - return upd_msg; - } - - return mesg; -} - -/*static*/ -void LLTranslate::verifyKey(EService service, const LLSD &key, KeyVerificationResult_fn fnc) -{ - LLTranslationAPIHandler& handler = getHandler(service); - - handler.verifyKey(key, fnc); -} - - -//static -std::string LLTranslate::getTranslateLanguage() -{ - std::string language = gSavedSettings.getString("TranslateLanguage"); - if (language.empty() || language == "default") - { - language = LLUI::getLanguage(); - } - language = language.substr(0,2); - return language; -} - -// static -bool LLTranslate::isTranslationConfigured() -{ - return getPreferredHandler().isConfigured(); -} - -void LLTranslate::logCharsSeen(size_t count) -{ - mCharsSeen += count; -} - -void LLTranslate::logCharsSent(size_t count) -{ - mCharsSent += count; -} - -void LLTranslate::logSuccess(S32 count) -{ - mSuccessCount += count; -} - -void LLTranslate::logFailure(S32 count) -{ - mFailureCount += count; -} - -LLSD LLTranslate::asLLSD() const -{ - LLSD res; - bool on = gSavedSettings.getBOOL("TranslateChat"); - res["on"] = on; - res["chars_seen"] = (S32) mCharsSeen; - if (on) - { - res["chars_sent"] = (S32) mCharsSent; - res["success_count"] = mSuccessCount; - res["failure_count"] = mFailureCount; - res["language"] = getTranslateLanguage(); - res["service"] = gSavedSettings.getString("TranslationService"); - } - return res; -} - -// static -LLTranslationAPIHandler& LLTranslate::getPreferredHandler() -{ - EService service = SERVICE_AZURE; - - std::string service_str = gSavedSettings.getString("TranslationService"); - if (service_str == "google") - { - service = SERVICE_GOOGLE; - } - if (service_str == "azure") - { - service = SERVICE_AZURE; - } - if (service_str == "deepl") - { - service = SERVICE_DEEPL; - } - - return getHandler(service); -} - -// static -LLTranslationAPIHandler& LLTranslate::getHandler(EService service) -{ - static LLGoogleTranslationHandler google; - static LLAzureTranslationHandler azure; - static LLDeepLTranslationHandler deepl; - - switch (service) - { - case SERVICE_AZURE: - return azure; - case SERVICE_GOOGLE: - return google; - case SERVICE_DEEPL: - return deepl; - } - - return azure; -} +/** +* @file lltranslate.cpp +* @brief Functions for translating text via Google Translate. +* + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lltranslate.h" + +#include + +#include "llbufferstream.h" +#include "lltrans.h" +#include "llui.h" +#include "llversioninfo.h" +#include "llviewercontrol.h" +#include "llcoros.h" +#include "llcorehttputil.h" +#include "llurlregistry.h" +#include "stringize.h" + +#include + +static const std::string AZURE_NOTRANSLATE_OPENING_TAG("
"); +static const std::string AZURE_NOTRANSLATE_CLOSING_TAG("
"); + +/** +* Handler of an HTTP machine translation service. +* +* Derived classes know the service URL +* and how to parse the translation result. +*/ +class LLTranslationAPIHandler +{ +public: + typedef std::pair LanguagePair_t; + + /** + * Get URL for translation of the given string. + * + * Sending HTTP GET request to the URL will initiate translation. + * + * @param[out] url Place holder for the result. + * @param from_lang Source language. Leave empty for auto-detection. + * @param to_lang Target language. + * @param text Text to translate. + */ + virtual std::string getTranslateURL( + const std::string &from_lang, + const std::string &to_lang, + const std::string &text) const = 0; + + /** + * Get URL to verify the given API key. + * + * Sending request to the URL verifies the key. + * Positive HTTP response (code 200) means that the key is valid. + * + * @param[out] url Place holder for the URL. + * @param[in] key Key to verify. + */ + virtual std::string getKeyVerificationURL( + const LLSD &key) const = 0; + + /** + * Check API verification response. + * + * @param[out] bool true if valid. + * @param[in] response + * @param[in] status + */ + virtual bool checkVerificationResponse( + const LLSD &response, + int status) const = 0; + + /** + * Parse translation response. + * + * @param[in,out] status HTTP status. May be modified while parsing. + * @param body Response text. + * @param[out] translation Translated text. + * @param[out] detected_lang Detected source language. May be empty. + * @param[out] err_msg Error message (in case of error). + */ + virtual bool parseResponse( + const LLSD& http_response, + int& status, + const std::string& body, + std::string& translation, + std::string& detected_lang, + std::string& err_msg) const = 0; + + /** + * @return if the handler is configured to function properly + */ + virtual bool isConfigured() const = 0; + + virtual LLTranslate::EService getCurrentService() = 0; + + virtual void verifyKey(const LLSD &key, LLTranslate::KeyVerificationResult_fn fnc) = 0; + virtual void translateMessage(LanguagePair_t fromTo, std::string msg, LLTranslate::TranslationSuccess_fn success, LLTranslate::TranslationFailure_fn failure); + + + virtual ~LLTranslationAPIHandler() {} + + void verifyKeyCoro(LLTranslate::EService service, LLSD key, LLTranslate::KeyVerificationResult_fn fnc); + void translateMessageCoro(LanguagePair_t fromTo, std::string msg, LLTranslate::TranslationSuccess_fn success, LLTranslate::TranslationFailure_fn failure); + + virtual void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent) const = 0; + virtual void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent, const LLSD &key) const = 0; + virtual LLSD sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, + LLCore::HttpRequest::ptr_t request, + LLCore::HttpOptions::ptr_t options, + LLCore::HttpHeaders::ptr_t headers, + const std::string & url, + const std::string & msg, + const std::string& from_lang, + const std::string& to_lang) const = 0; + virtual LLSD verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, + LLCore::HttpRequest::ptr_t request, + LLCore::HttpOptions::ptr_t options, + LLCore::HttpHeaders::ptr_t headers, + const std::string & url) const = 0; +}; + +void LLTranslationAPIHandler::translateMessage(LanguagePair_t fromTo, std::string msg, LLTranslate::TranslationSuccess_fn success, LLTranslate::TranslationFailure_fn failure) +{ + LLCoros::instance().launch("Translation", boost::bind(&LLTranslationAPIHandler::translateMessageCoro, + this, fromTo, msg, success, failure)); + +} + +void LLTranslationAPIHandler::verifyKeyCoro(LLTranslate::EService service, LLSD key, LLTranslate::KeyVerificationResult_fn fnc) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); + + + std::string user_agent = stringize( + LLVersionInfo::instance().getChannel(), ' ', + LLVersionInfo::instance().getMajor(), '.', + LLVersionInfo::instance().getMinor(), '.', + LLVersionInfo::instance().getPatch(), " (", + LLVersionInfo::instance().getBuild(), ')'); + + initHttpHeader(httpHeaders, user_agent, key); + + httpOpts->setFollowRedirects(true); + httpOpts->setSSLVerifyPeer(false); + + std::string url = this->getKeyVerificationURL(key); + if (url.empty()) + { + LL_INFOS("Translate") << "No translation URL" << LL_ENDL; + return; + } + + std::string::size_type delim_pos = url.find("://"); + if (delim_pos == std::string::npos) + { + LL_INFOS("Translate") << "URL is missing a scheme" << LL_ENDL; + return; + } + + LLSD result = verifyAndSuspend(httpAdapter, httpRequest, httpOpts, httpHeaders, url); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + bool bOk = true; + int parseResult = status.getType(); + if (!checkVerificationResponse(httpResults, parseResult)) + { + bOk = false; + } + + if (!fnc.empty()) + { + fnc(service, bOk, parseResult); + } +} + +void LLTranslationAPIHandler::translateMessageCoro(LanguagePair_t fromTo, std::string msg, + LLTranslate::TranslationSuccess_fn success, LLTranslate::TranslationFailure_fn failure) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getMerchantStatusCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); + + + std::string user_agent = stringize( + LLVersionInfo::instance().getChannel(), ' ', + LLVersionInfo::instance().getMajor(), '.', + LLVersionInfo::instance().getMinor(), '.', + LLVersionInfo::instance().getPatch(), " (", + LLVersionInfo::instance().getBuild(), ')'); + + initHttpHeader(httpHeaders, user_agent); + httpOpts->setSSLVerifyPeer(false); + + std::string url = this->getTranslateURL(fromTo.first, fromTo.second, msg); + if (url.empty()) + { + LL_INFOS("Translate") << "No translation URL" << LL_ENDL; + return; + } + + LLSD result = sendMessageAndSuspend(httpAdapter, httpRequest, httpOpts, httpHeaders, url, msg, fromTo.first, fromTo.second); + + if (LLApp::isQuitting()) + { + return; + } + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + std::string translation, err_msg; + std::string detected_lang(fromTo.second); + + int parseResult = status.getType(); + const LLSD::Binary &rawBody = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_RAW].asBinary(); + std::string body(rawBody.begin(), rawBody.end()); + + bool res = false; + + try + { + res = parseResponse(httpResults, parseResult, body, translation, detected_lang, err_msg); + } + catch (std::out_of_range&) + { + LL_WARNS() << "Out of range exception on string " << body << LL_ENDL; + } + catch (...) + { + LOG_UNHANDLED_EXCEPTION( "Exception on string " + body ); + } + + if (res) + { + // Fix up the response + LLStringUtil::replaceString(translation, "<", "<"); + LLStringUtil::replaceString(translation, ">", ">"); + LLStringUtil::replaceString(translation, """, "\""); + LLStringUtil::replaceString(translation, "'", "'"); + LLStringUtil::replaceString(translation, "&", "&"); + LLStringUtil::replaceString(translation, "'", "'"); + + if (!success.empty()) + success(translation, detected_lang); + } + else + { + if (err_msg.empty() && httpResults.has("error_body")) + { + err_msg = httpResults["error_body"].asString(); + } + + if (err_msg.empty()) + { + err_msg = LLTrans::getString("TranslationResponseParseError"); + } + + LL_WARNS() << "Translation request failed: " << err_msg << LL_ENDL; + if (!failure.empty()) + failure(status, err_msg); + } +} + +//========================================================================= +/// Google Translate v2 API handler. +class LLGoogleTranslationHandler : public LLTranslationAPIHandler +{ + LOG_CLASS(LLGoogleTranslationHandler); + +public: + std::string getTranslateURL( + const std::string &from_lang, + const std::string &to_lang, + const std::string &text) const override; + std::string getKeyVerificationURL( + const LLSD &key) const override; + bool checkVerificationResponse( + const LLSD &response, + int status) const override; + bool parseResponse( + const LLSD& http_response, + int& status, + const std::string& body, + std::string& translation, + std::string& detected_lang, + std::string& err_msg) const override; + bool isConfigured() const override; + + LLTranslate::EService getCurrentService() override { return LLTranslate::EService::SERVICE_GOOGLE; } + + void verifyKey(const LLSD &key, LLTranslate::KeyVerificationResult_fn fnc) override; + + void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent) const override; + void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent, const LLSD &key) const override; + LLSD sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, + LLCore::HttpRequest::ptr_t request, + LLCore::HttpOptions::ptr_t options, + LLCore::HttpHeaders::ptr_t headers, + const std::string & url, + const std::string & msg, + const std::string& from_lang, + const std::string& to_lang) const override; + + LLSD verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, + LLCore::HttpRequest::ptr_t request, + LLCore::HttpOptions::ptr_t options, + LLCore::HttpHeaders::ptr_t headers, + const std::string & url) const override; + +private: + static void parseErrorResponse( + const boost::json::value& root, + int& status, + std::string& err_msg); + static bool parseTranslation( + const boost::json::value& root, + std::string& translation, + std::string& detected_lang); + static std::string getAPIKey(); +}; + +//------------------------------------------------------------------------- +// virtual +std::string LLGoogleTranslationHandler::getTranslateURL( + const std::string &from_lang, + const std::string &to_lang, + const std::string &text) const +{ + std::string url = std::string("https://www.googleapis.com/language/translate/v2?key=") + + getAPIKey() + "&q=" + LLURI::escape(text) + "&target=" + to_lang; + if (!from_lang.empty()) + { + url += "&source=" + from_lang; + } + return url; +} + +// virtual +std::string LLGoogleTranslationHandler::getKeyVerificationURL( + const LLSD& key) const +{ + std::string url = std::string("https://www.googleapis.com/language/translate/v2/languages?key=") + + key.asString() +"&target=en"; + return url; +} + +//virtual +bool LLGoogleTranslationHandler::checkVerificationResponse( + const LLSD &response, + int status) const +{ + return status == HTTP_OK; +} + +// virtual +bool LLGoogleTranslationHandler::parseResponse( + const LLSD& http_response, + int& status, + const std::string& body, + std::string& translation, + std::string& detected_lang, + std::string& err_msg) const +{ + const std::string& text = !body.empty() ? body : http_response["error_body"].asStringRef(); + + boost::json::error_code ec; + boost::json::value root = boost::json::parse(text, ec); + if (ec.failed()) + { + err_msg = ec.what(); + return false; + } + + if (root.is_object()) + { + // Request succeeded, extract translation from the XML body. + if (parseTranslation(root, translation, detected_lang)) + return true; + + // Request failed. Extract error message from the XML body. + parseErrorResponse(root, status, err_msg); + } + + return false; +} + +// virtual +bool LLGoogleTranslationHandler::isConfigured() const +{ + return !getAPIKey().empty(); +} + +// static +void LLGoogleTranslationHandler::parseErrorResponse( + const boost::json::value& root, + int& status, + std::string& err_msg) +{ + boost::json::error_code ec; + auto message = root.find_pointer("/data/message", ec); + auto code = root.find_pointer("/data/code", ec); + if (!message || !code) + { + return; + } + + auto message_val = boost::json::try_value_to(*message); + auto code_val = boost::json::try_value_to(*code); + if (!message_val || !code_val) + { + return; + } + + err_msg = message_val.value(); + status = code_val.value(); +} + +// static +bool LLGoogleTranslationHandler::parseTranslation( + const boost::json::value& root, + std::string& translation, + std::string& detected_lang) +{ + boost::json::error_code ec; + auto translated_text = root.find_pointer("/data/translations/0/translatedText", ec); + if (!translated_text) return false; + + auto text_val = boost::json::try_value_to(*translated_text); + if (!text_val) + { + LL_WARNS() << "Failed to parse translation" << text_val.error() << LL_ENDL; + return false; + } + + translation = text_val.value(); + + auto language = root.find_pointer("/data/translations/0/detectedSourceLanguage", ec); + if (language) + { + auto lang_val = boost::json::try_value_to(*language); + detected_lang = lang_val ? lang_val.value() : ""; + } + + return true; +} + +// static +std::string LLGoogleTranslationHandler::getAPIKey() +{ + static LLCachedControl google_key(gSavedSettings, "GoogleTranslateAPIKey"); + return google_key; +} + +/*virtual*/ +void LLGoogleTranslationHandler::verifyKey(const LLSD &key, LLTranslate::KeyVerificationResult_fn fnc) +{ + LLCoros::instance().launch("Google /Verify Key", boost::bind(&LLTranslationAPIHandler::verifyKeyCoro, + this, LLTranslate::SERVICE_GOOGLE, key, fnc)); +} + +/*virtual*/ +void LLGoogleTranslationHandler::initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent) const +{ + headers->append(HTTP_OUT_HEADER_ACCEPT, HTTP_CONTENT_JSON); + headers->append(HTTP_OUT_HEADER_USER_AGENT, user_agent); +} + +/*virtual*/ +void LLGoogleTranslationHandler::initHttpHeader( + LLCore::HttpHeaders::ptr_t headers, + const std::string& user_agent, + const LLSD &key) const +{ + initHttpHeader(headers, user_agent); +} + +LLSD LLGoogleTranslationHandler::sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, + LLCore::HttpRequest::ptr_t request, + LLCore::HttpOptions::ptr_t options, + LLCore::HttpHeaders::ptr_t headers, + const std::string & url, + const std::string & msg, + const std::string& from_lang, + const std::string& to_lang) const +{ + return adapter->getRawAndSuspend(request, url, options, headers); +} + +LLSD LLGoogleTranslationHandler::verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, + LLCore::HttpRequest::ptr_t request, + LLCore::HttpOptions::ptr_t options, + LLCore::HttpHeaders::ptr_t headers, + const std::string & url) const +{ + return adapter->getAndSuspend(request, url, options, headers); +} + +//========================================================================= +/// Microsoft Translator v2 API handler. +class LLAzureTranslationHandler : public LLTranslationAPIHandler +{ + LOG_CLASS(LLAzureTranslationHandler); + +public: + std::string getTranslateURL( + const std::string &from_lang, + const std::string &to_lang, + const std::string &text) const override; + std::string getKeyVerificationURL( + const LLSD &key) const override; + bool checkVerificationResponse( + const LLSD &response, + int status) const override; + bool parseResponse( + const LLSD& http_response, + int& status, + const std::string& body, + std::string& translation, + std::string& detected_lang, + std::string& err_msg) const override; + bool isConfigured() const override; + + LLTranslate::EService getCurrentService() override { return LLTranslate::EService::SERVICE_AZURE; } + + void verifyKey(const LLSD &key, LLTranslate::KeyVerificationResult_fn fnc) override; + + void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent) const override; + void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent, const LLSD &key) const override; + LLSD sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, + LLCore::HttpRequest::ptr_t request, + LLCore::HttpOptions::ptr_t options, + LLCore::HttpHeaders::ptr_t headers, + const std::string & url, + const std::string & msg, + const std::string& from_lang, + const std::string& to_lang) const override; + + LLSD verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, + LLCore::HttpRequest::ptr_t request, + LLCore::HttpOptions::ptr_t options, + LLCore::HttpHeaders::ptr_t headers, + const std::string & url) const override; +private: + static std::string parseErrorResponse( + const std::string& body); + static LLSD getAPIKey(); + static std::string getAPILanguageCode(const std::string& lang); + +}; + +//------------------------------------------------------------------------- +// virtual +std::string LLAzureTranslationHandler::getTranslateURL( + const std::string &from_lang, + const std::string &to_lang, + const std::string &text) const +{ + std::string url; + LLSD key = getAPIKey(); + if (key.isMap()) + { + std::string endpoint = key["endpoint"].asString(); + + if (*endpoint.rbegin() != '/') + { + endpoint += "/"; + } + url = endpoint + std::string("translate?api-version=3.0&to=") + + getAPILanguageCode(to_lang); + } + return url; +} + + +// virtual +std::string LLAzureTranslationHandler::getKeyVerificationURL( + const LLSD& key) const +{ + std::string url; + if (key.isMap()) + { + std::string endpoint = key["endpoint"].asString(); + if (*endpoint.rbegin() != '/') + { + endpoint += "/"; + } + url = endpoint + std::string("translate?api-version=3.0&to=en"); + } + return url; +} + +//virtual +bool LLAzureTranslationHandler::checkVerificationResponse( + const LLSD &response, + int status) const +{ + if (status == HTTP_UNAUTHORIZED) + { + LL_DEBUGS("Translate") << "Key unathorised" << LL_ENDL; + return false; + } + + if (status == HTTP_NOT_FOUND) + { + LL_DEBUGS("Translate") << "Either endpoint doesn't have requested resource" << LL_ENDL; + return false; + } + + if (status != HTTP_BAD_REQUEST) + { + LL_DEBUGS("Translate") << "Unexpected error code" << LL_ENDL; + return false; + } + + if (!response.has("error_body")) + { + LL_DEBUGS("Translate") << "Unexpected response, no error returned" << LL_ENDL; + return false; + } + + // Expected: "{\"error\":{\"code\":400000,\"message\":\"One of the request inputs is not valid.\"}}" + // But for now just verify response is a valid json + + boost::json::error_code ec; + boost::json::value root = boost::json::parse(response["error_body"].asString(), ec); + if (ec.failed()) + { + LL_DEBUGS("Translate") << "Failed to parse error_body:" << ec.what() << LL_ENDL; + return false; + } + + return true; +} + +// virtual +bool LLAzureTranslationHandler::parseResponse( + const LLSD& http_response, + int& status, + const std::string& body, + std::string& translation, + std::string& detected_lang, + std::string& err_msg) const +{ + if (status != HTTP_OK) + { + if (http_response.has("error_body")) + err_msg = parseErrorResponse(http_response["error_body"].asString()); + return false; + } + + //Example: + // "[{\"detectedLanguage\":{\"language\":\"en\",\"score\":1.0},\"translations\":[{\"text\":\"Hello, what is your name?\",\"to\":\"en\"}]}]" + + boost::json::error_code ec; + boost::json::value root = boost::json::parse(body, ec); + if (ec.failed()) + { + err_msg = ec.what(); + return false; + } + auto language = root.find_pointer("/0/detectedLanguage/language", ec); + if (!language) return false; + + auto translated_text = root.find_pointer("/0/translations/0/text", ec); + if (!translated_text) return false; + + auto lang_val = boost::json::try_value_to(*language); + auto text_val = boost::json::try_value_to(*translated_text); + if (!lang_val || !text_val) + { + LL_WARNS() << "Failed to parse translation" << lang_val.error() << text_val.error() << LL_ENDL; + return false; + } + + detected_lang = lang_val.value(); + translation = text_val.value(); + + return true; +} + +// virtual +bool LLAzureTranslationHandler::isConfigured() const +{ + return getAPIKey().isMap(); +} + +//static +std::string LLAzureTranslationHandler::parseErrorResponse( + const std::string& body) +{ + // Expected: "{\"error\":{\"code\":400000,\"message\":\"One of the request inputs is not valid.\"}}" + // But for now just verify response is a valid json with an error + + boost::json::error_code ec; + boost::json::value root = boost::json::parse(body, ec); + if (ec.failed()) + { + return {}; + } + + auto err_msg = root.find_pointer("/error/message", ec); + if (!err_msg) + { + return {}; + } + + auto err_msg_val = boost::json::try_value_to(*err_msg); + if (!err_msg_val) + { + return {}; + } + return err_msg_val.value(); +} + +// static +LLSD LLAzureTranslationHandler::getAPIKey() +{ + static LLCachedControl azure_key(gSavedSettings, "AzureTranslateAPIKey"); + return azure_key; +} + +// static +std::string LLAzureTranslationHandler::getAPILanguageCode(const std::string& lang) +{ + return lang == "zh" ? "zh-CHT" : lang; // treat Chinese as Traditional Chinese +} + +/*virtual*/ +void LLAzureTranslationHandler::verifyKey(const LLSD &key, LLTranslate::KeyVerificationResult_fn fnc) +{ + LLCoros::instance().launch("Azure /Verify Key", boost::bind(&LLTranslationAPIHandler::verifyKeyCoro, + this, LLTranslate::SERVICE_AZURE, key, fnc)); +} +/*virtual*/ +void LLAzureTranslationHandler::initHttpHeader( + LLCore::HttpHeaders::ptr_t headers, + const std::string& user_agent) const +{ + initHttpHeader(headers, user_agent, getAPIKey()); +} + +/*virtual*/ +void LLAzureTranslationHandler::initHttpHeader( + LLCore::HttpHeaders::ptr_t headers, + const std::string& user_agent, + const LLSD &key) const +{ + headers->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_JSON); + headers->append(HTTP_OUT_HEADER_USER_AGENT, user_agent); + + if (key.has("id")) + { + // Token based autorization + headers->append("Ocp-Apim-Subscription-Key", key["id"].asString()); + } + if (key.has("region")) + { + // ex: "westeurope" + headers->append("Ocp-Apim-Subscription-Region", key["region"].asString()); + } +} + +LLSD LLAzureTranslationHandler::sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, + LLCore::HttpRequest::ptr_t request, + LLCore::HttpOptions::ptr_t options, + LLCore::HttpHeaders::ptr_t headers, + const std::string & url, + const std::string & msg, + const std::string& from_lang, + const std::string& to_lang) const +{ + LLCore::BufferArray::ptr_t rawbody(new LLCore::BufferArray); + LLCore::BufferArrayStream outs(rawbody.get()); + + static const std::string allowed_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz " + "0123456789" + "-._~"; + + outs << "[{\"text\":\""; + outs << LLURI::escape(msg, allowed_chars); + outs << "\"}]"; + + return adapter->postRawAndSuspend(request, url, rawbody, options, headers); +} + +LLSD LLAzureTranslationHandler::verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, + LLCore::HttpRequest::ptr_t request, + LLCore::HttpOptions::ptr_t options, + LLCore::HttpHeaders::ptr_t headers, + const std::string & url) const +{ + LLCore::BufferArray::ptr_t rawbody(new LLCore::BufferArray); + LLCore::BufferArrayStream outs(rawbody.get()); + outs << "[{\"intentionally_invalid_400\"}]"; + + return adapter->postRawAndSuspend(request, url, rawbody, options, headers); +} + +//========================================================================= +/// DeepL Translator API handler. +class LLDeepLTranslationHandler: public LLTranslationAPIHandler +{ + LOG_CLASS(LLDeepLTranslationHandler); + +public: + std::string getTranslateURL( + const std::string& from_lang, + const std::string& to_lang, + const std::string& text) const override; + std::string getKeyVerificationURL( + const LLSD& key) const override; + bool checkVerificationResponse( + const LLSD& response, + int status) const override; + bool parseResponse( + const LLSD& http_response, + int& status, + const std::string& body, + std::string& translation, + std::string& detected_lang, + std::string& err_msg) const override; + bool isConfigured() const override; + + LLTranslate::EService getCurrentService() override + { + return LLTranslate::EService::SERVICE_DEEPL; + } + + void verifyKey(const LLSD& key, LLTranslate::KeyVerificationResult_fn fnc) override; + + void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent) const override; + void initHttpHeader(LLCore::HttpHeaders::ptr_t headers, const std::string& user_agent, const LLSD& key) const override; + LLSD sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, + LLCore::HttpRequest::ptr_t request, + LLCore::HttpOptions::ptr_t options, + LLCore::HttpHeaders::ptr_t headers, + const std::string& url, + const std::string& msg, + const std::string& from_lang, + const std::string& to_lang) const override; + + LLSD verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, + LLCore::HttpRequest::ptr_t request, + LLCore::HttpOptions::ptr_t options, + LLCore::HttpHeaders::ptr_t headers, + const std::string& url) const override; +private: + static std::string parseErrorResponse( + const std::string& body); + static LLSD getAPIKey(); + static std::string getAPILanguageCode(const std::string& lang); +}; + +//------------------------------------------------------------------------- +// virtual +std::string LLDeepLTranslationHandler::getTranslateURL( + const std::string& from_lang, + const std::string& to_lang, + const std::string& text) const +{ + std::string url; + LLSD key = getAPIKey(); + if (key.isMap()) + { + url = key["domain"].asString(); + + if (*url.rbegin() != '/') + { + url += "/"; + } + url += std::string("v2/translate"); + } + return url; +} + + +// virtual +std::string LLDeepLTranslationHandler::getKeyVerificationURL( + const LLSD& key) const +{ + std::string url; + if (key.isMap()) + { + url = key["domain"].asString(); + + if (*url.rbegin() != '/') + { + url += "/"; + } + url += std::string("v2/translate"); + } + return url; +} + +//virtual +bool LLDeepLTranslationHandler::checkVerificationResponse( + const LLSD& response, + int status) const +{ + // Might need to parse body to make sure we got + // a valid response and not a message + return status == HTTP_OK; +} + +// virtual +bool LLDeepLTranslationHandler::parseResponse( + const LLSD& http_response, + int& status, + const std::string& body, + std::string& translation, + std::string& detected_lang, + std::string& err_msg) const +{ + if (status != HTTP_OK) + { + if (http_response.has("error_body")) + err_msg = parseErrorResponse(http_response["error_body"].asString()); + return false; + } + + //Example: + // "{\"translations\":[{\"detected_source_language\":\"EN\",\"text\":\"test\"}]}" + + boost::json::error_code ec; + boost::json::value root = boost::json::parse(body, ec); + if (ec.failed()) + { + err_msg = ec.message(); + return false; + } + + auto detected_langp = root.find_pointer("/translations/0/detected_source_language", ec); + if (!detected_langp || ec.failed()) // empty response? should not happen + { + err_msg = ec.message(); + return false; + } + + // Request succeeded, extract translation from the response. + auto text_valp = root.find_pointer("/translations/0/text", ec); + if (!text_valp || ec.failed()) + { + err_msg = ec.message(); + return false; + } + + auto lang_result = boost::json::try_value_to(*detected_langp); + auto text_result = boost::json::try_value_to(*text_valp); + if (!lang_result || !text_result) + { + return false; + } + + detected_lang = lang_result.value(); + LLStringUtil::toLower(detected_lang); + translation = text_result.value(); + + return true; +} + +// virtual +bool LLDeepLTranslationHandler::isConfigured() const +{ + return getAPIKey().isMap(); +} + +//static +std::string LLDeepLTranslationHandler::parseErrorResponse( + const std::string& body) +{ + // Example: "{\"message\":\"One of the request inputs is not valid.\"}" + boost::json::error_code ec; + boost::json::value root = boost::json::parse(body, ec); + if (ec.failed()) + { + return {}; + } + + auto message_ptr = root.find_pointer("/message", ec); + if (!message_ptr || ec.failed()) + { + return {}; + } + + auto message_val = boost::json::try_value_to(*message_ptr); + if (!message_val) + return {}; + + return message_val.value(); +} + +// static +LLSD LLDeepLTranslationHandler::getAPIKey() +{ + static LLCachedControl deepl_key(gSavedSettings, "DeepLTranslateAPIKey"); + return deepl_key; +} + +// static +std::string LLDeepLTranslationHandler::getAPILanguageCode(const std::string& lang) +{ + return lang == "zh" ? "zh-CHT" : lang; // treat Chinese as Traditional Chinese +} + +/*virtual*/ +void LLDeepLTranslationHandler::verifyKey(const LLSD& key, LLTranslate::KeyVerificationResult_fn fnc) +{ + LLCoros::instance().launch("DeepL /Verify Key", boost::bind(&LLTranslationAPIHandler::verifyKeyCoro, + this, LLTranslate::SERVICE_DEEPL, key, fnc)); +} +/*virtual*/ +void LLDeepLTranslationHandler::initHttpHeader( + LLCore::HttpHeaders::ptr_t headers, + const std::string& user_agent) const +{ + initHttpHeader(headers, user_agent, getAPIKey()); +} + +/*virtual*/ +void LLDeepLTranslationHandler::initHttpHeader( + LLCore::HttpHeaders::ptr_t headers, + const std::string& user_agent, + const LLSD& key) const +{ + headers->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/x-www-form-urlencoded"); + headers->append(HTTP_OUT_HEADER_USER_AGENT, user_agent); + + if (key.has("id")) + { + std::string authkey = "DeepL-Auth-Key " + key["id"].asString(); + headers->append(HTTP_OUT_HEADER_AUTHORIZATION, authkey); + } +} + +LLSD LLDeepLTranslationHandler::sendMessageAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, + LLCore::HttpRequest::ptr_t request, + LLCore::HttpOptions::ptr_t options, + LLCore::HttpHeaders::ptr_t headers, + const std::string& url, + const std::string& msg, + const std::string& from_lang, + const std::string& to_lang) const +{ + LLCore::BufferArray::ptr_t rawbody(new LLCore::BufferArray); + LLCore::BufferArrayStream outs(rawbody.get()); + outs << "text="; + std::string escaped_string = LLURI::escape(msg); + outs << escaped_string; + outs << "&target_lang="; + std::string lang = to_lang; + LLStringUtil::toUpper(lang); + outs << lang; + + return adapter->postRawAndSuspend(request, url, rawbody, options, headers); +} + +LLSD LLDeepLTranslationHandler::verifyAndSuspend(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter, + LLCore::HttpRequest::ptr_t request, + LLCore::HttpOptions::ptr_t options, + LLCore::HttpHeaders::ptr_t headers, + const std::string& url) const +{ + LLCore::BufferArray::ptr_t rawbody(new LLCore::BufferArray); + LLCore::BufferArrayStream outs(rawbody.get()); + outs << "text=&target_lang=EN"; + + return adapter->postRawAndSuspend(request, url, rawbody, options, headers); +} + +//========================================================================= +LLTranslate::LLTranslate(): + mCharsSeen(0), + mCharsSent(0), + mFailureCount(0), + mSuccessCount(0) +{ +} + +LLTranslate::~LLTranslate() +{ +} + +/*static*/ +void LLTranslate::translateMessage(const std::string &from_lang, const std::string &to_lang, + const std::string &mesg, TranslationSuccess_fn success, TranslationFailure_fn failure) +{ + LLTranslationAPIHandler& handler = getPreferredHandler(); + + handler.translateMessage(LLTranslationAPIHandler::LanguagePair_t(from_lang, to_lang), addNoTranslateTags(mesg), success, failure); +} + +std::string LLTranslate::addNoTranslateTags(std::string mesg) +{ + if (getPreferredHandler().getCurrentService() == SERVICE_GOOGLE) + { + return mesg; + } + + if (getPreferredHandler().getCurrentService() == SERVICE_DEEPL) + { + return mesg; + } + + if (getPreferredHandler().getCurrentService() == SERVICE_AZURE) + { + // https://learn.microsoft.com/en-us/azure/cognitive-services/translator/prevent-translation + std::string upd_msg(mesg); + LLUrlMatch match; + S32 dif = 0; + //surround all links (including SLURLs) with 'no-translate' tags to prevent unnecessary translation + while (LLUrlRegistry::instance().findUrl(mesg, match)) + { + upd_msg.insert(dif + match.getStart(), AZURE_NOTRANSLATE_OPENING_TAG); + upd_msg.insert(dif + AZURE_NOTRANSLATE_OPENING_TAG.size() + match.getEnd() + 1, AZURE_NOTRANSLATE_CLOSING_TAG); + mesg.erase(match.getStart(), match.getEnd() - match.getStart()); + dif += match.getEnd() - match.getStart() + AZURE_NOTRANSLATE_OPENING_TAG.size() + AZURE_NOTRANSLATE_CLOSING_TAG.size(); + } + return upd_msg; + } + return mesg; +} + +std::string LLTranslate::removeNoTranslateTags(std::string mesg) +{ + if (getPreferredHandler().getCurrentService() == SERVICE_GOOGLE) + { + return mesg; + } + if (getPreferredHandler().getCurrentService() == SERVICE_DEEPL) + { + return mesg; + } + + if (getPreferredHandler().getCurrentService() == SERVICE_AZURE) + { + std::string upd_msg(mesg); + LLUrlMatch match; + S32 opening_tag_size = AZURE_NOTRANSLATE_OPENING_TAG.size(); + S32 closing_tag_size = AZURE_NOTRANSLATE_CLOSING_TAG.size(); + S32 dif = 0; + //remove 'no-translate' tags we added to the links before + while (LLUrlRegistry::instance().findUrl(mesg, match)) + { + if (upd_msg.substr(dif + match.getStart() - opening_tag_size, opening_tag_size) == AZURE_NOTRANSLATE_OPENING_TAG) + { + upd_msg.erase(dif + match.getStart() - opening_tag_size, opening_tag_size); + dif -= opening_tag_size; + + if (upd_msg.substr(dif + match.getEnd() + 1, closing_tag_size) == AZURE_NOTRANSLATE_CLOSING_TAG) + { + upd_msg.replace(dif + match.getEnd() + 1, closing_tag_size, " "); + dif -= closing_tag_size - 1; + } + } + mesg.erase(match.getStart(), match.getUrl().size()); + dif += match.getUrl().size(); + } + return upd_msg; + } + + return mesg; +} + +/*static*/ +void LLTranslate::verifyKey(EService service, const LLSD &key, KeyVerificationResult_fn fnc) +{ + LLTranslationAPIHandler& handler = getHandler(service); + + handler.verifyKey(key, fnc); +} + + +//static +std::string LLTranslate::getTranslateLanguage() +{ + std::string language = gSavedSettings.getString("TranslateLanguage"); + if (language.empty() || language == "default") + { + language = LLUI::getLanguage(); + } + language = language.substr(0,2); + return language; +} + +// static +bool LLTranslate::isTranslationConfigured() +{ + return getPreferredHandler().isConfigured(); +} + +void LLTranslate::logCharsSeen(size_t count) +{ + mCharsSeen += count; +} + +void LLTranslate::logCharsSent(size_t count) +{ + mCharsSent += count; +} + +void LLTranslate::logSuccess(S32 count) +{ + mSuccessCount += count; +} + +void LLTranslate::logFailure(S32 count) +{ + mFailureCount += count; +} + +LLSD LLTranslate::asLLSD() const +{ + LLSD res; + bool on = gSavedSettings.getBOOL("TranslateChat"); + res["on"] = on; + res["chars_seen"] = (S32) mCharsSeen; + if (on) + { + res["chars_sent"] = (S32) mCharsSent; + res["success_count"] = mSuccessCount; + res["failure_count"] = mFailureCount; + res["language"] = getTranslateLanguage(); + res["service"] = gSavedSettings.getString("TranslationService"); + } + return res; +} + +// static +LLTranslationAPIHandler& LLTranslate::getPreferredHandler() +{ + EService service = SERVICE_AZURE; + + std::string service_str = gSavedSettings.getString("TranslationService"); + if (service_str == "google") + { + service = SERVICE_GOOGLE; + } + if (service_str == "azure") + { + service = SERVICE_AZURE; + } + if (service_str == "deepl") + { + service = SERVICE_DEEPL; + } + + return getHandler(service); +} + +// static +LLTranslationAPIHandler& LLTranslate::getHandler(EService service) +{ + static LLGoogleTranslationHandler google; + static LLAzureTranslationHandler azure; + static LLDeepLTranslationHandler deepl; + + switch (service) + { + case SERVICE_AZURE: + return azure; + case SERVICE_GOOGLE: + return google; + case SERVICE_DEEPL: + return deepl; + } + + return azure; +} diff --git a/indra/newview/lltranslate.h b/indra/newview/lltranslate.h index 4dec49bf65..0ad769b27f 100644 --- a/indra/newview/lltranslate.h +++ b/indra/newview/lltranslate.h @@ -1,112 +1,112 @@ -/** -* @file lltranslate.h -* @brief Human language translation class and JSON response receiver. -* - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLTRANSLATE_H -#define LL_LLTRANSLATE_H - -#include "llbufferstream.h" -#include - -#include "llsingleton.h" - -class LLTranslationAPIHandler; -/** - * Entry point for machine translation services. - * - * Basically, to translate a string, we need to know the URL - * of a translation service, have a valid API for the service - * and be given the target language. - * - * Callers specify the string to translate and the target language, - * LLTranslate takes care of the rest. - * - * API keys for translation are taken from saved settings. - */ -class LLTranslate: public LLSingleton -{ - LLSINGLETON(LLTranslate); - ~LLTranslate(); - LOG_CLASS(LLTranslate); - -public : - - typedef enum e_service { - SERVICE_AZURE, - SERVICE_GOOGLE, - SERVICE_DEEPL, - } EService; - - typedef boost::function KeyVerificationResult_fn; - typedef boost::function TranslationSuccess_fn; - typedef boost::function TranslationFailure_fn; - - /** - * Translate given text. - * - * @param receiver Object to pass translation result to. - * @param from_lang Source language. Leave empty for auto-detection. - * @param to_lang Target language. - * @param mesg Text to translate. - */ - static void translateMessage(const std::string &from_lang, const std::string &to_lang, const std::string &mesg, TranslationSuccess_fn success, TranslationFailure_fn failure); - - /** - * Verify given API key of a translation service. - * - * @param receiver Object to pass verification result to. - * @param key Key to verify. - */ - static void verifyKey(EService service, const LLSD &key, KeyVerificationResult_fn fnc); - - /** - * @return translation target language - */ - static std::string getTranslateLanguage(); - - /** - * @return true if translation is configured properly. - */ - static bool isTranslationConfigured(); - - static std::string addNoTranslateTags(std::string mesg); - static std::string removeNoTranslateTags(std::string mesg); - - void logCharsSeen(size_t count); - void logCharsSent(size_t count); - void logSuccess(S32 count); - void logFailure(S32 count); - LLSD asLLSD() const; -private: - static LLTranslationAPIHandler& getPreferredHandler(); - static LLTranslationAPIHandler& getHandler(EService service); - - size_t mCharsSeen; - size_t mCharsSent; - S32 mFailureCount; - S32 mSuccessCount; -}; - -#endif +/** +* @file lltranslate.h +* @brief Human language translation class and JSON response receiver. +* + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLTRANSLATE_H +#define LL_LLTRANSLATE_H + +#include "llbufferstream.h" +#include + +#include "llsingleton.h" + +class LLTranslationAPIHandler; +/** + * Entry point for machine translation services. + * + * Basically, to translate a string, we need to know the URL + * of a translation service, have a valid API for the service + * and be given the target language. + * + * Callers specify the string to translate and the target language, + * LLTranslate takes care of the rest. + * + * API keys for translation are taken from saved settings. + */ +class LLTranslate: public LLSingleton +{ + LLSINGLETON(LLTranslate); + ~LLTranslate(); + LOG_CLASS(LLTranslate); + +public : + + typedef enum e_service { + SERVICE_AZURE, + SERVICE_GOOGLE, + SERVICE_DEEPL, + } EService; + + typedef boost::function KeyVerificationResult_fn; + typedef boost::function TranslationSuccess_fn; + typedef boost::function TranslationFailure_fn; + + /** + * Translate given text. + * + * @param receiver Object to pass translation result to. + * @param from_lang Source language. Leave empty for auto-detection. + * @param to_lang Target language. + * @param mesg Text to translate. + */ + static void translateMessage(const std::string &from_lang, const std::string &to_lang, const std::string &mesg, TranslationSuccess_fn success, TranslationFailure_fn failure); + + /** + * Verify given API key of a translation service. + * + * @param receiver Object to pass verification result to. + * @param key Key to verify. + */ + static void verifyKey(EService service, const LLSD &key, KeyVerificationResult_fn fnc); + + /** + * @return translation target language + */ + static std::string getTranslateLanguage(); + + /** + * @return true if translation is configured properly. + */ + static bool isTranslationConfigured(); + + static std::string addNoTranslateTags(std::string mesg); + static std::string removeNoTranslateTags(std::string mesg); + + void logCharsSeen(size_t count); + void logCharsSent(size_t count); + void logSuccess(S32 count); + void logFailure(S32 count); + LLSD asLLSD() const; +private: + static LLTranslationAPIHandler& getPreferredHandler(); + static LLTranslationAPIHandler& getHandler(EService service); + + size_t mCharsSeen; + size_t mCharsSent; + S32 mFailureCount; + S32 mSuccessCount; +}; + +#endif diff --git a/indra/newview/lluiavatar.cpp b/indra/newview/lluiavatar.cpp index 81567a6fee..c687c4f162 100644 --- a/indra/newview/lluiavatar.cpp +++ b/indra/newview/lluiavatar.cpp @@ -1,61 +1,61 @@ -/** - * @file lluiavatar.cpp - * @brief Implementation for special dummy avatar used in some UI views - * - * $LicenseInfo:firstyear=2017&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2017, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "lluiavatar.h" -#include "llagent.h" // Get state values from here -#include "llviewerobjectlist.h" -#include "pipeline.h" -#include "llanimationstates.h" -#include "llviewercontrol.h" -#include "llmeshrepository.h" -#include "llviewerregion.h" - -LLUIAvatar::LLUIAvatar(const LLUUID& id, const LLPCode pcode, LLViewerRegion* regionp) : - LLVOAvatar(id, pcode, regionp) -{ - mIsDummy = true; - mIsUIAvatar = true; -} - -// virtual -LLUIAvatar::~LLUIAvatar() -{ -} - -// virtual -void LLUIAvatar::initInstance() -{ - LLVOAvatar::initInstance(); - - createDrawable( &gPipeline ); - setPositionAgent(LLVector3::zero); - slamPosition(); - updateJointLODs(); - updateGeometry(mDrawable); - - mInitFlags |= 1<<3; -} +/** + * @file lluiavatar.cpp + * @brief Implementation for special dummy avatar used in some UI views + * + * $LicenseInfo:firstyear=2017&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2017, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "lluiavatar.h" +#include "llagent.h" // Get state values from here +#include "llviewerobjectlist.h" +#include "pipeline.h" +#include "llanimationstates.h" +#include "llviewercontrol.h" +#include "llmeshrepository.h" +#include "llviewerregion.h" + +LLUIAvatar::LLUIAvatar(const LLUUID& id, const LLPCode pcode, LLViewerRegion* regionp) : + LLVOAvatar(id, pcode, regionp) +{ + mIsDummy = true; + mIsUIAvatar = true; +} + +// virtual +LLUIAvatar::~LLUIAvatar() +{ +} + +// virtual +void LLUIAvatar::initInstance() +{ + LLVOAvatar::initInstance(); + + createDrawable( &gPipeline ); + setPositionAgent(LLVector3::zero); + slamPosition(); + updateJointLODs(); + updateGeometry(mDrawable); + + mInitFlags |= 1<<3; +} diff --git a/indra/newview/lluiavatar.h b/indra/newview/lluiavatar.h index 862d0591e8..3df3f5afa2 100644 --- a/indra/newview/lluiavatar.h +++ b/indra/newview/lluiavatar.h @@ -1,45 +1,45 @@ -/** - * @file lluiavatar.h - * @brief Special dummy avatar used in some UI views - * - * $LicenseInfo:firstyear=2017&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2017, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_UIAVATAR_H -#define LL_UIAVATAR_H - -#include "llvoavatar.h" -#include "llvovolume.h" - -class LLUIAvatar: - public LLVOAvatar -{ - LOG_CLASS(LLUIAvatar); - -public: - LLUIAvatar(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); - virtual void initInstance(); // Called after construction to initialize the class. - virtual ~LLUIAvatar(); - virtual bool isBuddy() const { return false; } -}; - -#endif //LL_CONTROLAVATAR_H +/** + * @file lluiavatar.h + * @brief Special dummy avatar used in some UI views + * + * $LicenseInfo:firstyear=2017&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2017, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_UIAVATAR_H +#define LL_UIAVATAR_H + +#include "llvoavatar.h" +#include "llvovolume.h" + +class LLUIAvatar: + public LLVOAvatar +{ + LOG_CLASS(LLUIAvatar); + +public: + LLUIAvatar(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); + virtual void initInstance(); // Called after construction to initialize the class. + virtual ~LLUIAvatar(); + virtual bool isBuddy() const { return false; } +}; + +#endif //LL_CONTROLAVATAR_H diff --git a/indra/newview/lluploaddialog.cpp b/indra/newview/lluploaddialog.cpp index 8641cdc114..ec54123074 100644 --- a/indra/newview/lluploaddialog.cpp +++ b/indra/newview/lluploaddialog.cpp @@ -1,162 +1,162 @@ -/** - * @file lluploaddialog.cpp - * @brief LLUploadDialog class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "lluploaddialog.h" -#include "llviewerwindow.h" -#include "llfontgl.h" -#include "llresmgr.h" -#include "lltextbox.h" -#include "llbutton.h" -#include "llkeyboard.h" -#include "llfocusmgr.h" -#include "llviewercontrol.h" -#include "llrootview.h" - -// static -LLUploadDialog* LLUploadDialog::sDialog = NULL; - -// static -LLUploadDialog* LLUploadDialog::modalUploadDialog(const std::string& msg) -{ - // Note: object adds, removes, and destroys itself. - return new LLUploadDialog(msg); -} - -// static -void LLUploadDialog::modalUploadFinished() -{ - // Note: object adds, removes, and destroys itself. - delete LLUploadDialog::sDialog; - LLUploadDialog::sDialog = NULL; -} - -//////////////////////////////////////////////////////////// -// Private methods - -LLUploadDialog::LLUploadDialog( const std::string& msg) - : LLPanel() -{ - setBackgroundVisible( true ); - - if( LLUploadDialog::sDialog ) - { - delete LLUploadDialog::sDialog; - } - LLUploadDialog::sDialog = this; - - const LLFontGL* font = LLFontGL::getFontSansSerif(); - LLRect msg_rect; - for (int line_num=0; line_num<16; ++line_num) - { - LLTextBox::Params params; - params.name("Filename"); - params.rect(msg_rect); - params.initial_value("Filename"); - params.font(font); - mLabelBox[line_num] = LLUICtrlFactory::create (params); - addChild(mLabelBox[line_num]); - } - - setMessage(msg); - - // The dialog view is a root view - gViewerWindow->addPopup(this); -} - -void LLUploadDialog::setMessage( const std::string& msg) -{ - const LLFontGL* font = LLFontGL::getFontSansSerif(); - - const S32 VPAD = 16; - const S32 HPAD = 25; - - // Make the text boxes a little wider than the text - const S32 TEXT_PAD = 8; - - // Split message into lines, separated by '\n' - S32 max_msg_width = 0; - std::list msg_lines; - - S32 size = msg.size() + 1; - std::vector temp_msg(size); // non-const copy to make strtok happy - strcpy( &temp_msg[0], msg.c_str()); - char* token = strtok( &temp_msg[0], "\n" ); - while( token ) - { - std::string tokstr(token); - S32 cur_width = S32(font->getWidth(tokstr) + 0.99f) + TEXT_PAD; - max_msg_width = llmax( max_msg_width, cur_width ); - msg_lines.push_back( tokstr ); - token = strtok( NULL, "\n" ); - } - - S32 line_height = font->getLineHeight(); - S32 dialog_width = max_msg_width + 2 * HPAD; - S32 dialog_height = line_height * msg_lines.size() + 2 * VPAD; - - reshape( dialog_width, dialog_height, false ); - - // Message - S32 msg_x = (getRect().getWidth() - max_msg_width) / 2; - S32 msg_y = getRect().getHeight() - VPAD - line_height; - int line_num; - for (line_num=0; line_num<16; ++line_num) - { - mLabelBox[line_num]->setVisible(false); - } - line_num = 0; - for (std::list::iterator iter = msg_lines.begin(); - iter != msg_lines.end(); ++iter) - { - std::string& cur_line = *iter; - LLRect msg_rect; - msg_rect.setOriginAndSize( msg_x, msg_y, max_msg_width, line_height ); - mLabelBox[line_num]->setRect(msg_rect); - mLabelBox[line_num]->setText(cur_line); - mLabelBox[line_num]->setColor( LLUIColorTable::instance().getColor( "LabelTextColor" ) ); - mLabelBox[line_num]->setVisible(true); - msg_y -= line_height; - ++line_num; - } - - centerWithin(gViewerWindow->getRootView()->getRect()); -} - -LLUploadDialog::~LLUploadDialog() -{ - gViewerWindow->removePopup(this); - gFocusMgr.releaseFocusIfNeeded( this ); - -// LLFilePicker::instance().reset(); - - - LLUploadDialog::sDialog = NULL; -} - - - +/** + * @file lluploaddialog.cpp + * @brief LLUploadDialog class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "lluploaddialog.h" +#include "llviewerwindow.h" +#include "llfontgl.h" +#include "llresmgr.h" +#include "lltextbox.h" +#include "llbutton.h" +#include "llkeyboard.h" +#include "llfocusmgr.h" +#include "llviewercontrol.h" +#include "llrootview.h" + +// static +LLUploadDialog* LLUploadDialog::sDialog = NULL; + +// static +LLUploadDialog* LLUploadDialog::modalUploadDialog(const std::string& msg) +{ + // Note: object adds, removes, and destroys itself. + return new LLUploadDialog(msg); +} + +// static +void LLUploadDialog::modalUploadFinished() +{ + // Note: object adds, removes, and destroys itself. + delete LLUploadDialog::sDialog; + LLUploadDialog::sDialog = NULL; +} + +//////////////////////////////////////////////////////////// +// Private methods + +LLUploadDialog::LLUploadDialog( const std::string& msg) + : LLPanel() +{ + setBackgroundVisible( true ); + + if( LLUploadDialog::sDialog ) + { + delete LLUploadDialog::sDialog; + } + LLUploadDialog::sDialog = this; + + const LLFontGL* font = LLFontGL::getFontSansSerif(); + LLRect msg_rect; + for (int line_num=0; line_num<16; ++line_num) + { + LLTextBox::Params params; + params.name("Filename"); + params.rect(msg_rect); + params.initial_value("Filename"); + params.font(font); + mLabelBox[line_num] = LLUICtrlFactory::create (params); + addChild(mLabelBox[line_num]); + } + + setMessage(msg); + + // The dialog view is a root view + gViewerWindow->addPopup(this); +} + +void LLUploadDialog::setMessage( const std::string& msg) +{ + const LLFontGL* font = LLFontGL::getFontSansSerif(); + + const S32 VPAD = 16; + const S32 HPAD = 25; + + // Make the text boxes a little wider than the text + const S32 TEXT_PAD = 8; + + // Split message into lines, separated by '\n' + S32 max_msg_width = 0; + std::list msg_lines; + + S32 size = msg.size() + 1; + std::vector temp_msg(size); // non-const copy to make strtok happy + strcpy( &temp_msg[0], msg.c_str()); + char* token = strtok( &temp_msg[0], "\n" ); + while( token ) + { + std::string tokstr(token); + S32 cur_width = S32(font->getWidth(tokstr) + 0.99f) + TEXT_PAD; + max_msg_width = llmax( max_msg_width, cur_width ); + msg_lines.push_back( tokstr ); + token = strtok( NULL, "\n" ); + } + + S32 line_height = font->getLineHeight(); + S32 dialog_width = max_msg_width + 2 * HPAD; + S32 dialog_height = line_height * msg_lines.size() + 2 * VPAD; + + reshape( dialog_width, dialog_height, false ); + + // Message + S32 msg_x = (getRect().getWidth() - max_msg_width) / 2; + S32 msg_y = getRect().getHeight() - VPAD - line_height; + int line_num; + for (line_num=0; line_num<16; ++line_num) + { + mLabelBox[line_num]->setVisible(false); + } + line_num = 0; + for (std::list::iterator iter = msg_lines.begin(); + iter != msg_lines.end(); ++iter) + { + std::string& cur_line = *iter; + LLRect msg_rect; + msg_rect.setOriginAndSize( msg_x, msg_y, max_msg_width, line_height ); + mLabelBox[line_num]->setRect(msg_rect); + mLabelBox[line_num]->setText(cur_line); + mLabelBox[line_num]->setColor( LLUIColorTable::instance().getColor( "LabelTextColor" ) ); + mLabelBox[line_num]->setVisible(true); + msg_y -= line_height; + ++line_num; + } + + centerWithin(gViewerWindow->getRootView()->getRect()); +} + +LLUploadDialog::~LLUploadDialog() +{ + gViewerWindow->removePopup(this); + gFocusMgr.releaseFocusIfNeeded( this ); + +// LLFilePicker::instance().reset(); + + + LLUploadDialog::sDialog = NULL; +} + + + diff --git a/indra/newview/llurl.cpp b/indra/newview/llurl.cpp index 0c91f2ece2..a4eb231341 100644 --- a/indra/newview/llurl.cpp +++ b/indra/newview/llurl.cpp @@ -1,290 +1,290 @@ -/** - * @file llurl.cpp - * @brief Text url class - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llurl.h" -#include "llerror.h" - -LLURL::LLURL() -{ - init(""); -} - -LLURL::LLURL(const LLURL &url) -{ - if (this != &url) - { - init(url.getFQURL()); - } - else - { - init(""); - } -} - -LLURL::LLURL(const char * url) -{ - init(url); -} - -LLURL::~LLURL() -{ - cleanup(); -} - -void LLURL::init(const char * url) -{ - mURI[0] = '\0'; - mAuthority[0] = '\0'; - mPath[0] = '\0'; - mFilename[0] = '\0'; - mExtension[0] = '\0'; - mTag[0] = '\0'; - - char url_copy[MAX_STRING]; /* Flawfinder: ignore */ - - strncpy (url_copy,url, MAX_STRING -1); /* Flawfinder: ignore */ - url_copy[MAX_STRING -1] = '\0'; - - char *parse; - char *leftover_url = url_copy; - S32 span = 0; - - // copy and lop off tag - if ((parse = strchr(url_copy,'#'))) - { - strncpy(mTag,parse+1, LL_MAX_PATH -1); /* Flawfinder: ignore */ - mTag[LL_MAX_PATH -1] = '\0'; - *parse = '\0'; - } - - // copy out URI if it exists, update leftover_url - if ((parse = strchr(url_copy,':'))) - { - *parse = '\0'; - strncpy(mURI,leftover_url, LL_MAX_PATH -1); /* Flawfinder: ignore */ - mURI[LL_MAX_PATH -1] = '\0'; - leftover_url = parse + 1; - } - - // copy out authority if it exists, update leftover_url - if ((leftover_url[0] == '/') && (leftover_url[1] == '/')) - { - leftover_url += 2; // skip the "//" - - span = strcspn(leftover_url, "/"); - strncat(mAuthority,leftover_url,span); /* Flawfinder: ignore */ - leftover_url += span; - } - - if ((parse = strrchr(leftover_url,'.'))) - { - // copy and lop off extension - strncpy(mExtension,parse+1, LL_MAX_PATH -1); /* Flawfinder: ignore */ - mExtension[LL_MAX_PATH -1] = '\0'; - *parse = '\0'; - } - - if ((parse = strrchr(leftover_url,'/'))) - { - parse++; - } - else - { - parse = leftover_url; - } - - // copy and lop off filename - strncpy(mFilename,parse, LL_MAX_PATH -1);/* Flawfinder: ignore */ - mFilename[LL_MAX_PATH -1] = '\0'; - *parse = '\0'; - - // what's left should be the path - strncpy(mPath,leftover_url, LL_MAX_PATH -1); /* Flawfinder: ignore */ - mPath[LL_MAX_PATH -1] = '\0'; - -// LL_INFOS() << url << " decomposed into: " << LL_ENDL; -// LL_INFOS() << " URI : <" << mURI << ">" << LL_ENDL; -// LL_INFOS() << " Auth: <" << mAuthority << ">" << LL_ENDL; -// LL_INFOS() << " Path: <" << mPath << ">" << LL_ENDL; -// LL_INFOS() << " File: <" << mFilename << ">" << LL_ENDL; -// LL_INFOS() << " Ext : <" << mExtension << ">" << LL_ENDL; -// LL_INFOS() << " Tag : <" << mTag << ">" << LL_ENDL; -} - -void LLURL::cleanup() -{ -} - -// Copy assignment -LLURL &LLURL::operator=(const LLURL &rhs) -{ - if (this != &rhs) - { - this->init(rhs.getFQURL()); - } - - return *this; -} - -// Compare -bool LLURL::operator==(const LLURL &rhs) const -{ - if ((strcmp(mURI, rhs.mURI)) - || (strcmp(mAuthority, rhs.mAuthority)) - || (strcmp(mPath, rhs.mPath)) - || (strcmp(mFilename, rhs.mFilename)) - || (strcmp(mExtension, rhs.mExtension)) - || (strcmp(mTag, rhs.mTag)) - ) - { - return false; - } - return true; -} - -bool LLURL::operator!=(const LLURL& rhs) const -{ - return !(*this == rhs); -} - -const char * LLURL::getFQURL() const -{ - char fqurl[LL_MAX_PATH]; /* Flawfinder: ignore */ - - fqurl[0] = '\0'; - - if (mURI[0]) - { - strncat(fqurl,mURI, LL_MAX_PATH - strlen(fqurl) -1); /* Flawfinder: ignore */ - strcat(fqurl,":"); /* Flawfinder: ignore */ - if (mAuthority[0]) - { - strcat(fqurl,"//"); /* Flawfinder: ignore */ - } - } - - if (mAuthority[0]) - { - strncat(fqurl,mAuthority, LL_MAX_PATH - strlen(fqurl) -1); /* Flawfinder: ignore */ - } - - strncat(fqurl,mPath, LL_MAX_PATH - strlen(fqurl) -1); /* Flawfinder: ignore */ - - strncat(fqurl,mFilename, LL_MAX_PATH - strlen(fqurl) -1); /* Flawfinder: ignore */ - - if (mExtension[0]) - { - strcat(fqurl,"."); /* Flawfinder: ignore */ - strncat(fqurl,mExtension, LL_MAX_PATH - strlen(fqurl) -1); /* Flawfinder: ignore */ - } - - if (mTag[0]) - { - strcat(fqurl,"#"); /* Flawfinder: ignore */ - strncat(fqurl,mTag, LL_MAX_PATH - strlen(fqurl) -1); /* Flawfinder: ignore */ - } - - strncpy(LLURL::sReturnString,fqurl, LL_MAX_PATH -1); /* Flawfinder: ignore */ - LLURL::sReturnString[LL_MAX_PATH -1] = '\0'; - - return(LLURL::sReturnString); -} - - -const char* LLURL::updateRelativePath(const LLURL &url) -{ - char new_path[LL_MAX_PATH]; /* Flawfinder: ignore */ - char tmp_path[LL_MAX_PATH]; /* Flawfinder: ignore */ - - char *parse; - - if (mPath[0] != '/') - { - //start with existing path - strncpy (new_path,url.mPath, LL_MAX_PATH -1); /* Flawfinder: ignore */ - new_path[LL_MAX_PATH -1] = '\0'; - strncpy (tmp_path,mPath, LL_MAX_PATH -1); /* Flawfinder: ignore */ - tmp_path[LL_MAX_PATH -1] = '\0'; - - parse = strtok(tmp_path,"/"); - while (parse) - { - if (!strcmp(parse,".")) - { - // skip this, it's meaningless - } - else if (!strcmp(parse,"..")) - { - if ((parse = strrchr(new_path, '/'))) - { - *parse = '\0'; - if ((parse = strrchr(new_path, '/'))) - { - *(parse+1) = '\0'; - } - else - { - new_path[0] = '\0'; - } - } - else - { - strcat(new_path,"../"); /* Flawfinder: ignore */ - } - - } - else - { - strncat(new_path,parse, LL_MAX_PATH - strlen(new_path) -1 ); /* Flawfinder: ignore */ - strcat(new_path,"/"); /* Flawfinder: ignore */ - } - parse = strtok(NULL,"/"); - } - strncpy(mPath,new_path, LL_MAX_PATH -1); /* Flawfinder: ignore */ - mPath[LL_MAX_PATH -1] = '\0'; - } - return mPath; -} - -const char * LLURL::getFullPath() -{ - strncpy(LLURL::sReturnString,mPath, LL_MAX_PATH -1); /* Flawfinder: ignore */ - LLURL::sReturnString[LL_MAX_PATH -1] = '\0'; - strncat(LLURL::sReturnString,mFilename, LL_MAX_PATH - strlen(LLURL::sReturnString) -1); /* Flawfinder: ignore */ - strcat(LLURL::sReturnString,"."); /* Flawfinder: ignore */ - strncat(LLURL::sReturnString,mExtension, LL_MAX_PATH - strlen(LLURL::sReturnString) -1); /* Flawfinder: ignore */ - return(sReturnString); -} - -const char * LLURL::getAuthority() -{ - strncpy(LLURL::sReturnString,mAuthority, LL_MAX_PATH -1); /* Flawfinder: ignore */ - LLURL::sReturnString[LL_MAX_PATH -1] = '\0'; - return(sReturnString); -} - -char LLURL::sReturnString[LL_MAX_PATH] = ""; +/** + * @file llurl.cpp + * @brief Text url class + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llurl.h" +#include "llerror.h" + +LLURL::LLURL() +{ + init(""); +} + +LLURL::LLURL(const LLURL &url) +{ + if (this != &url) + { + init(url.getFQURL()); + } + else + { + init(""); + } +} + +LLURL::LLURL(const char * url) +{ + init(url); +} + +LLURL::~LLURL() +{ + cleanup(); +} + +void LLURL::init(const char * url) +{ + mURI[0] = '\0'; + mAuthority[0] = '\0'; + mPath[0] = '\0'; + mFilename[0] = '\0'; + mExtension[0] = '\0'; + mTag[0] = '\0'; + + char url_copy[MAX_STRING]; /* Flawfinder: ignore */ + + strncpy (url_copy,url, MAX_STRING -1); /* Flawfinder: ignore */ + url_copy[MAX_STRING -1] = '\0'; + + char *parse; + char *leftover_url = url_copy; + S32 span = 0; + + // copy and lop off tag + if ((parse = strchr(url_copy,'#'))) + { + strncpy(mTag,parse+1, LL_MAX_PATH -1); /* Flawfinder: ignore */ + mTag[LL_MAX_PATH -1] = '\0'; + *parse = '\0'; + } + + // copy out URI if it exists, update leftover_url + if ((parse = strchr(url_copy,':'))) + { + *parse = '\0'; + strncpy(mURI,leftover_url, LL_MAX_PATH -1); /* Flawfinder: ignore */ + mURI[LL_MAX_PATH -1] = '\0'; + leftover_url = parse + 1; + } + + // copy out authority if it exists, update leftover_url + if ((leftover_url[0] == '/') && (leftover_url[1] == '/')) + { + leftover_url += 2; // skip the "//" + + span = strcspn(leftover_url, "/"); + strncat(mAuthority,leftover_url,span); /* Flawfinder: ignore */ + leftover_url += span; + } + + if ((parse = strrchr(leftover_url,'.'))) + { + // copy and lop off extension + strncpy(mExtension,parse+1, LL_MAX_PATH -1); /* Flawfinder: ignore */ + mExtension[LL_MAX_PATH -1] = '\0'; + *parse = '\0'; + } + + if ((parse = strrchr(leftover_url,'/'))) + { + parse++; + } + else + { + parse = leftover_url; + } + + // copy and lop off filename + strncpy(mFilename,parse, LL_MAX_PATH -1);/* Flawfinder: ignore */ + mFilename[LL_MAX_PATH -1] = '\0'; + *parse = '\0'; + + // what's left should be the path + strncpy(mPath,leftover_url, LL_MAX_PATH -1); /* Flawfinder: ignore */ + mPath[LL_MAX_PATH -1] = '\0'; + +// LL_INFOS() << url << " decomposed into: " << LL_ENDL; +// LL_INFOS() << " URI : <" << mURI << ">" << LL_ENDL; +// LL_INFOS() << " Auth: <" << mAuthority << ">" << LL_ENDL; +// LL_INFOS() << " Path: <" << mPath << ">" << LL_ENDL; +// LL_INFOS() << " File: <" << mFilename << ">" << LL_ENDL; +// LL_INFOS() << " Ext : <" << mExtension << ">" << LL_ENDL; +// LL_INFOS() << " Tag : <" << mTag << ">" << LL_ENDL; +} + +void LLURL::cleanup() +{ +} + +// Copy assignment +LLURL &LLURL::operator=(const LLURL &rhs) +{ + if (this != &rhs) + { + this->init(rhs.getFQURL()); + } + + return *this; +} + +// Compare +bool LLURL::operator==(const LLURL &rhs) const +{ + if ((strcmp(mURI, rhs.mURI)) + || (strcmp(mAuthority, rhs.mAuthority)) + || (strcmp(mPath, rhs.mPath)) + || (strcmp(mFilename, rhs.mFilename)) + || (strcmp(mExtension, rhs.mExtension)) + || (strcmp(mTag, rhs.mTag)) + ) + { + return false; + } + return true; +} + +bool LLURL::operator!=(const LLURL& rhs) const +{ + return !(*this == rhs); +} + +const char * LLURL::getFQURL() const +{ + char fqurl[LL_MAX_PATH]; /* Flawfinder: ignore */ + + fqurl[0] = '\0'; + + if (mURI[0]) + { + strncat(fqurl,mURI, LL_MAX_PATH - strlen(fqurl) -1); /* Flawfinder: ignore */ + strcat(fqurl,":"); /* Flawfinder: ignore */ + if (mAuthority[0]) + { + strcat(fqurl,"//"); /* Flawfinder: ignore */ + } + } + + if (mAuthority[0]) + { + strncat(fqurl,mAuthority, LL_MAX_PATH - strlen(fqurl) -1); /* Flawfinder: ignore */ + } + + strncat(fqurl,mPath, LL_MAX_PATH - strlen(fqurl) -1); /* Flawfinder: ignore */ + + strncat(fqurl,mFilename, LL_MAX_PATH - strlen(fqurl) -1); /* Flawfinder: ignore */ + + if (mExtension[0]) + { + strcat(fqurl,"."); /* Flawfinder: ignore */ + strncat(fqurl,mExtension, LL_MAX_PATH - strlen(fqurl) -1); /* Flawfinder: ignore */ + } + + if (mTag[0]) + { + strcat(fqurl,"#"); /* Flawfinder: ignore */ + strncat(fqurl,mTag, LL_MAX_PATH - strlen(fqurl) -1); /* Flawfinder: ignore */ + } + + strncpy(LLURL::sReturnString,fqurl, LL_MAX_PATH -1); /* Flawfinder: ignore */ + LLURL::sReturnString[LL_MAX_PATH -1] = '\0'; + + return(LLURL::sReturnString); +} + + +const char* LLURL::updateRelativePath(const LLURL &url) +{ + char new_path[LL_MAX_PATH]; /* Flawfinder: ignore */ + char tmp_path[LL_MAX_PATH]; /* Flawfinder: ignore */ + + char *parse; + + if (mPath[0] != '/') + { + //start with existing path + strncpy (new_path,url.mPath, LL_MAX_PATH -1); /* Flawfinder: ignore */ + new_path[LL_MAX_PATH -1] = '\0'; + strncpy (tmp_path,mPath, LL_MAX_PATH -1); /* Flawfinder: ignore */ + tmp_path[LL_MAX_PATH -1] = '\0'; + + parse = strtok(tmp_path,"/"); + while (parse) + { + if (!strcmp(parse,".")) + { + // skip this, it's meaningless + } + else if (!strcmp(parse,"..")) + { + if ((parse = strrchr(new_path, '/'))) + { + *parse = '\0'; + if ((parse = strrchr(new_path, '/'))) + { + *(parse+1) = '\0'; + } + else + { + new_path[0] = '\0'; + } + } + else + { + strcat(new_path,"../"); /* Flawfinder: ignore */ + } + + } + else + { + strncat(new_path,parse, LL_MAX_PATH - strlen(new_path) -1 ); /* Flawfinder: ignore */ + strcat(new_path,"/"); /* Flawfinder: ignore */ + } + parse = strtok(NULL,"/"); + } + strncpy(mPath,new_path, LL_MAX_PATH -1); /* Flawfinder: ignore */ + mPath[LL_MAX_PATH -1] = '\0'; + } + return mPath; +} + +const char * LLURL::getFullPath() +{ + strncpy(LLURL::sReturnString,mPath, LL_MAX_PATH -1); /* Flawfinder: ignore */ + LLURL::sReturnString[LL_MAX_PATH -1] = '\0'; + strncat(LLURL::sReturnString,mFilename, LL_MAX_PATH - strlen(LLURL::sReturnString) -1); /* Flawfinder: ignore */ + strcat(LLURL::sReturnString,"."); /* Flawfinder: ignore */ + strncat(LLURL::sReturnString,mExtension, LL_MAX_PATH - strlen(LLURL::sReturnString) -1); /* Flawfinder: ignore */ + return(sReturnString); +} + +const char * LLURL::getAuthority() +{ + strncpy(LLURL::sReturnString,mAuthority, LL_MAX_PATH -1); /* Flawfinder: ignore */ + LLURL::sReturnString[LL_MAX_PATH -1] = '\0'; + return(sReturnString); +} + +char LLURL::sReturnString[LL_MAX_PATH] = ""; diff --git a/indra/newview/llurl.h b/indra/newview/llurl.h index a1f9e91a5a..97b449d55f 100644 --- a/indra/newview/llurl.h +++ b/indra/newview/llurl.h @@ -1,95 +1,95 @@ -/** - * @file llurl.h - * @brief Text url class - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLURL_H -#define LL_LLURL_H - -// splits a URL into its parts, which are: -// -// [URI][AUTHORITY][PATH][FILENAME][EXTENSION][TAG] -// -// e.g. http://www.lindenlab.com/early/bite_me.html#where -// -// URI= "http" -// AUTHORITY= "www.lindenlab.com" -// PATH= "/early/" -// FILENAME= "bite_me" -// EXTENSION= "html" -// TAG= "where" -// -// -// test cases: -// -// http://www.lindenlab.com/early/bite_me.html#where -// http://www.lindenlab.com/ -// http://www.lindenlab.com -// www.lindenlab.com ? -// early/bite_me.html#where -// mailto://test@lindenlab.com -// mailto:test@lindenlab.com -// -// - - -class LLURL -{ -public: - LLURL(); - LLURL(const LLURL &url); - LLURL(const char * url); - - LLURL &operator=(const LLURL &rhs); - - virtual ~LLURL(); - - virtual void init (const char * url); - virtual void cleanup (); - - bool operator==(const LLURL &rhs) const; - bool operator!=(const LLURL &rhs) const; - - virtual const char *getFQURL() const; - virtual const char *getFullPath(); - virtual const char *getAuthority(); - - virtual const char *updateRelativePath(const LLURL &url); - - virtual bool isExtension(const char *compare) {return (!strcmp(mExtension,compare));}; - -public: - - char mURI[LL_MAX_PATH]; /* Flawfinder: ignore */ - char mAuthority[LL_MAX_PATH]; /* Flawfinder: ignore */ - char mPath[LL_MAX_PATH]; /* Flawfinder: ignore */ - char mFilename[LL_MAX_PATH]; /* Flawfinder: ignore */ - char mExtension[LL_MAX_PATH]; /* Flawfinder: ignore */ - char mTag[LL_MAX_PATH]; /* Flawfinder: ignore */ - - static char sReturnString[LL_MAX_PATH]; /* Flawfinder: ignore */ -}; - -#endif // LL_LLURL_H - +/** + * @file llurl.h + * @brief Text url class + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLURL_H +#define LL_LLURL_H + +// splits a URL into its parts, which are: +// +// [URI][AUTHORITY][PATH][FILENAME][EXTENSION][TAG] +// +// e.g. http://www.lindenlab.com/early/bite_me.html#where +// +// URI= "http" +// AUTHORITY= "www.lindenlab.com" +// PATH= "/early/" +// FILENAME= "bite_me" +// EXTENSION= "html" +// TAG= "where" +// +// +// test cases: +// +// http://www.lindenlab.com/early/bite_me.html#where +// http://www.lindenlab.com/ +// http://www.lindenlab.com +// www.lindenlab.com ? +// early/bite_me.html#where +// mailto://test@lindenlab.com +// mailto:test@lindenlab.com +// +// + + +class LLURL +{ +public: + LLURL(); + LLURL(const LLURL &url); + LLURL(const char * url); + + LLURL &operator=(const LLURL &rhs); + + virtual ~LLURL(); + + virtual void init (const char * url); + virtual void cleanup (); + + bool operator==(const LLURL &rhs) const; + bool operator!=(const LLURL &rhs) const; + + virtual const char *getFQURL() const; + virtual const char *getFullPath(); + virtual const char *getAuthority(); + + virtual const char *updateRelativePath(const LLURL &url); + + virtual bool isExtension(const char *compare) {return (!strcmp(mExtension,compare));}; + +public: + + char mURI[LL_MAX_PATH]; /* Flawfinder: ignore */ + char mAuthority[LL_MAX_PATH]; /* Flawfinder: ignore */ + char mPath[LL_MAX_PATH]; /* Flawfinder: ignore */ + char mFilename[LL_MAX_PATH]; /* Flawfinder: ignore */ + char mExtension[LL_MAX_PATH]; /* Flawfinder: ignore */ + char mTag[LL_MAX_PATH]; /* Flawfinder: ignore */ + + static char sReturnString[LL_MAX_PATH]; /* Flawfinder: ignore */ +}; + +#endif // LL_LLURL_H + diff --git a/indra/newview/llurllineeditorctrl.h b/indra/newview/llurllineeditorctrl.h index d52fb4833e..f5bf62c7d9 100644 --- a/indra/newview/llurllineeditorctrl.h +++ b/indra/newview/llurllineeditorctrl.h @@ -1,92 +1,92 @@ -/** - * @file llurllineeditorctrl.h - * @brief Combobox-like location input control - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLURLLINEEDITOR_H_ -#define LLURLLINEEDITOR_H_ - -#include "linden_common.h" - -#include "lllineeditor.h" -#include "lluictrl.h" - -// LLURLLineEditor class performing escaping of an URL while copying or cutting the target text -class LLURLLineEditor: public LLLineEditor { - LOG_CLASS( LLURLLineEditor); - -public: - // LLLineEditor overrides to do necessary escaping - /*virtual*/ void copy(); - /*virtual*/ void cut(); - -protected: - LLURLLineEditor(const Params&); - friend class LLUICtrlFactory; - friend class LLFloaterEditUI; - -private: - // util function to escape selected text and copy it to clipboard - void copyEscapedURLToClipboard(); - - // Helper class to do rollback if needed - class LLURLLineEditorRollback - { - public: - LLURLLineEditorRollback( LLURLLineEditor* ed ) - : - mCursorPos( ed->mCursorPos ), - mScrollHPos( ed->mScrollHPos ), - mIsSelecting( ed->mIsSelecting ), - mSelectionStart( ed->mSelectionStart ), - mSelectionEnd( ed->mSelectionEnd ) - { - mText = ed->getText(); - } - - void doRollback( LLURLLineEditor* ed ) - { - ed->mCursorPos = mCursorPos; - ed->mScrollHPos = mScrollHPos; - ed->mIsSelecting = mIsSelecting; - ed->mSelectionStart = mSelectionStart; - ed->mSelectionEnd = mSelectionEnd; - ed->mText = mText; - ed->mPrevText = mText; - } - - std::string getText() { return mText; } - - private: - std::string mText; - S32 mCursorPos; - S32 mScrollHPos; - bool mIsSelecting; - S32 mSelectionStart; - S32 mSelectionEnd; - }; // end class LLURLLineEditorRollback - -}; - -#endif /* LLURLLINEEDITOR_H_ */ +/** + * @file llurllineeditorctrl.h + * @brief Combobox-like location input control + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLURLLINEEDITOR_H_ +#define LLURLLINEEDITOR_H_ + +#include "linden_common.h" + +#include "lllineeditor.h" +#include "lluictrl.h" + +// LLURLLineEditor class performing escaping of an URL while copying or cutting the target text +class LLURLLineEditor: public LLLineEditor { + LOG_CLASS( LLURLLineEditor); + +public: + // LLLineEditor overrides to do necessary escaping + /*virtual*/ void copy(); + /*virtual*/ void cut(); + +protected: + LLURLLineEditor(const Params&); + friend class LLUICtrlFactory; + friend class LLFloaterEditUI; + +private: + // util function to escape selected text and copy it to clipboard + void copyEscapedURLToClipboard(); + + // Helper class to do rollback if needed + class LLURLLineEditorRollback + { + public: + LLURLLineEditorRollback( LLURLLineEditor* ed ) + : + mCursorPos( ed->mCursorPos ), + mScrollHPos( ed->mScrollHPos ), + mIsSelecting( ed->mIsSelecting ), + mSelectionStart( ed->mSelectionStart ), + mSelectionEnd( ed->mSelectionEnd ) + { + mText = ed->getText(); + } + + void doRollback( LLURLLineEditor* ed ) + { + ed->mCursorPos = mCursorPos; + ed->mScrollHPos = mScrollHPos; + ed->mIsSelecting = mIsSelecting; + ed->mSelectionStart = mSelectionStart; + ed->mSelectionEnd = mSelectionEnd; + ed->mText = mText; + ed->mPrevText = mText; + } + + std::string getText() { return mText; } + + private: + std::string mText; + S32 mCursorPos; + S32 mScrollHPos; + bool mIsSelecting; + S32 mSelectionStart; + S32 mSelectionEnd; + }; // end class LLURLLineEditorRollback + +}; + +#endif /* LLURLLINEEDITOR_H_ */ diff --git a/indra/newview/llviewerassetstorage.cpp b/indra/newview/llviewerassetstorage.cpp index 89963c2b7f..06410e4d6d 100644 --- a/indra/newview/llviewerassetstorage.cpp +++ b/indra/newview/llviewerassetstorage.cpp @@ -1,604 +1,604 @@ -/** - * @file llviewerassetstorage.cpp - * @brief Subclass capable of loading asset data to/from an external source. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llviewerassetstorage.h" - -#include "llfilesystem.h" -#include "message.h" - -#include "llagent.h" -#include "llappcorehttp.h" -#include "llviewerregion.h" - -#include "lltransfersourceasset.h" -#include "lltransfertargetvfile.h" -#include "llviewerassetstats.h" -#include "llcoros.h" -#include "llcoproceduremanager.h" -#include "lleventcoro.h" -#include "llsdutil.h" -#include "llworld.h" - -///---------------------------------------------------------------------------- -/// LLViewerAssetRequest -///---------------------------------------------------------------------------- - - // There is also PoolSizeAssetStorage value in setting that should mirror this name -static const std::string VIEWER_ASSET_STORAGE_CORO_POOL = "AssetStorage"; - -/** - * @brief Local class to encapsulate asset fetch requests with a timestamp. - * - * Derived from the common LLAssetRequest class, this is currently used - * only for fetch/get operations and its only function is to wrap remote - * asset fetch requests so that they can be timed. - */ -class LLViewerAssetRequest : public LLAssetRequest -{ -public: - LLViewerAssetRequest(const LLUUID &uuid, const LLAssetType::EType type, bool with_http) - : LLAssetRequest(uuid, type), - mMetricsStartTime(0), - mWithHTTP(with_http) - { - } - - LLViewerAssetRequest & operator=(const LLViewerAssetRequest &); // Not defined - // Default assignment operator valid - - // virtual - ~LLViewerAssetRequest() - { - recordMetrics(); - } - -protected: - void recordMetrics() - { - if (mMetricsStartTime.value()) - { - // Okay, it appears this request was used for useful things. Record - // the expected dequeue and duration of request processing. - LLViewerAssetStatsFF::record_dequeue(mType, mWithHTTP, false); - LLViewerAssetStatsFF::record_response(mType, mWithHTTP, false, - (LLViewerAssetStatsFF::get_timestamp() - - mMetricsStartTime), - mBytesFetched); - mMetricsStartTime = (U32Seconds)0; - } - } - -public: - LLViewerAssetStats::duration_t mMetricsStartTime; - bool mWithHTTP; -}; - -///---------------------------------------------------------------------------- -/// LLViewerAssetStorage -///---------------------------------------------------------------------------- - -S32 LLViewerAssetStorage::sAssetCoroCount = 0; - -// Unused? -LLViewerAssetStorage::LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, const LLHost &upstream_host) - : LLAssetStorage(msg, xfer, upstream_host), - mCountRequests(0), - mCountStarted(0), - mCountCompleted(0), - mCountSucceeded(0), - mTotalBytesFetched(0) -{ - LLCoprocedureManager::instance().initializePool(VIEWER_ASSET_STORAGE_CORO_POOL); -} - -LLViewerAssetStorage::LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer) - : LLAssetStorage(msg, xfer), - mCountRequests(0), - mCountStarted(0), - mCountCompleted(0), - mCountSucceeded(0), - mTotalBytesFetched(0) -{ - LLCoprocedureManager::instance().initializePool(VIEWER_ASSET_STORAGE_CORO_POOL); -} - -LLViewerAssetStorage::~LLViewerAssetStorage() -{ - if (!LLCoprocedureManager::wasDeleted()) - { - // This class has dedicated coroutine pool, clean it up, otherwise coroutines will crash later. - LLCoprocedureManager::instance().close(VIEWER_ASSET_STORAGE_CORO_POOL); - } -} - -// virtual -void LLViewerAssetStorage::storeAssetData( - const LLTransactionID& tid, - LLAssetType::EType asset_type, - LLStoreAssetCallback callback, - void* user_data, - bool temp_file, - bool is_priority, - bool store_local, - bool user_waiting, - F64Seconds timeout) -{ - LLAssetID asset_id = tid.makeAssetID(gAgent.getSecureSessionID()); - LL_DEBUGS("AssetStorage") << "LLViewerAssetStorage::storeAssetData (legacy) " << tid << ":" << LLAssetType::lookup(asset_type) - << " ASSET_ID: " << asset_id << LL_ENDL; - - if (mUpstreamHost.isOk()) - { - if (LLFileSystem::getExists(asset_id, asset_type)) - { - // Pack data into this packet if we can fit it. - U8 buffer[MTUBYTES]; - buffer[0] = 0; - - LLFileSystem vfile(asset_id, asset_type, LLFileSystem::READ); - S32 asset_size = vfile.getSize(); - - LLAssetRequest *req = new LLAssetRequest(asset_id, asset_type); - req->mUpCallback = callback; - req->mUserData = user_data; - - if (asset_size < 1) - { - // This can happen if there's a bug in our code or if the cache has been corrupted. - LL_WARNS("AssetStorage") << "LLViewerAssetStorage::storeAssetData() Data _should_ already be in the cache, but it's not! " << asset_id << LL_ENDL; - - delete req; - if (callback) - { - callback(asset_id, user_data, LL_ERR_ASSET_REQUEST_FAILED, LLExtStat::CACHE_CORRUPT); - } - return; - } - else - { - // LLAssetStorage metric: Successful Request - S32 size = LLFileSystem::getFileSize(asset_id, asset_type); - const char *message = "Added to upload queue"; - reportMetric( asset_id, asset_type, LLStringUtil::null, LLUUID::null, size, MR_OKAY, __FILE__, __LINE__, message ); - - if(is_priority) - { - mPendingUploads.push_front(req); - } - else - { - mPendingUploads.push_back(req); - } - } - - // Read the data from the cache if it'll fit in this packet. - if (asset_size + 100 < MTUBYTES) - { - bool res = vfile.read(buffer, asset_size); /* Flawfinder: ignore */ - S32 bytes_read = res ? vfile.getLastBytesRead() : 0; - - if( bytes_read == asset_size ) - { - req->mDataSentInFirstPacket = true; - //LL_INFOS() << "LLViewerAssetStorage::createAsset sending data in first packet" << LL_ENDL; - } - else - { - LL_WARNS("AssetStorage") << "Probable corruption in cache file, aborting store asset data" << LL_ENDL; - - if (callback) - { - callback(asset_id, user_data, LL_ERR_ASSET_REQUEST_NONEXISTENT_FILE, LLExtStat::CACHE_CORRUPT); - } - return; - } - } - else - { - // Too big, do an xfer - buffer[0] = 0; - asset_size = 0; - } - mMessageSys->newMessageFast(_PREHASH_AssetUploadRequest); - mMessageSys->nextBlockFast(_PREHASH_AssetBlock); - mMessageSys->addUUIDFast(_PREHASH_TransactionID, tid); - mMessageSys->addS8Fast(_PREHASH_Type, (S8)asset_type); - mMessageSys->addBOOLFast(_PREHASH_Tempfile, temp_file); - mMessageSys->addBOOLFast(_PREHASH_StoreLocal, store_local); - mMessageSys->addBinaryDataFast( _PREHASH_AssetData, buffer, asset_size ); - mMessageSys->sendReliable(mUpstreamHost); - } - else - { - LL_WARNS("AssetStorage") << "AssetStorage: attempt to upload non-existent vfile " << asset_id << ":" << LLAssetType::lookup(asset_type) << LL_ENDL; - reportMetric( asset_id, asset_type, LLStringUtil::null, LLUUID::null, 0, MR_ZERO_SIZE, __FILE__, __LINE__, "The file didn't exist or was zero length (cache - can't tell which)" ); - if (callback) - { - callback(asset_id, user_data, LL_ERR_ASSET_REQUEST_NONEXISTENT_FILE, LLExtStat::NONEXISTENT_FILE); - } - } - } - else - { - LL_WARNS("AssetStorage") << "Attempt to move asset store request upstream w/o valid upstream provider" << LL_ENDL; - // LLAssetStorage metric: Upstream provider dead - reportMetric( asset_id, asset_type, LLStringUtil::null, LLUUID::null, 0, MR_NO_UPSTREAM, __FILE__, __LINE__, "No upstream provider" ); - if (callback) - { - callback(asset_id, user_data, LL_ERR_CIRCUIT_GONE, LLExtStat::NO_UPSTREAM); - } - } -} - -void LLViewerAssetStorage::storeAssetData( - const std::string& filename, - const LLTransactionID& tid, - LLAssetType::EType asset_type, - LLStoreAssetCallback callback, - void* user_data, - bool temp_file, - bool is_priority, - bool user_waiting, - F64Seconds timeout) -{ - if(filename.empty()) - { - // LLAssetStorage metric: no filename - LL_ERRS() << "No filename specified" << LL_ENDL; - return; - } - - LLAssetID asset_id = tid.makeAssetID(gAgent.getSecureSessionID()); - LL_DEBUGS("AssetStorage") << "LLViewerAssetStorage::storeAssetData (legacy)" << asset_id << ":" << LLAssetType::lookup(asset_type) << LL_ENDL; - - LL_DEBUGS("AssetStorage") << "ASSET_ID: " << asset_id << LL_ENDL; - - S32 size = 0; - LLFILE* fp = LLFile::fopen(filename, "rb"); - if (fp) - { - fseek(fp, 0, SEEK_END); - size = ftell(fp); - fseek(fp, 0, SEEK_SET); - } - if( size ) - { - LLLegacyAssetRequest *legacy = new LLLegacyAssetRequest; - - legacy->mUpCallback = callback; - legacy->mUserData = user_data; - - LLFileSystem file(asset_id, asset_type, LLFileSystem::APPEND); - - const S32 buf_size = 65536; - U8 copy_buf[buf_size]; - while ((size = (S32)fread(copy_buf, 1, buf_size, fp))) - { - file.write(copy_buf, size); - } - fclose(fp); - - // if this upload fails, the caller needs to setup a new tempfile for us - if (temp_file) - { - LLFile::remove(filename); - } - - // LLAssetStorage metric: Success not needed; handled in the overloaded method here: - - LLViewerAssetStorage::storeAssetData( - tid, - asset_type, - legacyStoreDataCallback, - (void**)legacy, - temp_file, - is_priority); - } - else // size == 0 (but previous block changes size) - { - if( fp ) - { - // LLAssetStorage metric: Zero size - reportMetric( asset_id, asset_type, filename, LLUUID::null, 0, MR_ZERO_SIZE, __FILE__, __LINE__, "The file was zero length" ); - } - else - { - // LLAssetStorage metric: Missing File - reportMetric( asset_id, asset_type, filename, LLUUID::null, 0, MR_FILE_NONEXIST, __FILE__, __LINE__, "The file didn't exist" ); - } - if (callback) - { - callback(asset_id, user_data, LL_ERR_CANNOT_OPEN_FILE, LLExtStat::BLOCKED_FILE); - } - } -} - -/** - * @brief Allocate and queue an asset fetch request for the viewer - * - * This is a nearly-verbatim copy of the base class's implementation - * with the following changes: - * - Use a locally-derived request class - * - Start timing for metrics when request is queued - * - * This is an unfortunate implementation choice but it's forced by - * current conditions. A refactoring that might clean up the layers - * of responsibility or introduce factories or more virtualization - * of methods would enable a more attractive solution. - * - * If LLAssetStorage::_queueDataRequest changes, this must change - * as well. - */ - -// virtual -void LLViewerAssetStorage::_queueDataRequest( - const LLUUID& uuid, - LLAssetType::EType atype, - LLGetAssetCallback callback, - void *user_data, - bool duplicate, - bool is_priority) -{ - mCountRequests++; - queueRequestHttp(uuid, atype, callback, user_data, duplicate, is_priority); -} - -void LLViewerAssetStorage::queueRequestHttp( - const LLUUID& uuid, - LLAssetType::EType atype, - LLGetAssetCallback callback, - void *user_data, - bool duplicate, - bool is_priority) -{ - LL_DEBUGS("ViewerAsset") << "Request asset via HTTP " << uuid << " type " << LLAssetType::lookup(atype) << LL_ENDL; - - bool with_http = true; - LLViewerAssetRequest *req = new LLViewerAssetRequest(uuid, atype, with_http); - req->mDownCallback = callback; - req->mUserData = user_data; - req->mIsPriority = is_priority; - if (!duplicate) - { - // Only collect metrics for non-duplicate requests. Others - // are piggy-backing and will artificially lower averages. - req->mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp(); - } - mPendingDownloads.push_back(req); - - // This is the same as the current UDP logic - don't re-request a duplicate. - if (!duplicate) - { - LLCoprocedureManager* manager = LLCoprocedureManager::getInstance(); - bool with_http = true; - bool is_temp = false; - LLViewerAssetStatsFF::record_enqueue(atype, with_http, is_temp); - manager->enqueueCoprocedure( - VIEWER_ASSET_STORAGE_CORO_POOL, - "LLViewerAssetStorage::assetRequestCoro", - [this, req, uuid, atype, callback, user_data] - (LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t&, const LLUUID&) - { - assetRequestCoro(req, uuid, atype, callback, user_data); - }); - } -} - -void LLViewerAssetStorage::capsRecvForRegion(const LLUUID& region_id, std::string pumpname) -{ - LLViewerRegion *regionp = LLWorld::instance().getRegionFromID(region_id); - if (!regionp) - { - LL_WARNS("ViewerAsset") << "region not found for region_id " << region_id << LL_ENDL; - } - else - { - mViewerAssetUrl = regionp->getViewerAssetUrl(); - } - - LLEventPumps::instance().obtain(pumpname).post(LLSD()); -} - -struct LLScopedIncrement -{ - LLScopedIncrement(S32& counter): - mCounter(counter) - { - ++mCounter; - } - ~LLScopedIncrement() - { - --mCounter; - } - S32& mCounter; -}; - -void LLViewerAssetStorage::assetRequestCoro( - LLViewerAssetRequest *req, - const LLUUID uuid, - LLAssetType::EType atype, - LLGetAssetCallback callback, - void *user_data) -{ - LLScopedIncrement coro_count_boost(sAssetCoroCount); // static counter since corotine can outlive LLViewerAssetStorage - - S32 result_code = LL_ERR_NOERR; - LLExtStat ext_status = LLExtStat::NONE; - - if (!gAssetStorage) - { - LL_WARNS_ONCE("ViewerAsset") << "Asset request fails: asset storage no longer exists" << LL_ENDL; - return; - } - - mCountStarted++; - - if (!gAgent.getRegion()) - { - LL_WARNS_ONCE("ViewerAsset") << "Asset request fails: no region set" << LL_ENDL; - result_code = LL_ERR_ASSET_REQUEST_FAILED; - ext_status = LLExtStat::NONE; - removeAndCallbackPendingDownloads(uuid, atype, uuid, atype, result_code, ext_status); - return; - } - else if (!gAgent.getRegion()->capabilitiesReceived()) - { - LL_WARNS_ONCE("ViewerAsset") << "Waiting for capabilities" << LL_ENDL; - - LLEventStream capsRecv("waitForCaps", true); - - gAgent.getRegion()->setCapabilitiesReceivedCallback( - boost::bind(&LLViewerAssetStorage::capsRecvForRegion, this, _1, capsRecv.getName())); - - llcoro::suspendUntilEventOn(capsRecv); - - if (LLApp::isExiting() || !gAssetStorage) - { - return; - } - - LL_WARNS_ONCE("ViewerAsset") << "capsRecv got event" << LL_ENDL; - LL_WARNS_ONCE("ViewerAsset") << "region " << gAgent.getRegion() << " mViewerAssetUrl " << mViewerAssetUrl << LL_ENDL; - } - if (mViewerAssetUrl.empty() && gAgent.getRegion()) - { - mViewerAssetUrl = gAgent.getRegion()->getViewerAssetUrl(); - } - if (mViewerAssetUrl.empty()) - { - LL_WARNS_ONCE("ViewerAsset") << "asset request fails: caps received but no viewer asset cap found" << LL_ENDL; - result_code = LL_ERR_ASSET_REQUEST_FAILED; - ext_status = LLExtStat::NONE; - removeAndCallbackPendingDownloads(uuid, atype, uuid, atype, result_code, ext_status); - return; - } - std::string url = getAssetURL(mViewerAssetUrl, uuid,atype); - LL_DEBUGS("ViewerAsset") << "request url: " << url << LL_ENDL; - - LLCore::HttpRequest::policy_t httpPolicy(LLAppCoreHttp::AP_TEXTURE); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("assetRequestCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); - - LLSD result = httpAdapter->getRawAndSuspend(httpRequest, url, httpOpts); - - if (LLApp::isExiting() || !gAssetStorage) - { - // Bail out if result arrives after shutdown has been started. - return; - } - - mCountCompleted++; - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - if (!status) - { - LL_DEBUGS("ViewerAsset") << "request failed, status " << status.toTerseString() << LL_ENDL; - result_code = LL_ERR_ASSET_REQUEST_FAILED; - ext_status = LLExtStat::NONE; - } - else if (!result.has(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_RAW)) - { - LL_DEBUGS("ViewerAsset") << "request failed, no data returned!" << LL_ENDL; - result_code = LL_ERR_ASSET_REQUEST_FAILED; - ext_status = LLExtStat::NONE; - } - else if (!result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_RAW].isBinary()) - { - LL_DEBUGS("ViewerAsset") << "request failed, invalid data format!" << LL_ENDL; - result_code = LL_ERR_ASSET_REQUEST_FAILED; - ext_status = LLExtStat::NONE; - } - else - { - LL_DEBUGS("ViewerAsset") << "request succeeded, url " << url << LL_ENDL; - - const LLSD::Binary &raw = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_RAW].asBinary(); - - S32 size = raw.size(); - if (size > 0) - { - mTotalBytesFetched += size; - - // This create-then-rename flow is modeled on - // LLTransferTargetVFile, which is what was used in the UDP - // case. - LLUUID temp_id; - temp_id.generate(); - LLFileSystem vf(temp_id, atype, LLFileSystem::WRITE); - req->mBytesFetched = size; - if (!vf.write(raw.data(),size)) - { - // TODO asset-http: handle error - LL_WARNS("ViewerAsset") << "Failure in vf.write()" << LL_ENDL; - result_code = LL_ERR_ASSET_REQUEST_FAILED; - ext_status = LLExtStat::CACHE_CORRUPT; - } - else if (!vf.rename(uuid, atype)) - { - LL_WARNS("ViewerAsset") << "rename failed" << LL_ENDL; - result_code = LL_ERR_ASSET_REQUEST_FAILED; - ext_status = LLExtStat::CACHE_CORRUPT; - } - else - { - mCountSucceeded++; - } - } - else - { - // TODO asset-http: handle invalid size case - LL_WARNS("ViewerAsset") << "bad size" << LL_ENDL; - result_code = LL_ERR_ASSET_REQUEST_FAILED; - ext_status = LLExtStat::NONE; - } - } - - // Clean up pending downloads and trigger callbacks - removeAndCallbackPendingDownloads(uuid, atype, uuid, atype, result_code, ext_status); -} - -std::string LLViewerAssetStorage::getAssetURL(const std::string& cap_url, const LLUUID& uuid, LLAssetType::EType atype) -{ - std::string type_name = LLAssetType::lookup(atype); - std::string url = cap_url + "/?" + type_name + "_id=" + uuid.asString(); - return url; -} - -void LLViewerAssetStorage::logAssetStorageInfo() -{ - LLMemory::logMemoryInfo(true); - LL_INFOS("AssetStorage") << "Active coros " << sAssetCoroCount << LL_ENDL; - LL_INFOS("AssetStorage") << "mPendingDownloads size " << mPendingDownloads.size() << LL_ENDL; - LL_INFOS("AssetStorage") << "mCountStarted " << mCountStarted << LL_ENDL; - LL_INFOS("AssetStorage") << "mCountCompleted " << mCountCompleted << LL_ENDL; - LL_INFOS("AssetStorage") << "mCountSucceeded " << mCountSucceeded << LL_ENDL; - LL_INFOS("AssetStorage") << "mTotalBytesFetched " << mTotalBytesFetched << LL_ENDL; -} +/** + * @file llviewerassetstorage.cpp + * @brief Subclass capable of loading asset data to/from an external source. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llviewerassetstorage.h" + +#include "llfilesystem.h" +#include "message.h" + +#include "llagent.h" +#include "llappcorehttp.h" +#include "llviewerregion.h" + +#include "lltransfersourceasset.h" +#include "lltransfertargetvfile.h" +#include "llviewerassetstats.h" +#include "llcoros.h" +#include "llcoproceduremanager.h" +#include "lleventcoro.h" +#include "llsdutil.h" +#include "llworld.h" + +///---------------------------------------------------------------------------- +/// LLViewerAssetRequest +///---------------------------------------------------------------------------- + + // There is also PoolSizeAssetStorage value in setting that should mirror this name +static const std::string VIEWER_ASSET_STORAGE_CORO_POOL = "AssetStorage"; + +/** + * @brief Local class to encapsulate asset fetch requests with a timestamp. + * + * Derived from the common LLAssetRequest class, this is currently used + * only for fetch/get operations and its only function is to wrap remote + * asset fetch requests so that they can be timed. + */ +class LLViewerAssetRequest : public LLAssetRequest +{ +public: + LLViewerAssetRequest(const LLUUID &uuid, const LLAssetType::EType type, bool with_http) + : LLAssetRequest(uuid, type), + mMetricsStartTime(0), + mWithHTTP(with_http) + { + } + + LLViewerAssetRequest & operator=(const LLViewerAssetRequest &); // Not defined + // Default assignment operator valid + + // virtual + ~LLViewerAssetRequest() + { + recordMetrics(); + } + +protected: + void recordMetrics() + { + if (mMetricsStartTime.value()) + { + // Okay, it appears this request was used for useful things. Record + // the expected dequeue and duration of request processing. + LLViewerAssetStatsFF::record_dequeue(mType, mWithHTTP, false); + LLViewerAssetStatsFF::record_response(mType, mWithHTTP, false, + (LLViewerAssetStatsFF::get_timestamp() + - mMetricsStartTime), + mBytesFetched); + mMetricsStartTime = (U32Seconds)0; + } + } + +public: + LLViewerAssetStats::duration_t mMetricsStartTime; + bool mWithHTTP; +}; + +///---------------------------------------------------------------------------- +/// LLViewerAssetStorage +///---------------------------------------------------------------------------- + +S32 LLViewerAssetStorage::sAssetCoroCount = 0; + +// Unused? +LLViewerAssetStorage::LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, const LLHost &upstream_host) + : LLAssetStorage(msg, xfer, upstream_host), + mCountRequests(0), + mCountStarted(0), + mCountCompleted(0), + mCountSucceeded(0), + mTotalBytesFetched(0) +{ + LLCoprocedureManager::instance().initializePool(VIEWER_ASSET_STORAGE_CORO_POOL); +} + +LLViewerAssetStorage::LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer) + : LLAssetStorage(msg, xfer), + mCountRequests(0), + mCountStarted(0), + mCountCompleted(0), + mCountSucceeded(0), + mTotalBytesFetched(0) +{ + LLCoprocedureManager::instance().initializePool(VIEWER_ASSET_STORAGE_CORO_POOL); +} + +LLViewerAssetStorage::~LLViewerAssetStorage() +{ + if (!LLCoprocedureManager::wasDeleted()) + { + // This class has dedicated coroutine pool, clean it up, otherwise coroutines will crash later. + LLCoprocedureManager::instance().close(VIEWER_ASSET_STORAGE_CORO_POOL); + } +} + +// virtual +void LLViewerAssetStorage::storeAssetData( + const LLTransactionID& tid, + LLAssetType::EType asset_type, + LLStoreAssetCallback callback, + void* user_data, + bool temp_file, + bool is_priority, + bool store_local, + bool user_waiting, + F64Seconds timeout) +{ + LLAssetID asset_id = tid.makeAssetID(gAgent.getSecureSessionID()); + LL_DEBUGS("AssetStorage") << "LLViewerAssetStorage::storeAssetData (legacy) " << tid << ":" << LLAssetType::lookup(asset_type) + << " ASSET_ID: " << asset_id << LL_ENDL; + + if (mUpstreamHost.isOk()) + { + if (LLFileSystem::getExists(asset_id, asset_type)) + { + // Pack data into this packet if we can fit it. + U8 buffer[MTUBYTES]; + buffer[0] = 0; + + LLFileSystem vfile(asset_id, asset_type, LLFileSystem::READ); + S32 asset_size = vfile.getSize(); + + LLAssetRequest *req = new LLAssetRequest(asset_id, asset_type); + req->mUpCallback = callback; + req->mUserData = user_data; + + if (asset_size < 1) + { + // This can happen if there's a bug in our code or if the cache has been corrupted. + LL_WARNS("AssetStorage") << "LLViewerAssetStorage::storeAssetData() Data _should_ already be in the cache, but it's not! " << asset_id << LL_ENDL; + + delete req; + if (callback) + { + callback(asset_id, user_data, LL_ERR_ASSET_REQUEST_FAILED, LLExtStat::CACHE_CORRUPT); + } + return; + } + else + { + // LLAssetStorage metric: Successful Request + S32 size = LLFileSystem::getFileSize(asset_id, asset_type); + const char *message = "Added to upload queue"; + reportMetric( asset_id, asset_type, LLStringUtil::null, LLUUID::null, size, MR_OKAY, __FILE__, __LINE__, message ); + + if(is_priority) + { + mPendingUploads.push_front(req); + } + else + { + mPendingUploads.push_back(req); + } + } + + // Read the data from the cache if it'll fit in this packet. + if (asset_size + 100 < MTUBYTES) + { + bool res = vfile.read(buffer, asset_size); /* Flawfinder: ignore */ + S32 bytes_read = res ? vfile.getLastBytesRead() : 0; + + if( bytes_read == asset_size ) + { + req->mDataSentInFirstPacket = true; + //LL_INFOS() << "LLViewerAssetStorage::createAsset sending data in first packet" << LL_ENDL; + } + else + { + LL_WARNS("AssetStorage") << "Probable corruption in cache file, aborting store asset data" << LL_ENDL; + + if (callback) + { + callback(asset_id, user_data, LL_ERR_ASSET_REQUEST_NONEXISTENT_FILE, LLExtStat::CACHE_CORRUPT); + } + return; + } + } + else + { + // Too big, do an xfer + buffer[0] = 0; + asset_size = 0; + } + mMessageSys->newMessageFast(_PREHASH_AssetUploadRequest); + mMessageSys->nextBlockFast(_PREHASH_AssetBlock); + mMessageSys->addUUIDFast(_PREHASH_TransactionID, tid); + mMessageSys->addS8Fast(_PREHASH_Type, (S8)asset_type); + mMessageSys->addBOOLFast(_PREHASH_Tempfile, temp_file); + mMessageSys->addBOOLFast(_PREHASH_StoreLocal, store_local); + mMessageSys->addBinaryDataFast( _PREHASH_AssetData, buffer, asset_size ); + mMessageSys->sendReliable(mUpstreamHost); + } + else + { + LL_WARNS("AssetStorage") << "AssetStorage: attempt to upload non-existent vfile " << asset_id << ":" << LLAssetType::lookup(asset_type) << LL_ENDL; + reportMetric( asset_id, asset_type, LLStringUtil::null, LLUUID::null, 0, MR_ZERO_SIZE, __FILE__, __LINE__, "The file didn't exist or was zero length (cache - can't tell which)" ); + if (callback) + { + callback(asset_id, user_data, LL_ERR_ASSET_REQUEST_NONEXISTENT_FILE, LLExtStat::NONEXISTENT_FILE); + } + } + } + else + { + LL_WARNS("AssetStorage") << "Attempt to move asset store request upstream w/o valid upstream provider" << LL_ENDL; + // LLAssetStorage metric: Upstream provider dead + reportMetric( asset_id, asset_type, LLStringUtil::null, LLUUID::null, 0, MR_NO_UPSTREAM, __FILE__, __LINE__, "No upstream provider" ); + if (callback) + { + callback(asset_id, user_data, LL_ERR_CIRCUIT_GONE, LLExtStat::NO_UPSTREAM); + } + } +} + +void LLViewerAssetStorage::storeAssetData( + const std::string& filename, + const LLTransactionID& tid, + LLAssetType::EType asset_type, + LLStoreAssetCallback callback, + void* user_data, + bool temp_file, + bool is_priority, + bool user_waiting, + F64Seconds timeout) +{ + if(filename.empty()) + { + // LLAssetStorage metric: no filename + LL_ERRS() << "No filename specified" << LL_ENDL; + return; + } + + LLAssetID asset_id = tid.makeAssetID(gAgent.getSecureSessionID()); + LL_DEBUGS("AssetStorage") << "LLViewerAssetStorage::storeAssetData (legacy)" << asset_id << ":" << LLAssetType::lookup(asset_type) << LL_ENDL; + + LL_DEBUGS("AssetStorage") << "ASSET_ID: " << asset_id << LL_ENDL; + + S32 size = 0; + LLFILE* fp = LLFile::fopen(filename, "rb"); + if (fp) + { + fseek(fp, 0, SEEK_END); + size = ftell(fp); + fseek(fp, 0, SEEK_SET); + } + if( size ) + { + LLLegacyAssetRequest *legacy = new LLLegacyAssetRequest; + + legacy->mUpCallback = callback; + legacy->mUserData = user_data; + + LLFileSystem file(asset_id, asset_type, LLFileSystem::APPEND); + + const S32 buf_size = 65536; + U8 copy_buf[buf_size]; + while ((size = (S32)fread(copy_buf, 1, buf_size, fp))) + { + file.write(copy_buf, size); + } + fclose(fp); + + // if this upload fails, the caller needs to setup a new tempfile for us + if (temp_file) + { + LLFile::remove(filename); + } + + // LLAssetStorage metric: Success not needed; handled in the overloaded method here: + + LLViewerAssetStorage::storeAssetData( + tid, + asset_type, + legacyStoreDataCallback, + (void**)legacy, + temp_file, + is_priority); + } + else // size == 0 (but previous block changes size) + { + if( fp ) + { + // LLAssetStorage metric: Zero size + reportMetric( asset_id, asset_type, filename, LLUUID::null, 0, MR_ZERO_SIZE, __FILE__, __LINE__, "The file was zero length" ); + } + else + { + // LLAssetStorage metric: Missing File + reportMetric( asset_id, asset_type, filename, LLUUID::null, 0, MR_FILE_NONEXIST, __FILE__, __LINE__, "The file didn't exist" ); + } + if (callback) + { + callback(asset_id, user_data, LL_ERR_CANNOT_OPEN_FILE, LLExtStat::BLOCKED_FILE); + } + } +} + +/** + * @brief Allocate and queue an asset fetch request for the viewer + * + * This is a nearly-verbatim copy of the base class's implementation + * with the following changes: + * - Use a locally-derived request class + * - Start timing for metrics when request is queued + * + * This is an unfortunate implementation choice but it's forced by + * current conditions. A refactoring that might clean up the layers + * of responsibility or introduce factories or more virtualization + * of methods would enable a more attractive solution. + * + * If LLAssetStorage::_queueDataRequest changes, this must change + * as well. + */ + +// virtual +void LLViewerAssetStorage::_queueDataRequest( + const LLUUID& uuid, + LLAssetType::EType atype, + LLGetAssetCallback callback, + void *user_data, + bool duplicate, + bool is_priority) +{ + mCountRequests++; + queueRequestHttp(uuid, atype, callback, user_data, duplicate, is_priority); +} + +void LLViewerAssetStorage::queueRequestHttp( + const LLUUID& uuid, + LLAssetType::EType atype, + LLGetAssetCallback callback, + void *user_data, + bool duplicate, + bool is_priority) +{ + LL_DEBUGS("ViewerAsset") << "Request asset via HTTP " << uuid << " type " << LLAssetType::lookup(atype) << LL_ENDL; + + bool with_http = true; + LLViewerAssetRequest *req = new LLViewerAssetRequest(uuid, atype, with_http); + req->mDownCallback = callback; + req->mUserData = user_data; + req->mIsPriority = is_priority; + if (!duplicate) + { + // Only collect metrics for non-duplicate requests. Others + // are piggy-backing and will artificially lower averages. + req->mMetricsStartTime = LLViewerAssetStatsFF::get_timestamp(); + } + mPendingDownloads.push_back(req); + + // This is the same as the current UDP logic - don't re-request a duplicate. + if (!duplicate) + { + LLCoprocedureManager* manager = LLCoprocedureManager::getInstance(); + bool with_http = true; + bool is_temp = false; + LLViewerAssetStatsFF::record_enqueue(atype, with_http, is_temp); + manager->enqueueCoprocedure( + VIEWER_ASSET_STORAGE_CORO_POOL, + "LLViewerAssetStorage::assetRequestCoro", + [this, req, uuid, atype, callback, user_data] + (LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t&, const LLUUID&) + { + assetRequestCoro(req, uuid, atype, callback, user_data); + }); + } +} + +void LLViewerAssetStorage::capsRecvForRegion(const LLUUID& region_id, std::string pumpname) +{ + LLViewerRegion *regionp = LLWorld::instance().getRegionFromID(region_id); + if (!regionp) + { + LL_WARNS("ViewerAsset") << "region not found for region_id " << region_id << LL_ENDL; + } + else + { + mViewerAssetUrl = regionp->getViewerAssetUrl(); + } + + LLEventPumps::instance().obtain(pumpname).post(LLSD()); +} + +struct LLScopedIncrement +{ + LLScopedIncrement(S32& counter): + mCounter(counter) + { + ++mCounter; + } + ~LLScopedIncrement() + { + --mCounter; + } + S32& mCounter; +}; + +void LLViewerAssetStorage::assetRequestCoro( + LLViewerAssetRequest *req, + const LLUUID uuid, + LLAssetType::EType atype, + LLGetAssetCallback callback, + void *user_data) +{ + LLScopedIncrement coro_count_boost(sAssetCoroCount); // static counter since corotine can outlive LLViewerAssetStorage + + S32 result_code = LL_ERR_NOERR; + LLExtStat ext_status = LLExtStat::NONE; + + if (!gAssetStorage) + { + LL_WARNS_ONCE("ViewerAsset") << "Asset request fails: asset storage no longer exists" << LL_ENDL; + return; + } + + mCountStarted++; + + if (!gAgent.getRegion()) + { + LL_WARNS_ONCE("ViewerAsset") << "Asset request fails: no region set" << LL_ENDL; + result_code = LL_ERR_ASSET_REQUEST_FAILED; + ext_status = LLExtStat::NONE; + removeAndCallbackPendingDownloads(uuid, atype, uuid, atype, result_code, ext_status); + return; + } + else if (!gAgent.getRegion()->capabilitiesReceived()) + { + LL_WARNS_ONCE("ViewerAsset") << "Waiting for capabilities" << LL_ENDL; + + LLEventStream capsRecv("waitForCaps", true); + + gAgent.getRegion()->setCapabilitiesReceivedCallback( + boost::bind(&LLViewerAssetStorage::capsRecvForRegion, this, _1, capsRecv.getName())); + + llcoro::suspendUntilEventOn(capsRecv); + + if (LLApp::isExiting() || !gAssetStorage) + { + return; + } + + LL_WARNS_ONCE("ViewerAsset") << "capsRecv got event" << LL_ENDL; + LL_WARNS_ONCE("ViewerAsset") << "region " << gAgent.getRegion() << " mViewerAssetUrl " << mViewerAssetUrl << LL_ENDL; + } + if (mViewerAssetUrl.empty() && gAgent.getRegion()) + { + mViewerAssetUrl = gAgent.getRegion()->getViewerAssetUrl(); + } + if (mViewerAssetUrl.empty()) + { + LL_WARNS_ONCE("ViewerAsset") << "asset request fails: caps received but no viewer asset cap found" << LL_ENDL; + result_code = LL_ERR_ASSET_REQUEST_FAILED; + ext_status = LLExtStat::NONE; + removeAndCallbackPendingDownloads(uuid, atype, uuid, atype, result_code, ext_status); + return; + } + std::string url = getAssetURL(mViewerAssetUrl, uuid,atype); + LL_DEBUGS("ViewerAsset") << "request url: " << url << LL_ENDL; + + LLCore::HttpRequest::policy_t httpPolicy(LLAppCoreHttp::AP_TEXTURE); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("assetRequestCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); + + LLSD result = httpAdapter->getRawAndSuspend(httpRequest, url, httpOpts); + + if (LLApp::isExiting() || !gAssetStorage) + { + // Bail out if result arrives after shutdown has been started. + return; + } + + mCountCompleted++; + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + if (!status) + { + LL_DEBUGS("ViewerAsset") << "request failed, status " << status.toTerseString() << LL_ENDL; + result_code = LL_ERR_ASSET_REQUEST_FAILED; + ext_status = LLExtStat::NONE; + } + else if (!result.has(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_RAW)) + { + LL_DEBUGS("ViewerAsset") << "request failed, no data returned!" << LL_ENDL; + result_code = LL_ERR_ASSET_REQUEST_FAILED; + ext_status = LLExtStat::NONE; + } + else if (!result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_RAW].isBinary()) + { + LL_DEBUGS("ViewerAsset") << "request failed, invalid data format!" << LL_ENDL; + result_code = LL_ERR_ASSET_REQUEST_FAILED; + ext_status = LLExtStat::NONE; + } + else + { + LL_DEBUGS("ViewerAsset") << "request succeeded, url " << url << LL_ENDL; + + const LLSD::Binary &raw = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_RAW].asBinary(); + + S32 size = raw.size(); + if (size > 0) + { + mTotalBytesFetched += size; + + // This create-then-rename flow is modeled on + // LLTransferTargetVFile, which is what was used in the UDP + // case. + LLUUID temp_id; + temp_id.generate(); + LLFileSystem vf(temp_id, atype, LLFileSystem::WRITE); + req->mBytesFetched = size; + if (!vf.write(raw.data(),size)) + { + // TODO asset-http: handle error + LL_WARNS("ViewerAsset") << "Failure in vf.write()" << LL_ENDL; + result_code = LL_ERR_ASSET_REQUEST_FAILED; + ext_status = LLExtStat::CACHE_CORRUPT; + } + else if (!vf.rename(uuid, atype)) + { + LL_WARNS("ViewerAsset") << "rename failed" << LL_ENDL; + result_code = LL_ERR_ASSET_REQUEST_FAILED; + ext_status = LLExtStat::CACHE_CORRUPT; + } + else + { + mCountSucceeded++; + } + } + else + { + // TODO asset-http: handle invalid size case + LL_WARNS("ViewerAsset") << "bad size" << LL_ENDL; + result_code = LL_ERR_ASSET_REQUEST_FAILED; + ext_status = LLExtStat::NONE; + } + } + + // Clean up pending downloads and trigger callbacks + removeAndCallbackPendingDownloads(uuid, atype, uuid, atype, result_code, ext_status); +} + +std::string LLViewerAssetStorage::getAssetURL(const std::string& cap_url, const LLUUID& uuid, LLAssetType::EType atype) +{ + std::string type_name = LLAssetType::lookup(atype); + std::string url = cap_url + "/?" + type_name + "_id=" + uuid.asString(); + return url; +} + +void LLViewerAssetStorage::logAssetStorageInfo() +{ + LLMemory::logMemoryInfo(true); + LL_INFOS("AssetStorage") << "Active coros " << sAssetCoroCount << LL_ENDL; + LL_INFOS("AssetStorage") << "mPendingDownloads size " << mPendingDownloads.size() << LL_ENDL; + LL_INFOS("AssetStorage") << "mCountStarted " << mCountStarted << LL_ENDL; + LL_INFOS("AssetStorage") << "mCountCompleted " << mCountCompleted << LL_ENDL; + LL_INFOS("AssetStorage") << "mCountSucceeded " << mCountSucceeded << LL_ENDL; + LL_INFOS("AssetStorage") << "mTotalBytesFetched " << mTotalBytesFetched << LL_ENDL; +} diff --git a/indra/newview/llviewerassetstorage.h b/indra/newview/llviewerassetstorage.h index f70e9de616..fdb8af7457 100644 --- a/indra/newview/llviewerassetstorage.h +++ b/indra/newview/llviewerassetstorage.h @@ -1,130 +1,130 @@ -/** - * @file llviewerassetstorage.h - * @brief Class for loading asset data to/from an external source. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLVIEWERASSETSTORAGE_H -#define LLVIEWERASSETSTORAGE_H - -#include "llassetstorage.h" -#include "llcorehttputil.h" - -class LLFileSystem; - -class LLViewerAssetRequest; - -class LLViewerAssetStorage : public LLAssetStorage -{ -public: - LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, const LLHost &upstream_host); - - LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer); - - ~LLViewerAssetStorage(); - - void storeAssetData( - const LLTransactionID& tid, - LLAssetType::EType atype, - LLStoreAssetCallback callback, - void* user_data, - bool temp_file = false, - bool is_priority = false, - bool store_local = false, - bool user_waiting=false, - F64Seconds timeout=LL_ASSET_STORAGE_TIMEOUT) override; - - void storeAssetData( - const std::string& filename, - const LLTransactionID& tid, - LLAssetType::EType type, - LLStoreAssetCallback callback, - void* user_data, - bool temp_file = false, - bool is_priority = false, - bool user_waiting=false, - F64Seconds timeout=LL_ASSET_STORAGE_TIMEOUT) override; - -protected: - void _queueDataRequest(const LLUUID& uuid, - LLAssetType::EType type, - LLGetAssetCallback callback, - void *user_data, - bool duplicate, - bool is_priority) override; - - void queueRequestHttp(const LLUUID& uuid, - LLAssetType::EType type, - LLGetAssetCallback callback, - void *user_data, - bool duplicate, - bool is_priority); - - void capsRecvForRegion(const LLUUID& region_id, std::string pumpname); - - void assetRequestCoro(LLViewerAssetRequest *req, - const LLUUID uuid, - LLAssetType::EType atype, - LLGetAssetCallback callback, - void *user_data); - - std::string getAssetURL(const std::string& cap_url, const LLUUID& uuid, LLAssetType::EType atype); - - void logAssetStorageInfo() override; - - // Asset storage works through coroutines and coroutines have limited queue capacity - // This class is meant to temporary store requests when fiber's queue is full - class CoroWaitList - { - public: - CoroWaitList(LLViewerAssetRequest *req, - const LLUUID& uuid, - LLAssetType::EType atype, - LLGetAssetCallback &callback, - void *user_data) - : mRequest(req), - mId(uuid), - mType(atype), - mCallback(callback), - mUserData(user_data) - { - } - - LLViewerAssetRequest* mRequest; - LLUUID mId; - LLAssetType::EType mType; - LLGetAssetCallback mCallback; - void *mUserData; - }; - - std::string mViewerAssetUrl; - S32 mCountRequests; - S32 mCountStarted; - S32 mCountCompleted; - S32 mCountSucceeded; - S64 mTotalBytesFetched; - - static S32 sAssetCoroCount; // coroutine count, static since coroutines can outlive LLViewerAssetStorage -}; - -#endif +/** + * @file llviewerassetstorage.h + * @brief Class for loading asset data to/from an external source. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLVIEWERASSETSTORAGE_H +#define LLVIEWERASSETSTORAGE_H + +#include "llassetstorage.h" +#include "llcorehttputil.h" + +class LLFileSystem; + +class LLViewerAssetRequest; + +class LLViewerAssetStorage : public LLAssetStorage +{ +public: + LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer, const LLHost &upstream_host); + + LLViewerAssetStorage(LLMessageSystem *msg, LLXferManager *xfer); + + ~LLViewerAssetStorage(); + + void storeAssetData( + const LLTransactionID& tid, + LLAssetType::EType atype, + LLStoreAssetCallback callback, + void* user_data, + bool temp_file = false, + bool is_priority = false, + bool store_local = false, + bool user_waiting=false, + F64Seconds timeout=LL_ASSET_STORAGE_TIMEOUT) override; + + void storeAssetData( + const std::string& filename, + const LLTransactionID& tid, + LLAssetType::EType type, + LLStoreAssetCallback callback, + void* user_data, + bool temp_file = false, + bool is_priority = false, + bool user_waiting=false, + F64Seconds timeout=LL_ASSET_STORAGE_TIMEOUT) override; + +protected: + void _queueDataRequest(const LLUUID& uuid, + LLAssetType::EType type, + LLGetAssetCallback callback, + void *user_data, + bool duplicate, + bool is_priority) override; + + void queueRequestHttp(const LLUUID& uuid, + LLAssetType::EType type, + LLGetAssetCallback callback, + void *user_data, + bool duplicate, + bool is_priority); + + void capsRecvForRegion(const LLUUID& region_id, std::string pumpname); + + void assetRequestCoro(LLViewerAssetRequest *req, + const LLUUID uuid, + LLAssetType::EType atype, + LLGetAssetCallback callback, + void *user_data); + + std::string getAssetURL(const std::string& cap_url, const LLUUID& uuid, LLAssetType::EType atype); + + void logAssetStorageInfo() override; + + // Asset storage works through coroutines and coroutines have limited queue capacity + // This class is meant to temporary store requests when fiber's queue is full + class CoroWaitList + { + public: + CoroWaitList(LLViewerAssetRequest *req, + const LLUUID& uuid, + LLAssetType::EType atype, + LLGetAssetCallback &callback, + void *user_data) + : mRequest(req), + mId(uuid), + mType(atype), + mCallback(callback), + mUserData(user_data) + { + } + + LLViewerAssetRequest* mRequest; + LLUUID mId; + LLAssetType::EType mType; + LLGetAssetCallback mCallback; + void *mUserData; + }; + + std::string mViewerAssetUrl; + S32 mCountRequests; + S32 mCountStarted; + S32 mCountCompleted; + S32 mCountSucceeded; + S64 mTotalBytesFetched; + + static S32 sAssetCoroCount; // coroutine count, static since coroutines can outlive LLViewerAssetStorage +}; + +#endif diff --git a/indra/newview/llviewerassetupload.cpp b/indra/newview/llviewerassetupload.cpp index 76592fd67b..b12acc8f42 100644 --- a/indra/newview/llviewerassetupload.cpp +++ b/indra/newview/llviewerassetupload.cpp @@ -1,1032 +1,1032 @@ -/** -* @file llviewerassetupload.cpp -* @author optional -* @brief brief description of the file -* -* $LicenseInfo:firstyear=2011&license=viewerlgpl$ -* Second Life Viewer Source Code -* Copyright (C) 2011, Linden Research, Inc. -* -* This library is free software; you can redistribute it and/or -* modify it under the terms of the GNU Lesser General Public -* License as published by the Free Software Foundation; -* version 2.1 of the License only. -* -* This library is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* Lesser General Public License for more details. -* -* You should have received a copy of the GNU Lesser General Public -* License along with this library; if not, write to the Free Software -* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -* -* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA -* $/LicenseInfo$ -*/ - -#include "llviewerprecompiledheaders.h" - -#include "linden_common.h" -#include "llviewertexturelist.h" -#include "llimage.h" -#include "lltrans.h" -#include "lluuid.h" -#include "llvorbisencode.h" -#include "lluploaddialog.h" -#include "llnotificationsutil.h" -#include "llagent.h" -#include "llfloaterreg.h" -#include "llfloatersnapshot.h" -#include "llstatusbar.h" -#include "llinventorypanel.h" -#include "llsdutil.h" -#include "llviewerassetupload.h" -#include "llappviewer.h" -#include "llviewerstats.h" -#include "llfilesystem.h" -#include "llgesturemgr.h" -#include "llpreviewnotecard.h" -#include "llpreviewgesture.h" -#include "llcoproceduremanager.h" -#include "llthread.h" -#include "llkeyframemotion.h" -#include "lldatapacker.h" -#include "llvoavatarself.h" - -void dialog_refresh_all(); - -static const U32 LL_ASSET_UPLOAD_TIMEOUT_SEC = 60; - -LLResourceUploadInfo::LLResourceUploadInfo(LLTransactionID transactId, - LLAssetType::EType assetType, std::string name, std::string description, - S32 compressionInfo, LLFolderType::EType destinationType, - LLInventoryType::EType inventoryType, U32 nextOWnerPerms, - U32 groupPerms, U32 everyonePerms, S32 expectedCost, bool showInventory) : - mTransactionId(transactId), - mAssetType(assetType), - mName(name), - mDescription(description), - mCompressionInfo(compressionInfo), - mDestinationFolderType(destinationType), - mInventoryType(inventoryType), - mNextOwnerPerms(nextOWnerPerms), - mGroupPerms(groupPerms), - mEveryonePerms(everyonePerms), - mExpectedUploadCost(expectedCost), - mShowInventory(showInventory), - mFolderId(LLUUID::null), - mItemId(LLUUID::null), - mAssetId(LLAssetID::null) -{ } - - -LLResourceUploadInfo::LLResourceUploadInfo(std::string name, - std::string description, S32 compressionInfo, - LLFolderType::EType destinationType, LLInventoryType::EType inventoryType, - U32 nextOWnerPerms, U32 groupPerms, U32 everyonePerms, S32 expectedCost, bool showInventory) : - mName(name), - mDescription(description), - mCompressionInfo(compressionInfo), - mDestinationFolderType(destinationType), - mInventoryType(inventoryType), - mNextOwnerPerms(nextOWnerPerms), - mGroupPerms(groupPerms), - mEveryonePerms(everyonePerms), - mExpectedUploadCost(expectedCost), - mShowInventory(showInventory), - mTransactionId(), - mAssetType(LLAssetType::AT_NONE), - mFolderId(LLUUID::null), - mItemId(LLUUID::null), - mAssetId(LLAssetID::null) -{ - mTransactionId.generate(); -} - -LLResourceUploadInfo::LLResourceUploadInfo(LLAssetID assetId, LLAssetType::EType assetType, std::string name) : - mAssetId(assetId), - mAssetType(assetType), - mName(name), - mDescription(), - mCompressionInfo(0), - mDestinationFolderType(LLFolderType::FT_NONE), - mInventoryType(LLInventoryType::IT_NONE), - mNextOwnerPerms(0), - mGroupPerms(0), - mEveryonePerms(0), - mExpectedUploadCost(0), - mShowInventory(true), - mTransactionId(), - mFolderId(LLUUID::null), - mItemId(LLUUID::null) -{ -} - -LLSD LLResourceUploadInfo::prepareUpload() -{ - if (mAssetId.isNull()) - generateNewAssetId(); - - incrementUploadStats(); - assignDefaults(); - - return LLSD().with("success", LLSD::Boolean(true)); -} - -std::string LLResourceUploadInfo::getAssetTypeString() const -{ - return LLAssetType::lookup(mAssetType); -} - -std::string LLResourceUploadInfo::getInventoryTypeString() const -{ - return LLInventoryType::lookup(mInventoryType); -} - -LLSD LLResourceUploadInfo::generatePostBody() -{ - LLSD body; - - body["folder_id"] = mFolderId; - body["asset_type"] = getAssetTypeString(); - body["inventory_type"] = getInventoryTypeString(); - body["name"] = mName; - body["description"] = mDescription; - body["next_owner_mask"] = LLSD::Integer(mNextOwnerPerms); - body["group_mask"] = LLSD::Integer(mGroupPerms); - body["everyone_mask"] = LLSD::Integer(mEveryonePerms); - - return body; - -} - -void LLResourceUploadInfo::logPreparedUpload() -{ - LL_INFOS() << "*** Uploading: " << std::endl << - "Type: " << LLAssetType::lookup(mAssetType) << std::endl << - "UUID: " << mAssetId.asString() << std::endl << - "Name: " << mName << std::endl << - "Desc: " << mDescription << std::endl << - "Expected Upload Cost: " << mExpectedUploadCost << std::endl << - "Folder: " << mFolderId << std::endl << - "Asset Type: " << LLAssetType::lookup(mAssetType) << LL_ENDL; -} - -LLUUID LLResourceUploadInfo::finishUpload(LLSD &result) -{ - if (getFolderId().isNull()) - { - return LLUUID::null; - } - - U32 permsEveryone = PERM_NONE; - U32 permsGroup = PERM_NONE; - U32 permsNextOwner = PERM_ALL; - - if (result.has("new_next_owner_mask")) - { - // The server provided creation perms so use them. - // Do not assume we got the perms we asked for in - // since the server may not have granted them all. - permsEveryone = result["new_everyone_mask"].asInteger(); - permsGroup = result["new_group_mask"].asInteger(); - permsNextOwner = result["new_next_owner_mask"].asInteger(); - } - else - { - // The server doesn't provide creation perms - // so use old assumption-based perms. - if (getAssetTypeString() != "snapshot") - { - permsNextOwner = PERM_MOVE | PERM_TRANSFER; - } - } - - LLPermissions new_perms; - new_perms.init( - gAgent.getID(), - gAgent.getID(), - LLUUID::null, - LLUUID::null); - - new_perms.initMasks( - PERM_ALL, - PERM_ALL, - permsEveryone, - permsGroup, - permsNextOwner); - - U32 flagsInventoryItem = 0; - if (result.has("inventory_flags")) - { - flagsInventoryItem = static_cast(result["inventory_flags"].asInteger()); - if (flagsInventoryItem != 0) - { - LL_INFOS() << "inventory_item_flags " << flagsInventoryItem << LL_ENDL; - } - } - S32 creationDate = time_corrected(); - - LLUUID serverInventoryItem = result["new_inventory_item"].asUUID(); - LLUUID serverAssetId = result["new_asset"].asUUID(); - - LLPointer item = new LLViewerInventoryItem( - serverInventoryItem, - getFolderId(), - new_perms, - serverAssetId, - getAssetType(), - getInventoryType(), - getName(), - getDescription(), - LLSaleInfo::DEFAULT, - flagsInventoryItem, - creationDate); - - gInventory.updateItem(item); - gInventory.notifyObservers(); - - return serverInventoryItem; -} - - -LLAssetID LLResourceUploadInfo::generateNewAssetId() -{ - if (gDisconnected) - { - LLAssetID rv; - - rv.setNull(); - return rv; - } - mAssetId = mTransactionId.makeAssetID(gAgent.getSecureSessionID()); - - return mAssetId; -} - -void LLResourceUploadInfo::incrementUploadStats() const -{ - if (LLAssetType::AT_SOUND == mAssetType) - { - add(LLStatViewer::UPLOAD_SOUND, 1); - } - else if (LLAssetType::AT_TEXTURE == mAssetType) - { - add(LLStatViewer::UPLOAD_TEXTURE, 1); - } - else if (LLAssetType::AT_ANIMATION == mAssetType) - { - add(LLStatViewer::ANIMATION_UPLOADS, 1); - } -} - -void LLResourceUploadInfo::assignDefaults() -{ - if (LLInventoryType::IT_NONE == mInventoryType) - { - mInventoryType = LLInventoryType::defaultForAssetType(mAssetType); - } - LLStringUtil::stripNonprintable(mName); - LLStringUtil::stripNonprintable(mDescription); - - if (mName.empty()) - { - mName = "(No Name)"; - } - if (mDescription.empty()) - { - mDescription = "(No Description)"; - } - - mFolderId = gInventory.findUserDefinedCategoryUUIDForType( - (mDestinationFolderType == LLFolderType::FT_NONE) ? - (LLFolderType::EType)mAssetType : mDestinationFolderType); -} - -std::string LLResourceUploadInfo::getDisplayName() const -{ - return (mName.empty()) ? mAssetId.asString() : mName; -}; - -bool LLResourceUploadInfo::findAssetTypeOfExtension(const std::string& exten, LLAssetType::EType& asset_type) -{ - U32 codec; - return findAssetTypeAndCodecOfExtension(exten, asset_type, codec, false); -} - -// static -bool LLResourceUploadInfo::findAssetTypeAndCodecOfExtension(const std::string& exten, LLAssetType::EType& asset_type, U32& codec, bool bulk_upload) -{ - bool succ = false; - std::string exten_lc(exten); - LLStringUtil::toLower(exten_lc); - codec = LLImageBase::getCodecFromExtension(exten_lc); - if (codec != IMG_CODEC_INVALID) - { - asset_type = LLAssetType::AT_TEXTURE; - succ = true; - } - else if (exten_lc == "wav") - { - asset_type = LLAssetType::AT_SOUND; - succ = true; - } - else if (exten_lc == "anim") - { - asset_type = LLAssetType::AT_ANIMATION; - succ = true; - } - else if (!bulk_upload && (exten_lc == "bvh")) - { - asset_type = LLAssetType::AT_ANIMATION; - succ = true; - } - - return succ; -} - -//========================================================================= -LLNewFileResourceUploadInfo::LLNewFileResourceUploadInfo( - std::string fileName, - std::string name, - std::string description, - S32 compressionInfo, - LLFolderType::EType destinationType, - LLInventoryType::EType inventoryType, - U32 nextOWnerPerms, - U32 groupPerms, - U32 everyonePerms, - S32 expectedCost, - bool show_inventory) : - LLResourceUploadInfo(name, description, compressionInfo, - destinationType, inventoryType, - nextOWnerPerms, groupPerms, everyonePerms, expectedCost, show_inventory), - mFileName(fileName) -{ -} - -LLSD LLNewFileResourceUploadInfo::prepareUpload() -{ - if (getAssetId().isNull()) - generateNewAssetId(); - - LLSD result = exportTempFile(); - if (result.has("error")) - return result; - - return LLResourceUploadInfo::prepareUpload(); -} - -LLSD LLNewFileResourceUploadInfo::exportTempFile() -{ - std::string filename = gDirUtilp->getTempFilename(); - - std::string exten = gDirUtilp->getExtension(getFileName()); - - LLAssetType::EType assetType = LLAssetType::AT_NONE; - U32 codec = IMG_CODEC_INVALID; - bool found_type = findAssetTypeAndCodecOfExtension(exten, assetType, codec); - - std::string errorMessage; - std::string errorLabel; - - bool error = false; - - if (exten.empty()) - { - std::string shortName = gDirUtilp->getBaseFileName(filename); - - // No extension - errorMessage = llformat( - "No file extension for the file: '%s'\nPlease make sure the file has a correct file extension", - shortName.c_str()); - errorLabel = "NoFileExtension"; - error = true; - } - else if (!found_type) - { - // Unknown extension - errorMessage = llformat(LLTrans::getString("UnknownFileExtension").c_str(), exten.c_str()); - errorLabel = "ErrorMessage"; - error = true;; - } - else if (assetType == LLAssetType::AT_TEXTURE) - { - // It's an image file, the upload procedure is the same for all - if (!LLViewerTextureList::createUploadFile(getFileName(), filename, codec)) - { - errorMessage = llformat("Problem with file %s:\n\n%s\n", - getFileName().c_str(), LLImage::getLastThreadError().c_str()); - errorLabel = "ProblemWithFile"; - error = true; - } - } - else if (assetType == LLAssetType::AT_SOUND) - { - S32 encodeResult = 0; - - LL_INFOS() << "Attempting to encode wav as an ogg file" << LL_ENDL; - - encodeResult = encode_vorbis_file(getFileName(), filename); - - if (LLVORBISENC_NOERR != encodeResult) - { - switch (encodeResult) - { - case LLVORBISENC_DEST_OPEN_ERR: - errorMessage = llformat("Couldn't open temporary compressed sound file for writing: %s\n", filename.c_str()); - errorLabel = "CannotOpenTemporarySoundFile"; - break; - - default: - errorMessage = llformat("Unknown vorbis encode failure on: %s\n", getFileName().c_str()); - errorLabel = "UnknownVorbisEncodeFailure"; - break; - } - error = true; - } - } - else if (exten == "bvh") - { - errorMessage = llformat("We do not currently support bulk upload of animation files\n"); - errorLabel = "DoNotSupportBulkAnimationUpload"; - error = true; - } - else if (exten == "anim") - { - // Default unless everything succeeds - errorLabel = "ProblemWithFile"; - error = true; - - // read from getFileName() - LLAPRFile infile; - infile.open(getFileName(),LL_APR_RB); - if (!infile.getFileHandle()) - { - LL_WARNS() << "Couldn't open file for reading: " << getFileName() << LL_ENDL; - errorMessage = llformat("Failed to open animation file %s\n", getFileName().c_str()); - } - else - { - S32 size = LLAPRFile::size(getFileName()); - U8* buffer = new U8[size]; - S32 size_read = infile.read(buffer,size); - if (size_read != size) - { - errorMessage = llformat("Failed to read animation file %s: wanted %d bytes, got %d\n", getFileName().c_str(), size, size_read); - } - else - { - LLDataPackerBinaryBuffer dp(buffer, size); - LLKeyframeMotion *motionp = new LLKeyframeMotion(getAssetId()); - motionp->setCharacter(gAgentAvatarp); - if (motionp->deserialize(dp, getAssetId(), false)) - { - // write to temp file - bool succ = motionp->dumpToFile(filename); - if (succ) - { - assetType = LLAssetType::AT_ANIMATION; - errorLabel = ""; - error = false; - } - else - { - errorMessage = "Failed saving temporary animation file"; - } - } - else - { - errorMessage = "Failed reading animation file"; - } - } - } - } - else - { - // Unknown extension - errorMessage = llformat(LLTrans::getString("UnknownFileExtension").c_str(), exten.c_str()); - errorLabel = "ErrorMessage"; - error = true;; - } - - if (error) - { - LLSD errorResult(LLSD::emptyMap()); - - errorResult["error"] = LLSD::Binary(true); - errorResult["message"] = errorMessage; - errorResult["label"] = errorLabel; - return errorResult; - } - - setAssetType(assetType); - - // copy this file into the cache for upload - S32 file_size; - LLAPRFile infile; - infile.open(filename, LL_APR_RB, NULL, &file_size); - if (infile.getFileHandle()) - { - LLFileSystem file(getAssetId(), assetType, LLFileSystem::APPEND); - - const S32 buf_size = 65536; - U8 copy_buf[buf_size]; - while ((file_size = infile.read(copy_buf, buf_size))) - { - file.write(copy_buf, file_size); - } - } - else - { - errorMessage = llformat("Unable to access output file: %s", filename.c_str()); - LLSD errorResult(LLSD::emptyMap()); - - errorResult["error"] = LLSD::Binary(true); - errorResult["message"] = errorMessage; - return errorResult; - } - - return LLSD(); - -} - -//========================================================================= -LLNewBufferedResourceUploadInfo::LLNewBufferedResourceUploadInfo( - const std::string& buffer, - const LLAssetID& asset_id, - std::string name, - std::string description, - S32 compressionInfo, - LLFolderType::EType destinationType, - LLInventoryType::EType inventoryType, - LLAssetType::EType assetType, - U32 nextOWnerPerms, - U32 groupPerms, - U32 everyonePerms, - S32 expectedCost, - bool show_inventory, - uploadFinish_f finish, - uploadFailure_f failure) - : LLResourceUploadInfo(name, description, compressionInfo, - destinationType, inventoryType, - nextOWnerPerms, groupPerms, everyonePerms, expectedCost, show_inventory) - , mBuffer(buffer) - , mFinishFn(finish) - , mFailureFn(failure) -{ - setAssetType(assetType); - setAssetId(asset_id); -} - -LLSD LLNewBufferedResourceUploadInfo::prepareUpload() -{ - if (getAssetId().isNull()) - generateNewAssetId(); - - LLSD result = exportTempFile(); - if (result.has("error")) - return result; - - return LLResourceUploadInfo::prepareUpload(); -} - -LLSD LLNewBufferedResourceUploadInfo::exportTempFile() -{ - std::string filename = gDirUtilp->getTempFilename(); - - // copy buffer to the cache for upload - LLFileSystem file(getAssetId(), getAssetType(), LLFileSystem::APPEND); - file.write((U8*) mBuffer.c_str(), mBuffer.size()); - - return LLSD(); -} - -LLUUID LLNewBufferedResourceUploadInfo::finishUpload(LLSD &result) -{ - LLUUID newItemId = LLResourceUploadInfo::finishUpload(result); - - if (mFinishFn) - { - mFinishFn(result["new_asset"].asUUID(), result); - } - - return newItemId; -} - -bool LLNewBufferedResourceUploadInfo::failedUpload(LLSD &result, std::string &reason) -{ - if (mFailureFn) - { - return mFailureFn(getAssetId(), result, reason); - } - - return false; // Not handled -} - -//========================================================================= -LLBufferedAssetUploadInfo::LLBufferedAssetUploadInfo(LLUUID itemId, LLAssetType::EType assetType, std::string buffer, invnUploadFinish_f finish, uploadFailed_f failed) : - LLResourceUploadInfo(std::string(), std::string(), 0, LLFolderType::FT_NONE, LLInventoryType::IT_NONE, - 0, 0, 0, 0), - mTaskUpload(false), - mTaskId(LLUUID::null), - mContents(buffer), - mInvnFinishFn(finish), - mTaskFinishFn(nullptr), - mFailureFn(failed), - mStoredToCache(false) -{ - setItemId(itemId); - setAssetType(assetType); -} - -LLBufferedAssetUploadInfo::LLBufferedAssetUploadInfo(LLUUID itemId, LLPointer image, invnUploadFinish_f finish) : - LLResourceUploadInfo(std::string(), std::string(), 0, LLFolderType::FT_NONE, LLInventoryType::IT_NONE, - 0, 0, 0, 0), - mTaskUpload(false), - mTaskId(LLUUID::null), - mContents(), - mInvnFinishFn(finish), - mTaskFinishFn(nullptr), - mFailureFn(nullptr), - mStoredToCache(false) -{ - setItemId(itemId); - - LLImageDataSharedLock lock(image); - - EImageCodec codec = static_cast(image->getCodec()); - - switch (codec) - { - case IMG_CODEC_JPEG: - setAssetType(LLAssetType::AT_IMAGE_JPEG); - LL_INFOS() << "Upload Asset type set to JPEG." << LL_ENDL; - break; - case IMG_CODEC_TGA: - setAssetType(LLAssetType::AT_IMAGE_TGA); - LL_INFOS() << "Upload Asset type set to TGA." << LL_ENDL; - break; - default: - LL_WARNS() << "Unknown codec to asset type transition. Codec=" << (int)codec << "." << LL_ENDL; - break; - } - - size_t imageSize = image->getDataSize(); - mContents.reserve(imageSize); - mContents.assign((char *)image->getData(), imageSize); -} - -LLBufferedAssetUploadInfo::LLBufferedAssetUploadInfo(LLUUID taskId, LLUUID itemId, LLAssetType::EType assetType, std::string buffer, taskUploadFinish_f finish, uploadFailed_f failed) : - LLResourceUploadInfo(std::string(), std::string(), 0, LLFolderType::FT_NONE, LLInventoryType::IT_NONE, - 0, 0, 0, 0), - mTaskUpload(true), - mTaskId(taskId), - mContents(buffer), - mInvnFinishFn(nullptr), - mTaskFinishFn(finish), - mFailureFn(failed), - mStoredToCache(false) -{ - setItemId(itemId); - setAssetType(assetType); -} - -LLSD LLBufferedAssetUploadInfo::prepareUpload() -{ - if (getAssetId().isNull()) - generateNewAssetId(); - - LLFileSystem file(getAssetId(), getAssetType(), LLFileSystem::APPEND); - - S32 size = mContents.length() + 1; - file.write((U8*)mContents.c_str(), size); - - mStoredToCache = true; - - return LLSD().with("success", LLSD::Boolean(true)); -} - -LLSD LLBufferedAssetUploadInfo::generatePostBody() -{ - LLSD body; - - if (!getTaskId().isNull()) - { - body["task_id"] = getTaskId(); - } - body["item_id"] = getItemId(); - - return body; -} - -LLUUID LLBufferedAssetUploadInfo::finishUpload(LLSD &result) -{ - LLUUID newAssetId = result["new_asset"].asUUID(); - LLUUID itemId = getItemId(); - - if (mStoredToCache) - { - LLAssetType::EType assetType(getAssetType()); - LLFileSystem::renameFile(getAssetId(), assetType, newAssetId, assetType); - } - - if (mTaskUpload) - { - LLUUID taskId = getTaskId(); - - dialog_refresh_all(); - - if (mTaskFinishFn) - { - mTaskFinishFn(itemId, taskId, newAssetId, result); - } - } - else - { - LLUUID newItemId(LLUUID::null); - - if (itemId.notNull()) - { - LLViewerInventoryItem* item = (LLViewerInventoryItem*)gInventory.getItem(itemId); - if (!item) - { - LL_WARNS() << "Inventory item for " << getDisplayName() << " is no longer in agent inventory." << LL_ENDL; - return newAssetId; - } - - // Update viewer inventory item - LLPointer newItem = new LLViewerInventoryItem(item); - newItem->setAssetUUID(newAssetId); - - gInventory.updateItem(newItem); - - newItemId = newItem->getUUID(); - LL_INFOS() << "Inventory item " << item->getName() << " saved into " << newAssetId.asString() << LL_ENDL; - } - - if (mInvnFinishFn) - { - mInvnFinishFn(itemId, newAssetId, newItemId, result); - } - gInventory.notifyObservers(); - } - - return newAssetId; -} - -bool LLBufferedAssetUploadInfo::failedUpload(LLSD &result, std::string &reason) -{ - if (mFailureFn) - { - return mFailureFn(getItemId(), getTaskId(), result, reason); - } - return false; -} - -//========================================================================= - -LLScriptAssetUpload::LLScriptAssetUpload(LLUUID itemId, std::string buffer, invnUploadFinish_f finish, uploadFailed_f failed): - LLBufferedAssetUploadInfo(itemId, LLAssetType::AT_LSL_TEXT, buffer, finish, failed), - mExerienceId(), - mTargetType(MONO), - mIsRunning(false) -{ -} - -LLScriptAssetUpload::LLScriptAssetUpload(LLUUID taskId, LLUUID itemId, TargetType_t targetType, - bool isRunning, LLUUID exerienceId, std::string buffer, taskUploadFinish_f finish, uploadFailed_f failed): - LLBufferedAssetUploadInfo(taskId, itemId, LLAssetType::AT_LSL_TEXT, buffer, finish, failed), - mExerienceId(exerienceId), - mTargetType(targetType), - mIsRunning(isRunning) -{ -} - -LLSD LLScriptAssetUpload::generatePostBody() -{ - LLSD body; - - if (getTaskId().isNull()) - { - body["item_id"] = getItemId(); - body["target"] = "mono"; - } - else - { - body["task_id"] = getTaskId(); - body["item_id"] = getItemId(); - body["is_script_running"] = getIsRunning(); - body["target"] = (getTargetType() == MONO) ? "mono" : "lsl2"; - body["experience"] = getExerienceId(); - } - - return body; -} - -//========================================================================= -/*static*/ -LLUUID LLViewerAssetUpload::EnqueueInventoryUpload(const std::string &url, const LLResourceUploadInfo::ptr_t &uploadInfo) -{ - std::string procName("LLViewerAssetUpload::AssetInventoryUploadCoproc("); - - LLUUID queueId = LLCoprocedureManager::instance().enqueueCoprocedure("Upload", - procName + LLAssetType::lookup(uploadInfo->getAssetType()) + ")", - boost::bind(&LLViewerAssetUpload::AssetInventoryUploadCoproc, _1, _2, url, uploadInfo)); - - return queueId; -} - -//========================================================================= -/*static*/ -void LLViewerAssetUpload::AssetInventoryUploadCoproc(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &httpAdapter, - const LLUUID &id, std::string url, LLResourceUploadInfo::ptr_t uploadInfo) -{ - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOptions(new LLCore::HttpOptions); - httpOptions->setTimeout(LL_ASSET_UPLOAD_TIMEOUT_SEC); - - LLSD result = uploadInfo->prepareUpload(); - uploadInfo->logPreparedUpload(); - - if (result.has("error")) - { - HandleUploadError(LLCore::HttpStatus(499), result, uploadInfo); - return; - } - - llcoro::suspend(); - - if (uploadInfo->showUploadDialog()) - { - std::string uploadMessage = "Uploading...\n\n"; - uploadMessage.append(uploadInfo->getDisplayName()); - LLUploadDialog::modalUploadDialog(uploadMessage); - } - - LLSD body = uploadInfo->generatePostBody(); - - result = httpAdapter->postAndSuspend(httpRequest, url, body, httpOptions); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if ((!status) || (result.has("error"))) - { - HandleUploadError(status, result, uploadInfo); - if (uploadInfo->showUploadDialog()) - LLUploadDialog::modalUploadFinished(); - return; - } - - std::string uploader = result["uploader"].asString(); - - bool success = false; - if (!uploader.empty() && uploadInfo->getAssetId().notNull()) - { - result = httpAdapter->postFileAndSuspend(httpRequest, uploader, uploadInfo->getAssetId(), uploadInfo->getAssetType(), httpOptions); - httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - std::string ulstate = result["state"].asString(); - - if ((!status) || (ulstate != "complete")) - { - HandleUploadError(status, result, uploadInfo); - if (uploadInfo->showUploadDialog()) - LLUploadDialog::modalUploadFinished(); - return; - } - if (!result.has("success")) - { - result["success"] = LLSD::Boolean((ulstate == "complete") && status); - } - - S32 uploadPrice = result["upload_price"].asInteger(); - - if (uploadPrice > 0) - { - // this upload costed us L$, update our balance - // and display something saying that it cost L$ - LLStatusBar::sendMoneyBalanceRequest(); - - LLSD args; - args["AMOUNT"] = llformat("%d", uploadPrice); - LLNotificationsUtil::add("UploadPayment", args); - } - } - else - { - LL_WARNS() << "No upload url provided. Nothing uploaded, responding with previous result." << LL_ENDL; - } - LLUUID serverInventoryItem = uploadInfo->finishUpload(result); - - if (uploadInfo->showInventoryPanel()) - { - if (serverInventoryItem.notNull()) - { - success = true; - - LLFocusableElement* focus = gFocusMgr.getKeyboardFocus(); - - // Show the preview panel for textures and sounds to let - // user know that the image (or snapshot) arrived intact. - LLInventoryPanel* panel = LLInventoryPanel::getActiveInventoryPanel(false); - LLInventoryPanel::openInventoryPanelAndSetSelection(true, serverInventoryItem, false, false, !panel); - - // restore keyboard focus - gFocusMgr.setKeyboardFocus(focus); - } - else - { - LL_WARNS() << "Can't find a folder to put it in" << LL_ENDL; - } - } - - // remove the "Uploading..." message - if (uploadInfo->showUploadDialog()) - LLUploadDialog::modalUploadFinished(); - - // Let the Snapshot floater know we have finished uploading a snapshot to inventory - LLFloater* floater_snapshot = LLFloaterReg::findInstance("snapshot"); - if (uploadInfo->getAssetType() == LLAssetType::AT_TEXTURE && floater_snapshot && floater_snapshot->isShown()) - { - floater_snapshot->notify(LLSD().with("set-finished", LLSD().with("ok", success).with("msg", "inventory"))); - } -} - -//========================================================================= -/*static*/ -void LLViewerAssetUpload::HandleUploadError(LLCore::HttpStatus status, LLSD &result, LLResourceUploadInfo::ptr_t &uploadInfo) -{ - std::string reason; - std::string label("CannotUploadReason"); - - LL_WARNS() << ll_pretty_print_sd(result) << LL_ENDL; - - if (result.has("label")) - { - label = result["label"].asString(); - } - - if (result.has("message")) - { - reason = result["message"].asString(); - } - else - { - switch (status.getType()) - { - case 404: - reason = LLTrans::getString("AssetUploadServerUnreacheble"); - break; - case 499: - reason = LLTrans::getString("AssetUploadServerDifficulties"); - break; - case 503: - reason = LLTrans::getString("AssetUploadServerUnavaliable"); - break; - default: - reason = LLTrans::getString("AssetUploadRequestInvalid"); - } - } - - LLSD args; - if(label == "ErrorMessage") - { - args["ERROR_MESSAGE"] = reason; - } - else - { - args["FILE"] = uploadInfo->getDisplayName(); - args["REASON"] = reason; - args["ERROR"] = reason; - } - - LLNotificationsUtil::add(label, args); - - if (uploadInfo->failedUpload(result, reason)) - { - // no further action required, already handled by a callback - // ex: do not trigger snapshot floater when failing material texture - return; - } - - // Todo: move these floater specific actions into proper callbacks - - // Let the Snapshot floater know we have failed uploading. - LLFloaterSnapshot* floater_snapshot = LLFloaterSnapshot::findInstance(); - if (floater_snapshot && floater_snapshot->isWaitingState()) - { - if (uploadInfo->getAssetType() == LLAssetType::AT_IMAGE_JPEG) - { - floater_snapshot->notify(LLSD().with("set-finished", LLSD().with("ok", false).with("msg", "postcard"))); - } - if (uploadInfo->getAssetType() == LLAssetType::AT_TEXTURE) - { - floater_snapshot->notify(LLSD().with("set-finished", LLSD().with("ok", false).with("msg", "inventory"))); - } - } -} - +/** +* @file llviewerassetupload.cpp +* @author optional +* @brief brief description of the file +* +* $LicenseInfo:firstyear=2011&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2011, Linden Research, Inc. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" + +#include "linden_common.h" +#include "llviewertexturelist.h" +#include "llimage.h" +#include "lltrans.h" +#include "lluuid.h" +#include "llvorbisencode.h" +#include "lluploaddialog.h" +#include "llnotificationsutil.h" +#include "llagent.h" +#include "llfloaterreg.h" +#include "llfloatersnapshot.h" +#include "llstatusbar.h" +#include "llinventorypanel.h" +#include "llsdutil.h" +#include "llviewerassetupload.h" +#include "llappviewer.h" +#include "llviewerstats.h" +#include "llfilesystem.h" +#include "llgesturemgr.h" +#include "llpreviewnotecard.h" +#include "llpreviewgesture.h" +#include "llcoproceduremanager.h" +#include "llthread.h" +#include "llkeyframemotion.h" +#include "lldatapacker.h" +#include "llvoavatarself.h" + +void dialog_refresh_all(); + +static const U32 LL_ASSET_UPLOAD_TIMEOUT_SEC = 60; + +LLResourceUploadInfo::LLResourceUploadInfo(LLTransactionID transactId, + LLAssetType::EType assetType, std::string name, std::string description, + S32 compressionInfo, LLFolderType::EType destinationType, + LLInventoryType::EType inventoryType, U32 nextOWnerPerms, + U32 groupPerms, U32 everyonePerms, S32 expectedCost, bool showInventory) : + mTransactionId(transactId), + mAssetType(assetType), + mName(name), + mDescription(description), + mCompressionInfo(compressionInfo), + mDestinationFolderType(destinationType), + mInventoryType(inventoryType), + mNextOwnerPerms(nextOWnerPerms), + mGroupPerms(groupPerms), + mEveryonePerms(everyonePerms), + mExpectedUploadCost(expectedCost), + mShowInventory(showInventory), + mFolderId(LLUUID::null), + mItemId(LLUUID::null), + mAssetId(LLAssetID::null) +{ } + + +LLResourceUploadInfo::LLResourceUploadInfo(std::string name, + std::string description, S32 compressionInfo, + LLFolderType::EType destinationType, LLInventoryType::EType inventoryType, + U32 nextOWnerPerms, U32 groupPerms, U32 everyonePerms, S32 expectedCost, bool showInventory) : + mName(name), + mDescription(description), + mCompressionInfo(compressionInfo), + mDestinationFolderType(destinationType), + mInventoryType(inventoryType), + mNextOwnerPerms(nextOWnerPerms), + mGroupPerms(groupPerms), + mEveryonePerms(everyonePerms), + mExpectedUploadCost(expectedCost), + mShowInventory(showInventory), + mTransactionId(), + mAssetType(LLAssetType::AT_NONE), + mFolderId(LLUUID::null), + mItemId(LLUUID::null), + mAssetId(LLAssetID::null) +{ + mTransactionId.generate(); +} + +LLResourceUploadInfo::LLResourceUploadInfo(LLAssetID assetId, LLAssetType::EType assetType, std::string name) : + mAssetId(assetId), + mAssetType(assetType), + mName(name), + mDescription(), + mCompressionInfo(0), + mDestinationFolderType(LLFolderType::FT_NONE), + mInventoryType(LLInventoryType::IT_NONE), + mNextOwnerPerms(0), + mGroupPerms(0), + mEveryonePerms(0), + mExpectedUploadCost(0), + mShowInventory(true), + mTransactionId(), + mFolderId(LLUUID::null), + mItemId(LLUUID::null) +{ +} + +LLSD LLResourceUploadInfo::prepareUpload() +{ + if (mAssetId.isNull()) + generateNewAssetId(); + + incrementUploadStats(); + assignDefaults(); + + return LLSD().with("success", LLSD::Boolean(true)); +} + +std::string LLResourceUploadInfo::getAssetTypeString() const +{ + return LLAssetType::lookup(mAssetType); +} + +std::string LLResourceUploadInfo::getInventoryTypeString() const +{ + return LLInventoryType::lookup(mInventoryType); +} + +LLSD LLResourceUploadInfo::generatePostBody() +{ + LLSD body; + + body["folder_id"] = mFolderId; + body["asset_type"] = getAssetTypeString(); + body["inventory_type"] = getInventoryTypeString(); + body["name"] = mName; + body["description"] = mDescription; + body["next_owner_mask"] = LLSD::Integer(mNextOwnerPerms); + body["group_mask"] = LLSD::Integer(mGroupPerms); + body["everyone_mask"] = LLSD::Integer(mEveryonePerms); + + return body; + +} + +void LLResourceUploadInfo::logPreparedUpload() +{ + LL_INFOS() << "*** Uploading: " << std::endl << + "Type: " << LLAssetType::lookup(mAssetType) << std::endl << + "UUID: " << mAssetId.asString() << std::endl << + "Name: " << mName << std::endl << + "Desc: " << mDescription << std::endl << + "Expected Upload Cost: " << mExpectedUploadCost << std::endl << + "Folder: " << mFolderId << std::endl << + "Asset Type: " << LLAssetType::lookup(mAssetType) << LL_ENDL; +} + +LLUUID LLResourceUploadInfo::finishUpload(LLSD &result) +{ + if (getFolderId().isNull()) + { + return LLUUID::null; + } + + U32 permsEveryone = PERM_NONE; + U32 permsGroup = PERM_NONE; + U32 permsNextOwner = PERM_ALL; + + if (result.has("new_next_owner_mask")) + { + // The server provided creation perms so use them. + // Do not assume we got the perms we asked for in + // since the server may not have granted them all. + permsEveryone = result["new_everyone_mask"].asInteger(); + permsGroup = result["new_group_mask"].asInteger(); + permsNextOwner = result["new_next_owner_mask"].asInteger(); + } + else + { + // The server doesn't provide creation perms + // so use old assumption-based perms. + if (getAssetTypeString() != "snapshot") + { + permsNextOwner = PERM_MOVE | PERM_TRANSFER; + } + } + + LLPermissions new_perms; + new_perms.init( + gAgent.getID(), + gAgent.getID(), + LLUUID::null, + LLUUID::null); + + new_perms.initMasks( + PERM_ALL, + PERM_ALL, + permsEveryone, + permsGroup, + permsNextOwner); + + U32 flagsInventoryItem = 0; + if (result.has("inventory_flags")) + { + flagsInventoryItem = static_cast(result["inventory_flags"].asInteger()); + if (flagsInventoryItem != 0) + { + LL_INFOS() << "inventory_item_flags " << flagsInventoryItem << LL_ENDL; + } + } + S32 creationDate = time_corrected(); + + LLUUID serverInventoryItem = result["new_inventory_item"].asUUID(); + LLUUID serverAssetId = result["new_asset"].asUUID(); + + LLPointer item = new LLViewerInventoryItem( + serverInventoryItem, + getFolderId(), + new_perms, + serverAssetId, + getAssetType(), + getInventoryType(), + getName(), + getDescription(), + LLSaleInfo::DEFAULT, + flagsInventoryItem, + creationDate); + + gInventory.updateItem(item); + gInventory.notifyObservers(); + + return serverInventoryItem; +} + + +LLAssetID LLResourceUploadInfo::generateNewAssetId() +{ + if (gDisconnected) + { + LLAssetID rv; + + rv.setNull(); + return rv; + } + mAssetId = mTransactionId.makeAssetID(gAgent.getSecureSessionID()); + + return mAssetId; +} + +void LLResourceUploadInfo::incrementUploadStats() const +{ + if (LLAssetType::AT_SOUND == mAssetType) + { + add(LLStatViewer::UPLOAD_SOUND, 1); + } + else if (LLAssetType::AT_TEXTURE == mAssetType) + { + add(LLStatViewer::UPLOAD_TEXTURE, 1); + } + else if (LLAssetType::AT_ANIMATION == mAssetType) + { + add(LLStatViewer::ANIMATION_UPLOADS, 1); + } +} + +void LLResourceUploadInfo::assignDefaults() +{ + if (LLInventoryType::IT_NONE == mInventoryType) + { + mInventoryType = LLInventoryType::defaultForAssetType(mAssetType); + } + LLStringUtil::stripNonprintable(mName); + LLStringUtil::stripNonprintable(mDescription); + + if (mName.empty()) + { + mName = "(No Name)"; + } + if (mDescription.empty()) + { + mDescription = "(No Description)"; + } + + mFolderId = gInventory.findUserDefinedCategoryUUIDForType( + (mDestinationFolderType == LLFolderType::FT_NONE) ? + (LLFolderType::EType)mAssetType : mDestinationFolderType); +} + +std::string LLResourceUploadInfo::getDisplayName() const +{ + return (mName.empty()) ? mAssetId.asString() : mName; +}; + +bool LLResourceUploadInfo::findAssetTypeOfExtension(const std::string& exten, LLAssetType::EType& asset_type) +{ + U32 codec; + return findAssetTypeAndCodecOfExtension(exten, asset_type, codec, false); +} + +// static +bool LLResourceUploadInfo::findAssetTypeAndCodecOfExtension(const std::string& exten, LLAssetType::EType& asset_type, U32& codec, bool bulk_upload) +{ + bool succ = false; + std::string exten_lc(exten); + LLStringUtil::toLower(exten_lc); + codec = LLImageBase::getCodecFromExtension(exten_lc); + if (codec != IMG_CODEC_INVALID) + { + asset_type = LLAssetType::AT_TEXTURE; + succ = true; + } + else if (exten_lc == "wav") + { + asset_type = LLAssetType::AT_SOUND; + succ = true; + } + else if (exten_lc == "anim") + { + asset_type = LLAssetType::AT_ANIMATION; + succ = true; + } + else if (!bulk_upload && (exten_lc == "bvh")) + { + asset_type = LLAssetType::AT_ANIMATION; + succ = true; + } + + return succ; +} + +//========================================================================= +LLNewFileResourceUploadInfo::LLNewFileResourceUploadInfo( + std::string fileName, + std::string name, + std::string description, + S32 compressionInfo, + LLFolderType::EType destinationType, + LLInventoryType::EType inventoryType, + U32 nextOWnerPerms, + U32 groupPerms, + U32 everyonePerms, + S32 expectedCost, + bool show_inventory) : + LLResourceUploadInfo(name, description, compressionInfo, + destinationType, inventoryType, + nextOWnerPerms, groupPerms, everyonePerms, expectedCost, show_inventory), + mFileName(fileName) +{ +} + +LLSD LLNewFileResourceUploadInfo::prepareUpload() +{ + if (getAssetId().isNull()) + generateNewAssetId(); + + LLSD result = exportTempFile(); + if (result.has("error")) + return result; + + return LLResourceUploadInfo::prepareUpload(); +} + +LLSD LLNewFileResourceUploadInfo::exportTempFile() +{ + std::string filename = gDirUtilp->getTempFilename(); + + std::string exten = gDirUtilp->getExtension(getFileName()); + + LLAssetType::EType assetType = LLAssetType::AT_NONE; + U32 codec = IMG_CODEC_INVALID; + bool found_type = findAssetTypeAndCodecOfExtension(exten, assetType, codec); + + std::string errorMessage; + std::string errorLabel; + + bool error = false; + + if (exten.empty()) + { + std::string shortName = gDirUtilp->getBaseFileName(filename); + + // No extension + errorMessage = llformat( + "No file extension for the file: '%s'\nPlease make sure the file has a correct file extension", + shortName.c_str()); + errorLabel = "NoFileExtension"; + error = true; + } + else if (!found_type) + { + // Unknown extension + errorMessage = llformat(LLTrans::getString("UnknownFileExtension").c_str(), exten.c_str()); + errorLabel = "ErrorMessage"; + error = true;; + } + else if (assetType == LLAssetType::AT_TEXTURE) + { + // It's an image file, the upload procedure is the same for all + if (!LLViewerTextureList::createUploadFile(getFileName(), filename, codec)) + { + errorMessage = llformat("Problem with file %s:\n\n%s\n", + getFileName().c_str(), LLImage::getLastThreadError().c_str()); + errorLabel = "ProblemWithFile"; + error = true; + } + } + else if (assetType == LLAssetType::AT_SOUND) + { + S32 encodeResult = 0; + + LL_INFOS() << "Attempting to encode wav as an ogg file" << LL_ENDL; + + encodeResult = encode_vorbis_file(getFileName(), filename); + + if (LLVORBISENC_NOERR != encodeResult) + { + switch (encodeResult) + { + case LLVORBISENC_DEST_OPEN_ERR: + errorMessage = llformat("Couldn't open temporary compressed sound file for writing: %s\n", filename.c_str()); + errorLabel = "CannotOpenTemporarySoundFile"; + break; + + default: + errorMessage = llformat("Unknown vorbis encode failure on: %s\n", getFileName().c_str()); + errorLabel = "UnknownVorbisEncodeFailure"; + break; + } + error = true; + } + } + else if (exten == "bvh") + { + errorMessage = llformat("We do not currently support bulk upload of animation files\n"); + errorLabel = "DoNotSupportBulkAnimationUpload"; + error = true; + } + else if (exten == "anim") + { + // Default unless everything succeeds + errorLabel = "ProblemWithFile"; + error = true; + + // read from getFileName() + LLAPRFile infile; + infile.open(getFileName(),LL_APR_RB); + if (!infile.getFileHandle()) + { + LL_WARNS() << "Couldn't open file for reading: " << getFileName() << LL_ENDL; + errorMessage = llformat("Failed to open animation file %s\n", getFileName().c_str()); + } + else + { + S32 size = LLAPRFile::size(getFileName()); + U8* buffer = new U8[size]; + S32 size_read = infile.read(buffer,size); + if (size_read != size) + { + errorMessage = llformat("Failed to read animation file %s: wanted %d bytes, got %d\n", getFileName().c_str(), size, size_read); + } + else + { + LLDataPackerBinaryBuffer dp(buffer, size); + LLKeyframeMotion *motionp = new LLKeyframeMotion(getAssetId()); + motionp->setCharacter(gAgentAvatarp); + if (motionp->deserialize(dp, getAssetId(), false)) + { + // write to temp file + bool succ = motionp->dumpToFile(filename); + if (succ) + { + assetType = LLAssetType::AT_ANIMATION; + errorLabel = ""; + error = false; + } + else + { + errorMessage = "Failed saving temporary animation file"; + } + } + else + { + errorMessage = "Failed reading animation file"; + } + } + } + } + else + { + // Unknown extension + errorMessage = llformat(LLTrans::getString("UnknownFileExtension").c_str(), exten.c_str()); + errorLabel = "ErrorMessage"; + error = true;; + } + + if (error) + { + LLSD errorResult(LLSD::emptyMap()); + + errorResult["error"] = LLSD::Binary(true); + errorResult["message"] = errorMessage; + errorResult["label"] = errorLabel; + return errorResult; + } + + setAssetType(assetType); + + // copy this file into the cache for upload + S32 file_size; + LLAPRFile infile; + infile.open(filename, LL_APR_RB, NULL, &file_size); + if (infile.getFileHandle()) + { + LLFileSystem file(getAssetId(), assetType, LLFileSystem::APPEND); + + const S32 buf_size = 65536; + U8 copy_buf[buf_size]; + while ((file_size = infile.read(copy_buf, buf_size))) + { + file.write(copy_buf, file_size); + } + } + else + { + errorMessage = llformat("Unable to access output file: %s", filename.c_str()); + LLSD errorResult(LLSD::emptyMap()); + + errorResult["error"] = LLSD::Binary(true); + errorResult["message"] = errorMessage; + return errorResult; + } + + return LLSD(); + +} + +//========================================================================= +LLNewBufferedResourceUploadInfo::LLNewBufferedResourceUploadInfo( + const std::string& buffer, + const LLAssetID& asset_id, + std::string name, + std::string description, + S32 compressionInfo, + LLFolderType::EType destinationType, + LLInventoryType::EType inventoryType, + LLAssetType::EType assetType, + U32 nextOWnerPerms, + U32 groupPerms, + U32 everyonePerms, + S32 expectedCost, + bool show_inventory, + uploadFinish_f finish, + uploadFailure_f failure) + : LLResourceUploadInfo(name, description, compressionInfo, + destinationType, inventoryType, + nextOWnerPerms, groupPerms, everyonePerms, expectedCost, show_inventory) + , mBuffer(buffer) + , mFinishFn(finish) + , mFailureFn(failure) +{ + setAssetType(assetType); + setAssetId(asset_id); +} + +LLSD LLNewBufferedResourceUploadInfo::prepareUpload() +{ + if (getAssetId().isNull()) + generateNewAssetId(); + + LLSD result = exportTempFile(); + if (result.has("error")) + return result; + + return LLResourceUploadInfo::prepareUpload(); +} + +LLSD LLNewBufferedResourceUploadInfo::exportTempFile() +{ + std::string filename = gDirUtilp->getTempFilename(); + + // copy buffer to the cache for upload + LLFileSystem file(getAssetId(), getAssetType(), LLFileSystem::APPEND); + file.write((U8*) mBuffer.c_str(), mBuffer.size()); + + return LLSD(); +} + +LLUUID LLNewBufferedResourceUploadInfo::finishUpload(LLSD &result) +{ + LLUUID newItemId = LLResourceUploadInfo::finishUpload(result); + + if (mFinishFn) + { + mFinishFn(result["new_asset"].asUUID(), result); + } + + return newItemId; +} + +bool LLNewBufferedResourceUploadInfo::failedUpload(LLSD &result, std::string &reason) +{ + if (mFailureFn) + { + return mFailureFn(getAssetId(), result, reason); + } + + return false; // Not handled +} + +//========================================================================= +LLBufferedAssetUploadInfo::LLBufferedAssetUploadInfo(LLUUID itemId, LLAssetType::EType assetType, std::string buffer, invnUploadFinish_f finish, uploadFailed_f failed) : + LLResourceUploadInfo(std::string(), std::string(), 0, LLFolderType::FT_NONE, LLInventoryType::IT_NONE, + 0, 0, 0, 0), + mTaskUpload(false), + mTaskId(LLUUID::null), + mContents(buffer), + mInvnFinishFn(finish), + mTaskFinishFn(nullptr), + mFailureFn(failed), + mStoredToCache(false) +{ + setItemId(itemId); + setAssetType(assetType); +} + +LLBufferedAssetUploadInfo::LLBufferedAssetUploadInfo(LLUUID itemId, LLPointer image, invnUploadFinish_f finish) : + LLResourceUploadInfo(std::string(), std::string(), 0, LLFolderType::FT_NONE, LLInventoryType::IT_NONE, + 0, 0, 0, 0), + mTaskUpload(false), + mTaskId(LLUUID::null), + mContents(), + mInvnFinishFn(finish), + mTaskFinishFn(nullptr), + mFailureFn(nullptr), + mStoredToCache(false) +{ + setItemId(itemId); + + LLImageDataSharedLock lock(image); + + EImageCodec codec = static_cast(image->getCodec()); + + switch (codec) + { + case IMG_CODEC_JPEG: + setAssetType(LLAssetType::AT_IMAGE_JPEG); + LL_INFOS() << "Upload Asset type set to JPEG." << LL_ENDL; + break; + case IMG_CODEC_TGA: + setAssetType(LLAssetType::AT_IMAGE_TGA); + LL_INFOS() << "Upload Asset type set to TGA." << LL_ENDL; + break; + default: + LL_WARNS() << "Unknown codec to asset type transition. Codec=" << (int)codec << "." << LL_ENDL; + break; + } + + size_t imageSize = image->getDataSize(); + mContents.reserve(imageSize); + mContents.assign((char *)image->getData(), imageSize); +} + +LLBufferedAssetUploadInfo::LLBufferedAssetUploadInfo(LLUUID taskId, LLUUID itemId, LLAssetType::EType assetType, std::string buffer, taskUploadFinish_f finish, uploadFailed_f failed) : + LLResourceUploadInfo(std::string(), std::string(), 0, LLFolderType::FT_NONE, LLInventoryType::IT_NONE, + 0, 0, 0, 0), + mTaskUpload(true), + mTaskId(taskId), + mContents(buffer), + mInvnFinishFn(nullptr), + mTaskFinishFn(finish), + mFailureFn(failed), + mStoredToCache(false) +{ + setItemId(itemId); + setAssetType(assetType); +} + +LLSD LLBufferedAssetUploadInfo::prepareUpload() +{ + if (getAssetId().isNull()) + generateNewAssetId(); + + LLFileSystem file(getAssetId(), getAssetType(), LLFileSystem::APPEND); + + S32 size = mContents.length() + 1; + file.write((U8*)mContents.c_str(), size); + + mStoredToCache = true; + + return LLSD().with("success", LLSD::Boolean(true)); +} + +LLSD LLBufferedAssetUploadInfo::generatePostBody() +{ + LLSD body; + + if (!getTaskId().isNull()) + { + body["task_id"] = getTaskId(); + } + body["item_id"] = getItemId(); + + return body; +} + +LLUUID LLBufferedAssetUploadInfo::finishUpload(LLSD &result) +{ + LLUUID newAssetId = result["new_asset"].asUUID(); + LLUUID itemId = getItemId(); + + if (mStoredToCache) + { + LLAssetType::EType assetType(getAssetType()); + LLFileSystem::renameFile(getAssetId(), assetType, newAssetId, assetType); + } + + if (mTaskUpload) + { + LLUUID taskId = getTaskId(); + + dialog_refresh_all(); + + if (mTaskFinishFn) + { + mTaskFinishFn(itemId, taskId, newAssetId, result); + } + } + else + { + LLUUID newItemId(LLUUID::null); + + if (itemId.notNull()) + { + LLViewerInventoryItem* item = (LLViewerInventoryItem*)gInventory.getItem(itemId); + if (!item) + { + LL_WARNS() << "Inventory item for " << getDisplayName() << " is no longer in agent inventory." << LL_ENDL; + return newAssetId; + } + + // Update viewer inventory item + LLPointer newItem = new LLViewerInventoryItem(item); + newItem->setAssetUUID(newAssetId); + + gInventory.updateItem(newItem); + + newItemId = newItem->getUUID(); + LL_INFOS() << "Inventory item " << item->getName() << " saved into " << newAssetId.asString() << LL_ENDL; + } + + if (mInvnFinishFn) + { + mInvnFinishFn(itemId, newAssetId, newItemId, result); + } + gInventory.notifyObservers(); + } + + return newAssetId; +} + +bool LLBufferedAssetUploadInfo::failedUpload(LLSD &result, std::string &reason) +{ + if (mFailureFn) + { + return mFailureFn(getItemId(), getTaskId(), result, reason); + } + return false; +} + +//========================================================================= + +LLScriptAssetUpload::LLScriptAssetUpload(LLUUID itemId, std::string buffer, invnUploadFinish_f finish, uploadFailed_f failed): + LLBufferedAssetUploadInfo(itemId, LLAssetType::AT_LSL_TEXT, buffer, finish, failed), + mExerienceId(), + mTargetType(MONO), + mIsRunning(false) +{ +} + +LLScriptAssetUpload::LLScriptAssetUpload(LLUUID taskId, LLUUID itemId, TargetType_t targetType, + bool isRunning, LLUUID exerienceId, std::string buffer, taskUploadFinish_f finish, uploadFailed_f failed): + LLBufferedAssetUploadInfo(taskId, itemId, LLAssetType::AT_LSL_TEXT, buffer, finish, failed), + mExerienceId(exerienceId), + mTargetType(targetType), + mIsRunning(isRunning) +{ +} + +LLSD LLScriptAssetUpload::generatePostBody() +{ + LLSD body; + + if (getTaskId().isNull()) + { + body["item_id"] = getItemId(); + body["target"] = "mono"; + } + else + { + body["task_id"] = getTaskId(); + body["item_id"] = getItemId(); + body["is_script_running"] = getIsRunning(); + body["target"] = (getTargetType() == MONO) ? "mono" : "lsl2"; + body["experience"] = getExerienceId(); + } + + return body; +} + +//========================================================================= +/*static*/ +LLUUID LLViewerAssetUpload::EnqueueInventoryUpload(const std::string &url, const LLResourceUploadInfo::ptr_t &uploadInfo) +{ + std::string procName("LLViewerAssetUpload::AssetInventoryUploadCoproc("); + + LLUUID queueId = LLCoprocedureManager::instance().enqueueCoprocedure("Upload", + procName + LLAssetType::lookup(uploadInfo->getAssetType()) + ")", + boost::bind(&LLViewerAssetUpload::AssetInventoryUploadCoproc, _1, _2, url, uploadInfo)); + + return queueId; +} + +//========================================================================= +/*static*/ +void LLViewerAssetUpload::AssetInventoryUploadCoproc(LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t &httpAdapter, + const LLUUID &id, std::string url, LLResourceUploadInfo::ptr_t uploadInfo) +{ + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOptions(new LLCore::HttpOptions); + httpOptions->setTimeout(LL_ASSET_UPLOAD_TIMEOUT_SEC); + + LLSD result = uploadInfo->prepareUpload(); + uploadInfo->logPreparedUpload(); + + if (result.has("error")) + { + HandleUploadError(LLCore::HttpStatus(499), result, uploadInfo); + return; + } + + llcoro::suspend(); + + if (uploadInfo->showUploadDialog()) + { + std::string uploadMessage = "Uploading...\n\n"; + uploadMessage.append(uploadInfo->getDisplayName()); + LLUploadDialog::modalUploadDialog(uploadMessage); + } + + LLSD body = uploadInfo->generatePostBody(); + + result = httpAdapter->postAndSuspend(httpRequest, url, body, httpOptions); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if ((!status) || (result.has("error"))) + { + HandleUploadError(status, result, uploadInfo); + if (uploadInfo->showUploadDialog()) + LLUploadDialog::modalUploadFinished(); + return; + } + + std::string uploader = result["uploader"].asString(); + + bool success = false; + if (!uploader.empty() && uploadInfo->getAssetId().notNull()) + { + result = httpAdapter->postFileAndSuspend(httpRequest, uploader, uploadInfo->getAssetId(), uploadInfo->getAssetType(), httpOptions); + httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + std::string ulstate = result["state"].asString(); + + if ((!status) || (ulstate != "complete")) + { + HandleUploadError(status, result, uploadInfo); + if (uploadInfo->showUploadDialog()) + LLUploadDialog::modalUploadFinished(); + return; + } + if (!result.has("success")) + { + result["success"] = LLSD::Boolean((ulstate == "complete") && status); + } + + S32 uploadPrice = result["upload_price"].asInteger(); + + if (uploadPrice > 0) + { + // this upload costed us L$, update our balance + // and display something saying that it cost L$ + LLStatusBar::sendMoneyBalanceRequest(); + + LLSD args; + args["AMOUNT"] = llformat("%d", uploadPrice); + LLNotificationsUtil::add("UploadPayment", args); + } + } + else + { + LL_WARNS() << "No upload url provided. Nothing uploaded, responding with previous result." << LL_ENDL; + } + LLUUID serverInventoryItem = uploadInfo->finishUpload(result); + + if (uploadInfo->showInventoryPanel()) + { + if (serverInventoryItem.notNull()) + { + success = true; + + LLFocusableElement* focus = gFocusMgr.getKeyboardFocus(); + + // Show the preview panel for textures and sounds to let + // user know that the image (or snapshot) arrived intact. + LLInventoryPanel* panel = LLInventoryPanel::getActiveInventoryPanel(false); + LLInventoryPanel::openInventoryPanelAndSetSelection(true, serverInventoryItem, false, false, !panel); + + // restore keyboard focus + gFocusMgr.setKeyboardFocus(focus); + } + else + { + LL_WARNS() << "Can't find a folder to put it in" << LL_ENDL; + } + } + + // remove the "Uploading..." message + if (uploadInfo->showUploadDialog()) + LLUploadDialog::modalUploadFinished(); + + // Let the Snapshot floater know we have finished uploading a snapshot to inventory + LLFloater* floater_snapshot = LLFloaterReg::findInstance("snapshot"); + if (uploadInfo->getAssetType() == LLAssetType::AT_TEXTURE && floater_snapshot && floater_snapshot->isShown()) + { + floater_snapshot->notify(LLSD().with("set-finished", LLSD().with("ok", success).with("msg", "inventory"))); + } +} + +//========================================================================= +/*static*/ +void LLViewerAssetUpload::HandleUploadError(LLCore::HttpStatus status, LLSD &result, LLResourceUploadInfo::ptr_t &uploadInfo) +{ + std::string reason; + std::string label("CannotUploadReason"); + + LL_WARNS() << ll_pretty_print_sd(result) << LL_ENDL; + + if (result.has("label")) + { + label = result["label"].asString(); + } + + if (result.has("message")) + { + reason = result["message"].asString(); + } + else + { + switch (status.getType()) + { + case 404: + reason = LLTrans::getString("AssetUploadServerUnreacheble"); + break; + case 499: + reason = LLTrans::getString("AssetUploadServerDifficulties"); + break; + case 503: + reason = LLTrans::getString("AssetUploadServerUnavaliable"); + break; + default: + reason = LLTrans::getString("AssetUploadRequestInvalid"); + } + } + + LLSD args; + if(label == "ErrorMessage") + { + args["ERROR_MESSAGE"] = reason; + } + else + { + args["FILE"] = uploadInfo->getDisplayName(); + args["REASON"] = reason; + args["ERROR"] = reason; + } + + LLNotificationsUtil::add(label, args); + + if (uploadInfo->failedUpload(result, reason)) + { + // no further action required, already handled by a callback + // ex: do not trigger snapshot floater when failing material texture + return; + } + + // Todo: move these floater specific actions into proper callbacks + + // Let the Snapshot floater know we have failed uploading. + LLFloaterSnapshot* floater_snapshot = LLFloaterSnapshot::findInstance(); + if (floater_snapshot && floater_snapshot->isWaitingState()) + { + if (uploadInfo->getAssetType() == LLAssetType::AT_IMAGE_JPEG) + { + floater_snapshot->notify(LLSD().with("set-finished", LLSD().with("ok", false).with("msg", "postcard"))); + } + if (uploadInfo->getAssetType() == LLAssetType::AT_TEXTURE) + { + floater_snapshot->notify(LLSD().with("set-finished", LLSD().with("ok", false).with("msg", "inventory"))); + } + } +} + diff --git a/indra/newview/llvieweraudio.cpp b/indra/newview/llvieweraudio.cpp index 9d17898b5e..b3b4f43e57 100644 --- a/indra/newview/llvieweraudio.cpp +++ b/indra/newview/llvieweraudio.cpp @@ -1,597 +1,597 @@ -/** - * @file llvieweraudio.cpp - * @brief Audio functions that used to be in viewer.cpp - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llaudioengine.h" -#include "llagent.h" -#include "llagentcamera.h" -#include "llappviewer.h" -#include "lldeferredsounds.h" -#include "llvieweraudio.h" -#include "llviewercamera.h" -#include "llviewercontrol.h" -#include "llviewerwindow.h" -#include "llvoiceclient.h" -#include "llviewermedia.h" -#include "llviewerregion.h" -#include "llprogressview.h" -#include "llcallbacklist.h" -#include "llstartup.h" -#include "llviewerparcelmgr.h" -#include "llparcel.h" -#include "llviewermessage.h" - -#include "llstreamingaudio.h" - -///////////////////////////////////////////////////////// -const U32 FMODEX_DECODE_BUFFER_SIZE = 1000; // in milliseconds -const U32 FMODEX_STREAM_BUFFER_SIZE = 7000; // in milliseconds - -LLViewerAudio::LLViewerAudio() : - mDone(true), - mFadeState(FADE_IDLE), - mFadeTime(), - mIdleListnerActive(false), - mForcedTeleportFade(false), - mWasPlaying(false) -{ - mTeleportFailedConnection = LLViewerParcelMgr::getInstance()-> - setTeleportFailedCallback(boost::bind(&LLViewerAudio::onTeleportFailed, this)); - mTeleportFinishedConnection = LLViewerParcelMgr::getInstance()-> - setTeleportFinishedCallback(boost::bind(&LLViewerAudio::onTeleportFinished, this, _1, _2)); - mTeleportStartedConnection = LLViewerMessage::getInstance()-> - setTeleportStartedCallback(boost::bind(&LLViewerAudio::onTeleportStarted, this)); -} - -LLViewerAudio::~LLViewerAudio() -{ - mTeleportFailedConnection.disconnect(); - mTeleportFinishedConnection.disconnect(); - mTeleportStartedConnection.disconnect(); -} - -void LLViewerAudio::registerIdleListener() -{ - if (!mIdleListnerActive) - { - mIdleListnerActive = true; - doOnIdleRepeating(boost::bind(boost::bind(&LLViewerAudio::onIdleUpdate, this))); - } -} - -void LLViewerAudio::startInternetStreamWithAutoFade(const std::string &streamURI) -{ - LL_DEBUGS("AudioEngine") << "Start with outo fade: " << streamURI << LL_ENDL; - - // Old and new stream are identical - if (mNextStreamURI == streamURI) - { - return; - } - - if (!gAudiop) - { - LL_WARNS("AudioEngine") << "LLAudioEngine instance doesn't exist!" << LL_ENDL; - return; - } - - // Record the URI we are going to be switching to - mNextStreamURI = streamURI; - - switch (mFadeState) - { - case FADE_IDLE: - // If a stream is playing fade it out first - if (!gAudiop->getInternetStreamURL().empty()) - { - // The order of these tests is important, state FADE_OUT will be processed below - mFadeState = FADE_OUT; - } - // Otherwise the new stream can be faded in - else - { - mFadeState = FADE_IN; - - LLStreamingAudioInterface *stream = gAudiop->getStreamingAudioImpl(); - if (stream && stream->supportsAdjustableBufferSizes()) - stream->setBufferSizes(FMODEX_STREAM_BUFFER_SIZE, FMODEX_DECODE_BUFFER_SIZE); - - gAudiop->startInternetStream(mNextStreamURI); - } - - startFading(); - break; - - case FADE_OUT: - startFading(); - break; - - case FADE_IN: - break; - - default: - LL_WARNS() << "Unknown fading state: " << mFadeState << LL_ENDL; - return; - } - - registerIdleListener(); -} - -// A return of false from onIdleUpdate means it will be called again next idle update. -// A return of true means we have finished with it and the callback will be deleted. -bool LLViewerAudio::onIdleUpdate() -{ - bool fadeIsFinished = false; - - // There is a delay in the login sequence between when the parcel information has - // arrived and the music stream is started and when the audio system is called to set - // initial volume levels. This code extends the fade time so you hear a full fade in. - if ((LLStartUp::getStartupState() < STATE_STARTED)) - { - stream_fade_timer.reset(); - stream_fade_timer.setTimerExpirySec(mFadeTime); - } - - if (mDone) - { - // This should be a rare or never occurring state. - if (mFadeState == FADE_IDLE) - { - deregisterIdleListener(); - fadeIsFinished = true; // Stop calling onIdleUpdate - } - - // we have finished the current fade operation - if (mFadeState == FADE_OUT) - { - if (gAudiop) - { - // Clear URI - LL_DEBUGS("AudioEngine") << "Done with audio fade" << LL_ENDL; - gAudiop->startInternetStream(LLStringUtil::null); - gAudiop->stopInternetStream(); - } - - if (!mNextStreamURI.empty()) - { - mFadeState = FADE_IN; - - if (gAudiop) - { - LL_DEBUGS("AudioEngine") << "Audio fade in: " << mNextStreamURI << LL_ENDL; - LLStreamingAudioInterface *stream = gAudiop->getStreamingAudioImpl(); - if(stream && stream->supportsAdjustableBufferSizes()) - stream->setBufferSizes(FMODEX_STREAM_BUFFER_SIZE, FMODEX_DECODE_BUFFER_SIZE); - - gAudiop->startInternetStream(mNextStreamURI); - } - - startFading(); - } - else - { - mFadeState = FADE_IDLE; - deregisterIdleListener(); - fadeIsFinished = true; // Stop calling onIdleUpdate - } - } - else if (mFadeState == FADE_IN) - { - if (gAudiop && mNextStreamURI != gAudiop->getInternetStreamURL()) - { - mFadeState = FADE_OUT; - startFading(); - } - else - { - mFadeState = FADE_IDLE; - deregisterIdleListener(); - fadeIsFinished = true; // Stop calling onIdleUpdate - } - } - } - - return fadeIsFinished; -} - -void LLViewerAudio::stopInternetStreamWithAutoFade() -{ - mFadeState = FADE_IDLE; - mNextStreamURI = LLStringUtil::null; - mDone = true; - - if (gAudiop) - { - LL_DEBUGS("AudioEngine") << "Stop audio fade" << LL_ENDL; - gAudiop->startInternetStream(LLStringUtil::null); - gAudiop->stopInternetStream(); - } -} - -void LLViewerAudio::startFading() -{ - const F32 AUDIO_MUSIC_FADE_IN_TIME = 3.0f; - const F32 AUDIO_MUSIC_FADE_OUT_TIME = 2.0f; - // This minimum fade time prevents divide by zero and negative times - const F32 AUDIO_MUSIC_MINIMUM_FADE_TIME = 0.01f; - - if (mDone) - { - // The fade state here should only be one of FADE_IN or FADE_OUT, but, in case it is not, - // rather than check for both states assume a fade in and check for the fade out case. - mFadeTime = LLViewerAudio::getInstance()->getFadeState() == LLViewerAudio::FADE_OUT ? - AUDIO_MUSIC_FADE_OUT_TIME : AUDIO_MUSIC_FADE_IN_TIME; - - // Prevent invalid fade time - mFadeTime = llmax(mFadeTime, AUDIO_MUSIC_MINIMUM_FADE_TIME); - - stream_fade_timer.reset(); - stream_fade_timer.setTimerExpirySec(mFadeTime); - mDone = false; - } -} - -F32 LLViewerAudio::getFadeVolume() -{ - F32 fade_volume = 1.0f; - - if (stream_fade_timer.hasExpired()) - { - mDone = true; - // If we have been fading out set volume to 0 until the next fade state occurs to prevent - // an audio transient. - if (LLViewerAudio::getInstance()->getFadeState() == LLViewerAudio::FADE_OUT) - { - fade_volume = 0.0f; - } - } - - if (!mDone) - { - // Calculate how far we are into the fade time - fade_volume = stream_fade_timer.getElapsedTimeF32() / mFadeTime; - - if (LLViewerAudio::getInstance()->getFadeState() == LLViewerAudio::FADE_OUT) - { - // If we are not fading in then we are fading out, so invert the fade - // direction; start loud and move towards zero volume. - fade_volume = 1.0f - fade_volume; - } - } - - return fade_volume; -} - -void LLViewerAudio::onTeleportStarted() -{ - if (gAudiop && !LLViewerAudio::getInstance()->getForcedTeleportFade()) - { - // Even though the music was turned off it was starting up (with autoplay disabled) occasionally - // after a failed teleport or after an intra-parcel teleport. Also, the music sometimes was not - // restarting after a successful intra-parcel teleport. Setting mWasPlaying fixes these issues. - LLViewerAudio::getInstance()->setWasPlaying(!gAudiop->getInternetStreamURL().empty()); - LLViewerAudio::getInstance()->setForcedTeleportFade(true); - LLViewerAudio::getInstance()->startInternetStreamWithAutoFade(LLStringUtil::null); - LLViewerAudio::getInstance()->setNextStreamURI(LLStringUtil::null); - } -} - -void LLViewerAudio::onTeleportFailed() -{ - // Calling audio_update_volume makes sure that the music stream is properly set to be restored to - // its previous value - audio_update_volume(false); - - if (gAudiop && mWasPlaying) - { - LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - if (parcel) - { - mNextStreamURI = parcel->getMusicURL(); - LL_INFOS() << "Teleport failed -- setting music stream to " << mNextStreamURI << LL_ENDL; - } - } - mWasPlaying = false; -} - -void LLViewerAudio::onTeleportFinished(const LLVector3d& pos, const bool& local) -{ - // Calling audio_update_volume makes sure that the music stream is properly set to be restored to - // its previous value - audio_update_volume(false); - - if (gAudiop && local && mWasPlaying) - { - LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - if (parcel) - { - mNextStreamURI = parcel->getMusicURL(); - LL_INFOS() << "Intraparcel teleport -- setting music stream to " << mNextStreamURI << LL_ENDL; - } - } - mWasPlaying = false; -} - -void init_audio() -{ - if (!gAudiop) - { - LL_WARNS() << "Failed to create an appropriate Audio Engine" << LL_ENDL; - return; - } - LLVector3d lpos_global = gAgentCamera.getCameraPositionGlobal(); - LLVector3 lpos_global_f; - - lpos_global_f.setVec(lpos_global); - - gAudiop->setListener(lpos_global_f, - LLVector3::zero, // LLViewerCamera::getInstance()->getVelocity(), // !!! BUG need to replace this with smoothed velocity! - LLViewerCamera::getInstance()->getUpAxis(), - LLViewerCamera::getInstance()->getAtAxis()); - -// load up our initial set of sounds we'll want so they're in memory and ready to be played - - bool mute_audio = gSavedSettings.getBOOL("MuteAudio"); - - if (!mute_audio && false == gSavedSettings.getBOOL("NoPreload")) - { - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndAlert"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndBadKeystroke"))); - //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndChatFromObject"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndClick"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndClickRelease"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndHealthReductionF"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndHealthReductionM"))); - //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndIncomingChat"))); - //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndIncomingIM"))); - //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndInvApplyToObject"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndInvalidOp"))); - //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndInventoryCopyToInv"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndMoneyChangeDown"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndMoneyChangeUp"))); - //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectCopyToInv"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectCreate"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectDelete"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectRezIn"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectRezOut"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndSnapshot"))); - //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndStartAutopilot"))); - //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndStartFollowpilot"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndStartIM"))); - //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndStopAutopilot"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndTeleportOut"))); - //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndTextureApplyToObject"))); - //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndTextureCopyToInv"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndTyping"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndWindowClose"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndWindowOpen"))); - gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndRestart"))); - } - - audio_update_volume(true); -} - -void audio_update_volume(bool force_update) -{ - F32 master_volume = gSavedSettings.getF32("AudioLevelMaster"); - bool mute_audio = gSavedSettings.getBOOL("MuteAudio"); - - LLProgressView* progress = gViewerWindow->getProgressView(); - bool progress_view_visible = false; - - if (progress) - { - progress_view_visible = progress->getVisible(); - } - - if (!gViewerWindow->getActive() && gSavedSettings.getBOOL("MuteWhenMinimized")) - { - mute_audio = true; - } - F32 mute_volume = mute_audio ? 0.0f : 1.0f; - - if (gAudiop) - { - // Sound Effects - - gAudiop->setMasterGain ( master_volume ); - - const F32 AUDIO_LEVEL_DOPPLER = 1.f; - gAudiop->setDopplerFactor(AUDIO_LEVEL_DOPPLER); - - if(!LLViewerCamera::getInstance()->cameraUnderWater()) - { - const F32 AUDIO_LEVEL_ROLLOFF = 1.f; - gAudiop->setRolloffFactor(AUDIO_LEVEL_ROLLOFF); - } - else - { - const F32 AUDIO_LEVEL_UNDERWATER_ROLLOFF = 5.f; - gAudiop->setRolloffFactor(AUDIO_LEVEL_UNDERWATER_ROLLOFF); - } - - gAudiop->setMuted(mute_audio || progress_view_visible); - - //Play any deferred sounds when unmuted - if(!gAudiop->getMuted()) - { - LLDeferredSounds::instance().playdeferredSounds(); - } - - if (force_update) - { - audio_update_wind(true); - } - - // handle secondary gains - gAudiop->setSecondaryGain(LLAudioEngine::AUDIO_TYPE_SFX, - gSavedSettings.getBOOL("MuteSounds") ? 0.f : gSavedSettings.getF32("AudioLevelSFX")); - gAudiop->setSecondaryGain(LLAudioEngine::AUDIO_TYPE_UI, - gSavedSettings.getBOOL("MuteUI") ? 0.f : gSavedSettings.getF32("AudioLevelUI")); - gAudiop->setSecondaryGain(LLAudioEngine::AUDIO_TYPE_AMBIENT, - gSavedSettings.getBOOL("MuteAmbient") ? 0.f : gSavedSettings.getF32("AudioLevelAmbient")); - - // Streaming Music - - if (!progress_view_visible && LLViewerAudio::getInstance()->getForcedTeleportFade()) - { - LLViewerAudio::getInstance()->setWasPlaying(!gAudiop->getInternetStreamURL().empty()); - LLViewerAudio::getInstance()->setForcedTeleportFade(false); - } - - F32 music_volume = gSavedSettings.getF32("AudioLevelMusic"); - bool music_muted = gSavedSettings.getBOOL("MuteMusic"); - F32 fade_volume = LLViewerAudio::getInstance()->getFadeVolume(); - - music_volume = mute_volume * master_volume * music_volume * fade_volume; - gAudiop->setInternetStreamGain (music_muted ? 0.f : music_volume); - } - - // Streaming Media - F32 media_volume = gSavedSettings.getF32("AudioLevelMedia"); - bool media_muted = gSavedSettings.getBOOL("MuteMedia"); - media_volume = mute_volume * master_volume * media_volume; - LLViewerMedia::getInstance()->setVolume( media_muted ? 0.0f : media_volume ); - - // Voice, this is parametric singleton, it gets initialized when ready - if (LLVoiceClient::instanceExists()) - { - F32 voice_volume = gSavedSettings.getF32("AudioLevelVoice"); - voice_volume = mute_volume * master_volume * voice_volume; - bool voice_mute = gSavedSettings.getBOOL("MuteVoice"); - LLVoiceClient *voice_inst = LLVoiceClient::getInstance(); - voice_inst->setVoiceVolume(voice_mute ? 0.f : voice_volume); - voice_inst->setMicGain(voice_mute ? 0.f : gSavedSettings.getF32("AudioLevelMic")); - - if (!gViewerWindow->getActive() && (gSavedSettings.getBOOL("MuteWhenMinimized"))) - { - voice_inst->setMuteMic(true); - } - else - { - voice_inst->setMuteMic(false); - } - } -} - -void audio_update_listener() -{ - if (gAudiop) - { - // update listener position because agent has moved - static LLUICachedControl mEarLocation("MediaSoundsEarLocation", 0); - LLVector3d ear_position; - switch(mEarLocation) - { - case 0: - default: - ear_position = gAgentCamera.getCameraPositionGlobal(); - break; - - case 1: - ear_position = gAgent.getPositionGlobal(); - break; - } - LLVector3d lpos_global = ear_position; - LLVector3 lpos_global_f; - lpos_global_f.setVec(lpos_global); - - gAudiop->setListener(lpos_global_f, - // LLViewerCamera::getInstance()VelocitySmoothed, - // LLVector3::zero, - gAgent.getVelocity(), // !!! *TODO: need to replace this with smoothed velocity! - LLViewerCamera::getInstance()->getUpAxis(), - LLViewerCamera::getInstance()->getAtAxis()); - } -} - -void audio_update_wind(bool force_update) -{ -#ifdef kAUDIO_ENABLE_WIND - - LLViewerRegion* region = gAgent.getRegion(); - if (region) - { - // Scale down the contribution of weather-simulation wind to the - // ambient wind noise. Wind velocity averages 3.5 m/s, with gusts to 7 m/s - // whereas steady-state avatar walk velocity is only 3.2 m/s. - // Without this the world feels desolate on first login when you are - // standing still. - const F32 WIND_LEVEL = 0.5f; - LLVector3 scaled_wind_vec = gWindVec * WIND_LEVEL; - - // Mix in the avatar's motion, subtract because when you walk north, - // the apparent wind moves south. - LLVector3 final_wind_vec = scaled_wind_vec - gAgent.getVelocity(); - - // rotate the wind vector to be listener (agent) relative - gRelativeWindVec = gAgent.getFrameAgent().rotateToLocal( final_wind_vec ); - - // don't use the setter setMaxWindGain() because we don't - // want to screw up the fade-in on startup by setting actual source gain - // outside the fade-in. - static LLCachedControl mute_audio(gSavedSettings, "MuteAudio"); - static LLCachedControl mute_ambient(gSavedSettings, "MuteAmbient"); - static LLCachedControl level_master(gSavedSettings, "AudioLevelMaster"); - static LLCachedControl level_ambient(gSavedSettings, "AudioLevelAmbient"); - - F32 master_volume = mute_audio() ? 0.f : level_master(); - F32 ambient_volume = mute_ambient() ? 0.f : level_ambient(); - F32 max_wind_volume = master_volume * ambient_volume; - - const F32 WIND_SOUND_TRANSITION_TIME = 2.f; - // amount to change volume this frame - F32 volume_delta = (LLFrameTimer::getFrameDeltaTimeF32() / WIND_SOUND_TRANSITION_TIME) * max_wind_volume; - if (force_update) - { - // initialize wind volume (force_update) by using large volume_delta - // which is sufficient to completely turn off or turn on wind noise - volume_delta = 1.f; - } - - if (!gAudiop) - { - LL_WARNS("AudioEngine") << "LLAudioEngine instance doesn't exist!" << LL_ENDL; - return; - } - - // mute wind when not flying - if (gAgent.getFlying()) - { - // volume increases by volume_delta, up to no more than max_wind_volume - gAudiop->mMaxWindGain = llmin(gAudiop->mMaxWindGain + volume_delta, max_wind_volume); - } - else - { - // volume decreases by volume_delta, down to no less than 0 - gAudiop->mMaxWindGain = llmax(gAudiop->mMaxWindGain - volume_delta, 0.f); - } - - gAudiop->updateWind(gRelativeWindVec, gAgentCamera.getCameraPositionAgent()[VZ] - gAgent.getRegion()->getWaterHeight()); - } -#endif -} +/** + * @file llvieweraudio.cpp + * @brief Audio functions that used to be in viewer.cpp + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llaudioengine.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llappviewer.h" +#include "lldeferredsounds.h" +#include "llvieweraudio.h" +#include "llviewercamera.h" +#include "llviewercontrol.h" +#include "llviewerwindow.h" +#include "llvoiceclient.h" +#include "llviewermedia.h" +#include "llviewerregion.h" +#include "llprogressview.h" +#include "llcallbacklist.h" +#include "llstartup.h" +#include "llviewerparcelmgr.h" +#include "llparcel.h" +#include "llviewermessage.h" + +#include "llstreamingaudio.h" + +///////////////////////////////////////////////////////// +const U32 FMODEX_DECODE_BUFFER_SIZE = 1000; // in milliseconds +const U32 FMODEX_STREAM_BUFFER_SIZE = 7000; // in milliseconds + +LLViewerAudio::LLViewerAudio() : + mDone(true), + mFadeState(FADE_IDLE), + mFadeTime(), + mIdleListnerActive(false), + mForcedTeleportFade(false), + mWasPlaying(false) +{ + mTeleportFailedConnection = LLViewerParcelMgr::getInstance()-> + setTeleportFailedCallback(boost::bind(&LLViewerAudio::onTeleportFailed, this)); + mTeleportFinishedConnection = LLViewerParcelMgr::getInstance()-> + setTeleportFinishedCallback(boost::bind(&LLViewerAudio::onTeleportFinished, this, _1, _2)); + mTeleportStartedConnection = LLViewerMessage::getInstance()-> + setTeleportStartedCallback(boost::bind(&LLViewerAudio::onTeleportStarted, this)); +} + +LLViewerAudio::~LLViewerAudio() +{ + mTeleportFailedConnection.disconnect(); + mTeleportFinishedConnection.disconnect(); + mTeleportStartedConnection.disconnect(); +} + +void LLViewerAudio::registerIdleListener() +{ + if (!mIdleListnerActive) + { + mIdleListnerActive = true; + doOnIdleRepeating(boost::bind(boost::bind(&LLViewerAudio::onIdleUpdate, this))); + } +} + +void LLViewerAudio::startInternetStreamWithAutoFade(const std::string &streamURI) +{ + LL_DEBUGS("AudioEngine") << "Start with outo fade: " << streamURI << LL_ENDL; + + // Old and new stream are identical + if (mNextStreamURI == streamURI) + { + return; + } + + if (!gAudiop) + { + LL_WARNS("AudioEngine") << "LLAudioEngine instance doesn't exist!" << LL_ENDL; + return; + } + + // Record the URI we are going to be switching to + mNextStreamURI = streamURI; + + switch (mFadeState) + { + case FADE_IDLE: + // If a stream is playing fade it out first + if (!gAudiop->getInternetStreamURL().empty()) + { + // The order of these tests is important, state FADE_OUT will be processed below + mFadeState = FADE_OUT; + } + // Otherwise the new stream can be faded in + else + { + mFadeState = FADE_IN; + + LLStreamingAudioInterface *stream = gAudiop->getStreamingAudioImpl(); + if (stream && stream->supportsAdjustableBufferSizes()) + stream->setBufferSizes(FMODEX_STREAM_BUFFER_SIZE, FMODEX_DECODE_BUFFER_SIZE); + + gAudiop->startInternetStream(mNextStreamURI); + } + + startFading(); + break; + + case FADE_OUT: + startFading(); + break; + + case FADE_IN: + break; + + default: + LL_WARNS() << "Unknown fading state: " << mFadeState << LL_ENDL; + return; + } + + registerIdleListener(); +} + +// A return of false from onIdleUpdate means it will be called again next idle update. +// A return of true means we have finished with it and the callback will be deleted. +bool LLViewerAudio::onIdleUpdate() +{ + bool fadeIsFinished = false; + + // There is a delay in the login sequence between when the parcel information has + // arrived and the music stream is started and when the audio system is called to set + // initial volume levels. This code extends the fade time so you hear a full fade in. + if ((LLStartUp::getStartupState() < STATE_STARTED)) + { + stream_fade_timer.reset(); + stream_fade_timer.setTimerExpirySec(mFadeTime); + } + + if (mDone) + { + // This should be a rare or never occurring state. + if (mFadeState == FADE_IDLE) + { + deregisterIdleListener(); + fadeIsFinished = true; // Stop calling onIdleUpdate + } + + // we have finished the current fade operation + if (mFadeState == FADE_OUT) + { + if (gAudiop) + { + // Clear URI + LL_DEBUGS("AudioEngine") << "Done with audio fade" << LL_ENDL; + gAudiop->startInternetStream(LLStringUtil::null); + gAudiop->stopInternetStream(); + } + + if (!mNextStreamURI.empty()) + { + mFadeState = FADE_IN; + + if (gAudiop) + { + LL_DEBUGS("AudioEngine") << "Audio fade in: " << mNextStreamURI << LL_ENDL; + LLStreamingAudioInterface *stream = gAudiop->getStreamingAudioImpl(); + if(stream && stream->supportsAdjustableBufferSizes()) + stream->setBufferSizes(FMODEX_STREAM_BUFFER_SIZE, FMODEX_DECODE_BUFFER_SIZE); + + gAudiop->startInternetStream(mNextStreamURI); + } + + startFading(); + } + else + { + mFadeState = FADE_IDLE; + deregisterIdleListener(); + fadeIsFinished = true; // Stop calling onIdleUpdate + } + } + else if (mFadeState == FADE_IN) + { + if (gAudiop && mNextStreamURI != gAudiop->getInternetStreamURL()) + { + mFadeState = FADE_OUT; + startFading(); + } + else + { + mFadeState = FADE_IDLE; + deregisterIdleListener(); + fadeIsFinished = true; // Stop calling onIdleUpdate + } + } + } + + return fadeIsFinished; +} + +void LLViewerAudio::stopInternetStreamWithAutoFade() +{ + mFadeState = FADE_IDLE; + mNextStreamURI = LLStringUtil::null; + mDone = true; + + if (gAudiop) + { + LL_DEBUGS("AudioEngine") << "Stop audio fade" << LL_ENDL; + gAudiop->startInternetStream(LLStringUtil::null); + gAudiop->stopInternetStream(); + } +} + +void LLViewerAudio::startFading() +{ + const F32 AUDIO_MUSIC_FADE_IN_TIME = 3.0f; + const F32 AUDIO_MUSIC_FADE_OUT_TIME = 2.0f; + // This minimum fade time prevents divide by zero and negative times + const F32 AUDIO_MUSIC_MINIMUM_FADE_TIME = 0.01f; + + if (mDone) + { + // The fade state here should only be one of FADE_IN or FADE_OUT, but, in case it is not, + // rather than check for both states assume a fade in and check for the fade out case. + mFadeTime = LLViewerAudio::getInstance()->getFadeState() == LLViewerAudio::FADE_OUT ? + AUDIO_MUSIC_FADE_OUT_TIME : AUDIO_MUSIC_FADE_IN_TIME; + + // Prevent invalid fade time + mFadeTime = llmax(mFadeTime, AUDIO_MUSIC_MINIMUM_FADE_TIME); + + stream_fade_timer.reset(); + stream_fade_timer.setTimerExpirySec(mFadeTime); + mDone = false; + } +} + +F32 LLViewerAudio::getFadeVolume() +{ + F32 fade_volume = 1.0f; + + if (stream_fade_timer.hasExpired()) + { + mDone = true; + // If we have been fading out set volume to 0 until the next fade state occurs to prevent + // an audio transient. + if (LLViewerAudio::getInstance()->getFadeState() == LLViewerAudio::FADE_OUT) + { + fade_volume = 0.0f; + } + } + + if (!mDone) + { + // Calculate how far we are into the fade time + fade_volume = stream_fade_timer.getElapsedTimeF32() / mFadeTime; + + if (LLViewerAudio::getInstance()->getFadeState() == LLViewerAudio::FADE_OUT) + { + // If we are not fading in then we are fading out, so invert the fade + // direction; start loud and move towards zero volume. + fade_volume = 1.0f - fade_volume; + } + } + + return fade_volume; +} + +void LLViewerAudio::onTeleportStarted() +{ + if (gAudiop && !LLViewerAudio::getInstance()->getForcedTeleportFade()) + { + // Even though the music was turned off it was starting up (with autoplay disabled) occasionally + // after a failed teleport or after an intra-parcel teleport. Also, the music sometimes was not + // restarting after a successful intra-parcel teleport. Setting mWasPlaying fixes these issues. + LLViewerAudio::getInstance()->setWasPlaying(!gAudiop->getInternetStreamURL().empty()); + LLViewerAudio::getInstance()->setForcedTeleportFade(true); + LLViewerAudio::getInstance()->startInternetStreamWithAutoFade(LLStringUtil::null); + LLViewerAudio::getInstance()->setNextStreamURI(LLStringUtil::null); + } +} + +void LLViewerAudio::onTeleportFailed() +{ + // Calling audio_update_volume makes sure that the music stream is properly set to be restored to + // its previous value + audio_update_volume(false); + + if (gAudiop && mWasPlaying) + { + LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if (parcel) + { + mNextStreamURI = parcel->getMusicURL(); + LL_INFOS() << "Teleport failed -- setting music stream to " << mNextStreamURI << LL_ENDL; + } + } + mWasPlaying = false; +} + +void LLViewerAudio::onTeleportFinished(const LLVector3d& pos, const bool& local) +{ + // Calling audio_update_volume makes sure that the music stream is properly set to be restored to + // its previous value + audio_update_volume(false); + + if (gAudiop && local && mWasPlaying) + { + LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if (parcel) + { + mNextStreamURI = parcel->getMusicURL(); + LL_INFOS() << "Intraparcel teleport -- setting music stream to " << mNextStreamURI << LL_ENDL; + } + } + mWasPlaying = false; +} + +void init_audio() +{ + if (!gAudiop) + { + LL_WARNS() << "Failed to create an appropriate Audio Engine" << LL_ENDL; + return; + } + LLVector3d lpos_global = gAgentCamera.getCameraPositionGlobal(); + LLVector3 lpos_global_f; + + lpos_global_f.setVec(lpos_global); + + gAudiop->setListener(lpos_global_f, + LLVector3::zero, // LLViewerCamera::getInstance()->getVelocity(), // !!! BUG need to replace this with smoothed velocity! + LLViewerCamera::getInstance()->getUpAxis(), + LLViewerCamera::getInstance()->getAtAxis()); + +// load up our initial set of sounds we'll want so they're in memory and ready to be played + + bool mute_audio = gSavedSettings.getBOOL("MuteAudio"); + + if (!mute_audio && false == gSavedSettings.getBOOL("NoPreload")) + { + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndAlert"))); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndBadKeystroke"))); + //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndChatFromObject"))); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndClick"))); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndClickRelease"))); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndHealthReductionF"))); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndHealthReductionM"))); + //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndIncomingChat"))); + //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndIncomingIM"))); + //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndInvApplyToObject"))); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndInvalidOp"))); + //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndInventoryCopyToInv"))); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndMoneyChangeDown"))); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndMoneyChangeUp"))); + //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectCopyToInv"))); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectCreate"))); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectDelete"))); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectRezIn"))); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndObjectRezOut"))); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndSnapshot"))); + //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndStartAutopilot"))); + //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndStartFollowpilot"))); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndStartIM"))); + //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndStopAutopilot"))); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndTeleportOut"))); + //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndTextureApplyToObject"))); + //gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndTextureCopyToInv"))); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndTyping"))); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndWindowClose"))); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndWindowOpen"))); + gAudiop->preloadSound(LLUUID(gSavedSettings.getString("UISndRestart"))); + } + + audio_update_volume(true); +} + +void audio_update_volume(bool force_update) +{ + F32 master_volume = gSavedSettings.getF32("AudioLevelMaster"); + bool mute_audio = gSavedSettings.getBOOL("MuteAudio"); + + LLProgressView* progress = gViewerWindow->getProgressView(); + bool progress_view_visible = false; + + if (progress) + { + progress_view_visible = progress->getVisible(); + } + + if (!gViewerWindow->getActive() && gSavedSettings.getBOOL("MuteWhenMinimized")) + { + mute_audio = true; + } + F32 mute_volume = mute_audio ? 0.0f : 1.0f; + + if (gAudiop) + { + // Sound Effects + + gAudiop->setMasterGain ( master_volume ); + + const F32 AUDIO_LEVEL_DOPPLER = 1.f; + gAudiop->setDopplerFactor(AUDIO_LEVEL_DOPPLER); + + if(!LLViewerCamera::getInstance()->cameraUnderWater()) + { + const F32 AUDIO_LEVEL_ROLLOFF = 1.f; + gAudiop->setRolloffFactor(AUDIO_LEVEL_ROLLOFF); + } + else + { + const F32 AUDIO_LEVEL_UNDERWATER_ROLLOFF = 5.f; + gAudiop->setRolloffFactor(AUDIO_LEVEL_UNDERWATER_ROLLOFF); + } + + gAudiop->setMuted(mute_audio || progress_view_visible); + + //Play any deferred sounds when unmuted + if(!gAudiop->getMuted()) + { + LLDeferredSounds::instance().playdeferredSounds(); + } + + if (force_update) + { + audio_update_wind(true); + } + + // handle secondary gains + gAudiop->setSecondaryGain(LLAudioEngine::AUDIO_TYPE_SFX, + gSavedSettings.getBOOL("MuteSounds") ? 0.f : gSavedSettings.getF32("AudioLevelSFX")); + gAudiop->setSecondaryGain(LLAudioEngine::AUDIO_TYPE_UI, + gSavedSettings.getBOOL("MuteUI") ? 0.f : gSavedSettings.getF32("AudioLevelUI")); + gAudiop->setSecondaryGain(LLAudioEngine::AUDIO_TYPE_AMBIENT, + gSavedSettings.getBOOL("MuteAmbient") ? 0.f : gSavedSettings.getF32("AudioLevelAmbient")); + + // Streaming Music + + if (!progress_view_visible && LLViewerAudio::getInstance()->getForcedTeleportFade()) + { + LLViewerAudio::getInstance()->setWasPlaying(!gAudiop->getInternetStreamURL().empty()); + LLViewerAudio::getInstance()->setForcedTeleportFade(false); + } + + F32 music_volume = gSavedSettings.getF32("AudioLevelMusic"); + bool music_muted = gSavedSettings.getBOOL("MuteMusic"); + F32 fade_volume = LLViewerAudio::getInstance()->getFadeVolume(); + + music_volume = mute_volume * master_volume * music_volume * fade_volume; + gAudiop->setInternetStreamGain (music_muted ? 0.f : music_volume); + } + + // Streaming Media + F32 media_volume = gSavedSettings.getF32("AudioLevelMedia"); + bool media_muted = gSavedSettings.getBOOL("MuteMedia"); + media_volume = mute_volume * master_volume * media_volume; + LLViewerMedia::getInstance()->setVolume( media_muted ? 0.0f : media_volume ); + + // Voice, this is parametric singleton, it gets initialized when ready + if (LLVoiceClient::instanceExists()) + { + F32 voice_volume = gSavedSettings.getF32("AudioLevelVoice"); + voice_volume = mute_volume * master_volume * voice_volume; + bool voice_mute = gSavedSettings.getBOOL("MuteVoice"); + LLVoiceClient *voice_inst = LLVoiceClient::getInstance(); + voice_inst->setVoiceVolume(voice_mute ? 0.f : voice_volume); + voice_inst->setMicGain(voice_mute ? 0.f : gSavedSettings.getF32("AudioLevelMic")); + + if (!gViewerWindow->getActive() && (gSavedSettings.getBOOL("MuteWhenMinimized"))) + { + voice_inst->setMuteMic(true); + } + else + { + voice_inst->setMuteMic(false); + } + } +} + +void audio_update_listener() +{ + if (gAudiop) + { + // update listener position because agent has moved + static LLUICachedControl mEarLocation("MediaSoundsEarLocation", 0); + LLVector3d ear_position; + switch(mEarLocation) + { + case 0: + default: + ear_position = gAgentCamera.getCameraPositionGlobal(); + break; + + case 1: + ear_position = gAgent.getPositionGlobal(); + break; + } + LLVector3d lpos_global = ear_position; + LLVector3 lpos_global_f; + lpos_global_f.setVec(lpos_global); + + gAudiop->setListener(lpos_global_f, + // LLViewerCamera::getInstance()VelocitySmoothed, + // LLVector3::zero, + gAgent.getVelocity(), // !!! *TODO: need to replace this with smoothed velocity! + LLViewerCamera::getInstance()->getUpAxis(), + LLViewerCamera::getInstance()->getAtAxis()); + } +} + +void audio_update_wind(bool force_update) +{ +#ifdef kAUDIO_ENABLE_WIND + + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + // Scale down the contribution of weather-simulation wind to the + // ambient wind noise. Wind velocity averages 3.5 m/s, with gusts to 7 m/s + // whereas steady-state avatar walk velocity is only 3.2 m/s. + // Without this the world feels desolate on first login when you are + // standing still. + const F32 WIND_LEVEL = 0.5f; + LLVector3 scaled_wind_vec = gWindVec * WIND_LEVEL; + + // Mix in the avatar's motion, subtract because when you walk north, + // the apparent wind moves south. + LLVector3 final_wind_vec = scaled_wind_vec - gAgent.getVelocity(); + + // rotate the wind vector to be listener (agent) relative + gRelativeWindVec = gAgent.getFrameAgent().rotateToLocal( final_wind_vec ); + + // don't use the setter setMaxWindGain() because we don't + // want to screw up the fade-in on startup by setting actual source gain + // outside the fade-in. + static LLCachedControl mute_audio(gSavedSettings, "MuteAudio"); + static LLCachedControl mute_ambient(gSavedSettings, "MuteAmbient"); + static LLCachedControl level_master(gSavedSettings, "AudioLevelMaster"); + static LLCachedControl level_ambient(gSavedSettings, "AudioLevelAmbient"); + + F32 master_volume = mute_audio() ? 0.f : level_master(); + F32 ambient_volume = mute_ambient() ? 0.f : level_ambient(); + F32 max_wind_volume = master_volume * ambient_volume; + + const F32 WIND_SOUND_TRANSITION_TIME = 2.f; + // amount to change volume this frame + F32 volume_delta = (LLFrameTimer::getFrameDeltaTimeF32() / WIND_SOUND_TRANSITION_TIME) * max_wind_volume; + if (force_update) + { + // initialize wind volume (force_update) by using large volume_delta + // which is sufficient to completely turn off or turn on wind noise + volume_delta = 1.f; + } + + if (!gAudiop) + { + LL_WARNS("AudioEngine") << "LLAudioEngine instance doesn't exist!" << LL_ENDL; + return; + } + + // mute wind when not flying + if (gAgent.getFlying()) + { + // volume increases by volume_delta, up to no more than max_wind_volume + gAudiop->mMaxWindGain = llmin(gAudiop->mMaxWindGain + volume_delta, max_wind_volume); + } + else + { + // volume decreases by volume_delta, down to no less than 0 + gAudiop->mMaxWindGain = llmax(gAudiop->mMaxWindGain - volume_delta, 0.f); + } + + gAudiop->updateWind(gRelativeWindVec, gAgentCamera.getCameraPositionAgent()[VZ] - gAgent.getRegion()->getWaterHeight()); + } +#endif +} diff --git a/indra/newview/llviewercamera.cpp b/indra/newview/llviewercamera.cpp index 898f271edc..b27deae5a5 100644 --- a/indra/newview/llviewercamera.cpp +++ b/indra/newview/llviewercamera.cpp @@ -1,898 +1,898 @@ -/** - * @file llviewercamera.cpp - * @brief LLViewerCamera class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#define LLVIEWERCAMERA_CPP -#include "llviewercamera.h" - -// Viewer includes -#include "llagent.h" -#include "llagentcamera.h" -#include "llmatrix4a.h" -#include "llviewercontrol.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llviewerwindow.h" -#include "llvovolume.h" -#include "llworld.h" -#include "lltoolmgr.h" -#include "llviewerjoystick.h" - -// Linden library includes -#include "lldrawable.h" -#include "llface.h" -#include "llgl.h" -#include "llglheaders.h" -#include "llquaternion.h" -#include "llwindow.h" // getPixelAspectRatio() -#include "lltracerecording.h" -#include "llenvironment.h" - -// System includes -#include // for setprecision - -LLTrace::CountStatHandle<> LLViewerCamera::sVelocityStat("camera_velocity"); -LLTrace::CountStatHandle<> LLViewerCamera::sAngularVelocityStat("camera_angular_velocity"); - -LLViewerCamera::eCameraID LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD; - -//glu pick matrix implementation borrowed from Mesa3D -glh::matrix4f gl_pick_matrix(GLfloat x, GLfloat y, GLfloat width, GLfloat height, GLint* viewport) -{ - GLfloat m[16]; - GLfloat sx, sy; - GLfloat tx, ty; - - sx = viewport[2] / width; - sy = viewport[3] / height; - tx = (viewport[2] + 2.f * (viewport[0] - x)) / width; - ty = (viewport[3] + 2.f * (viewport[1] - y)) / height; - - #define M(row,col) m[col*4+row] - M(0,0) = sx; M(0,1) = 0.f; M(0,2) = 0.f; M(0,3) = tx; - M(1,0) = 0.f; M(1,1) = sy; M(1,2) = 0.f; M(1,3) = ty; - M(2,0) = 0.f; M(2,1) = 0.f; M(2,2) = 1.f; M(2,3) = 0.f; - M(3,0) = 0.f; M(3,1) = 0.f; M(3,2) = 0.f; M(3,3) = 1.f; - #undef M - - return glh::matrix4f(m); -} - -LLViewerCamera::LLViewerCamera() : LLCamera() -{ - calcProjection(getFar()); - mCameraFOVDefault = DEFAULT_FIELD_OF_VIEW; - mPrevCameraFOVDefault = DEFAULT_FIELD_OF_VIEW; - mCosHalfCameraFOV = cosf(mCameraFOVDefault * 0.5f); - mPixelMeterRatio = 0.f; - mScreenPixelArea = 0; - mZoomFactor = 1.f; - mZoomSubregion = 1; - mAverageSpeed = 0.f; - mAverageAngularSpeed = 0.f; - gSavedSettings.getControl("CameraAngle")->getCommitSignal()->connect(boost::bind(&LLViewerCamera::updateCameraAngle, this, _2)); -} - -void LLViewerCamera::updateCameraLocation(const LLVector3 ¢er, const LLVector3 &up_direction, const LLVector3 &point_of_interest) -{ - // do not update if avatar didn't move - if (!LLViewerJoystick::getInstance()->getCameraNeedsUpdate()) - { - return; - } - - LLVector3 last_position; - LLVector3 last_axis; - last_position = getOrigin(); - last_axis = getAtAxis(); - - mLastPointOfInterest = point_of_interest; - - LLViewerRegion* regp = LLWorld::instance().getRegionFromPosAgent(getOrigin()); - if (!regp) - { - regp = gAgent.getRegion(); - } - - F32 water_height = (NULL != regp) ? regp->getWaterHeight() : 0.f; - - LLVector3 origin = center; - - { - if (origin.mV[2] > water_height) - { - origin.mV[2] = llmax(origin.mV[2], water_height + 0.20f); - } - else - { - origin.mV[2] = llmin(origin.mV[2], water_height - 0.20f); - } - } - - setOriginAndLookAt(origin, up_direction, point_of_interest); - - mVelocityDir = origin - last_position ; - F32 dpos = mVelocityDir.normVec() ; - LLQuaternion rotation; - rotation.shortestArc(last_axis, getAtAxis()); - - F32 x, y, z; - F32 drot; - rotation.getAngleAxis(&drot, &x, &y, &z); - - add(sVelocityStat, dpos); - add(sAngularVelocityStat, drot); - - mAverageSpeed = LLTrace::get_frame_recording().getPeriodMeanPerSec(sVelocityStat, 50); - mAverageAngularSpeed = LLTrace::get_frame_recording().getPeriodMeanPerSec(sAngularVelocityStat); - mCosHalfCameraFOV = cosf(0.5f * getView() * llmax(1.0f, getAspect())); - - // update pixel meter ratio using default fov, not modified one - mPixelMeterRatio = getViewHeightInPixels()/ (2.f*tanf(mCameraFOVDefault*0.5)); - // update screen pixel area - mScreenPixelArea =(S32)((F32)getViewHeightInPixels() * ((F32)getViewHeightInPixels() * getAspect())); -} - -const LLMatrix4 &LLViewerCamera::getProjection() const -{ - calcProjection(getFar()); - return mProjectionMatrix; - -} - -const LLMatrix4 &LLViewerCamera::getModelview() const -{ - LLMatrix4 cfr(OGL_TO_CFR_ROTATION); - getMatrixToLocal(mModelviewMatrix); - mModelviewMatrix *= cfr; - return mModelviewMatrix; -} - -void LLViewerCamera::calcProjection(const F32 far_distance) const -{ - F32 fov_y, z_far, z_near, aspect, f; - fov_y = getView(); - z_far = far_distance; - z_near = getNear(); - aspect = getAspect(); - - f = 1/tan(fov_y*0.5f); - - mProjectionMatrix.setZero(); - mProjectionMatrix.mMatrix[0][0] = f/aspect; - mProjectionMatrix.mMatrix[1][1] = f; - mProjectionMatrix.mMatrix[2][2] = (z_far + z_near)/(z_near - z_far); - mProjectionMatrix.mMatrix[3][2] = (2*z_far*z_near)/(z_near - z_far); - mProjectionMatrix.mMatrix[2][3] = -1; -} - -// Sets up opengl state for 3D drawing. If for selection, also -// sets up a pick matrix. x and y are ignored if for_selection is false. -// The picking region is centered on x,y and has the specified width and -// height. - -//static -void LLViewerCamera::updateFrustumPlanes(LLCamera& camera, bool ortho, bool zflip, bool no_hacks) -{ - GLint* viewport = (GLint*) gGLViewport; - F64 model[16]; - F64 proj[16]; - - for (U32 i = 0; i < 16; i++) - { - model[i] = (F64) gGLModelView[i]; - proj[i] = (F64) gGLProjection[i]; - } - - GLdouble objX,objY,objZ; - - LLVector3 frust[8]; - - if (no_hacks) - { - gluUnProject(viewport[0],viewport[1],0,model,proj,viewport,&objX,&objY,&objZ); - frust[0].setVec((F32)objX,(F32)objY,(F32)objZ); - gluUnProject(viewport[0]+viewport[2],viewport[1],0,model,proj,viewport,&objX,&objY,&objZ); - frust[1].setVec((F32)objX,(F32)objY,(F32)objZ); - gluUnProject(viewport[0]+viewport[2],viewport[1]+viewport[3],0,model,proj,viewport,&objX,&objY,&objZ); - frust[2].setVec((F32)objX,(F32)objY,(F32)objZ); - gluUnProject(viewport[0],viewport[1]+viewport[3],0,model,proj,viewport,&objX,&objY,&objZ); - frust[3].setVec((F32)objX,(F32)objY,(F32)objZ); - - gluUnProject(viewport[0],viewport[1],1,model,proj,viewport,&objX,&objY,&objZ); - frust[4].setVec((F32)objX,(F32)objY,(F32)objZ); - gluUnProject(viewport[0]+viewport[2],viewport[1],1,model,proj,viewport,&objX,&objY,&objZ); - frust[5].setVec((F32)objX,(F32)objY,(F32)objZ); - gluUnProject(viewport[0]+viewport[2],viewport[1]+viewport[3],1,model,proj,viewport,&objX,&objY,&objZ); - frust[6].setVec((F32)objX,(F32)objY,(F32)objZ); - gluUnProject(viewport[0],viewport[1]+viewport[3],1,model,proj,viewport,&objX,&objY,&objZ); - frust[7].setVec((F32)objX,(F32)objY,(F32)objZ); - } - else if (zflip) - { - gluUnProject(viewport[0],viewport[1]+viewport[3],0,model,proj,viewport,&objX,&objY,&objZ); - frust[0].setVec((F32)objX,(F32)objY,(F32)objZ); - gluUnProject(viewport[0]+viewport[2],viewport[1]+viewport[3],0,model,proj,viewport,&objX,&objY,&objZ); - frust[1].setVec((F32)objX,(F32)objY,(F32)objZ); - gluUnProject(viewport[0]+viewport[2],viewport[1],0,model,proj,viewport,&objX,&objY,&objZ); - frust[2].setVec((F32)objX,(F32)objY,(F32)objZ); - gluUnProject(viewport[0],viewport[1],0,model,proj,viewport,&objX,&objY,&objZ); - frust[3].setVec((F32)objX,(F32)objY,(F32)objZ); - - gluUnProject(viewport[0],viewport[1]+viewport[3],1,model,proj,viewport,&objX,&objY,&objZ); - frust[4].setVec((F32)objX,(F32)objY,(F32)objZ); - gluUnProject(viewport[0]+viewport[2],viewport[1]+viewport[3],1,model,proj,viewport,&objX,&objY,&objZ); - frust[5].setVec((F32)objX,(F32)objY,(F32)objZ); - gluUnProject(viewport[0]+viewport[2],viewport[1],1,model,proj,viewport,&objX,&objY,&objZ); - frust[6].setVec((F32)objX,(F32)objY,(F32)objZ); - gluUnProject(viewport[0],viewport[1],1,model,proj,viewport,&objX,&objY,&objZ); - frust[7].setVec((F32)objX,(F32)objY,(F32)objZ); - - for (U32 i = 0; i < 4; i++) - { - frust[i+4] = frust[i+4]-frust[i]; - frust[i+4].normVec(); - frust[i+4] = frust[i] + frust[i+4]*camera.getFar(); - } - } - else - { - gluUnProject(viewport[0],viewport[1],0,model,proj,viewport,&objX,&objY,&objZ); - frust[0].setVec((F32)objX,(F32)objY,(F32)objZ); - gluUnProject(viewport[0]+viewport[2],viewport[1],0,model,proj,viewport,&objX,&objY,&objZ); - frust[1].setVec((F32)objX,(F32)objY,(F32)objZ); - gluUnProject(viewport[0]+viewport[2],viewport[1]+viewport[3],0,model,proj,viewport,&objX,&objY,&objZ); - frust[2].setVec((F32)objX,(F32)objY,(F32)objZ); - gluUnProject(viewport[0],viewport[1]+viewport[3],0,model,proj,viewport,&objX,&objY,&objZ); - frust[3].setVec((F32)objX,(F32)objY,(F32)objZ); - - if (ortho) - { - LLVector3 far_shift = camera.getAtAxis()*camera.getFar()*2.f; - for (U32 i = 0; i < 4; i++) - { - frust[i+4] = frust[i] + far_shift; - } - } - else - { - for (U32 i = 0; i < 4; i++) - { - LLVector3 vec = frust[i] - camera.getOrigin(); - vec.normVec(); - frust[i+4] = camera.getOrigin() + vec*camera.getFar(); - } - } - } - - camera.calcAgentFrustumPlanes(frust); -} - -void LLViewerCamera::setPerspective(bool for_selection, - S32 x, S32 y_from_bot, S32 width, S32 height, - bool limit_select_distance, - F32 z_near, F32 z_far) -{ - F32 fov_y, aspect; - fov_y = RAD_TO_DEG * getView(); - bool z_default_far = false; - if (z_far <= 0) - { - z_default_far = true; - z_far = getFar(); - } - if (z_near <= 0) - { - z_near = getNear(); - } - aspect = getAspect(); - - // Load camera view matrix - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.loadIdentity(); - - glh::matrix4f proj_mat; - - if (for_selection) - { - // make a tiny little viewport - // anything drawn into this viewport will be "selected" - - GLint viewport[4]; - viewport[0] = gViewerWindow->getWorldViewRectRaw().mLeft; - viewport[1] = gViewerWindow->getWorldViewRectRaw().mBottom; - viewport[2] = gViewerWindow->getWorldViewRectRaw().getWidth(); - viewport[3] = gViewerWindow->getWorldViewRectRaw().getHeight(); - - proj_mat = gl_pick_matrix(x+width/2.f, y_from_bot+height/2.f, (GLfloat) width, (GLfloat) height, viewport); - - if (limit_select_distance) - { - // ...select distance from control - z_far = gSavedSettings.getF32("MaxSelectDistance"); - } - else - { - z_far = gAgentCamera.mDrawDistance; - } - } - else - { - // Only override the far clip if it's not passed in explicitly. - if (z_default_far) - { - z_far = MAX_FAR_CLIP; - } - glViewport(x, y_from_bot, width, height); - gGLViewport[0] = x; - gGLViewport[1] = y_from_bot; - gGLViewport[2] = width; - gGLViewport[3] = height; - } - - if (mZoomFactor > 1.f) - { - float offset = mZoomFactor - 1.f; - int pos_y = mZoomSubregion / llceil(mZoomFactor); - int pos_x = mZoomSubregion - (pos_y*llceil(mZoomFactor)); - glh::matrix4f translate; - translate.set_translate(glh::vec3f(offset - (F32)pos_x * 2.f, offset - (F32)pos_y * 2.f, 0.f)); - glh::matrix4f scale; - scale.set_scale(glh::vec3f(mZoomFactor, mZoomFactor, 1.f)); - - proj_mat = scale*proj_mat; - proj_mat = translate*proj_mat; - } - - calcProjection(z_far); // Update the projection matrix cache - - proj_mat *= gl_perspective(fov_y,aspect,z_near,z_far); - - gGL.loadMatrix(proj_mat.m); - - for (U32 i = 0; i < 16; i++) - { - gGLProjection[i] = proj_mat.m[i]; - } - - gGL.matrixMode(LLRender::MM_MODELVIEW); - - glh::matrix4f modelview((GLfloat*) OGL_TO_CFR_ROTATION); - - GLfloat ogl_matrix[16]; - - getOpenGLTransform(ogl_matrix); - - modelview *= glh::matrix4f(ogl_matrix); - - gGL.loadMatrix(modelview.m); - - if (for_selection && (width > 1 || height > 1)) - { - // NB: as of this writing, i believe the code below is broken (doesn't take into account the world view, assumes entire window) - // however, it is also unused (the GL matricies are used for selection, (see LLCamera::sphereInFrustum())) and so i'm not - // comfortable hacking on it. - calculateFrustumPlanesFromWindow((F32)(x - width / 2) / (F32)gViewerWindow->getWindowWidthScaled() - 0.5f, - (F32)(y_from_bot - height / 2) / (F32)gViewerWindow->getWindowHeightScaled() - 0.5f, - (F32)(x + width / 2) / (F32)gViewerWindow->getWindowWidthScaled() - 0.5f, - (F32)(y_from_bot + height / 2) / (F32)gViewerWindow->getWindowHeightScaled() - 0.5f); - - } - - // if not picking and not doing a snapshot, cache various GL matrices - if (!for_selection && mZoomFactor == 1.f) - { - // Save GL matrices for access elsewhere in code, especially project_world_to_screen - for (U32 i = 0; i < 16; i++) - { - gGLModelView[i] = modelview.m[i]; - } - } - - updateFrustumPlanes(*this); -} - - -// Uses the last GL matrices set in set_perspective to project a point from -// screen coordinates to the agent's region. -void LLViewerCamera::projectScreenToPosAgent(const S32 screen_x, const S32 screen_y, LLVector3* pos_agent) const -{ - GLdouble x, y, z; - - F64 mdlv[16]; - F64 proj[16]; - - for (U32 i = 0; i < 16; i++) - { - mdlv[i] = (F64) gGLModelView[i]; - proj[i] = (F64) gGLProjection[i]; - } - - gluUnProject( - GLdouble(screen_x), GLdouble(screen_y), 0.0, - mdlv, proj, (GLint*)gGLViewport, - &x, - &y, - &z ); - pos_agent->setVec( (F32)x, (F32)y, (F32)z ); -} - -// Uses the last GL matrices set in set_perspective to project a point from -// the agent's region space to screen coordinates. Returns true if point in within -// the current window. -bool LLViewerCamera::projectPosAgentToScreen(const LLVector3 &pos_agent, LLCoordGL &out_point, const bool clamp) const -{ - bool in_front = true; - GLdouble x, y, z; // object's window coords, GL-style - - LLVector3 dir_to_point = pos_agent - getOrigin(); - dir_to_point /= dir_to_point.magVec(); - - if (dir_to_point * getAtAxis() < 0.f) - { - if (clamp) - { - return false; - } - else - { - in_front = false; - } - } - - LLRect world_view_rect = gViewerWindow->getWorldViewRectRaw(); - S32 viewport[4]; - viewport[0] = world_view_rect.mLeft; - viewport[1] = world_view_rect.mBottom; - viewport[2] = world_view_rect.getWidth(); - viewport[3] = world_view_rect.getHeight(); - - F64 mdlv[16]; - F64 proj[16]; - - for (U32 i = 0; i < 16; i++) - { - mdlv[i] = (F64) gGLModelView[i]; - proj[i] = (F64) gGLProjection[i]; - } - - if (GL_TRUE == gluProject(pos_agent.mV[VX], pos_agent.mV[VY], pos_agent.mV[VZ], - mdlv, proj, (GLint*)viewport, - &x, &y, &z)) - { - // convert screen coordinates to virtual UI coordinates - x /= gViewerWindow->getDisplayScale().mV[VX]; - y /= gViewerWindow->getDisplayScale().mV[VY]; - - // should now have the x,y coords of grab_point in screen space - LLRect world_rect = gViewerWindow->getWorldViewRectScaled(); - - // convert to pixel coordinates - S32 int_x = lltrunc(x); - S32 int_y = lltrunc(y); - - bool valid = true; - - if (clamp) - { - if (int_x < world_rect.mLeft) - { - out_point.mX = world_rect.mLeft; - valid = false; - } - else if (int_x > world_rect.mRight) - { - out_point.mX = world_rect.mRight; - valid = false; - } - else - { - out_point.mX = int_x; - } - - if (int_y < world_rect.mBottom) - { - out_point.mY = world_rect.mBottom; - valid = false; - } - else if (int_y > world_rect.mTop) - { - out_point.mY = world_rect.mTop; - valid = false; - } - else - { - out_point.mY = int_y; - } - return valid; - } - else - { - out_point.mX = int_x; - out_point.mY = int_y; - - if (int_x < world_rect.mLeft) - { - valid = false; - } - else if (int_x > world_rect.mRight) - { - valid = false; - } - if (int_y < world_rect.mBottom) - { - valid = false; - } - else if (int_y > world_rect.mTop) - { - valid = false; - } - - return in_front && valid; - } - } - else - { - return false; - } -} - -// Uses the last GL matrices set in set_perspective to project a point from -// the agent's region space to the nearest edge in screen coordinates. -// Returns true if projection succeeds. -bool LLViewerCamera::projectPosAgentToScreenEdge(const LLVector3 &pos_agent, - LLCoordGL &out_point) const -{ - LLVector3 dir_to_point = pos_agent - getOrigin(); - dir_to_point /= dir_to_point.magVec(); - - bool in_front = true; - if (dir_to_point * getAtAxis() < 0.f) - { - in_front = false; - } - - LLRect world_view_rect = gViewerWindow->getWorldViewRectRaw(); - S32 viewport[4]; - viewport[0] = world_view_rect.mLeft; - viewport[1] = world_view_rect.mBottom; - viewport[2] = world_view_rect.getWidth(); - viewport[3] = world_view_rect.getHeight(); - GLdouble x, y, z; // object's window coords, GL-style - - F64 mdlv[16]; - F64 proj[16]; - - for (U32 i = 0; i < 16; i++) - { - mdlv[i] = (F64) gGLModelView[i]; - proj[i] = (F64) gGLProjection[i]; - } - - if (GL_TRUE == gluProject(pos_agent.mV[VX], pos_agent.mV[VY], - pos_agent.mV[VZ], mdlv, - proj, (GLint*)viewport, - &x, &y, &z)) - { - x /= gViewerWindow->getDisplayScale().mV[VX]; - y /= gViewerWindow->getDisplayScale().mV[VY]; - // should now have the x,y coords of grab_point in screen space - const LLRect& world_rect = gViewerWindow->getWorldViewRectScaled(); - - // ...sanity check - S32 int_x = lltrunc(x); - S32 int_y = lltrunc(y); - - // find the center - GLdouble center_x = (GLdouble)world_rect.getCenterX(); - GLdouble center_y = (GLdouble)world_rect.getCenterY(); - - if (x == center_x && y == center_y) - { - // can't project to edge from exact center - return false; - } - - // find the line from center to local - GLdouble line_x = x - center_x; - GLdouble line_y = y - center_y; - - int_x = lltrunc(center_x); - int_y = lltrunc(center_y); - - - if (0.f == line_x) - { - // the slope of the line is undefined - if (line_y > 0.f) - { - int_y = world_rect.mTop; - } - else - { - int_y = world_rect.mBottom; - } - } - else if (0 == world_rect.getWidth()) - { - // the diagonal slope of the view is undefined - if (y < world_rect.mBottom) - { - int_y = world_rect.mBottom; - } - else if ( y > world_rect.mTop) - { - int_y = world_rect.mTop; - } - } - else - { - F32 line_slope = (F32)(line_y / line_x); - F32 rect_slope = ((F32)world_rect.getHeight()) / ((F32)world_rect.getWidth()); - - if (fabs(line_slope) > rect_slope) - { - if (line_y < 0.f) - { - // bottom - int_y = world_rect.mBottom; - } - else - { - // top - int_y = world_rect.mTop; - } - int_x = lltrunc(((GLdouble)int_y - center_y) / line_slope + center_x); - } - else if (fabs(line_slope) < rect_slope) - { - if (line_x < 0.f) - { - // left - int_x = world_rect.mLeft; - } - else - { - // right - int_x = world_rect.mRight; - } - int_y = lltrunc(((GLdouble)int_x - center_x) * line_slope + center_y); - } - else - { - // exactly parallel ==> push to the corners - if (line_x > 0.f) - { - int_x = world_rect.mRight; - } - else - { - int_x = world_rect.mLeft; - } - if (line_y > 0.0f) - { - int_y = world_rect.mTop; - } - else - { - int_y = world_rect.mBottom; - } - } - } - if (!in_front) - { - int_x = world_rect.mLeft + world_rect.mRight - int_x; - int_y = world_rect.mBottom + world_rect.mTop - int_y; - } - - out_point.mX = int_x + world_rect.mLeft; - out_point.mY = int_y + world_rect.mBottom; - return true; - } - return false; -} - - -void LLViewerCamera::getPixelVectors(const LLVector3 &pos_agent, LLVector3 &up, LLVector3 &right) -{ - LLVector3 to_vec = pos_agent - getOrigin(); - - F32 at_dist = to_vec * getAtAxis(); - - F32 height_meters = at_dist* (F32)tan(getView()/2.f); - F32 height_pixels = getViewHeightInPixels()/2.f; - - F32 pixel_aspect = gViewerWindow->getWindow()->getPixelAspectRatio(); - - F32 meters_per_pixel = height_meters / height_pixels; - up = getUpAxis() * meters_per_pixel * gViewerWindow->getDisplayScale().mV[VY]; - right = -1.f * pixel_aspect * meters_per_pixel * getLeftAxis() * gViewerWindow->getDisplayScale().mV[VX]; -} - -LLVector3 LLViewerCamera::roundToPixel(const LLVector3 &pos_agent) -{ - F32 dist = (pos_agent - getOrigin()).magVec(); - // Convert to screen space and back, preserving the depth. - LLCoordGL screen_point; - if (!projectPosAgentToScreen(pos_agent, screen_point, false)) - { - // Off the screen, just return the original position. - return pos_agent; - } - - LLVector3 ray_dir; - - projectScreenToPosAgent(screen_point.mX, screen_point.mY, &ray_dir); - ray_dir -= getOrigin(); - ray_dir.normVec(); - - LLVector3 pos_agent_rounded = getOrigin() + ray_dir*dist; - - /* - LLVector3 pixel_x, pixel_y; - getPixelVectors(pos_agent_rounded, pixel_y, pixel_x); - pos_agent_rounded += 0.5f*pixel_x, 0.5f*pixel_y; - */ - return pos_agent_rounded; -} - -bool LLViewerCamera::cameraUnderWater() const -{ - LLViewerRegion* regionp = LLWorld::instance().getRegionFromPosAgent(getOrigin()); - - if (!regionp) - { - regionp = gAgent.getRegion(); - } - - if(!regionp) - { - return false ; - } - - return getOrigin().mV[VZ] < regionp->getWaterHeight(); -} - -bool LLViewerCamera::areVertsVisible(LLViewerObject* volumep, bool all_verts) -{ - S32 i, num_faces; - LLDrawable* drawablep = volumep->mDrawable; - - if (!drawablep) - { - return false; - } - - LLVolume* volume = volumep->getVolume(); - if (!volume) - { - return false; - } - - LLVOVolume* vo_volume = (LLVOVolume*) volumep; - - vo_volume->updateRelativeXform(); - LLMatrix4 mat = vo_volume->getRelativeXform(); - - LLMatrix4 render_mat(vo_volume->getRenderRotation(), LLVector4(vo_volume->getRenderPosition())); - - LLMatrix4a render_mata; - render_mata.loadu(render_mat); - LLMatrix4a mata; - mata.loadu(mat); - - num_faces = volume->getNumVolumeFaces(); - for (i = 0; i < num_faces; i++) - { - const LLVolumeFace& face = volume->getVolumeFace(i); - - for (U32 v = 0; v < face.mNumVertices; v++) - { - const LLVector4a& src_vec = face.mPositions[v]; - LLVector4a vec; - mata.affineTransform(src_vec, vec); - - if (drawablep->isActive()) - { - LLVector4a t = vec; - render_mata.affineTransform(t, vec); - } - - bool in_frustum = pointInFrustum(LLVector3(vec.getF32ptr())) > 0; - - if (( !in_frustum && all_verts) || - (in_frustum && !all_verts)) - { - return !all_verts; - } - } - } - return all_verts; -} - -extern bool gCubeSnapshot; - -// changes local camera and broadcasts change -/* virtual */ void LLViewerCamera::setView(F32 vertical_fov_rads) -{ - llassert(!gCubeSnapshot); - - F32 old_fov = LLViewerCamera::getInstance()->getView(); - - // cap the FoV - vertical_fov_rads = llclamp(vertical_fov_rads, getMinView(), getMaxView()); - - if (vertical_fov_rads == old_fov) return; - - // send the new value to the simulator - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_AgentFOV); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addU32Fast(_PREHASH_CircuitCode, gMessageSystem->mOurCircuitCode); - - msg->nextBlockFast(_PREHASH_FOVBlock); - msg->addU32Fast(_PREHASH_GenCounter, 0); - msg->addF32Fast(_PREHASH_VerticalAngle, vertical_fov_rads); - - gAgent.sendReliableMessage(); - - // sync the camera with the new value - LLCamera::setView(vertical_fov_rads); // call base implementation -} - -void LLViewerCamera::setViewNoBroadcast(F32 vertical_fov_rads) -{ - LLCamera::setView(vertical_fov_rads); -} - -void LLViewerCamera::setDefaultFOV(F32 vertical_fov_rads) -{ - vertical_fov_rads = llclamp(vertical_fov_rads, getMinView(), getMaxView()); - setView(vertical_fov_rads); - mCameraFOVDefault = vertical_fov_rads; - mCosHalfCameraFOV = cosf(mCameraFOVDefault * 0.5f); -} - -bool LLViewerCamera::isDefaultFOVChanged() -{ - if(mPrevCameraFOVDefault != mCameraFOVDefault) - { - mPrevCameraFOVDefault = mCameraFOVDefault; - return !gSavedSettings.getBOOL("IgnoreFOVZoomForLODs"); - } - return false; -} - -// static -void LLViewerCamera::updateCameraAngle( void* user_data, const LLSD& value) -{ - LLViewerCamera* self=(LLViewerCamera*)user_data; - self->setDefaultFOV(value.asReal()); -} - +/** + * @file llviewercamera.cpp + * @brief LLViewerCamera class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#define LLVIEWERCAMERA_CPP +#include "llviewercamera.h" + +// Viewer includes +#include "llagent.h" +#include "llagentcamera.h" +#include "llmatrix4a.h" +#include "llviewercontrol.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" +#include "llvovolume.h" +#include "llworld.h" +#include "lltoolmgr.h" +#include "llviewerjoystick.h" + +// Linden library includes +#include "lldrawable.h" +#include "llface.h" +#include "llgl.h" +#include "llglheaders.h" +#include "llquaternion.h" +#include "llwindow.h" // getPixelAspectRatio() +#include "lltracerecording.h" +#include "llenvironment.h" + +// System includes +#include // for setprecision + +LLTrace::CountStatHandle<> LLViewerCamera::sVelocityStat("camera_velocity"); +LLTrace::CountStatHandle<> LLViewerCamera::sAngularVelocityStat("camera_angular_velocity"); + +LLViewerCamera::eCameraID LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD; + +//glu pick matrix implementation borrowed from Mesa3D +glh::matrix4f gl_pick_matrix(GLfloat x, GLfloat y, GLfloat width, GLfloat height, GLint* viewport) +{ + GLfloat m[16]; + GLfloat sx, sy; + GLfloat tx, ty; + + sx = viewport[2] / width; + sy = viewport[3] / height; + tx = (viewport[2] + 2.f * (viewport[0] - x)) / width; + ty = (viewport[3] + 2.f * (viewport[1] - y)) / height; + + #define M(row,col) m[col*4+row] + M(0,0) = sx; M(0,1) = 0.f; M(0,2) = 0.f; M(0,3) = tx; + M(1,0) = 0.f; M(1,1) = sy; M(1,2) = 0.f; M(1,3) = ty; + M(2,0) = 0.f; M(2,1) = 0.f; M(2,2) = 1.f; M(2,3) = 0.f; + M(3,0) = 0.f; M(3,1) = 0.f; M(3,2) = 0.f; M(3,3) = 1.f; + #undef M + + return glh::matrix4f(m); +} + +LLViewerCamera::LLViewerCamera() : LLCamera() +{ + calcProjection(getFar()); + mCameraFOVDefault = DEFAULT_FIELD_OF_VIEW; + mPrevCameraFOVDefault = DEFAULT_FIELD_OF_VIEW; + mCosHalfCameraFOV = cosf(mCameraFOVDefault * 0.5f); + mPixelMeterRatio = 0.f; + mScreenPixelArea = 0; + mZoomFactor = 1.f; + mZoomSubregion = 1; + mAverageSpeed = 0.f; + mAverageAngularSpeed = 0.f; + gSavedSettings.getControl("CameraAngle")->getCommitSignal()->connect(boost::bind(&LLViewerCamera::updateCameraAngle, this, _2)); +} + +void LLViewerCamera::updateCameraLocation(const LLVector3 ¢er, const LLVector3 &up_direction, const LLVector3 &point_of_interest) +{ + // do not update if avatar didn't move + if (!LLViewerJoystick::getInstance()->getCameraNeedsUpdate()) + { + return; + } + + LLVector3 last_position; + LLVector3 last_axis; + last_position = getOrigin(); + last_axis = getAtAxis(); + + mLastPointOfInterest = point_of_interest; + + LLViewerRegion* regp = LLWorld::instance().getRegionFromPosAgent(getOrigin()); + if (!regp) + { + regp = gAgent.getRegion(); + } + + F32 water_height = (NULL != regp) ? regp->getWaterHeight() : 0.f; + + LLVector3 origin = center; + + { + if (origin.mV[2] > water_height) + { + origin.mV[2] = llmax(origin.mV[2], water_height + 0.20f); + } + else + { + origin.mV[2] = llmin(origin.mV[2], water_height - 0.20f); + } + } + + setOriginAndLookAt(origin, up_direction, point_of_interest); + + mVelocityDir = origin - last_position ; + F32 dpos = mVelocityDir.normVec() ; + LLQuaternion rotation; + rotation.shortestArc(last_axis, getAtAxis()); + + F32 x, y, z; + F32 drot; + rotation.getAngleAxis(&drot, &x, &y, &z); + + add(sVelocityStat, dpos); + add(sAngularVelocityStat, drot); + + mAverageSpeed = LLTrace::get_frame_recording().getPeriodMeanPerSec(sVelocityStat, 50); + mAverageAngularSpeed = LLTrace::get_frame_recording().getPeriodMeanPerSec(sAngularVelocityStat); + mCosHalfCameraFOV = cosf(0.5f * getView() * llmax(1.0f, getAspect())); + + // update pixel meter ratio using default fov, not modified one + mPixelMeterRatio = getViewHeightInPixels()/ (2.f*tanf(mCameraFOVDefault*0.5)); + // update screen pixel area + mScreenPixelArea =(S32)((F32)getViewHeightInPixels() * ((F32)getViewHeightInPixels() * getAspect())); +} + +const LLMatrix4 &LLViewerCamera::getProjection() const +{ + calcProjection(getFar()); + return mProjectionMatrix; + +} + +const LLMatrix4 &LLViewerCamera::getModelview() const +{ + LLMatrix4 cfr(OGL_TO_CFR_ROTATION); + getMatrixToLocal(mModelviewMatrix); + mModelviewMatrix *= cfr; + return mModelviewMatrix; +} + +void LLViewerCamera::calcProjection(const F32 far_distance) const +{ + F32 fov_y, z_far, z_near, aspect, f; + fov_y = getView(); + z_far = far_distance; + z_near = getNear(); + aspect = getAspect(); + + f = 1/tan(fov_y*0.5f); + + mProjectionMatrix.setZero(); + mProjectionMatrix.mMatrix[0][0] = f/aspect; + mProjectionMatrix.mMatrix[1][1] = f; + mProjectionMatrix.mMatrix[2][2] = (z_far + z_near)/(z_near - z_far); + mProjectionMatrix.mMatrix[3][2] = (2*z_far*z_near)/(z_near - z_far); + mProjectionMatrix.mMatrix[2][3] = -1; +} + +// Sets up opengl state for 3D drawing. If for selection, also +// sets up a pick matrix. x and y are ignored if for_selection is false. +// The picking region is centered on x,y and has the specified width and +// height. + +//static +void LLViewerCamera::updateFrustumPlanes(LLCamera& camera, bool ortho, bool zflip, bool no_hacks) +{ + GLint* viewport = (GLint*) gGLViewport; + F64 model[16]; + F64 proj[16]; + + for (U32 i = 0; i < 16; i++) + { + model[i] = (F64) gGLModelView[i]; + proj[i] = (F64) gGLProjection[i]; + } + + GLdouble objX,objY,objZ; + + LLVector3 frust[8]; + + if (no_hacks) + { + gluUnProject(viewport[0],viewport[1],0,model,proj,viewport,&objX,&objY,&objZ); + frust[0].setVec((F32)objX,(F32)objY,(F32)objZ); + gluUnProject(viewport[0]+viewport[2],viewport[1],0,model,proj,viewport,&objX,&objY,&objZ); + frust[1].setVec((F32)objX,(F32)objY,(F32)objZ); + gluUnProject(viewport[0]+viewport[2],viewport[1]+viewport[3],0,model,proj,viewport,&objX,&objY,&objZ); + frust[2].setVec((F32)objX,(F32)objY,(F32)objZ); + gluUnProject(viewport[0],viewport[1]+viewport[3],0,model,proj,viewport,&objX,&objY,&objZ); + frust[3].setVec((F32)objX,(F32)objY,(F32)objZ); + + gluUnProject(viewport[0],viewport[1],1,model,proj,viewport,&objX,&objY,&objZ); + frust[4].setVec((F32)objX,(F32)objY,(F32)objZ); + gluUnProject(viewport[0]+viewport[2],viewport[1],1,model,proj,viewport,&objX,&objY,&objZ); + frust[5].setVec((F32)objX,(F32)objY,(F32)objZ); + gluUnProject(viewport[0]+viewport[2],viewport[1]+viewport[3],1,model,proj,viewport,&objX,&objY,&objZ); + frust[6].setVec((F32)objX,(F32)objY,(F32)objZ); + gluUnProject(viewport[0],viewport[1]+viewport[3],1,model,proj,viewport,&objX,&objY,&objZ); + frust[7].setVec((F32)objX,(F32)objY,(F32)objZ); + } + else if (zflip) + { + gluUnProject(viewport[0],viewport[1]+viewport[3],0,model,proj,viewport,&objX,&objY,&objZ); + frust[0].setVec((F32)objX,(F32)objY,(F32)objZ); + gluUnProject(viewport[0]+viewport[2],viewport[1]+viewport[3],0,model,proj,viewport,&objX,&objY,&objZ); + frust[1].setVec((F32)objX,(F32)objY,(F32)objZ); + gluUnProject(viewport[0]+viewport[2],viewport[1],0,model,proj,viewport,&objX,&objY,&objZ); + frust[2].setVec((F32)objX,(F32)objY,(F32)objZ); + gluUnProject(viewport[0],viewport[1],0,model,proj,viewport,&objX,&objY,&objZ); + frust[3].setVec((F32)objX,(F32)objY,(F32)objZ); + + gluUnProject(viewport[0],viewport[1]+viewport[3],1,model,proj,viewport,&objX,&objY,&objZ); + frust[4].setVec((F32)objX,(F32)objY,(F32)objZ); + gluUnProject(viewport[0]+viewport[2],viewport[1]+viewport[3],1,model,proj,viewport,&objX,&objY,&objZ); + frust[5].setVec((F32)objX,(F32)objY,(F32)objZ); + gluUnProject(viewport[0]+viewport[2],viewport[1],1,model,proj,viewport,&objX,&objY,&objZ); + frust[6].setVec((F32)objX,(F32)objY,(F32)objZ); + gluUnProject(viewport[0],viewport[1],1,model,proj,viewport,&objX,&objY,&objZ); + frust[7].setVec((F32)objX,(F32)objY,(F32)objZ); + + for (U32 i = 0; i < 4; i++) + { + frust[i+4] = frust[i+4]-frust[i]; + frust[i+4].normVec(); + frust[i+4] = frust[i] + frust[i+4]*camera.getFar(); + } + } + else + { + gluUnProject(viewport[0],viewport[1],0,model,proj,viewport,&objX,&objY,&objZ); + frust[0].setVec((F32)objX,(F32)objY,(F32)objZ); + gluUnProject(viewport[0]+viewport[2],viewport[1],0,model,proj,viewport,&objX,&objY,&objZ); + frust[1].setVec((F32)objX,(F32)objY,(F32)objZ); + gluUnProject(viewport[0]+viewport[2],viewport[1]+viewport[3],0,model,proj,viewport,&objX,&objY,&objZ); + frust[2].setVec((F32)objX,(F32)objY,(F32)objZ); + gluUnProject(viewport[0],viewport[1]+viewport[3],0,model,proj,viewport,&objX,&objY,&objZ); + frust[3].setVec((F32)objX,(F32)objY,(F32)objZ); + + if (ortho) + { + LLVector3 far_shift = camera.getAtAxis()*camera.getFar()*2.f; + for (U32 i = 0; i < 4; i++) + { + frust[i+4] = frust[i] + far_shift; + } + } + else + { + for (U32 i = 0; i < 4; i++) + { + LLVector3 vec = frust[i] - camera.getOrigin(); + vec.normVec(); + frust[i+4] = camera.getOrigin() + vec*camera.getFar(); + } + } + } + + camera.calcAgentFrustumPlanes(frust); +} + +void LLViewerCamera::setPerspective(bool for_selection, + S32 x, S32 y_from_bot, S32 width, S32 height, + bool limit_select_distance, + F32 z_near, F32 z_far) +{ + F32 fov_y, aspect; + fov_y = RAD_TO_DEG * getView(); + bool z_default_far = false; + if (z_far <= 0) + { + z_default_far = true; + z_far = getFar(); + } + if (z_near <= 0) + { + z_near = getNear(); + } + aspect = getAspect(); + + // Load camera view matrix + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.loadIdentity(); + + glh::matrix4f proj_mat; + + if (for_selection) + { + // make a tiny little viewport + // anything drawn into this viewport will be "selected" + + GLint viewport[4]; + viewport[0] = gViewerWindow->getWorldViewRectRaw().mLeft; + viewport[1] = gViewerWindow->getWorldViewRectRaw().mBottom; + viewport[2] = gViewerWindow->getWorldViewRectRaw().getWidth(); + viewport[3] = gViewerWindow->getWorldViewRectRaw().getHeight(); + + proj_mat = gl_pick_matrix(x+width/2.f, y_from_bot+height/2.f, (GLfloat) width, (GLfloat) height, viewport); + + if (limit_select_distance) + { + // ...select distance from control + z_far = gSavedSettings.getF32("MaxSelectDistance"); + } + else + { + z_far = gAgentCamera.mDrawDistance; + } + } + else + { + // Only override the far clip if it's not passed in explicitly. + if (z_default_far) + { + z_far = MAX_FAR_CLIP; + } + glViewport(x, y_from_bot, width, height); + gGLViewport[0] = x; + gGLViewport[1] = y_from_bot; + gGLViewport[2] = width; + gGLViewport[3] = height; + } + + if (mZoomFactor > 1.f) + { + float offset = mZoomFactor - 1.f; + int pos_y = mZoomSubregion / llceil(mZoomFactor); + int pos_x = mZoomSubregion - (pos_y*llceil(mZoomFactor)); + glh::matrix4f translate; + translate.set_translate(glh::vec3f(offset - (F32)pos_x * 2.f, offset - (F32)pos_y * 2.f, 0.f)); + glh::matrix4f scale; + scale.set_scale(glh::vec3f(mZoomFactor, mZoomFactor, 1.f)); + + proj_mat = scale*proj_mat; + proj_mat = translate*proj_mat; + } + + calcProjection(z_far); // Update the projection matrix cache + + proj_mat *= gl_perspective(fov_y,aspect,z_near,z_far); + + gGL.loadMatrix(proj_mat.m); + + for (U32 i = 0; i < 16; i++) + { + gGLProjection[i] = proj_mat.m[i]; + } + + gGL.matrixMode(LLRender::MM_MODELVIEW); + + glh::matrix4f modelview((GLfloat*) OGL_TO_CFR_ROTATION); + + GLfloat ogl_matrix[16]; + + getOpenGLTransform(ogl_matrix); + + modelview *= glh::matrix4f(ogl_matrix); + + gGL.loadMatrix(modelview.m); + + if (for_selection && (width > 1 || height > 1)) + { + // NB: as of this writing, i believe the code below is broken (doesn't take into account the world view, assumes entire window) + // however, it is also unused (the GL matricies are used for selection, (see LLCamera::sphereInFrustum())) and so i'm not + // comfortable hacking on it. + calculateFrustumPlanesFromWindow((F32)(x - width / 2) / (F32)gViewerWindow->getWindowWidthScaled() - 0.5f, + (F32)(y_from_bot - height / 2) / (F32)gViewerWindow->getWindowHeightScaled() - 0.5f, + (F32)(x + width / 2) / (F32)gViewerWindow->getWindowWidthScaled() - 0.5f, + (F32)(y_from_bot + height / 2) / (F32)gViewerWindow->getWindowHeightScaled() - 0.5f); + + } + + // if not picking and not doing a snapshot, cache various GL matrices + if (!for_selection && mZoomFactor == 1.f) + { + // Save GL matrices for access elsewhere in code, especially project_world_to_screen + for (U32 i = 0; i < 16; i++) + { + gGLModelView[i] = modelview.m[i]; + } + } + + updateFrustumPlanes(*this); +} + + +// Uses the last GL matrices set in set_perspective to project a point from +// screen coordinates to the agent's region. +void LLViewerCamera::projectScreenToPosAgent(const S32 screen_x, const S32 screen_y, LLVector3* pos_agent) const +{ + GLdouble x, y, z; + + F64 mdlv[16]; + F64 proj[16]; + + for (U32 i = 0; i < 16; i++) + { + mdlv[i] = (F64) gGLModelView[i]; + proj[i] = (F64) gGLProjection[i]; + } + + gluUnProject( + GLdouble(screen_x), GLdouble(screen_y), 0.0, + mdlv, proj, (GLint*)gGLViewport, + &x, + &y, + &z ); + pos_agent->setVec( (F32)x, (F32)y, (F32)z ); +} + +// Uses the last GL matrices set in set_perspective to project a point from +// the agent's region space to screen coordinates. Returns true if point in within +// the current window. +bool LLViewerCamera::projectPosAgentToScreen(const LLVector3 &pos_agent, LLCoordGL &out_point, const bool clamp) const +{ + bool in_front = true; + GLdouble x, y, z; // object's window coords, GL-style + + LLVector3 dir_to_point = pos_agent - getOrigin(); + dir_to_point /= dir_to_point.magVec(); + + if (dir_to_point * getAtAxis() < 0.f) + { + if (clamp) + { + return false; + } + else + { + in_front = false; + } + } + + LLRect world_view_rect = gViewerWindow->getWorldViewRectRaw(); + S32 viewport[4]; + viewport[0] = world_view_rect.mLeft; + viewport[1] = world_view_rect.mBottom; + viewport[2] = world_view_rect.getWidth(); + viewport[3] = world_view_rect.getHeight(); + + F64 mdlv[16]; + F64 proj[16]; + + for (U32 i = 0; i < 16; i++) + { + mdlv[i] = (F64) gGLModelView[i]; + proj[i] = (F64) gGLProjection[i]; + } + + if (GL_TRUE == gluProject(pos_agent.mV[VX], pos_agent.mV[VY], pos_agent.mV[VZ], + mdlv, proj, (GLint*)viewport, + &x, &y, &z)) + { + // convert screen coordinates to virtual UI coordinates + x /= gViewerWindow->getDisplayScale().mV[VX]; + y /= gViewerWindow->getDisplayScale().mV[VY]; + + // should now have the x,y coords of grab_point in screen space + LLRect world_rect = gViewerWindow->getWorldViewRectScaled(); + + // convert to pixel coordinates + S32 int_x = lltrunc(x); + S32 int_y = lltrunc(y); + + bool valid = true; + + if (clamp) + { + if (int_x < world_rect.mLeft) + { + out_point.mX = world_rect.mLeft; + valid = false; + } + else if (int_x > world_rect.mRight) + { + out_point.mX = world_rect.mRight; + valid = false; + } + else + { + out_point.mX = int_x; + } + + if (int_y < world_rect.mBottom) + { + out_point.mY = world_rect.mBottom; + valid = false; + } + else if (int_y > world_rect.mTop) + { + out_point.mY = world_rect.mTop; + valid = false; + } + else + { + out_point.mY = int_y; + } + return valid; + } + else + { + out_point.mX = int_x; + out_point.mY = int_y; + + if (int_x < world_rect.mLeft) + { + valid = false; + } + else if (int_x > world_rect.mRight) + { + valid = false; + } + if (int_y < world_rect.mBottom) + { + valid = false; + } + else if (int_y > world_rect.mTop) + { + valid = false; + } + + return in_front && valid; + } + } + else + { + return false; + } +} + +// Uses the last GL matrices set in set_perspective to project a point from +// the agent's region space to the nearest edge in screen coordinates. +// Returns true if projection succeeds. +bool LLViewerCamera::projectPosAgentToScreenEdge(const LLVector3 &pos_agent, + LLCoordGL &out_point) const +{ + LLVector3 dir_to_point = pos_agent - getOrigin(); + dir_to_point /= dir_to_point.magVec(); + + bool in_front = true; + if (dir_to_point * getAtAxis() < 0.f) + { + in_front = false; + } + + LLRect world_view_rect = gViewerWindow->getWorldViewRectRaw(); + S32 viewport[4]; + viewport[0] = world_view_rect.mLeft; + viewport[1] = world_view_rect.mBottom; + viewport[2] = world_view_rect.getWidth(); + viewport[3] = world_view_rect.getHeight(); + GLdouble x, y, z; // object's window coords, GL-style + + F64 mdlv[16]; + F64 proj[16]; + + for (U32 i = 0; i < 16; i++) + { + mdlv[i] = (F64) gGLModelView[i]; + proj[i] = (F64) gGLProjection[i]; + } + + if (GL_TRUE == gluProject(pos_agent.mV[VX], pos_agent.mV[VY], + pos_agent.mV[VZ], mdlv, + proj, (GLint*)viewport, + &x, &y, &z)) + { + x /= gViewerWindow->getDisplayScale().mV[VX]; + y /= gViewerWindow->getDisplayScale().mV[VY]; + // should now have the x,y coords of grab_point in screen space + const LLRect& world_rect = gViewerWindow->getWorldViewRectScaled(); + + // ...sanity check + S32 int_x = lltrunc(x); + S32 int_y = lltrunc(y); + + // find the center + GLdouble center_x = (GLdouble)world_rect.getCenterX(); + GLdouble center_y = (GLdouble)world_rect.getCenterY(); + + if (x == center_x && y == center_y) + { + // can't project to edge from exact center + return false; + } + + // find the line from center to local + GLdouble line_x = x - center_x; + GLdouble line_y = y - center_y; + + int_x = lltrunc(center_x); + int_y = lltrunc(center_y); + + + if (0.f == line_x) + { + // the slope of the line is undefined + if (line_y > 0.f) + { + int_y = world_rect.mTop; + } + else + { + int_y = world_rect.mBottom; + } + } + else if (0 == world_rect.getWidth()) + { + // the diagonal slope of the view is undefined + if (y < world_rect.mBottom) + { + int_y = world_rect.mBottom; + } + else if ( y > world_rect.mTop) + { + int_y = world_rect.mTop; + } + } + else + { + F32 line_slope = (F32)(line_y / line_x); + F32 rect_slope = ((F32)world_rect.getHeight()) / ((F32)world_rect.getWidth()); + + if (fabs(line_slope) > rect_slope) + { + if (line_y < 0.f) + { + // bottom + int_y = world_rect.mBottom; + } + else + { + // top + int_y = world_rect.mTop; + } + int_x = lltrunc(((GLdouble)int_y - center_y) / line_slope + center_x); + } + else if (fabs(line_slope) < rect_slope) + { + if (line_x < 0.f) + { + // left + int_x = world_rect.mLeft; + } + else + { + // right + int_x = world_rect.mRight; + } + int_y = lltrunc(((GLdouble)int_x - center_x) * line_slope + center_y); + } + else + { + // exactly parallel ==> push to the corners + if (line_x > 0.f) + { + int_x = world_rect.mRight; + } + else + { + int_x = world_rect.mLeft; + } + if (line_y > 0.0f) + { + int_y = world_rect.mTop; + } + else + { + int_y = world_rect.mBottom; + } + } + } + if (!in_front) + { + int_x = world_rect.mLeft + world_rect.mRight - int_x; + int_y = world_rect.mBottom + world_rect.mTop - int_y; + } + + out_point.mX = int_x + world_rect.mLeft; + out_point.mY = int_y + world_rect.mBottom; + return true; + } + return false; +} + + +void LLViewerCamera::getPixelVectors(const LLVector3 &pos_agent, LLVector3 &up, LLVector3 &right) +{ + LLVector3 to_vec = pos_agent - getOrigin(); + + F32 at_dist = to_vec * getAtAxis(); + + F32 height_meters = at_dist* (F32)tan(getView()/2.f); + F32 height_pixels = getViewHeightInPixels()/2.f; + + F32 pixel_aspect = gViewerWindow->getWindow()->getPixelAspectRatio(); + + F32 meters_per_pixel = height_meters / height_pixels; + up = getUpAxis() * meters_per_pixel * gViewerWindow->getDisplayScale().mV[VY]; + right = -1.f * pixel_aspect * meters_per_pixel * getLeftAxis() * gViewerWindow->getDisplayScale().mV[VX]; +} + +LLVector3 LLViewerCamera::roundToPixel(const LLVector3 &pos_agent) +{ + F32 dist = (pos_agent - getOrigin()).magVec(); + // Convert to screen space and back, preserving the depth. + LLCoordGL screen_point; + if (!projectPosAgentToScreen(pos_agent, screen_point, false)) + { + // Off the screen, just return the original position. + return pos_agent; + } + + LLVector3 ray_dir; + + projectScreenToPosAgent(screen_point.mX, screen_point.mY, &ray_dir); + ray_dir -= getOrigin(); + ray_dir.normVec(); + + LLVector3 pos_agent_rounded = getOrigin() + ray_dir*dist; + + /* + LLVector3 pixel_x, pixel_y; + getPixelVectors(pos_agent_rounded, pixel_y, pixel_x); + pos_agent_rounded += 0.5f*pixel_x, 0.5f*pixel_y; + */ + return pos_agent_rounded; +} + +bool LLViewerCamera::cameraUnderWater() const +{ + LLViewerRegion* regionp = LLWorld::instance().getRegionFromPosAgent(getOrigin()); + + if (!regionp) + { + regionp = gAgent.getRegion(); + } + + if(!regionp) + { + return false ; + } + + return getOrigin().mV[VZ] < regionp->getWaterHeight(); +} + +bool LLViewerCamera::areVertsVisible(LLViewerObject* volumep, bool all_verts) +{ + S32 i, num_faces; + LLDrawable* drawablep = volumep->mDrawable; + + if (!drawablep) + { + return false; + } + + LLVolume* volume = volumep->getVolume(); + if (!volume) + { + return false; + } + + LLVOVolume* vo_volume = (LLVOVolume*) volumep; + + vo_volume->updateRelativeXform(); + LLMatrix4 mat = vo_volume->getRelativeXform(); + + LLMatrix4 render_mat(vo_volume->getRenderRotation(), LLVector4(vo_volume->getRenderPosition())); + + LLMatrix4a render_mata; + render_mata.loadu(render_mat); + LLMatrix4a mata; + mata.loadu(mat); + + num_faces = volume->getNumVolumeFaces(); + for (i = 0; i < num_faces; i++) + { + const LLVolumeFace& face = volume->getVolumeFace(i); + + for (U32 v = 0; v < face.mNumVertices; v++) + { + const LLVector4a& src_vec = face.mPositions[v]; + LLVector4a vec; + mata.affineTransform(src_vec, vec); + + if (drawablep->isActive()) + { + LLVector4a t = vec; + render_mata.affineTransform(t, vec); + } + + bool in_frustum = pointInFrustum(LLVector3(vec.getF32ptr())) > 0; + + if (( !in_frustum && all_verts) || + (in_frustum && !all_verts)) + { + return !all_verts; + } + } + } + return all_verts; +} + +extern bool gCubeSnapshot; + +// changes local camera and broadcasts change +/* virtual */ void LLViewerCamera::setView(F32 vertical_fov_rads) +{ + llassert(!gCubeSnapshot); + + F32 old_fov = LLViewerCamera::getInstance()->getView(); + + // cap the FoV + vertical_fov_rads = llclamp(vertical_fov_rads, getMinView(), getMaxView()); + + if (vertical_fov_rads == old_fov) return; + + // send the new value to the simulator + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_AgentFOV); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addU32Fast(_PREHASH_CircuitCode, gMessageSystem->mOurCircuitCode); + + msg->nextBlockFast(_PREHASH_FOVBlock); + msg->addU32Fast(_PREHASH_GenCounter, 0); + msg->addF32Fast(_PREHASH_VerticalAngle, vertical_fov_rads); + + gAgent.sendReliableMessage(); + + // sync the camera with the new value + LLCamera::setView(vertical_fov_rads); // call base implementation +} + +void LLViewerCamera::setViewNoBroadcast(F32 vertical_fov_rads) +{ + LLCamera::setView(vertical_fov_rads); +} + +void LLViewerCamera::setDefaultFOV(F32 vertical_fov_rads) +{ + vertical_fov_rads = llclamp(vertical_fov_rads, getMinView(), getMaxView()); + setView(vertical_fov_rads); + mCameraFOVDefault = vertical_fov_rads; + mCosHalfCameraFOV = cosf(mCameraFOVDefault * 0.5f); +} + +bool LLViewerCamera::isDefaultFOVChanged() +{ + if(mPrevCameraFOVDefault != mCameraFOVDefault) + { + mPrevCameraFOVDefault = mCameraFOVDefault; + return !gSavedSettings.getBOOL("IgnoreFOVZoomForLODs"); + } + return false; +} + +// static +void LLViewerCamera::updateCameraAngle( void* user_data, const LLSD& value) +{ + LLViewerCamera* self=(LLViewerCamera*)user_data; + self->setDefaultFOV(value.asReal()); +} + diff --git a/indra/newview/llviewercamera.h b/indra/newview/llviewercamera.h index 09b1a31e7e..6d8fb2a520 100644 --- a/indra/newview/llviewercamera.h +++ b/indra/newview/llviewercamera.h @@ -1,132 +1,132 @@ -/** - * @file llviewercamera.h - * @brief LLViewerCamera class header file - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVIEWERCAMERA_H -#define LL_LLVIEWERCAMERA_H - -#include "llcamera.h" -#include "llsingleton.h" -#include "lltimer.h" -#include "m4math.h" -#include "llcoord.h" -#include "lltrace.h" - -class LLViewerObject; -const bool FOR_SELECTION = true; -const bool NOT_FOR_SELECTION = false; - -class alignas(16) LLViewerCamera : public LLCamera, public LLSimpleton -{ - LL_ALIGN_NEW -public: - LLViewerCamera(); - - typedef enum - { - CAMERA_WORLD = 0, - CAMERA_SUN_SHADOW0, - CAMERA_SUN_SHADOW1, - CAMERA_SUN_SHADOW2, - CAMERA_SUN_SHADOW3, - CAMERA_SPOT_SHADOW0, - CAMERA_SPOT_SHADOW1, - CAMERA_WATER0, - CAMERA_WATER1, - NUM_CAMERAS - } eCameraID; - - static eCameraID sCurCameraID; - - void updateCameraLocation(const LLVector3 ¢er, - const LLVector3 &up_direction, - const LLVector3 &point_of_interest); - - static void updateFrustumPlanes(LLCamera& camera, bool ortho = false, bool zflip = false, bool no_hacks = false); - static void updateCameraAngle(void* user_data, const LLSD& value); - void setPerspective(bool for_selection, S32 x, S32 y_from_bot, S32 width, S32 height, bool limit_select_distance, F32 z_near = 0, F32 z_far = 0); - - const LLMatrix4 &getProjection() const; - const LLMatrix4 &getModelview() const; - - // Warning! These assume the current global matrices are correct - void projectScreenToPosAgent(const S32 screen_x, const S32 screen_y, LLVector3* pos_agent ) const; - bool projectPosAgentToScreen(const LLVector3 &pos_agent, LLCoordGL &out_point, const bool clamp = true) const; - bool projectPosAgentToScreenEdge(const LLVector3 &pos_agent, LLCoordGL &out_point) const; - - LLVector3 getVelocityDir() const {return mVelocityDir;} - static LLTrace::CountStatHandle<>* getVelocityStat() {return &sVelocityStat; } - static LLTrace::CountStatHandle<>* getAngularVelocityStat() {return &sAngularVelocityStat; } - F32 getCosHalfFov() {return mCosHalfCameraFOV;} - F32 getAverageSpeed() {return mAverageSpeed ;} - F32 getAverageAngularSpeed() {return mAverageAngularSpeed;} - - void getPixelVectors(const LLVector3 &pos_agent, LLVector3 &up, LLVector3 &right); - LLVector3 roundToPixel(const LLVector3 &pos_agent); - - // Sets the current matrix - /* virtual */ void setView(F32 vertical_fov_rads); // NOTE: broadcasts to simulator - void setViewNoBroadcast(F32 vertical_fov_rads); // set FOV without broadcasting to simulator (for temporary local cameras) - void setDefaultFOV(F32 fov) ; - F32 getDefaultFOV() { return mCameraFOVDefault; } - - bool isDefaultFOVChanged(); - - bool cameraUnderWater() const; - bool areVertsVisible(LLViewerObject* volumep, bool all_verts); - - const LLVector3 &getPointOfInterest() { return mLastPointOfInterest; } - F32 getPixelMeterRatio() const { return mPixelMeterRatio; } - S32 getScreenPixelArea() const { return mScreenPixelArea; } - - void setZoomParameters(F32 factor, S16 subregion) { mZoomFactor = factor; mZoomSubregion = subregion; } - F32 getZoomFactor() { return mZoomFactor; } - S16 getZoomSubRegion() { return mZoomSubregion; } - -protected: - void calcProjection(const F32 far_distance) const; - - static LLTrace::CountStatHandle<> sVelocityStat; - static LLTrace::CountStatHandle<> sAngularVelocityStat; - - LLVector3 mVelocityDir ; - F32 mAverageSpeed ; - F32 mAverageAngularSpeed ; - mutable LLMatrix4 mProjectionMatrix; // Cache of perspective matrix - mutable LLMatrix4 mModelviewMatrix; - F32 mCameraFOVDefault; - F32 mPrevCameraFOVDefault; - F32 mCosHalfCameraFOV; - LLVector3 mLastPointOfInterest; - F32 mPixelMeterRatio; // Divide by distance from camera to get pixels per meter at that distance. - S32 mScreenPixelArea; // Pixel area of entire window - F32 mZoomFactor; - S16 mZoomSubregion; - -public: -}; - - -#endif // LL_LLVIEWERCAMERA_H +/** + * @file llviewercamera.h + * @brief LLViewerCamera class header file + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVIEWERCAMERA_H +#define LL_LLVIEWERCAMERA_H + +#include "llcamera.h" +#include "llsingleton.h" +#include "lltimer.h" +#include "m4math.h" +#include "llcoord.h" +#include "lltrace.h" + +class LLViewerObject; +const bool FOR_SELECTION = true; +const bool NOT_FOR_SELECTION = false; + +class alignas(16) LLViewerCamera : public LLCamera, public LLSimpleton +{ + LL_ALIGN_NEW +public: + LLViewerCamera(); + + typedef enum + { + CAMERA_WORLD = 0, + CAMERA_SUN_SHADOW0, + CAMERA_SUN_SHADOW1, + CAMERA_SUN_SHADOW2, + CAMERA_SUN_SHADOW3, + CAMERA_SPOT_SHADOW0, + CAMERA_SPOT_SHADOW1, + CAMERA_WATER0, + CAMERA_WATER1, + NUM_CAMERAS + } eCameraID; + + static eCameraID sCurCameraID; + + void updateCameraLocation(const LLVector3 ¢er, + const LLVector3 &up_direction, + const LLVector3 &point_of_interest); + + static void updateFrustumPlanes(LLCamera& camera, bool ortho = false, bool zflip = false, bool no_hacks = false); + static void updateCameraAngle(void* user_data, const LLSD& value); + void setPerspective(bool for_selection, S32 x, S32 y_from_bot, S32 width, S32 height, bool limit_select_distance, F32 z_near = 0, F32 z_far = 0); + + const LLMatrix4 &getProjection() const; + const LLMatrix4 &getModelview() const; + + // Warning! These assume the current global matrices are correct + void projectScreenToPosAgent(const S32 screen_x, const S32 screen_y, LLVector3* pos_agent ) const; + bool projectPosAgentToScreen(const LLVector3 &pos_agent, LLCoordGL &out_point, const bool clamp = true) const; + bool projectPosAgentToScreenEdge(const LLVector3 &pos_agent, LLCoordGL &out_point) const; + + LLVector3 getVelocityDir() const {return mVelocityDir;} + static LLTrace::CountStatHandle<>* getVelocityStat() {return &sVelocityStat; } + static LLTrace::CountStatHandle<>* getAngularVelocityStat() {return &sAngularVelocityStat; } + F32 getCosHalfFov() {return mCosHalfCameraFOV;} + F32 getAverageSpeed() {return mAverageSpeed ;} + F32 getAverageAngularSpeed() {return mAverageAngularSpeed;} + + void getPixelVectors(const LLVector3 &pos_agent, LLVector3 &up, LLVector3 &right); + LLVector3 roundToPixel(const LLVector3 &pos_agent); + + // Sets the current matrix + /* virtual */ void setView(F32 vertical_fov_rads); // NOTE: broadcasts to simulator + void setViewNoBroadcast(F32 vertical_fov_rads); // set FOV without broadcasting to simulator (for temporary local cameras) + void setDefaultFOV(F32 fov) ; + F32 getDefaultFOV() { return mCameraFOVDefault; } + + bool isDefaultFOVChanged(); + + bool cameraUnderWater() const; + bool areVertsVisible(LLViewerObject* volumep, bool all_verts); + + const LLVector3 &getPointOfInterest() { return mLastPointOfInterest; } + F32 getPixelMeterRatio() const { return mPixelMeterRatio; } + S32 getScreenPixelArea() const { return mScreenPixelArea; } + + void setZoomParameters(F32 factor, S16 subregion) { mZoomFactor = factor; mZoomSubregion = subregion; } + F32 getZoomFactor() { return mZoomFactor; } + S16 getZoomSubRegion() { return mZoomSubregion; } + +protected: + void calcProjection(const F32 far_distance) const; + + static LLTrace::CountStatHandle<> sVelocityStat; + static LLTrace::CountStatHandle<> sAngularVelocityStat; + + LLVector3 mVelocityDir ; + F32 mAverageSpeed ; + F32 mAverageAngularSpeed ; + mutable LLMatrix4 mProjectionMatrix; // Cache of perspective matrix + mutable LLMatrix4 mModelviewMatrix; + F32 mCameraFOVDefault; + F32 mPrevCameraFOVDefault; + F32 mCosHalfCameraFOV; + LLVector3 mLastPointOfInterest; + F32 mPixelMeterRatio; // Divide by distance from camera to get pixels per meter at that distance. + S32 mScreenPixelArea; // Pixel area of entire window + F32 mZoomFactor; + S16 mZoomSubregion; + +public: +}; + + +#endif // LL_LLVIEWERCAMERA_H diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index d947a803ed..bb6ba4622a 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -1,881 +1,881 @@ -/** - * @file llviewercontrol.cpp - * @brief Viewer configuration - * @author Richard Nelson - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llviewercontrol.h" - -// Library includes -#include "llwindow.h" // getGamma() - -// For Listeners -#include "llaudioengine.h" -#include "llagent.h" -#include "llagentcamera.h" -#include "llconsole.h" -#include "lldrawpoolbump.h" -#include "lldrawpoolterrain.h" -#include "llflexibleobject.h" -#include "llfeaturemanager.h" -#include "llviewershadermgr.h" - -#include "llsky.h" -#include "llvieweraudio.h" -#include "llviewermenu.h" -#include "llviewertexturelist.h" -#include "llviewerthrottle.h" -#include "llviewerwindow.h" -#include "llvoavatarself.h" -#include "llvoiceclient.h" -#include "llvotree.h" -#include "llvovolume.h" -#include "llworld.h" -#include "pipeline.h" -#include "llviewerjoystick.h" -#include "llviewerobjectlist.h" -#include "llviewerparcelmgr.h" -#include "llparcel.h" -#include "llkeyboard.h" -#include "llerrorcontrol.h" -#include "llappviewer.h" -#include "llvosurfacepatch.h" -#include "llvowlsky.h" -#include "llrender.h" -#include "llnavigationbar.h" -#include "llnotificationsutil.h" -#include "llfloatertools.h" -#include "llpaneloutfitsinventory.h" -#include "llpanellogin.h" -#include "llpaneltopinfobar.h" -#include "llspellcheck.h" -#include "llslurl.h" -#include "llstartup.h" -#include "llperfstats.h" - -// Third party library includes -#include - -#ifdef TOGGLE_HACKED_GODLIKE_VIEWER -bool gHackGodmode = false; -#endif - -// Should you contemplate changing the name "Global", please first grep for -// that string literal. There are at least a couple other places in the C++ -// code that assume the LLControlGroup named "Global" is gSavedSettings. -LLControlGroup gSavedSettings("Global"); // saved at end of session -LLControlGroup gSavedPerAccountSettings("PerAccount"); // saved at end of session -LLControlGroup gCrashSettings("CrashSettings"); // saved at end of session -LLControlGroup gWarningSettings("Warnings"); // persists ignored dialogs/warnings - -std::string gLastRunVersion; - -extern bool gResizeScreenTexture; -extern bool gResizeShadowTexture; -extern bool gDebugGL; -//////////////////////////////////////////////////////////////////////////// -// Listeners - -static bool handleRenderAvatarMouselookChanged(const LLSD& newvalue) -{ - LLVOAvatar::sVisibleInFirstPerson = newvalue.asBoolean(); - return true; -} - -static bool handleRenderFarClipChanged(const LLSD& newvalue) -{ - if (LLStartUp::getStartupState() >= STATE_STARTED) - { - F32 draw_distance = (F32)newvalue.asReal(); - gAgentCamera.mDrawDistance = draw_distance; - LLWorld::getInstance()->setLandFarClip(draw_distance); - return true; - } - return false; -} - -static bool handleTerrainDetailChanged(const LLSD& newvalue) -{ - LLDrawPoolTerrain::sDetailMode = newvalue.asInteger(); - return true; -} - - -static bool handleDebugAvatarJointsChanged(const LLSD& newvalue) -{ - std::string new_string = newvalue.asString(); - LLJoint::setDebugJointNames(new_string); - return true; -} - -static bool handleAvatarHoverOffsetChanged(const LLSD& newvalue) -{ - if (isAgentAvatarValid()) - { - gAgentAvatarp->setHoverIfRegionEnabled(); - } - return true; -} - - -static bool handleSetShaderChanged(const LLSD& newvalue) -{ - // changing shader level may invalidate existing cached bump maps, as the shader type determines the format of the bump map it expects - clear and repopulate the bump cache - gBumpImageList.destroyGL(); - gBumpImageList.restoreGL(); - - if (gPipeline.isInit()) - { - // ALM depends onto atmospheric shaders, state might have changed - LLPipeline::refreshCachedSettings(); - } - - // else, leave terrain detail as is - LLViewerShaderMgr::instance()->setShaders(); - return true; -} - -static bool handleRenderPerfTestChanged(const LLSD& newvalue) -{ - bool status = !newvalue.asBoolean(); - if (!status) - { - gPipeline.clearRenderTypeMask(LLPipeline::RENDER_TYPE_WL_SKY, - LLPipeline::RENDER_TYPE_TERRAIN, - LLPipeline::RENDER_TYPE_GRASS, - LLPipeline::RENDER_TYPE_TREE, - LLPipeline::RENDER_TYPE_WATER, - LLPipeline::RENDER_TYPE_PASS_GRASS, - LLPipeline::RENDER_TYPE_HUD, - LLPipeline::RENDER_TYPE_CLOUDS, - LLPipeline::RENDER_TYPE_HUD_PARTICLES, - LLPipeline::END_RENDER_TYPES); - gPipeline.setRenderDebugFeatureControl(LLPipeline::RENDER_DEBUG_FEATURE_UI, false); - } - else - { - gPipeline.setRenderTypeMask(LLPipeline::RENDER_TYPE_WL_SKY, - LLPipeline::RENDER_TYPE_TERRAIN, - LLPipeline::RENDER_TYPE_GRASS, - LLPipeline::RENDER_TYPE_TREE, - LLPipeline::RENDER_TYPE_WATER, - LLPipeline::RENDER_TYPE_PASS_GRASS, - LLPipeline::RENDER_TYPE_HUD, - LLPipeline::RENDER_TYPE_CLOUDS, - LLPipeline::RENDER_TYPE_HUD_PARTICLES, - LLPipeline::END_RENDER_TYPES); - gPipeline.setRenderDebugFeatureControl(LLPipeline::RENDER_DEBUG_FEATURE_UI, true); - } - - return true; -} - -bool handleRenderTransparentWaterChanged(const LLSD& newvalue) -{ - if (gPipeline.isInit()) - { - gPipeline.updateRenderTransparentWater(); - gPipeline.releaseGLBuffers(); - gPipeline.createGLBuffers(); - LLViewerShaderMgr::instance()->setShaders(); - } - LLWorld::getInstance()->updateWaterObjects(); - return true; -} - - -static bool handleShadowsResized(const LLSD& newvalue) -{ - gPipeline.requestResizeShadowTexture(); - return true; -} - -static bool handleWindowResized(const LLSD& newvalue) -{ - gPipeline.requestResizeScreenTexture(); - return true; -} - -static bool handleReleaseGLBufferChanged(const LLSD& newvalue) -{ - if (gPipeline.isInit()) - { - gPipeline.releaseGLBuffers(); - gPipeline.createGLBuffers(); - } - return true; -} - -static bool handleLUTBufferChanged(const LLSD& newvalue) -{ - if (gPipeline.isInit()) - { - gPipeline.releaseLUTBuffers(); - gPipeline.createLUTBuffers(); - } - return true; -} - -static bool handleAnisotropicChanged(const LLSD& newvalue) -{ - LLImageGL::sGlobalUseAnisotropic = newvalue.asBoolean(); - LLImageGL::dirtyTexOptions(); - return true; -} - -static bool handleVSyncChanged(const LLSD& newvalue) -{ - LLPerfStats::tunables.vsyncEnabled = newvalue.asBoolean(); - gViewerWindow->getWindow()->toggleVSync(newvalue.asBoolean()); - - if (newvalue.asBoolean()) - { - U32 current_target = gSavedSettings.getU32("TargetFPS"); - gSavedSettings.setU32("TargetFPS", std::min((U32)gViewerWindow->getWindow()->getRefreshRate(), current_target)); - } - - return true; -} - -static bool handleVolumeLODChanged(const LLSD& newvalue) -{ - LLVOVolume::sLODFactor = llclamp((F32) newvalue.asReal(), 0.01f, MAX_LOD_FACTOR); - LLVOVolume::sDistanceFactor = 1.f-LLVOVolume::sLODFactor * 0.1f; - return true; -} - -static bool handleAvatarLODChanged(const LLSD& newvalue) -{ - LLVOAvatar::sLODFactor = llclamp((F32) newvalue.asReal(), 0.f, MAX_AVATAR_LOD_FACTOR); - return true; -} - -static bool handleAvatarPhysicsLODChanged(const LLSD& newvalue) -{ - LLVOAvatar::sPhysicsLODFactor = llclamp((F32) newvalue.asReal(), 0.f, MAX_AVATAR_LOD_FACTOR); - return true; -} - -static bool handleTerrainLODChanged(const LLSD& newvalue) -{ - LLVOSurfacePatch::sLODFactor = (F32)newvalue.asReal(); - //sqaure lod factor to get exponential range of [0,4] and keep - //a value of 1 in the middle of the detail slider for consistency - //with other detail sliders (see panel_preferences_graphics1.xml) - LLVOSurfacePatch::sLODFactor *= LLVOSurfacePatch::sLODFactor; - return true; -} - -static bool handleTreeLODChanged(const LLSD& newvalue) -{ - LLVOTree::sTreeFactor = (F32) newvalue.asReal(); - return true; -} - -static bool handleFlexLODChanged(const LLSD& newvalue) -{ - LLVolumeImplFlexible::sUpdateFactor = (F32) newvalue.asReal(); - return true; -} - -static bool handleGammaChanged(const LLSD& newvalue) -{ - F32 gamma = (F32) newvalue.asReal(); - if (gamma == 0.0f) - { - gamma = 1.0f; // restore normal gamma - } - if (gViewerWindow && gViewerWindow->getWindow() && gamma != gViewerWindow->getWindow()->getGamma()) - { - // Only save it if it's changed - if (!gViewerWindow->getWindow()->setGamma(gamma)) - { - LL_WARNS() << "setGamma failed!" << LL_ENDL; - } - } - - return true; -} - -const F32 MAX_USER_FOG_RATIO = 10.f; -const F32 MIN_USER_FOG_RATIO = 0.5f; - -static bool handleFogRatioChanged(const LLSD& newvalue) -{ - F32 fog_ratio = llmax(MIN_USER_FOG_RATIO, llmin((F32) newvalue.asReal(), MAX_USER_FOG_RATIO)); - gSky.setFogRatio(fog_ratio); - return true; -} - -static bool handleMaxPartCountChanged(const LLSD& newvalue) -{ - LLViewerPartSim::setMaxPartCount(newvalue.asInteger()); - return true; -} - -static bool handleChatFontSizeChanged(const LLSD& newvalue) -{ - if(gConsole) - { - gConsole->setFontSize(newvalue.asInteger()); - } - return true; -} - -static bool handleConsoleMaxLinesChanged(const LLSD& newvalue) -{ - if(gConsole) - { - gConsole->setMaxLines(newvalue.asInteger()); - } - return true; -} - -static void handleAudioVolumeChanged(const LLSD& newvalue) -{ - audio_update_volume(true); -} - -static bool handleJoystickChanged(const LLSD& newvalue) -{ - LLViewerJoystick::getInstance()->setCameraNeedsUpdate(true); - return true; -} - -static bool handleUseOcclusionChanged(const LLSD& newvalue) -{ - LLPipeline::sUseOcclusion = (newvalue.asBoolean() - && LLFeatureManager::getInstance()->isFeatureAvailable("UseOcclusion") && !gUseWireframe) ? 2 : 0; - return true; -} - -static bool handleUploadBakedTexOldChanged(const LLSD& newvalue) -{ - LLPipeline::sForceOldBakedUpload = newvalue.asBoolean(); - return true; -} - - -static bool handleWLSkyDetailChanged(const LLSD&) -{ - if (gSky.mVOWLSkyp.notNull()) - { - gSky.mVOWLSkyp->updateGeometry(gSky.mVOWLSkyp->mDrawable); - } - return true; -} - -static bool handleRepartition(const LLSD&) -{ - if (gPipeline.isInit()) - { - gOctreeMaxCapacity = gSavedSettings.getU32("OctreeMaxNodeCapacity"); - gOctreeMinSize = gSavedSettings.getF32("OctreeMinimumNodeSize"); - gObjectList.repartitionObjects(); - } - return true; -} - -static bool handleRenderDynamicLODChanged(const LLSD& newvalue) -{ - LLPipeline::sDynamicLOD = newvalue.asBoolean(); - return true; -} - -static bool handleReflectionProbeDetailChanged(const LLSD& newvalue) -{ - if (gPipeline.isInit()) - { - LLPipeline::refreshCachedSettings(); - gPipeline.releaseGLBuffers(); - gPipeline.createGLBuffers(); - LLViewerShaderMgr::instance()->setShaders(); - gPipeline.mReflectionMapManager.reset(); - } - return true; -} - -static bool handleRenderDebugPipelineChanged(const LLSD& newvalue) -{ - gDebugPipeline = newvalue.asBoolean(); - return true; -} - -static bool handleRenderResolutionDivisorChanged(const LLSD&) -{ - gResizeScreenTexture = true; - return true; -} - -static bool handleDebugViewsChanged(const LLSD& newvalue) -{ - LLView::sDebugRects = newvalue.asBoolean(); - return true; -} - -static bool handleLogFileChanged(const LLSD& newvalue) -{ - std::string log_filename = newvalue.asString(); - LLFile::remove(log_filename); - LLError::logToFile(log_filename); - LL_INFOS() << "Logging switched to " << log_filename << LL_ENDL; - return true; -} - -bool handleHideGroupTitleChanged(const LLSD& newvalue) -{ - gAgent.setHideGroupTitle(newvalue); - return true; -} - -bool handleEffectColorChanged(const LLSD& newvalue) -{ - gAgent.setEffectColor(LLColor4(newvalue)); - return true; -} - -bool handleHighResSnapshotChanged(const LLSD& newvalue) -{ - // High Res Snapshot active, must uncheck RenderUIInSnapshot - if (newvalue.asBoolean()) - { - gSavedSettings.setBOOL( "RenderUIInSnapshot", false); - } - return true; -} - -bool handleVoiceClientPrefsChanged(const LLSD& newvalue) -{ - if (LLVoiceClient::instanceExists()) - { - LLVoiceClient::getInstance()->updateSettings(); - } - return true; -} - -bool handleVelocityInterpolate(const LLSD& newvalue) -{ - LLMessageSystem* msg = gMessageSystem; - if ( newvalue.asBoolean() ) - { - msg->newMessageFast(_PREHASH_VelocityInterpolateOn); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - gAgent.sendReliableMessage(); - LL_INFOS() << "Velocity Interpolation On" << LL_ENDL; - } - else - { - msg->newMessageFast(_PREHASH_VelocityInterpolateOff); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - gAgent.sendReliableMessage(); - LL_INFOS() << "Velocity Interpolation Off" << LL_ENDL; - } - return true; -} - -bool handleForceShowGrid(const LLSD& newvalue) -{ - LLPanelLogin::updateLocationSelectorsVisibility(); - return true; -} - -bool handleLoginLocationChanged() -{ - /* - * This connects the default preference setting to the state of the login - * panel if it is displayed; if you open the preferences panel before - * logging in, and change the default login location there, the login - * panel immediately changes to match your new preference. - */ - std::string new_login_location = gSavedSettings.getString("LoginLocation"); - LL_DEBUGS("AppInit")< dict_list; - std::string dict_setting = gSavedSettings.getString("SpellCheckDictionary"); - boost::split(dict_list, dict_setting, boost::is_any_of(std::string(","))); - if (!dict_list.empty()) - { - LLSpellChecker::setUseSpellCheck(dict_list.front()); - dict_list.pop_front(); - LLSpellChecker::instance().setSecondaryDictionaries(dict_list); - return true; - } - } - LLSpellChecker::setUseSpellCheck(LLStringUtil::null); - return true; -} - -bool toggle_agent_pause(const LLSD& newvalue) -{ - if ( newvalue.asBoolean() ) - { - send_agent_pause(); - } - else - { - send_agent_resume(); - } - return true; -} - -bool toggle_show_navigation_panel(const LLSD& newvalue) -{ - bool value = newvalue.asBoolean(); - - LLNavigationBar::getInstance()->setVisible(value); - gSavedSettings.setBOOL("ShowMiniLocationPanel", !value); - gViewerWindow->reshapeStatusBarContainer(); - return true; -} - -bool toggle_show_mini_location_panel(const LLSD& newvalue) -{ - bool value = newvalue.asBoolean(); - - LLPanelTopInfoBar::getInstance()->setVisible(value); - gSavedSettings.setBOOL("ShowNavbarNavigationPanel", !value); - - return true; -} - -bool toggle_show_object_render_cost(const LLSD& newvalue) -{ - LLFloaterTools::sShowObjectCost = newvalue.asBoolean(); - return true; -} - -void handleTargetFPSChanged(const LLSD& newValue) -{ - const auto targetFPS = gSavedSettings.getU32("TargetFPS"); - - U32 frame_rate_limit = gViewerWindow->getWindow()->getRefreshRate(); - if(LLPerfStats::tunables.vsyncEnabled && (targetFPS > frame_rate_limit)) - { - gSavedSettings.setU32("TargetFPS", std::min(frame_rate_limit, targetFPS)); - } - else - { - LLPerfStats::tunables.userTargetFPS = targetFPS; - } -} - -void handleAutoTuneLockChanged(const LLSD& newValue) -{ - const auto newval = gSavedSettings.getBOOL("AutoTuneLock"); - LLPerfStats::tunables.userAutoTuneLock = newval; - - gSavedSettings.setBOOL("AutoTuneFPS", newval); -} - -void handleAutoTuneFPSChanged(const LLSD& newValue) -{ - const auto newval = gSavedSettings.getBOOL("AutoTuneFPS"); - LLPerfStats::tunables.userAutoTuneEnabled = newval; - if(newval && LLPerfStats::renderAvatarMaxART_ns == 0) // If we've enabled autotune we override "unlimited" to max - { - gSavedSettings.setF32("RenderAvatarMaxART",log10(LLPerfStats::ART_UNLIMITED_NANOS-1000));//triggers callback to update static var - } -} - -void handleRenderAvatarMaxARTChanged(const LLSD& newValue) -{ - LLPerfStats::tunables.updateRenderCostLimitFromSettings(); -} - -void handleUserTargetDrawDistanceChanged(const LLSD& newValue) -{ - const auto newval = gSavedSettings.getF32("AutoTuneRenderFarClipTarget"); - LLPerfStats::tunables.userTargetDrawDistance = newval; -} - -void handleUserMinDrawDistanceChanged(const LLSD &newValue) -{ - const auto newval = gSavedSettings.getF32("AutoTuneRenderFarClipMin"); - LLPerfStats::tunables.userMinDrawDistance = newval; -} - -void handleUserTargetReflectionsChanged(const LLSD& newValue) -{ - const auto newval = gSavedSettings.getS32("UserTargetReflections"); - LLPerfStats::tunables.userTargetReflections = newval; -} - -void handlePerformanceStatsEnabledChanged(const LLSD& newValue) -{ - const auto newval = gSavedSettings.getBOOL("PerfStatsCaptureEnabled"); - LLPerfStats::StatsRecorder::setEnabled(newval); -} -void handleUserImpostorByDistEnabledChanged(const LLSD& newValue) -{ - bool auto_tune_newval = false; - S32 mode = gSavedSettings.getS32("RenderAvatarComplexityMode"); - if (mode != LLVOAvatar::AV_RENDER_ONLY_SHOW_FRIENDS) - { - auto_tune_newval = gSavedSettings.getBOOL("AutoTuneImpostorByDistEnabled"); - } - LLPerfStats::tunables.userImpostorDistanceTuningEnabled = auto_tune_newval; -} -void handleUserImpostorDistanceChanged(const LLSD& newValue) -{ - const auto newval = gSavedSettings.getF32("AutoTuneImpostorFarAwayDistance"); - LLPerfStats::tunables.userImpostorDistance = newval; -} -void handleFPSTuningStrategyChanged(const LLSD& newValue) -{ - const auto newval = gSavedSettings.getU32("TuningFPSStrategy"); - LLPerfStats::tunables.userFPSTuningStrategy = newval; -} -//////////////////////////////////////////////////////////////////////////// - -LLPointer setting_get_control(LLControlGroup& group, const std::string& setting) -{ - LLPointer cntrl_ptr = group.getControl(setting); - if (cntrl_ptr.isNull()) - { - LL_ERRS() << "Unable to set up setting listener for " << setting - << ". Please reinstall viewer from https ://secondlife.com/support/downloads/ and contact https://support.secondlife.com if issue persists after reinstall." - << LL_ENDL; - } - return cntrl_ptr; -} - -void setting_setup_signal_listener(LLControlGroup& group, const std::string& setting, std::function callback) -{ - setting_get_control(group, setting)->getSignal()->connect([callback](LLControlVariable* control, const LLSD& new_val, const LLSD& old_val) - { - callback(new_val); - }); -} - -void setting_setup_signal_listener(LLControlGroup& group, const std::string& setting, std::function callback) -{ - setting_get_control(group, setting)->getSignal()->connect([callback](LLControlVariable* control, const LLSD& new_val, const LLSD& old_val) - { - callback(); - }); -} - -void settings_setup_listeners() -{ - setting_setup_signal_listener(gSavedSettings, "FirstPersonAvatarVisible", handleRenderAvatarMouselookChanged); - setting_setup_signal_listener(gSavedSettings, "RenderFarClip", handleRenderFarClipChanged); - setting_setup_signal_listener(gSavedSettings, "RenderTerrainDetail", handleTerrainDetailChanged); - setting_setup_signal_listener(gSavedSettings, "OctreeStaticObjectSizeFactor", handleRepartition); - setting_setup_signal_listener(gSavedSettings, "OctreeDistanceFactor", handleRepartition); - setting_setup_signal_listener(gSavedSettings, "OctreeMaxNodeCapacity", handleRepartition); - setting_setup_signal_listener(gSavedSettings, "OctreeAlphaDistanceFactor", handleRepartition); - setting_setup_signal_listener(gSavedSettings, "OctreeAttachmentSizeFactor", handleRepartition); - setting_setup_signal_listener(gSavedSettings, "RenderMaxTextureIndex", handleSetShaderChanged); - setting_setup_signal_listener(gSavedSettings, "RenderUIBuffer", handleWindowResized); - setting_setup_signal_listener(gSavedSettings, "RenderDepthOfField", handleReleaseGLBufferChanged); - setting_setup_signal_listener(gSavedSettings, "RenderFSAASamples", handleReleaseGLBufferChanged); - setting_setup_signal_listener(gSavedSettings, "RenderPostProcessingHDR", handleReleaseGLBufferChanged); - setting_setup_signal_listener(gSavedSettings, "RenderSpecularResX", handleLUTBufferChanged); - setting_setup_signal_listener(gSavedSettings, "RenderSpecularResY", handleLUTBufferChanged); - setting_setup_signal_listener(gSavedSettings, "RenderSpecularExponent", handleLUTBufferChanged); - setting_setup_signal_listener(gSavedSettings, "RenderAnisotropic", handleAnisotropicChanged); - setting_setup_signal_listener(gSavedSettings, "RenderShadowResolutionScale", handleShadowsResized); - setting_setup_signal_listener(gSavedSettings, "RenderGlow", handleReleaseGLBufferChanged); - setting_setup_signal_listener(gSavedSettings, "RenderGlow", handleSetShaderChanged); - setting_setup_signal_listener(gSavedSettings, "RenderGlowResolutionPow", handleReleaseGLBufferChanged); - setting_setup_signal_listener(gSavedSettings, "RenderGlowHDR", handleReleaseGLBufferChanged); - setting_setup_signal_listener(gSavedSettings, "RenderGlowNoise", handleSetShaderChanged); - setting_setup_signal_listener(gSavedSettings, "RenderGammaFull", handleSetShaderChanged); - setting_setup_signal_listener(gSavedSettings, "RenderVolumeLODFactor", handleVolumeLODChanged); - setting_setup_signal_listener(gSavedSettings, "RenderAvatarComplexityMode", handleUserImpostorByDistEnabledChanged); - setting_setup_signal_listener(gSavedSettings, "RenderAvatarLODFactor", handleAvatarLODChanged); - setting_setup_signal_listener(gSavedSettings, "RenderAvatarPhysicsLODFactor", handleAvatarPhysicsLODChanged); - setting_setup_signal_listener(gSavedSettings, "RenderTerrainLODFactor", handleTerrainLODChanged); - setting_setup_signal_listener(gSavedSettings, "RenderTreeLODFactor", handleTreeLODChanged); - setting_setup_signal_listener(gSavedSettings, "RenderFlexTimeFactor", handleFlexLODChanged); - setting_setup_signal_listener(gSavedSettings, "RenderGamma", handleGammaChanged); - setting_setup_signal_listener(gSavedSettings, "RenderFogRatio", handleFogRatioChanged); - setting_setup_signal_listener(gSavedSettings, "RenderMaxPartCount", handleMaxPartCountChanged); - setting_setup_signal_listener(gSavedSettings, "RenderDynamicLOD", handleRenderDynamicLODChanged); - setting_setup_signal_listener(gSavedSettings, "RenderVSyncEnable", handleVSyncChanged); - setting_setup_signal_listener(gSavedSettings, "RenderDeferredNoise", handleReleaseGLBufferChanged); - setting_setup_signal_listener(gSavedSettings, "RenderDebugPipeline", handleRenderDebugPipelineChanged); - setting_setup_signal_listener(gSavedSettings, "RenderResolutionDivisor", handleRenderResolutionDivisorChanged); - setting_setup_signal_listener(gSavedSettings, "RenderReflectionProbeLevel", handleReflectionProbeDetailChanged); - setting_setup_signal_listener(gSavedSettings, "RenderReflectionProbeDetail", handleReflectionProbeDetailChanged); - setting_setup_signal_listener(gSavedSettings, "RenderReflectionsEnabled", handleReflectionProbeDetailChanged); - setting_setup_signal_listener(gSavedSettings, "RenderScreenSpaceReflections", handleReflectionProbeDetailChanged); - setting_setup_signal_listener(gSavedSettings, "RenderShadowDetail", handleSetShaderChanged); - setting_setup_signal_listener(gSavedSettings, "RenderDeferredSSAO", handleSetShaderChanged); - setting_setup_signal_listener(gSavedSettings, "RenderPerformanceTest", handleRenderPerfTestChanged); - setting_setup_signal_listener(gSavedSettings, "ChatFontSize", handleChatFontSizeChanged); - setting_setup_signal_listener(gSavedSettings, "ConsoleMaxLines", handleConsoleMaxLinesChanged); - setting_setup_signal_listener(gSavedSettings, "UploadBakedTexOld", handleUploadBakedTexOldChanged); - setting_setup_signal_listener(gSavedSettings, "UseOcclusion", handleUseOcclusionChanged); - setting_setup_signal_listener(gSavedSettings, "AudioLevelMaster", handleAudioVolumeChanged); - setting_setup_signal_listener(gSavedSettings, "AudioLevelSFX", handleAudioVolumeChanged); - setting_setup_signal_listener(gSavedSettings, "AudioLevelUI", handleAudioVolumeChanged); - setting_setup_signal_listener(gSavedSettings, "AudioLevelAmbient", handleAudioVolumeChanged); - setting_setup_signal_listener(gSavedSettings, "AudioLevelMusic", handleAudioVolumeChanged); - setting_setup_signal_listener(gSavedSettings, "AudioLevelMedia", handleAudioVolumeChanged); - setting_setup_signal_listener(gSavedSettings, "AudioLevelVoice", handleAudioVolumeChanged); - setting_setup_signal_listener(gSavedSettings, "MuteAudio", handleAudioVolumeChanged); - setting_setup_signal_listener(gSavedSettings, "MuteMusic", handleAudioVolumeChanged); - setting_setup_signal_listener(gSavedSettings, "MuteMedia", handleAudioVolumeChanged); - setting_setup_signal_listener(gSavedSettings, "MuteVoice", handleAudioVolumeChanged); - setting_setup_signal_listener(gSavedSettings, "MuteAmbient", handleAudioVolumeChanged); - setting_setup_signal_listener(gSavedSettings, "MuteUI", handleAudioVolumeChanged); - setting_setup_signal_listener(gSavedSettings, "WLSkyDetail", handleWLSkyDetailChanged); - setting_setup_signal_listener(gSavedSettings, "JoystickAxis0", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "JoystickAxis1", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "JoystickAxis2", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "JoystickAxis3", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "JoystickAxis4", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "JoystickAxis5", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "JoystickAxis6", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale0", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale1", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale2", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale3", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale4", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale5", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale6", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone0", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone1", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone2", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone3", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone4", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone5", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone6", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "AvatarAxisScale0", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "AvatarAxisScale1", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "AvatarAxisScale2", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "AvatarAxisScale3", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "AvatarAxisScale4", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "AvatarAxisScale5", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "AvatarAxisDeadZone0", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "AvatarAxisDeadZone1", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "AvatarAxisDeadZone2", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "AvatarAxisDeadZone3", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "AvatarAxisDeadZone4", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "AvatarAxisDeadZone5", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "BuildAxisScale0", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "BuildAxisScale1", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "BuildAxisScale2", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "BuildAxisScale3", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "BuildAxisScale4", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "BuildAxisScale5", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "BuildAxisDeadZone0", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "BuildAxisDeadZone1", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "BuildAxisDeadZone2", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "BuildAxisDeadZone3", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "BuildAxisDeadZone4", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "BuildAxisDeadZone5", handleJoystickChanged); - setting_setup_signal_listener(gSavedSettings, "DebugViews", handleDebugViewsChanged); - setting_setup_signal_listener(gSavedSettings, "UserLogFile", handleLogFileChanged); - setting_setup_signal_listener(gSavedSettings, "RenderHideGroupTitle", handleHideGroupTitleChanged); - setting_setup_signal_listener(gSavedSettings, "HighResSnapshot", handleHighResSnapshotChanged); - setting_setup_signal_listener(gSavedSettings, "EnableVoiceChat", handleVoiceClientPrefsChanged); - setting_setup_signal_listener(gSavedSettings, "PTTCurrentlyEnabled", handleVoiceClientPrefsChanged); - setting_setup_signal_listener(gSavedSettings, "PushToTalkButton", handleVoiceClientPrefsChanged); - setting_setup_signal_listener(gSavedSettings, "PushToTalkToggle", handleVoiceClientPrefsChanged); - setting_setup_signal_listener(gSavedSettings, "VoiceEarLocation", handleVoiceClientPrefsChanged); - setting_setup_signal_listener(gSavedSettings, "VoiceInputAudioDevice", handleVoiceClientPrefsChanged); - setting_setup_signal_listener(gSavedSettings, "VoiceOutputAudioDevice", handleVoiceClientPrefsChanged); - setting_setup_signal_listener(gSavedSettings, "AudioLevelMic", handleVoiceClientPrefsChanged); - setting_setup_signal_listener(gSavedSettings, "LipSyncEnabled", handleVoiceClientPrefsChanged); - setting_setup_signal_listener(gSavedSettings, "VelocityInterpolate", handleVelocityInterpolate); - setting_setup_signal_listener(gSavedSettings, "QAMode", show_debug_menus); - setting_setup_signal_listener(gSavedSettings, "UseDebugMenus", show_debug_menus); - setting_setup_signal_listener(gSavedSettings, "AgentPause", toggle_agent_pause); - setting_setup_signal_listener(gSavedSettings, "ShowNavbarNavigationPanel", toggle_show_navigation_panel); - setting_setup_signal_listener(gSavedSettings, "ShowMiniLocationPanel", toggle_show_mini_location_panel); - setting_setup_signal_listener(gSavedSettings, "ShowObjectRenderingCost", toggle_show_object_render_cost); - setting_setup_signal_listener(gSavedSettings, "ForceShowGrid", handleForceShowGrid); - setting_setup_signal_listener(gSavedSettings, "RenderTransparentWater", handleRenderTransparentWaterChanged); - setting_setup_signal_listener(gSavedSettings, "SpellCheck", handleSpellCheckChanged); - setting_setup_signal_listener(gSavedSettings, "SpellCheckDictionary", handleSpellCheckChanged); - setting_setup_signal_listener(gSavedSettings, "LoginLocation", handleLoginLocationChanged); - setting_setup_signal_listener(gSavedSettings, "DebugAvatarJoints", handleDebugAvatarJointsChanged); - - setting_setup_signal_listener(gSavedSettings, "TargetFPS", handleTargetFPSChanged); - setting_setup_signal_listener(gSavedSettings, "AutoTuneFPS", handleAutoTuneFPSChanged); - setting_setup_signal_listener(gSavedSettings, "AutoTuneLock", handleAutoTuneLockChanged); - setting_setup_signal_listener(gSavedSettings, "RenderAvatarMaxART", handleRenderAvatarMaxARTChanged); - setting_setup_signal_listener(gSavedSettings, "PerfStatsCaptureEnabled", handlePerformanceStatsEnabledChanged); - setting_setup_signal_listener(gSavedSettings, "AutoTuneRenderFarClipTarget", handleUserTargetDrawDistanceChanged); - setting_setup_signal_listener(gSavedSettings, "AutoTuneRenderFarClipMin", handleUserMinDrawDistanceChanged); - setting_setup_signal_listener(gSavedSettings, "AutoTuneImpostorFarAwayDistance", handleUserImpostorDistanceChanged); - setting_setup_signal_listener(gSavedSettings, "AutoTuneImpostorByDistEnabled", handleUserImpostorByDistEnabledChanged); - setting_setup_signal_listener(gSavedSettings, "TuningFPSStrategy", handleFPSTuningStrategyChanged); - - setting_setup_signal_listener(gSavedPerAccountSettings, "AvatarHoverOffsetZ", handleAvatarHoverOffsetChanged); -} - -#if TEST_CACHED_CONTROL - -#define DECL_LLCC(T, V) static LLCachedControl mySetting_##T("TestCachedControl"#T, V) -DECL_LLCC(U32, (U32)666); -DECL_LLCC(S32, (S32)-666); -DECL_LLCC(F32, (F32)-666.666); -DECL_LLCC(bool, true); -DECL_LLCC(bool, false); -static LLCachedControl mySetting_string("TestCachedControlstring", "Default String Value"); -DECL_LLCC(LLVector3, LLVector3(1.0f, 2.0f, 3.0f)); -DECL_LLCC(LLVector3d, LLVector3d(6.0f, 5.0f, 4.0f)); -DECL_LLCC(LLRect, LLRect(0, 0, 100, 500)); -DECL_LLCC(LLColor4, LLColor4(0.0f, 0.5f, 1.0f)); -DECL_LLCC(LLColor3, LLColor3(1.0f, 0.f, 0.5f)); -DECL_LLCC(LLColor4U, LLColor4U(255, 200, 100, 255)); - -LLSD test_llsd = LLSD()["testing1"] = LLSD()["testing2"]; -DECL_LLCC(LLSD, test_llsd); - -void test_cached_control() -{ -#define do { TEST_LLCC(T, V) if((T)mySetting_##T != V) LL_ERRS() << "Fail "#T << LL_ENDL; } while(0) - TEST_LLCC(U32, 666); - TEST_LLCC(S32, (S32)-666); - TEST_LLCC(F32, (F32)-666.666); - TEST_LLCC(bool, true); - TEST_LLCC(bool, false); - if((std::string)mySetting_string != "Default String Value") LL_ERRS() << "Fail string" << LL_ENDL; - TEST_LLCC(LLVector3, LLVector3(1.0f, 2.0f, 3.0f)); - TEST_LLCC(LLVector3d, LLVector3d(6.0f, 5.0f, 4.0f)); - TEST_LLCC(LLRect, LLRect(0, 0, 100, 500)); - TEST_LLCC(LLColor4, LLColor4(0.0f, 0.5f, 1.0f)); - TEST_LLCC(LLColor3, LLColor3(1.0f, 0.f, 0.5f)); - TEST_LLCC(LLColor4U, LLColor4U(255, 200, 100, 255)); -//There's no LLSD comparsion for LLCC yet. TEST_LLCC(LLSD, test_llsd); -} -#endif // TEST_CACHED_CONTROL - +/** + * @file llviewercontrol.cpp + * @brief Viewer configuration + * @author Richard Nelson + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llviewercontrol.h" + +// Library includes +#include "llwindow.h" // getGamma() + +// For Listeners +#include "llaudioengine.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llconsole.h" +#include "lldrawpoolbump.h" +#include "lldrawpoolterrain.h" +#include "llflexibleobject.h" +#include "llfeaturemanager.h" +#include "llviewershadermgr.h" + +#include "llsky.h" +#include "llvieweraudio.h" +#include "llviewermenu.h" +#include "llviewertexturelist.h" +#include "llviewerthrottle.h" +#include "llviewerwindow.h" +#include "llvoavatarself.h" +#include "llvoiceclient.h" +#include "llvotree.h" +#include "llvovolume.h" +#include "llworld.h" +#include "pipeline.h" +#include "llviewerjoystick.h" +#include "llviewerobjectlist.h" +#include "llviewerparcelmgr.h" +#include "llparcel.h" +#include "llkeyboard.h" +#include "llerrorcontrol.h" +#include "llappviewer.h" +#include "llvosurfacepatch.h" +#include "llvowlsky.h" +#include "llrender.h" +#include "llnavigationbar.h" +#include "llnotificationsutil.h" +#include "llfloatertools.h" +#include "llpaneloutfitsinventory.h" +#include "llpanellogin.h" +#include "llpaneltopinfobar.h" +#include "llspellcheck.h" +#include "llslurl.h" +#include "llstartup.h" +#include "llperfstats.h" + +// Third party library includes +#include + +#ifdef TOGGLE_HACKED_GODLIKE_VIEWER +bool gHackGodmode = false; +#endif + +// Should you contemplate changing the name "Global", please first grep for +// that string literal. There are at least a couple other places in the C++ +// code that assume the LLControlGroup named "Global" is gSavedSettings. +LLControlGroup gSavedSettings("Global"); // saved at end of session +LLControlGroup gSavedPerAccountSettings("PerAccount"); // saved at end of session +LLControlGroup gCrashSettings("CrashSettings"); // saved at end of session +LLControlGroup gWarningSettings("Warnings"); // persists ignored dialogs/warnings + +std::string gLastRunVersion; + +extern bool gResizeScreenTexture; +extern bool gResizeShadowTexture; +extern bool gDebugGL; +//////////////////////////////////////////////////////////////////////////// +// Listeners + +static bool handleRenderAvatarMouselookChanged(const LLSD& newvalue) +{ + LLVOAvatar::sVisibleInFirstPerson = newvalue.asBoolean(); + return true; +} + +static bool handleRenderFarClipChanged(const LLSD& newvalue) +{ + if (LLStartUp::getStartupState() >= STATE_STARTED) + { + F32 draw_distance = (F32)newvalue.asReal(); + gAgentCamera.mDrawDistance = draw_distance; + LLWorld::getInstance()->setLandFarClip(draw_distance); + return true; + } + return false; +} + +static bool handleTerrainDetailChanged(const LLSD& newvalue) +{ + LLDrawPoolTerrain::sDetailMode = newvalue.asInteger(); + return true; +} + + +static bool handleDebugAvatarJointsChanged(const LLSD& newvalue) +{ + std::string new_string = newvalue.asString(); + LLJoint::setDebugJointNames(new_string); + return true; +} + +static bool handleAvatarHoverOffsetChanged(const LLSD& newvalue) +{ + if (isAgentAvatarValid()) + { + gAgentAvatarp->setHoverIfRegionEnabled(); + } + return true; +} + + +static bool handleSetShaderChanged(const LLSD& newvalue) +{ + // changing shader level may invalidate existing cached bump maps, as the shader type determines the format of the bump map it expects - clear and repopulate the bump cache + gBumpImageList.destroyGL(); + gBumpImageList.restoreGL(); + + if (gPipeline.isInit()) + { + // ALM depends onto atmospheric shaders, state might have changed + LLPipeline::refreshCachedSettings(); + } + + // else, leave terrain detail as is + LLViewerShaderMgr::instance()->setShaders(); + return true; +} + +static bool handleRenderPerfTestChanged(const LLSD& newvalue) +{ + bool status = !newvalue.asBoolean(); + if (!status) + { + gPipeline.clearRenderTypeMask(LLPipeline::RENDER_TYPE_WL_SKY, + LLPipeline::RENDER_TYPE_TERRAIN, + LLPipeline::RENDER_TYPE_GRASS, + LLPipeline::RENDER_TYPE_TREE, + LLPipeline::RENDER_TYPE_WATER, + LLPipeline::RENDER_TYPE_PASS_GRASS, + LLPipeline::RENDER_TYPE_HUD, + LLPipeline::RENDER_TYPE_CLOUDS, + LLPipeline::RENDER_TYPE_HUD_PARTICLES, + LLPipeline::END_RENDER_TYPES); + gPipeline.setRenderDebugFeatureControl(LLPipeline::RENDER_DEBUG_FEATURE_UI, false); + } + else + { + gPipeline.setRenderTypeMask(LLPipeline::RENDER_TYPE_WL_SKY, + LLPipeline::RENDER_TYPE_TERRAIN, + LLPipeline::RENDER_TYPE_GRASS, + LLPipeline::RENDER_TYPE_TREE, + LLPipeline::RENDER_TYPE_WATER, + LLPipeline::RENDER_TYPE_PASS_GRASS, + LLPipeline::RENDER_TYPE_HUD, + LLPipeline::RENDER_TYPE_CLOUDS, + LLPipeline::RENDER_TYPE_HUD_PARTICLES, + LLPipeline::END_RENDER_TYPES); + gPipeline.setRenderDebugFeatureControl(LLPipeline::RENDER_DEBUG_FEATURE_UI, true); + } + + return true; +} + +bool handleRenderTransparentWaterChanged(const LLSD& newvalue) +{ + if (gPipeline.isInit()) + { + gPipeline.updateRenderTransparentWater(); + gPipeline.releaseGLBuffers(); + gPipeline.createGLBuffers(); + LLViewerShaderMgr::instance()->setShaders(); + } + LLWorld::getInstance()->updateWaterObjects(); + return true; +} + + +static bool handleShadowsResized(const LLSD& newvalue) +{ + gPipeline.requestResizeShadowTexture(); + return true; +} + +static bool handleWindowResized(const LLSD& newvalue) +{ + gPipeline.requestResizeScreenTexture(); + return true; +} + +static bool handleReleaseGLBufferChanged(const LLSD& newvalue) +{ + if (gPipeline.isInit()) + { + gPipeline.releaseGLBuffers(); + gPipeline.createGLBuffers(); + } + return true; +} + +static bool handleLUTBufferChanged(const LLSD& newvalue) +{ + if (gPipeline.isInit()) + { + gPipeline.releaseLUTBuffers(); + gPipeline.createLUTBuffers(); + } + return true; +} + +static bool handleAnisotropicChanged(const LLSD& newvalue) +{ + LLImageGL::sGlobalUseAnisotropic = newvalue.asBoolean(); + LLImageGL::dirtyTexOptions(); + return true; +} + +static bool handleVSyncChanged(const LLSD& newvalue) +{ + LLPerfStats::tunables.vsyncEnabled = newvalue.asBoolean(); + gViewerWindow->getWindow()->toggleVSync(newvalue.asBoolean()); + + if (newvalue.asBoolean()) + { + U32 current_target = gSavedSettings.getU32("TargetFPS"); + gSavedSettings.setU32("TargetFPS", std::min((U32)gViewerWindow->getWindow()->getRefreshRate(), current_target)); + } + + return true; +} + +static bool handleVolumeLODChanged(const LLSD& newvalue) +{ + LLVOVolume::sLODFactor = llclamp((F32) newvalue.asReal(), 0.01f, MAX_LOD_FACTOR); + LLVOVolume::sDistanceFactor = 1.f-LLVOVolume::sLODFactor * 0.1f; + return true; +} + +static bool handleAvatarLODChanged(const LLSD& newvalue) +{ + LLVOAvatar::sLODFactor = llclamp((F32) newvalue.asReal(), 0.f, MAX_AVATAR_LOD_FACTOR); + return true; +} + +static bool handleAvatarPhysicsLODChanged(const LLSD& newvalue) +{ + LLVOAvatar::sPhysicsLODFactor = llclamp((F32) newvalue.asReal(), 0.f, MAX_AVATAR_LOD_FACTOR); + return true; +} + +static bool handleTerrainLODChanged(const LLSD& newvalue) +{ + LLVOSurfacePatch::sLODFactor = (F32)newvalue.asReal(); + //sqaure lod factor to get exponential range of [0,4] and keep + //a value of 1 in the middle of the detail slider for consistency + //with other detail sliders (see panel_preferences_graphics1.xml) + LLVOSurfacePatch::sLODFactor *= LLVOSurfacePatch::sLODFactor; + return true; +} + +static bool handleTreeLODChanged(const LLSD& newvalue) +{ + LLVOTree::sTreeFactor = (F32) newvalue.asReal(); + return true; +} + +static bool handleFlexLODChanged(const LLSD& newvalue) +{ + LLVolumeImplFlexible::sUpdateFactor = (F32) newvalue.asReal(); + return true; +} + +static bool handleGammaChanged(const LLSD& newvalue) +{ + F32 gamma = (F32) newvalue.asReal(); + if (gamma == 0.0f) + { + gamma = 1.0f; // restore normal gamma + } + if (gViewerWindow && gViewerWindow->getWindow() && gamma != gViewerWindow->getWindow()->getGamma()) + { + // Only save it if it's changed + if (!gViewerWindow->getWindow()->setGamma(gamma)) + { + LL_WARNS() << "setGamma failed!" << LL_ENDL; + } + } + + return true; +} + +const F32 MAX_USER_FOG_RATIO = 10.f; +const F32 MIN_USER_FOG_RATIO = 0.5f; + +static bool handleFogRatioChanged(const LLSD& newvalue) +{ + F32 fog_ratio = llmax(MIN_USER_FOG_RATIO, llmin((F32) newvalue.asReal(), MAX_USER_FOG_RATIO)); + gSky.setFogRatio(fog_ratio); + return true; +} + +static bool handleMaxPartCountChanged(const LLSD& newvalue) +{ + LLViewerPartSim::setMaxPartCount(newvalue.asInteger()); + return true; +} + +static bool handleChatFontSizeChanged(const LLSD& newvalue) +{ + if(gConsole) + { + gConsole->setFontSize(newvalue.asInteger()); + } + return true; +} + +static bool handleConsoleMaxLinesChanged(const LLSD& newvalue) +{ + if(gConsole) + { + gConsole->setMaxLines(newvalue.asInteger()); + } + return true; +} + +static void handleAudioVolumeChanged(const LLSD& newvalue) +{ + audio_update_volume(true); +} + +static bool handleJoystickChanged(const LLSD& newvalue) +{ + LLViewerJoystick::getInstance()->setCameraNeedsUpdate(true); + return true; +} + +static bool handleUseOcclusionChanged(const LLSD& newvalue) +{ + LLPipeline::sUseOcclusion = (newvalue.asBoolean() + && LLFeatureManager::getInstance()->isFeatureAvailable("UseOcclusion") && !gUseWireframe) ? 2 : 0; + return true; +} + +static bool handleUploadBakedTexOldChanged(const LLSD& newvalue) +{ + LLPipeline::sForceOldBakedUpload = newvalue.asBoolean(); + return true; +} + + +static bool handleWLSkyDetailChanged(const LLSD&) +{ + if (gSky.mVOWLSkyp.notNull()) + { + gSky.mVOWLSkyp->updateGeometry(gSky.mVOWLSkyp->mDrawable); + } + return true; +} + +static bool handleRepartition(const LLSD&) +{ + if (gPipeline.isInit()) + { + gOctreeMaxCapacity = gSavedSettings.getU32("OctreeMaxNodeCapacity"); + gOctreeMinSize = gSavedSettings.getF32("OctreeMinimumNodeSize"); + gObjectList.repartitionObjects(); + } + return true; +} + +static bool handleRenderDynamicLODChanged(const LLSD& newvalue) +{ + LLPipeline::sDynamicLOD = newvalue.asBoolean(); + return true; +} + +static bool handleReflectionProbeDetailChanged(const LLSD& newvalue) +{ + if (gPipeline.isInit()) + { + LLPipeline::refreshCachedSettings(); + gPipeline.releaseGLBuffers(); + gPipeline.createGLBuffers(); + LLViewerShaderMgr::instance()->setShaders(); + gPipeline.mReflectionMapManager.reset(); + } + return true; +} + +static bool handleRenderDebugPipelineChanged(const LLSD& newvalue) +{ + gDebugPipeline = newvalue.asBoolean(); + return true; +} + +static bool handleRenderResolutionDivisorChanged(const LLSD&) +{ + gResizeScreenTexture = true; + return true; +} + +static bool handleDebugViewsChanged(const LLSD& newvalue) +{ + LLView::sDebugRects = newvalue.asBoolean(); + return true; +} + +static bool handleLogFileChanged(const LLSD& newvalue) +{ + std::string log_filename = newvalue.asString(); + LLFile::remove(log_filename); + LLError::logToFile(log_filename); + LL_INFOS() << "Logging switched to " << log_filename << LL_ENDL; + return true; +} + +bool handleHideGroupTitleChanged(const LLSD& newvalue) +{ + gAgent.setHideGroupTitle(newvalue); + return true; +} + +bool handleEffectColorChanged(const LLSD& newvalue) +{ + gAgent.setEffectColor(LLColor4(newvalue)); + return true; +} + +bool handleHighResSnapshotChanged(const LLSD& newvalue) +{ + // High Res Snapshot active, must uncheck RenderUIInSnapshot + if (newvalue.asBoolean()) + { + gSavedSettings.setBOOL( "RenderUIInSnapshot", false); + } + return true; +} + +bool handleVoiceClientPrefsChanged(const LLSD& newvalue) +{ + if (LLVoiceClient::instanceExists()) + { + LLVoiceClient::getInstance()->updateSettings(); + } + return true; +} + +bool handleVelocityInterpolate(const LLSD& newvalue) +{ + LLMessageSystem* msg = gMessageSystem; + if ( newvalue.asBoolean() ) + { + msg->newMessageFast(_PREHASH_VelocityInterpolateOn); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + gAgent.sendReliableMessage(); + LL_INFOS() << "Velocity Interpolation On" << LL_ENDL; + } + else + { + msg->newMessageFast(_PREHASH_VelocityInterpolateOff); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + gAgent.sendReliableMessage(); + LL_INFOS() << "Velocity Interpolation Off" << LL_ENDL; + } + return true; +} + +bool handleForceShowGrid(const LLSD& newvalue) +{ + LLPanelLogin::updateLocationSelectorsVisibility(); + return true; +} + +bool handleLoginLocationChanged() +{ + /* + * This connects the default preference setting to the state of the login + * panel if it is displayed; if you open the preferences panel before + * logging in, and change the default login location there, the login + * panel immediately changes to match your new preference. + */ + std::string new_login_location = gSavedSettings.getString("LoginLocation"); + LL_DEBUGS("AppInit")< dict_list; + std::string dict_setting = gSavedSettings.getString("SpellCheckDictionary"); + boost::split(dict_list, dict_setting, boost::is_any_of(std::string(","))); + if (!dict_list.empty()) + { + LLSpellChecker::setUseSpellCheck(dict_list.front()); + dict_list.pop_front(); + LLSpellChecker::instance().setSecondaryDictionaries(dict_list); + return true; + } + } + LLSpellChecker::setUseSpellCheck(LLStringUtil::null); + return true; +} + +bool toggle_agent_pause(const LLSD& newvalue) +{ + if ( newvalue.asBoolean() ) + { + send_agent_pause(); + } + else + { + send_agent_resume(); + } + return true; +} + +bool toggle_show_navigation_panel(const LLSD& newvalue) +{ + bool value = newvalue.asBoolean(); + + LLNavigationBar::getInstance()->setVisible(value); + gSavedSettings.setBOOL("ShowMiniLocationPanel", !value); + gViewerWindow->reshapeStatusBarContainer(); + return true; +} + +bool toggle_show_mini_location_panel(const LLSD& newvalue) +{ + bool value = newvalue.asBoolean(); + + LLPanelTopInfoBar::getInstance()->setVisible(value); + gSavedSettings.setBOOL("ShowNavbarNavigationPanel", !value); + + return true; +} + +bool toggle_show_object_render_cost(const LLSD& newvalue) +{ + LLFloaterTools::sShowObjectCost = newvalue.asBoolean(); + return true; +} + +void handleTargetFPSChanged(const LLSD& newValue) +{ + const auto targetFPS = gSavedSettings.getU32("TargetFPS"); + + U32 frame_rate_limit = gViewerWindow->getWindow()->getRefreshRate(); + if(LLPerfStats::tunables.vsyncEnabled && (targetFPS > frame_rate_limit)) + { + gSavedSettings.setU32("TargetFPS", std::min(frame_rate_limit, targetFPS)); + } + else + { + LLPerfStats::tunables.userTargetFPS = targetFPS; + } +} + +void handleAutoTuneLockChanged(const LLSD& newValue) +{ + const auto newval = gSavedSettings.getBOOL("AutoTuneLock"); + LLPerfStats::tunables.userAutoTuneLock = newval; + + gSavedSettings.setBOOL("AutoTuneFPS", newval); +} + +void handleAutoTuneFPSChanged(const LLSD& newValue) +{ + const auto newval = gSavedSettings.getBOOL("AutoTuneFPS"); + LLPerfStats::tunables.userAutoTuneEnabled = newval; + if(newval && LLPerfStats::renderAvatarMaxART_ns == 0) // If we've enabled autotune we override "unlimited" to max + { + gSavedSettings.setF32("RenderAvatarMaxART",log10(LLPerfStats::ART_UNLIMITED_NANOS-1000));//triggers callback to update static var + } +} + +void handleRenderAvatarMaxARTChanged(const LLSD& newValue) +{ + LLPerfStats::tunables.updateRenderCostLimitFromSettings(); +} + +void handleUserTargetDrawDistanceChanged(const LLSD& newValue) +{ + const auto newval = gSavedSettings.getF32("AutoTuneRenderFarClipTarget"); + LLPerfStats::tunables.userTargetDrawDistance = newval; +} + +void handleUserMinDrawDistanceChanged(const LLSD &newValue) +{ + const auto newval = gSavedSettings.getF32("AutoTuneRenderFarClipMin"); + LLPerfStats::tunables.userMinDrawDistance = newval; +} + +void handleUserTargetReflectionsChanged(const LLSD& newValue) +{ + const auto newval = gSavedSettings.getS32("UserTargetReflections"); + LLPerfStats::tunables.userTargetReflections = newval; +} + +void handlePerformanceStatsEnabledChanged(const LLSD& newValue) +{ + const auto newval = gSavedSettings.getBOOL("PerfStatsCaptureEnabled"); + LLPerfStats::StatsRecorder::setEnabled(newval); +} +void handleUserImpostorByDistEnabledChanged(const LLSD& newValue) +{ + bool auto_tune_newval = false; + S32 mode = gSavedSettings.getS32("RenderAvatarComplexityMode"); + if (mode != LLVOAvatar::AV_RENDER_ONLY_SHOW_FRIENDS) + { + auto_tune_newval = gSavedSettings.getBOOL("AutoTuneImpostorByDistEnabled"); + } + LLPerfStats::tunables.userImpostorDistanceTuningEnabled = auto_tune_newval; +} +void handleUserImpostorDistanceChanged(const LLSD& newValue) +{ + const auto newval = gSavedSettings.getF32("AutoTuneImpostorFarAwayDistance"); + LLPerfStats::tunables.userImpostorDistance = newval; +} +void handleFPSTuningStrategyChanged(const LLSD& newValue) +{ + const auto newval = gSavedSettings.getU32("TuningFPSStrategy"); + LLPerfStats::tunables.userFPSTuningStrategy = newval; +} +//////////////////////////////////////////////////////////////////////////// + +LLPointer setting_get_control(LLControlGroup& group, const std::string& setting) +{ + LLPointer cntrl_ptr = group.getControl(setting); + if (cntrl_ptr.isNull()) + { + LL_ERRS() << "Unable to set up setting listener for " << setting + << ". Please reinstall viewer from https ://secondlife.com/support/downloads/ and contact https://support.secondlife.com if issue persists after reinstall." + << LL_ENDL; + } + return cntrl_ptr; +} + +void setting_setup_signal_listener(LLControlGroup& group, const std::string& setting, std::function callback) +{ + setting_get_control(group, setting)->getSignal()->connect([callback](LLControlVariable* control, const LLSD& new_val, const LLSD& old_val) + { + callback(new_val); + }); +} + +void setting_setup_signal_listener(LLControlGroup& group, const std::string& setting, std::function callback) +{ + setting_get_control(group, setting)->getSignal()->connect([callback](LLControlVariable* control, const LLSD& new_val, const LLSD& old_val) + { + callback(); + }); +} + +void settings_setup_listeners() +{ + setting_setup_signal_listener(gSavedSettings, "FirstPersonAvatarVisible", handleRenderAvatarMouselookChanged); + setting_setup_signal_listener(gSavedSettings, "RenderFarClip", handleRenderFarClipChanged); + setting_setup_signal_listener(gSavedSettings, "RenderTerrainDetail", handleTerrainDetailChanged); + setting_setup_signal_listener(gSavedSettings, "OctreeStaticObjectSizeFactor", handleRepartition); + setting_setup_signal_listener(gSavedSettings, "OctreeDistanceFactor", handleRepartition); + setting_setup_signal_listener(gSavedSettings, "OctreeMaxNodeCapacity", handleRepartition); + setting_setup_signal_listener(gSavedSettings, "OctreeAlphaDistanceFactor", handleRepartition); + setting_setup_signal_listener(gSavedSettings, "OctreeAttachmentSizeFactor", handleRepartition); + setting_setup_signal_listener(gSavedSettings, "RenderMaxTextureIndex", handleSetShaderChanged); + setting_setup_signal_listener(gSavedSettings, "RenderUIBuffer", handleWindowResized); + setting_setup_signal_listener(gSavedSettings, "RenderDepthOfField", handleReleaseGLBufferChanged); + setting_setup_signal_listener(gSavedSettings, "RenderFSAASamples", handleReleaseGLBufferChanged); + setting_setup_signal_listener(gSavedSettings, "RenderPostProcessingHDR", handleReleaseGLBufferChanged); + setting_setup_signal_listener(gSavedSettings, "RenderSpecularResX", handleLUTBufferChanged); + setting_setup_signal_listener(gSavedSettings, "RenderSpecularResY", handleLUTBufferChanged); + setting_setup_signal_listener(gSavedSettings, "RenderSpecularExponent", handleLUTBufferChanged); + setting_setup_signal_listener(gSavedSettings, "RenderAnisotropic", handleAnisotropicChanged); + setting_setup_signal_listener(gSavedSettings, "RenderShadowResolutionScale", handleShadowsResized); + setting_setup_signal_listener(gSavedSettings, "RenderGlow", handleReleaseGLBufferChanged); + setting_setup_signal_listener(gSavedSettings, "RenderGlow", handleSetShaderChanged); + setting_setup_signal_listener(gSavedSettings, "RenderGlowResolutionPow", handleReleaseGLBufferChanged); + setting_setup_signal_listener(gSavedSettings, "RenderGlowHDR", handleReleaseGLBufferChanged); + setting_setup_signal_listener(gSavedSettings, "RenderGlowNoise", handleSetShaderChanged); + setting_setup_signal_listener(gSavedSettings, "RenderGammaFull", handleSetShaderChanged); + setting_setup_signal_listener(gSavedSettings, "RenderVolumeLODFactor", handleVolumeLODChanged); + setting_setup_signal_listener(gSavedSettings, "RenderAvatarComplexityMode", handleUserImpostorByDistEnabledChanged); + setting_setup_signal_listener(gSavedSettings, "RenderAvatarLODFactor", handleAvatarLODChanged); + setting_setup_signal_listener(gSavedSettings, "RenderAvatarPhysicsLODFactor", handleAvatarPhysicsLODChanged); + setting_setup_signal_listener(gSavedSettings, "RenderTerrainLODFactor", handleTerrainLODChanged); + setting_setup_signal_listener(gSavedSettings, "RenderTreeLODFactor", handleTreeLODChanged); + setting_setup_signal_listener(gSavedSettings, "RenderFlexTimeFactor", handleFlexLODChanged); + setting_setup_signal_listener(gSavedSettings, "RenderGamma", handleGammaChanged); + setting_setup_signal_listener(gSavedSettings, "RenderFogRatio", handleFogRatioChanged); + setting_setup_signal_listener(gSavedSettings, "RenderMaxPartCount", handleMaxPartCountChanged); + setting_setup_signal_listener(gSavedSettings, "RenderDynamicLOD", handleRenderDynamicLODChanged); + setting_setup_signal_listener(gSavedSettings, "RenderVSyncEnable", handleVSyncChanged); + setting_setup_signal_listener(gSavedSettings, "RenderDeferredNoise", handleReleaseGLBufferChanged); + setting_setup_signal_listener(gSavedSettings, "RenderDebugPipeline", handleRenderDebugPipelineChanged); + setting_setup_signal_listener(gSavedSettings, "RenderResolutionDivisor", handleRenderResolutionDivisorChanged); + setting_setup_signal_listener(gSavedSettings, "RenderReflectionProbeLevel", handleReflectionProbeDetailChanged); + setting_setup_signal_listener(gSavedSettings, "RenderReflectionProbeDetail", handleReflectionProbeDetailChanged); + setting_setup_signal_listener(gSavedSettings, "RenderReflectionsEnabled", handleReflectionProbeDetailChanged); + setting_setup_signal_listener(gSavedSettings, "RenderScreenSpaceReflections", handleReflectionProbeDetailChanged); + setting_setup_signal_listener(gSavedSettings, "RenderShadowDetail", handleSetShaderChanged); + setting_setup_signal_listener(gSavedSettings, "RenderDeferredSSAO", handleSetShaderChanged); + setting_setup_signal_listener(gSavedSettings, "RenderPerformanceTest", handleRenderPerfTestChanged); + setting_setup_signal_listener(gSavedSettings, "ChatFontSize", handleChatFontSizeChanged); + setting_setup_signal_listener(gSavedSettings, "ConsoleMaxLines", handleConsoleMaxLinesChanged); + setting_setup_signal_listener(gSavedSettings, "UploadBakedTexOld", handleUploadBakedTexOldChanged); + setting_setup_signal_listener(gSavedSettings, "UseOcclusion", handleUseOcclusionChanged); + setting_setup_signal_listener(gSavedSettings, "AudioLevelMaster", handleAudioVolumeChanged); + setting_setup_signal_listener(gSavedSettings, "AudioLevelSFX", handleAudioVolumeChanged); + setting_setup_signal_listener(gSavedSettings, "AudioLevelUI", handleAudioVolumeChanged); + setting_setup_signal_listener(gSavedSettings, "AudioLevelAmbient", handleAudioVolumeChanged); + setting_setup_signal_listener(gSavedSettings, "AudioLevelMusic", handleAudioVolumeChanged); + setting_setup_signal_listener(gSavedSettings, "AudioLevelMedia", handleAudioVolumeChanged); + setting_setup_signal_listener(gSavedSettings, "AudioLevelVoice", handleAudioVolumeChanged); + setting_setup_signal_listener(gSavedSettings, "MuteAudio", handleAudioVolumeChanged); + setting_setup_signal_listener(gSavedSettings, "MuteMusic", handleAudioVolumeChanged); + setting_setup_signal_listener(gSavedSettings, "MuteMedia", handleAudioVolumeChanged); + setting_setup_signal_listener(gSavedSettings, "MuteVoice", handleAudioVolumeChanged); + setting_setup_signal_listener(gSavedSettings, "MuteAmbient", handleAudioVolumeChanged); + setting_setup_signal_listener(gSavedSettings, "MuteUI", handleAudioVolumeChanged); + setting_setup_signal_listener(gSavedSettings, "WLSkyDetail", handleWLSkyDetailChanged); + setting_setup_signal_listener(gSavedSettings, "JoystickAxis0", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "JoystickAxis1", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "JoystickAxis2", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "JoystickAxis3", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "JoystickAxis4", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "JoystickAxis5", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "JoystickAxis6", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale0", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale1", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale2", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale3", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale4", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale5", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "FlycamAxisScale6", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone0", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone1", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone2", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone3", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone4", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone5", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "FlycamAxisDeadZone6", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "AvatarAxisScale0", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "AvatarAxisScale1", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "AvatarAxisScale2", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "AvatarAxisScale3", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "AvatarAxisScale4", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "AvatarAxisScale5", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "AvatarAxisDeadZone0", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "AvatarAxisDeadZone1", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "AvatarAxisDeadZone2", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "AvatarAxisDeadZone3", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "AvatarAxisDeadZone4", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "AvatarAxisDeadZone5", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "BuildAxisScale0", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "BuildAxisScale1", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "BuildAxisScale2", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "BuildAxisScale3", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "BuildAxisScale4", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "BuildAxisScale5", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "BuildAxisDeadZone0", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "BuildAxisDeadZone1", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "BuildAxisDeadZone2", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "BuildAxisDeadZone3", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "BuildAxisDeadZone4", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "BuildAxisDeadZone5", handleJoystickChanged); + setting_setup_signal_listener(gSavedSettings, "DebugViews", handleDebugViewsChanged); + setting_setup_signal_listener(gSavedSettings, "UserLogFile", handleLogFileChanged); + setting_setup_signal_listener(gSavedSettings, "RenderHideGroupTitle", handleHideGroupTitleChanged); + setting_setup_signal_listener(gSavedSettings, "HighResSnapshot", handleHighResSnapshotChanged); + setting_setup_signal_listener(gSavedSettings, "EnableVoiceChat", handleVoiceClientPrefsChanged); + setting_setup_signal_listener(gSavedSettings, "PTTCurrentlyEnabled", handleVoiceClientPrefsChanged); + setting_setup_signal_listener(gSavedSettings, "PushToTalkButton", handleVoiceClientPrefsChanged); + setting_setup_signal_listener(gSavedSettings, "PushToTalkToggle", handleVoiceClientPrefsChanged); + setting_setup_signal_listener(gSavedSettings, "VoiceEarLocation", handleVoiceClientPrefsChanged); + setting_setup_signal_listener(gSavedSettings, "VoiceInputAudioDevice", handleVoiceClientPrefsChanged); + setting_setup_signal_listener(gSavedSettings, "VoiceOutputAudioDevice", handleVoiceClientPrefsChanged); + setting_setup_signal_listener(gSavedSettings, "AudioLevelMic", handleVoiceClientPrefsChanged); + setting_setup_signal_listener(gSavedSettings, "LipSyncEnabled", handleVoiceClientPrefsChanged); + setting_setup_signal_listener(gSavedSettings, "VelocityInterpolate", handleVelocityInterpolate); + setting_setup_signal_listener(gSavedSettings, "QAMode", show_debug_menus); + setting_setup_signal_listener(gSavedSettings, "UseDebugMenus", show_debug_menus); + setting_setup_signal_listener(gSavedSettings, "AgentPause", toggle_agent_pause); + setting_setup_signal_listener(gSavedSettings, "ShowNavbarNavigationPanel", toggle_show_navigation_panel); + setting_setup_signal_listener(gSavedSettings, "ShowMiniLocationPanel", toggle_show_mini_location_panel); + setting_setup_signal_listener(gSavedSettings, "ShowObjectRenderingCost", toggle_show_object_render_cost); + setting_setup_signal_listener(gSavedSettings, "ForceShowGrid", handleForceShowGrid); + setting_setup_signal_listener(gSavedSettings, "RenderTransparentWater", handleRenderTransparentWaterChanged); + setting_setup_signal_listener(gSavedSettings, "SpellCheck", handleSpellCheckChanged); + setting_setup_signal_listener(gSavedSettings, "SpellCheckDictionary", handleSpellCheckChanged); + setting_setup_signal_listener(gSavedSettings, "LoginLocation", handleLoginLocationChanged); + setting_setup_signal_listener(gSavedSettings, "DebugAvatarJoints", handleDebugAvatarJointsChanged); + + setting_setup_signal_listener(gSavedSettings, "TargetFPS", handleTargetFPSChanged); + setting_setup_signal_listener(gSavedSettings, "AutoTuneFPS", handleAutoTuneFPSChanged); + setting_setup_signal_listener(gSavedSettings, "AutoTuneLock", handleAutoTuneLockChanged); + setting_setup_signal_listener(gSavedSettings, "RenderAvatarMaxART", handleRenderAvatarMaxARTChanged); + setting_setup_signal_listener(gSavedSettings, "PerfStatsCaptureEnabled", handlePerformanceStatsEnabledChanged); + setting_setup_signal_listener(gSavedSettings, "AutoTuneRenderFarClipTarget", handleUserTargetDrawDistanceChanged); + setting_setup_signal_listener(gSavedSettings, "AutoTuneRenderFarClipMin", handleUserMinDrawDistanceChanged); + setting_setup_signal_listener(gSavedSettings, "AutoTuneImpostorFarAwayDistance", handleUserImpostorDistanceChanged); + setting_setup_signal_listener(gSavedSettings, "AutoTuneImpostorByDistEnabled", handleUserImpostorByDistEnabledChanged); + setting_setup_signal_listener(gSavedSettings, "TuningFPSStrategy", handleFPSTuningStrategyChanged); + + setting_setup_signal_listener(gSavedPerAccountSettings, "AvatarHoverOffsetZ", handleAvatarHoverOffsetChanged); +} + +#if TEST_CACHED_CONTROL + +#define DECL_LLCC(T, V) static LLCachedControl mySetting_##T("TestCachedControl"#T, V) +DECL_LLCC(U32, (U32)666); +DECL_LLCC(S32, (S32)-666); +DECL_LLCC(F32, (F32)-666.666); +DECL_LLCC(bool, true); +DECL_LLCC(bool, false); +static LLCachedControl mySetting_string("TestCachedControlstring", "Default String Value"); +DECL_LLCC(LLVector3, LLVector3(1.0f, 2.0f, 3.0f)); +DECL_LLCC(LLVector3d, LLVector3d(6.0f, 5.0f, 4.0f)); +DECL_LLCC(LLRect, LLRect(0, 0, 100, 500)); +DECL_LLCC(LLColor4, LLColor4(0.0f, 0.5f, 1.0f)); +DECL_LLCC(LLColor3, LLColor3(1.0f, 0.f, 0.5f)); +DECL_LLCC(LLColor4U, LLColor4U(255, 200, 100, 255)); + +LLSD test_llsd = LLSD()["testing1"] = LLSD()["testing2"]; +DECL_LLCC(LLSD, test_llsd); + +void test_cached_control() +{ +#define do { TEST_LLCC(T, V) if((T)mySetting_##T != V) LL_ERRS() << "Fail "#T << LL_ENDL; } while(0) + TEST_LLCC(U32, 666); + TEST_LLCC(S32, (S32)-666); + TEST_LLCC(F32, (F32)-666.666); + TEST_LLCC(bool, true); + TEST_LLCC(bool, false); + if((std::string)mySetting_string != "Default String Value") LL_ERRS() << "Fail string" << LL_ENDL; + TEST_LLCC(LLVector3, LLVector3(1.0f, 2.0f, 3.0f)); + TEST_LLCC(LLVector3d, LLVector3d(6.0f, 5.0f, 4.0f)); + TEST_LLCC(LLRect, LLRect(0, 0, 100, 500)); + TEST_LLCC(LLColor4, LLColor4(0.0f, 0.5f, 1.0f)); + TEST_LLCC(LLColor3, LLColor3(1.0f, 0.f, 0.5f)); + TEST_LLCC(LLColor4U, LLColor4U(255, 200, 100, 255)); +//There's no LLSD comparsion for LLCC yet. TEST_LLCC(LLSD, test_llsd); +} +#endif // TEST_CACHED_CONTROL + diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp index 4fbc0c8cff..b67d99a501 100644 --- a/indra/newview/llviewerdisplay.cpp +++ b/indra/newview/llviewerdisplay.cpp @@ -1,1735 +1,1735 @@ -/** - * @file llviewerdisplay.cpp - * @brief LLViewerDisplay class implementation - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llviewerdisplay.h" - -#include "llgl.h" -#include "llrender.h" -#include "llglheaders.h" -#include "llgltfmateriallist.h" -#include "llagent.h" -#include "llagentcamera.h" -#include "llviewercontrol.h" -#include "llcoord.h" -#include "llcriticaldamp.h" -#include "lldir.h" -#include "lldynamictexture.h" -#include "lldrawpoolalpha.h" -#include "llfeaturemanager.h" -//#include "llfirstuse.h" -#include "llhudmanager.h" -#include "llimagepng.h" -#include "llmemory.h" -#include "llselectmgr.h" -#include "llsky.h" -#include "llstartup.h" -#include "lltoolfocus.h" -#include "lltoolmgr.h" -#include "lltooldraganddrop.h" -#include "lltoolpie.h" -#include "lltracker.h" -#include "lltrans.h" -#include "llui.h" -#include "llviewercamera.h" -#include "llviewerobjectlist.h" -#include "llviewerparcelmgr.h" -#include "llviewerwindow.h" -#include "llvoavatarself.h" -#include "llvograss.h" -#include "llworld.h" -#include "pipeline.h" -#include "llspatialpartition.h" -#include "llappviewer.h" -#include "llstartup.h" -#include "llviewershadermgr.h" -#include "llfasttimer.h" -#include "llfloatertools.h" -#include "llviewertexturelist.h" -#include "llfocusmgr.h" -#include "llcubemap.h" -#include "llviewerregion.h" -#include "lldrawpoolwater.h" -#include "lldrawpoolbump.h" -#include "llpostprocess.h" -#include "llscenemonitor.h" - -#include "llenvironment.h" -#include "llperfstats.h" - -extern LLPointer gStartTexture; -extern bool gShiftFrame; - -LLPointer gDisconnectedImagep = NULL; - -// used to toggle renderer back on after teleport -bool gTeleportDisplay = false; -LLFrameTimer gTeleportDisplayTimer; -LLFrameTimer gTeleportArrivalTimer; -const F32 RESTORE_GL_TIME = 5.f; // Wait this long while reloading textures before we raise the curtain - -bool gForceRenderLandFence = false; -bool gDisplaySwapBuffers = false; -bool gDepthDirty = false; -bool gResizeScreenTexture = false; -bool gResizeShadowTexture = false; -bool gWindowResized = false; -bool gSnapshot = false; -bool gCubeSnapshot = false; -bool gSnapshotNoPost = false; -bool gShaderProfileFrame = false; - -// This is how long the sim will try to teleport you before giving up. -const F32 TELEPORT_EXPIRY = 15.0f; -// Additional time (in seconds) to wait per attachment -const F32 TELEPORT_EXPIRY_PER_ATTACHMENT = 3.f; - -U32 gRecentFrameCount = 0; // number of 'recent' frames -LLFrameTimer gRecentFPSTime; -LLFrameTimer gRecentMemoryTime; -LLFrameTimer gAssetStorageLogTime; - -// Rendering stuff -void pre_show_depth_buffer(); -void post_show_depth_buffer(); -void render_ui(F32 zoom_factor = 1.f, int subfield = 0); -void swap(); -void render_hud_attachments(); -void render_ui_3d(); -void render_ui_2d(); -void render_disconnected_background(); - -void display_startup() -{ - if ( !gViewerWindow - || !gViewerWindow->getActive() - || !gViewerWindow->getWindow()->getVisible() - || gViewerWindow->getWindow()->getMinimized() - || gNonInteractive) - { - return; - } - - gPipeline.updateGL(); - - // Written as branch to appease GCC which doesn't like different - // pointer types across ternary ops - // - if (!LLViewerFetchedTexture::sWhiteImagep.isNull()) - { - LLTexUnit::sWhiteTexture = LLViewerFetchedTexture::sWhiteImagep->getTexName(); - } - - LLGLSDefault gls_default; - - // Required for HTML update in login screen - static S32 frame_count = 0; - - LLGLState::checkStates(); - - if (frame_count++ > 1) // make sure we have rendered a frame first - { - LLViewerDynamicTexture::updateAllInstances(); - } - else - { - LL_DEBUGS("Window") << "First display_startup frame" << LL_ENDL; - } - - LLGLState::checkStates(); - - glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); // | GL_STENCIL_BUFFER_BIT); - LLGLSUIDefault gls_ui; - gPipeline.disableLights(); - - if (gViewerWindow) - gViewerWindow->setup2DRender(); - if (gViewerWindow) - gViewerWindow->draw(); - gGL.flush(); - - LLVertexBuffer::unbind(); - - LLGLState::checkStates(); - - if (gViewerWindow && gViewerWindow->getWindow()) - gViewerWindow->getWindow()->swapBuffers(); - - glClear(GL_DEPTH_BUFFER_BIT); -} - -void display_update_camera() -{ - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Update Camera"); - // TODO: cut draw distance down if customizing avatar? - // TODO: cut draw distance on per-parcel basis? - - // Cut draw distance in half when customizing avatar, - // but on the viewer only. - F32 final_far = gAgentCamera.mDrawDistance; - if (gCubeSnapshot) - { - final_far = gSavedSettings.getF32("RenderReflectionProbeDrawDistance"); - } - else if (CAMERA_MODE_CUSTOMIZE_AVATAR == gAgentCamera.getCameraMode()) - - { - final_far *= 0.5f; - } - LLViewerCamera::getInstance()->setFar(final_far); - gViewerWindow->setup3DRender(); - - if (!gCubeSnapshot) - { - // Update land visibility too - LLWorld::getInstance()->setLandFarClip(final_far); - } -} - -// Write some stats to LL_INFOS() -void display_stats() -{ - LL_PROFILE_ZONE_SCOPED - const F32 FPS_LOG_FREQUENCY = 10.f; - if (gRecentFPSTime.getElapsedTimeF32() >= FPS_LOG_FREQUENCY) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("DS - FPS"); - F32 fps = gRecentFrameCount / FPS_LOG_FREQUENCY; - LL_INFOS() << llformat("FPS: %.02f", fps) << LL_ENDL; - gRecentFrameCount = 0; - gRecentFPSTime.reset(); - } - F32 mem_log_freq = gSavedSettings.getF32("MemoryLogFrequency"); - if (mem_log_freq > 0.f && gRecentMemoryTime.getElapsedTimeF32() >= mem_log_freq) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("DS - Memory"); - gMemoryAllocated = U64Bytes(LLMemory::getCurrentRSS()); - U32Megabytes memory = gMemoryAllocated; - LL_INFOS() << "MEMORY: " << memory << LL_ENDL; - LLMemory::logMemoryInfo(true) ; - gRecentMemoryTime.reset(); - } - const F32 ASSET_STORAGE_LOG_FREQUENCY = 60.f; - if (gAssetStorageLogTime.getElapsedTimeF32() >= ASSET_STORAGE_LOG_FREQUENCY) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("DS - Asset Storage"); - gAssetStorageLogTime.reset(); - gAssetStorage->logAssetStorageInfo(); - } -} - -static void update_tp_display(bool minimized) -{ - static LLCachedControl teleport_arrival_delay(gSavedSettings, "TeleportArrivalDelay"); - static LLCachedControl teleport_local_delay(gSavedSettings, "TeleportLocalDelay"); - - S32 attach_count = 0; - if (isAgentAvatarValid()) - { - attach_count = gAgentAvatarp->getAttachmentCount(); - } - F32 teleport_save_time = TELEPORT_EXPIRY + TELEPORT_EXPIRY_PER_ATTACHMENT * attach_count; - F32 teleport_elapsed = gTeleportDisplayTimer.getElapsedTimeF32(); - F32 teleport_percent = teleport_elapsed * (100.f / teleport_save_time); - if (gAgent.getTeleportState() != LLAgent::TELEPORT_START && teleport_percent > 100.f) - { - // Give up. Don't keep the UI locked forever. - LL_WARNS("Teleport") << "Giving up on teleport. elapsed time " << teleport_elapsed << " exceeds max time " << teleport_save_time << LL_ENDL; - gAgent.setTeleportState(LLAgent::TELEPORT_NONE); - gAgent.setTeleportMessage(std::string()); - } - - // Make sure the TP progress panel gets hidden in case the viewer window - // is minimized *during* a TP. HB - if (minimized) - { - gViewerWindow->setShowProgress(false); - } - - const std::string& message = gAgent.getTeleportMessage(); - switch (gAgent.getTeleportState()) - { - case LLAgent::TELEPORT_PENDING: - { - gTeleportDisplayTimer.reset(); - const std::string& msg = LLAgent::sTeleportProgressMessages["pending"]; - if (!minimized) - { - gViewerWindow->setShowProgress(true); - gViewerWindow->setProgressPercent(llmin(teleport_percent, 0.0f)); - gViewerWindow->setProgressString(msg); - } - gAgent.setTeleportMessage(msg); - break; - } - - case LLAgent::TELEPORT_START: - { - // Transition to REQUESTED. Viewer has sent some kind - // of TeleportRequest to the source simulator - gTeleportDisplayTimer.reset(); - const std::string& msg = LLAgent::sTeleportProgressMessages["requesting"]; - LL_INFOS("Teleport") << "A teleport request has been sent, setting state to TELEPORT_REQUESTED" << LL_ENDL; - gAgent.setTeleportState(LLAgent::TELEPORT_REQUESTED); - gAgent.setTeleportMessage(msg); - if (!minimized) - { - gViewerWindow->setShowProgress(true); - gViewerWindow->setProgressPercent(llmin(teleport_percent, 0.0f)); - gViewerWindow->setProgressString(msg); - gViewerWindow->setProgressMessage(gAgent.mMOTD); - } - break; - } - - case LLAgent::TELEPORT_REQUESTED: - // Waiting for source simulator to respond - if (!minimized) - { - gViewerWindow->setProgressPercent(llmin(teleport_percent, 37.5f)); - gViewerWindow->setProgressString(message); - } - break; - - case LLAgent::TELEPORT_MOVING: - // Viewer has received destination location from source simulator - if (!minimized) - { - gViewerWindow->setProgressPercent(llmin(teleport_percent, 75.f)); - gViewerWindow->setProgressString(message); - } - break; - - case LLAgent::TELEPORT_START_ARRIVAL: - // Transition to ARRIVING. Viewer has received avatar update, etc., - // from destination simulator - gTeleportArrivalTimer.reset(); - LL_INFOS("Teleport") << "Changing state to TELEPORT_ARRIVING" << LL_ENDL; - gAgent.setTeleportState(LLAgent::TELEPORT_ARRIVING); - gAgent.setTeleportMessage(LLAgent::sTeleportProgressMessages["arriving"]); - gAgent.sheduleTeleportIM(); - gTextureList.mForceResetTextureStats = true; - gAgentCamera.resetView(true, true); - if (!minimized) - { - gViewerWindow->setProgressCancelButtonVisible(false, LLTrans::getString("Cancel")); - gViewerWindow->setProgressPercent(75.f); - } - break; - - case LLAgent::TELEPORT_ARRIVING: - // Make the user wait while content "pre-caches" - { - F32 arrival_fraction = (gTeleportArrivalTimer.getElapsedTimeF32() / teleport_arrival_delay()); - if (arrival_fraction > 1.f) - { - arrival_fraction = 1.f; - //LLFirstUse::useTeleport(); - LL_INFOS("Teleport") << "arrival_fraction is " << arrival_fraction << " changing state to TELEPORT_NONE" << LL_ENDL; - gAgent.setTeleportState(LLAgent::TELEPORT_NONE); - } - if (!minimized) - { - gViewerWindow->setProgressCancelButtonVisible(false, LLTrans::getString("Cancel")); - gViewerWindow->setProgressPercent(arrival_fraction * 25.f + 75.f); - gViewerWindow->setProgressString(message); - } - break; - } - - case LLAgent::TELEPORT_LOCAL: - // Short delay when teleporting in the same sim (progress screen active but not shown - did not - // fall-through from TELEPORT_START) - { - if (gTeleportDisplayTimer.getElapsedTimeF32() > teleport_local_delay()) - { - //LLFirstUse::useTeleport(); - LL_INFOS("Teleport") << "State is local and gTeleportDisplayTimer " << gTeleportDisplayTimer.getElapsedTimeF32() - << " exceeds teleport_local_delete " << teleport_local_delay - << "; setting state to TELEPORT_NONE" - << LL_ENDL; - gAgent.setTeleportState(LLAgent::TELEPORT_NONE); - } - break; - } - - case LLAgent::TELEPORT_NONE: - // No teleport in progress - gViewerWindow->setShowProgress(false); - gTeleportDisplay = false; - } -} - -// Paint the display! -void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) -{ - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Render"); - - LLPerfStats::RecordSceneTime T (LLPerfStats::StatType_t::RENDER_DISPLAY); // render time capture - This is the main stat for overall rendering. - - if (gWindowResized) - { //skip render on frames where window has been resized - LL_DEBUGS("Window") << "Resizing window" << LL_ENDL; - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Resize Window"); - gGL.flush(); - glClear(GL_COLOR_BUFFER_BIT); - gViewerWindow->getWindow()->swapBuffers(); - LLPipeline::refreshCachedSettings(); - gPipeline.resizeScreenTexture(); - gResizeScreenTexture = false; - gWindowResized = false; - return; - } - - if (gResizeShadowTexture) - { //skip render on frames where window has been resized - gPipeline.resizeShadowTexture(); - gResizeShadowTexture = false; - } - - gSnapshot = for_snapshot; - - if (LLPipeline::sRenderDeferred) - { //hack to make sky show up in deferred snapshots - for_snapshot = false; - } - - LLGLSDefault gls_default; - LLGLDepthTest gls_depth(GL_TRUE, GL_TRUE, GL_LEQUAL); - - LLVertexBuffer::unbind(); - - LLGLState::checkStates(); - - gPipeline.disableLights(); - - // Don't draw if the window is hidden or minimized. - // In fact, must explicitly check the minimized state before drawing. - // Attempting to draw into a minimized window causes a GL error. JC - if ( !gViewerWindow->getActive() - || !gViewerWindow->getWindow()->getVisible() - || gViewerWindow->getWindow()->getMinimized() - || gNonInteractive) - { - // Clean up memory the pools may have allocated - if (rebuild) - { - stop_glerror(); - gPipeline.rebuildPools(); - stop_glerror(); - } - - stop_glerror(); - gViewerWindow->returnEmptyPicks(); - stop_glerror(); - - // We still need to update the teleport progress (to get changes done - // in TP states, else the sim does not get the messages signaling the - // agent's arrival). This fixes BUG-230616. HB - if (gTeleportDisplay) - { - // true = minimized, do not show/update the TP screen. HB - update_tp_display(true); - } - return; - } - - gViewerWindow->checkSettings(); - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Picking"); - gViewerWindow->performPick(); - } - - LLAppViewer::instance()->pingMainloopTimeout("Display:CheckStates"); - LLGLState::checkStates(); - - ////////////////////////////////////////////////////////// - // - // Logic for forcing window updates if we're in drone mode. - // - - // *TODO: Investigate running display() during gHeadlessClient. See if this early exit is needed DK 2011-02-18 - if (gHeadlessClient) - { -#if LL_WINDOWS - static F32 last_update_time = 0.f; - if ((gFrameTimeSeconds - last_update_time) > 1.f) - { - InvalidateRect((HWND)gViewerWindow->getPlatformWindow(), NULL, false); - last_update_time = gFrameTimeSeconds; - } -#elif LL_DARWIN - // MBW -- Do something clever here. -#endif - // Not actually rendering, don't bother. - return; - } - - - // - // Bail out if we're in the startup state and don't want to try to - // render the world. - // - if (LLStartUp::getStartupState() < STATE_PRECACHE) - { - LLAppViewer::instance()->pingMainloopTimeout("Display:Startup"); - display_startup(); - return; - } - - - if (gShaderProfileFrame) - { - LLGLSLShader::initProfile(); - } - - //LLGLState::verify(false); - - ///////////////////////////////////////////////// - // - // Update GL Texture statistics (used for discard logic?) - // - - LLAppViewer::instance()->pingMainloopTimeout("Display:TextureStats"); - stop_glerror(); - - LLImageGL::updateStats(gFrameTimeSeconds); - - LLVOAvatar::sRenderName = gSavedSettings.getS32("AvatarNameTagMode"); - LLVOAvatar::sRenderGroupTitles = (gSavedSettings.getBOOL("NameTagShowGroupTitles") && gSavedSettings.getS32("AvatarNameTagMode")); - - gPipeline.mBackfaceCull = true; - gFrameCount++; - gRecentFrameCount++; - if (gFocusMgr.getAppHasFocus()) - { - gForegroundFrameCount++; - } - - ////////////////////////////////////////////////////////// - // - // Display start screen if we're teleporting, and skip render - // - - if (gTeleportDisplay) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Teleport Display"); - LLAppViewer::instance()->pingMainloopTimeout("Display:Teleport"); - // Note: false = not minimized, do update the TP screen. HB - update_tp_display(false); - } - else if(LLAppViewer::instance()->logoutRequestSent()) - { - LLAppViewer::instance()->pingMainloopTimeout("Display:Logout"); - F32 percent_done = gLogoutTimer.getElapsedTimeF32() * 100.f / gLogoutMaxTime; - if (percent_done > 100.f) - { - percent_done = 100.f; - } - - if( LLApp::isExiting() ) - { - percent_done = 100.f; - } - - gViewerWindow->setProgressPercent( percent_done ); - gViewerWindow->setProgressMessage(std::string()); - } - else - if (gRestoreGL) - { - LLAppViewer::instance()->pingMainloopTimeout("Display:RestoreGL"); - F32 percent_done = gRestoreGLTimer.getElapsedTimeF32() * 100.f / RESTORE_GL_TIME; - if( percent_done > 100.f ) - { - gViewerWindow->setShowProgress(false); - gRestoreGL = false; - } - else - { - - if( LLApp::isExiting() ) - { - percent_done = 100.f; - } - - gViewerWindow->setProgressPercent( percent_done ); - } - gViewerWindow->setProgressMessage(std::string()); - } - - ////////////////////////// - // - // Prepare for the next frame - // - - ///////////////////////////// - // - // Update the camera - // - // - - LLAppViewer::instance()->pingMainloopTimeout("Display:Camera"); - if (LLViewerCamera::instanceExists()) - { - LLViewerCamera::getInstance()->setZoomParameters(zoom_factor, subfield); - LLViewerCamera::getInstance()->setNear(MIN_NEAR_PLANE); - } - - ////////////////////////// - // - // clear the next buffer - // (must follow dynamic texture writing since that uses the frame buffer) - // - - if (gDisconnected) - { - LLAppViewer::instance()->pingMainloopTimeout("Display:Disconnected"); - render_ui(); - swap(); - } - - ////////////////////////// - // - // Set rendering options - // - // - LLAppViewer::instance()->pingMainloopTimeout("Display:RenderSetup"); - stop_glerror(); - - /////////////////////////////////////// - // - // Slam lighting parameters back to our defaults. - // Note that these are not the same as GL defaults... - - stop_glerror(); - gGL.setAmbientLightColor(LLColor4::white); - stop_glerror(); - - ///////////////////////////////////// - // - // Render - // - // Actually push all of our triangles to the screen. - // - - // do render-to-texture stuff here - if (gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_DYNAMIC_TEXTURES)) - { - LLAppViewer::instance()->pingMainloopTimeout("Display:DynamicTextures"); - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Update Dynamic Textures"); - if (LLViewerDynamicTexture::updateAllInstances()) - { - gGL.setColorMask(true, true); - glClear(GL_DEPTH_BUFFER_BIT); - } - } - - gViewerWindow->setup3DViewport(); - - gPipeline.resetFrameStats(); // Reset per-frame statistics. - - if (!gDisconnected) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("display - 1"); - LLAppViewer::instance()->pingMainloopTimeout("Display:Update"); - if (gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_HUD)) - { //don't draw hud objects in this frame - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_HUD); - } - - if (gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_HUD_PARTICLES)) - { //don't draw hud particles in this frame - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_HUD_PARTICLES); - } - - stop_glerror(); - display_update_camera(); - stop_glerror(); - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Env Update"); - // update all the sky/atmospheric/water settings - LLEnvironment::instance().update(LLViewerCamera::getInstance()); - } - - // *TODO: merge these two methods - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("HUD Update"); - LLHUDManager::getInstance()->updateEffects(); - LLHUDObject::updateAll(); - stop_glerror(); - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Update Geom"); - const F32 max_geom_update_time = 0.005f*10.f*gFrameIntervalSeconds.value(); // 50 ms/second update time - gPipeline.createObjects(max_geom_update_time); - gPipeline.processPartitionQ(); - gPipeline.updateGeom(max_geom_update_time); - stop_glerror(); - } - - gPipeline.updateGL(); - - stop_glerror(); - - LLAppViewer::instance()->pingMainloopTimeout("Display:Cull"); - - //Increment drawable frame counter - LLDrawable::incrementVisible(); - - LLSpatialGroup::sNoDelete = true; - LLTexUnit::sWhiteTexture = LLViewerFetchedTexture::sWhiteImagep->getTexName(); - - S32 occlusion = LLPipeline::sUseOcclusion; - if (gDepthDirty) - { //depth buffer is invalid, don't overwrite occlusion state - LLPipeline::sUseOcclusion = llmin(occlusion, 1); - } - gDepthDirty = false; - - LLGLState::checkStates(); - - static LLCullResult result; - LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD; - LLPipeline::sUnderWaterRender = LLViewerCamera::getInstance()->cameraUnderWater(); - gPipeline.updateCull(*LLViewerCamera::getInstance(), result); - stop_glerror(); - - LLGLState::checkStates(); - - LLAppViewer::instance()->pingMainloopTimeout("Display:Swap"); - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("display - 2") - if (gResizeScreenTexture) - { - gPipeline.resizeScreenTexture(); - gResizeScreenTexture = false; - } - - gGL.setColorMask(true, true); - glClearColor(0,0,0,0); - - LLGLState::checkStates(); - - if (!for_snapshot) - { - if (gFrameCount > 1 && !for_snapshot) - { //for some reason, ATI 4800 series will error out if you - //try to generate a shadow before the first frame is through - gPipeline.generateSunShadow(*LLViewerCamera::getInstance()); - } - - LLVertexBuffer::unbind(); - - LLGLState::checkStates(); - - glh::matrix4f proj = get_current_projection(); - glh::matrix4f mod = get_current_modelview(); - glViewport(0,0,512,512); - - LLVOAvatar::updateImpostors(); - - set_current_projection(proj); - set_current_modelview(mod); - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.loadMatrix(proj.m); - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.loadMatrix(mod.m); - gViewerWindow->setup3DViewport(); - - LLGLState::checkStates(); - } - glClear(GL_DEPTH_BUFFER_BIT); - } - - ////////////////////////////////////// - // - // Update images, using the image stats generated during object update/culling - // - // Can put objects onto the retextured list. - // - // Doing this here gives hardware occlusion queries extra time to complete - LLAppViewer::instance()->pingMainloopTimeout("Display:UpdateImages"); - - { - LL_PROFILE_ZONE_NAMED("Update Images"); - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Class"); - LLViewerTexture::updateClass(); - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Image Update Bump"); - gBumpImageList.updateImages(); // must be called before gTextureList version so that it's textures are thrown out first. - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("List"); - F32 max_image_decode_time = 0.050f*gFrameIntervalSeconds.value(); // 50 ms/second decode time - max_image_decode_time = llclamp(max_image_decode_time, 0.002f, 0.005f ); // min 2ms/frame, max 5ms/frame) - gTextureList.updateImages(max_image_decode_time); - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("GLTF Materials Cleanup"); - //remove dead gltf materials - gGLTFMaterialList.flushMaterials(); - } - } - - LLGLState::checkStates(); - - /////////////////////////////////// - // - // StateSort - // - // Responsible for taking visible objects, and adding them to the appropriate draw orders. - // In the case of alpha objects, z-sorts them first. - // Also creates special lists for outlines and selected face rendering. - // - LLAppViewer::instance()->pingMainloopTimeout("Display:StateSort"); - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("display - 4") - LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD; - gPipeline.stateSort(*LLViewerCamera::getInstance(), result); - stop_glerror(); - - if (rebuild) - { - ////////////////////////////////////// - // - // rebuildPools - // - // - gPipeline.rebuildPools(); - stop_glerror(); - } - } - - LLSceneMonitor::getInstance()->fetchQueryResult(); - - LLGLState::checkStates(); - - LLPipeline::sUseOcclusion = occlusion; - - { - LLAppViewer::instance()->pingMainloopTimeout("Display:Sky"); - LL_PROFILE_ZONE_NAMED_CATEGORY_ENVIRONMENT("update sky"); //LL_RECORD_BLOCK_TIME(FTM_UPDATE_SKY); - gSky.updateSky(); - } - - if(gUseWireframe) - { - glClearColor(0.5f, 0.5f, 0.5f, 0.f); - glClear(GL_COLOR_BUFFER_BIT); - } - - LLAppViewer::instance()->pingMainloopTimeout("Display:RenderStart"); - - //// render frontmost floater opaque for occlusion culling purposes - //LLFloater* frontmost_floaterp = gFloaterView->getFrontmost(); - //// assumes frontmost floater with focus is opaque - //if (frontmost_floaterp && gFocusMgr.childHasKeyboardFocus(frontmost_floaterp)) - //{ - // gGL.matrixMode(LLRender::MM_MODELVIEW); - // gGL.pushMatrix(); - // { - // gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - // glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE); - // gGL.loadIdentity(); - - // LLRect floater_rect = frontmost_floaterp->calcScreenRect(); - // // deflate by one pixel so rounding errors don't occlude outside of floater extents - // floater_rect.stretch(-1); - // LLRectf floater_3d_rect((F32)floater_rect.mLeft / (F32)gViewerWindow->getWindowWidthScaled(), - // (F32)floater_rect.mTop / (F32)gViewerWindow->getWindowHeightScaled(), - // (F32)floater_rect.mRight / (F32)gViewerWindow->getWindowWidthScaled(), - // (F32)floater_rect.mBottom / (F32)gViewerWindow->getWindowHeightScaled()); - // floater_3d_rect.translate(-0.5f, -0.5f); - // gGL.translatef(0.f, 0.f, -LLViewerCamera::getInstance()->getNear()); - // gGL.scalef(LLViewerCamera::getInstance()->getNear() * LLViewerCamera::getInstance()->getAspect() / sinf(LLViewerCamera::getInstance()->getView()), LLViewerCamera::getInstance()->getNear() / sinf(LLViewerCamera::getInstance()->getView()), 1.f); - // gGL.color4fv(LLColor4::white.mV); - // gGL.begin(LLVertexBuffer::QUADS); - // { - // gGL.vertex3f(floater_3d_rect.mLeft, floater_3d_rect.mBottom, 0.f); - // gGL.vertex3f(floater_3d_rect.mLeft, floater_3d_rect.mTop, 0.f); - // gGL.vertex3f(floater_3d_rect.mRight, floater_3d_rect.mTop, 0.f); - // gGL.vertex3f(floater_3d_rect.mRight, floater_3d_rect.mBottom, 0.f); - // } - // gGL.end(); - // glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); - // } - // gGL.popMatrix(); - //} - - LLPipeline::sUnderWaterRender = LLViewerCamera::getInstance()->cameraUnderWater(); - - LLGLState::checkStates(); - - stop_glerror(); - - gGL.setColorMask(true, true); - - if (LLPipeline::sRenderDeferred) - { - gPipeline.mRT->deferredScreen.bindTarget(); - if (gUseWireframe) - { - F32 g = 0.5f; - glClearColor(g, g, g, 1.f); - } - else - { - glClearColor(1, 0, 1, 1); - } - gPipeline.mRT->deferredScreen.clear(); - } - else - { - gPipeline.mRT->screen.bindTarget(); - if (LLPipeline::sUnderWaterRender && !gPipeline.canUseWindLightShaders()) - { - const LLColor4 &col = LLEnvironment::instance().getCurrentWater()->getWaterFogColor(); - glClearColor(col.mV[0], col.mV[1], col.mV[2], 0.f); - } - gPipeline.mRT->screen.clear(); - } - - gGL.setColorMask(true, false); - - LLAppViewer::instance()->pingMainloopTimeout("Display:RenderGeom"); - - if (!(LLAppViewer::instance()->logoutRequestSent() && LLAppViewer::instance()->hasSavedFinalSnapshot()) - && !gRestoreGL) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("display - 5") - LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD; - - if (gSavedSettings.getBOOL("RenderDepthPrePass")) - { - gGL.setColorMask(false, false); - - static const U32 types[] = { - LLRenderPass::PASS_SIMPLE, - LLRenderPass::PASS_FULLBRIGHT, - LLRenderPass::PASS_SHINY - }; - - U32 num_types = LL_ARRAY_SIZE(types); - gOcclusionProgram.bind(); - for (U32 i = 0; i < num_types; i++) - { - gPipeline.renderObjects(types[i], LLVertexBuffer::MAP_VERTEX, false); - } - - gOcclusionProgram.unbind(); - - } - - gGL.setColorMask(true, true); - gPipeline.renderGeomDeferred(*LLViewerCamera::getInstance(), true); - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Texture Unbind"); - for (U32 i = 0; i < gGLManager.mNumTextureImageUnits; i++) - { //dummy cleanup of any currently bound textures - if (gGL.getTexUnit(i)->getCurrType() != LLTexUnit::TT_NONE) - { - gGL.getTexUnit(i)->unbind(gGL.getTexUnit(i)->getCurrType()); - gGL.getTexUnit(i)->disable(); - } - } - } - - LLAppViewer::instance()->pingMainloopTimeout("Display:RenderFlush"); - - LLRenderTarget &rt = (gPipeline.sRenderDeferred ? gPipeline.mRT->deferredScreen : gPipeline.mRT->screen); - rt.flush(); - - if (LLPipeline::sRenderDeferred) - { - gPipeline.renderDeferredLighting(); - } - - LLPipeline::sUnderWaterRender = false; - - { - //capture the frame buffer. - LLSceneMonitor::getInstance()->capture(); - } - - LLAppViewer::instance()->pingMainloopTimeout("Display:RenderUI"); - if (!for_snapshot) - { - render_ui(); - swap(); - } - - - LLSpatialGroup::sNoDelete = false; - gPipeline.clearReferences(); - } - - LLAppViewer::instance()->pingMainloopTimeout("Display:FrameStats"); - - stop_glerror(); - - display_stats(); - - LLAppViewer::instance()->pingMainloopTimeout("Display:Done"); - - gShiftFrame = false; - - if (gShaderProfileFrame) - { - gShaderProfileFrame = false; - LLGLSLShader::finishProfile(); - } -} - -// WIP simplified copy of display() that does minimal work -void display_cube_face() -{ - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Render Cube Face"); - LL_PROFILE_GPU_ZONE("display cube face"); - - llassert(!gSnapshot); - llassert(!gTeleportDisplay); - llassert(LLStartUp::getStartupState() >= STATE_PRECACHE); - llassert(!LLAppViewer::instance()->logoutRequestSent()); - llassert(!gRestoreGL); - - bool rebuild = false; - - LLGLSDefault gls_default; - LLGLDepthTest gls_depth(GL_TRUE, GL_TRUE, GL_LEQUAL); - - LLVertexBuffer::unbind(); - - gPipeline.disableLights(); - - gPipeline.mBackfaceCull = true; - - gViewerWindow->setup3DViewport(); - - if (gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_HUD)) - { //don't draw hud objects in this frame - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_HUD); - } - - if (gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_HUD_PARTICLES)) - { //don't draw hud particles in this frame - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_HUD_PARTICLES); - } - - display_update_camera(); - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Env Update"); - // update all the sky/atmospheric/water settings - LLEnvironment::instance().update(LLViewerCamera::getInstance()); - } - - LLSpatialGroup::sNoDelete = true; - - S32 occlusion = LLPipeline::sUseOcclusion; - LLPipeline::sUseOcclusion = 0; // occlusion data is from main camera point of view, don't read or write it during cube snapshots - //gDepthDirty = true; //let "real" render pipe know it can't trust the depth buffer for occlusion data - - static LLCullResult result; - LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD; - LLPipeline::sUnderWaterRender = LLViewerCamera::getInstance()->cameraUnderWater(); - gPipeline.updateCull(*LLViewerCamera::getInstance(), result); - - gGL.setColorMask(true, true); - - glClearColor(0, 0, 0, 0); - gPipeline.generateSunShadow(*LLViewerCamera::getInstance()); - - glClear(GL_DEPTH_BUFFER_BIT); // | GL_STENCIL_BUFFER_BIT); - - { - LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD; - gPipeline.stateSort(*LLViewerCamera::getInstance(), result); - - if (rebuild) - { - ////////////////////////////////////// - // - // rebuildPools - // - // - gPipeline.rebuildPools(); - stop_glerror(); - } - } - - LLPipeline::sUseOcclusion = occlusion; - - LLAppViewer::instance()->pingMainloopTimeout("Display:RenderStart"); - - LLPipeline::sUnderWaterRender = LLViewerCamera::getInstance()->cameraUnderWater(); - - gGL.setColorMask(true, true); - - gPipeline.mRT->deferredScreen.bindTarget(); - if (gUseWireframe) - { - glClearColor(0.5f, 0.5f, 0.5f, 1.f); - } - else - { - glClearColor(1, 0, 1, 1); - } - gPipeline.mRT->deferredScreen.clear(); - - LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD; - - gPipeline.renderGeomDeferred(*LLViewerCamera::getInstance()); - - gPipeline.mRT->deferredScreen.flush(); - - gPipeline.renderDeferredLighting(); - - LLPipeline::sUnderWaterRender = false; - - // Finalize scene - //gPipeline.renderFinalize(); - - LLSpatialGroup::sNoDelete = false; - gPipeline.clearReferences(); -} - -void render_hud_attachments() -{ - LLPerfStats::RecordSceneTime T ( LLPerfStats::StatType_t::RENDER_HUDS); // render time capture - Primary contributor to HUDs (though these end up in render batches) - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.pushMatrix(); - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - - glh::matrix4f current_proj = get_current_projection(); - glh::matrix4f current_mod = get_current_modelview(); - - // clamp target zoom level to reasonable values - gAgentCamera.mHUDTargetZoom = llclamp(gAgentCamera.mHUDTargetZoom, 0.1f, 1.f); - // smoothly interpolate current zoom level - gAgentCamera.mHUDCurZoom = lerp(gAgentCamera.mHUDCurZoom, gAgentCamera.getAgentHUDTargetZoom(), LLSmoothInterpolation::getInterpolant(0.03f)); - - if (LLPipeline::sShowHUDAttachments && !gDisconnected && setup_hud_matrices()) - { - LLPipeline::sRenderingHUDs = true; - LLCamera hud_cam = *LLViewerCamera::getInstance(); - hud_cam.setOrigin(-1.f,0,0); - hud_cam.setAxes(LLVector3(1,0,0), LLVector3(0,1,0), LLVector3(0,0,1)); - LLViewerCamera::updateFrustumPlanes(hud_cam, true); - - bool render_particles = gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_PARTICLES) && gSavedSettings.getBOOL("RenderHUDParticles"); - - //only render hud objects - gPipeline.pushRenderTypeMask(); - - // turn off everything - gPipeline.andRenderTypeMask(LLPipeline::END_RENDER_TYPES); - // turn on HUD - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_HUD); - // turn on HUD particles - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_HUD_PARTICLES); - - // if particles are off, turn off hud-particles as well - if (!render_particles) - { - // turn back off HUD particles - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_HUD_PARTICLES); - } - - bool has_ui = gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI); - if (has_ui) - { - gPipeline.toggleRenderDebugFeature(LLPipeline::RENDER_DEBUG_FEATURE_UI); - } - - S32 use_occlusion = LLPipeline::sUseOcclusion; - LLPipeline::sUseOcclusion = 0; - - //cull, sort, and render hud objects - static LLCullResult result; - LLSpatialGroup::sNoDelete = true; - - LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD; - gPipeline.updateCull(hud_cam, result); - - // Toggle render types - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_BUMP); - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_SIMPLE); - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_VOLUME); - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_ALPHA); - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_ALPHA_PRE_WATER); - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_ALPHA_MASK); - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_FULLBRIGHT_ALPHA_MASK); - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_FULLBRIGHT); - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_GLTF_PBR); - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_GLTF_PBR_ALPHA_MASK); - - // Toggle render passes - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_ALPHA); - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_ALPHA_MASK); - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_BUMP); - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_MATERIAL); - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT); - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_ALPHA_MASK); - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_SHINY); - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_SHINY); - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_INVISIBLE); - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_INVISI_SHINY); - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_GLTF_PBR); - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_GLTF_PBR_ALPHA_MASK); - - gPipeline.stateSort(hud_cam, result); - - gPipeline.renderGeomPostDeferred(hud_cam); - - LLSpatialGroup::sNoDelete = false; - //gPipeline.clearReferences(); - - render_hud_elements(); - - //restore type mask - gPipeline.popRenderTypeMask(); - - if (has_ui) - { - gPipeline.toggleRenderDebugFeature(LLPipeline::RENDER_DEBUG_FEATURE_UI); - } - LLPipeline::sUseOcclusion = use_occlusion; - LLPipeline::sRenderingHUDs = false; - } - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.popMatrix(); - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.popMatrix(); - - set_current_projection(current_proj); - set_current_modelview(current_mod); -} - -LLRect get_whole_screen_region() -{ - LLRect whole_screen = gViewerWindow->getWorldViewRectScaled(); - - // apply camera zoom transform (for high res screenshots) - F32 zoom_factor = LLViewerCamera::getInstance()->getZoomFactor(); - S16 sub_region = LLViewerCamera::getInstance()->getZoomSubRegion(); - if (zoom_factor > 1.f) - { - S32 num_horizontal_tiles = llceil(zoom_factor); - S32 tile_width = ll_round((F32)gViewerWindow->getWorldViewWidthScaled() / zoom_factor); - S32 tile_height = ll_round((F32)gViewerWindow->getWorldViewHeightScaled() / zoom_factor); - int tile_y = sub_region / num_horizontal_tiles; - int tile_x = sub_region - (tile_y * num_horizontal_tiles); - - whole_screen.setLeftTopAndSize(tile_x * tile_width, gViewerWindow->getWorldViewHeightScaled() - (tile_y * tile_height), tile_width, tile_height); - } - return whole_screen; -} - -bool get_hud_matrices(const LLRect& screen_region, glh::matrix4f &proj, glh::matrix4f &model) -{ - if (isAgentAvatarValid() && gAgentAvatarp->hasHUDAttachment()) - { - F32 zoom_level = gAgentCamera.mHUDCurZoom; - LLBBox hud_bbox = gAgentAvatarp->getHUDBBox(); - - F32 hud_depth = llmax(1.f, hud_bbox.getExtentLocal().mV[VX] * 1.1f); - proj = gl_ortho(-0.5f * LLViewerCamera::getInstance()->getAspect(), 0.5f * LLViewerCamera::getInstance()->getAspect(), -0.5f, 0.5f, 0.f, hud_depth); - proj.element(2,2) = -0.01f; - - F32 aspect_ratio = LLViewerCamera::getInstance()->getAspect(); - - glh::matrix4f mat; - F32 scale_x = (F32)gViewerWindow->getWorldViewWidthScaled() / (F32)screen_region.getWidth(); - F32 scale_y = (F32)gViewerWindow->getWorldViewHeightScaled() / (F32)screen_region.getHeight(); - mat.set_scale(glh::vec3f(scale_x, scale_y, 1.f)); - mat.set_translate( - glh::vec3f(clamp_rescale((F32)(screen_region.getCenterX() - screen_region.mLeft), 0.f, (F32)gViewerWindow->getWorldViewWidthScaled(), 0.5f * scale_x * aspect_ratio, -0.5f * scale_x * aspect_ratio), - clamp_rescale((F32)(screen_region.getCenterY() - screen_region.mBottom), 0.f, (F32)gViewerWindow->getWorldViewHeightScaled(), 0.5f * scale_y, -0.5f * scale_y), - 0.f)); - proj *= mat; - - glh::matrix4f tmp_model((GLfloat*) OGL_TO_CFR_ROTATION); - - mat.set_scale(glh::vec3f(zoom_level, zoom_level, zoom_level)); - mat.set_translate(glh::vec3f(-hud_bbox.getCenterLocal().mV[VX] + (hud_depth * 0.5f), 0.f, 0.f)); - - tmp_model *= mat; - model = tmp_model; - return true; - } - else - { - return false; - } -} - -bool get_hud_matrices(glh::matrix4f &proj, glh::matrix4f &model) -{ - LLRect whole_screen = get_whole_screen_region(); - return get_hud_matrices(whole_screen, proj, model); -} - -bool setup_hud_matrices() -{ - LLRect whole_screen = get_whole_screen_region(); - return setup_hud_matrices(whole_screen); -} - -bool setup_hud_matrices(const LLRect& screen_region) -{ - glh::matrix4f proj, model; - bool result = get_hud_matrices(screen_region, proj, model); - if (!result) return result; - - // set up transform to keep HUD objects in front of camera - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.loadMatrix(proj.m); - set_current_projection(proj); - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.loadMatrix(model.m); - set_current_modelview(model); - return true; -} - -void render_ui(F32 zoom_factor, int subfield) -{ - LLPerfStats::RecordSceneTime T ( LLPerfStats::StatType_t::RENDER_UI ); // render time capture - Primary UI stat can have HUD time overlap (TODO) - LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; //LL_RECORD_BLOCK_TIME(FTM_RENDER_UI); - LL_PROFILE_GPU_ZONE("ui"); - LLGLState::checkStates(); - - glh::matrix4f saved_view = get_current_modelview(); - - if (!gSnapshot) - { - gGL.pushMatrix(); - gGL.loadMatrix(gGLLastModelView); - set_current_modelview(copy_matrix(gGLLastModelView)); - } - - if(LLSceneMonitor::getInstance()->needsUpdate()) - { - gGL.pushMatrix(); - gViewerWindow->setup2DRender(); - LLSceneMonitor::getInstance()->compare(); - gViewerWindow->setup3DRender(); - gGL.popMatrix(); - } - - // apply gamma correction and post effects - gPipeline.renderFinalize(); - - { - LLGLState::checkStates(); - - - LL_PROFILE_ZONE_NAMED_CATEGORY_UI("HUD"); - render_hud_elements(); - LLGLState::checkStates(); - render_hud_attachments(); - - LLGLState::checkStates(); - - LLGLSDefault gls_default; - LLGLSUIDefault gls_ui; - { - gPipeline.disableLights(); - } - - bool render_ui = gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI); - if (render_ui) - { - if (!gDisconnected) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_UI("UI 3D"); //LL_RECORD_BLOCK_TIME(FTM_RENDER_UI_3D); - LLGLState::checkStates(); - render_ui_3d(); - LLGLState::checkStates(); - } - else - { - render_disconnected_background(); - } - } - - if (render_ui) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_UI("UI 2D"); //LL_RECORD_BLOCK_TIME(FTM_RENDER_UI_2D); - LLHUDObject::renderAll(); - render_ui_2d(); - } - - gViewerWindow->setup2DRender(); - gViewerWindow->updateDebugText(); - gViewerWindow->drawDebugText(); - } - - if (!gSnapshot) - { - set_current_modelview(saved_view); - gGL.popMatrix(); - } -} - -void swap() -{ - LLPerfStats::RecordSceneTime T ( LLPerfStats::StatType_t::RENDER_SWAP ); // render time capture - Swap buffer time - can signify excessive data transfer to/from GPU - LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Swap"); - LL_PROFILE_GPU_ZONE("swap"); - if (gDisplaySwapBuffers) - { - gViewerWindow->getWindow()->swapBuffers(); - } - gDisplaySwapBuffers = true; -} - -void renderCoordinateAxes() -{ - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.begin(LLRender::LINES); - gGL.color3f(1.0f, 0.0f, 0.0f); // i direction = X-Axis = red - gGL.vertex3f(0.0f, 0.0f, 0.0f); - gGL.vertex3f(2.0f, 0.0f, 0.0f); - gGL.vertex3f(3.0f, 0.0f, 0.0f); - gGL.vertex3f(5.0f, 0.0f, 0.0f); - gGL.vertex3f(6.0f, 0.0f, 0.0f); - gGL.vertex3f(8.0f, 0.0f, 0.0f); - // Make an X - gGL.vertex3f(11.0f, 1.0f, 1.0f); - gGL.vertex3f(11.0f, -1.0f, -1.0f); - gGL.vertex3f(11.0f, 1.0f, -1.0f); - gGL.vertex3f(11.0f, -1.0f, 1.0f); - - gGL.color3f(0.0f, 1.0f, 0.0f); // j direction = Y-Axis = green - gGL.vertex3f(0.0f, 0.0f, 0.0f); - gGL.vertex3f(0.0f, 2.0f, 0.0f); - gGL.vertex3f(0.0f, 3.0f, 0.0f); - gGL.vertex3f(0.0f, 5.0f, 0.0f); - gGL.vertex3f(0.0f, 6.0f, 0.0f); - gGL.vertex3f(0.0f, 8.0f, 0.0f); - // Make a Y - gGL.vertex3f(1.0f, 11.0f, 1.0f); - gGL.vertex3f(0.0f, 11.0f, 0.0f); - gGL.vertex3f(-1.0f, 11.0f, 1.0f); - gGL.vertex3f(0.0f, 11.0f, 0.0f); - gGL.vertex3f(0.0f, 11.0f, 0.0f); - gGL.vertex3f(0.0f, 11.0f, -1.0f); - - gGL.color3f(0.0f, 0.0f, 1.0f); // Z-Axis = blue - gGL.vertex3f(0.0f, 0.0f, 0.0f); - gGL.vertex3f(0.0f, 0.0f, 2.0f); - gGL.vertex3f(0.0f, 0.0f, 3.0f); - gGL.vertex3f(0.0f, 0.0f, 5.0f); - gGL.vertex3f(0.0f, 0.0f, 6.0f); - gGL.vertex3f(0.0f, 0.0f, 8.0f); - // Make a Z - gGL.vertex3f(-1.0f, 1.0f, 11.0f); - gGL.vertex3f(1.0f, 1.0f, 11.0f); - gGL.vertex3f(1.0f, 1.0f, 11.0f); - gGL.vertex3f(-1.0f, -1.0f, 11.0f); - gGL.vertex3f(-1.0f, -1.0f, 11.0f); - gGL.vertex3f(1.0f, -1.0f, 11.0f); - gGL.end(); -} - - -void draw_axes() -{ - LLGLSUIDefault gls_ui; - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - // A vertical white line at origin - LLVector3 v = gAgent.getPositionAgent(); - gGL.begin(LLRender::LINES); - gGL.color3f(1.0f, 1.0f, 1.0f); - gGL.vertex3f(0.0f, 0.0f, 0.0f); - gGL.vertex3f(0.0f, 0.0f, 40.0f); - gGL.end(); - // Some coordinate axes - gGL.pushMatrix(); - gGL.translatef( v.mV[VX], v.mV[VY], v.mV[VZ] ); - renderCoordinateAxes(); - gGL.popMatrix(); -} - -void render_ui_3d() -{ - LLGLSPipeline gls_pipeline; - - ////////////////////////////////////// - // - // Render 3D UI elements - // NOTE: zbuffer is cleared before we get here by LLDrawPoolHUD, - // so 3d elements requiring Z buffer are moved to LLDrawPoolHUD - // - - ///////////////////////////////////////////////////////////// - // - // Render 2.5D elements (2D elements in the world) - // Stuff without z writes - // - - // Debugging stuff goes before the UI. - - stop_glerror(); - - gUIProgram.bind(); - gGL.color4f(1, 1, 1, 1); - - // Coordinate axes - if (gSavedSettings.getBOOL("ShowAxes")) - { - draw_axes(); - } - - gViewerWindow->renderSelections(false, false, true); // Non HUD call in render_hud_elements - - if (gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) - { - // Render debugging beacons. - gObjectList.renderObjectBeacons(); - gObjectList.resetObjectBeacons(); - gSky.addSunMoonBeacons(); - } - - stop_glerror(); -} - -void render_ui_2d() -{ - LLGLSUIDefault gls_ui; - - ///////////////////////////////////////////////////////////// - // - // Render 2D UI elements that overlay the world (no z compare) - - // Disable wireframe mode below here, as this is HUD/menus - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - - // Menu overlays, HUD, etc - gViewerWindow->setup2DRender(); - - F32 zoom_factor = LLViewerCamera::getInstance()->getZoomFactor(); - S16 sub_region = LLViewerCamera::getInstance()->getZoomSubRegion(); - - if (zoom_factor > 1.f) - { - //decompose subregion number to x and y values - int pos_y = sub_region / llceil(zoom_factor); - int pos_x = sub_region - (pos_y*llceil(zoom_factor)); - // offset for this tile - LLFontGL::sCurOrigin.mX -= ll_round((F32)gViewerWindow->getWindowWidthScaled() * (F32)pos_x / zoom_factor); - LLFontGL::sCurOrigin.mY -= ll_round((F32)gViewerWindow->getWindowHeightScaled() * (F32)pos_y / zoom_factor); - } - - stop_glerror(); - - // render outline for HUD - if (isAgentAvatarValid() && gAgentCamera.mHUDCurZoom < 0.98f) - { - gUIProgram.bind(); - gGL.pushMatrix(); - S32 half_width = (gViewerWindow->getWorldViewWidthScaled() / 2); - S32 half_height = (gViewerWindow->getWorldViewHeightScaled() / 2); - gGL.scalef(LLUI::getScaleFactor().mV[0], LLUI::getScaleFactor().mV[1], 1.f); - gGL.translatef((F32)half_width, (F32)half_height, 0.f); - F32 zoom = gAgentCamera.mHUDCurZoom; - gGL.scalef(zoom,zoom,1.f); - gGL.color4fv(LLColor4::white.mV); - gl_rect_2d(-half_width, half_height, half_width, -half_height, false); - gGL.popMatrix(); - gUIProgram.unbind(); - stop_glerror(); - } - - - if (gSavedSettings.getBOOL("RenderUIBuffer")) - { - if (LLView::sIsRectDirty) - { - LLView::sIsRectDirty = false; - LLRect t_rect; - - gPipeline.mRT->uiScreen.bindTarget(); - gGL.setColorMask(true, true); - { - static const S32 pad = 8; - - LLView::sDirtyRect.mLeft -= pad; - LLView::sDirtyRect.mRight += pad; - LLView::sDirtyRect.mBottom -= pad; - LLView::sDirtyRect.mTop += pad; - - LLGLEnable scissor(GL_SCISSOR_TEST); - static LLRect last_rect = LLView::sDirtyRect; - - //union with last rect to avoid mouse poop - last_rect.unionWith(LLView::sDirtyRect); - - t_rect = LLView::sDirtyRect; - LLView::sDirtyRect = last_rect; - last_rect = t_rect; - - last_rect.mLeft = LLRect::tCoordType(last_rect.mLeft / LLUI::getScaleFactor().mV[0]); - last_rect.mRight = LLRect::tCoordType(last_rect.mRight / LLUI::getScaleFactor().mV[0]); - last_rect.mTop = LLRect::tCoordType(last_rect.mTop / LLUI::getScaleFactor().mV[1]); - last_rect.mBottom = LLRect::tCoordType(last_rect.mBottom / LLUI::getScaleFactor().mV[1]); - - LLRect clip_rect(last_rect); - - glClear(GL_COLOR_BUFFER_BIT); - - gViewerWindow->draw(); - } - - gPipeline.mRT->uiScreen.flush(); - gGL.setColorMask(true, false); - - LLView::sDirtyRect = t_rect; - } - - LLGLDisable cull(GL_CULL_FACE); - LLGLDisable blend(GL_BLEND); - S32 width = gViewerWindow->getWindowWidthScaled(); - S32 height = gViewerWindow->getWindowHeightScaled(); - gGL.getTexUnit(0)->bind(&gPipeline.mRT->uiScreen); - gGL.begin(LLRender::TRIANGLE_STRIP); - gGL.color4f(1,1,1,1); - gGL.texCoord2f(0, 0); gGL.vertex2i(0, 0); - gGL.texCoord2f(width, 0); gGL.vertex2i(width, 0); - gGL.texCoord2f(0, height); gGL.vertex2i(0, height); - gGL.texCoord2f(width, height); gGL.vertex2i(width, height); - gGL.end(); - } - else - { - gViewerWindow->draw(); - } - - - - // reset current origin for font rendering, in case of tiling render - LLFontGL::sCurOrigin.set(0, 0); -} - -void render_disconnected_background() -{ - gUIProgram.bind(); - - gGL.color4f(1,1,1,1); - if (!gDisconnectedImagep && gDisconnected) - { - LL_INFOS() << "Loading last bitmap..." << LL_ENDL; - - std::string temp_str; - temp_str = gDirUtilp->getLindenUserDir() + gDirUtilp->getDirDelimiter() + LLStartUp::getScreenLastFilename(); - - LLPointer image_png = new LLImagePNG; - if( !image_png->load(temp_str) ) - { - //LL_INFOS() << "Bitmap load failed" << LL_ENDL; - return; - } - - LLPointer raw = new LLImageRaw; - if (!image_png->decode(raw, 0.0f)) - { - LL_INFOS() << "Bitmap decode failed" << LL_ENDL; - gDisconnectedImagep = NULL; - return; - } - - U8 *rawp = raw->getData(); - S32 npixels = (S32)image_png->getWidth()*(S32)image_png->getHeight(); - for (S32 i = 0; i < npixels; i++) - { - S32 sum = 0; - sum = *rawp + *(rawp+1) + *(rawp+2); - sum /= 3; - *rawp = ((S32)sum*6 + *rawp)/7; - rawp++; - *rawp = ((S32)sum*6 + *rawp)/7; - rawp++; - *rawp = ((S32)sum*6 + *rawp)/7; - rawp++; - } - - - raw->expandToPowerOfTwo(); - gDisconnectedImagep = LLViewerTextureManager::getLocalTexture(raw.get(), false ); - gStartTexture = gDisconnectedImagep; - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - } - - // Make sure the progress view always fills the entire window. - S32 width = gViewerWindow->getWindowWidthScaled(); - S32 height = gViewerWindow->getWindowHeightScaled(); - - if (gDisconnectedImagep) - { - LLGLSUIDefault gls_ui; - gViewerWindow->setup2DRender(); - gGL.pushMatrix(); - { - // scale ui to reflect UIScaleFactor - // this can't be done in setup2DRender because it requires a - // pushMatrix/popMatrix pair - const LLVector2& display_scale = gViewerWindow->getDisplayScale(); - gGL.scalef(display_scale.mV[VX], display_scale.mV[VY], 1.f); - - gGL.getTexUnit(0)->bind(gDisconnectedImagep); - gGL.color4f(1.f, 1.f, 1.f, 1.f); - gl_rect_2d_simple_tex(width, height); - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - } - gGL.popMatrix(); - } - gGL.flush(); - - gUIProgram.unbind(); -} - -void display_cleanup() -{ - gDisconnectedImagep = NULL; -} - +/** + * @file llviewerdisplay.cpp + * @brief LLViewerDisplay class implementation + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llviewerdisplay.h" + +#include "llgl.h" +#include "llrender.h" +#include "llglheaders.h" +#include "llgltfmateriallist.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llviewercontrol.h" +#include "llcoord.h" +#include "llcriticaldamp.h" +#include "lldir.h" +#include "lldynamictexture.h" +#include "lldrawpoolalpha.h" +#include "llfeaturemanager.h" +//#include "llfirstuse.h" +#include "llhudmanager.h" +#include "llimagepng.h" +#include "llmemory.h" +#include "llselectmgr.h" +#include "llsky.h" +#include "llstartup.h" +#include "lltoolfocus.h" +#include "lltoolmgr.h" +#include "lltooldraganddrop.h" +#include "lltoolpie.h" +#include "lltracker.h" +#include "lltrans.h" +#include "llui.h" +#include "llviewercamera.h" +#include "llviewerobjectlist.h" +#include "llviewerparcelmgr.h" +#include "llviewerwindow.h" +#include "llvoavatarself.h" +#include "llvograss.h" +#include "llworld.h" +#include "pipeline.h" +#include "llspatialpartition.h" +#include "llappviewer.h" +#include "llstartup.h" +#include "llviewershadermgr.h" +#include "llfasttimer.h" +#include "llfloatertools.h" +#include "llviewertexturelist.h" +#include "llfocusmgr.h" +#include "llcubemap.h" +#include "llviewerregion.h" +#include "lldrawpoolwater.h" +#include "lldrawpoolbump.h" +#include "llpostprocess.h" +#include "llscenemonitor.h" + +#include "llenvironment.h" +#include "llperfstats.h" + +extern LLPointer gStartTexture; +extern bool gShiftFrame; + +LLPointer gDisconnectedImagep = NULL; + +// used to toggle renderer back on after teleport +bool gTeleportDisplay = false; +LLFrameTimer gTeleportDisplayTimer; +LLFrameTimer gTeleportArrivalTimer; +const F32 RESTORE_GL_TIME = 5.f; // Wait this long while reloading textures before we raise the curtain + +bool gForceRenderLandFence = false; +bool gDisplaySwapBuffers = false; +bool gDepthDirty = false; +bool gResizeScreenTexture = false; +bool gResizeShadowTexture = false; +bool gWindowResized = false; +bool gSnapshot = false; +bool gCubeSnapshot = false; +bool gSnapshotNoPost = false; +bool gShaderProfileFrame = false; + +// This is how long the sim will try to teleport you before giving up. +const F32 TELEPORT_EXPIRY = 15.0f; +// Additional time (in seconds) to wait per attachment +const F32 TELEPORT_EXPIRY_PER_ATTACHMENT = 3.f; + +U32 gRecentFrameCount = 0; // number of 'recent' frames +LLFrameTimer gRecentFPSTime; +LLFrameTimer gRecentMemoryTime; +LLFrameTimer gAssetStorageLogTime; + +// Rendering stuff +void pre_show_depth_buffer(); +void post_show_depth_buffer(); +void render_ui(F32 zoom_factor = 1.f, int subfield = 0); +void swap(); +void render_hud_attachments(); +void render_ui_3d(); +void render_ui_2d(); +void render_disconnected_background(); + +void display_startup() +{ + if ( !gViewerWindow + || !gViewerWindow->getActive() + || !gViewerWindow->getWindow()->getVisible() + || gViewerWindow->getWindow()->getMinimized() + || gNonInteractive) + { + return; + } + + gPipeline.updateGL(); + + // Written as branch to appease GCC which doesn't like different + // pointer types across ternary ops + // + if (!LLViewerFetchedTexture::sWhiteImagep.isNull()) + { + LLTexUnit::sWhiteTexture = LLViewerFetchedTexture::sWhiteImagep->getTexName(); + } + + LLGLSDefault gls_default; + + // Required for HTML update in login screen + static S32 frame_count = 0; + + LLGLState::checkStates(); + + if (frame_count++ > 1) // make sure we have rendered a frame first + { + LLViewerDynamicTexture::updateAllInstances(); + } + else + { + LL_DEBUGS("Window") << "First display_startup frame" << LL_ENDL; + } + + LLGLState::checkStates(); + + glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); // | GL_STENCIL_BUFFER_BIT); + LLGLSUIDefault gls_ui; + gPipeline.disableLights(); + + if (gViewerWindow) + gViewerWindow->setup2DRender(); + if (gViewerWindow) + gViewerWindow->draw(); + gGL.flush(); + + LLVertexBuffer::unbind(); + + LLGLState::checkStates(); + + if (gViewerWindow && gViewerWindow->getWindow()) + gViewerWindow->getWindow()->swapBuffers(); + + glClear(GL_DEPTH_BUFFER_BIT); +} + +void display_update_camera() +{ + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Update Camera"); + // TODO: cut draw distance down if customizing avatar? + // TODO: cut draw distance on per-parcel basis? + + // Cut draw distance in half when customizing avatar, + // but on the viewer only. + F32 final_far = gAgentCamera.mDrawDistance; + if (gCubeSnapshot) + { + final_far = gSavedSettings.getF32("RenderReflectionProbeDrawDistance"); + } + else if (CAMERA_MODE_CUSTOMIZE_AVATAR == gAgentCamera.getCameraMode()) + + { + final_far *= 0.5f; + } + LLViewerCamera::getInstance()->setFar(final_far); + gViewerWindow->setup3DRender(); + + if (!gCubeSnapshot) + { + // Update land visibility too + LLWorld::getInstance()->setLandFarClip(final_far); + } +} + +// Write some stats to LL_INFOS() +void display_stats() +{ + LL_PROFILE_ZONE_SCOPED + const F32 FPS_LOG_FREQUENCY = 10.f; + if (gRecentFPSTime.getElapsedTimeF32() >= FPS_LOG_FREQUENCY) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("DS - FPS"); + F32 fps = gRecentFrameCount / FPS_LOG_FREQUENCY; + LL_INFOS() << llformat("FPS: %.02f", fps) << LL_ENDL; + gRecentFrameCount = 0; + gRecentFPSTime.reset(); + } + F32 mem_log_freq = gSavedSettings.getF32("MemoryLogFrequency"); + if (mem_log_freq > 0.f && gRecentMemoryTime.getElapsedTimeF32() >= mem_log_freq) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("DS - Memory"); + gMemoryAllocated = U64Bytes(LLMemory::getCurrentRSS()); + U32Megabytes memory = gMemoryAllocated; + LL_INFOS() << "MEMORY: " << memory << LL_ENDL; + LLMemory::logMemoryInfo(true) ; + gRecentMemoryTime.reset(); + } + const F32 ASSET_STORAGE_LOG_FREQUENCY = 60.f; + if (gAssetStorageLogTime.getElapsedTimeF32() >= ASSET_STORAGE_LOG_FREQUENCY) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("DS - Asset Storage"); + gAssetStorageLogTime.reset(); + gAssetStorage->logAssetStorageInfo(); + } +} + +static void update_tp_display(bool minimized) +{ + static LLCachedControl teleport_arrival_delay(gSavedSettings, "TeleportArrivalDelay"); + static LLCachedControl teleport_local_delay(gSavedSettings, "TeleportLocalDelay"); + + S32 attach_count = 0; + if (isAgentAvatarValid()) + { + attach_count = gAgentAvatarp->getAttachmentCount(); + } + F32 teleport_save_time = TELEPORT_EXPIRY + TELEPORT_EXPIRY_PER_ATTACHMENT * attach_count; + F32 teleport_elapsed = gTeleportDisplayTimer.getElapsedTimeF32(); + F32 teleport_percent = teleport_elapsed * (100.f / teleport_save_time); + if (gAgent.getTeleportState() != LLAgent::TELEPORT_START && teleport_percent > 100.f) + { + // Give up. Don't keep the UI locked forever. + LL_WARNS("Teleport") << "Giving up on teleport. elapsed time " << teleport_elapsed << " exceeds max time " << teleport_save_time << LL_ENDL; + gAgent.setTeleportState(LLAgent::TELEPORT_NONE); + gAgent.setTeleportMessage(std::string()); + } + + // Make sure the TP progress panel gets hidden in case the viewer window + // is minimized *during* a TP. HB + if (minimized) + { + gViewerWindow->setShowProgress(false); + } + + const std::string& message = gAgent.getTeleportMessage(); + switch (gAgent.getTeleportState()) + { + case LLAgent::TELEPORT_PENDING: + { + gTeleportDisplayTimer.reset(); + const std::string& msg = LLAgent::sTeleportProgressMessages["pending"]; + if (!minimized) + { + gViewerWindow->setShowProgress(true); + gViewerWindow->setProgressPercent(llmin(teleport_percent, 0.0f)); + gViewerWindow->setProgressString(msg); + } + gAgent.setTeleportMessage(msg); + break; + } + + case LLAgent::TELEPORT_START: + { + // Transition to REQUESTED. Viewer has sent some kind + // of TeleportRequest to the source simulator + gTeleportDisplayTimer.reset(); + const std::string& msg = LLAgent::sTeleportProgressMessages["requesting"]; + LL_INFOS("Teleport") << "A teleport request has been sent, setting state to TELEPORT_REQUESTED" << LL_ENDL; + gAgent.setTeleportState(LLAgent::TELEPORT_REQUESTED); + gAgent.setTeleportMessage(msg); + if (!minimized) + { + gViewerWindow->setShowProgress(true); + gViewerWindow->setProgressPercent(llmin(teleport_percent, 0.0f)); + gViewerWindow->setProgressString(msg); + gViewerWindow->setProgressMessage(gAgent.mMOTD); + } + break; + } + + case LLAgent::TELEPORT_REQUESTED: + // Waiting for source simulator to respond + if (!minimized) + { + gViewerWindow->setProgressPercent(llmin(teleport_percent, 37.5f)); + gViewerWindow->setProgressString(message); + } + break; + + case LLAgent::TELEPORT_MOVING: + // Viewer has received destination location from source simulator + if (!minimized) + { + gViewerWindow->setProgressPercent(llmin(teleport_percent, 75.f)); + gViewerWindow->setProgressString(message); + } + break; + + case LLAgent::TELEPORT_START_ARRIVAL: + // Transition to ARRIVING. Viewer has received avatar update, etc., + // from destination simulator + gTeleportArrivalTimer.reset(); + LL_INFOS("Teleport") << "Changing state to TELEPORT_ARRIVING" << LL_ENDL; + gAgent.setTeleportState(LLAgent::TELEPORT_ARRIVING); + gAgent.setTeleportMessage(LLAgent::sTeleportProgressMessages["arriving"]); + gAgent.sheduleTeleportIM(); + gTextureList.mForceResetTextureStats = true; + gAgentCamera.resetView(true, true); + if (!minimized) + { + gViewerWindow->setProgressCancelButtonVisible(false, LLTrans::getString("Cancel")); + gViewerWindow->setProgressPercent(75.f); + } + break; + + case LLAgent::TELEPORT_ARRIVING: + // Make the user wait while content "pre-caches" + { + F32 arrival_fraction = (gTeleportArrivalTimer.getElapsedTimeF32() / teleport_arrival_delay()); + if (arrival_fraction > 1.f) + { + arrival_fraction = 1.f; + //LLFirstUse::useTeleport(); + LL_INFOS("Teleport") << "arrival_fraction is " << arrival_fraction << " changing state to TELEPORT_NONE" << LL_ENDL; + gAgent.setTeleportState(LLAgent::TELEPORT_NONE); + } + if (!minimized) + { + gViewerWindow->setProgressCancelButtonVisible(false, LLTrans::getString("Cancel")); + gViewerWindow->setProgressPercent(arrival_fraction * 25.f + 75.f); + gViewerWindow->setProgressString(message); + } + break; + } + + case LLAgent::TELEPORT_LOCAL: + // Short delay when teleporting in the same sim (progress screen active but not shown - did not + // fall-through from TELEPORT_START) + { + if (gTeleportDisplayTimer.getElapsedTimeF32() > teleport_local_delay()) + { + //LLFirstUse::useTeleport(); + LL_INFOS("Teleport") << "State is local and gTeleportDisplayTimer " << gTeleportDisplayTimer.getElapsedTimeF32() + << " exceeds teleport_local_delete " << teleport_local_delay + << "; setting state to TELEPORT_NONE" + << LL_ENDL; + gAgent.setTeleportState(LLAgent::TELEPORT_NONE); + } + break; + } + + case LLAgent::TELEPORT_NONE: + // No teleport in progress + gViewerWindow->setShowProgress(false); + gTeleportDisplay = false; + } +} + +// Paint the display! +void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) +{ + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Render"); + + LLPerfStats::RecordSceneTime T (LLPerfStats::StatType_t::RENDER_DISPLAY); // render time capture - This is the main stat for overall rendering. + + if (gWindowResized) + { //skip render on frames where window has been resized + LL_DEBUGS("Window") << "Resizing window" << LL_ENDL; + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Resize Window"); + gGL.flush(); + glClear(GL_COLOR_BUFFER_BIT); + gViewerWindow->getWindow()->swapBuffers(); + LLPipeline::refreshCachedSettings(); + gPipeline.resizeScreenTexture(); + gResizeScreenTexture = false; + gWindowResized = false; + return; + } + + if (gResizeShadowTexture) + { //skip render on frames where window has been resized + gPipeline.resizeShadowTexture(); + gResizeShadowTexture = false; + } + + gSnapshot = for_snapshot; + + if (LLPipeline::sRenderDeferred) + { //hack to make sky show up in deferred snapshots + for_snapshot = false; + } + + LLGLSDefault gls_default; + LLGLDepthTest gls_depth(GL_TRUE, GL_TRUE, GL_LEQUAL); + + LLVertexBuffer::unbind(); + + LLGLState::checkStates(); + + gPipeline.disableLights(); + + // Don't draw if the window is hidden or minimized. + // In fact, must explicitly check the minimized state before drawing. + // Attempting to draw into a minimized window causes a GL error. JC + if ( !gViewerWindow->getActive() + || !gViewerWindow->getWindow()->getVisible() + || gViewerWindow->getWindow()->getMinimized() + || gNonInteractive) + { + // Clean up memory the pools may have allocated + if (rebuild) + { + stop_glerror(); + gPipeline.rebuildPools(); + stop_glerror(); + } + + stop_glerror(); + gViewerWindow->returnEmptyPicks(); + stop_glerror(); + + // We still need to update the teleport progress (to get changes done + // in TP states, else the sim does not get the messages signaling the + // agent's arrival). This fixes BUG-230616. HB + if (gTeleportDisplay) + { + // true = minimized, do not show/update the TP screen. HB + update_tp_display(true); + } + return; + } + + gViewerWindow->checkSettings(); + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Picking"); + gViewerWindow->performPick(); + } + + LLAppViewer::instance()->pingMainloopTimeout("Display:CheckStates"); + LLGLState::checkStates(); + + ////////////////////////////////////////////////////////// + // + // Logic for forcing window updates if we're in drone mode. + // + + // *TODO: Investigate running display() during gHeadlessClient. See if this early exit is needed DK 2011-02-18 + if (gHeadlessClient) + { +#if LL_WINDOWS + static F32 last_update_time = 0.f; + if ((gFrameTimeSeconds - last_update_time) > 1.f) + { + InvalidateRect((HWND)gViewerWindow->getPlatformWindow(), NULL, false); + last_update_time = gFrameTimeSeconds; + } +#elif LL_DARWIN + // MBW -- Do something clever here. +#endif + // Not actually rendering, don't bother. + return; + } + + + // + // Bail out if we're in the startup state and don't want to try to + // render the world. + // + if (LLStartUp::getStartupState() < STATE_PRECACHE) + { + LLAppViewer::instance()->pingMainloopTimeout("Display:Startup"); + display_startup(); + return; + } + + + if (gShaderProfileFrame) + { + LLGLSLShader::initProfile(); + } + + //LLGLState::verify(false); + + ///////////////////////////////////////////////// + // + // Update GL Texture statistics (used for discard logic?) + // + + LLAppViewer::instance()->pingMainloopTimeout("Display:TextureStats"); + stop_glerror(); + + LLImageGL::updateStats(gFrameTimeSeconds); + + LLVOAvatar::sRenderName = gSavedSettings.getS32("AvatarNameTagMode"); + LLVOAvatar::sRenderGroupTitles = (gSavedSettings.getBOOL("NameTagShowGroupTitles") && gSavedSettings.getS32("AvatarNameTagMode")); + + gPipeline.mBackfaceCull = true; + gFrameCount++; + gRecentFrameCount++; + if (gFocusMgr.getAppHasFocus()) + { + gForegroundFrameCount++; + } + + ////////////////////////////////////////////////////////// + // + // Display start screen if we're teleporting, and skip render + // + + if (gTeleportDisplay) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Teleport Display"); + LLAppViewer::instance()->pingMainloopTimeout("Display:Teleport"); + // Note: false = not minimized, do update the TP screen. HB + update_tp_display(false); + } + else if(LLAppViewer::instance()->logoutRequestSent()) + { + LLAppViewer::instance()->pingMainloopTimeout("Display:Logout"); + F32 percent_done = gLogoutTimer.getElapsedTimeF32() * 100.f / gLogoutMaxTime; + if (percent_done > 100.f) + { + percent_done = 100.f; + } + + if( LLApp::isExiting() ) + { + percent_done = 100.f; + } + + gViewerWindow->setProgressPercent( percent_done ); + gViewerWindow->setProgressMessage(std::string()); + } + else + if (gRestoreGL) + { + LLAppViewer::instance()->pingMainloopTimeout("Display:RestoreGL"); + F32 percent_done = gRestoreGLTimer.getElapsedTimeF32() * 100.f / RESTORE_GL_TIME; + if( percent_done > 100.f ) + { + gViewerWindow->setShowProgress(false); + gRestoreGL = false; + } + else + { + + if( LLApp::isExiting() ) + { + percent_done = 100.f; + } + + gViewerWindow->setProgressPercent( percent_done ); + } + gViewerWindow->setProgressMessage(std::string()); + } + + ////////////////////////// + // + // Prepare for the next frame + // + + ///////////////////////////// + // + // Update the camera + // + // + + LLAppViewer::instance()->pingMainloopTimeout("Display:Camera"); + if (LLViewerCamera::instanceExists()) + { + LLViewerCamera::getInstance()->setZoomParameters(zoom_factor, subfield); + LLViewerCamera::getInstance()->setNear(MIN_NEAR_PLANE); + } + + ////////////////////////// + // + // clear the next buffer + // (must follow dynamic texture writing since that uses the frame buffer) + // + + if (gDisconnected) + { + LLAppViewer::instance()->pingMainloopTimeout("Display:Disconnected"); + render_ui(); + swap(); + } + + ////////////////////////// + // + // Set rendering options + // + // + LLAppViewer::instance()->pingMainloopTimeout("Display:RenderSetup"); + stop_glerror(); + + /////////////////////////////////////// + // + // Slam lighting parameters back to our defaults. + // Note that these are not the same as GL defaults... + + stop_glerror(); + gGL.setAmbientLightColor(LLColor4::white); + stop_glerror(); + + ///////////////////////////////////// + // + // Render + // + // Actually push all of our triangles to the screen. + // + + // do render-to-texture stuff here + if (gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_DYNAMIC_TEXTURES)) + { + LLAppViewer::instance()->pingMainloopTimeout("Display:DynamicTextures"); + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Update Dynamic Textures"); + if (LLViewerDynamicTexture::updateAllInstances()) + { + gGL.setColorMask(true, true); + glClear(GL_DEPTH_BUFFER_BIT); + } + } + + gViewerWindow->setup3DViewport(); + + gPipeline.resetFrameStats(); // Reset per-frame statistics. + + if (!gDisconnected) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("display - 1"); + LLAppViewer::instance()->pingMainloopTimeout("Display:Update"); + if (gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_HUD)) + { //don't draw hud objects in this frame + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_HUD); + } + + if (gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_HUD_PARTICLES)) + { //don't draw hud particles in this frame + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_HUD_PARTICLES); + } + + stop_glerror(); + display_update_camera(); + stop_glerror(); + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Env Update"); + // update all the sky/atmospheric/water settings + LLEnvironment::instance().update(LLViewerCamera::getInstance()); + } + + // *TODO: merge these two methods + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("HUD Update"); + LLHUDManager::getInstance()->updateEffects(); + LLHUDObject::updateAll(); + stop_glerror(); + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Update Geom"); + const F32 max_geom_update_time = 0.005f*10.f*gFrameIntervalSeconds.value(); // 50 ms/second update time + gPipeline.createObjects(max_geom_update_time); + gPipeline.processPartitionQ(); + gPipeline.updateGeom(max_geom_update_time); + stop_glerror(); + } + + gPipeline.updateGL(); + + stop_glerror(); + + LLAppViewer::instance()->pingMainloopTimeout("Display:Cull"); + + //Increment drawable frame counter + LLDrawable::incrementVisible(); + + LLSpatialGroup::sNoDelete = true; + LLTexUnit::sWhiteTexture = LLViewerFetchedTexture::sWhiteImagep->getTexName(); + + S32 occlusion = LLPipeline::sUseOcclusion; + if (gDepthDirty) + { //depth buffer is invalid, don't overwrite occlusion state + LLPipeline::sUseOcclusion = llmin(occlusion, 1); + } + gDepthDirty = false; + + LLGLState::checkStates(); + + static LLCullResult result; + LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD; + LLPipeline::sUnderWaterRender = LLViewerCamera::getInstance()->cameraUnderWater(); + gPipeline.updateCull(*LLViewerCamera::getInstance(), result); + stop_glerror(); + + LLGLState::checkStates(); + + LLAppViewer::instance()->pingMainloopTimeout("Display:Swap"); + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("display - 2") + if (gResizeScreenTexture) + { + gPipeline.resizeScreenTexture(); + gResizeScreenTexture = false; + } + + gGL.setColorMask(true, true); + glClearColor(0,0,0,0); + + LLGLState::checkStates(); + + if (!for_snapshot) + { + if (gFrameCount > 1 && !for_snapshot) + { //for some reason, ATI 4800 series will error out if you + //try to generate a shadow before the first frame is through + gPipeline.generateSunShadow(*LLViewerCamera::getInstance()); + } + + LLVertexBuffer::unbind(); + + LLGLState::checkStates(); + + glh::matrix4f proj = get_current_projection(); + glh::matrix4f mod = get_current_modelview(); + glViewport(0,0,512,512); + + LLVOAvatar::updateImpostors(); + + set_current_projection(proj); + set_current_modelview(mod); + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.loadMatrix(proj.m); + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.loadMatrix(mod.m); + gViewerWindow->setup3DViewport(); + + LLGLState::checkStates(); + } + glClear(GL_DEPTH_BUFFER_BIT); + } + + ////////////////////////////////////// + // + // Update images, using the image stats generated during object update/culling + // + // Can put objects onto the retextured list. + // + // Doing this here gives hardware occlusion queries extra time to complete + LLAppViewer::instance()->pingMainloopTimeout("Display:UpdateImages"); + + { + LL_PROFILE_ZONE_NAMED("Update Images"); + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Class"); + LLViewerTexture::updateClass(); + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Image Update Bump"); + gBumpImageList.updateImages(); // must be called before gTextureList version so that it's textures are thrown out first. + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("List"); + F32 max_image_decode_time = 0.050f*gFrameIntervalSeconds.value(); // 50 ms/second decode time + max_image_decode_time = llclamp(max_image_decode_time, 0.002f, 0.005f ); // min 2ms/frame, max 5ms/frame) + gTextureList.updateImages(max_image_decode_time); + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("GLTF Materials Cleanup"); + //remove dead gltf materials + gGLTFMaterialList.flushMaterials(); + } + } + + LLGLState::checkStates(); + + /////////////////////////////////// + // + // StateSort + // + // Responsible for taking visible objects, and adding them to the appropriate draw orders. + // In the case of alpha objects, z-sorts them first. + // Also creates special lists for outlines and selected face rendering. + // + LLAppViewer::instance()->pingMainloopTimeout("Display:StateSort"); + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("display - 4") + LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD; + gPipeline.stateSort(*LLViewerCamera::getInstance(), result); + stop_glerror(); + + if (rebuild) + { + ////////////////////////////////////// + // + // rebuildPools + // + // + gPipeline.rebuildPools(); + stop_glerror(); + } + } + + LLSceneMonitor::getInstance()->fetchQueryResult(); + + LLGLState::checkStates(); + + LLPipeline::sUseOcclusion = occlusion; + + { + LLAppViewer::instance()->pingMainloopTimeout("Display:Sky"); + LL_PROFILE_ZONE_NAMED_CATEGORY_ENVIRONMENT("update sky"); //LL_RECORD_BLOCK_TIME(FTM_UPDATE_SKY); + gSky.updateSky(); + } + + if(gUseWireframe) + { + glClearColor(0.5f, 0.5f, 0.5f, 0.f); + glClear(GL_COLOR_BUFFER_BIT); + } + + LLAppViewer::instance()->pingMainloopTimeout("Display:RenderStart"); + + //// render frontmost floater opaque for occlusion culling purposes + //LLFloater* frontmost_floaterp = gFloaterView->getFrontmost(); + //// assumes frontmost floater with focus is opaque + //if (frontmost_floaterp && gFocusMgr.childHasKeyboardFocus(frontmost_floaterp)) + //{ + // gGL.matrixMode(LLRender::MM_MODELVIEW); + // gGL.pushMatrix(); + // { + // gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + // glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE); + // gGL.loadIdentity(); + + // LLRect floater_rect = frontmost_floaterp->calcScreenRect(); + // // deflate by one pixel so rounding errors don't occlude outside of floater extents + // floater_rect.stretch(-1); + // LLRectf floater_3d_rect((F32)floater_rect.mLeft / (F32)gViewerWindow->getWindowWidthScaled(), + // (F32)floater_rect.mTop / (F32)gViewerWindow->getWindowHeightScaled(), + // (F32)floater_rect.mRight / (F32)gViewerWindow->getWindowWidthScaled(), + // (F32)floater_rect.mBottom / (F32)gViewerWindow->getWindowHeightScaled()); + // floater_3d_rect.translate(-0.5f, -0.5f); + // gGL.translatef(0.f, 0.f, -LLViewerCamera::getInstance()->getNear()); + // gGL.scalef(LLViewerCamera::getInstance()->getNear() * LLViewerCamera::getInstance()->getAspect() / sinf(LLViewerCamera::getInstance()->getView()), LLViewerCamera::getInstance()->getNear() / sinf(LLViewerCamera::getInstance()->getView()), 1.f); + // gGL.color4fv(LLColor4::white.mV); + // gGL.begin(LLVertexBuffer::QUADS); + // { + // gGL.vertex3f(floater_3d_rect.mLeft, floater_3d_rect.mBottom, 0.f); + // gGL.vertex3f(floater_3d_rect.mLeft, floater_3d_rect.mTop, 0.f); + // gGL.vertex3f(floater_3d_rect.mRight, floater_3d_rect.mTop, 0.f); + // gGL.vertex3f(floater_3d_rect.mRight, floater_3d_rect.mBottom, 0.f); + // } + // gGL.end(); + // glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + // } + // gGL.popMatrix(); + //} + + LLPipeline::sUnderWaterRender = LLViewerCamera::getInstance()->cameraUnderWater(); + + LLGLState::checkStates(); + + stop_glerror(); + + gGL.setColorMask(true, true); + + if (LLPipeline::sRenderDeferred) + { + gPipeline.mRT->deferredScreen.bindTarget(); + if (gUseWireframe) + { + F32 g = 0.5f; + glClearColor(g, g, g, 1.f); + } + else + { + glClearColor(1, 0, 1, 1); + } + gPipeline.mRT->deferredScreen.clear(); + } + else + { + gPipeline.mRT->screen.bindTarget(); + if (LLPipeline::sUnderWaterRender && !gPipeline.canUseWindLightShaders()) + { + const LLColor4 &col = LLEnvironment::instance().getCurrentWater()->getWaterFogColor(); + glClearColor(col.mV[0], col.mV[1], col.mV[2], 0.f); + } + gPipeline.mRT->screen.clear(); + } + + gGL.setColorMask(true, false); + + LLAppViewer::instance()->pingMainloopTimeout("Display:RenderGeom"); + + if (!(LLAppViewer::instance()->logoutRequestSent() && LLAppViewer::instance()->hasSavedFinalSnapshot()) + && !gRestoreGL) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("display - 5") + LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD; + + if (gSavedSettings.getBOOL("RenderDepthPrePass")) + { + gGL.setColorMask(false, false); + + static const U32 types[] = { + LLRenderPass::PASS_SIMPLE, + LLRenderPass::PASS_FULLBRIGHT, + LLRenderPass::PASS_SHINY + }; + + U32 num_types = LL_ARRAY_SIZE(types); + gOcclusionProgram.bind(); + for (U32 i = 0; i < num_types; i++) + { + gPipeline.renderObjects(types[i], LLVertexBuffer::MAP_VERTEX, false); + } + + gOcclusionProgram.unbind(); + + } + + gGL.setColorMask(true, true); + gPipeline.renderGeomDeferred(*LLViewerCamera::getInstance(), true); + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Texture Unbind"); + for (U32 i = 0; i < gGLManager.mNumTextureImageUnits; i++) + { //dummy cleanup of any currently bound textures + if (gGL.getTexUnit(i)->getCurrType() != LLTexUnit::TT_NONE) + { + gGL.getTexUnit(i)->unbind(gGL.getTexUnit(i)->getCurrType()); + gGL.getTexUnit(i)->disable(); + } + } + } + + LLAppViewer::instance()->pingMainloopTimeout("Display:RenderFlush"); + + LLRenderTarget &rt = (gPipeline.sRenderDeferred ? gPipeline.mRT->deferredScreen : gPipeline.mRT->screen); + rt.flush(); + + if (LLPipeline::sRenderDeferred) + { + gPipeline.renderDeferredLighting(); + } + + LLPipeline::sUnderWaterRender = false; + + { + //capture the frame buffer. + LLSceneMonitor::getInstance()->capture(); + } + + LLAppViewer::instance()->pingMainloopTimeout("Display:RenderUI"); + if (!for_snapshot) + { + render_ui(); + swap(); + } + + + LLSpatialGroup::sNoDelete = false; + gPipeline.clearReferences(); + } + + LLAppViewer::instance()->pingMainloopTimeout("Display:FrameStats"); + + stop_glerror(); + + display_stats(); + + LLAppViewer::instance()->pingMainloopTimeout("Display:Done"); + + gShiftFrame = false; + + if (gShaderProfileFrame) + { + gShaderProfileFrame = false; + LLGLSLShader::finishProfile(); + } +} + +// WIP simplified copy of display() that does minimal work +void display_cube_face() +{ + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Render Cube Face"); + LL_PROFILE_GPU_ZONE("display cube face"); + + llassert(!gSnapshot); + llassert(!gTeleportDisplay); + llassert(LLStartUp::getStartupState() >= STATE_PRECACHE); + llassert(!LLAppViewer::instance()->logoutRequestSent()); + llassert(!gRestoreGL); + + bool rebuild = false; + + LLGLSDefault gls_default; + LLGLDepthTest gls_depth(GL_TRUE, GL_TRUE, GL_LEQUAL); + + LLVertexBuffer::unbind(); + + gPipeline.disableLights(); + + gPipeline.mBackfaceCull = true; + + gViewerWindow->setup3DViewport(); + + if (gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_HUD)) + { //don't draw hud objects in this frame + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_HUD); + } + + if (gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_HUD_PARTICLES)) + { //don't draw hud particles in this frame + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_HUD_PARTICLES); + } + + display_update_camera(); + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Env Update"); + // update all the sky/atmospheric/water settings + LLEnvironment::instance().update(LLViewerCamera::getInstance()); + } + + LLSpatialGroup::sNoDelete = true; + + S32 occlusion = LLPipeline::sUseOcclusion; + LLPipeline::sUseOcclusion = 0; // occlusion data is from main camera point of view, don't read or write it during cube snapshots + //gDepthDirty = true; //let "real" render pipe know it can't trust the depth buffer for occlusion data + + static LLCullResult result; + LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD; + LLPipeline::sUnderWaterRender = LLViewerCamera::getInstance()->cameraUnderWater(); + gPipeline.updateCull(*LLViewerCamera::getInstance(), result); + + gGL.setColorMask(true, true); + + glClearColor(0, 0, 0, 0); + gPipeline.generateSunShadow(*LLViewerCamera::getInstance()); + + glClear(GL_DEPTH_BUFFER_BIT); // | GL_STENCIL_BUFFER_BIT); + + { + LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD; + gPipeline.stateSort(*LLViewerCamera::getInstance(), result); + + if (rebuild) + { + ////////////////////////////////////// + // + // rebuildPools + // + // + gPipeline.rebuildPools(); + stop_glerror(); + } + } + + LLPipeline::sUseOcclusion = occlusion; + + LLAppViewer::instance()->pingMainloopTimeout("Display:RenderStart"); + + LLPipeline::sUnderWaterRender = LLViewerCamera::getInstance()->cameraUnderWater(); + + gGL.setColorMask(true, true); + + gPipeline.mRT->deferredScreen.bindTarget(); + if (gUseWireframe) + { + glClearColor(0.5f, 0.5f, 0.5f, 1.f); + } + else + { + glClearColor(1, 0, 1, 1); + } + gPipeline.mRT->deferredScreen.clear(); + + LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD; + + gPipeline.renderGeomDeferred(*LLViewerCamera::getInstance()); + + gPipeline.mRT->deferredScreen.flush(); + + gPipeline.renderDeferredLighting(); + + LLPipeline::sUnderWaterRender = false; + + // Finalize scene + //gPipeline.renderFinalize(); + + LLSpatialGroup::sNoDelete = false; + gPipeline.clearReferences(); +} + +void render_hud_attachments() +{ + LLPerfStats::RecordSceneTime T ( LLPerfStats::StatType_t::RENDER_HUDS); // render time capture - Primary contributor to HUDs (though these end up in render batches) + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.pushMatrix(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + + glh::matrix4f current_proj = get_current_projection(); + glh::matrix4f current_mod = get_current_modelview(); + + // clamp target zoom level to reasonable values + gAgentCamera.mHUDTargetZoom = llclamp(gAgentCamera.mHUDTargetZoom, 0.1f, 1.f); + // smoothly interpolate current zoom level + gAgentCamera.mHUDCurZoom = lerp(gAgentCamera.mHUDCurZoom, gAgentCamera.getAgentHUDTargetZoom(), LLSmoothInterpolation::getInterpolant(0.03f)); + + if (LLPipeline::sShowHUDAttachments && !gDisconnected && setup_hud_matrices()) + { + LLPipeline::sRenderingHUDs = true; + LLCamera hud_cam = *LLViewerCamera::getInstance(); + hud_cam.setOrigin(-1.f,0,0); + hud_cam.setAxes(LLVector3(1,0,0), LLVector3(0,1,0), LLVector3(0,0,1)); + LLViewerCamera::updateFrustumPlanes(hud_cam, true); + + bool render_particles = gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_PARTICLES) && gSavedSettings.getBOOL("RenderHUDParticles"); + + //only render hud objects + gPipeline.pushRenderTypeMask(); + + // turn off everything + gPipeline.andRenderTypeMask(LLPipeline::END_RENDER_TYPES); + // turn on HUD + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_HUD); + // turn on HUD particles + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_HUD_PARTICLES); + + // if particles are off, turn off hud-particles as well + if (!render_particles) + { + // turn back off HUD particles + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_HUD_PARTICLES); + } + + bool has_ui = gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI); + if (has_ui) + { + gPipeline.toggleRenderDebugFeature(LLPipeline::RENDER_DEBUG_FEATURE_UI); + } + + S32 use_occlusion = LLPipeline::sUseOcclusion; + LLPipeline::sUseOcclusion = 0; + + //cull, sort, and render hud objects + static LLCullResult result; + LLSpatialGroup::sNoDelete = true; + + LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD; + gPipeline.updateCull(hud_cam, result); + + // Toggle render types + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_BUMP); + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_SIMPLE); + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_VOLUME); + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_ALPHA); + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_ALPHA_PRE_WATER); + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_ALPHA_MASK); + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_FULLBRIGHT_ALPHA_MASK); + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_FULLBRIGHT); + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_GLTF_PBR); + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_GLTF_PBR_ALPHA_MASK); + + // Toggle render passes + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_ALPHA); + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_ALPHA_MASK); + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_BUMP); + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_MATERIAL); + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT); + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_ALPHA_MASK); + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_SHINY); + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_SHINY); + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_INVISIBLE); + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_INVISI_SHINY); + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_GLTF_PBR); + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_PASS_GLTF_PBR_ALPHA_MASK); + + gPipeline.stateSort(hud_cam, result); + + gPipeline.renderGeomPostDeferred(hud_cam); + + LLSpatialGroup::sNoDelete = false; + //gPipeline.clearReferences(); + + render_hud_elements(); + + //restore type mask + gPipeline.popRenderTypeMask(); + + if (has_ui) + { + gPipeline.toggleRenderDebugFeature(LLPipeline::RENDER_DEBUG_FEATURE_UI); + } + LLPipeline::sUseOcclusion = use_occlusion; + LLPipeline::sRenderingHUDs = false; + } + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.popMatrix(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.popMatrix(); + + set_current_projection(current_proj); + set_current_modelview(current_mod); +} + +LLRect get_whole_screen_region() +{ + LLRect whole_screen = gViewerWindow->getWorldViewRectScaled(); + + // apply camera zoom transform (for high res screenshots) + F32 zoom_factor = LLViewerCamera::getInstance()->getZoomFactor(); + S16 sub_region = LLViewerCamera::getInstance()->getZoomSubRegion(); + if (zoom_factor > 1.f) + { + S32 num_horizontal_tiles = llceil(zoom_factor); + S32 tile_width = ll_round((F32)gViewerWindow->getWorldViewWidthScaled() / zoom_factor); + S32 tile_height = ll_round((F32)gViewerWindow->getWorldViewHeightScaled() / zoom_factor); + int tile_y = sub_region / num_horizontal_tiles; + int tile_x = sub_region - (tile_y * num_horizontal_tiles); + + whole_screen.setLeftTopAndSize(tile_x * tile_width, gViewerWindow->getWorldViewHeightScaled() - (tile_y * tile_height), tile_width, tile_height); + } + return whole_screen; +} + +bool get_hud_matrices(const LLRect& screen_region, glh::matrix4f &proj, glh::matrix4f &model) +{ + if (isAgentAvatarValid() && gAgentAvatarp->hasHUDAttachment()) + { + F32 zoom_level = gAgentCamera.mHUDCurZoom; + LLBBox hud_bbox = gAgentAvatarp->getHUDBBox(); + + F32 hud_depth = llmax(1.f, hud_bbox.getExtentLocal().mV[VX] * 1.1f); + proj = gl_ortho(-0.5f * LLViewerCamera::getInstance()->getAspect(), 0.5f * LLViewerCamera::getInstance()->getAspect(), -0.5f, 0.5f, 0.f, hud_depth); + proj.element(2,2) = -0.01f; + + F32 aspect_ratio = LLViewerCamera::getInstance()->getAspect(); + + glh::matrix4f mat; + F32 scale_x = (F32)gViewerWindow->getWorldViewWidthScaled() / (F32)screen_region.getWidth(); + F32 scale_y = (F32)gViewerWindow->getWorldViewHeightScaled() / (F32)screen_region.getHeight(); + mat.set_scale(glh::vec3f(scale_x, scale_y, 1.f)); + mat.set_translate( + glh::vec3f(clamp_rescale((F32)(screen_region.getCenterX() - screen_region.mLeft), 0.f, (F32)gViewerWindow->getWorldViewWidthScaled(), 0.5f * scale_x * aspect_ratio, -0.5f * scale_x * aspect_ratio), + clamp_rescale((F32)(screen_region.getCenterY() - screen_region.mBottom), 0.f, (F32)gViewerWindow->getWorldViewHeightScaled(), 0.5f * scale_y, -0.5f * scale_y), + 0.f)); + proj *= mat; + + glh::matrix4f tmp_model((GLfloat*) OGL_TO_CFR_ROTATION); + + mat.set_scale(glh::vec3f(zoom_level, zoom_level, zoom_level)); + mat.set_translate(glh::vec3f(-hud_bbox.getCenterLocal().mV[VX] + (hud_depth * 0.5f), 0.f, 0.f)); + + tmp_model *= mat; + model = tmp_model; + return true; + } + else + { + return false; + } +} + +bool get_hud_matrices(glh::matrix4f &proj, glh::matrix4f &model) +{ + LLRect whole_screen = get_whole_screen_region(); + return get_hud_matrices(whole_screen, proj, model); +} + +bool setup_hud_matrices() +{ + LLRect whole_screen = get_whole_screen_region(); + return setup_hud_matrices(whole_screen); +} + +bool setup_hud_matrices(const LLRect& screen_region) +{ + glh::matrix4f proj, model; + bool result = get_hud_matrices(screen_region, proj, model); + if (!result) return result; + + // set up transform to keep HUD objects in front of camera + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.loadMatrix(proj.m); + set_current_projection(proj); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.loadMatrix(model.m); + set_current_modelview(model); + return true; +} + +void render_ui(F32 zoom_factor, int subfield) +{ + LLPerfStats::RecordSceneTime T ( LLPerfStats::StatType_t::RENDER_UI ); // render time capture - Primary UI stat can have HUD time overlap (TODO) + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; //LL_RECORD_BLOCK_TIME(FTM_RENDER_UI); + LL_PROFILE_GPU_ZONE("ui"); + LLGLState::checkStates(); + + glh::matrix4f saved_view = get_current_modelview(); + + if (!gSnapshot) + { + gGL.pushMatrix(); + gGL.loadMatrix(gGLLastModelView); + set_current_modelview(copy_matrix(gGLLastModelView)); + } + + if(LLSceneMonitor::getInstance()->needsUpdate()) + { + gGL.pushMatrix(); + gViewerWindow->setup2DRender(); + LLSceneMonitor::getInstance()->compare(); + gViewerWindow->setup3DRender(); + gGL.popMatrix(); + } + + // apply gamma correction and post effects + gPipeline.renderFinalize(); + + { + LLGLState::checkStates(); + + + LL_PROFILE_ZONE_NAMED_CATEGORY_UI("HUD"); + render_hud_elements(); + LLGLState::checkStates(); + render_hud_attachments(); + + LLGLState::checkStates(); + + LLGLSDefault gls_default; + LLGLSUIDefault gls_ui; + { + gPipeline.disableLights(); + } + + bool render_ui = gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI); + if (render_ui) + { + if (!gDisconnected) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_UI("UI 3D"); //LL_RECORD_BLOCK_TIME(FTM_RENDER_UI_3D); + LLGLState::checkStates(); + render_ui_3d(); + LLGLState::checkStates(); + } + else + { + render_disconnected_background(); + } + } + + if (render_ui) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_UI("UI 2D"); //LL_RECORD_BLOCK_TIME(FTM_RENDER_UI_2D); + LLHUDObject::renderAll(); + render_ui_2d(); + } + + gViewerWindow->setup2DRender(); + gViewerWindow->updateDebugText(); + gViewerWindow->drawDebugText(); + } + + if (!gSnapshot) + { + set_current_modelview(saved_view); + gGL.popMatrix(); + } +} + +void swap() +{ + LLPerfStats::RecordSceneTime T ( LLPerfStats::StatType_t::RENDER_SWAP ); // render time capture - Swap buffer time - can signify excessive data transfer to/from GPU + LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("Swap"); + LL_PROFILE_GPU_ZONE("swap"); + if (gDisplaySwapBuffers) + { + gViewerWindow->getWindow()->swapBuffers(); + } + gDisplaySwapBuffers = true; +} + +void renderCoordinateAxes() +{ + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gGL.begin(LLRender::LINES); + gGL.color3f(1.0f, 0.0f, 0.0f); // i direction = X-Axis = red + gGL.vertex3f(0.0f, 0.0f, 0.0f); + gGL.vertex3f(2.0f, 0.0f, 0.0f); + gGL.vertex3f(3.0f, 0.0f, 0.0f); + gGL.vertex3f(5.0f, 0.0f, 0.0f); + gGL.vertex3f(6.0f, 0.0f, 0.0f); + gGL.vertex3f(8.0f, 0.0f, 0.0f); + // Make an X + gGL.vertex3f(11.0f, 1.0f, 1.0f); + gGL.vertex3f(11.0f, -1.0f, -1.0f); + gGL.vertex3f(11.0f, 1.0f, -1.0f); + gGL.vertex3f(11.0f, -1.0f, 1.0f); + + gGL.color3f(0.0f, 1.0f, 0.0f); // j direction = Y-Axis = green + gGL.vertex3f(0.0f, 0.0f, 0.0f); + gGL.vertex3f(0.0f, 2.0f, 0.0f); + gGL.vertex3f(0.0f, 3.0f, 0.0f); + gGL.vertex3f(0.0f, 5.0f, 0.0f); + gGL.vertex3f(0.0f, 6.0f, 0.0f); + gGL.vertex3f(0.0f, 8.0f, 0.0f); + // Make a Y + gGL.vertex3f(1.0f, 11.0f, 1.0f); + gGL.vertex3f(0.0f, 11.0f, 0.0f); + gGL.vertex3f(-1.0f, 11.0f, 1.0f); + gGL.vertex3f(0.0f, 11.0f, 0.0f); + gGL.vertex3f(0.0f, 11.0f, 0.0f); + gGL.vertex3f(0.0f, 11.0f, -1.0f); + + gGL.color3f(0.0f, 0.0f, 1.0f); // Z-Axis = blue + gGL.vertex3f(0.0f, 0.0f, 0.0f); + gGL.vertex3f(0.0f, 0.0f, 2.0f); + gGL.vertex3f(0.0f, 0.0f, 3.0f); + gGL.vertex3f(0.0f, 0.0f, 5.0f); + gGL.vertex3f(0.0f, 0.0f, 6.0f); + gGL.vertex3f(0.0f, 0.0f, 8.0f); + // Make a Z + gGL.vertex3f(-1.0f, 1.0f, 11.0f); + gGL.vertex3f(1.0f, 1.0f, 11.0f); + gGL.vertex3f(1.0f, 1.0f, 11.0f); + gGL.vertex3f(-1.0f, -1.0f, 11.0f); + gGL.vertex3f(-1.0f, -1.0f, 11.0f); + gGL.vertex3f(1.0f, -1.0f, 11.0f); + gGL.end(); +} + + +void draw_axes() +{ + LLGLSUIDefault gls_ui; + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + // A vertical white line at origin + LLVector3 v = gAgent.getPositionAgent(); + gGL.begin(LLRender::LINES); + gGL.color3f(1.0f, 1.0f, 1.0f); + gGL.vertex3f(0.0f, 0.0f, 0.0f); + gGL.vertex3f(0.0f, 0.0f, 40.0f); + gGL.end(); + // Some coordinate axes + gGL.pushMatrix(); + gGL.translatef( v.mV[VX], v.mV[VY], v.mV[VZ] ); + renderCoordinateAxes(); + gGL.popMatrix(); +} + +void render_ui_3d() +{ + LLGLSPipeline gls_pipeline; + + ////////////////////////////////////// + // + // Render 3D UI elements + // NOTE: zbuffer is cleared before we get here by LLDrawPoolHUD, + // so 3d elements requiring Z buffer are moved to LLDrawPoolHUD + // + + ///////////////////////////////////////////////////////////// + // + // Render 2.5D elements (2D elements in the world) + // Stuff without z writes + // + + // Debugging stuff goes before the UI. + + stop_glerror(); + + gUIProgram.bind(); + gGL.color4f(1, 1, 1, 1); + + // Coordinate axes + if (gSavedSettings.getBOOL("ShowAxes")) + { + draw_axes(); + } + + gViewerWindow->renderSelections(false, false, true); // Non HUD call in render_hud_elements + + if (gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) + { + // Render debugging beacons. + gObjectList.renderObjectBeacons(); + gObjectList.resetObjectBeacons(); + gSky.addSunMoonBeacons(); + } + + stop_glerror(); +} + +void render_ui_2d() +{ + LLGLSUIDefault gls_ui; + + ///////////////////////////////////////////////////////////// + // + // Render 2D UI elements that overlay the world (no z compare) + + // Disable wireframe mode below here, as this is HUD/menus + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + + // Menu overlays, HUD, etc + gViewerWindow->setup2DRender(); + + F32 zoom_factor = LLViewerCamera::getInstance()->getZoomFactor(); + S16 sub_region = LLViewerCamera::getInstance()->getZoomSubRegion(); + + if (zoom_factor > 1.f) + { + //decompose subregion number to x and y values + int pos_y = sub_region / llceil(zoom_factor); + int pos_x = sub_region - (pos_y*llceil(zoom_factor)); + // offset for this tile + LLFontGL::sCurOrigin.mX -= ll_round((F32)gViewerWindow->getWindowWidthScaled() * (F32)pos_x / zoom_factor); + LLFontGL::sCurOrigin.mY -= ll_round((F32)gViewerWindow->getWindowHeightScaled() * (F32)pos_y / zoom_factor); + } + + stop_glerror(); + + // render outline for HUD + if (isAgentAvatarValid() && gAgentCamera.mHUDCurZoom < 0.98f) + { + gUIProgram.bind(); + gGL.pushMatrix(); + S32 half_width = (gViewerWindow->getWorldViewWidthScaled() / 2); + S32 half_height = (gViewerWindow->getWorldViewHeightScaled() / 2); + gGL.scalef(LLUI::getScaleFactor().mV[0], LLUI::getScaleFactor().mV[1], 1.f); + gGL.translatef((F32)half_width, (F32)half_height, 0.f); + F32 zoom = gAgentCamera.mHUDCurZoom; + gGL.scalef(zoom,zoom,1.f); + gGL.color4fv(LLColor4::white.mV); + gl_rect_2d(-half_width, half_height, half_width, -half_height, false); + gGL.popMatrix(); + gUIProgram.unbind(); + stop_glerror(); + } + + + if (gSavedSettings.getBOOL("RenderUIBuffer")) + { + if (LLView::sIsRectDirty) + { + LLView::sIsRectDirty = false; + LLRect t_rect; + + gPipeline.mRT->uiScreen.bindTarget(); + gGL.setColorMask(true, true); + { + static const S32 pad = 8; + + LLView::sDirtyRect.mLeft -= pad; + LLView::sDirtyRect.mRight += pad; + LLView::sDirtyRect.mBottom -= pad; + LLView::sDirtyRect.mTop += pad; + + LLGLEnable scissor(GL_SCISSOR_TEST); + static LLRect last_rect = LLView::sDirtyRect; + + //union with last rect to avoid mouse poop + last_rect.unionWith(LLView::sDirtyRect); + + t_rect = LLView::sDirtyRect; + LLView::sDirtyRect = last_rect; + last_rect = t_rect; + + last_rect.mLeft = LLRect::tCoordType(last_rect.mLeft / LLUI::getScaleFactor().mV[0]); + last_rect.mRight = LLRect::tCoordType(last_rect.mRight / LLUI::getScaleFactor().mV[0]); + last_rect.mTop = LLRect::tCoordType(last_rect.mTop / LLUI::getScaleFactor().mV[1]); + last_rect.mBottom = LLRect::tCoordType(last_rect.mBottom / LLUI::getScaleFactor().mV[1]); + + LLRect clip_rect(last_rect); + + glClear(GL_COLOR_BUFFER_BIT); + + gViewerWindow->draw(); + } + + gPipeline.mRT->uiScreen.flush(); + gGL.setColorMask(true, false); + + LLView::sDirtyRect = t_rect; + } + + LLGLDisable cull(GL_CULL_FACE); + LLGLDisable blend(GL_BLEND); + S32 width = gViewerWindow->getWindowWidthScaled(); + S32 height = gViewerWindow->getWindowHeightScaled(); + gGL.getTexUnit(0)->bind(&gPipeline.mRT->uiScreen); + gGL.begin(LLRender::TRIANGLE_STRIP); + gGL.color4f(1,1,1,1); + gGL.texCoord2f(0, 0); gGL.vertex2i(0, 0); + gGL.texCoord2f(width, 0); gGL.vertex2i(width, 0); + gGL.texCoord2f(0, height); gGL.vertex2i(0, height); + gGL.texCoord2f(width, height); gGL.vertex2i(width, height); + gGL.end(); + } + else + { + gViewerWindow->draw(); + } + + + + // reset current origin for font rendering, in case of tiling render + LLFontGL::sCurOrigin.set(0, 0); +} + +void render_disconnected_background() +{ + gUIProgram.bind(); + + gGL.color4f(1,1,1,1); + if (!gDisconnectedImagep && gDisconnected) + { + LL_INFOS() << "Loading last bitmap..." << LL_ENDL; + + std::string temp_str; + temp_str = gDirUtilp->getLindenUserDir() + gDirUtilp->getDirDelimiter() + LLStartUp::getScreenLastFilename(); + + LLPointer image_png = new LLImagePNG; + if( !image_png->load(temp_str) ) + { + //LL_INFOS() << "Bitmap load failed" << LL_ENDL; + return; + } + + LLPointer raw = new LLImageRaw; + if (!image_png->decode(raw, 0.0f)) + { + LL_INFOS() << "Bitmap decode failed" << LL_ENDL; + gDisconnectedImagep = NULL; + return; + } + + U8 *rawp = raw->getData(); + S32 npixels = (S32)image_png->getWidth()*(S32)image_png->getHeight(); + for (S32 i = 0; i < npixels; i++) + { + S32 sum = 0; + sum = *rawp + *(rawp+1) + *(rawp+2); + sum /= 3; + *rawp = ((S32)sum*6 + *rawp)/7; + rawp++; + *rawp = ((S32)sum*6 + *rawp)/7; + rawp++; + *rawp = ((S32)sum*6 + *rawp)/7; + rawp++; + } + + + raw->expandToPowerOfTwo(); + gDisconnectedImagep = LLViewerTextureManager::getLocalTexture(raw.get(), false ); + gStartTexture = gDisconnectedImagep; + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + } + + // Make sure the progress view always fills the entire window. + S32 width = gViewerWindow->getWindowWidthScaled(); + S32 height = gViewerWindow->getWindowHeightScaled(); + + if (gDisconnectedImagep) + { + LLGLSUIDefault gls_ui; + gViewerWindow->setup2DRender(); + gGL.pushMatrix(); + { + // scale ui to reflect UIScaleFactor + // this can't be done in setup2DRender because it requires a + // pushMatrix/popMatrix pair + const LLVector2& display_scale = gViewerWindow->getDisplayScale(); + gGL.scalef(display_scale.mV[VX], display_scale.mV[VY], 1.f); + + gGL.getTexUnit(0)->bind(gDisconnectedImagep); + gGL.color4f(1.f, 1.f, 1.f, 1.f); + gl_rect_2d_simple_tex(width, height); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + } + gGL.popMatrix(); + } + gGL.flush(); + + gUIProgram.unbind(); +} + +void display_cleanup() +{ + gDisconnectedImagep = NULL; +} + diff --git a/indra/newview/llviewerdisplay.h b/indra/newview/llviewerdisplay.h index 32a28251b9..673f51600d 100644 --- a/indra/newview/llviewerdisplay.h +++ b/indra/newview/llviewerdisplay.h @@ -1,46 +1,46 @@ -/** - * @file llviewerdisplay.h - * @brief LLViewerDisplay class header file - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVIEWERDISPLAY_H -#define LL_LLVIEWERDISPLAY_H - -class LLPostProcess; - -void display_startup(); -void display_cleanup(); - -void display(bool rebuild = true, F32 zoom_factor = 1.f, int subfield = 0, bool for_snapshot = false); - -extern bool gDisplaySwapBuffers; -extern bool gDepthDirty; -extern bool gTeleportDisplay; -extern LLFrameTimer gTeleportDisplayTimer; -extern bool gForceRenderLandFence; -extern bool gResizeScreenTexture; -extern bool gResizeShadowTexture; -extern bool gWindowResized; - -#endif // LL_LLVIEWERDISPLAY_H +/** + * @file llviewerdisplay.h + * @brief LLViewerDisplay class header file + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVIEWERDISPLAY_H +#define LL_LLVIEWERDISPLAY_H + +class LLPostProcess; + +void display_startup(); +void display_cleanup(); + +void display(bool rebuild = true, F32 zoom_factor = 1.f, int subfield = 0, bool for_snapshot = false); + +extern bool gDisplaySwapBuffers; +extern bool gDepthDirty; +extern bool gTeleportDisplay; +extern LLFrameTimer gTeleportDisplayTimer; +extern bool gForceRenderLandFence; +extern bool gResizeScreenTexture; +extern bool gResizeShadowTexture; +extern bool gWindowResized; + +#endif // LL_LLVIEWERDISPLAY_H diff --git a/indra/newview/llviewerfoldertype.cpp b/indra/newview/llviewerfoldertype.cpp index 8caa6df9cf..e9c3277721 100644 --- a/indra/newview/llviewerfoldertype.cpp +++ b/indra/newview/llviewerfoldertype.cpp @@ -1,321 +1,321 @@ -/** - * @file llfoldertype.cpp - * @brief Implementation of LLViewerFolderType functionality. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llviewerfoldertype.h" -#include "lldictionary.h" -#include "llmemory.h" -#include "llvisualparam.h" -#include "llcontrol.h" - -extern LLControlGroup gSavedSettings; - -static const std::string empty_string; - -struct ViewerFolderEntry : public LLDictionaryEntry -{ - // Constructor for non-ensembles - ViewerFolderEntry(const std::string &new_category_name, // default name when creating a new category of this type - const std::string &icon_name_open, // name of the folder icon - const std::string &icon_name_closed, - bool is_quiet, // folder doesn't need a UI update when changed - bool hide_if_empty, // folder not shown if empty - const std::string &dictionary_name = empty_string // no reverse lookup needed on non-ensembles, so in most cases just leave this blank - ) - : - LLDictionaryEntry(dictionary_name), - mNewCategoryName(new_category_name), - mIconNameOpen(icon_name_open), - mIconNameClosed(icon_name_closed), - mIsQuiet(is_quiet), - mHideIfEmpty(hide_if_empty) - { - mAllowedNames.clear(); - } - - // Constructor for ensembles - ViewerFolderEntry(const std::string &xui_name, // name of the xui menu item - const std::string &new_category_name, // default name when creating a new category of this type - const std::string &icon_name, // name of the folder icon - const std::string allowed_names // allowed item typenames for this folder type - ) - : - LLDictionaryEntry(xui_name), - /* Just use default icons until we actually support ensembles - mIconNameOpen(icon_name), - mIconNameClosed(icon_name), - */ - mIconNameOpen("Inv_FolderOpen"), mIconNameClosed("Inv_FolderClosed"), - mNewCategoryName(new_category_name), - mIsQuiet(false), - mHideIfEmpty(false) - { - const std::string delims (","); - LLStringUtilBase::getTokens(allowed_names, mAllowedNames, delims); - } - - bool getIsAllowedName(const std::string &name) const - { - if (mAllowedNames.empty()) - return false; - for (name_vec_t::const_iterator iter = mAllowedNames.begin(); - iter != mAllowedNames.end(); - iter++) - { - if (name == (*iter)) - return true; - } - return false; - } - const std::string mIconNameOpen; - const std::string mIconNameClosed; - const std::string mNewCategoryName; - typedef std::vector name_vec_t; - name_vec_t mAllowedNames; - bool mIsQuiet; - bool mHideIfEmpty; -}; - -class LLViewerFolderDictionary : public LLSingleton, - public LLDictionary -{ - LLSINGLETON(LLViewerFolderDictionary); -protected: - bool initEnsemblesFromFile(); // Reads in ensemble information from foldertypes.xml -}; - -LLViewerFolderDictionary::LLViewerFolderDictionary() -{ - // NEW CATEGORY NAME FOLDER OPEN FOLDER CLOSED QUIET? HIDE IF EMPTY? - // |-------------------------|-----------------------|----------------------|-----------|--------------| - addEntry(LLFolderType::FT_TEXTURE, new ViewerFolderEntry("Textures", "Inv_SysOpen", "Inv_SysClosed", false, true)); - addEntry(LLFolderType::FT_SOUND, new ViewerFolderEntry("Sounds", "Inv_SysOpen", "Inv_SysClosed", false, true)); - addEntry(LLFolderType::FT_CALLINGCARD, new ViewerFolderEntry("Calling Cards", "Inv_SysOpen", "Inv_SysClosed", false, true)); - addEntry(LLFolderType::FT_LANDMARK, new ViewerFolderEntry("Landmarks", "Inv_SysOpen", "Inv_SysClosed", false, true)); - addEntry(LLFolderType::FT_CLOTHING, new ViewerFolderEntry("Clothing", "Inv_SysOpen", "Inv_SysClosed", false, true)); - addEntry(LLFolderType::FT_OBJECT, new ViewerFolderEntry("Objects", "Inv_SysOpen", "Inv_SysClosed", false, true)); - addEntry(LLFolderType::FT_NOTECARD, new ViewerFolderEntry("Notecards", "Inv_SysOpen", "Inv_SysClosed", false, true)); - addEntry(LLFolderType::FT_ROOT_INVENTORY, new ViewerFolderEntry("My Inventory", "Inv_SysOpen", "Inv_SysClosed", false, false)); - addEntry(LLFolderType::FT_LSL_TEXT, new ViewerFolderEntry("Scripts", "Inv_SysOpen", "Inv_SysClosed", false, true)); - addEntry(LLFolderType::FT_BODYPART, new ViewerFolderEntry("Body Parts", "Inv_SysOpen", "Inv_SysClosed", false, true)); - addEntry(LLFolderType::FT_TRASH, new ViewerFolderEntry("Trash", "Inv_TrashOpen", "Inv_TrashClosed", true, false)); - addEntry(LLFolderType::FT_SNAPSHOT_CATEGORY, new ViewerFolderEntry("Photo Album", "Inv_SysOpen", "Inv_SysClosed", false, true)); - addEntry(LLFolderType::FT_LOST_AND_FOUND, new ViewerFolderEntry("Lost And Found", "Inv_LostOpen", "Inv_LostClosed", true, true)); - addEntry(LLFolderType::FT_ANIMATION, new ViewerFolderEntry("Animations", "Inv_SysOpen", "Inv_SysClosed", false, true)); - addEntry(LLFolderType::FT_GESTURE, new ViewerFolderEntry("Gestures", "Inv_SysOpen", "Inv_SysClosed", false, true)); - addEntry(LLFolderType::FT_FAVORITE, new ViewerFolderEntry("Favorites", "Inv_SysOpen", "Inv_SysClosed", false, true)); - - addEntry(LLFolderType::FT_CURRENT_OUTFIT, new ViewerFolderEntry("Current Outfit", "Inv_SysOpen", "Inv_SysClosed", true, false)); - addEntry(LLFolderType::FT_OUTFIT, new ViewerFolderEntry("New Outfit", "Inv_LookFolderOpen", "Inv_LookFolderClosed", true, false)); - addEntry(LLFolderType::FT_MY_OUTFITS, new ViewerFolderEntry("My Outfits", "Inv_SysOpen", "Inv_SysClosed", true, true)); - addEntry(LLFolderType::FT_MESH, new ViewerFolderEntry("Meshes", "Inv_SysOpen", "Inv_SysClosed", false, true)); - addEntry(LLFolderType::FT_SETTINGS, new ViewerFolderEntry("Settings", "Inv_SysOpen", "Inv_SysClosed", false, true)); - addEntry(LLFolderType::FT_MATERIAL, new ViewerFolderEntry("Materials", "Inv_SysOpen", "Inv_SysClosed", false, true)); - - bool boxes_invisible = !gSavedSettings.getBOOL("InventoryOutboxMakeVisible"); - addEntry(LLFolderType::FT_INBOX, new ViewerFolderEntry("Received Items", "Inv_SysOpen", "Inv_SysClosed", false, boxes_invisible)); - addEntry(LLFolderType::FT_OUTBOX, new ViewerFolderEntry("Merchant Outbox", "Inv_SysOpen", "Inv_SysClosed", false, true)); - - addEntry(LLFolderType::FT_BASIC_ROOT, new ViewerFolderEntry("Basic Root", "Inv_SysOpen", "Inv_SysClosed", false, true)); - - addEntry(LLFolderType::FT_MARKETPLACE_LISTINGS, new ViewerFolderEntry("Marketplace Listings", "Inv_SysOpen", "Inv_SysClosed", false, boxes_invisible)); - addEntry(LLFolderType::FT_MARKETPLACE_STOCK, new ViewerFolderEntry("New Stock", "Inv_StockFolderOpen", "Inv_StockFolderClosed", false, false, "default")); - addEntry(LLFolderType::FT_MARKETPLACE_VERSION, new ViewerFolderEntry("New Version", "Inv_VersionFolderOpen","Inv_VersionFolderClosed", false, false, "default")); - - addEntry(LLFolderType::FT_NONE, new ViewerFolderEntry("New Folder", "Inv_FolderOpen", "Inv_FolderClosed", false, false, "default")); - - for (U32 type = (U32)LLFolderType::FT_ENSEMBLE_START; type <= (U32)LLFolderType::FT_ENSEMBLE_END; ++type) - { - addEntry((LLFolderType::EType)type, new ViewerFolderEntry("New Folder", "Inv_FolderOpen", "Inv_FolderClosed", false, false)); - } -} - -bool LLViewerFolderDictionary::initEnsemblesFromFile() -{ - std::string xml_filename = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS,"foldertypes.xml"); - LLXmlTree folder_def; - if (!folder_def.parseFile(xml_filename)) - { - LL_ERRS() << "Failed to parse folders file " << xml_filename << LL_ENDL; - return false; - } - - LLXmlTreeNode* rootp = folder_def.getRoot(); - for (LLXmlTreeNode* ensemble = rootp->getFirstChild(); - ensemble; - ensemble = rootp->getNextChild()) - { - if (!ensemble->hasName("ensemble")) - { - LL_WARNS() << "Invalid ensemble definition node " << ensemble->getName() << LL_ENDL; - continue; - } - - S32 ensemble_type; - static LLStdStringHandle ensemble_num_string = LLXmlTree::addAttributeString("foldertype_num"); - if (!ensemble->getFastAttributeS32(ensemble_num_string, ensemble_type)) - { - LL_WARNS() << "No ensemble type defined" << LL_ENDL; - continue; - } - - - if (ensemble_type < S32(LLFolderType::FT_ENSEMBLE_START) || ensemble_type > S32(LLFolderType::FT_ENSEMBLE_END)) - { - LL_WARNS() << "Exceeded maximum ensemble index" << LLFolderType::FT_ENSEMBLE_END << LL_ENDL; - break; - } - - std::string xui_name; - static LLStdStringHandle xui_name_string = LLXmlTree::addAttributeString("xui_name"); - if (!ensemble->getFastAttributeString(xui_name_string, xui_name)) - { - LL_WARNS() << "No xui name defined" << LL_ENDL; - continue; - } - - std::string icon_name; - static LLStdStringHandle icon_name_string = LLXmlTree::addAttributeString("icon_name"); - if (!ensemble->getFastAttributeString(icon_name_string, icon_name)) - { - LL_WARNS() << "No ensemble icon name defined" << LL_ENDL; - continue; - } - - std::string allowed_names; - static LLStdStringHandle allowed_names_string = LLXmlTree::addAttributeString("allowed"); - if (!ensemble->getFastAttributeString(allowed_names_string, allowed_names)) - { - } - - // Add the entry and increment the asset number. - const static std::string new_ensemble_name = "New Ensemble"; - addEntry(LLFolderType::EType(ensemble_type), new ViewerFolderEntry(xui_name, new_ensemble_name, icon_name, allowed_names)); - } - - return true; -} - - -const std::string &LLViewerFolderType::lookupXUIName(LLFolderType::EType folder_type) -{ - const ViewerFolderEntry *entry = LLViewerFolderDictionary::getInstance()->lookup(folder_type); - if (entry) - { - return entry->mName; - } - return badLookup(); -} - -LLFolderType::EType LLViewerFolderType::lookupTypeFromXUIName(const std::string &name) -{ - return LLViewerFolderDictionary::getInstance()->lookup(name); -} - -const std::string &LLViewerFolderType::lookupIconName(LLFolderType::EType folder_type, bool is_open) -{ - const ViewerFolderEntry *entry = LLViewerFolderDictionary::getInstance()->lookup(folder_type); - if (entry) - { - if (is_open) - return entry->mIconNameOpen; - else - return entry->mIconNameClosed; - } - - // Error condition. Return something so that we don't show a grey box in inventory view. - const ViewerFolderEntry *default_entry = LLViewerFolderDictionary::getInstance()->lookup(LLFolderType::FT_NONE); - if (default_entry) - { - return default_entry->mIconNameClosed; - } - - // Should not get here unless there's something corrupted with the FT_NONE entry. - return badLookup(); -} - -bool LLViewerFolderType::lookupIsQuietType(LLFolderType::EType folder_type) -{ - const ViewerFolderEntry *entry = LLViewerFolderDictionary::getInstance()->lookup(folder_type); - if (entry) - { - return entry->mIsQuiet; - } - return false; -} - -bool LLViewerFolderType::lookupIsHiddenIfEmpty(LLFolderType::EType folder_type) -{ - const ViewerFolderEntry *entry = LLViewerFolderDictionary::getInstance()->lookup(folder_type); - if (entry) - { - return entry->mHideIfEmpty; - } - return false; -} - -const std::string &LLViewerFolderType::lookupNewCategoryName(LLFolderType::EType folder_type) -{ - const ViewerFolderEntry *entry = LLViewerFolderDictionary::getInstance()->lookup(folder_type); - if (entry) - { - return entry->mNewCategoryName; - } - return badLookup(); -} - -LLFolderType::EType LLViewerFolderType::lookupTypeFromNewCategoryName(const std::string& name) -{ - for (LLViewerFolderDictionary::const_iterator iter = LLViewerFolderDictionary::getInstance()->begin(); - iter != LLViewerFolderDictionary::getInstance()->end(); - iter++) - { - const ViewerFolderEntry *entry = iter->second; - if (entry->mNewCategoryName == name) - { - return iter->first; - } - } - return FT_NONE; -} - - -U64 LLViewerFolderType::lookupValidFolderTypes(const std::string& item_name) -{ - U64 matching_folders = 0; - for (LLViewerFolderDictionary::const_iterator iter = LLViewerFolderDictionary::getInstance()->begin(); - iter != LLViewerFolderDictionary::getInstance()->end(); - iter++) - { - const ViewerFolderEntry *entry = iter->second; - if (entry->getIsAllowedName(item_name)) - { - matching_folders |= 1LL << iter->first; - } - } - return matching_folders; -} +/** + * @file llfoldertype.cpp + * @brief Implementation of LLViewerFolderType functionality. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llviewerfoldertype.h" +#include "lldictionary.h" +#include "llmemory.h" +#include "llvisualparam.h" +#include "llcontrol.h" + +extern LLControlGroup gSavedSettings; + +static const std::string empty_string; + +struct ViewerFolderEntry : public LLDictionaryEntry +{ + // Constructor for non-ensembles + ViewerFolderEntry(const std::string &new_category_name, // default name when creating a new category of this type + const std::string &icon_name_open, // name of the folder icon + const std::string &icon_name_closed, + bool is_quiet, // folder doesn't need a UI update when changed + bool hide_if_empty, // folder not shown if empty + const std::string &dictionary_name = empty_string // no reverse lookup needed on non-ensembles, so in most cases just leave this blank + ) + : + LLDictionaryEntry(dictionary_name), + mNewCategoryName(new_category_name), + mIconNameOpen(icon_name_open), + mIconNameClosed(icon_name_closed), + mIsQuiet(is_quiet), + mHideIfEmpty(hide_if_empty) + { + mAllowedNames.clear(); + } + + // Constructor for ensembles + ViewerFolderEntry(const std::string &xui_name, // name of the xui menu item + const std::string &new_category_name, // default name when creating a new category of this type + const std::string &icon_name, // name of the folder icon + const std::string allowed_names // allowed item typenames for this folder type + ) + : + LLDictionaryEntry(xui_name), + /* Just use default icons until we actually support ensembles + mIconNameOpen(icon_name), + mIconNameClosed(icon_name), + */ + mIconNameOpen("Inv_FolderOpen"), mIconNameClosed("Inv_FolderClosed"), + mNewCategoryName(new_category_name), + mIsQuiet(false), + mHideIfEmpty(false) + { + const std::string delims (","); + LLStringUtilBase::getTokens(allowed_names, mAllowedNames, delims); + } + + bool getIsAllowedName(const std::string &name) const + { + if (mAllowedNames.empty()) + return false; + for (name_vec_t::const_iterator iter = mAllowedNames.begin(); + iter != mAllowedNames.end(); + iter++) + { + if (name == (*iter)) + return true; + } + return false; + } + const std::string mIconNameOpen; + const std::string mIconNameClosed; + const std::string mNewCategoryName; + typedef std::vector name_vec_t; + name_vec_t mAllowedNames; + bool mIsQuiet; + bool mHideIfEmpty; +}; + +class LLViewerFolderDictionary : public LLSingleton, + public LLDictionary +{ + LLSINGLETON(LLViewerFolderDictionary); +protected: + bool initEnsemblesFromFile(); // Reads in ensemble information from foldertypes.xml +}; + +LLViewerFolderDictionary::LLViewerFolderDictionary() +{ + // NEW CATEGORY NAME FOLDER OPEN FOLDER CLOSED QUIET? HIDE IF EMPTY? + // |-------------------------|-----------------------|----------------------|-----------|--------------| + addEntry(LLFolderType::FT_TEXTURE, new ViewerFolderEntry("Textures", "Inv_SysOpen", "Inv_SysClosed", false, true)); + addEntry(LLFolderType::FT_SOUND, new ViewerFolderEntry("Sounds", "Inv_SysOpen", "Inv_SysClosed", false, true)); + addEntry(LLFolderType::FT_CALLINGCARD, new ViewerFolderEntry("Calling Cards", "Inv_SysOpen", "Inv_SysClosed", false, true)); + addEntry(LLFolderType::FT_LANDMARK, new ViewerFolderEntry("Landmarks", "Inv_SysOpen", "Inv_SysClosed", false, true)); + addEntry(LLFolderType::FT_CLOTHING, new ViewerFolderEntry("Clothing", "Inv_SysOpen", "Inv_SysClosed", false, true)); + addEntry(LLFolderType::FT_OBJECT, new ViewerFolderEntry("Objects", "Inv_SysOpen", "Inv_SysClosed", false, true)); + addEntry(LLFolderType::FT_NOTECARD, new ViewerFolderEntry("Notecards", "Inv_SysOpen", "Inv_SysClosed", false, true)); + addEntry(LLFolderType::FT_ROOT_INVENTORY, new ViewerFolderEntry("My Inventory", "Inv_SysOpen", "Inv_SysClosed", false, false)); + addEntry(LLFolderType::FT_LSL_TEXT, new ViewerFolderEntry("Scripts", "Inv_SysOpen", "Inv_SysClosed", false, true)); + addEntry(LLFolderType::FT_BODYPART, new ViewerFolderEntry("Body Parts", "Inv_SysOpen", "Inv_SysClosed", false, true)); + addEntry(LLFolderType::FT_TRASH, new ViewerFolderEntry("Trash", "Inv_TrashOpen", "Inv_TrashClosed", true, false)); + addEntry(LLFolderType::FT_SNAPSHOT_CATEGORY, new ViewerFolderEntry("Photo Album", "Inv_SysOpen", "Inv_SysClosed", false, true)); + addEntry(LLFolderType::FT_LOST_AND_FOUND, new ViewerFolderEntry("Lost And Found", "Inv_LostOpen", "Inv_LostClosed", true, true)); + addEntry(LLFolderType::FT_ANIMATION, new ViewerFolderEntry("Animations", "Inv_SysOpen", "Inv_SysClosed", false, true)); + addEntry(LLFolderType::FT_GESTURE, new ViewerFolderEntry("Gestures", "Inv_SysOpen", "Inv_SysClosed", false, true)); + addEntry(LLFolderType::FT_FAVORITE, new ViewerFolderEntry("Favorites", "Inv_SysOpen", "Inv_SysClosed", false, true)); + + addEntry(LLFolderType::FT_CURRENT_OUTFIT, new ViewerFolderEntry("Current Outfit", "Inv_SysOpen", "Inv_SysClosed", true, false)); + addEntry(LLFolderType::FT_OUTFIT, new ViewerFolderEntry("New Outfit", "Inv_LookFolderOpen", "Inv_LookFolderClosed", true, false)); + addEntry(LLFolderType::FT_MY_OUTFITS, new ViewerFolderEntry("My Outfits", "Inv_SysOpen", "Inv_SysClosed", true, true)); + addEntry(LLFolderType::FT_MESH, new ViewerFolderEntry("Meshes", "Inv_SysOpen", "Inv_SysClosed", false, true)); + addEntry(LLFolderType::FT_SETTINGS, new ViewerFolderEntry("Settings", "Inv_SysOpen", "Inv_SysClosed", false, true)); + addEntry(LLFolderType::FT_MATERIAL, new ViewerFolderEntry("Materials", "Inv_SysOpen", "Inv_SysClosed", false, true)); + + bool boxes_invisible = !gSavedSettings.getBOOL("InventoryOutboxMakeVisible"); + addEntry(LLFolderType::FT_INBOX, new ViewerFolderEntry("Received Items", "Inv_SysOpen", "Inv_SysClosed", false, boxes_invisible)); + addEntry(LLFolderType::FT_OUTBOX, new ViewerFolderEntry("Merchant Outbox", "Inv_SysOpen", "Inv_SysClosed", false, true)); + + addEntry(LLFolderType::FT_BASIC_ROOT, new ViewerFolderEntry("Basic Root", "Inv_SysOpen", "Inv_SysClosed", false, true)); + + addEntry(LLFolderType::FT_MARKETPLACE_LISTINGS, new ViewerFolderEntry("Marketplace Listings", "Inv_SysOpen", "Inv_SysClosed", false, boxes_invisible)); + addEntry(LLFolderType::FT_MARKETPLACE_STOCK, new ViewerFolderEntry("New Stock", "Inv_StockFolderOpen", "Inv_StockFolderClosed", false, false, "default")); + addEntry(LLFolderType::FT_MARKETPLACE_VERSION, new ViewerFolderEntry("New Version", "Inv_VersionFolderOpen","Inv_VersionFolderClosed", false, false, "default")); + + addEntry(LLFolderType::FT_NONE, new ViewerFolderEntry("New Folder", "Inv_FolderOpen", "Inv_FolderClosed", false, false, "default")); + + for (U32 type = (U32)LLFolderType::FT_ENSEMBLE_START; type <= (U32)LLFolderType::FT_ENSEMBLE_END; ++type) + { + addEntry((LLFolderType::EType)type, new ViewerFolderEntry("New Folder", "Inv_FolderOpen", "Inv_FolderClosed", false, false)); + } +} + +bool LLViewerFolderDictionary::initEnsemblesFromFile() +{ + std::string xml_filename = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS,"foldertypes.xml"); + LLXmlTree folder_def; + if (!folder_def.parseFile(xml_filename)) + { + LL_ERRS() << "Failed to parse folders file " << xml_filename << LL_ENDL; + return false; + } + + LLXmlTreeNode* rootp = folder_def.getRoot(); + for (LLXmlTreeNode* ensemble = rootp->getFirstChild(); + ensemble; + ensemble = rootp->getNextChild()) + { + if (!ensemble->hasName("ensemble")) + { + LL_WARNS() << "Invalid ensemble definition node " << ensemble->getName() << LL_ENDL; + continue; + } + + S32 ensemble_type; + static LLStdStringHandle ensemble_num_string = LLXmlTree::addAttributeString("foldertype_num"); + if (!ensemble->getFastAttributeS32(ensemble_num_string, ensemble_type)) + { + LL_WARNS() << "No ensemble type defined" << LL_ENDL; + continue; + } + + + if (ensemble_type < S32(LLFolderType::FT_ENSEMBLE_START) || ensemble_type > S32(LLFolderType::FT_ENSEMBLE_END)) + { + LL_WARNS() << "Exceeded maximum ensemble index" << LLFolderType::FT_ENSEMBLE_END << LL_ENDL; + break; + } + + std::string xui_name; + static LLStdStringHandle xui_name_string = LLXmlTree::addAttributeString("xui_name"); + if (!ensemble->getFastAttributeString(xui_name_string, xui_name)) + { + LL_WARNS() << "No xui name defined" << LL_ENDL; + continue; + } + + std::string icon_name; + static LLStdStringHandle icon_name_string = LLXmlTree::addAttributeString("icon_name"); + if (!ensemble->getFastAttributeString(icon_name_string, icon_name)) + { + LL_WARNS() << "No ensemble icon name defined" << LL_ENDL; + continue; + } + + std::string allowed_names; + static LLStdStringHandle allowed_names_string = LLXmlTree::addAttributeString("allowed"); + if (!ensemble->getFastAttributeString(allowed_names_string, allowed_names)) + { + } + + // Add the entry and increment the asset number. + const static std::string new_ensemble_name = "New Ensemble"; + addEntry(LLFolderType::EType(ensemble_type), new ViewerFolderEntry(xui_name, new_ensemble_name, icon_name, allowed_names)); + } + + return true; +} + + +const std::string &LLViewerFolderType::lookupXUIName(LLFolderType::EType folder_type) +{ + const ViewerFolderEntry *entry = LLViewerFolderDictionary::getInstance()->lookup(folder_type); + if (entry) + { + return entry->mName; + } + return badLookup(); +} + +LLFolderType::EType LLViewerFolderType::lookupTypeFromXUIName(const std::string &name) +{ + return LLViewerFolderDictionary::getInstance()->lookup(name); +} + +const std::string &LLViewerFolderType::lookupIconName(LLFolderType::EType folder_type, bool is_open) +{ + const ViewerFolderEntry *entry = LLViewerFolderDictionary::getInstance()->lookup(folder_type); + if (entry) + { + if (is_open) + return entry->mIconNameOpen; + else + return entry->mIconNameClosed; + } + + // Error condition. Return something so that we don't show a grey box in inventory view. + const ViewerFolderEntry *default_entry = LLViewerFolderDictionary::getInstance()->lookup(LLFolderType::FT_NONE); + if (default_entry) + { + return default_entry->mIconNameClosed; + } + + // Should not get here unless there's something corrupted with the FT_NONE entry. + return badLookup(); +} + +bool LLViewerFolderType::lookupIsQuietType(LLFolderType::EType folder_type) +{ + const ViewerFolderEntry *entry = LLViewerFolderDictionary::getInstance()->lookup(folder_type); + if (entry) + { + return entry->mIsQuiet; + } + return false; +} + +bool LLViewerFolderType::lookupIsHiddenIfEmpty(LLFolderType::EType folder_type) +{ + const ViewerFolderEntry *entry = LLViewerFolderDictionary::getInstance()->lookup(folder_type); + if (entry) + { + return entry->mHideIfEmpty; + } + return false; +} + +const std::string &LLViewerFolderType::lookupNewCategoryName(LLFolderType::EType folder_type) +{ + const ViewerFolderEntry *entry = LLViewerFolderDictionary::getInstance()->lookup(folder_type); + if (entry) + { + return entry->mNewCategoryName; + } + return badLookup(); +} + +LLFolderType::EType LLViewerFolderType::lookupTypeFromNewCategoryName(const std::string& name) +{ + for (LLViewerFolderDictionary::const_iterator iter = LLViewerFolderDictionary::getInstance()->begin(); + iter != LLViewerFolderDictionary::getInstance()->end(); + iter++) + { + const ViewerFolderEntry *entry = iter->second; + if (entry->mNewCategoryName == name) + { + return iter->first; + } + } + return FT_NONE; +} + + +U64 LLViewerFolderType::lookupValidFolderTypes(const std::string& item_name) +{ + U64 matching_folders = 0; + for (LLViewerFolderDictionary::const_iterator iter = LLViewerFolderDictionary::getInstance()->begin(); + iter != LLViewerFolderDictionary::getInstance()->end(); + iter++) + { + const ViewerFolderEntry *entry = iter->second; + if (entry->getIsAllowedName(item_name)) + { + matching_folders |= 1LL << iter->first; + } + } + return matching_folders; +} diff --git a/indra/newview/llviewerfoldertype.h b/indra/newview/llviewerfoldertype.h index 051df9d548..f25c1113a3 100644 --- a/indra/newview/llviewerfoldertype.h +++ b/indra/newview/llviewerfoldertype.h @@ -1,54 +1,54 @@ -/** - * @file llviewerfoldertype.h - * @brief Declaration of LLAssetType. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVIEWERFOLDERTYPE_H -#define LL_LLVIEWERFOLDERTYPE_H - -#include -#include "llfoldertype.h" - -// This class is similar to llfoldertype, but contains methods -// only used by the viewer. This also handles ensembles. -class LLViewerFolderType : public LLFolderType -{ -public: - static const std::string& lookupXUIName(EType folder_type); // name used by the UI - static LLFolderType::EType lookupTypeFromXUIName(const std::string& name); - - static const std::string& lookupIconName(EType folder_type, bool is_open = false); // folder icon name - static bool lookupIsQuietType(EType folder_type); // folder doesn't require UI update when changes have occured - static bool lookupIsHiddenIfEmpty(EType folder_type); // folder is not displayed if empty - static const std::string& lookupNewCategoryName(EType folder_type); // default name when creating new category - static LLFolderType::EType lookupTypeFromNewCategoryName(const std::string& name); // default name when creating new category - - static U64 lookupValidFolderTypes(const std::string& item_name); // which folders allow an item of this type? - -protected: - LLViewerFolderType() {} - ~LLViewerFolderType() {} -}; - -#endif // LL_LLVIEWERFOLDERTYPE_H +/** + * @file llviewerfoldertype.h + * @brief Declaration of LLAssetType. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVIEWERFOLDERTYPE_H +#define LL_LLVIEWERFOLDERTYPE_H + +#include +#include "llfoldertype.h" + +// This class is similar to llfoldertype, but contains methods +// only used by the viewer. This also handles ensembles. +class LLViewerFolderType : public LLFolderType +{ +public: + static const std::string& lookupXUIName(EType folder_type); // name used by the UI + static LLFolderType::EType lookupTypeFromXUIName(const std::string& name); + + static const std::string& lookupIconName(EType folder_type, bool is_open = false); // folder icon name + static bool lookupIsQuietType(EType folder_type); // folder doesn't require UI update when changes have occured + static bool lookupIsHiddenIfEmpty(EType folder_type); // folder is not displayed if empty + static const std::string& lookupNewCategoryName(EType folder_type); // default name when creating new category + static LLFolderType::EType lookupTypeFromNewCategoryName(const std::string& name); // default name when creating new category + + static U64 lookupValidFolderTypes(const std::string& item_name); // which folders allow an item of this type? + +protected: + LLViewerFolderType() {} + ~LLViewerFolderType() {} +}; + +#endif // LL_LLVIEWERFOLDERTYPE_H diff --git a/indra/newview/llviewergesture.cpp b/indra/newview/llviewergesture.cpp index 44fcf9fcaa..4ddddf03f5 100644 --- a/indra/newview/llviewergesture.cpp +++ b/indra/newview/llviewergesture.cpp @@ -1,206 +1,206 @@ -/** - * @file llviewergesture.cpp - * @brief LLViewerGesture class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llviewergesture.h" - -#include "llaudioengine.h" -#include "lldir.h" -#include "llviewerinventory.h" -#include "sound_ids.h" // for testing - -#include "llfloaterreg.h" -#include "llkeyboard.h" // for key shortcuts for testing -#include "llinventorymodel.h" -#include "llvoavatar.h" -#include "llxfermanager.h" -#include "llviewermessage.h" // send_guid_sound_trigger -#include "llviewernetwork.h" -#include "llagent.h" -#include "llfloaterimnearbychat.h" - -// Globals -LLViewerGestureList gGestureList; - -const F32 LLViewerGesture::SOUND_VOLUME = 1.f; - -LLViewerGesture::LLViewerGesture() -: LLGesture() -{ } - -LLViewerGesture::LLViewerGesture(KEY key, MASK mask, const std::string &trigger, - const LLUUID &sound_item_id, - const std::string &animation, - const std::string &output_string) -: LLGesture(key, mask, trigger, sound_item_id, animation, output_string) -{ -} - -LLViewerGesture::LLViewerGesture(U8 **buffer, S32 max_size) -: LLGesture(buffer, max_size) -{ -} - -LLViewerGesture::LLViewerGesture(const LLViewerGesture &rhs) -: LLGesture((LLGesture)rhs) -{ -} - -bool LLViewerGesture::trigger(KEY key, MASK mask) -{ - if (mKey == key && mMask == mask) - { - doTrigger( true ); - return true; - } - else - { - return false; - } -} - - -bool LLViewerGesture::trigger(const std::string &trigger_string) -{ - // Assumes trigger_string is lowercase - if (mTriggerLower == trigger_string) - { - doTrigger( false ); - return true; - } - else - { - return false; - } -} - - -// private -void LLViewerGesture::doTrigger( bool send_chat ) -{ - if (mSoundItemID != LLUUID::null) - { - LLViewerInventoryItem *item; - item = gInventory.getItem(mSoundItemID); - if (item) - { - send_sound_trigger(item->getAssetUUID(), SOUND_VOLUME); - } - } - - if (!mAnimation.empty()) - { - // AFK animations trigger the special "away" state, which - // includes agent control settings. JC - if (mAnimation == "enter_away_from_keyboard_state" || mAnimation == "away") - { - gAgent.setAFK(); - } - else - { - LLUUID anim_id = gAnimLibrary.stringToAnimState(mAnimation); - gAgent.sendAnimationRequest(anim_id, ANIM_REQUEST_START); - } - } - - if (send_chat && !mOutputString.empty()) - { - // Don't play nodding animation, since that might not blend - // with the gesture animation. - (LLFloaterReg::getTypedInstance("nearby_chat"))-> - sendChatFromViewer(mOutputString, CHAT_TYPE_NORMAL, false); - } -} - - -LLViewerGestureList::LLViewerGestureList() -: LLGestureList() -{ - mIsLoaded = false; -} - - -// helper for deserialize that creates the right LLGesture subclass -LLGesture *LLViewerGestureList::create_gesture(U8 **buffer, S32 max_size) -{ - return new LLViewerGesture(buffer, max_size); -} - - -// See if the prefix matches any gesture. If so, return true -// and place the full text of the gesture trigger into -// output_str -bool LLViewerGestureList::matchPrefix(const std::string& in_str, std::string* out_str) -{ - S32 in_len = in_str.length(); - - std::string in_str_lc = in_str; - LLStringUtil::toLower(in_str_lc); - - for (S32 i = 0; i < count(); i++) - { - LLGesture* gesture = get(i); - const std::string &trigger = gesture->getTrigger(); - - if (in_len > (S32)trigger.length()) - { - // too short, bail out - continue; - } - - std::string trigger_trunc = utf8str_truncate(trigger, in_len); - LLStringUtil::toLower(trigger_trunc); - if (in_str_lc == trigger_trunc) - { - *out_str = trigger; - return true; - } - } - return false; -} - - -// static -void LLViewerGestureList::xferCallback(void *data, S32 size, void** /*user_data*/, S32 status) -{ - if (LL_ERR_NOERR == status) - { - U8 *buffer = (U8 *)data; - U8 *end = gGestureList.deserialize(buffer, size); - - if (end - buffer > size) - { - LL_ERRS() << "Read off of end of array, error in serialization" << LL_ENDL; - } - - gGestureList.mIsLoaded = true; - } - else - { - LL_WARNS() << "Unable to load gesture list!" << LL_ENDL; - } -} +/** + * @file llviewergesture.cpp + * @brief LLViewerGesture class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llviewergesture.h" + +#include "llaudioengine.h" +#include "lldir.h" +#include "llviewerinventory.h" +#include "sound_ids.h" // for testing + +#include "llfloaterreg.h" +#include "llkeyboard.h" // for key shortcuts for testing +#include "llinventorymodel.h" +#include "llvoavatar.h" +#include "llxfermanager.h" +#include "llviewermessage.h" // send_guid_sound_trigger +#include "llviewernetwork.h" +#include "llagent.h" +#include "llfloaterimnearbychat.h" + +// Globals +LLViewerGestureList gGestureList; + +const F32 LLViewerGesture::SOUND_VOLUME = 1.f; + +LLViewerGesture::LLViewerGesture() +: LLGesture() +{ } + +LLViewerGesture::LLViewerGesture(KEY key, MASK mask, const std::string &trigger, + const LLUUID &sound_item_id, + const std::string &animation, + const std::string &output_string) +: LLGesture(key, mask, trigger, sound_item_id, animation, output_string) +{ +} + +LLViewerGesture::LLViewerGesture(U8 **buffer, S32 max_size) +: LLGesture(buffer, max_size) +{ +} + +LLViewerGesture::LLViewerGesture(const LLViewerGesture &rhs) +: LLGesture((LLGesture)rhs) +{ +} + +bool LLViewerGesture::trigger(KEY key, MASK mask) +{ + if (mKey == key && mMask == mask) + { + doTrigger( true ); + return true; + } + else + { + return false; + } +} + + +bool LLViewerGesture::trigger(const std::string &trigger_string) +{ + // Assumes trigger_string is lowercase + if (mTriggerLower == trigger_string) + { + doTrigger( false ); + return true; + } + else + { + return false; + } +} + + +// private +void LLViewerGesture::doTrigger( bool send_chat ) +{ + if (mSoundItemID != LLUUID::null) + { + LLViewerInventoryItem *item; + item = gInventory.getItem(mSoundItemID); + if (item) + { + send_sound_trigger(item->getAssetUUID(), SOUND_VOLUME); + } + } + + if (!mAnimation.empty()) + { + // AFK animations trigger the special "away" state, which + // includes agent control settings. JC + if (mAnimation == "enter_away_from_keyboard_state" || mAnimation == "away") + { + gAgent.setAFK(); + } + else + { + LLUUID anim_id = gAnimLibrary.stringToAnimState(mAnimation); + gAgent.sendAnimationRequest(anim_id, ANIM_REQUEST_START); + } + } + + if (send_chat && !mOutputString.empty()) + { + // Don't play nodding animation, since that might not blend + // with the gesture animation. + (LLFloaterReg::getTypedInstance("nearby_chat"))-> + sendChatFromViewer(mOutputString, CHAT_TYPE_NORMAL, false); + } +} + + +LLViewerGestureList::LLViewerGestureList() +: LLGestureList() +{ + mIsLoaded = false; +} + + +// helper for deserialize that creates the right LLGesture subclass +LLGesture *LLViewerGestureList::create_gesture(U8 **buffer, S32 max_size) +{ + return new LLViewerGesture(buffer, max_size); +} + + +// See if the prefix matches any gesture. If so, return true +// and place the full text of the gesture trigger into +// output_str +bool LLViewerGestureList::matchPrefix(const std::string& in_str, std::string* out_str) +{ + S32 in_len = in_str.length(); + + std::string in_str_lc = in_str; + LLStringUtil::toLower(in_str_lc); + + for (S32 i = 0; i < count(); i++) + { + LLGesture* gesture = get(i); + const std::string &trigger = gesture->getTrigger(); + + if (in_len > (S32)trigger.length()) + { + // too short, bail out + continue; + } + + std::string trigger_trunc = utf8str_truncate(trigger, in_len); + LLStringUtil::toLower(trigger_trunc); + if (in_str_lc == trigger_trunc) + { + *out_str = trigger; + return true; + } + } + return false; +} + + +// static +void LLViewerGestureList::xferCallback(void *data, S32 size, void** /*user_data*/, S32 status) +{ + if (LL_ERR_NOERR == status) + { + U8 *buffer = (U8 *)data; + U8 *end = gGestureList.deserialize(buffer, size); + + if (end - buffer > size) + { + LL_ERRS() << "Read off of end of array, error in serialization" << LL_ENDL; + } + + gGestureList.mIsLoaded = true; + } + else + { + LL_WARNS() << "Unable to load gesture list!" << LL_ENDL; + } +} diff --git a/indra/newview/llviewergesture.h b/indra/newview/llviewergesture.h index 407857bb27..120a9cdda5 100644 --- a/indra/newview/llviewergesture.h +++ b/indra/newview/llviewergesture.h @@ -1,86 +1,86 @@ -/** - * @file llviewergesture.h - * @brief LLViewerGesture class header file - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVIEWERGESTURE_H -#define LL_LLVIEWERGESTURE_H - -#include "llanimationstates.h" -#include "lluuid.h" -#include "llstring.h" -#include "llgesture.h" - -class LLMessageSystem; - -class LLViewerGesture : public LLGesture -{ -public: - LLViewerGesture(); - LLViewerGesture(KEY key, MASK mask, const std::string &trigger, - const LLUUID &sound_item_id, const std::string &animation, - const std::string &output_string); - - LLViewerGesture(U8 **buffer, S32 max_size); // deserializes, advances buffer - LLViewerGesture(const LLViewerGesture &gesture); - - // Triggers if a key/mask matches it - virtual bool trigger(KEY key, MASK mask); - - // Triggers if case-insensitive substring matches (assumes string is lowercase) - virtual bool trigger(const std::string &string); - - void doTrigger( bool send_chat ); - -protected: - static const F32 SOUND_VOLUME; -}; - -class LLViewerGestureList : public LLGestureList -{ -public: - LLViewerGestureList(); - - //void requestFromServer(); - bool getIsLoaded() { return mIsLoaded; } - - //void requestResetFromServer( bool is_male ); - - // See if the prefix matches any gesture. If so, return true - // and place the full text of the gesture trigger into - // output_str - bool matchPrefix(const std::string& in_str, std::string* out_str); - - static void xferCallback(void *data, S32 size, void** /*user_data*/, S32 status); - -protected: - LLGesture *create_gesture(U8 **buffer, S32 max_size); - -protected: - bool mIsLoaded; -}; - -extern LLViewerGestureList gGestureList; - -#endif +/** + * @file llviewergesture.h + * @brief LLViewerGesture class header file + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVIEWERGESTURE_H +#define LL_LLVIEWERGESTURE_H + +#include "llanimationstates.h" +#include "lluuid.h" +#include "llstring.h" +#include "llgesture.h" + +class LLMessageSystem; + +class LLViewerGesture : public LLGesture +{ +public: + LLViewerGesture(); + LLViewerGesture(KEY key, MASK mask, const std::string &trigger, + const LLUUID &sound_item_id, const std::string &animation, + const std::string &output_string); + + LLViewerGesture(U8 **buffer, S32 max_size); // deserializes, advances buffer + LLViewerGesture(const LLViewerGesture &gesture); + + // Triggers if a key/mask matches it + virtual bool trigger(KEY key, MASK mask); + + // Triggers if case-insensitive substring matches (assumes string is lowercase) + virtual bool trigger(const std::string &string); + + void doTrigger( bool send_chat ); + +protected: + static const F32 SOUND_VOLUME; +}; + +class LLViewerGestureList : public LLGestureList +{ +public: + LLViewerGestureList(); + + //void requestFromServer(); + bool getIsLoaded() { return mIsLoaded; } + + //void requestResetFromServer( bool is_male ); + + // See if the prefix matches any gesture. If so, return true + // and place the full text of the gesture trigger into + // output_str + bool matchPrefix(const std::string& in_str, std::string* out_str); + + static void xferCallback(void *data, S32 size, void** /*user_data*/, S32 status); + +protected: + LLGesture *create_gesture(U8 **buffer, S32 max_size); + +protected: + bool mIsLoaded; +}; + +extern LLViewerGestureList gGestureList; + +#endif diff --git a/indra/newview/llviewerinput.cpp b/indra/newview/llviewerinput.cpp index 5d0833c714..8cc2f1e5d6 100644 --- a/indra/newview/llviewerinput.cpp +++ b/indra/newview/llviewerinput.cpp @@ -1,1867 +1,1867 @@ -/** - * @file llviewerinput.cpp - * @brief LLViewerInput class implementation - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llviewerinput.h" - -#include "llappviewer.h" -#include "llfloaterreg.h" -#include "llmath.h" -#include "llagent.h" -#include "llagentcamera.h" -#include "llfloaterimnearbychat.h" -#include "llfocusmgr.h" -#include "llkeybind.h" // LLKeyData -#include "llmorphview.h" -#include "llmoveview.h" -#include "llsetkeybinddialog.h" -#include "lltoolfocus.h" -#include "lltoolpie.h" -#include "llviewercontrol.h" -#include "llviewerwindow.h" -#include "llvoavatarself.h" -#include "llfloatercamera.h" -#include "llinitparam.h" -#include "llselectmgr.h" - -// -// Constants -// - -const F32 FLY_TIME = 0.5f; -const F32 FLY_FRAMES = 4; - -const F32 NUDGE_TIME = 0.25f; // in seconds -const S32 NUDGE_FRAMES = 2; -const F32 ORBIT_NUDGE_RATE = 0.05f; // fraction of normal speed - -const LLKeyData agent_control_lbutton(CLICK_LEFT, KEY_NONE, MASK_NONE, true); - -struct LLKeybindFunctionData -{ - LLKeybindFunctionData(boost::function function, bool global) - : - mFunction(function), - mIsGlobal(global) - { - } - boost::function mFunction; - // todo: might be good idea to make this into enum, like: global/inworld/menu - bool mIsGlobal; -}; - -struct LLKeyboardActionRegistry -: public LLRegistrySingleton -{ - LLSINGLETON_EMPTY_CTOR(LLKeyboardActionRegistry); -}; - -LLViewerInput gViewerInput; - -bool agent_jump( EKeystate s ) -{ - static bool first_fly_attempt(true); - if (KEYSTATE_UP == s) - { - first_fly_attempt = true; - return true; - } - F32 time = gKeyboard->getCurKeyElapsedTime(); - S32 frame_count = ll_round(gKeyboard->getCurKeyElapsedFrameCount()); - - if( time < FLY_TIME - || frame_count <= FLY_FRAMES - || gAgent.upGrabbed() - || !gSavedSettings.getBOOL("AutomaticFly")) - { - gAgent.moveUp(1); - } - else - { - gAgent.setFlying(true, first_fly_attempt); - first_fly_attempt = false; - gAgent.moveUp(1); - } - return true; -} - -bool agent_push_down( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - gAgent.moveUp(-1); - return true; -} - -static void agent_check_temporary_run(LLAgent::EDoubleTapRunMode mode) -{ - if (gAgent.mDoubleTapRunMode == mode && - gAgent.getRunning() && - !gAgent.getAlwaysRun()) - { - // Turn off temporary running. - gAgent.clearRunning(); - gAgent.sendWalkRun(gAgent.getRunning()); - } -} - -static void agent_handle_doubletap_run(EKeystate s, LLAgent::EDoubleTapRunMode mode) -{ - if (KEYSTATE_UP == s) - { - // Note: in case shift is already released, slide left/right run - // will be released in agent_turn_left()/agent_turn_right() - agent_check_temporary_run(mode); - } - else if (gSavedSettings.getBOOL("AllowTapTapHoldRun") && - KEYSTATE_DOWN == s && - !gAgent.getRunning()) - { - if (gAgent.mDoubleTapRunMode == mode && - gAgent.mDoubleTapRunTimer.getElapsedTimeF32() < NUDGE_TIME) - { - // Same walk-key was pushed again quickly; this is a - // double-tap so engage temporary running. - gAgent.setRunning(); - gAgent.sendWalkRun(gAgent.getRunning()); - } - - // Pressing any walk-key resets the double-tap timer - gAgent.mDoubleTapRunTimer.reset(); - gAgent.mDoubleTapRunMode = mode; - } -} - -static void agent_push_forwardbackward( EKeystate s, S32 direction, LLAgent::EDoubleTapRunMode mode ) -{ - agent_handle_doubletap_run(s, mode); - if (KEYSTATE_UP == s) return; - - F32 time = gKeyboard->getCurKeyElapsedTime(); - S32 frame_count = ll_round(gKeyboard->getCurKeyElapsedFrameCount()); - - if( time < NUDGE_TIME || frame_count <= NUDGE_FRAMES) - { - gAgent.moveAtNudge(direction); - } - else - { - gAgent.moveAt(direction); - } -} - -bool camera_move_forward( EKeystate s ); - -bool agent_push_forward( EKeystate s ) -{ - if(gAgent.isMovementLocked()) return true; - - //in free camera control mode we need to intercept keyboard events for avatar movements - if (LLFloaterCamera::inFreeCameraMode()) - { - camera_move_forward(s); - } - else - { - agent_push_forwardbackward(s, 1, LLAgent::DOUBLETAP_FORWARD); - } - return true; -} - -bool camera_move_backward( EKeystate s ); - -bool agent_push_backward( EKeystate s ) -{ - if(gAgent.isMovementLocked()) return true; - - //in free camera control mode we need to intercept keyboard events for avatar movements - if (LLFloaterCamera::inFreeCameraMode()) - { - camera_move_backward(s); - } - else if (!gAgent.backwardGrabbed() && gAgentAvatarp->isSitting() && gSavedSettings.getBOOL("LeaveMouselook")) - { - gAgentCamera.changeCameraToThirdPerson(); - } - else - { - agent_push_forwardbackward(s, -1, LLAgent::DOUBLETAP_BACKWARD); - } - return true; -} - -static void agent_slide_leftright( EKeystate s, S32 direction, LLAgent::EDoubleTapRunMode mode ) -{ - agent_handle_doubletap_run(s, mode); - if( KEYSTATE_UP == s ) return; - F32 time = gKeyboard->getCurKeyElapsedTime(); - S32 frame_count = ll_round(gKeyboard->getCurKeyElapsedFrameCount()); - - if( time < NUDGE_TIME || frame_count <= NUDGE_FRAMES) - { - gAgent.moveLeftNudge(direction); - } - else - { - gAgent.moveLeft(direction); - } -} - - -bool agent_slide_left( EKeystate s ) -{ - if(gAgent.isMovementLocked()) return true; - agent_slide_leftright(s, 1, LLAgent::DOUBLETAP_SLIDELEFT); - return true; -} - - -bool agent_slide_right( EKeystate s ) -{ - if(gAgent.isMovementLocked()) return true; - agent_slide_leftright(s, -1, LLAgent::DOUBLETAP_SLIDERIGHT); - return true; -} - -bool camera_spin_around_cw( EKeystate s ); - -bool agent_turn_left(EKeystate s) -{ - //in free camera control mode we need to intercept keyboard events for avatar movements - if (LLFloaterCamera::inFreeCameraMode()) - { - camera_spin_around_cw(s); - return true; - } - - if(gAgent.isMovementLocked()) return false; - - if (LLToolCamera::getInstance()->mouseSteerMode()) - { - agent_slide_left(s); - } - else - { - if (KEYSTATE_UP == s) - { - // Check temporary running. In case user released 'left' key with shift already released. - agent_check_temporary_run(LLAgent::DOUBLETAP_SLIDELEFT); - return true; - } - F32 time = gKeyboard->getCurKeyElapsedTime(); - gAgent.moveYaw( LLFloaterMove::getYawRate( time ) ); - } - return true; -} - -bool camera_spin_around_ccw( EKeystate s ); - -bool agent_turn_right( EKeystate s ) -{ - //in free camera control mode we need to intercept keyboard events for avatar movements - if (LLFloaterCamera::inFreeCameraMode()) - { - camera_spin_around_ccw(s); - return true; - } - - if(gAgent.isMovementLocked()) return false; - - if (LLToolCamera::getInstance()->mouseSteerMode()) - { - agent_slide_right(s); - } - else - { - if (KEYSTATE_UP == s) - { - // Check temporary running. In case user released 'right' key with shift already released. - agent_check_temporary_run(LLAgent::DOUBLETAP_SLIDERIGHT); - return true; - } - F32 time = gKeyboard->getCurKeyElapsedTime(); - gAgent.moveYaw( -LLFloaterMove::getYawRate( time ) ); - } - return true; -} - -bool agent_look_up( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - gAgent.movePitch(-1); - //gAgent.rotate(-2.f * DEG_TO_RAD, gAgent.getFrame().getLeftAxis() ); - return true; -} - - -bool agent_look_down( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - gAgent.movePitch(1); - //gAgent.rotate(2.f * DEG_TO_RAD, gAgent.getFrame().getLeftAxis() ); - return true; -} - -bool agent_toggle_fly( EKeystate s ) -{ - // Only catch the edge - if (KEYSTATE_DOWN == s ) - { - LLAgent::toggleFlying(); - } - return true; -} - -F32 get_orbit_rate() -{ - F32 time = gKeyboard->getCurKeyElapsedTime(); - if( time < NUDGE_TIME ) - { - F32 rate = ORBIT_NUDGE_RATE + time * (1 - ORBIT_NUDGE_RATE)/ NUDGE_TIME; - //LL_INFOS() << rate << LL_ENDL; - return rate; - } - else - { - return 1; - } -} - -bool camera_spin_around_ccw( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - gAgentCamera.unlockView(); - gAgentCamera.setOrbitLeftKey( get_orbit_rate() ); - return true; -} - - -bool camera_spin_around_cw( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - gAgentCamera.unlockView(); - gAgentCamera.setOrbitRightKey( get_orbit_rate() ); - return true; -} - -bool camera_spin_around_ccw_sitting( EKeystate s ) -{ - if( KEYSTATE_UP == s && gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_SLIDERIGHT ) return true; - if (gAgent.rotateGrabbed() || gAgentCamera.sitCameraEnabled() || gAgent.getRunning()) - { - //send keystrokes, but do not change camera - agent_turn_right(s); - } - else - { - //change camera but do not send keystrokes - gAgentCamera.unlockView(); - gAgentCamera.setOrbitLeftKey( get_orbit_rate() ); - } - return true; -} - - -bool camera_spin_around_cw_sitting( EKeystate s ) -{ - if( KEYSTATE_UP == s && gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_SLIDELEFT ) return true; - if (gAgent.rotateGrabbed() || gAgentCamera.sitCameraEnabled() || gAgent.getRunning()) - { - //send keystrokes, but do not change camera - agent_turn_left(s); - } - else - { - //change camera but do not send keystrokes - gAgentCamera.unlockView(); - gAgentCamera.setOrbitRightKey( get_orbit_rate() ); - } - return true; -} - - -bool camera_spin_over( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - gAgentCamera.unlockView(); - gAgentCamera.setOrbitUpKey( get_orbit_rate() ); - return true; -} - - -bool camera_spin_under( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - gAgentCamera.unlockView(); - gAgentCamera.setOrbitDownKey( get_orbit_rate() ); - return true; -} - -bool camera_spin_over_sitting( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - if (gAgent.upGrabbed() || gAgentCamera.sitCameraEnabled()) - { - //send keystrokes, but do not change camera - agent_jump(s); - } - else - { - //change camera but do not send keystrokes - gAgentCamera.setOrbitUpKey( get_orbit_rate() ); - } - return true; -} - - -bool camera_spin_under_sitting( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - if (gAgent.downGrabbed() || gAgentCamera.sitCameraEnabled()) - { - //send keystrokes, but do not change camera - agent_push_down(s); - } - else - { - //change camera but do not send keystrokes - gAgentCamera.setOrbitDownKey( get_orbit_rate() ); - } - return true; -} - -bool camera_move_forward( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - gAgentCamera.unlockView(); - gAgentCamera.setOrbitInKey( get_orbit_rate() ); - return true; -} - - -bool camera_move_backward( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - gAgentCamera.unlockView(); - gAgentCamera.setOrbitOutKey( get_orbit_rate() ); - return true; -} - -bool camera_move_forward_sitting( EKeystate s ) -{ - if( KEYSTATE_UP == s && gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_FORWARD ) return true; - if (gAgent.forwardGrabbed() || gAgentCamera.sitCameraEnabled() || (gAgent.getRunning() && !gAgent.getAlwaysRun())) - { - agent_push_forward(s); - } - else - { - gAgentCamera.setOrbitInKey( get_orbit_rate() ); - } - return true; -} - -bool camera_move_backward_sitting( EKeystate s ) -{ - if( KEYSTATE_UP == s && gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_BACKWARD ) return true; - - if (gAgent.backwardGrabbed() || gAgentCamera.sitCameraEnabled() || (gAgent.getRunning() && !gAgent.getAlwaysRun())) - { - agent_push_backward(s); - } - else - { - gAgentCamera.setOrbitOutKey( get_orbit_rate() ); - } - return true; -} - -bool camera_pan_up( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - gAgentCamera.unlockView(); - gAgentCamera.setPanUpKey( get_orbit_rate() ); - return true; -} - -bool camera_pan_down( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - gAgentCamera.unlockView(); - gAgentCamera.setPanDownKey( get_orbit_rate() ); - return true; -} - -bool camera_pan_left( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - gAgentCamera.unlockView(); - gAgentCamera.setPanLeftKey( get_orbit_rate() ); - return true; -} - -bool camera_pan_right( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - gAgentCamera.unlockView(); - gAgentCamera.setPanRightKey( get_orbit_rate() ); - return true; -} - -bool camera_pan_in( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - gAgentCamera.unlockView(); - gAgentCamera.setPanInKey( get_orbit_rate() ); - return true; -} - -bool camera_pan_out( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - gAgentCamera.unlockView(); - gAgentCamera.setPanOutKey( get_orbit_rate() ); - return true; -} - -bool camera_move_forward_fast( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - gAgentCamera.unlockView(); - gAgentCamera.setOrbitInKey(2.5f); - return true; -} - -bool camera_move_backward_fast( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - gAgentCamera.unlockView(); - gAgentCamera.setOrbitOutKey(2.5f); - return true; -} - - -bool edit_avatar_spin_ccw( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - gMorphView->setCameraDrivenByKeys( true ); - gAgentCamera.setOrbitLeftKey( get_orbit_rate() ); - //gMorphView->orbitLeft( get_orbit_rate() ); - return true; -} - - -bool edit_avatar_spin_cw( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - gMorphView->setCameraDrivenByKeys( true ); - gAgentCamera.setOrbitRightKey( get_orbit_rate() ); - //gMorphView->orbitRight( get_orbit_rate() ); - return true; -} - -bool edit_avatar_spin_over( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - gMorphView->setCameraDrivenByKeys( true ); - gAgentCamera.setOrbitUpKey( get_orbit_rate() ); - //gMorphView->orbitUp( get_orbit_rate() ); - return true; -} - - -bool edit_avatar_spin_under( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - gMorphView->setCameraDrivenByKeys( true ); - gAgentCamera.setOrbitDownKey( get_orbit_rate() ); - //gMorphView->orbitDown( get_orbit_rate() ); - return true; -} - -bool edit_avatar_move_forward( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - gMorphView->setCameraDrivenByKeys( true ); - gAgentCamera.setOrbitInKey( get_orbit_rate() ); - //gMorphView->orbitIn(); - return true; -} - - -bool edit_avatar_move_backward( EKeystate s ) -{ - if( KEYSTATE_UP == s ) return true; - gMorphView->setCameraDrivenByKeys( true ); - gAgentCamera.setOrbitOutKey( get_orbit_rate() ); - //gMorphView->orbitOut(); - return true; -} - -bool stop_moving( EKeystate s ) -{ - //it's supposed that 'stop moving' key will be held down for some time - if( KEYSTATE_UP == s ) return true; - // stop agent - gAgent.setControlFlags(AGENT_CONTROL_STOP); - - // cancel autopilot - gAgent.stopAutoPilot(); - return true; -} - -bool start_chat( EKeystate s ) -{ - if (LLAppViewer::instance()->quitRequested()) - { - return true; // can't talk, gotta go, kthxbye! - } - if (KEYSTATE_DOWN != s) return true; - - // start chat - LLFloaterIMNearbyChat::startChat(NULL); - return true; -} - -bool start_gesture( EKeystate s ) -{ - LLUICtrl* focus_ctrlp = dynamic_cast(gFocusMgr.getKeyboardFocus()); - if (KEYSTATE_UP == s && - ! (focus_ctrlp && focus_ctrlp->acceptsTextInput())) - { - if ((LLFloaterReg::getTypedInstance("nearby_chat"))->getCurrentChat().empty()) - { - // No existing chat in chat editor, insert '/' - LLFloaterIMNearbyChat::startChat("/"); - } - else - { - // Don't overwrite existing text in chat editor - LLFloaterIMNearbyChat::startChat(NULL); - } - } - return true; -} - -bool run_forward(EKeystate s) -{ - if (KEYSTATE_UP != s) - { - if (gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_FORWARD) - { - gAgent.mDoubleTapRunMode = LLAgent::DOUBLETAP_FORWARD; - } - if (!gAgent.getRunning()) - { - gAgent.setRunning(); - gAgent.sendWalkRun(true); - } - } - else if(KEYSTATE_UP == s) - { - if (gAgent.mDoubleTapRunMode == LLAgent::DOUBLETAP_FORWARD) - gAgent.mDoubleTapRunMode = LLAgent::DOUBLETAP_NONE; - gAgent.clearRunning(); - gAgent.sendWalkRun(false); - } - agent_push_forward(s); - return true; -} - -bool run_backward(EKeystate s) -{ - if (KEYSTATE_UP != s) - { - if (gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_BACKWARD) - { - gAgent.mDoubleTapRunMode = LLAgent::DOUBLETAP_BACKWARD; - } - if (!gAgent.getRunning()) - { - gAgent.setRunning(); - gAgent.sendWalkRun(true); - } - } - else if (KEYSTATE_UP == s) - { - if (gAgent.mDoubleTapRunMode == LLAgent::DOUBLETAP_BACKWARD) - gAgent.mDoubleTapRunMode = LLAgent::DOUBLETAP_NONE; - gAgent.clearRunning(); - gAgent.sendWalkRun(false); - } - agent_push_backward(s); - return true; -} - -bool run_left(EKeystate s) -{ - if (KEYSTATE_UP != s) - { - if (gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_SLIDELEFT) - { - gAgent.mDoubleTapRunMode = LLAgent::DOUBLETAP_SLIDELEFT; - } - if (!gAgent.getRunning()) - { - gAgent.setRunning(); - gAgent.sendWalkRun(true); - } - } - else if (KEYSTATE_UP == s) - { - if (gAgent.mDoubleTapRunMode == LLAgent::DOUBLETAP_SLIDELEFT) - gAgent.mDoubleTapRunMode = LLAgent::DOUBLETAP_NONE; - gAgent.clearRunning(); - gAgent.sendWalkRun(false); - } - agent_slide_left(s); - return true; -} - -bool run_right(EKeystate s) -{ - if (KEYSTATE_UP != s) - { - if (gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_SLIDERIGHT) - { - gAgent.mDoubleTapRunMode = LLAgent::DOUBLETAP_SLIDERIGHT; - } - if (!gAgent.getRunning()) - { - gAgent.setRunning(); - gAgent.sendWalkRun(true); - } - } - else if (KEYSTATE_UP == s) - { - if (gAgent.mDoubleTapRunMode == LLAgent::DOUBLETAP_SLIDERIGHT) - gAgent.mDoubleTapRunMode = LLAgent::DOUBLETAP_NONE; - gAgent.clearRunning(); - gAgent.sendWalkRun(false); - } - agent_slide_right(s); - return true; -} - -bool toggle_run(EKeystate s) -{ - if (KEYSTATE_DOWN != s) return true; - bool run = gAgent.getAlwaysRun(); - if (run) - { - gAgent.clearAlwaysRun(); - gAgent.clearRunning(); - } - else - { - gAgent.setAlwaysRun(); - gAgent.setRunning(); - } - gAgent.sendWalkRun(!run); - return true; -} - -bool toggle_sit(EKeystate s) -{ - if (KEYSTATE_DOWN != s) return true; - if (gAgent.isSitting()) - { - gAgent.standUp(); - } - else - { - gAgent.sitDown(); - } - return true; -} - -bool toggle_pause_media(EKeystate s) // analogue of play/pause button in top bar -{ - if (KEYSTATE_DOWN != s) return true; - bool pause = LLViewerMedia::getInstance()->isAnyMediaPlaying(); - LLViewerMedia::getInstance()->setAllMediaPaused(pause); - return true; -} - -bool toggle_enable_media(EKeystate s) -{ - if (KEYSTATE_DOWN != s) return true; - bool pause = LLViewerMedia::getInstance()->isAnyMediaPlaying() || LLViewerMedia::getInstance()->isAnyMediaShowing(); - LLViewerMedia::getInstance()->setAllMediaEnabled(!pause); - return true; -} - -bool walk_to(EKeystate s) -{ - if (KEYSTATE_DOWN != s) - { - // teleport/walk is usually on mouseclick, mouseclick needs - // to let AGENT_CONTROL_LBUTTON_UP happen if teleport didn't, - // so return false, but if it causes issues, do some kind of - // "return !has_teleported" - return false; - } - return LLToolPie::getInstance()->walkToClickedLocation(); -} - -bool teleport_to(EKeystate s) -{ - if (KEYSTATE_DOWN != s) return false; - return LLToolPie::getInstance()->teleportToClickedLocation(); -} - -bool toggle_voice(EKeystate s) -{ - if (KEYSTATE_DOWN != s) return true; - if (!LLAgent::isActionAllowed("speak")) return false; - LLVoiceClient::getInstance()->toggleUserPTTState(); - return true; -} - -bool voice_follow_key(EKeystate s) -{ - if (KEYSTATE_DOWN == s) - { - if (!LLAgent::isActionAllowed("speak")) return false; - LLVoiceClient::getInstance()->setUserPTTState(true); - return true; - } - else if (KEYSTATE_UP == s && LLVoiceClient::getInstance()->getUserPTTState()) - { - LLVoiceClient::getInstance()->setUserPTTState(false); - return true; - } - return false; -} - -bool script_trigger_lbutton(EKeystate s) -{ - // Check for script overriding/expecting left mouse button. - // Note that this does not pass event further and depends onto mouselook. - // Checks CONTROL_ML_LBUTTON_DOWN_INDEX for mouselook, - // CONTROL_LBUTTON_DOWN_INDEX for normal camera - if (gAgent.leftButtonGrabbed()) - { - bool mouselook = gAgentCamera.cameraMouselook(); - switch (s) - { - case KEYSTATE_DOWN: - if (mouselook) - { - gAgent.setControlFlags(AGENT_CONTROL_ML_LBUTTON_DOWN); - } - else - { - gAgent.setControlFlags(AGENT_CONTROL_LBUTTON_DOWN); - } - return true; - case KEYSTATE_UP: - if (mouselook) - { - gAgent.setControlFlags(AGENT_CONTROL_ML_LBUTTON_UP); - } - else - { - gAgent.setControlFlags(AGENT_CONTROL_LBUTTON_UP); - } - return true; - default: - break; - } - } - return false; -} - -// Used by scripts, for overriding/handling left mouse button -// see mControlsTakenCount -bool agent_control_lbutton_handle(EKeystate s) -{ - switch (s) - { - case KEYSTATE_DOWN: - gAgent.setControlFlags(AGENT_CONTROL_LBUTTON_DOWN); - break; - case KEYSTATE_UP: - gAgent.setControlFlags(AGENT_CONTROL_LBUTTON_UP); - break; - default: - break; - } - return true; -} - -// In-world keybindings, like walking or camera -#define REGISTER_KEYBOARD_ACTION(KEY, ACTION) LLREGISTER_STATIC(LLKeyboardActionRegistry, KEY, LLKeybindFunctionData(ACTION, false)); -// Global keybindings that should work even with floaters focused, like voice -#define REGISTER_KEYBOARD_GLOBAL_ACTION(KEY, ACTION) LLREGISTER_STATIC(LLKeyboardActionRegistry, KEY, LLKeybindFunctionData(ACTION, true)); -REGISTER_KEYBOARD_ACTION("jump", agent_jump); -REGISTER_KEYBOARD_ACTION("push_down", agent_push_down); -REGISTER_KEYBOARD_ACTION("push_forward", agent_push_forward); -REGISTER_KEYBOARD_ACTION("push_backward", agent_push_backward); -REGISTER_KEYBOARD_ACTION("look_up", agent_look_up); -REGISTER_KEYBOARD_ACTION("look_down", agent_look_down); -REGISTER_KEYBOARD_ACTION("toggle_fly", agent_toggle_fly); -REGISTER_KEYBOARD_ACTION("turn_left", agent_turn_left); -REGISTER_KEYBOARD_ACTION("turn_right", agent_turn_right); -REGISTER_KEYBOARD_ACTION("slide_left", agent_slide_left); -REGISTER_KEYBOARD_ACTION("slide_right", agent_slide_right); -REGISTER_KEYBOARD_ACTION("spin_around_ccw", camera_spin_around_ccw); -REGISTER_KEYBOARD_ACTION("spin_around_cw", camera_spin_around_cw); -REGISTER_KEYBOARD_ACTION("spin_around_ccw_sitting", camera_spin_around_ccw_sitting); -REGISTER_KEYBOARD_ACTION("spin_around_cw_sitting", camera_spin_around_cw_sitting); -REGISTER_KEYBOARD_ACTION("spin_over", camera_spin_over); -REGISTER_KEYBOARD_ACTION("spin_under", camera_spin_under); -REGISTER_KEYBOARD_ACTION("spin_over_sitting", camera_spin_over_sitting); -REGISTER_KEYBOARD_ACTION("spin_under_sitting", camera_spin_under_sitting); -REGISTER_KEYBOARD_ACTION("move_forward", camera_move_forward); -REGISTER_KEYBOARD_ACTION("move_backward", camera_move_backward); -REGISTER_KEYBOARD_ACTION("move_forward_sitting", camera_move_forward_sitting); -REGISTER_KEYBOARD_ACTION("move_backward_sitting", camera_move_backward_sitting); -REGISTER_KEYBOARD_ACTION("pan_up", camera_pan_up); -REGISTER_KEYBOARD_ACTION("pan_down", camera_pan_down); -REGISTER_KEYBOARD_ACTION("pan_left", camera_pan_left); -REGISTER_KEYBOARD_ACTION("pan_right", camera_pan_right); -REGISTER_KEYBOARD_ACTION("pan_in", camera_pan_in); -REGISTER_KEYBOARD_ACTION("pan_out", camera_pan_out); -REGISTER_KEYBOARD_ACTION("move_forward_fast", camera_move_forward_fast); -REGISTER_KEYBOARD_ACTION("move_backward_fast", camera_move_backward_fast); -REGISTER_KEYBOARD_ACTION("edit_avatar_spin_ccw", edit_avatar_spin_ccw); -REGISTER_KEYBOARD_ACTION("edit_avatar_spin_cw", edit_avatar_spin_cw); -REGISTER_KEYBOARD_ACTION("edit_avatar_spin_over", edit_avatar_spin_over); -REGISTER_KEYBOARD_ACTION("edit_avatar_spin_under", edit_avatar_spin_under); -REGISTER_KEYBOARD_ACTION("edit_avatar_move_forward", edit_avatar_move_forward); -REGISTER_KEYBOARD_ACTION("edit_avatar_move_backward", edit_avatar_move_backward); -REGISTER_KEYBOARD_ACTION("stop_moving", stop_moving); -REGISTER_KEYBOARD_ACTION("start_chat", start_chat); -REGISTER_KEYBOARD_ACTION("start_gesture", start_gesture); -REGISTER_KEYBOARD_ACTION("run_forward", run_forward); -REGISTER_KEYBOARD_ACTION("run_backward", run_backward); -REGISTER_KEYBOARD_ACTION("run_left", run_left); -REGISTER_KEYBOARD_ACTION("run_right", run_right); -REGISTER_KEYBOARD_ACTION("toggle_run", toggle_run); -REGISTER_KEYBOARD_ACTION("toggle_sit", toggle_sit); -REGISTER_KEYBOARD_ACTION("toggle_pause_media", toggle_pause_media); -REGISTER_KEYBOARD_ACTION("toggle_enable_media", toggle_enable_media); -REGISTER_KEYBOARD_ACTION("teleport_to", teleport_to); -REGISTER_KEYBOARD_ACTION("walk_to", walk_to); -REGISTER_KEYBOARD_GLOBAL_ACTION("toggle_voice", toggle_voice); -REGISTER_KEYBOARD_GLOBAL_ACTION("voice_follow_key", voice_follow_key); -REGISTER_KEYBOARD_ACTION(script_mouse_handler_name, script_trigger_lbutton); -#undef REGISTER_KEYBOARD_ACTION - -LLViewerInput::LLViewerInput() -{ - resetBindings(); - - for (S32 i = 0; i < KEY_COUNT; i++) - { - mKeyHandledByUI[i] = false; - } - for (S32 i = 0; i < CLICK_COUNT; i++) - { - mMouseLevel[i] = MOUSE_STATE_SILENT; - } - // we want the UI to never see these keys so that they can always control the avatar/camera - for(KEY k = KEY_PAD_UP; k <= KEY_PAD_DIVIDE; k++) - { - mKeysSkippedByUI.insert(k); - } -} - -LLViewerInput::~LLViewerInput() -{ - -} - -// static -bool LLViewerInput::modeFromString(const std::string& string, S32 *mode) -{ - if (string.empty()) - { - return false; - } - - std::string cmp_string = string; - LLStringUtil::toLower(cmp_string); - if (cmp_string == "first_person") - { - *mode = MODE_FIRST_PERSON; - return true; - } - else if (cmp_string == "third_person") - { - *mode = MODE_THIRD_PERSON; - return true; - } - else if (cmp_string == "edit_avatar") - { - *mode = MODE_EDIT_AVATAR; - return true; - } - else if (cmp_string == "sitting") - { - *mode = MODE_SITTING; - return true; - } - - S32 val = atoi(string.c_str()); - if (val >= 0 && val < MODE_COUNT) - { - *mode = val; - return true; - } - - return false; -} - -// static -bool LLViewerInput::mouseFromString(const std::string& string, EMouseClickType *mode) -{ - if (string == "LMB") - { - *mode = CLICK_LEFT; - return true; - } - else if (string == "Double LMB") - { - *mode = CLICK_DOUBLELEFT; - return true; - } - else if (string == "MMB") - { - *mode = CLICK_MIDDLE; - return true; - } - else if (string == "MB4") - { - *mode = CLICK_BUTTON4; - return true; - } - else if (string == "MB5") - { - *mode = CLICK_BUTTON5; - return true; - } - else - { - *mode = CLICK_NONE; - return false; - } -} - -bool LLViewerInput::handleKey(KEY translated_key, MASK translated_mask, bool repeated) -{ - // check for re-map - EKeyboardMode mode = gViewerInput.getMode(); - U32 keyidx = (translated_mask<<16) | translated_key; - key_remap_t::iterator iter = mRemapKeys[mode].find(keyidx); - if (iter != mRemapKeys[mode].end()) - { - translated_key = (iter->second) & 0xff; - translated_mask = (iter->second)>>16; - } - - // No repeats of F-keys - bool repeatable_key = (translated_key < KEY_F1 || translated_key > KEY_F12); - if (!repeatable_key && repeated) - { - return false; - } - - LL_DEBUGS("UserInput") << "keydown -" << translated_key << "-" << LL_ENDL; - // skip skipped keys - if(mKeysSkippedByUI.find(translated_key) != mKeysSkippedByUI.end()) - { - mKeyHandledByUI[translated_key] = false; - LL_INFOS("KeyboardHandling") << "Key wasn't handled by UI!" << LL_ENDL; - } - else - { - // it is sufficient to set this value once per call to handlekey - // without clearing it, as it is only used in the subsequent call to scanKey - mKeyHandledByUI[translated_key] = gViewerWindow->handleKey(translated_key, translated_mask); - // mKeyHandledByUI is not what you think ... this indicates whether the UI has handled this keypress yet (any keypress) - // NOT whether some UI shortcut wishes to handle the keypress - - } - return mKeyHandledByUI[translated_key]; -} - -bool LLViewerInput::handleKeyUp(KEY translated_key, MASK translated_mask) -{ - return gViewerWindow->handleKeyUp(translated_key, translated_mask); -} - -bool LLViewerInput::handleGlobalBindsKeyDown(KEY key, MASK mask) -{ - if (LLSetKeyBindDialog::isRecording()) - { - // handleGlobalBindsKeyDown happens before view handling, so can't - // be interupted by LLSetKeyBindDialog, check manually - return false; - } - S32 mode = getMode(); - return scanKey(mGlobalKeyBindings[mode], mGlobalKeyBindings[mode].size(), key, mask, true, false, false, false); -} - -bool LLViewerInput::handleGlobalBindsKeyUp(KEY key, MASK mask) -{ - if (LLSetKeyBindDialog::isRecording()) - { - // handleGlobalBindsKeyUp happens before view handling, so can't - // be interupted by LLSetKeyBindDialog, check manually - return false; - } - - S32 mode = getMode(); - return scanKey(mGlobalKeyBindings[mode], mGlobalKeyBindings[mode].size(), key, mask, false, true, false, false); -} - -bool LLViewerInput::handleGlobalBindsMouse(EMouseClickType clicktype, MASK mask, bool down) -{ - if (LLSetKeyBindDialog::isRecording()) - { - // handleGlobalBindsMouse happens before view handling, so can't - // be interupted by LLSetKeyBindDialog, check manually - return false; - } - - bool res = false; - S32 mode = getMode(); - if (down) - { - res = scanMouse(mGlobalMouseBindings[mode], mGlobalMouseBindings[mode].size(), clicktype, mask, MOUSE_STATE_DOWN, true); - } - else - { - res = scanMouse(mGlobalMouseBindings[mode], mGlobalMouseBindings[mode].size(), clicktype, mask, MOUSE_STATE_UP, true); - } - return res; -} - -bool LLViewerInput::bindKey(const S32 mode, const KEY key, const MASK mask, const std::string& function_name) -{ - S32 index; - typedef boost::function function_t; - function_t function = NULL; - std::string name; - - // Allow remapping of F2-F12 - if (function_name[0] == 'F') - { - int c1 = function_name[1] - '0'; - int c2 = function_name[2] ? function_name[2] - '0' : -1; - if (c1 >= 0 && c1 <= 9 && c2 >= -1 && c2 <= 9) - { - int idx = c1; - if (c2 >= 0) - idx = idx*10 + c2; - if (idx >=2 && idx <= 12) - { - U32 keyidx = ((mask<<16)|key); - (mRemapKeys[mode])[keyidx] = ((0<<16)|(KEY_F1+(idx-1))); - return true; - } - } - } - - // Not remapped, look for a function - - LLKeybindFunctionData* result = LLKeyboardActionRegistry::getValue(function_name); - if (result) - { - function = result->mFunction; - } - - if (!function) - { - LL_WARNS_ONCE() << "Can't bind key to function " << function_name << ", no function with this name found" << LL_ENDL; - return false; - } - - if (mode >= MODE_COUNT) - { - LL_ERRS() << "LLKeyboard::bindKey() - unknown mode passed" << mode << LL_ENDL; - return false; - } - - // check for duplicate first and overwrite - if (result->mIsGlobal) - { - S32 size = mGlobalKeyBindings[mode].size(); - for (index = 0; index < size; index++) - { - if (key == mGlobalKeyBindings[mode][index].mKey && mask == mGlobalKeyBindings[mode][index].mMask) - { - mGlobalKeyBindings[mode][index].mFunction = function; - return true; - } - } - } - else - { - S32 size = mKeyBindings[mode].size(); - for (index = 0; index < size; index++) - { - if (key == mKeyBindings[mode][index].mKey && mask == mKeyBindings[mode][index].mMask) - { - mKeyBindings[mode][index].mFunction = function; - return true; - } - } - } - - LLKeyboardBinding bind; - bind.mKey = key; - bind.mMask = mask; - bind.mFunction = function; - bind.mFunctionName = function_name; - - if (result->mIsGlobal) - { - mGlobalKeyBindings[mode].push_back(bind); - } - else - { - mKeyBindings[mode].push_back(bind); - } - - return true; -} - -bool LLViewerInput::bindMouse(const S32 mode, const EMouseClickType mouse, const MASK mask, const std::string& function_name) -{ - S32 index; - typedef boost::function function_t; - function_t function = NULL; - - if (mouse == CLICK_LEFT - && mask == MASK_NONE - && function_name == script_mouse_handler_name) - { - // Special case - // Left click has script overrides and by default - // is handled via agent_control_lbutton as last option - // In case of mouselook and present overrides it has highest - // priority even over UI and is handled in LLToolCompGun::handleMouseDown - // so just mark it as having default handler - mLMouseDefaultHandling[mode] = true; - return true; - } - - LLKeybindFunctionData* result = LLKeyboardActionRegistry::getValue(function_name); - if (result) - { - function = result->mFunction; - } - - if (!function) - { - LL_WARNS_ONCE() << "Can't bind mouse key to function " << function_name << ", no function with this name found" << LL_ENDL; - return false; - } - - if (mode >= MODE_COUNT) - { - LL_ERRS() << "LLKeyboard::bindKey() - unknown mode passed" << mode << LL_ENDL; - return false; - } - - // check for duplicate first and overwrite - if (result->mIsGlobal) - { - S32 size = mGlobalMouseBindings[mode].size(); - for (index = 0; index < size; index++) - { - if (mouse == mGlobalMouseBindings[mode][index].mMouse && mask == mGlobalMouseBindings[mode][index].mMask) - { - mGlobalMouseBindings[mode][index].mFunction = function; - return true; - } - } - } - else - { - S32 size = mMouseBindings[mode].size(); - for (index = 0; index < size; index++) - { - if (mouse == mMouseBindings[mode][index].mMouse && mask == mMouseBindings[mode][index].mMask) - { - mMouseBindings[mode][index].mFunction = function; - return true; - } - } - } - - LLMouseBinding bind; - bind.mMouse = mouse; - bind.mMask = mask; - bind.mFunction = function; - bind.mFunctionName = function_name; - - if (result->mIsGlobal) - { - mGlobalMouseBindings[mode].push_back(bind); - } - else - { - mMouseBindings[mode].push_back(bind); - } - - return true; -} - -LLViewerInput::KeyBinding::KeyBinding() -: key("key"), - mouse("mouse"), - mask("mask"), - command("command") -{} - -LLViewerInput::KeyMode::KeyMode() -: bindings("binding") -{} - -LLViewerInput::Keys::Keys() -: first_person("first_person"), - third_person("third_person"), - sitting("sitting"), - edit_avatar("edit_avatar"), - xml_version("xml_version", 0) -{} - -void LLViewerInput::resetBindings() -{ - for (S32 i = 0; i < MODE_COUNT; i++) - { - mGlobalKeyBindings[i].clear(); - mGlobalMouseBindings[i].clear(); - mKeyBindings[i].clear(); - mMouseBindings[i].clear(); - mLMouseDefaultHandling[i] = false; - } -} - -S32 LLViewerInput::loadBindingsXML(const std::string& filename) -{ - resetBindings(); - - S32 binding_count = 0; - Keys keys; - LLSimpleXUIParser parser; - - if (parser.readXUI(filename, keys) - && keys.validateBlock()) - { - binding_count += loadBindingMode(keys.first_person, MODE_FIRST_PERSON); - binding_count += loadBindingMode(keys.third_person, MODE_THIRD_PERSON); - binding_count += loadBindingMode(keys.sitting, MODE_SITTING); - binding_count += loadBindingMode(keys.edit_avatar, MODE_EDIT_AVATAR); - - // verify version - if (keys.xml_version < 1) - { - // updating from a version that was not aware of LMouse bindings - for (S32 i = 0; i < MODE_COUNT; i++) - { - mLMouseDefaultHandling[i] = true; - } - - // fix missing values - KeyBinding mouse_binding; - mouse_binding.key = ""; - mouse_binding.mask = "NONE"; - mouse_binding.mouse = "LMB"; - mouse_binding.command = script_mouse_handler_name; - - if (keys.third_person.isProvided()) - { - keys.third_person.bindings.add(mouse_binding); - } - - if (keys.first_person.isProvided()) - { - keys.first_person.bindings.add(mouse_binding); - } - - if (keys.sitting.isProvided()) - { - keys.sitting.bindings.add(mouse_binding); - } - - if (keys.edit_avatar.isProvided()) - { - keys.edit_avatar.bindings.add(mouse_binding); - } - - // fix version - keys.xml_version.set(keybindings_xml_version, true); - - // Write the resulting XML to file - LLXMLNodePtr output_node = new LLXMLNode("keys", false); - LLXUIParser write_parser; - write_parser.writeXUI(output_node, keys); - - if (!output_node->isNull()) - { - // file in app_settings is supposed to be up to date - // this is only for the file from user_settings - LL_INFOS("ViewerInput") << "Updating file " << filename << " to a newer version" << LL_ENDL; - LLFILE *fp = LLFile::fopen(filename, "w"); - if (fp != NULL) - { - LLXMLNode::writeHeaderToFile(fp); - output_node->writeToFile(fp); - fclose(fp); - } - } - } - } - return binding_count; -} - -S32 count_masks(const MASK &mask) -{ - S32 res = 0; - if (mask & MASK_CONTROL) - { - res++; - } - if (mask & MASK_SHIFT) - { - res++; - } - if (mask & MASK_ALT) - { - res++; - } - return res; -} - -bool compare_key_by_mask(LLKeyboardBinding i1, LLKeyboardBinding i2) -{ - return (count_masks(i1.mMask) > count_masks(i2.mMask)); -} - -bool compare_mouse_by_mask(LLMouseBinding i1, LLMouseBinding i2) -{ - return (count_masks(i1.mMask) > count_masks(i2.mMask)); -} - -S32 LLViewerInput::loadBindingMode(const LLViewerInput::KeyMode& keymode, S32 mode) -{ - S32 binding_count = 0; - for (LLInitParam::ParamIterator::const_iterator it = keymode.bindings.begin(), - end_it = keymode.bindings.end(); - it != end_it; - ++it) - { - bool processed = false; - std::string key_str = it->key.getValue(); - if (!key_str.empty() && key_str != "NONE") - { - KEY key; - LLKeyboard::keyFromString(key_str, &key); - if (key != KEY_NONE) - { - MASK mask; - LLKeyboard::maskFromString(it->mask, &mask); - bindKey(mode, key, mask, it->command); - processed = true; - } - else - { - LL_WARNS_ONCE() << "There might be issues in keybindings' file" << LL_ENDL; - } - } - if (!processed && it->mouse.isProvided() && !it->mouse.getValue().empty()) - { - EMouseClickType mouse; - mouseFromString(it->mouse.getValue(), &mouse); - if (mouse != CLICK_NONE) - { - MASK mask; - LLKeyboard::maskFromString(it->mask, &mask); - bindMouse(mode, mouse, mask, it->command); - processed = true; - } - else - { - LL_WARNS_ONCE() << "There might be issues in keybindings' file" << LL_ENDL; - } - } - if (processed) - { - // total - binding_count++; - } - } - - // sort lists by mask (so that Shift+W is executed before W, if both are assigned, but if Shift+W is not assigned W should be executed) - std::sort(mKeyBindings[mode].begin(), mKeyBindings[mode].end(), compare_key_by_mask); - std::sort(mMouseBindings[mode].begin(), mMouseBindings[mode].end(), compare_mouse_by_mask); - - return binding_count; -} - -EKeyboardMode LLViewerInput::getMode() const -{ - if ( gAgentCamera.cameraMouselook() ) - { - return MODE_FIRST_PERSON; - } - else if ( gMorphView && gMorphView->getVisible()) - { - return MODE_EDIT_AVATAR; - } - else if (isAgentAvatarValid() && gAgentAvatarp->isSitting()) - { - return MODE_SITTING; - } - else - { - return MODE_THIRD_PERSON; - } -} - -bool LLViewerInput::scanKey(const std::vector &binding, - S32 binding_count, - KEY key, - MASK mask, - bool key_down, - bool key_up, - bool key_level, - bool repeat) const -{ - for (S32 i = 0; i < binding_count; i++) - { - if (binding[i].mKey == key) - { - if ((binding[i].mMask & mask) == binding[i].mMask) - { - bool res = false; - if (key_down && !repeat) - { - // ...key went down this frame, call function - res = binding[i].mFunction( KEYSTATE_DOWN ); - return true; - } - else if (key_up) - { - // ...key went down this frame, call function - res = binding[i].mFunction( KEYSTATE_UP ); - } - else if (key_level) - { - // ...key held down from previous frame - // Not windows, just call the function. - res = binding[i].mFunction( KEYSTATE_LEVEL ); - }//if - // Key+Mask combinations are supposed to be unique, so we won't find anything else - return res; - }//if - }//if - }//for - return false; -} - -// Called from scanKeyboard. -bool LLViewerInput::scanKey(KEY key, bool key_down, bool key_up, bool key_level) const -{ - if (LLApp::isExiting()) - { - return false; - } - - S32 mode = getMode(); - // Consider keyboard scanning as NOT mouse event. JC - MASK mask = gKeyboard->currentMask(false); - - if (mKeyHandledByUI[key]) - { - return false; - } - - // don't process key down on repeated keys - bool repeat = gKeyboard->getKeyRepeated(key); - - bool res = scanKey(mKeyBindings[mode], mKeyBindings[mode].size(), key, mask, key_down, key_up, key_level, repeat); - - return res; -} - -bool LLViewerInput::handleMouse(LLWindow *window_impl, LLCoordGL pos, MASK mask, EMouseClickType clicktype, bool down) -{ - bool is_toolmgr_action = false; - bool handled = gViewerWindow->handleAnyMouseClick(window_impl, pos, mask, clicktype, down, is_toolmgr_action); - - if (clicktype != CLICK_NONE) - { - // Special case - // If UI doesn't handle double click, LMB click is issued, so supres LMB 'down' when doubleclick is set - // handle !down as if we are handling doubleclick - - bool double_click_sp = (clicktype == CLICK_LEFT - && (mMouseLevel[CLICK_DOUBLELEFT] != MOUSE_STATE_SILENT) - && mMouseLevel[CLICK_LEFT] == MOUSE_STATE_SILENT); - if (double_click_sp && !down) - { - // Process doubleclick instead - clicktype = CLICK_DOUBLELEFT; - } - - // If the first LMB click is handled by the menu, skip the following double click - static bool skip_double_click = false; - if (clicktype == CLICK_LEFT && down) - { - skip_double_click = is_toolmgr_action ? false : handled; - } - - if (double_click_sp && down) - { - // Consume click. - // Due to handling, double click that is not handled will be immediately followed by LMB click - } - else if (clicktype == CLICK_DOUBLELEFT && skip_double_click) - { - handled = true; - } - // If UI handled 'down', it should handle 'up' as well - // If we handle 'down' not by UI, then we should handle 'up'/'level' regardless of UI - else if (handled) - { - // UI handled new 'down' so iterupt whatever state we were in. - if (mMouseLevel[clicktype] != MOUSE_STATE_SILENT) - { - if (mMouseLevel[clicktype] == MOUSE_STATE_DOWN) - { - mMouseLevel[clicktype] = MOUSE_STATE_CLICK; - } - else - { - mMouseLevel[clicktype] = MOUSE_STATE_UP; - } - } - } - else if (down) - { - if (mMouseLevel[clicktype] == MOUSE_STATE_DOWN) - { - // this is repeated hit (mouse does not repeat event until release) - // for now treat rapid clicking like mouse being held - mMouseLevel[clicktype] = MOUSE_STATE_LEVEL; - } - else - { - mMouseLevel[clicktype] = MOUSE_STATE_DOWN; - } - } - else if (mMouseLevel[clicktype] != MOUSE_STATE_SILENT) - { - // Released mouse key - if (mMouseLevel[clicktype] == MOUSE_STATE_DOWN) - { - mMouseLevel[clicktype] = MOUSE_STATE_CLICK; - } - else - { - mMouseLevel[clicktype] = MOUSE_STATE_UP; - } - } - } - - return handled; -} - -bool LLViewerInput::scanMouse( - const std::vector &binding, - S32 binding_count, - EMouseClickType mouse, - MASK mask, - EMouseState state, - bool ignore_additional_masks -) const -{ - for (S32 i = 0; i < binding_count; i++) - { - if (binding[i].mMouse == mouse && (ignore_additional_masks ? (binding[i].mMask & mask) == binding[i].mMask : binding[i].mMask == mask)) - { - bool res = false; - switch (state) - { - case MOUSE_STATE_DOWN: - res = binding[i].mFunction(KEYSTATE_DOWN); - break; - case MOUSE_STATE_CLICK: - // Button went down and up in scope of single frame - // might not work best with some functions, - // but some function need specific states specifically - res = binding[i].mFunction(KEYSTATE_DOWN); - res |= binding[i].mFunction(KEYSTATE_UP); - break; - case MOUSE_STATE_LEVEL: - res = binding[i].mFunction(KEYSTATE_LEVEL); - break; - case MOUSE_STATE_UP: - res = binding[i].mFunction(KEYSTATE_UP); - break; - default: - break; - } - // Key+Mask combinations are supposed to be unique, no need to continue - return res; - } - } - return false; -} - -// todo: this recods key, scanMouse() triggers functions with EKeystate -bool LLViewerInput::scanMouse(EMouseClickType click, EMouseState state) const -{ - bool res = false; - S32 mode = getMode(); - MASK mask = gKeyboard->currentMask(true); - res = scanMouse(mMouseBindings[mode], mMouseBindings[mode].size(), click, mask, state, false); - - // No user defined actions found or those actions can't handle the key/button, - // so handle CONTROL_LBUTTON if nessesary. - // - // Default handling for MODE_FIRST_PERSON is in LLToolCompGun::handleMouseDown, - // and sends AGENT_CONTROL_ML_LBUTTON_DOWN, but it only applies if ML controls - // are leftButtonGrabbed(), send a normal click otherwise. - - if (!res - && mLMouseDefaultHandling[mode] - && (mode != MODE_FIRST_PERSON || !gAgent.leftButtonGrabbed()) - && (click == CLICK_LEFT || click == CLICK_DOUBLELEFT) - ) - { - switch (state) - { - case MOUSE_STATE_DOWN: - agent_control_lbutton_handle(KEYSTATE_DOWN); - res = true; - break; - case MOUSE_STATE_CLICK: - // might not work best with some functions, - // but some function need specific states too specifically - agent_control_lbutton_handle(KEYSTATE_DOWN); - agent_control_lbutton_handle(KEYSTATE_UP); - res = true; - break; - case MOUSE_STATE_UP: - agent_control_lbutton_handle(KEYSTATE_UP); - res = true; - break; - default: - break; - } - } - return res; -} - -void LLViewerInput::scanMouse() -{ - for (S32 i = 0; i < CLICK_COUNT; i++) - { - if (mMouseLevel[i] != MOUSE_STATE_SILENT) - { - scanMouse((EMouseClickType)i, mMouseLevel[i]); - if (mMouseLevel[i] == MOUSE_STATE_DOWN) - { - // mouse doesn't support 'continued' state, so after handling, switch to LEVEL - mMouseLevel[i] = MOUSE_STATE_LEVEL; - } - else if (mMouseLevel[i] == MOUSE_STATE_UP || mMouseLevel[i] == MOUSE_STATE_CLICK) - { - mMouseLevel[i] = MOUSE_STATE_SILENT; - } - } - } -} - -bool LLViewerInput::isMouseBindUsed(const EMouseClickType mouse, const MASK mask, const S32 mode) const -{ - S32 size = mMouseBindings[mode].size(); - for (S32 index = 0; index < size; index++) - { - if (mouse == mMouseBindings[mode][index].mMouse && mask == mMouseBindings[mode][index].mMask) - return true; - } - size = mGlobalMouseBindings[mode].size(); - for (S32 index = 0; index < size; index++) - { - if (mouse == mGlobalMouseBindings[mode][index].mMouse && mask == mGlobalMouseBindings[mode][index].mMask) - return true; - } - return false; -} - -std::string LLViewerInput::getKeyBindingAsString(const std::string& mode, const std::string& control) const -{ - S32 keyboard_mode; - if (!modeFromString(mode, &keyboard_mode)) - { - keyboard_mode = getMode(); - } - - std::string res; - bool needs_separator = false; - - // keybindings are sorted from having most mask to no mask (from restrictive to less restrictive), - // but it's visually better to present this data in reverse - std::vector::const_reverse_iterator iter_key = mKeyBindings[keyboard_mode].rbegin(); - while (iter_key != mKeyBindings[keyboard_mode].rend()) - { - if (iter_key->mFunctionName == control) - { - if (needs_separator) - { - res.append(" | "); - } - res.append(LLKeyboard::stringFromAccelerator(iter_key->mMask, iter_key->mKey)); - needs_separator = true; - } - iter_key++; - } - - std::vector::const_reverse_iterator iter_mouse = mMouseBindings[keyboard_mode].rbegin(); - while (iter_mouse != mMouseBindings[keyboard_mode].rend()) - { - if (iter_mouse->mFunctionName == control) - { - if (needs_separator) - { - res.append(" | "); - } - res.append(LLKeyboard::stringFromAccelerator(iter_mouse->mMask, iter_mouse->mMouse)); - needs_separator = true; - } - iter_mouse++; - } - - return res; -} +/** + * @file llviewerinput.cpp + * @brief LLViewerInput class implementation + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llviewerinput.h" + +#include "llappviewer.h" +#include "llfloaterreg.h" +#include "llmath.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llfloaterimnearbychat.h" +#include "llfocusmgr.h" +#include "llkeybind.h" // LLKeyData +#include "llmorphview.h" +#include "llmoveview.h" +#include "llsetkeybinddialog.h" +#include "lltoolfocus.h" +#include "lltoolpie.h" +#include "llviewercontrol.h" +#include "llviewerwindow.h" +#include "llvoavatarself.h" +#include "llfloatercamera.h" +#include "llinitparam.h" +#include "llselectmgr.h" + +// +// Constants +// + +const F32 FLY_TIME = 0.5f; +const F32 FLY_FRAMES = 4; + +const F32 NUDGE_TIME = 0.25f; // in seconds +const S32 NUDGE_FRAMES = 2; +const F32 ORBIT_NUDGE_RATE = 0.05f; // fraction of normal speed + +const LLKeyData agent_control_lbutton(CLICK_LEFT, KEY_NONE, MASK_NONE, true); + +struct LLKeybindFunctionData +{ + LLKeybindFunctionData(boost::function function, bool global) + : + mFunction(function), + mIsGlobal(global) + { + } + boost::function mFunction; + // todo: might be good idea to make this into enum, like: global/inworld/menu + bool mIsGlobal; +}; + +struct LLKeyboardActionRegistry +: public LLRegistrySingleton +{ + LLSINGLETON_EMPTY_CTOR(LLKeyboardActionRegistry); +}; + +LLViewerInput gViewerInput; + +bool agent_jump( EKeystate s ) +{ + static bool first_fly_attempt(true); + if (KEYSTATE_UP == s) + { + first_fly_attempt = true; + return true; + } + F32 time = gKeyboard->getCurKeyElapsedTime(); + S32 frame_count = ll_round(gKeyboard->getCurKeyElapsedFrameCount()); + + if( time < FLY_TIME + || frame_count <= FLY_FRAMES + || gAgent.upGrabbed() + || !gSavedSettings.getBOOL("AutomaticFly")) + { + gAgent.moveUp(1); + } + else + { + gAgent.setFlying(true, first_fly_attempt); + first_fly_attempt = false; + gAgent.moveUp(1); + } + return true; +} + +bool agent_push_down( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + gAgent.moveUp(-1); + return true; +} + +static void agent_check_temporary_run(LLAgent::EDoubleTapRunMode mode) +{ + if (gAgent.mDoubleTapRunMode == mode && + gAgent.getRunning() && + !gAgent.getAlwaysRun()) + { + // Turn off temporary running. + gAgent.clearRunning(); + gAgent.sendWalkRun(gAgent.getRunning()); + } +} + +static void agent_handle_doubletap_run(EKeystate s, LLAgent::EDoubleTapRunMode mode) +{ + if (KEYSTATE_UP == s) + { + // Note: in case shift is already released, slide left/right run + // will be released in agent_turn_left()/agent_turn_right() + agent_check_temporary_run(mode); + } + else if (gSavedSettings.getBOOL("AllowTapTapHoldRun") && + KEYSTATE_DOWN == s && + !gAgent.getRunning()) + { + if (gAgent.mDoubleTapRunMode == mode && + gAgent.mDoubleTapRunTimer.getElapsedTimeF32() < NUDGE_TIME) + { + // Same walk-key was pushed again quickly; this is a + // double-tap so engage temporary running. + gAgent.setRunning(); + gAgent.sendWalkRun(gAgent.getRunning()); + } + + // Pressing any walk-key resets the double-tap timer + gAgent.mDoubleTapRunTimer.reset(); + gAgent.mDoubleTapRunMode = mode; + } +} + +static void agent_push_forwardbackward( EKeystate s, S32 direction, LLAgent::EDoubleTapRunMode mode ) +{ + agent_handle_doubletap_run(s, mode); + if (KEYSTATE_UP == s) return; + + F32 time = gKeyboard->getCurKeyElapsedTime(); + S32 frame_count = ll_round(gKeyboard->getCurKeyElapsedFrameCount()); + + if( time < NUDGE_TIME || frame_count <= NUDGE_FRAMES) + { + gAgent.moveAtNudge(direction); + } + else + { + gAgent.moveAt(direction); + } +} + +bool camera_move_forward( EKeystate s ); + +bool agent_push_forward( EKeystate s ) +{ + if(gAgent.isMovementLocked()) return true; + + //in free camera control mode we need to intercept keyboard events for avatar movements + if (LLFloaterCamera::inFreeCameraMode()) + { + camera_move_forward(s); + } + else + { + agent_push_forwardbackward(s, 1, LLAgent::DOUBLETAP_FORWARD); + } + return true; +} + +bool camera_move_backward( EKeystate s ); + +bool agent_push_backward( EKeystate s ) +{ + if(gAgent.isMovementLocked()) return true; + + //in free camera control mode we need to intercept keyboard events for avatar movements + if (LLFloaterCamera::inFreeCameraMode()) + { + camera_move_backward(s); + } + else if (!gAgent.backwardGrabbed() && gAgentAvatarp->isSitting() && gSavedSettings.getBOOL("LeaveMouselook")) + { + gAgentCamera.changeCameraToThirdPerson(); + } + else + { + agent_push_forwardbackward(s, -1, LLAgent::DOUBLETAP_BACKWARD); + } + return true; +} + +static void agent_slide_leftright( EKeystate s, S32 direction, LLAgent::EDoubleTapRunMode mode ) +{ + agent_handle_doubletap_run(s, mode); + if( KEYSTATE_UP == s ) return; + F32 time = gKeyboard->getCurKeyElapsedTime(); + S32 frame_count = ll_round(gKeyboard->getCurKeyElapsedFrameCount()); + + if( time < NUDGE_TIME || frame_count <= NUDGE_FRAMES) + { + gAgent.moveLeftNudge(direction); + } + else + { + gAgent.moveLeft(direction); + } +} + + +bool agent_slide_left( EKeystate s ) +{ + if(gAgent.isMovementLocked()) return true; + agent_slide_leftright(s, 1, LLAgent::DOUBLETAP_SLIDELEFT); + return true; +} + + +bool agent_slide_right( EKeystate s ) +{ + if(gAgent.isMovementLocked()) return true; + agent_slide_leftright(s, -1, LLAgent::DOUBLETAP_SLIDERIGHT); + return true; +} + +bool camera_spin_around_cw( EKeystate s ); + +bool agent_turn_left(EKeystate s) +{ + //in free camera control mode we need to intercept keyboard events for avatar movements + if (LLFloaterCamera::inFreeCameraMode()) + { + camera_spin_around_cw(s); + return true; + } + + if(gAgent.isMovementLocked()) return false; + + if (LLToolCamera::getInstance()->mouseSteerMode()) + { + agent_slide_left(s); + } + else + { + if (KEYSTATE_UP == s) + { + // Check temporary running. In case user released 'left' key with shift already released. + agent_check_temporary_run(LLAgent::DOUBLETAP_SLIDELEFT); + return true; + } + F32 time = gKeyboard->getCurKeyElapsedTime(); + gAgent.moveYaw( LLFloaterMove::getYawRate( time ) ); + } + return true; +} + +bool camera_spin_around_ccw( EKeystate s ); + +bool agent_turn_right( EKeystate s ) +{ + //in free camera control mode we need to intercept keyboard events for avatar movements + if (LLFloaterCamera::inFreeCameraMode()) + { + camera_spin_around_ccw(s); + return true; + } + + if(gAgent.isMovementLocked()) return false; + + if (LLToolCamera::getInstance()->mouseSteerMode()) + { + agent_slide_right(s); + } + else + { + if (KEYSTATE_UP == s) + { + // Check temporary running. In case user released 'right' key with shift already released. + agent_check_temporary_run(LLAgent::DOUBLETAP_SLIDERIGHT); + return true; + } + F32 time = gKeyboard->getCurKeyElapsedTime(); + gAgent.moveYaw( -LLFloaterMove::getYawRate( time ) ); + } + return true; +} + +bool agent_look_up( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + gAgent.movePitch(-1); + //gAgent.rotate(-2.f * DEG_TO_RAD, gAgent.getFrame().getLeftAxis() ); + return true; +} + + +bool agent_look_down( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + gAgent.movePitch(1); + //gAgent.rotate(2.f * DEG_TO_RAD, gAgent.getFrame().getLeftAxis() ); + return true; +} + +bool agent_toggle_fly( EKeystate s ) +{ + // Only catch the edge + if (KEYSTATE_DOWN == s ) + { + LLAgent::toggleFlying(); + } + return true; +} + +F32 get_orbit_rate() +{ + F32 time = gKeyboard->getCurKeyElapsedTime(); + if( time < NUDGE_TIME ) + { + F32 rate = ORBIT_NUDGE_RATE + time * (1 - ORBIT_NUDGE_RATE)/ NUDGE_TIME; + //LL_INFOS() << rate << LL_ENDL; + return rate; + } + else + { + return 1; + } +} + +bool camera_spin_around_ccw( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + gAgentCamera.unlockView(); + gAgentCamera.setOrbitLeftKey( get_orbit_rate() ); + return true; +} + + +bool camera_spin_around_cw( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + gAgentCamera.unlockView(); + gAgentCamera.setOrbitRightKey( get_orbit_rate() ); + return true; +} + +bool camera_spin_around_ccw_sitting( EKeystate s ) +{ + if( KEYSTATE_UP == s && gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_SLIDERIGHT ) return true; + if (gAgent.rotateGrabbed() || gAgentCamera.sitCameraEnabled() || gAgent.getRunning()) + { + //send keystrokes, but do not change camera + agent_turn_right(s); + } + else + { + //change camera but do not send keystrokes + gAgentCamera.unlockView(); + gAgentCamera.setOrbitLeftKey( get_orbit_rate() ); + } + return true; +} + + +bool camera_spin_around_cw_sitting( EKeystate s ) +{ + if( KEYSTATE_UP == s && gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_SLIDELEFT ) return true; + if (gAgent.rotateGrabbed() || gAgentCamera.sitCameraEnabled() || gAgent.getRunning()) + { + //send keystrokes, but do not change camera + agent_turn_left(s); + } + else + { + //change camera but do not send keystrokes + gAgentCamera.unlockView(); + gAgentCamera.setOrbitRightKey( get_orbit_rate() ); + } + return true; +} + + +bool camera_spin_over( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + gAgentCamera.unlockView(); + gAgentCamera.setOrbitUpKey( get_orbit_rate() ); + return true; +} + + +bool camera_spin_under( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + gAgentCamera.unlockView(); + gAgentCamera.setOrbitDownKey( get_orbit_rate() ); + return true; +} + +bool camera_spin_over_sitting( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + if (gAgent.upGrabbed() || gAgentCamera.sitCameraEnabled()) + { + //send keystrokes, but do not change camera + agent_jump(s); + } + else + { + //change camera but do not send keystrokes + gAgentCamera.setOrbitUpKey( get_orbit_rate() ); + } + return true; +} + + +bool camera_spin_under_sitting( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + if (gAgent.downGrabbed() || gAgentCamera.sitCameraEnabled()) + { + //send keystrokes, but do not change camera + agent_push_down(s); + } + else + { + //change camera but do not send keystrokes + gAgentCamera.setOrbitDownKey( get_orbit_rate() ); + } + return true; +} + +bool camera_move_forward( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + gAgentCamera.unlockView(); + gAgentCamera.setOrbitInKey( get_orbit_rate() ); + return true; +} + + +bool camera_move_backward( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + gAgentCamera.unlockView(); + gAgentCamera.setOrbitOutKey( get_orbit_rate() ); + return true; +} + +bool camera_move_forward_sitting( EKeystate s ) +{ + if( KEYSTATE_UP == s && gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_FORWARD ) return true; + if (gAgent.forwardGrabbed() || gAgentCamera.sitCameraEnabled() || (gAgent.getRunning() && !gAgent.getAlwaysRun())) + { + agent_push_forward(s); + } + else + { + gAgentCamera.setOrbitInKey( get_orbit_rate() ); + } + return true; +} + +bool camera_move_backward_sitting( EKeystate s ) +{ + if( KEYSTATE_UP == s && gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_BACKWARD ) return true; + + if (gAgent.backwardGrabbed() || gAgentCamera.sitCameraEnabled() || (gAgent.getRunning() && !gAgent.getAlwaysRun())) + { + agent_push_backward(s); + } + else + { + gAgentCamera.setOrbitOutKey( get_orbit_rate() ); + } + return true; +} + +bool camera_pan_up( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + gAgentCamera.unlockView(); + gAgentCamera.setPanUpKey( get_orbit_rate() ); + return true; +} + +bool camera_pan_down( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + gAgentCamera.unlockView(); + gAgentCamera.setPanDownKey( get_orbit_rate() ); + return true; +} + +bool camera_pan_left( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + gAgentCamera.unlockView(); + gAgentCamera.setPanLeftKey( get_orbit_rate() ); + return true; +} + +bool camera_pan_right( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + gAgentCamera.unlockView(); + gAgentCamera.setPanRightKey( get_orbit_rate() ); + return true; +} + +bool camera_pan_in( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + gAgentCamera.unlockView(); + gAgentCamera.setPanInKey( get_orbit_rate() ); + return true; +} + +bool camera_pan_out( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + gAgentCamera.unlockView(); + gAgentCamera.setPanOutKey( get_orbit_rate() ); + return true; +} + +bool camera_move_forward_fast( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + gAgentCamera.unlockView(); + gAgentCamera.setOrbitInKey(2.5f); + return true; +} + +bool camera_move_backward_fast( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + gAgentCamera.unlockView(); + gAgentCamera.setOrbitOutKey(2.5f); + return true; +} + + +bool edit_avatar_spin_ccw( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + gMorphView->setCameraDrivenByKeys( true ); + gAgentCamera.setOrbitLeftKey( get_orbit_rate() ); + //gMorphView->orbitLeft( get_orbit_rate() ); + return true; +} + + +bool edit_avatar_spin_cw( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + gMorphView->setCameraDrivenByKeys( true ); + gAgentCamera.setOrbitRightKey( get_orbit_rate() ); + //gMorphView->orbitRight( get_orbit_rate() ); + return true; +} + +bool edit_avatar_spin_over( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + gMorphView->setCameraDrivenByKeys( true ); + gAgentCamera.setOrbitUpKey( get_orbit_rate() ); + //gMorphView->orbitUp( get_orbit_rate() ); + return true; +} + + +bool edit_avatar_spin_under( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + gMorphView->setCameraDrivenByKeys( true ); + gAgentCamera.setOrbitDownKey( get_orbit_rate() ); + //gMorphView->orbitDown( get_orbit_rate() ); + return true; +} + +bool edit_avatar_move_forward( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + gMorphView->setCameraDrivenByKeys( true ); + gAgentCamera.setOrbitInKey( get_orbit_rate() ); + //gMorphView->orbitIn(); + return true; +} + + +bool edit_avatar_move_backward( EKeystate s ) +{ + if( KEYSTATE_UP == s ) return true; + gMorphView->setCameraDrivenByKeys( true ); + gAgentCamera.setOrbitOutKey( get_orbit_rate() ); + //gMorphView->orbitOut(); + return true; +} + +bool stop_moving( EKeystate s ) +{ + //it's supposed that 'stop moving' key will be held down for some time + if( KEYSTATE_UP == s ) return true; + // stop agent + gAgent.setControlFlags(AGENT_CONTROL_STOP); + + // cancel autopilot + gAgent.stopAutoPilot(); + return true; +} + +bool start_chat( EKeystate s ) +{ + if (LLAppViewer::instance()->quitRequested()) + { + return true; // can't talk, gotta go, kthxbye! + } + if (KEYSTATE_DOWN != s) return true; + + // start chat + LLFloaterIMNearbyChat::startChat(NULL); + return true; +} + +bool start_gesture( EKeystate s ) +{ + LLUICtrl* focus_ctrlp = dynamic_cast(gFocusMgr.getKeyboardFocus()); + if (KEYSTATE_UP == s && + ! (focus_ctrlp && focus_ctrlp->acceptsTextInput())) + { + if ((LLFloaterReg::getTypedInstance("nearby_chat"))->getCurrentChat().empty()) + { + // No existing chat in chat editor, insert '/' + LLFloaterIMNearbyChat::startChat("/"); + } + else + { + // Don't overwrite existing text in chat editor + LLFloaterIMNearbyChat::startChat(NULL); + } + } + return true; +} + +bool run_forward(EKeystate s) +{ + if (KEYSTATE_UP != s) + { + if (gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_FORWARD) + { + gAgent.mDoubleTapRunMode = LLAgent::DOUBLETAP_FORWARD; + } + if (!gAgent.getRunning()) + { + gAgent.setRunning(); + gAgent.sendWalkRun(true); + } + } + else if(KEYSTATE_UP == s) + { + if (gAgent.mDoubleTapRunMode == LLAgent::DOUBLETAP_FORWARD) + gAgent.mDoubleTapRunMode = LLAgent::DOUBLETAP_NONE; + gAgent.clearRunning(); + gAgent.sendWalkRun(false); + } + agent_push_forward(s); + return true; +} + +bool run_backward(EKeystate s) +{ + if (KEYSTATE_UP != s) + { + if (gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_BACKWARD) + { + gAgent.mDoubleTapRunMode = LLAgent::DOUBLETAP_BACKWARD; + } + if (!gAgent.getRunning()) + { + gAgent.setRunning(); + gAgent.sendWalkRun(true); + } + } + else if (KEYSTATE_UP == s) + { + if (gAgent.mDoubleTapRunMode == LLAgent::DOUBLETAP_BACKWARD) + gAgent.mDoubleTapRunMode = LLAgent::DOUBLETAP_NONE; + gAgent.clearRunning(); + gAgent.sendWalkRun(false); + } + agent_push_backward(s); + return true; +} + +bool run_left(EKeystate s) +{ + if (KEYSTATE_UP != s) + { + if (gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_SLIDELEFT) + { + gAgent.mDoubleTapRunMode = LLAgent::DOUBLETAP_SLIDELEFT; + } + if (!gAgent.getRunning()) + { + gAgent.setRunning(); + gAgent.sendWalkRun(true); + } + } + else if (KEYSTATE_UP == s) + { + if (gAgent.mDoubleTapRunMode == LLAgent::DOUBLETAP_SLIDELEFT) + gAgent.mDoubleTapRunMode = LLAgent::DOUBLETAP_NONE; + gAgent.clearRunning(); + gAgent.sendWalkRun(false); + } + agent_slide_left(s); + return true; +} + +bool run_right(EKeystate s) +{ + if (KEYSTATE_UP != s) + { + if (gAgent.mDoubleTapRunMode != LLAgent::DOUBLETAP_SLIDERIGHT) + { + gAgent.mDoubleTapRunMode = LLAgent::DOUBLETAP_SLIDERIGHT; + } + if (!gAgent.getRunning()) + { + gAgent.setRunning(); + gAgent.sendWalkRun(true); + } + } + else if (KEYSTATE_UP == s) + { + if (gAgent.mDoubleTapRunMode == LLAgent::DOUBLETAP_SLIDERIGHT) + gAgent.mDoubleTapRunMode = LLAgent::DOUBLETAP_NONE; + gAgent.clearRunning(); + gAgent.sendWalkRun(false); + } + agent_slide_right(s); + return true; +} + +bool toggle_run(EKeystate s) +{ + if (KEYSTATE_DOWN != s) return true; + bool run = gAgent.getAlwaysRun(); + if (run) + { + gAgent.clearAlwaysRun(); + gAgent.clearRunning(); + } + else + { + gAgent.setAlwaysRun(); + gAgent.setRunning(); + } + gAgent.sendWalkRun(!run); + return true; +} + +bool toggle_sit(EKeystate s) +{ + if (KEYSTATE_DOWN != s) return true; + if (gAgent.isSitting()) + { + gAgent.standUp(); + } + else + { + gAgent.sitDown(); + } + return true; +} + +bool toggle_pause_media(EKeystate s) // analogue of play/pause button in top bar +{ + if (KEYSTATE_DOWN != s) return true; + bool pause = LLViewerMedia::getInstance()->isAnyMediaPlaying(); + LLViewerMedia::getInstance()->setAllMediaPaused(pause); + return true; +} + +bool toggle_enable_media(EKeystate s) +{ + if (KEYSTATE_DOWN != s) return true; + bool pause = LLViewerMedia::getInstance()->isAnyMediaPlaying() || LLViewerMedia::getInstance()->isAnyMediaShowing(); + LLViewerMedia::getInstance()->setAllMediaEnabled(!pause); + return true; +} + +bool walk_to(EKeystate s) +{ + if (KEYSTATE_DOWN != s) + { + // teleport/walk is usually on mouseclick, mouseclick needs + // to let AGENT_CONTROL_LBUTTON_UP happen if teleport didn't, + // so return false, but if it causes issues, do some kind of + // "return !has_teleported" + return false; + } + return LLToolPie::getInstance()->walkToClickedLocation(); +} + +bool teleport_to(EKeystate s) +{ + if (KEYSTATE_DOWN != s) return false; + return LLToolPie::getInstance()->teleportToClickedLocation(); +} + +bool toggle_voice(EKeystate s) +{ + if (KEYSTATE_DOWN != s) return true; + if (!LLAgent::isActionAllowed("speak")) return false; + LLVoiceClient::getInstance()->toggleUserPTTState(); + return true; +} + +bool voice_follow_key(EKeystate s) +{ + if (KEYSTATE_DOWN == s) + { + if (!LLAgent::isActionAllowed("speak")) return false; + LLVoiceClient::getInstance()->setUserPTTState(true); + return true; + } + else if (KEYSTATE_UP == s && LLVoiceClient::getInstance()->getUserPTTState()) + { + LLVoiceClient::getInstance()->setUserPTTState(false); + return true; + } + return false; +} + +bool script_trigger_lbutton(EKeystate s) +{ + // Check for script overriding/expecting left mouse button. + // Note that this does not pass event further and depends onto mouselook. + // Checks CONTROL_ML_LBUTTON_DOWN_INDEX for mouselook, + // CONTROL_LBUTTON_DOWN_INDEX for normal camera + if (gAgent.leftButtonGrabbed()) + { + bool mouselook = gAgentCamera.cameraMouselook(); + switch (s) + { + case KEYSTATE_DOWN: + if (mouselook) + { + gAgent.setControlFlags(AGENT_CONTROL_ML_LBUTTON_DOWN); + } + else + { + gAgent.setControlFlags(AGENT_CONTROL_LBUTTON_DOWN); + } + return true; + case KEYSTATE_UP: + if (mouselook) + { + gAgent.setControlFlags(AGENT_CONTROL_ML_LBUTTON_UP); + } + else + { + gAgent.setControlFlags(AGENT_CONTROL_LBUTTON_UP); + } + return true; + default: + break; + } + } + return false; +} + +// Used by scripts, for overriding/handling left mouse button +// see mControlsTakenCount +bool agent_control_lbutton_handle(EKeystate s) +{ + switch (s) + { + case KEYSTATE_DOWN: + gAgent.setControlFlags(AGENT_CONTROL_LBUTTON_DOWN); + break; + case KEYSTATE_UP: + gAgent.setControlFlags(AGENT_CONTROL_LBUTTON_UP); + break; + default: + break; + } + return true; +} + +// In-world keybindings, like walking or camera +#define REGISTER_KEYBOARD_ACTION(KEY, ACTION) LLREGISTER_STATIC(LLKeyboardActionRegistry, KEY, LLKeybindFunctionData(ACTION, false)); +// Global keybindings that should work even with floaters focused, like voice +#define REGISTER_KEYBOARD_GLOBAL_ACTION(KEY, ACTION) LLREGISTER_STATIC(LLKeyboardActionRegistry, KEY, LLKeybindFunctionData(ACTION, true)); +REGISTER_KEYBOARD_ACTION("jump", agent_jump); +REGISTER_KEYBOARD_ACTION("push_down", agent_push_down); +REGISTER_KEYBOARD_ACTION("push_forward", agent_push_forward); +REGISTER_KEYBOARD_ACTION("push_backward", agent_push_backward); +REGISTER_KEYBOARD_ACTION("look_up", agent_look_up); +REGISTER_KEYBOARD_ACTION("look_down", agent_look_down); +REGISTER_KEYBOARD_ACTION("toggle_fly", agent_toggle_fly); +REGISTER_KEYBOARD_ACTION("turn_left", agent_turn_left); +REGISTER_KEYBOARD_ACTION("turn_right", agent_turn_right); +REGISTER_KEYBOARD_ACTION("slide_left", agent_slide_left); +REGISTER_KEYBOARD_ACTION("slide_right", agent_slide_right); +REGISTER_KEYBOARD_ACTION("spin_around_ccw", camera_spin_around_ccw); +REGISTER_KEYBOARD_ACTION("spin_around_cw", camera_spin_around_cw); +REGISTER_KEYBOARD_ACTION("spin_around_ccw_sitting", camera_spin_around_ccw_sitting); +REGISTER_KEYBOARD_ACTION("spin_around_cw_sitting", camera_spin_around_cw_sitting); +REGISTER_KEYBOARD_ACTION("spin_over", camera_spin_over); +REGISTER_KEYBOARD_ACTION("spin_under", camera_spin_under); +REGISTER_KEYBOARD_ACTION("spin_over_sitting", camera_spin_over_sitting); +REGISTER_KEYBOARD_ACTION("spin_under_sitting", camera_spin_under_sitting); +REGISTER_KEYBOARD_ACTION("move_forward", camera_move_forward); +REGISTER_KEYBOARD_ACTION("move_backward", camera_move_backward); +REGISTER_KEYBOARD_ACTION("move_forward_sitting", camera_move_forward_sitting); +REGISTER_KEYBOARD_ACTION("move_backward_sitting", camera_move_backward_sitting); +REGISTER_KEYBOARD_ACTION("pan_up", camera_pan_up); +REGISTER_KEYBOARD_ACTION("pan_down", camera_pan_down); +REGISTER_KEYBOARD_ACTION("pan_left", camera_pan_left); +REGISTER_KEYBOARD_ACTION("pan_right", camera_pan_right); +REGISTER_KEYBOARD_ACTION("pan_in", camera_pan_in); +REGISTER_KEYBOARD_ACTION("pan_out", camera_pan_out); +REGISTER_KEYBOARD_ACTION("move_forward_fast", camera_move_forward_fast); +REGISTER_KEYBOARD_ACTION("move_backward_fast", camera_move_backward_fast); +REGISTER_KEYBOARD_ACTION("edit_avatar_spin_ccw", edit_avatar_spin_ccw); +REGISTER_KEYBOARD_ACTION("edit_avatar_spin_cw", edit_avatar_spin_cw); +REGISTER_KEYBOARD_ACTION("edit_avatar_spin_over", edit_avatar_spin_over); +REGISTER_KEYBOARD_ACTION("edit_avatar_spin_under", edit_avatar_spin_under); +REGISTER_KEYBOARD_ACTION("edit_avatar_move_forward", edit_avatar_move_forward); +REGISTER_KEYBOARD_ACTION("edit_avatar_move_backward", edit_avatar_move_backward); +REGISTER_KEYBOARD_ACTION("stop_moving", stop_moving); +REGISTER_KEYBOARD_ACTION("start_chat", start_chat); +REGISTER_KEYBOARD_ACTION("start_gesture", start_gesture); +REGISTER_KEYBOARD_ACTION("run_forward", run_forward); +REGISTER_KEYBOARD_ACTION("run_backward", run_backward); +REGISTER_KEYBOARD_ACTION("run_left", run_left); +REGISTER_KEYBOARD_ACTION("run_right", run_right); +REGISTER_KEYBOARD_ACTION("toggle_run", toggle_run); +REGISTER_KEYBOARD_ACTION("toggle_sit", toggle_sit); +REGISTER_KEYBOARD_ACTION("toggle_pause_media", toggle_pause_media); +REGISTER_KEYBOARD_ACTION("toggle_enable_media", toggle_enable_media); +REGISTER_KEYBOARD_ACTION("teleport_to", teleport_to); +REGISTER_KEYBOARD_ACTION("walk_to", walk_to); +REGISTER_KEYBOARD_GLOBAL_ACTION("toggle_voice", toggle_voice); +REGISTER_KEYBOARD_GLOBAL_ACTION("voice_follow_key", voice_follow_key); +REGISTER_KEYBOARD_ACTION(script_mouse_handler_name, script_trigger_lbutton); +#undef REGISTER_KEYBOARD_ACTION + +LLViewerInput::LLViewerInput() +{ + resetBindings(); + + for (S32 i = 0; i < KEY_COUNT; i++) + { + mKeyHandledByUI[i] = false; + } + for (S32 i = 0; i < CLICK_COUNT; i++) + { + mMouseLevel[i] = MOUSE_STATE_SILENT; + } + // we want the UI to never see these keys so that they can always control the avatar/camera + for(KEY k = KEY_PAD_UP; k <= KEY_PAD_DIVIDE; k++) + { + mKeysSkippedByUI.insert(k); + } +} + +LLViewerInput::~LLViewerInput() +{ + +} + +// static +bool LLViewerInput::modeFromString(const std::string& string, S32 *mode) +{ + if (string.empty()) + { + return false; + } + + std::string cmp_string = string; + LLStringUtil::toLower(cmp_string); + if (cmp_string == "first_person") + { + *mode = MODE_FIRST_PERSON; + return true; + } + else if (cmp_string == "third_person") + { + *mode = MODE_THIRD_PERSON; + return true; + } + else if (cmp_string == "edit_avatar") + { + *mode = MODE_EDIT_AVATAR; + return true; + } + else if (cmp_string == "sitting") + { + *mode = MODE_SITTING; + return true; + } + + S32 val = atoi(string.c_str()); + if (val >= 0 && val < MODE_COUNT) + { + *mode = val; + return true; + } + + return false; +} + +// static +bool LLViewerInput::mouseFromString(const std::string& string, EMouseClickType *mode) +{ + if (string == "LMB") + { + *mode = CLICK_LEFT; + return true; + } + else if (string == "Double LMB") + { + *mode = CLICK_DOUBLELEFT; + return true; + } + else if (string == "MMB") + { + *mode = CLICK_MIDDLE; + return true; + } + else if (string == "MB4") + { + *mode = CLICK_BUTTON4; + return true; + } + else if (string == "MB5") + { + *mode = CLICK_BUTTON5; + return true; + } + else + { + *mode = CLICK_NONE; + return false; + } +} + +bool LLViewerInput::handleKey(KEY translated_key, MASK translated_mask, bool repeated) +{ + // check for re-map + EKeyboardMode mode = gViewerInput.getMode(); + U32 keyidx = (translated_mask<<16) | translated_key; + key_remap_t::iterator iter = mRemapKeys[mode].find(keyidx); + if (iter != mRemapKeys[mode].end()) + { + translated_key = (iter->second) & 0xff; + translated_mask = (iter->second)>>16; + } + + // No repeats of F-keys + bool repeatable_key = (translated_key < KEY_F1 || translated_key > KEY_F12); + if (!repeatable_key && repeated) + { + return false; + } + + LL_DEBUGS("UserInput") << "keydown -" << translated_key << "-" << LL_ENDL; + // skip skipped keys + if(mKeysSkippedByUI.find(translated_key) != mKeysSkippedByUI.end()) + { + mKeyHandledByUI[translated_key] = false; + LL_INFOS("KeyboardHandling") << "Key wasn't handled by UI!" << LL_ENDL; + } + else + { + // it is sufficient to set this value once per call to handlekey + // without clearing it, as it is only used in the subsequent call to scanKey + mKeyHandledByUI[translated_key] = gViewerWindow->handleKey(translated_key, translated_mask); + // mKeyHandledByUI is not what you think ... this indicates whether the UI has handled this keypress yet (any keypress) + // NOT whether some UI shortcut wishes to handle the keypress + + } + return mKeyHandledByUI[translated_key]; +} + +bool LLViewerInput::handleKeyUp(KEY translated_key, MASK translated_mask) +{ + return gViewerWindow->handleKeyUp(translated_key, translated_mask); +} + +bool LLViewerInput::handleGlobalBindsKeyDown(KEY key, MASK mask) +{ + if (LLSetKeyBindDialog::isRecording()) + { + // handleGlobalBindsKeyDown happens before view handling, so can't + // be interupted by LLSetKeyBindDialog, check manually + return false; + } + S32 mode = getMode(); + return scanKey(mGlobalKeyBindings[mode], mGlobalKeyBindings[mode].size(), key, mask, true, false, false, false); +} + +bool LLViewerInput::handleGlobalBindsKeyUp(KEY key, MASK mask) +{ + if (LLSetKeyBindDialog::isRecording()) + { + // handleGlobalBindsKeyUp happens before view handling, so can't + // be interupted by LLSetKeyBindDialog, check manually + return false; + } + + S32 mode = getMode(); + return scanKey(mGlobalKeyBindings[mode], mGlobalKeyBindings[mode].size(), key, mask, false, true, false, false); +} + +bool LLViewerInput::handleGlobalBindsMouse(EMouseClickType clicktype, MASK mask, bool down) +{ + if (LLSetKeyBindDialog::isRecording()) + { + // handleGlobalBindsMouse happens before view handling, so can't + // be interupted by LLSetKeyBindDialog, check manually + return false; + } + + bool res = false; + S32 mode = getMode(); + if (down) + { + res = scanMouse(mGlobalMouseBindings[mode], mGlobalMouseBindings[mode].size(), clicktype, mask, MOUSE_STATE_DOWN, true); + } + else + { + res = scanMouse(mGlobalMouseBindings[mode], mGlobalMouseBindings[mode].size(), clicktype, mask, MOUSE_STATE_UP, true); + } + return res; +} + +bool LLViewerInput::bindKey(const S32 mode, const KEY key, const MASK mask, const std::string& function_name) +{ + S32 index; + typedef boost::function function_t; + function_t function = NULL; + std::string name; + + // Allow remapping of F2-F12 + if (function_name[0] == 'F') + { + int c1 = function_name[1] - '0'; + int c2 = function_name[2] ? function_name[2] - '0' : -1; + if (c1 >= 0 && c1 <= 9 && c2 >= -1 && c2 <= 9) + { + int idx = c1; + if (c2 >= 0) + idx = idx*10 + c2; + if (idx >=2 && idx <= 12) + { + U32 keyidx = ((mask<<16)|key); + (mRemapKeys[mode])[keyidx] = ((0<<16)|(KEY_F1+(idx-1))); + return true; + } + } + } + + // Not remapped, look for a function + + LLKeybindFunctionData* result = LLKeyboardActionRegistry::getValue(function_name); + if (result) + { + function = result->mFunction; + } + + if (!function) + { + LL_WARNS_ONCE() << "Can't bind key to function " << function_name << ", no function with this name found" << LL_ENDL; + return false; + } + + if (mode >= MODE_COUNT) + { + LL_ERRS() << "LLKeyboard::bindKey() - unknown mode passed" << mode << LL_ENDL; + return false; + } + + // check for duplicate first and overwrite + if (result->mIsGlobal) + { + S32 size = mGlobalKeyBindings[mode].size(); + for (index = 0; index < size; index++) + { + if (key == mGlobalKeyBindings[mode][index].mKey && mask == mGlobalKeyBindings[mode][index].mMask) + { + mGlobalKeyBindings[mode][index].mFunction = function; + return true; + } + } + } + else + { + S32 size = mKeyBindings[mode].size(); + for (index = 0; index < size; index++) + { + if (key == mKeyBindings[mode][index].mKey && mask == mKeyBindings[mode][index].mMask) + { + mKeyBindings[mode][index].mFunction = function; + return true; + } + } + } + + LLKeyboardBinding bind; + bind.mKey = key; + bind.mMask = mask; + bind.mFunction = function; + bind.mFunctionName = function_name; + + if (result->mIsGlobal) + { + mGlobalKeyBindings[mode].push_back(bind); + } + else + { + mKeyBindings[mode].push_back(bind); + } + + return true; +} + +bool LLViewerInput::bindMouse(const S32 mode, const EMouseClickType mouse, const MASK mask, const std::string& function_name) +{ + S32 index; + typedef boost::function function_t; + function_t function = NULL; + + if (mouse == CLICK_LEFT + && mask == MASK_NONE + && function_name == script_mouse_handler_name) + { + // Special case + // Left click has script overrides and by default + // is handled via agent_control_lbutton as last option + // In case of mouselook and present overrides it has highest + // priority even over UI and is handled in LLToolCompGun::handleMouseDown + // so just mark it as having default handler + mLMouseDefaultHandling[mode] = true; + return true; + } + + LLKeybindFunctionData* result = LLKeyboardActionRegistry::getValue(function_name); + if (result) + { + function = result->mFunction; + } + + if (!function) + { + LL_WARNS_ONCE() << "Can't bind mouse key to function " << function_name << ", no function with this name found" << LL_ENDL; + return false; + } + + if (mode >= MODE_COUNT) + { + LL_ERRS() << "LLKeyboard::bindKey() - unknown mode passed" << mode << LL_ENDL; + return false; + } + + // check for duplicate first and overwrite + if (result->mIsGlobal) + { + S32 size = mGlobalMouseBindings[mode].size(); + for (index = 0; index < size; index++) + { + if (mouse == mGlobalMouseBindings[mode][index].mMouse && mask == mGlobalMouseBindings[mode][index].mMask) + { + mGlobalMouseBindings[mode][index].mFunction = function; + return true; + } + } + } + else + { + S32 size = mMouseBindings[mode].size(); + for (index = 0; index < size; index++) + { + if (mouse == mMouseBindings[mode][index].mMouse && mask == mMouseBindings[mode][index].mMask) + { + mMouseBindings[mode][index].mFunction = function; + return true; + } + } + } + + LLMouseBinding bind; + bind.mMouse = mouse; + bind.mMask = mask; + bind.mFunction = function; + bind.mFunctionName = function_name; + + if (result->mIsGlobal) + { + mGlobalMouseBindings[mode].push_back(bind); + } + else + { + mMouseBindings[mode].push_back(bind); + } + + return true; +} + +LLViewerInput::KeyBinding::KeyBinding() +: key("key"), + mouse("mouse"), + mask("mask"), + command("command") +{} + +LLViewerInput::KeyMode::KeyMode() +: bindings("binding") +{} + +LLViewerInput::Keys::Keys() +: first_person("first_person"), + third_person("third_person"), + sitting("sitting"), + edit_avatar("edit_avatar"), + xml_version("xml_version", 0) +{} + +void LLViewerInput::resetBindings() +{ + for (S32 i = 0; i < MODE_COUNT; i++) + { + mGlobalKeyBindings[i].clear(); + mGlobalMouseBindings[i].clear(); + mKeyBindings[i].clear(); + mMouseBindings[i].clear(); + mLMouseDefaultHandling[i] = false; + } +} + +S32 LLViewerInput::loadBindingsXML(const std::string& filename) +{ + resetBindings(); + + S32 binding_count = 0; + Keys keys; + LLSimpleXUIParser parser; + + if (parser.readXUI(filename, keys) + && keys.validateBlock()) + { + binding_count += loadBindingMode(keys.first_person, MODE_FIRST_PERSON); + binding_count += loadBindingMode(keys.third_person, MODE_THIRD_PERSON); + binding_count += loadBindingMode(keys.sitting, MODE_SITTING); + binding_count += loadBindingMode(keys.edit_avatar, MODE_EDIT_AVATAR); + + // verify version + if (keys.xml_version < 1) + { + // updating from a version that was not aware of LMouse bindings + for (S32 i = 0; i < MODE_COUNT; i++) + { + mLMouseDefaultHandling[i] = true; + } + + // fix missing values + KeyBinding mouse_binding; + mouse_binding.key = ""; + mouse_binding.mask = "NONE"; + mouse_binding.mouse = "LMB"; + mouse_binding.command = script_mouse_handler_name; + + if (keys.third_person.isProvided()) + { + keys.third_person.bindings.add(mouse_binding); + } + + if (keys.first_person.isProvided()) + { + keys.first_person.bindings.add(mouse_binding); + } + + if (keys.sitting.isProvided()) + { + keys.sitting.bindings.add(mouse_binding); + } + + if (keys.edit_avatar.isProvided()) + { + keys.edit_avatar.bindings.add(mouse_binding); + } + + // fix version + keys.xml_version.set(keybindings_xml_version, true); + + // Write the resulting XML to file + LLXMLNodePtr output_node = new LLXMLNode("keys", false); + LLXUIParser write_parser; + write_parser.writeXUI(output_node, keys); + + if (!output_node->isNull()) + { + // file in app_settings is supposed to be up to date + // this is only for the file from user_settings + LL_INFOS("ViewerInput") << "Updating file " << filename << " to a newer version" << LL_ENDL; + LLFILE *fp = LLFile::fopen(filename, "w"); + if (fp != NULL) + { + LLXMLNode::writeHeaderToFile(fp); + output_node->writeToFile(fp); + fclose(fp); + } + } + } + } + return binding_count; +} + +S32 count_masks(const MASK &mask) +{ + S32 res = 0; + if (mask & MASK_CONTROL) + { + res++; + } + if (mask & MASK_SHIFT) + { + res++; + } + if (mask & MASK_ALT) + { + res++; + } + return res; +} + +bool compare_key_by_mask(LLKeyboardBinding i1, LLKeyboardBinding i2) +{ + return (count_masks(i1.mMask) > count_masks(i2.mMask)); +} + +bool compare_mouse_by_mask(LLMouseBinding i1, LLMouseBinding i2) +{ + return (count_masks(i1.mMask) > count_masks(i2.mMask)); +} + +S32 LLViewerInput::loadBindingMode(const LLViewerInput::KeyMode& keymode, S32 mode) +{ + S32 binding_count = 0; + for (LLInitParam::ParamIterator::const_iterator it = keymode.bindings.begin(), + end_it = keymode.bindings.end(); + it != end_it; + ++it) + { + bool processed = false; + std::string key_str = it->key.getValue(); + if (!key_str.empty() && key_str != "NONE") + { + KEY key; + LLKeyboard::keyFromString(key_str, &key); + if (key != KEY_NONE) + { + MASK mask; + LLKeyboard::maskFromString(it->mask, &mask); + bindKey(mode, key, mask, it->command); + processed = true; + } + else + { + LL_WARNS_ONCE() << "There might be issues in keybindings' file" << LL_ENDL; + } + } + if (!processed && it->mouse.isProvided() && !it->mouse.getValue().empty()) + { + EMouseClickType mouse; + mouseFromString(it->mouse.getValue(), &mouse); + if (mouse != CLICK_NONE) + { + MASK mask; + LLKeyboard::maskFromString(it->mask, &mask); + bindMouse(mode, mouse, mask, it->command); + processed = true; + } + else + { + LL_WARNS_ONCE() << "There might be issues in keybindings' file" << LL_ENDL; + } + } + if (processed) + { + // total + binding_count++; + } + } + + // sort lists by mask (so that Shift+W is executed before W, if both are assigned, but if Shift+W is not assigned W should be executed) + std::sort(mKeyBindings[mode].begin(), mKeyBindings[mode].end(), compare_key_by_mask); + std::sort(mMouseBindings[mode].begin(), mMouseBindings[mode].end(), compare_mouse_by_mask); + + return binding_count; +} + +EKeyboardMode LLViewerInput::getMode() const +{ + if ( gAgentCamera.cameraMouselook() ) + { + return MODE_FIRST_PERSON; + } + else if ( gMorphView && gMorphView->getVisible()) + { + return MODE_EDIT_AVATAR; + } + else if (isAgentAvatarValid() && gAgentAvatarp->isSitting()) + { + return MODE_SITTING; + } + else + { + return MODE_THIRD_PERSON; + } +} + +bool LLViewerInput::scanKey(const std::vector &binding, + S32 binding_count, + KEY key, + MASK mask, + bool key_down, + bool key_up, + bool key_level, + bool repeat) const +{ + for (S32 i = 0; i < binding_count; i++) + { + if (binding[i].mKey == key) + { + if ((binding[i].mMask & mask) == binding[i].mMask) + { + bool res = false; + if (key_down && !repeat) + { + // ...key went down this frame, call function + res = binding[i].mFunction( KEYSTATE_DOWN ); + return true; + } + else if (key_up) + { + // ...key went down this frame, call function + res = binding[i].mFunction( KEYSTATE_UP ); + } + else if (key_level) + { + // ...key held down from previous frame + // Not windows, just call the function. + res = binding[i].mFunction( KEYSTATE_LEVEL ); + }//if + // Key+Mask combinations are supposed to be unique, so we won't find anything else + return res; + }//if + }//if + }//for + return false; +} + +// Called from scanKeyboard. +bool LLViewerInput::scanKey(KEY key, bool key_down, bool key_up, bool key_level) const +{ + if (LLApp::isExiting()) + { + return false; + } + + S32 mode = getMode(); + // Consider keyboard scanning as NOT mouse event. JC + MASK mask = gKeyboard->currentMask(false); + + if (mKeyHandledByUI[key]) + { + return false; + } + + // don't process key down on repeated keys + bool repeat = gKeyboard->getKeyRepeated(key); + + bool res = scanKey(mKeyBindings[mode], mKeyBindings[mode].size(), key, mask, key_down, key_up, key_level, repeat); + + return res; +} + +bool LLViewerInput::handleMouse(LLWindow *window_impl, LLCoordGL pos, MASK mask, EMouseClickType clicktype, bool down) +{ + bool is_toolmgr_action = false; + bool handled = gViewerWindow->handleAnyMouseClick(window_impl, pos, mask, clicktype, down, is_toolmgr_action); + + if (clicktype != CLICK_NONE) + { + // Special case + // If UI doesn't handle double click, LMB click is issued, so supres LMB 'down' when doubleclick is set + // handle !down as if we are handling doubleclick + + bool double_click_sp = (clicktype == CLICK_LEFT + && (mMouseLevel[CLICK_DOUBLELEFT] != MOUSE_STATE_SILENT) + && mMouseLevel[CLICK_LEFT] == MOUSE_STATE_SILENT); + if (double_click_sp && !down) + { + // Process doubleclick instead + clicktype = CLICK_DOUBLELEFT; + } + + // If the first LMB click is handled by the menu, skip the following double click + static bool skip_double_click = false; + if (clicktype == CLICK_LEFT && down) + { + skip_double_click = is_toolmgr_action ? false : handled; + } + + if (double_click_sp && down) + { + // Consume click. + // Due to handling, double click that is not handled will be immediately followed by LMB click + } + else if (clicktype == CLICK_DOUBLELEFT && skip_double_click) + { + handled = true; + } + // If UI handled 'down', it should handle 'up' as well + // If we handle 'down' not by UI, then we should handle 'up'/'level' regardless of UI + else if (handled) + { + // UI handled new 'down' so iterupt whatever state we were in. + if (mMouseLevel[clicktype] != MOUSE_STATE_SILENT) + { + if (mMouseLevel[clicktype] == MOUSE_STATE_DOWN) + { + mMouseLevel[clicktype] = MOUSE_STATE_CLICK; + } + else + { + mMouseLevel[clicktype] = MOUSE_STATE_UP; + } + } + } + else if (down) + { + if (mMouseLevel[clicktype] == MOUSE_STATE_DOWN) + { + // this is repeated hit (mouse does not repeat event until release) + // for now treat rapid clicking like mouse being held + mMouseLevel[clicktype] = MOUSE_STATE_LEVEL; + } + else + { + mMouseLevel[clicktype] = MOUSE_STATE_DOWN; + } + } + else if (mMouseLevel[clicktype] != MOUSE_STATE_SILENT) + { + // Released mouse key + if (mMouseLevel[clicktype] == MOUSE_STATE_DOWN) + { + mMouseLevel[clicktype] = MOUSE_STATE_CLICK; + } + else + { + mMouseLevel[clicktype] = MOUSE_STATE_UP; + } + } + } + + return handled; +} + +bool LLViewerInput::scanMouse( + const std::vector &binding, + S32 binding_count, + EMouseClickType mouse, + MASK mask, + EMouseState state, + bool ignore_additional_masks +) const +{ + for (S32 i = 0; i < binding_count; i++) + { + if (binding[i].mMouse == mouse && (ignore_additional_masks ? (binding[i].mMask & mask) == binding[i].mMask : binding[i].mMask == mask)) + { + bool res = false; + switch (state) + { + case MOUSE_STATE_DOWN: + res = binding[i].mFunction(KEYSTATE_DOWN); + break; + case MOUSE_STATE_CLICK: + // Button went down and up in scope of single frame + // might not work best with some functions, + // but some function need specific states specifically + res = binding[i].mFunction(KEYSTATE_DOWN); + res |= binding[i].mFunction(KEYSTATE_UP); + break; + case MOUSE_STATE_LEVEL: + res = binding[i].mFunction(KEYSTATE_LEVEL); + break; + case MOUSE_STATE_UP: + res = binding[i].mFunction(KEYSTATE_UP); + break; + default: + break; + } + // Key+Mask combinations are supposed to be unique, no need to continue + return res; + } + } + return false; +} + +// todo: this recods key, scanMouse() triggers functions with EKeystate +bool LLViewerInput::scanMouse(EMouseClickType click, EMouseState state) const +{ + bool res = false; + S32 mode = getMode(); + MASK mask = gKeyboard->currentMask(true); + res = scanMouse(mMouseBindings[mode], mMouseBindings[mode].size(), click, mask, state, false); + + // No user defined actions found or those actions can't handle the key/button, + // so handle CONTROL_LBUTTON if nessesary. + // + // Default handling for MODE_FIRST_PERSON is in LLToolCompGun::handleMouseDown, + // and sends AGENT_CONTROL_ML_LBUTTON_DOWN, but it only applies if ML controls + // are leftButtonGrabbed(), send a normal click otherwise. + + if (!res + && mLMouseDefaultHandling[mode] + && (mode != MODE_FIRST_PERSON || !gAgent.leftButtonGrabbed()) + && (click == CLICK_LEFT || click == CLICK_DOUBLELEFT) + ) + { + switch (state) + { + case MOUSE_STATE_DOWN: + agent_control_lbutton_handle(KEYSTATE_DOWN); + res = true; + break; + case MOUSE_STATE_CLICK: + // might not work best with some functions, + // but some function need specific states too specifically + agent_control_lbutton_handle(KEYSTATE_DOWN); + agent_control_lbutton_handle(KEYSTATE_UP); + res = true; + break; + case MOUSE_STATE_UP: + agent_control_lbutton_handle(KEYSTATE_UP); + res = true; + break; + default: + break; + } + } + return res; +} + +void LLViewerInput::scanMouse() +{ + for (S32 i = 0; i < CLICK_COUNT; i++) + { + if (mMouseLevel[i] != MOUSE_STATE_SILENT) + { + scanMouse((EMouseClickType)i, mMouseLevel[i]); + if (mMouseLevel[i] == MOUSE_STATE_DOWN) + { + // mouse doesn't support 'continued' state, so after handling, switch to LEVEL + mMouseLevel[i] = MOUSE_STATE_LEVEL; + } + else if (mMouseLevel[i] == MOUSE_STATE_UP || mMouseLevel[i] == MOUSE_STATE_CLICK) + { + mMouseLevel[i] = MOUSE_STATE_SILENT; + } + } + } +} + +bool LLViewerInput::isMouseBindUsed(const EMouseClickType mouse, const MASK mask, const S32 mode) const +{ + S32 size = mMouseBindings[mode].size(); + for (S32 index = 0; index < size; index++) + { + if (mouse == mMouseBindings[mode][index].mMouse && mask == mMouseBindings[mode][index].mMask) + return true; + } + size = mGlobalMouseBindings[mode].size(); + for (S32 index = 0; index < size; index++) + { + if (mouse == mGlobalMouseBindings[mode][index].mMouse && mask == mGlobalMouseBindings[mode][index].mMask) + return true; + } + return false; +} + +std::string LLViewerInput::getKeyBindingAsString(const std::string& mode, const std::string& control) const +{ + S32 keyboard_mode; + if (!modeFromString(mode, &keyboard_mode)) + { + keyboard_mode = getMode(); + } + + std::string res; + bool needs_separator = false; + + // keybindings are sorted from having most mask to no mask (from restrictive to less restrictive), + // but it's visually better to present this data in reverse + std::vector::const_reverse_iterator iter_key = mKeyBindings[keyboard_mode].rbegin(); + while (iter_key != mKeyBindings[keyboard_mode].rend()) + { + if (iter_key->mFunctionName == control) + { + if (needs_separator) + { + res.append(" | "); + } + res.append(LLKeyboard::stringFromAccelerator(iter_key->mMask, iter_key->mKey)); + needs_separator = true; + } + iter_key++; + } + + std::vector::const_reverse_iterator iter_mouse = mMouseBindings[keyboard_mode].rbegin(); + while (iter_mouse != mMouseBindings[keyboard_mode].rend()) + { + if (iter_mouse->mFunctionName == control) + { + if (needs_separator) + { + res.append(" | "); + } + res.append(LLKeyboard::stringFromAccelerator(iter_mouse->mMask, iter_mouse->mMouse)); + needs_separator = true; + } + iter_mouse++; + } + + return res; +} diff --git a/indra/newview/llviewerinput.h b/indra/newview/llviewerinput.h index abd50fdbc6..50b2c4ab9f 100644 --- a/indra/newview/llviewerinput.h +++ b/indra/newview/llviewerinput.h @@ -1,198 +1,198 @@ -/** - * @file llviewerinput.h - * @brief LLViewerInput class header file - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVIEWERINPUT_H -#define LL_LLVIEWERINPUT_H - -#include "llkeyboard.h" // For EKeystate - -const S32 MAX_KEY_BINDINGS = 128; // was 60 -const S32 keybindings_xml_version = 1; -const std::string script_mouse_handler_name = "script_trigger_lbutton"; - -class LLWindow; - -class LLNamedFunction -{ -public: - LLNamedFunction() : mFunction(NULL) { }; - ~LLNamedFunction() { }; - - std::string mName; - LLKeyFunc mFunction; -}; - -class LLKeyboardBinding -{ -public: - KEY mKey; - MASK mMask; - - LLKeyFunc mFunction; - std::string mFunctionName; -}; - -class LLMouseBinding -{ -public: - EMouseClickType mMouse; - MASK mMask; - - LLKeyFunc mFunction; - std::string mFunctionName; -}; - - -typedef enum e_keyboard_mode -{ - MODE_FIRST_PERSON, - MODE_THIRD_PERSON, - MODE_EDIT_AVATAR, - MODE_SITTING, - MODE_COUNT -} EKeyboardMode; - -class LLViewerInput : public LLKeyBindingToStringHandler -{ -public: - struct KeyBinding : public LLInitParam::Block - { - Mandatory key, - mask, - command; - Optional mouse; // Note, not mandatory for the sake of backward campatibility with keys.xml - - KeyBinding(); - }; - - struct KeyMode : public LLInitParam::Block - { - Multiple bindings; - - KeyMode(); - }; - - struct Keys : public LLInitParam::Block - { - Optional first_person, - third_person, - sitting, - edit_avatar; - Optional xml_version; // 'xml', because 'version' appears to be reserved - Keys(); - }; - - LLViewerInput(); - virtual ~LLViewerInput(); - - bool handleKey(KEY key, MASK mask, bool repeated); - bool handleKeyUp(KEY key, MASK mask); - - // Handle 'global' keybindings that do not consume event, - // yet need to be processed early - // Example: we want voice to toggle even if some floater is focused - bool handleGlobalBindsKeyDown(KEY key, MASK mask); - bool handleGlobalBindsKeyUp(KEY key, MASK mask); - bool handleGlobalBindsMouse(EMouseClickType clicktype, MASK mask, bool down); - - S32 loadBindingsXML(const std::string& filename); // returns number bound, 0 on error - EKeyboardMode getMode() const; - - static bool modeFromString(const std::string& string, S32 *mode); // False on failure - static bool mouseFromString(const std::string& string, EMouseClickType *mode);// False on failure - - bool scanKey(KEY key, - bool key_down, - bool key_up, - bool key_level) const; - - // handleMouse() records state, scanMouse() goes through states, scanMouse(click) processes individual saved states after UI is done with them - bool handleMouse(LLWindow *window_impl, LLCoordGL pos, MASK mask, EMouseClickType clicktype, bool down); - void scanMouse(); - - bool isMouseBindUsed(const EMouseClickType mouse, const MASK mask, const S32 mode) const; - bool isLMouseHandlingDefault(const S32 mode) const { return mLMouseDefaultHandling[mode]; } - - // inherited from LLKeyBindingToStringHandler - virtual std::string getKeyBindingAsString(const std::string& mode, const std::string& control) const override; - -private: - bool scanKey(const std::vector &binding, - S32 binding_count, - KEY key, - MASK mask, - bool key_down, - bool key_up, - bool key_level, - bool repeat) const; - - enum EMouseState - { - MOUSE_STATE_DOWN, // key down this frame - MOUSE_STATE_CLICK, // key went up and down in scope of same frame - MOUSE_STATE_LEVEL, // clicked again fast, or never released - MOUSE_STATE_UP, // went up this frame - MOUSE_STATE_SILENT // notified about 'up', do not notify again - }; - bool scanMouse(EMouseClickType click, EMouseState state) const; - bool scanMouse(const std::vector &binding, - S32 binding_count, - EMouseClickType mouse, - MASK mask, - EMouseState state, - bool ignore_additional_masks) const; - - S32 loadBindingMode(const LLViewerInput::KeyMode& keymode, S32 mode); - bool bindKey(const S32 mode, const KEY key, const MASK mask, const std::string& function_name); - bool bindMouse(const S32 mode, const EMouseClickType mouse, const MASK mask, const std::string& function_name); - void resetBindings(); - - // Hold all the ugly stuff torn out to make LLKeyboard non-viewer-specific here - - // TODO: at some point it is better to remake this, especially keyaboard part - // would be much better to send to functions actual state of the button than - // to send what we think function wants based on collection of bools (mKeyRepeated, mKeyLevel, mKeyDown) - std::vector mKeyBindings[MODE_COUNT]; - std::vector mMouseBindings[MODE_COUNT]; - bool mLMouseDefaultHandling[MODE_COUNT]; // Due to having special priority - - // keybindings that do not consume event and are handled earlier, before floaters - std::vector mGlobalKeyBindings[MODE_COUNT]; - std::vector mGlobalMouseBindings[MODE_COUNT]; - - typedef std::map key_remap_t; - key_remap_t mRemapKeys[MODE_COUNT]; - std::set mKeysSkippedByUI; - bool mKeyHandledByUI[KEY_COUNT]; // key processed successfully by UI - - // This is indentical to what llkeyboard does (mKeyRepeated, mKeyLevel, mKeyDown e t c), - // just instead of remembering individually as bools, we record state as enum - EMouseState mMouseLevel[CLICK_COUNT]; // records of key state -}; - -extern LLViewerInput gViewerInput; - -#endif // LL_LLVIEWERINPUT_H +/** + * @file llviewerinput.h + * @brief LLViewerInput class header file + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVIEWERINPUT_H +#define LL_LLVIEWERINPUT_H + +#include "llkeyboard.h" // For EKeystate + +const S32 MAX_KEY_BINDINGS = 128; // was 60 +const S32 keybindings_xml_version = 1; +const std::string script_mouse_handler_name = "script_trigger_lbutton"; + +class LLWindow; + +class LLNamedFunction +{ +public: + LLNamedFunction() : mFunction(NULL) { }; + ~LLNamedFunction() { }; + + std::string mName; + LLKeyFunc mFunction; +}; + +class LLKeyboardBinding +{ +public: + KEY mKey; + MASK mMask; + + LLKeyFunc mFunction; + std::string mFunctionName; +}; + +class LLMouseBinding +{ +public: + EMouseClickType mMouse; + MASK mMask; + + LLKeyFunc mFunction; + std::string mFunctionName; +}; + + +typedef enum e_keyboard_mode +{ + MODE_FIRST_PERSON, + MODE_THIRD_PERSON, + MODE_EDIT_AVATAR, + MODE_SITTING, + MODE_COUNT +} EKeyboardMode; + +class LLViewerInput : public LLKeyBindingToStringHandler +{ +public: + struct KeyBinding : public LLInitParam::Block + { + Mandatory key, + mask, + command; + Optional mouse; // Note, not mandatory for the sake of backward campatibility with keys.xml + + KeyBinding(); + }; + + struct KeyMode : public LLInitParam::Block + { + Multiple bindings; + + KeyMode(); + }; + + struct Keys : public LLInitParam::Block + { + Optional first_person, + third_person, + sitting, + edit_avatar; + Optional xml_version; // 'xml', because 'version' appears to be reserved + Keys(); + }; + + LLViewerInput(); + virtual ~LLViewerInput(); + + bool handleKey(KEY key, MASK mask, bool repeated); + bool handleKeyUp(KEY key, MASK mask); + + // Handle 'global' keybindings that do not consume event, + // yet need to be processed early + // Example: we want voice to toggle even if some floater is focused + bool handleGlobalBindsKeyDown(KEY key, MASK mask); + bool handleGlobalBindsKeyUp(KEY key, MASK mask); + bool handleGlobalBindsMouse(EMouseClickType clicktype, MASK mask, bool down); + + S32 loadBindingsXML(const std::string& filename); // returns number bound, 0 on error + EKeyboardMode getMode() const; + + static bool modeFromString(const std::string& string, S32 *mode); // False on failure + static bool mouseFromString(const std::string& string, EMouseClickType *mode);// False on failure + + bool scanKey(KEY key, + bool key_down, + bool key_up, + bool key_level) const; + + // handleMouse() records state, scanMouse() goes through states, scanMouse(click) processes individual saved states after UI is done with them + bool handleMouse(LLWindow *window_impl, LLCoordGL pos, MASK mask, EMouseClickType clicktype, bool down); + void scanMouse(); + + bool isMouseBindUsed(const EMouseClickType mouse, const MASK mask, const S32 mode) const; + bool isLMouseHandlingDefault(const S32 mode) const { return mLMouseDefaultHandling[mode]; } + + // inherited from LLKeyBindingToStringHandler + virtual std::string getKeyBindingAsString(const std::string& mode, const std::string& control) const override; + +private: + bool scanKey(const std::vector &binding, + S32 binding_count, + KEY key, + MASK mask, + bool key_down, + bool key_up, + bool key_level, + bool repeat) const; + + enum EMouseState + { + MOUSE_STATE_DOWN, // key down this frame + MOUSE_STATE_CLICK, // key went up and down in scope of same frame + MOUSE_STATE_LEVEL, // clicked again fast, or never released + MOUSE_STATE_UP, // went up this frame + MOUSE_STATE_SILENT // notified about 'up', do not notify again + }; + bool scanMouse(EMouseClickType click, EMouseState state) const; + bool scanMouse(const std::vector &binding, + S32 binding_count, + EMouseClickType mouse, + MASK mask, + EMouseState state, + bool ignore_additional_masks) const; + + S32 loadBindingMode(const LLViewerInput::KeyMode& keymode, S32 mode); + bool bindKey(const S32 mode, const KEY key, const MASK mask, const std::string& function_name); + bool bindMouse(const S32 mode, const EMouseClickType mouse, const MASK mask, const std::string& function_name); + void resetBindings(); + + // Hold all the ugly stuff torn out to make LLKeyboard non-viewer-specific here + + // TODO: at some point it is better to remake this, especially keyaboard part + // would be much better to send to functions actual state of the button than + // to send what we think function wants based on collection of bools (mKeyRepeated, mKeyLevel, mKeyDown) + std::vector mKeyBindings[MODE_COUNT]; + std::vector mMouseBindings[MODE_COUNT]; + bool mLMouseDefaultHandling[MODE_COUNT]; // Due to having special priority + + // keybindings that do not consume event and are handled earlier, before floaters + std::vector mGlobalKeyBindings[MODE_COUNT]; + std::vector mGlobalMouseBindings[MODE_COUNT]; + + typedef std::map key_remap_t; + key_remap_t mRemapKeys[MODE_COUNT]; + std::set mKeysSkippedByUI; + bool mKeyHandledByUI[KEY_COUNT]; // key processed successfully by UI + + // This is indentical to what llkeyboard does (mKeyRepeated, mKeyLevel, mKeyDown e t c), + // just instead of remembering individually as bools, we record state as enum + EMouseState mMouseLevel[CLICK_COUNT]; // records of key state +}; + +extern LLViewerInput gViewerInput; + +#endif // LL_LLVIEWERINPUT_H diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index 33d31bf772..88f88d7436 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -1,2332 +1,2332 @@ -/** - * @file llviewerinventory.cpp - * @brief Implementation of the viewer side inventory objects. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2014, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llviewerinventory.h" - -#include "llnotificationsutil.h" -#include "llsdserialize.h" -#include "message.h" - -#include "llaisapi.h" -#include "llagent.h" -#include "llagentcamera.h" -#include "llagentwearables.h" -#include "llfloatersidepanelcontainer.h" -#include "llviewerfoldertype.h" -#include "llfloatersidepanelcontainer.h" -#include "llfolderview.h" -#include "llviewercontrol.h" -#include "llconsole.h" -#include "llinventorydefines.h" -#include "llinventoryfunctions.h" -#include "llinventorymodel.h" -#include "llinventorymodelbackgroundfetch.h" -#include "llgesturemgr.h" - -#include "llinventorybridge.h" -#include "llinventorypanel.h" -#include "lllandmarkactions.h" - -#include "llviewerassettype.h" -#include "llviewerregion.h" -#include "llviewerobjectlist.h" -#include "llpreviewgesture.h" -#include "llviewerwindow.h" -#include "lltrans.h" -#include "llappearancemgr.h" -#include "llcommandhandler.h" -#include "llviewermessage.h" -#include "llpanelmaininventory.h" -#include "llsidepanelappearance.h" -#include "llsidepanelinventory.h" -#include "llavatarnamecache.h" -#include "llavataractions.h" -#include "lllogininstance.h" -#include "llfavoritesbar.h" -#include "llfloaterperms.h" -#include "llclipboard.h" -#include "llhttpretrypolicy.h" -#include "llsettingsvo.h" - -// do-nothing ops for use in callbacks. -void no_op_inventory_func(const LLUUID&) {} -void no_op_llsd_func(const LLSD&) {} -void no_op() {} - -static const char * const LOG_INV("Inventory"); -static const char * const LOG_LOCAL("InventoryLocalize"); -static const char * const LOG_NOTECARD("copy_inventory_from_notecard"); - -static const std::string INV_OWNER_ID("owner_id"); -static const std::string INV_VERSION("version"); - -#if 1 -// *TODO$: LLInventoryCallback should be deprecated to conform to the new boost::bind/coroutine model. -// temp code in transition -void doInventoryCb(LLPointer cb, LLUUID id) -{ - if (cb.notNull()) - cb->fire(id); -} -#endif - -///---------------------------------------------------------------------------- -/// Helper class to store special inventory item names and their localized values. -///---------------------------------------------------------------------------- -class LLLocalizedInventoryItemsDictionary : public LLSingleton -{ - LLSINGLETON(LLLocalizedInventoryItemsDictionary); -public: - std::map mInventoryItemsDict; - - /** - * Finds passed name in dictionary and replaces it with found localized value. - * - * @param object_name - string to be localized. - * @return true if passed name was found and localized, false otherwise. - */ - bool localizeInventoryObjectName(std::string& object_name) - { - LL_DEBUGS(LOG_LOCAL) << "Searching for localization: " << object_name << LL_ENDL; - - std::map::const_iterator dictionary_iter = mInventoryItemsDict.find(object_name); - - bool found = dictionary_iter != mInventoryItemsDict.end(); - if(found) - { - object_name = dictionary_iter->second; - LL_DEBUGS(LOG_LOCAL) << "Found, new name is: " << object_name << LL_ENDL; - } - return found; - } -}; - -LLLocalizedInventoryItemsDictionary::LLLocalizedInventoryItemsDictionary() -{ - mInventoryItemsDict["New Shape"] = LLTrans::getString("New Shape"); - mInventoryItemsDict["New Skin"] = LLTrans::getString("New Skin"); - mInventoryItemsDict["New Hair"] = LLTrans::getString("New Hair"); - mInventoryItemsDict["New Eyes"] = LLTrans::getString("New Eyes"); - mInventoryItemsDict["New Shirt"] = LLTrans::getString("New Shirt"); - mInventoryItemsDict["New Pants"] = LLTrans::getString("New Pants"); - mInventoryItemsDict["New Shoes"] = LLTrans::getString("New Shoes"); - mInventoryItemsDict["New Socks"] = LLTrans::getString("New Socks"); - mInventoryItemsDict["New Jacket"] = LLTrans::getString("New Jacket"); - mInventoryItemsDict["New Gloves"] = LLTrans::getString("New Gloves"); - mInventoryItemsDict["New Undershirt"] = LLTrans::getString("New Undershirt"); - mInventoryItemsDict["New Underpants"] = LLTrans::getString("New Underpants"); - mInventoryItemsDict["New Skirt"] = LLTrans::getString("New Skirt"); - mInventoryItemsDict["New Alpha"] = LLTrans::getString("New Alpha"); - mInventoryItemsDict["New Tattoo"] = LLTrans::getString("New Tattoo"); - mInventoryItemsDict["New Universal"] = LLTrans::getString("New Universal"); - mInventoryItemsDict["New Physics"] = LLTrans::getString("New Physics"); - mInventoryItemsDict["Invalid Wearable"] = LLTrans::getString("Invalid Wearable"); - - mInventoryItemsDict["New Gesture"] = LLTrans::getString("New Gesture"); - mInventoryItemsDict["New Material"] = LLTrans::getString("New Material"); - mInventoryItemsDict["New Script"] = LLTrans::getString("New Script"); - mInventoryItemsDict["New Folder"] = LLTrans::getString("New Folder"); - mInventoryItemsDict["New Note"] = LLTrans::getString("New Note"); - mInventoryItemsDict["Contents"] = LLTrans::getString("Contents"); - - mInventoryItemsDict["Gesture"] = LLTrans::getString("Gesture"); - mInventoryItemsDict["Male Gestures"] = LLTrans::getString("Male Gestures"); - mInventoryItemsDict["Female Gestures"] = LLTrans::getString("Female Gestures"); - mInventoryItemsDict["Other Gestures"] = LLTrans::getString("Other Gestures"); - mInventoryItemsDict["Speech Gestures"] = LLTrans::getString("Speech Gestures"); - mInventoryItemsDict["Common Gestures"] = LLTrans::getString("Common Gestures"); - - //predefined gestures - - //male - mInventoryItemsDict["Male - Excuse me"] = LLTrans::getString("Male - Excuse me"); - mInventoryItemsDict["Male - Get lost"] = LLTrans::getString("Male - Get lost"); // double space after Male. EXT-8319 - mInventoryItemsDict["Male - Blow kiss"] = LLTrans::getString("Male - Blow kiss"); - mInventoryItemsDict["Male - Boo"] = LLTrans::getString("Male - Boo"); - mInventoryItemsDict["Male - Bored"] = LLTrans::getString("Male - Bored"); - mInventoryItemsDict["Male - Hey"] = LLTrans::getString("Male - Hey"); - mInventoryItemsDict["Male - Laugh"] = LLTrans::getString("Male - Laugh"); - mInventoryItemsDict["Male - Repulsed"] = LLTrans::getString("Male - Repulsed"); - mInventoryItemsDict["Male - Shrug"] = LLTrans::getString("Male - Shrug"); - mInventoryItemsDict["Male - Stick tougue out"] = LLTrans::getString("Male - Stick tougue out"); - mInventoryItemsDict["Male - Wow"] = LLTrans::getString("Male - Wow"); - - //female - mInventoryItemsDict["Female - Chuckle"] = LLTrans::getString("Female - Chuckle"); - mInventoryItemsDict["Female - Cry"] = LLTrans::getString("Female - Cry"); - mInventoryItemsDict["Female - Embarrassed"] = LLTrans::getString("Female - Embarrassed"); - mInventoryItemsDict["Female - Excuse me"] = LLTrans::getString("Female - Excuse me"); - mInventoryItemsDict["Female - Get lost"] = LLTrans::getString("Female - Get lost"); // double space after Female. EXT-8319 - mInventoryItemsDict["Female - Blow kiss"] = LLTrans::getString("Female - Blow kiss"); - mInventoryItemsDict["Female - Boo"] = LLTrans::getString("Female - Boo"); - mInventoryItemsDict["Female - Bored"] = LLTrans::getString("Female - Bored"); - mInventoryItemsDict["Female - Hey"] = LLTrans::getString("Female - Hey"); - mInventoryItemsDict["Female - Hey baby"] = LLTrans::getString("Female - Hey baby"); - mInventoryItemsDict["Female - Laugh"] = LLTrans::getString("Female - Laugh"); - mInventoryItemsDict["Female - Looking good"] = LLTrans::getString("Female - Looking good"); - mInventoryItemsDict["Female - Over here"] = LLTrans::getString("Female - Over here"); - mInventoryItemsDict["Female - Please"] = LLTrans::getString("Female - Please"); - mInventoryItemsDict["Female - Repulsed"] = LLTrans::getString("Female - Repulsed"); - mInventoryItemsDict["Female - Shrug"] = LLTrans::getString("Female - Shrug"); - mInventoryItemsDict["Female - Stick tougue out"]= LLTrans::getString("Female - Stick tougue out"); - mInventoryItemsDict["Female - Wow"] = LLTrans::getString("Female - Wow"); - - //common - mInventoryItemsDict["/bow"] = LLTrans::getString("/bow"); - mInventoryItemsDict["/clap"] = LLTrans::getString("/clap"); - mInventoryItemsDict["/count"] = LLTrans::getString("/count"); - mInventoryItemsDict["/extinguish"] = LLTrans::getString("/extinguish"); - mInventoryItemsDict["/kmb"] = LLTrans::getString("/kmb"); - mInventoryItemsDict["/muscle"] = LLTrans::getString("/muscle"); - mInventoryItemsDict["/no"] = LLTrans::getString("/no"); - mInventoryItemsDict["/no!"] = LLTrans::getString("/no!"); - mInventoryItemsDict["/paper"] = LLTrans::getString("/paper"); - mInventoryItemsDict["/pointme"] = LLTrans::getString("/pointme"); - mInventoryItemsDict["/pointyou"] = LLTrans::getString("/pointyou"); - mInventoryItemsDict["/rock"] = LLTrans::getString("/rock"); - mInventoryItemsDict["/scissor"] = LLTrans::getString("/scissor"); - mInventoryItemsDict["/smoke"] = LLTrans::getString("/smoke"); - mInventoryItemsDict["/stretch"] = LLTrans::getString("/stretch"); - mInventoryItemsDict["/whistle"] = LLTrans::getString("/whistle"); - mInventoryItemsDict["/yes"] = LLTrans::getString("/yes"); - mInventoryItemsDict["/yes!"] = LLTrans::getString("/yes!"); - mInventoryItemsDict["afk"] = LLTrans::getString("afk"); - mInventoryItemsDict["dance1"] = LLTrans::getString("dance1"); - mInventoryItemsDict["dance2"] = LLTrans::getString("dance2"); - mInventoryItemsDict["dance3"] = LLTrans::getString("dance3"); - mInventoryItemsDict["dance4"] = LLTrans::getString("dance4"); - mInventoryItemsDict["dance5"] = LLTrans::getString("dance5"); - mInventoryItemsDict["dance6"] = LLTrans::getString("dance6"); - mInventoryItemsDict["dance7"] = LLTrans::getString("dance7"); - mInventoryItemsDict["dance8"] = LLTrans::getString("dance8"); -} - -///---------------------------------------------------------------------------- -/// Local function declarations, constants, enums, and typedefs -///---------------------------------------------------------------------------- - -class LLInventoryHandler : public LLCommandHandler -{ -public: - LLInventoryHandler() : LLCommandHandler("inventory", UNTRUSTED_THROTTLE) { } - - virtual bool canHandleUntrusted( - const LLSD& params, - const LLSD& query_map, - LLMediaCtrl* web, - const std::string& nav_type) - { - if (params.size() < 1) - { - return true; // don't block, will fail later - } - - if (nav_type == NAV_TYPE_CLICKED - || nav_type == NAV_TYPE_EXTERNAL) - { - // NAV_TYPE_EXTERNAL will be throttled - return true; - } - - return false; - } - - bool handle(const LLSD& params, - const LLSD& query_map, - const std::string& grid, - LLMediaCtrl* web) - { - if (params.size() < 1) - { - return false; - } - - if (!LLUI::getInstance()->mSettingGroups["config"]->getBOOL("EnableInventory")) - { - LLNotificationsUtil::add("NoInventory", LLSD(), LLSD(), std::string("SwitchToStandardSkinAndQuit")); - return true; - } - - // support secondlife:///app/inventory/show - if (params[0].asString() == "show") - { - LLFloaterSidePanelContainer::showPanel("inventory", LLSD()); - return true; - } - - if (params[0].asString() == "filters") - { - LLSidepanelInventory *sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); - if (sidepanel_inventory) - { - LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); - if (main_inventory) - { - main_inventory->toggleFindOptions(); - } - } - return true; - } - - // otherwise, we need a UUID and a verb... - if (params.size() < 2) - { - return false; - } - LLUUID inventory_id; - if (!inventory_id.set(params[0], false)) - { - return false; - } - - const std::string verb = params[1].asString(); - if (verb == "select") - { - uuid_vec_t items_to_open; - items_to_open.push_back(inventory_id); - //inventory_handler is just a stub, because we don't know from who this offer - open_inventory_offer(items_to_open, "inventory_handler"); - return true; - } - - return false; - } -}; -LLInventoryHandler gInventoryHandler; - -///---------------------------------------------------------------------------- -/// Class LLViewerInventoryItem -///---------------------------------------------------------------------------- - -LLViewerInventoryItem::LLViewerInventoryItem(const LLUUID& uuid, - const LLUUID& parent_uuid, - const LLPermissions& perm, - const LLUUID& asset_uuid, - LLAssetType::EType type, - LLInventoryType::EType inv_type, - const std::string& name, - const std::string& desc, - const LLSaleInfo& sale_info, - U32 flags, - time_t creation_date_utc) : - LLInventoryItem(uuid, parent_uuid, perm, asset_uuid, type, inv_type, - name, desc, sale_info, flags, creation_date_utc), - mIsComplete(true) -{ -} - -LLViewerInventoryItem::LLViewerInventoryItem(const LLUUID& item_id, - const LLUUID& parent_id, - const std::string& name, - LLInventoryType::EType inv_type) : - LLInventoryItem(), - mIsComplete(false) -{ - mUUID = item_id; - mParentUUID = parent_id; - mInventoryType = inv_type; - mName = name; -} - -LLViewerInventoryItem::LLViewerInventoryItem() : - LLInventoryItem(), - mIsComplete(false) -{ -} - -LLViewerInventoryItem::LLViewerInventoryItem(const LLViewerInventoryItem* other) : - LLInventoryItem() -{ - copyViewerItem(other); - if (!mIsComplete) - { - LL_WARNS(LOG_INV) << "LLViewerInventoryItem copy constructor for incomplete item" - << mUUID << LL_ENDL; - } -} - -LLViewerInventoryItem::LLViewerInventoryItem(const LLInventoryItem *other) : - LLInventoryItem(other), - mIsComplete(true) -{ -} - - -LLViewerInventoryItem::~LLViewerInventoryItem() -{ -} - -void LLViewerInventoryItem::copyViewerItem(const LLViewerInventoryItem* other) -{ - LLInventoryItem::copyItem(other); - mIsComplete = other->mIsComplete; - mTransactionID = other->mTransactionID; -} - -// virtual -void LLViewerInventoryItem::copyItem(const LLInventoryItem *other) -{ - LLInventoryItem::copyItem(other); - mIsComplete = true; - mTransactionID.setNull(); -} - -void LLViewerInventoryItem::cloneViewerItem(LLPointer& newitem) const -{ - newitem = new LLViewerInventoryItem(this); - if(newitem.notNull()) - { - LLUUID item_id; - item_id.generate(); - newitem->setUUID(item_id); - } -} - -void LLViewerInventoryItem::updateServer(bool is_new) const -{ - if(!mIsComplete) - { - // *FIX: deal with this better. - // If we're crashing here then the UI is incorrectly enabled. - LL_ERRS(LOG_INV) << "LLViewerInventoryItem::updateServer() - for incomplete item" - << LL_ENDL; - return; - } - if(gAgent.getID() != mPermissions.getOwner()) - { - // *FIX: deal with this better. - LL_WARNS(LOG_INV) << "LLViewerInventoryItem::updateServer() - for unowned item " - << ll_pretty_print_sd(this->asLLSD()) - << LL_ENDL; - return; - } - LLInventoryModel::LLCategoryUpdate up(mParentUUID, is_new ? 1 : 0); - gInventory.accountForUpdate(up); - - LLSD updates = asLLSD(); - // Replace asset_id and/or shadow_id with transaction_id (hash_id) - if (updates.has("asset_id")) - { - updates.erase("asset_id"); - if(getTransactionID().notNull()) - { - updates["hash_id"] = getTransactionID(); - } - } - if (updates.has("shadow_id")) - { - updates.erase("shadow_id"); - if(getTransactionID().notNull()) - { - updates["hash_id"] = getTransactionID(); - } - } - AISAPI::completion_t cr = boost::bind(&doInventoryCb, (LLPointer)NULL, _1); - AISAPI::UpdateItem(getUUID(), updates, cr); -} - -void LLViewerInventoryItem::fetchFromServer(void) const -{ - if(!mIsComplete) - { - if (AISAPI::isAvailable()) // AIS v 3 - { - LLInventoryModelBackgroundFetch::getInstance()->scheduleItemFetch(mUUID); - } - else - { - std::string url; - - LLViewerRegion* region = gAgent.getRegion(); - // we have to check region. It can be null after region was destroyed. See EXT-245 - if (region) - { - if (gAgent.getID() != mPermissions.getOwner()) - { - url = region->getCapability("FetchLib2"); - } - else - { - url = region->getCapability("FetchInventory2"); - } - } - else - { - LL_WARNS(LOG_INV) << "Agent Region is absent" << LL_ENDL; - } - - if (!url.empty()) - { - LLSD body; - body["agent_id"] = gAgent.getID(); - body["items"][0]["owner_id"] = mPermissions.getOwner(); - body["items"][0]["item_id"] = mUUID; - - LLCore::HttpHandler::ptr_t handler(new LLInventoryModel::FetchItemHttpHandler(body)); - gInventory.requestPost(true, url, body, handler, "Inventory Item"); - } - } - } -} - -// virtual -bool LLViewerInventoryItem::unpackMessage(const LLSD& item) -{ - bool rv = LLInventoryItem::fromLLSD(item); - - LLLocalizedInventoryItemsDictionary::getInstance()->localizeInventoryObjectName(mName); - - mIsComplete = true; - return rv; -} - -// virtual -bool LLViewerInventoryItem::unpackMessage(LLMessageSystem* msg, const char* block, S32 block_num) -{ - bool rv = LLInventoryItem::unpackMessage(msg, block, block_num); - - LLLocalizedInventoryItemsDictionary::getInstance()->localizeInventoryObjectName(mName); - - mIsComplete = true; - return rv; -} - -void LLViewerInventoryItem::setTransactionID(const LLTransactionID& transaction_id) -{ - mTransactionID = transaction_id; -} - -void LLViewerInventoryItem::packMessage(LLMessageSystem* msg) const -{ - msg->addUUIDFast(_PREHASH_ItemID, mUUID); - msg->addUUIDFast(_PREHASH_FolderID, mParentUUID); - mPermissions.packMessage(msg); - msg->addUUIDFast(_PREHASH_TransactionID, mTransactionID); - S8 type = static_cast(mType); - msg->addS8Fast(_PREHASH_Type, type); - type = static_cast(mInventoryType); - msg->addS8Fast(_PREHASH_InvType, type); - msg->addU32Fast(_PREHASH_Flags, mFlags); - mSaleInfo.packMessage(msg); - msg->addStringFast(_PREHASH_Name, mName); - msg->addStringFast(_PREHASH_Description, mDescription); - msg->addS32Fast(_PREHASH_CreationDate, mCreationDate); - U32 crc = getCRC32(); - msg->addU32Fast(_PREHASH_CRC, crc); -} - -// virtual -bool LLViewerInventoryItem::importLegacyStream(std::istream& input_stream) -{ - bool rv = LLInventoryItem::importLegacyStream(input_stream); - mIsComplete = true; - return rv; -} - -void LLViewerInventoryItem::updateParentOnServer(bool restamp) const -{ - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_MoveInventoryItem); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addBOOLFast(_PREHASH_Stamp, restamp); - msg->nextBlockFast(_PREHASH_InventoryData); - msg->addUUIDFast(_PREHASH_ItemID, mUUID); - msg->addUUIDFast(_PREHASH_FolderID, mParentUUID); - msg->addString("NewName", NULL); - gAgent.sendReliableMessage(); -} - -//void LLViewerInventoryItem::setCloneCount(S32 clones) -//{ -// mClones = clones; -//} - -//S32 LLViewerInventoryItem::getCloneCount() const -//{ -// return mClones; -//} - -///---------------------------------------------------------------------------- -/// Class LLViewerInventoryCategory -///---------------------------------------------------------------------------- - -LLViewerInventoryCategory::LLViewerInventoryCategory(const LLUUID& uuid, - const LLUUID& parent_uuid, - LLFolderType::EType pref, - const std::string& name, - const LLUUID& owner_id) : - LLInventoryCategory(uuid, parent_uuid, pref, name), - mOwnerID(owner_id), - mVersion(LLViewerInventoryCategory::VERSION_UNKNOWN), - mDescendentCount(LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN), - mFetching(FETCH_NONE) -{ - mDescendentsRequested.reset(); -} - -LLViewerInventoryCategory::LLViewerInventoryCategory(const LLUUID& owner_id) : - mOwnerID(owner_id), - mVersion(LLViewerInventoryCategory::VERSION_UNKNOWN), - mDescendentCount(LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN), - mFetching(FETCH_NONE) -{ - mDescendentsRequested.reset(); -} - -LLViewerInventoryCategory::LLViewerInventoryCategory(const LLViewerInventoryCategory* other) -{ - copyViewerCategory(other); - mFetching = FETCH_NONE; -} - -LLViewerInventoryCategory::~LLViewerInventoryCategory() -{ -} - -void LLViewerInventoryCategory::copyViewerCategory(const LLViewerInventoryCategory* other) -{ - copyCategory(other); - mOwnerID = other->mOwnerID; - setVersion(other->getVersion()); - mDescendentCount = other->mDescendentCount; - mDescendentsRequested = other->mDescendentsRequested; -} - - -void LLViewerInventoryCategory::packMessage(LLMessageSystem* msg) const -{ - msg->addUUIDFast(_PREHASH_FolderID, mUUID); - msg->addUUIDFast(_PREHASH_ParentID, mParentUUID); - S8 type = static_cast(mPreferredType); - msg->addS8Fast(_PREHASH_Type, type); - msg->addStringFast(_PREHASH_Name, mName); -} - -void LLViewerInventoryCategory::updateParentOnServer(bool restamp) const -{ - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_MoveInventoryFolder); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - - msg->addBOOL("Stamp", restamp); - msg->nextBlockFast(_PREHASH_InventoryData); - msg->addUUIDFast(_PREHASH_FolderID, mUUID); - msg->addUUIDFast(_PREHASH_ParentID, mParentUUID); - gAgent.sendReliableMessage(); -} - -void LLViewerInventoryCategory::updateServer(bool is_new) const -{ - // communicate that change with the server. - - if (LLFolderType::lookupIsProtectedType(mPreferredType)) - { - LLNotificationsUtil::add("CannotModifyProtectedCategories"); - return; - } - - LLSD new_llsd = asLLSD(); - AISAPI::completion_t cr = boost::bind(&doInventoryCb, (LLPointer)NULL, _1); - AISAPI::UpdateCategory(getUUID(), new_llsd, cr); -} - -S32 LLViewerInventoryCategory::getVersion() const -{ - return mVersion; -} - -void LLViewerInventoryCategory::setVersion(S32 version) -{ - mVersion = version; -} - -bool LLViewerInventoryCategory::fetch(S32 expiry_seconds) -{ - if((VERSION_UNKNOWN == getVersion()) - && mDescendentsRequested.hasExpired()) //Expired check prevents multiple downloads. - { - LL_DEBUGS(LOG_INV) << "Fetching category children: " << mName << ", UUID: " << mUUID << LL_ENDL; - mDescendentsRequested.reset(); - mDescendentsRequested.setTimerExpirySec(expiry_seconds); - - std::string url; - if (gAgent.getRegion()) - { - url = gAgent.getRegion()->getCapability("FetchInventoryDescendents2"); - } - else - { - LL_WARNS_ONCE(LOG_INV) << "agent region is null" << LL_ENDL; - } - if (!url.empty() || AISAPI::isAvailable()) - { - LLInventoryModelBackgroundFetch::instance().start(mUUID, false); - } - return true; - } - return false; -} - -LLViewerInventoryCategory::EFetchType LLViewerInventoryCategory::getFetching() -{ - // if timer hasn't expired, request was scheduled, but not in progress - // if mFetching request was actually started - if (mDescendentsRequested.hasExpired()) - { - mFetching = FETCH_NONE; - } - return mFetching; -} - -void LLViewerInventoryCategory::setFetching(LLViewerInventoryCategory::EFetchType fetching) -{ - if (fetching == FETCH_FAILED) - { - const F32 FETCH_FAILURE_EXPIRY = 60.0f; - mDescendentsRequested.setTimerExpirySec(FETCH_FAILURE_EXPIRY); - mFetching = fetching; - } - else if (fetching > mFetching) // allow a switch from normal to recursive - { - if (mDescendentsRequested.hasExpired() || (mFetching == FETCH_NONE)) - { - mDescendentsRequested.reset(); - if (AISAPI::isAvailable()) - { - mDescendentsRequested.setTimerExpirySec(AISAPI::HTTP_TIMEOUT); - } - else - { - const F32 FETCH_TIMER_EXPIRY = 30.0f; - mDescendentsRequested.setTimerExpirySec(FETCH_TIMER_EXPIRY); - } - } - mFetching = fetching; - } - else if (fetching == FETCH_NONE) - { - mDescendentsRequested.stop(); - mFetching = fetching; - } -} - -S32 LLViewerInventoryCategory::getViewerDescendentCount() const -{ - LLInventoryModel::cat_array_t* cats; - LLInventoryModel::item_array_t* items; - gInventory.getDirectDescendentsOf(getUUID(), cats, items); - S32 descendents_actual = 0; - if(cats && items) - { - descendents_actual = cats->size() + items->size(); - } - return descendents_actual; -} - -LLSD LLViewerInventoryCategory::exportLLSD() const -{ - LLSD cat_data = LLInventoryCategory::exportLLSD(); - cat_data[INV_OWNER_ID] = mOwnerID; - cat_data[INV_VERSION] = mVersion; - - return cat_data; -} - -bool LLViewerInventoryCategory::importLLSD(const LLSD& cat_data) -{ - LLInventoryCategory::importLLSD(cat_data); - if (cat_data.has(INV_OWNER_ID)) - { - mOwnerID = cat_data[INV_OWNER_ID].asUUID(); - } - if (cat_data.has(INV_VERSION)) - { - setVersion(cat_data[INV_VERSION].asInteger()); - } - return true; -} - -bool LLViewerInventoryCategory::acceptItem(LLInventoryItem* inv_item) -{ - if (!inv_item) - { - return false; - } - - // Only stock folders have limitation on which item they will accept - bool accept = true; - if (getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK) - { - // If the item is copyable (i.e. non stock) do not accept the drop in a stock folder - if (inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) - { - accept = false; - } - else - { - LLInventoryModel::cat_array_t* cat_array; - LLInventoryModel::item_array_t* item_array; - gInventory.getDirectDescendentsOf(getUUID(),cat_array,item_array); - // Destination stock folder must be empty OR types of incoming and existing items must be identical and have the same permissions - accept = (!item_array->size() || - ((item_array->at(0)->getInventoryType() == inv_item->getInventoryType()) && - (item_array->at(0)->getPermissions().getMaskNextOwner() == inv_item->getPermissions().getMaskNextOwner()))); - } - } - return accept; -} - -void LLViewerInventoryCategory::determineFolderType() -{ - /* Do NOT uncomment this code. This is for future 2.1 support of ensembles. - llassert(false); - LLFolderType::EType original_type = getPreferredType(); - if (LLFolderType::lookupIsProtectedType(original_type)) - return; - - U64 folder_valid = 0; - U64 folder_invalid = 0; - LLInventoryModel::cat_array_t category_array; - LLInventoryModel::item_array_t item_array; - gInventory.collectDescendents(getUUID(),category_array,item_array,false); - - // For ensembles - if (category_array.empty()) - { - for (LLInventoryModel::item_array_t::iterator item_iter = item_array.begin(); - item_iter != item_array.end(); - item_iter++) - { - const LLViewerInventoryItem *item = (*item_iter); - if (item->getIsLinkType()) - return; - if (item->isWearableType()) - { - const LLWearableType::EType wearable_type = item->getWearableType(); - const std::string& wearable_name = LLWearableType::getTypeName(wearable_type); - U64 valid_folder_types = LLViewerFolderType::lookupValidFolderTypes(wearable_name); - folder_valid |= valid_folder_types; - folder_invalid |= ~valid_folder_types; - } - } - for (U8 i = LLFolderType::FT_ENSEMBLE_START; i <= LLFolderType::FT_ENSEMBLE_END; i++) - { - if ((folder_valid & (1LL << i)) && - !(folder_invalid & (1LL << i))) - { - changeType((LLFolderType::EType)i); - return; - } - } - } - if (LLFolderType::lookupIsEnsembleType(original_type)) - { - changeType(LLFolderType::FT_NONE); - } - llassert(false); - */ -} - -void LLViewerInventoryCategory::changeType(LLFolderType::EType new_folder_type) -{ - const LLUUID &folder_id = getUUID(); - const LLUUID &parent_id = getParentUUID(); - const std::string &name = getName(); - - LLPointer new_cat = new LLViewerInventoryCategory(folder_id, - parent_id, - new_folder_type, - name, - gAgent.getID()); - - - LLSD new_llsd = new_cat->asLLSD(); - AISAPI::completion_t cr = boost::bind(&doInventoryCb, (LLPointer) NULL, _1); - AISAPI::UpdateCategory(folder_id, new_llsd, cr); - - setPreferredType(new_folder_type); - gInventory.addChangedMask(LLInventoryObserver::LABEL, folder_id); -} - -void LLViewerInventoryCategory::localizeName() -{ - LLLocalizedInventoryItemsDictionary::getInstance()->localizeInventoryObjectName(mName); -} - -// virtual -bool LLViewerInventoryCategory::unpackMessage(const LLSD& category) -{ - bool rv = LLInventoryCategory::fromLLSD(category); - localizeName(); - return rv; -} - -// virtual -void LLViewerInventoryCategory::unpackMessage(LLMessageSystem* msg, const char* block, S32 block_num) -{ - LLInventoryCategory::unpackMessage(msg, block, block_num); - localizeName(); -} - -///---------------------------------------------------------------------------- -/// Local function definitions -///---------------------------------------------------------------------------- - -LLInventoryCallbackManager *LLInventoryCallbackManager::sInstance = NULL; - -LLInventoryCallbackManager::LLInventoryCallbackManager() : - mLastCallback(0) -{ - if( sInstance != NULL ) - { - LL_WARNS(LOG_INV) << "LLInventoryCallbackManager::LLInventoryCallbackManager: unexpected multiple instances" << LL_ENDL; - return; - } - sInstance = this; -} - -LLInventoryCallbackManager::~LLInventoryCallbackManager() -{ - if( sInstance != this ) - { - LL_WARNS(LOG_INV) << "LLInventoryCallbackManager::~LLInventoryCallbackManager: unexpected multiple instances" << LL_ENDL; - return; - } - sInstance = NULL; -} - -//static -void LLInventoryCallbackManager::destroyClass() -{ - if (sInstance) - { - for (callback_map_t::iterator it = sInstance->mMap.begin(), end_it = sInstance->mMap.end(); it != end_it; ++it) - { - // drop LLPointer reference to callback - it->second = NULL; - } - sInstance->mMap.clear(); - } -} - - -U32 LLInventoryCallbackManager::registerCB(LLPointer cb) -{ - if (cb.isNull()) - return 0; - - mLastCallback++; - if (!mLastCallback) - mLastCallback++; - - mMap[mLastCallback] = cb; - return mLastCallback; -} - -void LLInventoryCallbackManager::fire(U32 callback_id, const LLUUID& item_id) -{ - if (!callback_id || item_id.isNull()) - return; - - std::map >::iterator i; - - i = mMap.find(callback_id); - if (i != mMap.end()) - { - (*i).second->fire(item_id); - mMap.erase(i); - } -} - -void rez_attachment_cb(const LLUUID& inv_item, LLViewerJointAttachment *attachmentp) -{ - if (inv_item.isNull()) - return; - - LLViewerInventoryItem *item = gInventory.getItem(inv_item); - if (item) - { - rez_attachment(item, attachmentp); - } -} - -void activate_gesture_cb(const LLUUID& inv_item) -{ - if (inv_item.isNull()) - return; - LLViewerInventoryItem* item = gInventory.getItem(inv_item); - if (!item) - return; - if (item->getType() != LLAssetType::AT_GESTURE) - return; - - LLGestureMgr::instance().activateGesture(inv_item); -} - -void set_default_permissions(LLViewerInventoryItem* item, std::string perm_type) -{ - llassert(item); - LLPermissions perm = item->getPermissions(); - if (perm.getMaskEveryone() != LLFloaterPerms::getEveryonePerms(perm_type) - || perm.getMaskGroup() != LLFloaterPerms::getGroupPerms(perm_type)) - { - perm.setMaskEveryone(LLFloaterPerms::getEveryonePerms(perm_type)); - perm.setMaskGroup(LLFloaterPerms::getGroupPerms(perm_type)); - - item->setPermissions(perm); - - item->updateServer(false); - } -} - -void create_script_cb(const LLUUID& inv_item) -{ - if (!inv_item.isNull()) - { - LLViewerInventoryItem* item = gInventory.getItem(inv_item); - if (item) - { - set_default_permissions(item, "Scripts"); - - // item was just created, update even if permissions did not changed - gInventory.updateItem(item); - gInventory.notifyObservers(); - } - } -} - -void create_gesture_cb(const LLUUID& inv_item) -{ - if (!inv_item.isNull()) - { - LLGestureMgr::instance().activateGesture(inv_item); - - LLViewerInventoryItem* item = gInventory.getItem(inv_item); - if (item) - { - set_default_permissions(item, "Gestures"); - - gInventory.updateItem(item); - gInventory.notifyObservers(); - - LLPreviewGesture* preview = LLPreviewGesture::show(inv_item, LLUUID::null); - // Force to be entirely onscreen. - gFloaterView->adjustToFitScreen(preview, false); - } - } -} - - -void create_notecard_cb(const LLUUID& inv_item) -{ - if (!inv_item.isNull()) - { - LLViewerInventoryItem* item = gInventory.getItem(inv_item); - if (item) - { - set_default_permissions(item, "Notecards"); - - gInventory.updateItem(item); - gInventory.notifyObservers(); - } - } -} - -void create_gltf_material_cb(const LLUUID& inv_item) -{ - if (!inv_item.isNull()) - { - LLViewerInventoryItem* item = gInventory.getItem(inv_item); - if (item) - { - set_default_permissions(item, "Materials"); - - gInventory.updateItem(item); - gInventory.notifyObservers(); - } - } -} - -LLInventoryCallbackManager gInventoryCallbacks; - -void create_inventory_item( - const LLUUID& agent_id, - const LLUUID& session_id, - const LLUUID& parent_id, - const LLTransactionID& transaction_id, - const std::string& name, - const std::string& desc, - LLAssetType::EType asset_type, - LLInventoryType::EType inv_type, - U8 subtype, - U32 next_owner_perm, - LLPointer cb) -{ - //check if name is equal to one of special inventory items names - //EXT-5839 - std::string server_name = name; - - { - std::map::const_iterator dictionary_iter; - - for (dictionary_iter = LLLocalizedInventoryItemsDictionary::getInstance()->mInventoryItemsDict.begin(); - dictionary_iter != LLLocalizedInventoryItemsDictionary::getInstance()->mInventoryItemsDict.end(); - dictionary_iter++) - { - const std::string& localized_name = dictionary_iter->second; - if(localized_name == name) - { - server_name = dictionary_iter->first; - } - } - } - -#ifdef USE_AIS_FOR_NC - // D567 18.03.2023 not yet implemented within AIS3 - if (AISAPI::isAvailable()) - { - LLSD new_inventory = LLSD::emptyMap(); - new_inventory["items"] = LLSD::emptyArray(); - - LLPermissions perms; - perms.init( - gAgentID, - gAgentID, - LLUUID::null, - LLUUID::null); - perms.initMasks( - PERM_ALL, - PERM_ALL, - PERM_NONE, - PERM_NONE, - next_owner_perm); - - LLUUID null_id; - LLPointer item = new LLViewerInventoryItem( - null_id, /*don't know yet*/ - parent_id, - perms, - null_id, /*don't know yet*/ - asset_type, - inv_type, - server_name, - desc, - LLSaleInfo(), - 0, - 0 /*don't know yet, whenever server creates it*/); - LLSD item_sd = item->asLLSD(); - new_inventory["items"].append(item_sd); - AISAPI::completion_t cr = boost::bind(&doInventoryCb, cb, _1); - AISAPI::CreateInventory( - parent_id, - new_inventory, - cr); - return; - } - else - { - LL_WARNS() << "AIS v3 not available" << LL_ENDL; - } -#endif - - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_CreateInventoryItem); - msg->nextBlock(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, agent_id); - msg->addUUIDFast(_PREHASH_SessionID, session_id); - msg->nextBlock(_PREHASH_InventoryBlock); - msg->addU32Fast(_PREHASH_CallbackID, gInventoryCallbacks.registerCB(cb)); - msg->addUUIDFast(_PREHASH_FolderID, parent_id); - msg->addUUIDFast(_PREHASH_TransactionID, transaction_id); - msg->addU32Fast(_PREHASH_NextOwnerMask, next_owner_perm); - msg->addS8Fast(_PREHASH_Type, (S8)asset_type); - msg->addS8Fast(_PREHASH_InvType, (S8)inv_type); - msg->addU8Fast(_PREHASH_WearableType, (U8)subtype); - msg->addStringFast(_PREHASH_Name, server_name); - msg->addStringFast(_PREHASH_Description, desc); - - gAgent.sendReliableMessage(); -} - -void create_inventory_callingcard_callback(LLPointer cb, - const LLUUID &parent, - const LLUUID &avatar_id, - const LLAvatarName &av_name) -{ - std::string item_desc = avatar_id.asString(); - create_inventory_item(gAgent.getID(), - gAgent.getSessionID(), - parent, - LLTransactionID::tnull, - av_name.getUserName(), - item_desc, - LLAssetType::AT_CALLINGCARD, - LLInventoryType::IT_CALLINGCARD, - NO_INV_SUBTYPE, - PERM_MOVE | PERM_TRANSFER, - cb); -} - -void create_inventory_callingcard(const LLUUID& avatar_id, const LLUUID& parent /*= LLUUID::null*/, LLPointer cb/*=NULL*/) -{ - LLAvatarName av_name; - LLAvatarNameCache::get(avatar_id, boost::bind(&create_inventory_callingcard_callback, cb, parent, _1, _2)); -} - -void create_inventory_wearable(const LLUUID& agent_id, const LLUUID& session_id, - const LLUUID& parent, const LLTransactionID& transaction_id, - const std::string& name, - const std::string& desc, LLAssetType::EType asset_type, - LLWearableType::EType wtype, - U32 next_owner_perm, - LLPointer cb) -{ - create_inventory_item(agent_id, session_id, parent, transaction_id, - name, desc, asset_type, LLInventoryType::IT_WEARABLE, static_cast(wtype), - next_owner_perm, cb); -} - -void create_inventory_settings(const LLUUID& agent_id, const LLUUID& session_id, - const LLUUID& parent, const LLTransactionID& transaction_id, - const std::string& name, - const std::string& desc, - LLSettingsType::type_e settype, - U32 next_owner_perm, - LLPointer cb) -{ - create_inventory_item(agent_id, session_id, parent, transaction_id, - name, desc, LLAssetType::AT_SETTINGS, LLInventoryType::IT_SETTINGS, - static_cast(settype), next_owner_perm, cb); -} - - -void copy_inventory_item( - const LLUUID& agent_id, - const LLUUID& current_owner, - const LLUUID& item_id, - const LLUUID& parent_id, - const std::string& new_name, - LLPointer cb) -{ - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_CopyInventoryItem); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, agent_id); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_InventoryData); - msg->addU32Fast(_PREHASH_CallbackID, gInventoryCallbacks.registerCB(cb)); - msg->addUUIDFast(_PREHASH_OldAgentID, current_owner); - msg->addUUIDFast(_PREHASH_OldItemID, item_id); - msg->addUUIDFast(_PREHASH_NewFolderID, parent_id); - msg->addStringFast(_PREHASH_NewName, new_name); - gAgent.sendReliableMessage(); -} - -// Create link to single inventory object. -void link_inventory_object(const LLUUID& category, - LLConstPointer baseobj, - LLPointer cb) -{ - if (!baseobj) - { - LL_WARNS(LOG_INV) << "Attempt to link to non-existent object" << LL_ENDL; - return; - } - - LLInventoryObject::const_object_list_t obj_array; - obj_array.push_back(baseobj); - link_inventory_array(category, obj_array, cb); -} - -void link_inventory_object(const LLUUID& category, - const LLUUID& id, - LLPointer cb) -{ - LLConstPointer baseobj = gInventory.getObject(id); - link_inventory_object(category, baseobj, cb); -} - -// Create links to all listed inventory objects. -void link_inventory_array(const LLUUID& category, - LLInventoryObject::const_object_list_t& baseobj_array, - LLPointer cb) -{ -#ifndef LL_RELEASE_FOR_DOWNLOAD - const LLViewerInventoryCategory *cat = gInventory.getCategory(category); - const std::string cat_name = cat ? cat->getName() : "CAT NOT FOUND"; -#endif - LLInventoryObject::const_object_list_t::const_iterator it = baseobj_array.begin(); - LLInventoryObject::const_object_list_t::const_iterator end = baseobj_array.end(); - LLSD links = LLSD::emptyArray(); - for (; it != end; ++it) - { - const LLInventoryObject* baseobj = *it; - if (!baseobj) - { - LL_WARNS(LOG_INV) << "attempt to link to unknown object" << LL_ENDL; - continue; - } - - if (!LLAssetType::lookupCanLink(baseobj->getType())) - { - // Fail if item can be found but is of a type that can't be linked. - // Arguably should fail if the item can't be found too, but that could - // be a larger behavioral change. - LL_WARNS(LOG_INV) << "attempt to link an unlinkable object, type = " << baseobj->getActualType() << LL_ENDL; - continue; - } - - LLInventoryType::EType inv_type = LLInventoryType::IT_NONE; - LLAssetType::EType asset_type = LLAssetType::AT_NONE; - std::string new_desc; - LLUUID linkee_id; - if (dynamic_cast(baseobj)) - { - inv_type = LLInventoryType::IT_CATEGORY; - asset_type = LLAssetType::AT_LINK_FOLDER; - linkee_id = baseobj->getUUID(); - } - else - { - const LLViewerInventoryItem *baseitem = dynamic_cast(baseobj); - if (baseitem) - { - inv_type = baseitem->getInventoryType(); - new_desc = baseitem->getActualDescription(); - switch (baseitem->getActualType()) - { - case LLAssetType::AT_LINK: - case LLAssetType::AT_LINK_FOLDER: - linkee_id = baseobj->getLinkedUUID(); - asset_type = baseitem->getActualType(); - break; - default: - linkee_id = baseobj->getUUID(); - asset_type = LLAssetType::AT_LINK; - break; - } - } - else - { - LL_WARNS(LOG_INV) << "could not convert object into an item or category: " << baseobj->getUUID() << LL_ENDL; - continue; - } - } - - LLSD link = LLSD::emptyMap(); - link["linked_id"] = linkee_id; - link["type"] = (S8)asset_type; - link["inv_type"] = (S8)inv_type; - link["name"] = baseobj->getName(); - link["desc"] = new_desc; - links.append(link); - -#ifndef LL_RELEASE_FOR_DOWNLOAD - LL_DEBUGS(LOG_INV) << "Linking Object [ name:" << baseobj->getName() - << " UUID:" << baseobj->getUUID() - << " ] into Category [ name:" << cat_name - << " UUID:" << category << " ] " << LL_ENDL; -#endif - } - LLSD new_inventory = LLSD::emptyMap(); - new_inventory["links"] = links; - AISAPI::completion_t cr = boost::bind(&doInventoryCb, cb, _1); - AISAPI::CreateInventory(category, new_inventory, cr); -} - -void move_inventory_item( - const LLUUID& agent_id, - const LLUUID& session_id, - const LLUUID& item_id, - const LLUUID& parent_id, - const std::string& new_name, - LLPointer cb) -{ - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_MoveInventoryItem); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, agent_id); - msg->addUUIDFast(_PREHASH_SessionID, session_id); - msg->addBOOLFast(_PREHASH_Stamp, false); - msg->nextBlockFast(_PREHASH_InventoryData); - msg->addUUIDFast(_PREHASH_ItemID, item_id); - msg->addUUIDFast(_PREHASH_FolderID, parent_id); - msg->addStringFast(_PREHASH_NewName, new_name); - gAgent.sendReliableMessage(); -} - -// Should call this with an update_item that's been copied and -// modified from an original source item, rather than modifying the -// source item directly. -void update_inventory_item( - LLViewerInventoryItem *update_item, - LLPointer cb) -{ - const LLUUID& item_id = update_item->getUUID(); - - LLSD updates = update_item->asLLSD(); - // Replace asset_id and/or shadow_id with transaction_id (hash_id) - if (updates.has("asset_id")) - { - updates.erase("asset_id"); - if (update_item->getTransactionID().notNull()) - { - updates["hash_id"] = update_item->getTransactionID(); - } - } - if (updates.has("shadow_id")) - { - updates.erase("shadow_id"); - if (update_item->getTransactionID().notNull()) - { - updates["hash_id"] = update_item->getTransactionID(); - } - } - AISAPI::completion_t cr = boost::bind(&doInventoryCb, cb, _1); - AISAPI::UpdateItem(item_id, updates, cr); -} - -// Note this only supports updating an existing item. Goes through AISv3 -// code path where available. Not all uses of item->updateServer() can -// easily be switched to this paradigm. -void update_inventory_item( - const LLUUID& item_id, - const LLSD& updates, - LLPointer cb) -{ - AISAPI::completion_t cr = boost::bind(&doInventoryCb, cb, _1); - AISAPI::UpdateItem(item_id, updates, cr); -} - -void update_inventory_category( - const LLUUID& cat_id, - const LLSD& updates, - LLPointer cb) -{ - LLPointer obj = gInventory.getCategory(cat_id); - LL_DEBUGS(LOG_INV) << "cat_id: [" << cat_id << "] name " << (obj ? obj->getName() : "(NOT FOUND)") << LL_ENDL; - if(obj) - { - if (LLFolderType::lookupIsProtectedType(obj->getPreferredType()) - && (updates.size() != 1 || !updates.has("thumbnail"))) - { - LLNotificationsUtil::add("CannotModifyProtectedCategories"); - return; - } - - AISAPI::completion_t cr = boost::bind(&doInventoryCb, cb, _1); - AISAPI::UpdateCategory(cat_id, updates, cr); - } -} - -void remove_inventory_items( - LLInventoryObject::object_list_t& items_to_kill, - LLPointer cb - ) -{ - for (LLInventoryObject::object_list_t::iterator it = items_to_kill.begin(); - it != items_to_kill.end(); - ++it) - { - remove_inventory_item(*it, cb); - } -} - -void remove_inventory_item( - const LLUUID& item_id, - LLPointer cb, - bool immediate_delete) -{ - LLPointer obj = gInventory.getItem(item_id); - if (obj) - { - remove_inventory_item(obj, cb, immediate_delete); - } - else - { - LL_DEBUGS(LOG_INV) << "item_id: [" << item_id << "] name " << "(NOT FOUND)" << LL_ENDL; - } -} - -void remove_inventory_item( - LLPointer obj, - LLPointer cb, - bool immediate_delete) -{ - if(obj) - { - const LLUUID item_id(obj->getUUID()); - LL_DEBUGS(LOG_INV) << "item_id: [" << item_id << "] name " << obj->getName() << LL_ENDL; - if (AISAPI::isAvailable()) - { - AISAPI::completion_t cr = (cb) ? boost::bind(&doInventoryCb, cb, _1) : AISAPI::completion_t(); - AISAPI::RemoveItem(item_id, cr); - - if (immediate_delete) - { - gInventory.onObjectDeletedFromServer(item_id); - } - } - else - { - LL_WARNS(LOG_INV) << "Tried to use inventory without AIS API" << LL_ENDL; - } - } - else - { - // *TODO: Clean up callback? - LL_WARNS(LOG_INV) << "remove_inventory_item called for invalid or nonexistent item." << LL_ENDL; - } -} - -class LLRemoveCategoryOnDestroy: public LLInventoryCallback -{ -public: - LLRemoveCategoryOnDestroy(const LLUUID& cat_id, LLPointer cb): - mID(cat_id), - mCB(cb) - { - } - /* virtual */ void fire(const LLUUID& item_id) {} - ~LLRemoveCategoryOnDestroy() - { - LLInventoryModel::EHasChildren children = gInventory.categoryHasChildren(mID); - if(children != LLInventoryModel::CHILDREN_NO) - { - LL_WARNS(LOG_INV) << "remove descendents failed, cannot remove category " << LL_ENDL; - } - else - { - remove_inventory_category(mID, mCB); - } - } -private: - LLUUID mID; - LLPointer mCB; -}; - -void remove_inventory_category( - const LLUUID& cat_id, - LLPointer cb) -{ - LL_DEBUGS(LOG_INV) << "cat_id: [" << cat_id << "] " << LL_ENDL; - LLPointer obj = gInventory.getCategory(cat_id); - if(obj) - { - if (!gInventory.isCategoryComplete(cat_id)) - { - LL_WARNS() << "Removing (purging) incomplete category " << obj->getName() << LL_ENDL; - } - if(LLFolderType::lookupIsProtectedType(obj->getPreferredType())) - { - LLNotificationsUtil::add("CannotRemoveProtectedCategories"); - return; - } - AISAPI::completion_t cr = boost::bind(&doInventoryCb, cb, _1); - AISAPI::RemoveCategory(cat_id, cr); - } - else - { - LL_WARNS(LOG_INV) << "remove_inventory_category called for invalid or nonexistent item " << cat_id << LL_ENDL; - } -} - -void remove_inventory_object( - const LLUUID& object_id, - LLPointer cb) -{ - if (gInventory.getCategory(object_id)) - { - remove_inventory_category(object_id, cb); - } - else - { - remove_inventory_item(object_id, cb); - } -} - -// This is a method which collects the descendents of the id -// provided. If the category is not found, no action is -// taken. This method goes through the long winded process of -// cancelling any calling cards, removing server representation of -// folders, items, etc in a fairly efficient manner. -void purge_descendents_of(const LLUUID& id, LLPointer cb) -{ - LLInventoryModel::EHasChildren children = gInventory.categoryHasChildren(id); - if(children == LLInventoryModel::CHILDREN_NO) - { - LL_DEBUGS(LOG_INV) << "No descendents to purge for " << id << LL_ENDL; - return; - } - LLPointer cat = gInventory.getCategory(id); - if (cat.notNull()) - { - if (LLClipboard::instance().hasContents()) - { - // Remove items from clipboard or it will remain active even if there is nothing to paste/copy - LLInventoryModel::cat_array_t categories; - LLInventoryModel::item_array_t items; - gInventory.collectDescendents(id, categories, items, true); - - for (LLInventoryModel::cat_array_t::const_iterator it = categories.begin(); it != categories.end(); ++it) - { - if (LLClipboard::instance().isOnClipboard((*it)->getUUID())) - { - // No sense in removing single items, partial 'paste' will result in confusion only - LLClipboard::instance().reset(); - break; - } - } - if (LLClipboard::instance().hasContents()) - { - for (LLInventoryModel::item_array_t::const_iterator it = items.begin(); it != items.end(); ++it) - { - if (LLClipboard::instance().isOnClipboard((*it)->getUUID())) - { - LLClipboard::instance().reset(); - break; - } - } - } - } - - if (AISAPI::isAvailable()) - { - if (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) - { - LL_WARNS() << "Purging not fetched folder: " << cat->getName() << LL_ENDL; - } - AISAPI::completion_t cr = (cb) ? boost::bind(&doInventoryCb, cb, _1) : AISAPI::completion_t(); - AISAPI::PurgeDescendents(id, cr); - } - else - { - LL_WARNS(LOG_INV) << "Tried to use inventory without AIS API" << LL_ENDL; - } - } -} - -const LLUUID get_folder_by_itemtype(const LLInventoryItem *src) -{ - LLUUID retval = LLUUID::null; - - if (src) - { - retval = gInventory.findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(src->getType())); - } - - return retval; -} - -void copy_inventory_from_notecard(const LLUUID& destination_id, - const LLUUID& object_id, - const LLUUID& notecard_inv_id, - const LLInventoryItem *src, - U32 callback_id) -{ - if (NULL == src) - { - LL_WARNS(LOG_NOTECARD) << "Null pointer to item was passed for object_id " - << object_id << " and notecard_inv_id " - << notecard_inv_id << LL_ENDL; - return; - } - - LLViewerRegion* viewer_region = NULL; - LLViewerObject* vo = NULL; - if (object_id.notNull() && (vo = gObjectList.findObject(object_id)) != NULL) - { - viewer_region = vo->getRegion(); - } - - // Fallback to the agents region if for some reason the - // object isn't found in the viewer. - if (! viewer_region) - { - viewer_region = gAgent.getRegion(); - } - - if (! viewer_region) - { - LL_WARNS(LOG_NOTECARD) << "Can't find region from object_id " - << object_id << " or gAgent" - << LL_ENDL; - return; - } - - LLSD body; - body["notecard-id"] = notecard_inv_id; - body["object-id"] = object_id; - body["item-id"] = src->getUUID(); - body["folder-id"] = destination_id; - body["callback-id"] = (LLSD::Integer)callback_id; - - /// *TODO: RIDER: This posts the request under the agents policy. - /// When I convert the inventory over this call should be moved under that - /// policy as well. - if (!gAgent.requestPostCapability("CopyInventoryFromNotecard", body)) - { - LL_WARNS() << "SIM does not have the capability to copy from notecard." << LL_ENDL; - } -} - -void create_new_item(const std::string& name, - const LLUUID& parent_id, - LLAssetType::EType asset_type, - LLInventoryType::EType inv_type, - U32 next_owner_perm, - std::function created_cb = NULL) -{ - std::string desc; - LLViewerAssetType::generateDescriptionFor(asset_type, desc); - next_owner_perm = (next_owner_perm) ? next_owner_perm : PERM_MOVE | PERM_TRANSFER; - - LLPointer cb = NULL; - - switch (inv_type) - { - case LLInventoryType::IT_LSL: - { - cb = new LLBoostFuncInventoryCallback(create_script_cb); - next_owner_perm = LLFloaterPerms::getNextOwnerPerms("Scripts"); - break; - } - - case LLInventoryType::IT_GESTURE: - { - cb = new LLBoostFuncInventoryCallback(create_gesture_cb); - next_owner_perm = LLFloaterPerms::getNextOwnerPerms("Gestures"); - break; - } - - case LLInventoryType::IT_NOTECARD: - { - cb = new LLBoostFuncInventoryCallback(create_notecard_cb); - next_owner_perm = LLFloaterPerms::getNextOwnerPerms("Notecards"); - break; - } - - case LLInventoryType::IT_MATERIAL: - { - cb = new LLBoostFuncInventoryCallback(create_gltf_material_cb); - next_owner_perm = LLFloaterPerms::getNextOwnerPerms("Materials"); - break; - } - default: - { - cb = new LLBoostFuncInventoryCallback(); - break; - } - } - if (created_cb != NULL) - { - cb->addOnFireFunc(created_cb); - } - - create_inventory_item(gAgent.getID(), - gAgent.getSessionID(), - parent_id, - LLTransactionID::tnull, - name, - desc, - asset_type, - inv_type, - NO_INV_SUBTYPE, - next_owner_perm, - cb); -} - -void slam_inventory_folder(const LLUUID& folder_id, - const LLSD& contents, - LLPointer cb) -{ - LL_DEBUGS(LOG_INV) << "using AISv3 to slam folder, id " << folder_id - << " new contents: " << ll_pretty_print_sd(contents) << LL_ENDL; - - AISAPI::completion_t cr = boost::bind(&doInventoryCb, cb, _1); - AISAPI::SlamFolder(folder_id, contents, cr); -} - -void remove_folder_contents(const LLUUID& category, bool keep_outfit_links, - LLPointer cb) -{ - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - gInventory.collectDescendents(category, cats, items, - LLInventoryModel::EXCLUDE_TRASH); - for (S32 i = 0; i < items.size(); ++i) - { - LLViewerInventoryItem *item = items.at(i); - if (keep_outfit_links && (item->getActualType() == LLAssetType::AT_LINK_FOLDER)) - continue; - if (item->getIsLinkType()) - { - remove_inventory_item(item->getUUID(), cb); - } - } -} - -const std::string NEW_LSL_NAME = "New Script"; // *TODO:Translate? (probably not) -const std::string NEW_NOTECARD_NAME = "New Note"; // *TODO:Translate? (probably not) -const std::string NEW_GESTURE_NAME = "New Gesture"; // *TODO:Translate? (probably not) -const std::string NEW_MATERIAL_NAME = "New Material"; // *TODO:Translate? (probably not) - -// ! REFACTOR ! Really need to refactor this so that it's not a bunch of if-then statements... -void menu_create_inventory_item(LLInventoryPanel* panel, LLFolderBridge *bridge, const LLSD& userdata, const LLUUID& default_parent_uuid) -{ - menu_create_inventory_item(panel, bridge ? bridge->getUUID() : LLUUID::null, userdata, default_parent_uuid); -} - -void menu_create_inventory_item(LLInventoryPanel* panel, LLUUID dest_id, const LLSD& userdata, const LLUUID& default_parent_uuid, std::function created_cb) -{ - std::string type_name = userdata.asString(); - - if (("inbox" == type_name) || ("category" == type_name) || ("current" == type_name) || ("outfit" == type_name) || ("my_otfts" == type_name)) - { - LLFolderType::EType preferred_type = LLFolderType::lookup(type_name); - - LLUUID parent_id; - if (dest_id.notNull()) - { - parent_id = dest_id; - } - else if (default_parent_uuid.notNull()) - { - parent_id = default_parent_uuid; - } - else - { - parent_id = gInventory.getRootFolderID(); - } - - std::function callback_cat_created = NULL; - if (panel) - { - LLHandle handle = panel->getHandle(); - callback_cat_created = [handle](const LLUUID& new_category_id) - { - gInventory.notifyObservers(); - LLInventoryPanel* panel = static_cast(handle.get()); - if (panel) - { - panel->setSelectionByID(new_category_id, true); - } - LL_DEBUGS(LOG_INV) << "Done creating inventory: " << new_category_id << LL_ENDL; - }; - } - else if (created_cb != NULL) - { - callback_cat_created = created_cb; - } - gInventory.createNewCategory( - parent_id, - preferred_type, - LLStringUtil::null, - callback_cat_created); - } - else if ("lsl" == type_name) - { - const LLUUID parent_id = dest_id.notNull() ? dest_id : gInventory.findCategoryUUIDForType(LLFolderType::FT_LSL_TEXT); - create_new_item(NEW_LSL_NAME, - parent_id, - LLAssetType::AT_LSL_TEXT, - LLInventoryType::IT_LSL, - PERM_MOVE | PERM_TRANSFER, - created_cb); // overridden in create_new_item - } - else if ("notecard" == type_name) - { - const LLUUID parent_id = dest_id.notNull() ? dest_id : gInventory.findCategoryUUIDForType(LLFolderType::FT_NOTECARD); - create_new_item(NEW_NOTECARD_NAME, - parent_id, - LLAssetType::AT_NOTECARD, - LLInventoryType::IT_NOTECARD, - PERM_ALL, - created_cb); // overridden in create_new_item - } - else if ("gesture" == type_name) - { - const LLUUID parent_id = dest_id.notNull() ? dest_id : gInventory.findCategoryUUIDForType(LLFolderType::FT_GESTURE); - create_new_item(NEW_GESTURE_NAME, - parent_id, - LLAssetType::AT_GESTURE, - LLInventoryType::IT_GESTURE, - PERM_ALL, - created_cb); // overridden in create_new_item - } - else if ("material" == type_name) - { - const LLUUID parent_id = dest_id.notNull() ? dest_id : gInventory.findCategoryUUIDForType(LLFolderType::FT_MATERIAL); - create_new_item(NEW_MATERIAL_NAME, - parent_id, - LLAssetType::AT_MATERIAL, - LLInventoryType::IT_MATERIAL, - PERM_ALL, - created_cb); // overridden in create_new_item - } - else if (("sky" == type_name) || ("water" == type_name) || ("daycycle" == type_name)) - { - LLSettingsType::type_e stype(LLSettingsType::ST_NONE); - - if ("sky" == type_name) - { - stype = LLSettingsType::ST_SKY; - } - else if ("water" == type_name) - { - stype = LLSettingsType::ST_WATER; - } - else if ("daycycle" == type_name) - { - stype = LLSettingsType::ST_DAYCYCLE; - } - else - { - LL_ERRS(LOG_INV) << "Unknown settings type: '" << type_name << "'" << LL_ENDL; - return; - } - - LLUUID parent_id = dest_id.notNull() ? dest_id : gInventory.findCategoryUUIDForType(LLFolderType::FT_SETTINGS); - - LLSettingsVOBase::createNewInventoryItem(stype, parent_id, created_cb); - } - else - { - // Use for all clothing and body parts. Adding new wearable types requires updating LLWearableDictionary. - LLWearableType::EType wearable_type = LLWearableType::getInstance()->typeNameToType(type_name); - if (wearable_type >= LLWearableType::WT_SHAPE && wearable_type < LLWearableType::WT_COUNT) - { - const LLUUID parent_id = dest_id; - LLAgentWearables::createWearable(wearable_type, false, parent_id, created_cb); - } - else - { - LL_WARNS(LOG_INV) << "Can't create unrecognized type " << type_name << LL_ENDL; - } - } - if(panel) - { - panel->getRootFolder()->setNeedsAutoRename(true); - } -} - -LLAssetType::EType LLViewerInventoryItem::getType() const -{ - if (const LLViewerInventoryItem *linked_item = getLinkedItem()) - { - return linked_item->getType(); - } - if (const LLViewerInventoryCategory *linked_category = getLinkedCategory()) - { - return linked_category->getType(); - } - return LLInventoryItem::getType(); -} - -const LLUUID& LLViewerInventoryItem::getAssetUUID() const -{ - if (const LLViewerInventoryItem *linked_item = getLinkedItem()) - { - return linked_item->getAssetUUID(); - } - - return LLInventoryItem::getAssetUUID(); -} - -const LLUUID& LLViewerInventoryItem::getProtectedAssetUUID() const -{ - if (const LLViewerInventoryItem *linked_item = getLinkedItem()) - { - return linked_item->getProtectedAssetUUID(); - } - - // check for conditions under which we may return a visible UUID to the user - bool item_is_fullperm = getIsFullPerm(); - bool agent_is_godlike = gAgent.isGodlikeWithoutAdminMenuFakery(); - if (item_is_fullperm || agent_is_godlike) - { - return LLInventoryItem::getAssetUUID(); - } - - return LLUUID::null; -} - -const bool LLViewerInventoryItem::getIsFullPerm() const -{ - LLPermissions item_permissions = getPermissions(); - - // modify-ok & copy-ok & transfer-ok - return ( item_permissions.allowOperationBy(PERM_MODIFY, - gAgent.getID(), - gAgent.getGroupID()) && - item_permissions.allowOperationBy(PERM_COPY, - gAgent.getID(), - gAgent.getGroupID()) && - item_permissions.allowOperationBy(PERM_TRANSFER, - gAgent.getID(), - gAgent.getGroupID()) ); -} - -const std::string& LLViewerInventoryItem::getName() const -{ - if (const LLViewerInventoryItem *linked_item = getLinkedItem()) - { - return linked_item->getName(); - } - if (const LLViewerInventoryCategory *linked_category = getLinkedCategory()) - { - return linked_category->getName(); - } - - return LLInventoryItem::getName(); -} - -S32 LLViewerInventoryItem::getSortField() const -{ - return LLFavoritesOrderStorage::instance().getSortIndex(mUUID); -} - -//void LLViewerInventoryItem::setSortField(S32 sortField) -//{ -// LLFavoritesOrderStorage::instance().setSortIndex(mUUID, sortField); -// getSLURL(); -//} - -void LLViewerInventoryItem::getSLURL() -{ - LLFavoritesOrderStorage::instance().getSLURL(mAssetUUID); -} - -const LLPermissions& LLViewerInventoryItem::getPermissions() const -{ - // Use the actual permissions of the symlink, not its parent. - return LLInventoryItem::getPermissions(); -} - -const LLUUID& LLViewerInventoryItem::getCreatorUUID() const -{ - if (const LLViewerInventoryItem *linked_item = getLinkedItem()) - { - return linked_item->getCreatorUUID(); - } - - return LLInventoryItem::getCreatorUUID(); -} - -const std::string& LLViewerInventoryItem::getDescription() const -{ - if (const LLViewerInventoryItem *linked_item = getLinkedItem()) - { - return linked_item->getDescription(); - } - - return LLInventoryItem::getDescription(); -} - -const LLSaleInfo& LLViewerInventoryItem::getSaleInfo() const -{ - if (const LLViewerInventoryItem *linked_item = getLinkedItem()) - { - return linked_item->getSaleInfo(); - } - - return LLInventoryItem::getSaleInfo(); -} - -const LLUUID& LLViewerInventoryItem::getThumbnailUUID() const -{ - if (mThumbnailUUID.isNull() && mType == LLAssetType::AT_TEXTURE) - { - return mAssetUUID; - } - if (mThumbnailUUID.isNull() && mType == LLAssetType::AT_LINK) - { - LLViewerInventoryItem *linked_item = gInventory.getItem(mAssetUUID); - return linked_item ? linked_item->getThumbnailUUID() : LLUUID::null; - } - if (mThumbnailUUID.isNull() && mType == LLAssetType::AT_LINK_FOLDER) - { - LLViewerInventoryCategory *linked_cat = gInventory.getCategory(mAssetUUID); - return linked_cat ? linked_cat->getThumbnailUUID() : LLUUID::null; - } - return mThumbnailUUID; -} - -LLInventoryType::EType LLViewerInventoryItem::getInventoryType() const -{ - if (const LLViewerInventoryItem *linked_item = getLinkedItem()) - { - return linked_item->getInventoryType(); - } - - // Categories don't have types. If this item is an AT_FOLDER_LINK, - // treat it as a category. - if (getLinkedCategory()) - { - return LLInventoryType::IT_CATEGORY; - } - - return LLInventoryItem::getInventoryType(); -} - -U32 LLViewerInventoryItem::getFlags() const -{ - if (const LLViewerInventoryItem *linked_item = getLinkedItem()) - { - return linked_item->getFlags(); - } - return LLInventoryItem::getFlags(); -} - -bool LLViewerInventoryItem::isWearableType() const -{ - return (getInventoryType() == LLInventoryType::IT_WEARABLE); -} - -LLWearableType::EType LLViewerInventoryItem::getWearableType() const -{ - if (!isWearableType()) - { - return LLWearableType::WT_INVALID; - } - return LLWearableType::inventoryFlagsToWearableType(getFlags()); -} - -bool LLViewerInventoryItem::isSettingsType() const -{ - return (getInventoryType() == LLInventoryType::IT_SETTINGS); -} - -LLSettingsType::type_e LLViewerInventoryItem::getSettingsType() const -{ - if (!isSettingsType()) - { - return LLSettingsType::ST_NONE; - } - return LLSettingsType::fromInventoryFlags(getFlags()); -} - -time_t LLViewerInventoryItem::getCreationDate() const -{ - return LLInventoryItem::getCreationDate(); -} - -U32 LLViewerInventoryItem::getCRC32() const -{ - return LLInventoryItem::getCRC32(); -} - -// *TODO: mantipov: should be removed with LMSortPrefix patch in llinventorymodel.cpp, EXT-3985 -static char getSeparator() { return '@'; } -bool LLViewerInventoryItem::extractSortFieldAndDisplayName(const std::string& name, S32* sortField, std::string* displayName) -{ - using std::string; - using std::stringstream; - - const char separator = getSeparator(); - const string::size_type separatorPos = name.find(separator, 0); - - bool result = false; - - if (separatorPos < string::npos) - { - if (sortField) - { - /* - * The conversion from string to S32 is made this way instead of old plain - * atoi() to ensure portability. If on some other platform S32 will not be - * defined to be signed int, this conversion will still work because of - * operators overloading, but atoi() may fail. - */ - stringstream ss(name.substr(0, separatorPos)); - ss >> *sortField; - } - - if (displayName) - { - *displayName = name.substr(separatorPos + 1, string::npos); - } - - result = true; - } - - return result; -} - -// This returns true if the item that this item points to -// doesn't exist in memory (i.e. LLInventoryModel). The baseitem -// might still be in the database but just not loaded yet. -bool LLViewerInventoryItem::getIsBrokenLink() const -{ - // If the item's type resolves to be a link, that means either: - // A. It wasn't able to perform indirection, i.e. the baseobj doesn't exist in memory. - // B. It's pointing to another link, which is illegal. - return LLAssetType::lookupIsLinkType(getType()); -} - -LLViewerInventoryItem *LLViewerInventoryItem::getLinkedItem() const -{ - if (mType == LLAssetType::AT_LINK) - { - LLViewerInventoryItem *linked_item = gInventory.getItem(mAssetUUID); - if (linked_item && linked_item->getIsLinkType()) - { - LL_WARNS(LOG_INV) << "Warning: Accessing link to link" << LL_ENDL; - return NULL; - } - return linked_item; - } - return NULL; -} - -LLViewerInventoryCategory *LLViewerInventoryItem::getLinkedCategory() const -{ - if (mType == LLAssetType::AT_LINK_FOLDER) - { - LLViewerInventoryCategory *linked_category = gInventory.getCategory(mAssetUUID); - return linked_category; - } - return NULL; -} - -bool LLViewerInventoryItem::checkPermissionsSet(PermissionMask mask) const -{ - const LLPermissions& perm = getPermissions(); - PermissionMask curr_mask = PERM_NONE; - if(perm.getOwner() == gAgent.getID()) - { - curr_mask = perm.getMaskBase(); - } - else if(gAgent.isInGroup(perm.getGroup())) - { - curr_mask = perm.getMaskGroup(); - } - else - { - curr_mask = perm.getMaskEveryone(); - } - return ((curr_mask & mask) == mask); -} - -PermissionMask LLViewerInventoryItem::getPermissionMask() const -{ - const LLPermissions& permissions = getPermissions(); - - bool copy = permissions.allowCopyBy(gAgent.getID()); - bool mod = permissions.allowModifyBy(gAgent.getID()); - bool xfer = permissions.allowOperationBy(PERM_TRANSFER, gAgent.getID()); - PermissionMask perm_mask = 0; - if (copy) perm_mask |= PERM_COPY; - if (mod) perm_mask |= PERM_MODIFY; - if (xfer) perm_mask |= PERM_TRANSFER; - return perm_mask; -} - -//---------- - -void LLViewerInventoryItem::onCallingCardNameLookup(const LLUUID& id, const LLAvatarName& name) -{ - rename(name.getUserName()); - gInventory.addChangedMask(LLInventoryObserver::LABEL, getUUID()); - gInventory.notifyObservers(); -} - -class LLRegenerateLinkCollector : public LLInventoryCollectFunctor -{ -public: - LLRegenerateLinkCollector(const LLViewerInventoryItem *target_item) : mTargetItem(target_item) {} - virtual ~LLRegenerateLinkCollector() {} - virtual bool operator()(LLInventoryCategory* cat, - LLInventoryItem* item) - { - if (item) - { - if ((item->getName() == mTargetItem->getName()) && - (item->getInventoryType() == mTargetItem->getInventoryType()) && - (!item->getIsLinkType())) - { - return true; - } - } - return false; - } -protected: - const LLViewerInventoryItem* mTargetItem; -}; - -LLUUID find_possible_item_for_regeneration(const LLViewerInventoryItem *target_item) -{ - LLViewerInventoryCategory::cat_array_t cats; - LLViewerInventoryItem::item_array_t items; - - LLRegenerateLinkCollector candidate_matches(target_item); - gInventory.collectDescendentsIf(gInventory.getRootFolderID(), - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - candidate_matches); - for (LLViewerInventoryItem::item_array_t::const_iterator item_iter = items.begin(); - item_iter != items.end(); - ++item_iter) - { - const LLViewerInventoryItem *item = (*item_iter); - if (true) return item->getUUID(); - } - return LLUUID::null; -} - -// This currently dosen't work, because the sim does not allow us -// to change an item's assetID. -bool LLViewerInventoryItem::regenerateLink() -{ - const LLUUID target_item_id = find_possible_item_for_regeneration(this); - if (target_item_id.isNull()) - return false; - LLViewerInventoryCategory::cat_array_t cats; - LLViewerInventoryItem::item_array_t items; - LLAssetIDMatches asset_id_matches(getAssetUUID()); - gInventory.collectDescendentsIf(gInventory.getRootFolderID(), - cats, - items, - LLInventoryModel::EXCLUDE_TRASH, - asset_id_matches); - for (LLViewerInventoryItem::item_array_t::iterator item_iter = items.begin(); - item_iter != items.end(); - item_iter++) - { - LLViewerInventoryItem *item = (*item_iter); - item->setAssetUUID(target_item_id); - item->updateServer(false); - gInventory.addChangedMask(LLInventoryObserver::REBUILD, item->getUUID()); - } - gInventory.notifyObservers(); - return true; -} +/** + * @file llviewerinventory.cpp + * @brief Implementation of the viewer side inventory objects. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llviewerinventory.h" + +#include "llnotificationsutil.h" +#include "llsdserialize.h" +#include "message.h" + +#include "llaisapi.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llagentwearables.h" +#include "llfloatersidepanelcontainer.h" +#include "llviewerfoldertype.h" +#include "llfloatersidepanelcontainer.h" +#include "llfolderview.h" +#include "llviewercontrol.h" +#include "llconsole.h" +#include "llinventorydefines.h" +#include "llinventoryfunctions.h" +#include "llinventorymodel.h" +#include "llinventorymodelbackgroundfetch.h" +#include "llgesturemgr.h" + +#include "llinventorybridge.h" +#include "llinventorypanel.h" +#include "lllandmarkactions.h" + +#include "llviewerassettype.h" +#include "llviewerregion.h" +#include "llviewerobjectlist.h" +#include "llpreviewgesture.h" +#include "llviewerwindow.h" +#include "lltrans.h" +#include "llappearancemgr.h" +#include "llcommandhandler.h" +#include "llviewermessage.h" +#include "llpanelmaininventory.h" +#include "llsidepanelappearance.h" +#include "llsidepanelinventory.h" +#include "llavatarnamecache.h" +#include "llavataractions.h" +#include "lllogininstance.h" +#include "llfavoritesbar.h" +#include "llfloaterperms.h" +#include "llclipboard.h" +#include "llhttpretrypolicy.h" +#include "llsettingsvo.h" + +// do-nothing ops for use in callbacks. +void no_op_inventory_func(const LLUUID&) {} +void no_op_llsd_func(const LLSD&) {} +void no_op() {} + +static const char * const LOG_INV("Inventory"); +static const char * const LOG_LOCAL("InventoryLocalize"); +static const char * const LOG_NOTECARD("copy_inventory_from_notecard"); + +static const std::string INV_OWNER_ID("owner_id"); +static const std::string INV_VERSION("version"); + +#if 1 +// *TODO$: LLInventoryCallback should be deprecated to conform to the new boost::bind/coroutine model. +// temp code in transition +void doInventoryCb(LLPointer cb, LLUUID id) +{ + if (cb.notNull()) + cb->fire(id); +} +#endif + +///---------------------------------------------------------------------------- +/// Helper class to store special inventory item names and their localized values. +///---------------------------------------------------------------------------- +class LLLocalizedInventoryItemsDictionary : public LLSingleton +{ + LLSINGLETON(LLLocalizedInventoryItemsDictionary); +public: + std::map mInventoryItemsDict; + + /** + * Finds passed name in dictionary and replaces it with found localized value. + * + * @param object_name - string to be localized. + * @return true if passed name was found and localized, false otherwise. + */ + bool localizeInventoryObjectName(std::string& object_name) + { + LL_DEBUGS(LOG_LOCAL) << "Searching for localization: " << object_name << LL_ENDL; + + std::map::const_iterator dictionary_iter = mInventoryItemsDict.find(object_name); + + bool found = dictionary_iter != mInventoryItemsDict.end(); + if(found) + { + object_name = dictionary_iter->second; + LL_DEBUGS(LOG_LOCAL) << "Found, new name is: " << object_name << LL_ENDL; + } + return found; + } +}; + +LLLocalizedInventoryItemsDictionary::LLLocalizedInventoryItemsDictionary() +{ + mInventoryItemsDict["New Shape"] = LLTrans::getString("New Shape"); + mInventoryItemsDict["New Skin"] = LLTrans::getString("New Skin"); + mInventoryItemsDict["New Hair"] = LLTrans::getString("New Hair"); + mInventoryItemsDict["New Eyes"] = LLTrans::getString("New Eyes"); + mInventoryItemsDict["New Shirt"] = LLTrans::getString("New Shirt"); + mInventoryItemsDict["New Pants"] = LLTrans::getString("New Pants"); + mInventoryItemsDict["New Shoes"] = LLTrans::getString("New Shoes"); + mInventoryItemsDict["New Socks"] = LLTrans::getString("New Socks"); + mInventoryItemsDict["New Jacket"] = LLTrans::getString("New Jacket"); + mInventoryItemsDict["New Gloves"] = LLTrans::getString("New Gloves"); + mInventoryItemsDict["New Undershirt"] = LLTrans::getString("New Undershirt"); + mInventoryItemsDict["New Underpants"] = LLTrans::getString("New Underpants"); + mInventoryItemsDict["New Skirt"] = LLTrans::getString("New Skirt"); + mInventoryItemsDict["New Alpha"] = LLTrans::getString("New Alpha"); + mInventoryItemsDict["New Tattoo"] = LLTrans::getString("New Tattoo"); + mInventoryItemsDict["New Universal"] = LLTrans::getString("New Universal"); + mInventoryItemsDict["New Physics"] = LLTrans::getString("New Physics"); + mInventoryItemsDict["Invalid Wearable"] = LLTrans::getString("Invalid Wearable"); + + mInventoryItemsDict["New Gesture"] = LLTrans::getString("New Gesture"); + mInventoryItemsDict["New Material"] = LLTrans::getString("New Material"); + mInventoryItemsDict["New Script"] = LLTrans::getString("New Script"); + mInventoryItemsDict["New Folder"] = LLTrans::getString("New Folder"); + mInventoryItemsDict["New Note"] = LLTrans::getString("New Note"); + mInventoryItemsDict["Contents"] = LLTrans::getString("Contents"); + + mInventoryItemsDict["Gesture"] = LLTrans::getString("Gesture"); + mInventoryItemsDict["Male Gestures"] = LLTrans::getString("Male Gestures"); + mInventoryItemsDict["Female Gestures"] = LLTrans::getString("Female Gestures"); + mInventoryItemsDict["Other Gestures"] = LLTrans::getString("Other Gestures"); + mInventoryItemsDict["Speech Gestures"] = LLTrans::getString("Speech Gestures"); + mInventoryItemsDict["Common Gestures"] = LLTrans::getString("Common Gestures"); + + //predefined gestures + + //male + mInventoryItemsDict["Male - Excuse me"] = LLTrans::getString("Male - Excuse me"); + mInventoryItemsDict["Male - Get lost"] = LLTrans::getString("Male - Get lost"); // double space after Male. EXT-8319 + mInventoryItemsDict["Male - Blow kiss"] = LLTrans::getString("Male - Blow kiss"); + mInventoryItemsDict["Male - Boo"] = LLTrans::getString("Male - Boo"); + mInventoryItemsDict["Male - Bored"] = LLTrans::getString("Male - Bored"); + mInventoryItemsDict["Male - Hey"] = LLTrans::getString("Male - Hey"); + mInventoryItemsDict["Male - Laugh"] = LLTrans::getString("Male - Laugh"); + mInventoryItemsDict["Male - Repulsed"] = LLTrans::getString("Male - Repulsed"); + mInventoryItemsDict["Male - Shrug"] = LLTrans::getString("Male - Shrug"); + mInventoryItemsDict["Male - Stick tougue out"] = LLTrans::getString("Male - Stick tougue out"); + mInventoryItemsDict["Male - Wow"] = LLTrans::getString("Male - Wow"); + + //female + mInventoryItemsDict["Female - Chuckle"] = LLTrans::getString("Female - Chuckle"); + mInventoryItemsDict["Female - Cry"] = LLTrans::getString("Female - Cry"); + mInventoryItemsDict["Female - Embarrassed"] = LLTrans::getString("Female - Embarrassed"); + mInventoryItemsDict["Female - Excuse me"] = LLTrans::getString("Female - Excuse me"); + mInventoryItemsDict["Female - Get lost"] = LLTrans::getString("Female - Get lost"); // double space after Female. EXT-8319 + mInventoryItemsDict["Female - Blow kiss"] = LLTrans::getString("Female - Blow kiss"); + mInventoryItemsDict["Female - Boo"] = LLTrans::getString("Female - Boo"); + mInventoryItemsDict["Female - Bored"] = LLTrans::getString("Female - Bored"); + mInventoryItemsDict["Female - Hey"] = LLTrans::getString("Female - Hey"); + mInventoryItemsDict["Female - Hey baby"] = LLTrans::getString("Female - Hey baby"); + mInventoryItemsDict["Female - Laugh"] = LLTrans::getString("Female - Laugh"); + mInventoryItemsDict["Female - Looking good"] = LLTrans::getString("Female - Looking good"); + mInventoryItemsDict["Female - Over here"] = LLTrans::getString("Female - Over here"); + mInventoryItemsDict["Female - Please"] = LLTrans::getString("Female - Please"); + mInventoryItemsDict["Female - Repulsed"] = LLTrans::getString("Female - Repulsed"); + mInventoryItemsDict["Female - Shrug"] = LLTrans::getString("Female - Shrug"); + mInventoryItemsDict["Female - Stick tougue out"]= LLTrans::getString("Female - Stick tougue out"); + mInventoryItemsDict["Female - Wow"] = LLTrans::getString("Female - Wow"); + + //common + mInventoryItemsDict["/bow"] = LLTrans::getString("/bow"); + mInventoryItemsDict["/clap"] = LLTrans::getString("/clap"); + mInventoryItemsDict["/count"] = LLTrans::getString("/count"); + mInventoryItemsDict["/extinguish"] = LLTrans::getString("/extinguish"); + mInventoryItemsDict["/kmb"] = LLTrans::getString("/kmb"); + mInventoryItemsDict["/muscle"] = LLTrans::getString("/muscle"); + mInventoryItemsDict["/no"] = LLTrans::getString("/no"); + mInventoryItemsDict["/no!"] = LLTrans::getString("/no!"); + mInventoryItemsDict["/paper"] = LLTrans::getString("/paper"); + mInventoryItemsDict["/pointme"] = LLTrans::getString("/pointme"); + mInventoryItemsDict["/pointyou"] = LLTrans::getString("/pointyou"); + mInventoryItemsDict["/rock"] = LLTrans::getString("/rock"); + mInventoryItemsDict["/scissor"] = LLTrans::getString("/scissor"); + mInventoryItemsDict["/smoke"] = LLTrans::getString("/smoke"); + mInventoryItemsDict["/stretch"] = LLTrans::getString("/stretch"); + mInventoryItemsDict["/whistle"] = LLTrans::getString("/whistle"); + mInventoryItemsDict["/yes"] = LLTrans::getString("/yes"); + mInventoryItemsDict["/yes!"] = LLTrans::getString("/yes!"); + mInventoryItemsDict["afk"] = LLTrans::getString("afk"); + mInventoryItemsDict["dance1"] = LLTrans::getString("dance1"); + mInventoryItemsDict["dance2"] = LLTrans::getString("dance2"); + mInventoryItemsDict["dance3"] = LLTrans::getString("dance3"); + mInventoryItemsDict["dance4"] = LLTrans::getString("dance4"); + mInventoryItemsDict["dance5"] = LLTrans::getString("dance5"); + mInventoryItemsDict["dance6"] = LLTrans::getString("dance6"); + mInventoryItemsDict["dance7"] = LLTrans::getString("dance7"); + mInventoryItemsDict["dance8"] = LLTrans::getString("dance8"); +} + +///---------------------------------------------------------------------------- +/// Local function declarations, constants, enums, and typedefs +///---------------------------------------------------------------------------- + +class LLInventoryHandler : public LLCommandHandler +{ +public: + LLInventoryHandler() : LLCommandHandler("inventory", UNTRUSTED_THROTTLE) { } + + virtual bool canHandleUntrusted( + const LLSD& params, + const LLSD& query_map, + LLMediaCtrl* web, + const std::string& nav_type) + { + if (params.size() < 1) + { + return true; // don't block, will fail later + } + + if (nav_type == NAV_TYPE_CLICKED + || nav_type == NAV_TYPE_EXTERNAL) + { + // NAV_TYPE_EXTERNAL will be throttled + return true; + } + + return false; + } + + bool handle(const LLSD& params, + const LLSD& query_map, + const std::string& grid, + LLMediaCtrl* web) + { + if (params.size() < 1) + { + return false; + } + + if (!LLUI::getInstance()->mSettingGroups["config"]->getBOOL("EnableInventory")) + { + LLNotificationsUtil::add("NoInventory", LLSD(), LLSD(), std::string("SwitchToStandardSkinAndQuit")); + return true; + } + + // support secondlife:///app/inventory/show + if (params[0].asString() == "show") + { + LLFloaterSidePanelContainer::showPanel("inventory", LLSD()); + return true; + } + + if (params[0].asString() == "filters") + { + LLSidepanelInventory *sidepanel_inventory = LLFloaterSidePanelContainer::getPanel("inventory"); + if (sidepanel_inventory) + { + LLPanelMainInventory* main_inventory = sidepanel_inventory->getMainInventoryPanel(); + if (main_inventory) + { + main_inventory->toggleFindOptions(); + } + } + return true; + } + + // otherwise, we need a UUID and a verb... + if (params.size() < 2) + { + return false; + } + LLUUID inventory_id; + if (!inventory_id.set(params[0], false)) + { + return false; + } + + const std::string verb = params[1].asString(); + if (verb == "select") + { + uuid_vec_t items_to_open; + items_to_open.push_back(inventory_id); + //inventory_handler is just a stub, because we don't know from who this offer + open_inventory_offer(items_to_open, "inventory_handler"); + return true; + } + + return false; + } +}; +LLInventoryHandler gInventoryHandler; + +///---------------------------------------------------------------------------- +/// Class LLViewerInventoryItem +///---------------------------------------------------------------------------- + +LLViewerInventoryItem::LLViewerInventoryItem(const LLUUID& uuid, + const LLUUID& parent_uuid, + const LLPermissions& perm, + const LLUUID& asset_uuid, + LLAssetType::EType type, + LLInventoryType::EType inv_type, + const std::string& name, + const std::string& desc, + const LLSaleInfo& sale_info, + U32 flags, + time_t creation_date_utc) : + LLInventoryItem(uuid, parent_uuid, perm, asset_uuid, type, inv_type, + name, desc, sale_info, flags, creation_date_utc), + mIsComplete(true) +{ +} + +LLViewerInventoryItem::LLViewerInventoryItem(const LLUUID& item_id, + const LLUUID& parent_id, + const std::string& name, + LLInventoryType::EType inv_type) : + LLInventoryItem(), + mIsComplete(false) +{ + mUUID = item_id; + mParentUUID = parent_id; + mInventoryType = inv_type; + mName = name; +} + +LLViewerInventoryItem::LLViewerInventoryItem() : + LLInventoryItem(), + mIsComplete(false) +{ +} + +LLViewerInventoryItem::LLViewerInventoryItem(const LLViewerInventoryItem* other) : + LLInventoryItem() +{ + copyViewerItem(other); + if (!mIsComplete) + { + LL_WARNS(LOG_INV) << "LLViewerInventoryItem copy constructor for incomplete item" + << mUUID << LL_ENDL; + } +} + +LLViewerInventoryItem::LLViewerInventoryItem(const LLInventoryItem *other) : + LLInventoryItem(other), + mIsComplete(true) +{ +} + + +LLViewerInventoryItem::~LLViewerInventoryItem() +{ +} + +void LLViewerInventoryItem::copyViewerItem(const LLViewerInventoryItem* other) +{ + LLInventoryItem::copyItem(other); + mIsComplete = other->mIsComplete; + mTransactionID = other->mTransactionID; +} + +// virtual +void LLViewerInventoryItem::copyItem(const LLInventoryItem *other) +{ + LLInventoryItem::copyItem(other); + mIsComplete = true; + mTransactionID.setNull(); +} + +void LLViewerInventoryItem::cloneViewerItem(LLPointer& newitem) const +{ + newitem = new LLViewerInventoryItem(this); + if(newitem.notNull()) + { + LLUUID item_id; + item_id.generate(); + newitem->setUUID(item_id); + } +} + +void LLViewerInventoryItem::updateServer(bool is_new) const +{ + if(!mIsComplete) + { + // *FIX: deal with this better. + // If we're crashing here then the UI is incorrectly enabled. + LL_ERRS(LOG_INV) << "LLViewerInventoryItem::updateServer() - for incomplete item" + << LL_ENDL; + return; + } + if(gAgent.getID() != mPermissions.getOwner()) + { + // *FIX: deal with this better. + LL_WARNS(LOG_INV) << "LLViewerInventoryItem::updateServer() - for unowned item " + << ll_pretty_print_sd(this->asLLSD()) + << LL_ENDL; + return; + } + LLInventoryModel::LLCategoryUpdate up(mParentUUID, is_new ? 1 : 0); + gInventory.accountForUpdate(up); + + LLSD updates = asLLSD(); + // Replace asset_id and/or shadow_id with transaction_id (hash_id) + if (updates.has("asset_id")) + { + updates.erase("asset_id"); + if(getTransactionID().notNull()) + { + updates["hash_id"] = getTransactionID(); + } + } + if (updates.has("shadow_id")) + { + updates.erase("shadow_id"); + if(getTransactionID().notNull()) + { + updates["hash_id"] = getTransactionID(); + } + } + AISAPI::completion_t cr = boost::bind(&doInventoryCb, (LLPointer)NULL, _1); + AISAPI::UpdateItem(getUUID(), updates, cr); +} + +void LLViewerInventoryItem::fetchFromServer(void) const +{ + if(!mIsComplete) + { + if (AISAPI::isAvailable()) // AIS v 3 + { + LLInventoryModelBackgroundFetch::getInstance()->scheduleItemFetch(mUUID); + } + else + { + std::string url; + + LLViewerRegion* region = gAgent.getRegion(); + // we have to check region. It can be null after region was destroyed. See EXT-245 + if (region) + { + if (gAgent.getID() != mPermissions.getOwner()) + { + url = region->getCapability("FetchLib2"); + } + else + { + url = region->getCapability("FetchInventory2"); + } + } + else + { + LL_WARNS(LOG_INV) << "Agent Region is absent" << LL_ENDL; + } + + if (!url.empty()) + { + LLSD body; + body["agent_id"] = gAgent.getID(); + body["items"][0]["owner_id"] = mPermissions.getOwner(); + body["items"][0]["item_id"] = mUUID; + + LLCore::HttpHandler::ptr_t handler(new LLInventoryModel::FetchItemHttpHandler(body)); + gInventory.requestPost(true, url, body, handler, "Inventory Item"); + } + } + } +} + +// virtual +bool LLViewerInventoryItem::unpackMessage(const LLSD& item) +{ + bool rv = LLInventoryItem::fromLLSD(item); + + LLLocalizedInventoryItemsDictionary::getInstance()->localizeInventoryObjectName(mName); + + mIsComplete = true; + return rv; +} + +// virtual +bool LLViewerInventoryItem::unpackMessage(LLMessageSystem* msg, const char* block, S32 block_num) +{ + bool rv = LLInventoryItem::unpackMessage(msg, block, block_num); + + LLLocalizedInventoryItemsDictionary::getInstance()->localizeInventoryObjectName(mName); + + mIsComplete = true; + return rv; +} + +void LLViewerInventoryItem::setTransactionID(const LLTransactionID& transaction_id) +{ + mTransactionID = transaction_id; +} + +void LLViewerInventoryItem::packMessage(LLMessageSystem* msg) const +{ + msg->addUUIDFast(_PREHASH_ItemID, mUUID); + msg->addUUIDFast(_PREHASH_FolderID, mParentUUID); + mPermissions.packMessage(msg); + msg->addUUIDFast(_PREHASH_TransactionID, mTransactionID); + S8 type = static_cast(mType); + msg->addS8Fast(_PREHASH_Type, type); + type = static_cast(mInventoryType); + msg->addS8Fast(_PREHASH_InvType, type); + msg->addU32Fast(_PREHASH_Flags, mFlags); + mSaleInfo.packMessage(msg); + msg->addStringFast(_PREHASH_Name, mName); + msg->addStringFast(_PREHASH_Description, mDescription); + msg->addS32Fast(_PREHASH_CreationDate, mCreationDate); + U32 crc = getCRC32(); + msg->addU32Fast(_PREHASH_CRC, crc); +} + +// virtual +bool LLViewerInventoryItem::importLegacyStream(std::istream& input_stream) +{ + bool rv = LLInventoryItem::importLegacyStream(input_stream); + mIsComplete = true; + return rv; +} + +void LLViewerInventoryItem::updateParentOnServer(bool restamp) const +{ + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_MoveInventoryItem); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addBOOLFast(_PREHASH_Stamp, restamp); + msg->nextBlockFast(_PREHASH_InventoryData); + msg->addUUIDFast(_PREHASH_ItemID, mUUID); + msg->addUUIDFast(_PREHASH_FolderID, mParentUUID); + msg->addString("NewName", NULL); + gAgent.sendReliableMessage(); +} + +//void LLViewerInventoryItem::setCloneCount(S32 clones) +//{ +// mClones = clones; +//} + +//S32 LLViewerInventoryItem::getCloneCount() const +//{ +// return mClones; +//} + +///---------------------------------------------------------------------------- +/// Class LLViewerInventoryCategory +///---------------------------------------------------------------------------- + +LLViewerInventoryCategory::LLViewerInventoryCategory(const LLUUID& uuid, + const LLUUID& parent_uuid, + LLFolderType::EType pref, + const std::string& name, + const LLUUID& owner_id) : + LLInventoryCategory(uuid, parent_uuid, pref, name), + mOwnerID(owner_id), + mVersion(LLViewerInventoryCategory::VERSION_UNKNOWN), + mDescendentCount(LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN), + mFetching(FETCH_NONE) +{ + mDescendentsRequested.reset(); +} + +LLViewerInventoryCategory::LLViewerInventoryCategory(const LLUUID& owner_id) : + mOwnerID(owner_id), + mVersion(LLViewerInventoryCategory::VERSION_UNKNOWN), + mDescendentCount(LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN), + mFetching(FETCH_NONE) +{ + mDescendentsRequested.reset(); +} + +LLViewerInventoryCategory::LLViewerInventoryCategory(const LLViewerInventoryCategory* other) +{ + copyViewerCategory(other); + mFetching = FETCH_NONE; +} + +LLViewerInventoryCategory::~LLViewerInventoryCategory() +{ +} + +void LLViewerInventoryCategory::copyViewerCategory(const LLViewerInventoryCategory* other) +{ + copyCategory(other); + mOwnerID = other->mOwnerID; + setVersion(other->getVersion()); + mDescendentCount = other->mDescendentCount; + mDescendentsRequested = other->mDescendentsRequested; +} + + +void LLViewerInventoryCategory::packMessage(LLMessageSystem* msg) const +{ + msg->addUUIDFast(_PREHASH_FolderID, mUUID); + msg->addUUIDFast(_PREHASH_ParentID, mParentUUID); + S8 type = static_cast(mPreferredType); + msg->addS8Fast(_PREHASH_Type, type); + msg->addStringFast(_PREHASH_Name, mName); +} + +void LLViewerInventoryCategory::updateParentOnServer(bool restamp) const +{ + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_MoveInventoryFolder); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + + msg->addBOOL("Stamp", restamp); + msg->nextBlockFast(_PREHASH_InventoryData); + msg->addUUIDFast(_PREHASH_FolderID, mUUID); + msg->addUUIDFast(_PREHASH_ParentID, mParentUUID); + gAgent.sendReliableMessage(); +} + +void LLViewerInventoryCategory::updateServer(bool is_new) const +{ + // communicate that change with the server. + + if (LLFolderType::lookupIsProtectedType(mPreferredType)) + { + LLNotificationsUtil::add("CannotModifyProtectedCategories"); + return; + } + + LLSD new_llsd = asLLSD(); + AISAPI::completion_t cr = boost::bind(&doInventoryCb, (LLPointer)NULL, _1); + AISAPI::UpdateCategory(getUUID(), new_llsd, cr); +} + +S32 LLViewerInventoryCategory::getVersion() const +{ + return mVersion; +} + +void LLViewerInventoryCategory::setVersion(S32 version) +{ + mVersion = version; +} + +bool LLViewerInventoryCategory::fetch(S32 expiry_seconds) +{ + if((VERSION_UNKNOWN == getVersion()) + && mDescendentsRequested.hasExpired()) //Expired check prevents multiple downloads. + { + LL_DEBUGS(LOG_INV) << "Fetching category children: " << mName << ", UUID: " << mUUID << LL_ENDL; + mDescendentsRequested.reset(); + mDescendentsRequested.setTimerExpirySec(expiry_seconds); + + std::string url; + if (gAgent.getRegion()) + { + url = gAgent.getRegion()->getCapability("FetchInventoryDescendents2"); + } + else + { + LL_WARNS_ONCE(LOG_INV) << "agent region is null" << LL_ENDL; + } + if (!url.empty() || AISAPI::isAvailable()) + { + LLInventoryModelBackgroundFetch::instance().start(mUUID, false); + } + return true; + } + return false; +} + +LLViewerInventoryCategory::EFetchType LLViewerInventoryCategory::getFetching() +{ + // if timer hasn't expired, request was scheduled, but not in progress + // if mFetching request was actually started + if (mDescendentsRequested.hasExpired()) + { + mFetching = FETCH_NONE; + } + return mFetching; +} + +void LLViewerInventoryCategory::setFetching(LLViewerInventoryCategory::EFetchType fetching) +{ + if (fetching == FETCH_FAILED) + { + const F32 FETCH_FAILURE_EXPIRY = 60.0f; + mDescendentsRequested.setTimerExpirySec(FETCH_FAILURE_EXPIRY); + mFetching = fetching; + } + else if (fetching > mFetching) // allow a switch from normal to recursive + { + if (mDescendentsRequested.hasExpired() || (mFetching == FETCH_NONE)) + { + mDescendentsRequested.reset(); + if (AISAPI::isAvailable()) + { + mDescendentsRequested.setTimerExpirySec(AISAPI::HTTP_TIMEOUT); + } + else + { + const F32 FETCH_TIMER_EXPIRY = 30.0f; + mDescendentsRequested.setTimerExpirySec(FETCH_TIMER_EXPIRY); + } + } + mFetching = fetching; + } + else if (fetching == FETCH_NONE) + { + mDescendentsRequested.stop(); + mFetching = fetching; + } +} + +S32 LLViewerInventoryCategory::getViewerDescendentCount() const +{ + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(getUUID(), cats, items); + S32 descendents_actual = 0; + if(cats && items) + { + descendents_actual = cats->size() + items->size(); + } + return descendents_actual; +} + +LLSD LLViewerInventoryCategory::exportLLSD() const +{ + LLSD cat_data = LLInventoryCategory::exportLLSD(); + cat_data[INV_OWNER_ID] = mOwnerID; + cat_data[INV_VERSION] = mVersion; + + return cat_data; +} + +bool LLViewerInventoryCategory::importLLSD(const LLSD& cat_data) +{ + LLInventoryCategory::importLLSD(cat_data); + if (cat_data.has(INV_OWNER_ID)) + { + mOwnerID = cat_data[INV_OWNER_ID].asUUID(); + } + if (cat_data.has(INV_VERSION)) + { + setVersion(cat_data[INV_VERSION].asInteger()); + } + return true; +} + +bool LLViewerInventoryCategory::acceptItem(LLInventoryItem* inv_item) +{ + if (!inv_item) + { + return false; + } + + // Only stock folders have limitation on which item they will accept + bool accept = true; + if (getPreferredType() == LLFolderType::FT_MARKETPLACE_STOCK) + { + // If the item is copyable (i.e. non stock) do not accept the drop in a stock folder + if (inv_item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID(), gAgent.getGroupID())) + { + accept = false; + } + else + { + LLInventoryModel::cat_array_t* cat_array; + LLInventoryModel::item_array_t* item_array; + gInventory.getDirectDescendentsOf(getUUID(),cat_array,item_array); + // Destination stock folder must be empty OR types of incoming and existing items must be identical and have the same permissions + accept = (!item_array->size() || + ((item_array->at(0)->getInventoryType() == inv_item->getInventoryType()) && + (item_array->at(0)->getPermissions().getMaskNextOwner() == inv_item->getPermissions().getMaskNextOwner()))); + } + } + return accept; +} + +void LLViewerInventoryCategory::determineFolderType() +{ + /* Do NOT uncomment this code. This is for future 2.1 support of ensembles. + llassert(false); + LLFolderType::EType original_type = getPreferredType(); + if (LLFolderType::lookupIsProtectedType(original_type)) + return; + + U64 folder_valid = 0; + U64 folder_invalid = 0; + LLInventoryModel::cat_array_t category_array; + LLInventoryModel::item_array_t item_array; + gInventory.collectDescendents(getUUID(),category_array,item_array,false); + + // For ensembles + if (category_array.empty()) + { + for (LLInventoryModel::item_array_t::iterator item_iter = item_array.begin(); + item_iter != item_array.end(); + item_iter++) + { + const LLViewerInventoryItem *item = (*item_iter); + if (item->getIsLinkType()) + return; + if (item->isWearableType()) + { + const LLWearableType::EType wearable_type = item->getWearableType(); + const std::string& wearable_name = LLWearableType::getTypeName(wearable_type); + U64 valid_folder_types = LLViewerFolderType::lookupValidFolderTypes(wearable_name); + folder_valid |= valid_folder_types; + folder_invalid |= ~valid_folder_types; + } + } + for (U8 i = LLFolderType::FT_ENSEMBLE_START; i <= LLFolderType::FT_ENSEMBLE_END; i++) + { + if ((folder_valid & (1LL << i)) && + !(folder_invalid & (1LL << i))) + { + changeType((LLFolderType::EType)i); + return; + } + } + } + if (LLFolderType::lookupIsEnsembleType(original_type)) + { + changeType(LLFolderType::FT_NONE); + } + llassert(false); + */ +} + +void LLViewerInventoryCategory::changeType(LLFolderType::EType new_folder_type) +{ + const LLUUID &folder_id = getUUID(); + const LLUUID &parent_id = getParentUUID(); + const std::string &name = getName(); + + LLPointer new_cat = new LLViewerInventoryCategory(folder_id, + parent_id, + new_folder_type, + name, + gAgent.getID()); + + + LLSD new_llsd = new_cat->asLLSD(); + AISAPI::completion_t cr = boost::bind(&doInventoryCb, (LLPointer) NULL, _1); + AISAPI::UpdateCategory(folder_id, new_llsd, cr); + + setPreferredType(new_folder_type); + gInventory.addChangedMask(LLInventoryObserver::LABEL, folder_id); +} + +void LLViewerInventoryCategory::localizeName() +{ + LLLocalizedInventoryItemsDictionary::getInstance()->localizeInventoryObjectName(mName); +} + +// virtual +bool LLViewerInventoryCategory::unpackMessage(const LLSD& category) +{ + bool rv = LLInventoryCategory::fromLLSD(category); + localizeName(); + return rv; +} + +// virtual +void LLViewerInventoryCategory::unpackMessage(LLMessageSystem* msg, const char* block, S32 block_num) +{ + LLInventoryCategory::unpackMessage(msg, block, block_num); + localizeName(); +} + +///---------------------------------------------------------------------------- +/// Local function definitions +///---------------------------------------------------------------------------- + +LLInventoryCallbackManager *LLInventoryCallbackManager::sInstance = NULL; + +LLInventoryCallbackManager::LLInventoryCallbackManager() : + mLastCallback(0) +{ + if( sInstance != NULL ) + { + LL_WARNS(LOG_INV) << "LLInventoryCallbackManager::LLInventoryCallbackManager: unexpected multiple instances" << LL_ENDL; + return; + } + sInstance = this; +} + +LLInventoryCallbackManager::~LLInventoryCallbackManager() +{ + if( sInstance != this ) + { + LL_WARNS(LOG_INV) << "LLInventoryCallbackManager::~LLInventoryCallbackManager: unexpected multiple instances" << LL_ENDL; + return; + } + sInstance = NULL; +} + +//static +void LLInventoryCallbackManager::destroyClass() +{ + if (sInstance) + { + for (callback_map_t::iterator it = sInstance->mMap.begin(), end_it = sInstance->mMap.end(); it != end_it; ++it) + { + // drop LLPointer reference to callback + it->second = NULL; + } + sInstance->mMap.clear(); + } +} + + +U32 LLInventoryCallbackManager::registerCB(LLPointer cb) +{ + if (cb.isNull()) + return 0; + + mLastCallback++; + if (!mLastCallback) + mLastCallback++; + + mMap[mLastCallback] = cb; + return mLastCallback; +} + +void LLInventoryCallbackManager::fire(U32 callback_id, const LLUUID& item_id) +{ + if (!callback_id || item_id.isNull()) + return; + + std::map >::iterator i; + + i = mMap.find(callback_id); + if (i != mMap.end()) + { + (*i).second->fire(item_id); + mMap.erase(i); + } +} + +void rez_attachment_cb(const LLUUID& inv_item, LLViewerJointAttachment *attachmentp) +{ + if (inv_item.isNull()) + return; + + LLViewerInventoryItem *item = gInventory.getItem(inv_item); + if (item) + { + rez_attachment(item, attachmentp); + } +} + +void activate_gesture_cb(const LLUUID& inv_item) +{ + if (inv_item.isNull()) + return; + LLViewerInventoryItem* item = gInventory.getItem(inv_item); + if (!item) + return; + if (item->getType() != LLAssetType::AT_GESTURE) + return; + + LLGestureMgr::instance().activateGesture(inv_item); +} + +void set_default_permissions(LLViewerInventoryItem* item, std::string perm_type) +{ + llassert(item); + LLPermissions perm = item->getPermissions(); + if (perm.getMaskEveryone() != LLFloaterPerms::getEveryonePerms(perm_type) + || perm.getMaskGroup() != LLFloaterPerms::getGroupPerms(perm_type)) + { + perm.setMaskEveryone(LLFloaterPerms::getEveryonePerms(perm_type)); + perm.setMaskGroup(LLFloaterPerms::getGroupPerms(perm_type)); + + item->setPermissions(perm); + + item->updateServer(false); + } +} + +void create_script_cb(const LLUUID& inv_item) +{ + if (!inv_item.isNull()) + { + LLViewerInventoryItem* item = gInventory.getItem(inv_item); + if (item) + { + set_default_permissions(item, "Scripts"); + + // item was just created, update even if permissions did not changed + gInventory.updateItem(item); + gInventory.notifyObservers(); + } + } +} + +void create_gesture_cb(const LLUUID& inv_item) +{ + if (!inv_item.isNull()) + { + LLGestureMgr::instance().activateGesture(inv_item); + + LLViewerInventoryItem* item = gInventory.getItem(inv_item); + if (item) + { + set_default_permissions(item, "Gestures"); + + gInventory.updateItem(item); + gInventory.notifyObservers(); + + LLPreviewGesture* preview = LLPreviewGesture::show(inv_item, LLUUID::null); + // Force to be entirely onscreen. + gFloaterView->adjustToFitScreen(preview, false); + } + } +} + + +void create_notecard_cb(const LLUUID& inv_item) +{ + if (!inv_item.isNull()) + { + LLViewerInventoryItem* item = gInventory.getItem(inv_item); + if (item) + { + set_default_permissions(item, "Notecards"); + + gInventory.updateItem(item); + gInventory.notifyObservers(); + } + } +} + +void create_gltf_material_cb(const LLUUID& inv_item) +{ + if (!inv_item.isNull()) + { + LLViewerInventoryItem* item = gInventory.getItem(inv_item); + if (item) + { + set_default_permissions(item, "Materials"); + + gInventory.updateItem(item); + gInventory.notifyObservers(); + } + } +} + +LLInventoryCallbackManager gInventoryCallbacks; + +void create_inventory_item( + const LLUUID& agent_id, + const LLUUID& session_id, + const LLUUID& parent_id, + const LLTransactionID& transaction_id, + const std::string& name, + const std::string& desc, + LLAssetType::EType asset_type, + LLInventoryType::EType inv_type, + U8 subtype, + U32 next_owner_perm, + LLPointer cb) +{ + //check if name is equal to one of special inventory items names + //EXT-5839 + std::string server_name = name; + + { + std::map::const_iterator dictionary_iter; + + for (dictionary_iter = LLLocalizedInventoryItemsDictionary::getInstance()->mInventoryItemsDict.begin(); + dictionary_iter != LLLocalizedInventoryItemsDictionary::getInstance()->mInventoryItemsDict.end(); + dictionary_iter++) + { + const std::string& localized_name = dictionary_iter->second; + if(localized_name == name) + { + server_name = dictionary_iter->first; + } + } + } + +#ifdef USE_AIS_FOR_NC + // D567 18.03.2023 not yet implemented within AIS3 + if (AISAPI::isAvailable()) + { + LLSD new_inventory = LLSD::emptyMap(); + new_inventory["items"] = LLSD::emptyArray(); + + LLPermissions perms; + perms.init( + gAgentID, + gAgentID, + LLUUID::null, + LLUUID::null); + perms.initMasks( + PERM_ALL, + PERM_ALL, + PERM_NONE, + PERM_NONE, + next_owner_perm); + + LLUUID null_id; + LLPointer item = new LLViewerInventoryItem( + null_id, /*don't know yet*/ + parent_id, + perms, + null_id, /*don't know yet*/ + asset_type, + inv_type, + server_name, + desc, + LLSaleInfo(), + 0, + 0 /*don't know yet, whenever server creates it*/); + LLSD item_sd = item->asLLSD(); + new_inventory["items"].append(item_sd); + AISAPI::completion_t cr = boost::bind(&doInventoryCb, cb, _1); + AISAPI::CreateInventory( + parent_id, + new_inventory, + cr); + return; + } + else + { + LL_WARNS() << "AIS v3 not available" << LL_ENDL; + } +#endif + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_CreateInventoryItem); + msg->nextBlock(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, agent_id); + msg->addUUIDFast(_PREHASH_SessionID, session_id); + msg->nextBlock(_PREHASH_InventoryBlock); + msg->addU32Fast(_PREHASH_CallbackID, gInventoryCallbacks.registerCB(cb)); + msg->addUUIDFast(_PREHASH_FolderID, parent_id); + msg->addUUIDFast(_PREHASH_TransactionID, transaction_id); + msg->addU32Fast(_PREHASH_NextOwnerMask, next_owner_perm); + msg->addS8Fast(_PREHASH_Type, (S8)asset_type); + msg->addS8Fast(_PREHASH_InvType, (S8)inv_type); + msg->addU8Fast(_PREHASH_WearableType, (U8)subtype); + msg->addStringFast(_PREHASH_Name, server_name); + msg->addStringFast(_PREHASH_Description, desc); + + gAgent.sendReliableMessage(); +} + +void create_inventory_callingcard_callback(LLPointer cb, + const LLUUID &parent, + const LLUUID &avatar_id, + const LLAvatarName &av_name) +{ + std::string item_desc = avatar_id.asString(); + create_inventory_item(gAgent.getID(), + gAgent.getSessionID(), + parent, + LLTransactionID::tnull, + av_name.getUserName(), + item_desc, + LLAssetType::AT_CALLINGCARD, + LLInventoryType::IT_CALLINGCARD, + NO_INV_SUBTYPE, + PERM_MOVE | PERM_TRANSFER, + cb); +} + +void create_inventory_callingcard(const LLUUID& avatar_id, const LLUUID& parent /*= LLUUID::null*/, LLPointer cb/*=NULL*/) +{ + LLAvatarName av_name; + LLAvatarNameCache::get(avatar_id, boost::bind(&create_inventory_callingcard_callback, cb, parent, _1, _2)); +} + +void create_inventory_wearable(const LLUUID& agent_id, const LLUUID& session_id, + const LLUUID& parent, const LLTransactionID& transaction_id, + const std::string& name, + const std::string& desc, LLAssetType::EType asset_type, + LLWearableType::EType wtype, + U32 next_owner_perm, + LLPointer cb) +{ + create_inventory_item(agent_id, session_id, parent, transaction_id, + name, desc, asset_type, LLInventoryType::IT_WEARABLE, static_cast(wtype), + next_owner_perm, cb); +} + +void create_inventory_settings(const LLUUID& agent_id, const LLUUID& session_id, + const LLUUID& parent, const LLTransactionID& transaction_id, + const std::string& name, + const std::string& desc, + LLSettingsType::type_e settype, + U32 next_owner_perm, + LLPointer cb) +{ + create_inventory_item(agent_id, session_id, parent, transaction_id, + name, desc, LLAssetType::AT_SETTINGS, LLInventoryType::IT_SETTINGS, + static_cast(settype), next_owner_perm, cb); +} + + +void copy_inventory_item( + const LLUUID& agent_id, + const LLUUID& current_owner, + const LLUUID& item_id, + const LLUUID& parent_id, + const std::string& new_name, + LLPointer cb) +{ + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_CopyInventoryItem); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, agent_id); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_InventoryData); + msg->addU32Fast(_PREHASH_CallbackID, gInventoryCallbacks.registerCB(cb)); + msg->addUUIDFast(_PREHASH_OldAgentID, current_owner); + msg->addUUIDFast(_PREHASH_OldItemID, item_id); + msg->addUUIDFast(_PREHASH_NewFolderID, parent_id); + msg->addStringFast(_PREHASH_NewName, new_name); + gAgent.sendReliableMessage(); +} + +// Create link to single inventory object. +void link_inventory_object(const LLUUID& category, + LLConstPointer baseobj, + LLPointer cb) +{ + if (!baseobj) + { + LL_WARNS(LOG_INV) << "Attempt to link to non-existent object" << LL_ENDL; + return; + } + + LLInventoryObject::const_object_list_t obj_array; + obj_array.push_back(baseobj); + link_inventory_array(category, obj_array, cb); +} + +void link_inventory_object(const LLUUID& category, + const LLUUID& id, + LLPointer cb) +{ + LLConstPointer baseobj = gInventory.getObject(id); + link_inventory_object(category, baseobj, cb); +} + +// Create links to all listed inventory objects. +void link_inventory_array(const LLUUID& category, + LLInventoryObject::const_object_list_t& baseobj_array, + LLPointer cb) +{ +#ifndef LL_RELEASE_FOR_DOWNLOAD + const LLViewerInventoryCategory *cat = gInventory.getCategory(category); + const std::string cat_name = cat ? cat->getName() : "CAT NOT FOUND"; +#endif + LLInventoryObject::const_object_list_t::const_iterator it = baseobj_array.begin(); + LLInventoryObject::const_object_list_t::const_iterator end = baseobj_array.end(); + LLSD links = LLSD::emptyArray(); + for (; it != end; ++it) + { + const LLInventoryObject* baseobj = *it; + if (!baseobj) + { + LL_WARNS(LOG_INV) << "attempt to link to unknown object" << LL_ENDL; + continue; + } + + if (!LLAssetType::lookupCanLink(baseobj->getType())) + { + // Fail if item can be found but is of a type that can't be linked. + // Arguably should fail if the item can't be found too, but that could + // be a larger behavioral change. + LL_WARNS(LOG_INV) << "attempt to link an unlinkable object, type = " << baseobj->getActualType() << LL_ENDL; + continue; + } + + LLInventoryType::EType inv_type = LLInventoryType::IT_NONE; + LLAssetType::EType asset_type = LLAssetType::AT_NONE; + std::string new_desc; + LLUUID linkee_id; + if (dynamic_cast(baseobj)) + { + inv_type = LLInventoryType::IT_CATEGORY; + asset_type = LLAssetType::AT_LINK_FOLDER; + linkee_id = baseobj->getUUID(); + } + else + { + const LLViewerInventoryItem *baseitem = dynamic_cast(baseobj); + if (baseitem) + { + inv_type = baseitem->getInventoryType(); + new_desc = baseitem->getActualDescription(); + switch (baseitem->getActualType()) + { + case LLAssetType::AT_LINK: + case LLAssetType::AT_LINK_FOLDER: + linkee_id = baseobj->getLinkedUUID(); + asset_type = baseitem->getActualType(); + break; + default: + linkee_id = baseobj->getUUID(); + asset_type = LLAssetType::AT_LINK; + break; + } + } + else + { + LL_WARNS(LOG_INV) << "could not convert object into an item or category: " << baseobj->getUUID() << LL_ENDL; + continue; + } + } + + LLSD link = LLSD::emptyMap(); + link["linked_id"] = linkee_id; + link["type"] = (S8)asset_type; + link["inv_type"] = (S8)inv_type; + link["name"] = baseobj->getName(); + link["desc"] = new_desc; + links.append(link); + +#ifndef LL_RELEASE_FOR_DOWNLOAD + LL_DEBUGS(LOG_INV) << "Linking Object [ name:" << baseobj->getName() + << " UUID:" << baseobj->getUUID() + << " ] into Category [ name:" << cat_name + << " UUID:" << category << " ] " << LL_ENDL; +#endif + } + LLSD new_inventory = LLSD::emptyMap(); + new_inventory["links"] = links; + AISAPI::completion_t cr = boost::bind(&doInventoryCb, cb, _1); + AISAPI::CreateInventory(category, new_inventory, cr); +} + +void move_inventory_item( + const LLUUID& agent_id, + const LLUUID& session_id, + const LLUUID& item_id, + const LLUUID& parent_id, + const std::string& new_name, + LLPointer cb) +{ + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_MoveInventoryItem); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, agent_id); + msg->addUUIDFast(_PREHASH_SessionID, session_id); + msg->addBOOLFast(_PREHASH_Stamp, false); + msg->nextBlockFast(_PREHASH_InventoryData); + msg->addUUIDFast(_PREHASH_ItemID, item_id); + msg->addUUIDFast(_PREHASH_FolderID, parent_id); + msg->addStringFast(_PREHASH_NewName, new_name); + gAgent.sendReliableMessage(); +} + +// Should call this with an update_item that's been copied and +// modified from an original source item, rather than modifying the +// source item directly. +void update_inventory_item( + LLViewerInventoryItem *update_item, + LLPointer cb) +{ + const LLUUID& item_id = update_item->getUUID(); + + LLSD updates = update_item->asLLSD(); + // Replace asset_id and/or shadow_id with transaction_id (hash_id) + if (updates.has("asset_id")) + { + updates.erase("asset_id"); + if (update_item->getTransactionID().notNull()) + { + updates["hash_id"] = update_item->getTransactionID(); + } + } + if (updates.has("shadow_id")) + { + updates.erase("shadow_id"); + if (update_item->getTransactionID().notNull()) + { + updates["hash_id"] = update_item->getTransactionID(); + } + } + AISAPI::completion_t cr = boost::bind(&doInventoryCb, cb, _1); + AISAPI::UpdateItem(item_id, updates, cr); +} + +// Note this only supports updating an existing item. Goes through AISv3 +// code path where available. Not all uses of item->updateServer() can +// easily be switched to this paradigm. +void update_inventory_item( + const LLUUID& item_id, + const LLSD& updates, + LLPointer cb) +{ + AISAPI::completion_t cr = boost::bind(&doInventoryCb, cb, _1); + AISAPI::UpdateItem(item_id, updates, cr); +} + +void update_inventory_category( + const LLUUID& cat_id, + const LLSD& updates, + LLPointer cb) +{ + LLPointer obj = gInventory.getCategory(cat_id); + LL_DEBUGS(LOG_INV) << "cat_id: [" << cat_id << "] name " << (obj ? obj->getName() : "(NOT FOUND)") << LL_ENDL; + if(obj) + { + if (LLFolderType::lookupIsProtectedType(obj->getPreferredType()) + && (updates.size() != 1 || !updates.has("thumbnail"))) + { + LLNotificationsUtil::add("CannotModifyProtectedCategories"); + return; + } + + AISAPI::completion_t cr = boost::bind(&doInventoryCb, cb, _1); + AISAPI::UpdateCategory(cat_id, updates, cr); + } +} + +void remove_inventory_items( + LLInventoryObject::object_list_t& items_to_kill, + LLPointer cb + ) +{ + for (LLInventoryObject::object_list_t::iterator it = items_to_kill.begin(); + it != items_to_kill.end(); + ++it) + { + remove_inventory_item(*it, cb); + } +} + +void remove_inventory_item( + const LLUUID& item_id, + LLPointer cb, + bool immediate_delete) +{ + LLPointer obj = gInventory.getItem(item_id); + if (obj) + { + remove_inventory_item(obj, cb, immediate_delete); + } + else + { + LL_DEBUGS(LOG_INV) << "item_id: [" << item_id << "] name " << "(NOT FOUND)" << LL_ENDL; + } +} + +void remove_inventory_item( + LLPointer obj, + LLPointer cb, + bool immediate_delete) +{ + if(obj) + { + const LLUUID item_id(obj->getUUID()); + LL_DEBUGS(LOG_INV) << "item_id: [" << item_id << "] name " << obj->getName() << LL_ENDL; + if (AISAPI::isAvailable()) + { + AISAPI::completion_t cr = (cb) ? boost::bind(&doInventoryCb, cb, _1) : AISAPI::completion_t(); + AISAPI::RemoveItem(item_id, cr); + + if (immediate_delete) + { + gInventory.onObjectDeletedFromServer(item_id); + } + } + else + { + LL_WARNS(LOG_INV) << "Tried to use inventory without AIS API" << LL_ENDL; + } + } + else + { + // *TODO: Clean up callback? + LL_WARNS(LOG_INV) << "remove_inventory_item called for invalid or nonexistent item." << LL_ENDL; + } +} + +class LLRemoveCategoryOnDestroy: public LLInventoryCallback +{ +public: + LLRemoveCategoryOnDestroy(const LLUUID& cat_id, LLPointer cb): + mID(cat_id), + mCB(cb) + { + } + /* virtual */ void fire(const LLUUID& item_id) {} + ~LLRemoveCategoryOnDestroy() + { + LLInventoryModel::EHasChildren children = gInventory.categoryHasChildren(mID); + if(children != LLInventoryModel::CHILDREN_NO) + { + LL_WARNS(LOG_INV) << "remove descendents failed, cannot remove category " << LL_ENDL; + } + else + { + remove_inventory_category(mID, mCB); + } + } +private: + LLUUID mID; + LLPointer mCB; +}; + +void remove_inventory_category( + const LLUUID& cat_id, + LLPointer cb) +{ + LL_DEBUGS(LOG_INV) << "cat_id: [" << cat_id << "] " << LL_ENDL; + LLPointer obj = gInventory.getCategory(cat_id); + if(obj) + { + if (!gInventory.isCategoryComplete(cat_id)) + { + LL_WARNS() << "Removing (purging) incomplete category " << obj->getName() << LL_ENDL; + } + if(LLFolderType::lookupIsProtectedType(obj->getPreferredType())) + { + LLNotificationsUtil::add("CannotRemoveProtectedCategories"); + return; + } + AISAPI::completion_t cr = boost::bind(&doInventoryCb, cb, _1); + AISAPI::RemoveCategory(cat_id, cr); + } + else + { + LL_WARNS(LOG_INV) << "remove_inventory_category called for invalid or nonexistent item " << cat_id << LL_ENDL; + } +} + +void remove_inventory_object( + const LLUUID& object_id, + LLPointer cb) +{ + if (gInventory.getCategory(object_id)) + { + remove_inventory_category(object_id, cb); + } + else + { + remove_inventory_item(object_id, cb); + } +} + +// This is a method which collects the descendents of the id +// provided. If the category is not found, no action is +// taken. This method goes through the long winded process of +// cancelling any calling cards, removing server representation of +// folders, items, etc in a fairly efficient manner. +void purge_descendents_of(const LLUUID& id, LLPointer cb) +{ + LLInventoryModel::EHasChildren children = gInventory.categoryHasChildren(id); + if(children == LLInventoryModel::CHILDREN_NO) + { + LL_DEBUGS(LOG_INV) << "No descendents to purge for " << id << LL_ENDL; + return; + } + LLPointer cat = gInventory.getCategory(id); + if (cat.notNull()) + { + if (LLClipboard::instance().hasContents()) + { + // Remove items from clipboard or it will remain active even if there is nothing to paste/copy + LLInventoryModel::cat_array_t categories; + LLInventoryModel::item_array_t items; + gInventory.collectDescendents(id, categories, items, true); + + for (LLInventoryModel::cat_array_t::const_iterator it = categories.begin(); it != categories.end(); ++it) + { + if (LLClipboard::instance().isOnClipboard((*it)->getUUID())) + { + // No sense in removing single items, partial 'paste' will result in confusion only + LLClipboard::instance().reset(); + break; + } + } + if (LLClipboard::instance().hasContents()) + { + for (LLInventoryModel::item_array_t::const_iterator it = items.begin(); it != items.end(); ++it) + { + if (LLClipboard::instance().isOnClipboard((*it)->getUUID())) + { + LLClipboard::instance().reset(); + break; + } + } + } + } + + if (AISAPI::isAvailable()) + { + if (cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) + { + LL_WARNS() << "Purging not fetched folder: " << cat->getName() << LL_ENDL; + } + AISAPI::completion_t cr = (cb) ? boost::bind(&doInventoryCb, cb, _1) : AISAPI::completion_t(); + AISAPI::PurgeDescendents(id, cr); + } + else + { + LL_WARNS(LOG_INV) << "Tried to use inventory without AIS API" << LL_ENDL; + } + } +} + +const LLUUID get_folder_by_itemtype(const LLInventoryItem *src) +{ + LLUUID retval = LLUUID::null; + + if (src) + { + retval = gInventory.findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(src->getType())); + } + + return retval; +} + +void copy_inventory_from_notecard(const LLUUID& destination_id, + const LLUUID& object_id, + const LLUUID& notecard_inv_id, + const LLInventoryItem *src, + U32 callback_id) +{ + if (NULL == src) + { + LL_WARNS(LOG_NOTECARD) << "Null pointer to item was passed for object_id " + << object_id << " and notecard_inv_id " + << notecard_inv_id << LL_ENDL; + return; + } + + LLViewerRegion* viewer_region = NULL; + LLViewerObject* vo = NULL; + if (object_id.notNull() && (vo = gObjectList.findObject(object_id)) != NULL) + { + viewer_region = vo->getRegion(); + } + + // Fallback to the agents region if for some reason the + // object isn't found in the viewer. + if (! viewer_region) + { + viewer_region = gAgent.getRegion(); + } + + if (! viewer_region) + { + LL_WARNS(LOG_NOTECARD) << "Can't find region from object_id " + << object_id << " or gAgent" + << LL_ENDL; + return; + } + + LLSD body; + body["notecard-id"] = notecard_inv_id; + body["object-id"] = object_id; + body["item-id"] = src->getUUID(); + body["folder-id"] = destination_id; + body["callback-id"] = (LLSD::Integer)callback_id; + + /// *TODO: RIDER: This posts the request under the agents policy. + /// When I convert the inventory over this call should be moved under that + /// policy as well. + if (!gAgent.requestPostCapability("CopyInventoryFromNotecard", body)) + { + LL_WARNS() << "SIM does not have the capability to copy from notecard." << LL_ENDL; + } +} + +void create_new_item(const std::string& name, + const LLUUID& parent_id, + LLAssetType::EType asset_type, + LLInventoryType::EType inv_type, + U32 next_owner_perm, + std::function created_cb = NULL) +{ + std::string desc; + LLViewerAssetType::generateDescriptionFor(asset_type, desc); + next_owner_perm = (next_owner_perm) ? next_owner_perm : PERM_MOVE | PERM_TRANSFER; + + LLPointer cb = NULL; + + switch (inv_type) + { + case LLInventoryType::IT_LSL: + { + cb = new LLBoostFuncInventoryCallback(create_script_cb); + next_owner_perm = LLFloaterPerms::getNextOwnerPerms("Scripts"); + break; + } + + case LLInventoryType::IT_GESTURE: + { + cb = new LLBoostFuncInventoryCallback(create_gesture_cb); + next_owner_perm = LLFloaterPerms::getNextOwnerPerms("Gestures"); + break; + } + + case LLInventoryType::IT_NOTECARD: + { + cb = new LLBoostFuncInventoryCallback(create_notecard_cb); + next_owner_perm = LLFloaterPerms::getNextOwnerPerms("Notecards"); + break; + } + + case LLInventoryType::IT_MATERIAL: + { + cb = new LLBoostFuncInventoryCallback(create_gltf_material_cb); + next_owner_perm = LLFloaterPerms::getNextOwnerPerms("Materials"); + break; + } + default: + { + cb = new LLBoostFuncInventoryCallback(); + break; + } + } + if (created_cb != NULL) + { + cb->addOnFireFunc(created_cb); + } + + create_inventory_item(gAgent.getID(), + gAgent.getSessionID(), + parent_id, + LLTransactionID::tnull, + name, + desc, + asset_type, + inv_type, + NO_INV_SUBTYPE, + next_owner_perm, + cb); +} + +void slam_inventory_folder(const LLUUID& folder_id, + const LLSD& contents, + LLPointer cb) +{ + LL_DEBUGS(LOG_INV) << "using AISv3 to slam folder, id " << folder_id + << " new contents: " << ll_pretty_print_sd(contents) << LL_ENDL; + + AISAPI::completion_t cr = boost::bind(&doInventoryCb, cb, _1); + AISAPI::SlamFolder(folder_id, contents, cr); +} + +void remove_folder_contents(const LLUUID& category, bool keep_outfit_links, + LLPointer cb) +{ + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + gInventory.collectDescendents(category, cats, items, + LLInventoryModel::EXCLUDE_TRASH); + for (S32 i = 0; i < items.size(); ++i) + { + LLViewerInventoryItem *item = items.at(i); + if (keep_outfit_links && (item->getActualType() == LLAssetType::AT_LINK_FOLDER)) + continue; + if (item->getIsLinkType()) + { + remove_inventory_item(item->getUUID(), cb); + } + } +} + +const std::string NEW_LSL_NAME = "New Script"; // *TODO:Translate? (probably not) +const std::string NEW_NOTECARD_NAME = "New Note"; // *TODO:Translate? (probably not) +const std::string NEW_GESTURE_NAME = "New Gesture"; // *TODO:Translate? (probably not) +const std::string NEW_MATERIAL_NAME = "New Material"; // *TODO:Translate? (probably not) + +// ! REFACTOR ! Really need to refactor this so that it's not a bunch of if-then statements... +void menu_create_inventory_item(LLInventoryPanel* panel, LLFolderBridge *bridge, const LLSD& userdata, const LLUUID& default_parent_uuid) +{ + menu_create_inventory_item(panel, bridge ? bridge->getUUID() : LLUUID::null, userdata, default_parent_uuid); +} + +void menu_create_inventory_item(LLInventoryPanel* panel, LLUUID dest_id, const LLSD& userdata, const LLUUID& default_parent_uuid, std::function created_cb) +{ + std::string type_name = userdata.asString(); + + if (("inbox" == type_name) || ("category" == type_name) || ("current" == type_name) || ("outfit" == type_name) || ("my_otfts" == type_name)) + { + LLFolderType::EType preferred_type = LLFolderType::lookup(type_name); + + LLUUID parent_id; + if (dest_id.notNull()) + { + parent_id = dest_id; + } + else if (default_parent_uuid.notNull()) + { + parent_id = default_parent_uuid; + } + else + { + parent_id = gInventory.getRootFolderID(); + } + + std::function callback_cat_created = NULL; + if (panel) + { + LLHandle handle = panel->getHandle(); + callback_cat_created = [handle](const LLUUID& new_category_id) + { + gInventory.notifyObservers(); + LLInventoryPanel* panel = static_cast(handle.get()); + if (panel) + { + panel->setSelectionByID(new_category_id, true); + } + LL_DEBUGS(LOG_INV) << "Done creating inventory: " << new_category_id << LL_ENDL; + }; + } + else if (created_cb != NULL) + { + callback_cat_created = created_cb; + } + gInventory.createNewCategory( + parent_id, + preferred_type, + LLStringUtil::null, + callback_cat_created); + } + else if ("lsl" == type_name) + { + const LLUUID parent_id = dest_id.notNull() ? dest_id : gInventory.findCategoryUUIDForType(LLFolderType::FT_LSL_TEXT); + create_new_item(NEW_LSL_NAME, + parent_id, + LLAssetType::AT_LSL_TEXT, + LLInventoryType::IT_LSL, + PERM_MOVE | PERM_TRANSFER, + created_cb); // overridden in create_new_item + } + else if ("notecard" == type_name) + { + const LLUUID parent_id = dest_id.notNull() ? dest_id : gInventory.findCategoryUUIDForType(LLFolderType::FT_NOTECARD); + create_new_item(NEW_NOTECARD_NAME, + parent_id, + LLAssetType::AT_NOTECARD, + LLInventoryType::IT_NOTECARD, + PERM_ALL, + created_cb); // overridden in create_new_item + } + else if ("gesture" == type_name) + { + const LLUUID parent_id = dest_id.notNull() ? dest_id : gInventory.findCategoryUUIDForType(LLFolderType::FT_GESTURE); + create_new_item(NEW_GESTURE_NAME, + parent_id, + LLAssetType::AT_GESTURE, + LLInventoryType::IT_GESTURE, + PERM_ALL, + created_cb); // overridden in create_new_item + } + else if ("material" == type_name) + { + const LLUUID parent_id = dest_id.notNull() ? dest_id : gInventory.findCategoryUUIDForType(LLFolderType::FT_MATERIAL); + create_new_item(NEW_MATERIAL_NAME, + parent_id, + LLAssetType::AT_MATERIAL, + LLInventoryType::IT_MATERIAL, + PERM_ALL, + created_cb); // overridden in create_new_item + } + else if (("sky" == type_name) || ("water" == type_name) || ("daycycle" == type_name)) + { + LLSettingsType::type_e stype(LLSettingsType::ST_NONE); + + if ("sky" == type_name) + { + stype = LLSettingsType::ST_SKY; + } + else if ("water" == type_name) + { + stype = LLSettingsType::ST_WATER; + } + else if ("daycycle" == type_name) + { + stype = LLSettingsType::ST_DAYCYCLE; + } + else + { + LL_ERRS(LOG_INV) << "Unknown settings type: '" << type_name << "'" << LL_ENDL; + return; + } + + LLUUID parent_id = dest_id.notNull() ? dest_id : gInventory.findCategoryUUIDForType(LLFolderType::FT_SETTINGS); + + LLSettingsVOBase::createNewInventoryItem(stype, parent_id, created_cb); + } + else + { + // Use for all clothing and body parts. Adding new wearable types requires updating LLWearableDictionary. + LLWearableType::EType wearable_type = LLWearableType::getInstance()->typeNameToType(type_name); + if (wearable_type >= LLWearableType::WT_SHAPE && wearable_type < LLWearableType::WT_COUNT) + { + const LLUUID parent_id = dest_id; + LLAgentWearables::createWearable(wearable_type, false, parent_id, created_cb); + } + else + { + LL_WARNS(LOG_INV) << "Can't create unrecognized type " << type_name << LL_ENDL; + } + } + if(panel) + { + panel->getRootFolder()->setNeedsAutoRename(true); + } +} + +LLAssetType::EType LLViewerInventoryItem::getType() const +{ + if (const LLViewerInventoryItem *linked_item = getLinkedItem()) + { + return linked_item->getType(); + } + if (const LLViewerInventoryCategory *linked_category = getLinkedCategory()) + { + return linked_category->getType(); + } + return LLInventoryItem::getType(); +} + +const LLUUID& LLViewerInventoryItem::getAssetUUID() const +{ + if (const LLViewerInventoryItem *linked_item = getLinkedItem()) + { + return linked_item->getAssetUUID(); + } + + return LLInventoryItem::getAssetUUID(); +} + +const LLUUID& LLViewerInventoryItem::getProtectedAssetUUID() const +{ + if (const LLViewerInventoryItem *linked_item = getLinkedItem()) + { + return linked_item->getProtectedAssetUUID(); + } + + // check for conditions under which we may return a visible UUID to the user + bool item_is_fullperm = getIsFullPerm(); + bool agent_is_godlike = gAgent.isGodlikeWithoutAdminMenuFakery(); + if (item_is_fullperm || agent_is_godlike) + { + return LLInventoryItem::getAssetUUID(); + } + + return LLUUID::null; +} + +const bool LLViewerInventoryItem::getIsFullPerm() const +{ + LLPermissions item_permissions = getPermissions(); + + // modify-ok & copy-ok & transfer-ok + return ( item_permissions.allowOperationBy(PERM_MODIFY, + gAgent.getID(), + gAgent.getGroupID()) && + item_permissions.allowOperationBy(PERM_COPY, + gAgent.getID(), + gAgent.getGroupID()) && + item_permissions.allowOperationBy(PERM_TRANSFER, + gAgent.getID(), + gAgent.getGroupID()) ); +} + +const std::string& LLViewerInventoryItem::getName() const +{ + if (const LLViewerInventoryItem *linked_item = getLinkedItem()) + { + return linked_item->getName(); + } + if (const LLViewerInventoryCategory *linked_category = getLinkedCategory()) + { + return linked_category->getName(); + } + + return LLInventoryItem::getName(); +} + +S32 LLViewerInventoryItem::getSortField() const +{ + return LLFavoritesOrderStorage::instance().getSortIndex(mUUID); +} + +//void LLViewerInventoryItem::setSortField(S32 sortField) +//{ +// LLFavoritesOrderStorage::instance().setSortIndex(mUUID, sortField); +// getSLURL(); +//} + +void LLViewerInventoryItem::getSLURL() +{ + LLFavoritesOrderStorage::instance().getSLURL(mAssetUUID); +} + +const LLPermissions& LLViewerInventoryItem::getPermissions() const +{ + // Use the actual permissions of the symlink, not its parent. + return LLInventoryItem::getPermissions(); +} + +const LLUUID& LLViewerInventoryItem::getCreatorUUID() const +{ + if (const LLViewerInventoryItem *linked_item = getLinkedItem()) + { + return linked_item->getCreatorUUID(); + } + + return LLInventoryItem::getCreatorUUID(); +} + +const std::string& LLViewerInventoryItem::getDescription() const +{ + if (const LLViewerInventoryItem *linked_item = getLinkedItem()) + { + return linked_item->getDescription(); + } + + return LLInventoryItem::getDescription(); +} + +const LLSaleInfo& LLViewerInventoryItem::getSaleInfo() const +{ + if (const LLViewerInventoryItem *linked_item = getLinkedItem()) + { + return linked_item->getSaleInfo(); + } + + return LLInventoryItem::getSaleInfo(); +} + +const LLUUID& LLViewerInventoryItem::getThumbnailUUID() const +{ + if (mThumbnailUUID.isNull() && mType == LLAssetType::AT_TEXTURE) + { + return mAssetUUID; + } + if (mThumbnailUUID.isNull() && mType == LLAssetType::AT_LINK) + { + LLViewerInventoryItem *linked_item = gInventory.getItem(mAssetUUID); + return linked_item ? linked_item->getThumbnailUUID() : LLUUID::null; + } + if (mThumbnailUUID.isNull() && mType == LLAssetType::AT_LINK_FOLDER) + { + LLViewerInventoryCategory *linked_cat = gInventory.getCategory(mAssetUUID); + return linked_cat ? linked_cat->getThumbnailUUID() : LLUUID::null; + } + return mThumbnailUUID; +} + +LLInventoryType::EType LLViewerInventoryItem::getInventoryType() const +{ + if (const LLViewerInventoryItem *linked_item = getLinkedItem()) + { + return linked_item->getInventoryType(); + } + + // Categories don't have types. If this item is an AT_FOLDER_LINK, + // treat it as a category. + if (getLinkedCategory()) + { + return LLInventoryType::IT_CATEGORY; + } + + return LLInventoryItem::getInventoryType(); +} + +U32 LLViewerInventoryItem::getFlags() const +{ + if (const LLViewerInventoryItem *linked_item = getLinkedItem()) + { + return linked_item->getFlags(); + } + return LLInventoryItem::getFlags(); +} + +bool LLViewerInventoryItem::isWearableType() const +{ + return (getInventoryType() == LLInventoryType::IT_WEARABLE); +} + +LLWearableType::EType LLViewerInventoryItem::getWearableType() const +{ + if (!isWearableType()) + { + return LLWearableType::WT_INVALID; + } + return LLWearableType::inventoryFlagsToWearableType(getFlags()); +} + +bool LLViewerInventoryItem::isSettingsType() const +{ + return (getInventoryType() == LLInventoryType::IT_SETTINGS); +} + +LLSettingsType::type_e LLViewerInventoryItem::getSettingsType() const +{ + if (!isSettingsType()) + { + return LLSettingsType::ST_NONE; + } + return LLSettingsType::fromInventoryFlags(getFlags()); +} + +time_t LLViewerInventoryItem::getCreationDate() const +{ + return LLInventoryItem::getCreationDate(); +} + +U32 LLViewerInventoryItem::getCRC32() const +{ + return LLInventoryItem::getCRC32(); +} + +// *TODO: mantipov: should be removed with LMSortPrefix patch in llinventorymodel.cpp, EXT-3985 +static char getSeparator() { return '@'; } +bool LLViewerInventoryItem::extractSortFieldAndDisplayName(const std::string& name, S32* sortField, std::string* displayName) +{ + using std::string; + using std::stringstream; + + const char separator = getSeparator(); + const string::size_type separatorPos = name.find(separator, 0); + + bool result = false; + + if (separatorPos < string::npos) + { + if (sortField) + { + /* + * The conversion from string to S32 is made this way instead of old plain + * atoi() to ensure portability. If on some other platform S32 will not be + * defined to be signed int, this conversion will still work because of + * operators overloading, but atoi() may fail. + */ + stringstream ss(name.substr(0, separatorPos)); + ss >> *sortField; + } + + if (displayName) + { + *displayName = name.substr(separatorPos + 1, string::npos); + } + + result = true; + } + + return result; +} + +// This returns true if the item that this item points to +// doesn't exist in memory (i.e. LLInventoryModel). The baseitem +// might still be in the database but just not loaded yet. +bool LLViewerInventoryItem::getIsBrokenLink() const +{ + // If the item's type resolves to be a link, that means either: + // A. It wasn't able to perform indirection, i.e. the baseobj doesn't exist in memory. + // B. It's pointing to another link, which is illegal. + return LLAssetType::lookupIsLinkType(getType()); +} + +LLViewerInventoryItem *LLViewerInventoryItem::getLinkedItem() const +{ + if (mType == LLAssetType::AT_LINK) + { + LLViewerInventoryItem *linked_item = gInventory.getItem(mAssetUUID); + if (linked_item && linked_item->getIsLinkType()) + { + LL_WARNS(LOG_INV) << "Warning: Accessing link to link" << LL_ENDL; + return NULL; + } + return linked_item; + } + return NULL; +} + +LLViewerInventoryCategory *LLViewerInventoryItem::getLinkedCategory() const +{ + if (mType == LLAssetType::AT_LINK_FOLDER) + { + LLViewerInventoryCategory *linked_category = gInventory.getCategory(mAssetUUID); + return linked_category; + } + return NULL; +} + +bool LLViewerInventoryItem::checkPermissionsSet(PermissionMask mask) const +{ + const LLPermissions& perm = getPermissions(); + PermissionMask curr_mask = PERM_NONE; + if(perm.getOwner() == gAgent.getID()) + { + curr_mask = perm.getMaskBase(); + } + else if(gAgent.isInGroup(perm.getGroup())) + { + curr_mask = perm.getMaskGroup(); + } + else + { + curr_mask = perm.getMaskEveryone(); + } + return ((curr_mask & mask) == mask); +} + +PermissionMask LLViewerInventoryItem::getPermissionMask() const +{ + const LLPermissions& permissions = getPermissions(); + + bool copy = permissions.allowCopyBy(gAgent.getID()); + bool mod = permissions.allowModifyBy(gAgent.getID()); + bool xfer = permissions.allowOperationBy(PERM_TRANSFER, gAgent.getID()); + PermissionMask perm_mask = 0; + if (copy) perm_mask |= PERM_COPY; + if (mod) perm_mask |= PERM_MODIFY; + if (xfer) perm_mask |= PERM_TRANSFER; + return perm_mask; +} + +//---------- + +void LLViewerInventoryItem::onCallingCardNameLookup(const LLUUID& id, const LLAvatarName& name) +{ + rename(name.getUserName()); + gInventory.addChangedMask(LLInventoryObserver::LABEL, getUUID()); + gInventory.notifyObservers(); +} + +class LLRegenerateLinkCollector : public LLInventoryCollectFunctor +{ +public: + LLRegenerateLinkCollector(const LLViewerInventoryItem *target_item) : mTargetItem(target_item) {} + virtual ~LLRegenerateLinkCollector() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item) + { + if (item) + { + if ((item->getName() == mTargetItem->getName()) && + (item->getInventoryType() == mTargetItem->getInventoryType()) && + (!item->getIsLinkType())) + { + return true; + } + } + return false; + } +protected: + const LLViewerInventoryItem* mTargetItem; +}; + +LLUUID find_possible_item_for_regeneration(const LLViewerInventoryItem *target_item) +{ + LLViewerInventoryCategory::cat_array_t cats; + LLViewerInventoryItem::item_array_t items; + + LLRegenerateLinkCollector candidate_matches(target_item); + gInventory.collectDescendentsIf(gInventory.getRootFolderID(), + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + candidate_matches); + for (LLViewerInventoryItem::item_array_t::const_iterator item_iter = items.begin(); + item_iter != items.end(); + ++item_iter) + { + const LLViewerInventoryItem *item = (*item_iter); + if (true) return item->getUUID(); + } + return LLUUID::null; +} + +// This currently dosen't work, because the sim does not allow us +// to change an item's assetID. +bool LLViewerInventoryItem::regenerateLink() +{ + const LLUUID target_item_id = find_possible_item_for_regeneration(this); + if (target_item_id.isNull()) + return false; + LLViewerInventoryCategory::cat_array_t cats; + LLViewerInventoryItem::item_array_t items; + LLAssetIDMatches asset_id_matches(getAssetUUID()); + gInventory.collectDescendentsIf(gInventory.getRootFolderID(), + cats, + items, + LLInventoryModel::EXCLUDE_TRASH, + asset_id_matches); + for (LLViewerInventoryItem::item_array_t::iterator item_iter = items.begin(); + item_iter != items.end(); + item_iter++) + { + LLViewerInventoryItem *item = (*item_iter); + item->setAssetUUID(target_item_id); + item->updateServer(false); + gInventory.addChangedMask(LLInventoryObserver::REBUILD, item->getUUID()); + } + gInventory.notifyObservers(); + return true; +} diff --git a/indra/newview/llviewerinventory.h b/indra/newview/llviewerinventory.h index 08f0bdc4b4..21a6606253 100644 --- a/indra/newview/llviewerinventory.h +++ b/indra/newview/llviewerinventory.h @@ -1,482 +1,482 @@ -/** - * @file llviewerinventory.h - * @brief Declaration of the inventory bits that only used on the viewer. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVIEWERINVENTORY_H -#define LL_LLVIEWERINVENTORY_H - -#include "llinventory.h" -#include "llframetimer.h" -#include "llwearable.h" -#include "llinitdestroyclass.h" //for LLDestroyClass -#include "llinventorysettings.h" - -#include // boost::signals2::trackable - -class LLInventoryPanel; -class LLFolderView; -class LLFolderBridge; -class LLViewerInventoryCategory; -class LLInventoryCallback; -class LLAvatarName; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLViewerInventoryItem -// -// An inventory item represents something that the current user has in -// their inventory. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLViewerInventoryItem : public LLInventoryItem, public boost::signals2::trackable -{ -public: - typedef std::vector > item_array_t; - -protected: - ~LLViewerInventoryItem( void ); // ref counted - bool extractSortFieldAndDisplayName(S32* sortField, std::string* displayName) const { return extractSortFieldAndDisplayName(mName, sortField, displayName); } - mutable std::string mDisplayName; - -public: - virtual LLAssetType::EType getType() const; - virtual const LLUUID& getAssetUUID() const; - virtual const LLUUID& getProtectedAssetUUID() const; // returns LLUUID::null if current agent does not have permission to expose this asset's UUID to the user - virtual const std::string& getName() const; - virtual S32 getSortField() const; - //virtual void setSortField(S32 sortField); - virtual void getSLURL(); //Caches SLURL for landmark. //*TODO: Find a better way to do it and remove this method from here. - virtual const LLPermissions& getPermissions() const; - virtual const bool getIsFullPerm() const; // 'fullperm' in the popular sense: modify-ok & copy-ok & transfer-ok, no special god rules applied - virtual const LLUUID& getCreatorUUID() const; - virtual const std::string& getDescription() const; - virtual const LLSaleInfo& getSaleInfo() const; - virtual const LLUUID& getThumbnailUUID() const; - virtual LLInventoryType::EType getInventoryType() const; - virtual bool isWearableType() const; - virtual LLWearableType::EType getWearableType() const; - virtual bool isSettingsType() const; - virtual LLSettingsType::type_e getSettingsType() const; - - virtual U32 getFlags() const; - virtual time_t getCreationDate() const; - virtual U32 getCRC32() const; // really more of a checksum. - - static bool extractSortFieldAndDisplayName(const std::string& name, S32* sortField, std::string* displayName); - - // construct a complete viewer inventory item - LLViewerInventoryItem(const LLUUID& uuid, const LLUUID& parent_uuid, - const LLPermissions& permissions, - const LLUUID& asset_uuid, - LLAssetType::EType type, - LLInventoryType::EType inv_type, - const std::string& name, - const std::string& desc, - const LLSaleInfo& sale_info, - U32 flags, - time_t creation_date_utc); - - // construct a viewer inventory item which has the minimal amount - // of information to use in the UI. - LLViewerInventoryItem( - const LLUUID& item_id, - const LLUUID& parent_id, - const std::string& name, - LLInventoryType::EType inv_type); - - // construct an invalid and incomplete viewer inventory item. - // usually useful for unpacking or importing or what have you. - // *NOTE: it is important to call setComplete() if you expect the - // operations to provide all necessary information. - LLViewerInventoryItem(); - // Create a copy of an inventory item from a pointer to another item - // Note: Because InventoryItems are ref counted, - // reference copy (a = b) is prohibited - LLViewerInventoryItem(const LLViewerInventoryItem* other); - LLViewerInventoryItem(const LLInventoryItem* other); - - void copyViewerItem(const LLViewerInventoryItem* other); - /*virtual*/ void copyItem(const LLInventoryItem* other); - - // construct a new clone of this item - it creates a new viewer - // inventory item using the copy constructor, and returns it. - // It is up to the caller to delete (unref) the item. - void cloneViewerItem(LLPointer& newitem) const; - - // virtual methods - virtual void updateParentOnServer(bool restamp) const; - virtual void updateServer(bool is_new) const; - void fetchFromServer(void) const; - - virtual void packMessage(LLMessageSystem* msg) const; - virtual bool unpackMessage(LLMessageSystem* msg, const char* block, S32 block_num = 0); - virtual bool unpackMessage(const LLSD& item); - virtual bool importLegacyStream(std::istream& input_stream); - - // new methods - bool isFinished() const { return mIsComplete; } - void setComplete(bool complete) { mIsComplete = complete; } - //void updateAssetOnServer() const; - - virtual void setTransactionID(const LLTransactionID& transaction_id); - struct comparePointers - { - bool operator()(const LLPointer& a, const LLPointer& b) - { - return a->getName().compare(b->getName()) < 0; - } - }; - LLTransactionID getTransactionID() const { return mTransactionID; } - - bool getIsBrokenLink() const; // true if the baseitem this points to doesn't exist in memory. - LLViewerInventoryItem *getLinkedItem() const; - LLViewerInventoryCategory *getLinkedCategory() const; - - // Checks the items permissions (for owner, group, or everyone) and returns true if all mask bits are set. - bool checkPermissionsSet(PermissionMask mask) const; - PermissionMask getPermissionMask() const; - - // callback - void onCallingCardNameLookup(const LLUUID& id, const LLAvatarName& name); - - // If this is a broken link, try to fix it and any other identical link. - bool regenerateLink(); - -public: - bool mIsComplete; - LLTransactionID mTransactionID; -}; - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLViewerInventoryCategory -// -// An instance of this class represents a category of inventory -// items. Users come with a set of default categories, and can create -// new ones as needed. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -class LLViewerInventoryCategory : public LLInventoryCategory -{ -public: - typedef std::vector > cat_array_t; - -protected: - ~LLViewerInventoryCategory(); - -public: - LLViewerInventoryCategory(const LLUUID& uuid, const LLUUID& parent_uuid, - LLFolderType::EType preferred_type, - const std::string& name, - const LLUUID& owner_id); - LLViewerInventoryCategory(const LLUUID& owner_id); - // Create a copy of an inventory category from a pointer to another category - // Note: Because InventoryCategorys are ref counted, reference copy (a = b) - // is prohibited - LLViewerInventoryCategory(const LLViewerInventoryCategory* other); - void copyViewerCategory(const LLViewerInventoryCategory* other); - - virtual void updateParentOnServer(bool restamp_children) const; - virtual void updateServer(bool is_new) const; - - virtual void packMessage(LLMessageSystem* msg) const; - - const LLUUID& getOwnerID() const { return mOwnerID; } - - // Version handling - enum { VERSION_UNKNOWN = -1, VERSION_INITIAL = 1 }; - S32 getVersion() const; - void setVersion(S32 version); - - // Returns true if a fetch was issued (not nessesary in progress). - // no requests will happen during expiry_seconds even if fetch completed - bool fetch(S32 expiry_seconds = 10); - - typedef enum { - FETCH_NONE = 0, - FETCH_NORMAL, - FETCH_RECURSIVE, - FETCH_FAILED, // back off - } EFetchType; - EFetchType getFetching(); - // marks as fetch being in progress or as done - void setFetching(EFetchType); - - // used to help make caching more robust - for example, if - // someone is getting 4 packets but logs out after 3. the viewer - // may never know the cache is wrong. - enum { DESCENDENT_COUNT_UNKNOWN = -1 }; - S32 getDescendentCount() const { return mDescendentCount; } - void setDescendentCount(S32 descendents) { mDescendentCount = descendents; } - // How many descendents do we currently have information for in the InventoryModel? - S32 getViewerDescendentCount() const; - - LLSD exportLLSD() const; - bool importLLSD(const LLSD& cat_data); - - void determineFolderType(); - void changeType(LLFolderType::EType new_folder_type); - virtual void unpackMessage(LLMessageSystem* msg, const char* block, S32 block_num = 0); - virtual bool unpackMessage(const LLSD& category); - - // returns true if the category object will accept the incoming item - bool acceptItem(LLInventoryItem* inv_item); - -private: - friend class LLInventoryModel; - void localizeName(); // intended to be called from the LLInventoryModel - -protected: - LLUUID mOwnerID; - S32 mVersion; - S32 mDescendentCount; - EFetchType mFetching; - LLFrameTimer mDescendentsRequested; -}; - -class LLInventoryCallback : public LLRefCount -{ -public: - virtual ~LLInventoryCallback() {} - virtual void fire(const LLUUID& inv_item) = 0; -}; - -class LLViewerJointAttachment; - -void rez_attachment_cb(const LLUUID& inv_item, LLViewerJointAttachment *attachmentp); - -void activate_gesture_cb(const LLUUID& inv_item); - -void create_script_cb(const LLUUID& inv_item); -void create_gesture_cb(const LLUUID& inv_item); -void create_notecard_cb(const LLUUID& inv_item); - -class AddFavoriteLandmarkCallback : public LLInventoryCallback -{ -public: - AddFavoriteLandmarkCallback() : mTargetLandmarkId(LLUUID::null) {} - void setTargetLandmarkId(const LLUUID& target_uuid) { mTargetLandmarkId = target_uuid; } - -private: - void fire(const LLUUID& inv_item); - - LLUUID mTargetLandmarkId; -}; - -typedef boost::function inventory_func_type; -typedef boost::function llsd_func_type; -typedef boost::function nullary_func_type; - -void no_op_inventory_func(const LLUUID&); // A do-nothing inventory_func -void no_op_llsd_func(const LLSD&); // likewise for LLSD -void no_op(); // A do-nothing nullary func. - -// Shim between inventory callback and boost function/callable -class LLBoostFuncInventoryCallback: public LLInventoryCallback -{ -public: - - LLBoostFuncInventoryCallback(inventory_func_type fire_func, - nullary_func_type destroy_func = no_op): - mDestroyFunc(destroy_func) - { - mFireFuncs.push_back(fire_func); - } - - LLBoostFuncInventoryCallback() - { - } - - void addOnFireFunc(inventory_func_type fire_func) - { - mFireFuncs.push_back(fire_func); - } - - // virtual - void fire(const LLUUID& item_id) - { - for (inventory_func_type &func: mFireFuncs) - { - func(item_id); - } - } - - // virtual - ~LLBoostFuncInventoryCallback() -{ - mDestroyFunc(); - } - - -private: - std::list mFireFuncs; - nullary_func_type mDestroyFunc; -}; - -// misc functions -//void inventory_reliable_callback(void**, S32 status); - -class LLInventoryCallbackManager : public LLDestroyClass -{ - friend class LLDestroyClass; -public: - LLInventoryCallbackManager(); - ~LLInventoryCallbackManager(); - - void fire(U32 callback_id, const LLUUID& item_id); - U32 registerCB(LLPointer cb); -private: - typedef std::map > callback_map_t; - callback_map_t mMap; - U32 mLastCallback; - static LLInventoryCallbackManager *sInstance; - static void destroyClass(); - -public: - static bool is_instantiated() { return sInstance != NULL; } -}; -extern LLInventoryCallbackManager gInventoryCallbacks; - - -const U8 NO_INV_SUBTYPE{ 0 }; - -// *TODO: Find a home for these -void create_inventory_item(const LLUUID& agent_id, const LLUUID& session_id, - const LLUUID& parent, const LLTransactionID& transaction_id, - const std::string& name, - const std::string& desc, LLAssetType::EType asset_type, - LLInventoryType::EType inv_type, U8 subtype, - U32 next_owner_perm, - LLPointer cb); - -void create_inventory_wearable(const LLUUID& agent_id, const LLUUID& session_id, - const LLUUID& parent, const LLTransactionID& transaction_id, - const std::string& name, - const std::string& desc, LLAssetType::EType asset_type, - LLWearableType::EType wtype, - U32 next_owner_perm, - LLPointer cb); - -void create_inventory_settings(const LLUUID& agent_id, const LLUUID& session_id, - const LLUUID& parent, const LLTransactionID& transaction_id, - const std::string& name, const std::string& desc, - LLSettingsType::type_e settype, - U32 next_owner_perm, LLPointer cb); - - -void create_inventory_callingcard(const LLUUID& avatar_id, const LLUUID& parent = LLUUID::null, LLPointer cb=NULL); - -/** - * @brief Securely create a new inventory item by copying from another. - */ -void copy_inventory_item( - const LLUUID& agent_id, - const LLUUID& current_owner, - const LLUUID& item_id, - const LLUUID& parent_id, - const std::string& new_name, - LLPointer cb); - -// utility functions for inventory linking. -void link_inventory_object(const LLUUID& category, - LLConstPointer baseobj, - LLPointer cb); -void link_inventory_object(const LLUUID& category, - const LLUUID& id, - LLPointer cb); -void link_inventory_array(const LLUUID& category, - LLInventoryObject::const_object_list_t& baseobj_array, - LLPointer cb); - -void move_inventory_item( - const LLUUID& agent_id, - const LLUUID& session_id, - const LLUUID& item_id, - const LLUUID& parent_id, - const std::string& new_name, - LLPointer cb); - -void update_inventory_item( - LLViewerInventoryItem *update_item, - LLPointer cb); - -void update_inventory_item( - const LLUUID& item_id, - const LLSD& updates, - LLPointer cb); - -void update_inventory_category( - const LLUUID& cat_id, - const LLSD& updates, - LLPointer cb); - -void remove_inventory_items( - LLInventoryObject::object_list_t& items, - LLPointer cb); - -void remove_inventory_item( - LLPointer obj, - LLPointer cb, - bool immediate_delete = false); - -void remove_inventory_item( - const LLUUID& item_id, - LLPointer cb, - bool immediate_delete = false); - -void remove_inventory_category( - const LLUUID& cat_id, - LLPointer cb); - -void remove_inventory_object( - const LLUUID& object_id, - LLPointer cb); - -void purge_descendents_of( - const LLUUID& cat_id, - LLPointer cb); - -const LLUUID get_folder_by_itemtype(const LLInventoryItem *src); - -void copy_inventory_from_notecard(const LLUUID& destination_id, - const LLUUID& object_id, - const LLUUID& notecard_inv_id, - const LLInventoryItem *src, - U32 callback_id = 0); - -void menu_create_inventory_item(LLInventoryPanel* root, - LLFolderBridge* bridge, - const LLSD& userdata, - const LLUUID& default_parent_uuid = LLUUID::null); - -void menu_create_inventory_item(LLInventoryPanel* panel, LLUUID dest_id, const LLSD& userdata, const LLUUID& default_parent_uuid = LLUUID::null, std::function folder_created_cb = NULL); - -void slam_inventory_folder(const LLUUID& folder_id, - const LLSD& contents, - LLPointer cb); - -void remove_folder_contents(const LLUUID& folder_id, bool keep_outfit_links, - LLPointer cb); - -#endif // LL_LLVIEWERINVENTORY_H +/** + * @file llviewerinventory.h + * @brief Declaration of the inventory bits that only used on the viewer. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVIEWERINVENTORY_H +#define LL_LLVIEWERINVENTORY_H + +#include "llinventory.h" +#include "llframetimer.h" +#include "llwearable.h" +#include "llinitdestroyclass.h" //for LLDestroyClass +#include "llinventorysettings.h" + +#include // boost::signals2::trackable + +class LLInventoryPanel; +class LLFolderView; +class LLFolderBridge; +class LLViewerInventoryCategory; +class LLInventoryCallback; +class LLAvatarName; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLViewerInventoryItem +// +// An inventory item represents something that the current user has in +// their inventory. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLViewerInventoryItem : public LLInventoryItem, public boost::signals2::trackable +{ +public: + typedef std::vector > item_array_t; + +protected: + ~LLViewerInventoryItem( void ); // ref counted + bool extractSortFieldAndDisplayName(S32* sortField, std::string* displayName) const { return extractSortFieldAndDisplayName(mName, sortField, displayName); } + mutable std::string mDisplayName; + +public: + virtual LLAssetType::EType getType() const; + virtual const LLUUID& getAssetUUID() const; + virtual const LLUUID& getProtectedAssetUUID() const; // returns LLUUID::null if current agent does not have permission to expose this asset's UUID to the user + virtual const std::string& getName() const; + virtual S32 getSortField() const; + //virtual void setSortField(S32 sortField); + virtual void getSLURL(); //Caches SLURL for landmark. //*TODO: Find a better way to do it and remove this method from here. + virtual const LLPermissions& getPermissions() const; + virtual const bool getIsFullPerm() const; // 'fullperm' in the popular sense: modify-ok & copy-ok & transfer-ok, no special god rules applied + virtual const LLUUID& getCreatorUUID() const; + virtual const std::string& getDescription() const; + virtual const LLSaleInfo& getSaleInfo() const; + virtual const LLUUID& getThumbnailUUID() const; + virtual LLInventoryType::EType getInventoryType() const; + virtual bool isWearableType() const; + virtual LLWearableType::EType getWearableType() const; + virtual bool isSettingsType() const; + virtual LLSettingsType::type_e getSettingsType() const; + + virtual U32 getFlags() const; + virtual time_t getCreationDate() const; + virtual U32 getCRC32() const; // really more of a checksum. + + static bool extractSortFieldAndDisplayName(const std::string& name, S32* sortField, std::string* displayName); + + // construct a complete viewer inventory item + LLViewerInventoryItem(const LLUUID& uuid, const LLUUID& parent_uuid, + const LLPermissions& permissions, + const LLUUID& asset_uuid, + LLAssetType::EType type, + LLInventoryType::EType inv_type, + const std::string& name, + const std::string& desc, + const LLSaleInfo& sale_info, + U32 flags, + time_t creation_date_utc); + + // construct a viewer inventory item which has the minimal amount + // of information to use in the UI. + LLViewerInventoryItem( + const LLUUID& item_id, + const LLUUID& parent_id, + const std::string& name, + LLInventoryType::EType inv_type); + + // construct an invalid and incomplete viewer inventory item. + // usually useful for unpacking or importing or what have you. + // *NOTE: it is important to call setComplete() if you expect the + // operations to provide all necessary information. + LLViewerInventoryItem(); + // Create a copy of an inventory item from a pointer to another item + // Note: Because InventoryItems are ref counted, + // reference copy (a = b) is prohibited + LLViewerInventoryItem(const LLViewerInventoryItem* other); + LLViewerInventoryItem(const LLInventoryItem* other); + + void copyViewerItem(const LLViewerInventoryItem* other); + /*virtual*/ void copyItem(const LLInventoryItem* other); + + // construct a new clone of this item - it creates a new viewer + // inventory item using the copy constructor, and returns it. + // It is up to the caller to delete (unref) the item. + void cloneViewerItem(LLPointer& newitem) const; + + // virtual methods + virtual void updateParentOnServer(bool restamp) const; + virtual void updateServer(bool is_new) const; + void fetchFromServer(void) const; + + virtual void packMessage(LLMessageSystem* msg) const; + virtual bool unpackMessage(LLMessageSystem* msg, const char* block, S32 block_num = 0); + virtual bool unpackMessage(const LLSD& item); + virtual bool importLegacyStream(std::istream& input_stream); + + // new methods + bool isFinished() const { return mIsComplete; } + void setComplete(bool complete) { mIsComplete = complete; } + //void updateAssetOnServer() const; + + virtual void setTransactionID(const LLTransactionID& transaction_id); + struct comparePointers + { + bool operator()(const LLPointer& a, const LLPointer& b) + { + return a->getName().compare(b->getName()) < 0; + } + }; + LLTransactionID getTransactionID() const { return mTransactionID; } + + bool getIsBrokenLink() const; // true if the baseitem this points to doesn't exist in memory. + LLViewerInventoryItem *getLinkedItem() const; + LLViewerInventoryCategory *getLinkedCategory() const; + + // Checks the items permissions (for owner, group, or everyone) and returns true if all mask bits are set. + bool checkPermissionsSet(PermissionMask mask) const; + PermissionMask getPermissionMask() const; + + // callback + void onCallingCardNameLookup(const LLUUID& id, const LLAvatarName& name); + + // If this is a broken link, try to fix it and any other identical link. + bool regenerateLink(); + +public: + bool mIsComplete; + LLTransactionID mTransactionID; +}; + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLViewerInventoryCategory +// +// An instance of this class represents a category of inventory +// items. Users come with a set of default categories, and can create +// new ones as needed. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class LLViewerInventoryCategory : public LLInventoryCategory +{ +public: + typedef std::vector > cat_array_t; + +protected: + ~LLViewerInventoryCategory(); + +public: + LLViewerInventoryCategory(const LLUUID& uuid, const LLUUID& parent_uuid, + LLFolderType::EType preferred_type, + const std::string& name, + const LLUUID& owner_id); + LLViewerInventoryCategory(const LLUUID& owner_id); + // Create a copy of an inventory category from a pointer to another category + // Note: Because InventoryCategorys are ref counted, reference copy (a = b) + // is prohibited + LLViewerInventoryCategory(const LLViewerInventoryCategory* other); + void copyViewerCategory(const LLViewerInventoryCategory* other); + + virtual void updateParentOnServer(bool restamp_children) const; + virtual void updateServer(bool is_new) const; + + virtual void packMessage(LLMessageSystem* msg) const; + + const LLUUID& getOwnerID() const { return mOwnerID; } + + // Version handling + enum { VERSION_UNKNOWN = -1, VERSION_INITIAL = 1 }; + S32 getVersion() const; + void setVersion(S32 version); + + // Returns true if a fetch was issued (not nessesary in progress). + // no requests will happen during expiry_seconds even if fetch completed + bool fetch(S32 expiry_seconds = 10); + + typedef enum { + FETCH_NONE = 0, + FETCH_NORMAL, + FETCH_RECURSIVE, + FETCH_FAILED, // back off + } EFetchType; + EFetchType getFetching(); + // marks as fetch being in progress or as done + void setFetching(EFetchType); + + // used to help make caching more robust - for example, if + // someone is getting 4 packets but logs out after 3. the viewer + // may never know the cache is wrong. + enum { DESCENDENT_COUNT_UNKNOWN = -1 }; + S32 getDescendentCount() const { return mDescendentCount; } + void setDescendentCount(S32 descendents) { mDescendentCount = descendents; } + // How many descendents do we currently have information for in the InventoryModel? + S32 getViewerDescendentCount() const; + + LLSD exportLLSD() const; + bool importLLSD(const LLSD& cat_data); + + void determineFolderType(); + void changeType(LLFolderType::EType new_folder_type); + virtual void unpackMessage(LLMessageSystem* msg, const char* block, S32 block_num = 0); + virtual bool unpackMessage(const LLSD& category); + + // returns true if the category object will accept the incoming item + bool acceptItem(LLInventoryItem* inv_item); + +private: + friend class LLInventoryModel; + void localizeName(); // intended to be called from the LLInventoryModel + +protected: + LLUUID mOwnerID; + S32 mVersion; + S32 mDescendentCount; + EFetchType mFetching; + LLFrameTimer mDescendentsRequested; +}; + +class LLInventoryCallback : public LLRefCount +{ +public: + virtual ~LLInventoryCallback() {} + virtual void fire(const LLUUID& inv_item) = 0; +}; + +class LLViewerJointAttachment; + +void rez_attachment_cb(const LLUUID& inv_item, LLViewerJointAttachment *attachmentp); + +void activate_gesture_cb(const LLUUID& inv_item); + +void create_script_cb(const LLUUID& inv_item); +void create_gesture_cb(const LLUUID& inv_item); +void create_notecard_cb(const LLUUID& inv_item); + +class AddFavoriteLandmarkCallback : public LLInventoryCallback +{ +public: + AddFavoriteLandmarkCallback() : mTargetLandmarkId(LLUUID::null) {} + void setTargetLandmarkId(const LLUUID& target_uuid) { mTargetLandmarkId = target_uuid; } + +private: + void fire(const LLUUID& inv_item); + + LLUUID mTargetLandmarkId; +}; + +typedef boost::function inventory_func_type; +typedef boost::function llsd_func_type; +typedef boost::function nullary_func_type; + +void no_op_inventory_func(const LLUUID&); // A do-nothing inventory_func +void no_op_llsd_func(const LLSD&); // likewise for LLSD +void no_op(); // A do-nothing nullary func. + +// Shim between inventory callback and boost function/callable +class LLBoostFuncInventoryCallback: public LLInventoryCallback +{ +public: + + LLBoostFuncInventoryCallback(inventory_func_type fire_func, + nullary_func_type destroy_func = no_op): + mDestroyFunc(destroy_func) + { + mFireFuncs.push_back(fire_func); + } + + LLBoostFuncInventoryCallback() + { + } + + void addOnFireFunc(inventory_func_type fire_func) + { + mFireFuncs.push_back(fire_func); + } + + // virtual + void fire(const LLUUID& item_id) + { + for (inventory_func_type &func: mFireFuncs) + { + func(item_id); + } + } + + // virtual + ~LLBoostFuncInventoryCallback() +{ + mDestroyFunc(); + } + + +private: + std::list mFireFuncs; + nullary_func_type mDestroyFunc; +}; + +// misc functions +//void inventory_reliable_callback(void**, S32 status); + +class LLInventoryCallbackManager : public LLDestroyClass +{ + friend class LLDestroyClass; +public: + LLInventoryCallbackManager(); + ~LLInventoryCallbackManager(); + + void fire(U32 callback_id, const LLUUID& item_id); + U32 registerCB(LLPointer cb); +private: + typedef std::map > callback_map_t; + callback_map_t mMap; + U32 mLastCallback; + static LLInventoryCallbackManager *sInstance; + static void destroyClass(); + +public: + static bool is_instantiated() { return sInstance != NULL; } +}; +extern LLInventoryCallbackManager gInventoryCallbacks; + + +const U8 NO_INV_SUBTYPE{ 0 }; + +// *TODO: Find a home for these +void create_inventory_item(const LLUUID& agent_id, const LLUUID& session_id, + const LLUUID& parent, const LLTransactionID& transaction_id, + const std::string& name, + const std::string& desc, LLAssetType::EType asset_type, + LLInventoryType::EType inv_type, U8 subtype, + U32 next_owner_perm, + LLPointer cb); + +void create_inventory_wearable(const LLUUID& agent_id, const LLUUID& session_id, + const LLUUID& parent, const LLTransactionID& transaction_id, + const std::string& name, + const std::string& desc, LLAssetType::EType asset_type, + LLWearableType::EType wtype, + U32 next_owner_perm, + LLPointer cb); + +void create_inventory_settings(const LLUUID& agent_id, const LLUUID& session_id, + const LLUUID& parent, const LLTransactionID& transaction_id, + const std::string& name, const std::string& desc, + LLSettingsType::type_e settype, + U32 next_owner_perm, LLPointer cb); + + +void create_inventory_callingcard(const LLUUID& avatar_id, const LLUUID& parent = LLUUID::null, LLPointer cb=NULL); + +/** + * @brief Securely create a new inventory item by copying from another. + */ +void copy_inventory_item( + const LLUUID& agent_id, + const LLUUID& current_owner, + const LLUUID& item_id, + const LLUUID& parent_id, + const std::string& new_name, + LLPointer cb); + +// utility functions for inventory linking. +void link_inventory_object(const LLUUID& category, + LLConstPointer baseobj, + LLPointer cb); +void link_inventory_object(const LLUUID& category, + const LLUUID& id, + LLPointer cb); +void link_inventory_array(const LLUUID& category, + LLInventoryObject::const_object_list_t& baseobj_array, + LLPointer cb); + +void move_inventory_item( + const LLUUID& agent_id, + const LLUUID& session_id, + const LLUUID& item_id, + const LLUUID& parent_id, + const std::string& new_name, + LLPointer cb); + +void update_inventory_item( + LLViewerInventoryItem *update_item, + LLPointer cb); + +void update_inventory_item( + const LLUUID& item_id, + const LLSD& updates, + LLPointer cb); + +void update_inventory_category( + const LLUUID& cat_id, + const LLSD& updates, + LLPointer cb); + +void remove_inventory_items( + LLInventoryObject::object_list_t& items, + LLPointer cb); + +void remove_inventory_item( + LLPointer obj, + LLPointer cb, + bool immediate_delete = false); + +void remove_inventory_item( + const LLUUID& item_id, + LLPointer cb, + bool immediate_delete = false); + +void remove_inventory_category( + const LLUUID& cat_id, + LLPointer cb); + +void remove_inventory_object( + const LLUUID& object_id, + LLPointer cb); + +void purge_descendents_of( + const LLUUID& cat_id, + LLPointer cb); + +const LLUUID get_folder_by_itemtype(const LLInventoryItem *src); + +void copy_inventory_from_notecard(const LLUUID& destination_id, + const LLUUID& object_id, + const LLUUID& notecard_inv_id, + const LLInventoryItem *src, + U32 callback_id = 0); + +void menu_create_inventory_item(LLInventoryPanel* root, + LLFolderBridge* bridge, + const LLSD& userdata, + const LLUUID& default_parent_uuid = LLUUID::null); + +void menu_create_inventory_item(LLInventoryPanel* panel, LLUUID dest_id, const LLSD& userdata, const LLUUID& default_parent_uuid = LLUUID::null, std::function folder_created_cb = NULL); + +void slam_inventory_folder(const LLUUID& folder_id, + const LLSD& contents, + LLPointer cb); + +void remove_folder_contents(const LLUUID& folder_id, bool keep_outfit_links, + LLPointer cb); + +#endif // LL_LLVIEWERINVENTORY_H diff --git a/indra/newview/llviewerjoint.cpp b/indra/newview/llviewerjoint.cpp index f64f717975..3c5c4752df 100644 --- a/indra/newview/llviewerjoint.cpp +++ b/indra/newview/llviewerjoint.cpp @@ -1,172 +1,172 @@ -/** - * @file llviewerjoint.cpp - * @brief Implementation of LLViewerJoint class - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -//----------------------------------------------------------------------------- -// Header Files -//----------------------------------------------------------------------------- -#include "llviewerprecompiledheaders.h" - -#include "llviewerjoint.h" - -#include "llgl.h" -#include "llrender.h" -#include "llmath.h" -#include "llglheaders.h" -#include "llvoavatar.h" -#include "pipeline.h" - -static constexpr S32 MIN_PIXEL_AREA_3PASS_HAIR = 64*64; - -//----------------------------------------------------------------------------- -// LLViewerJoint() -// Class Constructors -//----------------------------------------------------------------------------- -LLViewerJoint::LLViewerJoint() : - LLAvatarJoint() -{ } - -LLViewerJoint::LLViewerJoint(S32 joint_num) : - LLAvatarJoint(joint_num) -{ } - -LLViewerJoint::LLViewerJoint(const std::string &name, LLJoint *parent) : - LLAvatarJoint(name, parent) -{ } - -//----------------------------------------------------------------------------- -// ~LLViewerJoint() -// Class Destructor -//----------------------------------------------------------------------------- -LLViewerJoint::~LLViewerJoint() -{ -} - -//-------------------------------------------------------------------- -// render() -//-------------------------------------------------------------------- -U32 LLViewerJoint::render( F32 pixelArea, bool first_pass, bool is_dummy ) -{ - stop_glerror(); - - U32 triangle_count = 0; - - //---------------------------------------------------------------- - // ignore invisible objects - //---------------------------------------------------------------- - if ( mValid ) - { - - - //---------------------------------------------------------------- - // if object is transparent, defer it, otherwise - // give the joint subclass a chance to draw itself - //---------------------------------------------------------------- - if ( is_dummy ) - { - triangle_count += drawShape( pixelArea, first_pass, is_dummy ); - } - else if (LLPipeline::sShadowRender) - { - triangle_count += drawShape(pixelArea, first_pass, is_dummy ); - } - else if ( isTransparent() && !LLPipeline::sReflectionRender) - { - // Hair and Skirt - if ((pixelArea > MIN_PIXEL_AREA_3PASS_HAIR)) - { - // render all three passes - LLGLDisable cull(GL_CULL_FACE); - // first pass renders without writing to the z buffer - { - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); - triangle_count += drawShape( pixelArea, first_pass, is_dummy ); - } - // second pass writes to z buffer only - gGL.setColorMask(false, false); - { - triangle_count += drawShape( pixelArea, false, is_dummy ); - } - // third past respects z buffer and writes color - gGL.setColorMask(true, false); - { - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); - triangle_count += drawShape( pixelArea, false, is_dummy ); - } - } - else - { - // Render Inside (no Z buffer write) - glCullFace(GL_FRONT); - { - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); - triangle_count += drawShape( pixelArea, first_pass, is_dummy ); - } - // Render Outside (write to the Z buffer) - glCullFace(GL_BACK); - { - triangle_count += drawShape( pixelArea, false, is_dummy ); - } - } - } - else - { - // set up render state - triangle_count += drawShape( pixelArea, first_pass ); - } - } - - //---------------------------------------------------------------- - // render children - //---------------------------------------------------------------- - for (LLJoint* j : mChildren) - { - // LLViewerJoint is derived from LLAvatarJoint, - // all children of LLAvatarJoint are assumed to be LLAvatarJoint - LLAvatarJoint* joint = static_cast(j); - F32 jointLOD = joint->getLOD(); - if (pixelArea >= jointLOD || sDisableLOD) - { - triangle_count += joint->render( pixelArea, true, is_dummy ); - - if (jointLOD != DEFAULT_AVATAR_JOINT_LOD) - { - break; - } - } - } - - return triangle_count; -} - -//-------------------------------------------------------------------- -// drawShape() -//-------------------------------------------------------------------- -U32 LLViewerJoint::drawShape( F32 pixelArea, bool first_pass, bool is_dummy ) -{ - return 0; -} - -// End +/** + * @file llviewerjoint.cpp + * @brief Implementation of LLViewerJoint class + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "llviewerprecompiledheaders.h" + +#include "llviewerjoint.h" + +#include "llgl.h" +#include "llrender.h" +#include "llmath.h" +#include "llglheaders.h" +#include "llvoavatar.h" +#include "pipeline.h" + +static constexpr S32 MIN_PIXEL_AREA_3PASS_HAIR = 64*64; + +//----------------------------------------------------------------------------- +// LLViewerJoint() +// Class Constructors +//----------------------------------------------------------------------------- +LLViewerJoint::LLViewerJoint() : + LLAvatarJoint() +{ } + +LLViewerJoint::LLViewerJoint(S32 joint_num) : + LLAvatarJoint(joint_num) +{ } + +LLViewerJoint::LLViewerJoint(const std::string &name, LLJoint *parent) : + LLAvatarJoint(name, parent) +{ } + +//----------------------------------------------------------------------------- +// ~LLViewerJoint() +// Class Destructor +//----------------------------------------------------------------------------- +LLViewerJoint::~LLViewerJoint() +{ +} + +//-------------------------------------------------------------------- +// render() +//-------------------------------------------------------------------- +U32 LLViewerJoint::render( F32 pixelArea, bool first_pass, bool is_dummy ) +{ + stop_glerror(); + + U32 triangle_count = 0; + + //---------------------------------------------------------------- + // ignore invisible objects + //---------------------------------------------------------------- + if ( mValid ) + { + + + //---------------------------------------------------------------- + // if object is transparent, defer it, otherwise + // give the joint subclass a chance to draw itself + //---------------------------------------------------------------- + if ( is_dummy ) + { + triangle_count += drawShape( pixelArea, first_pass, is_dummy ); + } + else if (LLPipeline::sShadowRender) + { + triangle_count += drawShape(pixelArea, first_pass, is_dummy ); + } + else if ( isTransparent() && !LLPipeline::sReflectionRender) + { + // Hair and Skirt + if ((pixelArea > MIN_PIXEL_AREA_3PASS_HAIR)) + { + // render all three passes + LLGLDisable cull(GL_CULL_FACE); + // first pass renders without writing to the z buffer + { + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); + triangle_count += drawShape( pixelArea, first_pass, is_dummy ); + } + // second pass writes to z buffer only + gGL.setColorMask(false, false); + { + triangle_count += drawShape( pixelArea, false, is_dummy ); + } + // third past respects z buffer and writes color + gGL.setColorMask(true, false); + { + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); + triangle_count += drawShape( pixelArea, false, is_dummy ); + } + } + else + { + // Render Inside (no Z buffer write) + glCullFace(GL_FRONT); + { + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); + triangle_count += drawShape( pixelArea, first_pass, is_dummy ); + } + // Render Outside (write to the Z buffer) + glCullFace(GL_BACK); + { + triangle_count += drawShape( pixelArea, false, is_dummy ); + } + } + } + else + { + // set up render state + triangle_count += drawShape( pixelArea, first_pass ); + } + } + + //---------------------------------------------------------------- + // render children + //---------------------------------------------------------------- + for (LLJoint* j : mChildren) + { + // LLViewerJoint is derived from LLAvatarJoint, + // all children of LLAvatarJoint are assumed to be LLAvatarJoint + LLAvatarJoint* joint = static_cast(j); + F32 jointLOD = joint->getLOD(); + if (pixelArea >= jointLOD || sDisableLOD) + { + triangle_count += joint->render( pixelArea, true, is_dummy ); + + if (jointLOD != DEFAULT_AVATAR_JOINT_LOD) + { + break; + } + } + } + + return triangle_count; +} + +//-------------------------------------------------------------------- +// drawShape() +//-------------------------------------------------------------------- +U32 LLViewerJoint::drawShape( F32 pixelArea, bool first_pass, bool is_dummy ) +{ + return 0; +} + +// End diff --git a/indra/newview/llviewerjoint.h b/indra/newview/llviewerjoint.h index 9c7e835436..7f52ca9623 100644 --- a/indra/newview/llviewerjoint.h +++ b/indra/newview/llviewerjoint.h @@ -1,67 +1,67 @@ -/** - * @file llviewerjoint.h - * @brief Implementation of LLViewerJoint class - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVIEWERJOINT_H -#define LL_LLVIEWERJOINT_H - -//----------------------------------------------------------------------------- -// Header Files -//----------------------------------------------------------------------------- -#include "llavatarjoint.h" -#include "lljointpickname.h" - -class LLFace; -class LLViewerJointMesh; - -//----------------------------------------------------------------------------- -// class LLViewerJoint -//----------------------------------------------------------------------------- -class LLViewerJoint : - public virtual LLAvatarJoint -{ -public: - LLViewerJoint(); - LLViewerJoint(S32 joint_num); - - // *TODO: Only used for LLVOAvatarSelf::mScreenp. *DOES NOT INITIALIZE mResetAfterRestoreOldXform* - LLViewerJoint(const std::string &name, LLJoint *parent = NULL); - virtual ~LLViewerJoint(); - - // Render character hierarchy. - // Traverses the entire joint hierarchy, setting up - // transforms and calling the drawShape(). - // Derived classes may add text/graphic output. - virtual U32 render( F32 pixelArea, bool first_pass = true, bool is_dummy = false ); // Returns triangle count - - // Draws the shape attached to a joint. - // Called by render(). - virtual U32 drawShape( F32 pixelArea, bool first_pass = true, bool is_dummy = false ); - virtual void drawNormals() {} -}; - -#endif // LL_LLVIEWERJOINT_H - - +/** + * @file llviewerjoint.h + * @brief Implementation of LLViewerJoint class + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVIEWERJOINT_H +#define LL_LLVIEWERJOINT_H + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "llavatarjoint.h" +#include "lljointpickname.h" + +class LLFace; +class LLViewerJointMesh; + +//----------------------------------------------------------------------------- +// class LLViewerJoint +//----------------------------------------------------------------------------- +class LLViewerJoint : + public virtual LLAvatarJoint +{ +public: + LLViewerJoint(); + LLViewerJoint(S32 joint_num); + + // *TODO: Only used for LLVOAvatarSelf::mScreenp. *DOES NOT INITIALIZE mResetAfterRestoreOldXform* + LLViewerJoint(const std::string &name, LLJoint *parent = NULL); + virtual ~LLViewerJoint(); + + // Render character hierarchy. + // Traverses the entire joint hierarchy, setting up + // transforms and calling the drawShape(). + // Derived classes may add text/graphic output. + virtual U32 render( F32 pixelArea, bool first_pass = true, bool is_dummy = false ); // Returns triangle count + + // Draws the shape attached to a joint. + // Called by render(). + virtual U32 drawShape( F32 pixelArea, bool first_pass = true, bool is_dummy = false ); + virtual void drawNormals() {} +}; + +#endif // LL_LLVIEWERJOINT_H + + diff --git a/indra/newview/llviewerjointattachment.cpp b/indra/newview/llviewerjointattachment.cpp index 266b3db23b..e733dafcae 100644 --- a/indra/newview/llviewerjointattachment.cpp +++ b/indra/newview/llviewerjointattachment.cpp @@ -1,481 +1,481 @@ -/** - * @file llviewerjointattachment.cpp - * @brief Implementation of LLViewerJointAttachment class - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llviewerjointattachment.h" - -#include "llviewercontrol.h" -#include "lldrawable.h" -#include "llgl.h" -#include "llhudtext.h" -#include "llrender.h" -#include "llvoavatarself.h" -#include "llvolume.h" -#include "pipeline.h" -#include "llspatialpartition.h" -#include "llinventorymodel.h" -#include "llviewerobjectlist.h" -#include "llface.h" -#include "llvoavatar.h" - -#include "llglheaders.h" - -extern LLPipeline gPipeline; -constexpr F32 MAX_ATTACHMENT_DIST = 3.5f; // meters - -//----------------------------------------------------------------------------- -// LLViewerJointAttachment() -//----------------------------------------------------------------------------- -LLViewerJointAttachment::LLViewerJointAttachment() : - mVisibleInFirst(false), - mGroup(0), - mIsHUDAttachment(false), - mPieSlice(-1) -{ - mValid = false; - mUpdateXform = false; - mAttachedObjects.clear(); -} - -//----------------------------------------------------------------------------- -// ~LLViewerJointAttachment() -//----------------------------------------------------------------------------- -LLViewerJointAttachment::~LLViewerJointAttachment() -{ -} - -//----------------------------------------------------------------------------- -// isTransparent() -//----------------------------------------------------------------------------- -bool LLViewerJointAttachment::isTransparent() -{ - return false; -} - -//----------------------------------------------------------------------------- -// drawShape() -//----------------------------------------------------------------------------- -U32 LLViewerJointAttachment::drawShape( F32 pixelArea, bool first_pass, bool is_dummy ) -{ - if (LLVOAvatar::sShowAttachmentPoints) - { - LLGLDisable cull_face(GL_CULL_FACE); - - gGL.color4f(1.f, 1.f, 1.f, 1.f); - gGL.begin(LLRender::QUADS); - { - gGL.vertex3f(-0.1f, 0.1f, 0.f); - gGL.vertex3f(-0.1f, -0.1f, 0.f); - gGL.vertex3f(0.1f, -0.1f, 0.f); - gGL.vertex3f(0.1f, 0.1f, 0.f); - }gGL.end(); - } - return 0; -} - -void LLViewerJointAttachment::setupDrawable(LLViewerObject *object) -{ - if (!object->mDrawable) - return; - if (object->mDrawable->isActive()) - { - object->mDrawable->makeStatic(false); - } - - object->mDrawable->mXform.setParent(getXform()); // LLViewerJointAttachment::lazyAttach - object->mDrawable->makeActive(); - LLVector3 current_pos = object->getRenderPosition(); - LLQuaternion current_rot = object->getRenderRotation(); - LLQuaternion attachment_pt_inv_rot = ~(getWorldRotation()); - - current_pos -= getWorldPosition(); - current_pos.rotVec(attachment_pt_inv_rot); - - current_rot = current_rot * attachment_pt_inv_rot; - - object->mDrawable->mXform.setPosition(current_pos); - object->mDrawable->mXform.setRotation(current_rot); - gPipeline.markMoved(object->mDrawable); - gPipeline.markTextured(object->mDrawable); // face may need to change draw pool to/from POOL_HUD - - if(mIsHUDAttachment) - { - for (S32 face_num = 0; face_num < object->mDrawable->getNumFaces(); face_num++) - { - LLFace *face = object->mDrawable->getFace(face_num); - if (face) - { - face->setState(LLFace::HUD_RENDER); - } - } - } - - LLViewerObject::const_child_list_t& child_list = object->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); ++iter) - { - LLViewerObject* childp = *iter; - if (childp && childp->mDrawable.notNull()) - { - gPipeline.markTextured(childp->mDrawable); // face may need to change draw pool to/from POOL_HUD - gPipeline.markMoved(childp->mDrawable); - - if(mIsHUDAttachment) - { - for (S32 face_num = 0; face_num < childp->mDrawable->getNumFaces(); face_num++) - { - LLFace * face = childp->mDrawable->getFace(face_num); - if (face) - { - face->setState(LLFace::HUD_RENDER); - } - } - } - } - } -} - -//----------------------------------------------------------------------------- -// addObject() -//----------------------------------------------------------------------------- -bool LLViewerJointAttachment::addObject(LLViewerObject* object) -{ - object->extractAttachmentItemID(); - - // Same object reattached - if (isObjectAttached(object)) - { - LL_INFOS() << "(same object re-attached)" << LL_ENDL; - removeObject(object); - // Pass through anyway to let setupDrawable() - // re-connect object to the joint correctly - } - - // Two instances of the same inventory item attached -- - // Request detach, and kill the object in the meantime. - if (getAttachedObject(object->getAttachmentItemID())) - { - LL_INFOS() << "(same object re-attached)" << LL_ENDL; - object->markDead(); - - // If this happens to be attached to self, then detach. - LLVOAvatarSelf::detachAttachmentIntoInventory(object->getAttachmentItemID()); - return false; - } - - mAttachedObjects.push_back(object); - setupDrawable(object); - - if (mIsHUDAttachment) - { - if (object->mText.notNull()) - { - object->mText->setOnHUDAttachment(true); - } - LLViewerObject::const_child_list_t& child_list = object->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); ++iter) - { - LLViewerObject* childp = *iter; - if (childp && childp->mText.notNull()) - { - childp->mText->setOnHUDAttachment(true); - } - } - } - calcLOD(); - mUpdateXform = true; - - return true; -} - -//----------------------------------------------------------------------------- -// removeObject() -//----------------------------------------------------------------------------- -void LLViewerJointAttachment::removeObject(LLViewerObject *object) -{ - attachedobjs_vec_t::iterator iter; - for (iter = mAttachedObjects.begin(); - iter != mAttachedObjects.end(); - ++iter) - { - LLViewerObject *attached_object = iter->get(); - if (attached_object == object) - { - break; - } - } - if (iter == mAttachedObjects.end()) - { - LL_WARNS() << "Could not find object to detach" << LL_ENDL; - return; - } - - // force object visibile - setAttachmentVisibility(true); - - mAttachedObjects.erase(iter); - if (object->mDrawable.notNull()) - { - //if object is active, make it static - if(object->mDrawable->isActive()) - { - object->mDrawable->makeStatic(false); - } - - LLVector3 cur_position = object->getRenderPosition(); - LLQuaternion cur_rotation = object->getRenderRotation(); - - object->mDrawable->mXform.setPosition(cur_position); - object->mDrawable->mXform.setRotation(cur_rotation); - gPipeline.markMoved(object->mDrawable, true); - gPipeline.markTextured(object->mDrawable); // face may need to change draw pool to/from POOL_HUD - - if (mIsHUDAttachment) - { - for (S32 face_num = 0; face_num < object->mDrawable->getNumFaces(); face_num++) - { - LLFace * face = object->mDrawable->getFace(face_num); - if (face) - { - face->clearState(LLFace::HUD_RENDER); - } - } - } - } - - LLViewerObject::const_child_list_t& child_list = object->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); ++iter) - { - LLViewerObject* childp = *iter; - if (childp && childp->mDrawable.notNull()) - { - gPipeline.markTextured(childp->mDrawable); // face may need to change draw pool to/from POOL_HUD - if (mIsHUDAttachment) - { - for (S32 face_num = 0; face_num < childp->mDrawable->getNumFaces(); face_num++) - { - LLFace * face = childp->mDrawable->getFace(face_num); - if (face) - { - face->clearState(LLFace::HUD_RENDER); - } - } - } - } - } - - if (mIsHUDAttachment) - { - if (object->mText.notNull()) - { - object->mText->setOnHUDAttachment(false); - } - LLViewerObject::const_child_list_t& child_list = object->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); ++iter) - { - LLViewerObject* childp = *iter; - if (childp->mText.notNull()) - { - childp->mText->setOnHUDAttachment(false); - } - } - } - if (mAttachedObjects.size() == 0) - { - mUpdateXform = false; - } - object->setAttachmentItemID(LLUUID::null); -} - -//----------------------------------------------------------------------------- -// setAttachmentVisibility() -//----------------------------------------------------------------------------- -void LLViewerJointAttachment::setAttachmentVisibility(bool visible) -{ - for (attachedobjs_vec_t::const_iterator iter = mAttachedObjects.begin(); - iter != mAttachedObjects.end(); - ++iter) - { - LLViewerObject *attached_obj = iter->get(); - if (!attached_obj || attached_obj->mDrawable.isNull() || - !(attached_obj->mDrawable->getSpatialBridge())) - continue; - - if (visible) - { - // Hack to make attachments not visible by disabling their type mask! - // This will break if you can ever attach non-volumes! - djs 02/14/03 - attached_obj->mDrawable->getSpatialBridge()->mDrawableType = - attached_obj->isHUDAttachment() ? LLPipeline::RENDER_TYPE_HUD : LLPipeline::RENDER_TYPE_VOLUME; - } - else - { - attached_obj->mDrawable->getSpatialBridge()->mDrawableType = 0; - } - } -} - -//----------------------------------------------------------------------------- -// setOriginalPosition() -//----------------------------------------------------------------------------- -void LLViewerJointAttachment::setOriginalPosition(LLVector3& position) -{ - mOriginalPos = position; - // SL-315 - setPosition(position); -} - -//----------------------------------------------------------------------------- -// getNumAnimatedObjects() -//----------------------------------------------------------------------------- -S32 LLViewerJointAttachment::getNumAnimatedObjects() const -{ - S32 count = 0; - for (attachedobjs_vec_t::const_iterator iter = mAttachedObjects.begin(); - iter != mAttachedObjects.end(); - ++iter) - { - const LLViewerObject *attached_object = iter->get(); - if (attached_object->isAnimatedObject()) - { - count++; - } - } - return count; -} - -//----------------------------------------------------------------------------- -// clampObjectPosition() -//----------------------------------------------------------------------------- -void LLViewerJointAttachment::clampObjectPosition() -{ - for (attachedobjs_vec_t::const_iterator iter = mAttachedObjects.begin(); - iter != mAttachedObjects.end(); - ++iter) - { - if (LLViewerObject *attached_object = iter->get()) - { - // *NOTE: object can drift when hitting maximum radius - LLVector3 attachmentPos = attached_object->getPosition(); - F32 dist = attachmentPos.normVec(); - dist = llmin(dist, MAX_ATTACHMENT_DIST); - attachmentPos *= dist; - attached_object->setPosition(attachmentPos); - } - } -} - -//----------------------------------------------------------------------------- -// calcLOD() -//----------------------------------------------------------------------------- -void LLViewerJointAttachment::calcLOD() -{ - F32 maxarea = 0; - for (attachedobjs_vec_t::const_iterator iter = mAttachedObjects.begin(); - iter != mAttachedObjects.end(); - ++iter) - { - if (LLViewerObject *attached_object = iter->get()) - { - maxarea = llmax(maxarea,attached_object->getMaxScale() * attached_object->getMidScale()); - LLViewerObject::const_child_list_t& child_list = attached_object->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); ++iter) - { - LLViewerObject* childp = *iter; - F32 area = childp->getMaxScale() * childp->getMidScale(); - maxarea = llmax(maxarea, area); - } - } - } - maxarea = llclamp(maxarea, .01f*.01f, 1.f); - F32 avatar_area = (4.f * 4.f); // pixels for an avatar sized attachment - F32 min_pixel_area = avatar_area / maxarea; - setLOD(min_pixel_area); -} - -//----------------------------------------------------------------------------- -// updateLOD() -//----------------------------------------------------------------------------- -bool LLViewerJointAttachment::updateLOD(F32 pixel_area, bool activate) -{ - bool res{ false }; - if (!mValid) - { - setValid(true, true); - res = true; - } - return res; -} - -bool LLViewerJointAttachment::isObjectAttached(const LLViewerObject *viewer_object) const -{ - for (attachedobjs_vec_t::const_iterator iter = mAttachedObjects.begin(); - iter != mAttachedObjects.end(); - ++iter) - { - const LLViewerObject* attached_object = iter->get(); - if (attached_object == viewer_object) - { - return true; - } - } - return false; -} - -const LLViewerObject *LLViewerJointAttachment::getAttachedObject(const LLUUID &object_id) const -{ - for (attachedobjs_vec_t::const_iterator iter = mAttachedObjects.begin(); - iter != mAttachedObjects.end(); - ++iter) - { - const LLViewerObject* attached_object = iter->get(); - if (attached_object->getAttachmentItemID() == object_id) - { - return attached_object; - } - } - return nullptr; -} - -LLViewerObject *LLViewerJointAttachment::getAttachedObject(const LLUUID &object_id) -{ - for (attachedobjs_vec_t::iterator iter = mAttachedObjects.begin(); - iter != mAttachedObjects.end(); - ++iter) - { - LLViewerObject* attached_object = iter->get(); - if (attached_object->getAttachmentItemID() == object_id) - { - return attached_object; - } - } - return nullptr; -} +/** + * @file llviewerjointattachment.cpp + * @brief Implementation of LLViewerJointAttachment class + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llviewerjointattachment.h" + +#include "llviewercontrol.h" +#include "lldrawable.h" +#include "llgl.h" +#include "llhudtext.h" +#include "llrender.h" +#include "llvoavatarself.h" +#include "llvolume.h" +#include "pipeline.h" +#include "llspatialpartition.h" +#include "llinventorymodel.h" +#include "llviewerobjectlist.h" +#include "llface.h" +#include "llvoavatar.h" + +#include "llglheaders.h" + +extern LLPipeline gPipeline; +constexpr F32 MAX_ATTACHMENT_DIST = 3.5f; // meters + +//----------------------------------------------------------------------------- +// LLViewerJointAttachment() +//----------------------------------------------------------------------------- +LLViewerJointAttachment::LLViewerJointAttachment() : + mVisibleInFirst(false), + mGroup(0), + mIsHUDAttachment(false), + mPieSlice(-1) +{ + mValid = false; + mUpdateXform = false; + mAttachedObjects.clear(); +} + +//----------------------------------------------------------------------------- +// ~LLViewerJointAttachment() +//----------------------------------------------------------------------------- +LLViewerJointAttachment::~LLViewerJointAttachment() +{ +} + +//----------------------------------------------------------------------------- +// isTransparent() +//----------------------------------------------------------------------------- +bool LLViewerJointAttachment::isTransparent() +{ + return false; +} + +//----------------------------------------------------------------------------- +// drawShape() +//----------------------------------------------------------------------------- +U32 LLViewerJointAttachment::drawShape( F32 pixelArea, bool first_pass, bool is_dummy ) +{ + if (LLVOAvatar::sShowAttachmentPoints) + { + LLGLDisable cull_face(GL_CULL_FACE); + + gGL.color4f(1.f, 1.f, 1.f, 1.f); + gGL.begin(LLRender::QUADS); + { + gGL.vertex3f(-0.1f, 0.1f, 0.f); + gGL.vertex3f(-0.1f, -0.1f, 0.f); + gGL.vertex3f(0.1f, -0.1f, 0.f); + gGL.vertex3f(0.1f, 0.1f, 0.f); + }gGL.end(); + } + return 0; +} + +void LLViewerJointAttachment::setupDrawable(LLViewerObject *object) +{ + if (!object->mDrawable) + return; + if (object->mDrawable->isActive()) + { + object->mDrawable->makeStatic(false); + } + + object->mDrawable->mXform.setParent(getXform()); // LLViewerJointAttachment::lazyAttach + object->mDrawable->makeActive(); + LLVector3 current_pos = object->getRenderPosition(); + LLQuaternion current_rot = object->getRenderRotation(); + LLQuaternion attachment_pt_inv_rot = ~(getWorldRotation()); + + current_pos -= getWorldPosition(); + current_pos.rotVec(attachment_pt_inv_rot); + + current_rot = current_rot * attachment_pt_inv_rot; + + object->mDrawable->mXform.setPosition(current_pos); + object->mDrawable->mXform.setRotation(current_rot); + gPipeline.markMoved(object->mDrawable); + gPipeline.markTextured(object->mDrawable); // face may need to change draw pool to/from POOL_HUD + + if(mIsHUDAttachment) + { + for (S32 face_num = 0; face_num < object->mDrawable->getNumFaces(); face_num++) + { + LLFace *face = object->mDrawable->getFace(face_num); + if (face) + { + face->setState(LLFace::HUD_RENDER); + } + } + } + + LLViewerObject::const_child_list_t& child_list = object->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); ++iter) + { + LLViewerObject* childp = *iter; + if (childp && childp->mDrawable.notNull()) + { + gPipeline.markTextured(childp->mDrawable); // face may need to change draw pool to/from POOL_HUD + gPipeline.markMoved(childp->mDrawable); + + if(mIsHUDAttachment) + { + for (S32 face_num = 0; face_num < childp->mDrawable->getNumFaces(); face_num++) + { + LLFace * face = childp->mDrawable->getFace(face_num); + if (face) + { + face->setState(LLFace::HUD_RENDER); + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +// addObject() +//----------------------------------------------------------------------------- +bool LLViewerJointAttachment::addObject(LLViewerObject* object) +{ + object->extractAttachmentItemID(); + + // Same object reattached + if (isObjectAttached(object)) + { + LL_INFOS() << "(same object re-attached)" << LL_ENDL; + removeObject(object); + // Pass through anyway to let setupDrawable() + // re-connect object to the joint correctly + } + + // Two instances of the same inventory item attached -- + // Request detach, and kill the object in the meantime. + if (getAttachedObject(object->getAttachmentItemID())) + { + LL_INFOS() << "(same object re-attached)" << LL_ENDL; + object->markDead(); + + // If this happens to be attached to self, then detach. + LLVOAvatarSelf::detachAttachmentIntoInventory(object->getAttachmentItemID()); + return false; + } + + mAttachedObjects.push_back(object); + setupDrawable(object); + + if (mIsHUDAttachment) + { + if (object->mText.notNull()) + { + object->mText->setOnHUDAttachment(true); + } + LLViewerObject::const_child_list_t& child_list = object->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); ++iter) + { + LLViewerObject* childp = *iter; + if (childp && childp->mText.notNull()) + { + childp->mText->setOnHUDAttachment(true); + } + } + } + calcLOD(); + mUpdateXform = true; + + return true; +} + +//----------------------------------------------------------------------------- +// removeObject() +//----------------------------------------------------------------------------- +void LLViewerJointAttachment::removeObject(LLViewerObject *object) +{ + attachedobjs_vec_t::iterator iter; + for (iter = mAttachedObjects.begin(); + iter != mAttachedObjects.end(); + ++iter) + { + LLViewerObject *attached_object = iter->get(); + if (attached_object == object) + { + break; + } + } + if (iter == mAttachedObjects.end()) + { + LL_WARNS() << "Could not find object to detach" << LL_ENDL; + return; + } + + // force object visibile + setAttachmentVisibility(true); + + mAttachedObjects.erase(iter); + if (object->mDrawable.notNull()) + { + //if object is active, make it static + if(object->mDrawable->isActive()) + { + object->mDrawable->makeStatic(false); + } + + LLVector3 cur_position = object->getRenderPosition(); + LLQuaternion cur_rotation = object->getRenderRotation(); + + object->mDrawable->mXform.setPosition(cur_position); + object->mDrawable->mXform.setRotation(cur_rotation); + gPipeline.markMoved(object->mDrawable, true); + gPipeline.markTextured(object->mDrawable); // face may need to change draw pool to/from POOL_HUD + + if (mIsHUDAttachment) + { + for (S32 face_num = 0; face_num < object->mDrawable->getNumFaces(); face_num++) + { + LLFace * face = object->mDrawable->getFace(face_num); + if (face) + { + face->clearState(LLFace::HUD_RENDER); + } + } + } + } + + LLViewerObject::const_child_list_t& child_list = object->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); ++iter) + { + LLViewerObject* childp = *iter; + if (childp && childp->mDrawable.notNull()) + { + gPipeline.markTextured(childp->mDrawable); // face may need to change draw pool to/from POOL_HUD + if (mIsHUDAttachment) + { + for (S32 face_num = 0; face_num < childp->mDrawable->getNumFaces(); face_num++) + { + LLFace * face = childp->mDrawable->getFace(face_num); + if (face) + { + face->clearState(LLFace::HUD_RENDER); + } + } + } + } + } + + if (mIsHUDAttachment) + { + if (object->mText.notNull()) + { + object->mText->setOnHUDAttachment(false); + } + LLViewerObject::const_child_list_t& child_list = object->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); ++iter) + { + LLViewerObject* childp = *iter; + if (childp->mText.notNull()) + { + childp->mText->setOnHUDAttachment(false); + } + } + } + if (mAttachedObjects.size() == 0) + { + mUpdateXform = false; + } + object->setAttachmentItemID(LLUUID::null); +} + +//----------------------------------------------------------------------------- +// setAttachmentVisibility() +//----------------------------------------------------------------------------- +void LLViewerJointAttachment::setAttachmentVisibility(bool visible) +{ + for (attachedobjs_vec_t::const_iterator iter = mAttachedObjects.begin(); + iter != mAttachedObjects.end(); + ++iter) + { + LLViewerObject *attached_obj = iter->get(); + if (!attached_obj || attached_obj->mDrawable.isNull() || + !(attached_obj->mDrawable->getSpatialBridge())) + continue; + + if (visible) + { + // Hack to make attachments not visible by disabling their type mask! + // This will break if you can ever attach non-volumes! - djs 02/14/03 + attached_obj->mDrawable->getSpatialBridge()->mDrawableType = + attached_obj->isHUDAttachment() ? LLPipeline::RENDER_TYPE_HUD : LLPipeline::RENDER_TYPE_VOLUME; + } + else + { + attached_obj->mDrawable->getSpatialBridge()->mDrawableType = 0; + } + } +} + +//----------------------------------------------------------------------------- +// setOriginalPosition() +//----------------------------------------------------------------------------- +void LLViewerJointAttachment::setOriginalPosition(LLVector3& position) +{ + mOriginalPos = position; + // SL-315 + setPosition(position); +} + +//----------------------------------------------------------------------------- +// getNumAnimatedObjects() +//----------------------------------------------------------------------------- +S32 LLViewerJointAttachment::getNumAnimatedObjects() const +{ + S32 count = 0; + for (attachedobjs_vec_t::const_iterator iter = mAttachedObjects.begin(); + iter != mAttachedObjects.end(); + ++iter) + { + const LLViewerObject *attached_object = iter->get(); + if (attached_object->isAnimatedObject()) + { + count++; + } + } + return count; +} + +//----------------------------------------------------------------------------- +// clampObjectPosition() +//----------------------------------------------------------------------------- +void LLViewerJointAttachment::clampObjectPosition() +{ + for (attachedobjs_vec_t::const_iterator iter = mAttachedObjects.begin(); + iter != mAttachedObjects.end(); + ++iter) + { + if (LLViewerObject *attached_object = iter->get()) + { + // *NOTE: object can drift when hitting maximum radius + LLVector3 attachmentPos = attached_object->getPosition(); + F32 dist = attachmentPos.normVec(); + dist = llmin(dist, MAX_ATTACHMENT_DIST); + attachmentPos *= dist; + attached_object->setPosition(attachmentPos); + } + } +} + +//----------------------------------------------------------------------------- +// calcLOD() +//----------------------------------------------------------------------------- +void LLViewerJointAttachment::calcLOD() +{ + F32 maxarea = 0; + for (attachedobjs_vec_t::const_iterator iter = mAttachedObjects.begin(); + iter != mAttachedObjects.end(); + ++iter) + { + if (LLViewerObject *attached_object = iter->get()) + { + maxarea = llmax(maxarea,attached_object->getMaxScale() * attached_object->getMidScale()); + LLViewerObject::const_child_list_t& child_list = attached_object->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); ++iter) + { + LLViewerObject* childp = *iter; + F32 area = childp->getMaxScale() * childp->getMidScale(); + maxarea = llmax(maxarea, area); + } + } + } + maxarea = llclamp(maxarea, .01f*.01f, 1.f); + F32 avatar_area = (4.f * 4.f); // pixels for an avatar sized attachment + F32 min_pixel_area = avatar_area / maxarea; + setLOD(min_pixel_area); +} + +//----------------------------------------------------------------------------- +// updateLOD() +//----------------------------------------------------------------------------- +bool LLViewerJointAttachment::updateLOD(F32 pixel_area, bool activate) +{ + bool res{ false }; + if (!mValid) + { + setValid(true, true); + res = true; + } + return res; +} + +bool LLViewerJointAttachment::isObjectAttached(const LLViewerObject *viewer_object) const +{ + for (attachedobjs_vec_t::const_iterator iter = mAttachedObjects.begin(); + iter != mAttachedObjects.end(); + ++iter) + { + const LLViewerObject* attached_object = iter->get(); + if (attached_object == viewer_object) + { + return true; + } + } + return false; +} + +const LLViewerObject *LLViewerJointAttachment::getAttachedObject(const LLUUID &object_id) const +{ + for (attachedobjs_vec_t::const_iterator iter = mAttachedObjects.begin(); + iter != mAttachedObjects.end(); + ++iter) + { + const LLViewerObject* attached_object = iter->get(); + if (attached_object->getAttachmentItemID() == object_id) + { + return attached_object; + } + } + return nullptr; +} + +LLViewerObject *LLViewerJointAttachment::getAttachedObject(const LLUUID &object_id) +{ + for (attachedobjs_vec_t::iterator iter = mAttachedObjects.begin(); + iter != mAttachedObjects.end(); + ++iter) + { + LLViewerObject* attached_object = iter->get(); + if (attached_object->getAttachmentItemID() == object_id) + { + return attached_object; + } + } + return nullptr; +} diff --git a/indra/newview/llviewerjointattachment.h b/indra/newview/llviewerjointattachment.h index ee86adcb24..3869213cc5 100644 --- a/indra/newview/llviewerjointattachment.h +++ b/indra/newview/llviewerjointattachment.h @@ -1,111 +1,111 @@ -/** - * @file llviewerjointattachment.h - * @brief Implementation of LLViewerJointAttachment class - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVIEWERJOINTATTACHMENT_H -#define LL_LLVIEWERJOINTATTACHMENT_H - -//----------------------------------------------------------------------------- -// Header Files -//----------------------------------------------------------------------------- -#include "llviewerjoint.h" -#include "llstring.h" -#include "lluuid.h" - -class LLDrawable; -class LLViewerObject; - -//----------------------------------------------------------------------------- -// class LLViewerJointAttachment -//----------------------------------------------------------------------------- -class LLViewerJointAttachment : - public LLViewerJoint -{ -public: - LLViewerJointAttachment(); - virtual ~LLViewerJointAttachment(); - - // Returns true if this object is transparent. - // This is used to determine in which order to draw objects. - bool isTransparent() override; - - // Draws the shape attached to a joint. - // Called by render(). - U32 drawShape( F32 pixelArea, bool first_pass, bool is_dummy ) override; - - bool updateLOD(F32 pixel_area, bool activate) override; - - // - // accessors - // - - void setPieSlice(S32 pie_slice) { mPieSlice = pie_slice; } - void setVisibleInFirstPerson(bool visibility) { mVisibleInFirst = visibility; } - bool getVisibleInFirstPerson() const { return mVisibleInFirst; } - void setGroup(S32 group) { mGroup = group; } - void setOriginalPosition(LLVector3 &position); - void setAttachmentVisibility(bool visible); - void setIsHUDAttachment(bool is_hud) { mIsHUDAttachment = is_hud; } - bool getIsHUDAttachment() const { return mIsHUDAttachment; } - - bool isAnimatable() const override { return false; } - - S32 getGroup() const { return mGroup; } - S32 getPieSlice() const { return mPieSlice; } - S32 getNumObjects() const { return mAttachedObjects.size(); } - S32 getNumAnimatedObjects() const; - - void clampObjectPosition(); - - // - // unique methods - // - bool addObject(LLViewerObject* object); - void removeObject(LLViewerObject *object); - - // - // attachments operations - // - bool isObjectAttached(const LLViewerObject *viewer_object) const; - const LLViewerObject *getAttachedObject(const LLUUID &object_id) const; - LLViewerObject *getAttachedObject(const LLUUID &object_id); - - // list of attachments for this joint - typedef std::vector > attachedobjs_vec_t; - attachedobjs_vec_t mAttachedObjects; - -protected: - void calcLOD(); - void setupDrawable(LLViewerObject *object); - -private: - bool mVisibleInFirst; - LLVector3 mOriginalPos; - S32 mGroup; - bool mIsHUDAttachment; - S32 mPieSlice; -}; - -#endif // LL_LLVIEWERJOINTATTACHMENT_H +/** + * @file llviewerjointattachment.h + * @brief Implementation of LLViewerJointAttachment class + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVIEWERJOINTATTACHMENT_H +#define LL_LLVIEWERJOINTATTACHMENT_H + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "llviewerjoint.h" +#include "llstring.h" +#include "lluuid.h" + +class LLDrawable; +class LLViewerObject; + +//----------------------------------------------------------------------------- +// class LLViewerJointAttachment +//----------------------------------------------------------------------------- +class LLViewerJointAttachment : + public LLViewerJoint +{ +public: + LLViewerJointAttachment(); + virtual ~LLViewerJointAttachment(); + + // Returns true if this object is transparent. + // This is used to determine in which order to draw objects. + bool isTransparent() override; + + // Draws the shape attached to a joint. + // Called by render(). + U32 drawShape( F32 pixelArea, bool first_pass, bool is_dummy ) override; + + bool updateLOD(F32 pixel_area, bool activate) override; + + // + // accessors + // + + void setPieSlice(S32 pie_slice) { mPieSlice = pie_slice; } + void setVisibleInFirstPerson(bool visibility) { mVisibleInFirst = visibility; } + bool getVisibleInFirstPerson() const { return mVisibleInFirst; } + void setGroup(S32 group) { mGroup = group; } + void setOriginalPosition(LLVector3 &position); + void setAttachmentVisibility(bool visible); + void setIsHUDAttachment(bool is_hud) { mIsHUDAttachment = is_hud; } + bool getIsHUDAttachment() const { return mIsHUDAttachment; } + + bool isAnimatable() const override { return false; } + + S32 getGroup() const { return mGroup; } + S32 getPieSlice() const { return mPieSlice; } + S32 getNumObjects() const { return mAttachedObjects.size(); } + S32 getNumAnimatedObjects() const; + + void clampObjectPosition(); + + // + // unique methods + // + bool addObject(LLViewerObject* object); + void removeObject(LLViewerObject *object); + + // + // attachments operations + // + bool isObjectAttached(const LLViewerObject *viewer_object) const; + const LLViewerObject *getAttachedObject(const LLUUID &object_id) const; + LLViewerObject *getAttachedObject(const LLUUID &object_id); + + // list of attachments for this joint + typedef std::vector > attachedobjs_vec_t; + attachedobjs_vec_t mAttachedObjects; + +protected: + void calcLOD(); + void setupDrawable(LLViewerObject *object); + +private: + bool mVisibleInFirst; + LLVector3 mOriginalPos; + S32 mGroup; + bool mIsHUDAttachment; + S32 mPieSlice; +}; + +#endif // LL_LLVIEWERJOINTATTACHMENT_H diff --git a/indra/newview/llviewerjointmesh.cpp b/indra/newview/llviewerjointmesh.cpp index cf228a97e2..ebc9b88709 100644 --- a/indra/newview/llviewerjointmesh.cpp +++ b/indra/newview/llviewerjointmesh.cpp @@ -1,525 +1,525 @@ - /** - * @file llviewerjointmesh.cpp - * @brief Implementation of LLViewerJointMesh class - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -//----------------------------------------------------------------------------- -// Header Files -//----------------------------------------------------------------------------- -#include "llviewerprecompiledheaders.h" - -#include "llfasttimer.h" -#include "llrender.h" - -#include "llapr.h" -#include "llbox.h" -#include "lldrawable.h" -#include "lldrawpoolavatar.h" -#include "lldrawpoolbump.h" -#include "lldynamictexture.h" -#include "llface.h" -#include "llglheaders.h" -#include "llviewertexlayer.h" -#include "llviewercamera.h" -#include "llviewercontrol.h" -#include "llviewertexturelist.h" -#include "llviewerjointmesh.h" -#include "llvoavatar.h" -#include "llsky.h" -#include "pipeline.h" -#include "llviewershadermgr.h" -#include "llmath.h" -#include "v4math.h" -#include "m3math.h" -#include "m4math.h" -#include "llmatrix4a.h" -#include "llperfstats.h" - -#if !LL_DARWIN && !LL_LINUX -extern PFNGLWEIGHTPOINTERARBPROC glWeightPointerARB; -extern PFNGLWEIGHTFVARBPROC glWeightfvARB; -extern PFNGLVERTEXBLENDARBPROC glVertexBlendARB; -#endif - -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- -// LLViewerJointMesh -//----------------------------------------------------------------------------- -//----------------------------------------------------------------------------- - - -//----------------------------------------------------------------------------- -// LLViewerJointMesh() -//----------------------------------------------------------------------------- -LLViewerJointMesh::LLViewerJointMesh() - : - LLAvatarJointMesh() -{ -} - - -//----------------------------------------------------------------------------- -// ~LLViewerJointMesh() -// Class Destructor -//----------------------------------------------------------------------------- -LLViewerJointMesh::~LLViewerJointMesh() -{ -} - -const S32 NUM_AXES = 3; - -// register layoud -// rotation X 0-n -// rotation Y 0-n -// rotation Z 0-n -// pivot parent 0-n -- child = n+1 - -static LLMatrix4 gJointMatUnaligned[32]; -static LLMatrix4a gJointMatAligned[32]; -static LLMatrix3 gJointRotUnaligned[32]; -static LLVector4 gJointPivot[32]; - -//----------------------------------------------------------------------------- -// uploadJointMatrices() -//----------------------------------------------------------------------------- -void LLViewerJointMesh::uploadJointMatrices() -{ - S32 joint_num; - LLPolyMesh *reference_mesh = mMesh->getReferenceMesh(); - LLDrawPool *poolp = mFace ? mFace->getPool() : NULL; - bool hardware_skinning = (poolp && poolp->getShaderLevel() > 0); - - //calculate joint matrices - for (joint_num = 0; joint_num < reference_mesh->mJointRenderData.size(); joint_num++) - { - LLMatrix4 joint_mat = *reference_mesh->mJointRenderData[joint_num]->mWorldMatrix; - - if (hardware_skinning) - { - joint_mat *= LLDrawPoolAvatar::getModelView(); - } - gJointMatUnaligned[joint_num] = joint_mat; - gJointRotUnaligned[joint_num] = joint_mat.getMat3(); - } - - bool last_pivot_uploaded{ false }; - S32 j = 0; - - //upload joint pivots - for (joint_num = 0; joint_num < reference_mesh->mJointRenderData.size(); joint_num++) - { - LLSkinJoint *sj = reference_mesh->mJointRenderData[joint_num]->mSkinJoint; - if (sj) - { - if (!last_pivot_uploaded) - { - LLVector4 parent_pivot(sj->mRootToParentJointSkinOffset); - parent_pivot.mV[VW] = 0.f; - gJointPivot[j++] = parent_pivot; - } - - LLVector4 child_pivot(sj->mRootToJointSkinOffset); - child_pivot.mV[VW] = 0.f; - - gJointPivot[j++] = child_pivot; - - last_pivot_uploaded = true; - } - else - { - last_pivot_uploaded = false; - } - } - - //add pivot point into transform - for (S32 i = 0; i < j; i++) - { - LLVector3 pivot; - pivot = LLVector3(gJointPivot[i]); - pivot = pivot * gJointRotUnaligned[i]; - gJointMatUnaligned[i].translate(pivot); - } - - // upload matrices - if (hardware_skinning) - { - GLfloat mat[45*4]; - memset(mat, 0, sizeof(GLfloat)*45*4); - - for (joint_num = 0; joint_num < reference_mesh->mJointRenderData.size(); joint_num++) - { - gJointMatUnaligned[joint_num].transpose(); - - for (S32 axis = 0; axis < NUM_AXES; axis++) - { - F32* vector = gJointMatUnaligned[joint_num].mMatrix[axis]; - U32 offset = LL_CHARACTER_MAX_JOINTS_PER_MESH*axis+joint_num; - memcpy(mat+offset*4, vector, sizeof(GLfloat)*4); - } - } - stop_glerror(); - if (LLGLSLShader::sCurBoundShaderPtr) - { - LLGLSLShader::sCurBoundShaderPtr->uniform4fv(LLViewerShaderMgr::AVATAR_MATRIX, 45, mat); - } - stop_glerror(); - } - else - { - //load gJointMatUnaligned into gJointMatAligned - for (joint_num = 0; joint_num < reference_mesh->mJointRenderData.size(); ++joint_num) - { - gJointMatAligned[joint_num].loadu(gJointMatUnaligned[joint_num]); - } - } -} - -//-------------------------------------------------------------------- -// DrawElementsBLEND and utility code -//-------------------------------------------------------------------- - -// compare_int is used by the qsort function to sort the index array -int compare_int(const void *a, const void *b) -{ - if (*(U32*)a < *(U32*)b) - { - return -1; - } - else if (*(U32*)a > *(U32*)b) - { - return 1; - } - else return 0; -} - -//-------------------------------------------------------------------- -// LLViewerJointMesh::drawShape() -//-------------------------------------------------------------------- -U32 LLViewerJointMesh::drawShape( F32 pixelArea, bool first_pass, bool is_dummy) -{ - if (!mValid || !mMesh || !mFace || !mVisible || - !mFace->getVertexBuffer() || - mMesh->getNumFaces() == 0 || - LLGLSLShader::sCurBoundShaderPtr == NULL) - { - return 0; - } - - U32 triangle_count = 0; - - S32 diffuse_channel = LLDrawPoolAvatar::sDiffuseChannel; - - stop_glerror(); - - //---------------------------------------------------------------- - // setup current color - //---------------------------------------------------------------- - if (is_dummy) - gGL.diffuseColor4fv(LLVOAvatar::getDummyColor().mV); - else - gGL.diffuseColor4fv(mColor.mV); - - stop_glerror(); - - LLGLSSpecular specular(LLColor4(1.f,1.f,1.f,1.f), 0.f); - - //---------------------------------------------------------------- - // setup current texture - //---------------------------------------------------------------- - llassert( !(mTexture.notNull() && mLayerSet) ); // mutually exclusive - - LLViewerTexLayerSet *layerset = dynamic_cast(mLayerSet); - if (mTestImageName) - { - gGL.getTexUnit(diffuse_channel)->bindManual(LLTexUnit::TT_TEXTURE, mTestImageName); - - if (mIsTransparent) - { - gGL.diffuseColor4f(1.f, 1.f, 1.f, 1.f); - } - else - { - gGL.diffuseColor4f(0.7f, 0.6f, 0.3f, 1.f); - } - } - else if( !is_dummy && layerset ) - { - if( layerset->hasComposite() ) - { - gGL.getTexUnit(diffuse_channel)->bind(layerset->getViewerComposite()); - } - else - { - gGL.getTexUnit(diffuse_channel)->bind(LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT)); - } - } - else if ( !is_dummy && mTexture.notNull() ) - { - gGL.getTexUnit(diffuse_channel)->bind(mTexture); - } - else - { - gGL.getTexUnit(diffuse_channel)->bind(LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT)); - } - - U32 start = mMesh->mFaceVertexOffset; - U32 end = start + mMesh->mFaceVertexCount - 1; - U32 count = mMesh->mFaceIndexCount; - U32 offset = mMesh->mFaceIndexOffset; - - LLVertexBuffer* buff = mFace->getVertexBuffer(); - - if (mMesh->hasWeights()) - { - if ((mFace->getPool()->getShaderLevel() > 0)) - { - if (first_pass) - { - uploadJointMatrices(); - } - } - - buff->setBuffer(); - buff->drawRange(LLRender::TRIANGLES, start, end, count, offset); - } - else - { - gGL.pushMatrix(); - LLMatrix4 jointToWorld = getWorldMatrix(); - gGL.multMatrix((GLfloat*)jointToWorld.mMatrix); - buff->setBuffer(); - buff->drawRange(LLRender::TRIANGLES, start, end, count, offset); - gGL.popMatrix(); - } - gPipeline.addTrianglesDrawn(count); - - triangle_count += count; - - return triangle_count; -} - -//----------------------------------------------------------------------------- -// updateFaceSizes() -//----------------------------------------------------------------------------- -void LLViewerJointMesh::updateFaceSizes(U32 &num_vertices, U32& num_indices, F32 pixel_area) -{ - //bump num_vertices to next multiple of 4 - num_vertices = (num_vertices + 0x3) & ~0x3; - - // Do a pre-alloc pass to determine sizes of data. - if (mMesh && mValid) - { - mMesh->mFaceVertexOffset = num_vertices; - mMesh->mFaceVertexCount = mMesh->getNumVertices(); - mMesh->mFaceIndexOffset = num_indices; - mMesh->mFaceIndexCount = mMesh->getSharedData()->mNumTriangleIndices; - - mMesh->getReferenceMesh()->mCurVertexCount = mMesh->mFaceVertexCount; - - num_vertices += mMesh->getNumVertices(); - num_indices += mMesh->mFaceIndexCount; - } -} - -//----------------------------------------------------------------------------- -// updateFaceData() -//----------------------------------------------------------------------------- - -void LLViewerJointMesh::updateFaceData(LLFace *face, F32 pixel_area, bool damp_wind, bool terse_update) -{ - //IF THIS FUNCTION BREAKS, SEE LLPOLYMESH CONSTRUCTOR AND CHECK ALIGNMENT OF INPUT ARRAYS - - mFace = face; - - if (!mFace->getVertexBuffer()) - { - return; - } - - LLDrawPool *poolp = mFace->getPool(); - bool hardware_skinning = (poolp && poolp->getShaderLevel() > 0); - - if (!hardware_skinning && terse_update) - { //no need to do terse updates if we're doing software vertex skinning - // since mMesh is being copied into mVertexBuffer every frame - return; - } - - LL_PROFILE_ZONE_SCOPED; - - LLStrider verticesp; - LLStrider normalsp; - LLStrider tex_coordsp; - LLStrider vertex_weightsp; - LLStrider clothing_weightsp; - LLStrider indicesp; - - // Copy data into the faces from the polymesh data. - if (mMesh && mValid) - { - const U32 num_verts = mMesh->getNumVertices(); - - if (num_verts) - { - face->getVertexBuffer()->getIndexStrider(indicesp); - face->getGeometryAvatar(verticesp, normalsp, tex_coordsp, vertex_weightsp, clothing_weightsp); - - verticesp += mMesh->mFaceVertexOffset; - normalsp += mMesh->mFaceVertexOffset; - - F32* v = (F32*) verticesp.get(); - F32* n = (F32*) normalsp.get(); - - U32 words = num_verts*4; - - LLVector4a::memcpyNonAliased16(v, (F32*) mMesh->getCoords(), words*sizeof(F32)); - LLVector4a::memcpyNonAliased16(n, (F32*) mMesh->getNormals(), words*sizeof(F32)); - - - if (!terse_update) - { - vertex_weightsp += mMesh->mFaceVertexOffset; - clothing_weightsp += mMesh->mFaceVertexOffset; - tex_coordsp += mMesh->mFaceVertexOffset; - - F32* tc = (F32*) tex_coordsp.get(); - F32* vw = (F32*) vertex_weightsp.get(); - F32* cw = (F32*) clothing_weightsp.get(); - - S32 tc_size = (num_verts*2*sizeof(F32)+0xF) & ~0xF; - LLVector4a::memcpyNonAliased16(tc, (F32*) mMesh->getTexCoords(), tc_size); - S32 vw_size = (num_verts*sizeof(F32)+0xF) & ~0xF; - LLVector4a::memcpyNonAliased16(vw, (F32*) mMesh->getWeights(), vw_size); - LLVector4a::memcpyNonAliased16(cw, (F32*) mMesh->getClothingWeights(), num_verts*4*sizeof(F32)); - } - - const U32 idx_count = mMesh->getNumFaces()*3; - - indicesp += mMesh->mFaceIndexOffset; - - U16* __restrict idx = indicesp.get(); - S32* __restrict src_idx = (S32*) mMesh->getFaces(); - - const S32 offset = (S32) mMesh->mFaceVertexOffset; - - for (S32 i = 0; i < idx_count; ++i) - { - *(idx++) = *(src_idx++)+offset; - } - } - } -} - - - -//----------------------------------------------------------------------------- -// updateLOD() -//----------------------------------------------------------------------------- -bool LLViewerJointMesh::updateLOD(F32 pixel_area, bool activate) -{ - bool valid = mValid; - setValid(activate, true); - return (valid != activate); -} - -// static -void LLViewerJointMesh::updateGeometry(LLFace *mFace, LLPolyMesh *mMesh) -{ - LLStrider o_vertices; - LLStrider o_normals; - - //get vertex and normal striders - LLVertexBuffer* buffer = mFace->getVertexBuffer(); - buffer->getVertexStrider(o_vertices, 0); - buffer->getNormalStrider(o_normals, 0); - - F32* __restrict vert = o_vertices[0].mV; - F32* __restrict norm = o_normals[0].mV; - - const F32* __restrict weights = mMesh->getWeights(); - const LLVector4a* __restrict coords = (LLVector4a*) mMesh->getCoords(); - const LLVector4a* __restrict normals = (LLVector4a*) mMesh->getNormals(); - - U32 offset = mMesh->mFaceVertexOffset*4; - vert += offset; - norm += offset; - - for (U32 index = 0; index < mMesh->getNumVertices(); index++) - { - // equivalent to joint = floorf(weights[index]); - S32 joint = _mm_cvtt_ss2si(_mm_load_ss(weights+index)); - F32 w = weights[index] - joint; - - LLMatrix4a gBlendMat; - - if (w != 0.f) - { - // blend between matrices and apply - gBlendMat.setLerp(gJointMatAligned[joint+0], - gJointMatAligned[joint+1], w); - - LLVector4a res; - gBlendMat.affineTransform(coords[index], res); - res.store4a(vert+index*4); - gBlendMat.rotate(normals[index], res); - res.store4a(norm+index*4); - } - else - { // No lerp required in this case. - LLVector4a res; - gJointMatAligned[joint].affineTransform(coords[index], res); - res.store4a(vert+index*4); - gJointMatAligned[joint].rotate(normals[index], res); - res.store4a(norm+index*4); - } - } - - buffer->unmapBuffer(); -} - -void LLViewerJointMesh::updateJointGeometry() -{ - if (!(mValid - && mMesh - && mFace - && mMesh->hasWeights() - && mFace->getVertexBuffer() - && LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_AVATAR) == 0)) - { - return; - } - - uploadJointMatrices(); - updateGeometry(mFace, mMesh); -} - -void LLViewerJointMesh::dump() -{ - if (mValid) - { - LL_INFOS() << "Usable LOD " << mName << LL_ENDL; - } -} - -// End + /** + * @file llviewerjointmesh.cpp + * @brief Implementation of LLViewerJointMesh class + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +//----------------------------------------------------------------------------- +// Header Files +//----------------------------------------------------------------------------- +#include "llviewerprecompiledheaders.h" + +#include "llfasttimer.h" +#include "llrender.h" + +#include "llapr.h" +#include "llbox.h" +#include "lldrawable.h" +#include "lldrawpoolavatar.h" +#include "lldrawpoolbump.h" +#include "lldynamictexture.h" +#include "llface.h" +#include "llglheaders.h" +#include "llviewertexlayer.h" +#include "llviewercamera.h" +#include "llviewercontrol.h" +#include "llviewertexturelist.h" +#include "llviewerjointmesh.h" +#include "llvoavatar.h" +#include "llsky.h" +#include "pipeline.h" +#include "llviewershadermgr.h" +#include "llmath.h" +#include "v4math.h" +#include "m3math.h" +#include "m4math.h" +#include "llmatrix4a.h" +#include "llperfstats.h" + +#if !LL_DARWIN && !LL_LINUX +extern PFNGLWEIGHTPOINTERARBPROC glWeightPointerARB; +extern PFNGLWEIGHTFVARBPROC glWeightfvARB; +extern PFNGLVERTEXBLENDARBPROC glVertexBlendARB; +#endif + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +// LLViewerJointMesh +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +// LLViewerJointMesh() +//----------------------------------------------------------------------------- +LLViewerJointMesh::LLViewerJointMesh() + : + LLAvatarJointMesh() +{ +} + + +//----------------------------------------------------------------------------- +// ~LLViewerJointMesh() +// Class Destructor +//----------------------------------------------------------------------------- +LLViewerJointMesh::~LLViewerJointMesh() +{ +} + +const S32 NUM_AXES = 3; + +// register layoud +// rotation X 0-n +// rotation Y 0-n +// rotation Z 0-n +// pivot parent 0-n -- child = n+1 + +static LLMatrix4 gJointMatUnaligned[32]; +static LLMatrix4a gJointMatAligned[32]; +static LLMatrix3 gJointRotUnaligned[32]; +static LLVector4 gJointPivot[32]; + +//----------------------------------------------------------------------------- +// uploadJointMatrices() +//----------------------------------------------------------------------------- +void LLViewerJointMesh::uploadJointMatrices() +{ + S32 joint_num; + LLPolyMesh *reference_mesh = mMesh->getReferenceMesh(); + LLDrawPool *poolp = mFace ? mFace->getPool() : NULL; + bool hardware_skinning = (poolp && poolp->getShaderLevel() > 0); + + //calculate joint matrices + for (joint_num = 0; joint_num < reference_mesh->mJointRenderData.size(); joint_num++) + { + LLMatrix4 joint_mat = *reference_mesh->mJointRenderData[joint_num]->mWorldMatrix; + + if (hardware_skinning) + { + joint_mat *= LLDrawPoolAvatar::getModelView(); + } + gJointMatUnaligned[joint_num] = joint_mat; + gJointRotUnaligned[joint_num] = joint_mat.getMat3(); + } + + bool last_pivot_uploaded{ false }; + S32 j = 0; + + //upload joint pivots + for (joint_num = 0; joint_num < reference_mesh->mJointRenderData.size(); joint_num++) + { + LLSkinJoint *sj = reference_mesh->mJointRenderData[joint_num]->mSkinJoint; + if (sj) + { + if (!last_pivot_uploaded) + { + LLVector4 parent_pivot(sj->mRootToParentJointSkinOffset); + parent_pivot.mV[VW] = 0.f; + gJointPivot[j++] = parent_pivot; + } + + LLVector4 child_pivot(sj->mRootToJointSkinOffset); + child_pivot.mV[VW] = 0.f; + + gJointPivot[j++] = child_pivot; + + last_pivot_uploaded = true; + } + else + { + last_pivot_uploaded = false; + } + } + + //add pivot point into transform + for (S32 i = 0; i < j; i++) + { + LLVector3 pivot; + pivot = LLVector3(gJointPivot[i]); + pivot = pivot * gJointRotUnaligned[i]; + gJointMatUnaligned[i].translate(pivot); + } + + // upload matrices + if (hardware_skinning) + { + GLfloat mat[45*4]; + memset(mat, 0, sizeof(GLfloat)*45*4); + + for (joint_num = 0; joint_num < reference_mesh->mJointRenderData.size(); joint_num++) + { + gJointMatUnaligned[joint_num].transpose(); + + for (S32 axis = 0; axis < NUM_AXES; axis++) + { + F32* vector = gJointMatUnaligned[joint_num].mMatrix[axis]; + U32 offset = LL_CHARACTER_MAX_JOINTS_PER_MESH*axis+joint_num; + memcpy(mat+offset*4, vector, sizeof(GLfloat)*4); + } + } + stop_glerror(); + if (LLGLSLShader::sCurBoundShaderPtr) + { + LLGLSLShader::sCurBoundShaderPtr->uniform4fv(LLViewerShaderMgr::AVATAR_MATRIX, 45, mat); + } + stop_glerror(); + } + else + { + //load gJointMatUnaligned into gJointMatAligned + for (joint_num = 0; joint_num < reference_mesh->mJointRenderData.size(); ++joint_num) + { + gJointMatAligned[joint_num].loadu(gJointMatUnaligned[joint_num]); + } + } +} + +//-------------------------------------------------------------------- +// DrawElementsBLEND and utility code +//-------------------------------------------------------------------- + +// compare_int is used by the qsort function to sort the index array +int compare_int(const void *a, const void *b) +{ + if (*(U32*)a < *(U32*)b) + { + return -1; + } + else if (*(U32*)a > *(U32*)b) + { + return 1; + } + else return 0; +} + +//-------------------------------------------------------------------- +// LLViewerJointMesh::drawShape() +//-------------------------------------------------------------------- +U32 LLViewerJointMesh::drawShape( F32 pixelArea, bool first_pass, bool is_dummy) +{ + if (!mValid || !mMesh || !mFace || !mVisible || + !mFace->getVertexBuffer() || + mMesh->getNumFaces() == 0 || + LLGLSLShader::sCurBoundShaderPtr == NULL) + { + return 0; + } + + U32 triangle_count = 0; + + S32 diffuse_channel = LLDrawPoolAvatar::sDiffuseChannel; + + stop_glerror(); + + //---------------------------------------------------------------- + // setup current color + //---------------------------------------------------------------- + if (is_dummy) + gGL.diffuseColor4fv(LLVOAvatar::getDummyColor().mV); + else + gGL.diffuseColor4fv(mColor.mV); + + stop_glerror(); + + LLGLSSpecular specular(LLColor4(1.f,1.f,1.f,1.f), 0.f); + + //---------------------------------------------------------------- + // setup current texture + //---------------------------------------------------------------- + llassert( !(mTexture.notNull() && mLayerSet) ); // mutually exclusive + + LLViewerTexLayerSet *layerset = dynamic_cast(mLayerSet); + if (mTestImageName) + { + gGL.getTexUnit(diffuse_channel)->bindManual(LLTexUnit::TT_TEXTURE, mTestImageName); + + if (mIsTransparent) + { + gGL.diffuseColor4f(1.f, 1.f, 1.f, 1.f); + } + else + { + gGL.diffuseColor4f(0.7f, 0.6f, 0.3f, 1.f); + } + } + else if( !is_dummy && layerset ) + { + if( layerset->hasComposite() ) + { + gGL.getTexUnit(diffuse_channel)->bind(layerset->getViewerComposite()); + } + else + { + gGL.getTexUnit(diffuse_channel)->bind(LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT)); + } + } + else if ( !is_dummy && mTexture.notNull() ) + { + gGL.getTexUnit(diffuse_channel)->bind(mTexture); + } + else + { + gGL.getTexUnit(diffuse_channel)->bind(LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT)); + } + + U32 start = mMesh->mFaceVertexOffset; + U32 end = start + mMesh->mFaceVertexCount - 1; + U32 count = mMesh->mFaceIndexCount; + U32 offset = mMesh->mFaceIndexOffset; + + LLVertexBuffer* buff = mFace->getVertexBuffer(); + + if (mMesh->hasWeights()) + { + if ((mFace->getPool()->getShaderLevel() > 0)) + { + if (first_pass) + { + uploadJointMatrices(); + } + } + + buff->setBuffer(); + buff->drawRange(LLRender::TRIANGLES, start, end, count, offset); + } + else + { + gGL.pushMatrix(); + LLMatrix4 jointToWorld = getWorldMatrix(); + gGL.multMatrix((GLfloat*)jointToWorld.mMatrix); + buff->setBuffer(); + buff->drawRange(LLRender::TRIANGLES, start, end, count, offset); + gGL.popMatrix(); + } + gPipeline.addTrianglesDrawn(count); + + triangle_count += count; + + return triangle_count; +} + +//----------------------------------------------------------------------------- +// updateFaceSizes() +//----------------------------------------------------------------------------- +void LLViewerJointMesh::updateFaceSizes(U32 &num_vertices, U32& num_indices, F32 pixel_area) +{ + //bump num_vertices to next multiple of 4 + num_vertices = (num_vertices + 0x3) & ~0x3; + + // Do a pre-alloc pass to determine sizes of data. + if (mMesh && mValid) + { + mMesh->mFaceVertexOffset = num_vertices; + mMesh->mFaceVertexCount = mMesh->getNumVertices(); + mMesh->mFaceIndexOffset = num_indices; + mMesh->mFaceIndexCount = mMesh->getSharedData()->mNumTriangleIndices; + + mMesh->getReferenceMesh()->mCurVertexCount = mMesh->mFaceVertexCount; + + num_vertices += mMesh->getNumVertices(); + num_indices += mMesh->mFaceIndexCount; + } +} + +//----------------------------------------------------------------------------- +// updateFaceData() +//----------------------------------------------------------------------------- + +void LLViewerJointMesh::updateFaceData(LLFace *face, F32 pixel_area, bool damp_wind, bool terse_update) +{ + //IF THIS FUNCTION BREAKS, SEE LLPOLYMESH CONSTRUCTOR AND CHECK ALIGNMENT OF INPUT ARRAYS + + mFace = face; + + if (!mFace->getVertexBuffer()) + { + return; + } + + LLDrawPool *poolp = mFace->getPool(); + bool hardware_skinning = (poolp && poolp->getShaderLevel() > 0); + + if (!hardware_skinning && terse_update) + { //no need to do terse updates if we're doing software vertex skinning + // since mMesh is being copied into mVertexBuffer every frame + return; + } + + LL_PROFILE_ZONE_SCOPED; + + LLStrider verticesp; + LLStrider normalsp; + LLStrider tex_coordsp; + LLStrider vertex_weightsp; + LLStrider clothing_weightsp; + LLStrider indicesp; + + // Copy data into the faces from the polymesh data. + if (mMesh && mValid) + { + const U32 num_verts = mMesh->getNumVertices(); + + if (num_verts) + { + face->getVertexBuffer()->getIndexStrider(indicesp); + face->getGeometryAvatar(verticesp, normalsp, tex_coordsp, vertex_weightsp, clothing_weightsp); + + verticesp += mMesh->mFaceVertexOffset; + normalsp += mMesh->mFaceVertexOffset; + + F32* v = (F32*) verticesp.get(); + F32* n = (F32*) normalsp.get(); + + U32 words = num_verts*4; + + LLVector4a::memcpyNonAliased16(v, (F32*) mMesh->getCoords(), words*sizeof(F32)); + LLVector4a::memcpyNonAliased16(n, (F32*) mMesh->getNormals(), words*sizeof(F32)); + + + if (!terse_update) + { + vertex_weightsp += mMesh->mFaceVertexOffset; + clothing_weightsp += mMesh->mFaceVertexOffset; + tex_coordsp += mMesh->mFaceVertexOffset; + + F32* tc = (F32*) tex_coordsp.get(); + F32* vw = (F32*) vertex_weightsp.get(); + F32* cw = (F32*) clothing_weightsp.get(); + + S32 tc_size = (num_verts*2*sizeof(F32)+0xF) & ~0xF; + LLVector4a::memcpyNonAliased16(tc, (F32*) mMesh->getTexCoords(), tc_size); + S32 vw_size = (num_verts*sizeof(F32)+0xF) & ~0xF; + LLVector4a::memcpyNonAliased16(vw, (F32*) mMesh->getWeights(), vw_size); + LLVector4a::memcpyNonAliased16(cw, (F32*) mMesh->getClothingWeights(), num_verts*4*sizeof(F32)); + } + + const U32 idx_count = mMesh->getNumFaces()*3; + + indicesp += mMesh->mFaceIndexOffset; + + U16* __restrict idx = indicesp.get(); + S32* __restrict src_idx = (S32*) mMesh->getFaces(); + + const S32 offset = (S32) mMesh->mFaceVertexOffset; + + for (S32 i = 0; i < idx_count; ++i) + { + *(idx++) = *(src_idx++)+offset; + } + } + } +} + + + +//----------------------------------------------------------------------------- +// updateLOD() +//----------------------------------------------------------------------------- +bool LLViewerJointMesh::updateLOD(F32 pixel_area, bool activate) +{ + bool valid = mValid; + setValid(activate, true); + return (valid != activate); +} + +// static +void LLViewerJointMesh::updateGeometry(LLFace *mFace, LLPolyMesh *mMesh) +{ + LLStrider o_vertices; + LLStrider o_normals; + + //get vertex and normal striders + LLVertexBuffer* buffer = mFace->getVertexBuffer(); + buffer->getVertexStrider(o_vertices, 0); + buffer->getNormalStrider(o_normals, 0); + + F32* __restrict vert = o_vertices[0].mV; + F32* __restrict norm = o_normals[0].mV; + + const F32* __restrict weights = mMesh->getWeights(); + const LLVector4a* __restrict coords = (LLVector4a*) mMesh->getCoords(); + const LLVector4a* __restrict normals = (LLVector4a*) mMesh->getNormals(); + + U32 offset = mMesh->mFaceVertexOffset*4; + vert += offset; + norm += offset; + + for (U32 index = 0; index < mMesh->getNumVertices(); index++) + { + // equivalent to joint = floorf(weights[index]); + S32 joint = _mm_cvtt_ss2si(_mm_load_ss(weights+index)); + F32 w = weights[index] - joint; + + LLMatrix4a gBlendMat; + + if (w != 0.f) + { + // blend between matrices and apply + gBlendMat.setLerp(gJointMatAligned[joint+0], + gJointMatAligned[joint+1], w); + + LLVector4a res; + gBlendMat.affineTransform(coords[index], res); + res.store4a(vert+index*4); + gBlendMat.rotate(normals[index], res); + res.store4a(norm+index*4); + } + else + { // No lerp required in this case. + LLVector4a res; + gJointMatAligned[joint].affineTransform(coords[index], res); + res.store4a(vert+index*4); + gJointMatAligned[joint].rotate(normals[index], res); + res.store4a(norm+index*4); + } + } + + buffer->unmapBuffer(); +} + +void LLViewerJointMesh::updateJointGeometry() +{ + if (!(mValid + && mMesh + && mFace + && mMesh->hasWeights() + && mFace->getVertexBuffer() + && LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_AVATAR) == 0)) + { + return; + } + + uploadJointMatrices(); + updateGeometry(mFace, mMesh); +} + +void LLViewerJointMesh::dump() +{ + if (mValid) + { + LL_INFOS() << "Usable LOD " << mName << LL_ENDL; + } +} + +// End diff --git a/indra/newview/llviewerjointmesh.h b/indra/newview/llviewerjointmesh.h index 3332dbde81..cfe8ecdb67 100644 --- a/indra/newview/llviewerjointmesh.h +++ b/indra/newview/llviewerjointmesh.h @@ -1,77 +1,77 @@ -/** - * @file llviewerjointmesh.h - * @brief Declaration of LLViewerJointMesh class - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVIEWERJOINTMESH_H -#define LL_LLVIEWERJOINTMESH_H - -#include "llviewerjoint.h" -#include "llviewertexture.h" -#include "llavatarjointmesh.h" -#include "llpolymesh.h" -#include "v4color.h" - -class LLDrawable; -class LLFace; -class LLCharacter; -class LLViewerTexLayerSet; - -//----------------------------------------------------------------------------- -// class LLViewerJointMesh -//----------------------------------------------------------------------------- -class LLViewerJointMesh : public LLAvatarJointMesh, public LLViewerJoint -{ -public: - // Constructor - LLViewerJointMesh(); - - // Destructor - virtual ~LLViewerJointMesh(); - - // Render time method to upload batches of joint matrices - void uploadJointMatrices(); - - // overloaded from base class - U32 drawShape( F32 pixelArea, bool first_pass, bool is_dummy ) override; - - // necessary because MS's compiler warns on function inheritance via dominance in the diamond inheritance here. - // warns even though LLViewerJoint holds the only non virtual implementation. - U32 render(F32 pixelArea, bool first_pass = true, bool is_dummy = false) override { return LLViewerJoint::render(pixelArea, first_pass, is_dummy); } - - void updateFaceSizes(U32 &num_vertices, U32& num_indices, F32 pixel_area) override; - void updateFaceData(LLFace *face, F32 pixel_area, bool damp_wind = false, bool terse_update = false) override; - bool updateLOD(F32 pixel_area, bool activate) override; - void updateJointGeometry() override; - void dump() override; - - bool isAnimatable() const override { return false; } - -private: - - //copy mesh into given face's vertex buffer, applying current animation pose - static void updateGeometry(LLFace* face, LLPolyMesh* mesh); -}; - -#endif // LL_LLVIEWERJOINTMESH_H +/** + * @file llviewerjointmesh.h + * @brief Declaration of LLViewerJointMesh class + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVIEWERJOINTMESH_H +#define LL_LLVIEWERJOINTMESH_H + +#include "llviewerjoint.h" +#include "llviewertexture.h" +#include "llavatarjointmesh.h" +#include "llpolymesh.h" +#include "v4color.h" + +class LLDrawable; +class LLFace; +class LLCharacter; +class LLViewerTexLayerSet; + +//----------------------------------------------------------------------------- +// class LLViewerJointMesh +//----------------------------------------------------------------------------- +class LLViewerJointMesh : public LLAvatarJointMesh, public LLViewerJoint +{ +public: + // Constructor + LLViewerJointMesh(); + + // Destructor + virtual ~LLViewerJointMesh(); + + // Render time method to upload batches of joint matrices + void uploadJointMatrices(); + + // overloaded from base class + U32 drawShape( F32 pixelArea, bool first_pass, bool is_dummy ) override; + + // necessary because MS's compiler warns on function inheritance via dominance in the diamond inheritance here. + // warns even though LLViewerJoint holds the only non virtual implementation. + U32 render(F32 pixelArea, bool first_pass = true, bool is_dummy = false) override { return LLViewerJoint::render(pixelArea, first_pass, is_dummy); } + + void updateFaceSizes(U32 &num_vertices, U32& num_indices, F32 pixel_area) override; + void updateFaceData(LLFace *face, F32 pixel_area, bool damp_wind = false, bool terse_update = false) override; + bool updateLOD(F32 pixel_area, bool activate) override; + void updateJointGeometry() override; + void dump() override; + + bool isAnimatable() const override { return false; } + +private: + + //copy mesh into given face's vertex buffer, applying current animation pose + static void updateGeometry(LLFace* face, LLPolyMesh* mesh); +}; + +#endif // LL_LLVIEWERJOINTMESH_H diff --git a/indra/newview/llviewerjoystick.cpp b/indra/newview/llviewerjoystick.cpp index a1d5f4c53e..ce6dfa4ad1 100644 --- a/indra/newview/llviewerjoystick.cpp +++ b/indra/newview/llviewerjoystick.cpp @@ -1,1601 +1,1601 @@ -/** - * @file llviewerjoystick.cpp - * @brief Joystick / NDOF device functionality. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llviewerjoystick.h" - -#include "llviewercontrol.h" -#include "llviewerwindow.h" -#include "llviewercamera.h" -#include "llappviewer.h" -#include "llkeyboard.h" -#include "lltoolmgr.h" -#include "llselectmgr.h" -#include "llviewermenu.h" -#include "llviewerwindow.h" -#include "llwindow.h" -#include "llagent.h" -#include "llagentcamera.h" -#include "llfocusmgr.h" - -#if LL_WINDOWS && !LL_MESA_HEADLESS -// Require DirectInput version 8 -#define DIRECTINPUT_VERSION 0x0800 - -#include -#endif - - -// ---------------------------------------------------------------------------- -// Constants - -#define X_I 1 -#define Y_I 2 -#define Z_I 0 -#define RX_I 4 -#define RY_I 5 -#define RZ_I 3 - -F32 LLViewerJoystick::sLastDelta[] = {0,0,0,0,0,0,0}; -F32 LLViewerJoystick::sDelta[] = {0,0,0,0,0,0,0}; - -// These constants specify the maximum absolute value coming in from the device. -// HACK ALERT! the value of MAX_JOYSTICK_INPUT_VALUE is not arbitrary as it -// should be. It has to be equal to 3000 because the SpaceNavigator on Windows -// refuses to respond to the DirectInput SetProperty call; it always returns -// values in the [-3000, 3000] range. -#define MAX_SPACENAVIGATOR_INPUT 3000.0f -#define MAX_JOYSTICK_INPUT_VALUE MAX_SPACENAVIGATOR_INPUT - - -#if LIB_NDOF -std::ostream& operator<<(std::ostream& out, NDOF_Device* ptr) -{ - if (! ptr) - { - return out << "nullptr"; - } - out << "NDOF_Device{ "; - out << "axes ["; - const char* delim = ""; - for (short axis = 0; axis < ptr->axes_count; ++axis) - { - out << delim << ptr->axes[axis]; - delim = ", "; - } - out << "]"; - out << ", buttons ["; - delim = ""; - for (short button = 0; button < ptr->btn_count; ++button) - { - out << delim << ptr->buttons[button]; - delim = ", "; - } - out << "]"; - out << ", range " << ptr->axes_min << ':' << ptr->axes_max; - // If we don't coerce these to unsigned, they're streamed as characters, - // e.g. ctrl-A or nul. - out << ", absolute " << unsigned(ptr->absolute); - out << ", valid " << unsigned(ptr->valid); - out << ", manufacturer '" << ptr->manufacturer << "'"; - out << ", product '" << ptr->product << "'"; - out << ", private " << ptr->private_data; - out << " }"; - return out; -} -#endif // LIB_NDOF - - -#if LL_WINDOWS && !LL_MESA_HEADLESS -// this should reflect ndof and set axises, see ndofdev_win.cpp from ndof package -BOOL CALLBACK EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE* inst, VOID* user_data) -{ - if (inst->dwType & DIDFT_AXIS) - { - LPDIRECTINPUTDEVICE8 device = *((LPDIRECTINPUTDEVICE8 *)user_data); - DIPROPRANGE diprg; - diprg.diph.dwSize = sizeof(DIPROPRANGE); - diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER); - diprg.diph.dwHow = DIPH_BYID; - diprg.diph.dwObj = inst->dwType; // specify the enumerated axis - - // Set the range for the axis - diprg.lMin = (long)-MAX_JOYSTICK_INPUT_VALUE; - diprg.lMax = (long)+MAX_JOYSTICK_INPUT_VALUE; - HRESULT hr = device->SetProperty(DIPROP_RANGE, &diprg.diph); - - if (FAILED(hr)) - { - return DIENUM_STOP; - } - } - - return DIENUM_CONTINUE; -} - -BOOL CALLBACK di8_devices_callback(LPCDIDEVICEINSTANCE device_instance_ptr, LPVOID pvRef) -{ - // Note: If a single device can function as more than one DirectInput - // device type, it is enumerated as each device type that it supports. - // Capable of detecting devices like Oculus Rift - if (device_instance_ptr) - { - std::string product_name = utf16str_to_utf8str(llutf16string(device_instance_ptr->tszProductName)); - - LLSD guid = LLViewerJoystick::getInstance()->getDeviceUUID(); - - bool init_device = false; - if (guid.isBinary()) - { - std::vector bin_bucket = guid.asBinary(); - init_device = memcmp(&bin_bucket[0], &device_instance_ptr->guidInstance, sizeof(GUID)) == 0; - } - else - { - // It might be better to init space navigator here, but if system doesn't has one, - // ndof will pick a random device, it is simpler to pick first device now to have an id - init_device = true; - } - - if (init_device) - { - LL_DEBUGS("Joystick") << "Found and attempting to use device: " << product_name << LL_ENDL; - LPDIRECTINPUT8 di8_interface = *((LPDIRECTINPUT8 *)gViewerWindow->getWindow()->getDirectInput8()); - LPDIRECTINPUTDEVICE8 device = NULL; - - HRESULT status = di8_interface->CreateDevice( - device_instance_ptr->guidInstance, // REFGUID rguid, - &device, // LPDIRECTINPUTDEVICE * lplpDirectInputDevice, - NULL // LPUNKNOWN pUnkOuter - ); - - if (status == DI_OK) - { - // prerequisite for aquire() - LL_DEBUGS("Joystick") << "Device created" << LL_ENDL; - status = device->SetDataFormat(&c_dfDIJoystick); // c_dfDIJoystick2 - } - - if (status == DI_OK) - { - // set properties - LL_DEBUGS("Joystick") << "Format set" << LL_ENDL; - status = device->EnumObjects(EnumObjectsCallback, &device, DIDFT_ALL); - } - - if (status == DI_OK) - { - LL_DEBUGS("Joystick") << "Properties updated" << LL_ENDL; - - S32 size = sizeof(GUID); - LLSD::Binary data; //just an std::vector - data.resize(size); - memcpy(&data[0], &device_instance_ptr->guidInstance /*POD _GUID*/, size); - LLViewerJoystick::getInstance()->initDevice(&device, product_name, LLSD(data)); - return DIENUM_STOP; - } - } - else - { - LL_DEBUGS("Joystick") << "Found device: " << product_name << LL_ENDL; - } - } - return DIENUM_CONTINUE; -} - -// Windows guids -// This is GUID2 so teoretically it can be memcpy copied into LLUUID -void guid_from_string(GUID &guid, const std::string &input) -{ - CLSIDFromString(utf8str_to_utf16str(input).c_str(), &guid); -} - -std::string string_from_guid(const GUID &guid) -{ - OLECHAR* guidString; //wchat - StringFromCLSID(guid, &guidString); - - // use guidString... - - std::string res = utf16str_to_utf8str(llutf16string(guidString)); - // ensure memory is freed - ::CoTaskMemFree(guidString); - - return res; -} -#elif LL_DARWIN - -bool macos_devices_callback(std::string &product_name, LLSD &data, void* userdata) -{ - std::string product = data["product"].asString(); - - return LLViewerJoystick::getInstance()->initDevice(nullptr, product, data); -} - -#endif - - -// ----------------------------------------------------------------------------- -void LLViewerJoystick::updateEnabled(bool autoenable) -{ - if (mDriverState == JDS_UNINITIALIZED) - { - gSavedSettings.setBOOL("JoystickEnabled", false); - } - else - { - // autoenable if user specifically chose this device - if (autoenable && (isLikeSpaceNavigator() || isDeviceUUIDSet())) - { - gSavedSettings.setBOOL("JoystickEnabled", true ); - } - } - if (!gSavedSettings.getBOOL("JoystickEnabled")) - { - mOverrideCamera = false; - } -} - -void LLViewerJoystick::setOverrideCamera(bool val) -{ - if (!gSavedSettings.getBOOL("JoystickEnabled")) - { - mOverrideCamera = false; - } - else - { - mOverrideCamera = val; - } - - if (mOverrideCamera) - { - gAgentCamera.changeCameraToDefault(); - } -} - -// ----------------------------------------------------------------------------- -#if LIB_NDOF -NDOF_HotPlugResult LLViewerJoystick::HotPlugAddCallback(NDOF_Device *dev) -{ - NDOF_HotPlugResult res = NDOF_DISCARD_HOTPLUGGED; - LLViewerJoystick* joystick(LLViewerJoystick::getInstance()); - if (joystick->mDriverState == JDS_UNINITIALIZED) - { - LL_INFOS("Joystick") << "HotPlugAddCallback: will use device:" << LL_ENDL; - ndof_dump(stderr, dev); - joystick->mNdofDev = dev; - joystick->mDriverState = JDS_INITIALIZED; - res = NDOF_KEEP_HOTPLUGGED; - } - joystick->updateEnabled(true); - return res; -} -#endif - -// ----------------------------------------------------------------------------- -#if LIB_NDOF -void LLViewerJoystick::HotPlugRemovalCallback(NDOF_Device *dev) -{ - LLViewerJoystick* joystick(LLViewerJoystick::getInstance()); - if (joystick->mNdofDev == dev) - { - LL_INFOS("Joystick") << "HotPlugRemovalCallback: joystick->mNdofDev=" - << joystick->mNdofDev << "; removed device:" << LL_ENDL; - ndof_dump(stderr, dev); - joystick->mDriverState = JDS_UNINITIALIZED; - } - joystick->updateEnabled(true); -} -#endif - -// ----------------------------------------------------------------------------- -LLViewerJoystick::LLViewerJoystick() -: mDriverState(JDS_UNINITIALIZED), - mNdofDev(NULL), - mResetFlag(false), - mCameraUpdated(true), - mOverrideCamera(false), - mJoystickRun(0) -{ - for (int i = 0; i < 6; i++) - { - mAxes[i] = sDelta[i] = sLastDelta[i] = 0.0f; - } - - memset(mBtn, 0, sizeof(mBtn)); - - // factor in bandwidth? bandwidth = gViewerStats->mKBitStat - mPerfScale = 4000.f / gSysCPU.getMHz(); // hmm. why? - - mLastDeviceUUID = LLSD::Integer(1); -} - -// ----------------------------------------------------------------------------- -LLViewerJoystick::~LLViewerJoystick() -{ - if (mDriverState == JDS_INITIALIZED) - { - terminate(); - } -} - -// ----------------------------------------------------------------------------- -void LLViewerJoystick::init(bool autoenable) -{ -#if LIB_NDOF - static bool libinit = false; - mDriverState = JDS_INITIALIZING; - - loadDeviceIdFromSettings(); - - if (!libinit) - { - // Note: The HotPlug callbacks are not actually getting called on Windows - if (ndof_libinit(HotPlugAddCallback, - HotPlugRemovalCallback, - gViewerWindow->getWindow()->getDirectInput8())) - { - mDriverState = JDS_UNINITIALIZED; - } - else - { - // NB: ndof_libinit succeeds when there's no device - libinit = true; - - // allocate memory once for an eventual device - mNdofDev = ndof_create(); - } - } - - if (libinit) - { - if (mNdofDev) - { - U32 device_type = 0; - void* win_callback = nullptr; - std::function osx_callback; - // di8_devices_callback callback is immediate and happens in scope of getInputDevices() -#if LL_WINDOWS && !LL_MESA_HEADLESS - // space navigator is marked as DI8DEVCLASS_GAMECTRL in ndof lib - device_type = DI8DEVCLASS_GAMECTRL; - win_callback = &di8_devices_callback; -#elif LL_DARWIN - osx_callback = macos_devices_callback; - - if (mLastDeviceUUID.isMap()) - { - std::string manufacturer = mLastDeviceUUID["manufacturer"].asString(); - std::string product = mLastDeviceUUID["product"].asString(); - - strncpy(mNdofDev->manufacturer, manufacturer.c_str(), sizeof(mNdofDev->manufacturer)); - strncpy(mNdofDev->product, product.c_str(), sizeof(mNdofDev->product)); - - if (ndof_init_first(mNdofDev, nullptr)) - { - mDriverState = JDS_INITIALIZING; - // Saved device no longer exist - // No device found - LL_WARNS() << "ndof_init_first FAILED" << LL_ENDL; - } - else - { - mDriverState = JDS_INITIALIZED; - } - } -#endif - if (mDriverState != JDS_INITIALIZED) - { - if (!gViewerWindow->getWindow()->getInputDevices(device_type, osx_callback, win_callback, NULL)) - { - LL_INFOS("Joystick") << "Failed to gather input devices. Falling back to ndof's init" << LL_ENDL; - // Failed to gather devices, init first suitable one - mLastDeviceUUID = LLSD(); - void *preffered_device = NULL; - initDevice(preffered_device); - } - } - - if (mDriverState == JDS_INITIALIZING) - { - LL_INFOS("Joystick") << "Found no matching joystick devices." << LL_ENDL; - mDriverState = JDS_UNINITIALIZED; - } - } - else - { - mDriverState = JDS_UNINITIALIZED; - } - } - - // Autoenable the joystick for recognized devices if nothing was connected previously - if (!autoenable) - { - autoenable = gSavedSettings.getString("JoystickInitialized").empty(); - } - updateEnabled(autoenable); - - if (mDriverState == JDS_INITIALIZED) - { - // A Joystick device is plugged in - if (isLikeSpaceNavigator()) - { - // It's a space navigator, we have defaults for it. - if (gSavedSettings.getString("JoystickInitialized") != "SpaceNavigator") - { - // Only set the defaults if we haven't already (in case they were overridden) - setSNDefaults(); - gSavedSettings.setString("JoystickInitialized", "SpaceNavigator"); - } - } - else - { - // It's not a Space Navigator - gSavedSettings.setString("JoystickInitialized", "UnknownDevice"); - } - } - else - { - // No device connected, don't change any settings - } - - LL_INFOS("Joystick") << "ndof: mDriverState=" << mDriverState << "; mNdofDev=" - << mNdofDev << "; libinit=" << libinit << LL_ENDL; -#endif -} - -void LLViewerJoystick::initDevice(LLSD &guid) -{ -#if LIB_NDOF - mLastDeviceUUID = guid; - U32 device_type = 0; - void* win_callback = nullptr; - std::function osx_callback; - mDriverState = JDS_INITIALIZING; - -#if LL_WINDOWS && !LL_MESA_HEADLESS - // space navigator is marked as DI8DEVCLASS_GAMECTRL in ndof lib - device_type = DI8DEVCLASS_GAMECTRL; - win_callback = &di8_devices_callback; -#elif LL_DARWIN - osx_callback = macos_devices_callback; - if (mLastDeviceUUID.isMap()) - { - std::string manufacturer = mLastDeviceUUID["manufacturer"].asString(); - std::string product = mLastDeviceUUID["product"].asString(); - - strncpy(mNdofDev->manufacturer, manufacturer.c_str(), sizeof(mNdofDev->manufacturer)); - strncpy(mNdofDev->product, product.c_str(), sizeof(mNdofDev->product)); - - if (ndof_init_first(mNdofDev, nullptr)) - { - mDriverState = JDS_INITIALIZING; - // Saved device no longer exist - // Np other device present - LL_WARNS() << "ndof_init_first FAILED" << LL_ENDL; - } - else - { - mDriverState = JDS_INITIALIZED; - } - } -#endif - - if (mDriverState != JDS_INITIALIZED) - { - if (!gViewerWindow->getWindow()->getInputDevices(device_type, osx_callback, win_callback, NULL)) - { - LL_INFOS("Joystick") << "Failed to gather input devices. Falling back to ndof's init" << LL_ENDL; - // Failed to gather devices from window, init first suitable one - void *preffered_device = NULL; - mLastDeviceUUID = LLSD(); - initDevice(preffered_device); - } - } - - if (mDriverState == JDS_INITIALIZING) - { - LL_INFOS("Joystick") << "Found no matching joystick devices." << LL_ENDL; - mDriverState = JDS_UNINITIALIZED; - } -#endif -} - -bool LLViewerJoystick::initDevice(void * preffered_device /*LPDIRECTINPUTDEVICE8*/, std::string &name, LLSD &guid) -{ -#if LIB_NDOF - mLastDeviceUUID = guid; - -#if LL_DARWIN - if (guid.isMap()) - { - std::string manufacturer = mLastDeviceUUID["manufacturer"].asString(); - std::string product = mLastDeviceUUID["product"].asString(); - - strncpy(mNdofDev->manufacturer, manufacturer.c_str(), sizeof(mNdofDev->manufacturer)); - strncpy(mNdofDev->product, product.c_str(), sizeof(mNdofDev->product)); - } - else - { - mNdofDev->product[0] = '\0'; - mNdofDev->manufacturer[0] = '\0'; - } -#else - strncpy(mNdofDev->product, name.c_str(), sizeof(mNdofDev->product)); - mNdofDev->manufacturer[0] = '\0'; -#endif - - return initDevice(preffered_device); -#else - return false; -#endif -} - -bool LLViewerJoystick::initDevice(void * preffered_device /* LPDIRECTINPUTDEVICE8* */) -{ -#if LIB_NDOF - // Different joysticks will return different ranges of raw values. - // Since we want to handle every device in the same uniform way, - // we initialize the mNdofDev struct and we set the range - // of values we would like to receive. - // - // HACK: On Windows, libndofdev passes our range to DI with a - // SetProperty call. This works but with one notable exception, the - // SpaceNavigator, who doesn't seem to care about the SetProperty - // call. In theory, we should handle this case inside libndofdev. - // However, the range we're setting here is arbitrary anyway, - // so let's just use the SpaceNavigator range for our purposes. - mNdofDev->axes_min = (long)-MAX_JOYSTICK_INPUT_VALUE; - mNdofDev->axes_max = (long)+MAX_JOYSTICK_INPUT_VALUE; - - // libndofdev could be used to return deltas. Here we choose to - // just have the absolute values instead. - mNdofDev->absolute = 1; - // init & use the first suitable NDOF device found on the USB chain - // On windows preffered_device needs to be a pointer to LPDIRECTINPUTDEVICE8 - if (ndof_init_first(mNdofDev, preffered_device)) - { - mDriverState = JDS_UNINITIALIZED; - LL_WARNS() << "ndof_init_first FAILED" << LL_ENDL; - } - else - { - mDriverState = JDS_INITIALIZED; - return true; - } -#endif - return false; -} - -// ----------------------------------------------------------------------------- -void LLViewerJoystick::terminate() -{ -#if LIB_NDOF - if (mNdofDev != NULL) - { - ndof_libcleanup(); // frees alocated memory in mNdofDev - mDriverState = JDS_UNINITIALIZED; - mNdofDev = NULL; - LL_INFOS("Joystick") << "Terminated connection with NDOF device." << LL_ENDL; - } -#endif -} - -// ----------------------------------------------------------------------------- -void LLViewerJoystick::updateStatus() -{ -#if LIB_NDOF - - ndof_update(mNdofDev); - - for (int i=0; i<6; i++) - { - mAxes[i] = (F32) mNdofDev->axes[i] / mNdofDev->axes_max; - } - - for (int i=0; i<16; i++) - { - mBtn[i] = mNdofDev->buttons[i]; - } - -#endif -} - -// ----------------------------------------------------------------------------- -F32 LLViewerJoystick::getJoystickAxis(U32 axis) const -{ - if (axis < 6) - { - return mAxes[axis]; - } - return 0.f; -} - -// ----------------------------------------------------------------------------- -U32 LLViewerJoystick::getJoystickButton(U32 button) const -{ - if (button < 16) - { - return mBtn[button]; - } - return 0; -} - -// ----------------------------------------------------------------------------- -void LLViewerJoystick::handleRun(F32 inc) -{ - // Decide whether to walk or run by applying a threshold, with slight - // hysteresis to avoid oscillating between the two with input spikes. - // Analog speed control would be better, but not likely any time soon. - if (inc > gSavedSettings.getF32("JoystickRunThreshold")) - { - if (1 == mJoystickRun) - { - ++mJoystickRun; - gAgent.setRunning(); - gAgent.sendWalkRun(gAgent.getRunning()); - } - else if (0 == mJoystickRun) - { - // hysteresis - respond NEXT frame - ++mJoystickRun; - } - } - else - { - if (mJoystickRun > 0) - { - --mJoystickRun; - if (0 == mJoystickRun) - { - gAgent.clearRunning(); - gAgent.sendWalkRun(gAgent.getRunning()); - } - } - } -} - -// ----------------------------------------------------------------------------- -void LLViewerJoystick::agentJump() -{ - gAgent.moveUp(1); -} - -// ----------------------------------------------------------------------------- -void LLViewerJoystick::agentSlide(F32 inc) -{ - if (inc < 0.f) - { - gAgent.moveLeft(1); - } - else if (inc > 0.f) - { - gAgent.moveLeft(-1); - } -} - -// ----------------------------------------------------------------------------- -void LLViewerJoystick::agentPush(F32 inc) -{ - if (inc < 0.f) // forward - { - gAgent.moveAt(1, false); - } - else if (inc > 0.f) // backward - { - gAgent.moveAt(-1, false); - } -} - -// ----------------------------------------------------------------------------- -void LLViewerJoystick::agentFly(F32 inc) -{ - if (inc < 0.f) - { - if (! (gAgent.getFlying() || - !gAgent.canFly() || - gAgent.upGrabbed() || - !gSavedSettings.getBOOL("AutomaticFly")) ) - { - gAgent.setFlying(true); - } - gAgent.moveUp(1); - } - else if (inc > 0.f) - { - // crouch - gAgent.moveUp(-1); - } -} - -// ----------------------------------------------------------------------------- -void LLViewerJoystick::agentPitch(F32 pitch_inc) -{ - if (pitch_inc < 0) - { - gAgent.setControlFlags(AGENT_CONTROL_PITCH_POS); - } - else if (pitch_inc > 0) - { - gAgent.setControlFlags(AGENT_CONTROL_PITCH_NEG); - } - - gAgent.pitch(-pitch_inc); -} - -// ----------------------------------------------------------------------------- -void LLViewerJoystick::agentYaw(F32 yaw_inc) -{ - // Cannot steer some vehicles in mouselook if the script grabs the controls - if (gAgentCamera.cameraMouselook() && !gSavedSettings.getBOOL("JoystickMouselookYaw")) - { - gAgent.rotate(-yaw_inc, gAgent.getReferenceUpVector()); - } - else - { - if (yaw_inc < 0) - { - gAgent.setControlFlags(AGENT_CONTROL_YAW_POS); - } - else if (yaw_inc > 0) - { - gAgent.setControlFlags(AGENT_CONTROL_YAW_NEG); - } - - gAgent.yaw(-yaw_inc); - } -} - -// ----------------------------------------------------------------------------- -void LLViewerJoystick::resetDeltas(S32 axis[]) -{ - for (U32 i = 0; i < 6; i++) - { - sLastDelta[i] = -mAxes[axis[i]]; - sDelta[i] = 0.f; - } - - sLastDelta[6] = sDelta[6] = 0.f; - mResetFlag = false; -} - -// ----------------------------------------------------------------------------- -void LLViewerJoystick::moveObjects(bool reset) -{ - static bool toggle_send_to_sim = false; - - if (!gFocusMgr.getAppHasFocus() || mDriverState != JDS_INITIALIZED - || !gSavedSettings.getBOOL("JoystickEnabled") || !gSavedSettings.getBOOL("JoystickBuildEnabled")) - { - return; - } - - S32 axis[] = - { - gSavedSettings.getS32("JoystickAxis0"), - gSavedSettings.getS32("JoystickAxis1"), - gSavedSettings.getS32("JoystickAxis2"), - gSavedSettings.getS32("JoystickAxis3"), - gSavedSettings.getS32("JoystickAxis4"), - gSavedSettings.getS32("JoystickAxis5"), - }; - - if (reset || mResetFlag) - { - resetDeltas(axis); - return; - } - - F32 axis_scale[] = - { - gSavedSettings.getF32("BuildAxisScale0"), - gSavedSettings.getF32("BuildAxisScale1"), - gSavedSettings.getF32("BuildAxisScale2"), - gSavedSettings.getF32("BuildAxisScale3"), - gSavedSettings.getF32("BuildAxisScale4"), - gSavedSettings.getF32("BuildAxisScale5"), - }; - - F32 dead_zone[] = - { - gSavedSettings.getF32("BuildAxisDeadZone0"), - gSavedSettings.getF32("BuildAxisDeadZone1"), - gSavedSettings.getF32("BuildAxisDeadZone2"), - gSavedSettings.getF32("BuildAxisDeadZone3"), - gSavedSettings.getF32("BuildAxisDeadZone4"), - gSavedSettings.getF32("BuildAxisDeadZone5"), - }; - - F32 cur_delta[6]; - F32 time = gFrameIntervalSeconds.value(); - - // avoid making ridicously big movements if there's a big drop in fps - if (time > .2f) - { - time = .2f; - } - - // max feather is 32 - F32 feather = gSavedSettings.getF32("BuildFeathering"); - bool is_zero = true, absolute = gSavedSettings.getBOOL("Cursor3D"); - - for (U32 i = 0; i < 6; i++) - { - cur_delta[i] = -mAxes[axis[i]]; - F32 tmp = cur_delta[i]; - if (absolute) - { - cur_delta[i] = cur_delta[i] - sLastDelta[i]; - } - sLastDelta[i] = tmp; - is_zero = is_zero && (cur_delta[i] == 0.f); - - if (cur_delta[i] > 0) - { - cur_delta[i] = llmax(cur_delta[i]-dead_zone[i], 0.f); - } - else - { - cur_delta[i] = llmin(cur_delta[i]+dead_zone[i], 0.f); - } - cur_delta[i] *= axis_scale[i]; - - if (!absolute) - { - cur_delta[i] *= time; - } - - sDelta[i] = sDelta[i] + (cur_delta[i]-sDelta[i])*time*feather; - } - - U32 upd_type = UPD_NONE; - LLVector3 v; - - if (!is_zero) - { - // Clear AFK state if moved beyond the deadzone - if (gAwayTimer.getElapsedTimeF32() > LLAgent::MIN_AFK_TIME) - { - gAgent.clearAFK(); - } - - if (sDelta[0] || sDelta[1] || sDelta[2]) - { - upd_type |= UPD_POSITION; - v.setVec(sDelta[0], sDelta[1], sDelta[2]); - } - - if (sDelta[3] || sDelta[4] || sDelta[5]) - { - upd_type |= UPD_ROTATION; - } - - // the selection update could fail, so we won't send - if (LLSelectMgr::getInstance()->selectionMove(v, sDelta[3],sDelta[4],sDelta[5], upd_type)) - { - toggle_send_to_sim = true; - } - } - else if (toggle_send_to_sim) - { - LLSelectMgr::getInstance()->sendSelectionMove(); - toggle_send_to_sim = false; - } -} - -// ----------------------------------------------------------------------------- -void LLViewerJoystick::moveAvatar(bool reset) -{ - if (!gFocusMgr.getAppHasFocus() || mDriverState != JDS_INITIALIZED - || !gSavedSettings.getBOOL("JoystickEnabled") || !gSavedSettings.getBOOL("JoystickAvatarEnabled")) - { - return; - } - - S32 axis[] = - { - // [1 0 2 4 3 5] - // [Z X Y RZ RX RY] - gSavedSettings.getS32("JoystickAxis0"), - gSavedSettings.getS32("JoystickAxis1"), - gSavedSettings.getS32("JoystickAxis2"), - gSavedSettings.getS32("JoystickAxis3"), - gSavedSettings.getS32("JoystickAxis4"), - gSavedSettings.getS32("JoystickAxis5") - }; - - if (reset || mResetFlag) - { - resetDeltas(axis); - if (reset) - { - // Note: moving the agent triggers agent camera mode; - // don't do this every time we set mResetFlag (e.g. because we gained focus) - gAgent.moveAt(0, true); - } - return; - } - - bool is_zero = true; - static bool button_held = false; - - if (mBtn[1] == 1) - { - // If AutomaticFly is enabled, then button1 merely causes a - // jump (as the up/down axis already controls flying) if on the - // ground, or cease flight if already flying. - // If AutomaticFly is disabled, then button1 toggles flying. - if (gSavedSettings.getBOOL("AutomaticFly")) - { - if (!gAgent.getFlying()) - { - gAgent.moveUp(1); - } - else if (!button_held) - { - button_held = true; - gAgent.setFlying(false); - } - } - else if (!button_held) - { - button_held = true; - gAgent.setFlying(!gAgent.getFlying()); - } - - is_zero = false; - } - else - { - button_held = false; - } - - F32 axis_scale[] = - { - gSavedSettings.getF32("AvatarAxisScale0"), - gSavedSettings.getF32("AvatarAxisScale1"), - gSavedSettings.getF32("AvatarAxisScale2"), - gSavedSettings.getF32("AvatarAxisScale3"), - gSavedSettings.getF32("AvatarAxisScale4"), - gSavedSettings.getF32("AvatarAxisScale5") - }; - - F32 dead_zone[] = - { - gSavedSettings.getF32("AvatarAxisDeadZone0"), - gSavedSettings.getF32("AvatarAxisDeadZone1"), - gSavedSettings.getF32("AvatarAxisDeadZone2"), - gSavedSettings.getF32("AvatarAxisDeadZone3"), - gSavedSettings.getF32("AvatarAxisDeadZone4"), - gSavedSettings.getF32("AvatarAxisDeadZone5") - }; - - // time interval in seconds between this frame and the previous - F32 time = gFrameIntervalSeconds.value(); - - // avoid making ridicously big movements if there's a big drop in fps - if (time > .2f) - { - time = .2f; - } - - // note: max feather is 32.0 - F32 feather = gSavedSettings.getF32("AvatarFeathering"); - - F32 cur_delta[6]; - F32 val, dom_mov = 0.f; - U32 dom_axis = Z_I; -#if LIB_NDOF - bool absolute = (gSavedSettings.getBOOL("Cursor3D") && mNdofDev->absolute); -#else - bool absolute = false; -#endif - // remove dead zones and determine biggest movement on the joystick - for (U32 i = 0; i < 6; i++) - { - cur_delta[i] = -mAxes[axis[i]]; - if (absolute) - { - F32 tmp = cur_delta[i]; - cur_delta[i] = cur_delta[i] - sLastDelta[i]; - sLastDelta[i] = tmp; - } - - if (cur_delta[i] > 0) - { - cur_delta[i] = llmax(cur_delta[i]-dead_zone[i], 0.f); - } - else - { - cur_delta[i] = llmin(cur_delta[i]+dead_zone[i], 0.f); - } - - // we don't care about Roll (RZ) and Z is calculated after the loop - if (i != Z_I && i != RZ_I) - { - // find out the axis with the biggest joystick motion - val = fabs(cur_delta[i]); - if (val > dom_mov) - { - dom_axis = i; - dom_mov = val; - } - } - - is_zero = is_zero && (cur_delta[i] == 0.f); - } - - if (!is_zero) - { - // Clear AFK state if moved beyond the deadzone - if (gAwayTimer.getElapsedTimeF32() > LLAgent::MIN_AFK_TIME) - { - gAgent.clearAFK(); - } - - setCameraNeedsUpdate(true); - } - - // forward|backward movements overrule the real dominant movement if - // they're bigger than its 20%. This is what you want 'cos moving forward - // is what you do most. We also added a special (even more lenient) case - // for RX|RY to allow walking while pitching and turning - if (fabs(cur_delta[Z_I]) > .2f * dom_mov - || ((dom_axis == RX_I || dom_axis == RY_I) - && fabs(cur_delta[Z_I]) > .05f * dom_mov)) - { - dom_axis = Z_I; - } - - sDelta[X_I] = -cur_delta[X_I] * axis_scale[X_I]; - sDelta[Y_I] = -cur_delta[Y_I] * axis_scale[Y_I]; - sDelta[Z_I] = -cur_delta[Z_I] * axis_scale[Z_I]; - cur_delta[RX_I] *= -axis_scale[RX_I] * mPerfScale; - cur_delta[RY_I] *= -axis_scale[RY_I] * mPerfScale; - - if (!absolute) - { - cur_delta[RX_I] *= time; - cur_delta[RY_I] *= time; - } - sDelta[RX_I] += (cur_delta[RX_I] - sDelta[RX_I]) * time * feather; - sDelta[RY_I] += (cur_delta[RY_I] - sDelta[RY_I]) * time * feather; - - handleRun((F32) sqrt(sDelta[Z_I]*sDelta[Z_I] + sDelta[X_I]*sDelta[X_I])); - - // Allow forward/backward movement some priority - if (dom_axis == Z_I) - { - agentPush(sDelta[Z_I]); // forward/back - - if (fabs(sDelta[X_I]) > .1f) - { - agentSlide(sDelta[X_I]); // move sideways - } - - if (fabs(sDelta[Y_I]) > .1f) - { - agentFly(sDelta[Y_I]); // up/down & crouch - } - - // too many rotations during walking can be confusing, so apply - // the deadzones one more time (quick & dirty), at 50%|30% power - F32 eff_rx = .3f * dead_zone[RX_I]; - F32 eff_ry = .3f * dead_zone[RY_I]; - - if (sDelta[RX_I] > 0) - { - eff_rx = llmax(sDelta[RX_I] - eff_rx, 0.f); - } - else - { - eff_rx = llmin(sDelta[RX_I] + eff_rx, 0.f); - } - - if (sDelta[RY_I] > 0) - { - eff_ry = llmax(sDelta[RY_I] - eff_ry, 0.f); - } - else - { - eff_ry = llmin(sDelta[RY_I] + eff_ry, 0.f); - } - - - if (fabs(eff_rx) > 0.f || fabs(eff_ry) > 0.f) - { - if (gAgent.getFlying()) - { - agentPitch(eff_rx); - agentYaw(eff_ry); - } - else - { - agentPitch(eff_rx); - agentYaw(2.f * eff_ry); - } - } - } - else - { - agentSlide(sDelta[X_I]); // move sideways - agentFly(sDelta[Y_I]); // up/down & crouch - agentPush(sDelta[Z_I]); // forward/back - agentPitch(sDelta[RX_I]); // pitch - agentYaw(sDelta[RY_I]); // turn - } -} - -// ----------------------------------------------------------------------------- -void LLViewerJoystick::moveFlycam(bool reset) -{ - static LLQuaternion sFlycamRotation; - static LLVector3 sFlycamPosition; - static F32 sFlycamZoom; - - if (!gFocusMgr.getAppHasFocus() || mDriverState != JDS_INITIALIZED - || !gSavedSettings.getBOOL("JoystickEnabled") || !gSavedSettings.getBOOL("JoystickFlycamEnabled")) - { - return; - } - - S32 axis[] = - { - gSavedSettings.getS32("JoystickAxis0"), - gSavedSettings.getS32("JoystickAxis1"), - gSavedSettings.getS32("JoystickAxis2"), - gSavedSettings.getS32("JoystickAxis3"), - gSavedSettings.getS32("JoystickAxis4"), - gSavedSettings.getS32("JoystickAxis5"), - gSavedSettings.getS32("JoystickAxis6") - }; - - bool in_build_mode = LLToolMgr::getInstance()->inBuildMode(); - if (reset || mResetFlag) - { - sFlycamPosition = LLViewerCamera::getInstance()->getOrigin(); - sFlycamRotation = LLViewerCamera::getInstance()->getQuaternion(); - sFlycamZoom = LLViewerCamera::getInstance()->getView(); - - resetDeltas(axis); - - return; - } - - F32 axis_scale[] = - { - gSavedSettings.getF32("FlycamAxisScale0"), - gSavedSettings.getF32("FlycamAxisScale1"), - gSavedSettings.getF32("FlycamAxisScale2"), - gSavedSettings.getF32("FlycamAxisScale3"), - gSavedSettings.getF32("FlycamAxisScale4"), - gSavedSettings.getF32("FlycamAxisScale5"), - gSavedSettings.getF32("FlycamAxisScale6") - }; - - F32 dead_zone[] = - { - gSavedSettings.getF32("FlycamAxisDeadZone0"), - gSavedSettings.getF32("FlycamAxisDeadZone1"), - gSavedSettings.getF32("FlycamAxisDeadZone2"), - gSavedSettings.getF32("FlycamAxisDeadZone3"), - gSavedSettings.getF32("FlycamAxisDeadZone4"), - gSavedSettings.getF32("FlycamAxisDeadZone5"), - gSavedSettings.getF32("FlycamAxisDeadZone6") - }; - - F32 time = gFrameIntervalSeconds.value(); - - // avoid making ridiculously big movements if there's a big drop in fps - if (time > .2f) - { - time = .2f; - } - - F32 cur_delta[7]; - F32 feather = gSavedSettings.getF32("FlycamFeathering"); - bool absolute = gSavedSettings.getBOOL("Cursor3D"); - bool is_zero = true; - - for (U32 i = 0; i < 7; i++) - { - cur_delta[i] = -getJoystickAxis(axis[i]); - - - F32 tmp = cur_delta[i]; - if (absolute) - { - cur_delta[i] = cur_delta[i] - sLastDelta[i]; - } - sLastDelta[i] = tmp; - - if (cur_delta[i] > 0) - { - cur_delta[i] = llmax(cur_delta[i]-dead_zone[i], 0.f); - } - else - { - cur_delta[i] = llmin(cur_delta[i]+dead_zone[i], 0.f); - } - - // We may want to scale camera movements up or down in build mode. - // NOTE: this needs to remain after the deadzone calculation, otherwise - // we have issues with flycam "jumping" when the build dialog is opened/closed -Nyx - if (in_build_mode) - { - if (i == X_I || i == Y_I || i == Z_I) - { - static LLCachedControl build_mode_scale(gSavedSettings,"FlycamBuildModeScale", 1.0); - cur_delta[i] *= build_mode_scale; - } - } - - cur_delta[i] *= axis_scale[i]; - - if (!absolute) - { - cur_delta[i] *= time; - } - - sDelta[i] = sDelta[i] + (cur_delta[i]-sDelta[i])*time*feather; - - is_zero = is_zero && (cur_delta[i] == 0.f); - - } - - // Clear AFK state if moved beyond the deadzone - if (!is_zero && gAwayTimer.getElapsedTimeF32() > LLAgent::MIN_AFK_TIME) - { - gAgent.clearAFK(); - } - - sFlycamPosition += LLVector3(sDelta) * sFlycamRotation; - - LLMatrix3 rot_mat(sDelta[3], sDelta[4], sDelta[5]); - sFlycamRotation = LLQuaternion(rot_mat)*sFlycamRotation; - - if (gSavedSettings.getBOOL("AutoLeveling")) - { - LLMatrix3 level(sFlycamRotation); - - LLVector3 x = LLVector3(level.mMatrix[0]); - LLVector3 y = LLVector3(level.mMatrix[1]); - LLVector3 z = LLVector3(level.mMatrix[2]); - - y.mV[2] = 0.f; - y.normVec(); - - level.setRows(x,y,z); - level.orthogonalize(); - - LLQuaternion quat(level); - sFlycamRotation = nlerp(llmin(feather*time,1.f), sFlycamRotation, quat); - } - - if (gSavedSettings.getBOOL("ZoomDirect")) - { - sFlycamZoom = sLastDelta[6]*axis_scale[6]+dead_zone[6]; - } - else - { - sFlycamZoom += sDelta[6]; - } - - LLMatrix3 mat(sFlycamRotation); - - LLViewerCamera::getInstance()->setView(sFlycamZoom); - LLViewerCamera::getInstance()->setOrigin(sFlycamPosition); - LLViewerCamera::getInstance()->mXAxis = LLVector3(mat.mMatrix[0]); - LLViewerCamera::getInstance()->mYAxis = LLVector3(mat.mMatrix[1]); - LLViewerCamera::getInstance()->mZAxis = LLVector3(mat.mMatrix[2]); -} - -// ----------------------------------------------------------------------------- -bool LLViewerJoystick::toggleFlycam() -{ - if (!gSavedSettings.getBOOL("JoystickEnabled") || !gSavedSettings.getBOOL("JoystickFlycamEnabled")) - { - mOverrideCamera = false; - return false; - } - - if (!mOverrideCamera) - { - gAgentCamera.changeCameraToDefault(); - } - - if (gAwayTimer.getElapsedTimeF32() > LLAgent::MIN_AFK_TIME) - { - gAgent.clearAFK(); - } - - mOverrideCamera = !mOverrideCamera; - if (mOverrideCamera) - { - moveFlycam(true); - - } - else - { - // Exiting from the flycam mode: since we are going to keep the flycam POV for - // the main camera until the avatar moves, we need to track this situation. - setCameraNeedsUpdate(false); - setNeedsReset(true); - } - return true; -} - -void LLViewerJoystick::scanJoystick() -{ - if (mDriverState != JDS_INITIALIZED || !gSavedSettings.getBOOL("JoystickEnabled")) - { - return; - } - -#if LL_WINDOWS - // On windows, the flycam is updated syncronously with a timer, so there is - // no need to update the status of the joystick here. - if (!mOverrideCamera) -#endif - updateStatus(); - - // App focus check Needs to happen AFTER updateStatus in case the joystick - // is not centred when the app loses focus. - if (!gFocusMgr.getAppHasFocus()) - { - return; - } - - static long toggle_flycam = 0; - - if (mBtn[0] == 1) - { - if (mBtn[0] != toggle_flycam) - { - toggle_flycam = toggleFlycam() ? 1 : 0; - } - } - else - { - toggle_flycam = 0; - } - - if (!mOverrideCamera && !(LLToolMgr::getInstance()->inBuildMode() && gSavedSettings.getBOOL("JoystickBuildEnabled"))) - { - moveAvatar(); - } -} - -// ----------------------------------------------------------------------------- -bool LLViewerJoystick::isDeviceUUIDSet() -{ -#if LL_WINDOWS && !LL_MESA_HEADLESS - // for ease of comparison and to dial less with platform specific variables, we store id as LLSD binary - return mLastDeviceUUID.isBinary(); -#elif LL_DARWIN - return mLastDeviceUUID.isMap(); -#else - return false; -#endif -} - -LLSD LLViewerJoystick::getDeviceUUID() -{ - return mLastDeviceUUID; -} - -std::string LLViewerJoystick::getDeviceUUIDString() -{ -#if LL_WINDOWS && !LL_MESA_HEADLESS - // Might be simpler to just convert _GUID into string everywhere, store and compare as string - if (mLastDeviceUUID.isBinary()) - { - S32 size = sizeof(GUID); - LLSD::Binary data = mLastDeviceUUID.asBinary(); - GUID guid; - memcpy(&guid, &data[0], size); - return string_from_guid(guid); - } - else - { - return std::string(); - } -#elif LL_DARWIN - if (mLastDeviceUUID.isMap()) - { - std::string manufacturer = mLastDeviceUUID["manufacturer"].asString(); - std::string product = mLastDeviceUUID["product"].asString(); - return manufacturer + ":" + product; - } - else - { - return std::string(); - } -#else - return std::string(); -#endif -} - -void LLViewerJoystick::saveDeviceIdToSettings() -{ -#if LL_WINDOWS && !LL_MESA_HEADLESS - // can't save as binary directly, - // someone editing the xml will corrupt it - // so convert to string first - std::string device_string = getDeviceUUIDString(); - gSavedSettings.setLLSD("JoystickDeviceUUID", LLSD(device_string)); -#else - LLSD device_id = getDeviceUUID(); - gSavedSettings.setLLSD("JoystickDeviceUUID", device_id); -#endif -} - -void LLViewerJoystick::loadDeviceIdFromSettings() -{ - LLSD dev_id = gSavedSettings.getLLSD("JoystickDeviceUUID"); -#if LL_WINDOWS && !LL_MESA_HEADLESS - // We can't save binary data to gSavedSettings, somebody editing the file will corrupt it, - // so _GUID data gets converted to string (we probably can convert it to LLUUID with memcpy) - // and here we need to convert it back to binary from string - std::string device_string; - if (dev_id.isString()) - { - device_string = dev_id.asString(); - } - if (device_string.empty()) - { - mLastDeviceUUID = LLSD(); - } - else - { - LL_DEBUGS("Joystick") << "Looking for device by id: " << device_string << LL_ENDL; - GUID guid; - guid_from_string(guid, device_string); - S32 size = sizeof(GUID); - LLSD::Binary data; //just an std::vector - data.resize(size); - memcpy(&data[0], &guid /*POD _GUID*/, size); - // We store this data in LLSD since it can handle both GUID2 and long - mLastDeviceUUID = LLSD(data); - } -#elif LL_DARWIN - if (!dev_id.isMap()) - { - mLastDeviceUUID = LLSD(); - } - else - { - std::string manufacturer = mLastDeviceUUID["manufacturer"].asString(); - std::string product = mLastDeviceUUID["product"].asString(); - LL_DEBUGS("Joystick") << "Looking for device by manufacturer: " << manufacturer << " and product: " << product << LL_ENDL; - // We store this data in LLSD since it can handle both GUID2 and long - mLastDeviceUUID = dev_id; - } -#else - mLastDeviceUUID = LLSD(); - //mLastDeviceUUID = gSavedSettings.getLLSD("JoystickDeviceUUID"); -#endif -} - -// ----------------------------------------------------------------------------- -std::string LLViewerJoystick::getDescription() -{ - std::string res; -#if LIB_NDOF - if (mDriverState == JDS_INITIALIZED && mNdofDev) - { - res = ll_safe_string(mNdofDev->product); - } -#endif - return res; -} - -bool LLViewerJoystick::isLikeSpaceNavigator() const -{ -#if LIB_NDOF - return (isJoystickInitialized() - && (strncmp(mNdofDev->product, "SpaceNavigator", 14) == 0 - || strncmp(mNdofDev->product, "SpaceExplorer", 13) == 0 - || strncmp(mNdofDev->product, "SpaceTraveler", 13) == 0 - || strncmp(mNdofDev->product, "SpacePilot", 10) == 0)); -#else - return false; -#endif -} - -// ----------------------------------------------------------------------------- -void LLViewerJoystick::setSNDefaults() -{ -#if LL_DARWIN || LL_LINUX - const float platformScale = 20.f; - const float platformScaleAvXZ = 1.f; - // The SpaceNavigator doesn't act as a 3D cursor on macOS / Linux. - const bool is_3d_cursor = false; -#else - const float platformScale = 1.f; - const float platformScaleAvXZ = 2.f; - const bool is_3d_cursor = true; -#endif - - //gViewerWindow->alertXml("CacheWillClear"); - LL_INFOS("Joystick") << "restoring SpaceNavigator defaults..." << LL_ENDL; - - gSavedSettings.setS32("JoystickAxis0", 1); // z (at) - gSavedSettings.setS32("JoystickAxis1", 0); // x (slide) - gSavedSettings.setS32("JoystickAxis2", 2); // y (up) - gSavedSettings.setS32("JoystickAxis3", 4); // pitch - gSavedSettings.setS32("JoystickAxis4", 3); // roll - gSavedSettings.setS32("JoystickAxis5", 5); // yaw - gSavedSettings.setS32("JoystickAxis6", -1); - - gSavedSettings.setBOOL("Cursor3D", is_3d_cursor); - gSavedSettings.setBOOL("AutoLeveling", true); - gSavedSettings.setBOOL("ZoomDirect", false); - - gSavedSettings.setF32("AvatarAxisScale0", 1.f * platformScaleAvXZ); - gSavedSettings.setF32("AvatarAxisScale1", 1.f * platformScaleAvXZ); - gSavedSettings.setF32("AvatarAxisScale2", 1.f); - gSavedSettings.setF32("AvatarAxisScale4", .1f * platformScale); - gSavedSettings.setF32("AvatarAxisScale5", .1f * platformScale); - gSavedSettings.setF32("AvatarAxisScale3", 0.f * platformScale); - gSavedSettings.setF32("BuildAxisScale1", .3f * platformScale); - gSavedSettings.setF32("BuildAxisScale2", .3f * platformScale); - gSavedSettings.setF32("BuildAxisScale0", .3f * platformScale); - gSavedSettings.setF32("BuildAxisScale4", .3f * platformScale); - gSavedSettings.setF32("BuildAxisScale5", .3f * platformScale); - gSavedSettings.setF32("BuildAxisScale3", .3f * platformScale); - gSavedSettings.setF32("FlycamAxisScale1", 2.f * platformScale); - gSavedSettings.setF32("FlycamAxisScale2", 2.f * platformScale); - gSavedSettings.setF32("FlycamAxisScale0", 2.1f * platformScale); - gSavedSettings.setF32("FlycamAxisScale4", .1f * platformScale); - gSavedSettings.setF32("FlycamAxisScale5", .15f * platformScale); - gSavedSettings.setF32("FlycamAxisScale3", 0.f * platformScale); - gSavedSettings.setF32("FlycamAxisScale6", 0.f * platformScale); - - gSavedSettings.setF32("AvatarAxisDeadZone0", .1f); - gSavedSettings.setF32("AvatarAxisDeadZone1", .1f); - gSavedSettings.setF32("AvatarAxisDeadZone2", .1f); - gSavedSettings.setF32("AvatarAxisDeadZone3", 1.f); - gSavedSettings.setF32("AvatarAxisDeadZone4", .02f); - gSavedSettings.setF32("AvatarAxisDeadZone5", .01f); - gSavedSettings.setF32("BuildAxisDeadZone0", .01f); - gSavedSettings.setF32("BuildAxisDeadZone1", .01f); - gSavedSettings.setF32("BuildAxisDeadZone2", .01f); - gSavedSettings.setF32("BuildAxisDeadZone3", .01f); - gSavedSettings.setF32("BuildAxisDeadZone4", .01f); - gSavedSettings.setF32("BuildAxisDeadZone5", .01f); - gSavedSettings.setF32("FlycamAxisDeadZone0", .01f); - gSavedSettings.setF32("FlycamAxisDeadZone1", .01f); - gSavedSettings.setF32("FlycamAxisDeadZone2", .01f); - gSavedSettings.setF32("FlycamAxisDeadZone3", .01f); - gSavedSettings.setF32("FlycamAxisDeadZone4", .01f); - gSavedSettings.setF32("FlycamAxisDeadZone5", .01f); - gSavedSettings.setF32("FlycamAxisDeadZone6", 1.f); - - gSavedSettings.setF32("AvatarFeathering", 6.f); - gSavedSettings.setF32("BuildFeathering", 12.f); - gSavedSettings.setF32("FlycamFeathering", 5.f); -} +/** + * @file llviewerjoystick.cpp + * @brief Joystick / NDOF device functionality. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llviewerjoystick.h" + +#include "llviewercontrol.h" +#include "llviewerwindow.h" +#include "llviewercamera.h" +#include "llappviewer.h" +#include "llkeyboard.h" +#include "lltoolmgr.h" +#include "llselectmgr.h" +#include "llviewermenu.h" +#include "llviewerwindow.h" +#include "llwindow.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llfocusmgr.h" + +#if LL_WINDOWS && !LL_MESA_HEADLESS +// Require DirectInput version 8 +#define DIRECTINPUT_VERSION 0x0800 + +#include +#endif + + +// ---------------------------------------------------------------------------- +// Constants + +#define X_I 1 +#define Y_I 2 +#define Z_I 0 +#define RX_I 4 +#define RY_I 5 +#define RZ_I 3 + +F32 LLViewerJoystick::sLastDelta[] = {0,0,0,0,0,0,0}; +F32 LLViewerJoystick::sDelta[] = {0,0,0,0,0,0,0}; + +// These constants specify the maximum absolute value coming in from the device. +// HACK ALERT! the value of MAX_JOYSTICK_INPUT_VALUE is not arbitrary as it +// should be. It has to be equal to 3000 because the SpaceNavigator on Windows +// refuses to respond to the DirectInput SetProperty call; it always returns +// values in the [-3000, 3000] range. +#define MAX_SPACENAVIGATOR_INPUT 3000.0f +#define MAX_JOYSTICK_INPUT_VALUE MAX_SPACENAVIGATOR_INPUT + + +#if LIB_NDOF +std::ostream& operator<<(std::ostream& out, NDOF_Device* ptr) +{ + if (! ptr) + { + return out << "nullptr"; + } + out << "NDOF_Device{ "; + out << "axes ["; + const char* delim = ""; + for (short axis = 0; axis < ptr->axes_count; ++axis) + { + out << delim << ptr->axes[axis]; + delim = ", "; + } + out << "]"; + out << ", buttons ["; + delim = ""; + for (short button = 0; button < ptr->btn_count; ++button) + { + out << delim << ptr->buttons[button]; + delim = ", "; + } + out << "]"; + out << ", range " << ptr->axes_min << ':' << ptr->axes_max; + // If we don't coerce these to unsigned, they're streamed as characters, + // e.g. ctrl-A or nul. + out << ", absolute " << unsigned(ptr->absolute); + out << ", valid " << unsigned(ptr->valid); + out << ", manufacturer '" << ptr->manufacturer << "'"; + out << ", product '" << ptr->product << "'"; + out << ", private " << ptr->private_data; + out << " }"; + return out; +} +#endif // LIB_NDOF + + +#if LL_WINDOWS && !LL_MESA_HEADLESS +// this should reflect ndof and set axises, see ndofdev_win.cpp from ndof package +BOOL CALLBACK EnumObjectsCallback(const DIDEVICEOBJECTINSTANCE* inst, VOID* user_data) +{ + if (inst->dwType & DIDFT_AXIS) + { + LPDIRECTINPUTDEVICE8 device = *((LPDIRECTINPUTDEVICE8 *)user_data); + DIPROPRANGE diprg; + diprg.diph.dwSize = sizeof(DIPROPRANGE); + diprg.diph.dwHeaderSize = sizeof(DIPROPHEADER); + diprg.diph.dwHow = DIPH_BYID; + diprg.diph.dwObj = inst->dwType; // specify the enumerated axis + + // Set the range for the axis + diprg.lMin = (long)-MAX_JOYSTICK_INPUT_VALUE; + diprg.lMax = (long)+MAX_JOYSTICK_INPUT_VALUE; + HRESULT hr = device->SetProperty(DIPROP_RANGE, &diprg.diph); + + if (FAILED(hr)) + { + return DIENUM_STOP; + } + } + + return DIENUM_CONTINUE; +} + +BOOL CALLBACK di8_devices_callback(LPCDIDEVICEINSTANCE device_instance_ptr, LPVOID pvRef) +{ + // Note: If a single device can function as more than one DirectInput + // device type, it is enumerated as each device type that it supports. + // Capable of detecting devices like Oculus Rift + if (device_instance_ptr) + { + std::string product_name = utf16str_to_utf8str(llutf16string(device_instance_ptr->tszProductName)); + + LLSD guid = LLViewerJoystick::getInstance()->getDeviceUUID(); + + bool init_device = false; + if (guid.isBinary()) + { + std::vector bin_bucket = guid.asBinary(); + init_device = memcmp(&bin_bucket[0], &device_instance_ptr->guidInstance, sizeof(GUID)) == 0; + } + else + { + // It might be better to init space navigator here, but if system doesn't has one, + // ndof will pick a random device, it is simpler to pick first device now to have an id + init_device = true; + } + + if (init_device) + { + LL_DEBUGS("Joystick") << "Found and attempting to use device: " << product_name << LL_ENDL; + LPDIRECTINPUT8 di8_interface = *((LPDIRECTINPUT8 *)gViewerWindow->getWindow()->getDirectInput8()); + LPDIRECTINPUTDEVICE8 device = NULL; + + HRESULT status = di8_interface->CreateDevice( + device_instance_ptr->guidInstance, // REFGUID rguid, + &device, // LPDIRECTINPUTDEVICE * lplpDirectInputDevice, + NULL // LPUNKNOWN pUnkOuter + ); + + if (status == DI_OK) + { + // prerequisite for aquire() + LL_DEBUGS("Joystick") << "Device created" << LL_ENDL; + status = device->SetDataFormat(&c_dfDIJoystick); // c_dfDIJoystick2 + } + + if (status == DI_OK) + { + // set properties + LL_DEBUGS("Joystick") << "Format set" << LL_ENDL; + status = device->EnumObjects(EnumObjectsCallback, &device, DIDFT_ALL); + } + + if (status == DI_OK) + { + LL_DEBUGS("Joystick") << "Properties updated" << LL_ENDL; + + S32 size = sizeof(GUID); + LLSD::Binary data; //just an std::vector + data.resize(size); + memcpy(&data[0], &device_instance_ptr->guidInstance /*POD _GUID*/, size); + LLViewerJoystick::getInstance()->initDevice(&device, product_name, LLSD(data)); + return DIENUM_STOP; + } + } + else + { + LL_DEBUGS("Joystick") << "Found device: " << product_name << LL_ENDL; + } + } + return DIENUM_CONTINUE; +} + +// Windows guids +// This is GUID2 so teoretically it can be memcpy copied into LLUUID +void guid_from_string(GUID &guid, const std::string &input) +{ + CLSIDFromString(utf8str_to_utf16str(input).c_str(), &guid); +} + +std::string string_from_guid(const GUID &guid) +{ + OLECHAR* guidString; //wchat + StringFromCLSID(guid, &guidString); + + // use guidString... + + std::string res = utf16str_to_utf8str(llutf16string(guidString)); + // ensure memory is freed + ::CoTaskMemFree(guidString); + + return res; +} +#elif LL_DARWIN + +bool macos_devices_callback(std::string &product_name, LLSD &data, void* userdata) +{ + std::string product = data["product"].asString(); + + return LLViewerJoystick::getInstance()->initDevice(nullptr, product, data); +} + +#endif + + +// ----------------------------------------------------------------------------- +void LLViewerJoystick::updateEnabled(bool autoenable) +{ + if (mDriverState == JDS_UNINITIALIZED) + { + gSavedSettings.setBOOL("JoystickEnabled", false); + } + else + { + // autoenable if user specifically chose this device + if (autoenable && (isLikeSpaceNavigator() || isDeviceUUIDSet())) + { + gSavedSettings.setBOOL("JoystickEnabled", true ); + } + } + if (!gSavedSettings.getBOOL("JoystickEnabled")) + { + mOverrideCamera = false; + } +} + +void LLViewerJoystick::setOverrideCamera(bool val) +{ + if (!gSavedSettings.getBOOL("JoystickEnabled")) + { + mOverrideCamera = false; + } + else + { + mOverrideCamera = val; + } + + if (mOverrideCamera) + { + gAgentCamera.changeCameraToDefault(); + } +} + +// ----------------------------------------------------------------------------- +#if LIB_NDOF +NDOF_HotPlugResult LLViewerJoystick::HotPlugAddCallback(NDOF_Device *dev) +{ + NDOF_HotPlugResult res = NDOF_DISCARD_HOTPLUGGED; + LLViewerJoystick* joystick(LLViewerJoystick::getInstance()); + if (joystick->mDriverState == JDS_UNINITIALIZED) + { + LL_INFOS("Joystick") << "HotPlugAddCallback: will use device:" << LL_ENDL; + ndof_dump(stderr, dev); + joystick->mNdofDev = dev; + joystick->mDriverState = JDS_INITIALIZED; + res = NDOF_KEEP_HOTPLUGGED; + } + joystick->updateEnabled(true); + return res; +} +#endif + +// ----------------------------------------------------------------------------- +#if LIB_NDOF +void LLViewerJoystick::HotPlugRemovalCallback(NDOF_Device *dev) +{ + LLViewerJoystick* joystick(LLViewerJoystick::getInstance()); + if (joystick->mNdofDev == dev) + { + LL_INFOS("Joystick") << "HotPlugRemovalCallback: joystick->mNdofDev=" + << joystick->mNdofDev << "; removed device:" << LL_ENDL; + ndof_dump(stderr, dev); + joystick->mDriverState = JDS_UNINITIALIZED; + } + joystick->updateEnabled(true); +} +#endif + +// ----------------------------------------------------------------------------- +LLViewerJoystick::LLViewerJoystick() +: mDriverState(JDS_UNINITIALIZED), + mNdofDev(NULL), + mResetFlag(false), + mCameraUpdated(true), + mOverrideCamera(false), + mJoystickRun(0) +{ + for (int i = 0; i < 6; i++) + { + mAxes[i] = sDelta[i] = sLastDelta[i] = 0.0f; + } + + memset(mBtn, 0, sizeof(mBtn)); + + // factor in bandwidth? bandwidth = gViewerStats->mKBitStat + mPerfScale = 4000.f / gSysCPU.getMHz(); // hmm. why? + + mLastDeviceUUID = LLSD::Integer(1); +} + +// ----------------------------------------------------------------------------- +LLViewerJoystick::~LLViewerJoystick() +{ + if (mDriverState == JDS_INITIALIZED) + { + terminate(); + } +} + +// ----------------------------------------------------------------------------- +void LLViewerJoystick::init(bool autoenable) +{ +#if LIB_NDOF + static bool libinit = false; + mDriverState = JDS_INITIALIZING; + + loadDeviceIdFromSettings(); + + if (!libinit) + { + // Note: The HotPlug callbacks are not actually getting called on Windows + if (ndof_libinit(HotPlugAddCallback, + HotPlugRemovalCallback, + gViewerWindow->getWindow()->getDirectInput8())) + { + mDriverState = JDS_UNINITIALIZED; + } + else + { + // NB: ndof_libinit succeeds when there's no device + libinit = true; + + // allocate memory once for an eventual device + mNdofDev = ndof_create(); + } + } + + if (libinit) + { + if (mNdofDev) + { + U32 device_type = 0; + void* win_callback = nullptr; + std::function osx_callback; + // di8_devices_callback callback is immediate and happens in scope of getInputDevices() +#if LL_WINDOWS && !LL_MESA_HEADLESS + // space navigator is marked as DI8DEVCLASS_GAMECTRL in ndof lib + device_type = DI8DEVCLASS_GAMECTRL; + win_callback = &di8_devices_callback; +#elif LL_DARWIN + osx_callback = macos_devices_callback; + + if (mLastDeviceUUID.isMap()) + { + std::string manufacturer = mLastDeviceUUID["manufacturer"].asString(); + std::string product = mLastDeviceUUID["product"].asString(); + + strncpy(mNdofDev->manufacturer, manufacturer.c_str(), sizeof(mNdofDev->manufacturer)); + strncpy(mNdofDev->product, product.c_str(), sizeof(mNdofDev->product)); + + if (ndof_init_first(mNdofDev, nullptr)) + { + mDriverState = JDS_INITIALIZING; + // Saved device no longer exist + // No device found + LL_WARNS() << "ndof_init_first FAILED" << LL_ENDL; + } + else + { + mDriverState = JDS_INITIALIZED; + } + } +#endif + if (mDriverState != JDS_INITIALIZED) + { + if (!gViewerWindow->getWindow()->getInputDevices(device_type, osx_callback, win_callback, NULL)) + { + LL_INFOS("Joystick") << "Failed to gather input devices. Falling back to ndof's init" << LL_ENDL; + // Failed to gather devices, init first suitable one + mLastDeviceUUID = LLSD(); + void *preffered_device = NULL; + initDevice(preffered_device); + } + } + + if (mDriverState == JDS_INITIALIZING) + { + LL_INFOS("Joystick") << "Found no matching joystick devices." << LL_ENDL; + mDriverState = JDS_UNINITIALIZED; + } + } + else + { + mDriverState = JDS_UNINITIALIZED; + } + } + + // Autoenable the joystick for recognized devices if nothing was connected previously + if (!autoenable) + { + autoenable = gSavedSettings.getString("JoystickInitialized").empty(); + } + updateEnabled(autoenable); + + if (mDriverState == JDS_INITIALIZED) + { + // A Joystick device is plugged in + if (isLikeSpaceNavigator()) + { + // It's a space navigator, we have defaults for it. + if (gSavedSettings.getString("JoystickInitialized") != "SpaceNavigator") + { + // Only set the defaults if we haven't already (in case they were overridden) + setSNDefaults(); + gSavedSettings.setString("JoystickInitialized", "SpaceNavigator"); + } + } + else + { + // It's not a Space Navigator + gSavedSettings.setString("JoystickInitialized", "UnknownDevice"); + } + } + else + { + // No device connected, don't change any settings + } + + LL_INFOS("Joystick") << "ndof: mDriverState=" << mDriverState << "; mNdofDev=" + << mNdofDev << "; libinit=" << libinit << LL_ENDL; +#endif +} + +void LLViewerJoystick::initDevice(LLSD &guid) +{ +#if LIB_NDOF + mLastDeviceUUID = guid; + U32 device_type = 0; + void* win_callback = nullptr; + std::function osx_callback; + mDriverState = JDS_INITIALIZING; + +#if LL_WINDOWS && !LL_MESA_HEADLESS + // space navigator is marked as DI8DEVCLASS_GAMECTRL in ndof lib + device_type = DI8DEVCLASS_GAMECTRL; + win_callback = &di8_devices_callback; +#elif LL_DARWIN + osx_callback = macos_devices_callback; + if (mLastDeviceUUID.isMap()) + { + std::string manufacturer = mLastDeviceUUID["manufacturer"].asString(); + std::string product = mLastDeviceUUID["product"].asString(); + + strncpy(mNdofDev->manufacturer, manufacturer.c_str(), sizeof(mNdofDev->manufacturer)); + strncpy(mNdofDev->product, product.c_str(), sizeof(mNdofDev->product)); + + if (ndof_init_first(mNdofDev, nullptr)) + { + mDriverState = JDS_INITIALIZING; + // Saved device no longer exist + // Np other device present + LL_WARNS() << "ndof_init_first FAILED" << LL_ENDL; + } + else + { + mDriverState = JDS_INITIALIZED; + } + } +#endif + + if (mDriverState != JDS_INITIALIZED) + { + if (!gViewerWindow->getWindow()->getInputDevices(device_type, osx_callback, win_callback, NULL)) + { + LL_INFOS("Joystick") << "Failed to gather input devices. Falling back to ndof's init" << LL_ENDL; + // Failed to gather devices from window, init first suitable one + void *preffered_device = NULL; + mLastDeviceUUID = LLSD(); + initDevice(preffered_device); + } + } + + if (mDriverState == JDS_INITIALIZING) + { + LL_INFOS("Joystick") << "Found no matching joystick devices." << LL_ENDL; + mDriverState = JDS_UNINITIALIZED; + } +#endif +} + +bool LLViewerJoystick::initDevice(void * preffered_device /*LPDIRECTINPUTDEVICE8*/, std::string &name, LLSD &guid) +{ +#if LIB_NDOF + mLastDeviceUUID = guid; + +#if LL_DARWIN + if (guid.isMap()) + { + std::string manufacturer = mLastDeviceUUID["manufacturer"].asString(); + std::string product = mLastDeviceUUID["product"].asString(); + + strncpy(mNdofDev->manufacturer, manufacturer.c_str(), sizeof(mNdofDev->manufacturer)); + strncpy(mNdofDev->product, product.c_str(), sizeof(mNdofDev->product)); + } + else + { + mNdofDev->product[0] = '\0'; + mNdofDev->manufacturer[0] = '\0'; + } +#else + strncpy(mNdofDev->product, name.c_str(), sizeof(mNdofDev->product)); + mNdofDev->manufacturer[0] = '\0'; +#endif + + return initDevice(preffered_device); +#else + return false; +#endif +} + +bool LLViewerJoystick::initDevice(void * preffered_device /* LPDIRECTINPUTDEVICE8* */) +{ +#if LIB_NDOF + // Different joysticks will return different ranges of raw values. + // Since we want to handle every device in the same uniform way, + // we initialize the mNdofDev struct and we set the range + // of values we would like to receive. + // + // HACK: On Windows, libndofdev passes our range to DI with a + // SetProperty call. This works but with one notable exception, the + // SpaceNavigator, who doesn't seem to care about the SetProperty + // call. In theory, we should handle this case inside libndofdev. + // However, the range we're setting here is arbitrary anyway, + // so let's just use the SpaceNavigator range for our purposes. + mNdofDev->axes_min = (long)-MAX_JOYSTICK_INPUT_VALUE; + mNdofDev->axes_max = (long)+MAX_JOYSTICK_INPUT_VALUE; + + // libndofdev could be used to return deltas. Here we choose to + // just have the absolute values instead. + mNdofDev->absolute = 1; + // init & use the first suitable NDOF device found on the USB chain + // On windows preffered_device needs to be a pointer to LPDIRECTINPUTDEVICE8 + if (ndof_init_first(mNdofDev, preffered_device)) + { + mDriverState = JDS_UNINITIALIZED; + LL_WARNS() << "ndof_init_first FAILED" << LL_ENDL; + } + else + { + mDriverState = JDS_INITIALIZED; + return true; + } +#endif + return false; +} + +// ----------------------------------------------------------------------------- +void LLViewerJoystick::terminate() +{ +#if LIB_NDOF + if (mNdofDev != NULL) + { + ndof_libcleanup(); // frees alocated memory in mNdofDev + mDriverState = JDS_UNINITIALIZED; + mNdofDev = NULL; + LL_INFOS("Joystick") << "Terminated connection with NDOF device." << LL_ENDL; + } +#endif +} + +// ----------------------------------------------------------------------------- +void LLViewerJoystick::updateStatus() +{ +#if LIB_NDOF + + ndof_update(mNdofDev); + + for (int i=0; i<6; i++) + { + mAxes[i] = (F32) mNdofDev->axes[i] / mNdofDev->axes_max; + } + + for (int i=0; i<16; i++) + { + mBtn[i] = mNdofDev->buttons[i]; + } + +#endif +} + +// ----------------------------------------------------------------------------- +F32 LLViewerJoystick::getJoystickAxis(U32 axis) const +{ + if (axis < 6) + { + return mAxes[axis]; + } + return 0.f; +} + +// ----------------------------------------------------------------------------- +U32 LLViewerJoystick::getJoystickButton(U32 button) const +{ + if (button < 16) + { + return mBtn[button]; + } + return 0; +} + +// ----------------------------------------------------------------------------- +void LLViewerJoystick::handleRun(F32 inc) +{ + // Decide whether to walk or run by applying a threshold, with slight + // hysteresis to avoid oscillating between the two with input spikes. + // Analog speed control would be better, but not likely any time soon. + if (inc > gSavedSettings.getF32("JoystickRunThreshold")) + { + if (1 == mJoystickRun) + { + ++mJoystickRun; + gAgent.setRunning(); + gAgent.sendWalkRun(gAgent.getRunning()); + } + else if (0 == mJoystickRun) + { + // hysteresis - respond NEXT frame + ++mJoystickRun; + } + } + else + { + if (mJoystickRun > 0) + { + --mJoystickRun; + if (0 == mJoystickRun) + { + gAgent.clearRunning(); + gAgent.sendWalkRun(gAgent.getRunning()); + } + } + } +} + +// ----------------------------------------------------------------------------- +void LLViewerJoystick::agentJump() +{ + gAgent.moveUp(1); +} + +// ----------------------------------------------------------------------------- +void LLViewerJoystick::agentSlide(F32 inc) +{ + if (inc < 0.f) + { + gAgent.moveLeft(1); + } + else if (inc > 0.f) + { + gAgent.moveLeft(-1); + } +} + +// ----------------------------------------------------------------------------- +void LLViewerJoystick::agentPush(F32 inc) +{ + if (inc < 0.f) // forward + { + gAgent.moveAt(1, false); + } + else if (inc > 0.f) // backward + { + gAgent.moveAt(-1, false); + } +} + +// ----------------------------------------------------------------------------- +void LLViewerJoystick::agentFly(F32 inc) +{ + if (inc < 0.f) + { + if (! (gAgent.getFlying() || + !gAgent.canFly() || + gAgent.upGrabbed() || + !gSavedSettings.getBOOL("AutomaticFly")) ) + { + gAgent.setFlying(true); + } + gAgent.moveUp(1); + } + else if (inc > 0.f) + { + // crouch + gAgent.moveUp(-1); + } +} + +// ----------------------------------------------------------------------------- +void LLViewerJoystick::agentPitch(F32 pitch_inc) +{ + if (pitch_inc < 0) + { + gAgent.setControlFlags(AGENT_CONTROL_PITCH_POS); + } + else if (pitch_inc > 0) + { + gAgent.setControlFlags(AGENT_CONTROL_PITCH_NEG); + } + + gAgent.pitch(-pitch_inc); +} + +// ----------------------------------------------------------------------------- +void LLViewerJoystick::agentYaw(F32 yaw_inc) +{ + // Cannot steer some vehicles in mouselook if the script grabs the controls + if (gAgentCamera.cameraMouselook() && !gSavedSettings.getBOOL("JoystickMouselookYaw")) + { + gAgent.rotate(-yaw_inc, gAgent.getReferenceUpVector()); + } + else + { + if (yaw_inc < 0) + { + gAgent.setControlFlags(AGENT_CONTROL_YAW_POS); + } + else if (yaw_inc > 0) + { + gAgent.setControlFlags(AGENT_CONTROL_YAW_NEG); + } + + gAgent.yaw(-yaw_inc); + } +} + +// ----------------------------------------------------------------------------- +void LLViewerJoystick::resetDeltas(S32 axis[]) +{ + for (U32 i = 0; i < 6; i++) + { + sLastDelta[i] = -mAxes[axis[i]]; + sDelta[i] = 0.f; + } + + sLastDelta[6] = sDelta[6] = 0.f; + mResetFlag = false; +} + +// ----------------------------------------------------------------------------- +void LLViewerJoystick::moveObjects(bool reset) +{ + static bool toggle_send_to_sim = false; + + if (!gFocusMgr.getAppHasFocus() || mDriverState != JDS_INITIALIZED + || !gSavedSettings.getBOOL("JoystickEnabled") || !gSavedSettings.getBOOL("JoystickBuildEnabled")) + { + return; + } + + S32 axis[] = + { + gSavedSettings.getS32("JoystickAxis0"), + gSavedSettings.getS32("JoystickAxis1"), + gSavedSettings.getS32("JoystickAxis2"), + gSavedSettings.getS32("JoystickAxis3"), + gSavedSettings.getS32("JoystickAxis4"), + gSavedSettings.getS32("JoystickAxis5"), + }; + + if (reset || mResetFlag) + { + resetDeltas(axis); + return; + } + + F32 axis_scale[] = + { + gSavedSettings.getF32("BuildAxisScale0"), + gSavedSettings.getF32("BuildAxisScale1"), + gSavedSettings.getF32("BuildAxisScale2"), + gSavedSettings.getF32("BuildAxisScale3"), + gSavedSettings.getF32("BuildAxisScale4"), + gSavedSettings.getF32("BuildAxisScale5"), + }; + + F32 dead_zone[] = + { + gSavedSettings.getF32("BuildAxisDeadZone0"), + gSavedSettings.getF32("BuildAxisDeadZone1"), + gSavedSettings.getF32("BuildAxisDeadZone2"), + gSavedSettings.getF32("BuildAxisDeadZone3"), + gSavedSettings.getF32("BuildAxisDeadZone4"), + gSavedSettings.getF32("BuildAxisDeadZone5"), + }; + + F32 cur_delta[6]; + F32 time = gFrameIntervalSeconds.value(); + + // avoid making ridicously big movements if there's a big drop in fps + if (time > .2f) + { + time = .2f; + } + + // max feather is 32 + F32 feather = gSavedSettings.getF32("BuildFeathering"); + bool is_zero = true, absolute = gSavedSettings.getBOOL("Cursor3D"); + + for (U32 i = 0; i < 6; i++) + { + cur_delta[i] = -mAxes[axis[i]]; + F32 tmp = cur_delta[i]; + if (absolute) + { + cur_delta[i] = cur_delta[i] - sLastDelta[i]; + } + sLastDelta[i] = tmp; + is_zero = is_zero && (cur_delta[i] == 0.f); + + if (cur_delta[i] > 0) + { + cur_delta[i] = llmax(cur_delta[i]-dead_zone[i], 0.f); + } + else + { + cur_delta[i] = llmin(cur_delta[i]+dead_zone[i], 0.f); + } + cur_delta[i] *= axis_scale[i]; + + if (!absolute) + { + cur_delta[i] *= time; + } + + sDelta[i] = sDelta[i] + (cur_delta[i]-sDelta[i])*time*feather; + } + + U32 upd_type = UPD_NONE; + LLVector3 v; + + if (!is_zero) + { + // Clear AFK state if moved beyond the deadzone + if (gAwayTimer.getElapsedTimeF32() > LLAgent::MIN_AFK_TIME) + { + gAgent.clearAFK(); + } + + if (sDelta[0] || sDelta[1] || sDelta[2]) + { + upd_type |= UPD_POSITION; + v.setVec(sDelta[0], sDelta[1], sDelta[2]); + } + + if (sDelta[3] || sDelta[4] || sDelta[5]) + { + upd_type |= UPD_ROTATION; + } + + // the selection update could fail, so we won't send + if (LLSelectMgr::getInstance()->selectionMove(v, sDelta[3],sDelta[4],sDelta[5], upd_type)) + { + toggle_send_to_sim = true; + } + } + else if (toggle_send_to_sim) + { + LLSelectMgr::getInstance()->sendSelectionMove(); + toggle_send_to_sim = false; + } +} + +// ----------------------------------------------------------------------------- +void LLViewerJoystick::moveAvatar(bool reset) +{ + if (!gFocusMgr.getAppHasFocus() || mDriverState != JDS_INITIALIZED + || !gSavedSettings.getBOOL("JoystickEnabled") || !gSavedSettings.getBOOL("JoystickAvatarEnabled")) + { + return; + } + + S32 axis[] = + { + // [1 0 2 4 3 5] + // [Z X Y RZ RX RY] + gSavedSettings.getS32("JoystickAxis0"), + gSavedSettings.getS32("JoystickAxis1"), + gSavedSettings.getS32("JoystickAxis2"), + gSavedSettings.getS32("JoystickAxis3"), + gSavedSettings.getS32("JoystickAxis4"), + gSavedSettings.getS32("JoystickAxis5") + }; + + if (reset || mResetFlag) + { + resetDeltas(axis); + if (reset) + { + // Note: moving the agent triggers agent camera mode; + // don't do this every time we set mResetFlag (e.g. because we gained focus) + gAgent.moveAt(0, true); + } + return; + } + + bool is_zero = true; + static bool button_held = false; + + if (mBtn[1] == 1) + { + // If AutomaticFly is enabled, then button1 merely causes a + // jump (as the up/down axis already controls flying) if on the + // ground, or cease flight if already flying. + // If AutomaticFly is disabled, then button1 toggles flying. + if (gSavedSettings.getBOOL("AutomaticFly")) + { + if (!gAgent.getFlying()) + { + gAgent.moveUp(1); + } + else if (!button_held) + { + button_held = true; + gAgent.setFlying(false); + } + } + else if (!button_held) + { + button_held = true; + gAgent.setFlying(!gAgent.getFlying()); + } + + is_zero = false; + } + else + { + button_held = false; + } + + F32 axis_scale[] = + { + gSavedSettings.getF32("AvatarAxisScale0"), + gSavedSettings.getF32("AvatarAxisScale1"), + gSavedSettings.getF32("AvatarAxisScale2"), + gSavedSettings.getF32("AvatarAxisScale3"), + gSavedSettings.getF32("AvatarAxisScale4"), + gSavedSettings.getF32("AvatarAxisScale5") + }; + + F32 dead_zone[] = + { + gSavedSettings.getF32("AvatarAxisDeadZone0"), + gSavedSettings.getF32("AvatarAxisDeadZone1"), + gSavedSettings.getF32("AvatarAxisDeadZone2"), + gSavedSettings.getF32("AvatarAxisDeadZone3"), + gSavedSettings.getF32("AvatarAxisDeadZone4"), + gSavedSettings.getF32("AvatarAxisDeadZone5") + }; + + // time interval in seconds between this frame and the previous + F32 time = gFrameIntervalSeconds.value(); + + // avoid making ridicously big movements if there's a big drop in fps + if (time > .2f) + { + time = .2f; + } + + // note: max feather is 32.0 + F32 feather = gSavedSettings.getF32("AvatarFeathering"); + + F32 cur_delta[6]; + F32 val, dom_mov = 0.f; + U32 dom_axis = Z_I; +#if LIB_NDOF + bool absolute = (gSavedSettings.getBOOL("Cursor3D") && mNdofDev->absolute); +#else + bool absolute = false; +#endif + // remove dead zones and determine biggest movement on the joystick + for (U32 i = 0; i < 6; i++) + { + cur_delta[i] = -mAxes[axis[i]]; + if (absolute) + { + F32 tmp = cur_delta[i]; + cur_delta[i] = cur_delta[i] - sLastDelta[i]; + sLastDelta[i] = tmp; + } + + if (cur_delta[i] > 0) + { + cur_delta[i] = llmax(cur_delta[i]-dead_zone[i], 0.f); + } + else + { + cur_delta[i] = llmin(cur_delta[i]+dead_zone[i], 0.f); + } + + // we don't care about Roll (RZ) and Z is calculated after the loop + if (i != Z_I && i != RZ_I) + { + // find out the axis with the biggest joystick motion + val = fabs(cur_delta[i]); + if (val > dom_mov) + { + dom_axis = i; + dom_mov = val; + } + } + + is_zero = is_zero && (cur_delta[i] == 0.f); + } + + if (!is_zero) + { + // Clear AFK state if moved beyond the deadzone + if (gAwayTimer.getElapsedTimeF32() > LLAgent::MIN_AFK_TIME) + { + gAgent.clearAFK(); + } + + setCameraNeedsUpdate(true); + } + + // forward|backward movements overrule the real dominant movement if + // they're bigger than its 20%. This is what you want 'cos moving forward + // is what you do most. We also added a special (even more lenient) case + // for RX|RY to allow walking while pitching and turning + if (fabs(cur_delta[Z_I]) > .2f * dom_mov + || ((dom_axis == RX_I || dom_axis == RY_I) + && fabs(cur_delta[Z_I]) > .05f * dom_mov)) + { + dom_axis = Z_I; + } + + sDelta[X_I] = -cur_delta[X_I] * axis_scale[X_I]; + sDelta[Y_I] = -cur_delta[Y_I] * axis_scale[Y_I]; + sDelta[Z_I] = -cur_delta[Z_I] * axis_scale[Z_I]; + cur_delta[RX_I] *= -axis_scale[RX_I] * mPerfScale; + cur_delta[RY_I] *= -axis_scale[RY_I] * mPerfScale; + + if (!absolute) + { + cur_delta[RX_I] *= time; + cur_delta[RY_I] *= time; + } + sDelta[RX_I] += (cur_delta[RX_I] - sDelta[RX_I]) * time * feather; + sDelta[RY_I] += (cur_delta[RY_I] - sDelta[RY_I]) * time * feather; + + handleRun((F32) sqrt(sDelta[Z_I]*sDelta[Z_I] + sDelta[X_I]*sDelta[X_I])); + + // Allow forward/backward movement some priority + if (dom_axis == Z_I) + { + agentPush(sDelta[Z_I]); // forward/back + + if (fabs(sDelta[X_I]) > .1f) + { + agentSlide(sDelta[X_I]); // move sideways + } + + if (fabs(sDelta[Y_I]) > .1f) + { + agentFly(sDelta[Y_I]); // up/down & crouch + } + + // too many rotations during walking can be confusing, so apply + // the deadzones one more time (quick & dirty), at 50%|30% power + F32 eff_rx = .3f * dead_zone[RX_I]; + F32 eff_ry = .3f * dead_zone[RY_I]; + + if (sDelta[RX_I] > 0) + { + eff_rx = llmax(sDelta[RX_I] - eff_rx, 0.f); + } + else + { + eff_rx = llmin(sDelta[RX_I] + eff_rx, 0.f); + } + + if (sDelta[RY_I] > 0) + { + eff_ry = llmax(sDelta[RY_I] - eff_ry, 0.f); + } + else + { + eff_ry = llmin(sDelta[RY_I] + eff_ry, 0.f); + } + + + if (fabs(eff_rx) > 0.f || fabs(eff_ry) > 0.f) + { + if (gAgent.getFlying()) + { + agentPitch(eff_rx); + agentYaw(eff_ry); + } + else + { + agentPitch(eff_rx); + agentYaw(2.f * eff_ry); + } + } + } + else + { + agentSlide(sDelta[X_I]); // move sideways + agentFly(sDelta[Y_I]); // up/down & crouch + agentPush(sDelta[Z_I]); // forward/back + agentPitch(sDelta[RX_I]); // pitch + agentYaw(sDelta[RY_I]); // turn + } +} + +// ----------------------------------------------------------------------------- +void LLViewerJoystick::moveFlycam(bool reset) +{ + static LLQuaternion sFlycamRotation; + static LLVector3 sFlycamPosition; + static F32 sFlycamZoom; + + if (!gFocusMgr.getAppHasFocus() || mDriverState != JDS_INITIALIZED + || !gSavedSettings.getBOOL("JoystickEnabled") || !gSavedSettings.getBOOL("JoystickFlycamEnabled")) + { + return; + } + + S32 axis[] = + { + gSavedSettings.getS32("JoystickAxis0"), + gSavedSettings.getS32("JoystickAxis1"), + gSavedSettings.getS32("JoystickAxis2"), + gSavedSettings.getS32("JoystickAxis3"), + gSavedSettings.getS32("JoystickAxis4"), + gSavedSettings.getS32("JoystickAxis5"), + gSavedSettings.getS32("JoystickAxis6") + }; + + bool in_build_mode = LLToolMgr::getInstance()->inBuildMode(); + if (reset || mResetFlag) + { + sFlycamPosition = LLViewerCamera::getInstance()->getOrigin(); + sFlycamRotation = LLViewerCamera::getInstance()->getQuaternion(); + sFlycamZoom = LLViewerCamera::getInstance()->getView(); + + resetDeltas(axis); + + return; + } + + F32 axis_scale[] = + { + gSavedSettings.getF32("FlycamAxisScale0"), + gSavedSettings.getF32("FlycamAxisScale1"), + gSavedSettings.getF32("FlycamAxisScale2"), + gSavedSettings.getF32("FlycamAxisScale3"), + gSavedSettings.getF32("FlycamAxisScale4"), + gSavedSettings.getF32("FlycamAxisScale5"), + gSavedSettings.getF32("FlycamAxisScale6") + }; + + F32 dead_zone[] = + { + gSavedSettings.getF32("FlycamAxisDeadZone0"), + gSavedSettings.getF32("FlycamAxisDeadZone1"), + gSavedSettings.getF32("FlycamAxisDeadZone2"), + gSavedSettings.getF32("FlycamAxisDeadZone3"), + gSavedSettings.getF32("FlycamAxisDeadZone4"), + gSavedSettings.getF32("FlycamAxisDeadZone5"), + gSavedSettings.getF32("FlycamAxisDeadZone6") + }; + + F32 time = gFrameIntervalSeconds.value(); + + // avoid making ridiculously big movements if there's a big drop in fps + if (time > .2f) + { + time = .2f; + } + + F32 cur_delta[7]; + F32 feather = gSavedSettings.getF32("FlycamFeathering"); + bool absolute = gSavedSettings.getBOOL("Cursor3D"); + bool is_zero = true; + + for (U32 i = 0; i < 7; i++) + { + cur_delta[i] = -getJoystickAxis(axis[i]); + + + F32 tmp = cur_delta[i]; + if (absolute) + { + cur_delta[i] = cur_delta[i] - sLastDelta[i]; + } + sLastDelta[i] = tmp; + + if (cur_delta[i] > 0) + { + cur_delta[i] = llmax(cur_delta[i]-dead_zone[i], 0.f); + } + else + { + cur_delta[i] = llmin(cur_delta[i]+dead_zone[i], 0.f); + } + + // We may want to scale camera movements up or down in build mode. + // NOTE: this needs to remain after the deadzone calculation, otherwise + // we have issues with flycam "jumping" when the build dialog is opened/closed -Nyx + if (in_build_mode) + { + if (i == X_I || i == Y_I || i == Z_I) + { + static LLCachedControl build_mode_scale(gSavedSettings,"FlycamBuildModeScale", 1.0); + cur_delta[i] *= build_mode_scale; + } + } + + cur_delta[i] *= axis_scale[i]; + + if (!absolute) + { + cur_delta[i] *= time; + } + + sDelta[i] = sDelta[i] + (cur_delta[i]-sDelta[i])*time*feather; + + is_zero = is_zero && (cur_delta[i] == 0.f); + + } + + // Clear AFK state if moved beyond the deadzone + if (!is_zero && gAwayTimer.getElapsedTimeF32() > LLAgent::MIN_AFK_TIME) + { + gAgent.clearAFK(); + } + + sFlycamPosition += LLVector3(sDelta) * sFlycamRotation; + + LLMatrix3 rot_mat(sDelta[3], sDelta[4], sDelta[5]); + sFlycamRotation = LLQuaternion(rot_mat)*sFlycamRotation; + + if (gSavedSettings.getBOOL("AutoLeveling")) + { + LLMatrix3 level(sFlycamRotation); + + LLVector3 x = LLVector3(level.mMatrix[0]); + LLVector3 y = LLVector3(level.mMatrix[1]); + LLVector3 z = LLVector3(level.mMatrix[2]); + + y.mV[2] = 0.f; + y.normVec(); + + level.setRows(x,y,z); + level.orthogonalize(); + + LLQuaternion quat(level); + sFlycamRotation = nlerp(llmin(feather*time,1.f), sFlycamRotation, quat); + } + + if (gSavedSettings.getBOOL("ZoomDirect")) + { + sFlycamZoom = sLastDelta[6]*axis_scale[6]+dead_zone[6]; + } + else + { + sFlycamZoom += sDelta[6]; + } + + LLMatrix3 mat(sFlycamRotation); + + LLViewerCamera::getInstance()->setView(sFlycamZoom); + LLViewerCamera::getInstance()->setOrigin(sFlycamPosition); + LLViewerCamera::getInstance()->mXAxis = LLVector3(mat.mMatrix[0]); + LLViewerCamera::getInstance()->mYAxis = LLVector3(mat.mMatrix[1]); + LLViewerCamera::getInstance()->mZAxis = LLVector3(mat.mMatrix[2]); +} + +// ----------------------------------------------------------------------------- +bool LLViewerJoystick::toggleFlycam() +{ + if (!gSavedSettings.getBOOL("JoystickEnabled") || !gSavedSettings.getBOOL("JoystickFlycamEnabled")) + { + mOverrideCamera = false; + return false; + } + + if (!mOverrideCamera) + { + gAgentCamera.changeCameraToDefault(); + } + + if (gAwayTimer.getElapsedTimeF32() > LLAgent::MIN_AFK_TIME) + { + gAgent.clearAFK(); + } + + mOverrideCamera = !mOverrideCamera; + if (mOverrideCamera) + { + moveFlycam(true); + + } + else + { + // Exiting from the flycam mode: since we are going to keep the flycam POV for + // the main camera until the avatar moves, we need to track this situation. + setCameraNeedsUpdate(false); + setNeedsReset(true); + } + return true; +} + +void LLViewerJoystick::scanJoystick() +{ + if (mDriverState != JDS_INITIALIZED || !gSavedSettings.getBOOL("JoystickEnabled")) + { + return; + } + +#if LL_WINDOWS + // On windows, the flycam is updated syncronously with a timer, so there is + // no need to update the status of the joystick here. + if (!mOverrideCamera) +#endif + updateStatus(); + + // App focus check Needs to happen AFTER updateStatus in case the joystick + // is not centred when the app loses focus. + if (!gFocusMgr.getAppHasFocus()) + { + return; + } + + static long toggle_flycam = 0; + + if (mBtn[0] == 1) + { + if (mBtn[0] != toggle_flycam) + { + toggle_flycam = toggleFlycam() ? 1 : 0; + } + } + else + { + toggle_flycam = 0; + } + + if (!mOverrideCamera && !(LLToolMgr::getInstance()->inBuildMode() && gSavedSettings.getBOOL("JoystickBuildEnabled"))) + { + moveAvatar(); + } +} + +// ----------------------------------------------------------------------------- +bool LLViewerJoystick::isDeviceUUIDSet() +{ +#if LL_WINDOWS && !LL_MESA_HEADLESS + // for ease of comparison and to dial less with platform specific variables, we store id as LLSD binary + return mLastDeviceUUID.isBinary(); +#elif LL_DARWIN + return mLastDeviceUUID.isMap(); +#else + return false; +#endif +} + +LLSD LLViewerJoystick::getDeviceUUID() +{ + return mLastDeviceUUID; +} + +std::string LLViewerJoystick::getDeviceUUIDString() +{ +#if LL_WINDOWS && !LL_MESA_HEADLESS + // Might be simpler to just convert _GUID into string everywhere, store and compare as string + if (mLastDeviceUUID.isBinary()) + { + S32 size = sizeof(GUID); + LLSD::Binary data = mLastDeviceUUID.asBinary(); + GUID guid; + memcpy(&guid, &data[0], size); + return string_from_guid(guid); + } + else + { + return std::string(); + } +#elif LL_DARWIN + if (mLastDeviceUUID.isMap()) + { + std::string manufacturer = mLastDeviceUUID["manufacturer"].asString(); + std::string product = mLastDeviceUUID["product"].asString(); + return manufacturer + ":" + product; + } + else + { + return std::string(); + } +#else + return std::string(); +#endif +} + +void LLViewerJoystick::saveDeviceIdToSettings() +{ +#if LL_WINDOWS && !LL_MESA_HEADLESS + // can't save as binary directly, + // someone editing the xml will corrupt it + // so convert to string first + std::string device_string = getDeviceUUIDString(); + gSavedSettings.setLLSD("JoystickDeviceUUID", LLSD(device_string)); +#else + LLSD device_id = getDeviceUUID(); + gSavedSettings.setLLSD("JoystickDeviceUUID", device_id); +#endif +} + +void LLViewerJoystick::loadDeviceIdFromSettings() +{ + LLSD dev_id = gSavedSettings.getLLSD("JoystickDeviceUUID"); +#if LL_WINDOWS && !LL_MESA_HEADLESS + // We can't save binary data to gSavedSettings, somebody editing the file will corrupt it, + // so _GUID data gets converted to string (we probably can convert it to LLUUID with memcpy) + // and here we need to convert it back to binary from string + std::string device_string; + if (dev_id.isString()) + { + device_string = dev_id.asString(); + } + if (device_string.empty()) + { + mLastDeviceUUID = LLSD(); + } + else + { + LL_DEBUGS("Joystick") << "Looking for device by id: " << device_string << LL_ENDL; + GUID guid; + guid_from_string(guid, device_string); + S32 size = sizeof(GUID); + LLSD::Binary data; //just an std::vector + data.resize(size); + memcpy(&data[0], &guid /*POD _GUID*/, size); + // We store this data in LLSD since it can handle both GUID2 and long + mLastDeviceUUID = LLSD(data); + } +#elif LL_DARWIN + if (!dev_id.isMap()) + { + mLastDeviceUUID = LLSD(); + } + else + { + std::string manufacturer = mLastDeviceUUID["manufacturer"].asString(); + std::string product = mLastDeviceUUID["product"].asString(); + LL_DEBUGS("Joystick") << "Looking for device by manufacturer: " << manufacturer << " and product: " << product << LL_ENDL; + // We store this data in LLSD since it can handle both GUID2 and long + mLastDeviceUUID = dev_id; + } +#else + mLastDeviceUUID = LLSD(); + //mLastDeviceUUID = gSavedSettings.getLLSD("JoystickDeviceUUID"); +#endif +} + +// ----------------------------------------------------------------------------- +std::string LLViewerJoystick::getDescription() +{ + std::string res; +#if LIB_NDOF + if (mDriverState == JDS_INITIALIZED && mNdofDev) + { + res = ll_safe_string(mNdofDev->product); + } +#endif + return res; +} + +bool LLViewerJoystick::isLikeSpaceNavigator() const +{ +#if LIB_NDOF + return (isJoystickInitialized() + && (strncmp(mNdofDev->product, "SpaceNavigator", 14) == 0 + || strncmp(mNdofDev->product, "SpaceExplorer", 13) == 0 + || strncmp(mNdofDev->product, "SpaceTraveler", 13) == 0 + || strncmp(mNdofDev->product, "SpacePilot", 10) == 0)); +#else + return false; +#endif +} + +// ----------------------------------------------------------------------------- +void LLViewerJoystick::setSNDefaults() +{ +#if LL_DARWIN || LL_LINUX + const float platformScale = 20.f; + const float platformScaleAvXZ = 1.f; + // The SpaceNavigator doesn't act as a 3D cursor on macOS / Linux. + const bool is_3d_cursor = false; +#else + const float platformScale = 1.f; + const float platformScaleAvXZ = 2.f; + const bool is_3d_cursor = true; +#endif + + //gViewerWindow->alertXml("CacheWillClear"); + LL_INFOS("Joystick") << "restoring SpaceNavigator defaults..." << LL_ENDL; + + gSavedSettings.setS32("JoystickAxis0", 1); // z (at) + gSavedSettings.setS32("JoystickAxis1", 0); // x (slide) + gSavedSettings.setS32("JoystickAxis2", 2); // y (up) + gSavedSettings.setS32("JoystickAxis3", 4); // pitch + gSavedSettings.setS32("JoystickAxis4", 3); // roll + gSavedSettings.setS32("JoystickAxis5", 5); // yaw + gSavedSettings.setS32("JoystickAxis6", -1); + + gSavedSettings.setBOOL("Cursor3D", is_3d_cursor); + gSavedSettings.setBOOL("AutoLeveling", true); + gSavedSettings.setBOOL("ZoomDirect", false); + + gSavedSettings.setF32("AvatarAxisScale0", 1.f * platformScaleAvXZ); + gSavedSettings.setF32("AvatarAxisScale1", 1.f * platformScaleAvXZ); + gSavedSettings.setF32("AvatarAxisScale2", 1.f); + gSavedSettings.setF32("AvatarAxisScale4", .1f * platformScale); + gSavedSettings.setF32("AvatarAxisScale5", .1f * platformScale); + gSavedSettings.setF32("AvatarAxisScale3", 0.f * platformScale); + gSavedSettings.setF32("BuildAxisScale1", .3f * platformScale); + gSavedSettings.setF32("BuildAxisScale2", .3f * platformScale); + gSavedSettings.setF32("BuildAxisScale0", .3f * platformScale); + gSavedSettings.setF32("BuildAxisScale4", .3f * platformScale); + gSavedSettings.setF32("BuildAxisScale5", .3f * platformScale); + gSavedSettings.setF32("BuildAxisScale3", .3f * platformScale); + gSavedSettings.setF32("FlycamAxisScale1", 2.f * platformScale); + gSavedSettings.setF32("FlycamAxisScale2", 2.f * platformScale); + gSavedSettings.setF32("FlycamAxisScale0", 2.1f * platformScale); + gSavedSettings.setF32("FlycamAxisScale4", .1f * platformScale); + gSavedSettings.setF32("FlycamAxisScale5", .15f * platformScale); + gSavedSettings.setF32("FlycamAxisScale3", 0.f * platformScale); + gSavedSettings.setF32("FlycamAxisScale6", 0.f * platformScale); + + gSavedSettings.setF32("AvatarAxisDeadZone0", .1f); + gSavedSettings.setF32("AvatarAxisDeadZone1", .1f); + gSavedSettings.setF32("AvatarAxisDeadZone2", .1f); + gSavedSettings.setF32("AvatarAxisDeadZone3", 1.f); + gSavedSettings.setF32("AvatarAxisDeadZone4", .02f); + gSavedSettings.setF32("AvatarAxisDeadZone5", .01f); + gSavedSettings.setF32("BuildAxisDeadZone0", .01f); + gSavedSettings.setF32("BuildAxisDeadZone1", .01f); + gSavedSettings.setF32("BuildAxisDeadZone2", .01f); + gSavedSettings.setF32("BuildAxisDeadZone3", .01f); + gSavedSettings.setF32("BuildAxisDeadZone4", .01f); + gSavedSettings.setF32("BuildAxisDeadZone5", .01f); + gSavedSettings.setF32("FlycamAxisDeadZone0", .01f); + gSavedSettings.setF32("FlycamAxisDeadZone1", .01f); + gSavedSettings.setF32("FlycamAxisDeadZone2", .01f); + gSavedSettings.setF32("FlycamAxisDeadZone3", .01f); + gSavedSettings.setF32("FlycamAxisDeadZone4", .01f); + gSavedSettings.setF32("FlycamAxisDeadZone5", .01f); + gSavedSettings.setF32("FlycamAxisDeadZone6", 1.f); + + gSavedSettings.setF32("AvatarFeathering", 6.f); + gSavedSettings.setF32("BuildFeathering", 12.f); + gSavedSettings.setF32("FlycamFeathering", 5.f); +} diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp index 4099ebd4cb..efe57661a9 100644 --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -1,4017 +1,4017 @@ -/** - * @file llviewermedia.cpp - * @brief Client interface to the media engine - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llviewermedia.h" - -#include "llagent.h" -#include "llagentcamera.h" -#include "llappviewer.h" -#include "llaudioengine.h" // for gAudiop -#include "llcallbacklist.h" -#include "lldir.h" -#include "lldiriterator.h" -#include "llevent.h" // LLSimpleListener -#include "llfilepicker.h" -#include "llfloaterwebcontent.h" // for handling window close requests and geometry change requests in media browser windows. -#include "llfocusmgr.h" -#include "llimagegl.h" -#include "llkeyboard.h" -#include "lllogininstance.h" -#include "llmarketplacefunctions.h" -#include "llmediaentry.h" -#include "llmimetypes.h" -#include "llmutelist.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "llavataractions.h" -#include "llparcel.h" -#include "llpluginclassmedia.h" -#include "llurldispatcher.h" -#include "lluuid.h" -#include "llversioninfo.h" -#include "llviewermediafocus.h" -#include "llviewercontrol.h" -#include "llviewermenufile.h" // LLFilePickerThread -#include "llviewernetwork.h" -#include "llviewerparcelaskplay.h" -#include "llviewerparcelmedia.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" -#include "llviewertexture.h" -#include "llviewertexturelist.h" -#include "llviewerwindow.h" -#include "llvoavatar.h" -#include "llvoavatarself.h" -#include "llvovolume.h" -#include "llfloaterreg.h" -#include "llwebprofile.h" -#include "llwindow.h" -#include "llvieweraudio.h" -#include "llcorehttputil.h" - -#include "llfloaterwebcontent.h" // for handling window close requests and geometry change requests in media browser windows. - -#include // for SkinFolder listener -#include - -extern bool gCubeSnapshot; - -// *TODO: Consider enabling mipmaps (they have been disabled for a long time). Likely has a significant performance impact for tiled/high texture repeat media. Mip generation in a shader may also be an option if necessary. -constexpr bool USE_MIPMAPS = false; - -void init_threaded_picker_load_dialog(LLPluginClassMedia* plugin, LLFilePicker::ELoadFilter filter, bool get_multiple) -{ - (new LLMediaFilePicker(plugin, filter, get_multiple))->getFile(); // will delete itself -} - -/////////////////////////////////////////////////////////////////////////////// - -// Move this to its own file. - -LLViewerMediaEventEmitter::~LLViewerMediaEventEmitter() -{ - observerListType::iterator iter = mObservers.begin(); - - while( iter != mObservers.end() ) - { - LLViewerMediaObserver *self = *iter; - iter++; - remObserver(self); - } -} - -/////////////////////////////////////////////////////////////////////////////// -// -bool LLViewerMediaEventEmitter::addObserver( LLViewerMediaObserver* observer ) -{ - if ( ! observer ) - return false; - - if ( std::find( mObservers.begin(), mObservers.end(), observer ) != mObservers.end() ) - return false; - - mObservers.push_back( observer ); - observer->mEmitters.push_back( this ); - - return true; -} - -/////////////////////////////////////////////////////////////////////////////// -// -bool LLViewerMediaEventEmitter::remObserver( LLViewerMediaObserver* observer ) -{ - if ( ! observer ) - return false; - - mObservers.remove( observer ); - observer->mEmitters.remove(this); - - return true; -} - -/////////////////////////////////////////////////////////////////////////////// -// -void LLViewerMediaEventEmitter::emitEvent( LLPluginClassMedia* media, LLViewerMediaObserver::EMediaEvent event ) -{ - // Broadcast the event to any observers. - observerListType::iterator iter = mObservers.begin(); - while( iter != mObservers.end() ) - { - LLViewerMediaObserver *self = *iter; - ++iter; - self->handleMediaEvent( media, event ); - } -} - -// Move this to its own file. -LLViewerMediaObserver::~LLViewerMediaObserver() -{ - std::list::iterator iter = mEmitters.begin(); - - while( iter != mEmitters.end() ) - { - LLViewerMediaEventEmitter *self = *iter; - iter++; - self->remObserver( this ); - } -} - - -static LLViewerMedia::impl_list sViewerMediaImplList; -static LLViewerMedia::impl_id_map sViewerMediaTextureIDMap; -static LLTimer sMediaCreateTimer; -static const F32 LLVIEWERMEDIA_CREATE_DELAY = 1.0f; -static F32 sGlobalVolume = 1.0f; -static bool sForceUpdate = false; -static LLUUID sOnlyAudibleTextureID = LLUUID::null; -static F64 sLowestLoadableImplInterest = 0.0f; - -////////////////////////////////////////////////////////////////////////////////////////// -static void add_media_impl(LLViewerMediaImpl* media) -{ - sViewerMediaImplList.push_back(media); -} - -////////////////////////////////////////////////////////////////////////////////////////// -static void remove_media_impl(LLViewerMediaImpl* media) -{ - LLViewerMedia::impl_list::iterator iter = sViewerMediaImplList.begin(); - LLViewerMedia::impl_list::iterator end = sViewerMediaImplList.end(); - - for(; iter != end; iter++) - { - if(media == *iter) - { - sViewerMediaImplList.erase(iter); - return; - } - } -} - -class LLViewerMediaMuteListObserver : public LLMuteListObserver -{ - /* virtual */ void onChange() { LLViewerMedia::getInstance()->muteListChanged();} -}; - -static LLViewerMediaMuteListObserver sViewerMediaMuteListObserver; -static bool sViewerMediaMuteListObserverInitialized = false; - - -////////////////////////////////////////////////////////////////////////////////////////// -// LLViewerMedia -////////////////////////////////////////////////////////////////////////////////////////// - -/*static*/ const char* LLViewerMedia::AUTO_PLAY_MEDIA_SETTING = "ParcelMediaAutoPlayEnable"; -/*static*/ const char* LLViewerMedia::SHOW_MEDIA_ON_OTHERS_SETTING = "MediaShowOnOthers"; -/*static*/ const char* LLViewerMedia::SHOW_MEDIA_WITHIN_PARCEL_SETTING = "MediaShowWithinParcel"; -/*static*/ const char* LLViewerMedia::SHOW_MEDIA_OUTSIDE_PARCEL_SETTING = "MediaShowOutsideParcel"; - -LLViewerMedia::LLViewerMedia(): -mAnyMediaShowing(false), -mAnyMediaPlaying(false), -mSpareBrowserMediaSource(NULL) -{ -} - -LLViewerMedia::~LLViewerMedia() -{ - gIdleCallbacks.deleteFunction(LLViewerMedia::onIdle, NULL); - mTeleportFinishConnection.disconnect(); - if (mSpareBrowserMediaSource != NULL) - { - delete mSpareBrowserMediaSource; - mSpareBrowserMediaSource = NULL; - } -} - -// static -void LLViewerMedia::initSingleton() -{ - gIdleCallbacks.addFunction(LLViewerMedia::onIdle, NULL); - mTeleportFinishConnection = LLViewerParcelMgr::getInstance()-> - setTeleportFinishedCallback(boost::bind(&LLViewerMedia::onTeleportFinished, this)); -} - -////////////////////////////////////////////////////////////////////////////////////////// -viewer_media_t LLViewerMedia::newMediaImpl( - const LLUUID& texture_id, - S32 media_width, - S32 media_height, - U8 media_auto_scale, - U8 media_loop) -{ - LLViewerMediaImpl* media_impl = getMediaImplFromTextureID(texture_id); - if(media_impl == NULL || texture_id.isNull()) - { - // Create the media impl - media_impl = new LLViewerMediaImpl(texture_id, media_width, media_height, media_auto_scale, media_loop); - } - else - { - media_impl->unload(); - media_impl->setTextureID(texture_id); - media_impl->mMediaWidth = media_width; - media_impl->mMediaHeight = media_height; - media_impl->mMediaAutoScale = media_auto_scale; - media_impl->mMediaLoop = media_loop; - } - - return media_impl; -} - -viewer_media_t LLViewerMedia::updateMediaImpl(LLMediaEntry* media_entry, const std::string& previous_url, bool update_from_self) -{ - llassert(!gCubeSnapshot); - // Try to find media with the same media ID - viewer_media_t media_impl = getMediaImplFromTextureID(media_entry->getMediaID()); - - LL_DEBUGS() << "called, current URL is \"" << media_entry->getCurrentURL() - << "\", previous URL is \"" << previous_url - << "\", update_from_self is " << (update_from_self?"true":"false") - << LL_ENDL; - - bool was_loaded = false; - bool needs_navigate = false; - - if(media_impl) - { - was_loaded = media_impl->hasMedia(); - - media_impl->setHomeURL(media_entry->getHomeURL()); - - media_impl->mMediaAutoScale = media_entry->getAutoScale(); - media_impl->mMediaLoop = media_entry->getAutoLoop(); - media_impl->mMediaWidth = media_entry->getWidthPixels(); - media_impl->mMediaHeight = media_entry->getHeightPixels(); - media_impl->mMediaAutoPlay = media_entry->getAutoPlay(); - media_impl->mMediaEntryURL = media_entry->getCurrentURL(); - if (media_impl->mMediaSource) - { - media_impl->mMediaSource->setAutoScale(media_impl->mMediaAutoScale); - media_impl->mMediaSource->setLoop(media_impl->mMediaLoop); - media_impl->mMediaSource->setSize(media_entry->getWidthPixels(), media_entry->getHeightPixels()); - } - - bool url_changed = (media_impl->mMediaEntryURL != previous_url); - if(media_impl->mMediaEntryURL.empty()) - { - if(url_changed) - { - // The current media URL is now empty. Unload the media source. - media_impl->unload(); - - LL_DEBUGS() << "Unloading media instance (new current URL is empty)." << LL_ENDL; - } - } - else - { - // The current media URL is not empty. - // If (the media was already loaded OR the media was set to autoplay) AND this update didn't come from this agent, - // do a navigate. - bool auto_play = media_impl->isAutoPlayable(); - if((was_loaded || auto_play) && !update_from_self) - { - needs_navigate = url_changed; - } - - LL_DEBUGS() << "was_loaded is " << (was_loaded?"true":"false") - << ", auto_play is " << (auto_play?"true":"false") - << ", needs_navigate is " << (needs_navigate?"true":"false") << LL_ENDL; - } - } - else - { - media_impl = newMediaImpl( - media_entry->getMediaID(), - media_entry->getWidthPixels(), - media_entry->getHeightPixels(), - media_entry->getAutoScale(), - media_entry->getAutoLoop()); - - media_impl->setHomeURL(media_entry->getHomeURL()); - media_impl->mMediaAutoPlay = media_entry->getAutoPlay(); - media_impl->mMediaEntryURL = media_entry->getCurrentURL(); - if(media_impl->isAutoPlayable()) - { - needs_navigate = true; - } - } - - if(media_impl) - { - if(needs_navigate) - { - media_impl->navigateTo(media_impl->mMediaEntryURL, "", true, true); - LL_DEBUGS() << "navigating to URL " << media_impl->mMediaEntryURL << LL_ENDL; - } - else if(!media_impl->mMediaURL.empty() && (media_impl->mMediaURL != media_impl->mMediaEntryURL)) - { - // If we already have a non-empty media URL set and we aren't doing a navigate, update the media URL to match the media entry. - media_impl->mMediaURL = media_impl->mMediaEntryURL; - - // If this causes a navigate at some point (such as after a reload), it should be considered server-driven so it isn't broadcast. - media_impl->mNavigateServerRequest = true; - - LL_DEBUGS() << "updating URL in the media impl to " << media_impl->mMediaEntryURL << LL_ENDL; - } - } - - return media_impl; -} - -////////////////////////////////////////////////////////////////////////////////////////// -LLViewerMediaImpl* LLViewerMedia::getMediaImplFromTextureID(const LLUUID& texture_id) -{ - LLViewerMediaImpl* result = NULL; - - // Look up the texture ID in the texture id->impl map. - impl_id_map::iterator iter = sViewerMediaTextureIDMap.find(texture_id); - if(iter != sViewerMediaTextureIDMap.end()) - { - result = iter->second; - } - - return result; -} - -////////////////////////////////////////////////////////////////////////////////////////// -std::string LLViewerMedia::getCurrentUserAgent() -{ - // Don't use user-visible string to avoid - // punctuation and strange characters. - std::string skin_name = gSavedSettings.getString("SkinCurrent"); - - // Just in case we need to check browser differences in A/B test - // builds. - std::string channel = LLVersionInfo::instance().getChannel(); - - // append our magic version number string to the browser user agent id - // See the HTTP 1.0 and 1.1 specifications for allowed formats: - // http://www.ietf.org/rfc/rfc1945.txt section 10.15 - // http://www.ietf.org/rfc/rfc2068.txt section 3.8 - // This was also helpful: - // http://www.mozilla.org/build/revised-user-agent-strings.html - std::ostringstream codec; - codec << "SecondLife/"; - codec << LLVersionInfo::instance().getVersion(); - codec << " (" << channel << "; " << skin_name << " skin)"; - LL_INFOS() << codec.str() << LL_ENDL; - - return codec.str(); -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMedia::updateBrowserUserAgent() -{ - std::string user_agent = getCurrentUserAgent(); - - impl_list::iterator iter = sViewerMediaImplList.begin(); - impl_list::iterator end = sViewerMediaImplList.end(); - - for(; iter != end; iter++) - { - LLViewerMediaImpl* pimpl = *iter; - if(pimpl->mMediaSource && pimpl->mMediaSource->pluginSupportsMediaBrowser()) - { - pimpl->mMediaSource->setBrowserUserAgent(user_agent); - } - } - -} - -////////////////////////////////////////////////////////////////////////////////////////// -bool LLViewerMedia::handleSkinCurrentChanged(const LLSD& /*newvalue*/) -{ - // gSavedSettings is already updated when this function is called. - updateBrowserUserAgent(); - return true; -} - -////////////////////////////////////////////////////////////////////////////////////////// -bool LLViewerMedia::textureHasMedia(const LLUUID& texture_id) -{ - impl_list::iterator iter = sViewerMediaImplList.begin(); - impl_list::iterator end = sViewerMediaImplList.end(); - - for(; iter != end; iter++) - { - LLViewerMediaImpl* pimpl = *iter; - if(pimpl->getMediaTextureID() == texture_id) - { - return true; - } - } - return false; -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMedia::setVolume(F32 volume) -{ - if(volume != sGlobalVolume || sForceUpdate) - { - sGlobalVolume = volume; - impl_list::iterator iter = sViewerMediaImplList.begin(); - impl_list::iterator end = sViewerMediaImplList.end(); - - for(; iter != end; iter++) - { - LLViewerMediaImpl* pimpl = *iter; - pimpl->updateVolume(); - } - - sForceUpdate = false; - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -F32 LLViewerMedia::getVolume() -{ - return sGlobalVolume; -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMedia::muteListChanged() -{ - // When the mute list changes, we need to check mute status on all impls. - impl_list::iterator iter = sViewerMediaImplList.begin(); - impl_list::iterator end = sViewerMediaImplList.end(); - - for(; iter != end; iter++) - { - LLViewerMediaImpl* pimpl = *iter; - pimpl->mNeedsMuteCheck = true; - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -bool LLViewerMedia::isInterestingEnough(const LLVOVolume *object, const F64 &object_interest) -{ - bool result = false; - - if (NULL == object) - { - result = false; - } - // Focused? Then it is interesting! - else if (LLViewerMediaFocus::getInstance()->getFocusedObjectID() == object->getID()) - { - result = true; - } - // Selected? Then it is interesting! - // XXX Sadly, 'contains()' doesn't take a const :( - else if (LLSelectMgr::getInstance()->getSelection()->contains(const_cast(object))) - { - result = true; - } - else - { - LL_DEBUGS() << "object interest = " << object_interest << ", lowest loadable = " << sLowestLoadableImplInterest << LL_ENDL; - if(object_interest >= sLowestLoadableImplInterest) - result = true; - } - - return result; -} - -LLViewerMedia::impl_list &LLViewerMedia::getPriorityList() -{ - return sViewerMediaImplList; -} - -// static -// This is the predicate function used to sort sViewerMediaImplList by priority. -bool LLViewerMedia::priorityComparitor(const LLViewerMediaImpl* i1, const LLViewerMediaImpl* i2) -{ - if(i1->isForcedUnloaded() && !i2->isForcedUnloaded()) - { - // Muted or failed items always go to the end of the list, period. - return false; - } - else if(i2->isForcedUnloaded() && !i1->isForcedUnloaded()) - { - // Muted or failed items always go to the end of the list, period. - return true; - } - else if(i1->hasFocus()) - { - // The item with user focus always comes to the front of the list, period. - return true; - } - else if(i2->hasFocus()) - { - // The item with user focus always comes to the front of the list, period. - return false; - } - else if(i1->isParcelMedia()) - { - // The parcel media impl sorts above all other inworld media, unless one has focus. - return true; - } - else if(i2->isParcelMedia()) - { - // The parcel media impl sorts above all other inworld media, unless one has focus. - return false; - } - else if(i1->getUsedInUI() && !i2->getUsedInUI()) - { - // i1 is a UI element, i2 is not. This makes i1 "less than" i2, so it sorts earlier in our list. - return true; - } - else if(i2->getUsedInUI() && !i1->getUsedInUI()) - { - // i2 is a UI element, i1 is not. This makes i2 "less than" i1, so it sorts earlier in our list. - return false; - } - else if(i1->isPlayable() && !i2->isPlayable()) - { - // Playable items sort above ones that wouldn't play even if they got high enough priority - return true; - } - else if(!i1->isPlayable() && i2->isPlayable()) - { - // Playable items sort above ones that wouldn't play even if they got high enough priority - return false; - } - else if(i1->getInterest() == i2->getInterest()) - { - // Generally this will mean both objects have zero interest. In this case, sort on distance. - return (i1->getProximityDistance() < i2->getProximityDistance()); - } - else - { - // The object with the larger interest value should be earlier in the list, so we reverse the sense of the comparison here. - return (i1->getInterest() > i2->getInterest()); - } -} - -static bool proximity_comparitor(const LLViewerMediaImpl* i1, const LLViewerMediaImpl* i2) -{ - if(i1->getProximityDistance() < i2->getProximityDistance()) - { - return true; - } - else if(i1->getProximityDistance() > i2->getProximityDistance()) - { - return false; - } - else - { - // Both objects have the same distance. This most likely means they're two faces of the same object. - // They may also be faces on different objects with exactly the same distance (like HUD objects). - // We don't actually care what the sort order is for this case, as long as it's stable and doesn't change when you enable/disable media. - // Comparing the impl pointers gives a completely arbitrary ordering, but it will be stable. - return (i1 < i2); - } -} - -static LLTrace::BlockTimerStatHandle FTM_MEDIA_UPDATE("Update Media"); -static LLTrace::BlockTimerStatHandle FTM_MEDIA_SPARE_IDLE("Spare Idle"); -static LLTrace::BlockTimerStatHandle FTM_MEDIA_UPDATE_INTEREST("Update/Interest"); -static LLTrace::BlockTimerStatHandle FTM_MEDIA_UPDATE_VOLUME("Update/Volume"); -static LLTrace::BlockTimerStatHandle FTM_MEDIA_SORT("Media Sort"); -static LLTrace::BlockTimerStatHandle FTM_MEDIA_SORT2("Media Sort 2"); -static LLTrace::BlockTimerStatHandle FTM_MEDIA_MISC("Misc"); - - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMedia::onIdle(void *dummy_arg) -{ - LLViewerMedia::getInstance()->updateMedia(dummy_arg); -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMedia::updateMedia(void *dummy_arg) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA; //LL_RECORD_BLOCK_TIME(FTM_MEDIA_UPDATE); - - llassert(!gCubeSnapshot); - - // Enable/disable the plugin read thread - LLPluginProcessParent::setUseReadThread(gSavedSettings.getBOOL("PluginUseReadThread")); - - // SL-16418 We can't call LLViewerMediaImpl->update() if we are in the state of shutting down. - if(LLApp::isExiting()) - { - setAllMediaEnabled(false); - return; - } - - // HACK: we always try to keep a spare running webkit plugin around to improve launch times. - // 2017-04-19 Removed CP - this doesn't appear to buy us much and consumes a lot of resources so - // removing it for now. - //createSpareBrowserMediaSource(); - - mAnyMediaShowing = false; - mAnyMediaPlaying = false; - - impl_list::iterator iter = sViewerMediaImplList.begin(); - impl_list::iterator end = sViewerMediaImplList.end(); - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_MEDIA("media update interest"); //LL_RECORD_BLOCK_TIME(FTM_MEDIA_UPDATE_INTEREST); - for(; iter != end;) - { - LLViewerMediaImpl* pimpl = *iter++; - pimpl->update(); - pimpl->calculateInterest(); - } - } - - // Let the spare media source actually launch - if(mSpareBrowserMediaSource) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_MEDIA("media spare idle"); //LL_RECORD_BLOCK_TIME(FTM_MEDIA_SPARE_IDLE); - mSpareBrowserMediaSource->idle(); - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_MEDIA("media sort"); //LL_RECORD_BLOCK_TIME(FTM_MEDIA_SORT); - // Sort the static instance list using our interest criteria - sViewerMediaImplList.sort(priorityComparitor); - } - - // Go through the list again and adjust according to priority. - iter = sViewerMediaImplList.begin(); - end = sViewerMediaImplList.end(); - - F64 total_cpu = 0.0f; - int impl_count_total = 0; - int impl_count_interest_low = 0; - int impl_count_interest_normal = 0; - - std::vector proximity_order; - - static LLCachedControl inworld_media_enabled(gSavedSettings, "AudioStreamingMedia", true); - static LLCachedControl inworld_audio_enabled(gSavedSettings, "AudioStreamingMusic", true); - U32 max_instances = gSavedSettings.getU32("PluginInstancesTotal"); - U32 max_normal = gSavedSettings.getU32("PluginInstancesNormal"); - U32 max_low = gSavedSettings.getU32("PluginInstancesLow"); - F32 max_cpu = gSavedSettings.getF32("PluginInstancesCPULimit"); - // Setting max_cpu to 0.0 disables CPU usage checking. - bool check_cpu_usage = (max_cpu != 0.0f); - - LLViewerMediaImpl* lowest_interest_loadable = NULL; - - // Notes on tweakable params: - // max_instances must be set high enough to allow the various instances used in the UI (for the help browser, search, etc.) to be loaded. - // If max_normal + max_low is less than max_instances, things will tend to get unloaded instead of being set to slideshow. - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_MEDIA("media misc"); //LL_RECORD_BLOCK_TIME(FTM_MEDIA_MISC); - for(; iter != end; iter++) - { - LLViewerMediaImpl* pimpl = *iter; - - LLPluginClassMedia::EPriority new_priority = LLPluginClassMedia::PRIORITY_NORMAL; - - if(pimpl->isForcedUnloaded() || (impl_count_total >= (int)max_instances)) - { - // Never load muted or failed impls. - // Hard limit on the number of instances that will be loaded at one time - new_priority = LLPluginClassMedia::PRIORITY_UNLOADED; - } - else if(!pimpl->getVisible()) - { - new_priority = LLPluginClassMedia::PRIORITY_HIDDEN; - } - else if(pimpl->hasFocus()) - { - new_priority = LLPluginClassMedia::PRIORITY_HIGH; - impl_count_interest_normal++; // count this against the count of "normal" instances for priority purposes - } - else if(pimpl->getUsedInUI()) - { - new_priority = LLPluginClassMedia::PRIORITY_NORMAL; - impl_count_interest_normal++; - } - else if(pimpl->isParcelMedia()) - { - new_priority = LLPluginClassMedia::PRIORITY_NORMAL; - impl_count_interest_normal++; - } - else - { - // Look at interest and CPU usage for instances that aren't in any of the above states. - - // Heuristic -- if the media texture's approximate screen area is less than 1/4 of the native area of the texture, - // turn it down to low instead of normal. This may downsample for plugins that support it. - bool media_is_small = false; - F64 approximate_interest = pimpl->getApproximateTextureInterest(); - if(approximate_interest == 0.0f) - { - // this media has no current size, which probably means it's not loaded. - media_is_small = true; - } - else if(pimpl->getInterest() < (approximate_interest / 4)) - { - media_is_small = true; - } - - if(pimpl->getInterest() == 0.0f) - { - // This media is completely invisible, due to being outside the view frustrum or out of range. - new_priority = LLPluginClassMedia::PRIORITY_HIDDEN; - } - else if(check_cpu_usage && (total_cpu > max_cpu)) - { - // Higher priority plugins have already used up the CPU budget. Set remaining ones to slideshow priority. - new_priority = LLPluginClassMedia::PRIORITY_SLIDESHOW; - } - else if((impl_count_interest_normal < (int)max_normal) && !media_is_small) - { - // Up to max_normal inworld get normal priority - new_priority = LLPluginClassMedia::PRIORITY_NORMAL; - impl_count_interest_normal++; - } - else if (impl_count_interest_low + impl_count_interest_normal < (int)max_low + (int)max_normal) - { - // The next max_low inworld get turned down - new_priority = LLPluginClassMedia::PRIORITY_LOW; - impl_count_interest_low++; - - // Set the low priority size for downsampling to approximately the size the texture is displayed at. - { - F32 approximate_interest_dimension = (F32) sqrt(pimpl->getInterest()); - - pimpl->setLowPrioritySizeLimit(ll_round(approximate_interest_dimension)); - } - } - else - { - // Any additional impls (up to max_instances) get very infrequent time - new_priority = LLPluginClassMedia::PRIORITY_SLIDESHOW; - } - } - - if(!pimpl->getUsedInUI() && (new_priority != LLPluginClassMedia::PRIORITY_UNLOADED)) - { - // This is a loadable inworld impl -- the last one in the list in this class defines the lowest loadable interest. - lowest_interest_loadable = pimpl; - - impl_count_total++; - } - - // Overrides if the window is minimized or we lost focus (taking care - // not to accidentally "raise" the priority either) - if (!gViewerWindow->getActive() /* viewer window minimized? */ - && new_priority > LLPluginClassMedia::PRIORITY_HIDDEN) - { - new_priority = LLPluginClassMedia::PRIORITY_HIDDEN; - } - else if (!gFocusMgr.getAppHasFocus() /* viewer window lost focus? */ - && new_priority > LLPluginClassMedia::PRIORITY_LOW) - { - new_priority = LLPluginClassMedia::PRIORITY_LOW; - } - - if(!inworld_media_enabled) - { - // If inworld media is locked out, force all inworld media to stay unloaded. - if(!pimpl->getUsedInUI()) - { - new_priority = LLPluginClassMedia::PRIORITY_UNLOADED; - } - } - // update the audio stream here as well - static bool restore_parcel_audio = false; - if( !inworld_audio_enabled) - { - if(LLViewerMedia::isParcelAudioPlaying() && gAudiop && LLViewerMedia::hasParcelAudio()) - { - LLViewerAudio::getInstance()->stopInternetStreamWithAutoFade(); - restore_parcel_audio = true; - } - } - else - { - if(gAudiop && LLViewerMedia::hasParcelAudio() && restore_parcel_audio && gSavedSettings.getBOOL("MediaTentativeAutoPlay")) - { - LLViewerAudio::getInstance()->startInternetStreamWithAutoFade(LLViewerMedia::getParcelAudioURL()); - restore_parcel_audio = false; - } - } - - pimpl->setPriority(new_priority); - - if(pimpl->getUsedInUI()) - { - // Any impls used in the UI should not be in the proximity list. - pimpl->mProximity = -1; - } - else - { - proximity_order.push_back(pimpl); - } - - total_cpu += pimpl->getCPUUsage(); - - if (!pimpl->getUsedInUI() && pimpl->hasMedia()) - { - mAnyMediaShowing = true; - } - - if (!pimpl->getUsedInUI() && pimpl->hasMedia() && (pimpl->isMediaPlaying() || !pimpl->isMediaTimeBased())) - { - // consider visible non-timebased media as playing - mAnyMediaPlaying = true; - } - - } - } - - // Re-calculate this every time. - sLowestLoadableImplInterest = 0.0f; - - // Only do this calculation if we've hit the impl count limit -- up until that point we always need to load media data. - if(lowest_interest_loadable && (impl_count_total >= (int)max_instances)) - { - // Get the interest value of this impl's object for use by isInterestingEnough - LLVOVolume *object = lowest_interest_loadable->getSomeObject(); - if(object) - { - // NOTE: Don't use getMediaInterest() here. We want the pixel area, not the total media interest, - // so that we match up with the calculation done in LLMediaDataClient. - sLowestLoadableImplInterest = object->getPixelArea(); - } - } - - if(gSavedSettings.getBOOL("MediaPerformanceManagerDebug")) - { - // Give impls the same ordering as the priority list - // they're already in the right order for this. - } - else - { - LL_PROFILE_ZONE_NAMED_CATEGORY_MEDIA("media sort2"); // LL_RECORD_BLOCK_TIME(FTM_MEDIA_SORT2); - // Use a distance-based sort for proximity values. - std::stable_sort(proximity_order.begin(), proximity_order.end(), proximity_comparitor); - } - - // Transfer the proximity order to the proximity fields in the objects. - for(int i = 0; i < (int)proximity_order.size(); i++) - { - proximity_order[i]->mProximity = i; - } - - LL_DEBUGS("PluginPriority") << "Total reported CPU usage is " << total_cpu << LL_ENDL; - -} - -////////////////////////////////////////////////////////////////////////////////////////// -bool LLViewerMedia::isAnyMediaShowing() -{ - return mAnyMediaShowing; -} - -////////////////////////////////////////////////////////////////////////////////////////// -bool LLViewerMedia::isAnyMediaPlaying() -{ - return mAnyMediaPlaying; -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMedia::setAllMediaEnabled(bool val) -{ - // Set "tentative" autoplay first. We need to do this here or else - // re-enabling won't start up the media below. - gSavedSettings.setBOOL("MediaTentativeAutoPlay", val); - - // Then - impl_list::iterator iter = sViewerMediaImplList.begin(); - impl_list::iterator end = sViewerMediaImplList.end(); - - for(; iter != end; iter++) - { - LLViewerMediaImpl* pimpl = *iter; - if (!pimpl->getUsedInUI()) - { - pimpl->setDisabled(!val); - } - } - - // Also do Parcel Media and Parcel Audio - if (val) - { - if (!LLViewerMedia::isParcelMediaPlaying() && LLViewerMedia::hasParcelMedia()) - { - LLViewerParcelMedia::getInstance()->play(LLViewerParcelMgr::getInstance()->getAgentParcel()); - } - - static LLCachedControl audio_streaming_music(gSavedSettings, "AudioStreamingMusic", true); - if (audio_streaming_music && - !LLViewerMedia::isParcelAudioPlaying() && - gAudiop && - LLViewerMedia::hasParcelAudio()) - { - if (LLAudioEngine::AUDIO_PAUSED == gAudiop->isInternetStreamPlaying()) - { - // 'false' means unpause - gAudiop->pauseInternetStream(false); - } - else - { - LLViewerAudio::getInstance()->startInternetStreamWithAutoFade(LLViewerMedia::getParcelAudioURL()); - } - } - } - else { - // This actually unloads the impl, as opposed to "stop"ping the media - LLViewerParcelMedia::getInstance()->stop(); - if (gAudiop) - { - LLViewerAudio::getInstance()->stopInternetStreamWithAutoFade(); - } - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMedia::setAllMediaPaused(bool val) -{ - // Set "tentative" autoplay first. We need to do this here or else - // re-enabling won't start up the media below. - gSavedSettings.setBOOL("MediaTentativeAutoPlay", !val); - - // Then - impl_list::iterator iter = sViewerMediaImplList.begin(); - impl_list::iterator end = sViewerMediaImplList.end(); - - for (; iter != end; iter++) - { - LLViewerMediaImpl* pimpl = *iter; - if (!pimpl->getUsedInUI()) - { - // upause/pause time based media, enable/disable any other - if (!val) - { - pimpl->setDisabled(val); - if (pimpl->isMediaTimeBased() && pimpl->isMediaPaused()) - { - pimpl->play(); - } - } - else if (pimpl->isMediaTimeBased() && pimpl->mMediaSource && (pimpl->isMediaPlaying() || pimpl->isMediaPaused())) - { - pimpl->pause(); - } - else - { - pimpl->setDisabled(val); - } - } - } - - LLParcel *agent_parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - - // Also do Parcel Media and Parcel Audio - if (!val) - { - if (!LLViewerMedia::isParcelMediaPlaying() && LLViewerMedia::hasParcelMedia()) - { - LLViewerParcelMedia::getInstance()->play(agent_parcel); - } - - static LLCachedControl audio_streaming_music(gSavedSettings, "AudioStreamingMusic", true); - if (audio_streaming_music && - !LLViewerMedia::isParcelAudioPlaying() && - gAudiop && - LLViewerMedia::hasParcelAudio()) - { - if (LLAudioEngine::AUDIO_PAUSED == gAudiop->isInternetStreamPlaying()) - { - // 'false' means unpause - gAudiop->pauseInternetStream(false); - } - else - { - LLViewerAudio::getInstance()->startInternetStreamWithAutoFade(LLViewerMedia::getParcelAudioURL()); - } - } - } - else { - // This actually unloads the impl, as opposed to "stop"ping the media - LLViewerParcelMedia::getInstance()->stop(); - if (gAudiop) - { - LLViewerAudio::getInstance()->stopInternetStreamWithAutoFade(); - } - } - - // remove play choice for current parcel - if (agent_parcel && gAgent.getRegion()) - { - LLViewerParcelAskPlay::getInstance()->resetSetting(gAgent.getRegion()->getRegionID(), agent_parcel->getLocalID()); - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -bool LLViewerMedia::isParcelMediaPlaying() -{ - viewer_media_t media = LLViewerParcelMedia::getInstance()->getParcelMedia(); - return (LLViewerMedia::hasParcelMedia() && media && media->hasMedia()); -} - -///////////////////////////////////////////////////////////////////////////////////////// -bool LLViewerMedia::isParcelAudioPlaying() -{ - return (LLViewerMedia::hasParcelAudio() && gAudiop && LLAudioEngine::AUDIO_PLAYING == gAudiop->isInternetStreamPlaying()); -} - -///////////////////////////////////////////////////////////////////////////////////////// -// static -void LLViewerMedia::authSubmitCallback(const LLSD& notification, const LLSD& response) -{ - LLViewerMedia::getInstance()->onAuthSubmit(notification, response); -} - -void LLViewerMedia::onAuthSubmit(const LLSD& notification, const LLSD& response) -{ - LLViewerMediaImpl *impl = LLViewerMedia::getMediaImplFromTextureID(notification["payload"]["media_id"]); - if(impl) - { - LLPluginClassMedia* media = impl->getMediaPlugin(); - if(media) - { - if (response["ok"]) - { - media->sendAuthResponse(true, response["username"], response["password"]); - } - else - { - media->sendAuthResponse(false, "", ""); - } - } - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMedia::clearAllCookies() -{ - // Clear all cookies for all plugins - impl_list::iterator iter = sViewerMediaImplList.begin(); - impl_list::iterator end = sViewerMediaImplList.end(); - for (; iter != end; iter++) - { - LLViewerMediaImpl* pimpl = *iter; - if(pimpl->mMediaSource) - { - pimpl->mMediaSource->clear_cookies(); - } - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMedia::clearAllCaches() -{ - // Clear all plugins' caches - impl_list::iterator iter = sViewerMediaImplList.begin(); - impl_list::iterator end = sViewerMediaImplList.end(); - for (; iter != end; iter++) - { - LLViewerMediaImpl* pimpl = *iter; - pimpl->clearCache(); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMedia::setCookiesEnabled(bool enabled) -{ - // Set the "cookies enabled" flag for all loaded plugins - impl_list::iterator iter = sViewerMediaImplList.begin(); - impl_list::iterator end = sViewerMediaImplList.end(); - for (; iter != end; iter++) - { - LLViewerMediaImpl* pimpl = *iter; - if(pimpl->mMediaSource) - { - pimpl->mMediaSource->cookies_enabled(enabled); - } - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMedia::setProxyConfig(bool enable, const std::string &host, int port) -{ - // Set the proxy config for all loaded plugins - impl_list::iterator iter = sViewerMediaImplList.begin(); - impl_list::iterator end = sViewerMediaImplList.end(); - for (; iter != end; iter++) - { - LLViewerMediaImpl* pimpl = *iter; - if(pimpl->mMediaSource) - { - pimpl->mMediaSource->proxy_setup(enable, host, port); - } - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -LLSD LLViewerMedia::getHeaders() -{ - LLSD headers = LLSD::emptyMap(); - headers[HTTP_OUT_HEADER_ACCEPT] = "*/*"; - // *TODO: Should this be 'application/llsd+xml' ? - // *TODO: Should this even be set at all? This header is only not overridden in 'GET' methods. - headers[HTTP_OUT_HEADER_CONTENT_TYPE] = HTTP_CONTENT_XML; - headers[HTTP_OUT_HEADER_COOKIE] = mOpenIDCookie; - headers[HTTP_OUT_HEADER_USER_AGENT] = getCurrentUserAgent(); - - return headers; -} - - ///////////////////////////////////////////////////////////////////////////////////////// -bool LLViewerMedia::parseRawCookie(const std::string raw_cookie, std::string& name, std::string& value, std::string& path, bool& httponly, bool& secure) -{ - std::size_t name_pos = raw_cookie.find_first_of("="); - if (name_pos != std::string::npos) - { - name = raw_cookie.substr(0, name_pos); - std::size_t value_pos = raw_cookie.find_first_of(";", name_pos); - if (value_pos != std::string::npos) - { - value = raw_cookie.substr(name_pos + 1, value_pos - name_pos - 1); - path = "/"; // assume root path for now - - httponly = true; // hard coded for now - secure = true; - - return true; - } - } - - return false; -} - -///////////////////////////////////////////////////////////////////////////////////////// -LLCore::HttpHeaders::ptr_t LLViewerMedia::getHttpHeaders() -{ - LLCore::HttpHeaders::ptr_t headers(new LLCore::HttpHeaders); - - headers->append(HTTP_OUT_HEADER_ACCEPT, "*/*"); - headers->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_XML); - headers->append(HTTP_OUT_HEADER_COOKIE, mOpenIDCookie); - headers->append(HTTP_OUT_HEADER_USER_AGENT, getCurrentUserAgent()); - - return headers; -} - - -///////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMedia::setOpenIDCookie(const std::string& url) -{ - if(!gNonInteractive && !mOpenIDCookie.empty()) - { - std::string profileUrl = getProfileURL(""); - - LLCoros::instance().launch("LLViewerMedia::getOpenIDCookieCoro", - boost::bind(&LLViewerMedia::getOpenIDCookieCoro, profileUrl)); - } -} - -//static -void LLViewerMedia::getOpenIDCookieCoro(std::string url) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getOpenIDCookieCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); - LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); - - httpOpts->setFollowRedirects(true); - httpOpts->setWantHeaders(true); - httpOpts->setSSLVerifyPeer(false); // viewer's cert bundle doesn't appear to agree with web certs from "https://my.secondlife.com/" - - LLURL hostUrl(url.c_str()); - std::string hostAuth = hostUrl.getAuthority(); - - // *TODO: Expand LLURL to split and extract this information better. - // The structure of a URL is well defined and needing to retrieve parts of it are common. - // original comment: - // The LLURL can give me the 'authority', which is of the form: [username[:password]@]hostname[:port] - // We want just the hostname for the cookie code, but LLURL doesn't seem to have a way to extract that. - // We therefore do it here. - std::string authority = getInstance()->mOpenIDURL.mAuthority; - std::string::size_type hostStart = authority.find('@'); - if (hostStart == std::string::npos) - { // no username/password - hostStart = 0; - } - else - { // Hostname starts after the @. - // Hostname starts after the @. - // (If the hostname part is empty, this may put host_start at the end of the string. In that case, it will end up passing through an empty hostname, which is correct.) - ++hostStart; - } - std::string::size_type hostEnd = authority.rfind(':'); - if ((hostEnd == std::string::npos) || (hostEnd < hostStart)) - { // no port - hostEnd = authority.size(); - } - - LLViewerMedia* inst = getInstance(); - if (url.length()) - { - LLMediaCtrl* media_instance = LLFloaterReg::getInstance("destinations")->getChild("destination_guide_contents"); - if (media_instance) - { - std::string cookie_host = authority.substr(hostStart, hostEnd - hostStart); - std::string cookie_name = ""; - std::string cookie_value = ""; - std::string cookie_path = ""; - bool httponly = true; - bool secure = true; - if (inst->parseRawCookie(inst->mOpenIDCookie, cookie_name, cookie_value, cookie_path, httponly, secure) && - media_instance->getMediaPlugin()) - { - // MAINT-5711 - inexplicably, the CEF setCookie function will no longer set the cookie if the - // url and domain are not the same. This used to be my.sl.com and id.sl.com respectively and worked. - // For now, we use the URL for the OpenID POST request since it will have the same authority - // as the domain field. - // (Feels like there must be a less dirty way to construct a URL from component LLURL parts) - // MAINT-6392 - Rider: Do not change, however, the original URI requested, since it is used further - // down. - std::string cefUrl(std::string(inst->mOpenIDURL.mURI) + "://" + std::string(inst->mOpenIDURL.mAuthority)); - - media_instance->getMediaPlugin()->setCookie(cefUrl, cookie_name, cookie_value, cookie_host, - cookie_path, httponly, secure); - - // Now that we have parsed the raw cookie, we must store it so that each new media instance - // can also get a copy and faciliate logging into internal SL sites. - media_instance->getMediaPlugin()->storeOpenIDCookie(cefUrl, cookie_name, cookie_value, - cookie_host, cookie_path, httponly, secure); - } - } - } - - // Note: Rider: MAINT-6392 - Some viewer code requires access to the my.sl.com openid cookie for such - // actions as posting snapshots to the feed. This is handled through HTTPCore rather than CEF and so - // we must learn to SHARE the cookies. - - // Do a web profile get so we can store the cookie - httpHeaders->append(HTTP_OUT_HEADER_ACCEPT, "*/*"); - httpHeaders->append(HTTP_OUT_HEADER_COOKIE, inst->mOpenIDCookie); - httpHeaders->append(HTTP_OUT_HEADER_USER_AGENT, inst->getCurrentUserAgent()); - - LL_DEBUGS("MediaAuth") << "Requesting " << url << LL_ENDL; - LL_DEBUGS("MediaAuth") << "sOpenIDCookie = [" << inst->mOpenIDCookie << "]" << LL_ENDL; - - LLSD result = httpAdapter->getRawAndSuspend(httpRequest, url, httpOpts, httpHeaders); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS("MediaAuth") << "Error getting web profile." << LL_ENDL; - return; - } - - LLSD resultHeaders = httpResults[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS]; - if (!resultHeaders.has(HTTP_IN_HEADER_SET_COOKIE)) - { - LL_WARNS("MediaAuth") << "No cookie in response." << LL_ENDL; - return; - } - - const std::string& cookie = resultHeaders[HTTP_IN_HEADER_SET_COOKIE].asStringRef(); - LL_DEBUGS("MediaAuth") << "cookie = " << cookie << LL_ENDL; - - // Set cookie for snapshot publishing. - std::string authCookie = cookie.substr(0, cookie.find(";")); // strip path - LLWebProfile::setAuthCookie(authCookie); -} - -///////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMedia::openIDSetup(const std::string &openidUrl, const std::string &openidToken) -{ - LL_DEBUGS("MediaAuth") << "url = \"" << openidUrl << "\", token = \"" << openidToken << "\"" << LL_ENDL; - - LLCoros::instance().launch("LLViewerMedia::openIDSetupCoro", - boost::bind(&LLViewerMedia::openIDSetupCoro, openidUrl, openidToken)); -} - -void LLViewerMedia::openIDSetupCoro(std::string openidUrl, std::string openidToken) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("openIDSetupCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); - LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); - - httpOpts->setWantHeaders(true); - - // post the token to the url - // the responder will need to extract the cookie(s). - // Save the OpenID URL for later -- we may need the host when adding the cookie. - getInstance()->mOpenIDURL.init(openidUrl.c_str()); - - // We shouldn't ever do this twice, but just in case this code gets repurposed later, clear existing cookies. - getInstance()->mOpenIDCookie.clear(); - - httpHeaders->append(HTTP_OUT_HEADER_ACCEPT, "*/*"); - httpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/x-www-form-urlencoded"); - - LLCore::BufferArray::ptr_t rawbody(new LLCore::BufferArray); - LLCore::BufferArrayStream bas(rawbody.get()); - - bas << std::noskipws << openidToken; - - LLSD result = httpAdapter->postRawAndSuspend(httpRequest, openidUrl, rawbody, httpOpts, httpHeaders); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS("MediaAuth") << "Error getting Open ID cookie" << LL_ENDL; - return; - } - - LLSD resultHeaders = httpResults[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS]; - if (!resultHeaders.has(HTTP_IN_HEADER_SET_COOKIE)) - { - LL_WARNS("MediaAuth") << "No cookie in response." << LL_ENDL; - return; - } - - // We don't care about the content of the response, only the Set-Cookie header. - const std::string& cookie = resultHeaders[HTTP_IN_HEADER_SET_COOKIE].asString(); - - // *TODO: What about bad status codes? Does this destroy previous cookies? - LLViewerMedia::getInstance()->openIDCookieResponse(openidUrl, cookie); - LL_DEBUGS("MediaAuth") << "OpenID cookie set." << LL_ENDL; - -} - -///////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMedia::openIDCookieResponse(const std::string& url, const std::string &cookie) -{ - LL_DEBUGS("MediaAuth") << "Cookie received: \"" << cookie << "\"" << LL_ENDL; - - mOpenIDCookie += cookie; - - setOpenIDCookie(url); -} - -///////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMedia::proxyWindowOpened(const std::string &target, const std::string &uuid) -{ - if(uuid.empty()) - return; - - for (impl_list::iterator iter = sViewerMediaImplList.begin(); iter != sViewerMediaImplList.end(); iter++) - { - if((*iter)->mMediaSource && (*iter)->mMediaSource->pluginSupportsMediaBrowser()) - { - (*iter)->mMediaSource->proxyWindowOpened(target, uuid); - } - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMedia::proxyWindowClosed(const std::string &uuid) -{ - if(uuid.empty()) - return; - - for (impl_list::iterator iter = sViewerMediaImplList.begin(); iter != sViewerMediaImplList.end(); iter++) - { - if((*iter)->mMediaSource && (*iter)->mMediaSource->pluginSupportsMediaBrowser()) - { - (*iter)->mMediaSource->proxyWindowClosed(uuid); - } - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMedia::createSpareBrowserMediaSource() -{ - // If we don't have a spare browser media source, create one. - // However, if PluginAttachDebuggerToPlugins is set then don't spawn a spare - // SLPlugin process in order to not be confused by an unrelated gdb terminal - // popping up at the moment we start a media plugin. - if (!mSpareBrowserMediaSource && !gSavedSettings.getBOOL("PluginAttachDebuggerToPlugins")) - { - // The null owner will keep the browser plugin from fully initializing - // (specifically, it keeps LLPluginClassMedia from negotiating a size change, - // which keeps MediaPluginWebkit::initBrowserWindow from doing anything until we have some necessary data, like the background color) - mSpareBrowserMediaSource = LLViewerMediaImpl::newSourceFromMediaType(HTTP_CONTENT_TEXT_HTML, NULL, 0, 0, 1.0); - } -} - -///////////////////////////////////////////////////////////////////////////////////////// -LLPluginClassMedia* LLViewerMedia::getSpareBrowserMediaSource() -{ - LLPluginClassMedia* result = mSpareBrowserMediaSource; - mSpareBrowserMediaSource = NULL; - return result; -}; - -bool LLViewerMedia::hasInWorldMedia() -{ - impl_list::iterator iter = sViewerMediaImplList.begin(); - impl_list::iterator end = sViewerMediaImplList.end(); - // This should be quick, because there should be very few non-in-world-media impls - for (; iter != end; iter++) - { - LLViewerMediaImpl* pimpl = *iter; - if (!pimpl->getUsedInUI() && !pimpl->isParcelMedia()) - { - // Found an in-world media impl - return true; - } - } - return false; -} - -////////////////////////////////////////////////////////////////////////////////////////// -bool LLViewerMedia::hasParcelMedia() -{ - return !LLViewerParcelMedia::getInstance()->getURL().empty(); -} - -////////////////////////////////////////////////////////////////////////////////////////// -bool LLViewerMedia::hasParcelAudio() -{ - return !LLViewerMedia::getParcelAudioURL().empty(); -} - -////////////////////////////////////////////////////////////////////////////////////////// -std::string LLViewerMedia::getParcelAudioURL() -{ - return LLViewerParcelMgr::getInstance()->getAgentParcel()->getMusicURL(); -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMedia::onTeleportFinished() -{ - // On teleport, clear this setting (i.e. set it to true) - gSavedSettings.setBOOL("MediaTentativeAutoPlay", true); - - LLViewerMediaImpl::sMimeTypesFailed.clear(); -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMedia::setOnlyAudibleMediaTextureID(const LLUUID& texture_id) -{ - sOnlyAudibleTextureID = texture_id; - sForceUpdate = true; -} - -std::vector LLViewerMediaImpl::sMimeTypesFailed; -////////////////////////////////////////////////////////////////////////////////////////// -// LLViewerMediaImpl -////////////////////////////////////////////////////////////////////////////////////////// -LLViewerMediaImpl::LLViewerMediaImpl( const LLUUID& texture_id, - S32 media_width, - S32 media_height, - U8 media_auto_scale, - U8 media_loop) -: - mMediaSource( NULL ), - mMovieImageHasMips(false), - mMediaWidth(media_width), - mMediaHeight(media_height), - mMediaAutoScale(media_auto_scale), - mMediaLoop(media_loop), - mNeedsNewTexture(true), - mTextureUsedWidth(0), - mTextureUsedHeight(0), - mSuspendUpdates(false), - mVisible(true), - mLastSetCursor( UI_CURSOR_ARROW ), - mMediaNavState( MEDIANAVSTATE_NONE ), - mInterest(0.0f), - mUsedInUI(false), - mHasFocus(false), - mPriority(LLPluginClassMedia::PRIORITY_UNLOADED), - mNavigateRediscoverType(false), - mNavigateServerRequest(false), - mMediaSourceFailed(false), - mRequestedVolume(1.0f), - mPreviousVolume(1.0f), - mIsMuted(false), - mNeedsMuteCheck(false), - mPreviousMediaState(MEDIA_NONE), - mPreviousMediaTime(0.0f), - mIsDisabled(false), - mIsParcelMedia(false), - mProximity(-1), - mProximityDistance(0.0f), - mMediaAutoPlay(false), - mInNearbyMediaList(false), - mClearCache(false), - mBackgroundColor(LLColor4::white), - mNavigateSuspended(false), - mNavigateSuspendedDeferred(false), - mIsUpdated(false), - mTrustedBrowser(false), - mZoomFactor(1.0), - mCleanBrowser(false), - mMimeProbe(), - mCanceling(false) -{ - - // Set up the mute list observer if it hasn't been set up already. - if(!sViewerMediaMuteListObserverInitialized) - { - LLMuteList::getInstance()->addObserver(&sViewerMediaMuteListObserver); - sViewerMediaMuteListObserverInitialized = true; - } - - add_media_impl(this); - - setTextureID(texture_id); - - // connect this media_impl to the media texture, creating it if it doesn't exist.0 - // This is necessary because we need to be able to use getMaxVirtualSize() even if the media plugin is not loaded. - // *TODO: Consider enabling mipmaps (they have been disabled for a long time). Likely has a significant performance impact for tiled/high texture repeat media. Mip generation in a shader may also be an option if necessary. - LLViewerMediaTexture* media_tex = LLViewerTextureManager::getMediaTexture(mTextureId, USE_MIPMAPS); - if(media_tex) - { - media_tex->setMediaImpl(); - } - - mMainQueue = LL::WorkQueue::getInstance("mainloop"); - mTexUpdateQueue = LL::WorkQueue::getInstance("LLImageGL"); // Share work queue with tex loader. -} - -////////////////////////////////////////////////////////////////////////////////////////// -LLViewerMediaImpl::~LLViewerMediaImpl() -{ - destroyMediaSource(); - - LLViewerMediaTexture::removeMediaImplFromTexture(mTextureId) ; - - setTextureID(); - remove_media_impl(this); -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::emitEvent(LLPluginClassMedia* plugin, LLViewerMediaObserver::EMediaEvent event) -{ - // Broadcast to observers using the superclass version - LLViewerMediaEventEmitter::emitEvent(plugin, event); - - // If this media is on one or more LLVOVolume objects, tell them about the event as well. - std::list< LLVOVolume* >::iterator iter = mObjectList.begin() ; - while(iter != mObjectList.end()) - { - LLVOVolume *self = *iter; - ++iter; - self->mediaEvent(this, plugin, event); - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -bool LLViewerMediaImpl::initializeMedia(const std::string& mime_type) -{ - bool mimeTypeChanged = (mMimeType != mime_type); - bool pluginChanged = (LLMIMETypes::implType(mCurrentMimeType) != LLMIMETypes::implType(mime_type)); - - if(!mMediaSource || pluginChanged) - { - // We don't have a plugin at all, or the new mime type is handled by a different plugin than the old mime type. - (void)initializePlugin(mime_type); - } - else if(mimeTypeChanged) - { - // The same plugin should be able to handle the new media -- just update the stored mime type. - mMimeType = mime_type; - } - - return (mMediaSource != NULL); -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::createMediaSource() -{ - if(mPriority == LLPluginClassMedia::PRIORITY_UNLOADED) - { - // This media shouldn't be created yet. - return; - } - - if(! mMediaURL.empty()) - { - navigateInternal(); - } - else if(! mMimeType.empty()) - { - if (!initializeMedia(mMimeType)) - { - LL_WARNS("Media") << "Failed to initialize media for mime type " << mMimeType << LL_ENDL; - } - } - -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::destroyMediaSource() -{ - mNeedsNewTexture = true; - - // Tell the viewer media texture it's no longer active - LLViewerMediaTexture* oldImage = LLViewerTextureManager::findMediaTexture( mTextureId ); - if (oldImage) - { - oldImage->setPlaying(false) ; - } - - cancelMimeTypeProbe(); - - { - LLMutexLock lock(&mLock); // Delay tear-down while bg thread is updating - if(mMediaSource) - { - mMediaSource->setDeleteOK(true) ; - mMediaSource = NULL; // shared pointer - } - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::setMediaType(const std::string& media_type) -{ - mMimeType = media_type; -} - -////////////////////////////////////////////////////////////////////////////////////////// -/*static*/ -LLPluginClassMedia* LLViewerMediaImpl::newSourceFromMediaType(std::string media_type, LLPluginClassMediaOwner *owner /* may be NULL */, S32 default_width, S32 default_height, F64 zoom_factor, const std::string target, bool clean_browser) -{ - if (gNonInteractive) - { - return NULL; - } - - std::string plugin_basename = LLMIMETypes::implType(media_type); - LLPluginClassMedia* media_source = NULL; - - // HACK: we always try to keep a spare running webkit plugin around to improve launch times. - // If a spare was already created before PluginAttachDebuggerToPlugins was set, don't use it. - // Do not use a spare if launching with full viewer control (e.g. Twitter and few others) - if ((plugin_basename == "media_plugin_cef") && - !gSavedSettings.getBOOL("PluginAttachDebuggerToPlugins") && !clean_browser) - { - media_source = LLViewerMedia::getInstance()->getSpareBrowserMediaSource(); - if(media_source) - { - media_source->setOwner(owner); - media_source->setTarget(target); - media_source->setSize(default_width, default_height); - media_source->setZoomFactor(zoom_factor); - - return media_source; - } - } - if(plugin_basename.empty()) - { - LL_WARNS_ONCE("Media") << "Couldn't find plugin for media type " << media_type << LL_ENDL; - } - else - { - std::string launcher_name = gDirUtilp->getLLPluginLauncher(); - std::string plugin_name = gDirUtilp->getLLPluginFilename(plugin_basename); - - std::string user_data_path_cache = gDirUtilp->getCacheDir(false); - user_data_path_cache += gDirUtilp->getDirDelimiter(); - - std::string user_data_path_cef_log = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "cef_log.txt"); - - // See if the plugin executable exists - llstat s; - if(LLFile::stat(launcher_name, &s)) - { - LL_WARNS_ONCE("Media") << "Couldn't find launcher at " << launcher_name << LL_ENDL; - } - else if(LLFile::stat(plugin_name, &s)) - { -#if !LL_LINUX - LL_WARNS_ONCE("Media") << "Couldn't find plugin at " << plugin_name << LL_ENDL; -#endif - } - else - { - media_source = new LLPluginClassMedia(owner); - media_source->setSize(default_width, default_height); - media_source->setUserDataPath(user_data_path_cache, gDirUtilp->getUserName(), user_data_path_cef_log); - media_source->setLanguageCode(LLUI::getLanguage()); - media_source->setZoomFactor(zoom_factor); - - // collect 'cookies enabled' setting from prefs and send to embedded browser - bool cookies_enabled = gSavedSettings.getBOOL( "CookiesEnabled" ); - media_source->cookies_enabled( cookies_enabled || clean_browser); - - // collect 'javascript enabled' setting from prefs and send to embedded browser - bool javascript_enabled = gSavedSettings.getBOOL("BrowserJavascriptEnabled"); - media_source->setJavascriptEnabled(javascript_enabled || clean_browser); - - // collect 'web security disabled' (see Chrome --web-security-disabled) setting from prefs and send to embedded browser - bool web_security_disabled = gSavedSettings.getBOOL("BrowserWebSecurityDisabled"); - media_source->setWebSecurityDisabled(web_security_disabled || clean_browser); - - // collect setting indicates if local file access from file URLs is allowed from prefs and send to embedded browser - bool file_access_from_file_urls = gSavedSettings.getBOOL("BrowserFileAccessFromFileUrls"); - media_source->setFileAccessFromFileUrlsEnabled(file_access_from_file_urls || clean_browser); - - // As of SL-15559 PDF files do not load in CEF v91 we enable plugins - // but explicitly disable Flash (PDF support in CEF is now treated as a plugin) - media_source->setPluginsEnabled(true); - - bool media_plugin_debugging_enabled = gSavedSettings.getBOOL("MediaPluginDebugging"); - media_source->enableMediaPluginDebugging( media_plugin_debugging_enabled || clean_browser); - - // need to set agent string here before instance created - media_source->setBrowserUserAgent(LLViewerMedia::getInstance()->getCurrentUserAgent()); - - // configure and pass proxy setup based on debug settings that are - // configured by UI in prefs -> setup - media_source->proxy_setup(gSavedSettings.getBOOL("BrowserProxyEnabled"), gSavedSettings.getString("BrowserProxyAddress"), gSavedSettings.getS32("BrowserProxyPort")); - - media_source->setTarget(target); - - const std::string plugin_dir = gDirUtilp->getLLPluginDir(); - if (media_source->init(launcher_name, plugin_dir, plugin_name, gSavedSettings.getBOOL("PluginAttachDebuggerToPlugins"))) - { - return media_source; - } - else - { - LL_WARNS("Media") << "Failed to init plugin. Destroying." << LL_ENDL; - delete media_source; - } - } - } -#if !LL_LINUX - LL_WARNS_ONCE("Plugin") << "plugin initialization failed for mime type: " << media_type << LL_ENDL; -#endif - - if(gAgent.isInitialized()) - { - if (std::find(sMimeTypesFailed.begin(), sMimeTypesFailed.end(), media_type) == sMimeTypesFailed.end()) - { - LLSD args; - args["MIME_TYPE"] = media_type; - LLNotificationsUtil::add("NoPlugin", args); - sMimeTypesFailed.push_back(media_type); - } - } - return NULL; -} - -////////////////////////////////////////////////////////////////////////////////////////// -bool LLViewerMediaImpl::initializePlugin(const std::string& media_type) -{ - if(mMediaSource) - { - // Save the previous media source's last set size before destroying it. - mMediaWidth = mMediaSource->getSetWidth(); - mMediaHeight = mMediaSource->getSetHeight(); - mZoomFactor = mMediaSource->getZoomFactor(); - } - - // Always delete the old media impl first. - destroyMediaSource(); - - // and unconditionally set the mime type - mMimeType = media_type; - - if(mPriority == LLPluginClassMedia::PRIORITY_UNLOADED) - { - // This impl should not be loaded at this time. - LL_DEBUGS("PluginPriority") << this << "Not loading (PRIORITY_UNLOADED)" << LL_ENDL; - - return false; - } - - // If we got here, we want to ignore previous init failures. - mMediaSourceFailed = false; - - // Save the MIME type that really caused the plugin to load - mCurrentMimeType = mMimeType; - - LLPluginClassMedia* media_source = newSourceFromMediaType(mMimeType, this, mMediaWidth, mMediaHeight, mZoomFactor, mTarget, mCleanBrowser); - - if (media_source) - { - media_source->injectOpenIDCookie(); - media_source->setDisableTimeout(gSavedSettings.getBOOL("DebugPluginDisableTimeout")); - media_source->setLoop(mMediaLoop); - media_source->setAutoScale(mMediaAutoScale); - media_source->setBrowserUserAgent(LLViewerMedia::getInstance()->getCurrentUserAgent()); - media_source->focus(mHasFocus); - media_source->setBackgroundColor(mBackgroundColor); - - if(gSavedSettings.getBOOL("BrowserIgnoreSSLCertErrors")) - { - media_source->ignore_ssl_cert_errors(true); - } - - // the correct way to deal with certs it to load ours from ca-bundle.crt and append them to the ones - // Qt/WebKit loads from your system location. - std::string ca_path = gDirUtilp->getCAFile(); - media_source->addCertificateFilePath( ca_path ); - - if(mClearCache) - { - mClearCache = false; - media_source->clear_cache(); - } - - mMediaSource.reset(media_source); - mMediaSource->setDeleteOK(false) ; - updateVolume(); - - return true; - } - - // Make sure the timer doesn't try re-initing this plugin repeatedly until something else changes. - mMediaSourceFailed = true; - - return false; -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::loadURI() -{ - if(mMediaSource) - { - // trim whitespace from front and back of URL - fixes EXT-5363 - LLStringUtil::trim( mMediaURL ); - - // URI often comes unescaped - std::string uri = LLURI::escapePathAndData(mMediaURL); - { - // Do not log the query parts - LLURI u(uri); - std::string sanitized_uri = (u.query().empty() ? uri : u.scheme() + "://" + u.authority() + u.path()); - LL_INFOS() << "Asking media source to load URI: " << sanitized_uri << LL_ENDL; - } - - mMediaSource->loadURI( uri ); - - // A non-zero mPreviousMediaTime means that either this media was previously unloaded by the priority code while playing/paused, - // or a seek happened before the media loaded. In either case, seek to the saved time. - if(mPreviousMediaTime != 0.0f) - { - seek(mPreviousMediaTime); - } - - if(mPreviousMediaState == MEDIA_PLAYING) - { - // This media was playing before this instance was unloaded. - start(); - } - else if(mPreviousMediaState == MEDIA_PAUSED) - { - // This media was paused before this instance was unloaded. - pause(); - } - else - { - // No relevant previous media play state -- if we're loading the URL, we want to start playing. - start(); - } - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::executeJavaScript(const std::string& code) -{ - if (mMediaSource) - { - mMediaSource->executeJavaScript(code); - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::setSize(int width, int height) -{ - mMediaWidth = width; - mMediaHeight = height; - if(mMediaSource) - { - mMediaSource->setSize(width, height); - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::showNotification(LLNotificationPtr notify) -{ - mNotification = notify; -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::hideNotification() -{ - mNotification.reset(); -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::play() -{ - // If the media source isn't there, try to initialize it and load an URL. - if(mMediaSource == NULL) - { - if(!initializeMedia(mMimeType)) - { - // This may be the case where the plugin's priority is PRIORITY_UNLOADED - return; - } - - // Only do this if the media source was just loaded. - loadURI(); - } - - // always start the media - start(); -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::stop() -{ - if(mMediaSource) - { - mMediaSource->stop(); - // destroyMediaSource(); - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::pause() -{ - if(mMediaSource) - { - mMediaSource->pause(); - } - else - { - mPreviousMediaState = MEDIA_PAUSED; - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::start() -{ - if(mMediaSource) - { - mMediaSource->start(); - } - else - { - mPreviousMediaState = MEDIA_PLAYING; - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::seek(F32 time) -{ - if(mMediaSource) - { - mMediaSource->seek(time); - } - else - { - // Save the seek time to be set when the media is loaded. - mPreviousMediaTime = time; - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::skipBack(F32 step_scale) -{ - if(mMediaSource) - { - if(mMediaSource->pluginSupportsMediaTime()) - { - F64 back_step = mMediaSource->getCurrentTime() - (mMediaSource->getDuration()*step_scale); - if(back_step < 0.0) - { - back_step = 0.0; - } - mMediaSource->seek(back_step); - } - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::skipForward(F32 step_scale) -{ - if(mMediaSource) - { - if(mMediaSource->pluginSupportsMediaTime()) - { - F64 forward_step = mMediaSource->getCurrentTime() + (mMediaSource->getDuration()*step_scale); - if(forward_step > mMediaSource->getDuration()) - { - forward_step = mMediaSource->getDuration(); - } - mMediaSource->seek(forward_step); - } - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::setVolume(F32 volume) -{ - mRequestedVolume = volume; - updateVolume(); -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::setMute(bool mute) -{ - if (mute) - { - mPreviousVolume = mRequestedVolume; - setVolume(0.0); - } - else - { - setVolume(mPreviousVolume); - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::updateVolume() -{ - LL_RECORD_BLOCK_TIME(FTM_MEDIA_UPDATE_VOLUME); - if(mMediaSource) - { - // always scale the volume by the global media volume - F32 volume = mRequestedVolume * LLViewerMedia::getInstance()->getVolume(); - - if (mProximityCamera > 0) - { - if (mProximityCamera > gSavedSettings.getF32("MediaRollOffMax")) - { - volume = 0; - } - else if (mProximityCamera > gSavedSettings.getF32("MediaRollOffMin")) - { - // attenuated_volume = 1 / (roll_off_rate * (d - min))^2 - // the +1 is there so that for distance 0 the volume stays the same - F64 adjusted_distance = mProximityCamera - gSavedSettings.getF32("MediaRollOffMin"); - F64 attenuation = 1.0 + (gSavedSettings.getF32("MediaRollOffRate") * adjusted_distance); - attenuation = 1.0 / (attenuation * attenuation); - // the attenuation multiplier should never be more than one since that would increase volume - volume = volume * llmin(1.0, attenuation); - } - } - - if (sOnlyAudibleTextureID == LLUUID::null || sOnlyAudibleTextureID == mTextureId) - { - mMediaSource->setVolume(volume); - } - else - { - mMediaSource->setVolume(0.0f); - } - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -F32 LLViewerMediaImpl::getVolume() -{ - return mRequestedVolume; -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::focus(bool focus) -{ - mHasFocus = focus; - - if (mMediaSource) - { - // call focus just for the hell of it, even though this apopears to be a nop - mMediaSource->focus(focus); - if (focus) - { - // spoof a mouse click to *actually* pass focus - // Don't do this anymore -- it actually clicks through now. -// mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_DOWN, 1, 1, 0); -// mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_UP, 1, 1, 0); - } - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -bool LLViewerMediaImpl::hasFocus() const -{ - // FIXME: This might be able to be a bit smarter by hooking into LLViewerMediaFocus, etc. - return mHasFocus; -} - -std::string LLViewerMediaImpl::getCurrentMediaURL() -{ - if(!mCurrentMediaURL.empty()) - { - return mCurrentMediaURL; - } - - return mMediaURL; -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::clearCache() -{ - if(mMediaSource) - { - mMediaSource->clear_cache(); - } - else - { - mClearCache = true; - } -} - - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::setPageZoomFactor( double factor ) -{ - if(mMediaSource && factor != mZoomFactor) - { - mZoomFactor = factor; - mMediaSource->set_page_zoom_factor( factor ); - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::mouseDown(S32 x, S32 y, MASK mask, S32 button) -{ - scaleMouse(&x, &y); - mLastMouseX = x; - mLastMouseY = y; -// LL_INFOS() << "mouse down (" << x << ", " << y << ")" << LL_ENDL; - if (mMediaSource) - { - mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_DOWN, button, x, y, mask); - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::mouseUp(S32 x, S32 y, MASK mask, S32 button) -{ - scaleMouse(&x, &y); - mLastMouseX = x; - mLastMouseY = y; -// LL_INFOS() << "mouse up (" << x << ", " << y << ")" << LL_ENDL; - if (mMediaSource) - { - mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_UP, button, x, y, mask); - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::mouseMove(S32 x, S32 y, MASK mask) -{ - scaleMouse(&x, &y); - mLastMouseX = x; - mLastMouseY = y; -// LL_INFOS() << "mouse move (" << x << ", " << y << ")" << LL_ENDL; - if (mMediaSource) - { - mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_MOVE, 0, x, y, mask); - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -//static -void LLViewerMediaImpl::scaleTextureCoords(const LLVector2& texture_coords, S32 *x, S32 *y) -{ - F32 texture_x = texture_coords.mV[VX]; - F32 texture_y = texture_coords.mV[VY]; - - // Deal with repeating textures by wrapping the coordinates into the range [0, 1.0) - texture_x = fmodf(texture_x, 1.0f); - if(texture_x < 0.0f) - texture_x = 1.0 + texture_x; - - texture_y = fmodf(texture_y, 1.0f); - if(texture_y < 0.0f) - texture_y = 1.0 + texture_y; - - // scale x and y to texel units. - *x = ll_round(texture_x * mMediaSource->getTextureWidth()); - *y = ll_round((1.0f - texture_y) * mMediaSource->getTextureHeight()); - - // Adjust for the difference between the actual texture height and the amount of the texture in use. - *y -= (mMediaSource->getTextureHeight() - mMediaSource->getHeight()); -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::mouseDown(const LLVector2& texture_coords, MASK mask, S32 button) -{ - if(mMediaSource) - { - S32 x, y; - scaleTextureCoords(texture_coords, &x, &y); - - mouseDown(x, y, mask, button); - } -} - -void LLViewerMediaImpl::mouseUp(const LLVector2& texture_coords, MASK mask, S32 button) -{ - if(mMediaSource) - { - S32 x, y; - scaleTextureCoords(texture_coords, &x, &y); - - mouseUp(x, y, mask, button); - } -} - -void LLViewerMediaImpl::mouseMove(const LLVector2& texture_coords, MASK mask) -{ - if(mMediaSource) - { - S32 x, y; - scaleTextureCoords(texture_coords, &x, &y); - - mouseMove(x, y, mask); - } -} - -void LLViewerMediaImpl::mouseDoubleClick(const LLVector2& texture_coords, MASK mask) -{ - if (mMediaSource) - { - S32 x, y; - scaleTextureCoords(texture_coords, &x, &y); - - mouseDoubleClick(x, y, mask); - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::mouseDoubleClick(S32 x, S32 y, MASK mask, S32 button) -{ - scaleMouse(&x, &y); - mLastMouseX = x; - mLastMouseY = y; - if (mMediaSource) - { - mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_DOUBLE_CLICK, button, x, y, mask); - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::scrollWheel(const LLVector2& texture_coords, S32 scroll_x, S32 scroll_y, MASK mask) -{ - if (mMediaSource) - { - S32 x, y; - scaleTextureCoords(texture_coords, &x, &y); - - scrollWheel(x, y, scroll_x, scroll_y, mask); - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::scrollWheel(S32 x, S32 y, S32 scroll_x, S32 scroll_y, MASK mask) -{ - scaleMouse(&x, &y); - mLastMouseX = x; - mLastMouseY = y; - if (mMediaSource) - { - mMediaSource->scrollEvent(x, y, scroll_x, scroll_y, mask); - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::onMouseCaptureLost() -{ - if (mMediaSource) - { - mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_UP, 0, mLastMouseX, mLastMouseY, 0); - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -bool LLViewerMediaImpl::handleMouseUp(S32 x, S32 y, MASK mask) -{ - // NOTE: this is called when the mouse is released when we have capture. - // Due to the way mouse coordinates are mapped to the object, we can't use the x and y coordinates that come in with the event. - - if(hasMouseCapture()) - { - // Release the mouse -- this will also send a mouseup to the media - gFocusMgr.setMouseCapture( nullptr ); - } - - return true; -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::updateJavascriptObject() -{ - static LLFrameTimer timer ; - - if ( mMediaSource ) - { - // flag to expose this information to internal browser or not. - bool enable = gSavedSettings.getBOOL("BrowserEnableJSObject"); - - if(!enable) - { - return ; //no need to go further. - } - - if(timer.getElapsedTimeF32() < 1.0f) - { - return ; //do not update more than once per second. - } - timer.reset() ; - - mMediaSource->jsEnableObject( enable ); - - // these values are only menaingful after login so don't set them before - bool logged_in = LLLoginInstance::getInstance()->authSuccess(); - if ( logged_in ) - { - // current location within a region - LLVector3 agent_pos = gAgent.getPositionAgent(); - double x = agent_pos.mV[ VX ]; - double y = agent_pos.mV[ VY ]; - double z = agent_pos.mV[ VZ ]; - mMediaSource->jsAgentLocationEvent( x, y, z ); - - // current location within the grid - LLVector3d agent_pos_global = gAgent.getLastPositionGlobal(); - double global_x = agent_pos_global.mdV[ VX ]; - double global_y = agent_pos_global.mdV[ VY ]; - double global_z = agent_pos_global.mdV[ VZ ]; - mMediaSource->jsAgentGlobalLocationEvent( global_x, global_y, global_z ); - - // current agent orientation - double rotation = atan2( gAgent.getAtAxis().mV[VX], gAgent.getAtAxis().mV[VY] ); - double angle = rotation * RAD_TO_DEG; - if ( angle < 0.0f ) angle = 360.0f + angle; // TODO: has to be a better way to get orientation! - mMediaSource->jsAgentOrientationEvent( angle ); - - // current region agent is in - std::string region_name(""); - LLViewerRegion* region = gAgent.getRegion(); - if ( region ) - { - region_name = region->getName(); - }; - mMediaSource->jsAgentRegionEvent( region_name ); - } - - // language code the viewer is set to - mMediaSource->jsAgentLanguageEvent( LLUI::getLanguage() ); - - // maturity setting the agent has selected - if ( gAgent.prefersAdult() ) - mMediaSource->jsAgentMaturityEvent( "GMA" ); // Adult means see adult, mature and general content - else - if ( gAgent.prefersMature() ) - mMediaSource->jsAgentMaturityEvent( "GM" ); // Mature means see mature and general content - else - if ( gAgent.prefersPG() ) - mMediaSource->jsAgentMaturityEvent( "G" ); // PG means only see General content - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -const std::string& LLViewerMediaImpl::getName() const -{ - if (mMediaSource) - { - return mMediaSource->getMediaName(); - } - - return LLStringUtil::null; -}; - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::navigateBack() -{ - if (mMediaSource) - { - mMediaSource->browse_back(); - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::navigateForward() -{ - if (mMediaSource) - { - mMediaSource->browse_forward(); - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::navigateReload() -{ - navigateTo(getCurrentMediaURL(), "", true, false); -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::navigateHome() -{ - bool rediscover_mimetype = mHomeMimeType.empty(); - navigateTo(mHomeURL, mHomeMimeType, rediscover_mimetype, false); -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::unload() -{ - // Unload the media impl and clear its state. - destroyMediaSource(); - resetPreviousMediaState(); - mMediaURL.clear(); - mMimeType.clear(); - mCurrentMediaURL.clear(); - mCurrentMimeType.clear(); -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::navigateTo(const std::string& url, const std::string& mime_type, bool rediscover_type, bool server_request, bool clean_browser) -{ - cancelMimeTypeProbe(); - - if(mMediaURL != url) - { - // Don't carry media play state across distinct URLs. - resetPreviousMediaState(); - } - - // Always set the current URL and MIME type. - mMediaURL = url; - mMimeType = mime_type; - mCleanBrowser = clean_browser; - - // Clear the current media URL, since it will no longer be correct. - mCurrentMediaURL.clear(); - - // if mime type discovery was requested, we'll need to do it when the media loads - mNavigateRediscoverType = rediscover_type; - - // and if this was a server request, the navigate on load will also need to be one. - mNavigateServerRequest = server_request; - - // An explicit navigate resets the "failed" flag. - mMediaSourceFailed = false; - - if(mPriority == LLPluginClassMedia::PRIORITY_UNLOADED) - { - // Helpful to have media urls in log file. Shouldn't be spammy. - { - // Do not log the query parts - LLURI u(url); - std::string sanitized_url = (u.query().empty() ? url : u.scheme() + "://" + u.authority() + u.path()); - LL_INFOS() << "NOT LOADING media id= " << mTextureId << " url=" << sanitized_url << ", mime_type=" << mime_type << LL_ENDL; - } - - // This impl should not be loaded at this time. - LL_DEBUGS("PluginPriority") << this << "Not loading (PRIORITY_UNLOADED)" << LL_ENDL; - - return; - } - - navigateInternal(); -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::navigateInternal() -{ - // Helpful to have media urls in log file. Shouldn't be spammy. - { - // Do not log the query parts - LLURI u(mMediaURL); - std::string sanitized_url = (u.query().empty() ? mMediaURL : u.scheme() + "://" + u.authority() + u.path()); - LL_INFOS() << "media id= " << mTextureId << " url=" << sanitized_url << ", mime_type=" << mMimeType << LL_ENDL; - } - - if(mNavigateSuspended) - { - LL_WARNS() << "Deferring navigate." << LL_ENDL; - mNavigateSuspendedDeferred = true; - return; - } - - - if (!mMimeProbe.expired()) - { - LL_WARNS() << "MIME type probe already in progress -- bailing out." << LL_ENDL; - return; - } - - if(mNavigateServerRequest) - { - setNavState(MEDIANAVSTATE_SERVER_SENT); - } - else - { - setNavState(MEDIANAVSTATE_NONE); - } - - // If the caller has specified a non-empty MIME type, look that up in our MIME types list. - // If we have a plugin for that MIME type, use that instead of attempting auto-discovery. - // This helps in supporting legacy media content where the server the media resides on returns a bogus MIME type - // but the parcel owner has correctly set the MIME type in the parcel media settings. - - if(!mMimeType.empty() && (mMimeType != LLMIMETypes::getDefaultMimeType())) - { - std::string plugin_basename = LLMIMETypes::implType(mMimeType); - if(!plugin_basename.empty()) - { - // We have a plugin for this mime type - mNavigateRediscoverType = false; - } - } - - if(mNavigateRediscoverType) - { - - LLURI uri(mMediaURL); - std::string scheme = uri.scheme(); - - if(scheme.empty() || "http" == scheme || "https" == scheme) - { - LLCoros::instance().launch("LLViewerMediaImpl::mimeDiscoveryCoro", - boost::bind(&LLViewerMediaImpl::mimeDiscoveryCoro, this, mMediaURL)); - } - else if("data" == scheme || "file" == scheme || "about" == scheme) - { - // FIXME: figure out how to really discover the type for these schemes - // We use "data" internally for a text/html url for loading the login screen - if(initializeMedia(HTTP_CONTENT_TEXT_HTML)) - { - loadURI(); - } - } - else - { - // This catches 'rtsp://' urls - if(initializeMedia(scheme)) - { - loadURI(); - } - } - } - else if(initializeMedia(mMimeType)) - { - loadURI(); - } - else - { - LL_WARNS("Media") << "Couldn't navigate to: " << mMediaURL << " as there is no media type for: " << mMimeType << LL_ENDL; - } -} - -void LLViewerMediaImpl::mimeDiscoveryCoro(std::string url) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("mimeDiscoveryCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); - LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); - - // Increment our refcount so that we do not go away while the coroutine is active. - this->ref(); - - mMimeProbe = httpAdapter; - - httpOpts->setFollowRedirects(true); - httpOpts->setHeadersOnly(true); - - httpHeaders->append(HTTP_OUT_HEADER_ACCEPT, "*/*"); - httpHeaders->append(HTTP_OUT_HEADER_COOKIE, ""); - - LLSD result = httpAdapter->getRawAndSuspend(httpRequest, url, httpOpts, httpHeaders); - - mMimeProbe.reset(); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS() << "Error retrieving media headers." << LL_ENDL; - } - - if (this->getNumRefs() > 1) - { // if there is only a single ref count outstanding it will be the one we took out above... - // we can skip the rest of this routine - - LLSD resultHeaders = httpResults[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS]; - - const std::string& mediaType = resultHeaders[HTTP_IN_HEADER_CONTENT_TYPE].asStringRef(); - - std::string::size_type idx1 = mediaType.find_first_of(";"); - std::string mimeType = mediaType.substr(0, idx1); - - // We now no longer need to check the error code returned from the probe. - // If we have a mime type, use it. If not, default to the web plugin and let it handle error reporting. - // The probe was successful. - if (mimeType.empty()) - { - // Some sites don't return any content-type header at all. - // Treat an empty mime type as text/html. - mimeType = HTTP_CONTENT_TEXT_HTML; - } - - LL_DEBUGS() << "Media type \"" << mediaType << "\", mime type is \"" << mimeType << "\"" << LL_ENDL; - - // the call to initializeMedia may disconnect the responder, which will clear mMediaImpl. - // Make a local copy so we can call loadURI() afterwards. - - if (!mimeType.empty()) - { - if (initializeMedia(mimeType)) - { - loadURI(); - } - } - - } - else - { - LL_WARNS() << "LLViewerMediaImpl to be released." << LL_ENDL; - } - - this->unref(); -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::navigateStop() -{ - if(mMediaSource) - { - mMediaSource->browse_stop(); - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -bool LLViewerMediaImpl::handleKeyHere(KEY key, MASK mask) -{ - bool result = false; - - if (mMediaSource) - { - // FIXME: THIS IS SO WRONG. - // Menu keys should be handled by the menu system and not passed to UI elements, but this is how LLTextEditor and LLLineEditor do it... - if (MASK_CONTROL & mask && key != KEY_LEFT && key != KEY_RIGHT && key != KEY_HOME && key != KEY_END) - { - result = true; - } - - if (!result) - { - LLSD native_key_data = gViewerWindow->getWindow()->getNativeKeyData(); - result = mMediaSource->keyEvent(LLPluginClassMedia::KEY_EVENT_DOWN, key, mask, native_key_data); - } - } - - return result; -} - -////////////////////////////////////////////////////////////////////////////////////////// -bool LLViewerMediaImpl::handleKeyUpHere(KEY key, MASK mask) -{ - bool result = false; - - if (mMediaSource) - { - // FIXME: THIS IS SO WRONG. - // Menu keys should be handled by the menu system and not passed to UI elements, but this is how LLTextEditor and LLLineEditor do it... - if (MASK_CONTROL & mask && key != KEY_LEFT && key != KEY_RIGHT && key != KEY_HOME && key != KEY_END) - { - result = true; - } - - if (!result) - { - LLSD native_key_data = gViewerWindow->getWindow()->getNativeKeyData(); - result = mMediaSource->keyEvent(LLPluginClassMedia::KEY_EVENT_UP, key, mask, native_key_data); - } - } - - return result; -} - -////////////////////////////////////////////////////////////////////////////////////////// -bool LLViewerMediaImpl::handleUnicodeCharHere(llwchar uni_char) -{ - bool result = false; - - if (mMediaSource) - { - // only accept 'printable' characters, sigh... - if (uni_char >= 32 // discard 'control' characters - && uni_char != 127) // SDL thinks this is 'delete' - yuck. - { - LLSD native_key_data = gViewerWindow->getWindow()->getNativeKeyData(); - - mMediaSource->textInput(wstring_to_utf8str(LLWString(1, uni_char)), gKeyboard->currentMask(false), native_key_data); - } - } - - return result; -} - -////////////////////////////////////////////////////////////////////////////////////////// -bool LLViewerMediaImpl::canNavigateForward() -{ - bool result = false; - if (mMediaSource) - { - result = mMediaSource->getHistoryForwardAvailable(); - } - return result; -} - -////////////////////////////////////////////////////////////////////////////////////////// -bool LLViewerMediaImpl::canNavigateBack() -{ - bool result = false; - if (mMediaSource) - { - result = mMediaSource->getHistoryBackAvailable(); - } - return result; -} - -////////////////////////////////////////////////////////////////////////////////////////// -static LLTrace::BlockTimerStatHandle FTM_MEDIA_DO_UPDATE("Do Update"); -static LLTrace::BlockTimerStatHandle FTM_MEDIA_GET_DATA("Get Data"); -static LLTrace::BlockTimerStatHandle FTM_MEDIA_SET_SUBIMAGE("Set Subimage"); - - -void LLViewerMediaImpl::update() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA; //LL_RECORD_BLOCK_TIME(FTM_MEDIA_DO_UPDATE); - if(mMediaSource == NULL) - { - if(mPriority == LLPluginClassMedia::PRIORITY_UNLOADED) - { - // This media source should not be loaded. - } - else if(mPriority <= LLPluginClassMedia::PRIORITY_SLIDESHOW) - { - // Don't load new instances that are at PRIORITY_SLIDESHOW or below. They're just kept around to preserve state. - } - else if (!mMimeProbe.expired()) - { - // this media source is doing a MIME type probe -- don't try loading it again. - } - else - { - // This media may need to be loaded. - if(sMediaCreateTimer.hasExpired()) - { - LL_DEBUGS("PluginPriority") << this << ": creating media based on timer expiration" << LL_ENDL; - createMediaSource(); - sMediaCreateTimer.setTimerExpirySec(LLVIEWERMEDIA_CREATE_DELAY); - } - else - { - LL_DEBUGS("PluginPriority") << this << ": NOT creating media (waiting on timer)" << LL_ENDL; - } - } - } - else - { - updateVolume(); - - // TODO: this is updated every frame - is this bad? - // Removing this as part of the post viewer64 media update - // Removed as not implemented in CEF embedded browser - // See MAINT-8194 for a more fuller description - // updateJavascriptObject(); - } - - - if(mMediaSource == NULL) - { - return; - } - - // Make sure a navigate doesn't happen during the idle -- it can cause mMediaSource to get destroyed, which can cause a crash. - setNavigateSuspended(true); - - mMediaSource->idle(); - - setNavigateSuspended(false); - - if(mMediaSource == NULL) - { - return; - } - - if(mMediaSource->isPluginExited()) - { - resetPreviousMediaState(); - destroyMediaSource(); - return; - } - - if(!mMediaSource->textureValid()) - { - return; - } - - if(mSuspendUpdates || !mVisible) - { - return; - } - - - LLViewerMediaTexture* media_tex; - U8* data; - S32 data_width; - S32 data_height; - S32 x_pos; - S32 y_pos; - S32 width; - S32 height; - - if (preMediaTexUpdate(media_tex, data, data_width, data_height, x_pos, y_pos, width, height)) - { - // Push update to worker thread - auto main_queue = LLImageGLThread::sEnabledMedia ? mMainQueue.lock() : nullptr; - if (main_queue) - { - mTextureUpdatePending = true; - ref(); // protect texture from deletion while active on bg queue - media_tex->ref(); - main_queue->postTo( - mTexUpdateQueue, // Worker thread queue - [=]() // work done on update worker thread - { -#if LL_IMAGEGL_THREAD_CHECK - media_tex->getGLTexture()->mActiveThread = LLThread::currentID(); -#endif - doMediaTexUpdate(media_tex, data, data_width, data_height, x_pos, y_pos, width, height, true); - }, - [=]() // callback to main thread - { -#if LL_IMAGEGL_THREAD_CHECK - media_tex->getGLTexture()->mActiveThread = LLThread::currentID(); -#endif - mTextureUpdatePending = false; - media_tex->unref(); - unref(); - }); - } - else - { - doMediaTexUpdate(media_tex, data, data_width, data_height, x_pos, y_pos, width, height, false); // otherwise, update on main thread - } - } -} - -bool LLViewerMediaImpl::preMediaTexUpdate(LLViewerMediaTexture*& media_tex, U8*& data, S32& data_width, S32& data_height, S32& x_pos, S32& y_pos, S32& width, S32& height) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA; - - bool retval = false; - - if (!mTextureUpdatePending) - { - media_tex = updateMediaImage(); - - if (media_tex && mMediaSource) - { - LLRect dirty_rect; - S32 media_width = mMediaSource->getTextureWidth(); - S32 media_height = mMediaSource->getTextureHeight(); - //S32 media_depth = mMediaSource->getTextureDepth(); - - // Since we're updating this texture, we know it's playing. Tell the texture to do its replacement magic so it gets rendered. - media_tex->setPlaying(true); - - if (mMediaSource->getDirty(&dirty_rect)) - { - // Constrain the dirty rect to be inside the texture - x_pos = llmax(dirty_rect.mLeft, 0); - y_pos = llmax(dirty_rect.mBottom, 0); - width = llmin(dirty_rect.mRight, media_width) - x_pos; - height = llmin(dirty_rect.mTop, media_height) - y_pos; - - if (width > 0 && height > 0) - { - data = mMediaSource->getBitsData(); - data_width = mMediaSource->getWidth(); - data_height = mMediaSource->getHeight(); - - if (data != NULL) - { - // data is ready to be copied to GL - retval = true; - } - } - - mMediaSource->resetDirty(); - } - } - } - - return retval; -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::doMediaTexUpdate(LLViewerMediaTexture* media_tex, U8* data, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height, bool sync) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA; - LLMutexLock lock(&mLock); // don't allow media source tear-down during update - - // wrap "data" in an LLImageRaw but do NOT make a copy - LLPointer raw = new LLImageRaw(data, media_tex->getWidth(), media_tex->getHeight(), media_tex->getComponents(), true); - - // *NOTE: Recreating the GL texture each media update may seem wasteful - // (note the texture creation in preMediaTexUpdate), however, it apparently - // prevents GL calls from blocking, due to poor bookkeeping of state of - // updated textures by the OpenGL implementation. (Windows 10/Nvidia) - // -Cosmic,2023-04-04 - // Allocate GL texture based on LLImageRaw but do NOT copy to GL - LLGLuint tex_name = 0; - media_tex->createGLTexture(0, raw, 0, true, LLGLTexture::OTHER, true, &tex_name); - - // copy just the subimage covered by the image raw to GL - media_tex->setSubImage(data, data_width, data_height, x_pos, y_pos, width, height, tex_name); - - if (sync) - { - media_tex->getGLTexture()->syncToMainThread(tex_name); - } - else - { - media_tex->getGLTexture()->syncTexName(tex_name); - } - - // release the data pointer before freeing raw so LLImageRaw destructor doesn't - // free memory at data pointer - raw->releaseData(); -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::updateImagesMediaStreams() -{ -} - -////////////////////////////////////////////////////////////////////////////////////////// -LLViewerMediaTexture* LLViewerMediaImpl::updateMediaImage() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA; - llassert(!gCubeSnapshot); - if (!mMediaSource) - { - return nullptr; // not ready for updating - } - - //llassert(!mTextureId.isNull()); - // *TODO: Consider enabling mipmaps (they have been disabled for a long time). Likely has a significant performance impact for tiled/high texture repeat media. Mip generation in a shader may also be an option if necessary. - LLViewerMediaTexture* media_tex = LLViewerTextureManager::getMediaTexture( mTextureId, USE_MIPMAPS ); - - if ( mNeedsNewTexture - || (media_tex->getWidth() != mMediaSource->getTextureWidth()) - || (media_tex->getHeight() != mMediaSource->getTextureHeight()) - || (mTextureUsedWidth != mMediaSource->getWidth()) - || (mTextureUsedHeight != mMediaSource->getHeight()) - ) - { - LL_DEBUGS("Media") << "initializing media placeholder" << LL_ENDL; - LL_DEBUGS("Media") << "movie image id " << mTextureId << LL_ENDL; - - int texture_width = mMediaSource->getTextureWidth(); - int texture_height = mMediaSource->getTextureHeight(); - int texture_depth = mMediaSource->getTextureDepth(); - - // MEDIAOPT: check to see if size actually changed before doing work - media_tex->destroyGLTexture(); - - // MEDIAOPT: seems insane that we actually have to make an imageraw then - // immediately discard it - LLPointer raw = new LLImageRaw(texture_width, texture_height, texture_depth); - // Clear the texture to the background color, ignoring alpha. - // convert background color channels from [0.0, 1.0] to [0, 255]; - raw->clear(int(mBackgroundColor.mV[VX] * 255.0f), int(mBackgroundColor.mV[VY] * 255.0f), int(mBackgroundColor.mV[VZ] * 255.0f), 0xff); - - // ask media source for correct GL image format constants - media_tex->setExplicitFormat(mMediaSource->getTextureFormatInternal(), - mMediaSource->getTextureFormatPrimary(), - mMediaSource->getTextureFormatType(), - mMediaSource->getTextureFormatSwapBytes()); - - int discard_level = 0; - media_tex->createGLTexture(discard_level, raw); - - // MEDIAOPT: set this dynamically on play/stop - // FIXME -// media_tex->mIsMediaTexture = true; - mNeedsNewTexture = false; - - // If the amount of the texture being drawn by the media goes down in either width or height, - // recreate the texture to avoid leaving parts of the old image behind. - mTextureUsedWidth = mMediaSource->getWidth(); - mTextureUsedHeight = mMediaSource->getHeight(); - } - return media_tex; -} - - -////////////////////////////////////////////////////////////////////////////////////////// -LLUUID LLViewerMediaImpl::getMediaTextureID() const -{ - return mTextureId; -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::setVisible(bool visible) -{ - mVisible = visible; - - if(mVisible) - { - if(mMediaSource && mMediaSource->isPluginExited()) - { - destroyMediaSource(); - } - - if(!mMediaSource) - { - createMediaSource(); - } - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::mouseCapture() -{ - gFocusMgr.setMouseCapture(this); -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::scaleMouse(S32 *mouse_x, S32 *mouse_y) -{ -#if 0 - S32 media_width, media_height; - S32 texture_width, texture_height; - getMediaSize( &media_width, &media_height ); - getTextureSize( &texture_width, &texture_height ); - S32 y_delta = texture_height - media_height; - - *mouse_y -= y_delta; -#endif -} - - - -////////////////////////////////////////////////////////////////////////////////////////// -bool LLViewerMediaImpl::isMediaTimeBased() -{ - bool result = false; - - if(mMediaSource) - { - result = mMediaSource->pluginSupportsMediaTime(); - } - - return result; -} - -////////////////////////////////////////////////////////////////////////////////////////// -bool LLViewerMediaImpl::isMediaPlaying() -{ - bool result = false; - - if(mMediaSource) - { - EMediaStatus status = mMediaSource->getStatus(); - if(status == MEDIA_PLAYING || status == MEDIA_LOADING) - result = true; - } - - return result; -} -////////////////////////////////////////////////////////////////////////////////////////// -bool LLViewerMediaImpl::isMediaPaused() -{ - bool result = false; - - if(mMediaSource) - { - if(mMediaSource->getStatus() == MEDIA_PAUSED) - result = true; - } - - return result; -} - -////////////////////////////////////////////////////////////////////////////////////////// -// -bool LLViewerMediaImpl::hasMedia() const -{ - return mMediaSource != NULL; -} - -////////////////////////////////////////////////////////////////////////////////////////// -// -void LLViewerMediaImpl::resetPreviousMediaState() -{ - mPreviousMediaState = MEDIA_NONE; - mPreviousMediaTime = 0.0f; -} - - -////////////////////////////////////////////////////////////////////////////////////////// -// -void LLViewerMediaImpl::setDisabled(bool disabled, bool forcePlayOnEnable) -{ - if(mIsDisabled != disabled) - { - // Only do this on actual state transitions. - mIsDisabled = disabled; - - if(mIsDisabled) - { - // We just disabled this media. Clear all state. - unload(); - } - else - { - // We just (re)enabled this media. Do a navigate if auto-play is in order. - if(isAutoPlayable() || forcePlayOnEnable) - { - navigateTo(mMediaEntryURL, "", true, true); - } - } - - } -}; - -////////////////////////////////////////////////////////////////////////////////////////// -// -bool LLViewerMediaImpl::isForcedUnloaded() const -{ - if(mIsMuted || mMediaSourceFailed || mIsDisabled) - { - return true; - } - - // If this media's class is not supposed to be shown, unload - if (!shouldShowBasedOnClass() || isObscured()) - { - return true; - } - - return false; -} - -////////////////////////////////////////////////////////////////////////////////////////// -// -bool LLViewerMediaImpl::isPlayable() const -{ - if(isForcedUnloaded()) - { - // All of the forced-unloaded criteria also imply not playable. - return false; - } - - if(hasMedia()) - { - // Anything that's already playing is, by definition, playable. - return true; - } - - if(!mMediaURL.empty()) - { - // If something has navigated the instance, it's ready to be played. - return true; - } - - return false; -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::handleMediaEvent(LLPluginClassMedia* plugin, LLPluginClassMediaOwner::EMediaEvent event) -{ - bool pass_through = true; - switch(event) - { - case MEDIA_EVENT_CLICK_LINK_NOFOLLOW: - { - LL_DEBUGS("Media") << "MEDIA_EVENT_CLICK_LINK_NOFOLLOW, uri is: " << plugin->getClickURL() << LL_ENDL; - std::string url = plugin->getClickURL(); - std::string nav_type = plugin->getClickNavType(); - LLURLDispatcher::dispatch(url, nav_type, NULL, mTrustedBrowser); - } - break; - case MEDIA_EVENT_CLICK_LINK_HREF: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CLICK_LINK_HREF, target is \"" << plugin->getClickTarget() << "\", uri is " << plugin->getClickURL() << LL_ENDL; - }; - break; - case MEDIA_EVENT_PLUGIN_FAILED_LAUNCH: - { - // The plugin failed to load properly. Make sure the timer doesn't retry. - // TODO: maybe mark this plugin as not loadable somehow? - mMediaSourceFailed = true; - - // Reset the last known state of the media to defaults. - resetPreviousMediaState(); - - // TODO: may want a different message for this case? - LLSD args; - args["PLUGIN"] = LLMIMETypes::implType(mCurrentMimeType); - LLNotificationsUtil::add("MediaPluginFailed", args); - } - break; - - case MEDIA_EVENT_PLUGIN_FAILED: - { - // The plugin crashed. - mMediaSourceFailed = true; - - // Reset the last known state of the media to defaults. - resetPreviousMediaState(); - - LLSD args; - args["PLUGIN"] = LLMIMETypes::implType(mCurrentMimeType); - // SJB: This is getting called every frame if the plugin fails to load, continuously respawining the alert! - //LLNotificationsUtil::add("MediaPluginFailed", args); - } - break; - - case MEDIA_EVENT_CURSOR_CHANGED: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CURSOR_CHANGED, new cursor is " << plugin->getCursorName() << LL_ENDL; - - std::string cursor = plugin->getCursorName(); - mLastSetCursor = getCursorFromString(cursor); - } - break; - - case LLViewerMediaObserver::MEDIA_EVENT_FILE_DOWNLOAD: - { - LL_DEBUGS("Media") << "Media event - file download requested - filename is " << plugin->getFileDownloadFilename() << LL_ENDL; - } - break; - - case LLViewerMediaObserver::MEDIA_EVENT_NAVIGATE_BEGIN: - { - LL_DEBUGS("Media") << "MEDIA_EVENT_NAVIGATE_BEGIN, uri is: " << plugin->getNavigateURI() << LL_ENDL; - hideNotification(); - - if(getNavState() == MEDIANAVSTATE_SERVER_SENT) - { - setNavState(MEDIANAVSTATE_SERVER_BEGUN); - } - else - { - setNavState(MEDIANAVSTATE_BEGUN); - } - } - break; - - case LLViewerMediaObserver::MEDIA_EVENT_NAVIGATE_COMPLETE: - { - LL_DEBUGS("Media") << "MEDIA_EVENT_NAVIGATE_COMPLETE, uri is: " << plugin->getNavigateURI() << LL_ENDL; - - std::string url = plugin->getNavigateURI(); - if(getNavState() == MEDIANAVSTATE_BEGUN) - { - if(mCurrentMediaURL == url) - { - // This is a navigate that takes us to the same url as the previous navigate. - setNavState(MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED_SPURIOUS); - } - else - { - mCurrentMediaURL = url; - setNavState(MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED); - } - } - else if(getNavState() == MEDIANAVSTATE_SERVER_BEGUN) - { - mCurrentMediaURL = url; - setNavState(MEDIANAVSTATE_SERVER_COMPLETE_BEFORE_LOCATION_CHANGED); - } - else - { - // all other cases need to leave the state alone. - } - } - break; - - case LLViewerMediaObserver::MEDIA_EVENT_LOCATION_CHANGED: - { - LL_DEBUGS("Media") << "MEDIA_EVENT_LOCATION_CHANGED, uri is: " << plugin->getLocation() << LL_ENDL; - - std::string url = plugin->getLocation(); - - if(getNavState() == MEDIANAVSTATE_BEGUN) - { - if(mCurrentMediaURL == url) - { - // This is a navigate that takes us to the same url as the previous navigate. - setNavState(MEDIANAVSTATE_FIRST_LOCATION_CHANGED_SPURIOUS); - } - else - { - mCurrentMediaURL = url; - setNavState(MEDIANAVSTATE_FIRST_LOCATION_CHANGED); - } - } - else if(getNavState() == MEDIANAVSTATE_SERVER_BEGUN) - { - mCurrentMediaURL = url; - setNavState(MEDIANAVSTATE_SERVER_FIRST_LOCATION_CHANGED); - } - else - { - bool internal_nav = false; - if (url != mCurrentMediaURL) - { - // Check if it is internal navigation - // Note: Not sure if we should detect internal navigations as 'address change', - // but they are not redirects and do not cause NAVIGATE_BEGIN (also see SL-1005) - size_t pos = url.find("#"); - if (pos != std::string::npos) - { - // assume that new link always have '#', so this is either - // transfer from 'link#1' to 'link#2' or from link to 'link#2' - // filter out cases like 'redirect?link' - std::string base_url = url.substr(0, pos); - pos = mCurrentMediaURL.find(base_url); - if (pos == 0) - { - // base link hasn't changed - internal_nav = true; - } - } - } - - if (internal_nav) - { - // Internal navigation by '#' - mCurrentMediaURL = url; - setNavState(MEDIANAVSTATE_FIRST_LOCATION_CHANGED); - } - else - { - // Don't track redirects. - setNavState(MEDIANAVSTATE_NONE); - } - } - } - break; - - case LLViewerMediaObserver::MEDIA_EVENT_PICK_FILE_REQUEST: - { - LL_DEBUGS("Media") << "Media event - file pick requested." << LL_ENDL; - - init_threaded_picker_load_dialog(plugin, LLFilePicker::FFLOAD_ALL, plugin->getIsMultipleFilePick()); - } - break; - - case LLViewerMediaObserver::MEDIA_EVENT_AUTH_REQUEST: - { - LLNotification::Params auth_request_params; - auth_request_params.name = "AuthRequest"; - - // pass in host name and realm for site (may be zero length but will always exist) - LLSD args; - LLURL raw_url( plugin->getAuthURL().c_str() ); - args["HOST_NAME"] = raw_url.getAuthority(); - args["REALM"] = plugin->getAuthRealm(); - auth_request_params.substitutions = args; - - auth_request_params.payload = LLSD().with("media_id", mTextureId); - auth_request_params.functor.function = boost::bind(&LLViewerMedia::authSubmitCallback, _1, _2); - LLNotifications::instance().add(auth_request_params); - }; - break; - - case LLViewerMediaObserver::MEDIA_EVENT_CLOSE_REQUEST: - { - std::string uuid = plugin->getClickUUID(); - - LL_INFOS() << "MEDIA_EVENT_CLOSE_REQUEST for uuid " << uuid << LL_ENDL; - - if(uuid.empty()) - { - // This close request is directed at this instance, let it fall through. - } - else - { - // This close request is directed at another instance - pass_through = false; - LLFloaterWebContent::closeRequest(uuid); - } - } - break; - - case LLViewerMediaObserver::MEDIA_EVENT_GEOMETRY_CHANGE: - { - std::string uuid = plugin->getClickUUID(); - - LL_INFOS() << "MEDIA_EVENT_GEOMETRY_CHANGE for uuid " << uuid << LL_ENDL; - - if(uuid.empty()) - { - // This geometry change request is directed at this instance, let it fall through. - } - else - { - // This request is directed at another instance - pass_through = false; - LLFloaterWebContent::geometryChanged(uuid, plugin->getGeometryX(), plugin->getGeometryY(), plugin->getGeometryWidth(), plugin->getGeometryHeight()); - } - } - break; - - default: - break; - } - - if(pass_through) - { - // Just chain the event to observers. - emitEvent(plugin, event); - } -} - -//////////////////////////////////////////////////////////////////////////////// -// virtual -void -LLViewerMediaImpl::cut() -{ - if (mMediaSource) - mMediaSource->cut(); -} - -//////////////////////////////////////////////////////////////////////////////// -// virtual -bool -LLViewerMediaImpl::canCut() const -{ - if (mMediaSource) - return mMediaSource->canCut(); - else - return false; -} - -//////////////////////////////////////////////////////////////////////////////// -// virtual -void -LLViewerMediaImpl::copy() -{ - if (mMediaSource) - mMediaSource->copy(); -} - -//////////////////////////////////////////////////////////////////////////////// -// virtual -bool -LLViewerMediaImpl::canCopy() const -{ - if (mMediaSource) - return mMediaSource->canCopy(); - else - return false; -} - -//////////////////////////////////////////////////////////////////////////////// -// virtual -void -LLViewerMediaImpl::paste() -{ - if (mMediaSource) - mMediaSource->paste(); -} - -//////////////////////////////////////////////////////////////////////////////// -// virtual -bool -LLViewerMediaImpl::canPaste() const -{ - if (mMediaSource) - return mMediaSource->canPaste(); - else - return false; -} - -void LLViewerMediaImpl::setUpdated(bool updated) -{ - mIsUpdated = updated ; -} - -bool LLViewerMediaImpl::isUpdated() -{ - return mIsUpdated ; -} - -static LLTrace::BlockTimerStatHandle FTM_MEDIA_CALCULATE_INTEREST("Calculate Interest"); - -void LLViewerMediaImpl::calculateInterest() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA; //LL_RECORD_BLOCK_TIME(FTM_MEDIA_CALCULATE_INTEREST); - LLViewerMediaTexture* texture = LLViewerTextureManager::findMediaTexture( mTextureId ); - - llassert(!gCubeSnapshot); - - if(texture != NULL) - { - mInterest = texture->getMaxVirtualSize(); - } - else - { - // This will be a relatively common case now, since it will always be true for unloaded media. - mInterest = 0.0f; - } - - // Calculate distance from the avatar, for use in the proximity calculation. - mProximityDistance = 0.0f; - mProximityCamera = 0.0f; - if(!mObjectList.empty()) - { - // Just use the first object in the list. We could go through the list and find the closest object, but this should work well enough. - std::list< LLVOVolume* >::iterator iter = mObjectList.begin() ; - LLVOVolume* objp = *iter ; - llassert_always(objp != NULL) ; - - // The distance calculation is invalid for HUD attachments -- leave both mProximityDistance and mProximityCamera at 0 for them. - if(!objp->isHUDAttachment()) - { - LLVector3d obj_global = objp->getPositionGlobal() ; - LLVector3d agent_global = gAgent.getPositionGlobal() ; - LLVector3d global_delta = agent_global - obj_global ; - mProximityDistance = global_delta.magVecSquared(); // use distance-squared because it's cheaper and sorts the same. - - static LLUICachedControl mEarLocation("MediaSoundsEarLocation", 0); - LLVector3d ear_position; - switch(mEarLocation) - { - case 0: - default: - ear_position = gAgentCamera.getCameraPositionGlobal(); - break; - - case 1: - ear_position = agent_global; - break; - } - LLVector3d camera_delta = ear_position - obj_global; - mProximityCamera = camera_delta.magVec(); - } - } - - if(mNeedsMuteCheck) - { - // Check all objects this instance is associated with, and those objects' owners, against the mute list - mIsMuted = false; - - std::list< LLVOVolume* >::iterator iter = mObjectList.begin() ; - for(; iter != mObjectList.end() ; ++iter) - { - LLVOVolume *obj = *iter; - llassert(obj); - if (!obj) continue; - if(LLMuteList::getInstance() && - LLMuteList::getInstance()->isMuted(obj->getID())) - { - mIsMuted = true; - } - else - { - // We won't have full permissions data for all objects. Attempt to mute objects when we can tell their owners are muted. - if (LLSelectMgr::getInstance()) - { - LLPermissions* obj_perm = LLSelectMgr::getInstance()->findObjectPermissions(obj); - if(obj_perm) - { - if(LLMuteList::getInstance() && - LLMuteList::getInstance()->isMuted(obj_perm->getOwner())) - mIsMuted = true; - } - } - } - } - - mNeedsMuteCheck = false; - } -} - -F64 LLViewerMediaImpl::getApproximateTextureInterest() -{ - F64 result = 0.0f; - - if(mMediaSource) - { - result = mMediaSource->getFullWidth(); - result *= mMediaSource->getFullHeight(); - } - else - { - // No media source is loaded -- all we have to go on is the texture size that has been set on the impl, if any. - result = mMediaWidth; - result *= mMediaHeight; - } - - return result; -} - -void LLViewerMediaImpl::setUsedInUI(bool used_in_ui) -{ - mUsedInUI = used_in_ui; - - // HACK: Force elements used in UI to load right away. - // This fixes some issues where UI code that uses the browser instance doesn't expect it to be unloaded. - if(mUsedInUI && (mPriority == LLPluginClassMedia::PRIORITY_UNLOADED)) - { - if(getVisible()) - { - setPriority(LLPluginClassMedia::PRIORITY_NORMAL); - } - else - { - setPriority(LLPluginClassMedia::PRIORITY_HIDDEN); - } - - createMediaSource(); - } -}; - -void LLViewerMediaImpl::setBackgroundColor(LLColor4 color) -{ - mBackgroundColor = color; - - if(mMediaSource) - { - mMediaSource->setBackgroundColor(mBackgroundColor); - } -}; - -F64 LLViewerMediaImpl::getCPUUsage() const -{ - F64 result = 0.0f; - - if(mMediaSource) - { - result = mMediaSource->getCPUUsage(); - } - - return result; -} - -void LLViewerMediaImpl::setPriority(LLPluginClassMedia::EPriority priority) -{ - if(mPriority != priority) - { - LL_DEBUGS("PluginPriority") - << "changing priority of media id " << mTextureId - << " from " << LLPluginClassMedia::priorityToString(mPriority) - << " to " << LLPluginClassMedia::priorityToString(priority) - << LL_ENDL; - } - - mPriority = priority; - - if(priority == LLPluginClassMedia::PRIORITY_UNLOADED) - { - if(mMediaSource) - { - // Need to unload the media source - - // First, save off previous media state - mPreviousMediaState = mMediaSource->getStatus(); - mPreviousMediaTime = mMediaSource->getCurrentTime(); - - destroyMediaSource(); - } - } - - if(mMediaSource) - { - mMediaSource->setPriority(mPriority); - } - - // NOTE: loading (or reloading) media sources whose priority has risen above PRIORITY_UNLOADED is done in update(). -} - -void LLViewerMediaImpl::setLowPrioritySizeLimit(int size) -{ - if(mMediaSource) - { - mMediaSource->setLowPrioritySizeLimit(size); - } -} - -void LLViewerMediaImpl::setNavState(EMediaNavState state) -{ - mMediaNavState = state; - - switch (state) - { - case MEDIANAVSTATE_NONE: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_NONE" << LL_ENDL; break; - case MEDIANAVSTATE_BEGUN: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_BEGUN" << LL_ENDL; break; - case MEDIANAVSTATE_FIRST_LOCATION_CHANGED: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_FIRST_LOCATION_CHANGED" << LL_ENDL; break; - case MEDIANAVSTATE_FIRST_LOCATION_CHANGED_SPURIOUS: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_FIRST_LOCATION_CHANGED_SPURIOUS" << LL_ENDL; break; - case MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED" << LL_ENDL; break; - case MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED_SPURIOUS: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED_SPURIOUS" << LL_ENDL; break; - case MEDIANAVSTATE_SERVER_SENT: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_SERVER_SENT" << LL_ENDL; break; - case MEDIANAVSTATE_SERVER_BEGUN: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_SERVER_BEGUN" << LL_ENDL; break; - case MEDIANAVSTATE_SERVER_FIRST_LOCATION_CHANGED: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_SERVER_FIRST_LOCATION_CHANGED" << LL_ENDL; break; - case MEDIANAVSTATE_SERVER_COMPLETE_BEFORE_LOCATION_CHANGED: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_SERVER_COMPLETE_BEFORE_LOCATION_CHANGED" << LL_ENDL; break; - } -} - -void LLViewerMediaImpl::setNavigateSuspended(bool suspend) -{ - if(mNavigateSuspended != suspend) - { - mNavigateSuspended = suspend; - if(!suspend) - { - // We're coming out of suspend. If someone tried to do a navigate while suspended, do one now instead. - if(mNavigateSuspendedDeferred) - { - mNavigateSuspendedDeferred = false; - navigateInternal(); - } - } - } -} - -void LLViewerMediaImpl::cancelMimeTypeProbe() -{ - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t probeAdapter = mMimeProbe.lock(); - - if (probeAdapter) - probeAdapter->cancelSuspendedOperation(); - -} - -void LLViewerMediaImpl::addObject(LLVOVolume* obj) -{ - std::list< LLVOVolume* >::iterator iter = mObjectList.begin() ; - for(; iter != mObjectList.end() ; ++iter) - { - if(*iter == obj) - { - return ; //already in the list. - } - } - - mObjectList.push_back(obj) ; - mNeedsMuteCheck = true; -} - -void LLViewerMediaImpl::removeObject(LLVOVolume* obj) -{ - mObjectList.remove(obj) ; - mNeedsMuteCheck = true; -} - -const std::list< LLVOVolume* >* LLViewerMediaImpl::getObjectList() const -{ - return &mObjectList ; -} - -LLVOVolume *LLViewerMediaImpl::getSomeObject() -{ - LLVOVolume *result = NULL; - - std::list< LLVOVolume* >::iterator iter = mObjectList.begin() ; - if(iter != mObjectList.end()) - { - result = *iter; - } - - return result; -} - -void LLViewerMediaImpl::setTextureID(LLUUID id) -{ - if(id != mTextureId) - { - if(mTextureId.notNull()) - { - // Remove this item's entry from the map - sViewerMediaTextureIDMap.erase(mTextureId); - } - - if(id.notNull()) - { - sViewerMediaTextureIDMap.insert(LLViewerMedia::impl_id_map::value_type(id, this)); - } - - mTextureId = id; - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -// -bool LLViewerMediaImpl::isAutoPlayable() const -{ - return (mMediaAutoPlay && - gSavedSettings.getS32("ParcelMediaAutoPlayEnable") != 0 && - gSavedSettings.getBOOL("MediaTentativeAutoPlay")); -} - -////////////////////////////////////////////////////////////////////////////////////////// -// -bool LLViewerMediaImpl::shouldShowBasedOnClass() const -{ - // If this is parcel media or in the UI, return true always - if (getUsedInUI() || isParcelMedia()) return true; - - bool attached_to_another_avatar = isAttachedToAnotherAvatar(); - bool inside_parcel = isInAgentParcel(); - - // LL_INFOS() << " hasFocus = " << hasFocus() << - // " others = " << (attached_to_another_avatar && gSavedSettings.getBOOL(LLViewerMedia::SHOW_MEDIA_ON_OTHERS_SETTING)) << - // " within = " << (inside_parcel && gSavedSettings.getBOOL(LLViewerMedia::SHOW_MEDIA_WITHIN_PARCEL_SETTING)) << - // " outside = " << (!inside_parcel && gSavedSettings.getBOOL(LLViewerMedia::SHOW_MEDIA_OUTSIDE_PARCEL_SETTING)) << LL_ENDL; - - // If it has focus, we should show it - // This is incorrect, and causes EXT-6750 (disabled attachment media still plays) -// if (hasFocus()) -// return true; - - // If it is attached to an avatar and the pref is off, we shouldn't show it - if (attached_to_another_avatar) - { - static LLCachedControl show_media_on_others(gSavedSettings, LLViewerMedia::SHOW_MEDIA_ON_OTHERS_SETTING, false); - return show_media_on_others; - } - if (inside_parcel) - { - static LLCachedControl show_media_within_parcel(gSavedSettings, LLViewerMedia::SHOW_MEDIA_WITHIN_PARCEL_SETTING, true); - - return show_media_within_parcel; - } - else - { - static LLCachedControl show_media_outside_parcel(gSavedSettings, LLViewerMedia::SHOW_MEDIA_OUTSIDE_PARCEL_SETTING, true); - - return show_media_outside_parcel; - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -// -bool LLViewerMediaImpl::isObscured() const -{ - if (getUsedInUI() || isParcelMedia() || isAttachedToHUD()) return false; - - LLParcel* agent_parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - if (!agent_parcel) - { - return false; - } - - if (agent_parcel->getObscureMOAP() && !isInAgentParcel()) - { - return true; - } - - return false; -} - -bool LLViewerMediaImpl::isAttachedToHUD() const -{ - std::list< LLVOVolume* >::const_iterator iter = mObjectList.begin(); - std::list< LLVOVolume* >::const_iterator end = mObjectList.end(); - for ( ; iter != end; iter++) - { - if ((*iter)->isHUDAttachment()) - { - return true; - } - } - return false; -} - -////////////////////////////////////////////////////////////////////////////////////////// -// -bool LLViewerMediaImpl::isAttachedToAnotherAvatar() const -{ - bool result = false; - - std::list< LLVOVolume* >::const_iterator iter = mObjectList.begin(); - std::list< LLVOVolume* >::const_iterator end = mObjectList.end(); - for ( ; iter != end; iter++) - { - if (isObjectAttachedToAnotherAvatar(*iter)) - { - result = true; - break; - } - } - return result; -} - -////////////////////////////////////////////////////////////////////////////////////////// -// -//static -bool LLViewerMediaImpl::isObjectAttachedToAnotherAvatar(LLVOVolume *obj) -{ - bool result = false; - LLXform *xform = obj; - // Walk up parent chain - while (NULL != xform) - { - LLViewerObject *object = dynamic_cast (xform); - if (NULL != object) - { - LLVOAvatar *avatar = object->asAvatar(); - if ((NULL != avatar) && (avatar != gAgentAvatarp)) - { - result = true; - break; - } - } - xform = xform->getParent(); - } - return result; -} - -////////////////////////////////////////////////////////////////////////////////////////// -// -bool LLViewerMediaImpl::isInAgentParcel() const -{ - bool result = false; - - std::list< LLVOVolume* >::const_iterator iter = mObjectList.begin(); - std::list< LLVOVolume* >::const_iterator end = mObjectList.end(); - for ( ; iter != end; iter++) - { - LLVOVolume *object = *iter; - if (LLViewerMediaImpl::isObjectInAgentParcel(object)) - { - result = true; - break; - } - } - return result; -} - -LLNotificationPtr LLViewerMediaImpl::getCurrentNotification() const -{ - return mNotification; -} - -////////////////////////////////////////////////////////////////////////////////////////// -// -// static -bool LLViewerMediaImpl::isObjectInAgentParcel(LLVOVolume *obj) -{ - return (LLViewerParcelMgr::getInstance()->inAgentParcel(obj->getPositionGlobal())); -} +/** + * @file llviewermedia.cpp + * @brief Client interface to the media engine + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llviewermedia.h" + +#include "llagent.h" +#include "llagentcamera.h" +#include "llappviewer.h" +#include "llaudioengine.h" // for gAudiop +#include "llcallbacklist.h" +#include "lldir.h" +#include "lldiriterator.h" +#include "llevent.h" // LLSimpleListener +#include "llfilepicker.h" +#include "llfloaterwebcontent.h" // for handling window close requests and geometry change requests in media browser windows. +#include "llfocusmgr.h" +#include "llimagegl.h" +#include "llkeyboard.h" +#include "lllogininstance.h" +#include "llmarketplacefunctions.h" +#include "llmediaentry.h" +#include "llmimetypes.h" +#include "llmutelist.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "llavataractions.h" +#include "llparcel.h" +#include "llpluginclassmedia.h" +#include "llurldispatcher.h" +#include "lluuid.h" +#include "llversioninfo.h" +#include "llviewermediafocus.h" +#include "llviewercontrol.h" +#include "llviewermenufile.h" // LLFilePickerThread +#include "llviewernetwork.h" +#include "llviewerparcelaskplay.h" +#include "llviewerparcelmedia.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "llviewertexture.h" +#include "llviewertexturelist.h" +#include "llviewerwindow.h" +#include "llvoavatar.h" +#include "llvoavatarself.h" +#include "llvovolume.h" +#include "llfloaterreg.h" +#include "llwebprofile.h" +#include "llwindow.h" +#include "llvieweraudio.h" +#include "llcorehttputil.h" + +#include "llfloaterwebcontent.h" // for handling window close requests and geometry change requests in media browser windows. + +#include // for SkinFolder listener +#include + +extern bool gCubeSnapshot; + +// *TODO: Consider enabling mipmaps (they have been disabled for a long time). Likely has a significant performance impact for tiled/high texture repeat media. Mip generation in a shader may also be an option if necessary. +constexpr bool USE_MIPMAPS = false; + +void init_threaded_picker_load_dialog(LLPluginClassMedia* plugin, LLFilePicker::ELoadFilter filter, bool get_multiple) +{ + (new LLMediaFilePicker(plugin, filter, get_multiple))->getFile(); // will delete itself +} + +/////////////////////////////////////////////////////////////////////////////// + +// Move this to its own file. + +LLViewerMediaEventEmitter::~LLViewerMediaEventEmitter() +{ + observerListType::iterator iter = mObservers.begin(); + + while( iter != mObservers.end() ) + { + LLViewerMediaObserver *self = *iter; + iter++; + remObserver(self); + } +} + +/////////////////////////////////////////////////////////////////////////////// +// +bool LLViewerMediaEventEmitter::addObserver( LLViewerMediaObserver* observer ) +{ + if ( ! observer ) + return false; + + if ( std::find( mObservers.begin(), mObservers.end(), observer ) != mObservers.end() ) + return false; + + mObservers.push_back( observer ); + observer->mEmitters.push_back( this ); + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// +bool LLViewerMediaEventEmitter::remObserver( LLViewerMediaObserver* observer ) +{ + if ( ! observer ) + return false; + + mObservers.remove( observer ); + observer->mEmitters.remove(this); + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// +void LLViewerMediaEventEmitter::emitEvent( LLPluginClassMedia* media, LLViewerMediaObserver::EMediaEvent event ) +{ + // Broadcast the event to any observers. + observerListType::iterator iter = mObservers.begin(); + while( iter != mObservers.end() ) + { + LLViewerMediaObserver *self = *iter; + ++iter; + self->handleMediaEvent( media, event ); + } +} + +// Move this to its own file. +LLViewerMediaObserver::~LLViewerMediaObserver() +{ + std::list::iterator iter = mEmitters.begin(); + + while( iter != mEmitters.end() ) + { + LLViewerMediaEventEmitter *self = *iter; + iter++; + self->remObserver( this ); + } +} + + +static LLViewerMedia::impl_list sViewerMediaImplList; +static LLViewerMedia::impl_id_map sViewerMediaTextureIDMap; +static LLTimer sMediaCreateTimer; +static const F32 LLVIEWERMEDIA_CREATE_DELAY = 1.0f; +static F32 sGlobalVolume = 1.0f; +static bool sForceUpdate = false; +static LLUUID sOnlyAudibleTextureID = LLUUID::null; +static F64 sLowestLoadableImplInterest = 0.0f; + +////////////////////////////////////////////////////////////////////////////////////////// +static void add_media_impl(LLViewerMediaImpl* media) +{ + sViewerMediaImplList.push_back(media); +} + +////////////////////////////////////////////////////////////////////////////////////////// +static void remove_media_impl(LLViewerMediaImpl* media) +{ + LLViewerMedia::impl_list::iterator iter = sViewerMediaImplList.begin(); + LLViewerMedia::impl_list::iterator end = sViewerMediaImplList.end(); + + for(; iter != end; iter++) + { + if(media == *iter) + { + sViewerMediaImplList.erase(iter); + return; + } + } +} + +class LLViewerMediaMuteListObserver : public LLMuteListObserver +{ + /* virtual */ void onChange() { LLViewerMedia::getInstance()->muteListChanged();} +}; + +static LLViewerMediaMuteListObserver sViewerMediaMuteListObserver; +static bool sViewerMediaMuteListObserverInitialized = false; + + +////////////////////////////////////////////////////////////////////////////////////////// +// LLViewerMedia +////////////////////////////////////////////////////////////////////////////////////////// + +/*static*/ const char* LLViewerMedia::AUTO_PLAY_MEDIA_SETTING = "ParcelMediaAutoPlayEnable"; +/*static*/ const char* LLViewerMedia::SHOW_MEDIA_ON_OTHERS_SETTING = "MediaShowOnOthers"; +/*static*/ const char* LLViewerMedia::SHOW_MEDIA_WITHIN_PARCEL_SETTING = "MediaShowWithinParcel"; +/*static*/ const char* LLViewerMedia::SHOW_MEDIA_OUTSIDE_PARCEL_SETTING = "MediaShowOutsideParcel"; + +LLViewerMedia::LLViewerMedia(): +mAnyMediaShowing(false), +mAnyMediaPlaying(false), +mSpareBrowserMediaSource(NULL) +{ +} + +LLViewerMedia::~LLViewerMedia() +{ + gIdleCallbacks.deleteFunction(LLViewerMedia::onIdle, NULL); + mTeleportFinishConnection.disconnect(); + if (mSpareBrowserMediaSource != NULL) + { + delete mSpareBrowserMediaSource; + mSpareBrowserMediaSource = NULL; + } +} + +// static +void LLViewerMedia::initSingleton() +{ + gIdleCallbacks.addFunction(LLViewerMedia::onIdle, NULL); + mTeleportFinishConnection = LLViewerParcelMgr::getInstance()-> + setTeleportFinishedCallback(boost::bind(&LLViewerMedia::onTeleportFinished, this)); +} + +////////////////////////////////////////////////////////////////////////////////////////// +viewer_media_t LLViewerMedia::newMediaImpl( + const LLUUID& texture_id, + S32 media_width, + S32 media_height, + U8 media_auto_scale, + U8 media_loop) +{ + LLViewerMediaImpl* media_impl = getMediaImplFromTextureID(texture_id); + if(media_impl == NULL || texture_id.isNull()) + { + // Create the media impl + media_impl = new LLViewerMediaImpl(texture_id, media_width, media_height, media_auto_scale, media_loop); + } + else + { + media_impl->unload(); + media_impl->setTextureID(texture_id); + media_impl->mMediaWidth = media_width; + media_impl->mMediaHeight = media_height; + media_impl->mMediaAutoScale = media_auto_scale; + media_impl->mMediaLoop = media_loop; + } + + return media_impl; +} + +viewer_media_t LLViewerMedia::updateMediaImpl(LLMediaEntry* media_entry, const std::string& previous_url, bool update_from_self) +{ + llassert(!gCubeSnapshot); + // Try to find media with the same media ID + viewer_media_t media_impl = getMediaImplFromTextureID(media_entry->getMediaID()); + + LL_DEBUGS() << "called, current URL is \"" << media_entry->getCurrentURL() + << "\", previous URL is \"" << previous_url + << "\", update_from_self is " << (update_from_self?"true":"false") + << LL_ENDL; + + bool was_loaded = false; + bool needs_navigate = false; + + if(media_impl) + { + was_loaded = media_impl->hasMedia(); + + media_impl->setHomeURL(media_entry->getHomeURL()); + + media_impl->mMediaAutoScale = media_entry->getAutoScale(); + media_impl->mMediaLoop = media_entry->getAutoLoop(); + media_impl->mMediaWidth = media_entry->getWidthPixels(); + media_impl->mMediaHeight = media_entry->getHeightPixels(); + media_impl->mMediaAutoPlay = media_entry->getAutoPlay(); + media_impl->mMediaEntryURL = media_entry->getCurrentURL(); + if (media_impl->mMediaSource) + { + media_impl->mMediaSource->setAutoScale(media_impl->mMediaAutoScale); + media_impl->mMediaSource->setLoop(media_impl->mMediaLoop); + media_impl->mMediaSource->setSize(media_entry->getWidthPixels(), media_entry->getHeightPixels()); + } + + bool url_changed = (media_impl->mMediaEntryURL != previous_url); + if(media_impl->mMediaEntryURL.empty()) + { + if(url_changed) + { + // The current media URL is now empty. Unload the media source. + media_impl->unload(); + + LL_DEBUGS() << "Unloading media instance (new current URL is empty)." << LL_ENDL; + } + } + else + { + // The current media URL is not empty. + // If (the media was already loaded OR the media was set to autoplay) AND this update didn't come from this agent, + // do a navigate. + bool auto_play = media_impl->isAutoPlayable(); + if((was_loaded || auto_play) && !update_from_self) + { + needs_navigate = url_changed; + } + + LL_DEBUGS() << "was_loaded is " << (was_loaded?"true":"false") + << ", auto_play is " << (auto_play?"true":"false") + << ", needs_navigate is " << (needs_navigate?"true":"false") << LL_ENDL; + } + } + else + { + media_impl = newMediaImpl( + media_entry->getMediaID(), + media_entry->getWidthPixels(), + media_entry->getHeightPixels(), + media_entry->getAutoScale(), + media_entry->getAutoLoop()); + + media_impl->setHomeURL(media_entry->getHomeURL()); + media_impl->mMediaAutoPlay = media_entry->getAutoPlay(); + media_impl->mMediaEntryURL = media_entry->getCurrentURL(); + if(media_impl->isAutoPlayable()) + { + needs_navigate = true; + } + } + + if(media_impl) + { + if(needs_navigate) + { + media_impl->navigateTo(media_impl->mMediaEntryURL, "", true, true); + LL_DEBUGS() << "navigating to URL " << media_impl->mMediaEntryURL << LL_ENDL; + } + else if(!media_impl->mMediaURL.empty() && (media_impl->mMediaURL != media_impl->mMediaEntryURL)) + { + // If we already have a non-empty media URL set and we aren't doing a navigate, update the media URL to match the media entry. + media_impl->mMediaURL = media_impl->mMediaEntryURL; + + // If this causes a navigate at some point (such as after a reload), it should be considered server-driven so it isn't broadcast. + media_impl->mNavigateServerRequest = true; + + LL_DEBUGS() << "updating URL in the media impl to " << media_impl->mMediaEntryURL << LL_ENDL; + } + } + + return media_impl; +} + +////////////////////////////////////////////////////////////////////////////////////////// +LLViewerMediaImpl* LLViewerMedia::getMediaImplFromTextureID(const LLUUID& texture_id) +{ + LLViewerMediaImpl* result = NULL; + + // Look up the texture ID in the texture id->impl map. + impl_id_map::iterator iter = sViewerMediaTextureIDMap.find(texture_id); + if(iter != sViewerMediaTextureIDMap.end()) + { + result = iter->second; + } + + return result; +} + +////////////////////////////////////////////////////////////////////////////////////////// +std::string LLViewerMedia::getCurrentUserAgent() +{ + // Don't use user-visible string to avoid + // punctuation and strange characters. + std::string skin_name = gSavedSettings.getString("SkinCurrent"); + + // Just in case we need to check browser differences in A/B test + // builds. + std::string channel = LLVersionInfo::instance().getChannel(); + + // append our magic version number string to the browser user agent id + // See the HTTP 1.0 and 1.1 specifications for allowed formats: + // http://www.ietf.org/rfc/rfc1945.txt section 10.15 + // http://www.ietf.org/rfc/rfc2068.txt section 3.8 + // This was also helpful: + // http://www.mozilla.org/build/revised-user-agent-strings.html + std::ostringstream codec; + codec << "SecondLife/"; + codec << LLVersionInfo::instance().getVersion(); + codec << " (" << channel << "; " << skin_name << " skin)"; + LL_INFOS() << codec.str() << LL_ENDL; + + return codec.str(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMedia::updateBrowserUserAgent() +{ + std::string user_agent = getCurrentUserAgent(); + + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + + for(; iter != end; iter++) + { + LLViewerMediaImpl* pimpl = *iter; + if(pimpl->mMediaSource && pimpl->mMediaSource->pluginSupportsMediaBrowser()) + { + pimpl->mMediaSource->setBrowserUserAgent(user_agent); + } + } + +} + +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMedia::handleSkinCurrentChanged(const LLSD& /*newvalue*/) +{ + // gSavedSettings is already updated when this function is called. + updateBrowserUserAgent(); + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMedia::textureHasMedia(const LLUUID& texture_id) +{ + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + + for(; iter != end; iter++) + { + LLViewerMediaImpl* pimpl = *iter; + if(pimpl->getMediaTextureID() == texture_id) + { + return true; + } + } + return false; +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMedia::setVolume(F32 volume) +{ + if(volume != sGlobalVolume || sForceUpdate) + { + sGlobalVolume = volume; + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + + for(; iter != end; iter++) + { + LLViewerMediaImpl* pimpl = *iter; + pimpl->updateVolume(); + } + + sForceUpdate = false; + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +F32 LLViewerMedia::getVolume() +{ + return sGlobalVolume; +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMedia::muteListChanged() +{ + // When the mute list changes, we need to check mute status on all impls. + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + + for(; iter != end; iter++) + { + LLViewerMediaImpl* pimpl = *iter; + pimpl->mNeedsMuteCheck = true; + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMedia::isInterestingEnough(const LLVOVolume *object, const F64 &object_interest) +{ + bool result = false; + + if (NULL == object) + { + result = false; + } + // Focused? Then it is interesting! + else if (LLViewerMediaFocus::getInstance()->getFocusedObjectID() == object->getID()) + { + result = true; + } + // Selected? Then it is interesting! + // XXX Sadly, 'contains()' doesn't take a const :( + else if (LLSelectMgr::getInstance()->getSelection()->contains(const_cast(object))) + { + result = true; + } + else + { + LL_DEBUGS() << "object interest = " << object_interest << ", lowest loadable = " << sLowestLoadableImplInterest << LL_ENDL; + if(object_interest >= sLowestLoadableImplInterest) + result = true; + } + + return result; +} + +LLViewerMedia::impl_list &LLViewerMedia::getPriorityList() +{ + return sViewerMediaImplList; +} + +// static +// This is the predicate function used to sort sViewerMediaImplList by priority. +bool LLViewerMedia::priorityComparitor(const LLViewerMediaImpl* i1, const LLViewerMediaImpl* i2) +{ + if(i1->isForcedUnloaded() && !i2->isForcedUnloaded()) + { + // Muted or failed items always go to the end of the list, period. + return false; + } + else if(i2->isForcedUnloaded() && !i1->isForcedUnloaded()) + { + // Muted or failed items always go to the end of the list, period. + return true; + } + else if(i1->hasFocus()) + { + // The item with user focus always comes to the front of the list, period. + return true; + } + else if(i2->hasFocus()) + { + // The item with user focus always comes to the front of the list, period. + return false; + } + else if(i1->isParcelMedia()) + { + // The parcel media impl sorts above all other inworld media, unless one has focus. + return true; + } + else if(i2->isParcelMedia()) + { + // The parcel media impl sorts above all other inworld media, unless one has focus. + return false; + } + else if(i1->getUsedInUI() && !i2->getUsedInUI()) + { + // i1 is a UI element, i2 is not. This makes i1 "less than" i2, so it sorts earlier in our list. + return true; + } + else if(i2->getUsedInUI() && !i1->getUsedInUI()) + { + // i2 is a UI element, i1 is not. This makes i2 "less than" i1, so it sorts earlier in our list. + return false; + } + else if(i1->isPlayable() && !i2->isPlayable()) + { + // Playable items sort above ones that wouldn't play even if they got high enough priority + return true; + } + else if(!i1->isPlayable() && i2->isPlayable()) + { + // Playable items sort above ones that wouldn't play even if they got high enough priority + return false; + } + else if(i1->getInterest() == i2->getInterest()) + { + // Generally this will mean both objects have zero interest. In this case, sort on distance. + return (i1->getProximityDistance() < i2->getProximityDistance()); + } + else + { + // The object with the larger interest value should be earlier in the list, so we reverse the sense of the comparison here. + return (i1->getInterest() > i2->getInterest()); + } +} + +static bool proximity_comparitor(const LLViewerMediaImpl* i1, const LLViewerMediaImpl* i2) +{ + if(i1->getProximityDistance() < i2->getProximityDistance()) + { + return true; + } + else if(i1->getProximityDistance() > i2->getProximityDistance()) + { + return false; + } + else + { + // Both objects have the same distance. This most likely means they're two faces of the same object. + // They may also be faces on different objects with exactly the same distance (like HUD objects). + // We don't actually care what the sort order is for this case, as long as it's stable and doesn't change when you enable/disable media. + // Comparing the impl pointers gives a completely arbitrary ordering, but it will be stable. + return (i1 < i2); + } +} + +static LLTrace::BlockTimerStatHandle FTM_MEDIA_UPDATE("Update Media"); +static LLTrace::BlockTimerStatHandle FTM_MEDIA_SPARE_IDLE("Spare Idle"); +static LLTrace::BlockTimerStatHandle FTM_MEDIA_UPDATE_INTEREST("Update/Interest"); +static LLTrace::BlockTimerStatHandle FTM_MEDIA_UPDATE_VOLUME("Update/Volume"); +static LLTrace::BlockTimerStatHandle FTM_MEDIA_SORT("Media Sort"); +static LLTrace::BlockTimerStatHandle FTM_MEDIA_SORT2("Media Sort 2"); +static LLTrace::BlockTimerStatHandle FTM_MEDIA_MISC("Misc"); + + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMedia::onIdle(void *dummy_arg) +{ + LLViewerMedia::getInstance()->updateMedia(dummy_arg); +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMedia::updateMedia(void *dummy_arg) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA; //LL_RECORD_BLOCK_TIME(FTM_MEDIA_UPDATE); + + llassert(!gCubeSnapshot); + + // Enable/disable the plugin read thread + LLPluginProcessParent::setUseReadThread(gSavedSettings.getBOOL("PluginUseReadThread")); + + // SL-16418 We can't call LLViewerMediaImpl->update() if we are in the state of shutting down. + if(LLApp::isExiting()) + { + setAllMediaEnabled(false); + return; + } + + // HACK: we always try to keep a spare running webkit plugin around to improve launch times. + // 2017-04-19 Removed CP - this doesn't appear to buy us much and consumes a lot of resources so + // removing it for now. + //createSpareBrowserMediaSource(); + + mAnyMediaShowing = false; + mAnyMediaPlaying = false; + + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_MEDIA("media update interest"); //LL_RECORD_BLOCK_TIME(FTM_MEDIA_UPDATE_INTEREST); + for(; iter != end;) + { + LLViewerMediaImpl* pimpl = *iter++; + pimpl->update(); + pimpl->calculateInterest(); + } + } + + // Let the spare media source actually launch + if(mSpareBrowserMediaSource) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_MEDIA("media spare idle"); //LL_RECORD_BLOCK_TIME(FTM_MEDIA_SPARE_IDLE); + mSpareBrowserMediaSource->idle(); + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_MEDIA("media sort"); //LL_RECORD_BLOCK_TIME(FTM_MEDIA_SORT); + // Sort the static instance list using our interest criteria + sViewerMediaImplList.sort(priorityComparitor); + } + + // Go through the list again and adjust according to priority. + iter = sViewerMediaImplList.begin(); + end = sViewerMediaImplList.end(); + + F64 total_cpu = 0.0f; + int impl_count_total = 0; + int impl_count_interest_low = 0; + int impl_count_interest_normal = 0; + + std::vector proximity_order; + + static LLCachedControl inworld_media_enabled(gSavedSettings, "AudioStreamingMedia", true); + static LLCachedControl inworld_audio_enabled(gSavedSettings, "AudioStreamingMusic", true); + U32 max_instances = gSavedSettings.getU32("PluginInstancesTotal"); + U32 max_normal = gSavedSettings.getU32("PluginInstancesNormal"); + U32 max_low = gSavedSettings.getU32("PluginInstancesLow"); + F32 max_cpu = gSavedSettings.getF32("PluginInstancesCPULimit"); + // Setting max_cpu to 0.0 disables CPU usage checking. + bool check_cpu_usage = (max_cpu != 0.0f); + + LLViewerMediaImpl* lowest_interest_loadable = NULL; + + // Notes on tweakable params: + // max_instances must be set high enough to allow the various instances used in the UI (for the help browser, search, etc.) to be loaded. + // If max_normal + max_low is less than max_instances, things will tend to get unloaded instead of being set to slideshow. + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_MEDIA("media misc"); //LL_RECORD_BLOCK_TIME(FTM_MEDIA_MISC); + for(; iter != end; iter++) + { + LLViewerMediaImpl* pimpl = *iter; + + LLPluginClassMedia::EPriority new_priority = LLPluginClassMedia::PRIORITY_NORMAL; + + if(pimpl->isForcedUnloaded() || (impl_count_total >= (int)max_instances)) + { + // Never load muted or failed impls. + // Hard limit on the number of instances that will be loaded at one time + new_priority = LLPluginClassMedia::PRIORITY_UNLOADED; + } + else if(!pimpl->getVisible()) + { + new_priority = LLPluginClassMedia::PRIORITY_HIDDEN; + } + else if(pimpl->hasFocus()) + { + new_priority = LLPluginClassMedia::PRIORITY_HIGH; + impl_count_interest_normal++; // count this against the count of "normal" instances for priority purposes + } + else if(pimpl->getUsedInUI()) + { + new_priority = LLPluginClassMedia::PRIORITY_NORMAL; + impl_count_interest_normal++; + } + else if(pimpl->isParcelMedia()) + { + new_priority = LLPluginClassMedia::PRIORITY_NORMAL; + impl_count_interest_normal++; + } + else + { + // Look at interest and CPU usage for instances that aren't in any of the above states. + + // Heuristic -- if the media texture's approximate screen area is less than 1/4 of the native area of the texture, + // turn it down to low instead of normal. This may downsample for plugins that support it. + bool media_is_small = false; + F64 approximate_interest = pimpl->getApproximateTextureInterest(); + if(approximate_interest == 0.0f) + { + // this media has no current size, which probably means it's not loaded. + media_is_small = true; + } + else if(pimpl->getInterest() < (approximate_interest / 4)) + { + media_is_small = true; + } + + if(pimpl->getInterest() == 0.0f) + { + // This media is completely invisible, due to being outside the view frustrum or out of range. + new_priority = LLPluginClassMedia::PRIORITY_HIDDEN; + } + else if(check_cpu_usage && (total_cpu > max_cpu)) + { + // Higher priority plugins have already used up the CPU budget. Set remaining ones to slideshow priority. + new_priority = LLPluginClassMedia::PRIORITY_SLIDESHOW; + } + else if((impl_count_interest_normal < (int)max_normal) && !media_is_small) + { + // Up to max_normal inworld get normal priority + new_priority = LLPluginClassMedia::PRIORITY_NORMAL; + impl_count_interest_normal++; + } + else if (impl_count_interest_low + impl_count_interest_normal < (int)max_low + (int)max_normal) + { + // The next max_low inworld get turned down + new_priority = LLPluginClassMedia::PRIORITY_LOW; + impl_count_interest_low++; + + // Set the low priority size for downsampling to approximately the size the texture is displayed at. + { + F32 approximate_interest_dimension = (F32) sqrt(pimpl->getInterest()); + + pimpl->setLowPrioritySizeLimit(ll_round(approximate_interest_dimension)); + } + } + else + { + // Any additional impls (up to max_instances) get very infrequent time + new_priority = LLPluginClassMedia::PRIORITY_SLIDESHOW; + } + } + + if(!pimpl->getUsedInUI() && (new_priority != LLPluginClassMedia::PRIORITY_UNLOADED)) + { + // This is a loadable inworld impl -- the last one in the list in this class defines the lowest loadable interest. + lowest_interest_loadable = pimpl; + + impl_count_total++; + } + + // Overrides if the window is minimized or we lost focus (taking care + // not to accidentally "raise" the priority either) + if (!gViewerWindow->getActive() /* viewer window minimized? */ + && new_priority > LLPluginClassMedia::PRIORITY_HIDDEN) + { + new_priority = LLPluginClassMedia::PRIORITY_HIDDEN; + } + else if (!gFocusMgr.getAppHasFocus() /* viewer window lost focus? */ + && new_priority > LLPluginClassMedia::PRIORITY_LOW) + { + new_priority = LLPluginClassMedia::PRIORITY_LOW; + } + + if(!inworld_media_enabled) + { + // If inworld media is locked out, force all inworld media to stay unloaded. + if(!pimpl->getUsedInUI()) + { + new_priority = LLPluginClassMedia::PRIORITY_UNLOADED; + } + } + // update the audio stream here as well + static bool restore_parcel_audio = false; + if( !inworld_audio_enabled) + { + if(LLViewerMedia::isParcelAudioPlaying() && gAudiop && LLViewerMedia::hasParcelAudio()) + { + LLViewerAudio::getInstance()->stopInternetStreamWithAutoFade(); + restore_parcel_audio = true; + } + } + else + { + if(gAudiop && LLViewerMedia::hasParcelAudio() && restore_parcel_audio && gSavedSettings.getBOOL("MediaTentativeAutoPlay")) + { + LLViewerAudio::getInstance()->startInternetStreamWithAutoFade(LLViewerMedia::getParcelAudioURL()); + restore_parcel_audio = false; + } + } + + pimpl->setPriority(new_priority); + + if(pimpl->getUsedInUI()) + { + // Any impls used in the UI should not be in the proximity list. + pimpl->mProximity = -1; + } + else + { + proximity_order.push_back(pimpl); + } + + total_cpu += pimpl->getCPUUsage(); + + if (!pimpl->getUsedInUI() && pimpl->hasMedia()) + { + mAnyMediaShowing = true; + } + + if (!pimpl->getUsedInUI() && pimpl->hasMedia() && (pimpl->isMediaPlaying() || !pimpl->isMediaTimeBased())) + { + // consider visible non-timebased media as playing + mAnyMediaPlaying = true; + } + + } + } + + // Re-calculate this every time. + sLowestLoadableImplInterest = 0.0f; + + // Only do this calculation if we've hit the impl count limit -- up until that point we always need to load media data. + if(lowest_interest_loadable && (impl_count_total >= (int)max_instances)) + { + // Get the interest value of this impl's object for use by isInterestingEnough + LLVOVolume *object = lowest_interest_loadable->getSomeObject(); + if(object) + { + // NOTE: Don't use getMediaInterest() here. We want the pixel area, not the total media interest, + // so that we match up with the calculation done in LLMediaDataClient. + sLowestLoadableImplInterest = object->getPixelArea(); + } + } + + if(gSavedSettings.getBOOL("MediaPerformanceManagerDebug")) + { + // Give impls the same ordering as the priority list + // they're already in the right order for this. + } + else + { + LL_PROFILE_ZONE_NAMED_CATEGORY_MEDIA("media sort2"); // LL_RECORD_BLOCK_TIME(FTM_MEDIA_SORT2); + // Use a distance-based sort for proximity values. + std::stable_sort(proximity_order.begin(), proximity_order.end(), proximity_comparitor); + } + + // Transfer the proximity order to the proximity fields in the objects. + for(int i = 0; i < (int)proximity_order.size(); i++) + { + proximity_order[i]->mProximity = i; + } + + LL_DEBUGS("PluginPriority") << "Total reported CPU usage is " << total_cpu << LL_ENDL; + +} + +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMedia::isAnyMediaShowing() +{ + return mAnyMediaShowing; +} + +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMedia::isAnyMediaPlaying() +{ + return mAnyMediaPlaying; +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMedia::setAllMediaEnabled(bool val) +{ + // Set "tentative" autoplay first. We need to do this here or else + // re-enabling won't start up the media below. + gSavedSettings.setBOOL("MediaTentativeAutoPlay", val); + + // Then + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + + for(; iter != end; iter++) + { + LLViewerMediaImpl* pimpl = *iter; + if (!pimpl->getUsedInUI()) + { + pimpl->setDisabled(!val); + } + } + + // Also do Parcel Media and Parcel Audio + if (val) + { + if (!LLViewerMedia::isParcelMediaPlaying() && LLViewerMedia::hasParcelMedia()) + { + LLViewerParcelMedia::getInstance()->play(LLViewerParcelMgr::getInstance()->getAgentParcel()); + } + + static LLCachedControl audio_streaming_music(gSavedSettings, "AudioStreamingMusic", true); + if (audio_streaming_music && + !LLViewerMedia::isParcelAudioPlaying() && + gAudiop && + LLViewerMedia::hasParcelAudio()) + { + if (LLAudioEngine::AUDIO_PAUSED == gAudiop->isInternetStreamPlaying()) + { + // 'false' means unpause + gAudiop->pauseInternetStream(false); + } + else + { + LLViewerAudio::getInstance()->startInternetStreamWithAutoFade(LLViewerMedia::getParcelAudioURL()); + } + } + } + else { + // This actually unloads the impl, as opposed to "stop"ping the media + LLViewerParcelMedia::getInstance()->stop(); + if (gAudiop) + { + LLViewerAudio::getInstance()->stopInternetStreamWithAutoFade(); + } + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMedia::setAllMediaPaused(bool val) +{ + // Set "tentative" autoplay first. We need to do this here or else + // re-enabling won't start up the media below. + gSavedSettings.setBOOL("MediaTentativeAutoPlay", !val); + + // Then + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + + for (; iter != end; iter++) + { + LLViewerMediaImpl* pimpl = *iter; + if (!pimpl->getUsedInUI()) + { + // upause/pause time based media, enable/disable any other + if (!val) + { + pimpl->setDisabled(val); + if (pimpl->isMediaTimeBased() && pimpl->isMediaPaused()) + { + pimpl->play(); + } + } + else if (pimpl->isMediaTimeBased() && pimpl->mMediaSource && (pimpl->isMediaPlaying() || pimpl->isMediaPaused())) + { + pimpl->pause(); + } + else + { + pimpl->setDisabled(val); + } + } + } + + LLParcel *agent_parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + + // Also do Parcel Media and Parcel Audio + if (!val) + { + if (!LLViewerMedia::isParcelMediaPlaying() && LLViewerMedia::hasParcelMedia()) + { + LLViewerParcelMedia::getInstance()->play(agent_parcel); + } + + static LLCachedControl audio_streaming_music(gSavedSettings, "AudioStreamingMusic", true); + if (audio_streaming_music && + !LLViewerMedia::isParcelAudioPlaying() && + gAudiop && + LLViewerMedia::hasParcelAudio()) + { + if (LLAudioEngine::AUDIO_PAUSED == gAudiop->isInternetStreamPlaying()) + { + // 'false' means unpause + gAudiop->pauseInternetStream(false); + } + else + { + LLViewerAudio::getInstance()->startInternetStreamWithAutoFade(LLViewerMedia::getParcelAudioURL()); + } + } + } + else { + // This actually unloads the impl, as opposed to "stop"ping the media + LLViewerParcelMedia::getInstance()->stop(); + if (gAudiop) + { + LLViewerAudio::getInstance()->stopInternetStreamWithAutoFade(); + } + } + + // remove play choice for current parcel + if (agent_parcel && gAgent.getRegion()) + { + LLViewerParcelAskPlay::getInstance()->resetSetting(gAgent.getRegion()->getRegionID(), agent_parcel->getLocalID()); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMedia::isParcelMediaPlaying() +{ + viewer_media_t media = LLViewerParcelMedia::getInstance()->getParcelMedia(); + return (LLViewerMedia::hasParcelMedia() && media && media->hasMedia()); +} + +///////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMedia::isParcelAudioPlaying() +{ + return (LLViewerMedia::hasParcelAudio() && gAudiop && LLAudioEngine::AUDIO_PLAYING == gAudiop->isInternetStreamPlaying()); +} + +///////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::authSubmitCallback(const LLSD& notification, const LLSD& response) +{ + LLViewerMedia::getInstance()->onAuthSubmit(notification, response); +} + +void LLViewerMedia::onAuthSubmit(const LLSD& notification, const LLSD& response) +{ + LLViewerMediaImpl *impl = LLViewerMedia::getMediaImplFromTextureID(notification["payload"]["media_id"]); + if(impl) + { + LLPluginClassMedia* media = impl->getMediaPlugin(); + if(media) + { + if (response["ok"]) + { + media->sendAuthResponse(true, response["username"], response["password"]); + } + else + { + media->sendAuthResponse(false, "", ""); + } + } + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMedia::clearAllCookies() +{ + // Clear all cookies for all plugins + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + for (; iter != end; iter++) + { + LLViewerMediaImpl* pimpl = *iter; + if(pimpl->mMediaSource) + { + pimpl->mMediaSource->clear_cookies(); + } + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMedia::clearAllCaches() +{ + // Clear all plugins' caches + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + for (; iter != end; iter++) + { + LLViewerMediaImpl* pimpl = *iter; + pimpl->clearCache(); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMedia::setCookiesEnabled(bool enabled) +{ + // Set the "cookies enabled" flag for all loaded plugins + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + for (; iter != end; iter++) + { + LLViewerMediaImpl* pimpl = *iter; + if(pimpl->mMediaSource) + { + pimpl->mMediaSource->cookies_enabled(enabled); + } + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMedia::setProxyConfig(bool enable, const std::string &host, int port) +{ + // Set the proxy config for all loaded plugins + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + for (; iter != end; iter++) + { + LLViewerMediaImpl* pimpl = *iter; + if(pimpl->mMediaSource) + { + pimpl->mMediaSource->proxy_setup(enable, host, port); + } + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +LLSD LLViewerMedia::getHeaders() +{ + LLSD headers = LLSD::emptyMap(); + headers[HTTP_OUT_HEADER_ACCEPT] = "*/*"; + // *TODO: Should this be 'application/llsd+xml' ? + // *TODO: Should this even be set at all? This header is only not overridden in 'GET' methods. + headers[HTTP_OUT_HEADER_CONTENT_TYPE] = HTTP_CONTENT_XML; + headers[HTTP_OUT_HEADER_COOKIE] = mOpenIDCookie; + headers[HTTP_OUT_HEADER_USER_AGENT] = getCurrentUserAgent(); + + return headers; +} + + ///////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMedia::parseRawCookie(const std::string raw_cookie, std::string& name, std::string& value, std::string& path, bool& httponly, bool& secure) +{ + std::size_t name_pos = raw_cookie.find_first_of("="); + if (name_pos != std::string::npos) + { + name = raw_cookie.substr(0, name_pos); + std::size_t value_pos = raw_cookie.find_first_of(";", name_pos); + if (value_pos != std::string::npos) + { + value = raw_cookie.substr(name_pos + 1, value_pos - name_pos - 1); + path = "/"; // assume root path for now + + httponly = true; // hard coded for now + secure = true; + + return true; + } + } + + return false; +} + +///////////////////////////////////////////////////////////////////////////////////////// +LLCore::HttpHeaders::ptr_t LLViewerMedia::getHttpHeaders() +{ + LLCore::HttpHeaders::ptr_t headers(new LLCore::HttpHeaders); + + headers->append(HTTP_OUT_HEADER_ACCEPT, "*/*"); + headers->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_XML); + headers->append(HTTP_OUT_HEADER_COOKIE, mOpenIDCookie); + headers->append(HTTP_OUT_HEADER_USER_AGENT, getCurrentUserAgent()); + + return headers; +} + + +///////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMedia::setOpenIDCookie(const std::string& url) +{ + if(!gNonInteractive && !mOpenIDCookie.empty()) + { + std::string profileUrl = getProfileURL(""); + + LLCoros::instance().launch("LLViewerMedia::getOpenIDCookieCoro", + boost::bind(&LLViewerMedia::getOpenIDCookieCoro, profileUrl)); + } +} + +//static +void LLViewerMedia::getOpenIDCookieCoro(std::string url) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("getOpenIDCookieCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); + + httpOpts->setFollowRedirects(true); + httpOpts->setWantHeaders(true); + httpOpts->setSSLVerifyPeer(false); // viewer's cert bundle doesn't appear to agree with web certs from "https://my.secondlife.com/" + + LLURL hostUrl(url.c_str()); + std::string hostAuth = hostUrl.getAuthority(); + + // *TODO: Expand LLURL to split and extract this information better. + // The structure of a URL is well defined and needing to retrieve parts of it are common. + // original comment: + // The LLURL can give me the 'authority', which is of the form: [username[:password]@]hostname[:port] + // We want just the hostname for the cookie code, but LLURL doesn't seem to have a way to extract that. + // We therefore do it here. + std::string authority = getInstance()->mOpenIDURL.mAuthority; + std::string::size_type hostStart = authority.find('@'); + if (hostStart == std::string::npos) + { // no username/password + hostStart = 0; + } + else + { // Hostname starts after the @. + // Hostname starts after the @. + // (If the hostname part is empty, this may put host_start at the end of the string. In that case, it will end up passing through an empty hostname, which is correct.) + ++hostStart; + } + std::string::size_type hostEnd = authority.rfind(':'); + if ((hostEnd == std::string::npos) || (hostEnd < hostStart)) + { // no port + hostEnd = authority.size(); + } + + LLViewerMedia* inst = getInstance(); + if (url.length()) + { + LLMediaCtrl* media_instance = LLFloaterReg::getInstance("destinations")->getChild("destination_guide_contents"); + if (media_instance) + { + std::string cookie_host = authority.substr(hostStart, hostEnd - hostStart); + std::string cookie_name = ""; + std::string cookie_value = ""; + std::string cookie_path = ""; + bool httponly = true; + bool secure = true; + if (inst->parseRawCookie(inst->mOpenIDCookie, cookie_name, cookie_value, cookie_path, httponly, secure) && + media_instance->getMediaPlugin()) + { + // MAINT-5711 - inexplicably, the CEF setCookie function will no longer set the cookie if the + // url and domain are not the same. This used to be my.sl.com and id.sl.com respectively and worked. + // For now, we use the URL for the OpenID POST request since it will have the same authority + // as the domain field. + // (Feels like there must be a less dirty way to construct a URL from component LLURL parts) + // MAINT-6392 - Rider: Do not change, however, the original URI requested, since it is used further + // down. + std::string cefUrl(std::string(inst->mOpenIDURL.mURI) + "://" + std::string(inst->mOpenIDURL.mAuthority)); + + media_instance->getMediaPlugin()->setCookie(cefUrl, cookie_name, cookie_value, cookie_host, + cookie_path, httponly, secure); + + // Now that we have parsed the raw cookie, we must store it so that each new media instance + // can also get a copy and faciliate logging into internal SL sites. + media_instance->getMediaPlugin()->storeOpenIDCookie(cefUrl, cookie_name, cookie_value, + cookie_host, cookie_path, httponly, secure); + } + } + } + + // Note: Rider: MAINT-6392 - Some viewer code requires access to the my.sl.com openid cookie for such + // actions as posting snapshots to the feed. This is handled through HTTPCore rather than CEF and so + // we must learn to SHARE the cookies. + + // Do a web profile get so we can store the cookie + httpHeaders->append(HTTP_OUT_HEADER_ACCEPT, "*/*"); + httpHeaders->append(HTTP_OUT_HEADER_COOKIE, inst->mOpenIDCookie); + httpHeaders->append(HTTP_OUT_HEADER_USER_AGENT, inst->getCurrentUserAgent()); + + LL_DEBUGS("MediaAuth") << "Requesting " << url << LL_ENDL; + LL_DEBUGS("MediaAuth") << "sOpenIDCookie = [" << inst->mOpenIDCookie << "]" << LL_ENDL; + + LLSD result = httpAdapter->getRawAndSuspend(httpRequest, url, httpOpts, httpHeaders); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS("MediaAuth") << "Error getting web profile." << LL_ENDL; + return; + } + + LLSD resultHeaders = httpResults[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS]; + if (!resultHeaders.has(HTTP_IN_HEADER_SET_COOKIE)) + { + LL_WARNS("MediaAuth") << "No cookie in response." << LL_ENDL; + return; + } + + const std::string& cookie = resultHeaders[HTTP_IN_HEADER_SET_COOKIE].asStringRef(); + LL_DEBUGS("MediaAuth") << "cookie = " << cookie << LL_ENDL; + + // Set cookie for snapshot publishing. + std::string authCookie = cookie.substr(0, cookie.find(";")); // strip path + LLWebProfile::setAuthCookie(authCookie); +} + +///////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMedia::openIDSetup(const std::string &openidUrl, const std::string &openidToken) +{ + LL_DEBUGS("MediaAuth") << "url = \"" << openidUrl << "\", token = \"" << openidToken << "\"" << LL_ENDL; + + LLCoros::instance().launch("LLViewerMedia::openIDSetupCoro", + boost::bind(&LLViewerMedia::openIDSetupCoro, openidUrl, openidToken)); +} + +void LLViewerMedia::openIDSetupCoro(std::string openidUrl, std::string openidToken) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("openIDSetupCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); + + httpOpts->setWantHeaders(true); + + // post the token to the url + // the responder will need to extract the cookie(s). + // Save the OpenID URL for later -- we may need the host when adding the cookie. + getInstance()->mOpenIDURL.init(openidUrl.c_str()); + + // We shouldn't ever do this twice, but just in case this code gets repurposed later, clear existing cookies. + getInstance()->mOpenIDCookie.clear(); + + httpHeaders->append(HTTP_OUT_HEADER_ACCEPT, "*/*"); + httpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/x-www-form-urlencoded"); + + LLCore::BufferArray::ptr_t rawbody(new LLCore::BufferArray); + LLCore::BufferArrayStream bas(rawbody.get()); + + bas << std::noskipws << openidToken; + + LLSD result = httpAdapter->postRawAndSuspend(httpRequest, openidUrl, rawbody, httpOpts, httpHeaders); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS("MediaAuth") << "Error getting Open ID cookie" << LL_ENDL; + return; + } + + LLSD resultHeaders = httpResults[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS]; + if (!resultHeaders.has(HTTP_IN_HEADER_SET_COOKIE)) + { + LL_WARNS("MediaAuth") << "No cookie in response." << LL_ENDL; + return; + } + + // We don't care about the content of the response, only the Set-Cookie header. + const std::string& cookie = resultHeaders[HTTP_IN_HEADER_SET_COOKIE].asString(); + + // *TODO: What about bad status codes? Does this destroy previous cookies? + LLViewerMedia::getInstance()->openIDCookieResponse(openidUrl, cookie); + LL_DEBUGS("MediaAuth") << "OpenID cookie set." << LL_ENDL; + +} + +///////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMedia::openIDCookieResponse(const std::string& url, const std::string &cookie) +{ + LL_DEBUGS("MediaAuth") << "Cookie received: \"" << cookie << "\"" << LL_ENDL; + + mOpenIDCookie += cookie; + + setOpenIDCookie(url); +} + +///////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMedia::proxyWindowOpened(const std::string &target, const std::string &uuid) +{ + if(uuid.empty()) + return; + + for (impl_list::iterator iter = sViewerMediaImplList.begin(); iter != sViewerMediaImplList.end(); iter++) + { + if((*iter)->mMediaSource && (*iter)->mMediaSource->pluginSupportsMediaBrowser()) + { + (*iter)->mMediaSource->proxyWindowOpened(target, uuid); + } + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMedia::proxyWindowClosed(const std::string &uuid) +{ + if(uuid.empty()) + return; + + for (impl_list::iterator iter = sViewerMediaImplList.begin(); iter != sViewerMediaImplList.end(); iter++) + { + if((*iter)->mMediaSource && (*iter)->mMediaSource->pluginSupportsMediaBrowser()) + { + (*iter)->mMediaSource->proxyWindowClosed(uuid); + } + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMedia::createSpareBrowserMediaSource() +{ + // If we don't have a spare browser media source, create one. + // However, if PluginAttachDebuggerToPlugins is set then don't spawn a spare + // SLPlugin process in order to not be confused by an unrelated gdb terminal + // popping up at the moment we start a media plugin. + if (!mSpareBrowserMediaSource && !gSavedSettings.getBOOL("PluginAttachDebuggerToPlugins")) + { + // The null owner will keep the browser plugin from fully initializing + // (specifically, it keeps LLPluginClassMedia from negotiating a size change, + // which keeps MediaPluginWebkit::initBrowserWindow from doing anything until we have some necessary data, like the background color) + mSpareBrowserMediaSource = LLViewerMediaImpl::newSourceFromMediaType(HTTP_CONTENT_TEXT_HTML, NULL, 0, 0, 1.0); + } +} + +///////////////////////////////////////////////////////////////////////////////////////// +LLPluginClassMedia* LLViewerMedia::getSpareBrowserMediaSource() +{ + LLPluginClassMedia* result = mSpareBrowserMediaSource; + mSpareBrowserMediaSource = NULL; + return result; +}; + +bool LLViewerMedia::hasInWorldMedia() +{ + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + // This should be quick, because there should be very few non-in-world-media impls + for (; iter != end; iter++) + { + LLViewerMediaImpl* pimpl = *iter; + if (!pimpl->getUsedInUI() && !pimpl->isParcelMedia()) + { + // Found an in-world media impl + return true; + } + } + return false; +} + +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMedia::hasParcelMedia() +{ + return !LLViewerParcelMedia::getInstance()->getURL().empty(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMedia::hasParcelAudio() +{ + return !LLViewerMedia::getParcelAudioURL().empty(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +std::string LLViewerMedia::getParcelAudioURL() +{ + return LLViewerParcelMgr::getInstance()->getAgentParcel()->getMusicURL(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMedia::onTeleportFinished() +{ + // On teleport, clear this setting (i.e. set it to true) + gSavedSettings.setBOOL("MediaTentativeAutoPlay", true); + + LLViewerMediaImpl::sMimeTypesFailed.clear(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMedia::setOnlyAudibleMediaTextureID(const LLUUID& texture_id) +{ + sOnlyAudibleTextureID = texture_id; + sForceUpdate = true; +} + +std::vector LLViewerMediaImpl::sMimeTypesFailed; +////////////////////////////////////////////////////////////////////////////////////////// +// LLViewerMediaImpl +////////////////////////////////////////////////////////////////////////////////////////// +LLViewerMediaImpl::LLViewerMediaImpl( const LLUUID& texture_id, + S32 media_width, + S32 media_height, + U8 media_auto_scale, + U8 media_loop) +: + mMediaSource( NULL ), + mMovieImageHasMips(false), + mMediaWidth(media_width), + mMediaHeight(media_height), + mMediaAutoScale(media_auto_scale), + mMediaLoop(media_loop), + mNeedsNewTexture(true), + mTextureUsedWidth(0), + mTextureUsedHeight(0), + mSuspendUpdates(false), + mVisible(true), + mLastSetCursor( UI_CURSOR_ARROW ), + mMediaNavState( MEDIANAVSTATE_NONE ), + mInterest(0.0f), + mUsedInUI(false), + mHasFocus(false), + mPriority(LLPluginClassMedia::PRIORITY_UNLOADED), + mNavigateRediscoverType(false), + mNavigateServerRequest(false), + mMediaSourceFailed(false), + mRequestedVolume(1.0f), + mPreviousVolume(1.0f), + mIsMuted(false), + mNeedsMuteCheck(false), + mPreviousMediaState(MEDIA_NONE), + mPreviousMediaTime(0.0f), + mIsDisabled(false), + mIsParcelMedia(false), + mProximity(-1), + mProximityDistance(0.0f), + mMediaAutoPlay(false), + mInNearbyMediaList(false), + mClearCache(false), + mBackgroundColor(LLColor4::white), + mNavigateSuspended(false), + mNavigateSuspendedDeferred(false), + mIsUpdated(false), + mTrustedBrowser(false), + mZoomFactor(1.0), + mCleanBrowser(false), + mMimeProbe(), + mCanceling(false) +{ + + // Set up the mute list observer if it hasn't been set up already. + if(!sViewerMediaMuteListObserverInitialized) + { + LLMuteList::getInstance()->addObserver(&sViewerMediaMuteListObserver); + sViewerMediaMuteListObserverInitialized = true; + } + + add_media_impl(this); + + setTextureID(texture_id); + + // connect this media_impl to the media texture, creating it if it doesn't exist.0 + // This is necessary because we need to be able to use getMaxVirtualSize() even if the media plugin is not loaded. + // *TODO: Consider enabling mipmaps (they have been disabled for a long time). Likely has a significant performance impact for tiled/high texture repeat media. Mip generation in a shader may also be an option if necessary. + LLViewerMediaTexture* media_tex = LLViewerTextureManager::getMediaTexture(mTextureId, USE_MIPMAPS); + if(media_tex) + { + media_tex->setMediaImpl(); + } + + mMainQueue = LL::WorkQueue::getInstance("mainloop"); + mTexUpdateQueue = LL::WorkQueue::getInstance("LLImageGL"); // Share work queue with tex loader. +} + +////////////////////////////////////////////////////////////////////////////////////////// +LLViewerMediaImpl::~LLViewerMediaImpl() +{ + destroyMediaSource(); + + LLViewerMediaTexture::removeMediaImplFromTexture(mTextureId) ; + + setTextureID(); + remove_media_impl(this); +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::emitEvent(LLPluginClassMedia* plugin, LLViewerMediaObserver::EMediaEvent event) +{ + // Broadcast to observers using the superclass version + LLViewerMediaEventEmitter::emitEvent(plugin, event); + + // If this media is on one or more LLVOVolume objects, tell them about the event as well. + std::list< LLVOVolume* >::iterator iter = mObjectList.begin() ; + while(iter != mObjectList.end()) + { + LLVOVolume *self = *iter; + ++iter; + self->mediaEvent(this, plugin, event); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::initializeMedia(const std::string& mime_type) +{ + bool mimeTypeChanged = (mMimeType != mime_type); + bool pluginChanged = (LLMIMETypes::implType(mCurrentMimeType) != LLMIMETypes::implType(mime_type)); + + if(!mMediaSource || pluginChanged) + { + // We don't have a plugin at all, or the new mime type is handled by a different plugin than the old mime type. + (void)initializePlugin(mime_type); + } + else if(mimeTypeChanged) + { + // The same plugin should be able to handle the new media -- just update the stored mime type. + mMimeType = mime_type; + } + + return (mMediaSource != NULL); +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::createMediaSource() +{ + if(mPriority == LLPluginClassMedia::PRIORITY_UNLOADED) + { + // This media shouldn't be created yet. + return; + } + + if(! mMediaURL.empty()) + { + navigateInternal(); + } + else if(! mMimeType.empty()) + { + if (!initializeMedia(mMimeType)) + { + LL_WARNS("Media") << "Failed to initialize media for mime type " << mMimeType << LL_ENDL; + } + } + +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::destroyMediaSource() +{ + mNeedsNewTexture = true; + + // Tell the viewer media texture it's no longer active + LLViewerMediaTexture* oldImage = LLViewerTextureManager::findMediaTexture( mTextureId ); + if (oldImage) + { + oldImage->setPlaying(false) ; + } + + cancelMimeTypeProbe(); + + { + LLMutexLock lock(&mLock); // Delay tear-down while bg thread is updating + if(mMediaSource) + { + mMediaSource->setDeleteOK(true) ; + mMediaSource = NULL; // shared pointer + } + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::setMediaType(const std::string& media_type) +{ + mMimeType = media_type; +} + +////////////////////////////////////////////////////////////////////////////////////////// +/*static*/ +LLPluginClassMedia* LLViewerMediaImpl::newSourceFromMediaType(std::string media_type, LLPluginClassMediaOwner *owner /* may be NULL */, S32 default_width, S32 default_height, F64 zoom_factor, const std::string target, bool clean_browser) +{ + if (gNonInteractive) + { + return NULL; + } + + std::string plugin_basename = LLMIMETypes::implType(media_type); + LLPluginClassMedia* media_source = NULL; + + // HACK: we always try to keep a spare running webkit plugin around to improve launch times. + // If a spare was already created before PluginAttachDebuggerToPlugins was set, don't use it. + // Do not use a spare if launching with full viewer control (e.g. Twitter and few others) + if ((plugin_basename == "media_plugin_cef") && + !gSavedSettings.getBOOL("PluginAttachDebuggerToPlugins") && !clean_browser) + { + media_source = LLViewerMedia::getInstance()->getSpareBrowserMediaSource(); + if(media_source) + { + media_source->setOwner(owner); + media_source->setTarget(target); + media_source->setSize(default_width, default_height); + media_source->setZoomFactor(zoom_factor); + + return media_source; + } + } + if(plugin_basename.empty()) + { + LL_WARNS_ONCE("Media") << "Couldn't find plugin for media type " << media_type << LL_ENDL; + } + else + { + std::string launcher_name = gDirUtilp->getLLPluginLauncher(); + std::string plugin_name = gDirUtilp->getLLPluginFilename(plugin_basename); + + std::string user_data_path_cache = gDirUtilp->getCacheDir(false); + user_data_path_cache += gDirUtilp->getDirDelimiter(); + + std::string user_data_path_cef_log = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "cef_log.txt"); + + // See if the plugin executable exists + llstat s; + if(LLFile::stat(launcher_name, &s)) + { + LL_WARNS_ONCE("Media") << "Couldn't find launcher at " << launcher_name << LL_ENDL; + } + else if(LLFile::stat(plugin_name, &s)) + { +#if !LL_LINUX + LL_WARNS_ONCE("Media") << "Couldn't find plugin at " << plugin_name << LL_ENDL; +#endif + } + else + { + media_source = new LLPluginClassMedia(owner); + media_source->setSize(default_width, default_height); + media_source->setUserDataPath(user_data_path_cache, gDirUtilp->getUserName(), user_data_path_cef_log); + media_source->setLanguageCode(LLUI::getLanguage()); + media_source->setZoomFactor(zoom_factor); + + // collect 'cookies enabled' setting from prefs and send to embedded browser + bool cookies_enabled = gSavedSettings.getBOOL( "CookiesEnabled" ); + media_source->cookies_enabled( cookies_enabled || clean_browser); + + // collect 'javascript enabled' setting from prefs and send to embedded browser + bool javascript_enabled = gSavedSettings.getBOOL("BrowserJavascriptEnabled"); + media_source->setJavascriptEnabled(javascript_enabled || clean_browser); + + // collect 'web security disabled' (see Chrome --web-security-disabled) setting from prefs and send to embedded browser + bool web_security_disabled = gSavedSettings.getBOOL("BrowserWebSecurityDisabled"); + media_source->setWebSecurityDisabled(web_security_disabled || clean_browser); + + // collect setting indicates if local file access from file URLs is allowed from prefs and send to embedded browser + bool file_access_from_file_urls = gSavedSettings.getBOOL("BrowserFileAccessFromFileUrls"); + media_source->setFileAccessFromFileUrlsEnabled(file_access_from_file_urls || clean_browser); + + // As of SL-15559 PDF files do not load in CEF v91 we enable plugins + // but explicitly disable Flash (PDF support in CEF is now treated as a plugin) + media_source->setPluginsEnabled(true); + + bool media_plugin_debugging_enabled = gSavedSettings.getBOOL("MediaPluginDebugging"); + media_source->enableMediaPluginDebugging( media_plugin_debugging_enabled || clean_browser); + + // need to set agent string here before instance created + media_source->setBrowserUserAgent(LLViewerMedia::getInstance()->getCurrentUserAgent()); + + // configure and pass proxy setup based on debug settings that are + // configured by UI in prefs -> setup + media_source->proxy_setup(gSavedSettings.getBOOL("BrowserProxyEnabled"), gSavedSettings.getString("BrowserProxyAddress"), gSavedSettings.getS32("BrowserProxyPort")); + + media_source->setTarget(target); + + const std::string plugin_dir = gDirUtilp->getLLPluginDir(); + if (media_source->init(launcher_name, plugin_dir, plugin_name, gSavedSettings.getBOOL("PluginAttachDebuggerToPlugins"))) + { + return media_source; + } + else + { + LL_WARNS("Media") << "Failed to init plugin. Destroying." << LL_ENDL; + delete media_source; + } + } + } +#if !LL_LINUX + LL_WARNS_ONCE("Plugin") << "plugin initialization failed for mime type: " << media_type << LL_ENDL; +#endif + + if(gAgent.isInitialized()) + { + if (std::find(sMimeTypesFailed.begin(), sMimeTypesFailed.end(), media_type) == sMimeTypesFailed.end()) + { + LLSD args; + args["MIME_TYPE"] = media_type; + LLNotificationsUtil::add("NoPlugin", args); + sMimeTypesFailed.push_back(media_type); + } + } + return NULL; +} + +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::initializePlugin(const std::string& media_type) +{ + if(mMediaSource) + { + // Save the previous media source's last set size before destroying it. + mMediaWidth = mMediaSource->getSetWidth(); + mMediaHeight = mMediaSource->getSetHeight(); + mZoomFactor = mMediaSource->getZoomFactor(); + } + + // Always delete the old media impl first. + destroyMediaSource(); + + // and unconditionally set the mime type + mMimeType = media_type; + + if(mPriority == LLPluginClassMedia::PRIORITY_UNLOADED) + { + // This impl should not be loaded at this time. + LL_DEBUGS("PluginPriority") << this << "Not loading (PRIORITY_UNLOADED)" << LL_ENDL; + + return false; + } + + // If we got here, we want to ignore previous init failures. + mMediaSourceFailed = false; + + // Save the MIME type that really caused the plugin to load + mCurrentMimeType = mMimeType; + + LLPluginClassMedia* media_source = newSourceFromMediaType(mMimeType, this, mMediaWidth, mMediaHeight, mZoomFactor, mTarget, mCleanBrowser); + + if (media_source) + { + media_source->injectOpenIDCookie(); + media_source->setDisableTimeout(gSavedSettings.getBOOL("DebugPluginDisableTimeout")); + media_source->setLoop(mMediaLoop); + media_source->setAutoScale(mMediaAutoScale); + media_source->setBrowserUserAgent(LLViewerMedia::getInstance()->getCurrentUserAgent()); + media_source->focus(mHasFocus); + media_source->setBackgroundColor(mBackgroundColor); + + if(gSavedSettings.getBOOL("BrowserIgnoreSSLCertErrors")) + { + media_source->ignore_ssl_cert_errors(true); + } + + // the correct way to deal with certs it to load ours from ca-bundle.crt and append them to the ones + // Qt/WebKit loads from your system location. + std::string ca_path = gDirUtilp->getCAFile(); + media_source->addCertificateFilePath( ca_path ); + + if(mClearCache) + { + mClearCache = false; + media_source->clear_cache(); + } + + mMediaSource.reset(media_source); + mMediaSource->setDeleteOK(false) ; + updateVolume(); + + return true; + } + + // Make sure the timer doesn't try re-initing this plugin repeatedly until something else changes. + mMediaSourceFailed = true; + + return false; +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::loadURI() +{ + if(mMediaSource) + { + // trim whitespace from front and back of URL - fixes EXT-5363 + LLStringUtil::trim( mMediaURL ); + + // URI often comes unescaped + std::string uri = LLURI::escapePathAndData(mMediaURL); + { + // Do not log the query parts + LLURI u(uri); + std::string sanitized_uri = (u.query().empty() ? uri : u.scheme() + "://" + u.authority() + u.path()); + LL_INFOS() << "Asking media source to load URI: " << sanitized_uri << LL_ENDL; + } + + mMediaSource->loadURI( uri ); + + // A non-zero mPreviousMediaTime means that either this media was previously unloaded by the priority code while playing/paused, + // or a seek happened before the media loaded. In either case, seek to the saved time. + if(mPreviousMediaTime != 0.0f) + { + seek(mPreviousMediaTime); + } + + if(mPreviousMediaState == MEDIA_PLAYING) + { + // This media was playing before this instance was unloaded. + start(); + } + else if(mPreviousMediaState == MEDIA_PAUSED) + { + // This media was paused before this instance was unloaded. + pause(); + } + else + { + // No relevant previous media play state -- if we're loading the URL, we want to start playing. + start(); + } + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::executeJavaScript(const std::string& code) +{ + if (mMediaSource) + { + mMediaSource->executeJavaScript(code); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::setSize(int width, int height) +{ + mMediaWidth = width; + mMediaHeight = height; + if(mMediaSource) + { + mMediaSource->setSize(width, height); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::showNotification(LLNotificationPtr notify) +{ + mNotification = notify; +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::hideNotification() +{ + mNotification.reset(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::play() +{ + // If the media source isn't there, try to initialize it and load an URL. + if(mMediaSource == NULL) + { + if(!initializeMedia(mMimeType)) + { + // This may be the case where the plugin's priority is PRIORITY_UNLOADED + return; + } + + // Only do this if the media source was just loaded. + loadURI(); + } + + // always start the media + start(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::stop() +{ + if(mMediaSource) + { + mMediaSource->stop(); + // destroyMediaSource(); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::pause() +{ + if(mMediaSource) + { + mMediaSource->pause(); + } + else + { + mPreviousMediaState = MEDIA_PAUSED; + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::start() +{ + if(mMediaSource) + { + mMediaSource->start(); + } + else + { + mPreviousMediaState = MEDIA_PLAYING; + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::seek(F32 time) +{ + if(mMediaSource) + { + mMediaSource->seek(time); + } + else + { + // Save the seek time to be set when the media is loaded. + mPreviousMediaTime = time; + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::skipBack(F32 step_scale) +{ + if(mMediaSource) + { + if(mMediaSource->pluginSupportsMediaTime()) + { + F64 back_step = mMediaSource->getCurrentTime() - (mMediaSource->getDuration()*step_scale); + if(back_step < 0.0) + { + back_step = 0.0; + } + mMediaSource->seek(back_step); + } + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::skipForward(F32 step_scale) +{ + if(mMediaSource) + { + if(mMediaSource->pluginSupportsMediaTime()) + { + F64 forward_step = mMediaSource->getCurrentTime() + (mMediaSource->getDuration()*step_scale); + if(forward_step > mMediaSource->getDuration()) + { + forward_step = mMediaSource->getDuration(); + } + mMediaSource->seek(forward_step); + } + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::setVolume(F32 volume) +{ + mRequestedVolume = volume; + updateVolume(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::setMute(bool mute) +{ + if (mute) + { + mPreviousVolume = mRequestedVolume; + setVolume(0.0); + } + else + { + setVolume(mPreviousVolume); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::updateVolume() +{ + LL_RECORD_BLOCK_TIME(FTM_MEDIA_UPDATE_VOLUME); + if(mMediaSource) + { + // always scale the volume by the global media volume + F32 volume = mRequestedVolume * LLViewerMedia::getInstance()->getVolume(); + + if (mProximityCamera > 0) + { + if (mProximityCamera > gSavedSettings.getF32("MediaRollOffMax")) + { + volume = 0; + } + else if (mProximityCamera > gSavedSettings.getF32("MediaRollOffMin")) + { + // attenuated_volume = 1 / (roll_off_rate * (d - min))^2 + // the +1 is there so that for distance 0 the volume stays the same + F64 adjusted_distance = mProximityCamera - gSavedSettings.getF32("MediaRollOffMin"); + F64 attenuation = 1.0 + (gSavedSettings.getF32("MediaRollOffRate") * adjusted_distance); + attenuation = 1.0 / (attenuation * attenuation); + // the attenuation multiplier should never be more than one since that would increase volume + volume = volume * llmin(1.0, attenuation); + } + } + + if (sOnlyAudibleTextureID == LLUUID::null || sOnlyAudibleTextureID == mTextureId) + { + mMediaSource->setVolume(volume); + } + else + { + mMediaSource->setVolume(0.0f); + } + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +F32 LLViewerMediaImpl::getVolume() +{ + return mRequestedVolume; +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::focus(bool focus) +{ + mHasFocus = focus; + + if (mMediaSource) + { + // call focus just for the hell of it, even though this apopears to be a nop + mMediaSource->focus(focus); + if (focus) + { + // spoof a mouse click to *actually* pass focus + // Don't do this anymore -- it actually clicks through now. +// mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_DOWN, 1, 1, 0); +// mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_UP, 1, 1, 0); + } + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::hasFocus() const +{ + // FIXME: This might be able to be a bit smarter by hooking into LLViewerMediaFocus, etc. + return mHasFocus; +} + +std::string LLViewerMediaImpl::getCurrentMediaURL() +{ + if(!mCurrentMediaURL.empty()) + { + return mCurrentMediaURL; + } + + return mMediaURL; +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::clearCache() +{ + if(mMediaSource) + { + mMediaSource->clear_cache(); + } + else + { + mClearCache = true; + } +} + + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::setPageZoomFactor( double factor ) +{ + if(mMediaSource && factor != mZoomFactor) + { + mZoomFactor = factor; + mMediaSource->set_page_zoom_factor( factor ); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::mouseDown(S32 x, S32 y, MASK mask, S32 button) +{ + scaleMouse(&x, &y); + mLastMouseX = x; + mLastMouseY = y; +// LL_INFOS() << "mouse down (" << x << ", " << y << ")" << LL_ENDL; + if (mMediaSource) + { + mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_DOWN, button, x, y, mask); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::mouseUp(S32 x, S32 y, MASK mask, S32 button) +{ + scaleMouse(&x, &y); + mLastMouseX = x; + mLastMouseY = y; +// LL_INFOS() << "mouse up (" << x << ", " << y << ")" << LL_ENDL; + if (mMediaSource) + { + mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_UP, button, x, y, mask); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::mouseMove(S32 x, S32 y, MASK mask) +{ + scaleMouse(&x, &y); + mLastMouseX = x; + mLastMouseY = y; +// LL_INFOS() << "mouse move (" << x << ", " << y << ")" << LL_ENDL; + if (mMediaSource) + { + mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_MOVE, 0, x, y, mask); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +//static +void LLViewerMediaImpl::scaleTextureCoords(const LLVector2& texture_coords, S32 *x, S32 *y) +{ + F32 texture_x = texture_coords.mV[VX]; + F32 texture_y = texture_coords.mV[VY]; + + // Deal with repeating textures by wrapping the coordinates into the range [0, 1.0) + texture_x = fmodf(texture_x, 1.0f); + if(texture_x < 0.0f) + texture_x = 1.0 + texture_x; + + texture_y = fmodf(texture_y, 1.0f); + if(texture_y < 0.0f) + texture_y = 1.0 + texture_y; + + // scale x and y to texel units. + *x = ll_round(texture_x * mMediaSource->getTextureWidth()); + *y = ll_round((1.0f - texture_y) * mMediaSource->getTextureHeight()); + + // Adjust for the difference between the actual texture height and the amount of the texture in use. + *y -= (mMediaSource->getTextureHeight() - mMediaSource->getHeight()); +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::mouseDown(const LLVector2& texture_coords, MASK mask, S32 button) +{ + if(mMediaSource) + { + S32 x, y; + scaleTextureCoords(texture_coords, &x, &y); + + mouseDown(x, y, mask, button); + } +} + +void LLViewerMediaImpl::mouseUp(const LLVector2& texture_coords, MASK mask, S32 button) +{ + if(mMediaSource) + { + S32 x, y; + scaleTextureCoords(texture_coords, &x, &y); + + mouseUp(x, y, mask, button); + } +} + +void LLViewerMediaImpl::mouseMove(const LLVector2& texture_coords, MASK mask) +{ + if(mMediaSource) + { + S32 x, y; + scaleTextureCoords(texture_coords, &x, &y); + + mouseMove(x, y, mask); + } +} + +void LLViewerMediaImpl::mouseDoubleClick(const LLVector2& texture_coords, MASK mask) +{ + if (mMediaSource) + { + S32 x, y; + scaleTextureCoords(texture_coords, &x, &y); + + mouseDoubleClick(x, y, mask); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::mouseDoubleClick(S32 x, S32 y, MASK mask, S32 button) +{ + scaleMouse(&x, &y); + mLastMouseX = x; + mLastMouseY = y; + if (mMediaSource) + { + mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_DOUBLE_CLICK, button, x, y, mask); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::scrollWheel(const LLVector2& texture_coords, S32 scroll_x, S32 scroll_y, MASK mask) +{ + if (mMediaSource) + { + S32 x, y; + scaleTextureCoords(texture_coords, &x, &y); + + scrollWheel(x, y, scroll_x, scroll_y, mask); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::scrollWheel(S32 x, S32 y, S32 scroll_x, S32 scroll_y, MASK mask) +{ + scaleMouse(&x, &y); + mLastMouseX = x; + mLastMouseY = y; + if (mMediaSource) + { + mMediaSource->scrollEvent(x, y, scroll_x, scroll_y, mask); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::onMouseCaptureLost() +{ + if (mMediaSource) + { + mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_UP, 0, mLastMouseX, mLastMouseY, 0); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::handleMouseUp(S32 x, S32 y, MASK mask) +{ + // NOTE: this is called when the mouse is released when we have capture. + // Due to the way mouse coordinates are mapped to the object, we can't use the x and y coordinates that come in with the event. + + if(hasMouseCapture()) + { + // Release the mouse -- this will also send a mouseup to the media + gFocusMgr.setMouseCapture( nullptr ); + } + + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::updateJavascriptObject() +{ + static LLFrameTimer timer ; + + if ( mMediaSource ) + { + // flag to expose this information to internal browser or not. + bool enable = gSavedSettings.getBOOL("BrowserEnableJSObject"); + + if(!enable) + { + return ; //no need to go further. + } + + if(timer.getElapsedTimeF32() < 1.0f) + { + return ; //do not update more than once per second. + } + timer.reset() ; + + mMediaSource->jsEnableObject( enable ); + + // these values are only menaingful after login so don't set them before + bool logged_in = LLLoginInstance::getInstance()->authSuccess(); + if ( logged_in ) + { + // current location within a region + LLVector3 agent_pos = gAgent.getPositionAgent(); + double x = agent_pos.mV[ VX ]; + double y = agent_pos.mV[ VY ]; + double z = agent_pos.mV[ VZ ]; + mMediaSource->jsAgentLocationEvent( x, y, z ); + + // current location within the grid + LLVector3d agent_pos_global = gAgent.getLastPositionGlobal(); + double global_x = agent_pos_global.mdV[ VX ]; + double global_y = agent_pos_global.mdV[ VY ]; + double global_z = agent_pos_global.mdV[ VZ ]; + mMediaSource->jsAgentGlobalLocationEvent( global_x, global_y, global_z ); + + // current agent orientation + double rotation = atan2( gAgent.getAtAxis().mV[VX], gAgent.getAtAxis().mV[VY] ); + double angle = rotation * RAD_TO_DEG; + if ( angle < 0.0f ) angle = 360.0f + angle; // TODO: has to be a better way to get orientation! + mMediaSource->jsAgentOrientationEvent( angle ); + + // current region agent is in + std::string region_name(""); + LLViewerRegion* region = gAgent.getRegion(); + if ( region ) + { + region_name = region->getName(); + }; + mMediaSource->jsAgentRegionEvent( region_name ); + } + + // language code the viewer is set to + mMediaSource->jsAgentLanguageEvent( LLUI::getLanguage() ); + + // maturity setting the agent has selected + if ( gAgent.prefersAdult() ) + mMediaSource->jsAgentMaturityEvent( "GMA" ); // Adult means see adult, mature and general content + else + if ( gAgent.prefersMature() ) + mMediaSource->jsAgentMaturityEvent( "GM" ); // Mature means see mature and general content + else + if ( gAgent.prefersPG() ) + mMediaSource->jsAgentMaturityEvent( "G" ); // PG means only see General content + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +const std::string& LLViewerMediaImpl::getName() const +{ + if (mMediaSource) + { + return mMediaSource->getMediaName(); + } + + return LLStringUtil::null; +}; + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::navigateBack() +{ + if (mMediaSource) + { + mMediaSource->browse_back(); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::navigateForward() +{ + if (mMediaSource) + { + mMediaSource->browse_forward(); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::navigateReload() +{ + navigateTo(getCurrentMediaURL(), "", true, false); +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::navigateHome() +{ + bool rediscover_mimetype = mHomeMimeType.empty(); + navigateTo(mHomeURL, mHomeMimeType, rediscover_mimetype, false); +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::unload() +{ + // Unload the media impl and clear its state. + destroyMediaSource(); + resetPreviousMediaState(); + mMediaURL.clear(); + mMimeType.clear(); + mCurrentMediaURL.clear(); + mCurrentMimeType.clear(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::navigateTo(const std::string& url, const std::string& mime_type, bool rediscover_type, bool server_request, bool clean_browser) +{ + cancelMimeTypeProbe(); + + if(mMediaURL != url) + { + // Don't carry media play state across distinct URLs. + resetPreviousMediaState(); + } + + // Always set the current URL and MIME type. + mMediaURL = url; + mMimeType = mime_type; + mCleanBrowser = clean_browser; + + // Clear the current media URL, since it will no longer be correct. + mCurrentMediaURL.clear(); + + // if mime type discovery was requested, we'll need to do it when the media loads + mNavigateRediscoverType = rediscover_type; + + // and if this was a server request, the navigate on load will also need to be one. + mNavigateServerRequest = server_request; + + // An explicit navigate resets the "failed" flag. + mMediaSourceFailed = false; + + if(mPriority == LLPluginClassMedia::PRIORITY_UNLOADED) + { + // Helpful to have media urls in log file. Shouldn't be spammy. + { + // Do not log the query parts + LLURI u(url); + std::string sanitized_url = (u.query().empty() ? url : u.scheme() + "://" + u.authority() + u.path()); + LL_INFOS() << "NOT LOADING media id= " << mTextureId << " url=" << sanitized_url << ", mime_type=" << mime_type << LL_ENDL; + } + + // This impl should not be loaded at this time. + LL_DEBUGS("PluginPriority") << this << "Not loading (PRIORITY_UNLOADED)" << LL_ENDL; + + return; + } + + navigateInternal(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::navigateInternal() +{ + // Helpful to have media urls in log file. Shouldn't be spammy. + { + // Do not log the query parts + LLURI u(mMediaURL); + std::string sanitized_url = (u.query().empty() ? mMediaURL : u.scheme() + "://" + u.authority() + u.path()); + LL_INFOS() << "media id= " << mTextureId << " url=" << sanitized_url << ", mime_type=" << mMimeType << LL_ENDL; + } + + if(mNavigateSuspended) + { + LL_WARNS() << "Deferring navigate." << LL_ENDL; + mNavigateSuspendedDeferred = true; + return; + } + + + if (!mMimeProbe.expired()) + { + LL_WARNS() << "MIME type probe already in progress -- bailing out." << LL_ENDL; + return; + } + + if(mNavigateServerRequest) + { + setNavState(MEDIANAVSTATE_SERVER_SENT); + } + else + { + setNavState(MEDIANAVSTATE_NONE); + } + + // If the caller has specified a non-empty MIME type, look that up in our MIME types list. + // If we have a plugin for that MIME type, use that instead of attempting auto-discovery. + // This helps in supporting legacy media content where the server the media resides on returns a bogus MIME type + // but the parcel owner has correctly set the MIME type in the parcel media settings. + + if(!mMimeType.empty() && (mMimeType != LLMIMETypes::getDefaultMimeType())) + { + std::string plugin_basename = LLMIMETypes::implType(mMimeType); + if(!plugin_basename.empty()) + { + // We have a plugin for this mime type + mNavigateRediscoverType = false; + } + } + + if(mNavigateRediscoverType) + { + + LLURI uri(mMediaURL); + std::string scheme = uri.scheme(); + + if(scheme.empty() || "http" == scheme || "https" == scheme) + { + LLCoros::instance().launch("LLViewerMediaImpl::mimeDiscoveryCoro", + boost::bind(&LLViewerMediaImpl::mimeDiscoveryCoro, this, mMediaURL)); + } + else if("data" == scheme || "file" == scheme || "about" == scheme) + { + // FIXME: figure out how to really discover the type for these schemes + // We use "data" internally for a text/html url for loading the login screen + if(initializeMedia(HTTP_CONTENT_TEXT_HTML)) + { + loadURI(); + } + } + else + { + // This catches 'rtsp://' urls + if(initializeMedia(scheme)) + { + loadURI(); + } + } + } + else if(initializeMedia(mMimeType)) + { + loadURI(); + } + else + { + LL_WARNS("Media") << "Couldn't navigate to: " << mMediaURL << " as there is no media type for: " << mMimeType << LL_ENDL; + } +} + +void LLViewerMediaImpl::mimeDiscoveryCoro(std::string url) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("mimeDiscoveryCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); + + // Increment our refcount so that we do not go away while the coroutine is active. + this->ref(); + + mMimeProbe = httpAdapter; + + httpOpts->setFollowRedirects(true); + httpOpts->setHeadersOnly(true); + + httpHeaders->append(HTTP_OUT_HEADER_ACCEPT, "*/*"); + httpHeaders->append(HTTP_OUT_HEADER_COOKIE, ""); + + LLSD result = httpAdapter->getRawAndSuspend(httpRequest, url, httpOpts, httpHeaders); + + mMimeProbe.reset(); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS() << "Error retrieving media headers." << LL_ENDL; + } + + if (this->getNumRefs() > 1) + { // if there is only a single ref count outstanding it will be the one we took out above... + // we can skip the rest of this routine + + LLSD resultHeaders = httpResults[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS]; + + const std::string& mediaType = resultHeaders[HTTP_IN_HEADER_CONTENT_TYPE].asStringRef(); + + std::string::size_type idx1 = mediaType.find_first_of(";"); + std::string mimeType = mediaType.substr(0, idx1); + + // We now no longer need to check the error code returned from the probe. + // If we have a mime type, use it. If not, default to the web plugin and let it handle error reporting. + // The probe was successful. + if (mimeType.empty()) + { + // Some sites don't return any content-type header at all. + // Treat an empty mime type as text/html. + mimeType = HTTP_CONTENT_TEXT_HTML; + } + + LL_DEBUGS() << "Media type \"" << mediaType << "\", mime type is \"" << mimeType << "\"" << LL_ENDL; + + // the call to initializeMedia may disconnect the responder, which will clear mMediaImpl. + // Make a local copy so we can call loadURI() afterwards. + + if (!mimeType.empty()) + { + if (initializeMedia(mimeType)) + { + loadURI(); + } + } + + } + else + { + LL_WARNS() << "LLViewerMediaImpl to be released." << LL_ENDL; + } + + this->unref(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::navigateStop() +{ + if(mMediaSource) + { + mMediaSource->browse_stop(); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::handleKeyHere(KEY key, MASK mask) +{ + bool result = false; + + if (mMediaSource) + { + // FIXME: THIS IS SO WRONG. + // Menu keys should be handled by the menu system and not passed to UI elements, but this is how LLTextEditor and LLLineEditor do it... + if (MASK_CONTROL & mask && key != KEY_LEFT && key != KEY_RIGHT && key != KEY_HOME && key != KEY_END) + { + result = true; + } + + if (!result) + { + LLSD native_key_data = gViewerWindow->getWindow()->getNativeKeyData(); + result = mMediaSource->keyEvent(LLPluginClassMedia::KEY_EVENT_DOWN, key, mask, native_key_data); + } + } + + return result; +} + +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::handleKeyUpHere(KEY key, MASK mask) +{ + bool result = false; + + if (mMediaSource) + { + // FIXME: THIS IS SO WRONG. + // Menu keys should be handled by the menu system and not passed to UI elements, but this is how LLTextEditor and LLLineEditor do it... + if (MASK_CONTROL & mask && key != KEY_LEFT && key != KEY_RIGHT && key != KEY_HOME && key != KEY_END) + { + result = true; + } + + if (!result) + { + LLSD native_key_data = gViewerWindow->getWindow()->getNativeKeyData(); + result = mMediaSource->keyEvent(LLPluginClassMedia::KEY_EVENT_UP, key, mask, native_key_data); + } + } + + return result; +} + +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::handleUnicodeCharHere(llwchar uni_char) +{ + bool result = false; + + if (mMediaSource) + { + // only accept 'printable' characters, sigh... + if (uni_char >= 32 // discard 'control' characters + && uni_char != 127) // SDL thinks this is 'delete' - yuck. + { + LLSD native_key_data = gViewerWindow->getWindow()->getNativeKeyData(); + + mMediaSource->textInput(wstring_to_utf8str(LLWString(1, uni_char)), gKeyboard->currentMask(false), native_key_data); + } + } + + return result; +} + +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::canNavigateForward() +{ + bool result = false; + if (mMediaSource) + { + result = mMediaSource->getHistoryForwardAvailable(); + } + return result; +} + +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::canNavigateBack() +{ + bool result = false; + if (mMediaSource) + { + result = mMediaSource->getHistoryBackAvailable(); + } + return result; +} + +////////////////////////////////////////////////////////////////////////////////////////// +static LLTrace::BlockTimerStatHandle FTM_MEDIA_DO_UPDATE("Do Update"); +static LLTrace::BlockTimerStatHandle FTM_MEDIA_GET_DATA("Get Data"); +static LLTrace::BlockTimerStatHandle FTM_MEDIA_SET_SUBIMAGE("Set Subimage"); + + +void LLViewerMediaImpl::update() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA; //LL_RECORD_BLOCK_TIME(FTM_MEDIA_DO_UPDATE); + if(mMediaSource == NULL) + { + if(mPriority == LLPluginClassMedia::PRIORITY_UNLOADED) + { + // This media source should not be loaded. + } + else if(mPriority <= LLPluginClassMedia::PRIORITY_SLIDESHOW) + { + // Don't load new instances that are at PRIORITY_SLIDESHOW or below. They're just kept around to preserve state. + } + else if (!mMimeProbe.expired()) + { + // this media source is doing a MIME type probe -- don't try loading it again. + } + else + { + // This media may need to be loaded. + if(sMediaCreateTimer.hasExpired()) + { + LL_DEBUGS("PluginPriority") << this << ": creating media based on timer expiration" << LL_ENDL; + createMediaSource(); + sMediaCreateTimer.setTimerExpirySec(LLVIEWERMEDIA_CREATE_DELAY); + } + else + { + LL_DEBUGS("PluginPriority") << this << ": NOT creating media (waiting on timer)" << LL_ENDL; + } + } + } + else + { + updateVolume(); + + // TODO: this is updated every frame - is this bad? + // Removing this as part of the post viewer64 media update + // Removed as not implemented in CEF embedded browser + // See MAINT-8194 for a more fuller description + // updateJavascriptObject(); + } + + + if(mMediaSource == NULL) + { + return; + } + + // Make sure a navigate doesn't happen during the idle -- it can cause mMediaSource to get destroyed, which can cause a crash. + setNavigateSuspended(true); + + mMediaSource->idle(); + + setNavigateSuspended(false); + + if(mMediaSource == NULL) + { + return; + } + + if(mMediaSource->isPluginExited()) + { + resetPreviousMediaState(); + destroyMediaSource(); + return; + } + + if(!mMediaSource->textureValid()) + { + return; + } + + if(mSuspendUpdates || !mVisible) + { + return; + } + + + LLViewerMediaTexture* media_tex; + U8* data; + S32 data_width; + S32 data_height; + S32 x_pos; + S32 y_pos; + S32 width; + S32 height; + + if (preMediaTexUpdate(media_tex, data, data_width, data_height, x_pos, y_pos, width, height)) + { + // Push update to worker thread + auto main_queue = LLImageGLThread::sEnabledMedia ? mMainQueue.lock() : nullptr; + if (main_queue) + { + mTextureUpdatePending = true; + ref(); // protect texture from deletion while active on bg queue + media_tex->ref(); + main_queue->postTo( + mTexUpdateQueue, // Worker thread queue + [=]() // work done on update worker thread + { +#if LL_IMAGEGL_THREAD_CHECK + media_tex->getGLTexture()->mActiveThread = LLThread::currentID(); +#endif + doMediaTexUpdate(media_tex, data, data_width, data_height, x_pos, y_pos, width, height, true); + }, + [=]() // callback to main thread + { +#if LL_IMAGEGL_THREAD_CHECK + media_tex->getGLTexture()->mActiveThread = LLThread::currentID(); +#endif + mTextureUpdatePending = false; + media_tex->unref(); + unref(); + }); + } + else + { + doMediaTexUpdate(media_tex, data, data_width, data_height, x_pos, y_pos, width, height, false); // otherwise, update on main thread + } + } +} + +bool LLViewerMediaImpl::preMediaTexUpdate(LLViewerMediaTexture*& media_tex, U8*& data, S32& data_width, S32& data_height, S32& x_pos, S32& y_pos, S32& width, S32& height) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA; + + bool retval = false; + + if (!mTextureUpdatePending) + { + media_tex = updateMediaImage(); + + if (media_tex && mMediaSource) + { + LLRect dirty_rect; + S32 media_width = mMediaSource->getTextureWidth(); + S32 media_height = mMediaSource->getTextureHeight(); + //S32 media_depth = mMediaSource->getTextureDepth(); + + // Since we're updating this texture, we know it's playing. Tell the texture to do its replacement magic so it gets rendered. + media_tex->setPlaying(true); + + if (mMediaSource->getDirty(&dirty_rect)) + { + // Constrain the dirty rect to be inside the texture + x_pos = llmax(dirty_rect.mLeft, 0); + y_pos = llmax(dirty_rect.mBottom, 0); + width = llmin(dirty_rect.mRight, media_width) - x_pos; + height = llmin(dirty_rect.mTop, media_height) - y_pos; + + if (width > 0 && height > 0) + { + data = mMediaSource->getBitsData(); + data_width = mMediaSource->getWidth(); + data_height = mMediaSource->getHeight(); + + if (data != NULL) + { + // data is ready to be copied to GL + retval = true; + } + } + + mMediaSource->resetDirty(); + } + } + } + + return retval; +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::doMediaTexUpdate(LLViewerMediaTexture* media_tex, U8* data, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height, bool sync) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA; + LLMutexLock lock(&mLock); // don't allow media source tear-down during update + + // wrap "data" in an LLImageRaw but do NOT make a copy + LLPointer raw = new LLImageRaw(data, media_tex->getWidth(), media_tex->getHeight(), media_tex->getComponents(), true); + + // *NOTE: Recreating the GL texture each media update may seem wasteful + // (note the texture creation in preMediaTexUpdate), however, it apparently + // prevents GL calls from blocking, due to poor bookkeeping of state of + // updated textures by the OpenGL implementation. (Windows 10/Nvidia) + // -Cosmic,2023-04-04 + // Allocate GL texture based on LLImageRaw but do NOT copy to GL + LLGLuint tex_name = 0; + media_tex->createGLTexture(0, raw, 0, true, LLGLTexture::OTHER, true, &tex_name); + + // copy just the subimage covered by the image raw to GL + media_tex->setSubImage(data, data_width, data_height, x_pos, y_pos, width, height, tex_name); + + if (sync) + { + media_tex->getGLTexture()->syncToMainThread(tex_name); + } + else + { + media_tex->getGLTexture()->syncTexName(tex_name); + } + + // release the data pointer before freeing raw so LLImageRaw destructor doesn't + // free memory at data pointer + raw->releaseData(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::updateImagesMediaStreams() +{ +} + +////////////////////////////////////////////////////////////////////////////////////////// +LLViewerMediaTexture* LLViewerMediaImpl::updateMediaImage() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA; + llassert(!gCubeSnapshot); + if (!mMediaSource) + { + return nullptr; // not ready for updating + } + + //llassert(!mTextureId.isNull()); + // *TODO: Consider enabling mipmaps (they have been disabled for a long time). Likely has a significant performance impact for tiled/high texture repeat media. Mip generation in a shader may also be an option if necessary. + LLViewerMediaTexture* media_tex = LLViewerTextureManager::getMediaTexture( mTextureId, USE_MIPMAPS ); + + if ( mNeedsNewTexture + || (media_tex->getWidth() != mMediaSource->getTextureWidth()) + || (media_tex->getHeight() != mMediaSource->getTextureHeight()) + || (mTextureUsedWidth != mMediaSource->getWidth()) + || (mTextureUsedHeight != mMediaSource->getHeight()) + ) + { + LL_DEBUGS("Media") << "initializing media placeholder" << LL_ENDL; + LL_DEBUGS("Media") << "movie image id " << mTextureId << LL_ENDL; + + int texture_width = mMediaSource->getTextureWidth(); + int texture_height = mMediaSource->getTextureHeight(); + int texture_depth = mMediaSource->getTextureDepth(); + + // MEDIAOPT: check to see if size actually changed before doing work + media_tex->destroyGLTexture(); + + // MEDIAOPT: seems insane that we actually have to make an imageraw then + // immediately discard it + LLPointer raw = new LLImageRaw(texture_width, texture_height, texture_depth); + // Clear the texture to the background color, ignoring alpha. + // convert background color channels from [0.0, 1.0] to [0, 255]; + raw->clear(int(mBackgroundColor.mV[VX] * 255.0f), int(mBackgroundColor.mV[VY] * 255.0f), int(mBackgroundColor.mV[VZ] * 255.0f), 0xff); + + // ask media source for correct GL image format constants + media_tex->setExplicitFormat(mMediaSource->getTextureFormatInternal(), + mMediaSource->getTextureFormatPrimary(), + mMediaSource->getTextureFormatType(), + mMediaSource->getTextureFormatSwapBytes()); + + int discard_level = 0; + media_tex->createGLTexture(discard_level, raw); + + // MEDIAOPT: set this dynamically on play/stop + // FIXME +// media_tex->mIsMediaTexture = true; + mNeedsNewTexture = false; + + // If the amount of the texture being drawn by the media goes down in either width or height, + // recreate the texture to avoid leaving parts of the old image behind. + mTextureUsedWidth = mMediaSource->getWidth(); + mTextureUsedHeight = mMediaSource->getHeight(); + } + return media_tex; +} + + +////////////////////////////////////////////////////////////////////////////////////////// +LLUUID LLViewerMediaImpl::getMediaTextureID() const +{ + return mTextureId; +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::setVisible(bool visible) +{ + mVisible = visible; + + if(mVisible) + { + if(mMediaSource && mMediaSource->isPluginExited()) + { + destroyMediaSource(); + } + + if(!mMediaSource) + { + createMediaSource(); + } + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::mouseCapture() +{ + gFocusMgr.setMouseCapture(this); +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::scaleMouse(S32 *mouse_x, S32 *mouse_y) +{ +#if 0 + S32 media_width, media_height; + S32 texture_width, texture_height; + getMediaSize( &media_width, &media_height ); + getTextureSize( &texture_width, &texture_height ); + S32 y_delta = texture_height - media_height; + + *mouse_y -= y_delta; +#endif +} + + + +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::isMediaTimeBased() +{ + bool result = false; + + if(mMediaSource) + { + result = mMediaSource->pluginSupportsMediaTime(); + } + + return result; +} + +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::isMediaPlaying() +{ + bool result = false; + + if(mMediaSource) + { + EMediaStatus status = mMediaSource->getStatus(); + if(status == MEDIA_PLAYING || status == MEDIA_LOADING) + result = true; + } + + return result; +} +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::isMediaPaused() +{ + bool result = false; + + if(mMediaSource) + { + if(mMediaSource->getStatus() == MEDIA_PAUSED) + result = true; + } + + return result; +} + +////////////////////////////////////////////////////////////////////////////////////////// +// +bool LLViewerMediaImpl::hasMedia() const +{ + return mMediaSource != NULL; +} + +////////////////////////////////////////////////////////////////////////////////////////// +// +void LLViewerMediaImpl::resetPreviousMediaState() +{ + mPreviousMediaState = MEDIA_NONE; + mPreviousMediaTime = 0.0f; +} + + +////////////////////////////////////////////////////////////////////////////////////////// +// +void LLViewerMediaImpl::setDisabled(bool disabled, bool forcePlayOnEnable) +{ + if(mIsDisabled != disabled) + { + // Only do this on actual state transitions. + mIsDisabled = disabled; + + if(mIsDisabled) + { + // We just disabled this media. Clear all state. + unload(); + } + else + { + // We just (re)enabled this media. Do a navigate if auto-play is in order. + if(isAutoPlayable() || forcePlayOnEnable) + { + navigateTo(mMediaEntryURL, "", true, true); + } + } + + } +}; + +////////////////////////////////////////////////////////////////////////////////////////// +// +bool LLViewerMediaImpl::isForcedUnloaded() const +{ + if(mIsMuted || mMediaSourceFailed || mIsDisabled) + { + return true; + } + + // If this media's class is not supposed to be shown, unload + if (!shouldShowBasedOnClass() || isObscured()) + { + return true; + } + + return false; +} + +////////////////////////////////////////////////////////////////////////////////////////// +// +bool LLViewerMediaImpl::isPlayable() const +{ + if(isForcedUnloaded()) + { + // All of the forced-unloaded criteria also imply not playable. + return false; + } + + if(hasMedia()) + { + // Anything that's already playing is, by definition, playable. + return true; + } + + if(!mMediaURL.empty()) + { + // If something has navigated the instance, it's ready to be played. + return true; + } + + return false; +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::handleMediaEvent(LLPluginClassMedia* plugin, LLPluginClassMediaOwner::EMediaEvent event) +{ + bool pass_through = true; + switch(event) + { + case MEDIA_EVENT_CLICK_LINK_NOFOLLOW: + { + LL_DEBUGS("Media") << "MEDIA_EVENT_CLICK_LINK_NOFOLLOW, uri is: " << plugin->getClickURL() << LL_ENDL; + std::string url = plugin->getClickURL(); + std::string nav_type = plugin->getClickNavType(); + LLURLDispatcher::dispatch(url, nav_type, NULL, mTrustedBrowser); + } + break; + case MEDIA_EVENT_CLICK_LINK_HREF: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CLICK_LINK_HREF, target is \"" << plugin->getClickTarget() << "\", uri is " << plugin->getClickURL() << LL_ENDL; + }; + break; + case MEDIA_EVENT_PLUGIN_FAILED_LAUNCH: + { + // The plugin failed to load properly. Make sure the timer doesn't retry. + // TODO: maybe mark this plugin as not loadable somehow? + mMediaSourceFailed = true; + + // Reset the last known state of the media to defaults. + resetPreviousMediaState(); + + // TODO: may want a different message for this case? + LLSD args; + args["PLUGIN"] = LLMIMETypes::implType(mCurrentMimeType); + LLNotificationsUtil::add("MediaPluginFailed", args); + } + break; + + case MEDIA_EVENT_PLUGIN_FAILED: + { + // The plugin crashed. + mMediaSourceFailed = true; + + // Reset the last known state of the media to defaults. + resetPreviousMediaState(); + + LLSD args; + args["PLUGIN"] = LLMIMETypes::implType(mCurrentMimeType); + // SJB: This is getting called every frame if the plugin fails to load, continuously respawining the alert! + //LLNotificationsUtil::add("MediaPluginFailed", args); + } + break; + + case MEDIA_EVENT_CURSOR_CHANGED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CURSOR_CHANGED, new cursor is " << plugin->getCursorName() << LL_ENDL; + + std::string cursor = plugin->getCursorName(); + mLastSetCursor = getCursorFromString(cursor); + } + break; + + case LLViewerMediaObserver::MEDIA_EVENT_FILE_DOWNLOAD: + { + LL_DEBUGS("Media") << "Media event - file download requested - filename is " << plugin->getFileDownloadFilename() << LL_ENDL; + } + break; + + case LLViewerMediaObserver::MEDIA_EVENT_NAVIGATE_BEGIN: + { + LL_DEBUGS("Media") << "MEDIA_EVENT_NAVIGATE_BEGIN, uri is: " << plugin->getNavigateURI() << LL_ENDL; + hideNotification(); + + if(getNavState() == MEDIANAVSTATE_SERVER_SENT) + { + setNavState(MEDIANAVSTATE_SERVER_BEGUN); + } + else + { + setNavState(MEDIANAVSTATE_BEGUN); + } + } + break; + + case LLViewerMediaObserver::MEDIA_EVENT_NAVIGATE_COMPLETE: + { + LL_DEBUGS("Media") << "MEDIA_EVENT_NAVIGATE_COMPLETE, uri is: " << plugin->getNavigateURI() << LL_ENDL; + + std::string url = plugin->getNavigateURI(); + if(getNavState() == MEDIANAVSTATE_BEGUN) + { + if(mCurrentMediaURL == url) + { + // This is a navigate that takes us to the same url as the previous navigate. + setNavState(MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED_SPURIOUS); + } + else + { + mCurrentMediaURL = url; + setNavState(MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED); + } + } + else if(getNavState() == MEDIANAVSTATE_SERVER_BEGUN) + { + mCurrentMediaURL = url; + setNavState(MEDIANAVSTATE_SERVER_COMPLETE_BEFORE_LOCATION_CHANGED); + } + else + { + // all other cases need to leave the state alone. + } + } + break; + + case LLViewerMediaObserver::MEDIA_EVENT_LOCATION_CHANGED: + { + LL_DEBUGS("Media") << "MEDIA_EVENT_LOCATION_CHANGED, uri is: " << plugin->getLocation() << LL_ENDL; + + std::string url = plugin->getLocation(); + + if(getNavState() == MEDIANAVSTATE_BEGUN) + { + if(mCurrentMediaURL == url) + { + // This is a navigate that takes us to the same url as the previous navigate. + setNavState(MEDIANAVSTATE_FIRST_LOCATION_CHANGED_SPURIOUS); + } + else + { + mCurrentMediaURL = url; + setNavState(MEDIANAVSTATE_FIRST_LOCATION_CHANGED); + } + } + else if(getNavState() == MEDIANAVSTATE_SERVER_BEGUN) + { + mCurrentMediaURL = url; + setNavState(MEDIANAVSTATE_SERVER_FIRST_LOCATION_CHANGED); + } + else + { + bool internal_nav = false; + if (url != mCurrentMediaURL) + { + // Check if it is internal navigation + // Note: Not sure if we should detect internal navigations as 'address change', + // but they are not redirects and do not cause NAVIGATE_BEGIN (also see SL-1005) + size_t pos = url.find("#"); + if (pos != std::string::npos) + { + // assume that new link always have '#', so this is either + // transfer from 'link#1' to 'link#2' or from link to 'link#2' + // filter out cases like 'redirect?link' + std::string base_url = url.substr(0, pos); + pos = mCurrentMediaURL.find(base_url); + if (pos == 0) + { + // base link hasn't changed + internal_nav = true; + } + } + } + + if (internal_nav) + { + // Internal navigation by '#' + mCurrentMediaURL = url; + setNavState(MEDIANAVSTATE_FIRST_LOCATION_CHANGED); + } + else + { + // Don't track redirects. + setNavState(MEDIANAVSTATE_NONE); + } + } + } + break; + + case LLViewerMediaObserver::MEDIA_EVENT_PICK_FILE_REQUEST: + { + LL_DEBUGS("Media") << "Media event - file pick requested." << LL_ENDL; + + init_threaded_picker_load_dialog(plugin, LLFilePicker::FFLOAD_ALL, plugin->getIsMultipleFilePick()); + } + break; + + case LLViewerMediaObserver::MEDIA_EVENT_AUTH_REQUEST: + { + LLNotification::Params auth_request_params; + auth_request_params.name = "AuthRequest"; + + // pass in host name and realm for site (may be zero length but will always exist) + LLSD args; + LLURL raw_url( plugin->getAuthURL().c_str() ); + args["HOST_NAME"] = raw_url.getAuthority(); + args["REALM"] = plugin->getAuthRealm(); + auth_request_params.substitutions = args; + + auth_request_params.payload = LLSD().with("media_id", mTextureId); + auth_request_params.functor.function = boost::bind(&LLViewerMedia::authSubmitCallback, _1, _2); + LLNotifications::instance().add(auth_request_params); + }; + break; + + case LLViewerMediaObserver::MEDIA_EVENT_CLOSE_REQUEST: + { + std::string uuid = plugin->getClickUUID(); + + LL_INFOS() << "MEDIA_EVENT_CLOSE_REQUEST for uuid " << uuid << LL_ENDL; + + if(uuid.empty()) + { + // This close request is directed at this instance, let it fall through. + } + else + { + // This close request is directed at another instance + pass_through = false; + LLFloaterWebContent::closeRequest(uuid); + } + } + break; + + case LLViewerMediaObserver::MEDIA_EVENT_GEOMETRY_CHANGE: + { + std::string uuid = plugin->getClickUUID(); + + LL_INFOS() << "MEDIA_EVENT_GEOMETRY_CHANGE for uuid " << uuid << LL_ENDL; + + if(uuid.empty()) + { + // This geometry change request is directed at this instance, let it fall through. + } + else + { + // This request is directed at another instance + pass_through = false; + LLFloaterWebContent::geometryChanged(uuid, plugin->getGeometryX(), plugin->getGeometryY(), plugin->getGeometryWidth(), plugin->getGeometryHeight()); + } + } + break; + + default: + break; + } + + if(pass_through) + { + // Just chain the event to observers. + emitEvent(plugin, event); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// virtual +void +LLViewerMediaImpl::cut() +{ + if (mMediaSource) + mMediaSource->cut(); +} + +//////////////////////////////////////////////////////////////////////////////// +// virtual +bool +LLViewerMediaImpl::canCut() const +{ + if (mMediaSource) + return mMediaSource->canCut(); + else + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// virtual +void +LLViewerMediaImpl::copy() +{ + if (mMediaSource) + mMediaSource->copy(); +} + +//////////////////////////////////////////////////////////////////////////////// +// virtual +bool +LLViewerMediaImpl::canCopy() const +{ + if (mMediaSource) + return mMediaSource->canCopy(); + else + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// virtual +void +LLViewerMediaImpl::paste() +{ + if (mMediaSource) + mMediaSource->paste(); +} + +//////////////////////////////////////////////////////////////////////////////// +// virtual +bool +LLViewerMediaImpl::canPaste() const +{ + if (mMediaSource) + return mMediaSource->canPaste(); + else + return false; +} + +void LLViewerMediaImpl::setUpdated(bool updated) +{ + mIsUpdated = updated ; +} + +bool LLViewerMediaImpl::isUpdated() +{ + return mIsUpdated ; +} + +static LLTrace::BlockTimerStatHandle FTM_MEDIA_CALCULATE_INTEREST("Calculate Interest"); + +void LLViewerMediaImpl::calculateInterest() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA; //LL_RECORD_BLOCK_TIME(FTM_MEDIA_CALCULATE_INTEREST); + LLViewerMediaTexture* texture = LLViewerTextureManager::findMediaTexture( mTextureId ); + + llassert(!gCubeSnapshot); + + if(texture != NULL) + { + mInterest = texture->getMaxVirtualSize(); + } + else + { + // This will be a relatively common case now, since it will always be true for unloaded media. + mInterest = 0.0f; + } + + // Calculate distance from the avatar, for use in the proximity calculation. + mProximityDistance = 0.0f; + mProximityCamera = 0.0f; + if(!mObjectList.empty()) + { + // Just use the first object in the list. We could go through the list and find the closest object, but this should work well enough. + std::list< LLVOVolume* >::iterator iter = mObjectList.begin() ; + LLVOVolume* objp = *iter ; + llassert_always(objp != NULL) ; + + // The distance calculation is invalid for HUD attachments -- leave both mProximityDistance and mProximityCamera at 0 for them. + if(!objp->isHUDAttachment()) + { + LLVector3d obj_global = objp->getPositionGlobal() ; + LLVector3d agent_global = gAgent.getPositionGlobal() ; + LLVector3d global_delta = agent_global - obj_global ; + mProximityDistance = global_delta.magVecSquared(); // use distance-squared because it's cheaper and sorts the same. + + static LLUICachedControl mEarLocation("MediaSoundsEarLocation", 0); + LLVector3d ear_position; + switch(mEarLocation) + { + case 0: + default: + ear_position = gAgentCamera.getCameraPositionGlobal(); + break; + + case 1: + ear_position = agent_global; + break; + } + LLVector3d camera_delta = ear_position - obj_global; + mProximityCamera = camera_delta.magVec(); + } + } + + if(mNeedsMuteCheck) + { + // Check all objects this instance is associated with, and those objects' owners, against the mute list + mIsMuted = false; + + std::list< LLVOVolume* >::iterator iter = mObjectList.begin() ; + for(; iter != mObjectList.end() ; ++iter) + { + LLVOVolume *obj = *iter; + llassert(obj); + if (!obj) continue; + if(LLMuteList::getInstance() && + LLMuteList::getInstance()->isMuted(obj->getID())) + { + mIsMuted = true; + } + else + { + // We won't have full permissions data for all objects. Attempt to mute objects when we can tell their owners are muted. + if (LLSelectMgr::getInstance()) + { + LLPermissions* obj_perm = LLSelectMgr::getInstance()->findObjectPermissions(obj); + if(obj_perm) + { + if(LLMuteList::getInstance() && + LLMuteList::getInstance()->isMuted(obj_perm->getOwner())) + mIsMuted = true; + } + } + } + } + + mNeedsMuteCheck = false; + } +} + +F64 LLViewerMediaImpl::getApproximateTextureInterest() +{ + F64 result = 0.0f; + + if(mMediaSource) + { + result = mMediaSource->getFullWidth(); + result *= mMediaSource->getFullHeight(); + } + else + { + // No media source is loaded -- all we have to go on is the texture size that has been set on the impl, if any. + result = mMediaWidth; + result *= mMediaHeight; + } + + return result; +} + +void LLViewerMediaImpl::setUsedInUI(bool used_in_ui) +{ + mUsedInUI = used_in_ui; + + // HACK: Force elements used in UI to load right away. + // This fixes some issues where UI code that uses the browser instance doesn't expect it to be unloaded. + if(mUsedInUI && (mPriority == LLPluginClassMedia::PRIORITY_UNLOADED)) + { + if(getVisible()) + { + setPriority(LLPluginClassMedia::PRIORITY_NORMAL); + } + else + { + setPriority(LLPluginClassMedia::PRIORITY_HIDDEN); + } + + createMediaSource(); + } +}; + +void LLViewerMediaImpl::setBackgroundColor(LLColor4 color) +{ + mBackgroundColor = color; + + if(mMediaSource) + { + mMediaSource->setBackgroundColor(mBackgroundColor); + } +}; + +F64 LLViewerMediaImpl::getCPUUsage() const +{ + F64 result = 0.0f; + + if(mMediaSource) + { + result = mMediaSource->getCPUUsage(); + } + + return result; +} + +void LLViewerMediaImpl::setPriority(LLPluginClassMedia::EPriority priority) +{ + if(mPriority != priority) + { + LL_DEBUGS("PluginPriority") + << "changing priority of media id " << mTextureId + << " from " << LLPluginClassMedia::priorityToString(mPriority) + << " to " << LLPluginClassMedia::priorityToString(priority) + << LL_ENDL; + } + + mPriority = priority; + + if(priority == LLPluginClassMedia::PRIORITY_UNLOADED) + { + if(mMediaSource) + { + // Need to unload the media source + + // First, save off previous media state + mPreviousMediaState = mMediaSource->getStatus(); + mPreviousMediaTime = mMediaSource->getCurrentTime(); + + destroyMediaSource(); + } + } + + if(mMediaSource) + { + mMediaSource->setPriority(mPriority); + } + + // NOTE: loading (or reloading) media sources whose priority has risen above PRIORITY_UNLOADED is done in update(). +} + +void LLViewerMediaImpl::setLowPrioritySizeLimit(int size) +{ + if(mMediaSource) + { + mMediaSource->setLowPrioritySizeLimit(size); + } +} + +void LLViewerMediaImpl::setNavState(EMediaNavState state) +{ + mMediaNavState = state; + + switch (state) + { + case MEDIANAVSTATE_NONE: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_NONE" << LL_ENDL; break; + case MEDIANAVSTATE_BEGUN: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_BEGUN" << LL_ENDL; break; + case MEDIANAVSTATE_FIRST_LOCATION_CHANGED: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_FIRST_LOCATION_CHANGED" << LL_ENDL; break; + case MEDIANAVSTATE_FIRST_LOCATION_CHANGED_SPURIOUS: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_FIRST_LOCATION_CHANGED_SPURIOUS" << LL_ENDL; break; + case MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED" << LL_ENDL; break; + case MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED_SPURIOUS: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED_SPURIOUS" << LL_ENDL; break; + case MEDIANAVSTATE_SERVER_SENT: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_SERVER_SENT" << LL_ENDL; break; + case MEDIANAVSTATE_SERVER_BEGUN: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_SERVER_BEGUN" << LL_ENDL; break; + case MEDIANAVSTATE_SERVER_FIRST_LOCATION_CHANGED: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_SERVER_FIRST_LOCATION_CHANGED" << LL_ENDL; break; + case MEDIANAVSTATE_SERVER_COMPLETE_BEFORE_LOCATION_CHANGED: LL_DEBUGS("Media") << "Setting nav state to MEDIANAVSTATE_SERVER_COMPLETE_BEFORE_LOCATION_CHANGED" << LL_ENDL; break; + } +} + +void LLViewerMediaImpl::setNavigateSuspended(bool suspend) +{ + if(mNavigateSuspended != suspend) + { + mNavigateSuspended = suspend; + if(!suspend) + { + // We're coming out of suspend. If someone tried to do a navigate while suspended, do one now instead. + if(mNavigateSuspendedDeferred) + { + mNavigateSuspendedDeferred = false; + navigateInternal(); + } + } + } +} + +void LLViewerMediaImpl::cancelMimeTypeProbe() +{ + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t probeAdapter = mMimeProbe.lock(); + + if (probeAdapter) + probeAdapter->cancelSuspendedOperation(); + +} + +void LLViewerMediaImpl::addObject(LLVOVolume* obj) +{ + std::list< LLVOVolume* >::iterator iter = mObjectList.begin() ; + for(; iter != mObjectList.end() ; ++iter) + { + if(*iter == obj) + { + return ; //already in the list. + } + } + + mObjectList.push_back(obj) ; + mNeedsMuteCheck = true; +} + +void LLViewerMediaImpl::removeObject(LLVOVolume* obj) +{ + mObjectList.remove(obj) ; + mNeedsMuteCheck = true; +} + +const std::list< LLVOVolume* >* LLViewerMediaImpl::getObjectList() const +{ + return &mObjectList ; +} + +LLVOVolume *LLViewerMediaImpl::getSomeObject() +{ + LLVOVolume *result = NULL; + + std::list< LLVOVolume* >::iterator iter = mObjectList.begin() ; + if(iter != mObjectList.end()) + { + result = *iter; + } + + return result; +} + +void LLViewerMediaImpl::setTextureID(LLUUID id) +{ + if(id != mTextureId) + { + if(mTextureId.notNull()) + { + // Remove this item's entry from the map + sViewerMediaTextureIDMap.erase(mTextureId); + } + + if(id.notNull()) + { + sViewerMediaTextureIDMap.insert(LLViewerMedia::impl_id_map::value_type(id, this)); + } + + mTextureId = id; + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +// +bool LLViewerMediaImpl::isAutoPlayable() const +{ + return (mMediaAutoPlay && + gSavedSettings.getS32("ParcelMediaAutoPlayEnable") != 0 && + gSavedSettings.getBOOL("MediaTentativeAutoPlay")); +} + +////////////////////////////////////////////////////////////////////////////////////////// +// +bool LLViewerMediaImpl::shouldShowBasedOnClass() const +{ + // If this is parcel media or in the UI, return true always + if (getUsedInUI() || isParcelMedia()) return true; + + bool attached_to_another_avatar = isAttachedToAnotherAvatar(); + bool inside_parcel = isInAgentParcel(); + + // LL_INFOS() << " hasFocus = " << hasFocus() << + // " others = " << (attached_to_another_avatar && gSavedSettings.getBOOL(LLViewerMedia::SHOW_MEDIA_ON_OTHERS_SETTING)) << + // " within = " << (inside_parcel && gSavedSettings.getBOOL(LLViewerMedia::SHOW_MEDIA_WITHIN_PARCEL_SETTING)) << + // " outside = " << (!inside_parcel && gSavedSettings.getBOOL(LLViewerMedia::SHOW_MEDIA_OUTSIDE_PARCEL_SETTING)) << LL_ENDL; + + // If it has focus, we should show it + // This is incorrect, and causes EXT-6750 (disabled attachment media still plays) +// if (hasFocus()) +// return true; + + // If it is attached to an avatar and the pref is off, we shouldn't show it + if (attached_to_another_avatar) + { + static LLCachedControl show_media_on_others(gSavedSettings, LLViewerMedia::SHOW_MEDIA_ON_OTHERS_SETTING, false); + return show_media_on_others; + } + if (inside_parcel) + { + static LLCachedControl show_media_within_parcel(gSavedSettings, LLViewerMedia::SHOW_MEDIA_WITHIN_PARCEL_SETTING, true); + + return show_media_within_parcel; + } + else + { + static LLCachedControl show_media_outside_parcel(gSavedSettings, LLViewerMedia::SHOW_MEDIA_OUTSIDE_PARCEL_SETTING, true); + + return show_media_outside_parcel; + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +// +bool LLViewerMediaImpl::isObscured() const +{ + if (getUsedInUI() || isParcelMedia() || isAttachedToHUD()) return false; + + LLParcel* agent_parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if (!agent_parcel) + { + return false; + } + + if (agent_parcel->getObscureMOAP() && !isInAgentParcel()) + { + return true; + } + + return false; +} + +bool LLViewerMediaImpl::isAttachedToHUD() const +{ + std::list< LLVOVolume* >::const_iterator iter = mObjectList.begin(); + std::list< LLVOVolume* >::const_iterator end = mObjectList.end(); + for ( ; iter != end; iter++) + { + if ((*iter)->isHUDAttachment()) + { + return true; + } + } + return false; +} + +////////////////////////////////////////////////////////////////////////////////////////// +// +bool LLViewerMediaImpl::isAttachedToAnotherAvatar() const +{ + bool result = false; + + std::list< LLVOVolume* >::const_iterator iter = mObjectList.begin(); + std::list< LLVOVolume* >::const_iterator end = mObjectList.end(); + for ( ; iter != end; iter++) + { + if (isObjectAttachedToAnotherAvatar(*iter)) + { + result = true; + break; + } + } + return result; +} + +////////////////////////////////////////////////////////////////////////////////////////// +// +//static +bool LLViewerMediaImpl::isObjectAttachedToAnotherAvatar(LLVOVolume *obj) +{ + bool result = false; + LLXform *xform = obj; + // Walk up parent chain + while (NULL != xform) + { + LLViewerObject *object = dynamic_cast (xform); + if (NULL != object) + { + LLVOAvatar *avatar = object->asAvatar(); + if ((NULL != avatar) && (avatar != gAgentAvatarp)) + { + result = true; + break; + } + } + xform = xform->getParent(); + } + return result; +} + +////////////////////////////////////////////////////////////////////////////////////////// +// +bool LLViewerMediaImpl::isInAgentParcel() const +{ + bool result = false; + + std::list< LLVOVolume* >::const_iterator iter = mObjectList.begin(); + std::list< LLVOVolume* >::const_iterator end = mObjectList.end(); + for ( ; iter != end; iter++) + { + LLVOVolume *object = *iter; + if (LLViewerMediaImpl::isObjectInAgentParcel(object)) + { + result = true; + break; + } + } + return result; +} + +LLNotificationPtr LLViewerMediaImpl::getCurrentNotification() const +{ + return mNotification; +} + +////////////////////////////////////////////////////////////////////////////////////////// +// +// static +bool LLViewerMediaImpl::isObjectInAgentParcel(LLVOVolume *obj) +{ + return (LLViewerParcelMgr::getInstance()->inAgentParcel(obj->getPositionGlobal())); +} diff --git a/indra/newview/llviewermedia.h b/indra/newview/llviewermedia.h index 1d582ece61..c8780f1c10 100644 --- a/indra/newview/llviewermedia.h +++ b/indra/newview/llviewermedia.h @@ -1,505 +1,505 @@ -/** - * @file llviewermedia.h - * @brief Client interface to the media engine - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLVIEWERMEDIA_H -#define LLVIEWERMEDIA_H - -#include "llfocusmgr.h" -#include "lleditmenuhandler.h" - -#include "llpanel.h" -#include "llpluginclassmediaowner.h" - -#include "llviewermediaobserver.h" - -#include "llpluginclassmedia.h" -#include "v4color.h" -#include "llnotificationptr.h" - -#include "llurl.h" -#include "lleventcoro.h" -#include "llcoros.h" -#include "llcorehttputil.h" - -class LLViewerMediaImpl; -class LLUUID; -class LLViewerMediaTexture; -class LLMediaEntry; -class LLVOVolume; -class LLMimeDiscoveryResponder; - -typedef LLPointer viewer_media_t; -/////////////////////////////////////////////////////////////////////////////// -// -class LLViewerMediaEventEmitter -{ -public: - virtual ~LLViewerMediaEventEmitter(); - - bool addObserver( LLViewerMediaObserver* subject ); - bool remObserver( LLViewerMediaObserver* subject ); - virtual void emitEvent(LLPluginClassMedia* self, LLViewerMediaObserver::EMediaEvent event); - -private: - typedef std::list< LLViewerMediaObserver* > observerListType; - observerListType mObservers; -}; - -class LLViewerMediaImpl; - -class LLViewerMedia: public LLSingleton -{ - LLSINGLETON(LLViewerMedia); - ~LLViewerMedia(); - void initSingleton() override; - LOG_CLASS(LLViewerMedia); - -public: - // String to get/set media autoplay in gSavedSettings - static const char* AUTO_PLAY_MEDIA_SETTING; - static const char* SHOW_MEDIA_ON_OTHERS_SETTING; - static const char* SHOW_MEDIA_WITHIN_PARCEL_SETTING; - static const char* SHOW_MEDIA_OUTSIDE_PARCEL_SETTING; - - typedef std::list impl_list; - - typedef std::map impl_id_map; - - // Special case early init for just web browser component - // so we can show login screen. See .cpp file for details. JC - - viewer_media_t newMediaImpl(const LLUUID& texture_id, - S32 media_width = 0, - S32 media_height = 0, - U8 media_auto_scale = false, - U8 media_loop = false); - - viewer_media_t updateMediaImpl(LLMediaEntry* media_entry, const std::string& previous_url, bool update_from_self); - LLViewerMediaImpl* getMediaImplFromTextureID(const LLUUID& texture_id); - std::string getCurrentUserAgent(); - void updateBrowserUserAgent(); - bool handleSkinCurrentChanged(const LLSD& /*newvalue*/); - bool textureHasMedia(const LLUUID& texture_id); - void setVolume(F32 volume); - - // Is any media currently "showing"? Includes Parcel Media. Does not include media in the UI. - bool isAnyMediaShowing(); - // Shows if any media is playing, counts visible non time based media as playing. Does not include media in the UI. - bool isAnyMediaPlaying(); - // Set all media enabled or disabled, depending on val. Does not include media in the UI. - void setAllMediaEnabled(bool val); - // Set all media paused(stopped for non time based) or playing, depending on val. Does not include media in the UI. - void setAllMediaPaused(bool val); - - static void onIdle(void* dummy_arg = NULL); // updateMedia wrapper - void updateMedia(void* dummy_arg = NULL); - - F32 getVolume(); - void muteListChanged(); - bool isInterestingEnough(const LLVOVolume* object, const F64 &object_interest); - - // Returns the priority-sorted list of all media impls. - impl_list &getPriorityList(); - - // This is the comparitor used to sort the list. - static bool priorityComparitor(const LLViewerMediaImpl* i1, const LLViewerMediaImpl* i2); - - // These are just helper functions for the convenience of others working with media - bool hasInWorldMedia(); - std::string getParcelAudioURL(); - bool hasParcelMedia(); - bool hasParcelAudio(); - bool isParcelMediaPlaying(); - bool isParcelAudioPlaying(); - - static void authSubmitCallback(const LLSD& notification, const LLSD& response); - - // Clear all cookies for all plugins - void clearAllCookies(); - - // Clear all plugins' caches - void clearAllCaches(); - - // Set the "cookies enabled" flag for all loaded plugins - void setCookiesEnabled(bool enabled); - - // Set the proxy config for all loaded plugins - void setProxyConfig(bool enable, const std::string &host, int port); - - void openIDSetup(const std::string &openid_url, const std::string &openid_token); - void openIDCookieResponse(const std::string& url, const std::string &cookie); - - void proxyWindowOpened(const std::string &target, const std::string &uuid); - void proxyWindowClosed(const std::string &uuid); - - void createSpareBrowserMediaSource(); - LLPluginClassMedia* getSpareBrowserMediaSource(); - - void setOnlyAudibleMediaTextureID(const LLUUID& texture_id); - - LLSD getHeaders(); - LLCore::HttpHeaders::ptr_t getHttpHeaders(); - -private: - void onAuthSubmit(const LLSD& notification, const LLSD& response); - bool parseRawCookie(const std::string raw_cookie, std::string& name, std::string& value, std::string& path, bool& httponly, bool& secure); - void setOpenIDCookie(const std::string& url); - void onTeleportFinished(); - - static void openIDSetupCoro(std::string openidUrl, std::string openidToken); - static void getOpenIDCookieCoro(std::string url); - - bool mAnyMediaShowing; - bool mAnyMediaPlaying; - LLURL mOpenIDURL; - std::string mOpenIDCookie; - LLPluginClassMedia* mSpareBrowserMediaSource; - boost::signals2::connection mTeleportFinishConnection; -}; - -// Implementation functions not exported into header file -class LLViewerMediaImpl - : public LLMouseHandler, public LLRefCount, public LLPluginClassMediaOwner, public LLViewerMediaEventEmitter, public LLEditMenuHandler -{ - LOG_CLASS(LLViewerMediaImpl); -public: - - friend class LLViewerMedia; - - LLViewerMediaImpl( - const LLUUID& texture_id, - S32 media_width, - S32 media_height, - U8 media_auto_scale, - U8 media_loop); - - ~LLViewerMediaImpl(); - - // Override inherited version from LLViewerMediaEventEmitter - virtual void emitEvent(LLPluginClassMedia* self, LLViewerMediaObserver::EMediaEvent event); - - void createMediaSource(); - void destroyMediaSource(); - void setMediaType(const std::string& media_type); - bool initializeMedia(const std::string& mime_type); - bool initializePlugin(const std::string& media_type); - void loadURI(); - void executeJavaScript(const std::string& code); - LLPluginClassMedia* getMediaPlugin() { return mMediaSource.get(); } - void setSize(int width, int height); - - void showNotification(LLNotificationPtr notify); - void hideNotification(); - - void play(); - void stop(); - void pause(); - void start(); - void seek(F32 time); - void skipBack(F32 step_scale); - void skipForward(F32 step_scale); - void setVolume(F32 volume); - void setMute(bool mute); - void updateVolume(); - F32 getVolume(); - void focus(bool focus); - // True if the impl has user focus. - bool hasFocus() const; - void mouseDown(S32 x, S32 y, MASK mask, S32 button = 0); - void mouseUp(S32 x, S32 y, MASK mask, S32 button = 0); - void mouseMove(S32 x, S32 y, MASK mask); - void mouseDown(const LLVector2& texture_coords, MASK mask, S32 button = 0); - void mouseUp(const LLVector2& texture_coords, MASK mask, S32 button = 0); - void mouseMove(const LLVector2& texture_coords, MASK mask); - void mouseDoubleClick(const LLVector2& texture_coords, MASK mask); - void mouseDoubleClick(S32 x, S32 y, MASK mask, S32 button = 0); - void scrollWheel(const LLVector2& texture_coords, S32 scroll_x, S32 scroll_y, MASK mask); - void scrollWheel(S32 x, S32 y, S32 scroll_x, S32 scroll_y, MASK mask); - void mouseCapture(); - - void navigateBack(); - void navigateForward(); - void navigateReload(); - void navigateHome(); - void unload(); - void navigateTo(const std::string& url, const std::string& mime_type = "", bool rediscover_type = false, bool server_request = false, bool clean_browser = false); - void navigateInternal(); - void navigateStop(); - bool handleKeyHere(KEY key, MASK mask); - bool handleKeyUpHere(KEY key, MASK mask); - bool handleUnicodeCharHere(llwchar uni_char); - bool canNavigateForward(); - bool canNavigateBack(); - std::string getMediaURL() const { return mMediaURL; } - std::string getCurrentMediaURL(); - std::string getHomeURL() { return mHomeURL; } - std::string getMediaEntryURL() { return mMediaEntryURL; } - void setHomeURL(const std::string& home_url, const std::string& mime_type = LLStringUtil::null) { mHomeURL = home_url; mHomeMimeType = mime_type;}; - void clearCache(); - void setPageZoomFactor( double factor ); - double getPageZoomFactor() {return mZoomFactor;} - std::string getMimeType() { return mMimeType; } - void scaleMouse(S32 *mouse_x, S32 *mouse_y); - void scaleTextureCoords(const LLVector2& texture_coords, S32 *x, S32 *y); - - void update(); - bool preMediaTexUpdate(LLViewerMediaTexture*& media_tex, U8*& data, S32& data_width, S32& data_height, S32& x_pos, S32& y_pos, S32& width, S32& height); - void doMediaTexUpdate(LLViewerMediaTexture* media_tex, U8* data, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height, bool sync); - void updateImagesMediaStreams(); - LLUUID getMediaTextureID() const; - - void suspendUpdates(bool suspend) { mSuspendUpdates = suspend; } - void setVisible(bool visible); - bool getVisible() const { return mVisible; } - bool isVisible() const { return mVisible; } - - bool isMediaTimeBased(); - bool isMediaPlaying(); - bool isMediaPaused(); - bool hasMedia() const; - bool isMediaFailed() const { return mMediaSourceFailed; } - void setMediaFailed(bool val) { mMediaSourceFailed = val; } - void resetPreviousMediaState(); - - void setDisabled(bool disabled, bool forcePlayOnEnable = false); - bool isMediaDisabled() const { return mIsDisabled; }; - - void setInNearbyMediaList(bool in_list) { mInNearbyMediaList = in_list; } - bool getInNearbyMediaList() { return mInNearbyMediaList; } - - // returns true if this instance should not be loaded (disabled, muted object, crashed, etc.) - bool isForcedUnloaded() const; - - // returns true if this instance could be playable based on autoplay setting, current load state, etc. - bool isPlayable() const; - - void setIsParcelMedia(bool is_parcel_media) { mIsParcelMedia = is_parcel_media; } - bool isParcelMedia() const { return mIsParcelMedia; } - - ECursorType getLastSetCursor() { return mLastSetCursor; } - - void setTarget(const std::string& target) { mTarget = target; } - - // utility function to create a ready-to-use media instance from a desired media type. - static LLPluginClassMedia* newSourceFromMediaType(std::string media_type, LLPluginClassMediaOwner *owner /* may be NULL */, S32 default_width, S32 default_height, F64 zoom_factor, const std::string target = LLStringUtil::null, bool clean_browser = false); - - // Internally set our desired browser user agent string, including - // the Second Life version and skin name. Used because we can - // switch skins without restarting the app. - static void updateBrowserUserAgent(); - - // Callback for when the SkinCurrent control is changed to - // switch the user agent string to indicate the new skin. - static bool handleSkinCurrentChanged(const LLSD& newvalue); - - // need these to handle mouseup... - /*virtual*/ void onMouseCaptureLost(); - /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); - - // Grr... the only thing I want as an LLMouseHandler are the onMouseCaptureLost and handleMouseUp calls. - // Sadly, these are all pure virtual, so I have to supply implementations here: - /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask) { return false; }; - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask) { return false; }; - /*virtual*/ bool handleScrollWheel(S32 x, S32 y, S32 clicks) { return false; }; - /*virtual*/ bool handleScrollHWheel(S32 x, S32 y, S32 clicks) { return false; }; - /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask) { return false; }; - /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask) { return false; }; - /*virtual*/ bool handleRightMouseUp(S32 x, S32 y, MASK mask) { return false; }; - /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask) { return false; }; - /*virtual*/ bool handleMiddleMouseDown(S32 x, S32 y, MASK mask) { return false; }; - /*virtual*/ bool handleMiddleMouseUp(S32 x, S32 y, MASK mask) {return false; }; - /*virtual*/ const std::string& getName() const; - - /*virtual*/ void screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const {}; - /*virtual*/ void localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const {}; - /*virtual*/ bool hasMouseCapture() { return gFocusMgr.getMouseCapture() == this; }; - - // Inherited from LLPluginClassMediaOwner - /*virtual*/ void handleMediaEvent(LLPluginClassMedia* plugin, LLPluginClassMediaOwner::EMediaEvent); - - // LLEditMenuHandler overrides - /*virtual*/ void cut(); - /*virtual*/ bool canCut() const; - - /*virtual*/ void copy(); - /*virtual*/ bool canCopy() const; - - /*virtual*/ void paste(); - /*virtual*/ bool canPaste() const; - - void addObject(LLVOVolume* obj) ; - void removeObject(LLVOVolume* obj) ; - const std::list< LLVOVolume* >* getObjectList() const ; - LLVOVolume *getSomeObject(); - void setUpdated(bool updated) ; - bool isUpdated() ; - - // updates the javascript object in the embedded browser with viewer values - void updateJavascriptObject(); - - // Updates the "interest" value in this object - void calculateInterest(); - F64 getInterest() const { return mInterest; }; - F64 getApproximateTextureInterest(); - S32 getProximity() const { return mProximity; }; - F64 getProximityDistance() const { return mProximityDistance; }; - - // Mark this object as being used in a UI panel instead of on a prim - // This will be used as part of the interest sorting algorithm. - void setUsedInUI(bool used_in_ui); - bool getUsedInUI() const { return mUsedInUI; }; - - void setBackgroundColor(LLColor4 color); - - F64 getCPUUsage() const; - - void setPriority(LLPluginClassMedia::EPriority priority); - LLPluginClassMedia::EPriority getPriority() { return mPriority; }; - - void setLowPrioritySizeLimit(int size); - - void setTextureID(LLUUID id = LLUUID::null); - - bool isTrustedBrowser() { return mTrustedBrowser; } - void setTrustedBrowser(bool trusted) { mTrustedBrowser = trusted; } - - typedef enum - { - MEDIANAVSTATE_NONE, // State is outside what we need to track for navigation. - MEDIANAVSTATE_BEGUN, // a MEDIA_EVENT_NAVIGATE_BEGIN has been received which was not server-directed - MEDIANAVSTATE_FIRST_LOCATION_CHANGED, // first LOCATION_CHANGED event after a non-server-directed BEGIN - MEDIANAVSTATE_FIRST_LOCATION_CHANGED_SPURIOUS, // Same as above, but the new URL is identical to the previously navigated URL. - MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED, // we received a NAVIGATE_COMPLETE event before the first LOCATION_CHANGED - MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED_SPURIOUS,// Same as above, but the new URL is identical to the previously navigated URL. - MEDIANAVSTATE_SERVER_SENT, // server-directed nav has been requested, but MEDIA_EVENT_NAVIGATE_BEGIN hasn't been received yet - MEDIANAVSTATE_SERVER_BEGUN, // MEDIA_EVENT_NAVIGATE_BEGIN has been received which was server-directed - MEDIANAVSTATE_SERVER_FIRST_LOCATION_CHANGED, // first LOCATION_CHANGED event after a server-directed BEGIN - MEDIANAVSTATE_SERVER_COMPLETE_BEFORE_LOCATION_CHANGED // we received a NAVIGATE_COMPLETE event before the first LOCATION_CHANGED - - }EMediaNavState; - - // Returns the current nav state of the media. - // note that this will be updated BEFORE listeners and objects receive media messages - EMediaNavState getNavState() { return mMediaNavState; } - void setNavState(EMediaNavState state); - - void setNavigateSuspended(bool suspend); - bool isNavigateSuspended() { return mNavigateSuspended; }; - - void cancelMimeTypeProbe(); - - bool isAttachedToHUD() const; - - // Is this media attached to an avatar *not* self - bool isAttachedToAnotherAvatar() const; - - // Is this media in the agent's parcel? - bool isInAgentParcel() const; - - // get currently active notification associated with this media instance - LLNotificationPtr getCurrentNotification() const; - -private: - bool isAutoPlayable() const; - bool shouldShowBasedOnClass() const; - bool isObscured() const; - static bool isObjectAttachedToAnotherAvatar(LLVOVolume *obj); - static bool isObjectInAgentParcel(LLVOVolume *obj); - -private: - // a single media url with some data and an impl. - std::shared_ptr mMediaSource; - LLMutex mLock; - F64 mZoomFactor; - LLUUID mTextureId; - bool mMovieImageHasMips; - std::string mMediaURL; // The last media url set with NavigateTo - std::string mHomeURL; - std::string mHomeMimeType; // forced mime type for home url - std::string mMimeType; - std::string mCurrentMediaURL; // The most current media url from the plugin (via the "location changed" or "navigate complete" events). - std::string mCurrentMimeType; // The MIME type that caused the currently loaded plugin to be loaded. - S32 mLastMouseX; // save the last mouse coord we get, so when we lose capture we can simulate a mouseup at that point. - S32 mLastMouseY; - S32 mMediaWidth; - S32 mMediaHeight; - bool mMediaAutoScale; - bool mMediaLoop; - bool mNeedsNewTexture; - S32 mTextureUsedWidth; - S32 mTextureUsedHeight; - bool mSuspendUpdates; - bool mTextureUpdatePending = false; - bool mVisible; - ECursorType mLastSetCursor; - EMediaNavState mMediaNavState; - F64 mInterest; - bool mUsedInUI; - bool mHasFocus; - LLPluginClassMedia::EPriority mPriority; - bool mNavigateRediscoverType; - bool mNavigateServerRequest; - bool mMediaSourceFailed; - F32 mRequestedVolume; - F32 mPreviousVolume; - bool mIsMuted; - bool mNeedsMuteCheck; - int mPreviousMediaState; - F64 mPreviousMediaTime; - bool mIsDisabled; - bool mIsParcelMedia; - S32 mProximity; - F64 mProximityDistance; - F64 mProximityCamera; - bool mMediaAutoPlay; - std::string mMediaEntryURL; - bool mInNearbyMediaList; // used by LLPanelNearbyMedia::refreshList() for performance reasons - bool mClearCache; - LLColor4 mBackgroundColor; - bool mNavigateSuspended; - bool mNavigateSuspendedDeferred; - bool mTrustedBrowser; - std::string mTarget; - LLNotificationPtr mNotification; - bool mCleanBrowser; // force the creation of a clean browsing target with full options enabled - static std::vector sMimeTypesFailed; - LLPointer mRawImage; //backing buffer for texture updates -private: - bool mIsUpdated ; - std::list< LLVOVolume* > mObjectList ; - - void mimeDiscoveryCoro(std::string url); - LLCoreHttpUtil::HttpCoroutineAdapter::wptr_t mMimeProbe; - bool mCanceling; - -private: - LLViewerMediaTexture *updateMediaImage(); - LL::WorkQueue::weak_t mMainQueue; - LL::WorkQueue::weak_t mTexUpdateQueue; - -}; - -#endif // LLVIEWERMEDIA_H +/** + * @file llviewermedia.h + * @brief Client interface to the media engine + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLVIEWERMEDIA_H +#define LLVIEWERMEDIA_H + +#include "llfocusmgr.h" +#include "lleditmenuhandler.h" + +#include "llpanel.h" +#include "llpluginclassmediaowner.h" + +#include "llviewermediaobserver.h" + +#include "llpluginclassmedia.h" +#include "v4color.h" +#include "llnotificationptr.h" + +#include "llurl.h" +#include "lleventcoro.h" +#include "llcoros.h" +#include "llcorehttputil.h" + +class LLViewerMediaImpl; +class LLUUID; +class LLViewerMediaTexture; +class LLMediaEntry; +class LLVOVolume; +class LLMimeDiscoveryResponder; + +typedef LLPointer viewer_media_t; +/////////////////////////////////////////////////////////////////////////////// +// +class LLViewerMediaEventEmitter +{ +public: + virtual ~LLViewerMediaEventEmitter(); + + bool addObserver( LLViewerMediaObserver* subject ); + bool remObserver( LLViewerMediaObserver* subject ); + virtual void emitEvent(LLPluginClassMedia* self, LLViewerMediaObserver::EMediaEvent event); + +private: + typedef std::list< LLViewerMediaObserver* > observerListType; + observerListType mObservers; +}; + +class LLViewerMediaImpl; + +class LLViewerMedia: public LLSingleton +{ + LLSINGLETON(LLViewerMedia); + ~LLViewerMedia(); + void initSingleton() override; + LOG_CLASS(LLViewerMedia); + +public: + // String to get/set media autoplay in gSavedSettings + static const char* AUTO_PLAY_MEDIA_SETTING; + static const char* SHOW_MEDIA_ON_OTHERS_SETTING; + static const char* SHOW_MEDIA_WITHIN_PARCEL_SETTING; + static const char* SHOW_MEDIA_OUTSIDE_PARCEL_SETTING; + + typedef std::list impl_list; + + typedef std::map impl_id_map; + + // Special case early init for just web browser component + // so we can show login screen. See .cpp file for details. JC + + viewer_media_t newMediaImpl(const LLUUID& texture_id, + S32 media_width = 0, + S32 media_height = 0, + U8 media_auto_scale = false, + U8 media_loop = false); + + viewer_media_t updateMediaImpl(LLMediaEntry* media_entry, const std::string& previous_url, bool update_from_self); + LLViewerMediaImpl* getMediaImplFromTextureID(const LLUUID& texture_id); + std::string getCurrentUserAgent(); + void updateBrowserUserAgent(); + bool handleSkinCurrentChanged(const LLSD& /*newvalue*/); + bool textureHasMedia(const LLUUID& texture_id); + void setVolume(F32 volume); + + // Is any media currently "showing"? Includes Parcel Media. Does not include media in the UI. + bool isAnyMediaShowing(); + // Shows if any media is playing, counts visible non time based media as playing. Does not include media in the UI. + bool isAnyMediaPlaying(); + // Set all media enabled or disabled, depending on val. Does not include media in the UI. + void setAllMediaEnabled(bool val); + // Set all media paused(stopped for non time based) or playing, depending on val. Does not include media in the UI. + void setAllMediaPaused(bool val); + + static void onIdle(void* dummy_arg = NULL); // updateMedia wrapper + void updateMedia(void* dummy_arg = NULL); + + F32 getVolume(); + void muteListChanged(); + bool isInterestingEnough(const LLVOVolume* object, const F64 &object_interest); + + // Returns the priority-sorted list of all media impls. + impl_list &getPriorityList(); + + // This is the comparitor used to sort the list. + static bool priorityComparitor(const LLViewerMediaImpl* i1, const LLViewerMediaImpl* i2); + + // These are just helper functions for the convenience of others working with media + bool hasInWorldMedia(); + std::string getParcelAudioURL(); + bool hasParcelMedia(); + bool hasParcelAudio(); + bool isParcelMediaPlaying(); + bool isParcelAudioPlaying(); + + static void authSubmitCallback(const LLSD& notification, const LLSD& response); + + // Clear all cookies for all plugins + void clearAllCookies(); + + // Clear all plugins' caches + void clearAllCaches(); + + // Set the "cookies enabled" flag for all loaded plugins + void setCookiesEnabled(bool enabled); + + // Set the proxy config for all loaded plugins + void setProxyConfig(bool enable, const std::string &host, int port); + + void openIDSetup(const std::string &openid_url, const std::string &openid_token); + void openIDCookieResponse(const std::string& url, const std::string &cookie); + + void proxyWindowOpened(const std::string &target, const std::string &uuid); + void proxyWindowClosed(const std::string &uuid); + + void createSpareBrowserMediaSource(); + LLPluginClassMedia* getSpareBrowserMediaSource(); + + void setOnlyAudibleMediaTextureID(const LLUUID& texture_id); + + LLSD getHeaders(); + LLCore::HttpHeaders::ptr_t getHttpHeaders(); + +private: + void onAuthSubmit(const LLSD& notification, const LLSD& response); + bool parseRawCookie(const std::string raw_cookie, std::string& name, std::string& value, std::string& path, bool& httponly, bool& secure); + void setOpenIDCookie(const std::string& url); + void onTeleportFinished(); + + static void openIDSetupCoro(std::string openidUrl, std::string openidToken); + static void getOpenIDCookieCoro(std::string url); + + bool mAnyMediaShowing; + bool mAnyMediaPlaying; + LLURL mOpenIDURL; + std::string mOpenIDCookie; + LLPluginClassMedia* mSpareBrowserMediaSource; + boost::signals2::connection mTeleportFinishConnection; +}; + +// Implementation functions not exported into header file +class LLViewerMediaImpl + : public LLMouseHandler, public LLRefCount, public LLPluginClassMediaOwner, public LLViewerMediaEventEmitter, public LLEditMenuHandler +{ + LOG_CLASS(LLViewerMediaImpl); +public: + + friend class LLViewerMedia; + + LLViewerMediaImpl( + const LLUUID& texture_id, + S32 media_width, + S32 media_height, + U8 media_auto_scale, + U8 media_loop); + + ~LLViewerMediaImpl(); + + // Override inherited version from LLViewerMediaEventEmitter + virtual void emitEvent(LLPluginClassMedia* self, LLViewerMediaObserver::EMediaEvent event); + + void createMediaSource(); + void destroyMediaSource(); + void setMediaType(const std::string& media_type); + bool initializeMedia(const std::string& mime_type); + bool initializePlugin(const std::string& media_type); + void loadURI(); + void executeJavaScript(const std::string& code); + LLPluginClassMedia* getMediaPlugin() { return mMediaSource.get(); } + void setSize(int width, int height); + + void showNotification(LLNotificationPtr notify); + void hideNotification(); + + void play(); + void stop(); + void pause(); + void start(); + void seek(F32 time); + void skipBack(F32 step_scale); + void skipForward(F32 step_scale); + void setVolume(F32 volume); + void setMute(bool mute); + void updateVolume(); + F32 getVolume(); + void focus(bool focus); + // True if the impl has user focus. + bool hasFocus() const; + void mouseDown(S32 x, S32 y, MASK mask, S32 button = 0); + void mouseUp(S32 x, S32 y, MASK mask, S32 button = 0); + void mouseMove(S32 x, S32 y, MASK mask); + void mouseDown(const LLVector2& texture_coords, MASK mask, S32 button = 0); + void mouseUp(const LLVector2& texture_coords, MASK mask, S32 button = 0); + void mouseMove(const LLVector2& texture_coords, MASK mask); + void mouseDoubleClick(const LLVector2& texture_coords, MASK mask); + void mouseDoubleClick(S32 x, S32 y, MASK mask, S32 button = 0); + void scrollWheel(const LLVector2& texture_coords, S32 scroll_x, S32 scroll_y, MASK mask); + void scrollWheel(S32 x, S32 y, S32 scroll_x, S32 scroll_y, MASK mask); + void mouseCapture(); + + void navigateBack(); + void navigateForward(); + void navigateReload(); + void navigateHome(); + void unload(); + void navigateTo(const std::string& url, const std::string& mime_type = "", bool rediscover_type = false, bool server_request = false, bool clean_browser = false); + void navigateInternal(); + void navigateStop(); + bool handleKeyHere(KEY key, MASK mask); + bool handleKeyUpHere(KEY key, MASK mask); + bool handleUnicodeCharHere(llwchar uni_char); + bool canNavigateForward(); + bool canNavigateBack(); + std::string getMediaURL() const { return mMediaURL; } + std::string getCurrentMediaURL(); + std::string getHomeURL() { return mHomeURL; } + std::string getMediaEntryURL() { return mMediaEntryURL; } + void setHomeURL(const std::string& home_url, const std::string& mime_type = LLStringUtil::null) { mHomeURL = home_url; mHomeMimeType = mime_type;}; + void clearCache(); + void setPageZoomFactor( double factor ); + double getPageZoomFactor() {return mZoomFactor;} + std::string getMimeType() { return mMimeType; } + void scaleMouse(S32 *mouse_x, S32 *mouse_y); + void scaleTextureCoords(const LLVector2& texture_coords, S32 *x, S32 *y); + + void update(); + bool preMediaTexUpdate(LLViewerMediaTexture*& media_tex, U8*& data, S32& data_width, S32& data_height, S32& x_pos, S32& y_pos, S32& width, S32& height); + void doMediaTexUpdate(LLViewerMediaTexture* media_tex, U8* data, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height, bool sync); + void updateImagesMediaStreams(); + LLUUID getMediaTextureID() const; + + void suspendUpdates(bool suspend) { mSuspendUpdates = suspend; } + void setVisible(bool visible); + bool getVisible() const { return mVisible; } + bool isVisible() const { return mVisible; } + + bool isMediaTimeBased(); + bool isMediaPlaying(); + bool isMediaPaused(); + bool hasMedia() const; + bool isMediaFailed() const { return mMediaSourceFailed; } + void setMediaFailed(bool val) { mMediaSourceFailed = val; } + void resetPreviousMediaState(); + + void setDisabled(bool disabled, bool forcePlayOnEnable = false); + bool isMediaDisabled() const { return mIsDisabled; }; + + void setInNearbyMediaList(bool in_list) { mInNearbyMediaList = in_list; } + bool getInNearbyMediaList() { return mInNearbyMediaList; } + + // returns true if this instance should not be loaded (disabled, muted object, crashed, etc.) + bool isForcedUnloaded() const; + + // returns true if this instance could be playable based on autoplay setting, current load state, etc. + bool isPlayable() const; + + void setIsParcelMedia(bool is_parcel_media) { mIsParcelMedia = is_parcel_media; } + bool isParcelMedia() const { return mIsParcelMedia; } + + ECursorType getLastSetCursor() { return mLastSetCursor; } + + void setTarget(const std::string& target) { mTarget = target; } + + // utility function to create a ready-to-use media instance from a desired media type. + static LLPluginClassMedia* newSourceFromMediaType(std::string media_type, LLPluginClassMediaOwner *owner /* may be NULL */, S32 default_width, S32 default_height, F64 zoom_factor, const std::string target = LLStringUtil::null, bool clean_browser = false); + + // Internally set our desired browser user agent string, including + // the Second Life version and skin name. Used because we can + // switch skins without restarting the app. + static void updateBrowserUserAgent(); + + // Callback for when the SkinCurrent control is changed to + // switch the user agent string to indicate the new skin. + static bool handleSkinCurrentChanged(const LLSD& newvalue); + + // need these to handle mouseup... + /*virtual*/ void onMouseCaptureLost(); + /*virtual*/ bool handleMouseUp(S32 x, S32 y, MASK mask); + + // Grr... the only thing I want as an LLMouseHandler are the onMouseCaptureLost and handleMouseUp calls. + // Sadly, these are all pure virtual, so I have to supply implementations here: + /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask) { return false; }; + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask) { return false; }; + /*virtual*/ bool handleScrollWheel(S32 x, S32 y, S32 clicks) { return false; }; + /*virtual*/ bool handleScrollHWheel(S32 x, S32 y, S32 clicks) { return false; }; + /*virtual*/ bool handleDoubleClick(S32 x, S32 y, MASK mask) { return false; }; + /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask) { return false; }; + /*virtual*/ bool handleRightMouseUp(S32 x, S32 y, MASK mask) { return false; }; + /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask) { return false; }; + /*virtual*/ bool handleMiddleMouseDown(S32 x, S32 y, MASK mask) { return false; }; + /*virtual*/ bool handleMiddleMouseUp(S32 x, S32 y, MASK mask) {return false; }; + /*virtual*/ const std::string& getName() const; + + /*virtual*/ void screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const {}; + /*virtual*/ void localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const {}; + /*virtual*/ bool hasMouseCapture() { return gFocusMgr.getMouseCapture() == this; }; + + // Inherited from LLPluginClassMediaOwner + /*virtual*/ void handleMediaEvent(LLPluginClassMedia* plugin, LLPluginClassMediaOwner::EMediaEvent); + + // LLEditMenuHandler overrides + /*virtual*/ void cut(); + /*virtual*/ bool canCut() const; + + /*virtual*/ void copy(); + /*virtual*/ bool canCopy() const; + + /*virtual*/ void paste(); + /*virtual*/ bool canPaste() const; + + void addObject(LLVOVolume* obj) ; + void removeObject(LLVOVolume* obj) ; + const std::list< LLVOVolume* >* getObjectList() const ; + LLVOVolume *getSomeObject(); + void setUpdated(bool updated) ; + bool isUpdated() ; + + // updates the javascript object in the embedded browser with viewer values + void updateJavascriptObject(); + + // Updates the "interest" value in this object + void calculateInterest(); + F64 getInterest() const { return mInterest; }; + F64 getApproximateTextureInterest(); + S32 getProximity() const { return mProximity; }; + F64 getProximityDistance() const { return mProximityDistance; }; + + // Mark this object as being used in a UI panel instead of on a prim + // This will be used as part of the interest sorting algorithm. + void setUsedInUI(bool used_in_ui); + bool getUsedInUI() const { return mUsedInUI; }; + + void setBackgroundColor(LLColor4 color); + + F64 getCPUUsage() const; + + void setPriority(LLPluginClassMedia::EPriority priority); + LLPluginClassMedia::EPriority getPriority() { return mPriority; }; + + void setLowPrioritySizeLimit(int size); + + void setTextureID(LLUUID id = LLUUID::null); + + bool isTrustedBrowser() { return mTrustedBrowser; } + void setTrustedBrowser(bool trusted) { mTrustedBrowser = trusted; } + + typedef enum + { + MEDIANAVSTATE_NONE, // State is outside what we need to track for navigation. + MEDIANAVSTATE_BEGUN, // a MEDIA_EVENT_NAVIGATE_BEGIN has been received which was not server-directed + MEDIANAVSTATE_FIRST_LOCATION_CHANGED, // first LOCATION_CHANGED event after a non-server-directed BEGIN + MEDIANAVSTATE_FIRST_LOCATION_CHANGED_SPURIOUS, // Same as above, but the new URL is identical to the previously navigated URL. + MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED, // we received a NAVIGATE_COMPLETE event before the first LOCATION_CHANGED + MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED_SPURIOUS,// Same as above, but the new URL is identical to the previously navigated URL. + MEDIANAVSTATE_SERVER_SENT, // server-directed nav has been requested, but MEDIA_EVENT_NAVIGATE_BEGIN hasn't been received yet + MEDIANAVSTATE_SERVER_BEGUN, // MEDIA_EVENT_NAVIGATE_BEGIN has been received which was server-directed + MEDIANAVSTATE_SERVER_FIRST_LOCATION_CHANGED, // first LOCATION_CHANGED event after a server-directed BEGIN + MEDIANAVSTATE_SERVER_COMPLETE_BEFORE_LOCATION_CHANGED // we received a NAVIGATE_COMPLETE event before the first LOCATION_CHANGED + + }EMediaNavState; + + // Returns the current nav state of the media. + // note that this will be updated BEFORE listeners and objects receive media messages + EMediaNavState getNavState() { return mMediaNavState; } + void setNavState(EMediaNavState state); + + void setNavigateSuspended(bool suspend); + bool isNavigateSuspended() { return mNavigateSuspended; }; + + void cancelMimeTypeProbe(); + + bool isAttachedToHUD() const; + + // Is this media attached to an avatar *not* self + bool isAttachedToAnotherAvatar() const; + + // Is this media in the agent's parcel? + bool isInAgentParcel() const; + + // get currently active notification associated with this media instance + LLNotificationPtr getCurrentNotification() const; + +private: + bool isAutoPlayable() const; + bool shouldShowBasedOnClass() const; + bool isObscured() const; + static bool isObjectAttachedToAnotherAvatar(LLVOVolume *obj); + static bool isObjectInAgentParcel(LLVOVolume *obj); + +private: + // a single media url with some data and an impl. + std::shared_ptr mMediaSource; + LLMutex mLock; + F64 mZoomFactor; + LLUUID mTextureId; + bool mMovieImageHasMips; + std::string mMediaURL; // The last media url set with NavigateTo + std::string mHomeURL; + std::string mHomeMimeType; // forced mime type for home url + std::string mMimeType; + std::string mCurrentMediaURL; // The most current media url from the plugin (via the "location changed" or "navigate complete" events). + std::string mCurrentMimeType; // The MIME type that caused the currently loaded plugin to be loaded. + S32 mLastMouseX; // save the last mouse coord we get, so when we lose capture we can simulate a mouseup at that point. + S32 mLastMouseY; + S32 mMediaWidth; + S32 mMediaHeight; + bool mMediaAutoScale; + bool mMediaLoop; + bool mNeedsNewTexture; + S32 mTextureUsedWidth; + S32 mTextureUsedHeight; + bool mSuspendUpdates; + bool mTextureUpdatePending = false; + bool mVisible; + ECursorType mLastSetCursor; + EMediaNavState mMediaNavState; + F64 mInterest; + bool mUsedInUI; + bool mHasFocus; + LLPluginClassMedia::EPriority mPriority; + bool mNavigateRediscoverType; + bool mNavigateServerRequest; + bool mMediaSourceFailed; + F32 mRequestedVolume; + F32 mPreviousVolume; + bool mIsMuted; + bool mNeedsMuteCheck; + int mPreviousMediaState; + F64 mPreviousMediaTime; + bool mIsDisabled; + bool mIsParcelMedia; + S32 mProximity; + F64 mProximityDistance; + F64 mProximityCamera; + bool mMediaAutoPlay; + std::string mMediaEntryURL; + bool mInNearbyMediaList; // used by LLPanelNearbyMedia::refreshList() for performance reasons + bool mClearCache; + LLColor4 mBackgroundColor; + bool mNavigateSuspended; + bool mNavigateSuspendedDeferred; + bool mTrustedBrowser; + std::string mTarget; + LLNotificationPtr mNotification; + bool mCleanBrowser; // force the creation of a clean browsing target with full options enabled + static std::vector sMimeTypesFailed; + LLPointer mRawImage; //backing buffer for texture updates +private: + bool mIsUpdated ; + std::list< LLVOVolume* > mObjectList ; + + void mimeDiscoveryCoro(std::string url); + LLCoreHttpUtil::HttpCoroutineAdapter::wptr_t mMimeProbe; + bool mCanceling; + +private: + LLViewerMediaTexture *updateMediaImage(); + LL::WorkQueue::weak_t mMainQueue; + LL::WorkQueue::weak_t mTexUpdateQueue; + +}; + +#endif // LLVIEWERMEDIA_H diff --git a/indra/newview/llviewermediafocus.cpp b/indra/newview/llviewermediafocus.cpp index cc638bb248..c8d25180b9 100644 --- a/indra/newview/llviewermediafocus.cpp +++ b/indra/newview/llviewermediafocus.cpp @@ -1,639 +1,639 @@ -/** - * @file llviewermediafocus.cpp - * @brief Governs focus on Media prims - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llviewermediafocus.h" - -//LLViewerMediaFocus -#include "llviewerobjectlist.h" -#include "llpanelprimmediacontrols.h" -#include "llpluginclassmedia.h" -#include "llagent.h" -#include "llagentcamera.h" -#include "lltoolpie.h" -#include "llviewercamera.h" -#include "llviewermedia.h" -#include "llhudview.h" -#include "lluictrlfactory.h" -#include "lldrawable.h" -#include "llparcel.h" -#include "llviewerparcelmgr.h" -#include "llweb.h" -#include "llmediaentry.h" -#include "llkeyboard.h" -#include "lltoolmgr.h" -#include "llvovolume.h" -#include "llhelp.h" - -// -// LLViewerMediaFocus -// - -LLViewerMediaFocus::LLViewerMediaFocus() -: mFocusedObjectFace(0), - mHoverObjectFace(0) -{ -} - -LLViewerMediaFocus::~LLViewerMediaFocus() -{ - // The destructor for LLSingletons happens at atexit() time, which is too late to do much. - // Clean up in cleanupClass() instead. - gFocusMgr.removeKeyboardFocusWithoutCallback(this); -} - -void LLViewerMediaFocus::setFocusFace(LLPointer objectp, S32 face, viewer_media_t media_impl, LLVector3 pick_normal) -{ - LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - - LLViewerMediaImpl *old_media_impl = getFocusedMediaImpl(); - if(old_media_impl) - { - old_media_impl->focus(false); - } - - // Always clear the current selection. If we're setting focus on a face, we'll reselect the correct object below. - LLSelectMgr::getInstance()->deselectAll(); - mSelection = NULL; - - if (media_impl.notNull() && objectp.notNull()) - { - bool face_auto_zoom = false; - mPrevFocusedImplID = LLUUID::null; - mFocusedImplID = media_impl->getMediaTextureID(); - mFocusedObjectID = objectp->getID(); - mFocusedObjectFace = face; - mFocusedObjectNormal = pick_normal; - - // Set the selection in the selection manager so we can draw the focus ring. - mSelection = LLSelectMgr::getInstance()->selectObjectOnly(objectp, face); - - // Focusing on a media face clears its disable flag. - media_impl->setDisabled(false); - - LLTextureEntry* tep = objectp->getTE(face); - if(tep->hasMedia()) - { - LLMediaEntry* mep = tep->getMediaData(); - face_auto_zoom = mep->getAutoZoom(); - if(!media_impl->hasMedia()) - { - std::string url = mep->getCurrentURL().empty() ? mep->getHomeURL() : mep->getCurrentURL(); - media_impl->navigateTo(url, "", true); - } - } - else - { - // This should never happen. - LL_WARNS() << "Can't find media entry for focused face" << LL_ENDL; - } - - media_impl->focus(true); - gFocusMgr.setKeyboardFocus(this); - LLViewerMediaImpl* impl = getFocusedMediaImpl(); - if (impl) - { - LLEditMenuHandler::gEditMenuHandler = impl; - } - - // We must do this before processing the media HUD zoom, or it may zoom to the wrong face. - update(); - - if(mMediaControls.get()) - { - if(face_auto_zoom && !static_cast(parcel->getMediaPreventCameraZoom())) - { - // Zoom in on this face - mMediaControls.get()->resetZoomLevel(false); - mMediaControls.get()->nextZoomLevel(); - } - else - { - // Reset the controls' zoom level without moving the camera. - // This fixes the case where clicking focus between two non-autozoom faces doesn't change the zoom-out button back to a zoom-in button. - mMediaControls.get()->resetZoomLevel(false); - } - } - } - else - { - if(hasFocus()) - { - gFocusMgr.setKeyboardFocus(NULL); - } - - LLViewerMediaImpl* impl = getFocusedMediaImpl(); - if (LLEditMenuHandler::gEditMenuHandler == impl) - { - LLEditMenuHandler::gEditMenuHandler = NULL; - } - - - mFocusedImplID = LLUUID::null; - if (objectp.notNull()) - { - // Still record the focused object...it may mean we need to load media data. - // This will aid us in determining this object is "important enough" - mFocusedObjectID = objectp->getID(); - mFocusedObjectFace = face; - } - else { - mFocusedObjectID = LLUUID::null; - mFocusedObjectFace = 0; - } - } -} - -void LLViewerMediaFocus::clearFocus() -{ - setFocusFace(NULL, 0, NULL); -} - -void LLViewerMediaFocus::setHoverFace(LLPointer objectp, S32 face, viewer_media_t media_impl, LLVector3 pick_normal) -{ - if (media_impl.notNull()) - { - mHoverImplID = media_impl->getMediaTextureID(); - mHoverObjectID = objectp->getID(); - mHoverObjectFace = face; - mHoverObjectNormal = pick_normal; - } - else - { - mHoverObjectID = LLUUID::null; - mHoverObjectFace = 0; - mHoverImplID = LLUUID::null; - } -} - -void LLViewerMediaFocus::clearHover() -{ - setHoverFace(NULL, 0, NULL); -} - - -bool LLViewerMediaFocus::getFocus() -{ - if (gFocusMgr.getKeyboardFocus() == this) - { - return true; - } - return false; -} - -// This function selects an ideal viewing distance based on the focused object, pick normal, and padding value -LLVector3d LLViewerMediaFocus::setCameraZoom(LLViewerObject* object, LLVector3 normal, F32 padding_factor, bool zoom_in_only) -{ - LLVector3d camera_pos; - if (object) - { - gAgentCamera.setFocusOnAvatar(false, ANIMATE); - - LLBBox bbox = object->getBoundingBoxAgent(); - LLVector3d center = gAgent.getPosGlobalFromAgent(bbox.getCenterAgent()); - F32 height; - F32 width; - F32 depth; - F32 angle_of_view; - F32 distance; - - // We need the aspect ratio, and the 3 components of the bbox as height, width, and depth. - F32 aspect_ratio = getBBoxAspectRatio(bbox, normal, &height, &width, &depth); - F32 camera_aspect = LLViewerCamera::getInstance()->getAspect(); - - LL_DEBUGS() << "normal = " << normal << ", aspect_ratio = " << aspect_ratio << ", camera_aspect = " << camera_aspect << LL_ENDL; - - // We will normally use the side of the volume aligned with the short side of the screen (i.e. the height for - // a screen in a landscape aspect ratio), however there is an edge case where the aspect ratio of the object is - // more extreme than the screen. In this case we invert the logic, using the longer component of both the object - // and the screen. - bool invert = (camera_aspect > 1.0f && aspect_ratio > camera_aspect) || - (camera_aspect < 1.0f && aspect_ratio < camera_aspect); - - // To calculate the optimum viewing distance we will need the angle of the shorter side of the view rectangle. - // In portrait mode this is the width, and in landscape it is the height. - // We then calculate the distance based on the corresponding side of the object bbox (width for portrait, height for landscape) - // We will add half the depth of the bounding box, as the distance projection uses the center point of the bbox. - if(camera_aspect < 1.0f || invert) - { - angle_of_view = llmax(0.1f, LLViewerCamera::getInstance()->getView() * LLViewerCamera::getInstance()->getAspect()); - distance = width * 0.5 * padding_factor / tan(angle_of_view * 0.5f ); - - LL_DEBUGS() << "using width (" << width << "), angle_of_view = " << angle_of_view << ", distance = " << distance << LL_ENDL; - } - else - { - angle_of_view = llmax(0.1f, LLViewerCamera::getInstance()->getView()); - distance = height * 0.5 * padding_factor / tan(angle_of_view * 0.5f ); - - LL_DEBUGS() << "using height (" << height << "), angle_of_view = " << angle_of_view << ", distance = " << distance << LL_ENDL; - } - - distance += depth * 0.5; - - // Finally animate the camera to this new position and focal point - LLVector3d target_pos; - // The target lookat position is the center of the selection (in global coords) - target_pos = center; - // Target look-from (camera) position is "distance" away from the target along the normal - LLVector3d pickNormal = LLVector3d(normal); - pickNormal.normalize(); - camera_pos = target_pos + pickNormal * distance; - if (pickNormal == LLVector3d::z_axis || pickNormal == LLVector3d::z_axis_neg) - { - // If the normal points directly up, the camera will "flip" around. - // We try to avoid this by adjusting the target camera position a - // smidge towards current camera position - // *NOTE: this solution is not perfect. All it attempts to solve is the - // "looking down" problem where the camera flips around when it animates - // to that position. You still are not guaranteed to be looking at the - // media in the correct orientation. What this solution does is it will - // put the camera into position keeping as best it can the current - // orientation with respect to the face. In other words, if before zoom - // the media appears "upside down" from the camera, after zooming it will - // still be upside down, but at least it will not flip. - LLVector3d cur_camera_pos = LLVector3d(gAgentCamera.getCameraPositionGlobal()); - LLVector3d delta = (cur_camera_pos - camera_pos); - F64 len = delta.length(); - delta.normalize(); - // Move 1% of the distance towards original camera location - camera_pos += 0.01 * len * delta; - } - - // If we are not allowing zooming out and the old camera position is closer to - // the center then the new intended camera position, don't move camera and return - if (zoom_in_only && - (dist_vec_squared(gAgentCamera.getCameraPositionGlobal(), target_pos) < dist_vec_squared(camera_pos, target_pos))) - { - return camera_pos; - } - - gAgentCamera.setCameraPosAndFocusGlobal(camera_pos, target_pos, object->getID() ); - - } - else - { - // If we have no object, focus back on the avatar. - gAgentCamera.setFocusOnAvatar(true, ANIMATE); - } - return camera_pos; -} -void LLViewerMediaFocus::onFocusReceived() -{ - LLViewerMediaImpl* media_impl = getFocusedMediaImpl(); - if(media_impl) - media_impl->focus(true); - - LLFocusableElement::onFocusReceived(); -} - -void LLViewerMediaFocus::onFocusLost() -{ - LLViewerMediaImpl* media_impl = getFocusedMediaImpl(); - if(media_impl) - media_impl->focus(false); - - gViewerWindow->focusClient(); - LLFocusableElement::onFocusLost(); -} - -bool LLViewerMediaFocus::handleKey(KEY key, MASK mask, bool called_from_parent) -{ - LLViewerMediaImpl* media_impl = getFocusedMediaImpl(); - if(media_impl) - { - media_impl->handleKeyHere(key, mask); - - if (KEY_ESCAPE == key) - { - // Reset camera zoom in this case. - if(mFocusedImplID.notNull()) - { - if(mMediaControls.get()) - { - mMediaControls.get()->resetZoomLevel(true); - } - } - - clearFocus(); - } - } - - return true; -} - -bool LLViewerMediaFocus::handleKeyUp(KEY key, MASK mask, bool called_from_parent) -{ - LLViewerMediaImpl* media_impl = getFocusedMediaImpl(); - if (media_impl) - { - media_impl->handleKeyUpHere(key, mask); - } - return true; -} - - - -bool LLViewerMediaFocus::handleUnicodeChar(llwchar uni_char, bool called_from_parent) -{ - LLViewerMediaImpl* media_impl = getFocusedMediaImpl(); - if(media_impl) - media_impl->handleUnicodeCharHere(uni_char); - return true; -} - -bool LLViewerMediaFocus::handleScrollWheel(const LLVector2& texture_coords, S32 clicks_x, S32 clicks_y) -{ - bool retval = false; - LLViewerMediaImpl* media_impl = getFocusedMediaImpl(); - if (media_impl && media_impl->hasMedia()) - { - media_impl->scrollWheel(texture_coords, clicks_x, clicks_y, gKeyboard->currentMask(true)); - retval = true; - } - return retval; -} - -bool LLViewerMediaFocus::handleScrollWheel(S32 x, S32 y, S32 clicks_x, S32 clicks_y) -{ - bool retval = false; - LLViewerMediaImpl* media_impl = getFocusedMediaImpl(); - if(media_impl && media_impl->hasMedia()) - { - media_impl->scrollWheel(x, y, clicks_x, clicks_y, gKeyboard->currentMask(true)); - retval = true; - } - return retval; -} - -void LLViewerMediaFocus::update() -{ - if(mFocusedImplID.notNull()) - { - // We have a focused impl/face. - if(!getFocus()) - { - // We've lost keyboard focus -- check to see whether the media controls have it - if(mMediaControls.get() && mMediaControls.get()->hasFocus()) - { - // the media controls have focus -- don't clear. - } - else - { - // Someone else has focus -- back off. - mPrevFocusedImplID = mFocusedImplID; - clearFocus(); - } - } - else if(LLToolMgr::getInstance()->inBuildMode()) - { - // Build tools are selected -- clear focus. - clearFocus(); - } - } - - - LLViewerMediaImpl *media_impl = getFocusedMediaImpl(); - LLViewerObject *viewer_object = getFocusedObject(); - S32 face = mFocusedObjectFace; - LLVector3 normal = mFocusedObjectNormal; - - if(!media_impl || !viewer_object) - { - media_impl = getHoverMediaImpl(); - viewer_object = getHoverObject(); - face = mHoverObjectFace; - normal = mHoverObjectNormal; - } - - if(media_impl && viewer_object) - { - // We have an object and impl to point at. - - // Make sure the media HUD object exists. - if(! mMediaControls.get()) - { - LLPanelPrimMediaControls* media_controls = new LLPanelPrimMediaControls(); - mMediaControls = media_controls->getHandle(); - gHUDView->addChild(media_controls); - } - mMediaControls.get()->setMediaFace(viewer_object, face, media_impl, normal); - } - else - { - // The media HUD is no longer needed. - if(mMediaControls.get()) - { - mMediaControls.get()->setMediaFace(NULL, 0, NULL); - } - } -} - - -// This function calculates the aspect ratio and the world aligned components of a selection bounding box. -F32 LLViewerMediaFocus::getBBoxAspectRatio(const LLBBox& bbox, const LLVector3& normal, F32* height, F32* width, F32* depth) -{ - // Convert the selection normal and an up vector to local coordinate space of the bbox - LLVector3 local_normal = bbox.agentToLocalBasis(normal); - LLVector3 z_vec = bbox.agentToLocalBasis(LLVector3(0.0f, 0.0f, 1.0f)); - - LLVector3 comp1(0.f,0.f,0.f); - LLVector3 comp2(0.f,0.f,0.f); - LLVector3 bbox_max = bbox.getExtentLocal(); - F32 dot1 = 0.f; - F32 dot2 = 0.f; - - LL_DEBUGS() << "bounding box local size = " << bbox_max << ", local_normal = " << local_normal << LL_ENDL; - - // The largest component of the localized normal vector is the depth component - // meaning that the other two are the legs of the rectangle. - local_normal.abs(); - - // Using temporary variables for these makes the logic a bit more readable. - bool XgtY = (local_normal.mV[VX] > local_normal.mV[VY]); - bool XgtZ = (local_normal.mV[VX] > local_normal.mV[VZ]); - bool YgtZ = (local_normal.mV[VY] > local_normal.mV[VZ]); - - if(XgtY && XgtZ) - { - LL_DEBUGS() << "x component of normal is longest, using y and z" << LL_ENDL; - comp1.mV[VY] = bbox_max.mV[VY]; - comp2.mV[VZ] = bbox_max.mV[VZ]; - *depth = bbox_max.mV[VX]; - } - else if(!XgtY && YgtZ) - { - LL_DEBUGS() << "y component of normal is longest, using x and z" << LL_ENDL; - comp1.mV[VX] = bbox_max.mV[VX]; - comp2.mV[VZ] = bbox_max.mV[VZ]; - *depth = bbox_max.mV[VY]; - } - else - { - LL_DEBUGS() << "z component of normal is longest, using x and y" << LL_ENDL; - comp1.mV[VX] = bbox_max.mV[VX]; - comp2.mV[VY] = bbox_max.mV[VY]; - *depth = bbox_max.mV[VZ]; - } - - // The height is the vector closest to vertical in the bbox coordinate space (highest dot product value) - dot1 = comp1 * z_vec; - dot2 = comp2 * z_vec; - if(fabs(dot1) > fabs(dot2)) - { - *height = comp1.length(); - *width = comp2.length(); - - LL_DEBUGS() << "comp1 = " << comp1 << ", height = " << *height << LL_ENDL; - LL_DEBUGS() << "comp2 = " << comp2 << ", width = " << *width << LL_ENDL; - } - else - { - *height = comp2.length(); - *width = comp1.length(); - - LL_DEBUGS() << "comp2 = " << comp2 << ", height = " << *height << LL_ENDL; - LL_DEBUGS() << "comp1 = " << comp1 << ", width = " << *width << LL_ENDL; - } - - LL_DEBUGS() << "returning " << (*width / *height) << LL_ENDL; - - // Return the aspect ratio. - return *width / *height; -} - -bool LLViewerMediaFocus::isFocusedOnFace(LLPointer objectp, S32 face) -{ - return objectp->getID() == mFocusedObjectID && face == mFocusedObjectFace; -} - -bool LLViewerMediaFocus::isHoveringOverFace(LLPointer objectp, S32 face) -{ - return objectp->getID() == mHoverObjectID && face == mHoverObjectFace; -} - - -LLViewerMediaImpl* LLViewerMediaFocus::getFocusedMediaImpl() -{ - return LLViewerMedia::getInstance()->getMediaImplFromTextureID(mFocusedImplID); -} - -LLViewerObject* LLViewerMediaFocus::getFocusedObject() -{ - return gObjectList.findObject(mFocusedObjectID); -} - -LLViewerMediaImpl* LLViewerMediaFocus::getHoverMediaImpl() -{ - return LLViewerMedia::getInstance()->getMediaImplFromTextureID(mHoverImplID); -} - -LLViewerObject* LLViewerMediaFocus::getHoverObject() -{ - return gObjectList.findObject(mHoverObjectID); -} - -void LLViewerMediaFocus::focusZoomOnMedia(LLUUID media_id) -{ - LLViewerMediaImpl* impl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(media_id); - - if(impl) - { - // Get the first object from the media impl's object list. This is completely arbitrary, but should suffice. - LLVOVolume *obj = impl->getSomeObject(); - if(obj) - { - // This media is attached to at least one object. Figure out which face it's on. - S32 face = obj->getFaceIndexWithMediaImpl(impl, -1); - - // We don't have a proper pick normal here, and finding a face's real normal is... complicated. - LLVector3 normal = obj->getApproximateFaceNormal(face); - if(normal.isNull()) - { - // If that didn't work, use the inverse of the camera "look at" axis, which should keep the camera pointed in the same direction. -// LL_INFOS() << "approximate face normal invalid, using camera direction." << LL_ENDL; - normal = LLViewerCamera::getInstance()->getAtAxis(); - normal *= (F32)-1.0f; - } - - // Attempt to focus/zoom on that face. - setFocusFace(obj, face, impl, normal); - - if(mMediaControls.get()) - { - mMediaControls.get()->resetZoomLevel(); - mMediaControls.get()->nextZoomLevel(); - } - } - } -} - -void LLViewerMediaFocus::unZoom() -{ - if(mMediaControls.get()) - { - mMediaControls.get()->resetZoomLevel(); - } -} - -bool LLViewerMediaFocus::isZoomed() const -{ - return (mMediaControls.get() && mMediaControls.get()->getZoomLevel() != LLPanelPrimMediaControls::ZOOM_NONE); -} - -bool LLViewerMediaFocus::isZoomedOnMedia(LLUUID media_id) -{ - if (isZoomed()) - { - return (mFocusedImplID == media_id) || (mPrevFocusedImplID == media_id); - } - return false; -} - -LLUUID LLViewerMediaFocus::getControlsMediaID() -{ - if(getFocusedMediaImpl()) - { - return mFocusedImplID; - } - else if(getHoverMediaImpl()) - { - return mHoverImplID; - } - - return LLUUID::null; -} - -bool LLViewerMediaFocus::wantsKeyUpKeyDown() const -{ - return true; -} - -bool LLViewerMediaFocus::wantsReturnKey() const -{ - return true; -} +/** + * @file llviewermediafocus.cpp + * @brief Governs focus on Media prims + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llviewermediafocus.h" + +//LLViewerMediaFocus +#include "llviewerobjectlist.h" +#include "llpanelprimmediacontrols.h" +#include "llpluginclassmedia.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "lltoolpie.h" +#include "llviewercamera.h" +#include "llviewermedia.h" +#include "llhudview.h" +#include "lluictrlfactory.h" +#include "lldrawable.h" +#include "llparcel.h" +#include "llviewerparcelmgr.h" +#include "llweb.h" +#include "llmediaentry.h" +#include "llkeyboard.h" +#include "lltoolmgr.h" +#include "llvovolume.h" +#include "llhelp.h" + +// +// LLViewerMediaFocus +// + +LLViewerMediaFocus::LLViewerMediaFocus() +: mFocusedObjectFace(0), + mHoverObjectFace(0) +{ +} + +LLViewerMediaFocus::~LLViewerMediaFocus() +{ + // The destructor for LLSingletons happens at atexit() time, which is too late to do much. + // Clean up in cleanupClass() instead. + gFocusMgr.removeKeyboardFocusWithoutCallback(this); +} + +void LLViewerMediaFocus::setFocusFace(LLPointer objectp, S32 face, viewer_media_t media_impl, LLVector3 pick_normal) +{ + LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + + LLViewerMediaImpl *old_media_impl = getFocusedMediaImpl(); + if(old_media_impl) + { + old_media_impl->focus(false); + } + + // Always clear the current selection. If we're setting focus on a face, we'll reselect the correct object below. + LLSelectMgr::getInstance()->deselectAll(); + mSelection = NULL; + + if (media_impl.notNull() && objectp.notNull()) + { + bool face_auto_zoom = false; + mPrevFocusedImplID = LLUUID::null; + mFocusedImplID = media_impl->getMediaTextureID(); + mFocusedObjectID = objectp->getID(); + mFocusedObjectFace = face; + mFocusedObjectNormal = pick_normal; + + // Set the selection in the selection manager so we can draw the focus ring. + mSelection = LLSelectMgr::getInstance()->selectObjectOnly(objectp, face); + + // Focusing on a media face clears its disable flag. + media_impl->setDisabled(false); + + LLTextureEntry* tep = objectp->getTE(face); + if(tep->hasMedia()) + { + LLMediaEntry* mep = tep->getMediaData(); + face_auto_zoom = mep->getAutoZoom(); + if(!media_impl->hasMedia()) + { + std::string url = mep->getCurrentURL().empty() ? mep->getHomeURL() : mep->getCurrentURL(); + media_impl->navigateTo(url, "", true); + } + } + else + { + // This should never happen. + LL_WARNS() << "Can't find media entry for focused face" << LL_ENDL; + } + + media_impl->focus(true); + gFocusMgr.setKeyboardFocus(this); + LLViewerMediaImpl* impl = getFocusedMediaImpl(); + if (impl) + { + LLEditMenuHandler::gEditMenuHandler = impl; + } + + // We must do this before processing the media HUD zoom, or it may zoom to the wrong face. + update(); + + if(mMediaControls.get()) + { + if(face_auto_zoom && !static_cast(parcel->getMediaPreventCameraZoom())) + { + // Zoom in on this face + mMediaControls.get()->resetZoomLevel(false); + mMediaControls.get()->nextZoomLevel(); + } + else + { + // Reset the controls' zoom level without moving the camera. + // This fixes the case where clicking focus between two non-autozoom faces doesn't change the zoom-out button back to a zoom-in button. + mMediaControls.get()->resetZoomLevel(false); + } + } + } + else + { + if(hasFocus()) + { + gFocusMgr.setKeyboardFocus(NULL); + } + + LLViewerMediaImpl* impl = getFocusedMediaImpl(); + if (LLEditMenuHandler::gEditMenuHandler == impl) + { + LLEditMenuHandler::gEditMenuHandler = NULL; + } + + + mFocusedImplID = LLUUID::null; + if (objectp.notNull()) + { + // Still record the focused object...it may mean we need to load media data. + // This will aid us in determining this object is "important enough" + mFocusedObjectID = objectp->getID(); + mFocusedObjectFace = face; + } + else { + mFocusedObjectID = LLUUID::null; + mFocusedObjectFace = 0; + } + } +} + +void LLViewerMediaFocus::clearFocus() +{ + setFocusFace(NULL, 0, NULL); +} + +void LLViewerMediaFocus::setHoverFace(LLPointer objectp, S32 face, viewer_media_t media_impl, LLVector3 pick_normal) +{ + if (media_impl.notNull()) + { + mHoverImplID = media_impl->getMediaTextureID(); + mHoverObjectID = objectp->getID(); + mHoverObjectFace = face; + mHoverObjectNormal = pick_normal; + } + else + { + mHoverObjectID = LLUUID::null; + mHoverObjectFace = 0; + mHoverImplID = LLUUID::null; + } +} + +void LLViewerMediaFocus::clearHover() +{ + setHoverFace(NULL, 0, NULL); +} + + +bool LLViewerMediaFocus::getFocus() +{ + if (gFocusMgr.getKeyboardFocus() == this) + { + return true; + } + return false; +} + +// This function selects an ideal viewing distance based on the focused object, pick normal, and padding value +LLVector3d LLViewerMediaFocus::setCameraZoom(LLViewerObject* object, LLVector3 normal, F32 padding_factor, bool zoom_in_only) +{ + LLVector3d camera_pos; + if (object) + { + gAgentCamera.setFocusOnAvatar(false, ANIMATE); + + LLBBox bbox = object->getBoundingBoxAgent(); + LLVector3d center = gAgent.getPosGlobalFromAgent(bbox.getCenterAgent()); + F32 height; + F32 width; + F32 depth; + F32 angle_of_view; + F32 distance; + + // We need the aspect ratio, and the 3 components of the bbox as height, width, and depth. + F32 aspect_ratio = getBBoxAspectRatio(bbox, normal, &height, &width, &depth); + F32 camera_aspect = LLViewerCamera::getInstance()->getAspect(); + + LL_DEBUGS() << "normal = " << normal << ", aspect_ratio = " << aspect_ratio << ", camera_aspect = " << camera_aspect << LL_ENDL; + + // We will normally use the side of the volume aligned with the short side of the screen (i.e. the height for + // a screen in a landscape aspect ratio), however there is an edge case where the aspect ratio of the object is + // more extreme than the screen. In this case we invert the logic, using the longer component of both the object + // and the screen. + bool invert = (camera_aspect > 1.0f && aspect_ratio > camera_aspect) || + (camera_aspect < 1.0f && aspect_ratio < camera_aspect); + + // To calculate the optimum viewing distance we will need the angle of the shorter side of the view rectangle. + // In portrait mode this is the width, and in landscape it is the height. + // We then calculate the distance based on the corresponding side of the object bbox (width for portrait, height for landscape) + // We will add half the depth of the bounding box, as the distance projection uses the center point of the bbox. + if(camera_aspect < 1.0f || invert) + { + angle_of_view = llmax(0.1f, LLViewerCamera::getInstance()->getView() * LLViewerCamera::getInstance()->getAspect()); + distance = width * 0.5 * padding_factor / tan(angle_of_view * 0.5f ); + + LL_DEBUGS() << "using width (" << width << "), angle_of_view = " << angle_of_view << ", distance = " << distance << LL_ENDL; + } + else + { + angle_of_view = llmax(0.1f, LLViewerCamera::getInstance()->getView()); + distance = height * 0.5 * padding_factor / tan(angle_of_view * 0.5f ); + + LL_DEBUGS() << "using height (" << height << "), angle_of_view = " << angle_of_view << ", distance = " << distance << LL_ENDL; + } + + distance += depth * 0.5; + + // Finally animate the camera to this new position and focal point + LLVector3d target_pos; + // The target lookat position is the center of the selection (in global coords) + target_pos = center; + // Target look-from (camera) position is "distance" away from the target along the normal + LLVector3d pickNormal = LLVector3d(normal); + pickNormal.normalize(); + camera_pos = target_pos + pickNormal * distance; + if (pickNormal == LLVector3d::z_axis || pickNormal == LLVector3d::z_axis_neg) + { + // If the normal points directly up, the camera will "flip" around. + // We try to avoid this by adjusting the target camera position a + // smidge towards current camera position + // *NOTE: this solution is not perfect. All it attempts to solve is the + // "looking down" problem where the camera flips around when it animates + // to that position. You still are not guaranteed to be looking at the + // media in the correct orientation. What this solution does is it will + // put the camera into position keeping as best it can the current + // orientation with respect to the face. In other words, if before zoom + // the media appears "upside down" from the camera, after zooming it will + // still be upside down, but at least it will not flip. + LLVector3d cur_camera_pos = LLVector3d(gAgentCamera.getCameraPositionGlobal()); + LLVector3d delta = (cur_camera_pos - camera_pos); + F64 len = delta.length(); + delta.normalize(); + // Move 1% of the distance towards original camera location + camera_pos += 0.01 * len * delta; + } + + // If we are not allowing zooming out and the old camera position is closer to + // the center then the new intended camera position, don't move camera and return + if (zoom_in_only && + (dist_vec_squared(gAgentCamera.getCameraPositionGlobal(), target_pos) < dist_vec_squared(camera_pos, target_pos))) + { + return camera_pos; + } + + gAgentCamera.setCameraPosAndFocusGlobal(camera_pos, target_pos, object->getID() ); + + } + else + { + // If we have no object, focus back on the avatar. + gAgentCamera.setFocusOnAvatar(true, ANIMATE); + } + return camera_pos; +} +void LLViewerMediaFocus::onFocusReceived() +{ + LLViewerMediaImpl* media_impl = getFocusedMediaImpl(); + if(media_impl) + media_impl->focus(true); + + LLFocusableElement::onFocusReceived(); +} + +void LLViewerMediaFocus::onFocusLost() +{ + LLViewerMediaImpl* media_impl = getFocusedMediaImpl(); + if(media_impl) + media_impl->focus(false); + + gViewerWindow->focusClient(); + LLFocusableElement::onFocusLost(); +} + +bool LLViewerMediaFocus::handleKey(KEY key, MASK mask, bool called_from_parent) +{ + LLViewerMediaImpl* media_impl = getFocusedMediaImpl(); + if(media_impl) + { + media_impl->handleKeyHere(key, mask); + + if (KEY_ESCAPE == key) + { + // Reset camera zoom in this case. + if(mFocusedImplID.notNull()) + { + if(mMediaControls.get()) + { + mMediaControls.get()->resetZoomLevel(true); + } + } + + clearFocus(); + } + } + + return true; +} + +bool LLViewerMediaFocus::handleKeyUp(KEY key, MASK mask, bool called_from_parent) +{ + LLViewerMediaImpl* media_impl = getFocusedMediaImpl(); + if (media_impl) + { + media_impl->handleKeyUpHere(key, mask); + } + return true; +} + + + +bool LLViewerMediaFocus::handleUnicodeChar(llwchar uni_char, bool called_from_parent) +{ + LLViewerMediaImpl* media_impl = getFocusedMediaImpl(); + if(media_impl) + media_impl->handleUnicodeCharHere(uni_char); + return true; +} + +bool LLViewerMediaFocus::handleScrollWheel(const LLVector2& texture_coords, S32 clicks_x, S32 clicks_y) +{ + bool retval = false; + LLViewerMediaImpl* media_impl = getFocusedMediaImpl(); + if (media_impl && media_impl->hasMedia()) + { + media_impl->scrollWheel(texture_coords, clicks_x, clicks_y, gKeyboard->currentMask(true)); + retval = true; + } + return retval; +} + +bool LLViewerMediaFocus::handleScrollWheel(S32 x, S32 y, S32 clicks_x, S32 clicks_y) +{ + bool retval = false; + LLViewerMediaImpl* media_impl = getFocusedMediaImpl(); + if(media_impl && media_impl->hasMedia()) + { + media_impl->scrollWheel(x, y, clicks_x, clicks_y, gKeyboard->currentMask(true)); + retval = true; + } + return retval; +} + +void LLViewerMediaFocus::update() +{ + if(mFocusedImplID.notNull()) + { + // We have a focused impl/face. + if(!getFocus()) + { + // We've lost keyboard focus -- check to see whether the media controls have it + if(mMediaControls.get() && mMediaControls.get()->hasFocus()) + { + // the media controls have focus -- don't clear. + } + else + { + // Someone else has focus -- back off. + mPrevFocusedImplID = mFocusedImplID; + clearFocus(); + } + } + else if(LLToolMgr::getInstance()->inBuildMode()) + { + // Build tools are selected -- clear focus. + clearFocus(); + } + } + + + LLViewerMediaImpl *media_impl = getFocusedMediaImpl(); + LLViewerObject *viewer_object = getFocusedObject(); + S32 face = mFocusedObjectFace; + LLVector3 normal = mFocusedObjectNormal; + + if(!media_impl || !viewer_object) + { + media_impl = getHoverMediaImpl(); + viewer_object = getHoverObject(); + face = mHoverObjectFace; + normal = mHoverObjectNormal; + } + + if(media_impl && viewer_object) + { + // We have an object and impl to point at. + + // Make sure the media HUD object exists. + if(! mMediaControls.get()) + { + LLPanelPrimMediaControls* media_controls = new LLPanelPrimMediaControls(); + mMediaControls = media_controls->getHandle(); + gHUDView->addChild(media_controls); + } + mMediaControls.get()->setMediaFace(viewer_object, face, media_impl, normal); + } + else + { + // The media HUD is no longer needed. + if(mMediaControls.get()) + { + mMediaControls.get()->setMediaFace(NULL, 0, NULL); + } + } +} + + +// This function calculates the aspect ratio and the world aligned components of a selection bounding box. +F32 LLViewerMediaFocus::getBBoxAspectRatio(const LLBBox& bbox, const LLVector3& normal, F32* height, F32* width, F32* depth) +{ + // Convert the selection normal and an up vector to local coordinate space of the bbox + LLVector3 local_normal = bbox.agentToLocalBasis(normal); + LLVector3 z_vec = bbox.agentToLocalBasis(LLVector3(0.0f, 0.0f, 1.0f)); + + LLVector3 comp1(0.f,0.f,0.f); + LLVector3 comp2(0.f,0.f,0.f); + LLVector3 bbox_max = bbox.getExtentLocal(); + F32 dot1 = 0.f; + F32 dot2 = 0.f; + + LL_DEBUGS() << "bounding box local size = " << bbox_max << ", local_normal = " << local_normal << LL_ENDL; + + // The largest component of the localized normal vector is the depth component + // meaning that the other two are the legs of the rectangle. + local_normal.abs(); + + // Using temporary variables for these makes the logic a bit more readable. + bool XgtY = (local_normal.mV[VX] > local_normal.mV[VY]); + bool XgtZ = (local_normal.mV[VX] > local_normal.mV[VZ]); + bool YgtZ = (local_normal.mV[VY] > local_normal.mV[VZ]); + + if(XgtY && XgtZ) + { + LL_DEBUGS() << "x component of normal is longest, using y and z" << LL_ENDL; + comp1.mV[VY] = bbox_max.mV[VY]; + comp2.mV[VZ] = bbox_max.mV[VZ]; + *depth = bbox_max.mV[VX]; + } + else if(!XgtY && YgtZ) + { + LL_DEBUGS() << "y component of normal is longest, using x and z" << LL_ENDL; + comp1.mV[VX] = bbox_max.mV[VX]; + comp2.mV[VZ] = bbox_max.mV[VZ]; + *depth = bbox_max.mV[VY]; + } + else + { + LL_DEBUGS() << "z component of normal is longest, using x and y" << LL_ENDL; + comp1.mV[VX] = bbox_max.mV[VX]; + comp2.mV[VY] = bbox_max.mV[VY]; + *depth = bbox_max.mV[VZ]; + } + + // The height is the vector closest to vertical in the bbox coordinate space (highest dot product value) + dot1 = comp1 * z_vec; + dot2 = comp2 * z_vec; + if(fabs(dot1) > fabs(dot2)) + { + *height = comp1.length(); + *width = comp2.length(); + + LL_DEBUGS() << "comp1 = " << comp1 << ", height = " << *height << LL_ENDL; + LL_DEBUGS() << "comp2 = " << comp2 << ", width = " << *width << LL_ENDL; + } + else + { + *height = comp2.length(); + *width = comp1.length(); + + LL_DEBUGS() << "comp2 = " << comp2 << ", height = " << *height << LL_ENDL; + LL_DEBUGS() << "comp1 = " << comp1 << ", width = " << *width << LL_ENDL; + } + + LL_DEBUGS() << "returning " << (*width / *height) << LL_ENDL; + + // Return the aspect ratio. + return *width / *height; +} + +bool LLViewerMediaFocus::isFocusedOnFace(LLPointer objectp, S32 face) +{ + return objectp->getID() == mFocusedObjectID && face == mFocusedObjectFace; +} + +bool LLViewerMediaFocus::isHoveringOverFace(LLPointer objectp, S32 face) +{ + return objectp->getID() == mHoverObjectID && face == mHoverObjectFace; +} + + +LLViewerMediaImpl* LLViewerMediaFocus::getFocusedMediaImpl() +{ + return LLViewerMedia::getInstance()->getMediaImplFromTextureID(mFocusedImplID); +} + +LLViewerObject* LLViewerMediaFocus::getFocusedObject() +{ + return gObjectList.findObject(mFocusedObjectID); +} + +LLViewerMediaImpl* LLViewerMediaFocus::getHoverMediaImpl() +{ + return LLViewerMedia::getInstance()->getMediaImplFromTextureID(mHoverImplID); +} + +LLViewerObject* LLViewerMediaFocus::getHoverObject() +{ + return gObjectList.findObject(mHoverObjectID); +} + +void LLViewerMediaFocus::focusZoomOnMedia(LLUUID media_id) +{ + LLViewerMediaImpl* impl = LLViewerMedia::getInstance()->getMediaImplFromTextureID(media_id); + + if(impl) + { + // Get the first object from the media impl's object list. This is completely arbitrary, but should suffice. + LLVOVolume *obj = impl->getSomeObject(); + if(obj) + { + // This media is attached to at least one object. Figure out which face it's on. + S32 face = obj->getFaceIndexWithMediaImpl(impl, -1); + + // We don't have a proper pick normal here, and finding a face's real normal is... complicated. + LLVector3 normal = obj->getApproximateFaceNormal(face); + if(normal.isNull()) + { + // If that didn't work, use the inverse of the camera "look at" axis, which should keep the camera pointed in the same direction. +// LL_INFOS() << "approximate face normal invalid, using camera direction." << LL_ENDL; + normal = LLViewerCamera::getInstance()->getAtAxis(); + normal *= (F32)-1.0f; + } + + // Attempt to focus/zoom on that face. + setFocusFace(obj, face, impl, normal); + + if(mMediaControls.get()) + { + mMediaControls.get()->resetZoomLevel(); + mMediaControls.get()->nextZoomLevel(); + } + } + } +} + +void LLViewerMediaFocus::unZoom() +{ + if(mMediaControls.get()) + { + mMediaControls.get()->resetZoomLevel(); + } +} + +bool LLViewerMediaFocus::isZoomed() const +{ + return (mMediaControls.get() && mMediaControls.get()->getZoomLevel() != LLPanelPrimMediaControls::ZOOM_NONE); +} + +bool LLViewerMediaFocus::isZoomedOnMedia(LLUUID media_id) +{ + if (isZoomed()) + { + return (mFocusedImplID == media_id) || (mPrevFocusedImplID == media_id); + } + return false; +} + +LLUUID LLViewerMediaFocus::getControlsMediaID() +{ + if(getFocusedMediaImpl()) + { + return mFocusedImplID; + } + else if(getHoverMediaImpl()) + { + return mHoverImplID; + } + + return LLUUID::null; +} + +bool LLViewerMediaFocus::wantsKeyUpKeyDown() const +{ + return true; +} + +bool LLViewerMediaFocus::wantsReturnKey() const +{ + return true; +} diff --git a/indra/newview/llviewermediafocus.h b/indra/newview/llviewermediafocus.h index b17f1928fd..9a9fc72e70 100644 --- a/indra/newview/llviewermediafocus.h +++ b/indra/newview/llviewermediafocus.h @@ -1,120 +1,120 @@ -/** - * @file llpanelmsgs.h - * @brief Message popup preferences panel - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_VIEWERMEDIAFOCUS_H -#define LL_VIEWERMEDIAFOCUS_H - -// includes for LLViewerMediaFocus -#include "llfocusmgr.h" -#include "llviewermedia.h" -#include "llviewerobject.h" -#include "llviewerwindow.h" -#include "llselectmgr.h" - -class LLViewerMediaImpl; -class LLPanelPrimMediaControls; - -class LLViewerMediaFocus : - public LLFocusableElement, - public LLSingleton -{ - LLSINGLETON(LLViewerMediaFocus); - ~LLViewerMediaFocus(); - -public: - // Set/clear the face that has media focus (takes keyboard input and has the full set of controls) - void setFocusFace(LLPointer objectp, S32 face, viewer_media_t media_impl, LLVector3 pick_normal = LLVector3::zero); - void clearFocus(); - - // Set/clear the face that has "media hover" (has the mimimal set of controls to zoom in or pop out into a media browser). - // If a media face has focus, the media hover will be ignored. - void setHoverFace(LLPointer objectp, S32 face, viewer_media_t media_impl, LLVector3 pick_normal = LLVector3::zero); - void clearHover(); - - bool getFocus(); - /*virtual*/ bool handleKey(KEY key, MASK mask, bool called_from_parent) override; - /*virtual*/ bool handleKeyUp(KEY key, MASK mask, bool called_from_parent) override; - /*virtual*/ bool handleUnicodeChar(llwchar uni_char, bool called_from_parent) override; - bool handleScrollWheel(const LLVector2& texture_coords, S32 clicks_x, S32 clicks_y); - bool handleScrollWheel(S32 x, S32 y, S32 clicks_x, S32 clicks_y); - - void update(); - - static LLVector3d setCameraZoom(LLViewerObject* object, LLVector3 normal, F32 padding_factor, bool zoom_in_only = false); - static F32 getBBoxAspectRatio(const LLBBox& bbox, const LLVector3& normal, F32* height, F32* width, F32* depth); - - bool isFocusedOnFace(LLPointer objectp, S32 face); - bool isHoveringOverFace(LLPointer objectp, S32 face); - bool isHoveringOverFocused() { return mFocusedObjectID == mHoverObjectID && mFocusedObjectFace == mHoverObjectFace; }; - - // These look up (by uuid) and return the values that were set with setFocusFace. They will return null if the objects have been destroyed. - LLViewerMediaImpl* getFocusedMediaImpl(); - LLViewerObject* getFocusedObject(); - S32 getFocusedFace() { return mFocusedObjectFace; } - LLUUID getFocusedObjectID() { return mFocusedObjectID; } - - // These look up (by uuid) and return the values that were set with setHoverFace. They will return null if the objects have been destroyed. - LLViewerMediaImpl* getHoverMediaImpl(); - LLViewerObject* getHoverObject(); - S32 getHoverFace() { return mHoverObjectFace; } - - // Try to focus/zoom on the specified media (if it's on an object in world). - void focusZoomOnMedia(LLUUID media_id); - // Are we zoomed in? - bool isZoomed() const; - bool isZoomedOnMedia(LLUUID media_id); - void unZoom(); - - // Return the ID of the media instance the controls are currently attached to (either focus or hover). - LLUUID getControlsMediaID(); - - // The MoaP object wants keyup and keydown events. Overridden to return true. - virtual bool wantsKeyUpKeyDown() const override; - virtual bool wantsReturnKey() const override; - -protected: - /*virtual*/ void onFocusReceived() override; - /*virtual*/ void onFocusLost() override; - -private: - - LLHandle mMediaControls; - LLObjectSelectionHandle mSelection; - - LLUUID mFocusedObjectID; - S32 mFocusedObjectFace; - LLUUID mFocusedImplID; - LLUUID mPrevFocusedImplID; - LLVector3 mFocusedObjectNormal; - - LLUUID mHoverObjectID; - S32 mHoverObjectFace; - LLUUID mHoverImplID; - LLVector3 mHoverObjectNormal; -}; - - -#endif // LL_VIEWERMEDIAFOCUS_H +/** + * @file llpanelmsgs.h + * @brief Message popup preferences panel + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_VIEWERMEDIAFOCUS_H +#define LL_VIEWERMEDIAFOCUS_H + +// includes for LLViewerMediaFocus +#include "llfocusmgr.h" +#include "llviewermedia.h" +#include "llviewerobject.h" +#include "llviewerwindow.h" +#include "llselectmgr.h" + +class LLViewerMediaImpl; +class LLPanelPrimMediaControls; + +class LLViewerMediaFocus : + public LLFocusableElement, + public LLSingleton +{ + LLSINGLETON(LLViewerMediaFocus); + ~LLViewerMediaFocus(); + +public: + // Set/clear the face that has media focus (takes keyboard input and has the full set of controls) + void setFocusFace(LLPointer objectp, S32 face, viewer_media_t media_impl, LLVector3 pick_normal = LLVector3::zero); + void clearFocus(); + + // Set/clear the face that has "media hover" (has the mimimal set of controls to zoom in or pop out into a media browser). + // If a media face has focus, the media hover will be ignored. + void setHoverFace(LLPointer objectp, S32 face, viewer_media_t media_impl, LLVector3 pick_normal = LLVector3::zero); + void clearHover(); + + bool getFocus(); + /*virtual*/ bool handleKey(KEY key, MASK mask, bool called_from_parent) override; + /*virtual*/ bool handleKeyUp(KEY key, MASK mask, bool called_from_parent) override; + /*virtual*/ bool handleUnicodeChar(llwchar uni_char, bool called_from_parent) override; + bool handleScrollWheel(const LLVector2& texture_coords, S32 clicks_x, S32 clicks_y); + bool handleScrollWheel(S32 x, S32 y, S32 clicks_x, S32 clicks_y); + + void update(); + + static LLVector3d setCameraZoom(LLViewerObject* object, LLVector3 normal, F32 padding_factor, bool zoom_in_only = false); + static F32 getBBoxAspectRatio(const LLBBox& bbox, const LLVector3& normal, F32* height, F32* width, F32* depth); + + bool isFocusedOnFace(LLPointer objectp, S32 face); + bool isHoveringOverFace(LLPointer objectp, S32 face); + bool isHoveringOverFocused() { return mFocusedObjectID == mHoverObjectID && mFocusedObjectFace == mHoverObjectFace; }; + + // These look up (by uuid) and return the values that were set with setFocusFace. They will return null if the objects have been destroyed. + LLViewerMediaImpl* getFocusedMediaImpl(); + LLViewerObject* getFocusedObject(); + S32 getFocusedFace() { return mFocusedObjectFace; } + LLUUID getFocusedObjectID() { return mFocusedObjectID; } + + // These look up (by uuid) and return the values that were set with setHoverFace. They will return null if the objects have been destroyed. + LLViewerMediaImpl* getHoverMediaImpl(); + LLViewerObject* getHoverObject(); + S32 getHoverFace() { return mHoverObjectFace; } + + // Try to focus/zoom on the specified media (if it's on an object in world). + void focusZoomOnMedia(LLUUID media_id); + // Are we zoomed in? + bool isZoomed() const; + bool isZoomedOnMedia(LLUUID media_id); + void unZoom(); + + // Return the ID of the media instance the controls are currently attached to (either focus or hover). + LLUUID getControlsMediaID(); + + // The MoaP object wants keyup and keydown events. Overridden to return true. + virtual bool wantsKeyUpKeyDown() const override; + virtual bool wantsReturnKey() const override; + +protected: + /*virtual*/ void onFocusReceived() override; + /*virtual*/ void onFocusLost() override; + +private: + + LLHandle mMediaControls; + LLObjectSelectionHandle mSelection; + + LLUUID mFocusedObjectID; + S32 mFocusedObjectFace; + LLUUID mFocusedImplID; + LLUUID mPrevFocusedImplID; + LLVector3 mFocusedObjectNormal; + + LLUUID mHoverObjectID; + S32 mHoverObjectFace; + LLUUID mHoverImplID; + LLVector3 mHoverObjectNormal; +}; + + +#endif // LL_VIEWERMEDIAFOCUS_H diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index f25e137e1b..273aa3441a 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -1,9989 +1,9989 @@ -/** - * @file llviewermenu.cpp - * @brief Builds menus out of items. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2014, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#ifdef INCLUDE_VLD -#include "vld.h" -#endif - -#include "llviewermenu.h" - -// linden library includes -#include "llavatarnamecache.h" // IDEVO (I Are Not Men!) -#include "llcombobox.h" -#include "llcoros.h" -#include "llfloaterreg.h" -#include "llfloatersidepanelcontainer.h" -#include "llinventorypanel.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "llviewereventrecorder.h" - -// newview includes -#include "llagent.h" -#include "llagentaccess.h" -#include "llagentbenefits.h" -#include "llagentcamera.h" -#include "llagentui.h" -#include "llagentwearables.h" -#include "llagentpilot.h" -#include "llcompilequeue.h" -#include "llconsole.h" -#include "lldebugview.h" -#include "lldiskcache.h" -#include "llenvironment.h" -#include "llfilepicker.h" -#include "llfirstuse.h" -#include "llfloaterabout.h" -#include "llfloaterbuy.h" -#include "llfloaterbuycontents.h" -#include "llbuycurrencyhtml.h" -#include "llfloatergodtools.h" -#include "llfloaterimcontainer.h" -#include "llfloaterland.h" -#include "llfloaterimnearbychat.h" -#include "llfloaterlandholdings.h" -#include "llfloaterpathfindingcharacters.h" -#include "llfloaterpathfindinglinksets.h" -#include "llfloaterpay.h" -#include "llfloaterreporter.h" -#include "llfloatersearch.h" -#include "llfloaterscriptdebug.h" -#include "llfloatersnapshot.h" -#include "llfloatertools.h" -#include "llfloaterworldmap.h" -#include "llfloaterbuildoptions.h" -#include "llavataractions.h" -#include "lllandmarkactions.h" -#include "llgroupmgr.h" -#include "lltooltip.h" -#include "lltoolface.h" -#include "llhints.h" -#include "llhudeffecttrail.h" -#include "llhudmanager.h" -#include "llimview.h" -#include "llinventorybridge.h" -#include "llinventorydefines.h" -#include "llinventoryfunctions.h" -#include "llpanellogin.h" -#include "llpanelblockedlist.h" -#include "llpanelmaininventory.h" -#include "llmarketplacefunctions.h" -#include "llmaterialeditor.h" -#include "llmenuoptionpathfindingrebakenavmesh.h" -#include "llmoveview.h" -#include "llnavigationbar.h" -#include "llparcel.h" -#include "llrootview.h" -#include "llsceneview.h" -#include "llscenemonitor.h" -#include "llselectmgr.h" -#include "llsidepanelappearance.h" -#include "llspellcheckmenuhandler.h" -#include "llstatusbar.h" -#include "lltextureview.h" -#include "lltoolbarview.h" -#include "lltoolcomp.h" -#include "lltoolmgr.h" -#include "lltoolpie.h" -#include "lltoolselectland.h" -#include "lltrans.h" -#include "llviewerdisplay.h" //for gWindowResized -#include "llviewergenericmessage.h" -#include "llviewerhelp.h" -#include "llviewermenufile.h" // init_menu_file() -#include "llviewermessage.h" -#include "llviewernetwork.h" -#include "llviewerobjectlist.h" -#include "llviewerparcelmgr.h" -#include "llviewerstats.h" -#include "llviewerstatsrecorder.h" -#include "llvoavatarself.h" -#include "llvoicevivox.h" -#include "llworld.h" -#include "llworldmap.h" -#include "pipeline.h" -#include "llviewerjoystick.h" -#include "llfloatercamera.h" -#include "lluilistener.h" -#include "llappearancemgr.h" -#include "lltrans.h" -#include "lltoolgrab.h" -#include "llwindow.h" -#include "llpathfindingmanager.h" -#include "llstartup.h" -#include "boost/unordered_map.hpp" -#include -#include -#include "llcleanup.h" -#include "llviewershadermgr.h" - -using namespace LLAvatarAppearanceDefines; - -typedef LLPointer LLViewerObjectPtr; - -static boost::unordered_map sDefaultItemLabels; - -bool enable_land_build(void*); -bool enable_object_build(void*); - -LLVOAvatar* find_avatar_from_object( LLViewerObject* object ); -LLVOAvatar* find_avatar_from_object( const LLUUID& object_id ); - -void handle_test_load_url(void*); - -// -// Evil hackish imported globals - -//extern bool gHideSelectedObjects; -//extern bool gAllowSelectAvatar; -//extern bool gDebugAvatarRotation; -extern bool gDebugClicks; -extern bool gDebugWindowProc; -extern bool gShaderProfileFrame; - -//extern bool gDebugTextEditorTips; -//extern bool gDebugSelectMgr; - -// -// Globals -// - -LLMenuBarGL *gMenuBarView = NULL; -LLViewerMenuHolderGL *gMenuHolder = NULL; -LLMenuGL *gPopupMenuView = NULL; -LLMenuGL *gEditMenu = NULL; -LLMenuBarGL *gLoginMenuBarView = NULL; - -// Pie menus -LLContextMenu *gMenuAvatarSelf = NULL; -LLContextMenu *gMenuAvatarOther = NULL; -LLContextMenu *gMenuObject = NULL; -LLContextMenu *gMenuAttachmentSelf = NULL; -LLContextMenu *gMenuAttachmentOther = NULL; -LLContextMenu *gMenuLand = NULL; -LLContextMenu *gMenuMuteParticle = NULL; - -const std::string SAVE_INTO_TASK_INVENTORY("Save Object Back to Object Contents"); - -LLMenuGL* gAttachSubMenu = NULL; -LLMenuGL* gDetachSubMenu = NULL; -LLMenuGL* gTakeOffClothes = NULL; -LLMenuGL* gDetachAvatarMenu = NULL; -LLMenuGL* gDetachHUDAvatarMenu = NULL; -LLContextMenu* gAttachScreenPieMenu = NULL; -LLContextMenu* gAttachPieMenu = NULL; -LLContextMenu* gAttachBodyPartPieMenus[9]; -LLContextMenu* gDetachPieMenu = NULL; -LLContextMenu* gDetachScreenPieMenu = NULL; -LLContextMenu* gDetachAttSelfMenu = NULL; -LLContextMenu* gDetachHUDAttSelfMenu = NULL; -LLContextMenu* gDetachBodyPartPieMenus[9]; - -// -// Local prototypes - -// File Menu -void handle_compress_image(void*); -void handle_compress_file_test(void*); - - -// Edit menu -void handle_dump_group_info(void *); -void handle_dump_capabilities_info(void *); - -// Advanced->Consoles menu -void handle_region_dump_settings(void*); -void handle_region_dump_temp_asset_data(void*); -void handle_region_clear_temp_asset_data(void*); - -// Object pie menu -bool sitting_on_selection(); - -void near_sit_object(); -//void label_sit_or_stand(std::string& label, void*); -// buy and take alias into the same UI positions, so these -// declarations handle this mess. -bool is_selection_buy_not_take(); -S32 selection_price(); -bool enable_take(); -void handle_object_show_inspector(); -void handle_avatar_show_inspector(); -bool confirm_take(const LLSD& notification, const LLSD& response, LLObjectSelectionHandle selection_handle); -bool confirm_take_separate(const LLSD ¬ification, const LLSD &response, LLObjectSelectionHandle selection_handle); - -void handle_buy_object(LLSaleInfo sale_info); -void handle_buy_contents(LLSaleInfo sale_info); - -// Land pie menu -void near_sit_down_point(bool success, void *); - -// Avatar pie menu - -// Debug menu - - -void velocity_interpolate( void* ); -void handle_visual_leak_detector_toggle(void*); -void handle_rebake_textures(void*); -bool check_admin_override(void*); -void handle_admin_override_toggle(void*); -#ifdef TOGGLE_HACKED_GODLIKE_VIEWER -void handle_toggle_hacked_godmode(void*); -bool check_toggle_hacked_godmode(void*); -bool enable_toggle_hacked_godmode(void*); -#endif - -void toggle_show_xui_names(void *); -bool check_show_xui_names(void *); - -// Debug UI - -void handle_buy_currency_test(void*); - -void handle_god_mode(void*); - -// God menu -void handle_leave_god_mode(void*); - - -void handle_reset_view(); - -void handle_duplicate_in_place(void*); - -void handle_object_owner_self(void*); -void handle_object_owner_permissive(void*); -void handle_object_lock(void*); -void handle_object_asset_ids(void*); -void force_take_copy(void*); - -void handle_force_parcel_owner_to_me(void*); -void handle_force_parcel_to_content(void*); -void handle_claim_public_land(void*); - -void handle_god_request_avatar_geometry(void *); // Hack for easy testing of new avatar geometry -void reload_vertex_shader(void *); -void handle_disconnect_viewer(void *); - -void force_error_breakpoint(void *); -void force_error_llerror(void *); -void force_error_llerror_msg(void*); -void force_error_bad_memory_access(void *); -void force_error_infinite_loop(void *); -void force_error_software_exception(void *); -void force_error_os_exception(void*); -void force_error_driver_crash(void *); -void force_error_coroutine_crash(void *); -void force_error_thread_crash(void *); - -void handle_force_delete(void*); -void print_object_info(void*); -void print_agent_nvpairs(void*); -void toggle_debug_menus(void*); -void upload_done_callback(const LLUUID& uuid, void* user_data, S32 result, LLExtStat ext_status); -void dump_select_mgr(void*); - -void dump_inventory(void*); -void toggle_visibility(void*); -bool get_visibility(void*); - -// Avatar Pie menu -void request_friendship(const LLUUID& agent_id); - -// Tools menu -void handle_selected_texture_info(void*); -void handle_selected_material_info(); - -void handle_dump_followcam(void*); -void handle_viewer_enable_message_log(void*); -void handle_viewer_disable_message_log(void*); - -bool enable_buy_land(void*); - -// Help menu - -void handle_test_male(void *); -void handle_test_female(void *); -void handle_dump_attachments(void *); -void handle_dump_avatar_local_textures(void*); -void handle_debug_avatar_textures(void*); -void handle_grab_baked_texture(void*); -bool enable_grab_baked_texture(void*); -void handle_dump_region_object_cache(void*); -void handle_reset_interest_lists(void *); - -bool enable_save_into_task_inventory(void*); - -bool enable_detach(const LLSD& = LLSD()); -void menu_toggle_attached_lights(void* user_data); -void menu_toggle_attached_particles(void* user_data); - -class LLMenuParcelObserver : public LLParcelObserver -{ -public: - LLMenuParcelObserver(); - ~LLMenuParcelObserver(); - virtual void changed(); -}; - -static LLMenuParcelObserver* gMenuParcelObserver = NULL; - -static LLUIListener sUIListener; - -LLMenuParcelObserver::LLMenuParcelObserver() -{ - LLViewerParcelMgr::getInstance()->addObserver(this); -} - -LLMenuParcelObserver::~LLMenuParcelObserver() -{ - LLViewerParcelMgr::getInstance()->removeObserver(this); -} - -void LLMenuParcelObserver::changed() -{ - LLParcel *parcel = LLViewerParcelMgr::getInstance()->getParcelSelection()->getParcel(); - if (gMenuLand && parcel) - { - LLView* child = gMenuLand->findChild("Land Buy Pass"); - if (child) - { - child->setEnabled(LLPanelLandGeneral::enableBuyPass(NULL) && !(parcel->getOwnerID() == gAgent.getID())); - } - - child = gMenuLand->findChild("Land Buy"); - if (child) - { - bool buyable = enable_buy_land(NULL); - child->setEnabled(buyable); - } - } -} - - -void initialize_menus(); - -//----------------------------------------------------------------------------- -// Initialize main menus -// -// HOW TO NAME MENUS: -// -// First Letter Of Each Word Is Capitalized, Even At Or And -// -// Items that lead to dialog boxes end in "..." -// -// Break up groups of more than 6 items with separators -//----------------------------------------------------------------------------- - -void set_merchant_SLM_menu() -{ - // All other cases (new merchant, not merchant, migrated merchant): show the new Marketplace Listings menu and enable the tool - gMenuHolder->getChild("MarketplaceListings")->setVisible(true); - LLCommand* command = LLCommandManager::instance().getCommand("marketplacelistings"); - gToolBarView->enableCommand(command->id(), true); - - const LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); - if (marketplacelistings_id.isNull()) - { - U32 mkt_status = LLMarketplaceData::instance().getSLMStatus(); - bool is_merchant = (mkt_status == MarketplaceStatusCodes::MARKET_PLACE_MERCHANT) || (mkt_status == MarketplaceStatusCodes::MARKET_PLACE_MIGRATED_MERCHANT); - if (is_merchant) - { - gInventory.ensureCategoryForTypeExists(LLFolderType::FT_MARKETPLACE_LISTINGS); - LL_WARNS("SLM") << "Creating the marketplace listings folder for a merchant" << LL_ENDL; - } - } -} - -void check_merchant_status(bool force) -{ - if (force) - { - // Reset the SLM status: we actually want to check again, that's the point of calling check_merchant_status() - LLMarketplaceData::instance().setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_NOT_INITIALIZED); - } - // Hide SLM related menu item - gMenuHolder->getChild("MarketplaceListings")->setVisible(false); - - // Also disable the toolbar button for Marketplace Listings - LLCommand* command = LLCommandManager::instance().getCommand("marketplacelistings"); - gToolBarView->enableCommand(command->id(), false); - - // Launch an SLM test connection to get the merchant status - LLMarketplaceData::instance().initializeSLM(boost::bind(&set_merchant_SLM_menu)); -} - -void init_menus() -{ - // Initialize actions - initialize_menus(); - - /// - /// Popup menu - /// - /// The popup menu is now populated by the show_context_menu() - /// method. - - LLMenuGL::Params menu_params; - menu_params.name = "Popup"; - menu_params.visible = false; - gPopupMenuView = LLUICtrlFactory::create(menu_params); - gMenuHolder->addChild( gPopupMenuView ); - - /// - /// Context menus - /// - - const widget_registry_t& registry = - LLViewerMenuHolderGL::child_registry_t::instance(); - gEditMenu = LLUICtrlFactory::createFromFile("menu_edit.xml", gMenuHolder, registry); - gMenuAvatarSelf = LLUICtrlFactory::createFromFile( - "menu_avatar_self.xml", gMenuHolder, registry); - gMenuAvatarOther = LLUICtrlFactory::createFromFile( - "menu_avatar_other.xml", gMenuHolder, registry); - - gDetachScreenPieMenu = gMenuHolder->getChild("Object Detach HUD", true); - gDetachPieMenu = gMenuHolder->getChild("Object Detach", true); - - gMenuObject = LLUICtrlFactory::createFromFile( - "menu_object.xml", gMenuHolder, registry); - - gAttachScreenPieMenu = gMenuHolder->getChild("Object Attach HUD"); - gAttachPieMenu = gMenuHolder->getChild("Object Attach"); - - gMenuAttachmentSelf = LLUICtrlFactory::createFromFile( - "menu_attachment_self.xml", gMenuHolder, registry); - gMenuAttachmentOther = LLUICtrlFactory::createFromFile( - "menu_attachment_other.xml", gMenuHolder, registry); - - gDetachHUDAttSelfMenu = gMenuHolder->getChild("Detach Self HUD", true); - gDetachAttSelfMenu = gMenuHolder->getChild("Detach Self", true); - - gMenuLand = LLUICtrlFactory::createFromFile( - "menu_land.xml", gMenuHolder, registry); - - gMenuMuteParticle = LLUICtrlFactory::createFromFile( - "menu_mute_particle.xml", gMenuHolder, registry); - - /// - /// set up the colors - /// - LLColor4 color; - - LLColor4 context_menu_color = LLUIColorTable::instance().getColor("MenuPopupBgColor"); - - gMenuAvatarSelf->setBackgroundColor( context_menu_color ); - gMenuAvatarOther->setBackgroundColor( context_menu_color ); - gMenuObject->setBackgroundColor( context_menu_color ); - gMenuAttachmentSelf->setBackgroundColor( context_menu_color ); - gMenuAttachmentOther->setBackgroundColor( context_menu_color ); - - gMenuLand->setBackgroundColor( context_menu_color ); - - color = LLUIColorTable::instance().getColor( "MenuPopupBgColor" ); - gPopupMenuView->setBackgroundColor( color ); - - // If we are not in production, use a different color to make it apparent. - if (LLGridManager::getInstance()->isInProductionGrid()) - { - color = LLUIColorTable::instance().getColor( "MenuBarBgColor" ); - } - else - { - color = LLUIColorTable::instance().getColor( "MenuNonProductionBgColor" ); - } - - LLView* menu_bar_holder = gViewerWindow->getRootView()->getChildView("menu_bar_holder"); - - gMenuBarView = LLUICtrlFactory::getInstance()->createFromFile("menu_viewer.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - gMenuBarView->setRect(LLRect(0, menu_bar_holder->getRect().mTop, 0, menu_bar_holder->getRect().mTop - MENU_BAR_HEIGHT)); - gMenuBarView->setBackgroundColor( color ); - - menu_bar_holder->addChild(gMenuBarView); - - gViewerWindow->setMenuBackgroundColor(false, - LLGridManager::getInstance()->isInProductionGrid()); - - // *TODO:Also fix cost in llfolderview.cpp for Inventory menus - const std::string texture_upload_cost_str = std::to_string(LLAgentBenefitsMgr::current().getTextureUploadCost()); - const std::string sound_upload_cost_str = std::to_string(LLAgentBenefitsMgr::current().getSoundUploadCost()); - const std::string animation_upload_cost_str = std::to_string(LLAgentBenefitsMgr::current().getAnimationUploadCost()); - gMenuHolder->childSetLabelArg("Upload Image", "[COST]", texture_upload_cost_str); - gMenuHolder->childSetLabelArg("Upload Sound", "[COST]", sound_upload_cost_str); - gMenuHolder->childSetLabelArg("Upload Animation", "[COST]", animation_upload_cost_str); - - gAttachSubMenu = gMenuBarView->findChildMenuByName("Attach Object", true); - gDetachSubMenu = gMenuBarView->findChildMenuByName("Detach Object", true); - - gDetachAvatarMenu = gMenuHolder->getChild("Avatar Detach", true); - gDetachHUDAvatarMenu = gMenuHolder->getChild("Avatar Detach HUD", true); - - // Don't display the Memory console menu if the feature is turned off - LLMenuItemCheckGL *memoryMenu = gMenuBarView->getChild("Memory", true); - if (memoryMenu) - { - memoryMenu->setVisible(false); - } - - gMenuBarView->createJumpKeys(); - - // Let land based option enable when parcel changes - gMenuParcelObserver = new LLMenuParcelObserver(); - - gLoginMenuBarView = LLUICtrlFactory::getInstance()->createFromFile("menu_login.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - gLoginMenuBarView->arrangeAndClear(); - LLRect menuBarRect = gLoginMenuBarView->getRect(); - menuBarRect.setLeftTopAndSize(0, menu_bar_holder->getRect().getHeight(), menuBarRect.getWidth(), menuBarRect.getHeight()); - gLoginMenuBarView->setRect(menuBarRect); - gLoginMenuBarView->setBackgroundColor( color ); - menu_bar_holder->addChild(gLoginMenuBarView); - - // tooltips are on top of EVERYTHING, including menus - gViewerWindow->getRootView()->sendChildToFront(gToolTipView); -} - -/////////////////// -// SHOW CONSOLES // -/////////////////// - - -class LLAdvancedToggleConsole : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string console_type = userdata.asString(); - if ("texture" == console_type) - { - toggle_visibility( (void*)gTextureView ); - } - else if ("debug" == console_type) - { - toggle_visibility( (void*)static_cast(gDebugView->mDebugConsolep)); - } - else if ("fast timers" == console_type) - { - LLFloaterReg::toggleInstance("block_timers"); - } - else if ("scene view" == console_type) - { - toggle_visibility( (void*)gSceneView); - } - else if ("scene monitor" == console_type) - { - toggle_visibility( (void*)gSceneMonitorView); - } - - return true; - } -}; -class LLAdvancedCheckConsole : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string console_type = userdata.asString(); - bool new_value = false; - if ("texture" == console_type) - { - new_value = get_visibility( (void*)gTextureView ); - } - else if ("debug" == console_type) - { - new_value = get_visibility( (void*)((LLView*)gDebugView->mDebugConsolep) ); - } - else if ("fast timers" == console_type) - { - new_value = LLFloaterReg::instanceVisible("block_timers"); - } - else if ("scene view" == console_type) - { - new_value = get_visibility( (void*) gSceneView); - } - else if ("scene monitor" == console_type) - { - new_value = get_visibility( (void*) gSceneMonitorView); - } - - return new_value; - } -}; - - -////////////////////////// -// DUMP INFO TO CONSOLE // -////////////////////////// - - -class LLAdvancedDumpInfoToConsole : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - gDebugView->mDebugConsolep->setVisible(true); - std::string info_type = userdata.asString(); - if ("region" == info_type) - { - handle_region_dump_settings(NULL); - } - else if ("group" == info_type) - { - handle_dump_group_info(NULL); - } - else if ("capabilities" == info_type) - { - handle_dump_capabilities_info(NULL); - } - return true; - } -}; - - -////////////// -// HUD INFO // -////////////// - - -class LLAdvancedToggleHUDInfo : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string info_type = userdata.asString(); - - if ("camera" == info_type) - { - gDisplayCameraPos = !(gDisplayCameraPos); - } - else if ("wind" == info_type) - { - gDisplayWindInfo = !(gDisplayWindInfo); - } - else if ("fov" == info_type) - { - gDisplayFOV = !(gDisplayFOV); - } - else if ("badge" == info_type) - { - gDisplayBadge = !(gDisplayBadge); - } - return true; - } -}; - -class LLAdvancedCheckHUDInfo : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string info_type = userdata.asString(); - bool new_value = false; - if ("camera" == info_type) - { - new_value = gDisplayCameraPos; - } - else if ("wind" == info_type) - { - new_value = gDisplayWindInfo; - } - else if ("fov" == info_type) - { - new_value = gDisplayFOV; - } - else if ("badge" == info_type) - { - new_value = gDisplayBadge; - } - return new_value; - } -}; - - -/////////////////////// -// CLEAR GROUP CACHE // -/////////////////////// - -class LLAdvancedClearGroupCache : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLGroupMgr::debugClearAllGroups(NULL); - return true; - } -}; - - - - -///////////////// -// RENDER TYPE // -///////////////// -U32 render_type_from_string(std::string render_type) -{ - if ("simple" == render_type) - { - return LLPipeline::RENDER_TYPE_SIMPLE; - } - else if ("alpha" == render_type) - { - return LLPipeline::RENDER_TYPE_ALPHA; - } - else if ("tree" == render_type) - { - return LLPipeline::RENDER_TYPE_TREE; - } - else if ("character" == render_type) - { - return LLPipeline::RENDER_TYPE_AVATAR; - } - else if ("controlAV" == render_type) // Animesh - { - return LLPipeline::RENDER_TYPE_CONTROL_AV; - } - else if ("surfacePatch" == render_type) - { - return LLPipeline::RENDER_TYPE_TERRAIN; - } - else if ("sky" == render_type) - { - return LLPipeline::RENDER_TYPE_SKY; - } - else if ("water" == render_type) - { - return LLPipeline::RENDER_TYPE_WATER; - } - else if ("volume" == render_type) - { - return LLPipeline::RENDER_TYPE_VOLUME; - } - else if ("grass" == render_type) - { - return LLPipeline::RENDER_TYPE_GRASS; - } - else if ("clouds" == render_type) - { - return LLPipeline::RENDER_TYPE_CLOUDS; - } - else if ("particles" == render_type) - { - return LLPipeline::RENDER_TYPE_PARTICLES; - } - else if ("bump" == render_type) - { - return LLPipeline::RENDER_TYPE_BUMP; - } - else if ("pbr" == render_type) - { - return LLPipeline::RENDER_TYPE_GLTF_PBR; - } - else - { - return 0; - } -} - - -class LLAdvancedToggleRenderType : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - U32 render_type = render_type_from_string( userdata.asString() ); - if ( render_type != 0 ) - { - LLPipeline::toggleRenderTypeControl( render_type ); - } - return true; - } -}; - - -class LLAdvancedCheckRenderType : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - U32 render_type = render_type_from_string( userdata.asString() ); - bool new_value = false; - - if ( render_type != 0 ) - { - new_value = LLPipeline::hasRenderTypeControl( render_type ); - } - - return new_value; - } -}; - - -///////////// -// FEATURE // -///////////// -U32 feature_from_string(std::string feature) -{ - if ("ui" == feature) - { - return LLPipeline::RENDER_DEBUG_FEATURE_UI; - } - else if ("selected" == feature) - { - return LLPipeline::RENDER_DEBUG_FEATURE_SELECTED; - } - else if ("highlighted" == feature) - { - return LLPipeline::RENDER_DEBUG_FEATURE_HIGHLIGHTED; - } - else if ("dynamic textures" == feature) - { - return LLPipeline::RENDER_DEBUG_FEATURE_DYNAMIC_TEXTURES; - } - else if ("foot shadows" == feature) - { - return LLPipeline::RENDER_DEBUG_FEATURE_FOOT_SHADOWS; - } - else if ("fog" == feature) - { - return LLPipeline::RENDER_DEBUG_FEATURE_FOG; - } - else if ("fr info" == feature) - { - return LLPipeline::RENDER_DEBUG_FEATURE_FR_INFO; - } - else if ("flexible" == feature) - { - return LLPipeline::RENDER_DEBUG_FEATURE_FLEXIBLE; - } - else - { - return 0; - } -}; - - -class LLAdvancedToggleFeature : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - U32 feature = feature_from_string( userdata.asString() ); - if ( feature != 0 ) - { - LLPipeline::toggleRenderDebugFeature( feature ); - } - return true; - } -}; - -class LLAdvancedCheckFeature : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) -{ - U32 feature = feature_from_string( userdata.asString() ); - bool new_value = false; - - if ( feature != 0 ) - { - new_value = LLPipeline::toggleRenderDebugFeatureControl( feature ); - } - - return new_value; -} -}; - -class LLAdvancedCheckDisplayTextureDensity : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string mode = userdata.asString(); - if (!gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXEL_DENSITY)) - { - return mode == "none"; - } - if (mode == "current") - { - return LLViewerTexture::sDebugTexelsMode == LLViewerTexture::DEBUG_TEXELS_CURRENT; - } - else if (mode == "desired") - { - return LLViewerTexture::sDebugTexelsMode == LLViewerTexture::DEBUG_TEXELS_DESIRED; - } - else if (mode == "full") - { - return LLViewerTexture::sDebugTexelsMode == LLViewerTexture::DEBUG_TEXELS_FULL; - } - return false; - } -}; - -class LLAdvancedSetDisplayTextureDensity : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string mode = userdata.asString(); - if (mode == "none") - { - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXEL_DENSITY)) - { - gPipeline.toggleRenderDebug(LLPipeline::RENDER_DEBUG_TEXEL_DENSITY); - } - LLViewerTexture::sDebugTexelsMode = LLViewerTexture::DEBUG_TEXELS_OFF; - } - else if (mode == "current") - { - if (!gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXEL_DENSITY)) - { - gPipeline.toggleRenderDebug(LLPipeline::RENDER_DEBUG_TEXEL_DENSITY); - } - LLViewerTexture::sDebugTexelsMode = LLViewerTexture::DEBUG_TEXELS_CURRENT; - } - else if (mode == "desired") - { - if (!gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXEL_DENSITY)) - { - gPipeline.toggleRenderDebug(LLPipeline::RENDER_DEBUG_TEXEL_DENSITY); - } - gPipeline.setRenderDebugFeatureControl(LLPipeline::RENDER_DEBUG_TEXEL_DENSITY, true); - LLViewerTexture::sDebugTexelsMode = LLViewerTexture::DEBUG_TEXELS_DESIRED; - } - else if (mode == "full") - { - if (!gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXEL_DENSITY)) - { - gPipeline.toggleRenderDebug(LLPipeline::RENDER_DEBUG_TEXEL_DENSITY); - } - LLViewerTexture::sDebugTexelsMode = LLViewerTexture::DEBUG_TEXELS_FULL; - } - return true; - } -}; - - -////////////////// -// INFO DISPLAY // -////////////////// -U64 info_display_from_string(std::string info_display) -{ - if ("verify" == info_display) - { - return LLPipeline::RENDER_DEBUG_VERIFY; - } - else if ("bboxes" == info_display) - { - return LLPipeline::RENDER_DEBUG_BBOXES; - } - else if ("normals" == info_display) - { - return LLPipeline::RENDER_DEBUG_NORMALS; - } - else if ("points" == info_display) - { - return LLPipeline::RENDER_DEBUG_POINTS; - } - else if ("octree" == info_display) - { - return LLPipeline::RENDER_DEBUG_OCTREE; - } - else if ("shadow frusta" == info_display) - { - return LLPipeline::RENDER_DEBUG_SHADOW_FRUSTA; - } - else if ("physics shapes" == info_display) - { - return LLPipeline::RENDER_DEBUG_PHYSICS_SHAPES; - } - else if ("occlusion" == info_display) - { - return LLPipeline::RENDER_DEBUG_OCCLUSION; - } - else if ("render batches" == info_display) - { - return LLPipeline::RENDER_DEBUG_BATCH_SIZE; - } - else if ("update type" == info_display) - { - return LLPipeline::RENDER_DEBUG_UPDATE_TYPE; - } - else if ("texture anim" == info_display) - { - return LLPipeline::RENDER_DEBUG_TEXTURE_ANIM; - } - else if ("texture priority" == info_display) - { - return LLPipeline::RENDER_DEBUG_TEXTURE_PRIORITY; - } - else if ("texture area" == info_display) - { - return LLPipeline::RENDER_DEBUG_TEXTURE_AREA; - } - else if ("face area" == info_display) - { - return LLPipeline::RENDER_DEBUG_FACE_AREA; - } - else if ("lod info" == info_display) - { - return LLPipeline::RENDER_DEBUG_LOD_INFO; - } - else if ("lights" == info_display) - { - return LLPipeline::RENDER_DEBUG_LIGHTS; - } - else if ("particles" == info_display) - { - return LLPipeline::RENDER_DEBUG_PARTICLES; - } - else if ("composition" == info_display) - { - return LLPipeline::RENDER_DEBUG_COMPOSITION; - } - else if ("avatardrawinfo" == info_display) - { - return (LLPipeline::RENDER_DEBUG_AVATAR_DRAW_INFO); - } - else if ("glow" == info_display) - { - return LLPipeline::RENDER_DEBUG_GLOW; - } - else if ("collision skeleton" == info_display) - { - return LLPipeline::RENDER_DEBUG_AVATAR_VOLUME; - } - else if ("joints" == info_display) - { - return LLPipeline::RENDER_DEBUG_AVATAR_JOINTS; - } - else if ("raycast" == info_display) - { - return LLPipeline::RENDER_DEBUG_RAYCAST; - } - else if ("agent target" == info_display) - { - return LLPipeline::RENDER_DEBUG_AGENT_TARGET; - } - else if ("sculpt" == info_display) - { - return LLPipeline::RENDER_DEBUG_SCULPTED; - } - else if ("wind vectors" == info_display) - { - return LLPipeline::RENDER_DEBUG_WIND_VECTORS; - } - else if ("texel density" == info_display) - { - return LLPipeline::RENDER_DEBUG_TEXEL_DENSITY; - } - else if ("triangle count" == info_display) - { - return LLPipeline::RENDER_DEBUG_TRIANGLE_COUNT; - } - else if ("impostors" == info_display) - { - return LLPipeline::RENDER_DEBUG_IMPOSTORS; - } - else if ("reflection probes" == info_display) - { - return LLPipeline::RENDER_DEBUG_REFLECTION_PROBES; - } - else if ("probe updates" == info_display) - { - return LLPipeline::RENDER_DEBUG_PROBE_UPDATES; - } - else - { - LL_WARNS() << "unrecognized feature name '" << info_display << "'" << LL_ENDL; - return 0; - } -}; - -class LLAdvancedToggleInfoDisplay : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - U64 info_display = info_display_from_string( userdata.asString() ); - - LL_INFOS("ViewerMenu") << "toggle " << userdata.asString() << LL_ENDL; - - if ( info_display != 0 ) - { - LLPipeline::toggleRenderDebug( info_display ); - } - - return true; - } -}; - - -class LLAdvancedCheckInfoDisplay : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - U64 info_display = info_display_from_string( userdata.asString() ); - bool new_value = false; - - if ( info_display != 0 ) - { - new_value = LLPipeline::toggleRenderDebugControl( info_display ); - } - - return new_value; - } -}; - - -/////////////////////////// -//// RANDOMIZE FRAMERATE // -/////////////////////////// - - -class LLAdvancedToggleRandomizeFramerate : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - gRandomizeFramerate = !(gRandomizeFramerate); - return true; - } -}; - -class LLAdvancedCheckRandomizeFramerate : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = gRandomizeFramerate; - return new_value; - } -}; - -/////////////////////////// -//// PERIODIC SLOW FRAME // -/////////////////////////// - - -class LLAdvancedTogglePeriodicSlowFrame : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - gPeriodicSlowFrame = !(gPeriodicSlowFrame); - return true; - } -}; - -class LLAdvancedCheckPeriodicSlowFrame : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = gPeriodicSlowFrame; - return new_value; - } -}; - - -/////////////////////////// -// SELECTED TEXTURE INFO // -// -/////////////////////////// - - -class LLAdvancedSelectedTextureInfo : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_selected_texture_info(NULL); - return true; - } -}; - -////////////////////// -// TOGGLE WIREFRAME // -////////////////////// - -class LLAdvancedToggleWireframe : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - gUseWireframe = !(gUseWireframe); - - return true; - } -}; - -class LLAdvancedCheckWireframe : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - return gUseWireframe; - } -}; - - -////////////////////////// -// DUMP SCRIPTED CAMERA // -////////////////////////// - -class LLAdvancedDumpScriptedCamera : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_dump_followcam(NULL); - return true; -} -}; - - - -////////////////////////////// -// DUMP REGION OBJECT CACHE // -////////////////////////////// - - -class LLAdvancedDumpRegionObjectCache : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_dump_region_object_cache(NULL); - return true; - } -}; - -class LLAdvancedToggleInterestList360Mode : public view_listener_t -{ -public: - bool handleEvent(const LLSD &userdata) - { - // Toggle the mode - regions will get updated - if (gAgent.getInterestListMode() == IL_MODE_360) - { - gAgent.changeInterestListMode(IL_MODE_DEFAULT); - } - else - { - gAgent.changeInterestListMode(IL_MODE_360); - } - return true; - } -}; - -class LLAdvancedCheckInterestList360Mode : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - return (gAgent.getInterestListMode() == IL_MODE_360); - } -}; - -class LLAdvancedToggleStatsRecorder : public view_listener_t -{ - bool handleEvent(const LLSD &userdata) - { - if (LLViewerStatsRecorder::instance().isEnabled()) - { // Turn off both recording and logging - LLViewerStatsRecorder::instance().enableObjectStatsRecording(false); - } - else - { // Turn on both recording and logging - LLViewerStatsRecorder::instance().enableObjectStatsRecording(true, true); - } - return true; - } -}; - -class LLAdvancedCheckStatsRecorder : public view_listener_t -{ - bool handleEvent(const LLSD &userdata) - { // Use the logging state as the indicator of whether the stats recorder is on - return LLViewerStatsRecorder::instance().isLogging(); - } -}; - -class LLAdvancedResetInterestLists : public view_listener_t -{ - bool handleEvent(const LLSD &userdata) - { // Reset all region interest lists - handle_reset_interest_lists(NULL); - return true; - } -}; - - -class LLAdvancedBuyCurrencyTest : public view_listener_t - { - bool handleEvent(const LLSD& userdata) - { - handle_buy_currency_test(NULL); - return true; - } -}; - - -///////////////////// -// DUMP SELECT MGR // -///////////////////// - - -class LLAdvancedDumpSelectMgr : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - dump_select_mgr(NULL); - return true; - } -}; - - - -//////////////////// -// DUMP INVENTORY // -//////////////////// - - -class LLAdvancedDumpInventory : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - dump_inventory(NULL); - return true; - } -}; - - - -//////////////////////////////// -// PRINT SELECTED OBJECT INFO // -//////////////////////////////// - - -class LLAdvancedPrintSelectedObjectInfo : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - print_object_info(NULL); - return true; - } -}; - - - -////////////////////// -// PRINT AGENT INFO // -////////////////////// - - -class LLAdvancedPrintAgentInfo : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - print_agent_nvpairs(NULL); - return true; - } -}; - -////////////////// -// DEBUG CLICKS // -////////////////// - - -class LLAdvancedToggleDebugClicks : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - gDebugClicks = !(gDebugClicks); - return true; - } -}; - -class LLAdvancedCheckDebugClicks : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = gDebugClicks; - return new_value; - } -}; - - - -///////////////// -// DEBUG VIEWS // -///////////////// - - -class LLAdvancedToggleDebugViews : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLView::sDebugRects = !(LLView::sDebugRects); - return true; - } -}; - -class LLAdvancedCheckDebugViews : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = LLView::sDebugRects; - return new_value; - } -}; - - - -/////////////////// -// DEBUG UNICODE // -/////////////////// - - -class LLAdvancedToggleDebugUnicode : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLView::sDebugUnicode = !(LLView::sDebugUnicode); - return true; - } -}; - -class LLAdvancedCheckDebugUnicode : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - return LLView::sDebugUnicode; - } -}; - - - -////////////////// -// DEBUG CAMERA // -////////////////// - - -class LLAdvancedToggleDebugCamera : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLView::sDebugCamera = !(LLView::sDebugCamera); - LLFloaterCamera::onDebugCameraToggled(); - return true; - } -}; - -class LLAdvancedCheckDebugCamera : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - return LLView::sDebugCamera; - } -}; - - - -/////////////////////// -// XUI NAME TOOLTIPS // -/////////////////////// - - -class LLAdvancedToggleXUINameTooltips : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - toggle_show_xui_names(NULL); - return true; - } -}; - -class LLAdvancedCheckXUINameTooltips : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = check_show_xui_names(NULL); - return new_value; - } -}; - - - -//////////////////////// -// DEBUG MOUSE EVENTS // -//////////////////////// - - -class LLAdvancedToggleDebugMouseEvents : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLView::sDebugMouseHandling = !(LLView::sDebugMouseHandling); - return true; - } -}; - -class LLAdvancedCheckDebugMouseEvents : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = LLView::sDebugMouseHandling; - return new_value; - } -}; - - - -//////////////// -// DEBUG KEYS // -//////////////// - - -class LLAdvancedToggleDebugKeys : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLView::sDebugKeys = !(LLView::sDebugKeys); - return true; - } -}; - -class LLAdvancedCheckDebugKeys : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = LLView::sDebugKeys; - return new_value; - } -}; - - - -/////////////////////// -// DEBUG WINDOW PROC // -/////////////////////// - - -class LLAdvancedToggleDebugWindowProc : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - gDebugWindowProc = !(gDebugWindowProc); - return true; - } -}; - -class LLAdvancedCheckDebugWindowProc : public view_listener_t - { - bool handleEvent(const LLSD& userdata) - { - bool new_value = gDebugWindowProc; - return new_value; - } -}; - -// ------------------------------XUI MENU --------------------------- - -class LLAdvancedSendTestIms : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLIMModel::instance().testMessages(); - return true; -} -}; - - -/////////////// -// XUI NAMES // -/////////////// - - -class LLAdvancedToggleXUINames : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - toggle_show_xui_names(NULL); - return true; - } -}; - -class LLAdvancedCheckXUINames : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = check_show_xui_names(NULL); - return new_value; - } -}; - - -//////////////////////// -// GRAB BAKED TEXTURE // -//////////////////////// - - -class LLAdvancedGrabBakedTexture : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string texture_type = userdata.asString(); - if ("iris" == texture_type) - { - handle_grab_baked_texture( (void*)BAKED_EYES ); - } - else if ("head" == texture_type) - { - handle_grab_baked_texture( (void*)BAKED_HEAD ); - } - else if ("upper" == texture_type) - { - handle_grab_baked_texture( (void*)BAKED_UPPER ); - } - else if ("lower" == texture_type) - { - handle_grab_baked_texture( (void*)BAKED_LOWER ); - } - else if ("skirt" == texture_type) - { - handle_grab_baked_texture( (void*)BAKED_SKIRT ); - } - else if ("hair" == texture_type) - { - handle_grab_baked_texture( (void*)BAKED_HAIR ); - } - - return true; - } -}; - -class LLAdvancedEnableGrabBakedTexture : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) -{ - std::string texture_type = userdata.asString(); - bool new_value = false; - - if ("iris" == texture_type) - { - new_value = enable_grab_baked_texture( (void*)BAKED_EYES ); - } - else if ("head" == texture_type) - { - new_value = enable_grab_baked_texture( (void*)BAKED_HEAD ); - } - else if ("upper" == texture_type) - { - new_value = enable_grab_baked_texture( (void*)BAKED_UPPER ); - } - else if ("lower" == texture_type) - { - new_value = enable_grab_baked_texture( (void*)BAKED_LOWER ); - } - else if ("skirt" == texture_type) - { - new_value = enable_grab_baked_texture( (void*)BAKED_SKIRT ); - } - else if ("hair" == texture_type) - { - new_value = enable_grab_baked_texture( (void*)BAKED_HAIR ); - } - - return new_value; -} -}; - -/////////////////////// -// APPEARANCE TO XML // -/////////////////////// - - -class LLAdvancedEnableAppearanceToXML : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLViewerObject *obj = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); - if (obj && obj->isAnimatedObject() && obj->getControlAvatar()) - { - return gSavedSettings.getBOOL("DebugAnimatedObjects"); - } - else if (obj && obj->isAttachment() && obj->getAvatar()) - { - return gSavedSettings.getBOOL("DebugAvatarAppearanceMessage"); - } - else if (obj && obj->isAvatar()) - { - // This has to be a non-control avatar, because control avs are invisible and unclickable. - return gSavedSettings.getBOOL("DebugAvatarAppearanceMessage"); - } - else - { - return false; - } - } -}; - -class LLAdvancedAppearanceToXML : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string emptyname; - LLViewerObject *obj = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); - LLVOAvatar *avatar = NULL; - if (obj) - { - if (obj->isAvatar()) - { - avatar = obj->asAvatar(); - } - else - { - // If there is a selection, find the associated - // avatar. Normally there's only one obvious choice. But - // what should be returned if the object is in an attached - // animated object? getAvatar() will give the skeleton of - // the animated object. getAvatarAncestor() will give the - // actual human-driven avatar. - avatar = obj->getAvatar(); - } - } - else - { - // If no selection, use the self avatar. - avatar = gAgentAvatarp; - } - if (avatar) - { - avatar->dumpArchetypeXML(emptyname); - } - return true; - } -}; - - - -/////////////////////////////// -// TOGGLE CHARACTER GEOMETRY // -/////////////////////////////// - - -class LLAdvancedToggleCharacterGeometry : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_god_request_avatar_geometry(NULL); - return true; -} -}; - - - ///////////////////////////// -// TEST MALE / TEST FEMALE // -///////////////////////////// - -class LLAdvancedTestMale : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_test_male(NULL); - return true; - } -}; - - -class LLAdvancedTestFemale : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_test_female(NULL); - return true; - } -}; - -class LLAdvancedForceParamsToDefault : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLAgent::clearVisualParams(NULL); - return true; - } -}; - - -////////////////////////// -// ANIMATION SPEED // -////////////////////////// - -// Utility function to set all AV time factors to the same global value -static void set_all_animation_time_factors(F32 time_factor) -{ - LLMotionController::setCurrentTimeFactor(time_factor); - for (std::vector::iterator iter = LLCharacter::sInstances.begin(); - iter != LLCharacter::sInstances.end(); ++iter) - { - (*iter)->setAnimTimeFactor(time_factor); - } -} - -class LLAdvancedAnimTenFaster : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - //LL_INFOS() << "LLAdvancedAnimTenFaster" << LL_ENDL; - F32 time_factor = LLMotionController::getCurrentTimeFactor(); - time_factor = llmin(time_factor + 0.1f, 2.f); // Upper limit is 200% speed - set_all_animation_time_factors(time_factor); - return true; - } -}; - -class LLAdvancedAnimTenSlower : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - //LL_INFOS() << "LLAdvancedAnimTenSlower" << LL_ENDL; - F32 time_factor = LLMotionController::getCurrentTimeFactor(); - time_factor = llmax(time_factor - 0.1f, 0.1f); // Lower limit is at 10% of normal speed - set_all_animation_time_factors(time_factor); - return true; - } -}; - -class LLAdvancedAnimResetAll : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - set_all_animation_time_factors(1.f); - return true; - } -}; - - -////////////////////////// -// RELOAD VERTEX SHADER // -////////////////////////// - - -class LLAdvancedReloadVertexShader : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - reload_vertex_shader(NULL); - return true; - } -}; - - - -//////////////////// -// ANIMATION INFO // -//////////////////// - - -class LLAdvancedToggleAnimationInfo : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLVOAvatar::sShowAnimationDebug = !(LLVOAvatar::sShowAnimationDebug); - return true; - } -}; - -class LLAdvancedCheckAnimationInfo : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = LLVOAvatar::sShowAnimationDebug; - return new_value; - } -}; - - -////////////////// -// SHOW LOOK AT // -////////////////// - - -class LLAdvancedToggleShowLookAt : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLHUDEffectLookAt::sDebugLookAt = !(LLHUDEffectLookAt::sDebugLookAt); - return true; - } -}; - -class LLAdvancedCheckShowLookAt : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = LLHUDEffectLookAt::sDebugLookAt; - return new_value; - } -}; - - - -/////////////////// -// SHOW POINT AT // -/////////////////// - - -class LLAdvancedToggleShowPointAt : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLHUDEffectPointAt::sDebugPointAt = !(LLHUDEffectPointAt::sDebugPointAt); - return true; - } -}; - -class LLAdvancedCheckShowPointAt : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = LLHUDEffectPointAt::sDebugPointAt; - return new_value; - } -}; - - - -///////////////////////// -// DEBUG JOINT UPDATES // -///////////////////////// - - -class LLAdvancedToggleDebugJointUpdates : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLVOAvatar::sJointDebug = !(LLVOAvatar::sJointDebug); - return true; - } -}; - -class LLAdvancedCheckDebugJointUpdates : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = LLVOAvatar::sJointDebug; - return new_value; - } -}; - - - -///////////////// -// DISABLE LOD // -///////////////// - - -class LLAdvancedToggleDisableLOD : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLViewerJoint::sDisableLOD = !(LLViewerJoint::sDisableLOD); - return true; - } -}; - -class LLAdvancedCheckDisableLOD : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = LLViewerJoint::sDisableLOD; - return new_value; - } -}; - - - -///////////////////////// -// DEBUG CHARACTER VIS // -///////////////////////// - - -class LLAdvancedToggleDebugCharacterVis : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLVOAvatar::sDebugInvisible = !(LLVOAvatar::sDebugInvisible); - return true; - } -}; - -class LLAdvancedCheckDebugCharacterVis : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = LLVOAvatar::sDebugInvisible; - return new_value; - } -}; - - -////////////////////// -// DUMP ATTACHMENTS // -////////////////////// - - -class LLAdvancedDumpAttachments : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_dump_attachments(NULL); - return true; - } -}; - - - -///////////////////// -// REBAKE TEXTURES // -///////////////////// - - -class LLAdvancedRebakeTextures : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_rebake_textures(NULL); - return true; - } -}; - - -#if 1 //ndef LL_RELEASE_FOR_DOWNLOAD -/////////////////////////// -// DEBUG AVATAR TEXTURES // -/////////////////////////// - - -class LLAdvancedDebugAvatarTextures : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - if (gAgent.isGodlike()) - { - handle_debug_avatar_textures(NULL); - } - return true; - } -}; - -//////////////////////////////// -// DUMP AVATAR LOCAL TEXTURES // -//////////////////////////////// - - -class LLAdvancedDumpAvatarLocalTextures : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { -#ifndef LL_RELEASE_FOR_DOWNLOAD - handle_dump_avatar_local_textures(NULL); -#endif - return true; - } -}; - -#endif - -///////////////// -// MESSAGE LOG // -///////////////// - - -class LLAdvancedEnableMessageLog : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_viewer_enable_message_log(NULL); - return true; - } -}; - -class LLAdvancedDisableMessageLog : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_viewer_disable_message_log(NULL); - return true; - } -}; - -///////////////// -// DROP PACKET // -///////////////// - - -class LLAdvancedDropPacket : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - gMessageSystem->mPacketRing.dropPackets(1); - return true; - } -}; - -////////////////////// -// PURGE DISK CACHE // -////////////////////// - - -class LLAdvancedPurgeDiskCache : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop"); - LL::WorkQueue::ptr_t general_queue = LL::WorkQueue::getInstance("General"); - llassert_always(main_queue); - llassert_always(general_queue); - main_queue->postTo( - general_queue, - []() // Work done on general queue - { - LLDiskCache::getInstance()->purge(); - // Nothing needed to return - }, - [](){}); // Callback to main thread is empty as there is nothing left to do - - return true; - } -}; - - -//////////////////////// -// PURGE SHADER CACHE // -//////////////////////// - - -class LLAdvancedPurgeShaderCache : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLViewerShaderMgr::instance()->clearShaderCache(); - LLViewerShaderMgr::instance()->setShaders(); - return true; - } -}; - -//////////////////// -// EVENT Recorder // -/////////////////// - - -class LLAdvancedViewerEventRecorder : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string command = userdata.asString(); - if ("start playback" == command) - { - LL_INFOS() << "Event Playback starting" << LL_ENDL; - LLViewerEventRecorder::instance().playbackRecording(); - LL_INFOS() << "Event Playback completed" << LL_ENDL; - } - else if ("stop playback" == command) - { - // Future - } - else if ("start recording" == command) - { - LLViewerEventRecorder::instance().setEventLoggingOn(); - LL_INFOS() << "Event recording started" << LL_ENDL; - } - else if ("stop recording" == command) - { - LLViewerEventRecorder::instance().setEventLoggingOff(); - LL_INFOS() << "Event recording stopped" << LL_ENDL; - } - - return true; - } -}; - - - - -///////////////// -// AGENT PILOT // -///////////////// - - -class LLAdvancedAgentPilot : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string command = userdata.asString(); - if ("start playback" == command) - { - gAgentPilot.setNumRuns(-1); - gAgentPilot.startPlayback(); - } - else if ("stop playback" == command) - { - gAgentPilot.stopPlayback(); - } - else if ("start record" == command) - { - gAgentPilot.startRecord(); - } - else if ("stop record" == command) - { - gAgentPilot.stopRecord(); - } - - return true; - } -}; - - - -////////////////////// -// AGENT PILOT LOOP // -////////////////////// - - -class LLAdvancedToggleAgentPilotLoop : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - gAgentPilot.setLoop(!gAgentPilot.getLoop()); - return true; - } -}; - -class LLAdvancedCheckAgentPilotLoop : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = gAgentPilot.getLoop(); - return new_value; - } -}; - - -///////////////////////// -// SHOW OBJECT UPDATES // -///////////////////////// - - -class LLAdvancedToggleShowObjectUpdates : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - gShowObjectUpdates = !(gShowObjectUpdates); - return true; - } -}; - -class LLAdvancedCheckShowObjectUpdates : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = gShowObjectUpdates; - return new_value; - } -}; - - - -//////////////////// -// COMPRESS IMAGE // -//////////////////// - - -class LLAdvancedCompressImage : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_compress_image(NULL); - return true; - } -}; - - - -//////////////////////// -// COMPRESS FILE TEST // -//////////////////////// - -class LLAdvancedCompressFileTest : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_compress_file_test(NULL); - return true; - } -}; - - -///////////////////////// -// SHOW DEBUG SETTINGS // -///////////////////////// - - -class LLAdvancedShowDebugSettings : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLFloaterReg::showInstance("settings_debug",userdata); - return true; - } -}; - - - -//////////////////////// -// VIEW ADMIN OPTIONS // -//////////////////////// - -class LLAdvancedEnableViewAdminOptions : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - // Don't enable in god mode since the admin menu is shown anyway. - // Only enable if the user has set the appropriate debug setting. - bool new_value = !gAgent.getAgentAccess().isGodlikeWithoutAdminMenuFakery() && gSavedSettings.getBOOL("AdminMenu"); - return new_value; - } -}; - -class LLAdvancedToggleViewAdminOptions : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_admin_override_toggle(NULL); - return true; - } -}; - -class LLAdvancedToggleVisualLeakDetector : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_visual_leak_detector_toggle(NULL); - return true; - } -}; - -class LLAdvancedCheckViewAdminOptions : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = check_admin_override(NULL) || gAgent.isGodlike(); - return new_value; - } -}; - -////////////////// -// ADMIN STATUS // -////////////////// - - -class LLAdvancedRequestAdminStatus : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_god_mode(NULL); - return true; - } -}; - -class LLAdvancedLeaveAdminStatus : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_leave_god_mode(NULL); - return true; - } -}; - -////////////////////////// -// Advanced > Debugging // -////////////////////////// - -class LLAdvancedForceErrorBreakpoint : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - force_error_breakpoint(NULL); - return true; - } -}; - -class LLAdvancedForceErrorLlerror : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - force_error_llerror(NULL); - return true; - } -}; - -class LLAdvancedForceErrorLlerrorMsg: public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - force_error_llerror_msg(NULL); - return true; - } -}; - -class LLAdvancedForceErrorBadMemoryAccess : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - force_error_bad_memory_access(NULL); - return true; - } -}; - -class LLAdvancedForceErrorBadMemoryAccessCoro : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLCoros::instance().launch( - "AdvancedForceErrorBadMemoryAccessCoro", - [](){ - // Wait for one mainloop() iteration, letting the enclosing - // handleEvent() method return. - llcoro::suspend(); - force_error_bad_memory_access(NULL); - }); - return true; - } -}; - -class LLAdvancedForceErrorInfiniteLoop : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - force_error_infinite_loop(NULL); - return true; - } -}; - -class LLAdvancedForceErrorSoftwareException : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - force_error_software_exception(NULL); - return true; - } -}; - -class LLAdvancedForceOSException: public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - force_error_os_exception(NULL); - return true; - } -}; - -class LLAdvancedForceErrorSoftwareExceptionCoro : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLCoros::instance().launch( - "AdvancedForceErrorSoftwareExceptionCoro", - [](){ - // Wait for one mainloop() iteration, letting the enclosing - // handleEvent() method return. - llcoro::suspend(); - force_error_software_exception(NULL); - }); - return true; - } -}; - -class LLAdvancedForceErrorDriverCrash : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - force_error_driver_crash(NULL); - return true; - } -}; - -class LLAdvancedForceErrorCoroutineCrash : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - force_error_coroutine_crash(NULL); - return true; - } -}; - -class LLAdvancedForceErrorThreadCrash : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - force_error_thread_crash(NULL); - return true; - } -}; - -class LLAdvancedForceErrorDisconnectViewer : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_disconnect_viewer(NULL); - return true; -} -}; - - -#ifdef TOGGLE_HACKED_GODLIKE_VIEWER - -class LLAdvancedHandleToggleHackedGodmode : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_toggle_hacked_godmode(NULL); - return true; - } -}; - -class LLAdvancedCheckToggleHackedGodmode : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - check_toggle_hacked_godmode(NULL); - return true; - } -}; - -class LLAdvancedEnableToggleHackedGodmode : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = enable_toggle_hacked_godmode(NULL); - return new_value; - } -}; -#endif - - -// -////------------------------------------------------------------------- -//// Advanced menu -////------------------------------------------------------------------- - - -////////////////// -// DEVELOP MENU // -////////////////// - -class LLDevelopCheckLoggingLevel : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - U32 level = userdata.asInteger(); - return (static_cast(level) == LLError::getDefaultLevel()); - } -}; - -class LLDevelopSetLoggingLevel : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - U32 level = userdata.asInteger(); - LLError::setDefaultLevel(static_cast(level)); - return true; - } -}; - -////////////////// -// ADMIN MENU // -////////////////// - -// Admin > Object -class LLAdminForceTakeCopy : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - force_take_copy(NULL); - return true; - } -}; - -class LLAdminHandleObjectOwnerSelf : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_object_owner_self(NULL); - return true; - } -}; -class LLAdminHandleObjectOwnerPermissive : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_object_owner_permissive(NULL); - return true; - } -}; - -class LLAdminHandleForceDelete : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_force_delete(NULL); - return true; - } -}; - -class LLAdminHandleObjectLock : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_object_lock(NULL); - return true; - } -}; - -class LLAdminHandleObjectAssetIDs: public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_object_asset_ids(NULL); - return true; - } -}; - -//Admin >Parcel -class LLAdminHandleForceParcelOwnerToMe: public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_force_parcel_owner_to_me(NULL); - return true; - } -}; -class LLAdminHandleForceParcelToContent: public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_force_parcel_to_content(NULL); - return true; - } -}; -class LLAdminHandleClaimPublicLand: public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_claim_public_land(NULL); - return true; - } -}; - -// Admin > Region -class LLAdminHandleRegionDumpTempAssetData: public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_region_dump_temp_asset_data(NULL); - return true; - } -}; -//Admin (Top Level) - -class LLAdminOnSaveState: public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLPanelRegionTools::onSaveState(NULL); - return true; -} -}; - - -//----------------------------------------------------------------------------- -// cleanup_menus() -//----------------------------------------------------------------------------- -void cleanup_menus() -{ - delete gMenuParcelObserver; - gMenuParcelObserver = NULL; - - delete gMenuAvatarSelf; - gMenuAvatarSelf = NULL; - - delete gMenuAvatarOther; - gMenuAvatarOther = NULL; - - delete gMenuObject; - gMenuObject = NULL; - - delete gMenuAttachmentSelf; - gMenuAttachmentSelf = NULL; - - delete gMenuAttachmentOther; - gMenuAttachmentSelf = NULL; - - delete gMenuLand; - gMenuLand = NULL; - - delete gMenuMuteParticle; - gMenuMuteParticle = NULL; - - delete gMenuBarView; - gMenuBarView = NULL; - - delete gPopupMenuView; - gPopupMenuView = NULL; - - delete gMenuHolder; - gMenuHolder = NULL; -} - -//----------------------------------------------------------------------------- -// Object pie menu -//----------------------------------------------------------------------------- - -class LLObjectReportAbuse : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); - if (objectp) - { - LLFloaterReporter::showFromObject(objectp->getID()); - } - return true; - } -}; - -// Enabled it you clicked an object -class LLObjectEnableReportAbuse : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = LLSelectMgr::getInstance()->getSelection()->getObjectCount() != 0; - return new_value; - } -}; - - -void handle_object_touch() -{ - LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); - if (!object) return; - - LLPickInfo pick = LLToolPie::getInstance()->getPick(); - - // *NOTE: Hope the packets arrive safely and in order or else - // there will be some problems. - // *TODO: Just fix this bad assumption. - send_ObjectGrab_message(object, pick, LLVector3::zero); - send_ObjectDeGrab_message(object, pick); -} - -void handle_object_show_original() -{ - LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); - if (!object) - { - return; - } - - LLViewerObject *parent = (LLViewerObject*)object->getParent(); - while (parent) - { - if(parent->isAvatar()) - { - break; - } - object = parent; - parent = (LLViewerObject*)parent->getParent(); - } - - if (!object || object->isAvatar()) - { - return; - } - - show_item_original(object->getAttachmentItemID()); -} - - -static void init_default_item_label(LLUICtrl* ctrl) -{ - const std::string& item_name = ctrl->getName(); - boost::unordered_map::iterator it = sDefaultItemLabels.find(item_name); - if (it == sDefaultItemLabels.end()) - { - // *NOTE: This will not work for items of type LLMenuItemCheckGL because they return boolean value - // (doesn't seem to matter much ATM). - LLStringExplicit default_label = ctrl->getValue().asString(); - if (!default_label.empty()) - { - sDefaultItemLabels.insert(std::pair(item_name, default_label)); - } - } -} - -static LLStringExplicit get_default_item_label(const std::string& item_name) -{ - LLStringExplicit res(""); - boost::unordered_map::iterator it = sDefaultItemLabels.find(item_name); - if (it != sDefaultItemLabels.end()) - { - res = it->second; - } - - return res; -} - - -bool enable_object_touch(LLUICtrl* ctrl) -{ - bool new_value = false; - LLViewerObject* obj = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); - if (obj) - { - LLViewerObject* parent = (LLViewerObject*)obj->getParent(); - new_value = obj->flagHandleTouch() || (parent && parent->flagHandleTouch()); - } - - init_default_item_label(ctrl); - - // Update label based on the node touch name if available. - LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(); - if (node && node->mValid && !node->mTouchName.empty()) - { - ctrl->setValue(node->mTouchName); - } - else - { - ctrl->setValue(get_default_item_label(ctrl->getName())); - } - - return new_value; -}; - -//void label_touch(std::string& label, void*) -//{ -// LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(); -// if (node && node->mValid && !node->mTouchName.empty()) -// { -// label.assign(node->mTouchName); -// } -// else -// { -// label.assign("Touch"); -// } -//} - -void handle_object_open() -{ - LLFloaterReg::showInstance("openobject"); -} - -bool enable_object_inspect() -{ - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - LLViewerObject* selected_objectp = selection->getFirstRootObject(); - return selected_objectp != NULL; -} - -struct LLSelectedTEGetmatIdAndPermissions : public LLSelectedTEFunctor -{ - LLSelectedTEGetmatIdAndPermissions() - : mCanCopy(true) - , mCanModify(true) - , mCanTransfer(true) - , mHasNonPbrFaces(false) - {} - bool apply(LLViewerObject* objectp, S32 te_index) - { - mCanCopy &= (bool)objectp->permCopy(); - mCanTransfer &= (bool)objectp->permTransfer(); - mCanModify &= (bool)objectp->permModify(); - LLUUID mat_id = objectp->getRenderMaterialID(te_index); - if (mat_id.notNull()) - { - mMaterialId = mat_id; - } - else - { - mHasNonPbrFaces = true; - } - return true; - } - bool mCanCopy; - bool mCanModify; - bool mCanTransfer; - bool mHasNonPbrFaces; - LLUUID mMaterialId; -}; - -bool enable_object_edit_gltf_material() -{ - if (!LLMaterialEditor::capabilitiesAvailable()) - { - return false; - } - - LLSelectedTEGetmatIdAndPermissions func; - LLSelectMgr::getInstance()->getSelection()->applyToTEs(&func); - return func.mCanModify && !func.mHasNonPbrFaces; -} - -bool enable_object_open() -{ - // Look for contents in root object, which is all the LLFloaterOpenObject - // understands. - LLViewerObject* obj = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); - if (!obj) return false; - - LLViewerObject* root = obj->getRootEdit(); - if (!root) return false; - - return root->allowOpen(); -} - - -class LLViewJoystickFlycam : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_toggle_flycam(); - return true; - } -}; - -class LLViewCheckJoystickFlycam : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = LLViewerJoystick::getInstance()->getOverrideCamera(); - return new_value; - } -}; - -void handle_toggle_flycam() -{ - LLViewerJoystick::getInstance()->toggleFlycam(); -} - -class LLObjectBuild : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - if (gAgentCamera.getFocusOnAvatar() && !LLToolMgr::getInstance()->inEdit() && gSavedSettings.getBOOL("EditCameraMovement") ) - { - // zoom in if we're looking at the avatar - gAgentCamera.setFocusOnAvatar(false, ANIMATE); - gAgentCamera.setFocusGlobal(LLToolPie::getInstance()->getPick()); - gAgentCamera.cameraZoomIn(0.666f); - gAgentCamera.cameraOrbitOver( 30.f * DEG_TO_RAD ); - gViewerWindow->moveCursorToCenter(); - } - else if ( gSavedSettings.getBOOL("EditCameraMovement") ) - { - gAgentCamera.setFocusGlobal(LLToolPie::getInstance()->getPick()); - gViewerWindow->moveCursorToCenter(); - } - - LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); - LLToolMgr::getInstance()->getCurrentToolset()->selectTool( LLToolCompCreate::getInstance() ); - - // Could be first use - //LLFirstUse::useBuild(); - return true; - } -}; - -void update_camera() -{ - LLViewerParcelMgr::getInstance()->deselectLand(); - - if (gAgentCamera.getFocusOnAvatar() && !LLToolMgr::getInstance()->inEdit()) - { - LLFloaterTools::sPreviousFocusOnAvatar = true; - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - - if (selection->getSelectType() == SELECT_TYPE_HUD || !gSavedSettings.getBOOL("EditCameraMovement")) - { - // always freeze camera in space, even if camera doesn't move - // so, for example, follow cam scripts can't affect you when in build mode - gAgentCamera.setFocusGlobal(gAgentCamera.calcFocusPositionTargetGlobal(), LLUUID::null); - gAgentCamera.setFocusOnAvatar(false, ANIMATE); - } - else - { - gAgentCamera.setFocusOnAvatar(false, ANIMATE); - LLViewerObject* selected_objectp = selection->getFirstRootObject(); - if (selected_objectp) - { - // zoom in on object center instead of where we clicked, as we need to see the manipulator handles - gAgentCamera.setFocusGlobal(selected_objectp->getPositionGlobal(), selected_objectp->getID()); - gAgentCamera.cameraZoomIn(0.666f); - gAgentCamera.cameraOrbitOver(30.f * DEG_TO_RAD); - gViewerWindow->moveCursorToCenter(); - } - } - } -} - -void handle_object_edit() -{ - update_camera(); - - LLFloaterReg::showInstance("build"); - - LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); - gFloaterTools->setEditTool( LLToolCompTranslate::getInstance() ); - - LLViewerJoystick::getInstance()->moveObjects(true); - LLViewerJoystick::getInstance()->setNeedsReset(true); - - // Could be first use - //LLFirstUse::useBuild(); - return; -} - -void handle_object_edit_gltf_material() -{ - if (!LLFloaterReg::instanceVisible("build")) - { - handle_object_edit(); // does update_camera(); - } - else - { - update_camera(); - - LLViewerJoystick::getInstance()->moveObjects(true); - LLViewerJoystick::getInstance()->setNeedsReset(true); - } - - LLMaterialEditor::loadLive(); -} - -void handle_attachment_edit(const LLUUID& inv_item_id) -{ - if (isAgentAvatarValid()) - { - if (LLViewerObject* attached_obj = gAgentAvatarp->getWornAttachment(inv_item_id)) - { - LLSelectMgr::getInstance()->deselectAll(); - LLSelectMgr::getInstance()->selectObjectAndFamily(attached_obj); - - handle_object_edit(); - } - } -} - -void handle_attachment_touch(const LLUUID& inv_item_id) -{ - if ( (isAgentAvatarValid()) && (enable_attachment_touch(inv_item_id)) ) - { - if (LLViewerObject* attach_obj = gAgentAvatarp->getWornAttachment(gInventory.getLinkedItemID(inv_item_id))) - { - LLSelectMgr::getInstance()->deselectAll(); - - LLObjectSelectionHandle sel = LLSelectMgr::getInstance()->selectObjectAndFamily(attach_obj); - if (!LLToolMgr::getInstance()->inBuildMode()) - { - struct SetTransient : public LLSelectedNodeFunctor - { - bool apply(LLSelectNode* node) - { - node->setTransient(true); - return true; - } - } f; - sel->applyToNodes(&f); - } - - handle_object_touch(); - } - } -} - -bool enable_attachment_touch(const LLUUID& inv_item_id) -{ - if (isAgentAvatarValid()) - { - const LLViewerObject* attach_obj = gAgentAvatarp->getWornAttachment(gInventory.getLinkedItemID(inv_item_id)); - return (attach_obj) && (attach_obj->flagHandleTouch()); - } - return false; -} - -void handle_object_inspect() -{ - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - LLViewerObject* selected_objectp = selection->getFirstRootObject(); - if (selected_objectp) - { - LLFloaterReg::showInstance("task_properties"); - } - - /* - // Old floater properties - LLFloaterReg::showInstance("inspect", LLSD()); - */ -} - -//--------------------------------------------------------------------------- -// Land pie menu -//--------------------------------------------------------------------------- -class LLLandBuild : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLViewerParcelMgr::getInstance()->deselectLand(); - - if (gAgentCamera.getFocusOnAvatar() && !LLToolMgr::getInstance()->inEdit() && gSavedSettings.getBOOL("EditCameraMovement") ) - { - // zoom in if we're looking at the avatar - gAgentCamera.setFocusOnAvatar(false, ANIMATE); - gAgentCamera.setFocusGlobal(LLToolPie::getInstance()->getPick()); - gAgentCamera.cameraZoomIn(0.666f); - gAgentCamera.cameraOrbitOver( 30.f * DEG_TO_RAD ); - gViewerWindow->moveCursorToCenter(); - } - else if ( gSavedSettings.getBOOL("EditCameraMovement") ) - { - // otherwise just move focus - gAgentCamera.setFocusGlobal(LLToolPie::getInstance()->getPick()); - gViewerWindow->moveCursorToCenter(); - } - - - LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); - LLToolMgr::getInstance()->getCurrentToolset()->selectTool( LLToolCompCreate::getInstance() ); - - // Could be first use - //LLFirstUse::useBuild(); - return true; - } -}; - -class LLLandBuyPass : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLPanelLandGeneral::onClickBuyPass((void *)false); - return true; - } -}; - -class LLLandEnableBuyPass : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = LLPanelLandGeneral::enableBuyPass(NULL); - return new_value; - } -}; - -// BUG: Should really check if CLICK POINT is in a parcel where you can build. -bool enable_land_build(void*) -{ - if (gAgent.isGodlike()) return true; - if (gAgent.inPrelude()) return false; - - bool can_build = false; - LLParcel* agent_parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - if (agent_parcel) - { - can_build = agent_parcel->getAllowModify(); - } - return can_build; -} - -// BUG: Should really check if OBJECT is in a parcel where you can build. -bool enable_object_build(void*) -{ - if (gAgent.isGodlike()) return true; - if (gAgent.inPrelude()) return false; - - bool can_build = false; - LLParcel* agent_parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - if (agent_parcel) - { - can_build = agent_parcel->getAllowModify(); - } - return can_build; -} - -bool enable_object_edit() -{ - if (!isAgentAvatarValid()) return false; - - // *HACK: The new "prelude" Help Islands have a build sandbox area, - // so users need the Edit and Create pie menu options when they are - // there. Eventually this needs to be replaced with code that only - // lets you edit objects if you have permission to do so (edit perms, - // group edit, god). See also lltoolbar.cpp. JC - bool enable = false; - if (gAgent.inPrelude()) - { - enable = LLViewerParcelMgr::getInstance()->allowAgentBuild() - || LLSelectMgr::getInstance()->getSelection()->isAttachment(); - } - else if (LLSelectMgr::getInstance()->selectGetAllValidAndObjectsFound()) - { - enable = true; - } - - return enable; -} - -bool enable_mute_particle() -{ - const LLPickInfo& pick = LLToolPie::getInstance()->getPick(); - - return pick.mParticleOwnerID != LLUUID::null && pick.mParticleOwnerID != gAgent.getID(); -} - -// mutually exclusive - show either edit option or build in menu -bool enable_object_build() -{ - return !enable_object_edit(); -} - -bool enable_object_select_in_pathfinding_linksets() -{ - return LLPathfindingManager::getInstance()->isPathfindingEnabledForCurrentRegion() && LLSelectMgr::getInstance()->selectGetEditableLinksets(); -} - -bool visible_object_select_in_pathfinding_linksets() -{ - return LLPathfindingManager::getInstance()->isPathfindingEnabledForCurrentRegion(); -} - -bool enable_object_select_in_pathfinding_characters() -{ - return LLPathfindingManager::getInstance()->isPathfindingEnabledForCurrentRegion() && LLSelectMgr::getInstance()->selectGetViewableCharacters(); -} - -bool enable_os_exception() -{ -#if LL_DARWIN - return true; -#else - return false; -#endif -} - -class LLSelfRemoveAllAttachments : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLAppearanceMgr::instance().removeAllAttachmentsFromAvatar(); - return true; - } -}; - -class LLSelfEnableRemoveAllAttachments : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = false; - if (isAgentAvatarValid()) - { - for (LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); - iter != gAgentAvatarp->mAttachmentPoints.end(); ) - { - LLVOAvatar::attachment_map_t::iterator curiter = iter++; - LLViewerJointAttachment* attachment = curiter->second; - if (attachment->getNumObjects() > 0) - { - new_value = true; - break; - } - } - } - return new_value; - } -}; - -bool enable_has_attachments(void*) -{ - - return false; -} - -//--------------------------------------------------------------------------- -// Avatar pie menu -//--------------------------------------------------------------------------- -//void handle_follow(void *userdata) -//{ -// // follow a given avatar by ID -// LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); -// if (objectp) -// { -// gAgent.startFollowPilot(objectp->getID()); -// } -//} - -bool enable_object_mute() -{ - LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); - if (!object) return false; - - LLVOAvatar* avatar = find_avatar_from_object(object); - if (avatar) - { - // It's an avatar - LLNameValue *lastname = avatar->getNVPair("LastName"); - bool is_linden = - lastname && !LLStringUtil::compareStrings(lastname->getString(), "Linden"); - bool is_self = avatar->isSelf(); - return !is_linden && !is_self; - } - else - { - // Just a regular object - return LLSelectMgr::getInstance()->getSelection()->contains( object, SELECT_ALL_TES ) && - !LLMuteList::getInstance()->isMuted(object->getID()); - } -} - -bool enable_object_unmute() -{ - LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); - if (!object) return false; - - LLVOAvatar* avatar = find_avatar_from_object(object); - if (avatar) - { - // It's an avatar - LLNameValue *lastname = avatar->getNVPair("LastName"); - bool is_linden = - lastname && !LLStringUtil::compareStrings(lastname->getString(), "Linden"); - bool is_self = avatar->isSelf(); - return !is_linden && !is_self; - } - else - { - // Just a regular object - return LLSelectMgr::getInstance()->getSelection()->contains( object, SELECT_ALL_TES ) && - LLMuteList::getInstance()->isMuted(object->getID());; - } -} - - -// 0 = normal, 1 = always, 2 = never -class LLAvatarCheckImpostorMode : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); - if (!object) return false; - - LLVOAvatar* avatar = find_avatar_from_object(object); - if (!avatar) return false; - - U32 mode = userdata.asInteger(); - switch (mode) - { - case 0: - return (avatar->getVisualMuteSettings() == LLVOAvatar::AV_RENDER_NORMALLY); - case 1: - return (avatar->getVisualMuteSettings() == LLVOAvatar::AV_DO_NOT_RENDER); - case 2: - return (avatar->getVisualMuteSettings() == LLVOAvatar::AV_ALWAYS_RENDER); - case 4: - return (avatar->getVisualMuteSettings() != LLVOAvatar::AV_RENDER_NORMALLY); - default: - return false; - } - } // handleEvent() -}; - -// 0 = normal, 1 = always, 2 = never -class LLAvatarSetImpostorMode : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); - if (!object) return false; - - LLVOAvatar* avatar = find_avatar_from_object(object); - if (!avatar) return false; - - U32 mode = userdata.asInteger(); - switch (mode) - { - case 0: - avatar->setVisualMuteSettings(LLVOAvatar::AV_RENDER_NORMALLY); - break; - case 1: - avatar->setVisualMuteSettings(LLVOAvatar::AV_DO_NOT_RENDER); - break; - case 2: - avatar->setVisualMuteSettings(LLVOAvatar::AV_ALWAYS_RENDER); - break; - default: - return false; - } - - LLVOAvatar::cullAvatarsByPixelArea(); - return true; - } // handleEvent() -}; - - -class LLObjectMute : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); - if (!object) return true; - - LLUUID id; - std::string name; - LLMute::EType type; - LLVOAvatar* avatar = find_avatar_from_object(object); - if (avatar) - { - avatar->mNeedsImpostorUpdate = true; - avatar->mLastImpostorUpdateReason = 9; - - id = avatar->getID(); - - LLNameValue *firstname = avatar->getNVPair("FirstName"); - LLNameValue *lastname = avatar->getNVPair("LastName"); - if (firstname && lastname) - { - name = LLCacheName::buildFullName( - firstname->getString(), lastname->getString()); - } - - type = LLMute::AGENT; - } - else - { - // it's an object - id = object->getID(); - - LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(); - if (node) - { - name = node->mName; - } - - type = LLMute::OBJECT; - } - - LLMute mute(id, name, type); - if (LLMuteList::getInstance()->isMuted(mute.mID)) - { - LLMuteList::getInstance()->remove(mute); - } - else - { - LLMuteList::getInstance()->add(mute); - LLPanelBlockedList::showPanelAndSelect(mute.mID); - } - - return true; - } -}; - -bool handle_go_to() -{ - // try simulator autopilot - std::vector strings; - std::string val; - LLVector3d pos = LLToolPie::getInstance()->getPick().mPosGlobal; - val = llformat("%g", pos.mdV[VX]); - strings.push_back(val); - val = llformat("%g", pos.mdV[VY]); - strings.push_back(val); - val = llformat("%g", pos.mdV[VZ]); - strings.push_back(val); - send_generic_message("autopilot", strings); - - LLViewerParcelMgr::getInstance()->deselectLand(); - - if (isAgentAvatarValid() && !gSavedSettings.getBOOL("AutoPilotLocksCamera")) - { - gAgentCamera.setFocusGlobal(gAgentCamera.getFocusTargetGlobal(), gAgentAvatarp->getID()); - } - else - { - // Snap camera back to behind avatar - gAgentCamera.setFocusOnAvatar(true, ANIMATE); - } - - // Could be first use - //LLFirstUse::useGoTo(); - return true; -} - -class LLGoToObject : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - return handle_go_to(); - } -}; - -class LLAvatarReportAbuse : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLVOAvatar* avatar = find_avatar_from_object( LLSelectMgr::getInstance()->getSelection()->getPrimaryObject() ); - if(avatar) - { - LLFloaterReporter::showFromObject(avatar->getID()); - } - return true; - } -}; - - -//--------------------------------------------------------------------------- -// Parcel freeze, eject, etc. -//--------------------------------------------------------------------------- -bool callback_freeze(const LLSD& notification, const LLSD& response) -{ - LLUUID avatar_id = notification["payload"]["avatar_id"].asUUID(); - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - - if (0 == option || 1 == option) - { - U32 flags = 0x0; - if (1 == option) - { - // unfreeze - flags |= 0x1; - } - - LLMessageSystem* msg = gMessageSystem; - LLViewerObject* avatar = gObjectList.findObject(avatar_id); - - if (avatar) - { - msg->newMessage("FreezeUser"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->nextBlock("Data"); - msg->addUUID("TargetID", avatar_id ); - msg->addU32("Flags", flags ); - msg->sendReliable( avatar->getRegion()->getHost() ); - } - } - return false; -} - - -void handle_avatar_freeze(const LLSD& avatar_id) -{ - // Use avatar_id if available, otherwise default to right-click avatar - LLVOAvatar* avatar = NULL; - if (avatar_id.asUUID().notNull()) - { - avatar = find_avatar_from_object(avatar_id.asUUID()); - } - else - { - avatar = find_avatar_from_object( - LLSelectMgr::getInstance()->getSelection()->getPrimaryObject()); - } - - if( avatar ) - { - std::string fullname = avatar->getFullname(); - LLSD payload; - payload["avatar_id"] = avatar->getID(); - - if (!fullname.empty()) - { - LLSD args; - args["AVATAR_NAME"] = fullname; - LLNotificationsUtil::add("FreezeAvatarFullname", - args, - payload, - callback_freeze); - } - else - { - LLNotificationsUtil::add("FreezeAvatar", - LLSD(), - payload, - callback_freeze); - } - } -} - -class LLAvatarVisibleDebug : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - return gAgent.isGodlike(); - } -}; - -class LLAvatarDebug : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLVOAvatar* avatar = find_avatar_from_object( LLSelectMgr::getInstance()->getSelection()->getPrimaryObject() ); - if( avatar ) - { - if (avatar->isSelf()) - { - ((LLVOAvatarSelf *)avatar)->dumpLocalTextures(); - } - LL_INFOS() << "Dumping temporary asset data to simulator logs for avatar " << avatar->getID() << LL_ENDL; - std::vector strings; - strings.push_back(avatar->getID().asString()); - LLUUID invoice; - send_generic_message("dumptempassetdata", strings, invoice); - LLFloaterReg::showInstance( "avatar_textures", LLSD(avatar->getID()) ); - } - return true; - } -}; - -bool callback_eject(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (2 == option) - { - // Cancel button. - return false; - } - LLUUID avatar_id = notification["payload"]["avatar_id"].asUUID(); - bool ban_enabled = notification["payload"]["ban_enabled"].asBoolean(); - - if (0 == option) - { - // Eject button - LLMessageSystem* msg = gMessageSystem; - LLViewerObject* avatar = gObjectList.findObject(avatar_id); - - if (avatar) - { - U32 flags = 0x0; - msg->newMessage("EjectUser"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID() ); - msg->addUUID("SessionID", gAgent.getSessionID() ); - msg->nextBlock("Data"); - msg->addUUID("TargetID", avatar_id ); - msg->addU32("Flags", flags ); - msg->sendReliable( avatar->getRegion()->getHost() ); - } - } - else if (ban_enabled) - { - // This is tricky. It is similar to say if it is not an 'Eject' button, - // and it is also not an 'Cancle' button, and ban_enabled==ture, - // it should be the 'Eject and Ban' button. - LLMessageSystem* msg = gMessageSystem; - LLViewerObject* avatar = gObjectList.findObject(avatar_id); - - if (avatar) - { - U32 flags = 0x1; - msg->newMessage("EjectUser"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID() ); - msg->addUUID("SessionID", gAgent.getSessionID() ); - msg->nextBlock("Data"); - msg->addUUID("TargetID", avatar_id ); - msg->addU32("Flags", flags ); - msg->sendReliable( avatar->getRegion()->getHost() ); - } - } - return false; -} - -void handle_avatar_eject(const LLSD& avatar_id) -{ - // Use avatar_id if available, otherwise default to right-click avatar - LLVOAvatar* avatar = NULL; - if (avatar_id.asUUID().notNull()) - { - avatar = find_avatar_from_object(avatar_id.asUUID()); - } - else - { - avatar = find_avatar_from_object( - LLSelectMgr::getInstance()->getSelection()->getPrimaryObject()); - } - - if( avatar ) - { - LLSD payload; - payload["avatar_id"] = avatar->getID(); - std::string fullname = avatar->getFullname(); - - const LLVector3d& pos = avatar->getPositionGlobal(); - LLParcel* parcel = LLViewerParcelMgr::getInstance()->selectParcelAt(pos)->getParcel(); - - if (LLViewerParcelMgr::getInstance()->isParcelOwnedByAgent(parcel,GP_LAND_MANAGE_BANNED)) - { - payload["ban_enabled"] = true; - if (!fullname.empty()) - { - LLSD args; - args["AVATAR_NAME"] = fullname; - LLNotificationsUtil::add("EjectAvatarFullname", - args, - payload, - callback_eject); - } - else - { - LLNotificationsUtil::add("EjectAvatarFullname", - LLSD(), - payload, - callback_eject); - } - } - else - { - payload["ban_enabled"] = false; - if (!fullname.empty()) - { - LLSD args; - args["AVATAR_NAME"] = fullname; - LLNotificationsUtil::add("EjectAvatarFullnameNoBan", - args, - payload, - callback_eject); - } - else - { - LLNotificationsUtil::add("EjectAvatarNoBan", - LLSD(), - payload, - callback_eject); - } - } - } -} - -bool my_profile_visible() -{ - LLFloater* floaterp = LLAvatarActions::getProfileFloater(gAgentID); - return floaterp && floaterp->isInVisibleChain(); -} - -bool picks_tab_visible() -{ - return my_profile_visible() && LLAvatarActions::isPickTabSelected(gAgentID); -} - -bool enable_freeze_eject(const LLSD& avatar_id) -{ - // Use avatar_id if available, otherwise default to right-click avatar - LLVOAvatar* avatar = NULL; - if (avatar_id.asUUID().notNull()) - { - avatar = find_avatar_from_object(avatar_id.asUUID()); - } - else - { - avatar = find_avatar_from_object( - LLSelectMgr::getInstance()->getSelection()->getPrimaryObject()); - } - if (!avatar) return false; - - // Gods can always freeze - if (gAgent.isGodlike()) return true; - - // Estate owners / managers can freeze - // Parcel owners can also freeze - const LLVector3& pos = avatar->getPositionRegion(); - const LLVector3d& pos_global = avatar->getPositionGlobal(); - LLParcel* parcel = LLViewerParcelMgr::getInstance()->selectParcelAt(pos_global)->getParcel(); - LLViewerRegion* region = avatar->getRegion(); - if (!region) return false; - - bool new_value = region->isOwnedSelf(pos); - if (!new_value || region->isOwnedGroup(pos)) - { - new_value = LLViewerParcelMgr::getInstance()->isParcelOwnedByAgent(parcel,GP_LAND_ADMIN); - } - return new_value; -} - -bool callback_leave_group(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) - { - LLMessageSystem *msg = gMessageSystem; - - msg->newMessageFast(_PREHASH_LeaveGroupRequest); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_GroupData); - msg->addUUIDFast(_PREHASH_GroupID, gAgent.getGroupID() ); - gAgent.sendReliableMessage(); - } - return false; -} - -void append_aggregate(std::string& string, const LLAggregatePermissions& ag_perm, PermissionBit bit, const char* txt) -{ - LLAggregatePermissions::EValue val = ag_perm.getValue(bit); - std::string buffer; - switch(val) - { - case LLAggregatePermissions::AP_NONE: - buffer = llformat( "* %s None\n", txt); - break; - case LLAggregatePermissions::AP_SOME: - buffer = llformat( "* %s Some\n", txt); - break; - case LLAggregatePermissions::AP_ALL: - buffer = llformat( "* %s All\n", txt); - break; - case LLAggregatePermissions::AP_EMPTY: - default: - break; - } - string.append(buffer); -} - -bool enable_buy_object() -{ - // In order to buy, there must only be 1 purchaseable object in - // the selection manager. - if(LLSelectMgr::getInstance()->getSelection()->getRootObjectCount() != 1) return false; - LLViewerObject* obj = NULL; - LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(); - if(node) - { - obj = node->getObject(); - if(!obj) return false; - - if( for_sale_selection(node) ) - { - // *NOTE: Is this needed? This checks to see if anyone owns the - // object, dating back to when we had "public" objects owned by - // no one. JC - if(obj->permAnyOwner()) return true; - } - } - return false; -} - -// Note: This will only work if the selected object's data has been -// received by the viewer and cached in the selection manager. -void handle_buy_object(LLSaleInfo sale_info) -{ - if(!LLSelectMgr::getInstance()->selectGetAllRootsValid()) - { - LLNotificationsUtil::add("UnableToBuyWhileDownloading"); - return; - } - - LLUUID owner_id; - std::string owner_name; - bool owners_identical = LLSelectMgr::getInstance()->selectGetOwner(owner_id, owner_name); - if (!owners_identical) - { - LLNotificationsUtil::add("CannotBuyObjectsFromDifferentOwners"); - return; - } - - LLPermissions perm; - bool valid = LLSelectMgr::getInstance()->selectGetPermissions(perm); - LLAggregatePermissions ag_perm; - valid &= LLSelectMgr::getInstance()->selectGetAggregatePermissions(ag_perm); - if(!valid || !sale_info.isForSale() || !perm.allowTransferTo(gAgent.getID())) - { - LLNotificationsUtil::add("ObjectNotForSale"); - return; - } - - LLFloaterBuy::show(sale_info); -} - - -void handle_buy_contents(LLSaleInfo sale_info) -{ - LLFloaterBuyContents::show(sale_info); -} - -void handle_region_dump_temp_asset_data(void*) -{ - LL_INFOS() << "Dumping temporary asset data to simulator logs" << LL_ENDL; - std::vector strings; - LLUUID invoice; - send_generic_message("dumptempassetdata", strings, invoice); -} - -void handle_region_clear_temp_asset_data(void*) -{ - LL_INFOS() << "Clearing temporary asset data" << LL_ENDL; - std::vector strings; - LLUUID invoice; - send_generic_message("cleartempassetdata", strings, invoice); -} - -void handle_region_dump_settings(void*) -{ - LLViewerRegion* regionp = gAgent.getRegion(); - if (regionp) - { - LL_INFOS() << "Damage: " << (regionp->getAllowDamage() ? "on" : "off") << LL_ENDL; - LL_INFOS() << "Landmark: " << (regionp->getAllowLandmark() ? "on" : "off") << LL_ENDL; - LL_INFOS() << "SetHome: " << (regionp->getAllowSetHome() ? "on" : "off") << LL_ENDL; - LL_INFOS() << "ResetHome: " << (regionp->getResetHomeOnTeleport() ? "on" : "off") << LL_ENDL; - LL_INFOS() << "SunFixed: " << (regionp->getSunFixed() ? "on" : "off") << LL_ENDL; - LL_INFOS() << "BlockFly: " << (regionp->getBlockFly() ? "on" : "off") << LL_ENDL; - LL_INFOS() << "AllowP2P: " << (regionp->getAllowDirectTeleport() ? "on" : "off") << LL_ENDL; - LL_INFOS() << "Water: " << (regionp->getWaterHeight()) << LL_ENDL; - } -} - -void handle_dump_group_info(void *) -{ - gAgent.dumpGroupInfo(); -} - -void handle_dump_capabilities_info(void *) -{ - LLViewerRegion* regionp = gAgent.getRegion(); - if (regionp) - { - regionp->logActiveCapabilities(); - } -} - -void handle_dump_region_object_cache(void*) -{ - LLViewerRegion* regionp = gAgent.getRegion(); - if (regionp) - { - regionp->dumpCache(); - } -} - -void handle_reset_interest_lists(void *) -{ - // Check all regions and reset their interest list - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); - ++iter) - { - LLViewerRegion *regionp = *iter; - if (regionp && regionp->isAlive() && regionp->capabilitiesReceived()) - { - regionp->resetInterestList(); - } - } -} - - -void handle_dump_focus() -{ - LLUICtrl *ctrl = dynamic_cast(gFocusMgr.getKeyboardFocus()); - - LL_INFOS() << "Keyboard focus " << (ctrl ? ctrl->getName() : "(none)") << LL_ENDL; -} - -class LLSelfStandUp : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - gAgent.standUp(); - return true; - } -}; - -bool enable_standup_self() -{ - return isAgentAvatarValid() && gAgentAvatarp->isSitting(); -} - -class LLSelfSitDown : public view_listener_t - { - bool handleEvent(const LLSD& userdata) - { - gAgent.sitDown(); - return true; - } - }; - - - -bool show_sitdown_self() -{ - return isAgentAvatarValid() && !gAgentAvatarp->isSitting(); -} - -bool enable_sitdown_self() -{ - return show_sitdown_self() && !gAgentAvatarp->isEditingAppearance() && !gAgent.getFlying(); -} - -class LLSelfToggleSitStand : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - if (isAgentAvatarValid()) - { - if (gAgentAvatarp->isSitting()) - { - gAgent.standUp(); - } - else - { - gAgent.sitDown(); - } - } - return true; - } -}; - -bool enable_sit_stand() -{ - return enable_sitdown_self() || enable_standup_self(); -} - -bool enable_fly_land() -{ - return gAgent.getFlying() || LLAgent::enableFlying(); -} - -class LLCheckPanelPeopleTab : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string panel_name = userdata.asString(); - - LLPanel *panel = LLFloaterSidePanelContainer::getPanel("people", panel_name); - if(panel && panel->isInVisibleChain()) - { - return true; - } - return false; - } -}; -// Toggle one of "People" panel tabs in side tray. -class LLTogglePanelPeopleTab : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string panel_name = userdata.asString(); - - LLSD param; - param["people_panel_tab_name"] = panel_name; - - if ( panel_name == "friends_panel" - || panel_name == "groups_panel" - || panel_name == "nearby_panel" - || panel_name == "blocked_panel") - { - return togglePeoplePanel(panel_name, param); - } - else - { - return false; - } - } - - static bool togglePeoplePanel(const std::string& panel_name, const LLSD& param) - { - LLPanel *panel = LLFloaterSidePanelContainer::getPanel("people", panel_name); - if(!panel) - return false; - - if (panel->isInVisibleChain()) - { - LLFloaterReg::hideInstance("people"); - } - else - { - LLFloaterSidePanelContainer::showPanel("people", "panel_people", param) ; - } - - return true; - } -}; - -bool check_admin_override(void*) -{ - return gAgent.getAdminOverride(); -} - -void handle_admin_override_toggle(void*) -{ - gAgent.setAdminOverride(!gAgent.getAdminOverride()); - - // The above may have affected which debug menus are visible - show_debug_menus(); -} - -void handle_visual_leak_detector_toggle(void*) -{ - static bool vld_enabled = false; - - if ( vld_enabled ) - { -#ifdef INCLUDE_VLD - // only works for debug builds (hard coded into vld.h) -#ifdef _DEBUG - // start with Visual Leak Detector turned off - VLDDisable(); -#endif // _DEBUG -#endif // INCLUDE_VLD - vld_enabled = false; - } - else - { -#ifdef INCLUDE_VLD - // only works for debug builds (hard coded into vld.h) - #ifdef _DEBUG - // start with Visual Leak Detector turned off - VLDEnable(); - #endif // _DEBUG -#endif // INCLUDE_VLD - - vld_enabled = true; - }; -} - -void handle_god_mode(void*) -{ - gAgent.requestEnterGodMode(); -} - -void handle_leave_god_mode(void*) -{ - gAgent.requestLeaveGodMode(); -} - -void set_god_level(U8 god_level) -{ - U8 old_god_level = gAgent.getGodLevel(); - gAgent.setGodLevel( god_level ); - LLViewerParcelMgr::getInstance()->notifyObservers(); - - // God mode changes region visibility - LLWorldMap::getInstance()->reloadItems(true); - - // inventory in items may change in god mode - gObjectList.dirtyAllObjectInventory(); - - if(gViewerWindow) - { - gViewerWindow->setMenuBackgroundColor(god_level > GOD_NOT, - LLGridManager::getInstance()->isInProductionGrid()); - } - - LLSD args; - if(god_level > GOD_NOT) - { - args["LEVEL"] = llformat("%d",(S32)god_level); - LLNotificationsUtil::add("EnteringGodMode", args); - } - else - { - args["LEVEL"] = llformat("%d",(S32)old_god_level); - LLNotificationsUtil::add("LeavingGodMode", args); - } - - // changing god-level can affect which menus we see - show_debug_menus(); - - // changing god-level can invalidate search results - LLFloaterSearch *search = dynamic_cast(LLFloaterReg::getInstance("search")); - if (search) - { - search->godLevelChanged(god_level); - } -} - -#ifdef TOGGLE_HACKED_GODLIKE_VIEWER -void handle_toggle_hacked_godmode(void*) -{ - gHackGodmode = !gHackGodmode; - set_god_level(gHackGodmode ? GOD_MAINTENANCE : GOD_NOT); -} - -bool check_toggle_hacked_godmode(void*) -{ - return gHackGodmode; -} - -bool enable_toggle_hacked_godmode(void*) -{ - return !LLGridManager::getInstance()->isInProductionGrid(); -} -#endif - -void process_grant_godlike_powers(LLMessageSystem* msg, void**) -{ - LLUUID agent_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); - LLUUID session_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_SessionID, session_id); - if((agent_id == gAgent.getID()) && (session_id == gAgent.getSessionID())) - { - U8 god_level; - msg->getU8Fast(_PREHASH_GrantData, _PREHASH_GodLevel, god_level); - set_god_level(god_level); - } - else - { - LL_WARNS() << "Grant godlike for wrong agent " << agent_id << LL_ENDL; - } -} - -/* -class LLHaveCallingcard : public LLInventoryCollectFunctor -{ -public: - LLHaveCallingcard(const LLUUID& agent_id); - virtual ~LLHaveCallingcard() {} - virtual bool operator()(LLInventoryCategory* cat, - LLInventoryItem* item); - bool isThere() const { return mIsThere;} -protected: - LLUUID mID; - bool mIsThere; -}; - -LLHaveCallingcard::LLHaveCallingcard(const LLUUID& agent_id) : - mID(agent_id), - mIsThere(false) -{ -} - -bool LLHaveCallingcard::operator()(LLInventoryCategory* cat, - LLInventoryItem* item) -{ - if(item) - { - if((item->getType() == LLAssetType::AT_CALLINGCARD) - && (item->getCreatorUUID() == mID)) - { - mIsThere = true; - } - } - return false; -} -*/ - -bool is_agent_mappable(const LLUUID& agent_id) -{ - const LLRelationship* buddy_info = NULL; - bool is_friend = LLAvatarActions::isFriend(agent_id); - - if (is_friend) - buddy_info = LLAvatarTracker::instance().getBuddyInfo(agent_id); - - return (buddy_info && - buddy_info->isOnline() && - buddy_info->isRightGrantedFrom(LLRelationship::GRANT_MAP_LOCATION) - ); -} - - -// Enable a menu item when you don't have someone's card. -class LLAvatarEnableAddFriend : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLVOAvatar* avatar = find_avatar_from_object(LLSelectMgr::getInstance()->getSelection()->getPrimaryObject()); - bool new_value = avatar && !LLAvatarActions::isFriend(avatar->getID()); - return new_value; - } -}; - -void request_friendship(const LLUUID& dest_id) -{ - LLViewerObject* dest = gObjectList.findObject(dest_id); - if(dest && dest->isAvatar()) - { - std::string full_name; - LLNameValue* nvfirst = dest->getNVPair("FirstName"); - LLNameValue* nvlast = dest->getNVPair("LastName"); - if(nvfirst && nvlast) - { - full_name = LLCacheName::buildFullName( - nvfirst->getString(), nvlast->getString()); - } - if (!full_name.empty()) - { - LLAvatarActions::requestFriendshipDialog(dest_id, full_name); - } - else - { - LLNotificationsUtil::add("CantOfferFriendship"); - } - } -} - - -class LLEditEnableCustomizeAvatar : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = gAgentWearables.areWearablesLoaded(); - return new_value; - } -}; - -class LLEnableEditShape : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - return gAgentWearables.isWearableModifiable(LLWearableType::WT_SHAPE, 0); - } -}; - -class LLEnableHoverHeight : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - return gAgent.getRegion() && gAgent.getRegion()->avatarHoverHeightEnabled(); - } -}; - -class LLEnableEditPhysics : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - //return gAgentWearables.isWearableModifiable(LLWearableType::WT_SHAPE, 0); - return true; - } -}; - -bool is_object_sittable() -{ - LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); - - if (object && object->getPCode() == LL_PCODE_VOLUME) - { - return true; - } - else - { - return false; - } -} - -// only works on pie menu -void handle_object_sit(LLViewerObject *object, const LLVector3 &offset) -{ - // get object selection offset - - if (object && object->getPCode() == LL_PCODE_VOLUME) - { - - gMessageSystem->newMessageFast(_PREHASH_AgentRequestSit); - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - gMessageSystem->nextBlockFast(_PREHASH_TargetObject); - gMessageSystem->addUUIDFast(_PREHASH_TargetID, object->mID); - gMessageSystem->addVector3Fast(_PREHASH_Offset, offset); - - object->getRegion()->sendReliableMessage(); - } -} - -void handle_object_sit_or_stand() -{ - LLPickInfo pick = LLToolPie::getInstance()->getPick(); - LLViewerObject *object = pick.getObject(); - if (!object || pick.mPickType == LLPickInfo::PICK_FLORA) - { - return; - } - - if (sitting_on_selection()) - { - gAgent.standUp(); - return; - } - - handle_object_sit(object, pick.mObjectOffset); -} - -void handle_object_sit(const LLUUID& object_id) -{ - LLViewerObject* obj = gObjectList.findObject(object_id); - if (!obj) - { - return; - } - - LLVector3 offset(0, 0, 0); - handle_object_sit(obj, offset); -} - -void near_sit_down_point(bool success, void *) -{ - if (success) - { - gAgent.setFlying(false); - gAgent.clearControlFlags(AGENT_CONTROL_STAND_UP); // might have been set by autopilot - gAgent.setControlFlags(AGENT_CONTROL_SIT_ON_GROUND); - } -} - -class LLLandSit : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - if (gAgent.isSitting()) - { - gAgent.standUp(); - } - LLVector3d posGlobal = LLToolPie::getInstance()->getPick().mPosGlobal; - - LLQuaternion target_rot; - if (isAgentAvatarValid()) - { - target_rot = gAgentAvatarp->getRotation(); - } - else - { - target_rot = gAgent.getFrameAgent().getQuaternion(); - } - gAgent.startAutoPilotGlobal(posGlobal, "Sit", &target_rot, near_sit_down_point, NULL, 0.7f); - return true; - } -}; - -class LLLandCanSit : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLVector3d posGlobal = LLToolPie::getInstance()->getPick().mPosGlobal; - return !posGlobal.isExactlyZero(); // valid position, not beyond draw distance - } -}; - -//------------------------------------------------------------------- -// Help menu functions -//------------------------------------------------------------------- - -// -// Major mode switching -// -void reset_view_final( bool proceed ); - -void handle_reset_view() -{ - if (gAgentCamera.cameraCustomizeAvatar()) - { - // switching to outfit selector should automagically save any currently edited wearable - LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "my_outfits")); - } - gAgentCamera.setFocusOnAvatar(true, false, false); - reset_view_final( true ); - LLFloaterCamera::resetCameraMode(); -} - -class LLViewResetView : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - handle_reset_view(); - return true; - } -}; - -// Note: extra parameters allow this function to be called from dialog. -void reset_view_final( bool proceed ) -{ - if( !proceed ) - { - return; - } - - gAgentCamera.resetView(true, true); - gAgentCamera.setLookAt(LOOKAT_TARGET_CLEAR); -} - -class LLViewLookAtLastChatter : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - gAgentCamera.lookAtLastChat(); - return true; - } -}; - -class LLViewMouselook : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - if (!gAgentCamera.cameraMouselook()) - { - gAgentCamera.changeCameraToMouselook(); - } - else - { - gAgentCamera.changeCameraToDefault(); - } - return true; - } -}; - -class LLViewDefaultUISize : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - gSavedSettings.setF32("UIScaleFactor", 1.0f); - gSavedSettings.setBOOL("UIAutoScale", false); - gViewerWindow->reshape(gViewerWindow->getWindowWidthRaw(), gViewerWindow->getWindowHeightRaw()); - return true; - } -}; - -class LLViewToggleUI : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - if(gAgentCamera.getCameraMode() != CAMERA_MODE_MOUSELOOK) - { - LLNotification::Params params("ConfirmHideUI"); - params.functor.function(boost::bind(&LLViewToggleUI::confirm, this, _1, _2)); - LLSD substitutions; -#if LL_DARWIN - substitutions["SHORTCUT"] = "Cmd+Shift+U"; -#else - substitutions["SHORTCUT"] = "Ctrl+Shift+U"; -#endif - params.substitutions = substitutions; - if (!gSavedSettings.getBOOL("HideUIControls")) - { - // hiding, so show notification - LLNotifications::instance().add(params); - } - else - { - LLNotifications::instance().forceResponse(params, 0); - } - } - return true; - } - - void confirm(const LLSD& notification, const LLSD& response) - { - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - - if (option == 0) // OK - { - gViewerWindow->setUIVisibility(gSavedSettings.getBOOL("HideUIControls")); - LLPanelStandStopFlying::getInstance()->setVisible(gSavedSettings.getBOOL("HideUIControls")); - gSavedSettings.setBOOL("HideUIControls",!gSavedSettings.getBOOL("HideUIControls")); - } - } -}; - -void handle_duplicate_in_place(void*) -{ - LL_INFOS() << "handle_duplicate_in_place" << LL_ENDL; - - LLVector3 offset(0.f, 0.f, 0.f); - LLSelectMgr::getInstance()->selectDuplicate(offset, true); -} - - - -/* - * No longer able to support viewer side manipulations in this way - * -void god_force_inv_owner_permissive(LLViewerObject* object, - LLInventoryObject::object_list_t* inventory, - S32 serial_num, - void*) -{ - typedef std::vector > item_array_t; - item_array_t items; - - LLInventoryObject::object_list_t::const_iterator inv_it = inventory->begin(); - LLInventoryObject::object_list_t::const_iterator inv_end = inventory->end(); - for ( ; inv_it != inv_end; ++inv_it) - { - if(((*inv_it)->getType() != LLAssetType::AT_CATEGORY)) - { - LLInventoryObject* obj = *inv_it; - LLPointer new_item = new LLViewerInventoryItem((LLViewerInventoryItem*)obj); - LLPermissions perm(new_item->getPermissions()); - perm.setMaskBase(PERM_ALL); - perm.setMaskOwner(PERM_ALL); - new_item->setPermissions(perm); - items.push_back(new_item); - } - } - item_array_t::iterator end = items.end(); - item_array_t::iterator it; - for(it = items.begin(); it != end; ++it) - { - // since we have the inventory item in the callback, it should not - // invalidate iteration through the selection manager. - object->updateInventory((*it), TASK_INVENTORY_ITEM_KEY, false); - } -} -*/ - -void handle_object_owner_permissive(void*) -{ - // only send this if they're a god. - if(gAgent.isGodlike()) - { - // do the objects. - LLSelectMgr::getInstance()->selectionSetObjectPermissions(PERM_BASE, true, PERM_ALL, true); - LLSelectMgr::getInstance()->selectionSetObjectPermissions(PERM_OWNER, true, PERM_ALL, true); - } -} - -void handle_object_owner_self(void*) -{ - // only send this if they're a god. - if(gAgent.isGodlike()) - { - LLSelectMgr::getInstance()->sendOwner(gAgent.getID(), gAgent.getGroupID(), true); - } -} - -// Shortcut to set owner permissions to not editable. -void handle_object_lock(void*) -{ - LLSelectMgr::getInstance()->selectionSetObjectPermissions(PERM_OWNER, false, PERM_MODIFY); -} - -void handle_object_asset_ids(void*) -{ - // only send this if they're a god. - if (gAgent.isGodlike()) - { - LLSelectMgr::getInstance()->sendGodlikeRequest("objectinfo", "assetids"); - } -} - -void handle_force_parcel_owner_to_me(void*) -{ - LLViewerParcelMgr::getInstance()->sendParcelGodForceOwner( gAgent.getID() ); -} - -void handle_force_parcel_to_content(void*) -{ - LLViewerParcelMgr::getInstance()->sendParcelGodForceToContent(); -} - -void handle_claim_public_land(void*) -{ - if (LLViewerParcelMgr::getInstance()->getSelectionRegion() != gAgent.getRegion()) - { - LLNotificationsUtil::add("ClaimPublicLand"); - return; - } - - LLVector3d west_south_global; - LLVector3d east_north_global; - LLViewerParcelMgr::getInstance()->getSelection(west_south_global, east_north_global); - LLVector3 west_south = gAgent.getPosAgentFromGlobal(west_south_global); - LLVector3 east_north = gAgent.getPosAgentFromGlobal(east_north_global); - - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("GodlikeMessage"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->addUUIDFast(_PREHASH_TransactionID, LLUUID::null); //not used - msg->nextBlock("MethodData"); - msg->addString("Method", "claimpublicland"); - msg->addUUID("Invoice", LLUUID::null); - std::string buffer; - buffer = llformat( "%f", west_south.mV[VX]); - msg->nextBlock("ParamList"); - msg->addString("Parameter", buffer); - buffer = llformat( "%f", west_south.mV[VY]); - msg->nextBlock("ParamList"); - msg->addString("Parameter", buffer); - buffer = llformat( "%f", east_north.mV[VX]); - msg->nextBlock("ParamList"); - msg->addString("Parameter", buffer); - buffer = llformat( "%f", east_north.mV[VY]); - msg->nextBlock("ParamList"); - msg->addString("Parameter", buffer); - gAgent.sendReliableMessage(); -} - - - -// HACK for easily testing new avatar geometry -void handle_god_request_avatar_geometry(void *) -{ - if (gAgent.isGodlike()) - { - LLSelectMgr::getInstance()->sendGodlikeRequest("avatar toggle", ""); - } -} - -static bool get_derezzable_objects( - EDeRezDestination dest, - std::string& error, - LLViewerRegion*& first_region, - std::vector* derez_objectsp, - bool only_check = false) -{ - bool found = false; - - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - - if (derez_objectsp) - derez_objectsp->reserve(selection->getRootObjectCount()); - - // Check conditions that we can't deal with, building a list of - // everything that we'll actually be derezzing. - for (LLObjectSelection::valid_root_iterator iter = selection->valid_root_begin(); - iter != selection->valid_root_end(); iter++) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - LLViewerRegion* region = object->getRegion(); - if (!first_region) - { - first_region = region; - } - else - { - if(region != first_region) - { - // Derez doesn't work at all if the some of the objects - // are in regions besides the first object selected. - - // ...crosses region boundaries - error = "AcquireErrorObjectSpan"; - break; - } - } - if (object->isAvatar()) - { - // ...don't acquire avatars - continue; - } - - // If AssetContainers are being sent back, they will appear as - // boxes in the owner's inventory. - if (object->getNVPair("AssetContainer") - && dest != DRD_RETURN_TO_OWNER) - { - // this object is an asset container, derez its contents, not it - LL_WARNS() << "Attempt to derez deprecated AssetContainer object type not supported." << LL_ENDL; - /* - object->requestInventory(container_inventory_arrived, - (void *)(bool)(DRD_TAKE_INTO_AGENT_INVENTORY == dest)); - */ - continue; - } - bool can_derez_current = false; - switch(dest) - { - case DRD_TAKE_INTO_AGENT_INVENTORY: - case DRD_TRASH: - if (!object->isPermanentEnforced() && - ((node->mPermissions->allowTransferTo(gAgent.getID()) && object->permModify()) - || (node->allowOperationOnNode(PERM_OWNER, GP_OBJECT_MANIPULATE)))) - { - can_derez_current = true; - } - break; - - case DRD_RETURN_TO_OWNER: - if(!object->isAttachment()) - { - can_derez_current = true; - } - break; - - default: - if((node->mPermissions->allowTransferTo(gAgent.getID()) - && object->permCopy()) - || gAgent.isGodlike()) - { - can_derez_current = true; - } - break; - } - if(can_derez_current) - { - found = true; - - if (only_check) - // one found, no need to traverse to the end - break; - - if (derez_objectsp) - derez_objectsp->push_back(object); - - } - } - - return found; -} - -static bool can_derez(EDeRezDestination dest) -{ - LLViewerRegion* first_region = NULL; - std::string error; - return get_derezzable_objects(dest, error, first_region, NULL, true); -} - -static void derez_objects( - EDeRezDestination dest, - const LLUUID& dest_id, - LLViewerRegion*& first_region, - std::string& error, - std::vector* objectsp) -{ - std::vector derez_objects; - - if (!objectsp) // if objects to derez not specified - { - // get them from selection - if (!get_derezzable_objects(dest, error, first_region, &derez_objects, false)) - { - LL_WARNS() << "No objects to derez" << LL_ENDL; - return; - } - - objectsp = &derez_objects; - } - - - if(gAgentCamera.cameraMouselook()) - { - gAgentCamera.changeCameraToDefault(); - } - - // This constant is based on (1200 - HEADER_SIZE) / 4 bytes per - // root. I lopped off a few (33) to provide a bit - // pad. HEADER_SIZE is currently 67 bytes, most of which is UUIDs. - // This gives us a maximum of 63500 root objects - which should - // satisfy anybody. - const S32 MAX_ROOTS_PER_PACKET = 250; - const S32 MAX_PACKET_COUNT = 254; - F32 packets = ceil((F32)objectsp->size() / (F32)MAX_ROOTS_PER_PACKET); - if(packets > (F32)MAX_PACKET_COUNT) - { - error = "AcquireErrorTooManyObjects"; - } - - if(error.empty() && objectsp->size() > 0) - { - U8 d = (U8)dest; - LLUUID tid; - tid.generate(); - U8 packet_count = (U8)packets; - S32 object_index = 0; - S32 objects_in_packet = 0; - LLMessageSystem* msg = gMessageSystem; - for(U8 packet_number = 0; - packet_number < packet_count; - ++packet_number) - { - msg->newMessageFast(_PREHASH_DeRezObject); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_AgentBlock); - msg->addUUIDFast(_PREHASH_GroupID, gAgent.getGroupID()); - msg->addU8Fast(_PREHASH_Destination, d); - msg->addUUIDFast(_PREHASH_DestinationID, dest_id); - msg->addUUIDFast(_PREHASH_TransactionID, tid); - msg->addU8Fast(_PREHASH_PacketCount, packet_count); - msg->addU8Fast(_PREHASH_PacketNumber, packet_number); - objects_in_packet = 0; - while((object_index < objectsp->size()) - && (objects_in_packet++ < MAX_ROOTS_PER_PACKET)) - - { - LLViewerObject* object = objectsp->at(object_index++); - msg->nextBlockFast(_PREHASH_ObjectData); - msg->addU32Fast(_PREHASH_ObjectLocalID, object->getLocalID()); - // VEFFECT: DerezObject - LLHUDEffectSpiral* effectp = (LLHUDEffectSpiral*)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_POINT, true); - effectp->setPositionGlobal(object->getPositionGlobal()); - effectp->setColor(LLColor4U(gAgent.getEffectColor())); - } - msg->sendReliable(first_region->getHost()); - } - make_ui_sound("UISndObjectRezOut"); - - // Busy count decremented by inventory update, so only increment - // if will be causing an update. - if (dest != DRD_RETURN_TO_OWNER) - { - gViewerWindow->getWindow()->incBusyCount(); - } - } - else if(!error.empty()) - { - LLNotificationsUtil::add(error); - } -} - -static void derez_objects(EDeRezDestination dest, const LLUUID& dest_id) -{ - LLViewerRegion* first_region = NULL; - std::string error; - derez_objects(dest, dest_id, first_region, error, NULL); -} - -static void derez_objects_separate(EDeRezDestination dest, const LLUUID &dest_id) -{ - std::vector derez_object_list; - std::string error; - LLViewerRegion* first_region = NULL; - if (!get_derezzable_objects(dest, error, first_region, &derez_object_list, false)) - { - LL_WARNS() << "No objects to derez" << LL_ENDL; - return; - } - for (LLViewerObject *opjectp : derez_object_list) - { - std::vector buf_list; - buf_list.push_back(opjectp); - derez_objects(dest, dest_id, first_region, error, &buf_list); - } -} - -void handle_take_copy() -{ - if (LLSelectMgr::getInstance()->getSelection()->isEmpty()) return; - - const LLUUID category_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_OBJECT); - derez_objects(DRD_ACQUIRE_TO_AGENT_INVENTORY, category_id); -} - -void handle_take_separate_copy() -{ - if (LLSelectMgr::getInstance()->getSelection()->isEmpty()) - return; - - const LLUUID category_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_OBJECT); - derez_objects_separate(DRD_ACQUIRE_TO_AGENT_INVENTORY, category_id); -} - -void handle_link_objects() -{ - if (LLSelectMgr::getInstance()->getSelection()->isEmpty()) - { - LLFloaterReg::toggleInstanceOrBringToFront("places"); - } - else - { - LLSelectMgr::getInstance()->linkObjects(); - } -} - -// You can return an object to its owner if it is on your land. -class LLObjectReturn : public view_listener_t -{ -public: - LLObjectReturn() : mFirstRegion(NULL) {} - -private: - bool handleEvent(const LLSD& userdata) - { - if (LLSelectMgr::getInstance()->getSelection()->isEmpty()) return true; - - mObjectSelection = LLSelectMgr::getInstance()->getEditSelection(); - - // Save selected objects, so that we still know what to return after the confirmation dialog resets selection. - get_derezzable_objects(DRD_RETURN_TO_OWNER, mError, mFirstRegion, &mReturnableObjects); - - LLNotificationsUtil::add("ReturnToOwner", LLSD(), LLSD(), boost::bind(&LLObjectReturn::onReturnToOwner, this, _1, _2)); - return true; - } - - bool onReturnToOwner(const LLSD& notification, const LLSD& response) - { - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (0 == option) - { - // Ignore category ID for this derez destination. - derez_objects(DRD_RETURN_TO_OWNER, LLUUID::null, mFirstRegion, mError, &mReturnableObjects); - } - - mReturnableObjects.clear(); - mError.clear(); - mFirstRegion = NULL; - - // drop reference to current selection - mObjectSelection = NULL; - return false; - } - - LLObjectSelectionHandle mObjectSelection; - - std::vector mReturnableObjects; - std::string mError; - LLViewerRegion* mFirstRegion; -}; - - -// Allow return to owner if one or more of the selected items is -// over land you own. -class LLObjectEnableReturn : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - if (LLSelectMgr::getInstance()->getSelection()->isEmpty()) - { - // Do not enable if nothing selected - return false; - } -#ifdef HACKED_GODLIKE_VIEWER - bool new_value = true; -#else - bool new_value = false; - if (gAgent.isGodlike()) - { - new_value = true; - } - else - { - new_value = can_derez(DRD_RETURN_TO_OWNER); - } -#endif - return new_value; - } -}; - -void force_take_copy(void*) -{ - if (LLSelectMgr::getInstance()->getSelection()->isEmpty()) return; - const LLUUID category_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_OBJECT); - derez_objects(DRD_FORCE_TO_GOD_INVENTORY, category_id); -} - -void handle_take(bool take_separate) -{ - // we want to use the folder this was derezzed from if it's - // available. Otherwise, derez to the normal place. - if(LLSelectMgr::getInstance()->getSelection()->isEmpty()) - { - return; - } - - bool you_own_everything = true; - bool locked_but_takeable_object = false; - LLUUID category_id; - - for (LLObjectSelection::root_iterator iter = LLSelectMgr::getInstance()->getSelection()->root_begin(); - iter != LLSelectMgr::getInstance()->getSelection()->root_end(); iter++) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - if(object) - { - if(!object->permYouOwner()) - { - you_own_everything = false; - } - - if(!object->permMove()) - { - locked_but_takeable_object = true; - } - } - if(node->mFolderID.notNull()) - { - if(category_id.isNull()) - { - category_id = node->mFolderID; - } - else if(category_id != node->mFolderID) - { - // we have found two potential destinations. break out - // now and send to the default location. - category_id.setNull(); - break; - } - } - } - if(category_id.notNull()) - { - // there is an unambiguous destination. See if this agent has - // such a location and it is not in the trash or library - if(!gInventory.getCategory(category_id)) - { - // nope, set to NULL. - category_id.setNull(); - } - if(category_id.notNull()) - { - // check trash - const LLUUID trash = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - if(category_id == trash || gInventory.isObjectDescendentOf(category_id, trash)) - { - category_id.setNull(); - } - - // check library - if(gInventory.isObjectDescendentOf(category_id, gInventory.getLibraryRootFolderID())) - { - category_id.setNull(); - } - - // check inbox - const LLUUID inbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX); - if (category_id == inbox_id || gInventory.isObjectDescendentOf(category_id, inbox_id)) - { - category_id.setNull(); - } - } - } - if(category_id.isNull()) - { - category_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_OBJECT); - } - LLSD payload; - payload["folder_id"] = category_id; - - LLNotification::Params params("ConfirmObjectTakeLock"); - params.payload(payload); - // MAINT-290 - // Reason: Showing the confirmation dialog resets object selection, thus there is nothing to derez. - // Fix: pass selection to the confirm_take, so that selection doesn't "die" after confirmation dialog is opened - params.functor.function([take_separate](const LLSD ¬ification, const LLSD &response) - { - if (take_separate) - { - confirm_take_separate(notification, response, LLSelectMgr::instance().getSelection()); - } - else - { - confirm_take(notification, response, LLSelectMgr::instance().getSelection()); - } - }); - - if(locked_but_takeable_object || - !you_own_everything) - { - if(locked_but_takeable_object && you_own_everything) - { - params.name("ConfirmObjectTakeLock"); - } - else if(!locked_but_takeable_object && !you_own_everything) - { - params.name("ConfirmObjectTakeNoOwn"); - } - else - { - params.name("ConfirmObjectTakeLockNoOwn"); - } - - LLNotifications::instance().add(params); - } - else - { - LLNotifications::instance().forceResponse(params, 0); - } -} - -void handle_object_show_inspector() -{ - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - LLViewerObject* objectp = selection->getFirstRootObject(true); - if (!objectp) - { - return; - } - - LLSD params; - params["object_id"] = objectp->getID(); - LLFloaterReg::showInstance("inspect_object", params); -} - -void handle_avatar_show_inspector() -{ - LLVOAvatar* avatar = find_avatar_from_object( LLSelectMgr::getInstance()->getSelection()->getPrimaryObject() ); - if(avatar) - { - LLSD params; - params["avatar_id"] = avatar->getID(); - LLFloaterReg::showInstance("inspect_avatar", params); - } -} - - - -bool confirm_take(const LLSD& notification, const LLSD& response, LLObjectSelectionHandle selection_handle) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if(enable_take() && (option == 0)) - { - derez_objects(DRD_TAKE_INTO_AGENT_INVENTORY, notification["payload"]["folder_id"].asUUID()); - } - return false; -} - -bool confirm_take_separate(const LLSD ¬ification, const LLSD &response, LLObjectSelectionHandle selection_handle) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (enable_take() && (option == 0)) - { - derez_objects_separate(DRD_TAKE_INTO_AGENT_INVENTORY, notification["payload"]["folder_id"].asUUID()); - } - return false; -} - -// You can take an item when it is public and transferrable, or when -// you own it. We err on the side of enabling the item when at least -// one item selected can be copied to inventory. -bool enable_take() -{ - if (sitting_on_selection()) - { - return false; - } - - for (LLObjectSelection::valid_root_iterator iter = LLSelectMgr::getInstance()->getSelection()->valid_root_begin(); - iter != LLSelectMgr::getInstance()->getSelection()->valid_root_end(); iter++) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - if (object->isAvatar()) - { - // ...don't acquire avatars - continue; - } - -#ifdef HACKED_GODLIKE_VIEWER - return true; -#else -# ifdef TOGGLE_HACKED_GODLIKE_VIEWER - if (!LLGridManager::getInstance()->isInProductionGrid() - && gAgent.isGodlike()) - { - return true; - } -# endif - if(!object->isPermanentEnforced() && - ((node->mPermissions->allowTransferTo(gAgent.getID()) - && object->permModify()) - || (node->mPermissions->getOwner() == gAgent.getID()))) - { - return !object->isAttachment(); - } -#endif - } - return false; -} - - -void handle_buy_or_take() -{ - if (LLSelectMgr::getInstance()->getSelection()->isEmpty()) - { - return; - } - - if (is_selection_buy_not_take()) - { - S32 total_price = selection_price(); - - if (total_price <= gStatusBar->getBalance() || total_price == 0) - { - handle_buy(); - } - else - { - LLStringUtil::format_map_t args; - args["AMOUNT"] = llformat("%d", total_price); - LLBuyCurrencyHTML::openCurrencyFloater( LLTrans::getString( "this_object_costs", args ), total_price ); - } - } - else - { - handle_take(); - } -} - -bool visible_buy_object() -{ - return is_selection_buy_not_take() && enable_buy_object(); -} - -bool visible_take_object() -{ - return !is_selection_buy_not_take() && enable_take(); -} - -bool is_multiple_selection() -{ - return (LLSelectMgr::getInstance()->getSelection()->getRootObjectCount() > 1); -} - -bool is_single_selection() -{ - return !is_multiple_selection(); -} - -bool enable_take_objects() -{ - return visible_take_object() && is_multiple_selection(); -} - -bool tools_visible_buy_object() -{ - return is_selection_buy_not_take(); -} - -bool tools_visible_take_object() -{ - return !is_selection_buy_not_take(); -} - -class LLToolsEnableBuyOrTake : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool is_buy = is_selection_buy_not_take(); - bool new_value = is_buy ? enable_buy_object() : enable_take(); - return new_value; - } -}; - -// This is a small helper function to determine if we have a buy or a -// take in the selection. This method is to help with the aliasing -// problems of putting buy and take in the same pie menu space. After -// a fair amont of discussion, it was determined to prefer buy over -// take. The reasoning follows from the fact that when users walk up -// to buy something, they will click on one or more items. Thus, if -// anything is for sale, it becomes a buy operation, and the server -// will group all of the buy items, and copyable/modifiable items into -// one package and give the end user as much as the permissions will -// allow. If the user wanted to take something, they will select fewer -// and fewer items until only 'takeable' items are left. The one -// exception is if you own everything in the selection that is for -// sale, in this case, you can't buy stuff from yourself, so you can -// take it. -// return value = true if selection is a 'buy'. -// false if selection is a 'take' -bool is_selection_buy_not_take() -{ - for (LLObjectSelection::root_iterator iter = LLSelectMgr::getInstance()->getSelection()->root_begin(); - iter != LLSelectMgr::getInstance()->getSelection()->root_end(); iter++) - { - LLSelectNode* node = *iter; - LLViewerObject* obj = node->getObject(); - if(obj && !(obj->permYouOwner()) && (node->mSaleInfo.isForSale())) - { - // you do not own the object and it is for sale, thus, - // it's a buy - return true; - } - } - return false; -} - -S32 selection_price() -{ - S32 total_price = 0; - for (LLObjectSelection::root_iterator iter = LLSelectMgr::getInstance()->getSelection()->root_begin(); - iter != LLSelectMgr::getInstance()->getSelection()->root_end(); iter++) - { - LLSelectNode* node = *iter; - LLViewerObject* obj = node->getObject(); - if(obj && !(obj->permYouOwner()) && (node->mSaleInfo.isForSale())) - { - // you do not own the object and it is for sale. - // Add its price. - total_price += node->mSaleInfo.getSalePrice(); - } - } - - return total_price; -} -/* -bool callback_show_buy_currency(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (0 == option) - { - LL_INFOS() << "Loading page " << LLNotifications::instance().getGlobalString("BUY_CURRENCY_URL") << LL_ENDL; - LLWeb::loadURL(LLNotifications::instance().getGlobalString("BUY_CURRENCY_URL")); - } - return false; -} -*/ - -void show_buy_currency(const char* extra) -{ - // Don't show currency web page for branded clients. -/* - std::ostringstream mesg; - if (extra != NULL) - { - mesg << extra << "\n \n"; - } - mesg << "Go to " << LLNotifications::instance().getGlobalString("BUY_CURRENCY_URL")<< "\nfor information on purchasing currency?"; -*/ - LLSD args; - if (extra != NULL) - { - args["EXTRA"] = extra; - } - LLNotificationsUtil::add("PromptGoToCurrencyPage", args);//, LLSD(), callback_show_buy_currency); -} - -void handle_buy() -{ - if (LLSelectMgr::getInstance()->getSelection()->isEmpty()) return; - - LLSaleInfo sale_info; - bool valid = LLSelectMgr::getInstance()->selectGetSaleInfo(sale_info); - if (!valid) return; - - S32 price = sale_info.getSalePrice(); - - if (price > 0 && price > gStatusBar->getBalance()) - { - LLStringUtil::format_map_t args; - args["AMOUNT"] = llformat("%d", price); - LLBuyCurrencyHTML::openCurrencyFloater( LLTrans::getString("this_object_costs", args), price ); - return; - } - - if (sale_info.getSaleType() == LLSaleInfo::FS_CONTENTS) - { - handle_buy_contents(sale_info); - } - else - { - handle_buy_object(sale_info); - } -} - -bool anyone_copy_selection(LLSelectNode* nodep) -{ - bool perm_copy = (bool)(nodep->getObject()->permCopy()); - bool all_copy = (bool)(nodep->mPermissions->getMaskEveryone() & PERM_COPY); - return perm_copy && all_copy; -} - -bool for_sale_selection(LLSelectNode* nodep) -{ - return nodep->mSaleInfo.isForSale() - && nodep->mPermissions->getMaskOwner() & PERM_TRANSFER - && (nodep->mPermissions->getMaskOwner() & PERM_COPY - || nodep->mSaleInfo.getSaleType() != LLSaleInfo::FS_COPY); -} - -bool sitting_on_selection() -{ - LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(); - if (!node) - { - return false; - } - - if (!node->mValid) - { - return false; - } - - LLViewerObject* root_object = node->getObject(); - if (!root_object) - { - return false; - } - - // Need to determine if avatar is sitting on this object - if (!isAgentAvatarValid()) return false; - - return (gAgentAvatarp->isSitting() && gAgentAvatarp->getRoot() == root_object); -} - -class LLToolsSaveToObjectInventory : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(); - if(node && (node->mValid) && (!node->mFromTaskID.isNull())) - { - // *TODO: check to see if the fromtaskid object exists. - derez_objects(DRD_SAVE_INTO_TASK_INVENTORY, node->mFromTaskID); - } - return true; - } -}; - -class LLToolsEnablePathfinding : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - return (LLPathfindingManager::getInstance() != NULL) && LLPathfindingManager::getInstance()->isPathfindingEnabledForCurrentRegion(); - } -}; - -class LLToolsEnablePathfindingView : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - return (LLPathfindingManager::getInstance() != NULL) && LLPathfindingManager::getInstance()->isPathfindingEnabledForCurrentRegion() && LLPathfindingManager::getInstance()->isPathfindingViewEnabled(); - } -}; - -class LLToolsDoPathfindingRebakeRegion : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool hasPathfinding = (LLPathfindingManager::getInstance() != NULL); - - if (hasPathfinding) - { - LLMenuOptionPathfindingRebakeNavmesh::getInstance()->sendRequestRebakeNavmesh(); - } - - return hasPathfinding; - } -}; - -class LLToolsEnablePathfindingRebakeRegion : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool returnValue = false; - - if (LLNavigationBar::instanceExists()) - { - returnValue = LLNavigationBar::getInstance()->isRebakeNavMeshAvailable(); - } - return returnValue; - } -}; - -// Round the position of all root objects to the grid -class LLToolsSnapObjectXY : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - F64 snap_size = (F64)gSavedSettings.getF32("GridResolution"); - - for (LLObjectSelection::root_iterator iter = LLSelectMgr::getInstance()->getSelection()->root_begin(); - iter != LLSelectMgr::getInstance()->getSelection()->root_end(); iter++) - { - LLSelectNode* node = *iter; - LLViewerObject* obj = node->getObject(); - if (obj->permModify()) - { - LLVector3d pos_global = obj->getPositionGlobal(); - F64 round_x = fmod(pos_global.mdV[VX], snap_size); - if (round_x < snap_size * 0.5) - { - // closer to round down - pos_global.mdV[VX] -= round_x; - } - else - { - // closer to round up - pos_global.mdV[VX] -= round_x; - pos_global.mdV[VX] += snap_size; - } - - F64 round_y = fmod(pos_global.mdV[VY], snap_size); - if (round_y < snap_size * 0.5) - { - pos_global.mdV[VY] -= round_y; - } - else - { - pos_global.mdV[VY] -= round_y; - pos_global.mdV[VY] += snap_size; - } - - obj->setPositionGlobal(pos_global, false); - } - } - LLSelectMgr::getInstance()->sendMultipleUpdate(UPD_POSITION); - return true; - } -}; - -// Determine if the option to cycle between linked prims is shown -class LLToolsEnableSelectNextPart : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = (!LLSelectMgr::getInstance()->getSelection()->isEmpty() - && (gSavedSettings.getBOOL("EditLinkedParts") - || LLToolFace::getInstance() == LLToolMgr::getInstance()->getCurrentTool())); - return new_value; - } -}; - -// Cycle selection through linked children or/and faces in selected object. -// FIXME: Order of children list is not always the same as sim's idea of link order. This may confuse -// resis. Need link position added to sim messages to address this. -class LLToolsSelectNextPartFace : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool cycle_faces = LLToolFace::getInstance() == LLToolMgr::getInstance()->getCurrentTool(); - bool cycle_linked = gSavedSettings.getBOOL("EditLinkedParts"); - - if (!cycle_faces && !cycle_linked) - { - // Nothing to do - return true; - } - - bool fwd = (userdata.asString() == "next"); - bool prev = (userdata.asString() == "previous"); - bool ifwd = (userdata.asString() == "includenext"); - bool iprev = (userdata.asString() == "includeprevious"); - - LLViewerObject* to_select = NULL; - bool restart_face_on_part = !cycle_faces; - S32 new_te = 0; - - if (cycle_faces) - { - // Cycle through faces of current selection, if end is reached, swithc to next part (if present) - LLSelectNode* nodep = LLSelectMgr::getInstance()->getSelection()->getFirstNode(); - if (!nodep) return false; - to_select = nodep->getObject(); - if (!to_select) return false; - - S32 te_count = to_select->getNumTEs(); - S32 selected_te = nodep->getLastOperatedTE(); - - if (fwd || ifwd) - { - if (selected_te < 0) - { - new_te = 0; - } - else if (selected_te + 1 < te_count) - { - // select next face - new_te = selected_te + 1; - } - else - { - // restart from first face on next part - restart_face_on_part = true; - } - } - else if (prev || iprev) - { - if (selected_te > te_count) - { - new_te = te_count - 1; - } - else if (selected_te - 1 >= 0) - { - // select previous face - new_te = selected_te - 1; - } - else - { - // restart from last face on next part - restart_face_on_part = true; - } - } - } - - S32 object_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount(); - if (cycle_linked && object_count && restart_face_on_part) - { - LLViewerObject* selected = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); - if (selected && selected->getRootEdit()) - { - LLViewerObject::child_list_t children = selected->getRootEdit()->getChildren(); - children.push_front(selected->getRootEdit()); // need root in the list too - - for (LLViewerObject::child_list_t::iterator iter = children.begin(); iter != children.end(); ++iter) - { - if ((*iter)->isSelected()) - { - if (object_count > 1 && (fwd || prev)) // multiple selection, find first or last selected if not include - { - to_select = *iter; - if (fwd) - { - // stop searching if going forward; repeat to get last hit if backward - break; - } - } - else if ((object_count == 1) || (ifwd || iprev)) // single selection or include - { - if (fwd || ifwd) - { - ++iter; - while (iter != children.end() && ((*iter)->isAvatar() || (ifwd && (*iter)->isSelected()))) - { - ++iter; // skip sitting avatars and selected if include - } - } - else // backward - { - iter = (iter == children.begin() ? children.end() : iter); - --iter; - while (iter != children.begin() && ((*iter)->isAvatar() || (iprev && (*iter)->isSelected()))) - { - --iter; // skip sitting avatars and selected if include - } - } - iter = (iter == children.end() ? children.begin() : iter); - to_select = *iter; - break; - } - } - } - } - } - - if (to_select) - { - if (gFocusMgr.childHasKeyboardFocus(gFloaterTools)) - { - gFocusMgr.setKeyboardFocus(NULL); // force edit toolbox to commit any changes - } - if (fwd || prev) - { - LLSelectMgr::getInstance()->deselectAll(); - } - if (cycle_faces) - { - if (restart_face_on_part) - { - if (fwd || ifwd) - { - new_te = 0; - } - else - { - new_te = to_select->getNumTEs() - 1; - } - } - LLSelectMgr::getInstance()->selectObjectOnly(to_select, new_te); - LLSelectMgr::getInstance()->addAsIndividual(to_select, new_te, false); - } - else - { - LLSelectMgr::getInstance()->selectObjectOnly(to_select); - } - return true; - } - return true; - } -}; - -class LLToolsStopAllAnimations : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - gAgent.stopCurrentAnimations(); - return true; - } -}; - -class LLToolsReleaseKeys : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - gAgent.forceReleaseControls(); - - return true; - } -}; - -class LLToolsEnableReleaseKeys : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - return gAgent.anyControlGrabbed(); - } -}; - - -class LLEditEnableCut : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = LLEditMenuHandler::gEditMenuHandler && LLEditMenuHandler::gEditMenuHandler->canCut(); - return new_value; - } -}; - -class LLEditCut : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - if( LLEditMenuHandler::gEditMenuHandler ) - { - LLEditMenuHandler::gEditMenuHandler->cut(); - } - return true; - } -}; - -class LLEditEnableCopy : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = LLEditMenuHandler::gEditMenuHandler && LLEditMenuHandler::gEditMenuHandler->canCopy(); - return new_value; - } -}; - -class LLEditCopy : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - if( LLEditMenuHandler::gEditMenuHandler ) - { - LLEditMenuHandler::gEditMenuHandler->copy(); - } - return true; - } -}; - -class LLEditEnablePaste : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = LLEditMenuHandler::gEditMenuHandler && LLEditMenuHandler::gEditMenuHandler->canPaste(); - return new_value; - } -}; - -class LLEditPaste : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - if( LLEditMenuHandler::gEditMenuHandler ) - { - LLEditMenuHandler::gEditMenuHandler->paste(); - } - return true; - } -}; - -class LLEditEnableDelete : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = LLEditMenuHandler::gEditMenuHandler && LLEditMenuHandler::gEditMenuHandler->canDoDelete(); - return new_value; - } -}; - -class LLEditDelete : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - // If a text field can do a deletion, it gets precedence over deleting - // an object in the world. - if( LLEditMenuHandler::gEditMenuHandler && LLEditMenuHandler::gEditMenuHandler->canDoDelete()) - { - LLEditMenuHandler::gEditMenuHandler->doDelete(); - } - - // and close any pie/context menus when done - gMenuHolder->hideMenus(); - - // When deleting an object we may not actually be done - // Keep selection so we know what to delete when confirmation is needed about the delete - gMenuObject->hide(); - return true; - } -}; - -void handle_spellcheck_replace_with_suggestion(const LLUICtrl* ctrl, const LLSD& param) -{ - const LLContextMenu* menu = dynamic_cast(ctrl->getParent()); - LLSpellCheckMenuHandler* spellcheck_handler = (menu) ? dynamic_cast(menu->getSpawningView()) : NULL; - if ( (!spellcheck_handler) || (!spellcheck_handler->getSpellCheck()) ) - { - return; - } - - U32 index = 0; - if ( (!LLStringUtil::convertToU32(param.asString(), index)) || (index >= spellcheck_handler->getSuggestionCount()) ) - { - return; - } - - spellcheck_handler->replaceWithSuggestion(index); -} - -bool visible_spellcheck_suggestion(LLUICtrl* ctrl, const LLSD& param) -{ - LLMenuItemGL* item = dynamic_cast(ctrl); - const LLContextMenu* menu = (item) ? dynamic_cast(item->getParent()) : NULL; - const LLSpellCheckMenuHandler* spellcheck_handler = (menu) ? dynamic_cast(menu->getSpawningView()) : NULL; - if ( (!spellcheck_handler) || (!spellcheck_handler->getSpellCheck()) ) - { - return false; - } - - U32 index = 0; - if ( (!LLStringUtil::convertToU32(param.asString(), index)) || (index >= spellcheck_handler->getSuggestionCount()) ) - { - return false; - } - - item->setLabel(spellcheck_handler->getSuggestion(index)); - return true; -} - -void handle_spellcheck_add_to_dictionary(const LLUICtrl* ctrl) -{ - const LLContextMenu* menu = dynamic_cast(ctrl->getParent()); - LLSpellCheckMenuHandler* spellcheck_handler = (menu) ? dynamic_cast(menu->getSpawningView()) : NULL; - if ( (spellcheck_handler) && (spellcheck_handler->canAddToDictionary()) ) - { - spellcheck_handler->addToDictionary(); - } -} - -bool enable_spellcheck_add_to_dictionary(const LLUICtrl* ctrl) -{ - const LLContextMenu* menu = dynamic_cast(ctrl->getParent()); - const LLSpellCheckMenuHandler* spellcheck_handler = (menu) ? dynamic_cast(menu->getSpawningView()) : NULL; - return (spellcheck_handler) && (spellcheck_handler->canAddToDictionary()); -} - -void handle_spellcheck_add_to_ignore(const LLUICtrl* ctrl) -{ - const LLContextMenu* menu = dynamic_cast(ctrl->getParent()); - LLSpellCheckMenuHandler* spellcheck_handler = (menu) ? dynamic_cast(menu->getSpawningView()) : NULL; - if ( (spellcheck_handler) && (spellcheck_handler->canAddToIgnore()) ) - { - spellcheck_handler->addToIgnore(); - } -} - -bool enable_spellcheck_add_to_ignore(const LLUICtrl* ctrl) -{ - const LLContextMenu* menu = dynamic_cast(ctrl->getParent()); - const LLSpellCheckMenuHandler* spellcheck_handler = (menu) ? dynamic_cast(menu->getSpawningView()) : NULL; - return (spellcheck_handler) && (spellcheck_handler->canAddToIgnore()); -} - -bool enable_object_return() -{ - return (!LLSelectMgr::getInstance()->getSelection()->isEmpty() && - (gAgent.isGodlike() || can_derez(DRD_RETURN_TO_OWNER))); -} - -bool enable_object_delete() -{ - bool new_value = -#ifdef HACKED_GODLIKE_VIEWER - true; -#else -# ifdef TOGGLE_HACKED_GODLIKE_VIEWER - (!LLGridManager::getInstance()->isInProductionGrid() - && gAgent.isGodlike()) || -# endif - LLSelectMgr::getInstance()->canDoDelete(); -#endif - return new_value; -} - -class LLObjectsReturnPackage -{ -public: - LLObjectsReturnPackage() : mObjectSelection(), mReturnableObjects(), mError(), mFirstRegion(NULL) {}; - ~LLObjectsReturnPackage() - { - mObjectSelection.clear(); - mReturnableObjects.clear(); - mError.clear(); - mFirstRegion = NULL; - }; - - LLObjectSelectionHandle mObjectSelection; - std::vector mReturnableObjects; - std::string mError; - LLViewerRegion *mFirstRegion; -}; - -static void return_objects(LLObjectsReturnPackage *objectsReturnPackage, const LLSD& notification, const LLSD& response) -{ - if (LLNotificationsUtil::getSelectedOption(notification, response) == 0) - { - // Ignore category ID for this derez destination. - derez_objects(DRD_RETURN_TO_OWNER, LLUUID::null, objectsReturnPackage->mFirstRegion, objectsReturnPackage->mError, &objectsReturnPackage->mReturnableObjects); - } - - delete objectsReturnPackage; -} - -void handle_object_return() -{ - if (!LLSelectMgr::getInstance()->getSelection()->isEmpty()) - { - LLObjectsReturnPackage *objectsReturnPackage = new LLObjectsReturnPackage(); - objectsReturnPackage->mObjectSelection = LLSelectMgr::getInstance()->getEditSelection(); - - // Save selected objects, so that we still know what to return after the confirmation dialog resets selection. - get_derezzable_objects(DRD_RETURN_TO_OWNER, objectsReturnPackage->mError, objectsReturnPackage->mFirstRegion, &objectsReturnPackage->mReturnableObjects); - - LLNotificationsUtil::add("ReturnToOwner", LLSD(), LLSD(), boost::bind(&return_objects, objectsReturnPackage, _1, _2)); - } -} - -void handle_object_delete() -{ - - if (LLSelectMgr::getInstance()) - { - LLSelectMgr::getInstance()->doDelete(); - } - - // and close any pie/context menus when done - gMenuHolder->hideMenus(); - - // When deleting an object we may not actually be done - // Keep selection so we know what to delete when confirmation is needed about the delete - gMenuObject->hide(); - return; -} - -void handle_force_delete(void*) -{ - LLSelectMgr::getInstance()->selectForceDelete(); -} - -class LLViewEnableJoystickFlycam : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = (gSavedSettings.getBOOL("JoystickEnabled") && gSavedSettings.getBOOL("JoystickFlycamEnabled")); - return new_value; - } -}; - -class LLViewEnableLastChatter : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - // *TODO: add check that last chatter is in range - bool new_value = (gAgentCamera.cameraThirdPerson() && gAgent.getLastChatter().notNull()); - return new_value; - } -}; - -class LLEditEnableDeselect : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = LLEditMenuHandler::gEditMenuHandler && LLEditMenuHandler::gEditMenuHandler->canDeselect(); - return new_value; - } -}; - -class LLEditDeselect : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - if( LLEditMenuHandler::gEditMenuHandler ) - { - LLEditMenuHandler::gEditMenuHandler->deselect(); - } - return true; - } -}; - -class LLEditEnableSelectAll : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = LLEditMenuHandler::gEditMenuHandler && LLEditMenuHandler::gEditMenuHandler->canSelectAll(); - return new_value; - } -}; - - -class LLEditSelectAll : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - if( LLEditMenuHandler::gEditMenuHandler ) - { - LLEditMenuHandler::gEditMenuHandler->selectAll(); - } - return true; - } -}; - - -class LLEditEnableUndo : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = LLEditMenuHandler::gEditMenuHandler && LLEditMenuHandler::gEditMenuHandler->canUndo(); - return new_value; - } -}; - -class LLEditUndo : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - if( LLEditMenuHandler::gEditMenuHandler && LLEditMenuHandler::gEditMenuHandler->canUndo() ) - { - LLEditMenuHandler::gEditMenuHandler->undo(); - } - return true; - } -}; - -class LLEditEnableRedo : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = LLEditMenuHandler::gEditMenuHandler && LLEditMenuHandler::gEditMenuHandler->canRedo(); - return new_value; - } -}; - -class LLEditRedo : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - if( LLEditMenuHandler::gEditMenuHandler && LLEditMenuHandler::gEditMenuHandler->canRedo() ) - { - LLEditMenuHandler::gEditMenuHandler->redo(); - } - return true; - } -}; - - - -void print_object_info(void*) -{ - LLSelectMgr::getInstance()->selectionDump(); -} - -void print_agent_nvpairs(void*) -{ - LLViewerObject *objectp; - - LL_INFOS() << "Agent Name Value Pairs" << LL_ENDL; - - objectp = gObjectList.findObject(gAgentID); - if (objectp) - { - objectp->printNameValuePairs(); - } - else - { - LL_INFOS() << "Can't find agent object" << LL_ENDL; - } - - LL_INFOS() << "Camera at " << gAgentCamera.getCameraPositionGlobal() << LL_ENDL; -} - -void show_debug_menus() -{ - // this might get called at login screen where there is no menu so only toggle it if one exists - if ( gMenuBarView ) - { - bool debug = gSavedSettings.getBOOL("UseDebugMenus"); - bool qamode = gSavedSettings.getBOOL("QAMode"); - - gMenuBarView->setItemVisible("Advanced", debug); -// gMenuBarView->setItemEnabled("Advanced", debug); // Don't disable Advanced keyboard shortcuts when hidden - - gMenuBarView->setItemVisible("Debug", qamode); - gMenuBarView->setItemEnabled("Debug", qamode); - - gMenuBarView->setItemVisible("Develop", qamode); - gMenuBarView->setItemEnabled("Develop", qamode); - - // Server ('Admin') menu hidden when not in godmode. - const bool show_server_menu = (gAgent.getGodLevel() > GOD_NOT || (debug && gAgent.getAdminOverride())); - gMenuBarView->setItemVisible("Admin", show_server_menu); - gMenuBarView->setItemEnabled("Admin", show_server_menu); - } - if (gLoginMenuBarView) - { - bool debug = gSavedSettings.getBOOL("UseDebugMenus"); - gLoginMenuBarView->setItemVisible("Debug", debug); - gLoginMenuBarView->setItemEnabled("Debug", debug); - } -} - -void toggle_debug_menus(void*) -{ - bool visible = ! gSavedSettings.getBOOL("UseDebugMenus"); - gSavedSettings.setBOOL("UseDebugMenus", visible); - show_debug_menus(); -} - - -// LLUUID gExporterRequestID; -// std::string gExportDirectory; - -// LLUploadDialog *gExportDialog = NULL; - -// void handle_export_selected( void * ) -// { -// LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); -// if (selection->isEmpty()) -// { -// return; -// } -// LL_INFOS() << "Exporting selected objects:" << LL_ENDL; - -// gExporterRequestID.generate(); -// gExportDirectory = ""; - -// LLMessageSystem* msg = gMessageSystem; -// msg->newMessageFast(_PREHASH_ObjectExportSelected); -// msg->nextBlockFast(_PREHASH_AgentData); -// msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); -// msg->addUUIDFast(_PREHASH_RequestID, gExporterRequestID); -// msg->addS16Fast(_PREHASH_VolumeDetail, 4); - -// for (LLObjectSelection::root_iterator iter = selection->root_begin(); -// iter != selection->root_end(); iter++) -// { -// LLSelectNode* node = *iter; -// LLViewerObject* object = node->getObject(); -// msg->nextBlockFast(_PREHASH_ObjectData); -// msg->addUUIDFast(_PREHASH_ObjectID, object->getID()); -// LL_INFOS() << "Object: " << object->getID() << LL_ENDL; -// } -// msg->sendReliable(gAgent.getRegion()->getHost()); - -// gExportDialog = LLUploadDialog::modalUploadDialog("Exporting selected objects..."); -// } -// - -class LLCommunicateNearbyChat : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLFloaterIMContainer* im_box = LLFloaterIMContainer::getInstance(); - LLFloaterIMNearbyChat* floater_nearby = LLFloaterReg::getTypedInstance("nearby_chat"); - if (floater_nearby->isInVisibleChain() && !floater_nearby->isTornOff() - && im_box->getSelectedSession() == LLUUID() && im_box->getConversationListItemSize() > 1) - { - im_box->selectNextorPreviousConversation(false); - } - else - { - LLFloaterReg::toggleInstanceOrBringToFront("nearby_chat"); - } - return true; - } -}; - -class LLWorldSetHomeLocation : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - // we just send the message and let the server check for failure cases - // server will echo back a "Home position set." alert if it succeeds - // and the home location screencapture happens when that alert is recieved - gAgent.setStartPosition(START_LOCATION_ID_HOME); - return true; - } -}; - -class LLWorldLindenHome : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string url = LLFloaterLandHoldings::sHasLindenHome ? LLTrans::getString("lindenhomes_my_home_url") : LLTrans::getString("lindenhomes_get_home_url"); - LLWeb::loadURL(url); - return true; - } -}; - -class LLWorldTeleportHome : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - gAgent.teleportHome(); - return true; - } -}; - -class LLWorldAlwaysRun : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - // as well as altering the default walk-vs-run state, - // we also change the *current* walk-vs-run state. - if (gAgent.getAlwaysRun()) - { - gAgent.clearAlwaysRun(); - gAgent.clearRunning(); - } - else - { - gAgent.setAlwaysRun(); - gAgent.setRunning(); - } - - // tell the simulator. - gAgent.sendWalkRun(gAgent.getAlwaysRun()); - - // Update Movement Controls according to AlwaysRun mode - LLFloaterMove::setAlwaysRunMode(gAgent.getAlwaysRun()); - - return true; - } -}; - -class LLWorldCheckAlwaysRun : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = gAgent.getAlwaysRun(); - return new_value; - } -}; - -class LLWorldSetAway : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - if (gAgent.getAFK()) - { - gAgent.clearAFK(); - } - else - { - gAgent.setAFK(); - } - return true; - } -}; - -class LLWorldSetDoNotDisturb : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - if (gAgent.isDoNotDisturb()) - { - gAgent.setDoNotDisturb(false); - } - else - { - gAgent.setDoNotDisturb(true); - LLNotificationsUtil::add("DoNotDisturbModeSet"); - } - return true; - } -}; - -class LLWorldCreateLandmark : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLFloaterReg::showInstance("add_landmark"); - - return true; - } -}; - -class LLWorldPlaceProfile : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLFloaterSidePanelContainer::showPanel("places", LLSD().with("type", "agent")); - - return true; - } -}; - -void handle_look_at_selection(const LLSD& param) -{ - const F32 PADDING_FACTOR = 1.75f; - bool zoom = (param.asString() == "zoom"); - if (!LLSelectMgr::getInstance()->getSelection()->isEmpty()) - { - gAgentCamera.setFocusOnAvatar(false, ANIMATE); - - LLBBox selection_bbox = LLSelectMgr::getInstance()->getBBoxOfSelection(); - F32 angle_of_view = llmax(0.1f, LLViewerCamera::getInstance()->getAspect() > 1.f ? LLViewerCamera::getInstance()->getView() * LLViewerCamera::getInstance()->getAspect() : LLViewerCamera::getInstance()->getView()); - F32 distance = selection_bbox.getExtentLocal().magVec() * PADDING_FACTOR / atan(angle_of_view); - - LLVector3 obj_to_cam = LLViewerCamera::getInstance()->getOrigin() - selection_bbox.getCenterAgent(); - obj_to_cam.normVec(); - - LLUUID object_id; - if (LLSelectMgr::getInstance()->getSelection()->getPrimaryObject()) - { - object_id = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject()->mID; - } - if (zoom) - { - // Make sure we are not increasing the distance between the camera and object - LLVector3d orig_distance = gAgentCamera.getCameraPositionGlobal() - LLSelectMgr::getInstance()->getSelectionCenterGlobal(); - distance = llmin(distance, (F32) orig_distance.length()); - - gAgentCamera.setCameraPosAndFocusGlobal(LLSelectMgr::getInstance()->getSelectionCenterGlobal() + LLVector3d(obj_to_cam * distance), - LLSelectMgr::getInstance()->getSelectionCenterGlobal(), - object_id ); - - } - else - { - gAgentCamera.setFocusGlobal( LLSelectMgr::getInstance()->getSelectionCenterGlobal(), object_id ); - } - } -} - -void handle_zoom_to_object(LLUUID object_id) -{ - const F32 PADDING_FACTOR = 2.f; - - LLViewerObject* object = gObjectList.findObject(object_id); - - if (object) - { - gAgentCamera.setFocusOnAvatar(false, ANIMATE); - - LLBBox bbox = object->getBoundingBoxAgent() ; - F32 angle_of_view = llmax(0.1f, LLViewerCamera::getInstance()->getAspect() > 1.f ? LLViewerCamera::getInstance()->getView() * LLViewerCamera::getInstance()->getAspect() : LLViewerCamera::getInstance()->getView()); - F32 distance = bbox.getExtentLocal().magVec() * PADDING_FACTOR / atan(angle_of_view); - - LLVector3 obj_to_cam = LLViewerCamera::getInstance()->getOrigin() - bbox.getCenterAgent(); - obj_to_cam.normVec(); - - - LLVector3d object_center_global = gAgent.getPosGlobalFromAgent(bbox.getCenterAgent()); - - gAgentCamera.setCameraPosAndFocusGlobal(object_center_global + LLVector3d(obj_to_cam * distance), - object_center_global, - object_id ); - } -} - -class LLAvatarInviteToGroup : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLVOAvatar* avatar = find_avatar_from_object( LLSelectMgr::getInstance()->getSelection()->getPrimaryObject() ); - if(avatar) - { - LLAvatarActions::inviteToGroup(avatar->getID()); - } - return true; - } -}; - -class LLAvatarAddFriend : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLVOAvatar* avatar = find_avatar_from_object( LLSelectMgr::getInstance()->getSelection()->getPrimaryObject() ); - if(avatar && !LLAvatarActions::isFriend(avatar->getID())) - { - request_friendship(avatar->getID()); - } - return true; - } -}; - - -class LLAvatarToggleMyProfile : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLFloater* instance = LLAvatarActions::getProfileFloater(gAgent.getID()); - if (LLFloater::isMinimized(instance)) - { - instance->setMinimized(false); - instance->setFocus(true); - } - else if (!LLFloater::isShown(instance)) - { - LLAvatarActions::showProfile(gAgent.getID()); - } - else if (!instance->hasFocus() && !instance->getIsChrome()) - { - instance->setFocus(true); - } - else - { - instance->closeFloater(); - } - return true; - } -}; - -class LLAvatarTogglePicks : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLFloater * instance = LLAvatarActions::getProfileFloater(gAgent.getID()); - if (LLFloater::isMinimized(instance) || (instance && !instance->hasFocus() && !instance->getIsChrome())) - { - instance->setMinimized(false); - instance->setFocus(true); - LLAvatarActions::showPicks(gAgent.getID()); - } - else if (picks_tab_visible()) - { - instance->closeFloater(); - } - else - { - LLAvatarActions::showPicks(gAgent.getID()); - } - return true; - } -}; - -class LLAvatarToggleSearch : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLFloater* instance = LLFloaterReg::findInstance("search"); - if (LLFloater::isMinimized(instance)) - { - instance->setMinimized(false); - instance->setFocus(true); - } - else if (!LLFloater::isShown(instance)) - { - LLFloaterReg::showInstance("search"); - } - else if (!instance->hasFocus() && !instance->getIsChrome()) - { - instance->setFocus(true); - } - else - { - instance->closeFloater(); - } - return true; - } -}; - -class LLAvatarResetSkeleton: public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLVOAvatar* avatar = NULL; - LLViewerObject *obj = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); - if (obj) - { - avatar = obj->getAvatar(); - } - if(avatar) - { - avatar->resetSkeleton(false); - } - return true; - } -}; - -class LLAvatarEnableResetSkeleton: public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLViewerObject *obj = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); - if (obj && obj->getAvatar()) - { - return true; - } - return false; - } -}; - - -class LLAvatarResetSkeletonAndAnimations : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLVOAvatar* avatar = find_avatar_from_object(LLSelectMgr::getInstance()->getSelection()->getPrimaryObject()); - if (avatar) - { - avatar->resetSkeleton(true); - } - return true; - } -}; - -class LLAvatarResetSelfSkeletonAndAnimations : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLVOAvatar* avatar = find_avatar_from_object(LLSelectMgr::getInstance()->getSelection()->getPrimaryObject()); - if (avatar) - { - avatar->resetSkeleton(true); - } - else - { - gAgentAvatarp->resetSkeleton(true); - } - return true; - } -}; - - -class LLAvatarAddContact : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLVOAvatar* avatar = find_avatar_from_object( LLSelectMgr::getInstance()->getSelection()->getPrimaryObject() ); - if(avatar) - { - create_inventory_callingcard(avatar->getID()); - } - return true; - } -}; - -bool complete_give_money(const LLSD& notification, const LLSD& response, LLObjectSelectionHandle selection) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) - { - gAgent.setDoNotDisturb(false); - } - - LLViewerObject* objectp = selection->getPrimaryObject(); - - // Show avatar's name if paying attachment - if (objectp && objectp->isAttachment()) - { - while (objectp && !objectp->isAvatar()) - { - objectp = (LLViewerObject*)objectp->getParent(); - } - } - - if (objectp) - { - if (objectp->isAvatar()) - { - const bool is_group = false; - LLFloaterPayUtil::payDirectly(&give_money, - objectp->getID(), - is_group); - } - else - { - LLFloaterPayUtil::payViaObject(&give_money, selection); - } - } - return false; -} - -void handle_give_money_dialog() -{ - LLNotification::Params params("DoNotDisturbModePay"); - params.functor.function(boost::bind(complete_give_money, _1, _2, LLSelectMgr::getInstance()->getSelection())); - - if (gAgent.isDoNotDisturb()) - { - // warn users of being in do not disturb mode during a transaction - LLNotifications::instance().add(params); - } - else - { - LLNotifications::instance().forceResponse(params, 1); - } -} - -bool enable_pay_avatar() -{ - LLViewerObject* obj = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); - LLVOAvatar* avatar = find_avatar_from_object(obj); - return (avatar != NULL); -} - -bool enable_pay_object() -{ - LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); - if( object ) - { - LLViewerObject *parent = (LLViewerObject *)object->getParent(); - if((object->flagTakesMoney()) || (parent && parent->flagTakesMoney())) - { - return true; - } - } - return false; -} - -bool enable_object_stand_up() -{ - // 'Object Stand Up' menu item is enabled when agent is sitting on selection - return sitting_on_selection(); -} - -bool enable_object_sit(LLUICtrl* ctrl) -{ - // 'Object Sit' menu item is enabled when agent is not sitting on selection - bool sitting_on_sel = sitting_on_selection(); - if (!sitting_on_sel) - { - // init default labels - init_default_item_label(ctrl); - - // Update label - LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(); - if (node && node->mValid && !node->mSitName.empty()) - { - ctrl->setValue(node->mSitName); - } - else - { - ctrl->setValue(get_default_item_label(ctrl->getName())); - } - } - return !sitting_on_sel && is_object_sittable(); -} - -void dump_select_mgr(void*) -{ - LLSelectMgr::getInstance()->dump(); -} - -void dump_inventory(void*) -{ - gInventory.dumpInventory(); -} - - -void handle_dump_followcam(void*) -{ - LLFollowCamMgr::getInstance()->dump(); -} - -void handle_viewer_enable_message_log(void*) -{ - gMessageSystem->startLogging(); -} - -void handle_viewer_disable_message_log(void*) -{ - gMessageSystem->stopLogging(); -} - -void handle_customize_avatar() -{ - LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "my_outfits")); -} - -void handle_edit_outfit() -{ - LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "edit_outfit")); -} - -void handle_now_wearing() -{ - LLSidepanelAppearance *panel_appearance = dynamic_cast(LLFloaterSidePanelContainer::getPanel("appearance")); - if (panel_appearance && panel_appearance->isInVisibleChain() && panel_appearance->isCOFPanelVisible()) - { - LLFloaterReg::findInstance("appearance")->closeFloater(); - return; - } - - LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "now_wearing")); -} - -void handle_edit_shape() -{ - LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "edit_shape")); -} - -void handle_hover_height() -{ - LLFloaterReg::showInstance("edit_hover_height"); -} - -void handle_edit_physics() -{ - LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "edit_physics")); -} - -void handle_report_abuse() -{ - // Prevent menu from appearing in screen shot. - gMenuHolder->hideMenus(); - LLFloaterReporter::showFromMenu(COMPLAINT_REPORT); -} - -void handle_buy_currency() -{ - LLBuyCurrencyHTML::openCurrencyFloater(); -} - -class LLFloaterVisible : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string floater_name = userdata.asString(); - bool new_value = false; - { - new_value = LLFloaterReg::instanceVisible(floater_name); - } - return new_value; - } -}; - -class LLShowHelp : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string help_topic = userdata.asString(); - LLViewerHelp* vhelp = LLViewerHelp::getInstance(); - vhelp->showTopic(help_topic); - return true; - } -}; - -class LLToggleHelp : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLFloater* help_browser = (LLFloaterReg::findInstance("help_browser")); - if (help_browser && help_browser->isInVisibleChain()) - { - help_browser->closeFloater(); - } - else - { - std::string help_topic = userdata.asString(); - LLViewerHelp* vhelp = LLViewerHelp::getInstance(); - vhelp->showTopic(help_topic); - } - return true; - } -}; - -class LLToggleSpeak : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLVoiceClient::getInstance()->toggleUserPTTState(); - return true; - } -}; -class LLShowSidetrayPanel : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string floater_name = userdata.asString(); - - LLPanel* panel = LLFloaterSidePanelContainer::getPanel(floater_name); - if (panel) - { - if (panel->isInVisibleChain()) - { - LLFloaterReg::getInstance(floater_name)->closeFloater(); - } - else - { - LLFloaterReg::getInstance(floater_name)->openFloater(); - } - } - return true; - } -}; - -class LLSidetrayPanelVisible : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string floater_name = userdata.asString(); - // Toggle the panel - if (LLFloaterReg::getInstance(floater_name)->isInVisibleChain()) - { - return true; - } - else - { - return false; - } - - } -}; - - -bool callback_show_url(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (0 == option) - { - LLWeb::loadURL(notification["payload"]["url"].asString()); - } - return false; -} - -class LLPromptShowURL : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string param = userdata.asString(); - std::string::size_type offset = param.find(","); - if (offset != param.npos) - { - std::string alert = param.substr(0, offset); - std::string url = param.substr(offset+1); - - if (LLWeb::useExternalBrowser(url)) - { - LLSD payload; - payload["url"] = url; - LLNotificationsUtil::add(alert, LLSD(), payload, callback_show_url); - } - else - { - LLWeb::loadURL(url); - } - } - else - { - LL_INFOS() << "PromptShowURL invalid parameters! Expecting \"ALERT,URL\"." << LL_ENDL; - } - return true; - } -}; - -bool callback_show_file(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (0 == option) - { - LLWeb::loadURL(notification["payload"]["url"]); - } - return false; -} - -class LLPromptShowFile : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string param = userdata.asString(); - std::string::size_type offset = param.find(","); - if (offset != param.npos) - { - std::string alert = param.substr(0, offset); - std::string file = param.substr(offset+1); - - LLSD payload; - payload["url"] = file; - LLNotificationsUtil::add(alert, LLSD(), payload, callback_show_file); - } - else - { - LL_INFOS() << "PromptShowFile invalid parameters! Expecting \"ALERT,FILE\"." << LL_ENDL; - } - return true; - } -}; - -class LLShowAgentProfile : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLUUID agent_id; - if (userdata.asString() == "agent") - { - agent_id = gAgent.getID(); - } - else if (userdata.asString() == "hit object") - { - LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); - if (objectp) - { - agent_id = objectp->getID(); - } - } - else - { - agent_id = userdata.asUUID(); - } - - LLVOAvatar* avatar = find_avatar_from_object(agent_id); - if (avatar) - { - LLAvatarActions::showProfile(avatar->getID()); - } - return true; - } -}; - -class LLShowAgentProfilePicks : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLAvatarActions::showPicks(gAgent.getID()); - return true; - } -}; - -class LLToggleAgentProfile : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLUUID agent_id; - if (userdata.asString() == "agent") - { - agent_id = gAgent.getID(); - } - else if (userdata.asString() == "hit object") - { - LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); - if (objectp) - { - agent_id = objectp->getID(); - } - } - else - { - agent_id = userdata.asUUID(); - } - - LLVOAvatar* avatar = find_avatar_from_object(agent_id); - if (avatar) - { - if (!LLAvatarActions::profileVisible(avatar->getID())) - { - LLAvatarActions::showProfile(avatar->getID()); - } - else - { - LLAvatarActions::hideProfile(avatar->getID()); - } - } - return true; - } -}; - -class LLLandEdit : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - if (gAgentCamera.getFocusOnAvatar() && gSavedSettings.getBOOL("EditCameraMovement") ) - { - // zoom in if we're looking at the avatar - gAgentCamera.setFocusOnAvatar(false, ANIMATE); - gAgentCamera.setFocusGlobal(LLToolPie::getInstance()->getPick()); - - gAgentCamera.cameraOrbitOver( F_PI * 0.25f ); - gViewerWindow->moveCursorToCenter(); - } - else if ( gSavedSettings.getBOOL("EditCameraMovement") ) - { - gAgentCamera.setFocusGlobal(LLToolPie::getInstance()->getPick()); - gViewerWindow->moveCursorToCenter(); - } - - - LLViewerParcelMgr::getInstance()->selectParcelAt( LLToolPie::getInstance()->getPick().mPosGlobal ); - - LLFloaterReg::showInstance("build"); - - // Switch to land edit toolset - LLToolMgr::getInstance()->getCurrentToolset()->selectTool( LLToolSelectLand::getInstance() ); - return true; - } -}; - -class LLMuteParticle : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLUUID id = LLToolPie::getInstance()->getPick().mParticleOwnerID; - - if (id.notNull()) - { - LLAvatarName av_name; - LLAvatarNameCache::get(id, &av_name); - - LLMute mute(id, av_name.getUserName(), LLMute::AGENT); - if (LLMuteList::getInstance()->isMuted(mute.mID)) - { - LLMuteList::getInstance()->remove(mute); - } - else - { - LLMuteList::getInstance()->add(mute); - LLPanelBlockedList::showPanelAndSelect(mute.mID); - } - } - - return true; - } -}; - -class LLWorldEnableBuyLand : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = LLViewerParcelMgr::getInstance()->canAgentBuyParcel( - LLViewerParcelMgr::getInstance()->selectionEmpty() - ? LLViewerParcelMgr::getInstance()->getAgentParcel() - : LLViewerParcelMgr::getInstance()->getParcelSelection()->getParcel(), - false); - return new_value; - } -}; - -bool enable_buy_land(void*) -{ - return LLViewerParcelMgr::getInstance()->canAgentBuyParcel( - LLViewerParcelMgr::getInstance()->getParcelSelection()->getParcel(), false); -} - -void handle_buy_land() -{ - LLViewerParcelMgr* vpm = LLViewerParcelMgr::getInstance(); - if (vpm->selectionEmpty()) - { - vpm->selectParcelAt(gAgent.getPositionGlobal()); - } - vpm->startBuyLand(); -} - -class LLObjectAttachToAvatar : public view_listener_t -{ -public: - LLObjectAttachToAvatar(bool replace) : mReplace(replace) {} - static void setObjectSelection(LLObjectSelectionHandle selection) { sObjectSelection = selection; } - -private: - bool handleEvent(const LLSD& userdata) - { - setObjectSelection(LLSelectMgr::getInstance()->getSelection()); - LLViewerObject* selectedObject = sObjectSelection->getFirstRootObject(); - if (selectedObject) - { - S32 index = userdata.asInteger(); - LLViewerJointAttachment* attachment_point = NULL; - if (index > 0) - attachment_point = get_if_there(gAgentAvatarp->mAttachmentPoints, index, (LLViewerJointAttachment*)NULL); - confirmReplaceAttachment(0, attachment_point); - } - return true; - } - - static void onNearAttachObject(bool success, void *user_data); - void confirmReplaceAttachment(S32 option, LLViewerJointAttachment* attachment_point); - class CallbackData : public LLSelectionCallbackData - { - public: - CallbackData(LLViewerJointAttachment* point, bool replace) : LLSelectionCallbackData(), mAttachmentPoint(point), mReplace(replace) {} - - LLViewerJointAttachment* mAttachmentPoint; - bool mReplace; - }; - -protected: - static LLObjectSelectionHandle sObjectSelection; - bool mReplace; -}; - -LLObjectSelectionHandle LLObjectAttachToAvatar::sObjectSelection; - -// static -void LLObjectAttachToAvatar::onNearAttachObject(bool success, void *user_data) -{ - if (!user_data) return; - CallbackData* cb_data = static_cast(user_data); - - if (success) - { - const LLViewerJointAttachment *attachment = cb_data->mAttachmentPoint; - - U8 attachment_id = 0; - if (attachment) - { - for (LLVOAvatar::attachment_map_t::const_iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); - iter != gAgentAvatarp->mAttachmentPoints.end(); ++iter) - { - if (iter->second == attachment) - { - attachment_id = iter->first; - break; - } - } - } - else - { - // interpret 0 as "default location" - attachment_id = 0; - } - LLSelectMgr::getInstance()->sendAttach(cb_data->getSelection(), attachment_id, cb_data->mReplace); - } - LLObjectAttachToAvatar::setObjectSelection(NULL); - - delete cb_data; -} - -// static -void LLObjectAttachToAvatar::confirmReplaceAttachment(S32 option, LLViewerJointAttachment* attachment_point) -{ - if (option == 0/*YES*/) - { - LLViewerObject* selectedObject = LLSelectMgr::getInstance()->getSelection()->getFirstRootObject(); - if (selectedObject) - { - const F32 MIN_STOP_DISTANCE = 1.f; // meters - const F32 ARM_LENGTH = 0.5f; // meters - const F32 SCALE_FUDGE = 1.5f; - - F32 stop_distance = SCALE_FUDGE * selectedObject->getMaxScale() + ARM_LENGTH; - if (stop_distance < MIN_STOP_DISTANCE) - { - stop_distance = MIN_STOP_DISTANCE; - } - - LLVector3 walkToSpot = selectedObject->getPositionAgent(); - - // make sure we stop in front of the object - LLVector3 delta = walkToSpot - gAgent.getPositionAgent(); - delta.normVec(); - delta = delta * 0.5f; - walkToSpot -= delta; - - // The callback will be called even if avatar fails to get close enough to the object, so we won't get a memory leak. - CallbackData* user_data = new CallbackData(attachment_point, mReplace); - gAgent.startAutoPilotGlobal(gAgent.getPosGlobalFromAgent(walkToSpot), "Attach", NULL, onNearAttachObject, user_data, stop_distance); - gAgentCamera.clearFocusObject(); - } - } -} - -void callback_attachment_drop(const LLSD& notification, const LLSD& response) -{ - // Ensure user confirmed the drop - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option != 0) return; - - // Called when the user clicked on an object attached to them - // and selected "Drop". - LLUUID object_id = notification["payload"]["object_id"].asUUID(); - LLViewerObject *object = gObjectList.findObject(object_id); - - if (!object) - { - LL_WARNS() << "handle_drop_attachment() - no object to drop" << LL_ENDL; - return; - } - - LLViewerObject *parent = (LLViewerObject*)object->getParent(); - while (parent) - { - if(parent->isAvatar()) - { - break; - } - object = parent; - parent = (LLViewerObject*)parent->getParent(); - } - - if (!object) - { - LL_WARNS() << "handle_detach() - no object to detach" << LL_ENDL; - return; - } - - if (object->isAvatar()) - { - LL_WARNS() << "Trying to detach avatar from avatar." << LL_ENDL; - return; - } - - // reselect the object - LLSelectMgr::getInstance()->selectObjectAndFamily(object); - - LLSelectMgr::getInstance()->sendDropAttachment(); - - return; -} - -class LLAttachmentDrop : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLSD payload; - LLViewerObject *object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); - - if (object) - { - payload["object_id"] = object->getID(); - } - else - { - LL_WARNS() << "Drop object not found" << LL_ENDL; - return true; - } - - LLNotificationsUtil::add("AttachmentDrop", LLSD(), payload, &callback_attachment_drop); - return true; - } -}; - -// called from avatar pie menu -class LLAttachmentDetachFromPoint : public view_listener_t -{ - bool handleEvent(const LLSD& user_data) - { - uuid_vec_t ids_to_remove; - const LLViewerJointAttachment *attachment = get_if_there(gAgentAvatarp->mAttachmentPoints, user_data.asInteger(), (LLViewerJointAttachment*)NULL); - if (attachment->getNumObjects() > 0) - { - for (LLViewerJointAttachment::attachedobjs_vec_t::const_iterator iter = attachment->mAttachedObjects.begin(); - iter != attachment->mAttachedObjects.end(); - iter++) - { - LLViewerObject *attached_object = iter->get(); - ids_to_remove.push_back(attached_object->getAttachmentItemID()); - } - } - if (!ids_to_remove.empty()) - { - LLAppearanceMgr::instance().removeItemsFromAvatar(ids_to_remove); - } - return true; - } -}; - -static bool onEnableAttachmentLabel(LLUICtrl* ctrl, const LLSD& data) -{ - std::string label; - LLMenuItemGL* menu = dynamic_cast(ctrl); - if (menu) - { - const LLViewerJointAttachment *attachment = get_if_there(gAgentAvatarp->mAttachmentPoints, data["index"].asInteger(), (LLViewerJointAttachment*)NULL); - if (attachment) - { - label = data["label"].asString(); - for (LLViewerJointAttachment::attachedobjs_vec_t::const_iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - const LLViewerObject* attached_object = attachment_iter->get(); - if (attached_object) - { - LLViewerInventoryItem* itemp = gInventory.getItem(attached_object->getAttachmentItemID()); - if (itemp) - { - label += std::string(" (") + itemp->getName() + std::string(")"); - break; - } - } - } - } - menu->setLabel(label); - } - return true; -} - -class LLAttachmentDetach : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - // Called when the user clicked on an object attached to them - // and selected "Detach". - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - LLViewerObject *object = selection->getPrimaryObject(); - if (!object) - { - LL_WARNS() << "handle_detach() - no object to detach" << LL_ENDL; - return true; - } - - struct f: public LLSelectedObjectFunctor - { - f() : mAvatarsInSelection(false) {} - virtual bool apply(LLViewerObject* objectp) - { - if (!objectp) - { - return false; - } - - if (objectp->isAvatar()) - { - mAvatarsInSelection = true; - return false; - } - - LLViewerObject* parent = (LLViewerObject*)objectp->getParent(); - while (parent) - { - if(parent->isAvatar()) - { - break; - } - objectp = parent; - parent = (LLViewerObject*)parent->getParent(); - } - - // std::set to avoid dupplicate 'roots' from linksets - mRemoveSet.insert(objectp->getAttachmentItemID()); - - return true; - } - bool mAvatarsInSelection; - uuid_set_t mRemoveSet; - } func; - // Probbly can run applyToRootObjects instead, - // but previous version of this code worked for any selected object - selection->applyToObjects(&func); - - if (func.mAvatarsInSelection) - { - // Not possible under normal circumstances - // Either avatar selection is ON or has to do with animeshes - // Better stop this than mess something - LL_WARNS() << "Trying to detach avatar from avatar." << LL_ENDL; - return true; - } - - if (func.mRemoveSet.empty()) - { - LL_WARNS() << "handle_detach() - no valid attachments in selection to detach" << LL_ENDL; - return true; - } - - uuid_vec_t detach_list(func.mRemoveSet.begin(), func.mRemoveSet.end()); - LLAppearanceMgr::instance().removeItemsFromAvatar(detach_list); - - return true; - } -}; - -//Adding an observer for a Jira 2422 and needs to be a fetch observer -//for Jira 3119 -class LLWornItemFetchedObserver : public LLInventoryFetchItemsObserver -{ -public: - LLWornItemFetchedObserver(const LLUUID& worn_item_id) : - LLInventoryFetchItemsObserver(worn_item_id) - {} - virtual ~LLWornItemFetchedObserver() {} - -protected: - virtual void done() - { - gMenuAttachmentSelf->buildDrawLabels(); - gInventory.removeObserver(this); - delete this; - } -}; - -// You can only drop items on parcels where you can build. -class LLAttachmentEnableDrop : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool can_build = gAgent.isGodlike() || (LLViewerParcelMgr::getInstance()->allowAgentBuild()); - - //Add an inventory observer to only allow dropping the newly attached item - //once it exists in your inventory. Look at Jira 2422. - //-jwolk - - // A bug occurs when you wear/drop an item before it actively is added to your inventory - // if this is the case (you're on a slow sim, etc.) a copy of the object, - // well, a newly created object with the same properties, is placed - // in your inventory. Therefore, we disable the drop option until the - // item is in your inventory - - LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); - LLViewerJointAttachment* attachment = NULL; - LLInventoryItem* item = NULL; - - // Do not enable drop if all faces of object are not enabled - if (object && LLSelectMgr::getInstance()->getSelection()->contains(object,SELECT_ALL_TES )) - { - S32 attachmentID = ATTACHMENT_ID_FROM_STATE(object->getAttachmentState()); - attachment = get_if_there(gAgentAvatarp->mAttachmentPoints, attachmentID, (LLViewerJointAttachment*)NULL); - - if (attachment) - { - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - // make sure item is in your inventory (it could be a delayed attach message being sent from the sim) - // so check to see if the item is in the inventory already - item = gInventory.getItem(attachment_iter->get()->getAttachmentItemID()); - if (!item) - { - // Item does not exist, make an observer to enable the pie menu - // when the item finishes fetching worst case scenario - // if a fetch is already out there (being sent from a slow sim) - // we refetch and there are 2 fetches - LLWornItemFetchedObserver* worn_item_fetched = new LLWornItemFetchedObserver((*attachment_iter)->getAttachmentItemID()); - worn_item_fetched->startFetch(); - gInventory.addObserver(worn_item_fetched); - } - } - } - } - - //now check to make sure that the item is actually in the inventory before we enable dropping it - bool new_value = enable_detach() && can_build && item; - - return new_value; - } -}; - -bool enable_detach(const LLSD&) -{ - LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); - - // Only enable detach if all faces of object are selected - if (!object || - !object->isAttachment() || - !LLSelectMgr::getInstance()->getSelection()->contains(object,SELECT_ALL_TES )) - { - return false; - } - - // Find the avatar who owns this attachment - LLViewerObject* avatar = object; - while (avatar) - { - // ...if it's you, good to detach - if (avatar->getID() == gAgent.getID()) - { - return true; - } - - avatar = (LLViewerObject*)avatar->getParent(); - } - - return false; -} - -class LLAttachmentEnableDetach : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = enable_detach(); - return new_value; - } -}; - -// Used to tell if the selected object can be attached to your avatar. -bool object_selected_and_point_valid() -{ - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - for (LLObjectSelection::root_iterator iter = selection->root_begin(); - iter != selection->root_end(); iter++) - { - LLSelectNode* node = *iter; - LLViewerObject* object = node->getObject(); - LLViewerObject::const_child_list_t& child_list = object->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); iter++) - { - LLViewerObject* child = *iter; - if (child->isAvatar()) - { - return false; - } - } - } - - return (selection->getRootObjectCount() == 1) && - (selection->getFirstRootObject()->getPCode() == LL_PCODE_VOLUME) && - selection->getFirstRootObject()->permYouOwner() && - selection->getFirstRootObject()->flagObjectMove() && - !selection->getFirstRootObject()->flagObjectPermanent() && - !((LLViewerObject*)selection->getFirstRootObject()->getRoot())->isAvatar() && - (selection->getFirstRootObject()->getNVPair("AssetContainer") == NULL); -} - - -bool object_is_wearable() -{ - if (!isAgentAvatarValid()) - { - return false; - } - if (!object_selected_and_point_valid()) - { - return false; - } - if (sitting_on_selection()) - { - return false; - } - if (!gAgentAvatarp->canAttachMoreObjects()) - { - return false; - } - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - for (LLObjectSelection::valid_root_iterator iter = LLSelectMgr::getInstance()->getSelection()->valid_root_begin(); - iter != LLSelectMgr::getInstance()->getSelection()->valid_root_end(); iter++) - { - LLSelectNode* node = *iter; - if (node->mPermissions->getOwner() == gAgent.getID()) - { - return true; - } - } - return false; -} - - -class LLAttachmentPointFilled : public view_listener_t -{ - bool handleEvent(const LLSD& user_data) - { - bool enable = false; - LLVOAvatar::attachment_map_t::iterator found_it = gAgentAvatarp->mAttachmentPoints.find(user_data.asInteger()); - if (found_it != gAgentAvatarp->mAttachmentPoints.end()) - { - enable = found_it->second->getNumObjects() > 0; - } - return enable; - } -}; - -class LLAvatarSendIM : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLVOAvatar* avatar = find_avatar_from_object( LLSelectMgr::getInstance()->getSelection()->getPrimaryObject() ); - if(avatar) - { - LLAvatarActions::startIM(avatar->getID()); - } - return true; - } -}; - -class LLAvatarCall : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLVOAvatar* avatar = find_avatar_from_object( LLSelectMgr::getInstance()->getSelection()->getPrimaryObject() ); - if(avatar) - { - LLAvatarActions::startCall(avatar->getID()); - } - return true; - } -}; - -namespace -{ - struct QueueObjects : public LLSelectedNodeFunctor - { - bool scripted; - bool modifiable; - LLFloaterScriptQueue* mQueue; - QueueObjects(LLFloaterScriptQueue* q) : mQueue(q), scripted(false), modifiable(false) {} - virtual bool apply(LLSelectNode* node) - { - LLViewerObject* obj = node->getObject(); - if (!obj) - { - return true; - } - scripted = obj->flagScripted(); - modifiable = obj->permModify(); - - if( scripted && modifiable ) - { - mQueue->addObject(obj->getID(), node->mName); - return false; - } - else - { - return true; // fail: stop applying - } - } - }; -} - -bool queue_actions(LLFloaterScriptQueue* q, const std::string& msg) -{ - QueueObjects func(q); - LLSelectMgr *mgr = LLSelectMgr::getInstance(); - LLObjectSelectionHandle selectHandle = mgr->getSelection(); - bool fail = selectHandle->applyToNodes(&func); - if(fail) - { - if ( !func.scripted ) - { - std::string noscriptmsg = std::string("Cannot") + msg + "SelectObjectsNoScripts"; - LLNotificationsUtil::add(noscriptmsg); - } - else if ( !func.modifiable ) - { - std::string nomodmsg = std::string("Cannot") + msg + "SelectObjectsNoPermission"; - LLNotificationsUtil::add(nomodmsg); - } - else - { - LL_ERRS() << "Bad logic." << LL_ENDL; - } - q->closeFloater(); - } - else - { - if (!q->start()) - { - LL_WARNS() << "Unexpected script compile failure." << LL_ENDL; - } - } - return !fail; -} - -class LLToolsSelectedScriptAction : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string action = userdata.asString(); - bool mono = false; - std::string msg, name; - std::string title; - if (action == "compile mono") - { - name = "compile_queue"; - mono = true; - msg = "Recompile"; - title = LLTrans::getString("CompileQueueTitle"); - } - if (action == "compile lsl") - { - name = "compile_queue"; - msg = "Recompile"; - title = LLTrans::getString("CompileQueueTitle"); - } - else if (action == "reset") - { - name = "reset_queue"; - msg = "Reset"; - title = LLTrans::getString("ResetQueueTitle"); - } - else if (action == "start") - { - name = "start_queue"; - msg = "SetRunning"; - title = LLTrans::getString("RunQueueTitle"); - } - else if (action == "stop") - { - name = "stop_queue"; - msg = "SetRunningNot"; - title = LLTrans::getString("NotRunQueueTitle"); - } - LLUUID id; id.generate(); - - LLFloaterScriptQueue* queue =LLFloaterReg::getTypedInstance(name, LLSD(id)); - if (queue) - { - queue->setMono(mono); - if (queue_actions(queue, msg)) - { - queue->setTitle(title); - } - } - else - { - LL_WARNS() << "Failed to generate LLFloaterScriptQueue with action: " << action << LL_ENDL; - } - return true; - } -}; - -void handle_selected_texture_info(void*) -{ - for (LLObjectSelection::valid_iterator iter = LLSelectMgr::getInstance()->getSelection()->valid_begin(); - iter != LLSelectMgr::getInstance()->getSelection()->valid_end(); iter++) - { - LLSelectNode* node = *iter; - - std::string msg; - msg.assign("Texture info for: "); - msg.append(node->mName); - - U8 te_count = node->getObject()->getNumTEs(); - // map from texture ID to list of faces using it - typedef std::map< LLUUID, std::vector > map_t; - map_t faces_per_texture; - for (U8 i = 0; i < te_count; i++) - { - if (!node->isTESelected(i)) continue; - - LLViewerTexture* img = node->getObject()->getTEImage(i); - LLUUID image_id = img->getID(); - faces_per_texture[image_id].push_back(i); - } - // Per-texture, dump which faces are using it. - map_t::iterator it; - for (it = faces_per_texture.begin(); it != faces_per_texture.end(); ++it) - { - U8 te = it->second[0]; - LLViewerTexture* img = node->getObject()->getTEImage(te); - S32 height = img->getHeight(); - S32 width = img->getWidth(); - S32 components = img->getComponents(); - msg.append(llformat("\n%dx%d %s on face ", - width, - height, - (components == 4 ? "alpha" : "opaque"))); - for (U8 i = 0; i < it->second.size(); ++i) - { - msg.append( llformat("%d ", (S32)(it->second[i]))); - } - } - LLSD args; - args["MESSAGE"] = msg; - LLNotificationsUtil::add("SystemMessage", args); - } -} - -void handle_selected_material_info() -{ - for (LLObjectSelection::valid_iterator iter = LLSelectMgr::getInstance()->getSelection()->valid_begin(); - iter != LLSelectMgr::getInstance()->getSelection()->valid_end(); iter++) - { - LLSelectNode* node = *iter; - - std::string msg; - msg.assign("Material info for: \n"); - msg.append(node->mName); - - U8 te_count = node->getObject()->getNumTEs(); - // map from material ID to list of faces using it - typedef std::map > map_t; - map_t faces_per_material; - for (U8 i = 0; i < te_count; i++) - { - if (!node->isTESelected(i)) continue; - - const LLMaterialID& material_id = node->getObject()->getTE(i)->getMaterialID(); - faces_per_material[material_id].push_back(i); - } - // Per-material, dump which faces are using it. - map_t::iterator it; - for (it = faces_per_material.begin(); it != faces_per_material.end(); ++it) - { - const LLMaterialID& material_id = it->first; - msg += llformat("%s on face ", material_id.asString().c_str()); - for (U8 i = 0; i < it->second.size(); ++i) - { - msg.append( llformat("%d ", (S32)(it->second[i]))); - } - msg.append("\n"); - } - - LLSD args; - args["MESSAGE"] = msg; - LLNotificationsUtil::add("SystemMessage", args); - } -} - -void handle_test_male(void*) -{ - LLAppearanceMgr::instance().wearOutfitByName("Male Shape & Outfit"); - //gGestureList.requestResetFromServer( true ); -} - -void handle_test_female(void*) -{ - LLAppearanceMgr::instance().wearOutfitByName("Female Shape & Outfit"); - //gGestureList.requestResetFromServer( false ); -} - -void handle_dump_attachments(void*) -{ - if(!isAgentAvatarValid()) return; - - for (LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); - iter != gAgentAvatarp->mAttachmentPoints.end(); ) - { - LLVOAvatar::attachment_map_t::iterator curiter = iter++; - LLViewerJointAttachment* attachment = curiter->second; - S32 key = curiter->first; - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - LLViewerObject *attached_object = attachment_iter->get(); - bool visible = (attached_object != NULL && - attached_object->mDrawable.notNull() && - !attached_object->mDrawable->isRenderType(0)); - LLVector3 pos; - if (visible) pos = attached_object->mDrawable->getPosition(); - LL_INFOS() << "ATTACHMENT " << key << ": item_id=" << attached_object->getAttachmentItemID() - << (attached_object ? " present " : " absent ") - << (visible ? "visible " : "invisible ") - << " at " << pos - << " and " << (visible ? attached_object->getPosition() : LLVector3::zero) - << LL_ENDL; - } - } -} - - -// these are used in the gl menus to set control values, generically. -class LLToggleControl : public view_listener_t -{ -protected: - - bool handleEvent(const LLSD& userdata) - { - std::string control_name = userdata.asString(); - bool checked = gSavedSettings.getBOOL( control_name ); - gSavedSettings.setBOOL( control_name, !checked ); - return true; - } -}; - -class LLCheckControl : public view_listener_t -{ - bool handleEvent( const LLSD& userdata) - { - std::string callback_data = userdata.asString(); - bool new_value = gSavedSettings.getBOOL(callback_data); - return new_value; - } -}; - -// not so generic - -class LLAdvancedCheckRenderShadowOption: public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string control_name = userdata.asString(); - S32 current_shadow_level = gSavedSettings.getS32(control_name); - if (current_shadow_level == 0) // is off - { - return false; - } - else // is on - { - return true; - } - } -}; - -class LLAdvancedClickRenderShadowOption: public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string control_name = userdata.asString(); - S32 current_shadow_level = gSavedSettings.getS32(control_name); - if (current_shadow_level == 0) // upgrade to level 2 - { - gSavedSettings.setS32(control_name, 2); - } - else // downgrade to level 0 - { - gSavedSettings.setS32(control_name, 0); - } - return true; - } -}; - -class LLAdvancedClickRenderProfile: public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - gShaderProfileFrame = true; - return true; - } -}; - -F32 gpu_benchmark(); - -class LLAdvancedClickRenderBenchmark: public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - gpu_benchmark(); - return true; - } -}; - -// these are used in the gl menus to set control values that require shader recompilation -class LLToggleShaderControl : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string control_name = userdata.asString(); - bool checked = gSavedSettings.getBOOL( control_name ); - gSavedSettings.setBOOL( control_name, !checked ); - LLPipeline::refreshCachedSettings(); - LLViewerShaderMgr::instance()->setShaders(); - return !checked; - } -}; - -void menu_toggle_attached_lights(void* user_data) -{ - LLPipeline::sRenderAttachedLights = gSavedSettings.getBOOL("RenderAttachedLights"); -} - -void menu_toggle_attached_particles(void* user_data) -{ - LLPipeline::sRenderAttachedParticles = gSavedSettings.getBOOL("RenderAttachedParticles"); -} - -class LLAdvancedHandleAttachedLightParticles: public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string control_name = userdata.asString(); - - // toggle the control - gSavedSettings.setBOOL(control_name, - !gSavedSettings.getBOOL(control_name)); - - // update internal flags - if (control_name == "RenderAttachedLights") - { - menu_toggle_attached_lights(NULL); - } - else if (control_name == "RenderAttachedParticles") - { - menu_toggle_attached_particles(NULL); - } - return true; - } -}; - -class LLSomethingSelected : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = !(LLSelectMgr::getInstance()->getSelection()->isEmpty()); - return new_value; - } -}; - -class LLSomethingSelectedNoHUD : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - bool new_value = !(selection->isEmpty()) && !(selection->getSelectType() == SELECT_TYPE_HUD); - return new_value; - } -}; - -static bool is_editable_selected() -{ - return (LLSelectMgr::getInstance()->getSelection()->getFirstEditableObject() != NULL); -} - -class LLEditableSelected : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - return is_editable_selected(); - } -}; - -class LLEditableSelectedMono : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = false; - LLViewerRegion* region = gAgent.getRegion(); - if(region && gMenuHolder) - { - bool have_cap = (! region->getCapability("UpdateScriptTask").empty()); - new_value = is_editable_selected() && have_cap; - } - return new_value; - } -}; - -bool enable_object_take_copy() -{ - bool all_valid = false; - if (LLSelectMgr::getInstance()) - { - if (LLSelectMgr::getInstance()->getSelection()->getRootObjectCount() > 0) - { - all_valid = true; -#ifndef HACKED_GODLIKE_VIEWER -# ifdef TOGGLE_HACKED_GODLIKE_VIEWER - if (LLGridManager::getInstance()->isInProductionGrid() - || !gAgent.isGodlike()) -# endif - { - struct f : public LLSelectedObjectFunctor - { - virtual bool apply(LLViewerObject* obj) - { - return (!obj->permCopy() || obj->isAttachment()); - } - } func; - const bool firstonly = true; - bool any_invalid = LLSelectMgr::getInstance()->getSelection()->applyToRootObjects(&func, firstonly); - all_valid = !any_invalid; - } -#endif // HACKED_GODLIKE_VIEWER - } - } - - return all_valid; -} - -bool enable_take_copy_objects() -{ - return enable_object_take_copy() && is_multiple_selection(); -} - -class LLHasAsset : public LLInventoryCollectFunctor -{ -public: - LLHasAsset(const LLUUID& id) : mAssetID(id), mHasAsset(false) {} - virtual ~LLHasAsset() {} - virtual bool operator()(LLInventoryCategory* cat, - LLInventoryItem* item); - bool hasAsset() const { return mHasAsset; } - -protected: - LLUUID mAssetID; - bool mHasAsset; -}; - -bool LLHasAsset::operator()(LLInventoryCategory* cat, - LLInventoryItem* item) -{ - if(item && item->getAssetUUID() == mAssetID) - { - mHasAsset = true; - } - return false; -} - - -bool enable_save_into_task_inventory(void*) -{ - LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(); - if(node && (node->mValid) && (!node->mFromTaskID.isNull())) - { - // *TODO: check to see if the fromtaskid object exists. - LLViewerObject* obj = node->getObject(); - if( obj && !obj->isAttachment() ) - { - return true; - } - } - return false; -} - -class LLToolsEnableSaveToObjectInventory : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = enable_save_into_task_inventory(NULL); - return new_value; - } -}; - -class LLToggleHowTo : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLFloaterReg::toggleInstanceOrBringToFront("guidebook"); - return true; - } -}; - -class LLViewEnableMouselook : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - // You can't go directly from customize avatar to mouselook. - // TODO: write code with appropriate dialogs to handle this transition. - bool new_value = (CAMERA_MODE_CUSTOMIZE_AVATAR != gAgentCamera.getCameraMode() && !gSavedSettings.getBOOL("FreezeTime")); - return new_value; - } -}; - -class LLToolsEnableToolNotPie : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = ( LLToolMgr::getInstance()->getBaseTool() != LLToolPie::getInstance() ); - return new_value; - } -}; - -class LLWorldEnableCreateLandmark : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - return !LLLandmarkActions::landmarkAlreadyExists(); - } -}; - -class LLWorldEnableSetHomeLocation : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = gAgent.isGodlike() || - (gAgent.getRegion() && gAgent.getRegion()->getAllowSetHome()); - return new_value; - } -}; - -class LLWorldEnableTeleportHome : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLViewerRegion* regionp = gAgent.getRegion(); - bool agent_on_prelude = (regionp && regionp->isPrelude()); - bool enable_teleport_home = gAgent.isGodlike() || !agent_on_prelude; - return enable_teleport_home; - } -}; - -bool enable_god_full(void*) -{ - return gAgent.getGodLevel() >= GOD_FULL; -} - -bool enable_god_liaison(void*) -{ - return gAgent.getGodLevel() >= GOD_LIAISON; -} - -bool is_god_customer_service() -{ - return gAgent.getGodLevel() >= GOD_CUSTOMER_SERVICE; -} - -bool enable_god_basic(void*) -{ - return gAgent.getGodLevel() > GOD_NOT; -} - - -void toggle_show_xui_names(void *) -{ - gSavedSettings.setBOOL("DebugShowXUINames", !gSavedSettings.getBOOL("DebugShowXUINames")); -} - -bool check_show_xui_names(void *) -{ - return gSavedSettings.getBOOL("DebugShowXUINames"); -} - -class LLToolsSelectOnlyMyObjects : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool cur_val = gSavedSettings.getBOOL("SelectOwnedOnly"); - - gSavedSettings.setBOOL("SelectOwnedOnly", ! cur_val ); - - return true; - } -}; - -class LLToolsSelectOnlyMovableObjects : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool cur_val = gSavedSettings.getBOOL("SelectMovableOnly"); - - gSavedSettings.setBOOL("SelectMovableOnly", ! cur_val ); - - return true; - } -}; - -class LLToolsSelectInvisibleObjects : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool cur_val = gSavedSettings.getBOOL("SelectInvisibleObjects"); - - gSavedSettings.setBOOL("SelectInvisibleObjects", !cur_val); - - return true; - } -}; - -class LLToolsSelectReflectionProbes: public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool cur_val = gSavedSettings.getBOOL("SelectReflectionProbes"); - - gSavedSettings.setBOOL("SelectReflectionProbes", !cur_val); - - return true; - } -}; - -class LLToolsSelectBySurrounding : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLSelectMgr::sRectSelectInclusive = !LLSelectMgr::sRectSelectInclusive; - - gSavedSettings.setBOOL("RectangleSelectInclusive", LLSelectMgr::sRectSelectInclusive); - return true; - } -}; - -class LLToolsShowHiddenSelection : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - // TomY TODO Merge these - LLSelectMgr::sRenderHiddenSelections = !LLSelectMgr::sRenderHiddenSelections; - - gSavedSettings.setBOOL("RenderHiddenSelections", LLSelectMgr::sRenderHiddenSelections); - return true; - } -}; - -class LLToolsShowSelectionLightRadius : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - // TomY TODO merge these - LLSelectMgr::sRenderLightRadius = !LLSelectMgr::sRenderLightRadius; - - gSavedSettings.setBOOL("RenderLightRadius", LLSelectMgr::sRenderLightRadius); - return true; - } -}; - -class LLToolsEditLinkedParts : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool select_individuals = !gSavedSettings.getBOOL("EditLinkedParts"); - gSavedSettings.setBOOL( "EditLinkedParts", select_individuals ); - if (select_individuals) - { - LLSelectMgr::getInstance()->demoteSelectionToIndividuals(); - } - else - { - LLSelectMgr::getInstance()->promoteSelectionToRoot(); - } - return true; - } -}; - -void reload_vertex_shader(void *) -{ - //THIS WOULD BE AN AWESOME PLACE TO RELOAD SHADERS... just a thought - DaveP -} - -void handle_dump_avatar_local_textures(void*) -{ - gAgentAvatarp->dumpLocalTextures(); -} - -void handle_dump_timers() -{ - LLTrace::BlockTimer::dumpCurTimes(); -} - -void handle_debug_avatar_textures(void*) -{ - LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); - if (objectp) - { - LLFloaterReg::showInstance( "avatar_textures", LLSD(objectp->getID()) ); - } -} - -void handle_grab_baked_texture(void* data) -{ - EBakedTextureIndex baked_tex_index = (EBakedTextureIndex)((intptr_t)data); - if (!isAgentAvatarValid()) return; - - const LLUUID& asset_id = gAgentAvatarp->grabBakedTexture(baked_tex_index); - LL_INFOS("texture") << "Adding baked texture " << asset_id << " to inventory." << LL_ENDL; - LLAssetType::EType asset_type = LLAssetType::AT_TEXTURE; - LLInventoryType::EType inv_type = LLInventoryType::IT_TEXTURE; - const LLUUID folder_id = gInventory.findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(asset_type)); - if(folder_id.notNull()) - { - std::string name; - name = "Baked " + LLAvatarAppearance::getDictionary()->getBakedTexture(baked_tex_index)->mNameCapitalized + " Texture"; - - LLUUID item_id; - item_id.generate(); - LLPermissions perm; - perm.init(gAgentID, - gAgentID, - LLUUID::null, - LLUUID::null); - U32 next_owner_perm = PERM_MOVE | PERM_TRANSFER; - perm.initMasks(PERM_ALL, - PERM_ALL, - PERM_NONE, - PERM_NONE, - next_owner_perm); - time_t creation_date_now = time_corrected(); - LLPointer item - = new LLViewerInventoryItem(item_id, - folder_id, - perm, - asset_id, - asset_type, - inv_type, - name, - LLStringUtil::null, - LLSaleInfo::DEFAULT, - LLInventoryItemFlags::II_FLAGS_NONE, - creation_date_now); - - item->updateServer(true); - gInventory.updateItem(item); - gInventory.notifyObservers(); - - // Show the preview panel for textures to let - // user know that the image is now in inventory. - LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(); - if(active_panel) - { - LLFocusableElement* focus_ctrl = gFocusMgr.getKeyboardFocus(); - - active_panel->setSelection(item_id, TAKE_FOCUS_NO); - active_panel->openSelected(); - //LLFloaterInventory::dumpSelectionInformation((void*)view); - // restore keyboard focus - gFocusMgr.setKeyboardFocus(focus_ctrl); - } - } - else - { - LL_WARNS() << "Can't find a folder to put it in" << LL_ENDL; - } -} - -bool enable_grab_baked_texture(void* data) -{ - EBakedTextureIndex index = (EBakedTextureIndex)((intptr_t)data); - if (isAgentAvatarValid()) - { - return gAgentAvatarp->canGrabBakedTexture(index); - } - return false; -} - -// Returns a pointer to the avatar give the UUID of the avatar OR of an attachment the avatar is wearing. -// Returns NULL on failure. -LLVOAvatar* find_avatar_from_object( LLViewerObject* object ) -{ - if (object) - { - if( object->isAttachment() ) - { - do - { - object = (LLViewerObject*) object->getParent(); - } - while( object && !object->isAvatar() ); - } - else if( !object->isAvatar() ) - { - object = NULL; - } - } - - return (LLVOAvatar*) object; -} - - -// Returns a pointer to the avatar give the UUID of the avatar OR of an attachment the avatar is wearing. -// Returns NULL on failure. -LLVOAvatar* find_avatar_from_object( const LLUUID& object_id ) -{ - return find_avatar_from_object( gObjectList.findObject(object_id) ); -} - - -void handle_disconnect_viewer(void *) -{ - LLAppViewer::instance()->forceDisconnect(LLTrans::getString("TestingDisconnect")); -} - -void force_error_breakpoint(void *) -{ - LLAppViewer::instance()->forceErrorBreakpoint(); -} - -void force_error_llerror(void *) -{ - LLAppViewer::instance()->forceErrorLLError(); -} - -void force_error_llerror_msg(void*) -{ - LLAppViewer::instance()->forceErrorLLErrorMsg(); -} - -void force_error_bad_memory_access(void *) -{ - LLAppViewer::instance()->forceErrorBadMemoryAccess(); -} - -void force_error_infinite_loop(void *) -{ - LLAppViewer::instance()->forceErrorInfiniteLoop(); -} - -void force_error_software_exception(void *) -{ - LLAppViewer::instance()->forceErrorSoftwareException(); -} - -void force_error_os_exception(void*) -{ - LLAppViewer::instance()->forceErrorOSSpecificException(); -} - -void force_error_driver_crash(void *) -{ - LLAppViewer::instance()->forceErrorDriverCrash(); -} - -void force_error_coroutine_crash(void *) -{ - LLAppViewer::instance()->forceErrorCoroutineCrash(); -} - -void force_error_thread_crash(void *) -{ - LLAppViewer::instance()->forceErrorThreadCrash(); -} - -class LLToolsUseSelectionForGrid : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLSelectMgr::getInstance()->clearGridObjects(); - struct f : public LLSelectedObjectFunctor - { - virtual bool apply(LLViewerObject* objectp) - { - LLSelectMgr::getInstance()->addGridObject(objectp); - return true; - } - } func; - LLSelectMgr::getInstance()->getSelection()->applyToRootObjects(&func); - LLSelectMgr::getInstance()->setGridMode(GRID_MODE_REF_OBJECT); - LLFloaterTools::setGridMode((S32)GRID_MODE_REF_OBJECT); - return true; - } -}; - -void handle_test_load_url(void*) -{ - LLWeb::loadURL(""); - LLWeb::loadURL("hacker://www.google.com/"); - LLWeb::loadURL("http"); - LLWeb::loadURL("http://www.google.com/"); -} - -// -// LLViewerMenuHolderGL -// -static LLDefaultChildRegistry::Register r("menu_holder"); - -LLViewerMenuHolderGL::LLViewerMenuHolderGL(const LLViewerMenuHolderGL::Params& p) -: LLMenuHolderGL(p) -{} - -bool LLViewerMenuHolderGL::hideMenus() -{ - bool handled = false; - - if (LLMenuHolderGL::hideMenus()) - { - handled = true; - } - - // drop pie menu selection - mParcelSelection = nullptr; - mObjectSelection = nullptr; - - if (gMenuBarView) - { - gMenuBarView->clearHoverItem(); - gMenuBarView->resetMenuTrigger(); - } - - return handled; -} - -void LLViewerMenuHolderGL::setParcelSelection(LLSafeHandle selection) -{ - mParcelSelection = selection; -} - -void LLViewerMenuHolderGL::setObjectSelection(LLSafeHandle selection) -{ - mObjectSelection = selection; -} - - -const LLRect LLViewerMenuHolderGL::getMenuRect() const -{ - return LLRect(0, getRect().getHeight() - MENU_BAR_HEIGHT, getRect().getWidth(), STATUS_BAR_HEIGHT); -} - -void handle_web_browser_test(const LLSD& param) -{ - std::string url = param.asString(); - if (url.empty()) - { - url = "about:blank"; - } - LLWeb::loadURLInternal(url); -} - -bool callback_clear_cache_immediately(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if ( option == 0 ) // YES - { - //clear cache - LLAppViewer::instance()->purgeCacheImmediate(); - } - - return false; -} - -void handle_cache_clear_immediately() -{ - LLNotificationsUtil::add("ConfirmClearCache", LLSD(), LLSD(), callback_clear_cache_immediately); -} - -void handle_web_content_test(const LLSD& param) -{ - std::string url = param.asString(); - LLWeb::loadURLInternal(url, LLStringUtil::null, LLStringUtil::null, true); -} - -void handle_show_url(const LLSD& param) -{ - std::string url = param.asString(); - if (LLWeb::useExternalBrowser(url)) - { - LLWeb::loadURLExternal(url); - } - else - { - LLWeb::loadURLInternal(url); - } - -} - -void handle_report_bug(const LLSD& param) -{ - std::string url = gSavedSettings.getString("ReportBugURL"); - LLWeb::loadURLExternal(url); -} - -void handle_buy_currency_test(void*) -{ - std::string url = - "http://sarahd-sl-13041.webdev.lindenlab.com/app/lindex/index.php?agent_id=[AGENT_ID]&secure_session_id=[SESSION_ID]&lang=[LANGUAGE]"; - - LLStringUtil::format_map_t replace; - replace["[AGENT_ID]"] = gAgent.getID().asString(); - replace["[SESSION_ID]"] = gAgent.getSecureSessionID().asString(); - replace["[LANGUAGE]"] = LLUI::getLanguage(); - LLStringUtil::format(url, replace); - - LL_INFOS() << "buy currency url " << url << LL_ENDL; - - LLFloaterReg::showInstance("buy_currency_html", LLSD(url)); -} - -// SUNSHINE CLEANUP - is only the request update at the end needed now? -void handle_rebake_textures(void*) -{ - if (!isAgentAvatarValid()) return; - - // Slam pending upload count to "unstick" things - bool slam_for_debug = true; - gAgentAvatarp->forceBakeAllTextures(slam_for_debug); - if (gAgent.getRegion() && gAgent.getRegion()->getCentralBakeVersion()) - { - LLAppearanceMgr::instance().requestServerAppearanceUpdate(); - } -} - -void toggle_visibility(void* user_data) -{ - LLView* viewp = (LLView*)user_data; - viewp->setVisible(!viewp->getVisible()); -} - -bool get_visibility(void* user_data) -{ - LLView* viewp = (LLView*)user_data; - return viewp->getVisible(); -} - -class LLViewShowHoverTips : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - gSavedSettings.setBOOL("ShowHoverTips", !gSavedSettings.getBOOL("ShowHoverTips")); - return true; - } -}; - -class LLViewCheckShowHoverTips : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = gSavedSettings.getBOOL("ShowHoverTips"); - return new_value; - } -}; - -class LLViewHighlightTransparent : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLDrawPoolAlpha::sShowDebugAlpha = !LLDrawPoolAlpha::sShowDebugAlpha; - - // invisible objects skip building their render batches unless sShowDebugAlpha is true, so rebuild batches whenever toggling this flag - gPipeline.rebuildDrawInfo(); - return true; - } -}; - -class LLViewCheckHighlightTransparent : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = LLDrawPoolAlpha::sShowDebugAlpha; - return new_value; - } -}; - -class LLViewBeaconWidth : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string width = userdata.asString(); - if(width == "1") - { - gSavedSettings.setS32("DebugBeaconLineWidth", 1); - } - else if(width == "4") - { - gSavedSettings.setS32("DebugBeaconLineWidth", 4); - } - else if(width == "16") - { - gSavedSettings.setS32("DebugBeaconLineWidth", 16); - } - else if(width == "32") - { - gSavedSettings.setS32("DebugBeaconLineWidth", 32); - } - - return true; - } -}; - - -class LLViewToggleBeacon : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string beacon = userdata.asString(); - if (beacon == "scriptsbeacon") - { - LLPipeline::toggleRenderScriptedBeacons(); - gSavedSettings.setBOOL( "scriptsbeacon", LLPipeline::getRenderScriptedBeacons() ); - // toggle the other one off if it's on - if (LLPipeline::getRenderScriptedBeacons() && LLPipeline::getRenderScriptedTouchBeacons()) - { - LLPipeline::toggleRenderScriptedTouchBeacons(); - gSavedSettings.setBOOL( "scripttouchbeacon", LLPipeline::getRenderScriptedTouchBeacons() ); - } - } - else if (beacon == "physicalbeacon") - { - LLPipeline::toggleRenderPhysicalBeacons(); - gSavedSettings.setBOOL( "physicalbeacon", LLPipeline::getRenderPhysicalBeacons() ); - } - else if (beacon == "moapbeacon") - { - LLPipeline::toggleRenderMOAPBeacons(); - gSavedSettings.setBOOL( "moapbeacon", LLPipeline::getRenderMOAPBeacons() ); - } - else if (beacon == "soundsbeacon") - { - LLPipeline::toggleRenderSoundBeacons(); - gSavedSettings.setBOOL( "soundsbeacon", LLPipeline::getRenderSoundBeacons() ); - } - else if (beacon == "particlesbeacon") - { - LLPipeline::toggleRenderParticleBeacons(); - gSavedSettings.setBOOL( "particlesbeacon", LLPipeline::getRenderParticleBeacons() ); - } - else if (beacon == "scripttouchbeacon") - { - LLPipeline::toggleRenderScriptedTouchBeacons(); - gSavedSettings.setBOOL( "scripttouchbeacon", LLPipeline::getRenderScriptedTouchBeacons() ); - // toggle the other one off if it's on - if (LLPipeline::getRenderScriptedBeacons() && LLPipeline::getRenderScriptedTouchBeacons()) - { - LLPipeline::toggleRenderScriptedBeacons(); - gSavedSettings.setBOOL( "scriptsbeacon", LLPipeline::getRenderScriptedBeacons() ); - } - } - else if (beacon == "sunbeacon") - { - gSavedSettings.setBOOL("sunbeacon", !gSavedSettings.getBOOL("sunbeacon")); - } - else if (beacon == "moonbeacon") - { - gSavedSettings.setBOOL("moonbeacon", !gSavedSettings.getBOOL("moonbeacon")); - } - else if (beacon == "renderbeacons") - { - LLPipeline::toggleRenderBeacons(); - gSavedSettings.setBOOL( "renderbeacons", LLPipeline::getRenderBeacons() ); - // toggle the other one on if it's not - if (!LLPipeline::getRenderBeacons() && !LLPipeline::getRenderHighlights()) - { - LLPipeline::toggleRenderHighlights(); - gSavedSettings.setBOOL( "renderhighlights", LLPipeline::getRenderHighlights() ); - } - } - else if (beacon == "renderhighlights") - { - LLPipeline::toggleRenderHighlights(); - gSavedSettings.setBOOL( "renderhighlights", LLPipeline::getRenderHighlights() ); - // toggle the other one on if it's not - if (!LLPipeline::getRenderBeacons() && !LLPipeline::getRenderHighlights()) - { - LLPipeline::toggleRenderBeacons(); - gSavedSettings.setBOOL( "renderbeacons", LLPipeline::getRenderBeacons() ); - } - } - - return true; - } -}; - -class LLViewCheckBeaconEnabled : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string beacon = userdata.asString(); - bool new_value = false; - if (beacon == "scriptsbeacon") - { - new_value = gSavedSettings.getBOOL( "scriptsbeacon"); - LLPipeline::setRenderScriptedBeacons(new_value); - } - else if (beacon == "moapbeacon") - { - new_value = gSavedSettings.getBOOL( "moapbeacon"); - LLPipeline::setRenderMOAPBeacons(new_value); - } - else if (beacon == "physicalbeacon") - { - new_value = gSavedSettings.getBOOL( "physicalbeacon"); - LLPipeline::setRenderPhysicalBeacons(new_value); - } - else if (beacon == "soundsbeacon") - { - new_value = gSavedSettings.getBOOL( "soundsbeacon"); - LLPipeline::setRenderSoundBeacons(new_value); - } - else if (beacon == "particlesbeacon") - { - new_value = gSavedSettings.getBOOL( "particlesbeacon"); - LLPipeline::setRenderParticleBeacons(new_value); - } - else if (beacon == "scripttouchbeacon") - { - new_value = gSavedSettings.getBOOL( "scripttouchbeacon"); - LLPipeline::setRenderScriptedTouchBeacons(new_value); - } - else if (beacon == "renderbeacons") - { - new_value = gSavedSettings.getBOOL( "renderbeacons"); - LLPipeline::setRenderBeacons(new_value); - } - else if (beacon == "renderhighlights") - { - new_value = gSavedSettings.getBOOL( "renderhighlights"); - LLPipeline::setRenderHighlights(new_value); - } - return new_value; - } -}; - -class LLViewToggleRenderType : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string type = userdata.asString(); - if (type == "hideparticles") - { - LLPipeline::toggleRenderType(LLPipeline::RENDER_TYPE_PARTICLES); - } - return true; - } -}; - -class LLViewCheckRenderType : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string type = userdata.asString(); - bool new_value = false; - if (type == "hideparticles") - { - new_value = LLPipeline::toggleRenderTypeControlNegated(LLPipeline::RENDER_TYPE_PARTICLES); - } - return new_value; - } -}; - -class LLViewStatusAway : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - return (gAgent.isInitialized() && gAgent.getAFK()); - } -}; - -class LLViewStatusDoNotDisturb : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - return (gAgent.isInitialized() && gAgent.isDoNotDisturb()); - } -}; - -class LLViewShowHUDAttachments : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLPipeline::sShowHUDAttachments = !LLPipeline::sShowHUDAttachments; - return true; - } -}; - -class LLViewCheckHUDAttachments : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool new_value = LLPipeline::sShowHUDAttachments; - return new_value; - } -}; - -class LLEditEnableTakeOff : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string clothing = userdata.asString(); - LLWearableType::EType type = LLWearableType::getInstance()->typeNameToType(clothing); - if (type >= LLWearableType::WT_SHAPE && type < LLWearableType::WT_COUNT) - return LLAgentWearables::selfHasWearable(type); - return false; - } -}; - -class LLEditTakeOff : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string clothing = userdata.asString(); - if (clothing == "all") - LLAppearanceMgr::instance().removeAllClothesFromAvatar(); - else - { - LLWearableType::EType type = LLWearableType::getInstance()->typeNameToType(clothing); - if (type >= LLWearableType::WT_SHAPE - && type < LLWearableType::WT_COUNT - && (gAgentWearables.getWearableCount(type) > 0)) - { - // MULTI-WEARABLES: assuming user wanted to remove top shirt. - U32 wearable_index = gAgentWearables.getWearableCount(type) - 1; - LLUUID item_id = gAgentWearables.getWearableItemID(type,wearable_index); - LLAppearanceMgr::instance().removeItemFromAvatar(item_id); - } - - } - return true; - } -}; - -class LLToolsSelectTool : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string tool_name = userdata.asString(); - if (tool_name == "focus") - { - LLToolMgr::getInstance()->getCurrentToolset()->selectToolByIndex(1); - } - else if (tool_name == "move") - { - LLToolMgr::getInstance()->getCurrentToolset()->selectToolByIndex(2); - } - else if (tool_name == "edit") - { - LLToolMgr::getInstance()->getCurrentToolset()->selectToolByIndex(3); - } - else if (tool_name == "create") - { - LLToolMgr::getInstance()->getCurrentToolset()->selectToolByIndex(4); - } - else if (tool_name == "land") - { - LLToolMgr::getInstance()->getCurrentToolset()->selectToolByIndex(5); - } - - // Note: if floater is not visible LLViewerWindow::updateLayout() will - // attempt to open it, but it won't bring it to front or de-minimize. - if (gFloaterTools && (gFloaterTools->isMinimized() || !gFloaterTools->isShown() || !gFloaterTools->isFrontmost())) - { - gFloaterTools->setMinimized(false); - gFloaterTools->openFloater(); - gFloaterTools->setVisibleAndFrontmost(true); - } - return true; - } -}; - -/// WINDLIGHT callbacks -class LLWorldEnvSettings : public view_listener_t -{ - void defocusEnvFloaters() - { - //currently there is only one instance of each floater - std::vector env_floaters_names = { "env_edit_extdaycycle", "env_fixed_environmentent_water", "env_fixed_environmentent_sky" }; - for (std::vector::const_iterator it = env_floaters_names.begin(); it != env_floaters_names.end(); ++it) - { - LLFloater* env_floater = LLFloaterReg::findTypedInstance(*it); - if (env_floater) - { - env_floater->setFocus(false); - } - } - } - - bool handleEvent(const LLSD& userdata) - { - std::string event_name = userdata.asString(); - - if (event_name == "sunrise") - { - LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_SUNRISE, LLEnvironment::TRANSITION_INSTANT); - LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); - defocusEnvFloaters(); - } - else if (event_name == "noon") - { - LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_MIDDAY, LLEnvironment::TRANSITION_INSTANT); - LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); - defocusEnvFloaters(); - } - else if (event_name == "legacy noon") - { - LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_LEGACY_MIDDAY, LLEnvironment::TRANSITION_INSTANT); - LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); - defocusEnvFloaters(); - } - else if (event_name == "sunset") - { - LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_SUNSET, LLEnvironment::TRANSITION_INSTANT); - LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); - defocusEnvFloaters(); - } - else if (event_name == "midnight") - { - LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_MIDNIGHT, LLEnvironment::TRANSITION_INSTANT); - LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); - defocusEnvFloaters(); - } - else if (event_name == "region") - { - // reset probe data when reverting back to region sky setting - gPipeline.mReflectionMapManager.reset(); - - LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_LOCAL); - LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); - defocusEnvFloaters(); - } - else if (event_name == "pause_clouds") - { - if (LLEnvironment::instance().isCloudScrollPaused()) - LLEnvironment::instance().resumeCloudScroll(); - else - LLEnvironment::instance().pauseCloudScroll(); - } - else if (event_name == "adjust_tool") - { - LLFloaterReg::showInstance("env_adjust_snapshot"); - } - else if (event_name == "my_environs") - { - LLFloaterReg::showInstance("my_environments"); - } - - return true; - } -}; - -class LLWorldEnableEnvSettings : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool result = false; - std::string event_name = userdata.asString(); - - if (event_name == "pause_clouds") - { - return LLEnvironment::instance().isCloudScrollPaused(); - } - - LLSettingsSky::ptr_t sky = LLEnvironment::instance().getEnvironmentFixedSky(LLEnvironment::ENV_LOCAL); - - if (!sky) - { - return (event_name == "region"); - } - - std::string skyname = (sky) ? sky->getName() : ""; - LLUUID skyid = (sky) ? sky->getAssetId() : LLUUID::null; - - if (event_name == "sunrise") - { - result = (skyid == LLEnvironment::KNOWN_SKY_SUNRISE); - } - else if (event_name == "noon") - { - result = (skyid == LLEnvironment::KNOWN_SKY_MIDDAY); - } - else if (event_name == "legacy noon") - { - result = (skyid == LLEnvironment::KNOWN_SKY_LEGACY_MIDDAY); - } - else if (event_name == "sunset") - { - result = (skyid == LLEnvironment::KNOWN_SKY_SUNSET); - } - else if (event_name == "midnight") - { - result = (skyid == LLEnvironment::KNOWN_SKY_MIDNIGHT); - } - else if (event_name == "region") - { - return false; - } - else - { - LL_WARNS() << "Unknown time-of-day item: " << event_name << LL_ENDL; - } - return result; - } -}; - -class LLWorldEnvPreset : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - std::string item = userdata.asString(); - - if (item == "new_water") - { - LLFloaterReg::showInstance("env_fixed_environmentent_water", "new"); - } - else if (item == "edit_water") - { - LLFloaterReg::showInstance("env_fixed_environmentent_water", "edit"); - } - else if (item == "new_sky") - { - LLFloaterReg::showInstance("env_fixed_environmentent_sky", "new"); - } - else if (item == "edit_sky") - { - LLFloaterReg::showInstance("env_fixed_environmentent_sky", "edit"); - } - else if (item == "new_day_cycle") - { - LLFloaterReg::showInstance("env_edit_extdaycycle", LLSDMap("edit_context", "inventory")); - } - else if (item == "edit_day_cycle") - { - LLFloaterReg::showInstance("env_edit_extdaycycle", LLSDMap("edit_context", "inventory")); - } - else - { - LL_WARNS() << "Unknown item selected" << LL_ENDL; - } - - return true; - } -}; - -class LLWorldEnableEnvPreset : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - - return false; - } -}; - - -/// Post-Process callbacks -class LLWorldPostProcess : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLFloaterReg::showInstance("env_post_process"); - return true; - } -}; - -class LLWorldCheckBanLines : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - S32 callback_data = userdata.asInteger(); - return gSavedSettings.getS32("ShowBanLines") == callback_data; - } -}; - -class LLWorldShowBanLines : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - S32 callback_data = userdata.asInteger(); - gSavedSettings.setS32("ShowBanLines", callback_data); - return true; - } -}; - -void handle_flush_name_caches() -{ - if (gCacheName) gCacheName->clear(); -} - -class LLUploadCostCalculator : public view_listener_t -{ - std::string mCostStr; - - bool handleEvent(const LLSD& userdata) - { - std::vector fields; - std::string str = userdata.asString(); - boost::split(fields, str, boost::is_any_of(",")); - if (fields.size()<1) - { - return false; - } - std::string menu_name = fields[0]; - std::string asset_type_str = "texture"; - if (fields.size()>1) - { - asset_type_str = fields[1]; - } - LL_DEBUGS("Benefits") << "userdata " << userdata << " menu_name " << menu_name << " asset_type_str " << asset_type_str << LL_ENDL; - calculateCost(asset_type_str); - gMenuHolder->childSetLabelArg(menu_name, "[COST]", mCostStr); - - return true; - } - - void calculateCost(const std::string& asset_type_str); - -public: - LLUploadCostCalculator() - { - } -}; - -class LLUpdateMembershipLabel : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - const std::string label_str = LLAgentBenefitsMgr::isCurrent("Base") ? LLTrans::getString("MembershipUpgradeText") : LLTrans::getString("MembershipPremiumText"); - gMenuHolder->childSetLabelArg("Membership", "[Membership]", label_str); - - return true; - } -}; - -void handle_voice_morphing_subscribe() -{ - LLWeb::loadURL(LLTrans::getString("voice_morphing_url")); -} - -void handle_premium_voice_morphing_subscribe() -{ - LLWeb::loadURL(LLTrans::getString("premium_voice_morphing_url")); -} - -class LLToggleUIHints : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool ui_hints_enabled = gSavedSettings.getBOOL("EnableUIHints"); - // toggle - ui_hints_enabled = !ui_hints_enabled; - gSavedSettings.setBOOL("EnableUIHints", ui_hints_enabled); - return true; - } -}; - -void LLUploadCostCalculator::calculateCost(const std::string& asset_type_str) -{ - S32 upload_cost = -1; - - if (asset_type_str == "texture") - { - upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost(); - } - else if (asset_type_str == "animation") - { - upload_cost = LLAgentBenefitsMgr::current().getAnimationUploadCost(); - } - else if (asset_type_str == "sound") - { - upload_cost = LLAgentBenefitsMgr::current().getSoundUploadCost(); - } - if (upload_cost < 0) - { - LL_WARNS() << "Unable to find upload cost for asset_type_str " << asset_type_str << LL_ENDL; - } - mCostStr = std::to_string(upload_cost); -} - -void show_navbar_context_menu(LLView* ctrl, S32 x, S32 y) -{ - static LLMenuGL* show_navbar_context_menu = LLUICtrlFactory::getInstance()->createFromFile("menu_hide_navbar.xml", - gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - if(gMenuHolder->hasVisibleMenu()) - { - gMenuHolder->hideMenus(); - } - show_navbar_context_menu->buildDrawLabels(); - show_navbar_context_menu->updateParent(LLMenuGL::sMenuContainer); - LLMenuGL::showPopup(ctrl, show_navbar_context_menu, x, y); -} - -void show_topinfobar_context_menu(LLView* ctrl, S32 x, S32 y) -{ - static LLMenuGL* show_topbarinfo_context_menu = LLUICtrlFactory::getInstance()->createFromFile("menu_topinfobar.xml", - gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - - LLMenuItemGL* landmark_item = show_topbarinfo_context_menu->getChild("Landmark"); - if (!LLLandmarkActions::landmarkAlreadyExists()) - { - landmark_item->setLabel(LLTrans::getString("AddLandmarkNavBarMenu")); - } - else - { - landmark_item->setLabel(LLTrans::getString("EditLandmarkNavBarMenu")); - } - - if(gMenuHolder->hasVisibleMenu()) - { - gMenuHolder->hideMenus(); - } - - show_topbarinfo_context_menu->buildDrawLabels(); - show_topbarinfo_context_menu->updateParent(LLMenuGL::sMenuContainer); - LLMenuGL::showPopup(ctrl, show_topbarinfo_context_menu, x, y); -} - -void initialize_edit_menu() -{ - view_listener_t::addMenu(new LLEditUndo(), "Edit.Undo"); - view_listener_t::addMenu(new LLEditRedo(), "Edit.Redo"); - view_listener_t::addMenu(new LLEditCut(), "Edit.Cut"); - view_listener_t::addMenu(new LLEditCopy(), "Edit.Copy"); - view_listener_t::addMenu(new LLEditPaste(), "Edit.Paste"); - view_listener_t::addMenu(new LLEditDelete(), "Edit.Delete"); - view_listener_t::addMenu(new LLEditSelectAll(), "Edit.SelectAll"); - view_listener_t::addMenu(new LLEditDeselect(), "Edit.Deselect"); - view_listener_t::addMenu(new LLEditTakeOff(), "Edit.TakeOff"); - view_listener_t::addMenu(new LLEditEnableUndo(), "Edit.EnableUndo"); - view_listener_t::addMenu(new LLEditEnableRedo(), "Edit.EnableRedo"); - view_listener_t::addMenu(new LLEditEnableCut(), "Edit.EnableCut"); - view_listener_t::addMenu(new LLEditEnableCopy(), "Edit.EnableCopy"); - view_listener_t::addMenu(new LLEditEnablePaste(), "Edit.EnablePaste"); - view_listener_t::addMenu(new LLEditEnableDelete(), "Edit.EnableDelete"); - view_listener_t::addMenu(new LLEditEnableSelectAll(), "Edit.EnableSelectAll"); - view_listener_t::addMenu(new LLEditEnableDeselect(), "Edit.EnableDeselect"); - -} - -void initialize_spellcheck_menu() -{ - LLUICtrl::CommitCallbackRegistry::Registrar& commit = LLUICtrl::CommitCallbackRegistry::currentRegistrar(); - LLUICtrl::EnableCallbackRegistry::Registrar& enable = LLUICtrl::EnableCallbackRegistry::currentRegistrar(); - - commit.add("SpellCheck.ReplaceWithSuggestion", boost::bind(&handle_spellcheck_replace_with_suggestion, _1, _2)); - enable.add("SpellCheck.VisibleSuggestion", boost::bind(&visible_spellcheck_suggestion, _1, _2)); - commit.add("SpellCheck.AddToDictionary", boost::bind(&handle_spellcheck_add_to_dictionary, _1)); - enable.add("SpellCheck.EnableAddToDictionary", boost::bind(&enable_spellcheck_add_to_dictionary, _1)); - commit.add("SpellCheck.AddToIgnore", boost::bind(&handle_spellcheck_add_to_ignore, _1)); - enable.add("SpellCheck.EnableAddToIgnore", boost::bind(&enable_spellcheck_add_to_ignore, _1)); -} - -void initialize_menus() -{ - // A parameterized event handler used as ctrl-8/9/0 zoom controls below. - class LLZoomer : public view_listener_t - { - public: - // The "mult" parameter says whether "val" is a multiplier or used to set the value. - LLZoomer(F32 val, bool mult=true) : mVal(val), mMult(mult) {} - bool handleEvent(const LLSD& userdata) - { - F32 new_fov_rad = mMult ? LLViewerCamera::getInstance()->getDefaultFOV() * mVal : mVal; - LLViewerCamera::getInstance()->setDefaultFOV(new_fov_rad); - gSavedSettings.setF32("CameraAngle", LLViewerCamera::getInstance()->getView()); // setView may have clamped it. - return true; - } - private: - F32 mVal; - bool mMult; - }; - - LLUICtrl::EnableCallbackRegistry::Registrar& enable = LLUICtrl::EnableCallbackRegistry::currentRegistrar(); - LLUICtrl::CommitCallbackRegistry::Registrar& commit = LLUICtrl::CommitCallbackRegistry::currentRegistrar(); - - // Generic enable and visible - // Don't prepend MenuName.Foo because these can be used in any menu. - enable.add("IsGodCustomerService", boost::bind(&is_god_customer_service)); - - enable.add("displayViewerEventRecorderMenuItems",boost::bind(&LLViewerEventRecorder::displayViewerEventRecorderMenuItems,&LLViewerEventRecorder::instance())); - - view_listener_t::addEnable(new LLUploadCostCalculator(), "Upload.CalculateCosts"); - - view_listener_t::addEnable(new LLUpdateMembershipLabel(), "Membership.UpdateLabel"); - - enable.add("Conversation.IsConversationLoggingAllowed", boost::bind(&LLFloaterIMContainer::isConversationLoggingAllowed)); - - // Agent - commit.add("Agent.toggleFlying", boost::bind(&LLAgent::toggleFlying)); - enable.add("Agent.enableFlyLand", boost::bind(&enable_fly_land)); - commit.add("Agent.PressMicrophone", boost::bind(&LLAgent::pressMicrophone, _2)); - commit.add("Agent.ReleaseMicrophone", boost::bind(&LLAgent::releaseMicrophone, _2)); - commit.add("Agent.ToggleMicrophone", boost::bind(&LLAgent::toggleMicrophone, _2)); - enable.add("Agent.IsMicrophoneOn", boost::bind(&LLAgent::isMicrophoneOn, _2)); - enable.add("Agent.IsActionAllowed", boost::bind(&LLAgent::isActionAllowed, _2)); - - // File menu - init_menu_file(); - - view_listener_t::addMenu(new LLEditEnableTakeOff(), "Edit.EnableTakeOff"); - view_listener_t::addMenu(new LLEditEnableCustomizeAvatar(), "Edit.EnableCustomizeAvatar"); - view_listener_t::addMenu(new LLEnableEditShape(), "Edit.EnableEditShape"); - view_listener_t::addMenu(new LLEnableHoverHeight(), "Edit.EnableHoverHeight"); - view_listener_t::addMenu(new LLEnableEditPhysics(), "Edit.EnableEditPhysics"); - commit.add("CustomizeAvatar", boost::bind(&handle_customize_avatar)); - commit.add("NowWearing", boost::bind(&handle_now_wearing)); - commit.add("EditOutfit", boost::bind(&handle_edit_outfit)); - commit.add("EditShape", boost::bind(&handle_edit_shape)); - commit.add("HoverHeight", boost::bind(&handle_hover_height)); - commit.add("EditPhysics", boost::bind(&handle_edit_physics)); - - // View menu - view_listener_t::addMenu(new LLViewMouselook(), "View.Mouselook"); - view_listener_t::addMenu(new LLViewJoystickFlycam(), "View.JoystickFlycam"); - view_listener_t::addMenu(new LLViewResetView(), "View.ResetView"); - view_listener_t::addMenu(new LLViewLookAtLastChatter(), "View.LookAtLastChatter"); - view_listener_t::addMenu(new LLViewShowHoverTips(), "View.ShowHoverTips"); - view_listener_t::addMenu(new LLViewHighlightTransparent(), "View.HighlightTransparent"); - view_listener_t::addMenu(new LLViewToggleRenderType(), "View.ToggleRenderType"); - view_listener_t::addMenu(new LLViewShowHUDAttachments(), "View.ShowHUDAttachments"); - view_listener_t::addMenu(new LLZoomer(1.2f), "View.ZoomOut"); - view_listener_t::addMenu(new LLZoomer(1/1.2f), "View.ZoomIn"); - view_listener_t::addMenu(new LLZoomer(DEFAULT_FIELD_OF_VIEW, false), "View.ZoomDefault"); - view_listener_t::addMenu(new LLViewDefaultUISize(), "View.DefaultUISize"); - view_listener_t::addMenu(new LLViewToggleUI(), "View.ToggleUI"); - - view_listener_t::addMenu(new LLViewEnableMouselook(), "View.EnableMouselook"); - view_listener_t::addMenu(new LLViewEnableJoystickFlycam(), "View.EnableJoystickFlycam"); - view_listener_t::addMenu(new LLViewEnableLastChatter(), "View.EnableLastChatter"); - - view_listener_t::addMenu(new LLViewCheckJoystickFlycam(), "View.CheckJoystickFlycam"); - view_listener_t::addMenu(new LLViewCheckShowHoverTips(), "View.CheckShowHoverTips"); - view_listener_t::addMenu(new LLViewCheckHighlightTransparent(), "View.CheckHighlightTransparent"); - view_listener_t::addMenu(new LLViewCheckRenderType(), "View.CheckRenderType"); - view_listener_t::addMenu(new LLViewStatusAway(), "View.Status.CheckAway"); - view_listener_t::addMenu(new LLViewStatusDoNotDisturb(), "View.Status.CheckDoNotDisturb"); - view_listener_t::addMenu(new LLViewCheckHUDAttachments(), "View.CheckHUDAttachments"); - - //Communicate Nearby chat - view_listener_t::addMenu(new LLCommunicateNearbyChat(), "Communicate.NearbyChat"); - - // Communicate > Voice morphing > Subscribe... - commit.add("Communicate.VoiceMorphing.Subscribe", boost::bind(&handle_voice_morphing_subscribe)); - // Communicate > Voice morphing > Premium perk... - commit.add("Communicate.VoiceMorphing.PremiumPerk", boost::bind(&handle_premium_voice_morphing_subscribe)); - LLVivoxVoiceClient * voice_clientp = LLVivoxVoiceClient::getInstance(); - enable.add("Communicate.VoiceMorphing.NoVoiceMorphing.Check" - , boost::bind(&LLVivoxVoiceClient::onCheckVoiceEffect, voice_clientp, "NoVoiceMorphing")); - commit.add("Communicate.VoiceMorphing.NoVoiceMorphing.Click" - , boost::bind(&LLVivoxVoiceClient::onClickVoiceEffect, voice_clientp, "NoVoiceMorphing")); - - // World menu - view_listener_t::addMenu(new LLWorldAlwaysRun(), "World.AlwaysRun"); - view_listener_t::addMenu(new LLWorldCreateLandmark(), "World.CreateLandmark"); - view_listener_t::addMenu(new LLWorldPlaceProfile(), "World.PlaceProfile"); - view_listener_t::addMenu(new LLWorldSetHomeLocation(), "World.SetHomeLocation"); - view_listener_t::addMenu(new LLWorldTeleportHome(), "World.TeleportHome"); - view_listener_t::addMenu(new LLWorldSetAway(), "World.SetAway"); - view_listener_t::addMenu(new LLWorldSetDoNotDisturb(), "World.SetDoNotDisturb"); - view_listener_t::addMenu(new LLWorldLindenHome(), "World.LindenHome"); - - view_listener_t::addMenu(new LLWorldEnableCreateLandmark(), "World.EnableCreateLandmark"); - view_listener_t::addMenu(new LLWorldEnableSetHomeLocation(), "World.EnableSetHomeLocation"); - view_listener_t::addMenu(new LLWorldEnableTeleportHome(), "World.EnableTeleportHome"); - view_listener_t::addMenu(new LLWorldEnableBuyLand(), "World.EnableBuyLand"); - - view_listener_t::addMenu(new LLWorldCheckAlwaysRun(), "World.CheckAlwaysRun"); - - view_listener_t::addMenu(new LLWorldEnvSettings(), "World.EnvSettings"); - view_listener_t::addMenu(new LLWorldEnableEnvSettings(), "World.EnableEnvSettings"); - view_listener_t::addMenu(new LLWorldEnvPreset(), "World.EnvPreset"); - view_listener_t::addMenu(new LLWorldEnableEnvPreset(), "World.EnableEnvPreset"); - view_listener_t::addMenu(new LLWorldPostProcess(), "World.PostProcess"); - view_listener_t::addMenu(new LLWorldCheckBanLines() , "World.CheckBanLines"); - view_listener_t::addMenu(new LLWorldShowBanLines() , "World.ShowBanLines"); - - // Tools menu - view_listener_t::addMenu(new LLToolsSelectTool(), "Tools.SelectTool"); - view_listener_t::addMenu(new LLToolsSelectOnlyMyObjects(), "Tools.SelectOnlyMyObjects"); - view_listener_t::addMenu(new LLToolsSelectOnlyMovableObjects(), "Tools.SelectOnlyMovableObjects"); - view_listener_t::addMenu(new LLToolsSelectInvisibleObjects(), "Tools.SelectInvisibleObjects"); - view_listener_t::addMenu(new LLToolsSelectReflectionProbes(), "Tools.SelectReflectionProbes"); - view_listener_t::addMenu(new LLToolsSelectBySurrounding(), "Tools.SelectBySurrounding"); - view_listener_t::addMenu(new LLToolsShowHiddenSelection(), "Tools.ShowHiddenSelection"); - view_listener_t::addMenu(new LLToolsShowSelectionLightRadius(), "Tools.ShowSelectionLightRadius"); - view_listener_t::addMenu(new LLToolsEditLinkedParts(), "Tools.EditLinkedParts"); - view_listener_t::addMenu(new LLToolsSnapObjectXY(), "Tools.SnapObjectXY"); - view_listener_t::addMenu(new LLToolsUseSelectionForGrid(), "Tools.UseSelectionForGrid"); - view_listener_t::addMenu(new LLToolsSelectNextPartFace(), "Tools.SelectNextPart"); - commit.add("Tools.Link", boost::bind(&handle_link_objects)); - commit.add("Tools.Unlink", boost::bind(&LLSelectMgr::unlinkObjects, LLSelectMgr::getInstance())); - view_listener_t::addMenu(new LLToolsStopAllAnimations(), "Tools.StopAllAnimations"); - view_listener_t::addMenu(new LLToolsReleaseKeys(), "Tools.ReleaseKeys"); - view_listener_t::addMenu(new LLToolsEnableReleaseKeys(), "Tools.EnableReleaseKeys"); - commit.add("Tools.LookAtSelection", boost::bind(&handle_look_at_selection, _2)); - commit.add("Tools.BuyOrTake", boost::bind(&handle_buy_or_take)); - commit.add("Tools.TakeCopy", boost::bind(&handle_take_copy)); - view_listener_t::addMenu(new LLToolsSaveToObjectInventory(), "Tools.SaveToObjectInventory"); - view_listener_t::addMenu(new LLToolsSelectedScriptAction(), "Tools.SelectedScriptAction"); - - view_listener_t::addMenu(new LLToolsEnableToolNotPie(), "Tools.EnableToolNotPie"); - view_listener_t::addMenu(new LLToolsEnableSelectNextPart(), "Tools.EnableSelectNextPart"); - enable.add("Tools.EnableLink", boost::bind(&LLSelectMgr::enableLinkObjects, LLSelectMgr::getInstance())); - enable.add("Tools.EnableUnlink", boost::bind(&LLSelectMgr::enableUnlinkObjects, LLSelectMgr::getInstance())); - view_listener_t::addMenu(new LLToolsEnableBuyOrTake(), "Tools.EnableBuyOrTake"); - enable.add("Tools.EnableTakeCopy", boost::bind(&enable_object_take_copy)); - enable.add("Tools.EnableCopySeparate", boost::bind(&enable_take_copy_objects)); - enable.add("Tools.VisibleBuyObject", boost::bind(&tools_visible_buy_object)); - enable.add("Tools.VisibleTakeObject", boost::bind(&tools_visible_take_object)); - view_listener_t::addMenu(new LLToolsEnableSaveToObjectInventory(), "Tools.EnableSaveToObjectInventory"); - - view_listener_t::addMenu(new LLToolsEnablePathfinding(), "Tools.EnablePathfinding"); - view_listener_t::addMenu(new LLToolsEnablePathfindingView(), "Tools.EnablePathfindingView"); - view_listener_t::addMenu(new LLToolsDoPathfindingRebakeRegion(), "Tools.DoPathfindingRebakeRegion"); - view_listener_t::addMenu(new LLToolsEnablePathfindingRebakeRegion(), "Tools.EnablePathfindingRebakeRegion"); - - // Help menu - // most items use the ShowFloater method - view_listener_t::addMenu(new LLToggleHowTo(), "Help.ToggleHowTo"); - - // Advanced menu - view_listener_t::addMenu(new LLAdvancedToggleConsole(), "Advanced.ToggleConsole"); - view_listener_t::addMenu(new LLAdvancedCheckConsole(), "Advanced.CheckConsole"); - view_listener_t::addMenu(new LLAdvancedDumpInfoToConsole(), "Advanced.DumpInfoToConsole"); - - // Advanced > HUD Info - view_listener_t::addMenu(new LLAdvancedToggleHUDInfo(), "Advanced.ToggleHUDInfo"); - view_listener_t::addMenu(new LLAdvancedCheckHUDInfo(), "Advanced.CheckHUDInfo"); - - // Advanced Other Settings - view_listener_t::addMenu(new LLAdvancedClearGroupCache(), "Advanced.ClearGroupCache"); - - // Advanced > Render > Types - view_listener_t::addMenu(new LLAdvancedToggleRenderType(), "Advanced.ToggleRenderType"); - view_listener_t::addMenu(new LLAdvancedCheckRenderType(), "Advanced.CheckRenderType"); - - //// Advanced > Render > Features - view_listener_t::addMenu(new LLAdvancedToggleFeature(), "Advanced.ToggleFeature"); - view_listener_t::addMenu(new LLAdvancedCheckFeature(), "Advanced.CheckFeature"); - - view_listener_t::addMenu(new LLAdvancedCheckDisplayTextureDensity(), "Advanced.CheckDisplayTextureDensity"); - view_listener_t::addMenu(new LLAdvancedSetDisplayTextureDensity(), "Advanced.SetDisplayTextureDensity"); - - // Advanced > Render > Info Displays - view_listener_t::addMenu(new LLAdvancedToggleInfoDisplay(), "Advanced.ToggleInfoDisplay"); - view_listener_t::addMenu(new LLAdvancedCheckInfoDisplay(), "Advanced.CheckInfoDisplay"); - view_listener_t::addMenu(new LLAdvancedSelectedTextureInfo(), "Advanced.SelectedTextureInfo"); - commit.add("Advanced.SelectedMaterialInfo", boost::bind(&handle_selected_material_info)); - view_listener_t::addMenu(new LLAdvancedToggleWireframe(), "Advanced.ToggleWireframe"); - view_listener_t::addMenu(new LLAdvancedCheckWireframe(), "Advanced.CheckWireframe"); - // Develop > Render - view_listener_t::addMenu(new LLAdvancedToggleRandomizeFramerate(), "Advanced.ToggleRandomizeFramerate"); - view_listener_t::addMenu(new LLAdvancedCheckRandomizeFramerate(), "Advanced.CheckRandomizeFramerate"); - view_listener_t::addMenu(new LLAdvancedTogglePeriodicSlowFrame(), "Advanced.TogglePeriodicSlowFrame"); - view_listener_t::addMenu(new LLAdvancedCheckPeriodicSlowFrame(), "Advanced.CheckPeriodicSlowFrame"); - view_listener_t::addMenu(new LLAdvancedHandleAttachedLightParticles(), "Advanced.HandleAttachedLightParticles"); - view_listener_t::addMenu(new LLAdvancedCheckRenderShadowOption(), "Advanced.CheckRenderShadowOption"); - view_listener_t::addMenu(new LLAdvancedClickRenderShadowOption(), "Advanced.ClickRenderShadowOption"); - view_listener_t::addMenu(new LLAdvancedClickRenderProfile(), "Advanced.ClickRenderProfile"); - view_listener_t::addMenu(new LLAdvancedClickRenderBenchmark(), "Advanced.ClickRenderBenchmark"); - view_listener_t::addMenu(new LLAdvancedPurgeShaderCache(), "Advanced.ClearShaderCache"); - - #ifdef TOGGLE_HACKED_GODLIKE_VIEWER - view_listener_t::addMenu(new LLAdvancedHandleToggleHackedGodmode(), "Advanced.HandleToggleHackedGodmode"); - view_listener_t::addMenu(new LLAdvancedCheckToggleHackedGodmode(), "Advanced.CheckToggleHackedGodmode"); - view_listener_t::addMenu(new LLAdvancedEnableToggleHackedGodmode(), "Advanced.EnableToggleHackedGodmode"); - #endif - - // Advanced > World - view_listener_t::addMenu(new LLAdvancedDumpScriptedCamera(), "Advanced.DumpScriptedCamera"); - view_listener_t::addMenu(new LLAdvancedDumpRegionObjectCache(), "Advanced.DumpRegionObjectCache"); - view_listener_t::addMenu(new LLAdvancedToggleStatsRecorder(), "Advanced.ToggleStatsRecorder"); - view_listener_t::addMenu(new LLAdvancedCheckStatsRecorder(), "Advanced.CheckStatsRecorder"); - view_listener_t::addMenu(new LLAdvancedToggleInterestList360Mode(), "Advanced.ToggleInterestList360Mode"); - view_listener_t::addMenu(new LLAdvancedCheckInterestList360Mode(), "Advanced.CheckInterestList360Mode"); - view_listener_t::addMenu(new LLAdvancedResetInterestLists(), "Advanced.ResetInterestLists"); - - // Advanced > UI - commit.add("Advanced.WebBrowserTest", boost::bind(&handle_web_browser_test, _2)); // sigh! this one opens the MEDIA browser - commit.add("Advanced.WebContentTest", boost::bind(&handle_web_content_test, _2)); // this one opens the Web Content floater - commit.add("Advanced.ShowURL", boost::bind(&handle_show_url, _2)); - commit.add("Advanced.ReportBug", boost::bind(&handle_report_bug, _2)); - view_listener_t::addMenu(new LLAdvancedBuyCurrencyTest(), "Advanced.BuyCurrencyTest"); - view_listener_t::addMenu(new LLAdvancedDumpSelectMgr(), "Advanced.DumpSelectMgr"); - view_listener_t::addMenu(new LLAdvancedDumpInventory(), "Advanced.DumpInventory"); - commit.add("Advanced.DumpTimers", boost::bind(&handle_dump_timers) ); - commit.add("Advanced.DumpFocusHolder", boost::bind(&handle_dump_focus) ); - view_listener_t::addMenu(new LLAdvancedPrintSelectedObjectInfo(), "Advanced.PrintSelectedObjectInfo"); - view_listener_t::addMenu(new LLAdvancedPrintAgentInfo(), "Advanced.PrintAgentInfo"); - view_listener_t::addMenu(new LLAdvancedToggleDebugClicks(), "Advanced.ToggleDebugClicks"); - view_listener_t::addMenu(new LLAdvancedCheckDebugClicks(), "Advanced.CheckDebugClicks"); - view_listener_t::addMenu(new LLAdvancedCheckDebugViews(), "Advanced.CheckDebugViews"); - view_listener_t::addMenu(new LLAdvancedToggleDebugViews(), "Advanced.ToggleDebugViews"); - view_listener_t::addMenu(new LLAdvancedCheckDebugUnicode(), "Advanced.CheckDebugUnicode"); - view_listener_t::addMenu(new LLAdvancedToggleDebugUnicode(), "Advanced.ToggleDebugUnicode"); - view_listener_t::addMenu(new LLAdvancedCheckDebugCamera(), "Advanced.CheckDebugCamera"); - view_listener_t::addMenu(new LLAdvancedToggleDebugCamera(), "Advanced.ToggleDebugCamera"); - view_listener_t::addMenu(new LLAdvancedToggleXUINameTooltips(), "Advanced.ToggleXUINameTooltips"); - view_listener_t::addMenu(new LLAdvancedCheckXUINameTooltips(), "Advanced.CheckXUINameTooltips"); - view_listener_t::addMenu(new LLAdvancedToggleDebugMouseEvents(), "Advanced.ToggleDebugMouseEvents"); - view_listener_t::addMenu(new LLAdvancedCheckDebugMouseEvents(), "Advanced.CheckDebugMouseEvents"); - view_listener_t::addMenu(new LLAdvancedToggleDebugKeys(), "Advanced.ToggleDebugKeys"); - view_listener_t::addMenu(new LLAdvancedCheckDebugKeys(), "Advanced.CheckDebugKeys"); - view_listener_t::addMenu(new LLAdvancedToggleDebugWindowProc(), "Advanced.ToggleDebugWindowProc"); - view_listener_t::addMenu(new LLAdvancedCheckDebugWindowProc(), "Advanced.CheckDebugWindowProc"); - - // Advanced > XUI - commit.add("Advanced.ReloadColorSettings", boost::bind(&LLUIColorTable::loadFromSettings, LLUIColorTable::getInstance())); - view_listener_t::addMenu(new LLAdvancedToggleXUINames(), "Advanced.ToggleXUINames"); - view_listener_t::addMenu(new LLAdvancedCheckXUINames(), "Advanced.CheckXUINames"); - view_listener_t::addMenu(new LLAdvancedSendTestIms(), "Advanced.SendTestIMs"); - commit.add("Advanced.FlushNameCaches", boost::bind(&handle_flush_name_caches)); - - // Advanced > Character > Grab Baked Texture - view_listener_t::addMenu(new LLAdvancedGrabBakedTexture(), "Advanced.GrabBakedTexture"); - view_listener_t::addMenu(new LLAdvancedEnableGrabBakedTexture(), "Advanced.EnableGrabBakedTexture"); - - // Advanced > Character > Character Tests - view_listener_t::addMenu(new LLAdvancedAppearanceToXML(), "Advanced.AppearanceToXML"); - view_listener_t::addMenu(new LLAdvancedEnableAppearanceToXML(), "Advanced.EnableAppearanceToXML"); - view_listener_t::addMenu(new LLAdvancedToggleCharacterGeometry(), "Advanced.ToggleCharacterGeometry"); - - view_listener_t::addMenu(new LLAdvancedTestMale(), "Advanced.TestMale"); - view_listener_t::addMenu(new LLAdvancedTestFemale(), "Advanced.TestFemale"); - - // Advanced > Character > Animation Speed - view_listener_t::addMenu(new LLAdvancedAnimTenFaster(), "Advanced.AnimTenFaster"); - view_listener_t::addMenu(new LLAdvancedAnimTenSlower(), "Advanced.AnimTenSlower"); - view_listener_t::addMenu(new LLAdvancedAnimResetAll(), "Advanced.AnimResetAll"); - - // Advanced > Character (toplevel) - view_listener_t::addMenu(new LLAdvancedForceParamsToDefault(), "Advanced.ForceParamsToDefault"); - view_listener_t::addMenu(new LLAdvancedReloadVertexShader(), "Advanced.ReloadVertexShader"); - view_listener_t::addMenu(new LLAdvancedToggleAnimationInfo(), "Advanced.ToggleAnimationInfo"); - view_listener_t::addMenu(new LLAdvancedCheckAnimationInfo(), "Advanced.CheckAnimationInfo"); - view_listener_t::addMenu(new LLAdvancedToggleShowLookAt(), "Advanced.ToggleShowLookAt"); - view_listener_t::addMenu(new LLAdvancedCheckShowLookAt(), "Advanced.CheckShowLookAt"); - view_listener_t::addMenu(new LLAdvancedToggleShowPointAt(), "Advanced.ToggleShowPointAt"); - view_listener_t::addMenu(new LLAdvancedCheckShowPointAt(), "Advanced.CheckShowPointAt"); - view_listener_t::addMenu(new LLAdvancedToggleDebugJointUpdates(), "Advanced.ToggleDebugJointUpdates"); - view_listener_t::addMenu(new LLAdvancedCheckDebugJointUpdates(), "Advanced.CheckDebugJointUpdates"); - view_listener_t::addMenu(new LLAdvancedToggleDisableLOD(), "Advanced.ToggleDisableLOD"); - view_listener_t::addMenu(new LLAdvancedCheckDisableLOD(), "Advanced.CheckDisableLOD"); - view_listener_t::addMenu(new LLAdvancedToggleDebugCharacterVis(), "Advanced.ToggleDebugCharacterVis"); - view_listener_t::addMenu(new LLAdvancedCheckDebugCharacterVis(), "Advanced.CheckDebugCharacterVis"); - view_listener_t::addMenu(new LLAdvancedDumpAttachments(), "Advanced.DumpAttachments"); - view_listener_t::addMenu(new LLAdvancedRebakeTextures(), "Advanced.RebakeTextures"); - view_listener_t::addMenu(new LLAdvancedDebugAvatarTextures(), "Advanced.DebugAvatarTextures"); - view_listener_t::addMenu(new LLAdvancedDumpAvatarLocalTextures(), "Advanced.DumpAvatarLocalTextures"); - // Advanced > Network - view_listener_t::addMenu(new LLAdvancedEnableMessageLog(), "Advanced.EnableMessageLog"); - view_listener_t::addMenu(new LLAdvancedDisableMessageLog(), "Advanced.DisableMessageLog"); - view_listener_t::addMenu(new LLAdvancedDropPacket(), "Advanced.DropPacket"); - - // Advanced > Cache - view_listener_t::addMenu(new LLAdvancedPurgeDiskCache(), "Advanced.PurgeDiskCache"); - - // Advanced > Recorder - view_listener_t::addMenu(new LLAdvancedAgentPilot(), "Advanced.AgentPilot"); - view_listener_t::addMenu(new LLAdvancedToggleAgentPilotLoop(), "Advanced.ToggleAgentPilotLoop"); - view_listener_t::addMenu(new LLAdvancedCheckAgentPilotLoop(), "Advanced.CheckAgentPilotLoop"); - view_listener_t::addMenu(new LLAdvancedViewerEventRecorder(), "Advanced.EventRecorder"); - - // Advanced > Debugging - view_listener_t::addMenu(new LLAdvancedForceErrorBreakpoint(), "Advanced.ForceErrorBreakpoint"); - view_listener_t::addMenu(new LLAdvancedForceErrorLlerror(), "Advanced.ForceErrorLlerror"); - view_listener_t::addMenu(new LLAdvancedForceErrorLlerrorMsg(), "Advanced.ForceErrorLlerrorMsg"); - view_listener_t::addMenu(new LLAdvancedForceErrorBadMemoryAccess(), "Advanced.ForceErrorBadMemoryAccess"); - view_listener_t::addMenu(new LLAdvancedForceErrorBadMemoryAccessCoro(), "Advanced.ForceErrorBadMemoryAccessCoro"); - view_listener_t::addMenu(new LLAdvancedForceErrorInfiniteLoop(), "Advanced.ForceErrorInfiniteLoop"); - view_listener_t::addMenu(new LLAdvancedForceErrorSoftwareException(), "Advanced.ForceErrorSoftwareException"); - view_listener_t::addMenu(new LLAdvancedForceOSException(), "Advanced.ForceErrorOSException"); - view_listener_t::addMenu(new LLAdvancedForceErrorSoftwareExceptionCoro(), "Advanced.ForceErrorSoftwareExceptionCoro"); - view_listener_t::addMenu(new LLAdvancedForceErrorDriverCrash(), "Advanced.ForceErrorDriverCrash"); - view_listener_t::addMenu(new LLAdvancedForceErrorCoroutineCrash(), "Advanced.ForceErrorCoroutineCrash"); - view_listener_t::addMenu(new LLAdvancedForceErrorThreadCrash(), "Advanced.ForceErrorThreadCrash"); - view_listener_t::addMenu(new LLAdvancedForceErrorDisconnectViewer(), "Advanced.ForceErrorDisconnectViewer"); - - // Advanced (toplevel) - view_listener_t::addMenu(new LLAdvancedToggleShowObjectUpdates(), "Advanced.ToggleShowObjectUpdates"); - view_listener_t::addMenu(new LLAdvancedCheckShowObjectUpdates(), "Advanced.CheckShowObjectUpdates"); - view_listener_t::addMenu(new LLAdvancedCompressImage(), "Advanced.CompressImage"); - view_listener_t::addMenu(new LLAdvancedCompressFileTest(), "Advanced.CompressFileTest"); - view_listener_t::addMenu(new LLAdvancedShowDebugSettings(), "Advanced.ShowDebugSettings"); - view_listener_t::addMenu(new LLAdvancedEnableViewAdminOptions(), "Advanced.EnableViewAdminOptions"); - view_listener_t::addMenu(new LLAdvancedToggleViewAdminOptions(), "Advanced.ToggleViewAdminOptions"); - view_listener_t::addMenu(new LLAdvancedCheckViewAdminOptions(), "Advanced.CheckViewAdminOptions"); - view_listener_t::addMenu(new LLAdvancedToggleVisualLeakDetector(), "Advanced.ToggleVisualLeakDetector"); - - view_listener_t::addMenu(new LLAdvancedRequestAdminStatus(), "Advanced.RequestAdminStatus"); - view_listener_t::addMenu(new LLAdvancedLeaveAdminStatus(), "Advanced.LeaveAdminStatus"); - - // Develop >Set logging level - view_listener_t::addMenu(new LLDevelopCheckLoggingLevel(), "Develop.CheckLoggingLevel"); - view_listener_t::addMenu(new LLDevelopSetLoggingLevel(), "Develop.SetLoggingLevel"); - - //Develop (clear cache immediately) - commit.add("Develop.ClearCache", boost::bind(&handle_cache_clear_immediately) ); - - // Develop (Fonts debugging) - commit.add("Develop.Fonts.Dump", boost::bind(&LLFontGL::dumpFonts)); - commit.add("Develop.Fonts.DumpTextures", boost::bind(&LLFontGL::dumpFontTextures)); - - // Admin >Object - view_listener_t::addMenu(new LLAdminForceTakeCopy(), "Admin.ForceTakeCopy"); - view_listener_t::addMenu(new LLAdminHandleObjectOwnerSelf(), "Admin.HandleObjectOwnerSelf"); - view_listener_t::addMenu(new LLAdminHandleObjectOwnerPermissive(), "Admin.HandleObjectOwnerPermissive"); - view_listener_t::addMenu(new LLAdminHandleForceDelete(), "Admin.HandleForceDelete"); - view_listener_t::addMenu(new LLAdminHandleObjectLock(), "Admin.HandleObjectLock"); - view_listener_t::addMenu(new LLAdminHandleObjectAssetIDs(), "Admin.HandleObjectAssetIDs"); - - // Admin >Parcel - view_listener_t::addMenu(new LLAdminHandleForceParcelOwnerToMe(), "Admin.HandleForceParcelOwnerToMe"); - view_listener_t::addMenu(new LLAdminHandleForceParcelToContent(), "Admin.HandleForceParcelToContent"); - view_listener_t::addMenu(new LLAdminHandleClaimPublicLand(), "Admin.HandleClaimPublicLand"); - - // Admin >Region - view_listener_t::addMenu(new LLAdminHandleRegionDumpTempAssetData(), "Admin.HandleRegionDumpTempAssetData"); - // Admin top level - view_listener_t::addMenu(new LLAdminOnSaveState(), "Admin.OnSaveState"); - - // Self context menu - view_listener_t::addMenu(new LLSelfToggleSitStand(), "Self.ToggleSitStand"); - enable.add("Self.EnableSitStand", boost::bind(&enable_sit_stand)); - view_listener_t::addMenu(new LLSelfRemoveAllAttachments(), "Self.RemoveAllAttachments"); - - view_listener_t::addMenu(new LLSelfEnableRemoveAllAttachments(), "Self.EnableRemoveAllAttachments"); - - // we don't use boost::bind directly to delay side tray construction - view_listener_t::addMenu( new LLTogglePanelPeopleTab(), "SideTray.PanelPeopleTab"); - view_listener_t::addMenu( new LLCheckPanelPeopleTab(), "SideTray.CheckPanelPeopleTab"); - - // Avatar pie menu - view_listener_t::addMenu(new LLAvatarCheckImpostorMode(), "Avatar.CheckImpostorMode"); - view_listener_t::addMenu(new LLAvatarSetImpostorMode(), "Avatar.SetImpostorMode"); - view_listener_t::addMenu(new LLObjectMute(), "Avatar.Mute"); - view_listener_t::addMenu(new LLAvatarAddFriend(), "Avatar.AddFriend"); - view_listener_t::addMenu(new LLAvatarAddContact(), "Avatar.AddContact"); - commit.add("Avatar.Freeze", boost::bind(&handle_avatar_freeze, LLSD())); - view_listener_t::addMenu(new LLAvatarDebug(), "Avatar.Debug"); - view_listener_t::addMenu(new LLAvatarVisibleDebug(), "Avatar.VisibleDebug"); - view_listener_t::addMenu(new LLAvatarInviteToGroup(), "Avatar.InviteToGroup"); - commit.add("Avatar.Eject", boost::bind(&handle_avatar_eject, LLSD())); - commit.add("Avatar.ShowInspector", boost::bind(&handle_avatar_show_inspector)); - view_listener_t::addMenu(new LLAvatarSendIM(), "Avatar.SendIM"); - view_listener_t::addMenu(new LLAvatarCall(), "Avatar.Call"); - enable.add("Avatar.EnableCall", boost::bind(&LLAvatarActions::canCall)); - view_listener_t::addMenu(new LLAvatarReportAbuse(), "Avatar.ReportAbuse"); - view_listener_t::addMenu(new LLAvatarToggleMyProfile(), "Avatar.ToggleMyProfile"); - view_listener_t::addMenu(new LLAvatarTogglePicks(), "Avatar.TogglePicks"); - view_listener_t::addMenu(new LLAvatarToggleSearch(), "Avatar.ToggleSearch"); - view_listener_t::addMenu(new LLAvatarResetSkeleton(), "Avatar.ResetSkeleton"); - view_listener_t::addMenu(new LLAvatarEnableResetSkeleton(), "Avatar.EnableResetSkeleton"); - view_listener_t::addMenu(new LLAvatarResetSkeletonAndAnimations(), "Avatar.ResetSkeletonAndAnimations"); - view_listener_t::addMenu(new LLAvatarResetSelfSkeletonAndAnimations(), "Avatar.ResetSelfSkeletonAndAnimations"); - enable.add("Avatar.IsMyProfileOpen", boost::bind(&my_profile_visible)); - enable.add("Avatar.IsPicksTabOpen", boost::bind(&picks_tab_visible)); - - commit.add("Avatar.OpenMarketplace", boost::bind(&LLWeb::loadURLExternal, gSavedSettings.getString("MarketplaceURL"))); - - view_listener_t::addMenu(new LLAvatarEnableAddFriend(), "Avatar.EnableAddFriend"); - enable.add("Avatar.EnableFreezeEject", boost::bind(&enable_freeze_eject, _2)); - - // Object pie menu - view_listener_t::addMenu(new LLObjectBuild(), "Object.Build"); - commit.add("Object.Touch", boost::bind(&handle_object_touch)); - commit.add("Object.ShowOriginal", boost::bind(&handle_object_show_original)); - commit.add("Object.SitOrStand", boost::bind(&handle_object_sit_or_stand)); - commit.add("Object.Delete", boost::bind(&handle_object_delete)); - view_listener_t::addMenu(new LLObjectAttachToAvatar(true), "Object.AttachToAvatar"); - view_listener_t::addMenu(new LLObjectAttachToAvatar(false), "Object.AttachAddToAvatar"); - view_listener_t::addMenu(new LLObjectReturn(), "Object.Return"); - commit.add("Object.Duplicate", boost::bind(&LLSelectMgr::duplicate, LLSelectMgr::getInstance())); - view_listener_t::addMenu(new LLObjectReportAbuse(), "Object.ReportAbuse"); - view_listener_t::addMenu(new LLObjectMute(), "Object.Mute"); - - enable.add("Object.VisibleTake", boost::bind(&visible_take_object)); - enable.add("Object.VisibleTakeMultiple", boost::bind(&is_multiple_selection)); - enable.add("Object.VisibleTakeSingle", boost::bind(&is_single_selection)); - enable.add("Object.EnableTakeMultiple", boost::bind(&enable_take_objects)); - enable.add("Object.VisibleBuy", boost::bind(&visible_buy_object)); - - commit.add("Object.Buy", boost::bind(&handle_buy)); - commit.add("Object.Edit", boost::bind(&handle_object_edit)); - commit.add("Object.Edit", boost::bind(&handle_object_edit)); - commit.add("Object.EditGLTFMaterial", boost::bind(&handle_object_edit_gltf_material)); - commit.add("Object.Inspect", boost::bind(&handle_object_inspect)); - commit.add("Object.Open", boost::bind(&handle_object_open)); - commit.add("Object.Take", boost::bind(&handle_take, false)); - commit.add("Object.TakeSeparate", boost::bind(&handle_take, true)); - commit.add("Object.TakeSeparateCopy", boost::bind(&handle_take_separate_copy)); - commit.add("Object.ShowInspector", boost::bind(&handle_object_show_inspector)); - enable.add("Object.EnableInspect", boost::bind(&enable_object_inspect)); - enable.add("Object.EnableEditGLTFMaterial", boost::bind(&enable_object_edit_gltf_material)); - enable.add("Object.EnableOpen", boost::bind(&enable_object_open)); - enable.add("Object.EnableTouch", boost::bind(&enable_object_touch, _1)); - enable.add("Object.EnableDelete", boost::bind(&enable_object_delete)); - enable.add("Object.EnableWear", boost::bind(&object_is_wearable)); - - enable.add("Object.EnableStandUp", boost::bind(&enable_object_stand_up)); - enable.add("Object.EnableSit", boost::bind(&enable_object_sit, _1)); - - view_listener_t::addMenu(new LLObjectEnableReturn(), "Object.EnableReturn"); - enable.add("Object.EnableDuplicate", boost::bind(&LLSelectMgr::canDuplicate, LLSelectMgr::getInstance())); - view_listener_t::addMenu(new LLObjectEnableReportAbuse(), "Object.EnableReportAbuse"); - - enable.add("Avatar.EnableMute", boost::bind(&enable_object_mute)); - enable.add("Object.EnableMute", boost::bind(&enable_object_mute)); - enable.add("Object.EnableUnmute", boost::bind(&enable_object_unmute)); - enable.add("Object.EnableBuy", boost::bind(&enable_buy_object)); - commit.add("Object.ZoomIn", boost::bind(&handle_look_at_selection, "zoom")); - - // Attachment pie menu - enable.add("Attachment.Label", boost::bind(&onEnableAttachmentLabel, _1, _2)); - view_listener_t::addMenu(new LLAttachmentDrop(), "Attachment.Drop"); - view_listener_t::addMenu(new LLAttachmentDetachFromPoint(), "Attachment.DetachFromPoint"); - view_listener_t::addMenu(new LLAttachmentDetach(), "Attachment.Detach"); - view_listener_t::addMenu(new LLAttachmentPointFilled(), "Attachment.PointFilled"); - view_listener_t::addMenu(new LLAttachmentEnableDrop(), "Attachment.EnableDrop"); - view_listener_t::addMenu(new LLAttachmentEnableDetach(), "Attachment.EnableDetach"); - - // Land pie menu - view_listener_t::addMenu(new LLLandBuild(), "Land.Build"); - view_listener_t::addMenu(new LLLandSit(), "Land.Sit"); - view_listener_t::addMenu(new LLLandCanSit(), "Land.CanSit"); - view_listener_t::addMenu(new LLLandBuyPass(), "Land.BuyPass"); - view_listener_t::addMenu(new LLLandEdit(), "Land.Edit"); - - // Particle muting - view_listener_t::addMenu(new LLMuteParticle(), "Particle.Mute"); - - view_listener_t::addMenu(new LLLandEnableBuyPass(), "Land.EnableBuyPass"); - commit.add("Land.Buy", boost::bind(&handle_buy_land)); - - // Generic actions - commit.add("ReportAbuse", boost::bind(&handle_report_abuse)); - commit.add("BuyCurrency", boost::bind(&handle_buy_currency)); - view_listener_t::addMenu(new LLShowHelp(), "ShowHelp"); - view_listener_t::addMenu(new LLToggleHelp(), "ToggleHelp"); - view_listener_t::addMenu(new LLToggleSpeak(), "ToggleSpeak"); - view_listener_t::addMenu(new LLPromptShowURL(), "PromptShowURL"); - view_listener_t::addMenu(new LLShowAgentProfile(), "ShowAgentProfile"); - view_listener_t::addMenu(new LLShowAgentProfilePicks(), "ShowAgentProfilePicks"); - view_listener_t::addMenu(new LLToggleAgentProfile(), "ToggleAgentProfile"); - view_listener_t::addMenu(new LLToggleControl(), "ToggleControl"); - view_listener_t::addMenu(new LLToggleShaderControl(), "ToggleShaderControl"); - view_listener_t::addMenu(new LLCheckControl(), "CheckControl"); - view_listener_t::addMenu(new LLGoToObject(), "GoToObject"); - commit.add("PayObject", boost::bind(&handle_give_money_dialog)); - - commit.add("Inventory.NewWindow", boost::bind(&LLPanelMainInventory::newWindow)); - - enable.add("EnablePayObject", boost::bind(&enable_pay_object)); - enable.add("EnablePayAvatar", boost::bind(&enable_pay_avatar)); - enable.add("EnableEdit", boost::bind(&enable_object_edit)); - enable.add("EnableMuteParticle", boost::bind(&enable_mute_particle)); - enable.add("VisibleBuild", boost::bind(&enable_object_build)); - commit.add("Pathfinding.Linksets.Select", boost::bind(&LLFloaterPathfindingLinksets::openLinksetsWithSelectedObjects)); - enable.add("EnableSelectInPathfindingLinksets", boost::bind(&enable_object_select_in_pathfinding_linksets)); - enable.add("VisibleSelectInPathfindingLinksets", boost::bind(&visible_object_select_in_pathfinding_linksets)); - commit.add("Pathfinding.Characters.Select", boost::bind(&LLFloaterPathfindingCharacters::openCharactersWithSelectedObjects)); - enable.add("EnableSelectInPathfindingCharacters", boost::bind(&enable_object_select_in_pathfinding_characters)); - enable.add("Advanced.EnableErrorOSException", boost::bind(&enable_os_exception)); - - view_listener_t::addMenu(new LLFloaterVisible(), "FloaterVisible"); - view_listener_t::addMenu(new LLShowSidetrayPanel(), "ShowSidetrayPanel"); - view_listener_t::addMenu(new LLSidetrayPanelVisible(), "SidetrayPanelVisible"); - view_listener_t::addMenu(new LLSomethingSelected(), "SomethingSelected"); - view_listener_t::addMenu(new LLSomethingSelectedNoHUD(), "SomethingSelectedNoHUD"); - view_listener_t::addMenu(new LLEditableSelected(), "EditableSelected"); - view_listener_t::addMenu(new LLEditableSelectedMono(), "EditableSelectedMono"); - view_listener_t::addMenu(new LLToggleUIHints(), "ToggleUIHints"); -} +/** + * @file llviewermenu.cpp + * @brief Builds menus out of items. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#ifdef INCLUDE_VLD +#include "vld.h" +#endif + +#include "llviewermenu.h" + +// linden library includes +#include "llavatarnamecache.h" // IDEVO (I Are Not Men!) +#include "llcombobox.h" +#include "llcoros.h" +#include "llfloaterreg.h" +#include "llfloatersidepanelcontainer.h" +#include "llinventorypanel.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "llviewereventrecorder.h" + +// newview includes +#include "llagent.h" +#include "llagentaccess.h" +#include "llagentbenefits.h" +#include "llagentcamera.h" +#include "llagentui.h" +#include "llagentwearables.h" +#include "llagentpilot.h" +#include "llcompilequeue.h" +#include "llconsole.h" +#include "lldebugview.h" +#include "lldiskcache.h" +#include "llenvironment.h" +#include "llfilepicker.h" +#include "llfirstuse.h" +#include "llfloaterabout.h" +#include "llfloaterbuy.h" +#include "llfloaterbuycontents.h" +#include "llbuycurrencyhtml.h" +#include "llfloatergodtools.h" +#include "llfloaterimcontainer.h" +#include "llfloaterland.h" +#include "llfloaterimnearbychat.h" +#include "llfloaterlandholdings.h" +#include "llfloaterpathfindingcharacters.h" +#include "llfloaterpathfindinglinksets.h" +#include "llfloaterpay.h" +#include "llfloaterreporter.h" +#include "llfloatersearch.h" +#include "llfloaterscriptdebug.h" +#include "llfloatersnapshot.h" +#include "llfloatertools.h" +#include "llfloaterworldmap.h" +#include "llfloaterbuildoptions.h" +#include "llavataractions.h" +#include "lllandmarkactions.h" +#include "llgroupmgr.h" +#include "lltooltip.h" +#include "lltoolface.h" +#include "llhints.h" +#include "llhudeffecttrail.h" +#include "llhudmanager.h" +#include "llimview.h" +#include "llinventorybridge.h" +#include "llinventorydefines.h" +#include "llinventoryfunctions.h" +#include "llpanellogin.h" +#include "llpanelblockedlist.h" +#include "llpanelmaininventory.h" +#include "llmarketplacefunctions.h" +#include "llmaterialeditor.h" +#include "llmenuoptionpathfindingrebakenavmesh.h" +#include "llmoveview.h" +#include "llnavigationbar.h" +#include "llparcel.h" +#include "llrootview.h" +#include "llsceneview.h" +#include "llscenemonitor.h" +#include "llselectmgr.h" +#include "llsidepanelappearance.h" +#include "llspellcheckmenuhandler.h" +#include "llstatusbar.h" +#include "lltextureview.h" +#include "lltoolbarview.h" +#include "lltoolcomp.h" +#include "lltoolmgr.h" +#include "lltoolpie.h" +#include "lltoolselectland.h" +#include "lltrans.h" +#include "llviewerdisplay.h" //for gWindowResized +#include "llviewergenericmessage.h" +#include "llviewerhelp.h" +#include "llviewermenufile.h" // init_menu_file() +#include "llviewermessage.h" +#include "llviewernetwork.h" +#include "llviewerobjectlist.h" +#include "llviewerparcelmgr.h" +#include "llviewerstats.h" +#include "llviewerstatsrecorder.h" +#include "llvoavatarself.h" +#include "llvoicevivox.h" +#include "llworld.h" +#include "llworldmap.h" +#include "pipeline.h" +#include "llviewerjoystick.h" +#include "llfloatercamera.h" +#include "lluilistener.h" +#include "llappearancemgr.h" +#include "lltrans.h" +#include "lltoolgrab.h" +#include "llwindow.h" +#include "llpathfindingmanager.h" +#include "llstartup.h" +#include "boost/unordered_map.hpp" +#include +#include +#include "llcleanup.h" +#include "llviewershadermgr.h" + +using namespace LLAvatarAppearanceDefines; + +typedef LLPointer LLViewerObjectPtr; + +static boost::unordered_map sDefaultItemLabels; + +bool enable_land_build(void*); +bool enable_object_build(void*); + +LLVOAvatar* find_avatar_from_object( LLViewerObject* object ); +LLVOAvatar* find_avatar_from_object( const LLUUID& object_id ); + +void handle_test_load_url(void*); + +// +// Evil hackish imported globals + +//extern bool gHideSelectedObjects; +//extern bool gAllowSelectAvatar; +//extern bool gDebugAvatarRotation; +extern bool gDebugClicks; +extern bool gDebugWindowProc; +extern bool gShaderProfileFrame; + +//extern bool gDebugTextEditorTips; +//extern bool gDebugSelectMgr; + +// +// Globals +// + +LLMenuBarGL *gMenuBarView = NULL; +LLViewerMenuHolderGL *gMenuHolder = NULL; +LLMenuGL *gPopupMenuView = NULL; +LLMenuGL *gEditMenu = NULL; +LLMenuBarGL *gLoginMenuBarView = NULL; + +// Pie menus +LLContextMenu *gMenuAvatarSelf = NULL; +LLContextMenu *gMenuAvatarOther = NULL; +LLContextMenu *gMenuObject = NULL; +LLContextMenu *gMenuAttachmentSelf = NULL; +LLContextMenu *gMenuAttachmentOther = NULL; +LLContextMenu *gMenuLand = NULL; +LLContextMenu *gMenuMuteParticle = NULL; + +const std::string SAVE_INTO_TASK_INVENTORY("Save Object Back to Object Contents"); + +LLMenuGL* gAttachSubMenu = NULL; +LLMenuGL* gDetachSubMenu = NULL; +LLMenuGL* gTakeOffClothes = NULL; +LLMenuGL* gDetachAvatarMenu = NULL; +LLMenuGL* gDetachHUDAvatarMenu = NULL; +LLContextMenu* gAttachScreenPieMenu = NULL; +LLContextMenu* gAttachPieMenu = NULL; +LLContextMenu* gAttachBodyPartPieMenus[9]; +LLContextMenu* gDetachPieMenu = NULL; +LLContextMenu* gDetachScreenPieMenu = NULL; +LLContextMenu* gDetachAttSelfMenu = NULL; +LLContextMenu* gDetachHUDAttSelfMenu = NULL; +LLContextMenu* gDetachBodyPartPieMenus[9]; + +// +// Local prototypes + +// File Menu +void handle_compress_image(void*); +void handle_compress_file_test(void*); + + +// Edit menu +void handle_dump_group_info(void *); +void handle_dump_capabilities_info(void *); + +// Advanced->Consoles menu +void handle_region_dump_settings(void*); +void handle_region_dump_temp_asset_data(void*); +void handle_region_clear_temp_asset_data(void*); + +// Object pie menu +bool sitting_on_selection(); + +void near_sit_object(); +//void label_sit_or_stand(std::string& label, void*); +// buy and take alias into the same UI positions, so these +// declarations handle this mess. +bool is_selection_buy_not_take(); +S32 selection_price(); +bool enable_take(); +void handle_object_show_inspector(); +void handle_avatar_show_inspector(); +bool confirm_take(const LLSD& notification, const LLSD& response, LLObjectSelectionHandle selection_handle); +bool confirm_take_separate(const LLSD ¬ification, const LLSD &response, LLObjectSelectionHandle selection_handle); + +void handle_buy_object(LLSaleInfo sale_info); +void handle_buy_contents(LLSaleInfo sale_info); + +// Land pie menu +void near_sit_down_point(bool success, void *); + +// Avatar pie menu + +// Debug menu + + +void velocity_interpolate( void* ); +void handle_visual_leak_detector_toggle(void*); +void handle_rebake_textures(void*); +bool check_admin_override(void*); +void handle_admin_override_toggle(void*); +#ifdef TOGGLE_HACKED_GODLIKE_VIEWER +void handle_toggle_hacked_godmode(void*); +bool check_toggle_hacked_godmode(void*); +bool enable_toggle_hacked_godmode(void*); +#endif + +void toggle_show_xui_names(void *); +bool check_show_xui_names(void *); + +// Debug UI + +void handle_buy_currency_test(void*); + +void handle_god_mode(void*); + +// God menu +void handle_leave_god_mode(void*); + + +void handle_reset_view(); + +void handle_duplicate_in_place(void*); + +void handle_object_owner_self(void*); +void handle_object_owner_permissive(void*); +void handle_object_lock(void*); +void handle_object_asset_ids(void*); +void force_take_copy(void*); + +void handle_force_parcel_owner_to_me(void*); +void handle_force_parcel_to_content(void*); +void handle_claim_public_land(void*); + +void handle_god_request_avatar_geometry(void *); // Hack for easy testing of new avatar geometry +void reload_vertex_shader(void *); +void handle_disconnect_viewer(void *); + +void force_error_breakpoint(void *); +void force_error_llerror(void *); +void force_error_llerror_msg(void*); +void force_error_bad_memory_access(void *); +void force_error_infinite_loop(void *); +void force_error_software_exception(void *); +void force_error_os_exception(void*); +void force_error_driver_crash(void *); +void force_error_coroutine_crash(void *); +void force_error_thread_crash(void *); + +void handle_force_delete(void*); +void print_object_info(void*); +void print_agent_nvpairs(void*); +void toggle_debug_menus(void*); +void upload_done_callback(const LLUUID& uuid, void* user_data, S32 result, LLExtStat ext_status); +void dump_select_mgr(void*); + +void dump_inventory(void*); +void toggle_visibility(void*); +bool get_visibility(void*); + +// Avatar Pie menu +void request_friendship(const LLUUID& agent_id); + +// Tools menu +void handle_selected_texture_info(void*); +void handle_selected_material_info(); + +void handle_dump_followcam(void*); +void handle_viewer_enable_message_log(void*); +void handle_viewer_disable_message_log(void*); + +bool enable_buy_land(void*); + +// Help menu + +void handle_test_male(void *); +void handle_test_female(void *); +void handle_dump_attachments(void *); +void handle_dump_avatar_local_textures(void*); +void handle_debug_avatar_textures(void*); +void handle_grab_baked_texture(void*); +bool enable_grab_baked_texture(void*); +void handle_dump_region_object_cache(void*); +void handle_reset_interest_lists(void *); + +bool enable_save_into_task_inventory(void*); + +bool enable_detach(const LLSD& = LLSD()); +void menu_toggle_attached_lights(void* user_data); +void menu_toggle_attached_particles(void* user_data); + +class LLMenuParcelObserver : public LLParcelObserver +{ +public: + LLMenuParcelObserver(); + ~LLMenuParcelObserver(); + virtual void changed(); +}; + +static LLMenuParcelObserver* gMenuParcelObserver = NULL; + +static LLUIListener sUIListener; + +LLMenuParcelObserver::LLMenuParcelObserver() +{ + LLViewerParcelMgr::getInstance()->addObserver(this); +} + +LLMenuParcelObserver::~LLMenuParcelObserver() +{ + LLViewerParcelMgr::getInstance()->removeObserver(this); +} + +void LLMenuParcelObserver::changed() +{ + LLParcel *parcel = LLViewerParcelMgr::getInstance()->getParcelSelection()->getParcel(); + if (gMenuLand && parcel) + { + LLView* child = gMenuLand->findChild("Land Buy Pass"); + if (child) + { + child->setEnabled(LLPanelLandGeneral::enableBuyPass(NULL) && !(parcel->getOwnerID() == gAgent.getID())); + } + + child = gMenuLand->findChild("Land Buy"); + if (child) + { + bool buyable = enable_buy_land(NULL); + child->setEnabled(buyable); + } + } +} + + +void initialize_menus(); + +//----------------------------------------------------------------------------- +// Initialize main menus +// +// HOW TO NAME MENUS: +// +// First Letter Of Each Word Is Capitalized, Even At Or And +// +// Items that lead to dialog boxes end in "..." +// +// Break up groups of more than 6 items with separators +//----------------------------------------------------------------------------- + +void set_merchant_SLM_menu() +{ + // All other cases (new merchant, not merchant, migrated merchant): show the new Marketplace Listings menu and enable the tool + gMenuHolder->getChild("MarketplaceListings")->setVisible(true); + LLCommand* command = LLCommandManager::instance().getCommand("marketplacelistings"); + gToolBarView->enableCommand(command->id(), true); + + const LLUUID marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS); + if (marketplacelistings_id.isNull()) + { + U32 mkt_status = LLMarketplaceData::instance().getSLMStatus(); + bool is_merchant = (mkt_status == MarketplaceStatusCodes::MARKET_PLACE_MERCHANT) || (mkt_status == MarketplaceStatusCodes::MARKET_PLACE_MIGRATED_MERCHANT); + if (is_merchant) + { + gInventory.ensureCategoryForTypeExists(LLFolderType::FT_MARKETPLACE_LISTINGS); + LL_WARNS("SLM") << "Creating the marketplace listings folder for a merchant" << LL_ENDL; + } + } +} + +void check_merchant_status(bool force) +{ + if (force) + { + // Reset the SLM status: we actually want to check again, that's the point of calling check_merchant_status() + LLMarketplaceData::instance().setSLMStatus(MarketplaceStatusCodes::MARKET_PLACE_NOT_INITIALIZED); + } + // Hide SLM related menu item + gMenuHolder->getChild("MarketplaceListings")->setVisible(false); + + // Also disable the toolbar button for Marketplace Listings + LLCommand* command = LLCommandManager::instance().getCommand("marketplacelistings"); + gToolBarView->enableCommand(command->id(), false); + + // Launch an SLM test connection to get the merchant status + LLMarketplaceData::instance().initializeSLM(boost::bind(&set_merchant_SLM_menu)); +} + +void init_menus() +{ + // Initialize actions + initialize_menus(); + + /// + /// Popup menu + /// + /// The popup menu is now populated by the show_context_menu() + /// method. + + LLMenuGL::Params menu_params; + menu_params.name = "Popup"; + menu_params.visible = false; + gPopupMenuView = LLUICtrlFactory::create(menu_params); + gMenuHolder->addChild( gPopupMenuView ); + + /// + /// Context menus + /// + + const widget_registry_t& registry = + LLViewerMenuHolderGL::child_registry_t::instance(); + gEditMenu = LLUICtrlFactory::createFromFile("menu_edit.xml", gMenuHolder, registry); + gMenuAvatarSelf = LLUICtrlFactory::createFromFile( + "menu_avatar_self.xml", gMenuHolder, registry); + gMenuAvatarOther = LLUICtrlFactory::createFromFile( + "menu_avatar_other.xml", gMenuHolder, registry); + + gDetachScreenPieMenu = gMenuHolder->getChild("Object Detach HUD", true); + gDetachPieMenu = gMenuHolder->getChild("Object Detach", true); + + gMenuObject = LLUICtrlFactory::createFromFile( + "menu_object.xml", gMenuHolder, registry); + + gAttachScreenPieMenu = gMenuHolder->getChild("Object Attach HUD"); + gAttachPieMenu = gMenuHolder->getChild("Object Attach"); + + gMenuAttachmentSelf = LLUICtrlFactory::createFromFile( + "menu_attachment_self.xml", gMenuHolder, registry); + gMenuAttachmentOther = LLUICtrlFactory::createFromFile( + "menu_attachment_other.xml", gMenuHolder, registry); + + gDetachHUDAttSelfMenu = gMenuHolder->getChild("Detach Self HUD", true); + gDetachAttSelfMenu = gMenuHolder->getChild("Detach Self", true); + + gMenuLand = LLUICtrlFactory::createFromFile( + "menu_land.xml", gMenuHolder, registry); + + gMenuMuteParticle = LLUICtrlFactory::createFromFile( + "menu_mute_particle.xml", gMenuHolder, registry); + + /// + /// set up the colors + /// + LLColor4 color; + + LLColor4 context_menu_color = LLUIColorTable::instance().getColor("MenuPopupBgColor"); + + gMenuAvatarSelf->setBackgroundColor( context_menu_color ); + gMenuAvatarOther->setBackgroundColor( context_menu_color ); + gMenuObject->setBackgroundColor( context_menu_color ); + gMenuAttachmentSelf->setBackgroundColor( context_menu_color ); + gMenuAttachmentOther->setBackgroundColor( context_menu_color ); + + gMenuLand->setBackgroundColor( context_menu_color ); + + color = LLUIColorTable::instance().getColor( "MenuPopupBgColor" ); + gPopupMenuView->setBackgroundColor( color ); + + // If we are not in production, use a different color to make it apparent. + if (LLGridManager::getInstance()->isInProductionGrid()) + { + color = LLUIColorTable::instance().getColor( "MenuBarBgColor" ); + } + else + { + color = LLUIColorTable::instance().getColor( "MenuNonProductionBgColor" ); + } + + LLView* menu_bar_holder = gViewerWindow->getRootView()->getChildView("menu_bar_holder"); + + gMenuBarView = LLUICtrlFactory::getInstance()->createFromFile("menu_viewer.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + gMenuBarView->setRect(LLRect(0, menu_bar_holder->getRect().mTop, 0, menu_bar_holder->getRect().mTop - MENU_BAR_HEIGHT)); + gMenuBarView->setBackgroundColor( color ); + + menu_bar_holder->addChild(gMenuBarView); + + gViewerWindow->setMenuBackgroundColor(false, + LLGridManager::getInstance()->isInProductionGrid()); + + // *TODO:Also fix cost in llfolderview.cpp for Inventory menus + const std::string texture_upload_cost_str = std::to_string(LLAgentBenefitsMgr::current().getTextureUploadCost()); + const std::string sound_upload_cost_str = std::to_string(LLAgentBenefitsMgr::current().getSoundUploadCost()); + const std::string animation_upload_cost_str = std::to_string(LLAgentBenefitsMgr::current().getAnimationUploadCost()); + gMenuHolder->childSetLabelArg("Upload Image", "[COST]", texture_upload_cost_str); + gMenuHolder->childSetLabelArg("Upload Sound", "[COST]", sound_upload_cost_str); + gMenuHolder->childSetLabelArg("Upload Animation", "[COST]", animation_upload_cost_str); + + gAttachSubMenu = gMenuBarView->findChildMenuByName("Attach Object", true); + gDetachSubMenu = gMenuBarView->findChildMenuByName("Detach Object", true); + + gDetachAvatarMenu = gMenuHolder->getChild("Avatar Detach", true); + gDetachHUDAvatarMenu = gMenuHolder->getChild("Avatar Detach HUD", true); + + // Don't display the Memory console menu if the feature is turned off + LLMenuItemCheckGL *memoryMenu = gMenuBarView->getChild("Memory", true); + if (memoryMenu) + { + memoryMenu->setVisible(false); + } + + gMenuBarView->createJumpKeys(); + + // Let land based option enable when parcel changes + gMenuParcelObserver = new LLMenuParcelObserver(); + + gLoginMenuBarView = LLUICtrlFactory::getInstance()->createFromFile("menu_login.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + gLoginMenuBarView->arrangeAndClear(); + LLRect menuBarRect = gLoginMenuBarView->getRect(); + menuBarRect.setLeftTopAndSize(0, menu_bar_holder->getRect().getHeight(), menuBarRect.getWidth(), menuBarRect.getHeight()); + gLoginMenuBarView->setRect(menuBarRect); + gLoginMenuBarView->setBackgroundColor( color ); + menu_bar_holder->addChild(gLoginMenuBarView); + + // tooltips are on top of EVERYTHING, including menus + gViewerWindow->getRootView()->sendChildToFront(gToolTipView); +} + +/////////////////// +// SHOW CONSOLES // +/////////////////// + + +class LLAdvancedToggleConsole : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string console_type = userdata.asString(); + if ("texture" == console_type) + { + toggle_visibility( (void*)gTextureView ); + } + else if ("debug" == console_type) + { + toggle_visibility( (void*)static_cast(gDebugView->mDebugConsolep)); + } + else if ("fast timers" == console_type) + { + LLFloaterReg::toggleInstance("block_timers"); + } + else if ("scene view" == console_type) + { + toggle_visibility( (void*)gSceneView); + } + else if ("scene monitor" == console_type) + { + toggle_visibility( (void*)gSceneMonitorView); + } + + return true; + } +}; +class LLAdvancedCheckConsole : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string console_type = userdata.asString(); + bool new_value = false; + if ("texture" == console_type) + { + new_value = get_visibility( (void*)gTextureView ); + } + else if ("debug" == console_type) + { + new_value = get_visibility( (void*)((LLView*)gDebugView->mDebugConsolep) ); + } + else if ("fast timers" == console_type) + { + new_value = LLFloaterReg::instanceVisible("block_timers"); + } + else if ("scene view" == console_type) + { + new_value = get_visibility( (void*) gSceneView); + } + else if ("scene monitor" == console_type) + { + new_value = get_visibility( (void*) gSceneMonitorView); + } + + return new_value; + } +}; + + +////////////////////////// +// DUMP INFO TO CONSOLE // +////////////////////////// + + +class LLAdvancedDumpInfoToConsole : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + gDebugView->mDebugConsolep->setVisible(true); + std::string info_type = userdata.asString(); + if ("region" == info_type) + { + handle_region_dump_settings(NULL); + } + else if ("group" == info_type) + { + handle_dump_group_info(NULL); + } + else if ("capabilities" == info_type) + { + handle_dump_capabilities_info(NULL); + } + return true; + } +}; + + +////////////// +// HUD INFO // +////////////// + + +class LLAdvancedToggleHUDInfo : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string info_type = userdata.asString(); + + if ("camera" == info_type) + { + gDisplayCameraPos = !(gDisplayCameraPos); + } + else if ("wind" == info_type) + { + gDisplayWindInfo = !(gDisplayWindInfo); + } + else if ("fov" == info_type) + { + gDisplayFOV = !(gDisplayFOV); + } + else if ("badge" == info_type) + { + gDisplayBadge = !(gDisplayBadge); + } + return true; + } +}; + +class LLAdvancedCheckHUDInfo : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string info_type = userdata.asString(); + bool new_value = false; + if ("camera" == info_type) + { + new_value = gDisplayCameraPos; + } + else if ("wind" == info_type) + { + new_value = gDisplayWindInfo; + } + else if ("fov" == info_type) + { + new_value = gDisplayFOV; + } + else if ("badge" == info_type) + { + new_value = gDisplayBadge; + } + return new_value; + } +}; + + +/////////////////////// +// CLEAR GROUP CACHE // +/////////////////////// + +class LLAdvancedClearGroupCache : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLGroupMgr::debugClearAllGroups(NULL); + return true; + } +}; + + + + +///////////////// +// RENDER TYPE // +///////////////// +U32 render_type_from_string(std::string render_type) +{ + if ("simple" == render_type) + { + return LLPipeline::RENDER_TYPE_SIMPLE; + } + else if ("alpha" == render_type) + { + return LLPipeline::RENDER_TYPE_ALPHA; + } + else if ("tree" == render_type) + { + return LLPipeline::RENDER_TYPE_TREE; + } + else if ("character" == render_type) + { + return LLPipeline::RENDER_TYPE_AVATAR; + } + else if ("controlAV" == render_type) // Animesh + { + return LLPipeline::RENDER_TYPE_CONTROL_AV; + } + else if ("surfacePatch" == render_type) + { + return LLPipeline::RENDER_TYPE_TERRAIN; + } + else if ("sky" == render_type) + { + return LLPipeline::RENDER_TYPE_SKY; + } + else if ("water" == render_type) + { + return LLPipeline::RENDER_TYPE_WATER; + } + else if ("volume" == render_type) + { + return LLPipeline::RENDER_TYPE_VOLUME; + } + else if ("grass" == render_type) + { + return LLPipeline::RENDER_TYPE_GRASS; + } + else if ("clouds" == render_type) + { + return LLPipeline::RENDER_TYPE_CLOUDS; + } + else if ("particles" == render_type) + { + return LLPipeline::RENDER_TYPE_PARTICLES; + } + else if ("bump" == render_type) + { + return LLPipeline::RENDER_TYPE_BUMP; + } + else if ("pbr" == render_type) + { + return LLPipeline::RENDER_TYPE_GLTF_PBR; + } + else + { + return 0; + } +} + + +class LLAdvancedToggleRenderType : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + U32 render_type = render_type_from_string( userdata.asString() ); + if ( render_type != 0 ) + { + LLPipeline::toggleRenderTypeControl( render_type ); + } + return true; + } +}; + + +class LLAdvancedCheckRenderType : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + U32 render_type = render_type_from_string( userdata.asString() ); + bool new_value = false; + + if ( render_type != 0 ) + { + new_value = LLPipeline::hasRenderTypeControl( render_type ); + } + + return new_value; + } +}; + + +///////////// +// FEATURE // +///////////// +U32 feature_from_string(std::string feature) +{ + if ("ui" == feature) + { + return LLPipeline::RENDER_DEBUG_FEATURE_UI; + } + else if ("selected" == feature) + { + return LLPipeline::RENDER_DEBUG_FEATURE_SELECTED; + } + else if ("highlighted" == feature) + { + return LLPipeline::RENDER_DEBUG_FEATURE_HIGHLIGHTED; + } + else if ("dynamic textures" == feature) + { + return LLPipeline::RENDER_DEBUG_FEATURE_DYNAMIC_TEXTURES; + } + else if ("foot shadows" == feature) + { + return LLPipeline::RENDER_DEBUG_FEATURE_FOOT_SHADOWS; + } + else if ("fog" == feature) + { + return LLPipeline::RENDER_DEBUG_FEATURE_FOG; + } + else if ("fr info" == feature) + { + return LLPipeline::RENDER_DEBUG_FEATURE_FR_INFO; + } + else if ("flexible" == feature) + { + return LLPipeline::RENDER_DEBUG_FEATURE_FLEXIBLE; + } + else + { + return 0; + } +}; + + +class LLAdvancedToggleFeature : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + U32 feature = feature_from_string( userdata.asString() ); + if ( feature != 0 ) + { + LLPipeline::toggleRenderDebugFeature( feature ); + } + return true; + } +}; + +class LLAdvancedCheckFeature : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) +{ + U32 feature = feature_from_string( userdata.asString() ); + bool new_value = false; + + if ( feature != 0 ) + { + new_value = LLPipeline::toggleRenderDebugFeatureControl( feature ); + } + + return new_value; +} +}; + +class LLAdvancedCheckDisplayTextureDensity : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string mode = userdata.asString(); + if (!gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXEL_DENSITY)) + { + return mode == "none"; + } + if (mode == "current") + { + return LLViewerTexture::sDebugTexelsMode == LLViewerTexture::DEBUG_TEXELS_CURRENT; + } + else if (mode == "desired") + { + return LLViewerTexture::sDebugTexelsMode == LLViewerTexture::DEBUG_TEXELS_DESIRED; + } + else if (mode == "full") + { + return LLViewerTexture::sDebugTexelsMode == LLViewerTexture::DEBUG_TEXELS_FULL; + } + return false; + } +}; + +class LLAdvancedSetDisplayTextureDensity : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string mode = userdata.asString(); + if (mode == "none") + { + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXEL_DENSITY)) + { + gPipeline.toggleRenderDebug(LLPipeline::RENDER_DEBUG_TEXEL_DENSITY); + } + LLViewerTexture::sDebugTexelsMode = LLViewerTexture::DEBUG_TEXELS_OFF; + } + else if (mode == "current") + { + if (!gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXEL_DENSITY)) + { + gPipeline.toggleRenderDebug(LLPipeline::RENDER_DEBUG_TEXEL_DENSITY); + } + LLViewerTexture::sDebugTexelsMode = LLViewerTexture::DEBUG_TEXELS_CURRENT; + } + else if (mode == "desired") + { + if (!gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXEL_DENSITY)) + { + gPipeline.toggleRenderDebug(LLPipeline::RENDER_DEBUG_TEXEL_DENSITY); + } + gPipeline.setRenderDebugFeatureControl(LLPipeline::RENDER_DEBUG_TEXEL_DENSITY, true); + LLViewerTexture::sDebugTexelsMode = LLViewerTexture::DEBUG_TEXELS_DESIRED; + } + else if (mode == "full") + { + if (!gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXEL_DENSITY)) + { + gPipeline.toggleRenderDebug(LLPipeline::RENDER_DEBUG_TEXEL_DENSITY); + } + LLViewerTexture::sDebugTexelsMode = LLViewerTexture::DEBUG_TEXELS_FULL; + } + return true; + } +}; + + +////////////////// +// INFO DISPLAY // +////////////////// +U64 info_display_from_string(std::string info_display) +{ + if ("verify" == info_display) + { + return LLPipeline::RENDER_DEBUG_VERIFY; + } + else if ("bboxes" == info_display) + { + return LLPipeline::RENDER_DEBUG_BBOXES; + } + else if ("normals" == info_display) + { + return LLPipeline::RENDER_DEBUG_NORMALS; + } + else if ("points" == info_display) + { + return LLPipeline::RENDER_DEBUG_POINTS; + } + else if ("octree" == info_display) + { + return LLPipeline::RENDER_DEBUG_OCTREE; + } + else if ("shadow frusta" == info_display) + { + return LLPipeline::RENDER_DEBUG_SHADOW_FRUSTA; + } + else if ("physics shapes" == info_display) + { + return LLPipeline::RENDER_DEBUG_PHYSICS_SHAPES; + } + else if ("occlusion" == info_display) + { + return LLPipeline::RENDER_DEBUG_OCCLUSION; + } + else if ("render batches" == info_display) + { + return LLPipeline::RENDER_DEBUG_BATCH_SIZE; + } + else if ("update type" == info_display) + { + return LLPipeline::RENDER_DEBUG_UPDATE_TYPE; + } + else if ("texture anim" == info_display) + { + return LLPipeline::RENDER_DEBUG_TEXTURE_ANIM; + } + else if ("texture priority" == info_display) + { + return LLPipeline::RENDER_DEBUG_TEXTURE_PRIORITY; + } + else if ("texture area" == info_display) + { + return LLPipeline::RENDER_DEBUG_TEXTURE_AREA; + } + else if ("face area" == info_display) + { + return LLPipeline::RENDER_DEBUG_FACE_AREA; + } + else if ("lod info" == info_display) + { + return LLPipeline::RENDER_DEBUG_LOD_INFO; + } + else if ("lights" == info_display) + { + return LLPipeline::RENDER_DEBUG_LIGHTS; + } + else if ("particles" == info_display) + { + return LLPipeline::RENDER_DEBUG_PARTICLES; + } + else if ("composition" == info_display) + { + return LLPipeline::RENDER_DEBUG_COMPOSITION; + } + else if ("avatardrawinfo" == info_display) + { + return (LLPipeline::RENDER_DEBUG_AVATAR_DRAW_INFO); + } + else if ("glow" == info_display) + { + return LLPipeline::RENDER_DEBUG_GLOW; + } + else if ("collision skeleton" == info_display) + { + return LLPipeline::RENDER_DEBUG_AVATAR_VOLUME; + } + else if ("joints" == info_display) + { + return LLPipeline::RENDER_DEBUG_AVATAR_JOINTS; + } + else if ("raycast" == info_display) + { + return LLPipeline::RENDER_DEBUG_RAYCAST; + } + else if ("agent target" == info_display) + { + return LLPipeline::RENDER_DEBUG_AGENT_TARGET; + } + else if ("sculpt" == info_display) + { + return LLPipeline::RENDER_DEBUG_SCULPTED; + } + else if ("wind vectors" == info_display) + { + return LLPipeline::RENDER_DEBUG_WIND_VECTORS; + } + else if ("texel density" == info_display) + { + return LLPipeline::RENDER_DEBUG_TEXEL_DENSITY; + } + else if ("triangle count" == info_display) + { + return LLPipeline::RENDER_DEBUG_TRIANGLE_COUNT; + } + else if ("impostors" == info_display) + { + return LLPipeline::RENDER_DEBUG_IMPOSTORS; + } + else if ("reflection probes" == info_display) + { + return LLPipeline::RENDER_DEBUG_REFLECTION_PROBES; + } + else if ("probe updates" == info_display) + { + return LLPipeline::RENDER_DEBUG_PROBE_UPDATES; + } + else + { + LL_WARNS() << "unrecognized feature name '" << info_display << "'" << LL_ENDL; + return 0; + } +}; + +class LLAdvancedToggleInfoDisplay : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + U64 info_display = info_display_from_string( userdata.asString() ); + + LL_INFOS("ViewerMenu") << "toggle " << userdata.asString() << LL_ENDL; + + if ( info_display != 0 ) + { + LLPipeline::toggleRenderDebug( info_display ); + } + + return true; + } +}; + + +class LLAdvancedCheckInfoDisplay : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + U64 info_display = info_display_from_string( userdata.asString() ); + bool new_value = false; + + if ( info_display != 0 ) + { + new_value = LLPipeline::toggleRenderDebugControl( info_display ); + } + + return new_value; + } +}; + + +/////////////////////////// +//// RANDOMIZE FRAMERATE // +/////////////////////////// + + +class LLAdvancedToggleRandomizeFramerate : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + gRandomizeFramerate = !(gRandomizeFramerate); + return true; + } +}; + +class LLAdvancedCheckRandomizeFramerate : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = gRandomizeFramerate; + return new_value; + } +}; + +/////////////////////////// +//// PERIODIC SLOW FRAME // +/////////////////////////// + + +class LLAdvancedTogglePeriodicSlowFrame : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + gPeriodicSlowFrame = !(gPeriodicSlowFrame); + return true; + } +}; + +class LLAdvancedCheckPeriodicSlowFrame : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = gPeriodicSlowFrame; + return new_value; + } +}; + + +/////////////////////////// +// SELECTED TEXTURE INFO // +// +/////////////////////////// + + +class LLAdvancedSelectedTextureInfo : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_selected_texture_info(NULL); + return true; + } +}; + +////////////////////// +// TOGGLE WIREFRAME // +////////////////////// + +class LLAdvancedToggleWireframe : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + gUseWireframe = !(gUseWireframe); + + return true; + } +}; + +class LLAdvancedCheckWireframe : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + return gUseWireframe; + } +}; + + +////////////////////////// +// DUMP SCRIPTED CAMERA // +////////////////////////// + +class LLAdvancedDumpScriptedCamera : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_dump_followcam(NULL); + return true; +} +}; + + + +////////////////////////////// +// DUMP REGION OBJECT CACHE // +////////////////////////////// + + +class LLAdvancedDumpRegionObjectCache : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_dump_region_object_cache(NULL); + return true; + } +}; + +class LLAdvancedToggleInterestList360Mode : public view_listener_t +{ +public: + bool handleEvent(const LLSD &userdata) + { + // Toggle the mode - regions will get updated + if (gAgent.getInterestListMode() == IL_MODE_360) + { + gAgent.changeInterestListMode(IL_MODE_DEFAULT); + } + else + { + gAgent.changeInterestListMode(IL_MODE_360); + } + return true; + } +}; + +class LLAdvancedCheckInterestList360Mode : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + return (gAgent.getInterestListMode() == IL_MODE_360); + } +}; + +class LLAdvancedToggleStatsRecorder : public view_listener_t +{ + bool handleEvent(const LLSD &userdata) + { + if (LLViewerStatsRecorder::instance().isEnabled()) + { // Turn off both recording and logging + LLViewerStatsRecorder::instance().enableObjectStatsRecording(false); + } + else + { // Turn on both recording and logging + LLViewerStatsRecorder::instance().enableObjectStatsRecording(true, true); + } + return true; + } +}; + +class LLAdvancedCheckStatsRecorder : public view_listener_t +{ + bool handleEvent(const LLSD &userdata) + { // Use the logging state as the indicator of whether the stats recorder is on + return LLViewerStatsRecorder::instance().isLogging(); + } +}; + +class LLAdvancedResetInterestLists : public view_listener_t +{ + bool handleEvent(const LLSD &userdata) + { // Reset all region interest lists + handle_reset_interest_lists(NULL); + return true; + } +}; + + +class LLAdvancedBuyCurrencyTest : public view_listener_t + { + bool handleEvent(const LLSD& userdata) + { + handle_buy_currency_test(NULL); + return true; + } +}; + + +///////////////////// +// DUMP SELECT MGR // +///////////////////// + + +class LLAdvancedDumpSelectMgr : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + dump_select_mgr(NULL); + return true; + } +}; + + + +//////////////////// +// DUMP INVENTORY // +//////////////////// + + +class LLAdvancedDumpInventory : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + dump_inventory(NULL); + return true; + } +}; + + + +//////////////////////////////// +// PRINT SELECTED OBJECT INFO // +//////////////////////////////// + + +class LLAdvancedPrintSelectedObjectInfo : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + print_object_info(NULL); + return true; + } +}; + + + +////////////////////// +// PRINT AGENT INFO // +////////////////////// + + +class LLAdvancedPrintAgentInfo : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + print_agent_nvpairs(NULL); + return true; + } +}; + +////////////////// +// DEBUG CLICKS // +////////////////// + + +class LLAdvancedToggleDebugClicks : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + gDebugClicks = !(gDebugClicks); + return true; + } +}; + +class LLAdvancedCheckDebugClicks : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = gDebugClicks; + return new_value; + } +}; + + + +///////////////// +// DEBUG VIEWS // +///////////////// + + +class LLAdvancedToggleDebugViews : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLView::sDebugRects = !(LLView::sDebugRects); + return true; + } +}; + +class LLAdvancedCheckDebugViews : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = LLView::sDebugRects; + return new_value; + } +}; + + + +/////////////////// +// DEBUG UNICODE // +/////////////////// + + +class LLAdvancedToggleDebugUnicode : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLView::sDebugUnicode = !(LLView::sDebugUnicode); + return true; + } +}; + +class LLAdvancedCheckDebugUnicode : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + return LLView::sDebugUnicode; + } +}; + + + +////////////////// +// DEBUG CAMERA // +////////////////// + + +class LLAdvancedToggleDebugCamera : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLView::sDebugCamera = !(LLView::sDebugCamera); + LLFloaterCamera::onDebugCameraToggled(); + return true; + } +}; + +class LLAdvancedCheckDebugCamera : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + return LLView::sDebugCamera; + } +}; + + + +/////////////////////// +// XUI NAME TOOLTIPS // +/////////////////////// + + +class LLAdvancedToggleXUINameTooltips : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + toggle_show_xui_names(NULL); + return true; + } +}; + +class LLAdvancedCheckXUINameTooltips : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = check_show_xui_names(NULL); + return new_value; + } +}; + + + +//////////////////////// +// DEBUG MOUSE EVENTS // +//////////////////////// + + +class LLAdvancedToggleDebugMouseEvents : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLView::sDebugMouseHandling = !(LLView::sDebugMouseHandling); + return true; + } +}; + +class LLAdvancedCheckDebugMouseEvents : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = LLView::sDebugMouseHandling; + return new_value; + } +}; + + + +//////////////// +// DEBUG KEYS // +//////////////// + + +class LLAdvancedToggleDebugKeys : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLView::sDebugKeys = !(LLView::sDebugKeys); + return true; + } +}; + +class LLAdvancedCheckDebugKeys : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = LLView::sDebugKeys; + return new_value; + } +}; + + + +/////////////////////// +// DEBUG WINDOW PROC // +/////////////////////// + + +class LLAdvancedToggleDebugWindowProc : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + gDebugWindowProc = !(gDebugWindowProc); + return true; + } +}; + +class LLAdvancedCheckDebugWindowProc : public view_listener_t + { + bool handleEvent(const LLSD& userdata) + { + bool new_value = gDebugWindowProc; + return new_value; + } +}; + +// ------------------------------XUI MENU --------------------------- + +class LLAdvancedSendTestIms : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLIMModel::instance().testMessages(); + return true; +} +}; + + +/////////////// +// XUI NAMES // +/////////////// + + +class LLAdvancedToggleXUINames : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + toggle_show_xui_names(NULL); + return true; + } +}; + +class LLAdvancedCheckXUINames : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = check_show_xui_names(NULL); + return new_value; + } +}; + + +//////////////////////// +// GRAB BAKED TEXTURE // +//////////////////////// + + +class LLAdvancedGrabBakedTexture : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string texture_type = userdata.asString(); + if ("iris" == texture_type) + { + handle_grab_baked_texture( (void*)BAKED_EYES ); + } + else if ("head" == texture_type) + { + handle_grab_baked_texture( (void*)BAKED_HEAD ); + } + else if ("upper" == texture_type) + { + handle_grab_baked_texture( (void*)BAKED_UPPER ); + } + else if ("lower" == texture_type) + { + handle_grab_baked_texture( (void*)BAKED_LOWER ); + } + else if ("skirt" == texture_type) + { + handle_grab_baked_texture( (void*)BAKED_SKIRT ); + } + else if ("hair" == texture_type) + { + handle_grab_baked_texture( (void*)BAKED_HAIR ); + } + + return true; + } +}; + +class LLAdvancedEnableGrabBakedTexture : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) +{ + std::string texture_type = userdata.asString(); + bool new_value = false; + + if ("iris" == texture_type) + { + new_value = enable_grab_baked_texture( (void*)BAKED_EYES ); + } + else if ("head" == texture_type) + { + new_value = enable_grab_baked_texture( (void*)BAKED_HEAD ); + } + else if ("upper" == texture_type) + { + new_value = enable_grab_baked_texture( (void*)BAKED_UPPER ); + } + else if ("lower" == texture_type) + { + new_value = enable_grab_baked_texture( (void*)BAKED_LOWER ); + } + else if ("skirt" == texture_type) + { + new_value = enable_grab_baked_texture( (void*)BAKED_SKIRT ); + } + else if ("hair" == texture_type) + { + new_value = enable_grab_baked_texture( (void*)BAKED_HAIR ); + } + + return new_value; +} +}; + +/////////////////////// +// APPEARANCE TO XML // +/////////////////////// + + +class LLAdvancedEnableAppearanceToXML : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLViewerObject *obj = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + if (obj && obj->isAnimatedObject() && obj->getControlAvatar()) + { + return gSavedSettings.getBOOL("DebugAnimatedObjects"); + } + else if (obj && obj->isAttachment() && obj->getAvatar()) + { + return gSavedSettings.getBOOL("DebugAvatarAppearanceMessage"); + } + else if (obj && obj->isAvatar()) + { + // This has to be a non-control avatar, because control avs are invisible and unclickable. + return gSavedSettings.getBOOL("DebugAvatarAppearanceMessage"); + } + else + { + return false; + } + } +}; + +class LLAdvancedAppearanceToXML : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string emptyname; + LLViewerObject *obj = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + LLVOAvatar *avatar = NULL; + if (obj) + { + if (obj->isAvatar()) + { + avatar = obj->asAvatar(); + } + else + { + // If there is a selection, find the associated + // avatar. Normally there's only one obvious choice. But + // what should be returned if the object is in an attached + // animated object? getAvatar() will give the skeleton of + // the animated object. getAvatarAncestor() will give the + // actual human-driven avatar. + avatar = obj->getAvatar(); + } + } + else + { + // If no selection, use the self avatar. + avatar = gAgentAvatarp; + } + if (avatar) + { + avatar->dumpArchetypeXML(emptyname); + } + return true; + } +}; + + + +/////////////////////////////// +// TOGGLE CHARACTER GEOMETRY // +/////////////////////////////// + + +class LLAdvancedToggleCharacterGeometry : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_god_request_avatar_geometry(NULL); + return true; +} +}; + + + ///////////////////////////// +// TEST MALE / TEST FEMALE // +///////////////////////////// + +class LLAdvancedTestMale : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_test_male(NULL); + return true; + } +}; + + +class LLAdvancedTestFemale : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_test_female(NULL); + return true; + } +}; + +class LLAdvancedForceParamsToDefault : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLAgent::clearVisualParams(NULL); + return true; + } +}; + + +////////////////////////// +// ANIMATION SPEED // +////////////////////////// + +// Utility function to set all AV time factors to the same global value +static void set_all_animation_time_factors(F32 time_factor) +{ + LLMotionController::setCurrentTimeFactor(time_factor); + for (std::vector::iterator iter = LLCharacter::sInstances.begin(); + iter != LLCharacter::sInstances.end(); ++iter) + { + (*iter)->setAnimTimeFactor(time_factor); + } +} + +class LLAdvancedAnimTenFaster : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + //LL_INFOS() << "LLAdvancedAnimTenFaster" << LL_ENDL; + F32 time_factor = LLMotionController::getCurrentTimeFactor(); + time_factor = llmin(time_factor + 0.1f, 2.f); // Upper limit is 200% speed + set_all_animation_time_factors(time_factor); + return true; + } +}; + +class LLAdvancedAnimTenSlower : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + //LL_INFOS() << "LLAdvancedAnimTenSlower" << LL_ENDL; + F32 time_factor = LLMotionController::getCurrentTimeFactor(); + time_factor = llmax(time_factor - 0.1f, 0.1f); // Lower limit is at 10% of normal speed + set_all_animation_time_factors(time_factor); + return true; + } +}; + +class LLAdvancedAnimResetAll : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + set_all_animation_time_factors(1.f); + return true; + } +}; + + +////////////////////////// +// RELOAD VERTEX SHADER // +////////////////////////// + + +class LLAdvancedReloadVertexShader : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + reload_vertex_shader(NULL); + return true; + } +}; + + + +//////////////////// +// ANIMATION INFO // +//////////////////// + + +class LLAdvancedToggleAnimationInfo : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLVOAvatar::sShowAnimationDebug = !(LLVOAvatar::sShowAnimationDebug); + return true; + } +}; + +class LLAdvancedCheckAnimationInfo : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = LLVOAvatar::sShowAnimationDebug; + return new_value; + } +}; + + +////////////////// +// SHOW LOOK AT // +////////////////// + + +class LLAdvancedToggleShowLookAt : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLHUDEffectLookAt::sDebugLookAt = !(LLHUDEffectLookAt::sDebugLookAt); + return true; + } +}; + +class LLAdvancedCheckShowLookAt : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = LLHUDEffectLookAt::sDebugLookAt; + return new_value; + } +}; + + + +/////////////////// +// SHOW POINT AT // +/////////////////// + + +class LLAdvancedToggleShowPointAt : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLHUDEffectPointAt::sDebugPointAt = !(LLHUDEffectPointAt::sDebugPointAt); + return true; + } +}; + +class LLAdvancedCheckShowPointAt : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = LLHUDEffectPointAt::sDebugPointAt; + return new_value; + } +}; + + + +///////////////////////// +// DEBUG JOINT UPDATES // +///////////////////////// + + +class LLAdvancedToggleDebugJointUpdates : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLVOAvatar::sJointDebug = !(LLVOAvatar::sJointDebug); + return true; + } +}; + +class LLAdvancedCheckDebugJointUpdates : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = LLVOAvatar::sJointDebug; + return new_value; + } +}; + + + +///////////////// +// DISABLE LOD // +///////////////// + + +class LLAdvancedToggleDisableLOD : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLViewerJoint::sDisableLOD = !(LLViewerJoint::sDisableLOD); + return true; + } +}; + +class LLAdvancedCheckDisableLOD : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = LLViewerJoint::sDisableLOD; + return new_value; + } +}; + + + +///////////////////////// +// DEBUG CHARACTER VIS // +///////////////////////// + + +class LLAdvancedToggleDebugCharacterVis : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLVOAvatar::sDebugInvisible = !(LLVOAvatar::sDebugInvisible); + return true; + } +}; + +class LLAdvancedCheckDebugCharacterVis : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = LLVOAvatar::sDebugInvisible; + return new_value; + } +}; + + +////////////////////// +// DUMP ATTACHMENTS // +////////////////////// + + +class LLAdvancedDumpAttachments : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_dump_attachments(NULL); + return true; + } +}; + + + +///////////////////// +// REBAKE TEXTURES // +///////////////////// + + +class LLAdvancedRebakeTextures : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_rebake_textures(NULL); + return true; + } +}; + + +#if 1 //ndef LL_RELEASE_FOR_DOWNLOAD +/////////////////////////// +// DEBUG AVATAR TEXTURES // +/////////////////////////// + + +class LLAdvancedDebugAvatarTextures : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + if (gAgent.isGodlike()) + { + handle_debug_avatar_textures(NULL); + } + return true; + } +}; + +//////////////////////////////// +// DUMP AVATAR LOCAL TEXTURES // +//////////////////////////////// + + +class LLAdvancedDumpAvatarLocalTextures : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { +#ifndef LL_RELEASE_FOR_DOWNLOAD + handle_dump_avatar_local_textures(NULL); +#endif + return true; + } +}; + +#endif + +///////////////// +// MESSAGE LOG // +///////////////// + + +class LLAdvancedEnableMessageLog : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_viewer_enable_message_log(NULL); + return true; + } +}; + +class LLAdvancedDisableMessageLog : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_viewer_disable_message_log(NULL); + return true; + } +}; + +///////////////// +// DROP PACKET // +///////////////// + + +class LLAdvancedDropPacket : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + gMessageSystem->mPacketRing.dropPackets(1); + return true; + } +}; + +////////////////////// +// PURGE DISK CACHE // +////////////////////// + + +class LLAdvancedPurgeDiskCache : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop"); + LL::WorkQueue::ptr_t general_queue = LL::WorkQueue::getInstance("General"); + llassert_always(main_queue); + llassert_always(general_queue); + main_queue->postTo( + general_queue, + []() // Work done on general queue + { + LLDiskCache::getInstance()->purge(); + // Nothing needed to return + }, + [](){}); // Callback to main thread is empty as there is nothing left to do + + return true; + } +}; + + +//////////////////////// +// PURGE SHADER CACHE // +//////////////////////// + + +class LLAdvancedPurgeShaderCache : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLViewerShaderMgr::instance()->clearShaderCache(); + LLViewerShaderMgr::instance()->setShaders(); + return true; + } +}; + +//////////////////// +// EVENT Recorder // +/////////////////// + + +class LLAdvancedViewerEventRecorder : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string command = userdata.asString(); + if ("start playback" == command) + { + LL_INFOS() << "Event Playback starting" << LL_ENDL; + LLViewerEventRecorder::instance().playbackRecording(); + LL_INFOS() << "Event Playback completed" << LL_ENDL; + } + else if ("stop playback" == command) + { + // Future + } + else if ("start recording" == command) + { + LLViewerEventRecorder::instance().setEventLoggingOn(); + LL_INFOS() << "Event recording started" << LL_ENDL; + } + else if ("stop recording" == command) + { + LLViewerEventRecorder::instance().setEventLoggingOff(); + LL_INFOS() << "Event recording stopped" << LL_ENDL; + } + + return true; + } +}; + + + + +///////////////// +// AGENT PILOT // +///////////////// + + +class LLAdvancedAgentPilot : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string command = userdata.asString(); + if ("start playback" == command) + { + gAgentPilot.setNumRuns(-1); + gAgentPilot.startPlayback(); + } + else if ("stop playback" == command) + { + gAgentPilot.stopPlayback(); + } + else if ("start record" == command) + { + gAgentPilot.startRecord(); + } + else if ("stop record" == command) + { + gAgentPilot.stopRecord(); + } + + return true; + } +}; + + + +////////////////////// +// AGENT PILOT LOOP // +////////////////////// + + +class LLAdvancedToggleAgentPilotLoop : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + gAgentPilot.setLoop(!gAgentPilot.getLoop()); + return true; + } +}; + +class LLAdvancedCheckAgentPilotLoop : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = gAgentPilot.getLoop(); + return new_value; + } +}; + + +///////////////////////// +// SHOW OBJECT UPDATES // +///////////////////////// + + +class LLAdvancedToggleShowObjectUpdates : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + gShowObjectUpdates = !(gShowObjectUpdates); + return true; + } +}; + +class LLAdvancedCheckShowObjectUpdates : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = gShowObjectUpdates; + return new_value; + } +}; + + + +//////////////////// +// COMPRESS IMAGE // +//////////////////// + + +class LLAdvancedCompressImage : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_compress_image(NULL); + return true; + } +}; + + + +//////////////////////// +// COMPRESS FILE TEST // +//////////////////////// + +class LLAdvancedCompressFileTest : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_compress_file_test(NULL); + return true; + } +}; + + +///////////////////////// +// SHOW DEBUG SETTINGS // +///////////////////////// + + +class LLAdvancedShowDebugSettings : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLFloaterReg::showInstance("settings_debug",userdata); + return true; + } +}; + + + +//////////////////////// +// VIEW ADMIN OPTIONS // +//////////////////////// + +class LLAdvancedEnableViewAdminOptions : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + // Don't enable in god mode since the admin menu is shown anyway. + // Only enable if the user has set the appropriate debug setting. + bool new_value = !gAgent.getAgentAccess().isGodlikeWithoutAdminMenuFakery() && gSavedSettings.getBOOL("AdminMenu"); + return new_value; + } +}; + +class LLAdvancedToggleViewAdminOptions : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_admin_override_toggle(NULL); + return true; + } +}; + +class LLAdvancedToggleVisualLeakDetector : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_visual_leak_detector_toggle(NULL); + return true; + } +}; + +class LLAdvancedCheckViewAdminOptions : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = check_admin_override(NULL) || gAgent.isGodlike(); + return new_value; + } +}; + +////////////////// +// ADMIN STATUS // +////////////////// + + +class LLAdvancedRequestAdminStatus : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_god_mode(NULL); + return true; + } +}; + +class LLAdvancedLeaveAdminStatus : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_leave_god_mode(NULL); + return true; + } +}; + +////////////////////////// +// Advanced > Debugging // +////////////////////////// + +class LLAdvancedForceErrorBreakpoint : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + force_error_breakpoint(NULL); + return true; + } +}; + +class LLAdvancedForceErrorLlerror : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + force_error_llerror(NULL); + return true; + } +}; + +class LLAdvancedForceErrorLlerrorMsg: public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + force_error_llerror_msg(NULL); + return true; + } +}; + +class LLAdvancedForceErrorBadMemoryAccess : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + force_error_bad_memory_access(NULL); + return true; + } +}; + +class LLAdvancedForceErrorBadMemoryAccessCoro : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLCoros::instance().launch( + "AdvancedForceErrorBadMemoryAccessCoro", + [](){ + // Wait for one mainloop() iteration, letting the enclosing + // handleEvent() method return. + llcoro::suspend(); + force_error_bad_memory_access(NULL); + }); + return true; + } +}; + +class LLAdvancedForceErrorInfiniteLoop : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + force_error_infinite_loop(NULL); + return true; + } +}; + +class LLAdvancedForceErrorSoftwareException : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + force_error_software_exception(NULL); + return true; + } +}; + +class LLAdvancedForceOSException: public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + force_error_os_exception(NULL); + return true; + } +}; + +class LLAdvancedForceErrorSoftwareExceptionCoro : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLCoros::instance().launch( + "AdvancedForceErrorSoftwareExceptionCoro", + [](){ + // Wait for one mainloop() iteration, letting the enclosing + // handleEvent() method return. + llcoro::suspend(); + force_error_software_exception(NULL); + }); + return true; + } +}; + +class LLAdvancedForceErrorDriverCrash : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + force_error_driver_crash(NULL); + return true; + } +}; + +class LLAdvancedForceErrorCoroutineCrash : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + force_error_coroutine_crash(NULL); + return true; + } +}; + +class LLAdvancedForceErrorThreadCrash : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + force_error_thread_crash(NULL); + return true; + } +}; + +class LLAdvancedForceErrorDisconnectViewer : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_disconnect_viewer(NULL); + return true; +} +}; + + +#ifdef TOGGLE_HACKED_GODLIKE_VIEWER + +class LLAdvancedHandleToggleHackedGodmode : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_toggle_hacked_godmode(NULL); + return true; + } +}; + +class LLAdvancedCheckToggleHackedGodmode : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + check_toggle_hacked_godmode(NULL); + return true; + } +}; + +class LLAdvancedEnableToggleHackedGodmode : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = enable_toggle_hacked_godmode(NULL); + return new_value; + } +}; +#endif + + +// +////------------------------------------------------------------------- +//// Advanced menu +////------------------------------------------------------------------- + + +////////////////// +// DEVELOP MENU // +////////////////// + +class LLDevelopCheckLoggingLevel : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + U32 level = userdata.asInteger(); + return (static_cast(level) == LLError::getDefaultLevel()); + } +}; + +class LLDevelopSetLoggingLevel : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + U32 level = userdata.asInteger(); + LLError::setDefaultLevel(static_cast(level)); + return true; + } +}; + +////////////////// +// ADMIN MENU // +////////////////// + +// Admin > Object +class LLAdminForceTakeCopy : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + force_take_copy(NULL); + return true; + } +}; + +class LLAdminHandleObjectOwnerSelf : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_object_owner_self(NULL); + return true; + } +}; +class LLAdminHandleObjectOwnerPermissive : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_object_owner_permissive(NULL); + return true; + } +}; + +class LLAdminHandleForceDelete : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_force_delete(NULL); + return true; + } +}; + +class LLAdminHandleObjectLock : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_object_lock(NULL); + return true; + } +}; + +class LLAdminHandleObjectAssetIDs: public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_object_asset_ids(NULL); + return true; + } +}; + +//Admin >Parcel +class LLAdminHandleForceParcelOwnerToMe: public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_force_parcel_owner_to_me(NULL); + return true; + } +}; +class LLAdminHandleForceParcelToContent: public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_force_parcel_to_content(NULL); + return true; + } +}; +class LLAdminHandleClaimPublicLand: public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_claim_public_land(NULL); + return true; + } +}; + +// Admin > Region +class LLAdminHandleRegionDumpTempAssetData: public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_region_dump_temp_asset_data(NULL); + return true; + } +}; +//Admin (Top Level) + +class LLAdminOnSaveState: public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLPanelRegionTools::onSaveState(NULL); + return true; +} +}; + + +//----------------------------------------------------------------------------- +// cleanup_menus() +//----------------------------------------------------------------------------- +void cleanup_menus() +{ + delete gMenuParcelObserver; + gMenuParcelObserver = NULL; + + delete gMenuAvatarSelf; + gMenuAvatarSelf = NULL; + + delete gMenuAvatarOther; + gMenuAvatarOther = NULL; + + delete gMenuObject; + gMenuObject = NULL; + + delete gMenuAttachmentSelf; + gMenuAttachmentSelf = NULL; + + delete gMenuAttachmentOther; + gMenuAttachmentSelf = NULL; + + delete gMenuLand; + gMenuLand = NULL; + + delete gMenuMuteParticle; + gMenuMuteParticle = NULL; + + delete gMenuBarView; + gMenuBarView = NULL; + + delete gPopupMenuView; + gPopupMenuView = NULL; + + delete gMenuHolder; + gMenuHolder = NULL; +} + +//----------------------------------------------------------------------------- +// Object pie menu +//----------------------------------------------------------------------------- + +class LLObjectReportAbuse : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + if (objectp) + { + LLFloaterReporter::showFromObject(objectp->getID()); + } + return true; + } +}; + +// Enabled it you clicked an object +class LLObjectEnableReportAbuse : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = LLSelectMgr::getInstance()->getSelection()->getObjectCount() != 0; + return new_value; + } +}; + + +void handle_object_touch() +{ + LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + if (!object) return; + + LLPickInfo pick = LLToolPie::getInstance()->getPick(); + + // *NOTE: Hope the packets arrive safely and in order or else + // there will be some problems. + // *TODO: Just fix this bad assumption. + send_ObjectGrab_message(object, pick, LLVector3::zero); + send_ObjectDeGrab_message(object, pick); +} + +void handle_object_show_original() +{ + LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + if (!object) + { + return; + } + + LLViewerObject *parent = (LLViewerObject*)object->getParent(); + while (parent) + { + if(parent->isAvatar()) + { + break; + } + object = parent; + parent = (LLViewerObject*)parent->getParent(); + } + + if (!object || object->isAvatar()) + { + return; + } + + show_item_original(object->getAttachmentItemID()); +} + + +static void init_default_item_label(LLUICtrl* ctrl) +{ + const std::string& item_name = ctrl->getName(); + boost::unordered_map::iterator it = sDefaultItemLabels.find(item_name); + if (it == sDefaultItemLabels.end()) + { + // *NOTE: This will not work for items of type LLMenuItemCheckGL because they return boolean value + // (doesn't seem to matter much ATM). + LLStringExplicit default_label = ctrl->getValue().asString(); + if (!default_label.empty()) + { + sDefaultItemLabels.insert(std::pair(item_name, default_label)); + } + } +} + +static LLStringExplicit get_default_item_label(const std::string& item_name) +{ + LLStringExplicit res(""); + boost::unordered_map::iterator it = sDefaultItemLabels.find(item_name); + if (it != sDefaultItemLabels.end()) + { + res = it->second; + } + + return res; +} + + +bool enable_object_touch(LLUICtrl* ctrl) +{ + bool new_value = false; + LLViewerObject* obj = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + if (obj) + { + LLViewerObject* parent = (LLViewerObject*)obj->getParent(); + new_value = obj->flagHandleTouch() || (parent && parent->flagHandleTouch()); + } + + init_default_item_label(ctrl); + + // Update label based on the node touch name if available. + LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(); + if (node && node->mValid && !node->mTouchName.empty()) + { + ctrl->setValue(node->mTouchName); + } + else + { + ctrl->setValue(get_default_item_label(ctrl->getName())); + } + + return new_value; +}; + +//void label_touch(std::string& label, void*) +//{ +// LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(); +// if (node && node->mValid && !node->mTouchName.empty()) +// { +// label.assign(node->mTouchName); +// } +// else +// { +// label.assign("Touch"); +// } +//} + +void handle_object_open() +{ + LLFloaterReg::showInstance("openobject"); +} + +bool enable_object_inspect() +{ + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + LLViewerObject* selected_objectp = selection->getFirstRootObject(); + return selected_objectp != NULL; +} + +struct LLSelectedTEGetmatIdAndPermissions : public LLSelectedTEFunctor +{ + LLSelectedTEGetmatIdAndPermissions() + : mCanCopy(true) + , mCanModify(true) + , mCanTransfer(true) + , mHasNonPbrFaces(false) + {} + bool apply(LLViewerObject* objectp, S32 te_index) + { + mCanCopy &= (bool)objectp->permCopy(); + mCanTransfer &= (bool)objectp->permTransfer(); + mCanModify &= (bool)objectp->permModify(); + LLUUID mat_id = objectp->getRenderMaterialID(te_index); + if (mat_id.notNull()) + { + mMaterialId = mat_id; + } + else + { + mHasNonPbrFaces = true; + } + return true; + } + bool mCanCopy; + bool mCanModify; + bool mCanTransfer; + bool mHasNonPbrFaces; + LLUUID mMaterialId; +}; + +bool enable_object_edit_gltf_material() +{ + if (!LLMaterialEditor::capabilitiesAvailable()) + { + return false; + } + + LLSelectedTEGetmatIdAndPermissions func; + LLSelectMgr::getInstance()->getSelection()->applyToTEs(&func); + return func.mCanModify && !func.mHasNonPbrFaces; +} + +bool enable_object_open() +{ + // Look for contents in root object, which is all the LLFloaterOpenObject + // understands. + LLViewerObject* obj = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + if (!obj) return false; + + LLViewerObject* root = obj->getRootEdit(); + if (!root) return false; + + return root->allowOpen(); +} + + +class LLViewJoystickFlycam : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_toggle_flycam(); + return true; + } +}; + +class LLViewCheckJoystickFlycam : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = LLViewerJoystick::getInstance()->getOverrideCamera(); + return new_value; + } +}; + +void handle_toggle_flycam() +{ + LLViewerJoystick::getInstance()->toggleFlycam(); +} + +class LLObjectBuild : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + if (gAgentCamera.getFocusOnAvatar() && !LLToolMgr::getInstance()->inEdit() && gSavedSettings.getBOOL("EditCameraMovement") ) + { + // zoom in if we're looking at the avatar + gAgentCamera.setFocusOnAvatar(false, ANIMATE); + gAgentCamera.setFocusGlobal(LLToolPie::getInstance()->getPick()); + gAgentCamera.cameraZoomIn(0.666f); + gAgentCamera.cameraOrbitOver( 30.f * DEG_TO_RAD ); + gViewerWindow->moveCursorToCenter(); + } + else if ( gSavedSettings.getBOOL("EditCameraMovement") ) + { + gAgentCamera.setFocusGlobal(LLToolPie::getInstance()->getPick()); + gViewerWindow->moveCursorToCenter(); + } + + LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); + LLToolMgr::getInstance()->getCurrentToolset()->selectTool( LLToolCompCreate::getInstance() ); + + // Could be first use + //LLFirstUse::useBuild(); + return true; + } +}; + +void update_camera() +{ + LLViewerParcelMgr::getInstance()->deselectLand(); + + if (gAgentCamera.getFocusOnAvatar() && !LLToolMgr::getInstance()->inEdit()) + { + LLFloaterTools::sPreviousFocusOnAvatar = true; + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + + if (selection->getSelectType() == SELECT_TYPE_HUD || !gSavedSettings.getBOOL("EditCameraMovement")) + { + // always freeze camera in space, even if camera doesn't move + // so, for example, follow cam scripts can't affect you when in build mode + gAgentCamera.setFocusGlobal(gAgentCamera.calcFocusPositionTargetGlobal(), LLUUID::null); + gAgentCamera.setFocusOnAvatar(false, ANIMATE); + } + else + { + gAgentCamera.setFocusOnAvatar(false, ANIMATE); + LLViewerObject* selected_objectp = selection->getFirstRootObject(); + if (selected_objectp) + { + // zoom in on object center instead of where we clicked, as we need to see the manipulator handles + gAgentCamera.setFocusGlobal(selected_objectp->getPositionGlobal(), selected_objectp->getID()); + gAgentCamera.cameraZoomIn(0.666f); + gAgentCamera.cameraOrbitOver(30.f * DEG_TO_RAD); + gViewerWindow->moveCursorToCenter(); + } + } + } +} + +void handle_object_edit() +{ + update_camera(); + + LLFloaterReg::showInstance("build"); + + LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); + gFloaterTools->setEditTool( LLToolCompTranslate::getInstance() ); + + LLViewerJoystick::getInstance()->moveObjects(true); + LLViewerJoystick::getInstance()->setNeedsReset(true); + + // Could be first use + //LLFirstUse::useBuild(); + return; +} + +void handle_object_edit_gltf_material() +{ + if (!LLFloaterReg::instanceVisible("build")) + { + handle_object_edit(); // does update_camera(); + } + else + { + update_camera(); + + LLViewerJoystick::getInstance()->moveObjects(true); + LLViewerJoystick::getInstance()->setNeedsReset(true); + } + + LLMaterialEditor::loadLive(); +} + +void handle_attachment_edit(const LLUUID& inv_item_id) +{ + if (isAgentAvatarValid()) + { + if (LLViewerObject* attached_obj = gAgentAvatarp->getWornAttachment(inv_item_id)) + { + LLSelectMgr::getInstance()->deselectAll(); + LLSelectMgr::getInstance()->selectObjectAndFamily(attached_obj); + + handle_object_edit(); + } + } +} + +void handle_attachment_touch(const LLUUID& inv_item_id) +{ + if ( (isAgentAvatarValid()) && (enable_attachment_touch(inv_item_id)) ) + { + if (LLViewerObject* attach_obj = gAgentAvatarp->getWornAttachment(gInventory.getLinkedItemID(inv_item_id))) + { + LLSelectMgr::getInstance()->deselectAll(); + + LLObjectSelectionHandle sel = LLSelectMgr::getInstance()->selectObjectAndFamily(attach_obj); + if (!LLToolMgr::getInstance()->inBuildMode()) + { + struct SetTransient : public LLSelectedNodeFunctor + { + bool apply(LLSelectNode* node) + { + node->setTransient(true); + return true; + } + } f; + sel->applyToNodes(&f); + } + + handle_object_touch(); + } + } +} + +bool enable_attachment_touch(const LLUUID& inv_item_id) +{ + if (isAgentAvatarValid()) + { + const LLViewerObject* attach_obj = gAgentAvatarp->getWornAttachment(gInventory.getLinkedItemID(inv_item_id)); + return (attach_obj) && (attach_obj->flagHandleTouch()); + } + return false; +} + +void handle_object_inspect() +{ + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + LLViewerObject* selected_objectp = selection->getFirstRootObject(); + if (selected_objectp) + { + LLFloaterReg::showInstance("task_properties"); + } + + /* + // Old floater properties + LLFloaterReg::showInstance("inspect", LLSD()); + */ +} + +//--------------------------------------------------------------------------- +// Land pie menu +//--------------------------------------------------------------------------- +class LLLandBuild : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLViewerParcelMgr::getInstance()->deselectLand(); + + if (gAgentCamera.getFocusOnAvatar() && !LLToolMgr::getInstance()->inEdit() && gSavedSettings.getBOOL("EditCameraMovement") ) + { + // zoom in if we're looking at the avatar + gAgentCamera.setFocusOnAvatar(false, ANIMATE); + gAgentCamera.setFocusGlobal(LLToolPie::getInstance()->getPick()); + gAgentCamera.cameraZoomIn(0.666f); + gAgentCamera.cameraOrbitOver( 30.f * DEG_TO_RAD ); + gViewerWindow->moveCursorToCenter(); + } + else if ( gSavedSettings.getBOOL("EditCameraMovement") ) + { + // otherwise just move focus + gAgentCamera.setFocusGlobal(LLToolPie::getInstance()->getPick()); + gViewerWindow->moveCursorToCenter(); + } + + + LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); + LLToolMgr::getInstance()->getCurrentToolset()->selectTool( LLToolCompCreate::getInstance() ); + + // Could be first use + //LLFirstUse::useBuild(); + return true; + } +}; + +class LLLandBuyPass : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLPanelLandGeneral::onClickBuyPass((void *)false); + return true; + } +}; + +class LLLandEnableBuyPass : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = LLPanelLandGeneral::enableBuyPass(NULL); + return new_value; + } +}; + +// BUG: Should really check if CLICK POINT is in a parcel where you can build. +bool enable_land_build(void*) +{ + if (gAgent.isGodlike()) return true; + if (gAgent.inPrelude()) return false; + + bool can_build = false; + LLParcel* agent_parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if (agent_parcel) + { + can_build = agent_parcel->getAllowModify(); + } + return can_build; +} + +// BUG: Should really check if OBJECT is in a parcel where you can build. +bool enable_object_build(void*) +{ + if (gAgent.isGodlike()) return true; + if (gAgent.inPrelude()) return false; + + bool can_build = false; + LLParcel* agent_parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if (agent_parcel) + { + can_build = agent_parcel->getAllowModify(); + } + return can_build; +} + +bool enable_object_edit() +{ + if (!isAgentAvatarValid()) return false; + + // *HACK: The new "prelude" Help Islands have a build sandbox area, + // so users need the Edit and Create pie menu options when they are + // there. Eventually this needs to be replaced with code that only + // lets you edit objects if you have permission to do so (edit perms, + // group edit, god). See also lltoolbar.cpp. JC + bool enable = false; + if (gAgent.inPrelude()) + { + enable = LLViewerParcelMgr::getInstance()->allowAgentBuild() + || LLSelectMgr::getInstance()->getSelection()->isAttachment(); + } + else if (LLSelectMgr::getInstance()->selectGetAllValidAndObjectsFound()) + { + enable = true; + } + + return enable; +} + +bool enable_mute_particle() +{ + const LLPickInfo& pick = LLToolPie::getInstance()->getPick(); + + return pick.mParticleOwnerID != LLUUID::null && pick.mParticleOwnerID != gAgent.getID(); +} + +// mutually exclusive - show either edit option or build in menu +bool enable_object_build() +{ + return !enable_object_edit(); +} + +bool enable_object_select_in_pathfinding_linksets() +{ + return LLPathfindingManager::getInstance()->isPathfindingEnabledForCurrentRegion() && LLSelectMgr::getInstance()->selectGetEditableLinksets(); +} + +bool visible_object_select_in_pathfinding_linksets() +{ + return LLPathfindingManager::getInstance()->isPathfindingEnabledForCurrentRegion(); +} + +bool enable_object_select_in_pathfinding_characters() +{ + return LLPathfindingManager::getInstance()->isPathfindingEnabledForCurrentRegion() && LLSelectMgr::getInstance()->selectGetViewableCharacters(); +} + +bool enable_os_exception() +{ +#if LL_DARWIN + return true; +#else + return false; +#endif +} + +class LLSelfRemoveAllAttachments : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLAppearanceMgr::instance().removeAllAttachmentsFromAvatar(); + return true; + } +}; + +class LLSelfEnableRemoveAllAttachments : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = false; + if (isAgentAvatarValid()) + { + for (LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); + iter != gAgentAvatarp->mAttachmentPoints.end(); ) + { + LLVOAvatar::attachment_map_t::iterator curiter = iter++; + LLViewerJointAttachment* attachment = curiter->second; + if (attachment->getNumObjects() > 0) + { + new_value = true; + break; + } + } + } + return new_value; + } +}; + +bool enable_has_attachments(void*) +{ + + return false; +} + +//--------------------------------------------------------------------------- +// Avatar pie menu +//--------------------------------------------------------------------------- +//void handle_follow(void *userdata) +//{ +// // follow a given avatar by ID +// LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); +// if (objectp) +// { +// gAgent.startFollowPilot(objectp->getID()); +// } +//} + +bool enable_object_mute() +{ + LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + if (!object) return false; + + LLVOAvatar* avatar = find_avatar_from_object(object); + if (avatar) + { + // It's an avatar + LLNameValue *lastname = avatar->getNVPair("LastName"); + bool is_linden = + lastname && !LLStringUtil::compareStrings(lastname->getString(), "Linden"); + bool is_self = avatar->isSelf(); + return !is_linden && !is_self; + } + else + { + // Just a regular object + return LLSelectMgr::getInstance()->getSelection()->contains( object, SELECT_ALL_TES ) && + !LLMuteList::getInstance()->isMuted(object->getID()); + } +} + +bool enable_object_unmute() +{ + LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + if (!object) return false; + + LLVOAvatar* avatar = find_avatar_from_object(object); + if (avatar) + { + // It's an avatar + LLNameValue *lastname = avatar->getNVPair("LastName"); + bool is_linden = + lastname && !LLStringUtil::compareStrings(lastname->getString(), "Linden"); + bool is_self = avatar->isSelf(); + return !is_linden && !is_self; + } + else + { + // Just a regular object + return LLSelectMgr::getInstance()->getSelection()->contains( object, SELECT_ALL_TES ) && + LLMuteList::getInstance()->isMuted(object->getID());; + } +} + + +// 0 = normal, 1 = always, 2 = never +class LLAvatarCheckImpostorMode : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + if (!object) return false; + + LLVOAvatar* avatar = find_avatar_from_object(object); + if (!avatar) return false; + + U32 mode = userdata.asInteger(); + switch (mode) + { + case 0: + return (avatar->getVisualMuteSettings() == LLVOAvatar::AV_RENDER_NORMALLY); + case 1: + return (avatar->getVisualMuteSettings() == LLVOAvatar::AV_DO_NOT_RENDER); + case 2: + return (avatar->getVisualMuteSettings() == LLVOAvatar::AV_ALWAYS_RENDER); + case 4: + return (avatar->getVisualMuteSettings() != LLVOAvatar::AV_RENDER_NORMALLY); + default: + return false; + } + } // handleEvent() +}; + +// 0 = normal, 1 = always, 2 = never +class LLAvatarSetImpostorMode : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + if (!object) return false; + + LLVOAvatar* avatar = find_avatar_from_object(object); + if (!avatar) return false; + + U32 mode = userdata.asInteger(); + switch (mode) + { + case 0: + avatar->setVisualMuteSettings(LLVOAvatar::AV_RENDER_NORMALLY); + break; + case 1: + avatar->setVisualMuteSettings(LLVOAvatar::AV_DO_NOT_RENDER); + break; + case 2: + avatar->setVisualMuteSettings(LLVOAvatar::AV_ALWAYS_RENDER); + break; + default: + return false; + } + + LLVOAvatar::cullAvatarsByPixelArea(); + return true; + } // handleEvent() +}; + + +class LLObjectMute : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + if (!object) return true; + + LLUUID id; + std::string name; + LLMute::EType type; + LLVOAvatar* avatar = find_avatar_from_object(object); + if (avatar) + { + avatar->mNeedsImpostorUpdate = true; + avatar->mLastImpostorUpdateReason = 9; + + id = avatar->getID(); + + LLNameValue *firstname = avatar->getNVPair("FirstName"); + LLNameValue *lastname = avatar->getNVPair("LastName"); + if (firstname && lastname) + { + name = LLCacheName::buildFullName( + firstname->getString(), lastname->getString()); + } + + type = LLMute::AGENT; + } + else + { + // it's an object + id = object->getID(); + + LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(); + if (node) + { + name = node->mName; + } + + type = LLMute::OBJECT; + } + + LLMute mute(id, name, type); + if (LLMuteList::getInstance()->isMuted(mute.mID)) + { + LLMuteList::getInstance()->remove(mute); + } + else + { + LLMuteList::getInstance()->add(mute); + LLPanelBlockedList::showPanelAndSelect(mute.mID); + } + + return true; + } +}; + +bool handle_go_to() +{ + // try simulator autopilot + std::vector strings; + std::string val; + LLVector3d pos = LLToolPie::getInstance()->getPick().mPosGlobal; + val = llformat("%g", pos.mdV[VX]); + strings.push_back(val); + val = llformat("%g", pos.mdV[VY]); + strings.push_back(val); + val = llformat("%g", pos.mdV[VZ]); + strings.push_back(val); + send_generic_message("autopilot", strings); + + LLViewerParcelMgr::getInstance()->deselectLand(); + + if (isAgentAvatarValid() && !gSavedSettings.getBOOL("AutoPilotLocksCamera")) + { + gAgentCamera.setFocusGlobal(gAgentCamera.getFocusTargetGlobal(), gAgentAvatarp->getID()); + } + else + { + // Snap camera back to behind avatar + gAgentCamera.setFocusOnAvatar(true, ANIMATE); + } + + // Could be first use + //LLFirstUse::useGoTo(); + return true; +} + +class LLGoToObject : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + return handle_go_to(); + } +}; + +class LLAvatarReportAbuse : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLVOAvatar* avatar = find_avatar_from_object( LLSelectMgr::getInstance()->getSelection()->getPrimaryObject() ); + if(avatar) + { + LLFloaterReporter::showFromObject(avatar->getID()); + } + return true; + } +}; + + +//--------------------------------------------------------------------------- +// Parcel freeze, eject, etc. +//--------------------------------------------------------------------------- +bool callback_freeze(const LLSD& notification, const LLSD& response) +{ + LLUUID avatar_id = notification["payload"]["avatar_id"].asUUID(); + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + + if (0 == option || 1 == option) + { + U32 flags = 0x0; + if (1 == option) + { + // unfreeze + flags |= 0x1; + } + + LLMessageSystem* msg = gMessageSystem; + LLViewerObject* avatar = gObjectList.findObject(avatar_id); + + if (avatar) + { + msg->newMessage("FreezeUser"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->nextBlock("Data"); + msg->addUUID("TargetID", avatar_id ); + msg->addU32("Flags", flags ); + msg->sendReliable( avatar->getRegion()->getHost() ); + } + } + return false; +} + + +void handle_avatar_freeze(const LLSD& avatar_id) +{ + // Use avatar_id if available, otherwise default to right-click avatar + LLVOAvatar* avatar = NULL; + if (avatar_id.asUUID().notNull()) + { + avatar = find_avatar_from_object(avatar_id.asUUID()); + } + else + { + avatar = find_avatar_from_object( + LLSelectMgr::getInstance()->getSelection()->getPrimaryObject()); + } + + if( avatar ) + { + std::string fullname = avatar->getFullname(); + LLSD payload; + payload["avatar_id"] = avatar->getID(); + + if (!fullname.empty()) + { + LLSD args; + args["AVATAR_NAME"] = fullname; + LLNotificationsUtil::add("FreezeAvatarFullname", + args, + payload, + callback_freeze); + } + else + { + LLNotificationsUtil::add("FreezeAvatar", + LLSD(), + payload, + callback_freeze); + } + } +} + +class LLAvatarVisibleDebug : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + return gAgent.isGodlike(); + } +}; + +class LLAvatarDebug : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLVOAvatar* avatar = find_avatar_from_object( LLSelectMgr::getInstance()->getSelection()->getPrimaryObject() ); + if( avatar ) + { + if (avatar->isSelf()) + { + ((LLVOAvatarSelf *)avatar)->dumpLocalTextures(); + } + LL_INFOS() << "Dumping temporary asset data to simulator logs for avatar " << avatar->getID() << LL_ENDL; + std::vector strings; + strings.push_back(avatar->getID().asString()); + LLUUID invoice; + send_generic_message("dumptempassetdata", strings, invoice); + LLFloaterReg::showInstance( "avatar_textures", LLSD(avatar->getID()) ); + } + return true; + } +}; + +bool callback_eject(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (2 == option) + { + // Cancel button. + return false; + } + LLUUID avatar_id = notification["payload"]["avatar_id"].asUUID(); + bool ban_enabled = notification["payload"]["ban_enabled"].asBoolean(); + + if (0 == option) + { + // Eject button + LLMessageSystem* msg = gMessageSystem; + LLViewerObject* avatar = gObjectList.findObject(avatar_id); + + if (avatar) + { + U32 flags = 0x0; + msg->newMessage("EjectUser"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID() ); + msg->addUUID("SessionID", gAgent.getSessionID() ); + msg->nextBlock("Data"); + msg->addUUID("TargetID", avatar_id ); + msg->addU32("Flags", flags ); + msg->sendReliable( avatar->getRegion()->getHost() ); + } + } + else if (ban_enabled) + { + // This is tricky. It is similar to say if it is not an 'Eject' button, + // and it is also not an 'Cancle' button, and ban_enabled==ture, + // it should be the 'Eject and Ban' button. + LLMessageSystem* msg = gMessageSystem; + LLViewerObject* avatar = gObjectList.findObject(avatar_id); + + if (avatar) + { + U32 flags = 0x1; + msg->newMessage("EjectUser"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID() ); + msg->addUUID("SessionID", gAgent.getSessionID() ); + msg->nextBlock("Data"); + msg->addUUID("TargetID", avatar_id ); + msg->addU32("Flags", flags ); + msg->sendReliable( avatar->getRegion()->getHost() ); + } + } + return false; +} + +void handle_avatar_eject(const LLSD& avatar_id) +{ + // Use avatar_id if available, otherwise default to right-click avatar + LLVOAvatar* avatar = NULL; + if (avatar_id.asUUID().notNull()) + { + avatar = find_avatar_from_object(avatar_id.asUUID()); + } + else + { + avatar = find_avatar_from_object( + LLSelectMgr::getInstance()->getSelection()->getPrimaryObject()); + } + + if( avatar ) + { + LLSD payload; + payload["avatar_id"] = avatar->getID(); + std::string fullname = avatar->getFullname(); + + const LLVector3d& pos = avatar->getPositionGlobal(); + LLParcel* parcel = LLViewerParcelMgr::getInstance()->selectParcelAt(pos)->getParcel(); + + if (LLViewerParcelMgr::getInstance()->isParcelOwnedByAgent(parcel,GP_LAND_MANAGE_BANNED)) + { + payload["ban_enabled"] = true; + if (!fullname.empty()) + { + LLSD args; + args["AVATAR_NAME"] = fullname; + LLNotificationsUtil::add("EjectAvatarFullname", + args, + payload, + callback_eject); + } + else + { + LLNotificationsUtil::add("EjectAvatarFullname", + LLSD(), + payload, + callback_eject); + } + } + else + { + payload["ban_enabled"] = false; + if (!fullname.empty()) + { + LLSD args; + args["AVATAR_NAME"] = fullname; + LLNotificationsUtil::add("EjectAvatarFullnameNoBan", + args, + payload, + callback_eject); + } + else + { + LLNotificationsUtil::add("EjectAvatarNoBan", + LLSD(), + payload, + callback_eject); + } + } + } +} + +bool my_profile_visible() +{ + LLFloater* floaterp = LLAvatarActions::getProfileFloater(gAgentID); + return floaterp && floaterp->isInVisibleChain(); +} + +bool picks_tab_visible() +{ + return my_profile_visible() && LLAvatarActions::isPickTabSelected(gAgentID); +} + +bool enable_freeze_eject(const LLSD& avatar_id) +{ + // Use avatar_id if available, otherwise default to right-click avatar + LLVOAvatar* avatar = NULL; + if (avatar_id.asUUID().notNull()) + { + avatar = find_avatar_from_object(avatar_id.asUUID()); + } + else + { + avatar = find_avatar_from_object( + LLSelectMgr::getInstance()->getSelection()->getPrimaryObject()); + } + if (!avatar) return false; + + // Gods can always freeze + if (gAgent.isGodlike()) return true; + + // Estate owners / managers can freeze + // Parcel owners can also freeze + const LLVector3& pos = avatar->getPositionRegion(); + const LLVector3d& pos_global = avatar->getPositionGlobal(); + LLParcel* parcel = LLViewerParcelMgr::getInstance()->selectParcelAt(pos_global)->getParcel(); + LLViewerRegion* region = avatar->getRegion(); + if (!region) return false; + + bool new_value = region->isOwnedSelf(pos); + if (!new_value || region->isOwnedGroup(pos)) + { + new_value = LLViewerParcelMgr::getInstance()->isParcelOwnedByAgent(parcel,GP_LAND_ADMIN); + } + return new_value; +} + +bool callback_leave_group(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) + { + LLMessageSystem *msg = gMessageSystem; + + msg->newMessageFast(_PREHASH_LeaveGroupRequest); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_GroupData); + msg->addUUIDFast(_PREHASH_GroupID, gAgent.getGroupID() ); + gAgent.sendReliableMessage(); + } + return false; +} + +void append_aggregate(std::string& string, const LLAggregatePermissions& ag_perm, PermissionBit bit, const char* txt) +{ + LLAggregatePermissions::EValue val = ag_perm.getValue(bit); + std::string buffer; + switch(val) + { + case LLAggregatePermissions::AP_NONE: + buffer = llformat( "* %s None\n", txt); + break; + case LLAggregatePermissions::AP_SOME: + buffer = llformat( "* %s Some\n", txt); + break; + case LLAggregatePermissions::AP_ALL: + buffer = llformat( "* %s All\n", txt); + break; + case LLAggregatePermissions::AP_EMPTY: + default: + break; + } + string.append(buffer); +} + +bool enable_buy_object() +{ + // In order to buy, there must only be 1 purchaseable object in + // the selection manager. + if(LLSelectMgr::getInstance()->getSelection()->getRootObjectCount() != 1) return false; + LLViewerObject* obj = NULL; + LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(); + if(node) + { + obj = node->getObject(); + if(!obj) return false; + + if( for_sale_selection(node) ) + { + // *NOTE: Is this needed? This checks to see if anyone owns the + // object, dating back to when we had "public" objects owned by + // no one. JC + if(obj->permAnyOwner()) return true; + } + } + return false; +} + +// Note: This will only work if the selected object's data has been +// received by the viewer and cached in the selection manager. +void handle_buy_object(LLSaleInfo sale_info) +{ + if(!LLSelectMgr::getInstance()->selectGetAllRootsValid()) + { + LLNotificationsUtil::add("UnableToBuyWhileDownloading"); + return; + } + + LLUUID owner_id; + std::string owner_name; + bool owners_identical = LLSelectMgr::getInstance()->selectGetOwner(owner_id, owner_name); + if (!owners_identical) + { + LLNotificationsUtil::add("CannotBuyObjectsFromDifferentOwners"); + return; + } + + LLPermissions perm; + bool valid = LLSelectMgr::getInstance()->selectGetPermissions(perm); + LLAggregatePermissions ag_perm; + valid &= LLSelectMgr::getInstance()->selectGetAggregatePermissions(ag_perm); + if(!valid || !sale_info.isForSale() || !perm.allowTransferTo(gAgent.getID())) + { + LLNotificationsUtil::add("ObjectNotForSale"); + return; + } + + LLFloaterBuy::show(sale_info); +} + + +void handle_buy_contents(LLSaleInfo sale_info) +{ + LLFloaterBuyContents::show(sale_info); +} + +void handle_region_dump_temp_asset_data(void*) +{ + LL_INFOS() << "Dumping temporary asset data to simulator logs" << LL_ENDL; + std::vector strings; + LLUUID invoice; + send_generic_message("dumptempassetdata", strings, invoice); +} + +void handle_region_clear_temp_asset_data(void*) +{ + LL_INFOS() << "Clearing temporary asset data" << LL_ENDL; + std::vector strings; + LLUUID invoice; + send_generic_message("cleartempassetdata", strings, invoice); +} + +void handle_region_dump_settings(void*) +{ + LLViewerRegion* regionp = gAgent.getRegion(); + if (regionp) + { + LL_INFOS() << "Damage: " << (regionp->getAllowDamage() ? "on" : "off") << LL_ENDL; + LL_INFOS() << "Landmark: " << (regionp->getAllowLandmark() ? "on" : "off") << LL_ENDL; + LL_INFOS() << "SetHome: " << (regionp->getAllowSetHome() ? "on" : "off") << LL_ENDL; + LL_INFOS() << "ResetHome: " << (regionp->getResetHomeOnTeleport() ? "on" : "off") << LL_ENDL; + LL_INFOS() << "SunFixed: " << (regionp->getSunFixed() ? "on" : "off") << LL_ENDL; + LL_INFOS() << "BlockFly: " << (regionp->getBlockFly() ? "on" : "off") << LL_ENDL; + LL_INFOS() << "AllowP2P: " << (regionp->getAllowDirectTeleport() ? "on" : "off") << LL_ENDL; + LL_INFOS() << "Water: " << (regionp->getWaterHeight()) << LL_ENDL; + } +} + +void handle_dump_group_info(void *) +{ + gAgent.dumpGroupInfo(); +} + +void handle_dump_capabilities_info(void *) +{ + LLViewerRegion* regionp = gAgent.getRegion(); + if (regionp) + { + regionp->logActiveCapabilities(); + } +} + +void handle_dump_region_object_cache(void*) +{ + LLViewerRegion* regionp = gAgent.getRegion(); + if (regionp) + { + regionp->dumpCache(); + } +} + +void handle_reset_interest_lists(void *) +{ + // Check all regions and reset their interest list + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); + ++iter) + { + LLViewerRegion *regionp = *iter; + if (regionp && regionp->isAlive() && regionp->capabilitiesReceived()) + { + regionp->resetInterestList(); + } + } +} + + +void handle_dump_focus() +{ + LLUICtrl *ctrl = dynamic_cast(gFocusMgr.getKeyboardFocus()); + + LL_INFOS() << "Keyboard focus " << (ctrl ? ctrl->getName() : "(none)") << LL_ENDL; +} + +class LLSelfStandUp : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + gAgent.standUp(); + return true; + } +}; + +bool enable_standup_self() +{ + return isAgentAvatarValid() && gAgentAvatarp->isSitting(); +} + +class LLSelfSitDown : public view_listener_t + { + bool handleEvent(const LLSD& userdata) + { + gAgent.sitDown(); + return true; + } + }; + + + +bool show_sitdown_self() +{ + return isAgentAvatarValid() && !gAgentAvatarp->isSitting(); +} + +bool enable_sitdown_self() +{ + return show_sitdown_self() && !gAgentAvatarp->isEditingAppearance() && !gAgent.getFlying(); +} + +class LLSelfToggleSitStand : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + if (isAgentAvatarValid()) + { + if (gAgentAvatarp->isSitting()) + { + gAgent.standUp(); + } + else + { + gAgent.sitDown(); + } + } + return true; + } +}; + +bool enable_sit_stand() +{ + return enable_sitdown_self() || enable_standup_self(); +} + +bool enable_fly_land() +{ + return gAgent.getFlying() || LLAgent::enableFlying(); +} + +class LLCheckPanelPeopleTab : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string panel_name = userdata.asString(); + + LLPanel *panel = LLFloaterSidePanelContainer::getPanel("people", panel_name); + if(panel && panel->isInVisibleChain()) + { + return true; + } + return false; + } +}; +// Toggle one of "People" panel tabs in side tray. +class LLTogglePanelPeopleTab : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string panel_name = userdata.asString(); + + LLSD param; + param["people_panel_tab_name"] = panel_name; + + if ( panel_name == "friends_panel" + || panel_name == "groups_panel" + || panel_name == "nearby_panel" + || panel_name == "blocked_panel") + { + return togglePeoplePanel(panel_name, param); + } + else + { + return false; + } + } + + static bool togglePeoplePanel(const std::string& panel_name, const LLSD& param) + { + LLPanel *panel = LLFloaterSidePanelContainer::getPanel("people", panel_name); + if(!panel) + return false; + + if (panel->isInVisibleChain()) + { + LLFloaterReg::hideInstance("people"); + } + else + { + LLFloaterSidePanelContainer::showPanel("people", "panel_people", param) ; + } + + return true; + } +}; + +bool check_admin_override(void*) +{ + return gAgent.getAdminOverride(); +} + +void handle_admin_override_toggle(void*) +{ + gAgent.setAdminOverride(!gAgent.getAdminOverride()); + + // The above may have affected which debug menus are visible + show_debug_menus(); +} + +void handle_visual_leak_detector_toggle(void*) +{ + static bool vld_enabled = false; + + if ( vld_enabled ) + { +#ifdef INCLUDE_VLD + // only works for debug builds (hard coded into vld.h) +#ifdef _DEBUG + // start with Visual Leak Detector turned off + VLDDisable(); +#endif // _DEBUG +#endif // INCLUDE_VLD + vld_enabled = false; + } + else + { +#ifdef INCLUDE_VLD + // only works for debug builds (hard coded into vld.h) + #ifdef _DEBUG + // start with Visual Leak Detector turned off + VLDEnable(); + #endif // _DEBUG +#endif // INCLUDE_VLD + + vld_enabled = true; + }; +} + +void handle_god_mode(void*) +{ + gAgent.requestEnterGodMode(); +} + +void handle_leave_god_mode(void*) +{ + gAgent.requestLeaveGodMode(); +} + +void set_god_level(U8 god_level) +{ + U8 old_god_level = gAgent.getGodLevel(); + gAgent.setGodLevel( god_level ); + LLViewerParcelMgr::getInstance()->notifyObservers(); + + // God mode changes region visibility + LLWorldMap::getInstance()->reloadItems(true); + + // inventory in items may change in god mode + gObjectList.dirtyAllObjectInventory(); + + if(gViewerWindow) + { + gViewerWindow->setMenuBackgroundColor(god_level > GOD_NOT, + LLGridManager::getInstance()->isInProductionGrid()); + } + + LLSD args; + if(god_level > GOD_NOT) + { + args["LEVEL"] = llformat("%d",(S32)god_level); + LLNotificationsUtil::add("EnteringGodMode", args); + } + else + { + args["LEVEL"] = llformat("%d",(S32)old_god_level); + LLNotificationsUtil::add("LeavingGodMode", args); + } + + // changing god-level can affect which menus we see + show_debug_menus(); + + // changing god-level can invalidate search results + LLFloaterSearch *search = dynamic_cast(LLFloaterReg::getInstance("search")); + if (search) + { + search->godLevelChanged(god_level); + } +} + +#ifdef TOGGLE_HACKED_GODLIKE_VIEWER +void handle_toggle_hacked_godmode(void*) +{ + gHackGodmode = !gHackGodmode; + set_god_level(gHackGodmode ? GOD_MAINTENANCE : GOD_NOT); +} + +bool check_toggle_hacked_godmode(void*) +{ + return gHackGodmode; +} + +bool enable_toggle_hacked_godmode(void*) +{ + return !LLGridManager::getInstance()->isInProductionGrid(); +} +#endif + +void process_grant_godlike_powers(LLMessageSystem* msg, void**) +{ + LLUUID agent_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); + LLUUID session_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_SessionID, session_id); + if((agent_id == gAgent.getID()) && (session_id == gAgent.getSessionID())) + { + U8 god_level; + msg->getU8Fast(_PREHASH_GrantData, _PREHASH_GodLevel, god_level); + set_god_level(god_level); + } + else + { + LL_WARNS() << "Grant godlike for wrong agent " << agent_id << LL_ENDL; + } +} + +/* +class LLHaveCallingcard : public LLInventoryCollectFunctor +{ +public: + LLHaveCallingcard(const LLUUID& agent_id); + virtual ~LLHaveCallingcard() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); + bool isThere() const { return mIsThere;} +protected: + LLUUID mID; + bool mIsThere; +}; + +LLHaveCallingcard::LLHaveCallingcard(const LLUUID& agent_id) : + mID(agent_id), + mIsThere(false) +{ +} + +bool LLHaveCallingcard::operator()(LLInventoryCategory* cat, + LLInventoryItem* item) +{ + if(item) + { + if((item->getType() == LLAssetType::AT_CALLINGCARD) + && (item->getCreatorUUID() == mID)) + { + mIsThere = true; + } + } + return false; +} +*/ + +bool is_agent_mappable(const LLUUID& agent_id) +{ + const LLRelationship* buddy_info = NULL; + bool is_friend = LLAvatarActions::isFriend(agent_id); + + if (is_friend) + buddy_info = LLAvatarTracker::instance().getBuddyInfo(agent_id); + + return (buddy_info && + buddy_info->isOnline() && + buddy_info->isRightGrantedFrom(LLRelationship::GRANT_MAP_LOCATION) + ); +} + + +// Enable a menu item when you don't have someone's card. +class LLAvatarEnableAddFriend : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLVOAvatar* avatar = find_avatar_from_object(LLSelectMgr::getInstance()->getSelection()->getPrimaryObject()); + bool new_value = avatar && !LLAvatarActions::isFriend(avatar->getID()); + return new_value; + } +}; + +void request_friendship(const LLUUID& dest_id) +{ + LLViewerObject* dest = gObjectList.findObject(dest_id); + if(dest && dest->isAvatar()) + { + std::string full_name; + LLNameValue* nvfirst = dest->getNVPair("FirstName"); + LLNameValue* nvlast = dest->getNVPair("LastName"); + if(nvfirst && nvlast) + { + full_name = LLCacheName::buildFullName( + nvfirst->getString(), nvlast->getString()); + } + if (!full_name.empty()) + { + LLAvatarActions::requestFriendshipDialog(dest_id, full_name); + } + else + { + LLNotificationsUtil::add("CantOfferFriendship"); + } + } +} + + +class LLEditEnableCustomizeAvatar : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = gAgentWearables.areWearablesLoaded(); + return new_value; + } +}; + +class LLEnableEditShape : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + return gAgentWearables.isWearableModifiable(LLWearableType::WT_SHAPE, 0); + } +}; + +class LLEnableHoverHeight : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + return gAgent.getRegion() && gAgent.getRegion()->avatarHoverHeightEnabled(); + } +}; + +class LLEnableEditPhysics : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + //return gAgentWearables.isWearableModifiable(LLWearableType::WT_SHAPE, 0); + return true; + } +}; + +bool is_object_sittable() +{ + LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + + if (object && object->getPCode() == LL_PCODE_VOLUME) + { + return true; + } + else + { + return false; + } +} + +// only works on pie menu +void handle_object_sit(LLViewerObject *object, const LLVector3 &offset) +{ + // get object selection offset + + if (object && object->getPCode() == LL_PCODE_VOLUME) + { + + gMessageSystem->newMessageFast(_PREHASH_AgentRequestSit); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + gMessageSystem->nextBlockFast(_PREHASH_TargetObject); + gMessageSystem->addUUIDFast(_PREHASH_TargetID, object->mID); + gMessageSystem->addVector3Fast(_PREHASH_Offset, offset); + + object->getRegion()->sendReliableMessage(); + } +} + +void handle_object_sit_or_stand() +{ + LLPickInfo pick = LLToolPie::getInstance()->getPick(); + LLViewerObject *object = pick.getObject(); + if (!object || pick.mPickType == LLPickInfo::PICK_FLORA) + { + return; + } + + if (sitting_on_selection()) + { + gAgent.standUp(); + return; + } + + handle_object_sit(object, pick.mObjectOffset); +} + +void handle_object_sit(const LLUUID& object_id) +{ + LLViewerObject* obj = gObjectList.findObject(object_id); + if (!obj) + { + return; + } + + LLVector3 offset(0, 0, 0); + handle_object_sit(obj, offset); +} + +void near_sit_down_point(bool success, void *) +{ + if (success) + { + gAgent.setFlying(false); + gAgent.clearControlFlags(AGENT_CONTROL_STAND_UP); // might have been set by autopilot + gAgent.setControlFlags(AGENT_CONTROL_SIT_ON_GROUND); + } +} + +class LLLandSit : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + if (gAgent.isSitting()) + { + gAgent.standUp(); + } + LLVector3d posGlobal = LLToolPie::getInstance()->getPick().mPosGlobal; + + LLQuaternion target_rot; + if (isAgentAvatarValid()) + { + target_rot = gAgentAvatarp->getRotation(); + } + else + { + target_rot = gAgent.getFrameAgent().getQuaternion(); + } + gAgent.startAutoPilotGlobal(posGlobal, "Sit", &target_rot, near_sit_down_point, NULL, 0.7f); + return true; + } +}; + +class LLLandCanSit : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLVector3d posGlobal = LLToolPie::getInstance()->getPick().mPosGlobal; + return !posGlobal.isExactlyZero(); // valid position, not beyond draw distance + } +}; + +//------------------------------------------------------------------- +// Help menu functions +//------------------------------------------------------------------- + +// +// Major mode switching +// +void reset_view_final( bool proceed ); + +void handle_reset_view() +{ + if (gAgentCamera.cameraCustomizeAvatar()) + { + // switching to outfit selector should automagically save any currently edited wearable + LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "my_outfits")); + } + gAgentCamera.setFocusOnAvatar(true, false, false); + reset_view_final( true ); + LLFloaterCamera::resetCameraMode(); +} + +class LLViewResetView : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_reset_view(); + return true; + } +}; + +// Note: extra parameters allow this function to be called from dialog. +void reset_view_final( bool proceed ) +{ + if( !proceed ) + { + return; + } + + gAgentCamera.resetView(true, true); + gAgentCamera.setLookAt(LOOKAT_TARGET_CLEAR); +} + +class LLViewLookAtLastChatter : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + gAgentCamera.lookAtLastChat(); + return true; + } +}; + +class LLViewMouselook : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + if (!gAgentCamera.cameraMouselook()) + { + gAgentCamera.changeCameraToMouselook(); + } + else + { + gAgentCamera.changeCameraToDefault(); + } + return true; + } +}; + +class LLViewDefaultUISize : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + gSavedSettings.setF32("UIScaleFactor", 1.0f); + gSavedSettings.setBOOL("UIAutoScale", false); + gViewerWindow->reshape(gViewerWindow->getWindowWidthRaw(), gViewerWindow->getWindowHeightRaw()); + return true; + } +}; + +class LLViewToggleUI : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + if(gAgentCamera.getCameraMode() != CAMERA_MODE_MOUSELOOK) + { + LLNotification::Params params("ConfirmHideUI"); + params.functor.function(boost::bind(&LLViewToggleUI::confirm, this, _1, _2)); + LLSD substitutions; +#if LL_DARWIN + substitutions["SHORTCUT"] = "Cmd+Shift+U"; +#else + substitutions["SHORTCUT"] = "Ctrl+Shift+U"; +#endif + params.substitutions = substitutions; + if (!gSavedSettings.getBOOL("HideUIControls")) + { + // hiding, so show notification + LLNotifications::instance().add(params); + } + else + { + LLNotifications::instance().forceResponse(params, 0); + } + } + return true; + } + + void confirm(const LLSD& notification, const LLSD& response) + { + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + + if (option == 0) // OK + { + gViewerWindow->setUIVisibility(gSavedSettings.getBOOL("HideUIControls")); + LLPanelStandStopFlying::getInstance()->setVisible(gSavedSettings.getBOOL("HideUIControls")); + gSavedSettings.setBOOL("HideUIControls",!gSavedSettings.getBOOL("HideUIControls")); + } + } +}; + +void handle_duplicate_in_place(void*) +{ + LL_INFOS() << "handle_duplicate_in_place" << LL_ENDL; + + LLVector3 offset(0.f, 0.f, 0.f); + LLSelectMgr::getInstance()->selectDuplicate(offset, true); +} + + + +/* + * No longer able to support viewer side manipulations in this way + * +void god_force_inv_owner_permissive(LLViewerObject* object, + LLInventoryObject::object_list_t* inventory, + S32 serial_num, + void*) +{ + typedef std::vector > item_array_t; + item_array_t items; + + LLInventoryObject::object_list_t::const_iterator inv_it = inventory->begin(); + LLInventoryObject::object_list_t::const_iterator inv_end = inventory->end(); + for ( ; inv_it != inv_end; ++inv_it) + { + if(((*inv_it)->getType() != LLAssetType::AT_CATEGORY)) + { + LLInventoryObject* obj = *inv_it; + LLPointer new_item = new LLViewerInventoryItem((LLViewerInventoryItem*)obj); + LLPermissions perm(new_item->getPermissions()); + perm.setMaskBase(PERM_ALL); + perm.setMaskOwner(PERM_ALL); + new_item->setPermissions(perm); + items.push_back(new_item); + } + } + item_array_t::iterator end = items.end(); + item_array_t::iterator it; + for(it = items.begin(); it != end; ++it) + { + // since we have the inventory item in the callback, it should not + // invalidate iteration through the selection manager. + object->updateInventory((*it), TASK_INVENTORY_ITEM_KEY, false); + } +} +*/ + +void handle_object_owner_permissive(void*) +{ + // only send this if they're a god. + if(gAgent.isGodlike()) + { + // do the objects. + LLSelectMgr::getInstance()->selectionSetObjectPermissions(PERM_BASE, true, PERM_ALL, true); + LLSelectMgr::getInstance()->selectionSetObjectPermissions(PERM_OWNER, true, PERM_ALL, true); + } +} + +void handle_object_owner_self(void*) +{ + // only send this if they're a god. + if(gAgent.isGodlike()) + { + LLSelectMgr::getInstance()->sendOwner(gAgent.getID(), gAgent.getGroupID(), true); + } +} + +// Shortcut to set owner permissions to not editable. +void handle_object_lock(void*) +{ + LLSelectMgr::getInstance()->selectionSetObjectPermissions(PERM_OWNER, false, PERM_MODIFY); +} + +void handle_object_asset_ids(void*) +{ + // only send this if they're a god. + if (gAgent.isGodlike()) + { + LLSelectMgr::getInstance()->sendGodlikeRequest("objectinfo", "assetids"); + } +} + +void handle_force_parcel_owner_to_me(void*) +{ + LLViewerParcelMgr::getInstance()->sendParcelGodForceOwner( gAgent.getID() ); +} + +void handle_force_parcel_to_content(void*) +{ + LLViewerParcelMgr::getInstance()->sendParcelGodForceToContent(); +} + +void handle_claim_public_land(void*) +{ + if (LLViewerParcelMgr::getInstance()->getSelectionRegion() != gAgent.getRegion()) + { + LLNotificationsUtil::add("ClaimPublicLand"); + return; + } + + LLVector3d west_south_global; + LLVector3d east_north_global; + LLViewerParcelMgr::getInstance()->getSelection(west_south_global, east_north_global); + LLVector3 west_south = gAgent.getPosAgentFromGlobal(west_south_global); + LLVector3 east_north = gAgent.getPosAgentFromGlobal(east_north_global); + + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("GodlikeMessage"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->addUUIDFast(_PREHASH_TransactionID, LLUUID::null); //not used + msg->nextBlock("MethodData"); + msg->addString("Method", "claimpublicland"); + msg->addUUID("Invoice", LLUUID::null); + std::string buffer; + buffer = llformat( "%f", west_south.mV[VX]); + msg->nextBlock("ParamList"); + msg->addString("Parameter", buffer); + buffer = llformat( "%f", west_south.mV[VY]); + msg->nextBlock("ParamList"); + msg->addString("Parameter", buffer); + buffer = llformat( "%f", east_north.mV[VX]); + msg->nextBlock("ParamList"); + msg->addString("Parameter", buffer); + buffer = llformat( "%f", east_north.mV[VY]); + msg->nextBlock("ParamList"); + msg->addString("Parameter", buffer); + gAgent.sendReliableMessage(); +} + + + +// HACK for easily testing new avatar geometry +void handle_god_request_avatar_geometry(void *) +{ + if (gAgent.isGodlike()) + { + LLSelectMgr::getInstance()->sendGodlikeRequest("avatar toggle", ""); + } +} + +static bool get_derezzable_objects( + EDeRezDestination dest, + std::string& error, + LLViewerRegion*& first_region, + std::vector* derez_objectsp, + bool only_check = false) +{ + bool found = false; + + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + + if (derez_objectsp) + derez_objectsp->reserve(selection->getRootObjectCount()); + + // Check conditions that we can't deal with, building a list of + // everything that we'll actually be derezzing. + for (LLObjectSelection::valid_root_iterator iter = selection->valid_root_begin(); + iter != selection->valid_root_end(); iter++) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + LLViewerRegion* region = object->getRegion(); + if (!first_region) + { + first_region = region; + } + else + { + if(region != first_region) + { + // Derez doesn't work at all if the some of the objects + // are in regions besides the first object selected. + + // ...crosses region boundaries + error = "AcquireErrorObjectSpan"; + break; + } + } + if (object->isAvatar()) + { + // ...don't acquire avatars + continue; + } + + // If AssetContainers are being sent back, they will appear as + // boxes in the owner's inventory. + if (object->getNVPair("AssetContainer") + && dest != DRD_RETURN_TO_OWNER) + { + // this object is an asset container, derez its contents, not it + LL_WARNS() << "Attempt to derez deprecated AssetContainer object type not supported." << LL_ENDL; + /* + object->requestInventory(container_inventory_arrived, + (void *)(bool)(DRD_TAKE_INTO_AGENT_INVENTORY == dest)); + */ + continue; + } + bool can_derez_current = false; + switch(dest) + { + case DRD_TAKE_INTO_AGENT_INVENTORY: + case DRD_TRASH: + if (!object->isPermanentEnforced() && + ((node->mPermissions->allowTransferTo(gAgent.getID()) && object->permModify()) + || (node->allowOperationOnNode(PERM_OWNER, GP_OBJECT_MANIPULATE)))) + { + can_derez_current = true; + } + break; + + case DRD_RETURN_TO_OWNER: + if(!object->isAttachment()) + { + can_derez_current = true; + } + break; + + default: + if((node->mPermissions->allowTransferTo(gAgent.getID()) + && object->permCopy()) + || gAgent.isGodlike()) + { + can_derez_current = true; + } + break; + } + if(can_derez_current) + { + found = true; + + if (only_check) + // one found, no need to traverse to the end + break; + + if (derez_objectsp) + derez_objectsp->push_back(object); + + } + } + + return found; +} + +static bool can_derez(EDeRezDestination dest) +{ + LLViewerRegion* first_region = NULL; + std::string error; + return get_derezzable_objects(dest, error, first_region, NULL, true); +} + +static void derez_objects( + EDeRezDestination dest, + const LLUUID& dest_id, + LLViewerRegion*& first_region, + std::string& error, + std::vector* objectsp) +{ + std::vector derez_objects; + + if (!objectsp) // if objects to derez not specified + { + // get them from selection + if (!get_derezzable_objects(dest, error, first_region, &derez_objects, false)) + { + LL_WARNS() << "No objects to derez" << LL_ENDL; + return; + } + + objectsp = &derez_objects; + } + + + if(gAgentCamera.cameraMouselook()) + { + gAgentCamera.changeCameraToDefault(); + } + + // This constant is based on (1200 - HEADER_SIZE) / 4 bytes per + // root. I lopped off a few (33) to provide a bit + // pad. HEADER_SIZE is currently 67 bytes, most of which is UUIDs. + // This gives us a maximum of 63500 root objects - which should + // satisfy anybody. + const S32 MAX_ROOTS_PER_PACKET = 250; + const S32 MAX_PACKET_COUNT = 254; + F32 packets = ceil((F32)objectsp->size() / (F32)MAX_ROOTS_PER_PACKET); + if(packets > (F32)MAX_PACKET_COUNT) + { + error = "AcquireErrorTooManyObjects"; + } + + if(error.empty() && objectsp->size() > 0) + { + U8 d = (U8)dest; + LLUUID tid; + tid.generate(); + U8 packet_count = (U8)packets; + S32 object_index = 0; + S32 objects_in_packet = 0; + LLMessageSystem* msg = gMessageSystem; + for(U8 packet_number = 0; + packet_number < packet_count; + ++packet_number) + { + msg->newMessageFast(_PREHASH_DeRezObject); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_AgentBlock); + msg->addUUIDFast(_PREHASH_GroupID, gAgent.getGroupID()); + msg->addU8Fast(_PREHASH_Destination, d); + msg->addUUIDFast(_PREHASH_DestinationID, dest_id); + msg->addUUIDFast(_PREHASH_TransactionID, tid); + msg->addU8Fast(_PREHASH_PacketCount, packet_count); + msg->addU8Fast(_PREHASH_PacketNumber, packet_number); + objects_in_packet = 0; + while((object_index < objectsp->size()) + && (objects_in_packet++ < MAX_ROOTS_PER_PACKET)) + + { + LLViewerObject* object = objectsp->at(object_index++); + msg->nextBlockFast(_PREHASH_ObjectData); + msg->addU32Fast(_PREHASH_ObjectLocalID, object->getLocalID()); + // VEFFECT: DerezObject + LLHUDEffectSpiral* effectp = (LLHUDEffectSpiral*)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_POINT, true); + effectp->setPositionGlobal(object->getPositionGlobal()); + effectp->setColor(LLColor4U(gAgent.getEffectColor())); + } + msg->sendReliable(first_region->getHost()); + } + make_ui_sound("UISndObjectRezOut"); + + // Busy count decremented by inventory update, so only increment + // if will be causing an update. + if (dest != DRD_RETURN_TO_OWNER) + { + gViewerWindow->getWindow()->incBusyCount(); + } + } + else if(!error.empty()) + { + LLNotificationsUtil::add(error); + } +} + +static void derez_objects(EDeRezDestination dest, const LLUUID& dest_id) +{ + LLViewerRegion* first_region = NULL; + std::string error; + derez_objects(dest, dest_id, first_region, error, NULL); +} + +static void derez_objects_separate(EDeRezDestination dest, const LLUUID &dest_id) +{ + std::vector derez_object_list; + std::string error; + LLViewerRegion* first_region = NULL; + if (!get_derezzable_objects(dest, error, first_region, &derez_object_list, false)) + { + LL_WARNS() << "No objects to derez" << LL_ENDL; + return; + } + for (LLViewerObject *opjectp : derez_object_list) + { + std::vector buf_list; + buf_list.push_back(opjectp); + derez_objects(dest, dest_id, first_region, error, &buf_list); + } +} + +void handle_take_copy() +{ + if (LLSelectMgr::getInstance()->getSelection()->isEmpty()) return; + + const LLUUID category_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_OBJECT); + derez_objects(DRD_ACQUIRE_TO_AGENT_INVENTORY, category_id); +} + +void handle_take_separate_copy() +{ + if (LLSelectMgr::getInstance()->getSelection()->isEmpty()) + return; + + const LLUUID category_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_OBJECT); + derez_objects_separate(DRD_ACQUIRE_TO_AGENT_INVENTORY, category_id); +} + +void handle_link_objects() +{ + if (LLSelectMgr::getInstance()->getSelection()->isEmpty()) + { + LLFloaterReg::toggleInstanceOrBringToFront("places"); + } + else + { + LLSelectMgr::getInstance()->linkObjects(); + } +} + +// You can return an object to its owner if it is on your land. +class LLObjectReturn : public view_listener_t +{ +public: + LLObjectReturn() : mFirstRegion(NULL) {} + +private: + bool handleEvent(const LLSD& userdata) + { + if (LLSelectMgr::getInstance()->getSelection()->isEmpty()) return true; + + mObjectSelection = LLSelectMgr::getInstance()->getEditSelection(); + + // Save selected objects, so that we still know what to return after the confirmation dialog resets selection. + get_derezzable_objects(DRD_RETURN_TO_OWNER, mError, mFirstRegion, &mReturnableObjects); + + LLNotificationsUtil::add("ReturnToOwner", LLSD(), LLSD(), boost::bind(&LLObjectReturn::onReturnToOwner, this, _1, _2)); + return true; + } + + bool onReturnToOwner(const LLSD& notification, const LLSD& response) + { + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (0 == option) + { + // Ignore category ID for this derez destination. + derez_objects(DRD_RETURN_TO_OWNER, LLUUID::null, mFirstRegion, mError, &mReturnableObjects); + } + + mReturnableObjects.clear(); + mError.clear(); + mFirstRegion = NULL; + + // drop reference to current selection + mObjectSelection = NULL; + return false; + } + + LLObjectSelectionHandle mObjectSelection; + + std::vector mReturnableObjects; + std::string mError; + LLViewerRegion* mFirstRegion; +}; + + +// Allow return to owner if one or more of the selected items is +// over land you own. +class LLObjectEnableReturn : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + if (LLSelectMgr::getInstance()->getSelection()->isEmpty()) + { + // Do not enable if nothing selected + return false; + } +#ifdef HACKED_GODLIKE_VIEWER + bool new_value = true; +#else + bool new_value = false; + if (gAgent.isGodlike()) + { + new_value = true; + } + else + { + new_value = can_derez(DRD_RETURN_TO_OWNER); + } +#endif + return new_value; + } +}; + +void force_take_copy(void*) +{ + if (LLSelectMgr::getInstance()->getSelection()->isEmpty()) return; + const LLUUID category_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_OBJECT); + derez_objects(DRD_FORCE_TO_GOD_INVENTORY, category_id); +} + +void handle_take(bool take_separate) +{ + // we want to use the folder this was derezzed from if it's + // available. Otherwise, derez to the normal place. + if(LLSelectMgr::getInstance()->getSelection()->isEmpty()) + { + return; + } + + bool you_own_everything = true; + bool locked_but_takeable_object = false; + LLUUID category_id; + + for (LLObjectSelection::root_iterator iter = LLSelectMgr::getInstance()->getSelection()->root_begin(); + iter != LLSelectMgr::getInstance()->getSelection()->root_end(); iter++) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + if(object) + { + if(!object->permYouOwner()) + { + you_own_everything = false; + } + + if(!object->permMove()) + { + locked_but_takeable_object = true; + } + } + if(node->mFolderID.notNull()) + { + if(category_id.isNull()) + { + category_id = node->mFolderID; + } + else if(category_id != node->mFolderID) + { + // we have found two potential destinations. break out + // now and send to the default location. + category_id.setNull(); + break; + } + } + } + if(category_id.notNull()) + { + // there is an unambiguous destination. See if this agent has + // such a location and it is not in the trash or library + if(!gInventory.getCategory(category_id)) + { + // nope, set to NULL. + category_id.setNull(); + } + if(category_id.notNull()) + { + // check trash + const LLUUID trash = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + if(category_id == trash || gInventory.isObjectDescendentOf(category_id, trash)) + { + category_id.setNull(); + } + + // check library + if(gInventory.isObjectDescendentOf(category_id, gInventory.getLibraryRootFolderID())) + { + category_id.setNull(); + } + + // check inbox + const LLUUID inbox_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_INBOX); + if (category_id == inbox_id || gInventory.isObjectDescendentOf(category_id, inbox_id)) + { + category_id.setNull(); + } + } + } + if(category_id.isNull()) + { + category_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_OBJECT); + } + LLSD payload; + payload["folder_id"] = category_id; + + LLNotification::Params params("ConfirmObjectTakeLock"); + params.payload(payload); + // MAINT-290 + // Reason: Showing the confirmation dialog resets object selection, thus there is nothing to derez. + // Fix: pass selection to the confirm_take, so that selection doesn't "die" after confirmation dialog is opened + params.functor.function([take_separate](const LLSD ¬ification, const LLSD &response) + { + if (take_separate) + { + confirm_take_separate(notification, response, LLSelectMgr::instance().getSelection()); + } + else + { + confirm_take(notification, response, LLSelectMgr::instance().getSelection()); + } + }); + + if(locked_but_takeable_object || + !you_own_everything) + { + if(locked_but_takeable_object && you_own_everything) + { + params.name("ConfirmObjectTakeLock"); + } + else if(!locked_but_takeable_object && !you_own_everything) + { + params.name("ConfirmObjectTakeNoOwn"); + } + else + { + params.name("ConfirmObjectTakeLockNoOwn"); + } + + LLNotifications::instance().add(params); + } + else + { + LLNotifications::instance().forceResponse(params, 0); + } +} + +void handle_object_show_inspector() +{ + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + LLViewerObject* objectp = selection->getFirstRootObject(true); + if (!objectp) + { + return; + } + + LLSD params; + params["object_id"] = objectp->getID(); + LLFloaterReg::showInstance("inspect_object", params); +} + +void handle_avatar_show_inspector() +{ + LLVOAvatar* avatar = find_avatar_from_object( LLSelectMgr::getInstance()->getSelection()->getPrimaryObject() ); + if(avatar) + { + LLSD params; + params["avatar_id"] = avatar->getID(); + LLFloaterReg::showInstance("inspect_avatar", params); + } +} + + + +bool confirm_take(const LLSD& notification, const LLSD& response, LLObjectSelectionHandle selection_handle) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if(enable_take() && (option == 0)) + { + derez_objects(DRD_TAKE_INTO_AGENT_INVENTORY, notification["payload"]["folder_id"].asUUID()); + } + return false; +} + +bool confirm_take_separate(const LLSD ¬ification, const LLSD &response, LLObjectSelectionHandle selection_handle) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (enable_take() && (option == 0)) + { + derez_objects_separate(DRD_TAKE_INTO_AGENT_INVENTORY, notification["payload"]["folder_id"].asUUID()); + } + return false; +} + +// You can take an item when it is public and transferrable, or when +// you own it. We err on the side of enabling the item when at least +// one item selected can be copied to inventory. +bool enable_take() +{ + if (sitting_on_selection()) + { + return false; + } + + for (LLObjectSelection::valid_root_iterator iter = LLSelectMgr::getInstance()->getSelection()->valid_root_begin(); + iter != LLSelectMgr::getInstance()->getSelection()->valid_root_end(); iter++) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + if (object->isAvatar()) + { + // ...don't acquire avatars + continue; + } + +#ifdef HACKED_GODLIKE_VIEWER + return true; +#else +# ifdef TOGGLE_HACKED_GODLIKE_VIEWER + if (!LLGridManager::getInstance()->isInProductionGrid() + && gAgent.isGodlike()) + { + return true; + } +# endif + if(!object->isPermanentEnforced() && + ((node->mPermissions->allowTransferTo(gAgent.getID()) + && object->permModify()) + || (node->mPermissions->getOwner() == gAgent.getID()))) + { + return !object->isAttachment(); + } +#endif + } + return false; +} + + +void handle_buy_or_take() +{ + if (LLSelectMgr::getInstance()->getSelection()->isEmpty()) + { + return; + } + + if (is_selection_buy_not_take()) + { + S32 total_price = selection_price(); + + if (total_price <= gStatusBar->getBalance() || total_price == 0) + { + handle_buy(); + } + else + { + LLStringUtil::format_map_t args; + args["AMOUNT"] = llformat("%d", total_price); + LLBuyCurrencyHTML::openCurrencyFloater( LLTrans::getString( "this_object_costs", args ), total_price ); + } + } + else + { + handle_take(); + } +} + +bool visible_buy_object() +{ + return is_selection_buy_not_take() && enable_buy_object(); +} + +bool visible_take_object() +{ + return !is_selection_buy_not_take() && enable_take(); +} + +bool is_multiple_selection() +{ + return (LLSelectMgr::getInstance()->getSelection()->getRootObjectCount() > 1); +} + +bool is_single_selection() +{ + return !is_multiple_selection(); +} + +bool enable_take_objects() +{ + return visible_take_object() && is_multiple_selection(); +} + +bool tools_visible_buy_object() +{ + return is_selection_buy_not_take(); +} + +bool tools_visible_take_object() +{ + return !is_selection_buy_not_take(); +} + +class LLToolsEnableBuyOrTake : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool is_buy = is_selection_buy_not_take(); + bool new_value = is_buy ? enable_buy_object() : enable_take(); + return new_value; + } +}; + +// This is a small helper function to determine if we have a buy or a +// take in the selection. This method is to help with the aliasing +// problems of putting buy and take in the same pie menu space. After +// a fair amont of discussion, it was determined to prefer buy over +// take. The reasoning follows from the fact that when users walk up +// to buy something, they will click on one or more items. Thus, if +// anything is for sale, it becomes a buy operation, and the server +// will group all of the buy items, and copyable/modifiable items into +// one package and give the end user as much as the permissions will +// allow. If the user wanted to take something, they will select fewer +// and fewer items until only 'takeable' items are left. The one +// exception is if you own everything in the selection that is for +// sale, in this case, you can't buy stuff from yourself, so you can +// take it. +// return value = true if selection is a 'buy'. +// false if selection is a 'take' +bool is_selection_buy_not_take() +{ + for (LLObjectSelection::root_iterator iter = LLSelectMgr::getInstance()->getSelection()->root_begin(); + iter != LLSelectMgr::getInstance()->getSelection()->root_end(); iter++) + { + LLSelectNode* node = *iter; + LLViewerObject* obj = node->getObject(); + if(obj && !(obj->permYouOwner()) && (node->mSaleInfo.isForSale())) + { + // you do not own the object and it is for sale, thus, + // it's a buy + return true; + } + } + return false; +} + +S32 selection_price() +{ + S32 total_price = 0; + for (LLObjectSelection::root_iterator iter = LLSelectMgr::getInstance()->getSelection()->root_begin(); + iter != LLSelectMgr::getInstance()->getSelection()->root_end(); iter++) + { + LLSelectNode* node = *iter; + LLViewerObject* obj = node->getObject(); + if(obj && !(obj->permYouOwner()) && (node->mSaleInfo.isForSale())) + { + // you do not own the object and it is for sale. + // Add its price. + total_price += node->mSaleInfo.getSalePrice(); + } + } + + return total_price; +} +/* +bool callback_show_buy_currency(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (0 == option) + { + LL_INFOS() << "Loading page " << LLNotifications::instance().getGlobalString("BUY_CURRENCY_URL") << LL_ENDL; + LLWeb::loadURL(LLNotifications::instance().getGlobalString("BUY_CURRENCY_URL")); + } + return false; +} +*/ + +void show_buy_currency(const char* extra) +{ + // Don't show currency web page for branded clients. +/* + std::ostringstream mesg; + if (extra != NULL) + { + mesg << extra << "\n \n"; + } + mesg << "Go to " << LLNotifications::instance().getGlobalString("BUY_CURRENCY_URL")<< "\nfor information on purchasing currency?"; +*/ + LLSD args; + if (extra != NULL) + { + args["EXTRA"] = extra; + } + LLNotificationsUtil::add("PromptGoToCurrencyPage", args);//, LLSD(), callback_show_buy_currency); +} + +void handle_buy() +{ + if (LLSelectMgr::getInstance()->getSelection()->isEmpty()) return; + + LLSaleInfo sale_info; + bool valid = LLSelectMgr::getInstance()->selectGetSaleInfo(sale_info); + if (!valid) return; + + S32 price = sale_info.getSalePrice(); + + if (price > 0 && price > gStatusBar->getBalance()) + { + LLStringUtil::format_map_t args; + args["AMOUNT"] = llformat("%d", price); + LLBuyCurrencyHTML::openCurrencyFloater( LLTrans::getString("this_object_costs", args), price ); + return; + } + + if (sale_info.getSaleType() == LLSaleInfo::FS_CONTENTS) + { + handle_buy_contents(sale_info); + } + else + { + handle_buy_object(sale_info); + } +} + +bool anyone_copy_selection(LLSelectNode* nodep) +{ + bool perm_copy = (bool)(nodep->getObject()->permCopy()); + bool all_copy = (bool)(nodep->mPermissions->getMaskEveryone() & PERM_COPY); + return perm_copy && all_copy; +} + +bool for_sale_selection(LLSelectNode* nodep) +{ + return nodep->mSaleInfo.isForSale() + && nodep->mPermissions->getMaskOwner() & PERM_TRANSFER + && (nodep->mPermissions->getMaskOwner() & PERM_COPY + || nodep->mSaleInfo.getSaleType() != LLSaleInfo::FS_COPY); +} + +bool sitting_on_selection() +{ + LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(); + if (!node) + { + return false; + } + + if (!node->mValid) + { + return false; + } + + LLViewerObject* root_object = node->getObject(); + if (!root_object) + { + return false; + } + + // Need to determine if avatar is sitting on this object + if (!isAgentAvatarValid()) return false; + + return (gAgentAvatarp->isSitting() && gAgentAvatarp->getRoot() == root_object); +} + +class LLToolsSaveToObjectInventory : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(); + if(node && (node->mValid) && (!node->mFromTaskID.isNull())) + { + // *TODO: check to see if the fromtaskid object exists. + derez_objects(DRD_SAVE_INTO_TASK_INVENTORY, node->mFromTaskID); + } + return true; + } +}; + +class LLToolsEnablePathfinding : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + return (LLPathfindingManager::getInstance() != NULL) && LLPathfindingManager::getInstance()->isPathfindingEnabledForCurrentRegion(); + } +}; + +class LLToolsEnablePathfindingView : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + return (LLPathfindingManager::getInstance() != NULL) && LLPathfindingManager::getInstance()->isPathfindingEnabledForCurrentRegion() && LLPathfindingManager::getInstance()->isPathfindingViewEnabled(); + } +}; + +class LLToolsDoPathfindingRebakeRegion : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool hasPathfinding = (LLPathfindingManager::getInstance() != NULL); + + if (hasPathfinding) + { + LLMenuOptionPathfindingRebakeNavmesh::getInstance()->sendRequestRebakeNavmesh(); + } + + return hasPathfinding; + } +}; + +class LLToolsEnablePathfindingRebakeRegion : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool returnValue = false; + + if (LLNavigationBar::instanceExists()) + { + returnValue = LLNavigationBar::getInstance()->isRebakeNavMeshAvailable(); + } + return returnValue; + } +}; + +// Round the position of all root objects to the grid +class LLToolsSnapObjectXY : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + F64 snap_size = (F64)gSavedSettings.getF32("GridResolution"); + + for (LLObjectSelection::root_iterator iter = LLSelectMgr::getInstance()->getSelection()->root_begin(); + iter != LLSelectMgr::getInstance()->getSelection()->root_end(); iter++) + { + LLSelectNode* node = *iter; + LLViewerObject* obj = node->getObject(); + if (obj->permModify()) + { + LLVector3d pos_global = obj->getPositionGlobal(); + F64 round_x = fmod(pos_global.mdV[VX], snap_size); + if (round_x < snap_size * 0.5) + { + // closer to round down + pos_global.mdV[VX] -= round_x; + } + else + { + // closer to round up + pos_global.mdV[VX] -= round_x; + pos_global.mdV[VX] += snap_size; + } + + F64 round_y = fmod(pos_global.mdV[VY], snap_size); + if (round_y < snap_size * 0.5) + { + pos_global.mdV[VY] -= round_y; + } + else + { + pos_global.mdV[VY] -= round_y; + pos_global.mdV[VY] += snap_size; + } + + obj->setPositionGlobal(pos_global, false); + } + } + LLSelectMgr::getInstance()->sendMultipleUpdate(UPD_POSITION); + return true; + } +}; + +// Determine if the option to cycle between linked prims is shown +class LLToolsEnableSelectNextPart : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = (!LLSelectMgr::getInstance()->getSelection()->isEmpty() + && (gSavedSettings.getBOOL("EditLinkedParts") + || LLToolFace::getInstance() == LLToolMgr::getInstance()->getCurrentTool())); + return new_value; + } +}; + +// Cycle selection through linked children or/and faces in selected object. +// FIXME: Order of children list is not always the same as sim's idea of link order. This may confuse +// resis. Need link position added to sim messages to address this. +class LLToolsSelectNextPartFace : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool cycle_faces = LLToolFace::getInstance() == LLToolMgr::getInstance()->getCurrentTool(); + bool cycle_linked = gSavedSettings.getBOOL("EditLinkedParts"); + + if (!cycle_faces && !cycle_linked) + { + // Nothing to do + return true; + } + + bool fwd = (userdata.asString() == "next"); + bool prev = (userdata.asString() == "previous"); + bool ifwd = (userdata.asString() == "includenext"); + bool iprev = (userdata.asString() == "includeprevious"); + + LLViewerObject* to_select = NULL; + bool restart_face_on_part = !cycle_faces; + S32 new_te = 0; + + if (cycle_faces) + { + // Cycle through faces of current selection, if end is reached, swithc to next part (if present) + LLSelectNode* nodep = LLSelectMgr::getInstance()->getSelection()->getFirstNode(); + if (!nodep) return false; + to_select = nodep->getObject(); + if (!to_select) return false; + + S32 te_count = to_select->getNumTEs(); + S32 selected_te = nodep->getLastOperatedTE(); + + if (fwd || ifwd) + { + if (selected_te < 0) + { + new_te = 0; + } + else if (selected_te + 1 < te_count) + { + // select next face + new_te = selected_te + 1; + } + else + { + // restart from first face on next part + restart_face_on_part = true; + } + } + else if (prev || iprev) + { + if (selected_te > te_count) + { + new_te = te_count - 1; + } + else if (selected_te - 1 >= 0) + { + // select previous face + new_te = selected_te - 1; + } + else + { + // restart from last face on next part + restart_face_on_part = true; + } + } + } + + S32 object_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount(); + if (cycle_linked && object_count && restart_face_on_part) + { + LLViewerObject* selected = LLSelectMgr::getInstance()->getSelection()->getFirstObject(); + if (selected && selected->getRootEdit()) + { + LLViewerObject::child_list_t children = selected->getRootEdit()->getChildren(); + children.push_front(selected->getRootEdit()); // need root in the list too + + for (LLViewerObject::child_list_t::iterator iter = children.begin(); iter != children.end(); ++iter) + { + if ((*iter)->isSelected()) + { + if (object_count > 1 && (fwd || prev)) // multiple selection, find first or last selected if not include + { + to_select = *iter; + if (fwd) + { + // stop searching if going forward; repeat to get last hit if backward + break; + } + } + else if ((object_count == 1) || (ifwd || iprev)) // single selection or include + { + if (fwd || ifwd) + { + ++iter; + while (iter != children.end() && ((*iter)->isAvatar() || (ifwd && (*iter)->isSelected()))) + { + ++iter; // skip sitting avatars and selected if include + } + } + else // backward + { + iter = (iter == children.begin() ? children.end() : iter); + --iter; + while (iter != children.begin() && ((*iter)->isAvatar() || (iprev && (*iter)->isSelected()))) + { + --iter; // skip sitting avatars and selected if include + } + } + iter = (iter == children.end() ? children.begin() : iter); + to_select = *iter; + break; + } + } + } + } + } + + if (to_select) + { + if (gFocusMgr.childHasKeyboardFocus(gFloaterTools)) + { + gFocusMgr.setKeyboardFocus(NULL); // force edit toolbox to commit any changes + } + if (fwd || prev) + { + LLSelectMgr::getInstance()->deselectAll(); + } + if (cycle_faces) + { + if (restart_face_on_part) + { + if (fwd || ifwd) + { + new_te = 0; + } + else + { + new_te = to_select->getNumTEs() - 1; + } + } + LLSelectMgr::getInstance()->selectObjectOnly(to_select, new_te); + LLSelectMgr::getInstance()->addAsIndividual(to_select, new_te, false); + } + else + { + LLSelectMgr::getInstance()->selectObjectOnly(to_select); + } + return true; + } + return true; + } +}; + +class LLToolsStopAllAnimations : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + gAgent.stopCurrentAnimations(); + return true; + } +}; + +class LLToolsReleaseKeys : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + gAgent.forceReleaseControls(); + + return true; + } +}; + +class LLToolsEnableReleaseKeys : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + return gAgent.anyControlGrabbed(); + } +}; + + +class LLEditEnableCut : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = LLEditMenuHandler::gEditMenuHandler && LLEditMenuHandler::gEditMenuHandler->canCut(); + return new_value; + } +}; + +class LLEditCut : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + if( LLEditMenuHandler::gEditMenuHandler ) + { + LLEditMenuHandler::gEditMenuHandler->cut(); + } + return true; + } +}; + +class LLEditEnableCopy : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = LLEditMenuHandler::gEditMenuHandler && LLEditMenuHandler::gEditMenuHandler->canCopy(); + return new_value; + } +}; + +class LLEditCopy : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + if( LLEditMenuHandler::gEditMenuHandler ) + { + LLEditMenuHandler::gEditMenuHandler->copy(); + } + return true; + } +}; + +class LLEditEnablePaste : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = LLEditMenuHandler::gEditMenuHandler && LLEditMenuHandler::gEditMenuHandler->canPaste(); + return new_value; + } +}; + +class LLEditPaste : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + if( LLEditMenuHandler::gEditMenuHandler ) + { + LLEditMenuHandler::gEditMenuHandler->paste(); + } + return true; + } +}; + +class LLEditEnableDelete : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = LLEditMenuHandler::gEditMenuHandler && LLEditMenuHandler::gEditMenuHandler->canDoDelete(); + return new_value; + } +}; + +class LLEditDelete : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + // If a text field can do a deletion, it gets precedence over deleting + // an object in the world. + if( LLEditMenuHandler::gEditMenuHandler && LLEditMenuHandler::gEditMenuHandler->canDoDelete()) + { + LLEditMenuHandler::gEditMenuHandler->doDelete(); + } + + // and close any pie/context menus when done + gMenuHolder->hideMenus(); + + // When deleting an object we may not actually be done + // Keep selection so we know what to delete when confirmation is needed about the delete + gMenuObject->hide(); + return true; + } +}; + +void handle_spellcheck_replace_with_suggestion(const LLUICtrl* ctrl, const LLSD& param) +{ + const LLContextMenu* menu = dynamic_cast(ctrl->getParent()); + LLSpellCheckMenuHandler* spellcheck_handler = (menu) ? dynamic_cast(menu->getSpawningView()) : NULL; + if ( (!spellcheck_handler) || (!spellcheck_handler->getSpellCheck()) ) + { + return; + } + + U32 index = 0; + if ( (!LLStringUtil::convertToU32(param.asString(), index)) || (index >= spellcheck_handler->getSuggestionCount()) ) + { + return; + } + + spellcheck_handler->replaceWithSuggestion(index); +} + +bool visible_spellcheck_suggestion(LLUICtrl* ctrl, const LLSD& param) +{ + LLMenuItemGL* item = dynamic_cast(ctrl); + const LLContextMenu* menu = (item) ? dynamic_cast(item->getParent()) : NULL; + const LLSpellCheckMenuHandler* spellcheck_handler = (menu) ? dynamic_cast(menu->getSpawningView()) : NULL; + if ( (!spellcheck_handler) || (!spellcheck_handler->getSpellCheck()) ) + { + return false; + } + + U32 index = 0; + if ( (!LLStringUtil::convertToU32(param.asString(), index)) || (index >= spellcheck_handler->getSuggestionCount()) ) + { + return false; + } + + item->setLabel(spellcheck_handler->getSuggestion(index)); + return true; +} + +void handle_spellcheck_add_to_dictionary(const LLUICtrl* ctrl) +{ + const LLContextMenu* menu = dynamic_cast(ctrl->getParent()); + LLSpellCheckMenuHandler* spellcheck_handler = (menu) ? dynamic_cast(menu->getSpawningView()) : NULL; + if ( (spellcheck_handler) && (spellcheck_handler->canAddToDictionary()) ) + { + spellcheck_handler->addToDictionary(); + } +} + +bool enable_spellcheck_add_to_dictionary(const LLUICtrl* ctrl) +{ + const LLContextMenu* menu = dynamic_cast(ctrl->getParent()); + const LLSpellCheckMenuHandler* spellcheck_handler = (menu) ? dynamic_cast(menu->getSpawningView()) : NULL; + return (spellcheck_handler) && (spellcheck_handler->canAddToDictionary()); +} + +void handle_spellcheck_add_to_ignore(const LLUICtrl* ctrl) +{ + const LLContextMenu* menu = dynamic_cast(ctrl->getParent()); + LLSpellCheckMenuHandler* spellcheck_handler = (menu) ? dynamic_cast(menu->getSpawningView()) : NULL; + if ( (spellcheck_handler) && (spellcheck_handler->canAddToIgnore()) ) + { + spellcheck_handler->addToIgnore(); + } +} + +bool enable_spellcheck_add_to_ignore(const LLUICtrl* ctrl) +{ + const LLContextMenu* menu = dynamic_cast(ctrl->getParent()); + const LLSpellCheckMenuHandler* spellcheck_handler = (menu) ? dynamic_cast(menu->getSpawningView()) : NULL; + return (spellcheck_handler) && (spellcheck_handler->canAddToIgnore()); +} + +bool enable_object_return() +{ + return (!LLSelectMgr::getInstance()->getSelection()->isEmpty() && + (gAgent.isGodlike() || can_derez(DRD_RETURN_TO_OWNER))); +} + +bool enable_object_delete() +{ + bool new_value = +#ifdef HACKED_GODLIKE_VIEWER + true; +#else +# ifdef TOGGLE_HACKED_GODLIKE_VIEWER + (!LLGridManager::getInstance()->isInProductionGrid() + && gAgent.isGodlike()) || +# endif + LLSelectMgr::getInstance()->canDoDelete(); +#endif + return new_value; +} + +class LLObjectsReturnPackage +{ +public: + LLObjectsReturnPackage() : mObjectSelection(), mReturnableObjects(), mError(), mFirstRegion(NULL) {}; + ~LLObjectsReturnPackage() + { + mObjectSelection.clear(); + mReturnableObjects.clear(); + mError.clear(); + mFirstRegion = NULL; + }; + + LLObjectSelectionHandle mObjectSelection; + std::vector mReturnableObjects; + std::string mError; + LLViewerRegion *mFirstRegion; +}; + +static void return_objects(LLObjectsReturnPackage *objectsReturnPackage, const LLSD& notification, const LLSD& response) +{ + if (LLNotificationsUtil::getSelectedOption(notification, response) == 0) + { + // Ignore category ID for this derez destination. + derez_objects(DRD_RETURN_TO_OWNER, LLUUID::null, objectsReturnPackage->mFirstRegion, objectsReturnPackage->mError, &objectsReturnPackage->mReturnableObjects); + } + + delete objectsReturnPackage; +} + +void handle_object_return() +{ + if (!LLSelectMgr::getInstance()->getSelection()->isEmpty()) + { + LLObjectsReturnPackage *objectsReturnPackage = new LLObjectsReturnPackage(); + objectsReturnPackage->mObjectSelection = LLSelectMgr::getInstance()->getEditSelection(); + + // Save selected objects, so that we still know what to return after the confirmation dialog resets selection. + get_derezzable_objects(DRD_RETURN_TO_OWNER, objectsReturnPackage->mError, objectsReturnPackage->mFirstRegion, &objectsReturnPackage->mReturnableObjects); + + LLNotificationsUtil::add("ReturnToOwner", LLSD(), LLSD(), boost::bind(&return_objects, objectsReturnPackage, _1, _2)); + } +} + +void handle_object_delete() +{ + + if (LLSelectMgr::getInstance()) + { + LLSelectMgr::getInstance()->doDelete(); + } + + // and close any pie/context menus when done + gMenuHolder->hideMenus(); + + // When deleting an object we may not actually be done + // Keep selection so we know what to delete when confirmation is needed about the delete + gMenuObject->hide(); + return; +} + +void handle_force_delete(void*) +{ + LLSelectMgr::getInstance()->selectForceDelete(); +} + +class LLViewEnableJoystickFlycam : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = (gSavedSettings.getBOOL("JoystickEnabled") && gSavedSettings.getBOOL("JoystickFlycamEnabled")); + return new_value; + } +}; + +class LLViewEnableLastChatter : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + // *TODO: add check that last chatter is in range + bool new_value = (gAgentCamera.cameraThirdPerson() && gAgent.getLastChatter().notNull()); + return new_value; + } +}; + +class LLEditEnableDeselect : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = LLEditMenuHandler::gEditMenuHandler && LLEditMenuHandler::gEditMenuHandler->canDeselect(); + return new_value; + } +}; + +class LLEditDeselect : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + if( LLEditMenuHandler::gEditMenuHandler ) + { + LLEditMenuHandler::gEditMenuHandler->deselect(); + } + return true; + } +}; + +class LLEditEnableSelectAll : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = LLEditMenuHandler::gEditMenuHandler && LLEditMenuHandler::gEditMenuHandler->canSelectAll(); + return new_value; + } +}; + + +class LLEditSelectAll : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + if( LLEditMenuHandler::gEditMenuHandler ) + { + LLEditMenuHandler::gEditMenuHandler->selectAll(); + } + return true; + } +}; + + +class LLEditEnableUndo : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = LLEditMenuHandler::gEditMenuHandler && LLEditMenuHandler::gEditMenuHandler->canUndo(); + return new_value; + } +}; + +class LLEditUndo : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + if( LLEditMenuHandler::gEditMenuHandler && LLEditMenuHandler::gEditMenuHandler->canUndo() ) + { + LLEditMenuHandler::gEditMenuHandler->undo(); + } + return true; + } +}; + +class LLEditEnableRedo : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = LLEditMenuHandler::gEditMenuHandler && LLEditMenuHandler::gEditMenuHandler->canRedo(); + return new_value; + } +}; + +class LLEditRedo : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + if( LLEditMenuHandler::gEditMenuHandler && LLEditMenuHandler::gEditMenuHandler->canRedo() ) + { + LLEditMenuHandler::gEditMenuHandler->redo(); + } + return true; + } +}; + + + +void print_object_info(void*) +{ + LLSelectMgr::getInstance()->selectionDump(); +} + +void print_agent_nvpairs(void*) +{ + LLViewerObject *objectp; + + LL_INFOS() << "Agent Name Value Pairs" << LL_ENDL; + + objectp = gObjectList.findObject(gAgentID); + if (objectp) + { + objectp->printNameValuePairs(); + } + else + { + LL_INFOS() << "Can't find agent object" << LL_ENDL; + } + + LL_INFOS() << "Camera at " << gAgentCamera.getCameraPositionGlobal() << LL_ENDL; +} + +void show_debug_menus() +{ + // this might get called at login screen where there is no menu so only toggle it if one exists + if ( gMenuBarView ) + { + bool debug = gSavedSettings.getBOOL("UseDebugMenus"); + bool qamode = gSavedSettings.getBOOL("QAMode"); + + gMenuBarView->setItemVisible("Advanced", debug); +// gMenuBarView->setItemEnabled("Advanced", debug); // Don't disable Advanced keyboard shortcuts when hidden + + gMenuBarView->setItemVisible("Debug", qamode); + gMenuBarView->setItemEnabled("Debug", qamode); + + gMenuBarView->setItemVisible("Develop", qamode); + gMenuBarView->setItemEnabled("Develop", qamode); + + // Server ('Admin') menu hidden when not in godmode. + const bool show_server_menu = (gAgent.getGodLevel() > GOD_NOT || (debug && gAgent.getAdminOverride())); + gMenuBarView->setItemVisible("Admin", show_server_menu); + gMenuBarView->setItemEnabled("Admin", show_server_menu); + } + if (gLoginMenuBarView) + { + bool debug = gSavedSettings.getBOOL("UseDebugMenus"); + gLoginMenuBarView->setItemVisible("Debug", debug); + gLoginMenuBarView->setItemEnabled("Debug", debug); + } +} + +void toggle_debug_menus(void*) +{ + bool visible = ! gSavedSettings.getBOOL("UseDebugMenus"); + gSavedSettings.setBOOL("UseDebugMenus", visible); + show_debug_menus(); +} + + +// LLUUID gExporterRequestID; +// std::string gExportDirectory; + +// LLUploadDialog *gExportDialog = NULL; + +// void handle_export_selected( void * ) +// { +// LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); +// if (selection->isEmpty()) +// { +// return; +// } +// LL_INFOS() << "Exporting selected objects:" << LL_ENDL; + +// gExporterRequestID.generate(); +// gExportDirectory = ""; + +// LLMessageSystem* msg = gMessageSystem; +// msg->newMessageFast(_PREHASH_ObjectExportSelected); +// msg->nextBlockFast(_PREHASH_AgentData); +// msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); +// msg->addUUIDFast(_PREHASH_RequestID, gExporterRequestID); +// msg->addS16Fast(_PREHASH_VolumeDetail, 4); + +// for (LLObjectSelection::root_iterator iter = selection->root_begin(); +// iter != selection->root_end(); iter++) +// { +// LLSelectNode* node = *iter; +// LLViewerObject* object = node->getObject(); +// msg->nextBlockFast(_PREHASH_ObjectData); +// msg->addUUIDFast(_PREHASH_ObjectID, object->getID()); +// LL_INFOS() << "Object: " << object->getID() << LL_ENDL; +// } +// msg->sendReliable(gAgent.getRegion()->getHost()); + +// gExportDialog = LLUploadDialog::modalUploadDialog("Exporting selected objects..."); +// } +// + +class LLCommunicateNearbyChat : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLFloaterIMContainer* im_box = LLFloaterIMContainer::getInstance(); + LLFloaterIMNearbyChat* floater_nearby = LLFloaterReg::getTypedInstance("nearby_chat"); + if (floater_nearby->isInVisibleChain() && !floater_nearby->isTornOff() + && im_box->getSelectedSession() == LLUUID() && im_box->getConversationListItemSize() > 1) + { + im_box->selectNextorPreviousConversation(false); + } + else + { + LLFloaterReg::toggleInstanceOrBringToFront("nearby_chat"); + } + return true; + } +}; + +class LLWorldSetHomeLocation : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + // we just send the message and let the server check for failure cases + // server will echo back a "Home position set." alert if it succeeds + // and the home location screencapture happens when that alert is recieved + gAgent.setStartPosition(START_LOCATION_ID_HOME); + return true; + } +}; + +class LLWorldLindenHome : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string url = LLFloaterLandHoldings::sHasLindenHome ? LLTrans::getString("lindenhomes_my_home_url") : LLTrans::getString("lindenhomes_get_home_url"); + LLWeb::loadURL(url); + return true; + } +}; + +class LLWorldTeleportHome : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + gAgent.teleportHome(); + return true; + } +}; + +class LLWorldAlwaysRun : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + // as well as altering the default walk-vs-run state, + // we also change the *current* walk-vs-run state. + if (gAgent.getAlwaysRun()) + { + gAgent.clearAlwaysRun(); + gAgent.clearRunning(); + } + else + { + gAgent.setAlwaysRun(); + gAgent.setRunning(); + } + + // tell the simulator. + gAgent.sendWalkRun(gAgent.getAlwaysRun()); + + // Update Movement Controls according to AlwaysRun mode + LLFloaterMove::setAlwaysRunMode(gAgent.getAlwaysRun()); + + return true; + } +}; + +class LLWorldCheckAlwaysRun : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = gAgent.getAlwaysRun(); + return new_value; + } +}; + +class LLWorldSetAway : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + if (gAgent.getAFK()) + { + gAgent.clearAFK(); + } + else + { + gAgent.setAFK(); + } + return true; + } +}; + +class LLWorldSetDoNotDisturb : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + if (gAgent.isDoNotDisturb()) + { + gAgent.setDoNotDisturb(false); + } + else + { + gAgent.setDoNotDisturb(true); + LLNotificationsUtil::add("DoNotDisturbModeSet"); + } + return true; + } +}; + +class LLWorldCreateLandmark : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLFloaterReg::showInstance("add_landmark"); + + return true; + } +}; + +class LLWorldPlaceProfile : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLFloaterSidePanelContainer::showPanel("places", LLSD().with("type", "agent")); + + return true; + } +}; + +void handle_look_at_selection(const LLSD& param) +{ + const F32 PADDING_FACTOR = 1.75f; + bool zoom = (param.asString() == "zoom"); + if (!LLSelectMgr::getInstance()->getSelection()->isEmpty()) + { + gAgentCamera.setFocusOnAvatar(false, ANIMATE); + + LLBBox selection_bbox = LLSelectMgr::getInstance()->getBBoxOfSelection(); + F32 angle_of_view = llmax(0.1f, LLViewerCamera::getInstance()->getAspect() > 1.f ? LLViewerCamera::getInstance()->getView() * LLViewerCamera::getInstance()->getAspect() : LLViewerCamera::getInstance()->getView()); + F32 distance = selection_bbox.getExtentLocal().magVec() * PADDING_FACTOR / atan(angle_of_view); + + LLVector3 obj_to_cam = LLViewerCamera::getInstance()->getOrigin() - selection_bbox.getCenterAgent(); + obj_to_cam.normVec(); + + LLUUID object_id; + if (LLSelectMgr::getInstance()->getSelection()->getPrimaryObject()) + { + object_id = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject()->mID; + } + if (zoom) + { + // Make sure we are not increasing the distance between the camera and object + LLVector3d orig_distance = gAgentCamera.getCameraPositionGlobal() - LLSelectMgr::getInstance()->getSelectionCenterGlobal(); + distance = llmin(distance, (F32) orig_distance.length()); + + gAgentCamera.setCameraPosAndFocusGlobal(LLSelectMgr::getInstance()->getSelectionCenterGlobal() + LLVector3d(obj_to_cam * distance), + LLSelectMgr::getInstance()->getSelectionCenterGlobal(), + object_id ); + + } + else + { + gAgentCamera.setFocusGlobal( LLSelectMgr::getInstance()->getSelectionCenterGlobal(), object_id ); + } + } +} + +void handle_zoom_to_object(LLUUID object_id) +{ + const F32 PADDING_FACTOR = 2.f; + + LLViewerObject* object = gObjectList.findObject(object_id); + + if (object) + { + gAgentCamera.setFocusOnAvatar(false, ANIMATE); + + LLBBox bbox = object->getBoundingBoxAgent() ; + F32 angle_of_view = llmax(0.1f, LLViewerCamera::getInstance()->getAspect() > 1.f ? LLViewerCamera::getInstance()->getView() * LLViewerCamera::getInstance()->getAspect() : LLViewerCamera::getInstance()->getView()); + F32 distance = bbox.getExtentLocal().magVec() * PADDING_FACTOR / atan(angle_of_view); + + LLVector3 obj_to_cam = LLViewerCamera::getInstance()->getOrigin() - bbox.getCenterAgent(); + obj_to_cam.normVec(); + + + LLVector3d object_center_global = gAgent.getPosGlobalFromAgent(bbox.getCenterAgent()); + + gAgentCamera.setCameraPosAndFocusGlobal(object_center_global + LLVector3d(obj_to_cam * distance), + object_center_global, + object_id ); + } +} + +class LLAvatarInviteToGroup : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLVOAvatar* avatar = find_avatar_from_object( LLSelectMgr::getInstance()->getSelection()->getPrimaryObject() ); + if(avatar) + { + LLAvatarActions::inviteToGroup(avatar->getID()); + } + return true; + } +}; + +class LLAvatarAddFriend : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLVOAvatar* avatar = find_avatar_from_object( LLSelectMgr::getInstance()->getSelection()->getPrimaryObject() ); + if(avatar && !LLAvatarActions::isFriend(avatar->getID())) + { + request_friendship(avatar->getID()); + } + return true; + } +}; + + +class LLAvatarToggleMyProfile : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLFloater* instance = LLAvatarActions::getProfileFloater(gAgent.getID()); + if (LLFloater::isMinimized(instance)) + { + instance->setMinimized(false); + instance->setFocus(true); + } + else if (!LLFloater::isShown(instance)) + { + LLAvatarActions::showProfile(gAgent.getID()); + } + else if (!instance->hasFocus() && !instance->getIsChrome()) + { + instance->setFocus(true); + } + else + { + instance->closeFloater(); + } + return true; + } +}; + +class LLAvatarTogglePicks : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLFloater * instance = LLAvatarActions::getProfileFloater(gAgent.getID()); + if (LLFloater::isMinimized(instance) || (instance && !instance->hasFocus() && !instance->getIsChrome())) + { + instance->setMinimized(false); + instance->setFocus(true); + LLAvatarActions::showPicks(gAgent.getID()); + } + else if (picks_tab_visible()) + { + instance->closeFloater(); + } + else + { + LLAvatarActions::showPicks(gAgent.getID()); + } + return true; + } +}; + +class LLAvatarToggleSearch : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLFloater* instance = LLFloaterReg::findInstance("search"); + if (LLFloater::isMinimized(instance)) + { + instance->setMinimized(false); + instance->setFocus(true); + } + else if (!LLFloater::isShown(instance)) + { + LLFloaterReg::showInstance("search"); + } + else if (!instance->hasFocus() && !instance->getIsChrome()) + { + instance->setFocus(true); + } + else + { + instance->closeFloater(); + } + return true; + } +}; + +class LLAvatarResetSkeleton: public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLVOAvatar* avatar = NULL; + LLViewerObject *obj = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + if (obj) + { + avatar = obj->getAvatar(); + } + if(avatar) + { + avatar->resetSkeleton(false); + } + return true; + } +}; + +class LLAvatarEnableResetSkeleton: public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLViewerObject *obj = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + if (obj && obj->getAvatar()) + { + return true; + } + return false; + } +}; + + +class LLAvatarResetSkeletonAndAnimations : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLVOAvatar* avatar = find_avatar_from_object(LLSelectMgr::getInstance()->getSelection()->getPrimaryObject()); + if (avatar) + { + avatar->resetSkeleton(true); + } + return true; + } +}; + +class LLAvatarResetSelfSkeletonAndAnimations : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLVOAvatar* avatar = find_avatar_from_object(LLSelectMgr::getInstance()->getSelection()->getPrimaryObject()); + if (avatar) + { + avatar->resetSkeleton(true); + } + else + { + gAgentAvatarp->resetSkeleton(true); + } + return true; + } +}; + + +class LLAvatarAddContact : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLVOAvatar* avatar = find_avatar_from_object( LLSelectMgr::getInstance()->getSelection()->getPrimaryObject() ); + if(avatar) + { + create_inventory_callingcard(avatar->getID()); + } + return true; + } +}; + +bool complete_give_money(const LLSD& notification, const LLSD& response, LLObjectSelectionHandle selection) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) + { + gAgent.setDoNotDisturb(false); + } + + LLViewerObject* objectp = selection->getPrimaryObject(); + + // Show avatar's name if paying attachment + if (objectp && objectp->isAttachment()) + { + while (objectp && !objectp->isAvatar()) + { + objectp = (LLViewerObject*)objectp->getParent(); + } + } + + if (objectp) + { + if (objectp->isAvatar()) + { + const bool is_group = false; + LLFloaterPayUtil::payDirectly(&give_money, + objectp->getID(), + is_group); + } + else + { + LLFloaterPayUtil::payViaObject(&give_money, selection); + } + } + return false; +} + +void handle_give_money_dialog() +{ + LLNotification::Params params("DoNotDisturbModePay"); + params.functor.function(boost::bind(complete_give_money, _1, _2, LLSelectMgr::getInstance()->getSelection())); + + if (gAgent.isDoNotDisturb()) + { + // warn users of being in do not disturb mode during a transaction + LLNotifications::instance().add(params); + } + else + { + LLNotifications::instance().forceResponse(params, 1); + } +} + +bool enable_pay_avatar() +{ + LLViewerObject* obj = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + LLVOAvatar* avatar = find_avatar_from_object(obj); + return (avatar != NULL); +} + +bool enable_pay_object() +{ + LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + if( object ) + { + LLViewerObject *parent = (LLViewerObject *)object->getParent(); + if((object->flagTakesMoney()) || (parent && parent->flagTakesMoney())) + { + return true; + } + } + return false; +} + +bool enable_object_stand_up() +{ + // 'Object Stand Up' menu item is enabled when agent is sitting on selection + return sitting_on_selection(); +} + +bool enable_object_sit(LLUICtrl* ctrl) +{ + // 'Object Sit' menu item is enabled when agent is not sitting on selection + bool sitting_on_sel = sitting_on_selection(); + if (!sitting_on_sel) + { + // init default labels + init_default_item_label(ctrl); + + // Update label + LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(); + if (node && node->mValid && !node->mSitName.empty()) + { + ctrl->setValue(node->mSitName); + } + else + { + ctrl->setValue(get_default_item_label(ctrl->getName())); + } + } + return !sitting_on_sel && is_object_sittable(); +} + +void dump_select_mgr(void*) +{ + LLSelectMgr::getInstance()->dump(); +} + +void dump_inventory(void*) +{ + gInventory.dumpInventory(); +} + + +void handle_dump_followcam(void*) +{ + LLFollowCamMgr::getInstance()->dump(); +} + +void handle_viewer_enable_message_log(void*) +{ + gMessageSystem->startLogging(); +} + +void handle_viewer_disable_message_log(void*) +{ + gMessageSystem->stopLogging(); +} + +void handle_customize_avatar() +{ + LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "my_outfits")); +} + +void handle_edit_outfit() +{ + LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "edit_outfit")); +} + +void handle_now_wearing() +{ + LLSidepanelAppearance *panel_appearance = dynamic_cast(LLFloaterSidePanelContainer::getPanel("appearance")); + if (panel_appearance && panel_appearance->isInVisibleChain() && panel_appearance->isCOFPanelVisible()) + { + LLFloaterReg::findInstance("appearance")->closeFloater(); + return; + } + + LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "now_wearing")); +} + +void handle_edit_shape() +{ + LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "edit_shape")); +} + +void handle_hover_height() +{ + LLFloaterReg::showInstance("edit_hover_height"); +} + +void handle_edit_physics() +{ + LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "edit_physics")); +} + +void handle_report_abuse() +{ + // Prevent menu from appearing in screen shot. + gMenuHolder->hideMenus(); + LLFloaterReporter::showFromMenu(COMPLAINT_REPORT); +} + +void handle_buy_currency() +{ + LLBuyCurrencyHTML::openCurrencyFloater(); +} + +class LLFloaterVisible : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string floater_name = userdata.asString(); + bool new_value = false; + { + new_value = LLFloaterReg::instanceVisible(floater_name); + } + return new_value; + } +}; + +class LLShowHelp : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string help_topic = userdata.asString(); + LLViewerHelp* vhelp = LLViewerHelp::getInstance(); + vhelp->showTopic(help_topic); + return true; + } +}; + +class LLToggleHelp : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLFloater* help_browser = (LLFloaterReg::findInstance("help_browser")); + if (help_browser && help_browser->isInVisibleChain()) + { + help_browser->closeFloater(); + } + else + { + std::string help_topic = userdata.asString(); + LLViewerHelp* vhelp = LLViewerHelp::getInstance(); + vhelp->showTopic(help_topic); + } + return true; + } +}; + +class LLToggleSpeak : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLVoiceClient::getInstance()->toggleUserPTTState(); + return true; + } +}; +class LLShowSidetrayPanel : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string floater_name = userdata.asString(); + + LLPanel* panel = LLFloaterSidePanelContainer::getPanel(floater_name); + if (panel) + { + if (panel->isInVisibleChain()) + { + LLFloaterReg::getInstance(floater_name)->closeFloater(); + } + else + { + LLFloaterReg::getInstance(floater_name)->openFloater(); + } + } + return true; + } +}; + +class LLSidetrayPanelVisible : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string floater_name = userdata.asString(); + // Toggle the panel + if (LLFloaterReg::getInstance(floater_name)->isInVisibleChain()) + { + return true; + } + else + { + return false; + } + + } +}; + + +bool callback_show_url(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (0 == option) + { + LLWeb::loadURL(notification["payload"]["url"].asString()); + } + return false; +} + +class LLPromptShowURL : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string param = userdata.asString(); + std::string::size_type offset = param.find(","); + if (offset != param.npos) + { + std::string alert = param.substr(0, offset); + std::string url = param.substr(offset+1); + + if (LLWeb::useExternalBrowser(url)) + { + LLSD payload; + payload["url"] = url; + LLNotificationsUtil::add(alert, LLSD(), payload, callback_show_url); + } + else + { + LLWeb::loadURL(url); + } + } + else + { + LL_INFOS() << "PromptShowURL invalid parameters! Expecting \"ALERT,URL\"." << LL_ENDL; + } + return true; + } +}; + +bool callback_show_file(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (0 == option) + { + LLWeb::loadURL(notification["payload"]["url"]); + } + return false; +} + +class LLPromptShowFile : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string param = userdata.asString(); + std::string::size_type offset = param.find(","); + if (offset != param.npos) + { + std::string alert = param.substr(0, offset); + std::string file = param.substr(offset+1); + + LLSD payload; + payload["url"] = file; + LLNotificationsUtil::add(alert, LLSD(), payload, callback_show_file); + } + else + { + LL_INFOS() << "PromptShowFile invalid parameters! Expecting \"ALERT,FILE\"." << LL_ENDL; + } + return true; + } +}; + +class LLShowAgentProfile : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLUUID agent_id; + if (userdata.asString() == "agent") + { + agent_id = gAgent.getID(); + } + else if (userdata.asString() == "hit object") + { + LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + if (objectp) + { + agent_id = objectp->getID(); + } + } + else + { + agent_id = userdata.asUUID(); + } + + LLVOAvatar* avatar = find_avatar_from_object(agent_id); + if (avatar) + { + LLAvatarActions::showProfile(avatar->getID()); + } + return true; + } +}; + +class LLShowAgentProfilePicks : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLAvatarActions::showPicks(gAgent.getID()); + return true; + } +}; + +class LLToggleAgentProfile : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLUUID agent_id; + if (userdata.asString() == "agent") + { + agent_id = gAgent.getID(); + } + else if (userdata.asString() == "hit object") + { + LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + if (objectp) + { + agent_id = objectp->getID(); + } + } + else + { + agent_id = userdata.asUUID(); + } + + LLVOAvatar* avatar = find_avatar_from_object(agent_id); + if (avatar) + { + if (!LLAvatarActions::profileVisible(avatar->getID())) + { + LLAvatarActions::showProfile(avatar->getID()); + } + else + { + LLAvatarActions::hideProfile(avatar->getID()); + } + } + return true; + } +}; + +class LLLandEdit : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + if (gAgentCamera.getFocusOnAvatar() && gSavedSettings.getBOOL("EditCameraMovement") ) + { + // zoom in if we're looking at the avatar + gAgentCamera.setFocusOnAvatar(false, ANIMATE); + gAgentCamera.setFocusGlobal(LLToolPie::getInstance()->getPick()); + + gAgentCamera.cameraOrbitOver( F_PI * 0.25f ); + gViewerWindow->moveCursorToCenter(); + } + else if ( gSavedSettings.getBOOL("EditCameraMovement") ) + { + gAgentCamera.setFocusGlobal(LLToolPie::getInstance()->getPick()); + gViewerWindow->moveCursorToCenter(); + } + + + LLViewerParcelMgr::getInstance()->selectParcelAt( LLToolPie::getInstance()->getPick().mPosGlobal ); + + LLFloaterReg::showInstance("build"); + + // Switch to land edit toolset + LLToolMgr::getInstance()->getCurrentToolset()->selectTool( LLToolSelectLand::getInstance() ); + return true; + } +}; + +class LLMuteParticle : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLUUID id = LLToolPie::getInstance()->getPick().mParticleOwnerID; + + if (id.notNull()) + { + LLAvatarName av_name; + LLAvatarNameCache::get(id, &av_name); + + LLMute mute(id, av_name.getUserName(), LLMute::AGENT); + if (LLMuteList::getInstance()->isMuted(mute.mID)) + { + LLMuteList::getInstance()->remove(mute); + } + else + { + LLMuteList::getInstance()->add(mute); + LLPanelBlockedList::showPanelAndSelect(mute.mID); + } + } + + return true; + } +}; + +class LLWorldEnableBuyLand : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = LLViewerParcelMgr::getInstance()->canAgentBuyParcel( + LLViewerParcelMgr::getInstance()->selectionEmpty() + ? LLViewerParcelMgr::getInstance()->getAgentParcel() + : LLViewerParcelMgr::getInstance()->getParcelSelection()->getParcel(), + false); + return new_value; + } +}; + +bool enable_buy_land(void*) +{ + return LLViewerParcelMgr::getInstance()->canAgentBuyParcel( + LLViewerParcelMgr::getInstance()->getParcelSelection()->getParcel(), false); +} + +void handle_buy_land() +{ + LLViewerParcelMgr* vpm = LLViewerParcelMgr::getInstance(); + if (vpm->selectionEmpty()) + { + vpm->selectParcelAt(gAgent.getPositionGlobal()); + } + vpm->startBuyLand(); +} + +class LLObjectAttachToAvatar : public view_listener_t +{ +public: + LLObjectAttachToAvatar(bool replace) : mReplace(replace) {} + static void setObjectSelection(LLObjectSelectionHandle selection) { sObjectSelection = selection; } + +private: + bool handleEvent(const LLSD& userdata) + { + setObjectSelection(LLSelectMgr::getInstance()->getSelection()); + LLViewerObject* selectedObject = sObjectSelection->getFirstRootObject(); + if (selectedObject) + { + S32 index = userdata.asInteger(); + LLViewerJointAttachment* attachment_point = NULL; + if (index > 0) + attachment_point = get_if_there(gAgentAvatarp->mAttachmentPoints, index, (LLViewerJointAttachment*)NULL); + confirmReplaceAttachment(0, attachment_point); + } + return true; + } + + static void onNearAttachObject(bool success, void *user_data); + void confirmReplaceAttachment(S32 option, LLViewerJointAttachment* attachment_point); + class CallbackData : public LLSelectionCallbackData + { + public: + CallbackData(LLViewerJointAttachment* point, bool replace) : LLSelectionCallbackData(), mAttachmentPoint(point), mReplace(replace) {} + + LLViewerJointAttachment* mAttachmentPoint; + bool mReplace; + }; + +protected: + static LLObjectSelectionHandle sObjectSelection; + bool mReplace; +}; + +LLObjectSelectionHandle LLObjectAttachToAvatar::sObjectSelection; + +// static +void LLObjectAttachToAvatar::onNearAttachObject(bool success, void *user_data) +{ + if (!user_data) return; + CallbackData* cb_data = static_cast(user_data); + + if (success) + { + const LLViewerJointAttachment *attachment = cb_data->mAttachmentPoint; + + U8 attachment_id = 0; + if (attachment) + { + for (LLVOAvatar::attachment_map_t::const_iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); + iter != gAgentAvatarp->mAttachmentPoints.end(); ++iter) + { + if (iter->second == attachment) + { + attachment_id = iter->first; + break; + } + } + } + else + { + // interpret 0 as "default location" + attachment_id = 0; + } + LLSelectMgr::getInstance()->sendAttach(cb_data->getSelection(), attachment_id, cb_data->mReplace); + } + LLObjectAttachToAvatar::setObjectSelection(NULL); + + delete cb_data; +} + +// static +void LLObjectAttachToAvatar::confirmReplaceAttachment(S32 option, LLViewerJointAttachment* attachment_point) +{ + if (option == 0/*YES*/) + { + LLViewerObject* selectedObject = LLSelectMgr::getInstance()->getSelection()->getFirstRootObject(); + if (selectedObject) + { + const F32 MIN_STOP_DISTANCE = 1.f; // meters + const F32 ARM_LENGTH = 0.5f; // meters + const F32 SCALE_FUDGE = 1.5f; + + F32 stop_distance = SCALE_FUDGE * selectedObject->getMaxScale() + ARM_LENGTH; + if (stop_distance < MIN_STOP_DISTANCE) + { + stop_distance = MIN_STOP_DISTANCE; + } + + LLVector3 walkToSpot = selectedObject->getPositionAgent(); + + // make sure we stop in front of the object + LLVector3 delta = walkToSpot - gAgent.getPositionAgent(); + delta.normVec(); + delta = delta * 0.5f; + walkToSpot -= delta; + + // The callback will be called even if avatar fails to get close enough to the object, so we won't get a memory leak. + CallbackData* user_data = new CallbackData(attachment_point, mReplace); + gAgent.startAutoPilotGlobal(gAgent.getPosGlobalFromAgent(walkToSpot), "Attach", NULL, onNearAttachObject, user_data, stop_distance); + gAgentCamera.clearFocusObject(); + } + } +} + +void callback_attachment_drop(const LLSD& notification, const LLSD& response) +{ + // Ensure user confirmed the drop + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) return; + + // Called when the user clicked on an object attached to them + // and selected "Drop". + LLUUID object_id = notification["payload"]["object_id"].asUUID(); + LLViewerObject *object = gObjectList.findObject(object_id); + + if (!object) + { + LL_WARNS() << "handle_drop_attachment() - no object to drop" << LL_ENDL; + return; + } + + LLViewerObject *parent = (LLViewerObject*)object->getParent(); + while (parent) + { + if(parent->isAvatar()) + { + break; + } + object = parent; + parent = (LLViewerObject*)parent->getParent(); + } + + if (!object) + { + LL_WARNS() << "handle_detach() - no object to detach" << LL_ENDL; + return; + } + + if (object->isAvatar()) + { + LL_WARNS() << "Trying to detach avatar from avatar." << LL_ENDL; + return; + } + + // reselect the object + LLSelectMgr::getInstance()->selectObjectAndFamily(object); + + LLSelectMgr::getInstance()->sendDropAttachment(); + + return; +} + +class LLAttachmentDrop : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLSD payload; + LLViewerObject *object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + + if (object) + { + payload["object_id"] = object->getID(); + } + else + { + LL_WARNS() << "Drop object not found" << LL_ENDL; + return true; + } + + LLNotificationsUtil::add("AttachmentDrop", LLSD(), payload, &callback_attachment_drop); + return true; + } +}; + +// called from avatar pie menu +class LLAttachmentDetachFromPoint : public view_listener_t +{ + bool handleEvent(const LLSD& user_data) + { + uuid_vec_t ids_to_remove; + const LLViewerJointAttachment *attachment = get_if_there(gAgentAvatarp->mAttachmentPoints, user_data.asInteger(), (LLViewerJointAttachment*)NULL); + if (attachment->getNumObjects() > 0) + { + for (LLViewerJointAttachment::attachedobjs_vec_t::const_iterator iter = attachment->mAttachedObjects.begin(); + iter != attachment->mAttachedObjects.end(); + iter++) + { + LLViewerObject *attached_object = iter->get(); + ids_to_remove.push_back(attached_object->getAttachmentItemID()); + } + } + if (!ids_to_remove.empty()) + { + LLAppearanceMgr::instance().removeItemsFromAvatar(ids_to_remove); + } + return true; + } +}; + +static bool onEnableAttachmentLabel(LLUICtrl* ctrl, const LLSD& data) +{ + std::string label; + LLMenuItemGL* menu = dynamic_cast(ctrl); + if (menu) + { + const LLViewerJointAttachment *attachment = get_if_there(gAgentAvatarp->mAttachmentPoints, data["index"].asInteger(), (LLViewerJointAttachment*)NULL); + if (attachment) + { + label = data["label"].asString(); + for (LLViewerJointAttachment::attachedobjs_vec_t::const_iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + const LLViewerObject* attached_object = attachment_iter->get(); + if (attached_object) + { + LLViewerInventoryItem* itemp = gInventory.getItem(attached_object->getAttachmentItemID()); + if (itemp) + { + label += std::string(" (") + itemp->getName() + std::string(")"); + break; + } + } + } + } + menu->setLabel(label); + } + return true; +} + +class LLAttachmentDetach : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + // Called when the user clicked on an object attached to them + // and selected "Detach". + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + LLViewerObject *object = selection->getPrimaryObject(); + if (!object) + { + LL_WARNS() << "handle_detach() - no object to detach" << LL_ENDL; + return true; + } + + struct f: public LLSelectedObjectFunctor + { + f() : mAvatarsInSelection(false) {} + virtual bool apply(LLViewerObject* objectp) + { + if (!objectp) + { + return false; + } + + if (objectp->isAvatar()) + { + mAvatarsInSelection = true; + return false; + } + + LLViewerObject* parent = (LLViewerObject*)objectp->getParent(); + while (parent) + { + if(parent->isAvatar()) + { + break; + } + objectp = parent; + parent = (LLViewerObject*)parent->getParent(); + } + + // std::set to avoid dupplicate 'roots' from linksets + mRemoveSet.insert(objectp->getAttachmentItemID()); + + return true; + } + bool mAvatarsInSelection; + uuid_set_t mRemoveSet; + } func; + // Probbly can run applyToRootObjects instead, + // but previous version of this code worked for any selected object + selection->applyToObjects(&func); + + if (func.mAvatarsInSelection) + { + // Not possible under normal circumstances + // Either avatar selection is ON or has to do with animeshes + // Better stop this than mess something + LL_WARNS() << "Trying to detach avatar from avatar." << LL_ENDL; + return true; + } + + if (func.mRemoveSet.empty()) + { + LL_WARNS() << "handle_detach() - no valid attachments in selection to detach" << LL_ENDL; + return true; + } + + uuid_vec_t detach_list(func.mRemoveSet.begin(), func.mRemoveSet.end()); + LLAppearanceMgr::instance().removeItemsFromAvatar(detach_list); + + return true; + } +}; + +//Adding an observer for a Jira 2422 and needs to be a fetch observer +//for Jira 3119 +class LLWornItemFetchedObserver : public LLInventoryFetchItemsObserver +{ +public: + LLWornItemFetchedObserver(const LLUUID& worn_item_id) : + LLInventoryFetchItemsObserver(worn_item_id) + {} + virtual ~LLWornItemFetchedObserver() {} + +protected: + virtual void done() + { + gMenuAttachmentSelf->buildDrawLabels(); + gInventory.removeObserver(this); + delete this; + } +}; + +// You can only drop items on parcels where you can build. +class LLAttachmentEnableDrop : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool can_build = gAgent.isGodlike() || (LLViewerParcelMgr::getInstance()->allowAgentBuild()); + + //Add an inventory observer to only allow dropping the newly attached item + //once it exists in your inventory. Look at Jira 2422. + //-jwolk + + // A bug occurs when you wear/drop an item before it actively is added to your inventory + // if this is the case (you're on a slow sim, etc.) a copy of the object, + // well, a newly created object with the same properties, is placed + // in your inventory. Therefore, we disable the drop option until the + // item is in your inventory + + LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + LLViewerJointAttachment* attachment = NULL; + LLInventoryItem* item = NULL; + + // Do not enable drop if all faces of object are not enabled + if (object && LLSelectMgr::getInstance()->getSelection()->contains(object,SELECT_ALL_TES )) + { + S32 attachmentID = ATTACHMENT_ID_FROM_STATE(object->getAttachmentState()); + attachment = get_if_there(gAgentAvatarp->mAttachmentPoints, attachmentID, (LLViewerJointAttachment*)NULL); + + if (attachment) + { + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + // make sure item is in your inventory (it could be a delayed attach message being sent from the sim) + // so check to see if the item is in the inventory already + item = gInventory.getItem(attachment_iter->get()->getAttachmentItemID()); + if (!item) + { + // Item does not exist, make an observer to enable the pie menu + // when the item finishes fetching worst case scenario + // if a fetch is already out there (being sent from a slow sim) + // we refetch and there are 2 fetches + LLWornItemFetchedObserver* worn_item_fetched = new LLWornItemFetchedObserver((*attachment_iter)->getAttachmentItemID()); + worn_item_fetched->startFetch(); + gInventory.addObserver(worn_item_fetched); + } + } + } + } + + //now check to make sure that the item is actually in the inventory before we enable dropping it + bool new_value = enable_detach() && can_build && item; + + return new_value; + } +}; + +bool enable_detach(const LLSD&) +{ + LLViewerObject* object = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + + // Only enable detach if all faces of object are selected + if (!object || + !object->isAttachment() || + !LLSelectMgr::getInstance()->getSelection()->contains(object,SELECT_ALL_TES )) + { + return false; + } + + // Find the avatar who owns this attachment + LLViewerObject* avatar = object; + while (avatar) + { + // ...if it's you, good to detach + if (avatar->getID() == gAgent.getID()) + { + return true; + } + + avatar = (LLViewerObject*)avatar->getParent(); + } + + return false; +} + +class LLAttachmentEnableDetach : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = enable_detach(); + return new_value; + } +}; + +// Used to tell if the selected object can be attached to your avatar. +bool object_selected_and_point_valid() +{ + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + for (LLObjectSelection::root_iterator iter = selection->root_begin(); + iter != selection->root_end(); iter++) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + LLViewerObject::const_child_list_t& child_list = object->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); iter++) + { + LLViewerObject* child = *iter; + if (child->isAvatar()) + { + return false; + } + } + } + + return (selection->getRootObjectCount() == 1) && + (selection->getFirstRootObject()->getPCode() == LL_PCODE_VOLUME) && + selection->getFirstRootObject()->permYouOwner() && + selection->getFirstRootObject()->flagObjectMove() && + !selection->getFirstRootObject()->flagObjectPermanent() && + !((LLViewerObject*)selection->getFirstRootObject()->getRoot())->isAvatar() && + (selection->getFirstRootObject()->getNVPair("AssetContainer") == NULL); +} + + +bool object_is_wearable() +{ + if (!isAgentAvatarValid()) + { + return false; + } + if (!object_selected_and_point_valid()) + { + return false; + } + if (sitting_on_selection()) + { + return false; + } + if (!gAgentAvatarp->canAttachMoreObjects()) + { + return false; + } + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + for (LLObjectSelection::valid_root_iterator iter = LLSelectMgr::getInstance()->getSelection()->valid_root_begin(); + iter != LLSelectMgr::getInstance()->getSelection()->valid_root_end(); iter++) + { + LLSelectNode* node = *iter; + if (node->mPermissions->getOwner() == gAgent.getID()) + { + return true; + } + } + return false; +} + + +class LLAttachmentPointFilled : public view_listener_t +{ + bool handleEvent(const LLSD& user_data) + { + bool enable = false; + LLVOAvatar::attachment_map_t::iterator found_it = gAgentAvatarp->mAttachmentPoints.find(user_data.asInteger()); + if (found_it != gAgentAvatarp->mAttachmentPoints.end()) + { + enable = found_it->second->getNumObjects() > 0; + } + return enable; + } +}; + +class LLAvatarSendIM : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLVOAvatar* avatar = find_avatar_from_object( LLSelectMgr::getInstance()->getSelection()->getPrimaryObject() ); + if(avatar) + { + LLAvatarActions::startIM(avatar->getID()); + } + return true; + } +}; + +class LLAvatarCall : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLVOAvatar* avatar = find_avatar_from_object( LLSelectMgr::getInstance()->getSelection()->getPrimaryObject() ); + if(avatar) + { + LLAvatarActions::startCall(avatar->getID()); + } + return true; + } +}; + +namespace +{ + struct QueueObjects : public LLSelectedNodeFunctor + { + bool scripted; + bool modifiable; + LLFloaterScriptQueue* mQueue; + QueueObjects(LLFloaterScriptQueue* q) : mQueue(q), scripted(false), modifiable(false) {} + virtual bool apply(LLSelectNode* node) + { + LLViewerObject* obj = node->getObject(); + if (!obj) + { + return true; + } + scripted = obj->flagScripted(); + modifiable = obj->permModify(); + + if( scripted && modifiable ) + { + mQueue->addObject(obj->getID(), node->mName); + return false; + } + else + { + return true; // fail: stop applying + } + } + }; +} + +bool queue_actions(LLFloaterScriptQueue* q, const std::string& msg) +{ + QueueObjects func(q); + LLSelectMgr *mgr = LLSelectMgr::getInstance(); + LLObjectSelectionHandle selectHandle = mgr->getSelection(); + bool fail = selectHandle->applyToNodes(&func); + if(fail) + { + if ( !func.scripted ) + { + std::string noscriptmsg = std::string("Cannot") + msg + "SelectObjectsNoScripts"; + LLNotificationsUtil::add(noscriptmsg); + } + else if ( !func.modifiable ) + { + std::string nomodmsg = std::string("Cannot") + msg + "SelectObjectsNoPermission"; + LLNotificationsUtil::add(nomodmsg); + } + else + { + LL_ERRS() << "Bad logic." << LL_ENDL; + } + q->closeFloater(); + } + else + { + if (!q->start()) + { + LL_WARNS() << "Unexpected script compile failure." << LL_ENDL; + } + } + return !fail; +} + +class LLToolsSelectedScriptAction : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string action = userdata.asString(); + bool mono = false; + std::string msg, name; + std::string title; + if (action == "compile mono") + { + name = "compile_queue"; + mono = true; + msg = "Recompile"; + title = LLTrans::getString("CompileQueueTitle"); + } + if (action == "compile lsl") + { + name = "compile_queue"; + msg = "Recompile"; + title = LLTrans::getString("CompileQueueTitle"); + } + else if (action == "reset") + { + name = "reset_queue"; + msg = "Reset"; + title = LLTrans::getString("ResetQueueTitle"); + } + else if (action == "start") + { + name = "start_queue"; + msg = "SetRunning"; + title = LLTrans::getString("RunQueueTitle"); + } + else if (action == "stop") + { + name = "stop_queue"; + msg = "SetRunningNot"; + title = LLTrans::getString("NotRunQueueTitle"); + } + LLUUID id; id.generate(); + + LLFloaterScriptQueue* queue =LLFloaterReg::getTypedInstance(name, LLSD(id)); + if (queue) + { + queue->setMono(mono); + if (queue_actions(queue, msg)) + { + queue->setTitle(title); + } + } + else + { + LL_WARNS() << "Failed to generate LLFloaterScriptQueue with action: " << action << LL_ENDL; + } + return true; + } +}; + +void handle_selected_texture_info(void*) +{ + for (LLObjectSelection::valid_iterator iter = LLSelectMgr::getInstance()->getSelection()->valid_begin(); + iter != LLSelectMgr::getInstance()->getSelection()->valid_end(); iter++) + { + LLSelectNode* node = *iter; + + std::string msg; + msg.assign("Texture info for: "); + msg.append(node->mName); + + U8 te_count = node->getObject()->getNumTEs(); + // map from texture ID to list of faces using it + typedef std::map< LLUUID, std::vector > map_t; + map_t faces_per_texture; + for (U8 i = 0; i < te_count; i++) + { + if (!node->isTESelected(i)) continue; + + LLViewerTexture* img = node->getObject()->getTEImage(i); + LLUUID image_id = img->getID(); + faces_per_texture[image_id].push_back(i); + } + // Per-texture, dump which faces are using it. + map_t::iterator it; + for (it = faces_per_texture.begin(); it != faces_per_texture.end(); ++it) + { + U8 te = it->second[0]; + LLViewerTexture* img = node->getObject()->getTEImage(te); + S32 height = img->getHeight(); + S32 width = img->getWidth(); + S32 components = img->getComponents(); + msg.append(llformat("\n%dx%d %s on face ", + width, + height, + (components == 4 ? "alpha" : "opaque"))); + for (U8 i = 0; i < it->second.size(); ++i) + { + msg.append( llformat("%d ", (S32)(it->second[i]))); + } + } + LLSD args; + args["MESSAGE"] = msg; + LLNotificationsUtil::add("SystemMessage", args); + } +} + +void handle_selected_material_info() +{ + for (LLObjectSelection::valid_iterator iter = LLSelectMgr::getInstance()->getSelection()->valid_begin(); + iter != LLSelectMgr::getInstance()->getSelection()->valid_end(); iter++) + { + LLSelectNode* node = *iter; + + std::string msg; + msg.assign("Material info for: \n"); + msg.append(node->mName); + + U8 te_count = node->getObject()->getNumTEs(); + // map from material ID to list of faces using it + typedef std::map > map_t; + map_t faces_per_material; + for (U8 i = 0; i < te_count; i++) + { + if (!node->isTESelected(i)) continue; + + const LLMaterialID& material_id = node->getObject()->getTE(i)->getMaterialID(); + faces_per_material[material_id].push_back(i); + } + // Per-material, dump which faces are using it. + map_t::iterator it; + for (it = faces_per_material.begin(); it != faces_per_material.end(); ++it) + { + const LLMaterialID& material_id = it->first; + msg += llformat("%s on face ", material_id.asString().c_str()); + for (U8 i = 0; i < it->second.size(); ++i) + { + msg.append( llformat("%d ", (S32)(it->second[i]))); + } + msg.append("\n"); + } + + LLSD args; + args["MESSAGE"] = msg; + LLNotificationsUtil::add("SystemMessage", args); + } +} + +void handle_test_male(void*) +{ + LLAppearanceMgr::instance().wearOutfitByName("Male Shape & Outfit"); + //gGestureList.requestResetFromServer( true ); +} + +void handle_test_female(void*) +{ + LLAppearanceMgr::instance().wearOutfitByName("Female Shape & Outfit"); + //gGestureList.requestResetFromServer( false ); +} + +void handle_dump_attachments(void*) +{ + if(!isAgentAvatarValid()) return; + + for (LLVOAvatar::attachment_map_t::iterator iter = gAgentAvatarp->mAttachmentPoints.begin(); + iter != gAgentAvatarp->mAttachmentPoints.end(); ) + { + LLVOAvatar::attachment_map_t::iterator curiter = iter++; + LLViewerJointAttachment* attachment = curiter->second; + S32 key = curiter->first; + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + LLViewerObject *attached_object = attachment_iter->get(); + bool visible = (attached_object != NULL && + attached_object->mDrawable.notNull() && + !attached_object->mDrawable->isRenderType(0)); + LLVector3 pos; + if (visible) pos = attached_object->mDrawable->getPosition(); + LL_INFOS() << "ATTACHMENT " << key << ": item_id=" << attached_object->getAttachmentItemID() + << (attached_object ? " present " : " absent ") + << (visible ? "visible " : "invisible ") + << " at " << pos + << " and " << (visible ? attached_object->getPosition() : LLVector3::zero) + << LL_ENDL; + } + } +} + + +// these are used in the gl menus to set control values, generically. +class LLToggleControl : public view_listener_t +{ +protected: + + bool handleEvent(const LLSD& userdata) + { + std::string control_name = userdata.asString(); + bool checked = gSavedSettings.getBOOL( control_name ); + gSavedSettings.setBOOL( control_name, !checked ); + return true; + } +}; + +class LLCheckControl : public view_listener_t +{ + bool handleEvent( const LLSD& userdata) + { + std::string callback_data = userdata.asString(); + bool new_value = gSavedSettings.getBOOL(callback_data); + return new_value; + } +}; + +// not so generic + +class LLAdvancedCheckRenderShadowOption: public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string control_name = userdata.asString(); + S32 current_shadow_level = gSavedSettings.getS32(control_name); + if (current_shadow_level == 0) // is off + { + return false; + } + else // is on + { + return true; + } + } +}; + +class LLAdvancedClickRenderShadowOption: public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string control_name = userdata.asString(); + S32 current_shadow_level = gSavedSettings.getS32(control_name); + if (current_shadow_level == 0) // upgrade to level 2 + { + gSavedSettings.setS32(control_name, 2); + } + else // downgrade to level 0 + { + gSavedSettings.setS32(control_name, 0); + } + return true; + } +}; + +class LLAdvancedClickRenderProfile: public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + gShaderProfileFrame = true; + return true; + } +}; + +F32 gpu_benchmark(); + +class LLAdvancedClickRenderBenchmark: public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + gpu_benchmark(); + return true; + } +}; + +// these are used in the gl menus to set control values that require shader recompilation +class LLToggleShaderControl : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string control_name = userdata.asString(); + bool checked = gSavedSettings.getBOOL( control_name ); + gSavedSettings.setBOOL( control_name, !checked ); + LLPipeline::refreshCachedSettings(); + LLViewerShaderMgr::instance()->setShaders(); + return !checked; + } +}; + +void menu_toggle_attached_lights(void* user_data) +{ + LLPipeline::sRenderAttachedLights = gSavedSettings.getBOOL("RenderAttachedLights"); +} + +void menu_toggle_attached_particles(void* user_data) +{ + LLPipeline::sRenderAttachedParticles = gSavedSettings.getBOOL("RenderAttachedParticles"); +} + +class LLAdvancedHandleAttachedLightParticles: public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string control_name = userdata.asString(); + + // toggle the control + gSavedSettings.setBOOL(control_name, + !gSavedSettings.getBOOL(control_name)); + + // update internal flags + if (control_name == "RenderAttachedLights") + { + menu_toggle_attached_lights(NULL); + } + else if (control_name == "RenderAttachedParticles") + { + menu_toggle_attached_particles(NULL); + } + return true; + } +}; + +class LLSomethingSelected : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = !(LLSelectMgr::getInstance()->getSelection()->isEmpty()); + return new_value; + } +}; + +class LLSomethingSelectedNoHUD : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + bool new_value = !(selection->isEmpty()) && !(selection->getSelectType() == SELECT_TYPE_HUD); + return new_value; + } +}; + +static bool is_editable_selected() +{ + return (LLSelectMgr::getInstance()->getSelection()->getFirstEditableObject() != NULL); +} + +class LLEditableSelected : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + return is_editable_selected(); + } +}; + +class LLEditableSelectedMono : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = false; + LLViewerRegion* region = gAgent.getRegion(); + if(region && gMenuHolder) + { + bool have_cap = (! region->getCapability("UpdateScriptTask").empty()); + new_value = is_editable_selected() && have_cap; + } + return new_value; + } +}; + +bool enable_object_take_copy() +{ + bool all_valid = false; + if (LLSelectMgr::getInstance()) + { + if (LLSelectMgr::getInstance()->getSelection()->getRootObjectCount() > 0) + { + all_valid = true; +#ifndef HACKED_GODLIKE_VIEWER +# ifdef TOGGLE_HACKED_GODLIKE_VIEWER + if (LLGridManager::getInstance()->isInProductionGrid() + || !gAgent.isGodlike()) +# endif + { + struct f : public LLSelectedObjectFunctor + { + virtual bool apply(LLViewerObject* obj) + { + return (!obj->permCopy() || obj->isAttachment()); + } + } func; + const bool firstonly = true; + bool any_invalid = LLSelectMgr::getInstance()->getSelection()->applyToRootObjects(&func, firstonly); + all_valid = !any_invalid; + } +#endif // HACKED_GODLIKE_VIEWER + } + } + + return all_valid; +} + +bool enable_take_copy_objects() +{ + return enable_object_take_copy() && is_multiple_selection(); +} + +class LLHasAsset : public LLInventoryCollectFunctor +{ +public: + LLHasAsset(const LLUUID& id) : mAssetID(id), mHasAsset(false) {} + virtual ~LLHasAsset() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); + bool hasAsset() const { return mHasAsset; } + +protected: + LLUUID mAssetID; + bool mHasAsset; +}; + +bool LLHasAsset::operator()(LLInventoryCategory* cat, + LLInventoryItem* item) +{ + if(item && item->getAssetUUID() == mAssetID) + { + mHasAsset = true; + } + return false; +} + + +bool enable_save_into_task_inventory(void*) +{ + LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(); + if(node && (node->mValid) && (!node->mFromTaskID.isNull())) + { + // *TODO: check to see if the fromtaskid object exists. + LLViewerObject* obj = node->getObject(); + if( obj && !obj->isAttachment() ) + { + return true; + } + } + return false; +} + +class LLToolsEnableSaveToObjectInventory : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = enable_save_into_task_inventory(NULL); + return new_value; + } +}; + +class LLToggleHowTo : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLFloaterReg::toggleInstanceOrBringToFront("guidebook"); + return true; + } +}; + +class LLViewEnableMouselook : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + // You can't go directly from customize avatar to mouselook. + // TODO: write code with appropriate dialogs to handle this transition. + bool new_value = (CAMERA_MODE_CUSTOMIZE_AVATAR != gAgentCamera.getCameraMode() && !gSavedSettings.getBOOL("FreezeTime")); + return new_value; + } +}; + +class LLToolsEnableToolNotPie : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = ( LLToolMgr::getInstance()->getBaseTool() != LLToolPie::getInstance() ); + return new_value; + } +}; + +class LLWorldEnableCreateLandmark : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + return !LLLandmarkActions::landmarkAlreadyExists(); + } +}; + +class LLWorldEnableSetHomeLocation : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = gAgent.isGodlike() || + (gAgent.getRegion() && gAgent.getRegion()->getAllowSetHome()); + return new_value; + } +}; + +class LLWorldEnableTeleportHome : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLViewerRegion* regionp = gAgent.getRegion(); + bool agent_on_prelude = (regionp && regionp->isPrelude()); + bool enable_teleport_home = gAgent.isGodlike() || !agent_on_prelude; + return enable_teleport_home; + } +}; + +bool enable_god_full(void*) +{ + return gAgent.getGodLevel() >= GOD_FULL; +} + +bool enable_god_liaison(void*) +{ + return gAgent.getGodLevel() >= GOD_LIAISON; +} + +bool is_god_customer_service() +{ + return gAgent.getGodLevel() >= GOD_CUSTOMER_SERVICE; +} + +bool enable_god_basic(void*) +{ + return gAgent.getGodLevel() > GOD_NOT; +} + + +void toggle_show_xui_names(void *) +{ + gSavedSettings.setBOOL("DebugShowXUINames", !gSavedSettings.getBOOL("DebugShowXUINames")); +} + +bool check_show_xui_names(void *) +{ + return gSavedSettings.getBOOL("DebugShowXUINames"); +} + +class LLToolsSelectOnlyMyObjects : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool cur_val = gSavedSettings.getBOOL("SelectOwnedOnly"); + + gSavedSettings.setBOOL("SelectOwnedOnly", ! cur_val ); + + return true; + } +}; + +class LLToolsSelectOnlyMovableObjects : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool cur_val = gSavedSettings.getBOOL("SelectMovableOnly"); + + gSavedSettings.setBOOL("SelectMovableOnly", ! cur_val ); + + return true; + } +}; + +class LLToolsSelectInvisibleObjects : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool cur_val = gSavedSettings.getBOOL("SelectInvisibleObjects"); + + gSavedSettings.setBOOL("SelectInvisibleObjects", !cur_val); + + return true; + } +}; + +class LLToolsSelectReflectionProbes: public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool cur_val = gSavedSettings.getBOOL("SelectReflectionProbes"); + + gSavedSettings.setBOOL("SelectReflectionProbes", !cur_val); + + return true; + } +}; + +class LLToolsSelectBySurrounding : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLSelectMgr::sRectSelectInclusive = !LLSelectMgr::sRectSelectInclusive; + + gSavedSettings.setBOOL("RectangleSelectInclusive", LLSelectMgr::sRectSelectInclusive); + return true; + } +}; + +class LLToolsShowHiddenSelection : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + // TomY TODO Merge these + LLSelectMgr::sRenderHiddenSelections = !LLSelectMgr::sRenderHiddenSelections; + + gSavedSettings.setBOOL("RenderHiddenSelections", LLSelectMgr::sRenderHiddenSelections); + return true; + } +}; + +class LLToolsShowSelectionLightRadius : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + // TomY TODO merge these + LLSelectMgr::sRenderLightRadius = !LLSelectMgr::sRenderLightRadius; + + gSavedSettings.setBOOL("RenderLightRadius", LLSelectMgr::sRenderLightRadius); + return true; + } +}; + +class LLToolsEditLinkedParts : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool select_individuals = !gSavedSettings.getBOOL("EditLinkedParts"); + gSavedSettings.setBOOL( "EditLinkedParts", select_individuals ); + if (select_individuals) + { + LLSelectMgr::getInstance()->demoteSelectionToIndividuals(); + } + else + { + LLSelectMgr::getInstance()->promoteSelectionToRoot(); + } + return true; + } +}; + +void reload_vertex_shader(void *) +{ + //THIS WOULD BE AN AWESOME PLACE TO RELOAD SHADERS... just a thought - DaveP +} + +void handle_dump_avatar_local_textures(void*) +{ + gAgentAvatarp->dumpLocalTextures(); +} + +void handle_dump_timers() +{ + LLTrace::BlockTimer::dumpCurTimes(); +} + +void handle_debug_avatar_textures(void*) +{ + LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject(); + if (objectp) + { + LLFloaterReg::showInstance( "avatar_textures", LLSD(objectp->getID()) ); + } +} + +void handle_grab_baked_texture(void* data) +{ + EBakedTextureIndex baked_tex_index = (EBakedTextureIndex)((intptr_t)data); + if (!isAgentAvatarValid()) return; + + const LLUUID& asset_id = gAgentAvatarp->grabBakedTexture(baked_tex_index); + LL_INFOS("texture") << "Adding baked texture " << asset_id << " to inventory." << LL_ENDL; + LLAssetType::EType asset_type = LLAssetType::AT_TEXTURE; + LLInventoryType::EType inv_type = LLInventoryType::IT_TEXTURE; + const LLUUID folder_id = gInventory.findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(asset_type)); + if(folder_id.notNull()) + { + std::string name; + name = "Baked " + LLAvatarAppearance::getDictionary()->getBakedTexture(baked_tex_index)->mNameCapitalized + " Texture"; + + LLUUID item_id; + item_id.generate(); + LLPermissions perm; + perm.init(gAgentID, + gAgentID, + LLUUID::null, + LLUUID::null); + U32 next_owner_perm = PERM_MOVE | PERM_TRANSFER; + perm.initMasks(PERM_ALL, + PERM_ALL, + PERM_NONE, + PERM_NONE, + next_owner_perm); + time_t creation_date_now = time_corrected(); + LLPointer item + = new LLViewerInventoryItem(item_id, + folder_id, + perm, + asset_id, + asset_type, + inv_type, + name, + LLStringUtil::null, + LLSaleInfo::DEFAULT, + LLInventoryItemFlags::II_FLAGS_NONE, + creation_date_now); + + item->updateServer(true); + gInventory.updateItem(item); + gInventory.notifyObservers(); + + // Show the preview panel for textures to let + // user know that the image is now in inventory. + LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(); + if(active_panel) + { + LLFocusableElement* focus_ctrl = gFocusMgr.getKeyboardFocus(); + + active_panel->setSelection(item_id, TAKE_FOCUS_NO); + active_panel->openSelected(); + //LLFloaterInventory::dumpSelectionInformation((void*)view); + // restore keyboard focus + gFocusMgr.setKeyboardFocus(focus_ctrl); + } + } + else + { + LL_WARNS() << "Can't find a folder to put it in" << LL_ENDL; + } +} + +bool enable_grab_baked_texture(void* data) +{ + EBakedTextureIndex index = (EBakedTextureIndex)((intptr_t)data); + if (isAgentAvatarValid()) + { + return gAgentAvatarp->canGrabBakedTexture(index); + } + return false; +} + +// Returns a pointer to the avatar give the UUID of the avatar OR of an attachment the avatar is wearing. +// Returns NULL on failure. +LLVOAvatar* find_avatar_from_object( LLViewerObject* object ) +{ + if (object) + { + if( object->isAttachment() ) + { + do + { + object = (LLViewerObject*) object->getParent(); + } + while( object && !object->isAvatar() ); + } + else if( !object->isAvatar() ) + { + object = NULL; + } + } + + return (LLVOAvatar*) object; +} + + +// Returns a pointer to the avatar give the UUID of the avatar OR of an attachment the avatar is wearing. +// Returns NULL on failure. +LLVOAvatar* find_avatar_from_object( const LLUUID& object_id ) +{ + return find_avatar_from_object( gObjectList.findObject(object_id) ); +} + + +void handle_disconnect_viewer(void *) +{ + LLAppViewer::instance()->forceDisconnect(LLTrans::getString("TestingDisconnect")); +} + +void force_error_breakpoint(void *) +{ + LLAppViewer::instance()->forceErrorBreakpoint(); +} + +void force_error_llerror(void *) +{ + LLAppViewer::instance()->forceErrorLLError(); +} + +void force_error_llerror_msg(void*) +{ + LLAppViewer::instance()->forceErrorLLErrorMsg(); +} + +void force_error_bad_memory_access(void *) +{ + LLAppViewer::instance()->forceErrorBadMemoryAccess(); +} + +void force_error_infinite_loop(void *) +{ + LLAppViewer::instance()->forceErrorInfiniteLoop(); +} + +void force_error_software_exception(void *) +{ + LLAppViewer::instance()->forceErrorSoftwareException(); +} + +void force_error_os_exception(void*) +{ + LLAppViewer::instance()->forceErrorOSSpecificException(); +} + +void force_error_driver_crash(void *) +{ + LLAppViewer::instance()->forceErrorDriverCrash(); +} + +void force_error_coroutine_crash(void *) +{ + LLAppViewer::instance()->forceErrorCoroutineCrash(); +} + +void force_error_thread_crash(void *) +{ + LLAppViewer::instance()->forceErrorThreadCrash(); +} + +class LLToolsUseSelectionForGrid : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLSelectMgr::getInstance()->clearGridObjects(); + struct f : public LLSelectedObjectFunctor + { + virtual bool apply(LLViewerObject* objectp) + { + LLSelectMgr::getInstance()->addGridObject(objectp); + return true; + } + } func; + LLSelectMgr::getInstance()->getSelection()->applyToRootObjects(&func); + LLSelectMgr::getInstance()->setGridMode(GRID_MODE_REF_OBJECT); + LLFloaterTools::setGridMode((S32)GRID_MODE_REF_OBJECT); + return true; + } +}; + +void handle_test_load_url(void*) +{ + LLWeb::loadURL(""); + LLWeb::loadURL("hacker://www.google.com/"); + LLWeb::loadURL("http"); + LLWeb::loadURL("http://www.google.com/"); +} + +// +// LLViewerMenuHolderGL +// +static LLDefaultChildRegistry::Register r("menu_holder"); + +LLViewerMenuHolderGL::LLViewerMenuHolderGL(const LLViewerMenuHolderGL::Params& p) +: LLMenuHolderGL(p) +{} + +bool LLViewerMenuHolderGL::hideMenus() +{ + bool handled = false; + + if (LLMenuHolderGL::hideMenus()) + { + handled = true; + } + + // drop pie menu selection + mParcelSelection = nullptr; + mObjectSelection = nullptr; + + if (gMenuBarView) + { + gMenuBarView->clearHoverItem(); + gMenuBarView->resetMenuTrigger(); + } + + return handled; +} + +void LLViewerMenuHolderGL::setParcelSelection(LLSafeHandle selection) +{ + mParcelSelection = selection; +} + +void LLViewerMenuHolderGL::setObjectSelection(LLSafeHandle selection) +{ + mObjectSelection = selection; +} + + +const LLRect LLViewerMenuHolderGL::getMenuRect() const +{ + return LLRect(0, getRect().getHeight() - MENU_BAR_HEIGHT, getRect().getWidth(), STATUS_BAR_HEIGHT); +} + +void handle_web_browser_test(const LLSD& param) +{ + std::string url = param.asString(); + if (url.empty()) + { + url = "about:blank"; + } + LLWeb::loadURLInternal(url); +} + +bool callback_clear_cache_immediately(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if ( option == 0 ) // YES + { + //clear cache + LLAppViewer::instance()->purgeCacheImmediate(); + } + + return false; +} + +void handle_cache_clear_immediately() +{ + LLNotificationsUtil::add("ConfirmClearCache", LLSD(), LLSD(), callback_clear_cache_immediately); +} + +void handle_web_content_test(const LLSD& param) +{ + std::string url = param.asString(); + LLWeb::loadURLInternal(url, LLStringUtil::null, LLStringUtil::null, true); +} + +void handle_show_url(const LLSD& param) +{ + std::string url = param.asString(); + if (LLWeb::useExternalBrowser(url)) + { + LLWeb::loadURLExternal(url); + } + else + { + LLWeb::loadURLInternal(url); + } + +} + +void handle_report_bug(const LLSD& param) +{ + std::string url = gSavedSettings.getString("ReportBugURL"); + LLWeb::loadURLExternal(url); +} + +void handle_buy_currency_test(void*) +{ + std::string url = + "http://sarahd-sl-13041.webdev.lindenlab.com/app/lindex/index.php?agent_id=[AGENT_ID]&secure_session_id=[SESSION_ID]&lang=[LANGUAGE]"; + + LLStringUtil::format_map_t replace; + replace["[AGENT_ID]"] = gAgent.getID().asString(); + replace["[SESSION_ID]"] = gAgent.getSecureSessionID().asString(); + replace["[LANGUAGE]"] = LLUI::getLanguage(); + LLStringUtil::format(url, replace); + + LL_INFOS() << "buy currency url " << url << LL_ENDL; + + LLFloaterReg::showInstance("buy_currency_html", LLSD(url)); +} + +// SUNSHINE CLEANUP - is only the request update at the end needed now? +void handle_rebake_textures(void*) +{ + if (!isAgentAvatarValid()) return; + + // Slam pending upload count to "unstick" things + bool slam_for_debug = true; + gAgentAvatarp->forceBakeAllTextures(slam_for_debug); + if (gAgent.getRegion() && gAgent.getRegion()->getCentralBakeVersion()) + { + LLAppearanceMgr::instance().requestServerAppearanceUpdate(); + } +} + +void toggle_visibility(void* user_data) +{ + LLView* viewp = (LLView*)user_data; + viewp->setVisible(!viewp->getVisible()); +} + +bool get_visibility(void* user_data) +{ + LLView* viewp = (LLView*)user_data; + return viewp->getVisible(); +} + +class LLViewShowHoverTips : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + gSavedSettings.setBOOL("ShowHoverTips", !gSavedSettings.getBOOL("ShowHoverTips")); + return true; + } +}; + +class LLViewCheckShowHoverTips : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = gSavedSettings.getBOOL("ShowHoverTips"); + return new_value; + } +}; + +class LLViewHighlightTransparent : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLDrawPoolAlpha::sShowDebugAlpha = !LLDrawPoolAlpha::sShowDebugAlpha; + + // invisible objects skip building their render batches unless sShowDebugAlpha is true, so rebuild batches whenever toggling this flag + gPipeline.rebuildDrawInfo(); + return true; + } +}; + +class LLViewCheckHighlightTransparent : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = LLDrawPoolAlpha::sShowDebugAlpha; + return new_value; + } +}; + +class LLViewBeaconWidth : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string width = userdata.asString(); + if(width == "1") + { + gSavedSettings.setS32("DebugBeaconLineWidth", 1); + } + else if(width == "4") + { + gSavedSettings.setS32("DebugBeaconLineWidth", 4); + } + else if(width == "16") + { + gSavedSettings.setS32("DebugBeaconLineWidth", 16); + } + else if(width == "32") + { + gSavedSettings.setS32("DebugBeaconLineWidth", 32); + } + + return true; + } +}; + + +class LLViewToggleBeacon : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string beacon = userdata.asString(); + if (beacon == "scriptsbeacon") + { + LLPipeline::toggleRenderScriptedBeacons(); + gSavedSettings.setBOOL( "scriptsbeacon", LLPipeline::getRenderScriptedBeacons() ); + // toggle the other one off if it's on + if (LLPipeline::getRenderScriptedBeacons() && LLPipeline::getRenderScriptedTouchBeacons()) + { + LLPipeline::toggleRenderScriptedTouchBeacons(); + gSavedSettings.setBOOL( "scripttouchbeacon", LLPipeline::getRenderScriptedTouchBeacons() ); + } + } + else if (beacon == "physicalbeacon") + { + LLPipeline::toggleRenderPhysicalBeacons(); + gSavedSettings.setBOOL( "physicalbeacon", LLPipeline::getRenderPhysicalBeacons() ); + } + else if (beacon == "moapbeacon") + { + LLPipeline::toggleRenderMOAPBeacons(); + gSavedSettings.setBOOL( "moapbeacon", LLPipeline::getRenderMOAPBeacons() ); + } + else if (beacon == "soundsbeacon") + { + LLPipeline::toggleRenderSoundBeacons(); + gSavedSettings.setBOOL( "soundsbeacon", LLPipeline::getRenderSoundBeacons() ); + } + else if (beacon == "particlesbeacon") + { + LLPipeline::toggleRenderParticleBeacons(); + gSavedSettings.setBOOL( "particlesbeacon", LLPipeline::getRenderParticleBeacons() ); + } + else if (beacon == "scripttouchbeacon") + { + LLPipeline::toggleRenderScriptedTouchBeacons(); + gSavedSettings.setBOOL( "scripttouchbeacon", LLPipeline::getRenderScriptedTouchBeacons() ); + // toggle the other one off if it's on + if (LLPipeline::getRenderScriptedBeacons() && LLPipeline::getRenderScriptedTouchBeacons()) + { + LLPipeline::toggleRenderScriptedBeacons(); + gSavedSettings.setBOOL( "scriptsbeacon", LLPipeline::getRenderScriptedBeacons() ); + } + } + else if (beacon == "sunbeacon") + { + gSavedSettings.setBOOL("sunbeacon", !gSavedSettings.getBOOL("sunbeacon")); + } + else if (beacon == "moonbeacon") + { + gSavedSettings.setBOOL("moonbeacon", !gSavedSettings.getBOOL("moonbeacon")); + } + else if (beacon == "renderbeacons") + { + LLPipeline::toggleRenderBeacons(); + gSavedSettings.setBOOL( "renderbeacons", LLPipeline::getRenderBeacons() ); + // toggle the other one on if it's not + if (!LLPipeline::getRenderBeacons() && !LLPipeline::getRenderHighlights()) + { + LLPipeline::toggleRenderHighlights(); + gSavedSettings.setBOOL( "renderhighlights", LLPipeline::getRenderHighlights() ); + } + } + else if (beacon == "renderhighlights") + { + LLPipeline::toggleRenderHighlights(); + gSavedSettings.setBOOL( "renderhighlights", LLPipeline::getRenderHighlights() ); + // toggle the other one on if it's not + if (!LLPipeline::getRenderBeacons() && !LLPipeline::getRenderHighlights()) + { + LLPipeline::toggleRenderBeacons(); + gSavedSettings.setBOOL( "renderbeacons", LLPipeline::getRenderBeacons() ); + } + } + + return true; + } +}; + +class LLViewCheckBeaconEnabled : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string beacon = userdata.asString(); + bool new_value = false; + if (beacon == "scriptsbeacon") + { + new_value = gSavedSettings.getBOOL( "scriptsbeacon"); + LLPipeline::setRenderScriptedBeacons(new_value); + } + else if (beacon == "moapbeacon") + { + new_value = gSavedSettings.getBOOL( "moapbeacon"); + LLPipeline::setRenderMOAPBeacons(new_value); + } + else if (beacon == "physicalbeacon") + { + new_value = gSavedSettings.getBOOL( "physicalbeacon"); + LLPipeline::setRenderPhysicalBeacons(new_value); + } + else if (beacon == "soundsbeacon") + { + new_value = gSavedSettings.getBOOL( "soundsbeacon"); + LLPipeline::setRenderSoundBeacons(new_value); + } + else if (beacon == "particlesbeacon") + { + new_value = gSavedSettings.getBOOL( "particlesbeacon"); + LLPipeline::setRenderParticleBeacons(new_value); + } + else if (beacon == "scripttouchbeacon") + { + new_value = gSavedSettings.getBOOL( "scripttouchbeacon"); + LLPipeline::setRenderScriptedTouchBeacons(new_value); + } + else if (beacon == "renderbeacons") + { + new_value = gSavedSettings.getBOOL( "renderbeacons"); + LLPipeline::setRenderBeacons(new_value); + } + else if (beacon == "renderhighlights") + { + new_value = gSavedSettings.getBOOL( "renderhighlights"); + LLPipeline::setRenderHighlights(new_value); + } + return new_value; + } +}; + +class LLViewToggleRenderType : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string type = userdata.asString(); + if (type == "hideparticles") + { + LLPipeline::toggleRenderType(LLPipeline::RENDER_TYPE_PARTICLES); + } + return true; + } +}; + +class LLViewCheckRenderType : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string type = userdata.asString(); + bool new_value = false; + if (type == "hideparticles") + { + new_value = LLPipeline::toggleRenderTypeControlNegated(LLPipeline::RENDER_TYPE_PARTICLES); + } + return new_value; + } +}; + +class LLViewStatusAway : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + return (gAgent.isInitialized() && gAgent.getAFK()); + } +}; + +class LLViewStatusDoNotDisturb : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + return (gAgent.isInitialized() && gAgent.isDoNotDisturb()); + } +}; + +class LLViewShowHUDAttachments : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLPipeline::sShowHUDAttachments = !LLPipeline::sShowHUDAttachments; + return true; + } +}; + +class LLViewCheckHUDAttachments : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool new_value = LLPipeline::sShowHUDAttachments; + return new_value; + } +}; + +class LLEditEnableTakeOff : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string clothing = userdata.asString(); + LLWearableType::EType type = LLWearableType::getInstance()->typeNameToType(clothing); + if (type >= LLWearableType::WT_SHAPE && type < LLWearableType::WT_COUNT) + return LLAgentWearables::selfHasWearable(type); + return false; + } +}; + +class LLEditTakeOff : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string clothing = userdata.asString(); + if (clothing == "all") + LLAppearanceMgr::instance().removeAllClothesFromAvatar(); + else + { + LLWearableType::EType type = LLWearableType::getInstance()->typeNameToType(clothing); + if (type >= LLWearableType::WT_SHAPE + && type < LLWearableType::WT_COUNT + && (gAgentWearables.getWearableCount(type) > 0)) + { + // MULTI-WEARABLES: assuming user wanted to remove top shirt. + U32 wearable_index = gAgentWearables.getWearableCount(type) - 1; + LLUUID item_id = gAgentWearables.getWearableItemID(type,wearable_index); + LLAppearanceMgr::instance().removeItemFromAvatar(item_id); + } + + } + return true; + } +}; + +class LLToolsSelectTool : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string tool_name = userdata.asString(); + if (tool_name == "focus") + { + LLToolMgr::getInstance()->getCurrentToolset()->selectToolByIndex(1); + } + else if (tool_name == "move") + { + LLToolMgr::getInstance()->getCurrentToolset()->selectToolByIndex(2); + } + else if (tool_name == "edit") + { + LLToolMgr::getInstance()->getCurrentToolset()->selectToolByIndex(3); + } + else if (tool_name == "create") + { + LLToolMgr::getInstance()->getCurrentToolset()->selectToolByIndex(4); + } + else if (tool_name == "land") + { + LLToolMgr::getInstance()->getCurrentToolset()->selectToolByIndex(5); + } + + // Note: if floater is not visible LLViewerWindow::updateLayout() will + // attempt to open it, but it won't bring it to front or de-minimize. + if (gFloaterTools && (gFloaterTools->isMinimized() || !gFloaterTools->isShown() || !gFloaterTools->isFrontmost())) + { + gFloaterTools->setMinimized(false); + gFloaterTools->openFloater(); + gFloaterTools->setVisibleAndFrontmost(true); + } + return true; + } +}; + +/// WINDLIGHT callbacks +class LLWorldEnvSettings : public view_listener_t +{ + void defocusEnvFloaters() + { + //currently there is only one instance of each floater + std::vector env_floaters_names = { "env_edit_extdaycycle", "env_fixed_environmentent_water", "env_fixed_environmentent_sky" }; + for (std::vector::const_iterator it = env_floaters_names.begin(); it != env_floaters_names.end(); ++it) + { + LLFloater* env_floater = LLFloaterReg::findTypedInstance(*it); + if (env_floater) + { + env_floater->setFocus(false); + } + } + } + + bool handleEvent(const LLSD& userdata) + { + std::string event_name = userdata.asString(); + + if (event_name == "sunrise") + { + LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_SUNRISE, LLEnvironment::TRANSITION_INSTANT); + LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); + defocusEnvFloaters(); + } + else if (event_name == "noon") + { + LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_MIDDAY, LLEnvironment::TRANSITION_INSTANT); + LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); + defocusEnvFloaters(); + } + else if (event_name == "legacy noon") + { + LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_LEGACY_MIDDAY, LLEnvironment::TRANSITION_INSTANT); + LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); + defocusEnvFloaters(); + } + else if (event_name == "sunset") + { + LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_SUNSET, LLEnvironment::TRANSITION_INSTANT); + LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); + defocusEnvFloaters(); + } + else if (event_name == "midnight") + { + LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_MIDNIGHT, LLEnvironment::TRANSITION_INSTANT); + LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); + defocusEnvFloaters(); + } + else if (event_name == "region") + { + // reset probe data when reverting back to region sky setting + gPipeline.mReflectionMapManager.reset(); + + LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_LOCAL); + LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); + defocusEnvFloaters(); + } + else if (event_name == "pause_clouds") + { + if (LLEnvironment::instance().isCloudScrollPaused()) + LLEnvironment::instance().resumeCloudScroll(); + else + LLEnvironment::instance().pauseCloudScroll(); + } + else if (event_name == "adjust_tool") + { + LLFloaterReg::showInstance("env_adjust_snapshot"); + } + else if (event_name == "my_environs") + { + LLFloaterReg::showInstance("my_environments"); + } + + return true; + } +}; + +class LLWorldEnableEnvSettings : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool result = false; + std::string event_name = userdata.asString(); + + if (event_name == "pause_clouds") + { + return LLEnvironment::instance().isCloudScrollPaused(); + } + + LLSettingsSky::ptr_t sky = LLEnvironment::instance().getEnvironmentFixedSky(LLEnvironment::ENV_LOCAL); + + if (!sky) + { + return (event_name == "region"); + } + + std::string skyname = (sky) ? sky->getName() : ""; + LLUUID skyid = (sky) ? sky->getAssetId() : LLUUID::null; + + if (event_name == "sunrise") + { + result = (skyid == LLEnvironment::KNOWN_SKY_SUNRISE); + } + else if (event_name == "noon") + { + result = (skyid == LLEnvironment::KNOWN_SKY_MIDDAY); + } + else if (event_name == "legacy noon") + { + result = (skyid == LLEnvironment::KNOWN_SKY_LEGACY_MIDDAY); + } + else if (event_name == "sunset") + { + result = (skyid == LLEnvironment::KNOWN_SKY_SUNSET); + } + else if (event_name == "midnight") + { + result = (skyid == LLEnvironment::KNOWN_SKY_MIDNIGHT); + } + else if (event_name == "region") + { + return false; + } + else + { + LL_WARNS() << "Unknown time-of-day item: " << event_name << LL_ENDL; + } + return result; + } +}; + +class LLWorldEnvPreset : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + std::string item = userdata.asString(); + + if (item == "new_water") + { + LLFloaterReg::showInstance("env_fixed_environmentent_water", "new"); + } + else if (item == "edit_water") + { + LLFloaterReg::showInstance("env_fixed_environmentent_water", "edit"); + } + else if (item == "new_sky") + { + LLFloaterReg::showInstance("env_fixed_environmentent_sky", "new"); + } + else if (item == "edit_sky") + { + LLFloaterReg::showInstance("env_fixed_environmentent_sky", "edit"); + } + else if (item == "new_day_cycle") + { + LLFloaterReg::showInstance("env_edit_extdaycycle", LLSDMap("edit_context", "inventory")); + } + else if (item == "edit_day_cycle") + { + LLFloaterReg::showInstance("env_edit_extdaycycle", LLSDMap("edit_context", "inventory")); + } + else + { + LL_WARNS() << "Unknown item selected" << LL_ENDL; + } + + return true; + } +}; + +class LLWorldEnableEnvPreset : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + + return false; + } +}; + + +/// Post-Process callbacks +class LLWorldPostProcess : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLFloaterReg::showInstance("env_post_process"); + return true; + } +}; + +class LLWorldCheckBanLines : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + S32 callback_data = userdata.asInteger(); + return gSavedSettings.getS32("ShowBanLines") == callback_data; + } +}; + +class LLWorldShowBanLines : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + S32 callback_data = userdata.asInteger(); + gSavedSettings.setS32("ShowBanLines", callback_data); + return true; + } +}; + +void handle_flush_name_caches() +{ + if (gCacheName) gCacheName->clear(); +} + +class LLUploadCostCalculator : public view_listener_t +{ + std::string mCostStr; + + bool handleEvent(const LLSD& userdata) + { + std::vector fields; + std::string str = userdata.asString(); + boost::split(fields, str, boost::is_any_of(",")); + if (fields.size()<1) + { + return false; + } + std::string menu_name = fields[0]; + std::string asset_type_str = "texture"; + if (fields.size()>1) + { + asset_type_str = fields[1]; + } + LL_DEBUGS("Benefits") << "userdata " << userdata << " menu_name " << menu_name << " asset_type_str " << asset_type_str << LL_ENDL; + calculateCost(asset_type_str); + gMenuHolder->childSetLabelArg(menu_name, "[COST]", mCostStr); + + return true; + } + + void calculateCost(const std::string& asset_type_str); + +public: + LLUploadCostCalculator() + { + } +}; + +class LLUpdateMembershipLabel : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + const std::string label_str = LLAgentBenefitsMgr::isCurrent("Base") ? LLTrans::getString("MembershipUpgradeText") : LLTrans::getString("MembershipPremiumText"); + gMenuHolder->childSetLabelArg("Membership", "[Membership]", label_str); + + return true; + } +}; + +void handle_voice_morphing_subscribe() +{ + LLWeb::loadURL(LLTrans::getString("voice_morphing_url")); +} + +void handle_premium_voice_morphing_subscribe() +{ + LLWeb::loadURL(LLTrans::getString("premium_voice_morphing_url")); +} + +class LLToggleUIHints : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool ui_hints_enabled = gSavedSettings.getBOOL("EnableUIHints"); + // toggle + ui_hints_enabled = !ui_hints_enabled; + gSavedSettings.setBOOL("EnableUIHints", ui_hints_enabled); + return true; + } +}; + +void LLUploadCostCalculator::calculateCost(const std::string& asset_type_str) +{ + S32 upload_cost = -1; + + if (asset_type_str == "texture") + { + upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost(); + } + else if (asset_type_str == "animation") + { + upload_cost = LLAgentBenefitsMgr::current().getAnimationUploadCost(); + } + else if (asset_type_str == "sound") + { + upload_cost = LLAgentBenefitsMgr::current().getSoundUploadCost(); + } + if (upload_cost < 0) + { + LL_WARNS() << "Unable to find upload cost for asset_type_str " << asset_type_str << LL_ENDL; + } + mCostStr = std::to_string(upload_cost); +} + +void show_navbar_context_menu(LLView* ctrl, S32 x, S32 y) +{ + static LLMenuGL* show_navbar_context_menu = LLUICtrlFactory::getInstance()->createFromFile("menu_hide_navbar.xml", + gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + if(gMenuHolder->hasVisibleMenu()) + { + gMenuHolder->hideMenus(); + } + show_navbar_context_menu->buildDrawLabels(); + show_navbar_context_menu->updateParent(LLMenuGL::sMenuContainer); + LLMenuGL::showPopup(ctrl, show_navbar_context_menu, x, y); +} + +void show_topinfobar_context_menu(LLView* ctrl, S32 x, S32 y) +{ + static LLMenuGL* show_topbarinfo_context_menu = LLUICtrlFactory::getInstance()->createFromFile("menu_topinfobar.xml", + gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + + LLMenuItemGL* landmark_item = show_topbarinfo_context_menu->getChild("Landmark"); + if (!LLLandmarkActions::landmarkAlreadyExists()) + { + landmark_item->setLabel(LLTrans::getString("AddLandmarkNavBarMenu")); + } + else + { + landmark_item->setLabel(LLTrans::getString("EditLandmarkNavBarMenu")); + } + + if(gMenuHolder->hasVisibleMenu()) + { + gMenuHolder->hideMenus(); + } + + show_topbarinfo_context_menu->buildDrawLabels(); + show_topbarinfo_context_menu->updateParent(LLMenuGL::sMenuContainer); + LLMenuGL::showPopup(ctrl, show_topbarinfo_context_menu, x, y); +} + +void initialize_edit_menu() +{ + view_listener_t::addMenu(new LLEditUndo(), "Edit.Undo"); + view_listener_t::addMenu(new LLEditRedo(), "Edit.Redo"); + view_listener_t::addMenu(new LLEditCut(), "Edit.Cut"); + view_listener_t::addMenu(new LLEditCopy(), "Edit.Copy"); + view_listener_t::addMenu(new LLEditPaste(), "Edit.Paste"); + view_listener_t::addMenu(new LLEditDelete(), "Edit.Delete"); + view_listener_t::addMenu(new LLEditSelectAll(), "Edit.SelectAll"); + view_listener_t::addMenu(new LLEditDeselect(), "Edit.Deselect"); + view_listener_t::addMenu(new LLEditTakeOff(), "Edit.TakeOff"); + view_listener_t::addMenu(new LLEditEnableUndo(), "Edit.EnableUndo"); + view_listener_t::addMenu(new LLEditEnableRedo(), "Edit.EnableRedo"); + view_listener_t::addMenu(new LLEditEnableCut(), "Edit.EnableCut"); + view_listener_t::addMenu(new LLEditEnableCopy(), "Edit.EnableCopy"); + view_listener_t::addMenu(new LLEditEnablePaste(), "Edit.EnablePaste"); + view_listener_t::addMenu(new LLEditEnableDelete(), "Edit.EnableDelete"); + view_listener_t::addMenu(new LLEditEnableSelectAll(), "Edit.EnableSelectAll"); + view_listener_t::addMenu(new LLEditEnableDeselect(), "Edit.EnableDeselect"); + +} + +void initialize_spellcheck_menu() +{ + LLUICtrl::CommitCallbackRegistry::Registrar& commit = LLUICtrl::CommitCallbackRegistry::currentRegistrar(); + LLUICtrl::EnableCallbackRegistry::Registrar& enable = LLUICtrl::EnableCallbackRegistry::currentRegistrar(); + + commit.add("SpellCheck.ReplaceWithSuggestion", boost::bind(&handle_spellcheck_replace_with_suggestion, _1, _2)); + enable.add("SpellCheck.VisibleSuggestion", boost::bind(&visible_spellcheck_suggestion, _1, _2)); + commit.add("SpellCheck.AddToDictionary", boost::bind(&handle_spellcheck_add_to_dictionary, _1)); + enable.add("SpellCheck.EnableAddToDictionary", boost::bind(&enable_spellcheck_add_to_dictionary, _1)); + commit.add("SpellCheck.AddToIgnore", boost::bind(&handle_spellcheck_add_to_ignore, _1)); + enable.add("SpellCheck.EnableAddToIgnore", boost::bind(&enable_spellcheck_add_to_ignore, _1)); +} + +void initialize_menus() +{ + // A parameterized event handler used as ctrl-8/9/0 zoom controls below. + class LLZoomer : public view_listener_t + { + public: + // The "mult" parameter says whether "val" is a multiplier or used to set the value. + LLZoomer(F32 val, bool mult=true) : mVal(val), mMult(mult) {} + bool handleEvent(const LLSD& userdata) + { + F32 new_fov_rad = mMult ? LLViewerCamera::getInstance()->getDefaultFOV() * mVal : mVal; + LLViewerCamera::getInstance()->setDefaultFOV(new_fov_rad); + gSavedSettings.setF32("CameraAngle", LLViewerCamera::getInstance()->getView()); // setView may have clamped it. + return true; + } + private: + F32 mVal; + bool mMult; + }; + + LLUICtrl::EnableCallbackRegistry::Registrar& enable = LLUICtrl::EnableCallbackRegistry::currentRegistrar(); + LLUICtrl::CommitCallbackRegistry::Registrar& commit = LLUICtrl::CommitCallbackRegistry::currentRegistrar(); + + // Generic enable and visible + // Don't prepend MenuName.Foo because these can be used in any menu. + enable.add("IsGodCustomerService", boost::bind(&is_god_customer_service)); + + enable.add("displayViewerEventRecorderMenuItems",boost::bind(&LLViewerEventRecorder::displayViewerEventRecorderMenuItems,&LLViewerEventRecorder::instance())); + + view_listener_t::addEnable(new LLUploadCostCalculator(), "Upload.CalculateCosts"); + + view_listener_t::addEnable(new LLUpdateMembershipLabel(), "Membership.UpdateLabel"); + + enable.add("Conversation.IsConversationLoggingAllowed", boost::bind(&LLFloaterIMContainer::isConversationLoggingAllowed)); + + // Agent + commit.add("Agent.toggleFlying", boost::bind(&LLAgent::toggleFlying)); + enable.add("Agent.enableFlyLand", boost::bind(&enable_fly_land)); + commit.add("Agent.PressMicrophone", boost::bind(&LLAgent::pressMicrophone, _2)); + commit.add("Agent.ReleaseMicrophone", boost::bind(&LLAgent::releaseMicrophone, _2)); + commit.add("Agent.ToggleMicrophone", boost::bind(&LLAgent::toggleMicrophone, _2)); + enable.add("Agent.IsMicrophoneOn", boost::bind(&LLAgent::isMicrophoneOn, _2)); + enable.add("Agent.IsActionAllowed", boost::bind(&LLAgent::isActionAllowed, _2)); + + // File menu + init_menu_file(); + + view_listener_t::addMenu(new LLEditEnableTakeOff(), "Edit.EnableTakeOff"); + view_listener_t::addMenu(new LLEditEnableCustomizeAvatar(), "Edit.EnableCustomizeAvatar"); + view_listener_t::addMenu(new LLEnableEditShape(), "Edit.EnableEditShape"); + view_listener_t::addMenu(new LLEnableHoverHeight(), "Edit.EnableHoverHeight"); + view_listener_t::addMenu(new LLEnableEditPhysics(), "Edit.EnableEditPhysics"); + commit.add("CustomizeAvatar", boost::bind(&handle_customize_avatar)); + commit.add("NowWearing", boost::bind(&handle_now_wearing)); + commit.add("EditOutfit", boost::bind(&handle_edit_outfit)); + commit.add("EditShape", boost::bind(&handle_edit_shape)); + commit.add("HoverHeight", boost::bind(&handle_hover_height)); + commit.add("EditPhysics", boost::bind(&handle_edit_physics)); + + // View menu + view_listener_t::addMenu(new LLViewMouselook(), "View.Mouselook"); + view_listener_t::addMenu(new LLViewJoystickFlycam(), "View.JoystickFlycam"); + view_listener_t::addMenu(new LLViewResetView(), "View.ResetView"); + view_listener_t::addMenu(new LLViewLookAtLastChatter(), "View.LookAtLastChatter"); + view_listener_t::addMenu(new LLViewShowHoverTips(), "View.ShowHoverTips"); + view_listener_t::addMenu(new LLViewHighlightTransparent(), "View.HighlightTransparent"); + view_listener_t::addMenu(new LLViewToggleRenderType(), "View.ToggleRenderType"); + view_listener_t::addMenu(new LLViewShowHUDAttachments(), "View.ShowHUDAttachments"); + view_listener_t::addMenu(new LLZoomer(1.2f), "View.ZoomOut"); + view_listener_t::addMenu(new LLZoomer(1/1.2f), "View.ZoomIn"); + view_listener_t::addMenu(new LLZoomer(DEFAULT_FIELD_OF_VIEW, false), "View.ZoomDefault"); + view_listener_t::addMenu(new LLViewDefaultUISize(), "View.DefaultUISize"); + view_listener_t::addMenu(new LLViewToggleUI(), "View.ToggleUI"); + + view_listener_t::addMenu(new LLViewEnableMouselook(), "View.EnableMouselook"); + view_listener_t::addMenu(new LLViewEnableJoystickFlycam(), "View.EnableJoystickFlycam"); + view_listener_t::addMenu(new LLViewEnableLastChatter(), "View.EnableLastChatter"); + + view_listener_t::addMenu(new LLViewCheckJoystickFlycam(), "View.CheckJoystickFlycam"); + view_listener_t::addMenu(new LLViewCheckShowHoverTips(), "View.CheckShowHoverTips"); + view_listener_t::addMenu(new LLViewCheckHighlightTransparent(), "View.CheckHighlightTransparent"); + view_listener_t::addMenu(new LLViewCheckRenderType(), "View.CheckRenderType"); + view_listener_t::addMenu(new LLViewStatusAway(), "View.Status.CheckAway"); + view_listener_t::addMenu(new LLViewStatusDoNotDisturb(), "View.Status.CheckDoNotDisturb"); + view_listener_t::addMenu(new LLViewCheckHUDAttachments(), "View.CheckHUDAttachments"); + + //Communicate Nearby chat + view_listener_t::addMenu(new LLCommunicateNearbyChat(), "Communicate.NearbyChat"); + + // Communicate > Voice morphing > Subscribe... + commit.add("Communicate.VoiceMorphing.Subscribe", boost::bind(&handle_voice_morphing_subscribe)); + // Communicate > Voice morphing > Premium perk... + commit.add("Communicate.VoiceMorphing.PremiumPerk", boost::bind(&handle_premium_voice_morphing_subscribe)); + LLVivoxVoiceClient * voice_clientp = LLVivoxVoiceClient::getInstance(); + enable.add("Communicate.VoiceMorphing.NoVoiceMorphing.Check" + , boost::bind(&LLVivoxVoiceClient::onCheckVoiceEffect, voice_clientp, "NoVoiceMorphing")); + commit.add("Communicate.VoiceMorphing.NoVoiceMorphing.Click" + , boost::bind(&LLVivoxVoiceClient::onClickVoiceEffect, voice_clientp, "NoVoiceMorphing")); + + // World menu + view_listener_t::addMenu(new LLWorldAlwaysRun(), "World.AlwaysRun"); + view_listener_t::addMenu(new LLWorldCreateLandmark(), "World.CreateLandmark"); + view_listener_t::addMenu(new LLWorldPlaceProfile(), "World.PlaceProfile"); + view_listener_t::addMenu(new LLWorldSetHomeLocation(), "World.SetHomeLocation"); + view_listener_t::addMenu(new LLWorldTeleportHome(), "World.TeleportHome"); + view_listener_t::addMenu(new LLWorldSetAway(), "World.SetAway"); + view_listener_t::addMenu(new LLWorldSetDoNotDisturb(), "World.SetDoNotDisturb"); + view_listener_t::addMenu(new LLWorldLindenHome(), "World.LindenHome"); + + view_listener_t::addMenu(new LLWorldEnableCreateLandmark(), "World.EnableCreateLandmark"); + view_listener_t::addMenu(new LLWorldEnableSetHomeLocation(), "World.EnableSetHomeLocation"); + view_listener_t::addMenu(new LLWorldEnableTeleportHome(), "World.EnableTeleportHome"); + view_listener_t::addMenu(new LLWorldEnableBuyLand(), "World.EnableBuyLand"); + + view_listener_t::addMenu(new LLWorldCheckAlwaysRun(), "World.CheckAlwaysRun"); + + view_listener_t::addMenu(new LLWorldEnvSettings(), "World.EnvSettings"); + view_listener_t::addMenu(new LLWorldEnableEnvSettings(), "World.EnableEnvSettings"); + view_listener_t::addMenu(new LLWorldEnvPreset(), "World.EnvPreset"); + view_listener_t::addMenu(new LLWorldEnableEnvPreset(), "World.EnableEnvPreset"); + view_listener_t::addMenu(new LLWorldPostProcess(), "World.PostProcess"); + view_listener_t::addMenu(new LLWorldCheckBanLines() , "World.CheckBanLines"); + view_listener_t::addMenu(new LLWorldShowBanLines() , "World.ShowBanLines"); + + // Tools menu + view_listener_t::addMenu(new LLToolsSelectTool(), "Tools.SelectTool"); + view_listener_t::addMenu(new LLToolsSelectOnlyMyObjects(), "Tools.SelectOnlyMyObjects"); + view_listener_t::addMenu(new LLToolsSelectOnlyMovableObjects(), "Tools.SelectOnlyMovableObjects"); + view_listener_t::addMenu(new LLToolsSelectInvisibleObjects(), "Tools.SelectInvisibleObjects"); + view_listener_t::addMenu(new LLToolsSelectReflectionProbes(), "Tools.SelectReflectionProbes"); + view_listener_t::addMenu(new LLToolsSelectBySurrounding(), "Tools.SelectBySurrounding"); + view_listener_t::addMenu(new LLToolsShowHiddenSelection(), "Tools.ShowHiddenSelection"); + view_listener_t::addMenu(new LLToolsShowSelectionLightRadius(), "Tools.ShowSelectionLightRadius"); + view_listener_t::addMenu(new LLToolsEditLinkedParts(), "Tools.EditLinkedParts"); + view_listener_t::addMenu(new LLToolsSnapObjectXY(), "Tools.SnapObjectXY"); + view_listener_t::addMenu(new LLToolsUseSelectionForGrid(), "Tools.UseSelectionForGrid"); + view_listener_t::addMenu(new LLToolsSelectNextPartFace(), "Tools.SelectNextPart"); + commit.add("Tools.Link", boost::bind(&handle_link_objects)); + commit.add("Tools.Unlink", boost::bind(&LLSelectMgr::unlinkObjects, LLSelectMgr::getInstance())); + view_listener_t::addMenu(new LLToolsStopAllAnimations(), "Tools.StopAllAnimations"); + view_listener_t::addMenu(new LLToolsReleaseKeys(), "Tools.ReleaseKeys"); + view_listener_t::addMenu(new LLToolsEnableReleaseKeys(), "Tools.EnableReleaseKeys"); + commit.add("Tools.LookAtSelection", boost::bind(&handle_look_at_selection, _2)); + commit.add("Tools.BuyOrTake", boost::bind(&handle_buy_or_take)); + commit.add("Tools.TakeCopy", boost::bind(&handle_take_copy)); + view_listener_t::addMenu(new LLToolsSaveToObjectInventory(), "Tools.SaveToObjectInventory"); + view_listener_t::addMenu(new LLToolsSelectedScriptAction(), "Tools.SelectedScriptAction"); + + view_listener_t::addMenu(new LLToolsEnableToolNotPie(), "Tools.EnableToolNotPie"); + view_listener_t::addMenu(new LLToolsEnableSelectNextPart(), "Tools.EnableSelectNextPart"); + enable.add("Tools.EnableLink", boost::bind(&LLSelectMgr::enableLinkObjects, LLSelectMgr::getInstance())); + enable.add("Tools.EnableUnlink", boost::bind(&LLSelectMgr::enableUnlinkObjects, LLSelectMgr::getInstance())); + view_listener_t::addMenu(new LLToolsEnableBuyOrTake(), "Tools.EnableBuyOrTake"); + enable.add("Tools.EnableTakeCopy", boost::bind(&enable_object_take_copy)); + enable.add("Tools.EnableCopySeparate", boost::bind(&enable_take_copy_objects)); + enable.add("Tools.VisibleBuyObject", boost::bind(&tools_visible_buy_object)); + enable.add("Tools.VisibleTakeObject", boost::bind(&tools_visible_take_object)); + view_listener_t::addMenu(new LLToolsEnableSaveToObjectInventory(), "Tools.EnableSaveToObjectInventory"); + + view_listener_t::addMenu(new LLToolsEnablePathfinding(), "Tools.EnablePathfinding"); + view_listener_t::addMenu(new LLToolsEnablePathfindingView(), "Tools.EnablePathfindingView"); + view_listener_t::addMenu(new LLToolsDoPathfindingRebakeRegion(), "Tools.DoPathfindingRebakeRegion"); + view_listener_t::addMenu(new LLToolsEnablePathfindingRebakeRegion(), "Tools.EnablePathfindingRebakeRegion"); + + // Help menu + // most items use the ShowFloater method + view_listener_t::addMenu(new LLToggleHowTo(), "Help.ToggleHowTo"); + + // Advanced menu + view_listener_t::addMenu(new LLAdvancedToggleConsole(), "Advanced.ToggleConsole"); + view_listener_t::addMenu(new LLAdvancedCheckConsole(), "Advanced.CheckConsole"); + view_listener_t::addMenu(new LLAdvancedDumpInfoToConsole(), "Advanced.DumpInfoToConsole"); + + // Advanced > HUD Info + view_listener_t::addMenu(new LLAdvancedToggleHUDInfo(), "Advanced.ToggleHUDInfo"); + view_listener_t::addMenu(new LLAdvancedCheckHUDInfo(), "Advanced.CheckHUDInfo"); + + // Advanced Other Settings + view_listener_t::addMenu(new LLAdvancedClearGroupCache(), "Advanced.ClearGroupCache"); + + // Advanced > Render > Types + view_listener_t::addMenu(new LLAdvancedToggleRenderType(), "Advanced.ToggleRenderType"); + view_listener_t::addMenu(new LLAdvancedCheckRenderType(), "Advanced.CheckRenderType"); + + //// Advanced > Render > Features + view_listener_t::addMenu(new LLAdvancedToggleFeature(), "Advanced.ToggleFeature"); + view_listener_t::addMenu(new LLAdvancedCheckFeature(), "Advanced.CheckFeature"); + + view_listener_t::addMenu(new LLAdvancedCheckDisplayTextureDensity(), "Advanced.CheckDisplayTextureDensity"); + view_listener_t::addMenu(new LLAdvancedSetDisplayTextureDensity(), "Advanced.SetDisplayTextureDensity"); + + // Advanced > Render > Info Displays + view_listener_t::addMenu(new LLAdvancedToggleInfoDisplay(), "Advanced.ToggleInfoDisplay"); + view_listener_t::addMenu(new LLAdvancedCheckInfoDisplay(), "Advanced.CheckInfoDisplay"); + view_listener_t::addMenu(new LLAdvancedSelectedTextureInfo(), "Advanced.SelectedTextureInfo"); + commit.add("Advanced.SelectedMaterialInfo", boost::bind(&handle_selected_material_info)); + view_listener_t::addMenu(new LLAdvancedToggleWireframe(), "Advanced.ToggleWireframe"); + view_listener_t::addMenu(new LLAdvancedCheckWireframe(), "Advanced.CheckWireframe"); + // Develop > Render + view_listener_t::addMenu(new LLAdvancedToggleRandomizeFramerate(), "Advanced.ToggleRandomizeFramerate"); + view_listener_t::addMenu(new LLAdvancedCheckRandomizeFramerate(), "Advanced.CheckRandomizeFramerate"); + view_listener_t::addMenu(new LLAdvancedTogglePeriodicSlowFrame(), "Advanced.TogglePeriodicSlowFrame"); + view_listener_t::addMenu(new LLAdvancedCheckPeriodicSlowFrame(), "Advanced.CheckPeriodicSlowFrame"); + view_listener_t::addMenu(new LLAdvancedHandleAttachedLightParticles(), "Advanced.HandleAttachedLightParticles"); + view_listener_t::addMenu(new LLAdvancedCheckRenderShadowOption(), "Advanced.CheckRenderShadowOption"); + view_listener_t::addMenu(new LLAdvancedClickRenderShadowOption(), "Advanced.ClickRenderShadowOption"); + view_listener_t::addMenu(new LLAdvancedClickRenderProfile(), "Advanced.ClickRenderProfile"); + view_listener_t::addMenu(new LLAdvancedClickRenderBenchmark(), "Advanced.ClickRenderBenchmark"); + view_listener_t::addMenu(new LLAdvancedPurgeShaderCache(), "Advanced.ClearShaderCache"); + + #ifdef TOGGLE_HACKED_GODLIKE_VIEWER + view_listener_t::addMenu(new LLAdvancedHandleToggleHackedGodmode(), "Advanced.HandleToggleHackedGodmode"); + view_listener_t::addMenu(new LLAdvancedCheckToggleHackedGodmode(), "Advanced.CheckToggleHackedGodmode"); + view_listener_t::addMenu(new LLAdvancedEnableToggleHackedGodmode(), "Advanced.EnableToggleHackedGodmode"); + #endif + + // Advanced > World + view_listener_t::addMenu(new LLAdvancedDumpScriptedCamera(), "Advanced.DumpScriptedCamera"); + view_listener_t::addMenu(new LLAdvancedDumpRegionObjectCache(), "Advanced.DumpRegionObjectCache"); + view_listener_t::addMenu(new LLAdvancedToggleStatsRecorder(), "Advanced.ToggleStatsRecorder"); + view_listener_t::addMenu(new LLAdvancedCheckStatsRecorder(), "Advanced.CheckStatsRecorder"); + view_listener_t::addMenu(new LLAdvancedToggleInterestList360Mode(), "Advanced.ToggleInterestList360Mode"); + view_listener_t::addMenu(new LLAdvancedCheckInterestList360Mode(), "Advanced.CheckInterestList360Mode"); + view_listener_t::addMenu(new LLAdvancedResetInterestLists(), "Advanced.ResetInterestLists"); + + // Advanced > UI + commit.add("Advanced.WebBrowserTest", boost::bind(&handle_web_browser_test, _2)); // sigh! this one opens the MEDIA browser + commit.add("Advanced.WebContentTest", boost::bind(&handle_web_content_test, _2)); // this one opens the Web Content floater + commit.add("Advanced.ShowURL", boost::bind(&handle_show_url, _2)); + commit.add("Advanced.ReportBug", boost::bind(&handle_report_bug, _2)); + view_listener_t::addMenu(new LLAdvancedBuyCurrencyTest(), "Advanced.BuyCurrencyTest"); + view_listener_t::addMenu(new LLAdvancedDumpSelectMgr(), "Advanced.DumpSelectMgr"); + view_listener_t::addMenu(new LLAdvancedDumpInventory(), "Advanced.DumpInventory"); + commit.add("Advanced.DumpTimers", boost::bind(&handle_dump_timers) ); + commit.add("Advanced.DumpFocusHolder", boost::bind(&handle_dump_focus) ); + view_listener_t::addMenu(new LLAdvancedPrintSelectedObjectInfo(), "Advanced.PrintSelectedObjectInfo"); + view_listener_t::addMenu(new LLAdvancedPrintAgentInfo(), "Advanced.PrintAgentInfo"); + view_listener_t::addMenu(new LLAdvancedToggleDebugClicks(), "Advanced.ToggleDebugClicks"); + view_listener_t::addMenu(new LLAdvancedCheckDebugClicks(), "Advanced.CheckDebugClicks"); + view_listener_t::addMenu(new LLAdvancedCheckDebugViews(), "Advanced.CheckDebugViews"); + view_listener_t::addMenu(new LLAdvancedToggleDebugViews(), "Advanced.ToggleDebugViews"); + view_listener_t::addMenu(new LLAdvancedCheckDebugUnicode(), "Advanced.CheckDebugUnicode"); + view_listener_t::addMenu(new LLAdvancedToggleDebugUnicode(), "Advanced.ToggleDebugUnicode"); + view_listener_t::addMenu(new LLAdvancedCheckDebugCamera(), "Advanced.CheckDebugCamera"); + view_listener_t::addMenu(new LLAdvancedToggleDebugCamera(), "Advanced.ToggleDebugCamera"); + view_listener_t::addMenu(new LLAdvancedToggleXUINameTooltips(), "Advanced.ToggleXUINameTooltips"); + view_listener_t::addMenu(new LLAdvancedCheckXUINameTooltips(), "Advanced.CheckXUINameTooltips"); + view_listener_t::addMenu(new LLAdvancedToggleDebugMouseEvents(), "Advanced.ToggleDebugMouseEvents"); + view_listener_t::addMenu(new LLAdvancedCheckDebugMouseEvents(), "Advanced.CheckDebugMouseEvents"); + view_listener_t::addMenu(new LLAdvancedToggleDebugKeys(), "Advanced.ToggleDebugKeys"); + view_listener_t::addMenu(new LLAdvancedCheckDebugKeys(), "Advanced.CheckDebugKeys"); + view_listener_t::addMenu(new LLAdvancedToggleDebugWindowProc(), "Advanced.ToggleDebugWindowProc"); + view_listener_t::addMenu(new LLAdvancedCheckDebugWindowProc(), "Advanced.CheckDebugWindowProc"); + + // Advanced > XUI + commit.add("Advanced.ReloadColorSettings", boost::bind(&LLUIColorTable::loadFromSettings, LLUIColorTable::getInstance())); + view_listener_t::addMenu(new LLAdvancedToggleXUINames(), "Advanced.ToggleXUINames"); + view_listener_t::addMenu(new LLAdvancedCheckXUINames(), "Advanced.CheckXUINames"); + view_listener_t::addMenu(new LLAdvancedSendTestIms(), "Advanced.SendTestIMs"); + commit.add("Advanced.FlushNameCaches", boost::bind(&handle_flush_name_caches)); + + // Advanced > Character > Grab Baked Texture + view_listener_t::addMenu(new LLAdvancedGrabBakedTexture(), "Advanced.GrabBakedTexture"); + view_listener_t::addMenu(new LLAdvancedEnableGrabBakedTexture(), "Advanced.EnableGrabBakedTexture"); + + // Advanced > Character > Character Tests + view_listener_t::addMenu(new LLAdvancedAppearanceToXML(), "Advanced.AppearanceToXML"); + view_listener_t::addMenu(new LLAdvancedEnableAppearanceToXML(), "Advanced.EnableAppearanceToXML"); + view_listener_t::addMenu(new LLAdvancedToggleCharacterGeometry(), "Advanced.ToggleCharacterGeometry"); + + view_listener_t::addMenu(new LLAdvancedTestMale(), "Advanced.TestMale"); + view_listener_t::addMenu(new LLAdvancedTestFemale(), "Advanced.TestFemale"); + + // Advanced > Character > Animation Speed + view_listener_t::addMenu(new LLAdvancedAnimTenFaster(), "Advanced.AnimTenFaster"); + view_listener_t::addMenu(new LLAdvancedAnimTenSlower(), "Advanced.AnimTenSlower"); + view_listener_t::addMenu(new LLAdvancedAnimResetAll(), "Advanced.AnimResetAll"); + + // Advanced > Character (toplevel) + view_listener_t::addMenu(new LLAdvancedForceParamsToDefault(), "Advanced.ForceParamsToDefault"); + view_listener_t::addMenu(new LLAdvancedReloadVertexShader(), "Advanced.ReloadVertexShader"); + view_listener_t::addMenu(new LLAdvancedToggleAnimationInfo(), "Advanced.ToggleAnimationInfo"); + view_listener_t::addMenu(new LLAdvancedCheckAnimationInfo(), "Advanced.CheckAnimationInfo"); + view_listener_t::addMenu(new LLAdvancedToggleShowLookAt(), "Advanced.ToggleShowLookAt"); + view_listener_t::addMenu(new LLAdvancedCheckShowLookAt(), "Advanced.CheckShowLookAt"); + view_listener_t::addMenu(new LLAdvancedToggleShowPointAt(), "Advanced.ToggleShowPointAt"); + view_listener_t::addMenu(new LLAdvancedCheckShowPointAt(), "Advanced.CheckShowPointAt"); + view_listener_t::addMenu(new LLAdvancedToggleDebugJointUpdates(), "Advanced.ToggleDebugJointUpdates"); + view_listener_t::addMenu(new LLAdvancedCheckDebugJointUpdates(), "Advanced.CheckDebugJointUpdates"); + view_listener_t::addMenu(new LLAdvancedToggleDisableLOD(), "Advanced.ToggleDisableLOD"); + view_listener_t::addMenu(new LLAdvancedCheckDisableLOD(), "Advanced.CheckDisableLOD"); + view_listener_t::addMenu(new LLAdvancedToggleDebugCharacterVis(), "Advanced.ToggleDebugCharacterVis"); + view_listener_t::addMenu(new LLAdvancedCheckDebugCharacterVis(), "Advanced.CheckDebugCharacterVis"); + view_listener_t::addMenu(new LLAdvancedDumpAttachments(), "Advanced.DumpAttachments"); + view_listener_t::addMenu(new LLAdvancedRebakeTextures(), "Advanced.RebakeTextures"); + view_listener_t::addMenu(new LLAdvancedDebugAvatarTextures(), "Advanced.DebugAvatarTextures"); + view_listener_t::addMenu(new LLAdvancedDumpAvatarLocalTextures(), "Advanced.DumpAvatarLocalTextures"); + // Advanced > Network + view_listener_t::addMenu(new LLAdvancedEnableMessageLog(), "Advanced.EnableMessageLog"); + view_listener_t::addMenu(new LLAdvancedDisableMessageLog(), "Advanced.DisableMessageLog"); + view_listener_t::addMenu(new LLAdvancedDropPacket(), "Advanced.DropPacket"); + + // Advanced > Cache + view_listener_t::addMenu(new LLAdvancedPurgeDiskCache(), "Advanced.PurgeDiskCache"); + + // Advanced > Recorder + view_listener_t::addMenu(new LLAdvancedAgentPilot(), "Advanced.AgentPilot"); + view_listener_t::addMenu(new LLAdvancedToggleAgentPilotLoop(), "Advanced.ToggleAgentPilotLoop"); + view_listener_t::addMenu(new LLAdvancedCheckAgentPilotLoop(), "Advanced.CheckAgentPilotLoop"); + view_listener_t::addMenu(new LLAdvancedViewerEventRecorder(), "Advanced.EventRecorder"); + + // Advanced > Debugging + view_listener_t::addMenu(new LLAdvancedForceErrorBreakpoint(), "Advanced.ForceErrorBreakpoint"); + view_listener_t::addMenu(new LLAdvancedForceErrorLlerror(), "Advanced.ForceErrorLlerror"); + view_listener_t::addMenu(new LLAdvancedForceErrorLlerrorMsg(), "Advanced.ForceErrorLlerrorMsg"); + view_listener_t::addMenu(new LLAdvancedForceErrorBadMemoryAccess(), "Advanced.ForceErrorBadMemoryAccess"); + view_listener_t::addMenu(new LLAdvancedForceErrorBadMemoryAccessCoro(), "Advanced.ForceErrorBadMemoryAccessCoro"); + view_listener_t::addMenu(new LLAdvancedForceErrorInfiniteLoop(), "Advanced.ForceErrorInfiniteLoop"); + view_listener_t::addMenu(new LLAdvancedForceErrorSoftwareException(), "Advanced.ForceErrorSoftwareException"); + view_listener_t::addMenu(new LLAdvancedForceOSException(), "Advanced.ForceErrorOSException"); + view_listener_t::addMenu(new LLAdvancedForceErrorSoftwareExceptionCoro(), "Advanced.ForceErrorSoftwareExceptionCoro"); + view_listener_t::addMenu(new LLAdvancedForceErrorDriverCrash(), "Advanced.ForceErrorDriverCrash"); + view_listener_t::addMenu(new LLAdvancedForceErrorCoroutineCrash(), "Advanced.ForceErrorCoroutineCrash"); + view_listener_t::addMenu(new LLAdvancedForceErrorThreadCrash(), "Advanced.ForceErrorThreadCrash"); + view_listener_t::addMenu(new LLAdvancedForceErrorDisconnectViewer(), "Advanced.ForceErrorDisconnectViewer"); + + // Advanced (toplevel) + view_listener_t::addMenu(new LLAdvancedToggleShowObjectUpdates(), "Advanced.ToggleShowObjectUpdates"); + view_listener_t::addMenu(new LLAdvancedCheckShowObjectUpdates(), "Advanced.CheckShowObjectUpdates"); + view_listener_t::addMenu(new LLAdvancedCompressImage(), "Advanced.CompressImage"); + view_listener_t::addMenu(new LLAdvancedCompressFileTest(), "Advanced.CompressFileTest"); + view_listener_t::addMenu(new LLAdvancedShowDebugSettings(), "Advanced.ShowDebugSettings"); + view_listener_t::addMenu(new LLAdvancedEnableViewAdminOptions(), "Advanced.EnableViewAdminOptions"); + view_listener_t::addMenu(new LLAdvancedToggleViewAdminOptions(), "Advanced.ToggleViewAdminOptions"); + view_listener_t::addMenu(new LLAdvancedCheckViewAdminOptions(), "Advanced.CheckViewAdminOptions"); + view_listener_t::addMenu(new LLAdvancedToggleVisualLeakDetector(), "Advanced.ToggleVisualLeakDetector"); + + view_listener_t::addMenu(new LLAdvancedRequestAdminStatus(), "Advanced.RequestAdminStatus"); + view_listener_t::addMenu(new LLAdvancedLeaveAdminStatus(), "Advanced.LeaveAdminStatus"); + + // Develop >Set logging level + view_listener_t::addMenu(new LLDevelopCheckLoggingLevel(), "Develop.CheckLoggingLevel"); + view_listener_t::addMenu(new LLDevelopSetLoggingLevel(), "Develop.SetLoggingLevel"); + + //Develop (clear cache immediately) + commit.add("Develop.ClearCache", boost::bind(&handle_cache_clear_immediately) ); + + // Develop (Fonts debugging) + commit.add("Develop.Fonts.Dump", boost::bind(&LLFontGL::dumpFonts)); + commit.add("Develop.Fonts.DumpTextures", boost::bind(&LLFontGL::dumpFontTextures)); + + // Admin >Object + view_listener_t::addMenu(new LLAdminForceTakeCopy(), "Admin.ForceTakeCopy"); + view_listener_t::addMenu(new LLAdminHandleObjectOwnerSelf(), "Admin.HandleObjectOwnerSelf"); + view_listener_t::addMenu(new LLAdminHandleObjectOwnerPermissive(), "Admin.HandleObjectOwnerPermissive"); + view_listener_t::addMenu(new LLAdminHandleForceDelete(), "Admin.HandleForceDelete"); + view_listener_t::addMenu(new LLAdminHandleObjectLock(), "Admin.HandleObjectLock"); + view_listener_t::addMenu(new LLAdminHandleObjectAssetIDs(), "Admin.HandleObjectAssetIDs"); + + // Admin >Parcel + view_listener_t::addMenu(new LLAdminHandleForceParcelOwnerToMe(), "Admin.HandleForceParcelOwnerToMe"); + view_listener_t::addMenu(new LLAdminHandleForceParcelToContent(), "Admin.HandleForceParcelToContent"); + view_listener_t::addMenu(new LLAdminHandleClaimPublicLand(), "Admin.HandleClaimPublicLand"); + + // Admin >Region + view_listener_t::addMenu(new LLAdminHandleRegionDumpTempAssetData(), "Admin.HandleRegionDumpTempAssetData"); + // Admin top level + view_listener_t::addMenu(new LLAdminOnSaveState(), "Admin.OnSaveState"); + + // Self context menu + view_listener_t::addMenu(new LLSelfToggleSitStand(), "Self.ToggleSitStand"); + enable.add("Self.EnableSitStand", boost::bind(&enable_sit_stand)); + view_listener_t::addMenu(new LLSelfRemoveAllAttachments(), "Self.RemoveAllAttachments"); + + view_listener_t::addMenu(new LLSelfEnableRemoveAllAttachments(), "Self.EnableRemoveAllAttachments"); + + // we don't use boost::bind directly to delay side tray construction + view_listener_t::addMenu( new LLTogglePanelPeopleTab(), "SideTray.PanelPeopleTab"); + view_listener_t::addMenu( new LLCheckPanelPeopleTab(), "SideTray.CheckPanelPeopleTab"); + + // Avatar pie menu + view_listener_t::addMenu(new LLAvatarCheckImpostorMode(), "Avatar.CheckImpostorMode"); + view_listener_t::addMenu(new LLAvatarSetImpostorMode(), "Avatar.SetImpostorMode"); + view_listener_t::addMenu(new LLObjectMute(), "Avatar.Mute"); + view_listener_t::addMenu(new LLAvatarAddFriend(), "Avatar.AddFriend"); + view_listener_t::addMenu(new LLAvatarAddContact(), "Avatar.AddContact"); + commit.add("Avatar.Freeze", boost::bind(&handle_avatar_freeze, LLSD())); + view_listener_t::addMenu(new LLAvatarDebug(), "Avatar.Debug"); + view_listener_t::addMenu(new LLAvatarVisibleDebug(), "Avatar.VisibleDebug"); + view_listener_t::addMenu(new LLAvatarInviteToGroup(), "Avatar.InviteToGroup"); + commit.add("Avatar.Eject", boost::bind(&handle_avatar_eject, LLSD())); + commit.add("Avatar.ShowInspector", boost::bind(&handle_avatar_show_inspector)); + view_listener_t::addMenu(new LLAvatarSendIM(), "Avatar.SendIM"); + view_listener_t::addMenu(new LLAvatarCall(), "Avatar.Call"); + enable.add("Avatar.EnableCall", boost::bind(&LLAvatarActions::canCall)); + view_listener_t::addMenu(new LLAvatarReportAbuse(), "Avatar.ReportAbuse"); + view_listener_t::addMenu(new LLAvatarToggleMyProfile(), "Avatar.ToggleMyProfile"); + view_listener_t::addMenu(new LLAvatarTogglePicks(), "Avatar.TogglePicks"); + view_listener_t::addMenu(new LLAvatarToggleSearch(), "Avatar.ToggleSearch"); + view_listener_t::addMenu(new LLAvatarResetSkeleton(), "Avatar.ResetSkeleton"); + view_listener_t::addMenu(new LLAvatarEnableResetSkeleton(), "Avatar.EnableResetSkeleton"); + view_listener_t::addMenu(new LLAvatarResetSkeletonAndAnimations(), "Avatar.ResetSkeletonAndAnimations"); + view_listener_t::addMenu(new LLAvatarResetSelfSkeletonAndAnimations(), "Avatar.ResetSelfSkeletonAndAnimations"); + enable.add("Avatar.IsMyProfileOpen", boost::bind(&my_profile_visible)); + enable.add("Avatar.IsPicksTabOpen", boost::bind(&picks_tab_visible)); + + commit.add("Avatar.OpenMarketplace", boost::bind(&LLWeb::loadURLExternal, gSavedSettings.getString("MarketplaceURL"))); + + view_listener_t::addMenu(new LLAvatarEnableAddFriend(), "Avatar.EnableAddFriend"); + enable.add("Avatar.EnableFreezeEject", boost::bind(&enable_freeze_eject, _2)); + + // Object pie menu + view_listener_t::addMenu(new LLObjectBuild(), "Object.Build"); + commit.add("Object.Touch", boost::bind(&handle_object_touch)); + commit.add("Object.ShowOriginal", boost::bind(&handle_object_show_original)); + commit.add("Object.SitOrStand", boost::bind(&handle_object_sit_or_stand)); + commit.add("Object.Delete", boost::bind(&handle_object_delete)); + view_listener_t::addMenu(new LLObjectAttachToAvatar(true), "Object.AttachToAvatar"); + view_listener_t::addMenu(new LLObjectAttachToAvatar(false), "Object.AttachAddToAvatar"); + view_listener_t::addMenu(new LLObjectReturn(), "Object.Return"); + commit.add("Object.Duplicate", boost::bind(&LLSelectMgr::duplicate, LLSelectMgr::getInstance())); + view_listener_t::addMenu(new LLObjectReportAbuse(), "Object.ReportAbuse"); + view_listener_t::addMenu(new LLObjectMute(), "Object.Mute"); + + enable.add("Object.VisibleTake", boost::bind(&visible_take_object)); + enable.add("Object.VisibleTakeMultiple", boost::bind(&is_multiple_selection)); + enable.add("Object.VisibleTakeSingle", boost::bind(&is_single_selection)); + enable.add("Object.EnableTakeMultiple", boost::bind(&enable_take_objects)); + enable.add("Object.VisibleBuy", boost::bind(&visible_buy_object)); + + commit.add("Object.Buy", boost::bind(&handle_buy)); + commit.add("Object.Edit", boost::bind(&handle_object_edit)); + commit.add("Object.Edit", boost::bind(&handle_object_edit)); + commit.add("Object.EditGLTFMaterial", boost::bind(&handle_object_edit_gltf_material)); + commit.add("Object.Inspect", boost::bind(&handle_object_inspect)); + commit.add("Object.Open", boost::bind(&handle_object_open)); + commit.add("Object.Take", boost::bind(&handle_take, false)); + commit.add("Object.TakeSeparate", boost::bind(&handle_take, true)); + commit.add("Object.TakeSeparateCopy", boost::bind(&handle_take_separate_copy)); + commit.add("Object.ShowInspector", boost::bind(&handle_object_show_inspector)); + enable.add("Object.EnableInspect", boost::bind(&enable_object_inspect)); + enable.add("Object.EnableEditGLTFMaterial", boost::bind(&enable_object_edit_gltf_material)); + enable.add("Object.EnableOpen", boost::bind(&enable_object_open)); + enable.add("Object.EnableTouch", boost::bind(&enable_object_touch, _1)); + enable.add("Object.EnableDelete", boost::bind(&enable_object_delete)); + enable.add("Object.EnableWear", boost::bind(&object_is_wearable)); + + enable.add("Object.EnableStandUp", boost::bind(&enable_object_stand_up)); + enable.add("Object.EnableSit", boost::bind(&enable_object_sit, _1)); + + view_listener_t::addMenu(new LLObjectEnableReturn(), "Object.EnableReturn"); + enable.add("Object.EnableDuplicate", boost::bind(&LLSelectMgr::canDuplicate, LLSelectMgr::getInstance())); + view_listener_t::addMenu(new LLObjectEnableReportAbuse(), "Object.EnableReportAbuse"); + + enable.add("Avatar.EnableMute", boost::bind(&enable_object_mute)); + enable.add("Object.EnableMute", boost::bind(&enable_object_mute)); + enable.add("Object.EnableUnmute", boost::bind(&enable_object_unmute)); + enable.add("Object.EnableBuy", boost::bind(&enable_buy_object)); + commit.add("Object.ZoomIn", boost::bind(&handle_look_at_selection, "zoom")); + + // Attachment pie menu + enable.add("Attachment.Label", boost::bind(&onEnableAttachmentLabel, _1, _2)); + view_listener_t::addMenu(new LLAttachmentDrop(), "Attachment.Drop"); + view_listener_t::addMenu(new LLAttachmentDetachFromPoint(), "Attachment.DetachFromPoint"); + view_listener_t::addMenu(new LLAttachmentDetach(), "Attachment.Detach"); + view_listener_t::addMenu(new LLAttachmentPointFilled(), "Attachment.PointFilled"); + view_listener_t::addMenu(new LLAttachmentEnableDrop(), "Attachment.EnableDrop"); + view_listener_t::addMenu(new LLAttachmentEnableDetach(), "Attachment.EnableDetach"); + + // Land pie menu + view_listener_t::addMenu(new LLLandBuild(), "Land.Build"); + view_listener_t::addMenu(new LLLandSit(), "Land.Sit"); + view_listener_t::addMenu(new LLLandCanSit(), "Land.CanSit"); + view_listener_t::addMenu(new LLLandBuyPass(), "Land.BuyPass"); + view_listener_t::addMenu(new LLLandEdit(), "Land.Edit"); + + // Particle muting + view_listener_t::addMenu(new LLMuteParticle(), "Particle.Mute"); + + view_listener_t::addMenu(new LLLandEnableBuyPass(), "Land.EnableBuyPass"); + commit.add("Land.Buy", boost::bind(&handle_buy_land)); + + // Generic actions + commit.add("ReportAbuse", boost::bind(&handle_report_abuse)); + commit.add("BuyCurrency", boost::bind(&handle_buy_currency)); + view_listener_t::addMenu(new LLShowHelp(), "ShowHelp"); + view_listener_t::addMenu(new LLToggleHelp(), "ToggleHelp"); + view_listener_t::addMenu(new LLToggleSpeak(), "ToggleSpeak"); + view_listener_t::addMenu(new LLPromptShowURL(), "PromptShowURL"); + view_listener_t::addMenu(new LLShowAgentProfile(), "ShowAgentProfile"); + view_listener_t::addMenu(new LLShowAgentProfilePicks(), "ShowAgentProfilePicks"); + view_listener_t::addMenu(new LLToggleAgentProfile(), "ToggleAgentProfile"); + view_listener_t::addMenu(new LLToggleControl(), "ToggleControl"); + view_listener_t::addMenu(new LLToggleShaderControl(), "ToggleShaderControl"); + view_listener_t::addMenu(new LLCheckControl(), "CheckControl"); + view_listener_t::addMenu(new LLGoToObject(), "GoToObject"); + commit.add("PayObject", boost::bind(&handle_give_money_dialog)); + + commit.add("Inventory.NewWindow", boost::bind(&LLPanelMainInventory::newWindow)); + + enable.add("EnablePayObject", boost::bind(&enable_pay_object)); + enable.add("EnablePayAvatar", boost::bind(&enable_pay_avatar)); + enable.add("EnableEdit", boost::bind(&enable_object_edit)); + enable.add("EnableMuteParticle", boost::bind(&enable_mute_particle)); + enable.add("VisibleBuild", boost::bind(&enable_object_build)); + commit.add("Pathfinding.Linksets.Select", boost::bind(&LLFloaterPathfindingLinksets::openLinksetsWithSelectedObjects)); + enable.add("EnableSelectInPathfindingLinksets", boost::bind(&enable_object_select_in_pathfinding_linksets)); + enable.add("VisibleSelectInPathfindingLinksets", boost::bind(&visible_object_select_in_pathfinding_linksets)); + commit.add("Pathfinding.Characters.Select", boost::bind(&LLFloaterPathfindingCharacters::openCharactersWithSelectedObjects)); + enable.add("EnableSelectInPathfindingCharacters", boost::bind(&enable_object_select_in_pathfinding_characters)); + enable.add("Advanced.EnableErrorOSException", boost::bind(&enable_os_exception)); + + view_listener_t::addMenu(new LLFloaterVisible(), "FloaterVisible"); + view_listener_t::addMenu(new LLShowSidetrayPanel(), "ShowSidetrayPanel"); + view_listener_t::addMenu(new LLSidetrayPanelVisible(), "SidetrayPanelVisible"); + view_listener_t::addMenu(new LLSomethingSelected(), "SomethingSelected"); + view_listener_t::addMenu(new LLSomethingSelectedNoHUD(), "SomethingSelectedNoHUD"); + view_listener_t::addMenu(new LLEditableSelected(), "EditableSelected"); + view_listener_t::addMenu(new LLEditableSelectedMono(), "EditableSelectedMono"); + view_listener_t::addMenu(new LLToggleUIHints(), "ToggleUIHints"); +} diff --git a/indra/newview/llviewermenu.h b/indra/newview/llviewermenu.h index fce6a29eb2..8c5e0705d0 100644 --- a/indra/newview/llviewermenu.h +++ b/indra/newview/llviewermenu.h @@ -1,210 +1,210 @@ -/** - * @file llviewermenu.h - * @brief Builds menus out of objects - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVIEWERMENU_H -#define LL_LLVIEWERMENU_H - -#include "../llui/llmenugl.h" -#include "llsafehandle.h" - -class LLMessageSystem; -class LLSD; -class LLUICtrl; -class LLView; -class LLParcelSelection; -class LLObjectSelection; -class LLSelectNode; - -void initialize_edit_menu(); -void initialize_spellcheck_menu(); -void init_menus(); -void cleanup_menus(); - -void show_debug_menus(); // checks for if menus should be shown first. -void toggle_debug_menus(void*); -void show_context_menu( S32 x, S32 y, MASK mask ); -void show_build_mode_context_menu(S32 x, S32 y, MASK mask); -void show_navbar_context_menu(LLView* ctrl, S32 x, S32 y); -void show_topinfobar_context_menu(LLView* ctrl, S32 x, S32 y); -void handle_reset_view(); -void handle_cut(void*); -void handle_copy(void*); -void handle_paste(void*); -void handle_delete(void*); -void handle_redo(void*); -void handle_undo(void*); -void handle_select_all(void*); -void handle_deselect(void*); -void handle_delete_object(); -void handle_duplicate(void*); -void handle_duplicate_in_place(void*); -bool enable_not_have_card(void *userdata); -void process_grant_godlike_powers(LLMessageSystem* msg, void**); - -bool enable_cut(void*); -bool enable_copy(void*); -bool enable_paste(void*); -bool enable_select_all(void*); -bool enable_deselect(void*); -bool enable_undo(void*); -bool enable_redo(void*); - -bool is_agent_mappable(const LLUUID& agent_id); - -void confirm_replace_attachment(S32 option, void* user_data); -void handle_detach_from_avatar(const LLSD& user_data); -void attach_label(std::string& label, const LLSD&); -void detach_label(std::string& label, const LLSD&); -void handle_detach(void*); -bool enable_god_full(void* user_data); -bool enable_god_liaison(void* user_data); -bool enable_god_basic(void* user_data); -void check_merchant_status(bool force = false); - -void exchange_callingcard(const LLUUID& dest_id); - -void handle_gestures(void*); -void handle_sit_down(void*); -void handle_object_build(void*); -void handle_object_touch(); -bool enable_object_edit_gltf_material(); -bool enable_object_save_gltf_material(); -bool enable_object_open(); -void handle_object_open(); - -bool visible_take_object(); -bool tools_visible_take_object(); -bool enable_object_take_copy(); -bool enable_object_return(); -bool enable_object_delete(); - -// Buy either contents or object itself -void handle_buy(); -void handle_take(bool take_separate = false); -void handle_take_copy(); -void handle_look_at_selection(const LLSD& param); -void handle_zoom_to_object(LLUUID object_id); -void handle_object_return(); -void handle_object_delete(); -void handle_object_edit(); -void handle_object_edit_gltf_material(); -void handle_object_save_gltf_material(); - -void handle_attachment_edit(const LLUUID& inv_item_id); -void handle_attachment_touch(const LLUUID& inv_item_id); -bool enable_attachment_touch(const LLUUID& inv_item_id); - -void handle_buy_land(); - -// Takes avatar UUID, or if no UUID passed, uses last selected object -void handle_avatar_freeze(const LLSD& avatar_id); - -// Takes avatar UUID, or if no UUID passed, uses last selected object -void handle_avatar_eject(const LLSD& avatar_id); - -bool enable_freeze_eject(const LLSD& avatar_id); - -// Can anyone take a free copy of the object? -// *TODO: Move to separate file -bool anyone_copy_selection(LLSelectNode* nodep); - -// Is this selected object for sale? -// *TODO: Move to separate file -bool for_sale_selection(LLSelectNode* nodep); - -void handle_toggle_flycam(); - -void handle_object_sit_or_stand(); -void handle_object_sit(const LLUUID& object_id); -void handle_give_money_dialog(); -bool enable_pay_object(); -bool enable_buy_object(); -bool handle_go_to(); - -// Export to XML or Collada -void handle_export_selected( void * ); - -// Convert strings to internal types -U32 render_type_from_string(std::string render_type); -U32 feature_from_string(std::string feature); -U64 info_display_from_string(std::string info_display); - -class LLViewerMenuHolderGL : public LLMenuHolderGL -{ -public: - struct Params : public LLInitParam::Block - {}; - - LLViewerMenuHolderGL(const Params& p); - - virtual bool hideMenus(); - - void setParcelSelection(LLSafeHandle selection); - void setObjectSelection(LLSafeHandle selection); - - virtual const LLRect getMenuRect() const; - -protected: - LLSafeHandle mParcelSelection; - LLSafeHandle mObjectSelection; -}; - -extern LLMenuBarGL* gMenuBarView; -//extern LLView* gMenuBarHolder; -extern LLMenuGL* gEditMenu; -extern LLMenuGL* gPopupMenuView; -extern LLViewerMenuHolderGL* gMenuHolder; -extern LLMenuBarGL* gLoginMenuBarView; - -// Context menus in 3D scene -extern LLContextMenu *gMenuAvatarSelf; -extern LLContextMenu *gMenuAvatarOther; -extern LLContextMenu *gMenuObject; -extern LLContextMenu *gMenuAttachmentSelf; -extern LLContextMenu *gMenuAttachmentOther; -extern LLContextMenu *gMenuLand; -extern LLContextMenu *gMenuMuteParticle; - -// Needed to build menus when attachment site list available -extern LLMenuGL* gAttachSubMenu; -extern LLMenuGL* gDetachSubMenu; -extern LLMenuGL* gTakeOffClothes; -extern LLMenuGL* gDetachAvatarMenu; -extern LLMenuGL* gDetachHUDAvatarMenu; -extern LLContextMenu* gAttachScreenPieMenu; -extern LLContextMenu* gDetachScreenPieMenu; -extern LLContextMenu* gDetachHUDAttSelfMenu; -extern LLContextMenu* gAttachPieMenu; -extern LLContextMenu* gDetachPieMenu; -extern LLContextMenu* gDetachAttSelfMenu; -extern LLContextMenu* gAttachBodyPartPieMenus[9]; -extern LLContextMenu* gDetachBodyPartPieMenus[9]; - -extern LLMenuItemCallGL* gMutePieMenu; -extern LLMenuItemCallGL* gMuteObjectPieMenu; -extern LLMenuItemCallGL* gBuyPassPieMenu; - -#endif +/** + * @file llviewermenu.h + * @brief Builds menus out of objects + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVIEWERMENU_H +#define LL_LLVIEWERMENU_H + +#include "../llui/llmenugl.h" +#include "llsafehandle.h" + +class LLMessageSystem; +class LLSD; +class LLUICtrl; +class LLView; +class LLParcelSelection; +class LLObjectSelection; +class LLSelectNode; + +void initialize_edit_menu(); +void initialize_spellcheck_menu(); +void init_menus(); +void cleanup_menus(); + +void show_debug_menus(); // checks for if menus should be shown first. +void toggle_debug_menus(void*); +void show_context_menu( S32 x, S32 y, MASK mask ); +void show_build_mode_context_menu(S32 x, S32 y, MASK mask); +void show_navbar_context_menu(LLView* ctrl, S32 x, S32 y); +void show_topinfobar_context_menu(LLView* ctrl, S32 x, S32 y); +void handle_reset_view(); +void handle_cut(void*); +void handle_copy(void*); +void handle_paste(void*); +void handle_delete(void*); +void handle_redo(void*); +void handle_undo(void*); +void handle_select_all(void*); +void handle_deselect(void*); +void handle_delete_object(); +void handle_duplicate(void*); +void handle_duplicate_in_place(void*); +bool enable_not_have_card(void *userdata); +void process_grant_godlike_powers(LLMessageSystem* msg, void**); + +bool enable_cut(void*); +bool enable_copy(void*); +bool enable_paste(void*); +bool enable_select_all(void*); +bool enable_deselect(void*); +bool enable_undo(void*); +bool enable_redo(void*); + +bool is_agent_mappable(const LLUUID& agent_id); + +void confirm_replace_attachment(S32 option, void* user_data); +void handle_detach_from_avatar(const LLSD& user_data); +void attach_label(std::string& label, const LLSD&); +void detach_label(std::string& label, const LLSD&); +void handle_detach(void*); +bool enable_god_full(void* user_data); +bool enable_god_liaison(void* user_data); +bool enable_god_basic(void* user_data); +void check_merchant_status(bool force = false); + +void exchange_callingcard(const LLUUID& dest_id); + +void handle_gestures(void*); +void handle_sit_down(void*); +void handle_object_build(void*); +void handle_object_touch(); +bool enable_object_edit_gltf_material(); +bool enable_object_save_gltf_material(); +bool enable_object_open(); +void handle_object_open(); + +bool visible_take_object(); +bool tools_visible_take_object(); +bool enable_object_take_copy(); +bool enable_object_return(); +bool enable_object_delete(); + +// Buy either contents or object itself +void handle_buy(); +void handle_take(bool take_separate = false); +void handle_take_copy(); +void handle_look_at_selection(const LLSD& param); +void handle_zoom_to_object(LLUUID object_id); +void handle_object_return(); +void handle_object_delete(); +void handle_object_edit(); +void handle_object_edit_gltf_material(); +void handle_object_save_gltf_material(); + +void handle_attachment_edit(const LLUUID& inv_item_id); +void handle_attachment_touch(const LLUUID& inv_item_id); +bool enable_attachment_touch(const LLUUID& inv_item_id); + +void handle_buy_land(); + +// Takes avatar UUID, or if no UUID passed, uses last selected object +void handle_avatar_freeze(const LLSD& avatar_id); + +// Takes avatar UUID, or if no UUID passed, uses last selected object +void handle_avatar_eject(const LLSD& avatar_id); + +bool enable_freeze_eject(const LLSD& avatar_id); + +// Can anyone take a free copy of the object? +// *TODO: Move to separate file +bool anyone_copy_selection(LLSelectNode* nodep); + +// Is this selected object for sale? +// *TODO: Move to separate file +bool for_sale_selection(LLSelectNode* nodep); + +void handle_toggle_flycam(); + +void handle_object_sit_or_stand(); +void handle_object_sit(const LLUUID& object_id); +void handle_give_money_dialog(); +bool enable_pay_object(); +bool enable_buy_object(); +bool handle_go_to(); + +// Export to XML or Collada +void handle_export_selected( void * ); + +// Convert strings to internal types +U32 render_type_from_string(std::string render_type); +U32 feature_from_string(std::string feature); +U64 info_display_from_string(std::string info_display); + +class LLViewerMenuHolderGL : public LLMenuHolderGL +{ +public: + struct Params : public LLInitParam::Block + {}; + + LLViewerMenuHolderGL(const Params& p); + + virtual bool hideMenus(); + + void setParcelSelection(LLSafeHandle selection); + void setObjectSelection(LLSafeHandle selection); + + virtual const LLRect getMenuRect() const; + +protected: + LLSafeHandle mParcelSelection; + LLSafeHandle mObjectSelection; +}; + +extern LLMenuBarGL* gMenuBarView; +//extern LLView* gMenuBarHolder; +extern LLMenuGL* gEditMenu; +extern LLMenuGL* gPopupMenuView; +extern LLViewerMenuHolderGL* gMenuHolder; +extern LLMenuBarGL* gLoginMenuBarView; + +// Context menus in 3D scene +extern LLContextMenu *gMenuAvatarSelf; +extern LLContextMenu *gMenuAvatarOther; +extern LLContextMenu *gMenuObject; +extern LLContextMenu *gMenuAttachmentSelf; +extern LLContextMenu *gMenuAttachmentOther; +extern LLContextMenu *gMenuLand; +extern LLContextMenu *gMenuMuteParticle; + +// Needed to build menus when attachment site list available +extern LLMenuGL* gAttachSubMenu; +extern LLMenuGL* gDetachSubMenu; +extern LLMenuGL* gTakeOffClothes; +extern LLMenuGL* gDetachAvatarMenu; +extern LLMenuGL* gDetachHUDAvatarMenu; +extern LLContextMenu* gAttachScreenPieMenu; +extern LLContextMenu* gDetachScreenPieMenu; +extern LLContextMenu* gDetachHUDAttSelfMenu; +extern LLContextMenu* gAttachPieMenu; +extern LLContextMenu* gDetachPieMenu; +extern LLContextMenu* gDetachAttSelfMenu; +extern LLContextMenu* gAttachBodyPartPieMenus[9]; +extern LLContextMenu* gDetachBodyPartPieMenus[9]; + +extern LLMenuItemCallGL* gMutePieMenu; +extern LLMenuItemCallGL* gMuteObjectPieMenu; +extern LLMenuItemCallGL* gBuyPassPieMenu; + +#endif diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp index bf618a765b..177df9af61 100644 --- a/indra/newview/llviewermenufile.cpp +++ b/indra/newview/llviewermenufile.cpp @@ -1,1290 +1,1290 @@ -/** - * @file llviewermenufile.cpp - * @brief "File" menu in the main menu bar. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llviewermenufile.h" - -// project includes -#include "llagent.h" -#include "llagentbenefits.h" -#include "llagentcamera.h" -#include "llfilepicker.h" -#include "llfloaterreg.h" -#include "llbuycurrencyhtml.h" -#include "llfloatermap.h" -#include "llfloatermodelpreview.h" -#include "llmaterialeditor.h" -#include "llfloaterperms.h" -#include "llfloatersnapshot.h" -#include "llfloatersimplesnapshot.h" -#include "llimage.h" -#include "llimagebmp.h" -#include "llimagepng.h" -#include "llimagej2c.h" -#include "llimagejpeg.h" -#include "llimagetga.h" -#include "llinventorymodel.h" // gInventory -#include "llpluginclassmedia.h" -#include "llresourcedata.h" -#include "llstatusbar.h" -#include "lltinygltfhelper.h" -#include "lltoast.h" -#include "llviewercontrol.h" // gSavedSettings -#include "llviewertexturelist.h" -#include "lluictrlfactory.h" -#include "llviewerinventory.h" -#include "llviewermenu.h" // gMenuHolder -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" -#include "llviewerstats.h" -#include "llviewerwindow.h" -#include "llappviewer.h" -#include "lluploaddialog.h" -#include "lltrans.h" -#include "llfloaterbuycurrency.h" -#include "llviewerassetupload.h" - -// linden libraries -#include "llnotificationsutil.h" -#include "llsdserialize.h" -#include "llsdutil.h" -#include "llstring.h" -#include "lltransactiontypes.h" -#include "lluuid.h" -#include "llvorbisencode.h" -#include "message.h" - -// system libraries -#include - -class LLFileEnableUpload : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - return true; - } -}; - -class LLFileEnableUploadModel : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLFloaterModelPreview* fmp = (LLFloaterModelPreview*) LLFloaterReg::findInstance("upload_model"); - if (fmp && fmp->isModelLoading()) - { - return false; - } - - return true; - } -}; - -class LLFileEnableUploadMaterial : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - if (gAgent.getRegionCapability("UpdateMaterialAgentInventory").empty()) - { - return false; - } - - return true; - } -}; - -class LLMeshEnabled : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - return gSavedSettings.getBOOL("MeshEnabled"); - } -}; - -class LLMeshUploadVisible : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - return gMeshRepo.meshUploadEnabled(); - } -}; - -LLMutex* LLFilePickerThread::sMutex = NULL; -std::queue LLFilePickerThread::sDeadQ; - -void LLFilePickerThread::getFile() -{ -#if LL_WINDOWS - // Todo: get rid of LLFilePickerThread and make this modeless - start(); -#elif LL_DARWIN - runModeless(); -#else - run(); -#endif -} - -//virtual -void LLFilePickerThread::run() -{ -#if LL_WINDOWS - bool blocking = false; -#else - bool blocking = true; // modal -#endif - - LLFilePicker picker; - - if (mIsSaveDialog) - { - if (picker.getSaveFile(mSaveFilter, mProposedName, blocking)) - { - mResponses.push_back(picker.getFirstFile()); - } - } - else - { - bool result = mIsGetMultiple ? picker.getMultipleOpenFiles(mLoadFilter, blocking) : picker.getOpenFile(mLoadFilter, blocking); - if (result) - { - std::string filename = picker.getFirstFile(); // consider copying mFiles directly - do - { - mResponses.push_back(filename); - filename = picker.getNextFile(); - } - while (mIsGetMultiple && !filename.empty()); - } - } - - { - LLMutexLock lock(sMutex); - sDeadQ.push(this); - } -} - -void LLFilePickerThread::runModeless() -{ - bool result = false; - LLFilePicker picker; - - if (mIsSaveDialog) - { - result = picker.getSaveFileModeless(mSaveFilter, - mProposedName, - modelessStringCallback, - this); - } - else if (mIsGetMultiple) - { - result = picker.getMultipleOpenFilesModeless(mLoadFilter, modelessVectorCallback, this); - } - else - { - result = picker.getOpenFileModeless(mLoadFilter, modelessVectorCallback, this); - } - - if (!result) - { - LLMutexLock lock(sMutex); - sDeadQ.push(this); - } -} - -void LLFilePickerThread::modelessStringCallback(bool success, - std::string &response, - void *user_data) -{ - LLFilePickerThread *picker = (LLFilePickerThread*)user_data; - if (success) - { - picker->mResponses.push_back(response); - } - - { - LLMutexLock lock(sMutex); - sDeadQ.push(picker); - } -} - -void LLFilePickerThread::modelessVectorCallback(bool success, - std::vector &responses, - void *user_data) -{ - LLFilePickerThread *picker = (LLFilePickerThread*)user_data; - if (success) - { - if (picker->mIsGetMultiple) - { - picker->mResponses = responses; - } - else - { - std::vector::iterator iter = responses.begin(); - while (iter != responses.end()) - { - if (!iter->empty()) - { - picker->mResponses.push_back(*iter); - break; - } - iter++; - } - } - } - - { - LLMutexLock lock(sMutex); - sDeadQ.push(picker); - } -} - -//static -void LLFilePickerThread::initClass() -{ - sMutex = new LLMutex(); -} - -//static -void LLFilePickerThread::cleanupClass() -{ - clearDead(); - - delete sMutex; - sMutex = NULL; -} - -//static -void LLFilePickerThread::clearDead() -{ - if (!sDeadQ.empty()) - { - LLMutexLock lock(sMutex); - while (!sDeadQ.empty()) - { - LLFilePickerThread* thread = sDeadQ.front(); - thread->notify(thread->mResponses); - delete thread; - sDeadQ.pop(); - } - } -} - -LLFilePickerReplyThread::LLFilePickerReplyThread(const file_picked_signal_t::slot_type& cb, LLFilePicker::ELoadFilter filter, bool get_multiple, const file_picked_signal_t::slot_type& failure_cb) - : LLFilePickerThread(filter, get_multiple), - mLoadFilter(filter), - mSaveFilter(LLFilePicker::FFSAVE_ALL), - mFilePickedSignal(NULL), - mFailureSignal(NULL) -{ - mFilePickedSignal = new file_picked_signal_t(); - mFilePickedSignal->connect(cb); - - mFailureSignal = new file_picked_signal_t(); - mFailureSignal->connect(failure_cb); -} - -LLFilePickerReplyThread::LLFilePickerReplyThread(const file_picked_signal_t::slot_type& cb, LLFilePicker::ESaveFilter filter, const std::string &proposed_name, const file_picked_signal_t::slot_type& failure_cb) - : LLFilePickerThread(filter, proposed_name), - mLoadFilter(LLFilePicker::FFLOAD_ALL), - mSaveFilter(filter), - mFilePickedSignal(NULL), - mFailureSignal(NULL) -{ - mFilePickedSignal = new file_picked_signal_t(); - mFilePickedSignal->connect(cb); - - mFailureSignal = new file_picked_signal_t(); - mFailureSignal->connect(failure_cb); -} - -LLFilePickerReplyThread::~LLFilePickerReplyThread() -{ - delete mFilePickedSignal; - delete mFailureSignal; -} - -void LLFilePickerReplyThread::startPicker(const file_picked_signal_t::slot_type & cb, LLFilePicker::ELoadFilter filter, bool get_multiple, const file_picked_signal_t::slot_type & failure_cb) -{ - (new LLFilePickerReplyThread(cb, filter, get_multiple, failure_cb))->getFile(); -} - -void LLFilePickerReplyThread::startPicker(const file_picked_signal_t::slot_type & cb, LLFilePicker::ESaveFilter filter, const std::string & proposed_name, const file_picked_signal_t::slot_type & failure_cb) -{ - (new LLFilePickerReplyThread(cb, filter, proposed_name, failure_cb))->getFile(); -} - -void LLFilePickerReplyThread::notify(const std::vector& filenames) -{ - if (filenames.empty()) - { - if (mFailureSignal) - { - (*mFailureSignal)(filenames, mLoadFilter, mSaveFilter); - } - } - else - { - if (mFilePickedSignal) - { - (*mFilePickedSignal)(filenames, mLoadFilter, mSaveFilter); - } - } -} - - -LLMediaFilePicker::LLMediaFilePicker(LLPluginClassMedia* plugin, LLFilePicker::ELoadFilter filter, bool get_multiple) - : LLFilePickerThread(filter, get_multiple), - mPlugin(plugin->getSharedPtr()) -{ -} - -LLMediaFilePicker::LLMediaFilePicker(LLPluginClassMedia* plugin, LLFilePicker::ESaveFilter filter, const std::string &proposed_name) - : LLFilePickerThread(filter, proposed_name), - mPlugin(plugin->getSharedPtr()) -{ -} - -void LLMediaFilePicker::notify(const std::vector& filenames) -{ - mPlugin->sendPickFileResponse(mResponses); - mPlugin = NULL; -} - -//============================================================================ - -#if LL_WINDOWS -static std::string SOUND_EXTENSIONS = "wav"; -static std::string IMAGE_EXTENSIONS = "tga bmp jpg jpeg png"; -static std::string ANIM_EXTENSIONS = "bvh anim"; -static std::string XML_EXTENSIONS = "xml"; -static std::string SLOBJECT_EXTENSIONS = "slobject"; -#endif -static std::string ALL_FILE_EXTENSIONS = "*.*"; -static std::string MODEL_EXTENSIONS = "dae"; -static std::string MATERIAL_EXTENSIONS = "gltf glb"; - -std::string build_extensions_string(LLFilePicker::ELoadFilter filter) -{ - switch(filter) - { -#if LL_WINDOWS - case LLFilePicker::FFLOAD_IMAGE: - return IMAGE_EXTENSIONS; - case LLFilePicker::FFLOAD_WAV: - return SOUND_EXTENSIONS; - case LLFilePicker::FFLOAD_ANIM: - return ANIM_EXTENSIONS; - case LLFilePicker::FFLOAD_SLOBJECT: - return SLOBJECT_EXTENSIONS; - case LLFilePicker::FFLOAD_MODEL: - return MODEL_EXTENSIONS; - case LLFilePicker::FFLOAD_MATERIAL: - return MATERIAL_EXTENSIONS; - case LLFilePicker::FFLOAD_XML: - return XML_EXTENSIONS; - case LLFilePicker::FFLOAD_ALL: - case LLFilePicker::FFLOAD_EXE: - return ALL_FILE_EXTENSIONS; -#endif - default: - return ALL_FILE_EXTENSIONS; - } -} - - -const bool check_file_extension(const std::string& filename, LLFilePicker::ELoadFilter type) -{ - std::string ext = gDirUtilp->getExtension(filename); - - //strincmp doesn't like NULL pointers - if (ext.empty()) - { - std::string short_name = gDirUtilp->getBaseFileName(filename); - - // No extension - LLSD args; - args["FILE"] = short_name; - LLNotificationsUtil::add("NoFileExtension", args); - return false; - } - else - { - //so there is an extension - //loop over the valid extensions and compare to see - //if the extension is valid - - //now grab the set of valid file extensions - std::string valid_extensions = build_extensions_string(type); - - bool ext_valid = false; - - typedef boost::tokenizer > tokenizer; - boost::char_separator sep(" "); - tokenizer tokens(valid_extensions, sep); - tokenizer::iterator token_iter; - - //now loop over all valid file extensions - //and compare them to the extension of the file - //to be uploaded - for (token_iter = tokens.begin(); - token_iter != tokens.end() && !ext_valid; - ++token_iter) - { - const std::string& cur_token = *token_iter; - - if (cur_token == ext || cur_token == "*.*") - { - //valid extension - //or the acceptable extension is any - ext_valid = true; - } - }//end for (loop over all tokens) - - if (!ext_valid) - { - //should only get here if the extension exists - //but is invalid - LLSD args; - args["EXTENSION"] = ext; - args["VALIDS"] = valid_extensions; - LLNotificationsUtil::add("InvalidFileExtension", args); - return false; - } - }//end else (non-null extension) - return true; -} - -const void upload_single_file(const std::vector& filenames, LLFilePicker::ELoadFilter type) -{ - std::string filename = filenames[0]; - if (!check_file_extension(filename, type)) return; - - if (!filename.empty()) - { - if (type == LLFilePicker::FFLOAD_WAV) - { - // pre-qualify wavs to make sure the format is acceptable - std::string error_msg; - if (check_for_invalid_wav_formats(filename, error_msg)) - { - LL_INFOS() << error_msg << ": " << filename << LL_ENDL; - LLSD args; - args["FILE"] = filename; - LLNotificationsUtil::add(error_msg, args); - return; - } - else - { - LLFloaterReg::showInstance("upload_sound", LLSD(filename)); - } - } - if (type == LLFilePicker::FFLOAD_IMAGE) - { - LLFloaterReg::showInstance("upload_image", LLSD(filename)); - } - if (type == LLFilePicker::FFLOAD_ANIM) - { - std::string filename_lc(filename); - LLStringUtil::toLower(filename_lc); - if (filename_lc.rfind(".anim") != std::string::npos) - { - LLFloaterReg::showInstance("upload_anim_anim", LLSD(filename)); - } - else - { - LLFloaterReg::showInstance("upload_anim_bvh", LLSD(filename)); - } - } - } - return; -} - -void do_bulk_upload(std::vector filenames, const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option != 0) - { - // Cancel upload - return; - } - - for (std::vector::const_iterator in_iter = filenames.begin(); in_iter != filenames.end(); ++in_iter) - { - std::string filename = (*in_iter); - - std::string name = gDirUtilp->getBaseFileName(filename, true); - std::string asset_name = name; - LLStringUtil::replaceNonstandardASCII(asset_name, '?'); - LLStringUtil::replaceChar(asset_name, '|', '?'); - LLStringUtil::stripNonprintable(asset_name); - LLStringUtil::trim(asset_name); - - std::string ext = gDirUtilp->getExtension(filename); - LLAssetType::EType asset_type; - U32 codec; - S32 expected_upload_cost; - if (LLResourceUploadInfo::findAssetTypeAndCodecOfExtension(ext, asset_type, codec) && - LLAgentBenefitsMgr::current().findUploadCost(asset_type, expected_upload_cost)) - { - LLResourceUploadInfo::ptr_t uploadInfo(new LLNewFileResourceUploadInfo( - filename, - asset_name, - asset_name, 0, - LLFolderType::FT_NONE, LLInventoryType::IT_NONE, - LLFloaterPerms::getNextOwnerPerms("Uploads"), - LLFloaterPerms::getGroupPerms("Uploads"), - LLFloaterPerms::getEveryonePerms("Uploads"), - expected_upload_cost)); - - upload_new_resource(uploadInfo); - } - - // gltf does not use normal upload procedure - if (ext == "gltf" || ext == "glb") - { - tinygltf::Model model; - if (LLTinyGLTFHelper::loadModel(filename, model)) - { - S32 materials_in_file = model.materials.size(); - - for (S32 i = 0; i < materials_in_file; i++) - { - // Todo: - // 1. Decouple bulk upload from material editor - // 2. Take into account possiblity of identical textures - LLMaterialEditor::uploadMaterialFromModel(filename, model, i); - } - } - } - } -} - -bool get_bulk_upload_expected_cost(const std::vector& filenames, S32& total_cost, S32& file_count, S32& bvh_count) -{ - total_cost = 0; - file_count = 0; - bvh_count = 0; - for (std::vector::const_iterator in_iter = filenames.begin(); in_iter != filenames.end(); ++in_iter) - { - std::string filename = (*in_iter); - std::string ext = gDirUtilp->getExtension(filename); - - if (ext == "bvh") - { - bvh_count++; - } - - LLAssetType::EType asset_type; - U32 codec; - S32 cost; - - if (LLResourceUploadInfo::findAssetTypeAndCodecOfExtension(ext, asset_type, codec) && - LLAgentBenefitsMgr::current().findUploadCost(asset_type, cost)) - { - total_cost += cost; - file_count++; - } - - if (ext == "gltf" || ext == "glb") - { - S32 texture_upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost(); - - tinygltf::Model model; - - if (LLTinyGLTFHelper::loadModel(filename, model)) - { - S32 materials_in_file = model.materials.size(); - - for (S32 i = 0; i < materials_in_file; i++) - { - LLPointer material = new LLFetchedGLTFMaterial(); - std::string material_name; - bool decode_successful = LLTinyGLTFHelper::getMaterialFromModel(filename, model, i, material.get(), material_name); - - if (decode_successful) - { - // Todo: make it account for possibility of same texture in different - // materials and even in scope of same material - S32 texture_count = 0; - if (material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR].notNull()) - { - texture_count++; - } - if (material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS].notNull()) - { - texture_count++; - } - if (material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL].notNull()) - { - texture_count++; - } - if (material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE].notNull()) - { - texture_count++; - } - total_cost += texture_count * texture_upload_cost; - file_count++; - } - } - } - } - } - - return file_count > 0; -} - -const void upload_bulk(const std::vector& filenames, LLFilePicker::ELoadFilter type) -{ - // TODO: - // Check user balance for entire cost - // Charge user entire cost - // Loop, uploading - // If an upload fails, refund the user for that one - // - // Also fix single upload to charge first, then refund - - // FIXME PREMIUM what about known types that can't be bulk uploaded - // (bvh)? These will fail in the item by item upload but won't be - // mentioned in the notification. - std::vector filtered_filenames; - for (std::vector::const_iterator in_iter = filenames.begin(); in_iter != filenames.end(); ++in_iter) - { - const std::string& filename = *in_iter; - if (check_file_extension(filename, type)) - { - filtered_filenames.push_back(filename); - } - } - - S32 expected_upload_cost; - S32 expected_upload_count; - S32 bvh_count; - if (get_bulk_upload_expected_cost(filtered_filenames, expected_upload_cost, expected_upload_count, bvh_count)) - { - LLSD args; - args["COST"] = expected_upload_cost; - args["COUNT"] = expected_upload_count; - LLNotificationsUtil::add("BulkUploadCostConfirmation", args, LLSD(), boost::bind(do_bulk_upload, filtered_filenames, _1, _2)); - - if (filtered_filenames.size() > expected_upload_count) - { - if (bvh_count == filtered_filenames.size() - expected_upload_count) - { - LLNotificationsUtil::add("DoNotSupportBulkAnimationUpload"); - } - else - { - LLNotificationsUtil::add("BulkUploadIncompatibleFiles"); - } - } - } - else if (bvh_count == filtered_filenames.size()) - { - LLNotificationsUtil::add("DoNotSupportBulkAnimationUpload"); - } - else - { - LLNotificationsUtil::add("BulkUploadNoCompatibleFiles"); - } - -} - -class LLFileUploadImage : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - if (gAgentCamera.cameraMouselook()) - { - gAgentCamera.changeCameraToDefault(); - } - LLFilePickerReplyThread::startPicker(boost::bind(&upload_single_file, _1, _2), LLFilePicker::FFLOAD_IMAGE, false); - return true; - } -}; - -class LLFileUploadModel : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLFloaterModelPreview::showModelPreview(); - return true; - } -}; - -class LLFileUploadMaterial : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLMaterialEditor::importMaterial(); - return true; - } -}; - -class LLFileUploadSound : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - if (gAgentCamera.cameraMouselook()) - { - gAgentCamera.changeCameraToDefault(); - } - LLFilePickerReplyThread::startPicker(boost::bind(&upload_single_file, _1, _2), LLFilePicker::FFLOAD_WAV, false); - return true; - } -}; - -class LLFileUploadAnim : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - if (gAgentCamera.cameraMouselook()) - { - gAgentCamera.changeCameraToDefault(); - } - LLFilePickerReplyThread::startPicker(boost::bind(&upload_single_file, _1, _2), LLFilePicker::FFLOAD_ANIM, false); - return true; - } -}; - -class LLFileUploadBulk : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - if (gAgentCamera.cameraMouselook()) - { - gAgentCamera.changeCameraToDefault(); - } - LLFilePickerReplyThread::startPicker(boost::bind(&upload_bulk, _1, _2), LLFilePicker::FFLOAD_ALL, true); - return true; - } -}; - -void upload_error(const std::string& error_message, const std::string& label, const std::string& filename, const LLSD& args) -{ - LL_WARNS() << error_message << LL_ENDL; - LLNotificationsUtil::add(label, args); - if(LLFile::remove(filename) == -1) - { - LL_DEBUGS() << "unable to remove temp file" << LL_ENDL; - } - LLFilePicker::instance().reset(); -} - -class LLFileEnableCloseWindow : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool frontmost_fl_exists = (NULL != gFloaterView->getFrontmostClosableFloater()); - bool frontmost_snapshot_fl_exists = (NULL != gSnapshotFloaterView->getFrontmostClosableFloater()); - - return !LLNotificationsUI::LLToast::isAlertToastShown() && (frontmost_fl_exists || frontmost_snapshot_fl_exists); - } -}; - -class LLFileCloseWindow : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool frontmost_fl_exists = (NULL != gFloaterView->getFrontmostClosableFloater()); - LLFloater* snapshot_floater = gSnapshotFloaterView->getFrontmostClosableFloater(); - - if(snapshot_floater && (!frontmost_fl_exists || snapshot_floater->hasFocus())) - { - snapshot_floater->closeFloater(); - if (gFocusMgr.getKeyboardFocus() == NULL) - { - gFloaterView->focusFrontFloater(); - } - } - else - { - LLFloater::closeFrontmostFloater(); - } - if (gMenuHolder) gMenuHolder->hideMenus(); - return true; - } -}; - -class LLFileEnableCloseAllWindows : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLFloaterSnapshot* floater_snapshot = LLFloaterSnapshot::findInstance(); - bool is_floaters_snapshot_opened = (floater_snapshot && floater_snapshot->isInVisibleChain()); - bool open_children = gFloaterView->allChildrenClosed() && !is_floaters_snapshot_opened; - return !open_children && !LLNotificationsUI::LLToast::isAlertToastShown(); - } -}; - -class LLFileCloseAllWindows : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - bool app_quitting = false; - gFloaterView->closeAllChildren(app_quitting); - LLFloaterSnapshot* floater_snapshot = LLFloaterSnapshot::findInstance(); - if (floater_snapshot) - floater_snapshot->closeFloater(app_quitting); - if (gMenuHolder) gMenuHolder->hideMenus(); - return true; - } -}; - -class LLFileTakeSnapshotToDisk : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLPointer raw = new LLImageRaw; - - S32 width = gViewerWindow->getWindowWidthRaw(); - S32 height = gViewerWindow->getWindowHeightRaw(); - - bool render_ui = gSavedSettings.getBOOL("RenderUIInSnapshot"); - bool render_hud = gSavedSettings.getBOOL("RenderHUDInSnapshot"); - bool render_no_post = gSavedSettings.getBOOL("RenderSnapshotNoPost"); - - bool high_res = gSavedSettings.getBOOL("HighResSnapshot"); - if (high_res) - { - width *= 2; - height *= 2; - // not compatible with UI/HUD - render_ui = false; - render_hud = false; - } - - if (gViewerWindow->rawSnapshot(raw, - width, - height, - true, - false, - render_ui, - render_hud, - false, - render_no_post, - LLSnapshotModel::SNAPSHOT_TYPE_COLOR, - high_res ? S32_MAX : MAX_SNAPSHOT_IMAGE_SIZE)) //per side - { - LLPointer formatted; - LLSnapshotModel::ESnapshotFormat fmt = (LLSnapshotModel::ESnapshotFormat) gSavedSettings.getS32("SnapshotFormat"); - switch (fmt) - { - case LLSnapshotModel::SNAPSHOT_FORMAT_JPEG: - formatted = new LLImageJPEG(gSavedSettings.getS32("SnapshotQuality")); - break; - default: - LL_WARNS() << "Unknown local snapshot format: " << fmt << LL_ENDL; - case LLSnapshotModel::SNAPSHOT_FORMAT_PNG: - formatted = new LLImagePNG; - break; - case LLSnapshotModel::SNAPSHOT_FORMAT_BMP: - formatted = new LLImageBMP; - break; - } - formatted->enableOverSize() ; - formatted->encode(raw, 0); - formatted->disableOverSize() ; - LLSnapshotLivePreview::saveLocal(formatted); - } - return true; - } -}; - -class LLFileQuit : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLAppViewer::instance()->userQuit(); - return true; - } -}; - - -void handle_compress_image(void*) -{ - LLFilePicker& picker = LLFilePicker::instance(); - if (picker.getMultipleOpenFiles(LLFilePicker::FFLOAD_IMAGE)) - { - std::string infile = picker.getFirstFile(); - while (!infile.empty()) - { - std::string outfile = infile + ".j2c"; - - LL_INFOS() << "Input: " << infile << LL_ENDL; - LL_INFOS() << "Output: " << outfile << LL_ENDL; - - bool success; - - success = LLViewerTextureList::createUploadFile(infile, outfile, IMG_CODEC_TGA); - - if (success) - { - LL_INFOS() << "Compression complete" << LL_ENDL; - } - else - { - LL_INFOS() << "Compression failed: " << LLImage::getLastThreadError() << LL_ENDL; - } - - infile = picker.getNextFile(); - } - } -} - -// No convinient check in LLFile, and correct way would be something -// like GetFileSizeEx, which is too OS specific for current purpose -// so doing dirty, but OS independent fopen and fseek -size_t get_file_size(std::string &filename) -{ - LLFILE* file = LLFile::fopen(filename, "rb"); /*Flawfinder: ignore*/ - if (!file) - { - LL_WARNS() << "Error opening " << filename << LL_ENDL; - return 0; - } - - // read in the whole file - fseek(file, 0L, SEEK_END); - size_t file_length = (size_t)ftell(file); - fclose(file); - return file_length; -} - -void handle_compress_file_test(void*) -{ - LLFilePicker& picker = LLFilePicker::instance(); - if (picker.getOpenFile()) - { - std::string infile = picker.getFirstFile(); - if (!infile.empty()) - { - std::string packfile = infile + ".pack_test"; - std::string unpackfile = infile + ".unpack_test"; - - S64Bytes initial_size = S64Bytes(get_file_size(infile)); - - bool success; - - F64 total_seconds = LLTimer::getTotalSeconds(); - success = gzip_file(infile, packfile); - F64 result_pack_seconds = LLTimer::getTotalSeconds() - total_seconds; - - if (success) - { - S64Bytes packed_size = S64Bytes(get_file_size(packfile)); - - LL_INFOS() << "Packing complete, time: " << result_pack_seconds << " size: " << packed_size << LL_ENDL; - total_seconds = LLTimer::getTotalSeconds(); - success = gunzip_file(packfile, unpackfile); - F64 result_unpack_seconds = LLTimer::getTotalSeconds() - total_seconds; - - if (success) - { - S64Bytes unpacked_size = S64Bytes(get_file_size(unpackfile)); - - LL_INFOS() << "Unpacking complete, time: " << result_unpack_seconds << " size: " << unpacked_size << LL_ENDL; - - LLSD args; - args["FILE"] = infile; - args["PACK_TIME"] = result_pack_seconds; - args["UNPACK_TIME"] = result_unpack_seconds; - args["SIZE"] = LLSD::Integer(initial_size.valueInUnits()); - args["PSIZE"] = LLSD::Integer(packed_size.valueInUnits()); - args["USIZE"] = LLSD::Integer(unpacked_size.valueInUnits()); - LLNotificationsUtil::add("CompressionTestResults", args); - - LLFile::remove(packfile); - LLFile::remove(unpackfile); - } - else - { - LL_INFOS() << "Failed to uncompress file: " << packfile << LL_ENDL; - LLFile::remove(packfile); - } - - } - else - { - LL_INFOS() << "Failed to compres file: " << infile << LL_ENDL; - } - } - else - { - LL_INFOS() << "Failed to open file" << LL_ENDL; - } - } - else - { - LL_INFOS() << "Failed to open file" << LL_ENDL; - } -} - - -LLUUID upload_new_resource( - const std::string& src_filename, - std::string name, - std::string desc, - S32 compression_info, - LLFolderType::EType destination_folder_type, - LLInventoryType::EType inv_type, - U32 next_owner_perms, - U32 group_perms, - U32 everyone_perms, - const std::string& display_name, - LLAssetStorage::LLStoreAssetCallback callback, - S32 expected_upload_cost, - void *userdata, - bool show_inventory) -{ - - LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared( - src_filename, - name, desc, compression_info, - destination_folder_type, inv_type, - next_owner_perms, group_perms, everyone_perms, - expected_upload_cost, show_inventory)); - upload_new_resource(uploadInfo, callback, userdata); - - return LLUUID::null; -} - -void upload_done_callback( - const LLUUID& uuid, - void* user_data, - S32 result, - LLExtStat ext_status) // StoreAssetData callback (fixed) -{ - LLResourceData* data = (LLResourceData*)user_data; - S32 expected_upload_cost = data ? data->mExpectedUploadCost : 0; - //LLAssetType::EType pref_loc = data->mPreferredLocation; - bool is_balance_sufficient = true; - - if(data) - { - if (result >= 0) - { - LLFolderType::EType dest_loc = (data->mPreferredLocation == LLFolderType::FT_NONE) ? LLFolderType::assetTypeToFolderType(data->mAssetInfo.mType) : data->mPreferredLocation; - - if (LLAssetType::AT_SOUND == data->mAssetInfo.mType || - LLAssetType::AT_TEXTURE == data->mAssetInfo.mType || - LLAssetType::AT_ANIMATION == data->mAssetInfo.mType) - { - // Charge the user for the upload. - LLViewerRegion* region = gAgent.getRegion(); - - if(!(can_afford_transaction(expected_upload_cost))) - { - LLBuyCurrencyHTML::openCurrencyFloater( "", expected_upload_cost ); - is_balance_sufficient = false; - } - else if(region) - { - // Charge user for upload - gStatusBar->debitBalance(expected_upload_cost); - - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_MoneyTransferRequest); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_MoneyData); - msg->addUUIDFast(_PREHASH_SourceID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_DestID, LLUUID::null); - msg->addU8("Flags", 0); - // we tell the sim how much we were expecting to pay so it - // can respond to any discrepancy - msg->addS32Fast(_PREHASH_Amount, expected_upload_cost); - msg->addU8Fast(_PREHASH_AggregatePermNextOwner, (U8)LLAggregatePermissions::AP_EMPTY); - msg->addU8Fast(_PREHASH_AggregatePermInventory, (U8)LLAggregatePermissions::AP_EMPTY); - msg->addS32Fast(_PREHASH_TransactionType, TRANS_UPLOAD_CHARGE); - msg->addStringFast(_PREHASH_Description, NULL); - msg->sendReliable(region->getHost()); - } - } - - if(is_balance_sufficient) - { - // Actually add the upload to inventory - LL_INFOS() << "Adding " << uuid << " to inventory." << LL_ENDL; - const LLUUID folder_id = gInventory.findCategoryUUIDForType(dest_loc); - if(folder_id.notNull()) - { - U32 next_owner_perms = data->mNextOwnerPerm; - if(PERM_NONE == next_owner_perms) - { - next_owner_perms = PERM_MOVE | PERM_TRANSFER; - } - create_inventory_item(gAgent.getID(), gAgent.getSessionID(), - folder_id, data->mAssetInfo.mTransactionID, data->mAssetInfo.getName(), - data->mAssetInfo.getDescription(), data->mAssetInfo.mType, - data->mInventoryType, NO_INV_SUBTYPE, next_owner_perms, - LLPointer(NULL)); - } - else - { - LL_WARNS() << "Can't find a folder to put it in" << LL_ENDL; - } - } - } - else // if(result >= 0) - { - LLSD args; - args["FILE"] = LLInventoryType::lookupHumanReadable(data->mInventoryType); - args["REASON"] = std::string(LLAssetStorage::getErrorString(result)); - LLNotificationsUtil::add("CannotUploadReason", args); - } - - delete data; - data = NULL; - } - - LLUploadDialog::modalUploadFinished(); - - // *NOTE: This is a pretty big hack. What this does is check the - // file picker if there are any more pending uploads. If so, - // upload that file. - const std::string& next_file = LLFilePicker::instance().getNextFile(); - if(is_balance_sufficient && !next_file.empty()) - { - std::string asset_name = gDirUtilp->getBaseFileName(next_file, true); - LLStringUtil::replaceNonstandardASCII( asset_name, '?' ); - LLStringUtil::replaceChar(asset_name, '|', '?'); - LLStringUtil::stripNonprintable(asset_name); - LLStringUtil::trim(asset_name); - - std::string display_name = LLStringUtil::null; - LLAssetStorage::LLStoreAssetCallback callback; - void *userdata = NULL; - upload_new_resource( - next_file, - asset_name, - asset_name, // file - 0, - LLFolderType::FT_NONE, - LLInventoryType::IT_NONE, - LLFloaterPerms::getNextOwnerPerms("Uploads"), - LLFloaterPerms::getGroupPerms("Uploads"), - LLFloaterPerms::getEveryonePerms("Uploads"), - display_name, - callback, - expected_upload_cost, // assuming next in a group of uploads is of roughly the same type, i.e. same upload cost - userdata); - } -} - -void upload_new_resource( - LLResourceUploadInfo::ptr_t &uploadInfo, - LLAssetStorage::LLStoreAssetCallback callback, - void *userdata) -{ - if(gDisconnected) - { - return ; - } - -// uploadInfo->setAssetType(assetType); -// uploadInfo->setTransactionId(tid); - - - std::string url = gAgent.getRegionCapability("NewFileAgentInventory"); - - if ( !url.empty() ) - { - LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo); - } - else - { - uploadInfo->prepareUpload(); - uploadInfo->logPreparedUpload(); - - LL_INFOS() << "NewAgentInventory capability not found, new agent inventory via asset system." << LL_ENDL; - // check for adequate funds - // TODO: do this check on the sim - if (LLAssetType::AT_SOUND == uploadInfo->getAssetType() || - LLAssetType::AT_TEXTURE == uploadInfo->getAssetType() || - LLAssetType::AT_ANIMATION == uploadInfo->getAssetType()) - { - S32 balance = gStatusBar->getBalance(); - if (balance < uploadInfo->getExpectedUploadCost()) - { - // insufficient funds, bail on this upload - LLBuyCurrencyHTML::openCurrencyFloater("", uploadInfo->getExpectedUploadCost()); - return; - } - } - - LLResourceData* data = new LLResourceData; - data->mAssetInfo.mTransactionID = uploadInfo->getTransactionId(); - data->mAssetInfo.mUuid = uploadInfo->getAssetId(); - data->mAssetInfo.mType = uploadInfo->getAssetType(); - data->mAssetInfo.mCreatorID = gAgentID; - data->mInventoryType = uploadInfo->getInventoryType(); - data->mNextOwnerPerm = uploadInfo->getNextOwnerPerms(); - data->mExpectedUploadCost = uploadInfo->getExpectedUploadCost(); - data->mUserData = userdata; - data->mAssetInfo.setName(uploadInfo->getName()); - data->mAssetInfo.setDescription(uploadInfo->getDescription()); - data->mPreferredLocation = uploadInfo->getDestinationFolderType(); - - LLAssetStorage::LLStoreAssetCallback asset_callback = &upload_done_callback; - if (callback) - { - asset_callback = callback; - } - gAssetStorage->storeAssetData( - data->mAssetInfo.mTransactionID, - data->mAssetInfo.mType, - asset_callback, - (void*)data, - false); - } -} - - -void init_menu_file() -{ - view_listener_t::addCommit(new LLFileUploadImage(), "File.UploadImage"); - view_listener_t::addCommit(new LLFileUploadSound(), "File.UploadSound"); - view_listener_t::addCommit(new LLFileUploadAnim(), "File.UploadAnim"); - view_listener_t::addCommit(new LLFileUploadModel(), "File.UploadModel"); - view_listener_t::addCommit(new LLFileUploadMaterial(), "File.UploadMaterial"); - view_listener_t::addCommit(new LLFileUploadBulk(), "File.UploadBulk"); - view_listener_t::addCommit(new LLFileCloseWindow(), "File.CloseWindow"); - view_listener_t::addCommit(new LLFileCloseAllWindows(), "File.CloseAllWindows"); - view_listener_t::addEnable(new LLFileEnableCloseWindow(), "File.EnableCloseWindow"); - view_listener_t::addEnable(new LLFileEnableCloseAllWindows(), "File.EnableCloseAllWindows"); - view_listener_t::addCommit(new LLFileTakeSnapshotToDisk(), "File.TakeSnapshotToDisk"); - view_listener_t::addCommit(new LLFileQuit(), "File.Quit"); - - view_listener_t::addEnable(new LLFileEnableUpload(), "File.EnableUpload"); - view_listener_t::addEnable(new LLFileEnableUploadModel(), "File.EnableUploadModel"); - view_listener_t::addEnable(new LLFileEnableUploadMaterial(), "File.EnableUploadMaterial"); - view_listener_t::addMenu(new LLMeshEnabled(), "File.MeshEnabled"); - view_listener_t::addMenu(new LLMeshUploadVisible(), "File.VisibleUploadModel"); - - // "File.SaveTexture" moved to llpanelmaininventory so that it can be properly handled. -} +/** + * @file llviewermenufile.cpp + * @brief "File" menu in the main menu bar. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llviewermenufile.h" + +// project includes +#include "llagent.h" +#include "llagentbenefits.h" +#include "llagentcamera.h" +#include "llfilepicker.h" +#include "llfloaterreg.h" +#include "llbuycurrencyhtml.h" +#include "llfloatermap.h" +#include "llfloatermodelpreview.h" +#include "llmaterialeditor.h" +#include "llfloaterperms.h" +#include "llfloatersnapshot.h" +#include "llfloatersimplesnapshot.h" +#include "llimage.h" +#include "llimagebmp.h" +#include "llimagepng.h" +#include "llimagej2c.h" +#include "llimagejpeg.h" +#include "llimagetga.h" +#include "llinventorymodel.h" // gInventory +#include "llpluginclassmedia.h" +#include "llresourcedata.h" +#include "llstatusbar.h" +#include "lltinygltfhelper.h" +#include "lltoast.h" +#include "llviewercontrol.h" // gSavedSettings +#include "llviewertexturelist.h" +#include "lluictrlfactory.h" +#include "llviewerinventory.h" +#include "llviewermenu.h" // gMenuHolder +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "llviewerstats.h" +#include "llviewerwindow.h" +#include "llappviewer.h" +#include "lluploaddialog.h" +#include "lltrans.h" +#include "llfloaterbuycurrency.h" +#include "llviewerassetupload.h" + +// linden libraries +#include "llnotificationsutil.h" +#include "llsdserialize.h" +#include "llsdutil.h" +#include "llstring.h" +#include "lltransactiontypes.h" +#include "lluuid.h" +#include "llvorbisencode.h" +#include "message.h" + +// system libraries +#include + +class LLFileEnableUpload : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + return true; + } +}; + +class LLFileEnableUploadModel : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLFloaterModelPreview* fmp = (LLFloaterModelPreview*) LLFloaterReg::findInstance("upload_model"); + if (fmp && fmp->isModelLoading()) + { + return false; + } + + return true; + } +}; + +class LLFileEnableUploadMaterial : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + if (gAgent.getRegionCapability("UpdateMaterialAgentInventory").empty()) + { + return false; + } + + return true; + } +}; + +class LLMeshEnabled : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + return gSavedSettings.getBOOL("MeshEnabled"); + } +}; + +class LLMeshUploadVisible : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + return gMeshRepo.meshUploadEnabled(); + } +}; + +LLMutex* LLFilePickerThread::sMutex = NULL; +std::queue LLFilePickerThread::sDeadQ; + +void LLFilePickerThread::getFile() +{ +#if LL_WINDOWS + // Todo: get rid of LLFilePickerThread and make this modeless + start(); +#elif LL_DARWIN + runModeless(); +#else + run(); +#endif +} + +//virtual +void LLFilePickerThread::run() +{ +#if LL_WINDOWS + bool blocking = false; +#else + bool blocking = true; // modal +#endif + + LLFilePicker picker; + + if (mIsSaveDialog) + { + if (picker.getSaveFile(mSaveFilter, mProposedName, blocking)) + { + mResponses.push_back(picker.getFirstFile()); + } + } + else + { + bool result = mIsGetMultiple ? picker.getMultipleOpenFiles(mLoadFilter, blocking) : picker.getOpenFile(mLoadFilter, blocking); + if (result) + { + std::string filename = picker.getFirstFile(); // consider copying mFiles directly + do + { + mResponses.push_back(filename); + filename = picker.getNextFile(); + } + while (mIsGetMultiple && !filename.empty()); + } + } + + { + LLMutexLock lock(sMutex); + sDeadQ.push(this); + } +} + +void LLFilePickerThread::runModeless() +{ + bool result = false; + LLFilePicker picker; + + if (mIsSaveDialog) + { + result = picker.getSaveFileModeless(mSaveFilter, + mProposedName, + modelessStringCallback, + this); + } + else if (mIsGetMultiple) + { + result = picker.getMultipleOpenFilesModeless(mLoadFilter, modelessVectorCallback, this); + } + else + { + result = picker.getOpenFileModeless(mLoadFilter, modelessVectorCallback, this); + } + + if (!result) + { + LLMutexLock lock(sMutex); + sDeadQ.push(this); + } +} + +void LLFilePickerThread::modelessStringCallback(bool success, + std::string &response, + void *user_data) +{ + LLFilePickerThread *picker = (LLFilePickerThread*)user_data; + if (success) + { + picker->mResponses.push_back(response); + } + + { + LLMutexLock lock(sMutex); + sDeadQ.push(picker); + } +} + +void LLFilePickerThread::modelessVectorCallback(bool success, + std::vector &responses, + void *user_data) +{ + LLFilePickerThread *picker = (LLFilePickerThread*)user_data; + if (success) + { + if (picker->mIsGetMultiple) + { + picker->mResponses = responses; + } + else + { + std::vector::iterator iter = responses.begin(); + while (iter != responses.end()) + { + if (!iter->empty()) + { + picker->mResponses.push_back(*iter); + break; + } + iter++; + } + } + } + + { + LLMutexLock lock(sMutex); + sDeadQ.push(picker); + } +} + +//static +void LLFilePickerThread::initClass() +{ + sMutex = new LLMutex(); +} + +//static +void LLFilePickerThread::cleanupClass() +{ + clearDead(); + + delete sMutex; + sMutex = NULL; +} + +//static +void LLFilePickerThread::clearDead() +{ + if (!sDeadQ.empty()) + { + LLMutexLock lock(sMutex); + while (!sDeadQ.empty()) + { + LLFilePickerThread* thread = sDeadQ.front(); + thread->notify(thread->mResponses); + delete thread; + sDeadQ.pop(); + } + } +} + +LLFilePickerReplyThread::LLFilePickerReplyThread(const file_picked_signal_t::slot_type& cb, LLFilePicker::ELoadFilter filter, bool get_multiple, const file_picked_signal_t::slot_type& failure_cb) + : LLFilePickerThread(filter, get_multiple), + mLoadFilter(filter), + mSaveFilter(LLFilePicker::FFSAVE_ALL), + mFilePickedSignal(NULL), + mFailureSignal(NULL) +{ + mFilePickedSignal = new file_picked_signal_t(); + mFilePickedSignal->connect(cb); + + mFailureSignal = new file_picked_signal_t(); + mFailureSignal->connect(failure_cb); +} + +LLFilePickerReplyThread::LLFilePickerReplyThread(const file_picked_signal_t::slot_type& cb, LLFilePicker::ESaveFilter filter, const std::string &proposed_name, const file_picked_signal_t::slot_type& failure_cb) + : LLFilePickerThread(filter, proposed_name), + mLoadFilter(LLFilePicker::FFLOAD_ALL), + mSaveFilter(filter), + mFilePickedSignal(NULL), + mFailureSignal(NULL) +{ + mFilePickedSignal = new file_picked_signal_t(); + mFilePickedSignal->connect(cb); + + mFailureSignal = new file_picked_signal_t(); + mFailureSignal->connect(failure_cb); +} + +LLFilePickerReplyThread::~LLFilePickerReplyThread() +{ + delete mFilePickedSignal; + delete mFailureSignal; +} + +void LLFilePickerReplyThread::startPicker(const file_picked_signal_t::slot_type & cb, LLFilePicker::ELoadFilter filter, bool get_multiple, const file_picked_signal_t::slot_type & failure_cb) +{ + (new LLFilePickerReplyThread(cb, filter, get_multiple, failure_cb))->getFile(); +} + +void LLFilePickerReplyThread::startPicker(const file_picked_signal_t::slot_type & cb, LLFilePicker::ESaveFilter filter, const std::string & proposed_name, const file_picked_signal_t::slot_type & failure_cb) +{ + (new LLFilePickerReplyThread(cb, filter, proposed_name, failure_cb))->getFile(); +} + +void LLFilePickerReplyThread::notify(const std::vector& filenames) +{ + if (filenames.empty()) + { + if (mFailureSignal) + { + (*mFailureSignal)(filenames, mLoadFilter, mSaveFilter); + } + } + else + { + if (mFilePickedSignal) + { + (*mFilePickedSignal)(filenames, mLoadFilter, mSaveFilter); + } + } +} + + +LLMediaFilePicker::LLMediaFilePicker(LLPluginClassMedia* plugin, LLFilePicker::ELoadFilter filter, bool get_multiple) + : LLFilePickerThread(filter, get_multiple), + mPlugin(plugin->getSharedPtr()) +{ +} + +LLMediaFilePicker::LLMediaFilePicker(LLPluginClassMedia* plugin, LLFilePicker::ESaveFilter filter, const std::string &proposed_name) + : LLFilePickerThread(filter, proposed_name), + mPlugin(plugin->getSharedPtr()) +{ +} + +void LLMediaFilePicker::notify(const std::vector& filenames) +{ + mPlugin->sendPickFileResponse(mResponses); + mPlugin = NULL; +} + +//============================================================================ + +#if LL_WINDOWS +static std::string SOUND_EXTENSIONS = "wav"; +static std::string IMAGE_EXTENSIONS = "tga bmp jpg jpeg png"; +static std::string ANIM_EXTENSIONS = "bvh anim"; +static std::string XML_EXTENSIONS = "xml"; +static std::string SLOBJECT_EXTENSIONS = "slobject"; +#endif +static std::string ALL_FILE_EXTENSIONS = "*.*"; +static std::string MODEL_EXTENSIONS = "dae"; +static std::string MATERIAL_EXTENSIONS = "gltf glb"; + +std::string build_extensions_string(LLFilePicker::ELoadFilter filter) +{ + switch(filter) + { +#if LL_WINDOWS + case LLFilePicker::FFLOAD_IMAGE: + return IMAGE_EXTENSIONS; + case LLFilePicker::FFLOAD_WAV: + return SOUND_EXTENSIONS; + case LLFilePicker::FFLOAD_ANIM: + return ANIM_EXTENSIONS; + case LLFilePicker::FFLOAD_SLOBJECT: + return SLOBJECT_EXTENSIONS; + case LLFilePicker::FFLOAD_MODEL: + return MODEL_EXTENSIONS; + case LLFilePicker::FFLOAD_MATERIAL: + return MATERIAL_EXTENSIONS; + case LLFilePicker::FFLOAD_XML: + return XML_EXTENSIONS; + case LLFilePicker::FFLOAD_ALL: + case LLFilePicker::FFLOAD_EXE: + return ALL_FILE_EXTENSIONS; +#endif + default: + return ALL_FILE_EXTENSIONS; + } +} + + +const bool check_file_extension(const std::string& filename, LLFilePicker::ELoadFilter type) +{ + std::string ext = gDirUtilp->getExtension(filename); + + //strincmp doesn't like NULL pointers + if (ext.empty()) + { + std::string short_name = gDirUtilp->getBaseFileName(filename); + + // No extension + LLSD args; + args["FILE"] = short_name; + LLNotificationsUtil::add("NoFileExtension", args); + return false; + } + else + { + //so there is an extension + //loop over the valid extensions and compare to see + //if the extension is valid + + //now grab the set of valid file extensions + std::string valid_extensions = build_extensions_string(type); + + bool ext_valid = false; + + typedef boost::tokenizer > tokenizer; + boost::char_separator sep(" "); + tokenizer tokens(valid_extensions, sep); + tokenizer::iterator token_iter; + + //now loop over all valid file extensions + //and compare them to the extension of the file + //to be uploaded + for (token_iter = tokens.begin(); + token_iter != tokens.end() && !ext_valid; + ++token_iter) + { + const std::string& cur_token = *token_iter; + + if (cur_token == ext || cur_token == "*.*") + { + //valid extension + //or the acceptable extension is any + ext_valid = true; + } + }//end for (loop over all tokens) + + if (!ext_valid) + { + //should only get here if the extension exists + //but is invalid + LLSD args; + args["EXTENSION"] = ext; + args["VALIDS"] = valid_extensions; + LLNotificationsUtil::add("InvalidFileExtension", args); + return false; + } + }//end else (non-null extension) + return true; +} + +const void upload_single_file(const std::vector& filenames, LLFilePicker::ELoadFilter type) +{ + std::string filename = filenames[0]; + if (!check_file_extension(filename, type)) return; + + if (!filename.empty()) + { + if (type == LLFilePicker::FFLOAD_WAV) + { + // pre-qualify wavs to make sure the format is acceptable + std::string error_msg; + if (check_for_invalid_wav_formats(filename, error_msg)) + { + LL_INFOS() << error_msg << ": " << filename << LL_ENDL; + LLSD args; + args["FILE"] = filename; + LLNotificationsUtil::add(error_msg, args); + return; + } + else + { + LLFloaterReg::showInstance("upload_sound", LLSD(filename)); + } + } + if (type == LLFilePicker::FFLOAD_IMAGE) + { + LLFloaterReg::showInstance("upload_image", LLSD(filename)); + } + if (type == LLFilePicker::FFLOAD_ANIM) + { + std::string filename_lc(filename); + LLStringUtil::toLower(filename_lc); + if (filename_lc.rfind(".anim") != std::string::npos) + { + LLFloaterReg::showInstance("upload_anim_anim", LLSD(filename)); + } + else + { + LLFloaterReg::showInstance("upload_anim_bvh", LLSD(filename)); + } + } + } + return; +} + +void do_bulk_upload(std::vector filenames, const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option != 0) + { + // Cancel upload + return; + } + + for (std::vector::const_iterator in_iter = filenames.begin(); in_iter != filenames.end(); ++in_iter) + { + std::string filename = (*in_iter); + + std::string name = gDirUtilp->getBaseFileName(filename, true); + std::string asset_name = name; + LLStringUtil::replaceNonstandardASCII(asset_name, '?'); + LLStringUtil::replaceChar(asset_name, '|', '?'); + LLStringUtil::stripNonprintable(asset_name); + LLStringUtil::trim(asset_name); + + std::string ext = gDirUtilp->getExtension(filename); + LLAssetType::EType asset_type; + U32 codec; + S32 expected_upload_cost; + if (LLResourceUploadInfo::findAssetTypeAndCodecOfExtension(ext, asset_type, codec) && + LLAgentBenefitsMgr::current().findUploadCost(asset_type, expected_upload_cost)) + { + LLResourceUploadInfo::ptr_t uploadInfo(new LLNewFileResourceUploadInfo( + filename, + asset_name, + asset_name, 0, + LLFolderType::FT_NONE, LLInventoryType::IT_NONE, + LLFloaterPerms::getNextOwnerPerms("Uploads"), + LLFloaterPerms::getGroupPerms("Uploads"), + LLFloaterPerms::getEveryonePerms("Uploads"), + expected_upload_cost)); + + upload_new_resource(uploadInfo); + } + + // gltf does not use normal upload procedure + if (ext == "gltf" || ext == "glb") + { + tinygltf::Model model; + if (LLTinyGLTFHelper::loadModel(filename, model)) + { + S32 materials_in_file = model.materials.size(); + + for (S32 i = 0; i < materials_in_file; i++) + { + // Todo: + // 1. Decouple bulk upload from material editor + // 2. Take into account possiblity of identical textures + LLMaterialEditor::uploadMaterialFromModel(filename, model, i); + } + } + } + } +} + +bool get_bulk_upload_expected_cost(const std::vector& filenames, S32& total_cost, S32& file_count, S32& bvh_count) +{ + total_cost = 0; + file_count = 0; + bvh_count = 0; + for (std::vector::const_iterator in_iter = filenames.begin(); in_iter != filenames.end(); ++in_iter) + { + std::string filename = (*in_iter); + std::string ext = gDirUtilp->getExtension(filename); + + if (ext == "bvh") + { + bvh_count++; + } + + LLAssetType::EType asset_type; + U32 codec; + S32 cost; + + if (LLResourceUploadInfo::findAssetTypeAndCodecOfExtension(ext, asset_type, codec) && + LLAgentBenefitsMgr::current().findUploadCost(asset_type, cost)) + { + total_cost += cost; + file_count++; + } + + if (ext == "gltf" || ext == "glb") + { + S32 texture_upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost(); + + tinygltf::Model model; + + if (LLTinyGLTFHelper::loadModel(filename, model)) + { + S32 materials_in_file = model.materials.size(); + + for (S32 i = 0; i < materials_in_file; i++) + { + LLPointer material = new LLFetchedGLTFMaterial(); + std::string material_name; + bool decode_successful = LLTinyGLTFHelper::getMaterialFromModel(filename, model, i, material.get(), material_name); + + if (decode_successful) + { + // Todo: make it account for possibility of same texture in different + // materials and even in scope of same material + S32 texture_count = 0; + if (material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR].notNull()) + { + texture_count++; + } + if (material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS].notNull()) + { + texture_count++; + } + if (material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL].notNull()) + { + texture_count++; + } + if (material->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE].notNull()) + { + texture_count++; + } + total_cost += texture_count * texture_upload_cost; + file_count++; + } + } + } + } + } + + return file_count > 0; +} + +const void upload_bulk(const std::vector& filenames, LLFilePicker::ELoadFilter type) +{ + // TODO: + // Check user balance for entire cost + // Charge user entire cost + // Loop, uploading + // If an upload fails, refund the user for that one + // + // Also fix single upload to charge first, then refund + + // FIXME PREMIUM what about known types that can't be bulk uploaded + // (bvh)? These will fail in the item by item upload but won't be + // mentioned in the notification. + std::vector filtered_filenames; + for (std::vector::const_iterator in_iter = filenames.begin(); in_iter != filenames.end(); ++in_iter) + { + const std::string& filename = *in_iter; + if (check_file_extension(filename, type)) + { + filtered_filenames.push_back(filename); + } + } + + S32 expected_upload_cost; + S32 expected_upload_count; + S32 bvh_count; + if (get_bulk_upload_expected_cost(filtered_filenames, expected_upload_cost, expected_upload_count, bvh_count)) + { + LLSD args; + args["COST"] = expected_upload_cost; + args["COUNT"] = expected_upload_count; + LLNotificationsUtil::add("BulkUploadCostConfirmation", args, LLSD(), boost::bind(do_bulk_upload, filtered_filenames, _1, _2)); + + if (filtered_filenames.size() > expected_upload_count) + { + if (bvh_count == filtered_filenames.size() - expected_upload_count) + { + LLNotificationsUtil::add("DoNotSupportBulkAnimationUpload"); + } + else + { + LLNotificationsUtil::add("BulkUploadIncompatibleFiles"); + } + } + } + else if (bvh_count == filtered_filenames.size()) + { + LLNotificationsUtil::add("DoNotSupportBulkAnimationUpload"); + } + else + { + LLNotificationsUtil::add("BulkUploadNoCompatibleFiles"); + } + +} + +class LLFileUploadImage : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + if (gAgentCamera.cameraMouselook()) + { + gAgentCamera.changeCameraToDefault(); + } + LLFilePickerReplyThread::startPicker(boost::bind(&upload_single_file, _1, _2), LLFilePicker::FFLOAD_IMAGE, false); + return true; + } +}; + +class LLFileUploadModel : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLFloaterModelPreview::showModelPreview(); + return true; + } +}; + +class LLFileUploadMaterial : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLMaterialEditor::importMaterial(); + return true; + } +}; + +class LLFileUploadSound : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + if (gAgentCamera.cameraMouselook()) + { + gAgentCamera.changeCameraToDefault(); + } + LLFilePickerReplyThread::startPicker(boost::bind(&upload_single_file, _1, _2), LLFilePicker::FFLOAD_WAV, false); + return true; + } +}; + +class LLFileUploadAnim : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + if (gAgentCamera.cameraMouselook()) + { + gAgentCamera.changeCameraToDefault(); + } + LLFilePickerReplyThread::startPicker(boost::bind(&upload_single_file, _1, _2), LLFilePicker::FFLOAD_ANIM, false); + return true; + } +}; + +class LLFileUploadBulk : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + if (gAgentCamera.cameraMouselook()) + { + gAgentCamera.changeCameraToDefault(); + } + LLFilePickerReplyThread::startPicker(boost::bind(&upload_bulk, _1, _2), LLFilePicker::FFLOAD_ALL, true); + return true; + } +}; + +void upload_error(const std::string& error_message, const std::string& label, const std::string& filename, const LLSD& args) +{ + LL_WARNS() << error_message << LL_ENDL; + LLNotificationsUtil::add(label, args); + if(LLFile::remove(filename) == -1) + { + LL_DEBUGS() << "unable to remove temp file" << LL_ENDL; + } + LLFilePicker::instance().reset(); +} + +class LLFileEnableCloseWindow : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool frontmost_fl_exists = (NULL != gFloaterView->getFrontmostClosableFloater()); + bool frontmost_snapshot_fl_exists = (NULL != gSnapshotFloaterView->getFrontmostClosableFloater()); + + return !LLNotificationsUI::LLToast::isAlertToastShown() && (frontmost_fl_exists || frontmost_snapshot_fl_exists); + } +}; + +class LLFileCloseWindow : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool frontmost_fl_exists = (NULL != gFloaterView->getFrontmostClosableFloater()); + LLFloater* snapshot_floater = gSnapshotFloaterView->getFrontmostClosableFloater(); + + if(snapshot_floater && (!frontmost_fl_exists || snapshot_floater->hasFocus())) + { + snapshot_floater->closeFloater(); + if (gFocusMgr.getKeyboardFocus() == NULL) + { + gFloaterView->focusFrontFloater(); + } + } + else + { + LLFloater::closeFrontmostFloater(); + } + if (gMenuHolder) gMenuHolder->hideMenus(); + return true; + } +}; + +class LLFileEnableCloseAllWindows : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLFloaterSnapshot* floater_snapshot = LLFloaterSnapshot::findInstance(); + bool is_floaters_snapshot_opened = (floater_snapshot && floater_snapshot->isInVisibleChain()); + bool open_children = gFloaterView->allChildrenClosed() && !is_floaters_snapshot_opened; + return !open_children && !LLNotificationsUI::LLToast::isAlertToastShown(); + } +}; + +class LLFileCloseAllWindows : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + bool app_quitting = false; + gFloaterView->closeAllChildren(app_quitting); + LLFloaterSnapshot* floater_snapshot = LLFloaterSnapshot::findInstance(); + if (floater_snapshot) + floater_snapshot->closeFloater(app_quitting); + if (gMenuHolder) gMenuHolder->hideMenus(); + return true; + } +}; + +class LLFileTakeSnapshotToDisk : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLPointer raw = new LLImageRaw; + + S32 width = gViewerWindow->getWindowWidthRaw(); + S32 height = gViewerWindow->getWindowHeightRaw(); + + bool render_ui = gSavedSettings.getBOOL("RenderUIInSnapshot"); + bool render_hud = gSavedSettings.getBOOL("RenderHUDInSnapshot"); + bool render_no_post = gSavedSettings.getBOOL("RenderSnapshotNoPost"); + + bool high_res = gSavedSettings.getBOOL("HighResSnapshot"); + if (high_res) + { + width *= 2; + height *= 2; + // not compatible with UI/HUD + render_ui = false; + render_hud = false; + } + + if (gViewerWindow->rawSnapshot(raw, + width, + height, + true, + false, + render_ui, + render_hud, + false, + render_no_post, + LLSnapshotModel::SNAPSHOT_TYPE_COLOR, + high_res ? S32_MAX : MAX_SNAPSHOT_IMAGE_SIZE)) //per side + { + LLPointer formatted; + LLSnapshotModel::ESnapshotFormat fmt = (LLSnapshotModel::ESnapshotFormat) gSavedSettings.getS32("SnapshotFormat"); + switch (fmt) + { + case LLSnapshotModel::SNAPSHOT_FORMAT_JPEG: + formatted = new LLImageJPEG(gSavedSettings.getS32("SnapshotQuality")); + break; + default: + LL_WARNS() << "Unknown local snapshot format: " << fmt << LL_ENDL; + case LLSnapshotModel::SNAPSHOT_FORMAT_PNG: + formatted = new LLImagePNG; + break; + case LLSnapshotModel::SNAPSHOT_FORMAT_BMP: + formatted = new LLImageBMP; + break; + } + formatted->enableOverSize() ; + formatted->encode(raw, 0); + formatted->disableOverSize() ; + LLSnapshotLivePreview::saveLocal(formatted); + } + return true; + } +}; + +class LLFileQuit : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + LLAppViewer::instance()->userQuit(); + return true; + } +}; + + +void handle_compress_image(void*) +{ + LLFilePicker& picker = LLFilePicker::instance(); + if (picker.getMultipleOpenFiles(LLFilePicker::FFLOAD_IMAGE)) + { + std::string infile = picker.getFirstFile(); + while (!infile.empty()) + { + std::string outfile = infile + ".j2c"; + + LL_INFOS() << "Input: " << infile << LL_ENDL; + LL_INFOS() << "Output: " << outfile << LL_ENDL; + + bool success; + + success = LLViewerTextureList::createUploadFile(infile, outfile, IMG_CODEC_TGA); + + if (success) + { + LL_INFOS() << "Compression complete" << LL_ENDL; + } + else + { + LL_INFOS() << "Compression failed: " << LLImage::getLastThreadError() << LL_ENDL; + } + + infile = picker.getNextFile(); + } + } +} + +// No convinient check in LLFile, and correct way would be something +// like GetFileSizeEx, which is too OS specific for current purpose +// so doing dirty, but OS independent fopen and fseek +size_t get_file_size(std::string &filename) +{ + LLFILE* file = LLFile::fopen(filename, "rb"); /*Flawfinder: ignore*/ + if (!file) + { + LL_WARNS() << "Error opening " << filename << LL_ENDL; + return 0; + } + + // read in the whole file + fseek(file, 0L, SEEK_END); + size_t file_length = (size_t)ftell(file); + fclose(file); + return file_length; +} + +void handle_compress_file_test(void*) +{ + LLFilePicker& picker = LLFilePicker::instance(); + if (picker.getOpenFile()) + { + std::string infile = picker.getFirstFile(); + if (!infile.empty()) + { + std::string packfile = infile + ".pack_test"; + std::string unpackfile = infile + ".unpack_test"; + + S64Bytes initial_size = S64Bytes(get_file_size(infile)); + + bool success; + + F64 total_seconds = LLTimer::getTotalSeconds(); + success = gzip_file(infile, packfile); + F64 result_pack_seconds = LLTimer::getTotalSeconds() - total_seconds; + + if (success) + { + S64Bytes packed_size = S64Bytes(get_file_size(packfile)); + + LL_INFOS() << "Packing complete, time: " << result_pack_seconds << " size: " << packed_size << LL_ENDL; + total_seconds = LLTimer::getTotalSeconds(); + success = gunzip_file(packfile, unpackfile); + F64 result_unpack_seconds = LLTimer::getTotalSeconds() - total_seconds; + + if (success) + { + S64Bytes unpacked_size = S64Bytes(get_file_size(unpackfile)); + + LL_INFOS() << "Unpacking complete, time: " << result_unpack_seconds << " size: " << unpacked_size << LL_ENDL; + + LLSD args; + args["FILE"] = infile; + args["PACK_TIME"] = result_pack_seconds; + args["UNPACK_TIME"] = result_unpack_seconds; + args["SIZE"] = LLSD::Integer(initial_size.valueInUnits()); + args["PSIZE"] = LLSD::Integer(packed_size.valueInUnits()); + args["USIZE"] = LLSD::Integer(unpacked_size.valueInUnits()); + LLNotificationsUtil::add("CompressionTestResults", args); + + LLFile::remove(packfile); + LLFile::remove(unpackfile); + } + else + { + LL_INFOS() << "Failed to uncompress file: " << packfile << LL_ENDL; + LLFile::remove(packfile); + } + + } + else + { + LL_INFOS() << "Failed to compres file: " << infile << LL_ENDL; + } + } + else + { + LL_INFOS() << "Failed to open file" << LL_ENDL; + } + } + else + { + LL_INFOS() << "Failed to open file" << LL_ENDL; + } +} + + +LLUUID upload_new_resource( + const std::string& src_filename, + std::string name, + std::string desc, + S32 compression_info, + LLFolderType::EType destination_folder_type, + LLInventoryType::EType inv_type, + U32 next_owner_perms, + U32 group_perms, + U32 everyone_perms, + const std::string& display_name, + LLAssetStorage::LLStoreAssetCallback callback, + S32 expected_upload_cost, + void *userdata, + bool show_inventory) +{ + + LLResourceUploadInfo::ptr_t uploadInfo(std::make_shared( + src_filename, + name, desc, compression_info, + destination_folder_type, inv_type, + next_owner_perms, group_perms, everyone_perms, + expected_upload_cost, show_inventory)); + upload_new_resource(uploadInfo, callback, userdata); + + return LLUUID::null; +} + +void upload_done_callback( + const LLUUID& uuid, + void* user_data, + S32 result, + LLExtStat ext_status) // StoreAssetData callback (fixed) +{ + LLResourceData* data = (LLResourceData*)user_data; + S32 expected_upload_cost = data ? data->mExpectedUploadCost : 0; + //LLAssetType::EType pref_loc = data->mPreferredLocation; + bool is_balance_sufficient = true; + + if(data) + { + if (result >= 0) + { + LLFolderType::EType dest_loc = (data->mPreferredLocation == LLFolderType::FT_NONE) ? LLFolderType::assetTypeToFolderType(data->mAssetInfo.mType) : data->mPreferredLocation; + + if (LLAssetType::AT_SOUND == data->mAssetInfo.mType || + LLAssetType::AT_TEXTURE == data->mAssetInfo.mType || + LLAssetType::AT_ANIMATION == data->mAssetInfo.mType) + { + // Charge the user for the upload. + LLViewerRegion* region = gAgent.getRegion(); + + if(!(can_afford_transaction(expected_upload_cost))) + { + LLBuyCurrencyHTML::openCurrencyFloater( "", expected_upload_cost ); + is_balance_sufficient = false; + } + else if(region) + { + // Charge user for upload + gStatusBar->debitBalance(expected_upload_cost); + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_MoneyTransferRequest); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_MoneyData); + msg->addUUIDFast(_PREHASH_SourceID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_DestID, LLUUID::null); + msg->addU8("Flags", 0); + // we tell the sim how much we were expecting to pay so it + // can respond to any discrepancy + msg->addS32Fast(_PREHASH_Amount, expected_upload_cost); + msg->addU8Fast(_PREHASH_AggregatePermNextOwner, (U8)LLAggregatePermissions::AP_EMPTY); + msg->addU8Fast(_PREHASH_AggregatePermInventory, (U8)LLAggregatePermissions::AP_EMPTY); + msg->addS32Fast(_PREHASH_TransactionType, TRANS_UPLOAD_CHARGE); + msg->addStringFast(_PREHASH_Description, NULL); + msg->sendReliable(region->getHost()); + } + } + + if(is_balance_sufficient) + { + // Actually add the upload to inventory + LL_INFOS() << "Adding " << uuid << " to inventory." << LL_ENDL; + const LLUUID folder_id = gInventory.findCategoryUUIDForType(dest_loc); + if(folder_id.notNull()) + { + U32 next_owner_perms = data->mNextOwnerPerm; + if(PERM_NONE == next_owner_perms) + { + next_owner_perms = PERM_MOVE | PERM_TRANSFER; + } + create_inventory_item(gAgent.getID(), gAgent.getSessionID(), + folder_id, data->mAssetInfo.mTransactionID, data->mAssetInfo.getName(), + data->mAssetInfo.getDescription(), data->mAssetInfo.mType, + data->mInventoryType, NO_INV_SUBTYPE, next_owner_perms, + LLPointer(NULL)); + } + else + { + LL_WARNS() << "Can't find a folder to put it in" << LL_ENDL; + } + } + } + else // if(result >= 0) + { + LLSD args; + args["FILE"] = LLInventoryType::lookupHumanReadable(data->mInventoryType); + args["REASON"] = std::string(LLAssetStorage::getErrorString(result)); + LLNotificationsUtil::add("CannotUploadReason", args); + } + + delete data; + data = NULL; + } + + LLUploadDialog::modalUploadFinished(); + + // *NOTE: This is a pretty big hack. What this does is check the + // file picker if there are any more pending uploads. If so, + // upload that file. + const std::string& next_file = LLFilePicker::instance().getNextFile(); + if(is_balance_sufficient && !next_file.empty()) + { + std::string asset_name = gDirUtilp->getBaseFileName(next_file, true); + LLStringUtil::replaceNonstandardASCII( asset_name, '?' ); + LLStringUtil::replaceChar(asset_name, '|', '?'); + LLStringUtil::stripNonprintable(asset_name); + LLStringUtil::trim(asset_name); + + std::string display_name = LLStringUtil::null; + LLAssetStorage::LLStoreAssetCallback callback; + void *userdata = NULL; + upload_new_resource( + next_file, + asset_name, + asset_name, // file + 0, + LLFolderType::FT_NONE, + LLInventoryType::IT_NONE, + LLFloaterPerms::getNextOwnerPerms("Uploads"), + LLFloaterPerms::getGroupPerms("Uploads"), + LLFloaterPerms::getEveryonePerms("Uploads"), + display_name, + callback, + expected_upload_cost, // assuming next in a group of uploads is of roughly the same type, i.e. same upload cost + userdata); + } +} + +void upload_new_resource( + LLResourceUploadInfo::ptr_t &uploadInfo, + LLAssetStorage::LLStoreAssetCallback callback, + void *userdata) +{ + if(gDisconnected) + { + return ; + } + +// uploadInfo->setAssetType(assetType); +// uploadInfo->setTransactionId(tid); + + + std::string url = gAgent.getRegionCapability("NewFileAgentInventory"); + + if ( !url.empty() ) + { + LLViewerAssetUpload::EnqueueInventoryUpload(url, uploadInfo); + } + else + { + uploadInfo->prepareUpload(); + uploadInfo->logPreparedUpload(); + + LL_INFOS() << "NewAgentInventory capability not found, new agent inventory via asset system." << LL_ENDL; + // check for adequate funds + // TODO: do this check on the sim + if (LLAssetType::AT_SOUND == uploadInfo->getAssetType() || + LLAssetType::AT_TEXTURE == uploadInfo->getAssetType() || + LLAssetType::AT_ANIMATION == uploadInfo->getAssetType()) + { + S32 balance = gStatusBar->getBalance(); + if (balance < uploadInfo->getExpectedUploadCost()) + { + // insufficient funds, bail on this upload + LLBuyCurrencyHTML::openCurrencyFloater("", uploadInfo->getExpectedUploadCost()); + return; + } + } + + LLResourceData* data = new LLResourceData; + data->mAssetInfo.mTransactionID = uploadInfo->getTransactionId(); + data->mAssetInfo.mUuid = uploadInfo->getAssetId(); + data->mAssetInfo.mType = uploadInfo->getAssetType(); + data->mAssetInfo.mCreatorID = gAgentID; + data->mInventoryType = uploadInfo->getInventoryType(); + data->mNextOwnerPerm = uploadInfo->getNextOwnerPerms(); + data->mExpectedUploadCost = uploadInfo->getExpectedUploadCost(); + data->mUserData = userdata; + data->mAssetInfo.setName(uploadInfo->getName()); + data->mAssetInfo.setDescription(uploadInfo->getDescription()); + data->mPreferredLocation = uploadInfo->getDestinationFolderType(); + + LLAssetStorage::LLStoreAssetCallback asset_callback = &upload_done_callback; + if (callback) + { + asset_callback = callback; + } + gAssetStorage->storeAssetData( + data->mAssetInfo.mTransactionID, + data->mAssetInfo.mType, + asset_callback, + (void*)data, + false); + } +} + + +void init_menu_file() +{ + view_listener_t::addCommit(new LLFileUploadImage(), "File.UploadImage"); + view_listener_t::addCommit(new LLFileUploadSound(), "File.UploadSound"); + view_listener_t::addCommit(new LLFileUploadAnim(), "File.UploadAnim"); + view_listener_t::addCommit(new LLFileUploadModel(), "File.UploadModel"); + view_listener_t::addCommit(new LLFileUploadMaterial(), "File.UploadMaterial"); + view_listener_t::addCommit(new LLFileUploadBulk(), "File.UploadBulk"); + view_listener_t::addCommit(new LLFileCloseWindow(), "File.CloseWindow"); + view_listener_t::addCommit(new LLFileCloseAllWindows(), "File.CloseAllWindows"); + view_listener_t::addEnable(new LLFileEnableCloseWindow(), "File.EnableCloseWindow"); + view_listener_t::addEnable(new LLFileEnableCloseAllWindows(), "File.EnableCloseAllWindows"); + view_listener_t::addCommit(new LLFileTakeSnapshotToDisk(), "File.TakeSnapshotToDisk"); + view_listener_t::addCommit(new LLFileQuit(), "File.Quit"); + + view_listener_t::addEnable(new LLFileEnableUpload(), "File.EnableUpload"); + view_listener_t::addEnable(new LLFileEnableUploadModel(), "File.EnableUploadModel"); + view_listener_t::addEnable(new LLFileEnableUploadMaterial(), "File.EnableUploadMaterial"); + view_listener_t::addMenu(new LLMeshEnabled(), "File.MeshEnabled"); + view_listener_t::addMenu(new LLMeshUploadVisible(), "File.VisibleUploadModel"); + + // "File.SaveTexture" moved to llpanelmaininventory so that it can be properly handled. +} diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index e7c5e3a464..f1fdfc548b 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -1,7061 +1,7061 @@ -/** - * @file llviewermessage.cpp - * @brief Dumping ground for viewer-side message system callbacks. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llviewermessage.h" - -// Linden libraries -#include "llanimationstates.h" -#include "llaudioengine.h" -#include "llavataractions.h" -#include "llavatarnamecache.h" // IDEVO HACK -#include "lleventtimer.h" -#include "llfloatercreatelandmark.h" -#include "llfloaterreg.h" -#include "llfolderview.h" -#include "llfollowcamparams.h" -#include "llinventorydefines.h" -#include "lllslconstants.h" -#include "llmaterialtable.h" -#include "llregionhandle.h" -#include "llsd.h" -#include "llsdserialize.h" -#include "llteleportflags.h" -#include "lltoastnotifypanel.h" -#include "lltransactionflags.h" -#include "llfilesystem.h" -#include "llxfermanager.h" -#include "mean_collision_data.h" - -#include "llagent.h" -#include "llagentbenefits.h" -#include "llagentcamera.h" -#include "llcallingcard.h" -#include "llbuycurrencyhtml.h" -#include "llcontrolavatar.h" -#include "llfirstuse.h" -#include "llfloaterbump.h" -#include "llfloaterbuyland.h" -#include "llfloaterland.h" -#include "llfloaterregioninfo.h" -#include "llfloaterlandholdings.h" -#include "llfloaterpreference.h" -#include "llfloatersidepanelcontainer.h" -#include "llfloatersnapshot.h" -#include "llhudeffecttrail.h" -#include "llhudmanager.h" -#include "llimprocessing.h" -#include "llinventoryfunctions.h" -#include "llinventoryobserver.h" -#include "llinventorypanel.h" -#include "llfloaterimnearbychat.h" -#include "llmarketplacefunctions.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "llpanelgrouplandmoney.h" -#include "llpanelmaininventory.h" -#include "llrecentpeople.h" -#include "llscriptfloater.h" -#include "llscriptruntimeperms.h" -#include "llselectmgr.h" -#include "llstartup.h" -#include "llsky.h" -#include "llslurl.h" -#include "llstatenums.h" -#include "llstatusbar.h" -#include "llimview.h" -#include "llspeakers.h" -#include "lltrans.h" -#include "lltranslate.h" -#include "llviewerfoldertype.h" -#include "llvoavatar.h" // IDEVO HACK -#include "lluri.h" -#include "llviewergenericmessage.h" -#include "llviewermenu.h" -#include "llviewerinventory.h" -#include "llviewerjoystick.h" -#include "llviewerobjectlist.h" -#include "llviewerparcelmgr.h" -#include "llviewerstats.h" -#include "llviewerstatsrecorder.h" -#include "llviewertexteditor.h" -#include "llviewerthrottle.h" -#include "llviewerwindow.h" -#include "llvlmanager.h" -#include "llvoavatarself.h" -#include "llvovolume.h" -#include "llworld.h" -#include "pipeline.h" -#include "llfloaterworldmap.h" -#include "llviewerdisplay.h" -#include "llkeythrottle.h" -#include "llgroupactions.h" -#include "llagentui.h" -#include "llpanelblockedlist.h" -#include "llpanelplaceprofile.h" -#include "llviewerregion.h" -#include "llfloaterregionrestarting.h" - -#include "llnotificationmanager.h" // -#include "llexperiencecache.h" - -#include "llexperiencecache.h" -#include "lluiusage.h" - -extern void on_new_message(const LLSD& msg); - -extern bool gCubeSnapshot; - -// -// Constants -// -const F32 CAMERA_POSITION_THRESHOLD_SQUARED = 0.001f * 0.001f; - -// Determine how quickly residents' scripts can issue question dialogs -// Allow bursts of up to 5 dialogs in 10 seconds. 10*2=20 seconds recovery if throttle kicks in -static const U32 LLREQUEST_PERMISSION_THROTTLE_LIMIT = 5; // requests -static const F32 LLREQUEST_PERMISSION_THROTTLE_INTERVAL = 10.0f; // seconds - -extern bool gDebugClicks; -extern bool gShiftFrame; - -// function prototypes -bool check_offer_throttle(const std::string& from_name, bool check_only); -bool check_asset_previewable(const LLAssetType::EType asset_type); -static void process_money_balance_reply_extended(LLMessageSystem* msg); -bool handle_trusted_experiences_notification(const LLSD&); - -//inventory offer throttle globals -LLFrameTimer gThrottleTimer; -const U32 OFFER_THROTTLE_MAX_COUNT=5; //number of items per time period -const F32 OFFER_THROTTLE_TIME=10.f; //time period in seconds - -// Agent Update Flags (U8) -const U8 AU_FLAGS_NONE = 0x00; -const U8 AU_FLAGS_HIDETITLE = 0x01; -const U8 AU_FLAGS_CLIENT_AUTOPILOT = 0x02; - -void accept_friendship_coro(std::string url, LLSD notification) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("friendshipResponceErrorProcessing", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - if (url.empty()) - { - LL_WARNS("Friendship") << "Empty capability!" << LL_ENDL; - return; - } - - LLSD payload = notification["payload"]; - url += "?from=" + payload["from_id"].asString(); - url += "&agent_name=\"" + LLURI::escape(gAgentAvatarp->getFullname()) + "\""; - - LLSD data; - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, data); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS("Friendship") << "HTTP status, " << status.toTerseString() << - ". friendship offer accept failed." << LL_ENDL; - } - else - { - if (!result.has("success") || !result["success"].asBoolean()) - { - LL_WARNS("Friendship") << "Server failed to process accepted friendship. " << httpResults << LL_ENDL; - } - else - { - LL_DEBUGS("Friendship") << "Adding friend to list" << httpResults << LL_ENDL; - // add friend to recent people list - LLRecentPeople::instance().add(payload["from_id"]); - - LLNotificationsUtil::add("FriendshipAcceptedByMe", - notification["substitutions"], payload); - } - } -} - -void decline_friendship_coro(std::string url, LLSD notification, S32 option) -{ - if (url.empty()) - { - LL_WARNS("Friendship") << "Empty capability!" << LL_ENDL; - return; - } - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("friendshipResponceErrorProcessing", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD payload = notification["payload"]; - url += "?from=" + payload["from_id"].asString(); - - LLSD result = httpAdapter->deleteAndSuspend(httpRequest, url); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS("Friendship") << "HTTP status, " << status.toTerseString() << - ". friendship offer decline failed." << LL_ENDL; - } - else - { - if (!result.has("success") || !result["success"].asBoolean()) - { - LL_WARNS("Friendship") << "Server failed to process declined friendship. " << httpResults << LL_ENDL; - } - else - { - LL_DEBUGS("Friendship") << "Friendship declined" << httpResults << LL_ENDL; - if (option == 1) - { - LLNotificationsUtil::add("FriendshipDeclinedByMe", - notification["substitutions"], payload); - } - else if (option == 2) - { - // start IM session - LLAvatarActions::startIM(payload["from_id"].asUUID()); - } - } - } -} - -bool friendship_offer_callback(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - LLMessageSystem* msg = gMessageSystem; - const LLSD& payload = notification["payload"]; - LLNotificationPtr notification_ptr = LLNotifications::instance().find(notification["id"].asUUID()); - - // this will be skipped if the user offering friendship is blocked - if (notification_ptr) - { - switch(option) - { - case 0: - { - LLUIUsage::instance().logCommand("Agent.AcceptFriendship"); - // accept - LLAvatarTracker::formFriendship(payload["from_id"]); - - const LLUUID fid = gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD); - - // This will also trigger an onlinenotification if the user is online - std::string url = gAgent.getRegionCapability("AcceptFriendship"); - LL_DEBUGS("Friendship") << "Cap string: " << url << LL_ENDL; - if (!url.empty() && payload.has("online") && !payload["online"].asBoolean()) - { - LL_DEBUGS("Friendship") << "Accepting friendship via capability" << LL_ENDL; - LLCoros::instance().launch("LLMessageSystem::acceptFriendshipOffer", - boost::bind(accept_friendship_coro, url, notification)); - } - else if (payload.has("session_id") && payload["session_id"].asUUID().notNull()) - { - LL_DEBUGS("Friendship") << "Accepting friendship via viewer message" << LL_ENDL; - msg->newMessageFast(_PREHASH_AcceptFriendship); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_TransactionBlock); - msg->addUUIDFast(_PREHASH_TransactionID, payload["session_id"]); - msg->nextBlockFast(_PREHASH_FolderData); - msg->addUUIDFast(_PREHASH_FolderID, fid); - msg->sendReliable(LLHost(payload["sender"].asString())); - - // add friend to recent people list - LLRecentPeople::instance().add(payload["from_id"]); - LLNotificationsUtil::add("FriendshipAcceptedByMe", - notification["substitutions"], payload); - } - else - { - LL_WARNS("Friendship") << "Failed to accept friendship offer, neither capability nor transaction id are accessible" << LL_ENDL; - } - break; - } - case 1: // Decline - // fall-through - case 2: // Send IM - decline and start IM session - { - LLUIUsage::instance().logCommand("Agent.DeclineFriendship"); - // decline - // We no longer notify other viewers, but we DO still send - // the rejection to the simulator to delete the pending userop. - std::string url = gAgent.getRegionCapability("DeclineFriendship"); - LL_DEBUGS("Friendship") << "Cap string: " << url << LL_ENDL; - if (!url.empty() && payload.has("online") && !payload["online"].asBoolean()) - { - LL_DEBUGS("Friendship") << "Declining friendship via capability" << LL_ENDL; - LLCoros::instance().launch("LLMessageSystem::declineFriendshipOffer", - boost::bind(decline_friendship_coro, url, notification, option)); - } - else if (payload.has("session_id") && payload["session_id"].asUUID().notNull()) - { - LL_DEBUGS("Friendship") << "Declining friendship via viewer message" << LL_ENDL; - msg->newMessageFast(_PREHASH_DeclineFriendship); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_TransactionBlock); - msg->addUUIDFast(_PREHASH_TransactionID, payload["session_id"]); - msg->sendReliable(LLHost(payload["sender"].asString())); - - if (option == 1) // due to fall-through - { - LLNotificationsUtil::add("FriendshipDeclinedByMe", - notification["substitutions"], payload); - } - else if (option == 2) - { - // start IM session - LLAvatarActions::startIM(payload["from_id"].asUUID()); - } - } - else - { - LL_WARNS("Friendship") << "Failed to decline friendship offer, neither capability nor transaction id are accessible" << LL_ENDL; - } - } - default: - // close button probably, possibly timed out - break; - } - - // TODO: this set of calls has undesirable behavior under Windows OS (CHUI-985): - // here appears three additional toasts instead one modified - // need investigation and fix - - // LLNotificationFormPtr modified_form(new LLNotificationForm(*notification_ptr->getForm())); - // modified_form->setElementEnabled("Accept", false); - // modified_form->setElementEnabled("Decline", false); - // notification_ptr->updateForm(modified_form); - // notification_ptr->repost(); - } - - return false; -} -static LLNotificationFunctorRegistration friendship_offer_callback_reg("OfferFriendship", friendship_offer_callback); -static LLNotificationFunctorRegistration friendship_offer_callback_reg_nm("OfferFriendshipNoMessage", friendship_offer_callback); - -// Functions -// - -void give_money(const LLUUID& uuid, LLViewerRegion* region, S32 amount, bool is_group, - S32 trx_type, const std::string& desc) -{ - if(0 == amount || !region) return; - amount = abs(amount); - LL_INFOS("Messaging") << "give_money(" << uuid << "," << amount << ")"<< LL_ENDL; - if(can_afford_transaction(amount)) - { - if (uuid.isNull()) - { - LL_WARNS() << "Failed to send L$ gift to to Null UUID." << LL_ENDL; - return; - } -// gStatusBar->debitBalance(amount); - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_MoneyTransferRequest); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_MoneyData); - msg->addUUIDFast(_PREHASH_SourceID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_DestID, uuid); - msg->addU8Fast(_PREHASH_Flags, pack_transaction_flags(false, is_group)); - msg->addS32Fast(_PREHASH_Amount, amount); - msg->addU8Fast(_PREHASH_AggregatePermNextOwner, (U8)LLAggregatePermissions::AP_EMPTY); - msg->addU8Fast(_PREHASH_AggregatePermInventory, (U8)LLAggregatePermissions::AP_EMPTY); - msg->addS32Fast(_PREHASH_TransactionType, trx_type ); - msg->addStringFast(_PREHASH_Description, desc); - msg->sendReliable(region->getHost()); - } - else - { - LLStringUtil::format_map_t args; - args["AMOUNT"] = llformat("%d", amount); - LLBuyCurrencyHTML::openCurrencyFloater( LLTrans::getString("giving", args), amount ); - } -} - -void send_complete_agent_movement(const LLHost& sim_host) -{ - LL_DEBUGS("Teleport", "Messaging") << "Sending CompleteAgentMovement to sim_host " << sim_host << LL_ENDL; - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_CompleteAgentMovement); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addU32Fast(_PREHASH_CircuitCode, msg->mOurCircuitCode); - msg->sendReliable(sim_host); -} - -void process_logout_reply(LLMessageSystem* msg, void**) -{ - // The server has told us it's ok to quit. - LL_DEBUGS("Messaging") << "process_logout_reply" << LL_ENDL; - - LLUUID agent_id; - msg->getUUID("AgentData", "AgentID", agent_id); - LLUUID session_id; - msg->getUUID("AgentData", "SessionID", session_id); - if((agent_id != gAgent.getID()) || (session_id != gAgent.getSessionID())) - { - LL_WARNS("Messaging") << "Bogus Logout Reply" << LL_ENDL; - } - - LLInventoryModel::update_map_t parents; - S32 count = msg->getNumberOfBlocksFast( _PREHASH_InventoryData ); - for(S32 i = 0; i < count; ++i) - { - LLUUID item_id; - msg->getUUIDFast(_PREHASH_InventoryData, _PREHASH_ItemID, item_id, i); - - if( (1 == count) && item_id.isNull() ) - { - // Detect dummy item. Indicates an empty list. - break; - } - - // We do not need to track the asset ids, just account for an - // updated inventory version. - LL_INFOS("Messaging") << "process_logout_reply itemID=" << item_id << LL_ENDL; - LLInventoryItem* item = gInventory.getItem( item_id ); - if( item ) - { - parents[item->getParentUUID()] = 0; - gInventory.addChangedMask(LLInventoryObserver::INTERNAL, item_id); - } - else - { - LL_INFOS("Messaging") << "process_logout_reply item not found: " << item_id << LL_ENDL; - } - } - LLAppViewer::instance()->forceQuit(); -} - -void process_layer_data(LLMessageSystem *mesgsys, void **user_data) -{ - LLViewerRegion *regionp = LLWorld::getInstance()->getRegion(mesgsys->getSender()); - - LL_DEBUGS_ONCE("SceneLoadTiming") << "Received layer data" << LL_ENDL; - - if(!regionp) - { - LL_WARNS() << "Invalid region for layer data." << LL_ENDL; - return; - } - S32 size; - S8 type; - - mesgsys->getS8Fast(_PREHASH_LayerID, _PREHASH_Type, type); - size = mesgsys->getSizeFast(_PREHASH_LayerData, _PREHASH_Data); - if (0 == size) - { - LL_WARNS("Messaging") << "Layer data has zero size." << LL_ENDL; - return; - } - if (size < 0) - { - // getSizeFast() is probably trying to tell us about an error - LL_WARNS("Messaging") << "getSizeFast() returned negative result: " - << size - << LL_ENDL; - return; - } - U8 *datap = new U8[size]; - mesgsys->getBinaryDataFast(_PREHASH_LayerData, _PREHASH_Data, datap, size); - LLVLData *vl_datap = new LLVLData(regionp, type, datap, size); - if (mesgsys->getReceiveCompressedSize()) - { - gVLManager.addLayerData(vl_datap, (S32Bytes)mesgsys->getReceiveCompressedSize()); - } - else - { - gVLManager.addLayerData(vl_datap, (S32Bytes)mesgsys->getReceiveSize()); - } -} - -// S32 exported_object_count = 0; -// S32 exported_image_count = 0; -// S32 current_object_count = 0; -// S32 current_image_count = 0; - -// extern LLNotifyBox *gExporterNotify; -// extern LLUUID gExporterRequestID; -// extern std::string gExportDirectory; - -// extern LLUploadDialog *gExportDialog; - -// std::string gExportedFile; - -// std::map gImageChecksums; - -// void export_complete() -// { -// LLUploadDialog::modalUploadFinished(); -// gExporterRequestID.setNull(); -// gExportDirectory = ""; - -// LLFILE* fXML = LLFile::fopen(gExportedFile, "rb"); /* Flawfinder: ignore */ -// fseek(fXML, 0, SEEK_END); -// long length = ftell(fXML); -// fseek(fXML, 0, SEEK_SET); -// U8 *buffer = new U8[length + 1]; -// size_t nread = fread(buffer, 1, length, fXML); -// if (nread < (size_t) length) -// { -// LL_WARNS("Messaging") << "Short read" << LL_ENDL; -// } -// buffer[nread] = '\0'; -// fclose(fXML); - -// char *pos = (char *)buffer; -// while ((pos = strstr(pos+1, ""); - -// if (pos_uuid) -// { -// char image_uuid_str[UUID_STR_SIZE]; /* Flawfinder: ignore */ -// memcpy(image_uuid_str, pos_uuid+2, UUID_STR_SIZE-1); /* Flawfinder: ignore */ -// image_uuid_str[UUID_STR_SIZE-1] = 0; - -// LLUUID image_uuid(image_uuid_str); - -// LL_INFOS("Messaging") << "Found UUID: " << image_uuid << LL_ENDL; - -// std::map::iterator itor = gImageChecksums.find(image_uuid); -// if (itor != gImageChecksums.end()) -// { -// LL_INFOS("Messaging") << "Replacing with checksum: " << itor->second << LL_ENDL; -// if (!itor->second.empty()) -// { -// memcpy(&pos_check[10], itor->second.c_str(), 32); /* Flawfinder: ignore */ -// } -// } -// } -// } -// } - -// LLFILE* fXMLOut = LLFile::fopen(gExportedFile, "wb"); /* Flawfinder: ignore */ -// if (fwrite(buffer, 1, length, fXMLOut) != length) -// { -// LL_WARNS("Messaging") << "Short write" << LL_ENDL; -// } -// fclose(fXMLOut); - -// delete [] buffer; -// } - - -// void exported_item_complete(const LLTSCode status, void *user_data) -// { -// //std::string *filename = (std::string *)user_data; - -// if (status < LLTS_OK) -// { -// LL_WARNS("Messaging") << "Export failed!" << LL_ENDL; -// } -// else -// { -// ++current_object_count; -// if (current_image_count == exported_image_count && current_object_count == exported_object_count) -// { -// LL_INFOS("Messaging") << "*** Export complete ***" << LL_ENDL; - -// export_complete(); -// } -// else -// { -// gExportDialog->setMessage(llformat("Exported %d/%d object files, %d/%d textures.", current_object_count, exported_object_count, current_image_count, exported_image_count)); -// } -// } -// } - -// struct exported_image_info -// { -// LLUUID image_id; -// std::string filename; -// U32 image_num; -// }; - -// void exported_j2c_complete(const LLTSCode status, void *user_data) -// { -// exported_image_info *info = (exported_image_info *)user_data; -// LLUUID image_id = info->image_id; -// U32 image_num = info->image_num; -// std::string filename = info->filename; -// delete info; - -// if (status < LLTS_OK) -// { -// LL_WARNS("Messaging") << "Image download failed!" << LL_ENDL; -// } -// else -// { -// LLFILE* fIn = LLFile::fopen(filename, "rb"); /* Flawfinder: ignore */ -// if (fIn) -// { -// LLPointer ImageUtility = new LLImageJ2C; -// LLPointer TargaUtility = new LLImageTGA; - -// fseek(fIn, 0, SEEK_END); -// S32 length = ftell(fIn); -// fseek(fIn, 0, SEEK_SET); -// U8 *buffer = ImageUtility->allocateData(length); -// if (fread(buffer, 1, length, fIn) != length) -// { -// LL_WARNS("Messaging") << "Short read" << LL_ENDL; -// } -// fclose(fIn); -// LLFile::remove(filename); - -// // Convert to TGA -// LLPointer image = new LLImageRaw(); - -// ImageUtility->updateData(); -// ImageUtility->decode(image, 100000.0f); - -// TargaUtility->encode(image); -// U8 *data = TargaUtility->getData(); -// S32 data_size = TargaUtility->getDataSize(); - -// std::string file_path = gDirUtilp->getDirName(filename); - -// std::string output_file = llformat("%s/image-%03d.tga", file_path.c_str(), image_num);//filename; -// //S32 name_len = output_file.length(); -// //strcpy(&output_file[name_len-3], "tga"); -// LLFILE* fOut = LLFile::fopen(output_file, "wb"); /* Flawfinder: ignore */ -// char md5_hash_string[33]; /* Flawfinder: ignore */ -// strcpy(md5_hash_string, "00000000000000000000000000000000"); /* Flawfinder: ignore */ -// if (fOut) -// { -// if (fwrite(data, 1, data_size, fOut) != data_size) -// { -// LL_WARNS("Messaging") << "Short write" << LL_ENDL; -// } -// fseek(fOut, 0, SEEK_SET); -// fclose(fOut); -// fOut = LLFile::fopen(output_file, "rb"); /* Flawfinder: ignore */ -// LLMD5 my_md5_hash(fOut); -// my_md5_hash.hex_digest(md5_hash_string); -// } - -// gImageChecksums.insert(std::pair(image_id, md5_hash_string)); -// } -// } - -// ++current_image_count; -// if (current_image_count == exported_image_count && current_object_count == exported_object_count) -// { -// LL_INFOS("Messaging") << "*** Export textures complete ***" << LL_ENDL; -// export_complete(); -// } -// else -// { -// gExportDialog->setMessage(llformat("Exported %d/%d object files, %d/%d textures.", current_object_count, exported_object_count, current_image_count, exported_image_count)); -// } -//} - -void process_derez_ack(LLMessageSystem*, void**) -{ - if(gViewerWindow) gViewerWindow->getWindow()->decBusyCount(); -} - -void process_places_reply(LLMessageSystem* msg, void** data) -{ - LLUUID query_id; - - msg->getUUID("AgentData", "QueryID", query_id); - if (query_id.isNull()) - { - LLFloaterLandHoldings::processPlacesReply(msg, data); - } - else if(gAgent.isInGroup(query_id)) - { - LLPanelGroupLandMoney::processPlacesReply(msg, data); - } - else - { - LL_WARNS("Messaging") << "Got invalid PlacesReply message" << LL_ENDL; - } -} - -void send_sound_trigger(const LLUUID& sound_id, F32 gain) -{ - if (sound_id.isNull() || gAgent.getRegion() == NULL) - { - // disconnected agent or zero guids don't get sent (no sound) - return; - } - - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_SoundTrigger); - msg->nextBlockFast(_PREHASH_SoundData); - msg->addUUIDFast(_PREHASH_SoundID, sound_id); - // Client untrusted, ids set on sim - msg->addUUIDFast(_PREHASH_OwnerID, LLUUID::null ); - msg->addUUIDFast(_PREHASH_ObjectID, LLUUID::null ); - msg->addUUIDFast(_PREHASH_ParentID, LLUUID::null ); - - msg->addU64Fast(_PREHASH_Handle, gAgent.getRegion()->getHandle()); - - LLVector3 position = gAgent.getPositionAgent(); - msg->addVector3Fast(_PREHASH_Position, position); - msg->addF32Fast(_PREHASH_Gain, gain); - - gAgent.sendMessage(); -} - -static LLSD sSavedGroupInvite; -static LLSD sSavedResponse; - -void response_group_invitation_coro(std::string url, LLUUID group_id, bool notify_and_update) -{ - if (url.empty()) - { - LL_WARNS("GroupInvite") << "Empty capability!" << LL_ENDL; - return; - } - - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("responseGroupInvitation", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD payload; - payload["group"] = group_id; - - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, payload); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS("GroupInvite") << "HTTP status, " << status.toTerseString() << - ". Group " << group_id << " invitation response processing failed." << LL_ENDL; - } - else - { - if (!result.has("success") || !result["success"].asBoolean()) - { - LL_WARNS("GroupInvite") << "Server failed to process group " << group_id << " invitation response. " << httpResults << LL_ENDL; - } - else - { - LL_DEBUGS("GroupInvite") << "Successfully sent response to group " << group_id << " invitation" << LL_ENDL; - if (notify_and_update) - { - gAgent.sendAgentDataUpdateRequest(); - - LLGroupMgr::getInstance()->clearGroupData(group_id); - // refresh the floater for this group, if any. - LLGroupActions::refresh(group_id); - } - } - } -} - -void send_join_group_response(LLUUID group_id, LLUUID transaction_id, bool accept_invite, S32 fee, bool use_offline_cap, LLSD &payload) -{ - if (accept_invite && fee > 0) - { - // If there is a fee to join this group, make - // sure the user is sure they want to join. - LLSD args; - args["COST"] = llformat("%d", fee); - // Set the fee for next time to 0, so that we don't keep - // asking about a fee. - LLSD next_payload = payload; - next_payload["fee"] = 0; - LLNotificationsUtil::add("JoinGroupCanAfford", - args, - next_payload); - } - else if (use_offline_cap) - { - std::string url; - if (accept_invite) - { - url = gAgent.getRegionCapability("AcceptGroupInvite"); - } - else - { - url = gAgent.getRegionCapability("DeclineGroupInvite"); - } - - if (!url.empty()) - { - LL_DEBUGS("GroupInvite") << "Capability url: " << url << LL_ENDL; - LLCoros::instance().launch("LLMessageSystem::acceptGroupInvitation", - boost::bind(response_group_invitation_coro, url, group_id, accept_invite)); - } - else - { - // if sim has no this cap, we can do nothing - regular request will fail - LL_WARNS("GroupInvite") << "No capability, can't reply to offline invitation!" << LL_ENDL; - } - } - else - { - LL_DEBUGS("GroupInvite") << "Replying to group invite via IM message" << LL_ENDL; - - EInstantMessage type = accept_invite ? IM_GROUP_INVITATION_ACCEPT : IM_GROUP_INVITATION_DECLINE; - - if (accept_invite) - { - LLUIUsage::instance().logCommand("Group.Join"); - } - - send_improved_im(group_id, - std::string("name"), - std::string("message"), - IM_ONLINE, - type, - transaction_id); - } -} - -void send_join_group_response(LLUUID group_id, LLUUID transaction_id, bool accept_invite, S32 fee, bool use_offline_cap) -{ - LLSD payload; - if (accept_invite) - { - payload["group_id"] = group_id; - payload["transaction_id"] = transaction_id; - payload["fee"] = fee; - payload["use_offline_cap"] = use_offline_cap; - } - send_join_group_response(group_id, transaction_id, accept_invite, fee, use_offline_cap, payload); -} - -bool join_group_response(const LLSD& notification, const LLSD& response) -{ -// A bit of variable saving and restoring is used to deal with the case where your group list is full and you -// receive an invitation to another group. The data from that invitation is stored in the sSaved -// variables. If you then drop a group and click on the Join button the stored data is restored and used -// to join the group. - LLSD notification_adjusted = notification; - LLSD response_adjusted = response; - - std::string action = notification["name"]; - -// Storing all the information by group id allows for the rare case of being at your maximum -// group count and receiving more than one invitation. - std::string id = notification_adjusted["payload"]["group_id"].asString(); - - if ("JoinGroup" == action || "JoinGroupCanAfford" == action) - { - sSavedGroupInvite[id] = notification; - sSavedResponse[id] = response; - } - else if ("JoinedTooManyGroupsMember" == action) - { - S32 opt = LLNotificationsUtil::getSelectedOption(notification, response); - if (0 == opt) // Join button pressed - { - notification_adjusted = sSavedGroupInvite[id]; - response_adjusted = sSavedResponse[id]; - } - } - - S32 option = LLNotificationsUtil::getSelectedOption(notification_adjusted, response_adjusted); - bool accept_invite = false; - - LLUUID group_id = notification_adjusted["payload"]["group_id"].asUUID(); - LLUUID transaction_id = notification_adjusted["payload"]["transaction_id"].asUUID(); - std::string name = notification_adjusted["payload"]["name"].asString(); - std::string message = notification_adjusted["payload"]["message"].asString(); - S32 fee = notification_adjusted["payload"]["fee"].asInteger(); - U8 use_offline_cap = notification_adjusted["payload"]["use_offline_cap"].asInteger(); - - if (option == 2 && !group_id.isNull()) - { - LLGroupActions::show(group_id); - LLSD args; - args["MESSAGE"] = message; - LLNotificationsUtil::add("JoinGroup", args, notification_adjusted["payload"]); - return false; - } - - if(option == 0 && !group_id.isNull()) - { - // check for promotion or demotion. - S32 max_groups = LLAgentBenefitsMgr::current().getGroupMembershipLimit(); - if(gAgent.isInGroup(group_id)) ++max_groups; - - if(gAgent.mGroups.size() < max_groups) - { - accept_invite = true; - } - else - { - LLSD args; - args["NAME"] = name; - LLNotificationsUtil::add("JoinedTooManyGroupsMember", args, notification_adjusted["payload"]); - return false; - } - } - send_join_group_response(group_id, transaction_id, accept_invite, fee, use_offline_cap, notification_adjusted["payload"]); - - sSavedGroupInvite[id] = LLSD::emptyMap(); - sSavedResponse[id] = LLSD::emptyMap(); - - return false; -} - -static void highlight_inventory_objects_in_panel(const std::vector& items, LLInventoryPanel *inventory_panel) -{ - if (NULL == inventory_panel) return; - - for (std::vector::const_iterator item_iter = items.begin(); - item_iter != items.end(); - ++item_iter) - { - const LLUUID& item_id = (*item_iter); - if(!highlight_offered_object(item_id)) - { - continue; - } - - LLInventoryObject* item = gInventory.getObject(item_id); - llassert(item); - if (!item) { - continue; - } - - LL_DEBUGS("Inventory_Move") << "Highlighting inventory item: " << item->getName() << ", " << item_id << LL_ENDL; - LLFolderView* fv = inventory_panel->getRootFolder(); - if (fv) - { - LLFolderViewItem* fv_item = inventory_panel->getItemByID(item_id); - if (fv_item) - { - LLFolderViewItem* fv_folder = fv_item->getParentFolder(); - if (fv_folder) - { - // Parent folders can be different in case of 2 consecutive drag and drop - // operations when the second one is started before the first one completes. - LL_DEBUGS("Inventory_Move") << "Open folder: " << fv_folder->getName() << LL_ENDL; - fv_folder->setOpen(true); - if (fv_folder->isSelected()) - { - fv->changeSelection(fv_folder, false); - } - } - fv->changeSelection(fv_item, true); - } - } - } -} - -static LLNotificationFunctorRegistration jgr_1("JoinGroup", join_group_response); -static LLNotificationFunctorRegistration jgr_2("JoinedTooManyGroupsMember", join_group_response); -static LLNotificationFunctorRegistration jgr_3("JoinGroupCanAfford", join_group_response); - - -//----------------------------------------------------------------------------- -// Instant Message -//----------------------------------------------------------------------------- -class LLOpenAgentOffer : public LLInventoryFetchItemsObserver -{ -public: - LLOpenAgentOffer(const LLUUID& object_id, - const std::string& from_name) : - LLInventoryFetchItemsObserver(object_id), - mFromName(from_name) {} - /*virtual*/ void startFetch() - { - for (uuid_vec_t::const_iterator it = mIDs.begin(); it < mIDs.end(); ++it) - { - LLViewerInventoryCategory* cat = gInventory.getCategory(*it); - if (cat) - { - mComplete.push_back((*it)); - } - } - LLInventoryFetchItemsObserver::startFetch(); - } - /*virtual*/ void done() - { - open_inventory_offer(mComplete, mFromName); - gInventory.removeObserver(this); - delete this; - } -private: - std::string mFromName; -}; - -/** - * Class to observe adding of new items moved from the world to user's inventory to select them in inventory. - * - * We can't create it each time items are moved because "drop" event is sent separately for each - * element even while multi-dragging. We have to have the only instance of the observer. See EXT-4347. - */ -class LLViewerInventoryMoveFromWorldObserver : public LLInventoryAddItemByAssetObserver -{ -public: - LLViewerInventoryMoveFromWorldObserver() - : LLInventoryAddItemByAssetObserver() - { - - } - - void setMoveIntoFolderID(const LLUUID& into_folder_uuid) {mMoveIntoFolderID = into_folder_uuid; } - -private: - /*virtual */void onAssetAdded(const LLUUID& asset_id) - { - // Store active Inventory panel. - if (LLInventoryPanel::getActiveInventoryPanel()) - { - mActivePanel = LLInventoryPanel::getActiveInventoryPanel()->getHandle(); - } - - // Store selected items (without destination folder) - mSelectedItems.clear(); - if (LLInventoryPanel::getActiveInventoryPanel()) - { - std::set selection = LLInventoryPanel::getActiveInventoryPanel()->getRootFolder()->getSelectionList(); - for (std::set::iterator it = selection.begin(), end_it = selection.end(); - it != end_it; - ++it) - { - mSelectedItems.insert(static_cast((*it)->getViewModelItem())->getUUID()); - } - } - mSelectedItems.erase(mMoveIntoFolderID); - } - - /** - * Selects added inventory items watched by their Asset UUIDs if selection was not changed since - * all items were started to watch (dropped into a folder). - */ - void done() - { - LLInventoryPanel* active_panel = dynamic_cast(mActivePanel.get()); - - // if selection is not changed since watch started lets hightlight new items. - if (active_panel && !isSelectionChanged()) - { - LL_DEBUGS("Inventory_Move") << "Selecting new items..." << LL_ENDL; - active_panel->clearSelection(); - highlight_inventory_objects_in_panel(mAddedItems, active_panel); - } - } - - /** - * Returns true if selected inventory items were changed since moved inventory items were started to watch. - */ - bool isSelectionChanged() - { - LLInventoryPanel* active_panel = LLInventoryPanel::getActiveInventoryPanel(); - - if (NULL == active_panel) - { - return true; - } - - // get selected items (without destination folder) - selected_items_t selected_items; - - std::set selection = active_panel->getRootFolder()->getSelectionList(); - for (std::set::iterator it = selection.begin(), end_it = selection.end(); - it != end_it; - ++it) - { - selected_items.insert(static_cast((*it)->getViewModelItem())->getUUID()); - } - selected_items.erase(mMoveIntoFolderID); - - // compare stored & current sets of selected items - selected_items_t different_items; - std::set_symmetric_difference(mSelectedItems.begin(), mSelectedItems.end(), - selected_items.begin(), selected_items.end(), std::inserter(different_items, different_items.begin())); - - LL_DEBUGS("Inventory_Move") << "Selected firstly: " << mSelectedItems.size() - << ", now: " << selected_items.size() << ", difference: " << different_items.size() << LL_ENDL; - - return different_items.size() > 0; - } - - LLHandle mActivePanel; - typedef std::set selected_items_t; - selected_items_t mSelectedItems; - - /** - * UUID of FolderViewFolder into which watched items are moved. - * - * Destination FolderViewFolder becomes selected while mouse hovering (when dragged items are dropped). - * - * If mouse is moved out it set unselected and number of selected items is changed - * even if selected items in Inventory stay the same. - * So, it is used to update stored selection list. - * - * @see onAssetAdded() - * @see isSelectionChanged() - */ - LLUUID mMoveIntoFolderID; -}; - -LLViewerInventoryMoveFromWorldObserver* gInventoryMoveObserver = NULL; - -void set_dad_inventory_item(LLInventoryItem* inv_item, const LLUUID& into_folder_uuid) -{ - start_new_inventory_observer(); - - gInventoryMoveObserver->setMoveIntoFolderID(into_folder_uuid); - gInventoryMoveObserver->watchAsset(inv_item->getAssetUUID()); -} - - -/** - * Class to observe moving of items and to select them in inventory. - * - * Used currently for dragging from inbox to regular inventory folders - */ - -class LLViewerInventoryMoveObserver : public LLInventoryObserver -{ -public: - - LLViewerInventoryMoveObserver(const LLUUID& object_id) - : LLInventoryObserver() - , mObjectID(object_id) - { - if (LLInventoryPanel::getActiveInventoryPanel()) - { - mActivePanel = LLInventoryPanel::getActiveInventoryPanel()->getHandle(); - } - } - - virtual ~LLViewerInventoryMoveObserver() {} - virtual void changed(U32 mask); - -private: - LLUUID mObjectID; - LLHandle mActivePanel; - -}; - -void LLViewerInventoryMoveObserver::changed(U32 mask) -{ - LLInventoryPanel* active_panel = dynamic_cast(mActivePanel.get()); - - if (NULL == active_panel) - { - gInventory.removeObserver(this); - return; - } - - if((mask & (LLInventoryObserver::STRUCTURE)) != 0) - { - const std::set& changed_items = gInventory.getChangedIDs(); - - std::set::const_iterator id_it = changed_items.begin(); - std::set::const_iterator id_end = changed_items.end(); - for (;id_it != id_end; ++id_it) - { - if ((*id_it) == mObjectID) - { - active_panel->clearSelection(); - std::vector items; - items.push_back(mObjectID); - highlight_inventory_objects_in_panel(items, active_panel); - active_panel->getRootFolder()->scrollToShowSelection(); - - gInventory.removeObserver(this); - break; - } - } - } -} - -void set_dad_inbox_object(const LLUUID& object_id) -{ - LLViewerInventoryMoveObserver* move_observer = new LLViewerInventoryMoveObserver(object_id); - gInventory.addObserver(move_observer); -} - -//unlike the FetchObserver for AgentOffer, we only make one -//instance of the AddedObserver for TaskOffers -//and it never dies. We do this because we don't know the UUID of -//task offers until they are accepted, so we don't wouldn't -//know what to watch for, so instead we just watch for all additions. -class LLOpenTaskOffer : public LLInventoryAddedObserver -{ -protected: - /*virtual*/ void done() - { - uuid_vec_t added; - for(uuid_set_t::const_iterator it = gInventory.getAddedIDs().begin(); it != gInventory.getAddedIDs().end(); ++it) - { - added.push_back(*it); - } - for (uuid_vec_t::iterator it = added.begin(); it != added.end();) - { - const LLUUID& item_uuid = *it; - bool was_moved = false; - LLInventoryObject* added_object = gInventory.getObject(item_uuid); - if (added_object) - { - // cast to item to get Asset UUID - LLInventoryItem* added_item = dynamic_cast(added_object); - if (added_item) - { - const LLUUID& asset_uuid = added_item->getAssetUUID(); - if (gInventoryMoveObserver->isAssetWatched(asset_uuid)) - { - LL_DEBUGS("Inventory_Move") << "Found asset UUID: " << asset_uuid << LL_ENDL; - was_moved = true; - } - } - } - - if (was_moved) - { - it = added.erase(it); - } - else ++it; - } - - open_inventory_offer(added, ""); - } - }; - -class LLOpenTaskGroupOffer : public LLInventoryAddedObserver -{ -protected: - /*virtual*/ void done() - { - uuid_vec_t added; - for(uuid_set_t::const_iterator it = gInventory.getAddedIDs().begin(); it != gInventory.getAddedIDs().end(); ++it) - { - added.push_back(*it); - } - open_inventory_offer(added, "group_offer"); - gInventory.removeObserver(this); - delete this; - } -}; - -//one global instance to bind them -LLOpenTaskOffer* gNewInventoryObserver=NULL; -class LLNewInventoryHintObserver : public LLInventoryAddedObserver -{ -protected: - /*virtual*/ void done() - { - LLFirstUse::newInventory(); - } -}; - -LLNewInventoryHintObserver* gNewInventoryHintObserver=NULL; - -void start_new_inventory_observer() -{ - if (!gNewInventoryObserver) //task offer observer - { - // Observer is deleted by gInventory - gNewInventoryObserver = new LLOpenTaskOffer; - gInventory.addObserver(gNewInventoryObserver); - } - - if (!gInventoryMoveObserver) //inventory move from the world observer - { - // Observer is deleted by gInventory - gInventoryMoveObserver = new LLViewerInventoryMoveFromWorldObserver; - gInventory.addObserver(gInventoryMoveObserver); - } - - if (!gNewInventoryHintObserver) - { - // Observer is deleted by gInventory - gNewInventoryHintObserver = new LLNewInventoryHintObserver(); - gInventory.addObserver(gNewInventoryHintObserver); - } -} - -class LLDiscardAgentOffer : public LLInventoryFetchItemsObserver -{ - LOG_CLASS(LLDiscardAgentOffer); - -public: - LLDiscardAgentOffer(const LLUUID& folder_id, const LLUUID& object_id) : - LLInventoryFetchItemsObserver(object_id), - mFolderID(folder_id), - mObjectID(object_id) {} - - virtual void done() - { - LL_DEBUGS("Messaging") << "LLDiscardAgentOffer::done()" << LL_ENDL; - - // We're invoked from LLInventoryModel::notifyObservers(). - // If we now try to remove the inventory item, it will cause a nested - // notifyObservers() call, which won't work. - // So defer moving the item to trash until viewer gets idle (in a moment). - // Use removeObject() rather than removeItem() because at this level, - // the object could be either an item or a folder. - LLAppViewer::instance()->addOnIdleCallback(boost::bind(&LLInventoryModel::removeObject, &gInventory, mObjectID)); - gInventory.removeObserver(this); - delete this; - } - -protected: - LLUUID mFolderID; - LLUUID mObjectID; -}; - - -//Returns true if we are OK, false if we are throttled -//Set check_only true if you want to know the throttle status -//without registering a hit -bool check_offer_throttle(const std::string& from_name, bool check_only) -{ - static U32 throttle_count; - static bool throttle_logged; - LLChat chat; - std::string log_message; - - if (!gSavedSettings.getBOOL("ShowNewInventory")) - return false; - - if (check_only) - { - return gThrottleTimer.hasExpired(); - } - - if(gThrottleTimer.checkExpirationAndReset(OFFER_THROTTLE_TIME)) - { - LL_DEBUGS("Messaging") << "Throttle Expired" << LL_ENDL; - throttle_count=1; - throttle_logged=false; - return true; - } - else //has not expired - { - LL_DEBUGS("Messaging") << "Throttle Not Expired, Count: " << throttle_count << LL_ENDL; - // When downloading the initial inventory we get a lot of new items - // coming in and can't tell that from spam. - if (LLStartUp::getStartupState() >= STATE_STARTED - && throttle_count >= OFFER_THROTTLE_MAX_COUNT) - { - if (!throttle_logged) - { - // Use the name of the last item giver, who is probably the person - // spamming you. - - LLStringUtil::format_map_t arg; - std::string log_msg; - std::ostringstream time ; - time<getSecondLifeTitle(); - arg["TIME"] = time.str(); - - if (!from_name.empty()) - { - arg["FROM_NAME"] = from_name; - log_msg = LLTrans::getString("ItemsComingInTooFastFrom", arg); - } - else - { - log_msg = LLTrans::getString("ItemsComingInTooFast", arg); - } - - //this is kinda important, so actually put it on screen - LLSD args; - args["MESSAGE"] = log_msg; - LLNotificationsUtil::add("SystemMessage", args); - - throttle_logged=true; - } - return false; - } - else - { - throttle_count++; - return true; - } - } -} - -// Return "true" if we have a preview method for that asset type, "false" otherwise -bool check_asset_previewable(const LLAssetType::EType asset_type) -{ - return (asset_type == LLAssetType::AT_NOTECARD) || - (asset_type == LLAssetType::AT_LANDMARK) || - (asset_type == LLAssetType::AT_TEXTURE) || - (asset_type == LLAssetType::AT_ANIMATION) || - (asset_type == LLAssetType::AT_SCRIPT) || - (asset_type == LLAssetType::AT_SOUND) || - (asset_type == LLAssetType::AT_MATERIAL); -} - -void open_inventory_offer(const uuid_vec_t& objects, const std::string& from_name) -{ - for (uuid_vec_t::const_iterator obj_iter = objects.begin(); - obj_iter != objects.end(); - ++obj_iter) - { - const LLUUID& obj_id = (*obj_iter); - if(!highlight_offered_object(obj_id)) - { - const LLViewerInventoryCategory *parent = gInventory.getFirstNondefaultParent(obj_id); - if (parent && (parent->getPreferredType() == LLFolderType::FT_TRASH)) - { - gInventory.checkTrashOverflow(); - } - continue; - } - - const LLInventoryObject *obj = gInventory.getObject(obj_id); - if (!obj) - { - LL_WARNS() << "Cannot find object [ itemID:" << obj_id << " ] to open." << LL_ENDL; - continue; - } - - const LLAssetType::EType asset_type = obj->getActualType(); - - // Either an inventory item or a category. - const LLInventoryItem* item = dynamic_cast(obj); - if (item && check_asset_previewable(asset_type)) - { - //////////////////////////////////////////////////////////////////////////////// - // Special handling for various types. - if (check_offer_throttle(from_name, false)) // If we are throttled, don't display - { - LL_DEBUGS("Messaging") << "Highlighting inventory item: " << item->getUUID() << LL_ENDL; - // If we opened this ourselves, focus it - const bool take_focus = from_name.empty() ? TAKE_FOCUS_YES : TAKE_FOCUS_NO; - switch(asset_type) - { - case LLAssetType::AT_NOTECARD: - { - LLFloaterReg::showInstance("preview_notecard", LLSD(obj_id), take_focus); - break; - } - case LLAssetType::AT_LANDMARK: - { - LLInventoryCategory* parent_folder = gInventory.getCategory(item->getParentUUID()); - if ("inventory_handler" == from_name) - { - LLFloaterSidePanelContainer::showPanel("places", LLSD().with("type", "landmark").with("id", item->getUUID())); - } - else if("group_offer" == from_name) - { - // "group_offer" is passed by LLOpenTaskGroupOffer - // Notification about added landmark will be generated under the "from_name.empty()" called from LLOpenTaskOffer::done(). - LLSD args; - args["type"] = "landmark"; - args["id"] = obj_id; - LLFloaterSidePanelContainer::showPanel("places", args); - - continue; - } - else if(from_name.empty()) - { - std::string folder_name; - if (parent_folder) - { - // Localize folder name. - // *TODO: share this code? - folder_name = parent_folder->getName(); - if (LLFolderType::lookupIsProtectedType(parent_folder->getPreferredType())) - { - LLTrans::findString(folder_name, "InvFolder " + folder_name); - } - } - else - { - folder_name = LLTrans::getString("Unknown"); - } - - // we receive a message from LLOpenTaskOffer, it mean that new landmark has been added. - LLSD args; - args["LANDMARK_NAME"] = item->getName(); - args["FOLDER_NAME"] = folder_name; - LLNotificationsUtil::add("LandmarkCreated", args); - } - } - break; - case LLAssetType::AT_TEXTURE: - { - LLFloaterReg::showInstance("preview_texture", LLSD(obj_id), take_focus); - break; - } - case LLAssetType::AT_ANIMATION: - LLFloaterReg::showInstance("preview_anim", LLSD(obj_id), take_focus); - break; - case LLAssetType::AT_SCRIPT: - LLFloaterReg::showInstance("preview_script", LLSD(obj_id), take_focus); - break; - case LLAssetType::AT_SOUND: - LLFloaterReg::showInstance("preview_sound", LLSD(obj_id), take_focus); - break; - case LLAssetType::AT_MATERIAL: - // Explicitly do nothing -- we don't want to open the material editor every time you add a material to inventory - break; - default: - LL_DEBUGS("Messaging") << "No preview method for previewable asset type : " << LLAssetType::lookupHumanReadable(asset_type) << LL_ENDL; - break; - } - } - } - - //////////////////////////////////////////////////////////////////////////////// - static LLUICachedControl find_original_new_floater("FindOriginalOpenWindow", false); - //show in a new single-folder window - if(find_original_new_floater && !from_name.empty()) - { - const LLInventoryObject *obj = gInventory.getObject(obj_id); - if (obj && obj->getParentUUID().notNull()) - { - if (obj->getActualType() == LLAssetType::AT_CATEGORY) - { - LLPanelMainInventory::newFolderWindow(obj_id); - } - else - { - LLPanelMainInventory::newFolderWindow(obj->getParentUUID(), obj_id); - } - } - } - else - { - // Highlight item - bool show_in_inventory = gSavedSettings.get("ShowInInventory"); - bool auto_open = - show_in_inventory && // don't open if ShowInInventory is false - !from_name.empty(); // don't open if it's not from anyone - - // SL-20419 : Don't change active tab if floater is visible - LLFloater* instance = LLFloaterReg::findInstance("inventory"); - bool use_main_panel = instance && instance->getVisible(); - - if (auto_open) - { - LLFloaterReg::showInstance("inventory"); - } - - LLInventoryPanel::openInventoryPanelAndSetSelection(auto_open, obj_id, use_main_panel); - } - } -} - -bool highlight_offered_object(const LLUUID& obj_id) -{ - const LLInventoryObject* obj = gInventory.getObject(obj_id); - if(!obj) - { - LL_WARNS("Messaging") << "Unable to show inventory item: " << obj_id << LL_ENDL; - return false; - } - - //////////////////////////////////////////////////////////////////////////////// - // Don't highlight if it's in certain "quiet" folders which don't need UI - // notification (e.g. trash, cof, lost-and-found). - if(!gAgent.getAFK()) - { - const LLViewerInventoryCategory *parent = gInventory.getFirstNondefaultParent(obj_id); - if (parent) - { - const LLFolderType::EType parent_type = parent->getPreferredType(); - if (LLViewerFolderType::lookupIsQuietType(parent_type)) - { - return false; - } - } - } - - if (obj->getType() == LLAssetType::AT_LANDMARK) - { - LLFloaterCreateLandmark *floater = LLFloaterReg::findTypedInstance("add_landmark"); - if (floater && floater->getItem() && floater->getItem()->getUUID() == obj_id) - { - // LLFloaterCreateLandmark is supposed to handle this, - // keep landmark creation floater at the front - return false; - } - } - - return true; -} - -void inventory_offer_mute_callback(const LLUUID& blocked_id, - const std::string& full_name, - bool is_group) -{ - // *NOTE: blocks owner if the offer came from an object - LLMute::EType mute_type = is_group ? LLMute::GROUP : LLMute::AGENT; - - LLMute mute(blocked_id, full_name, mute_type); - if (LLMuteList::getInstance()->add(mute)) - { - LLPanelBlockedList::showPanelAndSelect(blocked_id); - } - - // purge the message queue of any previously queued inventory offers from the same source. - class OfferMatcher : public LLNotificationsUI::LLScreenChannel::Matcher - { - public: - OfferMatcher(const LLUUID& to_block) : blocked_id(to_block) {} - bool matches(const LLNotificationPtr notification) const - { - if(notification->getName() == "ObjectGiveItem" - || notification->getName() == "OwnObjectGiveItem" - || notification->getName() == "UserGiveItem") - { - return (notification->getPayload()["from_id"].asUUID() == blocked_id); - } - return false; - } - private: - const LLUUID& blocked_id; - }; - - LLNotificationsUI::LLChannelManager::getInstance()->killToastsFromChannel( - LLNotificationsUI::NOTIFICATION_CHANNEL_UUID, - OfferMatcher(blocked_id)); -} - - -void inventory_offer_mute_avatar_callback(const LLUUID& blocked_id, - const LLAvatarName& av_name) -{ - inventory_offer_mute_callback(blocked_id, av_name.getUserName(), false); -} - - -std::string LLOfferInfo::mResponderType = "offer_info"; - -LLOfferInfo::LLOfferInfo() - : LLNotificationResponderInterface() - , mFromGroup(false) - , mFromObject(false) - , mIM(IM_NOTHING_SPECIAL) - , mType(LLAssetType::AT_NONE) - , mPersist(false) -{ -} - -LLOfferInfo::LLOfferInfo(const LLSD& sd) -{ - mIM = (EInstantMessage)sd["im_type"].asInteger(); - mFromID = sd["from_id"].asUUID(); - mFromGroup = sd["from_group"].asBoolean(); - mFromObject = sd["from_object"].asBoolean(); - mTransactionID = sd["transaction_id"].asUUID(); - mFolderID = sd["folder_id"].asUUID(); - mObjectID = sd["object_id"].asUUID(); - mType = LLAssetType::lookup(sd["type"].asString().c_str()); - mFromName = sd["from_name"].asString(); - mDesc = sd["description"].asString(); - mHost = LLHost(sd["sender"].asString()); - mPersist = sd["persist"].asBoolean(); -} - -LLOfferInfo::LLOfferInfo(const LLOfferInfo& info) -{ - mIM = info.mIM; - mFromID = info.mFromID; - mFromGroup = info.mFromGroup; - mFromObject = info.mFromObject; - mTransactionID = info.mTransactionID; - mFolderID = info.mFolderID; - mObjectID = info.mObjectID; - mType = info.mType; - mFromName = info.mFromName; - mDesc = info.mDesc; - mHost = info.mHost; - mPersist = info.mPersist; -} - -LLSD LLOfferInfo::asLLSD() -{ - LLSD sd; - sd["responder_type"] = mResponderType; - sd["im_type"] = mIM; - sd["from_id"] = mFromID; - sd["from_group"] = mFromGroup; - sd["from_object"] = mFromObject; - sd["transaction_id"] = mTransactionID; - sd["folder_id"] = mFolderID; - sd["object_id"] = mObjectID; - sd["type"] = LLAssetType::lookup(mType); - sd["from_name"] = mFromName; - sd["description"] = mDesc; - sd["sender"] = mHost.getIPandPort(); - sd["persist"] = mPersist; - return sd; -} - -void LLOfferInfo::fromLLSD(const LLSD& params) -{ - *this = params; -} - -void LLOfferInfo::sendReceiveResponse(bool accept, const LLUUID &destination_folder_id) -{ - if(IM_INVENTORY_OFFERED == mIM) - { - // add buddy to recent people list - LLRecentPeople::instance().add(mFromID); - } - - if (mTransactionID.isNull()) - { - // Not provided, message won't work - return; - } - - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_ImprovedInstantMessage); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_MessageBlock); - msg->addBOOLFast(_PREHASH_FromGroup, false); - msg->addUUIDFast(_PREHASH_ToAgentID, mFromID); - msg->addU8Fast(_PREHASH_Offline, IM_ONLINE); - msg->addUUIDFast(_PREHASH_ID, mTransactionID); - msg->addU32Fast(_PREHASH_Timestamp, NO_TIMESTAMP); // no timestamp necessary - std::string name; - LLAgentUI::buildFullname(name); - msg->addStringFast(_PREHASH_FromAgentName, name); - msg->addStringFast(_PREHASH_Message, ""); - msg->addU32Fast(_PREHASH_ParentEstateID, 0); - msg->addUUIDFast(_PREHASH_RegionID, LLUUID::null); - msg->addVector3Fast(_PREHASH_Position, gAgent.getPositionAgent()); - - // ACCEPT. The math for the dialog works, because the accept - // for inventory_offered, task_inventory_offer or - // group_notice_inventory is 1 greater than the offer integer value. - // Generates IM_INVENTORY_ACCEPTED, IM_TASK_INVENTORY_ACCEPTED, - // or IM_GROUP_NOTICE_INVENTORY_ACCEPTED - // Decline for inventory_offered, task_inventory_offer or - // group_notice_inventory is 2 greater than the offer integer value. - - EInstantMessage im = mIM; - if (mIM == IM_GROUP_NOTICE_REQUESTED) - { - // Request has no responder dialogs - im = IM_GROUP_NOTICE; - } - - if (accept) - { - msg->addU8Fast(_PREHASH_Dialog, (U8)(im + 1)); - msg->addBinaryDataFast(_PREHASH_BinaryBucket, &(destination_folder_id.mData), - sizeof(destination_folder_id.mData)); - } - else - { - msg->addU8Fast(_PREHASH_Dialog, (U8)(im + 2)); - msg->addBinaryDataFast(_PREHASH_BinaryBucket, EMPTY_BINARY_BUCKET, EMPTY_BINARY_BUCKET_SIZE); - } - // send the message - msg->sendReliable(mHost); - - // transaction id is usable only once - // Note: a bit of a hack, clicking group notice attachment will not close notice - // so we reset no longer usable transaction id to know not to send message again - // Once capabilities for responses will be implemented LLOfferInfo will have to - // remember that it already responded in another way and ignore IOR_DECLINE - mTransactionID.setNull(); -} - -void LLOfferInfo::handleRespond(const LLSD& notification, const LLSD& response) -{ - initRespondFunctionMap(); - - const std::string name = notification["name"].asString(); - if(mRespondFunctions.find(name) == mRespondFunctions.end()) - { - LL_WARNS() << "Unexpected notification name : " << name << LL_ENDL; - llassert(!"Unexpected notification name"); - return; - } - - mRespondFunctions[name](notification, response); -} - -bool LLOfferInfo::inventory_offer_callback(const LLSD& notification, const LLSD& response) -{ - LLChat chat; - std::string log_message; - S32 button = LLNotificationsUtil::getSelectedOption(notification, response); - - LLInventoryObserver* opener = NULL; - LLViewerInventoryCategory* catp = NULL; - catp = (LLViewerInventoryCategory*)gInventory.getCategory(mObjectID); - LLViewerInventoryItem* itemp = NULL; - if(!catp) - { - itemp = (LLViewerInventoryItem*)gInventory.getItem(mObjectID); - } - - LLNotificationPtr notification_ptr = LLNotifications::instance().find(notification["id"].asUUID()); - - // For muting, we need to add the mute, then decline the offer. - // This must be done here because: - // * callback may be called immediately, - // * adding the mute sends a message, - // * we can't build two messages at once. - if (IOR_MUTE == button) // Block - { - if (notification_ptr != NULL) - { - if (mFromGroup) - { - gCacheName->getGroup(mFromID, boost::bind(&inventory_offer_mute_callback, _1, _2, _3)); - } - else - { - LLAvatarNameCache::get(mFromID, boost::bind(&inventory_offer_mute_avatar_callback, _1, _2)); - } - } - } - - std::string from_string; // Used in the pop-up. - std::string chatHistory_string; // Used in chat history. - - // TODO: when task inventory offers can also be handled the new way, migrate the code that sets these strings here: - from_string = chatHistory_string = mFromName; - - // accept goes to proper folder, decline gets accepted to trash, muted gets declined - bool accept_to_trash = true; - - LLNotificationFormPtr modified_form(notification_ptr ? new LLNotificationForm(*notification_ptr->getForm()) : new LLNotificationForm()); - - switch(button) - { - case IOR_SHOW: - // we will want to open this item when it comes back. - LL_DEBUGS("Messaging") << "Initializing an opener for tid: " << mTransactionID - << LL_ENDL; - switch (mIM) - { - case IM_INVENTORY_OFFERED: - { - // This is an offer from an agent. In this case, the back - // end has already copied the items into your inventory, - // so we can fetch it out of our inventory. - if (gSavedSettings.getBOOL("ShowOfferedInventory")) - { - LLOpenAgentOffer* open_agent_offer = new LLOpenAgentOffer(mObjectID, from_string); - open_agent_offer->startFetch(); - if(catp || (itemp && itemp->isFinished())) - { - open_agent_offer->done(); - } - else - { - opener = open_agent_offer; - } - } - } - break; - case IM_GROUP_NOTICE: - case IM_GROUP_NOTICE_REQUESTED: - opener = new LLOpenTaskGroupOffer; - sendReceiveResponse(true, mFolderID); - break; - case IM_TASK_INVENTORY_OFFERED: - // This is an offer from a task or group. - // We don't use a new instance of an opener - // We instead use the singular observer gOpenTaskOffer - // Since it already exists, we don't need to actually do anything - break; - default: - LL_WARNS("Messaging") << "inventory_offer_callback: unknown offer type" << LL_ENDL; - break; - } - - if (modified_form != NULL) - { - modified_form->setElementEnabled("Show", false); - } - break; - // end switch (mIM) - - case IOR_ACCEPT: - //don't spam them if they are getting flooded - if (check_offer_throttle(mFromName, true)) - { - log_message = "" + chatHistory_string + " " + LLTrans::getString("InvOfferGaveYou") + " " + getSanitizedDescription() + LLTrans::getString("."); - LLSD args; - args["MESSAGE"] = log_message; - LLNotificationsUtil::add("SystemMessageTip", args); - } - - break; - - case IOR_MUTE: - if (modified_form != NULL) - { - modified_form->setElementEnabled("Mute", false); - } - accept_to_trash = false; // for notices, but IOR_MUTE normally doesn't happen for notices - // MUTE falls through to decline - case IOR_DECLINE: - { - { - LLStringUtil::format_map_t log_message_args; - log_message_args["DESC"] = mDesc; - log_message_args["NAME"] = mFromName; - log_message = LLTrans::getString("InvOfferDecline", log_message_args); - } - chat.mText = log_message; - if( LLMuteList::getInstance()->isMuted(mFromID ) && ! LLMuteList::isLinden(mFromName) ) // muting for SL-42269 - { - chat.mMuted = true; - accept_to_trash = false; // will send decline message - } - - // *NOTE dzaporozhan - // Disabled logging to old chat floater to fix crash in group notices - EXT-4149 - // LLFloaterChat::addChatHistory(chat); - - if (mObjectID.notNull()) //make sure we can discard - { - LLDiscardAgentOffer* discard_agent_offer = new LLDiscardAgentOffer(mFolderID, mObjectID); - discard_agent_offer->startFetch(); - if ((catp && gInventory.isCategoryComplete(mObjectID)) || (itemp && itemp->isFinished())) - { - discard_agent_offer->done(); - } - else - { - opener = discard_agent_offer; - } - } - else if (mIM == IM_GROUP_NOTICE) - { - // group notice needs to request object to trash so that user will see it later - // Note: muted agent offers go to trash, not sure if we should do same for notices - LLUUID trash = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - sendReceiveResponse(accept_to_trash, trash); - } - - if (modified_form != NULL) - { - modified_form->setElementEnabled("Show", false); - modified_form->setElementEnabled("Discard", false); - } - - break; - } - default: - // close button probably - // In case of agent offers item has already been fetched and is in your inventory, we simply won't highlight it - // OR delete it if the notification gets killed, since we don't want that to be a vector for - // losing inventory offers. - if (mIM == IM_GROUP_NOTICE) - { - LLUUID trash = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - sendReceiveResponse(true, trash); - } - break; - } - - if(opener) - { - gInventory.addObserver(opener); - } - - if(!mPersist) - { - delete this; - } - - return false; -} - -bool LLOfferInfo::inventory_task_offer_callback(const LLSD& notification, const LLSD& response) -{ - LLChat chat; - std::string log_message; - S32 button = LLNotification::getSelectedOption(notification, response); - - // For muting, we need to add the mute, then decline the offer. - // This must be done here because: - // * callback may be called immediately, - // * adding the mute sends a message, - // * we can't build two messages at once. - if (2 == button) - { - LLNotificationPtr notification_ptr = LLNotifications::instance().find(notification["id"].asUUID()); - - llassert(notification_ptr != NULL); - if (notification_ptr != NULL) - { - if (mFromGroup) - { - gCacheName->getGroup(mFromID, boost::bind(&inventory_offer_mute_callback, _1, _2, _3)); - } - else - { - LLAvatarNameCache::get(mFromID, boost::bind(&inventory_offer_mute_avatar_callback, _1, _2)); - } - } - } - - std::string from_string; // Used in the pop-up. - std::string chatHistory_string; // Used in chat history. - - if (mFromObject) - { - std::string quot = LLTrans::getString("'"); - if (mFromGroup) - { - std::string group_name; - if (gCacheName->getGroupName(mFromID, group_name)) - { - from_string = LLTrans::getString("InvOfferAnObjectNamed") + " " + - quot + mFromName + quot + " " + - LLTrans::getString("InvOfferOwnedByGroup") + " " + - quot + group_name + quot; - chatHistory_string = mFromName + " " + - LLTrans::getString("InvOfferOwnedByGroup") + " " + - quot + group_name + quot; - } - else - { - from_string = LLTrans::getString("InvOfferAnObjectNamed") + " " + - quot + mFromName + quot + " " + - LLTrans::getString("InvOfferOwnedByUnknownGroup"); - chatHistory_string = mFromName + " " + - LLTrans::getString("InvOfferOwnedByUnknownGroup"); - } - } - else - { - LLAvatarName av_name; - if (LLAvatarNameCache::get(mFromID, &av_name)) - { - from_string = LLTrans::getString("InvOfferAnObjectNamed") + " " + - quot + mFromName + quot + " " + - LLTrans::getString("InvOfferOwnedBy") + " " + av_name.getUserName(); - chatHistory_string = mFromName + " " + - LLTrans::getString("InvOfferOwnedBy") + " " + av_name.getUserName(); - } - else - { - from_string = LLTrans::getString("InvOfferAnObjectNamed") + " " + - quot + mFromName + quot + " " + - LLTrans::getString("InvOfferOwnedByUnknownUser"); - chatHistory_string = mFromName + " " + - LLTrans::getString("InvOfferOwnedByUnknownUser"); - } - } - } - else - { - from_string = chatHistory_string = mFromName; - } - - LLUUID destination; - bool accept = true; - - // If user accepted, accept to proper folder, if user discarded, accept to trash. - switch(button) - { - case IOR_ACCEPT: - destination = mFolderID; - //don't spam user if flooded - if (check_offer_throttle(mFromName, true)) - { - log_message = "" + chatHistory_string + " " + LLTrans::getString("InvOfferGaveYou") + " " + getSanitizedDescription() + LLTrans::getString("."); - LLSD args; - args["MESSAGE"] = log_message; - LLNotificationsUtil::add("SystemMessageTip", args); - } - break; - case IOR_MUTE: - // MUTE falls through to decline - accept = false; - case IOR_DECLINE: - default: - // close button probably (or any of the fall-throughs from above) - destination = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - if (accept && LLMuteList::getInstance()->isMuted(mFromID, mFromName)) - { - // Note: muted offers are usually declined automatically, - // but user can mute object after receiving message - accept = false; - } - break; - } - - sendReceiveResponse(accept, destination); - - if(!mPersist) - { - delete this; - } - return false; -} - -std::string LLOfferInfo::getSanitizedDescription() -{ - // currently we get description from server as: 'Object' ( Location ) - // object name shouldn't be shown as a hyperlink - std::string description = mDesc; - - std::size_t start = mDesc.find_first_of("'"); - std::size_t end = mDesc.find_last_of("'"); - if ((start != std::string::npos) && (end != std::string::npos)) - { - description.insert(start, ""); - description.insert(end + 8, ""); - } - return description; -} - - -void LLOfferInfo::initRespondFunctionMap() -{ - if(mRespondFunctions.empty()) - { - mRespondFunctions["ObjectGiveItem"] = boost::bind(&LLOfferInfo::inventory_task_offer_callback, this, _1, _2); - mRespondFunctions["OwnObjectGiveItem"] = boost::bind(&LLOfferInfo::inventory_task_offer_callback, this, _1, _2); - mRespondFunctions["UserGiveItem"] = boost::bind(&LLOfferInfo::inventory_offer_callback, this, _1, _2); - } -} - -bool lure_callback(const LLSD& notification, const LLSD& response) -{ - S32 option = 0; - if (response.isInteger()) - { - option = response.asInteger(); - } - else - { - option = LLNotificationsUtil::getSelectedOption(notification, response); - } - - LLUUID from_id = notification["payload"]["from_id"].asUUID(); - LLUUID lure_id = notification["payload"]["lure_id"].asUUID(); - bool godlike = notification["payload"]["godlike"].asBoolean(); - - switch(option) - { - case 0: - { - // accept - gAgent.teleportViaLure(lure_id, godlike); - } - break; - case 1: - default: - // decline - send_simple_im(from_id, - LLStringUtil::null, - IM_LURE_DECLINED, - lure_id); - break; - } - - LLNotificationPtr notification_ptr = LLNotifications::instance().find(notification["id"].asUUID()); - - if (notification_ptr) - { - LLNotificationFormPtr modified_form(new LLNotificationForm(*notification_ptr->getForm())); - modified_form->setElementEnabled("Teleport", false); - modified_form->setElementEnabled("Cancel", false); - notification_ptr->updateForm(modified_form); - notification_ptr->repost(); - } - - return false; -} -static LLNotificationFunctorRegistration lure_callback_reg("TeleportOffered", lure_callback); - -bool mature_lure_callback(const LLSD& notification, const LLSD& response) -{ - S32 option = 0; - if (response.isInteger()) - { - option = response.asInteger(); - } - else - { - option = LLNotificationsUtil::getSelectedOption(notification, response); - } - - LLUUID from_id = notification["payload"]["from_id"].asUUID(); - LLUUID lure_id = notification["payload"]["lure_id"].asUUID(); - bool godlike = notification["payload"]["godlike"].asBoolean(); - U8 region_access = static_cast(notification["payload"]["region_maturity"].asInteger()); - - switch(option) - { - case 0: - { - // accept - gSavedSettings.setU32("PreferredMaturity", static_cast(region_access)); - gAgent.setMaturityRatingChangeDuringTeleport(region_access); - gAgent.teleportViaLure(lure_id, godlike); - } - break; - case 1: - default: - // decline - send_simple_im(from_id, - LLStringUtil::null, - IM_LURE_DECLINED, - lure_id); - break; - } - return false; -} -static LLNotificationFunctorRegistration mature_lure_callback_reg("TeleportOffered_MaturityExceeded", mature_lure_callback); - -bool goto_url_callback(const LLSD& notification, const LLSD& response) -{ - std::string url = notification["payload"]["url"].asString(); - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if(1 == option) - { - LLWeb::loadURL(url); - } - return false; -} -static LLNotificationFunctorRegistration goto_url_callback_reg("GotoURL", goto_url_callback); - -bool inspect_remote_object_callback(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (0 == option) - { - LLFloaterReg::showInstance("inspect_remote_object", notification["payload"]); - } - return false; -} -static LLNotificationFunctorRegistration inspect_remote_object_callback_reg("ServerObjectMessage", inspect_remote_object_callback); - -class LLPostponedServerObjectNotification: public LLPostponedNotification -{ -protected: - /* virtual */ - void modifyNotificationParams() - { - LLSD payload = mParams.payload; - mParams.payload = payload; - } -}; - -void process_improved_im(LLMessageSystem *msg, void **user_data) -{ - LL_PROFILE_ZONE_SCOPED; - - LLUUID from_id; - bool from_group; - LLUUID to_id; - U8 offline; - U8 d = 0; - LLUUID session_id; - U32 timestamp; - std::string agentName; - std::string message; - U32 parent_estate_id = 0; - LLUUID region_id; - LLVector3 position; - U8 binary_bucket[MTUBYTES]; - S32 binary_bucket_size; - - // *TODO: Translate - need to fix the full name to first/last (maybe) - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, from_id); - msg->getBOOLFast(_PREHASH_MessageBlock, _PREHASH_FromGroup, from_group); - msg->getUUIDFast(_PREHASH_MessageBlock, _PREHASH_ToAgentID, to_id); - msg->getU8Fast(_PREHASH_MessageBlock, _PREHASH_Offline, offline); - msg->getU8Fast(_PREHASH_MessageBlock, _PREHASH_Dialog, d); - msg->getUUIDFast(_PREHASH_MessageBlock, _PREHASH_ID, session_id); - msg->getU32Fast(_PREHASH_MessageBlock, _PREHASH_Timestamp, timestamp); - //msg->getData("MessageBlock", "Count", &count); - msg->getStringFast(_PREHASH_MessageBlock, _PREHASH_FromAgentName, agentName); - msg->getStringFast(_PREHASH_MessageBlock, _PREHASH_Message, message); - msg->getU32Fast(_PREHASH_MessageBlock, _PREHASH_ParentEstateID, parent_estate_id); - msg->getUUIDFast(_PREHASH_MessageBlock, _PREHASH_RegionID, region_id); - msg->getVector3Fast(_PREHASH_MessageBlock, _PREHASH_Position, position); - msg->getBinaryDataFast(_PREHASH_MessageBlock, _PREHASH_BinaryBucket, binary_bucket, 0, 0, MTUBYTES); - binary_bucket_size = msg->getSizeFast(_PREHASH_MessageBlock, _PREHASH_BinaryBucket); - EInstantMessage dialog = (EInstantMessage)d; - LLHost sender = msg->getSender(); - - LLIMProcessing::processNewMessage(from_id, - from_group, - to_id, - offline, - dialog, - session_id, - timestamp, - agentName, - message, - parent_estate_id, - region_id, - position, - binary_bucket, - binary_bucket_size, - sender); -} - -void send_do_not_disturb_message (LLMessageSystem* msg, const LLUUID& from_id, const LLUUID& session_id) -{ - if (gAgent.isDoNotDisturb()) - { - std::string my_name; - LLAgentUI::buildFullname(my_name); - std::string response = gSavedPerAccountSettings.getString("DoNotDisturbModeResponse"); - pack_instant_message( - msg, - gAgent.getID(), - false, - gAgent.getSessionID(), - from_id, - my_name, - response, - IM_ONLINE, - IM_DO_NOT_DISTURB_AUTO_RESPONSE, - session_id); - gAgent.sendReliableMessage(); - } -} - -bool callingcard_offer_callback(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - LLUUID fid; - LLUUID from_id; - LLMessageSystem* msg = gMessageSystem; - switch(option) - { - case 0: - // accept - msg->newMessageFast(_PREHASH_AcceptCallingCard); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_TransactionBlock); - msg->addUUIDFast(_PREHASH_TransactionID, notification["payload"]["transaction_id"].asUUID()); - fid = gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD); - msg->nextBlockFast(_PREHASH_FolderData); - msg->addUUIDFast(_PREHASH_FolderID, fid); - msg->sendReliable(LLHost(notification["payload"]["sender"].asString())); - break; - case 1: - // decline - msg->newMessageFast(_PREHASH_DeclineCallingCard); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_TransactionBlock); - msg->addUUIDFast(_PREHASH_TransactionID, notification["payload"]["transaction_id"].asUUID()); - msg->sendReliable(LLHost(notification["payload"]["sender"].asString())); - send_do_not_disturb_message(msg, notification["payload"]["source_id"].asUUID()); - break; - default: - // close button probably, possibly timed out - break; - } - - return false; -} -static LLNotificationFunctorRegistration callingcard_offer_cb_reg("OfferCallingCard", callingcard_offer_callback); - -void process_offer_callingcard(LLMessageSystem* msg, void**) -{ - // someone has offered to form a friendship - LL_DEBUGS("Messaging") << "callingcard offer" << LL_ENDL; - - LLUUID source_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, source_id); - LLUUID tid; - msg->getUUIDFast(_PREHASH_AgentBlock, _PREHASH_TransactionID, tid); - - LLSD payload; - payload["transaction_id"] = tid; - payload["source_id"] = source_id; - payload["sender"] = msg->getSender().getIPandPort(); - - LLViewerObject* source = gObjectList.findObject(source_id); - LLSD args; - std::string source_name; - if(source && source->isAvatar()) - { - LLNameValue* nvfirst = source->getNVPair("FirstName"); - LLNameValue* nvlast = source->getNVPair("LastName"); - if (nvfirst && nvlast) - { - source_name = LLCacheName::buildFullName( - nvfirst->getString(), nvlast->getString()); - } - } - - if(!source_name.empty()) - { - if (gAgent.isDoNotDisturb() - || LLMuteList::getInstance()->isMuted(source_id, source_name, LLMute::flagTextChat)) - { - // automatically decline offer - LLNotifications::instance().forceResponse(LLNotification::Params("OfferCallingCard").payload(payload), 1); - } - else - { - args["NAME"] = source_name; - LLNotificationsUtil::add("OfferCallingCard", args, payload); - } - } - else - { - LL_WARNS("Messaging") << "Calling card offer from an unknown source." << LL_ENDL; - } -} - -void process_accept_callingcard(LLMessageSystem* msg, void**) -{ - LLNotificationsUtil::add("CallingCardAccepted"); -} - -void process_decline_callingcard(LLMessageSystem* msg, void**) -{ - LLNotificationsUtil::add("CallingCardDeclined"); -} - -void translateSuccess(LLChat chat, LLSD toastArgs, std::string originalMsg, std::string expectLang, std::string translation, const std::string detected_language) -{ - // filter out non-interesting responses - if (!translation.empty() - && ((detected_language.empty()) || (expectLang != detected_language)) - && (LLStringUtil::compareInsensitive(translation, originalMsg) != 0)) - { - chat.mText += " (" + LLTranslate::removeNoTranslateTags(translation) + ")"; - } - - LLTranslate::instance().logSuccess(1); - LLNotificationsUI::LLNotificationManager::instance().onChat(chat, toastArgs); -} - -void translateFailure(LLChat chat, LLSD toastArgs, int status, const std::string err_msg) -{ - std::string msg = LLTrans::getString("TranslationFailed", LLSD().with("[REASON]", err_msg)); - LLStringUtil::replaceString(msg, "\n", " "); // we want one-line error messages - chat.mText += " (" + msg + ")"; - - LLTranslate::instance().logFailure(1); - LLNotificationsUI::LLNotificationManager::instance().onChat(chat, toastArgs); -} - - -void process_chat_from_simulator(LLMessageSystem *msg, void **user_data) -{ - if (gNonInteractive) - { - return; - } - LLChat chat; - std::string mesg; - std::string from_name; - U8 source_temp; - U8 type_temp; - U8 audible_temp; - LLColor4 color(1.0f, 1.0f, 1.0f, 1.0f); - LLUUID from_id; - LLUUID owner_id; - LLViewerObject* chatter; - - msg->getString("ChatData", "FromName", from_name); - - msg->getUUID("ChatData", "SourceID", from_id); - chat.mFromID = from_id; - - // Object owner for objects - msg->getUUID("ChatData", "OwnerID", owner_id); - - msg->getU8Fast(_PREHASH_ChatData, _PREHASH_SourceType, source_temp); - chat.mSourceType = (EChatSourceType)source_temp; - - msg->getU8("ChatData", "ChatType", type_temp); - chat.mChatType = (EChatType)type_temp; - - msg->getU8Fast(_PREHASH_ChatData, _PREHASH_Audible, audible_temp); - chat.mAudible = (EChatAudible)audible_temp; - - chat.mTime = LLFrameTimer::getElapsedSeconds(); - - // IDEVO Correct for new-style "Resident" names - if (chat.mSourceType == CHAT_SOURCE_AGENT) - { - // I don't know if it's OK to change this here, if - // anything downstream does lookups by name, for instance - - LLAvatarName av_name; - if (LLAvatarNameCache::get(from_id, &av_name)) - { - chat.mFromName = av_name.getCompleteName(); - } - else - { - chat.mFromName = LLCacheName::cleanFullName(from_name); - } - } - else - { - // make sure that we don't have an empty or all-whitespace name - LLStringUtil::trim(from_name); - if (from_name.empty()) - { - from_name = LLTrans::getString("Unnamed"); - } - chat.mFromName = from_name; - } - - bool is_do_not_disturb = gAgent.isDoNotDisturb(); - - bool is_muted = false; - bool is_linden = false; - is_muted = LLMuteList::getInstance()->isMuted( - from_id, - from_name, - LLMute::flagTextChat) - || LLMuteList::getInstance()->isMuted(owner_id, LLMute::flagTextChat); - is_linden = chat.mSourceType != CHAT_SOURCE_OBJECT && - LLMuteList::isLinden(from_name); - - if (is_muted && (chat.mSourceType == CHAT_SOURCE_OBJECT)) - { - return; - } - - bool is_audible = (CHAT_AUDIBLE_FULLY == chat.mAudible); - chatter = gObjectList.findObject(from_id); - if (chatter) - { - chat.mPosAgent = chatter->getPositionAgent(); - - // Make swirly things only for talking objects. (not script debug messages, though) - if (chat.mSourceType == CHAT_SOURCE_OBJECT - && chat.mChatType != CHAT_TYPE_DEBUG_MSG - && gSavedSettings.getBOOL("EffectScriptChatParticles") ) - { - LLPointer psc = new LLViewerPartSourceChat(chatter->getPositionAgent()); - psc->setSourceObject(chatter); - psc->setColor(color); - //We set the particles to be owned by the object's owner, - //just in case they should be muted by the mute list - psc->setOwnerUUID(owner_id); - LLViewerPartSim::getInstance()->addPartSource(psc); - } - - // record last audible utterance - if (is_audible - && (is_linden || (!is_muted && !is_do_not_disturb))) - { - if (chat.mChatType != CHAT_TYPE_START - && chat.mChatType != CHAT_TYPE_STOP) - { - gAgent.heardChat(chat.mFromID); - } - } - } - - if (is_audible) - { - //bool visible_in_chat_bubble = false; - - color.setVec(1.f,1.f,1.f,1.f); - msg->getStringFast(_PREHASH_ChatData, _PREHASH_Message, mesg); - - bool ircstyle = false; - - // Look for IRC-style emotes here so chatbubbles work - std::string prefix = mesg.substr(0, 4); - if (prefix == "/me " || prefix == "/me'") - { - ircstyle = true; - } - chat.mText = mesg; - - // Look for the start of typing so we can put "..." in the bubbles. - if (CHAT_TYPE_START == chat.mChatType) - { - LLLocalSpeakerMgr::getInstance()->setSpeakerTyping(from_id, true); - - // Might not have the avatar constructed yet, eg on login. - if (chatter && chatter->isAvatar()) - { - ((LLVOAvatar*)chatter)->startTyping(); - } - return; - } - else if (CHAT_TYPE_STOP == chat.mChatType) - { - LLLocalSpeakerMgr::getInstance()->setSpeakerTyping(from_id, false); - - // Might not have the avatar constructed yet, eg on login. - if (chatter && chatter->isAvatar()) - { - ((LLVOAvatar*)chatter)->stopTyping(); - } - return; - } - - // Look for IRC-style emotes - if (ircstyle) - { - // set CHAT_STYLE_IRC to avoid adding Avatar Name as author of message. See EXT-656 - chat.mChatStyle = CHAT_STYLE_IRC; - - // Do nothing, ircstyle is fixed above for chat bubbles - } - else - { - chat.mText = ""; - switch(chat.mChatType) - { - case CHAT_TYPE_WHISPER: - chat.mText = LLTrans::getString("whisper") + " "; - break; - case CHAT_TYPE_DEBUG_MSG: - case CHAT_TYPE_OWNER: - case CHAT_TYPE_NORMAL: - case CHAT_TYPE_DIRECT: - break; - case CHAT_TYPE_SHOUT: - chat.mText = LLTrans::getString("shout") + " "; - break; - case CHAT_TYPE_START: - case CHAT_TYPE_STOP: - LL_WARNS("Messaging") << "Got chat type start/stop in main chat processing." << LL_ENDL; - break; - default: - LL_WARNS("Messaging") << "Unknown type " << chat.mChatType << " in chat!" << LL_ENDL; - break; - } - - chat.mText += mesg; - } - - // We have a real utterance now, so can stop showing "..." and proceed. - if (chatter && chatter->isAvatar()) - { - LLLocalSpeakerMgr::getInstance()->setSpeakerTyping(from_id, false); - ((LLVOAvatar*)chatter)->stopTyping(); - - if (!is_muted && !is_do_not_disturb) - { - //visible_in_chat_bubble = gSavedSettings.getBOOL("UseChatBubbles"); - std::string formated_msg = ""; - LLViewerChat::formatChatMsg(chat, formated_msg); - LLChat chat_bubble = chat; - chat_bubble.mText = formated_msg; - ((LLVOAvatar*)chatter)->addChat(chat_bubble); - } - } - - if (chatter) - { - chat.mPosAgent = chatter->getPositionAgent(); - } - - // truth table: - // LINDEN BUSY MUTED OWNED_BY_YOU TASK DISPLAY STORE IN HISTORY - // F F F F * Yes Yes - // F F F T * Yes Yes - // F F T F * No No - // F F T T * No No - // F T F F * No Yes - // F T F T * Yes Yes - // F T T F * No No - // F T T T * No No - // T * * * F Yes Yes - - chat.mMuted = is_muted && !is_linden; - - // pass owner_id to chat so that we can display the remote - // object inspect for an object that is chatting with you - LLSD args; - chat.mOwnerID = owner_id; - - LLTranslate::instance().logCharsSeen(mesg.size()); - if (gSavedSettings.getBOOL("TranslateChat") && chat.mSourceType != CHAT_SOURCE_SYSTEM) - { - if (chat.mChatStyle == CHAT_STYLE_IRC) - { - mesg = mesg.substr(4, std::string::npos); - } - const std::string from_lang = ""; // leave empty to trigger autodetect - const std::string to_lang = LLTranslate::getTranslateLanguage(); - - LLTranslate::instance().logCharsSent(mesg.size()); - LLTranslate::translateMessage(from_lang, to_lang, mesg, - boost::bind(&translateSuccess, chat, args, mesg, from_lang, _1, _2), - boost::bind(&translateFailure, chat, args, _1, _2)); - - } - else - { - LLNotificationsUI::LLNotificationManager::instance().onChat(chat, args); - } - - // don't call notification for debug messages from not owned objects - if (chat.mChatType == CHAT_TYPE_DEBUG_MSG) - { - if (gAgentID != chat.mOwnerID) - { - return; - } - } - - if (mesg != "") - { - LLSD msg_notify = LLSD(LLSD::emptyMap()); - msg_notify["session_id"] = LLUUID(); - msg_notify["from_id"] = chat.mFromID; - msg_notify["source_type"] = chat.mSourceType; - on_new_message(msg_notify); - } - - } -} - - -// Simulator we're on is informing the viewer that the agent -// is starting to teleport (perhaps to another sim, perhaps to the -// same sim). If we initiated the teleport process by sending some kind -// of TeleportRequest, then this info is redundant, but if the sim -// initiated the teleport (via a script call, being killed, etc.) -// then this info is news to us. -void process_teleport_start(LLMessageSystem *msg, void**) -{ - // on teleport, don't tell them about destination guide anymore - LLFirstUse::notUsingDestinationGuide(false); - U32 teleport_flags = 0x0; - msg->getU32("Info", "TeleportFlags", teleport_flags); - - if (gAgent.getTeleportState() == LLAgent::TELEPORT_MOVING) - { - // Race condition? - LL_WARNS("Messaging") << "Got TeleportStart, but teleport already in progress. TeleportFlags=" << teleport_flags << LL_ENDL; - } - - LL_DEBUGS("Messaging") << "Got TeleportStart with TeleportFlags=" << teleport_flags << ". gTeleportDisplay: " << gTeleportDisplay << ", gAgent.mTeleportState: " << gAgent.getTeleportState() << LL_ENDL; - - // *NOTE: The server sends two StartTeleport packets when you are teleporting to a LM - LLViewerMessage::getInstance()->mTeleportStartedSignal(); - - if (teleport_flags & TELEPORT_FLAGS_DISABLE_CANCEL) - { - gViewerWindow->setProgressCancelButtonVisible(false); - } - else - { - gViewerWindow->setProgressCancelButtonVisible(true, LLTrans::getString("Cancel")); - } - - // Freeze the UI and show progress bar - // Note: could add data here to differentiate between normal teleport and death. - - if( gAgent.getTeleportState() == LLAgent::TELEPORT_NONE ) - { - gTeleportDisplay = true; - gAgent.setTeleportState( LLAgent::TELEPORT_START ); - make_ui_sound("UISndTeleportOut"); - - LL_INFOS("Messaging") << "Teleport initiated by remote TeleportStart message with TeleportFlags: " << teleport_flags << LL_ENDL; - - // Don't call LLFirstUse::useTeleport here because this could be - // due to being killed, which would send you home, not to a Telehub - } -} - -boost::signals2::connection LLViewerMessage::setTeleportStartedCallback(teleport_started_callback_t cb) -{ - return mTeleportStartedSignal.connect(cb); -} - -void process_teleport_progress(LLMessageSystem* msg, void**) -{ - LLUUID agent_id; - msg->getUUID("AgentData", "AgentID", agent_id); - if((gAgent.getID() != agent_id) - || (gAgent.getTeleportState() == LLAgent::TELEPORT_NONE)) - { - LL_WARNS("Messaging") << "Unexpected teleport progress message." << LL_ENDL; - return; - } - U32 teleport_flags = 0x0; - msg->getU32("Info", "TeleportFlags", teleport_flags); - if (teleport_flags & TELEPORT_FLAGS_DISABLE_CANCEL) - { - gViewerWindow->setProgressCancelButtonVisible(false); - } - else - { - gViewerWindow->setProgressCancelButtonVisible(true, LLTrans::getString("Cancel")); - } - std::string buffer; - msg->getString("Info", "Message", buffer); - LL_DEBUGS("Messaging") << "teleport progress: " << buffer << " flags: " << teleport_flags << LL_ENDL; - - //Sorta hacky...default to using simulator raw messages - //if we don't find the coresponding mapping in our progress mappings - std::string message = buffer; - - if (LLAgent::sTeleportProgressMessages.find(buffer) != - LLAgent::sTeleportProgressMessages.end() ) - { - message = LLAgent::sTeleportProgressMessages[buffer]; - } - - gAgent.setTeleportMessage(LLAgent::sTeleportProgressMessages[message]); -} - -class LLFetchInWelcomeArea : public LLInventoryFetchDescendentsObserver -{ -public: - LLFetchInWelcomeArea(const uuid_vec_t &ids) : - LLInventoryFetchDescendentsObserver(ids) - {} - virtual void done() - { - LLIsType is_landmark(LLAssetType::AT_LANDMARK); - LLIsType is_card(LLAssetType::AT_CALLINGCARD); - - LLInventoryModel::cat_array_t card_cats; - LLInventoryModel::item_array_t card_items; - LLInventoryModel::cat_array_t land_cats; - LLInventoryModel::item_array_t land_items; - - uuid_vec_t::iterator it = mComplete.begin(); - uuid_vec_t::iterator end = mComplete.end(); - for(; it != end; ++it) - { - gInventory.collectDescendentsIf( - (*it), - land_cats, - land_items, - LLInventoryModel::EXCLUDE_TRASH, - is_landmark); - gInventory.collectDescendentsIf( - (*it), - card_cats, - card_items, - LLInventoryModel::EXCLUDE_TRASH, - is_card); - } - - gInventory.removeObserver(this); - delete this; - } -}; - - - -class LLPostTeleportNotifiers : public LLEventTimer -{ -public: - LLPostTeleportNotifiers(); - virtual ~LLPostTeleportNotifiers(); - - //function to be called at the supplied frequency - virtual bool tick(); -}; - -LLPostTeleportNotifiers::LLPostTeleportNotifiers() : LLEventTimer( 2.0 ) -{ -}; - -LLPostTeleportNotifiers::~LLPostTeleportNotifiers() -{ -} - -bool LLPostTeleportNotifiers::tick() -{ - bool all_done = false; - if ( gAgent.getTeleportState() == LLAgent::TELEPORT_NONE ) - { - // get callingcards and landmarks available to the user arriving. - uuid_vec_t folders; - const LLUUID callingcard_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD); - if(callingcard_id.notNull()) - folders.push_back(callingcard_id); - const LLUUID folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK); - if(folder_id.notNull()) - folders.push_back(folder_id); - if(!folders.empty()) - { - LLFetchInWelcomeArea* fetcher = new LLFetchInWelcomeArea(folders); - fetcher->startFetch(); - if(fetcher->isFinished()) - { - fetcher->done(); - } - else - { - gInventory.addObserver(fetcher); - } - } - all_done = true; - } - - return all_done; -} - - - -// Teleport notification from the simulator -// We're going to pretend to be a new agent -void process_teleport_finish(LLMessageSystem* msg, void**) -{ - LL_DEBUGS("Teleport","Messaging") << "Received TeleportFinish message" << LL_ENDL; - LLUUID agent_id; - msg->getUUIDFast(_PREHASH_Info, _PREHASH_AgentID, agent_id); - if (agent_id != gAgent.getID()) - { - LL_WARNS("Teleport","Messaging") << "Got teleport notification for wrong agent " << agent_id << " expected " << gAgent.getID() << ", ignoring!" << LL_ENDL; - return; - } - - if (gAgent.getTeleportState() == LLAgent::TELEPORT_NONE) - { - if (gAgent.canRestoreCanceledTeleport()) - { - // Server either ignored teleport cancel message or did not receive it in time. - // This message can't be ignored since teleport is complete at server side - LL_INFOS("Teleport") << "Restoring canceled teleport request" << LL_ENDL; - gAgent.restoreCanceledTeleportRequest(); - } - else - { - // Race condition? Make sure all variables are set correctly for teleport to work - LL_WARNS("Teleport","Messaging") << "Teleport 'finish' message without 'start'. Setting state to TELEPORT_REQUESTED" << LL_ENDL; - gTeleportDisplay = true; - LLViewerMessage::getInstance()->mTeleportStartedSignal(); - gAgent.setTeleportState(LLAgent::TELEPORT_REQUESTED); - make_ui_sound("UISndTeleportOut"); - } - } - else if (gAgent.getTeleportState() == LLAgent::TELEPORT_MOVING) - { - LL_WARNS("Teleport","Messaging") << "Teleport message in the middle of other teleport" << LL_ENDL; - } - - // Teleport is finished; it can't be cancelled now. - gViewerWindow->setProgressCancelButtonVisible(false); - - // Do teleport effect for where you're leaving - // VEFFECT: TeleportStart - LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_POINT, true); - effectp->setPositionGlobal(gAgent.getPositionGlobal()); - effectp->setColor(LLColor4U(gAgent.getEffectColor())); - LLHUDManager::getInstance()->sendEffects(); - - U32 location_id; - U32 sim_ip; - U16 sim_port; - LLVector3 pos, look_at; - U64 region_handle; - msg->getU32Fast(_PREHASH_Info, _PREHASH_LocationID, location_id); - msg->getIPAddrFast(_PREHASH_Info, _PREHASH_SimIP, sim_ip); - msg->getIPPortFast(_PREHASH_Info, _PREHASH_SimPort, sim_port); - //msg->getVector3Fast(_PREHASH_Info, _PREHASH_Position, pos); - //msg->getVector3Fast(_PREHASH_Info, _PREHASH_LookAt, look_at); - msg->getU64Fast(_PREHASH_Info, _PREHASH_RegionHandle, region_handle); - U32 teleport_flags; - msg->getU32Fast(_PREHASH_Info, _PREHASH_TeleportFlags, teleport_flags); - - std::string seedCap; - msg->getStringFast(_PREHASH_Info, _PREHASH_SeedCapability, seedCap); - - LL_DEBUGS("Teleport") << "TeleportFinish message params are:" - << " sim_ip " << sim_ip - << " sim_port " << sim_port - << " region_handle " << region_handle - << " teleport_flags " << teleport_flags - << " seedCap " << seedCap - << LL_ENDL; - - // update home location if we are teleporting out of prelude - specific to teleporting to welcome area - if((teleport_flags & TELEPORT_FLAGS_SET_HOME_TO_TARGET) - && (!gAgent.isGodlike())) - { - gAgent.setHomePosRegion(region_handle, pos); - - // Create a timer that will send notices when teleporting is all finished. Since this is - // based on the LLEventTimer class, it will be managed by that class and not orphaned or leaked. - new LLPostTeleportNotifiers(); - } - - LLHost sim_host(sim_ip, sim_port); - - // Viewer trusts the simulator. - gMessageSystem->enableCircuit(sim_host, true); - LLViewerRegion* regionp = LLWorld::getInstance()->addRegion(region_handle, sim_host); - -/* - // send camera update to new region - gAgentCamera.updateCamera(); - - // likewise make sure the camera is behind the avatar - gAgentCamera.resetView(true); - LLVector3 shift_vector = regionp->getPosRegionFromGlobal(gAgent.getRegion()->getOriginGlobal()); - gAgent.setRegion(regionp); - gObjectList.shiftObjects(shift_vector); - - if (isAgentAvatarValid()) - { - gAgentAvatarp->clearChatText(); - gAgentCamera.slamLookAt(look_at); - } - gAgent.setPositionAgent(pos); - gAssetStorage->setUpstream(sim); - gCacheName->setUpstream(sim); -*/ - - // Make sure we're standing - gAgent.standUp(); - - // now, use the circuit info to tell simulator about us! - LL_INFOS("Teleport","Messaging") << "process_teleport_finish() sending UseCircuitCode to enable sim_host " - << sim_host << " with code " << msg->mOurCircuitCode << LL_ENDL; - msg->newMessageFast(_PREHASH_UseCircuitCode); - msg->nextBlockFast(_PREHASH_CircuitCode); - msg->addU32Fast(_PREHASH_Code, msg->getOurCircuitCode()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addUUIDFast(_PREHASH_ID, gAgent.getID()); - msg->sendReliable(sim_host); - - LL_INFOS("Teleport") << "Calling send_complete_agent_movement() and setting state to TELEPORT_MOVING" << LL_ENDL; - send_complete_agent_movement(sim_host); - gAgent.setTeleportState( LLAgent::TELEPORT_MOVING ); - gAgent.setTeleportMessage(LLAgent::sTeleportProgressMessages["contacting"]); - - LL_DEBUGS("CrossingCaps") << "Calling setSeedCapability(). Seed cap == " - << seedCap << LL_ENDL; - regionp->setSeedCapability(seedCap); - - // Don't send camera updates to the new region until we're - // actually there... - - - // Now do teleport effect for where you're going. - // VEFFECT: TeleportEnd - effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_POINT, true); - effectp->setPositionGlobal(gAgent.getPositionGlobal()); - - effectp->setColor(LLColor4U(gAgent.getEffectColor())); - LLHUDManager::getInstance()->sendEffects(); - -// gTeleportDisplay = true; -// gTeleportDisplayTimer.reset(); -// gViewerWindow->setShowProgress(true); -} - -// stuff we have to do every time we get an AvatarInitComplete from a sim -/* -void process_avatar_init_complete(LLMessageSystem* msg, void**) -{ - LLVector3 agent_pos; - msg->getVector3Fast(_PREHASH_AvatarData, _PREHASH_Position, agent_pos); - agent_movement_complete(msg->getSender(), agent_pos); -} -*/ - -void process_agent_movement_complete(LLMessageSystem* msg, void**) -{ - LL_INFOS("Teleport","Messaging") << "Received ProcessAgentMovementComplete" << LL_ENDL; - - gShiftFrame = true; - gAgentMovementCompleted = true; - - LLUUID agent_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); - LLUUID session_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_SessionID, session_id); - if((gAgent.getID() != agent_id) || (gAgent.getSessionID() != session_id)) - { - LL_WARNS("Teleport", "Messaging") << "Incorrect agent or session id in process_agent_movement_complete()" - << " agent " << agent_id << " expected " << gAgent.getID() - << " session " << session_id << " expected " << gAgent.getSessionID() - << ", ignoring" << LL_ENDL; - return; - } - - // *TODO: check timestamp to make sure the movement compleation - // makes sense. - LLVector3 agent_pos; - msg->getVector3Fast(_PREHASH_Data, _PREHASH_Position, agent_pos); - LLVector3 look_at; - msg->getVector3Fast(_PREHASH_Data, _PREHASH_LookAt, look_at); - U64 region_handle; - msg->getU64Fast(_PREHASH_Data, _PREHASH_RegionHandle, region_handle); - - std::string version_channel; - msg->getString("SimData", "ChannelVersion", version_channel); - - if (!isAgentAvatarValid()) - { - // Could happen if you were immediately god-teleported away on login, - // maybe other cases. Continue, but warn. - LL_WARNS("Teleport", "Messaging") << "agent_movement_complete() with NULL avatarp." << LL_ENDL; - } - - F32 x, y; - from_region_handle(region_handle, &x, &y); - LLViewerRegion* regionp = LLWorld::getInstance()->getRegionFromHandle(region_handle); - if (!regionp) - { - if (gAgent.getRegion()) - { - LL_WARNS("Teleport", "Messaging") << "current region origin " - << gAgent.getRegion()->getOriginGlobal() << " id " << gAgent.getRegion()->getRegionID() << LL_ENDL; - } - - LL_WARNS("Teleport", "Messaging") << "Agent being sent to invalid home region: " - << x << ":" << y - << " current pos " << gAgent.getPositionGlobal() - << ", calling forceDisconnect()" - << LL_ENDL; - LLAppViewer::instance()->forceDisconnect(LLTrans::getString("SentToInvalidRegion")); - return; - - } - - LL_INFOS("Teleport","Messaging") << "Changing home region to region id " << regionp->getRegionID() << " handle " << region_handle << " == x,y " << x << "," << y << LL_ENDL; - - // set our upstream host the new simulator and shuffle things as - // appropriate. - LLVector3 shift_vector = regionp->getPosRegionFromGlobal( - gAgent.getRegion()->getOriginGlobal()); - gAgent.setRegion(regionp); - gObjectList.shiftObjects(shift_vector); - gAssetStorage->setUpstream(msg->getSender()); - gCacheName->setUpstream(msg->getSender()); - gViewerThrottle.sendToSim(); - gViewerWindow->sendShapeToSim(); - - bool is_teleport = gAgent.getTeleportState() == LLAgent::TELEPORT_MOVING; - - if( is_teleport ) - { - if (gAgent.getTeleportKeepsLookAt()) - { - // *NOTE: the LookAt data we get from the sim here doesn't - // seem to be useful, so get it from the camera instead - look_at = LLViewerCamera::getInstance()->getAtAxis(); - } - // Force the camera back onto the agent, don't animate. - gAgentCamera.setFocusOnAvatar(true, false); - gAgentCamera.slamLookAt(look_at); - gAgentCamera.updateCamera(); - - LL_INFOS("Teleport") << "Agent movement complete, setting state to TELEPORT_START_ARRIVAL" << LL_ENDL; - gAgent.setTeleportState( LLAgent::TELEPORT_START_ARRIVAL ); - - if (isAgentAvatarValid()) - { - // Set the new position - gAgentAvatarp->setPositionAgent(agent_pos); - gAgentAvatarp->clearChat(); - gAgentAvatarp->slamPosition(); - } - } - else - { - // This is initial log-in or a region crossing - LL_INFOS("Teleport") << "State is not TELEPORT_MOVING, so this is initial log-in or region crossing. " - << "Setting state to TELEPORT_NONE" << LL_ENDL; - gAgent.setTeleportState( LLAgent::TELEPORT_NONE ); - - if(LLStartUp::getStartupState() < STATE_STARTED) - { // This is initial log-in, not a region crossing: - // Set the camera looking ahead of the AV so send_agent_update() below - // will report the correct location to the server. - LLVector3 look_at_point = look_at; - look_at_point = agent_pos + look_at_point.rotVec(gAgent.getQuat()); - - static LLVector3 up_direction(0.0f, 0.0f, 1.0f); - LLViewerCamera::getInstance()->lookAt(agent_pos, look_at_point, up_direction); - } - } - - if ( LLTracker::isTracking(NULL) ) - { - // Check distance to beacon, if < 5m, remove beacon - LLVector3d beacon_pos = LLTracker::getTrackedPositionGlobal(); - LLVector3 beacon_dir(agent_pos.mV[VX] - (F32)fmod(beacon_pos.mdV[VX], 256.0), agent_pos.mV[VY] - (F32)fmod(beacon_pos.mdV[VY], 256.0), 0); - if (beacon_dir.magVecSquared() < 25.f) - { - LLTracker::stopTracking(false); - } - else if ( is_teleport && !gAgent.getTeleportKeepsLookAt() && look_at.isExactlyZero()) - { - //look at the beacon - LLVector3 global_agent_pos = agent_pos; - global_agent_pos[0] += x; - global_agent_pos[1] += y; - look_at = (LLVector3)beacon_pos - global_agent_pos; - look_at.normVec(); - gAgentCamera.slamLookAt(look_at); - } - } - - // TODO: Put back a check for flying status! DK 12/19/05 - // Sim tells us whether the new position is off the ground - /* - if (teleport_flags & TELEPORT_FLAGS_IS_FLYING) - { - gAgent.setFlying(true); - } - else - { - gAgent.setFlying(false); - } - */ - - send_agent_update(true, true); - - if (gAgent.getRegion()->getBlockFly()) - { - gAgent.setFlying(gAgent.canFly()); - } - - // force simulator to recognize do not disturb state - if (gAgent.isDoNotDisturb()) - { - gAgent.setDoNotDisturb(true); - } - else - { - gAgent.setDoNotDisturb(false); - } - - if (isAgentAvatarValid()) - { - gAgentAvatarp->mFootPlane.clearVec(); - } - - // send walk-vs-run status - gAgent.sendWalkRun(gAgent.getRunning() || gAgent.getAlwaysRun()); - - // If the server version has changed, display an info box and offer - // to display the release notes, unless this is the initial log in. - if (gLastVersionChannel == version_channel) - { - return; - } - - gLastVersionChannel = version_channel; -} - -void process_crossed_region(LLMessageSystem* msg, void**) -{ - LLUUID agent_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); - LLUUID session_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_SessionID, session_id); - if((gAgent.getID() != agent_id) || (gAgent.getSessionID() != session_id)) - { - LL_WARNS("Messaging") << "Incorrect id in process_crossed_region()" - << LL_ENDL; - return; - } - LL_INFOS("Messaging") << "process_crossed_region()" << LL_ENDL; - gAgentAvatarp->resetRegionCrossingTimer(); - - U32 sim_ip; - msg->getIPAddrFast(_PREHASH_RegionData, _PREHASH_SimIP, sim_ip); - U16 sim_port; - msg->getIPPortFast(_PREHASH_RegionData, _PREHASH_SimPort, sim_port); - LLHost sim_host(sim_ip, sim_port); - U64 region_handle; - msg->getU64Fast(_PREHASH_RegionData, _PREHASH_RegionHandle, region_handle); - - std::string seedCap; - msg->getStringFast(_PREHASH_RegionData, _PREHASH_SeedCapability, seedCap); - - send_complete_agent_movement(sim_host); - - LLViewerRegion* regionp = LLWorld::getInstance()->addRegion(region_handle, sim_host); - - LL_DEBUGS("CrossingCaps") << "Calling setSeedCapability from process_crossed_region(). Seed cap == " - << seedCap << LL_ENDL; - regionp->setSeedCapability(seedCap); -} - - - -// Sends avatar and camera information to simulator. -// Sent roughly once per frame, or 20 times per second, whichever is less often - -const F32 THRESHOLD_HEAD_ROT_QDOT = 0.9997f; // ~= 2.5 degrees -- if its less than this we need to update head_rot -const F32 MAX_HEAD_ROT_QDOT = 0.99999f; // ~= 0.5 degrees -- if its greater than this then no need to update head_rot - // between these values we delay the updates (but no more than one second) - -void send_agent_update(bool force_send, bool send_reliable) -{ - LL_PROFILE_ZONE_SCOPED; - llassert(!gCubeSnapshot); - - if (gAgent.getTeleportState() != LLAgent::TELEPORT_NONE) - { - // We don't care if they want to send an agent update, they're not allowed to until the simulator - // that's the target is ready to receive them (after avatar_init_complete is received) - return; - } - - // We have already requested to log out. Don't send agent updates. - if(LLAppViewer::instance()->logoutRequestSent()) - { - return; - } - - // no region to send update to - if(gAgent.getRegion() == NULL) - { - return; - } - - const F32 TRANSLATE_THRESHOLD = 0.01f; - - // NOTA BENE: This is (intentionally?) using the small angle sine approximation to test for rotation - // Plus, there is an extra 0.5 in the mix since the perpendicular between last_camera_at and getAtAxis() bisects cam_rot_change - // Thus, we're actually testing against 0.2 degrees - const F32 ROTATION_THRESHOLD = 0.1f * 2.f*F_PI/360.f; // Rotation thresh 0.2 deg, see note above - - const U8 DUP_MSGS = 1; // HACK! number of times to repeat data on motionless agent - - // Store data on last sent update so that if no changes, no send - static LLVector3 last_camera_pos_agent, - last_camera_at, - last_camera_left, - last_camera_up; - - static LLVector3 cam_center_chg, - cam_rot_chg; - - static LLQuaternion last_head_rot; - static U32 last_control_flags = 0; - static U8 last_render_state; - static U8 duplicate_count = 0; - static F32 head_rot_chg = 1.0; - static U8 last_flags; - - LLMessageSystem *msg = gMessageSystem; - LLVector3 camera_pos_agent; // local to avatar's region - U8 render_state; - - LLQuaternion body_rotation = gAgent.getFrameAgent().getQuaternion(); - LLQuaternion head_rotation = gAgent.getHeadRotation(); - - camera_pos_agent = gAgentCamera.getCameraPositionAgent(); - - render_state = gAgent.getRenderState(); - - U32 control_flag_change = 0; - U8 flag_change = 0; - - cam_center_chg = last_camera_pos_agent - camera_pos_agent; - cam_rot_chg = last_camera_at - LLViewerCamera::getInstance()->getAtAxis(); - - // If a modifier key is held down, turn off - // LBUTTON and ML_LBUTTON so that using the camera (alt-key) doesn't - // trigger a control event. - U32 control_flags = gAgent.getControlFlags(); - - MASK key_mask = gKeyboard->currentMask(true); - - if (key_mask & MASK_ALT || key_mask & MASK_CONTROL) - { - control_flags &= ~( AGENT_CONTROL_LBUTTON_DOWN | - AGENT_CONTROL_ML_LBUTTON_DOWN ); - control_flags |= AGENT_CONTROL_LBUTTON_UP | - AGENT_CONTROL_ML_LBUTTON_UP ; - } - - control_flag_change = last_control_flags ^ control_flags; - - U8 flags = AU_FLAGS_NONE; - if (gAgent.isGroupTitleHidden()) - { - flags |= AU_FLAGS_HIDETITLE; - } - if (gAgent.getAutoPilot()) - { - flags |= AU_FLAGS_CLIENT_AUTOPILOT; - } - - flag_change = last_flags ^ flags; - - head_rot_chg = dot(last_head_rot, head_rotation); - - //static S32 msg_number = 0; // Used for diagnostic log messages - - if (force_send || - (cam_center_chg.magVec() > TRANSLATE_THRESHOLD) || - (head_rot_chg < THRESHOLD_HEAD_ROT_QDOT) || - (last_render_state != render_state) || - (cam_rot_chg.magVec() > ROTATION_THRESHOLD) || - control_flag_change != 0 || - flag_change != 0) - { - /* Diagnotics to show why we send the AgentUpdate message. Also un-commment the msg_number code above and below this block - msg_number += 1; - if (head_rot_chg < THRESHOLD_HEAD_ROT_QDOT) - { - //LL_INFOS("Messaging") << "head rot " << head_rotation << LL_ENDL; - LL_INFOS("Messaging") << "msg " << msg_number << ", frame " << LLFrameTimer::getFrameCount() << ", head_rot_chg " << head_rot_chg << LL_ENDL; - } - if (cam_rot_chg.magVec() > ROTATION_THRESHOLD) - { - LL_INFOS("Messaging") << "msg " << msg_number << ", frame " << LLFrameTimer::getFrameCount() << ", cam rot " << cam_rot_chg.magVec() << LL_ENDL; - } - if (cam_center_chg.magVec() > TRANSLATE_THRESHOLD) - { - LL_INFOS("Messaging") << "msg " << msg_number << ", frame " << LLFrameTimer::getFrameCount() << ", cam center " << cam_center_chg.magVec() << LL_ENDL; - } -// if (drag_delta_chg.magVec() > TRANSLATE_THRESHOLD) -// { -// LL_INFOS("Messaging") << "drag delta " << drag_delta_chg.magVec() << LL_ENDL; -// } - if (control_flag_change) - { - LL_INFOS("Messaging") << "msg " << msg_number << ", frame " << LLFrameTimer::getFrameCount() << ", dcf = " << control_flag_change << LL_ENDL; - } -*/ - - duplicate_count = 0; - } - else - { - duplicate_count++; - - if (head_rot_chg < MAX_HEAD_ROT_QDOT && duplicate_count < AGENT_UPDATES_PER_SECOND) - { - // The head_rotation is sent for updating things like attached guns. - // We only trigger a new update when head_rotation deviates beyond - // some threshold from the last update, however this can break fine - // adjustments when trying to aim an attached gun, so what we do here - // (where we would normally skip sending an update when nothing has changed) - // is gradually reduce the threshold to allow a better update to - // eventually get sent... should update to within 0.5 degrees in less - // than a second. - if (head_rot_chg < THRESHOLD_HEAD_ROT_QDOT + (MAX_HEAD_ROT_QDOT - THRESHOLD_HEAD_ROT_QDOT) * duplicate_count / AGENT_UPDATES_PER_SECOND) - { - duplicate_count = 0; - } - else - { - return; - } - } - else - { - return; - } - } - - if (duplicate_count < DUP_MSGS && !gDisconnected) - { - /* More diagnostics to count AgentUpdate messages - static S32 update_sec = 0; - static S32 update_count = 0; - static S32 max_update_count = 0; - S32 cur_sec = lltrunc( LLTimer::getTotalSeconds() ); - update_count += 1; - if (cur_sec != update_sec) - { - if (update_sec != 0) - { - update_sec = cur_sec; - //msg_number = 0; - max_update_count = llmax(max_update_count, update_count); - LL_INFOS() << "Sent " << update_count << " AgentUpdate messages per second, max is " << max_update_count << LL_ENDL; - } - update_sec = cur_sec; - update_count = 0; - } - */ - - // Build the message - msg->newMessageFast(_PREHASH_AgentUpdate); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addQuatFast(_PREHASH_BodyRotation, body_rotation); - msg->addQuatFast(_PREHASH_HeadRotation, head_rotation); - msg->addU8Fast(_PREHASH_State, render_state); - msg->addU8Fast(_PREHASH_Flags, flags); - -// if (camera_pos_agent.mV[VY] > 255.f) -// { -// LL_INFOS("Messaging") << "Sending camera center " << camera_pos_agent << LL_ENDL; -// } - - msg->addVector3Fast(_PREHASH_CameraCenter, camera_pos_agent); - msg->addVector3Fast(_PREHASH_CameraAtAxis, LLViewerCamera::getInstance()->getAtAxis()); - msg->addVector3Fast(_PREHASH_CameraLeftAxis, LLViewerCamera::getInstance()->getLeftAxis()); - msg->addVector3Fast(_PREHASH_CameraUpAxis, LLViewerCamera::getInstance()->getUpAxis()); - msg->addF32Fast(_PREHASH_Far, gAgentCamera.mDrawDistance); - - msg->addU32Fast(_PREHASH_ControlFlags, control_flags); - - if (gDebugClicks) - { - if (control_flags & AGENT_CONTROL_LBUTTON_DOWN) - { - LL_INFOS("Messaging") << "AgentUpdate left button down" << LL_ENDL; - } - - if (control_flags & AGENT_CONTROL_LBUTTON_UP) - { - LL_INFOS("Messaging") << "AgentUpdate left button up" << LL_ENDL; - } - } - - gAgent.enableControlFlagReset(); - - if (!send_reliable) - { - gAgent.sendMessage(); - } - else - { - gAgent.sendReliableMessage(); - } - -// LL_DEBUGS("Messaging") << "agent " << avatar_pos_agent << " cam " << camera_pos_agent << LL_ENDL; - - // Copy the old data - last_head_rot = head_rotation; - last_render_state = render_state; - last_camera_pos_agent = camera_pos_agent; - last_camera_at = LLViewerCamera::getInstance()->getAtAxis(); - last_camera_left = LLViewerCamera::getInstance()->getLeftAxis(); - last_camera_up = LLViewerCamera::getInstance()->getUpAxis(); - last_control_flags = control_flags; - last_flags = flags; - } -} - - -// sounds can arrive before objects, store them for a short time -// Note: this is a workaround for MAINT-4743, real fix would be to make -// server send sound along with object update that creates (rezes) the object -class PostponedSoundData -{ -public: - PostponedSoundData() : - mExpirationTime(0) - {} - PostponedSoundData(const LLUUID &object_id, const LLUUID &sound_id, const LLUUID& owner_id, const F32 gain, const U8 flags); - bool hasExpired() { return LLFrameTimer::getTotalSeconds() > mExpirationTime; } - - LLUUID mObjectId; - LLUUID mSoundId; - LLUUID mOwnerId; - F32 mGain; - U8 mFlags; - static const F64 MAXIMUM_PLAY_DELAY; - -private: - F64 mExpirationTime; //seconds since epoch -}; -const F64 PostponedSoundData::MAXIMUM_PLAY_DELAY = 15.0; -static F64 postponed_sounds_update_expiration = 0.0; -static std::map postponed_sounds; - -void set_attached_sound(LLViewerObject *objectp, const LLUUID &object_id, const LLUUID &sound_id, const LLUUID& owner_id, const F32 gain, const U8 flags) -{ - if (LLMuteList::getInstance()->isMuted(object_id)) return; - - if (LLMuteList::getInstance()->isMuted(owner_id, LLMute::flagObjectSounds)) return; - - // Don't play sounds from a region with maturity above current agent maturity - LLVector3d pos = objectp->getPositionGlobal(); - if (!gAgent.canAccessMaturityAtGlobal(pos)) - { - return; - } - - objectp->setAttachedSound(sound_id, owner_id, gain, flags); -} - -PostponedSoundData::PostponedSoundData(const LLUUID &object_id, const LLUUID &sound_id, const LLUUID& owner_id, const F32 gain, const U8 flags) - : - mObjectId(object_id), - mSoundId(sound_id), - mOwnerId(owner_id), - mGain(gain), - mFlags(flags), - mExpirationTime(LLFrameTimer::getTotalSeconds() + MAXIMUM_PLAY_DELAY) -{ -} - -// static -void update_attached_sounds() -{ - if (postponed_sounds.empty()) - { - return; - } - - std::map::iterator iter = postponed_sounds.begin(); - std::map::iterator end = postponed_sounds.end(); - while (iter != end) - { - std::map::iterator cur_iter = iter++; - PostponedSoundData* data = &cur_iter->second; - if (data->hasExpired()) - { - postponed_sounds.erase(cur_iter); - } - else - { - LLViewerObject *objectp = gObjectList.findObject(data->mObjectId); - if (objectp) - { - set_attached_sound(objectp, data->mObjectId, data->mSoundId, data->mOwnerId, data->mGain, data->mFlags); - postponed_sounds.erase(cur_iter); - } - } - } - postponed_sounds_update_expiration = LLFrameTimer::getTotalSeconds() + 2 * PostponedSoundData::MAXIMUM_PLAY_DELAY; -} - -//static -void clear_expired_postponed_sounds() -{ - if (postponed_sounds_update_expiration > LLFrameTimer::getTotalSeconds()) - { - return; - } - std::map::iterator iter = postponed_sounds.begin(); - std::map::iterator end = postponed_sounds.end(); - while (iter != end) - { - std::map::iterator cur_iter = iter++; - PostponedSoundData* data = &cur_iter->second; - if (data->hasExpired()) - { - postponed_sounds.erase(cur_iter); - } - } - postponed_sounds_update_expiration = LLFrameTimer::getTotalSeconds() + 2 * PostponedSoundData::MAXIMUM_PLAY_DELAY; -} - -// *TODO: Remove this dependency, or figure out a better way to handle -// this hack. -extern U32Bits gObjectData; - -void process_object_update(LLMessageSystem *mesgsys, void **user_data) -{ - // Update the data counters - if (mesgsys->getReceiveCompressedSize()) - { - gObjectData += (U32Bytes)mesgsys->getReceiveCompressedSize(); - } - else - { - gObjectData += (U32Bytes)mesgsys->getReceiveSize(); - } - - // Update the object... - S32 old_num_objects = gObjectList.mNumNewObjects; - gObjectList.processObjectUpdate(mesgsys, user_data, OUT_FULL); - if (old_num_objects != gObjectList.mNumNewObjects) - { - update_attached_sounds(); - } -} - -void process_compressed_object_update(LLMessageSystem *mesgsys, void **user_data) -{ - // Update the data counters - if (mesgsys->getReceiveCompressedSize()) - { - gObjectData += (U32Bytes)mesgsys->getReceiveCompressedSize(); - } - else - { - gObjectData += (U32Bytes)mesgsys->getReceiveSize(); - } - - // Update the object... - S32 old_num_objects = gObjectList.mNumNewObjects; - gObjectList.processCompressedObjectUpdate(mesgsys, user_data, OUT_FULL_COMPRESSED); - if (old_num_objects != gObjectList.mNumNewObjects) - { - update_attached_sounds(); - } -} - -void process_cached_object_update(LLMessageSystem *mesgsys, void **user_data) -{ - // Update the data counters - if (mesgsys->getReceiveCompressedSize()) - { - gObjectData += (U32Bytes)mesgsys->getReceiveCompressedSize(); - } - else - { - gObjectData += (U32Bytes)mesgsys->getReceiveSize(); - } - - // Update the object... - gObjectList.processCachedObjectUpdate(mesgsys, user_data, OUT_FULL_CACHED); -} - - -void process_terse_object_update_improved(LLMessageSystem *mesgsys, void **user_data) -{ - if (mesgsys->getReceiveCompressedSize()) - { - gObjectData += (U32Bytes)mesgsys->getReceiveCompressedSize(); - } - else - { - gObjectData += (U32Bytes)mesgsys->getReceiveSize(); - } - - S32 old_num_objects = gObjectList.mNumNewObjects; - gObjectList.processCompressedObjectUpdate(mesgsys, user_data, OUT_TERSE_IMPROVED); - if (old_num_objects != gObjectList.mNumNewObjects) - { - update_attached_sounds(); - } -} - -void process_kill_object(LLMessageSystem *mesgsys, void **user_data) -{ - LL_PROFILE_ZONE_SCOPED; - - LLUUID id; - - U32 ip = mesgsys->getSenderIP(); - U32 port = mesgsys->getSenderPort(); - LLViewerRegion* regionp = NULL; - { - LLHost host(ip, port); - regionp = LLWorld::getInstance()->getRegion(host); - } - - bool delete_object = LLViewerRegion::sVOCacheCullingEnabled; - S32 num_objects = mesgsys->getNumberOfBlocksFast(_PREHASH_ObjectData); - for (S32 i = 0; i < num_objects; ++i) - { - U32 local_id; - mesgsys->getU32Fast(_PREHASH_ObjectData, _PREHASH_ID, local_id, i); - - LLViewerObjectList::getUUIDFromLocal(id, local_id, ip, port); - if (id == LLUUID::null) - { - LL_DEBUGS("Messaging") << "Unknown kill for local " << local_id << LL_ENDL; - continue; - } - else - { - LL_DEBUGS("Messaging") << "Kill message for local " << local_id << LL_ENDL; - } - - if (id == gAgentID) - { - // never kill our avatar - continue; - } - - LLViewerObject *objectp = gObjectList.findObject(id); - if (objectp) - { - // Display green bubble on kill - if ( gShowObjectUpdates ) - { - LLColor4 color(0.f,1.f,0.f,1.f); - gPipeline.addDebugBlip(objectp->getPositionAgent(), color); - LL_DEBUGS("MessageBlip") << "Kill blip for local " << local_id << " at " << objectp->getPositionAgent() << LL_ENDL; - } - - // Do the kill - gObjectList.killObject(objectp); - } - - if(delete_object) - { - regionp->killCacheEntry(local_id); - } - - // We should remove the object from selection after it is marked dead by gObjectList to make LLToolGrab, - // which is using the object, release the mouse capture correctly when the object dies. - // See LLToolGrab::handleHoverActive() and LLToolGrab::handleHoverNonPhysical(). - LLSelectMgr::getInstance()->removeObjectFromSelections(id); - - } // end for loop - - LLViewerStatsRecorder::instance().recordObjectKills(num_objects); -} - -void process_time_synch(LLMessageSystem *mesgsys, void **user_data) -{ - LLVector3 sun_direction; - LLVector3 moon_direction; - LLVector3 sun_ang_velocity; - F32 phase; - U64 space_time_usec; - - U32 seconds_per_day; - U32 seconds_per_year; - - // "SimulatorViewerTimeMessage" - mesgsys->getU64Fast(_PREHASH_TimeInfo, _PREHASH_UsecSinceStart, space_time_usec); - mesgsys->getU32Fast(_PREHASH_TimeInfo, _PREHASH_SecPerDay, seconds_per_day); - mesgsys->getU32Fast(_PREHASH_TimeInfo, _PREHASH_SecPerYear, seconds_per_year); - - // This should eventually be moved to an "UpdateHeavenlyBodies" message - mesgsys->getF32Fast(_PREHASH_TimeInfo, _PREHASH_SunPhase, phase); - mesgsys->getVector3Fast(_PREHASH_TimeInfo, _PREHASH_SunDirection, sun_direction); - mesgsys->getVector3Fast(_PREHASH_TimeInfo, _PREHASH_SunAngVelocity, sun_ang_velocity); - - LLWorld::getInstance()->setSpaceTimeUSec(space_time_usec); - - LL_DEBUGS("WindlightSync") << "Sun phase: " << phase << " rad = " << fmodf(phase / F_TWO_PI + 0.25, 1.f) * 24.f << " h" << LL_ENDL; - - /* LAPRAS - We decode these parts of the message but ignore them - as the real values are provided elsewhere. */ - (void)sun_direction, (void)moon_direction, (void)phase; -} - -void process_sound_trigger(LLMessageSystem *msg, void **) -{ - if (!gAudiop) - { -#if !LL_LINUX - LL_WARNS("AudioEngine") << "LLAudioEngine instance doesn't exist!" << LL_ENDL; -#endif - return; - } - - U64 region_handle = 0; - F32 gain = 0; - LLUUID sound_id; - LLUUID owner_id; - LLUUID object_id; - LLUUID parent_id; - LLVector3 pos_local; - - msg->getUUIDFast(_PREHASH_SoundData, _PREHASH_SoundID, sound_id); - msg->getUUIDFast(_PREHASH_SoundData, _PREHASH_OwnerID, owner_id); - msg->getUUIDFast(_PREHASH_SoundData, _PREHASH_ObjectID, object_id); - msg->getUUIDFast(_PREHASH_SoundData, _PREHASH_ParentID, parent_id); - msg->getU64Fast(_PREHASH_SoundData, _PREHASH_Handle, region_handle); - msg->getVector3Fast(_PREHASH_SoundData, _PREHASH_Position, pos_local); - msg->getF32Fast(_PREHASH_SoundData, _PREHASH_Gain, gain); - - // adjust sound location to true global coords - LLVector3d pos_global = from_region_handle(region_handle); - pos_global.mdV[VX] += pos_local.mV[VX]; - pos_global.mdV[VY] += pos_local.mV[VY]; - pos_global.mdV[VZ] += pos_local.mV[VZ]; - - // Don't play a trigger sound if you can't hear it due - // to parcel "local audio only" settings. - if (!LLViewerParcelMgr::getInstance()->canHearSound(pos_global)) return; - - // Don't play sounds triggered by someone you muted. - if (LLMuteList::getInstance()->isMuted(owner_id, LLMute::flagObjectSounds)) return; - - // Don't play sounds from an object you muted - if (LLMuteList::getInstance()->isMuted(object_id)) return; - - // Don't play sounds from an object whose parent you muted - if (parent_id.notNull() - && LLMuteList::getInstance()->isMuted(parent_id)) - { - return; - } - - // Don't play sounds from a region with maturity above current agent maturity - if( !gAgent.canAccessMaturityInRegion( region_handle ) ) - { - return; - } - - // Don't play sounds from gestures if they are not enabled. - // Do play sounds triggered by avatar, since muting your own - // gesture sounds and your own sounds played inworld from - // Inventory can cause confusion. - if (object_id == owner_id - && owner_id != gAgentID - && !gSavedSettings.getBOOL("EnableGestureSounds")) - { - return; - } - - if (LLMaterialTable::basic.isCollisionSound(sound_id) && !gSavedSettings.getBOOL("EnableCollisionSounds")) - { - return; - } - - gAudiop->triggerSound(sound_id, owner_id, gain, LLAudioEngine::AUDIO_TYPE_SFX, pos_global); -} - -void process_preload_sound(LLMessageSystem *msg, void **user_data) -{ - if (!gAudiop) - { -#if !LL_LINUX - LL_WARNS("AudioEngine") << "LLAudioEngine instance doesn't exist!" << LL_ENDL; -#endif - return; - } - - LLUUID sound_id; - LLUUID object_id; - LLUUID owner_id; - - msg->getUUIDFast(_PREHASH_DataBlock, _PREHASH_SoundID, sound_id); - msg->getUUIDFast(_PREHASH_DataBlock, _PREHASH_ObjectID, object_id); - msg->getUUIDFast(_PREHASH_DataBlock, _PREHASH_OwnerID, owner_id); - - LLViewerObject *objectp = gObjectList.findObject(object_id); - if (!objectp) return; - - if (LLMuteList::getInstance()->isMuted(object_id)) return; - if (LLMuteList::getInstance()->isMuted(owner_id, LLMute::flagObjectSounds)) return; - - LLAudioSource *sourcep = objectp->getAudioSource(owner_id); - if (!sourcep) return; - - LLAudioData *datap = gAudiop->getAudioData(sound_id); - - // Note that I don't actually do any loading of the - // audio data into a buffer at this point, as it won't actually - // help us out. - - // Don't play sounds from a region with maturity above current agent maturity - LLVector3d pos_global = objectp->getPositionGlobal(); - if (gAgent.canAccessMaturityAtGlobal(pos_global)) - { - // Add audioData starts a transfer internally. - sourcep->addAudioData(datap, false); - } -} - -void process_attached_sound(LLMessageSystem *msg, void **user_data) -{ - F32 gain = 0; - LLUUID sound_id; - LLUUID object_id; - LLUUID owner_id; - U8 flags; - - msg->getUUIDFast(_PREHASH_DataBlock, _PREHASH_SoundID, sound_id); - msg->getUUIDFast(_PREHASH_DataBlock, _PREHASH_ObjectID, object_id); - msg->getUUIDFast(_PREHASH_DataBlock, _PREHASH_OwnerID, owner_id); - msg->getF32Fast(_PREHASH_DataBlock, _PREHASH_Gain, gain); - msg->getU8Fast(_PREHASH_DataBlock, _PREHASH_Flags, flags); - - LLViewerObject *objectp = gObjectList.findObject(object_id); - if (objectp) - { - set_attached_sound(objectp, object_id, sound_id, owner_id, gain, flags); - } - else if (sound_id.notNull()) - { - // we don't know about this object yet, probably it has yet to arrive - // std::map for dupplicate prevention. - postponed_sounds[object_id] = (PostponedSoundData(object_id, sound_id, owner_id, gain, flags)); - clear_expired_postponed_sounds(); - } - else - { - std::map::iterator iter = postponed_sounds.find(object_id); - if (iter != postponed_sounds.end()) - { - postponed_sounds.erase(iter); - } - } -} - -void process_attached_sound_gain_change(LLMessageSystem *mesgsys, void **user_data) -{ - F32 gain = 0; - LLUUID object_guid; - LLViewerObject *objectp = NULL; - - mesgsys->getUUIDFast(_PREHASH_DataBlock, _PREHASH_ObjectID, object_guid); - - if (!((objectp = gObjectList.findObject(object_guid)))) - { - // we don't know about this object, just bail - return; - } - - mesgsys->getF32Fast(_PREHASH_DataBlock, _PREHASH_Gain, gain); - - objectp->adjustAudioGain(gain); -} - - -void process_health_message(LLMessageSystem *mesgsys, void **user_data) -{ - F32 health; - - mesgsys->getF32Fast(_PREHASH_HealthData, _PREHASH_Health, health); - - if (gStatusBar) - { - gStatusBar->setHealth((S32)health); - } -} - - -void process_sim_stats(LLMessageSystem *msg, void **user_data) -{ - S32 count = msg->getNumberOfBlocks("Stat"); - for (S32 i = 0; i < count; ++i) - { - U32 stat_id; - F32 stat_value; - msg->getU32("Stat", "StatID", stat_id, i); - msg->getF32("Stat", "StatValue", stat_value, i); - auto measurementp = LLStatViewer::SimMeasurementSampler::getInstance((ESimStatID)stat_id); - - if (measurementp ) - { - measurementp->sample(stat_value); - } - else - { - LL_WARNS() << "Unknown sim stat identifier: " << stat_id << LL_ENDL; - } - } - - // - // Various hacks that aren't statistics, but are being handled here. - // - U32 max_tasks_per_region; - U64 region_flags; - msg->getU32("Region", "ObjectCapacity", max_tasks_per_region); - - if (msg->has(_PREHASH_RegionInfo)) - { - msg->getU64("RegionInfo", "RegionFlagsExtended", region_flags); - } - else - { - U32 flags = 0; - msg->getU32("Region", "RegionFlags", flags); - region_flags = flags; - } - - LLViewerRegion* regionp = gAgent.getRegion(); - if (regionp) - { - bool was_flying = gAgent.getFlying(); - regionp->setRegionFlags(region_flags); - regionp->setMaxTasks(max_tasks_per_region); - // HACK: This makes agents drop from the sky if the region is - // set to no fly while people are still in the sim. - if (was_flying && regionp->getBlockFly()) - { - gAgent.setFlying(gAgent.canFly()); - } - } -} - - - -void process_avatar_animation(LLMessageSystem *mesgsys, void **user_data) -{ - LLUUID animation_id; - LLUUID uuid; - S32 anim_sequence_id; - LLVOAvatar *avatarp = NULL; - - mesgsys->getUUIDFast(_PREHASH_Sender, _PREHASH_ID, uuid); - - LLViewerObject *objp = gObjectList.findObject(uuid); - if (objp) - { - avatarp = objp->asAvatar(); - } - - if (!avatarp) - { - // no agent by this ID...error? - LL_WARNS("Messaging") << "Received animation state for unknown avatar " << uuid << LL_ENDL; - return; - } - - S32 num_blocks = mesgsys->getNumberOfBlocksFast(_PREHASH_AnimationList); - S32 num_source_blocks = mesgsys->getNumberOfBlocksFast(_PREHASH_AnimationSourceList); - - LL_DEBUGS("Messaging", "Motion") << "Processing " << num_blocks << " Animations" << LL_ENDL; - - //clear animation flags - avatarp->mSignaledAnimations.clear(); - - if (avatarp->isSelf()) - { - LLUUID object_id; - - for( S32 i = 0; i < num_blocks; i++ ) - { - mesgsys->getUUIDFast(_PREHASH_AnimationList, _PREHASH_AnimID, animation_id, i); - mesgsys->getS32Fast(_PREHASH_AnimationList, _PREHASH_AnimSequenceID, anim_sequence_id, i); - - avatarp->mSignaledAnimations[animation_id] = anim_sequence_id; - - // *HACK: Disabling flying mode if it has been enabled shortly before the agent - // stand up animation is signaled. In this case we don't get a signal to start - // flying animation from server, the AGENT_CONTROL_FLY flag remains set but the - // avatar does not play flying animation, so we switch flying mode off. - // See LLAgent::setFlying(). This may cause "Stop Flying" button to blink. - // See EXT-2781. - if (animation_id == ANIM_AGENT_STANDUP && gAgent.getFlying()) - { - gAgent.setFlying(false); - } - - if (i < num_source_blocks) - { - mesgsys->getUUIDFast(_PREHASH_AnimationSourceList, _PREHASH_ObjectID, object_id, i); - - LLViewerObject* object = gObjectList.findObject(object_id); - if (object) - { - object->setFlagsWithoutUpdate(FLAGS_ANIM_SOURCE, true); - - bool anim_found = false; - LLVOAvatar::AnimSourceIterator anim_it = avatarp->mAnimationSources.find(object_id); - for (;anim_it != avatarp->mAnimationSources.end(); ++anim_it) - { - if (anim_it->first != object_id) - { - // elements with the same key are always contiguous, bail if we went past the - // end of this object's animations - break; - } - if (anim_it->second == animation_id) - { - anim_found = true; - break; - } - } - - if (!anim_found) - { - avatarp->mAnimationSources.insert(LLVOAvatar::AnimationSourceMap::value_type(object_id, animation_id)); - } - } - LL_DEBUGS("Messaging", "Motion") << "Anim sequence ID: " << anim_sequence_id - << " Animation id: " << animation_id - << " From block: " << object_id << LL_ENDL; - } - else - { - LL_DEBUGS("Messaging", "Motion") << "Anim sequence ID: " << anim_sequence_id - << " Animation id: " << animation_id << LL_ENDL; - } - } - } - else - { - for( S32 i = 0; i < num_blocks; i++ ) - { - mesgsys->getUUIDFast(_PREHASH_AnimationList, _PREHASH_AnimID, animation_id, i); - mesgsys->getS32Fast(_PREHASH_AnimationList, _PREHASH_AnimSequenceID, anim_sequence_id, i); - avatarp->mSignaledAnimations[animation_id] = anim_sequence_id; - } - } - - if (num_blocks) - { - avatarp->processAnimationStateChanges(); - } -} - - -void process_object_animation(LLMessageSystem *mesgsys, void **user_data) -{ - LLUUID animation_id; - LLUUID uuid; - S32 anim_sequence_id; - - mesgsys->getUUIDFast(_PREHASH_Sender, _PREHASH_ID, uuid); - - LL_DEBUGS("AnimatedObjectsNotify") << "Received animation state for object " << uuid << LL_ENDL; - - signaled_animation_map_t signaled_anims; - S32 num_blocks = mesgsys->getNumberOfBlocksFast(_PREHASH_AnimationList); - LL_DEBUGS("AnimatedObjectsNotify") << "processing object animation requests, num_blocks " << num_blocks << " uuid " << uuid << LL_ENDL; - for( S32 i = 0; i < num_blocks; i++ ) - { - mesgsys->getUUIDFast(_PREHASH_AnimationList, _PREHASH_AnimID, animation_id, i); - mesgsys->getS32Fast(_PREHASH_AnimationList, _PREHASH_AnimSequenceID, anim_sequence_id, i); - signaled_anims[animation_id] = anim_sequence_id; - LL_DEBUGS("AnimatedObjectsNotify") << "added signaled_anims animation request for object " - << uuid << " animation id " << animation_id << LL_ENDL; - } - LLObjectSignaledAnimationMap::instance().getMap()[uuid] = signaled_anims; - - LLViewerObject *objp = gObjectList.findObject(uuid); - if (!objp || objp->isDead()) - { - LL_DEBUGS("AnimatedObjectsNotify") << "Received animation state for unknown object " << uuid << LL_ENDL; - return; - } - - LLVOVolume *volp = dynamic_cast(objp); - if (!volp) - { - LL_DEBUGS("AnimatedObjectsNotify") << "Received animation state for non-volume object " << uuid << LL_ENDL; - return; - } - - if (!volp->isAnimatedObject()) - { - LL_DEBUGS("AnimatedObjectsNotify") << "Received animation state for non-animated object " << uuid << LL_ENDL; - return; - } - - volp->updateControlAvatar(); - LLControlAvatar *avatarp = volp->getControlAvatar(); - if (!avatarp) - { - LL_DEBUGS("AnimatedObjectsNotify") << "Received animation request for object with no control avatar, ignoring " << uuid << LL_ENDL; - return; - } - - if (!avatarp->mPlaying) - { - avatarp->mPlaying = true; - //if (!avatarp->mRootVolp->isAnySelected()) - { - avatarp->updateVolumeGeom(); - avatarp->mRootVolp->recursiveMarkForUpdate(); - } - } - - avatarp->updateAnimations(); -} - - -void process_avatar_appearance(LLMessageSystem *mesgsys, void **user_data) -{ - LLUUID uuid; - mesgsys->getUUIDFast(_PREHASH_Sender, _PREHASH_ID, uuid); - - LLVOAvatar* avatarp = (LLVOAvatar *)gObjectList.findObject(uuid); - if (avatarp) - { - avatarp->processAvatarAppearance( mesgsys ); - } - else - { - LL_WARNS("Messaging") << "avatar_appearance sent for unknown avatar " << uuid << LL_ENDL; - } -} - -void process_camera_constraint(LLMessageSystem *mesgsys, void **user_data) -{ - LLVector4 cameraCollidePlane; - mesgsys->getVector4Fast(_PREHASH_CameraCollidePlane, _PREHASH_Plane, cameraCollidePlane); - - gAgentCamera.setCameraCollidePlane(cameraCollidePlane); -} - -void near_sit_object(bool success, void *data) -{ - if (success) - { - // Send message to sit on object - gMessageSystem->newMessageFast(_PREHASH_AgentSit); - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - gAgent.sendReliableMessage(); - } -} - -void process_avatar_sit_response(LLMessageSystem *mesgsys, void **user_data) -{ - LLVector3 sitPosition; - LLQuaternion sitRotation; - LLUUID sitObjectID; - bool use_autopilot; - mesgsys->getUUIDFast(_PREHASH_SitObject, _PREHASH_ID, sitObjectID); - mesgsys->getBOOLFast(_PREHASH_SitTransform, _PREHASH_AutoPilot, use_autopilot); - mesgsys->getVector3Fast(_PREHASH_SitTransform, _PREHASH_SitPosition, sitPosition); - mesgsys->getQuatFast(_PREHASH_SitTransform, _PREHASH_SitRotation, sitRotation); - LLVector3 camera_eye; - mesgsys->getVector3Fast(_PREHASH_SitTransform, _PREHASH_CameraEyeOffset, camera_eye); - LLVector3 camera_at; - mesgsys->getVector3Fast(_PREHASH_SitTransform, _PREHASH_CameraAtOffset, camera_at); - bool force_mouselook; - mesgsys->getBOOLFast(_PREHASH_SitTransform, _PREHASH_ForceMouselook, force_mouselook); - - if (isAgentAvatarValid() && dist_vec_squared(camera_eye, camera_at) > CAMERA_POSITION_THRESHOLD_SQUARED) - { - gAgentCamera.setSitCamera(sitObjectID, camera_eye, camera_at); - } - - gAgentCamera.setForceMouselook(force_mouselook); - // Forcing turning off flying here to prevent flying after pressing "Stand" - // to stand up from an object. See EXT-1655. - gAgent.setFlying(false); - - LLViewerObject* object = gObjectList.findObject(sitObjectID); - if (object) - { - LLVector3 sit_spot = object->getPositionAgent() + (sitPosition * object->getRotation()); - if (!use_autopilot || (isAgentAvatarValid() && gAgentAvatarp->isSitting() && gAgentAvatarp->getRoot() == object->getRoot())) - { - //we're already sitting on this object, so don't autopilot - } - else - { - gAgent.startAutoPilotGlobal(gAgent.getPosGlobalFromAgent(sit_spot), "Sit", &sitRotation, near_sit_object, NULL, 0.5f); - } - } - else - { - LL_WARNS("Messaging") << "Received sit approval for unknown object " << sitObjectID << LL_ENDL; - } -} - -void process_clear_follow_cam_properties(LLMessageSystem *mesgsys, void **user_data) -{ - LLUUID source_id; - - mesgsys->getUUIDFast(_PREHASH_ObjectData, _PREHASH_ObjectID, source_id); - - LLFollowCamMgr::getInstance()->removeFollowCamParams(source_id); -} - -void process_set_follow_cam_properties(LLMessageSystem *mesgsys, void **user_data) -{ - S32 type; - F32 value; - bool settingPosition = false; - bool settingFocus = false; - bool settingFocusOffset = false; - LLVector3 position; - LLVector3 focus; - LLVector3 focus_offset; - - LLUUID source_id; - - mesgsys->getUUIDFast(_PREHASH_ObjectData, _PREHASH_ObjectID, source_id); - - LLViewerObject* objectp = gObjectList.findObject(source_id); - if (objectp) - { - objectp->setFlagsWithoutUpdate(FLAGS_CAMERA_SOURCE, true); - } - - S32 num_objects = mesgsys->getNumberOfBlocks("CameraProperty"); - for (S32 block_index = 0; block_index < num_objects; block_index++) - { - mesgsys->getS32("CameraProperty", "Type", type, block_index); - mesgsys->getF32("CameraProperty", "Value", value, block_index); - switch(type) - { - case FOLLOWCAM_PITCH: - LLFollowCamMgr::getInstance()->setPitch(source_id, value); - break; - case FOLLOWCAM_FOCUS_OFFSET_X: - focus_offset.mV[VX] = value; - settingFocusOffset = true; - break; - case FOLLOWCAM_FOCUS_OFFSET_Y: - focus_offset.mV[VY] = value; - settingFocusOffset = true; - break; - case FOLLOWCAM_FOCUS_OFFSET_Z: - focus_offset.mV[VZ] = value; - settingFocusOffset = true; - break; - case FOLLOWCAM_POSITION_LAG: - LLFollowCamMgr::getInstance()->setPositionLag(source_id, value); - break; - case FOLLOWCAM_FOCUS_LAG: - LLFollowCamMgr::getInstance()->setFocusLag(source_id, value); - break; - case FOLLOWCAM_DISTANCE: - LLFollowCamMgr::getInstance()->setDistance(source_id, value); - break; - case FOLLOWCAM_BEHINDNESS_ANGLE: - LLFollowCamMgr::getInstance()->setBehindnessAngle(source_id, value); - break; - case FOLLOWCAM_BEHINDNESS_LAG: - LLFollowCamMgr::getInstance()->setBehindnessLag(source_id, value); - break; - case FOLLOWCAM_POSITION_THRESHOLD: - LLFollowCamMgr::getInstance()->setPositionThreshold(source_id, value); - break; - case FOLLOWCAM_FOCUS_THRESHOLD: - LLFollowCamMgr::getInstance()->setFocusThreshold(source_id, value); - break; - case FOLLOWCAM_ACTIVE: - //if 1, set using followcam,. - LLFollowCamMgr::getInstance()->setCameraActive(source_id, value != 0.f); - break; - case FOLLOWCAM_POSITION_X: - settingPosition = true; - position.mV[ 0 ] = value; - break; - case FOLLOWCAM_POSITION_Y: - settingPosition = true; - position.mV[ 1 ] = value; - break; - case FOLLOWCAM_POSITION_Z: - settingPosition = true; - position.mV[ 2 ] = value; - break; - case FOLLOWCAM_FOCUS_X: - settingFocus = true; - focus.mV[ 0 ] = value; - break; - case FOLLOWCAM_FOCUS_Y: - settingFocus = true; - focus.mV[ 1 ] = value; - break; - case FOLLOWCAM_FOCUS_Z: - settingFocus = true; - focus.mV[ 2 ] = value; - break; - case FOLLOWCAM_POSITION_LOCKED: - LLFollowCamMgr::getInstance()->setPositionLocked(source_id, value != 0.f); - break; - case FOLLOWCAM_FOCUS_LOCKED: - LLFollowCamMgr::getInstance()->setFocusLocked(source_id, value != 0.f); - break; - - default: - break; - } - } - - if ( settingPosition ) - { - LLFollowCamMgr::getInstance()->setPosition(source_id, position); - } - if ( settingFocus ) - { - LLFollowCamMgr::getInstance()->setFocus(source_id, focus); - } - if ( settingFocusOffset ) - { - LLFollowCamMgr::getInstance()->setFocusOffset(source_id, focus_offset); - } -} -//end Ventrella - - -// Culled from newsim lltask.cpp -void process_name_value(LLMessageSystem *mesgsys, void **user_data) -{ - std::string temp_str; - LLUUID id; - S32 i, num_blocks; - - mesgsys->getUUIDFast(_PREHASH_TaskData, _PREHASH_ID, id); - - LLViewerObject* object = gObjectList.findObject(id); - - if (object) - { - num_blocks = mesgsys->getNumberOfBlocksFast(_PREHASH_NameValueData); - for (i = 0; i < num_blocks; i++) - { - mesgsys->getStringFast(_PREHASH_NameValueData, _PREHASH_NVPair, temp_str, i); - LL_INFOS("Messaging") << "Added to object Name Value: " << temp_str << LL_ENDL; - object->addNVPair(temp_str); - } - } - else - { - LL_INFOS("Messaging") << "Can't find object " << id << " to add name value pair" << LL_ENDL; - } -} - -void process_remove_name_value(LLMessageSystem *mesgsys, void **user_data) -{ - std::string temp_str; - LLUUID id; - S32 i, num_blocks; - - mesgsys->getUUIDFast(_PREHASH_TaskData, _PREHASH_ID, id); - - LLViewerObject* object = gObjectList.findObject(id); - - if (object) - { - num_blocks = mesgsys->getNumberOfBlocksFast(_PREHASH_NameValueData); - for (i = 0; i < num_blocks; i++) - { - mesgsys->getStringFast(_PREHASH_NameValueData, _PREHASH_NVPair, temp_str, i); - LL_INFOS("Messaging") << "Removed from object Name Value: " << temp_str << LL_ENDL; - object->removeNVPair(temp_str); - } - } - else - { - LL_INFOS("Messaging") << "Can't find object " << id << " to remove name value pair" << LL_ENDL; - } -} - -void process_kick_user(LLMessageSystem *msg, void** /*user_data*/) -{ - std::string message; - - msg->getStringFast(_PREHASH_UserInfo, _PREHASH_Reason, message); - - LLAppViewer::instance()->forceDisconnect(message); -} - - -/* -void process_user_list_reply(LLMessageSystem *msg, void **user_data) -{ - LLUserList::processUserListReply(msg, user_data); - return; - char firstname[MAX_STRING+1]; - char lastname[MAX_STRING+1]; - U8 status; - S32 user_count; - - user_count = msg->getNumberOfBlocks("UserBlock"); - - for (S32 i = 0; i < user_count; i++) - { - msg->getData("UserBlock", i, "FirstName", firstname); - msg->getData("UserBlock", i, "LastName", lastname); - msg->getData("UserBlock", i, "Status", &status); - - if (status & 0x01) - { - dialog_friends_add_friend(buffer, true); - } - else - { - dialog_friends_add_friend(buffer, false); - } - } - - dialog_friends_done_adding(); -} -*/ - -// this is not handled in processUpdateMessage -/* -void process_time_dilation(LLMessageSystem *msg, void **user_data) -{ - // get the time_dilation - U16 foo; - msg->getData("TimeDilation", "TimeDilation", &foo); - F32 time_dilation = ((F32) foo) / 65535.f; - - // get the pointer to the right region - U32 ip = msg->getSenderIP(); - U32 port = msg->getSenderPort(); - LLViewerRegion *regionp = LLWorld::getInstance()->getRegion(ip, port); - if (regionp) - { - regionp->setTimeDilation(time_dilation); - } -} -*/ - - -void process_money_balance_reply( LLMessageSystem* msg, void** ) -{ - S32 balance = 0; - S32 credit = 0; - S32 committed = 0; - std::string desc; - LLUUID tid; - - msg->getUUID("MoneyData", "TransactionID", tid); - msg->getS32("MoneyData", "MoneyBalance", balance); - msg->getS32("MoneyData", "SquareMetersCredit", credit); - msg->getS32("MoneyData", "SquareMetersCommitted", committed); - msg->getStringFast(_PREHASH_MoneyData, _PREHASH_Description, desc); - LL_INFOS("Messaging") << "L$, credit, committed: " << balance << " " << credit << " " - << committed << LL_ENDL; - - if (gStatusBar) - { - gStatusBar->setBalance(balance); - gStatusBar->setLandCredit(credit); - gStatusBar->setLandCommitted(committed); - } - - if (desc.empty() - || !gSavedSettings.getBOOL("NotifyMoneyChange")) - { - // ...nothing to display - return; - } - - // Suppress duplicate messages about the same transaction - static std::deque recent; - if (std::find(recent.rbegin(), recent.rend(), tid) != recent.rend()) - { - return; - } - - // Once the 'recent' container gets large enough, chop some - // off the beginning. - const U32 MAX_LOOKBACK = 30; - const S32 POP_FRONT_SIZE = 12; - if(recent.size() > MAX_LOOKBACK) - { - LL_DEBUGS("Messaging") << "Removing oldest transaction records" << LL_ENDL; - recent.erase(recent.begin(), recent.begin() + POP_FRONT_SIZE); - } - //LL_DEBUGS("Messaging") << "Pushing back transaction " << tid << LL_ENDL; - recent.push_back(tid); - - if (msg->has("TransactionInfo")) - { - // ...message has extended info for localization - process_money_balance_reply_extended(msg); - } - else - { - // Only old dev grids will not supply the TransactionInfo block, - // so we can just use the hard-coded English string. - LLSD args; - args["MESSAGE"] = desc; - LLNotificationsUtil::add("SystemMessage", args); - } -} - -static std::string reason_from_transaction_type(S32 transaction_type, - const std::string& item_desc) -{ - // *NOTE: The keys for the reason strings are unusual because - // an earlier version of the code used English language strings - // extracted from hard-coded server English descriptions. - // Keeping them so we don't have to re-localize them. - switch (transaction_type) - { - case TRANS_OBJECT_SALE: - { - LLStringUtil::format_map_t arg; - arg["ITEM"] = item_desc; - return LLTrans::getString("for item", arg); - } - case TRANS_LAND_SALE: - return LLTrans::getString("for a parcel of land"); - - case TRANS_LAND_PASS_SALE: - return LLTrans::getString("for a land access pass"); - - case TRANS_GROUP_LAND_DEED: - return LLTrans::getString("for deeding land"); - - case TRANS_GROUP_CREATE: - return LLTrans::getString("to create a group"); - - case TRANS_GROUP_JOIN: - return LLTrans::getString("to join a group"); - - case TRANS_UPLOAD_CHARGE: - return LLTrans::getString("to upload"); - - case TRANS_CLASSIFIED_CHARGE: - return LLTrans::getString("to publish a classified ad"); - - case TRANS_GIFT: - // Simulator returns "Payment" if no custom description has been entered - return (item_desc == "Payment" ? std::string() : item_desc); - - // These have no reason to display, but are expected and should not - // generate warnings - case TRANS_PAY_OBJECT: - case TRANS_OBJECT_PAYS: - return std::string(); - - default: - LL_WARNS() << "Unknown transaction type " - << transaction_type << LL_ENDL; - return std::string(); - } -} - -static void money_balance_group_notify(const LLUUID& group_id, - const std::string& name, - bool is_group, - std::string notification, - LLSD args, - LLSD payload) -{ - // Message uses name SLURLs, don't actually have to substitute in - // the name. We're just making sure it's available. - // Notification is either PaymentReceived or PaymentSent - LLNotificationsUtil::add(notification, args, payload); -} - -static void money_balance_avatar_notify(const LLUUID& agent_id, - const LLAvatarName& av_name, - std::string notification, - LLSD args, - LLSD payload) -{ - // Message uses name SLURLs, don't actually have to substitute in - // the name. We're just making sure it's available. - // Notification is either PaymentReceived or PaymentSent - LLNotificationsUtil::add(notification, args, payload); -} - -static void process_money_balance_reply_extended(LLMessageSystem* msg) -{ - // Added in server 1.40 and viewer 2.1, support for localization - // and agent ids for name lookup. - S32 transaction_type = 0; - LLUUID source_id; - bool is_source_group = false; - LLUUID dest_id; - bool is_dest_group = false; - S32 amount = 0; - std::string item_description; - bool success = false; - - msg->getS32("TransactionInfo", "TransactionType", transaction_type); - msg->getUUID("TransactionInfo", "SourceID", source_id); - msg->getBOOL("TransactionInfo", "IsSourceGroup", is_source_group); - msg->getUUID("TransactionInfo", "DestID", dest_id); - msg->getBOOL("TransactionInfo", "IsDestGroup", is_dest_group); - msg->getS32("TransactionInfo", "Amount", amount); - msg->getString("TransactionInfo", "ItemDescription", item_description); - msg->getBOOL("MoneyData", "TransactionSuccess", success); - LL_INFOS("Money") << "MoneyBalanceReply source " << source_id - << " dest " << dest_id - << " type " << transaction_type - << " item " << item_description << LL_ENDL; - - if (source_id.isNull() && dest_id.isNull()) - { - // this is a pure balance update, no notification required - return; - } - - std::string source_slurl; - if (is_source_group) - { - source_slurl = - LLSLURL( "group", source_id, "inspect").getSLURLString(); - } - else - { - source_slurl = - LLSLURL( "agent", source_id, "completename").getSLURLString(); - } - - std::string dest_slurl; - if (is_dest_group) - { - dest_slurl = - LLSLURL( "group", dest_id, "inspect").getSLURLString(); - } - else - { - dest_slurl = - LLSLURL( "agent", dest_id, "completename").getSLURLString(); - } - - std::string reason = - reason_from_transaction_type(transaction_type, item_description); - - LLStringUtil::format_map_t args; - args["REASON"] = reason; // could be empty - args["AMOUNT"] = llformat("%d", amount); - - // Need to delay until name looked up, so need to know whether or not - // is group - bool is_name_group = false; - LLUUID name_id; - std::string message; - std::string notification; - LLSD final_args; - LLSD payload; - - bool you_paid_someone = (source_id == gAgentID); - std::string gift_suffix = (transaction_type == TRANS_GIFT ? "_gift" : ""); - if (you_paid_someone) - { - if(!gSavedSettings.getBOOL("NotifyMoneySpend")) - { - return; - } - args["NAME"] = dest_slurl; - is_name_group = is_dest_group; - name_id = dest_id; - if (!reason.empty()) - { - if (dest_id.notNull()) - { - message = success ? LLTrans::getString("you_paid_ldollars" + gift_suffix, args) : - LLTrans::getString("you_paid_failure_ldollars" + gift_suffix, args); - } - else - { - // transaction fee to the system, eg, to create a group - message = success ? LLTrans::getString("you_paid_ldollars_no_name", args) : - LLTrans::getString("you_paid_failure_ldollars_no_name", args); - } - } - else - { - if (dest_id.notNull()) - { - message = success ? LLTrans::getString("you_paid_ldollars_no_reason", args) : - LLTrans::getString("you_paid_failure_ldollars_no_reason", args); - } - else - { - // no target, no reason, you just paid money - message = success ? LLTrans::getString("you_paid_ldollars_no_info", args) : - LLTrans::getString("you_paid_failure_ldollars_no_info", args); - } - } - final_args["MESSAGE"] = message; - payload["dest_id"] = dest_id; - notification = success ? "PaymentSent" : "PaymentFailure"; - } - else - { - // ...someone paid you - if(!gSavedSettings.getBOOL("NotifyMoneyReceived")) - { - return; - } - args["NAME"] = source_slurl; - is_name_group = is_source_group; - name_id = source_id; - - if (!reason.empty() && !LLMuteList::getInstance()->isMuted(source_id)) - { - message = LLTrans::getString("paid_you_ldollars" + gift_suffix, args); - } - else - { - message = LLTrans::getString("paid_you_ldollars_no_reason", args); - } - final_args["MESSAGE"] = message; - - // make notification loggable - payload["from_id"] = source_id; - notification = "PaymentReceived"; - } - - // Despite using SLURLs, wait until the name is available before - // showing the notification, otherwise the UI layout is strange and - // the user sees a "Loading..." message - if (is_name_group) - { - gCacheName->getGroup(name_id, - boost::bind(&money_balance_group_notify, - _1, _2, _3, - notification, final_args, payload)); - } - else - { - LLAvatarNameCache::get(name_id, boost::bind(&money_balance_avatar_notify, _1, _2, notification, final_args, payload)); - } -} - -bool handle_prompt_for_maturity_level_change_callback(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - - if (0 == option) - { - // set the preference to the maturity of the region we're calling - U8 preferredMaturity = static_cast(notification["payload"]["_region_access"].asInteger()); - gSavedSettings.setU32("PreferredMaturity", static_cast(preferredMaturity)); - } - - return false; -} - -bool handle_prompt_for_maturity_level_change_and_reteleport_callback(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - - if (0 == option) - { - // set the preference to the maturity of the region we're calling - U8 preferredMaturity = static_cast(notification["payload"]["_region_access"].asInteger()); - gSavedSettings.setU32("PreferredMaturity", static_cast(preferredMaturity)); - gAgent.setMaturityRatingChangeDuringTeleport(preferredMaturity); - gAgent.restartFailedTeleportRequest(); - } - else - { - gAgent.clearTeleportRequest(); - } - - return false; -} - -// some of the server notifications need special handling. This is where we do that. -bool handle_special_notification(std::string notificationID, LLSD& llsdBlock) -{ - bool returnValue = false; - if(llsdBlock.has("_region_access")) - { - U8 regionAccess = static_cast(llsdBlock["_region_access"].asInteger()); - std::string regionMaturity = LLViewerRegion::accessToString(regionAccess); - LLStringUtil::toLower(regionMaturity); - llsdBlock["REGIONMATURITY"] = regionMaturity; - LLNotificationPtr maturityLevelNotification; - std::string notifySuffix = "_Notify"; - if (regionAccess == SIM_ACCESS_MATURE) - { - if (gAgent.isTeen()) - { - gAgent.clearTeleportRequest(); - maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_AdultsOnlyContent", llsdBlock); - returnValue = true; - - notifySuffix = "_NotifyAdultsOnly"; - } - else if (gAgent.prefersPG()) - { - maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_Change", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_callback); - returnValue = true; - } - else if (LLStringUtil::compareStrings(notificationID, "RegionEntryAccessBlocked") == 0) - { - maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_PreferencesOutOfSync", llsdBlock, llsdBlock); - returnValue = true; - } - } - else if (regionAccess == SIM_ACCESS_ADULT) - { - if (!gAgent.isAdult()) - { - gAgent.clearTeleportRequest(); - maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_AdultsOnlyContent", llsdBlock); - returnValue = true; - - notifySuffix = "_NotifyAdultsOnly"; - } - else if (gAgent.prefersPG() || gAgent.prefersMature()) - { - maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_Change", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_callback); - returnValue = true; - } - else if (LLStringUtil::compareStrings(notificationID, "RegionEntryAccessBlocked") == 0) - { - maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_PreferencesOutOfSync", llsdBlock, llsdBlock); - returnValue = true; - } - } - - if ((maturityLevelNotification == NULL) || maturityLevelNotification->isIgnored()) - { - // Given a simple notification if no maturityLevelNotification is set or it is ignore - LLNotificationsUtil::add(notificationID + notifySuffix, llsdBlock); - } - } - - return returnValue; -} - -bool handle_trusted_experiences_notification(const LLSD& llsdBlock) -{ - if(llsdBlock.has("trusted_experiences")) - { - std::ostringstream str; - const LLSD& experiences = llsdBlock["trusted_experiences"]; - LLSD::array_const_iterator it = experiences.beginArray(); - for(/**/; it != experiences.endArray(); ++it) - { - str<asUUID(), "profile").getSLURLString() << "\n"; - } - std::string str_list = str.str(); - if(!str_list.empty()) - { - LLNotificationsUtil::add("TrustedExperiencesAvailable", LLSD::emptyMap().with("EXPERIENCE_LIST", (LLSD)str_list)); - return true; - } - } - return false; -} - -// some of the server notifications need special handling. This is where we do that. -bool handle_teleport_access_blocked(LLSD& llsdBlock, const std::string & notificationID, const std::string & defaultMessage) -{ - bool returnValue = false; - if(llsdBlock.has("_region_access")) - { - U8 regionAccess = static_cast(llsdBlock["_region_access"].asInteger()); - std::string regionMaturity = LLViewerRegion::accessToString(regionAccess); - LLStringUtil::toLower(regionMaturity); - llsdBlock["REGIONMATURITY"] = regionMaturity; - - LLNotificationPtr tp_failure_notification; - std::string notifySuffix; - - if (notificationID == std::string("TeleportEntryAccessBlocked")) - { - notifySuffix = "_Notify"; - if (regionAccess == SIM_ACCESS_MATURE) - { - if (gAgent.isTeen()) - { - gAgent.clearTeleportRequest(); - tp_failure_notification = LLNotificationsUtil::add(notificationID+"_AdultsOnlyContent", llsdBlock); - returnValue = true; - - notifySuffix = "_NotifyAdultsOnly"; - } - else if (gAgent.prefersPG()) - { - if (gAgent.hasRestartableFailedTeleportRequest()) - { - tp_failure_notification = LLNotificationsUtil::add(notificationID+"_ChangeAndReTeleport", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_and_reteleport_callback); - returnValue = true; - } - else - { - gAgent.clearTeleportRequest(); - tp_failure_notification = LLNotificationsUtil::add(notificationID+"_Change", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_callback); - returnValue = true; - } - } - else - { - gAgent.clearTeleportRequest(); - tp_failure_notification = LLNotificationsUtil::add(notificationID+"_PreferencesOutOfSync", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_callback); - returnValue = true; - } - } - else if (regionAccess == SIM_ACCESS_ADULT) - { - if (!gAgent.isAdult()) - { - gAgent.clearTeleportRequest(); - tp_failure_notification = LLNotificationsUtil::add(notificationID+"_AdultsOnlyContent", llsdBlock); - returnValue = true; - - notifySuffix = "_NotifyAdultsOnly"; - } - else if (gAgent.prefersPG() || gAgent.prefersMature()) - { - if (gAgent.hasRestartableFailedTeleportRequest()) - { - tp_failure_notification = LLNotificationsUtil::add(notificationID+"_ChangeAndReTeleport", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_and_reteleport_callback); - returnValue = true; - } - else - { - gAgent.clearTeleportRequest(); - tp_failure_notification = LLNotificationsUtil::add(notificationID+"_Change", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_callback); - returnValue = true; - } - } - else - { - gAgent.clearTeleportRequest(); - tp_failure_notification = LLNotificationsUtil::add(notificationID+"_PreferencesOutOfSync", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_callback); - returnValue = true; - } - } - } // End of special handling for "TeleportEntryAccessBlocked" - else - { // Normal case, no message munging - gAgent.clearTeleportRequest(); - if (LLNotifications::getInstance()->templateExists(notificationID)) - { - tp_failure_notification = LLNotificationsUtil::add(notificationID, llsdBlock, llsdBlock); - } - else - { - llsdBlock["MESSAGE"] = defaultMessage; - tp_failure_notification = LLNotificationsUtil::add("GenericAlertOK", llsdBlock); - } - returnValue = true; - } - - if ((tp_failure_notification == NULL) || tp_failure_notification->isIgnored()) - { - // Given a simple notification if no tp_failure_notification is set or it is ignore - LLNotificationsUtil::add(notificationID + notifySuffix, llsdBlock); - } - } - - handle_trusted_experiences_notification(llsdBlock); - return returnValue; -} - -bool attempt_standard_notification(LLMessageSystem* msgsystem) -{ - // if we have additional alert data - if (msgsystem->has(_PREHASH_AlertInfo) && msgsystem->getNumberOfBlocksFast(_PREHASH_AlertInfo) > 0) - { - // notification was specified using the new mechanism, so we can just handle it here - std::string notificationID; - msgsystem->getStringFast(_PREHASH_AlertInfo, _PREHASH_Message, notificationID); - - //SL-13824 skip notification when both joining a group and leaving a group - //remove this after server stops sending these messages - if (notificationID == "JoinGroupSuccess" || - notificationID == "GroupDepart") - { - return true; - } - - if (!LLNotifications::getInstance()->templateExists(notificationID)) - { - return false; - } - - std::string llsdRaw; - LLSD llsdBlock; - msgsystem->getStringFast(_PREHASH_AlertInfo, _PREHASH_Message, notificationID); - msgsystem->getStringFast(_PREHASH_AlertInfo, _PREHASH_ExtraParams, llsdRaw); - if (llsdRaw.length()) - { - std::istringstream llsdData(llsdRaw); - if (!LLSDSerialize::deserialize(llsdBlock, llsdData, llsdRaw.length())) - { - LL_WARNS() << "attempt_standard_notification: Attempted to read notification parameter data into LLSD but failed:" << llsdRaw << LL_ENDL; - } - } - - - handle_trusted_experiences_notification(llsdBlock); - - if ( - (notificationID == "RegionEntryAccessBlocked") || - (notificationID == "LandClaimAccessBlocked") || - (notificationID == "LandBuyAccessBlocked") - - ) - { - /*--------------------------------------------------------------------- - (Commented so a grep will find the notification strings, since - we construct them on the fly; if you add additional notifications, - please update the comment.) - - Could throw any of the following notifications: - - RegionEntryAccessBlocked - RegionEntryAccessBlocked_Notify - RegionEntryAccessBlocked_NotifyAdultsOnly - RegionEntryAccessBlocked_Change - RegionEntryAccessBlocked_AdultsOnlyContent - RegionEntryAccessBlocked_ChangeAndReTeleport - LandClaimAccessBlocked - LandClaimAccessBlocked_Notify - LandClaimAccessBlocked_NotifyAdultsOnly - LandClaimAccessBlocked_Change - LandClaimAccessBlocked_AdultsOnlyContent - LandBuyAccessBlocked - LandBuyAccessBlocked_Notify - LandBuyAccessBlocked_NotifyAdultsOnly - LandBuyAccessBlocked_Change - LandBuyAccessBlocked_AdultsOnlyContent - - -----------------------------------------------------------------------*/ - static LLCachedControl ban_lines_mode(gSavedSettings , "ShowBanLines" , LLViewerParcelMgr::PARCEL_BAN_LINES_ON_COLLISION); - if (ban_lines_mode == LLViewerParcelMgr::PARCEL_BAN_LINES_ON_COLLISION) - { - LLViewerParcelMgr::getInstance()->resetCollisionTimer(); - } - if (handle_special_notification(notificationID, llsdBlock)) - { - return true; - } - } - // HACK -- handle callbacks for specific alerts. - if( notificationID == "HomePositionSet" ) - { - // save the home location image to disk - std::string snap_filename = gDirUtilp->getLindenUserDir(); - snap_filename += gDirUtilp->getDirDelimiter(); - snap_filename += LLStartUp::getScreenHomeFilename(); - gViewerWindow->saveSnapshot(snap_filename, - gViewerWindow->getWindowWidthRaw(), - gViewerWindow->getWindowHeightRaw(), - false, //UI - gSavedSettings.getBOOL("RenderHUDInSnapshot"), - false, - LLSnapshotModel::SNAPSHOT_TYPE_COLOR, - LLSnapshotModel::SNAPSHOT_FORMAT_PNG); - } - - if (notificationID == "RegionRestartMinutes" || - notificationID == "RegionRestartSeconds") - { - S32 seconds; - if (notificationID == "RegionRestartMinutes") - { - seconds = 60 * static_cast(llsdBlock["MINUTES"].asInteger()); - } - else - { - seconds = static_cast(llsdBlock["SECONDS"].asInteger()); - } - - LLFloaterRegionRestarting* floaterp = LLFloaterReg::findTypedInstance("region_restarting"); - - if (floaterp) - { - LLFloaterRegionRestarting::updateTime(seconds); - } - else - { - LLSD params; - params["NAME"] = llsdBlock["NAME"]; - params["SECONDS"] = (LLSD::Integer)seconds; - LLFloaterRegionRestarting* restarting_floater = dynamic_cast(LLFloaterReg::showInstance("region_restarting", params)); - if(restarting_floater) - { - restarting_floater->center(); - } - } - - make_ui_sound("UISndRestart"); - } - - // Special Marketplace update notification - if (notificationID == "SLM_UPDATE_FOLDER") - { - std::string state = llsdBlock["state"].asString(); - if (state == "deleted") - { - // Perform the deletion viewer side, no alert shown in this case - LLMarketplaceData::instance().deleteListing(llsdBlock["listing_id"].asInteger()); - return true; - } - else - { - // In general, no message will be displayed, all we want is to get the listing updated in the marketplace floater - // If getListing() fails though, the message of the alert will be shown by the caller of attempt_standard_notification() - return LLMarketplaceData::instance().getListing(llsdBlock["listing_id"].asInteger()); - } - } - - // Error Notification can come with and without reason - if (notificationID == "JoinGroupError") - { - if (llsdBlock.has("reason")) - { - LLNotificationsUtil::add("JoinGroupErrorReason", llsdBlock); - return true; - } - if (llsdBlock.has("group_id")) - { - LLGroupData agent_gdatap; - bool is_member = gAgent.getGroupData(llsdBlock["group_id"].asUUID(), agent_gdatap); - if (is_member) - { - LLSD args; - args["reason"] = LLTrans::getString("AlreadyInGroup"); - LLNotificationsUtil::add("JoinGroupErrorReason", args); - return true; - } - } - } - - LLNotificationsUtil::add(notificationID, llsdBlock); - return true; - } - return false; -} - - -static void process_special_alert_messages(const std::string & message) -{ - // Do special handling for alert messages. This is a legacy hack, and any actual displayed - // text should be altered in the notifications.xml files. - if ( message == "You died and have been teleported to your home location") - { - add(LLStatViewer::KILLED, 1); - } - else if( message == "Home position set." ) - { - // save the home location image to disk - std::string snap_filename = gDirUtilp->getLindenUserDir(); - snap_filename += gDirUtilp->getDirDelimiter(); - snap_filename += LLStartUp::getScreenHomeFilename(); - gViewerWindow->saveSnapshot(snap_filename, - gViewerWindow->getWindowWidthRaw(), - gViewerWindow->getWindowHeightRaw(), - false, - gSavedSettings.getBOOL("RenderHUDInSnapshot"), - false, - LLSnapshotModel::SNAPSHOT_TYPE_COLOR, - LLSnapshotModel::SNAPSHOT_FORMAT_PNG); - } -} - - - -void process_agent_alert_message(LLMessageSystem* msgsystem, void** user_data) -{ - // make sure the cursor is back to the usual default since the - // alert is probably due to some kind of error. - gViewerWindow->getWindow()->resetBusyCount(); - - std::string message; - msgsystem->getStringFast(_PREHASH_AlertData, _PREHASH_Message, message); - - process_special_alert_messages(message); - - if (!attempt_standard_notification(msgsystem)) - { - bool modal = false; - msgsystem->getBOOL("AlertData", "Modal", modal); - process_alert_core(message, modal); - } -} - -// The only difference between this routine and the previous is the fact that -// for this routine, the modal parameter is always false. Sadly, for the message -// handled by this routine, there is no "Modal" parameter on the message, and -// there's no API to tell if a message has the given parameter or not. -// So we can't handle the messages with the same handler. -void process_alert_message(LLMessageSystem *msgsystem, void **user_data) -{ - // make sure the cursor is back to the usual default since the - // alert is probably due to some kind of error. - gViewerWindow->getWindow()->resetBusyCount(); - - std::string message; - msgsystem->getStringFast(_PREHASH_AlertData, _PREHASH_Message, message); - process_special_alert_messages(message); - - if (!attempt_standard_notification(msgsystem)) - { - bool modal = false; - process_alert_core(message, modal); - - static LLCachedControl ban_lines_mode(gSavedSettings , "ShowBanLines" , LLViewerParcelMgr::PARCEL_BAN_LINES_ON_COLLISION); - if (ban_lines_mode == LLViewerParcelMgr::PARCEL_BAN_LINES_ON_COLLISION - && message.find("Cannot enter parcel") != std::string::npos) - { - LLViewerParcelMgr::getInstance()->resetCollisionTimer(); - } - } -} - -bool handle_not_age_verified_alert(const std::string &pAlertName) -{ - LLNotificationPtr notification = LLNotificationsUtil::add(pAlertName); - if ((notification == NULL) || notification->isIgnored()) - { - LLNotificationsUtil::add(pAlertName + "_Notify"); - } - - return true; -} - -bool handle_special_alerts(const std::string &pAlertName) -{ - bool isHandled = false; - if (LLStringUtil::compareStrings(pAlertName, "NotAgeVerified") == 0) - { - - isHandled = handle_not_age_verified_alert(pAlertName); - } - - return isHandled; -} - -void process_alert_core(const std::string& message, bool modal) -{ - const std::string ALERT_PREFIX("ALERT: "); - const std::string NOTIFY_PREFIX("NOTIFY: "); - if (message.find(ALERT_PREFIX) == 0) - { - // Allow the server to spawn a named alert so that server alerts can be - // translated out of English. - std::string alert_name(message.substr(ALERT_PREFIX.length())); - if (!handle_special_alerts(alert_name)) - { - LLNotificationsUtil::add(alert_name); - } - } - else if (message.find(NOTIFY_PREFIX) == 0) - { - // Allow the server to spawn a named notification so that server notifications can be - // translated out of English. - std::string notify_name(message.substr(NOTIFY_PREFIX.length())); - LLNotificationsUtil::add(notify_name); - } - else if (message[0] == '/') - { - // System message is important, show in upper-right box not tip - std::string text(message.substr(1)); - LLSD args; - - // *NOTE: If the text from the server ever changes this line will need to be adjusted. - std::string restart_cancelled = "Region restart cancelled."; - if (text.substr(0, restart_cancelled.length()) == restart_cancelled) - { - LLFloaterRegionRestarting::close(); - } - - std::string new_msg =LLNotifications::instance().getGlobalString(text); - args["MESSAGE"] = new_msg; - LLNotificationsUtil::add("SystemMessage", args); - } - else if (modal) - { - LLSD args; - std::string new_msg =LLNotifications::instance().getGlobalString(message); - args["ERROR_MESSAGE"] = new_msg; - LLNotificationsUtil::add("ErrorMessage", args); - } - else - { - // Hack fix for EXP-623 (blame fix on RN :)) to avoid a sim deploy - const std::string AUTOPILOT_CANCELED_MSG("Autopilot canceled"); - if (message.find(AUTOPILOT_CANCELED_MSG) == std::string::npos ) - { - LLSD args; - std::string new_msg =LLNotifications::instance().getGlobalString(message); - - std::string localized_msg; - bool is_message_localized = LLTrans::findString(localized_msg, new_msg); - - args["MESSAGE"] = is_message_localized ? localized_msg : new_msg; - LLNotificationsUtil::add("SystemMessageTip", args); - } - } -} - -mean_collision_list_t gMeanCollisionList; -time_t gLastDisplayedTime = 0; - -void handle_show_mean_events(void *) -{ - LLFloaterReg::showInstance("bumps"); - //LLFloaterBump::showInstance(); -} - -void mean_name_callback(const LLUUID &id, const LLAvatarName& av_name) -{ - static const U32 max_collision_list_size = 20; - if (gMeanCollisionList.size() > max_collision_list_size) - { - mean_collision_list_t::iterator iter = gMeanCollisionList.begin(); - for (U32 i=0; imPerp == id) - { - mcd->mFullName = av_name.getUserName(); - } - } -} - -void process_mean_collision_alert_message(LLMessageSystem *msgsystem, void **user_data) -{ - if (gAgent.inPrelude()) - { - // In prelude, bumping is OK. This dialog is rather confusing to - // newbies, so we don't show it. Drop the packet on the floor. - return; - } - - // make sure the cursor is back to the usual default since the - // alert is probably due to some kind of error. - gViewerWindow->getWindow()->resetBusyCount(); - - LLUUID perp; - U32 time; - U8 u8type; - EMeanCollisionType type; - F32 mag; - - S32 i, num = msgsystem->getNumberOfBlocks(_PREHASH_MeanCollision); - - for (i = 0; i < num; i++) - { - msgsystem->getUUIDFast(_PREHASH_MeanCollision, _PREHASH_Perp, perp); - msgsystem->getU32Fast(_PREHASH_MeanCollision, _PREHASH_Time, time); - msgsystem->getF32Fast(_PREHASH_MeanCollision, _PREHASH_Mag, mag); - msgsystem->getU8Fast(_PREHASH_MeanCollision, _PREHASH_Type, u8type); - - type = (EMeanCollisionType)u8type; - - bool b_found = false; - - for (mean_collision_list_t::iterator iter = gMeanCollisionList.begin(); - iter != gMeanCollisionList.end(); ++iter) - { - LLMeanCollisionData *mcd = *iter; - if ((mcd->mPerp == perp) && (mcd->mType == type)) - { - mcd->mTime = time; - mcd->mMag = mag; - b_found = true; - break; - } - } - - if (!b_found) - { - LLMeanCollisionData *mcd = new LLMeanCollisionData(gAgentID, perp, time, type, mag); - gMeanCollisionList.push_front(mcd); - LLAvatarNameCache::get(perp, boost::bind(&mean_name_callback, _1, _2)); - } - } - LLFloaterBump* bumps_floater = LLFloaterBump::getInstance(); - if(bumps_floater && bumps_floater->isInVisibleChain()) - { - bumps_floater->populateCollisionList(); - } -} - -void process_frozen_message(LLMessageSystem *msgsystem, void **user_data) -{ - // make sure the cursor is back to the usual default since the - // alert is probably due to some kind of error. - gViewerWindow->getWindow()->resetBusyCount(); - bool b_frozen; - - msgsystem->getBOOL("FrozenData", "Data", b_frozen); - - // TODO: make being frozen change view - if (b_frozen) - { - } - else - { - } -} - -// do some extra stuff once we get our economy data -void process_economy_data(LLMessageSystem *msg, void** /*user_data*/) -{ - LL_DEBUGS("Benefits") << "Received economy data, not currently used" << LL_ENDL; -} - -void notify_cautioned_script_question(const LLSD& notification, const LLSD& response, S32 orig_questions, bool granted) -{ - // only continue if at least some permissions were requested - if (orig_questions) - { - // check to see if the person we are asking - - // "'[OBJECTNAME]', an object owned by '[OWNERNAME]', - // located in [REGIONNAME] at [REGIONPOS], - // has been permission to: [PERMISSIONS]." - - LLUIString notice(LLTrans::getString(granted ? "ScriptQuestionCautionChatGranted" : "ScriptQuestionCautionChatDenied")); - - // always include the object name and owner name - notice.setArg("[OBJECTNAME]", notification["payload"]["object_name"].asString()); - notice.setArg("[OWNERNAME]", notification["payload"]["owner_name"].asString()); - - // try to lookup viewerobject that corresponds to the object that - // requested permissions (here, taskid->requesting object id) - bool foundpos = false; - LLViewerObject* viewobj = gObjectList.findObject(notification["payload"]["task_id"].asUUID()); - if (viewobj) - { - // found the viewerobject, get it's position in its region - LLVector3 objpos(viewobj->getPosition()); - - // try to lookup the name of the region the object is in - LLViewerRegion* viewregion = viewobj->getRegion(); - if (viewregion) - { - // got the region, so include the region and 3d coordinates of the object - notice.setArg("[REGIONNAME]", viewregion->getName()); - std::string formatpos = llformat("%.1f, %.1f,%.1f", objpos[VX], objpos[VY], objpos[VZ]); - notice.setArg("[REGIONPOS]", formatpos); - - foundpos = true; - } - } - - if (!foundpos) - { - // unable to determine location of the object - notice.setArg("[REGIONNAME]", "(unknown region)"); - notice.setArg("[REGIONPOS]", "(unknown position)"); - } - - // check each permission that was requested, and list each - // permission that has been flagged as a caution permission - bool caution = false; - S32 count = 0; - std::string perms; - for (const script_perm_t& script_perm : SCRIPT_PERMISSIONS) - { - if ((orig_questions & script_perm.permbit) - && script_perm.caution) - { - count++; - caution = true; - - // add a comma before the permission description if it is not the first permission - // added to the list or the last permission to check - if (count > 1) - { - perms.append(", "); - } - - perms.append(LLTrans::getString(script_perm.question)); - } - } - - notice.setArg("[PERMISSIONS]", perms); - - // log a chat message as long as at least one requested permission - // is a caution permission - if (caution) - { - LLChat chat(notice.getString()); - // LLFloaterChat::addChat(chat, false, false); - } - } -} - -void script_question_mute(const LLUUID& item_id, const std::string& object_name); - -void experiencePermissionBlock(LLUUID experience, LLSD result) -{ - LLSD permission; - LLSD data; - permission["permission"] = "Block"; - data[experience.asString()] = permission; - data["experience"] = experience; - LLEventPumps::instance().obtain("experience_permission").post(data); -} - -bool script_question_cb(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - LLMessageSystem *msg = gMessageSystem; - S32 orig = notification["payload"]["questions"].asInteger(); - S32 new_questions = orig; - - if (response["Details"]) - { - // respawn notification... - LLNotificationsUtil::add(notification["name"], notification["substitutions"], notification["payload"]); - - // ...with description on top - LLNotificationsUtil::add("DebitPermissionDetails"); - return false; - } - - LLUUID experience; - if(notification["payload"].has("experience")) - { - experience = notification["payload"]["experience"].asUUID(); - } - - // check whether permissions were granted or denied - bool allowed = true; - // the "yes/accept" button is the first button in the template, making it button 0 - // if any other button was clicked, the permissions were denied - if (option != 0) - { - new_questions = 0; - allowed = false; - } - else if(experience.notNull()) - { - LLSD permission; - LLSD data; - permission["permission"]="Allow"; - - data[experience.asString()]=permission; - data["experience"]=experience; - LLEventPumps::instance().obtain("experience_permission").post(data); - } - - LLUUID task_id = notification["payload"]["task_id"].asUUID(); - LLUUID item_id = notification["payload"]["item_id"].asUUID(); - - // reply with the permissions granted or denied - msg->newMessageFast(_PREHASH_ScriptAnswerYes); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_Data); - msg->addUUIDFast(_PREHASH_TaskID, task_id); - msg->addUUIDFast(_PREHASH_ItemID, item_id); - msg->addS32Fast(_PREHASH_Questions, new_questions); - msg->sendReliable(LLHost(notification["payload"]["sender"].asString())); - - // only log a chat message if caution prompts are enabled - if (gSavedSettings.getBOOL("PermissionsCautionEnabled")) - { - // log a chat message, if appropriate - notify_cautioned_script_question(notification, response, orig, allowed); - } - - if ( response["Mute"] ) // mute - { - script_question_mute(task_id,notification["payload"]["object_name"].asString()); - } - if ( response["BlockExperience"] ) - { - if(experience.notNull()) - { - LLViewerRegion* region = gAgent.getRegion(); - if (!region) - return false; - - LLExperienceCache::instance().setExperiencePermission(experience, std::string("Block"), boost::bind(&experiencePermissionBlock, experience, _1)); - - } -} - return false; -} - -void script_question_mute(const LLUUID& task_id, const std::string& object_name) -{ - LLMuteList::getInstance()->add(LLMute(task_id, object_name, LLMute::OBJECT)); - - // purge the message queue of any previously queued requests from the same source. DEV-4879 - class OfferMatcher : public LLNotificationsUI::LLScreenChannel::Matcher - { - public: - OfferMatcher(const LLUUID& to_block) : blocked_id(to_block) {} - bool matches(const LLNotificationPtr notification) const - { - if (notification->getName() == "ScriptQuestionCaution" - || notification->getName() == "ScriptQuestion") - { - return (notification->getPayload()["task_id"].asUUID() == blocked_id); - } - return false; - } - private: - const LLUUID& blocked_id; - }; - - LLNotificationsUI::LLChannelManager::getInstance()->killToastsFromChannel( - LLNotificationsUI::NOTIFICATION_CHANNEL_UUID, - OfferMatcher(task_id)); -} - -static LLNotificationFunctorRegistration script_question_cb_reg_1("ScriptQuestion", script_question_cb); -static LLNotificationFunctorRegistration script_question_cb_reg_2("ScriptQuestionCaution", script_question_cb); -static LLNotificationFunctorRegistration script_question_cb_reg_3("ScriptQuestionExperience", script_question_cb); - -void process_script_experience_details(const LLSD& experience_details, LLSD args, LLSD payload) -{ - if(experience_details[LLExperienceCache::PROPERTIES].asInteger() & LLExperienceCache::PROPERTY_GRID) - { - args["GRID_WIDE"] = LLTrans::getString("Grid-Scope"); - } - else - { - args["GRID_WIDE"] = LLTrans::getString("Land-Scope"); - } - args["EXPERIENCE"] = LLSLURL("experience", experience_details[LLExperienceCache::EXPERIENCE_ID].asUUID(), "profile").getSLURLString(); - - LLNotificationsUtil::add("ScriptQuestionExperience", args, payload); -} - -void process_script_question(LLMessageSystem *msg, void **user_data) -{ - // *TODO: Translate owner name -> [FIRST] [LAST] - - LLHost sender = msg->getSender(); - - LLUUID taskid; - LLUUID itemid; - S32 questions; - std::string object_name; - std::string owner_name; - LLUUID experienceid; - - // taskid -> object key of object requesting permissions - msg->getUUIDFast(_PREHASH_Data, _PREHASH_TaskID, taskid ); - // itemid -> script asset key of script requesting permissions - msg->getUUIDFast(_PREHASH_Data, _PREHASH_ItemID, itemid ); - msg->getStringFast(_PREHASH_Data, _PREHASH_ObjectName, object_name); - msg->getStringFast(_PREHASH_Data, _PREHASH_ObjectOwner, owner_name); - msg->getS32Fast(_PREHASH_Data, _PREHASH_Questions, questions ); - - if(msg->has(_PREHASH_Experience)) - { - msg->getUUIDFast(_PREHASH_Experience, _PREHASH_ExperienceID, experienceid); - } - - // Special case. If the objects are owned by this agent, throttle per-object instead - // of per-owner. It's common for residents to reset a ton of scripts that re-request - // permissions, as with tier boxes. UUIDs can't be valid agent names and vice-versa, - // so we'll reuse the same namespace for both throttle types. - std::string throttle_name = owner_name; - std::string self_name; - LLAgentUI::buildFullname( self_name ); // does not include ' Resident' - std::string clean_owner_name = LLCacheName::cleanFullName(owner_name); // removes ' Resident' - if( clean_owner_name == self_name ) - { - throttle_name = taskid.getString(); - } - - // don't display permission requests if this object is muted - if (LLMuteList::getInstance()->isMuted(taskid)) return; - - // throttle excessive requests from any specific user's scripts - typedef LLKeyThrottle LLStringThrottle; - static LLStringThrottle question_throttle( LLREQUEST_PERMISSION_THROTTLE_LIMIT, LLREQUEST_PERMISSION_THROTTLE_INTERVAL ); - - switch (question_throttle.noteAction(throttle_name)) - { - case LLStringThrottle::THROTTLE_NEWLY_BLOCKED: - LL_INFOS("Messaging") << "process_script_question throttled" - << " owner_name:" << owner_name - << LL_ENDL; - // Fall through - - case LLStringThrottle::THROTTLE_BLOCKED: - // Escape altogether until we recover - return; - - case LLStringThrottle::THROTTLE_OK: - break; - } - - std::string script_question; - if (questions) - { - bool caution = false; - S32 count = 0; - LLSD args; - args["OBJECTNAME"] = object_name; - args["NAME"] = clean_owner_name; - S32 known_questions = 0; - bool has_not_only_debit = questions ^ SCRIPT_PERMISSIONS[SCRIPT_PERMISSION_DEBIT].permbit; - // check the received permission flags against each permission - for (const script_perm_t& script_perm : SCRIPT_PERMISSIONS) - { - if (questions & script_perm.permbit) - { - count++; - known_questions |= script_perm.permbit; - // check whether permission question should cause special caution dialog - caution |= (script_perm.caution); - - if (("ScriptTakeMoney" == script_perm.question) && has_not_only_debit) - continue; - - if (LLTrans::getString(script_perm.question).empty()) - { - continue; - } - - script_question += " " + LLTrans::getString(script_perm.question) + "\n"; - } - } - - args["QUESTIONS"] = script_question; - - if (known_questions != questions) - { - // This is in addition to the normal dialog. - // Viewer got a request for not supported/implemented permission - LL_WARNS("Messaging") << "Object \"" << object_name << "\" requested " << script_question - << " permission. Permission is unknown and can't be granted. Item id: " << itemid - << " taskid:" << taskid << LL_ENDL; - } - - if (known_questions) - { - LLSD payload; - payload["task_id"] = taskid; - payload["item_id"] = itemid; - payload["sender"] = sender.getIPandPort(); - payload["questions"] = known_questions; - payload["object_name"] = object_name; - payload["owner_name"] = owner_name; - - // check whether cautions are even enabled or not - const char* notification = "ScriptQuestion"; - - if(caution && gSavedSettings.getBOOL("PermissionsCautionEnabled")) - { - args["FOOTERTEXT"] = (count > 1) ? LLTrans::getString("AdditionalPermissionsRequestHeader") + "\n\n" + script_question : ""; - notification = "ScriptQuestionCaution"; - } - else if(experienceid.notNull()) - { - payload["experience"]=experienceid; - LLExperienceCache::instance().get(experienceid, boost::bind(process_script_experience_details, _1, args, payload)); - return; - } - - LLNotificationsUtil::add(notification, args, payload); - } - } -} - - -void process_derez_container(LLMessageSystem *msg, void**) -{ - LL_WARNS("Messaging") << "call to deprecated process_derez_container" << LL_ENDL; -} - -void container_inventory_arrived(LLViewerObject* object, - LLInventoryObject::object_list_t* inventory, - S32 serial_num, - void* data) -{ - LL_DEBUGS("Messaging") << "container_inventory_arrived()" << LL_ENDL; - if( gAgentCamera.cameraMouselook() ) - { - gAgentCamera.changeCameraToDefault(); - } - - LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(); - - if (inventory->size() > 2) - { - // create a new inventory category to put this in - LLUUID cat_id; - gInventory.createNewCategory( - gInventory.getRootFolderID(), - LLFolderType::FT_NONE, - LLTrans::getString("AcquiredItems"), - [inventory](const LLUUID &new_cat_id) - { - LLInventoryObject::object_list_t::const_iterator it = inventory->begin(); - LLInventoryObject::object_list_t::const_iterator end = inventory->end(); - for (; it != end; ++it) - { - if ((*it)->getType() != LLAssetType::AT_CATEGORY) - { - LLInventoryObject* obj = (LLInventoryObject*)(*it); - LLInventoryItem* item = (LLInventoryItem*)(obj); - LLUUID item_id; - item_id.generate(); - time_t creation_date_utc = time_corrected(); - LLPointer new_item - = new LLViewerInventoryItem(item_id, - new_cat_id, - item->getPermissions(), - item->getAssetUUID(), - item->getType(), - item->getInventoryType(), - item->getName(), - item->getDescription(), - LLSaleInfo::DEFAULT, - item->getFlags(), - creation_date_utc); - new_item->updateServer(true); - gInventory.updateItem(new_item); - } - } - gInventory.notifyObservers(); - - LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(); - if (active_panel) - { - active_panel->setSelection(new_cat_id, TAKE_FOCUS_NO); - } - }); - } - else if (inventory->size() == 2) - { - // we're going to get one fake root category as well as the - // one actual object - LLInventoryObject::object_list_t::iterator it = inventory->begin(); - - if ((*it)->getType() == LLAssetType::AT_CATEGORY) - { - ++it; - } - - LLInventoryItem* item = (LLInventoryItem*)((LLInventoryObject*)(*it)); - const LLUUID category = gInventory.findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(item->getType())); - - LLUUID item_id; - item_id.generate(); - time_t creation_date_utc = time_corrected(); - LLPointer new_item - = new LLViewerInventoryItem(item_id, category, - item->getPermissions(), - item->getAssetUUID(), - item->getType(), - item->getInventoryType(), - item->getName(), - item->getDescription(), - LLSaleInfo::DEFAULT, - item->getFlags(), - creation_date_utc); - new_item->updateServer(true); - gInventory.updateItem(new_item); - gInventory.notifyObservers(); - if(active_panel) - { - active_panel->setSelection(item_id, TAKE_FOCUS_NO); - } - } - - // we've got the inventory, now delete this object if this was a take - bool delete_object = (bool)(intptr_t)data; - LLViewerRegion *region = gAgent.getRegion(); - if (delete_object && region) - { - gMessageSystem->newMessageFast(_PREHASH_ObjectDelete); - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - const U8 NO_FORCE = 0; - gMessageSystem->addU8Fast(_PREHASH_Force, NO_FORCE); - gMessageSystem->nextBlockFast(_PREHASH_ObjectData); - gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, object->getLocalID()); - gMessageSystem->sendReliable(region->getHost()); - } -} - -// method to format the time. -std::string formatted_time(const time_t& the_time) -{ - std::string dateStr = "["+LLTrans::getString("LTimeWeek")+"] [" - +LLTrans::getString("LTimeMonth")+"] [" - +LLTrans::getString("LTimeDay")+"] [" - +LLTrans::getString("LTimeHour")+"]:[" - +LLTrans::getString("LTimeMin")+"]:[" - +LLTrans::getString("LTimeSec")+"] [" - +LLTrans::getString("LTimeYear")+"]"; - - LLSD substitution; - substitution["datetime"] = (S32) the_time; - LLStringUtil::format (dateStr, substitution); - return dateStr; -} - - -void process_teleport_failed(LLMessageSystem *msg, void**) -{ - LL_WARNS("Teleport","Messaging") << "Received TeleportFailed message" << LL_ENDL; - - std::string message_id; // Tag from server, like "RegionEntryAccessBlocked" - std::string big_reason; // Actual message to display - LLSD args; - - // Let the interested parties know that teleport failed. - LLViewerParcelMgr::getInstance()->onTeleportFailed(); - - // if we have additional alert data - if (msg->has(_PREHASH_AlertInfo) && msg->getSizeFast(_PREHASH_AlertInfo, _PREHASH_Message) > 0) - { - // Get the message ID - msg->getStringFast(_PREHASH_AlertInfo, _PREHASH_Message, message_id); - big_reason = LLAgent::sTeleportErrorMessages[message_id]; - if ( big_reason.size() <= 0 ) - { - // Nothing found in the map - use what the server returned in the original message block - msg->getStringFast(_PREHASH_Info, _PREHASH_Reason, big_reason); - } - LL_WARNS("Teleport") << "AlertInfo message_id " << message_id << " reason: " << big_reason << LL_ENDL; - - LLSD llsd_block; - std::string llsd_raw; - msg->getStringFast(_PREHASH_AlertInfo, _PREHASH_ExtraParams, llsd_raw); - if (llsd_raw.length()) - { - std::istringstream llsd_data(llsd_raw); - if (!LLSDSerialize::deserialize(llsd_block, llsd_data, llsd_raw.length())) - { - LL_WARNS("Teleport") << "process_teleport_failed: Attempted to read alert parameter data into LLSD but failed:" << llsd_raw << LL_ENDL; - } - else - { - LL_WARNS("Teleport") << "AlertInfo llsd block received: " << llsd_block << LL_ENDL; - if(llsd_block.has("REGION_NAME")) - { - std::string region_name = llsd_block["REGION_NAME"].asString(); - if(!region_name.empty()) - { - LLStringUtil::format_map_t name_args; - name_args["[REGION_NAME]"] = region_name; - LLStringUtil::format(big_reason, name_args); - } - } - // change notification name in this special case - if (handle_teleport_access_blocked(llsd_block, message_id, args["REASON"])) - { - if( gAgent.getTeleportState() != LLAgent::TELEPORT_NONE ) - { - LL_WARNS("Teleport") << "called handle_teleport_access_blocked, setting state to TELEPORT_NONE" << LL_ENDL; - gAgent.setTeleportState( LLAgent::TELEPORT_NONE ); - } - return; - } - } - } - args["REASON"] = big_reason; - } - else - { // Extra message payload not found - use what the simulator sent - msg->getStringFast(_PREHASH_Info, _PREHASH_Reason, message_id); - - big_reason = LLAgent::sTeleportErrorMessages[message_id]; - if ( big_reason.size() > 0 ) - { // Substitute verbose reason from the local map - args["REASON"] = big_reason; - } - else - { // Nothing found in the map - use what the server returned - args["REASON"] = message_id; - } - } - LL_WARNS("Teleport") << "Displaying CouldNotTeleportReason string, REASON= " << args["REASON"] << LL_ENDL; - - LLNotificationsUtil::add("CouldNotTeleportReason", args); - - if( gAgent.getTeleportState() != LLAgent::TELEPORT_NONE ) - { - LL_WARNS("Teleport") << "End of process_teleport_failed(). Reason message arg is " << args["REASON"] - << ". Setting state to TELEPORT_NONE" << LL_ENDL; - gAgent.setTeleportState( LLAgent::TELEPORT_NONE ); - } -} - -void process_teleport_local(LLMessageSystem *msg,void**) -{ - LL_INFOS("Teleport","Messaging") << "Received TeleportLocal message" << LL_ENDL; - - LLUUID agent_id; - msg->getUUIDFast(_PREHASH_Info, _PREHASH_AgentID, agent_id); - if (agent_id != gAgent.getID()) - { - LL_WARNS("Teleport", "Messaging") << "Got teleport notification for wrong agent " << agent_id << " expected " << gAgent.getID() << ", ignoring!" << LL_ENDL; - return; - } - - U32 location_id; - LLVector3 pos, look_at; - U32 teleport_flags; - msg->getU32Fast(_PREHASH_Info, _PREHASH_LocationID, location_id); - msg->getVector3Fast(_PREHASH_Info, _PREHASH_Position, pos); - msg->getVector3Fast(_PREHASH_Info, _PREHASH_LookAt, look_at); - msg->getU32Fast(_PREHASH_Info, _PREHASH_TeleportFlags, teleport_flags); - - LL_INFOS("Teleport") << "Message params are location_id " << location_id << " teleport_flags " << teleport_flags << LL_ENDL; - if( gAgent.getTeleportState() != LLAgent::TELEPORT_NONE ) - { - if( gAgent.getTeleportState() == LLAgent::TELEPORT_LOCAL ) - { - // To prevent TeleportStart messages re-activating the progress screen right - // after tp, keep the teleport state and let progress screen clear it after a short delay - // (progress screen is active but not visible) *TODO: remove when SVC-5290 is fixed - gTeleportDisplayTimer.reset(); - gTeleportDisplay = true; - } - else - { - LL_WARNS("Teleport") << "State is not TELEPORT_LOCAL: " << gAgent.getTeleportStateName() - << ", setting state to TELEPORT_NONE" << LL_ENDL; - gAgent.setTeleportState( LLAgent::TELEPORT_NONE ); - } - } - - // Sim tells us whether the new position is off the ground - if (teleport_flags & TELEPORT_FLAGS_IS_FLYING) - { - gAgent.setFlying(true); - } - else - { - gAgent.setFlying(false); - } - - gAgent.setPositionAgent(pos); - gAgentCamera.slamLookAt(look_at); - - if ( !(gAgent.getTeleportKeepsLookAt() && LLViewerJoystick::getInstance()->getOverrideCamera()) ) - { - gAgentCamera.resetView(true, true); - } - - // send camera update to new region - gAgentCamera.updateCamera(); - - send_agent_update(true, true); - - // Let the interested parties know we've teleported. - // Vadim *HACK: Agent position seems to get reset (to render position?) - // on each frame, so we have to pass the new position manually. - LLViewerParcelMgr::getInstance()->onTeleportFinished(true, gAgent.getPosGlobalFromAgent(pos)); -} - -void send_simple_im(const LLUUID& to_id, - const std::string& message, - EInstantMessage dialog, - const LLUUID& id) -{ - std::string my_name; - LLAgentUI::buildFullname(my_name); - send_improved_im(to_id, - my_name, - message, - IM_ONLINE, - dialog, - id, - NO_TIMESTAMP, - (U8*)EMPTY_BINARY_BUCKET, - EMPTY_BINARY_BUCKET_SIZE); -} - -void send_group_notice(const LLUUID& group_id, - const std::string& subject, - const std::string& message, - const LLInventoryItem* item) -{ - // Put this notice into an instant message form. - // This will mean converting the item to a binary bucket, - // and the subject/message into a single field. - std::string my_name; - LLAgentUI::buildFullname(my_name); - - // Combine subject + message into a single string. - std::ostringstream subject_and_message; - // TODO: turn all existing |'s into ||'s in subject and message. - subject_and_message << subject << "|" << message; - - // Create an empty binary bucket. - U8 bin_bucket[MAX_INVENTORY_BUFFER_SIZE]; - U8* bucket_to_send = bin_bucket; - bin_bucket[0] = '\0'; - S32 bin_bucket_size = EMPTY_BINARY_BUCKET_SIZE; - // If there is an item being sent, pack it into the binary bucket. - if (item) - { - LLSD item_def; - item_def["item_id"] = item->getUUID(); - item_def["owner_id"] = item->getPermissions().getOwner(); - std::ostringstream ostr; - LLSDSerialize::serialize(item_def, ostr, LLSDSerialize::LLSD_XML); - bin_bucket_size = ostr.str().copy( - (char*)bin_bucket, ostr.str().size()); - bin_bucket[bin_bucket_size] = '\0'; - } - else - { - bucket_to_send = (U8*) EMPTY_BINARY_BUCKET; - } - - - send_improved_im( - group_id, - my_name, - subject_and_message.str(), - IM_ONLINE, - IM_GROUP_NOTICE, - LLUUID::null, - NO_TIMESTAMP, - bucket_to_send, - bin_bucket_size); -} - -void send_lures(const LLSD& notification, const LLSD& response) -{ - std::string text = response["message"].asString(); - LLSLURL slurl; - LLAgentUI::buildSLURL(slurl); - text.append("\r\n").append(slurl.getSLURLString()); - - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_StartLure); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_Info); - msg->addU8Fast(_PREHASH_LureType, (U8)0); // sim will fill this in. - msg->addStringFast(_PREHASH_Message, text); - for(LLSD::array_const_iterator it = notification["payload"]["ids"].beginArray(); - it != notification["payload"]["ids"].endArray(); - ++it) - { - LLUUID target_id = it->asUUID(); - - msg->nextBlockFast(_PREHASH_TargetData); - msg->addUUIDFast(_PREHASH_TargetID, target_id); - - // Record the offer. - { - LLAvatarName av_name; - LLAvatarNameCache::get(target_id, &av_name); // for im log filenames - LLSD args; - args["TO_NAME"] = LLSLURL("agent", target_id, "completename").getSLURLString();; - - LLSD payload; - - //*TODO please rewrite all keys to the same case, lower or upper - payload["from_id"] = target_id; - payload["SUPPRESS_TOAST"] = true; - LLNotificationsUtil::add("TeleportOfferSent", args, payload); - - // Add the recepient to the recent people list. - LLRecentPeople::instance().add(target_id); - } - } - gAgent.sendReliableMessage(); -} - -bool handle_lure_callback(const LLSD& notification, const LLSD& response) -{ - static const unsigned OFFER_RECIPIENT_LIMIT = 250; - if(notification["payload"]["ids"].size() > OFFER_RECIPIENT_LIMIT) - { - // More than OFFER_RECIPIENT_LIMIT targets will overload the message - // producing an llerror. - LLSD args; - args["OFFERS"] = LLSD::Integer(notification["payload"]["ids"].size()); - args["LIMIT"] = static_cast(OFFER_RECIPIENT_LIMIT); - LLNotificationsUtil::add("TooManyTeleportOffers", args); - return false; - } - - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - - if(0 == option) - { - send_lures(notification, response); - } - - return false; -} - -void handle_lure(const LLUUID& invitee) -{ - std::vector ids; - ids.push_back(invitee); - handle_lure(ids); -} - -// Prompt for a message to the invited user. -void handle_lure(const uuid_vec_t& ids) -{ - if (ids.empty()) return; - - if (!gAgent.getRegion()) return; - - LLSD edit_args; - edit_args["REGION"] = gAgent.getRegion()->getName(); - - LLSD payload; - for (std::vector::const_iterator it = ids.begin(); - it != ids.end(); - ++it) - { - payload["ids"].append(*it); - } - if (gAgent.isGodlike()) - { - LLNotificationsUtil::add("OfferTeleportFromGod", edit_args, payload, handle_lure_callback); - } - else - { - LLNotificationsUtil::add("OfferTeleport", edit_args, payload, handle_lure_callback); - } -} - -bool teleport_request_callback(const LLSD& notification, const LLSD& response) -{ - LLUUID from_id = notification["payload"]["from_id"].asUUID(); - if(from_id.isNull()) - { - LL_WARNS() << "from_id is NULL" << LL_ENDL; - return false; - } - - LLAvatarName av_name; - LLAvatarNameCache::get(from_id, &av_name); - - if(LLMuteList::getInstance()->isMuted(from_id) && !LLMuteList::isLinden(av_name.getUserName())) - { - return false; - } - - S32 option = 0; - if (response.isInteger()) - { - option = response.asInteger(); - } - else - { - option = LLNotificationsUtil::getSelectedOption(notification, response); - } - - switch(option) - { - // Yes - case 0: - { - LLSD dummy_notification; - dummy_notification["payload"]["ids"][0] = from_id; - - LLSD dummy_response; - dummy_response["message"] = response["message"]; - - send_lures(dummy_notification, dummy_response); - } - break; - - // No - case 1: - default: - break; - } - - return false; -} - -static LLNotificationFunctorRegistration teleport_request_callback_reg("TeleportRequest", teleport_request_callback); - -void send_improved_im(const LLUUID& to_id, - const std::string& name, - const std::string& message, - U8 offline, - EInstantMessage dialog, - const LLUUID& id, - U32 timestamp, - const U8* binary_bucket, - S32 binary_bucket_size) -{ - pack_instant_message( - gMessageSystem, - gAgent.getID(), - false, - gAgent.getSessionID(), - to_id, - name, - message, - offline, - dialog, - id, - 0, - LLUUID::null, - gAgent.getPositionAgent(), - timestamp, - binary_bucket, - binary_bucket_size); - gAgent.sendReliableMessage(); -} - - -void send_places_query(const LLUUID& query_id, - const LLUUID& trans_id, - const std::string& query_text, - U32 query_flags, - S32 category, - const std::string& sim_name) -{ - LLMessageSystem* msg = gMessageSystem; - - msg->newMessage("PlacesQuery"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->addUUID("QueryID", query_id); - msg->nextBlock("TransactionData"); - msg->addUUID("TransactionID", trans_id); - msg->nextBlock("QueryData"); - msg->addString("QueryText", query_text); - msg->addU32("QueryFlags", query_flags); - msg->addS8("Category", (S8)category); - msg->addString("SimName", sim_name); - gAgent.sendReliableMessage(); -} - -// Deprecated in favor of cap "UserInfo" -void process_user_info_reply(LLMessageSystem* msg, void**) -{ - LLUUID agent_id; - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); - if(agent_id != gAgent.getID()) - { - LL_WARNS("Messaging") << "process_user_info_reply - " - << "wrong agent id." << LL_ENDL; - } - - std::string email; - msg->getStringFast(_PREHASH_UserData, _PREHASH_EMail, email); - std::string dir_visibility; - msg->getString( "UserData", "DirectoryVisibility", dir_visibility); - - LLFloaterPreference::updateUserInfo(dir_visibility); - LLFloaterSnapshot::setAgentEmail(email); -} - - -//--------------------------------------------------------------------------- -// Script Dialog -//--------------------------------------------------------------------------- - -const S32 SCRIPT_DIALOG_MAX_BUTTONS = 12; -const char* SCRIPT_DIALOG_HEADER = "Script Dialog:\n"; - -bool callback_script_dialog(const LLSD& notification, const LLSD& response) -{ - LLNotificationForm form(notification["form"]); - - std::string rtn_text; - S32 button_idx; - button_idx = LLNotification::getSelectedOption(notification, response); - if (response[TEXTBOX_MAGIC_TOKEN].isDefined()) - { - if (response[TEXTBOX_MAGIC_TOKEN].isString()) - rtn_text = response[TEXTBOX_MAGIC_TOKEN].asString(); - else - rtn_text.clear(); // bool marks empty string - } - else - { - rtn_text = LLNotification::getSelectedOptionName(response); - } - - // Button -2 = Mute - // Button -1 = Ignore - no processing needed for this button - // Buttons 0 and above = dialog choices - - if (-2 == button_idx) - { - std::string object_name = notification["payload"]["object_name"].asString(); - LLUUID object_id = notification["payload"]["object_id"].asUUID(); - LLMute mute(object_id, object_name, LLMute::OBJECT); - if (LLMuteList::getInstance()->add(mute)) - { - // This call opens the sidebar, displays the block list, and highlights the newly blocked - // object in the list so the user can see that their block click has taken effect. - LLPanelBlockedList::showPanelAndSelect(object_id); - } - } - - if (0 <= button_idx) - { - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("ScriptDialogReply"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->nextBlock("Data"); - msg->addUUID("ObjectID", notification["payload"]["object_id"].asUUID()); - msg->addS32("ChatChannel", notification["payload"]["chat_channel"].asInteger()); - msg->addS32("ButtonIndex", button_idx); - msg->addString("ButtonLabel", rtn_text); - msg->sendReliable(LLHost(notification["payload"]["sender"].asString())); - } - - return false; -} -static LLNotificationFunctorRegistration callback_script_dialog_reg_1("ScriptDialog", callback_script_dialog); -static LLNotificationFunctorRegistration callback_script_dialog_reg_2("ScriptDialogGroup", callback_script_dialog); - -void process_script_dialog(LLMessageSystem* msg, void**) -{ - S32 i; - LLSD payload; - - LLUUID object_id; - msg->getUUID("Data", "ObjectID", object_id); - -// For compability with OS grids first check for presence of extended packet before fetching data. - LLUUID owner_id; - if (gMessageSystem->getNumberOfBlocks("OwnerData") > 0) - { - msg->getUUID("OwnerData", "OwnerID", owner_id); - } - - if (LLMuteList::getInstance()->isMuted(object_id) || LLMuteList::getInstance()->isMuted(owner_id)) - { - return; - } - - std::string message; - std::string first_name; - std::string last_name; - std::string object_name; - - S32 chat_channel; - msg->getString("Data", "FirstName", first_name); - msg->getString("Data", "LastName", last_name); - msg->getString("Data", "ObjectName", object_name); - msg->getString("Data", "Message", message); - msg->getS32("Data", "ChatChannel", chat_channel); - - // unused for now - LLUUID image_id; - msg->getUUID("Data", "ImageID", image_id); - - payload["sender"] = msg->getSender().getIPandPort(); - payload["object_id"] = object_id; - payload["chat_channel"] = chat_channel; - payload["object_name"] = object_name; - - // build up custom form - S32 button_count = msg->getNumberOfBlocks("Buttons"); - if (button_count > SCRIPT_DIALOG_MAX_BUTTONS) - { - LL_WARNS() << "Too many script dialog buttons - omitting some" << LL_ENDL; - button_count = SCRIPT_DIALOG_MAX_BUTTONS; - } - - LLNotificationForm form; - for (i = 0; i < button_count; i++) - { - std::string tdesc; - msg->getString("Buttons", "ButtonLabel", tdesc, i); - form.addElement("button", std::string(tdesc)); - } - - LLSD args; - args["TITLE"] = object_name; - args["MESSAGE"] = message; - LLNotificationPtr notification; - if (!first_name.empty()) - { - args["NAME"] = LLCacheName::buildFullName(first_name, last_name); - notification = LLNotifications::instance().add( - LLNotification::Params("ScriptDialog").substitutions(args).payload(payload).form_elements(form.asLLSD())); - } - else - { - args["GROUPNAME"] = last_name; - notification = LLNotifications::instance().add( - LLNotification::Params("ScriptDialogGroup").substitutions(args).payload(payload).form_elements(form.asLLSD())); - } -} - -//--------------------------------------------------------------------------- - - -std::vector gLoadUrlList; - -bool callback_load_url(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - - if (0 == option) - { - LLWeb::loadURL(notification["payload"]["url"].asString()); - } - - return false; -} -static LLNotificationFunctorRegistration callback_load_url_reg("LoadWebPage", callback_load_url); - -// We've got the name of the person or group that owns the object hurling the url. -// Display confirmation dialog. -void callback_load_url_name(const LLUUID& id, const std::string& full_name, bool is_group) -{ - std::vector::iterator it; - for (it = gLoadUrlList.begin(); it != gLoadUrlList.end(); ) - { - LLSD load_url_info = *it; - if (load_url_info["owner_id"].asUUID() == id) - { - it = gLoadUrlList.erase(it); - - std::string owner_name; - if (is_group) - { - owner_name = full_name + LLTrans::getString("Group"); - } - else - { - owner_name = full_name; - } - - // For legacy name-only mutes. - if (LLMuteList::getInstance()->isMuted(LLUUID::null, owner_name)) - { - continue; - } - LLSD args; - args["URL"] = load_url_info["url"].asString(); - args["MESSAGE"] = load_url_info["message"].asString();; - args["OBJECTNAME"] = load_url_info["object_name"].asString(); - args["NAME_SLURL"] = LLSLURL(is_group ? "group" : "agent", id, "about").getSLURLString(); - - LLNotificationsUtil::add("LoadWebPage", args, load_url_info); - } - else - { - ++it; - } - } -} - -// We've got the name of the person who owns the object hurling the url. -void callback_load_url_avatar_name(const LLUUID& id, const LLAvatarName& av_name) -{ - callback_load_url_name(id, av_name.getUserName(), false); -} - -void process_load_url(LLMessageSystem* msg, void**) -{ - LLUUID object_id; - LLUUID owner_id; - bool owner_is_group; - char object_name[256]; /* Flawfinder: ignore */ - char message[256]; /* Flawfinder: ignore */ - char url[256]; /* Flawfinder: ignore */ - - msg->getString("Data", "ObjectName", 256, object_name); - msg->getUUID( "Data", "ObjectID", object_id); - msg->getUUID( "Data", "OwnerID", owner_id); - msg->getBOOL( "Data", "OwnerIsGroup", owner_is_group); - msg->getString("Data", "Message", 256, message); - msg->getString("Data", "URL", 256, url); - - LLSD payload; - payload["object_id"] = object_id; - payload["owner_id"] = owner_id; - payload["owner_is_group"] = owner_is_group; - payload["object_name"] = object_name; - payload["message"] = message; - payload["url"] = url; - - // URL is safety checked in load_url above - - // Check if object or owner is muted - if (LLMuteList::getInstance()->isMuted(object_id, object_name) || - LLMuteList::getInstance()->isMuted(owner_id)) - { - LL_INFOS("Messaging")<<"Ignoring load_url from muted object/owner."<getGroup(owner_id, boost::bind(&callback_load_url_name, _1, _2, _3)); - } - else - { - LLAvatarNameCache::get(owner_id, boost::bind(&callback_load_url_avatar_name, _1, _2)); - } -} - - -void callback_download_complete(void** data, S32 result, LLExtStat ext_status) -{ - std::string* filepath = (std::string*)data; - LLSD args; - args["DOWNLOAD_PATH"] = *filepath; - LLNotificationsUtil::add("FinishedRawDownload", args); - delete filepath; -} - - -void process_initiate_download(LLMessageSystem* msg, void**) -{ - LLUUID agent_id; - msg->getUUID("AgentData", "AgentID", agent_id); - if (agent_id != gAgent.getID()) - { - LL_WARNS("Messaging") << "Initiate download for wrong agent" << LL_ENDL; - return; - } - - std::string sim_filename; - std::string viewer_filename; - msg->getString("FileData", "SimFilename", sim_filename); - msg->getString("FileData", "ViewerFilename", viewer_filename); - - if (!gXferManager->validateFileForRequest(viewer_filename)) - { - LL_WARNS() << "SECURITY: Unauthorized download to local file " << viewer_filename << LL_ENDL; - return; - } - gXferManager->requestFile(viewer_filename, - sim_filename, - LL_PATH_NONE, - msg->getSender(), - false, // don't delete remote - callback_download_complete, - (void**)new std::string(viewer_filename)); -} - - -void process_script_teleport_request(LLMessageSystem* msg, void**) -{ - if (!gSavedSettings.getBOOL("ScriptsCanShowUI")) return; - - std::string object_name; - std::string sim_name; - LLVector3 pos; - LLVector3 look_at; - - msg->getString("Data", "ObjectName", object_name); - msg->getString("Data", "SimName", sim_name); - msg->getVector3("Data", "SimPosition", pos); - msg->getVector3("Data", "LookAt", look_at); - - LLFloaterWorldMap* instance = LLFloaterWorldMap::getInstance(); - if(instance) - { - LL_INFOS() << "Object named " << object_name - << " is offering TP to region " - << sim_name << " position " << pos - << LL_ENDL; - - instance->trackURL(sim_name, (S32)pos.mV[VX], (S32)pos.mV[VY], (S32)pos.mV[VZ]); - LLFloaterReg::showInstance("world_map", "center"); - } - - // remove above two lines and replace with below line - // to re-enable parcel browser for llMapDestination() - // LLURLDispatcher::dispatch(LLSLURL::buildSLURL(sim_name, (S32)pos.mV[VX], (S32)pos.mV[VY], (S32)pos.mV[VZ]), false); - -} - -void process_covenant_reply(LLMessageSystem* msg, void**) -{ - LLUUID covenant_id, estate_owner_id; - std::string estate_name; - U32 covenant_timestamp; - msg->getUUID("Data", "CovenantID", covenant_id); - msg->getU32("Data", "CovenantTimestamp", covenant_timestamp); - msg->getString("Data", "EstateName", estate_name); - msg->getUUID("Data", "EstateOwnerID", estate_owner_id); - - LLPanelEstateCovenant::updateEstateName(estate_name); - LLPanelLandCovenant::updateEstateName(estate_name); - LLPanelEstateInfo::updateEstateName(estate_name); - LLFloaterBuyLand::updateEstateName(estate_name); - - std::string owner_name = - LLSLURL("agent", estate_owner_id, "inspect").getSLURLString(); - LLPanelEstateCovenant::updateEstateOwnerName(owner_name); - LLPanelLandCovenant::updateEstateOwnerName(owner_name); - LLPanelEstateInfo::updateEstateOwnerName(owner_name); - LLFloaterBuyLand::updateEstateOwnerName(owner_name); - - LLPanelPlaceProfile* panel = LLFloaterSidePanelContainer::getPanel("places", "panel_place_profile"); - if (panel) - { - panel->updateEstateName(estate_name); - panel->updateEstateOwnerName(owner_name); - } - - // standard message, not from system - std::string last_modified; - if (covenant_timestamp == 0) - { - last_modified = LLTrans::getString("covenant_last_modified")+LLTrans::getString("never_text"); - } - else - { - last_modified = LLTrans::getString("covenant_last_modified")+"[" - +LLTrans::getString("LTimeWeek")+"] [" - +LLTrans::getString("LTimeMonth")+"] [" - +LLTrans::getString("LTimeDay")+"] [" - +LLTrans::getString("LTimeHour")+"]:[" - +LLTrans::getString("LTimeMin")+"]:[" - +LLTrans::getString("LTimeSec")+"] [" - +LLTrans::getString("LTimeYear")+"]"; - LLSD substitution; - substitution["datetime"] = (S32) covenant_timestamp; - LLStringUtil::format (last_modified, substitution); - } - - LLPanelEstateCovenant::updateLastModified(last_modified); - LLPanelLandCovenant::updateLastModified(last_modified); - LLFloaterBuyLand::updateLastModified(last_modified); - - // load the actual covenant asset data - const bool high_priority = true; - if (covenant_id.notNull()) - { - gAssetStorage->getEstateAsset(gAgent.getRegionHost(), - gAgent.getID(), - gAgent.getSessionID(), - covenant_id, - LLAssetType::AT_NOTECARD, - ET_Covenant, - onCovenantLoadComplete, - NULL, - high_priority); - } - else - { - std::string covenant_text; - if (estate_owner_id.isNull()) - { - // mainland - covenant_text = LLTrans::getString("RegionNoCovenant"); - } - else - { - covenant_text = LLTrans::getString("RegionNoCovenantOtherOwner"); - } - LLPanelEstateCovenant::updateCovenantText(covenant_text, covenant_id); - LLPanelLandCovenant::updateCovenantText(covenant_text); - LLFloaterBuyLand::updateCovenantText(covenant_text, covenant_id); - if (panel) - { - panel->updateCovenantText(covenant_text); - } - } -} - -void onCovenantLoadComplete(const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status) -{ - LL_DEBUGS("Messaging") << "onCovenantLoadComplete()" << LL_ENDL; - std::string covenant_text; - if(0 == status) - { - LLFileSystem file(asset_uuid, type, LLFileSystem::READ); - - S32 file_length = file.getSize(); - - std::vector buffer(file_length+1); - file.read((U8*)&buffer[0], file_length); - // put a EOS at the end - buffer[file_length] = '\0'; - - if( (file_length > 19) && !strncmp( &buffer[0], "Linden text version", 19 ) ) - { - LLViewerTextEditor::Params params; - params.name("temp"); - params.max_text_length(file_length+1); - LLViewerTextEditor * editor = LLUICtrlFactory::create (params); - if( !editor->importBuffer( &buffer[0], file_length+1 ) ) - { - LL_WARNS("Messaging") << "Problem importing estate covenant." << LL_ENDL; - covenant_text = "Problem importing estate covenant."; - } - else - { - // Version 0 (just text, doesn't include version number) - covenant_text = editor->getText(); - } - delete editor; - } - else - { - LL_WARNS("Messaging") << "Problem importing estate covenant: Covenant file format error." << LL_ENDL; - covenant_text = "Problem importing estate covenant: Covenant file format error."; - } - } - else - { - if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status || - LL_ERR_FILE_EMPTY == status) - { - covenant_text = "Estate covenant notecard is missing from database."; - } - else if (LL_ERR_INSUFFICIENT_PERMISSIONS == status) - { - covenant_text = "Insufficient permissions to view estate covenant."; - } - else - { - covenant_text = "Unable to load estate covenant at this time."; - } - - LL_WARNS("Messaging") << "Problem loading notecard: " << status << LL_ENDL; - } - LLPanelEstateCovenant::updateCovenantText(covenant_text, asset_uuid); - LLPanelLandCovenant::updateCovenantText(covenant_text); - LLFloaterBuyLand::updateCovenantText(covenant_text, asset_uuid); - - LLPanelPlaceProfile* panel = LLFloaterSidePanelContainer::getPanel("places", "panel_place_profile"); - if (panel) - { - panel->updateCovenantText(covenant_text); - } -} - - -void process_feature_disabled_message(LLMessageSystem* msg, void**) -{ - // Handle Blacklisted feature simulator response... - LLUUID agentID; - LLUUID transactionID; - std::string messageText; - msg->getStringFast(_PREHASH_FailureInfo,_PREHASH_ErrorMessage, messageText,0); - msg->getUUIDFast(_PREHASH_FailureInfo,_PREHASH_AgentID,agentID); - msg->getUUIDFast(_PREHASH_FailureInfo,_PREHASH_TransactionID,transactionID); - - LL_WARNS("Messaging") << "Blacklisted Feature Response:" << messageText << LL_ENDL; -} - -// ------------------------------------------------------------ -// Message system exception callbacks -// ------------------------------------------------------------ - -void invalid_message_callback(LLMessageSystem* msg, - void*, - EMessageException exception) -{ - LLAppViewer::instance()->badNetworkHandler(); -} - -// Please do not add more message handlers here. This file is huge. -// Put them in a file related to the functionality you are implementing. - -void LLOfferInfo::forceResponse(InventoryOfferResponse response) -{ - LLNotification::Params params("UserGiveItem"); - params.functor.function(boost::bind(&LLOfferInfo::inventory_offer_callback, this, _1, _2)); - LLNotifications::instance().forceResponse(params, response); -} - +/** + * @file llviewermessage.cpp + * @brief Dumping ground for viewer-side message system callbacks. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llviewermessage.h" + +// Linden libraries +#include "llanimationstates.h" +#include "llaudioengine.h" +#include "llavataractions.h" +#include "llavatarnamecache.h" // IDEVO HACK +#include "lleventtimer.h" +#include "llfloatercreatelandmark.h" +#include "llfloaterreg.h" +#include "llfolderview.h" +#include "llfollowcamparams.h" +#include "llinventorydefines.h" +#include "lllslconstants.h" +#include "llmaterialtable.h" +#include "llregionhandle.h" +#include "llsd.h" +#include "llsdserialize.h" +#include "llteleportflags.h" +#include "lltoastnotifypanel.h" +#include "lltransactionflags.h" +#include "llfilesystem.h" +#include "llxfermanager.h" +#include "mean_collision_data.h" + +#include "llagent.h" +#include "llagentbenefits.h" +#include "llagentcamera.h" +#include "llcallingcard.h" +#include "llbuycurrencyhtml.h" +#include "llcontrolavatar.h" +#include "llfirstuse.h" +#include "llfloaterbump.h" +#include "llfloaterbuyland.h" +#include "llfloaterland.h" +#include "llfloaterregioninfo.h" +#include "llfloaterlandholdings.h" +#include "llfloaterpreference.h" +#include "llfloatersidepanelcontainer.h" +#include "llfloatersnapshot.h" +#include "llhudeffecttrail.h" +#include "llhudmanager.h" +#include "llimprocessing.h" +#include "llinventoryfunctions.h" +#include "llinventoryobserver.h" +#include "llinventorypanel.h" +#include "llfloaterimnearbychat.h" +#include "llmarketplacefunctions.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "llpanelgrouplandmoney.h" +#include "llpanelmaininventory.h" +#include "llrecentpeople.h" +#include "llscriptfloater.h" +#include "llscriptruntimeperms.h" +#include "llselectmgr.h" +#include "llstartup.h" +#include "llsky.h" +#include "llslurl.h" +#include "llstatenums.h" +#include "llstatusbar.h" +#include "llimview.h" +#include "llspeakers.h" +#include "lltrans.h" +#include "lltranslate.h" +#include "llviewerfoldertype.h" +#include "llvoavatar.h" // IDEVO HACK +#include "lluri.h" +#include "llviewergenericmessage.h" +#include "llviewermenu.h" +#include "llviewerinventory.h" +#include "llviewerjoystick.h" +#include "llviewerobjectlist.h" +#include "llviewerparcelmgr.h" +#include "llviewerstats.h" +#include "llviewerstatsrecorder.h" +#include "llviewertexteditor.h" +#include "llviewerthrottle.h" +#include "llviewerwindow.h" +#include "llvlmanager.h" +#include "llvoavatarself.h" +#include "llvovolume.h" +#include "llworld.h" +#include "pipeline.h" +#include "llfloaterworldmap.h" +#include "llviewerdisplay.h" +#include "llkeythrottle.h" +#include "llgroupactions.h" +#include "llagentui.h" +#include "llpanelblockedlist.h" +#include "llpanelplaceprofile.h" +#include "llviewerregion.h" +#include "llfloaterregionrestarting.h" + +#include "llnotificationmanager.h" // +#include "llexperiencecache.h" + +#include "llexperiencecache.h" +#include "lluiusage.h" + +extern void on_new_message(const LLSD& msg); + +extern bool gCubeSnapshot; + +// +// Constants +// +const F32 CAMERA_POSITION_THRESHOLD_SQUARED = 0.001f * 0.001f; + +// Determine how quickly residents' scripts can issue question dialogs +// Allow bursts of up to 5 dialogs in 10 seconds. 10*2=20 seconds recovery if throttle kicks in +static const U32 LLREQUEST_PERMISSION_THROTTLE_LIMIT = 5; // requests +static const F32 LLREQUEST_PERMISSION_THROTTLE_INTERVAL = 10.0f; // seconds + +extern bool gDebugClicks; +extern bool gShiftFrame; + +// function prototypes +bool check_offer_throttle(const std::string& from_name, bool check_only); +bool check_asset_previewable(const LLAssetType::EType asset_type); +static void process_money_balance_reply_extended(LLMessageSystem* msg); +bool handle_trusted_experiences_notification(const LLSD&); + +//inventory offer throttle globals +LLFrameTimer gThrottleTimer; +const U32 OFFER_THROTTLE_MAX_COUNT=5; //number of items per time period +const F32 OFFER_THROTTLE_TIME=10.f; //time period in seconds + +// Agent Update Flags (U8) +const U8 AU_FLAGS_NONE = 0x00; +const U8 AU_FLAGS_HIDETITLE = 0x01; +const U8 AU_FLAGS_CLIENT_AUTOPILOT = 0x02; + +void accept_friendship_coro(std::string url, LLSD notification) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("friendshipResponceErrorProcessing", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + if (url.empty()) + { + LL_WARNS("Friendship") << "Empty capability!" << LL_ENDL; + return; + } + + LLSD payload = notification["payload"]; + url += "?from=" + payload["from_id"].asString(); + url += "&agent_name=\"" + LLURI::escape(gAgentAvatarp->getFullname()) + "\""; + + LLSD data; + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, data); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS("Friendship") << "HTTP status, " << status.toTerseString() << + ". friendship offer accept failed." << LL_ENDL; + } + else + { + if (!result.has("success") || !result["success"].asBoolean()) + { + LL_WARNS("Friendship") << "Server failed to process accepted friendship. " << httpResults << LL_ENDL; + } + else + { + LL_DEBUGS("Friendship") << "Adding friend to list" << httpResults << LL_ENDL; + // add friend to recent people list + LLRecentPeople::instance().add(payload["from_id"]); + + LLNotificationsUtil::add("FriendshipAcceptedByMe", + notification["substitutions"], payload); + } + } +} + +void decline_friendship_coro(std::string url, LLSD notification, S32 option) +{ + if (url.empty()) + { + LL_WARNS("Friendship") << "Empty capability!" << LL_ENDL; + return; + } + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("friendshipResponceErrorProcessing", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD payload = notification["payload"]; + url += "?from=" + payload["from_id"].asString(); + + LLSD result = httpAdapter->deleteAndSuspend(httpRequest, url); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS("Friendship") << "HTTP status, " << status.toTerseString() << + ". friendship offer decline failed." << LL_ENDL; + } + else + { + if (!result.has("success") || !result["success"].asBoolean()) + { + LL_WARNS("Friendship") << "Server failed to process declined friendship. " << httpResults << LL_ENDL; + } + else + { + LL_DEBUGS("Friendship") << "Friendship declined" << httpResults << LL_ENDL; + if (option == 1) + { + LLNotificationsUtil::add("FriendshipDeclinedByMe", + notification["substitutions"], payload); + } + else if (option == 2) + { + // start IM session + LLAvatarActions::startIM(payload["from_id"].asUUID()); + } + } + } +} + +bool friendship_offer_callback(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + LLMessageSystem* msg = gMessageSystem; + const LLSD& payload = notification["payload"]; + LLNotificationPtr notification_ptr = LLNotifications::instance().find(notification["id"].asUUID()); + + // this will be skipped if the user offering friendship is blocked + if (notification_ptr) + { + switch(option) + { + case 0: + { + LLUIUsage::instance().logCommand("Agent.AcceptFriendship"); + // accept + LLAvatarTracker::formFriendship(payload["from_id"]); + + const LLUUID fid = gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD); + + // This will also trigger an onlinenotification if the user is online + std::string url = gAgent.getRegionCapability("AcceptFriendship"); + LL_DEBUGS("Friendship") << "Cap string: " << url << LL_ENDL; + if (!url.empty() && payload.has("online") && !payload["online"].asBoolean()) + { + LL_DEBUGS("Friendship") << "Accepting friendship via capability" << LL_ENDL; + LLCoros::instance().launch("LLMessageSystem::acceptFriendshipOffer", + boost::bind(accept_friendship_coro, url, notification)); + } + else if (payload.has("session_id") && payload["session_id"].asUUID().notNull()) + { + LL_DEBUGS("Friendship") << "Accepting friendship via viewer message" << LL_ENDL; + msg->newMessageFast(_PREHASH_AcceptFriendship); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_TransactionBlock); + msg->addUUIDFast(_PREHASH_TransactionID, payload["session_id"]); + msg->nextBlockFast(_PREHASH_FolderData); + msg->addUUIDFast(_PREHASH_FolderID, fid); + msg->sendReliable(LLHost(payload["sender"].asString())); + + // add friend to recent people list + LLRecentPeople::instance().add(payload["from_id"]); + LLNotificationsUtil::add("FriendshipAcceptedByMe", + notification["substitutions"], payload); + } + else + { + LL_WARNS("Friendship") << "Failed to accept friendship offer, neither capability nor transaction id are accessible" << LL_ENDL; + } + break; + } + case 1: // Decline + // fall-through + case 2: // Send IM - decline and start IM session + { + LLUIUsage::instance().logCommand("Agent.DeclineFriendship"); + // decline + // We no longer notify other viewers, but we DO still send + // the rejection to the simulator to delete the pending userop. + std::string url = gAgent.getRegionCapability("DeclineFriendship"); + LL_DEBUGS("Friendship") << "Cap string: " << url << LL_ENDL; + if (!url.empty() && payload.has("online") && !payload["online"].asBoolean()) + { + LL_DEBUGS("Friendship") << "Declining friendship via capability" << LL_ENDL; + LLCoros::instance().launch("LLMessageSystem::declineFriendshipOffer", + boost::bind(decline_friendship_coro, url, notification, option)); + } + else if (payload.has("session_id") && payload["session_id"].asUUID().notNull()) + { + LL_DEBUGS("Friendship") << "Declining friendship via viewer message" << LL_ENDL; + msg->newMessageFast(_PREHASH_DeclineFriendship); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_TransactionBlock); + msg->addUUIDFast(_PREHASH_TransactionID, payload["session_id"]); + msg->sendReliable(LLHost(payload["sender"].asString())); + + if (option == 1) // due to fall-through + { + LLNotificationsUtil::add("FriendshipDeclinedByMe", + notification["substitutions"], payload); + } + else if (option == 2) + { + // start IM session + LLAvatarActions::startIM(payload["from_id"].asUUID()); + } + } + else + { + LL_WARNS("Friendship") << "Failed to decline friendship offer, neither capability nor transaction id are accessible" << LL_ENDL; + } + } + default: + // close button probably, possibly timed out + break; + } + + // TODO: this set of calls has undesirable behavior under Windows OS (CHUI-985): + // here appears three additional toasts instead one modified + // need investigation and fix + + // LLNotificationFormPtr modified_form(new LLNotificationForm(*notification_ptr->getForm())); + // modified_form->setElementEnabled("Accept", false); + // modified_form->setElementEnabled("Decline", false); + // notification_ptr->updateForm(modified_form); + // notification_ptr->repost(); + } + + return false; +} +static LLNotificationFunctorRegistration friendship_offer_callback_reg("OfferFriendship", friendship_offer_callback); +static LLNotificationFunctorRegistration friendship_offer_callback_reg_nm("OfferFriendshipNoMessage", friendship_offer_callback); + +// Functions +// + +void give_money(const LLUUID& uuid, LLViewerRegion* region, S32 amount, bool is_group, + S32 trx_type, const std::string& desc) +{ + if(0 == amount || !region) return; + amount = abs(amount); + LL_INFOS("Messaging") << "give_money(" << uuid << "," << amount << ")"<< LL_ENDL; + if(can_afford_transaction(amount)) + { + if (uuid.isNull()) + { + LL_WARNS() << "Failed to send L$ gift to to Null UUID." << LL_ENDL; + return; + } +// gStatusBar->debitBalance(amount); + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_MoneyTransferRequest); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_MoneyData); + msg->addUUIDFast(_PREHASH_SourceID, gAgent.getID() ); + msg->addUUIDFast(_PREHASH_DestID, uuid); + msg->addU8Fast(_PREHASH_Flags, pack_transaction_flags(false, is_group)); + msg->addS32Fast(_PREHASH_Amount, amount); + msg->addU8Fast(_PREHASH_AggregatePermNextOwner, (U8)LLAggregatePermissions::AP_EMPTY); + msg->addU8Fast(_PREHASH_AggregatePermInventory, (U8)LLAggregatePermissions::AP_EMPTY); + msg->addS32Fast(_PREHASH_TransactionType, trx_type ); + msg->addStringFast(_PREHASH_Description, desc); + msg->sendReliable(region->getHost()); + } + else + { + LLStringUtil::format_map_t args; + args["AMOUNT"] = llformat("%d", amount); + LLBuyCurrencyHTML::openCurrencyFloater( LLTrans::getString("giving", args), amount ); + } +} + +void send_complete_agent_movement(const LLHost& sim_host) +{ + LL_DEBUGS("Teleport", "Messaging") << "Sending CompleteAgentMovement to sim_host " << sim_host << LL_ENDL; + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_CompleteAgentMovement); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addU32Fast(_PREHASH_CircuitCode, msg->mOurCircuitCode); + msg->sendReliable(sim_host); +} + +void process_logout_reply(LLMessageSystem* msg, void**) +{ + // The server has told us it's ok to quit. + LL_DEBUGS("Messaging") << "process_logout_reply" << LL_ENDL; + + LLUUID agent_id; + msg->getUUID("AgentData", "AgentID", agent_id); + LLUUID session_id; + msg->getUUID("AgentData", "SessionID", session_id); + if((agent_id != gAgent.getID()) || (session_id != gAgent.getSessionID())) + { + LL_WARNS("Messaging") << "Bogus Logout Reply" << LL_ENDL; + } + + LLInventoryModel::update_map_t parents; + S32 count = msg->getNumberOfBlocksFast( _PREHASH_InventoryData ); + for(S32 i = 0; i < count; ++i) + { + LLUUID item_id; + msg->getUUIDFast(_PREHASH_InventoryData, _PREHASH_ItemID, item_id, i); + + if( (1 == count) && item_id.isNull() ) + { + // Detect dummy item. Indicates an empty list. + break; + } + + // We do not need to track the asset ids, just account for an + // updated inventory version. + LL_INFOS("Messaging") << "process_logout_reply itemID=" << item_id << LL_ENDL; + LLInventoryItem* item = gInventory.getItem( item_id ); + if( item ) + { + parents[item->getParentUUID()] = 0; + gInventory.addChangedMask(LLInventoryObserver::INTERNAL, item_id); + } + else + { + LL_INFOS("Messaging") << "process_logout_reply item not found: " << item_id << LL_ENDL; + } + } + LLAppViewer::instance()->forceQuit(); +} + +void process_layer_data(LLMessageSystem *mesgsys, void **user_data) +{ + LLViewerRegion *regionp = LLWorld::getInstance()->getRegion(mesgsys->getSender()); + + LL_DEBUGS_ONCE("SceneLoadTiming") << "Received layer data" << LL_ENDL; + + if(!regionp) + { + LL_WARNS() << "Invalid region for layer data." << LL_ENDL; + return; + } + S32 size; + S8 type; + + mesgsys->getS8Fast(_PREHASH_LayerID, _PREHASH_Type, type); + size = mesgsys->getSizeFast(_PREHASH_LayerData, _PREHASH_Data); + if (0 == size) + { + LL_WARNS("Messaging") << "Layer data has zero size." << LL_ENDL; + return; + } + if (size < 0) + { + // getSizeFast() is probably trying to tell us about an error + LL_WARNS("Messaging") << "getSizeFast() returned negative result: " + << size + << LL_ENDL; + return; + } + U8 *datap = new U8[size]; + mesgsys->getBinaryDataFast(_PREHASH_LayerData, _PREHASH_Data, datap, size); + LLVLData *vl_datap = new LLVLData(regionp, type, datap, size); + if (mesgsys->getReceiveCompressedSize()) + { + gVLManager.addLayerData(vl_datap, (S32Bytes)mesgsys->getReceiveCompressedSize()); + } + else + { + gVLManager.addLayerData(vl_datap, (S32Bytes)mesgsys->getReceiveSize()); + } +} + +// S32 exported_object_count = 0; +// S32 exported_image_count = 0; +// S32 current_object_count = 0; +// S32 current_image_count = 0; + +// extern LLNotifyBox *gExporterNotify; +// extern LLUUID gExporterRequestID; +// extern std::string gExportDirectory; + +// extern LLUploadDialog *gExportDialog; + +// std::string gExportedFile; + +// std::map gImageChecksums; + +// void export_complete() +// { +// LLUploadDialog::modalUploadFinished(); +// gExporterRequestID.setNull(); +// gExportDirectory = ""; + +// LLFILE* fXML = LLFile::fopen(gExportedFile, "rb"); /* Flawfinder: ignore */ +// fseek(fXML, 0, SEEK_END); +// long length = ftell(fXML); +// fseek(fXML, 0, SEEK_SET); +// U8 *buffer = new U8[length + 1]; +// size_t nread = fread(buffer, 1, length, fXML); +// if (nread < (size_t) length) +// { +// LL_WARNS("Messaging") << "Short read" << LL_ENDL; +// } +// buffer[nread] = '\0'; +// fclose(fXML); + +// char *pos = (char *)buffer; +// while ((pos = strstr(pos+1, ""); + +// if (pos_uuid) +// { +// char image_uuid_str[UUID_STR_SIZE]; /* Flawfinder: ignore */ +// memcpy(image_uuid_str, pos_uuid+2, UUID_STR_SIZE-1); /* Flawfinder: ignore */ +// image_uuid_str[UUID_STR_SIZE-1] = 0; + +// LLUUID image_uuid(image_uuid_str); + +// LL_INFOS("Messaging") << "Found UUID: " << image_uuid << LL_ENDL; + +// std::map::iterator itor = gImageChecksums.find(image_uuid); +// if (itor != gImageChecksums.end()) +// { +// LL_INFOS("Messaging") << "Replacing with checksum: " << itor->second << LL_ENDL; +// if (!itor->second.empty()) +// { +// memcpy(&pos_check[10], itor->second.c_str(), 32); /* Flawfinder: ignore */ +// } +// } +// } +// } +// } + +// LLFILE* fXMLOut = LLFile::fopen(gExportedFile, "wb"); /* Flawfinder: ignore */ +// if (fwrite(buffer, 1, length, fXMLOut) != length) +// { +// LL_WARNS("Messaging") << "Short write" << LL_ENDL; +// } +// fclose(fXMLOut); + +// delete [] buffer; +// } + + +// void exported_item_complete(const LLTSCode status, void *user_data) +// { +// //std::string *filename = (std::string *)user_data; + +// if (status < LLTS_OK) +// { +// LL_WARNS("Messaging") << "Export failed!" << LL_ENDL; +// } +// else +// { +// ++current_object_count; +// if (current_image_count == exported_image_count && current_object_count == exported_object_count) +// { +// LL_INFOS("Messaging") << "*** Export complete ***" << LL_ENDL; + +// export_complete(); +// } +// else +// { +// gExportDialog->setMessage(llformat("Exported %d/%d object files, %d/%d textures.", current_object_count, exported_object_count, current_image_count, exported_image_count)); +// } +// } +// } + +// struct exported_image_info +// { +// LLUUID image_id; +// std::string filename; +// U32 image_num; +// }; + +// void exported_j2c_complete(const LLTSCode status, void *user_data) +// { +// exported_image_info *info = (exported_image_info *)user_data; +// LLUUID image_id = info->image_id; +// U32 image_num = info->image_num; +// std::string filename = info->filename; +// delete info; + +// if (status < LLTS_OK) +// { +// LL_WARNS("Messaging") << "Image download failed!" << LL_ENDL; +// } +// else +// { +// LLFILE* fIn = LLFile::fopen(filename, "rb"); /* Flawfinder: ignore */ +// if (fIn) +// { +// LLPointer ImageUtility = new LLImageJ2C; +// LLPointer TargaUtility = new LLImageTGA; + +// fseek(fIn, 0, SEEK_END); +// S32 length = ftell(fIn); +// fseek(fIn, 0, SEEK_SET); +// U8 *buffer = ImageUtility->allocateData(length); +// if (fread(buffer, 1, length, fIn) != length) +// { +// LL_WARNS("Messaging") << "Short read" << LL_ENDL; +// } +// fclose(fIn); +// LLFile::remove(filename); + +// // Convert to TGA +// LLPointer image = new LLImageRaw(); + +// ImageUtility->updateData(); +// ImageUtility->decode(image, 100000.0f); + +// TargaUtility->encode(image); +// U8 *data = TargaUtility->getData(); +// S32 data_size = TargaUtility->getDataSize(); + +// std::string file_path = gDirUtilp->getDirName(filename); + +// std::string output_file = llformat("%s/image-%03d.tga", file_path.c_str(), image_num);//filename; +// //S32 name_len = output_file.length(); +// //strcpy(&output_file[name_len-3], "tga"); +// LLFILE* fOut = LLFile::fopen(output_file, "wb"); /* Flawfinder: ignore */ +// char md5_hash_string[33]; /* Flawfinder: ignore */ +// strcpy(md5_hash_string, "00000000000000000000000000000000"); /* Flawfinder: ignore */ +// if (fOut) +// { +// if (fwrite(data, 1, data_size, fOut) != data_size) +// { +// LL_WARNS("Messaging") << "Short write" << LL_ENDL; +// } +// fseek(fOut, 0, SEEK_SET); +// fclose(fOut); +// fOut = LLFile::fopen(output_file, "rb"); /* Flawfinder: ignore */ +// LLMD5 my_md5_hash(fOut); +// my_md5_hash.hex_digest(md5_hash_string); +// } + +// gImageChecksums.insert(std::pair(image_id, md5_hash_string)); +// } +// } + +// ++current_image_count; +// if (current_image_count == exported_image_count && current_object_count == exported_object_count) +// { +// LL_INFOS("Messaging") << "*** Export textures complete ***" << LL_ENDL; +// export_complete(); +// } +// else +// { +// gExportDialog->setMessage(llformat("Exported %d/%d object files, %d/%d textures.", current_object_count, exported_object_count, current_image_count, exported_image_count)); +// } +//} + +void process_derez_ack(LLMessageSystem*, void**) +{ + if(gViewerWindow) gViewerWindow->getWindow()->decBusyCount(); +} + +void process_places_reply(LLMessageSystem* msg, void** data) +{ + LLUUID query_id; + + msg->getUUID("AgentData", "QueryID", query_id); + if (query_id.isNull()) + { + LLFloaterLandHoldings::processPlacesReply(msg, data); + } + else if(gAgent.isInGroup(query_id)) + { + LLPanelGroupLandMoney::processPlacesReply(msg, data); + } + else + { + LL_WARNS("Messaging") << "Got invalid PlacesReply message" << LL_ENDL; + } +} + +void send_sound_trigger(const LLUUID& sound_id, F32 gain) +{ + if (sound_id.isNull() || gAgent.getRegion() == NULL) + { + // disconnected agent or zero guids don't get sent (no sound) + return; + } + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_SoundTrigger); + msg->nextBlockFast(_PREHASH_SoundData); + msg->addUUIDFast(_PREHASH_SoundID, sound_id); + // Client untrusted, ids set on sim + msg->addUUIDFast(_PREHASH_OwnerID, LLUUID::null ); + msg->addUUIDFast(_PREHASH_ObjectID, LLUUID::null ); + msg->addUUIDFast(_PREHASH_ParentID, LLUUID::null ); + + msg->addU64Fast(_PREHASH_Handle, gAgent.getRegion()->getHandle()); + + LLVector3 position = gAgent.getPositionAgent(); + msg->addVector3Fast(_PREHASH_Position, position); + msg->addF32Fast(_PREHASH_Gain, gain); + + gAgent.sendMessage(); +} + +static LLSD sSavedGroupInvite; +static LLSD sSavedResponse; + +void response_group_invitation_coro(std::string url, LLUUID group_id, bool notify_and_update) +{ + if (url.empty()) + { + LL_WARNS("GroupInvite") << "Empty capability!" << LL_ENDL; + return; + } + + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("responseGroupInvitation", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD payload; + payload["group"] = group_id; + + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, payload); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS("GroupInvite") << "HTTP status, " << status.toTerseString() << + ". Group " << group_id << " invitation response processing failed." << LL_ENDL; + } + else + { + if (!result.has("success") || !result["success"].asBoolean()) + { + LL_WARNS("GroupInvite") << "Server failed to process group " << group_id << " invitation response. " << httpResults << LL_ENDL; + } + else + { + LL_DEBUGS("GroupInvite") << "Successfully sent response to group " << group_id << " invitation" << LL_ENDL; + if (notify_and_update) + { + gAgent.sendAgentDataUpdateRequest(); + + LLGroupMgr::getInstance()->clearGroupData(group_id); + // refresh the floater for this group, if any. + LLGroupActions::refresh(group_id); + } + } + } +} + +void send_join_group_response(LLUUID group_id, LLUUID transaction_id, bool accept_invite, S32 fee, bool use_offline_cap, LLSD &payload) +{ + if (accept_invite && fee > 0) + { + // If there is a fee to join this group, make + // sure the user is sure they want to join. + LLSD args; + args["COST"] = llformat("%d", fee); + // Set the fee for next time to 0, so that we don't keep + // asking about a fee. + LLSD next_payload = payload; + next_payload["fee"] = 0; + LLNotificationsUtil::add("JoinGroupCanAfford", + args, + next_payload); + } + else if (use_offline_cap) + { + std::string url; + if (accept_invite) + { + url = gAgent.getRegionCapability("AcceptGroupInvite"); + } + else + { + url = gAgent.getRegionCapability("DeclineGroupInvite"); + } + + if (!url.empty()) + { + LL_DEBUGS("GroupInvite") << "Capability url: " << url << LL_ENDL; + LLCoros::instance().launch("LLMessageSystem::acceptGroupInvitation", + boost::bind(response_group_invitation_coro, url, group_id, accept_invite)); + } + else + { + // if sim has no this cap, we can do nothing - regular request will fail + LL_WARNS("GroupInvite") << "No capability, can't reply to offline invitation!" << LL_ENDL; + } + } + else + { + LL_DEBUGS("GroupInvite") << "Replying to group invite via IM message" << LL_ENDL; + + EInstantMessage type = accept_invite ? IM_GROUP_INVITATION_ACCEPT : IM_GROUP_INVITATION_DECLINE; + + if (accept_invite) + { + LLUIUsage::instance().logCommand("Group.Join"); + } + + send_improved_im(group_id, + std::string("name"), + std::string("message"), + IM_ONLINE, + type, + transaction_id); + } +} + +void send_join_group_response(LLUUID group_id, LLUUID transaction_id, bool accept_invite, S32 fee, bool use_offline_cap) +{ + LLSD payload; + if (accept_invite) + { + payload["group_id"] = group_id; + payload["transaction_id"] = transaction_id; + payload["fee"] = fee; + payload["use_offline_cap"] = use_offline_cap; + } + send_join_group_response(group_id, transaction_id, accept_invite, fee, use_offline_cap, payload); +} + +bool join_group_response(const LLSD& notification, const LLSD& response) +{ +// A bit of variable saving and restoring is used to deal with the case where your group list is full and you +// receive an invitation to another group. The data from that invitation is stored in the sSaved +// variables. If you then drop a group and click on the Join button the stored data is restored and used +// to join the group. + LLSD notification_adjusted = notification; + LLSD response_adjusted = response; + + std::string action = notification["name"]; + +// Storing all the information by group id allows for the rare case of being at your maximum +// group count and receiving more than one invitation. + std::string id = notification_adjusted["payload"]["group_id"].asString(); + + if ("JoinGroup" == action || "JoinGroupCanAfford" == action) + { + sSavedGroupInvite[id] = notification; + sSavedResponse[id] = response; + } + else if ("JoinedTooManyGroupsMember" == action) + { + S32 opt = LLNotificationsUtil::getSelectedOption(notification, response); + if (0 == opt) // Join button pressed + { + notification_adjusted = sSavedGroupInvite[id]; + response_adjusted = sSavedResponse[id]; + } + } + + S32 option = LLNotificationsUtil::getSelectedOption(notification_adjusted, response_adjusted); + bool accept_invite = false; + + LLUUID group_id = notification_adjusted["payload"]["group_id"].asUUID(); + LLUUID transaction_id = notification_adjusted["payload"]["transaction_id"].asUUID(); + std::string name = notification_adjusted["payload"]["name"].asString(); + std::string message = notification_adjusted["payload"]["message"].asString(); + S32 fee = notification_adjusted["payload"]["fee"].asInteger(); + U8 use_offline_cap = notification_adjusted["payload"]["use_offline_cap"].asInteger(); + + if (option == 2 && !group_id.isNull()) + { + LLGroupActions::show(group_id); + LLSD args; + args["MESSAGE"] = message; + LLNotificationsUtil::add("JoinGroup", args, notification_adjusted["payload"]); + return false; + } + + if(option == 0 && !group_id.isNull()) + { + // check for promotion or demotion. + S32 max_groups = LLAgentBenefitsMgr::current().getGroupMembershipLimit(); + if(gAgent.isInGroup(group_id)) ++max_groups; + + if(gAgent.mGroups.size() < max_groups) + { + accept_invite = true; + } + else + { + LLSD args; + args["NAME"] = name; + LLNotificationsUtil::add("JoinedTooManyGroupsMember", args, notification_adjusted["payload"]); + return false; + } + } + send_join_group_response(group_id, transaction_id, accept_invite, fee, use_offline_cap, notification_adjusted["payload"]); + + sSavedGroupInvite[id] = LLSD::emptyMap(); + sSavedResponse[id] = LLSD::emptyMap(); + + return false; +} + +static void highlight_inventory_objects_in_panel(const std::vector& items, LLInventoryPanel *inventory_panel) +{ + if (NULL == inventory_panel) return; + + for (std::vector::const_iterator item_iter = items.begin(); + item_iter != items.end(); + ++item_iter) + { + const LLUUID& item_id = (*item_iter); + if(!highlight_offered_object(item_id)) + { + continue; + } + + LLInventoryObject* item = gInventory.getObject(item_id); + llassert(item); + if (!item) { + continue; + } + + LL_DEBUGS("Inventory_Move") << "Highlighting inventory item: " << item->getName() << ", " << item_id << LL_ENDL; + LLFolderView* fv = inventory_panel->getRootFolder(); + if (fv) + { + LLFolderViewItem* fv_item = inventory_panel->getItemByID(item_id); + if (fv_item) + { + LLFolderViewItem* fv_folder = fv_item->getParentFolder(); + if (fv_folder) + { + // Parent folders can be different in case of 2 consecutive drag and drop + // operations when the second one is started before the first one completes. + LL_DEBUGS("Inventory_Move") << "Open folder: " << fv_folder->getName() << LL_ENDL; + fv_folder->setOpen(true); + if (fv_folder->isSelected()) + { + fv->changeSelection(fv_folder, false); + } + } + fv->changeSelection(fv_item, true); + } + } + } +} + +static LLNotificationFunctorRegistration jgr_1("JoinGroup", join_group_response); +static LLNotificationFunctorRegistration jgr_2("JoinedTooManyGroupsMember", join_group_response); +static LLNotificationFunctorRegistration jgr_3("JoinGroupCanAfford", join_group_response); + + +//----------------------------------------------------------------------------- +// Instant Message +//----------------------------------------------------------------------------- +class LLOpenAgentOffer : public LLInventoryFetchItemsObserver +{ +public: + LLOpenAgentOffer(const LLUUID& object_id, + const std::string& from_name) : + LLInventoryFetchItemsObserver(object_id), + mFromName(from_name) {} + /*virtual*/ void startFetch() + { + for (uuid_vec_t::const_iterator it = mIDs.begin(); it < mIDs.end(); ++it) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(*it); + if (cat) + { + mComplete.push_back((*it)); + } + } + LLInventoryFetchItemsObserver::startFetch(); + } + /*virtual*/ void done() + { + open_inventory_offer(mComplete, mFromName); + gInventory.removeObserver(this); + delete this; + } +private: + std::string mFromName; +}; + +/** + * Class to observe adding of new items moved from the world to user's inventory to select them in inventory. + * + * We can't create it each time items are moved because "drop" event is sent separately for each + * element even while multi-dragging. We have to have the only instance of the observer. See EXT-4347. + */ +class LLViewerInventoryMoveFromWorldObserver : public LLInventoryAddItemByAssetObserver +{ +public: + LLViewerInventoryMoveFromWorldObserver() + : LLInventoryAddItemByAssetObserver() + { + + } + + void setMoveIntoFolderID(const LLUUID& into_folder_uuid) {mMoveIntoFolderID = into_folder_uuid; } + +private: + /*virtual */void onAssetAdded(const LLUUID& asset_id) + { + // Store active Inventory panel. + if (LLInventoryPanel::getActiveInventoryPanel()) + { + mActivePanel = LLInventoryPanel::getActiveInventoryPanel()->getHandle(); + } + + // Store selected items (without destination folder) + mSelectedItems.clear(); + if (LLInventoryPanel::getActiveInventoryPanel()) + { + std::set selection = LLInventoryPanel::getActiveInventoryPanel()->getRootFolder()->getSelectionList(); + for (std::set::iterator it = selection.begin(), end_it = selection.end(); + it != end_it; + ++it) + { + mSelectedItems.insert(static_cast((*it)->getViewModelItem())->getUUID()); + } + } + mSelectedItems.erase(mMoveIntoFolderID); + } + + /** + * Selects added inventory items watched by their Asset UUIDs if selection was not changed since + * all items were started to watch (dropped into a folder). + */ + void done() + { + LLInventoryPanel* active_panel = dynamic_cast(mActivePanel.get()); + + // if selection is not changed since watch started lets hightlight new items. + if (active_panel && !isSelectionChanged()) + { + LL_DEBUGS("Inventory_Move") << "Selecting new items..." << LL_ENDL; + active_panel->clearSelection(); + highlight_inventory_objects_in_panel(mAddedItems, active_panel); + } + } + + /** + * Returns true if selected inventory items were changed since moved inventory items were started to watch. + */ + bool isSelectionChanged() + { + LLInventoryPanel* active_panel = LLInventoryPanel::getActiveInventoryPanel(); + + if (NULL == active_panel) + { + return true; + } + + // get selected items (without destination folder) + selected_items_t selected_items; + + std::set selection = active_panel->getRootFolder()->getSelectionList(); + for (std::set::iterator it = selection.begin(), end_it = selection.end(); + it != end_it; + ++it) + { + selected_items.insert(static_cast((*it)->getViewModelItem())->getUUID()); + } + selected_items.erase(mMoveIntoFolderID); + + // compare stored & current sets of selected items + selected_items_t different_items; + std::set_symmetric_difference(mSelectedItems.begin(), mSelectedItems.end(), + selected_items.begin(), selected_items.end(), std::inserter(different_items, different_items.begin())); + + LL_DEBUGS("Inventory_Move") << "Selected firstly: " << mSelectedItems.size() + << ", now: " << selected_items.size() << ", difference: " << different_items.size() << LL_ENDL; + + return different_items.size() > 0; + } + + LLHandle mActivePanel; + typedef std::set selected_items_t; + selected_items_t mSelectedItems; + + /** + * UUID of FolderViewFolder into which watched items are moved. + * + * Destination FolderViewFolder becomes selected while mouse hovering (when dragged items are dropped). + * + * If mouse is moved out it set unselected and number of selected items is changed + * even if selected items in Inventory stay the same. + * So, it is used to update stored selection list. + * + * @see onAssetAdded() + * @see isSelectionChanged() + */ + LLUUID mMoveIntoFolderID; +}; + +LLViewerInventoryMoveFromWorldObserver* gInventoryMoveObserver = NULL; + +void set_dad_inventory_item(LLInventoryItem* inv_item, const LLUUID& into_folder_uuid) +{ + start_new_inventory_observer(); + + gInventoryMoveObserver->setMoveIntoFolderID(into_folder_uuid); + gInventoryMoveObserver->watchAsset(inv_item->getAssetUUID()); +} + + +/** + * Class to observe moving of items and to select them in inventory. + * + * Used currently for dragging from inbox to regular inventory folders + */ + +class LLViewerInventoryMoveObserver : public LLInventoryObserver +{ +public: + + LLViewerInventoryMoveObserver(const LLUUID& object_id) + : LLInventoryObserver() + , mObjectID(object_id) + { + if (LLInventoryPanel::getActiveInventoryPanel()) + { + mActivePanel = LLInventoryPanel::getActiveInventoryPanel()->getHandle(); + } + } + + virtual ~LLViewerInventoryMoveObserver() {} + virtual void changed(U32 mask); + +private: + LLUUID mObjectID; + LLHandle mActivePanel; + +}; + +void LLViewerInventoryMoveObserver::changed(U32 mask) +{ + LLInventoryPanel* active_panel = dynamic_cast(mActivePanel.get()); + + if (NULL == active_panel) + { + gInventory.removeObserver(this); + return; + } + + if((mask & (LLInventoryObserver::STRUCTURE)) != 0) + { + const std::set& changed_items = gInventory.getChangedIDs(); + + std::set::const_iterator id_it = changed_items.begin(); + std::set::const_iterator id_end = changed_items.end(); + for (;id_it != id_end; ++id_it) + { + if ((*id_it) == mObjectID) + { + active_panel->clearSelection(); + std::vector items; + items.push_back(mObjectID); + highlight_inventory_objects_in_panel(items, active_panel); + active_panel->getRootFolder()->scrollToShowSelection(); + + gInventory.removeObserver(this); + break; + } + } + } +} + +void set_dad_inbox_object(const LLUUID& object_id) +{ + LLViewerInventoryMoveObserver* move_observer = new LLViewerInventoryMoveObserver(object_id); + gInventory.addObserver(move_observer); +} + +//unlike the FetchObserver for AgentOffer, we only make one +//instance of the AddedObserver for TaskOffers +//and it never dies. We do this because we don't know the UUID of +//task offers until they are accepted, so we don't wouldn't +//know what to watch for, so instead we just watch for all additions. +class LLOpenTaskOffer : public LLInventoryAddedObserver +{ +protected: + /*virtual*/ void done() + { + uuid_vec_t added; + for(uuid_set_t::const_iterator it = gInventory.getAddedIDs().begin(); it != gInventory.getAddedIDs().end(); ++it) + { + added.push_back(*it); + } + for (uuid_vec_t::iterator it = added.begin(); it != added.end();) + { + const LLUUID& item_uuid = *it; + bool was_moved = false; + LLInventoryObject* added_object = gInventory.getObject(item_uuid); + if (added_object) + { + // cast to item to get Asset UUID + LLInventoryItem* added_item = dynamic_cast(added_object); + if (added_item) + { + const LLUUID& asset_uuid = added_item->getAssetUUID(); + if (gInventoryMoveObserver->isAssetWatched(asset_uuid)) + { + LL_DEBUGS("Inventory_Move") << "Found asset UUID: " << asset_uuid << LL_ENDL; + was_moved = true; + } + } + } + + if (was_moved) + { + it = added.erase(it); + } + else ++it; + } + + open_inventory_offer(added, ""); + } + }; + +class LLOpenTaskGroupOffer : public LLInventoryAddedObserver +{ +protected: + /*virtual*/ void done() + { + uuid_vec_t added; + for(uuid_set_t::const_iterator it = gInventory.getAddedIDs().begin(); it != gInventory.getAddedIDs().end(); ++it) + { + added.push_back(*it); + } + open_inventory_offer(added, "group_offer"); + gInventory.removeObserver(this); + delete this; + } +}; + +//one global instance to bind them +LLOpenTaskOffer* gNewInventoryObserver=NULL; +class LLNewInventoryHintObserver : public LLInventoryAddedObserver +{ +protected: + /*virtual*/ void done() + { + LLFirstUse::newInventory(); + } +}; + +LLNewInventoryHintObserver* gNewInventoryHintObserver=NULL; + +void start_new_inventory_observer() +{ + if (!gNewInventoryObserver) //task offer observer + { + // Observer is deleted by gInventory + gNewInventoryObserver = new LLOpenTaskOffer; + gInventory.addObserver(gNewInventoryObserver); + } + + if (!gInventoryMoveObserver) //inventory move from the world observer + { + // Observer is deleted by gInventory + gInventoryMoveObserver = new LLViewerInventoryMoveFromWorldObserver; + gInventory.addObserver(gInventoryMoveObserver); + } + + if (!gNewInventoryHintObserver) + { + // Observer is deleted by gInventory + gNewInventoryHintObserver = new LLNewInventoryHintObserver(); + gInventory.addObserver(gNewInventoryHintObserver); + } +} + +class LLDiscardAgentOffer : public LLInventoryFetchItemsObserver +{ + LOG_CLASS(LLDiscardAgentOffer); + +public: + LLDiscardAgentOffer(const LLUUID& folder_id, const LLUUID& object_id) : + LLInventoryFetchItemsObserver(object_id), + mFolderID(folder_id), + mObjectID(object_id) {} + + virtual void done() + { + LL_DEBUGS("Messaging") << "LLDiscardAgentOffer::done()" << LL_ENDL; + + // We're invoked from LLInventoryModel::notifyObservers(). + // If we now try to remove the inventory item, it will cause a nested + // notifyObservers() call, which won't work. + // So defer moving the item to trash until viewer gets idle (in a moment). + // Use removeObject() rather than removeItem() because at this level, + // the object could be either an item or a folder. + LLAppViewer::instance()->addOnIdleCallback(boost::bind(&LLInventoryModel::removeObject, &gInventory, mObjectID)); + gInventory.removeObserver(this); + delete this; + } + +protected: + LLUUID mFolderID; + LLUUID mObjectID; +}; + + +//Returns true if we are OK, false if we are throttled +//Set check_only true if you want to know the throttle status +//without registering a hit +bool check_offer_throttle(const std::string& from_name, bool check_only) +{ + static U32 throttle_count; + static bool throttle_logged; + LLChat chat; + std::string log_message; + + if (!gSavedSettings.getBOOL("ShowNewInventory")) + return false; + + if (check_only) + { + return gThrottleTimer.hasExpired(); + } + + if(gThrottleTimer.checkExpirationAndReset(OFFER_THROTTLE_TIME)) + { + LL_DEBUGS("Messaging") << "Throttle Expired" << LL_ENDL; + throttle_count=1; + throttle_logged=false; + return true; + } + else //has not expired + { + LL_DEBUGS("Messaging") << "Throttle Not Expired, Count: " << throttle_count << LL_ENDL; + // When downloading the initial inventory we get a lot of new items + // coming in and can't tell that from spam. + if (LLStartUp::getStartupState() >= STATE_STARTED + && throttle_count >= OFFER_THROTTLE_MAX_COUNT) + { + if (!throttle_logged) + { + // Use the name of the last item giver, who is probably the person + // spamming you. + + LLStringUtil::format_map_t arg; + std::string log_msg; + std::ostringstream time ; + time<getSecondLifeTitle(); + arg["TIME"] = time.str(); + + if (!from_name.empty()) + { + arg["FROM_NAME"] = from_name; + log_msg = LLTrans::getString("ItemsComingInTooFastFrom", arg); + } + else + { + log_msg = LLTrans::getString("ItemsComingInTooFast", arg); + } + + //this is kinda important, so actually put it on screen + LLSD args; + args["MESSAGE"] = log_msg; + LLNotificationsUtil::add("SystemMessage", args); + + throttle_logged=true; + } + return false; + } + else + { + throttle_count++; + return true; + } + } +} + +// Return "true" if we have a preview method for that asset type, "false" otherwise +bool check_asset_previewable(const LLAssetType::EType asset_type) +{ + return (asset_type == LLAssetType::AT_NOTECARD) || + (asset_type == LLAssetType::AT_LANDMARK) || + (asset_type == LLAssetType::AT_TEXTURE) || + (asset_type == LLAssetType::AT_ANIMATION) || + (asset_type == LLAssetType::AT_SCRIPT) || + (asset_type == LLAssetType::AT_SOUND) || + (asset_type == LLAssetType::AT_MATERIAL); +} + +void open_inventory_offer(const uuid_vec_t& objects, const std::string& from_name) +{ + for (uuid_vec_t::const_iterator obj_iter = objects.begin(); + obj_iter != objects.end(); + ++obj_iter) + { + const LLUUID& obj_id = (*obj_iter); + if(!highlight_offered_object(obj_id)) + { + const LLViewerInventoryCategory *parent = gInventory.getFirstNondefaultParent(obj_id); + if (parent && (parent->getPreferredType() == LLFolderType::FT_TRASH)) + { + gInventory.checkTrashOverflow(); + } + continue; + } + + const LLInventoryObject *obj = gInventory.getObject(obj_id); + if (!obj) + { + LL_WARNS() << "Cannot find object [ itemID:" << obj_id << " ] to open." << LL_ENDL; + continue; + } + + const LLAssetType::EType asset_type = obj->getActualType(); + + // Either an inventory item or a category. + const LLInventoryItem* item = dynamic_cast(obj); + if (item && check_asset_previewable(asset_type)) + { + //////////////////////////////////////////////////////////////////////////////// + // Special handling for various types. + if (check_offer_throttle(from_name, false)) // If we are throttled, don't display + { + LL_DEBUGS("Messaging") << "Highlighting inventory item: " << item->getUUID() << LL_ENDL; + // If we opened this ourselves, focus it + const bool take_focus = from_name.empty() ? TAKE_FOCUS_YES : TAKE_FOCUS_NO; + switch(asset_type) + { + case LLAssetType::AT_NOTECARD: + { + LLFloaterReg::showInstance("preview_notecard", LLSD(obj_id), take_focus); + break; + } + case LLAssetType::AT_LANDMARK: + { + LLInventoryCategory* parent_folder = gInventory.getCategory(item->getParentUUID()); + if ("inventory_handler" == from_name) + { + LLFloaterSidePanelContainer::showPanel("places", LLSD().with("type", "landmark").with("id", item->getUUID())); + } + else if("group_offer" == from_name) + { + // "group_offer" is passed by LLOpenTaskGroupOffer + // Notification about added landmark will be generated under the "from_name.empty()" called from LLOpenTaskOffer::done(). + LLSD args; + args["type"] = "landmark"; + args["id"] = obj_id; + LLFloaterSidePanelContainer::showPanel("places", args); + + continue; + } + else if(from_name.empty()) + { + std::string folder_name; + if (parent_folder) + { + // Localize folder name. + // *TODO: share this code? + folder_name = parent_folder->getName(); + if (LLFolderType::lookupIsProtectedType(parent_folder->getPreferredType())) + { + LLTrans::findString(folder_name, "InvFolder " + folder_name); + } + } + else + { + folder_name = LLTrans::getString("Unknown"); + } + + // we receive a message from LLOpenTaskOffer, it mean that new landmark has been added. + LLSD args; + args["LANDMARK_NAME"] = item->getName(); + args["FOLDER_NAME"] = folder_name; + LLNotificationsUtil::add("LandmarkCreated", args); + } + } + break; + case LLAssetType::AT_TEXTURE: + { + LLFloaterReg::showInstance("preview_texture", LLSD(obj_id), take_focus); + break; + } + case LLAssetType::AT_ANIMATION: + LLFloaterReg::showInstance("preview_anim", LLSD(obj_id), take_focus); + break; + case LLAssetType::AT_SCRIPT: + LLFloaterReg::showInstance("preview_script", LLSD(obj_id), take_focus); + break; + case LLAssetType::AT_SOUND: + LLFloaterReg::showInstance("preview_sound", LLSD(obj_id), take_focus); + break; + case LLAssetType::AT_MATERIAL: + // Explicitly do nothing -- we don't want to open the material editor every time you add a material to inventory + break; + default: + LL_DEBUGS("Messaging") << "No preview method for previewable asset type : " << LLAssetType::lookupHumanReadable(asset_type) << LL_ENDL; + break; + } + } + } + + //////////////////////////////////////////////////////////////////////////////// + static LLUICachedControl find_original_new_floater("FindOriginalOpenWindow", false); + //show in a new single-folder window + if(find_original_new_floater && !from_name.empty()) + { + const LLInventoryObject *obj = gInventory.getObject(obj_id); + if (obj && obj->getParentUUID().notNull()) + { + if (obj->getActualType() == LLAssetType::AT_CATEGORY) + { + LLPanelMainInventory::newFolderWindow(obj_id); + } + else + { + LLPanelMainInventory::newFolderWindow(obj->getParentUUID(), obj_id); + } + } + } + else + { + // Highlight item + bool show_in_inventory = gSavedSettings.get("ShowInInventory"); + bool auto_open = + show_in_inventory && // don't open if ShowInInventory is false + !from_name.empty(); // don't open if it's not from anyone + + // SL-20419 : Don't change active tab if floater is visible + LLFloater* instance = LLFloaterReg::findInstance("inventory"); + bool use_main_panel = instance && instance->getVisible(); + + if (auto_open) + { + LLFloaterReg::showInstance("inventory"); + } + + LLInventoryPanel::openInventoryPanelAndSetSelection(auto_open, obj_id, use_main_panel); + } + } +} + +bool highlight_offered_object(const LLUUID& obj_id) +{ + const LLInventoryObject* obj = gInventory.getObject(obj_id); + if(!obj) + { + LL_WARNS("Messaging") << "Unable to show inventory item: " << obj_id << LL_ENDL; + return false; + } + + //////////////////////////////////////////////////////////////////////////////// + // Don't highlight if it's in certain "quiet" folders which don't need UI + // notification (e.g. trash, cof, lost-and-found). + if(!gAgent.getAFK()) + { + const LLViewerInventoryCategory *parent = gInventory.getFirstNondefaultParent(obj_id); + if (parent) + { + const LLFolderType::EType parent_type = parent->getPreferredType(); + if (LLViewerFolderType::lookupIsQuietType(parent_type)) + { + return false; + } + } + } + + if (obj->getType() == LLAssetType::AT_LANDMARK) + { + LLFloaterCreateLandmark *floater = LLFloaterReg::findTypedInstance("add_landmark"); + if (floater && floater->getItem() && floater->getItem()->getUUID() == obj_id) + { + // LLFloaterCreateLandmark is supposed to handle this, + // keep landmark creation floater at the front + return false; + } + } + + return true; +} + +void inventory_offer_mute_callback(const LLUUID& blocked_id, + const std::string& full_name, + bool is_group) +{ + // *NOTE: blocks owner if the offer came from an object + LLMute::EType mute_type = is_group ? LLMute::GROUP : LLMute::AGENT; + + LLMute mute(blocked_id, full_name, mute_type); + if (LLMuteList::getInstance()->add(mute)) + { + LLPanelBlockedList::showPanelAndSelect(blocked_id); + } + + // purge the message queue of any previously queued inventory offers from the same source. + class OfferMatcher : public LLNotificationsUI::LLScreenChannel::Matcher + { + public: + OfferMatcher(const LLUUID& to_block) : blocked_id(to_block) {} + bool matches(const LLNotificationPtr notification) const + { + if(notification->getName() == "ObjectGiveItem" + || notification->getName() == "OwnObjectGiveItem" + || notification->getName() == "UserGiveItem") + { + return (notification->getPayload()["from_id"].asUUID() == blocked_id); + } + return false; + } + private: + const LLUUID& blocked_id; + }; + + LLNotificationsUI::LLChannelManager::getInstance()->killToastsFromChannel( + LLNotificationsUI::NOTIFICATION_CHANNEL_UUID, + OfferMatcher(blocked_id)); +} + + +void inventory_offer_mute_avatar_callback(const LLUUID& blocked_id, + const LLAvatarName& av_name) +{ + inventory_offer_mute_callback(blocked_id, av_name.getUserName(), false); +} + + +std::string LLOfferInfo::mResponderType = "offer_info"; + +LLOfferInfo::LLOfferInfo() + : LLNotificationResponderInterface() + , mFromGroup(false) + , mFromObject(false) + , mIM(IM_NOTHING_SPECIAL) + , mType(LLAssetType::AT_NONE) + , mPersist(false) +{ +} + +LLOfferInfo::LLOfferInfo(const LLSD& sd) +{ + mIM = (EInstantMessage)sd["im_type"].asInteger(); + mFromID = sd["from_id"].asUUID(); + mFromGroup = sd["from_group"].asBoolean(); + mFromObject = sd["from_object"].asBoolean(); + mTransactionID = sd["transaction_id"].asUUID(); + mFolderID = sd["folder_id"].asUUID(); + mObjectID = sd["object_id"].asUUID(); + mType = LLAssetType::lookup(sd["type"].asString().c_str()); + mFromName = sd["from_name"].asString(); + mDesc = sd["description"].asString(); + mHost = LLHost(sd["sender"].asString()); + mPersist = sd["persist"].asBoolean(); +} + +LLOfferInfo::LLOfferInfo(const LLOfferInfo& info) +{ + mIM = info.mIM; + mFromID = info.mFromID; + mFromGroup = info.mFromGroup; + mFromObject = info.mFromObject; + mTransactionID = info.mTransactionID; + mFolderID = info.mFolderID; + mObjectID = info.mObjectID; + mType = info.mType; + mFromName = info.mFromName; + mDesc = info.mDesc; + mHost = info.mHost; + mPersist = info.mPersist; +} + +LLSD LLOfferInfo::asLLSD() +{ + LLSD sd; + sd["responder_type"] = mResponderType; + sd["im_type"] = mIM; + sd["from_id"] = mFromID; + sd["from_group"] = mFromGroup; + sd["from_object"] = mFromObject; + sd["transaction_id"] = mTransactionID; + sd["folder_id"] = mFolderID; + sd["object_id"] = mObjectID; + sd["type"] = LLAssetType::lookup(mType); + sd["from_name"] = mFromName; + sd["description"] = mDesc; + sd["sender"] = mHost.getIPandPort(); + sd["persist"] = mPersist; + return sd; +} + +void LLOfferInfo::fromLLSD(const LLSD& params) +{ + *this = params; +} + +void LLOfferInfo::sendReceiveResponse(bool accept, const LLUUID &destination_folder_id) +{ + if(IM_INVENTORY_OFFERED == mIM) + { + // add buddy to recent people list + LLRecentPeople::instance().add(mFromID); + } + + if (mTransactionID.isNull()) + { + // Not provided, message won't work + return; + } + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_ImprovedInstantMessage); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_MessageBlock); + msg->addBOOLFast(_PREHASH_FromGroup, false); + msg->addUUIDFast(_PREHASH_ToAgentID, mFromID); + msg->addU8Fast(_PREHASH_Offline, IM_ONLINE); + msg->addUUIDFast(_PREHASH_ID, mTransactionID); + msg->addU32Fast(_PREHASH_Timestamp, NO_TIMESTAMP); // no timestamp necessary + std::string name; + LLAgentUI::buildFullname(name); + msg->addStringFast(_PREHASH_FromAgentName, name); + msg->addStringFast(_PREHASH_Message, ""); + msg->addU32Fast(_PREHASH_ParentEstateID, 0); + msg->addUUIDFast(_PREHASH_RegionID, LLUUID::null); + msg->addVector3Fast(_PREHASH_Position, gAgent.getPositionAgent()); + + // ACCEPT. The math for the dialog works, because the accept + // for inventory_offered, task_inventory_offer or + // group_notice_inventory is 1 greater than the offer integer value. + // Generates IM_INVENTORY_ACCEPTED, IM_TASK_INVENTORY_ACCEPTED, + // or IM_GROUP_NOTICE_INVENTORY_ACCEPTED + // Decline for inventory_offered, task_inventory_offer or + // group_notice_inventory is 2 greater than the offer integer value. + + EInstantMessage im = mIM; + if (mIM == IM_GROUP_NOTICE_REQUESTED) + { + // Request has no responder dialogs + im = IM_GROUP_NOTICE; + } + + if (accept) + { + msg->addU8Fast(_PREHASH_Dialog, (U8)(im + 1)); + msg->addBinaryDataFast(_PREHASH_BinaryBucket, &(destination_folder_id.mData), + sizeof(destination_folder_id.mData)); + } + else + { + msg->addU8Fast(_PREHASH_Dialog, (U8)(im + 2)); + msg->addBinaryDataFast(_PREHASH_BinaryBucket, EMPTY_BINARY_BUCKET, EMPTY_BINARY_BUCKET_SIZE); + } + // send the message + msg->sendReliable(mHost); + + // transaction id is usable only once + // Note: a bit of a hack, clicking group notice attachment will not close notice + // so we reset no longer usable transaction id to know not to send message again + // Once capabilities for responses will be implemented LLOfferInfo will have to + // remember that it already responded in another way and ignore IOR_DECLINE + mTransactionID.setNull(); +} + +void LLOfferInfo::handleRespond(const LLSD& notification, const LLSD& response) +{ + initRespondFunctionMap(); + + const std::string name = notification["name"].asString(); + if(mRespondFunctions.find(name) == mRespondFunctions.end()) + { + LL_WARNS() << "Unexpected notification name : " << name << LL_ENDL; + llassert(!"Unexpected notification name"); + return; + } + + mRespondFunctions[name](notification, response); +} + +bool LLOfferInfo::inventory_offer_callback(const LLSD& notification, const LLSD& response) +{ + LLChat chat; + std::string log_message; + S32 button = LLNotificationsUtil::getSelectedOption(notification, response); + + LLInventoryObserver* opener = NULL; + LLViewerInventoryCategory* catp = NULL; + catp = (LLViewerInventoryCategory*)gInventory.getCategory(mObjectID); + LLViewerInventoryItem* itemp = NULL; + if(!catp) + { + itemp = (LLViewerInventoryItem*)gInventory.getItem(mObjectID); + } + + LLNotificationPtr notification_ptr = LLNotifications::instance().find(notification["id"].asUUID()); + + // For muting, we need to add the mute, then decline the offer. + // This must be done here because: + // * callback may be called immediately, + // * adding the mute sends a message, + // * we can't build two messages at once. + if (IOR_MUTE == button) // Block + { + if (notification_ptr != NULL) + { + if (mFromGroup) + { + gCacheName->getGroup(mFromID, boost::bind(&inventory_offer_mute_callback, _1, _2, _3)); + } + else + { + LLAvatarNameCache::get(mFromID, boost::bind(&inventory_offer_mute_avatar_callback, _1, _2)); + } + } + } + + std::string from_string; // Used in the pop-up. + std::string chatHistory_string; // Used in chat history. + + // TODO: when task inventory offers can also be handled the new way, migrate the code that sets these strings here: + from_string = chatHistory_string = mFromName; + + // accept goes to proper folder, decline gets accepted to trash, muted gets declined + bool accept_to_trash = true; + + LLNotificationFormPtr modified_form(notification_ptr ? new LLNotificationForm(*notification_ptr->getForm()) : new LLNotificationForm()); + + switch(button) + { + case IOR_SHOW: + // we will want to open this item when it comes back. + LL_DEBUGS("Messaging") << "Initializing an opener for tid: " << mTransactionID + << LL_ENDL; + switch (mIM) + { + case IM_INVENTORY_OFFERED: + { + // This is an offer from an agent. In this case, the back + // end has already copied the items into your inventory, + // so we can fetch it out of our inventory. + if (gSavedSettings.getBOOL("ShowOfferedInventory")) + { + LLOpenAgentOffer* open_agent_offer = new LLOpenAgentOffer(mObjectID, from_string); + open_agent_offer->startFetch(); + if(catp || (itemp && itemp->isFinished())) + { + open_agent_offer->done(); + } + else + { + opener = open_agent_offer; + } + } + } + break; + case IM_GROUP_NOTICE: + case IM_GROUP_NOTICE_REQUESTED: + opener = new LLOpenTaskGroupOffer; + sendReceiveResponse(true, mFolderID); + break; + case IM_TASK_INVENTORY_OFFERED: + // This is an offer from a task or group. + // We don't use a new instance of an opener + // We instead use the singular observer gOpenTaskOffer + // Since it already exists, we don't need to actually do anything + break; + default: + LL_WARNS("Messaging") << "inventory_offer_callback: unknown offer type" << LL_ENDL; + break; + } + + if (modified_form != NULL) + { + modified_form->setElementEnabled("Show", false); + } + break; + // end switch (mIM) + + case IOR_ACCEPT: + //don't spam them if they are getting flooded + if (check_offer_throttle(mFromName, true)) + { + log_message = "" + chatHistory_string + " " + LLTrans::getString("InvOfferGaveYou") + " " + getSanitizedDescription() + LLTrans::getString("."); + LLSD args; + args["MESSAGE"] = log_message; + LLNotificationsUtil::add("SystemMessageTip", args); + } + + break; + + case IOR_MUTE: + if (modified_form != NULL) + { + modified_form->setElementEnabled("Mute", false); + } + accept_to_trash = false; // for notices, but IOR_MUTE normally doesn't happen for notices + // MUTE falls through to decline + case IOR_DECLINE: + { + { + LLStringUtil::format_map_t log_message_args; + log_message_args["DESC"] = mDesc; + log_message_args["NAME"] = mFromName; + log_message = LLTrans::getString("InvOfferDecline", log_message_args); + } + chat.mText = log_message; + if( LLMuteList::getInstance()->isMuted(mFromID ) && ! LLMuteList::isLinden(mFromName) ) // muting for SL-42269 + { + chat.mMuted = true; + accept_to_trash = false; // will send decline message + } + + // *NOTE dzaporozhan + // Disabled logging to old chat floater to fix crash in group notices - EXT-4149 + // LLFloaterChat::addChatHistory(chat); + + if (mObjectID.notNull()) //make sure we can discard + { + LLDiscardAgentOffer* discard_agent_offer = new LLDiscardAgentOffer(mFolderID, mObjectID); + discard_agent_offer->startFetch(); + if ((catp && gInventory.isCategoryComplete(mObjectID)) || (itemp && itemp->isFinished())) + { + discard_agent_offer->done(); + } + else + { + opener = discard_agent_offer; + } + } + else if (mIM == IM_GROUP_NOTICE) + { + // group notice needs to request object to trash so that user will see it later + // Note: muted agent offers go to trash, not sure if we should do same for notices + LLUUID trash = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + sendReceiveResponse(accept_to_trash, trash); + } + + if (modified_form != NULL) + { + modified_form->setElementEnabled("Show", false); + modified_form->setElementEnabled("Discard", false); + } + + break; + } + default: + // close button probably + // In case of agent offers item has already been fetched and is in your inventory, we simply won't highlight it + // OR delete it if the notification gets killed, since we don't want that to be a vector for + // losing inventory offers. + if (mIM == IM_GROUP_NOTICE) + { + LLUUID trash = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + sendReceiveResponse(true, trash); + } + break; + } + + if(opener) + { + gInventory.addObserver(opener); + } + + if(!mPersist) + { + delete this; + } + + return false; +} + +bool LLOfferInfo::inventory_task_offer_callback(const LLSD& notification, const LLSD& response) +{ + LLChat chat; + std::string log_message; + S32 button = LLNotification::getSelectedOption(notification, response); + + // For muting, we need to add the mute, then decline the offer. + // This must be done here because: + // * callback may be called immediately, + // * adding the mute sends a message, + // * we can't build two messages at once. + if (2 == button) + { + LLNotificationPtr notification_ptr = LLNotifications::instance().find(notification["id"].asUUID()); + + llassert(notification_ptr != NULL); + if (notification_ptr != NULL) + { + if (mFromGroup) + { + gCacheName->getGroup(mFromID, boost::bind(&inventory_offer_mute_callback, _1, _2, _3)); + } + else + { + LLAvatarNameCache::get(mFromID, boost::bind(&inventory_offer_mute_avatar_callback, _1, _2)); + } + } + } + + std::string from_string; // Used in the pop-up. + std::string chatHistory_string; // Used in chat history. + + if (mFromObject) + { + std::string quot = LLTrans::getString("'"); + if (mFromGroup) + { + std::string group_name; + if (gCacheName->getGroupName(mFromID, group_name)) + { + from_string = LLTrans::getString("InvOfferAnObjectNamed") + " " + + quot + mFromName + quot + " " + + LLTrans::getString("InvOfferOwnedByGroup") + " " + + quot + group_name + quot; + chatHistory_string = mFromName + " " + + LLTrans::getString("InvOfferOwnedByGroup") + " " + + quot + group_name + quot; + } + else + { + from_string = LLTrans::getString("InvOfferAnObjectNamed") + " " + + quot + mFromName + quot + " " + + LLTrans::getString("InvOfferOwnedByUnknownGroup"); + chatHistory_string = mFromName + " " + + LLTrans::getString("InvOfferOwnedByUnknownGroup"); + } + } + else + { + LLAvatarName av_name; + if (LLAvatarNameCache::get(mFromID, &av_name)) + { + from_string = LLTrans::getString("InvOfferAnObjectNamed") + " " + + quot + mFromName + quot + " " + + LLTrans::getString("InvOfferOwnedBy") + " " + av_name.getUserName(); + chatHistory_string = mFromName + " " + + LLTrans::getString("InvOfferOwnedBy") + " " + av_name.getUserName(); + } + else + { + from_string = LLTrans::getString("InvOfferAnObjectNamed") + " " + + quot + mFromName + quot + " " + + LLTrans::getString("InvOfferOwnedByUnknownUser"); + chatHistory_string = mFromName + " " + + LLTrans::getString("InvOfferOwnedByUnknownUser"); + } + } + } + else + { + from_string = chatHistory_string = mFromName; + } + + LLUUID destination; + bool accept = true; + + // If user accepted, accept to proper folder, if user discarded, accept to trash. + switch(button) + { + case IOR_ACCEPT: + destination = mFolderID; + //don't spam user if flooded + if (check_offer_throttle(mFromName, true)) + { + log_message = "" + chatHistory_string + " " + LLTrans::getString("InvOfferGaveYou") + " " + getSanitizedDescription() + LLTrans::getString("."); + LLSD args; + args["MESSAGE"] = log_message; + LLNotificationsUtil::add("SystemMessageTip", args); + } + break; + case IOR_MUTE: + // MUTE falls through to decline + accept = false; + case IOR_DECLINE: + default: + // close button probably (or any of the fall-throughs from above) + destination = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + if (accept && LLMuteList::getInstance()->isMuted(mFromID, mFromName)) + { + // Note: muted offers are usually declined automatically, + // but user can mute object after receiving message + accept = false; + } + break; + } + + sendReceiveResponse(accept, destination); + + if(!mPersist) + { + delete this; + } + return false; +} + +std::string LLOfferInfo::getSanitizedDescription() +{ + // currently we get description from server as: 'Object' ( Location ) + // object name shouldn't be shown as a hyperlink + std::string description = mDesc; + + std::size_t start = mDesc.find_first_of("'"); + std::size_t end = mDesc.find_last_of("'"); + if ((start != std::string::npos) && (end != std::string::npos)) + { + description.insert(start, ""); + description.insert(end + 8, ""); + } + return description; +} + + +void LLOfferInfo::initRespondFunctionMap() +{ + if(mRespondFunctions.empty()) + { + mRespondFunctions["ObjectGiveItem"] = boost::bind(&LLOfferInfo::inventory_task_offer_callback, this, _1, _2); + mRespondFunctions["OwnObjectGiveItem"] = boost::bind(&LLOfferInfo::inventory_task_offer_callback, this, _1, _2); + mRespondFunctions["UserGiveItem"] = boost::bind(&LLOfferInfo::inventory_offer_callback, this, _1, _2); + } +} + +bool lure_callback(const LLSD& notification, const LLSD& response) +{ + S32 option = 0; + if (response.isInteger()) + { + option = response.asInteger(); + } + else + { + option = LLNotificationsUtil::getSelectedOption(notification, response); + } + + LLUUID from_id = notification["payload"]["from_id"].asUUID(); + LLUUID lure_id = notification["payload"]["lure_id"].asUUID(); + bool godlike = notification["payload"]["godlike"].asBoolean(); + + switch(option) + { + case 0: + { + // accept + gAgent.teleportViaLure(lure_id, godlike); + } + break; + case 1: + default: + // decline + send_simple_im(from_id, + LLStringUtil::null, + IM_LURE_DECLINED, + lure_id); + break; + } + + LLNotificationPtr notification_ptr = LLNotifications::instance().find(notification["id"].asUUID()); + + if (notification_ptr) + { + LLNotificationFormPtr modified_form(new LLNotificationForm(*notification_ptr->getForm())); + modified_form->setElementEnabled("Teleport", false); + modified_form->setElementEnabled("Cancel", false); + notification_ptr->updateForm(modified_form); + notification_ptr->repost(); + } + + return false; +} +static LLNotificationFunctorRegistration lure_callback_reg("TeleportOffered", lure_callback); + +bool mature_lure_callback(const LLSD& notification, const LLSD& response) +{ + S32 option = 0; + if (response.isInteger()) + { + option = response.asInteger(); + } + else + { + option = LLNotificationsUtil::getSelectedOption(notification, response); + } + + LLUUID from_id = notification["payload"]["from_id"].asUUID(); + LLUUID lure_id = notification["payload"]["lure_id"].asUUID(); + bool godlike = notification["payload"]["godlike"].asBoolean(); + U8 region_access = static_cast(notification["payload"]["region_maturity"].asInteger()); + + switch(option) + { + case 0: + { + // accept + gSavedSettings.setU32("PreferredMaturity", static_cast(region_access)); + gAgent.setMaturityRatingChangeDuringTeleport(region_access); + gAgent.teleportViaLure(lure_id, godlike); + } + break; + case 1: + default: + // decline + send_simple_im(from_id, + LLStringUtil::null, + IM_LURE_DECLINED, + lure_id); + break; + } + return false; +} +static LLNotificationFunctorRegistration mature_lure_callback_reg("TeleportOffered_MaturityExceeded", mature_lure_callback); + +bool goto_url_callback(const LLSD& notification, const LLSD& response) +{ + std::string url = notification["payload"]["url"].asString(); + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if(1 == option) + { + LLWeb::loadURL(url); + } + return false; +} +static LLNotificationFunctorRegistration goto_url_callback_reg("GotoURL", goto_url_callback); + +bool inspect_remote_object_callback(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (0 == option) + { + LLFloaterReg::showInstance("inspect_remote_object", notification["payload"]); + } + return false; +} +static LLNotificationFunctorRegistration inspect_remote_object_callback_reg("ServerObjectMessage", inspect_remote_object_callback); + +class LLPostponedServerObjectNotification: public LLPostponedNotification +{ +protected: + /* virtual */ + void modifyNotificationParams() + { + LLSD payload = mParams.payload; + mParams.payload = payload; + } +}; + +void process_improved_im(LLMessageSystem *msg, void **user_data) +{ + LL_PROFILE_ZONE_SCOPED; + + LLUUID from_id; + bool from_group; + LLUUID to_id; + U8 offline; + U8 d = 0; + LLUUID session_id; + U32 timestamp; + std::string agentName; + std::string message; + U32 parent_estate_id = 0; + LLUUID region_id; + LLVector3 position; + U8 binary_bucket[MTUBYTES]; + S32 binary_bucket_size; + + // *TODO: Translate - need to fix the full name to first/last (maybe) + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, from_id); + msg->getBOOLFast(_PREHASH_MessageBlock, _PREHASH_FromGroup, from_group); + msg->getUUIDFast(_PREHASH_MessageBlock, _PREHASH_ToAgentID, to_id); + msg->getU8Fast(_PREHASH_MessageBlock, _PREHASH_Offline, offline); + msg->getU8Fast(_PREHASH_MessageBlock, _PREHASH_Dialog, d); + msg->getUUIDFast(_PREHASH_MessageBlock, _PREHASH_ID, session_id); + msg->getU32Fast(_PREHASH_MessageBlock, _PREHASH_Timestamp, timestamp); + //msg->getData("MessageBlock", "Count", &count); + msg->getStringFast(_PREHASH_MessageBlock, _PREHASH_FromAgentName, agentName); + msg->getStringFast(_PREHASH_MessageBlock, _PREHASH_Message, message); + msg->getU32Fast(_PREHASH_MessageBlock, _PREHASH_ParentEstateID, parent_estate_id); + msg->getUUIDFast(_PREHASH_MessageBlock, _PREHASH_RegionID, region_id); + msg->getVector3Fast(_PREHASH_MessageBlock, _PREHASH_Position, position); + msg->getBinaryDataFast(_PREHASH_MessageBlock, _PREHASH_BinaryBucket, binary_bucket, 0, 0, MTUBYTES); + binary_bucket_size = msg->getSizeFast(_PREHASH_MessageBlock, _PREHASH_BinaryBucket); + EInstantMessage dialog = (EInstantMessage)d; + LLHost sender = msg->getSender(); + + LLIMProcessing::processNewMessage(from_id, + from_group, + to_id, + offline, + dialog, + session_id, + timestamp, + agentName, + message, + parent_estate_id, + region_id, + position, + binary_bucket, + binary_bucket_size, + sender); +} + +void send_do_not_disturb_message (LLMessageSystem* msg, const LLUUID& from_id, const LLUUID& session_id) +{ + if (gAgent.isDoNotDisturb()) + { + std::string my_name; + LLAgentUI::buildFullname(my_name); + std::string response = gSavedPerAccountSettings.getString("DoNotDisturbModeResponse"); + pack_instant_message( + msg, + gAgent.getID(), + false, + gAgent.getSessionID(), + from_id, + my_name, + response, + IM_ONLINE, + IM_DO_NOT_DISTURB_AUTO_RESPONSE, + session_id); + gAgent.sendReliableMessage(); + } +} + +bool callingcard_offer_callback(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + LLUUID fid; + LLUUID from_id; + LLMessageSystem* msg = gMessageSystem; + switch(option) + { + case 0: + // accept + msg->newMessageFast(_PREHASH_AcceptCallingCard); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_TransactionBlock); + msg->addUUIDFast(_PREHASH_TransactionID, notification["payload"]["transaction_id"].asUUID()); + fid = gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD); + msg->nextBlockFast(_PREHASH_FolderData); + msg->addUUIDFast(_PREHASH_FolderID, fid); + msg->sendReliable(LLHost(notification["payload"]["sender"].asString())); + break; + case 1: + // decline + msg->newMessageFast(_PREHASH_DeclineCallingCard); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_TransactionBlock); + msg->addUUIDFast(_PREHASH_TransactionID, notification["payload"]["transaction_id"].asUUID()); + msg->sendReliable(LLHost(notification["payload"]["sender"].asString())); + send_do_not_disturb_message(msg, notification["payload"]["source_id"].asUUID()); + break; + default: + // close button probably, possibly timed out + break; + } + + return false; +} +static LLNotificationFunctorRegistration callingcard_offer_cb_reg("OfferCallingCard", callingcard_offer_callback); + +void process_offer_callingcard(LLMessageSystem* msg, void**) +{ + // someone has offered to form a friendship + LL_DEBUGS("Messaging") << "callingcard offer" << LL_ENDL; + + LLUUID source_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, source_id); + LLUUID tid; + msg->getUUIDFast(_PREHASH_AgentBlock, _PREHASH_TransactionID, tid); + + LLSD payload; + payload["transaction_id"] = tid; + payload["source_id"] = source_id; + payload["sender"] = msg->getSender().getIPandPort(); + + LLViewerObject* source = gObjectList.findObject(source_id); + LLSD args; + std::string source_name; + if(source && source->isAvatar()) + { + LLNameValue* nvfirst = source->getNVPair("FirstName"); + LLNameValue* nvlast = source->getNVPair("LastName"); + if (nvfirst && nvlast) + { + source_name = LLCacheName::buildFullName( + nvfirst->getString(), nvlast->getString()); + } + } + + if(!source_name.empty()) + { + if (gAgent.isDoNotDisturb() + || LLMuteList::getInstance()->isMuted(source_id, source_name, LLMute::flagTextChat)) + { + // automatically decline offer + LLNotifications::instance().forceResponse(LLNotification::Params("OfferCallingCard").payload(payload), 1); + } + else + { + args["NAME"] = source_name; + LLNotificationsUtil::add("OfferCallingCard", args, payload); + } + } + else + { + LL_WARNS("Messaging") << "Calling card offer from an unknown source." << LL_ENDL; + } +} + +void process_accept_callingcard(LLMessageSystem* msg, void**) +{ + LLNotificationsUtil::add("CallingCardAccepted"); +} + +void process_decline_callingcard(LLMessageSystem* msg, void**) +{ + LLNotificationsUtil::add("CallingCardDeclined"); +} + +void translateSuccess(LLChat chat, LLSD toastArgs, std::string originalMsg, std::string expectLang, std::string translation, const std::string detected_language) +{ + // filter out non-interesting responses + if (!translation.empty() + && ((detected_language.empty()) || (expectLang != detected_language)) + && (LLStringUtil::compareInsensitive(translation, originalMsg) != 0)) + { + chat.mText += " (" + LLTranslate::removeNoTranslateTags(translation) + ")"; + } + + LLTranslate::instance().logSuccess(1); + LLNotificationsUI::LLNotificationManager::instance().onChat(chat, toastArgs); +} + +void translateFailure(LLChat chat, LLSD toastArgs, int status, const std::string err_msg) +{ + std::string msg = LLTrans::getString("TranslationFailed", LLSD().with("[REASON]", err_msg)); + LLStringUtil::replaceString(msg, "\n", " "); // we want one-line error messages + chat.mText += " (" + msg + ")"; + + LLTranslate::instance().logFailure(1); + LLNotificationsUI::LLNotificationManager::instance().onChat(chat, toastArgs); +} + + +void process_chat_from_simulator(LLMessageSystem *msg, void **user_data) +{ + if (gNonInteractive) + { + return; + } + LLChat chat; + std::string mesg; + std::string from_name; + U8 source_temp; + U8 type_temp; + U8 audible_temp; + LLColor4 color(1.0f, 1.0f, 1.0f, 1.0f); + LLUUID from_id; + LLUUID owner_id; + LLViewerObject* chatter; + + msg->getString("ChatData", "FromName", from_name); + + msg->getUUID("ChatData", "SourceID", from_id); + chat.mFromID = from_id; + + // Object owner for objects + msg->getUUID("ChatData", "OwnerID", owner_id); + + msg->getU8Fast(_PREHASH_ChatData, _PREHASH_SourceType, source_temp); + chat.mSourceType = (EChatSourceType)source_temp; + + msg->getU8("ChatData", "ChatType", type_temp); + chat.mChatType = (EChatType)type_temp; + + msg->getU8Fast(_PREHASH_ChatData, _PREHASH_Audible, audible_temp); + chat.mAudible = (EChatAudible)audible_temp; + + chat.mTime = LLFrameTimer::getElapsedSeconds(); + + // IDEVO Correct for new-style "Resident" names + if (chat.mSourceType == CHAT_SOURCE_AGENT) + { + // I don't know if it's OK to change this here, if + // anything downstream does lookups by name, for instance + + LLAvatarName av_name; + if (LLAvatarNameCache::get(from_id, &av_name)) + { + chat.mFromName = av_name.getCompleteName(); + } + else + { + chat.mFromName = LLCacheName::cleanFullName(from_name); + } + } + else + { + // make sure that we don't have an empty or all-whitespace name + LLStringUtil::trim(from_name); + if (from_name.empty()) + { + from_name = LLTrans::getString("Unnamed"); + } + chat.mFromName = from_name; + } + + bool is_do_not_disturb = gAgent.isDoNotDisturb(); + + bool is_muted = false; + bool is_linden = false; + is_muted = LLMuteList::getInstance()->isMuted( + from_id, + from_name, + LLMute::flagTextChat) + || LLMuteList::getInstance()->isMuted(owner_id, LLMute::flagTextChat); + is_linden = chat.mSourceType != CHAT_SOURCE_OBJECT && + LLMuteList::isLinden(from_name); + + if (is_muted && (chat.mSourceType == CHAT_SOURCE_OBJECT)) + { + return; + } + + bool is_audible = (CHAT_AUDIBLE_FULLY == chat.mAudible); + chatter = gObjectList.findObject(from_id); + if (chatter) + { + chat.mPosAgent = chatter->getPositionAgent(); + + // Make swirly things only for talking objects. (not script debug messages, though) + if (chat.mSourceType == CHAT_SOURCE_OBJECT + && chat.mChatType != CHAT_TYPE_DEBUG_MSG + && gSavedSettings.getBOOL("EffectScriptChatParticles") ) + { + LLPointer psc = new LLViewerPartSourceChat(chatter->getPositionAgent()); + psc->setSourceObject(chatter); + psc->setColor(color); + //We set the particles to be owned by the object's owner, + //just in case they should be muted by the mute list + psc->setOwnerUUID(owner_id); + LLViewerPartSim::getInstance()->addPartSource(psc); + } + + // record last audible utterance + if (is_audible + && (is_linden || (!is_muted && !is_do_not_disturb))) + { + if (chat.mChatType != CHAT_TYPE_START + && chat.mChatType != CHAT_TYPE_STOP) + { + gAgent.heardChat(chat.mFromID); + } + } + } + + if (is_audible) + { + //bool visible_in_chat_bubble = false; + + color.setVec(1.f,1.f,1.f,1.f); + msg->getStringFast(_PREHASH_ChatData, _PREHASH_Message, mesg); + + bool ircstyle = false; + + // Look for IRC-style emotes here so chatbubbles work + std::string prefix = mesg.substr(0, 4); + if (prefix == "/me " || prefix == "/me'") + { + ircstyle = true; + } + chat.mText = mesg; + + // Look for the start of typing so we can put "..." in the bubbles. + if (CHAT_TYPE_START == chat.mChatType) + { + LLLocalSpeakerMgr::getInstance()->setSpeakerTyping(from_id, true); + + // Might not have the avatar constructed yet, eg on login. + if (chatter && chatter->isAvatar()) + { + ((LLVOAvatar*)chatter)->startTyping(); + } + return; + } + else if (CHAT_TYPE_STOP == chat.mChatType) + { + LLLocalSpeakerMgr::getInstance()->setSpeakerTyping(from_id, false); + + // Might not have the avatar constructed yet, eg on login. + if (chatter && chatter->isAvatar()) + { + ((LLVOAvatar*)chatter)->stopTyping(); + } + return; + } + + // Look for IRC-style emotes + if (ircstyle) + { + // set CHAT_STYLE_IRC to avoid adding Avatar Name as author of message. See EXT-656 + chat.mChatStyle = CHAT_STYLE_IRC; + + // Do nothing, ircstyle is fixed above for chat bubbles + } + else + { + chat.mText = ""; + switch(chat.mChatType) + { + case CHAT_TYPE_WHISPER: + chat.mText = LLTrans::getString("whisper") + " "; + break; + case CHAT_TYPE_DEBUG_MSG: + case CHAT_TYPE_OWNER: + case CHAT_TYPE_NORMAL: + case CHAT_TYPE_DIRECT: + break; + case CHAT_TYPE_SHOUT: + chat.mText = LLTrans::getString("shout") + " "; + break; + case CHAT_TYPE_START: + case CHAT_TYPE_STOP: + LL_WARNS("Messaging") << "Got chat type start/stop in main chat processing." << LL_ENDL; + break; + default: + LL_WARNS("Messaging") << "Unknown type " << chat.mChatType << " in chat!" << LL_ENDL; + break; + } + + chat.mText += mesg; + } + + // We have a real utterance now, so can stop showing "..." and proceed. + if (chatter && chatter->isAvatar()) + { + LLLocalSpeakerMgr::getInstance()->setSpeakerTyping(from_id, false); + ((LLVOAvatar*)chatter)->stopTyping(); + + if (!is_muted && !is_do_not_disturb) + { + //visible_in_chat_bubble = gSavedSettings.getBOOL("UseChatBubbles"); + std::string formated_msg = ""; + LLViewerChat::formatChatMsg(chat, formated_msg); + LLChat chat_bubble = chat; + chat_bubble.mText = formated_msg; + ((LLVOAvatar*)chatter)->addChat(chat_bubble); + } + } + + if (chatter) + { + chat.mPosAgent = chatter->getPositionAgent(); + } + + // truth table: + // LINDEN BUSY MUTED OWNED_BY_YOU TASK DISPLAY STORE IN HISTORY + // F F F F * Yes Yes + // F F F T * Yes Yes + // F F T F * No No + // F F T T * No No + // F T F F * No Yes + // F T F T * Yes Yes + // F T T F * No No + // F T T T * No No + // T * * * F Yes Yes + + chat.mMuted = is_muted && !is_linden; + + // pass owner_id to chat so that we can display the remote + // object inspect for an object that is chatting with you + LLSD args; + chat.mOwnerID = owner_id; + + LLTranslate::instance().logCharsSeen(mesg.size()); + if (gSavedSettings.getBOOL("TranslateChat") && chat.mSourceType != CHAT_SOURCE_SYSTEM) + { + if (chat.mChatStyle == CHAT_STYLE_IRC) + { + mesg = mesg.substr(4, std::string::npos); + } + const std::string from_lang = ""; // leave empty to trigger autodetect + const std::string to_lang = LLTranslate::getTranslateLanguage(); + + LLTranslate::instance().logCharsSent(mesg.size()); + LLTranslate::translateMessage(from_lang, to_lang, mesg, + boost::bind(&translateSuccess, chat, args, mesg, from_lang, _1, _2), + boost::bind(&translateFailure, chat, args, _1, _2)); + + } + else + { + LLNotificationsUI::LLNotificationManager::instance().onChat(chat, args); + } + + // don't call notification for debug messages from not owned objects + if (chat.mChatType == CHAT_TYPE_DEBUG_MSG) + { + if (gAgentID != chat.mOwnerID) + { + return; + } + } + + if (mesg != "") + { + LLSD msg_notify = LLSD(LLSD::emptyMap()); + msg_notify["session_id"] = LLUUID(); + msg_notify["from_id"] = chat.mFromID; + msg_notify["source_type"] = chat.mSourceType; + on_new_message(msg_notify); + } + + } +} + + +// Simulator we're on is informing the viewer that the agent +// is starting to teleport (perhaps to another sim, perhaps to the +// same sim). If we initiated the teleport process by sending some kind +// of TeleportRequest, then this info is redundant, but if the sim +// initiated the teleport (via a script call, being killed, etc.) +// then this info is news to us. +void process_teleport_start(LLMessageSystem *msg, void**) +{ + // on teleport, don't tell them about destination guide anymore + LLFirstUse::notUsingDestinationGuide(false); + U32 teleport_flags = 0x0; + msg->getU32("Info", "TeleportFlags", teleport_flags); + + if (gAgent.getTeleportState() == LLAgent::TELEPORT_MOVING) + { + // Race condition? + LL_WARNS("Messaging") << "Got TeleportStart, but teleport already in progress. TeleportFlags=" << teleport_flags << LL_ENDL; + } + + LL_DEBUGS("Messaging") << "Got TeleportStart with TeleportFlags=" << teleport_flags << ". gTeleportDisplay: " << gTeleportDisplay << ", gAgent.mTeleportState: " << gAgent.getTeleportState() << LL_ENDL; + + // *NOTE: The server sends two StartTeleport packets when you are teleporting to a LM + LLViewerMessage::getInstance()->mTeleportStartedSignal(); + + if (teleport_flags & TELEPORT_FLAGS_DISABLE_CANCEL) + { + gViewerWindow->setProgressCancelButtonVisible(false); + } + else + { + gViewerWindow->setProgressCancelButtonVisible(true, LLTrans::getString("Cancel")); + } + + // Freeze the UI and show progress bar + // Note: could add data here to differentiate between normal teleport and death. + + if( gAgent.getTeleportState() == LLAgent::TELEPORT_NONE ) + { + gTeleportDisplay = true; + gAgent.setTeleportState( LLAgent::TELEPORT_START ); + make_ui_sound("UISndTeleportOut"); + + LL_INFOS("Messaging") << "Teleport initiated by remote TeleportStart message with TeleportFlags: " << teleport_flags << LL_ENDL; + + // Don't call LLFirstUse::useTeleport here because this could be + // due to being killed, which would send you home, not to a Telehub + } +} + +boost::signals2::connection LLViewerMessage::setTeleportStartedCallback(teleport_started_callback_t cb) +{ + return mTeleportStartedSignal.connect(cb); +} + +void process_teleport_progress(LLMessageSystem* msg, void**) +{ + LLUUID agent_id; + msg->getUUID("AgentData", "AgentID", agent_id); + if((gAgent.getID() != agent_id) + || (gAgent.getTeleportState() == LLAgent::TELEPORT_NONE)) + { + LL_WARNS("Messaging") << "Unexpected teleport progress message." << LL_ENDL; + return; + } + U32 teleport_flags = 0x0; + msg->getU32("Info", "TeleportFlags", teleport_flags); + if (teleport_flags & TELEPORT_FLAGS_DISABLE_CANCEL) + { + gViewerWindow->setProgressCancelButtonVisible(false); + } + else + { + gViewerWindow->setProgressCancelButtonVisible(true, LLTrans::getString("Cancel")); + } + std::string buffer; + msg->getString("Info", "Message", buffer); + LL_DEBUGS("Messaging") << "teleport progress: " << buffer << " flags: " << teleport_flags << LL_ENDL; + + //Sorta hacky...default to using simulator raw messages + //if we don't find the coresponding mapping in our progress mappings + std::string message = buffer; + + if (LLAgent::sTeleportProgressMessages.find(buffer) != + LLAgent::sTeleportProgressMessages.end() ) + { + message = LLAgent::sTeleportProgressMessages[buffer]; + } + + gAgent.setTeleportMessage(LLAgent::sTeleportProgressMessages[message]); +} + +class LLFetchInWelcomeArea : public LLInventoryFetchDescendentsObserver +{ +public: + LLFetchInWelcomeArea(const uuid_vec_t &ids) : + LLInventoryFetchDescendentsObserver(ids) + {} + virtual void done() + { + LLIsType is_landmark(LLAssetType::AT_LANDMARK); + LLIsType is_card(LLAssetType::AT_CALLINGCARD); + + LLInventoryModel::cat_array_t card_cats; + LLInventoryModel::item_array_t card_items; + LLInventoryModel::cat_array_t land_cats; + LLInventoryModel::item_array_t land_items; + + uuid_vec_t::iterator it = mComplete.begin(); + uuid_vec_t::iterator end = mComplete.end(); + for(; it != end; ++it) + { + gInventory.collectDescendentsIf( + (*it), + land_cats, + land_items, + LLInventoryModel::EXCLUDE_TRASH, + is_landmark); + gInventory.collectDescendentsIf( + (*it), + card_cats, + card_items, + LLInventoryModel::EXCLUDE_TRASH, + is_card); + } + + gInventory.removeObserver(this); + delete this; + } +}; + + + +class LLPostTeleportNotifiers : public LLEventTimer +{ +public: + LLPostTeleportNotifiers(); + virtual ~LLPostTeleportNotifiers(); + + //function to be called at the supplied frequency + virtual bool tick(); +}; + +LLPostTeleportNotifiers::LLPostTeleportNotifiers() : LLEventTimer( 2.0 ) +{ +}; + +LLPostTeleportNotifiers::~LLPostTeleportNotifiers() +{ +} + +bool LLPostTeleportNotifiers::tick() +{ + bool all_done = false; + if ( gAgent.getTeleportState() == LLAgent::TELEPORT_NONE ) + { + // get callingcards and landmarks available to the user arriving. + uuid_vec_t folders; + const LLUUID callingcard_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CALLINGCARD); + if(callingcard_id.notNull()) + folders.push_back(callingcard_id); + const LLUUID folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK); + if(folder_id.notNull()) + folders.push_back(folder_id); + if(!folders.empty()) + { + LLFetchInWelcomeArea* fetcher = new LLFetchInWelcomeArea(folders); + fetcher->startFetch(); + if(fetcher->isFinished()) + { + fetcher->done(); + } + else + { + gInventory.addObserver(fetcher); + } + } + all_done = true; + } + + return all_done; +} + + + +// Teleport notification from the simulator +// We're going to pretend to be a new agent +void process_teleport_finish(LLMessageSystem* msg, void**) +{ + LL_DEBUGS("Teleport","Messaging") << "Received TeleportFinish message" << LL_ENDL; + LLUUID agent_id; + msg->getUUIDFast(_PREHASH_Info, _PREHASH_AgentID, agent_id); + if (agent_id != gAgent.getID()) + { + LL_WARNS("Teleport","Messaging") << "Got teleport notification for wrong agent " << agent_id << " expected " << gAgent.getID() << ", ignoring!" << LL_ENDL; + return; + } + + if (gAgent.getTeleportState() == LLAgent::TELEPORT_NONE) + { + if (gAgent.canRestoreCanceledTeleport()) + { + // Server either ignored teleport cancel message or did not receive it in time. + // This message can't be ignored since teleport is complete at server side + LL_INFOS("Teleport") << "Restoring canceled teleport request" << LL_ENDL; + gAgent.restoreCanceledTeleportRequest(); + } + else + { + // Race condition? Make sure all variables are set correctly for teleport to work + LL_WARNS("Teleport","Messaging") << "Teleport 'finish' message without 'start'. Setting state to TELEPORT_REQUESTED" << LL_ENDL; + gTeleportDisplay = true; + LLViewerMessage::getInstance()->mTeleportStartedSignal(); + gAgent.setTeleportState(LLAgent::TELEPORT_REQUESTED); + make_ui_sound("UISndTeleportOut"); + } + } + else if (gAgent.getTeleportState() == LLAgent::TELEPORT_MOVING) + { + LL_WARNS("Teleport","Messaging") << "Teleport message in the middle of other teleport" << LL_ENDL; + } + + // Teleport is finished; it can't be cancelled now. + gViewerWindow->setProgressCancelButtonVisible(false); + + // Do teleport effect for where you're leaving + // VEFFECT: TeleportStart + LLHUDEffectSpiral *effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_POINT, true); + effectp->setPositionGlobal(gAgent.getPositionGlobal()); + effectp->setColor(LLColor4U(gAgent.getEffectColor())); + LLHUDManager::getInstance()->sendEffects(); + + U32 location_id; + U32 sim_ip; + U16 sim_port; + LLVector3 pos, look_at; + U64 region_handle; + msg->getU32Fast(_PREHASH_Info, _PREHASH_LocationID, location_id); + msg->getIPAddrFast(_PREHASH_Info, _PREHASH_SimIP, sim_ip); + msg->getIPPortFast(_PREHASH_Info, _PREHASH_SimPort, sim_port); + //msg->getVector3Fast(_PREHASH_Info, _PREHASH_Position, pos); + //msg->getVector3Fast(_PREHASH_Info, _PREHASH_LookAt, look_at); + msg->getU64Fast(_PREHASH_Info, _PREHASH_RegionHandle, region_handle); + U32 teleport_flags; + msg->getU32Fast(_PREHASH_Info, _PREHASH_TeleportFlags, teleport_flags); + + std::string seedCap; + msg->getStringFast(_PREHASH_Info, _PREHASH_SeedCapability, seedCap); + + LL_DEBUGS("Teleport") << "TeleportFinish message params are:" + << " sim_ip " << sim_ip + << " sim_port " << sim_port + << " region_handle " << region_handle + << " teleport_flags " << teleport_flags + << " seedCap " << seedCap + << LL_ENDL; + + // update home location if we are teleporting out of prelude - specific to teleporting to welcome area + if((teleport_flags & TELEPORT_FLAGS_SET_HOME_TO_TARGET) + && (!gAgent.isGodlike())) + { + gAgent.setHomePosRegion(region_handle, pos); + + // Create a timer that will send notices when teleporting is all finished. Since this is + // based on the LLEventTimer class, it will be managed by that class and not orphaned or leaked. + new LLPostTeleportNotifiers(); + } + + LLHost sim_host(sim_ip, sim_port); + + // Viewer trusts the simulator. + gMessageSystem->enableCircuit(sim_host, true); + LLViewerRegion* regionp = LLWorld::getInstance()->addRegion(region_handle, sim_host); + +/* + // send camera update to new region + gAgentCamera.updateCamera(); + + // likewise make sure the camera is behind the avatar + gAgentCamera.resetView(true); + LLVector3 shift_vector = regionp->getPosRegionFromGlobal(gAgent.getRegion()->getOriginGlobal()); + gAgent.setRegion(regionp); + gObjectList.shiftObjects(shift_vector); + + if (isAgentAvatarValid()) + { + gAgentAvatarp->clearChatText(); + gAgentCamera.slamLookAt(look_at); + } + gAgent.setPositionAgent(pos); + gAssetStorage->setUpstream(sim); + gCacheName->setUpstream(sim); +*/ + + // Make sure we're standing + gAgent.standUp(); + + // now, use the circuit info to tell simulator about us! + LL_INFOS("Teleport","Messaging") << "process_teleport_finish() sending UseCircuitCode to enable sim_host " + << sim_host << " with code " << msg->mOurCircuitCode << LL_ENDL; + msg->newMessageFast(_PREHASH_UseCircuitCode); + msg->nextBlockFast(_PREHASH_CircuitCode); + msg->addU32Fast(_PREHASH_Code, msg->getOurCircuitCode()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addUUIDFast(_PREHASH_ID, gAgent.getID()); + msg->sendReliable(sim_host); + + LL_INFOS("Teleport") << "Calling send_complete_agent_movement() and setting state to TELEPORT_MOVING" << LL_ENDL; + send_complete_agent_movement(sim_host); + gAgent.setTeleportState( LLAgent::TELEPORT_MOVING ); + gAgent.setTeleportMessage(LLAgent::sTeleportProgressMessages["contacting"]); + + LL_DEBUGS("CrossingCaps") << "Calling setSeedCapability(). Seed cap == " + << seedCap << LL_ENDL; + regionp->setSeedCapability(seedCap); + + // Don't send camera updates to the new region until we're + // actually there... + + + // Now do teleport effect for where you're going. + // VEFFECT: TeleportEnd + effectp = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_POINT, true); + effectp->setPositionGlobal(gAgent.getPositionGlobal()); + + effectp->setColor(LLColor4U(gAgent.getEffectColor())); + LLHUDManager::getInstance()->sendEffects(); + +// gTeleportDisplay = true; +// gTeleportDisplayTimer.reset(); +// gViewerWindow->setShowProgress(true); +} + +// stuff we have to do every time we get an AvatarInitComplete from a sim +/* +void process_avatar_init_complete(LLMessageSystem* msg, void**) +{ + LLVector3 agent_pos; + msg->getVector3Fast(_PREHASH_AvatarData, _PREHASH_Position, agent_pos); + agent_movement_complete(msg->getSender(), agent_pos); +} +*/ + +void process_agent_movement_complete(LLMessageSystem* msg, void**) +{ + LL_INFOS("Teleport","Messaging") << "Received ProcessAgentMovementComplete" << LL_ENDL; + + gShiftFrame = true; + gAgentMovementCompleted = true; + + LLUUID agent_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); + LLUUID session_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_SessionID, session_id); + if((gAgent.getID() != agent_id) || (gAgent.getSessionID() != session_id)) + { + LL_WARNS("Teleport", "Messaging") << "Incorrect agent or session id in process_agent_movement_complete()" + << " agent " << agent_id << " expected " << gAgent.getID() + << " session " << session_id << " expected " << gAgent.getSessionID() + << ", ignoring" << LL_ENDL; + return; + } + + // *TODO: check timestamp to make sure the movement compleation + // makes sense. + LLVector3 agent_pos; + msg->getVector3Fast(_PREHASH_Data, _PREHASH_Position, agent_pos); + LLVector3 look_at; + msg->getVector3Fast(_PREHASH_Data, _PREHASH_LookAt, look_at); + U64 region_handle; + msg->getU64Fast(_PREHASH_Data, _PREHASH_RegionHandle, region_handle); + + std::string version_channel; + msg->getString("SimData", "ChannelVersion", version_channel); + + if (!isAgentAvatarValid()) + { + // Could happen if you were immediately god-teleported away on login, + // maybe other cases. Continue, but warn. + LL_WARNS("Teleport", "Messaging") << "agent_movement_complete() with NULL avatarp." << LL_ENDL; + } + + F32 x, y; + from_region_handle(region_handle, &x, &y); + LLViewerRegion* regionp = LLWorld::getInstance()->getRegionFromHandle(region_handle); + if (!regionp) + { + if (gAgent.getRegion()) + { + LL_WARNS("Teleport", "Messaging") << "current region origin " + << gAgent.getRegion()->getOriginGlobal() << " id " << gAgent.getRegion()->getRegionID() << LL_ENDL; + } + + LL_WARNS("Teleport", "Messaging") << "Agent being sent to invalid home region: " + << x << ":" << y + << " current pos " << gAgent.getPositionGlobal() + << ", calling forceDisconnect()" + << LL_ENDL; + LLAppViewer::instance()->forceDisconnect(LLTrans::getString("SentToInvalidRegion")); + return; + + } + + LL_INFOS("Teleport","Messaging") << "Changing home region to region id " << regionp->getRegionID() << " handle " << region_handle << " == x,y " << x << "," << y << LL_ENDL; + + // set our upstream host the new simulator and shuffle things as + // appropriate. + LLVector3 shift_vector = regionp->getPosRegionFromGlobal( + gAgent.getRegion()->getOriginGlobal()); + gAgent.setRegion(regionp); + gObjectList.shiftObjects(shift_vector); + gAssetStorage->setUpstream(msg->getSender()); + gCacheName->setUpstream(msg->getSender()); + gViewerThrottle.sendToSim(); + gViewerWindow->sendShapeToSim(); + + bool is_teleport = gAgent.getTeleportState() == LLAgent::TELEPORT_MOVING; + + if( is_teleport ) + { + if (gAgent.getTeleportKeepsLookAt()) + { + // *NOTE: the LookAt data we get from the sim here doesn't + // seem to be useful, so get it from the camera instead + look_at = LLViewerCamera::getInstance()->getAtAxis(); + } + // Force the camera back onto the agent, don't animate. + gAgentCamera.setFocusOnAvatar(true, false); + gAgentCamera.slamLookAt(look_at); + gAgentCamera.updateCamera(); + + LL_INFOS("Teleport") << "Agent movement complete, setting state to TELEPORT_START_ARRIVAL" << LL_ENDL; + gAgent.setTeleportState( LLAgent::TELEPORT_START_ARRIVAL ); + + if (isAgentAvatarValid()) + { + // Set the new position + gAgentAvatarp->setPositionAgent(agent_pos); + gAgentAvatarp->clearChat(); + gAgentAvatarp->slamPosition(); + } + } + else + { + // This is initial log-in or a region crossing + LL_INFOS("Teleport") << "State is not TELEPORT_MOVING, so this is initial log-in or region crossing. " + << "Setting state to TELEPORT_NONE" << LL_ENDL; + gAgent.setTeleportState( LLAgent::TELEPORT_NONE ); + + if(LLStartUp::getStartupState() < STATE_STARTED) + { // This is initial log-in, not a region crossing: + // Set the camera looking ahead of the AV so send_agent_update() below + // will report the correct location to the server. + LLVector3 look_at_point = look_at; + look_at_point = agent_pos + look_at_point.rotVec(gAgent.getQuat()); + + static LLVector3 up_direction(0.0f, 0.0f, 1.0f); + LLViewerCamera::getInstance()->lookAt(agent_pos, look_at_point, up_direction); + } + } + + if ( LLTracker::isTracking(NULL) ) + { + // Check distance to beacon, if < 5m, remove beacon + LLVector3d beacon_pos = LLTracker::getTrackedPositionGlobal(); + LLVector3 beacon_dir(agent_pos.mV[VX] - (F32)fmod(beacon_pos.mdV[VX], 256.0), agent_pos.mV[VY] - (F32)fmod(beacon_pos.mdV[VY], 256.0), 0); + if (beacon_dir.magVecSquared() < 25.f) + { + LLTracker::stopTracking(false); + } + else if ( is_teleport && !gAgent.getTeleportKeepsLookAt() && look_at.isExactlyZero()) + { + //look at the beacon + LLVector3 global_agent_pos = agent_pos; + global_agent_pos[0] += x; + global_agent_pos[1] += y; + look_at = (LLVector3)beacon_pos - global_agent_pos; + look_at.normVec(); + gAgentCamera.slamLookAt(look_at); + } + } + + // TODO: Put back a check for flying status! DK 12/19/05 + // Sim tells us whether the new position is off the ground + /* + if (teleport_flags & TELEPORT_FLAGS_IS_FLYING) + { + gAgent.setFlying(true); + } + else + { + gAgent.setFlying(false); + } + */ + + send_agent_update(true, true); + + if (gAgent.getRegion()->getBlockFly()) + { + gAgent.setFlying(gAgent.canFly()); + } + + // force simulator to recognize do not disturb state + if (gAgent.isDoNotDisturb()) + { + gAgent.setDoNotDisturb(true); + } + else + { + gAgent.setDoNotDisturb(false); + } + + if (isAgentAvatarValid()) + { + gAgentAvatarp->mFootPlane.clearVec(); + } + + // send walk-vs-run status + gAgent.sendWalkRun(gAgent.getRunning() || gAgent.getAlwaysRun()); + + // If the server version has changed, display an info box and offer + // to display the release notes, unless this is the initial log in. + if (gLastVersionChannel == version_channel) + { + return; + } + + gLastVersionChannel = version_channel; +} + +void process_crossed_region(LLMessageSystem* msg, void**) +{ + LLUUID agent_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); + LLUUID session_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_SessionID, session_id); + if((gAgent.getID() != agent_id) || (gAgent.getSessionID() != session_id)) + { + LL_WARNS("Messaging") << "Incorrect id in process_crossed_region()" + << LL_ENDL; + return; + } + LL_INFOS("Messaging") << "process_crossed_region()" << LL_ENDL; + gAgentAvatarp->resetRegionCrossingTimer(); + + U32 sim_ip; + msg->getIPAddrFast(_PREHASH_RegionData, _PREHASH_SimIP, sim_ip); + U16 sim_port; + msg->getIPPortFast(_PREHASH_RegionData, _PREHASH_SimPort, sim_port); + LLHost sim_host(sim_ip, sim_port); + U64 region_handle; + msg->getU64Fast(_PREHASH_RegionData, _PREHASH_RegionHandle, region_handle); + + std::string seedCap; + msg->getStringFast(_PREHASH_RegionData, _PREHASH_SeedCapability, seedCap); + + send_complete_agent_movement(sim_host); + + LLViewerRegion* regionp = LLWorld::getInstance()->addRegion(region_handle, sim_host); + + LL_DEBUGS("CrossingCaps") << "Calling setSeedCapability from process_crossed_region(). Seed cap == " + << seedCap << LL_ENDL; + regionp->setSeedCapability(seedCap); +} + + + +// Sends avatar and camera information to simulator. +// Sent roughly once per frame, or 20 times per second, whichever is less often + +const F32 THRESHOLD_HEAD_ROT_QDOT = 0.9997f; // ~= 2.5 degrees -- if its less than this we need to update head_rot +const F32 MAX_HEAD_ROT_QDOT = 0.99999f; // ~= 0.5 degrees -- if its greater than this then no need to update head_rot + // between these values we delay the updates (but no more than one second) + +void send_agent_update(bool force_send, bool send_reliable) +{ + LL_PROFILE_ZONE_SCOPED; + llassert(!gCubeSnapshot); + + if (gAgent.getTeleportState() != LLAgent::TELEPORT_NONE) + { + // We don't care if they want to send an agent update, they're not allowed to until the simulator + // that's the target is ready to receive them (after avatar_init_complete is received) + return; + } + + // We have already requested to log out. Don't send agent updates. + if(LLAppViewer::instance()->logoutRequestSent()) + { + return; + } + + // no region to send update to + if(gAgent.getRegion() == NULL) + { + return; + } + + const F32 TRANSLATE_THRESHOLD = 0.01f; + + // NOTA BENE: This is (intentionally?) using the small angle sine approximation to test for rotation + // Plus, there is an extra 0.5 in the mix since the perpendicular between last_camera_at and getAtAxis() bisects cam_rot_change + // Thus, we're actually testing against 0.2 degrees + const F32 ROTATION_THRESHOLD = 0.1f * 2.f*F_PI/360.f; // Rotation thresh 0.2 deg, see note above + + const U8 DUP_MSGS = 1; // HACK! number of times to repeat data on motionless agent + + // Store data on last sent update so that if no changes, no send + static LLVector3 last_camera_pos_agent, + last_camera_at, + last_camera_left, + last_camera_up; + + static LLVector3 cam_center_chg, + cam_rot_chg; + + static LLQuaternion last_head_rot; + static U32 last_control_flags = 0; + static U8 last_render_state; + static U8 duplicate_count = 0; + static F32 head_rot_chg = 1.0; + static U8 last_flags; + + LLMessageSystem *msg = gMessageSystem; + LLVector3 camera_pos_agent; // local to avatar's region + U8 render_state; + + LLQuaternion body_rotation = gAgent.getFrameAgent().getQuaternion(); + LLQuaternion head_rotation = gAgent.getHeadRotation(); + + camera_pos_agent = gAgentCamera.getCameraPositionAgent(); + + render_state = gAgent.getRenderState(); + + U32 control_flag_change = 0; + U8 flag_change = 0; + + cam_center_chg = last_camera_pos_agent - camera_pos_agent; + cam_rot_chg = last_camera_at - LLViewerCamera::getInstance()->getAtAxis(); + + // If a modifier key is held down, turn off + // LBUTTON and ML_LBUTTON so that using the camera (alt-key) doesn't + // trigger a control event. + U32 control_flags = gAgent.getControlFlags(); + + MASK key_mask = gKeyboard->currentMask(true); + + if (key_mask & MASK_ALT || key_mask & MASK_CONTROL) + { + control_flags &= ~( AGENT_CONTROL_LBUTTON_DOWN | + AGENT_CONTROL_ML_LBUTTON_DOWN ); + control_flags |= AGENT_CONTROL_LBUTTON_UP | + AGENT_CONTROL_ML_LBUTTON_UP ; + } + + control_flag_change = last_control_flags ^ control_flags; + + U8 flags = AU_FLAGS_NONE; + if (gAgent.isGroupTitleHidden()) + { + flags |= AU_FLAGS_HIDETITLE; + } + if (gAgent.getAutoPilot()) + { + flags |= AU_FLAGS_CLIENT_AUTOPILOT; + } + + flag_change = last_flags ^ flags; + + head_rot_chg = dot(last_head_rot, head_rotation); + + //static S32 msg_number = 0; // Used for diagnostic log messages + + if (force_send || + (cam_center_chg.magVec() > TRANSLATE_THRESHOLD) || + (head_rot_chg < THRESHOLD_HEAD_ROT_QDOT) || + (last_render_state != render_state) || + (cam_rot_chg.magVec() > ROTATION_THRESHOLD) || + control_flag_change != 0 || + flag_change != 0) + { + /* Diagnotics to show why we send the AgentUpdate message. Also un-commment the msg_number code above and below this block + msg_number += 1; + if (head_rot_chg < THRESHOLD_HEAD_ROT_QDOT) + { + //LL_INFOS("Messaging") << "head rot " << head_rotation << LL_ENDL; + LL_INFOS("Messaging") << "msg " << msg_number << ", frame " << LLFrameTimer::getFrameCount() << ", head_rot_chg " << head_rot_chg << LL_ENDL; + } + if (cam_rot_chg.magVec() > ROTATION_THRESHOLD) + { + LL_INFOS("Messaging") << "msg " << msg_number << ", frame " << LLFrameTimer::getFrameCount() << ", cam rot " << cam_rot_chg.magVec() << LL_ENDL; + } + if (cam_center_chg.magVec() > TRANSLATE_THRESHOLD) + { + LL_INFOS("Messaging") << "msg " << msg_number << ", frame " << LLFrameTimer::getFrameCount() << ", cam center " << cam_center_chg.magVec() << LL_ENDL; + } +// if (drag_delta_chg.magVec() > TRANSLATE_THRESHOLD) +// { +// LL_INFOS("Messaging") << "drag delta " << drag_delta_chg.magVec() << LL_ENDL; +// } + if (control_flag_change) + { + LL_INFOS("Messaging") << "msg " << msg_number << ", frame " << LLFrameTimer::getFrameCount() << ", dcf = " << control_flag_change << LL_ENDL; + } +*/ + + duplicate_count = 0; + } + else + { + duplicate_count++; + + if (head_rot_chg < MAX_HEAD_ROT_QDOT && duplicate_count < AGENT_UPDATES_PER_SECOND) + { + // The head_rotation is sent for updating things like attached guns. + // We only trigger a new update when head_rotation deviates beyond + // some threshold from the last update, however this can break fine + // adjustments when trying to aim an attached gun, so what we do here + // (where we would normally skip sending an update when nothing has changed) + // is gradually reduce the threshold to allow a better update to + // eventually get sent... should update to within 0.5 degrees in less + // than a second. + if (head_rot_chg < THRESHOLD_HEAD_ROT_QDOT + (MAX_HEAD_ROT_QDOT - THRESHOLD_HEAD_ROT_QDOT) * duplicate_count / AGENT_UPDATES_PER_SECOND) + { + duplicate_count = 0; + } + else + { + return; + } + } + else + { + return; + } + } + + if (duplicate_count < DUP_MSGS && !gDisconnected) + { + /* More diagnostics to count AgentUpdate messages + static S32 update_sec = 0; + static S32 update_count = 0; + static S32 max_update_count = 0; + S32 cur_sec = lltrunc( LLTimer::getTotalSeconds() ); + update_count += 1; + if (cur_sec != update_sec) + { + if (update_sec != 0) + { + update_sec = cur_sec; + //msg_number = 0; + max_update_count = llmax(max_update_count, update_count); + LL_INFOS() << "Sent " << update_count << " AgentUpdate messages per second, max is " << max_update_count << LL_ENDL; + } + update_sec = cur_sec; + update_count = 0; + } + */ + + // Build the message + msg->newMessageFast(_PREHASH_AgentUpdate); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addQuatFast(_PREHASH_BodyRotation, body_rotation); + msg->addQuatFast(_PREHASH_HeadRotation, head_rotation); + msg->addU8Fast(_PREHASH_State, render_state); + msg->addU8Fast(_PREHASH_Flags, flags); + +// if (camera_pos_agent.mV[VY] > 255.f) +// { +// LL_INFOS("Messaging") << "Sending camera center " << camera_pos_agent << LL_ENDL; +// } + + msg->addVector3Fast(_PREHASH_CameraCenter, camera_pos_agent); + msg->addVector3Fast(_PREHASH_CameraAtAxis, LLViewerCamera::getInstance()->getAtAxis()); + msg->addVector3Fast(_PREHASH_CameraLeftAxis, LLViewerCamera::getInstance()->getLeftAxis()); + msg->addVector3Fast(_PREHASH_CameraUpAxis, LLViewerCamera::getInstance()->getUpAxis()); + msg->addF32Fast(_PREHASH_Far, gAgentCamera.mDrawDistance); + + msg->addU32Fast(_PREHASH_ControlFlags, control_flags); + + if (gDebugClicks) + { + if (control_flags & AGENT_CONTROL_LBUTTON_DOWN) + { + LL_INFOS("Messaging") << "AgentUpdate left button down" << LL_ENDL; + } + + if (control_flags & AGENT_CONTROL_LBUTTON_UP) + { + LL_INFOS("Messaging") << "AgentUpdate left button up" << LL_ENDL; + } + } + + gAgent.enableControlFlagReset(); + + if (!send_reliable) + { + gAgent.sendMessage(); + } + else + { + gAgent.sendReliableMessage(); + } + +// LL_DEBUGS("Messaging") << "agent " << avatar_pos_agent << " cam " << camera_pos_agent << LL_ENDL; + + // Copy the old data + last_head_rot = head_rotation; + last_render_state = render_state; + last_camera_pos_agent = camera_pos_agent; + last_camera_at = LLViewerCamera::getInstance()->getAtAxis(); + last_camera_left = LLViewerCamera::getInstance()->getLeftAxis(); + last_camera_up = LLViewerCamera::getInstance()->getUpAxis(); + last_control_flags = control_flags; + last_flags = flags; + } +} + + +// sounds can arrive before objects, store them for a short time +// Note: this is a workaround for MAINT-4743, real fix would be to make +// server send sound along with object update that creates (rezes) the object +class PostponedSoundData +{ +public: + PostponedSoundData() : + mExpirationTime(0) + {} + PostponedSoundData(const LLUUID &object_id, const LLUUID &sound_id, const LLUUID& owner_id, const F32 gain, const U8 flags); + bool hasExpired() { return LLFrameTimer::getTotalSeconds() > mExpirationTime; } + + LLUUID mObjectId; + LLUUID mSoundId; + LLUUID mOwnerId; + F32 mGain; + U8 mFlags; + static const F64 MAXIMUM_PLAY_DELAY; + +private: + F64 mExpirationTime; //seconds since epoch +}; +const F64 PostponedSoundData::MAXIMUM_PLAY_DELAY = 15.0; +static F64 postponed_sounds_update_expiration = 0.0; +static std::map postponed_sounds; + +void set_attached_sound(LLViewerObject *objectp, const LLUUID &object_id, const LLUUID &sound_id, const LLUUID& owner_id, const F32 gain, const U8 flags) +{ + if (LLMuteList::getInstance()->isMuted(object_id)) return; + + if (LLMuteList::getInstance()->isMuted(owner_id, LLMute::flagObjectSounds)) return; + + // Don't play sounds from a region with maturity above current agent maturity + LLVector3d pos = objectp->getPositionGlobal(); + if (!gAgent.canAccessMaturityAtGlobal(pos)) + { + return; + } + + objectp->setAttachedSound(sound_id, owner_id, gain, flags); +} + +PostponedSoundData::PostponedSoundData(const LLUUID &object_id, const LLUUID &sound_id, const LLUUID& owner_id, const F32 gain, const U8 flags) + : + mObjectId(object_id), + mSoundId(sound_id), + mOwnerId(owner_id), + mGain(gain), + mFlags(flags), + mExpirationTime(LLFrameTimer::getTotalSeconds() + MAXIMUM_PLAY_DELAY) +{ +} + +// static +void update_attached_sounds() +{ + if (postponed_sounds.empty()) + { + return; + } + + std::map::iterator iter = postponed_sounds.begin(); + std::map::iterator end = postponed_sounds.end(); + while (iter != end) + { + std::map::iterator cur_iter = iter++; + PostponedSoundData* data = &cur_iter->second; + if (data->hasExpired()) + { + postponed_sounds.erase(cur_iter); + } + else + { + LLViewerObject *objectp = gObjectList.findObject(data->mObjectId); + if (objectp) + { + set_attached_sound(objectp, data->mObjectId, data->mSoundId, data->mOwnerId, data->mGain, data->mFlags); + postponed_sounds.erase(cur_iter); + } + } + } + postponed_sounds_update_expiration = LLFrameTimer::getTotalSeconds() + 2 * PostponedSoundData::MAXIMUM_PLAY_DELAY; +} + +//static +void clear_expired_postponed_sounds() +{ + if (postponed_sounds_update_expiration > LLFrameTimer::getTotalSeconds()) + { + return; + } + std::map::iterator iter = postponed_sounds.begin(); + std::map::iterator end = postponed_sounds.end(); + while (iter != end) + { + std::map::iterator cur_iter = iter++; + PostponedSoundData* data = &cur_iter->second; + if (data->hasExpired()) + { + postponed_sounds.erase(cur_iter); + } + } + postponed_sounds_update_expiration = LLFrameTimer::getTotalSeconds() + 2 * PostponedSoundData::MAXIMUM_PLAY_DELAY; +} + +// *TODO: Remove this dependency, or figure out a better way to handle +// this hack. +extern U32Bits gObjectData; + +void process_object_update(LLMessageSystem *mesgsys, void **user_data) +{ + // Update the data counters + if (mesgsys->getReceiveCompressedSize()) + { + gObjectData += (U32Bytes)mesgsys->getReceiveCompressedSize(); + } + else + { + gObjectData += (U32Bytes)mesgsys->getReceiveSize(); + } + + // Update the object... + S32 old_num_objects = gObjectList.mNumNewObjects; + gObjectList.processObjectUpdate(mesgsys, user_data, OUT_FULL); + if (old_num_objects != gObjectList.mNumNewObjects) + { + update_attached_sounds(); + } +} + +void process_compressed_object_update(LLMessageSystem *mesgsys, void **user_data) +{ + // Update the data counters + if (mesgsys->getReceiveCompressedSize()) + { + gObjectData += (U32Bytes)mesgsys->getReceiveCompressedSize(); + } + else + { + gObjectData += (U32Bytes)mesgsys->getReceiveSize(); + } + + // Update the object... + S32 old_num_objects = gObjectList.mNumNewObjects; + gObjectList.processCompressedObjectUpdate(mesgsys, user_data, OUT_FULL_COMPRESSED); + if (old_num_objects != gObjectList.mNumNewObjects) + { + update_attached_sounds(); + } +} + +void process_cached_object_update(LLMessageSystem *mesgsys, void **user_data) +{ + // Update the data counters + if (mesgsys->getReceiveCompressedSize()) + { + gObjectData += (U32Bytes)mesgsys->getReceiveCompressedSize(); + } + else + { + gObjectData += (U32Bytes)mesgsys->getReceiveSize(); + } + + // Update the object... + gObjectList.processCachedObjectUpdate(mesgsys, user_data, OUT_FULL_CACHED); +} + + +void process_terse_object_update_improved(LLMessageSystem *mesgsys, void **user_data) +{ + if (mesgsys->getReceiveCompressedSize()) + { + gObjectData += (U32Bytes)mesgsys->getReceiveCompressedSize(); + } + else + { + gObjectData += (U32Bytes)mesgsys->getReceiveSize(); + } + + S32 old_num_objects = gObjectList.mNumNewObjects; + gObjectList.processCompressedObjectUpdate(mesgsys, user_data, OUT_TERSE_IMPROVED); + if (old_num_objects != gObjectList.mNumNewObjects) + { + update_attached_sounds(); + } +} + +void process_kill_object(LLMessageSystem *mesgsys, void **user_data) +{ + LL_PROFILE_ZONE_SCOPED; + + LLUUID id; + + U32 ip = mesgsys->getSenderIP(); + U32 port = mesgsys->getSenderPort(); + LLViewerRegion* regionp = NULL; + { + LLHost host(ip, port); + regionp = LLWorld::getInstance()->getRegion(host); + } + + bool delete_object = LLViewerRegion::sVOCacheCullingEnabled; + S32 num_objects = mesgsys->getNumberOfBlocksFast(_PREHASH_ObjectData); + for (S32 i = 0; i < num_objects; ++i) + { + U32 local_id; + mesgsys->getU32Fast(_PREHASH_ObjectData, _PREHASH_ID, local_id, i); + + LLViewerObjectList::getUUIDFromLocal(id, local_id, ip, port); + if (id == LLUUID::null) + { + LL_DEBUGS("Messaging") << "Unknown kill for local " << local_id << LL_ENDL; + continue; + } + else + { + LL_DEBUGS("Messaging") << "Kill message for local " << local_id << LL_ENDL; + } + + if (id == gAgentID) + { + // never kill our avatar + continue; + } + + LLViewerObject *objectp = gObjectList.findObject(id); + if (objectp) + { + // Display green bubble on kill + if ( gShowObjectUpdates ) + { + LLColor4 color(0.f,1.f,0.f,1.f); + gPipeline.addDebugBlip(objectp->getPositionAgent(), color); + LL_DEBUGS("MessageBlip") << "Kill blip for local " << local_id << " at " << objectp->getPositionAgent() << LL_ENDL; + } + + // Do the kill + gObjectList.killObject(objectp); + } + + if(delete_object) + { + regionp->killCacheEntry(local_id); + } + + // We should remove the object from selection after it is marked dead by gObjectList to make LLToolGrab, + // which is using the object, release the mouse capture correctly when the object dies. + // See LLToolGrab::handleHoverActive() and LLToolGrab::handleHoverNonPhysical(). + LLSelectMgr::getInstance()->removeObjectFromSelections(id); + + } // end for loop + + LLViewerStatsRecorder::instance().recordObjectKills(num_objects); +} + +void process_time_synch(LLMessageSystem *mesgsys, void **user_data) +{ + LLVector3 sun_direction; + LLVector3 moon_direction; + LLVector3 sun_ang_velocity; + F32 phase; + U64 space_time_usec; + + U32 seconds_per_day; + U32 seconds_per_year; + + // "SimulatorViewerTimeMessage" + mesgsys->getU64Fast(_PREHASH_TimeInfo, _PREHASH_UsecSinceStart, space_time_usec); + mesgsys->getU32Fast(_PREHASH_TimeInfo, _PREHASH_SecPerDay, seconds_per_day); + mesgsys->getU32Fast(_PREHASH_TimeInfo, _PREHASH_SecPerYear, seconds_per_year); + + // This should eventually be moved to an "UpdateHeavenlyBodies" message + mesgsys->getF32Fast(_PREHASH_TimeInfo, _PREHASH_SunPhase, phase); + mesgsys->getVector3Fast(_PREHASH_TimeInfo, _PREHASH_SunDirection, sun_direction); + mesgsys->getVector3Fast(_PREHASH_TimeInfo, _PREHASH_SunAngVelocity, sun_ang_velocity); + + LLWorld::getInstance()->setSpaceTimeUSec(space_time_usec); + + LL_DEBUGS("WindlightSync") << "Sun phase: " << phase << " rad = " << fmodf(phase / F_TWO_PI + 0.25, 1.f) * 24.f << " h" << LL_ENDL; + + /* LAPRAS + We decode these parts of the message but ignore them + as the real values are provided elsewhere. */ + (void)sun_direction, (void)moon_direction, (void)phase; +} + +void process_sound_trigger(LLMessageSystem *msg, void **) +{ + if (!gAudiop) + { +#if !LL_LINUX + LL_WARNS("AudioEngine") << "LLAudioEngine instance doesn't exist!" << LL_ENDL; +#endif + return; + } + + U64 region_handle = 0; + F32 gain = 0; + LLUUID sound_id; + LLUUID owner_id; + LLUUID object_id; + LLUUID parent_id; + LLVector3 pos_local; + + msg->getUUIDFast(_PREHASH_SoundData, _PREHASH_SoundID, sound_id); + msg->getUUIDFast(_PREHASH_SoundData, _PREHASH_OwnerID, owner_id); + msg->getUUIDFast(_PREHASH_SoundData, _PREHASH_ObjectID, object_id); + msg->getUUIDFast(_PREHASH_SoundData, _PREHASH_ParentID, parent_id); + msg->getU64Fast(_PREHASH_SoundData, _PREHASH_Handle, region_handle); + msg->getVector3Fast(_PREHASH_SoundData, _PREHASH_Position, pos_local); + msg->getF32Fast(_PREHASH_SoundData, _PREHASH_Gain, gain); + + // adjust sound location to true global coords + LLVector3d pos_global = from_region_handle(region_handle); + pos_global.mdV[VX] += pos_local.mV[VX]; + pos_global.mdV[VY] += pos_local.mV[VY]; + pos_global.mdV[VZ] += pos_local.mV[VZ]; + + // Don't play a trigger sound if you can't hear it due + // to parcel "local audio only" settings. + if (!LLViewerParcelMgr::getInstance()->canHearSound(pos_global)) return; + + // Don't play sounds triggered by someone you muted. + if (LLMuteList::getInstance()->isMuted(owner_id, LLMute::flagObjectSounds)) return; + + // Don't play sounds from an object you muted + if (LLMuteList::getInstance()->isMuted(object_id)) return; + + // Don't play sounds from an object whose parent you muted + if (parent_id.notNull() + && LLMuteList::getInstance()->isMuted(parent_id)) + { + return; + } + + // Don't play sounds from a region with maturity above current agent maturity + if( !gAgent.canAccessMaturityInRegion( region_handle ) ) + { + return; + } + + // Don't play sounds from gestures if they are not enabled. + // Do play sounds triggered by avatar, since muting your own + // gesture sounds and your own sounds played inworld from + // Inventory can cause confusion. + if (object_id == owner_id + && owner_id != gAgentID + && !gSavedSettings.getBOOL("EnableGestureSounds")) + { + return; + } + + if (LLMaterialTable::basic.isCollisionSound(sound_id) && !gSavedSettings.getBOOL("EnableCollisionSounds")) + { + return; + } + + gAudiop->triggerSound(sound_id, owner_id, gain, LLAudioEngine::AUDIO_TYPE_SFX, pos_global); +} + +void process_preload_sound(LLMessageSystem *msg, void **user_data) +{ + if (!gAudiop) + { +#if !LL_LINUX + LL_WARNS("AudioEngine") << "LLAudioEngine instance doesn't exist!" << LL_ENDL; +#endif + return; + } + + LLUUID sound_id; + LLUUID object_id; + LLUUID owner_id; + + msg->getUUIDFast(_PREHASH_DataBlock, _PREHASH_SoundID, sound_id); + msg->getUUIDFast(_PREHASH_DataBlock, _PREHASH_ObjectID, object_id); + msg->getUUIDFast(_PREHASH_DataBlock, _PREHASH_OwnerID, owner_id); + + LLViewerObject *objectp = gObjectList.findObject(object_id); + if (!objectp) return; + + if (LLMuteList::getInstance()->isMuted(object_id)) return; + if (LLMuteList::getInstance()->isMuted(owner_id, LLMute::flagObjectSounds)) return; + + LLAudioSource *sourcep = objectp->getAudioSource(owner_id); + if (!sourcep) return; + + LLAudioData *datap = gAudiop->getAudioData(sound_id); + + // Note that I don't actually do any loading of the + // audio data into a buffer at this point, as it won't actually + // help us out. + + // Don't play sounds from a region with maturity above current agent maturity + LLVector3d pos_global = objectp->getPositionGlobal(); + if (gAgent.canAccessMaturityAtGlobal(pos_global)) + { + // Add audioData starts a transfer internally. + sourcep->addAudioData(datap, false); + } +} + +void process_attached_sound(LLMessageSystem *msg, void **user_data) +{ + F32 gain = 0; + LLUUID sound_id; + LLUUID object_id; + LLUUID owner_id; + U8 flags; + + msg->getUUIDFast(_PREHASH_DataBlock, _PREHASH_SoundID, sound_id); + msg->getUUIDFast(_PREHASH_DataBlock, _PREHASH_ObjectID, object_id); + msg->getUUIDFast(_PREHASH_DataBlock, _PREHASH_OwnerID, owner_id); + msg->getF32Fast(_PREHASH_DataBlock, _PREHASH_Gain, gain); + msg->getU8Fast(_PREHASH_DataBlock, _PREHASH_Flags, flags); + + LLViewerObject *objectp = gObjectList.findObject(object_id); + if (objectp) + { + set_attached_sound(objectp, object_id, sound_id, owner_id, gain, flags); + } + else if (sound_id.notNull()) + { + // we don't know about this object yet, probably it has yet to arrive + // std::map for dupplicate prevention. + postponed_sounds[object_id] = (PostponedSoundData(object_id, sound_id, owner_id, gain, flags)); + clear_expired_postponed_sounds(); + } + else + { + std::map::iterator iter = postponed_sounds.find(object_id); + if (iter != postponed_sounds.end()) + { + postponed_sounds.erase(iter); + } + } +} + +void process_attached_sound_gain_change(LLMessageSystem *mesgsys, void **user_data) +{ + F32 gain = 0; + LLUUID object_guid; + LLViewerObject *objectp = NULL; + + mesgsys->getUUIDFast(_PREHASH_DataBlock, _PREHASH_ObjectID, object_guid); + + if (!((objectp = gObjectList.findObject(object_guid)))) + { + // we don't know about this object, just bail + return; + } + + mesgsys->getF32Fast(_PREHASH_DataBlock, _PREHASH_Gain, gain); + + objectp->adjustAudioGain(gain); +} + + +void process_health_message(LLMessageSystem *mesgsys, void **user_data) +{ + F32 health; + + mesgsys->getF32Fast(_PREHASH_HealthData, _PREHASH_Health, health); + + if (gStatusBar) + { + gStatusBar->setHealth((S32)health); + } +} + + +void process_sim_stats(LLMessageSystem *msg, void **user_data) +{ + S32 count = msg->getNumberOfBlocks("Stat"); + for (S32 i = 0; i < count; ++i) + { + U32 stat_id; + F32 stat_value; + msg->getU32("Stat", "StatID", stat_id, i); + msg->getF32("Stat", "StatValue", stat_value, i); + auto measurementp = LLStatViewer::SimMeasurementSampler::getInstance((ESimStatID)stat_id); + + if (measurementp ) + { + measurementp->sample(stat_value); + } + else + { + LL_WARNS() << "Unknown sim stat identifier: " << stat_id << LL_ENDL; + } + } + + // + // Various hacks that aren't statistics, but are being handled here. + // + U32 max_tasks_per_region; + U64 region_flags; + msg->getU32("Region", "ObjectCapacity", max_tasks_per_region); + + if (msg->has(_PREHASH_RegionInfo)) + { + msg->getU64("RegionInfo", "RegionFlagsExtended", region_flags); + } + else + { + U32 flags = 0; + msg->getU32("Region", "RegionFlags", flags); + region_flags = flags; + } + + LLViewerRegion* regionp = gAgent.getRegion(); + if (regionp) + { + bool was_flying = gAgent.getFlying(); + regionp->setRegionFlags(region_flags); + regionp->setMaxTasks(max_tasks_per_region); + // HACK: This makes agents drop from the sky if the region is + // set to no fly while people are still in the sim. + if (was_flying && regionp->getBlockFly()) + { + gAgent.setFlying(gAgent.canFly()); + } + } +} + + + +void process_avatar_animation(LLMessageSystem *mesgsys, void **user_data) +{ + LLUUID animation_id; + LLUUID uuid; + S32 anim_sequence_id; + LLVOAvatar *avatarp = NULL; + + mesgsys->getUUIDFast(_PREHASH_Sender, _PREHASH_ID, uuid); + + LLViewerObject *objp = gObjectList.findObject(uuid); + if (objp) + { + avatarp = objp->asAvatar(); + } + + if (!avatarp) + { + // no agent by this ID...error? + LL_WARNS("Messaging") << "Received animation state for unknown avatar " << uuid << LL_ENDL; + return; + } + + S32 num_blocks = mesgsys->getNumberOfBlocksFast(_PREHASH_AnimationList); + S32 num_source_blocks = mesgsys->getNumberOfBlocksFast(_PREHASH_AnimationSourceList); + + LL_DEBUGS("Messaging", "Motion") << "Processing " << num_blocks << " Animations" << LL_ENDL; + + //clear animation flags + avatarp->mSignaledAnimations.clear(); + + if (avatarp->isSelf()) + { + LLUUID object_id; + + for( S32 i = 0; i < num_blocks; i++ ) + { + mesgsys->getUUIDFast(_PREHASH_AnimationList, _PREHASH_AnimID, animation_id, i); + mesgsys->getS32Fast(_PREHASH_AnimationList, _PREHASH_AnimSequenceID, anim_sequence_id, i); + + avatarp->mSignaledAnimations[animation_id] = anim_sequence_id; + + // *HACK: Disabling flying mode if it has been enabled shortly before the agent + // stand up animation is signaled. In this case we don't get a signal to start + // flying animation from server, the AGENT_CONTROL_FLY flag remains set but the + // avatar does not play flying animation, so we switch flying mode off. + // See LLAgent::setFlying(). This may cause "Stop Flying" button to blink. + // See EXT-2781. + if (animation_id == ANIM_AGENT_STANDUP && gAgent.getFlying()) + { + gAgent.setFlying(false); + } + + if (i < num_source_blocks) + { + mesgsys->getUUIDFast(_PREHASH_AnimationSourceList, _PREHASH_ObjectID, object_id, i); + + LLViewerObject* object = gObjectList.findObject(object_id); + if (object) + { + object->setFlagsWithoutUpdate(FLAGS_ANIM_SOURCE, true); + + bool anim_found = false; + LLVOAvatar::AnimSourceIterator anim_it = avatarp->mAnimationSources.find(object_id); + for (;anim_it != avatarp->mAnimationSources.end(); ++anim_it) + { + if (anim_it->first != object_id) + { + // elements with the same key are always contiguous, bail if we went past the + // end of this object's animations + break; + } + if (anim_it->second == animation_id) + { + anim_found = true; + break; + } + } + + if (!anim_found) + { + avatarp->mAnimationSources.insert(LLVOAvatar::AnimationSourceMap::value_type(object_id, animation_id)); + } + } + LL_DEBUGS("Messaging", "Motion") << "Anim sequence ID: " << anim_sequence_id + << " Animation id: " << animation_id + << " From block: " << object_id << LL_ENDL; + } + else + { + LL_DEBUGS("Messaging", "Motion") << "Anim sequence ID: " << anim_sequence_id + << " Animation id: " << animation_id << LL_ENDL; + } + } + } + else + { + for( S32 i = 0; i < num_blocks; i++ ) + { + mesgsys->getUUIDFast(_PREHASH_AnimationList, _PREHASH_AnimID, animation_id, i); + mesgsys->getS32Fast(_PREHASH_AnimationList, _PREHASH_AnimSequenceID, anim_sequence_id, i); + avatarp->mSignaledAnimations[animation_id] = anim_sequence_id; + } + } + + if (num_blocks) + { + avatarp->processAnimationStateChanges(); + } +} + + +void process_object_animation(LLMessageSystem *mesgsys, void **user_data) +{ + LLUUID animation_id; + LLUUID uuid; + S32 anim_sequence_id; + + mesgsys->getUUIDFast(_PREHASH_Sender, _PREHASH_ID, uuid); + + LL_DEBUGS("AnimatedObjectsNotify") << "Received animation state for object " << uuid << LL_ENDL; + + signaled_animation_map_t signaled_anims; + S32 num_blocks = mesgsys->getNumberOfBlocksFast(_PREHASH_AnimationList); + LL_DEBUGS("AnimatedObjectsNotify") << "processing object animation requests, num_blocks " << num_blocks << " uuid " << uuid << LL_ENDL; + for( S32 i = 0; i < num_blocks; i++ ) + { + mesgsys->getUUIDFast(_PREHASH_AnimationList, _PREHASH_AnimID, animation_id, i); + mesgsys->getS32Fast(_PREHASH_AnimationList, _PREHASH_AnimSequenceID, anim_sequence_id, i); + signaled_anims[animation_id] = anim_sequence_id; + LL_DEBUGS("AnimatedObjectsNotify") << "added signaled_anims animation request for object " + << uuid << " animation id " << animation_id << LL_ENDL; + } + LLObjectSignaledAnimationMap::instance().getMap()[uuid] = signaled_anims; + + LLViewerObject *objp = gObjectList.findObject(uuid); + if (!objp || objp->isDead()) + { + LL_DEBUGS("AnimatedObjectsNotify") << "Received animation state for unknown object " << uuid << LL_ENDL; + return; + } + + LLVOVolume *volp = dynamic_cast(objp); + if (!volp) + { + LL_DEBUGS("AnimatedObjectsNotify") << "Received animation state for non-volume object " << uuid << LL_ENDL; + return; + } + + if (!volp->isAnimatedObject()) + { + LL_DEBUGS("AnimatedObjectsNotify") << "Received animation state for non-animated object " << uuid << LL_ENDL; + return; + } + + volp->updateControlAvatar(); + LLControlAvatar *avatarp = volp->getControlAvatar(); + if (!avatarp) + { + LL_DEBUGS("AnimatedObjectsNotify") << "Received animation request for object with no control avatar, ignoring " << uuid << LL_ENDL; + return; + } + + if (!avatarp->mPlaying) + { + avatarp->mPlaying = true; + //if (!avatarp->mRootVolp->isAnySelected()) + { + avatarp->updateVolumeGeom(); + avatarp->mRootVolp->recursiveMarkForUpdate(); + } + } + + avatarp->updateAnimations(); +} + + +void process_avatar_appearance(LLMessageSystem *mesgsys, void **user_data) +{ + LLUUID uuid; + mesgsys->getUUIDFast(_PREHASH_Sender, _PREHASH_ID, uuid); + + LLVOAvatar* avatarp = (LLVOAvatar *)gObjectList.findObject(uuid); + if (avatarp) + { + avatarp->processAvatarAppearance( mesgsys ); + } + else + { + LL_WARNS("Messaging") << "avatar_appearance sent for unknown avatar " << uuid << LL_ENDL; + } +} + +void process_camera_constraint(LLMessageSystem *mesgsys, void **user_data) +{ + LLVector4 cameraCollidePlane; + mesgsys->getVector4Fast(_PREHASH_CameraCollidePlane, _PREHASH_Plane, cameraCollidePlane); + + gAgentCamera.setCameraCollidePlane(cameraCollidePlane); +} + +void near_sit_object(bool success, void *data) +{ + if (success) + { + // Send message to sit on object + gMessageSystem->newMessageFast(_PREHASH_AgentSit); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + gAgent.sendReliableMessage(); + } +} + +void process_avatar_sit_response(LLMessageSystem *mesgsys, void **user_data) +{ + LLVector3 sitPosition; + LLQuaternion sitRotation; + LLUUID sitObjectID; + bool use_autopilot; + mesgsys->getUUIDFast(_PREHASH_SitObject, _PREHASH_ID, sitObjectID); + mesgsys->getBOOLFast(_PREHASH_SitTransform, _PREHASH_AutoPilot, use_autopilot); + mesgsys->getVector3Fast(_PREHASH_SitTransform, _PREHASH_SitPosition, sitPosition); + mesgsys->getQuatFast(_PREHASH_SitTransform, _PREHASH_SitRotation, sitRotation); + LLVector3 camera_eye; + mesgsys->getVector3Fast(_PREHASH_SitTransform, _PREHASH_CameraEyeOffset, camera_eye); + LLVector3 camera_at; + mesgsys->getVector3Fast(_PREHASH_SitTransform, _PREHASH_CameraAtOffset, camera_at); + bool force_mouselook; + mesgsys->getBOOLFast(_PREHASH_SitTransform, _PREHASH_ForceMouselook, force_mouselook); + + if (isAgentAvatarValid() && dist_vec_squared(camera_eye, camera_at) > CAMERA_POSITION_THRESHOLD_SQUARED) + { + gAgentCamera.setSitCamera(sitObjectID, camera_eye, camera_at); + } + + gAgentCamera.setForceMouselook(force_mouselook); + // Forcing turning off flying here to prevent flying after pressing "Stand" + // to stand up from an object. See EXT-1655. + gAgent.setFlying(false); + + LLViewerObject* object = gObjectList.findObject(sitObjectID); + if (object) + { + LLVector3 sit_spot = object->getPositionAgent() + (sitPosition * object->getRotation()); + if (!use_autopilot || (isAgentAvatarValid() && gAgentAvatarp->isSitting() && gAgentAvatarp->getRoot() == object->getRoot())) + { + //we're already sitting on this object, so don't autopilot + } + else + { + gAgent.startAutoPilotGlobal(gAgent.getPosGlobalFromAgent(sit_spot), "Sit", &sitRotation, near_sit_object, NULL, 0.5f); + } + } + else + { + LL_WARNS("Messaging") << "Received sit approval for unknown object " << sitObjectID << LL_ENDL; + } +} + +void process_clear_follow_cam_properties(LLMessageSystem *mesgsys, void **user_data) +{ + LLUUID source_id; + + mesgsys->getUUIDFast(_PREHASH_ObjectData, _PREHASH_ObjectID, source_id); + + LLFollowCamMgr::getInstance()->removeFollowCamParams(source_id); +} + +void process_set_follow_cam_properties(LLMessageSystem *mesgsys, void **user_data) +{ + S32 type; + F32 value; + bool settingPosition = false; + bool settingFocus = false; + bool settingFocusOffset = false; + LLVector3 position; + LLVector3 focus; + LLVector3 focus_offset; + + LLUUID source_id; + + mesgsys->getUUIDFast(_PREHASH_ObjectData, _PREHASH_ObjectID, source_id); + + LLViewerObject* objectp = gObjectList.findObject(source_id); + if (objectp) + { + objectp->setFlagsWithoutUpdate(FLAGS_CAMERA_SOURCE, true); + } + + S32 num_objects = mesgsys->getNumberOfBlocks("CameraProperty"); + for (S32 block_index = 0; block_index < num_objects; block_index++) + { + mesgsys->getS32("CameraProperty", "Type", type, block_index); + mesgsys->getF32("CameraProperty", "Value", value, block_index); + switch(type) + { + case FOLLOWCAM_PITCH: + LLFollowCamMgr::getInstance()->setPitch(source_id, value); + break; + case FOLLOWCAM_FOCUS_OFFSET_X: + focus_offset.mV[VX] = value; + settingFocusOffset = true; + break; + case FOLLOWCAM_FOCUS_OFFSET_Y: + focus_offset.mV[VY] = value; + settingFocusOffset = true; + break; + case FOLLOWCAM_FOCUS_OFFSET_Z: + focus_offset.mV[VZ] = value; + settingFocusOffset = true; + break; + case FOLLOWCAM_POSITION_LAG: + LLFollowCamMgr::getInstance()->setPositionLag(source_id, value); + break; + case FOLLOWCAM_FOCUS_LAG: + LLFollowCamMgr::getInstance()->setFocusLag(source_id, value); + break; + case FOLLOWCAM_DISTANCE: + LLFollowCamMgr::getInstance()->setDistance(source_id, value); + break; + case FOLLOWCAM_BEHINDNESS_ANGLE: + LLFollowCamMgr::getInstance()->setBehindnessAngle(source_id, value); + break; + case FOLLOWCAM_BEHINDNESS_LAG: + LLFollowCamMgr::getInstance()->setBehindnessLag(source_id, value); + break; + case FOLLOWCAM_POSITION_THRESHOLD: + LLFollowCamMgr::getInstance()->setPositionThreshold(source_id, value); + break; + case FOLLOWCAM_FOCUS_THRESHOLD: + LLFollowCamMgr::getInstance()->setFocusThreshold(source_id, value); + break; + case FOLLOWCAM_ACTIVE: + //if 1, set using followcam,. + LLFollowCamMgr::getInstance()->setCameraActive(source_id, value != 0.f); + break; + case FOLLOWCAM_POSITION_X: + settingPosition = true; + position.mV[ 0 ] = value; + break; + case FOLLOWCAM_POSITION_Y: + settingPosition = true; + position.mV[ 1 ] = value; + break; + case FOLLOWCAM_POSITION_Z: + settingPosition = true; + position.mV[ 2 ] = value; + break; + case FOLLOWCAM_FOCUS_X: + settingFocus = true; + focus.mV[ 0 ] = value; + break; + case FOLLOWCAM_FOCUS_Y: + settingFocus = true; + focus.mV[ 1 ] = value; + break; + case FOLLOWCAM_FOCUS_Z: + settingFocus = true; + focus.mV[ 2 ] = value; + break; + case FOLLOWCAM_POSITION_LOCKED: + LLFollowCamMgr::getInstance()->setPositionLocked(source_id, value != 0.f); + break; + case FOLLOWCAM_FOCUS_LOCKED: + LLFollowCamMgr::getInstance()->setFocusLocked(source_id, value != 0.f); + break; + + default: + break; + } + } + + if ( settingPosition ) + { + LLFollowCamMgr::getInstance()->setPosition(source_id, position); + } + if ( settingFocus ) + { + LLFollowCamMgr::getInstance()->setFocus(source_id, focus); + } + if ( settingFocusOffset ) + { + LLFollowCamMgr::getInstance()->setFocusOffset(source_id, focus_offset); + } +} +//end Ventrella + + +// Culled from newsim lltask.cpp +void process_name_value(LLMessageSystem *mesgsys, void **user_data) +{ + std::string temp_str; + LLUUID id; + S32 i, num_blocks; + + mesgsys->getUUIDFast(_PREHASH_TaskData, _PREHASH_ID, id); + + LLViewerObject* object = gObjectList.findObject(id); + + if (object) + { + num_blocks = mesgsys->getNumberOfBlocksFast(_PREHASH_NameValueData); + for (i = 0; i < num_blocks; i++) + { + mesgsys->getStringFast(_PREHASH_NameValueData, _PREHASH_NVPair, temp_str, i); + LL_INFOS("Messaging") << "Added to object Name Value: " << temp_str << LL_ENDL; + object->addNVPair(temp_str); + } + } + else + { + LL_INFOS("Messaging") << "Can't find object " << id << " to add name value pair" << LL_ENDL; + } +} + +void process_remove_name_value(LLMessageSystem *mesgsys, void **user_data) +{ + std::string temp_str; + LLUUID id; + S32 i, num_blocks; + + mesgsys->getUUIDFast(_PREHASH_TaskData, _PREHASH_ID, id); + + LLViewerObject* object = gObjectList.findObject(id); + + if (object) + { + num_blocks = mesgsys->getNumberOfBlocksFast(_PREHASH_NameValueData); + for (i = 0; i < num_blocks; i++) + { + mesgsys->getStringFast(_PREHASH_NameValueData, _PREHASH_NVPair, temp_str, i); + LL_INFOS("Messaging") << "Removed from object Name Value: " << temp_str << LL_ENDL; + object->removeNVPair(temp_str); + } + } + else + { + LL_INFOS("Messaging") << "Can't find object " << id << " to remove name value pair" << LL_ENDL; + } +} + +void process_kick_user(LLMessageSystem *msg, void** /*user_data*/) +{ + std::string message; + + msg->getStringFast(_PREHASH_UserInfo, _PREHASH_Reason, message); + + LLAppViewer::instance()->forceDisconnect(message); +} + + +/* +void process_user_list_reply(LLMessageSystem *msg, void **user_data) +{ + LLUserList::processUserListReply(msg, user_data); + return; + char firstname[MAX_STRING+1]; + char lastname[MAX_STRING+1]; + U8 status; + S32 user_count; + + user_count = msg->getNumberOfBlocks("UserBlock"); + + for (S32 i = 0; i < user_count; i++) + { + msg->getData("UserBlock", i, "FirstName", firstname); + msg->getData("UserBlock", i, "LastName", lastname); + msg->getData("UserBlock", i, "Status", &status); + + if (status & 0x01) + { + dialog_friends_add_friend(buffer, true); + } + else + { + dialog_friends_add_friend(buffer, false); + } + } + + dialog_friends_done_adding(); +} +*/ + +// this is not handled in processUpdateMessage +/* +void process_time_dilation(LLMessageSystem *msg, void **user_data) +{ + // get the time_dilation + U16 foo; + msg->getData("TimeDilation", "TimeDilation", &foo); + F32 time_dilation = ((F32) foo) / 65535.f; + + // get the pointer to the right region + U32 ip = msg->getSenderIP(); + U32 port = msg->getSenderPort(); + LLViewerRegion *regionp = LLWorld::getInstance()->getRegion(ip, port); + if (regionp) + { + regionp->setTimeDilation(time_dilation); + } +} +*/ + + +void process_money_balance_reply( LLMessageSystem* msg, void** ) +{ + S32 balance = 0; + S32 credit = 0; + S32 committed = 0; + std::string desc; + LLUUID tid; + + msg->getUUID("MoneyData", "TransactionID", tid); + msg->getS32("MoneyData", "MoneyBalance", balance); + msg->getS32("MoneyData", "SquareMetersCredit", credit); + msg->getS32("MoneyData", "SquareMetersCommitted", committed); + msg->getStringFast(_PREHASH_MoneyData, _PREHASH_Description, desc); + LL_INFOS("Messaging") << "L$, credit, committed: " << balance << " " << credit << " " + << committed << LL_ENDL; + + if (gStatusBar) + { + gStatusBar->setBalance(balance); + gStatusBar->setLandCredit(credit); + gStatusBar->setLandCommitted(committed); + } + + if (desc.empty() + || !gSavedSettings.getBOOL("NotifyMoneyChange")) + { + // ...nothing to display + return; + } + + // Suppress duplicate messages about the same transaction + static std::deque recent; + if (std::find(recent.rbegin(), recent.rend(), tid) != recent.rend()) + { + return; + } + + // Once the 'recent' container gets large enough, chop some + // off the beginning. + const U32 MAX_LOOKBACK = 30; + const S32 POP_FRONT_SIZE = 12; + if(recent.size() > MAX_LOOKBACK) + { + LL_DEBUGS("Messaging") << "Removing oldest transaction records" << LL_ENDL; + recent.erase(recent.begin(), recent.begin() + POP_FRONT_SIZE); + } + //LL_DEBUGS("Messaging") << "Pushing back transaction " << tid << LL_ENDL; + recent.push_back(tid); + + if (msg->has("TransactionInfo")) + { + // ...message has extended info for localization + process_money_balance_reply_extended(msg); + } + else + { + // Only old dev grids will not supply the TransactionInfo block, + // so we can just use the hard-coded English string. + LLSD args; + args["MESSAGE"] = desc; + LLNotificationsUtil::add("SystemMessage", args); + } +} + +static std::string reason_from_transaction_type(S32 transaction_type, + const std::string& item_desc) +{ + // *NOTE: The keys for the reason strings are unusual because + // an earlier version of the code used English language strings + // extracted from hard-coded server English descriptions. + // Keeping them so we don't have to re-localize them. + switch (transaction_type) + { + case TRANS_OBJECT_SALE: + { + LLStringUtil::format_map_t arg; + arg["ITEM"] = item_desc; + return LLTrans::getString("for item", arg); + } + case TRANS_LAND_SALE: + return LLTrans::getString("for a parcel of land"); + + case TRANS_LAND_PASS_SALE: + return LLTrans::getString("for a land access pass"); + + case TRANS_GROUP_LAND_DEED: + return LLTrans::getString("for deeding land"); + + case TRANS_GROUP_CREATE: + return LLTrans::getString("to create a group"); + + case TRANS_GROUP_JOIN: + return LLTrans::getString("to join a group"); + + case TRANS_UPLOAD_CHARGE: + return LLTrans::getString("to upload"); + + case TRANS_CLASSIFIED_CHARGE: + return LLTrans::getString("to publish a classified ad"); + + case TRANS_GIFT: + // Simulator returns "Payment" if no custom description has been entered + return (item_desc == "Payment" ? std::string() : item_desc); + + // These have no reason to display, but are expected and should not + // generate warnings + case TRANS_PAY_OBJECT: + case TRANS_OBJECT_PAYS: + return std::string(); + + default: + LL_WARNS() << "Unknown transaction type " + << transaction_type << LL_ENDL; + return std::string(); + } +} + +static void money_balance_group_notify(const LLUUID& group_id, + const std::string& name, + bool is_group, + std::string notification, + LLSD args, + LLSD payload) +{ + // Message uses name SLURLs, don't actually have to substitute in + // the name. We're just making sure it's available. + // Notification is either PaymentReceived or PaymentSent + LLNotificationsUtil::add(notification, args, payload); +} + +static void money_balance_avatar_notify(const LLUUID& agent_id, + const LLAvatarName& av_name, + std::string notification, + LLSD args, + LLSD payload) +{ + // Message uses name SLURLs, don't actually have to substitute in + // the name. We're just making sure it's available. + // Notification is either PaymentReceived or PaymentSent + LLNotificationsUtil::add(notification, args, payload); +} + +static void process_money_balance_reply_extended(LLMessageSystem* msg) +{ + // Added in server 1.40 and viewer 2.1, support for localization + // and agent ids for name lookup. + S32 transaction_type = 0; + LLUUID source_id; + bool is_source_group = false; + LLUUID dest_id; + bool is_dest_group = false; + S32 amount = 0; + std::string item_description; + bool success = false; + + msg->getS32("TransactionInfo", "TransactionType", transaction_type); + msg->getUUID("TransactionInfo", "SourceID", source_id); + msg->getBOOL("TransactionInfo", "IsSourceGroup", is_source_group); + msg->getUUID("TransactionInfo", "DestID", dest_id); + msg->getBOOL("TransactionInfo", "IsDestGroup", is_dest_group); + msg->getS32("TransactionInfo", "Amount", amount); + msg->getString("TransactionInfo", "ItemDescription", item_description); + msg->getBOOL("MoneyData", "TransactionSuccess", success); + LL_INFOS("Money") << "MoneyBalanceReply source " << source_id + << " dest " << dest_id + << " type " << transaction_type + << " item " << item_description << LL_ENDL; + + if (source_id.isNull() && dest_id.isNull()) + { + // this is a pure balance update, no notification required + return; + } + + std::string source_slurl; + if (is_source_group) + { + source_slurl = + LLSLURL( "group", source_id, "inspect").getSLURLString(); + } + else + { + source_slurl = + LLSLURL( "agent", source_id, "completename").getSLURLString(); + } + + std::string dest_slurl; + if (is_dest_group) + { + dest_slurl = + LLSLURL( "group", dest_id, "inspect").getSLURLString(); + } + else + { + dest_slurl = + LLSLURL( "agent", dest_id, "completename").getSLURLString(); + } + + std::string reason = + reason_from_transaction_type(transaction_type, item_description); + + LLStringUtil::format_map_t args; + args["REASON"] = reason; // could be empty + args["AMOUNT"] = llformat("%d", amount); + + // Need to delay until name looked up, so need to know whether or not + // is group + bool is_name_group = false; + LLUUID name_id; + std::string message; + std::string notification; + LLSD final_args; + LLSD payload; + + bool you_paid_someone = (source_id == gAgentID); + std::string gift_suffix = (transaction_type == TRANS_GIFT ? "_gift" : ""); + if (you_paid_someone) + { + if(!gSavedSettings.getBOOL("NotifyMoneySpend")) + { + return; + } + args["NAME"] = dest_slurl; + is_name_group = is_dest_group; + name_id = dest_id; + if (!reason.empty()) + { + if (dest_id.notNull()) + { + message = success ? LLTrans::getString("you_paid_ldollars" + gift_suffix, args) : + LLTrans::getString("you_paid_failure_ldollars" + gift_suffix, args); + } + else + { + // transaction fee to the system, eg, to create a group + message = success ? LLTrans::getString("you_paid_ldollars_no_name", args) : + LLTrans::getString("you_paid_failure_ldollars_no_name", args); + } + } + else + { + if (dest_id.notNull()) + { + message = success ? LLTrans::getString("you_paid_ldollars_no_reason", args) : + LLTrans::getString("you_paid_failure_ldollars_no_reason", args); + } + else + { + // no target, no reason, you just paid money + message = success ? LLTrans::getString("you_paid_ldollars_no_info", args) : + LLTrans::getString("you_paid_failure_ldollars_no_info", args); + } + } + final_args["MESSAGE"] = message; + payload["dest_id"] = dest_id; + notification = success ? "PaymentSent" : "PaymentFailure"; + } + else + { + // ...someone paid you + if(!gSavedSettings.getBOOL("NotifyMoneyReceived")) + { + return; + } + args["NAME"] = source_slurl; + is_name_group = is_source_group; + name_id = source_id; + + if (!reason.empty() && !LLMuteList::getInstance()->isMuted(source_id)) + { + message = LLTrans::getString("paid_you_ldollars" + gift_suffix, args); + } + else + { + message = LLTrans::getString("paid_you_ldollars_no_reason", args); + } + final_args["MESSAGE"] = message; + + // make notification loggable + payload["from_id"] = source_id; + notification = "PaymentReceived"; + } + + // Despite using SLURLs, wait until the name is available before + // showing the notification, otherwise the UI layout is strange and + // the user sees a "Loading..." message + if (is_name_group) + { + gCacheName->getGroup(name_id, + boost::bind(&money_balance_group_notify, + _1, _2, _3, + notification, final_args, payload)); + } + else + { + LLAvatarNameCache::get(name_id, boost::bind(&money_balance_avatar_notify, _1, _2, notification, final_args, payload)); + } +} + +bool handle_prompt_for_maturity_level_change_callback(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + + if (0 == option) + { + // set the preference to the maturity of the region we're calling + U8 preferredMaturity = static_cast(notification["payload"]["_region_access"].asInteger()); + gSavedSettings.setU32("PreferredMaturity", static_cast(preferredMaturity)); + } + + return false; +} + +bool handle_prompt_for_maturity_level_change_and_reteleport_callback(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + + if (0 == option) + { + // set the preference to the maturity of the region we're calling + U8 preferredMaturity = static_cast(notification["payload"]["_region_access"].asInteger()); + gSavedSettings.setU32("PreferredMaturity", static_cast(preferredMaturity)); + gAgent.setMaturityRatingChangeDuringTeleport(preferredMaturity); + gAgent.restartFailedTeleportRequest(); + } + else + { + gAgent.clearTeleportRequest(); + } + + return false; +} + +// some of the server notifications need special handling. This is where we do that. +bool handle_special_notification(std::string notificationID, LLSD& llsdBlock) +{ + bool returnValue = false; + if(llsdBlock.has("_region_access")) + { + U8 regionAccess = static_cast(llsdBlock["_region_access"].asInteger()); + std::string regionMaturity = LLViewerRegion::accessToString(regionAccess); + LLStringUtil::toLower(regionMaturity); + llsdBlock["REGIONMATURITY"] = regionMaturity; + LLNotificationPtr maturityLevelNotification; + std::string notifySuffix = "_Notify"; + if (regionAccess == SIM_ACCESS_MATURE) + { + if (gAgent.isTeen()) + { + gAgent.clearTeleportRequest(); + maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_AdultsOnlyContent", llsdBlock); + returnValue = true; + + notifySuffix = "_NotifyAdultsOnly"; + } + else if (gAgent.prefersPG()) + { + maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_Change", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_callback); + returnValue = true; + } + else if (LLStringUtil::compareStrings(notificationID, "RegionEntryAccessBlocked") == 0) + { + maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_PreferencesOutOfSync", llsdBlock, llsdBlock); + returnValue = true; + } + } + else if (regionAccess == SIM_ACCESS_ADULT) + { + if (!gAgent.isAdult()) + { + gAgent.clearTeleportRequest(); + maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_AdultsOnlyContent", llsdBlock); + returnValue = true; + + notifySuffix = "_NotifyAdultsOnly"; + } + else if (gAgent.prefersPG() || gAgent.prefersMature()) + { + maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_Change", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_callback); + returnValue = true; + } + else if (LLStringUtil::compareStrings(notificationID, "RegionEntryAccessBlocked") == 0) + { + maturityLevelNotification = LLNotificationsUtil::add(notificationID+"_PreferencesOutOfSync", llsdBlock, llsdBlock); + returnValue = true; + } + } + + if ((maturityLevelNotification == NULL) || maturityLevelNotification->isIgnored()) + { + // Given a simple notification if no maturityLevelNotification is set or it is ignore + LLNotificationsUtil::add(notificationID + notifySuffix, llsdBlock); + } + } + + return returnValue; +} + +bool handle_trusted_experiences_notification(const LLSD& llsdBlock) +{ + if(llsdBlock.has("trusted_experiences")) + { + std::ostringstream str; + const LLSD& experiences = llsdBlock["trusted_experiences"]; + LLSD::array_const_iterator it = experiences.beginArray(); + for(/**/; it != experiences.endArray(); ++it) + { + str<asUUID(), "profile").getSLURLString() << "\n"; + } + std::string str_list = str.str(); + if(!str_list.empty()) + { + LLNotificationsUtil::add("TrustedExperiencesAvailable", LLSD::emptyMap().with("EXPERIENCE_LIST", (LLSD)str_list)); + return true; + } + } + return false; +} + +// some of the server notifications need special handling. This is where we do that. +bool handle_teleport_access_blocked(LLSD& llsdBlock, const std::string & notificationID, const std::string & defaultMessage) +{ + bool returnValue = false; + if(llsdBlock.has("_region_access")) + { + U8 regionAccess = static_cast(llsdBlock["_region_access"].asInteger()); + std::string regionMaturity = LLViewerRegion::accessToString(regionAccess); + LLStringUtil::toLower(regionMaturity); + llsdBlock["REGIONMATURITY"] = regionMaturity; + + LLNotificationPtr tp_failure_notification; + std::string notifySuffix; + + if (notificationID == std::string("TeleportEntryAccessBlocked")) + { + notifySuffix = "_Notify"; + if (regionAccess == SIM_ACCESS_MATURE) + { + if (gAgent.isTeen()) + { + gAgent.clearTeleportRequest(); + tp_failure_notification = LLNotificationsUtil::add(notificationID+"_AdultsOnlyContent", llsdBlock); + returnValue = true; + + notifySuffix = "_NotifyAdultsOnly"; + } + else if (gAgent.prefersPG()) + { + if (gAgent.hasRestartableFailedTeleportRequest()) + { + tp_failure_notification = LLNotificationsUtil::add(notificationID+"_ChangeAndReTeleport", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_and_reteleport_callback); + returnValue = true; + } + else + { + gAgent.clearTeleportRequest(); + tp_failure_notification = LLNotificationsUtil::add(notificationID+"_Change", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_callback); + returnValue = true; + } + } + else + { + gAgent.clearTeleportRequest(); + tp_failure_notification = LLNotificationsUtil::add(notificationID+"_PreferencesOutOfSync", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_callback); + returnValue = true; + } + } + else if (regionAccess == SIM_ACCESS_ADULT) + { + if (!gAgent.isAdult()) + { + gAgent.clearTeleportRequest(); + tp_failure_notification = LLNotificationsUtil::add(notificationID+"_AdultsOnlyContent", llsdBlock); + returnValue = true; + + notifySuffix = "_NotifyAdultsOnly"; + } + else if (gAgent.prefersPG() || gAgent.prefersMature()) + { + if (gAgent.hasRestartableFailedTeleportRequest()) + { + tp_failure_notification = LLNotificationsUtil::add(notificationID+"_ChangeAndReTeleport", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_and_reteleport_callback); + returnValue = true; + } + else + { + gAgent.clearTeleportRequest(); + tp_failure_notification = LLNotificationsUtil::add(notificationID+"_Change", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_callback); + returnValue = true; + } + } + else + { + gAgent.clearTeleportRequest(); + tp_failure_notification = LLNotificationsUtil::add(notificationID+"_PreferencesOutOfSync", llsdBlock, llsdBlock, handle_prompt_for_maturity_level_change_callback); + returnValue = true; + } + } + } // End of special handling for "TeleportEntryAccessBlocked" + else + { // Normal case, no message munging + gAgent.clearTeleportRequest(); + if (LLNotifications::getInstance()->templateExists(notificationID)) + { + tp_failure_notification = LLNotificationsUtil::add(notificationID, llsdBlock, llsdBlock); + } + else + { + llsdBlock["MESSAGE"] = defaultMessage; + tp_failure_notification = LLNotificationsUtil::add("GenericAlertOK", llsdBlock); + } + returnValue = true; + } + + if ((tp_failure_notification == NULL) || tp_failure_notification->isIgnored()) + { + // Given a simple notification if no tp_failure_notification is set or it is ignore + LLNotificationsUtil::add(notificationID + notifySuffix, llsdBlock); + } + } + + handle_trusted_experiences_notification(llsdBlock); + return returnValue; +} + +bool attempt_standard_notification(LLMessageSystem* msgsystem) +{ + // if we have additional alert data + if (msgsystem->has(_PREHASH_AlertInfo) && msgsystem->getNumberOfBlocksFast(_PREHASH_AlertInfo) > 0) + { + // notification was specified using the new mechanism, so we can just handle it here + std::string notificationID; + msgsystem->getStringFast(_PREHASH_AlertInfo, _PREHASH_Message, notificationID); + + //SL-13824 skip notification when both joining a group and leaving a group + //remove this after server stops sending these messages + if (notificationID == "JoinGroupSuccess" || + notificationID == "GroupDepart") + { + return true; + } + + if (!LLNotifications::getInstance()->templateExists(notificationID)) + { + return false; + } + + std::string llsdRaw; + LLSD llsdBlock; + msgsystem->getStringFast(_PREHASH_AlertInfo, _PREHASH_Message, notificationID); + msgsystem->getStringFast(_PREHASH_AlertInfo, _PREHASH_ExtraParams, llsdRaw); + if (llsdRaw.length()) + { + std::istringstream llsdData(llsdRaw); + if (!LLSDSerialize::deserialize(llsdBlock, llsdData, llsdRaw.length())) + { + LL_WARNS() << "attempt_standard_notification: Attempted to read notification parameter data into LLSD but failed:" << llsdRaw << LL_ENDL; + } + } + + + handle_trusted_experiences_notification(llsdBlock); + + if ( + (notificationID == "RegionEntryAccessBlocked") || + (notificationID == "LandClaimAccessBlocked") || + (notificationID == "LandBuyAccessBlocked") + + ) + { + /*--------------------------------------------------------------------- + (Commented so a grep will find the notification strings, since + we construct them on the fly; if you add additional notifications, + please update the comment.) + + Could throw any of the following notifications: + + RegionEntryAccessBlocked + RegionEntryAccessBlocked_Notify + RegionEntryAccessBlocked_NotifyAdultsOnly + RegionEntryAccessBlocked_Change + RegionEntryAccessBlocked_AdultsOnlyContent + RegionEntryAccessBlocked_ChangeAndReTeleport + LandClaimAccessBlocked + LandClaimAccessBlocked_Notify + LandClaimAccessBlocked_NotifyAdultsOnly + LandClaimAccessBlocked_Change + LandClaimAccessBlocked_AdultsOnlyContent + LandBuyAccessBlocked + LandBuyAccessBlocked_Notify + LandBuyAccessBlocked_NotifyAdultsOnly + LandBuyAccessBlocked_Change + LandBuyAccessBlocked_AdultsOnlyContent + + -----------------------------------------------------------------------*/ + static LLCachedControl ban_lines_mode(gSavedSettings , "ShowBanLines" , LLViewerParcelMgr::PARCEL_BAN_LINES_ON_COLLISION); + if (ban_lines_mode == LLViewerParcelMgr::PARCEL_BAN_LINES_ON_COLLISION) + { + LLViewerParcelMgr::getInstance()->resetCollisionTimer(); + } + if (handle_special_notification(notificationID, llsdBlock)) + { + return true; + } + } + // HACK -- handle callbacks for specific alerts. + if( notificationID == "HomePositionSet" ) + { + // save the home location image to disk + std::string snap_filename = gDirUtilp->getLindenUserDir(); + snap_filename += gDirUtilp->getDirDelimiter(); + snap_filename += LLStartUp::getScreenHomeFilename(); + gViewerWindow->saveSnapshot(snap_filename, + gViewerWindow->getWindowWidthRaw(), + gViewerWindow->getWindowHeightRaw(), + false, //UI + gSavedSettings.getBOOL("RenderHUDInSnapshot"), + false, + LLSnapshotModel::SNAPSHOT_TYPE_COLOR, + LLSnapshotModel::SNAPSHOT_FORMAT_PNG); + } + + if (notificationID == "RegionRestartMinutes" || + notificationID == "RegionRestartSeconds") + { + S32 seconds; + if (notificationID == "RegionRestartMinutes") + { + seconds = 60 * static_cast(llsdBlock["MINUTES"].asInteger()); + } + else + { + seconds = static_cast(llsdBlock["SECONDS"].asInteger()); + } + + LLFloaterRegionRestarting* floaterp = LLFloaterReg::findTypedInstance("region_restarting"); + + if (floaterp) + { + LLFloaterRegionRestarting::updateTime(seconds); + } + else + { + LLSD params; + params["NAME"] = llsdBlock["NAME"]; + params["SECONDS"] = (LLSD::Integer)seconds; + LLFloaterRegionRestarting* restarting_floater = dynamic_cast(LLFloaterReg::showInstance("region_restarting", params)); + if(restarting_floater) + { + restarting_floater->center(); + } + } + + make_ui_sound("UISndRestart"); + } + + // Special Marketplace update notification + if (notificationID == "SLM_UPDATE_FOLDER") + { + std::string state = llsdBlock["state"].asString(); + if (state == "deleted") + { + // Perform the deletion viewer side, no alert shown in this case + LLMarketplaceData::instance().deleteListing(llsdBlock["listing_id"].asInteger()); + return true; + } + else + { + // In general, no message will be displayed, all we want is to get the listing updated in the marketplace floater + // If getListing() fails though, the message of the alert will be shown by the caller of attempt_standard_notification() + return LLMarketplaceData::instance().getListing(llsdBlock["listing_id"].asInteger()); + } + } + + // Error Notification can come with and without reason + if (notificationID == "JoinGroupError") + { + if (llsdBlock.has("reason")) + { + LLNotificationsUtil::add("JoinGroupErrorReason", llsdBlock); + return true; + } + if (llsdBlock.has("group_id")) + { + LLGroupData agent_gdatap; + bool is_member = gAgent.getGroupData(llsdBlock["group_id"].asUUID(), agent_gdatap); + if (is_member) + { + LLSD args; + args["reason"] = LLTrans::getString("AlreadyInGroup"); + LLNotificationsUtil::add("JoinGroupErrorReason", args); + return true; + } + } + } + + LLNotificationsUtil::add(notificationID, llsdBlock); + return true; + } + return false; +} + + +static void process_special_alert_messages(const std::string & message) +{ + // Do special handling for alert messages. This is a legacy hack, and any actual displayed + // text should be altered in the notifications.xml files. + if ( message == "You died and have been teleported to your home location") + { + add(LLStatViewer::KILLED, 1); + } + else if( message == "Home position set." ) + { + // save the home location image to disk + std::string snap_filename = gDirUtilp->getLindenUserDir(); + snap_filename += gDirUtilp->getDirDelimiter(); + snap_filename += LLStartUp::getScreenHomeFilename(); + gViewerWindow->saveSnapshot(snap_filename, + gViewerWindow->getWindowWidthRaw(), + gViewerWindow->getWindowHeightRaw(), + false, + gSavedSettings.getBOOL("RenderHUDInSnapshot"), + false, + LLSnapshotModel::SNAPSHOT_TYPE_COLOR, + LLSnapshotModel::SNAPSHOT_FORMAT_PNG); + } +} + + + +void process_agent_alert_message(LLMessageSystem* msgsystem, void** user_data) +{ + // make sure the cursor is back to the usual default since the + // alert is probably due to some kind of error. + gViewerWindow->getWindow()->resetBusyCount(); + + std::string message; + msgsystem->getStringFast(_PREHASH_AlertData, _PREHASH_Message, message); + + process_special_alert_messages(message); + + if (!attempt_standard_notification(msgsystem)) + { + bool modal = false; + msgsystem->getBOOL("AlertData", "Modal", modal); + process_alert_core(message, modal); + } +} + +// The only difference between this routine and the previous is the fact that +// for this routine, the modal parameter is always false. Sadly, for the message +// handled by this routine, there is no "Modal" parameter on the message, and +// there's no API to tell if a message has the given parameter or not. +// So we can't handle the messages with the same handler. +void process_alert_message(LLMessageSystem *msgsystem, void **user_data) +{ + // make sure the cursor is back to the usual default since the + // alert is probably due to some kind of error. + gViewerWindow->getWindow()->resetBusyCount(); + + std::string message; + msgsystem->getStringFast(_PREHASH_AlertData, _PREHASH_Message, message); + process_special_alert_messages(message); + + if (!attempt_standard_notification(msgsystem)) + { + bool modal = false; + process_alert_core(message, modal); + + static LLCachedControl ban_lines_mode(gSavedSettings , "ShowBanLines" , LLViewerParcelMgr::PARCEL_BAN_LINES_ON_COLLISION); + if (ban_lines_mode == LLViewerParcelMgr::PARCEL_BAN_LINES_ON_COLLISION + && message.find("Cannot enter parcel") != std::string::npos) + { + LLViewerParcelMgr::getInstance()->resetCollisionTimer(); + } + } +} + +bool handle_not_age_verified_alert(const std::string &pAlertName) +{ + LLNotificationPtr notification = LLNotificationsUtil::add(pAlertName); + if ((notification == NULL) || notification->isIgnored()) + { + LLNotificationsUtil::add(pAlertName + "_Notify"); + } + + return true; +} + +bool handle_special_alerts(const std::string &pAlertName) +{ + bool isHandled = false; + if (LLStringUtil::compareStrings(pAlertName, "NotAgeVerified") == 0) + { + + isHandled = handle_not_age_verified_alert(pAlertName); + } + + return isHandled; +} + +void process_alert_core(const std::string& message, bool modal) +{ + const std::string ALERT_PREFIX("ALERT: "); + const std::string NOTIFY_PREFIX("NOTIFY: "); + if (message.find(ALERT_PREFIX) == 0) + { + // Allow the server to spawn a named alert so that server alerts can be + // translated out of English. + std::string alert_name(message.substr(ALERT_PREFIX.length())); + if (!handle_special_alerts(alert_name)) + { + LLNotificationsUtil::add(alert_name); + } + } + else if (message.find(NOTIFY_PREFIX) == 0) + { + // Allow the server to spawn a named notification so that server notifications can be + // translated out of English. + std::string notify_name(message.substr(NOTIFY_PREFIX.length())); + LLNotificationsUtil::add(notify_name); + } + else if (message[0] == '/') + { + // System message is important, show in upper-right box not tip + std::string text(message.substr(1)); + LLSD args; + + // *NOTE: If the text from the server ever changes this line will need to be adjusted. + std::string restart_cancelled = "Region restart cancelled."; + if (text.substr(0, restart_cancelled.length()) == restart_cancelled) + { + LLFloaterRegionRestarting::close(); + } + + std::string new_msg =LLNotifications::instance().getGlobalString(text); + args["MESSAGE"] = new_msg; + LLNotificationsUtil::add("SystemMessage", args); + } + else if (modal) + { + LLSD args; + std::string new_msg =LLNotifications::instance().getGlobalString(message); + args["ERROR_MESSAGE"] = new_msg; + LLNotificationsUtil::add("ErrorMessage", args); + } + else + { + // Hack fix for EXP-623 (blame fix on RN :)) to avoid a sim deploy + const std::string AUTOPILOT_CANCELED_MSG("Autopilot canceled"); + if (message.find(AUTOPILOT_CANCELED_MSG) == std::string::npos ) + { + LLSD args; + std::string new_msg =LLNotifications::instance().getGlobalString(message); + + std::string localized_msg; + bool is_message_localized = LLTrans::findString(localized_msg, new_msg); + + args["MESSAGE"] = is_message_localized ? localized_msg : new_msg; + LLNotificationsUtil::add("SystemMessageTip", args); + } + } +} + +mean_collision_list_t gMeanCollisionList; +time_t gLastDisplayedTime = 0; + +void handle_show_mean_events(void *) +{ + LLFloaterReg::showInstance("bumps"); + //LLFloaterBump::showInstance(); +} + +void mean_name_callback(const LLUUID &id, const LLAvatarName& av_name) +{ + static const U32 max_collision_list_size = 20; + if (gMeanCollisionList.size() > max_collision_list_size) + { + mean_collision_list_t::iterator iter = gMeanCollisionList.begin(); + for (U32 i=0; imPerp == id) + { + mcd->mFullName = av_name.getUserName(); + } + } +} + +void process_mean_collision_alert_message(LLMessageSystem *msgsystem, void **user_data) +{ + if (gAgent.inPrelude()) + { + // In prelude, bumping is OK. This dialog is rather confusing to + // newbies, so we don't show it. Drop the packet on the floor. + return; + } + + // make sure the cursor is back to the usual default since the + // alert is probably due to some kind of error. + gViewerWindow->getWindow()->resetBusyCount(); + + LLUUID perp; + U32 time; + U8 u8type; + EMeanCollisionType type; + F32 mag; + + S32 i, num = msgsystem->getNumberOfBlocks(_PREHASH_MeanCollision); + + for (i = 0; i < num; i++) + { + msgsystem->getUUIDFast(_PREHASH_MeanCollision, _PREHASH_Perp, perp); + msgsystem->getU32Fast(_PREHASH_MeanCollision, _PREHASH_Time, time); + msgsystem->getF32Fast(_PREHASH_MeanCollision, _PREHASH_Mag, mag); + msgsystem->getU8Fast(_PREHASH_MeanCollision, _PREHASH_Type, u8type); + + type = (EMeanCollisionType)u8type; + + bool b_found = false; + + for (mean_collision_list_t::iterator iter = gMeanCollisionList.begin(); + iter != gMeanCollisionList.end(); ++iter) + { + LLMeanCollisionData *mcd = *iter; + if ((mcd->mPerp == perp) && (mcd->mType == type)) + { + mcd->mTime = time; + mcd->mMag = mag; + b_found = true; + break; + } + } + + if (!b_found) + { + LLMeanCollisionData *mcd = new LLMeanCollisionData(gAgentID, perp, time, type, mag); + gMeanCollisionList.push_front(mcd); + LLAvatarNameCache::get(perp, boost::bind(&mean_name_callback, _1, _2)); + } + } + LLFloaterBump* bumps_floater = LLFloaterBump::getInstance(); + if(bumps_floater && bumps_floater->isInVisibleChain()) + { + bumps_floater->populateCollisionList(); + } +} + +void process_frozen_message(LLMessageSystem *msgsystem, void **user_data) +{ + // make sure the cursor is back to the usual default since the + // alert is probably due to some kind of error. + gViewerWindow->getWindow()->resetBusyCount(); + bool b_frozen; + + msgsystem->getBOOL("FrozenData", "Data", b_frozen); + + // TODO: make being frozen change view + if (b_frozen) + { + } + else + { + } +} + +// do some extra stuff once we get our economy data +void process_economy_data(LLMessageSystem *msg, void** /*user_data*/) +{ + LL_DEBUGS("Benefits") << "Received economy data, not currently used" << LL_ENDL; +} + +void notify_cautioned_script_question(const LLSD& notification, const LLSD& response, S32 orig_questions, bool granted) +{ + // only continue if at least some permissions were requested + if (orig_questions) + { + // check to see if the person we are asking + + // "'[OBJECTNAME]', an object owned by '[OWNERNAME]', + // located in [REGIONNAME] at [REGIONPOS], + // has been permission to: [PERMISSIONS]." + + LLUIString notice(LLTrans::getString(granted ? "ScriptQuestionCautionChatGranted" : "ScriptQuestionCautionChatDenied")); + + // always include the object name and owner name + notice.setArg("[OBJECTNAME]", notification["payload"]["object_name"].asString()); + notice.setArg("[OWNERNAME]", notification["payload"]["owner_name"].asString()); + + // try to lookup viewerobject that corresponds to the object that + // requested permissions (here, taskid->requesting object id) + bool foundpos = false; + LLViewerObject* viewobj = gObjectList.findObject(notification["payload"]["task_id"].asUUID()); + if (viewobj) + { + // found the viewerobject, get it's position in its region + LLVector3 objpos(viewobj->getPosition()); + + // try to lookup the name of the region the object is in + LLViewerRegion* viewregion = viewobj->getRegion(); + if (viewregion) + { + // got the region, so include the region and 3d coordinates of the object + notice.setArg("[REGIONNAME]", viewregion->getName()); + std::string formatpos = llformat("%.1f, %.1f,%.1f", objpos[VX], objpos[VY], objpos[VZ]); + notice.setArg("[REGIONPOS]", formatpos); + + foundpos = true; + } + } + + if (!foundpos) + { + // unable to determine location of the object + notice.setArg("[REGIONNAME]", "(unknown region)"); + notice.setArg("[REGIONPOS]", "(unknown position)"); + } + + // check each permission that was requested, and list each + // permission that has been flagged as a caution permission + bool caution = false; + S32 count = 0; + std::string perms; + for (const script_perm_t& script_perm : SCRIPT_PERMISSIONS) + { + if ((orig_questions & script_perm.permbit) + && script_perm.caution) + { + count++; + caution = true; + + // add a comma before the permission description if it is not the first permission + // added to the list or the last permission to check + if (count > 1) + { + perms.append(", "); + } + + perms.append(LLTrans::getString(script_perm.question)); + } + } + + notice.setArg("[PERMISSIONS]", perms); + + // log a chat message as long as at least one requested permission + // is a caution permission + if (caution) + { + LLChat chat(notice.getString()); + // LLFloaterChat::addChat(chat, false, false); + } + } +} + +void script_question_mute(const LLUUID& item_id, const std::string& object_name); + +void experiencePermissionBlock(LLUUID experience, LLSD result) +{ + LLSD permission; + LLSD data; + permission["permission"] = "Block"; + data[experience.asString()] = permission; + data["experience"] = experience; + LLEventPumps::instance().obtain("experience_permission").post(data); +} + +bool script_question_cb(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + LLMessageSystem *msg = gMessageSystem; + S32 orig = notification["payload"]["questions"].asInteger(); + S32 new_questions = orig; + + if (response["Details"]) + { + // respawn notification... + LLNotificationsUtil::add(notification["name"], notification["substitutions"], notification["payload"]); + + // ...with description on top + LLNotificationsUtil::add("DebitPermissionDetails"); + return false; + } + + LLUUID experience; + if(notification["payload"].has("experience")) + { + experience = notification["payload"]["experience"].asUUID(); + } + + // check whether permissions were granted or denied + bool allowed = true; + // the "yes/accept" button is the first button in the template, making it button 0 + // if any other button was clicked, the permissions were denied + if (option != 0) + { + new_questions = 0; + allowed = false; + } + else if(experience.notNull()) + { + LLSD permission; + LLSD data; + permission["permission"]="Allow"; + + data[experience.asString()]=permission; + data["experience"]=experience; + LLEventPumps::instance().obtain("experience_permission").post(data); + } + + LLUUID task_id = notification["payload"]["task_id"].asUUID(); + LLUUID item_id = notification["payload"]["item_id"].asUUID(); + + // reply with the permissions granted or denied + msg->newMessageFast(_PREHASH_ScriptAnswerYes); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_Data); + msg->addUUIDFast(_PREHASH_TaskID, task_id); + msg->addUUIDFast(_PREHASH_ItemID, item_id); + msg->addS32Fast(_PREHASH_Questions, new_questions); + msg->sendReliable(LLHost(notification["payload"]["sender"].asString())); + + // only log a chat message if caution prompts are enabled + if (gSavedSettings.getBOOL("PermissionsCautionEnabled")) + { + // log a chat message, if appropriate + notify_cautioned_script_question(notification, response, orig, allowed); + } + + if ( response["Mute"] ) // mute + { + script_question_mute(task_id,notification["payload"]["object_name"].asString()); + } + if ( response["BlockExperience"] ) + { + if(experience.notNull()) + { + LLViewerRegion* region = gAgent.getRegion(); + if (!region) + return false; + + LLExperienceCache::instance().setExperiencePermission(experience, std::string("Block"), boost::bind(&experiencePermissionBlock, experience, _1)); + + } +} + return false; +} + +void script_question_mute(const LLUUID& task_id, const std::string& object_name) +{ + LLMuteList::getInstance()->add(LLMute(task_id, object_name, LLMute::OBJECT)); + + // purge the message queue of any previously queued requests from the same source. DEV-4879 + class OfferMatcher : public LLNotificationsUI::LLScreenChannel::Matcher + { + public: + OfferMatcher(const LLUUID& to_block) : blocked_id(to_block) {} + bool matches(const LLNotificationPtr notification) const + { + if (notification->getName() == "ScriptQuestionCaution" + || notification->getName() == "ScriptQuestion") + { + return (notification->getPayload()["task_id"].asUUID() == blocked_id); + } + return false; + } + private: + const LLUUID& blocked_id; + }; + + LLNotificationsUI::LLChannelManager::getInstance()->killToastsFromChannel( + LLNotificationsUI::NOTIFICATION_CHANNEL_UUID, + OfferMatcher(task_id)); +} + +static LLNotificationFunctorRegistration script_question_cb_reg_1("ScriptQuestion", script_question_cb); +static LLNotificationFunctorRegistration script_question_cb_reg_2("ScriptQuestionCaution", script_question_cb); +static LLNotificationFunctorRegistration script_question_cb_reg_3("ScriptQuestionExperience", script_question_cb); + +void process_script_experience_details(const LLSD& experience_details, LLSD args, LLSD payload) +{ + if(experience_details[LLExperienceCache::PROPERTIES].asInteger() & LLExperienceCache::PROPERTY_GRID) + { + args["GRID_WIDE"] = LLTrans::getString("Grid-Scope"); + } + else + { + args["GRID_WIDE"] = LLTrans::getString("Land-Scope"); + } + args["EXPERIENCE"] = LLSLURL("experience", experience_details[LLExperienceCache::EXPERIENCE_ID].asUUID(), "profile").getSLURLString(); + + LLNotificationsUtil::add("ScriptQuestionExperience", args, payload); +} + +void process_script_question(LLMessageSystem *msg, void **user_data) +{ + // *TODO: Translate owner name -> [FIRST] [LAST] + + LLHost sender = msg->getSender(); + + LLUUID taskid; + LLUUID itemid; + S32 questions; + std::string object_name; + std::string owner_name; + LLUUID experienceid; + + // taskid -> object key of object requesting permissions + msg->getUUIDFast(_PREHASH_Data, _PREHASH_TaskID, taskid ); + // itemid -> script asset key of script requesting permissions + msg->getUUIDFast(_PREHASH_Data, _PREHASH_ItemID, itemid ); + msg->getStringFast(_PREHASH_Data, _PREHASH_ObjectName, object_name); + msg->getStringFast(_PREHASH_Data, _PREHASH_ObjectOwner, owner_name); + msg->getS32Fast(_PREHASH_Data, _PREHASH_Questions, questions ); + + if(msg->has(_PREHASH_Experience)) + { + msg->getUUIDFast(_PREHASH_Experience, _PREHASH_ExperienceID, experienceid); + } + + // Special case. If the objects are owned by this agent, throttle per-object instead + // of per-owner. It's common for residents to reset a ton of scripts that re-request + // permissions, as with tier boxes. UUIDs can't be valid agent names and vice-versa, + // so we'll reuse the same namespace for both throttle types. + std::string throttle_name = owner_name; + std::string self_name; + LLAgentUI::buildFullname( self_name ); // does not include ' Resident' + std::string clean_owner_name = LLCacheName::cleanFullName(owner_name); // removes ' Resident' + if( clean_owner_name == self_name ) + { + throttle_name = taskid.getString(); + } + + // don't display permission requests if this object is muted + if (LLMuteList::getInstance()->isMuted(taskid)) return; + + // throttle excessive requests from any specific user's scripts + typedef LLKeyThrottle LLStringThrottle; + static LLStringThrottle question_throttle( LLREQUEST_PERMISSION_THROTTLE_LIMIT, LLREQUEST_PERMISSION_THROTTLE_INTERVAL ); + + switch (question_throttle.noteAction(throttle_name)) + { + case LLStringThrottle::THROTTLE_NEWLY_BLOCKED: + LL_INFOS("Messaging") << "process_script_question throttled" + << " owner_name:" << owner_name + << LL_ENDL; + // Fall through + + case LLStringThrottle::THROTTLE_BLOCKED: + // Escape altogether until we recover + return; + + case LLStringThrottle::THROTTLE_OK: + break; + } + + std::string script_question; + if (questions) + { + bool caution = false; + S32 count = 0; + LLSD args; + args["OBJECTNAME"] = object_name; + args["NAME"] = clean_owner_name; + S32 known_questions = 0; + bool has_not_only_debit = questions ^ SCRIPT_PERMISSIONS[SCRIPT_PERMISSION_DEBIT].permbit; + // check the received permission flags against each permission + for (const script_perm_t& script_perm : SCRIPT_PERMISSIONS) + { + if (questions & script_perm.permbit) + { + count++; + known_questions |= script_perm.permbit; + // check whether permission question should cause special caution dialog + caution |= (script_perm.caution); + + if (("ScriptTakeMoney" == script_perm.question) && has_not_only_debit) + continue; + + if (LLTrans::getString(script_perm.question).empty()) + { + continue; + } + + script_question += " " + LLTrans::getString(script_perm.question) + "\n"; + } + } + + args["QUESTIONS"] = script_question; + + if (known_questions != questions) + { + // This is in addition to the normal dialog. + // Viewer got a request for not supported/implemented permission + LL_WARNS("Messaging") << "Object \"" << object_name << "\" requested " << script_question + << " permission. Permission is unknown and can't be granted. Item id: " << itemid + << " taskid:" << taskid << LL_ENDL; + } + + if (known_questions) + { + LLSD payload; + payload["task_id"] = taskid; + payload["item_id"] = itemid; + payload["sender"] = sender.getIPandPort(); + payload["questions"] = known_questions; + payload["object_name"] = object_name; + payload["owner_name"] = owner_name; + + // check whether cautions are even enabled or not + const char* notification = "ScriptQuestion"; + + if(caution && gSavedSettings.getBOOL("PermissionsCautionEnabled")) + { + args["FOOTERTEXT"] = (count > 1) ? LLTrans::getString("AdditionalPermissionsRequestHeader") + "\n\n" + script_question : ""; + notification = "ScriptQuestionCaution"; + } + else if(experienceid.notNull()) + { + payload["experience"]=experienceid; + LLExperienceCache::instance().get(experienceid, boost::bind(process_script_experience_details, _1, args, payload)); + return; + } + + LLNotificationsUtil::add(notification, args, payload); + } + } +} + + +void process_derez_container(LLMessageSystem *msg, void**) +{ + LL_WARNS("Messaging") << "call to deprecated process_derez_container" << LL_ENDL; +} + +void container_inventory_arrived(LLViewerObject* object, + LLInventoryObject::object_list_t* inventory, + S32 serial_num, + void* data) +{ + LL_DEBUGS("Messaging") << "container_inventory_arrived()" << LL_ENDL; + if( gAgentCamera.cameraMouselook() ) + { + gAgentCamera.changeCameraToDefault(); + } + + LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(); + + if (inventory->size() > 2) + { + // create a new inventory category to put this in + LLUUID cat_id; + gInventory.createNewCategory( + gInventory.getRootFolderID(), + LLFolderType::FT_NONE, + LLTrans::getString("AcquiredItems"), + [inventory](const LLUUID &new_cat_id) + { + LLInventoryObject::object_list_t::const_iterator it = inventory->begin(); + LLInventoryObject::object_list_t::const_iterator end = inventory->end(); + for (; it != end; ++it) + { + if ((*it)->getType() != LLAssetType::AT_CATEGORY) + { + LLInventoryObject* obj = (LLInventoryObject*)(*it); + LLInventoryItem* item = (LLInventoryItem*)(obj); + LLUUID item_id; + item_id.generate(); + time_t creation_date_utc = time_corrected(); + LLPointer new_item + = new LLViewerInventoryItem(item_id, + new_cat_id, + item->getPermissions(), + item->getAssetUUID(), + item->getType(), + item->getInventoryType(), + item->getName(), + item->getDescription(), + LLSaleInfo::DEFAULT, + item->getFlags(), + creation_date_utc); + new_item->updateServer(true); + gInventory.updateItem(new_item); + } + } + gInventory.notifyObservers(); + + LLInventoryPanel *active_panel = LLInventoryPanel::getActiveInventoryPanel(); + if (active_panel) + { + active_panel->setSelection(new_cat_id, TAKE_FOCUS_NO); + } + }); + } + else if (inventory->size() == 2) + { + // we're going to get one fake root category as well as the + // one actual object + LLInventoryObject::object_list_t::iterator it = inventory->begin(); + + if ((*it)->getType() == LLAssetType::AT_CATEGORY) + { + ++it; + } + + LLInventoryItem* item = (LLInventoryItem*)((LLInventoryObject*)(*it)); + const LLUUID category = gInventory.findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(item->getType())); + + LLUUID item_id; + item_id.generate(); + time_t creation_date_utc = time_corrected(); + LLPointer new_item + = new LLViewerInventoryItem(item_id, category, + item->getPermissions(), + item->getAssetUUID(), + item->getType(), + item->getInventoryType(), + item->getName(), + item->getDescription(), + LLSaleInfo::DEFAULT, + item->getFlags(), + creation_date_utc); + new_item->updateServer(true); + gInventory.updateItem(new_item); + gInventory.notifyObservers(); + if(active_panel) + { + active_panel->setSelection(item_id, TAKE_FOCUS_NO); + } + } + + // we've got the inventory, now delete this object if this was a take + bool delete_object = (bool)(intptr_t)data; + LLViewerRegion *region = gAgent.getRegion(); + if (delete_object && region) + { + gMessageSystem->newMessageFast(_PREHASH_ObjectDelete); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + const U8 NO_FORCE = 0; + gMessageSystem->addU8Fast(_PREHASH_Force, NO_FORCE); + gMessageSystem->nextBlockFast(_PREHASH_ObjectData); + gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, object->getLocalID()); + gMessageSystem->sendReliable(region->getHost()); + } +} + +// method to format the time. +std::string formatted_time(const time_t& the_time) +{ + std::string dateStr = "["+LLTrans::getString("LTimeWeek")+"] [" + +LLTrans::getString("LTimeMonth")+"] [" + +LLTrans::getString("LTimeDay")+"] [" + +LLTrans::getString("LTimeHour")+"]:[" + +LLTrans::getString("LTimeMin")+"]:[" + +LLTrans::getString("LTimeSec")+"] [" + +LLTrans::getString("LTimeYear")+"]"; + + LLSD substitution; + substitution["datetime"] = (S32) the_time; + LLStringUtil::format (dateStr, substitution); + return dateStr; +} + + +void process_teleport_failed(LLMessageSystem *msg, void**) +{ + LL_WARNS("Teleport","Messaging") << "Received TeleportFailed message" << LL_ENDL; + + std::string message_id; // Tag from server, like "RegionEntryAccessBlocked" + std::string big_reason; // Actual message to display + LLSD args; + + // Let the interested parties know that teleport failed. + LLViewerParcelMgr::getInstance()->onTeleportFailed(); + + // if we have additional alert data + if (msg->has(_PREHASH_AlertInfo) && msg->getSizeFast(_PREHASH_AlertInfo, _PREHASH_Message) > 0) + { + // Get the message ID + msg->getStringFast(_PREHASH_AlertInfo, _PREHASH_Message, message_id); + big_reason = LLAgent::sTeleportErrorMessages[message_id]; + if ( big_reason.size() <= 0 ) + { + // Nothing found in the map - use what the server returned in the original message block + msg->getStringFast(_PREHASH_Info, _PREHASH_Reason, big_reason); + } + LL_WARNS("Teleport") << "AlertInfo message_id " << message_id << " reason: " << big_reason << LL_ENDL; + + LLSD llsd_block; + std::string llsd_raw; + msg->getStringFast(_PREHASH_AlertInfo, _PREHASH_ExtraParams, llsd_raw); + if (llsd_raw.length()) + { + std::istringstream llsd_data(llsd_raw); + if (!LLSDSerialize::deserialize(llsd_block, llsd_data, llsd_raw.length())) + { + LL_WARNS("Teleport") << "process_teleport_failed: Attempted to read alert parameter data into LLSD but failed:" << llsd_raw << LL_ENDL; + } + else + { + LL_WARNS("Teleport") << "AlertInfo llsd block received: " << llsd_block << LL_ENDL; + if(llsd_block.has("REGION_NAME")) + { + std::string region_name = llsd_block["REGION_NAME"].asString(); + if(!region_name.empty()) + { + LLStringUtil::format_map_t name_args; + name_args["[REGION_NAME]"] = region_name; + LLStringUtil::format(big_reason, name_args); + } + } + // change notification name in this special case + if (handle_teleport_access_blocked(llsd_block, message_id, args["REASON"])) + { + if( gAgent.getTeleportState() != LLAgent::TELEPORT_NONE ) + { + LL_WARNS("Teleport") << "called handle_teleport_access_blocked, setting state to TELEPORT_NONE" << LL_ENDL; + gAgent.setTeleportState( LLAgent::TELEPORT_NONE ); + } + return; + } + } + } + args["REASON"] = big_reason; + } + else + { // Extra message payload not found - use what the simulator sent + msg->getStringFast(_PREHASH_Info, _PREHASH_Reason, message_id); + + big_reason = LLAgent::sTeleportErrorMessages[message_id]; + if ( big_reason.size() > 0 ) + { // Substitute verbose reason from the local map + args["REASON"] = big_reason; + } + else + { // Nothing found in the map - use what the server returned + args["REASON"] = message_id; + } + } + LL_WARNS("Teleport") << "Displaying CouldNotTeleportReason string, REASON= " << args["REASON"] << LL_ENDL; + + LLNotificationsUtil::add("CouldNotTeleportReason", args); + + if( gAgent.getTeleportState() != LLAgent::TELEPORT_NONE ) + { + LL_WARNS("Teleport") << "End of process_teleport_failed(). Reason message arg is " << args["REASON"] + << ". Setting state to TELEPORT_NONE" << LL_ENDL; + gAgent.setTeleportState( LLAgent::TELEPORT_NONE ); + } +} + +void process_teleport_local(LLMessageSystem *msg,void**) +{ + LL_INFOS("Teleport","Messaging") << "Received TeleportLocal message" << LL_ENDL; + + LLUUID agent_id; + msg->getUUIDFast(_PREHASH_Info, _PREHASH_AgentID, agent_id); + if (agent_id != gAgent.getID()) + { + LL_WARNS("Teleport", "Messaging") << "Got teleport notification for wrong agent " << agent_id << " expected " << gAgent.getID() << ", ignoring!" << LL_ENDL; + return; + } + + U32 location_id; + LLVector3 pos, look_at; + U32 teleport_flags; + msg->getU32Fast(_PREHASH_Info, _PREHASH_LocationID, location_id); + msg->getVector3Fast(_PREHASH_Info, _PREHASH_Position, pos); + msg->getVector3Fast(_PREHASH_Info, _PREHASH_LookAt, look_at); + msg->getU32Fast(_PREHASH_Info, _PREHASH_TeleportFlags, teleport_flags); + + LL_INFOS("Teleport") << "Message params are location_id " << location_id << " teleport_flags " << teleport_flags << LL_ENDL; + if( gAgent.getTeleportState() != LLAgent::TELEPORT_NONE ) + { + if( gAgent.getTeleportState() == LLAgent::TELEPORT_LOCAL ) + { + // To prevent TeleportStart messages re-activating the progress screen right + // after tp, keep the teleport state and let progress screen clear it after a short delay + // (progress screen is active but not visible) *TODO: remove when SVC-5290 is fixed + gTeleportDisplayTimer.reset(); + gTeleportDisplay = true; + } + else + { + LL_WARNS("Teleport") << "State is not TELEPORT_LOCAL: " << gAgent.getTeleportStateName() + << ", setting state to TELEPORT_NONE" << LL_ENDL; + gAgent.setTeleportState( LLAgent::TELEPORT_NONE ); + } + } + + // Sim tells us whether the new position is off the ground + if (teleport_flags & TELEPORT_FLAGS_IS_FLYING) + { + gAgent.setFlying(true); + } + else + { + gAgent.setFlying(false); + } + + gAgent.setPositionAgent(pos); + gAgentCamera.slamLookAt(look_at); + + if ( !(gAgent.getTeleportKeepsLookAt() && LLViewerJoystick::getInstance()->getOverrideCamera()) ) + { + gAgentCamera.resetView(true, true); + } + + // send camera update to new region + gAgentCamera.updateCamera(); + + send_agent_update(true, true); + + // Let the interested parties know we've teleported. + // Vadim *HACK: Agent position seems to get reset (to render position?) + // on each frame, so we have to pass the new position manually. + LLViewerParcelMgr::getInstance()->onTeleportFinished(true, gAgent.getPosGlobalFromAgent(pos)); +} + +void send_simple_im(const LLUUID& to_id, + const std::string& message, + EInstantMessage dialog, + const LLUUID& id) +{ + std::string my_name; + LLAgentUI::buildFullname(my_name); + send_improved_im(to_id, + my_name, + message, + IM_ONLINE, + dialog, + id, + NO_TIMESTAMP, + (U8*)EMPTY_BINARY_BUCKET, + EMPTY_BINARY_BUCKET_SIZE); +} + +void send_group_notice(const LLUUID& group_id, + const std::string& subject, + const std::string& message, + const LLInventoryItem* item) +{ + // Put this notice into an instant message form. + // This will mean converting the item to a binary bucket, + // and the subject/message into a single field. + std::string my_name; + LLAgentUI::buildFullname(my_name); + + // Combine subject + message into a single string. + std::ostringstream subject_and_message; + // TODO: turn all existing |'s into ||'s in subject and message. + subject_and_message << subject << "|" << message; + + // Create an empty binary bucket. + U8 bin_bucket[MAX_INVENTORY_BUFFER_SIZE]; + U8* bucket_to_send = bin_bucket; + bin_bucket[0] = '\0'; + S32 bin_bucket_size = EMPTY_BINARY_BUCKET_SIZE; + // If there is an item being sent, pack it into the binary bucket. + if (item) + { + LLSD item_def; + item_def["item_id"] = item->getUUID(); + item_def["owner_id"] = item->getPermissions().getOwner(); + std::ostringstream ostr; + LLSDSerialize::serialize(item_def, ostr, LLSDSerialize::LLSD_XML); + bin_bucket_size = ostr.str().copy( + (char*)bin_bucket, ostr.str().size()); + bin_bucket[bin_bucket_size] = '\0'; + } + else + { + bucket_to_send = (U8*) EMPTY_BINARY_BUCKET; + } + + + send_improved_im( + group_id, + my_name, + subject_and_message.str(), + IM_ONLINE, + IM_GROUP_NOTICE, + LLUUID::null, + NO_TIMESTAMP, + bucket_to_send, + bin_bucket_size); +} + +void send_lures(const LLSD& notification, const LLSD& response) +{ + std::string text = response["message"].asString(); + LLSLURL slurl; + LLAgentUI::buildSLURL(slurl); + text.append("\r\n").append(slurl.getSLURLString()); + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_StartLure); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_Info); + msg->addU8Fast(_PREHASH_LureType, (U8)0); // sim will fill this in. + msg->addStringFast(_PREHASH_Message, text); + for(LLSD::array_const_iterator it = notification["payload"]["ids"].beginArray(); + it != notification["payload"]["ids"].endArray(); + ++it) + { + LLUUID target_id = it->asUUID(); + + msg->nextBlockFast(_PREHASH_TargetData); + msg->addUUIDFast(_PREHASH_TargetID, target_id); + + // Record the offer. + { + LLAvatarName av_name; + LLAvatarNameCache::get(target_id, &av_name); // for im log filenames + LLSD args; + args["TO_NAME"] = LLSLURL("agent", target_id, "completename").getSLURLString();; + + LLSD payload; + + //*TODO please rewrite all keys to the same case, lower or upper + payload["from_id"] = target_id; + payload["SUPPRESS_TOAST"] = true; + LLNotificationsUtil::add("TeleportOfferSent", args, payload); + + // Add the recepient to the recent people list. + LLRecentPeople::instance().add(target_id); + } + } + gAgent.sendReliableMessage(); +} + +bool handle_lure_callback(const LLSD& notification, const LLSD& response) +{ + static const unsigned OFFER_RECIPIENT_LIMIT = 250; + if(notification["payload"]["ids"].size() > OFFER_RECIPIENT_LIMIT) + { + // More than OFFER_RECIPIENT_LIMIT targets will overload the message + // producing an llerror. + LLSD args; + args["OFFERS"] = LLSD::Integer(notification["payload"]["ids"].size()); + args["LIMIT"] = static_cast(OFFER_RECIPIENT_LIMIT); + LLNotificationsUtil::add("TooManyTeleportOffers", args); + return false; + } + + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + + if(0 == option) + { + send_lures(notification, response); + } + + return false; +} + +void handle_lure(const LLUUID& invitee) +{ + std::vector ids; + ids.push_back(invitee); + handle_lure(ids); +} + +// Prompt for a message to the invited user. +void handle_lure(const uuid_vec_t& ids) +{ + if (ids.empty()) return; + + if (!gAgent.getRegion()) return; + + LLSD edit_args; + edit_args["REGION"] = gAgent.getRegion()->getName(); + + LLSD payload; + for (std::vector::const_iterator it = ids.begin(); + it != ids.end(); + ++it) + { + payload["ids"].append(*it); + } + if (gAgent.isGodlike()) + { + LLNotificationsUtil::add("OfferTeleportFromGod", edit_args, payload, handle_lure_callback); + } + else + { + LLNotificationsUtil::add("OfferTeleport", edit_args, payload, handle_lure_callback); + } +} + +bool teleport_request_callback(const LLSD& notification, const LLSD& response) +{ + LLUUID from_id = notification["payload"]["from_id"].asUUID(); + if(from_id.isNull()) + { + LL_WARNS() << "from_id is NULL" << LL_ENDL; + return false; + } + + LLAvatarName av_name; + LLAvatarNameCache::get(from_id, &av_name); + + if(LLMuteList::getInstance()->isMuted(from_id) && !LLMuteList::isLinden(av_name.getUserName())) + { + return false; + } + + S32 option = 0; + if (response.isInteger()) + { + option = response.asInteger(); + } + else + { + option = LLNotificationsUtil::getSelectedOption(notification, response); + } + + switch(option) + { + // Yes + case 0: + { + LLSD dummy_notification; + dummy_notification["payload"]["ids"][0] = from_id; + + LLSD dummy_response; + dummy_response["message"] = response["message"]; + + send_lures(dummy_notification, dummy_response); + } + break; + + // No + case 1: + default: + break; + } + + return false; +} + +static LLNotificationFunctorRegistration teleport_request_callback_reg("TeleportRequest", teleport_request_callback); + +void send_improved_im(const LLUUID& to_id, + const std::string& name, + const std::string& message, + U8 offline, + EInstantMessage dialog, + const LLUUID& id, + U32 timestamp, + const U8* binary_bucket, + S32 binary_bucket_size) +{ + pack_instant_message( + gMessageSystem, + gAgent.getID(), + false, + gAgent.getSessionID(), + to_id, + name, + message, + offline, + dialog, + id, + 0, + LLUUID::null, + gAgent.getPositionAgent(), + timestamp, + binary_bucket, + binary_bucket_size); + gAgent.sendReliableMessage(); +} + + +void send_places_query(const LLUUID& query_id, + const LLUUID& trans_id, + const std::string& query_text, + U32 query_flags, + S32 category, + const std::string& sim_name) +{ + LLMessageSystem* msg = gMessageSystem; + + msg->newMessage("PlacesQuery"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->addUUID("QueryID", query_id); + msg->nextBlock("TransactionData"); + msg->addUUID("TransactionID", trans_id); + msg->nextBlock("QueryData"); + msg->addString("QueryText", query_text); + msg->addU32("QueryFlags", query_flags); + msg->addS8("Category", (S8)category); + msg->addString("SimName", sim_name); + gAgent.sendReliableMessage(); +} + +// Deprecated in favor of cap "UserInfo" +void process_user_info_reply(LLMessageSystem* msg, void**) +{ + LLUUID agent_id; + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id); + if(agent_id != gAgent.getID()) + { + LL_WARNS("Messaging") << "process_user_info_reply - " + << "wrong agent id." << LL_ENDL; + } + + std::string email; + msg->getStringFast(_PREHASH_UserData, _PREHASH_EMail, email); + std::string dir_visibility; + msg->getString( "UserData", "DirectoryVisibility", dir_visibility); + + LLFloaterPreference::updateUserInfo(dir_visibility); + LLFloaterSnapshot::setAgentEmail(email); +} + + +//--------------------------------------------------------------------------- +// Script Dialog +//--------------------------------------------------------------------------- + +const S32 SCRIPT_DIALOG_MAX_BUTTONS = 12; +const char* SCRIPT_DIALOG_HEADER = "Script Dialog:\n"; + +bool callback_script_dialog(const LLSD& notification, const LLSD& response) +{ + LLNotificationForm form(notification["form"]); + + std::string rtn_text; + S32 button_idx; + button_idx = LLNotification::getSelectedOption(notification, response); + if (response[TEXTBOX_MAGIC_TOKEN].isDefined()) + { + if (response[TEXTBOX_MAGIC_TOKEN].isString()) + rtn_text = response[TEXTBOX_MAGIC_TOKEN].asString(); + else + rtn_text.clear(); // bool marks empty string + } + else + { + rtn_text = LLNotification::getSelectedOptionName(response); + } + + // Button -2 = Mute + // Button -1 = Ignore - no processing needed for this button + // Buttons 0 and above = dialog choices + + if (-2 == button_idx) + { + std::string object_name = notification["payload"]["object_name"].asString(); + LLUUID object_id = notification["payload"]["object_id"].asUUID(); + LLMute mute(object_id, object_name, LLMute::OBJECT); + if (LLMuteList::getInstance()->add(mute)) + { + // This call opens the sidebar, displays the block list, and highlights the newly blocked + // object in the list so the user can see that their block click has taken effect. + LLPanelBlockedList::showPanelAndSelect(object_id); + } + } + + if (0 <= button_idx) + { + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("ScriptDialogReply"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->nextBlock("Data"); + msg->addUUID("ObjectID", notification["payload"]["object_id"].asUUID()); + msg->addS32("ChatChannel", notification["payload"]["chat_channel"].asInteger()); + msg->addS32("ButtonIndex", button_idx); + msg->addString("ButtonLabel", rtn_text); + msg->sendReliable(LLHost(notification["payload"]["sender"].asString())); + } + + return false; +} +static LLNotificationFunctorRegistration callback_script_dialog_reg_1("ScriptDialog", callback_script_dialog); +static LLNotificationFunctorRegistration callback_script_dialog_reg_2("ScriptDialogGroup", callback_script_dialog); + +void process_script_dialog(LLMessageSystem* msg, void**) +{ + S32 i; + LLSD payload; + + LLUUID object_id; + msg->getUUID("Data", "ObjectID", object_id); + +// For compability with OS grids first check for presence of extended packet before fetching data. + LLUUID owner_id; + if (gMessageSystem->getNumberOfBlocks("OwnerData") > 0) + { + msg->getUUID("OwnerData", "OwnerID", owner_id); + } + + if (LLMuteList::getInstance()->isMuted(object_id) || LLMuteList::getInstance()->isMuted(owner_id)) + { + return; + } + + std::string message; + std::string first_name; + std::string last_name; + std::string object_name; + + S32 chat_channel; + msg->getString("Data", "FirstName", first_name); + msg->getString("Data", "LastName", last_name); + msg->getString("Data", "ObjectName", object_name); + msg->getString("Data", "Message", message); + msg->getS32("Data", "ChatChannel", chat_channel); + + // unused for now + LLUUID image_id; + msg->getUUID("Data", "ImageID", image_id); + + payload["sender"] = msg->getSender().getIPandPort(); + payload["object_id"] = object_id; + payload["chat_channel"] = chat_channel; + payload["object_name"] = object_name; + + // build up custom form + S32 button_count = msg->getNumberOfBlocks("Buttons"); + if (button_count > SCRIPT_DIALOG_MAX_BUTTONS) + { + LL_WARNS() << "Too many script dialog buttons - omitting some" << LL_ENDL; + button_count = SCRIPT_DIALOG_MAX_BUTTONS; + } + + LLNotificationForm form; + for (i = 0; i < button_count; i++) + { + std::string tdesc; + msg->getString("Buttons", "ButtonLabel", tdesc, i); + form.addElement("button", std::string(tdesc)); + } + + LLSD args; + args["TITLE"] = object_name; + args["MESSAGE"] = message; + LLNotificationPtr notification; + if (!first_name.empty()) + { + args["NAME"] = LLCacheName::buildFullName(first_name, last_name); + notification = LLNotifications::instance().add( + LLNotification::Params("ScriptDialog").substitutions(args).payload(payload).form_elements(form.asLLSD())); + } + else + { + args["GROUPNAME"] = last_name; + notification = LLNotifications::instance().add( + LLNotification::Params("ScriptDialogGroup").substitutions(args).payload(payload).form_elements(form.asLLSD())); + } +} + +//--------------------------------------------------------------------------- + + +std::vector gLoadUrlList; + +bool callback_load_url(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + + if (0 == option) + { + LLWeb::loadURL(notification["payload"]["url"].asString()); + } + + return false; +} +static LLNotificationFunctorRegistration callback_load_url_reg("LoadWebPage", callback_load_url); + +// We've got the name of the person or group that owns the object hurling the url. +// Display confirmation dialog. +void callback_load_url_name(const LLUUID& id, const std::string& full_name, bool is_group) +{ + std::vector::iterator it; + for (it = gLoadUrlList.begin(); it != gLoadUrlList.end(); ) + { + LLSD load_url_info = *it; + if (load_url_info["owner_id"].asUUID() == id) + { + it = gLoadUrlList.erase(it); + + std::string owner_name; + if (is_group) + { + owner_name = full_name + LLTrans::getString("Group"); + } + else + { + owner_name = full_name; + } + + // For legacy name-only mutes. + if (LLMuteList::getInstance()->isMuted(LLUUID::null, owner_name)) + { + continue; + } + LLSD args; + args["URL"] = load_url_info["url"].asString(); + args["MESSAGE"] = load_url_info["message"].asString();; + args["OBJECTNAME"] = load_url_info["object_name"].asString(); + args["NAME_SLURL"] = LLSLURL(is_group ? "group" : "agent", id, "about").getSLURLString(); + + LLNotificationsUtil::add("LoadWebPage", args, load_url_info); + } + else + { + ++it; + } + } +} + +// We've got the name of the person who owns the object hurling the url. +void callback_load_url_avatar_name(const LLUUID& id, const LLAvatarName& av_name) +{ + callback_load_url_name(id, av_name.getUserName(), false); +} + +void process_load_url(LLMessageSystem* msg, void**) +{ + LLUUID object_id; + LLUUID owner_id; + bool owner_is_group; + char object_name[256]; /* Flawfinder: ignore */ + char message[256]; /* Flawfinder: ignore */ + char url[256]; /* Flawfinder: ignore */ + + msg->getString("Data", "ObjectName", 256, object_name); + msg->getUUID( "Data", "ObjectID", object_id); + msg->getUUID( "Data", "OwnerID", owner_id); + msg->getBOOL( "Data", "OwnerIsGroup", owner_is_group); + msg->getString("Data", "Message", 256, message); + msg->getString("Data", "URL", 256, url); + + LLSD payload; + payload["object_id"] = object_id; + payload["owner_id"] = owner_id; + payload["owner_is_group"] = owner_is_group; + payload["object_name"] = object_name; + payload["message"] = message; + payload["url"] = url; + + // URL is safety checked in load_url above + + // Check if object or owner is muted + if (LLMuteList::getInstance()->isMuted(object_id, object_name) || + LLMuteList::getInstance()->isMuted(owner_id)) + { + LL_INFOS("Messaging")<<"Ignoring load_url from muted object/owner."<getGroup(owner_id, boost::bind(&callback_load_url_name, _1, _2, _3)); + } + else + { + LLAvatarNameCache::get(owner_id, boost::bind(&callback_load_url_avatar_name, _1, _2)); + } +} + + +void callback_download_complete(void** data, S32 result, LLExtStat ext_status) +{ + std::string* filepath = (std::string*)data; + LLSD args; + args["DOWNLOAD_PATH"] = *filepath; + LLNotificationsUtil::add("FinishedRawDownload", args); + delete filepath; +} + + +void process_initiate_download(LLMessageSystem* msg, void**) +{ + LLUUID agent_id; + msg->getUUID("AgentData", "AgentID", agent_id); + if (agent_id != gAgent.getID()) + { + LL_WARNS("Messaging") << "Initiate download for wrong agent" << LL_ENDL; + return; + } + + std::string sim_filename; + std::string viewer_filename; + msg->getString("FileData", "SimFilename", sim_filename); + msg->getString("FileData", "ViewerFilename", viewer_filename); + + if (!gXferManager->validateFileForRequest(viewer_filename)) + { + LL_WARNS() << "SECURITY: Unauthorized download to local file " << viewer_filename << LL_ENDL; + return; + } + gXferManager->requestFile(viewer_filename, + sim_filename, + LL_PATH_NONE, + msg->getSender(), + false, // don't delete remote + callback_download_complete, + (void**)new std::string(viewer_filename)); +} + + +void process_script_teleport_request(LLMessageSystem* msg, void**) +{ + if (!gSavedSettings.getBOOL("ScriptsCanShowUI")) return; + + std::string object_name; + std::string sim_name; + LLVector3 pos; + LLVector3 look_at; + + msg->getString("Data", "ObjectName", object_name); + msg->getString("Data", "SimName", sim_name); + msg->getVector3("Data", "SimPosition", pos); + msg->getVector3("Data", "LookAt", look_at); + + LLFloaterWorldMap* instance = LLFloaterWorldMap::getInstance(); + if(instance) + { + LL_INFOS() << "Object named " << object_name + << " is offering TP to region " + << sim_name << " position " << pos + << LL_ENDL; + + instance->trackURL(sim_name, (S32)pos.mV[VX], (S32)pos.mV[VY], (S32)pos.mV[VZ]); + LLFloaterReg::showInstance("world_map", "center"); + } + + // remove above two lines and replace with below line + // to re-enable parcel browser for llMapDestination() + // LLURLDispatcher::dispatch(LLSLURL::buildSLURL(sim_name, (S32)pos.mV[VX], (S32)pos.mV[VY], (S32)pos.mV[VZ]), false); + +} + +void process_covenant_reply(LLMessageSystem* msg, void**) +{ + LLUUID covenant_id, estate_owner_id; + std::string estate_name; + U32 covenant_timestamp; + msg->getUUID("Data", "CovenantID", covenant_id); + msg->getU32("Data", "CovenantTimestamp", covenant_timestamp); + msg->getString("Data", "EstateName", estate_name); + msg->getUUID("Data", "EstateOwnerID", estate_owner_id); + + LLPanelEstateCovenant::updateEstateName(estate_name); + LLPanelLandCovenant::updateEstateName(estate_name); + LLPanelEstateInfo::updateEstateName(estate_name); + LLFloaterBuyLand::updateEstateName(estate_name); + + std::string owner_name = + LLSLURL("agent", estate_owner_id, "inspect").getSLURLString(); + LLPanelEstateCovenant::updateEstateOwnerName(owner_name); + LLPanelLandCovenant::updateEstateOwnerName(owner_name); + LLPanelEstateInfo::updateEstateOwnerName(owner_name); + LLFloaterBuyLand::updateEstateOwnerName(owner_name); + + LLPanelPlaceProfile* panel = LLFloaterSidePanelContainer::getPanel("places", "panel_place_profile"); + if (panel) + { + panel->updateEstateName(estate_name); + panel->updateEstateOwnerName(owner_name); + } + + // standard message, not from system + std::string last_modified; + if (covenant_timestamp == 0) + { + last_modified = LLTrans::getString("covenant_last_modified")+LLTrans::getString("never_text"); + } + else + { + last_modified = LLTrans::getString("covenant_last_modified")+"[" + +LLTrans::getString("LTimeWeek")+"] [" + +LLTrans::getString("LTimeMonth")+"] [" + +LLTrans::getString("LTimeDay")+"] [" + +LLTrans::getString("LTimeHour")+"]:[" + +LLTrans::getString("LTimeMin")+"]:[" + +LLTrans::getString("LTimeSec")+"] [" + +LLTrans::getString("LTimeYear")+"]"; + LLSD substitution; + substitution["datetime"] = (S32) covenant_timestamp; + LLStringUtil::format (last_modified, substitution); + } + + LLPanelEstateCovenant::updateLastModified(last_modified); + LLPanelLandCovenant::updateLastModified(last_modified); + LLFloaterBuyLand::updateLastModified(last_modified); + + // load the actual covenant asset data + const bool high_priority = true; + if (covenant_id.notNull()) + { + gAssetStorage->getEstateAsset(gAgent.getRegionHost(), + gAgent.getID(), + gAgent.getSessionID(), + covenant_id, + LLAssetType::AT_NOTECARD, + ET_Covenant, + onCovenantLoadComplete, + NULL, + high_priority); + } + else + { + std::string covenant_text; + if (estate_owner_id.isNull()) + { + // mainland + covenant_text = LLTrans::getString("RegionNoCovenant"); + } + else + { + covenant_text = LLTrans::getString("RegionNoCovenantOtherOwner"); + } + LLPanelEstateCovenant::updateCovenantText(covenant_text, covenant_id); + LLPanelLandCovenant::updateCovenantText(covenant_text); + LLFloaterBuyLand::updateCovenantText(covenant_text, covenant_id); + if (panel) + { + panel->updateCovenantText(covenant_text); + } + } +} + +void onCovenantLoadComplete(const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status) +{ + LL_DEBUGS("Messaging") << "onCovenantLoadComplete()" << LL_ENDL; + std::string covenant_text; + if(0 == status) + { + LLFileSystem file(asset_uuid, type, LLFileSystem::READ); + + S32 file_length = file.getSize(); + + std::vector buffer(file_length+1); + file.read((U8*)&buffer[0], file_length); + // put a EOS at the end + buffer[file_length] = '\0'; + + if( (file_length > 19) && !strncmp( &buffer[0], "Linden text version", 19 ) ) + { + LLViewerTextEditor::Params params; + params.name("temp"); + params.max_text_length(file_length+1); + LLViewerTextEditor * editor = LLUICtrlFactory::create (params); + if( !editor->importBuffer( &buffer[0], file_length+1 ) ) + { + LL_WARNS("Messaging") << "Problem importing estate covenant." << LL_ENDL; + covenant_text = "Problem importing estate covenant."; + } + else + { + // Version 0 (just text, doesn't include version number) + covenant_text = editor->getText(); + } + delete editor; + } + else + { + LL_WARNS("Messaging") << "Problem importing estate covenant: Covenant file format error." << LL_ENDL; + covenant_text = "Problem importing estate covenant: Covenant file format error."; + } + } + else + { + if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status || + LL_ERR_FILE_EMPTY == status) + { + covenant_text = "Estate covenant notecard is missing from database."; + } + else if (LL_ERR_INSUFFICIENT_PERMISSIONS == status) + { + covenant_text = "Insufficient permissions to view estate covenant."; + } + else + { + covenant_text = "Unable to load estate covenant at this time."; + } + + LL_WARNS("Messaging") << "Problem loading notecard: " << status << LL_ENDL; + } + LLPanelEstateCovenant::updateCovenantText(covenant_text, asset_uuid); + LLPanelLandCovenant::updateCovenantText(covenant_text); + LLFloaterBuyLand::updateCovenantText(covenant_text, asset_uuid); + + LLPanelPlaceProfile* panel = LLFloaterSidePanelContainer::getPanel("places", "panel_place_profile"); + if (panel) + { + panel->updateCovenantText(covenant_text); + } +} + + +void process_feature_disabled_message(LLMessageSystem* msg, void**) +{ + // Handle Blacklisted feature simulator response... + LLUUID agentID; + LLUUID transactionID; + std::string messageText; + msg->getStringFast(_PREHASH_FailureInfo,_PREHASH_ErrorMessage, messageText,0); + msg->getUUIDFast(_PREHASH_FailureInfo,_PREHASH_AgentID,agentID); + msg->getUUIDFast(_PREHASH_FailureInfo,_PREHASH_TransactionID,transactionID); + + LL_WARNS("Messaging") << "Blacklisted Feature Response:" << messageText << LL_ENDL; +} + +// ------------------------------------------------------------ +// Message system exception callbacks +// ------------------------------------------------------------ + +void invalid_message_callback(LLMessageSystem* msg, + void*, + EMessageException exception) +{ + LLAppViewer::instance()->badNetworkHandler(); +} + +// Please do not add more message handlers here. This file is huge. +// Put them in a file related to the functionality you are implementing. + +void LLOfferInfo::forceResponse(InventoryOfferResponse response) +{ + LLNotification::Params params("UserGiveItem"); + params.functor.function(boost::bind(&LLOfferInfo::inventory_offer_callback, this, _1, _2)); + LLNotifications::instance().forceResponse(params, response); +} + diff --git a/indra/newview/llviewermessage.h b/indra/newview/llviewermessage.h index ae2a33d640..52f383faa9 100644 --- a/indra/newview/llviewermessage.h +++ b/indra/newview/llviewermessage.h @@ -1,275 +1,275 @@ -/** - * @file llviewermessage.h - * @brief Message system callbacks for viewer. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVIEWERMESSAGE_H -#define LL_LLVIEWERMESSAGE_H - -#include "llassettype.h" -#include "llinstantmessage.h" -#include "llpointer.h" -#include "lltransactiontypes.h" -#include "lluuid.h" -#include "message.h" -#include "llnotifications.h" -#include "llextendedstatus.h" - -#include -#include - -// -// Forward declarations -// -class LLColor4; -class LLInventoryObject; -class LLInventoryItem; -class LLMeanCollisionData; -class LLMessageSystem; -class LLViewerObject; -class LLViewerRegion; - -// -// Prototypes -// - -enum InventoryOfferResponse -{ - IOR_ACCEPT, - IOR_DECLINE, - IOR_MUTE, - IOR_SHOW -}; - -bool can_afford_transaction(S32 cost); -void give_money(const LLUUID& uuid, LLViewerRegion* region, S32 amount, bool is_group = false, - S32 trx_type = TRANS_GIFT, const std::string& desc = LLStringUtil::null); -void send_join_group_response(LLUUID group_id, - LLUUID transaction_id, - bool accept_invite, - S32 fee, - bool use_offline_cap); - -void process_logout_reply(LLMessageSystem* msg, void**); -void process_layer_data(LLMessageSystem *mesgsys, void **user_data); -void process_derez_ack(LLMessageSystem*, void**); -void process_places_reply(LLMessageSystem* msg, void** data); -void send_sound_trigger(const LLUUID& sound_id, F32 gain); -void process_improved_im(LLMessageSystem *msg, void **user_data); -void process_script_question(LLMessageSystem *msg, void **user_data); -void process_chat_from_simulator(LLMessageSystem *mesgsys, void **user_data); - -//void process_agent_to_new_region(LLMessageSystem *mesgsys, void **user_data); -void send_agent_update(bool force_send, bool send_reliable = false); -void process_object_update(LLMessageSystem *mesgsys, void **user_data); -void process_compressed_object_update(LLMessageSystem *mesgsys, void **user_data); -void process_cached_object_update(LLMessageSystem *mesgsys, void **user_data); -void process_terse_object_update_improved(LLMessageSystem *mesgsys, void **user_data); - -void send_simulator_throttle_settings(const LLHost &host); -void process_kill_object( LLMessageSystem *mesgsys, void **user_data); -void process_time_synch( LLMessageSystem *mesgsys, void **user_data); -void process_sound_trigger(LLMessageSystem *mesgsys, void **user_data); -void process_preload_sound( LLMessageSystem *mesgsys, void **user_data); -void process_attached_sound( LLMessageSystem *mesgsys, void **user_data); -void process_attached_sound_gain_change( LLMessageSystem *mesgsys, void **user_data); -void process_energy_statistics(LLMessageSystem *mesgsys, void **user_data); -void process_health_message(LLMessageSystem *mesgsys, void **user_data); -void process_sim_stats(LLMessageSystem *mesgsys, void **user_data); -void process_shooter_agent_hit(LLMessageSystem* msg, void** user_data); -void process_avatar_animation(LLMessageSystem *mesgsys, void **user_data); -void process_object_animation(LLMessageSystem *mesgsys, void **user_data); -void process_avatar_appearance(LLMessageSystem *mesgsys, void **user_data); -void process_camera_constraint(LLMessageSystem *mesgsys, void **user_data); -void process_avatar_sit_response(LLMessageSystem *mesgsys, void **user_data); -void process_set_follow_cam_properties(LLMessageSystem *mesgsys, void **user_data); -void process_clear_follow_cam_properties(LLMessageSystem *mesgsys, void **user_data); -void process_name_value(LLMessageSystem *mesgsys, void **user_data); -void process_remove_name_value(LLMessageSystem *mesgsys, void **user_data); -void process_kick_user(LLMessageSystem *msg, void** /*user_data*/); -//void process_avatar_init_complete(LLMessageSystem *msg, void** /*user_data*/); - -void process_economy_data(LLMessageSystem *msg, void** /*user_data*/); -void process_money_balance_reply(LLMessageSystem* msg_system, void**); -void process_adjust_balance(LLMessageSystem* msg_system, void**); - -bool attempt_standard_notification(LLMessageSystem* msg); -void process_alert_message(LLMessageSystem* msg, void**); -void process_agent_alert_message(LLMessageSystem* msgsystem, void** user_data); -void process_alert_core(const std::string& message, bool modal); - -// "Mean" or player-vs-player abuse -typedef std::list mean_collision_list_t; -extern mean_collision_list_t gMeanCollisionList; - -void process_mean_collision_alert_message(LLMessageSystem* msg, void**); - -void process_frozen_message(LLMessageSystem* msg, void**); - -void process_derez_container(LLMessageSystem *msg, void**); -void container_inventory_arrived(LLViewerObject* object, - std::list >* inventory, //LLInventoryObject::object_list_t - S32 serial_num, - void* data); - -// agent movement -void send_complete_agent_movement(const LLHost& sim_host); -void process_agent_movement_complete(LLMessageSystem* msg, void**); -void process_crossed_region(LLMessageSystem* msg, void**); -void process_teleport_start(LLMessageSystem* msg, void**); -void process_teleport_progress(LLMessageSystem* msg, void**); -void process_teleport_failed(LLMessageSystem *msg,void**); -void process_teleport_finish(LLMessageSystem *msg, void**); - -//void process_user_sim_location_reply(LLMessageSystem *msg,void**); -void process_teleport_local(LLMessageSystem *msg,void**); -void process_user_sim_location_reply(LLMessageSystem *msg,void**); - -void send_simple_im(const LLUUID& to_id, - const std::string& message, - EInstantMessage dialog = IM_NOTHING_SPECIAL, - const LLUUID& id = LLUUID::null); - -void send_group_notice(const LLUUID& group_id, - const std::string& subject, - const std::string& message, - const LLInventoryItem* item); - -void send_do_not_disturb_message (LLMessageSystem* msg, const LLUUID& from_id, const LLUUID& session_id = LLUUID::null); - -void handle_lure(const LLUUID& invitee); -void handle_lure(const uuid_vec_t& ids); - -// always from gAgent and -// routes through the gAgent's current simulator -void send_improved_im(const LLUUID& to_id, - const std::string& name, - const std::string& message, - U8 offline = IM_ONLINE, - EInstantMessage dialog = IM_NOTHING_SPECIAL, - const LLUUID& id = LLUUID::null, - U32 timestamp = NO_TIMESTAMP, - const U8* binary_bucket = (U8*)EMPTY_BINARY_BUCKET, - S32 binary_bucket_size = EMPTY_BINARY_BUCKET_SIZE); - -void process_user_info_reply(LLMessageSystem* msg, void**); - -// method to format the time. -std::string formatted_time(const time_t& the_time); - -void send_places_query(const LLUUID& query_id, - const LLUUID& trans_id, - const std::string& query_text, - U32 query_flags, - S32 category, - const std::string& sim_name); -void process_script_dialog(LLMessageSystem* msg, void**); -void process_load_url(LLMessageSystem* msg, void**); -void process_script_teleport_request(LLMessageSystem* msg, void**); -void process_covenant_reply(LLMessageSystem* msg, void**); -void onCovenantLoadComplete(const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status); - -// calling cards -void process_offer_callingcard(LLMessageSystem* msg, void**); -void process_accept_callingcard(LLMessageSystem* msg, void**); -void process_decline_callingcard(LLMessageSystem* msg, void**); - -// Message system exception prototypes -void invalid_message_callback(LLMessageSystem*, void*, EMessageException); - -void process_initiate_download(LLMessageSystem* msg, void**); -void start_new_inventory_observer(); -void open_inventory_offer(const uuid_vec_t& items, const std::string& from_name); - -// Returns true if item is not in certain "quiet" folder which don't need UI -// notification (e.g. trash, cof, lost-and-found) and agent is not AFK, false otherwise. -// Returns false if item is not found. -bool highlight_offered_object(const LLUUID& obj_id); - -void set_dad_inventory_item(LLInventoryItem* inv_item, const LLUUID& into_folder_uuid); -void set_dad_inbox_object(const LLUUID& object_id); - -class LLViewerMessage : public LLSingleton -{ - LLSINGLETON_EMPTY_CTOR(LLViewerMessage); -public: - typedef boost::function teleport_started_callback_t; - typedef boost::signals2::signal teleport_started_signal_t; - boost::signals2::connection setTeleportStartedCallback(teleport_started_callback_t cb); - - teleport_started_signal_t mTeleportStartedSignal; -}; - -class LLOfferInfo : public LLNotificationResponderInterface -{ -public: - LLOfferInfo(); - LLOfferInfo(const LLSD& sd); - - LLOfferInfo(const LLOfferInfo& info); - - void forceResponse(InventoryOfferResponse response); - - static std::string mResponderType; - EInstantMessage mIM; - LLUUID mFromID; - bool mFromGroup; - bool mFromObject; - LLUUID mTransactionID; - LLUUID mFolderID; - LLUUID mObjectID; - LLAssetType::EType mType; - std::string mFromName; - std::string mDesc; - LLHost mHost; - bool mPersist; - - // LLNotificationResponderInterface implementation - /*virtual*/ LLSD asLLSD(); - /*virtual*/ void fromLLSD(const LLSD& params); - /*virtual*/ void handleRespond(const LLSD& notification, const LLSD& response); - - void send_auto_receive_response() { sendReceiveResponse(true, mFolderID); } - - // TODO - replace all references with handleRespond() - bool inventory_offer_callback(const LLSD& notification, const LLSD& response); - bool inventory_task_offer_callback(const LLSD& notification, const LLSD& response); - -private: - - void initRespondFunctionMap(); - std::string getSanitizedDescription(); - void sendReceiveResponse(bool accept, const LLUUID &destination_folder_id); - - typedef boost::function respond_function_t; - typedef std::map respond_function_map_t; - - respond_function_map_t mRespondFunctions; -}; - -void process_feature_disabled_message(LLMessageSystem* msg, void**); - -#endif +/** + * @file llviewermessage.h + * @brief Message system callbacks for viewer. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVIEWERMESSAGE_H +#define LL_LLVIEWERMESSAGE_H + +#include "llassettype.h" +#include "llinstantmessage.h" +#include "llpointer.h" +#include "lltransactiontypes.h" +#include "lluuid.h" +#include "message.h" +#include "llnotifications.h" +#include "llextendedstatus.h" + +#include +#include + +// +// Forward declarations +// +class LLColor4; +class LLInventoryObject; +class LLInventoryItem; +class LLMeanCollisionData; +class LLMessageSystem; +class LLViewerObject; +class LLViewerRegion; + +// +// Prototypes +// + +enum InventoryOfferResponse +{ + IOR_ACCEPT, + IOR_DECLINE, + IOR_MUTE, + IOR_SHOW +}; + +bool can_afford_transaction(S32 cost); +void give_money(const LLUUID& uuid, LLViewerRegion* region, S32 amount, bool is_group = false, + S32 trx_type = TRANS_GIFT, const std::string& desc = LLStringUtil::null); +void send_join_group_response(LLUUID group_id, + LLUUID transaction_id, + bool accept_invite, + S32 fee, + bool use_offline_cap); + +void process_logout_reply(LLMessageSystem* msg, void**); +void process_layer_data(LLMessageSystem *mesgsys, void **user_data); +void process_derez_ack(LLMessageSystem*, void**); +void process_places_reply(LLMessageSystem* msg, void** data); +void send_sound_trigger(const LLUUID& sound_id, F32 gain); +void process_improved_im(LLMessageSystem *msg, void **user_data); +void process_script_question(LLMessageSystem *msg, void **user_data); +void process_chat_from_simulator(LLMessageSystem *mesgsys, void **user_data); + +//void process_agent_to_new_region(LLMessageSystem *mesgsys, void **user_data); +void send_agent_update(bool force_send, bool send_reliable = false); +void process_object_update(LLMessageSystem *mesgsys, void **user_data); +void process_compressed_object_update(LLMessageSystem *mesgsys, void **user_data); +void process_cached_object_update(LLMessageSystem *mesgsys, void **user_data); +void process_terse_object_update_improved(LLMessageSystem *mesgsys, void **user_data); + +void send_simulator_throttle_settings(const LLHost &host); +void process_kill_object( LLMessageSystem *mesgsys, void **user_data); +void process_time_synch( LLMessageSystem *mesgsys, void **user_data); +void process_sound_trigger(LLMessageSystem *mesgsys, void **user_data); +void process_preload_sound( LLMessageSystem *mesgsys, void **user_data); +void process_attached_sound( LLMessageSystem *mesgsys, void **user_data); +void process_attached_sound_gain_change( LLMessageSystem *mesgsys, void **user_data); +void process_energy_statistics(LLMessageSystem *mesgsys, void **user_data); +void process_health_message(LLMessageSystem *mesgsys, void **user_data); +void process_sim_stats(LLMessageSystem *mesgsys, void **user_data); +void process_shooter_agent_hit(LLMessageSystem* msg, void** user_data); +void process_avatar_animation(LLMessageSystem *mesgsys, void **user_data); +void process_object_animation(LLMessageSystem *mesgsys, void **user_data); +void process_avatar_appearance(LLMessageSystem *mesgsys, void **user_data); +void process_camera_constraint(LLMessageSystem *mesgsys, void **user_data); +void process_avatar_sit_response(LLMessageSystem *mesgsys, void **user_data); +void process_set_follow_cam_properties(LLMessageSystem *mesgsys, void **user_data); +void process_clear_follow_cam_properties(LLMessageSystem *mesgsys, void **user_data); +void process_name_value(LLMessageSystem *mesgsys, void **user_data); +void process_remove_name_value(LLMessageSystem *mesgsys, void **user_data); +void process_kick_user(LLMessageSystem *msg, void** /*user_data*/); +//void process_avatar_init_complete(LLMessageSystem *msg, void** /*user_data*/); + +void process_economy_data(LLMessageSystem *msg, void** /*user_data*/); +void process_money_balance_reply(LLMessageSystem* msg_system, void**); +void process_adjust_balance(LLMessageSystem* msg_system, void**); + +bool attempt_standard_notification(LLMessageSystem* msg); +void process_alert_message(LLMessageSystem* msg, void**); +void process_agent_alert_message(LLMessageSystem* msgsystem, void** user_data); +void process_alert_core(const std::string& message, bool modal); + +// "Mean" or player-vs-player abuse +typedef std::list mean_collision_list_t; +extern mean_collision_list_t gMeanCollisionList; + +void process_mean_collision_alert_message(LLMessageSystem* msg, void**); + +void process_frozen_message(LLMessageSystem* msg, void**); + +void process_derez_container(LLMessageSystem *msg, void**); +void container_inventory_arrived(LLViewerObject* object, + std::list >* inventory, //LLInventoryObject::object_list_t + S32 serial_num, + void* data); + +// agent movement +void send_complete_agent_movement(const LLHost& sim_host); +void process_agent_movement_complete(LLMessageSystem* msg, void**); +void process_crossed_region(LLMessageSystem* msg, void**); +void process_teleport_start(LLMessageSystem* msg, void**); +void process_teleport_progress(LLMessageSystem* msg, void**); +void process_teleport_failed(LLMessageSystem *msg,void**); +void process_teleport_finish(LLMessageSystem *msg, void**); + +//void process_user_sim_location_reply(LLMessageSystem *msg,void**); +void process_teleport_local(LLMessageSystem *msg,void**); +void process_user_sim_location_reply(LLMessageSystem *msg,void**); + +void send_simple_im(const LLUUID& to_id, + const std::string& message, + EInstantMessage dialog = IM_NOTHING_SPECIAL, + const LLUUID& id = LLUUID::null); + +void send_group_notice(const LLUUID& group_id, + const std::string& subject, + const std::string& message, + const LLInventoryItem* item); + +void send_do_not_disturb_message (LLMessageSystem* msg, const LLUUID& from_id, const LLUUID& session_id = LLUUID::null); + +void handle_lure(const LLUUID& invitee); +void handle_lure(const uuid_vec_t& ids); + +// always from gAgent and +// routes through the gAgent's current simulator +void send_improved_im(const LLUUID& to_id, + const std::string& name, + const std::string& message, + U8 offline = IM_ONLINE, + EInstantMessage dialog = IM_NOTHING_SPECIAL, + const LLUUID& id = LLUUID::null, + U32 timestamp = NO_TIMESTAMP, + const U8* binary_bucket = (U8*)EMPTY_BINARY_BUCKET, + S32 binary_bucket_size = EMPTY_BINARY_BUCKET_SIZE); + +void process_user_info_reply(LLMessageSystem* msg, void**); + +// method to format the time. +std::string formatted_time(const time_t& the_time); + +void send_places_query(const LLUUID& query_id, + const LLUUID& trans_id, + const std::string& query_text, + U32 query_flags, + S32 category, + const std::string& sim_name); +void process_script_dialog(LLMessageSystem* msg, void**); +void process_load_url(LLMessageSystem* msg, void**); +void process_script_teleport_request(LLMessageSystem* msg, void**); +void process_covenant_reply(LLMessageSystem* msg, void**); +void onCovenantLoadComplete(const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status); + +// calling cards +void process_offer_callingcard(LLMessageSystem* msg, void**); +void process_accept_callingcard(LLMessageSystem* msg, void**); +void process_decline_callingcard(LLMessageSystem* msg, void**); + +// Message system exception prototypes +void invalid_message_callback(LLMessageSystem*, void*, EMessageException); + +void process_initiate_download(LLMessageSystem* msg, void**); +void start_new_inventory_observer(); +void open_inventory_offer(const uuid_vec_t& items, const std::string& from_name); + +// Returns true if item is not in certain "quiet" folder which don't need UI +// notification (e.g. trash, cof, lost-and-found) and agent is not AFK, false otherwise. +// Returns false if item is not found. +bool highlight_offered_object(const LLUUID& obj_id); + +void set_dad_inventory_item(LLInventoryItem* inv_item, const LLUUID& into_folder_uuid); +void set_dad_inbox_object(const LLUUID& object_id); + +class LLViewerMessage : public LLSingleton +{ + LLSINGLETON_EMPTY_CTOR(LLViewerMessage); +public: + typedef boost::function teleport_started_callback_t; + typedef boost::signals2::signal teleport_started_signal_t; + boost::signals2::connection setTeleportStartedCallback(teleport_started_callback_t cb); + + teleport_started_signal_t mTeleportStartedSignal; +}; + +class LLOfferInfo : public LLNotificationResponderInterface +{ +public: + LLOfferInfo(); + LLOfferInfo(const LLSD& sd); + + LLOfferInfo(const LLOfferInfo& info); + + void forceResponse(InventoryOfferResponse response); + + static std::string mResponderType; + EInstantMessage mIM; + LLUUID mFromID; + bool mFromGroup; + bool mFromObject; + LLUUID mTransactionID; + LLUUID mFolderID; + LLUUID mObjectID; + LLAssetType::EType mType; + std::string mFromName; + std::string mDesc; + LLHost mHost; + bool mPersist; + + // LLNotificationResponderInterface implementation + /*virtual*/ LLSD asLLSD(); + /*virtual*/ void fromLLSD(const LLSD& params); + /*virtual*/ void handleRespond(const LLSD& notification, const LLSD& response); + + void send_auto_receive_response() { sendReceiveResponse(true, mFolderID); } + + // TODO - replace all references with handleRespond() + bool inventory_offer_callback(const LLSD& notification, const LLSD& response); + bool inventory_task_offer_callback(const LLSD& notification, const LLSD& response); + +private: + + void initRespondFunctionMap(); + std::string getSanitizedDescription(); + void sendReceiveResponse(bool accept, const LLUUID &destination_folder_id); + + typedef boost::function respond_function_t; + typedef std::map respond_function_map_t; + + respond_function_map_t mRespondFunctions; +}; + +void process_feature_disabled_message(LLMessageSystem* msg, void**); + +#endif diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index ec2078a365..c791a30d1a 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -1,7381 +1,7381 @@ -/** - * @file llviewerobject.cpp - * @brief Base class for viewer objects - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llviewerobject.h" - -#include "llaudioengine.h" -#include "indra_constants.h" -#include "llmath.h" -#include "llflexibleobject.h" -#include "llviewercontrol.h" -#include "lldatapacker.h" -#include "llfasttimer.h" -#include "llfloaterreg.h" -#include "llfontgl.h" -#include "llframetimer.h" -#include "llhudicon.h" -#include "llinventory.h" -#include "llinventorydefines.h" -#include "llmaterialtable.h" -#include "llmutelist.h" -#include "llnamevalue.h" -#include "llprimitive.h" -#include "llquantize.h" -#include "llregionhandle.h" -#include "llsdserialize.h" -#include "lltree_common.h" -#include "llxfermanager.h" -#include "message.h" -#include "object_flags.h" - -#include "llaudiosourcevo.h" -#include "llagent.h" -#include "llagentcamera.h" -#include "llagentwearables.h" -#include "llbbox.h" -#include "llbox.h" -#include "llcylinder.h" -#include "llcontrolavatar.h" -#include "lldrawable.h" -#include "llface.h" -#include "llfloatertools.h" -#include "llfollowcam.h" -#include "llhudtext.h" -#include "llselectmgr.h" -#include "llrendersphere.h" -#include "lltooldraganddrop.h" -#include "lluiavatar.h" -#include "llviewercamera.h" -#include "llviewertexturelist.h" -#include "llviewerinventory.h" -#include "llviewerobjectlist.h" -#include "llviewerparceloverlay.h" -#include "llviewerpartsource.h" -#include "llviewerregion.h" -#include "llviewerstats.h" -#include "llviewertextureanim.h" -#include "llviewerwindow.h" // For getSpinAxis -#include "llvoavatar.h" -#include "llvoavatarself.h" -#include "llvograss.h" -#include "llvosky.h" -#include "llvolume.h" -#include "llvolumemessage.h" -#include "llvopartgroup.h" -#include "llvosurfacepatch.h" -#include "llvotree.h" -#include "llvovolume.h" -#include "llvowater.h" -#include "llworld.h" -#include "llui.h" -#include "pipeline.h" -#include "llviewernetwork.h" -#include "llvowlsky.h" -#include "llmanip.h" -#include "lltrans.h" -#include "llsdutil.h" -#include "llmediaentry.h" -#include "llfloaterperms.h" -#include "llvocache.h" -#include "llcleanup.h" -#include "llcallstack.h" -#include "llmeshrepository.h" -#include "llgltfmateriallist.h" -#include "llgl.h" - -//#define DEBUG_UPDATE_TYPE - -bool LLViewerObject::sVelocityInterpolate = true; -bool LLViewerObject::sPingInterpolate = true; - -U32 LLViewerObject::sNumZombieObjects = 0; -S32 LLViewerObject::sNumObjects = 0; -bool LLViewerObject::sMapDebug = true; -LLColor4 LLViewerObject::sEditSelectColor( 1.0f, 1.f, 0.f, 0.3f); // Edit OK -LLColor4 LLViewerObject::sNoEditSelectColor( 1.0f, 0.f, 0.f, 0.3f); // Can't edit -S32 LLViewerObject::sAxisArrowLength(50); - - -bool LLViewerObject::sPulseEnabled(false); -bool LLViewerObject::sUseSharedDrawables(false); // true - -// sMaxUpdateInterpolationTime must be greater than sPhaseOutUpdateInterpolationTime -F64Seconds LLViewerObject::sMaxUpdateInterpolationTime(3.0); // For motion interpolation: after X seconds with no updates, don't predict object motion -F64Seconds LLViewerObject::sPhaseOutUpdateInterpolationTime(2.0); // For motion interpolation: after Y seconds with no updates, taper off motion prediction -F64Seconds LLViewerObject::sMaxRegionCrossingInterpolationTime(1.0);// For motion interpolation: don't interpolate over this time on region crossing - -std::map LLViewerObject::sObjectDataMap; - -// The maximum size of an object extra parameters binary (packed) block -#define MAX_OBJECT_PARAMS_SIZE 1024 - -// At 45 Hz collisions seem stable and objects seem -// to settle down at a reasonable rate. -// JC 3/18/2003 - -const F32 PHYSICS_TIMESTEP = 1.f / 45.f; -const U32 MAX_INV_FILE_READ_FAILS = 25; -const S32 MAX_OBJECT_BINARY_DATA_SIZE = 60 + 16; - -const F64 INVENTORY_UPDATE_WAIT_TIME_DESYNC = 5; // seconds -const F64 INVENTORY_UPDATE_WAIT_TIME_OUTDATED = 1; - -// static -LLViewerObject *LLViewerObject::createObject(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp, S32 flags) -{ - LL_PROFILE_ZONE_SCOPED; - LL_DEBUGS("ObjectUpdate") << "creating " << id << LL_ENDL; - dumpStack("ObjectUpdateStack"); - - LLViewerObject *res = NULL; - - if (gNonInteractive - && pcode != LL_PCODE_LEGACY_AVATAR - && pcode != LL_VO_SURFACE_PATCH - && pcode != LL_VO_WATER - && pcode != LL_VO_VOID_WATER - && pcode != LL_VO_WL_SKY - && pcode != LL_VO_SKY - && pcode != LL_VO_PART_GROUP - ) - { - return res; - } - switch (pcode) - { - case LL_PCODE_VOLUME: - { - res = new LLVOVolume(id, pcode, regionp); break; - break; - } - case LL_PCODE_LEGACY_AVATAR: - { - if (id == gAgentID) - { - if (!gAgentAvatarp) - { - gAgentAvatarp = new LLVOAvatarSelf(id, pcode, regionp); - gAgentAvatarp->initInstance(); - gAgentWearables.setAvatarObject(gAgentAvatarp); - } - else - { - if (isAgentAvatarValid()) - { - gAgentAvatarp->updateRegion(regionp); - } - } - res = gAgentAvatarp; - } - else if (flags & CO_FLAG_CONTROL_AVATAR) - { - LLControlAvatar *control_avatar = new LLControlAvatar(id, pcode, regionp); - control_avatar->initInstance(); - res = control_avatar; - } - else if (flags & CO_FLAG_UI_AVATAR) - { - LLUIAvatar *ui_avatar = new LLUIAvatar(id, pcode, regionp); - ui_avatar->initInstance(); - res = ui_avatar; - } - else - { - LLVOAvatar *avatar = new LLVOAvatar(id, pcode, regionp); - avatar->initInstance(); - res = avatar; - } - break; - } - case LL_PCODE_LEGACY_GRASS: - res = new LLVOGrass(id, pcode, regionp); break; - case LL_PCODE_LEGACY_PART_SYS: -// LL_WARNS() << "Creating old part sys!" << LL_ENDL; -// res = new LLVOPart(id, pcode, regionp); break; - res = NULL; break; - case LL_PCODE_LEGACY_TREE: - res = new LLVOTree(id, pcode, regionp); break; - case LL_PCODE_TREE_NEW: -// LL_WARNS() << "Creating new tree!" << LL_ENDL; -// res = new LLVOTree(id, pcode, regionp); break; - res = NULL; break; - case LL_VO_SURFACE_PATCH: - res = new LLVOSurfacePatch(id, pcode, regionp); break; - case LL_VO_SKY: - res = new LLVOSky(id, pcode, regionp); break; - case LL_VO_VOID_WATER: - res = new LLVOVoidWater(id, pcode, regionp); break; - case LL_VO_WATER: - res = new LLVOWater(id, pcode, regionp); break; - case LL_VO_PART_GROUP: - res = new LLVOPartGroup(id, pcode, regionp); break; - case LL_VO_HUD_PART_GROUP: - res = new LLVOHUDPartGroup(id, pcode, regionp); break; - case LL_VO_WL_SKY: - res = new LLVOWLSky(id, pcode, regionp); break; - default: - LL_WARNS() << "Unknown object pcode " << (S32)pcode << LL_ENDL; - res = NULL; break; - } - - return res; -} - -LLViewerObject::LLViewerObject(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp, bool is_global) -: LLPrimitive(), - mChildList(), - mID(id), - mLocalID(0), - mTotalCRC(0), - mListIndex(-1), - mTEImages(NULL), - mTENormalMaps(NULL), - mTESpecularMaps(NULL), - mbCanSelect(true), - mFlags(0), - mPhysicsShapeType(0), - mPhysicsGravity(0), - mPhysicsFriction(0), - mPhysicsDensity(0), - mPhysicsRestitution(0), - mDrawable(), - mCreateSelected(false), - mRenderMedia(false), - mBestUpdatePrecision(0), - mText(), - mHudText(""), - mHudTextColor(LLColor4::white), - mControlAvatar(NULL), - mLastInterpUpdateSecs(0.f), - mLastMessageUpdateSecs(0.f), - mLatestRecvPacketID(0), - mRegionCrossExpire(0), - mData(NULL), - mAudioSourcep(NULL), - mAudioGain(1.f), - mSoundCutOffRadius(0.f), - mAppAngle(0.f), - mPixelArea(1024.f), - mInventory(NULL), - mInventorySerialNum(0), - mExpectedInventorySerialNum(0), - mInvRequestState(INVENTORY_REQUEST_STOPPED), - mInvRequestXFerId(0), - mInventoryDirty(false), - mRegionp(regionp), - mDead(false), - mOrphaned(false), - mUserSelected(false), - mOnActiveList(false), - mOnMap(false), - mStatic(false), - mSeatCount(0), - mNumFaces(0), - mRotTime(0.f), - mAngularVelocityRot(), - mPreviousRotation(), - mAttachmentState(0), - mMedia(NULL), - mClickAction(0), - mObjectCost(0), - mLinksetCost(0), - mPhysicsCost(0), - mLinksetPhysicsCost(0.f), - mCostStale(true), - mPhysicsShapeUnknown(true), - mAttachmentItemID(LLUUID::null), - mLastUpdateType(OUT_UNKNOWN), - mLastUpdateCached(false), - mCachedMuteListUpdateTime(0), - mCachedOwnerInMuteList(false), - mRiggedAttachedWarned(false) -{ - if (!is_global) - { - llassert(mRegionp); - } - - LLPrimitive::init_primitive(pcode); - - // CP: added 12/2/2005 - this was being initialised to 0, not the current frame time - mLastInterpUpdateSecs = LLFrameTimer::getElapsedSeconds(); - - mPositionRegion = LLVector3(0.f, 0.f, 0.f); - - if (!is_global && mRegionp) - { - mPositionAgent = mRegionp->getOriginAgent(); - } - resetRot(); - - LLViewerObject::sNumObjects++; -} - -LLViewerObject::~LLViewerObject() -{ - deleteTEImages(); - - // unhook from reflection probe manager - if (mReflectionProbe.notNull()) - { - mReflectionProbe->mViewerObject = nullptr; - mReflectionProbe = nullptr; - } - - if(mInventory) - { - mInventory->clear(); // will deref and delete entries - delete mInventory; - mInventory = NULL; - } - - if (mPartSourcep) - { - mPartSourcep->setDead(); - mPartSourcep = NULL; - } - - if (mText) - { - // something recovered LLHUDText when object was already dead - mText->markDead(); - mText = NULL; - } - - // Delete memory associated with extra parameters. - std::unordered_map::iterator iter; - for (iter = mExtraParameterList.begin(); iter != mExtraParameterList.end(); ++iter) - { - if(iter->second != NULL) - { - delete iter->second->data; - delete iter->second; - } - } - mExtraParameterList.clear(); - - for_each(mNameValuePairs.begin(), mNameValuePairs.end(), DeletePairedPointer()) ; - mNameValuePairs.clear(); - - delete[] mData; - mData = NULL; - - delete mMedia; - mMedia = NULL; - - sNumObjects--; - sNumZombieObjects--; - llassert(mChildList.size() == 0); - llassert(mControlAvatar.isNull()); // Should have been cleaned by now - if (mControlAvatar.notNull()) - { - mControlAvatar->markForDeath(); - mControlAvatar = NULL; - LL_WARNS() << "Dead object owned a live control avatar" << LL_ENDL; - } - - clearInventoryListeners(); -} - -void LLViewerObject::deleteTEImages() -{ - delete[] mTEImages; - mTEImages = NULL; - - if (mTENormalMaps != NULL) - { - delete[] mTENormalMaps; - mTENormalMaps = NULL; - } - - if (mTESpecularMaps != NULL) - { - delete[] mTESpecularMaps; - mTESpecularMaps = NULL; - } -} - -void LLViewerObject::markDead() -{ - if (!mDead) - { - LL_PROFILE_ZONE_SCOPED; - //LL_INFOS() << "Marking self " << mLocalID << " as dead." << LL_ENDL; - - // Root object of this hierarchy unlinks itself. - if (getParent()) - { - ((LLViewerObject *)getParent())->removeChild(this); - } - LLUUID mesh_id; - { - LLVOAvatar *av = getAvatar(); - if (av && LLVOAvatar::getRiggedMeshID(this,mesh_id)) - { - // This case is needed for indirectly attached mesh objects. - av->updateAttachmentOverrides(); - } - } - if (getControlAvatar()) - { - unlinkControlAvatar(); - } - - // Mark itself as dead - mDead = true; - if(mRegionp) - { - mRegionp->removeFromCreatedList(getLocalID()); - } - gObjectList.cleanupReferences(this); - - LLViewerObject *childp; - while (mChildList.size() > 0) - { - childp = mChildList.back(); - if (childp->getPCode() != LL_PCODE_LEGACY_AVATAR) - { - //LL_INFOS() << "Marking child " << childp->getLocalID() << " as dead." << LL_ENDL; - childp->setParent(NULL); // LLViewerObject::markDead 1 - childp->markDead(); - } - else - { - // make sure avatar is no longer parented, - // so we can properly set it's position - childp->setDrawableParent(NULL); - ((LLVOAvatar*)childp)->getOffObject(); - childp->setParent(NULL); // LLViewerObject::markDead 2 - } - mChildList.pop_back(); - } - - if (mDrawable.notNull()) - { - // Drawables are reference counted, mark as dead, then nuke the pointer. - mDrawable->markDead(); - mDrawable = NULL; - } - - if (mText) - { - mText->markDead(); - mText = NULL; - } - - if (mIcon) - { - mIcon->markDead(); - mIcon = NULL; - } - - if (mPartSourcep) - { - mPartSourcep->setDead(); - mPartSourcep = NULL; - } - - if (mAudioSourcep) - { - // Do some cleanup - if (gAudiop) - { - gAudiop->cleanupAudioSource(mAudioSourcep); - } - mAudioSourcep = NULL; - } - - if (flagAnimSource()) - { - if (isAgentAvatarValid()) - { - // stop motions associated with this object - gAgentAvatarp->stopMotionFromSource(mID); - } - } - - if (flagCameraSource()) - { - LLFollowCamMgr::getInstance()->removeFollowCamParams(mID); - } - - if (mReflectionProbe.notNull()) - { - mReflectionProbe->mViewerObject = nullptr; - mReflectionProbe = nullptr; - } - - sNumZombieObjects++; - } -} - -void LLViewerObject::dump() const -{ - LL_INFOS() << "Type: " << pCodeToString(mPrimitiveCode) << LL_ENDL; - LL_INFOS() << "Drawable: " << (LLDrawable *)mDrawable << LL_ENDL; - LL_INFOS() << "Update Age: " << LLFrameTimer::getElapsedSeconds() - mLastMessageUpdateSecs << LL_ENDL; - - LL_INFOS() << "Parent: " << getParent() << LL_ENDL; - LL_INFOS() << "ID: " << mID << LL_ENDL; - LL_INFOS() << "LocalID: " << mLocalID << LL_ENDL; - LL_INFOS() << "PositionRegion: " << getPositionRegion() << LL_ENDL; - LL_INFOS() << "PositionAgent: " << getPositionAgent() << LL_ENDL; - LL_INFOS() << "PositionGlobal: " << getPositionGlobal() << LL_ENDL; - LL_INFOS() << "Velocity: " << getVelocity() << LL_ENDL; - if (mDrawable.notNull() && - mDrawable->getNumFaces() && - mDrawable->getFace(0)) - { - LLFacePool *poolp = mDrawable->getFace(0)->getPool(); - if (poolp) - { - LL_INFOS() << "Pool: " << poolp << LL_ENDL; - LL_INFOS() << "Pool reference count: " << poolp->mReferences.size() << LL_ENDL; - } - } - //LL_INFOS() << "BoxTree Min: " << mDrawable->getBox()->getMin() << LL_ENDL; - //LL_INFOS() << "BoxTree Max: " << mDrawable->getBox()->getMin() << LL_ENDL; - /* - LL_INFOS() << "Velocity: " << getVelocity() << LL_ENDL; - LL_INFOS() << "AnyOwner: " << permAnyOwner() << " YouOwner: " << permYouOwner() << " Edit: " << mPermEdit << LL_ENDL; - LL_INFOS() << "UsePhysics: " << flagUsePhysics() << " CanSelect " << mbCanSelect << " UserSelected " << mUserSelected << LL_ENDL; - LL_INFOS() << "AppAngle: " << mAppAngle << LL_ENDL; - LL_INFOS() << "PixelArea: " << mPixelArea << LL_ENDL; - - char buffer[1000]; - char *key; - for (key = mNameValuePairs.getFirstKey(); key; key = mNameValuePairs.getNextKey() ) - { - mNameValuePairs[key]->printNameValue(buffer); - LL_INFOS() << buffer << LL_ENDL; - } - for (child_list_t::iterator iter = mChildList.begin(); - iter != mChildList.end(); iter++) - { - LLViewerObject* child = *iter; - LL_INFOS() << " child " << child->getID() << LL_ENDL; - } - */ -} - -void LLViewerObject::printNameValuePairs() const -{ - for (name_value_map_t::const_iterator iter = mNameValuePairs.begin(); - iter != mNameValuePairs.end(); iter++) - { - LLNameValue* nv = iter->second; - LL_INFOS() << nv->printNameValue() << LL_ENDL; - } -} - -void LLViewerObject::initVOClasses() -{ - // Initialized shared class stuff first. - LLVOAvatar::initClass(); - LLVOTree::initClass(); - LL_INFOS() << "Viewer Object size: " << sizeof(LLViewerObject) << LL_ENDL; - LLVOGrass::initClass(); - LLVOWater::initClass(); - LLVOVolume::initClass(); - - initObjectDataMap(); -} - -void LLViewerObject::cleanupVOClasses() -{ - SUBSYSTEM_CLEANUP(LLVOGrass); - SUBSYSTEM_CLEANUP(LLVOWater); - SUBSYSTEM_CLEANUP(LLVOTree); - SUBSYSTEM_CLEANUP(LLVOAvatar); - SUBSYSTEM_CLEANUP(LLVOVolume); - - sObjectDataMap.clear(); -} - -//object data map for compressed && !OUT_TERSE_IMPROVED -//static -void LLViewerObject::initObjectDataMap() -{ - U32 count = 0; - - sObjectDataMap["ID"] = count; //full id //LLUUID - count += sizeof(LLUUID); - - sObjectDataMap["LocalID"] = count; //U32 - count += sizeof(U32); - - sObjectDataMap["PCode"] = count; //U8 - count += sizeof(U8); - - sObjectDataMap["State"] = count; //U8 - count += sizeof(U8); - - sObjectDataMap["CRC"] = count; //U32 - count += sizeof(U32); - - sObjectDataMap["Material"] = count; //U8 - count += sizeof(U8); - - sObjectDataMap["ClickAction"] = count; //U8 - count += sizeof(U8); - - sObjectDataMap["Scale"] = count; //LLVector3 - count += sizeof(LLVector3); - - sObjectDataMap["Pos"] = count; //LLVector3 - count += sizeof(LLVector3); - - sObjectDataMap["Rot"] = count; //LLVector3 - count += sizeof(LLVector3); - - sObjectDataMap["SpecialCode"] = count; //U32 - count += sizeof(U32); - - sObjectDataMap["Owner"] = count; //LLUUID - count += sizeof(LLUUID); - - sObjectDataMap["Omega"] = count; //LLVector3, when SpecialCode & 0x80 is set - count += sizeof(LLVector3); - - //ParentID is after Omega if there is Omega, otherwise is after Owner - sObjectDataMap["ParentID"] = count;//U32, when SpecialCode & 0x20 is set - count += sizeof(U32); - - //------- - //The rest items are not included here - //------- -} - -//static -void LLViewerObject::unpackVector3(LLDataPackerBinaryBuffer* dp, LLVector3& value, std::string name) -{ - dp->shift(sObjectDataMap[name]); - dp->unpackVector3(value, name.c_str()); - dp->reset(); -} - -//static -void LLViewerObject::unpackUUID(LLDataPackerBinaryBuffer* dp, LLUUID& value, std::string name) -{ - dp->shift(sObjectDataMap[name]); - dp->unpackUUID(value, name.c_str()); - dp->reset(); -} - -//static -void LLViewerObject::unpackU32(LLDataPackerBinaryBuffer* dp, U32& value, std::string name) -{ - dp->shift(sObjectDataMap[name]); - dp->unpackU32(value, name.c_str()); - dp->reset(); -} - -//static -void LLViewerObject::unpackU8(LLDataPackerBinaryBuffer* dp, U8& value, std::string name) -{ - dp->shift(sObjectDataMap[name]); - dp->unpackU8(value, name.c_str()); - dp->reset(); -} - -//static -U32 LLViewerObject::unpackParentID(LLDataPackerBinaryBuffer* dp, U32& parent_id) -{ - dp->shift(sObjectDataMap["SpecialCode"]); - U32 value; - dp->unpackU32(value, "SpecialCode"); - - parent_id = 0; - if(value & 0x20) - { - S32 offset = sObjectDataMap["ParentID"]; - if(!(value & 0x80)) - { - offset -= sizeof(LLVector3); - } - - dp->shift(offset); - dp->unpackU32(parent_id, "ParentID"); - } - dp->reset(); - - return parent_id; -} - -// Replaces all name value pairs with data from \n delimited list -// Does not update server -void LLViewerObject::setNameValueList(const std::string& name_value_list) -{ - // Clear out the old - for_each(mNameValuePairs.begin(), mNameValuePairs.end(), DeletePairedPointer()) ; - mNameValuePairs.clear(); - - // Bring in the new - std::string::size_type length = name_value_list.length(); - std::string::size_type start = 0; - while (start < length) - { - std::string::size_type end = name_value_list.find_first_of("\n", start); - if (end == std::string::npos) end = length; - if (end > start) - { - std::string tok = name_value_list.substr(start, end - start); - addNVPair(tok); - } - start = end+1; - } -} - -bool LLViewerObject::isAnySelected() const -{ - bool any_selected = isSelected(); - for (child_list_t::const_iterator iter = mChildList.begin(); - iter != mChildList.end(); iter++) - { - const LLViewerObject* child = *iter; - any_selected = any_selected || child->isSelected(); - } - return any_selected; -} - -void LLViewerObject::setSelected(bool sel) -{ - mUserSelected = sel; - resetRot(); - - if (!sel) - { - setAllTESelected(false); - } -} - -// This method returns true if the object is over land owned by the -// agent. -bool LLViewerObject::isReturnable() -{ - if (isAttachment()) - { - return false; - } - - std::vector boxes; - boxes.push_back(LLBBox(getPositionRegion(), getRotationRegion(), getScale() * -0.5f, getScale() * 0.5f).getAxisAligned()); - for (child_list_t::iterator iter = mChildList.begin(); - iter != mChildList.end(); iter++) - { - LLViewerObject* child = *iter; - boxes.push_back( LLBBox(child->getPositionRegion(), child->getRotationRegion(), child->getScale() * -0.5f, child->getScale() * 0.5f).getAxisAligned()); - } - - bool result = (mRegionp && mRegionp->objectIsReturnable(getPositionRegion(), boxes)) ? 1 : 0; - - if ( !result ) - { - //Get list of neighboring regions relative to this vo's region - std::vector uniqueRegions; - mRegionp->getNeighboringRegions( uniqueRegions ); - - //Build aabb's - for root and all children - std::vector returnables; - typedef std::vector::iterator RegionIt; - RegionIt regionStart = uniqueRegions.begin(); - RegionIt regionEnd = uniqueRegions.end(); - - for (; regionStart != regionEnd; ++regionStart ) - { - LLViewerRegion* pTargetRegion = *regionStart; - //Add the root vo as there may be no children and we still want - //to test for any edge overlap - buildReturnablesForChildrenVO( returnables, this, pTargetRegion ); - //Add it's children - for (child_list_t::iterator iter = mChildList.begin(); iter != mChildList.end(); iter++) - { - LLViewerObject* pChild = *iter; - buildReturnablesForChildrenVO( returnables, pChild, pTargetRegion ); - } - } - - //TBD#Eventually create a region -> box list map - typedef std::vector::iterator ReturnablesIt; - ReturnablesIt retCurrentIt = returnables.begin(); - ReturnablesIt retEndIt = returnables.end(); - - for ( ; retCurrentIt !=retEndIt; ++retCurrentIt ) - { - boxes.clear(); - LLViewerRegion* pRegion = (*retCurrentIt).pRegion; - boxes.push_back( (*retCurrentIt).box ); - bool retResult = pRegion - && pRegion->childrenObjectReturnable( boxes ) - && pRegion->canManageEstate(); - if ( retResult ) - { - result = true; - break; - } - } - } - return result; -} - -void LLViewerObject::buildReturnablesForChildrenVO( std::vector& returnables, LLViewerObject* pChild, LLViewerRegion* pTargetRegion ) -{ - if ( !pChild ) - { - LL_ERRS()<<"child viewerobject is NULL "<mChildList.begin(); iter != pChild->mChildList.end(); iter++) - { - LLViewerObject* pChildofChild = *iter; - buildReturnablesForChildrenVO( returnables, pChildofChild, pTargetRegion ); - } -} - -void LLViewerObject::constructAndAddReturnable( std::vector& returnables, LLViewerObject* pChild, LLViewerRegion* pTargetRegion ) -{ - - LLVector3 targetRegionPos; - targetRegionPos.setVec( pChild->getPositionGlobal() ); - - LLBBox childBBox = LLBBox( targetRegionPos, pChild->getRotationRegion(), pChild->getScale() * -0.5f, - pChild->getScale() * 0.5f).getAxisAligned(); - - LLVector3 edgeA = targetRegionPos + childBBox.getMinLocal(); - LLVector3 edgeB = targetRegionPos + childBBox.getMaxLocal(); - - LLVector3d edgeAd, edgeBd; - edgeAd.setVec(edgeA); - edgeBd.setVec(edgeB); - - //Only add the box when either of the extents are in a neighboring region - if ( pTargetRegion->pointInRegionGlobal( edgeAd ) || pTargetRegion->pointInRegionGlobal( edgeBd ) ) - { - PotentialReturnableObject returnableObj; - returnableObj.box = childBBox; - returnableObj.pRegion = pTargetRegion; - returnables.push_back( returnableObj ); - } -} - -bool LLViewerObject::crossesParcelBounds() -{ - std::vector boxes; - boxes.push_back(LLBBox(getPositionRegion(), getRotationRegion(), getScale() * -0.5f, getScale() * 0.5f).getAxisAligned()); - for (child_list_t::iterator iter = mChildList.begin(); - iter != mChildList.end(); iter++) - { - LLViewerObject* child = *iter; - boxes.push_back(LLBBox(child->getPositionRegion(), child->getRotationRegion(), child->getScale() * -0.5f, child->getScale() * 0.5f).getAxisAligned()); - } - - return mRegionp && mRegionp->objectsCrossParcel(boxes); -} - -bool LLViewerObject::setParent(LLViewerObject* parent) -{ - if(mParent != parent) - { - LLViewerObject* old_parent = (LLViewerObject*)mParent ; - bool ret = LLPrimitive::setParent(parent); - if(ret && old_parent && parent) - { - old_parent->removeChild(this) ; - } - return ret ; - } - - return false ; -} - -void LLViewerObject::addChild(LLViewerObject *childp) -{ - for (child_list_t::iterator i = mChildList.begin(); i != mChildList.end(); ++i) - { - if (*i == childp) - { //already has child - return; - } - } - - if (!isAvatar()) - { - // propagate selection properties - childp->mbCanSelect = mbCanSelect; - } - - if(childp->setParent(this)) - { - mChildList.push_back(childp); - childp->afterReparent(); - - if (childp->isAvatar()) - { - mSeatCount++; - } - } -} - -void LLViewerObject::onReparent(LLViewerObject *old_parent, LLViewerObject *new_parent) -{ -} - -void LLViewerObject::afterReparent() -{ -} - -void LLViewerObject::removeChild(LLViewerObject *childp) -{ - for (child_list_t::iterator i = mChildList.begin(); i != mChildList.end(); ++i) - { - if (*i == childp) - { - if (!childp->isAvatar() && mDrawable.notNull() && mDrawable->isActive() && childp->mDrawable.notNull() && !isAvatar()) - { - gPipeline.markRebuild(childp->mDrawable, LLDrawable::REBUILD_VOLUME); - } - - mChildList.erase(i); - - if(childp->getParent() == this) - { - childp->setParent(NULL); - } - - if (childp->isAvatar()) - { - mSeatCount--; - } - break; - } - } - - if (childp->isSelected()) - { - LLSelectMgr::getInstance()->deselectObjectAndFamily(childp); - bool add_to_end = true; - LLSelectMgr::getInstance()->selectObjectAndFamily(childp, add_to_end); - } -} - -void LLViewerObject::addThisAndAllChildren(std::vector& objects) -{ - objects.push_back(this); - for (child_list_t::iterator iter = mChildList.begin(); - iter != mChildList.end(); iter++) - { - LLViewerObject* child = *iter; - if (!child->isAvatar()) - { - child->addThisAndAllChildren(objects); - } - } -} - -void LLViewerObject::addThisAndNonJointChildren(std::vector& objects) -{ - objects.push_back(this); - // don't add any attachments when temporarily selecting avatar - if (isAvatar()) - { - return; - } - for (child_list_t::iterator iter = mChildList.begin(); - iter != mChildList.end(); iter++) - { - LLViewerObject* child = *iter; - if ( (!child->isAvatar())) - { - child->addThisAndNonJointChildren(objects); - } - } -} - -bool LLViewerObject::isChild(LLViewerObject *childp) const -{ - for (child_list_t::const_iterator iter = mChildList.begin(); - iter != mChildList.end(); iter++) - { - LLViewerObject* testchild = *iter; - if (testchild == childp) - return true; - } - return false; -} - -// returns true if at least one avatar is sitting on this object -bool LLViewerObject::isSeat() const -{ - return mSeatCount > 0; -} - -bool LLViewerObject::setDrawableParent(LLDrawable* parentp) -{ - if (mDrawable.isNull()) - { - return false; - } - - bool ret = mDrawable->mXform.setParent(parentp ? &parentp->mXform : NULL); - if(!ret) - { - return false ; - } - LLDrawable* old_parent = mDrawable->mParent; - mDrawable->mParent = parentp; - - if (parentp && mDrawable->isActive()) - { - parentp->makeActive(); - parentp->setState(LLDrawable::ACTIVE_CHILD); - } - - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); - if( (old_parent != parentp && old_parent) - || (parentp && parentp->isActive())) - { - // *TODO we should not be relying on setDrawable parent to call markMoved - gPipeline.markMoved(mDrawable, false); - } - else if (!mDrawable->isAvatar()) - { - mDrawable->updateXform(true); - /*if (!mDrawable->getSpatialGroup()) - { - mDrawable->movePartition(); - }*/ - } - - return ret; -} - -// Show or hide particles, icon and HUD -void LLViewerObject::hideExtraDisplayItems( bool hidden ) -{ - if( mPartSourcep.notNull() ) - { - LLViewerPartSourceScript *partSourceScript = mPartSourcep.get(); - partSourceScript->setSuspended( hidden ); - } - - if( mText.notNull() ) - { - LLHUDText *hudText = mText.get(); - hudText->setHidden( hidden ); - } - - if( mIcon.notNull() ) - { - LLHUDIcon *hudIcon = mIcon.get(); - hudIcon->setHidden( hidden ); - } -} - -U32 LLViewerObject::checkMediaURL(const std::string &media_url) -{ - U32 retval = (U32)0x0; - if (!mMedia && !media_url.empty()) - { - retval |= MEDIA_URL_ADDED; - mMedia = new LLViewerObjectMedia; - mMedia->mMediaURL = media_url; - mMedia->mMediaType = LLViewerObject::MEDIA_SET; - mMedia->mPassedWhitelist = false; - } - else if (mMedia) - { - if (media_url.empty()) - { - retval |= MEDIA_URL_REMOVED; - delete mMedia; - mMedia = NULL; - } - else if (mMedia->mMediaURL != media_url) // <-- This is an optimization. If they are equal don't bother with below's test. - { - /*if (! (LLTextureEntry::getAgentIDFromMediaVersionString(media_url) == gAgent.getID() && - LLTextureEntry::getVersionFromMediaVersionString(media_url) == - LLTextureEntry::getVersionFromMediaVersionString(mMedia->mMediaURL) + 1)) - */ - { - // If the media URL is different and WE were not the one who - // changed it, mark dirty. - retval |= MEDIA_URL_UPDATED; - } - mMedia->mMediaURL = media_url; - mMedia->mPassedWhitelist = false; - } - } - return retval; -} - -//extract spatial information from object update message -//return parent_id -//static -U32 LLViewerObject::extractSpatialExtents(LLDataPackerBinaryBuffer *dp, LLVector3& pos, LLVector3& scale, LLQuaternion& rot) -{ - U32 parent_id = 0; - LLViewerObject::unpackParentID(dp, parent_id); - - LLViewerObject::unpackVector3(dp, scale, "Scale"); - LLViewerObject::unpackVector3(dp, pos, "Pos"); - - LLVector3 vec; - LLViewerObject::unpackVector3(dp, vec, "Rot"); - rot.unpackFromVector3(vec); - - return parent_id; -} - -U32 LLViewerObject::processUpdateMessage(LLMessageSystem *mesgsys, - void **user_data, - U32 block_num, - const EObjectUpdateType update_type, - LLDataPacker *dp) -{ - LL_PROFILE_ZONE_SCOPED; - LL_DEBUGS_ONCE("SceneLoadTiming") << "Received viewer object data" << LL_ENDL; - - LL_DEBUGS("ObjectUpdate") << " mesgsys " << mesgsys << " dp " << dp << " id " << getID() << " update_type " << (S32) update_type << LL_ENDL; - dumpStack("ObjectUpdateStack"); - - // The new OBJECTDATA_FIELD_SIZE_124, OBJECTDATA_FIELD_SIZE_140, OBJECTDATA_FIELD_SIZE_80 - // and OBJECTDATA_FIELD_SIZE_64 lengths should be supported in the existing cases below. - // Each case should start at the beginning of the buffer and extract all known - // values, and ignore any unknown data at the end of the buffer. - // This allows new data in the future without breaking current viewers. - const S32 OBJECTDATA_FIELD_SIZE_140 = 140; // Full precision avatar update for future extended data - const S32 OBJECTDATA_FIELD_SIZE_124 = 124; // Full precision object update for future extended data - const S32 OBJECTDATA_FIELD_SIZE_76 = 76; // Full precision avatar update - const S32 OBJECTDATA_FIELD_SIZE_60 = 60; // Full precision object update - const S32 OBJECTDATA_FIELD_SIZE_80 = 80; // Terse avatar update, 16 bit precision for future extended data - const S32 OBJECTDATA_FIELD_SIZE_64 = 64; // Terse object update, 16 bit precision for future extended data - const S32 OBJECTDATA_FIELD_SIZE_48 = 48; // Terse avatar update, 16 bit precision - const S32 OBJECTDATA_FIELD_SIZE_32 = 32; // Terse object update, 16 bit precision - - U32 retval = 0x0; - - // If region is removed from the list it is also deleted. - if (!LLWorld::instance().isRegionListed(mRegionp)) - { - LL_WARNS() << "Updating object in an invalid region" << LL_ENDL; - return retval; - } - - // Coordinates of objects on simulators are region-local. - U64 region_handle = 0; - - if(mesgsys != NULL) - { - mesgsys->getU64Fast(_PREHASH_RegionData, _PREHASH_RegionHandle, region_handle); - LLViewerRegion* regionp = LLWorld::getInstance()->getRegionFromHandle(region_handle); - if(regionp != mRegionp && regionp && mRegionp)//region cross - { - //this is the redundant position and region update, but it is necessary in case the viewer misses the following - //position and region update messages from sim. - //this redundant update should not cause any problems. - LLVector3 delta_pos = mRegionp->getOriginAgent() - regionp->getOriginAgent(); - setPositionParent(getPosition() + delta_pos); //update to the new region position immediately. - setRegion(regionp) ; //change the region. - } - else - { - if(regionp != mRegionp) - { - if(mRegionp) - { - mRegionp->removeFromCreatedList(getLocalID()); - } - if(regionp) - { - regionp->addToCreatedList(getLocalID()); - } - } - mRegionp = regionp ; - } - } - - if (!mRegionp) - { - U32 x, y; - from_region_handle(region_handle, &x, &y); - - LL_WARNS("UpdateFail") << "Object has invalid region " << x << ":" << y << "!" << LL_ENDL; - return retval; - } - - F32 time_dilation = 1.f; - if(mesgsys != NULL) - { - U16 time_dilation16; - mesgsys->getU16Fast(_PREHASH_RegionData, _PREHASH_TimeDilation, time_dilation16); - time_dilation = ((F32) time_dilation16) / 65535.f; - mRegionp->setTimeDilation(time_dilation); - } - - // this will be used to determine if we've really changed position - // Use getPosition, not getPositionRegion, since this is what we're comparing directly against. - LLVector3 test_pos_parent = getPosition(); - - // This needs to match the largest size below. See switch(length) - U8 data[MAX_OBJECT_BINARY_DATA_SIZE]; - -#ifdef LL_BIG_ENDIAN - U16 valswizzle[4]; -#endif - U16 *val; - const F32 size = LLWorld::getInstance()->getRegionWidthInMeters(); - const F32 MAX_HEIGHT = LLWorld::getInstance()->getRegionMaxHeight(); - const F32 MIN_HEIGHT = LLWorld::getInstance()->getRegionMinHeight(); - S32 length = 0; - S32 count = 0; - S32 this_update_precision = 32; // in bits - - // Temporaries, because we need to compare w/ previous to set dirty flags... - LLVector3 new_pos_parent; - LLVector3 new_vel; - LLVector3 new_acc; - LLVector3 new_angv; - LLVector3 old_angv = getAngularVelocity(); - LLQuaternion new_rot; - LLVector3 new_scale = getScale(); - - U32 parent_id = 0; - U8 material = 0; - U8 click_action = 0; - U32 crc = 0; - - bool old_special_hover_cursor = specialHoverCursor(); - - LLViewerObject *cur_parentp = (LLViewerObject *)getParent(); - - if (cur_parentp) - { - parent_id = cur_parentp->mLocalID; - } - - if (!dp) - { - switch(update_type) - { - case OUT_FULL: - { -#ifdef DEBUG_UPDATE_TYPE - LL_INFOS() << "Full:" << getID() << LL_ENDL; -#endif - //clear cost and linkset cost - setObjectCostStale(); - if (isSelected()) - { - gFloaterTools->dirty(); - } - - LLUUID audio_uuid; - LLUUID owner_id; // only valid if audio_uuid or particle system is not null - F32 gain; - F32 cutoff; - U8 sound_flags; - - mesgsys->getU32Fast( _PREHASH_ObjectData, _PREHASH_CRC, crc, block_num); - mesgsys->getU32Fast( _PREHASH_ObjectData, _PREHASH_ParentID, parent_id, block_num); - mesgsys->getUUIDFast(_PREHASH_ObjectData, _PREHASH_Sound, audio_uuid, block_num ); - // HACK: Owner id only valid if non-null sound id or particle system - mesgsys->getUUIDFast(_PREHASH_ObjectData, _PREHASH_OwnerID, owner_id, block_num ); - mesgsys->getF32Fast( _PREHASH_ObjectData, _PREHASH_Gain, gain, block_num ); - mesgsys->getF32Fast( _PREHASH_ObjectData, _PREHASH_Radius, cutoff, block_num ); - mesgsys->getU8Fast( _PREHASH_ObjectData, _PREHASH_Flags, sound_flags, block_num ); - mesgsys->getU8Fast( _PREHASH_ObjectData, _PREHASH_Material, material, block_num ); - mesgsys->getU8Fast( _PREHASH_ObjectData, _PREHASH_ClickAction, click_action, block_num); - mesgsys->getVector3Fast(_PREHASH_ObjectData, _PREHASH_Scale, new_scale, block_num ); - length = mesgsys->getSizeFast(_PREHASH_ObjectData, block_num, _PREHASH_ObjectData); - mesgsys->getBinaryDataFast(_PREHASH_ObjectData, _PREHASH_ObjectData, data, length, block_num, MAX_OBJECT_BINARY_DATA_SIZE); - length = llmin(length, MAX_OBJECT_BINARY_DATA_SIZE); // getBinaryDataFast() safely fills the buffer to max_size - - mTotalCRC = crc; - // Might need to update mSourceMuted here to properly pick up new radius - mSoundCutOffRadius = cutoff; - - // Owner ID used for sound muting or particle system muting - setAttachedSound(audio_uuid, owner_id, gain, sound_flags); - - U8 old_material = getMaterial(); - if (old_material != material) - { - setMaterial(material); - if (mDrawable.notNull()) - { - gPipeline.markMoved(mDrawable, false); // undamped - } - } - setClickAction(click_action); - - count = 0; - LLVector4 collision_plane; - - switch(length) - { - case OBJECTDATA_FIELD_SIZE_140: - case OBJECTDATA_FIELD_SIZE_76: - // pull out collision normal for avatar - htolememcpy(collision_plane.mV, &data[count], MVT_LLVector4, sizeof(LLVector4)); - ((LLVOAvatar*)this)->setFootPlane(collision_plane); - count += sizeof(LLVector4); - - case OBJECTDATA_FIELD_SIZE_124: - case OBJECTDATA_FIELD_SIZE_60: - this_update_precision = 32; - // this is a full precision update - // pos - htolememcpy(new_pos_parent.mV, &data[count], MVT_LLVector3, sizeof(LLVector3)); - count += sizeof(LLVector3); - // vel - htolememcpy((void*)getVelocity().mV, &data[count], MVT_LLVector3, sizeof(LLVector3)); - count += sizeof(LLVector3); - // acc - htolememcpy((void*)getAcceleration().mV, &data[count], MVT_LLVector3, sizeof(LLVector3)); - count += sizeof(LLVector3); - // theta - { - LLVector3 vec; - htolememcpy(vec.mV, &data[count], MVT_LLVector3, sizeof(LLVector3)); - new_rot.unpackFromVector3(vec); - } - count += sizeof(LLVector3); - // omega - htolememcpy((void*)new_angv.mV, &data[count], MVT_LLVector3, sizeof(LLVector3)); - if (new_angv.isExactlyZero()) - { - // reset rotation time - resetRot(); - } - setAngularVelocity(new_angv); - count += sizeof(LLVector3); -#if LL_DARWIN - if (length == OBJECTDATA_FIELD_SIZE_76 || - length == OBJECTDATA_FIELD_SIZE_140) - { - setAngularVelocity(LLVector3::zero); - } -#endif - break; - - // length values 48, 32 and 16 were once in viewer code but - // are never sent by the SL simulator - default: - LL_WARNS("UpdateFail") << "Unexpected ObjectData buffer size " << length - << " for " << getID() << " with OUT_FULL message" << LL_ENDL; - } - - //////////////////////////////////////////////////// - // - // Here we handle data specific to the full message. - // - - U32 flags; - mesgsys->getU32Fast(_PREHASH_ObjectData, _PREHASH_UpdateFlags, flags, block_num); - // clear all but local flags - mFlags &= FLAGS_LOCAL; - mFlags |= flags; - - U8 state; - mesgsys->getU8Fast(_PREHASH_ObjectData, _PREHASH_State, state, block_num ); - mAttachmentState = state; - - // ...new objects that should come in selected need to be added to the selected list - mCreateSelected = ((flags & FLAGS_CREATE_SELECTED) != 0); - - // Set all name value pairs - S32 nv_size = mesgsys->getSizeFast(_PREHASH_ObjectData, block_num, _PREHASH_NameValue); - if (nv_size > 0) - { - std::string name_value_list; - mesgsys->getStringFast(_PREHASH_ObjectData, _PREHASH_NameValue, name_value_list, block_num); - setNameValueList(name_value_list); - } - - // Clear out any existing generic data - if (mData) - { - delete [] mData; - mData = NULL; - } - - // Dec 2023 new generic data: - // Trees work as before, this field contains genome data - // Not a tree: root objects send 1 byte with the number of - // total prims in the linkset - // If the generic data size is zero, then number of prims is 1 - // - // Viewers should not check for specific data sizes exactly, but if - // the field has data, process it from the start and ignore the remainder. - - // Check for appended generic data - const S32 GENERIC_DATA_BUFFER_SIZE = 16; - S32 data_size = mesgsys->getSizeFast(_PREHASH_ObjectData, block_num, _PREHASH_Data); - if (data_size > 0) - { // has generic data - if (getPCode() == LL_PCODE_LEGACY_TREE || getPCode() == LL_PCODE_TREE_NEW) - { - mData = new U8[data_size]; - mesgsys->getBinaryDataFast(_PREHASH_ObjectData, _PREHASH_Data, mData, data_size, block_num); - LL_DEBUGS("NewObjectData") << "Read " << data_size << " bytes tree genome data for " << getID() << ", pcode " - << getPCodeString() << ", value " << (S32) mData[0] << LL_ENDL; - } - else - { // Extract number of prims - U8 generic_data[GENERIC_DATA_BUFFER_SIZE]; - mesgsys->getBinaryDataFast(_PREHASH_ObjectData, _PREHASH_Data, - &generic_data[0], llmin(data_size, GENERIC_DATA_BUFFER_SIZE), block_num); - // This is sample code to extract the number of prims - // Future viewers should use it for their own purposes - if (!isAvatar()) - { - S32 num_prims = (S32) generic_data[0]; - LL_DEBUGS("NewObjectData") << "Root prim " << getID() << " has " - << num_prims << " prims in linkset" << LL_ENDL; - } - } - } - - S32 text_size = mesgsys->getSizeFast(_PREHASH_ObjectData, block_num, _PREHASH_Text); - if (text_size > 1) - { - // Setup object text - if (!mText) - { - initHudText(); - } - - std::string temp_string; - mesgsys->getStringFast(_PREHASH_ObjectData, _PREHASH_Text, temp_string, block_num ); - - LLColor4U coloru; - mesgsys->getBinaryDataFast(_PREHASH_ObjectData, _PREHASH_TextColor, coloru.mV, 4, block_num); - - // alpha was flipped so that it zero encoded better - coloru.mV[3] = 255 - coloru.mV[3]; - mText->setColor(LLColor4(coloru)); - mText->setString(temp_string); - - mHudText = temp_string; - mHudTextColor = LLColor4(coloru); - - setChanged(MOVED | SILHOUETTE); - } - else - { - if (mText.notNull()) - { - mText->markDead(); - mText = NULL; - } - mHudText.clear(); - } - - std::string media_url; - mesgsys->getStringFast(_PREHASH_ObjectData, _PREHASH_MediaURL, media_url, block_num); - retval |= checkMediaURL(media_url); - - // - // Unpack particle system data - // - unpackParticleSource(block_num, owner_id); - - // Mark all extra parameters not used - std::unordered_map::iterator iter; - for (iter = mExtraParameterList.begin(); iter != mExtraParameterList.end(); ++iter) - { - iter->second->in_use = false; - } - - // Unpack extra parameters - S32 size = mesgsys->getSizeFast(_PREHASH_ObjectData, block_num, _PREHASH_ExtraParams); - if (size > 0) - { - U8 *buffer = new U8[size]; - mesgsys->getBinaryDataFast(_PREHASH_ObjectData, _PREHASH_ExtraParams, buffer, size, block_num); - LLDataPackerBinaryBuffer dp(buffer, size); - - U8 num_parameters; - dp.unpackU8(num_parameters, "num_params"); - U8 param_block[MAX_OBJECT_PARAMS_SIZE]; - for (U8 param=0; paramsecond->in_use) - { - // Send an update message in case it was formerly in use - parameterChanged(iter->first, iter->second->data, false, false); - } - } - - break; - } - - case OUT_TERSE_IMPROVED: - { -#ifdef DEBUG_UPDATE_TYPE - LL_INFOS() << "TI:" << getID() << LL_ENDL; -#endif - length = mesgsys->getSizeFast(_PREHASH_ObjectData, block_num, _PREHASH_ObjectData); - mesgsys->getBinaryDataFast(_PREHASH_ObjectData, _PREHASH_ObjectData, data, length, block_num, MAX_OBJECT_BINARY_DATA_SIZE); - length = llmin(length, MAX_OBJECT_BINARY_DATA_SIZE); // getBinaryDataFast() safely fills the buffer to max_size - count = 0; - LLVector4 collision_plane; - - switch(length) - { - case OBJECTDATA_FIELD_SIZE_80: - case OBJECTDATA_FIELD_SIZE_48: - // pull out collision normal for avatar - htolememcpy(collision_plane.mV, &data[count], MVT_LLVector4, sizeof(LLVector4)); - ((LLVOAvatar*)this)->setFootPlane(collision_plane); - count += sizeof(LLVector4); - - case OBJECTDATA_FIELD_SIZE_64: - case OBJECTDATA_FIELD_SIZE_32: - // this is a terse 16 bit quantized update - this_update_precision = 16; - test_pos_parent.quantize16(-0.5f*size, 1.5f*size, MIN_HEIGHT, MAX_HEIGHT); - -#ifdef LL_BIG_ENDIAN - htolememcpy(valswizzle, &data[count], MVT_U16Vec3, 6); - val = valswizzle; -#else - val = (U16 *) &data[count]; -#endif - count += sizeof(U16)*3; - new_pos_parent.mV[VX] = U16_to_F32(val[VX], -0.5f*size, 1.5f*size); - new_pos_parent.mV[VY] = U16_to_F32(val[VY], -0.5f*size, 1.5f*size); - new_pos_parent.mV[VZ] = U16_to_F32(val[VZ], MIN_HEIGHT, MAX_HEIGHT); - -#ifdef LL_BIG_ENDIAN - htolememcpy(valswizzle, &data[count], MVT_U16Vec3, 6); - val = valswizzle; -#else - val = (U16 *) &data[count]; -#endif - count += sizeof(U16)*3; - setVelocity(U16_to_F32(val[VX], -size, size), - U16_to_F32(val[VY], -size, size), - U16_to_F32(val[VZ], -size, size)); - -#ifdef LL_BIG_ENDIAN - htolememcpy(valswizzle, &data[count], MVT_U16Vec3, 6); - val = valswizzle; -#else - val = (U16 *) &data[count]; -#endif - count += sizeof(U16)*3; - setAcceleration(U16_to_F32(val[VX], -size, size), - U16_to_F32(val[VY], -size, size), - U16_to_F32(val[VZ], -size, size)); - -#ifdef LL_BIG_ENDIAN - htolememcpy(valswizzle, &data[count], MVT_U16Quat, 8); - val = valswizzle; -#else - val = (U16 *) &data[count]; -#endif - count += sizeof(U16)*4; - new_rot.mQ[VX] = U16_to_F32(val[VX], -1.f, 1.f); - new_rot.mQ[VY] = U16_to_F32(val[VY], -1.f, 1.f); - new_rot.mQ[VZ] = U16_to_F32(val[VZ], -1.f, 1.f); - new_rot.mQ[VW] = U16_to_F32(val[VW], -1.f, 1.f); - -#ifdef LL_BIG_ENDIAN - htolememcpy(valswizzle, &data[count], MVT_U16Vec3, 6); - val = valswizzle; -#else - val = (U16 *) &data[count]; -#endif - new_angv.set(U16_to_F32(val[VX], -size, size), - U16_to_F32(val[VY], -size, size), - U16_to_F32(val[VZ], -size, size)); - setAngularVelocity(new_angv); - break; - - // Previous viewers had code for length 76, 60 or 16 byte length - // with full precision or 8 bit quanitzation, but the - // SL servers will never send those data formats. If you ever see this - // warning in Second Life, please file a bug report - default: - LL_WARNS("UpdateFail") << "Unexpected ObjectData buffer size " << length << " for " << getID() - << " with OUT_FULL message" << LL_ENDL; - } - - U8 state; - mesgsys->getU8Fast(_PREHASH_ObjectData, _PREHASH_State, state, block_num ); - mAttachmentState = state; - break; - } - - default: - LL_WARNS("UpdateFail") << "Unknown uncompressed update type " << update_type << " for " << getID() << LL_ENDL; - break; - } - } - else - { - // handle the compressed case - have dp datapacker - LLUUID sound_uuid; - LLUUID owner_id; - F32 gain = 0; - U8 sound_flags = 0; - F32 cutoff = 0; - - U16 val[4]; - - U8 state; - - dp->unpackU8(state, "State"); - mAttachmentState = state; - - switch(update_type) - { - case OUT_TERSE_IMPROVED: - { -#ifdef DEBUG_UPDATE_TYPE - LL_INFOS() << "CompTI:" << getID() << LL_ENDL; -#endif - U8 value; - dp->unpackU8(value, "agent"); - if (value) - { - LLVector4 collision_plane; - dp->unpackVector4(collision_plane, "Plane"); - ((LLVOAvatar*)this)->setFootPlane(collision_plane); - } - test_pos_parent = getPosition(); - dp->unpackVector3(new_pos_parent, "Pos"); - dp->unpackU16(val[VX], "VelX"); - dp->unpackU16(val[VY], "VelY"); - dp->unpackU16(val[VZ], "VelZ"); - setVelocity(U16_to_F32(val[VX], -128.f, 128.f), - U16_to_F32(val[VY], -128.f, 128.f), - U16_to_F32(val[VZ], -128.f, 128.f)); - dp->unpackU16(val[VX], "AccX"); - dp->unpackU16(val[VY], "AccY"); - dp->unpackU16(val[VZ], "AccZ"); - setAcceleration(U16_to_F32(val[VX], -64.f, 64.f), - U16_to_F32(val[VY], -64.f, 64.f), - U16_to_F32(val[VZ], -64.f, 64.f)); - - dp->unpackU16(val[VX], "ThetaX"); - dp->unpackU16(val[VY], "ThetaY"); - dp->unpackU16(val[VZ], "ThetaZ"); - dp->unpackU16(val[VS], "ThetaS"); - new_rot.mQ[VX] = U16_to_F32(val[VX], -1.f, 1.f); - new_rot.mQ[VY] = U16_to_F32(val[VY], -1.f, 1.f); - new_rot.mQ[VZ] = U16_to_F32(val[VZ], -1.f, 1.f); - new_rot.mQ[VS] = U16_to_F32(val[VS], -1.f, 1.f); - dp->unpackU16(val[VX], "AccX"); - dp->unpackU16(val[VY], "AccY"); - dp->unpackU16(val[VZ], "AccZ"); - new_angv.set(U16_to_F32(val[VX], -64.f, 64.f), - U16_to_F32(val[VY], -64.f, 64.f), - U16_to_F32(val[VZ], -64.f, 64.f)); - setAngularVelocity(new_angv); - } - break; - case OUT_FULL_COMPRESSED: - case OUT_FULL_CACHED: - { -#ifdef DEBUG_UPDATE_TYPE - LL_INFOS() << "CompFull:" << getID() << LL_ENDL; -#endif - setObjectCostStale(); - - if (isSelected()) - { - gFloaterTools->dirty(); - } - - dp->unpackU32(crc, "CRC"); - mTotalCRC = crc; - dp->unpackU8(material, "Material"); - U8 old_material = getMaterial(); - if (old_material != material) - { - setMaterial(material); - if (mDrawable.notNull()) - { - gPipeline.markMoved(mDrawable, false); // undamped - } - } - dp->unpackU8(click_action, "ClickAction"); - setClickAction(click_action); - dp->unpackVector3(new_scale, "Scale"); - dp->unpackVector3(new_pos_parent, "Pos"); - LLVector3 vec; - dp->unpackVector3(vec, "Rot"); - new_rot.unpackFromVector3(vec); - setAcceleration(LLVector3::zero); - - U32 value; - dp->unpackU32(value, "SpecialCode"); - dp->setPassFlags(value); - dp->unpackUUID(owner_id, "Owner"); - - mOwnerID = owner_id; - - if (value & 0x80) - { - dp->unpackVector3(new_angv, "Omega"); - setAngularVelocity(new_angv); - } - - if (value & 0x20) - { - dp->unpackU32(parent_id, "ParentID"); - } - else - { - parent_id = 0; - } - - S32 sp_size; - U32 size; - if (value & 0x2) - { - sp_size = 1; - delete [] mData; - mData = new U8[1]; - dp->unpackU8(((U8*)mData)[0], "TreeData"); - } - else if (value & 0x1) - { - dp->unpackU32(size, "ScratchPadSize"); - delete [] mData; - mData = new U8[size]; - dp->unpackBinaryData((U8 *)mData, sp_size, "PartData"); - } - else - { - mData = NULL; - } - - // Setup object text - if (!mText && (value & 0x4)) - { - initHudText(); - } - - if (value & 0x4) - { - std::string temp_string; - dp->unpackString(temp_string, "Text"); - LLColor4U coloru; - dp->unpackBinaryDataFixed(coloru.mV, 4, "Color"); - coloru.mV[3] = 255 - coloru.mV[3]; - mText->setColor(LLColor4(coloru)); - mText->setString(temp_string); - - mHudText = temp_string; - mHudTextColor = LLColor4(coloru); - - setChanged(TEXTURE); - } - else - { - if (mText.notNull()) - { - mText->markDead(); - mText = NULL; - } - mHudText.clear(); - } - - std::string media_url; - if (value & 0x200) - { - dp->unpackString(media_url, "MediaURL"); - } - retval |= checkMediaURL(media_url); - - // - // Unpack particle system data (legacy) - // - if (value & 0x8) - { - unpackParticleSource(*dp, owner_id, true); - } - else if (!(value & 0x400)) - { - deleteParticleSource(); - } - - // Mark all extra parameters not used - std::unordered_map::iterator iter; - for (iter = mExtraParameterList.begin(); iter != mExtraParameterList.end(); ++iter) - { - iter->second->in_use = false; - } - - // Unpack extra params - U8 num_parameters; - dp->unpackU8(num_parameters, "num_params"); - U8 param_block[MAX_OBJECT_PARAMS_SIZE]; - for (U8 param=0; paramunpackU16(param_type, "param_type"); - dp->unpackBinaryData(param_block, param_size, "param_data"); - //LL_INFOS() << "Param type: " << param_type << ", Size: " << param_size << LL_ENDL; - LLDataPackerBinaryBuffer dp2(param_block, param_size); - unpackParameterEntry(param_type, &dp2); - } - - for (iter = mExtraParameterList.begin(); iter != mExtraParameterList.end(); ++iter) - { - if (!iter->second->in_use) - { - // Send an update message in case it was formerly in use - parameterChanged(iter->first, iter->second->data, false, false); - } - } - - if (value & 0x10) - { - dp->unpackUUID(sound_uuid, "SoundUUID"); - dp->unpackF32(gain, "SoundGain"); - dp->unpackU8(sound_flags, "SoundFlags"); - dp->unpackF32(cutoff, "SoundRadius"); - } - - if (value & 0x100) - { - std::string name_value_list; - dp->unpackString(name_value_list, "NV"); - - setNameValueList(name_value_list); - } - - mTotalCRC = crc; - mSoundCutOffRadius = cutoff; - - setAttachedSound(sound_uuid, owner_id, gain, sound_flags); - - // only get these flags on updates from sim, not cached ones - // Preload these five flags for every object. - // Finer shades require the object to be selected, and the selection manager - // stores the extended permission info. - if(mesgsys != NULL) - { - U32 flags; - mesgsys->getU32Fast(_PREHASH_ObjectData, _PREHASH_UpdateFlags, flags, block_num); - loadFlags(flags); - } - } - break; - - default: - LL_WARNS("UpdateFail") << "Unknown compressed update type " << update_type << " for " << getID() << LL_ENDL; - break; - } - } - - // - // Fix object parenting. - // - bool b_changed_status = false; - - if (OUT_TERSE_IMPROVED != update_type) - { - // We only need to update parenting on full updates, terse updates - // don't send parenting information. - if (!cur_parentp) - { - if (parent_id == 0) - { - // No parent now, no parent in message -> do nothing - } - else - { - // No parent now, new parent in message -> attach to that parent if possible - LLUUID parent_uuid; - - if(mesgsys != NULL) - { - LLViewerObjectList::getUUIDFromLocal(parent_uuid, - parent_id, - mesgsys->getSenderIP(), - mesgsys->getSenderPort()); - } - else - { - LLViewerObjectList::getUUIDFromLocal(parent_uuid, - parent_id, - mRegionp->getHost().getAddress(), - mRegionp->getHost().getPort()); - } - - LLViewerObject *sent_parentp = gObjectList.findObject(parent_uuid); - - // - // Check to see if we have the corresponding viewer object for the parent. - // - if (sent_parentp && sent_parentp->getParent() == this) - { - // Try to recover if we attempt to attach a parent to its child - LL_WARNS("UpdateFail") << "Attempt to attach a parent to it's child: " << this->getID() << " to " - << sent_parentp->getID() << LL_ENDL; - this->removeChild(sent_parentp); - sent_parentp->setDrawableParent(NULL); - } - - if (sent_parentp && (sent_parentp != this) && !sent_parentp->isDead()) - { - if (((LLViewerObject*)sent_parentp)->isAvatar()) - { - //LL_DEBUGS("Avatar") << "ATT got object update for attachment " << LL_ENDL; - } - - // - // We have a viewer object for the parent, and it's not dead. - // Do the actual reparenting here. - // - - // new parent is valid - b_changed_status = true; - // ...no current parent, so don't try to remove child - if (mDrawable.notNull()) - { - if (mDrawable->isDead() || !mDrawable->getVObj()) - { - LL_WARNS("UpdateFail") << "Drawable is dead or no VObj!" << LL_ENDL; - sent_parentp->addChild(this); - } - else - { - if (!setDrawableParent(sent_parentp->mDrawable)) // LLViewerObject::processUpdateMessage 1 - { - // Bad, we got a cycle somehow. - // Kill both the parent and the child, and - // set cache misses for both of them. - LL_WARNS("UpdateFail") << "Attempting to recover from parenting cycle! " - << "Killing " << sent_parentp->getID() << " and " << getID() - << ", Adding to cache miss list" << LL_ENDL; - setParent(NULL); - sent_parentp->setParent(NULL); - getRegion()->addCacheMissFull(getLocalID()); - getRegion()->addCacheMissFull(sent_parentp->getLocalID()); - gObjectList.killObject(sent_parentp); - gObjectList.killObject(this); - return retval; - } - sent_parentp->addChild(this); - // make sure this object gets a non-damped update - if (sent_parentp->mDrawable.notNull()) - { - gPipeline.markMoved(sent_parentp->mDrawable, false); // undamped - } - } - } - else - { - sent_parentp->addChild(this); - } - - // Show particles, icon and HUD - hideExtraDisplayItems( false ); - - setChanged(MOVED | SILHOUETTE); - } - else - { - // - // No corresponding viewer object for the parent, put the various - // pieces on the orphan list. - // - - //parent_id - U32 ip, port; - - if(mesgsys != NULL) - { - ip = mesgsys->getSenderIP(); - port = mesgsys->getSenderPort(); - } - else - { - ip = mRegionp->getHost().getAddress(); - port = mRegionp->getHost().getPort(); - } - gObjectList.orphanize(this, parent_id, ip, port); - - // Hide particles, icon and HUD - hideExtraDisplayItems( true ); - } - } - } - else - { - // BUG: this is a bad assumption once border crossing is alowed - if ( (parent_id == cur_parentp->mLocalID) - &&(update_type == OUT_TERSE_IMPROVED)) - { - // Parent now, same parent in message -> do nothing - - // Debugging for suspected problems with local ids. - //LLUUID parent_uuid; - //LLViewerObjectList::getUUIDFromLocal(parent_uuid, parent_id, mesgsys->getSenderIP(), mesgsys->getSenderPort() ); - //if (parent_uuid != cur_parentp->getID() ) - //{ - // LL_ERRS() << "Local ID match but UUID mismatch of viewer object" << LL_ENDL; - //} - } - else - { - // Parented now, different parent in message - LLViewerObject *sent_parentp; - if (parent_id == 0) - { - // - // This object is no longer parented, we sent in a zero parent ID. - // - sent_parentp = NULL; - } - else - { - LLUUID parent_uuid; - - if(mesgsys != NULL) - { - LLViewerObjectList::getUUIDFromLocal(parent_uuid, - parent_id, - gMessageSystem->getSenderIP(), - gMessageSystem->getSenderPort()); - } - else - { - LLViewerObjectList::getUUIDFromLocal(parent_uuid, - parent_id, - mRegionp->getHost().getAddress(), - mRegionp->getHost().getPort()); - } - sent_parentp = gObjectList.findObject(parent_uuid); - - if (isAvatar()) - { - // This logic is meant to handle the case where a sitting avatar has reached a new sim - // ahead of the object she was sitting on (which is common as objects are transfered through - // a slower route than agents)... - // In this case, the local id for the object will not be valid, since the viewer has not received - // a full update for the object from that sim yet, so we assume that the agent is still sitting - // where she was originally. --RN - if (!sent_parentp) - { - sent_parentp = cur_parentp; - } - } - else if (!sent_parentp) - { - // - // Switching parents, but we don't know the new parent. - // - U32 ip, port; - - if(mesgsys != NULL) - { - ip = mesgsys->getSenderIP(); - port = mesgsys->getSenderPort(); - } - else - { - ip = mRegionp->getHost().getAddress(); - port = mRegionp->getHost().getPort(); - } - - // We're an orphan, flag things appropriately. - gObjectList.orphanize(this, parent_id, ip, port); - } - } - - // Reattach if possible. - if (sent_parentp && sent_parentp != cur_parentp && sent_parentp != this) - { - // New parent is valid, detach and reattach - b_changed_status = true; - if (mDrawable.notNull()) - { - if (!setDrawableParent(sent_parentp->mDrawable)) // LLViewerObject::processUpdateMessage 2 - { - // Bad, we got a cycle somehow. - // Kill both the parent and the child, and - // set cache misses for both of them. - LL_WARNS() << "Attempting to recover from parenting cycle!" << LL_ENDL; - LL_WARNS() << "Killing " << sent_parentp->getID() << " and " << getID() << LL_ENDL; - LL_WARNS() << "Adding to cache miss list" << LL_ENDL; - setParent(NULL); - sent_parentp->setParent(NULL); - getRegion()->addCacheMissFull(getLocalID()); - getRegion()->addCacheMissFull(sent_parentp->getLocalID()); - gObjectList.killObject(sent_parentp); - gObjectList.killObject(this); - return retval; - } - // make sure this object gets a non-damped update - } - cur_parentp->removeChild(this); - sent_parentp->addChild(this); - setChanged(MOVED | SILHOUETTE); - sent_parentp->setChanged(MOVED | SILHOUETTE); - if (sent_parentp->mDrawable.notNull()) - { - gPipeline.markMoved(sent_parentp->mDrawable, false); // undamped - } - } - else if (!sent_parentp) - { - bool remove_parent = true; - // No new parent, or the parent that we sent doesn't exist on the viewer. - LLViewerObject *parentp = (LLViewerObject *)getParent(); - if (parentp) - { - if (parentp->getRegion() != getRegion()) - { - // This is probably an object flying across a region boundary, the - // object probably ISN'T being reparented, but just got an object - // update out of order (child update before parent). - //LL_INFOS() << "Don't reparent object handoffs!" << LL_ENDL; - remove_parent = false; - } - } - - if (remove_parent) - { - b_changed_status = true; - if (mDrawable.notNull()) - { - // clear parent to removeChild can put the drawable on the damped list - setDrawableParent(NULL); // LLViewerObject::processUpdateMessage 3 - } - - cur_parentp->removeChild(this); - - setChanged(MOVED | SILHOUETTE); - - if (mDrawable.notNull()) - { - // make sure this object gets a non-damped update - gPipeline.markMoved(mDrawable, false); // undamped - } - } - } - } - } - } - - new_rot.normQuat(); - - if (sPingInterpolate && mesgsys != NULL) - { - LLCircuitData *cdp = gMessageSystem->mCircuitInfo.findCircuit(mesgsys->getSender()); - if (cdp) - { - // Note: delay is U32 and usually less then second, - // converting it into seconds with valueInUnits will result in 0 - F32 ping_delay = 0.5f * time_dilation * ( ((F32)cdp->getPingDelay().value()) * 0.001f + gFrameDTClamped); - LLVector3 diff = getVelocity() * ping_delay; - new_pos_parent += diff; - } - else - { - LL_WARNS() << "findCircuit() returned NULL; skipping interpolation" << LL_ENDL; - } - } - - ////////////////////////// - // - // Set the generic change flags... - // - // - - // If we're going to skip this message, why are we - // doing all the parenting, etc above? - if(mesgsys != NULL) - { - U32 packet_id = mesgsys->getCurrentRecvPacketID(); - if (packet_id < mLatestRecvPacketID && - mLatestRecvPacketID - packet_id < 65536) - { - //skip application of this message, it's old - return retval; - } - mLatestRecvPacketID = packet_id; - } - - // Set the change flags for scale - if (new_scale != getScale()) - { - setChanged(SCALED | SILHOUETTE); - setScale(new_scale); // Must follow setting permYouOwner() - } - - // first, let's see if the new position is actually a change - - //static S32 counter = 0; - - F32 vel_mag_sq = getVelocity().magVecSquared(); - F32 accel_mag_sq = getAcceleration().magVecSquared(); - - if ( ((b_changed_status)||(test_pos_parent != new_pos_parent)) - ||( (!isSelected()) - &&( (vel_mag_sq != 0.f) - ||(accel_mag_sq != 0.f) - ||(this_update_precision > mBestUpdatePrecision)))) - { - mBestUpdatePrecision = this_update_precision; - - LLVector3 diff = new_pos_parent - test_pos_parent ; - F32 mag_sqr = diff.magVecSquared() ; - if(llfinite(mag_sqr)) - { - setPositionParent(new_pos_parent); - } - else - { - LL_WARNS() << "Can not move the object/avatar to an infinite location!" << LL_ENDL ; - - retval |= INVALID_UPDATE ; - } - - if (mParent && ((LLViewerObject*)mParent)->isAvatar()) - { - // we have changed the position of an attachment, so we need to clamp it - LLVOAvatar *avatar = (LLVOAvatar*)mParent; - - avatar->clampAttachmentPositions(); - } - - // If we're snapping the position by more than 0.5m, update LLViewerStats::mAgentPositionSnaps - if ( asAvatar() && asAvatar()->isSelf() && (mag_sqr > 0.25f) ) - { - record(LLStatViewer::AGENT_POSITION_SNAP, LLUnit(diff.length())); - } - } - - if ((new_rot.isNotEqualEps(getRotation(), F_ALMOST_ZERO)) - || (new_angv != old_angv)) - { - if (new_rot != mPreviousRotation) - { - resetRot(); - } - else if (new_angv != old_angv) - { - if (flagUsePhysics()) - { - resetRot(); - } - else - { - resetRotTime(); - } - } - - // Remember the last rotation value - mPreviousRotation = new_rot; - - // Set the rotation of the object followed by adjusting for the accumulated angular velocity (llSetTargetOmega) - setRotation(new_rot * mAngularVelocityRot); - setChanged(ROTATED | SILHOUETTE); - } - - if ( gShowObjectUpdates ) - { - LLColor4 color; - if (update_type == OUT_TERSE_IMPROVED) - { - color.setVec(0.f, 0.f, 1.f, 1.f); - } - else - { - color.setVec(1.f, 0.f, 0.f, 1.f); - } - gPipeline.addDebugBlip(getPositionAgent(), color); - LL_DEBUGS("MessageBlip") << "Update type " << (S32)update_type << " blip for local " << mLocalID << " at " << getPositionAgent() << LL_ENDL; - } - - const F32 MAG_CUTOFF = F_APPROXIMATELY_ZERO; - - llassert(vel_mag_sq >= 0.f); - llassert(accel_mag_sq >= 0.f); - llassert(getAngularVelocity().magVecSquared() >= 0.f); - - if ((MAG_CUTOFF >= vel_mag_sq) && - (MAG_CUTOFF >= accel_mag_sq) && - (MAG_CUTOFF >= getAngularVelocity().magVecSquared())) - { - mStatic = true; // This object doesn't move! - } - else - { - mStatic = false; - } - -// BUG: This code leads to problems during group rotate and any scale operation. -// Small discepencies between the simulator and viewer representations cause the -// selection center to creep, leading to objects moving around the wrong center. -// -// Removing this, however, means that if someone else drags an object you have -// selected, your selection center and dialog boxes will be wrong. It also means -// that higher precision information on selected objects will be ignored. -// -// I believe the group rotation problem is fixed. JNC 1.21.2002 -// - // Additionally, if any child is selected, need to update the dialogs and selection - // center. - bool needs_refresh = mUserSelected; - for (child_list_t::iterator iter = mChildList.begin(); - iter != mChildList.end(); iter++) - { - LLViewerObject* child = *iter; - needs_refresh = needs_refresh || child->mUserSelected; - } - - static LLCachedControl allow_select_avatar(gSavedSettings, "AllowSelectAvatar", false); - if (needs_refresh) - { - LLSelectMgr::getInstance()->updateSelectionCenter(); - dialog_refresh_all(); - } - else if (allow_select_avatar && asAvatar()) - { - // Override any avatar position updates received - // Works only if avatar was repositioned using build - // tools and build floater is visible - LLSelectMgr::getInstance()->overrideAvatarUpdates(); - } - - - // Mark update time as approx. now, with the ping delay. - // Ping delay is off because it's not set for velocity interpolation, causing - // much jumping and hopping around... - -// U32 ping_delay = mesgsys->mCircuitInfo.getPingDelay(); - mLastInterpUpdateSecs = LLFrameTimer::getElapsedSeconds(); - mLastMessageUpdateSecs = mLastInterpUpdateSecs; - if (mDrawable.notNull()) - { - // Don't clear invisibility flag on update if still orphaned! - if (mDrawable->isState(LLDrawable::FORCE_INVISIBLE) && !mOrphaned) - { -// LL_DEBUGS() << "Clearing force invisible: " << mID << ":" << getPCodeString() << ":" << getPositionAgent() << LL_ENDL; - mDrawable->clearState(LLDrawable::FORCE_INVISIBLE); - gPipeline.markRebuild( mDrawable, LLDrawable::REBUILD_ALL); - } - } - - // Update special hover cursor status - bool special_hover_cursor = specialHoverCursor(); - if (old_special_hover_cursor != special_hover_cursor - && mDrawable.notNull()) - { - mDrawable->updateSpecialHoverCursor(special_hover_cursor); - } - - return retval; -} - -bool LLViewerObject::isActive() const -{ - return true; -} - -//load flags from cache or from message -void LLViewerObject::loadFlags(U32 flags) -{ - if(flags == (U32)(-1)) - { - return; //invalid - } - - // keep local flags and overwrite remote-controlled flags - mFlags = (mFlags & FLAGS_LOCAL) | flags; - - // ...new objects that should come in selected need to be added to the selected list - mCreateSelected = ((flags & FLAGS_CREATE_SELECTED) != 0); - return; -} - -void LLViewerObject::idleUpdate(LLAgent &agent, const F64 &frame_time) -{ - if (!mDead) - { - if (!mStatic && sVelocityInterpolate && !isSelected()) - { - // calculate dt from last update - F32 time_dilation = mRegionp ? mRegionp->getTimeDilation() : 1.0f; - F32 dt_raw = ((F64Seconds)frame_time - mLastInterpUpdateSecs).value(); - F32 dt = time_dilation * dt_raw; - - applyAngularVelocity(dt); - - if (isAttachment()) - { - mLastInterpUpdateSecs = (F64Seconds)frame_time; - return; - } - else - { // Move object based on it's velocity and rotation - interpolateLinearMotion(frame_time, dt); - } - } - - updateDrawable(false); - } -} - - -// Move an object due to idle-time viewer side updates by interpolating motion -void LLViewerObject::interpolateLinearMotion(const F64SecondsImplicit& frame_time, const F32SecondsImplicit& dt_seconds) -{ - // linear motion - // PHYSICS_TIMESTEP is used below to correct for the fact that the velocity in object - // updates represents the average velocity of the last timestep, rather than the final velocity. - // the time dilation above should guarantee that dt is never less than PHYSICS_TIMESTEP, theoretically - // - // *TODO: should also wrap linear accel/velocity in check - // to see if object is selected, instead of explicitly - // zeroing it out - - F32 dt = dt_seconds; - F64Seconds time_since_last_update = frame_time - mLastMessageUpdateSecs; - if (time_since_last_update <= (F64Seconds)0.0 || dt <= 0.f) - { - return; - } - - LLVector3 accel = getAcceleration(); - LLVector3 vel = getVelocity(); - - if (sMaxUpdateInterpolationTime <= (F64Seconds)0.0) - { // Old code path ... unbounded, simple interpolation - if (!(accel.isExactlyZero() && vel.isExactlyZero())) - { - LLVector3 pos = (vel + (0.5f * (dt-PHYSICS_TIMESTEP)) * accel) * dt; - - // region local - setPositionRegion(pos + getPositionRegion()); - setVelocity(vel + accel*dt); - - // for objects that are spinning but not translating, make sure to flag them as having moved - setChanged(MOVED | SILHOUETTE); - } - } - else if (!accel.isExactlyZero() || !vel.isExactlyZero()) // object is moving - { // Object is moving, and hasn't been too long since we got an update from the server - - // Calculate predicted position and velocity - LLVector3 new_pos = (vel + (0.5f * (dt-PHYSICS_TIMESTEP)) * accel) * dt; - LLVector3 new_v = accel * dt; - - if (time_since_last_update > sPhaseOutUpdateInterpolationTime && - sPhaseOutUpdateInterpolationTime > (F64Seconds)0.0) - { // Haven't seen a viewer update in a while, check to see if the circuit is still active - if (mRegionp) - { // The simulator will NOT send updates if the object continues normally on the path - // predicted by the velocity and the acceleration (often gravity) sent to the viewer - // So check to see if the circuit is blocked, which means the sim is likely in a long lag - LLCircuitData *cdp = gMessageSystem->mCircuitInfo.findCircuit( mRegionp->getHost() ); - if (cdp) - { - // Find out how many seconds since last packet arrived on the circuit - F64Seconds time_since_last_packet = LLMessageSystem::getMessageTimeSeconds() - cdp->getLastPacketInTime(); - - if (!cdp->isAlive() || // Circuit is dead or blocked - cdp->isBlocked() || // or doesn't seem to be getting any packets - (time_since_last_packet > sPhaseOutUpdateInterpolationTime)) - { - // Start to reduce motion interpolation since we haven't seen a server update in a while - F64Seconds time_since_last_interpolation = frame_time - mLastInterpUpdateSecs; - F64 phase_out = 1.0; - if (time_since_last_update > sMaxUpdateInterpolationTime) - { // Past the time limit, so stop the object - phase_out = 0.0; - //LL_INFOS() << "Motion phase out to zero" << LL_ENDL; - - // Kill angular motion as well. Note - not adding this due to paranoia - // about stopping rotation for llTargetOmega objects and not having it restart - // setAngularVelocity(LLVector3::zero); - } - else if (mLastInterpUpdateSecs - mLastMessageUpdateSecs > sPhaseOutUpdateInterpolationTime) - { // Last update was already phased out a bit - phase_out = (sMaxUpdateInterpolationTime - time_since_last_update) / - (sMaxUpdateInterpolationTime - time_since_last_interpolation); - //LL_INFOS() << "Continuing motion phase out of " << (F32) phase_out << LL_ENDL; - } - else - { // Phase out from full value - phase_out = (sMaxUpdateInterpolationTime - time_since_last_update) / - (sMaxUpdateInterpolationTime - sPhaseOutUpdateInterpolationTime); - //LL_INFOS() << "Starting motion phase out of " << (F32) phase_out << LL_ENDL; - } - phase_out = llclamp(phase_out, 0.0, 1.0); - - new_pos = new_pos * ((F32) phase_out); - new_v = new_v * ((F32) phase_out); - } - } - } - } - - new_pos = new_pos + getPositionRegion(); - new_v = new_v + vel; - - - // Clamp interpolated position to minimum underground and maximum region height - LLVector3d new_pos_global = mRegionp->getPosGlobalFromRegion(new_pos); - F32 min_height; - if (isAvatar()) - { // Make a better guess about AVs not going underground - min_height = LLWorld::getInstance()->resolveLandHeightGlobal(new_pos_global); - min_height += (0.5f * getScale().mV[VZ]); - } - else - { // This will put the object underground, but we can't tell if it will stop - // at ground level or not - min_height = LLWorld::getInstance()->getMinAllowedZ(this, new_pos_global); - // Cap maximum height - new_pos.mV[VZ] = llmin(LLWorld::getInstance()->getRegionMaxHeight(), new_pos.mV[VZ]); - } - - new_pos.mV[VZ] = llmax(min_height, new_pos.mV[VZ]); - - // Check to see if it's going off the region - LLVector3 temp(new_pos.mV[VX], new_pos.mV[VY], 0.f); - if (temp.clamp(0.f, mRegionp->getWidth())) - { // Going off this region, so see if we might end up on another region - LLVector3d old_pos_global = mRegionp->getPosGlobalFromRegion(getPositionRegion()); - new_pos_global = mRegionp->getPosGlobalFromRegion(new_pos); // Re-fetch in case it got clipped above - - // Clip the positions to known regions - LLVector3d clip_pos_global = LLWorld::getInstance()->clipToVisibleRegions(old_pos_global, new_pos_global); - if (clip_pos_global != new_pos_global) - { - // Was clipped, so this means we hit a edge where there is no region to enter - LLVector3 clip_pos = mRegionp->getPosRegionFromGlobal(clip_pos_global); - LL_DEBUGS("Interpolate") << "Hit empty region edge, clipped predicted position to " - << clip_pos - << " from " << new_pos << LL_ENDL; - new_pos = clip_pos; - - // Stop motion and get server update for bouncing on the edge - new_v.clear(); - setAcceleration(LLVector3::zero); - } - else - { - // Check for how long we are crossing. - // Note: theoretically we can find time from velocity, acceleration and - // distance from border to new position, but it is not going to work - // if 'phase_out' activates - if (mRegionCrossExpire == 0) - { - // Workaround: we can't accurately figure out time when we cross border - // so just write down time 'after the fact', it is far from optimal in - // case of lags, but for lags sMaxUpdateInterpolationTime will kick in first - LL_DEBUGS("Interpolate") << "Predicted region crossing, new position " << new_pos << LL_ENDL; - mRegionCrossExpire = frame_time + sMaxRegionCrossingInterpolationTime; - } - else if (frame_time > mRegionCrossExpire) - { - // Predicting crossing over 1s, stop motion - // Stop motion - LL_DEBUGS("Interpolate") << "Predicting region crossing for too long, stopping at " << new_pos << LL_ENDL; - new_v.clear(); - setAcceleration(LLVector3::zero); - mRegionCrossExpire = 0; - } - } - } - else - { - mRegionCrossExpire = 0; - } - - // Set new position and velocity - setPositionRegion(new_pos); - setVelocity(new_v); - - // for objects that are spinning but not translating, make sure to flag them as having moved - setChanged(MOVED | SILHOUETTE); - } - - // Update the last time we did anything - mLastInterpUpdateSecs = frame_time; -} - - - -// delete an item in the inventory, but don't tell the server. This is -// used internally by remove, update, and savescript. -// This will only delete the first item with an item_id in the list -void LLViewerObject::deleteInventoryItem(const LLUUID& item_id) -{ - if(mInventory) - { - LLInventoryObject::object_list_t::iterator it = mInventory->begin(); - LLInventoryObject::object_list_t::iterator end = mInventory->end(); - for( ; it != end; ++it ) - { - if((*it)->getUUID() == item_id) - { - // This is safe only because we return immediatly. - mInventory->erase(it); // will deref and delete it - return; - } - } - doInventoryCallback(); - } -} - -void LLViewerObject::doUpdateInventory( - LLPointer& item, - U8 key, - bool is_new) -{ - LLViewerInventoryItem* old_item = NULL; - if(TASK_INVENTORY_ITEM_KEY == key) - { - old_item = (LLViewerInventoryItem*)getInventoryObject(item->getUUID()); - } - else if(TASK_INVENTORY_ASSET_KEY == key) - { - old_item = getInventoryItemByAsset(item->getAssetUUID()); - } - LLUUID item_id; - LLUUID new_owner; - LLUUID new_group; - bool group_owned = false; - if(old_item) - { - item_id = old_item->getUUID(); - new_owner = old_item->getPermissions().getOwner(); - new_group = old_item->getPermissions().getGroup(); - group_owned = old_item->getPermissions().isGroupOwned(); - old_item = NULL; - } - else - { - item_id = item->getUUID(); - } - if(!is_new && mInventory) - { - // Attempt to update the local inventory. If we can get the - // object perm, we have perfect visibility, so we want the - // serial number to match. Otherwise, take our best guess and - // make sure that the serial number does not match. - deleteInventoryItem(item_id); - LLPermissions perm(item->getPermissions()); - LLPermissions* obj_perm = LLSelectMgr::getInstance()->findObjectPermissions(this); - bool is_atomic = (S32)LLAssetType::AT_OBJECT != item->getType(); - if(obj_perm) - { - perm.setOwnerAndGroup(LLUUID::null, obj_perm->getOwner(), obj_perm->getGroup(), is_atomic); - } - else - { - if(group_owned) - { - perm.setOwnerAndGroup(LLUUID::null, new_owner, new_group, is_atomic); - } - else if(!new_owner.isNull()) - { - // The object used to be in inventory, so we can - // assume the owner and group will match what they are - // there. - perm.setOwnerAndGroup(LLUUID::null, new_owner, new_group, is_atomic); - } - // *FIX: can make an even better guess by using the mPermGroup flags - else if(permYouOwner()) - { - // best guess. - perm.setOwnerAndGroup(LLUUID::null, gAgent.getID(), item->getPermissions().getGroup(), is_atomic); - --mExpectedInventorySerialNum; - } - else - { - // dummy it up. - perm.setOwnerAndGroup(LLUUID::null, LLUUID::null, LLUUID::null, is_atomic); - --mExpectedInventorySerialNum; - } - } - LLViewerInventoryItem* oldItem = item; - LLViewerInventoryItem* new_item = new LLViewerInventoryItem(oldItem); - new_item->setPermissions(perm); - mInventory->push_front(new_item); - doInventoryCallback(); - ++mExpectedInventorySerialNum; - } - else if (is_new) - { - ++mExpectedInventorySerialNum; - } -} - -// save a script, which involves removing the old one, and rezzing -// in the new one. This method should be called with the asset id -// of the new and old script AFTER the bytecode has been saved. -void LLViewerObject::saveScript( - const LLViewerInventoryItem* item, - bool active, - bool is_new) -{ - /* - * XXXPAM Investigate not making this copy. Seems unecessary, but I'm unsure about the - * interaction with doUpdateInventory() called below. - */ - LL_DEBUGS() << "LLViewerObject::saveScript() " << item->getUUID() << " " << item->getAssetUUID() << LL_ENDL; - - LLPointer task_item = - new LLViewerInventoryItem(item->getUUID(), mID, item->getPermissions(), - item->getAssetUUID(), item->getType(), - item->getInventoryType(), - item->getName(), item->getDescription(), - item->getSaleInfo(), item->getFlags(), - item->getCreationDate()); - task_item->setTransactionID(item->getTransactionID()); - - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_RezScript); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addUUIDFast(_PREHASH_GroupID, gAgent.getGroupID()); - msg->nextBlockFast(_PREHASH_UpdateBlock); - msg->addU32Fast(_PREHASH_ObjectLocalID, (mLocalID)); - U8 enabled = active; - msg->addBOOLFast(_PREHASH_Enabled, enabled); - msg->nextBlockFast(_PREHASH_InventoryBlock); - task_item->packMessage(msg); - msg->sendReliable(mRegionp->getHost()); - - // do the internal logic - doUpdateInventory(task_item, TASK_INVENTORY_ITEM_KEY, is_new); -} - -void LLViewerObject::moveInventory(const LLUUID& folder_id, - const LLUUID& item_id) -{ - LL_DEBUGS() << "LLViewerObject::moveInventory " << item_id << LL_ENDL; - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_MoveTaskInventory); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addUUIDFast(_PREHASH_FolderID, folder_id); - msg->nextBlockFast(_PREHASH_InventoryData); - msg->addU32Fast(_PREHASH_LocalID, mLocalID); - msg->addUUIDFast(_PREHASH_ItemID, item_id); - msg->sendReliable(mRegionp->getHost()); - - LLInventoryObject* inv_obj = getInventoryObject(item_id); - if(inv_obj) - { - LLViewerInventoryItem* item = (LLViewerInventoryItem*)inv_obj; - if(!item->getPermissions().allowCopyBy(gAgent.getID())) - { - deleteInventoryItem(item_id); - ++mExpectedInventorySerialNum; - } - } -} - -void LLViewerObject::dirtyInventory() -{ - // If there aren't any LLVOInventoryListeners, we won't be - // able to update our mInventory when it comes back from the - // simulator, so we should not clear the inventory either. - if(mInventory && !mInventoryCallbacks.empty()) - { - mInventory->clear(); // will deref and delete entries - delete mInventory; - mInventory = NULL; - } - mInventoryDirty = true; -} - -void LLViewerObject::registerInventoryListener(LLVOInventoryListener* listener, void* user_data) -{ - LLInventoryCallbackInfo* info = new LLInventoryCallbackInfo; - info->mListener = listener; - info->mInventoryData = user_data; - mInventoryCallbacks.push_front(info); -} - -void LLViewerObject::removeInventoryListener(LLVOInventoryListener* listener) -{ - if (listener == NULL) - return; - for (callback_list_t::iterator iter = mInventoryCallbacks.begin(); - iter != mInventoryCallbacks.end(); ) - { - callback_list_t::iterator curiter = iter++; - LLInventoryCallbackInfo* info = *curiter; - if (info->mListener == listener) - { - delete info; - mInventoryCallbacks.erase(curiter); - break; - } - } -} - -bool LLViewerObject::isInventoryPending() -{ - return mInvRequestState != INVENTORY_REQUEST_STOPPED; -} - -void LLViewerObject::clearInventoryListeners() -{ - for_each(mInventoryCallbacks.begin(), mInventoryCallbacks.end(), DeletePointer()); - mInventoryCallbacks.clear(); -} - -bool LLViewerObject::hasInventoryListeners() -{ - return !mInventoryCallbacks.empty(); -} - -void LLViewerObject::requestInventory() -{ - if(mInventoryDirty && mInventory && !mInventoryCallbacks.empty()) - { - mInventory->clear(); // will deref and delete entries - delete mInventory; - mInventory = NULL; - } - - if(mInventory) - { - // inventory is either up to date or doesn't has a listener - // if it is dirty, leave it this way in case we gain a listener - doInventoryCallback(); - } - else - { - // since we are going to request it now - mInventoryDirty = false; - - // Note: throws away duplicate requests - fetchInventoryFromServer(); - } -} - -void LLViewerObject::fetchInventoryFromServer() -{ - if (!isInventoryPending()) - { - delete mInventory; - mInventory = NULL; - - // Results in processTaskInv - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_RequestTaskInventory); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_InventoryData); - msg->addU32Fast(_PREHASH_LocalID, mLocalID); - msg->sendReliable(mRegionp->getHost()); - - // This will get reset by doInventoryCallback or processTaskInv - mInvRequestState = INVENTORY_REQUEST_PENDING; - } -} - -void LLViewerObject::fetchInventoryDelayed(const F64 &time_seconds) -{ - // unless already waiting, drop previous request and shedule an update - if (mInvRequestState != INVENTORY_REQUEST_WAIT) - { - if (mInvRequestXFerId != 0) - { - // abort download. - gXferManager->abortRequestById(mInvRequestXFerId, -1); - mInvRequestXFerId = 0; - } - mInvRequestState = INVENTORY_REQUEST_WAIT; // affects isInventoryPending() - LLCoros::instance().launch("LLViewerObject::fetchInventoryDelayedCoro()", - boost::bind(&LLViewerObject::fetchInventoryDelayedCoro, mID, time_seconds)); - } -} - -//static -void LLViewerObject::fetchInventoryDelayedCoro(const LLUUID task_inv, const F64 time_seconds) -{ - llcoro::suspendUntilTimeout(time_seconds); - LLViewerObject *obj = gObjectList.findObject(task_inv); - if (obj) - { - // Might be good idea to prolong delay here in case expected serial changed. - // As it is, it will get a response with obsolete serial and will delay again. - - // drop waiting state to unlock isInventoryPending() - obj->mInvRequestState = INVENTORY_REQUEST_STOPPED; - obj->fetchInventoryFromServer(); - } -} - -LLControlAvatar *LLViewerObject::getControlAvatar() -{ - return getRootEdit()->mControlAvatar.get(); -} - -LLControlAvatar *LLViewerObject::getControlAvatar() const -{ - return getRootEdit()->mControlAvatar.get(); -} - -// Manage the control avatar state of a given object. -// Any object can be flagged as animated, but for performance reasons -// we don't want to incur the overhead of managing a control avatar -// unless this would have some user-visible consequence. That is, -// there should be at least one rigged mesh in the linkset. Operations -// that change the state of a linkset, such as linking or unlinking -// prims, can also mean that a control avatar needs to be added or -// removed. At the end, if there is a control avatar, we make sure -// that its animation state is current. -void LLViewerObject::updateControlAvatar() -{ - LLViewerObject *root = getRootEdit(); - bool is_animated_object = root->isAnimatedObject(); - bool has_control_avatar = getControlAvatar(); - if (!is_animated_object && !has_control_avatar) - { - return; - } - - // caller isn't supposed to operate on a dead object, - // avatar was already cleaned up - llassert(!isDead()); - - bool should_have_control_avatar = false; - if (is_animated_object) - { - bool any_rigged_mesh = root->isRiggedMesh(); - LLViewerObject::const_child_list_t& child_list = root->getChildren(); - for (LLViewerObject::const_child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); ++iter) - { - const LLViewerObject* child = *iter; - any_rigged_mesh = any_rigged_mesh || child->isRiggedMesh(); - } - should_have_control_avatar = is_animated_object && any_rigged_mesh; - } - - if (should_have_control_avatar && !has_control_avatar) - { - std::string vobj_name = llformat("Vol%p", root); - LL_DEBUGS("AnimatedObjects") << vobj_name << " calling linkControlAvatar()" << LL_ENDL; - root->linkControlAvatar(); - } - if (!should_have_control_avatar && has_control_avatar) - { - std::string vobj_name = llformat("Vol%p", root); - LL_DEBUGS("AnimatedObjects") << vobj_name << " calling unlinkControlAvatar()" << LL_ENDL; - root->unlinkControlAvatar(); - } - if (getControlAvatar()) - { - getControlAvatar()->updateAnimations(); - if (isSelected()) - { - LLSelectMgr::getInstance()->pauseAssociatedAvatars(); - } - } -} - -void LLViewerObject::linkControlAvatar() -{ - if (!getControlAvatar() && isRootEdit()) - { - LLVOVolume *volp = dynamic_cast(this); - if (!volp) - { - LL_WARNS() << "called with null or non-volume object" << LL_ENDL; - return; - } - mControlAvatar = LLControlAvatar::createControlAvatar(volp); - LL_DEBUGS("AnimatedObjects") << volp->getID() - << " created control av for " - << (S32) (1+volp->numChildren()) << " prims" << LL_ENDL; - } - LLControlAvatar *cav = getControlAvatar(); - if (cav) - { - cav->updateAttachmentOverrides(); - if (!cav->mPlaying) - { - cav->mPlaying = true; - //if (!cav->mRootVolp->isAnySelected()) - { - cav->updateVolumeGeom(); - cav->mRootVolp->recursiveMarkForUpdate(); - } - } - } - else - { - LL_WARNS() << "no control avatar found!" << LL_ENDL; - } -} - -void LLViewerObject::unlinkControlAvatar() -{ - if (getControlAvatar()) - { - getControlAvatar()->updateAttachmentOverrides(); - } - if (isRootEdit()) - { - // This will remove the entire linkset from the control avatar - if (mControlAvatar) - { - mControlAvatar->markForDeath(); - mControlAvatar = NULL; - } - } - // For non-root prims, removing from the linkset will - // automatically remove the control avatar connection. -} - -// virtual -bool LLViewerObject::isAnimatedObject() const -{ - return false; -} - -struct LLFilenameAndTask -{ - LLUUID mTaskID; - std::string mFilename; - - // for sequencing in case of multiple updates - S16 mSerial; -#ifdef _DEBUG - static S32 sCount; - LLFilenameAndTask() - { - ++sCount; - LL_DEBUGS() << "Constructing LLFilenameAndTask: " << sCount << LL_ENDL; - } - ~LLFilenameAndTask() - { - --sCount; - LL_DEBUGS() << "Destroying LLFilenameAndTask: " << sCount << LL_ENDL; - } -private: - LLFilenameAndTask(const LLFilenameAndTask& rhs); - const LLFilenameAndTask& operator=(const LLFilenameAndTask& rhs) const; -#endif -}; - -#ifdef _DEBUG -S32 LLFilenameAndTask::sCount = 0; -#endif - -// static -void LLViewerObject::processTaskInv(LLMessageSystem* msg, void** user_data) -{ - LLUUID task_id; - msg->getUUIDFast(_PREHASH_InventoryData, _PREHASH_TaskID, task_id); - LLViewerObject* object = gObjectList.findObject(task_id); - if (!object) - { - LL_WARNS() << "LLViewerObject::processTaskInv object " - << task_id << " does not exist." << LL_ENDL; - return; - } - - // we can receive multiple task updates simultaneously, make sure we will not rewrite newer with older update - S16 serial = 0; - msg->getS16Fast(_PREHASH_InventoryData, _PREHASH_Serial, serial); - - if (serial == object->mInventorySerialNum - && serial < object->mExpectedInventorySerialNum) - { - // Loop Protection. - // We received same serial twice. - // Viewer did some changes to inventory that couldn't be saved server side - // or something went wrong to cause serial to be out of sync. - // Drop xfer and restart after some time, assign server's value as expected - LL_WARNS() << "Task inventory serial might be out of sync, server serial: " << serial << " client expected serial: " << object->mExpectedInventorySerialNum << LL_ENDL; - object->mExpectedInventorySerialNum = serial; - object->fetchInventoryDelayed(INVENTORY_UPDATE_WAIT_TIME_DESYNC); - } - else if (serial < object->mExpectedInventorySerialNum) - { - // Out of date message, record to current serial for loop protection, but do not load it - // Drop xfer and restart after some time - if (serial < object->mInventorySerialNum) - { - LL_WARNS() << "Task serial decreased. Potentially out of order packet or desync." << LL_ENDL; - } - object->mInventorySerialNum = serial; - object->fetchInventoryDelayed(INVENTORY_UPDATE_WAIT_TIME_OUTDATED); - } - else if (serial >= object->mExpectedInventorySerialNum) - { - LLFilenameAndTask* ft = new LLFilenameAndTask; - ft->mTaskID = task_id; - ft->mSerial = serial; - - // We received version we expected or newer. Load it. - object->mInventorySerialNum = ft->mSerial; - object->mExpectedInventorySerialNum = ft->mSerial; - - std::string unclean_filename; - msg->getStringFast(_PREHASH_InventoryData, _PREHASH_Filename, unclean_filename); - ft->mFilename = LLDir::getScrubbedFileName(unclean_filename); - - if (ft->mFilename.empty()) - { - LL_DEBUGS() << "Task has no inventory" << LL_ENDL; - // mock up some inventory to make a drop target. - if (object->mInventory) - { - object->mInventory->clear(); // will deref and delete it - } - else - { - object->mInventory = new LLInventoryObject::object_list_t(); - } - LLPointer obj; - obj = new LLInventoryObject(object->mID, LLUUID::null, - LLAssetType::AT_CATEGORY, - "Contents"); - object->mInventory->push_front(obj); - object->doInventoryCallback(); - delete ft; - return; - } - U64 new_id = gXferManager->requestFile(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ft->mFilename), - ft->mFilename, LL_PATH_CACHE, - object->mRegionp->getHost(), - true, - &LLViewerObject::processTaskInvFile, - (void**)ft, // This takes ownership of ft - LLXferManager::HIGH_PRIORITY); - if (object->mInvRequestState == INVENTORY_XFER) - { - if (new_id > 0 && new_id != object->mInvRequestXFerId) - { - // we started new download. - gXferManager->abortRequestById(object->mInvRequestXFerId, -1); - object->mInvRequestXFerId = new_id; - } - } - else - { - object->mInvRequestState = INVENTORY_XFER; - object->mInvRequestXFerId = new_id; - } - } -} - -void LLViewerObject::processTaskInvFile(void** user_data, S32 error_code, LLExtStat ext_status) -{ - LLFilenameAndTask* ft = (LLFilenameAndTask*)user_data; - LLViewerObject* object = NULL; - - if (ft - && (0 == error_code) - && (object = gObjectList.findObject(ft->mTaskID)) - && ft->mSerial >= object->mInventorySerialNum) - { - object->mInventorySerialNum = ft->mSerial; - LL_DEBUGS() << "Receiving inventory task file for serial " << object->mInventorySerialNum << " taskid: " << ft->mTaskID << LL_ENDL; - if (ft->mSerial < object->mExpectedInventorySerialNum) - { - // User managed to change something while inventory was loading - LL_DEBUGS() << "Processing file that is potentially out of date for task: " << ft->mTaskID << LL_ENDL; - } - - if (object->loadTaskInvFile(ft->mFilename)) - { - - LLInventoryObject::object_list_t::iterator it = object->mInventory->begin(); - LLInventoryObject::object_list_t::iterator end = object->mInventory->end(); - std::list& pending_lst = object->mPendingInventoryItemsIDs; - - for (; it != end && pending_lst.size(); ++it) - { - LLViewerInventoryItem* item = dynamic_cast(it->get()); - if(item && item->getType() != LLAssetType::AT_CATEGORY) - { - std::list::iterator id_it = std::find(pending_lst.begin(), pending_lst.begin(), item->getAssetUUID()); - if (id_it != pending_lst.end()) - { - pending_lst.erase(id_it); - } - } - } - } - else - { - // MAINT-2597 - crash when trying to edit a no-mod object - // Somehow get an contents inventory response, but with an invalid stream (possibly 0 size?) - // Stated repro was specific to no-mod objects so failing without user interaction should be safe. - LL_WARNS() << "Trying to load invalid task inventory file. Ignoring file contents." << LL_ENDL; - } - } - else - { - // This Occurs When two requests were made, and the first one - // has already handled it. - LL_DEBUGS() << "Problem loading task inventory. Return code: " - << error_code << LL_ENDL; - } - delete ft; -} - -bool LLViewerObject::loadTaskInvFile(const std::string& filename) -{ - std::string filename_and_local_path = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, filename); - llifstream ifs(filename_and_local_path.c_str()); - if(ifs.good()) - { - U32 fail_count = 0; - char buffer[MAX_STRING]; /* Flawfinder: ignore */ - // *NOTE: This buffer size is hard coded into scanf() below. - char keyword[MAX_STRING]; /* Flawfinder: ignore */ - if(mInventory) - { - mInventory->clear(); // will deref and delete it - } - else - { - mInventory = new LLInventoryObject::object_list_t; - } - while(ifs.good()) - { - ifs.getline(buffer, MAX_STRING); - if (sscanf(buffer, " %254s", keyword) == EOF) /* Flawfinder: ignore */ - { - // Blank file? - LL_WARNS() << "Issue reading from file '" - << filename << "'" << LL_ENDL; - break; - } - else if(0 == strcmp("inv_item", keyword)) - { - LLPointer inv = new LLViewerInventoryItem; - inv->importLegacyStream(ifs); - mInventory->push_front(inv); - } - else if(0 == strcmp("inv_object", keyword)) - { - LLPointer inv = new LLInventoryObject; - inv->importLegacyStream(ifs); - inv->rename("Contents"); - mInventory->push_front(inv); - } - else if (fail_count >= MAX_INV_FILE_READ_FAILS) - { - LL_WARNS() << "Encountered too many unknowns while reading from file: '" - << filename << "'" << LL_ENDL; - break; - } - else - { - // Is there really a point to continue processing? We already failing to display full inventory - fail_count++; - LL_WARNS_ONCE() << "Unknown token while reading from inventory file. Token: '" - << keyword << "'" << LL_ENDL; - } - } - ifs.close(); - LLFile::remove(filename_and_local_path); - } - else - { - LL_WARNS() << "unable to load task inventory: " << filename_and_local_path - << LL_ENDL; - return false; - } - doInventoryCallback(); - - return true; -} - -void LLViewerObject::doInventoryCallback() -{ - for (callback_list_t::iterator iter = mInventoryCallbacks.begin(); - iter != mInventoryCallbacks.end(); ) - { - callback_list_t::iterator curiter = iter++; - LLInventoryCallbackInfo* info = *curiter; - if (info->mListener != NULL) - { - info->mListener->inventoryChanged(this, - mInventory, - mInventorySerialNum, - info->mInventoryData); - } - else - { - LL_INFOS() << "LLViewerObject::doInventoryCallback() deleting bad listener entry." << LL_ENDL; - delete info; - mInventoryCallbacks.erase(curiter); - } - } - - // release inventory loading state - mInvRequestXFerId = 0; - mInvRequestState = INVENTORY_REQUEST_STOPPED; -} - -void LLViewerObject::removeInventory(const LLUUID& item_id) -{ - // close associated floater properties - LLSD params; - params["id"] = item_id; - params["object"] = mID; - LLFloaterReg::hideInstance("item_properties", params); - - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_RemoveTaskInventory); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_InventoryData); - msg->addU32Fast(_PREHASH_LocalID, mLocalID); - msg->addUUIDFast(_PREHASH_ItemID, item_id); - msg->sendReliable(mRegionp->getHost()); - deleteInventoryItem(item_id); - ++mExpectedInventorySerialNum; -} - -bool LLViewerObject::isAssetInInventory(LLViewerInventoryItem* item, LLAssetType::EType type) -{ - bool result = false; - - if (item) - { - // For now mPendingInventoryItemsIDs only stores textures and materials - // but if it gets to store more types, it will need to verify type as well - // since null can be a shared default id and it is fine to need a null - // script and a null material simultaneously. - std::list::iterator begin = mPendingInventoryItemsIDs.begin(); - std::list::iterator end = mPendingInventoryItemsIDs.end(); - - bool is_fetching = std::find(begin, end, item->getAssetUUID()) != end; - - // null is the default asset for materials and default for scripts - // so need to check type as well - bool is_fetched = getInventoryItemByAsset(item->getAssetUUID(), type) != NULL; - - result = is_fetched || is_fetching; - } - - return result; -} - -void LLViewerObject::updateMaterialInventory(LLViewerInventoryItem* item, U8 key, bool is_new) -{ - if (!item) - { - return; - } - if (LLAssetType::AT_TEXTURE != item->getType() - && LLAssetType::AT_MATERIAL != item->getType()) - { - // Not supported - return; - } - - if (isAssetInInventory(item, item->getType())) - { - // already there - return; - } - - mPendingInventoryItemsIDs.push_back(item->getAssetUUID()); - updateInventory(item, key, is_new); -} - -void LLViewerObject::updateInventory( - LLViewerInventoryItem* item, - U8 key, - bool is_new) -{ - // This slices the object into what we're concerned about on the - // viewer. The simulator will take the permissions and transfer - // ownership. - LLPointer task_item = - new LLViewerInventoryItem(item->getUUID(), mID, item->getPermissions(), - item->getAssetUUID(), item->getType(), - item->getInventoryType(), - item->getName(), item->getDescription(), - item->getSaleInfo(), - item->getFlags(), - item->getCreationDate()); - task_item->setTransactionID(item->getTransactionID()); - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_UpdateTaskInventory); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_UpdateData); - msg->addU32Fast(_PREHASH_LocalID, mLocalID); - msg->addU8Fast(_PREHASH_Key, key); - msg->nextBlockFast(_PREHASH_InventoryData); - task_item->packMessage(msg); - msg->sendReliable(mRegionp->getHost()); - - // do the internal logic - doUpdateInventory(task_item, key, is_new); -} - -void LLViewerObject::updateInventoryLocal(LLInventoryItem* item, U8 key) -{ - LLPointer task_item = - new LLViewerInventoryItem(item->getUUID(), mID, item->getPermissions(), - item->getAssetUUID(), item->getType(), - item->getInventoryType(), - item->getName(), item->getDescription(), - item->getSaleInfo(), item->getFlags(), - item->getCreationDate()); - - // do the internal logic - const bool is_new = false; - doUpdateInventory(task_item, key, is_new); -} - -LLInventoryObject* LLViewerObject::getInventoryObject(const LLUUID& item_id) -{ - LLInventoryObject* rv = NULL; - if(mInventory) - { - LLInventoryObject::object_list_t::iterator it = mInventory->begin(); - LLInventoryObject::object_list_t::iterator end = mInventory->end(); - for ( ; it != end; ++it) - { - if((*it)->getUUID() == item_id) - { - rv = *it; - break; - } - } - } - return rv; -} - -LLInventoryItem* LLViewerObject::getInventoryItem(const LLUUID& item_id) -{ - LLInventoryObject* iobj = getInventoryObject(item_id); - if (!iobj || iobj->getType() == LLAssetType::AT_CATEGORY) - { - return NULL; - } - LLInventoryItem* item = dynamic_cast(iobj); - return item; -} - -void LLViewerObject::getInventoryContents(LLInventoryObject::object_list_t& objects) -{ - if(mInventory) - { - LLInventoryObject::object_list_t::iterator it = mInventory->begin(); - LLInventoryObject::object_list_t::iterator end = mInventory->end(); - for( ; it != end; ++it) - { - if ((*it)->getType() != LLAssetType::AT_CATEGORY) - { - objects.push_back(*it); - } - } - } -} - -LLInventoryObject* LLViewerObject::getInventoryRoot() -{ - if (!mInventory || !mInventory->size()) - { - return NULL; - } - return mInventory->back(); -} - -LLViewerInventoryItem* LLViewerObject::getInventoryItemByAsset(const LLUUID& asset_id) -{ - if (mInventoryDirty) - LL_WARNS() << "Peforming inventory lookup for object " << mID << " that has dirty inventory!" << LL_ENDL; - - LLViewerInventoryItem* rv = NULL; - if(mInventory) - { - LLViewerInventoryItem* item = NULL; - - LLInventoryObject::object_list_t::iterator it = mInventory->begin(); - LLInventoryObject::object_list_t::iterator end = mInventory->end(); - for( ; it != end; ++it) - { - LLInventoryObject* obj = *it; - if(obj->getType() != LLAssetType::AT_CATEGORY) - { - // *FIX: gank-ass down cast! - item = (LLViewerInventoryItem*)obj; - if(item->getAssetUUID() == asset_id) - { - rv = item; - break; - } - } - } - } - return rv; -} - -LLViewerInventoryItem* LLViewerObject::getInventoryItemByAsset(const LLUUID& asset_id, LLAssetType::EType type) -{ - if (mInventoryDirty) - LL_WARNS() << "Peforming inventory lookup for object " << mID << " that has dirty inventory!" << LL_ENDL; - - LLViewerInventoryItem* rv = NULL; - if (type == LLAssetType::AT_CATEGORY) - { - // Whatever called this shouldn't be trying to get a folder by asset - // categories don't have assets - llassert(0); - return rv; - } - - if (mInventory) - { - LLViewerInventoryItem* item = NULL; - - LLInventoryObject::object_list_t::iterator it = mInventory->begin(); - LLInventoryObject::object_list_t::iterator end = mInventory->end(); - for (; it != end; ++it) - { - LLInventoryObject* obj = *it; - if (obj->getType() == type) - { - // *FIX: gank-ass down cast! - item = (LLViewerInventoryItem*)obj; - if (item->getAssetUUID() == asset_id) - { - rv = item; - break; - } - } - } - } - return rv; -} - -void LLViewerObject::updateViewerInventoryAsset( - const LLViewerInventoryItem* item, - const LLUUID& new_asset) -{ - LLPointer task_item = - new LLViewerInventoryItem(item); - task_item->setAssetUUID(new_asset); - - // do the internal logic - doUpdateInventory(task_item, TASK_INVENTORY_ITEM_KEY, false); -} - -void LLViewerObject::setPixelAreaAndAngle(LLAgent &agent) -{ - if (getVolume()) - { //volumes calculate pixel area and angle per face - return; - } - - LLVector3 viewer_pos_agent = gAgentCamera.getCameraPositionAgent(); - LLVector3 pos_agent = getRenderPosition(); - - F32 dx = viewer_pos_agent.mV[VX] - pos_agent.mV[VX]; - F32 dy = viewer_pos_agent.mV[VY] - pos_agent.mV[VY]; - F32 dz = viewer_pos_agent.mV[VZ] - pos_agent.mV[VZ]; - - F32 max_scale = getMaxScale(); - F32 mid_scale = getMidScale(); - F32 min_scale = getMinScale(); - - // IW: estimate - when close to large objects, computing range based on distance from center is no good - // to try to get a min distance from face, subtract min_scale/2 from the range. - // This means we'll load too much detail sometimes, but that's better than not enough - // I don't think there's a better way to do this without calculating distance per-poly - F32 range = sqrt(dx*dx + dy*dy + dz*dz) - min_scale/2; - - LLViewerCamera* camera = LLViewerCamera::getInstance(); - if (range < 0.001f || isHUDAttachment()) // range == zero - { - mAppAngle = 180.f; - mPixelArea = (F32)camera->getScreenPixelArea(); - } - else - { - mAppAngle = (F32) atan2( max_scale, range) * RAD_TO_DEG; - - F32 pixels_per_meter = camera->getPixelMeterRatio() / range; - - mPixelArea = (pixels_per_meter * max_scale) * (pixels_per_meter * mid_scale); - if (mPixelArea > camera->getScreenPixelArea()) - { - mAppAngle = 180.f; - mPixelArea = (F32)camera->getScreenPixelArea(); - } - } -} - -bool LLViewerObject::updateLOD() -{ - return false; -} - -bool LLViewerObject::updateGeometry(LLDrawable *drawable) -{ - return false; -} - -void LLViewerObject::updateGL() -{ - -} - -void LLViewerObject::updateFaceSize(S32 idx) -{ - -} - -LLDrawable* LLViewerObject::createDrawable(LLPipeline *pipeline) -{ - return NULL; -} - -void LLViewerObject::setScale(const LLVector3 &scale, bool damped) -{ - LLPrimitive::setScale(scale); - if (mDrawable.notNull()) - { - //encompass completely sheared objects by taking - //the most extreme point possible (<1,1,0.5>) - mDrawable->setRadius(LLVector3(1,1,0.5f).scaleVec(scale).magVec()); - updateDrawable(damped); - } - - if( (LL_PCODE_VOLUME == getPCode()) && !isDead() ) - { - if (permYouOwner() || (scale.magVecSquared() > (7.5f * 7.5f)) ) - { - if (!mOnMap) - { - llassert_always(LLWorld::getInstance()->getRegionFromHandle(getRegion()->getHandle())); - - gObjectList.addToMap(this); - mOnMap = true; - } - } - else - { - if (mOnMap) - { - gObjectList.removeFromMap(this); - mOnMap = false; - } - } - } -} - -void LLViewerObject::setObjectCostStale() -{ - mCostStale = true; - // *NOTE: This is harmlessly redundant for Blinn-Phong material updates, as - // the root prim currently gets set stale anyway due to other property - // updates. But it is needed for GLTF material ID updates. - // -Cosmic,2023-06-27 - getRootEdit()->mCostStale = true; -} - -void LLViewerObject::setObjectCost(F32 cost) -{ - mObjectCost = cost; - mCostStale = false; - - if (isSelected()) - { - gFloaterTools->dirty(); - } -} - -void LLViewerObject::setLinksetCost(F32 cost) -{ - mLinksetCost = cost; - mCostStale = false; - - bool needs_refresh = isSelected(); - child_list_t::iterator iter = mChildList.begin(); - while(iter != mChildList.end() && !needs_refresh) - { - LLViewerObject* child = *iter; - needs_refresh = child->isSelected(); - iter++; - } - - if (needs_refresh) - { - gFloaterTools->dirty(); - } -} - -void LLViewerObject::setPhysicsCost(F32 cost) -{ - mPhysicsCost = cost; - mCostStale = false; - - if (isSelected()) - { - gFloaterTools->dirty(); - } -} - -void LLViewerObject::setLinksetPhysicsCost(F32 cost) -{ - mLinksetPhysicsCost = cost; - mCostStale = false; - - if (isSelected()) - { - gFloaterTools->dirty(); - } -} - - -F32 LLViewerObject::getObjectCost() -{ - if (mCostStale) - { - gObjectList.updateObjectCost(this); - } - - return mObjectCost; -} - -F32 LLViewerObject::getLinksetCost() -{ - if (mCostStale) - { - gObjectList.updateObjectCost(this); - } - - return mLinksetCost; -} - -F32 LLViewerObject::getPhysicsCost() -{ - if (mCostStale) - { - gObjectList.updateObjectCost(this); - } - - return mPhysicsCost; -} - -F32 LLViewerObject::getLinksetPhysicsCost() -{ - if (mCostStale) - { - gObjectList.updateObjectCost(this); - } - - return mLinksetPhysicsCost; -} - -F32 LLViewerObject::recursiveGetEstTrianglesMax() const -{ - F32 est_tris = getEstTrianglesMax(); - for (child_list_t::const_iterator iter = mChildList.begin(); - iter != mChildList.end(); iter++) - { - const LLViewerObject* child = *iter; - if (!child->isAvatar()) - { - est_tris += child->recursiveGetEstTrianglesMax(); - } - } - return est_tris; -} - -S32 LLViewerObject::getAnimatedObjectMaxTris() const -{ - S32 max_tris = 0; - if (gAgent.getRegion()) - { - LLSD features; - gAgent.getRegion()->getSimulatorFeatures(features); - if (features.has("AnimatedObjects")) - { - max_tris = features["AnimatedObjects"]["AnimatedObjectMaxTris"].asInteger(); - } - } - return max_tris; -} - -F32 LLViewerObject::getEstTrianglesMax() const -{ - return 0.f; -} - -F32 LLViewerObject::getEstTrianglesStreamingCost() const -{ - return 0.f; -} - -// virtual -F32 LLViewerObject::getStreamingCost() const -{ - return 0.f; -} - -// virtual -bool LLViewerObject::getCostData(LLMeshCostData& costs) const -{ - costs = LLMeshCostData(); - return false; -} - -U32 LLViewerObject::getTriangleCount(S32* vcount) const -{ - return 0; -} - -U32 LLViewerObject::getHighLODTriangleCount() -{ - return 0; -} - -U32 LLViewerObject::recursiveGetTriangleCount(S32* vcount) const -{ - S32 total_tris = getTriangleCount(vcount); - LLViewerObject::const_child_list_t& child_list = getChildren(); - for (LLViewerObject::const_child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); ++iter) - { - LLViewerObject* childp = *iter; - if (childp) - { - total_tris += childp->getTriangleCount(vcount); - } - } - return total_tris; -} - -// This is using the stored surface area for each volume (which -// defaults to 1.0 for the case of everything except a sculpt) and -// then scaling it linearly based on the largest dimension in the -// prim's scale. Should revisit at some point. -F32 LLViewerObject::recursiveGetScaledSurfaceArea() const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - F32 area = 0.f; - const LLDrawable* drawable = mDrawable; - if (drawable) - { - const LLVOVolume* volume = drawable->getVOVolume(); - if (volume) - { - if (volume->getVolume()) - { - const LLVector3& scale = volume->getScale(); - area += volume->getVolume()->getSurfaceArea() * llmax(llmax(scale.mV[0], scale.mV[1]), scale.mV[2]); - } - LLViewerObject::const_child_list_t children = volume->getChildren(); - for (LLViewerObject::const_child_list_t::const_iterator child_iter = children.begin(); - child_iter != children.end(); - ++child_iter) - { - LLViewerObject* child_obj = *child_iter; - LLVOVolume *child = dynamic_cast( child_obj ); - if (child && child->getVolume()) - { - const LLVector3& scale = child->getScale(); - area += child->getVolume()->getSurfaceArea() * llmax(llmax(scale.mV[0], scale.mV[1]), scale.mV[2]); - } - } - } - } - return area; -} - -void LLViewerObject::updateSpatialExtents(LLVector4a& newMin, LLVector4a &newMax) -{ - LLVector4a center; - center.load3(getRenderPosition().mV); - LLVector4a size; - size.load3(getScale().mV); - newMin.setSub(center, size); - newMax.setAdd(center, size); - - mDrawable->setPositionGroup(center); -} - -F32 LLViewerObject::getBinRadius() -{ - if (mDrawable.notNull()) - { - const LLVector4a* ext = mDrawable->getSpatialExtents(); - LLVector4a diff; - diff.setSub(ext[1], ext[0]); - return diff.getLength3().getF32(); - } - - return getScale().magVec(); -} - -F32 LLViewerObject::getMaxScale() const -{ - return llmax(getScale().mV[VX],getScale().mV[VY], getScale().mV[VZ]); -} - -F32 LLViewerObject::getMinScale() const -{ - return llmin(getScale().mV[0],getScale().mV[1],getScale().mV[2]); -} - -F32 LLViewerObject::getMidScale() const -{ - if (getScale().mV[VX] < getScale().mV[VY]) - { - if (getScale().mV[VY] < getScale().mV[VZ]) - { - return getScale().mV[VY]; - } - else if (getScale().mV[VX] < getScale().mV[VZ]) - { - return getScale().mV[VZ]; - } - else - { - return getScale().mV[VX]; - } - } - else if (getScale().mV[VX] < getScale().mV[VZ]) - { - return getScale().mV[VX]; - } - else if (getScale().mV[VY] < getScale().mV[VZ]) - { - return getScale().mV[VZ]; - } - else - { - return getScale().mV[VY]; - } -} - - -void LLViewerObject::updateTextures() -{ -} - -void LLViewerObject::boostTexturePriority(bool boost_children /* = true */) -{ - if (isDead() || !getVolume()) - { - return; - } - - S32 i; - S32 tex_count = getNumTEs(); - for (i = 0; i < tex_count; i++) - { - getTEImage(i)->setBoostLevel(LLGLTexture::BOOST_SELECTED); - } - - if (isSculpted() && !isMesh()) - { - LLSculptParams *sculpt_params = (LLSculptParams *)getParameterEntry(LLNetworkData::PARAMS_SCULPT); - LLUUID sculpt_id = sculpt_params->getSculptTexture(); - LLViewerTextureManager::getFetchedTexture(sculpt_id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE)->setBoostLevel(LLGLTexture::BOOST_SELECTED); - } - - if (boost_children) - { - for (child_list_t::iterator iter = mChildList.begin(); - iter != mChildList.end(); iter++) - { - LLViewerObject* child = *iter; - child->boostTexturePriority(); - } - } -} - -void LLViewerObject::setLineWidthForWindowSize(S32 window_width) -{ - if (window_width < 700) - { - LLUI::setLineWidth(2.0f); - } - else if (window_width < 1100) - { - LLUI::setLineWidth(3.0f); - } - else if (window_width < 2000) - { - LLUI::setLineWidth(4.0f); - } - else - { - // _damn_, what a nice monitor! - LLUI::setLineWidth(5.0f); - } -} - -void LLViewerObject::increaseArrowLength() -{ -/* ??? - if (mAxisArrowLength == 50) - { - mAxisArrowLength = 100; - } - else - { - mAxisArrowLength = 150; - } -*/ -} - - -void LLViewerObject::decreaseArrowLength() -{ -/* ??? - if (mAxisArrowLength == 150) - { - mAxisArrowLength = 100; - } - else - { - mAxisArrowLength = 50; - } -*/ -} - -// Culled from newsim LLTask::addNVPair -void LLViewerObject::addNVPair(const std::string& data) -{ - // cout << "LLViewerObject::addNVPair() with ---" << data << "---" << endl; - LLNameValue *nv = new LLNameValue(data.c_str()); - -// char splat[MAX_STRING]; -// temp->printNameValue(splat); -// LL_INFOS() << "addNVPair " << splat << LL_ENDL; - - name_value_map_t::iterator iter = mNameValuePairs.find(nv->mName); - if (iter != mNameValuePairs.end()) - { - LLNameValue* foundnv = iter->second; - if (foundnv->mClass != NVC_READ_ONLY) - { - delete foundnv; - mNameValuePairs.erase(iter); - } - else - { - delete nv; -// LL_INFOS() << "Trying to write to Read Only NVPair " << temp->mName << " in addNVPair()" << LL_ENDL; - return; - } - } - mNameValuePairs[nv->mName] = nv; -} - -bool LLViewerObject::removeNVPair(const std::string& name) -{ - char* canonical_name = gNVNameTable.addString(name); - - LL_DEBUGS() << "LLViewerObject::removeNVPair(): " << name << LL_ENDL; - - name_value_map_t::iterator iter = mNameValuePairs.find(canonical_name); - if (iter != mNameValuePairs.end()) - { - if( mRegionp ) - { - LLNameValue* nv = iter->second; -/* - std::string buffer = nv->printNameValue(); - gMessageSystem->newMessageFast(_PREHASH_RemoveNameValuePair); - gMessageSystem->nextBlockFast(_PREHASH_TaskData); - gMessageSystem->addUUIDFast(_PREHASH_ID, mID); - - gMessageSystem->nextBlockFast(_PREHASH_NameValueData); - gMessageSystem->addStringFast(_PREHASH_NVPair, buffer); - - gMessageSystem->sendReliable( mRegionp->getHost() ); -*/ - // Remove the NV pair from the local list. - delete nv; - mNameValuePairs.erase(iter); - return true; - } - else - { - LL_DEBUGS() << "removeNVPair - No region for object" << LL_ENDL; - } - } - return false; -} - - -LLNameValue *LLViewerObject::getNVPair(const std::string& name) const -{ - char *canonical_name; - - canonical_name = gNVNameTable.addString(name); - - // If you access a map with a name that isn't in it, it will add the name and a null pointer. - // So first check if the data is in the map. - name_value_map_t::const_iterator iter = mNameValuePairs.find(canonical_name); - if (iter != mNameValuePairs.end()) - { - return iter->second; - } - else - { - return NULL; - } -} - -void LLViewerObject::updatePositionCaches() const -{ - // If region is removed from the list it is also deleted. - if(mRegionp && LLWorld::instance().isRegionListed(mRegionp)) - { - if (!isRoot()) - { - mPositionRegion = ((LLViewerObject *)getParent())->getPositionRegion() + getPosition() * getParent()->getRotation(); - mPositionAgent = mRegionp->getPosAgentFromRegion(mPositionRegion); - } - else - { - mPositionRegion = getPosition(); - mPositionAgent = mRegionp->getPosAgentFromRegion(mPositionRegion); - } - } -} - -const LLVector3d LLViewerObject::getPositionGlobal() const -{ - // If region is removed from the list it is also deleted. - if(mRegionp && LLWorld::instance().isRegionListed(mRegionp)) - { - LLVector3d position_global = mRegionp->getPosGlobalFromRegion(getPositionRegion()); - - if (isAttachment()) - { - position_global = gAgent.getPosGlobalFromAgent(getRenderPosition()); - } - return position_global; - } - else - { - LLVector3d position_global(getPosition()); - return position_global; - } -} - -const LLVector3 &LLViewerObject::getPositionAgent() const -{ - // If region is removed from the list it is also deleted. - if(mRegionp && LLWorld::instance().isRegionListed(mRegionp)) - { - if (mDrawable.notNull() && (!mDrawable->isRoot() && getParent())) - { - // Don't return cached position if you have a parent, recalc (until all dirtying is done correctly. - LLVector3 position_region; - position_region = ((LLViewerObject *)getParent())->getPositionRegion() + getPosition() * getParent()->getRotation(); - mPositionAgent = mRegionp->getPosAgentFromRegion(position_region); - } - else - { - mPositionAgent = mRegionp->getPosAgentFromRegion(getPosition()); - } - } - return mPositionAgent; -} - -const LLVector3 &LLViewerObject::getPositionRegion() const -{ - if (!isRoot()) - { - LLViewerObject *parent = (LLViewerObject *)getParent(); - mPositionRegion = parent->getPositionRegion() + (getPosition() * parent->getRotation()); - } - else - { - mPositionRegion = getPosition(); - } - - return mPositionRegion; -} - -const LLVector3 LLViewerObject::getPositionEdit() const -{ - if (isRootEdit()) - { - return getPosition(); - } - else - { - LLViewerObject *parent = (LLViewerObject *)getParent(); - LLVector3 position_edit = parent->getPositionEdit() + getPosition() * parent->getRotationEdit(); - return position_edit; - } -} - -const LLVector3 LLViewerObject::getRenderPosition() const -{ - if (mDrawable.notNull() && mDrawable->isState(LLDrawable::RIGGED)) - { - LLControlAvatar *cav = getControlAvatar(); - if (isRoot() && cav) - { - F32 fixup; - if ( cav->hasPelvisFixup( fixup) ) - { - //Apply a pelvis fixup (as defined by the avs skin) - LLVector3 pos = mDrawable->getPositionAgent(); - pos[VZ] += fixup; - return pos; - } - } - LLVOAvatar* avatar = getAvatar(); - if ((avatar) && !getControlAvatar()) - { - return avatar->getPositionAgent(); - } - } - - if (mDrawable.isNull() || mDrawable->getGeneration() < 0) - { - return getPositionAgent(); - } - else - { - return mDrawable->getPositionAgent(); - } -} - -const LLVector3 LLViewerObject::getPivotPositionAgent() const -{ - return getRenderPosition(); -} - -const LLQuaternion LLViewerObject::getRenderRotation() const -{ - LLQuaternion ret; - if (mDrawable.notNull() && mDrawable->isState(LLDrawable::RIGGED) && !isAnimatedObject()) - { - return ret; - } - - if (mDrawable.isNull() || mDrawable->isStatic()) - { - ret = getRotationEdit(); - } - else - { - if (!mDrawable->isRoot()) - { - ret = getRotation() * LLQuaternion(mDrawable->getParent()->getWorldMatrix()); - } - else - { - ret = LLQuaternion(mDrawable->getWorldMatrix()); - } - } - - return ret; -} - -const LLMatrix4 LLViewerObject::getRenderMatrix() const -{ - return mDrawable->getWorldMatrix(); -} - -const LLQuaternion LLViewerObject::getRotationRegion() const -{ - LLQuaternion global_rotation = getRotation(); - if (!((LLXform *)this)->isRoot()) - { - global_rotation = global_rotation * getParent()->getRotation(); - } - return global_rotation; -} - -const LLQuaternion LLViewerObject::getRotationEdit() const -{ - LLQuaternion global_rotation = getRotation(); - if (!((LLXform *)this)->isRootEdit()) - { - global_rotation = global_rotation * getParent()->getRotation(); - } - return global_rotation; -} - -void LLViewerObject::setPositionAbsoluteGlobal( const LLVector3d &pos_global, bool damped ) -{ - if (isAttachment()) - { - LLVector3 new_pos = mRegionp->getPosRegionFromGlobal(pos_global); - if (isRootEdit()) - { - new_pos -= mDrawable->mXform.getParent()->getWorldPosition(); - LLQuaternion world_rotation = mDrawable->mXform.getParent()->getWorldRotation(); - new_pos = new_pos * ~world_rotation; - } - else - { - LLViewerObject* parentp = (LLViewerObject*)getParent(); - new_pos -= parentp->getPositionAgent(); - new_pos = new_pos * ~parentp->getRotationRegion(); - } - LLViewerObject::setPosition(new_pos); - - if (mParent && ((LLViewerObject*)mParent)->isAvatar()) - { - // we have changed the position of an attachment, so we need to clamp it - LLVOAvatar *avatar = (LLVOAvatar*)mParent; - - avatar->clampAttachmentPositions(); - } - } - else - { - if( isRoot() ) - { - setPositionRegion(mRegionp->getPosRegionFromGlobal(pos_global)); - } - else - { - // the relative position with the parent is not constant - LLViewerObject* parent = (LLViewerObject *)getParent(); - //RN: this assumes we are only calling this function from the edit tools - gPipeline.updateMoveNormalAsync(parent->mDrawable); - - LLVector3 pos_local = mRegionp->getPosRegionFromGlobal(pos_global) - parent->getPositionRegion(); - pos_local = pos_local * ~parent->getRotationRegion(); - LLViewerObject::setPosition( pos_local ); - } - } - //RN: assumes we always want to snap the object when calling this function - gPipeline.updateMoveNormalAsync(mDrawable); -} - -void LLViewerObject::setPosition(const LLVector3 &pos, bool damped) -{ - if (getPosition() != pos) - { - setChanged(TRANSLATED | SILHOUETTE); - } - - LLXform::setPosition(pos); - updateDrawable(damped); - if (isRoot()) - { - // position caches need to be up to date on root objects - updatePositionCaches(); - } -} - -void LLViewerObject::setPositionGlobal(const LLVector3d &pos_global, bool damped) -{ - if (isAttachment()) - { - if (isRootEdit()) - { - LLVector3 newPos = mRegionp->getPosRegionFromGlobal(pos_global); - newPos = newPos - mDrawable->mXform.getParent()->getWorldPosition(); - - LLQuaternion invWorldRotation = mDrawable->mXform.getParent()->getWorldRotation(); - invWorldRotation.transQuat(); - - newPos = newPos * invWorldRotation; - LLViewerObject::setPosition(newPos); - } - else - { - // assumes parent is root editable (root of attachment) - LLVector3 newPos = mRegionp->getPosRegionFromGlobal(pos_global); - newPos = newPos - mDrawable->mXform.getParent()->getWorldPosition(); - LLVector3 delta_pos = newPos - getPosition(); - - LLQuaternion invRotation = mDrawable->getRotation(); - invRotation.transQuat(); - - delta_pos = delta_pos * invRotation; - - // *FIX: is this right? Shouldn't we be calling the - // LLViewerObject version of setPosition? - LLVector3 old_pos = mDrawable->mXform.getParent()->getPosition(); - mDrawable->mXform.getParent()->setPosition(old_pos + delta_pos); - setChanged(TRANSLATED | SILHOUETTE); - } - if (mParent && ((LLViewerObject*)mParent)->isAvatar()) - { - // we have changed the position of an attachment, so we need to clamp it - LLVOAvatar *avatar = (LLVOAvatar*)mParent; - - avatar->clampAttachmentPositions(); - } - } - else - { - if (isRoot()) - { - setPositionRegion(mRegionp->getPosRegionFromGlobal(pos_global)); - } - else - { - // the relative position with the parent is constant, but the parent's position needs to be changed - LLVector3d position_offset; - position_offset.setVec(getPosition()*getParent()->getRotation()); - LLVector3d new_pos_global = pos_global - position_offset; - ((LLViewerObject *)getParent())->setPositionGlobal(new_pos_global); - } - } - updateDrawable(damped); -} - - -void LLViewerObject::setPositionParent(const LLVector3 &pos_parent, bool damped) -{ - // Set position relative to parent, if no parent, relative to region - if (!isRoot()) - { - LLViewerObject::setPosition(pos_parent, damped); - //updateDrawable(damped); - } - else - { - setPositionRegion(pos_parent, damped); - } -} - -void LLViewerObject::setPositionRegion(const LLVector3 &pos_region, bool damped) -{ - if (!isRootEdit()) - { - LLViewerObject* parent = (LLViewerObject*) getParent(); - LLViewerObject::setPosition((pos_region-parent->getPositionRegion())*~parent->getRotationRegion()); - } - else - { - LLViewerObject::setPosition(pos_region); - mPositionRegion = pos_region; - mPositionAgent = mRegionp->getPosAgentFromRegion(mPositionRegion); - } -} - -void LLViewerObject::setPositionAgent(const LLVector3 &pos_agent, bool damped) -{ - LLVector3 pos_region = getRegion()->getPosRegionFromAgent(pos_agent); - setPositionRegion(pos_region, damped); -} - -// identical to setPositionRegion() except it checks for child-joints -// and doesn't also move the joint-parent -// TODO -- implement similar intelligence for joint-parents toward -// their joint-children -void LLViewerObject::setPositionEdit(const LLVector3 &pos_edit, bool damped) -{ - if (!isRootEdit()) - { - // the relative position with the parent is constant, but the parent's position needs to be changed - LLVector3 position_offset = getPosition() * getParent()->getRotation(); - - ((LLViewerObject *)getParent())->setPositionEdit(pos_edit - position_offset); - updateDrawable(damped); - } - else - { - LLViewerObject::setPosition(pos_edit, damped); - mPositionRegion = pos_edit; - mPositionAgent = mRegionp->getPosAgentFromRegion(mPositionRegion); - } -} - - -LLViewerObject* LLViewerObject::getRootEdit() const -{ - const LLViewerObject* root = this; - while (root->mParent - && !((LLViewerObject*)root->mParent)->isAvatar()) - { - root = (LLViewerObject*)root->mParent; - } - return (LLViewerObject*)root; -} - - -bool LLViewerObject::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, - S32 face, - bool pick_transparent, - bool pick_rigged, - bool pick_unselectable, - S32* face_hit, - LLVector4a* intersection, - LLVector2* tex_coord, - LLVector4a* normal, - LLVector4a* tangent) -{ - return false; -} - -bool LLViewerObject::lineSegmentBoundingBox(const LLVector4a& start, const LLVector4a& end) -{ - if (mDrawable.isNull() || mDrawable->isDead()) - { - return false; - } - - const LLVector4a* ext = mDrawable->getSpatialExtents(); - - //VECTORIZE THIS - LLVector4a center; - center.setAdd(ext[1], ext[0]); - center.mul(0.5f); - LLVector4a size; - size.setSub(ext[1], ext[0]); - size.mul(0.5f); - - return LLLineSegmentBoxIntersect(start, end, center, size); -} - -U8 LLViewerObject::getMediaType() const -{ - if (mMedia) - { - return mMedia->mMediaType; - } - else - { - return LLViewerObject::MEDIA_NONE; - } -} - -void LLViewerObject::setMediaType(U8 media_type) -{ - if (!mMedia) - { - // TODO what if we don't have a media pointer? - } - else if (mMedia->mMediaType != media_type) - { - mMedia->mMediaType = media_type; - - // TODO: update materials with new image - } -} - -std::string LLViewerObject::getMediaURL() const -{ - if (mMedia) - { - return mMedia->mMediaURL; - } - else - { - return std::string(); - } -} - -void LLViewerObject::setMediaURL(const std::string& media_url) -{ - if (!mMedia) - { - mMedia = new LLViewerObjectMedia; - mMedia->mMediaURL = media_url; - mMedia->mPassedWhitelist = false; - - // TODO: update materials with new image - } - else if (mMedia->mMediaURL != media_url) - { - mMedia->mMediaURL = media_url; - mMedia->mPassedWhitelist = false; - - // TODO: update materials with new image - } -} - -bool LLViewerObject::getMediaPassedWhitelist() const -{ - if (mMedia) - { - return mMedia->mPassedWhitelist; - } - else - { - return false; - } -} - -void LLViewerObject::setMediaPassedWhitelist(bool passed) -{ - if (mMedia) - { - mMedia->mPassedWhitelist = passed; - } -} - -bool LLViewerObject::setMaterial(const U8 material) -{ - bool res = LLPrimitive::setMaterial(material); - if (res) - { - setChanged(TEXTURE); - } - return res; -} - -void LLViewerObject::setNumTEs(const U8 num_tes) -{ - U32 i; - if (num_tes != getNumTEs()) - { - if (num_tes) - { - LLPointer *new_images; - new_images = new LLPointer[num_tes]; - - LLPointer *new_normmaps; - new_normmaps = new LLPointer[num_tes]; - - LLPointer *new_specmaps; - new_specmaps = new LLPointer[num_tes]; - for (i = 0; i < num_tes; i++) - { - if (i < getNumTEs()) - { - new_images[i] = mTEImages[i]; - new_normmaps[i] = mTENormalMaps[i]; - new_specmaps[i] = mTESpecularMaps[i]; - } - else if (getNumTEs()) - { - new_images[i] = mTEImages[getNumTEs()-1]; - new_normmaps[i] = mTENormalMaps[getNumTEs()-1]; - new_specmaps[i] = mTESpecularMaps[getNumTEs()-1]; - } - else - { - new_images[i] = NULL; - new_normmaps[i] = NULL; - new_specmaps[i] = NULL; - } - } - - deleteTEImages(); - - mTEImages = new_images; - mTENormalMaps = new_normmaps; - mTESpecularMaps = new_specmaps; - } - else - { - deleteTEImages(); - } - - S32 original_tes = getNumTEs(); - - LLPrimitive::setNumTEs(num_tes); - setChanged(TEXTURE); - - // touch up GLTF materials - if (original_tes > 0) - { - for (int i = original_tes; i < getNumTEs(); ++i) - { - LLTextureEntry* src = getTE(original_tes - 1); - LLTextureEntry* tep = getTE(i); - setRenderMaterialID(i, getRenderMaterialID(original_tes - 1), false); - - if (tep) - { - LLGLTFMaterial* base_material = src->getGLTFMaterial(); - LLGLTFMaterial* override_material = src->getGLTFMaterialOverride(); - if (base_material && override_material) - { - tep->setGLTFMaterialOverride(new LLGLTFMaterial(*override_material)); - - LLGLTFMaterial* render_material = new LLFetchedGLTFMaterial(); - *render_material = *base_material; - render_material->applyOverride(*override_material); - tep->setGLTFRenderMaterial(render_material); - } - } - } - } - - if (mDrawable.notNull()) - { - gPipeline.markTextured(mDrawable); - } - } -} - -void LLViewerObject::sendMaterialUpdate() const -{ - LLViewerRegion* regionp = getRegion(); - if(!regionp) return; - gMessageSystem->newMessageFast(_PREHASH_ObjectMaterial); - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - gMessageSystem->nextBlockFast(_PREHASH_ObjectData); - gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, mLocalID ); - gMessageSystem->addU8Fast(_PREHASH_Material, getMaterial() ); - gMessageSystem->sendReliable( regionp->getHost() ); - -} - -//formerly send_object_shape(LLViewerObject *object) -void LLViewerObject::sendShapeUpdate() -{ - gMessageSystem->newMessageFast(_PREHASH_ObjectShape); - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - gMessageSystem->nextBlockFast(_PREHASH_ObjectData); - gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, mLocalID ); - - LLVolumeMessage::packVolumeParams(&getVolume()->getParams(), gMessageSystem); - - LLViewerRegion *regionp = getRegion(); - gMessageSystem->sendReliable( regionp->getHost() ); -} - - -void LLViewerObject::sendTEUpdate() const -{ - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_ObjectImage); - - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - - msg->nextBlockFast(_PREHASH_ObjectData); - msg->addU32Fast(_PREHASH_ObjectLocalID, mLocalID ); - if (mMedia) - { - msg->addString("MediaURL", mMedia->mMediaURL); - } - else - { - msg->addString("MediaURL", NULL); - } - - // TODO send media type - - packTEMessage(msg); - - LLViewerRegion *regionp = getRegion(); - msg->sendReliable( regionp->getHost() ); -} - -LLViewerTexture* LLViewerObject::getBakedTextureForMagicId(const LLUUID& id) -{ - if (!LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(id)) - { - return NULL; - } - - LLViewerObject *root = getRootEdit(); - if (root && root->isAnimatedObject()) - { - return LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); - } - - LLVOAvatar* avatar = getAvatar(); - if (avatar && !isHUDAttachment()) - { - LLAvatarAppearanceDefines::EBakedTextureIndex texIndex = LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::assetIdToBakedTextureIndex(id); - LLViewerTexture* bakedTexture = avatar->getBakedTexture(texIndex); - if (bakedTexture == NULL || bakedTexture->isMissingAsset()) - { - return LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); - } - else - { - return bakedTexture; - } - } - else - { - return LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); - } - -} - -void LLViewerObject::updateAvatarMeshVisibility(const LLUUID& id, const LLUUID& old_id) -{ - if (id == old_id) - { - return; - } - - if (!LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(old_id) && !LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(id)) - { - return; - } - - LLVOAvatar* avatar = getAvatar(); - if (avatar) - { - avatar->updateMeshVisibility(); - } -} - - -void LLViewerObject::setTE(const U8 te, const LLTextureEntry& texture_entry) -{ - LLUUID old_image_id; - if (getTE(te)) - { - old_image_id = getTE(te)->getID(); - } - - LLPrimitive::setTE(te, texture_entry); - - const LLUUID& image_id = getTE(te)->getID(); - LLViewerTexture* bakedTexture = getBakedTextureForMagicId(image_id); - mTEImages[te] = bakedTexture ? bakedTexture : LLViewerTextureManager::getFetchedTexture(image_id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); - - updateAvatarMeshVisibility(image_id, old_image_id); - - updateTEMaterialTextures(te); -} - -void LLViewerObject::updateTEMaterialTextures(U8 te) -{ - if (getTE(te)->getMaterialParams().notNull()) - { - const LLUUID& norm_id = getTE(te)->getMaterialParams()->getNormalID(); - mTENormalMaps[te] = LLViewerTextureManager::getFetchedTexture(norm_id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); - - const LLUUID& spec_id = getTE(te)->getMaterialParams()->getSpecularID(); - mTESpecularMaps[te] = LLViewerTextureManager::getFetchedTexture(spec_id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); - } - - LLFetchedGLTFMaterial* mat = (LLFetchedGLTFMaterial*) getTE(te)->getGLTFRenderMaterial(); - llassert(mat == nullptr || dynamic_cast(getTE(te)->getGLTFRenderMaterial()) != nullptr); - LLUUID mat_id = getRenderMaterialID(te); - if (mat == nullptr && mat_id.notNull()) - { - mat = (LLFetchedGLTFMaterial*) gGLTFMaterialList.getMaterial(mat_id); - llassert(mat == nullptr || dynamic_cast(gGLTFMaterialList.getMaterial(mat_id)) != nullptr); - if (mat->isFetching()) - { // material is not loaded yet, rebuild draw info when the object finishes loading - mat->onMaterialComplete([id=getID()] - { - LLViewerObject* obj = gObjectList.findObject(id); - if (obj) - { - obj->markForUpdate(); - } - }); - } - getTE(te)->setGLTFMaterial(mat); - } - else if (mat_id.isNull() && mat != nullptr) - { - mat = nullptr; - getTE(te)->setGLTFMaterial(nullptr); - } - - auto fetch_texture = [this](const LLUUID& id) - { - LLViewerFetchedTexture* img = nullptr; - if (id.notNull()) - { - if (LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(id)) - { - // TODO -- fall back to LLTextureEntry::mGLTFRenderMaterial when overriding with baked texture - LLViewerTexture* viewerTexture = getBakedTextureForMagicId(id); - img = viewerTexture ? dynamic_cast(viewerTexture) : nullptr; - } - else - { - img = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); - img->addTextureStats(64.f * 64.f, true); - } - } - - return img; - }; - - if (mat != nullptr) - { - mat->mBaseColorTexture = fetch_texture(mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR]); - mat->mNormalTexture = fetch_texture(mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL]); - mat->mMetallicRoughnessTexture = fetch_texture(mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS]); - mat->mEmissiveTexture= fetch_texture(mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE]); - } -} - -void LLViewerObject::refreshBakeTexture() -{ - for (int face_index = 0; face_index < getNumTEs(); face_index++) - { - LLTextureEntry* tex_entry = getTE(face_index); - if (tex_entry && LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(tex_entry->getID())) - { - const LLUUID& image_id = tex_entry->getID(); - LLViewerTexture* bakedTexture = getBakedTextureForMagicId(image_id); - changeTEImage(face_index, bakedTexture); - } - } -} - -void LLViewerObject::setTEImage(const U8 te, LLViewerTexture *imagep) -{ - if (mTEImages[te] != imagep) - { - LLUUID old_image_id = getTE(te) ? getTE(te)->getID() : LLUUID::null; - - LLPrimitive::setTETexture(te, imagep->getID()); - - LLViewerTexture* baked_texture = getBakedTextureForMagicId(imagep->getID()); - mTEImages[te] = baked_texture ? baked_texture : imagep; - updateAvatarMeshVisibility(imagep->getID(), old_image_id); - setChanged(TEXTURE); - if (mDrawable.notNull()) - { - gPipeline.markTextured(mDrawable); - } - } -} - -S32 LLViewerObject::setTETextureCore(const U8 te, LLViewerTexture *image) -{ - LLUUID old_image_id = getTE(te)->getID(); - const LLUUID& uuid = image ? image->getID() : LLUUID::null; - S32 retval = 0; - if (uuid != getTE(te)->getID() || - uuid == LLUUID::null) - { - retval = LLPrimitive::setTETexture(te, uuid); - LLViewerTexture* baked_texture = getBakedTextureForMagicId(uuid); - mTEImages[te] = baked_texture ? baked_texture : image; - updateAvatarMeshVisibility(uuid,old_image_id); - setChanged(TEXTURE); - if (mDrawable.notNull()) - { - gPipeline.markTextured(mDrawable); - } - } - return retval; -} - -S32 LLViewerObject::setTENormalMapCore(const U8 te, LLViewerTexture *image) -{ - S32 retval = TEM_CHANGE_TEXTURE; - const LLUUID& uuid = image ? image->getID() : LLUUID::null; - if (uuid != getTE(te)->getID() || - uuid == LLUUID::null) - { - LLTextureEntry* tep = getTE(te); - LLMaterial* mat = NULL; - if (tep) - { - mat = tep->getMaterialParams(); - } - - if (mat) - { - mat->setNormalID(uuid); - } - } - changeTENormalMap(te,image); - return retval; -} - -S32 LLViewerObject::setTESpecularMapCore(const U8 te, LLViewerTexture *image) -{ - S32 retval = TEM_CHANGE_TEXTURE; - const LLUUID& uuid = image ? image->getID() : LLUUID::null; - if (uuid != getTE(te)->getID() || - uuid == LLUUID::null) - { - LLTextureEntry* tep = getTE(te); - LLMaterial* mat = NULL; - if (tep) - { - mat = tep->getMaterialParams(); - } - - if (mat) - { - mat->setSpecularID(uuid); - } - } - changeTESpecularMap(te, image); - return retval; -} - -//virtual -void LLViewerObject::changeTEImage(S32 index, LLViewerTexture* new_image) -{ - if(index < 0 || index >= getNumTEs()) - { - return ; - } - mTEImages[index] = new_image ; -} - -void LLViewerObject::changeTENormalMap(S32 index, LLViewerTexture* new_image) -{ - if(index < 0 || index >= getNumTEs()) - { - return ; - } - mTENormalMaps[index] = new_image ; - refreshMaterials(); -} - -void LLViewerObject::changeTESpecularMap(S32 index, LLViewerTexture* new_image) -{ - if(index < 0 || index >= getNumTEs()) - { - return ; - } - mTESpecularMaps[index] = new_image ; - refreshMaterials(); -} - -S32 LLViewerObject::setTETexture(const U8 te, const LLUUID& uuid) -{ - // Invalid host == get from the agent's sim - LLViewerFetchedTexture *image = LLViewerTextureManager::getFetchedTexture( - uuid, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE, 0, 0, LLHost()); - return setTETextureCore(te, image); -} - -S32 LLViewerObject::setTENormalMap(const U8 te, const LLUUID& uuid) -{ - LLViewerFetchedTexture *image = (uuid == LLUUID::null) ? NULL : LLViewerTextureManager::getFetchedTexture( - uuid, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE, 0, 0, LLHost()); - return setTENormalMapCore(te, image); -} - -S32 LLViewerObject::setTESpecularMap(const U8 te, const LLUUID& uuid) -{ - LLViewerFetchedTexture *image = (uuid == LLUUID::null) ? NULL : LLViewerTextureManager::getFetchedTexture( - uuid, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE, 0, 0, LLHost()); - return setTESpecularMapCore(te, image); -} - -S32 LLViewerObject::setTEColor(const U8 te, const LLColor3& color) -{ - return setTEColor(te, LLColor4(color)); -} - -S32 LLViewerObject::setTEColor(const U8 te, const LLColor4& color) -{ - S32 retval = 0; - const LLTextureEntry *tep = getTE(te); - if (!tep) - { - LL_WARNS() << "No texture entry for te " << (S32)te << ", object " << mID << LL_ENDL; - } - else if (color != tep->getColor()) - { - retval = LLPrimitive::setTEColor(te, color); - if (mDrawable.notNull() && retval) - { - // These should only happen on updates which are not the initial update. - dirtyMesh(); - } - } - return retval; -} - -S32 LLViewerObject::setTEBumpmap(const U8 te, const U8 bump) -{ - S32 retval = 0; - const LLTextureEntry *tep = getTE(te); - if (!tep) - { - LL_WARNS() << "No texture entry for te " << (S32)te << ", object " << mID << LL_ENDL; - } - else if (bump != tep->getBumpmap()) - { - retval = LLPrimitive::setTEBumpmap(te, bump); - setChanged(TEXTURE); - if (mDrawable.notNull() && retval) - { - gPipeline.markTextured(mDrawable); - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_GEOMETRY); - } - } - return retval; -} - -S32 LLViewerObject::setTETexGen(const U8 te, const U8 texgen) -{ - S32 retval = 0; - const LLTextureEntry *tep = getTE(te); - if (!tep) - { - LL_WARNS() << "No texture entry for te " << (S32)te << ", object " << mID << LL_ENDL; - } - else if (texgen != tep->getTexGen()) - { - retval = LLPrimitive::setTETexGen(te, texgen); - setChanged(TEXTURE); - } - return retval; -} - -S32 LLViewerObject::setTEMediaTexGen(const U8 te, const U8 media) -{ - S32 retval = 0; - const LLTextureEntry *tep = getTE(te); - if (!tep) - { - LL_WARNS() << "No texture entry for te " << (S32)te << ", object " << mID << LL_ENDL; - } - else if (media != tep->getMediaTexGen()) - { - retval = LLPrimitive::setTEMediaTexGen(te, media); - setChanged(TEXTURE); - } - return retval; -} - -S32 LLViewerObject::setTEShiny(const U8 te, const U8 shiny) -{ - S32 retval = 0; - const LLTextureEntry *tep = getTE(te); - if (!tep) - { - LL_WARNS() << "No texture entry for te " << (S32)te << ", object " << mID << LL_ENDL; - } - else if (shiny != tep->getShiny()) - { - retval = LLPrimitive::setTEShiny(te, shiny); - setChanged(TEXTURE); - } - return retval; -} - -S32 LLViewerObject::setTEFullbright(const U8 te, const U8 fullbright) -{ - S32 retval = 0; - const LLTextureEntry *tep = getTE(te); - if (!tep) - { - LL_WARNS() << "No texture entry for te " << (S32)te << ", object " << mID << LL_ENDL; - } - else if (fullbright != tep->getFullbright()) - { - retval = LLPrimitive::setTEFullbright(te, fullbright); - setChanged(TEXTURE); - if (mDrawable.notNull() && retval) - { - gPipeline.markTextured(mDrawable); - } - } - return retval; -} - - -S32 LLViewerObject::setTEMediaFlags(const U8 te, const U8 media_flags) -{ - // this might need work for media type - S32 retval = 0; - const LLTextureEntry *tep = getTE(te); - if (!tep) - { - LL_WARNS() << "No texture entry for te " << (S32)te << ", object " << mID << LL_ENDL; - } - else if (media_flags != tep->getMediaFlags()) - { - retval = LLPrimitive::setTEMediaFlags(te, media_flags); - setChanged(TEXTURE); - if (mDrawable.notNull() && retval) - { - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_TCOORD); - gPipeline.markTextured(mDrawable); - } - } - return retval; -} - -S32 LLViewerObject::setTEGlow(const U8 te, const F32 glow) -{ - S32 retval = 0; - const LLTextureEntry *tep = getTE(te); - if (!tep) - { - LL_WARNS() << "No texture entry for te " << (S32)te << ", object " << mID << LL_ENDL; - } - else if (glow != tep->getGlow()) - { - retval = LLPrimitive::setTEGlow(te, glow); - setChanged(TEXTURE); - if (mDrawable.notNull() && retval) - { - gPipeline.markTextured(mDrawable); - } - } - return retval; -} - -S32 LLViewerObject::setTEMaterialID(const U8 te, const LLMaterialID& pMaterialID) -{ - S32 retval = 0; - const LLTextureEntry *tep = getTE(te); - if (!tep) - { - LL_WARNS("Material") << "No texture entry for te " << (S32)te - << ", object " << mID - << ", material " << pMaterialID - << LL_ENDL; - } - //else if (pMaterialID != tep->getMaterialID()) - { - LL_DEBUGS("Material") << "Changing texture entry for te " << (S32)te - << ", object " << mID - << ", material " << pMaterialID - << LL_ENDL; - retval = LLPrimitive::setTEMaterialID(te, pMaterialID); - refreshMaterials(); - } - return retval; -} - -S32 LLViewerObject::setTEMaterialParams(const U8 te, const LLMaterialPtr pMaterialParams) -{ - S32 retval = 0; - const LLTextureEntry *tep = getTE(te); - if (!tep) - { - LL_WARNS() << "No texture entry for te " << (S32)te << ", object " << mID << LL_ENDL; - return 0; - } - - retval = LLPrimitive::setTEMaterialParams(te, pMaterialParams); - LL_DEBUGS("Material") << "Changing material params for te " << (S32)te - << ", object " << mID - << " (" << retval << ")" - << LL_ENDL; - setTENormalMap(te, (pMaterialParams) ? pMaterialParams->getNormalID() : LLUUID::null); - setTESpecularMap(te, (pMaterialParams) ? pMaterialParams->getSpecularID() : LLUUID::null); - - return retval; -} - -S32 LLViewerObject::setTEGLTFMaterialOverride(U8 te, LLGLTFMaterial* override_mat) -{ - LL_PROFILE_ZONE_SCOPED; - S32 retval = TEM_CHANGE_NONE; - - LLTextureEntry* tep = getTE(te); - if (!tep) - { // this could happen if the object is not fully formed yet - // returning TEM_CHANGE_NONE here signals to LLGLTFMaterialList to queue the override for later - return retval; - } - - LLFetchedGLTFMaterial* src_mat = (LLFetchedGLTFMaterial*) tep->getGLTFMaterial(); - llassert(src_mat == nullptr || dynamic_cast(tep->getGLTFMaterial()) != nullptr); - // if override mat exists, we must also have a source mat - if (!src_mat) - { - // we can get into this state if an override has arrived before the viewer has - // received or handled an update, return TEM_CHANGE_NONE to signal to LLGLTFMaterialList that it - // should queue the update for later - return retval; - } - - if(src_mat->isFetching()) - { - // if still fetching, we need to wait until it is done and try again - return retval; - } - - retval = tep->setGLTFMaterialOverride(override_mat); - - if (retval) - { - if (override_mat) - { - LLFetchedGLTFMaterial* render_mat = new LLFetchedGLTFMaterial(*src_mat); - render_mat->applyOverride(*override_mat); - tep->setGLTFRenderMaterial(render_mat); - retval = TEM_CHANGE_TEXTURE; - - for (LLGLTFMaterial::local_tex_map_t::value_type &val : override_mat->mTrackingIdToLocalTexture) - { - LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(val.first, override_mat); - } - - } - else if (tep->setGLTFRenderMaterial(nullptr)) - { - retval = TEM_CHANGE_TEXTURE; - } - } - - return retval; -} - -void LLViewerObject::refreshMaterials() -{ - setChanged(TEXTURE); - if (mDrawable.notNull()) - { - gPipeline.markTextured(mDrawable); - } -} - -S32 LLViewerObject::setTEScale(const U8 te, const F32 s, const F32 t) -{ - S32 retval = 0; - retval = LLPrimitive::setTEScale(te, s, t); - setChanged(TEXTURE); - if (mDrawable.notNull() && retval) - { - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_TCOORD); - } - return retval; -} - -S32 LLViewerObject::setTEScaleS(const U8 te, const F32 s) -{ - S32 retval = LLPrimitive::setTEScaleS(te, s); - if (mDrawable.notNull() && retval) - { - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_TCOORD); - } - - return retval; -} - -S32 LLViewerObject::setTEScaleT(const U8 te, const F32 t) -{ - S32 retval = LLPrimitive::setTEScaleT(te, t); - if (mDrawable.notNull() && retval) - { - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_TCOORD); - } - - return retval; -} - -S32 LLViewerObject::setTEOffset(const U8 te, const F32 s, const F32 t) -{ - S32 retval = LLPrimitive::setTEOffset(te, s, t); - if (mDrawable.notNull() && retval) - { - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_TCOORD); - } - return retval; -} - -S32 LLViewerObject::setTEOffsetS(const U8 te, const F32 s) -{ - S32 retval = LLPrimitive::setTEOffsetS(te, s); - if (mDrawable.notNull() && retval) - { - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_TCOORD); - } - - return retval; -} - -S32 LLViewerObject::setTEOffsetT(const U8 te, const F32 t) -{ - S32 retval = LLPrimitive::setTEOffsetT(te, t); - if (mDrawable.notNull() && retval) - { - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_TCOORD); - } - - return retval; -} - -S32 LLViewerObject::setTERotation(const U8 te, const F32 r) -{ - S32 retval = LLPrimitive::setTERotation(te, r); - if (mDrawable.notNull() && retval) - { - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_TCOORD); - shrinkWrap(); - } - return retval; -} - - -LLViewerTexture *LLViewerObject::getTEImage(const U8 face) const -{ -// llassert(mTEImages); - - if (face < getNumTEs()) - { - LLViewerTexture* image = mTEImages[face]; - if (image) - { - return image; - } - else - { - return (LLViewerTexture*)(LLViewerFetchedTexture::sDefaultImagep); - } - } - - LL_ERRS() << llformat("Requested Image from invalid face: %d/%d",face,getNumTEs()) << LL_ENDL; - - return NULL; -} - - -bool LLViewerObject::isImageAlphaBlended(const U8 te) const -{ - LLViewerTexture* image = getTEImage(te); - LLGLenum format = image ? image->getPrimaryFormat() : GL_RGB; - switch (format) - { - case GL_RGBA: - case GL_ALPHA: - { - return true; - } - break; - - case GL_RGB: break; - default: - { - LL_WARNS() << "Unexpected tex format in LLViewerObject::isImageAlphaBlended...returning no alpha." << LL_ENDL; - } - break; - } - - return false; -} - -LLViewerTexture *LLViewerObject::getTENormalMap(const U8 face) const -{ - // llassert(mTEImages); - - if (face < getNumTEs()) - { - LLViewerTexture* image = mTENormalMaps[face]; - if (image) - { - return image; - } - else - { - return (LLViewerTexture*)(LLViewerFetchedTexture::sDefaultImagep); - } - } - - LL_ERRS() << llformat("Requested Image from invalid face: %d/%d",face,getNumTEs()) << LL_ENDL; - - return NULL; -} - -LLViewerTexture *LLViewerObject::getTESpecularMap(const U8 face) const -{ - // llassert(mTEImages); - - if (face < getNumTEs()) - { - LLViewerTexture* image = mTESpecularMaps[face]; - if (image) - { - return image; - } - else - { - return (LLViewerTexture*)(LLViewerFetchedTexture::sDefaultImagep); - } - } - - LL_ERRS() << llformat("Requested Image from invalid face: %d/%d",face,getNumTEs()) << LL_ENDL; - - return NULL; -} - -void LLViewerObject::fitFaceTexture(const U8 face) -{ - LL_INFOS() << "fitFaceTexture not implemented" << LL_ENDL; -} - -LLBBox LLViewerObject::getBoundingBoxAgent() const -{ - LLVector3 position_agent; - LLQuaternion rot; - LLViewerObject* avatar_parent = NULL; - LLViewerObject* root_edit = (LLViewerObject*)getRootEdit(); - if (root_edit) - { - avatar_parent = (LLViewerObject*)root_edit->getParent(); - } - - if (avatar_parent && avatar_parent->isAvatar() && - root_edit && root_edit->mDrawable.notNull() && root_edit->mDrawable->getXform()->getParent()) - { - LLXform* parent_xform = root_edit->mDrawable->getXform()->getParent(); - position_agent = (getPositionEdit() * parent_xform->getWorldRotation()) + parent_xform->getWorldPosition(); - rot = getRotationEdit() * parent_xform->getWorldRotation(); - } - else - { - position_agent = getPositionAgent(); - rot = getRotationRegion(); - } - - return LLBBox( position_agent, rot, getScale() * -0.5f, getScale() * 0.5f ); -} - -U32 LLViewerObject::getNumVertices() const -{ - U32 num_vertices = 0; - if (mDrawable.notNull()) - { - S32 i, num_faces; - num_faces = mDrawable->getNumFaces(); - for (i = 0; i < num_faces; i++) - { - LLFace * facep = mDrawable->getFace(i); - if (facep) - { - num_vertices += facep->getGeomCount(); - } - } - } - return num_vertices; -} - -U32 LLViewerObject::getNumIndices() const -{ - U32 num_indices = 0; - if (mDrawable.notNull()) - { - S32 i, num_faces; - num_faces = mDrawable->getNumFaces(); - for (i = 0; i < num_faces; i++) - { - LLFace * facep = mDrawable->getFace(i); - if (facep) - { - num_indices += facep->getIndicesCount(); - } - } - } - return num_indices; -} - -// Find the number of instances of this object's inventory that are of the given type -S32 LLViewerObject::countInventoryContents(LLAssetType::EType type) -{ - S32 count = 0; - if( mInventory ) - { - LLInventoryObject::object_list_t::const_iterator it = mInventory->begin(); - LLInventoryObject::object_list_t::const_iterator end = mInventory->end(); - for( ; it != end ; ++it ) - { - if( (*it)->getType() == type ) - { - ++count; - } - } - } - return count; -} - -void LLViewerObject::setDebugText(const std::string &utf8text, const LLColor4& color) -{ - if (utf8text.empty() && !mText) - { - return; - } - - if (!mText) - { - initHudText(); - } - mText->setColor(color); - mText->setString(utf8text); - mText->setZCompare(false); - mText->setDoFade(false); - updateText(); -} - -void LLViewerObject::appendDebugText(const std::string &utf8text) -{ - if (utf8text.empty() && !mText) - { - return; - } - - if (!mText) - { - initHudText(); - } - mText->addLine(utf8text, LLColor4::white); - mText->setZCompare(false); - mText->setDoFade(false); - updateText(); -} - -void LLViewerObject::initHudText() -{ - mText = (LLHUDText *)LLHUDObject::addHUDObject(LLHUDObject::LL_HUD_TEXT); - mText->setFont(LLFontGL::getFontSansSerif()); - mText->setVertAlignment(LLHUDText::ALIGN_VERT_TOP); - mText->setMaxLines(-1); - mText->setSourceObject(this); - mText->setOnHUDAttachment(isHUDAttachment()); -} - -void LLViewerObject::restoreHudText() -{ - if (mHudText.empty()) - { - if (mText) - { - mText->markDead(); - mText = NULL; - } - } - else - { - if (!mText) - { - initHudText(); - } - else - { - // Restore default values - mText->setZCompare(true); - mText->setDoFade(true); - } - mText->setColor(mHudTextColor); - mText->setString(mHudText); - } -} - -void LLViewerObject::setIcon(LLViewerTexture* icon_image) -{ - if (!mIcon) - { - mIcon = (LLHUDIcon *)LLHUDObject::addHUDObject(LLHUDObject::LL_HUD_ICON); - mIcon->setSourceObject(this); - mIcon->setImage(icon_image); - // *TODO: make this user configurable - mIcon->setScale(0.03f); - } - else - { - mIcon->restartLifeTimer(); - } -} - -void LLViewerObject::clearIcon() -{ - if (mIcon) - { - mIcon = NULL; - } -} - -LLViewerObject* LLViewerObject::getSubParent() -{ - return (LLViewerObject*) getParent(); -} - -const LLViewerObject* LLViewerObject::getSubParent() const -{ - return (const LLViewerObject*) getParent(); -} - -bool LLViewerObject::isOnMap() -{ - return mOnMap; -} - - -void LLViewerObject::updateText() -{ - if (!isDead()) - { - if (mText.notNull()) - { - LLVOAvatar* avatar = getAvatar(); - if (avatar) - { - mText->setHidden(avatar->isInMuteList()); - } - - LLVector3 up_offset(0,0,0); - up_offset.mV[2] = getScale().mV[VZ]*0.6f; - - if (mDrawable.notNull()) - { - mText->setPositionAgent(getRenderPosition() + up_offset); - } - else - { - mText->setPositionAgent(getPositionAgent() + up_offset); - } - } - } -} - -bool LLViewerObject::isOwnerInMuteList(LLUUID id) -{ - LLUUID owner_id = id.isNull() ? mOwnerID : id; - if (isAvatar() || owner_id.isNull()) - { - return false; - } - bool muted = false; - F64 now = LLFrameTimer::getTotalSeconds(); - if (now < mCachedMuteListUpdateTime) - { - muted = mCachedOwnerInMuteList; - } - else - { - muted = LLMuteList::getInstance()->isMuted(owner_id); - - const F64 SECONDS_BETWEEN_MUTE_UPDATES = 1; - mCachedMuteListUpdateTime = now + SECONDS_BETWEEN_MUTE_UPDATES; - mCachedOwnerInMuteList = muted; - } - return muted; -} - -LLVOAvatar* LLViewerObject::asAvatar() -{ - return NULL; -} - -// If this object is directly or indirectly parented by an avatar, -// return it. Normally getAvatar() is the correct function to call; -// it will give the avatar used for skinning. The exception is with -// animated objects that are also attachments; in that case, -// getAvatar() will return the control avatar, used for skinning, and -// getAvatarAncestor will return the avatar to which the object is -// attached. -LLVOAvatar* LLViewerObject::getAvatarAncestor() -{ - LLViewerObject *pobj = (LLViewerObject*) getParent(); - while (pobj) - { - LLVOAvatar *av = pobj->asAvatar(); - if (av) - { - return av; - } - pobj = (LLViewerObject*) pobj->getParent(); - } - return NULL; -} - -bool LLViewerObject::isParticleSource() const -{ - return !mPartSourcep.isNull() && !mPartSourcep->isDead(); -} - -void LLViewerObject::setParticleSource(const LLPartSysData& particle_parameters, const LLUUID& owner_id) -{ - if (mPartSourcep) - { - deleteParticleSource(); - } - - LLPointer pss = LLViewerPartSourceScript::createPSS(this, particle_parameters); - mPartSourcep = pss; - - if (mPartSourcep) - { - mPartSourcep->setOwnerUUID(owner_id); - - if (mPartSourcep->getImage()->getID() != mPartSourcep->mPartSysData.mPartImageID) - { - LLViewerTexture* image; - if (mPartSourcep->mPartSysData.mPartImageID == LLUUID::null) - { - image = LLViewerTextureManager::getFetchedTextureFromFile("pixiesmall.tga"); - } - else - { - image = LLViewerTextureManager::getFetchedTexture(mPartSourcep->mPartSysData.mPartImageID); - } - mPartSourcep->setImage(image); - } - } - LLViewerPartSim::getInstance()->addPartSource(pss); -} - -void LLViewerObject::unpackParticleSource(const S32 block_num, const LLUUID& owner_id) -{ - if (!mPartSourcep.isNull() && mPartSourcep->isDead()) - { - mPartSourcep = NULL; - } - if (mPartSourcep) - { - // If we've got one already, just update the existing source (or remove it) - if (!LLViewerPartSourceScript::unpackPSS(this, mPartSourcep, block_num)) - { - mPartSourcep->setDead(); - mPartSourcep = NULL; - } - } - else - { - LLPointer pss = LLViewerPartSourceScript::unpackPSS(this, NULL, block_num); - //If the owner is muted, don't create the system - if(LLMuteList::getInstance()->isMuted(owner_id, LLMute::flagParticles)) return; - - // We need to be able to deal with a particle source that hasn't changed, but still got an update! - if (pss) - { -// LL_INFOS() << "Making particle system with owner " << owner_id << LL_ENDL; - pss->setOwnerUUID(owner_id); - mPartSourcep = pss; - LLViewerPartSim::getInstance()->addPartSource(pss); - } - } - if (mPartSourcep) - { - if (mPartSourcep->getImage()->getID() != mPartSourcep->mPartSysData.mPartImageID) - { - LLViewerTexture* image; - if (mPartSourcep->mPartSysData.mPartImageID == LLUUID::null) - { - image = LLViewerTextureManager::getFetchedTextureFromFile("pixiesmall.j2c"); - } - else - { - image = LLViewerTextureManager::getFetchedTexture(mPartSourcep->mPartSysData.mPartImageID); - } - mPartSourcep->setImage(image); - } - } -} - -void LLViewerObject::unpackParticleSource(LLDataPacker &dp, const LLUUID& owner_id, bool legacy) -{ - if (!mPartSourcep.isNull() && mPartSourcep->isDead()) - { - mPartSourcep = NULL; - } - if (mPartSourcep) - { - // If we've got one already, just update the existing source (or remove it) - if (!LLViewerPartSourceScript::unpackPSS(this, mPartSourcep, dp, legacy)) - { - mPartSourcep->setDead(); - mPartSourcep = NULL; - } - } - else - { - LLPointer pss = LLViewerPartSourceScript::unpackPSS(this, NULL, dp, legacy); - //If the owner is muted, don't create the system - if(LLMuteList::getInstance()->isMuted(owner_id, LLMute::flagParticles)) return; - // We need to be able to deal with a particle source that hasn't changed, but still got an update! - if (pss) - { -// LL_INFOS() << "Making particle system with owner " << owner_id << LL_ENDL; - pss->setOwnerUUID(owner_id); - mPartSourcep = pss; - LLViewerPartSim::getInstance()->addPartSource(pss); - } - } - if (mPartSourcep) - { - if (mPartSourcep->getImage()->getID() != mPartSourcep->mPartSysData.mPartImageID) - { - LLViewerTexture* image; - if (mPartSourcep->mPartSysData.mPartImageID == LLUUID::null) - { - image = LLViewerTextureManager::getFetchedTextureFromFile("pixiesmall.j2c"); - } - else - { - image = LLViewerTextureManager::getFetchedTexture(mPartSourcep->mPartSysData.mPartImageID); - } - mPartSourcep->setImage(image); - } - } -} - -void LLViewerObject::deleteParticleSource() -{ - if (mPartSourcep.notNull()) - { - mPartSourcep->setDead(); - mPartSourcep = NULL; - } -} - -// virtual -void LLViewerObject::updateDrawable(bool force_damped) -{ - if (!isChanged(MOVED)) - { //most common case, having an empty if case here makes for better branch prediction - } - else if (mDrawable.notNull() && - !mDrawable->isState(LLDrawable::ON_MOVE_LIST)) - { - bool damped_motion = - !isChanged(SHIFTED) && // not shifted between regions this frame and... - ( force_damped || // ...forced into damped motion by application logic or... - ( !isSelected() && // ...not selected and... - ( mDrawable->isRoot() || // ... is root or ... - (getParent() && !((LLViewerObject*)getParent())->isSelected())// ... parent is not selected and ... - ) && - getPCode() == LL_PCODE_VOLUME && // ...is a volume object and... - getVelocity().isExactlyZero() && // ...is not moving physically and... - mDrawable->getGeneration() != -1 // ...was not created this frame. - ) - ); - gPipeline.markMoved(mDrawable, damped_motion); - } - clearChanged(SHIFTED); -} - -// virtual, overridden by LLVOVolume -F32 LLViewerObject::getVObjRadius() const -{ - return mDrawable.notNull() ? mDrawable->getRadius() : 0.f; -} - -void LLViewerObject::setAttachedSound(const LLUUID &audio_uuid, const LLUUID& owner_id, const F32 gain, const U8 flags) -{ - if (!gAudiop) - { - return; - } - - if (audio_uuid.isNull()) - { - if (!mAudioSourcep) - { - return; - } - if (mAudioSourcep->isLoop() && !mAudioSourcep->hasPendingPreloads()) - { - // We don't clear the sound if it's a loop, it'll go away on its own. - // At least, this appears to be how the scripts work. - // The attached sound ID is set to NULL to avoid it playing back when the - // object rezzes in on non-looping sounds. - //LL_INFOS() << "Clearing attached sound " << mAudioSourcep->getCurrentData()->getID() << LL_ENDL; - gAudiop->cleanupAudioSource(mAudioSourcep); - mAudioSourcep = NULL; - } - else if (flags & LL_SOUND_FLAG_STOP) - { - // Just shut off the sound - mAudioSourcep->stop(); - } - return; - } - if (flags & LL_SOUND_FLAG_LOOP - && mAudioSourcep && mAudioSourcep->isLoop() && mAudioSourcep->getCurrentData() - && mAudioSourcep->getCurrentData()->getID() == audio_uuid) - { - //LL_INFOS() << "Already playing this sound on a loop, ignoring" << LL_ENDL; - return; - } - - // don't clean up before previous sound is done. Solves: SL-33486 - if ( mAudioSourcep && mAudioSourcep->isDone() ) - { - gAudiop->cleanupAudioSource(mAudioSourcep); - mAudioSourcep = NULL; - } - - if (mAudioSourcep && mAudioSourcep->isMuted() && - mAudioSourcep->getCurrentData() && mAudioSourcep->getCurrentData()->getID() == audio_uuid) - { - //LL_INFOS() << "Already having this sound as muted sound, ignoring" << LL_ENDL; - return; - } - - getAudioSource(owner_id); - - if (mAudioSourcep) - { - bool queue = flags & LL_SOUND_FLAG_QUEUE; - mAudioGain = gain; - mAudioSourcep->setGain(gain); - mAudioSourcep->setLoop(flags & LL_SOUND_FLAG_LOOP); - mAudioSourcep->setSyncMaster(flags & LL_SOUND_FLAG_SYNC_MASTER); - mAudioSourcep->setSyncSlave(flags & LL_SOUND_FLAG_SYNC_SLAVE); - mAudioSourcep->setQueueSounds(queue); - if(!queue) // stop any current sound first to avoid "farts of doom" (SL-1541) -MG - { - mAudioSourcep->stop(); - } - - // Play this sound if region maturity permits - if( gAgent.canAccessMaturityAtGlobal(this->getPositionGlobal()) ) - { - //LL_INFOS() << "Playing attached sound " << audio_uuid << LL_ENDL; - // recheck cutoff radius in case this update was an object-update with new value - mAudioSourcep->checkCutOffRadius(); - mAudioSourcep->play(audio_uuid); - } - } -} - -LLAudioSource *LLViewerObject::getAudioSource(const LLUUID& owner_id) -{ - if (!mAudioSourcep) - { - // Arbitrary low gain for a sound that's not playing. - // This is used for sound preloads, for example. - LLAudioSourceVO *asvop = new LLAudioSourceVO(mID, owner_id, 0.01f, this); - - mAudioSourcep = asvop; - if(gAudiop) - { - gAudiop->addAudioSource(asvop); - } - } - - return mAudioSourcep; -} - -void LLViewerObject::adjustAudioGain(const F32 gain) -{ - if (mAudioSourcep) - { - mAudioGain = gain; - mAudioSourcep->setGain(mAudioGain); - } -} - -//---------------------------------------------------------------------------- - -bool LLViewerObject::unpackParameterEntry(U16 param_type, LLDataPacker *dp) -{ - if (LLNetworkData::PARAMS_MESH == param_type) - { - param_type = LLNetworkData::PARAMS_SCULPT; - } - ExtraParameter* param = getExtraParameterEntryCreate(param_type); - if (param) - { - param->data->unpack(*dp); - param->in_use = true; - parameterChanged(param_type, param->data, true, false); - return true; - } - else - { - return false; - } -} - -LLViewerObject::ExtraParameter* LLViewerObject::createNewParameterEntry(U16 param_type) -{ - LLNetworkData* new_block = NULL; - switch (param_type) - { - case LLNetworkData::PARAMS_FLEXIBLE: - { - new_block = new LLFlexibleObjectData(); - break; - } - case LLNetworkData::PARAMS_LIGHT: - { - new_block = new LLLightParams(); - break; - } - case LLNetworkData::PARAMS_SCULPT: - { - new_block = new LLSculptParams(); - break; - } - case LLNetworkData::PARAMS_LIGHT_IMAGE: - { - new_block = new LLLightImageParams(); - break; - } - case LLNetworkData::PARAMS_EXTENDED_MESH: - { - new_block = new LLExtendedMeshParams(); - break; - } - case LLNetworkData::PARAMS_RENDER_MATERIAL: - { - new_block = new LLRenderMaterialParams(); - break; - } - case LLNetworkData::PARAMS_REFLECTION_PROBE: - { - new_block = new LLReflectionProbeParams(); - break; - } - default: - { - LL_INFOS_ONCE() << "Unknown param type: " << param_type << LL_ENDL; - break; - } - }; - - if (new_block) - { - ExtraParameter* new_entry = new ExtraParameter; - new_entry->data = new_block; - new_entry->in_use = false; // not in use yet - llassert(mExtraParameterList[param_type] == nullptr); // leak -- redundantly allocated parameter entry - mExtraParameterList[param_type] = new_entry; - return new_entry; - } - return NULL; -} - -LLViewerObject::ExtraParameter* LLViewerObject::getExtraParameterEntry(U16 param_type) const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VIEWER; - std::unordered_map::const_iterator itor = mExtraParameterList.find(param_type); - if (itor != mExtraParameterList.end()) - { - return itor->second; - } - return NULL; -} - -LLViewerObject::ExtraParameter* LLViewerObject::getExtraParameterEntryCreate(U16 param_type) -{ - ExtraParameter* param = getExtraParameterEntry(param_type); - if (!param) - { - param = createNewParameterEntry(param_type); - } - return param; -} - -LLNetworkData* LLViewerObject::getParameterEntry(U16 param_type) const -{ - ExtraParameter* param = getExtraParameterEntry(param_type); - if (param) - { - return param->data; - } - else - { - return NULL; - } -} - -bool LLViewerObject::getParameterEntryInUse(U16 param_type) const -{ - ExtraParameter* param = getExtraParameterEntry(param_type); - if (param) - { - return param->in_use; - } - else - { - return false; - } -} - -bool LLViewerObject::setParameterEntry(U16 param_type, const LLNetworkData& new_value, bool local_origin) -{ - ExtraParameter* param = getExtraParameterEntryCreate(param_type); - if (param) - { - if (param->in_use && new_value == *(param->data)) - { - return false; - } - param->in_use = true; - param->data->copy(new_value); - parameterChanged(param_type, param->data, true, local_origin); - return true; - } - else - { - return false; - } -} - -// Assumed to be called locally -// If in_use is true, will crate a new extra parameter if none exists. -// Should always return true. -bool LLViewerObject::setParameterEntryInUse(U16 param_type, bool in_use, bool local_origin) -{ - ExtraParameter* param = getExtraParameterEntryCreate(param_type); - if (param && param->in_use != in_use) - { - param->in_use = in_use; - parameterChanged(param_type, param->data, in_use, local_origin); - return true; - } - return false; -} - -void LLViewerObject::parameterChanged(U16 param_type, bool local_origin) -{ - ExtraParameter* param = getExtraParameterEntry(param_type); - if (param) - { - parameterChanged(param_type, param->data, param->in_use, local_origin); - } -} - -void LLViewerObject::parameterChanged(U16 param_type, LLNetworkData* data, bool in_use, bool local_origin) -{ - if (local_origin) - { - // *NOTE: Do not send the render material ID in this way as it will get - // out-of-sync with other sent client data. - // See LLViewerObject::setRenderMaterialID and LLGLTFMaterialList - llassert(param_type != LLNetworkData::PARAMS_RENDER_MATERIAL); - - LLViewerRegion* regionp = getRegion(); - if(!regionp) return; - - // Change happened on the viewer. Send the change up - U8 tmp[MAX_OBJECT_PARAMS_SIZE]; - LLDataPackerBinaryBuffer dpb(tmp, MAX_OBJECT_PARAMS_SIZE); - if (data->pack(dpb)) - { - U32 datasize = (U32)dpb.getCurrentSize(); - - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_ObjectExtraParams); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ObjectData); - msg->addU32Fast(_PREHASH_ObjectLocalID, mLocalID ); - - msg->addU16Fast(_PREHASH_ParamType, param_type); - msg->addBOOLFast(_PREHASH_ParamInUse, in_use); - - msg->addU32Fast(_PREHASH_ParamSize, datasize); - msg->addBinaryDataFast(_PREHASH_ParamData, tmp, datasize); - - msg->sendReliable( regionp->getHost() ); - } - else - { - LL_WARNS() << "Failed to send object extra parameters: " << param_type << LL_ENDL; - } - } - else - { - if (param_type == LLNetworkData::PARAMS_RENDER_MATERIAL) - { - const LLRenderMaterialParams* params = in_use ? (LLRenderMaterialParams*)getParameterEntry(LLNetworkData::PARAMS_RENDER_MATERIAL) : nullptr; - setRenderMaterialIDs(params, local_origin); - } - } -} - -void LLViewerObject::setDrawableState(U32 state, bool recursive) -{ - if (mDrawable) - { - mDrawable->setState(state); - } - if (recursive) - { - for (child_list_t::iterator iter = mChildList.begin(); - iter != mChildList.end(); iter++) - { - LLViewerObject* child = *iter; - child->setDrawableState(state, recursive); - } - } -} - -void LLViewerObject::clearDrawableState(U32 state, bool recursive) -{ - if (mDrawable) - { - mDrawable->clearState(state); - } - if (recursive) - { - for (child_list_t::iterator iter = mChildList.begin(); - iter != mChildList.end(); iter++) - { - LLViewerObject* child = *iter; - child->clearDrawableState(state, recursive); - } - } -} - -bool LLViewerObject::isDrawableState(U32 state, bool recursive) const -{ - bool matches = false; - if (mDrawable) - { - matches = mDrawable->isState(state); - } - if (recursive) - { - for (child_list_t::const_iterator iter = mChildList.begin(); - (iter != mChildList.end()) && matches; iter++) - { - LLViewerObject* child = *iter; - matches &= child->isDrawableState(state, recursive); - } - } - - return matches; -} - - - -//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -// RN: these functions assume a 2-level hierarchy -//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - -// Owned by anyone? -bool LLViewerObject::permAnyOwner() const -{ - if (isRootEdit()) - { - return flagObjectAnyOwner(); - } - else - { - return ((LLViewerObject*)getParent())->permAnyOwner(); - } -} -// Owned by this viewer? -bool LLViewerObject::permYouOwner() const -{ - if (isRootEdit()) - { -#ifdef HACKED_GODLIKE_VIEWER - return true; -#else -# ifdef TOGGLE_HACKED_GODLIKE_VIEWER - if (!LLGridManager::getInstance()->isInProductionGrid() - && (gAgent.getGodLevel() >= GOD_MAINTENANCE)) - { - return true; - } -# endif - return flagObjectYouOwner(); -#endif - } - else - { - return ((LLViewerObject*)getParent())->permYouOwner(); - } -} - -// Owned by a group? -bool LLViewerObject::permGroupOwner() const -{ - if (isRootEdit()) - { - return flagObjectGroupOwned(); - } - else - { - return ((LLViewerObject*)getParent())->permGroupOwner(); - } -} - -// Can the owner edit -bool LLViewerObject::permOwnerModify() const -{ - if (isRootEdit()) - { -#ifdef HACKED_GODLIKE_VIEWER - return true; -#else -# ifdef TOGGLE_HACKED_GODLIKE_VIEWER - if (!LLGridManager::getInstance()->isInProductionGrid() - && (gAgent.getGodLevel() >= GOD_MAINTENANCE)) - { - return true; - } -# endif - return flagObjectOwnerModify(); -#endif - } - else - { - return ((LLViewerObject*)getParent())->permOwnerModify(); - } -} - -// Can edit -bool LLViewerObject::permModify() const -{ - if (isRootEdit()) - { -#ifdef HACKED_GODLIKE_VIEWER - return true; -#else -# ifdef TOGGLE_HACKED_GODLIKE_VIEWER - if (!LLGridManager::getInstance()->isInProductionGrid() - && (gAgent.getGodLevel() >= GOD_MAINTENANCE)) - { - return true; - } -# endif - return flagObjectModify(); -#endif - } - else - { - return ((LLViewerObject*)getParent())->permModify(); - } -} - -// Can copy -bool LLViewerObject::permCopy() const -{ - if (isRootEdit()) - { -#ifdef HACKED_GODLIKE_VIEWER - return true; -#else -# ifdef TOGGLE_HACKED_GODLIKE_VIEWER - if (!LLGridManager::getInstance()->isInProductionGrid() - && (gAgent.getGodLevel() >= GOD_MAINTENANCE)) - { - return true; - } -# endif - return flagObjectCopy(); -#endif - } - else - { - return ((LLViewerObject*)getParent())->permCopy(); - } -} - -// Can move -bool LLViewerObject::permMove() const -{ - if (isRootEdit()) - { -#ifdef HACKED_GODLIKE_VIEWER - return true; -#else -# ifdef TOGGLE_HACKED_GODLIKE_VIEWER - if (!LLGridManager::getInstance()->isInProductionGrid() - && (gAgent.getGodLevel() >= GOD_MAINTENANCE)) - { - return true; - } -# endif - return flagObjectMove(); -#endif - } - else - { - return ((LLViewerObject*)getParent())->permMove(); - } -} - -// Can be transferred -bool LLViewerObject::permTransfer() const -{ - if (isRootEdit()) - { -#ifdef HACKED_GODLIKE_VIEWER - return true; -#else -# ifdef TOGGLE_HACKED_GODLIKE_VIEWER - if (!LLGridManager::getInstance()->isInProductionGrid() - && (gAgent.getGodLevel() >= GOD_MAINTENANCE)) - { - return true; - } -# endif - return flagObjectTransfer(); -#endif - } - else - { - return ((LLViewerObject*)getParent())->permTransfer(); - } -} - -// Can only open objects that you own, or that someone has -// given you modify rights to. JC -bool LLViewerObject::allowOpen() const -{ - return !flagInventoryEmpty() && (permYouOwner() || permModify()); -} - -LLViewerObject::LLInventoryCallbackInfo::~LLInventoryCallbackInfo() -{ - if (mListener) - { - mListener->clearVOInventoryListener(); - } -} - -void LLViewerObject::updateVolume(const LLVolumeParams& volume_params) -{ - if (setVolume(volume_params, 1)) // *FIX: magic number, ack! - { - // Transmit the update to the simulator - sendShapeUpdate(); - markForUpdate(); - } -} - -void LLViewerObject::recursiveMarkForUpdate() -{ - for (LLViewerObject::child_list_t::iterator iter = mChildList.begin(); - iter != mChildList.end(); iter++) - { - LLViewerObject* child = *iter; - child->markForUpdate(); - } - markForUpdate(); -} - -void LLViewerObject::markForUpdate() -{ - if (mDrawable.notNull()) - { - gPipeline.markTextured(mDrawable); - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_GEOMETRY); - } -} - -bool LLViewerObject::isPermanentEnforced() const -{ - return flagObjectPermanent() && (mRegionp != gAgent.getRegion()) && !gAgent.isGodlike(); -} - -bool LLViewerObject::getIncludeInSearch() const -{ - return flagIncludeInSearch(); -} - -void LLViewerObject::setIncludeInSearch(bool include_in_search) -{ - setFlags(FLAGS_INCLUDE_IN_SEARCH, include_in_search); -} - -void LLViewerObject::setRegion(LLViewerRegion *regionp) -{ - if (!regionp) - { - LL_WARNS() << "viewer object set region to NULL" << LL_ENDL; - } - if(regionp != mRegionp) - { - if(mRegionp) - { - mRegionp->removeFromCreatedList(getLocalID()); - } - if(regionp) - { - regionp->addToCreatedList(getLocalID()); - } - } - - mLatestRecvPacketID = 0; - mRegionp = regionp; - - for (child_list_t::iterator i = mChildList.begin(); i != mChildList.end(); ++i) - { - LLViewerObject* child = *i; - child->setRegion(regionp); - } - - if (mControlAvatar) - { - mControlAvatar->setRegion(regionp); - } - - setChanged(MOVED | SILHOUETTE); - updateDrawable(false); -} - -// virtual -void LLViewerObject::updateRegion(LLViewerRegion *regionp) -{ -// if (regionp) -// { -// F64 now = LLFrameTimer::getElapsedSeconds(); -// LL_INFOS() << "Updating to region " << regionp->getName() -// << ", ms since last update message: " << (F32)((now - mLastMessageUpdateSecs) * 1000.0) -// << ", ms since last interpolation: " << (F32)((now - mLastInterpUpdateSecs) * 1000.0) -// << LL_ENDL; -// } -} - - -bool LLViewerObject::specialHoverCursor() const -{ - return flagUsePhysics() - || flagHandleTouch() - || (mClickAction != 0); -} - -void LLViewerObject::updateFlags(bool physics_changed) -{ - LLViewerRegion* regionp = getRegion(); - if(!regionp) return; - gMessageSystem->newMessage("ObjectFlagUpdate"); - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, getLocalID() ); - gMessageSystem->addBOOLFast(_PREHASH_UsePhysics, flagUsePhysics() ); - gMessageSystem->addBOOL("IsTemporary", flagTemporaryOnRez() ); - gMessageSystem->addBOOL("IsPhantom", flagPhantom() ); - - // stinson 02/28/2012 : This CastsShadows bool is no longer used in either the viewer or the simulator - // The simulator code does not even unpack this value when the message is received. - // This could be potentially hijacked in the future for another use should the urgent need arise. - gMessageSystem->addBOOL("CastsShadows", false ); - - if (physics_changed) - { - gMessageSystem->nextBlock("ExtraPhysics"); - gMessageSystem->addU8("PhysicsShapeType", getPhysicsShapeType() ); - gMessageSystem->addF32("Density", getPhysicsDensity() ); - gMessageSystem->addF32("Friction", getPhysicsFriction() ); - gMessageSystem->addF32("Restitution", getPhysicsRestitution() ); - gMessageSystem->addF32("GravityMultiplier", getPhysicsGravity() ); - } - gMessageSystem->sendReliable( regionp->getHost() ); -} - -bool LLViewerObject::setFlags(U32 flags, bool state) -{ - bool setit = setFlagsWithoutUpdate(flags, state); - - // BUG: Sometimes viewer physics and simulator physics get - // out of sync. To fix this, always send update to simulator. -// if (setit) - { - updateFlags(); - } - return setit; -} - -bool LLViewerObject::setFlagsWithoutUpdate(U32 flags, bool state) -{ - bool setit = false; - if (state) - { - if ((mFlags & flags) != flags) - { - mFlags |= flags; - setit = true; - } - } - else - { - if ((mFlags & flags) != 0) - { - mFlags &= ~flags; - setit = true; - } - } - return setit; -} - -void LLViewerObject::setPhysicsShapeType(U8 type) -{ - mPhysicsShapeUnknown = false; - if (type != mPhysicsShapeType) - { - mPhysicsShapeType = type; - setObjectCostStale(); -} -} - -void LLViewerObject::setPhysicsGravity(F32 gravity) -{ - mPhysicsGravity = gravity; -} - -void LLViewerObject::setPhysicsFriction(F32 friction) -{ - mPhysicsFriction = friction; -} - -void LLViewerObject::setPhysicsDensity(F32 density) -{ - mPhysicsDensity = density; -} - -void LLViewerObject::setPhysicsRestitution(F32 restitution) -{ - mPhysicsRestitution = restitution; -} - -U8 LLViewerObject::getPhysicsShapeType() const -{ - if (mPhysicsShapeUnknown) - { - gObjectList.updatePhysicsFlags(this); - } - - return mPhysicsShapeType; -} - -void LLViewerObject::applyAngularVelocity(F32 dt) -{ - //do target omega here - mRotTime += dt; - LLVector3 ang_vel = getAngularVelocity(); - F32 omega = ang_vel.magVecSquared(); - F32 angle = 0.0f; - LLQuaternion dQ; - if (omega > 0.00001f) - { - omega = sqrt(omega); - angle = omega * dt; - - ang_vel *= 1.f/omega; - - // calculate the delta increment based on the object's angular velocity - dQ.setQuat(angle, ang_vel); - - // accumulate the angular velocity rotations to re-apply in the case of an object update - mAngularVelocityRot *= dQ; - - // Just apply the delta increment to the current rotation - setRotation(getRotation()*dQ); - setChanged(MOVED | SILHOUETTE); - } -} - -void LLViewerObject::resetRotTime() -{ - mRotTime = 0.0f; -} - -void LLViewerObject::resetRot() -{ - resetRotTime(); - - // Reset the accumulated angular velocity rotation - mAngularVelocityRot.loadIdentity(); -} - -U32 LLViewerObject::getPartitionType() const -{ - return LLViewerRegion::PARTITION_NONE; -} - -void LLViewerObject::dirtySpatialGroup() const -{ - if (mDrawable) - { - LLSpatialGroup* group = mDrawable->getSpatialGroup(); - if (group) - { - group->dirtyGeom(); - gPipeline.markRebuild(group); - } - } -} - -void LLViewerObject::dirtyMesh() -{ - if (mDrawable) - { - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); - } -} - -F32 LLAlphaObject::getPartSize(S32 idx) -{ - return 0.f; -} - -void LLAlphaObject::getBlendFunc(S32 face, LLRender::eBlendFactor& src, LLRender::eBlendFactor& dst) -{ - -} - -// virtual -void LLStaticViewerObject::updateDrawable(bool force_damped) -{ - // Force an immediate rebuild on any update - if (mDrawable.notNull()) - { - mDrawable->updateXform(true); - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); - } - clearChanged(SHIFTED); -} - -void LLViewerObject::saveUnselectedChildrenPosition(std::vector& positions) -{ - if(mChildList.empty() || !positions.empty()) - { - return ; - } - - for (LLViewerObject::child_list_t::const_iterator iter = mChildList.begin(); - iter != mChildList.end(); iter++) - { - LLViewerObject* childp = *iter; - if (!childp->isSelected() && childp->mDrawable.notNull()) - { - positions.push_back(childp->getPositionEdit()); - } - } - - return ; -} - -void LLViewerObject::saveUnselectedChildrenRotation(std::vector& rotations) -{ - if(mChildList.empty()) - { - return ; - } - - for (LLViewerObject::child_list_t::const_iterator iter = mChildList.begin(); - iter != mChildList.end(); iter++) - { - LLViewerObject* childp = *iter; - if (!childp->isSelected() && childp->mDrawable.notNull()) - { - rotations.push_back(childp->getRotationEdit()); - } - } - - return ; -} - -//counter-rotation -void LLViewerObject::resetChildrenRotationAndPosition(const std::vector& rotations, - const std::vector& positions) -{ - if(mChildList.empty()) - { - return ; - } - - S32 index = 0 ; - LLQuaternion inv_rotation = ~getRotationEdit() ; - LLVector3 offset = getPositionEdit() ; - for (LLViewerObject::child_list_t::const_iterator iter = mChildList.begin(); - iter != mChildList.end(); iter++) - { - LLViewerObject* childp = *iter; - if (!childp->isSelected() && childp->mDrawable.notNull()) - { - if (childp->getPCode() != LL_PCODE_LEGACY_AVATAR) - { - childp->setRotation(rotations[index] * inv_rotation); - childp->setPosition((positions[index] - offset) * inv_rotation); - LLManip::rebuild(childp); - } - else //avatar - { - LLVector3 reset_pos = (positions[index] - offset) * inv_rotation ; - LLQuaternion reset_rot = rotations[index] * inv_rotation ; - - ((LLVOAvatar*)childp)->mDrawable->mXform.setPosition(reset_pos); - ((LLVOAvatar*)childp)->mDrawable->mXform.setRotation(reset_rot) ; - - ((LLVOAvatar*)childp)->mDrawable->getVObj()->setPosition(reset_pos, true); - ((LLVOAvatar*)childp)->mDrawable->getVObj()->setRotation(reset_rot, true) ; - - LLManip::rebuild(childp); - } - index++; - } - } - - return ; -} - -//counter-translation -void LLViewerObject::resetChildrenPosition(const LLVector3& offset, bool simplified, bool skip_avatar_child) -{ - if(mChildList.empty()) - { - return ; - } - - LLVector3 child_offset; - if(simplified) //translation only, rotation matrix does not change - { - child_offset = offset * ~getRotation(); - } - else //rotation matrix might change too. - { - if (isAttachment() && mDrawable.notNull()) - { - LLXform* attachment_point_xform = mDrawable->getXform()->getParent(); - LLQuaternion parent_rotation = getRotation() * attachment_point_xform->getWorldRotation(); - child_offset = offset * ~parent_rotation; - } - else - { - child_offset = offset * ~getRenderRotation(); - } - } - - for (LLViewerObject::child_list_t::const_iterator iter = mChildList.begin(); - iter != mChildList.end(); iter++) - { - LLViewerObject* childp = *iter; - - if (!childp->isSelected() && childp->mDrawable.notNull()) - { - if (childp->getPCode() != LL_PCODE_LEGACY_AVATAR) - { - childp->setPosition(childp->getPosition() + child_offset); - LLManip::rebuild(childp); - } - else //avatar - { - if(!skip_avatar_child) - { - LLVector3 reset_pos = ((LLVOAvatar*)childp)->mDrawable->mXform.getPosition() + child_offset ; - - ((LLVOAvatar*)childp)->mDrawable->mXform.setPosition(reset_pos); - ((LLVOAvatar*)childp)->mDrawable->getVObj()->setPosition(reset_pos); - LLManip::rebuild(childp); - } - } - } - } - - return ; -} - -// virtual -bool LLViewerObject::isTempAttachment() const -{ - return (mID.notNull() && (mID == mAttachmentItemID)); -} - -bool LLViewerObject::isHiglightedOrBeacon() const -{ - if (LLFloaterReg::instanceVisible("beacons") && (gPipeline.getRenderBeacons() || gPipeline.getRenderHighlights())) - { - bool has_media = (getMediaType() == LLViewerObject::MEDIA_SET); - bool is_scripted = !isAvatar() && !getParent() && flagScripted(); - bool is_physical = !isAvatar() && flagUsePhysics(); - - return (isParticleSource() && gPipeline.getRenderParticleBeacons()) - || (isAudioSource() && gPipeline.getRenderSoundBeacons()) - || (has_media && gPipeline.getRenderMOAPBeacons()) - || (is_scripted && gPipeline.getRenderScriptedBeacons()) - || (is_scripted && flagHandleTouch() && gPipeline.getRenderScriptedTouchBeacons()) - || (is_physical && gPipeline.getRenderPhysicalBeacons()); - } - return false; -} - - -const LLUUID &LLViewerObject::getAttachmentItemID() const -{ - return mAttachmentItemID; -} - -void LLViewerObject::setAttachmentItemID(const LLUUID &id) -{ - mAttachmentItemID = id; -} - -EObjectUpdateType LLViewerObject::getLastUpdateType() const -{ - return mLastUpdateType; -} - -void LLViewerObject::setLastUpdateType(EObjectUpdateType last_update_type) -{ - mLastUpdateType = last_update_type; -} - -bool LLViewerObject::getLastUpdateCached() const -{ - return mLastUpdateCached; -} - -void LLViewerObject::setLastUpdateCached(bool last_update_cached) -{ - mLastUpdateCached = last_update_cached; -} - -const LLUUID &LLViewerObject::extractAttachmentItemID() -{ - LLUUID item_id = LLUUID::null; - LLNameValue* item_id_nv = getNVPair("AttachItemID"); - if( item_id_nv ) - { - const char* s = item_id_nv->getString(); - if( s ) - { - item_id.set(s); - } - } - setAttachmentItemID(item_id); - return getAttachmentItemID(); -} - -const std::string& LLViewerObject::getAttachmentItemName() const -{ - static std::string empty; - LLInventoryItem *item = gInventory.getItem(getAttachmentItemID()); - if (isAttachment() && item) - { - return item->getName(); - } - return empty; -} - -//virtual -LLVOAvatar* LLViewerObject::getAvatar() const -{ - if (getControlAvatar()) - { - return getControlAvatar(); - } - if (isAttachment()) - { - LLViewerObject* vobj = (LLViewerObject*) getParent(); - - while (vobj && !vobj->asAvatar()) - { - vobj = (LLViewerObject*) vobj->getParent(); - } - - return (LLVOAvatar*) vobj; - } - - return NULL; -} - -bool LLViewerObject::hasRenderMaterialParams() const -{ - return getParameterEntryInUse(LLNetworkData::PARAMS_RENDER_MATERIAL); -} - -void LLViewerObject::setHasRenderMaterialParams(bool has_materials) -{ - bool had_materials = hasRenderMaterialParams(); - - if (had_materials != has_materials) - { - if (has_materials) - { - setParameterEntryInUse(LLNetworkData::PARAMS_RENDER_MATERIAL, true, true); - } - else - { - setParameterEntryInUse(LLNetworkData::PARAMS_RENDER_MATERIAL, false, true); - } - } -} - -const LLUUID& LLViewerObject::getRenderMaterialID(U8 te) const -{ - LLRenderMaterialParams* param_block = (LLRenderMaterialParams*)getParameterEntry(LLNetworkData::PARAMS_RENDER_MATERIAL); - if (param_block) - { - return param_block->getMaterial(te); - } - - return LLUUID::null; -} - -void LLViewerObject::rebuildMaterial() -{ - llassert(!isDead()); - - faceMappingChanged(); - gPipeline.markTextured(mDrawable); -} - -void LLViewerObject::setRenderMaterialID(S32 te_in, const LLUUID& id, bool update_server, bool local_origin) -{ - // implementation is delicate - - // if update is bound for server, should always null out GLTFRenderMaterial and clear GLTFMaterialOverride even if ids haven't changed - // (the case where ids haven't changed indicates the user has reapplied the original material, in which case overrides should be dropped) - // otherwise, should only null out the render material where ids or overrides have changed - // (the case where ids have changed but overrides are still present is from unsynchronized updates from the simulator, or synchronized - // updates with solely transform overrides) - - llassert(!update_server || local_origin); - - S32 start_idx = 0; - S32 end_idx = getNumTEs(); - - if (te_in != -1) - { - start_idx = te_in; - end_idx = start_idx + 1; - } - - start_idx = llmax(start_idx, 0); - end_idx = llmin(end_idx, (S32) getNumTEs()); - - LLRenderMaterialParams* param_block = (LLRenderMaterialParams*)getParameterEntry(LLNetworkData::PARAMS_RENDER_MATERIAL); - if (!param_block && id.notNull()) - { // block doesn't exist, but it will need to - param_block = (LLRenderMaterialParams*)createNewParameterEntry(LLNetworkData::PARAMS_RENDER_MATERIAL)->data; - } - - - LLFetchedGLTFMaterial* new_material = nullptr; - if (id.notNull()) - { - new_material = gGLTFMaterialList.getMaterial(id); - } - - // update local state - for (S32 te = start_idx; te < end_idx; ++te) - { - LLTextureEntry* tep = getTE(te); - - // If local_origin=false (i.e. it's from the server), we know the - // material has updated or been created, because extra params are - // checked for equality on unpacking. In that case, checking the - // material ID for inequality won't work, because the material ID has - // already been set. - bool material_changed = !local_origin || !param_block || id != param_block->getMaterial(te); - - if (update_server) - { - // Clear most overrides so the render material better matches the material - // ID (preserve transforms). If overrides become passthrough, set the overrides - // to nullptr. - if (tep->setBaseMaterial()) - { - material_changed = true; - } - } - - if (update_server || material_changed) - { - tep->setGLTFRenderMaterial(nullptr); - } - - if (new_material != tep->getGLTFMaterial()) - { - tep->setGLTFMaterial(new_material, !update_server); - } - - if (material_changed && new_material) - { - // Sometimes, the material may change out from underneath the overrides. - // This is usually due to the server sending a new material ID, but - // the overrides have not changed due to being only texture - // transforms. Re-apply the overrides to the render material here, - // if present. - const LLGLTFMaterial* override_material = tep->getGLTFMaterialOverride(); - if (override_material) - { - new_material->onMaterialComplete([obj_id = getID(), te]() - { - LLViewerObject* obj = gObjectList.findObject(obj_id); - if (!obj) { return; } - LLTextureEntry* tep = obj->getTE(te); - if (!tep) { return; } - const LLGLTFMaterial* new_material = tep->getGLTFMaterial(); - if (!new_material) { return; } - const LLGLTFMaterial* override_material = tep->getGLTFMaterialOverride(); - if (!override_material) { return; } - LLGLTFMaterial* render_material = new LLFetchedGLTFMaterial(); - *render_material = *new_material; - render_material->applyOverride(*override_material); - tep->setGLTFRenderMaterial(render_material); - }); - } - } - } - - // signal to render pipe that render batches must be rebuilt for this object - if (!new_material) - { - rebuildMaterial(); - } - else - { - new_material->onMaterialComplete([obj_id = getID()]() - { - LLViewerObject* obj = gObjectList.findObject(obj_id); - if (obj) - { - obj->rebuildMaterial(); - } - }); - } - - // predictively update LLRenderMaterialParams (don't wait for server) - if (param_block) - { // update existing parameter block - for (S32 te = start_idx; te < end_idx; ++te) - { - param_block->setMaterial(te, id); - } - } - - if (update_server) - { - // update via ModifyMaterialParams cap (server will echo back changes) - for (S32 te = start_idx; te < end_idx; ++te) - { - // This sends a cleared version of this object's current material - // override, but the override should already be cleared due to - // calling setBaseMaterial above. - LLGLTFMaterialList::queueApply(this, te, id); - } - } - - if (!update_server) - { - // Land impact may have changed - setObjectCostStale(); - } -} - -void LLViewerObject::setRenderMaterialIDs(const LLUUID& id) -{ - setRenderMaterialID(-1, id); -} - -void LLViewerObject::setRenderMaterialIDs(const LLRenderMaterialParams* material_params, bool local_origin) -{ - if (!local_origin) - { - for (S32 te = 0; te < getNumTEs(); ++te) - { - const LLUUID& id = material_params ? material_params->getMaterial(te) : LLUUID::null; - // We know material_params has updated or been created, because - // extra params are checked for equality on unpacking. - setRenderMaterialID(te, id, false, false); - } - } -} - -void LLViewerObject::shrinkWrap() -{ - if (!mShouldShrinkWrap) - { - mShouldShrinkWrap = true; - if (mDrawable) - { // we weren't shrink wrapped before but we are now, update the spatial partition - gPipeline.markPartitionMove(mDrawable); - } - } -} - -class ObjectPhysicsProperties : public LLHTTPNode -{ -public: - virtual void post( - ResponsePtr responder, - const LLSD& context, - const LLSD& input) const - { - LLSD object_data = input["body"]["ObjectData"]; - S32 num_entries = object_data.size(); - - for ( S32 i = 0; i < num_entries; i++ ) - { - LLSD& curr_object_data = object_data[i]; - U32 local_id = curr_object_data["LocalID"].asInteger(); - - // Iterate through nodes at end, since it can be on both the regular AND hover list - struct f : public LLSelectedNodeFunctor - { - U32 mID; - f(const U32& id) : mID(id) {} - virtual bool apply(LLSelectNode* node) - { - return (node->getObject() && node->getObject()->mLocalID == mID ); - } - } func(local_id); - - LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode(&func); - - if (node) - { - // The LLSD message builder doesn't know how to handle U8, so we need to send as S8 and cast - U8 type = (U8)curr_object_data["PhysicsShapeType"].asInteger(); - F32 density = (F32)curr_object_data["Density"].asReal(); - F32 friction = (F32)curr_object_data["Friction"].asReal(); - F32 restitution = (F32)curr_object_data["Restitution"].asReal(); - F32 gravity = (F32)curr_object_data["GravityMultiplier"].asReal(); - - node->getObject()->setPhysicsShapeType(type); - node->getObject()->setPhysicsGravity(gravity); - node->getObject()->setPhysicsFriction(friction); - node->getObject()->setPhysicsDensity(density); - node->getObject()->setPhysicsRestitution(restitution); - } - } - - dialog_refresh_all(); - }; -}; - -LLHTTPRegistration - gHTTPRegistrationObjectPhysicsProperties("/message/ObjectPhysicsProperties"); - +/** + * @file llviewerobject.cpp + * @brief Base class for viewer objects + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llviewerobject.h" + +#include "llaudioengine.h" +#include "indra_constants.h" +#include "llmath.h" +#include "llflexibleobject.h" +#include "llviewercontrol.h" +#include "lldatapacker.h" +#include "llfasttimer.h" +#include "llfloaterreg.h" +#include "llfontgl.h" +#include "llframetimer.h" +#include "llhudicon.h" +#include "llinventory.h" +#include "llinventorydefines.h" +#include "llmaterialtable.h" +#include "llmutelist.h" +#include "llnamevalue.h" +#include "llprimitive.h" +#include "llquantize.h" +#include "llregionhandle.h" +#include "llsdserialize.h" +#include "lltree_common.h" +#include "llxfermanager.h" +#include "message.h" +#include "object_flags.h" + +#include "llaudiosourcevo.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llagentwearables.h" +#include "llbbox.h" +#include "llbox.h" +#include "llcylinder.h" +#include "llcontrolavatar.h" +#include "lldrawable.h" +#include "llface.h" +#include "llfloatertools.h" +#include "llfollowcam.h" +#include "llhudtext.h" +#include "llselectmgr.h" +#include "llrendersphere.h" +#include "lltooldraganddrop.h" +#include "lluiavatar.h" +#include "llviewercamera.h" +#include "llviewertexturelist.h" +#include "llviewerinventory.h" +#include "llviewerobjectlist.h" +#include "llviewerparceloverlay.h" +#include "llviewerpartsource.h" +#include "llviewerregion.h" +#include "llviewerstats.h" +#include "llviewertextureanim.h" +#include "llviewerwindow.h" // For getSpinAxis +#include "llvoavatar.h" +#include "llvoavatarself.h" +#include "llvograss.h" +#include "llvosky.h" +#include "llvolume.h" +#include "llvolumemessage.h" +#include "llvopartgroup.h" +#include "llvosurfacepatch.h" +#include "llvotree.h" +#include "llvovolume.h" +#include "llvowater.h" +#include "llworld.h" +#include "llui.h" +#include "pipeline.h" +#include "llviewernetwork.h" +#include "llvowlsky.h" +#include "llmanip.h" +#include "lltrans.h" +#include "llsdutil.h" +#include "llmediaentry.h" +#include "llfloaterperms.h" +#include "llvocache.h" +#include "llcleanup.h" +#include "llcallstack.h" +#include "llmeshrepository.h" +#include "llgltfmateriallist.h" +#include "llgl.h" + +//#define DEBUG_UPDATE_TYPE + +bool LLViewerObject::sVelocityInterpolate = true; +bool LLViewerObject::sPingInterpolate = true; + +U32 LLViewerObject::sNumZombieObjects = 0; +S32 LLViewerObject::sNumObjects = 0; +bool LLViewerObject::sMapDebug = true; +LLColor4 LLViewerObject::sEditSelectColor( 1.0f, 1.f, 0.f, 0.3f); // Edit OK +LLColor4 LLViewerObject::sNoEditSelectColor( 1.0f, 0.f, 0.f, 0.3f); // Can't edit +S32 LLViewerObject::sAxisArrowLength(50); + + +bool LLViewerObject::sPulseEnabled(false); +bool LLViewerObject::sUseSharedDrawables(false); // true + +// sMaxUpdateInterpolationTime must be greater than sPhaseOutUpdateInterpolationTime +F64Seconds LLViewerObject::sMaxUpdateInterpolationTime(3.0); // For motion interpolation: after X seconds with no updates, don't predict object motion +F64Seconds LLViewerObject::sPhaseOutUpdateInterpolationTime(2.0); // For motion interpolation: after Y seconds with no updates, taper off motion prediction +F64Seconds LLViewerObject::sMaxRegionCrossingInterpolationTime(1.0);// For motion interpolation: don't interpolate over this time on region crossing + +std::map LLViewerObject::sObjectDataMap; + +// The maximum size of an object extra parameters binary (packed) block +#define MAX_OBJECT_PARAMS_SIZE 1024 + +// At 45 Hz collisions seem stable and objects seem +// to settle down at a reasonable rate. +// JC 3/18/2003 + +const F32 PHYSICS_TIMESTEP = 1.f / 45.f; +const U32 MAX_INV_FILE_READ_FAILS = 25; +const S32 MAX_OBJECT_BINARY_DATA_SIZE = 60 + 16; + +const F64 INVENTORY_UPDATE_WAIT_TIME_DESYNC = 5; // seconds +const F64 INVENTORY_UPDATE_WAIT_TIME_OUTDATED = 1; + +// static +LLViewerObject *LLViewerObject::createObject(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp, S32 flags) +{ + LL_PROFILE_ZONE_SCOPED; + LL_DEBUGS("ObjectUpdate") << "creating " << id << LL_ENDL; + dumpStack("ObjectUpdateStack"); + + LLViewerObject *res = NULL; + + if (gNonInteractive + && pcode != LL_PCODE_LEGACY_AVATAR + && pcode != LL_VO_SURFACE_PATCH + && pcode != LL_VO_WATER + && pcode != LL_VO_VOID_WATER + && pcode != LL_VO_WL_SKY + && pcode != LL_VO_SKY + && pcode != LL_VO_PART_GROUP + ) + { + return res; + } + switch (pcode) + { + case LL_PCODE_VOLUME: + { + res = new LLVOVolume(id, pcode, regionp); break; + break; + } + case LL_PCODE_LEGACY_AVATAR: + { + if (id == gAgentID) + { + if (!gAgentAvatarp) + { + gAgentAvatarp = new LLVOAvatarSelf(id, pcode, regionp); + gAgentAvatarp->initInstance(); + gAgentWearables.setAvatarObject(gAgentAvatarp); + } + else + { + if (isAgentAvatarValid()) + { + gAgentAvatarp->updateRegion(regionp); + } + } + res = gAgentAvatarp; + } + else if (flags & CO_FLAG_CONTROL_AVATAR) + { + LLControlAvatar *control_avatar = new LLControlAvatar(id, pcode, regionp); + control_avatar->initInstance(); + res = control_avatar; + } + else if (flags & CO_FLAG_UI_AVATAR) + { + LLUIAvatar *ui_avatar = new LLUIAvatar(id, pcode, regionp); + ui_avatar->initInstance(); + res = ui_avatar; + } + else + { + LLVOAvatar *avatar = new LLVOAvatar(id, pcode, regionp); + avatar->initInstance(); + res = avatar; + } + break; + } + case LL_PCODE_LEGACY_GRASS: + res = new LLVOGrass(id, pcode, regionp); break; + case LL_PCODE_LEGACY_PART_SYS: +// LL_WARNS() << "Creating old part sys!" << LL_ENDL; +// res = new LLVOPart(id, pcode, regionp); break; + res = NULL; break; + case LL_PCODE_LEGACY_TREE: + res = new LLVOTree(id, pcode, regionp); break; + case LL_PCODE_TREE_NEW: +// LL_WARNS() << "Creating new tree!" << LL_ENDL; +// res = new LLVOTree(id, pcode, regionp); break; + res = NULL; break; + case LL_VO_SURFACE_PATCH: + res = new LLVOSurfacePatch(id, pcode, regionp); break; + case LL_VO_SKY: + res = new LLVOSky(id, pcode, regionp); break; + case LL_VO_VOID_WATER: + res = new LLVOVoidWater(id, pcode, regionp); break; + case LL_VO_WATER: + res = new LLVOWater(id, pcode, regionp); break; + case LL_VO_PART_GROUP: + res = new LLVOPartGroup(id, pcode, regionp); break; + case LL_VO_HUD_PART_GROUP: + res = new LLVOHUDPartGroup(id, pcode, regionp); break; + case LL_VO_WL_SKY: + res = new LLVOWLSky(id, pcode, regionp); break; + default: + LL_WARNS() << "Unknown object pcode " << (S32)pcode << LL_ENDL; + res = NULL; break; + } + + return res; +} + +LLViewerObject::LLViewerObject(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp, bool is_global) +: LLPrimitive(), + mChildList(), + mID(id), + mLocalID(0), + mTotalCRC(0), + mListIndex(-1), + mTEImages(NULL), + mTENormalMaps(NULL), + mTESpecularMaps(NULL), + mbCanSelect(true), + mFlags(0), + mPhysicsShapeType(0), + mPhysicsGravity(0), + mPhysicsFriction(0), + mPhysicsDensity(0), + mPhysicsRestitution(0), + mDrawable(), + mCreateSelected(false), + mRenderMedia(false), + mBestUpdatePrecision(0), + mText(), + mHudText(""), + mHudTextColor(LLColor4::white), + mControlAvatar(NULL), + mLastInterpUpdateSecs(0.f), + mLastMessageUpdateSecs(0.f), + mLatestRecvPacketID(0), + mRegionCrossExpire(0), + mData(NULL), + mAudioSourcep(NULL), + mAudioGain(1.f), + mSoundCutOffRadius(0.f), + mAppAngle(0.f), + mPixelArea(1024.f), + mInventory(NULL), + mInventorySerialNum(0), + mExpectedInventorySerialNum(0), + mInvRequestState(INVENTORY_REQUEST_STOPPED), + mInvRequestXFerId(0), + mInventoryDirty(false), + mRegionp(regionp), + mDead(false), + mOrphaned(false), + mUserSelected(false), + mOnActiveList(false), + mOnMap(false), + mStatic(false), + mSeatCount(0), + mNumFaces(0), + mRotTime(0.f), + mAngularVelocityRot(), + mPreviousRotation(), + mAttachmentState(0), + mMedia(NULL), + mClickAction(0), + mObjectCost(0), + mLinksetCost(0), + mPhysicsCost(0), + mLinksetPhysicsCost(0.f), + mCostStale(true), + mPhysicsShapeUnknown(true), + mAttachmentItemID(LLUUID::null), + mLastUpdateType(OUT_UNKNOWN), + mLastUpdateCached(false), + mCachedMuteListUpdateTime(0), + mCachedOwnerInMuteList(false), + mRiggedAttachedWarned(false) +{ + if (!is_global) + { + llassert(mRegionp); + } + + LLPrimitive::init_primitive(pcode); + + // CP: added 12/2/2005 - this was being initialised to 0, not the current frame time + mLastInterpUpdateSecs = LLFrameTimer::getElapsedSeconds(); + + mPositionRegion = LLVector3(0.f, 0.f, 0.f); + + if (!is_global && mRegionp) + { + mPositionAgent = mRegionp->getOriginAgent(); + } + resetRot(); + + LLViewerObject::sNumObjects++; +} + +LLViewerObject::~LLViewerObject() +{ + deleteTEImages(); + + // unhook from reflection probe manager + if (mReflectionProbe.notNull()) + { + mReflectionProbe->mViewerObject = nullptr; + mReflectionProbe = nullptr; + } + + if(mInventory) + { + mInventory->clear(); // will deref and delete entries + delete mInventory; + mInventory = NULL; + } + + if (mPartSourcep) + { + mPartSourcep->setDead(); + mPartSourcep = NULL; + } + + if (mText) + { + // something recovered LLHUDText when object was already dead + mText->markDead(); + mText = NULL; + } + + // Delete memory associated with extra parameters. + std::unordered_map::iterator iter; + for (iter = mExtraParameterList.begin(); iter != mExtraParameterList.end(); ++iter) + { + if(iter->second != NULL) + { + delete iter->second->data; + delete iter->second; + } + } + mExtraParameterList.clear(); + + for_each(mNameValuePairs.begin(), mNameValuePairs.end(), DeletePairedPointer()) ; + mNameValuePairs.clear(); + + delete[] mData; + mData = NULL; + + delete mMedia; + mMedia = NULL; + + sNumObjects--; + sNumZombieObjects--; + llassert(mChildList.size() == 0); + llassert(mControlAvatar.isNull()); // Should have been cleaned by now + if (mControlAvatar.notNull()) + { + mControlAvatar->markForDeath(); + mControlAvatar = NULL; + LL_WARNS() << "Dead object owned a live control avatar" << LL_ENDL; + } + + clearInventoryListeners(); +} + +void LLViewerObject::deleteTEImages() +{ + delete[] mTEImages; + mTEImages = NULL; + + if (mTENormalMaps != NULL) + { + delete[] mTENormalMaps; + mTENormalMaps = NULL; + } + + if (mTESpecularMaps != NULL) + { + delete[] mTESpecularMaps; + mTESpecularMaps = NULL; + } +} + +void LLViewerObject::markDead() +{ + if (!mDead) + { + LL_PROFILE_ZONE_SCOPED; + //LL_INFOS() << "Marking self " << mLocalID << " as dead." << LL_ENDL; + + // Root object of this hierarchy unlinks itself. + if (getParent()) + { + ((LLViewerObject *)getParent())->removeChild(this); + } + LLUUID mesh_id; + { + LLVOAvatar *av = getAvatar(); + if (av && LLVOAvatar::getRiggedMeshID(this,mesh_id)) + { + // This case is needed for indirectly attached mesh objects. + av->updateAttachmentOverrides(); + } + } + if (getControlAvatar()) + { + unlinkControlAvatar(); + } + + // Mark itself as dead + mDead = true; + if(mRegionp) + { + mRegionp->removeFromCreatedList(getLocalID()); + } + gObjectList.cleanupReferences(this); + + LLViewerObject *childp; + while (mChildList.size() > 0) + { + childp = mChildList.back(); + if (childp->getPCode() != LL_PCODE_LEGACY_AVATAR) + { + //LL_INFOS() << "Marking child " << childp->getLocalID() << " as dead." << LL_ENDL; + childp->setParent(NULL); // LLViewerObject::markDead 1 + childp->markDead(); + } + else + { + // make sure avatar is no longer parented, + // so we can properly set it's position + childp->setDrawableParent(NULL); + ((LLVOAvatar*)childp)->getOffObject(); + childp->setParent(NULL); // LLViewerObject::markDead 2 + } + mChildList.pop_back(); + } + + if (mDrawable.notNull()) + { + // Drawables are reference counted, mark as dead, then nuke the pointer. + mDrawable->markDead(); + mDrawable = NULL; + } + + if (mText) + { + mText->markDead(); + mText = NULL; + } + + if (mIcon) + { + mIcon->markDead(); + mIcon = NULL; + } + + if (mPartSourcep) + { + mPartSourcep->setDead(); + mPartSourcep = NULL; + } + + if (mAudioSourcep) + { + // Do some cleanup + if (gAudiop) + { + gAudiop->cleanupAudioSource(mAudioSourcep); + } + mAudioSourcep = NULL; + } + + if (flagAnimSource()) + { + if (isAgentAvatarValid()) + { + // stop motions associated with this object + gAgentAvatarp->stopMotionFromSource(mID); + } + } + + if (flagCameraSource()) + { + LLFollowCamMgr::getInstance()->removeFollowCamParams(mID); + } + + if (mReflectionProbe.notNull()) + { + mReflectionProbe->mViewerObject = nullptr; + mReflectionProbe = nullptr; + } + + sNumZombieObjects++; + } +} + +void LLViewerObject::dump() const +{ + LL_INFOS() << "Type: " << pCodeToString(mPrimitiveCode) << LL_ENDL; + LL_INFOS() << "Drawable: " << (LLDrawable *)mDrawable << LL_ENDL; + LL_INFOS() << "Update Age: " << LLFrameTimer::getElapsedSeconds() - mLastMessageUpdateSecs << LL_ENDL; + + LL_INFOS() << "Parent: " << getParent() << LL_ENDL; + LL_INFOS() << "ID: " << mID << LL_ENDL; + LL_INFOS() << "LocalID: " << mLocalID << LL_ENDL; + LL_INFOS() << "PositionRegion: " << getPositionRegion() << LL_ENDL; + LL_INFOS() << "PositionAgent: " << getPositionAgent() << LL_ENDL; + LL_INFOS() << "PositionGlobal: " << getPositionGlobal() << LL_ENDL; + LL_INFOS() << "Velocity: " << getVelocity() << LL_ENDL; + if (mDrawable.notNull() && + mDrawable->getNumFaces() && + mDrawable->getFace(0)) + { + LLFacePool *poolp = mDrawable->getFace(0)->getPool(); + if (poolp) + { + LL_INFOS() << "Pool: " << poolp << LL_ENDL; + LL_INFOS() << "Pool reference count: " << poolp->mReferences.size() << LL_ENDL; + } + } + //LL_INFOS() << "BoxTree Min: " << mDrawable->getBox()->getMin() << LL_ENDL; + //LL_INFOS() << "BoxTree Max: " << mDrawable->getBox()->getMin() << LL_ENDL; + /* + LL_INFOS() << "Velocity: " << getVelocity() << LL_ENDL; + LL_INFOS() << "AnyOwner: " << permAnyOwner() << " YouOwner: " << permYouOwner() << " Edit: " << mPermEdit << LL_ENDL; + LL_INFOS() << "UsePhysics: " << flagUsePhysics() << " CanSelect " << mbCanSelect << " UserSelected " << mUserSelected << LL_ENDL; + LL_INFOS() << "AppAngle: " << mAppAngle << LL_ENDL; + LL_INFOS() << "PixelArea: " << mPixelArea << LL_ENDL; + + char buffer[1000]; + char *key; + for (key = mNameValuePairs.getFirstKey(); key; key = mNameValuePairs.getNextKey() ) + { + mNameValuePairs[key]->printNameValue(buffer); + LL_INFOS() << buffer << LL_ENDL; + } + for (child_list_t::iterator iter = mChildList.begin(); + iter != mChildList.end(); iter++) + { + LLViewerObject* child = *iter; + LL_INFOS() << " child " << child->getID() << LL_ENDL; + } + */ +} + +void LLViewerObject::printNameValuePairs() const +{ + for (name_value_map_t::const_iterator iter = mNameValuePairs.begin(); + iter != mNameValuePairs.end(); iter++) + { + LLNameValue* nv = iter->second; + LL_INFOS() << nv->printNameValue() << LL_ENDL; + } +} + +void LLViewerObject::initVOClasses() +{ + // Initialized shared class stuff first. + LLVOAvatar::initClass(); + LLVOTree::initClass(); + LL_INFOS() << "Viewer Object size: " << sizeof(LLViewerObject) << LL_ENDL; + LLVOGrass::initClass(); + LLVOWater::initClass(); + LLVOVolume::initClass(); + + initObjectDataMap(); +} + +void LLViewerObject::cleanupVOClasses() +{ + SUBSYSTEM_CLEANUP(LLVOGrass); + SUBSYSTEM_CLEANUP(LLVOWater); + SUBSYSTEM_CLEANUP(LLVOTree); + SUBSYSTEM_CLEANUP(LLVOAvatar); + SUBSYSTEM_CLEANUP(LLVOVolume); + + sObjectDataMap.clear(); +} + +//object data map for compressed && !OUT_TERSE_IMPROVED +//static +void LLViewerObject::initObjectDataMap() +{ + U32 count = 0; + + sObjectDataMap["ID"] = count; //full id //LLUUID + count += sizeof(LLUUID); + + sObjectDataMap["LocalID"] = count; //U32 + count += sizeof(U32); + + sObjectDataMap["PCode"] = count; //U8 + count += sizeof(U8); + + sObjectDataMap["State"] = count; //U8 + count += sizeof(U8); + + sObjectDataMap["CRC"] = count; //U32 + count += sizeof(U32); + + sObjectDataMap["Material"] = count; //U8 + count += sizeof(U8); + + sObjectDataMap["ClickAction"] = count; //U8 + count += sizeof(U8); + + sObjectDataMap["Scale"] = count; //LLVector3 + count += sizeof(LLVector3); + + sObjectDataMap["Pos"] = count; //LLVector3 + count += sizeof(LLVector3); + + sObjectDataMap["Rot"] = count; //LLVector3 + count += sizeof(LLVector3); + + sObjectDataMap["SpecialCode"] = count; //U32 + count += sizeof(U32); + + sObjectDataMap["Owner"] = count; //LLUUID + count += sizeof(LLUUID); + + sObjectDataMap["Omega"] = count; //LLVector3, when SpecialCode & 0x80 is set + count += sizeof(LLVector3); + + //ParentID is after Omega if there is Omega, otherwise is after Owner + sObjectDataMap["ParentID"] = count;//U32, when SpecialCode & 0x20 is set + count += sizeof(U32); + + //------- + //The rest items are not included here + //------- +} + +//static +void LLViewerObject::unpackVector3(LLDataPackerBinaryBuffer* dp, LLVector3& value, std::string name) +{ + dp->shift(sObjectDataMap[name]); + dp->unpackVector3(value, name.c_str()); + dp->reset(); +} + +//static +void LLViewerObject::unpackUUID(LLDataPackerBinaryBuffer* dp, LLUUID& value, std::string name) +{ + dp->shift(sObjectDataMap[name]); + dp->unpackUUID(value, name.c_str()); + dp->reset(); +} + +//static +void LLViewerObject::unpackU32(LLDataPackerBinaryBuffer* dp, U32& value, std::string name) +{ + dp->shift(sObjectDataMap[name]); + dp->unpackU32(value, name.c_str()); + dp->reset(); +} + +//static +void LLViewerObject::unpackU8(LLDataPackerBinaryBuffer* dp, U8& value, std::string name) +{ + dp->shift(sObjectDataMap[name]); + dp->unpackU8(value, name.c_str()); + dp->reset(); +} + +//static +U32 LLViewerObject::unpackParentID(LLDataPackerBinaryBuffer* dp, U32& parent_id) +{ + dp->shift(sObjectDataMap["SpecialCode"]); + U32 value; + dp->unpackU32(value, "SpecialCode"); + + parent_id = 0; + if(value & 0x20) + { + S32 offset = sObjectDataMap["ParentID"]; + if(!(value & 0x80)) + { + offset -= sizeof(LLVector3); + } + + dp->shift(offset); + dp->unpackU32(parent_id, "ParentID"); + } + dp->reset(); + + return parent_id; +} + +// Replaces all name value pairs with data from \n delimited list +// Does not update server +void LLViewerObject::setNameValueList(const std::string& name_value_list) +{ + // Clear out the old + for_each(mNameValuePairs.begin(), mNameValuePairs.end(), DeletePairedPointer()) ; + mNameValuePairs.clear(); + + // Bring in the new + std::string::size_type length = name_value_list.length(); + std::string::size_type start = 0; + while (start < length) + { + std::string::size_type end = name_value_list.find_first_of("\n", start); + if (end == std::string::npos) end = length; + if (end > start) + { + std::string tok = name_value_list.substr(start, end - start); + addNVPair(tok); + } + start = end+1; + } +} + +bool LLViewerObject::isAnySelected() const +{ + bool any_selected = isSelected(); + for (child_list_t::const_iterator iter = mChildList.begin(); + iter != mChildList.end(); iter++) + { + const LLViewerObject* child = *iter; + any_selected = any_selected || child->isSelected(); + } + return any_selected; +} + +void LLViewerObject::setSelected(bool sel) +{ + mUserSelected = sel; + resetRot(); + + if (!sel) + { + setAllTESelected(false); + } +} + +// This method returns true if the object is over land owned by the +// agent. +bool LLViewerObject::isReturnable() +{ + if (isAttachment()) + { + return false; + } + + std::vector boxes; + boxes.push_back(LLBBox(getPositionRegion(), getRotationRegion(), getScale() * -0.5f, getScale() * 0.5f).getAxisAligned()); + for (child_list_t::iterator iter = mChildList.begin(); + iter != mChildList.end(); iter++) + { + LLViewerObject* child = *iter; + boxes.push_back( LLBBox(child->getPositionRegion(), child->getRotationRegion(), child->getScale() * -0.5f, child->getScale() * 0.5f).getAxisAligned()); + } + + bool result = (mRegionp && mRegionp->objectIsReturnable(getPositionRegion(), boxes)) ? 1 : 0; + + if ( !result ) + { + //Get list of neighboring regions relative to this vo's region + std::vector uniqueRegions; + mRegionp->getNeighboringRegions( uniqueRegions ); + + //Build aabb's - for root and all children + std::vector returnables; + typedef std::vector::iterator RegionIt; + RegionIt regionStart = uniqueRegions.begin(); + RegionIt regionEnd = uniqueRegions.end(); + + for (; regionStart != regionEnd; ++regionStart ) + { + LLViewerRegion* pTargetRegion = *regionStart; + //Add the root vo as there may be no children and we still want + //to test for any edge overlap + buildReturnablesForChildrenVO( returnables, this, pTargetRegion ); + //Add it's children + for (child_list_t::iterator iter = mChildList.begin(); iter != mChildList.end(); iter++) + { + LLViewerObject* pChild = *iter; + buildReturnablesForChildrenVO( returnables, pChild, pTargetRegion ); + } + } + + //TBD#Eventually create a region -> box list map + typedef std::vector::iterator ReturnablesIt; + ReturnablesIt retCurrentIt = returnables.begin(); + ReturnablesIt retEndIt = returnables.end(); + + for ( ; retCurrentIt !=retEndIt; ++retCurrentIt ) + { + boxes.clear(); + LLViewerRegion* pRegion = (*retCurrentIt).pRegion; + boxes.push_back( (*retCurrentIt).box ); + bool retResult = pRegion + && pRegion->childrenObjectReturnable( boxes ) + && pRegion->canManageEstate(); + if ( retResult ) + { + result = true; + break; + } + } + } + return result; +} + +void LLViewerObject::buildReturnablesForChildrenVO( std::vector& returnables, LLViewerObject* pChild, LLViewerRegion* pTargetRegion ) +{ + if ( !pChild ) + { + LL_ERRS()<<"child viewerobject is NULL "<mChildList.begin(); iter != pChild->mChildList.end(); iter++) + { + LLViewerObject* pChildofChild = *iter; + buildReturnablesForChildrenVO( returnables, pChildofChild, pTargetRegion ); + } +} + +void LLViewerObject::constructAndAddReturnable( std::vector& returnables, LLViewerObject* pChild, LLViewerRegion* pTargetRegion ) +{ + + LLVector3 targetRegionPos; + targetRegionPos.setVec( pChild->getPositionGlobal() ); + + LLBBox childBBox = LLBBox( targetRegionPos, pChild->getRotationRegion(), pChild->getScale() * -0.5f, + pChild->getScale() * 0.5f).getAxisAligned(); + + LLVector3 edgeA = targetRegionPos + childBBox.getMinLocal(); + LLVector3 edgeB = targetRegionPos + childBBox.getMaxLocal(); + + LLVector3d edgeAd, edgeBd; + edgeAd.setVec(edgeA); + edgeBd.setVec(edgeB); + + //Only add the box when either of the extents are in a neighboring region + if ( pTargetRegion->pointInRegionGlobal( edgeAd ) || pTargetRegion->pointInRegionGlobal( edgeBd ) ) + { + PotentialReturnableObject returnableObj; + returnableObj.box = childBBox; + returnableObj.pRegion = pTargetRegion; + returnables.push_back( returnableObj ); + } +} + +bool LLViewerObject::crossesParcelBounds() +{ + std::vector boxes; + boxes.push_back(LLBBox(getPositionRegion(), getRotationRegion(), getScale() * -0.5f, getScale() * 0.5f).getAxisAligned()); + for (child_list_t::iterator iter = mChildList.begin(); + iter != mChildList.end(); iter++) + { + LLViewerObject* child = *iter; + boxes.push_back(LLBBox(child->getPositionRegion(), child->getRotationRegion(), child->getScale() * -0.5f, child->getScale() * 0.5f).getAxisAligned()); + } + + return mRegionp && mRegionp->objectsCrossParcel(boxes); +} + +bool LLViewerObject::setParent(LLViewerObject* parent) +{ + if(mParent != parent) + { + LLViewerObject* old_parent = (LLViewerObject*)mParent ; + bool ret = LLPrimitive::setParent(parent); + if(ret && old_parent && parent) + { + old_parent->removeChild(this) ; + } + return ret ; + } + + return false ; +} + +void LLViewerObject::addChild(LLViewerObject *childp) +{ + for (child_list_t::iterator i = mChildList.begin(); i != mChildList.end(); ++i) + { + if (*i == childp) + { //already has child + return; + } + } + + if (!isAvatar()) + { + // propagate selection properties + childp->mbCanSelect = mbCanSelect; + } + + if(childp->setParent(this)) + { + mChildList.push_back(childp); + childp->afterReparent(); + + if (childp->isAvatar()) + { + mSeatCount++; + } + } +} + +void LLViewerObject::onReparent(LLViewerObject *old_parent, LLViewerObject *new_parent) +{ +} + +void LLViewerObject::afterReparent() +{ +} + +void LLViewerObject::removeChild(LLViewerObject *childp) +{ + for (child_list_t::iterator i = mChildList.begin(); i != mChildList.end(); ++i) + { + if (*i == childp) + { + if (!childp->isAvatar() && mDrawable.notNull() && mDrawable->isActive() && childp->mDrawable.notNull() && !isAvatar()) + { + gPipeline.markRebuild(childp->mDrawable, LLDrawable::REBUILD_VOLUME); + } + + mChildList.erase(i); + + if(childp->getParent() == this) + { + childp->setParent(NULL); + } + + if (childp->isAvatar()) + { + mSeatCount--; + } + break; + } + } + + if (childp->isSelected()) + { + LLSelectMgr::getInstance()->deselectObjectAndFamily(childp); + bool add_to_end = true; + LLSelectMgr::getInstance()->selectObjectAndFamily(childp, add_to_end); + } +} + +void LLViewerObject::addThisAndAllChildren(std::vector& objects) +{ + objects.push_back(this); + for (child_list_t::iterator iter = mChildList.begin(); + iter != mChildList.end(); iter++) + { + LLViewerObject* child = *iter; + if (!child->isAvatar()) + { + child->addThisAndAllChildren(objects); + } + } +} + +void LLViewerObject::addThisAndNonJointChildren(std::vector& objects) +{ + objects.push_back(this); + // don't add any attachments when temporarily selecting avatar + if (isAvatar()) + { + return; + } + for (child_list_t::iterator iter = mChildList.begin(); + iter != mChildList.end(); iter++) + { + LLViewerObject* child = *iter; + if ( (!child->isAvatar())) + { + child->addThisAndNonJointChildren(objects); + } + } +} + +bool LLViewerObject::isChild(LLViewerObject *childp) const +{ + for (child_list_t::const_iterator iter = mChildList.begin(); + iter != mChildList.end(); iter++) + { + LLViewerObject* testchild = *iter; + if (testchild == childp) + return true; + } + return false; +} + +// returns true if at least one avatar is sitting on this object +bool LLViewerObject::isSeat() const +{ + return mSeatCount > 0; +} + +bool LLViewerObject::setDrawableParent(LLDrawable* parentp) +{ + if (mDrawable.isNull()) + { + return false; + } + + bool ret = mDrawable->mXform.setParent(parentp ? &parentp->mXform : NULL); + if(!ret) + { + return false ; + } + LLDrawable* old_parent = mDrawable->mParent; + mDrawable->mParent = parentp; + + if (parentp && mDrawable->isActive()) + { + parentp->makeActive(); + parentp->setState(LLDrawable::ACTIVE_CHILD); + } + + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); + if( (old_parent != parentp && old_parent) + || (parentp && parentp->isActive())) + { + // *TODO we should not be relying on setDrawable parent to call markMoved + gPipeline.markMoved(mDrawable, false); + } + else if (!mDrawable->isAvatar()) + { + mDrawable->updateXform(true); + /*if (!mDrawable->getSpatialGroup()) + { + mDrawable->movePartition(); + }*/ + } + + return ret; +} + +// Show or hide particles, icon and HUD +void LLViewerObject::hideExtraDisplayItems( bool hidden ) +{ + if( mPartSourcep.notNull() ) + { + LLViewerPartSourceScript *partSourceScript = mPartSourcep.get(); + partSourceScript->setSuspended( hidden ); + } + + if( mText.notNull() ) + { + LLHUDText *hudText = mText.get(); + hudText->setHidden( hidden ); + } + + if( mIcon.notNull() ) + { + LLHUDIcon *hudIcon = mIcon.get(); + hudIcon->setHidden( hidden ); + } +} + +U32 LLViewerObject::checkMediaURL(const std::string &media_url) +{ + U32 retval = (U32)0x0; + if (!mMedia && !media_url.empty()) + { + retval |= MEDIA_URL_ADDED; + mMedia = new LLViewerObjectMedia; + mMedia->mMediaURL = media_url; + mMedia->mMediaType = LLViewerObject::MEDIA_SET; + mMedia->mPassedWhitelist = false; + } + else if (mMedia) + { + if (media_url.empty()) + { + retval |= MEDIA_URL_REMOVED; + delete mMedia; + mMedia = NULL; + } + else if (mMedia->mMediaURL != media_url) // <-- This is an optimization. If they are equal don't bother with below's test. + { + /*if (! (LLTextureEntry::getAgentIDFromMediaVersionString(media_url) == gAgent.getID() && + LLTextureEntry::getVersionFromMediaVersionString(media_url) == + LLTextureEntry::getVersionFromMediaVersionString(mMedia->mMediaURL) + 1)) + */ + { + // If the media URL is different and WE were not the one who + // changed it, mark dirty. + retval |= MEDIA_URL_UPDATED; + } + mMedia->mMediaURL = media_url; + mMedia->mPassedWhitelist = false; + } + } + return retval; +} + +//extract spatial information from object update message +//return parent_id +//static +U32 LLViewerObject::extractSpatialExtents(LLDataPackerBinaryBuffer *dp, LLVector3& pos, LLVector3& scale, LLQuaternion& rot) +{ + U32 parent_id = 0; + LLViewerObject::unpackParentID(dp, parent_id); + + LLViewerObject::unpackVector3(dp, scale, "Scale"); + LLViewerObject::unpackVector3(dp, pos, "Pos"); + + LLVector3 vec; + LLViewerObject::unpackVector3(dp, vec, "Rot"); + rot.unpackFromVector3(vec); + + return parent_id; +} + +U32 LLViewerObject::processUpdateMessage(LLMessageSystem *mesgsys, + void **user_data, + U32 block_num, + const EObjectUpdateType update_type, + LLDataPacker *dp) +{ + LL_PROFILE_ZONE_SCOPED; + LL_DEBUGS_ONCE("SceneLoadTiming") << "Received viewer object data" << LL_ENDL; + + LL_DEBUGS("ObjectUpdate") << " mesgsys " << mesgsys << " dp " << dp << " id " << getID() << " update_type " << (S32) update_type << LL_ENDL; + dumpStack("ObjectUpdateStack"); + + // The new OBJECTDATA_FIELD_SIZE_124, OBJECTDATA_FIELD_SIZE_140, OBJECTDATA_FIELD_SIZE_80 + // and OBJECTDATA_FIELD_SIZE_64 lengths should be supported in the existing cases below. + // Each case should start at the beginning of the buffer and extract all known + // values, and ignore any unknown data at the end of the buffer. + // This allows new data in the future without breaking current viewers. + const S32 OBJECTDATA_FIELD_SIZE_140 = 140; // Full precision avatar update for future extended data + const S32 OBJECTDATA_FIELD_SIZE_124 = 124; // Full precision object update for future extended data + const S32 OBJECTDATA_FIELD_SIZE_76 = 76; // Full precision avatar update + const S32 OBJECTDATA_FIELD_SIZE_60 = 60; // Full precision object update + const S32 OBJECTDATA_FIELD_SIZE_80 = 80; // Terse avatar update, 16 bit precision for future extended data + const S32 OBJECTDATA_FIELD_SIZE_64 = 64; // Terse object update, 16 bit precision for future extended data + const S32 OBJECTDATA_FIELD_SIZE_48 = 48; // Terse avatar update, 16 bit precision + const S32 OBJECTDATA_FIELD_SIZE_32 = 32; // Terse object update, 16 bit precision + + U32 retval = 0x0; + + // If region is removed from the list it is also deleted. + if (!LLWorld::instance().isRegionListed(mRegionp)) + { + LL_WARNS() << "Updating object in an invalid region" << LL_ENDL; + return retval; + } + + // Coordinates of objects on simulators are region-local. + U64 region_handle = 0; + + if(mesgsys != NULL) + { + mesgsys->getU64Fast(_PREHASH_RegionData, _PREHASH_RegionHandle, region_handle); + LLViewerRegion* regionp = LLWorld::getInstance()->getRegionFromHandle(region_handle); + if(regionp != mRegionp && regionp && mRegionp)//region cross + { + //this is the redundant position and region update, but it is necessary in case the viewer misses the following + //position and region update messages from sim. + //this redundant update should not cause any problems. + LLVector3 delta_pos = mRegionp->getOriginAgent() - regionp->getOriginAgent(); + setPositionParent(getPosition() + delta_pos); //update to the new region position immediately. + setRegion(regionp) ; //change the region. + } + else + { + if(regionp != mRegionp) + { + if(mRegionp) + { + mRegionp->removeFromCreatedList(getLocalID()); + } + if(regionp) + { + regionp->addToCreatedList(getLocalID()); + } + } + mRegionp = regionp ; + } + } + + if (!mRegionp) + { + U32 x, y; + from_region_handle(region_handle, &x, &y); + + LL_WARNS("UpdateFail") << "Object has invalid region " << x << ":" << y << "!" << LL_ENDL; + return retval; + } + + F32 time_dilation = 1.f; + if(mesgsys != NULL) + { + U16 time_dilation16; + mesgsys->getU16Fast(_PREHASH_RegionData, _PREHASH_TimeDilation, time_dilation16); + time_dilation = ((F32) time_dilation16) / 65535.f; + mRegionp->setTimeDilation(time_dilation); + } + + // this will be used to determine if we've really changed position + // Use getPosition, not getPositionRegion, since this is what we're comparing directly against. + LLVector3 test_pos_parent = getPosition(); + + // This needs to match the largest size below. See switch(length) + U8 data[MAX_OBJECT_BINARY_DATA_SIZE]; + +#ifdef LL_BIG_ENDIAN + U16 valswizzle[4]; +#endif + U16 *val; + const F32 size = LLWorld::getInstance()->getRegionWidthInMeters(); + const F32 MAX_HEIGHT = LLWorld::getInstance()->getRegionMaxHeight(); + const F32 MIN_HEIGHT = LLWorld::getInstance()->getRegionMinHeight(); + S32 length = 0; + S32 count = 0; + S32 this_update_precision = 32; // in bits + + // Temporaries, because we need to compare w/ previous to set dirty flags... + LLVector3 new_pos_parent; + LLVector3 new_vel; + LLVector3 new_acc; + LLVector3 new_angv; + LLVector3 old_angv = getAngularVelocity(); + LLQuaternion new_rot; + LLVector3 new_scale = getScale(); + + U32 parent_id = 0; + U8 material = 0; + U8 click_action = 0; + U32 crc = 0; + + bool old_special_hover_cursor = specialHoverCursor(); + + LLViewerObject *cur_parentp = (LLViewerObject *)getParent(); + + if (cur_parentp) + { + parent_id = cur_parentp->mLocalID; + } + + if (!dp) + { + switch(update_type) + { + case OUT_FULL: + { +#ifdef DEBUG_UPDATE_TYPE + LL_INFOS() << "Full:" << getID() << LL_ENDL; +#endif + //clear cost and linkset cost + setObjectCostStale(); + if (isSelected()) + { + gFloaterTools->dirty(); + } + + LLUUID audio_uuid; + LLUUID owner_id; // only valid if audio_uuid or particle system is not null + F32 gain; + F32 cutoff; + U8 sound_flags; + + mesgsys->getU32Fast( _PREHASH_ObjectData, _PREHASH_CRC, crc, block_num); + mesgsys->getU32Fast( _PREHASH_ObjectData, _PREHASH_ParentID, parent_id, block_num); + mesgsys->getUUIDFast(_PREHASH_ObjectData, _PREHASH_Sound, audio_uuid, block_num ); + // HACK: Owner id only valid if non-null sound id or particle system + mesgsys->getUUIDFast(_PREHASH_ObjectData, _PREHASH_OwnerID, owner_id, block_num ); + mesgsys->getF32Fast( _PREHASH_ObjectData, _PREHASH_Gain, gain, block_num ); + mesgsys->getF32Fast( _PREHASH_ObjectData, _PREHASH_Radius, cutoff, block_num ); + mesgsys->getU8Fast( _PREHASH_ObjectData, _PREHASH_Flags, sound_flags, block_num ); + mesgsys->getU8Fast( _PREHASH_ObjectData, _PREHASH_Material, material, block_num ); + mesgsys->getU8Fast( _PREHASH_ObjectData, _PREHASH_ClickAction, click_action, block_num); + mesgsys->getVector3Fast(_PREHASH_ObjectData, _PREHASH_Scale, new_scale, block_num ); + length = mesgsys->getSizeFast(_PREHASH_ObjectData, block_num, _PREHASH_ObjectData); + mesgsys->getBinaryDataFast(_PREHASH_ObjectData, _PREHASH_ObjectData, data, length, block_num, MAX_OBJECT_BINARY_DATA_SIZE); + length = llmin(length, MAX_OBJECT_BINARY_DATA_SIZE); // getBinaryDataFast() safely fills the buffer to max_size + + mTotalCRC = crc; + // Might need to update mSourceMuted here to properly pick up new radius + mSoundCutOffRadius = cutoff; + + // Owner ID used for sound muting or particle system muting + setAttachedSound(audio_uuid, owner_id, gain, sound_flags); + + U8 old_material = getMaterial(); + if (old_material != material) + { + setMaterial(material); + if (mDrawable.notNull()) + { + gPipeline.markMoved(mDrawable, false); // undamped + } + } + setClickAction(click_action); + + count = 0; + LLVector4 collision_plane; + + switch(length) + { + case OBJECTDATA_FIELD_SIZE_140: + case OBJECTDATA_FIELD_SIZE_76: + // pull out collision normal for avatar + htolememcpy(collision_plane.mV, &data[count], MVT_LLVector4, sizeof(LLVector4)); + ((LLVOAvatar*)this)->setFootPlane(collision_plane); + count += sizeof(LLVector4); + + case OBJECTDATA_FIELD_SIZE_124: + case OBJECTDATA_FIELD_SIZE_60: + this_update_precision = 32; + // this is a full precision update + // pos + htolememcpy(new_pos_parent.mV, &data[count], MVT_LLVector3, sizeof(LLVector3)); + count += sizeof(LLVector3); + // vel + htolememcpy((void*)getVelocity().mV, &data[count], MVT_LLVector3, sizeof(LLVector3)); + count += sizeof(LLVector3); + // acc + htolememcpy((void*)getAcceleration().mV, &data[count], MVT_LLVector3, sizeof(LLVector3)); + count += sizeof(LLVector3); + // theta + { + LLVector3 vec; + htolememcpy(vec.mV, &data[count], MVT_LLVector3, sizeof(LLVector3)); + new_rot.unpackFromVector3(vec); + } + count += sizeof(LLVector3); + // omega + htolememcpy((void*)new_angv.mV, &data[count], MVT_LLVector3, sizeof(LLVector3)); + if (new_angv.isExactlyZero()) + { + // reset rotation time + resetRot(); + } + setAngularVelocity(new_angv); + count += sizeof(LLVector3); +#if LL_DARWIN + if (length == OBJECTDATA_FIELD_SIZE_76 || + length == OBJECTDATA_FIELD_SIZE_140) + { + setAngularVelocity(LLVector3::zero); + } +#endif + break; + + // length values 48, 32 and 16 were once in viewer code but + // are never sent by the SL simulator + default: + LL_WARNS("UpdateFail") << "Unexpected ObjectData buffer size " << length + << " for " << getID() << " with OUT_FULL message" << LL_ENDL; + } + + //////////////////////////////////////////////////// + // + // Here we handle data specific to the full message. + // + + U32 flags; + mesgsys->getU32Fast(_PREHASH_ObjectData, _PREHASH_UpdateFlags, flags, block_num); + // clear all but local flags + mFlags &= FLAGS_LOCAL; + mFlags |= flags; + + U8 state; + mesgsys->getU8Fast(_PREHASH_ObjectData, _PREHASH_State, state, block_num ); + mAttachmentState = state; + + // ...new objects that should come in selected need to be added to the selected list + mCreateSelected = ((flags & FLAGS_CREATE_SELECTED) != 0); + + // Set all name value pairs + S32 nv_size = mesgsys->getSizeFast(_PREHASH_ObjectData, block_num, _PREHASH_NameValue); + if (nv_size > 0) + { + std::string name_value_list; + mesgsys->getStringFast(_PREHASH_ObjectData, _PREHASH_NameValue, name_value_list, block_num); + setNameValueList(name_value_list); + } + + // Clear out any existing generic data + if (mData) + { + delete [] mData; + mData = NULL; + } + + // Dec 2023 new generic data: + // Trees work as before, this field contains genome data + // Not a tree: root objects send 1 byte with the number of + // total prims in the linkset + // If the generic data size is zero, then number of prims is 1 + // + // Viewers should not check for specific data sizes exactly, but if + // the field has data, process it from the start and ignore the remainder. + + // Check for appended generic data + const S32 GENERIC_DATA_BUFFER_SIZE = 16; + S32 data_size = mesgsys->getSizeFast(_PREHASH_ObjectData, block_num, _PREHASH_Data); + if (data_size > 0) + { // has generic data + if (getPCode() == LL_PCODE_LEGACY_TREE || getPCode() == LL_PCODE_TREE_NEW) + { + mData = new U8[data_size]; + mesgsys->getBinaryDataFast(_PREHASH_ObjectData, _PREHASH_Data, mData, data_size, block_num); + LL_DEBUGS("NewObjectData") << "Read " << data_size << " bytes tree genome data for " << getID() << ", pcode " + << getPCodeString() << ", value " << (S32) mData[0] << LL_ENDL; + } + else + { // Extract number of prims + U8 generic_data[GENERIC_DATA_BUFFER_SIZE]; + mesgsys->getBinaryDataFast(_PREHASH_ObjectData, _PREHASH_Data, + &generic_data[0], llmin(data_size, GENERIC_DATA_BUFFER_SIZE), block_num); + // This is sample code to extract the number of prims + // Future viewers should use it for their own purposes + if (!isAvatar()) + { + S32 num_prims = (S32) generic_data[0]; + LL_DEBUGS("NewObjectData") << "Root prim " << getID() << " has " + << num_prims << " prims in linkset" << LL_ENDL; + } + } + } + + S32 text_size = mesgsys->getSizeFast(_PREHASH_ObjectData, block_num, _PREHASH_Text); + if (text_size > 1) + { + // Setup object text + if (!mText) + { + initHudText(); + } + + std::string temp_string; + mesgsys->getStringFast(_PREHASH_ObjectData, _PREHASH_Text, temp_string, block_num ); + + LLColor4U coloru; + mesgsys->getBinaryDataFast(_PREHASH_ObjectData, _PREHASH_TextColor, coloru.mV, 4, block_num); + + // alpha was flipped so that it zero encoded better + coloru.mV[3] = 255 - coloru.mV[3]; + mText->setColor(LLColor4(coloru)); + mText->setString(temp_string); + + mHudText = temp_string; + mHudTextColor = LLColor4(coloru); + + setChanged(MOVED | SILHOUETTE); + } + else + { + if (mText.notNull()) + { + mText->markDead(); + mText = NULL; + } + mHudText.clear(); + } + + std::string media_url; + mesgsys->getStringFast(_PREHASH_ObjectData, _PREHASH_MediaURL, media_url, block_num); + retval |= checkMediaURL(media_url); + + // + // Unpack particle system data + // + unpackParticleSource(block_num, owner_id); + + // Mark all extra parameters not used + std::unordered_map::iterator iter; + for (iter = mExtraParameterList.begin(); iter != mExtraParameterList.end(); ++iter) + { + iter->second->in_use = false; + } + + // Unpack extra parameters + S32 size = mesgsys->getSizeFast(_PREHASH_ObjectData, block_num, _PREHASH_ExtraParams); + if (size > 0) + { + U8 *buffer = new U8[size]; + mesgsys->getBinaryDataFast(_PREHASH_ObjectData, _PREHASH_ExtraParams, buffer, size, block_num); + LLDataPackerBinaryBuffer dp(buffer, size); + + U8 num_parameters; + dp.unpackU8(num_parameters, "num_params"); + U8 param_block[MAX_OBJECT_PARAMS_SIZE]; + for (U8 param=0; paramsecond->in_use) + { + // Send an update message in case it was formerly in use + parameterChanged(iter->first, iter->second->data, false, false); + } + } + + break; + } + + case OUT_TERSE_IMPROVED: + { +#ifdef DEBUG_UPDATE_TYPE + LL_INFOS() << "TI:" << getID() << LL_ENDL; +#endif + length = mesgsys->getSizeFast(_PREHASH_ObjectData, block_num, _PREHASH_ObjectData); + mesgsys->getBinaryDataFast(_PREHASH_ObjectData, _PREHASH_ObjectData, data, length, block_num, MAX_OBJECT_BINARY_DATA_SIZE); + length = llmin(length, MAX_OBJECT_BINARY_DATA_SIZE); // getBinaryDataFast() safely fills the buffer to max_size + count = 0; + LLVector4 collision_plane; + + switch(length) + { + case OBJECTDATA_FIELD_SIZE_80: + case OBJECTDATA_FIELD_SIZE_48: + // pull out collision normal for avatar + htolememcpy(collision_plane.mV, &data[count], MVT_LLVector4, sizeof(LLVector4)); + ((LLVOAvatar*)this)->setFootPlane(collision_plane); + count += sizeof(LLVector4); + + case OBJECTDATA_FIELD_SIZE_64: + case OBJECTDATA_FIELD_SIZE_32: + // this is a terse 16 bit quantized update + this_update_precision = 16; + test_pos_parent.quantize16(-0.5f*size, 1.5f*size, MIN_HEIGHT, MAX_HEIGHT); + +#ifdef LL_BIG_ENDIAN + htolememcpy(valswizzle, &data[count], MVT_U16Vec3, 6); + val = valswizzle; +#else + val = (U16 *) &data[count]; +#endif + count += sizeof(U16)*3; + new_pos_parent.mV[VX] = U16_to_F32(val[VX], -0.5f*size, 1.5f*size); + new_pos_parent.mV[VY] = U16_to_F32(val[VY], -0.5f*size, 1.5f*size); + new_pos_parent.mV[VZ] = U16_to_F32(val[VZ], MIN_HEIGHT, MAX_HEIGHT); + +#ifdef LL_BIG_ENDIAN + htolememcpy(valswizzle, &data[count], MVT_U16Vec3, 6); + val = valswizzle; +#else + val = (U16 *) &data[count]; +#endif + count += sizeof(U16)*3; + setVelocity(U16_to_F32(val[VX], -size, size), + U16_to_F32(val[VY], -size, size), + U16_to_F32(val[VZ], -size, size)); + +#ifdef LL_BIG_ENDIAN + htolememcpy(valswizzle, &data[count], MVT_U16Vec3, 6); + val = valswizzle; +#else + val = (U16 *) &data[count]; +#endif + count += sizeof(U16)*3; + setAcceleration(U16_to_F32(val[VX], -size, size), + U16_to_F32(val[VY], -size, size), + U16_to_F32(val[VZ], -size, size)); + +#ifdef LL_BIG_ENDIAN + htolememcpy(valswizzle, &data[count], MVT_U16Quat, 8); + val = valswizzle; +#else + val = (U16 *) &data[count]; +#endif + count += sizeof(U16)*4; + new_rot.mQ[VX] = U16_to_F32(val[VX], -1.f, 1.f); + new_rot.mQ[VY] = U16_to_F32(val[VY], -1.f, 1.f); + new_rot.mQ[VZ] = U16_to_F32(val[VZ], -1.f, 1.f); + new_rot.mQ[VW] = U16_to_F32(val[VW], -1.f, 1.f); + +#ifdef LL_BIG_ENDIAN + htolememcpy(valswizzle, &data[count], MVT_U16Vec3, 6); + val = valswizzle; +#else + val = (U16 *) &data[count]; +#endif + new_angv.set(U16_to_F32(val[VX], -size, size), + U16_to_F32(val[VY], -size, size), + U16_to_F32(val[VZ], -size, size)); + setAngularVelocity(new_angv); + break; + + // Previous viewers had code for length 76, 60 or 16 byte length + // with full precision or 8 bit quanitzation, but the + // SL servers will never send those data formats. If you ever see this + // warning in Second Life, please file a bug report + default: + LL_WARNS("UpdateFail") << "Unexpected ObjectData buffer size " << length << " for " << getID() + << " with OUT_FULL message" << LL_ENDL; + } + + U8 state; + mesgsys->getU8Fast(_PREHASH_ObjectData, _PREHASH_State, state, block_num ); + mAttachmentState = state; + break; + } + + default: + LL_WARNS("UpdateFail") << "Unknown uncompressed update type " << update_type << " for " << getID() << LL_ENDL; + break; + } + } + else + { + // handle the compressed case - have dp datapacker + LLUUID sound_uuid; + LLUUID owner_id; + F32 gain = 0; + U8 sound_flags = 0; + F32 cutoff = 0; + + U16 val[4]; + + U8 state; + + dp->unpackU8(state, "State"); + mAttachmentState = state; + + switch(update_type) + { + case OUT_TERSE_IMPROVED: + { +#ifdef DEBUG_UPDATE_TYPE + LL_INFOS() << "CompTI:" << getID() << LL_ENDL; +#endif + U8 value; + dp->unpackU8(value, "agent"); + if (value) + { + LLVector4 collision_plane; + dp->unpackVector4(collision_plane, "Plane"); + ((LLVOAvatar*)this)->setFootPlane(collision_plane); + } + test_pos_parent = getPosition(); + dp->unpackVector3(new_pos_parent, "Pos"); + dp->unpackU16(val[VX], "VelX"); + dp->unpackU16(val[VY], "VelY"); + dp->unpackU16(val[VZ], "VelZ"); + setVelocity(U16_to_F32(val[VX], -128.f, 128.f), + U16_to_F32(val[VY], -128.f, 128.f), + U16_to_F32(val[VZ], -128.f, 128.f)); + dp->unpackU16(val[VX], "AccX"); + dp->unpackU16(val[VY], "AccY"); + dp->unpackU16(val[VZ], "AccZ"); + setAcceleration(U16_to_F32(val[VX], -64.f, 64.f), + U16_to_F32(val[VY], -64.f, 64.f), + U16_to_F32(val[VZ], -64.f, 64.f)); + + dp->unpackU16(val[VX], "ThetaX"); + dp->unpackU16(val[VY], "ThetaY"); + dp->unpackU16(val[VZ], "ThetaZ"); + dp->unpackU16(val[VS], "ThetaS"); + new_rot.mQ[VX] = U16_to_F32(val[VX], -1.f, 1.f); + new_rot.mQ[VY] = U16_to_F32(val[VY], -1.f, 1.f); + new_rot.mQ[VZ] = U16_to_F32(val[VZ], -1.f, 1.f); + new_rot.mQ[VS] = U16_to_F32(val[VS], -1.f, 1.f); + dp->unpackU16(val[VX], "AccX"); + dp->unpackU16(val[VY], "AccY"); + dp->unpackU16(val[VZ], "AccZ"); + new_angv.set(U16_to_F32(val[VX], -64.f, 64.f), + U16_to_F32(val[VY], -64.f, 64.f), + U16_to_F32(val[VZ], -64.f, 64.f)); + setAngularVelocity(new_angv); + } + break; + case OUT_FULL_COMPRESSED: + case OUT_FULL_CACHED: + { +#ifdef DEBUG_UPDATE_TYPE + LL_INFOS() << "CompFull:" << getID() << LL_ENDL; +#endif + setObjectCostStale(); + + if (isSelected()) + { + gFloaterTools->dirty(); + } + + dp->unpackU32(crc, "CRC"); + mTotalCRC = crc; + dp->unpackU8(material, "Material"); + U8 old_material = getMaterial(); + if (old_material != material) + { + setMaterial(material); + if (mDrawable.notNull()) + { + gPipeline.markMoved(mDrawable, false); // undamped + } + } + dp->unpackU8(click_action, "ClickAction"); + setClickAction(click_action); + dp->unpackVector3(new_scale, "Scale"); + dp->unpackVector3(new_pos_parent, "Pos"); + LLVector3 vec; + dp->unpackVector3(vec, "Rot"); + new_rot.unpackFromVector3(vec); + setAcceleration(LLVector3::zero); + + U32 value; + dp->unpackU32(value, "SpecialCode"); + dp->setPassFlags(value); + dp->unpackUUID(owner_id, "Owner"); + + mOwnerID = owner_id; + + if (value & 0x80) + { + dp->unpackVector3(new_angv, "Omega"); + setAngularVelocity(new_angv); + } + + if (value & 0x20) + { + dp->unpackU32(parent_id, "ParentID"); + } + else + { + parent_id = 0; + } + + S32 sp_size; + U32 size; + if (value & 0x2) + { + sp_size = 1; + delete [] mData; + mData = new U8[1]; + dp->unpackU8(((U8*)mData)[0], "TreeData"); + } + else if (value & 0x1) + { + dp->unpackU32(size, "ScratchPadSize"); + delete [] mData; + mData = new U8[size]; + dp->unpackBinaryData((U8 *)mData, sp_size, "PartData"); + } + else + { + mData = NULL; + } + + // Setup object text + if (!mText && (value & 0x4)) + { + initHudText(); + } + + if (value & 0x4) + { + std::string temp_string; + dp->unpackString(temp_string, "Text"); + LLColor4U coloru; + dp->unpackBinaryDataFixed(coloru.mV, 4, "Color"); + coloru.mV[3] = 255 - coloru.mV[3]; + mText->setColor(LLColor4(coloru)); + mText->setString(temp_string); + + mHudText = temp_string; + mHudTextColor = LLColor4(coloru); + + setChanged(TEXTURE); + } + else + { + if (mText.notNull()) + { + mText->markDead(); + mText = NULL; + } + mHudText.clear(); + } + + std::string media_url; + if (value & 0x200) + { + dp->unpackString(media_url, "MediaURL"); + } + retval |= checkMediaURL(media_url); + + // + // Unpack particle system data (legacy) + // + if (value & 0x8) + { + unpackParticleSource(*dp, owner_id, true); + } + else if (!(value & 0x400)) + { + deleteParticleSource(); + } + + // Mark all extra parameters not used + std::unordered_map::iterator iter; + for (iter = mExtraParameterList.begin(); iter != mExtraParameterList.end(); ++iter) + { + iter->second->in_use = false; + } + + // Unpack extra params + U8 num_parameters; + dp->unpackU8(num_parameters, "num_params"); + U8 param_block[MAX_OBJECT_PARAMS_SIZE]; + for (U8 param=0; paramunpackU16(param_type, "param_type"); + dp->unpackBinaryData(param_block, param_size, "param_data"); + //LL_INFOS() << "Param type: " << param_type << ", Size: " << param_size << LL_ENDL; + LLDataPackerBinaryBuffer dp2(param_block, param_size); + unpackParameterEntry(param_type, &dp2); + } + + for (iter = mExtraParameterList.begin(); iter != mExtraParameterList.end(); ++iter) + { + if (!iter->second->in_use) + { + // Send an update message in case it was formerly in use + parameterChanged(iter->first, iter->second->data, false, false); + } + } + + if (value & 0x10) + { + dp->unpackUUID(sound_uuid, "SoundUUID"); + dp->unpackF32(gain, "SoundGain"); + dp->unpackU8(sound_flags, "SoundFlags"); + dp->unpackF32(cutoff, "SoundRadius"); + } + + if (value & 0x100) + { + std::string name_value_list; + dp->unpackString(name_value_list, "NV"); + + setNameValueList(name_value_list); + } + + mTotalCRC = crc; + mSoundCutOffRadius = cutoff; + + setAttachedSound(sound_uuid, owner_id, gain, sound_flags); + + // only get these flags on updates from sim, not cached ones + // Preload these five flags for every object. + // Finer shades require the object to be selected, and the selection manager + // stores the extended permission info. + if(mesgsys != NULL) + { + U32 flags; + mesgsys->getU32Fast(_PREHASH_ObjectData, _PREHASH_UpdateFlags, flags, block_num); + loadFlags(flags); + } + } + break; + + default: + LL_WARNS("UpdateFail") << "Unknown compressed update type " << update_type << " for " << getID() << LL_ENDL; + break; + } + } + + // + // Fix object parenting. + // + bool b_changed_status = false; + + if (OUT_TERSE_IMPROVED != update_type) + { + // We only need to update parenting on full updates, terse updates + // don't send parenting information. + if (!cur_parentp) + { + if (parent_id == 0) + { + // No parent now, no parent in message -> do nothing + } + else + { + // No parent now, new parent in message -> attach to that parent if possible + LLUUID parent_uuid; + + if(mesgsys != NULL) + { + LLViewerObjectList::getUUIDFromLocal(parent_uuid, + parent_id, + mesgsys->getSenderIP(), + mesgsys->getSenderPort()); + } + else + { + LLViewerObjectList::getUUIDFromLocal(parent_uuid, + parent_id, + mRegionp->getHost().getAddress(), + mRegionp->getHost().getPort()); + } + + LLViewerObject *sent_parentp = gObjectList.findObject(parent_uuid); + + // + // Check to see if we have the corresponding viewer object for the parent. + // + if (sent_parentp && sent_parentp->getParent() == this) + { + // Try to recover if we attempt to attach a parent to its child + LL_WARNS("UpdateFail") << "Attempt to attach a parent to it's child: " << this->getID() << " to " + << sent_parentp->getID() << LL_ENDL; + this->removeChild(sent_parentp); + sent_parentp->setDrawableParent(NULL); + } + + if (sent_parentp && (sent_parentp != this) && !sent_parentp->isDead()) + { + if (((LLViewerObject*)sent_parentp)->isAvatar()) + { + //LL_DEBUGS("Avatar") << "ATT got object update for attachment " << LL_ENDL; + } + + // + // We have a viewer object for the parent, and it's not dead. + // Do the actual reparenting here. + // + + // new parent is valid + b_changed_status = true; + // ...no current parent, so don't try to remove child + if (mDrawable.notNull()) + { + if (mDrawable->isDead() || !mDrawable->getVObj()) + { + LL_WARNS("UpdateFail") << "Drawable is dead or no VObj!" << LL_ENDL; + sent_parentp->addChild(this); + } + else + { + if (!setDrawableParent(sent_parentp->mDrawable)) // LLViewerObject::processUpdateMessage 1 + { + // Bad, we got a cycle somehow. + // Kill both the parent and the child, and + // set cache misses for both of them. + LL_WARNS("UpdateFail") << "Attempting to recover from parenting cycle! " + << "Killing " << sent_parentp->getID() << " and " << getID() + << ", Adding to cache miss list" << LL_ENDL; + setParent(NULL); + sent_parentp->setParent(NULL); + getRegion()->addCacheMissFull(getLocalID()); + getRegion()->addCacheMissFull(sent_parentp->getLocalID()); + gObjectList.killObject(sent_parentp); + gObjectList.killObject(this); + return retval; + } + sent_parentp->addChild(this); + // make sure this object gets a non-damped update + if (sent_parentp->mDrawable.notNull()) + { + gPipeline.markMoved(sent_parentp->mDrawable, false); // undamped + } + } + } + else + { + sent_parentp->addChild(this); + } + + // Show particles, icon and HUD + hideExtraDisplayItems( false ); + + setChanged(MOVED | SILHOUETTE); + } + else + { + // + // No corresponding viewer object for the parent, put the various + // pieces on the orphan list. + // + + //parent_id + U32 ip, port; + + if(mesgsys != NULL) + { + ip = mesgsys->getSenderIP(); + port = mesgsys->getSenderPort(); + } + else + { + ip = mRegionp->getHost().getAddress(); + port = mRegionp->getHost().getPort(); + } + gObjectList.orphanize(this, parent_id, ip, port); + + // Hide particles, icon and HUD + hideExtraDisplayItems( true ); + } + } + } + else + { + // BUG: this is a bad assumption once border crossing is alowed + if ( (parent_id == cur_parentp->mLocalID) + &&(update_type == OUT_TERSE_IMPROVED)) + { + // Parent now, same parent in message -> do nothing + + // Debugging for suspected problems with local ids. + //LLUUID parent_uuid; + //LLViewerObjectList::getUUIDFromLocal(parent_uuid, parent_id, mesgsys->getSenderIP(), mesgsys->getSenderPort() ); + //if (parent_uuid != cur_parentp->getID() ) + //{ + // LL_ERRS() << "Local ID match but UUID mismatch of viewer object" << LL_ENDL; + //} + } + else + { + // Parented now, different parent in message + LLViewerObject *sent_parentp; + if (parent_id == 0) + { + // + // This object is no longer parented, we sent in a zero parent ID. + // + sent_parentp = NULL; + } + else + { + LLUUID parent_uuid; + + if(mesgsys != NULL) + { + LLViewerObjectList::getUUIDFromLocal(parent_uuid, + parent_id, + gMessageSystem->getSenderIP(), + gMessageSystem->getSenderPort()); + } + else + { + LLViewerObjectList::getUUIDFromLocal(parent_uuid, + parent_id, + mRegionp->getHost().getAddress(), + mRegionp->getHost().getPort()); + } + sent_parentp = gObjectList.findObject(parent_uuid); + + if (isAvatar()) + { + // This logic is meant to handle the case where a sitting avatar has reached a new sim + // ahead of the object she was sitting on (which is common as objects are transfered through + // a slower route than agents)... + // In this case, the local id for the object will not be valid, since the viewer has not received + // a full update for the object from that sim yet, so we assume that the agent is still sitting + // where she was originally. --RN + if (!sent_parentp) + { + sent_parentp = cur_parentp; + } + } + else if (!sent_parentp) + { + // + // Switching parents, but we don't know the new parent. + // + U32 ip, port; + + if(mesgsys != NULL) + { + ip = mesgsys->getSenderIP(); + port = mesgsys->getSenderPort(); + } + else + { + ip = mRegionp->getHost().getAddress(); + port = mRegionp->getHost().getPort(); + } + + // We're an orphan, flag things appropriately. + gObjectList.orphanize(this, parent_id, ip, port); + } + } + + // Reattach if possible. + if (sent_parentp && sent_parentp != cur_parentp && sent_parentp != this) + { + // New parent is valid, detach and reattach + b_changed_status = true; + if (mDrawable.notNull()) + { + if (!setDrawableParent(sent_parentp->mDrawable)) // LLViewerObject::processUpdateMessage 2 + { + // Bad, we got a cycle somehow. + // Kill both the parent and the child, and + // set cache misses for both of them. + LL_WARNS() << "Attempting to recover from parenting cycle!" << LL_ENDL; + LL_WARNS() << "Killing " << sent_parentp->getID() << " and " << getID() << LL_ENDL; + LL_WARNS() << "Adding to cache miss list" << LL_ENDL; + setParent(NULL); + sent_parentp->setParent(NULL); + getRegion()->addCacheMissFull(getLocalID()); + getRegion()->addCacheMissFull(sent_parentp->getLocalID()); + gObjectList.killObject(sent_parentp); + gObjectList.killObject(this); + return retval; + } + // make sure this object gets a non-damped update + } + cur_parentp->removeChild(this); + sent_parentp->addChild(this); + setChanged(MOVED | SILHOUETTE); + sent_parentp->setChanged(MOVED | SILHOUETTE); + if (sent_parentp->mDrawable.notNull()) + { + gPipeline.markMoved(sent_parentp->mDrawable, false); // undamped + } + } + else if (!sent_parentp) + { + bool remove_parent = true; + // No new parent, or the parent that we sent doesn't exist on the viewer. + LLViewerObject *parentp = (LLViewerObject *)getParent(); + if (parentp) + { + if (parentp->getRegion() != getRegion()) + { + // This is probably an object flying across a region boundary, the + // object probably ISN'T being reparented, but just got an object + // update out of order (child update before parent). + //LL_INFOS() << "Don't reparent object handoffs!" << LL_ENDL; + remove_parent = false; + } + } + + if (remove_parent) + { + b_changed_status = true; + if (mDrawable.notNull()) + { + // clear parent to removeChild can put the drawable on the damped list + setDrawableParent(NULL); // LLViewerObject::processUpdateMessage 3 + } + + cur_parentp->removeChild(this); + + setChanged(MOVED | SILHOUETTE); + + if (mDrawable.notNull()) + { + // make sure this object gets a non-damped update + gPipeline.markMoved(mDrawable, false); // undamped + } + } + } + } + } + } + + new_rot.normQuat(); + + if (sPingInterpolate && mesgsys != NULL) + { + LLCircuitData *cdp = gMessageSystem->mCircuitInfo.findCircuit(mesgsys->getSender()); + if (cdp) + { + // Note: delay is U32 and usually less then second, + // converting it into seconds with valueInUnits will result in 0 + F32 ping_delay = 0.5f * time_dilation * ( ((F32)cdp->getPingDelay().value()) * 0.001f + gFrameDTClamped); + LLVector3 diff = getVelocity() * ping_delay; + new_pos_parent += diff; + } + else + { + LL_WARNS() << "findCircuit() returned NULL; skipping interpolation" << LL_ENDL; + } + } + + ////////////////////////// + // + // Set the generic change flags... + // + // + + // If we're going to skip this message, why are we + // doing all the parenting, etc above? + if(mesgsys != NULL) + { + U32 packet_id = mesgsys->getCurrentRecvPacketID(); + if (packet_id < mLatestRecvPacketID && + mLatestRecvPacketID - packet_id < 65536) + { + //skip application of this message, it's old + return retval; + } + mLatestRecvPacketID = packet_id; + } + + // Set the change flags for scale + if (new_scale != getScale()) + { + setChanged(SCALED | SILHOUETTE); + setScale(new_scale); // Must follow setting permYouOwner() + } + + // first, let's see if the new position is actually a change + + //static S32 counter = 0; + + F32 vel_mag_sq = getVelocity().magVecSquared(); + F32 accel_mag_sq = getAcceleration().magVecSquared(); + + if ( ((b_changed_status)||(test_pos_parent != new_pos_parent)) + ||( (!isSelected()) + &&( (vel_mag_sq != 0.f) + ||(accel_mag_sq != 0.f) + ||(this_update_precision > mBestUpdatePrecision)))) + { + mBestUpdatePrecision = this_update_precision; + + LLVector3 diff = new_pos_parent - test_pos_parent ; + F32 mag_sqr = diff.magVecSquared() ; + if(llfinite(mag_sqr)) + { + setPositionParent(new_pos_parent); + } + else + { + LL_WARNS() << "Can not move the object/avatar to an infinite location!" << LL_ENDL ; + + retval |= INVALID_UPDATE ; + } + + if (mParent && ((LLViewerObject*)mParent)->isAvatar()) + { + // we have changed the position of an attachment, so we need to clamp it + LLVOAvatar *avatar = (LLVOAvatar*)mParent; + + avatar->clampAttachmentPositions(); + } + + // If we're snapping the position by more than 0.5m, update LLViewerStats::mAgentPositionSnaps + if ( asAvatar() && asAvatar()->isSelf() && (mag_sqr > 0.25f) ) + { + record(LLStatViewer::AGENT_POSITION_SNAP, LLUnit(diff.length())); + } + } + + if ((new_rot.isNotEqualEps(getRotation(), F_ALMOST_ZERO)) + || (new_angv != old_angv)) + { + if (new_rot != mPreviousRotation) + { + resetRot(); + } + else if (new_angv != old_angv) + { + if (flagUsePhysics()) + { + resetRot(); + } + else + { + resetRotTime(); + } + } + + // Remember the last rotation value + mPreviousRotation = new_rot; + + // Set the rotation of the object followed by adjusting for the accumulated angular velocity (llSetTargetOmega) + setRotation(new_rot * mAngularVelocityRot); + setChanged(ROTATED | SILHOUETTE); + } + + if ( gShowObjectUpdates ) + { + LLColor4 color; + if (update_type == OUT_TERSE_IMPROVED) + { + color.setVec(0.f, 0.f, 1.f, 1.f); + } + else + { + color.setVec(1.f, 0.f, 0.f, 1.f); + } + gPipeline.addDebugBlip(getPositionAgent(), color); + LL_DEBUGS("MessageBlip") << "Update type " << (S32)update_type << " blip for local " << mLocalID << " at " << getPositionAgent() << LL_ENDL; + } + + const F32 MAG_CUTOFF = F_APPROXIMATELY_ZERO; + + llassert(vel_mag_sq >= 0.f); + llassert(accel_mag_sq >= 0.f); + llassert(getAngularVelocity().magVecSquared() >= 0.f); + + if ((MAG_CUTOFF >= vel_mag_sq) && + (MAG_CUTOFF >= accel_mag_sq) && + (MAG_CUTOFF >= getAngularVelocity().magVecSquared())) + { + mStatic = true; // This object doesn't move! + } + else + { + mStatic = false; + } + +// BUG: This code leads to problems during group rotate and any scale operation. +// Small discepencies between the simulator and viewer representations cause the +// selection center to creep, leading to objects moving around the wrong center. +// +// Removing this, however, means that if someone else drags an object you have +// selected, your selection center and dialog boxes will be wrong. It also means +// that higher precision information on selected objects will be ignored. +// +// I believe the group rotation problem is fixed. JNC 1.21.2002 +// + // Additionally, if any child is selected, need to update the dialogs and selection + // center. + bool needs_refresh = mUserSelected; + for (child_list_t::iterator iter = mChildList.begin(); + iter != mChildList.end(); iter++) + { + LLViewerObject* child = *iter; + needs_refresh = needs_refresh || child->mUserSelected; + } + + static LLCachedControl allow_select_avatar(gSavedSettings, "AllowSelectAvatar", false); + if (needs_refresh) + { + LLSelectMgr::getInstance()->updateSelectionCenter(); + dialog_refresh_all(); + } + else if (allow_select_avatar && asAvatar()) + { + // Override any avatar position updates received + // Works only if avatar was repositioned using build + // tools and build floater is visible + LLSelectMgr::getInstance()->overrideAvatarUpdates(); + } + + + // Mark update time as approx. now, with the ping delay. + // Ping delay is off because it's not set for velocity interpolation, causing + // much jumping and hopping around... + +// U32 ping_delay = mesgsys->mCircuitInfo.getPingDelay(); + mLastInterpUpdateSecs = LLFrameTimer::getElapsedSeconds(); + mLastMessageUpdateSecs = mLastInterpUpdateSecs; + if (mDrawable.notNull()) + { + // Don't clear invisibility flag on update if still orphaned! + if (mDrawable->isState(LLDrawable::FORCE_INVISIBLE) && !mOrphaned) + { +// LL_DEBUGS() << "Clearing force invisible: " << mID << ":" << getPCodeString() << ":" << getPositionAgent() << LL_ENDL; + mDrawable->clearState(LLDrawable::FORCE_INVISIBLE); + gPipeline.markRebuild( mDrawable, LLDrawable::REBUILD_ALL); + } + } + + // Update special hover cursor status + bool special_hover_cursor = specialHoverCursor(); + if (old_special_hover_cursor != special_hover_cursor + && mDrawable.notNull()) + { + mDrawable->updateSpecialHoverCursor(special_hover_cursor); + } + + return retval; +} + +bool LLViewerObject::isActive() const +{ + return true; +} + +//load flags from cache or from message +void LLViewerObject::loadFlags(U32 flags) +{ + if(flags == (U32)(-1)) + { + return; //invalid + } + + // keep local flags and overwrite remote-controlled flags + mFlags = (mFlags & FLAGS_LOCAL) | flags; + + // ...new objects that should come in selected need to be added to the selected list + mCreateSelected = ((flags & FLAGS_CREATE_SELECTED) != 0); + return; +} + +void LLViewerObject::idleUpdate(LLAgent &agent, const F64 &frame_time) +{ + if (!mDead) + { + if (!mStatic && sVelocityInterpolate && !isSelected()) + { + // calculate dt from last update + F32 time_dilation = mRegionp ? mRegionp->getTimeDilation() : 1.0f; + F32 dt_raw = ((F64Seconds)frame_time - mLastInterpUpdateSecs).value(); + F32 dt = time_dilation * dt_raw; + + applyAngularVelocity(dt); + + if (isAttachment()) + { + mLastInterpUpdateSecs = (F64Seconds)frame_time; + return; + } + else + { // Move object based on it's velocity and rotation + interpolateLinearMotion(frame_time, dt); + } + } + + updateDrawable(false); + } +} + + +// Move an object due to idle-time viewer side updates by interpolating motion +void LLViewerObject::interpolateLinearMotion(const F64SecondsImplicit& frame_time, const F32SecondsImplicit& dt_seconds) +{ + // linear motion + // PHYSICS_TIMESTEP is used below to correct for the fact that the velocity in object + // updates represents the average velocity of the last timestep, rather than the final velocity. + // the time dilation above should guarantee that dt is never less than PHYSICS_TIMESTEP, theoretically + // + // *TODO: should also wrap linear accel/velocity in check + // to see if object is selected, instead of explicitly + // zeroing it out + + F32 dt = dt_seconds; + F64Seconds time_since_last_update = frame_time - mLastMessageUpdateSecs; + if (time_since_last_update <= (F64Seconds)0.0 || dt <= 0.f) + { + return; + } + + LLVector3 accel = getAcceleration(); + LLVector3 vel = getVelocity(); + + if (sMaxUpdateInterpolationTime <= (F64Seconds)0.0) + { // Old code path ... unbounded, simple interpolation + if (!(accel.isExactlyZero() && vel.isExactlyZero())) + { + LLVector3 pos = (vel + (0.5f * (dt-PHYSICS_TIMESTEP)) * accel) * dt; + + // region local + setPositionRegion(pos + getPositionRegion()); + setVelocity(vel + accel*dt); + + // for objects that are spinning but not translating, make sure to flag them as having moved + setChanged(MOVED | SILHOUETTE); + } + } + else if (!accel.isExactlyZero() || !vel.isExactlyZero()) // object is moving + { // Object is moving, and hasn't been too long since we got an update from the server + + // Calculate predicted position and velocity + LLVector3 new_pos = (vel + (0.5f * (dt-PHYSICS_TIMESTEP)) * accel) * dt; + LLVector3 new_v = accel * dt; + + if (time_since_last_update > sPhaseOutUpdateInterpolationTime && + sPhaseOutUpdateInterpolationTime > (F64Seconds)0.0) + { // Haven't seen a viewer update in a while, check to see if the circuit is still active + if (mRegionp) + { // The simulator will NOT send updates if the object continues normally on the path + // predicted by the velocity and the acceleration (often gravity) sent to the viewer + // So check to see if the circuit is blocked, which means the sim is likely in a long lag + LLCircuitData *cdp = gMessageSystem->mCircuitInfo.findCircuit( mRegionp->getHost() ); + if (cdp) + { + // Find out how many seconds since last packet arrived on the circuit + F64Seconds time_since_last_packet = LLMessageSystem::getMessageTimeSeconds() - cdp->getLastPacketInTime(); + + if (!cdp->isAlive() || // Circuit is dead or blocked + cdp->isBlocked() || // or doesn't seem to be getting any packets + (time_since_last_packet > sPhaseOutUpdateInterpolationTime)) + { + // Start to reduce motion interpolation since we haven't seen a server update in a while + F64Seconds time_since_last_interpolation = frame_time - mLastInterpUpdateSecs; + F64 phase_out = 1.0; + if (time_since_last_update > sMaxUpdateInterpolationTime) + { // Past the time limit, so stop the object + phase_out = 0.0; + //LL_INFOS() << "Motion phase out to zero" << LL_ENDL; + + // Kill angular motion as well. Note - not adding this due to paranoia + // about stopping rotation for llTargetOmega objects and not having it restart + // setAngularVelocity(LLVector3::zero); + } + else if (mLastInterpUpdateSecs - mLastMessageUpdateSecs > sPhaseOutUpdateInterpolationTime) + { // Last update was already phased out a bit + phase_out = (sMaxUpdateInterpolationTime - time_since_last_update) / + (sMaxUpdateInterpolationTime - time_since_last_interpolation); + //LL_INFOS() << "Continuing motion phase out of " << (F32) phase_out << LL_ENDL; + } + else + { // Phase out from full value + phase_out = (sMaxUpdateInterpolationTime - time_since_last_update) / + (sMaxUpdateInterpolationTime - sPhaseOutUpdateInterpolationTime); + //LL_INFOS() << "Starting motion phase out of " << (F32) phase_out << LL_ENDL; + } + phase_out = llclamp(phase_out, 0.0, 1.0); + + new_pos = new_pos * ((F32) phase_out); + new_v = new_v * ((F32) phase_out); + } + } + } + } + + new_pos = new_pos + getPositionRegion(); + new_v = new_v + vel; + + + // Clamp interpolated position to minimum underground and maximum region height + LLVector3d new_pos_global = mRegionp->getPosGlobalFromRegion(new_pos); + F32 min_height; + if (isAvatar()) + { // Make a better guess about AVs not going underground + min_height = LLWorld::getInstance()->resolveLandHeightGlobal(new_pos_global); + min_height += (0.5f * getScale().mV[VZ]); + } + else + { // This will put the object underground, but we can't tell if it will stop + // at ground level or not + min_height = LLWorld::getInstance()->getMinAllowedZ(this, new_pos_global); + // Cap maximum height + new_pos.mV[VZ] = llmin(LLWorld::getInstance()->getRegionMaxHeight(), new_pos.mV[VZ]); + } + + new_pos.mV[VZ] = llmax(min_height, new_pos.mV[VZ]); + + // Check to see if it's going off the region + LLVector3 temp(new_pos.mV[VX], new_pos.mV[VY], 0.f); + if (temp.clamp(0.f, mRegionp->getWidth())) + { // Going off this region, so see if we might end up on another region + LLVector3d old_pos_global = mRegionp->getPosGlobalFromRegion(getPositionRegion()); + new_pos_global = mRegionp->getPosGlobalFromRegion(new_pos); // Re-fetch in case it got clipped above + + // Clip the positions to known regions + LLVector3d clip_pos_global = LLWorld::getInstance()->clipToVisibleRegions(old_pos_global, new_pos_global); + if (clip_pos_global != new_pos_global) + { + // Was clipped, so this means we hit a edge where there is no region to enter + LLVector3 clip_pos = mRegionp->getPosRegionFromGlobal(clip_pos_global); + LL_DEBUGS("Interpolate") << "Hit empty region edge, clipped predicted position to " + << clip_pos + << " from " << new_pos << LL_ENDL; + new_pos = clip_pos; + + // Stop motion and get server update for bouncing on the edge + new_v.clear(); + setAcceleration(LLVector3::zero); + } + else + { + // Check for how long we are crossing. + // Note: theoretically we can find time from velocity, acceleration and + // distance from border to new position, but it is not going to work + // if 'phase_out' activates + if (mRegionCrossExpire == 0) + { + // Workaround: we can't accurately figure out time when we cross border + // so just write down time 'after the fact', it is far from optimal in + // case of lags, but for lags sMaxUpdateInterpolationTime will kick in first + LL_DEBUGS("Interpolate") << "Predicted region crossing, new position " << new_pos << LL_ENDL; + mRegionCrossExpire = frame_time + sMaxRegionCrossingInterpolationTime; + } + else if (frame_time > mRegionCrossExpire) + { + // Predicting crossing over 1s, stop motion + // Stop motion + LL_DEBUGS("Interpolate") << "Predicting region crossing for too long, stopping at " << new_pos << LL_ENDL; + new_v.clear(); + setAcceleration(LLVector3::zero); + mRegionCrossExpire = 0; + } + } + } + else + { + mRegionCrossExpire = 0; + } + + // Set new position and velocity + setPositionRegion(new_pos); + setVelocity(new_v); + + // for objects that are spinning but not translating, make sure to flag them as having moved + setChanged(MOVED | SILHOUETTE); + } + + // Update the last time we did anything + mLastInterpUpdateSecs = frame_time; +} + + + +// delete an item in the inventory, but don't tell the server. This is +// used internally by remove, update, and savescript. +// This will only delete the first item with an item_id in the list +void LLViewerObject::deleteInventoryItem(const LLUUID& item_id) +{ + if(mInventory) + { + LLInventoryObject::object_list_t::iterator it = mInventory->begin(); + LLInventoryObject::object_list_t::iterator end = mInventory->end(); + for( ; it != end; ++it ) + { + if((*it)->getUUID() == item_id) + { + // This is safe only because we return immediatly. + mInventory->erase(it); // will deref and delete it + return; + } + } + doInventoryCallback(); + } +} + +void LLViewerObject::doUpdateInventory( + LLPointer& item, + U8 key, + bool is_new) +{ + LLViewerInventoryItem* old_item = NULL; + if(TASK_INVENTORY_ITEM_KEY == key) + { + old_item = (LLViewerInventoryItem*)getInventoryObject(item->getUUID()); + } + else if(TASK_INVENTORY_ASSET_KEY == key) + { + old_item = getInventoryItemByAsset(item->getAssetUUID()); + } + LLUUID item_id; + LLUUID new_owner; + LLUUID new_group; + bool group_owned = false; + if(old_item) + { + item_id = old_item->getUUID(); + new_owner = old_item->getPermissions().getOwner(); + new_group = old_item->getPermissions().getGroup(); + group_owned = old_item->getPermissions().isGroupOwned(); + old_item = NULL; + } + else + { + item_id = item->getUUID(); + } + if(!is_new && mInventory) + { + // Attempt to update the local inventory. If we can get the + // object perm, we have perfect visibility, so we want the + // serial number to match. Otherwise, take our best guess and + // make sure that the serial number does not match. + deleteInventoryItem(item_id); + LLPermissions perm(item->getPermissions()); + LLPermissions* obj_perm = LLSelectMgr::getInstance()->findObjectPermissions(this); + bool is_atomic = (S32)LLAssetType::AT_OBJECT != item->getType(); + if(obj_perm) + { + perm.setOwnerAndGroup(LLUUID::null, obj_perm->getOwner(), obj_perm->getGroup(), is_atomic); + } + else + { + if(group_owned) + { + perm.setOwnerAndGroup(LLUUID::null, new_owner, new_group, is_atomic); + } + else if(!new_owner.isNull()) + { + // The object used to be in inventory, so we can + // assume the owner and group will match what they are + // there. + perm.setOwnerAndGroup(LLUUID::null, new_owner, new_group, is_atomic); + } + // *FIX: can make an even better guess by using the mPermGroup flags + else if(permYouOwner()) + { + // best guess. + perm.setOwnerAndGroup(LLUUID::null, gAgent.getID(), item->getPermissions().getGroup(), is_atomic); + --mExpectedInventorySerialNum; + } + else + { + // dummy it up. + perm.setOwnerAndGroup(LLUUID::null, LLUUID::null, LLUUID::null, is_atomic); + --mExpectedInventorySerialNum; + } + } + LLViewerInventoryItem* oldItem = item; + LLViewerInventoryItem* new_item = new LLViewerInventoryItem(oldItem); + new_item->setPermissions(perm); + mInventory->push_front(new_item); + doInventoryCallback(); + ++mExpectedInventorySerialNum; + } + else if (is_new) + { + ++mExpectedInventorySerialNum; + } +} + +// save a script, which involves removing the old one, and rezzing +// in the new one. This method should be called with the asset id +// of the new and old script AFTER the bytecode has been saved. +void LLViewerObject::saveScript( + const LLViewerInventoryItem* item, + bool active, + bool is_new) +{ + /* + * XXXPAM Investigate not making this copy. Seems unecessary, but I'm unsure about the + * interaction with doUpdateInventory() called below. + */ + LL_DEBUGS() << "LLViewerObject::saveScript() " << item->getUUID() << " " << item->getAssetUUID() << LL_ENDL; + + LLPointer task_item = + new LLViewerInventoryItem(item->getUUID(), mID, item->getPermissions(), + item->getAssetUUID(), item->getType(), + item->getInventoryType(), + item->getName(), item->getDescription(), + item->getSaleInfo(), item->getFlags(), + item->getCreationDate()); + task_item->setTransactionID(item->getTransactionID()); + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_RezScript); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addUUIDFast(_PREHASH_GroupID, gAgent.getGroupID()); + msg->nextBlockFast(_PREHASH_UpdateBlock); + msg->addU32Fast(_PREHASH_ObjectLocalID, (mLocalID)); + U8 enabled = active; + msg->addBOOLFast(_PREHASH_Enabled, enabled); + msg->nextBlockFast(_PREHASH_InventoryBlock); + task_item->packMessage(msg); + msg->sendReliable(mRegionp->getHost()); + + // do the internal logic + doUpdateInventory(task_item, TASK_INVENTORY_ITEM_KEY, is_new); +} + +void LLViewerObject::moveInventory(const LLUUID& folder_id, + const LLUUID& item_id) +{ + LL_DEBUGS() << "LLViewerObject::moveInventory " << item_id << LL_ENDL; + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_MoveTaskInventory); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addUUIDFast(_PREHASH_FolderID, folder_id); + msg->nextBlockFast(_PREHASH_InventoryData); + msg->addU32Fast(_PREHASH_LocalID, mLocalID); + msg->addUUIDFast(_PREHASH_ItemID, item_id); + msg->sendReliable(mRegionp->getHost()); + + LLInventoryObject* inv_obj = getInventoryObject(item_id); + if(inv_obj) + { + LLViewerInventoryItem* item = (LLViewerInventoryItem*)inv_obj; + if(!item->getPermissions().allowCopyBy(gAgent.getID())) + { + deleteInventoryItem(item_id); + ++mExpectedInventorySerialNum; + } + } +} + +void LLViewerObject::dirtyInventory() +{ + // If there aren't any LLVOInventoryListeners, we won't be + // able to update our mInventory when it comes back from the + // simulator, so we should not clear the inventory either. + if(mInventory && !mInventoryCallbacks.empty()) + { + mInventory->clear(); // will deref and delete entries + delete mInventory; + mInventory = NULL; + } + mInventoryDirty = true; +} + +void LLViewerObject::registerInventoryListener(LLVOInventoryListener* listener, void* user_data) +{ + LLInventoryCallbackInfo* info = new LLInventoryCallbackInfo; + info->mListener = listener; + info->mInventoryData = user_data; + mInventoryCallbacks.push_front(info); +} + +void LLViewerObject::removeInventoryListener(LLVOInventoryListener* listener) +{ + if (listener == NULL) + return; + for (callback_list_t::iterator iter = mInventoryCallbacks.begin(); + iter != mInventoryCallbacks.end(); ) + { + callback_list_t::iterator curiter = iter++; + LLInventoryCallbackInfo* info = *curiter; + if (info->mListener == listener) + { + delete info; + mInventoryCallbacks.erase(curiter); + break; + } + } +} + +bool LLViewerObject::isInventoryPending() +{ + return mInvRequestState != INVENTORY_REQUEST_STOPPED; +} + +void LLViewerObject::clearInventoryListeners() +{ + for_each(mInventoryCallbacks.begin(), mInventoryCallbacks.end(), DeletePointer()); + mInventoryCallbacks.clear(); +} + +bool LLViewerObject::hasInventoryListeners() +{ + return !mInventoryCallbacks.empty(); +} + +void LLViewerObject::requestInventory() +{ + if(mInventoryDirty && mInventory && !mInventoryCallbacks.empty()) + { + mInventory->clear(); // will deref and delete entries + delete mInventory; + mInventory = NULL; + } + + if(mInventory) + { + // inventory is either up to date or doesn't has a listener + // if it is dirty, leave it this way in case we gain a listener + doInventoryCallback(); + } + else + { + // since we are going to request it now + mInventoryDirty = false; + + // Note: throws away duplicate requests + fetchInventoryFromServer(); + } +} + +void LLViewerObject::fetchInventoryFromServer() +{ + if (!isInventoryPending()) + { + delete mInventory; + mInventory = NULL; + + // Results in processTaskInv + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_RequestTaskInventory); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_InventoryData); + msg->addU32Fast(_PREHASH_LocalID, mLocalID); + msg->sendReliable(mRegionp->getHost()); + + // This will get reset by doInventoryCallback or processTaskInv + mInvRequestState = INVENTORY_REQUEST_PENDING; + } +} + +void LLViewerObject::fetchInventoryDelayed(const F64 &time_seconds) +{ + // unless already waiting, drop previous request and shedule an update + if (mInvRequestState != INVENTORY_REQUEST_WAIT) + { + if (mInvRequestXFerId != 0) + { + // abort download. + gXferManager->abortRequestById(mInvRequestXFerId, -1); + mInvRequestXFerId = 0; + } + mInvRequestState = INVENTORY_REQUEST_WAIT; // affects isInventoryPending() + LLCoros::instance().launch("LLViewerObject::fetchInventoryDelayedCoro()", + boost::bind(&LLViewerObject::fetchInventoryDelayedCoro, mID, time_seconds)); + } +} + +//static +void LLViewerObject::fetchInventoryDelayedCoro(const LLUUID task_inv, const F64 time_seconds) +{ + llcoro::suspendUntilTimeout(time_seconds); + LLViewerObject *obj = gObjectList.findObject(task_inv); + if (obj) + { + // Might be good idea to prolong delay here in case expected serial changed. + // As it is, it will get a response with obsolete serial and will delay again. + + // drop waiting state to unlock isInventoryPending() + obj->mInvRequestState = INVENTORY_REQUEST_STOPPED; + obj->fetchInventoryFromServer(); + } +} + +LLControlAvatar *LLViewerObject::getControlAvatar() +{ + return getRootEdit()->mControlAvatar.get(); +} + +LLControlAvatar *LLViewerObject::getControlAvatar() const +{ + return getRootEdit()->mControlAvatar.get(); +} + +// Manage the control avatar state of a given object. +// Any object can be flagged as animated, but for performance reasons +// we don't want to incur the overhead of managing a control avatar +// unless this would have some user-visible consequence. That is, +// there should be at least one rigged mesh in the linkset. Operations +// that change the state of a linkset, such as linking or unlinking +// prims, can also mean that a control avatar needs to be added or +// removed. At the end, if there is a control avatar, we make sure +// that its animation state is current. +void LLViewerObject::updateControlAvatar() +{ + LLViewerObject *root = getRootEdit(); + bool is_animated_object = root->isAnimatedObject(); + bool has_control_avatar = getControlAvatar(); + if (!is_animated_object && !has_control_avatar) + { + return; + } + + // caller isn't supposed to operate on a dead object, + // avatar was already cleaned up + llassert(!isDead()); + + bool should_have_control_avatar = false; + if (is_animated_object) + { + bool any_rigged_mesh = root->isRiggedMesh(); + LLViewerObject::const_child_list_t& child_list = root->getChildren(); + for (LLViewerObject::const_child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); ++iter) + { + const LLViewerObject* child = *iter; + any_rigged_mesh = any_rigged_mesh || child->isRiggedMesh(); + } + should_have_control_avatar = is_animated_object && any_rigged_mesh; + } + + if (should_have_control_avatar && !has_control_avatar) + { + std::string vobj_name = llformat("Vol%p", root); + LL_DEBUGS("AnimatedObjects") << vobj_name << " calling linkControlAvatar()" << LL_ENDL; + root->linkControlAvatar(); + } + if (!should_have_control_avatar && has_control_avatar) + { + std::string vobj_name = llformat("Vol%p", root); + LL_DEBUGS("AnimatedObjects") << vobj_name << " calling unlinkControlAvatar()" << LL_ENDL; + root->unlinkControlAvatar(); + } + if (getControlAvatar()) + { + getControlAvatar()->updateAnimations(); + if (isSelected()) + { + LLSelectMgr::getInstance()->pauseAssociatedAvatars(); + } + } +} + +void LLViewerObject::linkControlAvatar() +{ + if (!getControlAvatar() && isRootEdit()) + { + LLVOVolume *volp = dynamic_cast(this); + if (!volp) + { + LL_WARNS() << "called with null or non-volume object" << LL_ENDL; + return; + } + mControlAvatar = LLControlAvatar::createControlAvatar(volp); + LL_DEBUGS("AnimatedObjects") << volp->getID() + << " created control av for " + << (S32) (1+volp->numChildren()) << " prims" << LL_ENDL; + } + LLControlAvatar *cav = getControlAvatar(); + if (cav) + { + cav->updateAttachmentOverrides(); + if (!cav->mPlaying) + { + cav->mPlaying = true; + //if (!cav->mRootVolp->isAnySelected()) + { + cav->updateVolumeGeom(); + cav->mRootVolp->recursiveMarkForUpdate(); + } + } + } + else + { + LL_WARNS() << "no control avatar found!" << LL_ENDL; + } +} + +void LLViewerObject::unlinkControlAvatar() +{ + if (getControlAvatar()) + { + getControlAvatar()->updateAttachmentOverrides(); + } + if (isRootEdit()) + { + // This will remove the entire linkset from the control avatar + if (mControlAvatar) + { + mControlAvatar->markForDeath(); + mControlAvatar = NULL; + } + } + // For non-root prims, removing from the linkset will + // automatically remove the control avatar connection. +} + +// virtual +bool LLViewerObject::isAnimatedObject() const +{ + return false; +} + +struct LLFilenameAndTask +{ + LLUUID mTaskID; + std::string mFilename; + + // for sequencing in case of multiple updates + S16 mSerial; +#ifdef _DEBUG + static S32 sCount; + LLFilenameAndTask() + { + ++sCount; + LL_DEBUGS() << "Constructing LLFilenameAndTask: " << sCount << LL_ENDL; + } + ~LLFilenameAndTask() + { + --sCount; + LL_DEBUGS() << "Destroying LLFilenameAndTask: " << sCount << LL_ENDL; + } +private: + LLFilenameAndTask(const LLFilenameAndTask& rhs); + const LLFilenameAndTask& operator=(const LLFilenameAndTask& rhs) const; +#endif +}; + +#ifdef _DEBUG +S32 LLFilenameAndTask::sCount = 0; +#endif + +// static +void LLViewerObject::processTaskInv(LLMessageSystem* msg, void** user_data) +{ + LLUUID task_id; + msg->getUUIDFast(_PREHASH_InventoryData, _PREHASH_TaskID, task_id); + LLViewerObject* object = gObjectList.findObject(task_id); + if (!object) + { + LL_WARNS() << "LLViewerObject::processTaskInv object " + << task_id << " does not exist." << LL_ENDL; + return; + } + + // we can receive multiple task updates simultaneously, make sure we will not rewrite newer with older update + S16 serial = 0; + msg->getS16Fast(_PREHASH_InventoryData, _PREHASH_Serial, serial); + + if (serial == object->mInventorySerialNum + && serial < object->mExpectedInventorySerialNum) + { + // Loop Protection. + // We received same serial twice. + // Viewer did some changes to inventory that couldn't be saved server side + // or something went wrong to cause serial to be out of sync. + // Drop xfer and restart after some time, assign server's value as expected + LL_WARNS() << "Task inventory serial might be out of sync, server serial: " << serial << " client expected serial: " << object->mExpectedInventorySerialNum << LL_ENDL; + object->mExpectedInventorySerialNum = serial; + object->fetchInventoryDelayed(INVENTORY_UPDATE_WAIT_TIME_DESYNC); + } + else if (serial < object->mExpectedInventorySerialNum) + { + // Out of date message, record to current serial for loop protection, but do not load it + // Drop xfer and restart after some time + if (serial < object->mInventorySerialNum) + { + LL_WARNS() << "Task serial decreased. Potentially out of order packet or desync." << LL_ENDL; + } + object->mInventorySerialNum = serial; + object->fetchInventoryDelayed(INVENTORY_UPDATE_WAIT_TIME_OUTDATED); + } + else if (serial >= object->mExpectedInventorySerialNum) + { + LLFilenameAndTask* ft = new LLFilenameAndTask; + ft->mTaskID = task_id; + ft->mSerial = serial; + + // We received version we expected or newer. Load it. + object->mInventorySerialNum = ft->mSerial; + object->mExpectedInventorySerialNum = ft->mSerial; + + std::string unclean_filename; + msg->getStringFast(_PREHASH_InventoryData, _PREHASH_Filename, unclean_filename); + ft->mFilename = LLDir::getScrubbedFileName(unclean_filename); + + if (ft->mFilename.empty()) + { + LL_DEBUGS() << "Task has no inventory" << LL_ENDL; + // mock up some inventory to make a drop target. + if (object->mInventory) + { + object->mInventory->clear(); // will deref and delete it + } + else + { + object->mInventory = new LLInventoryObject::object_list_t(); + } + LLPointer obj; + obj = new LLInventoryObject(object->mID, LLUUID::null, + LLAssetType::AT_CATEGORY, + "Contents"); + object->mInventory->push_front(obj); + object->doInventoryCallback(); + delete ft; + return; + } + U64 new_id = gXferManager->requestFile(gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ft->mFilename), + ft->mFilename, LL_PATH_CACHE, + object->mRegionp->getHost(), + true, + &LLViewerObject::processTaskInvFile, + (void**)ft, // This takes ownership of ft + LLXferManager::HIGH_PRIORITY); + if (object->mInvRequestState == INVENTORY_XFER) + { + if (new_id > 0 && new_id != object->mInvRequestXFerId) + { + // we started new download. + gXferManager->abortRequestById(object->mInvRequestXFerId, -1); + object->mInvRequestXFerId = new_id; + } + } + else + { + object->mInvRequestState = INVENTORY_XFER; + object->mInvRequestXFerId = new_id; + } + } +} + +void LLViewerObject::processTaskInvFile(void** user_data, S32 error_code, LLExtStat ext_status) +{ + LLFilenameAndTask* ft = (LLFilenameAndTask*)user_data; + LLViewerObject* object = NULL; + + if (ft + && (0 == error_code) + && (object = gObjectList.findObject(ft->mTaskID)) + && ft->mSerial >= object->mInventorySerialNum) + { + object->mInventorySerialNum = ft->mSerial; + LL_DEBUGS() << "Receiving inventory task file for serial " << object->mInventorySerialNum << " taskid: " << ft->mTaskID << LL_ENDL; + if (ft->mSerial < object->mExpectedInventorySerialNum) + { + // User managed to change something while inventory was loading + LL_DEBUGS() << "Processing file that is potentially out of date for task: " << ft->mTaskID << LL_ENDL; + } + + if (object->loadTaskInvFile(ft->mFilename)) + { + + LLInventoryObject::object_list_t::iterator it = object->mInventory->begin(); + LLInventoryObject::object_list_t::iterator end = object->mInventory->end(); + std::list& pending_lst = object->mPendingInventoryItemsIDs; + + for (; it != end && pending_lst.size(); ++it) + { + LLViewerInventoryItem* item = dynamic_cast(it->get()); + if(item && item->getType() != LLAssetType::AT_CATEGORY) + { + std::list::iterator id_it = std::find(pending_lst.begin(), pending_lst.begin(), item->getAssetUUID()); + if (id_it != pending_lst.end()) + { + pending_lst.erase(id_it); + } + } + } + } + else + { + // MAINT-2597 - crash when trying to edit a no-mod object + // Somehow get an contents inventory response, but with an invalid stream (possibly 0 size?) + // Stated repro was specific to no-mod objects so failing without user interaction should be safe. + LL_WARNS() << "Trying to load invalid task inventory file. Ignoring file contents." << LL_ENDL; + } + } + else + { + // This Occurs When two requests were made, and the first one + // has already handled it. + LL_DEBUGS() << "Problem loading task inventory. Return code: " + << error_code << LL_ENDL; + } + delete ft; +} + +bool LLViewerObject::loadTaskInvFile(const std::string& filename) +{ + std::string filename_and_local_path = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, filename); + llifstream ifs(filename_and_local_path.c_str()); + if(ifs.good()) + { + U32 fail_count = 0; + char buffer[MAX_STRING]; /* Flawfinder: ignore */ + // *NOTE: This buffer size is hard coded into scanf() below. + char keyword[MAX_STRING]; /* Flawfinder: ignore */ + if(mInventory) + { + mInventory->clear(); // will deref and delete it + } + else + { + mInventory = new LLInventoryObject::object_list_t; + } + while(ifs.good()) + { + ifs.getline(buffer, MAX_STRING); + if (sscanf(buffer, " %254s", keyword) == EOF) /* Flawfinder: ignore */ + { + // Blank file? + LL_WARNS() << "Issue reading from file '" + << filename << "'" << LL_ENDL; + break; + } + else if(0 == strcmp("inv_item", keyword)) + { + LLPointer inv = new LLViewerInventoryItem; + inv->importLegacyStream(ifs); + mInventory->push_front(inv); + } + else if(0 == strcmp("inv_object", keyword)) + { + LLPointer inv = new LLInventoryObject; + inv->importLegacyStream(ifs); + inv->rename("Contents"); + mInventory->push_front(inv); + } + else if (fail_count >= MAX_INV_FILE_READ_FAILS) + { + LL_WARNS() << "Encountered too many unknowns while reading from file: '" + << filename << "'" << LL_ENDL; + break; + } + else + { + // Is there really a point to continue processing? We already failing to display full inventory + fail_count++; + LL_WARNS_ONCE() << "Unknown token while reading from inventory file. Token: '" + << keyword << "'" << LL_ENDL; + } + } + ifs.close(); + LLFile::remove(filename_and_local_path); + } + else + { + LL_WARNS() << "unable to load task inventory: " << filename_and_local_path + << LL_ENDL; + return false; + } + doInventoryCallback(); + + return true; +} + +void LLViewerObject::doInventoryCallback() +{ + for (callback_list_t::iterator iter = mInventoryCallbacks.begin(); + iter != mInventoryCallbacks.end(); ) + { + callback_list_t::iterator curiter = iter++; + LLInventoryCallbackInfo* info = *curiter; + if (info->mListener != NULL) + { + info->mListener->inventoryChanged(this, + mInventory, + mInventorySerialNum, + info->mInventoryData); + } + else + { + LL_INFOS() << "LLViewerObject::doInventoryCallback() deleting bad listener entry." << LL_ENDL; + delete info; + mInventoryCallbacks.erase(curiter); + } + } + + // release inventory loading state + mInvRequestXFerId = 0; + mInvRequestState = INVENTORY_REQUEST_STOPPED; +} + +void LLViewerObject::removeInventory(const LLUUID& item_id) +{ + // close associated floater properties + LLSD params; + params["id"] = item_id; + params["object"] = mID; + LLFloaterReg::hideInstance("item_properties", params); + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_RemoveTaskInventory); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_InventoryData); + msg->addU32Fast(_PREHASH_LocalID, mLocalID); + msg->addUUIDFast(_PREHASH_ItemID, item_id); + msg->sendReliable(mRegionp->getHost()); + deleteInventoryItem(item_id); + ++mExpectedInventorySerialNum; +} + +bool LLViewerObject::isAssetInInventory(LLViewerInventoryItem* item, LLAssetType::EType type) +{ + bool result = false; + + if (item) + { + // For now mPendingInventoryItemsIDs only stores textures and materials + // but if it gets to store more types, it will need to verify type as well + // since null can be a shared default id and it is fine to need a null + // script and a null material simultaneously. + std::list::iterator begin = mPendingInventoryItemsIDs.begin(); + std::list::iterator end = mPendingInventoryItemsIDs.end(); + + bool is_fetching = std::find(begin, end, item->getAssetUUID()) != end; + + // null is the default asset for materials and default for scripts + // so need to check type as well + bool is_fetched = getInventoryItemByAsset(item->getAssetUUID(), type) != NULL; + + result = is_fetched || is_fetching; + } + + return result; +} + +void LLViewerObject::updateMaterialInventory(LLViewerInventoryItem* item, U8 key, bool is_new) +{ + if (!item) + { + return; + } + if (LLAssetType::AT_TEXTURE != item->getType() + && LLAssetType::AT_MATERIAL != item->getType()) + { + // Not supported + return; + } + + if (isAssetInInventory(item, item->getType())) + { + // already there + return; + } + + mPendingInventoryItemsIDs.push_back(item->getAssetUUID()); + updateInventory(item, key, is_new); +} + +void LLViewerObject::updateInventory( + LLViewerInventoryItem* item, + U8 key, + bool is_new) +{ + // This slices the object into what we're concerned about on the + // viewer. The simulator will take the permissions and transfer + // ownership. + LLPointer task_item = + new LLViewerInventoryItem(item->getUUID(), mID, item->getPermissions(), + item->getAssetUUID(), item->getType(), + item->getInventoryType(), + item->getName(), item->getDescription(), + item->getSaleInfo(), + item->getFlags(), + item->getCreationDate()); + task_item->setTransactionID(item->getTransactionID()); + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_UpdateTaskInventory); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_UpdateData); + msg->addU32Fast(_PREHASH_LocalID, mLocalID); + msg->addU8Fast(_PREHASH_Key, key); + msg->nextBlockFast(_PREHASH_InventoryData); + task_item->packMessage(msg); + msg->sendReliable(mRegionp->getHost()); + + // do the internal logic + doUpdateInventory(task_item, key, is_new); +} + +void LLViewerObject::updateInventoryLocal(LLInventoryItem* item, U8 key) +{ + LLPointer task_item = + new LLViewerInventoryItem(item->getUUID(), mID, item->getPermissions(), + item->getAssetUUID(), item->getType(), + item->getInventoryType(), + item->getName(), item->getDescription(), + item->getSaleInfo(), item->getFlags(), + item->getCreationDate()); + + // do the internal logic + const bool is_new = false; + doUpdateInventory(task_item, key, is_new); +} + +LLInventoryObject* LLViewerObject::getInventoryObject(const LLUUID& item_id) +{ + LLInventoryObject* rv = NULL; + if(mInventory) + { + LLInventoryObject::object_list_t::iterator it = mInventory->begin(); + LLInventoryObject::object_list_t::iterator end = mInventory->end(); + for ( ; it != end; ++it) + { + if((*it)->getUUID() == item_id) + { + rv = *it; + break; + } + } + } + return rv; +} + +LLInventoryItem* LLViewerObject::getInventoryItem(const LLUUID& item_id) +{ + LLInventoryObject* iobj = getInventoryObject(item_id); + if (!iobj || iobj->getType() == LLAssetType::AT_CATEGORY) + { + return NULL; + } + LLInventoryItem* item = dynamic_cast(iobj); + return item; +} + +void LLViewerObject::getInventoryContents(LLInventoryObject::object_list_t& objects) +{ + if(mInventory) + { + LLInventoryObject::object_list_t::iterator it = mInventory->begin(); + LLInventoryObject::object_list_t::iterator end = mInventory->end(); + for( ; it != end; ++it) + { + if ((*it)->getType() != LLAssetType::AT_CATEGORY) + { + objects.push_back(*it); + } + } + } +} + +LLInventoryObject* LLViewerObject::getInventoryRoot() +{ + if (!mInventory || !mInventory->size()) + { + return NULL; + } + return mInventory->back(); +} + +LLViewerInventoryItem* LLViewerObject::getInventoryItemByAsset(const LLUUID& asset_id) +{ + if (mInventoryDirty) + LL_WARNS() << "Peforming inventory lookup for object " << mID << " that has dirty inventory!" << LL_ENDL; + + LLViewerInventoryItem* rv = NULL; + if(mInventory) + { + LLViewerInventoryItem* item = NULL; + + LLInventoryObject::object_list_t::iterator it = mInventory->begin(); + LLInventoryObject::object_list_t::iterator end = mInventory->end(); + for( ; it != end; ++it) + { + LLInventoryObject* obj = *it; + if(obj->getType() != LLAssetType::AT_CATEGORY) + { + // *FIX: gank-ass down cast! + item = (LLViewerInventoryItem*)obj; + if(item->getAssetUUID() == asset_id) + { + rv = item; + break; + } + } + } + } + return rv; +} + +LLViewerInventoryItem* LLViewerObject::getInventoryItemByAsset(const LLUUID& asset_id, LLAssetType::EType type) +{ + if (mInventoryDirty) + LL_WARNS() << "Peforming inventory lookup for object " << mID << " that has dirty inventory!" << LL_ENDL; + + LLViewerInventoryItem* rv = NULL; + if (type == LLAssetType::AT_CATEGORY) + { + // Whatever called this shouldn't be trying to get a folder by asset + // categories don't have assets + llassert(0); + return rv; + } + + if (mInventory) + { + LLViewerInventoryItem* item = NULL; + + LLInventoryObject::object_list_t::iterator it = mInventory->begin(); + LLInventoryObject::object_list_t::iterator end = mInventory->end(); + for (; it != end; ++it) + { + LLInventoryObject* obj = *it; + if (obj->getType() == type) + { + // *FIX: gank-ass down cast! + item = (LLViewerInventoryItem*)obj; + if (item->getAssetUUID() == asset_id) + { + rv = item; + break; + } + } + } + } + return rv; +} + +void LLViewerObject::updateViewerInventoryAsset( + const LLViewerInventoryItem* item, + const LLUUID& new_asset) +{ + LLPointer task_item = + new LLViewerInventoryItem(item); + task_item->setAssetUUID(new_asset); + + // do the internal logic + doUpdateInventory(task_item, TASK_INVENTORY_ITEM_KEY, false); +} + +void LLViewerObject::setPixelAreaAndAngle(LLAgent &agent) +{ + if (getVolume()) + { //volumes calculate pixel area and angle per face + return; + } + + LLVector3 viewer_pos_agent = gAgentCamera.getCameraPositionAgent(); + LLVector3 pos_agent = getRenderPosition(); + + F32 dx = viewer_pos_agent.mV[VX] - pos_agent.mV[VX]; + F32 dy = viewer_pos_agent.mV[VY] - pos_agent.mV[VY]; + F32 dz = viewer_pos_agent.mV[VZ] - pos_agent.mV[VZ]; + + F32 max_scale = getMaxScale(); + F32 mid_scale = getMidScale(); + F32 min_scale = getMinScale(); + + // IW: estimate - when close to large objects, computing range based on distance from center is no good + // to try to get a min distance from face, subtract min_scale/2 from the range. + // This means we'll load too much detail sometimes, but that's better than not enough + // I don't think there's a better way to do this without calculating distance per-poly + F32 range = sqrt(dx*dx + dy*dy + dz*dz) - min_scale/2; + + LLViewerCamera* camera = LLViewerCamera::getInstance(); + if (range < 0.001f || isHUDAttachment()) // range == zero + { + mAppAngle = 180.f; + mPixelArea = (F32)camera->getScreenPixelArea(); + } + else + { + mAppAngle = (F32) atan2( max_scale, range) * RAD_TO_DEG; + + F32 pixels_per_meter = camera->getPixelMeterRatio() / range; + + mPixelArea = (pixels_per_meter * max_scale) * (pixels_per_meter * mid_scale); + if (mPixelArea > camera->getScreenPixelArea()) + { + mAppAngle = 180.f; + mPixelArea = (F32)camera->getScreenPixelArea(); + } + } +} + +bool LLViewerObject::updateLOD() +{ + return false; +} + +bool LLViewerObject::updateGeometry(LLDrawable *drawable) +{ + return false; +} + +void LLViewerObject::updateGL() +{ + +} + +void LLViewerObject::updateFaceSize(S32 idx) +{ + +} + +LLDrawable* LLViewerObject::createDrawable(LLPipeline *pipeline) +{ + return NULL; +} + +void LLViewerObject::setScale(const LLVector3 &scale, bool damped) +{ + LLPrimitive::setScale(scale); + if (mDrawable.notNull()) + { + //encompass completely sheared objects by taking + //the most extreme point possible (<1,1,0.5>) + mDrawable->setRadius(LLVector3(1,1,0.5f).scaleVec(scale).magVec()); + updateDrawable(damped); + } + + if( (LL_PCODE_VOLUME == getPCode()) && !isDead() ) + { + if (permYouOwner() || (scale.magVecSquared() > (7.5f * 7.5f)) ) + { + if (!mOnMap) + { + llassert_always(LLWorld::getInstance()->getRegionFromHandle(getRegion()->getHandle())); + + gObjectList.addToMap(this); + mOnMap = true; + } + } + else + { + if (mOnMap) + { + gObjectList.removeFromMap(this); + mOnMap = false; + } + } + } +} + +void LLViewerObject::setObjectCostStale() +{ + mCostStale = true; + // *NOTE: This is harmlessly redundant for Blinn-Phong material updates, as + // the root prim currently gets set stale anyway due to other property + // updates. But it is needed for GLTF material ID updates. + // -Cosmic,2023-06-27 + getRootEdit()->mCostStale = true; +} + +void LLViewerObject::setObjectCost(F32 cost) +{ + mObjectCost = cost; + mCostStale = false; + + if (isSelected()) + { + gFloaterTools->dirty(); + } +} + +void LLViewerObject::setLinksetCost(F32 cost) +{ + mLinksetCost = cost; + mCostStale = false; + + bool needs_refresh = isSelected(); + child_list_t::iterator iter = mChildList.begin(); + while(iter != mChildList.end() && !needs_refresh) + { + LLViewerObject* child = *iter; + needs_refresh = child->isSelected(); + iter++; + } + + if (needs_refresh) + { + gFloaterTools->dirty(); + } +} + +void LLViewerObject::setPhysicsCost(F32 cost) +{ + mPhysicsCost = cost; + mCostStale = false; + + if (isSelected()) + { + gFloaterTools->dirty(); + } +} + +void LLViewerObject::setLinksetPhysicsCost(F32 cost) +{ + mLinksetPhysicsCost = cost; + mCostStale = false; + + if (isSelected()) + { + gFloaterTools->dirty(); + } +} + + +F32 LLViewerObject::getObjectCost() +{ + if (mCostStale) + { + gObjectList.updateObjectCost(this); + } + + return mObjectCost; +} + +F32 LLViewerObject::getLinksetCost() +{ + if (mCostStale) + { + gObjectList.updateObjectCost(this); + } + + return mLinksetCost; +} + +F32 LLViewerObject::getPhysicsCost() +{ + if (mCostStale) + { + gObjectList.updateObjectCost(this); + } + + return mPhysicsCost; +} + +F32 LLViewerObject::getLinksetPhysicsCost() +{ + if (mCostStale) + { + gObjectList.updateObjectCost(this); + } + + return mLinksetPhysicsCost; +} + +F32 LLViewerObject::recursiveGetEstTrianglesMax() const +{ + F32 est_tris = getEstTrianglesMax(); + for (child_list_t::const_iterator iter = mChildList.begin(); + iter != mChildList.end(); iter++) + { + const LLViewerObject* child = *iter; + if (!child->isAvatar()) + { + est_tris += child->recursiveGetEstTrianglesMax(); + } + } + return est_tris; +} + +S32 LLViewerObject::getAnimatedObjectMaxTris() const +{ + S32 max_tris = 0; + if (gAgent.getRegion()) + { + LLSD features; + gAgent.getRegion()->getSimulatorFeatures(features); + if (features.has("AnimatedObjects")) + { + max_tris = features["AnimatedObjects"]["AnimatedObjectMaxTris"].asInteger(); + } + } + return max_tris; +} + +F32 LLViewerObject::getEstTrianglesMax() const +{ + return 0.f; +} + +F32 LLViewerObject::getEstTrianglesStreamingCost() const +{ + return 0.f; +} + +// virtual +F32 LLViewerObject::getStreamingCost() const +{ + return 0.f; +} + +// virtual +bool LLViewerObject::getCostData(LLMeshCostData& costs) const +{ + costs = LLMeshCostData(); + return false; +} + +U32 LLViewerObject::getTriangleCount(S32* vcount) const +{ + return 0; +} + +U32 LLViewerObject::getHighLODTriangleCount() +{ + return 0; +} + +U32 LLViewerObject::recursiveGetTriangleCount(S32* vcount) const +{ + S32 total_tris = getTriangleCount(vcount); + LLViewerObject::const_child_list_t& child_list = getChildren(); + for (LLViewerObject::const_child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); ++iter) + { + LLViewerObject* childp = *iter; + if (childp) + { + total_tris += childp->getTriangleCount(vcount); + } + } + return total_tris; +} + +// This is using the stored surface area for each volume (which +// defaults to 1.0 for the case of everything except a sculpt) and +// then scaling it linearly based on the largest dimension in the +// prim's scale. Should revisit at some point. +F32 LLViewerObject::recursiveGetScaledSurfaceArea() const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + F32 area = 0.f; + const LLDrawable* drawable = mDrawable; + if (drawable) + { + const LLVOVolume* volume = drawable->getVOVolume(); + if (volume) + { + if (volume->getVolume()) + { + const LLVector3& scale = volume->getScale(); + area += volume->getVolume()->getSurfaceArea() * llmax(llmax(scale.mV[0], scale.mV[1]), scale.mV[2]); + } + LLViewerObject::const_child_list_t children = volume->getChildren(); + for (LLViewerObject::const_child_list_t::const_iterator child_iter = children.begin(); + child_iter != children.end(); + ++child_iter) + { + LLViewerObject* child_obj = *child_iter; + LLVOVolume *child = dynamic_cast( child_obj ); + if (child && child->getVolume()) + { + const LLVector3& scale = child->getScale(); + area += child->getVolume()->getSurfaceArea() * llmax(llmax(scale.mV[0], scale.mV[1]), scale.mV[2]); + } + } + } + } + return area; +} + +void LLViewerObject::updateSpatialExtents(LLVector4a& newMin, LLVector4a &newMax) +{ + LLVector4a center; + center.load3(getRenderPosition().mV); + LLVector4a size; + size.load3(getScale().mV); + newMin.setSub(center, size); + newMax.setAdd(center, size); + + mDrawable->setPositionGroup(center); +} + +F32 LLViewerObject::getBinRadius() +{ + if (mDrawable.notNull()) + { + const LLVector4a* ext = mDrawable->getSpatialExtents(); + LLVector4a diff; + diff.setSub(ext[1], ext[0]); + return diff.getLength3().getF32(); + } + + return getScale().magVec(); +} + +F32 LLViewerObject::getMaxScale() const +{ + return llmax(getScale().mV[VX],getScale().mV[VY], getScale().mV[VZ]); +} + +F32 LLViewerObject::getMinScale() const +{ + return llmin(getScale().mV[0],getScale().mV[1],getScale().mV[2]); +} + +F32 LLViewerObject::getMidScale() const +{ + if (getScale().mV[VX] < getScale().mV[VY]) + { + if (getScale().mV[VY] < getScale().mV[VZ]) + { + return getScale().mV[VY]; + } + else if (getScale().mV[VX] < getScale().mV[VZ]) + { + return getScale().mV[VZ]; + } + else + { + return getScale().mV[VX]; + } + } + else if (getScale().mV[VX] < getScale().mV[VZ]) + { + return getScale().mV[VX]; + } + else if (getScale().mV[VY] < getScale().mV[VZ]) + { + return getScale().mV[VZ]; + } + else + { + return getScale().mV[VY]; + } +} + + +void LLViewerObject::updateTextures() +{ +} + +void LLViewerObject::boostTexturePriority(bool boost_children /* = true */) +{ + if (isDead() || !getVolume()) + { + return; + } + + S32 i; + S32 tex_count = getNumTEs(); + for (i = 0; i < tex_count; i++) + { + getTEImage(i)->setBoostLevel(LLGLTexture::BOOST_SELECTED); + } + + if (isSculpted() && !isMesh()) + { + LLSculptParams *sculpt_params = (LLSculptParams *)getParameterEntry(LLNetworkData::PARAMS_SCULPT); + LLUUID sculpt_id = sculpt_params->getSculptTexture(); + LLViewerTextureManager::getFetchedTexture(sculpt_id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE)->setBoostLevel(LLGLTexture::BOOST_SELECTED); + } + + if (boost_children) + { + for (child_list_t::iterator iter = mChildList.begin(); + iter != mChildList.end(); iter++) + { + LLViewerObject* child = *iter; + child->boostTexturePriority(); + } + } +} + +void LLViewerObject::setLineWidthForWindowSize(S32 window_width) +{ + if (window_width < 700) + { + LLUI::setLineWidth(2.0f); + } + else if (window_width < 1100) + { + LLUI::setLineWidth(3.0f); + } + else if (window_width < 2000) + { + LLUI::setLineWidth(4.0f); + } + else + { + // _damn_, what a nice monitor! + LLUI::setLineWidth(5.0f); + } +} + +void LLViewerObject::increaseArrowLength() +{ +/* ??? + if (mAxisArrowLength == 50) + { + mAxisArrowLength = 100; + } + else + { + mAxisArrowLength = 150; + } +*/ +} + + +void LLViewerObject::decreaseArrowLength() +{ +/* ??? + if (mAxisArrowLength == 150) + { + mAxisArrowLength = 100; + } + else + { + mAxisArrowLength = 50; + } +*/ +} + +// Culled from newsim LLTask::addNVPair +void LLViewerObject::addNVPair(const std::string& data) +{ + // cout << "LLViewerObject::addNVPair() with ---" << data << "---" << endl; + LLNameValue *nv = new LLNameValue(data.c_str()); + +// char splat[MAX_STRING]; +// temp->printNameValue(splat); +// LL_INFOS() << "addNVPair " << splat << LL_ENDL; + + name_value_map_t::iterator iter = mNameValuePairs.find(nv->mName); + if (iter != mNameValuePairs.end()) + { + LLNameValue* foundnv = iter->second; + if (foundnv->mClass != NVC_READ_ONLY) + { + delete foundnv; + mNameValuePairs.erase(iter); + } + else + { + delete nv; +// LL_INFOS() << "Trying to write to Read Only NVPair " << temp->mName << " in addNVPair()" << LL_ENDL; + return; + } + } + mNameValuePairs[nv->mName] = nv; +} + +bool LLViewerObject::removeNVPair(const std::string& name) +{ + char* canonical_name = gNVNameTable.addString(name); + + LL_DEBUGS() << "LLViewerObject::removeNVPair(): " << name << LL_ENDL; + + name_value_map_t::iterator iter = mNameValuePairs.find(canonical_name); + if (iter != mNameValuePairs.end()) + { + if( mRegionp ) + { + LLNameValue* nv = iter->second; +/* + std::string buffer = nv->printNameValue(); + gMessageSystem->newMessageFast(_PREHASH_RemoveNameValuePair); + gMessageSystem->nextBlockFast(_PREHASH_TaskData); + gMessageSystem->addUUIDFast(_PREHASH_ID, mID); + + gMessageSystem->nextBlockFast(_PREHASH_NameValueData); + gMessageSystem->addStringFast(_PREHASH_NVPair, buffer); + + gMessageSystem->sendReliable( mRegionp->getHost() ); +*/ + // Remove the NV pair from the local list. + delete nv; + mNameValuePairs.erase(iter); + return true; + } + else + { + LL_DEBUGS() << "removeNVPair - No region for object" << LL_ENDL; + } + } + return false; +} + + +LLNameValue *LLViewerObject::getNVPair(const std::string& name) const +{ + char *canonical_name; + + canonical_name = gNVNameTable.addString(name); + + // If you access a map with a name that isn't in it, it will add the name and a null pointer. + // So first check if the data is in the map. + name_value_map_t::const_iterator iter = mNameValuePairs.find(canonical_name); + if (iter != mNameValuePairs.end()) + { + return iter->second; + } + else + { + return NULL; + } +} + +void LLViewerObject::updatePositionCaches() const +{ + // If region is removed from the list it is also deleted. + if(mRegionp && LLWorld::instance().isRegionListed(mRegionp)) + { + if (!isRoot()) + { + mPositionRegion = ((LLViewerObject *)getParent())->getPositionRegion() + getPosition() * getParent()->getRotation(); + mPositionAgent = mRegionp->getPosAgentFromRegion(mPositionRegion); + } + else + { + mPositionRegion = getPosition(); + mPositionAgent = mRegionp->getPosAgentFromRegion(mPositionRegion); + } + } +} + +const LLVector3d LLViewerObject::getPositionGlobal() const +{ + // If region is removed from the list it is also deleted. + if(mRegionp && LLWorld::instance().isRegionListed(mRegionp)) + { + LLVector3d position_global = mRegionp->getPosGlobalFromRegion(getPositionRegion()); + + if (isAttachment()) + { + position_global = gAgent.getPosGlobalFromAgent(getRenderPosition()); + } + return position_global; + } + else + { + LLVector3d position_global(getPosition()); + return position_global; + } +} + +const LLVector3 &LLViewerObject::getPositionAgent() const +{ + // If region is removed from the list it is also deleted. + if(mRegionp && LLWorld::instance().isRegionListed(mRegionp)) + { + if (mDrawable.notNull() && (!mDrawable->isRoot() && getParent())) + { + // Don't return cached position if you have a parent, recalc (until all dirtying is done correctly. + LLVector3 position_region; + position_region = ((LLViewerObject *)getParent())->getPositionRegion() + getPosition() * getParent()->getRotation(); + mPositionAgent = mRegionp->getPosAgentFromRegion(position_region); + } + else + { + mPositionAgent = mRegionp->getPosAgentFromRegion(getPosition()); + } + } + return mPositionAgent; +} + +const LLVector3 &LLViewerObject::getPositionRegion() const +{ + if (!isRoot()) + { + LLViewerObject *parent = (LLViewerObject *)getParent(); + mPositionRegion = parent->getPositionRegion() + (getPosition() * parent->getRotation()); + } + else + { + mPositionRegion = getPosition(); + } + + return mPositionRegion; +} + +const LLVector3 LLViewerObject::getPositionEdit() const +{ + if (isRootEdit()) + { + return getPosition(); + } + else + { + LLViewerObject *parent = (LLViewerObject *)getParent(); + LLVector3 position_edit = parent->getPositionEdit() + getPosition() * parent->getRotationEdit(); + return position_edit; + } +} + +const LLVector3 LLViewerObject::getRenderPosition() const +{ + if (mDrawable.notNull() && mDrawable->isState(LLDrawable::RIGGED)) + { + LLControlAvatar *cav = getControlAvatar(); + if (isRoot() && cav) + { + F32 fixup; + if ( cav->hasPelvisFixup( fixup) ) + { + //Apply a pelvis fixup (as defined by the avs skin) + LLVector3 pos = mDrawable->getPositionAgent(); + pos[VZ] += fixup; + return pos; + } + } + LLVOAvatar* avatar = getAvatar(); + if ((avatar) && !getControlAvatar()) + { + return avatar->getPositionAgent(); + } + } + + if (mDrawable.isNull() || mDrawable->getGeneration() < 0) + { + return getPositionAgent(); + } + else + { + return mDrawable->getPositionAgent(); + } +} + +const LLVector3 LLViewerObject::getPivotPositionAgent() const +{ + return getRenderPosition(); +} + +const LLQuaternion LLViewerObject::getRenderRotation() const +{ + LLQuaternion ret; + if (mDrawable.notNull() && mDrawable->isState(LLDrawable::RIGGED) && !isAnimatedObject()) + { + return ret; + } + + if (mDrawable.isNull() || mDrawable->isStatic()) + { + ret = getRotationEdit(); + } + else + { + if (!mDrawable->isRoot()) + { + ret = getRotation() * LLQuaternion(mDrawable->getParent()->getWorldMatrix()); + } + else + { + ret = LLQuaternion(mDrawable->getWorldMatrix()); + } + } + + return ret; +} + +const LLMatrix4 LLViewerObject::getRenderMatrix() const +{ + return mDrawable->getWorldMatrix(); +} + +const LLQuaternion LLViewerObject::getRotationRegion() const +{ + LLQuaternion global_rotation = getRotation(); + if (!((LLXform *)this)->isRoot()) + { + global_rotation = global_rotation * getParent()->getRotation(); + } + return global_rotation; +} + +const LLQuaternion LLViewerObject::getRotationEdit() const +{ + LLQuaternion global_rotation = getRotation(); + if (!((LLXform *)this)->isRootEdit()) + { + global_rotation = global_rotation * getParent()->getRotation(); + } + return global_rotation; +} + +void LLViewerObject::setPositionAbsoluteGlobal( const LLVector3d &pos_global, bool damped ) +{ + if (isAttachment()) + { + LLVector3 new_pos = mRegionp->getPosRegionFromGlobal(pos_global); + if (isRootEdit()) + { + new_pos -= mDrawable->mXform.getParent()->getWorldPosition(); + LLQuaternion world_rotation = mDrawable->mXform.getParent()->getWorldRotation(); + new_pos = new_pos * ~world_rotation; + } + else + { + LLViewerObject* parentp = (LLViewerObject*)getParent(); + new_pos -= parentp->getPositionAgent(); + new_pos = new_pos * ~parentp->getRotationRegion(); + } + LLViewerObject::setPosition(new_pos); + + if (mParent && ((LLViewerObject*)mParent)->isAvatar()) + { + // we have changed the position of an attachment, so we need to clamp it + LLVOAvatar *avatar = (LLVOAvatar*)mParent; + + avatar->clampAttachmentPositions(); + } + } + else + { + if( isRoot() ) + { + setPositionRegion(mRegionp->getPosRegionFromGlobal(pos_global)); + } + else + { + // the relative position with the parent is not constant + LLViewerObject* parent = (LLViewerObject *)getParent(); + //RN: this assumes we are only calling this function from the edit tools + gPipeline.updateMoveNormalAsync(parent->mDrawable); + + LLVector3 pos_local = mRegionp->getPosRegionFromGlobal(pos_global) - parent->getPositionRegion(); + pos_local = pos_local * ~parent->getRotationRegion(); + LLViewerObject::setPosition( pos_local ); + } + } + //RN: assumes we always want to snap the object when calling this function + gPipeline.updateMoveNormalAsync(mDrawable); +} + +void LLViewerObject::setPosition(const LLVector3 &pos, bool damped) +{ + if (getPosition() != pos) + { + setChanged(TRANSLATED | SILHOUETTE); + } + + LLXform::setPosition(pos); + updateDrawable(damped); + if (isRoot()) + { + // position caches need to be up to date on root objects + updatePositionCaches(); + } +} + +void LLViewerObject::setPositionGlobal(const LLVector3d &pos_global, bool damped) +{ + if (isAttachment()) + { + if (isRootEdit()) + { + LLVector3 newPos = mRegionp->getPosRegionFromGlobal(pos_global); + newPos = newPos - mDrawable->mXform.getParent()->getWorldPosition(); + + LLQuaternion invWorldRotation = mDrawable->mXform.getParent()->getWorldRotation(); + invWorldRotation.transQuat(); + + newPos = newPos * invWorldRotation; + LLViewerObject::setPosition(newPos); + } + else + { + // assumes parent is root editable (root of attachment) + LLVector3 newPos = mRegionp->getPosRegionFromGlobal(pos_global); + newPos = newPos - mDrawable->mXform.getParent()->getWorldPosition(); + LLVector3 delta_pos = newPos - getPosition(); + + LLQuaternion invRotation = mDrawable->getRotation(); + invRotation.transQuat(); + + delta_pos = delta_pos * invRotation; + + // *FIX: is this right? Shouldn't we be calling the + // LLViewerObject version of setPosition? + LLVector3 old_pos = mDrawable->mXform.getParent()->getPosition(); + mDrawable->mXform.getParent()->setPosition(old_pos + delta_pos); + setChanged(TRANSLATED | SILHOUETTE); + } + if (mParent && ((LLViewerObject*)mParent)->isAvatar()) + { + // we have changed the position of an attachment, so we need to clamp it + LLVOAvatar *avatar = (LLVOAvatar*)mParent; + + avatar->clampAttachmentPositions(); + } + } + else + { + if (isRoot()) + { + setPositionRegion(mRegionp->getPosRegionFromGlobal(pos_global)); + } + else + { + // the relative position with the parent is constant, but the parent's position needs to be changed + LLVector3d position_offset; + position_offset.setVec(getPosition()*getParent()->getRotation()); + LLVector3d new_pos_global = pos_global - position_offset; + ((LLViewerObject *)getParent())->setPositionGlobal(new_pos_global); + } + } + updateDrawable(damped); +} + + +void LLViewerObject::setPositionParent(const LLVector3 &pos_parent, bool damped) +{ + // Set position relative to parent, if no parent, relative to region + if (!isRoot()) + { + LLViewerObject::setPosition(pos_parent, damped); + //updateDrawable(damped); + } + else + { + setPositionRegion(pos_parent, damped); + } +} + +void LLViewerObject::setPositionRegion(const LLVector3 &pos_region, bool damped) +{ + if (!isRootEdit()) + { + LLViewerObject* parent = (LLViewerObject*) getParent(); + LLViewerObject::setPosition((pos_region-parent->getPositionRegion())*~parent->getRotationRegion()); + } + else + { + LLViewerObject::setPosition(pos_region); + mPositionRegion = pos_region; + mPositionAgent = mRegionp->getPosAgentFromRegion(mPositionRegion); + } +} + +void LLViewerObject::setPositionAgent(const LLVector3 &pos_agent, bool damped) +{ + LLVector3 pos_region = getRegion()->getPosRegionFromAgent(pos_agent); + setPositionRegion(pos_region, damped); +} + +// identical to setPositionRegion() except it checks for child-joints +// and doesn't also move the joint-parent +// TODO -- implement similar intelligence for joint-parents toward +// their joint-children +void LLViewerObject::setPositionEdit(const LLVector3 &pos_edit, bool damped) +{ + if (!isRootEdit()) + { + // the relative position with the parent is constant, but the parent's position needs to be changed + LLVector3 position_offset = getPosition() * getParent()->getRotation(); + + ((LLViewerObject *)getParent())->setPositionEdit(pos_edit - position_offset); + updateDrawable(damped); + } + else + { + LLViewerObject::setPosition(pos_edit, damped); + mPositionRegion = pos_edit; + mPositionAgent = mRegionp->getPosAgentFromRegion(mPositionRegion); + } +} + + +LLViewerObject* LLViewerObject::getRootEdit() const +{ + const LLViewerObject* root = this; + while (root->mParent + && !((LLViewerObject*)root->mParent)->isAvatar()) + { + root = (LLViewerObject*)root->mParent; + } + return (LLViewerObject*)root; +} + + +bool LLViewerObject::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, + S32 face, + bool pick_transparent, + bool pick_rigged, + bool pick_unselectable, + S32* face_hit, + LLVector4a* intersection, + LLVector2* tex_coord, + LLVector4a* normal, + LLVector4a* tangent) +{ + return false; +} + +bool LLViewerObject::lineSegmentBoundingBox(const LLVector4a& start, const LLVector4a& end) +{ + if (mDrawable.isNull() || mDrawable->isDead()) + { + return false; + } + + const LLVector4a* ext = mDrawable->getSpatialExtents(); + + //VECTORIZE THIS + LLVector4a center; + center.setAdd(ext[1], ext[0]); + center.mul(0.5f); + LLVector4a size; + size.setSub(ext[1], ext[0]); + size.mul(0.5f); + + return LLLineSegmentBoxIntersect(start, end, center, size); +} + +U8 LLViewerObject::getMediaType() const +{ + if (mMedia) + { + return mMedia->mMediaType; + } + else + { + return LLViewerObject::MEDIA_NONE; + } +} + +void LLViewerObject::setMediaType(U8 media_type) +{ + if (!mMedia) + { + // TODO what if we don't have a media pointer? + } + else if (mMedia->mMediaType != media_type) + { + mMedia->mMediaType = media_type; + + // TODO: update materials with new image + } +} + +std::string LLViewerObject::getMediaURL() const +{ + if (mMedia) + { + return mMedia->mMediaURL; + } + else + { + return std::string(); + } +} + +void LLViewerObject::setMediaURL(const std::string& media_url) +{ + if (!mMedia) + { + mMedia = new LLViewerObjectMedia; + mMedia->mMediaURL = media_url; + mMedia->mPassedWhitelist = false; + + // TODO: update materials with new image + } + else if (mMedia->mMediaURL != media_url) + { + mMedia->mMediaURL = media_url; + mMedia->mPassedWhitelist = false; + + // TODO: update materials with new image + } +} + +bool LLViewerObject::getMediaPassedWhitelist() const +{ + if (mMedia) + { + return mMedia->mPassedWhitelist; + } + else + { + return false; + } +} + +void LLViewerObject::setMediaPassedWhitelist(bool passed) +{ + if (mMedia) + { + mMedia->mPassedWhitelist = passed; + } +} + +bool LLViewerObject::setMaterial(const U8 material) +{ + bool res = LLPrimitive::setMaterial(material); + if (res) + { + setChanged(TEXTURE); + } + return res; +} + +void LLViewerObject::setNumTEs(const U8 num_tes) +{ + U32 i; + if (num_tes != getNumTEs()) + { + if (num_tes) + { + LLPointer *new_images; + new_images = new LLPointer[num_tes]; + + LLPointer *new_normmaps; + new_normmaps = new LLPointer[num_tes]; + + LLPointer *new_specmaps; + new_specmaps = new LLPointer[num_tes]; + for (i = 0; i < num_tes; i++) + { + if (i < getNumTEs()) + { + new_images[i] = mTEImages[i]; + new_normmaps[i] = mTENormalMaps[i]; + new_specmaps[i] = mTESpecularMaps[i]; + } + else if (getNumTEs()) + { + new_images[i] = mTEImages[getNumTEs()-1]; + new_normmaps[i] = mTENormalMaps[getNumTEs()-1]; + new_specmaps[i] = mTESpecularMaps[getNumTEs()-1]; + } + else + { + new_images[i] = NULL; + new_normmaps[i] = NULL; + new_specmaps[i] = NULL; + } + } + + deleteTEImages(); + + mTEImages = new_images; + mTENormalMaps = new_normmaps; + mTESpecularMaps = new_specmaps; + } + else + { + deleteTEImages(); + } + + S32 original_tes = getNumTEs(); + + LLPrimitive::setNumTEs(num_tes); + setChanged(TEXTURE); + + // touch up GLTF materials + if (original_tes > 0) + { + for (int i = original_tes; i < getNumTEs(); ++i) + { + LLTextureEntry* src = getTE(original_tes - 1); + LLTextureEntry* tep = getTE(i); + setRenderMaterialID(i, getRenderMaterialID(original_tes - 1), false); + + if (tep) + { + LLGLTFMaterial* base_material = src->getGLTFMaterial(); + LLGLTFMaterial* override_material = src->getGLTFMaterialOverride(); + if (base_material && override_material) + { + tep->setGLTFMaterialOverride(new LLGLTFMaterial(*override_material)); + + LLGLTFMaterial* render_material = new LLFetchedGLTFMaterial(); + *render_material = *base_material; + render_material->applyOverride(*override_material); + tep->setGLTFRenderMaterial(render_material); + } + } + } + } + + if (mDrawable.notNull()) + { + gPipeline.markTextured(mDrawable); + } + } +} + +void LLViewerObject::sendMaterialUpdate() const +{ + LLViewerRegion* regionp = getRegion(); + if(!regionp) return; + gMessageSystem->newMessageFast(_PREHASH_ObjectMaterial); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + gMessageSystem->nextBlockFast(_PREHASH_ObjectData); + gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, mLocalID ); + gMessageSystem->addU8Fast(_PREHASH_Material, getMaterial() ); + gMessageSystem->sendReliable( regionp->getHost() ); + +} + +//formerly send_object_shape(LLViewerObject *object) +void LLViewerObject::sendShapeUpdate() +{ + gMessageSystem->newMessageFast(_PREHASH_ObjectShape); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + gMessageSystem->nextBlockFast(_PREHASH_ObjectData); + gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, mLocalID ); + + LLVolumeMessage::packVolumeParams(&getVolume()->getParams(), gMessageSystem); + + LLViewerRegion *regionp = getRegion(); + gMessageSystem->sendReliable( regionp->getHost() ); +} + + +void LLViewerObject::sendTEUpdate() const +{ + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_ObjectImage); + + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + + msg->nextBlockFast(_PREHASH_ObjectData); + msg->addU32Fast(_PREHASH_ObjectLocalID, mLocalID ); + if (mMedia) + { + msg->addString("MediaURL", mMedia->mMediaURL); + } + else + { + msg->addString("MediaURL", NULL); + } + + // TODO send media type + + packTEMessage(msg); + + LLViewerRegion *regionp = getRegion(); + msg->sendReliable( regionp->getHost() ); +} + +LLViewerTexture* LLViewerObject::getBakedTextureForMagicId(const LLUUID& id) +{ + if (!LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(id)) + { + return NULL; + } + + LLViewerObject *root = getRootEdit(); + if (root && root->isAnimatedObject()) + { + return LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + } + + LLVOAvatar* avatar = getAvatar(); + if (avatar && !isHUDAttachment()) + { + LLAvatarAppearanceDefines::EBakedTextureIndex texIndex = LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::assetIdToBakedTextureIndex(id); + LLViewerTexture* bakedTexture = avatar->getBakedTexture(texIndex); + if (bakedTexture == NULL || bakedTexture->isMissingAsset()) + { + return LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + } + else + { + return bakedTexture; + } + } + else + { + return LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + } + +} + +void LLViewerObject::updateAvatarMeshVisibility(const LLUUID& id, const LLUUID& old_id) +{ + if (id == old_id) + { + return; + } + + if (!LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(old_id) && !LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(id)) + { + return; + } + + LLVOAvatar* avatar = getAvatar(); + if (avatar) + { + avatar->updateMeshVisibility(); + } +} + + +void LLViewerObject::setTE(const U8 te, const LLTextureEntry& texture_entry) +{ + LLUUID old_image_id; + if (getTE(te)) + { + old_image_id = getTE(te)->getID(); + } + + LLPrimitive::setTE(te, texture_entry); + + const LLUUID& image_id = getTE(te)->getID(); + LLViewerTexture* bakedTexture = getBakedTextureForMagicId(image_id); + mTEImages[te] = bakedTexture ? bakedTexture : LLViewerTextureManager::getFetchedTexture(image_id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + + updateAvatarMeshVisibility(image_id, old_image_id); + + updateTEMaterialTextures(te); +} + +void LLViewerObject::updateTEMaterialTextures(U8 te) +{ + if (getTE(te)->getMaterialParams().notNull()) + { + const LLUUID& norm_id = getTE(te)->getMaterialParams()->getNormalID(); + mTENormalMaps[te] = LLViewerTextureManager::getFetchedTexture(norm_id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + + const LLUUID& spec_id = getTE(te)->getMaterialParams()->getSpecularID(); + mTESpecularMaps[te] = LLViewerTextureManager::getFetchedTexture(spec_id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + } + + LLFetchedGLTFMaterial* mat = (LLFetchedGLTFMaterial*) getTE(te)->getGLTFRenderMaterial(); + llassert(mat == nullptr || dynamic_cast(getTE(te)->getGLTFRenderMaterial()) != nullptr); + LLUUID mat_id = getRenderMaterialID(te); + if (mat == nullptr && mat_id.notNull()) + { + mat = (LLFetchedGLTFMaterial*) gGLTFMaterialList.getMaterial(mat_id); + llassert(mat == nullptr || dynamic_cast(gGLTFMaterialList.getMaterial(mat_id)) != nullptr); + if (mat->isFetching()) + { // material is not loaded yet, rebuild draw info when the object finishes loading + mat->onMaterialComplete([id=getID()] + { + LLViewerObject* obj = gObjectList.findObject(id); + if (obj) + { + obj->markForUpdate(); + } + }); + } + getTE(te)->setGLTFMaterial(mat); + } + else if (mat_id.isNull() && mat != nullptr) + { + mat = nullptr; + getTE(te)->setGLTFMaterial(nullptr); + } + + auto fetch_texture = [this](const LLUUID& id) + { + LLViewerFetchedTexture* img = nullptr; + if (id.notNull()) + { + if (LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(id)) + { + // TODO -- fall back to LLTextureEntry::mGLTFRenderMaterial when overriding with baked texture + LLViewerTexture* viewerTexture = getBakedTextureForMagicId(id); + img = viewerTexture ? dynamic_cast(viewerTexture) : nullptr; + } + else + { + img = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + img->addTextureStats(64.f * 64.f, true); + } + } + + return img; + }; + + if (mat != nullptr) + { + mat->mBaseColorTexture = fetch_texture(mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR]); + mat->mNormalTexture = fetch_texture(mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL]); + mat->mMetallicRoughnessTexture = fetch_texture(mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS]); + mat->mEmissiveTexture= fetch_texture(mat->mTextureId[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE]); + } +} + +void LLViewerObject::refreshBakeTexture() +{ + for (int face_index = 0; face_index < getNumTEs(); face_index++) + { + LLTextureEntry* tex_entry = getTE(face_index); + if (tex_entry && LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::isBakedImageId(tex_entry->getID())) + { + const LLUUID& image_id = tex_entry->getID(); + LLViewerTexture* bakedTexture = getBakedTextureForMagicId(image_id); + changeTEImage(face_index, bakedTexture); + } + } +} + +void LLViewerObject::setTEImage(const U8 te, LLViewerTexture *imagep) +{ + if (mTEImages[te] != imagep) + { + LLUUID old_image_id = getTE(te) ? getTE(te)->getID() : LLUUID::null; + + LLPrimitive::setTETexture(te, imagep->getID()); + + LLViewerTexture* baked_texture = getBakedTextureForMagicId(imagep->getID()); + mTEImages[te] = baked_texture ? baked_texture : imagep; + updateAvatarMeshVisibility(imagep->getID(), old_image_id); + setChanged(TEXTURE); + if (mDrawable.notNull()) + { + gPipeline.markTextured(mDrawable); + } + } +} + +S32 LLViewerObject::setTETextureCore(const U8 te, LLViewerTexture *image) +{ + LLUUID old_image_id = getTE(te)->getID(); + const LLUUID& uuid = image ? image->getID() : LLUUID::null; + S32 retval = 0; + if (uuid != getTE(te)->getID() || + uuid == LLUUID::null) + { + retval = LLPrimitive::setTETexture(te, uuid); + LLViewerTexture* baked_texture = getBakedTextureForMagicId(uuid); + mTEImages[te] = baked_texture ? baked_texture : image; + updateAvatarMeshVisibility(uuid,old_image_id); + setChanged(TEXTURE); + if (mDrawable.notNull()) + { + gPipeline.markTextured(mDrawable); + } + } + return retval; +} + +S32 LLViewerObject::setTENormalMapCore(const U8 te, LLViewerTexture *image) +{ + S32 retval = TEM_CHANGE_TEXTURE; + const LLUUID& uuid = image ? image->getID() : LLUUID::null; + if (uuid != getTE(te)->getID() || + uuid == LLUUID::null) + { + LLTextureEntry* tep = getTE(te); + LLMaterial* mat = NULL; + if (tep) + { + mat = tep->getMaterialParams(); + } + + if (mat) + { + mat->setNormalID(uuid); + } + } + changeTENormalMap(te,image); + return retval; +} + +S32 LLViewerObject::setTESpecularMapCore(const U8 te, LLViewerTexture *image) +{ + S32 retval = TEM_CHANGE_TEXTURE; + const LLUUID& uuid = image ? image->getID() : LLUUID::null; + if (uuid != getTE(te)->getID() || + uuid == LLUUID::null) + { + LLTextureEntry* tep = getTE(te); + LLMaterial* mat = NULL; + if (tep) + { + mat = tep->getMaterialParams(); + } + + if (mat) + { + mat->setSpecularID(uuid); + } + } + changeTESpecularMap(te, image); + return retval; +} + +//virtual +void LLViewerObject::changeTEImage(S32 index, LLViewerTexture* new_image) +{ + if(index < 0 || index >= getNumTEs()) + { + return ; + } + mTEImages[index] = new_image ; +} + +void LLViewerObject::changeTENormalMap(S32 index, LLViewerTexture* new_image) +{ + if(index < 0 || index >= getNumTEs()) + { + return ; + } + mTENormalMaps[index] = new_image ; + refreshMaterials(); +} + +void LLViewerObject::changeTESpecularMap(S32 index, LLViewerTexture* new_image) +{ + if(index < 0 || index >= getNumTEs()) + { + return ; + } + mTESpecularMaps[index] = new_image ; + refreshMaterials(); +} + +S32 LLViewerObject::setTETexture(const U8 te, const LLUUID& uuid) +{ + // Invalid host == get from the agent's sim + LLViewerFetchedTexture *image = LLViewerTextureManager::getFetchedTexture( + uuid, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE, 0, 0, LLHost()); + return setTETextureCore(te, image); +} + +S32 LLViewerObject::setTENormalMap(const U8 te, const LLUUID& uuid) +{ + LLViewerFetchedTexture *image = (uuid == LLUUID::null) ? NULL : LLViewerTextureManager::getFetchedTexture( + uuid, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE, 0, 0, LLHost()); + return setTENormalMapCore(te, image); +} + +S32 LLViewerObject::setTESpecularMap(const U8 te, const LLUUID& uuid) +{ + LLViewerFetchedTexture *image = (uuid == LLUUID::null) ? NULL : LLViewerTextureManager::getFetchedTexture( + uuid, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE, 0, 0, LLHost()); + return setTESpecularMapCore(te, image); +} + +S32 LLViewerObject::setTEColor(const U8 te, const LLColor3& color) +{ + return setTEColor(te, LLColor4(color)); +} + +S32 LLViewerObject::setTEColor(const U8 te, const LLColor4& color) +{ + S32 retval = 0; + const LLTextureEntry *tep = getTE(te); + if (!tep) + { + LL_WARNS() << "No texture entry for te " << (S32)te << ", object " << mID << LL_ENDL; + } + else if (color != tep->getColor()) + { + retval = LLPrimitive::setTEColor(te, color); + if (mDrawable.notNull() && retval) + { + // These should only happen on updates which are not the initial update. + dirtyMesh(); + } + } + return retval; +} + +S32 LLViewerObject::setTEBumpmap(const U8 te, const U8 bump) +{ + S32 retval = 0; + const LLTextureEntry *tep = getTE(te); + if (!tep) + { + LL_WARNS() << "No texture entry for te " << (S32)te << ", object " << mID << LL_ENDL; + } + else if (bump != tep->getBumpmap()) + { + retval = LLPrimitive::setTEBumpmap(te, bump); + setChanged(TEXTURE); + if (mDrawable.notNull() && retval) + { + gPipeline.markTextured(mDrawable); + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_GEOMETRY); + } + } + return retval; +} + +S32 LLViewerObject::setTETexGen(const U8 te, const U8 texgen) +{ + S32 retval = 0; + const LLTextureEntry *tep = getTE(te); + if (!tep) + { + LL_WARNS() << "No texture entry for te " << (S32)te << ", object " << mID << LL_ENDL; + } + else if (texgen != tep->getTexGen()) + { + retval = LLPrimitive::setTETexGen(te, texgen); + setChanged(TEXTURE); + } + return retval; +} + +S32 LLViewerObject::setTEMediaTexGen(const U8 te, const U8 media) +{ + S32 retval = 0; + const LLTextureEntry *tep = getTE(te); + if (!tep) + { + LL_WARNS() << "No texture entry for te " << (S32)te << ", object " << mID << LL_ENDL; + } + else if (media != tep->getMediaTexGen()) + { + retval = LLPrimitive::setTEMediaTexGen(te, media); + setChanged(TEXTURE); + } + return retval; +} + +S32 LLViewerObject::setTEShiny(const U8 te, const U8 shiny) +{ + S32 retval = 0; + const LLTextureEntry *tep = getTE(te); + if (!tep) + { + LL_WARNS() << "No texture entry for te " << (S32)te << ", object " << mID << LL_ENDL; + } + else if (shiny != tep->getShiny()) + { + retval = LLPrimitive::setTEShiny(te, shiny); + setChanged(TEXTURE); + } + return retval; +} + +S32 LLViewerObject::setTEFullbright(const U8 te, const U8 fullbright) +{ + S32 retval = 0; + const LLTextureEntry *tep = getTE(te); + if (!tep) + { + LL_WARNS() << "No texture entry for te " << (S32)te << ", object " << mID << LL_ENDL; + } + else if (fullbright != tep->getFullbright()) + { + retval = LLPrimitive::setTEFullbright(te, fullbright); + setChanged(TEXTURE); + if (mDrawable.notNull() && retval) + { + gPipeline.markTextured(mDrawable); + } + } + return retval; +} + + +S32 LLViewerObject::setTEMediaFlags(const U8 te, const U8 media_flags) +{ + // this might need work for media type + S32 retval = 0; + const LLTextureEntry *tep = getTE(te); + if (!tep) + { + LL_WARNS() << "No texture entry for te " << (S32)te << ", object " << mID << LL_ENDL; + } + else if (media_flags != tep->getMediaFlags()) + { + retval = LLPrimitive::setTEMediaFlags(te, media_flags); + setChanged(TEXTURE); + if (mDrawable.notNull() && retval) + { + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_TCOORD); + gPipeline.markTextured(mDrawable); + } + } + return retval; +} + +S32 LLViewerObject::setTEGlow(const U8 te, const F32 glow) +{ + S32 retval = 0; + const LLTextureEntry *tep = getTE(te); + if (!tep) + { + LL_WARNS() << "No texture entry for te " << (S32)te << ", object " << mID << LL_ENDL; + } + else if (glow != tep->getGlow()) + { + retval = LLPrimitive::setTEGlow(te, glow); + setChanged(TEXTURE); + if (mDrawable.notNull() && retval) + { + gPipeline.markTextured(mDrawable); + } + } + return retval; +} + +S32 LLViewerObject::setTEMaterialID(const U8 te, const LLMaterialID& pMaterialID) +{ + S32 retval = 0; + const LLTextureEntry *tep = getTE(te); + if (!tep) + { + LL_WARNS("Material") << "No texture entry for te " << (S32)te + << ", object " << mID + << ", material " << pMaterialID + << LL_ENDL; + } + //else if (pMaterialID != tep->getMaterialID()) + { + LL_DEBUGS("Material") << "Changing texture entry for te " << (S32)te + << ", object " << mID + << ", material " << pMaterialID + << LL_ENDL; + retval = LLPrimitive::setTEMaterialID(te, pMaterialID); + refreshMaterials(); + } + return retval; +} + +S32 LLViewerObject::setTEMaterialParams(const U8 te, const LLMaterialPtr pMaterialParams) +{ + S32 retval = 0; + const LLTextureEntry *tep = getTE(te); + if (!tep) + { + LL_WARNS() << "No texture entry for te " << (S32)te << ", object " << mID << LL_ENDL; + return 0; + } + + retval = LLPrimitive::setTEMaterialParams(te, pMaterialParams); + LL_DEBUGS("Material") << "Changing material params for te " << (S32)te + << ", object " << mID + << " (" << retval << ")" + << LL_ENDL; + setTENormalMap(te, (pMaterialParams) ? pMaterialParams->getNormalID() : LLUUID::null); + setTESpecularMap(te, (pMaterialParams) ? pMaterialParams->getSpecularID() : LLUUID::null); + + return retval; +} + +S32 LLViewerObject::setTEGLTFMaterialOverride(U8 te, LLGLTFMaterial* override_mat) +{ + LL_PROFILE_ZONE_SCOPED; + S32 retval = TEM_CHANGE_NONE; + + LLTextureEntry* tep = getTE(te); + if (!tep) + { // this could happen if the object is not fully formed yet + // returning TEM_CHANGE_NONE here signals to LLGLTFMaterialList to queue the override for later + return retval; + } + + LLFetchedGLTFMaterial* src_mat = (LLFetchedGLTFMaterial*) tep->getGLTFMaterial(); + llassert(src_mat == nullptr || dynamic_cast(tep->getGLTFMaterial()) != nullptr); + // if override mat exists, we must also have a source mat + if (!src_mat) + { + // we can get into this state if an override has arrived before the viewer has + // received or handled an update, return TEM_CHANGE_NONE to signal to LLGLTFMaterialList that it + // should queue the update for later + return retval; + } + + if(src_mat->isFetching()) + { + // if still fetching, we need to wait until it is done and try again + return retval; + } + + retval = tep->setGLTFMaterialOverride(override_mat); + + if (retval) + { + if (override_mat) + { + LLFetchedGLTFMaterial* render_mat = new LLFetchedGLTFMaterial(*src_mat); + render_mat->applyOverride(*override_mat); + tep->setGLTFRenderMaterial(render_mat); + retval = TEM_CHANGE_TEXTURE; + + for (LLGLTFMaterial::local_tex_map_t::value_type &val : override_mat->mTrackingIdToLocalTexture) + { + LLLocalBitmapMgr::getInstance()->associateGLTFMaterial(val.first, override_mat); + } + + } + else if (tep->setGLTFRenderMaterial(nullptr)) + { + retval = TEM_CHANGE_TEXTURE; + } + } + + return retval; +} + +void LLViewerObject::refreshMaterials() +{ + setChanged(TEXTURE); + if (mDrawable.notNull()) + { + gPipeline.markTextured(mDrawable); + } +} + +S32 LLViewerObject::setTEScale(const U8 te, const F32 s, const F32 t) +{ + S32 retval = 0; + retval = LLPrimitive::setTEScale(te, s, t); + setChanged(TEXTURE); + if (mDrawable.notNull() && retval) + { + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_TCOORD); + } + return retval; +} + +S32 LLViewerObject::setTEScaleS(const U8 te, const F32 s) +{ + S32 retval = LLPrimitive::setTEScaleS(te, s); + if (mDrawable.notNull() && retval) + { + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_TCOORD); + } + + return retval; +} + +S32 LLViewerObject::setTEScaleT(const U8 te, const F32 t) +{ + S32 retval = LLPrimitive::setTEScaleT(te, t); + if (mDrawable.notNull() && retval) + { + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_TCOORD); + } + + return retval; +} + +S32 LLViewerObject::setTEOffset(const U8 te, const F32 s, const F32 t) +{ + S32 retval = LLPrimitive::setTEOffset(te, s, t); + if (mDrawable.notNull() && retval) + { + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_TCOORD); + } + return retval; +} + +S32 LLViewerObject::setTEOffsetS(const U8 te, const F32 s) +{ + S32 retval = LLPrimitive::setTEOffsetS(te, s); + if (mDrawable.notNull() && retval) + { + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_TCOORD); + } + + return retval; +} + +S32 LLViewerObject::setTEOffsetT(const U8 te, const F32 t) +{ + S32 retval = LLPrimitive::setTEOffsetT(te, t); + if (mDrawable.notNull() && retval) + { + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_TCOORD); + } + + return retval; +} + +S32 LLViewerObject::setTERotation(const U8 te, const F32 r) +{ + S32 retval = LLPrimitive::setTERotation(te, r); + if (mDrawable.notNull() && retval) + { + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_TCOORD); + shrinkWrap(); + } + return retval; +} + + +LLViewerTexture *LLViewerObject::getTEImage(const U8 face) const +{ +// llassert(mTEImages); + + if (face < getNumTEs()) + { + LLViewerTexture* image = mTEImages[face]; + if (image) + { + return image; + } + else + { + return (LLViewerTexture*)(LLViewerFetchedTexture::sDefaultImagep); + } + } + + LL_ERRS() << llformat("Requested Image from invalid face: %d/%d",face,getNumTEs()) << LL_ENDL; + + return NULL; +} + + +bool LLViewerObject::isImageAlphaBlended(const U8 te) const +{ + LLViewerTexture* image = getTEImage(te); + LLGLenum format = image ? image->getPrimaryFormat() : GL_RGB; + switch (format) + { + case GL_RGBA: + case GL_ALPHA: + { + return true; + } + break; + + case GL_RGB: break; + default: + { + LL_WARNS() << "Unexpected tex format in LLViewerObject::isImageAlphaBlended...returning no alpha." << LL_ENDL; + } + break; + } + + return false; +} + +LLViewerTexture *LLViewerObject::getTENormalMap(const U8 face) const +{ + // llassert(mTEImages); + + if (face < getNumTEs()) + { + LLViewerTexture* image = mTENormalMaps[face]; + if (image) + { + return image; + } + else + { + return (LLViewerTexture*)(LLViewerFetchedTexture::sDefaultImagep); + } + } + + LL_ERRS() << llformat("Requested Image from invalid face: %d/%d",face,getNumTEs()) << LL_ENDL; + + return NULL; +} + +LLViewerTexture *LLViewerObject::getTESpecularMap(const U8 face) const +{ + // llassert(mTEImages); + + if (face < getNumTEs()) + { + LLViewerTexture* image = mTESpecularMaps[face]; + if (image) + { + return image; + } + else + { + return (LLViewerTexture*)(LLViewerFetchedTexture::sDefaultImagep); + } + } + + LL_ERRS() << llformat("Requested Image from invalid face: %d/%d",face,getNumTEs()) << LL_ENDL; + + return NULL; +} + +void LLViewerObject::fitFaceTexture(const U8 face) +{ + LL_INFOS() << "fitFaceTexture not implemented" << LL_ENDL; +} + +LLBBox LLViewerObject::getBoundingBoxAgent() const +{ + LLVector3 position_agent; + LLQuaternion rot; + LLViewerObject* avatar_parent = NULL; + LLViewerObject* root_edit = (LLViewerObject*)getRootEdit(); + if (root_edit) + { + avatar_parent = (LLViewerObject*)root_edit->getParent(); + } + + if (avatar_parent && avatar_parent->isAvatar() && + root_edit && root_edit->mDrawable.notNull() && root_edit->mDrawable->getXform()->getParent()) + { + LLXform* parent_xform = root_edit->mDrawable->getXform()->getParent(); + position_agent = (getPositionEdit() * parent_xform->getWorldRotation()) + parent_xform->getWorldPosition(); + rot = getRotationEdit() * parent_xform->getWorldRotation(); + } + else + { + position_agent = getPositionAgent(); + rot = getRotationRegion(); + } + + return LLBBox( position_agent, rot, getScale() * -0.5f, getScale() * 0.5f ); +} + +U32 LLViewerObject::getNumVertices() const +{ + U32 num_vertices = 0; + if (mDrawable.notNull()) + { + S32 i, num_faces; + num_faces = mDrawable->getNumFaces(); + for (i = 0; i < num_faces; i++) + { + LLFace * facep = mDrawable->getFace(i); + if (facep) + { + num_vertices += facep->getGeomCount(); + } + } + } + return num_vertices; +} + +U32 LLViewerObject::getNumIndices() const +{ + U32 num_indices = 0; + if (mDrawable.notNull()) + { + S32 i, num_faces; + num_faces = mDrawable->getNumFaces(); + for (i = 0; i < num_faces; i++) + { + LLFace * facep = mDrawable->getFace(i); + if (facep) + { + num_indices += facep->getIndicesCount(); + } + } + } + return num_indices; +} + +// Find the number of instances of this object's inventory that are of the given type +S32 LLViewerObject::countInventoryContents(LLAssetType::EType type) +{ + S32 count = 0; + if( mInventory ) + { + LLInventoryObject::object_list_t::const_iterator it = mInventory->begin(); + LLInventoryObject::object_list_t::const_iterator end = mInventory->end(); + for( ; it != end ; ++it ) + { + if( (*it)->getType() == type ) + { + ++count; + } + } + } + return count; +} + +void LLViewerObject::setDebugText(const std::string &utf8text, const LLColor4& color) +{ + if (utf8text.empty() && !mText) + { + return; + } + + if (!mText) + { + initHudText(); + } + mText->setColor(color); + mText->setString(utf8text); + mText->setZCompare(false); + mText->setDoFade(false); + updateText(); +} + +void LLViewerObject::appendDebugText(const std::string &utf8text) +{ + if (utf8text.empty() && !mText) + { + return; + } + + if (!mText) + { + initHudText(); + } + mText->addLine(utf8text, LLColor4::white); + mText->setZCompare(false); + mText->setDoFade(false); + updateText(); +} + +void LLViewerObject::initHudText() +{ + mText = (LLHUDText *)LLHUDObject::addHUDObject(LLHUDObject::LL_HUD_TEXT); + mText->setFont(LLFontGL::getFontSansSerif()); + mText->setVertAlignment(LLHUDText::ALIGN_VERT_TOP); + mText->setMaxLines(-1); + mText->setSourceObject(this); + mText->setOnHUDAttachment(isHUDAttachment()); +} + +void LLViewerObject::restoreHudText() +{ + if (mHudText.empty()) + { + if (mText) + { + mText->markDead(); + mText = NULL; + } + } + else + { + if (!mText) + { + initHudText(); + } + else + { + // Restore default values + mText->setZCompare(true); + mText->setDoFade(true); + } + mText->setColor(mHudTextColor); + mText->setString(mHudText); + } +} + +void LLViewerObject::setIcon(LLViewerTexture* icon_image) +{ + if (!mIcon) + { + mIcon = (LLHUDIcon *)LLHUDObject::addHUDObject(LLHUDObject::LL_HUD_ICON); + mIcon->setSourceObject(this); + mIcon->setImage(icon_image); + // *TODO: make this user configurable + mIcon->setScale(0.03f); + } + else + { + mIcon->restartLifeTimer(); + } +} + +void LLViewerObject::clearIcon() +{ + if (mIcon) + { + mIcon = NULL; + } +} + +LLViewerObject* LLViewerObject::getSubParent() +{ + return (LLViewerObject*) getParent(); +} + +const LLViewerObject* LLViewerObject::getSubParent() const +{ + return (const LLViewerObject*) getParent(); +} + +bool LLViewerObject::isOnMap() +{ + return mOnMap; +} + + +void LLViewerObject::updateText() +{ + if (!isDead()) + { + if (mText.notNull()) + { + LLVOAvatar* avatar = getAvatar(); + if (avatar) + { + mText->setHidden(avatar->isInMuteList()); + } + + LLVector3 up_offset(0,0,0); + up_offset.mV[2] = getScale().mV[VZ]*0.6f; + + if (mDrawable.notNull()) + { + mText->setPositionAgent(getRenderPosition() + up_offset); + } + else + { + mText->setPositionAgent(getPositionAgent() + up_offset); + } + } + } +} + +bool LLViewerObject::isOwnerInMuteList(LLUUID id) +{ + LLUUID owner_id = id.isNull() ? mOwnerID : id; + if (isAvatar() || owner_id.isNull()) + { + return false; + } + bool muted = false; + F64 now = LLFrameTimer::getTotalSeconds(); + if (now < mCachedMuteListUpdateTime) + { + muted = mCachedOwnerInMuteList; + } + else + { + muted = LLMuteList::getInstance()->isMuted(owner_id); + + const F64 SECONDS_BETWEEN_MUTE_UPDATES = 1; + mCachedMuteListUpdateTime = now + SECONDS_BETWEEN_MUTE_UPDATES; + mCachedOwnerInMuteList = muted; + } + return muted; +} + +LLVOAvatar* LLViewerObject::asAvatar() +{ + return NULL; +} + +// If this object is directly or indirectly parented by an avatar, +// return it. Normally getAvatar() is the correct function to call; +// it will give the avatar used for skinning. The exception is with +// animated objects that are also attachments; in that case, +// getAvatar() will return the control avatar, used for skinning, and +// getAvatarAncestor will return the avatar to which the object is +// attached. +LLVOAvatar* LLViewerObject::getAvatarAncestor() +{ + LLViewerObject *pobj = (LLViewerObject*) getParent(); + while (pobj) + { + LLVOAvatar *av = pobj->asAvatar(); + if (av) + { + return av; + } + pobj = (LLViewerObject*) pobj->getParent(); + } + return NULL; +} + +bool LLViewerObject::isParticleSource() const +{ + return !mPartSourcep.isNull() && !mPartSourcep->isDead(); +} + +void LLViewerObject::setParticleSource(const LLPartSysData& particle_parameters, const LLUUID& owner_id) +{ + if (mPartSourcep) + { + deleteParticleSource(); + } + + LLPointer pss = LLViewerPartSourceScript::createPSS(this, particle_parameters); + mPartSourcep = pss; + + if (mPartSourcep) + { + mPartSourcep->setOwnerUUID(owner_id); + + if (mPartSourcep->getImage()->getID() != mPartSourcep->mPartSysData.mPartImageID) + { + LLViewerTexture* image; + if (mPartSourcep->mPartSysData.mPartImageID == LLUUID::null) + { + image = LLViewerTextureManager::getFetchedTextureFromFile("pixiesmall.tga"); + } + else + { + image = LLViewerTextureManager::getFetchedTexture(mPartSourcep->mPartSysData.mPartImageID); + } + mPartSourcep->setImage(image); + } + } + LLViewerPartSim::getInstance()->addPartSource(pss); +} + +void LLViewerObject::unpackParticleSource(const S32 block_num, const LLUUID& owner_id) +{ + if (!mPartSourcep.isNull() && mPartSourcep->isDead()) + { + mPartSourcep = NULL; + } + if (mPartSourcep) + { + // If we've got one already, just update the existing source (or remove it) + if (!LLViewerPartSourceScript::unpackPSS(this, mPartSourcep, block_num)) + { + mPartSourcep->setDead(); + mPartSourcep = NULL; + } + } + else + { + LLPointer pss = LLViewerPartSourceScript::unpackPSS(this, NULL, block_num); + //If the owner is muted, don't create the system + if(LLMuteList::getInstance()->isMuted(owner_id, LLMute::flagParticles)) return; + + // We need to be able to deal with a particle source that hasn't changed, but still got an update! + if (pss) + { +// LL_INFOS() << "Making particle system with owner " << owner_id << LL_ENDL; + pss->setOwnerUUID(owner_id); + mPartSourcep = pss; + LLViewerPartSim::getInstance()->addPartSource(pss); + } + } + if (mPartSourcep) + { + if (mPartSourcep->getImage()->getID() != mPartSourcep->mPartSysData.mPartImageID) + { + LLViewerTexture* image; + if (mPartSourcep->mPartSysData.mPartImageID == LLUUID::null) + { + image = LLViewerTextureManager::getFetchedTextureFromFile("pixiesmall.j2c"); + } + else + { + image = LLViewerTextureManager::getFetchedTexture(mPartSourcep->mPartSysData.mPartImageID); + } + mPartSourcep->setImage(image); + } + } +} + +void LLViewerObject::unpackParticleSource(LLDataPacker &dp, const LLUUID& owner_id, bool legacy) +{ + if (!mPartSourcep.isNull() && mPartSourcep->isDead()) + { + mPartSourcep = NULL; + } + if (mPartSourcep) + { + // If we've got one already, just update the existing source (or remove it) + if (!LLViewerPartSourceScript::unpackPSS(this, mPartSourcep, dp, legacy)) + { + mPartSourcep->setDead(); + mPartSourcep = NULL; + } + } + else + { + LLPointer pss = LLViewerPartSourceScript::unpackPSS(this, NULL, dp, legacy); + //If the owner is muted, don't create the system + if(LLMuteList::getInstance()->isMuted(owner_id, LLMute::flagParticles)) return; + // We need to be able to deal with a particle source that hasn't changed, but still got an update! + if (pss) + { +// LL_INFOS() << "Making particle system with owner " << owner_id << LL_ENDL; + pss->setOwnerUUID(owner_id); + mPartSourcep = pss; + LLViewerPartSim::getInstance()->addPartSource(pss); + } + } + if (mPartSourcep) + { + if (mPartSourcep->getImage()->getID() != mPartSourcep->mPartSysData.mPartImageID) + { + LLViewerTexture* image; + if (mPartSourcep->mPartSysData.mPartImageID == LLUUID::null) + { + image = LLViewerTextureManager::getFetchedTextureFromFile("pixiesmall.j2c"); + } + else + { + image = LLViewerTextureManager::getFetchedTexture(mPartSourcep->mPartSysData.mPartImageID); + } + mPartSourcep->setImage(image); + } + } +} + +void LLViewerObject::deleteParticleSource() +{ + if (mPartSourcep.notNull()) + { + mPartSourcep->setDead(); + mPartSourcep = NULL; + } +} + +// virtual +void LLViewerObject::updateDrawable(bool force_damped) +{ + if (!isChanged(MOVED)) + { //most common case, having an empty if case here makes for better branch prediction + } + else if (mDrawable.notNull() && + !mDrawable->isState(LLDrawable::ON_MOVE_LIST)) + { + bool damped_motion = + !isChanged(SHIFTED) && // not shifted between regions this frame and... + ( force_damped || // ...forced into damped motion by application logic or... + ( !isSelected() && // ...not selected and... + ( mDrawable->isRoot() || // ... is root or ... + (getParent() && !((LLViewerObject*)getParent())->isSelected())// ... parent is not selected and ... + ) && + getPCode() == LL_PCODE_VOLUME && // ...is a volume object and... + getVelocity().isExactlyZero() && // ...is not moving physically and... + mDrawable->getGeneration() != -1 // ...was not created this frame. + ) + ); + gPipeline.markMoved(mDrawable, damped_motion); + } + clearChanged(SHIFTED); +} + +// virtual, overridden by LLVOVolume +F32 LLViewerObject::getVObjRadius() const +{ + return mDrawable.notNull() ? mDrawable->getRadius() : 0.f; +} + +void LLViewerObject::setAttachedSound(const LLUUID &audio_uuid, const LLUUID& owner_id, const F32 gain, const U8 flags) +{ + if (!gAudiop) + { + return; + } + + if (audio_uuid.isNull()) + { + if (!mAudioSourcep) + { + return; + } + if (mAudioSourcep->isLoop() && !mAudioSourcep->hasPendingPreloads()) + { + // We don't clear the sound if it's a loop, it'll go away on its own. + // At least, this appears to be how the scripts work. + // The attached sound ID is set to NULL to avoid it playing back when the + // object rezzes in on non-looping sounds. + //LL_INFOS() << "Clearing attached sound " << mAudioSourcep->getCurrentData()->getID() << LL_ENDL; + gAudiop->cleanupAudioSource(mAudioSourcep); + mAudioSourcep = NULL; + } + else if (flags & LL_SOUND_FLAG_STOP) + { + // Just shut off the sound + mAudioSourcep->stop(); + } + return; + } + if (flags & LL_SOUND_FLAG_LOOP + && mAudioSourcep && mAudioSourcep->isLoop() && mAudioSourcep->getCurrentData() + && mAudioSourcep->getCurrentData()->getID() == audio_uuid) + { + //LL_INFOS() << "Already playing this sound on a loop, ignoring" << LL_ENDL; + return; + } + + // don't clean up before previous sound is done. Solves: SL-33486 + if ( mAudioSourcep && mAudioSourcep->isDone() ) + { + gAudiop->cleanupAudioSource(mAudioSourcep); + mAudioSourcep = NULL; + } + + if (mAudioSourcep && mAudioSourcep->isMuted() && + mAudioSourcep->getCurrentData() && mAudioSourcep->getCurrentData()->getID() == audio_uuid) + { + //LL_INFOS() << "Already having this sound as muted sound, ignoring" << LL_ENDL; + return; + } + + getAudioSource(owner_id); + + if (mAudioSourcep) + { + bool queue = flags & LL_SOUND_FLAG_QUEUE; + mAudioGain = gain; + mAudioSourcep->setGain(gain); + mAudioSourcep->setLoop(flags & LL_SOUND_FLAG_LOOP); + mAudioSourcep->setSyncMaster(flags & LL_SOUND_FLAG_SYNC_MASTER); + mAudioSourcep->setSyncSlave(flags & LL_SOUND_FLAG_SYNC_SLAVE); + mAudioSourcep->setQueueSounds(queue); + if(!queue) // stop any current sound first to avoid "farts of doom" (SL-1541) -MG + { + mAudioSourcep->stop(); + } + + // Play this sound if region maturity permits + if( gAgent.canAccessMaturityAtGlobal(this->getPositionGlobal()) ) + { + //LL_INFOS() << "Playing attached sound " << audio_uuid << LL_ENDL; + // recheck cutoff radius in case this update was an object-update with new value + mAudioSourcep->checkCutOffRadius(); + mAudioSourcep->play(audio_uuid); + } + } +} + +LLAudioSource *LLViewerObject::getAudioSource(const LLUUID& owner_id) +{ + if (!mAudioSourcep) + { + // Arbitrary low gain for a sound that's not playing. + // This is used for sound preloads, for example. + LLAudioSourceVO *asvop = new LLAudioSourceVO(mID, owner_id, 0.01f, this); + + mAudioSourcep = asvop; + if(gAudiop) + { + gAudiop->addAudioSource(asvop); + } + } + + return mAudioSourcep; +} + +void LLViewerObject::adjustAudioGain(const F32 gain) +{ + if (mAudioSourcep) + { + mAudioGain = gain; + mAudioSourcep->setGain(mAudioGain); + } +} + +//---------------------------------------------------------------------------- + +bool LLViewerObject::unpackParameterEntry(U16 param_type, LLDataPacker *dp) +{ + if (LLNetworkData::PARAMS_MESH == param_type) + { + param_type = LLNetworkData::PARAMS_SCULPT; + } + ExtraParameter* param = getExtraParameterEntryCreate(param_type); + if (param) + { + param->data->unpack(*dp); + param->in_use = true; + parameterChanged(param_type, param->data, true, false); + return true; + } + else + { + return false; + } +} + +LLViewerObject::ExtraParameter* LLViewerObject::createNewParameterEntry(U16 param_type) +{ + LLNetworkData* new_block = NULL; + switch (param_type) + { + case LLNetworkData::PARAMS_FLEXIBLE: + { + new_block = new LLFlexibleObjectData(); + break; + } + case LLNetworkData::PARAMS_LIGHT: + { + new_block = new LLLightParams(); + break; + } + case LLNetworkData::PARAMS_SCULPT: + { + new_block = new LLSculptParams(); + break; + } + case LLNetworkData::PARAMS_LIGHT_IMAGE: + { + new_block = new LLLightImageParams(); + break; + } + case LLNetworkData::PARAMS_EXTENDED_MESH: + { + new_block = new LLExtendedMeshParams(); + break; + } + case LLNetworkData::PARAMS_RENDER_MATERIAL: + { + new_block = new LLRenderMaterialParams(); + break; + } + case LLNetworkData::PARAMS_REFLECTION_PROBE: + { + new_block = new LLReflectionProbeParams(); + break; + } + default: + { + LL_INFOS_ONCE() << "Unknown param type: " << param_type << LL_ENDL; + break; + } + }; + + if (new_block) + { + ExtraParameter* new_entry = new ExtraParameter; + new_entry->data = new_block; + new_entry->in_use = false; // not in use yet + llassert(mExtraParameterList[param_type] == nullptr); // leak -- redundantly allocated parameter entry + mExtraParameterList[param_type] = new_entry; + return new_entry; + } + return NULL; +} + +LLViewerObject::ExtraParameter* LLViewerObject::getExtraParameterEntry(U16 param_type) const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VIEWER; + std::unordered_map::const_iterator itor = mExtraParameterList.find(param_type); + if (itor != mExtraParameterList.end()) + { + return itor->second; + } + return NULL; +} + +LLViewerObject::ExtraParameter* LLViewerObject::getExtraParameterEntryCreate(U16 param_type) +{ + ExtraParameter* param = getExtraParameterEntry(param_type); + if (!param) + { + param = createNewParameterEntry(param_type); + } + return param; +} + +LLNetworkData* LLViewerObject::getParameterEntry(U16 param_type) const +{ + ExtraParameter* param = getExtraParameterEntry(param_type); + if (param) + { + return param->data; + } + else + { + return NULL; + } +} + +bool LLViewerObject::getParameterEntryInUse(U16 param_type) const +{ + ExtraParameter* param = getExtraParameterEntry(param_type); + if (param) + { + return param->in_use; + } + else + { + return false; + } +} + +bool LLViewerObject::setParameterEntry(U16 param_type, const LLNetworkData& new_value, bool local_origin) +{ + ExtraParameter* param = getExtraParameterEntryCreate(param_type); + if (param) + { + if (param->in_use && new_value == *(param->data)) + { + return false; + } + param->in_use = true; + param->data->copy(new_value); + parameterChanged(param_type, param->data, true, local_origin); + return true; + } + else + { + return false; + } +} + +// Assumed to be called locally +// If in_use is true, will crate a new extra parameter if none exists. +// Should always return true. +bool LLViewerObject::setParameterEntryInUse(U16 param_type, bool in_use, bool local_origin) +{ + ExtraParameter* param = getExtraParameterEntryCreate(param_type); + if (param && param->in_use != in_use) + { + param->in_use = in_use; + parameterChanged(param_type, param->data, in_use, local_origin); + return true; + } + return false; +} + +void LLViewerObject::parameterChanged(U16 param_type, bool local_origin) +{ + ExtraParameter* param = getExtraParameterEntry(param_type); + if (param) + { + parameterChanged(param_type, param->data, param->in_use, local_origin); + } +} + +void LLViewerObject::parameterChanged(U16 param_type, LLNetworkData* data, bool in_use, bool local_origin) +{ + if (local_origin) + { + // *NOTE: Do not send the render material ID in this way as it will get + // out-of-sync with other sent client data. + // See LLViewerObject::setRenderMaterialID and LLGLTFMaterialList + llassert(param_type != LLNetworkData::PARAMS_RENDER_MATERIAL); + + LLViewerRegion* regionp = getRegion(); + if(!regionp) return; + + // Change happened on the viewer. Send the change up + U8 tmp[MAX_OBJECT_PARAMS_SIZE]; + LLDataPackerBinaryBuffer dpb(tmp, MAX_OBJECT_PARAMS_SIZE); + if (data->pack(dpb)) + { + U32 datasize = (U32)dpb.getCurrentSize(); + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_ObjectExtraParams); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ObjectData); + msg->addU32Fast(_PREHASH_ObjectLocalID, mLocalID ); + + msg->addU16Fast(_PREHASH_ParamType, param_type); + msg->addBOOLFast(_PREHASH_ParamInUse, in_use); + + msg->addU32Fast(_PREHASH_ParamSize, datasize); + msg->addBinaryDataFast(_PREHASH_ParamData, tmp, datasize); + + msg->sendReliable( regionp->getHost() ); + } + else + { + LL_WARNS() << "Failed to send object extra parameters: " << param_type << LL_ENDL; + } + } + else + { + if (param_type == LLNetworkData::PARAMS_RENDER_MATERIAL) + { + const LLRenderMaterialParams* params = in_use ? (LLRenderMaterialParams*)getParameterEntry(LLNetworkData::PARAMS_RENDER_MATERIAL) : nullptr; + setRenderMaterialIDs(params, local_origin); + } + } +} + +void LLViewerObject::setDrawableState(U32 state, bool recursive) +{ + if (mDrawable) + { + mDrawable->setState(state); + } + if (recursive) + { + for (child_list_t::iterator iter = mChildList.begin(); + iter != mChildList.end(); iter++) + { + LLViewerObject* child = *iter; + child->setDrawableState(state, recursive); + } + } +} + +void LLViewerObject::clearDrawableState(U32 state, bool recursive) +{ + if (mDrawable) + { + mDrawable->clearState(state); + } + if (recursive) + { + for (child_list_t::iterator iter = mChildList.begin(); + iter != mChildList.end(); iter++) + { + LLViewerObject* child = *iter; + child->clearDrawableState(state, recursive); + } + } +} + +bool LLViewerObject::isDrawableState(U32 state, bool recursive) const +{ + bool matches = false; + if (mDrawable) + { + matches = mDrawable->isState(state); + } + if (recursive) + { + for (child_list_t::const_iterator iter = mChildList.begin(); + (iter != mChildList.end()) && matches; iter++) + { + LLViewerObject* child = *iter; + matches &= child->isDrawableState(state, recursive); + } + } + + return matches; +} + + + +//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// RN: these functions assume a 2-level hierarchy +//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +// Owned by anyone? +bool LLViewerObject::permAnyOwner() const +{ + if (isRootEdit()) + { + return flagObjectAnyOwner(); + } + else + { + return ((LLViewerObject*)getParent())->permAnyOwner(); + } +} +// Owned by this viewer? +bool LLViewerObject::permYouOwner() const +{ + if (isRootEdit()) + { +#ifdef HACKED_GODLIKE_VIEWER + return true; +#else +# ifdef TOGGLE_HACKED_GODLIKE_VIEWER + if (!LLGridManager::getInstance()->isInProductionGrid() + && (gAgent.getGodLevel() >= GOD_MAINTENANCE)) + { + return true; + } +# endif + return flagObjectYouOwner(); +#endif + } + else + { + return ((LLViewerObject*)getParent())->permYouOwner(); + } +} + +// Owned by a group? +bool LLViewerObject::permGroupOwner() const +{ + if (isRootEdit()) + { + return flagObjectGroupOwned(); + } + else + { + return ((LLViewerObject*)getParent())->permGroupOwner(); + } +} + +// Can the owner edit +bool LLViewerObject::permOwnerModify() const +{ + if (isRootEdit()) + { +#ifdef HACKED_GODLIKE_VIEWER + return true; +#else +# ifdef TOGGLE_HACKED_GODLIKE_VIEWER + if (!LLGridManager::getInstance()->isInProductionGrid() + && (gAgent.getGodLevel() >= GOD_MAINTENANCE)) + { + return true; + } +# endif + return flagObjectOwnerModify(); +#endif + } + else + { + return ((LLViewerObject*)getParent())->permOwnerModify(); + } +} + +// Can edit +bool LLViewerObject::permModify() const +{ + if (isRootEdit()) + { +#ifdef HACKED_GODLIKE_VIEWER + return true; +#else +# ifdef TOGGLE_HACKED_GODLIKE_VIEWER + if (!LLGridManager::getInstance()->isInProductionGrid() + && (gAgent.getGodLevel() >= GOD_MAINTENANCE)) + { + return true; + } +# endif + return flagObjectModify(); +#endif + } + else + { + return ((LLViewerObject*)getParent())->permModify(); + } +} + +// Can copy +bool LLViewerObject::permCopy() const +{ + if (isRootEdit()) + { +#ifdef HACKED_GODLIKE_VIEWER + return true; +#else +# ifdef TOGGLE_HACKED_GODLIKE_VIEWER + if (!LLGridManager::getInstance()->isInProductionGrid() + && (gAgent.getGodLevel() >= GOD_MAINTENANCE)) + { + return true; + } +# endif + return flagObjectCopy(); +#endif + } + else + { + return ((LLViewerObject*)getParent())->permCopy(); + } +} + +// Can move +bool LLViewerObject::permMove() const +{ + if (isRootEdit()) + { +#ifdef HACKED_GODLIKE_VIEWER + return true; +#else +# ifdef TOGGLE_HACKED_GODLIKE_VIEWER + if (!LLGridManager::getInstance()->isInProductionGrid() + && (gAgent.getGodLevel() >= GOD_MAINTENANCE)) + { + return true; + } +# endif + return flagObjectMove(); +#endif + } + else + { + return ((LLViewerObject*)getParent())->permMove(); + } +} + +// Can be transferred +bool LLViewerObject::permTransfer() const +{ + if (isRootEdit()) + { +#ifdef HACKED_GODLIKE_VIEWER + return true; +#else +# ifdef TOGGLE_HACKED_GODLIKE_VIEWER + if (!LLGridManager::getInstance()->isInProductionGrid() + && (gAgent.getGodLevel() >= GOD_MAINTENANCE)) + { + return true; + } +# endif + return flagObjectTransfer(); +#endif + } + else + { + return ((LLViewerObject*)getParent())->permTransfer(); + } +} + +// Can only open objects that you own, or that someone has +// given you modify rights to. JC +bool LLViewerObject::allowOpen() const +{ + return !flagInventoryEmpty() && (permYouOwner() || permModify()); +} + +LLViewerObject::LLInventoryCallbackInfo::~LLInventoryCallbackInfo() +{ + if (mListener) + { + mListener->clearVOInventoryListener(); + } +} + +void LLViewerObject::updateVolume(const LLVolumeParams& volume_params) +{ + if (setVolume(volume_params, 1)) // *FIX: magic number, ack! + { + // Transmit the update to the simulator + sendShapeUpdate(); + markForUpdate(); + } +} + +void LLViewerObject::recursiveMarkForUpdate() +{ + for (LLViewerObject::child_list_t::iterator iter = mChildList.begin(); + iter != mChildList.end(); iter++) + { + LLViewerObject* child = *iter; + child->markForUpdate(); + } + markForUpdate(); +} + +void LLViewerObject::markForUpdate() +{ + if (mDrawable.notNull()) + { + gPipeline.markTextured(mDrawable); + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_GEOMETRY); + } +} + +bool LLViewerObject::isPermanentEnforced() const +{ + return flagObjectPermanent() && (mRegionp != gAgent.getRegion()) && !gAgent.isGodlike(); +} + +bool LLViewerObject::getIncludeInSearch() const +{ + return flagIncludeInSearch(); +} + +void LLViewerObject::setIncludeInSearch(bool include_in_search) +{ + setFlags(FLAGS_INCLUDE_IN_SEARCH, include_in_search); +} + +void LLViewerObject::setRegion(LLViewerRegion *regionp) +{ + if (!regionp) + { + LL_WARNS() << "viewer object set region to NULL" << LL_ENDL; + } + if(regionp != mRegionp) + { + if(mRegionp) + { + mRegionp->removeFromCreatedList(getLocalID()); + } + if(regionp) + { + regionp->addToCreatedList(getLocalID()); + } + } + + mLatestRecvPacketID = 0; + mRegionp = regionp; + + for (child_list_t::iterator i = mChildList.begin(); i != mChildList.end(); ++i) + { + LLViewerObject* child = *i; + child->setRegion(regionp); + } + + if (mControlAvatar) + { + mControlAvatar->setRegion(regionp); + } + + setChanged(MOVED | SILHOUETTE); + updateDrawable(false); +} + +// virtual +void LLViewerObject::updateRegion(LLViewerRegion *regionp) +{ +// if (regionp) +// { +// F64 now = LLFrameTimer::getElapsedSeconds(); +// LL_INFOS() << "Updating to region " << regionp->getName() +// << ", ms since last update message: " << (F32)((now - mLastMessageUpdateSecs) * 1000.0) +// << ", ms since last interpolation: " << (F32)((now - mLastInterpUpdateSecs) * 1000.0) +// << LL_ENDL; +// } +} + + +bool LLViewerObject::specialHoverCursor() const +{ + return flagUsePhysics() + || flagHandleTouch() + || (mClickAction != 0); +} + +void LLViewerObject::updateFlags(bool physics_changed) +{ + LLViewerRegion* regionp = getRegion(); + if(!regionp) return; + gMessageSystem->newMessage("ObjectFlagUpdate"); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + gMessageSystem->addU32Fast(_PREHASH_ObjectLocalID, getLocalID() ); + gMessageSystem->addBOOLFast(_PREHASH_UsePhysics, flagUsePhysics() ); + gMessageSystem->addBOOL("IsTemporary", flagTemporaryOnRez() ); + gMessageSystem->addBOOL("IsPhantom", flagPhantom() ); + + // stinson 02/28/2012 : This CastsShadows bool is no longer used in either the viewer or the simulator + // The simulator code does not even unpack this value when the message is received. + // This could be potentially hijacked in the future for another use should the urgent need arise. + gMessageSystem->addBOOL("CastsShadows", false ); + + if (physics_changed) + { + gMessageSystem->nextBlock("ExtraPhysics"); + gMessageSystem->addU8("PhysicsShapeType", getPhysicsShapeType() ); + gMessageSystem->addF32("Density", getPhysicsDensity() ); + gMessageSystem->addF32("Friction", getPhysicsFriction() ); + gMessageSystem->addF32("Restitution", getPhysicsRestitution() ); + gMessageSystem->addF32("GravityMultiplier", getPhysicsGravity() ); + } + gMessageSystem->sendReliable( regionp->getHost() ); +} + +bool LLViewerObject::setFlags(U32 flags, bool state) +{ + bool setit = setFlagsWithoutUpdate(flags, state); + + // BUG: Sometimes viewer physics and simulator physics get + // out of sync. To fix this, always send update to simulator. +// if (setit) + { + updateFlags(); + } + return setit; +} + +bool LLViewerObject::setFlagsWithoutUpdate(U32 flags, bool state) +{ + bool setit = false; + if (state) + { + if ((mFlags & flags) != flags) + { + mFlags |= flags; + setit = true; + } + } + else + { + if ((mFlags & flags) != 0) + { + mFlags &= ~flags; + setit = true; + } + } + return setit; +} + +void LLViewerObject::setPhysicsShapeType(U8 type) +{ + mPhysicsShapeUnknown = false; + if (type != mPhysicsShapeType) + { + mPhysicsShapeType = type; + setObjectCostStale(); +} +} + +void LLViewerObject::setPhysicsGravity(F32 gravity) +{ + mPhysicsGravity = gravity; +} + +void LLViewerObject::setPhysicsFriction(F32 friction) +{ + mPhysicsFriction = friction; +} + +void LLViewerObject::setPhysicsDensity(F32 density) +{ + mPhysicsDensity = density; +} + +void LLViewerObject::setPhysicsRestitution(F32 restitution) +{ + mPhysicsRestitution = restitution; +} + +U8 LLViewerObject::getPhysicsShapeType() const +{ + if (mPhysicsShapeUnknown) + { + gObjectList.updatePhysicsFlags(this); + } + + return mPhysicsShapeType; +} + +void LLViewerObject::applyAngularVelocity(F32 dt) +{ + //do target omega here + mRotTime += dt; + LLVector3 ang_vel = getAngularVelocity(); + F32 omega = ang_vel.magVecSquared(); + F32 angle = 0.0f; + LLQuaternion dQ; + if (omega > 0.00001f) + { + omega = sqrt(omega); + angle = omega * dt; + + ang_vel *= 1.f/omega; + + // calculate the delta increment based on the object's angular velocity + dQ.setQuat(angle, ang_vel); + + // accumulate the angular velocity rotations to re-apply in the case of an object update + mAngularVelocityRot *= dQ; + + // Just apply the delta increment to the current rotation + setRotation(getRotation()*dQ); + setChanged(MOVED | SILHOUETTE); + } +} + +void LLViewerObject::resetRotTime() +{ + mRotTime = 0.0f; +} + +void LLViewerObject::resetRot() +{ + resetRotTime(); + + // Reset the accumulated angular velocity rotation + mAngularVelocityRot.loadIdentity(); +} + +U32 LLViewerObject::getPartitionType() const +{ + return LLViewerRegion::PARTITION_NONE; +} + +void LLViewerObject::dirtySpatialGroup() const +{ + if (mDrawable) + { + LLSpatialGroup* group = mDrawable->getSpatialGroup(); + if (group) + { + group->dirtyGeom(); + gPipeline.markRebuild(group); + } + } +} + +void LLViewerObject::dirtyMesh() +{ + if (mDrawable) + { + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); + } +} + +F32 LLAlphaObject::getPartSize(S32 idx) +{ + return 0.f; +} + +void LLAlphaObject::getBlendFunc(S32 face, LLRender::eBlendFactor& src, LLRender::eBlendFactor& dst) +{ + +} + +// virtual +void LLStaticViewerObject::updateDrawable(bool force_damped) +{ + // Force an immediate rebuild on any update + if (mDrawable.notNull()) + { + mDrawable->updateXform(true); + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); + } + clearChanged(SHIFTED); +} + +void LLViewerObject::saveUnselectedChildrenPosition(std::vector& positions) +{ + if(mChildList.empty() || !positions.empty()) + { + return ; + } + + for (LLViewerObject::child_list_t::const_iterator iter = mChildList.begin(); + iter != mChildList.end(); iter++) + { + LLViewerObject* childp = *iter; + if (!childp->isSelected() && childp->mDrawable.notNull()) + { + positions.push_back(childp->getPositionEdit()); + } + } + + return ; +} + +void LLViewerObject::saveUnselectedChildrenRotation(std::vector& rotations) +{ + if(mChildList.empty()) + { + return ; + } + + for (LLViewerObject::child_list_t::const_iterator iter = mChildList.begin(); + iter != mChildList.end(); iter++) + { + LLViewerObject* childp = *iter; + if (!childp->isSelected() && childp->mDrawable.notNull()) + { + rotations.push_back(childp->getRotationEdit()); + } + } + + return ; +} + +//counter-rotation +void LLViewerObject::resetChildrenRotationAndPosition(const std::vector& rotations, + const std::vector& positions) +{ + if(mChildList.empty()) + { + return ; + } + + S32 index = 0 ; + LLQuaternion inv_rotation = ~getRotationEdit() ; + LLVector3 offset = getPositionEdit() ; + for (LLViewerObject::child_list_t::const_iterator iter = mChildList.begin(); + iter != mChildList.end(); iter++) + { + LLViewerObject* childp = *iter; + if (!childp->isSelected() && childp->mDrawable.notNull()) + { + if (childp->getPCode() != LL_PCODE_LEGACY_AVATAR) + { + childp->setRotation(rotations[index] * inv_rotation); + childp->setPosition((positions[index] - offset) * inv_rotation); + LLManip::rebuild(childp); + } + else //avatar + { + LLVector3 reset_pos = (positions[index] - offset) * inv_rotation ; + LLQuaternion reset_rot = rotations[index] * inv_rotation ; + + ((LLVOAvatar*)childp)->mDrawable->mXform.setPosition(reset_pos); + ((LLVOAvatar*)childp)->mDrawable->mXform.setRotation(reset_rot) ; + + ((LLVOAvatar*)childp)->mDrawable->getVObj()->setPosition(reset_pos, true); + ((LLVOAvatar*)childp)->mDrawable->getVObj()->setRotation(reset_rot, true) ; + + LLManip::rebuild(childp); + } + index++; + } + } + + return ; +} + +//counter-translation +void LLViewerObject::resetChildrenPosition(const LLVector3& offset, bool simplified, bool skip_avatar_child) +{ + if(mChildList.empty()) + { + return ; + } + + LLVector3 child_offset; + if(simplified) //translation only, rotation matrix does not change + { + child_offset = offset * ~getRotation(); + } + else //rotation matrix might change too. + { + if (isAttachment() && mDrawable.notNull()) + { + LLXform* attachment_point_xform = mDrawable->getXform()->getParent(); + LLQuaternion parent_rotation = getRotation() * attachment_point_xform->getWorldRotation(); + child_offset = offset * ~parent_rotation; + } + else + { + child_offset = offset * ~getRenderRotation(); + } + } + + for (LLViewerObject::child_list_t::const_iterator iter = mChildList.begin(); + iter != mChildList.end(); iter++) + { + LLViewerObject* childp = *iter; + + if (!childp->isSelected() && childp->mDrawable.notNull()) + { + if (childp->getPCode() != LL_PCODE_LEGACY_AVATAR) + { + childp->setPosition(childp->getPosition() + child_offset); + LLManip::rebuild(childp); + } + else //avatar + { + if(!skip_avatar_child) + { + LLVector3 reset_pos = ((LLVOAvatar*)childp)->mDrawable->mXform.getPosition() + child_offset ; + + ((LLVOAvatar*)childp)->mDrawable->mXform.setPosition(reset_pos); + ((LLVOAvatar*)childp)->mDrawable->getVObj()->setPosition(reset_pos); + LLManip::rebuild(childp); + } + } + } + } + + return ; +} + +// virtual +bool LLViewerObject::isTempAttachment() const +{ + return (mID.notNull() && (mID == mAttachmentItemID)); +} + +bool LLViewerObject::isHiglightedOrBeacon() const +{ + if (LLFloaterReg::instanceVisible("beacons") && (gPipeline.getRenderBeacons() || gPipeline.getRenderHighlights())) + { + bool has_media = (getMediaType() == LLViewerObject::MEDIA_SET); + bool is_scripted = !isAvatar() && !getParent() && flagScripted(); + bool is_physical = !isAvatar() && flagUsePhysics(); + + return (isParticleSource() && gPipeline.getRenderParticleBeacons()) + || (isAudioSource() && gPipeline.getRenderSoundBeacons()) + || (has_media && gPipeline.getRenderMOAPBeacons()) + || (is_scripted && gPipeline.getRenderScriptedBeacons()) + || (is_scripted && flagHandleTouch() && gPipeline.getRenderScriptedTouchBeacons()) + || (is_physical && gPipeline.getRenderPhysicalBeacons()); + } + return false; +} + + +const LLUUID &LLViewerObject::getAttachmentItemID() const +{ + return mAttachmentItemID; +} + +void LLViewerObject::setAttachmentItemID(const LLUUID &id) +{ + mAttachmentItemID = id; +} + +EObjectUpdateType LLViewerObject::getLastUpdateType() const +{ + return mLastUpdateType; +} + +void LLViewerObject::setLastUpdateType(EObjectUpdateType last_update_type) +{ + mLastUpdateType = last_update_type; +} + +bool LLViewerObject::getLastUpdateCached() const +{ + return mLastUpdateCached; +} + +void LLViewerObject::setLastUpdateCached(bool last_update_cached) +{ + mLastUpdateCached = last_update_cached; +} + +const LLUUID &LLViewerObject::extractAttachmentItemID() +{ + LLUUID item_id = LLUUID::null; + LLNameValue* item_id_nv = getNVPair("AttachItemID"); + if( item_id_nv ) + { + const char* s = item_id_nv->getString(); + if( s ) + { + item_id.set(s); + } + } + setAttachmentItemID(item_id); + return getAttachmentItemID(); +} + +const std::string& LLViewerObject::getAttachmentItemName() const +{ + static std::string empty; + LLInventoryItem *item = gInventory.getItem(getAttachmentItemID()); + if (isAttachment() && item) + { + return item->getName(); + } + return empty; +} + +//virtual +LLVOAvatar* LLViewerObject::getAvatar() const +{ + if (getControlAvatar()) + { + return getControlAvatar(); + } + if (isAttachment()) + { + LLViewerObject* vobj = (LLViewerObject*) getParent(); + + while (vobj && !vobj->asAvatar()) + { + vobj = (LLViewerObject*) vobj->getParent(); + } + + return (LLVOAvatar*) vobj; + } + + return NULL; +} + +bool LLViewerObject::hasRenderMaterialParams() const +{ + return getParameterEntryInUse(LLNetworkData::PARAMS_RENDER_MATERIAL); +} + +void LLViewerObject::setHasRenderMaterialParams(bool has_materials) +{ + bool had_materials = hasRenderMaterialParams(); + + if (had_materials != has_materials) + { + if (has_materials) + { + setParameterEntryInUse(LLNetworkData::PARAMS_RENDER_MATERIAL, true, true); + } + else + { + setParameterEntryInUse(LLNetworkData::PARAMS_RENDER_MATERIAL, false, true); + } + } +} + +const LLUUID& LLViewerObject::getRenderMaterialID(U8 te) const +{ + LLRenderMaterialParams* param_block = (LLRenderMaterialParams*)getParameterEntry(LLNetworkData::PARAMS_RENDER_MATERIAL); + if (param_block) + { + return param_block->getMaterial(te); + } + + return LLUUID::null; +} + +void LLViewerObject::rebuildMaterial() +{ + llassert(!isDead()); + + faceMappingChanged(); + gPipeline.markTextured(mDrawable); +} + +void LLViewerObject::setRenderMaterialID(S32 te_in, const LLUUID& id, bool update_server, bool local_origin) +{ + // implementation is delicate + + // if update is bound for server, should always null out GLTFRenderMaterial and clear GLTFMaterialOverride even if ids haven't changed + // (the case where ids haven't changed indicates the user has reapplied the original material, in which case overrides should be dropped) + // otherwise, should only null out the render material where ids or overrides have changed + // (the case where ids have changed but overrides are still present is from unsynchronized updates from the simulator, or synchronized + // updates with solely transform overrides) + + llassert(!update_server || local_origin); + + S32 start_idx = 0; + S32 end_idx = getNumTEs(); + + if (te_in != -1) + { + start_idx = te_in; + end_idx = start_idx + 1; + } + + start_idx = llmax(start_idx, 0); + end_idx = llmin(end_idx, (S32) getNumTEs()); + + LLRenderMaterialParams* param_block = (LLRenderMaterialParams*)getParameterEntry(LLNetworkData::PARAMS_RENDER_MATERIAL); + if (!param_block && id.notNull()) + { // block doesn't exist, but it will need to + param_block = (LLRenderMaterialParams*)createNewParameterEntry(LLNetworkData::PARAMS_RENDER_MATERIAL)->data; + } + + + LLFetchedGLTFMaterial* new_material = nullptr; + if (id.notNull()) + { + new_material = gGLTFMaterialList.getMaterial(id); + } + + // update local state + for (S32 te = start_idx; te < end_idx; ++te) + { + LLTextureEntry* tep = getTE(te); + + // If local_origin=false (i.e. it's from the server), we know the + // material has updated or been created, because extra params are + // checked for equality on unpacking. In that case, checking the + // material ID for inequality won't work, because the material ID has + // already been set. + bool material_changed = !local_origin || !param_block || id != param_block->getMaterial(te); + + if (update_server) + { + // Clear most overrides so the render material better matches the material + // ID (preserve transforms). If overrides become passthrough, set the overrides + // to nullptr. + if (tep->setBaseMaterial()) + { + material_changed = true; + } + } + + if (update_server || material_changed) + { + tep->setGLTFRenderMaterial(nullptr); + } + + if (new_material != tep->getGLTFMaterial()) + { + tep->setGLTFMaterial(new_material, !update_server); + } + + if (material_changed && new_material) + { + // Sometimes, the material may change out from underneath the overrides. + // This is usually due to the server sending a new material ID, but + // the overrides have not changed due to being only texture + // transforms. Re-apply the overrides to the render material here, + // if present. + const LLGLTFMaterial* override_material = tep->getGLTFMaterialOverride(); + if (override_material) + { + new_material->onMaterialComplete([obj_id = getID(), te]() + { + LLViewerObject* obj = gObjectList.findObject(obj_id); + if (!obj) { return; } + LLTextureEntry* tep = obj->getTE(te); + if (!tep) { return; } + const LLGLTFMaterial* new_material = tep->getGLTFMaterial(); + if (!new_material) { return; } + const LLGLTFMaterial* override_material = tep->getGLTFMaterialOverride(); + if (!override_material) { return; } + LLGLTFMaterial* render_material = new LLFetchedGLTFMaterial(); + *render_material = *new_material; + render_material->applyOverride(*override_material); + tep->setGLTFRenderMaterial(render_material); + }); + } + } + } + + // signal to render pipe that render batches must be rebuilt for this object + if (!new_material) + { + rebuildMaterial(); + } + else + { + new_material->onMaterialComplete([obj_id = getID()]() + { + LLViewerObject* obj = gObjectList.findObject(obj_id); + if (obj) + { + obj->rebuildMaterial(); + } + }); + } + + // predictively update LLRenderMaterialParams (don't wait for server) + if (param_block) + { // update existing parameter block + for (S32 te = start_idx; te < end_idx; ++te) + { + param_block->setMaterial(te, id); + } + } + + if (update_server) + { + // update via ModifyMaterialParams cap (server will echo back changes) + for (S32 te = start_idx; te < end_idx; ++te) + { + // This sends a cleared version of this object's current material + // override, but the override should already be cleared due to + // calling setBaseMaterial above. + LLGLTFMaterialList::queueApply(this, te, id); + } + } + + if (!update_server) + { + // Land impact may have changed + setObjectCostStale(); + } +} + +void LLViewerObject::setRenderMaterialIDs(const LLUUID& id) +{ + setRenderMaterialID(-1, id); +} + +void LLViewerObject::setRenderMaterialIDs(const LLRenderMaterialParams* material_params, bool local_origin) +{ + if (!local_origin) + { + for (S32 te = 0; te < getNumTEs(); ++te) + { + const LLUUID& id = material_params ? material_params->getMaterial(te) : LLUUID::null; + // We know material_params has updated or been created, because + // extra params are checked for equality on unpacking. + setRenderMaterialID(te, id, false, false); + } + } +} + +void LLViewerObject::shrinkWrap() +{ + if (!mShouldShrinkWrap) + { + mShouldShrinkWrap = true; + if (mDrawable) + { // we weren't shrink wrapped before but we are now, update the spatial partition + gPipeline.markPartitionMove(mDrawable); + } + } +} + +class ObjectPhysicsProperties : public LLHTTPNode +{ +public: + virtual void post( + ResponsePtr responder, + const LLSD& context, + const LLSD& input) const + { + LLSD object_data = input["body"]["ObjectData"]; + S32 num_entries = object_data.size(); + + for ( S32 i = 0; i < num_entries; i++ ) + { + LLSD& curr_object_data = object_data[i]; + U32 local_id = curr_object_data["LocalID"].asInteger(); + + // Iterate through nodes at end, since it can be on both the regular AND hover list + struct f : public LLSelectedNodeFunctor + { + U32 mID; + f(const U32& id) : mID(id) {} + virtual bool apply(LLSelectNode* node) + { + return (node->getObject() && node->getObject()->mLocalID == mID ); + } + } func(local_id); + + LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode(&func); + + if (node) + { + // The LLSD message builder doesn't know how to handle U8, so we need to send as S8 and cast + U8 type = (U8)curr_object_data["PhysicsShapeType"].asInteger(); + F32 density = (F32)curr_object_data["Density"].asReal(); + F32 friction = (F32)curr_object_data["Friction"].asReal(); + F32 restitution = (F32)curr_object_data["Restitution"].asReal(); + F32 gravity = (F32)curr_object_data["GravityMultiplier"].asReal(); + + node->getObject()->setPhysicsShapeType(type); + node->getObject()->setPhysicsGravity(gravity); + node->getObject()->setPhysicsFriction(friction); + node->getObject()->setPhysicsDensity(density); + node->getObject()->setPhysicsRestitution(restitution); + } + } + + dialog_refresh_all(); + }; +}; + +LLHTTPRegistration + gHTTPRegistrationObjectPhysicsProperties("/message/ObjectPhysicsProperties"); + diff --git a/indra/newview/llviewerobject.h b/indra/newview/llviewerobject.h index ce16864679..cc852b4ce2 100644 --- a/indra/newview/llviewerobject.h +++ b/indra/newview/llviewerobject.h @@ -1,1014 +1,1014 @@ -/** - * @file llviewerobject.h - * @brief Description of LLViewerObject class, which is the base class for most objects in the viewer. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVIEWEROBJECT_H -#define LL_LLVIEWEROBJECT_H - -#include -#include - -#include "llassetstorage.h" -//#include "llhudicon.h" -#include "llinventory.h" -#include "llrefcount.h" -#include "llprimitive.h" -#include "lluuid.h" -#include "llvoinventorylistener.h" -#include "object_flags.h" -#include "llquaternion.h" -#include "v3dmath.h" -#include "v3math.h" -#include "llvertexbuffer.h" -#include "llbbox.h" -#include "llrigginginfo.h" -#include "llreflectionmap.h" - -class LLAgent; // TODO: Get rid of this. -class LLAudioSource; -class LLAudioSourceVO; -class LLColor4; -class LLControlAvatar; -class LLDataPacker; -class LLDataPackerBinaryBuffer; -class LLDrawable; -class LLHUDText; -class LLHost; -class LLMessageSystem; -class LLNameValue; -class LLPartSysData; -class LLPipeline; -class LLTextureEntry; -class LLVOAvatar; -class LLVOInventoryListener; -class LLViewerInventoryItem; -class LLViewerObject; -class LLViewerObjectMedia; -class LLViewerPartSourceScript; -class LLViewerRegion; -class LLViewerTexture; -class LLWorld; - -class LLMeshCostData; - -typedef enum e_object_update_type -{ - OUT_FULL, - OUT_TERSE_IMPROVED, - OUT_FULL_COMPRESSED, - OUT_FULL_CACHED, - OUT_UNKNOWN, -} EObjectUpdateType; - - -// callback typedef for inventory -typedef void (*inventory_callback)(LLViewerObject*, - LLInventoryObject::object_list_t*, - S32 serial_num, - void*); - -// for exporting textured materials from SL -struct LLMaterialExportInfo -{ -public: - LLMaterialExportInfo(S32 mat_index, S32 texture_index, LLColor4 color) : - mMaterialIndex(mat_index), mTextureIndex(texture_index), mColor(color) {}; - - S32 mMaterialIndex; - S32 mTextureIndex; - LLColor4 mColor; -}; - -struct PotentialReturnableObject -{ - LLBBox box; - LLViewerRegion* pRegion; -}; - -//============================================================================ - -class LLViewerObject -: public LLPrimitive, - public LLRefCount, - public LLGLUpdate -{ -protected: - virtual ~LLViewerObject(); // use unref() - - // TomY: Provide for a list of extra parameter structures, mapped by structure name - struct ExtraParameter - { - bool in_use; - LLNetworkData *data; - }; - std::unordered_map mExtraParameterList; - -public: - typedef std::list > child_list_t; - typedef std::list > vobj_list_t; - - typedef const child_list_t const_child_list_t; - - LLViewerObject(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp, bool is_global = false); - - virtual void markDead(); // Mark this object as dead, and clean up its references - bool isDead() const {return mDead;} - bool isOrphaned() const { return mOrphaned; } - bool isParticleSource() const; - - virtual LLVOAvatar* asAvatar(); - - LLVOAvatar* getAvatarAncestor(); - - static void initVOClasses(); - static void cleanupVOClasses(); - - void addNVPair(const std::string& data); - bool removeNVPair(const std::string& name); - LLNameValue* getNVPair(const std::string& name) const; // null if no name value pair by that name - - // Object create and update functions - virtual void idleUpdate(LLAgent &agent, const F64 &time); - - // Types of media we can associate - enum { MEDIA_NONE = 0, MEDIA_SET = 1 }; - - // Return codes for processUpdateMessage - enum { - MEDIA_URL_REMOVED = 0x1, - MEDIA_URL_ADDED = 0x2, - MEDIA_URL_UPDATED = 0x4, - MEDIA_FLAGS_CHANGED = 0x8, - INVALID_UPDATE = 0x80000000 - }; - - static U32 extractSpatialExtents(LLDataPackerBinaryBuffer *dp, LLVector3& pos, LLVector3& scale, LLQuaternion& rot); - virtual U32 processUpdateMessage(LLMessageSystem *mesgsys, - void **user_data, - U32 block_num, - const EObjectUpdateType update_type, - LLDataPacker *dp); - - - virtual bool isActive() const; // Whether this object needs to do an idleUpdate. - bool onActiveList() const {return mOnActiveList;} - void setOnActiveList(bool on_active) { mOnActiveList = on_active; } - - virtual bool isAttachment() const { return false; } - const std::string& getAttachmentItemName() const; - - virtual LLVOAvatar* getAvatar() const; //get the avatar this object is attached to, or NULL if object is not an attachment - - bool hasRenderMaterialParams() const; - void setHasRenderMaterialParams(bool has_params); - - const LLUUID& getRenderMaterialID(U8 te) const; - - // set the RenderMaterialID for the given TextureEntry - // te - TextureEntry index to set, or -1 for all TEs - // id - asset id of material asset - // update_server - if true, will send updates to server and clear most overrides - void setRenderMaterialID(S32 te, const LLUUID& id, bool update_server = true, bool local_origin = true); - void setRenderMaterialIDs(const LLUUID& id); - - virtual bool isHUDAttachment() const { return false; } - virtual bool isTempAttachment() const; - - virtual bool isHiglightedOrBeacon() const; - - virtual void updateRadius() {}; - virtual F32 getVObjRadius() const; // default implemenation is mDrawable->getRadius() - - // for jointed and other parent-relative hacks - LLViewerObject* getSubParent(); - const LLViewerObject* getSubParent() const; - - // Object visiblility and GPW functions - virtual void setPixelAreaAndAngle(LLAgent &agent); // Override to generate accurate apparent angle and area - - virtual U32 getNumVertices() const; - virtual U32 getNumIndices() const; - S32 getNumFaces() const { return mNumFaces; } - - // Graphical stuff for objects - maybe broken out into render class later? - virtual void updateTextures(); - virtual void faceMappingChanged() {} - virtual void boostTexturePriority(bool boost_children = true); // When you just want to boost priority of this object - - virtual LLDrawable* createDrawable(LLPipeline *pipeline); - virtual bool updateGeometry(LLDrawable *drawable); - virtual void updateGL(); - virtual void updateFaceSize(S32 idx); - virtual bool updateLOD(); - virtual bool setDrawableParent(LLDrawable* parentp); - F32 getRotTime() { return mRotTime; } -private: - void resetRotTime(); - void setRenderMaterialIDs(const LLRenderMaterialParams* material_params, bool local_origin); - void rebuildMaterial(); -public: - void resetRot(); - void applyAngularVelocity(F32 dt); - - void setLineWidthForWindowSize(S32 window_width); - - static void increaseArrowLength(); // makes axis arrows for selections longer - static void decreaseArrowLength(); // makes axis arrows for selections shorter - - // Accessor functions - LLViewerRegion* getRegion() const { return mRegionp; } - - bool isSelected() const { return mUserSelected; } - // Check whole linkset - bool isAnySelected() const; - virtual void setSelected(bool sel); - - const LLUUID &getID() const { return mID; } - U32 getLocalID() const { return mLocalID; } - U32 getCRC() const { return mTotalCRC; } - S32 getListIndex() const { return mListIndex; } - void setListIndex(S32 idx) { mListIndex = idx; } - - virtual bool isFlexible() const { return false; } - virtual bool isSculpted() const { return false; } - virtual bool isMesh() const { return false; } - virtual bool isRiggedMesh() const { return false; } - virtual bool hasLightTexture() const { return false; } - virtual bool isReflectionProbe() const { return false; } - - // This method returns true if the object is over land owned by - // the agent, one of its groups, or it encroaches and - // anti-encroachment is enabled - bool isReturnable(); - - void buildReturnablesForChildrenVO( std::vector& returnables, LLViewerObject* pChild, LLViewerRegion* pTargetRegion ); - void constructAndAddReturnable( std::vector& returnables, LLViewerObject* pChild, LLViewerRegion* pTargetRegion ); - - // This method returns true if the object crosses - // any parcel bounds in the region. - bool crossesParcelBounds(); - - /* - // This method will scan through this object, and then query the - // selection manager to see if the local agent probably has the - // ability to modify the object. Since this calls into the - // selection manager, you should avoid calling this method from - // there. - bool isProbablyModifiable() const; - */ - - virtual bool setParent(LLViewerObject* parent); - virtual void onReparent(LLViewerObject *old_parent, LLViewerObject *new_parent); - virtual void afterReparent(); - virtual void addChild(LLViewerObject *childp); - virtual void removeChild(LLViewerObject *childp); - const_child_list_t& getChildren() const { return mChildList; } - S32 numChildren() const { return mChildList.size(); } - void addThisAndAllChildren(std::vector& objects); - void addThisAndNonJointChildren(std::vector& objects); - bool isChild(LLViewerObject *childp) const; - bool isSeat() const; - - - //detect if given line segment (in agent space) intersects with this viewer object. - //returns true if intersection detected and returns information about intersection - virtual bool lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, - S32 face = -1, // which face to check, -1 = ALL_SIDES - bool pick_transparent = false, - bool pick_rigged = false, - bool pick_unselectable = true, - S32* face_hit = NULL, // which face was hit - LLVector4a* intersection = NULL, // return the intersection point - LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point - LLVector4a* normal = NULL, // return the surface normal at the intersection point - LLVector4a* tangent = NULL // return the surface tangent at the intersection point - ); - - virtual bool lineSegmentBoundingBox(const LLVector4a& start, const LLVector4a& end); - - virtual const LLVector3d getPositionGlobal() const; - virtual const LLVector3 &getPositionRegion() const; - virtual const LLVector3 getPositionEdit() const; - virtual const LLVector3 &getPositionAgent() const; - virtual const LLVector3 getRenderPosition() const; - - virtual const LLVector3 getPivotPositionAgent() const; // Usually = to getPositionAgent, unless like flex objects it's not - - LLViewerObject* getRootEdit() const; - - const LLQuaternion getRotationRegion() const; - const LLQuaternion getRotationEdit() const; - const LLQuaternion getRenderRotation() const; - virtual const LLMatrix4 getRenderMatrix() const; - - void setPosition(const LLVector3 &pos, bool damped = false); - void setPositionGlobal(const LLVector3d &position, bool damped = false); - void setPositionRegion(const LLVector3 &position, bool damped = false); - void setPositionEdit(const LLVector3 &position, bool damped = false); - void setPositionAgent(const LLVector3 &pos_agent, bool damped = false); - void setPositionParent(const LLVector3 &pos_parent, bool damped = false); - void setPositionAbsoluteGlobal( const LLVector3d &pos_global, bool damped = false ); - - virtual const LLMatrix4& getWorldMatrix(LLXformMatrix* xform) const { return xform->getWorldMatrix(); } - - inline void setRotation(const F32 x, const F32 y, const F32 z, bool damped = false); - inline void setRotation(const LLQuaternion& quat, bool damped = false); - - /*virtual*/ void setNumTEs(const U8 num_tes); - /*virtual*/ void setTE(const U8 te, const LLTextureEntry &texture_entry); - void updateTEMaterialTextures(U8 te); - /*virtual*/ S32 setTETexture(const U8 te, const LLUUID &uuid); - /*virtual*/ S32 setTENormalMap(const U8 te, const LLUUID &uuid); - /*virtual*/ S32 setTESpecularMap(const U8 te, const LLUUID &uuid); - S32 setTETextureCore(const U8 te, LLViewerTexture *image); - S32 setTENormalMapCore(const U8 te, LLViewerTexture *image); - S32 setTESpecularMapCore(const U8 te, LLViewerTexture *image); - /*virtual*/ S32 setTEColor(const U8 te, const LLColor3 &color); - /*virtual*/ S32 setTEColor(const U8 te, const LLColor4 &color); - /*virtual*/ S32 setTEScale(const U8 te, const F32 s, const F32 t); - /*virtual*/ S32 setTEScaleS(const U8 te, const F32 s); - /*virtual*/ S32 setTEScaleT(const U8 te, const F32 t); - /*virtual*/ S32 setTEOffset(const U8 te, const F32 s, const F32 t); - /*virtual*/ S32 setTEOffsetS(const U8 te, const F32 s); - /*virtual*/ S32 setTEOffsetT(const U8 te, const F32 t); - /*virtual*/ S32 setTERotation(const U8 te, const F32 r); - /*virtual*/ S32 setTEBumpmap(const U8 te, const U8 bump ); - /*virtual*/ S32 setTETexGen(const U8 te, const U8 texgen ); - /*virtual*/ S32 setTEMediaTexGen(const U8 te, const U8 media ); // *FIXME: this confusingly acts upon a superset of setTETexGen's flags without absorbing its semantics - /*virtual*/ S32 setTEShiny(const U8 te, const U8 shiny ); - /*virtual*/ S32 setTEFullbright(const U8 te, const U8 fullbright ); - /*virtual*/ S32 setTEMediaFlags(const U8 te, const U8 media_flags ); - /*virtual*/ S32 setTEGlow(const U8 te, const F32 glow); - /*virtual*/ S32 setTEMaterialID(const U8 te, const LLMaterialID& pMaterialID); - /*virtual*/ S32 setTEMaterialParams(const U8 te, const LLMaterialPtr pMaterialParams); - virtual S32 setTEGLTFMaterialOverride(U8 te, LLGLTFMaterial* mat); - - // Used by Materials update functions to properly kick off rebuilds - // of VBs etc when materials updates require changes. - // - void refreshMaterials(); - - /*virtual*/ bool setMaterial(const U8 material); - virtual void setTEImage(const U8 te, LLViewerTexture *imagep); // Not derived from LLPrimitive - virtual void changeTEImage(S32 index, LLViewerTexture* new_image) ; - virtual void changeTENormalMap(S32 index, LLViewerTexture* new_image) ; - virtual void changeTESpecularMap(S32 index, LLViewerTexture* new_image) ; - LLViewerTexture *getTEImage(const U8 te) const; - LLViewerTexture *getTENormalMap(const U8 te) const; - LLViewerTexture *getTESpecularMap(const U8 te) const; - - bool isImageAlphaBlended(const U8 te) const; - - void fitFaceTexture(const U8 face); - void sendTEUpdate() const; // Sends packed representation of all texture entry information - - virtual void setScale(const LLVector3 &scale, bool damped = false); - - S32 getAnimatedObjectMaxTris() const; - F32 recursiveGetEstTrianglesMax() const; - virtual F32 getEstTrianglesMax() const; - virtual F32 getEstTrianglesStreamingCost() const; - virtual F32 getStreamingCost() const; - virtual bool getCostData(LLMeshCostData& costs) const; - virtual U32 getTriangleCount(S32* vcount = NULL) const; - virtual U32 getHighLODTriangleCount(); - F32 recursiveGetScaledSurfaceArea() const; - - U32 recursiveGetTriangleCount(S32* vcount = NULL) const; - - void setObjectCost(F32 cost); - F32 getObjectCost(); - - void setLinksetCost(F32 cost); - F32 getLinksetCost(); - - void setPhysicsCost(F32 cost); - F32 getPhysicsCost(); - - void setLinksetPhysicsCost(F32 cost); - F32 getLinksetPhysicsCost(); - - void sendShapeUpdate(); - - U8 getAttachmentState() { return mAttachmentState; } - - F32 getAppAngle() const { return mAppAngle; } - F32 getPixelArea() const { return mPixelArea; } - void setPixelArea(F32 area) { mPixelArea = area; } - F32 getMaxScale() const; - F32 getMidScale() const; - F32 getMinScale() const; - - // Owner id is this object's owner - void setAttachedSound(const LLUUID &audio_uuid, const LLUUID& owner_id, const F32 gain, const U8 flags); - void adjustAudioGain(const F32 gain); - F32 getSoundCutOffRadius() const { return mSoundCutOffRadius; } - void clearAttachedSound() { mAudioSourcep = NULL; } - - // Create if necessary - LLAudioSource *getAudioSource(const LLUUID& owner_id); - bool isAudioSource() const {return mAudioSourcep != NULL;} - - U8 getMediaType() const; - void setMediaType(U8 media_type); - - std::string getMediaURL() const; - void setMediaURL(const std::string& media_url); - - bool getMediaPassedWhitelist() const; - void setMediaPassedWhitelist(bool passed); - - void sendMaterialUpdate() const; - - void setDebugText(const std::string &utf8text, const LLColor4& color = LLColor4::white); - void appendDebugText(const std::string &utf8text); - void initHudText(); - void restoreHudText(); - void setIcon(LLViewerTexture* icon_image); - void clearIcon(); - - void recursiveMarkForUpdate(); - virtual void markForUpdate(); - void updateVolume(const LLVolumeParams& volume_params); - virtual void updateSpatialExtents(LLVector4a& min, LLVector4a& max); - virtual F32 getBinRadius(); - - LLBBox getBoundingBoxAgent() const; - - void updatePositionCaches() const; // Update the global and region position caches from the object (and parent's) xform. - void updateText(); // update text label position - virtual void updateDrawable(bool force_damped); // force updates on static objects - - bool isOwnerInMuteList(LLUUID item_id = LLUUID()); - - void setDrawableState(U32 state, bool recursive = true); - void clearDrawableState(U32 state, bool recursive = true); - bool isDrawableState(U32 state, bool recursive = true) const; - - // Called when the drawable shifts - virtual void onShift(const LLVector4a &shift_vector) { } - - ////////////////////////////////////// - // - // Inventory methods - // - - // This function is called when someone is interested in a viewer - // object's inventory. The callback is called as soon as the - // viewer object has the inventory stored locally. - void registerInventoryListener(LLVOInventoryListener* listener, void* user_data); - void removeInventoryListener(LLVOInventoryListener* listener); - bool isInventoryPending(); - void clearInventoryListeners(); - bool hasInventoryListeners(); - void requestInventory(); - static void processTaskInv(LLMessageSystem* msg, void** user_data); - void removeInventory(const LLUUID& item_id); - - // The updateInventory() call potentially calls into the selection - // manager, so do no call updateInventory() from the selection - // manager until we have better iterators. - void updateInventory(LLViewerInventoryItem* item, U8 key, bool is_new); - void updateInventoryLocal(LLInventoryItem* item, U8 key); // Update without messaging. - void updateMaterialInventory(LLViewerInventoryItem* item, U8 key, bool is_new); - LLInventoryObject* getInventoryObject(const LLUUID& item_id); - LLInventoryItem* getInventoryItem(const LLUUID& item_id); - - // Get content except for root category - void getInventoryContents(LLInventoryObject::object_list_t& objects); - LLInventoryObject* getInventoryRoot(); - LLViewerInventoryItem* getInventoryItemByAsset(const LLUUID& asset_id); - LLViewerInventoryItem* getInventoryItemByAsset(const LLUUID& asset_id, LLAssetType::EType type); - S16 getInventorySerial() const { return mInventorySerialNum; } - - // These functions does viewer-side only object inventory modifications - void updateViewerInventoryAsset( - const LLViewerInventoryItem* item, - const LLUUID& new_asset); - - // This function will make sure that we refresh the inventory. - void dirtyInventory(); - bool isInventoryDirty() { return mInventoryDirty; } - - // save a script, which involves removing the old one, and rezzing - // in the new one. This method should be called with the asset id - // of the new and old script AFTER the bytecode has been saved. - void saveScript(const LLViewerInventoryItem* item, bool active, bool is_new); - - // move an inventory item out of the task and into agent - // inventory. This operation is based on messaging. No permissions - // checks are made on the viewer - the server will double check. - void moveInventory(const LLUUID& agent_folder, const LLUUID& item_id); - - // Find the number of instances of this object's inventory that are of the given type - S32 countInventoryContents( LLAssetType::EType type ); - - bool permAnyOwner() const; - bool permYouOwner() const; - bool permGroupOwner() const; - bool permOwnerModify() const; - bool permModify() const; - bool permCopy() const; - bool permMove() const; - bool permTransfer() const; - inline bool flagUsePhysics() const { return ((mFlags & FLAGS_USE_PHYSICS) != 0); } - inline bool flagObjectAnyOwner() const { return ((mFlags & FLAGS_OBJECT_ANY_OWNER) != 0); } - inline bool flagObjectYouOwner() const { return ((mFlags & FLAGS_OBJECT_YOU_OWNER) != 0); } - inline bool flagObjectGroupOwned() const { return ((mFlags & FLAGS_OBJECT_GROUP_OWNED) != 0); } - inline bool flagObjectOwnerModify() const { return ((mFlags & FLAGS_OBJECT_OWNER_MODIFY) != 0); } - inline bool flagObjectModify() const { return ((mFlags & FLAGS_OBJECT_MODIFY) != 0); } - inline bool flagObjectCopy() const { return ((mFlags & FLAGS_OBJECT_COPY) != 0); } - inline bool flagObjectMove() const { return ((mFlags & FLAGS_OBJECT_MOVE) != 0); } - inline bool flagObjectTransfer() const { return ((mFlags & FLAGS_OBJECT_TRANSFER) != 0); } - inline bool flagObjectPermanent() const { return ((mFlags & FLAGS_AFFECTS_NAVMESH) != 0); } - inline bool flagCharacter() const { return ((mFlags & FLAGS_CHARACTER) != 0); } - inline bool flagVolumeDetect() const { return ((mFlags & FLAGS_VOLUME_DETECT) != 0); } - inline bool flagIncludeInSearch() const { return ((mFlags & FLAGS_INCLUDE_IN_SEARCH) != 0); } - inline bool flagScripted() const { return ((mFlags & FLAGS_SCRIPTED) != 0); } - inline bool flagHandleTouch() const { return ((mFlags & FLAGS_HANDLE_TOUCH) != 0); } - inline bool flagTakesMoney() const { return ((mFlags & FLAGS_TAKES_MONEY) != 0); } - inline bool flagPhantom() const { return ((mFlags & FLAGS_PHANTOM) != 0); } - inline bool flagInventoryEmpty() const { return ((mFlags & FLAGS_INVENTORY_EMPTY) != 0); } - inline bool flagAllowInventoryAdd() const { return ((mFlags & FLAGS_ALLOW_INVENTORY_DROP) != 0); } - inline bool flagTemporaryOnRez() const { return ((mFlags & FLAGS_TEMPORARY_ON_REZ) != 0); } - inline bool flagAnimSource() const { return ((mFlags & FLAGS_ANIM_SOURCE) != 0); } - inline bool flagCameraSource() const { return ((mFlags & FLAGS_CAMERA_SOURCE) != 0); } - inline bool flagCameraDecoupled() const { return ((mFlags & FLAGS_CAMERA_DECOUPLED) != 0); } - - U8 getPhysicsShapeType() const; - inline F32 getPhysicsGravity() const { return mPhysicsGravity; } - inline F32 getPhysicsFriction() const { return mPhysicsFriction; } - inline F32 getPhysicsDensity() const { return mPhysicsDensity; } - inline F32 getPhysicsRestitution() const { return mPhysicsRestitution; } - - bool isPermanentEnforced() const; - - bool getIncludeInSearch() const; - void setIncludeInSearch(bool include_in_search); - - // Does "open" object menu item apply? - bool allowOpen() const; - - void setClickAction(U8 action) { mClickAction = action; } - U8 getClickAction() const { return mClickAction; } - bool specialHoverCursor() const; // does it have a special hover cursor? - - void setRegion(LLViewerRegion *regionp); - virtual void updateRegion(LLViewerRegion *regionp); - - void updateFlags(bool physics_changed = false); - void loadFlags(U32 flags); //load flags from cache or from message - bool setFlags(U32 flag, bool state); - bool setFlagsWithoutUpdate(U32 flag, bool state); - void setPhysicsShapeType(U8 type); - void setPhysicsGravity(F32 gravity); - void setPhysicsFriction(F32 friction); - void setPhysicsDensity(F32 density); - void setPhysicsRestitution(F32 restitution); - - virtual void dump() const; - static U32 getNumZombieObjects() { return sNumZombieObjects; } - - void printNameValuePairs() const; - - virtual S32 getLOD() const { return 3; } - virtual U32 getPartitionType() const; - void dirtySpatialGroup() const; - virtual void dirtyMesh(); - - virtual LLNetworkData* getParameterEntry(U16 param_type) const; - virtual bool setParameterEntry(U16 param_type, const LLNetworkData& new_value, bool local_origin); - virtual bool getParameterEntryInUse(U16 param_type) const; - virtual bool setParameterEntryInUse(U16 param_type, bool in_use, bool local_origin); - // Called when a parameter is changed - virtual void parameterChanged(U16 param_type, bool local_origin); - virtual void parameterChanged(U16 param_type, LLNetworkData* data, bool in_use, bool local_origin); - - bool isShrinkWrapped() const { return mShouldShrinkWrap; } - - // Used to improve performance. If an object is likely to rebuild its vertex buffer often - // as a side effect of some update (color update, scale, etc), setting this to true - // will cause it to be pushed deeper into the octree and isolate it from other nodes - // so that nearby objects won't attempt to share a vertex buffer with this object. - void shrinkWrap(); - - friend class LLViewerObjectList; - friend class LLViewerMediaList; - -public: - LLViewerTexture* getBakedTextureForMagicId(const LLUUID& id); - void updateAvatarMeshVisibility(const LLUUID& id, const LLUUID& old_id); - void refreshBakeTexture(); -public: - static void unpackVector3(LLDataPackerBinaryBuffer* dp, LLVector3& value, std::string name); - static void unpackUUID(LLDataPackerBinaryBuffer* dp, LLUUID& value, std::string name); - static void unpackU32(LLDataPackerBinaryBuffer* dp, U32& value, std::string name); - static void unpackU8(LLDataPackerBinaryBuffer* dp, U8& value, std::string name); - static U32 unpackParentID(LLDataPackerBinaryBuffer* dp, U32& parent_id); - -public: - //counter-translation - void resetChildrenPosition(const LLVector3& offset, bool simplified = false, bool skip_avatar_child = false) ; - //counter-rotation - void resetChildrenRotationAndPosition(const std::vector& rotations, - const std::vector& positions) ; - void saveUnselectedChildrenRotation(std::vector& rotations) ; - void saveUnselectedChildrenPosition(std::vector& positions) ; - std::vector mUnselectedChildrenPositions ; - -private: - void setObjectCostStale(); - bool isAssetInInventory(LLViewerInventoryItem* item, LLAssetType::EType type); - - ExtraParameter* createNewParameterEntry(U16 param_type); - ExtraParameter* getExtraParameterEntry(U16 param_type) const; - ExtraParameter* getExtraParameterEntryCreate(U16 param_type); - bool unpackParameterEntry(U16 param_type, LLDataPacker *dp); - - // This function checks to see if the given media URL has changed its version - // and the update wasn't due to this agent's last action. - U32 checkMediaURL(const std::string &media_url); - - // Motion prediction between updates - void interpolateLinearMotion(const F64SecondsImplicit & frame_time, const F32SecondsImplicit & dt); - - static void initObjectDataMap(); - - // forms task inventory request if none are pending, marks request as pending - void fetchInventoryFromServer(); - - // forms task inventory request after some time passed, marks request as pending - void fetchInventoryDelayed(const F64 &time_seconds); - static void fetchInventoryDelayedCoro(const LLUUID task_inv, const F64 time_seconds); - -public: - // - // Viewer-side only types - use the LL_PCODE_APP mask. - // - typedef enum e_vo_types - { - LL_VO_CLOUDS = LL_PCODE_APP | 0x20, // no longer used - LL_VO_SURFACE_PATCH = LL_PCODE_APP | 0x30, - LL_VO_WL_SKY = LL_PCODE_APP | 0x40, - LL_VO_SQUARE_TORUS = LL_PCODE_APP | 0x50, - LL_VO_SKY = LL_PCODE_APP | 0x60, - LL_VO_VOID_WATER = LL_PCODE_APP | 0x70, - LL_VO_WATER = LL_PCODE_APP | 0x80, - LL_VO_PART_GROUP = LL_PCODE_APP | 0xa0, - LL_VO_TRIANGLE_TORUS = LL_PCODE_APP | 0xb0, - LL_VO_HUD_PART_GROUP = LL_PCODE_APP | 0xc0, - } EVOType; - - typedef enum e_physics_shape_types - { - PHYSICS_SHAPE_PRIM = 0, - PHYSICS_SHAPE_NONE, - PHYSICS_SHAPE_CONVEX_HULL, - } EPhysicsShapeType; - - LLUUID mID; - LLUUID mOwnerID; //null if unknown - - // unique within region, not unique across regions - // Local ID = 0 is not used - U32 mLocalID; - - // Last total CRC received from sim, used for caching - U32 mTotalCRC; - - // index into LLViewerObjectList::mActiveObjects or -1 if not in list - S32 mListIndex; - - LLPointer *mTEImages; - LLPointer *mTENormalMaps; - LLPointer *mTESpecularMaps; - - // true if user can select this object by clicking under any circumstances (even if pick_unselectable is true) - // can likely be factored out - bool mbCanSelect; - -private: - // Grabbed from UPDATE_FLAGS - U32 mFlags; - - static std::map sObjectDataMap; -public: - // Sent to sim in UPDATE_FLAGS, received in ObjectPhysicsProperties - U8 mPhysicsShapeType; - F32 mPhysicsGravity; - F32 mPhysicsFriction; - F32 mPhysicsDensity; - F32 mPhysicsRestitution; - - - // Pipeline classes - LLPointer mDrawable; - - // Band-aid to select object after all creation initialization is done - bool mCreateSelected; - - // Replace textures with web pages on this object while drawing - bool mRenderMedia; - - bool mRiggedAttachedWarned; - - // In bits - S32 mBestUpdatePrecision; - - // TODO: Make all this stuff private. JC - LLPointer mText; - LLPointer mIcon; - - std::string mHudText; - LLColor4 mHudTextColor; - - static bool sUseSharedDrawables; - -public: - // Returns mControlAvatar for the edit root prim of this linkset - LLControlAvatar *getControlAvatar(); - LLControlAvatar *getControlAvatar() const; - - // Create or connect to an existing control av as applicable - void linkControlAvatar(); - // Remove any reference to control av for this prim - void unlinkControlAvatar(); - // Link or unlink as needed - void updateControlAvatar(); - - virtual bool isAnimatedObject() const; - - // Flags for createObject - static const S32 CO_FLAG_CONTROL_AVATAR = 1 << 0; - static const S32 CO_FLAG_UI_AVATAR = 1 << 1; - -protected: - LLPointer mControlAvatar; - -protected: - // delete an item in the inventory, but don't tell the - // server. This is used internally by remove, update, and - // savescript. - void deleteInventoryItem(const LLUUID& item_id); - - // do the update/caching logic. called by saveScript and - // updateInventory. - void doUpdateInventory(LLPointer& item, U8 key, bool is_new); - - static LLViewerObject *createObject(const LLUUID &id, LLPCode pcode, LLViewerRegion *regionp, S32 flags = 0); - - // Hide or show HUD, icon and particles - void hideExtraDisplayItems( bool hidden ); - - ////////////////////////// - // - // inventory functionality - // - - static void processTaskInvFile(void** user_data, S32 error_code, LLExtStat ext_status); - bool loadTaskInvFile(const std::string& filename); - void doInventoryCallback(); - - bool isOnMap(); - - void unpackParticleSource(const S32 block_num, const LLUUID& owner_id); - void unpackParticleSource(LLDataPacker &dp, const LLUUID& owner_id, bool legacy); - void deleteParticleSource(); - void setParticleSource(const LLPartSysData& particle_parameters, const LLUUID& owner_id); - -private: - void setNameValueList(const std::string& list); // clears nv pairs and then individually adds \n separated NV pairs from \0 terminated string - void deleteTEImages(); // correctly deletes list of images - -protected: - - typedef std::map name_value_map_t; - name_value_map_t mNameValuePairs; // Any name-value pairs stored by script - - child_list_t mChildList; - - F64Seconds mLastInterpUpdateSecs; // Last update for purposes of interpolation - F64Seconds mLastMessageUpdateSecs; // Last update from a message from the simulator - TPACKETID mLatestRecvPacketID; // Latest time stamp on message from simulator - F64SecondsImplicit mRegionCrossExpire; // frame time we detected region crossing in + wait time - - // extra data sent from the sim...currently only used for tree species info - U8* mData; - - LLPointer mPartSourcep; // Particle source associated with this object. - LLAudioSourceVO* mAudioSourcep; - F32 mAudioGain; - F32 mSoundCutOffRadius; - - F32 mAppAngle; // Apparent visual arc in degrees - F32 mPixelArea; // Apparent area in pixels - - // IDs of of all items in the object's content which are added to the object's content, - // but not updated on the server yet. After item was updated, its ID will be removed from this list. - std::list mPendingInventoryItemsIDs; - - // This is the object's inventory from the viewer's perspective. - LLInventoryObject::object_list_t* mInventory; - class LLInventoryCallbackInfo - { - public: - ~LLInventoryCallbackInfo(); - LLVOInventoryListener* mListener; - void* mInventoryData; - }; - typedef std::list callback_list_t; - callback_list_t mInventoryCallbacks; - S16 mInventorySerialNum; - S16 mExpectedInventorySerialNum; - - enum EInventoryRequestState - { - INVENTORY_REQUEST_STOPPED, - INVENTORY_REQUEST_WAIT, // delay before requesting - INVENTORY_REQUEST_PENDING, // just did fetchInventoryFromServer() - INVENTORY_XFER // processed response from 'fetch', now doing an xfer - }; - EInventoryRequestState mInvRequestState; - U64 mInvRequestXFerId; - bool mInventoryDirty; - - LLViewerRegion *mRegionp; // Region that this object belongs to. - bool mDead; - bool mOrphaned; // This is an orphaned child - bool mUserSelected; // Cached user select information - bool mOnActiveList; - bool mOnMap; // On the map. - bool mStatic; // Object doesn't move. - S32 mSeatCount; - S32 mNumFaces; - - F32 mRotTime; // Amount (in seconds) that object has rotated according to angular velocity (llSetTargetOmega) - LLQuaternion mAngularVelocityRot; // accumulated rotation from the angular velocity computations - LLQuaternion mPreviousRotation; - - U8 mAttachmentState; // this encodes the attachment id in a somewhat complex way. 0 if not an attachment. - LLViewerObjectMedia* mMedia; // NULL if no media associated - U8 mClickAction; - F32 mObjectCost; //resource cost of this object or -1 if unknown - F32 mLinksetCost; - F32 mPhysicsCost; - F32 mLinksetPhysicsCost; - - // If true, "shrink wrap" this volume in its spatial partition. See "shrinkWrap" - bool mShouldShrinkWrap = false; - - bool mCostStale; - mutable bool mPhysicsShapeUnknown; - - static U32 sNumZombieObjects; // Objects which are dead, but not deleted - - static bool sMapDebug; // Map render mode - static LLColor4 sEditSelectColor; - static LLColor4 sNoEditSelectColor; - static F32 sCurrentPulse; - static bool sPulseEnabled; - - static S32 sAxisArrowLength; - - - // These two caches are only correct for non-parented objects right now! - mutable LLVector3 mPositionRegion; - mutable LLVector3 mPositionAgent; - - static void setPhaseOutUpdateInterpolationTime(F32 value) { sPhaseOutUpdateInterpolationTime = (F64Seconds) value; } - static void setMaxUpdateInterpolationTime(F32 value) { sMaxUpdateInterpolationTime = (F64Seconds) value; } - static void setMaxRegionCrossingInterpolationTime(F32 value) { sMaxRegionCrossingInterpolationTime = (F64Seconds) value; } - - static void setVelocityInterpolate(bool value) { sVelocityInterpolate = value; } - static void setPingInterpolate(bool value) { sPingInterpolate = value; } - -private: - static S32 sNumObjects; - - static F64Seconds sPhaseOutUpdateInterpolationTime; // For motion interpolation - static F64Seconds sMaxUpdateInterpolationTime; // For motion interpolation - static F64Seconds sMaxRegionCrossingInterpolationTime; // For motion interpolation - - static bool sVelocityInterpolate; - static bool sPingInterpolate; - - bool mCachedOwnerInMuteList; - F64 mCachedMuteListUpdateTime; - - //-------------------------------------------------------------------- - // For objects that are attachments - //-------------------------------------------------------------------- -public: - const LLUUID &getAttachmentItemID() const; - void setAttachmentItemID(const LLUUID &id); - const LLUUID &extractAttachmentItemID(); // find&set the inventory item ID of the attached object - EObjectUpdateType getLastUpdateType() const; - void setLastUpdateType(EObjectUpdateType last_update_type); - bool getLastUpdateCached() const; - void setLastUpdateCached(bool last_update_cached); - - virtual void updateRiggingInfo() {} - - LLJointRiggingInfoTab mJointRiggingInfoTab; - -private: - LLUUID mAttachmentItemID; // ItemID of the associated object is in user inventory. - EObjectUpdateType mLastUpdateType; - bool mLastUpdateCached; - -public: - // reflection probe state - bool mIsReflectionProbe = false; // if true, this object should register itself with LLReflectionProbeManager - LLPointer mReflectionProbe = nullptr; // reflection probe coupled to this viewer object. If not null, should be deregistered when this object is destroyed - - // the amount of GPU time (in ms) it took to render this object according to LLPipeline::profileAvatar - // -1.f if no profile data available - F32 mGPURenderTime = -1.f; -}; - -/////////////////// -// -// Inlines -// -// - -inline void LLViewerObject::setRotation(const LLQuaternion& quat, bool damped) -{ - LLPrimitive::setRotation(quat); - setChanged(ROTATED | SILHOUETTE); - updateDrawable(damped); -} - -inline void LLViewerObject::setRotation(const F32 x, const F32 y, const F32 z, bool damped) -{ - LLPrimitive::setRotation(x, y, z); - setChanged(ROTATED | SILHOUETTE); - updateDrawable(damped); -} - -class LLViewerObjectMedia -{ -public: - LLViewerObjectMedia() : mMediaURL(), mPassedWhitelist(false), mMediaType(0) { } - - std::string mMediaURL; // for web pages on surfaces, one per prim - bool mPassedWhitelist; // user has OK'd display - U8 mMediaType; // see LLTextureEntry::WEB_PAGE, etc. -}; - -// subclass of viewer object that can be added to particle partitions -class LLAlphaObject : public LLViewerObject -{ -public: - LLAlphaObject(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) - : LLViewerObject(id,pcode,regionp) - { mDepth = 0.f; } - - virtual F32 getPartSize(S32 idx); - virtual void getGeometry(S32 idx, - LLStrider& verticesp, - LLStrider& normalsp, - LLStrider& texcoordsp, - LLStrider& colorsp, - LLStrider& emissivep, - LLStrider& indicesp) = 0; - - virtual void getBlendFunc(S32 face, LLRender::eBlendFactor& src, LLRender::eBlendFactor& dst); - - F32 mDepth; -}; - -class LLStaticViewerObject : public LLViewerObject -{ -public: - LLStaticViewerObject(const LLUUID& id, const LLPCode pcode, LLViewerRegion* regionp, bool is_global = false) - : LLViewerObject(id,pcode,regionp, is_global) - { } - - virtual void updateDrawable(bool force_damped); -}; - - -#endif +/** + * @file llviewerobject.h + * @brief Description of LLViewerObject class, which is the base class for most objects in the viewer. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVIEWEROBJECT_H +#define LL_LLVIEWEROBJECT_H + +#include +#include + +#include "llassetstorage.h" +//#include "llhudicon.h" +#include "llinventory.h" +#include "llrefcount.h" +#include "llprimitive.h" +#include "lluuid.h" +#include "llvoinventorylistener.h" +#include "object_flags.h" +#include "llquaternion.h" +#include "v3dmath.h" +#include "v3math.h" +#include "llvertexbuffer.h" +#include "llbbox.h" +#include "llrigginginfo.h" +#include "llreflectionmap.h" + +class LLAgent; // TODO: Get rid of this. +class LLAudioSource; +class LLAudioSourceVO; +class LLColor4; +class LLControlAvatar; +class LLDataPacker; +class LLDataPackerBinaryBuffer; +class LLDrawable; +class LLHUDText; +class LLHost; +class LLMessageSystem; +class LLNameValue; +class LLPartSysData; +class LLPipeline; +class LLTextureEntry; +class LLVOAvatar; +class LLVOInventoryListener; +class LLViewerInventoryItem; +class LLViewerObject; +class LLViewerObjectMedia; +class LLViewerPartSourceScript; +class LLViewerRegion; +class LLViewerTexture; +class LLWorld; + +class LLMeshCostData; + +typedef enum e_object_update_type +{ + OUT_FULL, + OUT_TERSE_IMPROVED, + OUT_FULL_COMPRESSED, + OUT_FULL_CACHED, + OUT_UNKNOWN, +} EObjectUpdateType; + + +// callback typedef for inventory +typedef void (*inventory_callback)(LLViewerObject*, + LLInventoryObject::object_list_t*, + S32 serial_num, + void*); + +// for exporting textured materials from SL +struct LLMaterialExportInfo +{ +public: + LLMaterialExportInfo(S32 mat_index, S32 texture_index, LLColor4 color) : + mMaterialIndex(mat_index), mTextureIndex(texture_index), mColor(color) {}; + + S32 mMaterialIndex; + S32 mTextureIndex; + LLColor4 mColor; +}; + +struct PotentialReturnableObject +{ + LLBBox box; + LLViewerRegion* pRegion; +}; + +//============================================================================ + +class LLViewerObject +: public LLPrimitive, + public LLRefCount, + public LLGLUpdate +{ +protected: + virtual ~LLViewerObject(); // use unref() + + // TomY: Provide for a list of extra parameter structures, mapped by structure name + struct ExtraParameter + { + bool in_use; + LLNetworkData *data; + }; + std::unordered_map mExtraParameterList; + +public: + typedef std::list > child_list_t; + typedef std::list > vobj_list_t; + + typedef const child_list_t const_child_list_t; + + LLViewerObject(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp, bool is_global = false); + + virtual void markDead(); // Mark this object as dead, and clean up its references + bool isDead() const {return mDead;} + bool isOrphaned() const { return mOrphaned; } + bool isParticleSource() const; + + virtual LLVOAvatar* asAvatar(); + + LLVOAvatar* getAvatarAncestor(); + + static void initVOClasses(); + static void cleanupVOClasses(); + + void addNVPair(const std::string& data); + bool removeNVPair(const std::string& name); + LLNameValue* getNVPair(const std::string& name) const; // null if no name value pair by that name + + // Object create and update functions + virtual void idleUpdate(LLAgent &agent, const F64 &time); + + // Types of media we can associate + enum { MEDIA_NONE = 0, MEDIA_SET = 1 }; + + // Return codes for processUpdateMessage + enum { + MEDIA_URL_REMOVED = 0x1, + MEDIA_URL_ADDED = 0x2, + MEDIA_URL_UPDATED = 0x4, + MEDIA_FLAGS_CHANGED = 0x8, + INVALID_UPDATE = 0x80000000 + }; + + static U32 extractSpatialExtents(LLDataPackerBinaryBuffer *dp, LLVector3& pos, LLVector3& scale, LLQuaternion& rot); + virtual U32 processUpdateMessage(LLMessageSystem *mesgsys, + void **user_data, + U32 block_num, + const EObjectUpdateType update_type, + LLDataPacker *dp); + + + virtual bool isActive() const; // Whether this object needs to do an idleUpdate. + bool onActiveList() const {return mOnActiveList;} + void setOnActiveList(bool on_active) { mOnActiveList = on_active; } + + virtual bool isAttachment() const { return false; } + const std::string& getAttachmentItemName() const; + + virtual LLVOAvatar* getAvatar() const; //get the avatar this object is attached to, or NULL if object is not an attachment + + bool hasRenderMaterialParams() const; + void setHasRenderMaterialParams(bool has_params); + + const LLUUID& getRenderMaterialID(U8 te) const; + + // set the RenderMaterialID for the given TextureEntry + // te - TextureEntry index to set, or -1 for all TEs + // id - asset id of material asset + // update_server - if true, will send updates to server and clear most overrides + void setRenderMaterialID(S32 te, const LLUUID& id, bool update_server = true, bool local_origin = true); + void setRenderMaterialIDs(const LLUUID& id); + + virtual bool isHUDAttachment() const { return false; } + virtual bool isTempAttachment() const; + + virtual bool isHiglightedOrBeacon() const; + + virtual void updateRadius() {}; + virtual F32 getVObjRadius() const; // default implemenation is mDrawable->getRadius() + + // for jointed and other parent-relative hacks + LLViewerObject* getSubParent(); + const LLViewerObject* getSubParent() const; + + // Object visiblility and GPW functions + virtual void setPixelAreaAndAngle(LLAgent &agent); // Override to generate accurate apparent angle and area + + virtual U32 getNumVertices() const; + virtual U32 getNumIndices() const; + S32 getNumFaces() const { return mNumFaces; } + + // Graphical stuff for objects - maybe broken out into render class later? + virtual void updateTextures(); + virtual void faceMappingChanged() {} + virtual void boostTexturePriority(bool boost_children = true); // When you just want to boost priority of this object + + virtual LLDrawable* createDrawable(LLPipeline *pipeline); + virtual bool updateGeometry(LLDrawable *drawable); + virtual void updateGL(); + virtual void updateFaceSize(S32 idx); + virtual bool updateLOD(); + virtual bool setDrawableParent(LLDrawable* parentp); + F32 getRotTime() { return mRotTime; } +private: + void resetRotTime(); + void setRenderMaterialIDs(const LLRenderMaterialParams* material_params, bool local_origin); + void rebuildMaterial(); +public: + void resetRot(); + void applyAngularVelocity(F32 dt); + + void setLineWidthForWindowSize(S32 window_width); + + static void increaseArrowLength(); // makes axis arrows for selections longer + static void decreaseArrowLength(); // makes axis arrows for selections shorter + + // Accessor functions + LLViewerRegion* getRegion() const { return mRegionp; } + + bool isSelected() const { return mUserSelected; } + // Check whole linkset + bool isAnySelected() const; + virtual void setSelected(bool sel); + + const LLUUID &getID() const { return mID; } + U32 getLocalID() const { return mLocalID; } + U32 getCRC() const { return mTotalCRC; } + S32 getListIndex() const { return mListIndex; } + void setListIndex(S32 idx) { mListIndex = idx; } + + virtual bool isFlexible() const { return false; } + virtual bool isSculpted() const { return false; } + virtual bool isMesh() const { return false; } + virtual bool isRiggedMesh() const { return false; } + virtual bool hasLightTexture() const { return false; } + virtual bool isReflectionProbe() const { return false; } + + // This method returns true if the object is over land owned by + // the agent, one of its groups, or it encroaches and + // anti-encroachment is enabled + bool isReturnable(); + + void buildReturnablesForChildrenVO( std::vector& returnables, LLViewerObject* pChild, LLViewerRegion* pTargetRegion ); + void constructAndAddReturnable( std::vector& returnables, LLViewerObject* pChild, LLViewerRegion* pTargetRegion ); + + // This method returns true if the object crosses + // any parcel bounds in the region. + bool crossesParcelBounds(); + + /* + // This method will scan through this object, and then query the + // selection manager to see if the local agent probably has the + // ability to modify the object. Since this calls into the + // selection manager, you should avoid calling this method from + // there. + bool isProbablyModifiable() const; + */ + + virtual bool setParent(LLViewerObject* parent); + virtual void onReparent(LLViewerObject *old_parent, LLViewerObject *new_parent); + virtual void afterReparent(); + virtual void addChild(LLViewerObject *childp); + virtual void removeChild(LLViewerObject *childp); + const_child_list_t& getChildren() const { return mChildList; } + S32 numChildren() const { return mChildList.size(); } + void addThisAndAllChildren(std::vector& objects); + void addThisAndNonJointChildren(std::vector& objects); + bool isChild(LLViewerObject *childp) const; + bool isSeat() const; + + + //detect if given line segment (in agent space) intersects with this viewer object. + //returns true if intersection detected and returns information about intersection + virtual bool lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, + S32 face = -1, // which face to check, -1 = ALL_SIDES + bool pick_transparent = false, + bool pick_rigged = false, + bool pick_unselectable = true, + S32* face_hit = NULL, // which face was hit + LLVector4a* intersection = NULL, // return the intersection point + LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point + LLVector4a* normal = NULL, // return the surface normal at the intersection point + LLVector4a* tangent = NULL // return the surface tangent at the intersection point + ); + + virtual bool lineSegmentBoundingBox(const LLVector4a& start, const LLVector4a& end); + + virtual const LLVector3d getPositionGlobal() const; + virtual const LLVector3 &getPositionRegion() const; + virtual const LLVector3 getPositionEdit() const; + virtual const LLVector3 &getPositionAgent() const; + virtual const LLVector3 getRenderPosition() const; + + virtual const LLVector3 getPivotPositionAgent() const; // Usually = to getPositionAgent, unless like flex objects it's not + + LLViewerObject* getRootEdit() const; + + const LLQuaternion getRotationRegion() const; + const LLQuaternion getRotationEdit() const; + const LLQuaternion getRenderRotation() const; + virtual const LLMatrix4 getRenderMatrix() const; + + void setPosition(const LLVector3 &pos, bool damped = false); + void setPositionGlobal(const LLVector3d &position, bool damped = false); + void setPositionRegion(const LLVector3 &position, bool damped = false); + void setPositionEdit(const LLVector3 &position, bool damped = false); + void setPositionAgent(const LLVector3 &pos_agent, bool damped = false); + void setPositionParent(const LLVector3 &pos_parent, bool damped = false); + void setPositionAbsoluteGlobal( const LLVector3d &pos_global, bool damped = false ); + + virtual const LLMatrix4& getWorldMatrix(LLXformMatrix* xform) const { return xform->getWorldMatrix(); } + + inline void setRotation(const F32 x, const F32 y, const F32 z, bool damped = false); + inline void setRotation(const LLQuaternion& quat, bool damped = false); + + /*virtual*/ void setNumTEs(const U8 num_tes); + /*virtual*/ void setTE(const U8 te, const LLTextureEntry &texture_entry); + void updateTEMaterialTextures(U8 te); + /*virtual*/ S32 setTETexture(const U8 te, const LLUUID &uuid); + /*virtual*/ S32 setTENormalMap(const U8 te, const LLUUID &uuid); + /*virtual*/ S32 setTESpecularMap(const U8 te, const LLUUID &uuid); + S32 setTETextureCore(const U8 te, LLViewerTexture *image); + S32 setTENormalMapCore(const U8 te, LLViewerTexture *image); + S32 setTESpecularMapCore(const U8 te, LLViewerTexture *image); + /*virtual*/ S32 setTEColor(const U8 te, const LLColor3 &color); + /*virtual*/ S32 setTEColor(const U8 te, const LLColor4 &color); + /*virtual*/ S32 setTEScale(const U8 te, const F32 s, const F32 t); + /*virtual*/ S32 setTEScaleS(const U8 te, const F32 s); + /*virtual*/ S32 setTEScaleT(const U8 te, const F32 t); + /*virtual*/ S32 setTEOffset(const U8 te, const F32 s, const F32 t); + /*virtual*/ S32 setTEOffsetS(const U8 te, const F32 s); + /*virtual*/ S32 setTEOffsetT(const U8 te, const F32 t); + /*virtual*/ S32 setTERotation(const U8 te, const F32 r); + /*virtual*/ S32 setTEBumpmap(const U8 te, const U8 bump ); + /*virtual*/ S32 setTETexGen(const U8 te, const U8 texgen ); + /*virtual*/ S32 setTEMediaTexGen(const U8 te, const U8 media ); // *FIXME: this confusingly acts upon a superset of setTETexGen's flags without absorbing its semantics + /*virtual*/ S32 setTEShiny(const U8 te, const U8 shiny ); + /*virtual*/ S32 setTEFullbright(const U8 te, const U8 fullbright ); + /*virtual*/ S32 setTEMediaFlags(const U8 te, const U8 media_flags ); + /*virtual*/ S32 setTEGlow(const U8 te, const F32 glow); + /*virtual*/ S32 setTEMaterialID(const U8 te, const LLMaterialID& pMaterialID); + /*virtual*/ S32 setTEMaterialParams(const U8 te, const LLMaterialPtr pMaterialParams); + virtual S32 setTEGLTFMaterialOverride(U8 te, LLGLTFMaterial* mat); + + // Used by Materials update functions to properly kick off rebuilds + // of VBs etc when materials updates require changes. + // + void refreshMaterials(); + + /*virtual*/ bool setMaterial(const U8 material); + virtual void setTEImage(const U8 te, LLViewerTexture *imagep); // Not derived from LLPrimitive + virtual void changeTEImage(S32 index, LLViewerTexture* new_image) ; + virtual void changeTENormalMap(S32 index, LLViewerTexture* new_image) ; + virtual void changeTESpecularMap(S32 index, LLViewerTexture* new_image) ; + LLViewerTexture *getTEImage(const U8 te) const; + LLViewerTexture *getTENormalMap(const U8 te) const; + LLViewerTexture *getTESpecularMap(const U8 te) const; + + bool isImageAlphaBlended(const U8 te) const; + + void fitFaceTexture(const U8 face); + void sendTEUpdate() const; // Sends packed representation of all texture entry information + + virtual void setScale(const LLVector3 &scale, bool damped = false); + + S32 getAnimatedObjectMaxTris() const; + F32 recursiveGetEstTrianglesMax() const; + virtual F32 getEstTrianglesMax() const; + virtual F32 getEstTrianglesStreamingCost() const; + virtual F32 getStreamingCost() const; + virtual bool getCostData(LLMeshCostData& costs) const; + virtual U32 getTriangleCount(S32* vcount = NULL) const; + virtual U32 getHighLODTriangleCount(); + F32 recursiveGetScaledSurfaceArea() const; + + U32 recursiveGetTriangleCount(S32* vcount = NULL) const; + + void setObjectCost(F32 cost); + F32 getObjectCost(); + + void setLinksetCost(F32 cost); + F32 getLinksetCost(); + + void setPhysicsCost(F32 cost); + F32 getPhysicsCost(); + + void setLinksetPhysicsCost(F32 cost); + F32 getLinksetPhysicsCost(); + + void sendShapeUpdate(); + + U8 getAttachmentState() { return mAttachmentState; } + + F32 getAppAngle() const { return mAppAngle; } + F32 getPixelArea() const { return mPixelArea; } + void setPixelArea(F32 area) { mPixelArea = area; } + F32 getMaxScale() const; + F32 getMidScale() const; + F32 getMinScale() const; + + // Owner id is this object's owner + void setAttachedSound(const LLUUID &audio_uuid, const LLUUID& owner_id, const F32 gain, const U8 flags); + void adjustAudioGain(const F32 gain); + F32 getSoundCutOffRadius() const { return mSoundCutOffRadius; } + void clearAttachedSound() { mAudioSourcep = NULL; } + + // Create if necessary + LLAudioSource *getAudioSource(const LLUUID& owner_id); + bool isAudioSource() const {return mAudioSourcep != NULL;} + + U8 getMediaType() const; + void setMediaType(U8 media_type); + + std::string getMediaURL() const; + void setMediaURL(const std::string& media_url); + + bool getMediaPassedWhitelist() const; + void setMediaPassedWhitelist(bool passed); + + void sendMaterialUpdate() const; + + void setDebugText(const std::string &utf8text, const LLColor4& color = LLColor4::white); + void appendDebugText(const std::string &utf8text); + void initHudText(); + void restoreHudText(); + void setIcon(LLViewerTexture* icon_image); + void clearIcon(); + + void recursiveMarkForUpdate(); + virtual void markForUpdate(); + void updateVolume(const LLVolumeParams& volume_params); + virtual void updateSpatialExtents(LLVector4a& min, LLVector4a& max); + virtual F32 getBinRadius(); + + LLBBox getBoundingBoxAgent() const; + + void updatePositionCaches() const; // Update the global and region position caches from the object (and parent's) xform. + void updateText(); // update text label position + virtual void updateDrawable(bool force_damped); // force updates on static objects + + bool isOwnerInMuteList(LLUUID item_id = LLUUID()); + + void setDrawableState(U32 state, bool recursive = true); + void clearDrawableState(U32 state, bool recursive = true); + bool isDrawableState(U32 state, bool recursive = true) const; + + // Called when the drawable shifts + virtual void onShift(const LLVector4a &shift_vector) { } + + ////////////////////////////////////// + // + // Inventory methods + // + + // This function is called when someone is interested in a viewer + // object's inventory. The callback is called as soon as the + // viewer object has the inventory stored locally. + void registerInventoryListener(LLVOInventoryListener* listener, void* user_data); + void removeInventoryListener(LLVOInventoryListener* listener); + bool isInventoryPending(); + void clearInventoryListeners(); + bool hasInventoryListeners(); + void requestInventory(); + static void processTaskInv(LLMessageSystem* msg, void** user_data); + void removeInventory(const LLUUID& item_id); + + // The updateInventory() call potentially calls into the selection + // manager, so do no call updateInventory() from the selection + // manager until we have better iterators. + void updateInventory(LLViewerInventoryItem* item, U8 key, bool is_new); + void updateInventoryLocal(LLInventoryItem* item, U8 key); // Update without messaging. + void updateMaterialInventory(LLViewerInventoryItem* item, U8 key, bool is_new); + LLInventoryObject* getInventoryObject(const LLUUID& item_id); + LLInventoryItem* getInventoryItem(const LLUUID& item_id); + + // Get content except for root category + void getInventoryContents(LLInventoryObject::object_list_t& objects); + LLInventoryObject* getInventoryRoot(); + LLViewerInventoryItem* getInventoryItemByAsset(const LLUUID& asset_id); + LLViewerInventoryItem* getInventoryItemByAsset(const LLUUID& asset_id, LLAssetType::EType type); + S16 getInventorySerial() const { return mInventorySerialNum; } + + // These functions does viewer-side only object inventory modifications + void updateViewerInventoryAsset( + const LLViewerInventoryItem* item, + const LLUUID& new_asset); + + // This function will make sure that we refresh the inventory. + void dirtyInventory(); + bool isInventoryDirty() { return mInventoryDirty; } + + // save a script, which involves removing the old one, and rezzing + // in the new one. This method should be called with the asset id + // of the new and old script AFTER the bytecode has been saved. + void saveScript(const LLViewerInventoryItem* item, bool active, bool is_new); + + // move an inventory item out of the task and into agent + // inventory. This operation is based on messaging. No permissions + // checks are made on the viewer - the server will double check. + void moveInventory(const LLUUID& agent_folder, const LLUUID& item_id); + + // Find the number of instances of this object's inventory that are of the given type + S32 countInventoryContents( LLAssetType::EType type ); + + bool permAnyOwner() const; + bool permYouOwner() const; + bool permGroupOwner() const; + bool permOwnerModify() const; + bool permModify() const; + bool permCopy() const; + bool permMove() const; + bool permTransfer() const; + inline bool flagUsePhysics() const { return ((mFlags & FLAGS_USE_PHYSICS) != 0); } + inline bool flagObjectAnyOwner() const { return ((mFlags & FLAGS_OBJECT_ANY_OWNER) != 0); } + inline bool flagObjectYouOwner() const { return ((mFlags & FLAGS_OBJECT_YOU_OWNER) != 0); } + inline bool flagObjectGroupOwned() const { return ((mFlags & FLAGS_OBJECT_GROUP_OWNED) != 0); } + inline bool flagObjectOwnerModify() const { return ((mFlags & FLAGS_OBJECT_OWNER_MODIFY) != 0); } + inline bool flagObjectModify() const { return ((mFlags & FLAGS_OBJECT_MODIFY) != 0); } + inline bool flagObjectCopy() const { return ((mFlags & FLAGS_OBJECT_COPY) != 0); } + inline bool flagObjectMove() const { return ((mFlags & FLAGS_OBJECT_MOVE) != 0); } + inline bool flagObjectTransfer() const { return ((mFlags & FLAGS_OBJECT_TRANSFER) != 0); } + inline bool flagObjectPermanent() const { return ((mFlags & FLAGS_AFFECTS_NAVMESH) != 0); } + inline bool flagCharacter() const { return ((mFlags & FLAGS_CHARACTER) != 0); } + inline bool flagVolumeDetect() const { return ((mFlags & FLAGS_VOLUME_DETECT) != 0); } + inline bool flagIncludeInSearch() const { return ((mFlags & FLAGS_INCLUDE_IN_SEARCH) != 0); } + inline bool flagScripted() const { return ((mFlags & FLAGS_SCRIPTED) != 0); } + inline bool flagHandleTouch() const { return ((mFlags & FLAGS_HANDLE_TOUCH) != 0); } + inline bool flagTakesMoney() const { return ((mFlags & FLAGS_TAKES_MONEY) != 0); } + inline bool flagPhantom() const { return ((mFlags & FLAGS_PHANTOM) != 0); } + inline bool flagInventoryEmpty() const { return ((mFlags & FLAGS_INVENTORY_EMPTY) != 0); } + inline bool flagAllowInventoryAdd() const { return ((mFlags & FLAGS_ALLOW_INVENTORY_DROP) != 0); } + inline bool flagTemporaryOnRez() const { return ((mFlags & FLAGS_TEMPORARY_ON_REZ) != 0); } + inline bool flagAnimSource() const { return ((mFlags & FLAGS_ANIM_SOURCE) != 0); } + inline bool flagCameraSource() const { return ((mFlags & FLAGS_CAMERA_SOURCE) != 0); } + inline bool flagCameraDecoupled() const { return ((mFlags & FLAGS_CAMERA_DECOUPLED) != 0); } + + U8 getPhysicsShapeType() const; + inline F32 getPhysicsGravity() const { return mPhysicsGravity; } + inline F32 getPhysicsFriction() const { return mPhysicsFriction; } + inline F32 getPhysicsDensity() const { return mPhysicsDensity; } + inline F32 getPhysicsRestitution() const { return mPhysicsRestitution; } + + bool isPermanentEnforced() const; + + bool getIncludeInSearch() const; + void setIncludeInSearch(bool include_in_search); + + // Does "open" object menu item apply? + bool allowOpen() const; + + void setClickAction(U8 action) { mClickAction = action; } + U8 getClickAction() const { return mClickAction; } + bool specialHoverCursor() const; // does it have a special hover cursor? + + void setRegion(LLViewerRegion *regionp); + virtual void updateRegion(LLViewerRegion *regionp); + + void updateFlags(bool physics_changed = false); + void loadFlags(U32 flags); //load flags from cache or from message + bool setFlags(U32 flag, bool state); + bool setFlagsWithoutUpdate(U32 flag, bool state); + void setPhysicsShapeType(U8 type); + void setPhysicsGravity(F32 gravity); + void setPhysicsFriction(F32 friction); + void setPhysicsDensity(F32 density); + void setPhysicsRestitution(F32 restitution); + + virtual void dump() const; + static U32 getNumZombieObjects() { return sNumZombieObjects; } + + void printNameValuePairs() const; + + virtual S32 getLOD() const { return 3; } + virtual U32 getPartitionType() const; + void dirtySpatialGroup() const; + virtual void dirtyMesh(); + + virtual LLNetworkData* getParameterEntry(U16 param_type) const; + virtual bool setParameterEntry(U16 param_type, const LLNetworkData& new_value, bool local_origin); + virtual bool getParameterEntryInUse(U16 param_type) const; + virtual bool setParameterEntryInUse(U16 param_type, bool in_use, bool local_origin); + // Called when a parameter is changed + virtual void parameterChanged(U16 param_type, bool local_origin); + virtual void parameterChanged(U16 param_type, LLNetworkData* data, bool in_use, bool local_origin); + + bool isShrinkWrapped() const { return mShouldShrinkWrap; } + + // Used to improve performance. If an object is likely to rebuild its vertex buffer often + // as a side effect of some update (color update, scale, etc), setting this to true + // will cause it to be pushed deeper into the octree and isolate it from other nodes + // so that nearby objects won't attempt to share a vertex buffer with this object. + void shrinkWrap(); + + friend class LLViewerObjectList; + friend class LLViewerMediaList; + +public: + LLViewerTexture* getBakedTextureForMagicId(const LLUUID& id); + void updateAvatarMeshVisibility(const LLUUID& id, const LLUUID& old_id); + void refreshBakeTexture(); +public: + static void unpackVector3(LLDataPackerBinaryBuffer* dp, LLVector3& value, std::string name); + static void unpackUUID(LLDataPackerBinaryBuffer* dp, LLUUID& value, std::string name); + static void unpackU32(LLDataPackerBinaryBuffer* dp, U32& value, std::string name); + static void unpackU8(LLDataPackerBinaryBuffer* dp, U8& value, std::string name); + static U32 unpackParentID(LLDataPackerBinaryBuffer* dp, U32& parent_id); + +public: + //counter-translation + void resetChildrenPosition(const LLVector3& offset, bool simplified = false, bool skip_avatar_child = false) ; + //counter-rotation + void resetChildrenRotationAndPosition(const std::vector& rotations, + const std::vector& positions) ; + void saveUnselectedChildrenRotation(std::vector& rotations) ; + void saveUnselectedChildrenPosition(std::vector& positions) ; + std::vector mUnselectedChildrenPositions ; + +private: + void setObjectCostStale(); + bool isAssetInInventory(LLViewerInventoryItem* item, LLAssetType::EType type); + + ExtraParameter* createNewParameterEntry(U16 param_type); + ExtraParameter* getExtraParameterEntry(U16 param_type) const; + ExtraParameter* getExtraParameterEntryCreate(U16 param_type); + bool unpackParameterEntry(U16 param_type, LLDataPacker *dp); + + // This function checks to see if the given media URL has changed its version + // and the update wasn't due to this agent's last action. + U32 checkMediaURL(const std::string &media_url); + + // Motion prediction between updates + void interpolateLinearMotion(const F64SecondsImplicit & frame_time, const F32SecondsImplicit & dt); + + static void initObjectDataMap(); + + // forms task inventory request if none are pending, marks request as pending + void fetchInventoryFromServer(); + + // forms task inventory request after some time passed, marks request as pending + void fetchInventoryDelayed(const F64 &time_seconds); + static void fetchInventoryDelayedCoro(const LLUUID task_inv, const F64 time_seconds); + +public: + // + // Viewer-side only types - use the LL_PCODE_APP mask. + // + typedef enum e_vo_types + { + LL_VO_CLOUDS = LL_PCODE_APP | 0x20, // no longer used + LL_VO_SURFACE_PATCH = LL_PCODE_APP | 0x30, + LL_VO_WL_SKY = LL_PCODE_APP | 0x40, + LL_VO_SQUARE_TORUS = LL_PCODE_APP | 0x50, + LL_VO_SKY = LL_PCODE_APP | 0x60, + LL_VO_VOID_WATER = LL_PCODE_APP | 0x70, + LL_VO_WATER = LL_PCODE_APP | 0x80, + LL_VO_PART_GROUP = LL_PCODE_APP | 0xa0, + LL_VO_TRIANGLE_TORUS = LL_PCODE_APP | 0xb0, + LL_VO_HUD_PART_GROUP = LL_PCODE_APP | 0xc0, + } EVOType; + + typedef enum e_physics_shape_types + { + PHYSICS_SHAPE_PRIM = 0, + PHYSICS_SHAPE_NONE, + PHYSICS_SHAPE_CONVEX_HULL, + } EPhysicsShapeType; + + LLUUID mID; + LLUUID mOwnerID; //null if unknown + + // unique within region, not unique across regions + // Local ID = 0 is not used + U32 mLocalID; + + // Last total CRC received from sim, used for caching + U32 mTotalCRC; + + // index into LLViewerObjectList::mActiveObjects or -1 if not in list + S32 mListIndex; + + LLPointer *mTEImages; + LLPointer *mTENormalMaps; + LLPointer *mTESpecularMaps; + + // true if user can select this object by clicking under any circumstances (even if pick_unselectable is true) + // can likely be factored out + bool mbCanSelect; + +private: + // Grabbed from UPDATE_FLAGS + U32 mFlags; + + static std::map sObjectDataMap; +public: + // Sent to sim in UPDATE_FLAGS, received in ObjectPhysicsProperties + U8 mPhysicsShapeType; + F32 mPhysicsGravity; + F32 mPhysicsFriction; + F32 mPhysicsDensity; + F32 mPhysicsRestitution; + + + // Pipeline classes + LLPointer mDrawable; + + // Band-aid to select object after all creation initialization is done + bool mCreateSelected; + + // Replace textures with web pages on this object while drawing + bool mRenderMedia; + + bool mRiggedAttachedWarned; + + // In bits + S32 mBestUpdatePrecision; + + // TODO: Make all this stuff private. JC + LLPointer mText; + LLPointer mIcon; + + std::string mHudText; + LLColor4 mHudTextColor; + + static bool sUseSharedDrawables; + +public: + // Returns mControlAvatar for the edit root prim of this linkset + LLControlAvatar *getControlAvatar(); + LLControlAvatar *getControlAvatar() const; + + // Create or connect to an existing control av as applicable + void linkControlAvatar(); + // Remove any reference to control av for this prim + void unlinkControlAvatar(); + // Link or unlink as needed + void updateControlAvatar(); + + virtual bool isAnimatedObject() const; + + // Flags for createObject + static const S32 CO_FLAG_CONTROL_AVATAR = 1 << 0; + static const S32 CO_FLAG_UI_AVATAR = 1 << 1; + +protected: + LLPointer mControlAvatar; + +protected: + // delete an item in the inventory, but don't tell the + // server. This is used internally by remove, update, and + // savescript. + void deleteInventoryItem(const LLUUID& item_id); + + // do the update/caching logic. called by saveScript and + // updateInventory. + void doUpdateInventory(LLPointer& item, U8 key, bool is_new); + + static LLViewerObject *createObject(const LLUUID &id, LLPCode pcode, LLViewerRegion *regionp, S32 flags = 0); + + // Hide or show HUD, icon and particles + void hideExtraDisplayItems( bool hidden ); + + ////////////////////////// + // + // inventory functionality + // + + static void processTaskInvFile(void** user_data, S32 error_code, LLExtStat ext_status); + bool loadTaskInvFile(const std::string& filename); + void doInventoryCallback(); + + bool isOnMap(); + + void unpackParticleSource(const S32 block_num, const LLUUID& owner_id); + void unpackParticleSource(LLDataPacker &dp, const LLUUID& owner_id, bool legacy); + void deleteParticleSource(); + void setParticleSource(const LLPartSysData& particle_parameters, const LLUUID& owner_id); + +private: + void setNameValueList(const std::string& list); // clears nv pairs and then individually adds \n separated NV pairs from \0 terminated string + void deleteTEImages(); // correctly deletes list of images + +protected: + + typedef std::map name_value_map_t; + name_value_map_t mNameValuePairs; // Any name-value pairs stored by script + + child_list_t mChildList; + + F64Seconds mLastInterpUpdateSecs; // Last update for purposes of interpolation + F64Seconds mLastMessageUpdateSecs; // Last update from a message from the simulator + TPACKETID mLatestRecvPacketID; // Latest time stamp on message from simulator + F64SecondsImplicit mRegionCrossExpire; // frame time we detected region crossing in + wait time + + // extra data sent from the sim...currently only used for tree species info + U8* mData; + + LLPointer mPartSourcep; // Particle source associated with this object. + LLAudioSourceVO* mAudioSourcep; + F32 mAudioGain; + F32 mSoundCutOffRadius; + + F32 mAppAngle; // Apparent visual arc in degrees + F32 mPixelArea; // Apparent area in pixels + + // IDs of of all items in the object's content which are added to the object's content, + // but not updated on the server yet. After item was updated, its ID will be removed from this list. + std::list mPendingInventoryItemsIDs; + + // This is the object's inventory from the viewer's perspective. + LLInventoryObject::object_list_t* mInventory; + class LLInventoryCallbackInfo + { + public: + ~LLInventoryCallbackInfo(); + LLVOInventoryListener* mListener; + void* mInventoryData; + }; + typedef std::list callback_list_t; + callback_list_t mInventoryCallbacks; + S16 mInventorySerialNum; + S16 mExpectedInventorySerialNum; + + enum EInventoryRequestState + { + INVENTORY_REQUEST_STOPPED, + INVENTORY_REQUEST_WAIT, // delay before requesting + INVENTORY_REQUEST_PENDING, // just did fetchInventoryFromServer() + INVENTORY_XFER // processed response from 'fetch', now doing an xfer + }; + EInventoryRequestState mInvRequestState; + U64 mInvRequestXFerId; + bool mInventoryDirty; + + LLViewerRegion *mRegionp; // Region that this object belongs to. + bool mDead; + bool mOrphaned; // This is an orphaned child + bool mUserSelected; // Cached user select information + bool mOnActiveList; + bool mOnMap; // On the map. + bool mStatic; // Object doesn't move. + S32 mSeatCount; + S32 mNumFaces; + + F32 mRotTime; // Amount (in seconds) that object has rotated according to angular velocity (llSetTargetOmega) + LLQuaternion mAngularVelocityRot; // accumulated rotation from the angular velocity computations + LLQuaternion mPreviousRotation; + + U8 mAttachmentState; // this encodes the attachment id in a somewhat complex way. 0 if not an attachment. + LLViewerObjectMedia* mMedia; // NULL if no media associated + U8 mClickAction; + F32 mObjectCost; //resource cost of this object or -1 if unknown + F32 mLinksetCost; + F32 mPhysicsCost; + F32 mLinksetPhysicsCost; + + // If true, "shrink wrap" this volume in its spatial partition. See "shrinkWrap" + bool mShouldShrinkWrap = false; + + bool mCostStale; + mutable bool mPhysicsShapeUnknown; + + static U32 sNumZombieObjects; // Objects which are dead, but not deleted + + static bool sMapDebug; // Map render mode + static LLColor4 sEditSelectColor; + static LLColor4 sNoEditSelectColor; + static F32 sCurrentPulse; + static bool sPulseEnabled; + + static S32 sAxisArrowLength; + + + // These two caches are only correct for non-parented objects right now! + mutable LLVector3 mPositionRegion; + mutable LLVector3 mPositionAgent; + + static void setPhaseOutUpdateInterpolationTime(F32 value) { sPhaseOutUpdateInterpolationTime = (F64Seconds) value; } + static void setMaxUpdateInterpolationTime(F32 value) { sMaxUpdateInterpolationTime = (F64Seconds) value; } + static void setMaxRegionCrossingInterpolationTime(F32 value) { sMaxRegionCrossingInterpolationTime = (F64Seconds) value; } + + static void setVelocityInterpolate(bool value) { sVelocityInterpolate = value; } + static void setPingInterpolate(bool value) { sPingInterpolate = value; } + +private: + static S32 sNumObjects; + + static F64Seconds sPhaseOutUpdateInterpolationTime; // For motion interpolation + static F64Seconds sMaxUpdateInterpolationTime; // For motion interpolation + static F64Seconds sMaxRegionCrossingInterpolationTime; // For motion interpolation + + static bool sVelocityInterpolate; + static bool sPingInterpolate; + + bool mCachedOwnerInMuteList; + F64 mCachedMuteListUpdateTime; + + //-------------------------------------------------------------------- + // For objects that are attachments + //-------------------------------------------------------------------- +public: + const LLUUID &getAttachmentItemID() const; + void setAttachmentItemID(const LLUUID &id); + const LLUUID &extractAttachmentItemID(); // find&set the inventory item ID of the attached object + EObjectUpdateType getLastUpdateType() const; + void setLastUpdateType(EObjectUpdateType last_update_type); + bool getLastUpdateCached() const; + void setLastUpdateCached(bool last_update_cached); + + virtual void updateRiggingInfo() {} + + LLJointRiggingInfoTab mJointRiggingInfoTab; + +private: + LLUUID mAttachmentItemID; // ItemID of the associated object is in user inventory. + EObjectUpdateType mLastUpdateType; + bool mLastUpdateCached; + +public: + // reflection probe state + bool mIsReflectionProbe = false; // if true, this object should register itself with LLReflectionProbeManager + LLPointer mReflectionProbe = nullptr; // reflection probe coupled to this viewer object. If not null, should be deregistered when this object is destroyed + + // the amount of GPU time (in ms) it took to render this object according to LLPipeline::profileAvatar + // -1.f if no profile data available + F32 mGPURenderTime = -1.f; +}; + +/////////////////// +// +// Inlines +// +// + +inline void LLViewerObject::setRotation(const LLQuaternion& quat, bool damped) +{ + LLPrimitive::setRotation(quat); + setChanged(ROTATED | SILHOUETTE); + updateDrawable(damped); +} + +inline void LLViewerObject::setRotation(const F32 x, const F32 y, const F32 z, bool damped) +{ + LLPrimitive::setRotation(x, y, z); + setChanged(ROTATED | SILHOUETTE); + updateDrawable(damped); +} + +class LLViewerObjectMedia +{ +public: + LLViewerObjectMedia() : mMediaURL(), mPassedWhitelist(false), mMediaType(0) { } + + std::string mMediaURL; // for web pages on surfaces, one per prim + bool mPassedWhitelist; // user has OK'd display + U8 mMediaType; // see LLTextureEntry::WEB_PAGE, etc. +}; + +// subclass of viewer object that can be added to particle partitions +class LLAlphaObject : public LLViewerObject +{ +public: + LLAlphaObject(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) + : LLViewerObject(id,pcode,regionp) + { mDepth = 0.f; } + + virtual F32 getPartSize(S32 idx); + virtual void getGeometry(S32 idx, + LLStrider& verticesp, + LLStrider& normalsp, + LLStrider& texcoordsp, + LLStrider& colorsp, + LLStrider& emissivep, + LLStrider& indicesp) = 0; + + virtual void getBlendFunc(S32 face, LLRender::eBlendFactor& src, LLRender::eBlendFactor& dst); + + F32 mDepth; +}; + +class LLStaticViewerObject : public LLViewerObject +{ +public: + LLStaticViewerObject(const LLUUID& id, const LLPCode pcode, LLViewerRegion* regionp, bool is_global = false) + : LLViewerObject(id,pcode,regionp, is_global) + { } + + virtual void updateDrawable(bool force_damped); +}; + + +#endif diff --git a/indra/newview/llviewerobjectlist.cpp b/indra/newview/llviewerobjectlist.cpp index f68a3da6ea..2f8e71c5e9 100644 --- a/indra/newview/llviewerobjectlist.cpp +++ b/indra/newview/llviewerobjectlist.cpp @@ -1,2148 +1,2148 @@ -/** - * @file llviewerobjectlist.cpp - * @brief Implementation of LLViewerObjectList class. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llviewerobjectlist.h" - -#include "message.h" -#include "llfasttimer.h" -#include "llrender.h" -#include "llwindow.h" // decBusyCount() - -#include "llviewercontrol.h" -#include "llface.h" -#include "llvoavatar.h" -#include "llviewerobject.h" -#include "llviewerwindow.h" -#include "llnetmap.h" -#include "llagent.h" -#include "llagentcamera.h" -#include "pipeline.h" -#include "llspatialpartition.h" -#include "lltooltip.h" -#include "llworld.h" -#include "llstring.h" -#include "llhudicon.h" -#include "llhudnametag.h" -#include "lldrawable.h" -#include "llflexibleobject.h" -#include "llviewertextureanim.h" -#include "xform.h" -#include "llsky.h" -#include "llviewercamera.h" -#include "llselectmgr.h" -#include "llresmgr.h" -#include "llsdutil.h" -#include "llviewerregion.h" -#include "llviewerstats.h" -#include "llviewerstatsrecorder.h" -#include "llvovolume.h" -#include "llvoavatarself.h" -#include "lltoolmgr.h" -#include "lltoolpie.h" -#include "llkeyboard.h" -#include "u64.h" -#include "llviewertexturelist.h" -#include "lldatapacker.h" -#include "llcallstack.h" -#ifdef LL_USESYSTEMLIBS -#include -#else -#include "zlib-ng/zlib.h" -#endif -#include "object_flags.h" - -#include "llappviewer.h" -#include "llfloaterperms.h" -#include "llvocache.h" -#include "llcorehttputil.h" -#include "llstartup.h" - -#include -#include - -extern F32 gMinObjectDistance; -extern bool gAnimateTextures; - -#define MAX_CONCURRENT_PHYSICS_REQUESTS 256 - -void dialog_refresh_all(); - -// Global lists of objects - should go away soon. -LLViewerObjectList gObjectList; - -extern LLPipeline gPipeline; - -// Statics for object lookup tables. -U32 LLViewerObjectList::sSimulatorMachineIndex = 1; // Not zero deliberately, to speed up index check. -std::map LLViewerObjectList::sIPAndPortToIndex; -std::map LLViewerObjectList::sIndexAndLocalIDToUUID; - -LLViewerObjectList::LLViewerObjectList() -{ - mCurLazyUpdateIndex = 0; - mCurBin = 0; - mNumDeadObjects = 0; - mNumOrphans = 0; - mNumNewObjects = 0; - mWasPaused = false; - mNumDeadObjectUpdates = 0; - mNumUnknownUpdates = 0; -} - -LLViewerObjectList::~LLViewerObjectList() -{ - destroy(); -} - -void LLViewerObjectList::destroy() -{ - killAllObjects(); - - resetObjectBeacons(); - mActiveObjects.clear(); - mDeadObjects.clear(); - mMapObjects.clear(); - mUUIDObjectMap.clear(); -} - - -void LLViewerObjectList::getUUIDFromLocal(LLUUID &id, - const U32 local_id, - const U32 ip, - const U32 port) -{ - U64 ipport = (((U64)ip) << 32) | (U64)port; - - U32 index = sIPAndPortToIndex[ipport]; - - if (!index) - { - index = sSimulatorMachineIndex++; - sIPAndPortToIndex[ipport] = index; - } - - U64 indexid = (((U64)index) << 32) | (U64)local_id; - - id = get_if_there(sIndexAndLocalIDToUUID, indexid, LLUUID::null); -} - -U64 LLViewerObjectList::getIndex(const U32 local_id, - const U32 ip, - const U32 port) -{ - U64 ipport = (((U64)ip) << 32) | (U64)port; - - U32 index = sIPAndPortToIndex[ipport]; - - if (!index) - { - return 0; - } - - return (((U64)index) << 32) | (U64)local_id; -} - -bool LLViewerObjectList::removeFromLocalIDTable(const LLViewerObject* objectp) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; - - if(objectp && objectp->getRegion()) - { - U32 local_id = objectp->mLocalID; - U32 ip = objectp->getRegion()->getHost().getAddress(); - U32 port = objectp->getRegion()->getHost().getPort(); - U64 ipport = (((U64)ip) << 32) | (U64)port; - U32 index = sIPAndPortToIndex[ipport]; - - // LL_INFOS() << "Removing object from table, local ID " << local_id << ", ip " << ip << ":" << port << LL_ENDL; - - U64 indexid = (((U64)index) << 32) | (U64)local_id; - - std::map::iterator iter = sIndexAndLocalIDToUUID.find(indexid); - if (iter == sIndexAndLocalIDToUUID.end()) - { - return false; - } - - // Found existing entry - if (iter->second == objectp->getID()) - { // Full UUIDs match, so remove the entry - sIndexAndLocalIDToUUID.erase(iter); - return true; - } - // UUIDs did not match - this would zap a valid entry, so don't erase it - //LL_INFOS() << "Tried to erase entry where id in table (" - // << iter->second << ") did not match object " << object.getID() << LL_ENDL; - } - - return false ; -} - -void LLViewerObjectList::setUUIDAndLocal(const LLUUID &id, - const U32 local_id, - const U32 ip, - const U32 port) -{ - U64 ipport = (((U64)ip) << 32) | (U64)port; - - U32 index = sIPAndPortToIndex[ipport]; - - if (!index) - { - index = sSimulatorMachineIndex++; - sIPAndPortToIndex[ipport] = index; - } - - U64 indexid = (((U64)index) << 32) | (U64)local_id; - - sIndexAndLocalIDToUUID[indexid] = id; - - //LL_INFOS() << "Adding object to table, full ID " << id - // << ", local ID " << local_id << ", ip " << ip << ":" << port << LL_ENDL; -} - -S32 gFullObjectUpdates = 0; -S32 gTerseObjectUpdates = 0; - -void LLViewerObjectList::processUpdateCore(LLViewerObject* objectp, - void** user_data, - U32 i, - const EObjectUpdateType update_type, - LLDataPacker* dpp, - bool just_created, - bool from_cache) -{ - LLMessageSystem* msg = NULL; - - if(!from_cache) - { - msg = gMessageSystem; - } - - // ignore returned flags - LL_DEBUGS("ObjectUpdate") << "uuid " << objectp->mID << " calling processUpdateMessage " - << objectp << " just_created " << just_created << " from_cache " << from_cache << " msg " << msg << LL_ENDL; - dumpStack("ObjectUpdateStack"); - - objectp->processUpdateMessage(msg, user_data, i, update_type, dpp); - - if (objectp->isDead()) - { - // The update failed - return; - } - - updateActive(objectp); - - if (just_created) - { - gPipeline.addObject(objectp); - } - - // Also sets the approx. pixel area - objectp->setPixelAreaAndAngle(gAgent); - - // RN: this must be called after we have a drawable - // (from gPipeline.addObject) - // so that the drawable parent is set properly - if(msg != NULL) - { - findOrphans(objectp, msg->getSenderIP(), msg->getSenderPort()); - } - else - { - LLViewerRegion* regionp = objectp->getRegion(); - if(regionp != NULL) - { - findOrphans(objectp, regionp->getHost().getAddress(), regionp->getHost().getPort()); - } - } - - // If we're just wandering around, don't create new objects selected. - if (just_created - && update_type != OUT_TERSE_IMPROVED - && objectp->mCreateSelected) - { - if ( LLToolMgr::getInstance()->getCurrentTool() != LLToolPie::getInstance() ) - { - // LL_INFOS() << "DEBUG selecting " << objectp->mID << " " - // << objectp->mLocalID << LL_ENDL; - LLSelectMgr::getInstance()->selectObjectAndFamily(objectp); - dialog_refresh_all(); - } - - objectp->mCreateSelected = false; - gViewerWindow->getWindow()->decBusyCount(); - gViewerWindow->setCursor( UI_CURSOR_ARROW ); - } -} - -static LLTrace::BlockTimerStatHandle FTM_PROCESS_OBJECTS("Process Objects"); - -LLViewerObject* LLViewerObjectList::processObjectUpdateFromCache(LLVOCacheEntry* entry, LLViewerRegion* regionp) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; - - LLDataPacker *cached_dpp = entry->getDP(); - - if (!cached_dpp || gNonInteractive) - { - return NULL; //nothing cached. - } - - LLViewerObject *objectp; - U32 local_id; - LLPCode pcode = 0; - LLUUID fullid; - LLViewerStatsRecorder& recorder = LLViewerStatsRecorder::instance(); - - // Cache Hit. - record(LLStatViewer::OBJECT_CACHE_HIT_RATE, LLUnits::Ratio::fromValue(1)); - - cached_dpp->reset(); - cached_dpp->unpackUUID(fullid, "ID"); - cached_dpp->unpackU32(local_id, "LocalID"); - cached_dpp->unpackU8(pcode, "PCode"); - - objectp = findObject(fullid); - - if (objectp) - { - if(!objectp->isDead() && (objectp->mLocalID != entry->getLocalID() || - objectp->getRegion() != regionp)) - { - removeFromLocalIDTable(objectp); - setUUIDAndLocal(fullid, entry->getLocalID(), - regionp->getHost().getAddress(), - regionp->getHost().getPort()); - - if (objectp->mLocalID != entry->getLocalID()) - { // Update local ID in object with the one sent from the region - objectp->mLocalID = entry->getLocalID(); - } - - if (objectp->getRegion() != regionp) - { // Object changed region, so update it - objectp->updateRegion(regionp); // for LLVOAvatar - } - } - else - { - //should fall through if already loaded because may need to update the object. - //return objectp; //already loaded. - } - } - - bool justCreated = false; - if (!objectp) - { - objectp = createObjectFromCache(pcode, regionp, fullid, entry->getLocalID()); - - LL_DEBUGS("ObjectUpdate") << "uuid " << fullid << " created objectp " << objectp << LL_ENDL; - dumpStack("ObjectUpdateStack"); - - if (!objectp) - { - LL_INFOS() << "createObject failure for object: " << fullid << LL_ENDL; - recorder.objectUpdateFailure(); - return NULL; - } - justCreated = true; - mNumNewObjects++; - } - - if (objectp->isDead()) - { - LL_WARNS() << "Dead object " << objectp->mID << " in UUID map 1!" << LL_ENDL; - } - - processUpdateCore(objectp, NULL, 0, OUT_FULL_CACHED, cached_dpp, justCreated, true); - objectp->loadFlags(entry->getUpdateFlags()); //just in case, reload update flags from cache. - - if(entry->getHitCount() > 0) - { - objectp->setLastUpdateType(OUT_FULL_CACHED); - } - else - { - objectp->setLastUpdateType(OUT_FULL_COMPRESSED); //newly cached - objectp->setLastUpdateCached(true); - } - LLVOAvatar::cullAvatarsByPixelArea(); - - return objectp; -} - -void LLViewerObjectList::processObjectUpdate(LLMessageSystem *mesgsys, - void **user_data, - const EObjectUpdateType update_type, - bool compressed) -{ - LL_RECORD_BLOCK_TIME(FTM_PROCESS_OBJECTS); - - LLViewerObject *objectp; - S32 num_objects; - U32 local_id; - LLPCode pcode = 0; - LLUUID fullid; - S32 i; - - // figure out which simulator these are from and get it's index - // Coordinates in simulators are region-local - // Until we get region-locality working on viewer we - // have to transform to absolute coordinates. - num_objects = mesgsys->getNumberOfBlocksFast(_PREHASH_ObjectData); - - // I don't think this case is ever hit. TODO* Test this. - if (!compressed && update_type != OUT_FULL) - { - //LL_INFOS() << "TEST: !cached && !compressed && update_type != OUT_FULL" << LL_ENDL; - gTerseObjectUpdates += num_objects; - /* - S32 size; - if (mesgsys->getReceiveCompressedSize()) - { - size = mesgsys->getReceiveCompressedSize(); - } - else - { - size = mesgsys->getReceiveSize(); - } - LL_INFOS() << "Received terse " << num_objects << " in " << size << " byte (" << size/num_objects << ")" << LL_ENDL; - */ - } - else - { - /* - S32 size; - if (mesgsys->getReceiveCompressedSize()) - { - size = mesgsys->getReceiveCompressedSize(); - } - else - { - size = mesgsys->getReceiveSize(); - } - - LL_INFOS() << "Received " << num_objects << " in " << size << " byte (" << size/num_objects << ")" << LL_ENDL; - */ - gFullObjectUpdates += num_objects; - } - - U64 region_handle; - mesgsys->getU64Fast(_PREHASH_RegionData, _PREHASH_RegionHandle, region_handle); - - LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle(region_handle); - - if (!regionp) - { - LL_WARNS() << "Object update from unknown region! " << region_handle << LL_ENDL; - return; - } - - U8 compressed_dpbuffer[2048]; - LLDataPackerBinaryBuffer compressed_dp(compressed_dpbuffer, 2048); - LLViewerStatsRecorder& recorder = LLViewerStatsRecorder::instance(); - - for (i = 0; i < num_objects; i++) - { - bool justCreated = false; - bool update_cache = false; //update object cache if it is a full-update or terse update - - if (compressed) - { - compressed_dp.reset(); - - S32 uncompressed_length = mesgsys->getSizeFast(_PREHASH_ObjectData, i, _PREHASH_Data); - LL_DEBUGS("ObjectUpdate") << "got binary data from message to compressed_dpbuffer" << LL_ENDL; - mesgsys->getBinaryDataFast(_PREHASH_ObjectData, _PREHASH_Data, compressed_dpbuffer, 0, i, 2048); - compressed_dp.assignBuffer(compressed_dpbuffer, uncompressed_length); - - if (update_type != OUT_TERSE_IMPROVED) // OUT_FULL_COMPRESSED only? - { - U32 flags = 0; - mesgsys->getU32Fast(_PREHASH_ObjectData, _PREHASH_UpdateFlags, flags, i); - - compressed_dp.unpackUUID(fullid, "ID"); - compressed_dp.unpackU32(local_id, "LocalID"); - compressed_dp.unpackU8(pcode, "PCode"); - - if (pcode == 0) - { - // object creation will fail, LLViewerObject::createObject() - LL_WARNS() << "Received object " << fullid - << " with 0 PCode. Local id: " << local_id - << " Flags: " << flags - << " Region: " << regionp->getName() - << " Region id: " << regionp->getRegionID() << LL_ENDL; - recorder.objectUpdateFailure(); - continue; - } - else if ((flags & FLAGS_TEMPORARY_ON_REZ) == 0) - { - //send to object cache - regionp->cacheFullUpdate(compressed_dp, flags); - continue; - } - } - else //OUT_TERSE_IMPROVED - { - update_cache = true; - compressed_dp.unpackU32(local_id, "LocalID"); - getUUIDFromLocal(fullid, - local_id, - gMessageSystem->getSenderIP(), - gMessageSystem->getSenderPort()); - if (fullid.isNull()) - { - LL_DEBUGS() << "update for unknown localid " << local_id << " host " << gMessageSystem->getSender() << ":" << gMessageSystem->getSenderPort() << LL_ENDL; - mNumUnknownUpdates++; - } - } - } - else if (update_type != OUT_FULL) // !compressed, !OUT_FULL ==> OUT_FULL_CACHED only? - { - mesgsys->getU32Fast(_PREHASH_ObjectData, _PREHASH_ID, local_id, i); - - getUUIDFromLocal(fullid, - local_id, - gMessageSystem->getSenderIP(), - gMessageSystem->getSenderPort()); - if (fullid.isNull()) - { - // LL_WARNS() << "update for unknown localid " << local_id << " host " << gMessageSystem->getSender() << LL_ENDL; - mNumUnknownUpdates++; - } - else - { - LL_DEBUGS("ObjectUpdate") << "Non-full, non-compressed update, obj " << local_id << ", global ID " << fullid << " from " << mesgsys->getSender() << LL_ENDL; - } - } - else // OUT_FULL only? - { - update_cache = true; - mesgsys->getUUIDFast(_PREHASH_ObjectData, _PREHASH_FullID, fullid, i); - mesgsys->getU32Fast(_PREHASH_ObjectData, _PREHASH_ID, local_id, i); - LL_DEBUGS("ObjectUpdate") << "Full Update, obj " << local_id << ", global ID " << fullid << " from " << mesgsys->getSender() << LL_ENDL; - } - objectp = findObject(fullid); - - if (compressed) - { - LL_DEBUGS("ObjectUpdate") << "uuid " << fullid << " received compressed data from message (earlier in function)" << LL_ENDL; - } - LL_DEBUGS("ObjectUpdate") << "uuid " << fullid << " objectp " << objectp - << " update_cache " << (S32) update_cache << " compressed " << compressed - << " update_type " << update_type << LL_ENDL; - dumpStack("ObjectUpdateStack"); - - if(update_cache) - { - //update object cache if the object receives a full-update or terse update - objectp = regionp->updateCacheEntry(local_id, objectp); - } - - // This looks like it will break if the local_id of the object doesn't change - // upon boundary crossing, but we check for region id matching later... - // Reset object local id and region pointer if things have changed - if (objectp && - ((objectp->mLocalID != local_id) || - (objectp->getRegion() != regionp))) - { - //if (objectp->getRegion()) - //{ - // LL_INFOS() << "Local ID change: Removing object from table, local ID " << objectp->mLocalID - // << ", id from message " << local_id << ", from " - // << LLHost(objectp->getRegion()->getHost().getAddress(), objectp->getRegion()->getHost().getPort()) - // << ", full id " << fullid - // << ", objects id " << objectp->getID() - // << ", regionp " << (U32) regionp << ", object region " << (U32) objectp->getRegion() - // << LL_ENDL; - //} - removeFromLocalIDTable(objectp); - setUUIDAndLocal(fullid, - local_id, - gMessageSystem->getSenderIP(), - gMessageSystem->getSenderPort()); - - if (objectp->mLocalID != local_id) - { // Update local ID in object with the one sent from the region - objectp->mLocalID = local_id; - } - - if (objectp->getRegion() != regionp) - { // Object changed region, so update it - objectp->updateRegion(regionp); // for LLVOAvatar - } - } - - if (!objectp) - { - if (compressed) - { - if (update_type == OUT_TERSE_IMPROVED) - { - // LL_INFOS() << "terse update for an unknown object (compressed):" << fullid << LL_ENDL; - recorder.objectUpdateFailure(); - continue; - } - } - else - { - if (update_type != OUT_FULL) - { - //LL_INFOS() << "terse update for an unknown object:" << fullid << LL_ENDL; - recorder.objectUpdateFailure(); - continue; - } - - mesgsys->getU8Fast(_PREHASH_ObjectData, _PREHASH_PCode, pcode, i); - - } -#ifdef IGNORE_DEAD - if (mDeadObjects.find(fullid) != mDeadObjects.end()) - { - mNumDeadObjectUpdates++; - //LL_INFOS() << "update for a dead object:" << fullid << LL_ENDL; - recorder.objectUpdateFailure(); - continue; - } -#endif - - objectp = createObject(pcode, regionp, fullid, local_id, gMessageSystem->getSender()); - - LL_DEBUGS("ObjectUpdate") << "creating object " << fullid << " result " << objectp << LL_ENDL; - dumpStack("ObjectUpdateStack"); - - if (!objectp) - { - LL_INFOS() << "createObject failure for object: " << fullid << LL_ENDL; - recorder.objectUpdateFailure(); - continue; - } - - justCreated = true; - mNumNewObjects++; - } - - if (objectp->isDead()) - { - LL_WARNS() << "Dead object " << objectp->mID << " in UUID map 1!" << LL_ENDL; - } - - //bool bCached = false; - if (compressed) - { - if (update_type != OUT_TERSE_IMPROVED) // OUT_FULL_COMPRESSED only? - { - objectp->mLocalID = local_id; - } - processUpdateCore(objectp, user_data, i, update_type, &compressed_dp, justCreated); - -#if 0 - if (update_type != OUT_TERSE_IMPROVED) // OUT_FULL_COMPRESSED only? - { - U32 flags = 0; - mesgsys->getU32Fast(_PREHASH_ObjectData, _PREHASH_UpdateFlags, flags, i); - - if(!(flags & FLAGS_TEMPORARY_ON_REZ)) - { - bCached = true; - LLViewerRegion::eCacheUpdateResult result = objectp->mRegionp->cacheFullUpdate(objectp, compressed_dp, flags); - recorder.cacheFullUpdate(result); - } - } -#endif - } - else - { - if (update_type == OUT_FULL) - { - objectp->mLocalID = local_id; - } - processUpdateCore(objectp, user_data, i, update_type, NULL, justCreated); - } - recorder.objectUpdateEvent(update_type); - objectp->setLastUpdateType(update_type); - } - - LLVOAvatar::cullAvatarsByPixelArea(); -} - -void LLViewerObjectList::processCompressedObjectUpdate(LLMessageSystem *mesgsys, - void **user_data, - const EObjectUpdateType update_type) -{ - processObjectUpdate(mesgsys, user_data, update_type, true); -} - -void LLViewerObjectList::processCachedObjectUpdate(LLMessageSystem *mesgsys, - void **user_data, - const EObjectUpdateType update_type) -{ - //processObjectUpdate(mesgsys, user_data, update_type, true, false); - - S32 num_objects = mesgsys->getNumberOfBlocksFast(_PREHASH_ObjectData); - gFullObjectUpdates += num_objects; - - U64 region_handle; - mesgsys->getU64Fast(_PREHASH_RegionData, _PREHASH_RegionHandle, region_handle); - LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle(region_handle); - if (!regionp) - { - LL_WARNS() << "Object update from unknown region! " << region_handle << LL_ENDL; - return; - } - - LLViewerStatsRecorder& recorder = LLViewerStatsRecorder::instance(); - - for (S32 i = 0; i < num_objects; i++) - { - U32 id; - U32 crc; - U32 flags; - mesgsys->getU32Fast(_PREHASH_ObjectData, _PREHASH_ID, id, i); - mesgsys->getU32Fast(_PREHASH_ObjectData, _PREHASH_CRC, crc, i); - mesgsys->getU32Fast(_PREHASH_ObjectData, _PREHASH_UpdateFlags, flags, i); - - LL_DEBUGS("ObjectUpdate") << "got probe for id " << id << " crc " << crc << LL_ENDL; - dumpStack("ObjectUpdateStack"); - - // Lookup data packer and add this id to cache miss lists if necessary. - U8 cache_miss_type = LLViewerRegion::CACHE_MISS_TYPE_NONE; - if (regionp->probeCache(id, crc, flags, cache_miss_type)) - { // Cache Hit - recorder.cacheHitEvent(); - } - else - { // Cache Miss - LL_DEBUGS("ObjectUpdate") << "cache miss for id " << id << " crc " << crc << " miss type " << (S32) cache_miss_type << LL_ENDL; - recorder.cacheMissEvent(cache_miss_type); - } - } - - return; -} - -void LLViewerObjectList::dirtyAllObjectInventory() -{ - for (vobj_list_t::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter) - { - (*iter)->dirtyInventory(); - } -} - -void LLViewerObjectList::updateApparentAngles(LLAgent &agent) -{ - S32 i; - LLViewerObject *objectp; - - S32 num_updates, max_value; - if (NUM_BINS - 1 == mCurBin) - { - // Remainder (mObjects.size() could have changed) - num_updates = (S32) mObjects.size() - mCurLazyUpdateIndex; - max_value = (S32) mObjects.size(); - } - else - { - num_updates = ((S32) mObjects.size() / NUM_BINS) + 1; - max_value = llmin((S32) mObjects.size(), mCurLazyUpdateIndex + num_updates); - } - - // Iterate through some of the objects and lazy update their texture priorities - for (i = mCurLazyUpdateIndex; i < max_value; i++) - { - objectp = mObjects[i]; - if (!objectp->isDead()) - { - // Update distance & gpw - objectp->setPixelAreaAndAngle(agent); // Also sets the approx. pixel area - objectp->updateTextures(); // Update the image levels of textures for this object. - } - } - - mCurLazyUpdateIndex = max_value; - if (mCurLazyUpdateIndex == mObjects.size()) - { - // restart - mCurLazyUpdateIndex = 0; - mCurBin = 0; // keep in sync with index (mObjects.size() could have changed) - } - else - { - mCurBin = (mCurBin + 1) % NUM_BINS; - } - -#if 0 - // Slam priorities for textures that we care about (hovered, selected, and focused) - // Hovered - // Assumes only one level deep of parenting - LLSelectNode* nodep = LLSelectMgr::instance().getHoverNode(); - if (nodep) - { - objectp = nodep->getObject(); - if (objectp) - { - objectp->boostTexturePriority(); - } - } - - // Focused - objectp = gAgentCamera.getFocusObject(); - if (objectp) - { - objectp->boostTexturePriority(); - } -#endif - - // Selected - struct f : public LLSelectedObjectFunctor - { - virtual bool apply(LLViewerObject* objectp) - { - if (objectp) - { - objectp->boostTexturePriority(); - } - return true; - } - } func; - LLSelectMgr::getInstance()->getSelection()->applyToRootObjects(&func); - - LLVOAvatar::cullAvatarsByPixelArea(); -} - -void LLViewerObjectList::update(LLAgent &agent) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; - - // Update globals - LLViewerObject::setVelocityInterpolate( gSavedSettings.getBOOL("VelocityInterpolate") ); - LLViewerObject::setPingInterpolate( gSavedSettings.getBOOL("PingInterpolate") ); - - F32 interp_time = gSavedSettings.getF32("InterpolationTime"); - F32 phase_out_time = gSavedSettings.getF32("InterpolationPhaseOut"); - F32 region_interp_time = llclamp(gSavedSettings.getF32("RegionCrossingInterpolationTime"), 0.5f, 5.f); - if (interp_time < 0.0 || - phase_out_time < 0.0 || - phase_out_time > interp_time) - { - LL_WARNS() << "Invalid values for InterpolationTime or InterpolationPhaseOut, resetting to defaults" << LL_ENDL; - interp_time = 3.0f; - phase_out_time = 1.0f; - } - LLViewerObject::setPhaseOutUpdateInterpolationTime( interp_time ); - LLViewerObject::setMaxUpdateInterpolationTime( phase_out_time ); - LLViewerObject::setMaxRegionCrossingInterpolationTime(region_interp_time); - - gAnimateTextures = gSavedSettings.getBOOL("AnimateTextures"); - - // update global timer - F32 last_time = gFrameTimeSeconds; - U64Microseconds time = totalTime(); // this will become the new gFrameTime when the update is done - // Time _can_ go backwards, for example if the user changes the system clock. - // It doesn't cause any fatal problems (just some oddness with stats), so we shouldn't assert here. -// llassert(time > gFrameTime); - F64Seconds time_diff = time - gFrameTime; - gFrameTime = time; - F64Seconds time_since_start = gFrameTime - gStartTime; - gFrameTimeSeconds = time_since_start; - - gFrameIntervalSeconds = gFrameTimeSeconds - last_time; - if (gFrameIntervalSeconds < 0.f) - { - gFrameIntervalSeconds = 0.f; - } - - //clear avatar LOD change counter - LLVOAvatar::sNumLODChangesThisFrame = 0; - - const F64 frame_time = LLFrameTimer::getElapsedSeconds(); - - LLViewerObject *objectp = NULL; - - // Make a copy of the list in case something in idleUpdate() messes with it - static std::vector idle_list; - - U32 idle_count = 0; - - { - for (std::vector >::iterator active_iter = mActiveObjects.begin(); - active_iter != mActiveObjects.end(); active_iter++) - { - objectp = *active_iter; - if (objectp) - { - if (idle_count >= idle_list.size()) - { - idle_list.push_back( objectp ); - } - else - { - idle_list[idle_count] = objectp; - } - ++idle_count; - } - else - { // There shouldn't be any NULL pointers in the list, but they have caused - // crashes before. This may be idleUpdate() messing with the list. - LL_WARNS() << "LLViewerObjectList::update has a NULL objectp" << LL_ENDL; - } - } - } - - std::vector::iterator idle_end = idle_list.begin()+idle_count; - - if (gSavedSettings.getBOOL("FreezeTime")) - { - - for (std::vector::iterator iter = idle_list.begin(); - iter != idle_end; iter++) - { - objectp = *iter; - if (objectp->isAvatar()) - { - objectp->idleUpdate(agent, frame_time); - } - } - } - else - { - for (std::vector::iterator idle_iter = idle_list.begin(); - idle_iter != idle_end; idle_iter++) - { - objectp = *idle_iter; - llassert(objectp->isActive()); - objectp->idleUpdate(agent, frame_time); - } - - //update flexible objects - LLVolumeImplFlexible::updateClass(); - - //update animated textures - if (gAnimateTextures) - { - LLViewerTextureAnim::updateClass(); - } - } - - - - fetchObjectCosts(); - fetchPhysicsFlags(); - - // update max computed render cost - LLVOVolume::updateRenderComplexity(); - - // compute all sorts of time-based stats - // don't factor frames that were paused into the stats - if (! mWasPaused) - { - LLViewerStats::getInstance()->updateFrameStats(time_diff); - } - - /* - // Debugging code for viewing orphans, and orphaned parents - LLUUID id; - for (i = 0; i < mOrphanParents.size(); i++) - { - id = sIndexAndLocalIDToUUID[mOrphanParents[i]]; - LLViewerObject *objectp = findObject(id); - if (objectp) - { - std::string id_str; - objectp->mID.toString(id_str); - std::string tmpstr = std::string("Par: ") + id_str; - addDebugBeacon(objectp->getPositionAgent(), - tmpstr, - LLColor4(1.f,0.f,0.f,1.f), - LLColor4(1.f,1.f,1.f,1.f)); - } - } - - LLColor4 text_color; - for (i = 0; i < mOrphanChildren.size(); i++) - { - OrphanInfo oi = mOrphanChildren[i]; - LLViewerObject *objectp = findObject(oi.mChildInfo); - if (objectp) - { - std::string id_str; - objectp->mID.toString(id_str); - std::string tmpstr; - if (objectp->getParent()) - { - tmpstr = std::string("ChP: ") + id_str; - text_color = LLColor4(0.f, 1.f, 0.f, 1.f); - } - else - { - tmpstr = std::string("ChNoP: ") + id_str; - text_color = LLColor4(1.f, 0.f, 0.f, 1.f); - } - id = sIndexAndLocalIDToUUID[oi.mParentInfo]; - addDebugBeacon(objectp->getPositionAgent() + LLVector3(0.f, 0.f, -0.25f), - tmpstr, - LLColor4(0.25f,0.25f,0.25f,1.f), - text_color); - } - i++; - } - */ - - sample(LLStatViewer::NUM_OBJECTS, mObjects.size()); - sample(LLStatViewer::NUM_ACTIVE_OBJECTS, idle_count); -} - -void LLViewerObjectList::fetchObjectCosts() -{ - // issue http request for stale object physics costs - if (!mStaleObjectCost.empty()) - { - LLViewerRegion* regionp = gAgent.getRegion(); - - if (regionp) - { - std::string url = regionp->getCapability("GetObjectCost"); - - if (!url.empty()) - { - LLCoros::instance().launch("LLViewerObjectList::fetchObjectCostsCoro", - boost::bind(&LLViewerObjectList::fetchObjectCostsCoro, this, url)); - } - else - { - mStaleObjectCost.clear(); - mPendingObjectCost.clear(); - } - } - } -} - -/*static*/ -void LLViewerObjectList::reportObjectCostFailure(LLSD &objectList) -{ - // TODO*: No more hard coding - for (LLSD::array_iterator it = objectList.beginArray(); it != objectList.endArray(); ++it) - { - gObjectList.onObjectCostFetchFailure(it->asUUID()); - } -} - - -void LLViewerObjectList::fetchObjectCostsCoro(std::string url) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("genericPostCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - - - uuid_set_t diff; - - std::set_difference(mStaleObjectCost.begin(), mStaleObjectCost.end(), - mPendingObjectCost.begin(), mPendingObjectCost.end(), - std::inserter(diff, diff.begin())); - - mStaleObjectCost.clear(); - - if (diff.empty()) - { - LL_INFOS() << "No outstanding object IDs to request. Pending count: " << mPendingObjectCost.size() << LL_ENDL; - return; - } - - LLSD idList(LLSD::emptyArray()); - - for (uuid_set_t::iterator it = diff.begin(); it != diff.end(); ++it) - { - idList.append(*it); - } - - mPendingObjectCost.insert(diff.begin(), diff.end()); - - LLSD postData = LLSD::emptyMap(); - - postData["object_ids"] = idList; - - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status || result.has("error")) - { - if (result.has("error")) - { - LL_WARNS() << "Application level error when fetching object " - << "cost. Message: " << result["error"]["message"].asString() - << ", identifier: " << result["error"]["identifier"].asString() - << LL_ENDL; - - // TODO*: Adaptively adjust request size if the - // service says we've requested too many and retry - } - reportObjectCostFailure(idList); - - return; - } - - // Success, grab the resource cost and linked set costs - // for an object if one was returned - for (LLSD::array_iterator it = idList.beginArray(); it != idList.endArray(); ++it) - { - LLUUID objectId = it->asUUID(); - - // Object could have been added to the mStaleObjectCost after request started - mStaleObjectCost.erase(objectId); - mPendingObjectCost.erase(objectId); - - // Check to see if the request contains data for the object - if (result.has(it->asString())) - { - LLSD objectData = result[it->asString()]; - - F32 linkCost = objectData["linked_set_resource_cost"].asReal(); - F32 objectCost = objectData["resource_cost"].asReal(); - F32 physicsCost = objectData["physics_cost"].asReal(); - F32 linkPhysicsCost = objectData["linked_set_physics_cost"].asReal(); - - gObjectList.updateObjectCost(objectId, objectCost, linkCost, physicsCost, linkPhysicsCost); - } - else - { - // TODO*: Give user feedback about the missing data? - gObjectList.onObjectCostFetchFailure(objectId); - } - } - -} - -void LLViewerObjectList::fetchPhysicsFlags() -{ - // issue http request for stale object physics flags - if (!mStalePhysicsFlags.empty()) - { - LLViewerRegion* regionp = gAgent.getRegion(); - - if (regionp) - { - std::string url = regionp->getCapability("GetObjectPhysicsData"); - - if (!url.empty()) - { - LLCoros::instance().launch("LLViewerObjectList::fetchPhisicsFlagsCoro", - boost::bind(&LLViewerObjectList::fetchPhisicsFlagsCoro, this, url)); - } - else - { - mStalePhysicsFlags.clear(); - mPendingPhysicsFlags.clear(); - } - } - } -} - -/*static*/ -void LLViewerObjectList::reportPhysicsFlagFailure(LLSD &objectList) -{ - // TODO*: No more hard coding - for (LLSD::array_iterator it = objectList.beginArray(); it != objectList.endArray(); ++it) - { - gObjectList.onPhysicsFlagsFetchFailure(it->asUUID()); - } -} - -void LLViewerObjectList::fetchPhisicsFlagsCoro(std::string url) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("genericPostCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD idList; - U32 objectIndex = 0; - - for (uuid_set_t::iterator it = mStalePhysicsFlags.begin(); it != mStalePhysicsFlags.end();) - { - // Check to see if a request for this object - // has already been made. - if (mPendingPhysicsFlags.find(*it) == mPendingPhysicsFlags.end()) - { - mPendingPhysicsFlags.insert(*it); - idList[objectIndex++] = *it; - } - - mStalePhysicsFlags.erase(it++); - - if (objectIndex >= MAX_CONCURRENT_PHYSICS_REQUESTS) - { - break; - } - } - - if (idList.size() < 1) - { - LL_INFOS() << "No outstanding object physics flags to request." << LL_ENDL; - return; - } - - LLSD postData = LLSD::emptyMap(); - - postData["object_ids"] = idList; - - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status || result.has("error")) - { - if (result.has("error")) - { - LL_WARNS() << "Application level error when fetching object " - << "physics flags. Message: " << result["error"]["message"].asString() - << ", identifier: " << result["error"]["identifier"].asString() - << LL_ENDL; - - // TODO*: Adaptively adjust request size if the - // service says we've requested too many and retry - } - reportPhysicsFlagFailure(idList); - - return; - } - - // Success, grab the resource cost and linked set costs - // for an object if one was returned - for (LLSD::array_iterator it = idList.beginArray(); it != idList.endArray(); ++it) - { - LLUUID objectId = it->asUUID(); - - // Check to see if the request contains data for the object - if (result.has(it->asString())) - { - const LLSD& data = result[it->asString()]; - - S32 shapeType = data["PhysicsShapeType"].asInteger(); - - gObjectList.updatePhysicsShapeType(objectId, shapeType); - - if (data.has("Density")) - { - F32 density = data["Density"].asReal(); - F32 friction = data["Friction"].asReal(); - F32 restitution = data["Restitution"].asReal(); - F32 gravityMult = data["GravityMultiplier"].asReal(); - - gObjectList.updatePhysicsProperties(objectId, density, - friction, restitution, gravityMult); - } - } - else - { - // TODO*: Give user feedback about the missing data? - gObjectList.onPhysicsFlagsFetchFailure(objectId); - } - } -} - -void LLViewerObjectList::clearDebugText() -{ - for (vobj_list_t::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter) - { - (*iter)->restoreHudText(); - } -} - - -void LLViewerObjectList::cleanupReferences(LLViewerObject *objectp) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; - - bool new_dead_object = true; - if (mDeadObjects.find(objectp->mID) != mDeadObjects.end()) - { - LL_INFOS() << "Object " << objectp->mID << " already on dead list!" << LL_ENDL; - new_dead_object = false; - } - else - { - mDeadObjects.insert(objectp->mID); - } - - // Cleanup any references we have to this object - // Remove from object map so noone can look it up. - - LL_DEBUGS("ObjectUpdate") << " dereferencing id " << objectp->mID << LL_ENDL; - dumpStack("ObjectUpdateStack"); - - mUUIDObjectMap.erase(objectp->mID); - - //if (objectp->getRegion()) - //{ - // LL_INFOS() << "cleanupReferences removing object from table, local ID " << objectp->mLocalID << ", ip " - // << objectp->getRegion()->getHost().getAddress() << ":" - // << objectp->getRegion()->getHost().getPort() << LL_ENDL; - //} - - removeFromLocalIDTable(objectp); - - if (objectp->onActiveList()) - { - //LL_INFOS() << "Removing " << objectp->mID << " " << objectp->getPCodeString() << " from active list in cleanupReferences." << LL_ENDL; - objectp->setOnActiveList(false); - removeFromActiveList(objectp); - } - - if (objectp->isOnMap()) - { - removeFromMap(objectp); - } - - // Don't clean up mObject references, these will be cleaned up more efficiently later! - - if(new_dead_object) - { - mNumDeadObjects++; - } -} - -bool LLViewerObjectList::killObject(LLViewerObject *objectp) -{ - LL_PROFILE_ZONE_SCOPED; - // Don't ever kill gAgentAvatarp, just force it to the agent's region - // unless region is NULL which is assumed to mean you are logging out. - if ((objectp == gAgentAvatarp) && gAgent.getRegion()) - { - objectp->setRegion(gAgent.getRegion()); - return false; - } - - // When we're killing objects, all we do is mark them as dead. - // We clean up the dead objects later. - - if (objectp) - { - // We are going to cleanup a lot of smart pointers to this object, they might be last, - // and object being NULLed while inside it's own function won't be pretty - // so create a pointer to make sure object will stay alive untill markDead() finishes - LLPointer sp(objectp); - sp->markDead(); // does the right thing if object already dead - return true; - } - - return false; -} - -void LLViewerObjectList::killObjects(LLViewerRegion *regionp) -{ - LL_PROFILE_ZONE_SCOPED; - LLViewerObject *objectp; - - - for (vobj_list_t::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter) - { - objectp = *iter; - - if (objectp->mRegionp == regionp) - { - killObject(objectp); - } - } - - // Have to clean right away because the region is becoming invalid. - cleanDeadObjects(false); -} - -void LLViewerObjectList::killAllObjects() -{ - // Used only on global destruction. - LLViewerObject *objectp; - - for (vobj_list_t::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter) - { - objectp = *iter; - killObject(objectp); - // Object must be dead, or it's the LLVOAvatarSelf which never dies. - llassert((objectp == gAgentAvatarp) || objectp->isDead()); - } - - cleanDeadObjects(false); - - if(!mObjects.empty()) - { - LL_WARNS() << "LLViewerObjectList::killAllObjects still has entries in mObjects: " << mObjects.size() << LL_ENDL; - mObjects.clear(); - } - - if (!mActiveObjects.empty()) - { - LL_WARNS() << "Some objects still on active object list!" << LL_ENDL; - mActiveObjects.clear(); - } - - if (!mMapObjects.empty()) - { - LL_WARNS() << "Some objects still on map object list!" << LL_ENDL; - mMapObjects.clear(); - } -} - -void LLViewerObjectList::cleanDeadObjects(bool use_timer) -{ - if (!mNumDeadObjects) - { - // No dead objects, don't need to scan object list. - return; - } - - LL_PROFILE_ZONE_SCOPED; - - S32 num_removed = 0; - LLViewerObject *objectp; - - vobj_list_t::reverse_iterator target = mObjects.rbegin(); - - vobj_list_t::iterator iter = mObjects.begin(); - for ( ; iter != mObjects.end(); ) - { - // Scan for all of the dead objects and put them all on the end of the list with no ref count ops - objectp = *iter; - if (objectp == NULL) - { //we caught up to the dead tail - break; - } - - if (objectp->isDead()) - { - LLPointer::swap(*iter, *target); - *target = NULL; - ++target; - num_removed++; - - if (num_removed == mNumDeadObjects || iter->isNull()) - { - // We've cleaned up all of the dead objects or caught up to the dead tail - break; - } - } - else - { - ++iter; - } - } - - llassert(num_removed == mNumDeadObjects); - - //erase as a block - mObjects.erase(mObjects.begin()+(mObjects.size()-mNumDeadObjects), mObjects.end()); - - // We've cleaned the global object list, now let's do some paranoia testing on objects - // before blowing away the dead list. - mDeadObjects.clear(); - mNumDeadObjects = 0; -} - -void LLViewerObjectList::removeFromActiveList(LLViewerObject* objectp) -{ - S32 idx = objectp->getListIndex(); - if (idx != -1) - { //remove by moving last element to this object's position - llassert(mActiveObjects[idx] == objectp); - - objectp->setListIndex(-1); - - S32 last_index = mActiveObjects.size()-1; - - if (idx != last_index) - { - mActiveObjects[idx] = mActiveObjects[last_index]; - mActiveObjects[idx]->setListIndex(idx); - } - - mActiveObjects.pop_back(); - } -} - -void LLViewerObjectList::updateActive(LLViewerObject *objectp) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE; - - if (objectp->isDead()) - { - return; // We don't update dead objects! - } - - bool active = objectp->isActive(); - if (active != objectp->onActiveList()) - { - if (active) - { - //LL_INFOS() << "Adding " << objectp->mID << " " << objectp->getPCodeString() << " to active list." << LL_ENDL; - S32 idx = objectp->getListIndex(); - if (idx <= -1) - { - mActiveObjects.push_back(objectp); - objectp->setListIndex(mActiveObjects.size()-1); - objectp->setOnActiveList(true); - } - else - { - llassert(idx < mActiveObjects.size()); - llassert(mActiveObjects[idx] == objectp); - - if (idx >= mActiveObjects.size() || - mActiveObjects[idx] != objectp) - { - LL_WARNS() << "Invalid object list index detected!" << LL_ENDL; - } - } - } - else - { - //LL_INFOS() << "Removing " << objectp->mID << " " << objectp->getPCodeString() << " from active list." << LL_ENDL; - removeFromActiveList(objectp); - objectp->setOnActiveList(false); - } - } - - //post condition: if object is active, it must be on the active list - llassert(!active || std::find(mActiveObjects.begin(), mActiveObjects.end(), objectp) != mActiveObjects.end()); - - //post condition: if object is not active, it must not be on the active list - llassert(active || std::find(mActiveObjects.begin(), mActiveObjects.end(), objectp) == mActiveObjects.end()); -} - -void LLViewerObjectList::updateObjectCost(LLViewerObject* object) -{ - if (!object->isRoot()) - { //always fetch cost for the parent when fetching cost for children - mStaleObjectCost.insert(((LLViewerObject*)object->getParent())->getID()); - } - mStaleObjectCost.insert(object->getID()); -} - -void LLViewerObjectList::updateObjectCost(const LLUUID& object_id, F32 object_cost, F32 link_cost, F32 physics_cost, F32 link_physics_cost) -{ - LLViewerObject* object = findObject(object_id); - if (object) - { - object->setObjectCost(object_cost); - object->setLinksetCost(link_cost); - object->setPhysicsCost(physics_cost); - object->setLinksetPhysicsCost(link_physics_cost); - } -} - -void LLViewerObjectList::onObjectCostFetchFailure(const LLUUID& object_id) -{ - //LL_WARNS() << "Failed to fetch object cost for object: " << object_id << LL_ENDL; - mPendingObjectCost.erase(object_id); -} - -void LLViewerObjectList::updatePhysicsFlags(const LLViewerObject* object) -{ - mStalePhysicsFlags.insert(object->getID()); -} - -void LLViewerObjectList::updatePhysicsShapeType(const LLUUID& object_id, S32 type) -{ - mPendingPhysicsFlags.erase(object_id); - LLViewerObject* object = findObject(object_id); - if (object) - { - object->setPhysicsShapeType(type); - } -} - -void LLViewerObjectList::updatePhysicsProperties(const LLUUID& object_id, - F32 density, - F32 friction, - F32 restitution, - F32 gravity_multiplier) -{ - mPendingPhysicsFlags.erase(object_id); - - LLViewerObject* object = findObject(object_id); - if (object) - { - object->setPhysicsDensity(density); - object->setPhysicsFriction(friction); - object->setPhysicsGravity(gravity_multiplier); - object->setPhysicsRestitution(restitution); - } -} - -void LLViewerObjectList::onPhysicsFlagsFetchFailure(const LLUUID& object_id) -{ - //LL_WARNS() << "Failed to fetch physics flags for object: " << object_id << LL_ENDL; - mPendingPhysicsFlags.erase(object_id); -} - -void LLViewerObjectList::shiftObjects(const LLVector3 &offset) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; - // This is called when we shift our origin when we cross region boundaries... - // We need to update many object caches, I'll document this more as I dig through the code - // cleaning things out... - - if (0 == offset.magVecSquared()) - { - return; - } - - - LLViewerObject *objectp; - for (vobj_list_t::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter) - { - objectp = *iter; - // There could be dead objects on the object list, so don't update stuff if the object is dead. - if (!objectp->isDead()) - { - objectp->updatePositionCaches(); - - if (objectp->mDrawable.notNull() && !objectp->mDrawable->isDead()) - { - gPipeline.markShift(objectp->mDrawable); - } - } - } - - gPipeline.shiftObjects(offset); - - LLWorld::getInstance()->shiftRegions(offset); -} - -void LLViewerObjectList::repartitionObjects() -{ - for (vobj_list_t::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter) - { - LLViewerObject* objectp = *iter; - if (!objectp->isDead()) - { - LLDrawable* drawable = objectp->mDrawable; - if (drawable && !drawable->isDead()) - { - drawable->updateBinRadius(); - drawable->updateSpatialExtents(); - drawable->movePartition(); - } - } - } -} - -//debug code -bool LLViewerObjectList::hasMapObjectInRegion(LLViewerRegion* regionp) -{ - for (vobj_list_t::iterator iter = mMapObjects.begin(); iter != mMapObjects.end(); ++iter) - { - LLViewerObject* objectp = *iter; - - if(objectp->isDead() || objectp->getRegion() == regionp) - { - return true ; - } - } - - return false ; -} - -//make sure the region is cleaned up. -void LLViewerObjectList::clearAllMapObjectsInRegion(LLViewerRegion* regionp) -{ - std::set dead_object_list ; - std::set region_object_list ; - for (vobj_list_t::iterator iter = mMapObjects.begin(); iter != mMapObjects.end(); ++iter) - { - LLViewerObject* objectp = *iter; - - if(objectp->isDead()) - { - dead_object_list.insert(objectp) ; - } - else if(objectp->getRegion() == regionp) - { - region_object_list.insert(objectp) ; - } - } - - if(dead_object_list.size() > 0) - { - LL_WARNS() << "There are " << dead_object_list.size() << " dead objects on the map!" << LL_ENDL ; - - for(std::set::iterator iter = dead_object_list.begin(); iter != dead_object_list.end(); ++iter) - { - cleanupReferences(*iter) ; - } - } - if(region_object_list.size() > 0) - { - LL_WARNS() << "There are " << region_object_list.size() << " objects not removed from the deleted region!" << LL_ENDL ; - - for(std::set::iterator iter = region_object_list.begin(); iter != region_object_list.end(); ++iter) - { - (*iter)->markDead() ; - } - } -} - - -void LLViewerObjectList::renderObjectsForMap(LLNetMap &netmap) -{ - LLColor4 above_water_color = LLUIColorTable::instance().getColor( "NetMapOtherOwnAboveWater" ); - LLColor4 below_water_color = LLUIColorTable::instance().getColor( "NetMapOtherOwnBelowWater" ); - LLColor4 you_own_above_water_color = - LLUIColorTable::instance().getColor( "NetMapYouOwnAboveWater" ); - LLColor4 you_own_below_water_color = - LLUIColorTable::instance().getColor( "NetMapYouOwnBelowWater" ); - LLColor4 group_own_above_water_color = - LLUIColorTable::instance().getColor( "NetMapGroupOwnAboveWater" ); - LLColor4 group_own_below_water_color = - LLUIColorTable::instance().getColor( "NetMapGroupOwnBelowWater" ); - - F32 max_radius = gSavedSettings.getF32("MiniMapPrimMaxRadius"); - - for (vobj_list_t::iterator iter = mMapObjects.begin(); iter != mMapObjects.end(); ++iter) - { - LLViewerObject* objectp = *iter; - - if(objectp->isDead())//some dead objects somehow not cleaned. - { - continue ; - } - - if (!objectp->getRegion() || objectp->isOrphaned() || objectp->isAttachment()) - { - continue; - } - const LLVector3& scale = objectp->getScale(); - const LLVector3d pos = objectp->getPositionGlobal(); - const F64 water_height = F64( objectp->getRegion()->getWaterHeight() ); - // LLWorld::getInstance()->getWaterHeight(); - - F32 approx_radius = (scale.mV[VX] + scale.mV[VY]) * 0.5f * 0.5f * 1.3f; // 1.3 is a fudge - - // Limit the size of megaprims so they don't blot out everything on the minimap. - // Attempting to draw very large megaprims also causes client lag. - // See DEV-17370 and DEV-29869/SNOW-79 for details. - approx_radius = llmin(approx_radius, max_radius); - - LLColor4U color = above_water_color; - if( objectp->permYouOwner() ) - { - const F32 MIN_RADIUS_FOR_OWNED_OBJECTS = 2.f; - if( approx_radius < MIN_RADIUS_FOR_OWNED_OBJECTS ) - { - approx_radius = MIN_RADIUS_FOR_OWNED_OBJECTS; - } - - if( pos.mdV[VZ] >= water_height ) - { - if ( objectp->permGroupOwner() ) - { - color = group_own_above_water_color; - } - else - { - color = you_own_above_water_color; - } - } - else - { - if ( objectp->permGroupOwner() ) - { - color = group_own_below_water_color; - } - else - { - color = you_own_below_water_color; - } - } - } - else - if( pos.mdV[VZ] < water_height ) - { - color = below_water_color; - } - - netmap.renderScaledPointGlobal( - pos, - color, - approx_radius ); - } -} - -void LLViewerObjectList::renderObjectBounds(const LLVector3 ¢er) -{ -} - -extern bool gCubeSnapshot; - -void LLViewerObjectList::addDebugBeacon(const LLVector3 &pos_agent, - const std::string &string, - const LLColor4 &color, - const LLColor4 &text_color, - S32 line_width) -{ - llassert(!gCubeSnapshot); - LLDebugBeacon beacon; - beacon.mPositionAgent = pos_agent; - beacon.mString = string; - beacon.mColor = color; - beacon.mTextColor = text_color; - beacon.mLineWidth = line_width; - - mDebugBeacons.push_back(beacon); -} - -void LLViewerObjectList::resetObjectBeacons() -{ - mDebugBeacons.clear(); -} - -LLViewerObject *LLViewerObjectList::createObjectViewer(const LLPCode pcode, LLViewerRegion *regionp, S32 flags) -{ - LLUUID fullid; - fullid.generate(); - - LLViewerObject *objectp = LLViewerObject::createObject(fullid, pcode, regionp, flags); - if (!objectp) - { -// LL_WARNS() << "Couldn't create object of type " << LLPrimitive::pCodeToString(pcode) << LL_ENDL; - return NULL; - } - - mUUIDObjectMap[fullid] = objectp; - - mObjects.push_back(objectp); - - updateActive(objectp); - - return objectp; -} - -LLViewerObject *LLViewerObjectList::createObjectFromCache(const LLPCode pcode, LLViewerRegion *regionp, const LLUUID &uuid, const U32 local_id) -{ - llassert_always(uuid.notNull()); - - LL_DEBUGS("ObjectUpdate") << "creating " << uuid << " local_id " << local_id << LL_ENDL; - dumpStack("ObjectUpdateStack"); - - LLViewerObject *objectp = LLViewerObject::createObject(uuid, pcode, regionp); - if (!objectp) - { -// LL_WARNS() << "Couldn't create object of type " << LLPrimitive::pCodeToString(pcode) << " id:" << fullid << LL_ENDL; - return NULL; - } - - objectp->mLocalID = local_id; - mUUIDObjectMap[uuid] = objectp; - setUUIDAndLocal(uuid, - local_id, - regionp->getHost().getAddress(), - regionp->getHost().getPort()); - mObjects.push_back(objectp); - - updateActive(objectp); - - return objectp; -} - -LLViewerObject *LLViewerObjectList::createObject(const LLPCode pcode, LLViewerRegion *regionp, - const LLUUID &uuid, const U32 local_id, const LLHost &sender) -{ - LLUUID fullid; - if (uuid == LLUUID::null) - { - fullid.generate(); - } - else - { - fullid = uuid; - } - - LL_DEBUGS("ObjectUpdate") << "createObject creating " << fullid << LL_ENDL; - dumpStack("ObjectUpdateStack"); - - LLViewerObject *objectp = LLViewerObject::createObject(fullid, pcode, regionp); - if (!objectp) - { -// LL_WARNS() << "Couldn't create object of type " << LLPrimitive::pCodeToString(pcode) << " id:" << fullid << LL_ENDL; - return NULL; - } - if(regionp) - { - regionp->addToCreatedList(local_id); - } - - mUUIDObjectMap[fullid] = objectp; - setUUIDAndLocal(fullid, - local_id, - gMessageSystem->getSenderIP(), - gMessageSystem->getSenderPort()); - - mObjects.push_back(objectp); - - updateActive(objectp); - - return objectp; -} - -LLViewerObject *LLViewerObjectList::replaceObject(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) -{ - LLViewerObject *old_instance = findObject(id); - if (old_instance) - { - //cleanupReferences(old_instance); - old_instance->markDead(); - - return createObject(pcode, regionp, id, old_instance->getLocalID(), LLHost()); - } - return NULL; -} - -S32 LLViewerObjectList::findReferences(LLDrawable *drawablep) const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE; - - LLViewerObject *objectp; - S32 num_refs = 0; - - for (vobj_list_t::const_iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter) - { - objectp = *iter; - if (objectp->mDrawable.notNull()) - { - num_refs += objectp->mDrawable->findReferences(drawablep); - } - } - return num_refs; -} - - -void LLViewerObjectList::orphanize(LLViewerObject *childp, U32 parent_id, U32 ip, U32 port) -{ - LL_DEBUGS("ORPHANS") << "Orphaning object " << childp->getID() << " with parent " << parent_id << LL_ENDL; - - // We're an orphan, flag things appropriately. - childp->mOrphaned = true; - if (childp->mDrawable.notNull()) - { - bool make_invisible = true; - LLViewerObject *parentp = (LLViewerObject *)childp->getParent(); - if (parentp) - { - if (parentp->getRegion() != childp->getRegion()) - { - // This is probably an object flying across a region boundary, the - // object probably ISN'T being reparented, but just got an object - // update out of order (child update before parent). - make_invisible = false; - //LL_INFOS() << "Don't make object handoffs invisible!" << LL_ENDL; - } - } - - if (make_invisible) - { - // Make sure that this object becomes invisible if it's an orphan - childp->mDrawable->setState(LLDrawable::FORCE_INVISIBLE); - } - } - - // Unknown parent, add to orpaned child list - U64 parent_info = getIndex(parent_id, ip, port); - - if (std::find(mOrphanParents.begin(), mOrphanParents.end(), parent_info) == mOrphanParents.end()) - { - mOrphanParents.push_back(parent_info); - } - - LLViewerObjectList::OrphanInfo oi(parent_info, childp->mID); - if (std::find(mOrphanChildren.begin(), mOrphanChildren.end(), oi) == mOrphanChildren.end()) - { - mOrphanChildren.push_back(oi); - mNumOrphans++; - } -} - - -void LLViewerObjectList::findOrphans(LLViewerObject* objectp, U32 ip, U32 port) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; - - if (objectp->isDead()) - { - LL_WARNS() << "Trying to find orphans for dead obj " << objectp->mID - << ":" << objectp->getPCodeString() << LL_ENDL; - return; - } - - //search object cache to get orphans - if(objectp->getRegion()) - { - objectp->getRegion()->findOrphans(objectp->getLocalID()); - } - - // See if we are a parent of an orphan. - // Note: This code is fairly inefficient but it should happen very rarely. - // It can be sped up if this is somehow a performance issue... - if (mOrphanParents.empty()) - { - // no known orphan parents - return; - } - if (std::find(mOrphanParents.begin(), mOrphanParents.end(), getIndex(objectp->mLocalID, ip, port)) == mOrphanParents.end()) - { - // did not find objectp in OrphanParent list - return; - } - - U64 parent_info = getIndex(objectp->mLocalID, ip, port); - bool orphans_found = false; - // Iterate through the orphan list, and set parents of matching children. - - for (std::vector::iterator iter = mOrphanChildren.begin(); iter != mOrphanChildren.end(); ) - { - if (iter->mParentInfo != parent_info) - { - ++iter; - continue; - } - LLViewerObject *childp = findObject(iter->mChildInfo); - if (childp) - { - if (childp == objectp) - { - LL_WARNS() << objectp->mID << " has self as parent, skipping!" - << LL_ENDL; - ++iter; - continue; - } - - LL_DEBUGS("ORPHANS") << "Reunited parent " << objectp->mID - << " with child " << childp->mID << LL_ENDL; - LL_DEBUGS("ORPHANS") << "Glob: " << objectp->getPositionGlobal() << LL_ENDL; - LL_DEBUGS("ORPHANS") << "Agent: " << objectp->getPositionAgent() << LL_ENDL; -#ifdef ORPHAN_SPAM - addDebugBeacon(objectp->getPositionAgent(),""); -#endif - gPipeline.markMoved(objectp->mDrawable); - objectp->setChanged(LLXform::MOVED | LLXform::SILHOUETTE); - - // Flag the object as no longer orphaned - childp->mOrphaned = false; - if (childp->mDrawable.notNull()) - { - // Make the drawable visible again and set the drawable parent - childp->mDrawable->clearState(LLDrawable::FORCE_INVISIBLE); - childp->setDrawableParent(objectp->mDrawable); // LLViewerObjectList::findOrphans() - gPipeline.markRebuild( childp->mDrawable, LLDrawable::REBUILD_ALL); - } - - // Make certain particles, icon and HUD aren't hidden - childp->hideExtraDisplayItems( false ); - - objectp->addChild(childp); - orphans_found = true; - ++iter; - } - else - { - LL_INFOS() << "Missing orphan child, removing from list" << LL_ENDL; - - iter = mOrphanChildren.erase(iter); - } - } - - // Remove orphan parent and children from lists now that they've been found - { - std::vector::iterator iter = std::find(mOrphanParents.begin(), mOrphanParents.end(), parent_info); - if (iter != mOrphanParents.end()) - { - mOrphanParents.erase(iter); - } - } - - for (std::vector::iterator iter = mOrphanChildren.begin(); iter != mOrphanChildren.end(); ) - { - if (iter->mParentInfo == parent_info) - { - iter = mOrphanChildren.erase(iter); - mNumOrphans--; - } - else - { - ++iter; - } - } - - if (orphans_found && objectp->isSelected()) - { - LLSelectNode* nodep = LLSelectMgr::getInstance()->getSelection()->findNode(objectp); - if (nodep && !nodep->mIndividualSelection) - { - // rebuild selection with orphans - LLSelectMgr::getInstance()->deselectObjectAndFamily(objectp); - LLSelectMgr::getInstance()->selectObjectAndFamily(objectp); - } - } -} - -//////////////////////////////////////////////////////////////////////////// - -LLViewerObjectList::OrphanInfo::OrphanInfo() - : mParentInfo(0) -{ -} - -LLViewerObjectList::OrphanInfo::OrphanInfo(const U64 parent_info, const LLUUID child_info) - : mParentInfo(parent_info), mChildInfo(child_info) -{ -} - -bool LLViewerObjectList::OrphanInfo::operator==(const OrphanInfo &rhs) const -{ - return (mParentInfo == rhs.mParentInfo) && (mChildInfo == rhs.mChildInfo); -} - -bool LLViewerObjectList::OrphanInfo::operator!=(const OrphanInfo &rhs) const -{ - return !operator==(rhs); -} - - -LLDebugBeacon::~LLDebugBeacon() -{ - if (mHUDObject.notNull()) - { - mHUDObject->markDead(); - } -} +/** + * @file llviewerobjectlist.cpp + * @brief Implementation of LLViewerObjectList class. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llviewerobjectlist.h" + +#include "message.h" +#include "llfasttimer.h" +#include "llrender.h" +#include "llwindow.h" // decBusyCount() + +#include "llviewercontrol.h" +#include "llface.h" +#include "llvoavatar.h" +#include "llviewerobject.h" +#include "llviewerwindow.h" +#include "llnetmap.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "pipeline.h" +#include "llspatialpartition.h" +#include "lltooltip.h" +#include "llworld.h" +#include "llstring.h" +#include "llhudicon.h" +#include "llhudnametag.h" +#include "lldrawable.h" +#include "llflexibleobject.h" +#include "llviewertextureanim.h" +#include "xform.h" +#include "llsky.h" +#include "llviewercamera.h" +#include "llselectmgr.h" +#include "llresmgr.h" +#include "llsdutil.h" +#include "llviewerregion.h" +#include "llviewerstats.h" +#include "llviewerstatsrecorder.h" +#include "llvovolume.h" +#include "llvoavatarself.h" +#include "lltoolmgr.h" +#include "lltoolpie.h" +#include "llkeyboard.h" +#include "u64.h" +#include "llviewertexturelist.h" +#include "lldatapacker.h" +#include "llcallstack.h" +#ifdef LL_USESYSTEMLIBS +#include +#else +#include "zlib-ng/zlib.h" +#endif +#include "object_flags.h" + +#include "llappviewer.h" +#include "llfloaterperms.h" +#include "llvocache.h" +#include "llcorehttputil.h" +#include "llstartup.h" + +#include +#include + +extern F32 gMinObjectDistance; +extern bool gAnimateTextures; + +#define MAX_CONCURRENT_PHYSICS_REQUESTS 256 + +void dialog_refresh_all(); + +// Global lists of objects - should go away soon. +LLViewerObjectList gObjectList; + +extern LLPipeline gPipeline; + +// Statics for object lookup tables. +U32 LLViewerObjectList::sSimulatorMachineIndex = 1; // Not zero deliberately, to speed up index check. +std::map LLViewerObjectList::sIPAndPortToIndex; +std::map LLViewerObjectList::sIndexAndLocalIDToUUID; + +LLViewerObjectList::LLViewerObjectList() +{ + mCurLazyUpdateIndex = 0; + mCurBin = 0; + mNumDeadObjects = 0; + mNumOrphans = 0; + mNumNewObjects = 0; + mWasPaused = false; + mNumDeadObjectUpdates = 0; + mNumUnknownUpdates = 0; +} + +LLViewerObjectList::~LLViewerObjectList() +{ + destroy(); +} + +void LLViewerObjectList::destroy() +{ + killAllObjects(); + + resetObjectBeacons(); + mActiveObjects.clear(); + mDeadObjects.clear(); + mMapObjects.clear(); + mUUIDObjectMap.clear(); +} + + +void LLViewerObjectList::getUUIDFromLocal(LLUUID &id, + const U32 local_id, + const U32 ip, + const U32 port) +{ + U64 ipport = (((U64)ip) << 32) | (U64)port; + + U32 index = sIPAndPortToIndex[ipport]; + + if (!index) + { + index = sSimulatorMachineIndex++; + sIPAndPortToIndex[ipport] = index; + } + + U64 indexid = (((U64)index) << 32) | (U64)local_id; + + id = get_if_there(sIndexAndLocalIDToUUID, indexid, LLUUID::null); +} + +U64 LLViewerObjectList::getIndex(const U32 local_id, + const U32 ip, + const U32 port) +{ + U64 ipport = (((U64)ip) << 32) | (U64)port; + + U32 index = sIPAndPortToIndex[ipport]; + + if (!index) + { + return 0; + } + + return (((U64)index) << 32) | (U64)local_id; +} + +bool LLViewerObjectList::removeFromLocalIDTable(const LLViewerObject* objectp) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; + + if(objectp && objectp->getRegion()) + { + U32 local_id = objectp->mLocalID; + U32 ip = objectp->getRegion()->getHost().getAddress(); + U32 port = objectp->getRegion()->getHost().getPort(); + U64 ipport = (((U64)ip) << 32) | (U64)port; + U32 index = sIPAndPortToIndex[ipport]; + + // LL_INFOS() << "Removing object from table, local ID " << local_id << ", ip " << ip << ":" << port << LL_ENDL; + + U64 indexid = (((U64)index) << 32) | (U64)local_id; + + std::map::iterator iter = sIndexAndLocalIDToUUID.find(indexid); + if (iter == sIndexAndLocalIDToUUID.end()) + { + return false; + } + + // Found existing entry + if (iter->second == objectp->getID()) + { // Full UUIDs match, so remove the entry + sIndexAndLocalIDToUUID.erase(iter); + return true; + } + // UUIDs did not match - this would zap a valid entry, so don't erase it + //LL_INFOS() << "Tried to erase entry where id in table (" + // << iter->second << ") did not match object " << object.getID() << LL_ENDL; + } + + return false ; +} + +void LLViewerObjectList::setUUIDAndLocal(const LLUUID &id, + const U32 local_id, + const U32 ip, + const U32 port) +{ + U64 ipport = (((U64)ip) << 32) | (U64)port; + + U32 index = sIPAndPortToIndex[ipport]; + + if (!index) + { + index = sSimulatorMachineIndex++; + sIPAndPortToIndex[ipport] = index; + } + + U64 indexid = (((U64)index) << 32) | (U64)local_id; + + sIndexAndLocalIDToUUID[indexid] = id; + + //LL_INFOS() << "Adding object to table, full ID " << id + // << ", local ID " << local_id << ", ip " << ip << ":" << port << LL_ENDL; +} + +S32 gFullObjectUpdates = 0; +S32 gTerseObjectUpdates = 0; + +void LLViewerObjectList::processUpdateCore(LLViewerObject* objectp, + void** user_data, + U32 i, + const EObjectUpdateType update_type, + LLDataPacker* dpp, + bool just_created, + bool from_cache) +{ + LLMessageSystem* msg = NULL; + + if(!from_cache) + { + msg = gMessageSystem; + } + + // ignore returned flags + LL_DEBUGS("ObjectUpdate") << "uuid " << objectp->mID << " calling processUpdateMessage " + << objectp << " just_created " << just_created << " from_cache " << from_cache << " msg " << msg << LL_ENDL; + dumpStack("ObjectUpdateStack"); + + objectp->processUpdateMessage(msg, user_data, i, update_type, dpp); + + if (objectp->isDead()) + { + // The update failed + return; + } + + updateActive(objectp); + + if (just_created) + { + gPipeline.addObject(objectp); + } + + // Also sets the approx. pixel area + objectp->setPixelAreaAndAngle(gAgent); + + // RN: this must be called after we have a drawable + // (from gPipeline.addObject) + // so that the drawable parent is set properly + if(msg != NULL) + { + findOrphans(objectp, msg->getSenderIP(), msg->getSenderPort()); + } + else + { + LLViewerRegion* regionp = objectp->getRegion(); + if(regionp != NULL) + { + findOrphans(objectp, regionp->getHost().getAddress(), regionp->getHost().getPort()); + } + } + + // If we're just wandering around, don't create new objects selected. + if (just_created + && update_type != OUT_TERSE_IMPROVED + && objectp->mCreateSelected) + { + if ( LLToolMgr::getInstance()->getCurrentTool() != LLToolPie::getInstance() ) + { + // LL_INFOS() << "DEBUG selecting " << objectp->mID << " " + // << objectp->mLocalID << LL_ENDL; + LLSelectMgr::getInstance()->selectObjectAndFamily(objectp); + dialog_refresh_all(); + } + + objectp->mCreateSelected = false; + gViewerWindow->getWindow()->decBusyCount(); + gViewerWindow->setCursor( UI_CURSOR_ARROW ); + } +} + +static LLTrace::BlockTimerStatHandle FTM_PROCESS_OBJECTS("Process Objects"); + +LLViewerObject* LLViewerObjectList::processObjectUpdateFromCache(LLVOCacheEntry* entry, LLViewerRegion* regionp) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; + + LLDataPacker *cached_dpp = entry->getDP(); + + if (!cached_dpp || gNonInteractive) + { + return NULL; //nothing cached. + } + + LLViewerObject *objectp; + U32 local_id; + LLPCode pcode = 0; + LLUUID fullid; + LLViewerStatsRecorder& recorder = LLViewerStatsRecorder::instance(); + + // Cache Hit. + record(LLStatViewer::OBJECT_CACHE_HIT_RATE, LLUnits::Ratio::fromValue(1)); + + cached_dpp->reset(); + cached_dpp->unpackUUID(fullid, "ID"); + cached_dpp->unpackU32(local_id, "LocalID"); + cached_dpp->unpackU8(pcode, "PCode"); + + objectp = findObject(fullid); + + if (objectp) + { + if(!objectp->isDead() && (objectp->mLocalID != entry->getLocalID() || + objectp->getRegion() != regionp)) + { + removeFromLocalIDTable(objectp); + setUUIDAndLocal(fullid, entry->getLocalID(), + regionp->getHost().getAddress(), + regionp->getHost().getPort()); + + if (objectp->mLocalID != entry->getLocalID()) + { // Update local ID in object with the one sent from the region + objectp->mLocalID = entry->getLocalID(); + } + + if (objectp->getRegion() != regionp) + { // Object changed region, so update it + objectp->updateRegion(regionp); // for LLVOAvatar + } + } + else + { + //should fall through if already loaded because may need to update the object. + //return objectp; //already loaded. + } + } + + bool justCreated = false; + if (!objectp) + { + objectp = createObjectFromCache(pcode, regionp, fullid, entry->getLocalID()); + + LL_DEBUGS("ObjectUpdate") << "uuid " << fullid << " created objectp " << objectp << LL_ENDL; + dumpStack("ObjectUpdateStack"); + + if (!objectp) + { + LL_INFOS() << "createObject failure for object: " << fullid << LL_ENDL; + recorder.objectUpdateFailure(); + return NULL; + } + justCreated = true; + mNumNewObjects++; + } + + if (objectp->isDead()) + { + LL_WARNS() << "Dead object " << objectp->mID << " in UUID map 1!" << LL_ENDL; + } + + processUpdateCore(objectp, NULL, 0, OUT_FULL_CACHED, cached_dpp, justCreated, true); + objectp->loadFlags(entry->getUpdateFlags()); //just in case, reload update flags from cache. + + if(entry->getHitCount() > 0) + { + objectp->setLastUpdateType(OUT_FULL_CACHED); + } + else + { + objectp->setLastUpdateType(OUT_FULL_COMPRESSED); //newly cached + objectp->setLastUpdateCached(true); + } + LLVOAvatar::cullAvatarsByPixelArea(); + + return objectp; +} + +void LLViewerObjectList::processObjectUpdate(LLMessageSystem *mesgsys, + void **user_data, + const EObjectUpdateType update_type, + bool compressed) +{ + LL_RECORD_BLOCK_TIME(FTM_PROCESS_OBJECTS); + + LLViewerObject *objectp; + S32 num_objects; + U32 local_id; + LLPCode pcode = 0; + LLUUID fullid; + S32 i; + + // figure out which simulator these are from and get it's index + // Coordinates in simulators are region-local + // Until we get region-locality working on viewer we + // have to transform to absolute coordinates. + num_objects = mesgsys->getNumberOfBlocksFast(_PREHASH_ObjectData); + + // I don't think this case is ever hit. TODO* Test this. + if (!compressed && update_type != OUT_FULL) + { + //LL_INFOS() << "TEST: !cached && !compressed && update_type != OUT_FULL" << LL_ENDL; + gTerseObjectUpdates += num_objects; + /* + S32 size; + if (mesgsys->getReceiveCompressedSize()) + { + size = mesgsys->getReceiveCompressedSize(); + } + else + { + size = mesgsys->getReceiveSize(); + } + LL_INFOS() << "Received terse " << num_objects << " in " << size << " byte (" << size/num_objects << ")" << LL_ENDL; + */ + } + else + { + /* + S32 size; + if (mesgsys->getReceiveCompressedSize()) + { + size = mesgsys->getReceiveCompressedSize(); + } + else + { + size = mesgsys->getReceiveSize(); + } + + LL_INFOS() << "Received " << num_objects << " in " << size << " byte (" << size/num_objects << ")" << LL_ENDL; + */ + gFullObjectUpdates += num_objects; + } + + U64 region_handle; + mesgsys->getU64Fast(_PREHASH_RegionData, _PREHASH_RegionHandle, region_handle); + + LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle(region_handle); + + if (!regionp) + { + LL_WARNS() << "Object update from unknown region! " << region_handle << LL_ENDL; + return; + } + + U8 compressed_dpbuffer[2048]; + LLDataPackerBinaryBuffer compressed_dp(compressed_dpbuffer, 2048); + LLViewerStatsRecorder& recorder = LLViewerStatsRecorder::instance(); + + for (i = 0; i < num_objects; i++) + { + bool justCreated = false; + bool update_cache = false; //update object cache if it is a full-update or terse update + + if (compressed) + { + compressed_dp.reset(); + + S32 uncompressed_length = mesgsys->getSizeFast(_PREHASH_ObjectData, i, _PREHASH_Data); + LL_DEBUGS("ObjectUpdate") << "got binary data from message to compressed_dpbuffer" << LL_ENDL; + mesgsys->getBinaryDataFast(_PREHASH_ObjectData, _PREHASH_Data, compressed_dpbuffer, 0, i, 2048); + compressed_dp.assignBuffer(compressed_dpbuffer, uncompressed_length); + + if (update_type != OUT_TERSE_IMPROVED) // OUT_FULL_COMPRESSED only? + { + U32 flags = 0; + mesgsys->getU32Fast(_PREHASH_ObjectData, _PREHASH_UpdateFlags, flags, i); + + compressed_dp.unpackUUID(fullid, "ID"); + compressed_dp.unpackU32(local_id, "LocalID"); + compressed_dp.unpackU8(pcode, "PCode"); + + if (pcode == 0) + { + // object creation will fail, LLViewerObject::createObject() + LL_WARNS() << "Received object " << fullid + << " with 0 PCode. Local id: " << local_id + << " Flags: " << flags + << " Region: " << regionp->getName() + << " Region id: " << regionp->getRegionID() << LL_ENDL; + recorder.objectUpdateFailure(); + continue; + } + else if ((flags & FLAGS_TEMPORARY_ON_REZ) == 0) + { + //send to object cache + regionp->cacheFullUpdate(compressed_dp, flags); + continue; + } + } + else //OUT_TERSE_IMPROVED + { + update_cache = true; + compressed_dp.unpackU32(local_id, "LocalID"); + getUUIDFromLocal(fullid, + local_id, + gMessageSystem->getSenderIP(), + gMessageSystem->getSenderPort()); + if (fullid.isNull()) + { + LL_DEBUGS() << "update for unknown localid " << local_id << " host " << gMessageSystem->getSender() << ":" << gMessageSystem->getSenderPort() << LL_ENDL; + mNumUnknownUpdates++; + } + } + } + else if (update_type != OUT_FULL) // !compressed, !OUT_FULL ==> OUT_FULL_CACHED only? + { + mesgsys->getU32Fast(_PREHASH_ObjectData, _PREHASH_ID, local_id, i); + + getUUIDFromLocal(fullid, + local_id, + gMessageSystem->getSenderIP(), + gMessageSystem->getSenderPort()); + if (fullid.isNull()) + { + // LL_WARNS() << "update for unknown localid " << local_id << " host " << gMessageSystem->getSender() << LL_ENDL; + mNumUnknownUpdates++; + } + else + { + LL_DEBUGS("ObjectUpdate") << "Non-full, non-compressed update, obj " << local_id << ", global ID " << fullid << " from " << mesgsys->getSender() << LL_ENDL; + } + } + else // OUT_FULL only? + { + update_cache = true; + mesgsys->getUUIDFast(_PREHASH_ObjectData, _PREHASH_FullID, fullid, i); + mesgsys->getU32Fast(_PREHASH_ObjectData, _PREHASH_ID, local_id, i); + LL_DEBUGS("ObjectUpdate") << "Full Update, obj " << local_id << ", global ID " << fullid << " from " << mesgsys->getSender() << LL_ENDL; + } + objectp = findObject(fullid); + + if (compressed) + { + LL_DEBUGS("ObjectUpdate") << "uuid " << fullid << " received compressed data from message (earlier in function)" << LL_ENDL; + } + LL_DEBUGS("ObjectUpdate") << "uuid " << fullid << " objectp " << objectp + << " update_cache " << (S32) update_cache << " compressed " << compressed + << " update_type " << update_type << LL_ENDL; + dumpStack("ObjectUpdateStack"); + + if(update_cache) + { + //update object cache if the object receives a full-update or terse update + objectp = regionp->updateCacheEntry(local_id, objectp); + } + + // This looks like it will break if the local_id of the object doesn't change + // upon boundary crossing, but we check for region id matching later... + // Reset object local id and region pointer if things have changed + if (objectp && + ((objectp->mLocalID != local_id) || + (objectp->getRegion() != regionp))) + { + //if (objectp->getRegion()) + //{ + // LL_INFOS() << "Local ID change: Removing object from table, local ID " << objectp->mLocalID + // << ", id from message " << local_id << ", from " + // << LLHost(objectp->getRegion()->getHost().getAddress(), objectp->getRegion()->getHost().getPort()) + // << ", full id " << fullid + // << ", objects id " << objectp->getID() + // << ", regionp " << (U32) regionp << ", object region " << (U32) objectp->getRegion() + // << LL_ENDL; + //} + removeFromLocalIDTable(objectp); + setUUIDAndLocal(fullid, + local_id, + gMessageSystem->getSenderIP(), + gMessageSystem->getSenderPort()); + + if (objectp->mLocalID != local_id) + { // Update local ID in object with the one sent from the region + objectp->mLocalID = local_id; + } + + if (objectp->getRegion() != regionp) + { // Object changed region, so update it + objectp->updateRegion(regionp); // for LLVOAvatar + } + } + + if (!objectp) + { + if (compressed) + { + if (update_type == OUT_TERSE_IMPROVED) + { + // LL_INFOS() << "terse update for an unknown object (compressed):" << fullid << LL_ENDL; + recorder.objectUpdateFailure(); + continue; + } + } + else + { + if (update_type != OUT_FULL) + { + //LL_INFOS() << "terse update for an unknown object:" << fullid << LL_ENDL; + recorder.objectUpdateFailure(); + continue; + } + + mesgsys->getU8Fast(_PREHASH_ObjectData, _PREHASH_PCode, pcode, i); + + } +#ifdef IGNORE_DEAD + if (mDeadObjects.find(fullid) != mDeadObjects.end()) + { + mNumDeadObjectUpdates++; + //LL_INFOS() << "update for a dead object:" << fullid << LL_ENDL; + recorder.objectUpdateFailure(); + continue; + } +#endif + + objectp = createObject(pcode, regionp, fullid, local_id, gMessageSystem->getSender()); + + LL_DEBUGS("ObjectUpdate") << "creating object " << fullid << " result " << objectp << LL_ENDL; + dumpStack("ObjectUpdateStack"); + + if (!objectp) + { + LL_INFOS() << "createObject failure for object: " << fullid << LL_ENDL; + recorder.objectUpdateFailure(); + continue; + } + + justCreated = true; + mNumNewObjects++; + } + + if (objectp->isDead()) + { + LL_WARNS() << "Dead object " << objectp->mID << " in UUID map 1!" << LL_ENDL; + } + + //bool bCached = false; + if (compressed) + { + if (update_type != OUT_TERSE_IMPROVED) // OUT_FULL_COMPRESSED only? + { + objectp->mLocalID = local_id; + } + processUpdateCore(objectp, user_data, i, update_type, &compressed_dp, justCreated); + +#if 0 + if (update_type != OUT_TERSE_IMPROVED) // OUT_FULL_COMPRESSED only? + { + U32 flags = 0; + mesgsys->getU32Fast(_PREHASH_ObjectData, _PREHASH_UpdateFlags, flags, i); + + if(!(flags & FLAGS_TEMPORARY_ON_REZ)) + { + bCached = true; + LLViewerRegion::eCacheUpdateResult result = objectp->mRegionp->cacheFullUpdate(objectp, compressed_dp, flags); + recorder.cacheFullUpdate(result); + } + } +#endif + } + else + { + if (update_type == OUT_FULL) + { + objectp->mLocalID = local_id; + } + processUpdateCore(objectp, user_data, i, update_type, NULL, justCreated); + } + recorder.objectUpdateEvent(update_type); + objectp->setLastUpdateType(update_type); + } + + LLVOAvatar::cullAvatarsByPixelArea(); +} + +void LLViewerObjectList::processCompressedObjectUpdate(LLMessageSystem *mesgsys, + void **user_data, + const EObjectUpdateType update_type) +{ + processObjectUpdate(mesgsys, user_data, update_type, true); +} + +void LLViewerObjectList::processCachedObjectUpdate(LLMessageSystem *mesgsys, + void **user_data, + const EObjectUpdateType update_type) +{ + //processObjectUpdate(mesgsys, user_data, update_type, true, false); + + S32 num_objects = mesgsys->getNumberOfBlocksFast(_PREHASH_ObjectData); + gFullObjectUpdates += num_objects; + + U64 region_handle; + mesgsys->getU64Fast(_PREHASH_RegionData, _PREHASH_RegionHandle, region_handle); + LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromHandle(region_handle); + if (!regionp) + { + LL_WARNS() << "Object update from unknown region! " << region_handle << LL_ENDL; + return; + } + + LLViewerStatsRecorder& recorder = LLViewerStatsRecorder::instance(); + + for (S32 i = 0; i < num_objects; i++) + { + U32 id; + U32 crc; + U32 flags; + mesgsys->getU32Fast(_PREHASH_ObjectData, _PREHASH_ID, id, i); + mesgsys->getU32Fast(_PREHASH_ObjectData, _PREHASH_CRC, crc, i); + mesgsys->getU32Fast(_PREHASH_ObjectData, _PREHASH_UpdateFlags, flags, i); + + LL_DEBUGS("ObjectUpdate") << "got probe for id " << id << " crc " << crc << LL_ENDL; + dumpStack("ObjectUpdateStack"); + + // Lookup data packer and add this id to cache miss lists if necessary. + U8 cache_miss_type = LLViewerRegion::CACHE_MISS_TYPE_NONE; + if (regionp->probeCache(id, crc, flags, cache_miss_type)) + { // Cache Hit + recorder.cacheHitEvent(); + } + else + { // Cache Miss + LL_DEBUGS("ObjectUpdate") << "cache miss for id " << id << " crc " << crc << " miss type " << (S32) cache_miss_type << LL_ENDL; + recorder.cacheMissEvent(cache_miss_type); + } + } + + return; +} + +void LLViewerObjectList::dirtyAllObjectInventory() +{ + for (vobj_list_t::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter) + { + (*iter)->dirtyInventory(); + } +} + +void LLViewerObjectList::updateApparentAngles(LLAgent &agent) +{ + S32 i; + LLViewerObject *objectp; + + S32 num_updates, max_value; + if (NUM_BINS - 1 == mCurBin) + { + // Remainder (mObjects.size() could have changed) + num_updates = (S32) mObjects.size() - mCurLazyUpdateIndex; + max_value = (S32) mObjects.size(); + } + else + { + num_updates = ((S32) mObjects.size() / NUM_BINS) + 1; + max_value = llmin((S32) mObjects.size(), mCurLazyUpdateIndex + num_updates); + } + + // Iterate through some of the objects and lazy update their texture priorities + for (i = mCurLazyUpdateIndex; i < max_value; i++) + { + objectp = mObjects[i]; + if (!objectp->isDead()) + { + // Update distance & gpw + objectp->setPixelAreaAndAngle(agent); // Also sets the approx. pixel area + objectp->updateTextures(); // Update the image levels of textures for this object. + } + } + + mCurLazyUpdateIndex = max_value; + if (mCurLazyUpdateIndex == mObjects.size()) + { + // restart + mCurLazyUpdateIndex = 0; + mCurBin = 0; // keep in sync with index (mObjects.size() could have changed) + } + else + { + mCurBin = (mCurBin + 1) % NUM_BINS; + } + +#if 0 + // Slam priorities for textures that we care about (hovered, selected, and focused) + // Hovered + // Assumes only one level deep of parenting + LLSelectNode* nodep = LLSelectMgr::instance().getHoverNode(); + if (nodep) + { + objectp = nodep->getObject(); + if (objectp) + { + objectp->boostTexturePriority(); + } + } + + // Focused + objectp = gAgentCamera.getFocusObject(); + if (objectp) + { + objectp->boostTexturePriority(); + } +#endif + + // Selected + struct f : public LLSelectedObjectFunctor + { + virtual bool apply(LLViewerObject* objectp) + { + if (objectp) + { + objectp->boostTexturePriority(); + } + return true; + } + } func; + LLSelectMgr::getInstance()->getSelection()->applyToRootObjects(&func); + + LLVOAvatar::cullAvatarsByPixelArea(); +} + +void LLViewerObjectList::update(LLAgent &agent) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; + + // Update globals + LLViewerObject::setVelocityInterpolate( gSavedSettings.getBOOL("VelocityInterpolate") ); + LLViewerObject::setPingInterpolate( gSavedSettings.getBOOL("PingInterpolate") ); + + F32 interp_time = gSavedSettings.getF32("InterpolationTime"); + F32 phase_out_time = gSavedSettings.getF32("InterpolationPhaseOut"); + F32 region_interp_time = llclamp(gSavedSettings.getF32("RegionCrossingInterpolationTime"), 0.5f, 5.f); + if (interp_time < 0.0 || + phase_out_time < 0.0 || + phase_out_time > interp_time) + { + LL_WARNS() << "Invalid values for InterpolationTime or InterpolationPhaseOut, resetting to defaults" << LL_ENDL; + interp_time = 3.0f; + phase_out_time = 1.0f; + } + LLViewerObject::setPhaseOutUpdateInterpolationTime( interp_time ); + LLViewerObject::setMaxUpdateInterpolationTime( phase_out_time ); + LLViewerObject::setMaxRegionCrossingInterpolationTime(region_interp_time); + + gAnimateTextures = gSavedSettings.getBOOL("AnimateTextures"); + + // update global timer + F32 last_time = gFrameTimeSeconds; + U64Microseconds time = totalTime(); // this will become the new gFrameTime when the update is done + // Time _can_ go backwards, for example if the user changes the system clock. + // It doesn't cause any fatal problems (just some oddness with stats), so we shouldn't assert here. +// llassert(time > gFrameTime); + F64Seconds time_diff = time - gFrameTime; + gFrameTime = time; + F64Seconds time_since_start = gFrameTime - gStartTime; + gFrameTimeSeconds = time_since_start; + + gFrameIntervalSeconds = gFrameTimeSeconds - last_time; + if (gFrameIntervalSeconds < 0.f) + { + gFrameIntervalSeconds = 0.f; + } + + //clear avatar LOD change counter + LLVOAvatar::sNumLODChangesThisFrame = 0; + + const F64 frame_time = LLFrameTimer::getElapsedSeconds(); + + LLViewerObject *objectp = NULL; + + // Make a copy of the list in case something in idleUpdate() messes with it + static std::vector idle_list; + + U32 idle_count = 0; + + { + for (std::vector >::iterator active_iter = mActiveObjects.begin(); + active_iter != mActiveObjects.end(); active_iter++) + { + objectp = *active_iter; + if (objectp) + { + if (idle_count >= idle_list.size()) + { + idle_list.push_back( objectp ); + } + else + { + idle_list[idle_count] = objectp; + } + ++idle_count; + } + else + { // There shouldn't be any NULL pointers in the list, but they have caused + // crashes before. This may be idleUpdate() messing with the list. + LL_WARNS() << "LLViewerObjectList::update has a NULL objectp" << LL_ENDL; + } + } + } + + std::vector::iterator idle_end = idle_list.begin()+idle_count; + + if (gSavedSettings.getBOOL("FreezeTime")) + { + + for (std::vector::iterator iter = idle_list.begin(); + iter != idle_end; iter++) + { + objectp = *iter; + if (objectp->isAvatar()) + { + objectp->idleUpdate(agent, frame_time); + } + } + } + else + { + for (std::vector::iterator idle_iter = idle_list.begin(); + idle_iter != idle_end; idle_iter++) + { + objectp = *idle_iter; + llassert(objectp->isActive()); + objectp->idleUpdate(agent, frame_time); + } + + //update flexible objects + LLVolumeImplFlexible::updateClass(); + + //update animated textures + if (gAnimateTextures) + { + LLViewerTextureAnim::updateClass(); + } + } + + + + fetchObjectCosts(); + fetchPhysicsFlags(); + + // update max computed render cost + LLVOVolume::updateRenderComplexity(); + + // compute all sorts of time-based stats + // don't factor frames that were paused into the stats + if (! mWasPaused) + { + LLViewerStats::getInstance()->updateFrameStats(time_diff); + } + + /* + // Debugging code for viewing orphans, and orphaned parents + LLUUID id; + for (i = 0; i < mOrphanParents.size(); i++) + { + id = sIndexAndLocalIDToUUID[mOrphanParents[i]]; + LLViewerObject *objectp = findObject(id); + if (objectp) + { + std::string id_str; + objectp->mID.toString(id_str); + std::string tmpstr = std::string("Par: ") + id_str; + addDebugBeacon(objectp->getPositionAgent(), + tmpstr, + LLColor4(1.f,0.f,0.f,1.f), + LLColor4(1.f,1.f,1.f,1.f)); + } + } + + LLColor4 text_color; + for (i = 0; i < mOrphanChildren.size(); i++) + { + OrphanInfo oi = mOrphanChildren[i]; + LLViewerObject *objectp = findObject(oi.mChildInfo); + if (objectp) + { + std::string id_str; + objectp->mID.toString(id_str); + std::string tmpstr; + if (objectp->getParent()) + { + tmpstr = std::string("ChP: ") + id_str; + text_color = LLColor4(0.f, 1.f, 0.f, 1.f); + } + else + { + tmpstr = std::string("ChNoP: ") + id_str; + text_color = LLColor4(1.f, 0.f, 0.f, 1.f); + } + id = sIndexAndLocalIDToUUID[oi.mParentInfo]; + addDebugBeacon(objectp->getPositionAgent() + LLVector3(0.f, 0.f, -0.25f), + tmpstr, + LLColor4(0.25f,0.25f,0.25f,1.f), + text_color); + } + i++; + } + */ + + sample(LLStatViewer::NUM_OBJECTS, mObjects.size()); + sample(LLStatViewer::NUM_ACTIVE_OBJECTS, idle_count); +} + +void LLViewerObjectList::fetchObjectCosts() +{ + // issue http request for stale object physics costs + if (!mStaleObjectCost.empty()) + { + LLViewerRegion* regionp = gAgent.getRegion(); + + if (regionp) + { + std::string url = regionp->getCapability("GetObjectCost"); + + if (!url.empty()) + { + LLCoros::instance().launch("LLViewerObjectList::fetchObjectCostsCoro", + boost::bind(&LLViewerObjectList::fetchObjectCostsCoro, this, url)); + } + else + { + mStaleObjectCost.clear(); + mPendingObjectCost.clear(); + } + } + } +} + +/*static*/ +void LLViewerObjectList::reportObjectCostFailure(LLSD &objectList) +{ + // TODO*: No more hard coding + for (LLSD::array_iterator it = objectList.beginArray(); it != objectList.endArray(); ++it) + { + gObjectList.onObjectCostFetchFailure(it->asUUID()); + } +} + + +void LLViewerObjectList::fetchObjectCostsCoro(std::string url) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("genericPostCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + + + uuid_set_t diff; + + std::set_difference(mStaleObjectCost.begin(), mStaleObjectCost.end(), + mPendingObjectCost.begin(), mPendingObjectCost.end(), + std::inserter(diff, diff.begin())); + + mStaleObjectCost.clear(); + + if (diff.empty()) + { + LL_INFOS() << "No outstanding object IDs to request. Pending count: " << mPendingObjectCost.size() << LL_ENDL; + return; + } + + LLSD idList(LLSD::emptyArray()); + + for (uuid_set_t::iterator it = diff.begin(); it != diff.end(); ++it) + { + idList.append(*it); + } + + mPendingObjectCost.insert(diff.begin(), diff.end()); + + LLSD postData = LLSD::emptyMap(); + + postData["object_ids"] = idList; + + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status || result.has("error")) + { + if (result.has("error")) + { + LL_WARNS() << "Application level error when fetching object " + << "cost. Message: " << result["error"]["message"].asString() + << ", identifier: " << result["error"]["identifier"].asString() + << LL_ENDL; + + // TODO*: Adaptively adjust request size if the + // service says we've requested too many and retry + } + reportObjectCostFailure(idList); + + return; + } + + // Success, grab the resource cost and linked set costs + // for an object if one was returned + for (LLSD::array_iterator it = idList.beginArray(); it != idList.endArray(); ++it) + { + LLUUID objectId = it->asUUID(); + + // Object could have been added to the mStaleObjectCost after request started + mStaleObjectCost.erase(objectId); + mPendingObjectCost.erase(objectId); + + // Check to see if the request contains data for the object + if (result.has(it->asString())) + { + LLSD objectData = result[it->asString()]; + + F32 linkCost = objectData["linked_set_resource_cost"].asReal(); + F32 objectCost = objectData["resource_cost"].asReal(); + F32 physicsCost = objectData["physics_cost"].asReal(); + F32 linkPhysicsCost = objectData["linked_set_physics_cost"].asReal(); + + gObjectList.updateObjectCost(objectId, objectCost, linkCost, physicsCost, linkPhysicsCost); + } + else + { + // TODO*: Give user feedback about the missing data? + gObjectList.onObjectCostFetchFailure(objectId); + } + } + +} + +void LLViewerObjectList::fetchPhysicsFlags() +{ + // issue http request for stale object physics flags + if (!mStalePhysicsFlags.empty()) + { + LLViewerRegion* regionp = gAgent.getRegion(); + + if (regionp) + { + std::string url = regionp->getCapability("GetObjectPhysicsData"); + + if (!url.empty()) + { + LLCoros::instance().launch("LLViewerObjectList::fetchPhisicsFlagsCoro", + boost::bind(&LLViewerObjectList::fetchPhisicsFlagsCoro, this, url)); + } + else + { + mStalePhysicsFlags.clear(); + mPendingPhysicsFlags.clear(); + } + } + } +} + +/*static*/ +void LLViewerObjectList::reportPhysicsFlagFailure(LLSD &objectList) +{ + // TODO*: No more hard coding + for (LLSD::array_iterator it = objectList.beginArray(); it != objectList.endArray(); ++it) + { + gObjectList.onPhysicsFlagsFetchFailure(it->asUUID()); + } +} + +void LLViewerObjectList::fetchPhisicsFlagsCoro(std::string url) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("genericPostCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD idList; + U32 objectIndex = 0; + + for (uuid_set_t::iterator it = mStalePhysicsFlags.begin(); it != mStalePhysicsFlags.end();) + { + // Check to see if a request for this object + // has already been made. + if (mPendingPhysicsFlags.find(*it) == mPendingPhysicsFlags.end()) + { + mPendingPhysicsFlags.insert(*it); + idList[objectIndex++] = *it; + } + + mStalePhysicsFlags.erase(it++); + + if (objectIndex >= MAX_CONCURRENT_PHYSICS_REQUESTS) + { + break; + } + } + + if (idList.size() < 1) + { + LL_INFOS() << "No outstanding object physics flags to request." << LL_ENDL; + return; + } + + LLSD postData = LLSD::emptyMap(); + + postData["object_ids"] = idList; + + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status || result.has("error")) + { + if (result.has("error")) + { + LL_WARNS() << "Application level error when fetching object " + << "physics flags. Message: " << result["error"]["message"].asString() + << ", identifier: " << result["error"]["identifier"].asString() + << LL_ENDL; + + // TODO*: Adaptively adjust request size if the + // service says we've requested too many and retry + } + reportPhysicsFlagFailure(idList); + + return; + } + + // Success, grab the resource cost and linked set costs + // for an object if one was returned + for (LLSD::array_iterator it = idList.beginArray(); it != idList.endArray(); ++it) + { + LLUUID objectId = it->asUUID(); + + // Check to see if the request contains data for the object + if (result.has(it->asString())) + { + const LLSD& data = result[it->asString()]; + + S32 shapeType = data["PhysicsShapeType"].asInteger(); + + gObjectList.updatePhysicsShapeType(objectId, shapeType); + + if (data.has("Density")) + { + F32 density = data["Density"].asReal(); + F32 friction = data["Friction"].asReal(); + F32 restitution = data["Restitution"].asReal(); + F32 gravityMult = data["GravityMultiplier"].asReal(); + + gObjectList.updatePhysicsProperties(objectId, density, + friction, restitution, gravityMult); + } + } + else + { + // TODO*: Give user feedback about the missing data? + gObjectList.onPhysicsFlagsFetchFailure(objectId); + } + } +} + +void LLViewerObjectList::clearDebugText() +{ + for (vobj_list_t::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter) + { + (*iter)->restoreHudText(); + } +} + + +void LLViewerObjectList::cleanupReferences(LLViewerObject *objectp) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; + + bool new_dead_object = true; + if (mDeadObjects.find(objectp->mID) != mDeadObjects.end()) + { + LL_INFOS() << "Object " << objectp->mID << " already on dead list!" << LL_ENDL; + new_dead_object = false; + } + else + { + mDeadObjects.insert(objectp->mID); + } + + // Cleanup any references we have to this object + // Remove from object map so noone can look it up. + + LL_DEBUGS("ObjectUpdate") << " dereferencing id " << objectp->mID << LL_ENDL; + dumpStack("ObjectUpdateStack"); + + mUUIDObjectMap.erase(objectp->mID); + + //if (objectp->getRegion()) + //{ + // LL_INFOS() << "cleanupReferences removing object from table, local ID " << objectp->mLocalID << ", ip " + // << objectp->getRegion()->getHost().getAddress() << ":" + // << objectp->getRegion()->getHost().getPort() << LL_ENDL; + //} + + removeFromLocalIDTable(objectp); + + if (objectp->onActiveList()) + { + //LL_INFOS() << "Removing " << objectp->mID << " " << objectp->getPCodeString() << " from active list in cleanupReferences." << LL_ENDL; + objectp->setOnActiveList(false); + removeFromActiveList(objectp); + } + + if (objectp->isOnMap()) + { + removeFromMap(objectp); + } + + // Don't clean up mObject references, these will be cleaned up more efficiently later! + + if(new_dead_object) + { + mNumDeadObjects++; + } +} + +bool LLViewerObjectList::killObject(LLViewerObject *objectp) +{ + LL_PROFILE_ZONE_SCOPED; + // Don't ever kill gAgentAvatarp, just force it to the agent's region + // unless region is NULL which is assumed to mean you are logging out. + if ((objectp == gAgentAvatarp) && gAgent.getRegion()) + { + objectp->setRegion(gAgent.getRegion()); + return false; + } + + // When we're killing objects, all we do is mark them as dead. + // We clean up the dead objects later. + + if (objectp) + { + // We are going to cleanup a lot of smart pointers to this object, they might be last, + // and object being NULLed while inside it's own function won't be pretty + // so create a pointer to make sure object will stay alive untill markDead() finishes + LLPointer sp(objectp); + sp->markDead(); // does the right thing if object already dead + return true; + } + + return false; +} + +void LLViewerObjectList::killObjects(LLViewerRegion *regionp) +{ + LL_PROFILE_ZONE_SCOPED; + LLViewerObject *objectp; + + + for (vobj_list_t::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter) + { + objectp = *iter; + + if (objectp->mRegionp == regionp) + { + killObject(objectp); + } + } + + // Have to clean right away because the region is becoming invalid. + cleanDeadObjects(false); +} + +void LLViewerObjectList::killAllObjects() +{ + // Used only on global destruction. + LLViewerObject *objectp; + + for (vobj_list_t::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter) + { + objectp = *iter; + killObject(objectp); + // Object must be dead, or it's the LLVOAvatarSelf which never dies. + llassert((objectp == gAgentAvatarp) || objectp->isDead()); + } + + cleanDeadObjects(false); + + if(!mObjects.empty()) + { + LL_WARNS() << "LLViewerObjectList::killAllObjects still has entries in mObjects: " << mObjects.size() << LL_ENDL; + mObjects.clear(); + } + + if (!mActiveObjects.empty()) + { + LL_WARNS() << "Some objects still on active object list!" << LL_ENDL; + mActiveObjects.clear(); + } + + if (!mMapObjects.empty()) + { + LL_WARNS() << "Some objects still on map object list!" << LL_ENDL; + mMapObjects.clear(); + } +} + +void LLViewerObjectList::cleanDeadObjects(bool use_timer) +{ + if (!mNumDeadObjects) + { + // No dead objects, don't need to scan object list. + return; + } + + LL_PROFILE_ZONE_SCOPED; + + S32 num_removed = 0; + LLViewerObject *objectp; + + vobj_list_t::reverse_iterator target = mObjects.rbegin(); + + vobj_list_t::iterator iter = mObjects.begin(); + for ( ; iter != mObjects.end(); ) + { + // Scan for all of the dead objects and put them all on the end of the list with no ref count ops + objectp = *iter; + if (objectp == NULL) + { //we caught up to the dead tail + break; + } + + if (objectp->isDead()) + { + LLPointer::swap(*iter, *target); + *target = NULL; + ++target; + num_removed++; + + if (num_removed == mNumDeadObjects || iter->isNull()) + { + // We've cleaned up all of the dead objects or caught up to the dead tail + break; + } + } + else + { + ++iter; + } + } + + llassert(num_removed == mNumDeadObjects); + + //erase as a block + mObjects.erase(mObjects.begin()+(mObjects.size()-mNumDeadObjects), mObjects.end()); + + // We've cleaned the global object list, now let's do some paranoia testing on objects + // before blowing away the dead list. + mDeadObjects.clear(); + mNumDeadObjects = 0; +} + +void LLViewerObjectList::removeFromActiveList(LLViewerObject* objectp) +{ + S32 idx = objectp->getListIndex(); + if (idx != -1) + { //remove by moving last element to this object's position + llassert(mActiveObjects[idx] == objectp); + + objectp->setListIndex(-1); + + S32 last_index = mActiveObjects.size()-1; + + if (idx != last_index) + { + mActiveObjects[idx] = mActiveObjects[last_index]; + mActiveObjects[idx]->setListIndex(idx); + } + + mActiveObjects.pop_back(); + } +} + +void LLViewerObjectList::updateActive(LLViewerObject *objectp) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE; + + if (objectp->isDead()) + { + return; // We don't update dead objects! + } + + bool active = objectp->isActive(); + if (active != objectp->onActiveList()) + { + if (active) + { + //LL_INFOS() << "Adding " << objectp->mID << " " << objectp->getPCodeString() << " to active list." << LL_ENDL; + S32 idx = objectp->getListIndex(); + if (idx <= -1) + { + mActiveObjects.push_back(objectp); + objectp->setListIndex(mActiveObjects.size()-1); + objectp->setOnActiveList(true); + } + else + { + llassert(idx < mActiveObjects.size()); + llassert(mActiveObjects[idx] == objectp); + + if (idx >= mActiveObjects.size() || + mActiveObjects[idx] != objectp) + { + LL_WARNS() << "Invalid object list index detected!" << LL_ENDL; + } + } + } + else + { + //LL_INFOS() << "Removing " << objectp->mID << " " << objectp->getPCodeString() << " from active list." << LL_ENDL; + removeFromActiveList(objectp); + objectp->setOnActiveList(false); + } + } + + //post condition: if object is active, it must be on the active list + llassert(!active || std::find(mActiveObjects.begin(), mActiveObjects.end(), objectp) != mActiveObjects.end()); + + //post condition: if object is not active, it must not be on the active list + llassert(active || std::find(mActiveObjects.begin(), mActiveObjects.end(), objectp) == mActiveObjects.end()); +} + +void LLViewerObjectList::updateObjectCost(LLViewerObject* object) +{ + if (!object->isRoot()) + { //always fetch cost for the parent when fetching cost for children + mStaleObjectCost.insert(((LLViewerObject*)object->getParent())->getID()); + } + mStaleObjectCost.insert(object->getID()); +} + +void LLViewerObjectList::updateObjectCost(const LLUUID& object_id, F32 object_cost, F32 link_cost, F32 physics_cost, F32 link_physics_cost) +{ + LLViewerObject* object = findObject(object_id); + if (object) + { + object->setObjectCost(object_cost); + object->setLinksetCost(link_cost); + object->setPhysicsCost(physics_cost); + object->setLinksetPhysicsCost(link_physics_cost); + } +} + +void LLViewerObjectList::onObjectCostFetchFailure(const LLUUID& object_id) +{ + //LL_WARNS() << "Failed to fetch object cost for object: " << object_id << LL_ENDL; + mPendingObjectCost.erase(object_id); +} + +void LLViewerObjectList::updatePhysicsFlags(const LLViewerObject* object) +{ + mStalePhysicsFlags.insert(object->getID()); +} + +void LLViewerObjectList::updatePhysicsShapeType(const LLUUID& object_id, S32 type) +{ + mPendingPhysicsFlags.erase(object_id); + LLViewerObject* object = findObject(object_id); + if (object) + { + object->setPhysicsShapeType(type); + } +} + +void LLViewerObjectList::updatePhysicsProperties(const LLUUID& object_id, + F32 density, + F32 friction, + F32 restitution, + F32 gravity_multiplier) +{ + mPendingPhysicsFlags.erase(object_id); + + LLViewerObject* object = findObject(object_id); + if (object) + { + object->setPhysicsDensity(density); + object->setPhysicsFriction(friction); + object->setPhysicsGravity(gravity_multiplier); + object->setPhysicsRestitution(restitution); + } +} + +void LLViewerObjectList::onPhysicsFlagsFetchFailure(const LLUUID& object_id) +{ + //LL_WARNS() << "Failed to fetch physics flags for object: " << object_id << LL_ENDL; + mPendingPhysicsFlags.erase(object_id); +} + +void LLViewerObjectList::shiftObjects(const LLVector3 &offset) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; + // This is called when we shift our origin when we cross region boundaries... + // We need to update many object caches, I'll document this more as I dig through the code + // cleaning things out... + + if (0 == offset.magVecSquared()) + { + return; + } + + + LLViewerObject *objectp; + for (vobj_list_t::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter) + { + objectp = *iter; + // There could be dead objects on the object list, so don't update stuff if the object is dead. + if (!objectp->isDead()) + { + objectp->updatePositionCaches(); + + if (objectp->mDrawable.notNull() && !objectp->mDrawable->isDead()) + { + gPipeline.markShift(objectp->mDrawable); + } + } + } + + gPipeline.shiftObjects(offset); + + LLWorld::getInstance()->shiftRegions(offset); +} + +void LLViewerObjectList::repartitionObjects() +{ + for (vobj_list_t::iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter) + { + LLViewerObject* objectp = *iter; + if (!objectp->isDead()) + { + LLDrawable* drawable = objectp->mDrawable; + if (drawable && !drawable->isDead()) + { + drawable->updateBinRadius(); + drawable->updateSpatialExtents(); + drawable->movePartition(); + } + } + } +} + +//debug code +bool LLViewerObjectList::hasMapObjectInRegion(LLViewerRegion* regionp) +{ + for (vobj_list_t::iterator iter = mMapObjects.begin(); iter != mMapObjects.end(); ++iter) + { + LLViewerObject* objectp = *iter; + + if(objectp->isDead() || objectp->getRegion() == regionp) + { + return true ; + } + } + + return false ; +} + +//make sure the region is cleaned up. +void LLViewerObjectList::clearAllMapObjectsInRegion(LLViewerRegion* regionp) +{ + std::set dead_object_list ; + std::set region_object_list ; + for (vobj_list_t::iterator iter = mMapObjects.begin(); iter != mMapObjects.end(); ++iter) + { + LLViewerObject* objectp = *iter; + + if(objectp->isDead()) + { + dead_object_list.insert(objectp) ; + } + else if(objectp->getRegion() == regionp) + { + region_object_list.insert(objectp) ; + } + } + + if(dead_object_list.size() > 0) + { + LL_WARNS() << "There are " << dead_object_list.size() << " dead objects on the map!" << LL_ENDL ; + + for(std::set::iterator iter = dead_object_list.begin(); iter != dead_object_list.end(); ++iter) + { + cleanupReferences(*iter) ; + } + } + if(region_object_list.size() > 0) + { + LL_WARNS() << "There are " << region_object_list.size() << " objects not removed from the deleted region!" << LL_ENDL ; + + for(std::set::iterator iter = region_object_list.begin(); iter != region_object_list.end(); ++iter) + { + (*iter)->markDead() ; + } + } +} + + +void LLViewerObjectList::renderObjectsForMap(LLNetMap &netmap) +{ + LLColor4 above_water_color = LLUIColorTable::instance().getColor( "NetMapOtherOwnAboveWater" ); + LLColor4 below_water_color = LLUIColorTable::instance().getColor( "NetMapOtherOwnBelowWater" ); + LLColor4 you_own_above_water_color = + LLUIColorTable::instance().getColor( "NetMapYouOwnAboveWater" ); + LLColor4 you_own_below_water_color = + LLUIColorTable::instance().getColor( "NetMapYouOwnBelowWater" ); + LLColor4 group_own_above_water_color = + LLUIColorTable::instance().getColor( "NetMapGroupOwnAboveWater" ); + LLColor4 group_own_below_water_color = + LLUIColorTable::instance().getColor( "NetMapGroupOwnBelowWater" ); + + F32 max_radius = gSavedSettings.getF32("MiniMapPrimMaxRadius"); + + for (vobj_list_t::iterator iter = mMapObjects.begin(); iter != mMapObjects.end(); ++iter) + { + LLViewerObject* objectp = *iter; + + if(objectp->isDead())//some dead objects somehow not cleaned. + { + continue ; + } + + if (!objectp->getRegion() || objectp->isOrphaned() || objectp->isAttachment()) + { + continue; + } + const LLVector3& scale = objectp->getScale(); + const LLVector3d pos = objectp->getPositionGlobal(); + const F64 water_height = F64( objectp->getRegion()->getWaterHeight() ); + // LLWorld::getInstance()->getWaterHeight(); + + F32 approx_radius = (scale.mV[VX] + scale.mV[VY]) * 0.5f * 0.5f * 1.3f; // 1.3 is a fudge + + // Limit the size of megaprims so they don't blot out everything on the minimap. + // Attempting to draw very large megaprims also causes client lag. + // See DEV-17370 and DEV-29869/SNOW-79 for details. + approx_radius = llmin(approx_radius, max_radius); + + LLColor4U color = above_water_color; + if( objectp->permYouOwner() ) + { + const F32 MIN_RADIUS_FOR_OWNED_OBJECTS = 2.f; + if( approx_radius < MIN_RADIUS_FOR_OWNED_OBJECTS ) + { + approx_radius = MIN_RADIUS_FOR_OWNED_OBJECTS; + } + + if( pos.mdV[VZ] >= water_height ) + { + if ( objectp->permGroupOwner() ) + { + color = group_own_above_water_color; + } + else + { + color = you_own_above_water_color; + } + } + else + { + if ( objectp->permGroupOwner() ) + { + color = group_own_below_water_color; + } + else + { + color = you_own_below_water_color; + } + } + } + else + if( pos.mdV[VZ] < water_height ) + { + color = below_water_color; + } + + netmap.renderScaledPointGlobal( + pos, + color, + approx_radius ); + } +} + +void LLViewerObjectList::renderObjectBounds(const LLVector3 ¢er) +{ +} + +extern bool gCubeSnapshot; + +void LLViewerObjectList::addDebugBeacon(const LLVector3 &pos_agent, + const std::string &string, + const LLColor4 &color, + const LLColor4 &text_color, + S32 line_width) +{ + llassert(!gCubeSnapshot); + LLDebugBeacon beacon; + beacon.mPositionAgent = pos_agent; + beacon.mString = string; + beacon.mColor = color; + beacon.mTextColor = text_color; + beacon.mLineWidth = line_width; + + mDebugBeacons.push_back(beacon); +} + +void LLViewerObjectList::resetObjectBeacons() +{ + mDebugBeacons.clear(); +} + +LLViewerObject *LLViewerObjectList::createObjectViewer(const LLPCode pcode, LLViewerRegion *regionp, S32 flags) +{ + LLUUID fullid; + fullid.generate(); + + LLViewerObject *objectp = LLViewerObject::createObject(fullid, pcode, regionp, flags); + if (!objectp) + { +// LL_WARNS() << "Couldn't create object of type " << LLPrimitive::pCodeToString(pcode) << LL_ENDL; + return NULL; + } + + mUUIDObjectMap[fullid] = objectp; + + mObjects.push_back(objectp); + + updateActive(objectp); + + return objectp; +} + +LLViewerObject *LLViewerObjectList::createObjectFromCache(const LLPCode pcode, LLViewerRegion *regionp, const LLUUID &uuid, const U32 local_id) +{ + llassert_always(uuid.notNull()); + + LL_DEBUGS("ObjectUpdate") << "creating " << uuid << " local_id " << local_id << LL_ENDL; + dumpStack("ObjectUpdateStack"); + + LLViewerObject *objectp = LLViewerObject::createObject(uuid, pcode, regionp); + if (!objectp) + { +// LL_WARNS() << "Couldn't create object of type " << LLPrimitive::pCodeToString(pcode) << " id:" << fullid << LL_ENDL; + return NULL; + } + + objectp->mLocalID = local_id; + mUUIDObjectMap[uuid] = objectp; + setUUIDAndLocal(uuid, + local_id, + regionp->getHost().getAddress(), + regionp->getHost().getPort()); + mObjects.push_back(objectp); + + updateActive(objectp); + + return objectp; +} + +LLViewerObject *LLViewerObjectList::createObject(const LLPCode pcode, LLViewerRegion *regionp, + const LLUUID &uuid, const U32 local_id, const LLHost &sender) +{ + LLUUID fullid; + if (uuid == LLUUID::null) + { + fullid.generate(); + } + else + { + fullid = uuid; + } + + LL_DEBUGS("ObjectUpdate") << "createObject creating " << fullid << LL_ENDL; + dumpStack("ObjectUpdateStack"); + + LLViewerObject *objectp = LLViewerObject::createObject(fullid, pcode, regionp); + if (!objectp) + { +// LL_WARNS() << "Couldn't create object of type " << LLPrimitive::pCodeToString(pcode) << " id:" << fullid << LL_ENDL; + return NULL; + } + if(regionp) + { + regionp->addToCreatedList(local_id); + } + + mUUIDObjectMap[fullid] = objectp; + setUUIDAndLocal(fullid, + local_id, + gMessageSystem->getSenderIP(), + gMessageSystem->getSenderPort()); + + mObjects.push_back(objectp); + + updateActive(objectp); + + return objectp; +} + +LLViewerObject *LLViewerObjectList::replaceObject(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) +{ + LLViewerObject *old_instance = findObject(id); + if (old_instance) + { + //cleanupReferences(old_instance); + old_instance->markDead(); + + return createObject(pcode, regionp, id, old_instance->getLocalID(), LLHost()); + } + return NULL; +} + +S32 LLViewerObjectList::findReferences(LLDrawable *drawablep) const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE; + + LLViewerObject *objectp; + S32 num_refs = 0; + + for (vobj_list_t::const_iterator iter = mObjects.begin(); iter != mObjects.end(); ++iter) + { + objectp = *iter; + if (objectp->mDrawable.notNull()) + { + num_refs += objectp->mDrawable->findReferences(drawablep); + } + } + return num_refs; +} + + +void LLViewerObjectList::orphanize(LLViewerObject *childp, U32 parent_id, U32 ip, U32 port) +{ + LL_DEBUGS("ORPHANS") << "Orphaning object " << childp->getID() << " with parent " << parent_id << LL_ENDL; + + // We're an orphan, flag things appropriately. + childp->mOrphaned = true; + if (childp->mDrawable.notNull()) + { + bool make_invisible = true; + LLViewerObject *parentp = (LLViewerObject *)childp->getParent(); + if (parentp) + { + if (parentp->getRegion() != childp->getRegion()) + { + // This is probably an object flying across a region boundary, the + // object probably ISN'T being reparented, but just got an object + // update out of order (child update before parent). + make_invisible = false; + //LL_INFOS() << "Don't make object handoffs invisible!" << LL_ENDL; + } + } + + if (make_invisible) + { + // Make sure that this object becomes invisible if it's an orphan + childp->mDrawable->setState(LLDrawable::FORCE_INVISIBLE); + } + } + + // Unknown parent, add to orpaned child list + U64 parent_info = getIndex(parent_id, ip, port); + + if (std::find(mOrphanParents.begin(), mOrphanParents.end(), parent_info) == mOrphanParents.end()) + { + mOrphanParents.push_back(parent_info); + } + + LLViewerObjectList::OrphanInfo oi(parent_info, childp->mID); + if (std::find(mOrphanChildren.begin(), mOrphanChildren.end(), oi) == mOrphanChildren.end()) + { + mOrphanChildren.push_back(oi); + mNumOrphans++; + } +} + + +void LLViewerObjectList::findOrphans(LLViewerObject* objectp, U32 ip, U32 port) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; + + if (objectp->isDead()) + { + LL_WARNS() << "Trying to find orphans for dead obj " << objectp->mID + << ":" << objectp->getPCodeString() << LL_ENDL; + return; + } + + //search object cache to get orphans + if(objectp->getRegion()) + { + objectp->getRegion()->findOrphans(objectp->getLocalID()); + } + + // See if we are a parent of an orphan. + // Note: This code is fairly inefficient but it should happen very rarely. + // It can be sped up if this is somehow a performance issue... + if (mOrphanParents.empty()) + { + // no known orphan parents + return; + } + if (std::find(mOrphanParents.begin(), mOrphanParents.end(), getIndex(objectp->mLocalID, ip, port)) == mOrphanParents.end()) + { + // did not find objectp in OrphanParent list + return; + } + + U64 parent_info = getIndex(objectp->mLocalID, ip, port); + bool orphans_found = false; + // Iterate through the orphan list, and set parents of matching children. + + for (std::vector::iterator iter = mOrphanChildren.begin(); iter != mOrphanChildren.end(); ) + { + if (iter->mParentInfo != parent_info) + { + ++iter; + continue; + } + LLViewerObject *childp = findObject(iter->mChildInfo); + if (childp) + { + if (childp == objectp) + { + LL_WARNS() << objectp->mID << " has self as parent, skipping!" + << LL_ENDL; + ++iter; + continue; + } + + LL_DEBUGS("ORPHANS") << "Reunited parent " << objectp->mID + << " with child " << childp->mID << LL_ENDL; + LL_DEBUGS("ORPHANS") << "Glob: " << objectp->getPositionGlobal() << LL_ENDL; + LL_DEBUGS("ORPHANS") << "Agent: " << objectp->getPositionAgent() << LL_ENDL; +#ifdef ORPHAN_SPAM + addDebugBeacon(objectp->getPositionAgent(),""); +#endif + gPipeline.markMoved(objectp->mDrawable); + objectp->setChanged(LLXform::MOVED | LLXform::SILHOUETTE); + + // Flag the object as no longer orphaned + childp->mOrphaned = false; + if (childp->mDrawable.notNull()) + { + // Make the drawable visible again and set the drawable parent + childp->mDrawable->clearState(LLDrawable::FORCE_INVISIBLE); + childp->setDrawableParent(objectp->mDrawable); // LLViewerObjectList::findOrphans() + gPipeline.markRebuild( childp->mDrawable, LLDrawable::REBUILD_ALL); + } + + // Make certain particles, icon and HUD aren't hidden + childp->hideExtraDisplayItems( false ); + + objectp->addChild(childp); + orphans_found = true; + ++iter; + } + else + { + LL_INFOS() << "Missing orphan child, removing from list" << LL_ENDL; + + iter = mOrphanChildren.erase(iter); + } + } + + // Remove orphan parent and children from lists now that they've been found + { + std::vector::iterator iter = std::find(mOrphanParents.begin(), mOrphanParents.end(), parent_info); + if (iter != mOrphanParents.end()) + { + mOrphanParents.erase(iter); + } + } + + for (std::vector::iterator iter = mOrphanChildren.begin(); iter != mOrphanChildren.end(); ) + { + if (iter->mParentInfo == parent_info) + { + iter = mOrphanChildren.erase(iter); + mNumOrphans--; + } + else + { + ++iter; + } + } + + if (orphans_found && objectp->isSelected()) + { + LLSelectNode* nodep = LLSelectMgr::getInstance()->getSelection()->findNode(objectp); + if (nodep && !nodep->mIndividualSelection) + { + // rebuild selection with orphans + LLSelectMgr::getInstance()->deselectObjectAndFamily(objectp); + LLSelectMgr::getInstance()->selectObjectAndFamily(objectp); + } + } +} + +//////////////////////////////////////////////////////////////////////////// + +LLViewerObjectList::OrphanInfo::OrphanInfo() + : mParentInfo(0) +{ +} + +LLViewerObjectList::OrphanInfo::OrphanInfo(const U64 parent_info, const LLUUID child_info) + : mParentInfo(parent_info), mChildInfo(child_info) +{ +} + +bool LLViewerObjectList::OrphanInfo::operator==(const OrphanInfo &rhs) const +{ + return (mParentInfo == rhs.mParentInfo) && (mChildInfo == rhs.mChildInfo); +} + +bool LLViewerObjectList::OrphanInfo::operator!=(const OrphanInfo &rhs) const +{ + return !operator==(rhs); +} + + +LLDebugBeacon::~LLDebugBeacon() +{ + if (mHUDObject.notNull()) + { + mHUDObject->markDead(); + } +} diff --git a/indra/newview/llviewerobjectlist.h b/indra/newview/llviewerobjectlist.h index f9f39b2879..f0f236d6ae 100644 --- a/indra/newview/llviewerobjectlist.h +++ b/indra/newview/llviewerobjectlist.h @@ -1,298 +1,298 @@ -/** - * @file llviewerobjectlist.h - * @brief Description of LLViewerObjectList class. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVIEWEROBJECTLIST_H -#define LL_LLVIEWEROBJECTLIST_H - -#include -#include - -// common includes -#include "llstring.h" -#include "lltrace.h" - -// project includes -#include "llviewerobject.h" -#include "lleventcoro.h" -#include "llcoros.h" - -class LLCamera; -class LLNetMap; -class LLDebugBeacon; -class LLVOCacheEntry; - -const U32 CLOSE_BIN_SIZE = 10; -const U32 NUM_BINS = 128; - -// GL name = position in object list + GL_NAME_INDEX_OFFSET so that -// we can have special numbers like zero. -const U32 GL_NAME_LAND = 0; -const U32 GL_NAME_PARCEL_WALL = 1; - -const U32 GL_NAME_INDEX_OFFSET = 10; - -class LLViewerObjectList -{ -public: - LLViewerObjectList(); - ~LLViewerObjectList(); - - void destroy(); - - // For internal use only. Does NOT take a local id, takes an index into - // an internal dynamic array. - inline LLViewerObject *getObject(const S32 index); - - inline LLViewerObject *findObject(const LLUUID &id); - LLViewerObject *createObjectViewer(const LLPCode pcode, LLViewerRegion *regionp, S32 flags = 0); // Create a viewer-side object - LLViewerObject *createObjectFromCache(const LLPCode pcode, LLViewerRegion *regionp, const LLUUID &uuid, const U32 local_id); - LLViewerObject *createObject(const LLPCode pcode, LLViewerRegion *regionp, - const LLUUID &uuid, const U32 local_id, const LLHost &sender); - - LLViewerObject *replaceObject(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); // TomY: hack to switch VO instances on the fly - - bool killObject(LLViewerObject *objectp); - void killObjects(LLViewerRegion *regionp); // Kill all objects owned by a particular region. - void killAllObjects(); - - void cleanDeadObjects(const bool use_timer = true); // Clean up the dead object list. - - // Simulator and viewer side object updates... - void processUpdateCore(LLViewerObject* objectp, void** data, U32 block, const EObjectUpdateType update_type, - LLDataPacker* dpp, bool justCreated, bool from_cache = false); - LLViewerObject* processObjectUpdateFromCache(LLVOCacheEntry* entry, LLViewerRegion* regionp); - void processObjectUpdate(LLMessageSystem *mesgsys, void **user_data, EObjectUpdateType update_type, bool compressed=false); - void processCompressedObjectUpdate(LLMessageSystem *mesgsys, void **user_data, EObjectUpdateType update_type); - void processCachedObjectUpdate(LLMessageSystem *mesgsys, void **user_data, EObjectUpdateType update_type); - void updateApparentAngles(LLAgent &agent); - void update(LLAgent &agent); - - void fetchObjectCosts(); - void fetchPhysicsFlags(); - - void updateObjectCost(LLViewerObject* object); - void updateObjectCost(const LLUUID& object_id, F32 object_cost, F32 link_cost, F32 physics_cost, F32 link_physics_cost); - void onObjectCostFetchFailure(const LLUUID& object_id); - - void updatePhysicsFlags(const LLViewerObject* object); - void onPhysicsFlagsFetchFailure(const LLUUID& object_id); - void updatePhysicsShapeType(const LLUUID& object_id, S32 type); - void updatePhysicsProperties(const LLUUID& object_id, - F32 density, - F32 friction, - F32 restitution, - F32 gravity_multiplier); - - void shiftObjects(const LLVector3 &offset); - void repartitionObjects(); - - bool hasMapObjectInRegion(LLViewerRegion* regionp) ; - void clearAllMapObjectsInRegion(LLViewerRegion* regionp) ; - void renderObjectsForMap(LLNetMap &netmap); - void renderObjectBounds(const LLVector3 ¢er); - - void addDebugBeacon(const LLVector3 &pos_agent, const std::string &string, - const LLColor4 &color=LLColor4(1.f, 0.f, 0.f, 0.5f), - const LLColor4 &text_color=LLColor4(1.f, 1.f, 1.f, 1.f), - S32 line_width = 1); - void renderObjectBeacons(); - void resetObjectBeacons(); - - void dirtyAllObjectInventory(); - - void removeFromActiveList(LLViewerObject* objectp); - void updateActive(LLViewerObject *objectp); - - void updateAvatarVisibility(); - - inline S32 getNumObjects() { return (S32) mObjects.size(); } - inline S32 getNumActiveObjects() { return (S32) mActiveObjects.size(); } - - void addToMap(LLViewerObject *objectp); - void removeFromMap(LLViewerObject *objectp); - - void clearDebugText(); - - //////////////////////////////////////////// - // - // Only accessed by markDead in LLViewerObject - void cleanupReferences(LLViewerObject *objectp); - - S32 findReferences(LLDrawable *drawablep) const; // Find references to drawable in all objects, and return value. - - S32 getOrphanParentCount() const { return (S32) mOrphanParents.size(); } - S32 getOrphanCount() const { return mNumOrphans; } - void orphanize(LLViewerObject *childp, U32 parent_id, U32 ip, U32 port); - void findOrphans(LLViewerObject* objectp, U32 ip, U32 port); - -public: - // Class for keeping track of orphaned objects - class OrphanInfo - { - public: - OrphanInfo(); - OrphanInfo(const U64 parent_info, const LLUUID child_info); - bool operator==(const OrphanInfo &rhs) const; - bool operator!=(const OrphanInfo &rhs) const; - U64 mParentInfo; - LLUUID mChildInfo; - }; - - - U32 mCurBin; // Current bin we're working on... - - // Statistics data (see also LLViewerStats) - S32 mNumNewObjects; - - // if we paused in the last frame - // used to discount stats from this frame - bool mWasPaused; - - static void getUUIDFromLocal(LLUUID &id, - const U32 local_id, - const U32 ip, - const U32 port); - static void setUUIDAndLocal(const LLUUID &id, - const U32 local_id, - const U32 ip, - const U32 port); // Requires knowledge of message system info! - - static bool removeFromLocalIDTable(const LLViewerObject* objectp); - // Used ONLY by the orphaned object code. - static U64 getIndex(const U32 local_id, const U32 ip, const U32 port); - - S32 mNumUnknownUpdates; - S32 mNumDeadObjectUpdates; - S32 mNumDeadObjects; -protected: - std::vector mOrphanParents; // LocalID/ip,port of orphaned objects - std::vector mOrphanChildren; // UUID's of orphaned objects - S32 mNumOrphans; - - typedef std::vector > vobj_list_t; - - vobj_list_t mObjects; - std::vector > mActiveObjects; - - vobj_list_t mMapObjects; - - uuid_set_t mDeadObjects; - - std::map > mUUIDObjectMap; - - //set of objects that need to update their cost - uuid_set_t mStaleObjectCost; - uuid_set_t mPendingObjectCost; - - //set of objects that need to update their physics flags - uuid_set_t mStalePhysicsFlags; - uuid_set_t mPendingPhysicsFlags; - - std::vector mDebugBeacons; - - S32 mCurLazyUpdateIndex; - - static U32 sSimulatorMachineIndex; - static std::map sIPAndPortToIndex; - - static std::map sIndexAndLocalIDToUUID; - - friend class LLViewerObject; - -private: - static void reportObjectCostFailure(LLSD &objectList); - void fetchObjectCostsCoro(std::string url); - - static void reportPhysicsFlagFailure(LLSD &obejectList); - void fetchPhisicsFlagsCoro(std::string url); - -}; - - -class LLDebugBeacon -{ -public: - ~LLDebugBeacon(); - - LLVector3 mPositionAgent; - std::string mString; - LLColor4 mColor; - LLColor4 mTextColor; - S32 mLineWidth; - LLPointer mHUDObject; -}; - - - -// Global object list -extern LLViewerObjectList gObjectList; - -// Inlines -/** - * Note: - * it will return NULL for offline avatar_id - */ -inline LLViewerObject *LLViewerObjectList::findObject(const LLUUID &id) -{ - std::map >::iterator iter = mUUIDObjectMap.find(id); - if(iter != mUUIDObjectMap.end()) - { - return iter->second; - } - else - { - return NULL; - } -} - -inline LLViewerObject *LLViewerObjectList::getObject(const S32 index) -{ - LLViewerObject *objectp; - objectp = mObjects[index]; - if (objectp->isDead()) - { - //LL_WARNS() << "Dead object " << objectp->mID << " in getObject" << LL_ENDL; - return NULL; - } - return objectp; -} - -inline void LLViewerObjectList::addToMap(LLViewerObject *objectp) -{ - mMapObjects.push_back(objectp); -} - -inline void LLViewerObjectList::removeFromMap(LLViewerObject *objectp) -{ - std::vector >::iterator iter = std::find(mMapObjects.begin(), mMapObjects.end(), objectp); - if (iter != mMapObjects.end()) - { - mMapObjects.erase(iter); - } -} - - -#endif // LL_VIEWER_OBJECT_LIST_H +/** + * @file llviewerobjectlist.h + * @brief Description of LLViewerObjectList class. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVIEWEROBJECTLIST_H +#define LL_LLVIEWEROBJECTLIST_H + +#include +#include + +// common includes +#include "llstring.h" +#include "lltrace.h" + +// project includes +#include "llviewerobject.h" +#include "lleventcoro.h" +#include "llcoros.h" + +class LLCamera; +class LLNetMap; +class LLDebugBeacon; +class LLVOCacheEntry; + +const U32 CLOSE_BIN_SIZE = 10; +const U32 NUM_BINS = 128; + +// GL name = position in object list + GL_NAME_INDEX_OFFSET so that +// we can have special numbers like zero. +const U32 GL_NAME_LAND = 0; +const U32 GL_NAME_PARCEL_WALL = 1; + +const U32 GL_NAME_INDEX_OFFSET = 10; + +class LLViewerObjectList +{ +public: + LLViewerObjectList(); + ~LLViewerObjectList(); + + void destroy(); + + // For internal use only. Does NOT take a local id, takes an index into + // an internal dynamic array. + inline LLViewerObject *getObject(const S32 index); + + inline LLViewerObject *findObject(const LLUUID &id); + LLViewerObject *createObjectViewer(const LLPCode pcode, LLViewerRegion *regionp, S32 flags = 0); // Create a viewer-side object + LLViewerObject *createObjectFromCache(const LLPCode pcode, LLViewerRegion *regionp, const LLUUID &uuid, const U32 local_id); + LLViewerObject *createObject(const LLPCode pcode, LLViewerRegion *regionp, + const LLUUID &uuid, const U32 local_id, const LLHost &sender); + + LLViewerObject *replaceObject(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); // TomY: hack to switch VO instances on the fly + + bool killObject(LLViewerObject *objectp); + void killObjects(LLViewerRegion *regionp); // Kill all objects owned by a particular region. + void killAllObjects(); + + void cleanDeadObjects(const bool use_timer = true); // Clean up the dead object list. + + // Simulator and viewer side object updates... + void processUpdateCore(LLViewerObject* objectp, void** data, U32 block, const EObjectUpdateType update_type, + LLDataPacker* dpp, bool justCreated, bool from_cache = false); + LLViewerObject* processObjectUpdateFromCache(LLVOCacheEntry* entry, LLViewerRegion* regionp); + void processObjectUpdate(LLMessageSystem *mesgsys, void **user_data, EObjectUpdateType update_type, bool compressed=false); + void processCompressedObjectUpdate(LLMessageSystem *mesgsys, void **user_data, EObjectUpdateType update_type); + void processCachedObjectUpdate(LLMessageSystem *mesgsys, void **user_data, EObjectUpdateType update_type); + void updateApparentAngles(LLAgent &agent); + void update(LLAgent &agent); + + void fetchObjectCosts(); + void fetchPhysicsFlags(); + + void updateObjectCost(LLViewerObject* object); + void updateObjectCost(const LLUUID& object_id, F32 object_cost, F32 link_cost, F32 physics_cost, F32 link_physics_cost); + void onObjectCostFetchFailure(const LLUUID& object_id); + + void updatePhysicsFlags(const LLViewerObject* object); + void onPhysicsFlagsFetchFailure(const LLUUID& object_id); + void updatePhysicsShapeType(const LLUUID& object_id, S32 type); + void updatePhysicsProperties(const LLUUID& object_id, + F32 density, + F32 friction, + F32 restitution, + F32 gravity_multiplier); + + void shiftObjects(const LLVector3 &offset); + void repartitionObjects(); + + bool hasMapObjectInRegion(LLViewerRegion* regionp) ; + void clearAllMapObjectsInRegion(LLViewerRegion* regionp) ; + void renderObjectsForMap(LLNetMap &netmap); + void renderObjectBounds(const LLVector3 ¢er); + + void addDebugBeacon(const LLVector3 &pos_agent, const std::string &string, + const LLColor4 &color=LLColor4(1.f, 0.f, 0.f, 0.5f), + const LLColor4 &text_color=LLColor4(1.f, 1.f, 1.f, 1.f), + S32 line_width = 1); + void renderObjectBeacons(); + void resetObjectBeacons(); + + void dirtyAllObjectInventory(); + + void removeFromActiveList(LLViewerObject* objectp); + void updateActive(LLViewerObject *objectp); + + void updateAvatarVisibility(); + + inline S32 getNumObjects() { return (S32) mObjects.size(); } + inline S32 getNumActiveObjects() { return (S32) mActiveObjects.size(); } + + void addToMap(LLViewerObject *objectp); + void removeFromMap(LLViewerObject *objectp); + + void clearDebugText(); + + //////////////////////////////////////////// + // + // Only accessed by markDead in LLViewerObject + void cleanupReferences(LLViewerObject *objectp); + + S32 findReferences(LLDrawable *drawablep) const; // Find references to drawable in all objects, and return value. + + S32 getOrphanParentCount() const { return (S32) mOrphanParents.size(); } + S32 getOrphanCount() const { return mNumOrphans; } + void orphanize(LLViewerObject *childp, U32 parent_id, U32 ip, U32 port); + void findOrphans(LLViewerObject* objectp, U32 ip, U32 port); + +public: + // Class for keeping track of orphaned objects + class OrphanInfo + { + public: + OrphanInfo(); + OrphanInfo(const U64 parent_info, const LLUUID child_info); + bool operator==(const OrphanInfo &rhs) const; + bool operator!=(const OrphanInfo &rhs) const; + U64 mParentInfo; + LLUUID mChildInfo; + }; + + + U32 mCurBin; // Current bin we're working on... + + // Statistics data (see also LLViewerStats) + S32 mNumNewObjects; + + // if we paused in the last frame + // used to discount stats from this frame + bool mWasPaused; + + static void getUUIDFromLocal(LLUUID &id, + const U32 local_id, + const U32 ip, + const U32 port); + static void setUUIDAndLocal(const LLUUID &id, + const U32 local_id, + const U32 ip, + const U32 port); // Requires knowledge of message system info! + + static bool removeFromLocalIDTable(const LLViewerObject* objectp); + // Used ONLY by the orphaned object code. + static U64 getIndex(const U32 local_id, const U32 ip, const U32 port); + + S32 mNumUnknownUpdates; + S32 mNumDeadObjectUpdates; + S32 mNumDeadObjects; +protected: + std::vector mOrphanParents; // LocalID/ip,port of orphaned objects + std::vector mOrphanChildren; // UUID's of orphaned objects + S32 mNumOrphans; + + typedef std::vector > vobj_list_t; + + vobj_list_t mObjects; + std::vector > mActiveObjects; + + vobj_list_t mMapObjects; + + uuid_set_t mDeadObjects; + + std::map > mUUIDObjectMap; + + //set of objects that need to update their cost + uuid_set_t mStaleObjectCost; + uuid_set_t mPendingObjectCost; + + //set of objects that need to update their physics flags + uuid_set_t mStalePhysicsFlags; + uuid_set_t mPendingPhysicsFlags; + + std::vector mDebugBeacons; + + S32 mCurLazyUpdateIndex; + + static U32 sSimulatorMachineIndex; + static std::map sIPAndPortToIndex; + + static std::map sIndexAndLocalIDToUUID; + + friend class LLViewerObject; + +private: + static void reportObjectCostFailure(LLSD &objectList); + void fetchObjectCostsCoro(std::string url); + + static void reportPhysicsFlagFailure(LLSD &obejectList); + void fetchPhisicsFlagsCoro(std::string url); + +}; + + +class LLDebugBeacon +{ +public: + ~LLDebugBeacon(); + + LLVector3 mPositionAgent; + std::string mString; + LLColor4 mColor; + LLColor4 mTextColor; + S32 mLineWidth; + LLPointer mHUDObject; +}; + + + +// Global object list +extern LLViewerObjectList gObjectList; + +// Inlines +/** + * Note: + * it will return NULL for offline avatar_id + */ +inline LLViewerObject *LLViewerObjectList::findObject(const LLUUID &id) +{ + std::map >::iterator iter = mUUIDObjectMap.find(id); + if(iter != mUUIDObjectMap.end()) + { + return iter->second; + } + else + { + return NULL; + } +} + +inline LLViewerObject *LLViewerObjectList::getObject(const S32 index) +{ + LLViewerObject *objectp; + objectp = mObjects[index]; + if (objectp->isDead()) + { + //LL_WARNS() << "Dead object " << objectp->mID << " in getObject" << LL_ENDL; + return NULL; + } + return objectp; +} + +inline void LLViewerObjectList::addToMap(LLViewerObject *objectp) +{ + mMapObjects.push_back(objectp); +} + +inline void LLViewerObjectList::removeFromMap(LLViewerObject *objectp) +{ + std::vector >::iterator iter = std::find(mMapObjects.begin(), mMapObjects.end(), objectp); + if (iter != mMapObjects.end()) + { + mMapObjects.erase(iter); + } +} + + +#endif // LL_VIEWER_OBJECT_LIST_H diff --git a/indra/newview/llvieweroctree.cpp b/indra/newview/llvieweroctree.cpp index 595be47fa3..b3cdc7a2d2 100644 --- a/indra/newview/llvieweroctree.cpp +++ b/indra/newview/llvieweroctree.cpp @@ -1,1550 +1,1550 @@ -/** - * @file llvieweroctree.cpp - * @brief LLViewerOctreeGroup class implementation and supporting functions - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llvieweroctree.h" -#include "llviewerregion.h" -#include "pipeline.h" -#include "llviewercontrol.h" -#include "llappviewer.h" -#include "llglslshader.h" -#include "llviewershadermgr.h" -#include "lldrawpoolwater.h" - -//----------------------------------------------------------------------------------- -//static variables definitions -//----------------------------------------------------------------------------------- -U32 LLViewerOctreeEntryData::sCurVisible = 10; //reserve the low numbers for special use. -bool LLViewerOctreeDebug::sInDebug = false; - -static LLTrace::CountStatHandle sOcclusionQueries("occlusion_queries", "Number of occlusion queries executed"), - sNumObjectsOccluded("occluded_objects", "Count of objects being occluded by a query"), - sNumObjectsUnoccluded("unoccluded_objects", "Count of objects being unoccluded by a query"); - -//----------------------------------------------------------------------------------- -//some global functions definitions -//----------------------------------------------------------------------------------- -typedef enum -{ - b000 = 0x00, - b001 = 0x01, - b010 = 0x02, - b011 = 0x03, - b100 = 0x04, - b101 = 0x05, - b110 = 0x06, - b111 = 0x07, -} eLoveTheBits; - -//contact Runitai Linden for a copy of the SL object used to write this table -//basically, you give the table a bitmask of the look-at vector to a node and it -//gives you a triangle fan index array -static U16 sOcclusionIndices[] = -{ - //000 - b111, b110, b010, b011, b001, b101, b100, b110, - //001 - b011, b010, b000, b001, b101, b111, b110, b010, - //010 - b101, b100, b110, b111, b011, b001, b000, b100, - //011 - b001, b000, b100, b101, b111, b011, b010, b000, - //100 - b110, b000, b010, b011, b111, b101, b100, b000, - //101 - b010, b100, b000, b001, b011, b111, b110, b100, - //110 - b100, b010, b110, b111, b101, b001, b000, b010, - //111 - b000, b110, b100, b101, b001, b011, b010, b110, -}; - -U32 get_box_fan_indices(LLCamera* camera, const LLVector4a& center) -{ - LLVector4a origin; - origin.load3(camera->getOrigin().mV); - - S32 cypher = center.greaterThan(origin).getGatheredBits() & 0x7; - - return cypher*8; -} - -U8* get_box_fan_indices_ptr(LLCamera* camera, const LLVector4a& center) -{ - LLVector4a origin; - origin.load3(camera->getOrigin().mV); - - S32 cypher = center.greaterThan(origin).getGatheredBits() & 0x7; - - return (U8*) (sOcclusionIndices+cypher*8); -} - -//create a vertex buffer for efficiently rendering cubes -LLVertexBuffer* ll_create_cube_vb(U32 type_mask) -{ - LLVertexBuffer* ret = new LLVertexBuffer(type_mask); - - ret->allocateBuffer(8, 64); - - LLStrider pos; - LLStrider idx; - - ret->getVertexStrider(pos); - ret->getIndexStrider(idx); - - pos[0] = LLVector3(-1,-1,-1); - pos[1] = LLVector3(-1,-1, 1); - pos[2] = LLVector3(-1, 1,-1); - pos[3] = LLVector3(-1, 1, 1); - pos[4] = LLVector3( 1,-1,-1); - pos[5] = LLVector3( 1,-1, 1); - pos[6] = LLVector3( 1, 1,-1); - pos[7] = LLVector3( 1, 1, 1); - - for (U32 i = 0; i < 64; i++) - { - idx[i] = sOcclusionIndices[i]; - } - - ret->unmapBuffer(); - - return ret; -} - - -#define LL_TRACK_PENDING_OCCLUSION_QUERIES 0 - -const F32 SG_OCCLUSION_FUDGE = 0.25f; -#define SG_DISCARD_TOLERANCE 0.01f - - -S32 AABBSphereIntersect(const LLVector3& min, const LLVector3& max, const LLVector3 &origin, const F32 &rad) -{ - return AABBSphereIntersectR2(min, max, origin, rad*rad); -} - -S32 AABBSphereIntersectR2(const LLVector3& min, const LLVector3& max, const LLVector3 &origin, const F32 &r) -{ - F32 d = 0.f; - F32 t; - - if ((min-origin).magVecSquared() < r && - (max-origin).magVecSquared() < r) - { - return 2; - } - - for (U32 i = 0; i < 3; i++) - { - if (origin.mV[i] < min.mV[i]) - { - t = min.mV[i] - origin.mV[i]; - d += t*t; - } - else if (origin.mV[i] > max.mV[i]) - { - t = origin.mV[i] - max.mV[i]; - d += t*t; - } - - if (d > r) - { - return 0; - } - } - - return 1; -} - - -S32 AABBSphereIntersect(const LLVector4a& min, const LLVector4a& max, const LLVector3 &origin, const F32 &rad) -{ - return AABBSphereIntersectR2(min, max, origin, rad*rad); -} - -S32 AABBSphereIntersectR2(const LLVector4a& min, const LLVector4a& max, const LLVector3 &origin, const F32 &r) -{ - F32 d = 0.f; - F32 t; - - LLVector4a origina; - origina.load3(origin.mV); - - LLVector4a v; - v.setSub(min, origina); - - if (v.dot3(v) < r) - { - v.setSub(max, origina); - if (v.dot3(v) < r) - { - return 2; - } - } - - - for (U32 i = 0; i < 3; i++) - { - if (origin.mV[i] < min[i]) - { - t = min[i] - origin.mV[i]; - d += t*t; - } - else if (origin.mV[i] > max[i]) - { - t = origin.mV[i] - max[i]; - d += t*t; - } - - if (d > r) - { - return 0; - } - } - - return 1; -} - -//----------------------------------------------------------------------------------- -//class LLViewerOctreeEntry definitions -//----------------------------------------------------------------------------------- -LLViewerOctreeEntry::LLViewerOctreeEntry() -: mGroup(NULL), - mBinRadius(0.f), - mBinIndex(-1), - mVisible(0) -{ - mPositionGroup.clear(); - mExtents[0].clear(); - mExtents[1].clear(); - - for(S32 i = 0; i < NUM_DATA_TYPE; i++) - { - mData[i] = NULL; - } -} - -LLViewerOctreeEntry::~LLViewerOctreeEntry() -{ - llassert(!mGroup); -} - -void LLViewerOctreeEntry::addData(LLViewerOctreeEntryData* data) -{ - //llassert(mData[data->getDataType()] == NULL); - llassert(data != NULL); - - mData[data->getDataType()] = data; -} - -void LLViewerOctreeEntry::removeData(LLViewerOctreeEntryData* data) -{ - //llassert(data->getDataType() != LLVOCACHEENTRY); //can not remove VOCache entry - - if(!mData[data->getDataType()]) - { - return; - } - if(mData[data->getDataType()] != data) - { - return; - } - - mData[data->getDataType()] = NULL; - - if(mGroup != NULL && !mData[LLDRAWABLE]) - { - LLViewerOctreeGroup* group = mGroup; - mGroup = NULL; - group->removeFromGroup(data); - - llassert(mBinIndex == -1); - } -} - -//called by group handleDestruction() ONLY when group is destroyed by octree. -void LLViewerOctreeEntry::nullGroup() -{ - mGroup = NULL; -} - -void LLViewerOctreeEntry::setGroup(LLViewerOctreeGroup* group) -{ - if(mGroup == group) - { - return; - } - - if(mGroup) - { - LLViewerOctreeGroup* old_group = mGroup; - mGroup = NULL; - old_group->removeFromGroup(this); - - llassert(mBinIndex == -1); - } - - mGroup = group; -} - -//----------------------------------------------------------------------------------- -//class LLViewerOctreeEntryData definitions -//----------------------------------------------------------------------------------- -LLViewerOctreeEntryData::~LLViewerOctreeEntryData() -{ - if(mEntry) - { - mEntry->removeData(this); - } -} - -LLViewerOctreeEntryData::LLViewerOctreeEntryData(LLViewerOctreeEntry::eEntryDataType_t data_type) - : mDataType(data_type), - mEntry(NULL) -{ -} - -//virtual -void LLViewerOctreeEntryData::setOctreeEntry(LLViewerOctreeEntry* entry) -{ - llassert_always(mEntry.isNull()); - - if(mEntry.notNull()) - { - return; - } - - if(!entry) - { - mEntry = new LLViewerOctreeEntry(); - } - else - { - mEntry = entry; - } - mEntry->addData(this); -} - -void LLViewerOctreeEntryData::removeOctreeEntry() -{ - if(mEntry) - { - mEntry->removeData(this); - mEntry = NULL; - } -} - -void LLViewerOctreeEntryData::setSpatialExtents(const LLVector3& min, const LLVector3& max) -{ - mEntry->mExtents[0].load3(min.mV); - mEntry->mExtents[1].load3(max.mV); -} - -void LLViewerOctreeEntryData::setSpatialExtents(const LLVector4a& min, const LLVector4a& max) -{ - mEntry->mExtents[0] = min; - mEntry->mExtents[1] = max; -} - -void LLViewerOctreeEntryData::setPositionGroup(const LLVector4a& pos) -{ - mEntry->mPositionGroup = pos; -} - -const LLVector4a* LLViewerOctreeEntryData::getSpatialExtents() const -{ - return mEntry->getSpatialExtents(); -} - -//virtual -void LLViewerOctreeEntryData::setGroup(LLViewerOctreeGroup* group) -{ - mEntry->setGroup(group); -} - -void LLViewerOctreeEntryData::shift(const LLVector4a &shift_vector) -{ - mEntry->mExtents[0].add(shift_vector); - mEntry->mExtents[1].add(shift_vector); - mEntry->mPositionGroup.add(shift_vector); -} - -LLViewerOctreeGroup* LLViewerOctreeEntryData::getGroup()const -{ - return mEntry.notNull() ? mEntry->mGroup : NULL; -} - -const LLVector4a& LLViewerOctreeEntryData::getPositionGroup() const -{ - return mEntry->getPositionGroup(); -} - -//virtual -bool LLViewerOctreeEntryData::isVisible() const -{ - if(mEntry) - { - return mEntry->mVisible == sCurVisible; - } - return false; -} - -//virtual -bool LLViewerOctreeEntryData::isRecentlyVisible() const -{ - if(!mEntry) - { - return false; - } - - if(isVisible()) - { - return true; - } - if(getGroup() && getGroup()->isRecentlyVisible()) - { - setVisible(); - return true; - } - - return false; -} - -void LLViewerOctreeEntryData::setVisible() const -{ - if(mEntry) - { - mEntry->mVisible = sCurVisible; - } -} - -void LLViewerOctreeEntryData::resetVisible() const -{ - if(mEntry) - { - mEntry->mVisible = 0; - } -} -//----------------------------------------------------------------------------------- -//class LLViewerOctreeGroup definitions -//----------------------------------------------------------------------------------- - -LLViewerOctreeGroup::~LLViewerOctreeGroup() -{ - //empty here -} - -LLViewerOctreeGroup::LLViewerOctreeGroup(OctreeNode* node) -: mOctreeNode(node), - mAnyVisible(0), - mState(CLEAN) -{ - LLVector4a tmp; - tmp.splat(0.f); - mExtents[0] = mExtents[1] = mObjectBounds[0] = mObjectBounds[1] = - mObjectExtents[0] = mObjectExtents[1] = tmp; - - mBounds[0] = node->getCenter(); - mBounds[1] = node->getSize(); - - mOctreeNode->addListener(this); -} - -bool LLViewerOctreeGroup::hasElement(LLViewerOctreeEntryData* data) -{ - if(!data->getEntry()) - { - return false; - } - return std::find(getDataBegin(), getDataEnd(), data->getEntry()) != getDataEnd(); -} - -bool LLViewerOctreeGroup::removeFromGroup(LLViewerOctreeEntryData* data) -{ - return removeFromGroup(data->getEntry()); -} - -bool LLViewerOctreeGroup::removeFromGroup(LLViewerOctreeEntry* entry) -{ - llassert(entry != NULL); - llassert(!entry->getGroup()); - - if(isDead()) //group is about to be destroyed, not need to double delete the entry. - { - entry->setBinIndex(-1); - return true; - } - - unbound(); - setState(OBJECT_DIRTY); - - if (mOctreeNode) - { - if (!mOctreeNode->remove(entry)) //this could cause *this* pointer to be destroyed, so no more function calls after this. - { - OCT_ERRS << "Could not remove LLVOCacheEntry from LLVOCacheOctreeGroup" << LL_ENDL; - return false; - } - } - - return true; -} - -//virtual -void LLViewerOctreeGroup::unbound() -{ - LL_PROFILE_ZONE_SCOPED; - if (isDirty()) - { - return; - } - - setState(DIRTY); - - //all the parent nodes need to rebound this child - if (mOctreeNode) - { - OctreeNode* parent = (OctreeNode*) mOctreeNode->getParent(); - while (parent != NULL) - { - LLViewerOctreeGroup* group = (LLViewerOctreeGroup*) parent->getListener(0); - if (!group || group->isDirty()) - { - return; - } - - group->setState(DIRTY); - parent = (OctreeNode*) parent->getParent(); - } - } -} - -//virtual -void LLViewerOctreeGroup::rebound() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_OCTREE; - if (!isDirty()) - { - return; - } - - if (mOctreeNode->getChildCount() == 1 && mOctreeNode->getElementCount() == 0) - { - LLViewerOctreeGroup* group = (LLViewerOctreeGroup*) mOctreeNode->getChild(0)->getListener(0); - group->rebound(); - - //copy single child's bounding box - mBounds[0] = group->mBounds[0]; - mBounds[1] = group->mBounds[1]; - mExtents[0] = group->mExtents[0]; - mExtents[1] = group->mExtents[1]; - - group->setState(SKIP_FRUSTUM_CHECK); - } - else if (mOctreeNode->getChildCount() == 0) - { //copy object bounding box if this is a leaf - boundObjects(true, mExtents[0], mExtents[1]); - mBounds[0] = mObjectBounds[0]; - mBounds[1] = mObjectBounds[1]; - } - else - { - LLVector4a& newMin = mExtents[0]; - LLVector4a& newMax = mExtents[1]; - LLViewerOctreeGroup* group = (LLViewerOctreeGroup*) mOctreeNode->getChild(0)->getListener(0); - group->clearState(SKIP_FRUSTUM_CHECK); - group->rebound(); - //initialize to first child - newMin = group->mExtents[0]; - newMax = group->mExtents[1]; - - //first, rebound children - for (U32 i = 1; i < mOctreeNode->getChildCount(); i++) - { - group = (LLViewerOctreeGroup*) mOctreeNode->getChild(i)->getListener(0); - group->clearState(SKIP_FRUSTUM_CHECK); - group->rebound(); - const LLVector4a& max = group->mExtents[1]; - const LLVector4a& min = group->mExtents[0]; - - newMax.setMax(newMax, max); - newMin.setMin(newMin, min); - } - - boundObjects(false, newMin, newMax); - - mBounds[0].setAdd(newMin, newMax); - mBounds[0].mul(0.5f); - mBounds[1].setSub(newMax, newMin); - mBounds[1].mul(0.5f); - } - - clearState(DIRTY); - - return; -} - -//virtual -void LLViewerOctreeGroup::handleInsertion(const TreeNode* node, LLViewerOctreeEntry* obj) -{ - obj->setGroup(this); - unbound(); - setState(OBJECT_DIRTY); -} - -//virtual -void LLViewerOctreeGroup::handleRemoval(const TreeNode* node, LLViewerOctreeEntry* obj) -{ - unbound(); - setState(OBJECT_DIRTY); - - obj->setGroup(NULL); //this could cause *this* pointer to be destroyed. So no more function calls after this. -} - -//virtual -void LLViewerOctreeGroup::handleDestruction(const TreeNode* node) -{ - if (isDead()) - { - return; - } - setState(DEAD); - for (OctreeNode::element_iter i = mOctreeNode->getDataBegin(); i != mOctreeNode->getDataEnd(); ++i) - { - LLViewerOctreeEntry* obj = *i; - if (obj && obj->getGroup() == this) - { - obj->nullGroup(); - } - } - mOctreeNode = NULL; -} - -//virtual -void LLViewerOctreeGroup::handleStateChange(const TreeNode* node) -{ - //drop bounding box upon state change - if (mOctreeNode != node) - { - mOctreeNode = (OctreeNode*) node; - } - unbound(); -} - -//virtual -void LLViewerOctreeGroup::handleChildAddition(const OctreeNode* parent, OctreeNode* child) -{ - if (child->getListenerCount() == 0) - { - new LLViewerOctreeGroup(child); - } - else - { - OCT_ERRS << "LLViewerOctreeGroup redundancy detected." << LL_ENDL; - } - - unbound(); - - ((LLViewerOctreeGroup*)child->getListener(0))->unbound(); -} - -//virtual -void LLViewerOctreeGroup::handleChildRemoval(const OctreeNode* parent, const OctreeNode* child) -{ - unbound(); -} - -LLViewerOctreeGroup* LLViewerOctreeGroup::getParent() -{ - if (isDead()) - { - return NULL; - } - - if(!mOctreeNode) - { - return NULL; - } - - OctreeNode* parent = mOctreeNode->getOctParent(); - - if (parent) - { - return (LLViewerOctreeGroup*) parent->getListener(0); - } - - return NULL; -} - -//virtual -bool LLViewerOctreeGroup::boundObjects(bool empty, LLVector4a& minOut, LLVector4a& maxOut) -{ - const OctreeNode* node = mOctreeNode; - - if (node->isEmpty()) - { //don't do anything if there are no objects - if (empty && mOctreeNode->getParent()) - { //only root is allowed to be empty - OCT_ERRS << "Empty leaf found in octree." << LL_ENDL; - } - return false; - } - - LLVector4a& newMin = mObjectExtents[0]; - LLVector4a& newMax = mObjectExtents[1]; - - if (hasState(OBJECT_DIRTY)) - { //calculate new bounding box - clearState(OBJECT_DIRTY); - - //initialize bounding box to first element - OctreeNode::const_element_iter i = node->getDataBegin(); - LLViewerOctreeEntry* entry = *i; - const LLVector4a* minMax = entry->getSpatialExtents(); - - newMin = minMax[0]; - newMax = minMax[1]; - - for (++i; i != node->getDataEnd(); ++i) - { - entry = *i; - minMax = entry->getSpatialExtents(); - - update_min_max(newMin, newMax, minMax[0]); - update_min_max(newMin, newMax, minMax[1]); - } - - mObjectBounds[0].setAdd(newMin, newMax); - mObjectBounds[0].mul(0.5f); - mObjectBounds[1].setSub(newMax, newMin); - mObjectBounds[1].mul(0.5f); - } - - if (empty) - { - minOut = newMin; - maxOut = newMax; - } - else - { - minOut.setMin(minOut, newMin); - maxOut.setMax(maxOut, newMax); - } - - return true; -} - -//virtual -bool LLViewerOctreeGroup::isVisible() const -{ - return mVisible[LLViewerCamera::sCurCameraID] >= LLViewerOctreeEntryData::getCurrentFrame(); -} - -//virtual -bool LLViewerOctreeGroup::isRecentlyVisible() const -{ - return false; -} - -void LLViewerOctreeGroup::setVisible() -{ - mVisible[LLViewerCamera::sCurCameraID] = LLViewerOctreeEntryData::getCurrentFrame(); - - if(LLViewerCamera::sCurCameraID < LLViewerCamera::CAMERA_WATER0) - { - mAnyVisible = LLViewerOctreeEntryData::getCurrentFrame(); - } -} - -void LLViewerOctreeGroup::checkStates() -{ -#if LL_OCTREE_PARANOIA_CHECK - //LLOctreeStateCheck checker; - //checker.traverse(mOctreeNode); -#endif -} - -//------------------------------------------------------------------------------------------- -//occulsion culling functions and classes -//------------------------------------------------------------------------------------------- -std::set LLOcclusionCullingGroup::sPendingQueries; - -static std::queue sFreeQueries; - -#define QUERY_POOL_SIZE 1024 - -U32 LLOcclusionCullingGroup::getNewOcclusionQueryObjectName() -{ - LL_PROFILE_ZONE_SCOPED; - - if (sFreeQueries.empty()) - { - //seed 1024 query names into the free query pool - GLuint queries[1024]; - glGenQueries(1024, queries); - for (int i = 0; i < 1024; ++i) - { - sFreeQueries.push(queries[i]); - } - } - - // pull from pool - GLuint ret = sFreeQueries.front(); - sFreeQueries.pop(); - return ret; -} - -void LLOcclusionCullingGroup::releaseOcclusionQueryObjectName(GLuint name) -{ - if (name != 0) - { - LL_PROFILE_ZONE_SCOPED; - sFreeQueries.push(name); - } -} - -//===================================== -// Occlusion State Set/Clear -//===================================== -class LLSpatialSetOcclusionState : public OctreeTraveler -{ -public: - U32 mState; - LLSpatialSetOcclusionState(U32 state) : mState(state) { } - virtual void visit(const OctreeNode* branch) - { - LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*)branch->getListener(0); - if(group) - { - group->setOcclusionState(mState); - } - } -}; - -class LLSpatialSetOcclusionStateDiff : public LLSpatialSetOcclusionState -{ -public: - LLSpatialSetOcclusionStateDiff(U32 state) : LLSpatialSetOcclusionState(state) { } - - virtual void traverse(const OctreeNode* n) - { - LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*) n->getListener(0); - - if (group && !group->isOcclusionState(mState)) - { - OctreeTraveler::traverse(n); - } - } -}; - - -LLOcclusionCullingGroup::LLOcclusionCullingGroup(OctreeNode* node, LLViewerOctreePartition* part) : - LLViewerOctreeGroup(node), - mSpatialPartition(part) -{ - part->mLODSeed = (part->mLODSeed+1)%part->mLODPeriod; - mLODHash = part->mLODSeed; - - OctreeNode* oct_parent = node->getOctParent(); - LLOcclusionCullingGroup* parent = oct_parent ? (LLOcclusionCullingGroup*) oct_parent->getListener(0) : NULL; - - for (U32 i = 0; i < LLViewerCamera::NUM_CAMERAS; i++) - { - mOcclusionQuery[i] = 0; - mOcclusionCheckCount[i] = 0; - mOcclusionIssued[i] = 0; - mOcclusionState[i] = parent ? SG_STATE_INHERIT_MASK & parent->mOcclusionState[i] : 0; - mVisible[i] = 0; - } -} - -LLOcclusionCullingGroup::~LLOcclusionCullingGroup() -{ - releaseOcclusionQueryObjectNames(); -} - -bool LLOcclusionCullingGroup::needsUpdate() -{ - return LLDrawable::getCurrentFrame() % mSpatialPartition->mLODPeriod == mLODHash; -} - -bool LLOcclusionCullingGroup::isRecentlyVisible() const -{ - const S32 MIN_VIS_FRAME_RANGE = 2; - return (LLDrawable::getCurrentFrame() - mVisible[LLViewerCamera::sCurCameraID]) < MIN_VIS_FRAME_RANGE ; -} - -bool LLOcclusionCullingGroup::isAnyRecentlyVisible() const -{ - const S32 MIN_VIS_FRAME_RANGE = 2; - return (LLDrawable::getCurrentFrame() - mAnyVisible) < MIN_VIS_FRAME_RANGE ; -} - -//virtual -void LLOcclusionCullingGroup::handleChildAddition(const OctreeNode* parent, OctreeNode* child) -{ - if (!child->hasListeners()) - { - new LLOcclusionCullingGroup(child, mSpatialPartition); - } - else - { - OCT_ERRS << "LLOcclusionCullingGroup redundancy detected." << LL_ENDL; - } - - unbound(); - - ((LLViewerOctreeGroup*)child->getListener(0))->unbound(); -} - -void LLOcclusionCullingGroup::releaseOcclusionQueryObjectNames() -{ - for (U32 i = 0; i < LLViewerCamera::NUM_CAMERAS; ++i) - { - if (mOcclusionQuery[i]) - { - releaseOcclusionQueryObjectName(mOcclusionQuery[i]); - mOcclusionQuery[i] = 0; - } - } -} - -void LLOcclusionCullingGroup::setOcclusionState(U32 state, S32 mode /* = STATE_MODE_SINGLE */ ) -{ - switch (mode) - { - case STATE_MODE_SINGLE: - if (state & OCCLUDED) - { - add(sNumObjectsOccluded, 1); - } - mOcclusionState[LLViewerCamera::sCurCameraID] |= state; - if ((state & DISCARD_QUERY) && mOcclusionQuery[LLViewerCamera::sCurCameraID]) - { - releaseOcclusionQueryObjectName(mOcclusionQuery[LLViewerCamera::sCurCameraID]); - mOcclusionQuery[LLViewerCamera::sCurCameraID] = 0; - } - break; - - case STATE_MODE_DIFF: - if (mOctreeNode) - { - LLSpatialSetOcclusionStateDiff setter(state); - setter.traverse(mOctreeNode); - } - break; - - case STATE_MODE_BRANCH: - if (mOctreeNode) - { - LLSpatialSetOcclusionState setter(state); - setter.traverse(mOctreeNode); - } - break; - - case STATE_MODE_ALL_CAMERAS: - for (U32 i = 0; i < LLViewerCamera::NUM_CAMERAS; i++) - { - mOcclusionState[i] |= state; - - if ((state & DISCARD_QUERY) && mOcclusionQuery[i]) - { - releaseOcclusionQueryObjectName(mOcclusionQuery[i]); - mOcclusionQuery[i] = 0; - } - } - break; - - default: - break; - } -} - -class LLSpatialClearOcclusionState : public OctreeTraveler -{ -public: - U32 mState; - - LLSpatialClearOcclusionState(U32 state) : mState(state) { } - virtual void visit(const OctreeNode* branch) - { - LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*)branch->getListener(0); - if(group) - { - group->clearOcclusionState(mState); - } - } -}; - -class LLSpatialClearOcclusionStateDiff : public LLSpatialClearOcclusionState -{ -public: - LLSpatialClearOcclusionStateDiff(U32 state) : LLSpatialClearOcclusionState(state) { } - - virtual void traverse(const OctreeNode* n) - { - LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*) n->getListener(0); - - if (group && group->isOcclusionState(mState)) - { - OctreeTraveler::traverse(n); - } - } -}; - -void LLOcclusionCullingGroup::clearOcclusionState(U32 state, S32 mode /* = STATE_MODE_SINGLE */) -{ - switch (mode) - { - case STATE_MODE_SINGLE: - if (state & OCCLUDED) - { - add(sNumObjectsUnoccluded, 1); - } - mOcclusionState[LLViewerCamera::sCurCameraID] &= ~state; - break; - - case STATE_MODE_DIFF: - if (mOctreeNode) - { - LLSpatialClearOcclusionStateDiff clearer(state); - clearer.traverse(mOctreeNode); - } - break; - - case STATE_MODE_BRANCH: - if (mOctreeNode) - { - LLSpatialClearOcclusionState clearer(state); - clearer.traverse(mOctreeNode); - } - break; - - case STATE_MODE_ALL_CAMERAS: - for (U32 i = 0; i < LLViewerCamera::NUM_CAMERAS; i++) - { - mOcclusionState[i] &= ~state; - } - break; - - default: - break; - } -} - -bool LLOcclusionCullingGroup::earlyFail(LLCamera* camera, const LLVector4a* bounds) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_OCTREE; - if (camera->getOrigin().isExactlyZero()) - { - return false; - } - - const F32 vel = SG_OCCLUSION_FUDGE*2.f; - LLVector4a fudge; - fudge.splat(vel); - - const LLVector4a& c = bounds[0]; - LLVector4a r; - r.setAdd(bounds[1], fudge); - - /*if (r.magVecSquared() > 1024.0*1024.0) - { - return true; - }*/ - - LLVector4a e; - e.load3(camera->getOrigin().mV); - - LLVector4a min; - min.setSub(c,r); - LLVector4a max; - max.setAdd(c,r); - - S32 lt = e.lessThan(min).getGatheredBits() & 0x7; - if (lt) - { - return false; - } - - S32 gt = e.greaterThan(max).getGatheredBits() & 0x7; - if (gt) - { - return false; - } - - return true; -} - -U32 LLOcclusionCullingGroup::getLastOcclusionIssuedTime() -{ - return mOcclusionIssued[LLViewerCamera::sCurCameraID]; -} - -void LLOcclusionCullingGroup::checkOcclusion() -{ - if (LLPipeline::sUseOcclusion < 2) return; // 0 - NoOcclusion, 1 = ReadOnly, 2 = ModifyOcclusionState TODO: DJH 11-2021 ENUM this - - LL_PROFILE_ZONE_SCOPED_CATEGORY_OCTREE; - LLOcclusionCullingGroup* parent = (LLOcclusionCullingGroup*)getParent(); - if (parent && parent->isOcclusionState(LLOcclusionCullingGroup::OCCLUDED)) - { //if the parent has been marked as occluded, the child is implicitly occluded - clearOcclusionState(QUERY_PENDING | DISCARD_QUERY); - return; - } - - if (mOcclusionQuery[LLViewerCamera::sCurCameraID] && isOcclusionState(QUERY_PENDING)) - { - if (isOcclusionState(DISCARD_QUERY)) - { // delete the query to avoid holding onto hundreds of pending queries - releaseOcclusionQueryObjectName(mOcclusionQuery[LLViewerCamera::sCurCameraID]); - mOcclusionQuery[LLViewerCamera::sCurCameraID] = 0; - // mark non-occluded - clearOcclusionState(LLOcclusionCullingGroup::OCCLUDED, LLOcclusionCullingGroup::STATE_MODE_DIFF); - clearOcclusionState(QUERY_PENDING | DISCARD_QUERY); - } - else - { - GLuint available; - { - LL_PROFILE_ZONE_NAMED_CATEGORY_OCTREE("co - query available"); - glGetQueryObjectuiv(mOcclusionQuery[LLViewerCamera::sCurCameraID], GL_QUERY_RESULT_AVAILABLE, &available); - mOcclusionCheckCount[LLViewerCamera::sCurCameraID]++; - } - - static LLCachedControl occlusion_timeout(gSavedSettings, "RenderOcclusionTimeout", 4); - - if (available || mOcclusionCheckCount[LLViewerCamera::sCurCameraID] > occlusion_timeout) - { - mOcclusionCheckCount[LLViewerCamera::sCurCameraID] = 0; - GLuint query_result; // Will be # samples drawn, or a boolean depending on mHasOcclusionQuery2 (both are type GLuint) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_OCTREE("co - query result"); - glGetQueryObjectuiv(mOcclusionQuery[LLViewerCamera::sCurCameraID], GL_QUERY_RESULT, &query_result); - } -#if LL_TRACK_PENDING_OCCLUSION_QUERIES - sPendingQueries.erase(mOcclusionQuery[LLViewerCamera::sCurCameraID]); -#endif - - if (query_result > 0) - { - clearOcclusionState(LLOcclusionCullingGroup::OCCLUDED, LLOcclusionCullingGroup::STATE_MODE_DIFF); - } - else - { - setOcclusionState(LLOcclusionCullingGroup::OCCLUDED, LLOcclusionCullingGroup::STATE_MODE_DIFF); - } - clearOcclusionState(QUERY_PENDING); - } - } - } - else if (mSpatialPartition->isOcclusionEnabled() && isOcclusionState(LLOcclusionCullingGroup::OCCLUDED)) - { //check occlusion has been issued for occluded node that has not had a query issued - assert_states_valid(this); - //clearOcclusionState(LLOcclusionCullingGroup::OCCLUDED, LLOcclusionCullingGroup::STATE_MODE_DIFF); - assert_states_valid(this); - } -} - -void LLOcclusionCullingGroup::doOcclusion(LLCamera* camera, const LLVector4a* shift) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_OCTREE; - if (mSpatialPartition->isOcclusionEnabled() && LLPipeline::sUseOcclusion > 1) - { - //move mBounds to the agent space if necessary - LLVector4a bounds[2]; - bounds[0] = mBounds[0]; - bounds[1] = mBounds[1]; - if(shift != NULL) - { - bounds[0].add(*shift); - } - - F32 OCCLUSION_FUDGE_Z = SG_OCCLUSION_FUDGE; //<-- #Solution #2 - if (LLPipeline::RENDER_TYPE_VOIDWATER == mSpatialPartition->mDrawableType) - { - OCCLUSION_FUDGE_Z = 1.; - } - - if (earlyFail(camera, bounds)) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_OCTREE("doOcclusion - early fail"); - setOcclusionState(LLOcclusionCullingGroup::DISCARD_QUERY); - assert_states_valid(this); - clearOcclusionState(LLOcclusionCullingGroup::OCCLUDED, LLOcclusionCullingGroup::STATE_MODE_DIFF); - assert_states_valid(this); - } - else - { - if (!isOcclusionState(QUERY_PENDING) || isOcclusionState(DISCARD_QUERY)) - { - { //no query pending, or previous query to be discarded - LL_PROFILE_ZONE_NAMED_CATEGORY_OCTREE("doOcclusion - render"); - - if (!mOcclusionQuery[LLViewerCamera::sCurCameraID]) - { - mOcclusionQuery[LLViewerCamera::sCurCameraID] = getNewOcclusionQueryObjectName(); - } - - // Depth clamp all water to avoid it being culled as a result of being - // behind the far clip plane, and in the case of edge water to avoid - // it being culled while still visible. - bool const use_depth_clamp = (mSpatialPartition->mDrawableType == LLPipeline::RENDER_TYPE_WATER || - mSpatialPartition->mDrawableType == LLPipeline::RENDER_TYPE_VOIDWATER); - - LLGLEnable clamp(use_depth_clamp ? GL_DEPTH_CLAMP : 0); - - U32 mode = gGLManager.mGLVersion >= 3.3f ? GL_ANY_SAMPLES_PASSED : GL_SAMPLES_PASSED; - -#if LL_TRACK_PENDING_OCCLUSION_QUERIES - sPendingQueries.insert(mOcclusionQuery[LLViewerCamera::sCurCameraID]); -#endif - add(sOcclusionQueries, 1); - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_OCTREE("doOcclusion - push"); - - //store which frame this query was issued on - mOcclusionIssued[LLViewerCamera::sCurCameraID] = gFrameCount; - - { - LL_PROFILE_ZONE_NAMED("glBeginQuery"); - - //get an occlusion query that hasn't been used in awhile - releaseOcclusionQueryObjectName(mOcclusionQuery[LLViewerCamera::sCurCameraID]); - mOcclusionQuery[LLViewerCamera::sCurCameraID] = getNewOcclusionQueryObjectName(); - glBeginQuery(mode, mOcclusionQuery[LLViewerCamera::sCurCameraID]); - } - - LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; - llassert(shader); - - shader->uniform3fv(LLShaderMgr::BOX_CENTER, 1, bounds[0].getF32ptr()); - shader->uniform3f(LLShaderMgr::BOX_SIZE, bounds[1][0]+SG_OCCLUSION_FUDGE, - bounds[1][1]+SG_OCCLUSION_FUDGE, - bounds[1][2]+OCCLUSION_FUDGE_Z); - - if (!use_depth_clamp && mSpatialPartition->mDrawableType == LLPipeline::RENDER_TYPE_VOIDWATER) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_OCTREE("doOcclusion - draw water"); - - LLGLSquashToFarClip squash; - if (camera->getOrigin().isExactlyZero()) - { //origin is invalid, draw entire box - gPipeline.mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, 0); - gPipeline.mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, b111*8); - } - else - { - gPipeline.mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, get_box_fan_indices(camera, bounds[0])); - } - } - else - { - LL_PROFILE_ZONE_NAMED_CATEGORY_OCTREE("doOcclusion - draw"); - if (camera->getOrigin().isExactlyZero()) - { //origin is invalid, draw entire box - gPipeline.mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, 0); - gPipeline.mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, b111*8); - } - else - { - gPipeline.mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, get_box_fan_indices(camera, bounds[0])); - } - } - - { - LL_PROFILE_ZONE_NAMED("glEndQuery"); - glEndQuery(mode); - } - } - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_OCTREE("doOcclusion - set state"); - setOcclusionState(LLOcclusionCullingGroup::QUERY_PENDING); - clearOcclusionState(LLOcclusionCullingGroup::DISCARD_QUERY); - } - } - } - } -} -//------------------------------------------------------------------------------------------- -//end of occulsion culling functions and classes -//------------------------------------------------------------------------------------------- - -//----------------------------------------------------------------------------------- -//class LLViewerOctreePartition definitions -//----------------------------------------------------------------------------------- -LLViewerOctreePartition::LLViewerOctreePartition() : - mRegionp(NULL), - mOcclusionEnabled(true), - mDrawableType(0), - mLODSeed(0), - mLODPeriod(1) -{ - LLVector4a center, size; - center.splat(0.f); - size.splat(1.f); - - mOctree = new OctreeRoot(center,size, NULL); -} - -LLViewerOctreePartition::~LLViewerOctreePartition() -{ - cleanup(); -} - -void LLViewerOctreePartition::cleanup() -{ - delete mOctree; - mOctree = nullptr; -} - -bool LLViewerOctreePartition::isOcclusionEnabled() -{ - return mOcclusionEnabled || LLPipeline::sUseOcclusion > 2; -} - - -//----------------------------------------------------------------------------------- -//class LLViewerOctreeCull definitions -//----------------------------------------------------------------------------------- - -//virtual -bool LLViewerOctreeCull::earlyFail(LLViewerOctreeGroup* group) -{ - return false; -} - -//virtual -void LLViewerOctreeCull::traverse(const OctreeNode* n) -{ - LLViewerOctreeGroup* group = (LLViewerOctreeGroup*) n->getListener(0); - - if (earlyFail(group)) - { - return; - } - - if (mRes == 2 || - (mRes && group->hasState(LLViewerOctreeGroup::SKIP_FRUSTUM_CHECK))) - { //fully in, just add everything - OctreeTraveler::traverse(n); - } - else - { - mRes = frustumCheck(group); - - if (mRes) - { //at least partially in, run on down - OctreeTraveler::traverse(n); - } - - mRes = 0; - } -} - -//------------------------------------------ -//agent space group culling -S32 LLViewerOctreeCull::AABBInFrustumNoFarClipGroupBounds(const LLViewerOctreeGroup* group) -{ - return mCamera->AABBInFrustumNoFarClip(group->mBounds[0], group->mBounds[1]); -} - -S32 LLViewerOctreeCull::AABBSphereIntersectGroupExtents(const LLViewerOctreeGroup* group) -{ - return AABBSphereIntersect(group->mExtents[0], group->mExtents[1], mCamera->getOrigin(), mCamera->mFrustumCornerDist); -} - -S32 LLViewerOctreeCull::AABBInFrustumGroupBounds(const LLViewerOctreeGroup* group) -{ - return mCamera->AABBInFrustum(group->mBounds[0], group->mBounds[1]); -} -//------------------------------------------ - -//------------------------------------------ -//agent space object set culling -S32 LLViewerOctreeCull::AABBInFrustumNoFarClipObjectBounds(const LLViewerOctreeGroup* group) -{ - return mCamera->AABBInFrustumNoFarClip(group->mObjectBounds[0], group->mObjectBounds[1]); -} - -S32 LLViewerOctreeCull::AABBSphereIntersectObjectExtents(const LLViewerOctreeGroup* group) -{ - return AABBSphereIntersect(group->mObjectExtents[0], group->mObjectExtents[1], mCamera->getOrigin(), mCamera->mFrustumCornerDist); -} - -S32 LLViewerOctreeCull::AABBInFrustumObjectBounds(const LLViewerOctreeGroup* group) -{ - return mCamera->AABBInFrustum(group->mObjectBounds[0], group->mObjectBounds[1]); -} -//------------------------------------------ - -//------------------------------------------ -//local regional space group culling -S32 LLViewerOctreeCull::AABBInRegionFrustumNoFarClipGroupBounds(const LLViewerOctreeGroup* group) -{ - return mCamera->AABBInRegionFrustumNoFarClip(group->mBounds[0], group->mBounds[1]); -} - -S32 LLViewerOctreeCull::AABBInRegionFrustumGroupBounds(const LLViewerOctreeGroup* group) -{ - return mCamera->AABBInRegionFrustum(group->mBounds[0], group->mBounds[1]); -} - -S32 LLViewerOctreeCull::AABBRegionSphereIntersectGroupExtents(const LLViewerOctreeGroup* group, const LLVector3& shift) -{ - return AABBSphereIntersect(group->mExtents[0], group->mExtents[1], mCamera->getOrigin() - shift, mCamera->mFrustumCornerDist); -} -//------------------------------------------ - -//------------------------------------------ -//local regional space object culling -S32 LLViewerOctreeCull::AABBInRegionFrustumObjectBounds(const LLViewerOctreeGroup* group) -{ - return mCamera->AABBInRegionFrustum(group->mObjectBounds[0], group->mObjectBounds[1]); -} - -S32 LLViewerOctreeCull::AABBInRegionFrustumNoFarClipObjectBounds(const LLViewerOctreeGroup* group) -{ - return mCamera->AABBInRegionFrustumNoFarClip(group->mObjectBounds[0], group->mObjectBounds[1]); -} - -S32 LLViewerOctreeCull::AABBRegionSphereIntersectObjectExtents(const LLViewerOctreeGroup* group, const LLVector3& shift) -{ - return AABBSphereIntersect(group->mObjectExtents[0], group->mObjectExtents[1], mCamera->getOrigin() - shift, mCamera->mFrustumCornerDist); -} -//------------------------------------------ -//check if the objects projection large enough - -bool LLViewerOctreeCull::checkProjectionArea(const LLVector4a& center, const LLVector4a& size, const LLVector3& shift, F32 pixel_threshold, F32 near_radius) -{ - LLVector3 local_orig = mCamera->getOrigin() - shift; - LLVector4a origin; - origin.load3(local_orig.mV); - - LLVector4a lookAt; - lookAt.setSub(center, origin); - F32 distance = lookAt.getLength3().getF32(); - if(distance <= near_radius) - { - return true; //always load close-by objects - } - - // treat object as if it were near_radius meters closer than it actually was. - // this allows us to get some temporal coherence on visibility...objects that can be reached quickly will tend to be visible - distance -= near_radius; - - F32 squared_rad = size.dot3(size).getF32(); - return squared_rad / distance > pixel_threshold; -} - -//virtual -bool LLViewerOctreeCull::checkObjects(const OctreeNode* branch, const LLViewerOctreeGroup* group) -{ - if (branch->getElementCount() == 0) //no elements - { - return false; - } - else if (branch->getChildCount() == 0) //leaf state, already checked tightest bounding box - { - return true; - } - else if (mRes == 1 && !frustumCheckObjects(group)) //no objects in frustum - { - return false; - } - - return true; -} - -//virtual -void LLViewerOctreeCull::preprocess(LLViewerOctreeGroup* group) -{ -} - -//virtual -void LLViewerOctreeCull::processGroup(LLViewerOctreeGroup* group) -{ -} - -//virtual -void LLViewerOctreeCull::visit(const OctreeNode* branch) -{ - LLViewerOctreeGroup* group = (LLViewerOctreeGroup*) branch->getListener(0); - - preprocess(group); - - if (checkObjects(branch, group)) - { - processGroup(group); - } -} - -//-------------------------------------------------------------- -//class LLViewerOctreeDebug -//virtual -void LLViewerOctreeDebug::visit(const OctreeNode* branch) -{ -#if 0 - LL_INFOS() << "Node: " << (U32)branch << " # Elements: " << branch->getElementCount() << " # Children: " << branch->getChildCount() << LL_ENDL; - for (U32 i = 0; i < branch->getChildCount(); i++) - { - LL_INFOS() << "Child " << i << " : " << (U32)branch->getChild(i) << LL_ENDL; - } -#endif - LLViewerOctreeGroup* group = (LLViewerOctreeGroup*) branch->getListener(0); - processGroup(group); -} - -//virtual -void LLViewerOctreeDebug::processGroup(LLViewerOctreeGroup* group) -{ -#if 0 - const LLVector4a* vec4 = group->getBounds(); - LLVector3 vec[2]; - vec[0].set(vec4[0].getF32ptr()); - vec[1].set(vec4[1].getF32ptr()); - LL_INFOS() << "Bounds: " << vec[0] << " : " << vec[1] << LL_ENDL; - - vec4 = group->getExtents(); - vec[0].set(vec4[0].getF32ptr()); - vec[1].set(vec4[1].getF32ptr()); - LL_INFOS() << "Extents: " << vec[0] << " : " << vec[1] << LL_ENDL; - - vec4 = group->getObjectBounds(); - vec[0].set(vec4[0].getF32ptr()); - vec[1].set(vec4[1].getF32ptr()); - LL_INFOS() << "ObjectBounds: " << vec[0] << " : " << vec[1] << LL_ENDL; - - vec4 = group->getObjectExtents(); - vec[0].set(vec4[0].getF32ptr()); - vec[1].set(vec4[1].getF32ptr()); - LL_INFOS() << "ObjectExtents: " << vec[0] << " : " << vec[1] << LL_ENDL; -#endif -} -//-------------------------------------------------------------- +/** + * @file llvieweroctree.cpp + * @brief LLViewerOctreeGroup class implementation and supporting functions + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llvieweroctree.h" +#include "llviewerregion.h" +#include "pipeline.h" +#include "llviewercontrol.h" +#include "llappviewer.h" +#include "llglslshader.h" +#include "llviewershadermgr.h" +#include "lldrawpoolwater.h" + +//----------------------------------------------------------------------------------- +//static variables definitions +//----------------------------------------------------------------------------------- +U32 LLViewerOctreeEntryData::sCurVisible = 10; //reserve the low numbers for special use. +bool LLViewerOctreeDebug::sInDebug = false; + +static LLTrace::CountStatHandle sOcclusionQueries("occlusion_queries", "Number of occlusion queries executed"), + sNumObjectsOccluded("occluded_objects", "Count of objects being occluded by a query"), + sNumObjectsUnoccluded("unoccluded_objects", "Count of objects being unoccluded by a query"); + +//----------------------------------------------------------------------------------- +//some global functions definitions +//----------------------------------------------------------------------------------- +typedef enum +{ + b000 = 0x00, + b001 = 0x01, + b010 = 0x02, + b011 = 0x03, + b100 = 0x04, + b101 = 0x05, + b110 = 0x06, + b111 = 0x07, +} eLoveTheBits; + +//contact Runitai Linden for a copy of the SL object used to write this table +//basically, you give the table a bitmask of the look-at vector to a node and it +//gives you a triangle fan index array +static U16 sOcclusionIndices[] = +{ + //000 + b111, b110, b010, b011, b001, b101, b100, b110, + //001 + b011, b010, b000, b001, b101, b111, b110, b010, + //010 + b101, b100, b110, b111, b011, b001, b000, b100, + //011 + b001, b000, b100, b101, b111, b011, b010, b000, + //100 + b110, b000, b010, b011, b111, b101, b100, b000, + //101 + b010, b100, b000, b001, b011, b111, b110, b100, + //110 + b100, b010, b110, b111, b101, b001, b000, b010, + //111 + b000, b110, b100, b101, b001, b011, b010, b110, +}; + +U32 get_box_fan_indices(LLCamera* camera, const LLVector4a& center) +{ + LLVector4a origin; + origin.load3(camera->getOrigin().mV); + + S32 cypher = center.greaterThan(origin).getGatheredBits() & 0x7; + + return cypher*8; +} + +U8* get_box_fan_indices_ptr(LLCamera* camera, const LLVector4a& center) +{ + LLVector4a origin; + origin.load3(camera->getOrigin().mV); + + S32 cypher = center.greaterThan(origin).getGatheredBits() & 0x7; + + return (U8*) (sOcclusionIndices+cypher*8); +} + +//create a vertex buffer for efficiently rendering cubes +LLVertexBuffer* ll_create_cube_vb(U32 type_mask) +{ + LLVertexBuffer* ret = new LLVertexBuffer(type_mask); + + ret->allocateBuffer(8, 64); + + LLStrider pos; + LLStrider idx; + + ret->getVertexStrider(pos); + ret->getIndexStrider(idx); + + pos[0] = LLVector3(-1,-1,-1); + pos[1] = LLVector3(-1,-1, 1); + pos[2] = LLVector3(-1, 1,-1); + pos[3] = LLVector3(-1, 1, 1); + pos[4] = LLVector3( 1,-1,-1); + pos[5] = LLVector3( 1,-1, 1); + pos[6] = LLVector3( 1, 1,-1); + pos[7] = LLVector3( 1, 1, 1); + + for (U32 i = 0; i < 64; i++) + { + idx[i] = sOcclusionIndices[i]; + } + + ret->unmapBuffer(); + + return ret; +} + + +#define LL_TRACK_PENDING_OCCLUSION_QUERIES 0 + +const F32 SG_OCCLUSION_FUDGE = 0.25f; +#define SG_DISCARD_TOLERANCE 0.01f + + +S32 AABBSphereIntersect(const LLVector3& min, const LLVector3& max, const LLVector3 &origin, const F32 &rad) +{ + return AABBSphereIntersectR2(min, max, origin, rad*rad); +} + +S32 AABBSphereIntersectR2(const LLVector3& min, const LLVector3& max, const LLVector3 &origin, const F32 &r) +{ + F32 d = 0.f; + F32 t; + + if ((min-origin).magVecSquared() < r && + (max-origin).magVecSquared() < r) + { + return 2; + } + + for (U32 i = 0; i < 3; i++) + { + if (origin.mV[i] < min.mV[i]) + { + t = min.mV[i] - origin.mV[i]; + d += t*t; + } + else if (origin.mV[i] > max.mV[i]) + { + t = origin.mV[i] - max.mV[i]; + d += t*t; + } + + if (d > r) + { + return 0; + } + } + + return 1; +} + + +S32 AABBSphereIntersect(const LLVector4a& min, const LLVector4a& max, const LLVector3 &origin, const F32 &rad) +{ + return AABBSphereIntersectR2(min, max, origin, rad*rad); +} + +S32 AABBSphereIntersectR2(const LLVector4a& min, const LLVector4a& max, const LLVector3 &origin, const F32 &r) +{ + F32 d = 0.f; + F32 t; + + LLVector4a origina; + origina.load3(origin.mV); + + LLVector4a v; + v.setSub(min, origina); + + if (v.dot3(v) < r) + { + v.setSub(max, origina); + if (v.dot3(v) < r) + { + return 2; + } + } + + + for (U32 i = 0; i < 3; i++) + { + if (origin.mV[i] < min[i]) + { + t = min[i] - origin.mV[i]; + d += t*t; + } + else if (origin.mV[i] > max[i]) + { + t = origin.mV[i] - max[i]; + d += t*t; + } + + if (d > r) + { + return 0; + } + } + + return 1; +} + +//----------------------------------------------------------------------------------- +//class LLViewerOctreeEntry definitions +//----------------------------------------------------------------------------------- +LLViewerOctreeEntry::LLViewerOctreeEntry() +: mGroup(NULL), + mBinRadius(0.f), + mBinIndex(-1), + mVisible(0) +{ + mPositionGroup.clear(); + mExtents[0].clear(); + mExtents[1].clear(); + + for(S32 i = 0; i < NUM_DATA_TYPE; i++) + { + mData[i] = NULL; + } +} + +LLViewerOctreeEntry::~LLViewerOctreeEntry() +{ + llassert(!mGroup); +} + +void LLViewerOctreeEntry::addData(LLViewerOctreeEntryData* data) +{ + //llassert(mData[data->getDataType()] == NULL); + llassert(data != NULL); + + mData[data->getDataType()] = data; +} + +void LLViewerOctreeEntry::removeData(LLViewerOctreeEntryData* data) +{ + //llassert(data->getDataType() != LLVOCACHEENTRY); //can not remove VOCache entry + + if(!mData[data->getDataType()]) + { + return; + } + if(mData[data->getDataType()] != data) + { + return; + } + + mData[data->getDataType()] = NULL; + + if(mGroup != NULL && !mData[LLDRAWABLE]) + { + LLViewerOctreeGroup* group = mGroup; + mGroup = NULL; + group->removeFromGroup(data); + + llassert(mBinIndex == -1); + } +} + +//called by group handleDestruction() ONLY when group is destroyed by octree. +void LLViewerOctreeEntry::nullGroup() +{ + mGroup = NULL; +} + +void LLViewerOctreeEntry::setGroup(LLViewerOctreeGroup* group) +{ + if(mGroup == group) + { + return; + } + + if(mGroup) + { + LLViewerOctreeGroup* old_group = mGroup; + mGroup = NULL; + old_group->removeFromGroup(this); + + llassert(mBinIndex == -1); + } + + mGroup = group; +} + +//----------------------------------------------------------------------------------- +//class LLViewerOctreeEntryData definitions +//----------------------------------------------------------------------------------- +LLViewerOctreeEntryData::~LLViewerOctreeEntryData() +{ + if(mEntry) + { + mEntry->removeData(this); + } +} + +LLViewerOctreeEntryData::LLViewerOctreeEntryData(LLViewerOctreeEntry::eEntryDataType_t data_type) + : mDataType(data_type), + mEntry(NULL) +{ +} + +//virtual +void LLViewerOctreeEntryData::setOctreeEntry(LLViewerOctreeEntry* entry) +{ + llassert_always(mEntry.isNull()); + + if(mEntry.notNull()) + { + return; + } + + if(!entry) + { + mEntry = new LLViewerOctreeEntry(); + } + else + { + mEntry = entry; + } + mEntry->addData(this); +} + +void LLViewerOctreeEntryData::removeOctreeEntry() +{ + if(mEntry) + { + mEntry->removeData(this); + mEntry = NULL; + } +} + +void LLViewerOctreeEntryData::setSpatialExtents(const LLVector3& min, const LLVector3& max) +{ + mEntry->mExtents[0].load3(min.mV); + mEntry->mExtents[1].load3(max.mV); +} + +void LLViewerOctreeEntryData::setSpatialExtents(const LLVector4a& min, const LLVector4a& max) +{ + mEntry->mExtents[0] = min; + mEntry->mExtents[1] = max; +} + +void LLViewerOctreeEntryData::setPositionGroup(const LLVector4a& pos) +{ + mEntry->mPositionGroup = pos; +} + +const LLVector4a* LLViewerOctreeEntryData::getSpatialExtents() const +{ + return mEntry->getSpatialExtents(); +} + +//virtual +void LLViewerOctreeEntryData::setGroup(LLViewerOctreeGroup* group) +{ + mEntry->setGroup(group); +} + +void LLViewerOctreeEntryData::shift(const LLVector4a &shift_vector) +{ + mEntry->mExtents[0].add(shift_vector); + mEntry->mExtents[1].add(shift_vector); + mEntry->mPositionGroup.add(shift_vector); +} + +LLViewerOctreeGroup* LLViewerOctreeEntryData::getGroup()const +{ + return mEntry.notNull() ? mEntry->mGroup : NULL; +} + +const LLVector4a& LLViewerOctreeEntryData::getPositionGroup() const +{ + return mEntry->getPositionGroup(); +} + +//virtual +bool LLViewerOctreeEntryData::isVisible() const +{ + if(mEntry) + { + return mEntry->mVisible == sCurVisible; + } + return false; +} + +//virtual +bool LLViewerOctreeEntryData::isRecentlyVisible() const +{ + if(!mEntry) + { + return false; + } + + if(isVisible()) + { + return true; + } + if(getGroup() && getGroup()->isRecentlyVisible()) + { + setVisible(); + return true; + } + + return false; +} + +void LLViewerOctreeEntryData::setVisible() const +{ + if(mEntry) + { + mEntry->mVisible = sCurVisible; + } +} + +void LLViewerOctreeEntryData::resetVisible() const +{ + if(mEntry) + { + mEntry->mVisible = 0; + } +} +//----------------------------------------------------------------------------------- +//class LLViewerOctreeGroup definitions +//----------------------------------------------------------------------------------- + +LLViewerOctreeGroup::~LLViewerOctreeGroup() +{ + //empty here +} + +LLViewerOctreeGroup::LLViewerOctreeGroup(OctreeNode* node) +: mOctreeNode(node), + mAnyVisible(0), + mState(CLEAN) +{ + LLVector4a tmp; + tmp.splat(0.f); + mExtents[0] = mExtents[1] = mObjectBounds[0] = mObjectBounds[1] = + mObjectExtents[0] = mObjectExtents[1] = tmp; + + mBounds[0] = node->getCenter(); + mBounds[1] = node->getSize(); + + mOctreeNode->addListener(this); +} + +bool LLViewerOctreeGroup::hasElement(LLViewerOctreeEntryData* data) +{ + if(!data->getEntry()) + { + return false; + } + return std::find(getDataBegin(), getDataEnd(), data->getEntry()) != getDataEnd(); +} + +bool LLViewerOctreeGroup::removeFromGroup(LLViewerOctreeEntryData* data) +{ + return removeFromGroup(data->getEntry()); +} + +bool LLViewerOctreeGroup::removeFromGroup(LLViewerOctreeEntry* entry) +{ + llassert(entry != NULL); + llassert(!entry->getGroup()); + + if(isDead()) //group is about to be destroyed, not need to double delete the entry. + { + entry->setBinIndex(-1); + return true; + } + + unbound(); + setState(OBJECT_DIRTY); + + if (mOctreeNode) + { + if (!mOctreeNode->remove(entry)) //this could cause *this* pointer to be destroyed, so no more function calls after this. + { + OCT_ERRS << "Could not remove LLVOCacheEntry from LLVOCacheOctreeGroup" << LL_ENDL; + return false; + } + } + + return true; +} + +//virtual +void LLViewerOctreeGroup::unbound() +{ + LL_PROFILE_ZONE_SCOPED; + if (isDirty()) + { + return; + } + + setState(DIRTY); + + //all the parent nodes need to rebound this child + if (mOctreeNode) + { + OctreeNode* parent = (OctreeNode*) mOctreeNode->getParent(); + while (parent != NULL) + { + LLViewerOctreeGroup* group = (LLViewerOctreeGroup*) parent->getListener(0); + if (!group || group->isDirty()) + { + return; + } + + group->setState(DIRTY); + parent = (OctreeNode*) parent->getParent(); + } + } +} + +//virtual +void LLViewerOctreeGroup::rebound() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_OCTREE; + if (!isDirty()) + { + return; + } + + if (mOctreeNode->getChildCount() == 1 && mOctreeNode->getElementCount() == 0) + { + LLViewerOctreeGroup* group = (LLViewerOctreeGroup*) mOctreeNode->getChild(0)->getListener(0); + group->rebound(); + + //copy single child's bounding box + mBounds[0] = group->mBounds[0]; + mBounds[1] = group->mBounds[1]; + mExtents[0] = group->mExtents[0]; + mExtents[1] = group->mExtents[1]; + + group->setState(SKIP_FRUSTUM_CHECK); + } + else if (mOctreeNode->getChildCount() == 0) + { //copy object bounding box if this is a leaf + boundObjects(true, mExtents[0], mExtents[1]); + mBounds[0] = mObjectBounds[0]; + mBounds[1] = mObjectBounds[1]; + } + else + { + LLVector4a& newMin = mExtents[0]; + LLVector4a& newMax = mExtents[1]; + LLViewerOctreeGroup* group = (LLViewerOctreeGroup*) mOctreeNode->getChild(0)->getListener(0); + group->clearState(SKIP_FRUSTUM_CHECK); + group->rebound(); + //initialize to first child + newMin = group->mExtents[0]; + newMax = group->mExtents[1]; + + //first, rebound children + for (U32 i = 1; i < mOctreeNode->getChildCount(); i++) + { + group = (LLViewerOctreeGroup*) mOctreeNode->getChild(i)->getListener(0); + group->clearState(SKIP_FRUSTUM_CHECK); + group->rebound(); + const LLVector4a& max = group->mExtents[1]; + const LLVector4a& min = group->mExtents[0]; + + newMax.setMax(newMax, max); + newMin.setMin(newMin, min); + } + + boundObjects(false, newMin, newMax); + + mBounds[0].setAdd(newMin, newMax); + mBounds[0].mul(0.5f); + mBounds[1].setSub(newMax, newMin); + mBounds[1].mul(0.5f); + } + + clearState(DIRTY); + + return; +} + +//virtual +void LLViewerOctreeGroup::handleInsertion(const TreeNode* node, LLViewerOctreeEntry* obj) +{ + obj->setGroup(this); + unbound(); + setState(OBJECT_DIRTY); +} + +//virtual +void LLViewerOctreeGroup::handleRemoval(const TreeNode* node, LLViewerOctreeEntry* obj) +{ + unbound(); + setState(OBJECT_DIRTY); + + obj->setGroup(NULL); //this could cause *this* pointer to be destroyed. So no more function calls after this. +} + +//virtual +void LLViewerOctreeGroup::handleDestruction(const TreeNode* node) +{ + if (isDead()) + { + return; + } + setState(DEAD); + for (OctreeNode::element_iter i = mOctreeNode->getDataBegin(); i != mOctreeNode->getDataEnd(); ++i) + { + LLViewerOctreeEntry* obj = *i; + if (obj && obj->getGroup() == this) + { + obj->nullGroup(); + } + } + mOctreeNode = NULL; +} + +//virtual +void LLViewerOctreeGroup::handleStateChange(const TreeNode* node) +{ + //drop bounding box upon state change + if (mOctreeNode != node) + { + mOctreeNode = (OctreeNode*) node; + } + unbound(); +} + +//virtual +void LLViewerOctreeGroup::handleChildAddition(const OctreeNode* parent, OctreeNode* child) +{ + if (child->getListenerCount() == 0) + { + new LLViewerOctreeGroup(child); + } + else + { + OCT_ERRS << "LLViewerOctreeGroup redundancy detected." << LL_ENDL; + } + + unbound(); + + ((LLViewerOctreeGroup*)child->getListener(0))->unbound(); +} + +//virtual +void LLViewerOctreeGroup::handleChildRemoval(const OctreeNode* parent, const OctreeNode* child) +{ + unbound(); +} + +LLViewerOctreeGroup* LLViewerOctreeGroup::getParent() +{ + if (isDead()) + { + return NULL; + } + + if(!mOctreeNode) + { + return NULL; + } + + OctreeNode* parent = mOctreeNode->getOctParent(); + + if (parent) + { + return (LLViewerOctreeGroup*) parent->getListener(0); + } + + return NULL; +} + +//virtual +bool LLViewerOctreeGroup::boundObjects(bool empty, LLVector4a& minOut, LLVector4a& maxOut) +{ + const OctreeNode* node = mOctreeNode; + + if (node->isEmpty()) + { //don't do anything if there are no objects + if (empty && mOctreeNode->getParent()) + { //only root is allowed to be empty + OCT_ERRS << "Empty leaf found in octree." << LL_ENDL; + } + return false; + } + + LLVector4a& newMin = mObjectExtents[0]; + LLVector4a& newMax = mObjectExtents[1]; + + if (hasState(OBJECT_DIRTY)) + { //calculate new bounding box + clearState(OBJECT_DIRTY); + + //initialize bounding box to first element + OctreeNode::const_element_iter i = node->getDataBegin(); + LLViewerOctreeEntry* entry = *i; + const LLVector4a* minMax = entry->getSpatialExtents(); + + newMin = minMax[0]; + newMax = minMax[1]; + + for (++i; i != node->getDataEnd(); ++i) + { + entry = *i; + minMax = entry->getSpatialExtents(); + + update_min_max(newMin, newMax, minMax[0]); + update_min_max(newMin, newMax, minMax[1]); + } + + mObjectBounds[0].setAdd(newMin, newMax); + mObjectBounds[0].mul(0.5f); + mObjectBounds[1].setSub(newMax, newMin); + mObjectBounds[1].mul(0.5f); + } + + if (empty) + { + minOut = newMin; + maxOut = newMax; + } + else + { + minOut.setMin(minOut, newMin); + maxOut.setMax(maxOut, newMax); + } + + return true; +} + +//virtual +bool LLViewerOctreeGroup::isVisible() const +{ + return mVisible[LLViewerCamera::sCurCameraID] >= LLViewerOctreeEntryData::getCurrentFrame(); +} + +//virtual +bool LLViewerOctreeGroup::isRecentlyVisible() const +{ + return false; +} + +void LLViewerOctreeGroup::setVisible() +{ + mVisible[LLViewerCamera::sCurCameraID] = LLViewerOctreeEntryData::getCurrentFrame(); + + if(LLViewerCamera::sCurCameraID < LLViewerCamera::CAMERA_WATER0) + { + mAnyVisible = LLViewerOctreeEntryData::getCurrentFrame(); + } +} + +void LLViewerOctreeGroup::checkStates() +{ +#if LL_OCTREE_PARANOIA_CHECK + //LLOctreeStateCheck checker; + //checker.traverse(mOctreeNode); +#endif +} + +//------------------------------------------------------------------------------------------- +//occulsion culling functions and classes +//------------------------------------------------------------------------------------------- +std::set LLOcclusionCullingGroup::sPendingQueries; + +static std::queue sFreeQueries; + +#define QUERY_POOL_SIZE 1024 + +U32 LLOcclusionCullingGroup::getNewOcclusionQueryObjectName() +{ + LL_PROFILE_ZONE_SCOPED; + + if (sFreeQueries.empty()) + { + //seed 1024 query names into the free query pool + GLuint queries[1024]; + glGenQueries(1024, queries); + for (int i = 0; i < 1024; ++i) + { + sFreeQueries.push(queries[i]); + } + } + + // pull from pool + GLuint ret = sFreeQueries.front(); + sFreeQueries.pop(); + return ret; +} + +void LLOcclusionCullingGroup::releaseOcclusionQueryObjectName(GLuint name) +{ + if (name != 0) + { + LL_PROFILE_ZONE_SCOPED; + sFreeQueries.push(name); + } +} + +//===================================== +// Occlusion State Set/Clear +//===================================== +class LLSpatialSetOcclusionState : public OctreeTraveler +{ +public: + U32 mState; + LLSpatialSetOcclusionState(U32 state) : mState(state) { } + virtual void visit(const OctreeNode* branch) + { + LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*)branch->getListener(0); + if(group) + { + group->setOcclusionState(mState); + } + } +}; + +class LLSpatialSetOcclusionStateDiff : public LLSpatialSetOcclusionState +{ +public: + LLSpatialSetOcclusionStateDiff(U32 state) : LLSpatialSetOcclusionState(state) { } + + virtual void traverse(const OctreeNode* n) + { + LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*) n->getListener(0); + + if (group && !group->isOcclusionState(mState)) + { + OctreeTraveler::traverse(n); + } + } +}; + + +LLOcclusionCullingGroup::LLOcclusionCullingGroup(OctreeNode* node, LLViewerOctreePartition* part) : + LLViewerOctreeGroup(node), + mSpatialPartition(part) +{ + part->mLODSeed = (part->mLODSeed+1)%part->mLODPeriod; + mLODHash = part->mLODSeed; + + OctreeNode* oct_parent = node->getOctParent(); + LLOcclusionCullingGroup* parent = oct_parent ? (LLOcclusionCullingGroup*) oct_parent->getListener(0) : NULL; + + for (U32 i = 0; i < LLViewerCamera::NUM_CAMERAS; i++) + { + mOcclusionQuery[i] = 0; + mOcclusionCheckCount[i] = 0; + mOcclusionIssued[i] = 0; + mOcclusionState[i] = parent ? SG_STATE_INHERIT_MASK & parent->mOcclusionState[i] : 0; + mVisible[i] = 0; + } +} + +LLOcclusionCullingGroup::~LLOcclusionCullingGroup() +{ + releaseOcclusionQueryObjectNames(); +} + +bool LLOcclusionCullingGroup::needsUpdate() +{ + return LLDrawable::getCurrentFrame() % mSpatialPartition->mLODPeriod == mLODHash; +} + +bool LLOcclusionCullingGroup::isRecentlyVisible() const +{ + const S32 MIN_VIS_FRAME_RANGE = 2; + return (LLDrawable::getCurrentFrame() - mVisible[LLViewerCamera::sCurCameraID]) < MIN_VIS_FRAME_RANGE ; +} + +bool LLOcclusionCullingGroup::isAnyRecentlyVisible() const +{ + const S32 MIN_VIS_FRAME_RANGE = 2; + return (LLDrawable::getCurrentFrame() - mAnyVisible) < MIN_VIS_FRAME_RANGE ; +} + +//virtual +void LLOcclusionCullingGroup::handleChildAddition(const OctreeNode* parent, OctreeNode* child) +{ + if (!child->hasListeners()) + { + new LLOcclusionCullingGroup(child, mSpatialPartition); + } + else + { + OCT_ERRS << "LLOcclusionCullingGroup redundancy detected." << LL_ENDL; + } + + unbound(); + + ((LLViewerOctreeGroup*)child->getListener(0))->unbound(); +} + +void LLOcclusionCullingGroup::releaseOcclusionQueryObjectNames() +{ + for (U32 i = 0; i < LLViewerCamera::NUM_CAMERAS; ++i) + { + if (mOcclusionQuery[i]) + { + releaseOcclusionQueryObjectName(mOcclusionQuery[i]); + mOcclusionQuery[i] = 0; + } + } +} + +void LLOcclusionCullingGroup::setOcclusionState(U32 state, S32 mode /* = STATE_MODE_SINGLE */ ) +{ + switch (mode) + { + case STATE_MODE_SINGLE: + if (state & OCCLUDED) + { + add(sNumObjectsOccluded, 1); + } + mOcclusionState[LLViewerCamera::sCurCameraID] |= state; + if ((state & DISCARD_QUERY) && mOcclusionQuery[LLViewerCamera::sCurCameraID]) + { + releaseOcclusionQueryObjectName(mOcclusionQuery[LLViewerCamera::sCurCameraID]); + mOcclusionQuery[LLViewerCamera::sCurCameraID] = 0; + } + break; + + case STATE_MODE_DIFF: + if (mOctreeNode) + { + LLSpatialSetOcclusionStateDiff setter(state); + setter.traverse(mOctreeNode); + } + break; + + case STATE_MODE_BRANCH: + if (mOctreeNode) + { + LLSpatialSetOcclusionState setter(state); + setter.traverse(mOctreeNode); + } + break; + + case STATE_MODE_ALL_CAMERAS: + for (U32 i = 0; i < LLViewerCamera::NUM_CAMERAS; i++) + { + mOcclusionState[i] |= state; + + if ((state & DISCARD_QUERY) && mOcclusionQuery[i]) + { + releaseOcclusionQueryObjectName(mOcclusionQuery[i]); + mOcclusionQuery[i] = 0; + } + } + break; + + default: + break; + } +} + +class LLSpatialClearOcclusionState : public OctreeTraveler +{ +public: + U32 mState; + + LLSpatialClearOcclusionState(U32 state) : mState(state) { } + virtual void visit(const OctreeNode* branch) + { + LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*)branch->getListener(0); + if(group) + { + group->clearOcclusionState(mState); + } + } +}; + +class LLSpatialClearOcclusionStateDiff : public LLSpatialClearOcclusionState +{ +public: + LLSpatialClearOcclusionStateDiff(U32 state) : LLSpatialClearOcclusionState(state) { } + + virtual void traverse(const OctreeNode* n) + { + LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*) n->getListener(0); + + if (group && group->isOcclusionState(mState)) + { + OctreeTraveler::traverse(n); + } + } +}; + +void LLOcclusionCullingGroup::clearOcclusionState(U32 state, S32 mode /* = STATE_MODE_SINGLE */) +{ + switch (mode) + { + case STATE_MODE_SINGLE: + if (state & OCCLUDED) + { + add(sNumObjectsUnoccluded, 1); + } + mOcclusionState[LLViewerCamera::sCurCameraID] &= ~state; + break; + + case STATE_MODE_DIFF: + if (mOctreeNode) + { + LLSpatialClearOcclusionStateDiff clearer(state); + clearer.traverse(mOctreeNode); + } + break; + + case STATE_MODE_BRANCH: + if (mOctreeNode) + { + LLSpatialClearOcclusionState clearer(state); + clearer.traverse(mOctreeNode); + } + break; + + case STATE_MODE_ALL_CAMERAS: + for (U32 i = 0; i < LLViewerCamera::NUM_CAMERAS; i++) + { + mOcclusionState[i] &= ~state; + } + break; + + default: + break; + } +} + +bool LLOcclusionCullingGroup::earlyFail(LLCamera* camera, const LLVector4a* bounds) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_OCTREE; + if (camera->getOrigin().isExactlyZero()) + { + return false; + } + + const F32 vel = SG_OCCLUSION_FUDGE*2.f; + LLVector4a fudge; + fudge.splat(vel); + + const LLVector4a& c = bounds[0]; + LLVector4a r; + r.setAdd(bounds[1], fudge); + + /*if (r.magVecSquared() > 1024.0*1024.0) + { + return true; + }*/ + + LLVector4a e; + e.load3(camera->getOrigin().mV); + + LLVector4a min; + min.setSub(c,r); + LLVector4a max; + max.setAdd(c,r); + + S32 lt = e.lessThan(min).getGatheredBits() & 0x7; + if (lt) + { + return false; + } + + S32 gt = e.greaterThan(max).getGatheredBits() & 0x7; + if (gt) + { + return false; + } + + return true; +} + +U32 LLOcclusionCullingGroup::getLastOcclusionIssuedTime() +{ + return mOcclusionIssued[LLViewerCamera::sCurCameraID]; +} + +void LLOcclusionCullingGroup::checkOcclusion() +{ + if (LLPipeline::sUseOcclusion < 2) return; // 0 - NoOcclusion, 1 = ReadOnly, 2 = ModifyOcclusionState TODO: DJH 11-2021 ENUM this + + LL_PROFILE_ZONE_SCOPED_CATEGORY_OCTREE; + LLOcclusionCullingGroup* parent = (LLOcclusionCullingGroup*)getParent(); + if (parent && parent->isOcclusionState(LLOcclusionCullingGroup::OCCLUDED)) + { //if the parent has been marked as occluded, the child is implicitly occluded + clearOcclusionState(QUERY_PENDING | DISCARD_QUERY); + return; + } + + if (mOcclusionQuery[LLViewerCamera::sCurCameraID] && isOcclusionState(QUERY_PENDING)) + { + if (isOcclusionState(DISCARD_QUERY)) + { // delete the query to avoid holding onto hundreds of pending queries + releaseOcclusionQueryObjectName(mOcclusionQuery[LLViewerCamera::sCurCameraID]); + mOcclusionQuery[LLViewerCamera::sCurCameraID] = 0; + // mark non-occluded + clearOcclusionState(LLOcclusionCullingGroup::OCCLUDED, LLOcclusionCullingGroup::STATE_MODE_DIFF); + clearOcclusionState(QUERY_PENDING | DISCARD_QUERY); + } + else + { + GLuint available; + { + LL_PROFILE_ZONE_NAMED_CATEGORY_OCTREE("co - query available"); + glGetQueryObjectuiv(mOcclusionQuery[LLViewerCamera::sCurCameraID], GL_QUERY_RESULT_AVAILABLE, &available); + mOcclusionCheckCount[LLViewerCamera::sCurCameraID]++; + } + + static LLCachedControl occlusion_timeout(gSavedSettings, "RenderOcclusionTimeout", 4); + + if (available || mOcclusionCheckCount[LLViewerCamera::sCurCameraID] > occlusion_timeout) + { + mOcclusionCheckCount[LLViewerCamera::sCurCameraID] = 0; + GLuint query_result; // Will be # samples drawn, or a boolean depending on mHasOcclusionQuery2 (both are type GLuint) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_OCTREE("co - query result"); + glGetQueryObjectuiv(mOcclusionQuery[LLViewerCamera::sCurCameraID], GL_QUERY_RESULT, &query_result); + } +#if LL_TRACK_PENDING_OCCLUSION_QUERIES + sPendingQueries.erase(mOcclusionQuery[LLViewerCamera::sCurCameraID]); +#endif + + if (query_result > 0) + { + clearOcclusionState(LLOcclusionCullingGroup::OCCLUDED, LLOcclusionCullingGroup::STATE_MODE_DIFF); + } + else + { + setOcclusionState(LLOcclusionCullingGroup::OCCLUDED, LLOcclusionCullingGroup::STATE_MODE_DIFF); + } + clearOcclusionState(QUERY_PENDING); + } + } + } + else if (mSpatialPartition->isOcclusionEnabled() && isOcclusionState(LLOcclusionCullingGroup::OCCLUDED)) + { //check occlusion has been issued for occluded node that has not had a query issued + assert_states_valid(this); + //clearOcclusionState(LLOcclusionCullingGroup::OCCLUDED, LLOcclusionCullingGroup::STATE_MODE_DIFF); + assert_states_valid(this); + } +} + +void LLOcclusionCullingGroup::doOcclusion(LLCamera* camera, const LLVector4a* shift) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_OCTREE; + if (mSpatialPartition->isOcclusionEnabled() && LLPipeline::sUseOcclusion > 1) + { + //move mBounds to the agent space if necessary + LLVector4a bounds[2]; + bounds[0] = mBounds[0]; + bounds[1] = mBounds[1]; + if(shift != NULL) + { + bounds[0].add(*shift); + } + + F32 OCCLUSION_FUDGE_Z = SG_OCCLUSION_FUDGE; //<-- #Solution #2 + if (LLPipeline::RENDER_TYPE_VOIDWATER == mSpatialPartition->mDrawableType) + { + OCCLUSION_FUDGE_Z = 1.; + } + + if (earlyFail(camera, bounds)) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_OCTREE("doOcclusion - early fail"); + setOcclusionState(LLOcclusionCullingGroup::DISCARD_QUERY); + assert_states_valid(this); + clearOcclusionState(LLOcclusionCullingGroup::OCCLUDED, LLOcclusionCullingGroup::STATE_MODE_DIFF); + assert_states_valid(this); + } + else + { + if (!isOcclusionState(QUERY_PENDING) || isOcclusionState(DISCARD_QUERY)) + { + { //no query pending, or previous query to be discarded + LL_PROFILE_ZONE_NAMED_CATEGORY_OCTREE("doOcclusion - render"); + + if (!mOcclusionQuery[LLViewerCamera::sCurCameraID]) + { + mOcclusionQuery[LLViewerCamera::sCurCameraID] = getNewOcclusionQueryObjectName(); + } + + // Depth clamp all water to avoid it being culled as a result of being + // behind the far clip plane, and in the case of edge water to avoid + // it being culled while still visible. + bool const use_depth_clamp = (mSpatialPartition->mDrawableType == LLPipeline::RENDER_TYPE_WATER || + mSpatialPartition->mDrawableType == LLPipeline::RENDER_TYPE_VOIDWATER); + + LLGLEnable clamp(use_depth_clamp ? GL_DEPTH_CLAMP : 0); + + U32 mode = gGLManager.mGLVersion >= 3.3f ? GL_ANY_SAMPLES_PASSED : GL_SAMPLES_PASSED; + +#if LL_TRACK_PENDING_OCCLUSION_QUERIES + sPendingQueries.insert(mOcclusionQuery[LLViewerCamera::sCurCameraID]); +#endif + add(sOcclusionQueries, 1); + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_OCTREE("doOcclusion - push"); + + //store which frame this query was issued on + mOcclusionIssued[LLViewerCamera::sCurCameraID] = gFrameCount; + + { + LL_PROFILE_ZONE_NAMED("glBeginQuery"); + + //get an occlusion query that hasn't been used in awhile + releaseOcclusionQueryObjectName(mOcclusionQuery[LLViewerCamera::sCurCameraID]); + mOcclusionQuery[LLViewerCamera::sCurCameraID] = getNewOcclusionQueryObjectName(); + glBeginQuery(mode, mOcclusionQuery[LLViewerCamera::sCurCameraID]); + } + + LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; + llassert(shader); + + shader->uniform3fv(LLShaderMgr::BOX_CENTER, 1, bounds[0].getF32ptr()); + shader->uniform3f(LLShaderMgr::BOX_SIZE, bounds[1][0]+SG_OCCLUSION_FUDGE, + bounds[1][1]+SG_OCCLUSION_FUDGE, + bounds[1][2]+OCCLUSION_FUDGE_Z); + + if (!use_depth_clamp && mSpatialPartition->mDrawableType == LLPipeline::RENDER_TYPE_VOIDWATER) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_OCTREE("doOcclusion - draw water"); + + LLGLSquashToFarClip squash; + if (camera->getOrigin().isExactlyZero()) + { //origin is invalid, draw entire box + gPipeline.mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, 0); + gPipeline.mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, b111*8); + } + else + { + gPipeline.mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, get_box_fan_indices(camera, bounds[0])); + } + } + else + { + LL_PROFILE_ZONE_NAMED_CATEGORY_OCTREE("doOcclusion - draw"); + if (camera->getOrigin().isExactlyZero()) + { //origin is invalid, draw entire box + gPipeline.mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, 0); + gPipeline.mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, b111*8); + } + else + { + gPipeline.mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, get_box_fan_indices(camera, bounds[0])); + } + } + + { + LL_PROFILE_ZONE_NAMED("glEndQuery"); + glEndQuery(mode); + } + } + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_OCTREE("doOcclusion - set state"); + setOcclusionState(LLOcclusionCullingGroup::QUERY_PENDING); + clearOcclusionState(LLOcclusionCullingGroup::DISCARD_QUERY); + } + } + } + } +} +//------------------------------------------------------------------------------------------- +//end of occulsion culling functions and classes +//------------------------------------------------------------------------------------------- + +//----------------------------------------------------------------------------------- +//class LLViewerOctreePartition definitions +//----------------------------------------------------------------------------------- +LLViewerOctreePartition::LLViewerOctreePartition() : + mRegionp(NULL), + mOcclusionEnabled(true), + mDrawableType(0), + mLODSeed(0), + mLODPeriod(1) +{ + LLVector4a center, size; + center.splat(0.f); + size.splat(1.f); + + mOctree = new OctreeRoot(center,size, NULL); +} + +LLViewerOctreePartition::~LLViewerOctreePartition() +{ + cleanup(); +} + +void LLViewerOctreePartition::cleanup() +{ + delete mOctree; + mOctree = nullptr; +} + +bool LLViewerOctreePartition::isOcclusionEnabled() +{ + return mOcclusionEnabled || LLPipeline::sUseOcclusion > 2; +} + + +//----------------------------------------------------------------------------------- +//class LLViewerOctreeCull definitions +//----------------------------------------------------------------------------------- + +//virtual +bool LLViewerOctreeCull::earlyFail(LLViewerOctreeGroup* group) +{ + return false; +} + +//virtual +void LLViewerOctreeCull::traverse(const OctreeNode* n) +{ + LLViewerOctreeGroup* group = (LLViewerOctreeGroup*) n->getListener(0); + + if (earlyFail(group)) + { + return; + } + + if (mRes == 2 || + (mRes && group->hasState(LLViewerOctreeGroup::SKIP_FRUSTUM_CHECK))) + { //fully in, just add everything + OctreeTraveler::traverse(n); + } + else + { + mRes = frustumCheck(group); + + if (mRes) + { //at least partially in, run on down + OctreeTraveler::traverse(n); + } + + mRes = 0; + } +} + +//------------------------------------------ +//agent space group culling +S32 LLViewerOctreeCull::AABBInFrustumNoFarClipGroupBounds(const LLViewerOctreeGroup* group) +{ + return mCamera->AABBInFrustumNoFarClip(group->mBounds[0], group->mBounds[1]); +} + +S32 LLViewerOctreeCull::AABBSphereIntersectGroupExtents(const LLViewerOctreeGroup* group) +{ + return AABBSphereIntersect(group->mExtents[0], group->mExtents[1], mCamera->getOrigin(), mCamera->mFrustumCornerDist); +} + +S32 LLViewerOctreeCull::AABBInFrustumGroupBounds(const LLViewerOctreeGroup* group) +{ + return mCamera->AABBInFrustum(group->mBounds[0], group->mBounds[1]); +} +//------------------------------------------ + +//------------------------------------------ +//agent space object set culling +S32 LLViewerOctreeCull::AABBInFrustumNoFarClipObjectBounds(const LLViewerOctreeGroup* group) +{ + return mCamera->AABBInFrustumNoFarClip(group->mObjectBounds[0], group->mObjectBounds[1]); +} + +S32 LLViewerOctreeCull::AABBSphereIntersectObjectExtents(const LLViewerOctreeGroup* group) +{ + return AABBSphereIntersect(group->mObjectExtents[0], group->mObjectExtents[1], mCamera->getOrigin(), mCamera->mFrustumCornerDist); +} + +S32 LLViewerOctreeCull::AABBInFrustumObjectBounds(const LLViewerOctreeGroup* group) +{ + return mCamera->AABBInFrustum(group->mObjectBounds[0], group->mObjectBounds[1]); +} +//------------------------------------------ + +//------------------------------------------ +//local regional space group culling +S32 LLViewerOctreeCull::AABBInRegionFrustumNoFarClipGroupBounds(const LLViewerOctreeGroup* group) +{ + return mCamera->AABBInRegionFrustumNoFarClip(group->mBounds[0], group->mBounds[1]); +} + +S32 LLViewerOctreeCull::AABBInRegionFrustumGroupBounds(const LLViewerOctreeGroup* group) +{ + return mCamera->AABBInRegionFrustum(group->mBounds[0], group->mBounds[1]); +} + +S32 LLViewerOctreeCull::AABBRegionSphereIntersectGroupExtents(const LLViewerOctreeGroup* group, const LLVector3& shift) +{ + return AABBSphereIntersect(group->mExtents[0], group->mExtents[1], mCamera->getOrigin() - shift, mCamera->mFrustumCornerDist); +} +//------------------------------------------ + +//------------------------------------------ +//local regional space object culling +S32 LLViewerOctreeCull::AABBInRegionFrustumObjectBounds(const LLViewerOctreeGroup* group) +{ + return mCamera->AABBInRegionFrustum(group->mObjectBounds[0], group->mObjectBounds[1]); +} + +S32 LLViewerOctreeCull::AABBInRegionFrustumNoFarClipObjectBounds(const LLViewerOctreeGroup* group) +{ + return mCamera->AABBInRegionFrustumNoFarClip(group->mObjectBounds[0], group->mObjectBounds[1]); +} + +S32 LLViewerOctreeCull::AABBRegionSphereIntersectObjectExtents(const LLViewerOctreeGroup* group, const LLVector3& shift) +{ + return AABBSphereIntersect(group->mObjectExtents[0], group->mObjectExtents[1], mCamera->getOrigin() - shift, mCamera->mFrustumCornerDist); +} +//------------------------------------------ +//check if the objects projection large enough + +bool LLViewerOctreeCull::checkProjectionArea(const LLVector4a& center, const LLVector4a& size, const LLVector3& shift, F32 pixel_threshold, F32 near_radius) +{ + LLVector3 local_orig = mCamera->getOrigin() - shift; + LLVector4a origin; + origin.load3(local_orig.mV); + + LLVector4a lookAt; + lookAt.setSub(center, origin); + F32 distance = lookAt.getLength3().getF32(); + if(distance <= near_radius) + { + return true; //always load close-by objects + } + + // treat object as if it were near_radius meters closer than it actually was. + // this allows us to get some temporal coherence on visibility...objects that can be reached quickly will tend to be visible + distance -= near_radius; + + F32 squared_rad = size.dot3(size).getF32(); + return squared_rad / distance > pixel_threshold; +} + +//virtual +bool LLViewerOctreeCull::checkObjects(const OctreeNode* branch, const LLViewerOctreeGroup* group) +{ + if (branch->getElementCount() == 0) //no elements + { + return false; + } + else if (branch->getChildCount() == 0) //leaf state, already checked tightest bounding box + { + return true; + } + else if (mRes == 1 && !frustumCheckObjects(group)) //no objects in frustum + { + return false; + } + + return true; +} + +//virtual +void LLViewerOctreeCull::preprocess(LLViewerOctreeGroup* group) +{ +} + +//virtual +void LLViewerOctreeCull::processGroup(LLViewerOctreeGroup* group) +{ +} + +//virtual +void LLViewerOctreeCull::visit(const OctreeNode* branch) +{ + LLViewerOctreeGroup* group = (LLViewerOctreeGroup*) branch->getListener(0); + + preprocess(group); + + if (checkObjects(branch, group)) + { + processGroup(group); + } +} + +//-------------------------------------------------------------- +//class LLViewerOctreeDebug +//virtual +void LLViewerOctreeDebug::visit(const OctreeNode* branch) +{ +#if 0 + LL_INFOS() << "Node: " << (U32)branch << " # Elements: " << branch->getElementCount() << " # Children: " << branch->getChildCount() << LL_ENDL; + for (U32 i = 0; i < branch->getChildCount(); i++) + { + LL_INFOS() << "Child " << i << " : " << (U32)branch->getChild(i) << LL_ENDL; + } +#endif + LLViewerOctreeGroup* group = (LLViewerOctreeGroup*) branch->getListener(0); + processGroup(group); +} + +//virtual +void LLViewerOctreeDebug::processGroup(LLViewerOctreeGroup* group) +{ +#if 0 + const LLVector4a* vec4 = group->getBounds(); + LLVector3 vec[2]; + vec[0].set(vec4[0].getF32ptr()); + vec[1].set(vec4[1].getF32ptr()); + LL_INFOS() << "Bounds: " << vec[0] << " : " << vec[1] << LL_ENDL; + + vec4 = group->getExtents(); + vec[0].set(vec4[0].getF32ptr()); + vec[1].set(vec4[1].getF32ptr()); + LL_INFOS() << "Extents: " << vec[0] << " : " << vec[1] << LL_ENDL; + + vec4 = group->getObjectBounds(); + vec[0].set(vec4[0].getF32ptr()); + vec[1].set(vec4[1].getF32ptr()); + LL_INFOS() << "ObjectBounds: " << vec[0] << " : " << vec[1] << LL_ENDL; + + vec4 = group->getObjectExtents(); + vec[0].set(vec4[0].getF32ptr()); + vec[1].set(vec4[1].getF32ptr()); + LL_INFOS() << "ObjectExtents: " << vec[0] << " : " << vec[1] << LL_ENDL; +#endif +} +//-------------------------------------------------------------- diff --git a/indra/newview/llvieweroctree.h b/indra/newview/llvieweroctree.h index f383e3a59a..60e17a6f58 100644 --- a/indra/newview/llvieweroctree.h +++ b/indra/newview/llvieweroctree.h @@ -1,425 +1,425 @@ -/** - * @file llvieweroctree.h - * @brief LLViewerObjectOctree.cpp header file, defining all supporting classes. - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_VIEWEROCTREE_H -#define LL_VIEWEROCTREE_H - -#include -#include - -#include "v2math.h" -#include "v3math.h" -#include "v4math.h" -#include "m4math.h" -#include "llvector4a.h" -#include "llquaternion.h" -#include "lloctree.h" -#include "llviewercamera.h" - -class LLViewerRegion; -class LLViewerOctreeEntryData; -class LLViewerOctreeGroup; -class LLViewerOctreeEntry; -class LLViewerOctreePartition; - -typedef LLOctreeListener> OctreeListener; -typedef LLTreeNode TreeNode; -typedef LLOctreeNode> OctreeNode; -typedef LLOctreeRoot> OctreeRoot; -typedef LLOctreeTraveler> OctreeTraveler; - -#if LL_OCTREE_PARANOIA_CHECK -#define assert_octree_valid(x) x->validate() -#define assert_states_valid(x) ((LLViewerOctreeGroup*) x->mSpatialPartition->mOctree->getListener(0))->checkStates() -#else -#define assert_octree_valid(x) -#define assert_states_valid(x) -#endif - -// get index buffer for binary encoded axis vertex buffer given a box at center being viewed by given camera -U32 get_box_fan_indices(LLCamera* camera, const LLVector4a& center); -U8* get_box_fan_indices_ptr(LLCamera* camera, const LLVector4a& center); - -S32 AABBSphereIntersect(const LLVector4a& min, const LLVector4a& max, const LLVector3 &origin, const F32 &rad); -S32 AABBSphereIntersectR2(const LLVector4a& min, const LLVector4a& max, const LLVector3 &origin, const F32 &radius_squared); - -S32 AABBSphereIntersect(const LLVector3& min, const LLVector3& max, const LLVector3 &origin, const F32 &rad); -S32 AABBSphereIntersectR2(const LLVector3& min, const LLVector3& max, const LLVector3 &origin, const F32 &radius_squared); - -//defines data needed for octree of an entry -//LL_ALIGN_PREFIX(16) -class LLViewerOctreeEntry : public LLRefCount -{ - LL_ALIGN_NEW - friend class LLViewerOctreeEntryData; - -public: - typedef enum - { - LLDRAWABLE = 0, - LLVOCACHEENTRY, - NUM_DATA_TYPE - }eEntryDataType_t; - -protected: - virtual ~LLViewerOctreeEntry(); - -public: - LLViewerOctreeEntry(); - - void nullGroup(); //called by group handleDestruction() only - void setGroup(LLViewerOctreeGroup* group); - void removeData(LLViewerOctreeEntryData* data); - - LLViewerOctreeEntryData* getDrawable() const {return mData[LLDRAWABLE];} - bool hasDrawable() const {return mData[LLDRAWABLE] != NULL;} - LLViewerOctreeEntryData* getVOCacheEntry() const {return mData[LLVOCACHEENTRY];} - bool hasVOCacheEntry() const {return mData[LLVOCACHEENTRY] != NULL;} - - const LLVector4a* getSpatialExtents() const {return mExtents;} - const LLVector4a& getPositionGroup() const {return mPositionGroup;} - LLViewerOctreeGroup* getGroup()const {return mGroup;} - - F32 getBinRadius() const {return mBinRadius;} - S32 getBinIndex() const {return mBinIndex; } - void setBinIndex(S32 index) const {mBinIndex = index; } - -private: - void addData(LLViewerOctreeEntryData* data); - -private: - LLViewerOctreeEntryData* mData[NUM_DATA_TYPE]; //do not use LLPointer here. - LLViewerOctreeGroup* mGroup; - - //aligned members - LL_ALIGN_16(LLVector4a mExtents[2]); - LL_ALIGN_16(LLVector4a mPositionGroup); - F32 mBinRadius; - mutable S32 mBinIndex; - mutable U32 mVisible; - -} ;//LL_ALIGN_POSTFIX(16); - -//defines an abstract class for entry data -//LL_ALIGN_PREFIX(16) -class LLViewerOctreeEntryData : public LLRefCount -{ -protected: - virtual ~LLViewerOctreeEntryData(); - -public: - LLViewerOctreeEntryData(const LLViewerOctreeEntryData& rhs) - { - *this = rhs; - } - LLViewerOctreeEntryData(LLViewerOctreeEntry::eEntryDataType_t data_type); - - LLViewerOctreeEntry::eEntryDataType_t getDataType() const {return mDataType;} - LLViewerOctreeEntry* getEntry() {return mEntry;} - - virtual void setOctreeEntry(LLViewerOctreeEntry* entry); - void removeOctreeEntry(); - - F32 getBinRadius() const {return mEntry->getBinRadius();} - const LLVector4a* getSpatialExtents() const; - LLViewerOctreeGroup* getGroup()const; - const LLVector4a& getPositionGroup() const; - - void setBinRadius(F32 rad) {mEntry->mBinRadius = rad;} - void setSpatialExtents(const LLVector3& min, const LLVector3& max); - void setSpatialExtents(const LLVector4a& min, const LLVector4a& max); - void setPositionGroup(const LLVector4a& pos); - - virtual void setGroup(LLViewerOctreeGroup* group); - void shift(const LLVector4a &shift_vector); - - U32 getVisible() const {return mEntry ? mEntry->mVisible : 0;} - void setVisible() const; - void resetVisible() const; - virtual bool isVisible() const; - virtual bool isRecentlyVisible() const; - - static S32 getCurrentFrame() { return sCurVisible; } - -protected: - LLVector4a& getGroupPosition() {return mEntry->mPositionGroup;} - void initVisible(U32 visible) {mEntry->mVisible = visible;} - - static void incrementVisible() {sCurVisible++;} -protected: - LLPointer mEntry; - LLViewerOctreeEntry::eEntryDataType_t mDataType; - static U32 sCurVisible; // Counter for what value of mVisible means currently visible -};//LL_ALIGN_POSTFIX(16); - - -//defines an octree group for an octree node, which contains multiple entries. -//LL_ALIGN_PREFIX(16) -class LLViewerOctreeGroup -: public OctreeListener -{ - LL_ALIGN_NEW - friend class LLViewerOctreeCull; -protected: - virtual ~LLViewerOctreeGroup(); - -public: - enum - { - CLEAN = 0x00000000, - DIRTY = 0x00000001, - OBJECT_DIRTY = 0x00000002, - SKIP_FRUSTUM_CHECK = 0x00000004, - DEAD = 0x00000008, - INVALID_STATE = 0x00000010, - }; - -public: - typedef OctreeNode::element_iter element_iter; - typedef OctreeNode::element_list element_list; - - LLViewerOctreeGroup(OctreeNode* node); - LLViewerOctreeGroup(const LLViewerOctreeGroup& rhs) - { - *this = rhs; - } - - bool removeFromGroup(LLViewerOctreeEntryData* data); - bool removeFromGroup(LLViewerOctreeEntry* entry); - - virtual void unbound(); - virtual void rebound(); - - bool isDead() { return hasState(DEAD); } - - void setVisible(); - bool isVisible() const; - virtual bool isRecentlyVisible() const; - S32 getVisible(LLViewerCamera::eCameraID id) const {return mVisible[id];} - S32 getAnyVisible() const {return mAnyVisible;} - bool isEmpty() const { return mOctreeNode->isEmpty(); } - - U32 getState() {return mState; } - bool isDirty() const {return mState & DIRTY;} - bool hasState(U32 state) const {return mState & state;} - void setState(U32 state) {mState |= state;} - void clearState(U32 state) {mState &= ~state;} - - //LISTENER FUNCTIONS - virtual void handleInsertion(const TreeNode* node, LLViewerOctreeEntry* obj); - virtual void handleRemoval(const TreeNode* node, LLViewerOctreeEntry* obj); - virtual void handleDestruction(const TreeNode* node); - virtual void handleStateChange(const TreeNode* node); - virtual void handleChildAddition(const OctreeNode* parent, OctreeNode* child); - virtual void handleChildRemoval(const OctreeNode* parent, const OctreeNode* child); - - OctreeNode* getOctreeNode() {return mOctreeNode;} - LLViewerOctreeGroup* getParent(); - - const LLVector4a* getBounds() const {return mBounds;} - const LLVector4a* getExtents() const {return mExtents;} - const LLVector4a* getObjectBounds() const {return mObjectBounds;} - const LLVector4a* getObjectExtents() const {return mObjectExtents;} - - //octree wrappers to make code more readable - element_iter getDataBegin() { return mOctreeNode->getDataBegin(); } - element_iter getDataEnd() { return mOctreeNode->getDataEnd(); } - U32 getElementCount() const { return mOctreeNode->getElementCount(); } - bool hasElement(LLViewerOctreeEntryData* data); - -protected: - void checkStates(); -private: - virtual bool boundObjects(bool empty, LLVector4a& minOut, LLVector4a& maxOut); - -protected: - U32 mState; - OctreeNode* mOctreeNode; - - LL_ALIGN_16(LLVector4a mBounds[2]); // bounding box (center, size) of this node and all its children (tight fit to objects) - LL_ALIGN_16(LLVector4a mObjectBounds[2]); // bounding box (center, size) of objects in this node - LL_ALIGN_16(LLVector4a mExtents[2]); // extents (min, max) of this node and all its children - LL_ALIGN_16(LLVector4a mObjectExtents[2]); // extents (min, max) of objects in this node - - S32 mAnyVisible; //latest visible to any camera - S32 mVisible[LLViewerCamera::NUM_CAMERAS]; - -};//LL_ALIGN_POSTFIX(16); - -//octree group which has capability to support occlusion culling -//LL_ALIGN_PREFIX(16) -class LLOcclusionCullingGroup : public LLViewerOctreeGroup -{ -public: - typedef enum - { - OCCLUDED = 0x00010000, - QUERY_PENDING = 0x00020000, - ACTIVE_OCCLUSION = 0x00040000, - DISCARD_QUERY = 0x00080000, - EARLY_FAIL = 0x00100000, - } eOcclusionState; - - typedef enum - { - STATE_MODE_SINGLE = 0, //set one node - STATE_MODE_BRANCH, //set entire branch - STATE_MODE_DIFF, //set entire branch as long as current state is different - STATE_MODE_ALL_CAMERAS, //used for occlusion state, set state for all cameras - } eSetStateMode; - -protected: - virtual ~LLOcclusionCullingGroup(); - -public: - LLOcclusionCullingGroup(OctreeNode* node, LLViewerOctreePartition* part); - LLOcclusionCullingGroup(const LLOcclusionCullingGroup& rhs) : LLViewerOctreeGroup(rhs) - { - *this = rhs; - } - - void setOcclusionState(U32 state, S32 mode = STATE_MODE_SINGLE); - void clearOcclusionState(U32 state, S32 mode = STATE_MODE_SINGLE); - void checkOcclusion(); //read back last occlusion query (if any) - void doOcclusion(LLCamera* camera, const LLVector4a* shift = NULL); //issue occlusion query - bool isOcclusionState(U32 state) const { return mOcclusionState[LLViewerCamera::sCurCameraID] & state; } - U32 getOcclusionState() const { return mOcclusionState[LLViewerCamera::sCurCameraID];} - - bool needsUpdate(); - U32 getLastOcclusionIssuedTime(); - - //virtual - void handleChildAddition(const OctreeNode* parent, OctreeNode* child); - - //virtual - bool isRecentlyVisible() const; - LLViewerOctreePartition* getSpatialPartition()const {return mSpatialPartition;} - bool isAnyRecentlyVisible() const; - - static U32 getNewOcclusionQueryObjectName(); - static void releaseOcclusionQueryObjectName(U32 name); - -protected: - void releaseOcclusionQueryObjectNames(); - -private: - bool earlyFail(LLCamera* camera, const LLVector4a* bounds); - -protected: - U32 mOcclusionState[LLViewerCamera::NUM_CAMERAS]; - U32 mOcclusionIssued[LLViewerCamera::NUM_CAMERAS]; - - S32 mLODHash; - - LLViewerOctreePartition* mSpatialPartition; - U32 mOcclusionQuery[LLViewerCamera::NUM_CAMERAS]; - U32 mOcclusionCheckCount[LLViewerCamera::NUM_CAMERAS]; - -public: - static std::set sPendingQueries; -};//LL_ALIGN_POSTFIX(16); - -class LLViewerOctreePartition -{ -public: - LLViewerOctreePartition(); - virtual ~LLViewerOctreePartition(); - - // Cull on arbitrary frustum - virtual S32 cull(LLCamera &camera, bool do_occlusion) = 0; - bool isOcclusionEnabled(); - -protected: - // MUST call from destructor of any derived classes (SL-17276) - void cleanup(); - -public: - U32 mPartitionType; - U32 mDrawableType; - OctreeNode* mOctree; - LLViewerRegion* mRegionp; // the region this partition belongs to. - bool mOcclusionEnabled; // if true, occlusion culling is performed - U32 mLODSeed; - U32 mLODPeriod; //number of frames between LOD updates for a given spatial group (staggered by mLODSeed) -}; - -class LLViewerOctreeCull : public OctreeTraveler -{ -public: - LLViewerOctreeCull(LLCamera* camera) - : mCamera(camera), mRes(0) { } - - virtual void traverse(const OctreeNode* n); - -protected: - virtual bool earlyFail(LLViewerOctreeGroup* group); - - //agent space group cull - S32 AABBInFrustumNoFarClipGroupBounds(const LLViewerOctreeGroup* group); - S32 AABBSphereIntersectGroupExtents(const LLViewerOctreeGroup* group); - S32 AABBInFrustumGroupBounds(const LLViewerOctreeGroup* group); - - //agent space object set cull - S32 AABBInFrustumNoFarClipObjectBounds(const LLViewerOctreeGroup* group); - S32 AABBSphereIntersectObjectExtents(const LLViewerOctreeGroup* group); - S32 AABBInFrustumObjectBounds(const LLViewerOctreeGroup* group); - - //local region space group cull - S32 AABBInRegionFrustumNoFarClipGroupBounds(const LLViewerOctreeGroup* group); - S32 AABBInRegionFrustumGroupBounds(const LLViewerOctreeGroup* group); - S32 AABBRegionSphereIntersectGroupExtents(const LLViewerOctreeGroup* group, const LLVector3& shift); - - //local region space object set cull - S32 AABBInRegionFrustumNoFarClipObjectBounds(const LLViewerOctreeGroup* group); - S32 AABBInRegionFrustumObjectBounds(const LLViewerOctreeGroup* group); - S32 AABBRegionSphereIntersectObjectExtents(const LLViewerOctreeGroup* group, const LLVector3& shift); - - virtual S32 frustumCheck(const LLViewerOctreeGroup* group) = 0; - virtual S32 frustumCheckObjects(const LLViewerOctreeGroup* group) = 0; - - bool checkProjectionArea(const LLVector4a& center, const LLVector4a& size, const LLVector3& shift, F32 pixel_threshold, F32 near_radius); - virtual bool checkObjects(const OctreeNode* branch, const LLViewerOctreeGroup* group); - virtual void preprocess(LLViewerOctreeGroup* group); - virtual void processGroup(LLViewerOctreeGroup* group); - virtual void visit(const OctreeNode* branch); - -protected: - LLCamera *mCamera; - S32 mRes; -}; - -//scan the octree, output the info of each node for debug use. -class LLViewerOctreeDebug : public OctreeTraveler -{ -public: - virtual void processGroup(LLViewerOctreeGroup* group); - virtual void visit(const OctreeNode* branch); - -public: - static bool sInDebug; -}; - -#endif +/** + * @file llvieweroctree.h + * @brief LLViewerObjectOctree.cpp header file, defining all supporting classes. + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_VIEWEROCTREE_H +#define LL_VIEWEROCTREE_H + +#include +#include + +#include "v2math.h" +#include "v3math.h" +#include "v4math.h" +#include "m4math.h" +#include "llvector4a.h" +#include "llquaternion.h" +#include "lloctree.h" +#include "llviewercamera.h" + +class LLViewerRegion; +class LLViewerOctreeEntryData; +class LLViewerOctreeGroup; +class LLViewerOctreeEntry; +class LLViewerOctreePartition; + +typedef LLOctreeListener> OctreeListener; +typedef LLTreeNode TreeNode; +typedef LLOctreeNode> OctreeNode; +typedef LLOctreeRoot> OctreeRoot; +typedef LLOctreeTraveler> OctreeTraveler; + +#if LL_OCTREE_PARANOIA_CHECK +#define assert_octree_valid(x) x->validate() +#define assert_states_valid(x) ((LLViewerOctreeGroup*) x->mSpatialPartition->mOctree->getListener(0))->checkStates() +#else +#define assert_octree_valid(x) +#define assert_states_valid(x) +#endif + +// get index buffer for binary encoded axis vertex buffer given a box at center being viewed by given camera +U32 get_box_fan_indices(LLCamera* camera, const LLVector4a& center); +U8* get_box_fan_indices_ptr(LLCamera* camera, const LLVector4a& center); + +S32 AABBSphereIntersect(const LLVector4a& min, const LLVector4a& max, const LLVector3 &origin, const F32 &rad); +S32 AABBSphereIntersectR2(const LLVector4a& min, const LLVector4a& max, const LLVector3 &origin, const F32 &radius_squared); + +S32 AABBSphereIntersect(const LLVector3& min, const LLVector3& max, const LLVector3 &origin, const F32 &rad); +S32 AABBSphereIntersectR2(const LLVector3& min, const LLVector3& max, const LLVector3 &origin, const F32 &radius_squared); + +//defines data needed for octree of an entry +//LL_ALIGN_PREFIX(16) +class LLViewerOctreeEntry : public LLRefCount +{ + LL_ALIGN_NEW + friend class LLViewerOctreeEntryData; + +public: + typedef enum + { + LLDRAWABLE = 0, + LLVOCACHEENTRY, + NUM_DATA_TYPE + }eEntryDataType_t; + +protected: + virtual ~LLViewerOctreeEntry(); + +public: + LLViewerOctreeEntry(); + + void nullGroup(); //called by group handleDestruction() only + void setGroup(LLViewerOctreeGroup* group); + void removeData(LLViewerOctreeEntryData* data); + + LLViewerOctreeEntryData* getDrawable() const {return mData[LLDRAWABLE];} + bool hasDrawable() const {return mData[LLDRAWABLE] != NULL;} + LLViewerOctreeEntryData* getVOCacheEntry() const {return mData[LLVOCACHEENTRY];} + bool hasVOCacheEntry() const {return mData[LLVOCACHEENTRY] != NULL;} + + const LLVector4a* getSpatialExtents() const {return mExtents;} + const LLVector4a& getPositionGroup() const {return mPositionGroup;} + LLViewerOctreeGroup* getGroup()const {return mGroup;} + + F32 getBinRadius() const {return mBinRadius;} + S32 getBinIndex() const {return mBinIndex; } + void setBinIndex(S32 index) const {mBinIndex = index; } + +private: + void addData(LLViewerOctreeEntryData* data); + +private: + LLViewerOctreeEntryData* mData[NUM_DATA_TYPE]; //do not use LLPointer here. + LLViewerOctreeGroup* mGroup; + + //aligned members + LL_ALIGN_16(LLVector4a mExtents[2]); + LL_ALIGN_16(LLVector4a mPositionGroup); + F32 mBinRadius; + mutable S32 mBinIndex; + mutable U32 mVisible; + +} ;//LL_ALIGN_POSTFIX(16); + +//defines an abstract class for entry data +//LL_ALIGN_PREFIX(16) +class LLViewerOctreeEntryData : public LLRefCount +{ +protected: + virtual ~LLViewerOctreeEntryData(); + +public: + LLViewerOctreeEntryData(const LLViewerOctreeEntryData& rhs) + { + *this = rhs; + } + LLViewerOctreeEntryData(LLViewerOctreeEntry::eEntryDataType_t data_type); + + LLViewerOctreeEntry::eEntryDataType_t getDataType() const {return mDataType;} + LLViewerOctreeEntry* getEntry() {return mEntry;} + + virtual void setOctreeEntry(LLViewerOctreeEntry* entry); + void removeOctreeEntry(); + + F32 getBinRadius() const {return mEntry->getBinRadius();} + const LLVector4a* getSpatialExtents() const; + LLViewerOctreeGroup* getGroup()const; + const LLVector4a& getPositionGroup() const; + + void setBinRadius(F32 rad) {mEntry->mBinRadius = rad;} + void setSpatialExtents(const LLVector3& min, const LLVector3& max); + void setSpatialExtents(const LLVector4a& min, const LLVector4a& max); + void setPositionGroup(const LLVector4a& pos); + + virtual void setGroup(LLViewerOctreeGroup* group); + void shift(const LLVector4a &shift_vector); + + U32 getVisible() const {return mEntry ? mEntry->mVisible : 0;} + void setVisible() const; + void resetVisible() const; + virtual bool isVisible() const; + virtual bool isRecentlyVisible() const; + + static S32 getCurrentFrame() { return sCurVisible; } + +protected: + LLVector4a& getGroupPosition() {return mEntry->mPositionGroup;} + void initVisible(U32 visible) {mEntry->mVisible = visible;} + + static void incrementVisible() {sCurVisible++;} +protected: + LLPointer mEntry; + LLViewerOctreeEntry::eEntryDataType_t mDataType; + static U32 sCurVisible; // Counter for what value of mVisible means currently visible +};//LL_ALIGN_POSTFIX(16); + + +//defines an octree group for an octree node, which contains multiple entries. +//LL_ALIGN_PREFIX(16) +class LLViewerOctreeGroup +: public OctreeListener +{ + LL_ALIGN_NEW + friend class LLViewerOctreeCull; +protected: + virtual ~LLViewerOctreeGroup(); + +public: + enum + { + CLEAN = 0x00000000, + DIRTY = 0x00000001, + OBJECT_DIRTY = 0x00000002, + SKIP_FRUSTUM_CHECK = 0x00000004, + DEAD = 0x00000008, + INVALID_STATE = 0x00000010, + }; + +public: + typedef OctreeNode::element_iter element_iter; + typedef OctreeNode::element_list element_list; + + LLViewerOctreeGroup(OctreeNode* node); + LLViewerOctreeGroup(const LLViewerOctreeGroup& rhs) + { + *this = rhs; + } + + bool removeFromGroup(LLViewerOctreeEntryData* data); + bool removeFromGroup(LLViewerOctreeEntry* entry); + + virtual void unbound(); + virtual void rebound(); + + bool isDead() { return hasState(DEAD); } + + void setVisible(); + bool isVisible() const; + virtual bool isRecentlyVisible() const; + S32 getVisible(LLViewerCamera::eCameraID id) const {return mVisible[id];} + S32 getAnyVisible() const {return mAnyVisible;} + bool isEmpty() const { return mOctreeNode->isEmpty(); } + + U32 getState() {return mState; } + bool isDirty() const {return mState & DIRTY;} + bool hasState(U32 state) const {return mState & state;} + void setState(U32 state) {mState |= state;} + void clearState(U32 state) {mState &= ~state;} + + //LISTENER FUNCTIONS + virtual void handleInsertion(const TreeNode* node, LLViewerOctreeEntry* obj); + virtual void handleRemoval(const TreeNode* node, LLViewerOctreeEntry* obj); + virtual void handleDestruction(const TreeNode* node); + virtual void handleStateChange(const TreeNode* node); + virtual void handleChildAddition(const OctreeNode* parent, OctreeNode* child); + virtual void handleChildRemoval(const OctreeNode* parent, const OctreeNode* child); + + OctreeNode* getOctreeNode() {return mOctreeNode;} + LLViewerOctreeGroup* getParent(); + + const LLVector4a* getBounds() const {return mBounds;} + const LLVector4a* getExtents() const {return mExtents;} + const LLVector4a* getObjectBounds() const {return mObjectBounds;} + const LLVector4a* getObjectExtents() const {return mObjectExtents;} + + //octree wrappers to make code more readable + element_iter getDataBegin() { return mOctreeNode->getDataBegin(); } + element_iter getDataEnd() { return mOctreeNode->getDataEnd(); } + U32 getElementCount() const { return mOctreeNode->getElementCount(); } + bool hasElement(LLViewerOctreeEntryData* data); + +protected: + void checkStates(); +private: + virtual bool boundObjects(bool empty, LLVector4a& minOut, LLVector4a& maxOut); + +protected: + U32 mState; + OctreeNode* mOctreeNode; + + LL_ALIGN_16(LLVector4a mBounds[2]); // bounding box (center, size) of this node and all its children (tight fit to objects) + LL_ALIGN_16(LLVector4a mObjectBounds[2]); // bounding box (center, size) of objects in this node + LL_ALIGN_16(LLVector4a mExtents[2]); // extents (min, max) of this node and all its children + LL_ALIGN_16(LLVector4a mObjectExtents[2]); // extents (min, max) of objects in this node + + S32 mAnyVisible; //latest visible to any camera + S32 mVisible[LLViewerCamera::NUM_CAMERAS]; + +};//LL_ALIGN_POSTFIX(16); + +//octree group which has capability to support occlusion culling +//LL_ALIGN_PREFIX(16) +class LLOcclusionCullingGroup : public LLViewerOctreeGroup +{ +public: + typedef enum + { + OCCLUDED = 0x00010000, + QUERY_PENDING = 0x00020000, + ACTIVE_OCCLUSION = 0x00040000, + DISCARD_QUERY = 0x00080000, + EARLY_FAIL = 0x00100000, + } eOcclusionState; + + typedef enum + { + STATE_MODE_SINGLE = 0, //set one node + STATE_MODE_BRANCH, //set entire branch + STATE_MODE_DIFF, //set entire branch as long as current state is different + STATE_MODE_ALL_CAMERAS, //used for occlusion state, set state for all cameras + } eSetStateMode; + +protected: + virtual ~LLOcclusionCullingGroup(); + +public: + LLOcclusionCullingGroup(OctreeNode* node, LLViewerOctreePartition* part); + LLOcclusionCullingGroup(const LLOcclusionCullingGroup& rhs) : LLViewerOctreeGroup(rhs) + { + *this = rhs; + } + + void setOcclusionState(U32 state, S32 mode = STATE_MODE_SINGLE); + void clearOcclusionState(U32 state, S32 mode = STATE_MODE_SINGLE); + void checkOcclusion(); //read back last occlusion query (if any) + void doOcclusion(LLCamera* camera, const LLVector4a* shift = NULL); //issue occlusion query + bool isOcclusionState(U32 state) const { return mOcclusionState[LLViewerCamera::sCurCameraID] & state; } + U32 getOcclusionState() const { return mOcclusionState[LLViewerCamera::sCurCameraID];} + + bool needsUpdate(); + U32 getLastOcclusionIssuedTime(); + + //virtual + void handleChildAddition(const OctreeNode* parent, OctreeNode* child); + + //virtual + bool isRecentlyVisible() const; + LLViewerOctreePartition* getSpatialPartition()const {return mSpatialPartition;} + bool isAnyRecentlyVisible() const; + + static U32 getNewOcclusionQueryObjectName(); + static void releaseOcclusionQueryObjectName(U32 name); + +protected: + void releaseOcclusionQueryObjectNames(); + +private: + bool earlyFail(LLCamera* camera, const LLVector4a* bounds); + +protected: + U32 mOcclusionState[LLViewerCamera::NUM_CAMERAS]; + U32 mOcclusionIssued[LLViewerCamera::NUM_CAMERAS]; + + S32 mLODHash; + + LLViewerOctreePartition* mSpatialPartition; + U32 mOcclusionQuery[LLViewerCamera::NUM_CAMERAS]; + U32 mOcclusionCheckCount[LLViewerCamera::NUM_CAMERAS]; + +public: + static std::set sPendingQueries; +};//LL_ALIGN_POSTFIX(16); + +class LLViewerOctreePartition +{ +public: + LLViewerOctreePartition(); + virtual ~LLViewerOctreePartition(); + + // Cull on arbitrary frustum + virtual S32 cull(LLCamera &camera, bool do_occlusion) = 0; + bool isOcclusionEnabled(); + +protected: + // MUST call from destructor of any derived classes (SL-17276) + void cleanup(); + +public: + U32 mPartitionType; + U32 mDrawableType; + OctreeNode* mOctree; + LLViewerRegion* mRegionp; // the region this partition belongs to. + bool mOcclusionEnabled; // if true, occlusion culling is performed + U32 mLODSeed; + U32 mLODPeriod; //number of frames between LOD updates for a given spatial group (staggered by mLODSeed) +}; + +class LLViewerOctreeCull : public OctreeTraveler +{ +public: + LLViewerOctreeCull(LLCamera* camera) + : mCamera(camera), mRes(0) { } + + virtual void traverse(const OctreeNode* n); + +protected: + virtual bool earlyFail(LLViewerOctreeGroup* group); + + //agent space group cull + S32 AABBInFrustumNoFarClipGroupBounds(const LLViewerOctreeGroup* group); + S32 AABBSphereIntersectGroupExtents(const LLViewerOctreeGroup* group); + S32 AABBInFrustumGroupBounds(const LLViewerOctreeGroup* group); + + //agent space object set cull + S32 AABBInFrustumNoFarClipObjectBounds(const LLViewerOctreeGroup* group); + S32 AABBSphereIntersectObjectExtents(const LLViewerOctreeGroup* group); + S32 AABBInFrustumObjectBounds(const LLViewerOctreeGroup* group); + + //local region space group cull + S32 AABBInRegionFrustumNoFarClipGroupBounds(const LLViewerOctreeGroup* group); + S32 AABBInRegionFrustumGroupBounds(const LLViewerOctreeGroup* group); + S32 AABBRegionSphereIntersectGroupExtents(const LLViewerOctreeGroup* group, const LLVector3& shift); + + //local region space object set cull + S32 AABBInRegionFrustumNoFarClipObjectBounds(const LLViewerOctreeGroup* group); + S32 AABBInRegionFrustumObjectBounds(const LLViewerOctreeGroup* group); + S32 AABBRegionSphereIntersectObjectExtents(const LLViewerOctreeGroup* group, const LLVector3& shift); + + virtual S32 frustumCheck(const LLViewerOctreeGroup* group) = 0; + virtual S32 frustumCheckObjects(const LLViewerOctreeGroup* group) = 0; + + bool checkProjectionArea(const LLVector4a& center, const LLVector4a& size, const LLVector3& shift, F32 pixel_threshold, F32 near_radius); + virtual bool checkObjects(const OctreeNode* branch, const LLViewerOctreeGroup* group); + virtual void preprocess(LLViewerOctreeGroup* group); + virtual void processGroup(LLViewerOctreeGroup* group); + virtual void visit(const OctreeNode* branch); + +protected: + LLCamera *mCamera; + S32 mRes; +}; + +//scan the octree, output the info of each node for debug use. +class LLViewerOctreeDebug : public OctreeTraveler +{ +public: + virtual void processGroup(LLViewerOctreeGroup* group); + virtual void visit(const OctreeNode* branch); + +public: + static bool sInDebug; +}; + +#endif diff --git a/indra/newview/llviewerparcelmedia.cpp b/indra/newview/llviewerparcelmedia.cpp index a815d4928f..9af0062f1e 100644 --- a/indra/newview/llviewerparcelmedia.cpp +++ b/indra/newview/llviewerparcelmedia.cpp @@ -1,628 +1,628 @@ -/** - * @file llviewerparcelmedia.cpp - * @brief Handlers for multimedia on a per-parcel basis - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llviewerparcelmedia.h" - -#include "llagent.h" -#include "llaudioengine.h" -#include "llmimetypes.h" -#include "llviewercontrol.h" -#include "llviewermedia.h" -#include "llviewerregion.h" -#include "llparcel.h" -#include "llviewerparcelmgr.h" -#include "lluuid.h" -#include "message.h" -#include "llviewermediafocus.h" -#include "llviewerparcelmediaautoplay.h" -#include "llnotificationsutil.h" -//#include "llfirstuse.h" -#include "llpluginclassmedia.h" -#include "llviewertexture.h" -#include "llcorehttputil.h" - - -LLViewerParcelMedia::LLViewerParcelMedia(): -mMediaParcelLocalID(0) -{ - LLMessageSystem* msg = gMessageSystem; - msg->setHandlerFunc("ParcelMediaCommandMessage", parcelMediaCommandMessageHandler ); - msg->setHandlerFunc("ParcelMediaUpdate", parcelMediaUpdateHandler ); - - // LLViewerParcelMediaAutoPlay will regularly check and autoplay media, - // might be good idea to just integrate it into LLViewerParcelMedia - LLSingleton::getInstance(); -} - -LLViewerParcelMedia::~LLViewerParcelMedia() -{ - // This needs to be destroyed before global destructor time. - mMediaImpl = NULL; -} - -////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerParcelMedia::update(LLParcel* parcel) -{ - if (/*LLViewerMedia::hasMedia()*/ true) - { - // we have a player - if (parcel) - { - if(!gAgent.getRegion()) - { - mMediaRegionID = LLUUID() ; - stop() ; - LL_DEBUGS("Media") << "no agent region, bailing out." << LL_ENDL; - return ; - } - - // we're in a parcel - S32 parcelid = parcel->getLocalID(); - - LLUUID regionid = gAgent.getRegion()->getRegionID(); - bool location_changed = false; - if (parcelid != mMediaParcelLocalID || regionid != mMediaRegionID) - { - LL_DEBUGS("Media") << "New parcel, parcel id = " << parcelid << ", region id = " << regionid << LL_ENDL; - mMediaParcelLocalID = parcelid; - mMediaRegionID = regionid; - location_changed = true; - } - - std::string mediaUrl = std::string ( parcel->getMediaURL () ); - std::string mediaCurrentUrl = std::string( parcel->getMediaCurrentURL()); - - // if we have a current (link sharing) url, use it instead - if (mediaCurrentUrl != "" && parcel->getMediaType() == HTTP_CONTENT_TEXT_HTML) - { - mediaUrl = mediaCurrentUrl; - } - - LLStringUtil::trim(mediaUrl); - - // If no parcel media is playing, nothing left to do - if(mMediaImpl.isNull()) - - { - // media will be autoplayed by LLViewerParcelMediaAutoPlay - return; - } - - // Media is playing...has something changed? - else if (( mMediaImpl->getMediaURL() != mediaUrl ) - || ( mMediaImpl->getMediaTextureID() != parcel->getMediaID() ) - || ( mMediaImpl->getMimeType() != parcel->getMediaType() )) - { - // Only play if the media types are the same and parcel stays same. - if(mMediaImpl->getMimeType() == parcel->getMediaType() - && !location_changed) - { - play(parcel); - } - - else - { - stop(); - } - } - } - else - { - stop(); - } - } -} - -// static -void LLViewerParcelMedia::play(LLParcel* parcel) -{ - LL_DEBUGS() << "LLViewerParcelMedia::play" << LL_ENDL; - - if (!parcel) return; - - if (!gSavedSettings.getBOOL("AudioStreamingMedia")) - return; - - std::string media_url = parcel->getMediaURL(); - std::string media_current_url = parcel->getMediaCurrentURL(); - std::string mime_type = parcel->getMediaType(); - LLUUID placeholder_texture_id = parcel->getMediaID(); - U8 media_auto_scale = parcel->getMediaAutoScale(); - U8 media_loop = parcel->getMediaLoop(); - S32 media_width = parcel->getMediaWidth(); - S32 media_height = parcel->getMediaHeight(); - - if(mMediaImpl) - { - // If the url and mime type are the same, call play again - if(mMediaImpl->getMediaURL() == media_url - && mMediaImpl->getMimeType() == mime_type - && mMediaImpl->getMediaTextureID() == placeholder_texture_id) - { - LL_DEBUGS("Media") << "playing with existing url " << media_url << LL_ENDL; - - mMediaImpl->play(); - } - // Else if the texture id's are the same, navigate and rediscover type - // MBW -- This causes other state from the previous parcel (texture size, autoscale, and looping) to get re-used incorrectly. - // It's also not really necessary -- just creating a new instance is fine. -// else if(mMediaImpl->getMediaTextureID() == placeholder_texture_id) -// { -// mMediaImpl->navigateTo(media_url, mime_type, true); -// } - else - { - // Since the texture id is different, we need to generate a new impl - - // Delete the old one first so they don't fight over the texture. - mMediaImpl = NULL; - - // A new impl will be created below. - } - } - - // Don't ever try to play if the media type is set to "none/none" - if(stricmp(mime_type.c_str(), LLMIMETypes::getDefaultMimeType().c_str()) != 0) - { - if(!mMediaImpl) - { - LL_DEBUGS("Media") << "new media impl with mime type " << mime_type << ", url " << media_url << LL_ENDL; - - // There is no media impl, make a new one - mMediaImpl = LLViewerMedia::getInstance()->newMediaImpl( - placeholder_texture_id, - media_width, - media_height, - media_auto_scale, - media_loop); - mMediaImpl->setIsParcelMedia(true); - mMediaImpl->navigateTo(media_url, mime_type, true); - } - - //LLFirstUse::useMedia(); - - LLViewerParcelMediaAutoPlay::playStarted(); - } -} - -// static -void LLViewerParcelMedia::stop() -{ - if(mMediaImpl.isNull()) - { - return; - } - - // We need to remove the media HUD if it is up. - LLViewerMediaFocus::getInstance()->clearFocus(); - - // This will unload & kill the media instance. - mMediaImpl = NULL; -} - -// static -void LLViewerParcelMedia::pause() -{ - if(mMediaImpl.isNull()) - { - return; - } - mMediaImpl->pause(); -} - -// static -void LLViewerParcelMedia::start() -{ - if(mMediaImpl.isNull()) - { - return; - } - mMediaImpl->start(); - - //LLFirstUse::useMedia(); - - LLViewerParcelMediaAutoPlay::playStarted(); -} - -// static -void LLViewerParcelMedia::seek(F32 time) -{ - if(mMediaImpl.isNull()) - { - return; - } - mMediaImpl->seek(time); -} - -// static -void LLViewerParcelMedia::focus(bool focus) -{ - mMediaImpl->focus(focus); -} - -// static -LLPluginClassMediaOwner::EMediaStatus LLViewerParcelMedia::getStatus() -{ - LLPluginClassMediaOwner::EMediaStatus result = LLPluginClassMediaOwner::MEDIA_NONE; - - if(mMediaImpl.notNull() && mMediaImpl->hasMedia()) - { - result = mMediaImpl->getMediaPlugin()->getStatus(); - } - - return result; -} - -// static -std::string LLViewerParcelMedia::getMimeType() -{ - return mMediaImpl.notNull() ? mMediaImpl->getMimeType() : LLMIMETypes::getDefaultMimeType(); -} - -//static -std::string LLViewerParcelMedia::getURL() -{ - std::string url; - if(mMediaImpl.notNull()) - url = mMediaImpl->getMediaURL(); - - if(stricmp(LLViewerParcelMgr::getInstance()->getAgentParcel()->getMediaType().c_str(), LLMIMETypes::getDefaultMimeType().c_str()) != 0) - { - if (url.empty()) - url = LLViewerParcelMgr::getInstance()->getAgentParcel()->getMediaCurrentURL(); - - if (url.empty()) - url = LLViewerParcelMgr::getInstance()->getAgentParcel()->getMediaURL(); - } - - return url; -} - -//static -std::string LLViewerParcelMedia::getName() -{ - if(mMediaImpl.notNull()) - return mMediaImpl->getName(); - return ""; -} - -viewer_media_t LLViewerParcelMedia::getParcelMedia() -{ - return mMediaImpl; -} - -////////////////////////////////////////////////////////////////////////////////////////// -// static -void LLViewerParcelMedia::parcelMediaCommandMessageHandler(LLMessageSystem *msg, void **) -{ - getInstance()->processParcelMediaCommandMessage(msg); -} - -void LLViewerParcelMedia::processParcelMediaCommandMessage( LLMessageSystem *msg) -{ - // extract the agent id - // LLUUID agent_id; - // msg->getUUID( agent_id ); - - U32 flags; - U32 command; - F32 time; - msg->getU32( "CommandBlock", "Flags", flags ); - msg->getU32( "CommandBlock", "Command", command); - msg->getF32( "CommandBlock", "Time", time ); - - if (flags &( (1<getAgentParcel(); - play(parcel); - } - } - else - // unload - if( command == PARCEL_MEDIA_COMMAND_UNLOAD ) - { - stop(); - } - } - - if (flags & (1<getAgentParcel(); - play(parcel); - } - seek(time); - } -} - -////////////////////////////////////////////////////////////////////////////////////////// -// static -void LLViewerParcelMedia::parcelMediaUpdateHandler(LLMessageSystem *msg, void **) -{ - getInstance()->processParcelMediaUpdate(msg); -} - -void LLViewerParcelMedia::processParcelMediaUpdate( LLMessageSystem *msg) -{ - LLUUID media_id; - std::string media_url; - std::string media_type; - S32 media_width = 0; - S32 media_height = 0; - U8 media_auto_scale = 0; - U8 media_loop = 0; - - msg->getUUID( "DataBlock", "MediaID", media_id ); - char media_url_buffer[257]; - msg->getString( "DataBlock", "MediaURL", 255, media_url_buffer ); - media_url = media_url_buffer; - msg->getU8("DataBlock", "MediaAutoScale", media_auto_scale); - - if (msg->has("DataBlockExtended")) // do we have the extended data? - { - char media_type_buffer[257]; - msg->getString("DataBlockExtended", "MediaType", 255, media_type_buffer); - media_type = media_type_buffer; - msg->getU8("DataBlockExtended", "MediaLoop", media_loop); - msg->getS32("DataBlockExtended", "MediaWidth", media_width); - msg->getS32("DataBlockExtended", "MediaHeight", media_height); - } - - LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - if (parcel) - { - bool same = ((parcel->getMediaURL() == media_url) && - (parcel->getMediaType() == media_type) && - (parcel->getMediaID() == media_id) && - (parcel->getMediaWidth() == media_width) && - (parcel->getMediaHeight() == media_height) && - (parcel->getMediaAutoScale() == media_auto_scale) && - (parcel->getMediaLoop() == media_loop)); - - if (!same) - { - // temporarily store these new values in the parcel - parcel->setMediaURL(media_url); - parcel->setMediaType(media_type); - parcel->setMediaID(media_id); - parcel->setMediaWidth(media_width); - parcel->setMediaHeight(media_height); - parcel->setMediaAutoScale(media_auto_scale); - parcel->setMediaLoop(media_loop); - - play(parcel); - } - } -} -// Static -///////////////////////////////////////////////////////////////////////////////////////// -// *TODO: I can not find any active code where this method is called... -void LLViewerParcelMedia::sendMediaNavigateMessage(const std::string& url) -{ - std::string region_url = gAgent.getRegionCapability("ParcelNavigateMedia"); - if (!region_url.empty()) - { - // send navigate event to sim for link sharing - LLSD body; - body["agent-id"] = gAgent.getID(); - body["local-id"] = LLViewerParcelMgr::getInstance()->getAgentParcel()->getLocalID(); - body["url"] = url; - - LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(region_url, body, - "Media Navigation sent to sim.", "Media Navigation failed to send to sim."); - } - else - { - LL_WARNS() << "can't get ParcelNavigateMedia capability" << LL_ENDL; - } - -} - -///////////////////////////////////////////////////////////////////////////////////////// -// inherited from LLViewerMediaObserver -// virtual -void LLViewerParcelMedia::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) -{ - switch(event) - { - case MEDIA_EVENT_DEBUG_MESSAGE: - { - // LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_DEBUG_MESSAGE " << LL_ENDL; - }; - break; - - case MEDIA_EVENT_CONTENT_UPDATED: - { - // LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CONTENT_UPDATED " << LL_ENDL; - }; - break; - - case MEDIA_EVENT_TIME_DURATION_UPDATED: - { - // LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_TIME_DURATION_UPDATED, time is " << self->getCurrentTime() << " of " << self->getDuration() << LL_ENDL; - }; - break; - - case MEDIA_EVENT_SIZE_CHANGED: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_SIZE_CHANGED " << LL_ENDL; - }; - break; - - case MEDIA_EVENT_CURSOR_CHANGED: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CURSOR_CHANGED, new cursor is " << self->getCursorName() << LL_ENDL; - }; - break; - - case MEDIA_EVENT_NAVIGATE_BEGIN: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_NAVIGATE_BEGIN " << LL_ENDL; - }; - break; - - case MEDIA_EVENT_NAVIGATE_COMPLETE: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_NAVIGATE_COMPLETE, result string is: " << self->getNavigateResultString() << LL_ENDL; - }; - break; - - case MEDIA_EVENT_PROGRESS_UPDATED: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PROGRESS_UPDATED, loading at " << self->getProgressPercent() << "%" << LL_ENDL; - }; - break; - - case MEDIA_EVENT_STATUS_TEXT_CHANGED: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_STATUS_TEXT_CHANGED, new status text is: " << self->getStatusText() << LL_ENDL; - }; - break; - - case MEDIA_EVENT_LOCATION_CHANGED: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_LOCATION_CHANGED, new uri is: " << self->getLocation() << LL_ENDL; - }; - break; - - case MEDIA_EVENT_NAVIGATE_ERROR_PAGE: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_NAVIGATE_ERROR_PAGE" << LL_ENDL; - }; - break; - - case MEDIA_EVENT_CLICK_LINK_HREF: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CLICK_LINK_HREF, target is \"" << self->getClickTarget() << "\", uri is " << self->getClickURL() << LL_ENDL; - }; - break; - - case MEDIA_EVENT_CLICK_LINK_NOFOLLOW: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CLICK_LINK_NOFOLLOW, uri is " << self->getClickURL() << LL_ENDL; - }; - break; - - case MEDIA_EVENT_PLUGIN_FAILED: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PLUGIN_FAILED" << LL_ENDL; - }; - break; - - case MEDIA_EVENT_PLUGIN_FAILED_LAUNCH: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PLUGIN_FAILED_LAUNCH" << LL_ENDL; - }; - break; - - case MEDIA_EVENT_NAME_CHANGED: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_NAME_CHANGED" << LL_ENDL; - }; - break; - - case MEDIA_EVENT_CLOSE_REQUEST: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CLOSE_REQUEST" << LL_ENDL; - } - break; - - case MEDIA_EVENT_PICK_FILE_REQUEST: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PICK_FILE_REQUEST" << LL_ENDL; - } - break; - - case MEDIA_EVENT_FILE_DOWNLOAD: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_FILE_DOWNLOAD" << LL_ENDL; - } - break; - - case MEDIA_EVENT_GEOMETRY_CHANGE: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_GEOMETRY_CHANGE, uuid is " << self->getClickUUID() << LL_ENDL; - } - break; - - case MEDIA_EVENT_AUTH_REQUEST: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_AUTH_REQUEST, url " << self->getAuthURL() << ", realm " << self->getAuthRealm() << LL_ENDL; - } - break; - - case MEDIA_EVENT_LINK_HOVERED: - { - LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_LINK_HOVERED, hover text is: " << self->getHoverText() << LL_ENDL; - }; - break; - }; -} - -// TODO: observer -/* -void LLViewerParcelMediaNavigationObserver::onNavigateComplete( const EventType& event_in ) -{ - std::string url = event_in.getStringValue(); - - if (mCurrentURL != url && ! mFromMessage) - { - LLViewerParcelMedia::sendMediaNavigateMessage(url); - } - - mCurrentURL = url; - mFromMessage = false; - -} -*/ +/** + * @file llviewerparcelmedia.cpp + * @brief Handlers for multimedia on a per-parcel basis + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llviewerparcelmedia.h" + +#include "llagent.h" +#include "llaudioengine.h" +#include "llmimetypes.h" +#include "llviewercontrol.h" +#include "llviewermedia.h" +#include "llviewerregion.h" +#include "llparcel.h" +#include "llviewerparcelmgr.h" +#include "lluuid.h" +#include "message.h" +#include "llviewermediafocus.h" +#include "llviewerparcelmediaautoplay.h" +#include "llnotificationsutil.h" +//#include "llfirstuse.h" +#include "llpluginclassmedia.h" +#include "llviewertexture.h" +#include "llcorehttputil.h" + + +LLViewerParcelMedia::LLViewerParcelMedia(): +mMediaParcelLocalID(0) +{ + LLMessageSystem* msg = gMessageSystem; + msg->setHandlerFunc("ParcelMediaCommandMessage", parcelMediaCommandMessageHandler ); + msg->setHandlerFunc("ParcelMediaUpdate", parcelMediaUpdateHandler ); + + // LLViewerParcelMediaAutoPlay will regularly check and autoplay media, + // might be good idea to just integrate it into LLViewerParcelMedia + LLSingleton::getInstance(); +} + +LLViewerParcelMedia::~LLViewerParcelMedia() +{ + // This needs to be destroyed before global destructor time. + mMediaImpl = NULL; +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerParcelMedia::update(LLParcel* parcel) +{ + if (/*LLViewerMedia::hasMedia()*/ true) + { + // we have a player + if (parcel) + { + if(!gAgent.getRegion()) + { + mMediaRegionID = LLUUID() ; + stop() ; + LL_DEBUGS("Media") << "no agent region, bailing out." << LL_ENDL; + return ; + } + + // we're in a parcel + S32 parcelid = parcel->getLocalID(); + + LLUUID regionid = gAgent.getRegion()->getRegionID(); + bool location_changed = false; + if (parcelid != mMediaParcelLocalID || regionid != mMediaRegionID) + { + LL_DEBUGS("Media") << "New parcel, parcel id = " << parcelid << ", region id = " << regionid << LL_ENDL; + mMediaParcelLocalID = parcelid; + mMediaRegionID = regionid; + location_changed = true; + } + + std::string mediaUrl = std::string ( parcel->getMediaURL () ); + std::string mediaCurrentUrl = std::string( parcel->getMediaCurrentURL()); + + // if we have a current (link sharing) url, use it instead + if (mediaCurrentUrl != "" && parcel->getMediaType() == HTTP_CONTENT_TEXT_HTML) + { + mediaUrl = mediaCurrentUrl; + } + + LLStringUtil::trim(mediaUrl); + + // If no parcel media is playing, nothing left to do + if(mMediaImpl.isNull()) + + { + // media will be autoplayed by LLViewerParcelMediaAutoPlay + return; + } + + // Media is playing...has something changed? + else if (( mMediaImpl->getMediaURL() != mediaUrl ) + || ( mMediaImpl->getMediaTextureID() != parcel->getMediaID() ) + || ( mMediaImpl->getMimeType() != parcel->getMediaType() )) + { + // Only play if the media types are the same and parcel stays same. + if(mMediaImpl->getMimeType() == parcel->getMediaType() + && !location_changed) + { + play(parcel); + } + + else + { + stop(); + } + } + } + else + { + stop(); + } + } +} + +// static +void LLViewerParcelMedia::play(LLParcel* parcel) +{ + LL_DEBUGS() << "LLViewerParcelMedia::play" << LL_ENDL; + + if (!parcel) return; + + if (!gSavedSettings.getBOOL("AudioStreamingMedia")) + return; + + std::string media_url = parcel->getMediaURL(); + std::string media_current_url = parcel->getMediaCurrentURL(); + std::string mime_type = parcel->getMediaType(); + LLUUID placeholder_texture_id = parcel->getMediaID(); + U8 media_auto_scale = parcel->getMediaAutoScale(); + U8 media_loop = parcel->getMediaLoop(); + S32 media_width = parcel->getMediaWidth(); + S32 media_height = parcel->getMediaHeight(); + + if(mMediaImpl) + { + // If the url and mime type are the same, call play again + if(mMediaImpl->getMediaURL() == media_url + && mMediaImpl->getMimeType() == mime_type + && mMediaImpl->getMediaTextureID() == placeholder_texture_id) + { + LL_DEBUGS("Media") << "playing with existing url " << media_url << LL_ENDL; + + mMediaImpl->play(); + } + // Else if the texture id's are the same, navigate and rediscover type + // MBW -- This causes other state from the previous parcel (texture size, autoscale, and looping) to get re-used incorrectly. + // It's also not really necessary -- just creating a new instance is fine. +// else if(mMediaImpl->getMediaTextureID() == placeholder_texture_id) +// { +// mMediaImpl->navigateTo(media_url, mime_type, true); +// } + else + { + // Since the texture id is different, we need to generate a new impl + + // Delete the old one first so they don't fight over the texture. + mMediaImpl = NULL; + + // A new impl will be created below. + } + } + + // Don't ever try to play if the media type is set to "none/none" + if(stricmp(mime_type.c_str(), LLMIMETypes::getDefaultMimeType().c_str()) != 0) + { + if(!mMediaImpl) + { + LL_DEBUGS("Media") << "new media impl with mime type " << mime_type << ", url " << media_url << LL_ENDL; + + // There is no media impl, make a new one + mMediaImpl = LLViewerMedia::getInstance()->newMediaImpl( + placeholder_texture_id, + media_width, + media_height, + media_auto_scale, + media_loop); + mMediaImpl->setIsParcelMedia(true); + mMediaImpl->navigateTo(media_url, mime_type, true); + } + + //LLFirstUse::useMedia(); + + LLViewerParcelMediaAutoPlay::playStarted(); + } +} + +// static +void LLViewerParcelMedia::stop() +{ + if(mMediaImpl.isNull()) + { + return; + } + + // We need to remove the media HUD if it is up. + LLViewerMediaFocus::getInstance()->clearFocus(); + + // This will unload & kill the media instance. + mMediaImpl = NULL; +} + +// static +void LLViewerParcelMedia::pause() +{ + if(mMediaImpl.isNull()) + { + return; + } + mMediaImpl->pause(); +} + +// static +void LLViewerParcelMedia::start() +{ + if(mMediaImpl.isNull()) + { + return; + } + mMediaImpl->start(); + + //LLFirstUse::useMedia(); + + LLViewerParcelMediaAutoPlay::playStarted(); +} + +// static +void LLViewerParcelMedia::seek(F32 time) +{ + if(mMediaImpl.isNull()) + { + return; + } + mMediaImpl->seek(time); +} + +// static +void LLViewerParcelMedia::focus(bool focus) +{ + mMediaImpl->focus(focus); +} + +// static +LLPluginClassMediaOwner::EMediaStatus LLViewerParcelMedia::getStatus() +{ + LLPluginClassMediaOwner::EMediaStatus result = LLPluginClassMediaOwner::MEDIA_NONE; + + if(mMediaImpl.notNull() && mMediaImpl->hasMedia()) + { + result = mMediaImpl->getMediaPlugin()->getStatus(); + } + + return result; +} + +// static +std::string LLViewerParcelMedia::getMimeType() +{ + return mMediaImpl.notNull() ? mMediaImpl->getMimeType() : LLMIMETypes::getDefaultMimeType(); +} + +//static +std::string LLViewerParcelMedia::getURL() +{ + std::string url; + if(mMediaImpl.notNull()) + url = mMediaImpl->getMediaURL(); + + if(stricmp(LLViewerParcelMgr::getInstance()->getAgentParcel()->getMediaType().c_str(), LLMIMETypes::getDefaultMimeType().c_str()) != 0) + { + if (url.empty()) + url = LLViewerParcelMgr::getInstance()->getAgentParcel()->getMediaCurrentURL(); + + if (url.empty()) + url = LLViewerParcelMgr::getInstance()->getAgentParcel()->getMediaURL(); + } + + return url; +} + +//static +std::string LLViewerParcelMedia::getName() +{ + if(mMediaImpl.notNull()) + return mMediaImpl->getName(); + return ""; +} + +viewer_media_t LLViewerParcelMedia::getParcelMedia() +{ + return mMediaImpl; +} + +////////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerParcelMedia::parcelMediaCommandMessageHandler(LLMessageSystem *msg, void **) +{ + getInstance()->processParcelMediaCommandMessage(msg); +} + +void LLViewerParcelMedia::processParcelMediaCommandMessage( LLMessageSystem *msg) +{ + // extract the agent id + // LLUUID agent_id; + // msg->getUUID( agent_id ); + + U32 flags; + U32 command; + F32 time; + msg->getU32( "CommandBlock", "Flags", flags ); + msg->getU32( "CommandBlock", "Command", command); + msg->getF32( "CommandBlock", "Time", time ); + + if (flags &( (1<getAgentParcel(); + play(parcel); + } + } + else + // unload + if( command == PARCEL_MEDIA_COMMAND_UNLOAD ) + { + stop(); + } + } + + if (flags & (1<getAgentParcel(); + play(parcel); + } + seek(time); + } +} + +////////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerParcelMedia::parcelMediaUpdateHandler(LLMessageSystem *msg, void **) +{ + getInstance()->processParcelMediaUpdate(msg); +} + +void LLViewerParcelMedia::processParcelMediaUpdate( LLMessageSystem *msg) +{ + LLUUID media_id; + std::string media_url; + std::string media_type; + S32 media_width = 0; + S32 media_height = 0; + U8 media_auto_scale = 0; + U8 media_loop = 0; + + msg->getUUID( "DataBlock", "MediaID", media_id ); + char media_url_buffer[257]; + msg->getString( "DataBlock", "MediaURL", 255, media_url_buffer ); + media_url = media_url_buffer; + msg->getU8("DataBlock", "MediaAutoScale", media_auto_scale); + + if (msg->has("DataBlockExtended")) // do we have the extended data? + { + char media_type_buffer[257]; + msg->getString("DataBlockExtended", "MediaType", 255, media_type_buffer); + media_type = media_type_buffer; + msg->getU8("DataBlockExtended", "MediaLoop", media_loop); + msg->getS32("DataBlockExtended", "MediaWidth", media_width); + msg->getS32("DataBlockExtended", "MediaHeight", media_height); + } + + LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if (parcel) + { + bool same = ((parcel->getMediaURL() == media_url) && + (parcel->getMediaType() == media_type) && + (parcel->getMediaID() == media_id) && + (parcel->getMediaWidth() == media_width) && + (parcel->getMediaHeight() == media_height) && + (parcel->getMediaAutoScale() == media_auto_scale) && + (parcel->getMediaLoop() == media_loop)); + + if (!same) + { + // temporarily store these new values in the parcel + parcel->setMediaURL(media_url); + parcel->setMediaType(media_type); + parcel->setMediaID(media_id); + parcel->setMediaWidth(media_width); + parcel->setMediaHeight(media_height); + parcel->setMediaAutoScale(media_auto_scale); + parcel->setMediaLoop(media_loop); + + play(parcel); + } + } +} +// Static +///////////////////////////////////////////////////////////////////////////////////////// +// *TODO: I can not find any active code where this method is called... +void LLViewerParcelMedia::sendMediaNavigateMessage(const std::string& url) +{ + std::string region_url = gAgent.getRegionCapability("ParcelNavigateMedia"); + if (!region_url.empty()) + { + // send navigate event to sim for link sharing + LLSD body; + body["agent-id"] = gAgent.getID(); + body["local-id"] = LLViewerParcelMgr::getInstance()->getAgentParcel()->getLocalID(); + body["url"] = url; + + LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(region_url, body, + "Media Navigation sent to sim.", "Media Navigation failed to send to sim."); + } + else + { + LL_WARNS() << "can't get ParcelNavigateMedia capability" << LL_ENDL; + } + +} + +///////////////////////////////////////////////////////////////////////////////////////// +// inherited from LLViewerMediaObserver +// virtual +void LLViewerParcelMedia::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) +{ + switch(event) + { + case MEDIA_EVENT_DEBUG_MESSAGE: + { + // LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_DEBUG_MESSAGE " << LL_ENDL; + }; + break; + + case MEDIA_EVENT_CONTENT_UPDATED: + { + // LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CONTENT_UPDATED " << LL_ENDL; + }; + break; + + case MEDIA_EVENT_TIME_DURATION_UPDATED: + { + // LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_TIME_DURATION_UPDATED, time is " << self->getCurrentTime() << " of " << self->getDuration() << LL_ENDL; + }; + break; + + case MEDIA_EVENT_SIZE_CHANGED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_SIZE_CHANGED " << LL_ENDL; + }; + break; + + case MEDIA_EVENT_CURSOR_CHANGED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CURSOR_CHANGED, new cursor is " << self->getCursorName() << LL_ENDL; + }; + break; + + case MEDIA_EVENT_NAVIGATE_BEGIN: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_NAVIGATE_BEGIN " << LL_ENDL; + }; + break; + + case MEDIA_EVENT_NAVIGATE_COMPLETE: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_NAVIGATE_COMPLETE, result string is: " << self->getNavigateResultString() << LL_ENDL; + }; + break; + + case MEDIA_EVENT_PROGRESS_UPDATED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PROGRESS_UPDATED, loading at " << self->getProgressPercent() << "%" << LL_ENDL; + }; + break; + + case MEDIA_EVENT_STATUS_TEXT_CHANGED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_STATUS_TEXT_CHANGED, new status text is: " << self->getStatusText() << LL_ENDL; + }; + break; + + case MEDIA_EVENT_LOCATION_CHANGED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_LOCATION_CHANGED, new uri is: " << self->getLocation() << LL_ENDL; + }; + break; + + case MEDIA_EVENT_NAVIGATE_ERROR_PAGE: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_NAVIGATE_ERROR_PAGE" << LL_ENDL; + }; + break; + + case MEDIA_EVENT_CLICK_LINK_HREF: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CLICK_LINK_HREF, target is \"" << self->getClickTarget() << "\", uri is " << self->getClickURL() << LL_ENDL; + }; + break; + + case MEDIA_EVENT_CLICK_LINK_NOFOLLOW: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CLICK_LINK_NOFOLLOW, uri is " << self->getClickURL() << LL_ENDL; + }; + break; + + case MEDIA_EVENT_PLUGIN_FAILED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PLUGIN_FAILED" << LL_ENDL; + }; + break; + + case MEDIA_EVENT_PLUGIN_FAILED_LAUNCH: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PLUGIN_FAILED_LAUNCH" << LL_ENDL; + }; + break; + + case MEDIA_EVENT_NAME_CHANGED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_NAME_CHANGED" << LL_ENDL; + }; + break; + + case MEDIA_EVENT_CLOSE_REQUEST: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CLOSE_REQUEST" << LL_ENDL; + } + break; + + case MEDIA_EVENT_PICK_FILE_REQUEST: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PICK_FILE_REQUEST" << LL_ENDL; + } + break; + + case MEDIA_EVENT_FILE_DOWNLOAD: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_FILE_DOWNLOAD" << LL_ENDL; + } + break; + + case MEDIA_EVENT_GEOMETRY_CHANGE: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_GEOMETRY_CHANGE, uuid is " << self->getClickUUID() << LL_ENDL; + } + break; + + case MEDIA_EVENT_AUTH_REQUEST: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_AUTH_REQUEST, url " << self->getAuthURL() << ", realm " << self->getAuthRealm() << LL_ENDL; + } + break; + + case MEDIA_EVENT_LINK_HOVERED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_LINK_HOVERED, hover text is: " << self->getHoverText() << LL_ENDL; + }; + break; + }; +} + +// TODO: observer +/* +void LLViewerParcelMediaNavigationObserver::onNavigateComplete( const EventType& event_in ) +{ + std::string url = event_in.getStringValue(); + + if (mCurrentURL != url && ! mFromMessage) + { + LLViewerParcelMedia::sendMediaNavigateMessage(url); + } + + mCurrentURL = url; + mFromMessage = false; + +} +*/ diff --git a/indra/newview/llviewerparcelmediaautoplay.cpp b/indra/newview/llviewerparcelmediaautoplay.cpp index ef22d1ff32..41bc5c8cfa 100644 --- a/indra/newview/llviewerparcelmediaautoplay.cpp +++ b/indra/newview/llviewerparcelmediaautoplay.cpp @@ -1,176 +1,176 @@ -/** - * @file llviewerparcelmediaautoplay.cpp - * @brief timer to automatically play media in a parcel - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#include "llviewerprecompiledheaders.h" - -#include "llviewerparcelmediaautoplay.h" - -#include "llparcel.h" -#include "llviewercontrol.h" -#include "llviewermedia.h" -#include "llviewerparcelaskplay.h" -#include "llviewerparcelmedia.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" -#include "llviewertexturelist.h" // for texture stats -#include "message.h" -#include "llagent.h" -#include "llmimetypes.h" - -const F32 AUTOPLAY_TIME = 5; // how many seconds before we autoplay -const F32 AUTOPLAY_SIZE = 24*24; // how big the texture must be (pixel area) before we autoplay -const F32 AUTOPLAY_SPEED = 0.1f; // how slow should the agent be moving to autoplay - -LLViewerParcelMediaAutoPlay::LLViewerParcelMediaAutoPlay() : - LLEventTimer(1), - - mLastParcelID(-1), - mPlayed(false), - mTimeInParcel(0) -{ -} - -// static -void LLViewerParcelMediaAutoPlay::playStarted() -{ - LLSingleton::getInstance()->mPlayed = true; -} - -bool LLViewerParcelMediaAutoPlay::tick() -{ - LLParcel *this_parcel = NULL; - LLViewerRegion *this_region = NULL; - std::string this_media_url; - std::string this_media_type; - LLUUID this_media_texture_id; - S32 this_parcel_id = 0; - LLUUID this_region_id; - - this_region = gAgent.getRegion(); - - if (this_region) - { - this_region_id = this_region->getRegionID(); - } - - this_parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - - if (this_parcel) - { - this_media_url = this_parcel->getMediaURL(); - - this_media_type = this_parcel->getMediaType(); - - this_media_texture_id = this_parcel->getMediaID(); - - this_parcel_id = this_parcel->getLocalID(); - } - - if (this_parcel_id != mLastParcelID || - this_region_id != mLastRegionID) - { - // we've entered a new parcel - mPlayed = false; // we haven't autoplayed yet - mTimeInParcel = 0; // reset our timer - mLastParcelID = this_parcel_id; - mLastRegionID = this_region_id; - } - - mTimeInParcel += mPeriod; // increase mTimeInParcel by the amount of time between ticks - - if ((!mPlayed) && // if we've never played - (mTimeInParcel > AUTOPLAY_TIME) && // and if we've been here for so many seconds - (!this_media_url.empty()) && // and if the parcel has media - (stricmp(this_media_type.c_str(), LLMIMETypes::getDefaultMimeType().c_str()) != 0) && - (!LLViewerParcelMedia::getInstance()->hasParcelMedia())) // and if the media is not already playing - { - if (this_media_texture_id.notNull()) // and if the media texture is good - { - LLViewerMediaTexture *image = LLViewerTextureManager::getMediaTexture(this_media_texture_id, false) ; - - F32 image_size = 0; - - if (image) - { - image_size = image->getMaxVirtualSize() ; - } - - if (gAgent.getVelocity().magVec() < AUTOPLAY_SPEED) // and if the agent is stopped (slow enough) - { - if (image_size > AUTOPLAY_SIZE) // and if the target texture is big enough on screen - { - if (this_parcel) - { - static LLCachedControl autoplay_mode(gSavedSettings, "ParcelMediaAutoPlayEnable"); - - switch (autoplay_mode()) - { - case 0: - // Disabled - break; - case 1: - // Play, default value for ParcelMediaAutoPlayEnable - LLViewerParcelMedia::getInstance()->play(this_parcel); - break; - case 2: - default: - { - // Ask - LLViewerParcelAskPlay::getInstance()->askToPlay(this_region->getRegionID(), - this_parcel->getLocalID(), - this_parcel->getMediaURL(), - onStartMediaResponse); - break; - } - } - } - - mPlayed = true; - } - } - } - } - - - return false; // continue ticking forever please. -} - -//static -void LLViewerParcelMediaAutoPlay::onStartMediaResponse(const LLUUID ®ion_id, const S32 &parcel_id, const std::string &url, const bool &play) -{ - if (play) - { - LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - - // make sure we are still there - if (parcel->getLocalID() == parcel_id && gAgent.getRegion()->getRegionID() == region_id) - { - LLViewerParcelMedia::getInstance()->play(parcel); - } - } -} - +/** + * @file llviewerparcelmediaautoplay.cpp + * @brief timer to automatically play media in a parcel + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#include "llviewerprecompiledheaders.h" + +#include "llviewerparcelmediaautoplay.h" + +#include "llparcel.h" +#include "llviewercontrol.h" +#include "llviewermedia.h" +#include "llviewerparcelaskplay.h" +#include "llviewerparcelmedia.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "llviewertexturelist.h" // for texture stats +#include "message.h" +#include "llagent.h" +#include "llmimetypes.h" + +const F32 AUTOPLAY_TIME = 5; // how many seconds before we autoplay +const F32 AUTOPLAY_SIZE = 24*24; // how big the texture must be (pixel area) before we autoplay +const F32 AUTOPLAY_SPEED = 0.1f; // how slow should the agent be moving to autoplay + +LLViewerParcelMediaAutoPlay::LLViewerParcelMediaAutoPlay() : + LLEventTimer(1), + + mLastParcelID(-1), + mPlayed(false), + mTimeInParcel(0) +{ +} + +// static +void LLViewerParcelMediaAutoPlay::playStarted() +{ + LLSingleton::getInstance()->mPlayed = true; +} + +bool LLViewerParcelMediaAutoPlay::tick() +{ + LLParcel *this_parcel = NULL; + LLViewerRegion *this_region = NULL; + std::string this_media_url; + std::string this_media_type; + LLUUID this_media_texture_id; + S32 this_parcel_id = 0; + LLUUID this_region_id; + + this_region = gAgent.getRegion(); + + if (this_region) + { + this_region_id = this_region->getRegionID(); + } + + this_parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + + if (this_parcel) + { + this_media_url = this_parcel->getMediaURL(); + + this_media_type = this_parcel->getMediaType(); + + this_media_texture_id = this_parcel->getMediaID(); + + this_parcel_id = this_parcel->getLocalID(); + } + + if (this_parcel_id != mLastParcelID || + this_region_id != mLastRegionID) + { + // we've entered a new parcel + mPlayed = false; // we haven't autoplayed yet + mTimeInParcel = 0; // reset our timer + mLastParcelID = this_parcel_id; + mLastRegionID = this_region_id; + } + + mTimeInParcel += mPeriod; // increase mTimeInParcel by the amount of time between ticks + + if ((!mPlayed) && // if we've never played + (mTimeInParcel > AUTOPLAY_TIME) && // and if we've been here for so many seconds + (!this_media_url.empty()) && // and if the parcel has media + (stricmp(this_media_type.c_str(), LLMIMETypes::getDefaultMimeType().c_str()) != 0) && + (!LLViewerParcelMedia::getInstance()->hasParcelMedia())) // and if the media is not already playing + { + if (this_media_texture_id.notNull()) // and if the media texture is good + { + LLViewerMediaTexture *image = LLViewerTextureManager::getMediaTexture(this_media_texture_id, false) ; + + F32 image_size = 0; + + if (image) + { + image_size = image->getMaxVirtualSize() ; + } + + if (gAgent.getVelocity().magVec() < AUTOPLAY_SPEED) // and if the agent is stopped (slow enough) + { + if (image_size > AUTOPLAY_SIZE) // and if the target texture is big enough on screen + { + if (this_parcel) + { + static LLCachedControl autoplay_mode(gSavedSettings, "ParcelMediaAutoPlayEnable"); + + switch (autoplay_mode()) + { + case 0: + // Disabled + break; + case 1: + // Play, default value for ParcelMediaAutoPlayEnable + LLViewerParcelMedia::getInstance()->play(this_parcel); + break; + case 2: + default: + { + // Ask + LLViewerParcelAskPlay::getInstance()->askToPlay(this_region->getRegionID(), + this_parcel->getLocalID(), + this_parcel->getMediaURL(), + onStartMediaResponse); + break; + } + } + } + + mPlayed = true; + } + } + } + } + + + return false; // continue ticking forever please. +} + +//static +void LLViewerParcelMediaAutoPlay::onStartMediaResponse(const LLUUID ®ion_id, const S32 &parcel_id, const std::string &url, const bool &play) +{ + if (play) + { + LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + + // make sure we are still there + if (parcel->getLocalID() == parcel_id && gAgent.getRegion()->getRegionID() == region_id) + { + LLViewerParcelMedia::getInstance()->play(parcel); + } + } +} + diff --git a/indra/newview/llviewerparcelmediaautoplay.h b/indra/newview/llviewerparcelmediaautoplay.h index e4663c5825..96b569f126 100644 --- a/indra/newview/llviewerparcelmediaautoplay.h +++ b/indra/newview/llviewerparcelmediaautoplay.h @@ -1,53 +1,53 @@ -/** - * @file llviewerparcelmediaautoplay.h - * @brief timer to automatically play media in a parcel - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LLVIEWERPARCELMEDIAAUTOPLAY_H -#define LLVIEWERPARCELMEDIAAUTOPLAY_H - -#include "lleventtimer.h" -#include "lluuid.h" - -// timer to automatically play media -class LLViewerParcelMediaAutoPlay : LLEventTimer, public LLSingleton -{ - LLSINGLETON(LLViewerParcelMediaAutoPlay); -public: - virtual bool tick() override; - static void playStarted(); - - private: - // for askToPlay - static void onStartMediaResponse(const LLUUID ®ion_id, const S32 &parcel_id, const std::string &url, const bool &play); - - private: - S32 mLastParcelID; - LLUUID mLastRegionID; - bool mPlayed; - F32 mTimeInParcel; -}; - - -#endif // LLVIEWERPARCELMEDIAAUTOPLAY_H +/** + * @file llviewerparcelmediaautoplay.h + * @brief timer to automatically play media in a parcel + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LLVIEWERPARCELMEDIAAUTOPLAY_H +#define LLVIEWERPARCELMEDIAAUTOPLAY_H + +#include "lleventtimer.h" +#include "lluuid.h" + +// timer to automatically play media +class LLViewerParcelMediaAutoPlay : LLEventTimer, public LLSingleton +{ + LLSINGLETON(LLViewerParcelMediaAutoPlay); +public: + virtual bool tick() override; + static void playStarted(); + + private: + // for askToPlay + static void onStartMediaResponse(const LLUUID ®ion_id, const S32 &parcel_id, const std::string &url, const bool &play); + + private: + S32 mLastParcelID; + LLUUID mLastRegionID; + bool mPlayed; + F32 mTimeInParcel; +}; + + +#endif // LLVIEWERPARCELMEDIAAUTOPLAY_H diff --git a/indra/newview/llviewerparcelmgr.cpp b/indra/newview/llviewerparcelmgr.cpp index 74b43976b8..d67e617a35 100644 --- a/indra/newview/llviewerparcelmgr.cpp +++ b/indra/newview/llviewerparcelmgr.cpp @@ -1,2734 +1,2734 @@ -/** - * @file llviewerparcelmgr.cpp - * @brief Viewer-side representation of owned land - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llviewerparcelmgr.h" - -// Library includes -#include "llaudioengine.h" -#include "indra_constants.h" -#include "llcachename.h" -#include "llgl.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "llparcel.h" -#include "message.h" -#include "llfloaterreg.h" - -// Viewer includes -#include "llagent.h" -#include "llagentaccess.h" -#include "llviewerparcelaskplay.h" -#include "llviewerwindow.h" -#include "llviewercontrol.h" -//#include "llfirstuse.h" -#include "llfloaterbuyland.h" -#include "llfloatergroups.h" -#include "llpanelnearbymedia.h" -#include "llfloatersellland.h" -#include "llfloatertools.h" -#include "llparcelselection.h" -#include "llresmgr.h" -#include "llsdutil.h" -#include "llsdutil_math.h" -#include "llslurl.h" -#include "llstatusbar.h" -#include "llui.h" -#include "llviewertexture.h" -#include "llviewertexturelist.h" -#include "llviewermenu.h" -#include "llviewerparcelmedia.h" -#include "llviewerparceloverlay.h" -#include "llviewerregion.h" -#include "llworld.h" -#include "roles_constants.h" -#include "llweb.h" -#include "llvieweraudio.h" -#include "llcorehttputil.h" - -#include "llenvironment.h" - -const F32 PARCEL_BAN_LINES_DRAW_SECS_ON_COLLISION = 10.f; -const F32 PARCEL_COLLISION_DRAW_SECS_ON_PROXIMITY = 1.f; - - -// Globals - -U8* LLViewerParcelMgr::sPackedOverlay = NULL; -S32 LLViewerParcelMgr::PARCEL_BAN_LINES_HIDE = 0; -S32 LLViewerParcelMgr::PARCEL_BAN_LINES_ON_COLLISION = 1; -S32 LLViewerParcelMgr::PARCEL_BAN_LINES_ON_PROXIMITY = 2; - -LLUUID gCurrentMovieID = LLUUID::null; - -LLPointer sBlockedImage; -LLPointer sPassImage; - -// Local functions -void callback_start_music(S32 option, void* data); -void optionally_prepare_video(const LLParcel *parcelp); -void callback_prepare_video(S32 option, void* data); -void prepare_video(const LLParcel *parcelp); -void start_video(const LLParcel *parcelp); -void stop_video(); -bool callback_god_force_owner(const LLSD&, const LLSD&); - -struct LLGodForceOwnerData -{ - LLUUID mOwnerID; - S32 mLocalID; - LLHost mHost; - - LLGodForceOwnerData( - const LLUUID& owner_id, - S32 local_parcel_id, - const LLHost& host) : - mOwnerID(owner_id), - mLocalID(local_parcel_id), - mHost(host) {} -}; - -// -// Methods -// -LLViewerParcelMgr::LLViewerParcelMgr() -: mSelected(false), - mRequestResult(0), - mWestSouth(), - mEastNorth(), - mSelectedDwell(DWELL_NAN), - mAgentParcelSequenceID(-1), - mHoverRequestResult(0), - mHoverWestSouth(), - mHoverEastNorth(), - mTeleportInProgressPosition(), - mRenderCollision(false), - mRenderSelection(true), - mCollisionBanned(0), - mCollisionTimer(), - mMediaParcelId(0), - mMediaRegionId(0) -{ - mCurrentParcel = new LLParcel(); - mCurrentParcelSelection = new LLParcelSelection(mCurrentParcel); - mFloatingParcelSelection = new LLParcelSelection(mCurrentParcel); - - mAgentParcel = new LLParcel(); - mHoverParcel = new LLParcel(); - mCollisionParcel = new LLParcel(); - - mParcelsPerEdge = S32( REGION_WIDTH_METERS / PARCEL_GRID_STEP_METERS ); - mHighlightSegments = new U8[(mParcelsPerEdge+1)*(mParcelsPerEdge+1)]; - resetSegments(mHighlightSegments); - - mCollisionSegments = new U8[(mParcelsPerEdge+1)*(mParcelsPerEdge+1)]; - resetSegments(mCollisionSegments); - - // JC: Resolved a merge conflict here, eliminated - // mBlockedImage->setAddressMode(LLTexUnit::TAM_WRAP); - // because it is done in llviewertexturelist.cpp - mBlockedImage = LLViewerTextureManager::getFetchedTextureFromFile("world/NoEntryLines.png", FTT_LOCAL_FILE, true, LLGLTexture::BOOST_UI); - mPassImage = LLViewerTextureManager::getFetchedTextureFromFile("world/NoEntryPassLines.png", FTT_LOCAL_FILE, true, LLGLTexture::BOOST_UI); - - S32 overlay_size = mParcelsPerEdge * mParcelsPerEdge / PARCEL_OVERLAY_CHUNKS; - sPackedOverlay = new U8[overlay_size]; - - mAgentParcelOverlay = new U8[mParcelsPerEdge * mParcelsPerEdge]; - S32 i; - for (i = 0; i < mParcelsPerEdge * mParcelsPerEdge; i++) - { - mAgentParcelOverlay[i] = 0; - } - - mTeleportInProgress = true; // the initial parcel update is treated like teleport -} - - -LLViewerParcelMgr::~LLViewerParcelMgr() -{ - mCurrentParcelSelection->setParcel(NULL); - mCurrentParcelSelection = NULL; - - mFloatingParcelSelection->setParcel(NULL); - mFloatingParcelSelection = NULL; - - delete mCurrentParcel; - mCurrentParcel = NULL; - - delete mAgentParcel; - mAgentParcel = NULL; - - delete mCollisionParcel; - mCollisionParcel = NULL; - - delete mHoverParcel; - mHoverParcel = NULL; - - delete[] mHighlightSegments; - mHighlightSegments = NULL; - - delete[] mCollisionSegments; - mCollisionSegments = NULL; - - delete[] sPackedOverlay; - sPackedOverlay = NULL; - - delete[] mAgentParcelOverlay; - mAgentParcelOverlay = NULL; - - sBlockedImage = NULL; - sPassImage = NULL; -} - -void LLViewerParcelMgr::dump() -{ - LL_INFOS() << "Parcel Manager Dump" << LL_ENDL; - LL_INFOS() << "mSelected " << S32(mSelected) << LL_ENDL; - LL_INFOS() << "Selected parcel: " << LL_ENDL; - LL_INFOS() << mWestSouth << " to " << mEastNorth << LL_ENDL; - mCurrentParcel->dump(); - LL_INFOS() << "banning " << mCurrentParcel->mBanList.size() << LL_ENDL; - - LLAccessEntry::map::const_iterator cit = mCurrentParcel->mBanList.begin(); - LLAccessEntry::map::const_iterator end = mCurrentParcel->mBanList.end(); - for ( ; cit != end; ++cit) - { - LL_INFOS() << "ban id " << (*cit).first << LL_ENDL; - } - LL_INFOS() << "Hover parcel:" << LL_ENDL; - mHoverParcel->dump(); - LL_INFOS() << "Agent parcel:" << LL_ENDL; - mAgentParcel->dump(); -} - - -LLViewerRegion* LLViewerParcelMgr::getSelectionRegion() -{ - return LLWorld::getInstance()->getRegionFromPosGlobal( mWestSouth ); -} - - -void LLViewerParcelMgr::getDisplayInfo(S32* area_out, S32* claim_out, - S32* rent_out, - bool* for_sale_out, - F32* dwell_out) -{ - S32 area = 0; - S32 price = 0; - S32 rent = 0; - bool for_sale = false; - F32 dwell = DWELL_NAN; - - if (mSelected) - { - if (mCurrentParcelSelection->mSelectedMultipleOwners) - { - area = mCurrentParcelSelection->getClaimableArea(); - } - else - { - area = getSelectedArea(); - } - - if (mCurrentParcel->getForSale()) - { - price = mCurrentParcel->getSalePrice(); - for_sale = true; - } - else - { - price = area * mCurrentParcel->getClaimPricePerMeter(); - for_sale = false; - } - - rent = mCurrentParcel->getTotalRent(); - - dwell = mSelectedDwell; - } - - *area_out = area; - *claim_out = price; - *rent_out = rent; - *for_sale_out = for_sale; - *dwell_out = dwell; -} - -S32 LLViewerParcelMgr::getSelectedArea() const -{ - S32 rv = 0; - if(mSelected && mCurrentParcel && mCurrentParcelSelection->mWholeParcelSelected) - { - rv = mCurrentParcel->getArea(); - } - else if(mSelected) - { - F64 width = mEastNorth.mdV[VX] - mWestSouth.mdV[VX]; - F64 height = mEastNorth.mdV[VY] - mWestSouth.mdV[VY]; - F32 area = (F32)(width * height); - rv = ll_round(area); - } - return rv; -} - -void LLViewerParcelMgr::resetSegments(U8* segments) -{ - S32 i; - S32 count = (mParcelsPerEdge+1)*(mParcelsPerEdge+1); - for (i = 0; i < count; i++) - { - segments[i] = 0x0; - } -} - - -void LLViewerParcelMgr::writeHighlightSegments(F32 west, F32 south, F32 east, - F32 north) -{ - S32 x, y; - S32 min_x = ll_round( west / PARCEL_GRID_STEP_METERS ); - S32 max_x = ll_round( east / PARCEL_GRID_STEP_METERS ); - S32 min_y = ll_round( south / PARCEL_GRID_STEP_METERS ); - S32 max_y = ll_round( north / PARCEL_GRID_STEP_METERS ); - - const S32 STRIDE = mParcelsPerEdge+1; - - // south edge - y = min_y; - for (x = min_x; x < max_x; x++) - { - // exclusive OR means that writing to this segment twice - // will turn it off - mHighlightSegments[x + y*STRIDE] ^= SOUTH_MASK; - } - - // west edge - x = min_x; - for (y = min_y; y < max_y; y++) - { - mHighlightSegments[x + y*STRIDE] ^= WEST_MASK; - } - - // north edge - draw the south border on the y+1'th cell, - // which given C-style arrays, is item foo[max_y] - y = max_y; - for (x = min_x; x < max_x; x++) - { - mHighlightSegments[x + y*STRIDE] ^= SOUTH_MASK; - } - - // east edge - draw west border on x+1'th cell - x = max_x; - for (y = min_y; y < max_y; y++) - { - mHighlightSegments[x + y*STRIDE] ^= WEST_MASK; - } -} - - -void LLViewerParcelMgr::writeSegmentsFromBitmap(U8* bitmap, U8* segments) -{ - S32 x; - S32 y; - const S32 IN_STRIDE = mParcelsPerEdge; - const S32 OUT_STRIDE = mParcelsPerEdge+1; - - for (y = 0; y < IN_STRIDE; y++) - { - x = 0; - while( x < IN_STRIDE ) - { - U8 byte = bitmap[ (x + y*IN_STRIDE) / 8 ]; - - S32 bit; - for (bit = 0; bit < 8; bit++) - { - if (byte & (1 << bit) ) - { - S32 out = x+y*OUT_STRIDE; - - // This and one above it - segments[out] ^= SOUTH_MASK; - segments[out+OUT_STRIDE] ^= SOUTH_MASK; - - // This and one to the right - segments[out] ^= WEST_MASK; - segments[out+1] ^= WEST_MASK; - } - x++; - } - } - } -} - - -void LLViewerParcelMgr::writeAgentParcelFromBitmap(U8* bitmap) -{ - S32 x; - S32 y; - const S32 IN_STRIDE = mParcelsPerEdge; - - for (y = 0; y < IN_STRIDE; y++) - { - x = 0; - while( x < IN_STRIDE ) - { - U8 byte = bitmap[ (x + y*IN_STRIDE) / 8 ]; - - S32 bit; - for (bit = 0; bit < 8; bit++) - { - if (byte & (1 << bit) ) - { - mAgentParcelOverlay[x+y*IN_STRIDE] = 1; - } - else - { - mAgentParcelOverlay[x+y*IN_STRIDE] = 0; - } - x++; - } - } - } -} - - -// Given a point, find the PARCEL_GRID_STEP x PARCEL_GRID_STEP block -// containing it and select that. -LLParcelSelectionHandle LLViewerParcelMgr::selectParcelAt(const LLVector3d& pos_global) -{ - LLVector3d southwest = pos_global; - LLVector3d northeast = pos_global; - - southwest -= LLVector3d( PARCEL_GRID_STEP_METERS/2, PARCEL_GRID_STEP_METERS/2, 0 ); - southwest.mdV[VX] = ll_round( southwest.mdV[VX], (F64)PARCEL_GRID_STEP_METERS ); - southwest.mdV[VY] = ll_round( southwest.mdV[VY], (F64)PARCEL_GRID_STEP_METERS ); - - northeast += LLVector3d( PARCEL_GRID_STEP_METERS/2, PARCEL_GRID_STEP_METERS/2, 0 ); - northeast.mdV[VX] = ll_round( northeast.mdV[VX], (F64)PARCEL_GRID_STEP_METERS ); - northeast.mdV[VY] = ll_round( northeast.mdV[VY], (F64)PARCEL_GRID_STEP_METERS ); - - // Snap to parcel - return selectLand( southwest, northeast, true ); -} - - -// Tries to select the parcel inside the rectangle -LLParcelSelectionHandle LLViewerParcelMgr::selectParcelInRectangle() -{ - return selectLand(mWestSouth, mEastNorth, true); -} - - -void LLViewerParcelMgr::selectCollisionParcel() -{ - // BUG: Claim to be in the agent's region - mWestSouth = gAgent.getRegion()->getOriginGlobal(); - mEastNorth = mWestSouth; - mEastNorth += LLVector3d(PARCEL_GRID_STEP_METERS, PARCEL_GRID_STEP_METERS, 0.0); - - // BUG: must be in the sim you are in - LLMessageSystem *msg = gMessageSystem; - msg->newMessageFast(_PREHASH_ParcelPropertiesRequestByID); - msg->nextBlockFast(_PREHASH_AgentID); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); - msg->nextBlockFast(_PREHASH_ParcelData); - msg->addS32Fast(_PREHASH_SequenceID, SELECTED_PARCEL_SEQ_ID ); - msg->addS32Fast(_PREHASH_LocalID, mCollisionParcel->getLocalID() ); - gAgent.sendReliableMessage(); - - mRequestResult = PARCEL_RESULT_NO_DATA; - - // Hack: Copy some data over temporarily - mCurrentParcel->setName( mCollisionParcel->getName() ); - mCurrentParcel->setDesc( mCollisionParcel->getDesc() ); - mCurrentParcel->setPassPrice(mCollisionParcel->getPassPrice()); - mCurrentParcel->setPassHours(mCollisionParcel->getPassHours()); - - // clear the list of segments to prevent flashing - resetSegments(mHighlightSegments); - - mFloatingParcelSelection->setParcel(mCurrentParcel); - mCurrentParcelSelection->setParcel(NULL); - mCurrentParcelSelection = new LLParcelSelection(mCurrentParcel); - - mSelected = true; - mCurrentParcelSelection->mWholeParcelSelected = true; - notifyObservers(); - return; -} - - -// snap_selection = auto-select the hit parcel, if there is exactly one -LLParcelSelectionHandle LLViewerParcelMgr::selectLand(const LLVector3d &corner1, const LLVector3d &corner2, - bool snap_selection) -{ - sanitize_corners( corner1, corner2, mWestSouth, mEastNorth ); - - // ...x isn't more than one meter away - F32 delta_x = getSelectionWidth(); - if (delta_x * delta_x <= 1.f * 1.f) - { - mSelected = false; - notifyObservers(); - return NULL; - } - - // ...y isn't more than one meter away - F32 delta_y = getSelectionHeight(); - if (delta_y * delta_y <= 1.f * 1.f) - { - mSelected = false; - notifyObservers(); - return NULL; - } - - // Can't select across region boundary - // We need to pull in the upper right corner by a little bit to allow - // selection up to the x = 256 or y = 256 edge. - LLVector3d east_north_region_check( mEastNorth ); - east_north_region_check.mdV[VX] -= 0.5; - east_north_region_check.mdV[VY] -= 0.5; - - LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosGlobal(mWestSouth); - LLViewerRegion *region_other = LLWorld::getInstance()->getRegionFromPosGlobal( east_north_region_check ); - - if(!region) - { - // just in case they somehow selected no land. - mSelected = false; - return NULL; - } - - if (region != region_other) - { - LLNotificationsUtil::add("CantSelectLandFromMultipleRegions"); - mSelected = false; - notifyObservers(); - return NULL; - } - - // Build region global copies of corners - LLVector3 wsb_region = region->getPosRegionFromGlobal( mWestSouth ); - LLVector3 ent_region = region->getPosRegionFromGlobal( mEastNorth ); - - // Send request message - LLMessageSystem *msg = gMessageSystem; - msg->newMessageFast(_PREHASH_ParcelPropertiesRequest); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); - msg->nextBlockFast(_PREHASH_ParcelData); - msg->addS32Fast(_PREHASH_SequenceID, SELECTED_PARCEL_SEQ_ID ); - msg->addF32Fast(_PREHASH_West, wsb_region.mV[VX] ); - msg->addF32Fast(_PREHASH_South, wsb_region.mV[VY] ); - msg->addF32Fast(_PREHASH_East, ent_region.mV[VX] ); - msg->addF32Fast(_PREHASH_North, ent_region.mV[VY] ); - msg->addBOOL("SnapSelection", snap_selection); - msg->sendReliable( region->getHost() ); - - mRequestResult = PARCEL_RESULT_NO_DATA; - - mFloatingParcelSelection->setParcel(mCurrentParcel); - mCurrentParcelSelection->setParcel(NULL); - mCurrentParcelSelection = new LLParcelSelection(mCurrentParcel); - - mSelected = true; - mCurrentParcelSelection->mWholeParcelSelected = snap_selection; - notifyObservers(); - return mCurrentParcelSelection; -} - -void LLViewerParcelMgr::deselectUnused() -{ - // no more outstanding references to this selection, other than our own - if (mCurrentParcelSelection->getNumRefs() == 1 && mFloatingParcelSelection->getNumRefs() == 1) - { - deselectLand(); - } -} - -void LLViewerParcelMgr::deselectLand() -{ - if (mSelected) - { - mSelected = false; - - // Invalidate the selected parcel - mCurrentParcel->setLocalID(-1); - mCurrentParcel->mAccessList.clear(); - mCurrentParcel->mBanList.clear(); - //mCurrentParcel->mRenterList.reset(); - - mSelectedDwell = DWELL_NAN; - - // invalidate parcel selection so that existing users of this selection can clean up - mCurrentParcelSelection->setParcel(NULL); - mFloatingParcelSelection->setParcel(NULL); - // create new parcel selection - mCurrentParcelSelection = new LLParcelSelection(mCurrentParcel); - - notifyObservers(); // Notify observers *after* changing the parcel selection - } -} - - -void LLViewerParcelMgr::addObserver(LLParcelObserver* observer) -{ - mObservers.push_back(observer); -} - - -void LLViewerParcelMgr::removeObserver(LLParcelObserver* observer) -{ - vector_replace_with_last(mObservers, observer); -} - - -// Call this method when it's time to update everyone on a new state. -// Copy the list because an observer could respond by removing itself -// from the list. -void LLViewerParcelMgr::notifyObservers() -{ - std::vector observers; - S32 count = mObservers.size(); - S32 i; - for(i = 0; i < count; ++i) - { - observers.push_back(mObservers.at(i)); - } - for(i = 0; i < count; ++i) - { - observers.at(i)->changed(); - } -} - - -// -// ACCESSORS -// -bool LLViewerParcelMgr::selectionEmpty() const -{ - return !mSelected; -} - - -LLParcelSelectionHandle LLViewerParcelMgr::getParcelSelection() const -{ - return mCurrentParcelSelection; -} - -LLParcelSelectionHandle LLViewerParcelMgr::getFloatingParcelSelection() const -{ - return mFloatingParcelSelection; -} - -LLParcel *LLViewerParcelMgr::getAgentParcel() const -{ - return mAgentParcel; -} - - -LLParcel * LLViewerParcelMgr::getAgentOrSelectedParcel() const -{ - LLParcel *parcel(nullptr); - - LLParcelSelectionHandle sel_handle(getFloatingParcelSelection()); - if (sel_handle) - { - LLParcelSelection *selection(sel_handle.get()); - if (selection) - { - parcel = selection->getParcel(); - if (parcel && (parcel->getLocalID() == INVALID_PARCEL_ID)) - { - parcel = NULL; - } - } - } - - if (!parcel) - parcel = LLViewerParcelMgr::instance().getAgentParcel(); - - return parcel; -} - -// Return whether the agent can build on the land they are on -bool LLViewerParcelMgr::allowAgentBuild() const -{ - if (mAgentParcel) - { - return (gAgent.isGodlike() || - (mAgentParcel->allowModifyBy(gAgent.getID(), gAgent.getGroupID())) || - (isParcelOwnedByAgent(mAgentParcel, GP_LAND_ALLOW_CREATE))); - } - else - { - return gAgent.isGodlike(); - } -} - -// Return whether anyone can build on the given parcel -bool LLViewerParcelMgr::allowAgentBuild(const LLParcel* parcel) const -{ - return parcel->getAllowModify(); -} - -bool LLViewerParcelMgr::allowAgentVoice() const -{ - return allowAgentVoice(gAgent.getRegion(), mAgentParcel); -} - -bool LLViewerParcelMgr::allowAgentVoice(const LLViewerRegion* region, const LLParcel* parcel) const -{ - return region && region->isVoiceEnabled() - && parcel && parcel->getParcelFlagAllowVoice(); -} - -bool LLViewerParcelMgr::allowAgentFly(const LLViewerRegion* region, const LLParcel* parcel) const -{ - return region && !region->getBlockFly() - && parcel && parcel->getAllowFly(); -} - -// Can the agent be pushed around by LLPushObject? -bool LLViewerParcelMgr::allowAgentPush(const LLViewerRegion* region, const LLParcel* parcel) const -{ - return region && !region->getRestrictPushObject() - && parcel && !parcel->getRestrictPushObject(); -} - -bool LLViewerParcelMgr::allowAgentScripts(const LLViewerRegion* region, const LLParcel* parcel) const -{ - // *NOTE: This code does not take into account group-owned parcels - // and the flag to allow group-owned scripted objects to run. - // This mirrors the traditional menu bar parcel icon code, but is not - // technically correct. - return region - && !region->getRegionFlag(REGION_FLAGS_SKIP_SCRIPTS) - && !region->getRegionFlag(REGION_FLAGS_ESTATE_SKIP_SCRIPTS) - && parcel - && parcel->getAllowOtherScripts(); -} - -bool LLViewerParcelMgr::allowAgentDamage(const LLViewerRegion* region, const LLParcel* parcel) const -{ - return (region && region->getAllowDamage()) - || (parcel && parcel->getAllowDamage()); -} - -bool LLViewerParcelMgr::isOwnedAt(const LLVector3d& pos_global) const -{ - LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal( pos_global ); - if (!region) return false; - - LLViewerParcelOverlay* overlay = region->getParcelOverlay(); - if (!overlay) return false; - - LLVector3 pos_region = region->getPosRegionFromGlobal( pos_global ); - - return overlay->isOwned( pos_region ); -} - -bool LLViewerParcelMgr::isOwnedSelfAt(const LLVector3d& pos_global) const -{ - LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal( pos_global ); - if (!region) return false; - - LLViewerParcelOverlay* overlay = region->getParcelOverlay(); - if (!overlay) return false; - - LLVector3 pos_region = region->getPosRegionFromGlobal( pos_global ); - - return overlay->isOwnedSelf( pos_region ); -} - -bool LLViewerParcelMgr::isOwnedOtherAt(const LLVector3d& pos_global) const -{ - LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal( pos_global ); - if (!region) return false; - - LLViewerParcelOverlay* overlay = region->getParcelOverlay(); - if (!overlay) return false; - - LLVector3 pos_region = region->getPosRegionFromGlobal( pos_global ); - - return overlay->isOwnedOther( pos_region ); -} - -bool LLViewerParcelMgr::isSoundLocal(const LLVector3d& pos_global) const -{ - LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal( pos_global ); - if (!region) return false; - - LLViewerParcelOverlay* overlay = region->getParcelOverlay(); - if (!overlay) return false; - - LLVector3 pos_region = region->getPosRegionFromGlobal( pos_global ); - - return overlay->isSoundLocal( pos_region ); -} - -bool LLViewerParcelMgr::canHearSound(const LLVector3d &pos_global) const -{ - bool in_agent_parcel = inAgentParcel(pos_global); - - if (in_agent_parcel) - { - // In same parcel as the agent - return true; - } - else - { - if (LLViewerParcelMgr::getInstance()->getAgentParcel()->getSoundLocal()) - { - // Not in same parcel, and agent parcel only has local sound - return false; - } - else if (LLViewerParcelMgr::getInstance()->isSoundLocal(pos_global)) - { - // Not in same parcel, and target parcel only has local sound - return false; - } - else - { - // Not in same parcel, but neither are local sound - return true; - } - } -} - - -bool LLViewerParcelMgr::inAgentParcel(const LLVector3d &pos_global) const -{ - LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal(pos_global); - LLViewerRegion* agent_region = gAgent.getRegion(); - if (!region || !agent_region) - return false; - - if (region != agent_region) - { - // Can't be in the agent parcel if you're not in the same region. - return false; - } - - LLVector3 pos_region = agent_region->getPosRegionFromGlobal(pos_global); - S32 row = S32(pos_region.mV[VY] / PARCEL_GRID_STEP_METERS); - S32 column = S32(pos_region.mV[VX] / PARCEL_GRID_STEP_METERS); - - if (mAgentParcelOverlay[row*mParcelsPerEdge + column]) - { - return true; - } - else - { - return false; - } -} - -// Returns NULL when there is no valid data. -LLParcel* LLViewerParcelMgr::getHoverParcel() const -{ - if (mHoverRequestResult == PARCEL_RESULT_SUCCESS) - { - return mHoverParcel; - } - else - { - return NULL; - } -} - -// Returns NULL when there is no valid data. -LLParcel* LLViewerParcelMgr::getCollisionParcel() const -{ - if (mRenderCollision) - { - return mCollisionParcel; - } - else - { - return NULL; - } -} - -// -// UTILITIES -// - -void LLViewerParcelMgr::render() -{ - if (mSelected && mRenderSelection && gSavedSettings.getBOOL("RenderParcelSelection") && !gDisconnected) - { - // Rendering is done in agent-coordinates, so need to supply - // an appropriate offset to the render code. - LLViewerRegion* regionp = LLWorld::getInstance()->getRegionFromPosGlobal(mWestSouth); - if (!regionp) return; - - renderHighlightSegments(mHighlightSegments, regionp); - } -} - - -void LLViewerParcelMgr::renderParcelCollision() -{ - static LLCachedControl ban_lines_mode(gSavedSettings , "ShowBanLines" , PARCEL_BAN_LINES_ON_COLLISION); - - // check for expiration - F32 expiration = (ban_lines_mode == PARCEL_BAN_LINES_ON_PROXIMITY) - ? PARCEL_COLLISION_DRAW_SECS_ON_PROXIMITY - : PARCEL_BAN_LINES_DRAW_SECS_ON_COLLISION; - if (mCollisionTimer.getElapsedTimeF32() > expiration) - { - mRenderCollision = false; - } - - if (mRenderCollision && ban_lines_mode != PARCEL_BAN_LINES_HIDE) - { - LLViewerRegion* regionp = gAgent.getRegion(); - if (regionp) - { - bool use_pass = mCollisionParcel->getParcelFlag(PF_USE_PASS_LIST); - renderCollisionSegments(mCollisionSegments, use_pass, regionp); - } - } -} - - -void LLViewerParcelMgr::sendParcelAccessListRequest(U32 flags) -{ - if (!mSelected) - { - return; - } - - LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosGlobal( mWestSouth ); - if (!region) return; - - LLMessageSystem *msg = gMessageSystem; - - - if (flags & AL_BAN) - { - mCurrentParcel->mBanList.clear(); - } - if (flags & AL_ACCESS) - { - mCurrentParcel->mAccessList.clear(); - } - if (flags & AL_ALLOW_EXPERIENCE) - { - mCurrentParcel->clearExperienceKeysByType(EXPERIENCE_KEY_TYPE_ALLOWED); - } - if (flags & AL_BLOCK_EXPERIENCE) - { - mCurrentParcel->clearExperienceKeysByType(EXPERIENCE_KEY_TYPE_BLOCKED); - } - - // Only the headers differ - msg->newMessageFast(_PREHASH_ParcelAccessListRequest); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_Data); - msg->addS32Fast(_PREHASH_SequenceID, 0); - msg->addU32Fast(_PREHASH_Flags, flags); - msg->addS32("LocalID", mCurrentParcel->getLocalID() ); - msg->sendReliable( region->getHost() ); -} - - -void LLViewerParcelMgr::sendParcelDwellRequest() -{ - if (!mSelected) - { - return; - } - - LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosGlobal( mWestSouth ); - if (!region) return; - - LLMessageSystem *msg = gMessageSystem; - - // Only the headers differ - msg->newMessage("ParcelDwellRequest"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID() ); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->nextBlock("Data"); - msg->addS32("LocalID", mCurrentParcel->getLocalID()); - msg->addUUID("ParcelID", LLUUID::null); // filled in on simulator - msg->sendReliable( region->getHost() ); -} - - -void LLViewerParcelMgr::sendParcelGodForceOwner(const LLUUID& owner_id) -{ - if (!mSelected) - { - LLNotificationsUtil::add("CannotSetLandOwnerNothingSelected"); - return; - } - - LL_INFOS("ParcelMgr") << "Claiming " << mWestSouth << " to " << mEastNorth << LL_ENDL; - - // BUG: Only works for the region containing mWestSouthBottom - LLVector3d east_north_region_check( mEastNorth ); - east_north_region_check.mdV[VX] -= 0.5; - east_north_region_check.mdV[VY] -= 0.5; - - LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosGlobal( mWestSouth ); - if (!region) - { - // TODO: Add a force owner version of this alert. - LLNotificationsUtil::add("CannotContentifyNoRegion"); - return; - } - - // BUG: Make work for cross-region selections - LLViewerRegion *region2 = LLWorld::getInstance()->getRegionFromPosGlobal( east_north_region_check ); - if (region != region2) - { - LLNotificationsUtil::add("CannotSetLandOwnerMultipleRegions"); - return; - } - - LL_INFOS("ParcelMgr") << "Region " << region->getOriginGlobal() << LL_ENDL; - - LLSD payload; - payload["owner_id"] = owner_id; - payload["parcel_local_id"] = mCurrentParcel->getLocalID(); - payload["region_host"] = region->getHost().getIPandPort(); - LLNotification::Params params("ForceOwnerAuctionWarning"); - params.payload(payload).functor.function(callback_god_force_owner); - - if(mCurrentParcel->getAuctionID()) - { - LLNotifications::instance().add(params); - } - else - { - LLNotifications::instance().forceResponse(params, 0); - } -} - -bool callback_god_force_owner(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if(0 == option) - { - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("ParcelGodForceOwner"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->nextBlock("Data"); - msg->addUUID("OwnerID", notification["payload"]["owner_id"].asUUID()); - msg->addS32( "LocalID", notification["payload"]["parcel_local_id"].asInteger()); - msg->sendReliable(LLHost(notification["payload"]["region_host"].asString())); - } - return false; -} - -void LLViewerParcelMgr::sendParcelGodForceToContent() -{ - if (!mSelected) - { - LLNotificationsUtil::add("CannotContentifyNothingSelected"); - return; - } - LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal( mWestSouth ); - if (!region) - { - LLNotificationsUtil::add("CannotContentifyNoRegion"); - return; - } - - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("ParcelGodMarkAsContent"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->nextBlock("ParcelData"); - msg->addS32("LocalID", mCurrentParcel->getLocalID()); - msg->sendReliable(region->getHost()); -} - -void LLViewerParcelMgr::sendParcelRelease() -{ - if (!mSelected) - { - LLNotificationsUtil::add("CannotReleaseLandNothingSelected"); - return; - } - - LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosGlobal( mWestSouth ); - if (!region) - { - LLNotificationsUtil::add("CannotReleaseLandNoRegion"); - return; - } - - //U32 flags = PR_NONE; - //if (god_force) flags |= PR_GOD_FORCE; - - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("ParcelRelease"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID() ); - msg->addUUID("SessionID", gAgent.getSessionID() ); - msg->nextBlock("Data"); - msg->addS32("LocalID", mCurrentParcel->getLocalID() ); - //msg->addU32("Flags", flags); - msg->sendReliable( region->getHost() ); - - // Blitz selection, since the parcel might be non-rectangular, and - // we won't have appropriate parcel information. - deselectLand(); -} - -class LLViewerParcelMgr::ParcelBuyInfo -{ -public: - LLUUID mAgent; - LLUUID mSession; - LLUUID mGroup; - bool mIsGroupOwned; - bool mRemoveContribution; - bool mIsClaim; - LLHost mHost; - - // for parcel buys - S32 mParcelID; - S32 mPrice; - S32 mArea; - - // for land claims - F32 mWest; - F32 mSouth; - F32 mEast; - F32 mNorth; -}; - -LLViewerParcelMgr::ParcelBuyInfo* LLViewerParcelMgr::setupParcelBuy( - const LLUUID& agent_id, - const LLUUID& session_id, - const LLUUID& group_id, - bool is_group_owned, - bool is_claim, - bool remove_contribution) -{ - if (!mSelected || !mCurrentParcel) - { - LLNotificationsUtil::add("CannotBuyLandNothingSelected"); - return NULL; - } - - LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosGlobal( mWestSouth ); - if (!region) - { - LLNotificationsUtil::add("CannotBuyLandNoRegion"); - return NULL; - } - - if (is_claim) - { - LL_INFOS("ParcelMgr") << "Claiming " << mWestSouth << " to " << mEastNorth << LL_ENDL; - LL_INFOS("ParcelMgr") << "Region " << region->getOriginGlobal() << LL_ENDL; - - // BUG: Only works for the region containing mWestSouthBottom - LLVector3d east_north_region_check( mEastNorth ); - east_north_region_check.mdV[VX] -= 0.5; - east_north_region_check.mdV[VY] -= 0.5; - - LLViewerRegion *region2 = LLWorld::getInstance()->getRegionFromPosGlobal( east_north_region_check ); - - if (region != region2) - { - LLNotificationsUtil::add("CantBuyLandAcrossMultipleRegions"); - return NULL; - } - } - - - ParcelBuyInfo* info = new ParcelBuyInfo; - - info->mAgent = agent_id; - info->mSession = session_id; - info->mGroup = group_id; - info->mIsGroupOwned = is_group_owned; - info->mIsClaim = is_claim; - info->mRemoveContribution = remove_contribution; - info->mHost = region->getHost(); - info->mPrice = mCurrentParcel->getSalePrice(); - info->mArea = mCurrentParcel->getArea(); - - if (!is_claim) - { - info->mParcelID = mCurrentParcel->getLocalID(); - } - else - { - // BUG: Make work for cross-region selections - LLVector3 west_south_bottom_region = region->getPosRegionFromGlobal( mWestSouth ); - LLVector3 east_north_top_region = region->getPosRegionFromGlobal( mEastNorth ); - - info->mWest = west_south_bottom_region.mV[VX]; - info->mSouth = west_south_bottom_region.mV[VY]; - info->mEast = east_north_top_region.mV[VX]; - info->mNorth = east_north_top_region.mV[VY]; - } - - return info; -} - -void LLViewerParcelMgr::sendParcelBuy(ParcelBuyInfo* info) -{ - // send the message - LLMessageSystem* msg = gMessageSystem; - msg->newMessage(info->mIsClaim ? "ParcelClaim" : "ParcelBuy"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", info->mAgent); - msg->addUUID("SessionID", info->mSession); - msg->nextBlock("Data"); - msg->addUUID("GroupID", info->mGroup); - msg->addBOOL("IsGroupOwned", info->mIsGroupOwned); - if (!info->mIsClaim) - { - msg->addBOOL("RemoveContribution", info->mRemoveContribution); - msg->addS32("LocalID", info->mParcelID); - } - msg->addBOOL("Final", true); // don't allow escrow buys - if (info->mIsClaim) - { - msg->nextBlock("ParcelData"); - msg->addF32("West", info->mWest); - msg->addF32("South", info->mSouth); - msg->addF32("East", info->mEast); - msg->addF32("North", info->mNorth); - } - else // ParcelBuy - { - msg->nextBlock("ParcelData"); - msg->addS32("Price",info->mPrice); - msg->addS32("Area",info->mArea); - } - msg->sendReliable(info->mHost); -} - -void LLViewerParcelMgr::deleteParcelBuy(ParcelBuyInfo* *info) -{ - // Must be here because ParcelBuyInfo is local to this .cpp file - delete *info; - *info = NULL; -} - -void LLViewerParcelMgr::sendParcelDeed(const LLUUID& group_id) -{ - if (!mSelected || !mCurrentParcel) - { - LLNotificationsUtil::add("CannotDeedLandNothingSelected"); - return; - } - if(group_id.isNull()) - { - LLNotificationsUtil::add("CannotDeedLandNoGroup"); - return; - } - LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosGlobal( mWestSouth ); - if (!region) - { - LLNotificationsUtil::add("CannotDeedLandNoRegion"); - return; - } - - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("ParcelDeedToGroup"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID() ); - msg->addUUID("SessionID", gAgent.getSessionID() ); - msg->nextBlock("Data"); - msg->addUUID("GroupID", group_id ); - msg->addS32("LocalID", mCurrentParcel->getLocalID() ); - //msg->addU32("JoinNeighbors", join); - msg->sendReliable( region->getHost() ); -} - - -/* -// *NOTE: We cannot easily make landmarks at global positions because -// global positions probably refer to a sim/local combination which -// can move over time. We could implement this by looking up the -// region global x,y, but it's easier to take it out for now. -void LLViewerParcelMgr::makeLandmarkAtSelection() -{ - // Don't create for parcels you don't own - if (gAgent.getID() != mCurrentParcel->getOwnerID()) - { - return; - } - - LLVector3d global_center(mWestSouth); - global_center += mEastNorth; - global_center *= 0.5f; - - LLViewerRegion* region; - region = LLWorld::getInstance()->getRegionFromPosGlobal(global_center); - - LLVector3 west_south_bottom_region = region->getPosRegionFromGlobal( mWestSouth ); - LLVector3 east_north_top_region = region->getPosRegionFromGlobal( mEastNorth ); - - std::string name("My Land"); - std::string buffer; - S32 pos_x = (S32)floor((west_south_bottom_region.mV[VX] + east_north_top_region.mV[VX]) / 2.0f); - S32 pos_y = (S32)floor((west_south_bottom_region.mV[VY] + east_north_top_region.mV[VY]) / 2.0f); - buffer = llformat("%s in %s (%d, %d)", - name.c_str(), - region->getName().c_str(), - pos_x, pos_y); - name.assign(buffer); - - create_landmark(name, "Claimed land", global_center); -} -*/ - -const std::string& LLViewerParcelMgr::getAgentParcelName() const -{ - return mAgentParcel->getName(); -} - - -const S32 LLViewerParcelMgr::getAgentParcelId() const -{ - if (mAgentParcel) - return mAgentParcel->getLocalID(); - return INVALID_PARCEL_ID; -} - -void LLViewerParcelMgr::sendParcelPropertiesUpdate(LLParcel* parcel, bool use_agent_region) -{ - if(!parcel) - return; - - LLViewerRegion *region = use_agent_region ? gAgent.getRegion() : LLWorld::getInstance()->getRegionFromPosGlobal( mWestSouth ); - if (!region) - return; - - LLSD body; - std::string url = region->getCapability("ParcelPropertiesUpdate"); - if (!url.empty()) - { - // request new properties update from simulator - U32 message_flags = 0x01; - body["flags"] = ll_sd_from_U32(message_flags); - parcel->packMessage(body); - LL_INFOS("ParcelMgr") << "Sending parcel properties update via capability to: " - << url << LL_ENDL; - - LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, body, - "Parcel Properties sent to sim.", "Parcel Properties failed to send to sim."); - } - else - { - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_ParcelPropertiesUpdate); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ParcelData); - msg->addS32Fast(_PREHASH_LocalID, parcel->getLocalID() ); - - U32 message_flags = 0x01; - msg->addU32("Flags", message_flags); - - parcel->packMessage(msg); - - msg->sendReliable( region->getHost() ); - } -} - - -void LLViewerParcelMgr::setHoverParcel(const LLVector3d& pos) -{ - static U32 last_west, last_south; - static LLUUID last_region; - - // only request parcel info if position has changed outside of the - // last parcel grid step - const U32 west_parcel_step = (U32) floor( pos.mdV[VX] / PARCEL_GRID_STEP_METERS ); - const U32 south_parcel_step = (U32) floor( pos.mdV[VY] / PARCEL_GRID_STEP_METERS ); - - if ((west_parcel_step == last_west) && (south_parcel_step == last_south)) - { - // We are staying in same segment - return; - } - - LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal( pos ); - if (!region) - { - return; - } - - LLUUID region_id = region->getRegionID(); - LLVector3 pos_in_region = region->getPosRegionFromGlobal(pos); - - bool request_properties = false; - if (region_id != last_region) - { - request_properties = true; - } - else - { - // Check if new position is in same parcel. - // This check is not ideal, since it checks by way of straight lines. - // So sometimes (small parcel in the middle of large one) it can - // decide that parcel actually changed, but it still allows to - // reduce amount of requests significantly - - S32 west_parcel_local = (S32)(pos_in_region.mV[VX] / PARCEL_GRID_STEP_METERS); - S32 south_parcel_local = (S32)(pos_in_region.mV[VY] / PARCEL_GRID_STEP_METERS); - - LLViewerParcelOverlay* overlay = region->getParcelOverlay(); - if (!overlay) - { - request_properties = true; - } - while (!request_properties && west_parcel_step < last_west) - { - S32 segment_shift = last_west - west_parcel_step; - request_properties = overlay->parcelLineFlags(south_parcel_local, west_parcel_local + segment_shift) & PARCEL_WEST_LINE; - last_west--; - } - while (!request_properties && south_parcel_step < last_south) - { - S32 segment_shift = last_south - south_parcel_step; - request_properties = overlay->parcelLineFlags(south_parcel_local + segment_shift, west_parcel_local) & PARCEL_SOUTH_LINE; - last_south--; - } - // Note: could have just swapped values, reused first two 'while' and set last_south, last_west separately, - // but this looks to be easier to understand/straightforward/less bulky - while (!request_properties && west_parcel_step > last_west) - { - S32 segment_shift = west_parcel_step - last_west; - request_properties = overlay->parcelLineFlags(south_parcel_local, west_parcel_local - segment_shift + 1) & PARCEL_WEST_LINE; - last_west++; - } - while (!request_properties && south_parcel_step > last_south) - { - S32 segment_shift = south_parcel_step - last_south; - request_properties = overlay->parcelLineFlags(south_parcel_local - segment_shift + 1, west_parcel_local) & PARCEL_SOUTH_LINE; - last_south++; - } - - // if (!request_properties) last_south and last_west will be equal to new values - } - - if (request_properties) - { - last_west = west_parcel_step; - last_south = south_parcel_step; - last_region = region_id; - - LL_DEBUGS("ParcelMgr") << "Requesting parcel properties on hover, for " << pos << LL_ENDL; - - - // Send a rectangle around the point. - // This means the parcel sent back is at least a rectangle around the point, - // which is more efficient for public land. Fewer requests are sent. JC - F32 west = PARCEL_GRID_STEP_METERS * floor(pos_in_region.mV[VX] / PARCEL_GRID_STEP_METERS); - F32 south = PARCEL_GRID_STEP_METERS * floor(pos_in_region.mV[VY] / PARCEL_GRID_STEP_METERS); - - F32 east = west + PARCEL_GRID_STEP_METERS; - F32 north = south + PARCEL_GRID_STEP_METERS; - - // Send request message - LLMessageSystem *msg = gMessageSystem; - msg->newMessageFast(_PREHASH_ParcelPropertiesRequest); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ParcelData); - msg->addS32Fast(_PREHASH_SequenceID, HOVERED_PARCEL_SEQ_ID); - msg->addF32Fast(_PREHASH_West, west); - msg->addF32Fast(_PREHASH_South, south); - msg->addF32Fast(_PREHASH_East, east); - msg->addF32Fast(_PREHASH_North, north); - msg->addBOOL("SnapSelection", false); - msg->sendReliable(region->getHost()); - - mHoverRequestResult = PARCEL_RESULT_NO_DATA; - } -} - - -// static -void LLViewerParcelMgr::processParcelOverlay(LLMessageSystem *msg, void **user) -{ - // Extract the packed overlay information - S32 packed_overlay_size = msg->getSizeFast(_PREHASH_ParcelData, _PREHASH_Data); - - if (packed_overlay_size <= 0) - { - LL_WARNS() << "Overlay size " << packed_overlay_size << LL_ENDL; - return; - } - - S32 parcels_per_edge = LLViewerParcelMgr::getInstance()->mParcelsPerEdge; - S32 expected_size = parcels_per_edge * parcels_per_edge / PARCEL_OVERLAY_CHUNKS; - if (packed_overlay_size != expected_size) - { - LL_WARNS() << "Got parcel overlay size " << packed_overlay_size - << " expecting " << expected_size << LL_ENDL; - return; - } - - S32 sequence_id; - msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_SequenceID, sequence_id); - msg->getBinaryDataFast( - _PREHASH_ParcelData, - _PREHASH_Data, - sPackedOverlay, - expected_size); - - LLHost host = msg->getSender(); - LLViewerRegion *region = LLWorld::getInstance()->getRegion(host); - if (region) - { - region->mParcelOverlay->uncompressLandOverlay( sequence_id, sPackedOverlay ); - } -} - -// static -void LLViewerParcelMgr::processParcelProperties(LLMessageSystem *msg, void **user) -{ - if (LLApp::isExiting() || gDisconnected) - { - LL_DEBUGS("ParcelMgr") << "Ignoring parcel properties, shutting down" << LL_ENDL; - return; - } - - S32 request_result; - S32 sequence_id; - bool snap_selection = false; - S32 self_count = 0; - S32 other_count = 0; - S32 public_count = 0; - S32 local_id; - LLUUID owner_id; - bool is_group_owned; - U32 auction_id = 0; - S32 claim_price_per_meter = 0; - S32 rent_price_per_meter = 0; - S32 claim_date = 0; - LLVector3 aabb_min; - LLVector3 aabb_max; - S32 area = 0; - S32 sw_max_prims = 0; - S32 sw_total_prims = 0; - //LLUUID buyer_id; - U8 status = 0; - S32 max_prims = 0; - S32 total_prims = 0; - S32 owner_prims = 0; - S32 group_prims = 0; - S32 other_prims = 0; - S32 selected_prims = 0; - F32 parcel_prim_bonus = 1.f; - bool region_push_override = false; - bool region_deny_anonymous_override = false; - bool region_deny_identified_override = false; // Deprecated - bool region_deny_transacted_override = false; // Deprecated - bool region_deny_age_unverified_override = false; - bool region_allow_access_override = true; - bool region_allow_environment_override = true; - S32 parcel_environment_version = 0; - bool agent_parcel_update = false; // updating previous(existing) agent parcel - U32 extended_flags = 0; //obscure MOAP - - S32 other_clean_time = 0; - - LLViewerParcelMgr& parcel_mgr = LLViewerParcelMgr::instance(); - - msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_RequestResult, request_result); - msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_SequenceID, sequence_id); - - if (request_result == PARCEL_RESULT_NO_DATA) - { - // no valid parcel data - LL_INFOS("ParcelMgr") << "no valid parcel data" << LL_ENDL; - return; - } - - // Decide where the data will go. - LLParcel* parcel = NULL; - if (sequence_id == SELECTED_PARCEL_SEQ_ID) - { - // ...selected parcels report this sequence id - parcel_mgr.mRequestResult = PARCEL_RESULT_SUCCESS; - parcel = parcel_mgr.mCurrentParcel; - } - else if (sequence_id == HOVERED_PARCEL_SEQ_ID) - { - parcel_mgr.mHoverRequestResult = PARCEL_RESULT_SUCCESS; - parcel = parcel_mgr.mHoverParcel; - } - else if (sequence_id == COLLISION_NOT_IN_GROUP_PARCEL_SEQ_ID || - sequence_id == COLLISION_NOT_ON_LIST_PARCEL_SEQ_ID || - sequence_id == COLLISION_BANNED_PARCEL_SEQ_ID) - { - parcel_mgr.mHoverRequestResult = PARCEL_RESULT_SUCCESS; - parcel = parcel_mgr.mCollisionParcel; - } - else if (sequence_id == 0 || sequence_id > parcel_mgr.mAgentParcelSequenceID) - { - // new agent parcel - // *TODO: Does it really make sense to set the agent parcel to this - // parcel if the client doesn't know what kind of parcel data this is? - parcel_mgr.mAgentParcelSequenceID = sequence_id; - parcel = parcel_mgr.mAgentParcel; - } - else - { - LL_INFOS("ParcelMgr") << "out of order agent parcel sequence id " << sequence_id - << " last good " << parcel_mgr.mAgentParcelSequenceID - << LL_ENDL; - return; - } - - msg->getBOOL("ParcelData", "SnapSelection", snap_selection); - msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_SelfCount, self_count); - msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_OtherCount, other_count); - msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_PublicCount, public_count); - msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_LocalID, local_id); - msg->getUUIDFast(_PREHASH_ParcelData, _PREHASH_OwnerID, owner_id); - msg->getBOOLFast(_PREHASH_ParcelData, _PREHASH_IsGroupOwned, is_group_owned); - msg->getU32Fast(_PREHASH_ParcelData, _PREHASH_AuctionID, auction_id); - msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_ClaimDate, claim_date); - msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_ClaimPrice, claim_price_per_meter); - msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_RentPrice, rent_price_per_meter); - msg->getVector3Fast(_PREHASH_ParcelData, _PREHASH_AABBMin, aabb_min); - msg->getVector3Fast(_PREHASH_ParcelData, _PREHASH_AABBMax, aabb_max); - msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_Area, area); - //msg->getUUIDFast( _PREHASH_ParcelData, _PREHASH_BuyerID, buyer_id); - msg->getU8("ParcelData", "Status", status); - msg->getS32("ParcelData", "SimWideMaxPrims", sw_max_prims); - msg->getS32("ParcelData", "SimWideTotalPrims", sw_total_prims); - msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_MaxPrims, max_prims); - msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_TotalPrims, total_prims); - msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_OwnerPrims, owner_prims); - msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_GroupPrims, group_prims); - msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_OtherPrims, other_prims); - msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_SelectedPrims, selected_prims); - msg->getF32Fast(_PREHASH_ParcelData, _PREHASH_ParcelPrimBonus, parcel_prim_bonus); - msg->getBOOLFast(_PREHASH_ParcelData, _PREHASH_RegionPushOverride, region_push_override); - msg->getBOOLFast(_PREHASH_ParcelData, _PREHASH_RegionDenyAnonymous, region_deny_anonymous_override); - msg->getBOOLFast(_PREHASH_ParcelData, _PREHASH_RegionDenyIdentified, region_deny_identified_override); // Deprecated - msg->getBOOLFast(_PREHASH_ParcelData, _PREHASH_RegionDenyTransacted, region_deny_transacted_override); // Deprecated - if (msg->getNumberOfBlocksFast(_PREHASH_AgeVerificationBlock)) - { - // this block was added later and may not be on older sims, so we have to test its existence first - msg->getBOOLFast(_PREHASH_AgeVerificationBlock, _PREHASH_RegionDenyAgeUnverified, region_deny_age_unverified_override); - } - - if (msg->getNumberOfBlocks(_PREHASH_RegionAllowAccessBlock)) - { - msg->getBOOLFast(_PREHASH_RegionAllowAccessBlock, _PREHASH_RegionAllowAccessOverride, region_allow_access_override); - } - - if (msg->getNumberOfBlocks(_PREHASH_ParcelExtendedFlags)) - { - msg->getU32Fast(_PREHASH_ParcelExtendedFlags, _PREHASH_Flags, extended_flags); - } - - if (msg->getNumberOfBlocks(_PREHASH_ParcelEnvironmentBlock)) - { - msg->getS32Fast(_PREHASH_ParcelEnvironmentBlock, _PREHASH_ParcelEnvironmentVersion, parcel_environment_version); - msg->getBOOLFast(_PREHASH_ParcelEnvironmentBlock, _PREHASH_RegionAllowEnvironmentOverride, region_allow_environment_override); - } - - msg->getS32("ParcelData", "OtherCleanTime", other_clean_time ); - - LL_DEBUGS("ParcelMgr") << "Processing parcel " << local_id << " update, target(sequence): " << sequence_id << LL_ENDL; - - // Actually extract the data. - if (parcel) - { - if (local_id == parcel_mgr.mAgentParcel->getLocalID()) - { - // Parcels in different regions can have same ids. - LLViewerRegion* parcel_region = LLWorld::getInstance()->getRegion(msg->getSender()); - LLViewerRegion* agent_region = gAgent.getRegion(); - if (parcel_region && agent_region && parcel_region->getRegionID() == agent_region->getRegionID()) - { - // we got an updated version of agent parcel - agent_parcel_update = true; - } - } - - S32 cur_parcel_environment_version = parcel->getParcelEnvironmentVersion(); - bool environment_changed = (cur_parcel_environment_version != parcel_environment_version); - - parcel->init(owner_id, - false, false, false, - claim_date, claim_price_per_meter, rent_price_per_meter, - area, other_prims, parcel_prim_bonus, is_group_owned); - parcel->setLocalID(local_id); - parcel->setAABBMin(aabb_min); - parcel->setAABBMax(aabb_max); - - parcel->setAuctionID(auction_id); - parcel->setOwnershipStatus((LLParcel::EOwnershipStatus)status); - - parcel->setSimWideMaxPrimCapacity(sw_max_prims); - parcel->setSimWidePrimCount(sw_total_prims); - parcel->setMaxPrimCapacity(max_prims); - parcel->setOwnerPrimCount(owner_prims); - parcel->setGroupPrimCount(group_prims); - parcel->setOtherPrimCount(other_prims); - parcel->setSelectedPrimCount(selected_prims); - parcel->setParcelPrimBonus(parcel_prim_bonus); - - parcel->setCleanOtherTime(other_clean_time); - parcel->setRegionPushOverride(region_push_override); - parcel->setRegionDenyAnonymousOverride(region_deny_anonymous_override); - parcel->setRegionDenyAgeUnverifiedOverride(region_deny_age_unverified_override); - parcel->setRegionAllowAccessOverride(region_allow_access_override); - parcel->setParcelEnvironmentVersion(cur_parcel_environment_version); - parcel->setRegionAllowEnvironmentOverride(region_allow_environment_override); - - parcel->setObscureMOAP((bool)extended_flags); - - parcel->unpackMessage(msg); - - if (parcel == parcel_mgr.mAgentParcel) - { - // new agent parcel - S32 bitmap_size = parcel_mgr.mParcelsPerEdge - * parcel_mgr.mParcelsPerEdge - / 8; - U8* bitmap = new U8[ bitmap_size ]; - msg->getBinaryDataFast(_PREHASH_ParcelData, _PREHASH_Bitmap, bitmap, bitmap_size); - - parcel_mgr.writeAgentParcelFromBitmap(bitmap); - delete[] bitmap; - - // Let interesting parties know about agent parcel change. - LLViewerParcelMgr* instance = LLViewerParcelMgr::getInstance(); - - if (instance->mTeleportInProgress) - { - instance->mTeleportInProgress = false; - if(instance->mTeleportInProgressPosition.isNull()) - { - //initial update - instance->mTeleportFinishedSignal(gAgent.getPositionGlobal(), false); - } - else - { - instance->mTeleportFinishedSignal(instance->mTeleportInProgressPosition, false); - } - } - parcel->setParcelEnvironmentVersion(parcel_environment_version); - LL_DEBUGS("ENVIRONMENT") << "Parcel environment version is " << parcel->getParcelEnvironmentVersion() << LL_ENDL; - - // Notify anything that wants to know when the agent changes parcels - gAgent.changeParcels(); - instance->mTeleportInProgress = false; - } - else if (agent_parcel_update) - { - parcel->setParcelEnvironmentVersion(parcel_environment_version); - // updated agent parcel - parcel_mgr.mAgentParcel->unpackMessage(msg); - if ((LLEnvironment::instance().isExtendedEnvironmentEnabled() && environment_changed)) - { - LL_DEBUGS("ENVIRONMENT") << "Parcel environment version is " << parcel->getParcelEnvironmentVersion() << LL_ENDL; - LLEnvironment::instance().requestParcel(local_id); - } - } - } - - // Handle updating selections, if necessary. - if (sequence_id == SELECTED_PARCEL_SEQ_ID) - { - // Update selected counts - parcel_mgr.mCurrentParcelSelection->mSelectedSelfCount = self_count; - parcel_mgr.mCurrentParcelSelection->mSelectedOtherCount = other_count; - parcel_mgr.mCurrentParcelSelection->mSelectedPublicCount = public_count; - - parcel_mgr.mCurrentParcelSelection->mSelectedMultipleOwners = - (request_result == PARCEL_RESULT_MULTIPLE); - - // Select the whole parcel - LLViewerRegion* region = LLWorld::getInstance()->getRegion( msg->getSender() ); - if (region) - { - if (!snap_selection) - { - // don't muck with the westsouth and eastnorth. - // just highlight it - LLVector3 west_south = region->getPosRegionFromGlobal(parcel_mgr.mWestSouth); - LLVector3 east_north = region->getPosRegionFromGlobal(parcel_mgr.mEastNorth); - - parcel_mgr.resetSegments(parcel_mgr.mHighlightSegments); - parcel_mgr.writeHighlightSegments( - west_south.mV[VX], - west_south.mV[VY], - east_north.mV[VX], - east_north.mV[VY] ); - parcel_mgr.mCurrentParcelSelection->mWholeParcelSelected = false; - } - else if (0 == local_id) - { - // this is public land, just highlight the selection - parcel_mgr.mWestSouth = region->getPosGlobalFromRegion( aabb_min ); - parcel_mgr.mEastNorth = region->getPosGlobalFromRegion( aabb_max ); - - parcel_mgr.resetSegments(parcel_mgr.mHighlightSegments); - parcel_mgr.writeHighlightSegments( - aabb_min.mV[VX], - aabb_min.mV[VY], - aabb_max.mV[VX], - aabb_max.mV[VY] ); - parcel_mgr.mCurrentParcelSelection->mWholeParcelSelected = true; - } - else - { - parcel_mgr.mWestSouth = region->getPosGlobalFromRegion( aabb_min ); - parcel_mgr.mEastNorth = region->getPosGlobalFromRegion( aabb_max ); - - // Owned land, highlight the boundaries - S32 bitmap_size = parcel_mgr.mParcelsPerEdge - * parcel_mgr.mParcelsPerEdge - / 8; - U8* bitmap = new U8[ bitmap_size ]; - msg->getBinaryDataFast(_PREHASH_ParcelData, _PREHASH_Bitmap, bitmap, bitmap_size); - - parcel_mgr.resetSegments(parcel_mgr.mHighlightSegments); - parcel_mgr.writeSegmentsFromBitmap( bitmap, parcel_mgr.mHighlightSegments ); - - delete[] bitmap; - bitmap = NULL; - - parcel_mgr.mCurrentParcelSelection->mWholeParcelSelected = true; - } - - // Request access list information for this land - parcel_mgr.sendParcelAccessListRequest(AL_ACCESS | AL_BAN | AL_ALLOW_EXPERIENCE | AL_BLOCK_EXPERIENCE); - - // Request dwell for this land, if it's not public land. - parcel_mgr.mSelectedDwell = DWELL_NAN; - if (0 != local_id) - { - parcel_mgr.sendParcelDwellRequest(); - } - - parcel_mgr.mSelected = true; - parcel_mgr.notifyObservers(); - } - } - else if (sequence_id == COLLISION_NOT_IN_GROUP_PARCEL_SEQ_ID || - sequence_id == COLLISION_NOT_ON_LIST_PARCEL_SEQ_ID || - sequence_id == COLLISION_BANNED_PARCEL_SEQ_ID) - { - // We're about to collide with this parcel - static LLCachedControl ban_lines_mode(gSavedSettings , "ShowBanLines" , PARCEL_BAN_LINES_ON_COLLISION); - if (ban_lines_mode == PARCEL_BAN_LINES_ON_PROXIMITY) - { - parcel_mgr.resetCollisionTimer(); - } - - // Differentiate this parcel if we are banned from it. - if (sequence_id == COLLISION_BANNED_PARCEL_SEQ_ID) - { - parcel_mgr.mCollisionBanned = BA_BANNED; - } - else if (sequence_id == COLLISION_NOT_IN_GROUP_PARCEL_SEQ_ID) - { - parcel_mgr.mCollisionBanned = BA_NOT_IN_GROUP; - } - else - { - parcel_mgr.mCollisionBanned = BA_NOT_ON_LIST; - - } - - S32 bitmap_size = parcel_mgr.mParcelsPerEdge - * parcel_mgr.mParcelsPerEdge - / 8; - U8* bitmap = new U8[ bitmap_size ]; - msg->getBinaryDataFast(_PREHASH_ParcelData, _PREHASH_Bitmap, bitmap, bitmap_size); - - parcel_mgr.resetSegments(parcel_mgr.mCollisionSegments); - parcel_mgr.writeSegmentsFromBitmap( bitmap, parcel_mgr.mCollisionSegments ); - - delete[] bitmap; - bitmap = NULL; - - } - else if (sequence_id == HOVERED_PARCEL_SEQ_ID) - { - LLViewerRegion *region = LLWorld::getInstance()->getRegion( msg->getSender() ); - if (region) - { - parcel_mgr.mHoverWestSouth = region->getPosGlobalFromRegion( aabb_min ); - parcel_mgr.mHoverEastNorth = region->getPosGlobalFromRegion( aabb_max ); - } - else - { - parcel_mgr.mHoverWestSouth.clearVec(); - parcel_mgr.mHoverEastNorth.clearVec(); - } - } - else - { - if (gNonInteractive) - { - return; - } - - // Check for video - LLViewerParcelMedia::getInstance()->update(parcel); - - // Then check for music - if (gAudiop) - { - if (parcel) - { - // Only update stream if parcel changed (recreated) or music is playing (enabled) - static LLCachedControl already_playing(gSavedSettings, "MediaTentativeAutoPlay", true); - if (!agent_parcel_update || already_playing) - { - LLViewerParcelAskPlay::getInstance()->cancelNotification(); - std::string music_url_raw = parcel->getMusicURL(); - - // Trim off whitespace from front and back - std::string music_url = music_url_raw; - LLStringUtil::trim(music_url); - - // If there is a new music URL and it's valid, play it. - if (music_url.size() > 12) - { - if (music_url.substr(0, 7) == "http://" - || music_url.substr(0, 8) == "https://") - { - LLViewerRegion *region = LLWorld::getInstance()->getRegion(msg->getSender()); - if (region) - { - optionallyStartMusic(music_url, parcel->mLocalID, region->getRegionID(), !agent_parcel_update); - } - } - else - { - LL_INFOS("ParcelMgr") << "Stopping parcel music (invalid audio stream URL)" << LL_ENDL; - // clears the URL - // null value causes fade out - LLViewerAudio::getInstance()->startInternetStreamWithAutoFade(LLStringUtil::null); - } - } - else if (!gAudiop->getInternetStreamURL().empty()) - { - LL_INFOS("ParcelMgr") << "Stopping parcel music (parcel stream URL is empty)" << LL_ENDL; - // null value causes fade out - LLViewerAudio::getInstance()->startInternetStreamWithAutoFade(LLStringUtil::null); - } - } - } - else - { - // Public land has no music - LLViewerParcelAskPlay::getInstance()->cancelNotification(); - LLViewerAudio::getInstance()->stopInternetStreamWithAutoFade(); - } - }//if gAudiop - }; -} - -//static -void LLViewerParcelMgr::onStartMusicResponse(const LLUUID ®ion_id, const S32 &parcel_id, const std::string &url, const bool &play) -{ - if (play) - { - LL_INFOS("ParcelMgr") << "Starting parcel music " << url << LL_ENDL; - LLViewerAudio::getInstance()->startInternetStreamWithAutoFade(url); - } - else - { - LLViewerAudio::getInstance()->startInternetStreamWithAutoFade(LLStringUtil::null); - } -} - -void LLViewerParcelMgr::optionallyStartMusic(const std::string &music_url, const S32 &local_id, const LLUUID ®ion_id, bool switched_parcel) -{ - static LLCachedControl streaming_music(gSavedSettings, "AudioStreamingMusic", true); - if (streaming_music) - { - static LLCachedControl autoplay_mode(gSavedSettings, "ParcelMediaAutoPlayEnable", 1); - static LLCachedControl tentative_autoplay(gSavedSettings, "MediaTentativeAutoPlay", true); - // only play music when you enter a new parcel if the UI control for this - // was not *explicitly* stopped by the user. (part of SL-4878) - LLPanelNearByMedia* nearby_media_panel = gStatusBar ? gStatusBar->getNearbyMediaPanel() : NULL; - LLViewerAudio* viewer_audio = LLViewerAudio::getInstance(); - - // ask mode //todo constants - if (autoplay_mode == 2) - { - // if user set media to play - ask - if ((nearby_media_panel && nearby_media_panel->getParcelAudioAutoStart()) - || (!nearby_media_panel && tentative_autoplay)) - { - // user did not stop audio - if (switched_parcel || music_url != viewer_audio->getNextStreamURI()) - { - viewer_audio->startInternetStreamWithAutoFade(LLStringUtil::null); - - LLViewerParcelAskPlay::getInstance()->askToPlay(region_id, - local_id, - music_url, - onStartMusicResponse); - } - // else do nothing: - // Parcel properties changed, but not url. - // We are already playing this url and asked about it when agent entered parcel - // or user started audio manually at some point - } - else - { - // stopped by the user, do not autoplay - LLViewerParcelAskPlay::getInstance()->cancelNotification(); - viewer_audio->startInternetStreamWithAutoFade(LLStringUtil::null); - } - } - // autoplay - else if ((nearby_media_panel - && nearby_media_panel->getParcelAudioAutoStart()) - // or they have expressed no opinion in the UI, but have autoplay on... - || (!nearby_media_panel - && autoplay_mode == 1 - && tentative_autoplay)) - { - LL_INFOS("ParcelMgr") << "Starting parcel music " << music_url << LL_ENDL; - viewer_audio->startInternetStreamWithAutoFade(music_url); - } - // autoplay off - else if(switched_parcel || music_url != viewer_audio->getNextStreamURI()) - { - viewer_audio->startInternetStreamWithAutoFade(LLStringUtil::null); - } - } -} - -// static -void LLViewerParcelMgr::processParcelAccessListReply(LLMessageSystem *msg, void **user) -{ - LLUUID agent_id; - S32 sequence_id = 0; - U32 message_flags = 0x0; - S32 parcel_id = -1; - - msg->getUUIDFast(_PREHASH_Data, _PREHASH_AgentID, agent_id); - msg->getS32Fast( _PREHASH_Data, _PREHASH_SequenceID, sequence_id ); //ignored - msg->getU32Fast( _PREHASH_Data, _PREHASH_Flags, message_flags); - msg->getS32Fast( _PREHASH_Data, _PREHASH_LocalID, parcel_id); - - LLParcel* parcel = LLViewerParcelMgr::getInstance()->mCurrentParcel; - if (!parcel) return; - - if (parcel_id != parcel->getLocalID()) - { - LL_WARNS_ONCE("ParcelMgr") << "processParcelAccessListReply for parcel " << parcel_id - << " which isn't the selected parcel " << parcel->getLocalID()<< LL_ENDL; - return; - } - - if (message_flags & AL_ACCESS) - { - parcel->unpackAccessEntries(msg, &(parcel->mAccessList) ); - } - else if (message_flags & AL_BAN) - { - parcel->unpackAccessEntries(msg, &(parcel->mBanList) ); - } - else if (message_flags & AL_ALLOW_EXPERIENCE) - { - parcel->unpackExperienceEntries(msg, EXPERIENCE_KEY_TYPE_ALLOWED); - } - else if (message_flags & AL_BLOCK_EXPERIENCE) - { - parcel->unpackExperienceEntries(msg, EXPERIENCE_KEY_TYPE_BLOCKED); - } - /*else if (message_flags & AL_RENTER) - { - parcel->unpackAccessEntries(msg, &(parcel->mRenterList) ); - }*/ - - LLViewerParcelMgr::getInstance()->notifyObservers(); -} - - -// static -void LLViewerParcelMgr::processParcelDwellReply(LLMessageSystem* msg, void**) -{ - LLUUID agent_id; - msg->getUUID("AgentData", "AgentID", agent_id); - - S32 local_id; - msg->getS32("Data", "LocalID", local_id); - - LLUUID parcel_id; - msg->getUUID("Data", "ParcelID", parcel_id); - - F32 dwell; - msg->getF32("Data", "Dwell", dwell); - - if (local_id == LLViewerParcelMgr::getInstance()->mCurrentParcel->getLocalID()) - { - LLViewerParcelMgr::getInstance()->mSelectedDwell = dwell; - LLViewerParcelMgr::getInstance()->notifyObservers(); - } -} - - -void LLViewerParcelMgr::sendParcelAccessListUpdate(U32 which) -{ - if (!mSelected) - { - return; - } - - LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal( mWestSouth ); - if (!region) return; - - LLParcel* parcel = mCurrentParcel; - if (!parcel) return; - - if (which & AL_ACCESS) - { - sendParcelAccessListUpdate(AL_ACCESS, parcel->mAccessList, region, parcel->getLocalID()); - } - - if (which & AL_BAN) - { - sendParcelAccessListUpdate(AL_BAN, parcel->mBanList, region, parcel->getLocalID()); - } - - if(which & AL_ALLOW_EXPERIENCE) - { - sendParcelAccessListUpdate(AL_ALLOW_EXPERIENCE, parcel->getExperienceKeysByType(EXPERIENCE_KEY_TYPE_ALLOWED), region, parcel->getLocalID()); - } - if(which & AL_BLOCK_EXPERIENCE) - { - sendParcelAccessListUpdate(AL_BLOCK_EXPERIENCE, parcel->getExperienceKeysByType(EXPERIENCE_KEY_TYPE_BLOCKED), region, parcel->getLocalID()); - } -} - -void LLViewerParcelMgr::sendParcelAccessListUpdate(U32 flags, const LLAccessEntry::map& entries, LLViewerRegion* region, S32 parcel_local_id) -{ - S32 count = entries.size(); - S32 num_sections = (S32) ceil(count/PARCEL_MAX_ENTRIES_PER_PACKET); - S32 sequence_id = 1; - bool start_message = true; - bool initial = true; - - LLUUID transactionUUID; - transactionUUID.generate(); - - - LLMessageSystem* msg = gMessageSystem; - - LLAccessEntry::map::const_iterator cit = entries.begin(); - LLAccessEntry::map::const_iterator end = entries.end(); - while ( (cit != end) || initial ) - { - if (start_message) - { - msg->newMessageFast(_PREHASH_ParcelAccessListUpdate); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); - msg->nextBlockFast(_PREHASH_Data); - msg->addU32Fast(_PREHASH_Flags, flags); - msg->addS32(_PREHASH_LocalID, parcel_local_id); - msg->addUUIDFast(_PREHASH_TransactionID, transactionUUID); - msg->addS32Fast(_PREHASH_SequenceID, sequence_id); - msg->addS32Fast(_PREHASH_Sections, num_sections); - start_message = false; - - if (initial && (cit == end)) - { - // pack an empty block if there will be no data - msg->nextBlockFast(_PREHASH_List); - msg->addUUIDFast(_PREHASH_ID, LLUUID::null ); - msg->addS32Fast(_PREHASH_Time, 0 ); - msg->addU32Fast(_PREHASH_Flags, 0 ); - } - - initial = false; - sequence_id++; - - } - - while ( (cit != end) && (msg->getCurrentSendTotal() < MTUBYTES)) - { - const LLAccessEntry& entry = (*cit).second; - - msg->nextBlockFast(_PREHASH_List); - msg->addUUIDFast(_PREHASH_ID, entry.mID ); - msg->addS32Fast(_PREHASH_Time, entry.mTime ); - msg->addU32Fast(_PREHASH_Flags, entry.mFlags ); - ++cit; - } - - start_message = true; - msg->sendReliable( region->getHost() ); - } -} - - -void LLViewerParcelMgr::deedLandToGroup() -{ - std::string group_name; - gCacheName->getGroupName(mCurrentParcel->getGroupID(), group_name); - LLSD args; - args["AREA"] = llformat("%d", mCurrentParcel->getArea()); - args["GROUP_NAME"] = group_name; - if(mCurrentParcel->getContributeWithDeed()) - { - args["NAME"] = LLSLURL("agent", mCurrentParcel->getOwnerID(), "completename").getSLURLString(); - LLNotificationsUtil::add("DeedLandToGroupWithContribution",args, LLSD(), deedAlertCB); - } - else - { - LLNotificationsUtil::add("DeedLandToGroup",args, LLSD(), deedAlertCB); - } -} - -// static -bool LLViewerParcelMgr::deedAlertCB(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) - { - LLParcel* parcel = LLViewerParcelMgr::getInstance()->getParcelSelection()->getParcel(); - LLUUID group_id; - if(parcel) - { - group_id = parcel->getGroupID(); - } - LLViewerParcelMgr::getInstance()->sendParcelDeed(group_id); - } - return false; -} - - -void LLViewerParcelMgr::startReleaseLand() -{ - if (!mSelected) - { - LLNotificationsUtil::add("CannotReleaseLandNothingSelected"); - return; - } - - if (mRequestResult == PARCEL_RESULT_NO_DATA) - { - LLNotificationsUtil::add("CannotReleaseLandWatingForServer"); - return; - } - - if (mRequestResult == PARCEL_RESULT_MULTIPLE) - { - LLNotificationsUtil::add("CannotReleaseLandSelected"); - return; - } - - if (!isParcelOwnedByAgent(mCurrentParcel, GP_LAND_RELEASE) - && !(gAgent.canManageEstate())) - { - LLNotificationsUtil::add("CannotReleaseLandDontOwn"); - return; - } - - LLVector3d parcel_center = (mWestSouth + mEastNorth) / 2.0; - LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal(parcel_center); - if (!region) - { - LLNotificationsUtil::add("CannotReleaseLandRegionNotFound"); - return; - } -/* - if (region->getRegionFlag(REGION_FLAGS_BLOCK_LAND_RESELL) - && !gAgent.isGodlike()) - { - LLSD args; - args["REGION"] = region->getName(); - LLNotificationsUtil::add("CannotReleaseLandNoTransfer", args); - return; - } -*/ - - if (!mCurrentParcelSelection->mWholeParcelSelected) - { - LLNotificationsUtil::add("CannotReleaseLandPartialSelection"); - return; - } - - // Compute claim price - LLSD args; - args["AREA"] = llformat("%d",mCurrentParcel->getArea()); - LLNotificationsUtil::add("ReleaseLandWarning", args, LLSD(), releaseAlertCB); -} - -bool LLViewerParcelMgr::canAgentBuyParcel(LLParcel* parcel, bool forGroup) const -{ - if (!parcel) - { - return false; - } - - if (mSelected && parcel == mCurrentParcel) - { - if (mRequestResult == PARCEL_RESULT_NO_DATA) - { - return false; - } - } - - const LLUUID& parcelOwner = parcel->getOwnerID(); - const LLUUID& authorizeBuyer = parcel->getAuthorizedBuyerID(); - - if (parcel->isPublic()) - { - return true; // change this if want to make it gods only - } - - LLVector3 parcel_coord = parcel->getCenterpoint(); - LLViewerRegion* regionp = LLWorld::getInstance()->getRegionFromPosAgent(parcel_coord); - if (regionp) - { - U8 sim_access = regionp->getSimAccess(); - const LLAgentAccess& agent_access = gAgent.getAgentAccess(); - // if the region is PG, we're happy already, so do nothing - // but if we're set to avoid either mature or adult, get us outta here - if ((sim_access == SIM_ACCESS_MATURE) && - !agent_access.canAccessMature()) - { - return false; - } - else if ((sim_access == SIM_ACCESS_ADULT) && - !agent_access.canAccessAdult()) - { - return false; - } - } - - bool isForSale = parcel->getForSale() - && ((parcel->getSalePrice() > 0) || (authorizeBuyer.notNull())); - - bool isEmpowered - = forGroup ? gAgent.hasPowerInActiveGroup(GP_LAND_DEED) : true; - - bool isOwner - = parcelOwner == (forGroup ? gAgent.getGroupID() : gAgent.getID()); - - bool isAuthorized - = (authorizeBuyer.isNull() - || (gAgent.getID() == authorizeBuyer) - || (gAgent.hasPowerInGroup(authorizeBuyer,GP_LAND_DEED) - && gAgent.hasPowerInGroup(authorizeBuyer,GP_LAND_SET_SALE_INFO))); - - return isForSale && !isOwner && isAuthorized && isEmpowered; -} - - -void LLViewerParcelMgr::startBuyLand(bool is_for_group) -{ - LLFloaterBuyLand::buyLand(getSelectionRegion(), mCurrentParcelSelection, is_for_group); -} - -void LLViewerParcelMgr::startSellLand() -{ - LLFloaterSellLand::sellLand(getSelectionRegion(), mCurrentParcelSelection); -} - -void LLViewerParcelMgr::startDivideLand() -{ - if (!mSelected) - { - LLNotificationsUtil::add("CannotDivideLandNothingSelected"); - return; - } - - if (mCurrentParcelSelection->mWholeParcelSelected) - { - LLNotificationsUtil::add("CannotDivideLandPartialSelection"); - return; - } - - LLSD payload; - payload["west_south_border"] = ll_sd_from_vector3d(mWestSouth); - payload["east_north_border"] = ll_sd_from_vector3d(mEastNorth); - - LLNotificationsUtil::add("LandDivideWarning", LLSD(), payload, callbackDivideLand); -} - -// static -bool LLViewerParcelMgr::callbackDivideLand(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - LLVector3d west_south_d = ll_vector3d_from_sd(notification["payload"]["west_south_border"]); - LLVector3d east_north_d = ll_vector3d_from_sd(notification["payload"]["east_north_border"]); - LLVector3d parcel_center = (west_south_d + east_north_d) / 2.0; - - LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal(parcel_center); - if (!region) - { - LLNotificationsUtil::add("CannotDivideLandNoRegion"); - return false; - } - - if (0 == option) - { - LLVector3 west_south = region->getPosRegionFromGlobal(west_south_d); - LLVector3 east_north = region->getPosRegionFromGlobal(east_north_d); - - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("ParcelDivide"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->nextBlock("ParcelData"); - msg->addF32("West", west_south.mV[VX]); - msg->addF32("South", west_south.mV[VY]); - msg->addF32("East", east_north.mV[VX]); - msg->addF32("North", east_north.mV[VY]); - msg->sendReliable(region->getHost()); - } - return false; -} - - -void LLViewerParcelMgr::startJoinLand() -{ - if (!mSelected) - { - LLNotificationsUtil::add("CannotJoinLandNothingSelected"); - return; - } - - if (mCurrentParcelSelection->mWholeParcelSelected) - { - LLNotificationsUtil::add("CannotJoinLandEntireParcelSelected"); - return; - } - - if (!mCurrentParcelSelection->mSelectedMultipleOwners) - { - LLNotificationsUtil::add("CannotJoinLandSelection"); - return; - } - - LLSD payload; - payload["west_south_border"] = ll_sd_from_vector3d(mWestSouth); - payload["east_north_border"] = ll_sd_from_vector3d(mEastNorth); - - LLNotificationsUtil::add("JoinLandWarning", LLSD(), payload, callbackJoinLand); -} - -// static -bool LLViewerParcelMgr::callbackJoinLand(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - LLVector3d west_south_d = ll_vector3d_from_sd(notification["payload"]["west_south_border"]); - LLVector3d east_north_d = ll_vector3d_from_sd(notification["payload"]["east_north_border"]); - LLVector3d parcel_center = (west_south_d + east_north_d) / 2.0; - - LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal(parcel_center); - if (!region) - { - LLNotificationsUtil::add("CannotJoinLandNoRegion"); - return false; - } - - if (0 == option) - { - LLVector3 west_south = region->getPosRegionFromGlobal(west_south_d); - LLVector3 east_north = region->getPosRegionFromGlobal(east_north_d); - - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("ParcelJoin"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->nextBlock("ParcelData"); - msg->addF32("West", west_south.mV[VX]); - msg->addF32("South", west_south.mV[VY]); - msg->addF32("East", east_north.mV[VX]); - msg->addF32("North", east_north.mV[VY]); - msg->sendReliable(region->getHost()); - } - return false; -} - - -void LLViewerParcelMgr::startDeedLandToGroup() -{ - if (!mSelected || !mCurrentParcel) - { - LLNotificationsUtil::add("CannotDeedLandNothingSelected"); - return; - } - - if (mRequestResult == PARCEL_RESULT_NO_DATA) - { - LLNotificationsUtil::add("CannotDeedLandWaitingForServer"); - return; - } - - if (mRequestResult == PARCEL_RESULT_MULTIPLE) - { - LLNotificationsUtil::add("CannotDeedLandMultipleSelected"); - return; - } - - LLVector3d parcel_center = (mWestSouth + mEastNorth) / 2.0; - LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal(parcel_center); - if (!region) - { - LLNotificationsUtil::add("CannotDeedLandNoRegion"); - return; - } - - /* - if(!gAgent.isGodlike()) - { - if(region->getRegionFlag(REGION_FLAGS_BLOCK_LAND_RESELL) - && (mCurrentParcel->getOwnerID() != region->getOwner())) - { - LLSD args; - args["REGION"] = region->getName(); - LLNotificationsUtil::add("CannotDeedLandNoTransfer", args); - return; - } - } - */ - - deedLandToGroup(); -} -void LLViewerParcelMgr::reclaimParcel() -{ - LLParcel* parcel = LLViewerParcelMgr::getInstance()->getParcelSelection()->getParcel(); - LLViewerRegion* regionp = LLViewerParcelMgr::getInstance()->getSelectionRegion(); - if(parcel && parcel->getOwnerID().notNull() - && (parcel->getOwnerID() != gAgent.getID()) - && regionp && (regionp->getOwner() == gAgent.getID())) - { - LLMessageSystem* msg = gMessageSystem; - msg->newMessage("ParcelReclaim"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->nextBlock("Data"); - msg->addS32("LocalID", parcel->getLocalID()); - msg->sendReliable(regionp->getHost()); - } -} - -// static -bool LLViewerParcelMgr::releaseAlertCB(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if (option == 0) - { - // Send the release message, not a force - LLViewerParcelMgr::getInstance()->sendParcelRelease(); - } - return false; -} - -void LLViewerParcelMgr::buyPass() -{ - LLParcel* parcel = getParcelSelection()->getParcel(); - if (!parcel) return; - - LLViewerRegion* region = getSelectionRegion(); - if (!region) return; - - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_ParcelBuyPass); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_ParcelData); - msg->addS32Fast(_PREHASH_LocalID, parcel->getLocalID() ); - msg->sendReliable( region->getHost() ); -} - -//Tells whether we are allowed to buy a pass or not -bool LLViewerParcelMgr::isCollisionBanned() -{ - if ((mCollisionBanned == BA_ALLOWED) || (mCollisionBanned == BA_NOT_ON_LIST) || (mCollisionBanned == BA_NOT_IN_GROUP)) - return false; - else - return true; -} - -// This implementation should mirror LLSimParcelMgr::isParcelOwnedBy -// static -bool LLViewerParcelMgr::isParcelOwnedByAgent(const LLParcel* parcelp, U64 group_proxy_power) -{ - if (!parcelp) - { - return false; - } - - // Gods can always assume ownership. - if (gAgent.isGodlike()) - { - return true; - } - - // The owner of a parcel automatically gets all powersr. - if (parcelp->getOwnerID() == gAgent.getID()) - { - return true; - } - - // Only gods can assume 'ownership' of public land. - if (parcelp->isPublic()) - { - return false; - } - - // Return whether or not the agent has group_proxy_power powers in the - // parcel's group. - return gAgent.hasPowerInGroup(parcelp->getOwnerID(), group_proxy_power); -} - -// This implementation should mirror llSimParcelMgr::isParcelModifiableBy -// static -bool LLViewerParcelMgr::isParcelModifiableByAgent(const LLParcel* parcelp, U64 group_proxy_power) -{ - // If the agent can assume ownership, it is probably modifiable. - bool rv = false; - if (parcelp) - { - // *NOTE: This should only work for leased parcels, but group owned - // parcels cannot be OS_LEASED yet. Phoenix 2003-12-15. - rv = isParcelOwnedByAgent(parcelp, group_proxy_power); - - // ... except for the case that the parcel is not OS_LEASED for agent-owned parcels. - if( (gAgent.getID() == parcelp->getOwnerID()) - && !gAgent.isGodlike() - && (parcelp->getOwnershipStatus() != LLParcel::OS_LEASED) ) - { - rv = false; - } - } - return rv; -} - -void sanitize_corners(const LLVector3d &corner1, - const LLVector3d &corner2, - LLVector3d &west_south_bottom, - LLVector3d &east_north_top) -{ - west_south_bottom.mdV[VX] = llmin( corner1.mdV[VX], corner2.mdV[VX] ); - west_south_bottom.mdV[VY] = llmin( corner1.mdV[VY], corner2.mdV[VY] ); - west_south_bottom.mdV[VZ] = llmin( corner1.mdV[VZ], corner2.mdV[VZ] ); - - east_north_top.mdV[VX] = llmax( corner1.mdV[VX], corner2.mdV[VX] ); - east_north_top.mdV[VY] = llmax( corner1.mdV[VY], corner2.mdV[VY] ); - east_north_top.mdV[VZ] = llmax( corner1.mdV[VZ], corner2.mdV[VZ] ); -} - - -void LLViewerParcelMgr::cleanupGlobals() -{ -} - -LLViewerTexture* LLViewerParcelMgr::getBlockedImage() const -{ - return sBlockedImage; -} - -LLViewerTexture* LLViewerParcelMgr::getPassImage() const -{ - return sPassImage; -} - -/* - * Set finish teleport callback. You can use it to observe all teleport events. - * NOTE: - * After local( in one region) teleports we - * cannot rely on gAgent.getPositionGlobal(), - * so the new position gets passed explicitly. - * Use args of this callback to get global position of avatar after teleport event. - */ -boost::signals2::connection LLViewerParcelMgr::setTeleportFinishedCallback(teleport_finished_callback_t cb) -{ - return mTeleportFinishedSignal.connect(cb); -} - -boost::signals2::connection LLViewerParcelMgr::setTeleportFailedCallback(teleport_failed_callback_t cb) -{ - return mTeleportFailedSignal.connect(cb); -} - -/* Ok, we're notified that teleport has been finished. - * We should now propagate the notification via mTeleportFinishedSignal - * to all interested parties. - */ -void LLViewerParcelMgr::onTeleportFinished(bool local, const LLVector3d& new_pos) -{ - // Treat only teleports within the same parcel as local (EXT-3139). - if (local && LLViewerParcelMgr::getInstance()->inAgentParcel(new_pos)) - { - // Local teleport. We already have the agent parcel data. - // Emit the signal immediately. - getInstance()->mTeleportFinishedSignal(new_pos, local); - } - else - { - // Non-local teleport (inter-region or between different parcels of the same region). - // The agent parcel data has not been updated yet. - // Let's wait for the update and then emit the signal. - mTeleportInProgressPosition = new_pos; - mTeleportInProgress = true; - } -} - -void LLViewerParcelMgr::onTeleportFailed() -{ - mTeleportFailedSignal(); -} - -bool LLViewerParcelMgr::getTeleportInProgress() -{ - return mTeleportInProgress // case where parcel data arrives after teleport - || gAgent.getTeleportState() > LLAgent::TELEPORT_NONE; // For LOCAL, no mTeleportInProgress -} +/** + * @file llviewerparcelmgr.cpp + * @brief Viewer-side representation of owned land + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llviewerparcelmgr.h" + +// Library includes +#include "llaudioengine.h" +#include "indra_constants.h" +#include "llcachename.h" +#include "llgl.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "llparcel.h" +#include "message.h" +#include "llfloaterreg.h" + +// Viewer includes +#include "llagent.h" +#include "llagentaccess.h" +#include "llviewerparcelaskplay.h" +#include "llviewerwindow.h" +#include "llviewercontrol.h" +//#include "llfirstuse.h" +#include "llfloaterbuyland.h" +#include "llfloatergroups.h" +#include "llpanelnearbymedia.h" +#include "llfloatersellland.h" +#include "llfloatertools.h" +#include "llparcelselection.h" +#include "llresmgr.h" +#include "llsdutil.h" +#include "llsdutil_math.h" +#include "llslurl.h" +#include "llstatusbar.h" +#include "llui.h" +#include "llviewertexture.h" +#include "llviewertexturelist.h" +#include "llviewermenu.h" +#include "llviewerparcelmedia.h" +#include "llviewerparceloverlay.h" +#include "llviewerregion.h" +#include "llworld.h" +#include "roles_constants.h" +#include "llweb.h" +#include "llvieweraudio.h" +#include "llcorehttputil.h" + +#include "llenvironment.h" + +const F32 PARCEL_BAN_LINES_DRAW_SECS_ON_COLLISION = 10.f; +const F32 PARCEL_COLLISION_DRAW_SECS_ON_PROXIMITY = 1.f; + + +// Globals + +U8* LLViewerParcelMgr::sPackedOverlay = NULL; +S32 LLViewerParcelMgr::PARCEL_BAN_LINES_HIDE = 0; +S32 LLViewerParcelMgr::PARCEL_BAN_LINES_ON_COLLISION = 1; +S32 LLViewerParcelMgr::PARCEL_BAN_LINES_ON_PROXIMITY = 2; + +LLUUID gCurrentMovieID = LLUUID::null; + +LLPointer sBlockedImage; +LLPointer sPassImage; + +// Local functions +void callback_start_music(S32 option, void* data); +void optionally_prepare_video(const LLParcel *parcelp); +void callback_prepare_video(S32 option, void* data); +void prepare_video(const LLParcel *parcelp); +void start_video(const LLParcel *parcelp); +void stop_video(); +bool callback_god_force_owner(const LLSD&, const LLSD&); + +struct LLGodForceOwnerData +{ + LLUUID mOwnerID; + S32 mLocalID; + LLHost mHost; + + LLGodForceOwnerData( + const LLUUID& owner_id, + S32 local_parcel_id, + const LLHost& host) : + mOwnerID(owner_id), + mLocalID(local_parcel_id), + mHost(host) {} +}; + +// +// Methods +// +LLViewerParcelMgr::LLViewerParcelMgr() +: mSelected(false), + mRequestResult(0), + mWestSouth(), + mEastNorth(), + mSelectedDwell(DWELL_NAN), + mAgentParcelSequenceID(-1), + mHoverRequestResult(0), + mHoverWestSouth(), + mHoverEastNorth(), + mTeleportInProgressPosition(), + mRenderCollision(false), + mRenderSelection(true), + mCollisionBanned(0), + mCollisionTimer(), + mMediaParcelId(0), + mMediaRegionId(0) +{ + mCurrentParcel = new LLParcel(); + mCurrentParcelSelection = new LLParcelSelection(mCurrentParcel); + mFloatingParcelSelection = new LLParcelSelection(mCurrentParcel); + + mAgentParcel = new LLParcel(); + mHoverParcel = new LLParcel(); + mCollisionParcel = new LLParcel(); + + mParcelsPerEdge = S32( REGION_WIDTH_METERS / PARCEL_GRID_STEP_METERS ); + mHighlightSegments = new U8[(mParcelsPerEdge+1)*(mParcelsPerEdge+1)]; + resetSegments(mHighlightSegments); + + mCollisionSegments = new U8[(mParcelsPerEdge+1)*(mParcelsPerEdge+1)]; + resetSegments(mCollisionSegments); + + // JC: Resolved a merge conflict here, eliminated + // mBlockedImage->setAddressMode(LLTexUnit::TAM_WRAP); + // because it is done in llviewertexturelist.cpp + mBlockedImage = LLViewerTextureManager::getFetchedTextureFromFile("world/NoEntryLines.png", FTT_LOCAL_FILE, true, LLGLTexture::BOOST_UI); + mPassImage = LLViewerTextureManager::getFetchedTextureFromFile("world/NoEntryPassLines.png", FTT_LOCAL_FILE, true, LLGLTexture::BOOST_UI); + + S32 overlay_size = mParcelsPerEdge * mParcelsPerEdge / PARCEL_OVERLAY_CHUNKS; + sPackedOverlay = new U8[overlay_size]; + + mAgentParcelOverlay = new U8[mParcelsPerEdge * mParcelsPerEdge]; + S32 i; + for (i = 0; i < mParcelsPerEdge * mParcelsPerEdge; i++) + { + mAgentParcelOverlay[i] = 0; + } + + mTeleportInProgress = true; // the initial parcel update is treated like teleport +} + + +LLViewerParcelMgr::~LLViewerParcelMgr() +{ + mCurrentParcelSelection->setParcel(NULL); + mCurrentParcelSelection = NULL; + + mFloatingParcelSelection->setParcel(NULL); + mFloatingParcelSelection = NULL; + + delete mCurrentParcel; + mCurrentParcel = NULL; + + delete mAgentParcel; + mAgentParcel = NULL; + + delete mCollisionParcel; + mCollisionParcel = NULL; + + delete mHoverParcel; + mHoverParcel = NULL; + + delete[] mHighlightSegments; + mHighlightSegments = NULL; + + delete[] mCollisionSegments; + mCollisionSegments = NULL; + + delete[] sPackedOverlay; + sPackedOverlay = NULL; + + delete[] mAgentParcelOverlay; + mAgentParcelOverlay = NULL; + + sBlockedImage = NULL; + sPassImage = NULL; +} + +void LLViewerParcelMgr::dump() +{ + LL_INFOS() << "Parcel Manager Dump" << LL_ENDL; + LL_INFOS() << "mSelected " << S32(mSelected) << LL_ENDL; + LL_INFOS() << "Selected parcel: " << LL_ENDL; + LL_INFOS() << mWestSouth << " to " << mEastNorth << LL_ENDL; + mCurrentParcel->dump(); + LL_INFOS() << "banning " << mCurrentParcel->mBanList.size() << LL_ENDL; + + LLAccessEntry::map::const_iterator cit = mCurrentParcel->mBanList.begin(); + LLAccessEntry::map::const_iterator end = mCurrentParcel->mBanList.end(); + for ( ; cit != end; ++cit) + { + LL_INFOS() << "ban id " << (*cit).first << LL_ENDL; + } + LL_INFOS() << "Hover parcel:" << LL_ENDL; + mHoverParcel->dump(); + LL_INFOS() << "Agent parcel:" << LL_ENDL; + mAgentParcel->dump(); +} + + +LLViewerRegion* LLViewerParcelMgr::getSelectionRegion() +{ + return LLWorld::getInstance()->getRegionFromPosGlobal( mWestSouth ); +} + + +void LLViewerParcelMgr::getDisplayInfo(S32* area_out, S32* claim_out, + S32* rent_out, + bool* for_sale_out, + F32* dwell_out) +{ + S32 area = 0; + S32 price = 0; + S32 rent = 0; + bool for_sale = false; + F32 dwell = DWELL_NAN; + + if (mSelected) + { + if (mCurrentParcelSelection->mSelectedMultipleOwners) + { + area = mCurrentParcelSelection->getClaimableArea(); + } + else + { + area = getSelectedArea(); + } + + if (mCurrentParcel->getForSale()) + { + price = mCurrentParcel->getSalePrice(); + for_sale = true; + } + else + { + price = area * mCurrentParcel->getClaimPricePerMeter(); + for_sale = false; + } + + rent = mCurrentParcel->getTotalRent(); + + dwell = mSelectedDwell; + } + + *area_out = area; + *claim_out = price; + *rent_out = rent; + *for_sale_out = for_sale; + *dwell_out = dwell; +} + +S32 LLViewerParcelMgr::getSelectedArea() const +{ + S32 rv = 0; + if(mSelected && mCurrentParcel && mCurrentParcelSelection->mWholeParcelSelected) + { + rv = mCurrentParcel->getArea(); + } + else if(mSelected) + { + F64 width = mEastNorth.mdV[VX] - mWestSouth.mdV[VX]; + F64 height = mEastNorth.mdV[VY] - mWestSouth.mdV[VY]; + F32 area = (F32)(width * height); + rv = ll_round(area); + } + return rv; +} + +void LLViewerParcelMgr::resetSegments(U8* segments) +{ + S32 i; + S32 count = (mParcelsPerEdge+1)*(mParcelsPerEdge+1); + for (i = 0; i < count; i++) + { + segments[i] = 0x0; + } +} + + +void LLViewerParcelMgr::writeHighlightSegments(F32 west, F32 south, F32 east, + F32 north) +{ + S32 x, y; + S32 min_x = ll_round( west / PARCEL_GRID_STEP_METERS ); + S32 max_x = ll_round( east / PARCEL_GRID_STEP_METERS ); + S32 min_y = ll_round( south / PARCEL_GRID_STEP_METERS ); + S32 max_y = ll_round( north / PARCEL_GRID_STEP_METERS ); + + const S32 STRIDE = mParcelsPerEdge+1; + + // south edge + y = min_y; + for (x = min_x; x < max_x; x++) + { + // exclusive OR means that writing to this segment twice + // will turn it off + mHighlightSegments[x + y*STRIDE] ^= SOUTH_MASK; + } + + // west edge + x = min_x; + for (y = min_y; y < max_y; y++) + { + mHighlightSegments[x + y*STRIDE] ^= WEST_MASK; + } + + // north edge - draw the south border on the y+1'th cell, + // which given C-style arrays, is item foo[max_y] + y = max_y; + for (x = min_x; x < max_x; x++) + { + mHighlightSegments[x + y*STRIDE] ^= SOUTH_MASK; + } + + // east edge - draw west border on x+1'th cell + x = max_x; + for (y = min_y; y < max_y; y++) + { + mHighlightSegments[x + y*STRIDE] ^= WEST_MASK; + } +} + + +void LLViewerParcelMgr::writeSegmentsFromBitmap(U8* bitmap, U8* segments) +{ + S32 x; + S32 y; + const S32 IN_STRIDE = mParcelsPerEdge; + const S32 OUT_STRIDE = mParcelsPerEdge+1; + + for (y = 0; y < IN_STRIDE; y++) + { + x = 0; + while( x < IN_STRIDE ) + { + U8 byte = bitmap[ (x + y*IN_STRIDE) / 8 ]; + + S32 bit; + for (bit = 0; bit < 8; bit++) + { + if (byte & (1 << bit) ) + { + S32 out = x+y*OUT_STRIDE; + + // This and one above it + segments[out] ^= SOUTH_MASK; + segments[out+OUT_STRIDE] ^= SOUTH_MASK; + + // This and one to the right + segments[out] ^= WEST_MASK; + segments[out+1] ^= WEST_MASK; + } + x++; + } + } + } +} + + +void LLViewerParcelMgr::writeAgentParcelFromBitmap(U8* bitmap) +{ + S32 x; + S32 y; + const S32 IN_STRIDE = mParcelsPerEdge; + + for (y = 0; y < IN_STRIDE; y++) + { + x = 0; + while( x < IN_STRIDE ) + { + U8 byte = bitmap[ (x + y*IN_STRIDE) / 8 ]; + + S32 bit; + for (bit = 0; bit < 8; bit++) + { + if (byte & (1 << bit) ) + { + mAgentParcelOverlay[x+y*IN_STRIDE] = 1; + } + else + { + mAgentParcelOverlay[x+y*IN_STRIDE] = 0; + } + x++; + } + } + } +} + + +// Given a point, find the PARCEL_GRID_STEP x PARCEL_GRID_STEP block +// containing it and select that. +LLParcelSelectionHandle LLViewerParcelMgr::selectParcelAt(const LLVector3d& pos_global) +{ + LLVector3d southwest = pos_global; + LLVector3d northeast = pos_global; + + southwest -= LLVector3d( PARCEL_GRID_STEP_METERS/2, PARCEL_GRID_STEP_METERS/2, 0 ); + southwest.mdV[VX] = ll_round( southwest.mdV[VX], (F64)PARCEL_GRID_STEP_METERS ); + southwest.mdV[VY] = ll_round( southwest.mdV[VY], (F64)PARCEL_GRID_STEP_METERS ); + + northeast += LLVector3d( PARCEL_GRID_STEP_METERS/2, PARCEL_GRID_STEP_METERS/2, 0 ); + northeast.mdV[VX] = ll_round( northeast.mdV[VX], (F64)PARCEL_GRID_STEP_METERS ); + northeast.mdV[VY] = ll_round( northeast.mdV[VY], (F64)PARCEL_GRID_STEP_METERS ); + + // Snap to parcel + return selectLand( southwest, northeast, true ); +} + + +// Tries to select the parcel inside the rectangle +LLParcelSelectionHandle LLViewerParcelMgr::selectParcelInRectangle() +{ + return selectLand(mWestSouth, mEastNorth, true); +} + + +void LLViewerParcelMgr::selectCollisionParcel() +{ + // BUG: Claim to be in the agent's region + mWestSouth = gAgent.getRegion()->getOriginGlobal(); + mEastNorth = mWestSouth; + mEastNorth += LLVector3d(PARCEL_GRID_STEP_METERS, PARCEL_GRID_STEP_METERS, 0.0); + + // BUG: must be in the sim you are in + LLMessageSystem *msg = gMessageSystem; + msg->newMessageFast(_PREHASH_ParcelPropertiesRequestByID); + msg->nextBlockFast(_PREHASH_AgentID); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); + msg->nextBlockFast(_PREHASH_ParcelData); + msg->addS32Fast(_PREHASH_SequenceID, SELECTED_PARCEL_SEQ_ID ); + msg->addS32Fast(_PREHASH_LocalID, mCollisionParcel->getLocalID() ); + gAgent.sendReliableMessage(); + + mRequestResult = PARCEL_RESULT_NO_DATA; + + // Hack: Copy some data over temporarily + mCurrentParcel->setName( mCollisionParcel->getName() ); + mCurrentParcel->setDesc( mCollisionParcel->getDesc() ); + mCurrentParcel->setPassPrice(mCollisionParcel->getPassPrice()); + mCurrentParcel->setPassHours(mCollisionParcel->getPassHours()); + + // clear the list of segments to prevent flashing + resetSegments(mHighlightSegments); + + mFloatingParcelSelection->setParcel(mCurrentParcel); + mCurrentParcelSelection->setParcel(NULL); + mCurrentParcelSelection = new LLParcelSelection(mCurrentParcel); + + mSelected = true; + mCurrentParcelSelection->mWholeParcelSelected = true; + notifyObservers(); + return; +} + + +// snap_selection = auto-select the hit parcel, if there is exactly one +LLParcelSelectionHandle LLViewerParcelMgr::selectLand(const LLVector3d &corner1, const LLVector3d &corner2, + bool snap_selection) +{ + sanitize_corners( corner1, corner2, mWestSouth, mEastNorth ); + + // ...x isn't more than one meter away + F32 delta_x = getSelectionWidth(); + if (delta_x * delta_x <= 1.f * 1.f) + { + mSelected = false; + notifyObservers(); + return NULL; + } + + // ...y isn't more than one meter away + F32 delta_y = getSelectionHeight(); + if (delta_y * delta_y <= 1.f * 1.f) + { + mSelected = false; + notifyObservers(); + return NULL; + } + + // Can't select across region boundary + // We need to pull in the upper right corner by a little bit to allow + // selection up to the x = 256 or y = 256 edge. + LLVector3d east_north_region_check( mEastNorth ); + east_north_region_check.mdV[VX] -= 0.5; + east_north_region_check.mdV[VY] -= 0.5; + + LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosGlobal(mWestSouth); + LLViewerRegion *region_other = LLWorld::getInstance()->getRegionFromPosGlobal( east_north_region_check ); + + if(!region) + { + // just in case they somehow selected no land. + mSelected = false; + return NULL; + } + + if (region != region_other) + { + LLNotificationsUtil::add("CantSelectLandFromMultipleRegions"); + mSelected = false; + notifyObservers(); + return NULL; + } + + // Build region global copies of corners + LLVector3 wsb_region = region->getPosRegionFromGlobal( mWestSouth ); + LLVector3 ent_region = region->getPosRegionFromGlobal( mEastNorth ); + + // Send request message + LLMessageSystem *msg = gMessageSystem; + msg->newMessageFast(_PREHASH_ParcelPropertiesRequest); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); + msg->nextBlockFast(_PREHASH_ParcelData); + msg->addS32Fast(_PREHASH_SequenceID, SELECTED_PARCEL_SEQ_ID ); + msg->addF32Fast(_PREHASH_West, wsb_region.mV[VX] ); + msg->addF32Fast(_PREHASH_South, wsb_region.mV[VY] ); + msg->addF32Fast(_PREHASH_East, ent_region.mV[VX] ); + msg->addF32Fast(_PREHASH_North, ent_region.mV[VY] ); + msg->addBOOL("SnapSelection", snap_selection); + msg->sendReliable( region->getHost() ); + + mRequestResult = PARCEL_RESULT_NO_DATA; + + mFloatingParcelSelection->setParcel(mCurrentParcel); + mCurrentParcelSelection->setParcel(NULL); + mCurrentParcelSelection = new LLParcelSelection(mCurrentParcel); + + mSelected = true; + mCurrentParcelSelection->mWholeParcelSelected = snap_selection; + notifyObservers(); + return mCurrentParcelSelection; +} + +void LLViewerParcelMgr::deselectUnused() +{ + // no more outstanding references to this selection, other than our own + if (mCurrentParcelSelection->getNumRefs() == 1 && mFloatingParcelSelection->getNumRefs() == 1) + { + deselectLand(); + } +} + +void LLViewerParcelMgr::deselectLand() +{ + if (mSelected) + { + mSelected = false; + + // Invalidate the selected parcel + mCurrentParcel->setLocalID(-1); + mCurrentParcel->mAccessList.clear(); + mCurrentParcel->mBanList.clear(); + //mCurrentParcel->mRenterList.reset(); + + mSelectedDwell = DWELL_NAN; + + // invalidate parcel selection so that existing users of this selection can clean up + mCurrentParcelSelection->setParcel(NULL); + mFloatingParcelSelection->setParcel(NULL); + // create new parcel selection + mCurrentParcelSelection = new LLParcelSelection(mCurrentParcel); + + notifyObservers(); // Notify observers *after* changing the parcel selection + } +} + + +void LLViewerParcelMgr::addObserver(LLParcelObserver* observer) +{ + mObservers.push_back(observer); +} + + +void LLViewerParcelMgr::removeObserver(LLParcelObserver* observer) +{ + vector_replace_with_last(mObservers, observer); +} + + +// Call this method when it's time to update everyone on a new state. +// Copy the list because an observer could respond by removing itself +// from the list. +void LLViewerParcelMgr::notifyObservers() +{ + std::vector observers; + S32 count = mObservers.size(); + S32 i; + for(i = 0; i < count; ++i) + { + observers.push_back(mObservers.at(i)); + } + for(i = 0; i < count; ++i) + { + observers.at(i)->changed(); + } +} + + +// +// ACCESSORS +// +bool LLViewerParcelMgr::selectionEmpty() const +{ + return !mSelected; +} + + +LLParcelSelectionHandle LLViewerParcelMgr::getParcelSelection() const +{ + return mCurrentParcelSelection; +} + +LLParcelSelectionHandle LLViewerParcelMgr::getFloatingParcelSelection() const +{ + return mFloatingParcelSelection; +} + +LLParcel *LLViewerParcelMgr::getAgentParcel() const +{ + return mAgentParcel; +} + + +LLParcel * LLViewerParcelMgr::getAgentOrSelectedParcel() const +{ + LLParcel *parcel(nullptr); + + LLParcelSelectionHandle sel_handle(getFloatingParcelSelection()); + if (sel_handle) + { + LLParcelSelection *selection(sel_handle.get()); + if (selection) + { + parcel = selection->getParcel(); + if (parcel && (parcel->getLocalID() == INVALID_PARCEL_ID)) + { + parcel = NULL; + } + } + } + + if (!parcel) + parcel = LLViewerParcelMgr::instance().getAgentParcel(); + + return parcel; +} + +// Return whether the agent can build on the land they are on +bool LLViewerParcelMgr::allowAgentBuild() const +{ + if (mAgentParcel) + { + return (gAgent.isGodlike() || + (mAgentParcel->allowModifyBy(gAgent.getID(), gAgent.getGroupID())) || + (isParcelOwnedByAgent(mAgentParcel, GP_LAND_ALLOW_CREATE))); + } + else + { + return gAgent.isGodlike(); + } +} + +// Return whether anyone can build on the given parcel +bool LLViewerParcelMgr::allowAgentBuild(const LLParcel* parcel) const +{ + return parcel->getAllowModify(); +} + +bool LLViewerParcelMgr::allowAgentVoice() const +{ + return allowAgentVoice(gAgent.getRegion(), mAgentParcel); +} + +bool LLViewerParcelMgr::allowAgentVoice(const LLViewerRegion* region, const LLParcel* parcel) const +{ + return region && region->isVoiceEnabled() + && parcel && parcel->getParcelFlagAllowVoice(); +} + +bool LLViewerParcelMgr::allowAgentFly(const LLViewerRegion* region, const LLParcel* parcel) const +{ + return region && !region->getBlockFly() + && parcel && parcel->getAllowFly(); +} + +// Can the agent be pushed around by LLPushObject? +bool LLViewerParcelMgr::allowAgentPush(const LLViewerRegion* region, const LLParcel* parcel) const +{ + return region && !region->getRestrictPushObject() + && parcel && !parcel->getRestrictPushObject(); +} + +bool LLViewerParcelMgr::allowAgentScripts(const LLViewerRegion* region, const LLParcel* parcel) const +{ + // *NOTE: This code does not take into account group-owned parcels + // and the flag to allow group-owned scripted objects to run. + // This mirrors the traditional menu bar parcel icon code, but is not + // technically correct. + return region + && !region->getRegionFlag(REGION_FLAGS_SKIP_SCRIPTS) + && !region->getRegionFlag(REGION_FLAGS_ESTATE_SKIP_SCRIPTS) + && parcel + && parcel->getAllowOtherScripts(); +} + +bool LLViewerParcelMgr::allowAgentDamage(const LLViewerRegion* region, const LLParcel* parcel) const +{ + return (region && region->getAllowDamage()) + || (parcel && parcel->getAllowDamage()); +} + +bool LLViewerParcelMgr::isOwnedAt(const LLVector3d& pos_global) const +{ + LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal( pos_global ); + if (!region) return false; + + LLViewerParcelOverlay* overlay = region->getParcelOverlay(); + if (!overlay) return false; + + LLVector3 pos_region = region->getPosRegionFromGlobal( pos_global ); + + return overlay->isOwned( pos_region ); +} + +bool LLViewerParcelMgr::isOwnedSelfAt(const LLVector3d& pos_global) const +{ + LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal( pos_global ); + if (!region) return false; + + LLViewerParcelOverlay* overlay = region->getParcelOverlay(); + if (!overlay) return false; + + LLVector3 pos_region = region->getPosRegionFromGlobal( pos_global ); + + return overlay->isOwnedSelf( pos_region ); +} + +bool LLViewerParcelMgr::isOwnedOtherAt(const LLVector3d& pos_global) const +{ + LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal( pos_global ); + if (!region) return false; + + LLViewerParcelOverlay* overlay = region->getParcelOverlay(); + if (!overlay) return false; + + LLVector3 pos_region = region->getPosRegionFromGlobal( pos_global ); + + return overlay->isOwnedOther( pos_region ); +} + +bool LLViewerParcelMgr::isSoundLocal(const LLVector3d& pos_global) const +{ + LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal( pos_global ); + if (!region) return false; + + LLViewerParcelOverlay* overlay = region->getParcelOverlay(); + if (!overlay) return false; + + LLVector3 pos_region = region->getPosRegionFromGlobal( pos_global ); + + return overlay->isSoundLocal( pos_region ); +} + +bool LLViewerParcelMgr::canHearSound(const LLVector3d &pos_global) const +{ + bool in_agent_parcel = inAgentParcel(pos_global); + + if (in_agent_parcel) + { + // In same parcel as the agent + return true; + } + else + { + if (LLViewerParcelMgr::getInstance()->getAgentParcel()->getSoundLocal()) + { + // Not in same parcel, and agent parcel only has local sound + return false; + } + else if (LLViewerParcelMgr::getInstance()->isSoundLocal(pos_global)) + { + // Not in same parcel, and target parcel only has local sound + return false; + } + else + { + // Not in same parcel, but neither are local sound + return true; + } + } +} + + +bool LLViewerParcelMgr::inAgentParcel(const LLVector3d &pos_global) const +{ + LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal(pos_global); + LLViewerRegion* agent_region = gAgent.getRegion(); + if (!region || !agent_region) + return false; + + if (region != agent_region) + { + // Can't be in the agent parcel if you're not in the same region. + return false; + } + + LLVector3 pos_region = agent_region->getPosRegionFromGlobal(pos_global); + S32 row = S32(pos_region.mV[VY] / PARCEL_GRID_STEP_METERS); + S32 column = S32(pos_region.mV[VX] / PARCEL_GRID_STEP_METERS); + + if (mAgentParcelOverlay[row*mParcelsPerEdge + column]) + { + return true; + } + else + { + return false; + } +} + +// Returns NULL when there is no valid data. +LLParcel* LLViewerParcelMgr::getHoverParcel() const +{ + if (mHoverRequestResult == PARCEL_RESULT_SUCCESS) + { + return mHoverParcel; + } + else + { + return NULL; + } +} + +// Returns NULL when there is no valid data. +LLParcel* LLViewerParcelMgr::getCollisionParcel() const +{ + if (mRenderCollision) + { + return mCollisionParcel; + } + else + { + return NULL; + } +} + +// +// UTILITIES +// + +void LLViewerParcelMgr::render() +{ + if (mSelected && mRenderSelection && gSavedSettings.getBOOL("RenderParcelSelection") && !gDisconnected) + { + // Rendering is done in agent-coordinates, so need to supply + // an appropriate offset to the render code. + LLViewerRegion* regionp = LLWorld::getInstance()->getRegionFromPosGlobal(mWestSouth); + if (!regionp) return; + + renderHighlightSegments(mHighlightSegments, regionp); + } +} + + +void LLViewerParcelMgr::renderParcelCollision() +{ + static LLCachedControl ban_lines_mode(gSavedSettings , "ShowBanLines" , PARCEL_BAN_LINES_ON_COLLISION); + + // check for expiration + F32 expiration = (ban_lines_mode == PARCEL_BAN_LINES_ON_PROXIMITY) + ? PARCEL_COLLISION_DRAW_SECS_ON_PROXIMITY + : PARCEL_BAN_LINES_DRAW_SECS_ON_COLLISION; + if (mCollisionTimer.getElapsedTimeF32() > expiration) + { + mRenderCollision = false; + } + + if (mRenderCollision && ban_lines_mode != PARCEL_BAN_LINES_HIDE) + { + LLViewerRegion* regionp = gAgent.getRegion(); + if (regionp) + { + bool use_pass = mCollisionParcel->getParcelFlag(PF_USE_PASS_LIST); + renderCollisionSegments(mCollisionSegments, use_pass, regionp); + } + } +} + + +void LLViewerParcelMgr::sendParcelAccessListRequest(U32 flags) +{ + if (!mSelected) + { + return; + } + + LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosGlobal( mWestSouth ); + if (!region) return; + + LLMessageSystem *msg = gMessageSystem; + + + if (flags & AL_BAN) + { + mCurrentParcel->mBanList.clear(); + } + if (flags & AL_ACCESS) + { + mCurrentParcel->mAccessList.clear(); + } + if (flags & AL_ALLOW_EXPERIENCE) + { + mCurrentParcel->clearExperienceKeysByType(EXPERIENCE_KEY_TYPE_ALLOWED); + } + if (flags & AL_BLOCK_EXPERIENCE) + { + mCurrentParcel->clearExperienceKeysByType(EXPERIENCE_KEY_TYPE_BLOCKED); + } + + // Only the headers differ + msg->newMessageFast(_PREHASH_ParcelAccessListRequest); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_Data); + msg->addS32Fast(_PREHASH_SequenceID, 0); + msg->addU32Fast(_PREHASH_Flags, flags); + msg->addS32("LocalID", mCurrentParcel->getLocalID() ); + msg->sendReliable( region->getHost() ); +} + + +void LLViewerParcelMgr::sendParcelDwellRequest() +{ + if (!mSelected) + { + return; + } + + LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosGlobal( mWestSouth ); + if (!region) return; + + LLMessageSystem *msg = gMessageSystem; + + // Only the headers differ + msg->newMessage("ParcelDwellRequest"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID() ); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->nextBlock("Data"); + msg->addS32("LocalID", mCurrentParcel->getLocalID()); + msg->addUUID("ParcelID", LLUUID::null); // filled in on simulator + msg->sendReliable( region->getHost() ); +} + + +void LLViewerParcelMgr::sendParcelGodForceOwner(const LLUUID& owner_id) +{ + if (!mSelected) + { + LLNotificationsUtil::add("CannotSetLandOwnerNothingSelected"); + return; + } + + LL_INFOS("ParcelMgr") << "Claiming " << mWestSouth << " to " << mEastNorth << LL_ENDL; + + // BUG: Only works for the region containing mWestSouthBottom + LLVector3d east_north_region_check( mEastNorth ); + east_north_region_check.mdV[VX] -= 0.5; + east_north_region_check.mdV[VY] -= 0.5; + + LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosGlobal( mWestSouth ); + if (!region) + { + // TODO: Add a force owner version of this alert. + LLNotificationsUtil::add("CannotContentifyNoRegion"); + return; + } + + // BUG: Make work for cross-region selections + LLViewerRegion *region2 = LLWorld::getInstance()->getRegionFromPosGlobal( east_north_region_check ); + if (region != region2) + { + LLNotificationsUtil::add("CannotSetLandOwnerMultipleRegions"); + return; + } + + LL_INFOS("ParcelMgr") << "Region " << region->getOriginGlobal() << LL_ENDL; + + LLSD payload; + payload["owner_id"] = owner_id; + payload["parcel_local_id"] = mCurrentParcel->getLocalID(); + payload["region_host"] = region->getHost().getIPandPort(); + LLNotification::Params params("ForceOwnerAuctionWarning"); + params.payload(payload).functor.function(callback_god_force_owner); + + if(mCurrentParcel->getAuctionID()) + { + LLNotifications::instance().add(params); + } + else + { + LLNotifications::instance().forceResponse(params, 0); + } +} + +bool callback_god_force_owner(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if(0 == option) + { + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("ParcelGodForceOwner"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->nextBlock("Data"); + msg->addUUID("OwnerID", notification["payload"]["owner_id"].asUUID()); + msg->addS32( "LocalID", notification["payload"]["parcel_local_id"].asInteger()); + msg->sendReliable(LLHost(notification["payload"]["region_host"].asString())); + } + return false; +} + +void LLViewerParcelMgr::sendParcelGodForceToContent() +{ + if (!mSelected) + { + LLNotificationsUtil::add("CannotContentifyNothingSelected"); + return; + } + LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal( mWestSouth ); + if (!region) + { + LLNotificationsUtil::add("CannotContentifyNoRegion"); + return; + } + + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("ParcelGodMarkAsContent"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->nextBlock("ParcelData"); + msg->addS32("LocalID", mCurrentParcel->getLocalID()); + msg->sendReliable(region->getHost()); +} + +void LLViewerParcelMgr::sendParcelRelease() +{ + if (!mSelected) + { + LLNotificationsUtil::add("CannotReleaseLandNothingSelected"); + return; + } + + LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosGlobal( mWestSouth ); + if (!region) + { + LLNotificationsUtil::add("CannotReleaseLandNoRegion"); + return; + } + + //U32 flags = PR_NONE; + //if (god_force) flags |= PR_GOD_FORCE; + + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("ParcelRelease"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID() ); + msg->addUUID("SessionID", gAgent.getSessionID() ); + msg->nextBlock("Data"); + msg->addS32("LocalID", mCurrentParcel->getLocalID() ); + //msg->addU32("Flags", flags); + msg->sendReliable( region->getHost() ); + + // Blitz selection, since the parcel might be non-rectangular, and + // we won't have appropriate parcel information. + deselectLand(); +} + +class LLViewerParcelMgr::ParcelBuyInfo +{ +public: + LLUUID mAgent; + LLUUID mSession; + LLUUID mGroup; + bool mIsGroupOwned; + bool mRemoveContribution; + bool mIsClaim; + LLHost mHost; + + // for parcel buys + S32 mParcelID; + S32 mPrice; + S32 mArea; + + // for land claims + F32 mWest; + F32 mSouth; + F32 mEast; + F32 mNorth; +}; + +LLViewerParcelMgr::ParcelBuyInfo* LLViewerParcelMgr::setupParcelBuy( + const LLUUID& agent_id, + const LLUUID& session_id, + const LLUUID& group_id, + bool is_group_owned, + bool is_claim, + bool remove_contribution) +{ + if (!mSelected || !mCurrentParcel) + { + LLNotificationsUtil::add("CannotBuyLandNothingSelected"); + return NULL; + } + + LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosGlobal( mWestSouth ); + if (!region) + { + LLNotificationsUtil::add("CannotBuyLandNoRegion"); + return NULL; + } + + if (is_claim) + { + LL_INFOS("ParcelMgr") << "Claiming " << mWestSouth << " to " << mEastNorth << LL_ENDL; + LL_INFOS("ParcelMgr") << "Region " << region->getOriginGlobal() << LL_ENDL; + + // BUG: Only works for the region containing mWestSouthBottom + LLVector3d east_north_region_check( mEastNorth ); + east_north_region_check.mdV[VX] -= 0.5; + east_north_region_check.mdV[VY] -= 0.5; + + LLViewerRegion *region2 = LLWorld::getInstance()->getRegionFromPosGlobal( east_north_region_check ); + + if (region != region2) + { + LLNotificationsUtil::add("CantBuyLandAcrossMultipleRegions"); + return NULL; + } + } + + + ParcelBuyInfo* info = new ParcelBuyInfo; + + info->mAgent = agent_id; + info->mSession = session_id; + info->mGroup = group_id; + info->mIsGroupOwned = is_group_owned; + info->mIsClaim = is_claim; + info->mRemoveContribution = remove_contribution; + info->mHost = region->getHost(); + info->mPrice = mCurrentParcel->getSalePrice(); + info->mArea = mCurrentParcel->getArea(); + + if (!is_claim) + { + info->mParcelID = mCurrentParcel->getLocalID(); + } + else + { + // BUG: Make work for cross-region selections + LLVector3 west_south_bottom_region = region->getPosRegionFromGlobal( mWestSouth ); + LLVector3 east_north_top_region = region->getPosRegionFromGlobal( mEastNorth ); + + info->mWest = west_south_bottom_region.mV[VX]; + info->mSouth = west_south_bottom_region.mV[VY]; + info->mEast = east_north_top_region.mV[VX]; + info->mNorth = east_north_top_region.mV[VY]; + } + + return info; +} + +void LLViewerParcelMgr::sendParcelBuy(ParcelBuyInfo* info) +{ + // send the message + LLMessageSystem* msg = gMessageSystem; + msg->newMessage(info->mIsClaim ? "ParcelClaim" : "ParcelBuy"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", info->mAgent); + msg->addUUID("SessionID", info->mSession); + msg->nextBlock("Data"); + msg->addUUID("GroupID", info->mGroup); + msg->addBOOL("IsGroupOwned", info->mIsGroupOwned); + if (!info->mIsClaim) + { + msg->addBOOL("RemoveContribution", info->mRemoveContribution); + msg->addS32("LocalID", info->mParcelID); + } + msg->addBOOL("Final", true); // don't allow escrow buys + if (info->mIsClaim) + { + msg->nextBlock("ParcelData"); + msg->addF32("West", info->mWest); + msg->addF32("South", info->mSouth); + msg->addF32("East", info->mEast); + msg->addF32("North", info->mNorth); + } + else // ParcelBuy + { + msg->nextBlock("ParcelData"); + msg->addS32("Price",info->mPrice); + msg->addS32("Area",info->mArea); + } + msg->sendReliable(info->mHost); +} + +void LLViewerParcelMgr::deleteParcelBuy(ParcelBuyInfo* *info) +{ + // Must be here because ParcelBuyInfo is local to this .cpp file + delete *info; + *info = NULL; +} + +void LLViewerParcelMgr::sendParcelDeed(const LLUUID& group_id) +{ + if (!mSelected || !mCurrentParcel) + { + LLNotificationsUtil::add("CannotDeedLandNothingSelected"); + return; + } + if(group_id.isNull()) + { + LLNotificationsUtil::add("CannotDeedLandNoGroup"); + return; + } + LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosGlobal( mWestSouth ); + if (!region) + { + LLNotificationsUtil::add("CannotDeedLandNoRegion"); + return; + } + + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("ParcelDeedToGroup"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID() ); + msg->addUUID("SessionID", gAgent.getSessionID() ); + msg->nextBlock("Data"); + msg->addUUID("GroupID", group_id ); + msg->addS32("LocalID", mCurrentParcel->getLocalID() ); + //msg->addU32("JoinNeighbors", join); + msg->sendReliable( region->getHost() ); +} + + +/* +// *NOTE: We cannot easily make landmarks at global positions because +// global positions probably refer to a sim/local combination which +// can move over time. We could implement this by looking up the +// region global x,y, but it's easier to take it out for now. +void LLViewerParcelMgr::makeLandmarkAtSelection() +{ + // Don't create for parcels you don't own + if (gAgent.getID() != mCurrentParcel->getOwnerID()) + { + return; + } + + LLVector3d global_center(mWestSouth); + global_center += mEastNorth; + global_center *= 0.5f; + + LLViewerRegion* region; + region = LLWorld::getInstance()->getRegionFromPosGlobal(global_center); + + LLVector3 west_south_bottom_region = region->getPosRegionFromGlobal( mWestSouth ); + LLVector3 east_north_top_region = region->getPosRegionFromGlobal( mEastNorth ); + + std::string name("My Land"); + std::string buffer; + S32 pos_x = (S32)floor((west_south_bottom_region.mV[VX] + east_north_top_region.mV[VX]) / 2.0f); + S32 pos_y = (S32)floor((west_south_bottom_region.mV[VY] + east_north_top_region.mV[VY]) / 2.0f); + buffer = llformat("%s in %s (%d, %d)", + name.c_str(), + region->getName().c_str(), + pos_x, pos_y); + name.assign(buffer); + + create_landmark(name, "Claimed land", global_center); +} +*/ + +const std::string& LLViewerParcelMgr::getAgentParcelName() const +{ + return mAgentParcel->getName(); +} + + +const S32 LLViewerParcelMgr::getAgentParcelId() const +{ + if (mAgentParcel) + return mAgentParcel->getLocalID(); + return INVALID_PARCEL_ID; +} + +void LLViewerParcelMgr::sendParcelPropertiesUpdate(LLParcel* parcel, bool use_agent_region) +{ + if(!parcel) + return; + + LLViewerRegion *region = use_agent_region ? gAgent.getRegion() : LLWorld::getInstance()->getRegionFromPosGlobal( mWestSouth ); + if (!region) + return; + + LLSD body; + std::string url = region->getCapability("ParcelPropertiesUpdate"); + if (!url.empty()) + { + // request new properties update from simulator + U32 message_flags = 0x01; + body["flags"] = ll_sd_from_U32(message_flags); + parcel->packMessage(body); + LL_INFOS("ParcelMgr") << "Sending parcel properties update via capability to: " + << url << LL_ENDL; + + LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, body, + "Parcel Properties sent to sim.", "Parcel Properties failed to send to sim."); + } + else + { + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_ParcelPropertiesUpdate); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ParcelData); + msg->addS32Fast(_PREHASH_LocalID, parcel->getLocalID() ); + + U32 message_flags = 0x01; + msg->addU32("Flags", message_flags); + + parcel->packMessage(msg); + + msg->sendReliable( region->getHost() ); + } +} + + +void LLViewerParcelMgr::setHoverParcel(const LLVector3d& pos) +{ + static U32 last_west, last_south; + static LLUUID last_region; + + // only request parcel info if position has changed outside of the + // last parcel grid step + const U32 west_parcel_step = (U32) floor( pos.mdV[VX] / PARCEL_GRID_STEP_METERS ); + const U32 south_parcel_step = (U32) floor( pos.mdV[VY] / PARCEL_GRID_STEP_METERS ); + + if ((west_parcel_step == last_west) && (south_parcel_step == last_south)) + { + // We are staying in same segment + return; + } + + LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal( pos ); + if (!region) + { + return; + } + + LLUUID region_id = region->getRegionID(); + LLVector3 pos_in_region = region->getPosRegionFromGlobal(pos); + + bool request_properties = false; + if (region_id != last_region) + { + request_properties = true; + } + else + { + // Check if new position is in same parcel. + // This check is not ideal, since it checks by way of straight lines. + // So sometimes (small parcel in the middle of large one) it can + // decide that parcel actually changed, but it still allows to + // reduce amount of requests significantly + + S32 west_parcel_local = (S32)(pos_in_region.mV[VX] / PARCEL_GRID_STEP_METERS); + S32 south_parcel_local = (S32)(pos_in_region.mV[VY] / PARCEL_GRID_STEP_METERS); + + LLViewerParcelOverlay* overlay = region->getParcelOverlay(); + if (!overlay) + { + request_properties = true; + } + while (!request_properties && west_parcel_step < last_west) + { + S32 segment_shift = last_west - west_parcel_step; + request_properties = overlay->parcelLineFlags(south_parcel_local, west_parcel_local + segment_shift) & PARCEL_WEST_LINE; + last_west--; + } + while (!request_properties && south_parcel_step < last_south) + { + S32 segment_shift = last_south - south_parcel_step; + request_properties = overlay->parcelLineFlags(south_parcel_local + segment_shift, west_parcel_local) & PARCEL_SOUTH_LINE; + last_south--; + } + // Note: could have just swapped values, reused first two 'while' and set last_south, last_west separately, + // but this looks to be easier to understand/straightforward/less bulky + while (!request_properties && west_parcel_step > last_west) + { + S32 segment_shift = west_parcel_step - last_west; + request_properties = overlay->parcelLineFlags(south_parcel_local, west_parcel_local - segment_shift + 1) & PARCEL_WEST_LINE; + last_west++; + } + while (!request_properties && south_parcel_step > last_south) + { + S32 segment_shift = south_parcel_step - last_south; + request_properties = overlay->parcelLineFlags(south_parcel_local - segment_shift + 1, west_parcel_local) & PARCEL_SOUTH_LINE; + last_south++; + } + + // if (!request_properties) last_south and last_west will be equal to new values + } + + if (request_properties) + { + last_west = west_parcel_step; + last_south = south_parcel_step; + last_region = region_id; + + LL_DEBUGS("ParcelMgr") << "Requesting parcel properties on hover, for " << pos << LL_ENDL; + + + // Send a rectangle around the point. + // This means the parcel sent back is at least a rectangle around the point, + // which is more efficient for public land. Fewer requests are sent. JC + F32 west = PARCEL_GRID_STEP_METERS * floor(pos_in_region.mV[VX] / PARCEL_GRID_STEP_METERS); + F32 south = PARCEL_GRID_STEP_METERS * floor(pos_in_region.mV[VY] / PARCEL_GRID_STEP_METERS); + + F32 east = west + PARCEL_GRID_STEP_METERS; + F32 north = south + PARCEL_GRID_STEP_METERS; + + // Send request message + LLMessageSystem *msg = gMessageSystem; + msg->newMessageFast(_PREHASH_ParcelPropertiesRequest); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ParcelData); + msg->addS32Fast(_PREHASH_SequenceID, HOVERED_PARCEL_SEQ_ID); + msg->addF32Fast(_PREHASH_West, west); + msg->addF32Fast(_PREHASH_South, south); + msg->addF32Fast(_PREHASH_East, east); + msg->addF32Fast(_PREHASH_North, north); + msg->addBOOL("SnapSelection", false); + msg->sendReliable(region->getHost()); + + mHoverRequestResult = PARCEL_RESULT_NO_DATA; + } +} + + +// static +void LLViewerParcelMgr::processParcelOverlay(LLMessageSystem *msg, void **user) +{ + // Extract the packed overlay information + S32 packed_overlay_size = msg->getSizeFast(_PREHASH_ParcelData, _PREHASH_Data); + + if (packed_overlay_size <= 0) + { + LL_WARNS() << "Overlay size " << packed_overlay_size << LL_ENDL; + return; + } + + S32 parcels_per_edge = LLViewerParcelMgr::getInstance()->mParcelsPerEdge; + S32 expected_size = parcels_per_edge * parcels_per_edge / PARCEL_OVERLAY_CHUNKS; + if (packed_overlay_size != expected_size) + { + LL_WARNS() << "Got parcel overlay size " << packed_overlay_size + << " expecting " << expected_size << LL_ENDL; + return; + } + + S32 sequence_id; + msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_SequenceID, sequence_id); + msg->getBinaryDataFast( + _PREHASH_ParcelData, + _PREHASH_Data, + sPackedOverlay, + expected_size); + + LLHost host = msg->getSender(); + LLViewerRegion *region = LLWorld::getInstance()->getRegion(host); + if (region) + { + region->mParcelOverlay->uncompressLandOverlay( sequence_id, sPackedOverlay ); + } +} + +// static +void LLViewerParcelMgr::processParcelProperties(LLMessageSystem *msg, void **user) +{ + if (LLApp::isExiting() || gDisconnected) + { + LL_DEBUGS("ParcelMgr") << "Ignoring parcel properties, shutting down" << LL_ENDL; + return; + } + + S32 request_result; + S32 sequence_id; + bool snap_selection = false; + S32 self_count = 0; + S32 other_count = 0; + S32 public_count = 0; + S32 local_id; + LLUUID owner_id; + bool is_group_owned; + U32 auction_id = 0; + S32 claim_price_per_meter = 0; + S32 rent_price_per_meter = 0; + S32 claim_date = 0; + LLVector3 aabb_min; + LLVector3 aabb_max; + S32 area = 0; + S32 sw_max_prims = 0; + S32 sw_total_prims = 0; + //LLUUID buyer_id; + U8 status = 0; + S32 max_prims = 0; + S32 total_prims = 0; + S32 owner_prims = 0; + S32 group_prims = 0; + S32 other_prims = 0; + S32 selected_prims = 0; + F32 parcel_prim_bonus = 1.f; + bool region_push_override = false; + bool region_deny_anonymous_override = false; + bool region_deny_identified_override = false; // Deprecated + bool region_deny_transacted_override = false; // Deprecated + bool region_deny_age_unverified_override = false; + bool region_allow_access_override = true; + bool region_allow_environment_override = true; + S32 parcel_environment_version = 0; + bool agent_parcel_update = false; // updating previous(existing) agent parcel + U32 extended_flags = 0; //obscure MOAP + + S32 other_clean_time = 0; + + LLViewerParcelMgr& parcel_mgr = LLViewerParcelMgr::instance(); + + msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_RequestResult, request_result); + msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_SequenceID, sequence_id); + + if (request_result == PARCEL_RESULT_NO_DATA) + { + // no valid parcel data + LL_INFOS("ParcelMgr") << "no valid parcel data" << LL_ENDL; + return; + } + + // Decide where the data will go. + LLParcel* parcel = NULL; + if (sequence_id == SELECTED_PARCEL_SEQ_ID) + { + // ...selected parcels report this sequence id + parcel_mgr.mRequestResult = PARCEL_RESULT_SUCCESS; + parcel = parcel_mgr.mCurrentParcel; + } + else if (sequence_id == HOVERED_PARCEL_SEQ_ID) + { + parcel_mgr.mHoverRequestResult = PARCEL_RESULT_SUCCESS; + parcel = parcel_mgr.mHoverParcel; + } + else if (sequence_id == COLLISION_NOT_IN_GROUP_PARCEL_SEQ_ID || + sequence_id == COLLISION_NOT_ON_LIST_PARCEL_SEQ_ID || + sequence_id == COLLISION_BANNED_PARCEL_SEQ_ID) + { + parcel_mgr.mHoverRequestResult = PARCEL_RESULT_SUCCESS; + parcel = parcel_mgr.mCollisionParcel; + } + else if (sequence_id == 0 || sequence_id > parcel_mgr.mAgentParcelSequenceID) + { + // new agent parcel + // *TODO: Does it really make sense to set the agent parcel to this + // parcel if the client doesn't know what kind of parcel data this is? + parcel_mgr.mAgentParcelSequenceID = sequence_id; + parcel = parcel_mgr.mAgentParcel; + } + else + { + LL_INFOS("ParcelMgr") << "out of order agent parcel sequence id " << sequence_id + << " last good " << parcel_mgr.mAgentParcelSequenceID + << LL_ENDL; + return; + } + + msg->getBOOL("ParcelData", "SnapSelection", snap_selection); + msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_SelfCount, self_count); + msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_OtherCount, other_count); + msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_PublicCount, public_count); + msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_LocalID, local_id); + msg->getUUIDFast(_PREHASH_ParcelData, _PREHASH_OwnerID, owner_id); + msg->getBOOLFast(_PREHASH_ParcelData, _PREHASH_IsGroupOwned, is_group_owned); + msg->getU32Fast(_PREHASH_ParcelData, _PREHASH_AuctionID, auction_id); + msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_ClaimDate, claim_date); + msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_ClaimPrice, claim_price_per_meter); + msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_RentPrice, rent_price_per_meter); + msg->getVector3Fast(_PREHASH_ParcelData, _PREHASH_AABBMin, aabb_min); + msg->getVector3Fast(_PREHASH_ParcelData, _PREHASH_AABBMax, aabb_max); + msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_Area, area); + //msg->getUUIDFast( _PREHASH_ParcelData, _PREHASH_BuyerID, buyer_id); + msg->getU8("ParcelData", "Status", status); + msg->getS32("ParcelData", "SimWideMaxPrims", sw_max_prims); + msg->getS32("ParcelData", "SimWideTotalPrims", sw_total_prims); + msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_MaxPrims, max_prims); + msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_TotalPrims, total_prims); + msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_OwnerPrims, owner_prims); + msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_GroupPrims, group_prims); + msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_OtherPrims, other_prims); + msg->getS32Fast(_PREHASH_ParcelData, _PREHASH_SelectedPrims, selected_prims); + msg->getF32Fast(_PREHASH_ParcelData, _PREHASH_ParcelPrimBonus, parcel_prim_bonus); + msg->getBOOLFast(_PREHASH_ParcelData, _PREHASH_RegionPushOverride, region_push_override); + msg->getBOOLFast(_PREHASH_ParcelData, _PREHASH_RegionDenyAnonymous, region_deny_anonymous_override); + msg->getBOOLFast(_PREHASH_ParcelData, _PREHASH_RegionDenyIdentified, region_deny_identified_override); // Deprecated + msg->getBOOLFast(_PREHASH_ParcelData, _PREHASH_RegionDenyTransacted, region_deny_transacted_override); // Deprecated + if (msg->getNumberOfBlocksFast(_PREHASH_AgeVerificationBlock)) + { + // this block was added later and may not be on older sims, so we have to test its existence first + msg->getBOOLFast(_PREHASH_AgeVerificationBlock, _PREHASH_RegionDenyAgeUnverified, region_deny_age_unverified_override); + } + + if (msg->getNumberOfBlocks(_PREHASH_RegionAllowAccessBlock)) + { + msg->getBOOLFast(_PREHASH_RegionAllowAccessBlock, _PREHASH_RegionAllowAccessOverride, region_allow_access_override); + } + + if (msg->getNumberOfBlocks(_PREHASH_ParcelExtendedFlags)) + { + msg->getU32Fast(_PREHASH_ParcelExtendedFlags, _PREHASH_Flags, extended_flags); + } + + if (msg->getNumberOfBlocks(_PREHASH_ParcelEnvironmentBlock)) + { + msg->getS32Fast(_PREHASH_ParcelEnvironmentBlock, _PREHASH_ParcelEnvironmentVersion, parcel_environment_version); + msg->getBOOLFast(_PREHASH_ParcelEnvironmentBlock, _PREHASH_RegionAllowEnvironmentOverride, region_allow_environment_override); + } + + msg->getS32("ParcelData", "OtherCleanTime", other_clean_time ); + + LL_DEBUGS("ParcelMgr") << "Processing parcel " << local_id << " update, target(sequence): " << sequence_id << LL_ENDL; + + // Actually extract the data. + if (parcel) + { + if (local_id == parcel_mgr.mAgentParcel->getLocalID()) + { + // Parcels in different regions can have same ids. + LLViewerRegion* parcel_region = LLWorld::getInstance()->getRegion(msg->getSender()); + LLViewerRegion* agent_region = gAgent.getRegion(); + if (parcel_region && agent_region && parcel_region->getRegionID() == agent_region->getRegionID()) + { + // we got an updated version of agent parcel + agent_parcel_update = true; + } + } + + S32 cur_parcel_environment_version = parcel->getParcelEnvironmentVersion(); + bool environment_changed = (cur_parcel_environment_version != parcel_environment_version); + + parcel->init(owner_id, + false, false, false, + claim_date, claim_price_per_meter, rent_price_per_meter, + area, other_prims, parcel_prim_bonus, is_group_owned); + parcel->setLocalID(local_id); + parcel->setAABBMin(aabb_min); + parcel->setAABBMax(aabb_max); + + parcel->setAuctionID(auction_id); + parcel->setOwnershipStatus((LLParcel::EOwnershipStatus)status); + + parcel->setSimWideMaxPrimCapacity(sw_max_prims); + parcel->setSimWidePrimCount(sw_total_prims); + parcel->setMaxPrimCapacity(max_prims); + parcel->setOwnerPrimCount(owner_prims); + parcel->setGroupPrimCount(group_prims); + parcel->setOtherPrimCount(other_prims); + parcel->setSelectedPrimCount(selected_prims); + parcel->setParcelPrimBonus(parcel_prim_bonus); + + parcel->setCleanOtherTime(other_clean_time); + parcel->setRegionPushOverride(region_push_override); + parcel->setRegionDenyAnonymousOverride(region_deny_anonymous_override); + parcel->setRegionDenyAgeUnverifiedOverride(region_deny_age_unverified_override); + parcel->setRegionAllowAccessOverride(region_allow_access_override); + parcel->setParcelEnvironmentVersion(cur_parcel_environment_version); + parcel->setRegionAllowEnvironmentOverride(region_allow_environment_override); + + parcel->setObscureMOAP((bool)extended_flags); + + parcel->unpackMessage(msg); + + if (parcel == parcel_mgr.mAgentParcel) + { + // new agent parcel + S32 bitmap_size = parcel_mgr.mParcelsPerEdge + * parcel_mgr.mParcelsPerEdge + / 8; + U8* bitmap = new U8[ bitmap_size ]; + msg->getBinaryDataFast(_PREHASH_ParcelData, _PREHASH_Bitmap, bitmap, bitmap_size); + + parcel_mgr.writeAgentParcelFromBitmap(bitmap); + delete[] bitmap; + + // Let interesting parties know about agent parcel change. + LLViewerParcelMgr* instance = LLViewerParcelMgr::getInstance(); + + if (instance->mTeleportInProgress) + { + instance->mTeleportInProgress = false; + if(instance->mTeleportInProgressPosition.isNull()) + { + //initial update + instance->mTeleportFinishedSignal(gAgent.getPositionGlobal(), false); + } + else + { + instance->mTeleportFinishedSignal(instance->mTeleportInProgressPosition, false); + } + } + parcel->setParcelEnvironmentVersion(parcel_environment_version); + LL_DEBUGS("ENVIRONMENT") << "Parcel environment version is " << parcel->getParcelEnvironmentVersion() << LL_ENDL; + + // Notify anything that wants to know when the agent changes parcels + gAgent.changeParcels(); + instance->mTeleportInProgress = false; + } + else if (agent_parcel_update) + { + parcel->setParcelEnvironmentVersion(parcel_environment_version); + // updated agent parcel + parcel_mgr.mAgentParcel->unpackMessage(msg); + if ((LLEnvironment::instance().isExtendedEnvironmentEnabled() && environment_changed)) + { + LL_DEBUGS("ENVIRONMENT") << "Parcel environment version is " << parcel->getParcelEnvironmentVersion() << LL_ENDL; + LLEnvironment::instance().requestParcel(local_id); + } + } + } + + // Handle updating selections, if necessary. + if (sequence_id == SELECTED_PARCEL_SEQ_ID) + { + // Update selected counts + parcel_mgr.mCurrentParcelSelection->mSelectedSelfCount = self_count; + parcel_mgr.mCurrentParcelSelection->mSelectedOtherCount = other_count; + parcel_mgr.mCurrentParcelSelection->mSelectedPublicCount = public_count; + + parcel_mgr.mCurrentParcelSelection->mSelectedMultipleOwners = + (request_result == PARCEL_RESULT_MULTIPLE); + + // Select the whole parcel + LLViewerRegion* region = LLWorld::getInstance()->getRegion( msg->getSender() ); + if (region) + { + if (!snap_selection) + { + // don't muck with the westsouth and eastnorth. + // just highlight it + LLVector3 west_south = region->getPosRegionFromGlobal(parcel_mgr.mWestSouth); + LLVector3 east_north = region->getPosRegionFromGlobal(parcel_mgr.mEastNorth); + + parcel_mgr.resetSegments(parcel_mgr.mHighlightSegments); + parcel_mgr.writeHighlightSegments( + west_south.mV[VX], + west_south.mV[VY], + east_north.mV[VX], + east_north.mV[VY] ); + parcel_mgr.mCurrentParcelSelection->mWholeParcelSelected = false; + } + else if (0 == local_id) + { + // this is public land, just highlight the selection + parcel_mgr.mWestSouth = region->getPosGlobalFromRegion( aabb_min ); + parcel_mgr.mEastNorth = region->getPosGlobalFromRegion( aabb_max ); + + parcel_mgr.resetSegments(parcel_mgr.mHighlightSegments); + parcel_mgr.writeHighlightSegments( + aabb_min.mV[VX], + aabb_min.mV[VY], + aabb_max.mV[VX], + aabb_max.mV[VY] ); + parcel_mgr.mCurrentParcelSelection->mWholeParcelSelected = true; + } + else + { + parcel_mgr.mWestSouth = region->getPosGlobalFromRegion( aabb_min ); + parcel_mgr.mEastNorth = region->getPosGlobalFromRegion( aabb_max ); + + // Owned land, highlight the boundaries + S32 bitmap_size = parcel_mgr.mParcelsPerEdge + * parcel_mgr.mParcelsPerEdge + / 8; + U8* bitmap = new U8[ bitmap_size ]; + msg->getBinaryDataFast(_PREHASH_ParcelData, _PREHASH_Bitmap, bitmap, bitmap_size); + + parcel_mgr.resetSegments(parcel_mgr.mHighlightSegments); + parcel_mgr.writeSegmentsFromBitmap( bitmap, parcel_mgr.mHighlightSegments ); + + delete[] bitmap; + bitmap = NULL; + + parcel_mgr.mCurrentParcelSelection->mWholeParcelSelected = true; + } + + // Request access list information for this land + parcel_mgr.sendParcelAccessListRequest(AL_ACCESS | AL_BAN | AL_ALLOW_EXPERIENCE | AL_BLOCK_EXPERIENCE); + + // Request dwell for this land, if it's not public land. + parcel_mgr.mSelectedDwell = DWELL_NAN; + if (0 != local_id) + { + parcel_mgr.sendParcelDwellRequest(); + } + + parcel_mgr.mSelected = true; + parcel_mgr.notifyObservers(); + } + } + else if (sequence_id == COLLISION_NOT_IN_GROUP_PARCEL_SEQ_ID || + sequence_id == COLLISION_NOT_ON_LIST_PARCEL_SEQ_ID || + sequence_id == COLLISION_BANNED_PARCEL_SEQ_ID) + { + // We're about to collide with this parcel + static LLCachedControl ban_lines_mode(gSavedSettings , "ShowBanLines" , PARCEL_BAN_LINES_ON_COLLISION); + if (ban_lines_mode == PARCEL_BAN_LINES_ON_PROXIMITY) + { + parcel_mgr.resetCollisionTimer(); + } + + // Differentiate this parcel if we are banned from it. + if (sequence_id == COLLISION_BANNED_PARCEL_SEQ_ID) + { + parcel_mgr.mCollisionBanned = BA_BANNED; + } + else if (sequence_id == COLLISION_NOT_IN_GROUP_PARCEL_SEQ_ID) + { + parcel_mgr.mCollisionBanned = BA_NOT_IN_GROUP; + } + else + { + parcel_mgr.mCollisionBanned = BA_NOT_ON_LIST; + + } + + S32 bitmap_size = parcel_mgr.mParcelsPerEdge + * parcel_mgr.mParcelsPerEdge + / 8; + U8* bitmap = new U8[ bitmap_size ]; + msg->getBinaryDataFast(_PREHASH_ParcelData, _PREHASH_Bitmap, bitmap, bitmap_size); + + parcel_mgr.resetSegments(parcel_mgr.mCollisionSegments); + parcel_mgr.writeSegmentsFromBitmap( bitmap, parcel_mgr.mCollisionSegments ); + + delete[] bitmap; + bitmap = NULL; + + } + else if (sequence_id == HOVERED_PARCEL_SEQ_ID) + { + LLViewerRegion *region = LLWorld::getInstance()->getRegion( msg->getSender() ); + if (region) + { + parcel_mgr.mHoverWestSouth = region->getPosGlobalFromRegion( aabb_min ); + parcel_mgr.mHoverEastNorth = region->getPosGlobalFromRegion( aabb_max ); + } + else + { + parcel_mgr.mHoverWestSouth.clearVec(); + parcel_mgr.mHoverEastNorth.clearVec(); + } + } + else + { + if (gNonInteractive) + { + return; + } + + // Check for video + LLViewerParcelMedia::getInstance()->update(parcel); + + // Then check for music + if (gAudiop) + { + if (parcel) + { + // Only update stream if parcel changed (recreated) or music is playing (enabled) + static LLCachedControl already_playing(gSavedSettings, "MediaTentativeAutoPlay", true); + if (!agent_parcel_update || already_playing) + { + LLViewerParcelAskPlay::getInstance()->cancelNotification(); + std::string music_url_raw = parcel->getMusicURL(); + + // Trim off whitespace from front and back + std::string music_url = music_url_raw; + LLStringUtil::trim(music_url); + + // If there is a new music URL and it's valid, play it. + if (music_url.size() > 12) + { + if (music_url.substr(0, 7) == "http://" + || music_url.substr(0, 8) == "https://") + { + LLViewerRegion *region = LLWorld::getInstance()->getRegion(msg->getSender()); + if (region) + { + optionallyStartMusic(music_url, parcel->mLocalID, region->getRegionID(), !agent_parcel_update); + } + } + else + { + LL_INFOS("ParcelMgr") << "Stopping parcel music (invalid audio stream URL)" << LL_ENDL; + // clears the URL + // null value causes fade out + LLViewerAudio::getInstance()->startInternetStreamWithAutoFade(LLStringUtil::null); + } + } + else if (!gAudiop->getInternetStreamURL().empty()) + { + LL_INFOS("ParcelMgr") << "Stopping parcel music (parcel stream URL is empty)" << LL_ENDL; + // null value causes fade out + LLViewerAudio::getInstance()->startInternetStreamWithAutoFade(LLStringUtil::null); + } + } + } + else + { + // Public land has no music + LLViewerParcelAskPlay::getInstance()->cancelNotification(); + LLViewerAudio::getInstance()->stopInternetStreamWithAutoFade(); + } + }//if gAudiop + }; +} + +//static +void LLViewerParcelMgr::onStartMusicResponse(const LLUUID ®ion_id, const S32 &parcel_id, const std::string &url, const bool &play) +{ + if (play) + { + LL_INFOS("ParcelMgr") << "Starting parcel music " << url << LL_ENDL; + LLViewerAudio::getInstance()->startInternetStreamWithAutoFade(url); + } + else + { + LLViewerAudio::getInstance()->startInternetStreamWithAutoFade(LLStringUtil::null); + } +} + +void LLViewerParcelMgr::optionallyStartMusic(const std::string &music_url, const S32 &local_id, const LLUUID ®ion_id, bool switched_parcel) +{ + static LLCachedControl streaming_music(gSavedSettings, "AudioStreamingMusic", true); + if (streaming_music) + { + static LLCachedControl autoplay_mode(gSavedSettings, "ParcelMediaAutoPlayEnable", 1); + static LLCachedControl tentative_autoplay(gSavedSettings, "MediaTentativeAutoPlay", true); + // only play music when you enter a new parcel if the UI control for this + // was not *explicitly* stopped by the user. (part of SL-4878) + LLPanelNearByMedia* nearby_media_panel = gStatusBar ? gStatusBar->getNearbyMediaPanel() : NULL; + LLViewerAudio* viewer_audio = LLViewerAudio::getInstance(); + + // ask mode //todo constants + if (autoplay_mode == 2) + { + // if user set media to play - ask + if ((nearby_media_panel && nearby_media_panel->getParcelAudioAutoStart()) + || (!nearby_media_panel && tentative_autoplay)) + { + // user did not stop audio + if (switched_parcel || music_url != viewer_audio->getNextStreamURI()) + { + viewer_audio->startInternetStreamWithAutoFade(LLStringUtil::null); + + LLViewerParcelAskPlay::getInstance()->askToPlay(region_id, + local_id, + music_url, + onStartMusicResponse); + } + // else do nothing: + // Parcel properties changed, but not url. + // We are already playing this url and asked about it when agent entered parcel + // or user started audio manually at some point + } + else + { + // stopped by the user, do not autoplay + LLViewerParcelAskPlay::getInstance()->cancelNotification(); + viewer_audio->startInternetStreamWithAutoFade(LLStringUtil::null); + } + } + // autoplay + else if ((nearby_media_panel + && nearby_media_panel->getParcelAudioAutoStart()) + // or they have expressed no opinion in the UI, but have autoplay on... + || (!nearby_media_panel + && autoplay_mode == 1 + && tentative_autoplay)) + { + LL_INFOS("ParcelMgr") << "Starting parcel music " << music_url << LL_ENDL; + viewer_audio->startInternetStreamWithAutoFade(music_url); + } + // autoplay off + else if(switched_parcel || music_url != viewer_audio->getNextStreamURI()) + { + viewer_audio->startInternetStreamWithAutoFade(LLStringUtil::null); + } + } +} + +// static +void LLViewerParcelMgr::processParcelAccessListReply(LLMessageSystem *msg, void **user) +{ + LLUUID agent_id; + S32 sequence_id = 0; + U32 message_flags = 0x0; + S32 parcel_id = -1; + + msg->getUUIDFast(_PREHASH_Data, _PREHASH_AgentID, agent_id); + msg->getS32Fast( _PREHASH_Data, _PREHASH_SequenceID, sequence_id ); //ignored + msg->getU32Fast( _PREHASH_Data, _PREHASH_Flags, message_flags); + msg->getS32Fast( _PREHASH_Data, _PREHASH_LocalID, parcel_id); + + LLParcel* parcel = LLViewerParcelMgr::getInstance()->mCurrentParcel; + if (!parcel) return; + + if (parcel_id != parcel->getLocalID()) + { + LL_WARNS_ONCE("ParcelMgr") << "processParcelAccessListReply for parcel " << parcel_id + << " which isn't the selected parcel " << parcel->getLocalID()<< LL_ENDL; + return; + } + + if (message_flags & AL_ACCESS) + { + parcel->unpackAccessEntries(msg, &(parcel->mAccessList) ); + } + else if (message_flags & AL_BAN) + { + parcel->unpackAccessEntries(msg, &(parcel->mBanList) ); + } + else if (message_flags & AL_ALLOW_EXPERIENCE) + { + parcel->unpackExperienceEntries(msg, EXPERIENCE_KEY_TYPE_ALLOWED); + } + else if (message_flags & AL_BLOCK_EXPERIENCE) + { + parcel->unpackExperienceEntries(msg, EXPERIENCE_KEY_TYPE_BLOCKED); + } + /*else if (message_flags & AL_RENTER) + { + parcel->unpackAccessEntries(msg, &(parcel->mRenterList) ); + }*/ + + LLViewerParcelMgr::getInstance()->notifyObservers(); +} + + +// static +void LLViewerParcelMgr::processParcelDwellReply(LLMessageSystem* msg, void**) +{ + LLUUID agent_id; + msg->getUUID("AgentData", "AgentID", agent_id); + + S32 local_id; + msg->getS32("Data", "LocalID", local_id); + + LLUUID parcel_id; + msg->getUUID("Data", "ParcelID", parcel_id); + + F32 dwell; + msg->getF32("Data", "Dwell", dwell); + + if (local_id == LLViewerParcelMgr::getInstance()->mCurrentParcel->getLocalID()) + { + LLViewerParcelMgr::getInstance()->mSelectedDwell = dwell; + LLViewerParcelMgr::getInstance()->notifyObservers(); + } +} + + +void LLViewerParcelMgr::sendParcelAccessListUpdate(U32 which) +{ + if (!mSelected) + { + return; + } + + LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal( mWestSouth ); + if (!region) return; + + LLParcel* parcel = mCurrentParcel; + if (!parcel) return; + + if (which & AL_ACCESS) + { + sendParcelAccessListUpdate(AL_ACCESS, parcel->mAccessList, region, parcel->getLocalID()); + } + + if (which & AL_BAN) + { + sendParcelAccessListUpdate(AL_BAN, parcel->mBanList, region, parcel->getLocalID()); + } + + if(which & AL_ALLOW_EXPERIENCE) + { + sendParcelAccessListUpdate(AL_ALLOW_EXPERIENCE, parcel->getExperienceKeysByType(EXPERIENCE_KEY_TYPE_ALLOWED), region, parcel->getLocalID()); + } + if(which & AL_BLOCK_EXPERIENCE) + { + sendParcelAccessListUpdate(AL_BLOCK_EXPERIENCE, parcel->getExperienceKeysByType(EXPERIENCE_KEY_TYPE_BLOCKED), region, parcel->getLocalID()); + } +} + +void LLViewerParcelMgr::sendParcelAccessListUpdate(U32 flags, const LLAccessEntry::map& entries, LLViewerRegion* region, S32 parcel_local_id) +{ + S32 count = entries.size(); + S32 num_sections = (S32) ceil(count/PARCEL_MAX_ENTRIES_PER_PACKET); + S32 sequence_id = 1; + bool start_message = true; + bool initial = true; + + LLUUID transactionUUID; + transactionUUID.generate(); + + + LLMessageSystem* msg = gMessageSystem; + + LLAccessEntry::map::const_iterator cit = entries.begin(); + LLAccessEntry::map::const_iterator end = entries.end(); + while ( (cit != end) || initial ) + { + if (start_message) + { + msg->newMessageFast(_PREHASH_ParcelAccessListUpdate); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID() ); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID() ); + msg->nextBlockFast(_PREHASH_Data); + msg->addU32Fast(_PREHASH_Flags, flags); + msg->addS32(_PREHASH_LocalID, parcel_local_id); + msg->addUUIDFast(_PREHASH_TransactionID, transactionUUID); + msg->addS32Fast(_PREHASH_SequenceID, sequence_id); + msg->addS32Fast(_PREHASH_Sections, num_sections); + start_message = false; + + if (initial && (cit == end)) + { + // pack an empty block if there will be no data + msg->nextBlockFast(_PREHASH_List); + msg->addUUIDFast(_PREHASH_ID, LLUUID::null ); + msg->addS32Fast(_PREHASH_Time, 0 ); + msg->addU32Fast(_PREHASH_Flags, 0 ); + } + + initial = false; + sequence_id++; + + } + + while ( (cit != end) && (msg->getCurrentSendTotal() < MTUBYTES)) + { + const LLAccessEntry& entry = (*cit).second; + + msg->nextBlockFast(_PREHASH_List); + msg->addUUIDFast(_PREHASH_ID, entry.mID ); + msg->addS32Fast(_PREHASH_Time, entry.mTime ); + msg->addU32Fast(_PREHASH_Flags, entry.mFlags ); + ++cit; + } + + start_message = true; + msg->sendReliable( region->getHost() ); + } +} + + +void LLViewerParcelMgr::deedLandToGroup() +{ + std::string group_name; + gCacheName->getGroupName(mCurrentParcel->getGroupID(), group_name); + LLSD args; + args["AREA"] = llformat("%d", mCurrentParcel->getArea()); + args["GROUP_NAME"] = group_name; + if(mCurrentParcel->getContributeWithDeed()) + { + args["NAME"] = LLSLURL("agent", mCurrentParcel->getOwnerID(), "completename").getSLURLString(); + LLNotificationsUtil::add("DeedLandToGroupWithContribution",args, LLSD(), deedAlertCB); + } + else + { + LLNotificationsUtil::add("DeedLandToGroup",args, LLSD(), deedAlertCB); + } +} + +// static +bool LLViewerParcelMgr::deedAlertCB(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) + { + LLParcel* parcel = LLViewerParcelMgr::getInstance()->getParcelSelection()->getParcel(); + LLUUID group_id; + if(parcel) + { + group_id = parcel->getGroupID(); + } + LLViewerParcelMgr::getInstance()->sendParcelDeed(group_id); + } + return false; +} + + +void LLViewerParcelMgr::startReleaseLand() +{ + if (!mSelected) + { + LLNotificationsUtil::add("CannotReleaseLandNothingSelected"); + return; + } + + if (mRequestResult == PARCEL_RESULT_NO_DATA) + { + LLNotificationsUtil::add("CannotReleaseLandWatingForServer"); + return; + } + + if (mRequestResult == PARCEL_RESULT_MULTIPLE) + { + LLNotificationsUtil::add("CannotReleaseLandSelected"); + return; + } + + if (!isParcelOwnedByAgent(mCurrentParcel, GP_LAND_RELEASE) + && !(gAgent.canManageEstate())) + { + LLNotificationsUtil::add("CannotReleaseLandDontOwn"); + return; + } + + LLVector3d parcel_center = (mWestSouth + mEastNorth) / 2.0; + LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal(parcel_center); + if (!region) + { + LLNotificationsUtil::add("CannotReleaseLandRegionNotFound"); + return; + } +/* + if (region->getRegionFlag(REGION_FLAGS_BLOCK_LAND_RESELL) + && !gAgent.isGodlike()) + { + LLSD args; + args["REGION"] = region->getName(); + LLNotificationsUtil::add("CannotReleaseLandNoTransfer", args); + return; + } +*/ + + if (!mCurrentParcelSelection->mWholeParcelSelected) + { + LLNotificationsUtil::add("CannotReleaseLandPartialSelection"); + return; + } + + // Compute claim price + LLSD args; + args["AREA"] = llformat("%d",mCurrentParcel->getArea()); + LLNotificationsUtil::add("ReleaseLandWarning", args, LLSD(), releaseAlertCB); +} + +bool LLViewerParcelMgr::canAgentBuyParcel(LLParcel* parcel, bool forGroup) const +{ + if (!parcel) + { + return false; + } + + if (mSelected && parcel == mCurrentParcel) + { + if (mRequestResult == PARCEL_RESULT_NO_DATA) + { + return false; + } + } + + const LLUUID& parcelOwner = parcel->getOwnerID(); + const LLUUID& authorizeBuyer = parcel->getAuthorizedBuyerID(); + + if (parcel->isPublic()) + { + return true; // change this if want to make it gods only + } + + LLVector3 parcel_coord = parcel->getCenterpoint(); + LLViewerRegion* regionp = LLWorld::getInstance()->getRegionFromPosAgent(parcel_coord); + if (regionp) + { + U8 sim_access = regionp->getSimAccess(); + const LLAgentAccess& agent_access = gAgent.getAgentAccess(); + // if the region is PG, we're happy already, so do nothing + // but if we're set to avoid either mature or adult, get us outta here + if ((sim_access == SIM_ACCESS_MATURE) && + !agent_access.canAccessMature()) + { + return false; + } + else if ((sim_access == SIM_ACCESS_ADULT) && + !agent_access.canAccessAdult()) + { + return false; + } + } + + bool isForSale = parcel->getForSale() + && ((parcel->getSalePrice() > 0) || (authorizeBuyer.notNull())); + + bool isEmpowered + = forGroup ? gAgent.hasPowerInActiveGroup(GP_LAND_DEED) : true; + + bool isOwner + = parcelOwner == (forGroup ? gAgent.getGroupID() : gAgent.getID()); + + bool isAuthorized + = (authorizeBuyer.isNull() + || (gAgent.getID() == authorizeBuyer) + || (gAgent.hasPowerInGroup(authorizeBuyer,GP_LAND_DEED) + && gAgent.hasPowerInGroup(authorizeBuyer,GP_LAND_SET_SALE_INFO))); + + return isForSale && !isOwner && isAuthorized && isEmpowered; +} + + +void LLViewerParcelMgr::startBuyLand(bool is_for_group) +{ + LLFloaterBuyLand::buyLand(getSelectionRegion(), mCurrentParcelSelection, is_for_group); +} + +void LLViewerParcelMgr::startSellLand() +{ + LLFloaterSellLand::sellLand(getSelectionRegion(), mCurrentParcelSelection); +} + +void LLViewerParcelMgr::startDivideLand() +{ + if (!mSelected) + { + LLNotificationsUtil::add("CannotDivideLandNothingSelected"); + return; + } + + if (mCurrentParcelSelection->mWholeParcelSelected) + { + LLNotificationsUtil::add("CannotDivideLandPartialSelection"); + return; + } + + LLSD payload; + payload["west_south_border"] = ll_sd_from_vector3d(mWestSouth); + payload["east_north_border"] = ll_sd_from_vector3d(mEastNorth); + + LLNotificationsUtil::add("LandDivideWarning", LLSD(), payload, callbackDivideLand); +} + +// static +bool LLViewerParcelMgr::callbackDivideLand(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + LLVector3d west_south_d = ll_vector3d_from_sd(notification["payload"]["west_south_border"]); + LLVector3d east_north_d = ll_vector3d_from_sd(notification["payload"]["east_north_border"]); + LLVector3d parcel_center = (west_south_d + east_north_d) / 2.0; + + LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal(parcel_center); + if (!region) + { + LLNotificationsUtil::add("CannotDivideLandNoRegion"); + return false; + } + + if (0 == option) + { + LLVector3 west_south = region->getPosRegionFromGlobal(west_south_d); + LLVector3 east_north = region->getPosRegionFromGlobal(east_north_d); + + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("ParcelDivide"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->nextBlock("ParcelData"); + msg->addF32("West", west_south.mV[VX]); + msg->addF32("South", west_south.mV[VY]); + msg->addF32("East", east_north.mV[VX]); + msg->addF32("North", east_north.mV[VY]); + msg->sendReliable(region->getHost()); + } + return false; +} + + +void LLViewerParcelMgr::startJoinLand() +{ + if (!mSelected) + { + LLNotificationsUtil::add("CannotJoinLandNothingSelected"); + return; + } + + if (mCurrentParcelSelection->mWholeParcelSelected) + { + LLNotificationsUtil::add("CannotJoinLandEntireParcelSelected"); + return; + } + + if (!mCurrentParcelSelection->mSelectedMultipleOwners) + { + LLNotificationsUtil::add("CannotJoinLandSelection"); + return; + } + + LLSD payload; + payload["west_south_border"] = ll_sd_from_vector3d(mWestSouth); + payload["east_north_border"] = ll_sd_from_vector3d(mEastNorth); + + LLNotificationsUtil::add("JoinLandWarning", LLSD(), payload, callbackJoinLand); +} + +// static +bool LLViewerParcelMgr::callbackJoinLand(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + LLVector3d west_south_d = ll_vector3d_from_sd(notification["payload"]["west_south_border"]); + LLVector3d east_north_d = ll_vector3d_from_sd(notification["payload"]["east_north_border"]); + LLVector3d parcel_center = (west_south_d + east_north_d) / 2.0; + + LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal(parcel_center); + if (!region) + { + LLNotificationsUtil::add("CannotJoinLandNoRegion"); + return false; + } + + if (0 == option) + { + LLVector3 west_south = region->getPosRegionFromGlobal(west_south_d); + LLVector3 east_north = region->getPosRegionFromGlobal(east_north_d); + + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("ParcelJoin"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->nextBlock("ParcelData"); + msg->addF32("West", west_south.mV[VX]); + msg->addF32("South", west_south.mV[VY]); + msg->addF32("East", east_north.mV[VX]); + msg->addF32("North", east_north.mV[VY]); + msg->sendReliable(region->getHost()); + } + return false; +} + + +void LLViewerParcelMgr::startDeedLandToGroup() +{ + if (!mSelected || !mCurrentParcel) + { + LLNotificationsUtil::add("CannotDeedLandNothingSelected"); + return; + } + + if (mRequestResult == PARCEL_RESULT_NO_DATA) + { + LLNotificationsUtil::add("CannotDeedLandWaitingForServer"); + return; + } + + if (mRequestResult == PARCEL_RESULT_MULTIPLE) + { + LLNotificationsUtil::add("CannotDeedLandMultipleSelected"); + return; + } + + LLVector3d parcel_center = (mWestSouth + mEastNorth) / 2.0; + LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal(parcel_center); + if (!region) + { + LLNotificationsUtil::add("CannotDeedLandNoRegion"); + return; + } + + /* + if(!gAgent.isGodlike()) + { + if(region->getRegionFlag(REGION_FLAGS_BLOCK_LAND_RESELL) + && (mCurrentParcel->getOwnerID() != region->getOwner())) + { + LLSD args; + args["REGION"] = region->getName(); + LLNotificationsUtil::add("CannotDeedLandNoTransfer", args); + return; + } + } + */ + + deedLandToGroup(); +} +void LLViewerParcelMgr::reclaimParcel() +{ + LLParcel* parcel = LLViewerParcelMgr::getInstance()->getParcelSelection()->getParcel(); + LLViewerRegion* regionp = LLViewerParcelMgr::getInstance()->getSelectionRegion(); + if(parcel && parcel->getOwnerID().notNull() + && (parcel->getOwnerID() != gAgent.getID()) + && regionp && (regionp->getOwner() == gAgent.getID())) + { + LLMessageSystem* msg = gMessageSystem; + msg->newMessage("ParcelReclaim"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->nextBlock("Data"); + msg->addS32("LocalID", parcel->getLocalID()); + msg->sendReliable(regionp->getHost()); + } +} + +// static +bool LLViewerParcelMgr::releaseAlertCB(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) + { + // Send the release message, not a force + LLViewerParcelMgr::getInstance()->sendParcelRelease(); + } + return false; +} + +void LLViewerParcelMgr::buyPass() +{ + LLParcel* parcel = getParcelSelection()->getParcel(); + if (!parcel) return; + + LLViewerRegion* region = getSelectionRegion(); + if (!region) return; + + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_ParcelBuyPass); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_ParcelData); + msg->addS32Fast(_PREHASH_LocalID, parcel->getLocalID() ); + msg->sendReliable( region->getHost() ); +} + +//Tells whether we are allowed to buy a pass or not +bool LLViewerParcelMgr::isCollisionBanned() +{ + if ((mCollisionBanned == BA_ALLOWED) || (mCollisionBanned == BA_NOT_ON_LIST) || (mCollisionBanned == BA_NOT_IN_GROUP)) + return false; + else + return true; +} + +// This implementation should mirror LLSimParcelMgr::isParcelOwnedBy +// static +bool LLViewerParcelMgr::isParcelOwnedByAgent(const LLParcel* parcelp, U64 group_proxy_power) +{ + if (!parcelp) + { + return false; + } + + // Gods can always assume ownership. + if (gAgent.isGodlike()) + { + return true; + } + + // The owner of a parcel automatically gets all powersr. + if (parcelp->getOwnerID() == gAgent.getID()) + { + return true; + } + + // Only gods can assume 'ownership' of public land. + if (parcelp->isPublic()) + { + return false; + } + + // Return whether or not the agent has group_proxy_power powers in the + // parcel's group. + return gAgent.hasPowerInGroup(parcelp->getOwnerID(), group_proxy_power); +} + +// This implementation should mirror llSimParcelMgr::isParcelModifiableBy +// static +bool LLViewerParcelMgr::isParcelModifiableByAgent(const LLParcel* parcelp, U64 group_proxy_power) +{ + // If the agent can assume ownership, it is probably modifiable. + bool rv = false; + if (parcelp) + { + // *NOTE: This should only work for leased parcels, but group owned + // parcels cannot be OS_LEASED yet. Phoenix 2003-12-15. + rv = isParcelOwnedByAgent(parcelp, group_proxy_power); + + // ... except for the case that the parcel is not OS_LEASED for agent-owned parcels. + if( (gAgent.getID() == parcelp->getOwnerID()) + && !gAgent.isGodlike() + && (parcelp->getOwnershipStatus() != LLParcel::OS_LEASED) ) + { + rv = false; + } + } + return rv; +} + +void sanitize_corners(const LLVector3d &corner1, + const LLVector3d &corner2, + LLVector3d &west_south_bottom, + LLVector3d &east_north_top) +{ + west_south_bottom.mdV[VX] = llmin( corner1.mdV[VX], corner2.mdV[VX] ); + west_south_bottom.mdV[VY] = llmin( corner1.mdV[VY], corner2.mdV[VY] ); + west_south_bottom.mdV[VZ] = llmin( corner1.mdV[VZ], corner2.mdV[VZ] ); + + east_north_top.mdV[VX] = llmax( corner1.mdV[VX], corner2.mdV[VX] ); + east_north_top.mdV[VY] = llmax( corner1.mdV[VY], corner2.mdV[VY] ); + east_north_top.mdV[VZ] = llmax( corner1.mdV[VZ], corner2.mdV[VZ] ); +} + + +void LLViewerParcelMgr::cleanupGlobals() +{ +} + +LLViewerTexture* LLViewerParcelMgr::getBlockedImage() const +{ + return sBlockedImage; +} + +LLViewerTexture* LLViewerParcelMgr::getPassImage() const +{ + return sPassImage; +} + +/* + * Set finish teleport callback. You can use it to observe all teleport events. + * NOTE: + * After local( in one region) teleports we + * cannot rely on gAgent.getPositionGlobal(), + * so the new position gets passed explicitly. + * Use args of this callback to get global position of avatar after teleport event. + */ +boost::signals2::connection LLViewerParcelMgr::setTeleportFinishedCallback(teleport_finished_callback_t cb) +{ + return mTeleportFinishedSignal.connect(cb); +} + +boost::signals2::connection LLViewerParcelMgr::setTeleportFailedCallback(teleport_failed_callback_t cb) +{ + return mTeleportFailedSignal.connect(cb); +} + +/* Ok, we're notified that teleport has been finished. + * We should now propagate the notification via mTeleportFinishedSignal + * to all interested parties. + */ +void LLViewerParcelMgr::onTeleportFinished(bool local, const LLVector3d& new_pos) +{ + // Treat only teleports within the same parcel as local (EXT-3139). + if (local && LLViewerParcelMgr::getInstance()->inAgentParcel(new_pos)) + { + // Local teleport. We already have the agent parcel data. + // Emit the signal immediately. + getInstance()->mTeleportFinishedSignal(new_pos, local); + } + else + { + // Non-local teleport (inter-region or between different parcels of the same region). + // The agent parcel data has not been updated yet. + // Let's wait for the update and then emit the signal. + mTeleportInProgressPosition = new_pos; + mTeleportInProgress = true; + } +} + +void LLViewerParcelMgr::onTeleportFailed() +{ + mTeleportFailedSignal(); +} + +bool LLViewerParcelMgr::getTeleportInProgress() +{ + return mTeleportInProgress // case where parcel data arrives after teleport + || gAgent.getTeleportState() > LLAgent::TELEPORT_NONE; // For LOCAL, no mTeleportInProgress +} diff --git a/indra/newview/llviewerparcelmgr.h b/indra/newview/llviewerparcelmgr.h index 00750ad8b7..974ea39359 100644 --- a/indra/newview/llviewerparcelmgr.h +++ b/indra/newview/llviewerparcelmgr.h @@ -1,385 +1,385 @@ -/** - * @file llviewerparcelmgr.h - * @brief Viewer-side representation of owned land - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVIEWERPARCELMGR_H -#define LL_LLVIEWERPARCELMGR_H - -#include "v3dmath.h" -#include "llframetimer.h" -#include "llsingleton.h" -#include "llparcelselection.h" -#include "llui.h" - -#include -#include - -class LLUUID; -class LLMessageSystem; -class LLParcel; -class LLViewerTexture; -class LLViewerRegion; - -const F32 DWELL_NAN = -1.0f; // A dwell having this value will be displayed as Loading... - -// Constants for sendLandOwner -//const U32 NO_NEIGHBOR_JOIN = 0x0; -//const U32 ALL_NEIGHBOR_JOIN = U32( NORTH_MASK -// | SOUTH_MASK -// | EAST_MASK -// | WEST_MASK); - -// Specify the type of land transfer taking place -//enum ELandTransferType -//{ -// LTT_RELEASE_LAND = 0x1, -// LTT_CLAIM_LAND = 0x2, -// LTT_BUY_LAND = 0x4, -// LTT_DEED_LAND = 0x8, -// LTT_FOR_GROUP = 0x16 -//}; - -// Base class for people who want to "observe" changes in the viewer -// parcel selection. - -//FIXME: this should be done by grabbing a floating parcel selection and observing changes on it, not the parcel mgr -//--RN -class LLParcelObserver -{ -public: - virtual ~LLParcelObserver() {}; - virtual void changed() = 0; -}; - -class LLViewerParcelMgr : public LLSingleton -{ - LLSINGLETON(LLViewerParcelMgr); - ~LLViewerParcelMgr(); - -public: - typedef boost::function teleport_finished_callback_t; - typedef boost::signals2::signal teleport_finished_signal_t; - typedef boost::function teleport_failed_callback_t; - typedef boost::signals2::signal teleport_failed_signal_t; - - static void cleanupGlobals(); - - bool selectionEmpty() const; - F32 getSelectionWidth() const { return F32(mEastNorth.mdV[VX] - mWestSouth.mdV[VX]); } - F32 getSelectionHeight() const { return F32(mEastNorth.mdV[VY] - mWestSouth.mdV[VY]); } - bool getSelection(LLVector3d &min, LLVector3d &max) { min = mWestSouth; max = mEastNorth; return !selectionEmpty();} - LLViewerRegion* getSelectionRegion(); - F32 getDwelling() const { return mSelectedDwell;} - - void getDisplayInfo(S32* area, S32* claim, S32* rent, bool* for_sale, F32* dwell); - - // Returns selected area - S32 getSelectedArea() const; - - void resetSegments(U8* segments); - - // write a rectangle's worth of line segments into the highlight array - void writeHighlightSegments(F32 west, F32 south, F32 east, F32 north); - - // Write highlight segments from a packed bitmap of the appropriate - // parcel. - void writeSegmentsFromBitmap(U8* bitmap, U8* segments); - - void writeAgentParcelFromBitmap(U8* bitmap); - - // Select the collision parcel - void selectCollisionParcel(); - - // Select the parcel at a specific point - LLParcelSelectionHandle selectParcelAt(const LLVector3d& pos_global); - - // Take the current rectangle select, and select the parcel contained - // within it. - LLParcelSelectionHandle selectParcelInRectangle(); - - // Select a piece of land - LLParcelSelectionHandle selectLand(const LLVector3d &corner1, const LLVector3d &corner2, - bool snap_to_parcel); - - // Clear the selection, and stop drawing the highlight. - void deselectLand(); - void deselectUnused(); - - void addObserver(LLParcelObserver* observer); - void removeObserver(LLParcelObserver* observer); - void notifyObservers(); - - void setSelectionVisible(bool visible) { mRenderSelection = visible; } - - bool isOwnedAt(const LLVector3d& pos_global) const; - bool isOwnedSelfAt(const LLVector3d& pos_global) const; - bool isOwnedOtherAt(const LLVector3d& pos_global) const; - bool isSoundLocal(const LLVector3d &pos_global) const; - - bool canHearSound(const LLVector3d &pos_global) const; - - // Returns a reference counted pointer to current parcel selection. - // Selection does not change to reflect new selections made by user - // Use this when implementing a task UI that refers to a specific - // selection. - LLParcelSelectionHandle getParcelSelection() const; - - // Returns a reference counted pointer to current parcel selection. - // Pointer tracks whatever the user has currently selected. - // Use this when implementing an inspector UI. - // http://en.wikipedia.org/wiki/Inspector_window - LLParcelSelectionHandle getFloatingParcelSelection() const; - - //LLParcel *getParcelSelection() const; - LLParcel *getAgentParcel() const; - LLParcel *getAgentOrSelectedParcel() const; - - bool inAgentParcel(const LLVector3d &pos_global) const; - - // Returns a pointer only when it has valid data. - LLParcel* getHoverParcel() const; - - LLParcel* getCollisionParcel() const; - - // Can this agent build on the parcel he is on? - // Used for parcel property icons in nav bar. - bool allowAgentBuild() const; - bool allowAgentBuild(const LLParcel* parcel) const; - - // Can this agent speak on the parcel he is on? - // Used for parcel property icons in nav bar. - bool allowAgentVoice() const; - bool allowAgentVoice(const LLViewerRegion* region, const LLParcel* parcel) const; - - // Can this agent start flying on this parcel? - // Used for parcel property icons in nav bar. - bool allowAgentFly(const LLViewerRegion* region, const LLParcel* parcel) const; - - // Can this agent be pushed by llPushObject() on this parcel? - // Used for parcel property icons in nav bar. - bool allowAgentPush(const LLViewerRegion* region, const LLParcel* parcel) const; - - // Can scripts written by non-parcel-owners run on the agent's current - // parcel? Used for parcel property icons in nav bar. - bool allowAgentScripts(const LLViewerRegion* region, const LLParcel* parcel) const; - - // Can the agent be damaged here? - // Used for parcel property icons in nav bar. - bool allowAgentDamage(const LLViewerRegion* region, const LLParcel* parcel) const; - - F32 getHoverParcelWidth() const - { return F32(mHoverEastNorth.mdV[VX] - mHoverWestSouth.mdV[VX]); } - - F32 getHoverParcelHeight() const - { return F32(mHoverEastNorth.mdV[VY] - mHoverWestSouth.mdV[VY]); } - - // UTILITIES - void render(); - void renderParcelCollision(); - - void renderRect( const LLVector3d &west_south_bottom, - const LLVector3d &east_north_top ); - void renderOneSegment(F32 x1, F32 y1, F32 x2, F32 y2, F32 height, U8 direction, LLViewerRegion* regionp); - void renderHighlightSegments(const U8* segments, LLViewerRegion* regionp); - void renderCollisionSegments(U8* segments, bool use_pass, LLViewerRegion* regionp); - - static S32 PARCEL_BAN_LINES_HIDE; - static S32 PARCEL_BAN_LINES_ON_COLLISION; - static S32 PARCEL_BAN_LINES_ON_PROXIMITY; - void resetCollisionTimer(); // Ban lines visibility timer - - void sendParcelGodForceOwner(const LLUUID& owner_id); - - // make the selected parcel a content parcel. - void sendParcelGodForceToContent(); - - // Pack information about this parcel and send it to the region - // containing the southwest corner of the selection. - // If want_reply_to_update, simulator will send back a ParcelProperties - // message. - void sendParcelPropertiesUpdate(LLParcel* parcel, bool use_agent_region = false); - - // Takes an Access List flag, like AL_ACCESS or AL_BAN - void sendParcelAccessListUpdate(U32 which); - - // Takes an Access List flag, like AL_ACCESS or AL_BAN - void sendParcelAccessListRequest(U32 flags); - - // Dwell is not part of the usual parcel update information because the - // simulator doesn't actually know the per-parcel dwell. Ack! We have - // to get it out of the database. - void sendParcelDwellRequest(); - - // If the point is outside the current hover parcel, request more data - void setHoverParcel(const LLVector3d& pos_global); - - bool canAgentBuyParcel(LLParcel*, bool forGroup) const; - -// void startClaimLand(bool is_for_group = false); - void startBuyLand(bool is_for_group = false); - void startSellLand(); - void startReleaseLand(); - void startDivideLand(); - void startJoinLand(); - void startDeedLandToGroup(); - void reclaimParcel(); - - void buyPass(); - - // Buying Land - - class ParcelBuyInfo; - ParcelBuyInfo* setupParcelBuy(const LLUUID& agent_id, - const LLUUID& session_id, - const LLUUID& group_id, - bool is_group_owned, - bool is_claim, - bool remove_contribution); - // callers responsibility to call deleteParcelBuy() on return value - void sendParcelBuy(ParcelBuyInfo*); - void deleteParcelBuy(ParcelBuyInfo* *info); - - void sendParcelDeed(const LLUUID& group_id); - - // Send the ParcelRelease message - void sendParcelRelease(); - - // accessors for mAgentParcel - const std::string& getAgentParcelName() const; - const S32 getAgentParcelId() const; - - // Create a landmark at the "appropriate" location for the - // currently selected parcel. - // *NOTE: Taken out 2005-03-21. Phoenix. - //void makeLandmarkAtSelection(); - - static void onStartMusicResponse(const LLUUID ®ion_id, const S32 &parcel_id, const std::string &url, const bool &play); - static void optionallyStartMusic(const std::string &music_url, const S32 &local_id, const LLUUID ®ion_id, bool switched_parcel); - - static void processParcelOverlay(LLMessageSystem *msg, void **user_data); - static void processParcelProperties(LLMessageSystem *msg, void **user_data); - static void processParcelAccessListReply(LLMessageSystem *msg, void **user); - static void processParcelDwellReply(LLMessageSystem *msg, void **user); - - void dump(); - - // Whether or not the collision border around the parcel is there because - // the agent is banned or not in the allowed group - bool isCollisionBanned(); - - boost::signals2::connection setTeleportFinishedCallback(teleport_finished_callback_t cb); - boost::signals2::connection setTeleportFailedCallback(teleport_failed_callback_t cb); - void onTeleportFinished(bool local, const LLVector3d& new_pos); - void onTeleportFailed(); - bool getTeleportInProgress(); - - static bool isParcelOwnedByAgent(const LLParcel* parcelp, U64 group_proxy_power); - static bool isParcelModifiableByAgent(const LLParcel* parcelp, U64 group_proxy_power); - -private: - static void sendParcelAccessListUpdate(U32 flags, const std::map& entries, LLViewerRegion* region, S32 parcel_local_id); - static void sendParcelExperienceUpdate( const U32 flags, uuid_vec_t experience_ids, LLViewerRegion* region, S32 parcel_local_id ); - static bool releaseAlertCB(const LLSD& notification, const LLSD& response); - - // If the user is claiming land and the current selection - // borders a piece of land the user already owns, ask if he - // wants to join this land to the other piece. - //void askJoinIfNecessary(ELandTransferType land_transfer_type); - //static void joinAlertCB(S32 option, void* data); - - //void buyAskMoney(ELandTransferType land_transfer_type); - - // move land from current owner to it's group. - void deedLandToGroup(); - - static bool deedAlertCB(const LLSD& notification, const LLSD& response); - - static bool callbackDivideLand(const LLSD& notification, const LLSD& response); - static bool callbackJoinLand(const LLSD& notification, const LLSD& response); - - //void finishClaim(bool user_to_user_sale, U32 join); - LLViewerTexture* getBlockedImage() const; - LLViewerTexture* getPassImage() const; - -private: - bool mSelected; - - LLParcel* mCurrentParcel; // selected parcel info - LLParcelSelectionHandle mCurrentParcelSelection; - LLParcelSelectionHandle mFloatingParcelSelection; - S32 mRequestResult; // result of last parcel request - LLVector3d mWestSouth; - LLVector3d mEastNorth; - F32 mSelectedDwell; - - LLParcel *mAgentParcel; // info for parcel agent is in - S32 mAgentParcelSequenceID; // incrementing counter to suppress out of order updates - - LLParcel* mHoverParcel; - S32 mHoverRequestResult; - LLVector3d mHoverWestSouth; - LLVector3d mHoverEastNorth; - - std::vector mObservers; - - bool mTeleportInProgress; - LLVector3d mTeleportInProgressPosition; - teleport_finished_signal_t mTeleportFinishedSignal; - teleport_failed_signal_t mTeleportFailedSignal; - - // Array of pieces of parcel edges to potentially draw - // Has (parcels_per_edge + 1) * (parcels_per_edge + 1) elements so - // we can represent edges of the grid. - // WEST_MASK = draw west edge - // SOUTH_MASK = draw south edge - S32 mParcelsPerEdge; - U8* mHighlightSegments; - U8* mAgentParcelOverlay; - - // Raw data buffer for unpacking parcel overlay chunks - // Size = parcels_per_edge * parcels_per_edge / parcel_overlay_chunks - static U8* sPackedOverlay; - - // Watch for pending collisions with a parcel you can't access. - // If it's coming, draw the parcel's boundaries. - LLParcel* mCollisionParcel; - U8* mCollisionSegments; - bool mRenderCollision; - bool mRenderSelection; - S32 mCollisionBanned; - LLFrameTimer mCollisionTimer; - LLViewerTexture* mBlockedImage; - LLViewerTexture* mPassImage; - - // Media - S32 mMediaParcelId; - U64 mMediaRegionId; -}; - - -void sanitize_corners(const LLVector3d &corner1, const LLVector3d &corner2, - LLVector3d &west_south_bottom, LLVector3d &east_north_top); - -#endif +/** + * @file llviewerparcelmgr.h + * @brief Viewer-side representation of owned land + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVIEWERPARCELMGR_H +#define LL_LLVIEWERPARCELMGR_H + +#include "v3dmath.h" +#include "llframetimer.h" +#include "llsingleton.h" +#include "llparcelselection.h" +#include "llui.h" + +#include +#include + +class LLUUID; +class LLMessageSystem; +class LLParcel; +class LLViewerTexture; +class LLViewerRegion; + +const F32 DWELL_NAN = -1.0f; // A dwell having this value will be displayed as Loading... + +// Constants for sendLandOwner +//const U32 NO_NEIGHBOR_JOIN = 0x0; +//const U32 ALL_NEIGHBOR_JOIN = U32( NORTH_MASK +// | SOUTH_MASK +// | EAST_MASK +// | WEST_MASK); + +// Specify the type of land transfer taking place +//enum ELandTransferType +//{ +// LTT_RELEASE_LAND = 0x1, +// LTT_CLAIM_LAND = 0x2, +// LTT_BUY_LAND = 0x4, +// LTT_DEED_LAND = 0x8, +// LTT_FOR_GROUP = 0x16 +//}; + +// Base class for people who want to "observe" changes in the viewer +// parcel selection. + +//FIXME: this should be done by grabbing a floating parcel selection and observing changes on it, not the parcel mgr +//--RN +class LLParcelObserver +{ +public: + virtual ~LLParcelObserver() {}; + virtual void changed() = 0; +}; + +class LLViewerParcelMgr : public LLSingleton +{ + LLSINGLETON(LLViewerParcelMgr); + ~LLViewerParcelMgr(); + +public: + typedef boost::function teleport_finished_callback_t; + typedef boost::signals2::signal teleport_finished_signal_t; + typedef boost::function teleport_failed_callback_t; + typedef boost::signals2::signal teleport_failed_signal_t; + + static void cleanupGlobals(); + + bool selectionEmpty() const; + F32 getSelectionWidth() const { return F32(mEastNorth.mdV[VX] - mWestSouth.mdV[VX]); } + F32 getSelectionHeight() const { return F32(mEastNorth.mdV[VY] - mWestSouth.mdV[VY]); } + bool getSelection(LLVector3d &min, LLVector3d &max) { min = mWestSouth; max = mEastNorth; return !selectionEmpty();} + LLViewerRegion* getSelectionRegion(); + F32 getDwelling() const { return mSelectedDwell;} + + void getDisplayInfo(S32* area, S32* claim, S32* rent, bool* for_sale, F32* dwell); + + // Returns selected area + S32 getSelectedArea() const; + + void resetSegments(U8* segments); + + // write a rectangle's worth of line segments into the highlight array + void writeHighlightSegments(F32 west, F32 south, F32 east, F32 north); + + // Write highlight segments from a packed bitmap of the appropriate + // parcel. + void writeSegmentsFromBitmap(U8* bitmap, U8* segments); + + void writeAgentParcelFromBitmap(U8* bitmap); + + // Select the collision parcel + void selectCollisionParcel(); + + // Select the parcel at a specific point + LLParcelSelectionHandle selectParcelAt(const LLVector3d& pos_global); + + // Take the current rectangle select, and select the parcel contained + // within it. + LLParcelSelectionHandle selectParcelInRectangle(); + + // Select a piece of land + LLParcelSelectionHandle selectLand(const LLVector3d &corner1, const LLVector3d &corner2, + bool snap_to_parcel); + + // Clear the selection, and stop drawing the highlight. + void deselectLand(); + void deselectUnused(); + + void addObserver(LLParcelObserver* observer); + void removeObserver(LLParcelObserver* observer); + void notifyObservers(); + + void setSelectionVisible(bool visible) { mRenderSelection = visible; } + + bool isOwnedAt(const LLVector3d& pos_global) const; + bool isOwnedSelfAt(const LLVector3d& pos_global) const; + bool isOwnedOtherAt(const LLVector3d& pos_global) const; + bool isSoundLocal(const LLVector3d &pos_global) const; + + bool canHearSound(const LLVector3d &pos_global) const; + + // Returns a reference counted pointer to current parcel selection. + // Selection does not change to reflect new selections made by user + // Use this when implementing a task UI that refers to a specific + // selection. + LLParcelSelectionHandle getParcelSelection() const; + + // Returns a reference counted pointer to current parcel selection. + // Pointer tracks whatever the user has currently selected. + // Use this when implementing an inspector UI. + // http://en.wikipedia.org/wiki/Inspector_window + LLParcelSelectionHandle getFloatingParcelSelection() const; + + //LLParcel *getParcelSelection() const; + LLParcel *getAgentParcel() const; + LLParcel *getAgentOrSelectedParcel() const; + + bool inAgentParcel(const LLVector3d &pos_global) const; + + // Returns a pointer only when it has valid data. + LLParcel* getHoverParcel() const; + + LLParcel* getCollisionParcel() const; + + // Can this agent build on the parcel he is on? + // Used for parcel property icons in nav bar. + bool allowAgentBuild() const; + bool allowAgentBuild(const LLParcel* parcel) const; + + // Can this agent speak on the parcel he is on? + // Used for parcel property icons in nav bar. + bool allowAgentVoice() const; + bool allowAgentVoice(const LLViewerRegion* region, const LLParcel* parcel) const; + + // Can this agent start flying on this parcel? + // Used for parcel property icons in nav bar. + bool allowAgentFly(const LLViewerRegion* region, const LLParcel* parcel) const; + + // Can this agent be pushed by llPushObject() on this parcel? + // Used for parcel property icons in nav bar. + bool allowAgentPush(const LLViewerRegion* region, const LLParcel* parcel) const; + + // Can scripts written by non-parcel-owners run on the agent's current + // parcel? Used for parcel property icons in nav bar. + bool allowAgentScripts(const LLViewerRegion* region, const LLParcel* parcel) const; + + // Can the agent be damaged here? + // Used for parcel property icons in nav bar. + bool allowAgentDamage(const LLViewerRegion* region, const LLParcel* parcel) const; + + F32 getHoverParcelWidth() const + { return F32(mHoverEastNorth.mdV[VX] - mHoverWestSouth.mdV[VX]); } + + F32 getHoverParcelHeight() const + { return F32(mHoverEastNorth.mdV[VY] - mHoverWestSouth.mdV[VY]); } + + // UTILITIES + void render(); + void renderParcelCollision(); + + void renderRect( const LLVector3d &west_south_bottom, + const LLVector3d &east_north_top ); + void renderOneSegment(F32 x1, F32 y1, F32 x2, F32 y2, F32 height, U8 direction, LLViewerRegion* regionp); + void renderHighlightSegments(const U8* segments, LLViewerRegion* regionp); + void renderCollisionSegments(U8* segments, bool use_pass, LLViewerRegion* regionp); + + static S32 PARCEL_BAN_LINES_HIDE; + static S32 PARCEL_BAN_LINES_ON_COLLISION; + static S32 PARCEL_BAN_LINES_ON_PROXIMITY; + void resetCollisionTimer(); // Ban lines visibility timer + + void sendParcelGodForceOwner(const LLUUID& owner_id); + + // make the selected parcel a content parcel. + void sendParcelGodForceToContent(); + + // Pack information about this parcel and send it to the region + // containing the southwest corner of the selection. + // If want_reply_to_update, simulator will send back a ParcelProperties + // message. + void sendParcelPropertiesUpdate(LLParcel* parcel, bool use_agent_region = false); + + // Takes an Access List flag, like AL_ACCESS or AL_BAN + void sendParcelAccessListUpdate(U32 which); + + // Takes an Access List flag, like AL_ACCESS or AL_BAN + void sendParcelAccessListRequest(U32 flags); + + // Dwell is not part of the usual parcel update information because the + // simulator doesn't actually know the per-parcel dwell. Ack! We have + // to get it out of the database. + void sendParcelDwellRequest(); + + // If the point is outside the current hover parcel, request more data + void setHoverParcel(const LLVector3d& pos_global); + + bool canAgentBuyParcel(LLParcel*, bool forGroup) const; + +// void startClaimLand(bool is_for_group = false); + void startBuyLand(bool is_for_group = false); + void startSellLand(); + void startReleaseLand(); + void startDivideLand(); + void startJoinLand(); + void startDeedLandToGroup(); + void reclaimParcel(); + + void buyPass(); + + // Buying Land + + class ParcelBuyInfo; + ParcelBuyInfo* setupParcelBuy(const LLUUID& agent_id, + const LLUUID& session_id, + const LLUUID& group_id, + bool is_group_owned, + bool is_claim, + bool remove_contribution); + // callers responsibility to call deleteParcelBuy() on return value + void sendParcelBuy(ParcelBuyInfo*); + void deleteParcelBuy(ParcelBuyInfo* *info); + + void sendParcelDeed(const LLUUID& group_id); + + // Send the ParcelRelease message + void sendParcelRelease(); + + // accessors for mAgentParcel + const std::string& getAgentParcelName() const; + const S32 getAgentParcelId() const; + + // Create a landmark at the "appropriate" location for the + // currently selected parcel. + // *NOTE: Taken out 2005-03-21. Phoenix. + //void makeLandmarkAtSelection(); + + static void onStartMusicResponse(const LLUUID ®ion_id, const S32 &parcel_id, const std::string &url, const bool &play); + static void optionallyStartMusic(const std::string &music_url, const S32 &local_id, const LLUUID ®ion_id, bool switched_parcel); + + static void processParcelOverlay(LLMessageSystem *msg, void **user_data); + static void processParcelProperties(LLMessageSystem *msg, void **user_data); + static void processParcelAccessListReply(LLMessageSystem *msg, void **user); + static void processParcelDwellReply(LLMessageSystem *msg, void **user); + + void dump(); + + // Whether or not the collision border around the parcel is there because + // the agent is banned or not in the allowed group + bool isCollisionBanned(); + + boost::signals2::connection setTeleportFinishedCallback(teleport_finished_callback_t cb); + boost::signals2::connection setTeleportFailedCallback(teleport_failed_callback_t cb); + void onTeleportFinished(bool local, const LLVector3d& new_pos); + void onTeleportFailed(); + bool getTeleportInProgress(); + + static bool isParcelOwnedByAgent(const LLParcel* parcelp, U64 group_proxy_power); + static bool isParcelModifiableByAgent(const LLParcel* parcelp, U64 group_proxy_power); + +private: + static void sendParcelAccessListUpdate(U32 flags, const std::map& entries, LLViewerRegion* region, S32 parcel_local_id); + static void sendParcelExperienceUpdate( const U32 flags, uuid_vec_t experience_ids, LLViewerRegion* region, S32 parcel_local_id ); + static bool releaseAlertCB(const LLSD& notification, const LLSD& response); + + // If the user is claiming land and the current selection + // borders a piece of land the user already owns, ask if he + // wants to join this land to the other piece. + //void askJoinIfNecessary(ELandTransferType land_transfer_type); + //static void joinAlertCB(S32 option, void* data); + + //void buyAskMoney(ELandTransferType land_transfer_type); + + // move land from current owner to it's group. + void deedLandToGroup(); + + static bool deedAlertCB(const LLSD& notification, const LLSD& response); + + static bool callbackDivideLand(const LLSD& notification, const LLSD& response); + static bool callbackJoinLand(const LLSD& notification, const LLSD& response); + + //void finishClaim(bool user_to_user_sale, U32 join); + LLViewerTexture* getBlockedImage() const; + LLViewerTexture* getPassImage() const; + +private: + bool mSelected; + + LLParcel* mCurrentParcel; // selected parcel info + LLParcelSelectionHandle mCurrentParcelSelection; + LLParcelSelectionHandle mFloatingParcelSelection; + S32 mRequestResult; // result of last parcel request + LLVector3d mWestSouth; + LLVector3d mEastNorth; + F32 mSelectedDwell; + + LLParcel *mAgentParcel; // info for parcel agent is in + S32 mAgentParcelSequenceID; // incrementing counter to suppress out of order updates + + LLParcel* mHoverParcel; + S32 mHoverRequestResult; + LLVector3d mHoverWestSouth; + LLVector3d mHoverEastNorth; + + std::vector mObservers; + + bool mTeleportInProgress; + LLVector3d mTeleportInProgressPosition; + teleport_finished_signal_t mTeleportFinishedSignal; + teleport_failed_signal_t mTeleportFailedSignal; + + // Array of pieces of parcel edges to potentially draw + // Has (parcels_per_edge + 1) * (parcels_per_edge + 1) elements so + // we can represent edges of the grid. + // WEST_MASK = draw west edge + // SOUTH_MASK = draw south edge + S32 mParcelsPerEdge; + U8* mHighlightSegments; + U8* mAgentParcelOverlay; + + // Raw data buffer for unpacking parcel overlay chunks + // Size = parcels_per_edge * parcels_per_edge / parcel_overlay_chunks + static U8* sPackedOverlay; + + // Watch for pending collisions with a parcel you can't access. + // If it's coming, draw the parcel's boundaries. + LLParcel* mCollisionParcel; + U8* mCollisionSegments; + bool mRenderCollision; + bool mRenderSelection; + S32 mCollisionBanned; + LLFrameTimer mCollisionTimer; + LLViewerTexture* mBlockedImage; + LLViewerTexture* mPassImage; + + // Media + S32 mMediaParcelId; + U64 mMediaRegionId; +}; + + +void sanitize_corners(const LLVector3d &corner1, const LLVector3d &corner2, + LLVector3d &west_south_bottom, LLVector3d &east_north_top); + +#endif diff --git a/indra/newview/llviewerparceloverlay.h b/indra/newview/llviewerparceloverlay.h index 0fce8f098d..d78005e376 100644 --- a/indra/newview/llviewerparceloverlay.h +++ b/indra/newview/llviewerparceloverlay.h @@ -1,128 +1,128 @@ -/** - * @file llviewerparceloverlay.h - * @brief LLViewerParcelOverlay class header file - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVIEWERPARCELOVERLAY_H -#define LL_LLVIEWERPARCELOVERLAY_H - -// The ownership data for land parcels. -// One of these structures per region. - -#include "llbbox.h" -#include "llframetimer.h" -#include "lluuid.h" -#include "llviewertexture.h" -#include "llgl.h" - -class LLViewerRegion; -class LLVector3; -class LLColor4U; -class LLVector2; - -class LLViewerParcelOverlay : public LLGLUpdate -{ -public: - LLViewerParcelOverlay(LLViewerRegion* region, F32 region_width_meters); - ~LLViewerParcelOverlay(); - - // ACCESS - LLViewerTexture* getTexture() const { return mTexture; } - - bool isOwned(const LLVector3& pos) const; - bool isOwnedSelf(const LLVector3& pos) const; - bool isOwnedGroup(const LLVector3& pos) const; - bool isOwnedOther(const LLVector3& pos) const; - - // "encroaches" means the prim hangs over the parcel, but its center - // might be in another parcel. for now, we simply test axis aligned - // bounding boxes which isn't perfect, but is close - bool encroachesOwned(const std::vector& boxes) const; - bool encroachesOnUnowned(const std::vector& boxes) const; - bool encroachesOnNearbyParcel(const std::vector& boxes) const; - - bool isSoundLocal(const LLVector3& pos) const; - - bool isBuildCameraAllowed(const LLVector3& pos) const; - F32 getOwnedRatio() const; - - // Returns the number of vertices drawn - void renderPropertyLines(); - void renderPropertyLinesOnMinimap(F32 scale_pixels_per_meter, const F32* parcel_outline_color); - - U8 ownership( const LLVector3& pos) const; - U8 parcelLineFlags( const LLVector3& pos) const; - U8 parcelLineFlags(S32 row, S32 col) const; - - // MANIPULATE - void uncompressLandOverlay(S32 chunk, U8 *compressed_overlay); - - // Indicate property lines and overlay texture need to be rebuilt. - void setDirty(); - - void idleUpdate(bool update_now = false); - void updateGL(); - -private: - // This is in parcel rows and columns, not grid rows and columns - // Stored in bottom three bits. - U8 ownership(S32 row, S32 col) const - { return parcelFlags(row, col, (U8)0x7); } - - U8 parcelFlags(S32 row, S32 col, U8 flags) const; - - void addPropertyLine(F32 start_x, F32 start_y, F32 dx, F32 dy, F32 tick_dx, F32 tick_dy, const LLColor4U& color); - - void updateOverlayTexture(); - void updatePropertyLines(); - -private: - // Back pointer to the region that owns this structure. - LLViewerRegion* mRegion; - - S32 mParcelGridsPerEdge; - - LLPointer mTexture; - LLPointer mImageRaw; - - // Size: mParcelGridsPerEdge * mParcelGridsPerEdge - // Each value is 0-3, PARCEL_AVAIL to PARCEL_SELF in the two low bits - // and other flags in the upper bits. - U8 *mOwnership; - - // Update propery lines and overlay texture - bool mDirty; - LLFrameTimer mTimeSinceLastUpdate; - S32 mOverlayTextureIdx; - - struct Edge - { - std::vector vertices; - LLColor4U color; - }; - - std::vector mEdges; -}; - -#endif +/** + * @file llviewerparceloverlay.h + * @brief LLViewerParcelOverlay class header file + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVIEWERPARCELOVERLAY_H +#define LL_LLVIEWERPARCELOVERLAY_H + +// The ownership data for land parcels. +// One of these structures per region. + +#include "llbbox.h" +#include "llframetimer.h" +#include "lluuid.h" +#include "llviewertexture.h" +#include "llgl.h" + +class LLViewerRegion; +class LLVector3; +class LLColor4U; +class LLVector2; + +class LLViewerParcelOverlay : public LLGLUpdate +{ +public: + LLViewerParcelOverlay(LLViewerRegion* region, F32 region_width_meters); + ~LLViewerParcelOverlay(); + + // ACCESS + LLViewerTexture* getTexture() const { return mTexture; } + + bool isOwned(const LLVector3& pos) const; + bool isOwnedSelf(const LLVector3& pos) const; + bool isOwnedGroup(const LLVector3& pos) const; + bool isOwnedOther(const LLVector3& pos) const; + + // "encroaches" means the prim hangs over the parcel, but its center + // might be in another parcel. for now, we simply test axis aligned + // bounding boxes which isn't perfect, but is close + bool encroachesOwned(const std::vector& boxes) const; + bool encroachesOnUnowned(const std::vector& boxes) const; + bool encroachesOnNearbyParcel(const std::vector& boxes) const; + + bool isSoundLocal(const LLVector3& pos) const; + + bool isBuildCameraAllowed(const LLVector3& pos) const; + F32 getOwnedRatio() const; + + // Returns the number of vertices drawn + void renderPropertyLines(); + void renderPropertyLinesOnMinimap(F32 scale_pixels_per_meter, const F32* parcel_outline_color); + + U8 ownership( const LLVector3& pos) const; + U8 parcelLineFlags( const LLVector3& pos) const; + U8 parcelLineFlags(S32 row, S32 col) const; + + // MANIPULATE + void uncompressLandOverlay(S32 chunk, U8 *compressed_overlay); + + // Indicate property lines and overlay texture need to be rebuilt. + void setDirty(); + + void idleUpdate(bool update_now = false); + void updateGL(); + +private: + // This is in parcel rows and columns, not grid rows and columns + // Stored in bottom three bits. + U8 ownership(S32 row, S32 col) const + { return parcelFlags(row, col, (U8)0x7); } + + U8 parcelFlags(S32 row, S32 col, U8 flags) const; + + void addPropertyLine(F32 start_x, F32 start_y, F32 dx, F32 dy, F32 tick_dx, F32 tick_dy, const LLColor4U& color); + + void updateOverlayTexture(); + void updatePropertyLines(); + +private: + // Back pointer to the region that owns this structure. + LLViewerRegion* mRegion; + + S32 mParcelGridsPerEdge; + + LLPointer mTexture; + LLPointer mImageRaw; + + // Size: mParcelGridsPerEdge * mParcelGridsPerEdge + // Each value is 0-3, PARCEL_AVAIL to PARCEL_SELF in the two low bits + // and other flags in the upper bits. + U8 *mOwnership; + + // Update propery lines and overlay texture + bool mDirty; + LLFrameTimer mTimeSinceLastUpdate; + S32 mOverlayTextureIdx; + + struct Edge + { + std::vector vertices; + LLColor4U color; + }; + + std::vector mEdges; +}; + +#endif diff --git a/indra/newview/llviewerpartsim.cpp b/indra/newview/llviewerpartsim.cpp index 932ab7a61d..30d17fe532 100644 --- a/indra/newview/llviewerpartsim.cpp +++ b/indra/newview/llviewerpartsim.cpp @@ -1,896 +1,896 @@ -/** - * @file llviewerpartsim.cpp - * @brief LLViewerPart class implementation - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llviewerpartsim.h" - -#include "llviewercontrol.h" - -#include "llagent.h" -#include "llviewercamera.h" -#include "llviewerobjectlist.h" -#include "llviewerpartsource.h" -#include "llviewerregion.h" -#include "llvopartgroup.h" -#include "llworld.h" -#include "llmutelist.h" -#include "pipeline.h" -#include "llspatialpartition.h" -#include "llvoavatarself.h" -#include "llvovolume.h" - -const F32 PART_SIM_BOX_SIDE = 16.f; - -//static -S32 LLViewerPartSim::sMaxParticleCount = 0; -S32 LLViewerPartSim::sParticleCount = 0; -S32 LLViewerPartSim::sParticleCount2 = 0; -// This controls how greedy individual particle burst sources are allowed to be, and adapts according to how near the particle-count limit we are. -F32 LLViewerPartSim::sParticleAdaptiveRate = 0.0625f; -F32 LLViewerPartSim::sParticleBurstRate = 0.5f; - -//static -const S32 LLViewerPartSim::MAX_PART_COUNT = 8192; -const F32 LLViewerPartSim::PART_THROTTLE_THRESHOLD = 0.9f; -const F32 LLViewerPartSim::PART_ADAPT_RATE_MULT = 2.0f; - -//static -const F32 LLViewerPartSim::PART_THROTTLE_RESCALE = PART_THROTTLE_THRESHOLD / (1.0f-PART_THROTTLE_THRESHOLD); -const F32 LLViewerPartSim::PART_ADAPT_RATE_MULT_RECIP = 1.0f/PART_ADAPT_RATE_MULT; - - -U32 LLViewerPart::sNextPartID = 1; - -F32 calc_desired_size(LLViewerCamera* camera, LLVector3 pos, LLVector2 scale) -{ - F32 desired_size = (pos - camera->getOrigin()).magVec(); - desired_size /= 4; - return llclamp(desired_size, scale.magVec()*0.5f, PART_SIM_BOX_SIDE*2); -} - -LLViewerPart::LLViewerPart() : - mPartID(0), - mLastUpdateTime(0.f), - mSkipOffset(0.f), - mVPCallback(NULL), - mImagep(NULL) -{ - mPartSourcep = NULL; - mParent = NULL; - mChild = NULL; - ++LLViewerPartSim::sParticleCount2 ; -} - -LLViewerPart::~LLViewerPart() -{ - if (mPartSourcep.notNull() && mPartSourcep->mLastPart == this) - { - mPartSourcep->mLastPart = NULL; - } - - //patch up holes in the ribbon - if (mParent) - { - llassert(mParent->mChild == this); - mParent->mChild = mChild; - } - - if (mChild) - { - llassert (mChild->mParent == this); - mChild->mParent = mParent; - } - - mPartSourcep = NULL; - - --LLViewerPartSim::sParticleCount2 ; -} - -void LLViewerPart::init(LLPointer sourcep, LLViewerTexture *imagep, LLVPCallback cb) -{ - mPartID = LLViewerPart::sNextPartID; - LLViewerPart::sNextPartID++; - mFlags = 0x00f; - mLastUpdateTime = 0.f; - mMaxAge = 10.f; - mSkipOffset = 0.0f; - - mVPCallback = cb; - mPartSourcep = sourcep; - - mImagep = imagep; -} - - -///////////////////////////// -// -// LLViewerPartGroup implementation -// -// - - -LLViewerPartGroup::LLViewerPartGroup(const LLVector3 ¢er_agent, const F32 box_side, bool hud) - : mHud(hud) -{ - mVOPartGroupp = NULL; - mUniformParticles = true; - - mRegionp = LLWorld::getInstance()->getRegionFromPosAgent(center_agent); - llassert_always(center_agent.isFinite()); - - if (!mRegionp) - { - //LL_WARNS() << "No region at position, using agent region!" << LL_ENDL; - mRegionp = gAgent.getRegion(); - } - mCenterAgent = center_agent; - mBoxRadius = F_SQRT3*box_side*0.5f; - - if (mHud) - { - mVOPartGroupp = (LLVOPartGroup *)gObjectList.createObjectViewer(LLViewerObject::LL_VO_HUD_PART_GROUP, getRegion()); - } - else - { - mVOPartGroupp = (LLVOPartGroup *)gObjectList.createObjectViewer(LLViewerObject::LL_VO_PART_GROUP, getRegion()); - } - mVOPartGroupp->setViewerPartGroup(this); - mVOPartGroupp->setPositionAgent(getCenterAgent()); - - mBoxSide = box_side; - - F32 scale = box_side * 0.5f; - - mVOPartGroupp->setScale(LLVector3(scale,scale,scale)); - - //gPipeline.addObject(mVOPartGroupp); - gPipeline.createObject(mVOPartGroupp); - - LLSpatialGroup* group = mVOPartGroupp->mDrawable->getSpatialGroup(); - - if (group != NULL) - { - LLVector3 center(group->getOctreeNode()->getCenter().getF32ptr()); - LLVector3 size(group->getOctreeNode()->getSize().getF32ptr()); - size += LLVector3(0.01f, 0.01f, 0.01f); - mMinObjPos = center - size; - mMaxObjPos = center + size; - } - else - { - // Not sure what else to set the obj bounds to when the drawable has no spatial group. - LLVector3 extents(mBoxRadius, mBoxRadius, mBoxRadius); - mMinObjPos = center_agent - extents; - mMaxObjPos = center_agent + extents; - } - - mSkippedTime = 0.f; - - static U32 id_seed = 0; - mID = ++id_seed; -} - -LLViewerPartGroup::~LLViewerPartGroup() -{ - cleanup(); - - S32 count = (S32) mParticles.size(); - for(S32 i = 0 ; i < count ; i++) - { - delete mParticles[i] ; - } - mParticles.clear(); - - LLViewerPartSim::decPartCount(count); -} - -void LLViewerPartGroup::cleanup() -{ - if (mVOPartGroupp) - { - if (!mVOPartGroupp->isDead()) - { - gObjectList.killObject(mVOPartGroupp); - } - mVOPartGroupp = NULL; - } -} - -bool LLViewerPartGroup::posInGroup(const LLVector3 &pos, const F32 desired_size) -{ - if ((pos.mV[VX] < mMinObjPos.mV[VX]) - || (pos.mV[VY] < mMinObjPos.mV[VY]) - || (pos.mV[VZ] < mMinObjPos.mV[VZ])) - { - return false; - } - - if ((pos.mV[VX] > mMaxObjPos.mV[VX]) - || (pos.mV[VY] > mMaxObjPos.mV[VY]) - || (pos.mV[VZ] > mMaxObjPos.mV[VZ])) - { - return false; - } - - if (desired_size > 0 && - (desired_size < mBoxRadius*0.5f || - desired_size > mBoxRadius*2.f)) - { - return false; - } - - return true; -} - - -bool LLViewerPartGroup::addPart(LLViewerPart* part, F32 desired_size) -{ - if (part->mFlags & LLPartData::LL_PART_HUD && !mHud) - { - return false; - } - - bool uniform_part = part->mScale.mV[0] == part->mScale.mV[1] && - !(part->mFlags & LLPartData::LL_PART_FOLLOW_VELOCITY_MASK); - - if (!posInGroup(part->mPosAgent, desired_size) || - (mUniformParticles && !uniform_part) || - (!mUniformParticles && uniform_part)) - { - return false; - } - - gPipeline.markRebuild(mVOPartGroupp->mDrawable, LLDrawable::REBUILD_ALL); - - mParticles.push_back(part); - part->mSkipOffset=mSkippedTime; - LLViewerPartSim::incPartCount(1); - return true; -} - - -void LLViewerPartGroup::updateParticles(const F32 lastdt) -{ - F32 dt; - - LLVector3 gravity(0.f, 0.f, GRAVITY); - - LLViewerPartSim::checkParticleCount(mParticles.size()); - - LLViewerCamera* camera = LLViewerCamera::getInstance(); - LLViewerRegion *regionp = getRegion(); - S32 end = (S32) mParticles.size(); - for (S32 i = 0 ; i < (S32)mParticles.size();) - { - LLVector3 a(0.f, 0.f, 0.f); - LLViewerPart* part = mParticles[i] ; - - dt = lastdt + mSkippedTime - part->mSkipOffset; - part->mSkipOffset = 0.f; - - // Update current time - const F32 cur_time = part->mLastUpdateTime + dt; - const F32 frac = cur_time / part->mMaxAge; - - // "Drift" the object based on the source object - if (part->mFlags & LLPartData::LL_PART_FOLLOW_SRC_MASK) - { - part->mPosAgent = part->mPartSourcep->mPosAgent; - part->mPosAgent += part->mPosOffset; - } - - // Do a custom callback if we have one... - if (part->mVPCallback) - { - (*part->mVPCallback)(*part, dt); - } - - if (part->mFlags & LLPartData::LL_PART_WIND_MASK) - { - part->mVelocity *= 1.f - 0.1f*dt; - part->mVelocity += 0.1f*dt*regionp->mWind.getVelocity(regionp->getPosRegionFromAgent(part->mPosAgent)); - } - - // Now do interpolation towards a target - if (part->mFlags & LLPartData::LL_PART_TARGET_POS_MASK) - { - F32 remaining = part->mMaxAge - part->mLastUpdateTime; - F32 step = dt / remaining; - - step = llclamp(step, 0.f, 0.1f); - step *= 5.f; - // we want a velocity that will result in reaching the target in the - // Interpolate towards the target. - LLVector3 delta_pos = part->mPartSourcep->mTargetPosAgent - part->mPosAgent; - - delta_pos /= remaining; - - part->mVelocity *= (1.f - step); - part->mVelocity += step*delta_pos; - } - - - if (part->mFlags & LLPartData::LL_PART_TARGET_LINEAR_MASK) - { - LLVector3 delta_pos = part->mPartSourcep->mTargetPosAgent - part->mPartSourcep->mPosAgent; - part->mPosAgent = part->mPartSourcep->mPosAgent; - part->mPosAgent += frac*delta_pos; - part->mVelocity = delta_pos; - } - else - { - // Do velocity interpolation - part->mPosAgent += dt*part->mVelocity; - part->mPosAgent += 0.5f*dt*dt*part->mAccel; - part->mVelocity += part->mAccel*dt; - } - - // Do a bounce test - if (part->mFlags & LLPartData::LL_PART_BOUNCE_MASK) - { - // Need to do point vs. plane check... - // For now, just check relative to object height... - F32 dz = part->mPosAgent.mV[VZ] - part->mPartSourcep->mPosAgent.mV[VZ]; - if (dz < 0) - { - part->mPosAgent.mV[VZ] += -2.f*dz; - part->mVelocity.mV[VZ] *= -0.75f; - } - } - - - // Reset the offset from the source position - if (part->mFlags & LLPartData::LL_PART_FOLLOW_SRC_MASK) - { - part->mPosOffset = part->mPosAgent; - part->mPosOffset -= part->mPartSourcep->mPosAgent; - } - - // Do color interpolation - if (part->mFlags & LLPartData::LL_PART_INTERP_COLOR_MASK) - { - part->mColor.setVec(part->mStartColor); - // note: LLColor4's v%k means multiply-alpha-only, - // LLColor4's v*k means multiply-rgb-only - part->mColor *= 1.f - frac; // rgb*k - part->mColor %= 1.f - frac; // alpha*k - part->mColor += frac%(frac*part->mEndColor); // rgb,alpha - } - - // Do scale interpolation - if (part->mFlags & LLPartData::LL_PART_INTERP_SCALE_MASK) - { - part->mScale.setVec(part->mStartScale); - part->mScale *= 1.f - frac; - part->mScale += frac*part->mEndScale; - } - - // Do glow interpolation - part->mGlow.mV[3] = (U8) ll_round(lerp(part->mStartGlow, part->mEndGlow, frac)*255.f); - - // Set the last update time to now. - part->mLastUpdateTime = cur_time; - - - // Kill dead particles (either flagged dead, or too old) - if ((part->mLastUpdateTime > part->mMaxAge) || (LLViewerPart::LL_PART_DEAD_MASK == part->mFlags)) - { - mParticles[i] = mParticles.back() ; - mParticles.pop_back() ; - delete part ; - } - else - { - F32 desired_size = calc_desired_size(camera, part->mPosAgent, part->mScale); - if (!posInGroup(part->mPosAgent, desired_size)) - { - // Transfer particles between groups - LLViewerPartSim::getInstance()->put(part) ; - mParticles[i] = mParticles.back() ; - mParticles.pop_back() ; - } - else - { - i++ ; - } - } - } - - S32 removed = end - (S32)mParticles.size(); - if (removed > 0) - { - // we removed one or more particles, so flag this group for update - if (mVOPartGroupp.notNull()) - { - gPipeline.markRebuild(mVOPartGroupp->mDrawable, LLDrawable::REBUILD_ALL); - } - LLViewerPartSim::decPartCount(removed); - } - - // Kill the viewer object if this particle group is empty - if (mParticles.empty()) - { - gObjectList.killObject(mVOPartGroupp); - mVOPartGroupp = NULL; - } - - LLViewerPartSim::checkParticleCount() ; -} - - -void LLViewerPartGroup::shift(const LLVector3 &offset) -{ - mCenterAgent += offset; - mMinObjPos += offset; - mMaxObjPos += offset; - - for (S32 i = 0 ; i < (S32)mParticles.size(); i++) - { - mParticles[i]->mPosAgent += offset; - } -} - -void LLViewerPartGroup::removeParticlesByID(const U32 source_id) -{ - for (S32 i = 0; i < (S32)mParticles.size(); i++) - { - if(mParticles[i]->mPartSourcep->getID() == source_id) - { - mParticles[i]->mFlags = LLViewerPart::LL_PART_DEAD_MASK; - } - } -} - -////////////////////////////////// -// -// LLViewerPartSim implementation -// -// - -//static -void LLViewerPartSim::checkParticleCount(U32 size) -{ - if(LLViewerPartSim::sParticleCount2 != LLViewerPartSim::sParticleCount) - { - LL_ERRS() << "sParticleCount: " << LLViewerPartSim::sParticleCount << " ; sParticleCount2: " << LLViewerPartSim::sParticleCount2 << LL_ENDL ; - } - - if(size > (U32)LLViewerPartSim::sParticleCount2) - { - LL_ERRS() << "curren particle size: " << LLViewerPartSim::sParticleCount2 << " array size: " << size << LL_ENDL ; - } -} - -LLViewerPartSim::LLViewerPartSim() -{ - sMaxParticleCount = llmin(gSavedSettings.getS32("RenderMaxPartCount"), LL_MAX_PARTICLE_COUNT); - static U32 id_seed = 0; - mID = ++id_seed; -} - -//enable/disable particle system -void LLViewerPartSim::enable(bool enabled) -{ - if(!enabled && sMaxParticleCount > 0) - { - sMaxParticleCount = 0; //disable - } - else if(enabled && sMaxParticleCount < 1) - { - sMaxParticleCount = llmin(gSavedSettings.getS32("RenderMaxPartCount"), LL_MAX_PARTICLE_COUNT); - } - - return; -} - -void LLViewerPartSim::destroyClass() -{ - S32 i; - S32 count; - - // Kill all of the groups (and particles) - count = (S32) mViewerPartGroups.size(); - for (i = 0; i < count; i++) - { - delete mViewerPartGroups[i]; - } - mViewerPartGroups.clear(); - - // Kill all of the sources - mViewerPartSources.clear(); -} - -//static -bool LLViewerPartSim::shouldAddPart() -{ - if (sParticleCount >= MAX_PART_COUNT) - { - return false; - } - - if (sParticleCount > PART_THROTTLE_THRESHOLD*sMaxParticleCount) - { - F32 frac = (F32)sParticleCount/(F32)sMaxParticleCount; - frac -= PART_THROTTLE_THRESHOLD; - frac *= PART_THROTTLE_RESCALE; - if (ll_frand() < frac) - { - // Skip... - return false; - } - } - - // Check frame rate, and don't add more if the viewer is really slow - const F32 MIN_FRAME_RATE_FOR_NEW_PARTICLES = 4.f; - if (gFPSClamped < MIN_FRAME_RATE_FOR_NEW_PARTICLES) - { - return false; - } - - return true; -} - -void LLViewerPartSim::addPart(LLViewerPart* part) -{ - if (sParticleCount < MAX_PART_COUNT) - { - put(part); - } - else - { - //delete the particle if can not add it in - delete part ; - part = NULL ; - } -} - - -LLViewerPartGroup *LLViewerPartSim::put(LLViewerPart* part) -{ - const F32 MAX_MAG = 1000000.f*1000000.f; // 1 million - LLViewerPartGroup *return_group = NULL ; - if (part->mPosAgent.magVecSquared() > MAX_MAG || !part->mPosAgent.isFinite()) - { -#if 0 && !LL_RELEASE_FOR_DOWNLOAD - LL_WARNS() << "LLViewerPartSim::put Part out of range!" << LL_ENDL; - LL_WARNS() << part->mPosAgent << LL_ENDL; -#endif - } - else - { - LLViewerCamera* camera = LLViewerCamera::getInstance(); - F32 desired_size = calc_desired_size(camera, part->mPosAgent, part->mScale); - - S32 count = (S32) mViewerPartGroups.size(); - for (S32 i = 0; i < count; i++) - { - if (mViewerPartGroups[i]->addPart(part, desired_size)) - { - // We found a spatial group that we fit into, add us and exit - return_group = mViewerPartGroups[i]; - break ; - } - } - - // Hmm, we didn't fit in any of the existing spatial groups - // Create a new one... - if(!return_group) - { - llassert_always(part->mPosAgent.isFinite()); - LLViewerPartGroup *groupp = createViewerPartGroup(part->mPosAgent, desired_size, part->mFlags & LLPartData::LL_PART_HUD); - groupp->mUniformParticles = (part->mScale.mV[0] == part->mScale.mV[1] && - !(part->mFlags & LLPartData::LL_PART_FOLLOW_VELOCITY_MASK)); - if (!groupp->addPart(part)) - { - LL_WARNS() << "LLViewerPartSim::put - Particle didn't go into its box!" << LL_ENDL; - LL_INFOS() << groupp->getCenterAgent() << LL_ENDL; - LL_INFOS() << part->mPosAgent << LL_ENDL; - mViewerPartGroups.pop_back() ; - delete groupp; - groupp = NULL ; - } - return_group = groupp; - } - } - - if(!return_group) //failed to insert the particle - { - delete part ; - part = NULL ; - } - - return return_group ; -} - -LLViewerPartGroup *LLViewerPartSim::createViewerPartGroup(const LLVector3 &pos_agent, const F32 desired_size, bool hud) -{ - //find a box that has a center position divisible by PART_SIM_BOX_SIDE that encompasses - //pos_agent - LLViewerPartGroup *groupp = new LLViewerPartGroup(pos_agent, desired_size, hud); - mViewerPartGroups.push_back(groupp); - return groupp; -} - - -void LLViewerPartSim::shift(const LLVector3 &offset) -{ - S32 i; - S32 count; - - count = (S32) mViewerPartSources.size(); - for (i = 0; i < count; i++) - { - mViewerPartSources[i]->mPosAgent += offset; - mViewerPartSources[i]->mTargetPosAgent += offset; - mViewerPartSources[i]->mLastUpdatePosAgent += offset; - } - - count = (S32) mViewerPartGroups.size(); - for (i = 0; i < count; i++) - { - mViewerPartGroups[i]->shift(offset); - } -} - -static LLTrace::BlockTimerStatHandle FTM_SIMULATE_PARTICLES("Simulate Particles"); - -void LLViewerPartSim::updateSimulation() -{ - static LLFrameTimer update_timer; - - const F32 dt = llmin(update_timer.getElapsedTimeAndResetF32(), 0.1f); - - if (!(gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_PARTICLES))) - { - return; - } - - LL_RECORD_BLOCK_TIME(FTM_SIMULATE_PARTICLES); - - // Start at a random particle system so the same - // particle system doesn't always get first pick at the - // particles. Theoretically we'd want to do this in distance - // order or something, but sorting particle sources will be a big - // pain. - S32 i; - S32 count = (S32) mViewerPartSources.size(); - S32 start = (S32)ll_frand((F32)count); - S32 dir = 1; - S32 deldir = 0; - if (ll_frand() > 0.5f) - { - dir = -1; - deldir = -1; - } - - S32 num_updates = 0; - for (i = start; num_updates < count;) - { - if (i >= count) - { - i = 0; - } - if (i < 0) - { - i = count - 1; - } - - if (!mViewerPartSources[i]->isDead()) - { - bool upd = true; - LLViewerObject* vobj = mViewerPartSources[i]->mSourceObjectp; - - if (vobj && vobj->isAvatar() && ((LLVOAvatar*)vobj)->isInMuteList()) - { - upd = false; - } - - if(vobj && vobj->isOwnerInMuteList(mViewerPartSources[i]->getOwnerUUID())) - { - upd = false; - } - - if (upd && vobj && (vobj->getPCode() == LL_PCODE_VOLUME)) - { - if(vobj->getAvatar() && vobj->getAvatar()->isTooComplex() && vobj->getAvatar()->isTooSlow()) - { - upd = false; - } - - LLVOVolume* vvo = (LLVOVolume *)vobj; - if (!LLPipeline::sRenderAttachedParticles && vvo && vvo->isAttachment()) - { - upd = false; - } - } - - if (upd) - { - mViewerPartSources[i]->update(dt); - } - } - - if (mViewerPartSources[i]->isDead()) - { - mViewerPartSources.erase(mViewerPartSources.begin() + i); - count--; - i+=deldir; - } - else - { - i += dir; - } - num_updates++; - } - - count = (S32) mViewerPartGroups.size(); - for (i = 0; i < count; i++) - { - LLViewerObject* vobj = mViewerPartGroups[i]->mVOPartGroupp; - - S32 visirate = 1; - if (vobj && !vobj->isDead() && vobj->mDrawable && !vobj->mDrawable->isDead()) - { - LLSpatialGroup* group = vobj->mDrawable->getSpatialGroup(); - if (group && !group->isVisible()) // && !group->isState(LLSpatialGroup::OBJECT_DIRTY)) - { - visirate = 8; - } - } - - if ((LLDrawable::getCurrentFrame()+mViewerPartGroups[i]->mID)%visirate == 0) - { - if (vobj && !vobj->isDead()) - { - gPipeline.markRebuild(vobj->mDrawable, LLDrawable::REBUILD_ALL); - } - mViewerPartGroups[i]->updateParticles(dt * visirate); - mViewerPartGroups[i]->mSkippedTime=0.0f; - if (!mViewerPartGroups[i]->getCount()) - { - delete mViewerPartGroups[i]; - mViewerPartGroups.erase(mViewerPartGroups.begin() + i); - i--; - count--; - } - } - else - { - mViewerPartGroups[i]->mSkippedTime+=dt; - } - - } - if (LLDrawable::getCurrentFrame()%16==0) - { - if (sParticleCount > sMaxParticleCount * 0.875f - && sParticleAdaptiveRate < 2.0f) - { - sParticleAdaptiveRate *= PART_ADAPT_RATE_MULT; - } - else - { - if (sParticleCount < sMaxParticleCount * 0.5f - && sParticleAdaptiveRate > 0.03125f) - { - sParticleAdaptiveRate *= PART_ADAPT_RATE_MULT_RECIP; - } - } - } - - updatePartBurstRate() ; - - //LL_INFOS() << "Particles: " << sParticleCount << " Adaptive Rate: " << sParticleAdaptiveRate << LL_ENDL; -} - -void LLViewerPartSim::updatePartBurstRate() -{ - if (!(LLDrawable::getCurrentFrame() & 0xf)) - { - if (sParticleCount >= MAX_PART_COUNT) //set rate to zero - { - sParticleBurstRate = 0.0f ; - } - else if(sParticleCount > 0) - { - if(sParticleBurstRate > 0.0000001f) - { - F32 total_particles = sParticleCount / sParticleBurstRate ; //estimated - F32 new_rate = llclamp(0.9f * sMaxParticleCount / total_particles, 0.0f, 1.0f) ; - F32 delta_rate_threshold = llmin(0.1f * llmax(new_rate, sParticleBurstRate), 0.1f) ; - F32 delta_rate = llclamp(new_rate - sParticleBurstRate, -1.0f * delta_rate_threshold, delta_rate_threshold) ; - - sParticleBurstRate = llclamp(sParticleBurstRate + 0.5f * delta_rate, 0.0f, 1.0f) ; - } - else - { - sParticleBurstRate += 0.0000001f ; - } - } - else - { - sParticleBurstRate += 0.00125f ; - } - } -} - -void LLViewerPartSim::addPartSource(LLPointer sourcep) -{ - if (!sourcep) - { - LL_WARNS() << "Null part source!" << LL_ENDL; - return; - } - sourcep->setStart() ; - mViewerPartSources.push_back(sourcep); -} - -void LLViewerPartSim::removeLastCreatedSource() -{ - mViewerPartSources.pop_back(); -} - -void LLViewerPartSim::cleanupRegion(LLViewerRegion *regionp) -{ - for (group_list_t::iterator i = mViewerPartGroups.begin(); i != mViewerPartGroups.end(); ) - { - group_list_t::iterator iter = i++; - - if ((*iter)->getRegion() == regionp) - { - delete *iter; - i = mViewerPartGroups.erase(iter); - } - } -} - -void LLViewerPartSim::clearParticlesByID(const U32 system_id) -{ - for (group_list_t::iterator g = mViewerPartGroups.begin(); g != mViewerPartGroups.end(); ++g) - { - (*g)->removeParticlesByID(system_id); - } - for (source_list_t::iterator i = mViewerPartSources.begin(); i != mViewerPartSources.end(); ++i) - { - if ((*i)->getID() == system_id) - { - (*i)->setDead(); - break; - } - } - -} - -void LLViewerPartSim::clearParticlesByOwnerID(const LLUUID& task_id) -{ - for (source_list_t::iterator iter = mViewerPartSources.begin(); iter != mViewerPartSources.end(); ++iter) - { - if ((*iter)->getOwnerUUID() == task_id) - { - clearParticlesByID((*iter)->getID()); - } - } -} +/** + * @file llviewerpartsim.cpp + * @brief LLViewerPart class implementation + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llviewerpartsim.h" + +#include "llviewercontrol.h" + +#include "llagent.h" +#include "llviewercamera.h" +#include "llviewerobjectlist.h" +#include "llviewerpartsource.h" +#include "llviewerregion.h" +#include "llvopartgroup.h" +#include "llworld.h" +#include "llmutelist.h" +#include "pipeline.h" +#include "llspatialpartition.h" +#include "llvoavatarself.h" +#include "llvovolume.h" + +const F32 PART_SIM_BOX_SIDE = 16.f; + +//static +S32 LLViewerPartSim::sMaxParticleCount = 0; +S32 LLViewerPartSim::sParticleCount = 0; +S32 LLViewerPartSim::sParticleCount2 = 0; +// This controls how greedy individual particle burst sources are allowed to be, and adapts according to how near the particle-count limit we are. +F32 LLViewerPartSim::sParticleAdaptiveRate = 0.0625f; +F32 LLViewerPartSim::sParticleBurstRate = 0.5f; + +//static +const S32 LLViewerPartSim::MAX_PART_COUNT = 8192; +const F32 LLViewerPartSim::PART_THROTTLE_THRESHOLD = 0.9f; +const F32 LLViewerPartSim::PART_ADAPT_RATE_MULT = 2.0f; + +//static +const F32 LLViewerPartSim::PART_THROTTLE_RESCALE = PART_THROTTLE_THRESHOLD / (1.0f-PART_THROTTLE_THRESHOLD); +const F32 LLViewerPartSim::PART_ADAPT_RATE_MULT_RECIP = 1.0f/PART_ADAPT_RATE_MULT; + + +U32 LLViewerPart::sNextPartID = 1; + +F32 calc_desired_size(LLViewerCamera* camera, LLVector3 pos, LLVector2 scale) +{ + F32 desired_size = (pos - camera->getOrigin()).magVec(); + desired_size /= 4; + return llclamp(desired_size, scale.magVec()*0.5f, PART_SIM_BOX_SIDE*2); +} + +LLViewerPart::LLViewerPart() : + mPartID(0), + mLastUpdateTime(0.f), + mSkipOffset(0.f), + mVPCallback(NULL), + mImagep(NULL) +{ + mPartSourcep = NULL; + mParent = NULL; + mChild = NULL; + ++LLViewerPartSim::sParticleCount2 ; +} + +LLViewerPart::~LLViewerPart() +{ + if (mPartSourcep.notNull() && mPartSourcep->mLastPart == this) + { + mPartSourcep->mLastPart = NULL; + } + + //patch up holes in the ribbon + if (mParent) + { + llassert(mParent->mChild == this); + mParent->mChild = mChild; + } + + if (mChild) + { + llassert (mChild->mParent == this); + mChild->mParent = mParent; + } + + mPartSourcep = NULL; + + --LLViewerPartSim::sParticleCount2 ; +} + +void LLViewerPart::init(LLPointer sourcep, LLViewerTexture *imagep, LLVPCallback cb) +{ + mPartID = LLViewerPart::sNextPartID; + LLViewerPart::sNextPartID++; + mFlags = 0x00f; + mLastUpdateTime = 0.f; + mMaxAge = 10.f; + mSkipOffset = 0.0f; + + mVPCallback = cb; + mPartSourcep = sourcep; + + mImagep = imagep; +} + + +///////////////////////////// +// +// LLViewerPartGroup implementation +// +// + + +LLViewerPartGroup::LLViewerPartGroup(const LLVector3 ¢er_agent, const F32 box_side, bool hud) + : mHud(hud) +{ + mVOPartGroupp = NULL; + mUniformParticles = true; + + mRegionp = LLWorld::getInstance()->getRegionFromPosAgent(center_agent); + llassert_always(center_agent.isFinite()); + + if (!mRegionp) + { + //LL_WARNS() << "No region at position, using agent region!" << LL_ENDL; + mRegionp = gAgent.getRegion(); + } + mCenterAgent = center_agent; + mBoxRadius = F_SQRT3*box_side*0.5f; + + if (mHud) + { + mVOPartGroupp = (LLVOPartGroup *)gObjectList.createObjectViewer(LLViewerObject::LL_VO_HUD_PART_GROUP, getRegion()); + } + else + { + mVOPartGroupp = (LLVOPartGroup *)gObjectList.createObjectViewer(LLViewerObject::LL_VO_PART_GROUP, getRegion()); + } + mVOPartGroupp->setViewerPartGroup(this); + mVOPartGroupp->setPositionAgent(getCenterAgent()); + + mBoxSide = box_side; + + F32 scale = box_side * 0.5f; + + mVOPartGroupp->setScale(LLVector3(scale,scale,scale)); + + //gPipeline.addObject(mVOPartGroupp); + gPipeline.createObject(mVOPartGroupp); + + LLSpatialGroup* group = mVOPartGroupp->mDrawable->getSpatialGroup(); + + if (group != NULL) + { + LLVector3 center(group->getOctreeNode()->getCenter().getF32ptr()); + LLVector3 size(group->getOctreeNode()->getSize().getF32ptr()); + size += LLVector3(0.01f, 0.01f, 0.01f); + mMinObjPos = center - size; + mMaxObjPos = center + size; + } + else + { + // Not sure what else to set the obj bounds to when the drawable has no spatial group. + LLVector3 extents(mBoxRadius, mBoxRadius, mBoxRadius); + mMinObjPos = center_agent - extents; + mMaxObjPos = center_agent + extents; + } + + mSkippedTime = 0.f; + + static U32 id_seed = 0; + mID = ++id_seed; +} + +LLViewerPartGroup::~LLViewerPartGroup() +{ + cleanup(); + + S32 count = (S32) mParticles.size(); + for(S32 i = 0 ; i < count ; i++) + { + delete mParticles[i] ; + } + mParticles.clear(); + + LLViewerPartSim::decPartCount(count); +} + +void LLViewerPartGroup::cleanup() +{ + if (mVOPartGroupp) + { + if (!mVOPartGroupp->isDead()) + { + gObjectList.killObject(mVOPartGroupp); + } + mVOPartGroupp = NULL; + } +} + +bool LLViewerPartGroup::posInGroup(const LLVector3 &pos, const F32 desired_size) +{ + if ((pos.mV[VX] < mMinObjPos.mV[VX]) + || (pos.mV[VY] < mMinObjPos.mV[VY]) + || (pos.mV[VZ] < mMinObjPos.mV[VZ])) + { + return false; + } + + if ((pos.mV[VX] > mMaxObjPos.mV[VX]) + || (pos.mV[VY] > mMaxObjPos.mV[VY]) + || (pos.mV[VZ] > mMaxObjPos.mV[VZ])) + { + return false; + } + + if (desired_size > 0 && + (desired_size < mBoxRadius*0.5f || + desired_size > mBoxRadius*2.f)) + { + return false; + } + + return true; +} + + +bool LLViewerPartGroup::addPart(LLViewerPart* part, F32 desired_size) +{ + if (part->mFlags & LLPartData::LL_PART_HUD && !mHud) + { + return false; + } + + bool uniform_part = part->mScale.mV[0] == part->mScale.mV[1] && + !(part->mFlags & LLPartData::LL_PART_FOLLOW_VELOCITY_MASK); + + if (!posInGroup(part->mPosAgent, desired_size) || + (mUniformParticles && !uniform_part) || + (!mUniformParticles && uniform_part)) + { + return false; + } + + gPipeline.markRebuild(mVOPartGroupp->mDrawable, LLDrawable::REBUILD_ALL); + + mParticles.push_back(part); + part->mSkipOffset=mSkippedTime; + LLViewerPartSim::incPartCount(1); + return true; +} + + +void LLViewerPartGroup::updateParticles(const F32 lastdt) +{ + F32 dt; + + LLVector3 gravity(0.f, 0.f, GRAVITY); + + LLViewerPartSim::checkParticleCount(mParticles.size()); + + LLViewerCamera* camera = LLViewerCamera::getInstance(); + LLViewerRegion *regionp = getRegion(); + S32 end = (S32) mParticles.size(); + for (S32 i = 0 ; i < (S32)mParticles.size();) + { + LLVector3 a(0.f, 0.f, 0.f); + LLViewerPart* part = mParticles[i] ; + + dt = lastdt + mSkippedTime - part->mSkipOffset; + part->mSkipOffset = 0.f; + + // Update current time + const F32 cur_time = part->mLastUpdateTime + dt; + const F32 frac = cur_time / part->mMaxAge; + + // "Drift" the object based on the source object + if (part->mFlags & LLPartData::LL_PART_FOLLOW_SRC_MASK) + { + part->mPosAgent = part->mPartSourcep->mPosAgent; + part->mPosAgent += part->mPosOffset; + } + + // Do a custom callback if we have one... + if (part->mVPCallback) + { + (*part->mVPCallback)(*part, dt); + } + + if (part->mFlags & LLPartData::LL_PART_WIND_MASK) + { + part->mVelocity *= 1.f - 0.1f*dt; + part->mVelocity += 0.1f*dt*regionp->mWind.getVelocity(regionp->getPosRegionFromAgent(part->mPosAgent)); + } + + // Now do interpolation towards a target + if (part->mFlags & LLPartData::LL_PART_TARGET_POS_MASK) + { + F32 remaining = part->mMaxAge - part->mLastUpdateTime; + F32 step = dt / remaining; + + step = llclamp(step, 0.f, 0.1f); + step *= 5.f; + // we want a velocity that will result in reaching the target in the + // Interpolate towards the target. + LLVector3 delta_pos = part->mPartSourcep->mTargetPosAgent - part->mPosAgent; + + delta_pos /= remaining; + + part->mVelocity *= (1.f - step); + part->mVelocity += step*delta_pos; + } + + + if (part->mFlags & LLPartData::LL_PART_TARGET_LINEAR_MASK) + { + LLVector3 delta_pos = part->mPartSourcep->mTargetPosAgent - part->mPartSourcep->mPosAgent; + part->mPosAgent = part->mPartSourcep->mPosAgent; + part->mPosAgent += frac*delta_pos; + part->mVelocity = delta_pos; + } + else + { + // Do velocity interpolation + part->mPosAgent += dt*part->mVelocity; + part->mPosAgent += 0.5f*dt*dt*part->mAccel; + part->mVelocity += part->mAccel*dt; + } + + // Do a bounce test + if (part->mFlags & LLPartData::LL_PART_BOUNCE_MASK) + { + // Need to do point vs. plane check... + // For now, just check relative to object height... + F32 dz = part->mPosAgent.mV[VZ] - part->mPartSourcep->mPosAgent.mV[VZ]; + if (dz < 0) + { + part->mPosAgent.mV[VZ] += -2.f*dz; + part->mVelocity.mV[VZ] *= -0.75f; + } + } + + + // Reset the offset from the source position + if (part->mFlags & LLPartData::LL_PART_FOLLOW_SRC_MASK) + { + part->mPosOffset = part->mPosAgent; + part->mPosOffset -= part->mPartSourcep->mPosAgent; + } + + // Do color interpolation + if (part->mFlags & LLPartData::LL_PART_INTERP_COLOR_MASK) + { + part->mColor.setVec(part->mStartColor); + // note: LLColor4's v%k means multiply-alpha-only, + // LLColor4's v*k means multiply-rgb-only + part->mColor *= 1.f - frac; // rgb*k + part->mColor %= 1.f - frac; // alpha*k + part->mColor += frac%(frac*part->mEndColor); // rgb,alpha + } + + // Do scale interpolation + if (part->mFlags & LLPartData::LL_PART_INTERP_SCALE_MASK) + { + part->mScale.setVec(part->mStartScale); + part->mScale *= 1.f - frac; + part->mScale += frac*part->mEndScale; + } + + // Do glow interpolation + part->mGlow.mV[3] = (U8) ll_round(lerp(part->mStartGlow, part->mEndGlow, frac)*255.f); + + // Set the last update time to now. + part->mLastUpdateTime = cur_time; + + + // Kill dead particles (either flagged dead, or too old) + if ((part->mLastUpdateTime > part->mMaxAge) || (LLViewerPart::LL_PART_DEAD_MASK == part->mFlags)) + { + mParticles[i] = mParticles.back() ; + mParticles.pop_back() ; + delete part ; + } + else + { + F32 desired_size = calc_desired_size(camera, part->mPosAgent, part->mScale); + if (!posInGroup(part->mPosAgent, desired_size)) + { + // Transfer particles between groups + LLViewerPartSim::getInstance()->put(part) ; + mParticles[i] = mParticles.back() ; + mParticles.pop_back() ; + } + else + { + i++ ; + } + } + } + + S32 removed = end - (S32)mParticles.size(); + if (removed > 0) + { + // we removed one or more particles, so flag this group for update + if (mVOPartGroupp.notNull()) + { + gPipeline.markRebuild(mVOPartGroupp->mDrawable, LLDrawable::REBUILD_ALL); + } + LLViewerPartSim::decPartCount(removed); + } + + // Kill the viewer object if this particle group is empty + if (mParticles.empty()) + { + gObjectList.killObject(mVOPartGroupp); + mVOPartGroupp = NULL; + } + + LLViewerPartSim::checkParticleCount() ; +} + + +void LLViewerPartGroup::shift(const LLVector3 &offset) +{ + mCenterAgent += offset; + mMinObjPos += offset; + mMaxObjPos += offset; + + for (S32 i = 0 ; i < (S32)mParticles.size(); i++) + { + mParticles[i]->mPosAgent += offset; + } +} + +void LLViewerPartGroup::removeParticlesByID(const U32 source_id) +{ + for (S32 i = 0; i < (S32)mParticles.size(); i++) + { + if(mParticles[i]->mPartSourcep->getID() == source_id) + { + mParticles[i]->mFlags = LLViewerPart::LL_PART_DEAD_MASK; + } + } +} + +////////////////////////////////// +// +// LLViewerPartSim implementation +// +// + +//static +void LLViewerPartSim::checkParticleCount(U32 size) +{ + if(LLViewerPartSim::sParticleCount2 != LLViewerPartSim::sParticleCount) + { + LL_ERRS() << "sParticleCount: " << LLViewerPartSim::sParticleCount << " ; sParticleCount2: " << LLViewerPartSim::sParticleCount2 << LL_ENDL ; + } + + if(size > (U32)LLViewerPartSim::sParticleCount2) + { + LL_ERRS() << "curren particle size: " << LLViewerPartSim::sParticleCount2 << " array size: " << size << LL_ENDL ; + } +} + +LLViewerPartSim::LLViewerPartSim() +{ + sMaxParticleCount = llmin(gSavedSettings.getS32("RenderMaxPartCount"), LL_MAX_PARTICLE_COUNT); + static U32 id_seed = 0; + mID = ++id_seed; +} + +//enable/disable particle system +void LLViewerPartSim::enable(bool enabled) +{ + if(!enabled && sMaxParticleCount > 0) + { + sMaxParticleCount = 0; //disable + } + else if(enabled && sMaxParticleCount < 1) + { + sMaxParticleCount = llmin(gSavedSettings.getS32("RenderMaxPartCount"), LL_MAX_PARTICLE_COUNT); + } + + return; +} + +void LLViewerPartSim::destroyClass() +{ + S32 i; + S32 count; + + // Kill all of the groups (and particles) + count = (S32) mViewerPartGroups.size(); + for (i = 0; i < count; i++) + { + delete mViewerPartGroups[i]; + } + mViewerPartGroups.clear(); + + // Kill all of the sources + mViewerPartSources.clear(); +} + +//static +bool LLViewerPartSim::shouldAddPart() +{ + if (sParticleCount >= MAX_PART_COUNT) + { + return false; + } + + if (sParticleCount > PART_THROTTLE_THRESHOLD*sMaxParticleCount) + { + F32 frac = (F32)sParticleCount/(F32)sMaxParticleCount; + frac -= PART_THROTTLE_THRESHOLD; + frac *= PART_THROTTLE_RESCALE; + if (ll_frand() < frac) + { + // Skip... + return false; + } + } + + // Check frame rate, and don't add more if the viewer is really slow + const F32 MIN_FRAME_RATE_FOR_NEW_PARTICLES = 4.f; + if (gFPSClamped < MIN_FRAME_RATE_FOR_NEW_PARTICLES) + { + return false; + } + + return true; +} + +void LLViewerPartSim::addPart(LLViewerPart* part) +{ + if (sParticleCount < MAX_PART_COUNT) + { + put(part); + } + else + { + //delete the particle if can not add it in + delete part ; + part = NULL ; + } +} + + +LLViewerPartGroup *LLViewerPartSim::put(LLViewerPart* part) +{ + const F32 MAX_MAG = 1000000.f*1000000.f; // 1 million + LLViewerPartGroup *return_group = NULL ; + if (part->mPosAgent.magVecSquared() > MAX_MAG || !part->mPosAgent.isFinite()) + { +#if 0 && !LL_RELEASE_FOR_DOWNLOAD + LL_WARNS() << "LLViewerPartSim::put Part out of range!" << LL_ENDL; + LL_WARNS() << part->mPosAgent << LL_ENDL; +#endif + } + else + { + LLViewerCamera* camera = LLViewerCamera::getInstance(); + F32 desired_size = calc_desired_size(camera, part->mPosAgent, part->mScale); + + S32 count = (S32) mViewerPartGroups.size(); + for (S32 i = 0; i < count; i++) + { + if (mViewerPartGroups[i]->addPart(part, desired_size)) + { + // We found a spatial group that we fit into, add us and exit + return_group = mViewerPartGroups[i]; + break ; + } + } + + // Hmm, we didn't fit in any of the existing spatial groups + // Create a new one... + if(!return_group) + { + llassert_always(part->mPosAgent.isFinite()); + LLViewerPartGroup *groupp = createViewerPartGroup(part->mPosAgent, desired_size, part->mFlags & LLPartData::LL_PART_HUD); + groupp->mUniformParticles = (part->mScale.mV[0] == part->mScale.mV[1] && + !(part->mFlags & LLPartData::LL_PART_FOLLOW_VELOCITY_MASK)); + if (!groupp->addPart(part)) + { + LL_WARNS() << "LLViewerPartSim::put - Particle didn't go into its box!" << LL_ENDL; + LL_INFOS() << groupp->getCenterAgent() << LL_ENDL; + LL_INFOS() << part->mPosAgent << LL_ENDL; + mViewerPartGroups.pop_back() ; + delete groupp; + groupp = NULL ; + } + return_group = groupp; + } + } + + if(!return_group) //failed to insert the particle + { + delete part ; + part = NULL ; + } + + return return_group ; +} + +LLViewerPartGroup *LLViewerPartSim::createViewerPartGroup(const LLVector3 &pos_agent, const F32 desired_size, bool hud) +{ + //find a box that has a center position divisible by PART_SIM_BOX_SIDE that encompasses + //pos_agent + LLViewerPartGroup *groupp = new LLViewerPartGroup(pos_agent, desired_size, hud); + mViewerPartGroups.push_back(groupp); + return groupp; +} + + +void LLViewerPartSim::shift(const LLVector3 &offset) +{ + S32 i; + S32 count; + + count = (S32) mViewerPartSources.size(); + for (i = 0; i < count; i++) + { + mViewerPartSources[i]->mPosAgent += offset; + mViewerPartSources[i]->mTargetPosAgent += offset; + mViewerPartSources[i]->mLastUpdatePosAgent += offset; + } + + count = (S32) mViewerPartGroups.size(); + for (i = 0; i < count; i++) + { + mViewerPartGroups[i]->shift(offset); + } +} + +static LLTrace::BlockTimerStatHandle FTM_SIMULATE_PARTICLES("Simulate Particles"); + +void LLViewerPartSim::updateSimulation() +{ + static LLFrameTimer update_timer; + + const F32 dt = llmin(update_timer.getElapsedTimeAndResetF32(), 0.1f); + + if (!(gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_PARTICLES))) + { + return; + } + + LL_RECORD_BLOCK_TIME(FTM_SIMULATE_PARTICLES); + + // Start at a random particle system so the same + // particle system doesn't always get first pick at the + // particles. Theoretically we'd want to do this in distance + // order or something, but sorting particle sources will be a big + // pain. + S32 i; + S32 count = (S32) mViewerPartSources.size(); + S32 start = (S32)ll_frand((F32)count); + S32 dir = 1; + S32 deldir = 0; + if (ll_frand() > 0.5f) + { + dir = -1; + deldir = -1; + } + + S32 num_updates = 0; + for (i = start; num_updates < count;) + { + if (i >= count) + { + i = 0; + } + if (i < 0) + { + i = count - 1; + } + + if (!mViewerPartSources[i]->isDead()) + { + bool upd = true; + LLViewerObject* vobj = mViewerPartSources[i]->mSourceObjectp; + + if (vobj && vobj->isAvatar() && ((LLVOAvatar*)vobj)->isInMuteList()) + { + upd = false; + } + + if(vobj && vobj->isOwnerInMuteList(mViewerPartSources[i]->getOwnerUUID())) + { + upd = false; + } + + if (upd && vobj && (vobj->getPCode() == LL_PCODE_VOLUME)) + { + if(vobj->getAvatar() && vobj->getAvatar()->isTooComplex() && vobj->getAvatar()->isTooSlow()) + { + upd = false; + } + + LLVOVolume* vvo = (LLVOVolume *)vobj; + if (!LLPipeline::sRenderAttachedParticles && vvo && vvo->isAttachment()) + { + upd = false; + } + } + + if (upd) + { + mViewerPartSources[i]->update(dt); + } + } + + if (mViewerPartSources[i]->isDead()) + { + mViewerPartSources.erase(mViewerPartSources.begin() + i); + count--; + i+=deldir; + } + else + { + i += dir; + } + num_updates++; + } + + count = (S32) mViewerPartGroups.size(); + for (i = 0; i < count; i++) + { + LLViewerObject* vobj = mViewerPartGroups[i]->mVOPartGroupp; + + S32 visirate = 1; + if (vobj && !vobj->isDead() && vobj->mDrawable && !vobj->mDrawable->isDead()) + { + LLSpatialGroup* group = vobj->mDrawable->getSpatialGroup(); + if (group && !group->isVisible()) // && !group->isState(LLSpatialGroup::OBJECT_DIRTY)) + { + visirate = 8; + } + } + + if ((LLDrawable::getCurrentFrame()+mViewerPartGroups[i]->mID)%visirate == 0) + { + if (vobj && !vobj->isDead()) + { + gPipeline.markRebuild(vobj->mDrawable, LLDrawable::REBUILD_ALL); + } + mViewerPartGroups[i]->updateParticles(dt * visirate); + mViewerPartGroups[i]->mSkippedTime=0.0f; + if (!mViewerPartGroups[i]->getCount()) + { + delete mViewerPartGroups[i]; + mViewerPartGroups.erase(mViewerPartGroups.begin() + i); + i--; + count--; + } + } + else + { + mViewerPartGroups[i]->mSkippedTime+=dt; + } + + } + if (LLDrawable::getCurrentFrame()%16==0) + { + if (sParticleCount > sMaxParticleCount * 0.875f + && sParticleAdaptiveRate < 2.0f) + { + sParticleAdaptiveRate *= PART_ADAPT_RATE_MULT; + } + else + { + if (sParticleCount < sMaxParticleCount * 0.5f + && sParticleAdaptiveRate > 0.03125f) + { + sParticleAdaptiveRate *= PART_ADAPT_RATE_MULT_RECIP; + } + } + } + + updatePartBurstRate() ; + + //LL_INFOS() << "Particles: " << sParticleCount << " Adaptive Rate: " << sParticleAdaptiveRate << LL_ENDL; +} + +void LLViewerPartSim::updatePartBurstRate() +{ + if (!(LLDrawable::getCurrentFrame() & 0xf)) + { + if (sParticleCount >= MAX_PART_COUNT) //set rate to zero + { + sParticleBurstRate = 0.0f ; + } + else if(sParticleCount > 0) + { + if(sParticleBurstRate > 0.0000001f) + { + F32 total_particles = sParticleCount / sParticleBurstRate ; //estimated + F32 new_rate = llclamp(0.9f * sMaxParticleCount / total_particles, 0.0f, 1.0f) ; + F32 delta_rate_threshold = llmin(0.1f * llmax(new_rate, sParticleBurstRate), 0.1f) ; + F32 delta_rate = llclamp(new_rate - sParticleBurstRate, -1.0f * delta_rate_threshold, delta_rate_threshold) ; + + sParticleBurstRate = llclamp(sParticleBurstRate + 0.5f * delta_rate, 0.0f, 1.0f) ; + } + else + { + sParticleBurstRate += 0.0000001f ; + } + } + else + { + sParticleBurstRate += 0.00125f ; + } + } +} + +void LLViewerPartSim::addPartSource(LLPointer sourcep) +{ + if (!sourcep) + { + LL_WARNS() << "Null part source!" << LL_ENDL; + return; + } + sourcep->setStart() ; + mViewerPartSources.push_back(sourcep); +} + +void LLViewerPartSim::removeLastCreatedSource() +{ + mViewerPartSources.pop_back(); +} + +void LLViewerPartSim::cleanupRegion(LLViewerRegion *regionp) +{ + for (group_list_t::iterator i = mViewerPartGroups.begin(); i != mViewerPartGroups.end(); ) + { + group_list_t::iterator iter = i++; + + if ((*iter)->getRegion() == regionp) + { + delete *iter; + i = mViewerPartGroups.erase(iter); + } + } +} + +void LLViewerPartSim::clearParticlesByID(const U32 system_id) +{ + for (group_list_t::iterator g = mViewerPartGroups.begin(); g != mViewerPartGroups.end(); ++g) + { + (*g)->removeParticlesByID(system_id); + } + for (source_list_t::iterator i = mViewerPartSources.begin(); i != mViewerPartSources.end(); ++i) + { + if ((*i)->getID() == system_id) + { + (*i)->setDead(); + break; + } + } + +} + +void LLViewerPartSim::clearParticlesByOwnerID(const LLUUID& task_id) +{ + for (source_list_t::iterator iter = mViewerPartSources.begin(); iter != mViewerPartSources.end(); ++iter) + { + if ((*iter)->getOwnerUUID() == task_id) + { + clearParticlesByID((*iter)->getID()); + } + } +} diff --git a/indra/newview/llviewerpartsim.h b/indra/newview/llviewerpartsim.h index cb519ba2ee..cf3843bd66 100644 --- a/indra/newview/llviewerpartsim.h +++ b/indra/newview/llviewerpartsim.h @@ -1,215 +1,215 @@ -/** - * @file llviewerpartsim.h - * @brief LLViewerPart class header file - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVIEWERPARTSIM_H -#define LL_LLVIEWERPARTSIM_H - -#include "llframetimer.h" -#include "llpointer.h" -#include "llpartdata.h" -#include "llviewerpartsource.h" - -class LLViewerTexture; -class LLViewerPart; -class LLViewerRegion; -class LLVOPartGroup; - -#define LL_MAX_PARTICLE_COUNT 8192 - -typedef void (*LLVPCallback)(LLViewerPart &part, const F32 dt); - -/////////////////// -// -// An individual particle -// - - -class LLViewerPart : public LLPartData -{ -public: - ~LLViewerPart(); -public: - LLViewerPart(); - - void init(LLPointer sourcep, LLViewerTexture *imagep, LLVPCallback cb); - - - U32 mPartID; // Particle ID used primarily for moving between groups - F32 mLastUpdateTime; // Last time the particle was updated - F32 mSkipOffset; // Offset against current group mSkippedTime - - LLVPCallback mVPCallback; // Callback function for more complicated behaviors - LLPointer mPartSourcep; // Particle source used for this object - - LLViewerPart* mParent; // particle to connect to if this is part of a particle ribbon - LLViewerPart* mChild; // child particle for clean reference destruction - - // Current particle state (possibly used for rendering) - LLPointer mImagep; - LLVector3 mPosAgent; - LLVector3 mVelocity; - LLVector3 mAccel; - LLVector3 mAxis; - LLColor4 mColor; - LLVector2 mScale; - F32 mStartGlow; - F32 mEndGlow; - LLColor4U mGlow; - - - static U32 sNextPartID; -}; - - - -class LLViewerPartGroup -{ -public: - LLViewerPartGroup(const LLVector3 ¢er, - const F32 box_radius, - bool hud); - virtual ~LLViewerPartGroup(); - - void cleanup(); - - bool addPart(LLViewerPart* part, const F32 desired_size = -1.f); - - void updateParticles(const F32 lastdt); - - bool posInGroup(const LLVector3 &pos, const F32 desired_size = -1.f); - - void shift(const LLVector3 &offset); - - F32 getBoxRadius() { return mBoxRadius; } - F32 getBoxSide() { return mBoxSide; } - - typedef std::vector part_list_t; - part_list_t mParticles; - - const LLVector3 &getCenterAgent() const { return mCenterAgent; } - S32 getCount() const { return (S32) mParticles.size(); } - LLViewerRegion *getRegion() const { return mRegionp; } - - void removeParticlesByID(const U32 source_id); - - LLPointer mVOPartGroupp; - - bool mUniformParticles; - U32 mID; - - F32 mSkippedTime; - bool mHud; - -protected: - LLVector3 mCenterAgent; - F32 mBoxRadius; - F32 mBoxSide; - LLVector3 mMinObjPos; - LLVector3 mMaxObjPos; - - LLViewerRegion *mRegionp; -}; - -class LLViewerPartSim : public LLSingleton -{ - LLSINGLETON(LLViewerPartSim); -public: - void destroyClass(); - - typedef std::vector group_list_t; - typedef std::vector > source_list_t; - - void enable(bool enabled); - - void shift(const LLVector3 &offset); - - void updateSimulation(); - - void addPartSource(LLPointer sourcep); - - void cleanupRegion(LLViewerRegion *regionp); - - static bool shouldAddPart(); // Just decides whether this particle should be added or not (for particle count capping) - F32 maxRate() // Return maximum particle generation rate - { - if (sParticleCount >= MAX_PART_COUNT) - { - return 1.f; - } - if (sParticleCount > PART_THROTTLE_THRESHOLD*sMaxParticleCount) - { - return (((F32)sParticleCount/(F32)sMaxParticleCount)-PART_THROTTLE_THRESHOLD)*PART_THROTTLE_RESCALE; - } - return 0.f; - } - F32 getRefRate() { return sParticleAdaptiveRate; } - F32 getBurstRate() {return sParticleBurstRate; } - void addPart(LLViewerPart* part); - void updatePartBurstRate() ; - void clearParticlesByID(const U32 system_id); - void clearParticlesByOwnerID(const LLUUID& task_id); - void removeLastCreatedSource(); - - const source_list_t* getParticleSystemList() const { return &mViewerPartSources; } - - friend class LLViewerPartGroup; - - bool aboveParticleLimit() const { return sParticleCount > sMaxParticleCount; } - - static void setMaxPartCount(const S32 max_parts) { sMaxParticleCount = max_parts; } - static S32 getMaxPartCount() { return sMaxParticleCount; } - static void incPartCount(const S32 count) { sParticleCount += count; } - static void decPartCount(const S32 count) { sParticleCount -= count; } - - U32 mID; - -protected: - LLViewerPartGroup *createViewerPartGroup(const LLVector3 &pos_agent, const F32 desired_size, bool hud); - LLViewerPartGroup *put(LLViewerPart* part); - - group_list_t mViewerPartGroups; - source_list_t mViewerPartSources; - LLFrameTimer mSimulationTimer; - - static S32 sMaxParticleCount; - static S32 sParticleCount; - static F32 sParticleAdaptiveRate; - static F32 sParticleBurstRate; - - static const S32 MAX_PART_COUNT; - static const F32 PART_THROTTLE_THRESHOLD; - static const F32 PART_THROTTLE_RESCALE; - static const F32 PART_ADAPT_RATE_MULT; - static const F32 PART_ADAPT_RATE_MULT_RECIP; - -//debug use only -public: - static S32 sParticleCount2; - - static void checkParticleCount(U32 size = 0) ; -}; - -#endif // LL_LLVIEWERPARTSIM_H +/** + * @file llviewerpartsim.h + * @brief LLViewerPart class header file + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVIEWERPARTSIM_H +#define LL_LLVIEWERPARTSIM_H + +#include "llframetimer.h" +#include "llpointer.h" +#include "llpartdata.h" +#include "llviewerpartsource.h" + +class LLViewerTexture; +class LLViewerPart; +class LLViewerRegion; +class LLVOPartGroup; + +#define LL_MAX_PARTICLE_COUNT 8192 + +typedef void (*LLVPCallback)(LLViewerPart &part, const F32 dt); + +/////////////////// +// +// An individual particle +// + + +class LLViewerPart : public LLPartData +{ +public: + ~LLViewerPart(); +public: + LLViewerPart(); + + void init(LLPointer sourcep, LLViewerTexture *imagep, LLVPCallback cb); + + + U32 mPartID; // Particle ID used primarily for moving between groups + F32 mLastUpdateTime; // Last time the particle was updated + F32 mSkipOffset; // Offset against current group mSkippedTime + + LLVPCallback mVPCallback; // Callback function for more complicated behaviors + LLPointer mPartSourcep; // Particle source used for this object + + LLViewerPart* mParent; // particle to connect to if this is part of a particle ribbon + LLViewerPart* mChild; // child particle for clean reference destruction + + // Current particle state (possibly used for rendering) + LLPointer mImagep; + LLVector3 mPosAgent; + LLVector3 mVelocity; + LLVector3 mAccel; + LLVector3 mAxis; + LLColor4 mColor; + LLVector2 mScale; + F32 mStartGlow; + F32 mEndGlow; + LLColor4U mGlow; + + + static U32 sNextPartID; +}; + + + +class LLViewerPartGroup +{ +public: + LLViewerPartGroup(const LLVector3 ¢er, + const F32 box_radius, + bool hud); + virtual ~LLViewerPartGroup(); + + void cleanup(); + + bool addPart(LLViewerPart* part, const F32 desired_size = -1.f); + + void updateParticles(const F32 lastdt); + + bool posInGroup(const LLVector3 &pos, const F32 desired_size = -1.f); + + void shift(const LLVector3 &offset); + + F32 getBoxRadius() { return mBoxRadius; } + F32 getBoxSide() { return mBoxSide; } + + typedef std::vector part_list_t; + part_list_t mParticles; + + const LLVector3 &getCenterAgent() const { return mCenterAgent; } + S32 getCount() const { return (S32) mParticles.size(); } + LLViewerRegion *getRegion() const { return mRegionp; } + + void removeParticlesByID(const U32 source_id); + + LLPointer mVOPartGroupp; + + bool mUniformParticles; + U32 mID; + + F32 mSkippedTime; + bool mHud; + +protected: + LLVector3 mCenterAgent; + F32 mBoxRadius; + F32 mBoxSide; + LLVector3 mMinObjPos; + LLVector3 mMaxObjPos; + + LLViewerRegion *mRegionp; +}; + +class LLViewerPartSim : public LLSingleton +{ + LLSINGLETON(LLViewerPartSim); +public: + void destroyClass(); + + typedef std::vector group_list_t; + typedef std::vector > source_list_t; + + void enable(bool enabled); + + void shift(const LLVector3 &offset); + + void updateSimulation(); + + void addPartSource(LLPointer sourcep); + + void cleanupRegion(LLViewerRegion *regionp); + + static bool shouldAddPart(); // Just decides whether this particle should be added or not (for particle count capping) + F32 maxRate() // Return maximum particle generation rate + { + if (sParticleCount >= MAX_PART_COUNT) + { + return 1.f; + } + if (sParticleCount > PART_THROTTLE_THRESHOLD*sMaxParticleCount) + { + return (((F32)sParticleCount/(F32)sMaxParticleCount)-PART_THROTTLE_THRESHOLD)*PART_THROTTLE_RESCALE; + } + return 0.f; + } + F32 getRefRate() { return sParticleAdaptiveRate; } + F32 getBurstRate() {return sParticleBurstRate; } + void addPart(LLViewerPart* part); + void updatePartBurstRate() ; + void clearParticlesByID(const U32 system_id); + void clearParticlesByOwnerID(const LLUUID& task_id); + void removeLastCreatedSource(); + + const source_list_t* getParticleSystemList() const { return &mViewerPartSources; } + + friend class LLViewerPartGroup; + + bool aboveParticleLimit() const { return sParticleCount > sMaxParticleCount; } + + static void setMaxPartCount(const S32 max_parts) { sMaxParticleCount = max_parts; } + static S32 getMaxPartCount() { return sMaxParticleCount; } + static void incPartCount(const S32 count) { sParticleCount += count; } + static void decPartCount(const S32 count) { sParticleCount -= count; } + + U32 mID; + +protected: + LLViewerPartGroup *createViewerPartGroup(const LLVector3 &pos_agent, const F32 desired_size, bool hud); + LLViewerPartGroup *put(LLViewerPart* part); + + group_list_t mViewerPartGroups; + source_list_t mViewerPartSources; + LLFrameTimer mSimulationTimer; + + static S32 sMaxParticleCount; + static S32 sParticleCount; + static F32 sParticleAdaptiveRate; + static F32 sParticleBurstRate; + + static const S32 MAX_PART_COUNT; + static const F32 PART_THROTTLE_THRESHOLD; + static const F32 PART_THROTTLE_RESCALE; + static const F32 PART_ADAPT_RATE_MULT; + static const F32 PART_ADAPT_RATE_MULT_RECIP; + +//debug use only +public: + static S32 sParticleCount2; + + static void checkParticleCount(U32 size = 0) ; +}; + +#endif // LL_LLVIEWERPARTSIM_H diff --git a/indra/newview/llviewerpartsource.cpp b/indra/newview/llviewerpartsource.cpp index 8664e93bc3..dd6a404836 100644 --- a/indra/newview/llviewerpartsource.cpp +++ b/indra/newview/llviewerpartsource.cpp @@ -1,946 +1,946 @@ -/** - * @file llviewerpartsource.cpp - * @brief LLViewerPartSource class implementation - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llviewerpartsource.h" - -#include "llviewercontrol.h" -#include "llrender.h" - -#include "llagent.h" -#include "lldrawable.h" -#include "llviewercamera.h" -#include "llviewertexturelist.h" -#include "llviewerobject.h" -#include "llviewerobjectlist.h" -#include "llvoavatar.h" -#include "llworld.h" -#include "pipeline.h" - - -static LLVOAvatar* find_avatar(const LLUUID& id) -{ - LLViewerObject *obj = gObjectList.findObject(id); - while (obj && obj->isAttachment()) - { - obj = (LLViewerObject *)obj->getParent(); - } - - if (obj && obj->isAvatar()) - { - return (LLVOAvatar*)obj; - } - else - { - return NULL; - } -} - -LLViewerPartSource::LLViewerPartSource(const U32 type) : - mType(type), - mOwnerUUID(LLUUID::null), - mPartFlags(0) -{ - mLastUpdateTime = 0.f; - mLastPartTime = 0.f; - mIsDead = false; - mIsSuspended = false; - static U32 id_seed = 0; - mID = ++id_seed; - - mLastPart = NULL; - - mDelay = 0 ; -} - -void LLViewerPartSource::setDead() -{ - mIsDead = true; -} - - -void LLViewerPartSource::updatePart(LLViewerPart &part, const F32 dt) -{ -} - -void LLViewerPartSource::update(const F32 dt) -{ - LL_ERRS() << "Creating default part source!" << LL_ENDL; -} - -LLUUID LLViewerPartSource::getImageUUID() const -{ - LLViewerTexture* imagep = mImagep; - if(imagep) - { - return imagep->getID(); - } - return LLUUID::null; -} -void LLViewerPartSource::setStart() -{ - //cancel delaying to start a new added particle source, because some particle source just emits for a short time. - //however, canceling this might cause overall particle emmitting fluctuate for a while because the new added source jumps to - //the current particle emmitting settings instantly. -->bao - mDelay = 0 ; //99 -} - -LLViewerPartSourceScript::LLViewerPartSourceScript(LLViewerObject *source_objp) : - LLViewerPartSource(LL_PART_SOURCE_SCRIPT) -{ - llassert(source_objp); - mSourceObjectp = source_objp; - mPosAgent = mSourceObjectp->getPositionAgent(); - mImagep = LLViewerTextureManager::getFetchedTextureFromFile("pixiesmall.j2c"); - - mImagep->setAddressMode(LLTexUnit::TAM_CLAMP); -} - - -void LLViewerPartSourceScript::setDead() -{ - mIsDead = true; - mSourceObjectp = NULL; - mTargetObjectp = NULL; -} - -void LLViewerPartSourceScript::update(const F32 dt) -{ - if( mIsSuspended ) - return; - - if (mOwnerAvatarp.isNull() && mOwnerUUID != LLUUID::null) - { - mOwnerAvatarp = find_avatar(mOwnerUUID); - } - if (mOwnerAvatarp.notNull() && LLVOAvatar::AOA_NORMAL != mOwnerAvatarp->getOverallAppearance()) - { - return; - } - - F32 old_update_time = mLastUpdateTime; - mLastUpdateTime += dt; - - F32 ref_rate_travelspeed = llmin(LLViewerPartSim::getInstance()->getRefRate(), 1.f); - - F32 dt_update = mLastUpdateTime - mLastPartTime; - - // Update this for objects which have the follow flag set... - if (!mSourceObjectp.isNull()) - { - if (mSourceObjectp->isDead()) - { - mSourceObjectp = NULL; - } - else if (mSourceObjectp->mDrawable.notNull()) - { - mPosAgent = mSourceObjectp->getRenderPosition(); - } - } - - if (mTargetObjectp.isNull() - && mPartSysData.mTargetUUID.notNull()) - { - // - // Hmm, missing object, let's see if we can find it... - // - - LLViewerObject *target_objp = gObjectList.findObject(mPartSysData.mTargetUUID); - setTargetObject(target_objp); - } - - if (!mTargetObjectp.isNull()) - { - if (mTargetObjectp->isDead()) - { - mTargetObjectp = NULL; - } - else if (mTargetObjectp->mDrawable.notNull()) - { - mTargetPosAgent = mTargetObjectp->getRenderPosition(); - } - } - - if (!mTargetObjectp) - { - mTargetPosAgent = mPosAgent; - } - - if (mPartSysData.mMaxAge && ((mPartSysData.mStartAge + mLastUpdateTime + dt_update) > mPartSysData.mMaxAge)) - { - // Kill particle source because it has outlived its max age... - setDead(); - return; - } - - - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_PARTICLES)) - { - if (mSourceObjectp.notNull()) - { - std::ostringstream ostr; - ostr << mPartSysData; - mSourceObjectp->setDebugText(ostr.str()); - } - } - - bool first_run = false; - if (old_update_time <= 0.f) - { - first_run = true; - } - - F32 max_time = llmax(1.f, 10.f*mPartSysData.mBurstRate); - dt_update = llmin(max_time, dt_update); - while ((dt_update > mPartSysData.mBurstRate) || first_run) - { - first_run = false; - - // Update the rotation of the particle source by the angular velocity - // First check to see if there is still an angular velocity. - F32 angular_velocity_mag = mPartSysData.mAngularVelocity.magVec(); - if (angular_velocity_mag != 0.0f) - { - F32 av_angle = dt * angular_velocity_mag; - LLQuaternion dquat(av_angle, mPartSysData.mAngularVelocity); - mRotation *= dquat; - } - else - { - // No angular velocity. Reset our rotation. - mRotation.setQuat(0, 0, 0); - } - - if (LLViewerPartSim::getInstance()->aboveParticleLimit()) - { - // Don't bother doing any more updates if we're above the particle limit, - // just give up. - mLastPartTime = mLastUpdateTime; - break; - - } - - // find the greatest length that the shortest side of a system - // particle is expected to have - F32 max_short_side = - llmax( - llmax(llmin(mPartSysData.mPartData.mStartScale[0], - mPartSysData.mPartData.mStartScale[1]), - llmin(mPartSysData.mPartData.mEndScale[0], - mPartSysData.mPartData.mEndScale[1])), - llmin((mPartSysData.mPartData.mStartScale[0] - + mPartSysData.mPartData.mEndScale[0])/2, - (mPartSysData.mPartData.mStartScale[1] - + mPartSysData.mPartData.mEndScale[1])/2)); - - F32 pixel_meter_ratio = LLViewerCamera::getInstance()->getPixelMeterRatio(); - - // Maximum distance at which spawned particles will be viewable - F32 max_dist = max_short_side * pixel_meter_ratio; - - if (max_dist < 0.25f) - { - // < 1 pixel wide at a distance of >=25cm. Particles - // this tiny are useless and mostly spawned by buggy - // sources - mLastPartTime = mLastUpdateTime; - break; - } - - // Distance from camera - F32 dist = (mPosAgent - LLViewerCamera::getInstance()->getOrigin()).magVec(); - - // Particle size vs distance vs maxage throttling - - F32 limited_rate=0.f; - if (dist - max_dist > 0.f) - { - if((dist - max_dist) * ref_rate_travelspeed > mPartSysData.mPartData.mMaxAge - 0.2f ) - { - // You need to travel faster than 1 divided by reference rate m/s directly towards these particles to see them at least 0.2s - mLastPartTime = mLastUpdateTime; - break; - } - limited_rate = ((dist - max_dist) * ref_rate_travelspeed) / mPartSysData.mPartData.mMaxAge; - } - - if(mDelay) - { - limited_rate = llmax(limited_rate, 0.01f * mDelay--) ; - } - - S32 i; - for (i = 0; i < mPartSysData.mBurstPartCount; i++) - { - if (ll_frand() < llmax(1.0f - LLViewerPartSim::getInstance()->getBurstRate(), limited_rate)) - { - // Limit particle generation - continue; - } - - LLViewerPart* part = new LLViewerPart(); - - part->init(this, mImagep, NULL); - part->mFlags = mPartSysData.mPartData.mFlags; - if (!mSourceObjectp.isNull() && mSourceObjectp->isHUDAttachment()) - { - part->mFlags |= LLPartData::LL_PART_HUD; - } - - if (part->mFlags & LLPartData::LL_PART_RIBBON_MASK && mLastPart) - { //set previous particle's parent to this particle to chain ribbon together - mLastPart->mParent = part; - part->mChild = mLastPart; - part->mAxis = LLVector3(0,0,1); - - if (mSourceObjectp.notNull()) - { - LLQuaternion rot = mSourceObjectp->getRenderRotation(); - part->mAxis *= rot; - } - } - - mLastPart = part; - - part->mMaxAge = mPartSysData.mPartData.mMaxAge; - part->mStartColor = mPartSysData.mPartData.mStartColor; - part->mEndColor = mPartSysData.mPartData.mEndColor; - part->mColor = part->mStartColor; - - part->mStartScale = mPartSysData.mPartData.mStartScale; - part->mEndScale = mPartSysData.mPartData.mEndScale; - part->mScale = part->mStartScale; - - part->mAccel = mPartSysData.mPartAccel; - - part->mBlendFuncDest = mPartSysData.mPartData.mBlendFuncDest; - part->mBlendFuncSource = mPartSysData.mPartData.mBlendFuncSource; - - part->mStartGlow = mPartSysData.mPartData.mStartGlow; - part->mEndGlow = mPartSysData.mPartData.mEndGlow; - part->mGlow = LLColor4U(0, 0, 0, (U8) ll_round(part->mStartGlow*255.f)); - - if (mPartSysData.mPattern & LLPartSysData::LL_PART_SRC_PATTERN_DROP) - { - part->mPosAgent = mPosAgent; - part->mVelocity.setVec(0.f, 0.f, 0.f); - } - else if (mPartSysData.mPattern & LLPartSysData::LL_PART_SRC_PATTERN_EXPLODE) - { - part->mPosAgent = mPosAgent; - LLVector3 part_dir_vector; - - F32 mvs; - do - { - part_dir_vector.mV[VX] = ll_frand(2.f) - 1.f; - part_dir_vector.mV[VY] = ll_frand(2.f) - 1.f; - part_dir_vector.mV[VZ] = ll_frand(2.f) - 1.f; - mvs = part_dir_vector.magVecSquared(); - } - while ((mvs > 1.f) || (mvs < 0.01f)); - - part_dir_vector.normVec(); - part->mPosAgent += mPartSysData.mBurstRadius*part_dir_vector; - part->mVelocity = part_dir_vector; - F32 speed = mPartSysData.mBurstSpeedMin + ll_frand(mPartSysData.mBurstSpeedMax - mPartSysData.mBurstSpeedMin); - part->mVelocity *= speed; - } - else if (mPartSysData.mPattern & LLPartSysData::LL_PART_SRC_PATTERN_ANGLE - || mPartSysData.mPattern & LLPartSysData::LL_PART_SRC_PATTERN_ANGLE_CONE) - { - part->mPosAgent = mPosAgent; - - // original implemenetation for part_dir_vector was just: - LLVector3 part_dir_vector(0.0, 0.0, 1.0); - // params from the script... - // outer = outer cone angle - // inner = inner cone angle - // between outer and inner there will be particles - F32 innerAngle = mPartSysData.mInnerAngle; - F32 outerAngle = mPartSysData.mOuterAngle; - - // generate a random angle within the given space... - F32 angle = innerAngle + ll_frand(outerAngle - innerAngle); - // split which side it will go on randomly... - if (ll_frand() < 0.5) - { - angle = -angle; - } - // Both patterns rotate around the x-axis first: - part_dir_vector.rotVec(angle, 1.0, 0.0, 0.0); - - // If this is a cone pattern, rotate again to create the cone. - if (mPartSysData.mPattern & LLPartSysData::LL_PART_SRC_PATTERN_ANGLE_CONE) - { - part_dir_vector.rotVec(ll_frand(4*F_PI), 0.0, 0.0, 1.0); - } - - // Only apply this rotation if using the deprecated angles. - if (! (mPartSysData.mFlags & LLPartSysData::LL_PART_USE_NEW_ANGLE)) - { - // Deprecated... - part_dir_vector.rotVec(outerAngle, 1.0, 0.0, 0.0); - } - - if (mSourceObjectp) - { - part_dir_vector = part_dir_vector * mSourceObjectp->getRenderRotation(); - } - - part_dir_vector = part_dir_vector * mRotation; - - part->mPosAgent += mPartSysData.mBurstRadius*part_dir_vector; - - part->mVelocity = part_dir_vector; - - F32 speed = mPartSysData.mBurstSpeedMin + ll_frand(mPartSysData.mBurstSpeedMax - mPartSysData.mBurstSpeedMin); - part->mVelocity *= speed; - } - else - { - part->mPosAgent = mPosAgent; - part->mVelocity.setVec(0.f, 0.f, 0.f); - //LL_WARNS() << "Unknown source pattern " << (S32)mPartSysData.mPattern << LL_ENDL; - } - - if (part->mFlags & LLPartData::LL_PART_FOLLOW_SRC_MASK || // SVC-193, VWR-717 - part->mFlags & LLPartData::LL_PART_TARGET_LINEAR_MASK) - { - mPartSysData.mBurstRadius = 0; - } - - LLViewerPartSim::getInstance()->addPart(part); - } - - mLastPartTime = mLastUpdateTime; - dt_update -= mPartSysData.mBurstRate; - } -} - -// static -LLPointer LLViewerPartSourceScript::unpackPSS(LLViewerObject *source_objp, LLPointer pssp, const S32 block_num) -{ - if (!pssp) - { - if (LLPartSysData::isNullPS(block_num)) - { - return NULL; - } - LLPointer new_pssp = new LLViewerPartSourceScript(source_objp); - if (!new_pssp->mPartSysData.unpackBlock(block_num)) - { - return NULL; - } - if (new_pssp->mPartSysData.mTargetUUID.notNull()) - { - LLViewerObject *target_objp = gObjectList.findObject(new_pssp->mPartSysData.mTargetUUID); - new_pssp->setTargetObject(target_objp); - } - return new_pssp; - } - else - { - if (LLPartSysData::isNullPS(block_num)) - { - return NULL; - } - - F32 prev_max_age = pssp->mPartSysData.mMaxAge; - F32 prev_start_age = pssp->mPartSysData.mStartAge; - if (!pssp->mPartSysData.unpackBlock(block_num)) - { - return NULL; - } - else if (pssp->mPartSysData.mMaxAge - && (prev_max_age != pssp->mPartSysData.mMaxAge || prev_start_age != pssp->mPartSysData.mStartAge)) - { - // reusing existing pss, so reset time to allow particles to start again - pssp->mLastUpdateTime = 0.f; - pssp->mLastPartTime = 0.f; - } - - if (pssp->mPartSysData.mTargetUUID.notNull()) - { - LLViewerObject *target_objp = gObjectList.findObject(pssp->mPartSysData.mTargetUUID); - pssp->setTargetObject(target_objp); - } - return pssp; - } -} - - -LLPointer LLViewerPartSourceScript::unpackPSS(LLViewerObject *source_objp, LLPointer pssp, LLDataPacker &dp, bool legacy) -{ - if (!pssp) - { - LLPointer new_pssp = new LLViewerPartSourceScript(source_objp); - if (legacy) - { - if (!new_pssp->mPartSysData.unpackLegacy(dp)) - { - return NULL; - } - } - else - { - if (!new_pssp->mPartSysData.unpack(dp)) - { - return NULL; - } - } - - if (new_pssp->mPartSysData.mTargetUUID.notNull()) - { - LLViewerObject *target_objp = gObjectList.findObject(new_pssp->mPartSysData.mTargetUUID); - new_pssp->setTargetObject(target_objp); - } - - return new_pssp; - } - else - { - if (legacy) - { - if (!pssp->mPartSysData.unpackLegacy(dp)) - { - return NULL; - } - } - else - { - if (!pssp->mPartSysData.unpack(dp)) - { - return NULL; - } - } - - if (pssp->mPartSysData.mTargetUUID.notNull()) - { - LLViewerObject *target_objp = gObjectList.findObject(pssp->mPartSysData.mTargetUUID); - pssp->setTargetObject(target_objp); - } - return pssp; - } -} - - -/* static */ -LLPointer LLViewerPartSourceScript::createPSS(LLViewerObject *source_objp, const LLPartSysData& particle_parameters) -{ - LLPointer new_pssp = new LLViewerPartSourceScript(source_objp); - - new_pssp->mPartSysData = particle_parameters; - - if (new_pssp->mPartSysData.mTargetUUID.notNull()) - { - LLViewerObject *target_objp = gObjectList.findObject(new_pssp->mPartSysData.mTargetUUID); - new_pssp->setTargetObject(target_objp); - } - return new_pssp; -} - - -void LLViewerPartSourceScript::setImage(LLViewerTexture *imagep) -{ - mImagep = imagep; -} - -void LLViewerPartSourceScript::setTargetObject(LLViewerObject *objp) -{ - mTargetObjectp = objp; -} - - - - - -LLViewerPartSourceSpiral::LLViewerPartSourceSpiral(const LLVector3 &pos) : - LLViewerPartSource(LL_PART_SOURCE_CHAT) -{ - mPosAgent = pos; -} - -void LLViewerPartSourceSpiral::setDead() -{ - mIsDead = true; - mSourceObjectp = NULL; -} - - -void LLViewerPartSourceSpiral::updatePart(LLViewerPart &part, const F32 dt) -{ - F32 frac = part.mLastUpdateTime/part.mMaxAge; - - LLVector3 center_pos; - LLPointer& ps = part.mPartSourcep; - LLViewerPartSourceSpiral *pss = (LLViewerPartSourceSpiral *)ps.get(); - if (!pss->mSourceObjectp.isNull() && !pss->mSourceObjectp->mDrawable.isNull()) - { - part.mPosAgent = pss->mSourceObjectp->getRenderPosition(); - } - else - { - part.mPosAgent = pss->mPosAgent; - } - F32 x = sin(F_TWO_PI*frac + part.mParameter); - F32 y = cos(F_TWO_PI*frac + part.mParameter); - - part.mPosAgent.mV[VX] += x; - part.mPosAgent.mV[VY] += y; - part.mPosAgent.mV[VZ] += -0.5f + frac; -} - - -void LLViewerPartSourceSpiral::update(const F32 dt) -{ - if (!mImagep) - { - mImagep = LLViewerTextureManager::getFetchedTextureFromFile("pixiesmall.j2c"); - } - - const F32 RATE = 0.025f; - - mLastUpdateTime += dt; - - F32 dt_update = mLastUpdateTime - mLastPartTime; - F32 max_time = llmax(1.f, 10.f*RATE); - dt_update = llmin(max_time, dt_update); - - if (dt_update > RATE) - { - mLastPartTime = mLastUpdateTime; - if (!LLViewerPartSim::getInstance()->shouldAddPart()) - { - // Particle simulation says we have too many particles, skip all this - return; - } - - if (!mSourceObjectp.isNull() && !mSourceObjectp->mDrawable.isNull()) - { - mPosAgent = mSourceObjectp->getRenderPosition(); - } - LLViewerPart* part = new LLViewerPart(); - part->init(this, mImagep, updatePart); - part->mStartColor = mColor; - part->mEndColor = mColor; - part->mEndColor.mV[3] = 0.f; - part->mPosAgent = mPosAgent; - part->mMaxAge = 1.f; - part->mFlags = LLViewerPart::LL_PART_INTERP_COLOR_MASK; - part->mLastUpdateTime = 0.f; - part->mScale.mV[0] = 0.25f; - part->mScale.mV[1] = 0.25f; - part->mParameter = ll_frand(F_TWO_PI); - part->mBlendFuncDest = LLRender::BF_ONE_MINUS_SOURCE_ALPHA; - part->mBlendFuncSource = LLRender::BF_SOURCE_ALPHA; - part->mStartGlow = 0.f; - part->mEndGlow = 0.f; - part->mGlow = LLColor4U(0, 0, 0, 0); - - LLViewerPartSim::getInstance()->addPart(part); - } -} - -void LLViewerPartSourceSpiral::setSourceObject(LLViewerObject *objp) -{ - mSourceObjectp = objp; -} - -void LLViewerPartSourceSpiral::setColor(const LLColor4 &color) -{ - mColor = color; -} - - - - - -LLViewerPartSourceBeam::LLViewerPartSourceBeam() : - LLViewerPartSource(LL_PART_SOURCE_BEAM) -{ -} - -LLViewerPartSourceBeam::~LLViewerPartSourceBeam() -{ -} - -void LLViewerPartSourceBeam::setDead() -{ - mIsDead = true; - mSourceObjectp = NULL; - mTargetObjectp = NULL; -} - -void LLViewerPartSourceBeam::setColor(const LLColor4 &color) -{ - mColor = color; -} - - -void LLViewerPartSourceBeam::updatePart(LLViewerPart &part, const F32 dt) -{ - F32 frac = part.mLastUpdateTime/part.mMaxAge; - - LLViewerPartSource *ps = (LLViewerPartSource*)part.mPartSourcep; - LLViewerPartSourceBeam *psb = (LLViewerPartSourceBeam *)ps; - if (psb->mSourceObjectp.isNull()) - { - part.mFlags = LLPartData::LL_PART_DEAD_MASK; - return; - } - - LLVector3 source_pos_agent; - LLVector3 target_pos_agent; - if (!psb->mSourceObjectp.isNull() && !psb->mSourceObjectp->mDrawable.isNull()) - { - if (psb->mSourceObjectp->isAvatar()) - { - LLViewerObject *objp = psb->mSourceObjectp; - LLVOAvatar *avp = (LLVOAvatar *)objp; - source_pos_agent = avp->mWristLeftp->getWorldPosition(); - } - else - { - source_pos_agent = psb->mSourceObjectp->getRenderPosition(); - } - } - if (!psb->mTargetObjectp.isNull() && !psb->mTargetObjectp->mDrawable.isNull()) - { - target_pos_agent = psb->mTargetObjectp->getRenderPosition(); - } - - part.mPosAgent = (1.f - frac) * source_pos_agent; - if (psb->mTargetObjectp.isNull()) - { - part.mPosAgent += frac * (gAgent.getPosAgentFromGlobal(psb->mLKGTargetPosGlobal)); - } - else - { - part.mPosAgent += frac * target_pos_agent; - } -} - - -void LLViewerPartSourceBeam::update(const F32 dt) -{ - const F32 RATE = 0.025f; - - mLastUpdateTime += dt; - - if (!mSourceObjectp.isNull() && !mSourceObjectp->mDrawable.isNull()) - { - if (mSourceObjectp->isAvatar()) - { - LLViewerObject *objp = mSourceObjectp; - LLVOAvatar *avp = (LLVOAvatar *)objp; - mPosAgent = avp->mWristLeftp->getWorldPosition(); - } - else - { - mPosAgent = mSourceObjectp->getRenderPosition(); - } - } - - if (!mTargetObjectp.isNull() && !mTargetObjectp->mDrawable.isNull()) - { - mTargetPosAgent = mTargetObjectp->getRenderPosition(); - } - else if (!mLKGTargetPosGlobal.isExactlyZero()) - { - mTargetPosAgent = gAgent.getPosAgentFromGlobal(mLKGTargetPosGlobal); - } - - F32 dt_update = mLastUpdateTime - mLastPartTime; - F32 max_time = llmax(1.f, 10.f*RATE); - dt_update = llmin(max_time, dt_update); - - if (dt_update > RATE) - { - mLastPartTime = mLastUpdateTime; - if (!LLViewerPartSim::getInstance()->shouldAddPart()) - { - // Particle simulation says we have too many particles, skip all this - return; - } - - if (!mImagep) - { - mImagep = LLViewerTextureManager::getFetchedTextureFromFile("pixiesmall.j2c"); - } - - LLViewerPart* part = new LLViewerPart(); - part->init(this, mImagep, updatePart); - - part->mFlags = LLPartData::LL_PART_INTERP_COLOR_MASK | - LLPartData::LL_PART_INTERP_SCALE_MASK | - LLPartData::LL_PART_TARGET_POS_MASK | - LLPartData::LL_PART_FOLLOW_VELOCITY_MASK; - part->mMaxAge = 0.5f; - part->mStartColor = mColor; - part->mEndColor = part->mStartColor; - part->mEndColor.mV[3] = 0.4f; - part->mColor = part->mStartColor; - - part->mStartScale = LLVector2(0.1f, 0.1f); - part->mEndScale = LLVector2(0.1f, 0.1f); - part->mScale = part->mStartScale; - - part->mPosAgent = mPosAgent; - part->mVelocity = mTargetPosAgent - mPosAgent; - - part->mBlendFuncDest = LLRender::BF_ONE_MINUS_SOURCE_ALPHA; - part->mBlendFuncSource = LLRender::BF_SOURCE_ALPHA; - part->mStartGlow = 0.f; - part->mEndGlow = 0.f; - part->mGlow = LLColor4U(0, 0, 0, 0); - - LLViewerPartSim::getInstance()->addPart(part); - } -} - -void LLViewerPartSourceBeam::setSourceObject(LLViewerObject* objp) -{ - mSourceObjectp = objp; -} - -void LLViewerPartSourceBeam::setTargetObject(LLViewerObject* objp) -{ - mTargetObjectp = objp; -} - - - - -LLViewerPartSourceChat::LLViewerPartSourceChat(const LLVector3 &pos) : - LLViewerPartSource(LL_PART_SOURCE_SPIRAL) -{ - mPosAgent = pos; -} - -void LLViewerPartSourceChat::setDead() -{ - mIsDead = true; - mSourceObjectp = NULL; -} - - -void LLViewerPartSourceChat::updatePart(LLViewerPart &part, const F32 dt) -{ - F32 frac = part.mLastUpdateTime/part.mMaxAge; - - LLVector3 center_pos; - LLViewerPartSource *ps = (LLViewerPartSource*)part.mPartSourcep; - LLViewerPartSourceChat *pss = (LLViewerPartSourceChat *)ps; - if (!pss->mSourceObjectp.isNull() && !pss->mSourceObjectp->mDrawable.isNull()) - { - part.mPosAgent = pss->mSourceObjectp->getRenderPosition(); - } - else - { - part.mPosAgent = pss->mPosAgent; - } - F32 x = sin(F_TWO_PI*frac + part.mParameter); - F32 y = cos(F_TWO_PI*frac + part.mParameter); - - part.mPosAgent.mV[VX] += x; - part.mPosAgent.mV[VY] += y; - part.mPosAgent.mV[VZ] += -0.5f + frac; -} - - -void LLViewerPartSourceChat::update(const F32 dt) -{ - if (!mImagep) - { - mImagep = LLViewerTextureManager::getFetchedTextureFromFile("pixiesmall.j2c"); - } - - - const F32 RATE = 0.025f; - - mLastUpdateTime += dt; - - if (mLastUpdateTime > 2.f) - { - // Kill particle source because it has outlived its max age... - setDead(); - return; - } - - F32 dt_update = mLastUpdateTime - mLastPartTime; - - // Clamp us to generating at most one second's worth of particles on a frame. - F32 max_time = llmax(1.f, 10.f*RATE); - dt_update = llmin(max_time, dt_update); - - if (dt_update > RATE) - { - mLastPartTime = mLastUpdateTime; - if (!LLViewerPartSim::getInstance()->shouldAddPart()) - { - // Particle simulation says we have too many particles, skip all this - return; - } - - if (!mSourceObjectp.isNull() && !mSourceObjectp->mDrawable.isNull()) - { - mPosAgent = mSourceObjectp->getRenderPosition(); - } - LLViewerPart* part = new LLViewerPart(); - part->init(this, mImagep, updatePart); - part->mStartColor = mColor; - part->mEndColor = mColor; - part->mEndColor.mV[3] = 0.f; - part->mPosAgent = mPosAgent; - part->mMaxAge = 1.f; - part->mFlags = LLViewerPart::LL_PART_INTERP_COLOR_MASK; - part->mLastUpdateTime = 0.f; - part->mScale.mV[0] = 0.25f; - part->mScale.mV[1] = 0.25f; - part->mParameter = ll_frand(F_TWO_PI); - part->mBlendFuncDest = LLRender::BF_ONE_MINUS_SOURCE_ALPHA; - part->mBlendFuncSource = LLRender::BF_SOURCE_ALPHA; - part->mStartGlow = 0.f; - part->mEndGlow = 0.f; - part->mGlow = LLColor4U(0, 0, 0, 0); - - - LLViewerPartSim::getInstance()->addPart(part); - } -} - -void LLViewerPartSourceChat::setSourceObject(LLViewerObject *objp) -{ - mSourceObjectp = objp; -} - -void LLViewerPartSourceChat::setColor(const LLColor4 &color) -{ - mColor = color; -} - - +/** + * @file llviewerpartsource.cpp + * @brief LLViewerPartSource class implementation + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llviewerpartsource.h" + +#include "llviewercontrol.h" +#include "llrender.h" + +#include "llagent.h" +#include "lldrawable.h" +#include "llviewercamera.h" +#include "llviewertexturelist.h" +#include "llviewerobject.h" +#include "llviewerobjectlist.h" +#include "llvoavatar.h" +#include "llworld.h" +#include "pipeline.h" + + +static LLVOAvatar* find_avatar(const LLUUID& id) +{ + LLViewerObject *obj = gObjectList.findObject(id); + while (obj && obj->isAttachment()) + { + obj = (LLViewerObject *)obj->getParent(); + } + + if (obj && obj->isAvatar()) + { + return (LLVOAvatar*)obj; + } + else + { + return NULL; + } +} + +LLViewerPartSource::LLViewerPartSource(const U32 type) : + mType(type), + mOwnerUUID(LLUUID::null), + mPartFlags(0) +{ + mLastUpdateTime = 0.f; + mLastPartTime = 0.f; + mIsDead = false; + mIsSuspended = false; + static U32 id_seed = 0; + mID = ++id_seed; + + mLastPart = NULL; + + mDelay = 0 ; +} + +void LLViewerPartSource::setDead() +{ + mIsDead = true; +} + + +void LLViewerPartSource::updatePart(LLViewerPart &part, const F32 dt) +{ +} + +void LLViewerPartSource::update(const F32 dt) +{ + LL_ERRS() << "Creating default part source!" << LL_ENDL; +} + +LLUUID LLViewerPartSource::getImageUUID() const +{ + LLViewerTexture* imagep = mImagep; + if(imagep) + { + return imagep->getID(); + } + return LLUUID::null; +} +void LLViewerPartSource::setStart() +{ + //cancel delaying to start a new added particle source, because some particle source just emits for a short time. + //however, canceling this might cause overall particle emmitting fluctuate for a while because the new added source jumps to + //the current particle emmitting settings instantly. -->bao + mDelay = 0 ; //99 +} + +LLViewerPartSourceScript::LLViewerPartSourceScript(LLViewerObject *source_objp) : + LLViewerPartSource(LL_PART_SOURCE_SCRIPT) +{ + llassert(source_objp); + mSourceObjectp = source_objp; + mPosAgent = mSourceObjectp->getPositionAgent(); + mImagep = LLViewerTextureManager::getFetchedTextureFromFile("pixiesmall.j2c"); + + mImagep->setAddressMode(LLTexUnit::TAM_CLAMP); +} + + +void LLViewerPartSourceScript::setDead() +{ + mIsDead = true; + mSourceObjectp = NULL; + mTargetObjectp = NULL; +} + +void LLViewerPartSourceScript::update(const F32 dt) +{ + if( mIsSuspended ) + return; + + if (mOwnerAvatarp.isNull() && mOwnerUUID != LLUUID::null) + { + mOwnerAvatarp = find_avatar(mOwnerUUID); + } + if (mOwnerAvatarp.notNull() && LLVOAvatar::AOA_NORMAL != mOwnerAvatarp->getOverallAppearance()) + { + return; + } + + F32 old_update_time = mLastUpdateTime; + mLastUpdateTime += dt; + + F32 ref_rate_travelspeed = llmin(LLViewerPartSim::getInstance()->getRefRate(), 1.f); + + F32 dt_update = mLastUpdateTime - mLastPartTime; + + // Update this for objects which have the follow flag set... + if (!mSourceObjectp.isNull()) + { + if (mSourceObjectp->isDead()) + { + mSourceObjectp = NULL; + } + else if (mSourceObjectp->mDrawable.notNull()) + { + mPosAgent = mSourceObjectp->getRenderPosition(); + } + } + + if (mTargetObjectp.isNull() + && mPartSysData.mTargetUUID.notNull()) + { + // + // Hmm, missing object, let's see if we can find it... + // + + LLViewerObject *target_objp = gObjectList.findObject(mPartSysData.mTargetUUID); + setTargetObject(target_objp); + } + + if (!mTargetObjectp.isNull()) + { + if (mTargetObjectp->isDead()) + { + mTargetObjectp = NULL; + } + else if (mTargetObjectp->mDrawable.notNull()) + { + mTargetPosAgent = mTargetObjectp->getRenderPosition(); + } + } + + if (!mTargetObjectp) + { + mTargetPosAgent = mPosAgent; + } + + if (mPartSysData.mMaxAge && ((mPartSysData.mStartAge + mLastUpdateTime + dt_update) > mPartSysData.mMaxAge)) + { + // Kill particle source because it has outlived its max age... + setDead(); + return; + } + + + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_PARTICLES)) + { + if (mSourceObjectp.notNull()) + { + std::ostringstream ostr; + ostr << mPartSysData; + mSourceObjectp->setDebugText(ostr.str()); + } + } + + bool first_run = false; + if (old_update_time <= 0.f) + { + first_run = true; + } + + F32 max_time = llmax(1.f, 10.f*mPartSysData.mBurstRate); + dt_update = llmin(max_time, dt_update); + while ((dt_update > mPartSysData.mBurstRate) || first_run) + { + first_run = false; + + // Update the rotation of the particle source by the angular velocity + // First check to see if there is still an angular velocity. + F32 angular_velocity_mag = mPartSysData.mAngularVelocity.magVec(); + if (angular_velocity_mag != 0.0f) + { + F32 av_angle = dt * angular_velocity_mag; + LLQuaternion dquat(av_angle, mPartSysData.mAngularVelocity); + mRotation *= dquat; + } + else + { + // No angular velocity. Reset our rotation. + mRotation.setQuat(0, 0, 0); + } + + if (LLViewerPartSim::getInstance()->aboveParticleLimit()) + { + // Don't bother doing any more updates if we're above the particle limit, + // just give up. + mLastPartTime = mLastUpdateTime; + break; + + } + + // find the greatest length that the shortest side of a system + // particle is expected to have + F32 max_short_side = + llmax( + llmax(llmin(mPartSysData.mPartData.mStartScale[0], + mPartSysData.mPartData.mStartScale[1]), + llmin(mPartSysData.mPartData.mEndScale[0], + mPartSysData.mPartData.mEndScale[1])), + llmin((mPartSysData.mPartData.mStartScale[0] + + mPartSysData.mPartData.mEndScale[0])/2, + (mPartSysData.mPartData.mStartScale[1] + + mPartSysData.mPartData.mEndScale[1])/2)); + + F32 pixel_meter_ratio = LLViewerCamera::getInstance()->getPixelMeterRatio(); + + // Maximum distance at which spawned particles will be viewable + F32 max_dist = max_short_side * pixel_meter_ratio; + + if (max_dist < 0.25f) + { + // < 1 pixel wide at a distance of >=25cm. Particles + // this tiny are useless and mostly spawned by buggy + // sources + mLastPartTime = mLastUpdateTime; + break; + } + + // Distance from camera + F32 dist = (mPosAgent - LLViewerCamera::getInstance()->getOrigin()).magVec(); + + // Particle size vs distance vs maxage throttling + + F32 limited_rate=0.f; + if (dist - max_dist > 0.f) + { + if((dist - max_dist) * ref_rate_travelspeed > mPartSysData.mPartData.mMaxAge - 0.2f ) + { + // You need to travel faster than 1 divided by reference rate m/s directly towards these particles to see them at least 0.2s + mLastPartTime = mLastUpdateTime; + break; + } + limited_rate = ((dist - max_dist) * ref_rate_travelspeed) / mPartSysData.mPartData.mMaxAge; + } + + if(mDelay) + { + limited_rate = llmax(limited_rate, 0.01f * mDelay--) ; + } + + S32 i; + for (i = 0; i < mPartSysData.mBurstPartCount; i++) + { + if (ll_frand() < llmax(1.0f - LLViewerPartSim::getInstance()->getBurstRate(), limited_rate)) + { + // Limit particle generation + continue; + } + + LLViewerPart* part = new LLViewerPart(); + + part->init(this, mImagep, NULL); + part->mFlags = mPartSysData.mPartData.mFlags; + if (!mSourceObjectp.isNull() && mSourceObjectp->isHUDAttachment()) + { + part->mFlags |= LLPartData::LL_PART_HUD; + } + + if (part->mFlags & LLPartData::LL_PART_RIBBON_MASK && mLastPart) + { //set previous particle's parent to this particle to chain ribbon together + mLastPart->mParent = part; + part->mChild = mLastPart; + part->mAxis = LLVector3(0,0,1); + + if (mSourceObjectp.notNull()) + { + LLQuaternion rot = mSourceObjectp->getRenderRotation(); + part->mAxis *= rot; + } + } + + mLastPart = part; + + part->mMaxAge = mPartSysData.mPartData.mMaxAge; + part->mStartColor = mPartSysData.mPartData.mStartColor; + part->mEndColor = mPartSysData.mPartData.mEndColor; + part->mColor = part->mStartColor; + + part->mStartScale = mPartSysData.mPartData.mStartScale; + part->mEndScale = mPartSysData.mPartData.mEndScale; + part->mScale = part->mStartScale; + + part->mAccel = mPartSysData.mPartAccel; + + part->mBlendFuncDest = mPartSysData.mPartData.mBlendFuncDest; + part->mBlendFuncSource = mPartSysData.mPartData.mBlendFuncSource; + + part->mStartGlow = mPartSysData.mPartData.mStartGlow; + part->mEndGlow = mPartSysData.mPartData.mEndGlow; + part->mGlow = LLColor4U(0, 0, 0, (U8) ll_round(part->mStartGlow*255.f)); + + if (mPartSysData.mPattern & LLPartSysData::LL_PART_SRC_PATTERN_DROP) + { + part->mPosAgent = mPosAgent; + part->mVelocity.setVec(0.f, 0.f, 0.f); + } + else if (mPartSysData.mPattern & LLPartSysData::LL_PART_SRC_PATTERN_EXPLODE) + { + part->mPosAgent = mPosAgent; + LLVector3 part_dir_vector; + + F32 mvs; + do + { + part_dir_vector.mV[VX] = ll_frand(2.f) - 1.f; + part_dir_vector.mV[VY] = ll_frand(2.f) - 1.f; + part_dir_vector.mV[VZ] = ll_frand(2.f) - 1.f; + mvs = part_dir_vector.magVecSquared(); + } + while ((mvs > 1.f) || (mvs < 0.01f)); + + part_dir_vector.normVec(); + part->mPosAgent += mPartSysData.mBurstRadius*part_dir_vector; + part->mVelocity = part_dir_vector; + F32 speed = mPartSysData.mBurstSpeedMin + ll_frand(mPartSysData.mBurstSpeedMax - mPartSysData.mBurstSpeedMin); + part->mVelocity *= speed; + } + else if (mPartSysData.mPattern & LLPartSysData::LL_PART_SRC_PATTERN_ANGLE + || mPartSysData.mPattern & LLPartSysData::LL_PART_SRC_PATTERN_ANGLE_CONE) + { + part->mPosAgent = mPosAgent; + + // original implemenetation for part_dir_vector was just: + LLVector3 part_dir_vector(0.0, 0.0, 1.0); + // params from the script... + // outer = outer cone angle + // inner = inner cone angle + // between outer and inner there will be particles + F32 innerAngle = mPartSysData.mInnerAngle; + F32 outerAngle = mPartSysData.mOuterAngle; + + // generate a random angle within the given space... + F32 angle = innerAngle + ll_frand(outerAngle - innerAngle); + // split which side it will go on randomly... + if (ll_frand() < 0.5) + { + angle = -angle; + } + // Both patterns rotate around the x-axis first: + part_dir_vector.rotVec(angle, 1.0, 0.0, 0.0); + + // If this is a cone pattern, rotate again to create the cone. + if (mPartSysData.mPattern & LLPartSysData::LL_PART_SRC_PATTERN_ANGLE_CONE) + { + part_dir_vector.rotVec(ll_frand(4*F_PI), 0.0, 0.0, 1.0); + } + + // Only apply this rotation if using the deprecated angles. + if (! (mPartSysData.mFlags & LLPartSysData::LL_PART_USE_NEW_ANGLE)) + { + // Deprecated... + part_dir_vector.rotVec(outerAngle, 1.0, 0.0, 0.0); + } + + if (mSourceObjectp) + { + part_dir_vector = part_dir_vector * mSourceObjectp->getRenderRotation(); + } + + part_dir_vector = part_dir_vector * mRotation; + + part->mPosAgent += mPartSysData.mBurstRadius*part_dir_vector; + + part->mVelocity = part_dir_vector; + + F32 speed = mPartSysData.mBurstSpeedMin + ll_frand(mPartSysData.mBurstSpeedMax - mPartSysData.mBurstSpeedMin); + part->mVelocity *= speed; + } + else + { + part->mPosAgent = mPosAgent; + part->mVelocity.setVec(0.f, 0.f, 0.f); + //LL_WARNS() << "Unknown source pattern " << (S32)mPartSysData.mPattern << LL_ENDL; + } + + if (part->mFlags & LLPartData::LL_PART_FOLLOW_SRC_MASK || // SVC-193, VWR-717 + part->mFlags & LLPartData::LL_PART_TARGET_LINEAR_MASK) + { + mPartSysData.mBurstRadius = 0; + } + + LLViewerPartSim::getInstance()->addPart(part); + } + + mLastPartTime = mLastUpdateTime; + dt_update -= mPartSysData.mBurstRate; + } +} + +// static +LLPointer LLViewerPartSourceScript::unpackPSS(LLViewerObject *source_objp, LLPointer pssp, const S32 block_num) +{ + if (!pssp) + { + if (LLPartSysData::isNullPS(block_num)) + { + return NULL; + } + LLPointer new_pssp = new LLViewerPartSourceScript(source_objp); + if (!new_pssp->mPartSysData.unpackBlock(block_num)) + { + return NULL; + } + if (new_pssp->mPartSysData.mTargetUUID.notNull()) + { + LLViewerObject *target_objp = gObjectList.findObject(new_pssp->mPartSysData.mTargetUUID); + new_pssp->setTargetObject(target_objp); + } + return new_pssp; + } + else + { + if (LLPartSysData::isNullPS(block_num)) + { + return NULL; + } + + F32 prev_max_age = pssp->mPartSysData.mMaxAge; + F32 prev_start_age = pssp->mPartSysData.mStartAge; + if (!pssp->mPartSysData.unpackBlock(block_num)) + { + return NULL; + } + else if (pssp->mPartSysData.mMaxAge + && (prev_max_age != pssp->mPartSysData.mMaxAge || prev_start_age != pssp->mPartSysData.mStartAge)) + { + // reusing existing pss, so reset time to allow particles to start again + pssp->mLastUpdateTime = 0.f; + pssp->mLastPartTime = 0.f; + } + + if (pssp->mPartSysData.mTargetUUID.notNull()) + { + LLViewerObject *target_objp = gObjectList.findObject(pssp->mPartSysData.mTargetUUID); + pssp->setTargetObject(target_objp); + } + return pssp; + } +} + + +LLPointer LLViewerPartSourceScript::unpackPSS(LLViewerObject *source_objp, LLPointer pssp, LLDataPacker &dp, bool legacy) +{ + if (!pssp) + { + LLPointer new_pssp = new LLViewerPartSourceScript(source_objp); + if (legacy) + { + if (!new_pssp->mPartSysData.unpackLegacy(dp)) + { + return NULL; + } + } + else + { + if (!new_pssp->mPartSysData.unpack(dp)) + { + return NULL; + } + } + + if (new_pssp->mPartSysData.mTargetUUID.notNull()) + { + LLViewerObject *target_objp = gObjectList.findObject(new_pssp->mPartSysData.mTargetUUID); + new_pssp->setTargetObject(target_objp); + } + + return new_pssp; + } + else + { + if (legacy) + { + if (!pssp->mPartSysData.unpackLegacy(dp)) + { + return NULL; + } + } + else + { + if (!pssp->mPartSysData.unpack(dp)) + { + return NULL; + } + } + + if (pssp->mPartSysData.mTargetUUID.notNull()) + { + LLViewerObject *target_objp = gObjectList.findObject(pssp->mPartSysData.mTargetUUID); + pssp->setTargetObject(target_objp); + } + return pssp; + } +} + + +/* static */ +LLPointer LLViewerPartSourceScript::createPSS(LLViewerObject *source_objp, const LLPartSysData& particle_parameters) +{ + LLPointer new_pssp = new LLViewerPartSourceScript(source_objp); + + new_pssp->mPartSysData = particle_parameters; + + if (new_pssp->mPartSysData.mTargetUUID.notNull()) + { + LLViewerObject *target_objp = gObjectList.findObject(new_pssp->mPartSysData.mTargetUUID); + new_pssp->setTargetObject(target_objp); + } + return new_pssp; +} + + +void LLViewerPartSourceScript::setImage(LLViewerTexture *imagep) +{ + mImagep = imagep; +} + +void LLViewerPartSourceScript::setTargetObject(LLViewerObject *objp) +{ + mTargetObjectp = objp; +} + + + + + +LLViewerPartSourceSpiral::LLViewerPartSourceSpiral(const LLVector3 &pos) : + LLViewerPartSource(LL_PART_SOURCE_CHAT) +{ + mPosAgent = pos; +} + +void LLViewerPartSourceSpiral::setDead() +{ + mIsDead = true; + mSourceObjectp = NULL; +} + + +void LLViewerPartSourceSpiral::updatePart(LLViewerPart &part, const F32 dt) +{ + F32 frac = part.mLastUpdateTime/part.mMaxAge; + + LLVector3 center_pos; + LLPointer& ps = part.mPartSourcep; + LLViewerPartSourceSpiral *pss = (LLViewerPartSourceSpiral *)ps.get(); + if (!pss->mSourceObjectp.isNull() && !pss->mSourceObjectp->mDrawable.isNull()) + { + part.mPosAgent = pss->mSourceObjectp->getRenderPosition(); + } + else + { + part.mPosAgent = pss->mPosAgent; + } + F32 x = sin(F_TWO_PI*frac + part.mParameter); + F32 y = cos(F_TWO_PI*frac + part.mParameter); + + part.mPosAgent.mV[VX] += x; + part.mPosAgent.mV[VY] += y; + part.mPosAgent.mV[VZ] += -0.5f + frac; +} + + +void LLViewerPartSourceSpiral::update(const F32 dt) +{ + if (!mImagep) + { + mImagep = LLViewerTextureManager::getFetchedTextureFromFile("pixiesmall.j2c"); + } + + const F32 RATE = 0.025f; + + mLastUpdateTime += dt; + + F32 dt_update = mLastUpdateTime - mLastPartTime; + F32 max_time = llmax(1.f, 10.f*RATE); + dt_update = llmin(max_time, dt_update); + + if (dt_update > RATE) + { + mLastPartTime = mLastUpdateTime; + if (!LLViewerPartSim::getInstance()->shouldAddPart()) + { + // Particle simulation says we have too many particles, skip all this + return; + } + + if (!mSourceObjectp.isNull() && !mSourceObjectp->mDrawable.isNull()) + { + mPosAgent = mSourceObjectp->getRenderPosition(); + } + LLViewerPart* part = new LLViewerPart(); + part->init(this, mImagep, updatePart); + part->mStartColor = mColor; + part->mEndColor = mColor; + part->mEndColor.mV[3] = 0.f; + part->mPosAgent = mPosAgent; + part->mMaxAge = 1.f; + part->mFlags = LLViewerPart::LL_PART_INTERP_COLOR_MASK; + part->mLastUpdateTime = 0.f; + part->mScale.mV[0] = 0.25f; + part->mScale.mV[1] = 0.25f; + part->mParameter = ll_frand(F_TWO_PI); + part->mBlendFuncDest = LLRender::BF_ONE_MINUS_SOURCE_ALPHA; + part->mBlendFuncSource = LLRender::BF_SOURCE_ALPHA; + part->mStartGlow = 0.f; + part->mEndGlow = 0.f; + part->mGlow = LLColor4U(0, 0, 0, 0); + + LLViewerPartSim::getInstance()->addPart(part); + } +} + +void LLViewerPartSourceSpiral::setSourceObject(LLViewerObject *objp) +{ + mSourceObjectp = objp; +} + +void LLViewerPartSourceSpiral::setColor(const LLColor4 &color) +{ + mColor = color; +} + + + + + +LLViewerPartSourceBeam::LLViewerPartSourceBeam() : + LLViewerPartSource(LL_PART_SOURCE_BEAM) +{ +} + +LLViewerPartSourceBeam::~LLViewerPartSourceBeam() +{ +} + +void LLViewerPartSourceBeam::setDead() +{ + mIsDead = true; + mSourceObjectp = NULL; + mTargetObjectp = NULL; +} + +void LLViewerPartSourceBeam::setColor(const LLColor4 &color) +{ + mColor = color; +} + + +void LLViewerPartSourceBeam::updatePart(LLViewerPart &part, const F32 dt) +{ + F32 frac = part.mLastUpdateTime/part.mMaxAge; + + LLViewerPartSource *ps = (LLViewerPartSource*)part.mPartSourcep; + LLViewerPartSourceBeam *psb = (LLViewerPartSourceBeam *)ps; + if (psb->mSourceObjectp.isNull()) + { + part.mFlags = LLPartData::LL_PART_DEAD_MASK; + return; + } + + LLVector3 source_pos_agent; + LLVector3 target_pos_agent; + if (!psb->mSourceObjectp.isNull() && !psb->mSourceObjectp->mDrawable.isNull()) + { + if (psb->mSourceObjectp->isAvatar()) + { + LLViewerObject *objp = psb->mSourceObjectp; + LLVOAvatar *avp = (LLVOAvatar *)objp; + source_pos_agent = avp->mWristLeftp->getWorldPosition(); + } + else + { + source_pos_agent = psb->mSourceObjectp->getRenderPosition(); + } + } + if (!psb->mTargetObjectp.isNull() && !psb->mTargetObjectp->mDrawable.isNull()) + { + target_pos_agent = psb->mTargetObjectp->getRenderPosition(); + } + + part.mPosAgent = (1.f - frac) * source_pos_agent; + if (psb->mTargetObjectp.isNull()) + { + part.mPosAgent += frac * (gAgent.getPosAgentFromGlobal(psb->mLKGTargetPosGlobal)); + } + else + { + part.mPosAgent += frac * target_pos_agent; + } +} + + +void LLViewerPartSourceBeam::update(const F32 dt) +{ + const F32 RATE = 0.025f; + + mLastUpdateTime += dt; + + if (!mSourceObjectp.isNull() && !mSourceObjectp->mDrawable.isNull()) + { + if (mSourceObjectp->isAvatar()) + { + LLViewerObject *objp = mSourceObjectp; + LLVOAvatar *avp = (LLVOAvatar *)objp; + mPosAgent = avp->mWristLeftp->getWorldPosition(); + } + else + { + mPosAgent = mSourceObjectp->getRenderPosition(); + } + } + + if (!mTargetObjectp.isNull() && !mTargetObjectp->mDrawable.isNull()) + { + mTargetPosAgent = mTargetObjectp->getRenderPosition(); + } + else if (!mLKGTargetPosGlobal.isExactlyZero()) + { + mTargetPosAgent = gAgent.getPosAgentFromGlobal(mLKGTargetPosGlobal); + } + + F32 dt_update = mLastUpdateTime - mLastPartTime; + F32 max_time = llmax(1.f, 10.f*RATE); + dt_update = llmin(max_time, dt_update); + + if (dt_update > RATE) + { + mLastPartTime = mLastUpdateTime; + if (!LLViewerPartSim::getInstance()->shouldAddPart()) + { + // Particle simulation says we have too many particles, skip all this + return; + } + + if (!mImagep) + { + mImagep = LLViewerTextureManager::getFetchedTextureFromFile("pixiesmall.j2c"); + } + + LLViewerPart* part = new LLViewerPart(); + part->init(this, mImagep, updatePart); + + part->mFlags = LLPartData::LL_PART_INTERP_COLOR_MASK | + LLPartData::LL_PART_INTERP_SCALE_MASK | + LLPartData::LL_PART_TARGET_POS_MASK | + LLPartData::LL_PART_FOLLOW_VELOCITY_MASK; + part->mMaxAge = 0.5f; + part->mStartColor = mColor; + part->mEndColor = part->mStartColor; + part->mEndColor.mV[3] = 0.4f; + part->mColor = part->mStartColor; + + part->mStartScale = LLVector2(0.1f, 0.1f); + part->mEndScale = LLVector2(0.1f, 0.1f); + part->mScale = part->mStartScale; + + part->mPosAgent = mPosAgent; + part->mVelocity = mTargetPosAgent - mPosAgent; + + part->mBlendFuncDest = LLRender::BF_ONE_MINUS_SOURCE_ALPHA; + part->mBlendFuncSource = LLRender::BF_SOURCE_ALPHA; + part->mStartGlow = 0.f; + part->mEndGlow = 0.f; + part->mGlow = LLColor4U(0, 0, 0, 0); + + LLViewerPartSim::getInstance()->addPart(part); + } +} + +void LLViewerPartSourceBeam::setSourceObject(LLViewerObject* objp) +{ + mSourceObjectp = objp; +} + +void LLViewerPartSourceBeam::setTargetObject(LLViewerObject* objp) +{ + mTargetObjectp = objp; +} + + + + +LLViewerPartSourceChat::LLViewerPartSourceChat(const LLVector3 &pos) : + LLViewerPartSource(LL_PART_SOURCE_SPIRAL) +{ + mPosAgent = pos; +} + +void LLViewerPartSourceChat::setDead() +{ + mIsDead = true; + mSourceObjectp = NULL; +} + + +void LLViewerPartSourceChat::updatePart(LLViewerPart &part, const F32 dt) +{ + F32 frac = part.mLastUpdateTime/part.mMaxAge; + + LLVector3 center_pos; + LLViewerPartSource *ps = (LLViewerPartSource*)part.mPartSourcep; + LLViewerPartSourceChat *pss = (LLViewerPartSourceChat *)ps; + if (!pss->mSourceObjectp.isNull() && !pss->mSourceObjectp->mDrawable.isNull()) + { + part.mPosAgent = pss->mSourceObjectp->getRenderPosition(); + } + else + { + part.mPosAgent = pss->mPosAgent; + } + F32 x = sin(F_TWO_PI*frac + part.mParameter); + F32 y = cos(F_TWO_PI*frac + part.mParameter); + + part.mPosAgent.mV[VX] += x; + part.mPosAgent.mV[VY] += y; + part.mPosAgent.mV[VZ] += -0.5f + frac; +} + + +void LLViewerPartSourceChat::update(const F32 dt) +{ + if (!mImagep) + { + mImagep = LLViewerTextureManager::getFetchedTextureFromFile("pixiesmall.j2c"); + } + + + const F32 RATE = 0.025f; + + mLastUpdateTime += dt; + + if (mLastUpdateTime > 2.f) + { + // Kill particle source because it has outlived its max age... + setDead(); + return; + } + + F32 dt_update = mLastUpdateTime - mLastPartTime; + + // Clamp us to generating at most one second's worth of particles on a frame. + F32 max_time = llmax(1.f, 10.f*RATE); + dt_update = llmin(max_time, dt_update); + + if (dt_update > RATE) + { + mLastPartTime = mLastUpdateTime; + if (!LLViewerPartSim::getInstance()->shouldAddPart()) + { + // Particle simulation says we have too many particles, skip all this + return; + } + + if (!mSourceObjectp.isNull() && !mSourceObjectp->mDrawable.isNull()) + { + mPosAgent = mSourceObjectp->getRenderPosition(); + } + LLViewerPart* part = new LLViewerPart(); + part->init(this, mImagep, updatePart); + part->mStartColor = mColor; + part->mEndColor = mColor; + part->mEndColor.mV[3] = 0.f; + part->mPosAgent = mPosAgent; + part->mMaxAge = 1.f; + part->mFlags = LLViewerPart::LL_PART_INTERP_COLOR_MASK; + part->mLastUpdateTime = 0.f; + part->mScale.mV[0] = 0.25f; + part->mScale.mV[1] = 0.25f; + part->mParameter = ll_frand(F_TWO_PI); + part->mBlendFuncDest = LLRender::BF_ONE_MINUS_SOURCE_ALPHA; + part->mBlendFuncSource = LLRender::BF_SOURCE_ALPHA; + part->mStartGlow = 0.f; + part->mEndGlow = 0.f; + part->mGlow = LLColor4U(0, 0, 0, 0); + + + LLViewerPartSim::getInstance()->addPart(part); + } +} + +void LLViewerPartSourceChat::setSourceObject(LLViewerObject *objp) +{ + mSourceObjectp = objp; +} + +void LLViewerPartSourceChat::setColor(const LLColor4 &color) +{ + mColor = color; +} + + diff --git a/indra/newview/llviewerpartsource.h b/indra/newview/llviewerpartsource.h index 682d545b94..dcf945dd43 100644 --- a/indra/newview/llviewerpartsource.h +++ b/indra/newview/llviewerpartsource.h @@ -1,211 +1,211 @@ -/** - * @file llviewerpartsource.h - * @brief LLViewerPartSource class header file - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVIEWERPARTSOURCE_H -#define LL_LLVIEWERPARTSOURCE_H - -#include "llrefcount.h" -#include "llpartdata.h" -#include "llpointer.h" -#include "llquaternion.h" -#include "v3math.h" - -//////////////////// -// -// A particle source - subclassed to generate particles with different behaviors -// -// - -class LLViewerTexture; -class LLViewerObject; -class LLViewerPart; -class LLVOAvatar; - -class LLViewerPartSource : public LLRefCount -{ -public: - enum - { - LL_PART_SOURCE_NULL, - LL_PART_SOURCE_SCRIPT, - LL_PART_SOURCE_SPIRAL, - LL_PART_SOURCE_BEAM, - LL_PART_SOURCE_CHAT - }; - - LLViewerPartSource(const U32 type); - - virtual void update(const F32 dt); // Return false if this source is dead... - - virtual void setDead(); - bool isDead() const { return mIsDead; } - void setSuspended( bool state ) { mIsSuspended = state; } - bool isSuspended() const { return mIsSuspended; } - U32 getType() const { return mType; } - static void updatePart(LLViewerPart &part, const F32 dt); - void setOwnerUUID(const LLUUID& owner_id) { mOwnerUUID = owner_id; } - LLUUID getOwnerUUID() const { return mOwnerUUID; } - U32 getID() const { return mID; } - LLUUID getImageUUID() const; - void setStart() ; - - LLVector3 mPosAgent; // Location of the particle source - LLVector3 mTargetPosAgent; // Location of the target position - LLVector3 mLastUpdatePosAgent; - LLPointer mSourceObjectp; - U32 mID; - LLViewerPart* mLastPart; //last particle emitted (for making particle ribbons) - -protected: - U32 mType; - bool mIsDead; - bool mIsSuspended; - F32 mLastUpdateTime; - F32 mLastPartTime; - LLUUID mOwnerUUID; - LLPointer mOwnerAvatarp; - LLPointer mImagep; - // Particle information - U32 mPartFlags; // Flags for the particle - U32 mDelay ; //delay to start particles -}; - - - -/////////////////////////////// -// -// LLViewerPartSourceScript -// -// Particle source that handles the "generic" script-drive particle source -// attached to objects -// - - -class LLViewerPartSourceScript : public LLViewerPartSource -{ -public: - LLViewerPartSourceScript(LLViewerObject *source_objp); - /*virtual*/ void update(const F32 dt); - - /*virtual*/ void setDead(); - - bool updateFromMesg(); - - // Returns a new particle source to attach to an object... - static LLPointer unpackPSS(LLViewerObject *source_objp, LLPointer pssp, const S32 block_num); - static LLPointer unpackPSS(LLViewerObject *source_objp, LLPointer pssp, LLDataPacker &dp, bool legacy); - static LLPointer createPSS(LLViewerObject *source_objp, const LLPartSysData& particle_parameters); - - LLViewerTexture *getImage() const { return mImagep; } - void setImage(LLViewerTexture *imagep); - LLPartSysData mPartSysData; - - void setTargetObject(LLViewerObject *objp); - -protected: - LLQuaternion mRotation; // Current rotation for particle source - LLPointer mTargetObjectp; // Target object for the particle source -}; - - -//////////////////////////// -// -// Particle source for spiral effect (customize avatar, mostly) -// - -class LLViewerPartSourceSpiral : public LLViewerPartSource -{ -public: - LLViewerPartSourceSpiral(const LLVector3 &pos); - - /*virtual*/ void setDead(); - - /*virtual*/ void update(const F32 dt); - - void setSourceObject(LLViewerObject *objp); - void setColor(const LLColor4 &color); - - static void updatePart(LLViewerPart &part, const F32 dt); - LLColor4 mColor; -protected: - LLVector3d mLKGSourcePosGlobal; -}; - - -//////////////////////////// -// -// Particle source for tractor(editing) beam -// - -class LLViewerPartSourceBeam : public LLViewerPartSource -{ -public: - LLViewerPartSourceBeam(); - - /*virtual*/ void setDead(); - - /*virtual*/ void update(const F32 dt); - - void setSourceObject(LLViewerObject *objp); - void setTargetObject(LLViewerObject *objp); - void setSourcePosGlobal(const LLVector3d &pos_global); - void setTargetPosGlobal(const LLVector3d &pos_global); - void setColor(const LLColor4 &color); - - static void updatePart(LLViewerPart &part, const F32 dt); - LLPointer mTargetObjectp; - LLVector3d mLKGTargetPosGlobal; - LLColor4 mColor; -protected: - ~LLViewerPartSourceBeam(); -}; - - - -////////////////////////// -// -// Particle source for chat effect -// - -class LLViewerPartSourceChat : public LLViewerPartSource -{ -public: - LLViewerPartSourceChat(const LLVector3 &pos); - - /*virtual*/ void setDead(); - - /*virtual*/ void update(const F32 dt); - - void setSourceObject(LLViewerObject *objp); - void setColor(const LLColor4 &color); - static void updatePart(LLViewerPart &part, const F32 dt); - LLColor4 mColor; -protected: - LLVector3d mLKGSourcePosGlobal; -}; - - -#endif // LL_LLVIEWERPARTSOURCE_H +/** + * @file llviewerpartsource.h + * @brief LLViewerPartSource class header file + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVIEWERPARTSOURCE_H +#define LL_LLVIEWERPARTSOURCE_H + +#include "llrefcount.h" +#include "llpartdata.h" +#include "llpointer.h" +#include "llquaternion.h" +#include "v3math.h" + +//////////////////// +// +// A particle source - subclassed to generate particles with different behaviors +// +// + +class LLViewerTexture; +class LLViewerObject; +class LLViewerPart; +class LLVOAvatar; + +class LLViewerPartSource : public LLRefCount +{ +public: + enum + { + LL_PART_SOURCE_NULL, + LL_PART_SOURCE_SCRIPT, + LL_PART_SOURCE_SPIRAL, + LL_PART_SOURCE_BEAM, + LL_PART_SOURCE_CHAT + }; + + LLViewerPartSource(const U32 type); + + virtual void update(const F32 dt); // Return false if this source is dead... + + virtual void setDead(); + bool isDead() const { return mIsDead; } + void setSuspended( bool state ) { mIsSuspended = state; } + bool isSuspended() const { return mIsSuspended; } + U32 getType() const { return mType; } + static void updatePart(LLViewerPart &part, const F32 dt); + void setOwnerUUID(const LLUUID& owner_id) { mOwnerUUID = owner_id; } + LLUUID getOwnerUUID() const { return mOwnerUUID; } + U32 getID() const { return mID; } + LLUUID getImageUUID() const; + void setStart() ; + + LLVector3 mPosAgent; // Location of the particle source + LLVector3 mTargetPosAgent; // Location of the target position + LLVector3 mLastUpdatePosAgent; + LLPointer mSourceObjectp; + U32 mID; + LLViewerPart* mLastPart; //last particle emitted (for making particle ribbons) + +protected: + U32 mType; + bool mIsDead; + bool mIsSuspended; + F32 mLastUpdateTime; + F32 mLastPartTime; + LLUUID mOwnerUUID; + LLPointer mOwnerAvatarp; + LLPointer mImagep; + // Particle information + U32 mPartFlags; // Flags for the particle + U32 mDelay ; //delay to start particles +}; + + + +/////////////////////////////// +// +// LLViewerPartSourceScript +// +// Particle source that handles the "generic" script-drive particle source +// attached to objects +// + + +class LLViewerPartSourceScript : public LLViewerPartSource +{ +public: + LLViewerPartSourceScript(LLViewerObject *source_objp); + /*virtual*/ void update(const F32 dt); + + /*virtual*/ void setDead(); + + bool updateFromMesg(); + + // Returns a new particle source to attach to an object... + static LLPointer unpackPSS(LLViewerObject *source_objp, LLPointer pssp, const S32 block_num); + static LLPointer unpackPSS(LLViewerObject *source_objp, LLPointer pssp, LLDataPacker &dp, bool legacy); + static LLPointer createPSS(LLViewerObject *source_objp, const LLPartSysData& particle_parameters); + + LLViewerTexture *getImage() const { return mImagep; } + void setImage(LLViewerTexture *imagep); + LLPartSysData mPartSysData; + + void setTargetObject(LLViewerObject *objp); + +protected: + LLQuaternion mRotation; // Current rotation for particle source + LLPointer mTargetObjectp; // Target object for the particle source +}; + + +//////////////////////////// +// +// Particle source for spiral effect (customize avatar, mostly) +// + +class LLViewerPartSourceSpiral : public LLViewerPartSource +{ +public: + LLViewerPartSourceSpiral(const LLVector3 &pos); + + /*virtual*/ void setDead(); + + /*virtual*/ void update(const F32 dt); + + void setSourceObject(LLViewerObject *objp); + void setColor(const LLColor4 &color); + + static void updatePart(LLViewerPart &part, const F32 dt); + LLColor4 mColor; +protected: + LLVector3d mLKGSourcePosGlobal; +}; + + +//////////////////////////// +// +// Particle source for tractor(editing) beam +// + +class LLViewerPartSourceBeam : public LLViewerPartSource +{ +public: + LLViewerPartSourceBeam(); + + /*virtual*/ void setDead(); + + /*virtual*/ void update(const F32 dt); + + void setSourceObject(LLViewerObject *objp); + void setTargetObject(LLViewerObject *objp); + void setSourcePosGlobal(const LLVector3d &pos_global); + void setTargetPosGlobal(const LLVector3d &pos_global); + void setColor(const LLColor4 &color); + + static void updatePart(LLViewerPart &part, const F32 dt); + LLPointer mTargetObjectp; + LLVector3d mLKGTargetPosGlobal; + LLColor4 mColor; +protected: + ~LLViewerPartSourceBeam(); +}; + + + +////////////////////////// +// +// Particle source for chat effect +// + +class LLViewerPartSourceChat : public LLViewerPartSource +{ +public: + LLViewerPartSourceChat(const LLVector3 &pos); + + /*virtual*/ void setDead(); + + /*virtual*/ void update(const F32 dt); + + void setSourceObject(LLViewerObject *objp); + void setColor(const LLColor4 &color); + static void updatePart(LLViewerPart &part, const F32 dt); + LLColor4 mColor; +protected: + LLVector3d mLKGSourcePosGlobal; +}; + + +#endif // LL_LLVIEWERPARTSOURCE_H diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index 7633ec62df..41af61ec97 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -1,3676 +1,3676 @@ -/** - * @file llviewerregion.cpp - * @brief Implementation of the LLViewerRegion class. - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010-2013, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llviewerregion.h" - -// linden libraries -#include "indra_constants.h" -#include "llaisapi.h" -#include "llavatarnamecache.h" // name lookup cap url -#include "llfloaterreg.h" -#include "llmath.h" -#include "llregionflags.h" -#include "llregionhandle.h" -#include "llsurface.h" -#include "message.h" -//#include "vmath.h" -#include "v3math.h" -#include "v4math.h" - -#include "llagent.h" -#include "llagentcamera.h" -#include "llappviewer.h" -#include "llavatarrenderinfoaccountant.h" -#include "llcallingcard.h" -#include "llcommandhandler.h" -#include "lldir.h" -#include "lleventpoll.h" -#include "llfloatergodtools.h" -#include "llfloaterreporter.h" -#include "llfloaterregioninfo.h" -#include "llgltfmateriallist.h" -#include "llhttpnode.h" -#include "llregioninfomodel.h" -#include "llsdutil.h" -#include "llstartup.h" -#include "lltrans.h" -#include "llurldispatcher.h" -#include "llviewerobjectlist.h" -#include "llviewerparceloverlay.h" -#include "llviewerstatsrecorder.h" -#include "llvlmanager.h" -#include "llvlcomposition.h" -#include "llvoavatarself.h" -#include "llvocache.h" -#include "llworld.h" -#include "llspatialpartition.h" -#include "stringize.h" -#include "llviewercontrol.h" -#include "llsdserialize.h" -#include "llfloaterperms.h" -#include "llvieweroctree.h" -#include "llviewerdisplay.h" -#include "llviewerwindow.h" -#include "llprogressview.h" -#include "llcoros.h" -#include "lleventcoro.h" -#include "llcorehttputil.h" -#include "llcallstack.h" -#include "llsettingsdaycycle.h" - -#include - -#ifdef LL_WINDOWS - #pragma warning(disable:4355) -#endif - -// When we receive a base grant of capabilities that has a different number of -// capabilities than the original base grant received for the region, print -// out the two lists of capabilities for analysis. -//#define DEBUG_CAPS_GRANTS - -// The server only keeps our pending agent info for 60 seconds. -// We want to allow for seed cap retry, but its not useful after that 60 seconds. -// Even though we gave up on login, keep trying for caps after we are logged in: -const S32 MAX_CAP_REQUEST_ATTEMPTS = 30; -const U32 DEFAULT_MAX_REGION_WIDE_PRIM_COUNT = 15000; - -bool LLViewerRegion::sVOCacheCullingEnabled = false; -S32 LLViewerRegion::sLastCameraUpdated = 0; -S32 LLViewerRegion::sNewObjectCreationThrottle = -1; -LLViewerRegion::vocache_entry_map_t LLViewerRegion::sRegionCacheCleanup; - -typedef std::map CapabilityMap; - -static void log_capabilities(const CapabilityMap &capmap); - -namespace -{ - -void newRegionEntry(LLViewerRegion& region) -{ - LL_INFOS("LLViewerRegion") << "Entering region [" << region.getName() << "]" << LL_ENDL; - gDebugInfo["CurrentRegion"] = region.getName(); - LLAppViewer::instance()->writeDebugInfo(); -} - -} // anonymous namespace - -// support for secondlife:///app/region/{REGION} SLapps -// N.B. this is defined to work exactly like the classic secondlife://{REGION} -// However, the later syntax cannot support spaces in the region name because -// spaces (and %20 chars) are illegal in the hostname of an http URL. Some -// browsers let you get away with this, but some do not (such as Qt's Webkit). -// Hence we introduced the newer secondlife:///app/region alternative. -class LLRegionHandler : public LLCommandHandler -{ -public: - // requests will be throttled from a non-trusted browser - LLRegionHandler() : LLCommandHandler("region", UNTRUSTED_THROTTLE) {} - - bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) - { - // make sure that we at least have a region name - int num_params = params.size(); - if (num_params < 1) - { - return false; - } - - // build a secondlife://{PLACE} SLurl from this SLapp - std::string url = "secondlife://"; - if (!grid.empty()) - { - url += grid + "/secondlife/"; - } - boost::regex name_rx("[A-Za-z0-9()_%]+"); - boost::regex coord_rx("[0-9]+"); - for (int i = 0; i < num_params; i++) - { - if (i > 0) - { - url += "/"; - } - if (!boost::regex_match(params[i].asString(), i > 0 ? coord_rx : name_rx)) - { - return false; - } - - url += params[i].asString(); - } - - // Process the SLapp as if it was a secondlife://{PLACE} SLurl - LLURLDispatcher::dispatch(url, LLCommandHandler::NAV_TYPE_CLICKED, web, true); - return true; - } - -}; -LLRegionHandler gRegionHandler; - - -class LLViewerRegionImpl -{ -public: - LLViewerRegionImpl(LLViewerRegion * region, LLHost const & host): - mHost(host), - mCompositionp(NULL), - mEventPoll(NULL), - mSeedCapMaxAttempts(MAX_CAP_REQUEST_ATTEMPTS), - mSeedCapAttempts(0), - mHttpResponderID(0), - mLastCameraUpdate(0), - mLastCameraOrigin(), - mVOCachePartition(NULL), - mLandp(NULL) - {} - - static void buildCapabilityNames(LLSD& capabilityNames); - - // The surfaces and other layers - LLSurface* mLandp; - - // Region geometry data - LLVector3d mOriginGlobal; // Location of southwest corner of region (meters) - LLVector3d mCenterGlobal; // Location of center in world space (meters) - LLHost mHost; - - // The unique ID for this region. - LLUUID mRegionID; - - // region/estate owner - usually null. - LLUUID mOwnerID; - - // Network statistics for the region's circuit... - LLTimer mLastNetUpdate; - - // Misc - LLVLComposition *mCompositionp; // Composition layer for the surface - - LLVOCacheEntry::vocache_entry_map_t mCacheMap; //all cached entries - LLVOCacheEntry::vocache_entry_set_t mActiveSet; //all active entries; - LLVOCacheEntry::vocache_entry_set_t mWaitingSet; //entries waiting for LLDrawable to be generated. - std::set< LLPointer > mVisibleGroups; //visible groupa - LLVOCachePartition* mVOCachePartition; - LLVOCacheEntry::vocache_entry_set_t mVisibleEntries; //must-be-created visible entries wait for objects creation. - LLVOCacheEntry::vocache_entry_priority_list_t mWaitingList; //transient list storing sorted visible entries waiting for object creation. - std::set mNonCacheableCreatedList; //list of local ids of all non-cacheable objects - LLVOCacheEntry::vocache_gltf_overrides_map_t mGLTFOverridesLLSD; // for materials - - // time? - // LRU info? - - // Cache ID is unique per-region, across renames, moving locations, - // etc. - LLUUID mCacheID; - - CapabilityMap mCapabilities; - CapabilityMap mSecondCapabilitiesTracker; - - LLEventPoll* mEventPoll; - - S32 mSeedCapMaxAttempts; - S32 mSeedCapAttempts; - - S32 mHttpResponderID; - - //spatial partitions for objects in this region - std::vector mObjectPartition; - - LLVector3 mLastCameraOrigin; - U32 mLastCameraUpdate; - - static void requestBaseCapabilitiesCoro(U64 regionHandle); - static void requestBaseCapabilitiesCompleteCoro(U64 regionHandle); - static void requestSimulatorFeatureCoro(std::string url, U64 regionHandle); -}; - -void LLViewerRegionImpl::requestBaseCapabilitiesCoro(U64 regionHandle) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("BaseCapabilitiesRequest", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD result; - LLViewerRegion *regionp = NULL; - - // This loop is used for retrying a capabilities request. - do - { - if (STATE_WORLD_INIT > LLStartUp::getStartupState()) - { - LL_INFOS("AppInit", "Capabilities") << "Aborting capabilities request, reason: returned to login screen" << LL_ENDL; - return; - } - - if (!LLWorld::instanceExists()) - { - LL_WARNS("AppInit", "Capabilities") << "Attempting to get capabilities, but world no longer exists!" << LL_ENDL; - return; - } - - regionp = LLWorld::getInstance()->getRegionFromHandle(regionHandle); - if (!regionp) //region was removed - { - LL_WARNS("AppInit", "Capabilities") << "Attempting to get capabilities for region that no longer exists!" << LL_ENDL; - return; // this error condition is not recoverable. - } - LLViewerRegionImpl* impl = regionp->getRegionImplNC(); - LL_DEBUGS("AppInit", "Capabilities") << "requesting seed caps for handle " << regionHandle - << " name " << regionp->getName() << LL_ENDL; - - std::string url = regionp->getCapability("Seed"); - if (url.empty()) - { - LL_WARNS("AppInit", "Capabilities") << "Failed to get seed capabilities, and can not determine url!" << LL_ENDL; - regionp->setCapabilitiesError(); - return; // this error condition is not recoverable. - } - - // record that we just entered a new region - newRegionEntry(*regionp); - - if (impl->mSeedCapAttempts > impl->mSeedCapMaxAttempts) - { - // *TODO: Give a user pop-up about this error? - LL_WARNS("AppInit", "Capabilities") << "Failed to get seed capabilities from '" << url << "' after " << impl->mSeedCapAttempts << " attempts. Giving up!" << LL_ENDL; - return; // this error condition is not recoverable. - } - - S32 id = ++(impl->mHttpResponderID); - - LLSD capabilityNames = LLSD::emptyArray(); - impl->buildCapabilityNames(capabilityNames); - - LL_INFOS("AppInit", "Capabilities") << "Requesting seed from " << url - << " region name " << regionp->getName() - << " region id " << regionp->getRegionID() - << " handle " << regionp->getHandle() - << " (attempt #" << impl->mSeedCapAttempts + 1 << ")" << LL_ENDL; - LL_DEBUGS("AppInit", "Capabilities") << "Capabilities requested: " << capabilityNames << LL_ENDL; - - regionp = NULL; - impl = NULL; - result = httpAdapter->postAndSuspend(httpRequest, url, capabilityNames); - - if (STATE_WORLD_INIT > LLStartUp::getStartupState()) - { - LL_INFOS("AppInit", "Capabilities") << "Aborting capabilities request, reason: returned to login screen" << LL_ENDL; - return; - } - - if (LLApp::isExiting() || gDisconnected) - { - LL_DEBUGS("AppInit", "Capabilities") << "Shutting down" << LL_ENDL; - return; - } - - if (!LLWorld::instanceExists()) - { - LL_WARNS("AppInit", "Capabilities") << "Received capabilities, but world no longer exists!" << LL_ENDL; - return; - } - - regionp = LLWorld::getInstance()->getRegionFromHandle(regionHandle); - if (!regionp) //region was removed - { - LL_WARNS("AppInit", "Capabilities") << "Received capabilities for region that no longer exists!" << LL_ENDL; - return; // this error condition is not recoverable. - } - - impl = regionp->getRegionImplNC(); - - ++(impl->mSeedCapAttempts); - - if (!result.isMap() || result.has("error")) - { - LL_WARNS("AppInit", "Capabilities") << "Malformed response" << LL_ENDL; - // setup for retry. - continue; - } - - LLSD httpResults = result["http_result"]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - if (!status) - { - LL_WARNS("AppInit", "Capabilities") << "HttpStatus error " << LL_ENDL; - // setup for retry. - continue; - } - - // remove the http_result from the llsd - result.erase("http_result"); - - if (id != impl->mHttpResponderID) // region is no longer referring to this request - { - LL_WARNS("AppInit", "Capabilities") << "Received results for a stale capabilities request!" << LL_ENDL; - // setup for retry. - continue; - } - - LLSD::map_const_iterator iter; - for (iter = result.beginMap(); iter != result.endMap(); ++iter) - { - regionp->setCapability(iter->first, iter->second); - - LL_DEBUGS("AppInit", "Capabilities") - << "Capability '" << iter->first << "' is '" << iter->second << "'" << LL_ENDL; - } - -#if 0 - log_capabilities(mCapabilities); -#endif - - LL_DEBUGS("AppInit", "Capabilities", "Teleport") << "received caps for handle " << regionHandle - << " region name " << regionp->getName() << LL_ENDL; - regionp->setCapabilitiesReceived(true); - - break; - } - while (true); - - if (regionp && regionp->isCapabilityAvailable("ServerReleaseNotes") && - regionp->getReleaseNotesRequested()) - { // *HACK: we're waiting for the ServerReleaseNotes - regionp->showReleaseNotes(); - } -} - - -void LLViewerRegionImpl::requestBaseCapabilitiesCompleteCoro(U64 regionHandle) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("BaseCapabilitiesRequest", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD result; - LLViewerRegion *regionp = NULL; - - // This loop is used for retrying a capabilities request. - do - { - LLWorld *world_inst = LLWorld::getInstance(); // Not a singleton! - if (!world_inst) - { - LL_WARNS("AppInit", "Capabilities") << "Attempting to get capabilities, but world no longer exists!" << LL_ENDL; - return; - } - - regionp = world_inst->getRegionFromHandle(regionHandle); - if (!regionp) //region was removed - { - LL_WARNS("AppInit", "Capabilities") << "Attempting to get capabilities for region that no longer exists!" << LL_ENDL; - break; // this error condition is not recoverable. - } - - std::string url = regionp->getCapabilityDebug("Seed"); - if (url.empty()) - { - LL_WARNS("AppInit", "Capabilities") << "Failed to get seed capabilities, and can not determine url!" << LL_ENDL; - if (regionp->getCapability("Seed").empty()) - { - // initial attempt failed to get this cap as well - regionp->setCapabilitiesError(); - } - break; // this error condition is not recoverable. - } - - // record that we just entered a new region - newRegionEntry(*regionp); - - LLSD capabilityNames = LLSD::emptyArray(); - buildCapabilityNames(capabilityNames); - - LL_INFOS("AppInit", "Capabilities") << "Requesting second Seed from " << url << " for region " << regionp->getRegionID() << LL_ENDL; - - regionp = NULL; - world_inst = NULL; - result = httpAdapter->postAndSuspend(httpRequest, url, capabilityNames); - - LLSD httpResults = result["http_result"]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - if (!status) - { - LL_WARNS("AppInit", "Capabilities") << "HttpStatus error " << LL_ENDL; - break; // no retry - } - - if (LLApp::isExiting() || gDisconnected) - { - break; - } - - world_inst = LLWorld::getInstance(); - if (!world_inst) - { - LL_WARNS("AppInit", "Capabilities") << "Received capabilities, but world no longer exists!" << LL_ENDL; - return; - } - - regionp = world_inst->getRegionFromHandle(regionHandle); - if (!regionp) //region was removed - { - LL_WARNS("AppInit", "Capabilities") << "Received capabilities for region that no longer exists!" << LL_ENDL; - break; // this error condition is not recoverable. - } - LLViewerRegionImpl* impl = regionp->getRegionImplNC(); - - // remove the http_result from the llsd - result.erase("http_result"); - - LLSD::map_const_iterator iter; - for (iter = result.beginMap(); iter != result.endMap(); ++iter) - { - regionp->setCapabilityDebug(iter->first, iter->second); - //LL_INFOS()<<"BaseCapabilitiesCompleteTracker New Caps "<first<<" "<< iter->second<mCapabilities); -#endif - - if (impl->mCapabilities.size() != impl->mSecondCapabilitiesTracker.size()) - { - LL_WARNS("AppInit", "Capabilities") - << "Sim sent duplicate base caps that differ in size from what we initially received - most likely content. " - << "mCapabilities == " << impl->mCapabilities.size() - << " mSecondCapabilitiesTracker == " << impl->mSecondCapabilitiesTracker.size() - << LL_ENDL; -#ifdef DEBUG_CAPS_GRANTS - LL_WARNS("AppInit", "Capabilities") - << "Initial Base capabilities: " << LL_ENDL; - - log_capabilities(impl->mCapabilities); - - LL_WARNS("AppInit", "Capabilities") - << "Latest base capabilities: " << LL_ENDL; - - log_capabilities(impl->mSecondCapabilitiesTracker); - -#endif - - if (impl->mSecondCapabilitiesTracker.size() > impl->mCapabilities.size()) - { - // *HACK Since we were granted more base capabilities in this grant request than the initial, replace - // the old with the new. This shouldn't happen i.e. we should always get the same capabilities from a - // sim. The simulator fix from SH-3895 should prevent it from happening, at least in the case of the - // inventory api capability grants. - - // Need to clear a std::map before copying into it because old keys take precedence. - impl->mCapabilities.clear(); - impl->mCapabilities = impl->mSecondCapabilitiesTracker; - } - } - else - { - LL_DEBUGS("CrossingCaps") << "Sim sent multiple base cap grants with matching sizes." << LL_ENDL; - } - impl->mSecondCapabilitiesTracker.clear(); - } - while (false); -} - -void LLViewerRegionImpl::requestSimulatorFeatureCoro(std::string url, U64 regionHandle) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("BaseCapabilitiesRequest", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLViewerRegion *regionp = NULL; - S32 attemptNumber = 0; - // This loop is used for retrying a capabilities request. - do - { - ++attemptNumber; - - if (attemptNumber > MAX_CAP_REQUEST_ATTEMPTS) - { - LL_WARNS("AppInit", "SimulatorFeatures") << "Retries count exceeded attempting to get Simulator feature from " - << url << LL_ENDL; - break; - } - - LLWorld *world_inst = LLWorld::getInstance(); // Not a singleton! - if (!world_inst) - { - LL_WARNS("AppInit", "Capabilities") << "Attempting to request Sim Feature, but world no longer exists!" << LL_ENDL; - return; - } - - regionp = world_inst->getRegionFromHandle(regionHandle); - if (!regionp) //region was removed - { - LL_WARNS("AppInit", "SimulatorFeatures") << "Attempting to request Sim Feature for region that no longer exists!" << LL_ENDL; - break; // this error condition is not recoverable. - } - - regionp = NULL; - world_inst = NULL; - LLSD result = httpAdapter->getAndSuspend(httpRequest, url); - - LLSD httpResults = result["http_result"]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - if (!status) - { - LL_WARNS("AppInit", "SimulatorFeatures") << "HttpStatus error retrying" << LL_ENDL; - continue; - } - - if (LLApp::isExiting() || gDisconnected) - { - break; - } - - // remove the http_result from the llsd - result.erase("http_result"); - - world_inst = LLWorld::getInstance(); - if (!world_inst) - { - LL_WARNS("AppInit", "Capabilities") << "Attempting to request Sim Feature, but world no longer exists!" << LL_ENDL; - return; - } - - regionp = world_inst->getRegionFromHandle(regionHandle); - if (!regionp) //region was removed - { - LL_WARNS("AppInit", "SimulatorFeatures") << "Attempting to set Sim Feature for region that no longer exists!" << LL_ENDL; - break; // this error condition is not recoverable. - } - - regionp->setSimulatorFeatures(result); - - break; - } - while (true); - -} - -LLViewerRegion::LLViewerRegion(const U64 &handle, - const LLHost &host, - const U32 grids_per_region_edge, - const U32 grids_per_patch_edge, - const F32 region_width_meters) -: mImpl(new LLViewerRegionImpl(this, host)), - mHandle(handle), - mTimeDilation(1.0f), - mName(""), - mZoning(""), - mIsEstateManager(false), - mRegionFlags( REGION_FLAGS_DEFAULT ), - mRegionProtocols( 0 ), - mSimAccess( SIM_ACCESS_MIN ), - mBillableFactor(1.0), - mMaxTasks(DEFAULT_MAX_REGION_WIDE_PRIM_COUNT), - mCentralBakeVersion(1), - mClassID(0), - mCPURatio(0), - mColoName("unknown"), - mProductSKU("unknown"), - mProductName("unknown"), - mViewerAssetUrl(""), - mCacheLoaded(false), - mCacheDirty(false), - mReleaseNotesRequested(false), - mCapabilitiesState(CAPABILITIES_STATE_INIT), - mSimulatorFeaturesReceived(false), - mBitsReceived(0.f), - mPacketsReceived(0.f), - mDead(false), - mLastVisitedEntry(NULL), - mInvisibilityCheckHistory(-1), - mPaused(false), - mRegionCacheHitCount(0), - mRegionCacheMissCount(0), - mInterestListMode(IL_MODE_DEFAULT) -{ - mWidth = region_width_meters; - mImpl->mOriginGlobal = from_region_handle(handle); - updateRenderMatrix(); - - mImpl->mLandp = new LLSurface('l', NULL); - - // Create the composition layer for the surface - mImpl->mCompositionp = - new LLVLComposition(mImpl->mLandp, - grids_per_region_edge, - region_width_meters / grids_per_region_edge); - mImpl->mCompositionp->setSurface(mImpl->mLandp); - - // Create the surfaces - mImpl->mLandp->setRegion(this); - mImpl->mLandp->create(grids_per_region_edge, - grids_per_patch_edge, - mImpl->mOriginGlobal, - mWidth); - - mParcelOverlay = new LLViewerParcelOverlay(this, region_width_meters); - - setOriginGlobal(from_region_handle(handle)); - calculateCenterGlobal(); - - // Create the object lists - initStats(); - - //create object partitions - //MUST MATCH declaration of eObjectPartitions - mImpl->mObjectPartition.push_back(new LLHUDPartition(this)); //PARTITION_HUD - mImpl->mObjectPartition.push_back(new LLTerrainPartition(this)); //PARTITION_TERRAIN - mImpl->mObjectPartition.push_back(new LLVoidWaterPartition(this)); //PARTITION_VOIDWATER - mImpl->mObjectPartition.push_back(new LLWaterPartition(this)); //PARTITION_WATER - mImpl->mObjectPartition.push_back(new LLTreePartition(this)); //PARTITION_TREE - mImpl->mObjectPartition.push_back(new LLParticlePartition(this)); //PARTITION_PARTICLE - mImpl->mObjectPartition.push_back(new LLGrassPartition(this)); //PARTITION_GRASS - mImpl->mObjectPartition.push_back(new LLVolumePartition(this)); //PARTITION_VOLUME - mImpl->mObjectPartition.push_back(new LLBridgePartition(this)); //PARTITION_BRIDGE - mImpl->mObjectPartition.push_back(new LLAvatarPartition(this)); //PARTITION_AVATAR - mImpl->mObjectPartition.push_back(new LLControlAVPartition(this)); //PARTITION_CONTROL_AV - mImpl->mObjectPartition.push_back(new LLHUDParticlePartition(this));//PARTITION_HUD_PARTICLE - mImpl->mObjectPartition.push_back(new LLVOCachePartition(this)); //PARTITION_VO_CACHE - mImpl->mObjectPartition.push_back(NULL); //PARTITION_NONE - mImpl->mVOCachePartition = getVOCachePartition(); - - setCapabilitiesReceivedCallback(boost::bind(&LLAvatarRenderInfoAccountant::scanNewRegion, _1)); -} - - -void LLViewerRegion::initStats() -{ - mImpl->mLastNetUpdate.reset(); - mPacketsIn = 0; - mBitsIn = (U32Bits)0; - mLastBitsIn = (U32Bits)0; - mLastPacketsIn = 0; - mPacketsOut = 0; - mLastPacketsOut = 0; - mPacketsLost = 0; - mLastPacketsLost = 0; - mPingDelay = (U32Seconds)0; - mAlive = false; // can become false if circuit disconnects -} - -static LLTrace::BlockTimerStatHandle FTM_CLEANUP_REGION_OBJECTS("Cleanup Region Objects"); -static LLTrace::BlockTimerStatHandle FTM_SAVE_REGION_CACHE("Save Region Cache"); - -LLViewerRegion::~LLViewerRegion() -{ - LL_PROFILE_ZONE_SCOPED; - mDead = true; - mImpl->mActiveSet.clear(); - mImpl->mVisibleEntries.clear(); - mImpl->mVisibleGroups.clear(); - mImpl->mWaitingSet.clear(); - - gVLManager.cleanupData(this); - // Can't do this on destruction, because the neighbor pointers might be invalid. - // This should be reference counted... - disconnectAllNeighbors(); - LLViewerPartSim::getInstance()->cleanupRegion(this); - - { - LL_RECORD_BLOCK_TIME(FTM_CLEANUP_REGION_OBJECTS); - gObjectList.killObjects(this); - } - - delete mImpl->mCompositionp; - delete mParcelOverlay; - delete mImpl->mLandp; - delete mImpl->mEventPoll; -#if 0 - LLHTTPSender::clearSender(mImpl->mHost); -#endif - std::for_each(mImpl->mObjectPartition.begin(), mImpl->mObjectPartition.end(), DeletePointer()); - - { - LL_RECORD_BLOCK_TIME(FTM_SAVE_REGION_CACHE); - saveObjectCache(); - } - - delete mImpl; - mImpl = NULL; -} - -/*virtual*/ -const LLHost& LLViewerRegion::getHost() const -{ - return mImpl->mHost; -} - -LLSurface & LLViewerRegion::getLand() const -{ - return *mImpl->mLandp; -} - -const LLUUID& LLViewerRegion::getRegionID() const -{ - return mImpl->mRegionID; -} - -void LLViewerRegion::setRegionID(const LLUUID& region_id) -{ - mImpl->mRegionID = region_id; -} - -void LLViewerRegion::loadObjectCache() -{ - if (mCacheLoaded) - { - return; - } - - // Presume success. If it fails, we don't want to try again. - mCacheLoaded = true; - - if(LLVOCache::instanceExists()) - { - LLVOCache & vocache = LLVOCache::instance(); - vocache.readFromCache(mHandle, mImpl->mCacheID, mImpl->mCacheMap); - vocache.readGenericExtrasFromCache(mHandle, mImpl->mCacheID, mImpl->mGLTFOverridesLLSD); - - if (mImpl->mCacheMap.empty()) - { - mCacheDirty = true; - } - } -} - - -void LLViewerRegion::saveObjectCache() -{ - if (!mCacheLoaded) - { - return; - } - - if (mImpl->mCacheMap.empty()) - { - return; - } - - if(LLVOCache::instanceExists()) - { - const F32 start_time_threshold = 600.0f; //seconds - bool removal_enabled = sVOCacheCullingEnabled && (mRegionTimer.getElapsedTimeF32() > start_time_threshold); //allow to remove invalid objects from object cache file. - - LLVOCache & instance = LLVOCache::instance(); - - instance.writeToCache(mHandle, mImpl->mCacheID, mImpl->mCacheMap, mCacheDirty, removal_enabled); - instance.writeGenericExtrasToCache(mHandle, mImpl->mCacheID, mImpl->mGLTFOverridesLLSD, mCacheDirty, removal_enabled); - mCacheDirty = false; - } - - if (LLAppViewer::instance()->isQuitting()) - { - mImpl->mCacheMap.clear(); - } - else - { - // Map of LLVOCacheEntry takes time to release, store map for cleanup on idle - sRegionCacheCleanup.insert(mImpl->mCacheMap.begin(), mImpl->mCacheMap.end()); - mImpl->mCacheMap.clear(); - // TODO - probably need to do the same for overrides cache - } -} - -void LLViewerRegion::sendMessage() -{ - gMessageSystem->sendMessage(mImpl->mHost); -} - -void LLViewerRegion::sendReliableMessage() -{ - gMessageSystem->sendReliable(mImpl->mHost); -} - -void LLViewerRegion::setWaterHeight(F32 water_level) -{ - mImpl->mLandp->setWaterHeight(water_level); -} - -F32 LLViewerRegion::getWaterHeight() const -{ - return mImpl->mLandp->getWaterHeight(); -} - -bool LLViewerRegion::isVoiceEnabled() const -{ - return getRegionFlag(REGION_FLAGS_ALLOW_VOICE); -} - -void LLViewerRegion::setRegionFlags(U64 flags) -{ - mRegionFlags = flags; -} - - -void LLViewerRegion::setOriginGlobal(const LLVector3d &origin_global) -{ - mImpl->mOriginGlobal = origin_global; - updateRenderMatrix(); - mImpl->mLandp->setOriginGlobal(origin_global); - mWind.setOriginGlobal(origin_global); - calculateCenterGlobal(); -} - -void LLViewerRegion::updateRenderMatrix() -{ - mRenderMatrix.setTranslation(getOriginAgent()); -} - -void LLViewerRegion::setTimeDilation(F32 time_dilation) -{ - mTimeDilation = time_dilation; -} - -const LLVector3d & LLViewerRegion::getOriginGlobal() const -{ - return mImpl->mOriginGlobal; -} - -LLVector3 LLViewerRegion::getOriginAgent() const -{ - return gAgent.getPosAgentFromGlobal(mImpl->mOriginGlobal); -} - -const LLVector3d & LLViewerRegion::getCenterGlobal() const -{ - return mImpl->mCenterGlobal; -} - -LLVector3 LLViewerRegion::getCenterAgent() const -{ - return gAgent.getPosAgentFromGlobal(mImpl->mCenterGlobal); -} - -void LLViewerRegion::setOwner(const LLUUID& owner_id) -{ - mImpl->mOwnerID = owner_id; -} - -const LLUUID& LLViewerRegion::getOwner() const -{ - return mImpl->mOwnerID; -} - -void LLViewerRegion::setRegionNameAndZone (const std::string& name_zone) -{ - std::string::size_type pipe_pos = name_zone.find('|'); - S32 length = name_zone.size(); - if (pipe_pos != std::string::npos) - { - mName = name_zone.substr(0, pipe_pos); - mZoning = name_zone.substr(pipe_pos+1, length-(pipe_pos+1)); - } - else - { - mName = name_zone; - mZoning = ""; - } - - LLStringUtil::stripNonprintable(mName); - LLStringUtil::stripNonprintable(mZoning); -} - -bool LLViewerRegion::canManageEstate() const -{ - return gAgent.isGodlike() - || isEstateManager() - || gAgent.getID() == getOwner(); -} - -const std::string LLViewerRegion::getSimAccessString() const -{ - return accessToString(mSimAccess); -} - -std::string LLViewerRegion::getLocalizedSimProductName() const -{ - std::string localized_spn; - return LLTrans::findString(localized_spn, mProductName) ? localized_spn : mProductName; -} - -// static -std::string LLViewerRegion::regionFlagsToString(U64 flags) -{ - std::string result; - - if (flags & REGION_FLAGS_SANDBOX) - { - result += "Sandbox"; - } - - if (flags & REGION_FLAGS_ALLOW_DAMAGE) - { - result += " Not Safe"; - } - - return result; -} - -// static -std::string LLViewerRegion::accessToString(U8 sim_access) -{ - switch(sim_access) - { - case SIM_ACCESS_PG: - return LLTrans::getString("SIM_ACCESS_PG"); - - case SIM_ACCESS_MATURE: - return LLTrans::getString("SIM_ACCESS_MATURE"); - - case SIM_ACCESS_ADULT: - return LLTrans::getString("SIM_ACCESS_ADULT"); - - case SIM_ACCESS_DOWN: - return LLTrans::getString("SIM_ACCESS_DOWN"); - - case SIM_ACCESS_MIN: - default: - return LLTrans::getString("SIM_ACCESS_MIN"); - } -} - -// static -std::string LLViewerRegion::getAccessIcon(U8 sim_access) -{ - switch(sim_access) - { - case SIM_ACCESS_MATURE: - return "Parcel_M_Dark"; - - case SIM_ACCESS_ADULT: - return "Parcel_R_Light"; - - case SIM_ACCESS_PG: - return "Parcel_PG_Light"; - - case SIM_ACCESS_MIN: - default: - return ""; - } -} - -// static -std::string LLViewerRegion::accessToShortString(U8 sim_access) -{ - switch(sim_access) /* Flawfinder: ignore */ - { - case SIM_ACCESS_PG: - return "PG"; - - case SIM_ACCESS_MATURE: - return "M"; - - case SIM_ACCESS_ADULT: - return "A"; - - case SIM_ACCESS_MIN: - default: - return "U"; - } -} - -// static -U8 LLViewerRegion::shortStringToAccess(const std::string &sim_access) -{ - U8 accessValue; - - if (LLStringUtil::compareStrings(sim_access, "PG") == 0) - { - accessValue = SIM_ACCESS_PG; - } - else if (LLStringUtil::compareStrings(sim_access, "M") == 0) - { - accessValue = SIM_ACCESS_MATURE; - } - else if (LLStringUtil::compareStrings(sim_access, "A") == 0) - { - accessValue = SIM_ACCESS_ADULT; - } - else - { - accessValue = SIM_ACCESS_MIN; - } - - return accessValue; -} - -// static -void LLViewerRegion::processRegionInfo(LLMessageSystem* msg, void**) -{ - // send it to 'observers' - // *TODO: switch the floaters to using LLRegionInfoModel - LL_INFOS() << "Processing region info" << LL_ENDL; - LLRegionInfoModel::instance().update(msg); - LLFloaterGodTools::processRegionInfo(msg); - LLFloaterRegionInfo::processRegionInfo(msg); -} - -void LLViewerRegion::setCacheID(const LLUUID& id) -{ - mImpl->mCacheID = id; -} - -void LLViewerRegion::renderPropertyLines() -{ - if (mParcelOverlay) - { - mParcelOverlay->renderPropertyLines(); - } -} - -void LLViewerRegion::renderPropertyLinesOnMinimap(F32 scale_pixels_per_meter, const F32 *parcel_outline_color) -{ - if (mParcelOverlay) - { - mParcelOverlay->renderPropertyLinesOnMinimap(scale_pixels_per_meter, parcel_outline_color); - } -} - - -// This gets called when the height field changes. -void LLViewerRegion::dirtyHeights() -{ - // Property lines need to be reconstructed when the land changes. - if (mParcelOverlay) - { - mParcelOverlay->setDirty(); - } -} - -//physically delete the cache entry -void LLViewerRegion::killCacheEntry(LLVOCacheEntry* entry, bool for_rendering) -{ - if(!entry || !entry->isValid()) - { - return; - } - - if(for_rendering && !entry->isState(LLVOCacheEntry::ACTIVE)) - { - addNewObject(entry); //force to add to rendering pipeline - } - - //remove from active list and waiting list - if(entry->isState(LLVOCacheEntry::ACTIVE)) - { - mImpl->mActiveSet.erase(entry); - } - else - { - if(entry->isState(LLVOCacheEntry::WAITING)) - { - mImpl->mWaitingSet.erase(entry); - } - - //remove from mVOCachePartition - removeFromVOCacheTree(entry); - } - - //remove from the forced visible list - mImpl->mVisibleEntries.erase(entry); - - //disconnect from parent if it is a child - if(entry->getParentID() > 0) - { - LLVOCacheEntry* parent = getCacheEntry(entry->getParentID()); - if(parent) - { - parent->removeChild(entry); - } - } - else if(entry->getNumOfChildren() > 0)//remove children from cache if has any - { - LLVOCacheEntry* child = entry->getChild(); - while(child != NULL) - { - killCacheEntry(child, for_rendering); - child = entry->getChild(); - } - } - - //will remove it from the object cache, real deletion - entry->setState(LLVOCacheEntry::INACTIVE); - entry->removeOctreeEntry(); - entry->setValid(false); - - // TODO kill extras/material overrides cache too -} - -//physically delete the cache entry -void LLViewerRegion::killCacheEntry(U32 local_id) -{ - killCacheEntry(getCacheEntry(local_id)); -} - -U32 LLViewerRegion::getNumOfActiveCachedObjects() const -{ - return mImpl->mActiveSet.size(); -} - -void LLViewerRegion::addActiveCacheEntry(LLVOCacheEntry* entry) -{ - if(!entry || mDead) - { - return; - } - if(entry->isState(LLVOCacheEntry::ACTIVE)) - { - return; //already inserted. - } - - if(entry->isState(LLVOCacheEntry::WAITING)) - { - mImpl->mWaitingSet.erase(entry); - } - - entry->setState(LLVOCacheEntry::ACTIVE); - entry->setVisible(); - - llassert(entry->getEntry()->hasDrawable()); - mImpl->mActiveSet.insert(entry); -} - -void LLViewerRegion::removeActiveCacheEntry(LLVOCacheEntry* entry, LLDrawable* drawablep) -{ - if(mDead || !entry || !entry->isValid()) - { - return; - } - if(!entry->isState(LLVOCacheEntry::ACTIVE)) - { - return; //not an active entry. - } - - //shift to the local regional space from agent space - if(drawablep != NULL && drawablep->getVObj().notNull()) - { - const LLVector3& pos = drawablep->getVObj()->getPositionRegion(); - LLVector4a shift; - shift.load3(pos.mV); - shift.sub(entry->getPositionGroup()); - entry->shift(shift); - } - - if(entry->getParentID() > 0) //is a child - { - LLVOCacheEntry* parent = getCacheEntry(entry->getParentID()); - if(parent) - { - parent->addChild(entry); - } - else //parent not in cache. - { - //this happens only when parent is not cacheable. - mOrphanMap[entry->getParentID()].push_back(entry->getLocalID()); - } - } - else //insert to vo cache tree. - { - entry->updateParentBoundingInfo(); - entry->saveBoundingSphere(); - addToVOCacheTree(entry); - } - - mImpl->mVisibleEntries.erase(entry); - mImpl->mActiveSet.erase(entry); - mImpl->mWaitingSet.erase(entry); - entry->setState(LLVOCacheEntry::INACTIVE); -} - -bool LLViewerRegion::addVisibleGroup(LLViewerOctreeGroup* group) -{ - if(mDead || group->isEmpty()) - { - return false; - } - - mImpl->mVisibleGroups.insert(group); - - return true; -} - -U32 LLViewerRegion::getNumOfVisibleGroups() const -{ - return mImpl ? mImpl->mVisibleGroups.size() : 0; -} - -void LLViewerRegion::updateReflectionProbes() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY; - const F32 probe_spacing = 32.f; - const F32 probe_radius = sqrtf((probe_spacing * 0.5f) * (probe_spacing * 0.5f) * 3.f); - const F32 hover_height = 2.f; - - F32 start = probe_spacing * 0.5f; - - U32 grid_width = REGION_WIDTH_METERS / probe_spacing; - - mReflectionMaps.resize(grid_width * grid_width); - - F32 water_height = getWaterHeight(); - LLVector3 origin = getOriginAgent(); - - for (U32 i = 0; i < grid_width; ++i) - { - F32 x = i * probe_spacing + start; - for (U32 j = 0; j < grid_width; ++j) - { - F32 y = j * probe_spacing + start; - - U32 idx = i * grid_width + j; - - if (mReflectionMaps[idx].isNull()) - { - mReflectionMaps[idx] = gPipeline.mReflectionMapManager.addProbe(); - } - - LLVector3 probe_origin = LLVector3(x, y, llmax(water_height, mImpl->mLandp->resolveHeightRegion(x, y))); - probe_origin.mV[2] += hover_height; - probe_origin += origin; - - mReflectionMaps[idx]->mOrigin.load3(probe_origin.mV); - mReflectionMaps[idx]->mRadius = probe_radius; - } - } -} - -void LLViewerRegion::addToVOCacheTree(LLVOCacheEntry* entry) -{ - if(!sVOCacheCullingEnabled) - { - return; - } - - if(mDead || !entry || !entry->getEntry() || !entry->isValid()) - { - return; - } - if(entry->getParentID() > 0) - { - return; //no child prim in cache octree. - } - - if(entry->hasState(LLVOCacheEntry::IN_VO_TREE)) - { - return; //already in the tree. - } - - llassert_always(!entry->getGroup()); //not in octree. - llassert(!entry->getEntry()->hasDrawable()); //not have drawables - - if(mImpl->mVOCachePartition->addEntry(entry->getEntry())) - { - entry->setState(LLVOCacheEntry::IN_VO_TREE); - } -} - -void LLViewerRegion::removeFromVOCacheTree(LLVOCacheEntry* entry) -{ - if(mDead || !entry || !entry->getEntry()) - { - return; - } - - if(!entry->hasState(LLVOCacheEntry::IN_VO_TREE)) - { - return; //not in the tree. - } - entry->clearState(LLVOCacheEntry::IN_VO_TREE); - - mImpl->mVOCachePartition->removeEntry(entry->getEntry()); -} - -//add child objects as visible entries -void LLViewerRegion::addVisibleChildCacheEntry(LLVOCacheEntry* parent, LLVOCacheEntry* child) -{ - if(mDead) - { - return; - } - - if(parent && (!parent->isValid() || !parent->isState(LLVOCacheEntry::ACTIVE))) - { - return; //parent must be valid and in rendering pipeline - } - - if(child && (!child->getEntry() || !child->isValid() || !child->isState(LLVOCacheEntry::INACTIVE))) - { - return; //child must be valid and not in the rendering pipeline - } - - if(child) - { - child->setState(LLVOCacheEntry::IN_QUEUE); - mImpl->mVisibleEntries.insert(child); - } - else if(parent && parent->getNumOfChildren() > 0) //add all children - { - child = parent->getChild(); - while(child != NULL) - { - addVisibleChildCacheEntry(NULL, child); - child = parent->getChild(); - } - } -} - -void LLViewerRegion::updateVisibleEntries(F32 max_time) -{ - if(mDead) - { - return; - } - - if(mImpl->mVisibleGroups.empty() && mImpl->mVisibleEntries.empty()) - { - return; - } - - if(!sNewObjectCreationThrottle) - { - return; - } - - const F32 LARGE_SCENE_CONTRIBUTION = 1000.f; //a large number to force to load the object. - const LLVector3 camera_origin = LLViewerCamera::getInstance()->getOrigin(); - const U32 cur_frame = LLViewerOctreeEntryData::getCurrentFrame(); - bool needs_update = ((cur_frame - mImpl->mLastCameraUpdate) > 5) && ((camera_origin - mImpl->mLastCameraOrigin).lengthSquared() > 10.f); - U32 last_update = mImpl->mLastCameraUpdate; - LLVector4a local_origin; - local_origin.load3((camera_origin - getOriginAgent()).mV); - - //process visible entries - for(LLVOCacheEntry::vocache_entry_set_t::iterator iter = mImpl->mVisibleEntries.begin(); iter != mImpl->mVisibleEntries.end();) - { - LLVOCacheEntry* vo_entry = *iter; - - if(vo_entry->isValid() && vo_entry->getState() < LLVOCacheEntry::WAITING) - { - //set a large number to force to load this object. - vo_entry->setSceneContribution(LARGE_SCENE_CONTRIBUTION); - - mImpl->mWaitingList.insert(vo_entry); - ++iter; - } - else - { - LLVOCacheEntry::vocache_entry_set_t::iterator next_iter = iter; - ++next_iter; - mImpl->mVisibleEntries.erase(iter); - iter = next_iter; - } - } - - // - //process visible groups - // - //object projected area threshold - F32 projection_threshold = LLVOCacheEntry::getSquaredPixelThreshold(mImpl->mVOCachePartition->isFrontCull()); - F32 dist_threshold = mImpl->mVOCachePartition->isFrontCull() ? gAgentCamera.mDrawDistance : LLVOCacheEntry::sRearFarRadius; - - std::set< LLPointer >::iterator group_iter = mImpl->mVisibleGroups.begin(); - for(; group_iter != mImpl->mVisibleGroups.end(); ++group_iter) - { - LLPointer group = *group_iter; - if(group->getNumRefs() < 3 || //group to be deleted - !group->getOctreeNode() || group->isEmpty()) //group empty - { - continue; - } - - for (LLViewerOctreeGroup::element_iter i = group->getDataBegin(); i != group->getDataEnd(); ++i) - { - if((*i)->hasVOCacheEntry()) - { - LLVOCacheEntry* vo_entry = (LLVOCacheEntry*)(*i)->getVOCacheEntry(); - - if(vo_entry->getParentID() > 0) //is a child - { - //child visibility depends on its parent. - continue; - } - if(!vo_entry->isValid()) - { - continue; //skip invalid entry. - } - - vo_entry->calcSceneContribution(local_origin, needs_update, last_update, dist_threshold); - if(vo_entry->getSceneContribution() > projection_threshold) - { - mImpl->mWaitingList.insert(vo_entry); - } - } - } - } - - if(needs_update) - { - mImpl->mLastCameraOrigin = camera_origin; - mImpl->mLastCameraUpdate = cur_frame; - } - - return; -} - -void LLViewerRegion::createVisibleObjects(F32 max_time) -{ - if(mDead) - { - return; - } - if(mImpl->mWaitingList.empty()) - { - mImpl->mVOCachePartition->setCullHistory(false); - return; - } - - S32 throttle = sNewObjectCreationThrottle; - bool has_new_obj = false; - LLTimer update_timer; - for(LLVOCacheEntry::vocache_entry_priority_list_t::iterator iter = mImpl->mWaitingList.begin(); - iter != mImpl->mWaitingList.end(); ++iter) - { - LLVOCacheEntry* vo_entry = *iter; - - if(vo_entry->getState() < LLVOCacheEntry::WAITING) - { - addNewObject(vo_entry); - has_new_obj = true; - if(throttle > 0 && !(--throttle) && update_timer.getElapsedTimeF32() > max_time) - { - break; - } - } - } - - mImpl->mVOCachePartition->setCullHistory(has_new_obj); - - return; -} - -void LLViewerRegion::clearCachedVisibleObjects() -{ - mImpl->mWaitingList.clear(); - mImpl->mVisibleGroups.clear(); - - //reset all occluders - mImpl->mVOCachePartition->resetOccluders(); - mPaused = true; - - //clean visible entries - for(LLVOCacheEntry::vocache_entry_set_t::iterator iter = mImpl->mVisibleEntries.begin(); iter != mImpl->mVisibleEntries.end();) - { - LLVOCacheEntry* entry = *iter; - LLVOCacheEntry* parent = getCacheEntry(entry->getParentID()); - - if(!entry->getParentID() || parent) //no child or parent is cache-able - { - if(parent) //has a cache-able parent - { - parent->addChild(entry); - } - - LLVOCacheEntry::vocache_entry_set_t::iterator next_iter = iter; - ++next_iter; - mImpl->mVisibleEntries.erase(iter); - iter = next_iter; - } - else //parent is not cache-able, leave it. - { - ++iter; - } - } - - //remove all visible entries. - mLastVisitedEntry = NULL; - std::vector delete_list; - for(LLVOCacheEntry::vocache_entry_set_t::iterator iter = mImpl->mActiveSet.begin(); - iter != mImpl->mActiveSet.end(); ++iter) - { - LLVOCacheEntry* vo_entry = *iter; - if (!vo_entry || !vo_entry->getEntry()) - { - continue; - } - LLDrawable* drawablep = (LLDrawable*)vo_entry->getEntry()->getDrawable(); - - if(drawablep && !drawablep->getParent()) - { - delete_list.push_back(drawablep); - } - } - - if(!delete_list.empty()) - { - for(S32 i = 0; i < delete_list.size(); i++) - { - gObjectList.killObject(delete_list[i]->getVObj()); - } - delete_list.clear(); - } - - return; -} - -//perform some necessary but very light updates. -//to replace the function idleUpdate(...) in case there is no enough time. -void LLViewerRegion::lightIdleUpdate() -{ - if(!sVOCacheCullingEnabled) - { - return; - } - if(mImpl->mCacheMap.empty()) - { - return; - } - - //reset all occluders - mImpl->mVOCachePartition->resetOccluders(); -} - -void LLViewerRegion::idleUpdate(F32 max_update_time) -{ - LL_PROFILE_ZONE_SCOPED; - LLTimer update_timer; - F32 max_time; - - mLastUpdate = LLViewerOctreeEntryData::getCurrentFrame(); - - mImpl->mLandp->idleUpdate(max_update_time); - - if (mParcelOverlay) - { - // Hopefully not a significant time sink... - mParcelOverlay->idleUpdate(); - } - - if(!sVOCacheCullingEnabled) - { - return; - } - if(mImpl->mCacheMap.empty()) - { - return; - } - if(mPaused) - { - mPaused = false; //unpause. - } - - LLViewerCamera::eCameraID old_camera_id = LLViewerCamera::sCurCameraID; - LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD; - - //reset all occluders - mImpl->mVOCachePartition->resetOccluders(); - - max_time = max_update_time - update_timer.getElapsedTimeF32(); - - //kill invisible objects - killInvisibleObjects(max_time * 0.4f); - max_time = max_update_time - update_timer.getElapsedTimeF32(); - - updateVisibleEntries(max_time); - max_time = max_update_time - update_timer.getElapsedTimeF32(); - - createVisibleObjects(max_time); - - mImpl->mWaitingList.clear(); - mImpl->mVisibleGroups.clear(); - - LLViewerCamera::sCurCameraID = old_camera_id; - return; -} - -// static -void LLViewerRegion::idleCleanup(F32 max_update_time) -{ - LLTimer update_timer; - while (!sRegionCacheCleanup.empty() && (max_update_time - update_timer.getElapsedTimeF32() > 0)) - { - sRegionCacheCleanup.erase(sRegionCacheCleanup.begin()); - } -} - -//update the throttling number for new object creation -void LLViewerRegion::calcNewObjectCreationThrottle() -{ - static LLCachedControl new_object_creation_throttle(gSavedSettings,"NewObjectCreationThrottle"); - static LLCachedControl throttle_delay_time(gSavedSettings,"NewObjectCreationThrottleDelayTime"); - static LLFrameTimer timer; - - // - //sNewObjectCreationThrottle = - //-2: throttle is disabled because either the screen is showing progress view, or immediate after the screen is not black - //-1: throttle is disabled by the debug setting - //0: no new object creation is allowed - //>0: valid throttling number - // - - if(gViewerWindow->getProgressView()->getVisible() && throttle_delay_time > 0.f) - { - sNewObjectCreationThrottle = -2; //cancel the throttling - timer.reset(); - } - else if(sNewObjectCreationThrottle < -1) //just recoved from the login/teleport screen - { - if(timer.getElapsedTimeF32() > throttle_delay_time) //wait for throttle_delay_time to reset the throttle - { - sNewObjectCreationThrottle = new_object_creation_throttle; //reset - if(sNewObjectCreationThrottle < -1) - { - sNewObjectCreationThrottle = -1; - } - } - } - - //update some LLVOCacheEntry debug setting factors. - LLVOCacheEntry::updateDebugSettings(); -} - -bool LLViewerRegion::isViewerCameraStatic() -{ - return sLastCameraUpdated < LLViewerOctreeEntryData::getCurrentFrame(); -} - -void LLViewerRegion::killInvisibleObjects(F32 max_time) -{ -#if 1 // TODO: kill this. This is ill-conceived, objects that aren't in the camera frustum should not be deleted from memory. - // because of this, every time you turn around the simulator sends a swarm of full object update messages from cache - // probe misses and objects have to be reloaded from scratch. From some reason, disabling this causes holes to - // appear in the scene when flying back and forth between regions - if(!sVOCacheCullingEnabled) - { - return; - } - if(mImpl->mActiveSet.empty()) - { - return; - } - if(sNewObjectCreationThrottle < 0) - { - return; - } - - LLTimer update_timer; - LLVector4a camera_origin; - camera_origin.load3(LLViewerCamera::getInstance()->getOrigin().mV); - LLVector4a local_origin; - local_origin.load3((LLViewerCamera::getInstance()->getOrigin() - getOriginAgent()).mV); - F32 back_threshold = LLVOCacheEntry::sRearFarRadius; - - size_t max_update = 64; - if(!mInvisibilityCheckHistory && isViewerCameraStatic()) - { - //history is clean, reduce number of checking - max_update /= 2; - } - - std::vector delete_list; - S32 update_counter = llmin(max_update, mImpl->mActiveSet.size()); - LLVOCacheEntry::vocache_entry_set_t::iterator iter = mImpl->mActiveSet.upper_bound(mLastVisitedEntry); - - for(; update_counter > 0; --update_counter, ++iter) - { - if(iter == mImpl->mActiveSet.end()) - { - iter = mImpl->mActiveSet.begin(); - } - if((*iter)->getParentID() > 0) - { - continue; //skip child objects, they are removed with their parent. - } - - LLVOCacheEntry* vo_entry = *iter; - if(!vo_entry->isAnyVisible(camera_origin, local_origin, back_threshold) && vo_entry->mLastCameraUpdated < sLastCameraUpdated) - { - killObject(vo_entry, delete_list); - } - - if(max_time < update_timer.getElapsedTimeF32()) //time out - { - break; - } - } - - if(iter == mImpl->mActiveSet.end()) - { - mLastVisitedEntry = NULL; - } - else - { - mLastVisitedEntry = *iter; - } - - mInvisibilityCheckHistory <<= 1; - if(!delete_list.empty()) - { - mInvisibilityCheckHistory |= 1; - S32 count = delete_list.size(); - for(S32 i = 0; i < count; i++) - { - gObjectList.killObject(delete_list[i]->getVObj()); - } - delete_list.clear(); - } - - return; -#endif -} - -void LLViewerRegion::killObject(LLVOCacheEntry* entry, std::vector& delete_list) -{ - //kill the object. - LLDrawable* drawablep = (LLDrawable*)entry->getEntry()->getDrawable(); - llassert(drawablep); - llassert(drawablep->getRegion() == this); - - if(drawablep && !drawablep->getParent()) - { - LLViewerObject* v_obj = drawablep->getVObj(); - if (v_obj->isSelected() - || (v_obj->flagAnimSource() && isAgentAvatarValid() && gAgentAvatarp->hasMotionFromSource(v_obj->getID()))) - { - // do not remove objects user is interacting with - ((LLViewerOctreeEntryData*)drawablep)->setVisible(); - return; - } - LLViewerObject::const_child_list_t& child_list = v_obj->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); iter++) - { - LLViewerObject* child = *iter; - if(child->mDrawable) - { - if( !child->mDrawable->getEntry() - || !child->mDrawable->getEntry()->hasVOCacheEntry() - || child->isSelected() - || (child->flagAnimSource() && isAgentAvatarValid() && gAgentAvatarp->hasMotionFromSource(child->getID()))) - { - //do not remove parent if any of its children non-cacheable, animating or selected - //especially for the case that an avatar sits on a cache-able object - ((LLViewerOctreeEntryData*)drawablep)->setVisible(); - return; - } - - LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*)child->mDrawable->getGroup(); - if(group && group->isAnyRecentlyVisible()) - { - //set the parent visible if any of its children visible. - ((LLViewerOctreeEntryData*)drawablep)->setVisible(); - return; - } - } - } - delete_list.push_back(drawablep); - } -} - -LLViewerObject* LLViewerRegion::addNewObject(LLVOCacheEntry* entry) -{ - if(!entry || !entry->getEntry()) - { - if(entry) - { - mImpl->mVisibleEntries.erase(entry); - entry->setState(LLVOCacheEntry::INACTIVE); - } - return NULL; - } - - LLViewerObject* obj = NULL; - if(!entry->getEntry()->hasDrawable()) //not added to the rendering pipeline yet - { - //add the object - obj = gObjectList.processObjectUpdateFromCache(entry, this); - if(obj) - { - if(!entry->isState(LLVOCacheEntry::ACTIVE)) - { - mImpl->mWaitingSet.insert(entry); - entry->setState(LLVOCacheEntry::WAITING); - } - } - } - else - { - LLViewerRegion* old_regionp = ((LLDrawable*)entry->getEntry()->getDrawable())->getRegion(); - if(old_regionp != this) - { - //this object exists in two regions at the same time; - //this case can be safely ignored here because - //server should soon send update message to remove one region for this object. - - LL_WARNS() << "Entry: " << entry->getLocalID() << " exists in two regions at the same time." << LL_ENDL; - return NULL; - } - - LL_WARNS() << "Entry: " << entry->getLocalID() << " in rendering pipeline but not set to be active." << LL_ENDL; - - //should not hit here any more, but does not hurt either, just put it back to active list - addActiveCacheEntry(entry); - } - - return obj; -} - -//update object cache if the object receives a full-update or terse update -//update_type == EObjectUpdateType::OUT_TERSE_IMPROVED or EObjectUpdateType::OUT_FULL -LLViewerObject* LLViewerRegion::updateCacheEntry(U32 local_id, LLViewerObject* objectp) -{ - LLVOCacheEntry* entry = getCacheEntry(local_id); - if (!entry) - { - return objectp; //not in the cache, do nothing. - } - if(!objectp) //object not created - { - //create a new object from cache. - objectp = addNewObject(entry); - } - - //remove from cache. - killCacheEntry(entry, true); - - return objectp; -} - -// As above, but forcibly do the update. -void LLViewerRegion::forceUpdate() -{ - mImpl->mLandp->idleUpdate(0.f); - - if (mParcelOverlay) - { - mParcelOverlay->idleUpdate(true); - } -} - -void LLViewerRegion::connectNeighbor(LLViewerRegion *neighborp, U32 direction) -{ - mImpl->mLandp->connectNeighbor(neighborp->mImpl->mLandp, direction); -} - - -void LLViewerRegion::disconnectAllNeighbors() -{ - mImpl->mLandp->disconnectAllNeighbors(); -} - -LLVLComposition * LLViewerRegion::getComposition() const -{ - return mImpl->mCompositionp; -} - -F32 LLViewerRegion::getCompositionXY(const S32 x, const S32 y) const -{ - if (x >= 256) - { - if (y >= 256) - { - LLVector3d center = getCenterGlobal() + LLVector3d(256.f, 256.f, 0.f); - LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromPosGlobal(center); - if (regionp) - { - // OK, we need to do some hackery here - different simulators no longer use - // the same composition values, necessarily. - // If we're attempting to blend, then we want to make the fractional part of - // this region match the fractional of the adjacent. For now, just minimize - // the delta. - F32 our_comp = getComposition()->getValueScaled(255, 255); - F32 adj_comp = regionp->getComposition()->getValueScaled(x - 256.f, y - 256.f); - while (llabs(our_comp - adj_comp) >= 1.f) - { - if (our_comp > adj_comp) - { - adj_comp += 1.f; - } - else - { - adj_comp -= 1.f; - } - } - return adj_comp; - } - } - else - { - LLVector3d center = getCenterGlobal() + LLVector3d(256.f, 0, 0.f); - LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromPosGlobal(center); - if (regionp) - { - // OK, we need to do some hackery here - different simulators no longer use - // the same composition values, necessarily. - // If we're attempting to blend, then we want to make the fractional part of - // this region match the fractional of the adjacent. For now, just minimize - // the delta. - F32 our_comp = getComposition()->getValueScaled(255.f, (F32)y); - F32 adj_comp = regionp->getComposition()->getValueScaled(x - 256.f, (F32)y); - while (llabs(our_comp - adj_comp) >= 1.f) - { - if (our_comp > adj_comp) - { - adj_comp += 1.f; - } - else - { - adj_comp -= 1.f; - } - } - return adj_comp; - } - } - } - else if (y >= 256) - { - LLVector3d center = getCenterGlobal() + LLVector3d(0.f, 256.f, 0.f); - LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromPosGlobal(center); - if (regionp) - { - // OK, we need to do some hackery here - different simulators no longer use - // the same composition values, necessarily. - // If we're attempting to blend, then we want to make the fractional part of - // this region match the fractional of the adjacent. For now, just minimize - // the delta. - F32 our_comp = getComposition()->getValueScaled((F32)x, 255.f); - F32 adj_comp = regionp->getComposition()->getValueScaled((F32)x, y - 256.f); - while (llabs(our_comp - adj_comp) >= 1.f) - { - if (our_comp > adj_comp) - { - adj_comp += 1.f; - } - else - { - adj_comp -= 1.f; - } - } - return adj_comp; - } - } - - return getComposition()->getValueScaled((F32)x, (F32)y); -} - -void LLViewerRegion::calculateCenterGlobal() -{ - mImpl->mCenterGlobal = mImpl->mOriginGlobal; - mImpl->mCenterGlobal.mdV[VX] += 0.5 * mWidth; - mImpl->mCenterGlobal.mdV[VY] += 0.5 * mWidth; - mImpl->mCenterGlobal.mdV[VZ] = 0.5 * mImpl->mLandp->getMinZ() + mImpl->mLandp->getMaxZ(); -} - -void LLViewerRegion::calculateCameraDistance() -{ - mCameraDistanceSquared = (F32)(gAgentCamera.getCameraPositionGlobal() - getCenterGlobal()).magVecSquared(); -} - -std::ostream& operator<<(std::ostream &s, const LLViewerRegion ®ion) -{ - s << "{ "; - s << region.mImpl->mHost; - s << " mOriginGlobal = " << region.getOriginGlobal()<< "\n"; - std::string name(region.getName()), zone(region.getZoning()); - if (! name.empty()) - { - s << " mName = " << name << '\n'; - } - if (! zone.empty()) - { - s << " mZoning = " << zone << '\n'; - } - s << "}"; - return s; -} - - -// ---------------- Protected Member Functions ---------------- - -void LLViewerRegion::updateNetStats() -{ - F32 dt = mImpl->mLastNetUpdate.getElapsedTimeAndResetF32(); - - LLCircuitData *cdp = gMessageSystem->mCircuitInfo.findCircuit(mImpl->mHost); - if (!cdp) - { - mAlive = false; - return; - } - - mAlive = true; - mDeltaTime = dt; - - mLastPacketsIn = mPacketsIn; - mLastBitsIn = mBitsIn; - mLastPacketsOut = mPacketsOut; - mLastPacketsLost = mPacketsLost; - - mPacketsIn = cdp->getPacketsIn(); - mBitsIn = 8 * cdp->getBytesIn(); - mPacketsOut = cdp->getPacketsOut(); - mPacketsLost = cdp->getPacketsLost(); - mPingDelay = cdp->getPingDelay(); - - mBitsReceived += mBitsIn - mLastBitsIn; - mPacketsReceived += mPacketsIn - mLastPacketsIn; -} - - -U32 LLViewerRegion::getPacketsLost() const -{ - LLCircuitData *cdp = gMessageSystem->mCircuitInfo.findCircuit(mImpl->mHost); - if (!cdp) - { - LL_INFOS() << "LLViewerRegion::getPacketsLost couldn't find circuit for " << mImpl->mHost << LL_ENDL; - return 0; - } - else - { - return cdp->getPacketsLost(); - } -} - -S32 LLViewerRegion::getHttpResponderID() const -{ - return mImpl->mHttpResponderID; -} - -bool LLViewerRegion::pointInRegionGlobal(const LLVector3d &point_global) const -{ - LLVector3 pos_region = getPosRegionFromGlobal(point_global); - - if (pos_region.mV[VX] < 0) - { - return false; - } - if (pos_region.mV[VX] >= mWidth) - { - return false; - } - if (pos_region.mV[VY] < 0) - { - return false; - } - if (pos_region.mV[VY] >= mWidth) - { - return false; - } - return true; -} - -LLVector3 LLViewerRegion::getPosRegionFromGlobal(const LLVector3d &point_global) const -{ - LLVector3 pos_region; - pos_region.setVec(point_global - mImpl->mOriginGlobal); - return pos_region; -} - -LLVector3d LLViewerRegion::getPosGlobalFromRegion(const LLVector3 &pos_region) const -{ - LLVector3d pos_region_d; - pos_region_d.setVec(pos_region); - return pos_region_d + mImpl->mOriginGlobal; -} - -LLVector3 LLViewerRegion::getPosAgentFromRegion(const LLVector3 &pos_region) const -{ - LLVector3d pos_global = getPosGlobalFromRegion(pos_region); - - return gAgent.getPosAgentFromGlobal(pos_global); -} - -LLVector3 LLViewerRegion::getPosRegionFromAgent(const LLVector3 &pos_agent) const -{ - return pos_agent - getOriginAgent(); -} - -F32 LLViewerRegion::getLandHeightRegion(const LLVector3& region_pos) -{ - return mImpl->mLandp->resolveHeightRegion( region_pos ); -} - -bool LLViewerRegion::isAlive() -{ - return mAlive; -} - -bool LLViewerRegion::isOwnedSelf(const LLVector3& pos) -{ - if (mParcelOverlay) - { - return mParcelOverlay->isOwnedSelf(pos); - } else { - return false; - } -} - -// Owned by a group you belong to? (officer or member) -bool LLViewerRegion::isOwnedGroup(const LLVector3& pos) -{ - if (mParcelOverlay) - { - return mParcelOverlay->isOwnedGroup(pos); - } else { - return false; - } -} - -// the new TCP coarse location handler node -class CoarseLocationUpdate : public LLHTTPNode -{ -public: - virtual void post( - ResponsePtr responder, - const LLSD& context, - const LLSD& input) const - { - LLHost host(input["sender"].asString()); - - LLWorld *world_inst = LLWorld::getInstance(); // Not a singleton! - if (!world_inst) - { - return; - } - - LLViewerRegion* region = world_inst->getRegion(host); - if( !region ) - { - return; - } - - S32 target_index = input["body"]["Index"][0]["Prey"].asInteger(); - S32 you_index = input["body"]["Index"][0]["You" ].asInteger(); - - std::vector* avatar_locs = ®ion->mMapAvatars; - std::vector* avatar_ids = ®ion->mMapAvatarIDs; - avatar_locs->clear(); - avatar_ids->clear(); - - //LL_INFOS() << "coarse locations agent[0] " << input["body"]["AgentData"][0]["AgentID"].asUUID() << LL_ENDL; - //LL_INFOS() << "my agent id = " << gAgent.getID() << LL_ENDL; - //LL_INFOS() << ll_pretty_print_sd(input) << LL_ENDL; - - LLSD - locs = input["body"]["Location"], - agents = input["body"]["AgentData"]; - LLSD::array_iterator - locs_it = locs.beginArray(), - agents_it = agents.beginArray(); - bool has_agent_data = input["body"].has("AgentData"); - - for(int i=0; - locs_it != locs.endArray(); - i++, locs_it++) - { - U8 - x = locs_it->get("X").asInteger(), - y = locs_it->get("Y").asInteger(), - z = locs_it->get("Z").asInteger(); - // treat the target specially for the map, and don't add you or the target - if(i == target_index) - { - LLVector3d global_pos(region->getOriginGlobal()); - global_pos.mdV[VX] += (F64)x; - global_pos.mdV[VY] += (F64)y; - global_pos.mdV[VZ] += (F64)z * 4.0; - LLAvatarTracker::instance().setTrackedCoarseLocation(global_pos); - } - else if( i != you_index) - { - U32 pos = 0x0; - pos |= x; - pos <<= 8; - pos |= y; - pos <<= 8; - pos |= z; - avatar_locs->push_back(pos); - //LL_INFOS() << "next pos: " << x << "," << y << "," << z << ": " << pos << LL_ENDL; - if(has_agent_data) // for backwards compatibility with old message format - { - LLUUID agent_id(agents_it->get("AgentID").asUUID()); - //LL_INFOS() << "next agent: " << agent_id.asString() << LL_ENDL; - avatar_ids->push_back(agent_id); - } - } - if (has_agent_data) - { - agents_it++; - } - } - } -}; - -// build the coarse location HTTP node under the "/message" URL -LLHTTPRegistration - gHTTPRegistrationCoarseLocationUpdate( - "/message/CoarseLocationUpdate"); - - -// the deprecated coarse location handler -void LLViewerRegion::updateCoarseLocations(LLMessageSystem* msg) -{ - //LL_INFOS() << "CoarseLocationUpdate" << LL_ENDL; - mMapAvatars.clear(); - mMapAvatarIDs.clear(); // only matters in a rare case but it's good to be safe. - - U8 x_pos = 0; - U8 y_pos = 0; - U8 z_pos = 0; - - U32 pos = 0x0; - - S16 agent_index; - S16 target_index; - msg->getS16Fast(_PREHASH_Index, _PREHASH_You, agent_index); - msg->getS16Fast(_PREHASH_Index, _PREHASH_Prey, target_index); - - bool has_agent_data = msg->has(_PREHASH_AgentData); - S32 count = msg->getNumberOfBlocksFast(_PREHASH_Location); - for(S32 i = 0; i < count; i++) - { - msg->getU8Fast(_PREHASH_Location, _PREHASH_X, x_pos, i); - msg->getU8Fast(_PREHASH_Location, _PREHASH_Y, y_pos, i); - msg->getU8Fast(_PREHASH_Location, _PREHASH_Z, z_pos, i); - LLUUID agent_id = LLUUID::null; - if(has_agent_data) - { - msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id, i); - } - - //LL_INFOS() << " object X: " << (S32)x_pos << " Y: " << (S32)y_pos - // << " Z: " << (S32)(z_pos * 4) - // << LL_ENDL; - - // treat the target specially for the map - if(i == target_index) - { - LLVector3d global_pos(mImpl->mOriginGlobal); - global_pos.mdV[VX] += (F64)(x_pos); - global_pos.mdV[VY] += (F64)(y_pos); - global_pos.mdV[VZ] += (F64)(z_pos) * 4.0; - LLAvatarTracker::instance().setTrackedCoarseLocation(global_pos); - } - - //don't add you - if( i != agent_index) - { - pos = 0x0; - pos |= x_pos; - pos <<= 8; - pos |= y_pos; - pos <<= 8; - pos |= z_pos; - mMapAvatars.push_back(pos); - if(has_agent_data) - { - mMapAvatarIDs.push_back(agent_id); - } - } - } -} - -void LLViewerRegion::getInfo(LLSD& info) -{ - info["Region"]["Host"] = getHost().getIPandPort(); - info["Region"]["Name"] = getName(); - U32 x, y; - from_region_handle(getHandle(), &x, &y); - info["Region"]["Handle"]["x"] = (LLSD::Integer)x; - info["Region"]["Handle"]["y"] = (LLSD::Integer)y; -} - -void LLViewerRegion::requestSimulatorFeatures() -{ - LL_DEBUGS("SimulatorFeatures") << "region " << getName() << " ptr " << this - << " trying to request SimulatorFeatures" << LL_ENDL; - // kick off a request for simulator features - std::string url = getCapability("SimulatorFeatures"); - if (!url.empty()) - { - std::string coroname = - LLCoros::instance().launch("LLViewerRegionImpl::requestSimulatorFeatureCoro", - boost::bind(&LLViewerRegionImpl::requestSimulatorFeatureCoro, url, getHandle())); - - // requestSimulatorFeatures can be called from other coros, - // launch() acts like a suspend() - // Make sure we are still good to do - LLCoros::checkStop(); - - LL_INFOS("AppInit", "SimulatorFeatures") << "Launching " << coroname << " requesting simulator features from " << url << " for region " << getRegionID() << LL_ENDL; - } - else - { - LL_WARNS("AppInit", "SimulatorFeatures") << "SimulatorFeatures cap not set" << LL_ENDL; - } -} - -boost::signals2::connection LLViewerRegion::setSimulatorFeaturesReceivedCallback(const caps_received_signal_t::slot_type& cb) -{ - return mSimulatorFeaturesReceivedSignal.connect(cb); -} - -void LLViewerRegion::setSimulatorFeaturesReceived(bool received) -{ - mSimulatorFeaturesReceived = received; - if (received) - { - mSimulatorFeaturesReceivedSignal(getRegionID(), this); - mSimulatorFeaturesReceivedSignal.disconnect_all_slots(); - } -} - -bool LLViewerRegion::simulatorFeaturesReceived() const -{ - return mSimulatorFeaturesReceived; -} - -void LLViewerRegion::getSimulatorFeatures(LLSD& sim_features) const -{ - sim_features = mSimulatorFeatures; - -} - -void LLViewerRegion::setSimulatorFeatures(const LLSD& sim_features) -{ - std::stringstream str; - - LLSDSerialize::toPrettyXML(sim_features, str); - LL_INFOS() << "region " << getName() << " " << str.str() << LL_ENDL; - mSimulatorFeatures = sim_features; - - setSimulatorFeaturesReceived(true); - -} - -//this is called when the parent is not cacheable. -//move all orphan children out of cache and insert to rendering octree. -void LLViewerRegion::findOrphans(U32 parent_id) -{ - orphan_list_t::iterator iter = mOrphanMap.find(parent_id); - if(iter != mOrphanMap.end()) - { - std::vector* children = &mOrphanMap[parent_id]; - for(S32 i = 0; i < children->size(); i++) - { - //parent is visible, so is the child. - addVisibleChildCacheEntry(NULL, getCacheEntry((*children)[i])); - } - children->clear(); - mOrphanMap.erase(parent_id); - } -} - -void LLViewerRegion::decodeBoundingInfo(LLVOCacheEntry* entry) -{ - if(!sVOCacheCullingEnabled) - { - gObjectList.processObjectUpdateFromCache(entry, this); - return; - } - if(!entry || !entry->isValid()) - { - return; - } - - if(!entry->getEntry()) - { - entry->setOctreeEntry(NULL); - } - - if(entry->getEntry()->hasDrawable()) //already in the rendering pipeline - { - LLViewerRegion* old_regionp = ((LLDrawable*)entry->getEntry()->getDrawable())->getRegion(); - if(old_regionp != this && old_regionp) - { - LLViewerObject* obj = ((LLDrawable*)entry->getEntry()->getDrawable())->getVObj(); - if(obj) - { - //remove from old region - old_regionp->killCacheEntry(obj->getLocalID()); - - //change region - obj->setRegion(this); - } - } - - addActiveCacheEntry(entry); - - //set parent id - U32 parent_id = 0; - if (entry->getDP()) // NULL if nothing cached - { - LLViewerObject::unpackParentID(entry->getDP(), parent_id); - } - if(parent_id != entry->getParentID()) - { - entry->setParentID(parent_id); - } - - //update the object - gObjectList.processObjectUpdateFromCache(entry, this); - return; //done - } - - //must not be active. - llassert_always(!entry->isState(LLVOCacheEntry::ACTIVE)); - removeFromVOCacheTree(entry); //remove from cache octree if it is in. - - LLVector3 pos; - LLVector3 scale; - LLQuaternion rot; - - //decode spatial info and parent info - U32 parent_id = entry->getDP() ? LLViewerObject::extractSpatialExtents(entry->getDP(), pos, scale, rot) : entry->getParentID(); - - U32 old_parent_id = entry->getParentID(); - bool same_old_parent = false; - if(parent_id != old_parent_id) //parent changed. - { - if(old_parent_id > 0) //has an old parent, disconnect it - { - LLVOCacheEntry* old_parent = getCacheEntry(old_parent_id); - if(old_parent) - { - old_parent->removeChild(entry); - if(!old_parent->isState(LLVOCacheEntry::INACTIVE)) - { - mImpl->mVisibleEntries.erase(entry); - entry->setState(LLVOCacheEntry::INACTIVE); - } - } - } - entry->setParentID(parent_id); - } - else - { - same_old_parent = true; - } - - if(parent_id > 0) //has a new parent - { - //1, find the parent in cache - LLVOCacheEntry* parent = getCacheEntry(parent_id); - - //2, parent is not in the cache, put into the orphan list. - if(!parent) - { - if(!same_old_parent) - { - //check if parent is non-cacheable and already created - if(isNonCacheableObjectCreated(parent_id)) - { - //parent is visible, so is the child. - addVisibleChildCacheEntry(NULL, entry); - } - else - { - entry->setBoundingInfo(pos, scale); - mOrphanMap[parent_id].push_back(entry->getLocalID()); - } - } - else - { - entry->setBoundingInfo(pos, scale); - } - } - else //parent in cache. - { - if(!parent->isState(LLVOCacheEntry::INACTIVE)) - { - //parent is visible, so is the child. - addVisibleChildCacheEntry(parent, entry); - } - else - { - entry->setBoundingInfo(pos, scale); - parent->addChild(entry); - - if(parent->getGroup()) //re-insert parent to vo-cache tree because its bounding info changed. - { - removeFromVOCacheTree(parent); - addToVOCacheTree(parent); - } - } - } - - return; - } - - // - //no parent - // - entry->setBoundingInfo(pos, scale); - - if(!parent_id) //a potential parent - { - //find all children and update their bounding info - orphan_list_t::iterator iter = mOrphanMap.find(entry->getLocalID()); - if(iter != mOrphanMap.end()) - { - std::vector* orphans = &mOrphanMap[entry->getLocalID()]; - S32 size = orphans->size(); - for(S32 i = 0; i < size; i++) - { - LLVOCacheEntry* child = getCacheEntry((*orphans)[i]); - if(child) - { - entry->addChild(child); - } - } - orphans->clear(); - mOrphanMap.erase(entry->getLocalID()); - } - } - - if(!entry->getGroup() && entry->isState(LLVOCacheEntry::INACTIVE)) - { - addToVOCacheTree(entry); - } - return ; -} - -LLViewerRegion::eCacheUpdateResult LLViewerRegion::cacheFullUpdate(LLDataPackerBinaryBuffer &dp, U32 flags) -{ - eCacheUpdateResult result; - U32 crc; - U32 local_id; - - LLViewerObject::unpackU32(&dp, local_id, "LocalID"); - LLViewerObject::unpackU32(&dp, crc, "CRC"); - - LLVOCacheEntry* entry = getCacheEntry(local_id, false); - - if (entry) - { - entry->setValid(); - - // we've seen this object before - if (entry->getCRC() == crc) - { - LL_DEBUGS("AnimatedObjects") << " got dupe for local_id " << local_id << LL_ENDL; - dumpStack("AnimatedObjectsStack"); - - // Record a hit - entry->recordDupe(); - result = CACHE_UPDATE_DUPE; - } - else //CRC changed - { - LL_DEBUGS("AnimatedObjects") << " got update for local_id " << local_id << LL_ENDL; - dumpStack("AnimatedObjectsStack"); - - // Update the cache entry - entry->updateEntry(crc, dp); - - decodeBoundingInfo(entry); - - result = CACHE_UPDATE_CHANGED; - } - } - else - { - LL_DEBUGS("AnimatedObjects") << " got first notification for local_id " << local_id << LL_ENDL; - dumpStack("AnimatedObjectsStack"); - - // we haven't seen this object before - // Create new entry and add to map - result = CACHE_UPDATE_ADDED; - entry = new LLVOCacheEntry(local_id, crc, dp); - record(LLStatViewer::OBJECT_CACHE_HIT_RATE, LLUnits::Ratio::fromValue(0)); - - mImpl->mCacheMap[local_id] = entry; - - decodeBoundingInfo(entry); - } - entry->setUpdateFlags(flags); - - return result; - } - -LLViewerRegion::eCacheUpdateResult LLViewerRegion::cacheFullUpdate(LLViewerObject* objectp, LLDataPackerBinaryBuffer &dp, U32 flags) -{ - eCacheUpdateResult result = cacheFullUpdate(dp, flags); - - return result; -} - -void LLViewerRegion::cacheFullUpdateGLTFOverride(const LLGLTFOverrideCacheEntry &override_data) -{ - U32 local_id = override_data.mLocalId; - mImpl->mGLTFOverridesLLSD[local_id] = override_data; -} - -LLVOCacheEntry* LLViewerRegion::getCacheEntryForOctree(U32 local_id) -{ - if(!sVOCacheCullingEnabled) - { - return NULL; - } - - LLVOCacheEntry* entry = getCacheEntry(local_id); - removeFromVOCacheTree(entry); - - return entry; -} - -LLVOCacheEntry* LLViewerRegion::getCacheEntry(U32 local_id, bool valid) -{ - LLVOCacheEntry::vocache_entry_map_t::iterator iter = mImpl->mCacheMap.find(local_id); - if(iter != mImpl->mCacheMap.end()) - { - if(!valid || iter->second->isValid()) - { - return iter->second; - } - } - return NULL; -} - -void LLViewerRegion::addCacheMiss(U32 id, LLViewerRegion::eCacheMissType cache_miss_type) -{ - mRegionCacheMissCount++; - mCacheMissList.push_back(CacheMissItem(id, cache_miss_type)); -} - -//check if a non-cacheable object is already created. -bool LLViewerRegion::isNonCacheableObjectCreated(U32 local_id) -{ - if(mImpl && local_id > 0 && mImpl->mNonCacheableCreatedList.find(local_id) != mImpl->mNonCacheableCreatedList.end()) - { - return true; - } - return false; -} - -void LLViewerRegion::removeFromCreatedList(U32 local_id) -{ - if(mImpl && local_id > 0) - { - std::set::iterator iter = mImpl->mNonCacheableCreatedList.find(local_id); - if(iter != mImpl->mNonCacheableCreatedList.end()) - { - mImpl->mNonCacheableCreatedList.erase(iter); - } - } - } - -void LLViewerRegion::addToCreatedList(U32 local_id) -{ - if(mImpl && local_id > 0) - { - mImpl->mNonCacheableCreatedList.insert(local_id); - } -} - -// Get data packer for this object, if we have cached data -// AND the CRC matches. JC -bool LLViewerRegion::probeCache(U32 local_id, U32 crc, U32 flags, U8 &cache_miss_type) -{ - //llassert(mCacheLoaded); This assert failes often, changing to early-out -- davep, 2010/10/18 - - LLVOCacheEntry* entry = getCacheEntry(local_id, false); - - if (entry) - { - // we've seen this object before - if (entry->getCRC() == crc) - { - // Record a hit - mRegionCacheHitCount++; - entry->recordHit(); - cache_miss_type = CACHE_MISS_TYPE_NONE; - entry->setUpdateFlags(flags); - - if(entry->isState(LLVOCacheEntry::ACTIVE)) - { - ((LLDrawable*)entry->getEntry()->getDrawable())->getVObj()->loadFlags(flags); - return true; - } - - if(entry->isValid()) - { - return true; //already probed - } - - entry->setValid(); - decodeBoundingInfo(entry); - - //loadCacheMiscExtras(local_id, entry, crc); - - return true; - } - else - { - // LL_INFOS() << "CRC miss for " << local_id << LL_ENDL; - - addCacheMiss(local_id, CACHE_MISS_TYPE_CRC); - cache_miss_type = CACHE_MISS_TYPE_CRC; - } - } - else - { // Total miss, don't have the object in cache - // LL_INFOS() << "Cache miss for " << local_id << LL_ENDL; - addCacheMiss(local_id, CACHE_MISS_TYPE_TOTAL); - cache_miss_type = CACHE_MISS_TYPE_TOTAL; - } - - return false; -} - -void LLViewerRegion::addCacheMissFull(const U32 local_id) -{ - addCacheMiss(local_id, CACHE_MISS_TYPE_TOTAL); -} - -void LLViewerRegion::requestCacheMisses() -{ - if (!mCacheMissList.size()) - { - return; - } - - LLMessageSystem* msg = gMessageSystem; - bool start_new_message = true; - S32 blocks = 0; - - //send requests for all cache-missed objects - for (CacheMissItem::cache_miss_list_t::iterator iter = mCacheMissList.begin(); iter != mCacheMissList.end(); ++iter) - { - if (start_new_message) - { - msg->newMessageFast(_PREHASH_RequestMultipleObjects); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - start_new_message = false; - } - - msg->nextBlockFast(_PREHASH_ObjectData); - msg->addU8Fast(_PREHASH_CacheMissType, (*iter).mType); - msg->addU32Fast(_PREHASH_ID, (*iter).mID); - - LL_DEBUGS("AnimatedObjects") << "Requesting cache missed object " << (*iter).mID << LL_ENDL; - - blocks++; - - if (blocks >= 255) - { - sendReliableMessage(); - start_new_message = true; - blocks = 0; - } - } - - // finish any pending message - if (!start_new_message) - { - sendReliableMessage(); - } - - mCacheDirty = true ; - // LL_INFOS() << "KILLDEBUG Sent cache miss full " << full_count << " crc " << crc_count << LL_ENDL; - LLViewerStatsRecorder::instance().requestCacheMissesEvent(mCacheMissList.size()); - - mCacheMissList.clear(); -} - -void LLViewerRegion::dumpCache() -{ - const S32 BINS = 4; - S32 hit_bin[BINS]; - S32 change_bin[BINS]; - - S32 i; - for (i = 0; i < BINS; ++i) - { - hit_bin[i] = 0; - change_bin[i] = 0; - } - - LLVOCacheEntry *entry; - for(LLVOCacheEntry::vocache_entry_map_t::iterator iter = mImpl->mCacheMap.begin(); iter != mImpl->mCacheMap.end(); ++iter) - { - entry = iter->second ; - - S32 hits = entry->getHitCount(); - S32 changes = entry->getCRCChangeCount(); - - hits = llclamp(hits, 0, BINS-1); - changes = llclamp(changes, 0, BINS-1); - - hit_bin[hits]++; - change_bin[changes]++; - } - - LL_INFOS() << "Count " << mImpl->mCacheMap.size() << LL_ENDL; - for (i = 0; i < BINS; i++) - { - LL_INFOS() << "Hits " << i << " " << hit_bin[i] << LL_ENDL; - } - for (i = 0; i < BINS; i++) - { - LL_INFOS() << "Changes " << i << " " << change_bin[i] << LL_ENDL; - } - // TODO - add overrides cache too -} - -void LLViewerRegion::unpackRegionHandshake() -{ - LLMessageSystem *msg = gMessageSystem; - - U64 region_flags = 0; - U64 region_protocols = 0; - U8 sim_access; - std::string sim_name; - LLUUID sim_owner; - bool is_estate_manager; - F32 water_height; - F32 billable_factor; - LLUUID cache_id; - - msg->getU8 ("RegionInfo", "SimAccess", sim_access); - msg->getString ("RegionInfo", "SimName", sim_name); - msg->getUUID ("RegionInfo", "SimOwner", sim_owner); - msg->getBOOL ("RegionInfo", "IsEstateManager", is_estate_manager); - msg->getF32 ("RegionInfo", "WaterHeight", water_height); - msg->getF32 ("RegionInfo", "BillableFactor", billable_factor); - msg->getUUID ("RegionInfo", "CacheID", cache_id ); - - if (msg->has(_PREHASH_RegionInfo4)) - { - msg->getU64Fast(_PREHASH_RegionInfo4, _PREHASH_RegionFlagsExtended, region_flags); - msg->getU64Fast(_PREHASH_RegionInfo4, _PREHASH_RegionProtocols, region_protocols); - } - else - { - U32 flags = 0; - msg->getU32Fast(_PREHASH_RegionInfo, _PREHASH_RegionFlags, flags); - region_flags = flags; - } - - setRegionFlags(region_flags); - setRegionProtocols(region_protocols); - setSimAccess(sim_access); - setRegionNameAndZone(sim_name); - setOwner(sim_owner); - setIsEstateManager(is_estate_manager); - setWaterHeight(water_height); - setBillableFactor(billable_factor); - setCacheID(cache_id); - - LLUUID region_id; - msg->getUUID("RegionInfo2", "RegionID", region_id); - setRegionID(region_id); - - // Retrieve the CR-53 (Homestead/Land SKU) information - S32 classID = 0; - S32 cpuRatio = 0; - std::string coloName; - std::string productSKU; - std::string productName; - - // the only reasonable way to decide if we actually have any data is to - // check to see if any of these fields have positive sizes - if (msg->getSize("RegionInfo3", "ColoName") > 0 || - msg->getSize("RegionInfo3", "ProductSKU") > 0 || - msg->getSize("RegionInfo3", "ProductName") > 0) - { - msg->getS32 ("RegionInfo3", "CPUClassID", classID); - msg->getS32 ("RegionInfo3", "CPURatio", cpuRatio); - msg->getString ("RegionInfo3", "ColoName", coloName); - msg->getString ("RegionInfo3", "ProductSKU", productSKU); - msg->getString ("RegionInfo3", "ProductName", productName); - - mClassID = classID; - mCPURatio = cpuRatio; - mColoName = coloName; - mProductSKU = productSKU; - mProductName = productName; - } - - mCentralBakeVersion = region_protocols & 1; // was (S32)gSavedSettings.getBOOL("UseServerTextureBaking"); - LLVLComposition *compp = getComposition(); - if (compp) - { - LLUUID tmp_id; - - bool changed = false; - - // Get the 4 textures for land - msg->getUUID("RegionInfo", "TerrainDetail0", tmp_id); - changed |= (tmp_id != compp->getDetailTextureID(0)); - compp->setDetailTextureID(0, tmp_id); - - msg->getUUID("RegionInfo", "TerrainDetail1", tmp_id); - changed |= (tmp_id != compp->getDetailTextureID(1)); - compp->setDetailTextureID(1, tmp_id); - - msg->getUUID("RegionInfo", "TerrainDetail2", tmp_id); - changed |= (tmp_id != compp->getDetailTextureID(2)); - compp->setDetailTextureID(2, tmp_id); - - msg->getUUID("RegionInfo", "TerrainDetail3", tmp_id); - changed |= (tmp_id != compp->getDetailTextureID(3)); - compp->setDetailTextureID(3, tmp_id); - - // Get the start altitude and range values for land textures - F32 tmp_f32; - msg->getF32("RegionInfo", "TerrainStartHeight00", tmp_f32); - changed |= (tmp_f32 != compp->getStartHeight(0)); - compp->setStartHeight(0, tmp_f32); - - msg->getF32("RegionInfo", "TerrainStartHeight01", tmp_f32); - changed |= (tmp_f32 != compp->getStartHeight(1)); - compp->setStartHeight(1, tmp_f32); - - msg->getF32("RegionInfo", "TerrainStartHeight10", tmp_f32); - changed |= (tmp_f32 != compp->getStartHeight(2)); - compp->setStartHeight(2, tmp_f32); - - msg->getF32("RegionInfo", "TerrainStartHeight11", tmp_f32); - changed |= (tmp_f32 != compp->getStartHeight(3)); - compp->setStartHeight(3, tmp_f32); - - - msg->getF32("RegionInfo", "TerrainHeightRange00", tmp_f32); - changed |= (tmp_f32 != compp->getHeightRange(0)); - compp->setHeightRange(0, tmp_f32); - - msg->getF32("RegionInfo", "TerrainHeightRange01", tmp_f32); - changed |= (tmp_f32 != compp->getHeightRange(1)); - compp->setHeightRange(1, tmp_f32); - - msg->getF32("RegionInfo", "TerrainHeightRange10", tmp_f32); - changed |= (tmp_f32 != compp->getHeightRange(2)); - compp->setHeightRange(2, tmp_f32); - - msg->getF32("RegionInfo", "TerrainHeightRange11", tmp_f32); - changed |= (tmp_f32 != compp->getHeightRange(3)); - compp->setHeightRange(3, tmp_f32); - - // If this is an UPDATE (params already ready, we need to regenerate - // all of our terrain stuff, by - if (compp->getParamsReady()) - { - // Update if the land changed - if (changed) - { - getLand().dirtyAllPatches(); - } - } - else - { - compp->setParamsReady(); - } - } - - - // Now that we have the name, we can load the cache file - // off disk. - loadObjectCache(); - - // After loading cache, signal that simulator can start - // sending data. - // TODO: Send all upstream viewer->sim handshake info here. - LLHost host = msg->getSender(); - msg->newMessage("RegionHandshakeReply"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->nextBlock("RegionInfo"); - - U32 flags = 0; - flags |= REGION_HANDSHAKE_SUPPORTS_SELF_APPEARANCE; - - if(sVOCacheCullingEnabled) - { - flags |= 0x00000001; //set the bit 0 to be 1 to ask sim to send all cacheable objects. - } - if(mImpl->mCacheMap.empty()) - { - flags |= 0x00000002; //set the bit 1 to be 1 to tell sim the cache file is empty, no need to send cache probes. - } - msg->addU32("Flags", flags ); - msg->sendReliable(host); - - mRegionTimer.reset(); //reset region timer. -} - -// static -void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames) -{ - capabilityNames.append("AbuseCategories"); - capabilityNames.append("AcceptFriendship"); - capabilityNames.append("AcceptGroupInvite"); // ReadOfflineMsgs recieved messages only!!! - capabilityNames.append("AgentPreferences"); - capabilityNames.append("AgentProfile"); - capabilityNames.append("AgentState"); - capabilityNames.append("AttachmentResources"); - capabilityNames.append("AvatarPickerSearch"); - capabilityNames.append("AvatarRenderInfo"); - capabilityNames.append("CharacterProperties"); - capabilityNames.append("ChatSessionRequest"); - capabilityNames.append("CopyInventoryFromNotecard"); - capabilityNames.append("CreateInventoryCategory"); - capabilityNames.append("DeclineFriendship"); - capabilityNames.append("DeclineGroupInvite"); // ReadOfflineMsgs recieved messages only!!! - capabilityNames.append("DispatchRegionInfo"); - capabilityNames.append("DirectDelivery"); - capabilityNames.append("EnvironmentSettings"); - capabilityNames.append("EstateAccess"); - capabilityNames.append("EstateChangeInfo"); - capabilityNames.append("EventQueueGet"); - capabilityNames.append("ExtEnvironment"); - - capabilityNames.append("FetchLib2"); - capabilityNames.append("FetchLibDescendents2"); - capabilityNames.append("FetchInventory2"); - capabilityNames.append("FetchInventoryDescendents2"); - capabilityNames.append("IncrementCOFVersion"); - AISAPI::getCapNames(capabilityNames); - - capabilityNames.append("InterestList"); - - capabilityNames.append("InventoryThumbnailUpload"); - capabilityNames.append("GetDisplayNames"); - capabilityNames.append("GetExperiences"); - capabilityNames.append("AgentExperiences"); - capabilityNames.append("FindExperienceByName"); - capabilityNames.append("GetExperienceInfo"); - capabilityNames.append("GetAdminExperiences"); - capabilityNames.append("GetCreatorExperiences"); - capabilityNames.append("ExperiencePreferences"); - capabilityNames.append("GroupExperiences"); - capabilityNames.append("UpdateExperience"); - capabilityNames.append("IsExperienceAdmin"); - capabilityNames.append("IsExperienceContributor"); - capabilityNames.append("RegionExperiences"); - capabilityNames.append("ExperienceQuery"); - capabilityNames.append("GetMetadata"); - capabilityNames.append("GetObjectCost"); - capabilityNames.append("GetObjectPhysicsData"); - capabilityNames.append("GroupAPIv1"); - capabilityNames.append("GroupMemberData"); - capabilityNames.append("GroupProposalBallot"); - capabilityNames.append("HomeLocation"); - capabilityNames.append("LandResources"); - capabilityNames.append("LSLSyntax"); - capabilityNames.append("MapLayer"); - capabilityNames.append("MapLayerGod"); - capabilityNames.append("MeshUploadFlag"); - capabilityNames.append("ModifyMaterialParams"); - capabilityNames.append("NavMeshGenerationStatus"); - capabilityNames.append("NewFileAgentInventory"); - capabilityNames.append("ObjectAnimation"); - capabilityNames.append("ObjectMedia"); - capabilityNames.append("ObjectMediaNavigate"); - capabilityNames.append("ObjectNavMeshProperties"); - capabilityNames.append("ParcelPropertiesUpdate"); - capabilityNames.append("ParcelVoiceInfoRequest"); - capabilityNames.append("ProductInfoRequest"); - capabilityNames.append("ProvisionVoiceAccountRequest"); - capabilityNames.append("ReadOfflineMsgs"); // Requires to respond reliably: AcceptFriendship, AcceptGroupInvite, DeclineFriendship, DeclineGroupInvite - capabilityNames.append("RegionObjects"); - capabilityNames.append("RemoteParcelRequest"); - capabilityNames.append("RenderMaterials"); - capabilityNames.append("RequestTextureDownload"); - capabilityNames.append("ResourceCostSelected"); - capabilityNames.append("RetrieveNavMeshSrc"); - capabilityNames.append("SearchStatRequest"); - capabilityNames.append("SearchStatTracking"); - capabilityNames.append("SendPostcard"); - capabilityNames.append("SendUserReport"); - capabilityNames.append("SendUserReportWithScreenshot"); - capabilityNames.append("ServerReleaseNotes"); - capabilityNames.append("SetDisplayName"); - capabilityNames.append("SimConsoleAsync"); - capabilityNames.append("SimulatorFeatures"); - capabilityNames.append("StartGroupProposal"); - capabilityNames.append("TerrainNavMeshProperties"); - capabilityNames.append("TextureStats"); - capabilityNames.append("UntrustedSimulatorMessage"); - capabilityNames.append("UpdateAgentInformation"); - capabilityNames.append("UpdateAgentLanguage"); - capabilityNames.append("UpdateAvatarAppearance"); - capabilityNames.append("UpdateGestureAgentInventory"); - capabilityNames.append("UpdateGestureTaskInventory"); - capabilityNames.append("UpdateNotecardAgentInventory"); - capabilityNames.append("UpdateNotecardTaskInventory"); - capabilityNames.append("UpdateScriptAgent"); - capabilityNames.append("UpdateScriptTask"); - capabilityNames.append("UpdateSettingsAgentInventory"); - capabilityNames.append("UpdateSettingsTaskInventory"); - capabilityNames.append("UploadAgentProfileImage"); - capabilityNames.append("UpdateMaterialAgentInventory"); - capabilityNames.append("UpdateMaterialTaskInventory"); - capabilityNames.append("UploadBakedTexture"); - capabilityNames.append("UserInfo"); - capabilityNames.append("ViewerAsset"); - capabilityNames.append("ViewerBenefits"); - capabilityNames.append("ViewerMetrics"); - capabilityNames.append("ViewerStartAuction"); - capabilityNames.append("ViewerStats"); - - // Please add new capabilities alphabetically to reduce - // merge conflicts. -} - -void LLViewerRegion::setSeedCapability(const std::string& url) -{ - if (getCapability("Seed") == url) - { - setCapabilityDebug("Seed", url); - LL_WARNS("CrossingCaps") << "Received duplicate seed capability for " << getRegionID() << ", posting to seed " << - url << LL_ENDL; - - //Instead of just returning we build up a second set of seed caps and compare them - //to the "original" seed cap received and determine why there is problem! - std::string coroname = - LLCoros::instance().launch("LLEnvironmentRequest::requestBaseCapabilitiesCompleteCoro", - boost::bind(&LLViewerRegionImpl::requestBaseCapabilitiesCompleteCoro, getHandle())); - - // setSeedCapability can be called from other coros, - // launch() acts like a suspend() - // Make sure we are still good to do - LLCoros::checkStop(); - - return; - } - - delete mImpl->mEventPoll; - mImpl->mEventPoll = NULL; - - mImpl->mCapabilities.clear(); - setCapability("Seed", url); - - std::string coroname = - LLCoros::instance().launch("LLViewerRegionImpl::requestBaseCapabilitiesCoro", - boost::bind(&LLViewerRegionImpl::requestBaseCapabilitiesCoro, getHandle())); - - // setSeedCapability can be called from other coros, - // launch() acts like a suspend() - // Make sure we are still good to do - LLCoros::checkStop(); - - LL_INFOS("AppInit", "Capabilities") << "Launching " << coroname << " requesting seed capabilities from " << url << " for region " << getRegionID() << LL_ENDL; -} - -S32 LLViewerRegion::getNumSeedCapRetries() -{ - return mImpl->mSeedCapAttempts; -} - -void LLViewerRegion::setCapability(const std::string& name, const std::string& url) -{ - if(name == "EventQueueGet") - { - delete mImpl->mEventPoll; - mImpl->mEventPoll = NULL; - mImpl->mEventPoll = new LLEventPoll(url, getHost()); - } - else if(name == "UntrustedSimulatorMessage") - { - mImpl->mHost.setUntrustedSimulatorCap(url); - } - else if (name == "SimulatorFeatures") - { - mImpl->mCapabilities["SimulatorFeatures"] = url; - requestSimulatorFeatures(); - } - else - { - mImpl->mCapabilities[name] = url; - if(name == "ViewerAsset") - { - /*==============================================================*/ - // The following inserted lines are a hack for testing MAINT-7081, - // which is why the indentation and formatting are left ugly. - const char* VIEWERASSET = getenv("VIEWERASSET"); - if (VIEWERASSET) - { - mImpl->mCapabilities[name] = VIEWERASSET; - mViewerAssetUrl = VIEWERASSET; - } - else - /*==============================================================*/ - mViewerAssetUrl = url; - } - } -} - -void LLViewerRegion::setCapabilityDebug(const std::string& name, const std::string& url) -{ - // Continue to not add certain caps, as we do in setCapability. This is so they match up when we check them later. - if ( ! ( name == "EventQueueGet" || name == "UntrustedSimulatorMessage" || name == "SimulatorFeatures" ) ) - { - mImpl->mSecondCapabilitiesTracker[name] = url; - if(name == "ViewerAsset") - { - /*==============================================================*/ - // The following inserted lines are a hack for testing MAINT-7081, - // which is why the indentation and formatting are left ugly. - const char* VIEWERASSET = getenv("VIEWERASSET"); - if (VIEWERASSET) - { - mImpl->mSecondCapabilitiesTracker[name] = VIEWERASSET; - mViewerAssetUrl = VIEWERASSET; - } - else - /*==============================================================*/ - mViewerAssetUrl = url; - } - } -} - -std::string LLViewerRegion::getCapabilityDebug(const std::string& name) const -{ - CapabilityMap::const_iterator iter = mImpl->mSecondCapabilitiesTracker.find(name); - if (iter == mImpl->mSecondCapabilitiesTracker.end()) - { - return ""; - } - - return iter->second; -} - - -bool LLViewerRegion::isSpecialCapabilityName(const std::string &name) -{ - return name == "EventQueueGet" || name == "UntrustedSimulatorMessage"; -} - -std::string LLViewerRegion::getCapability(const std::string& name) const -{ - if (!capabilitiesReceived() && (name!=std::string("Seed")) && (name!=std::string("ObjectMedia"))) - { - LL_WARNS() << "getCapability called before caps received for " << name << LL_ENDL; - } - - CapabilityMap::const_iterator iter = mImpl->mCapabilities.find(name); - if(iter == mImpl->mCapabilities.end()) - { - return ""; - } - - return iter->second; -} - -bool LLViewerRegion::isCapabilityAvailable(const std::string& name) const -{ - if (!capabilitiesReceived() && (name!=std::string("Seed")) && (name!=std::string("ObjectMedia"))) - { - LL_WARNS() << "isCapabilityAvailable called before caps received for " << name << LL_ENDL; - } - - CapabilityMap::const_iterator iter = mImpl->mCapabilities.find(name); - if(iter == mImpl->mCapabilities.end()) - { - return false; - } - - return true; -} - -bool LLViewerRegion::capabilitiesReceived() const -{ - return mCapabilitiesState == CAPABILITIES_STATE_RECEIVED; -} - -bool LLViewerRegion::capabilitiesError() const -{ - return mCapabilitiesState == CAPABILITIES_STATE_ERROR; -} - -void LLViewerRegion::setCapabilitiesReceived(bool received) -{ - mCapabilitiesState = received ? CAPABILITIES_STATE_RECEIVED : CAPABILITIES_STATE_INIT; - - // Tell interested parties that we've received capabilities, - // so that they can safely use getCapability(). - if (received) - { - mCapabilitiesReceivedSignal(getRegionID(), this); - - LLFloaterPermsDefault::sendInitialPerms(); - - // This is a single-shot signal. Forget callbacks to save resources. - mCapabilitiesReceivedSignal.disconnect_all_slots(); - - // Set the region to the desired interest list mode - setInterestListMode(gAgent.getInterestListMode()); - } -} - -void LLViewerRegion::setCapabilitiesError() -{ - mCapabilitiesState = CAPABILITIES_STATE_ERROR; -} - -boost::signals2::connection LLViewerRegion::setCapabilitiesReceivedCallback(const caps_received_signal_t::slot_type& cb) -{ - return mCapabilitiesReceivedSignal.connect(cb); -} - -void LLViewerRegion::logActiveCapabilities() const -{ - log_capabilities(mImpl->mCapabilities); -} - - -bool LLViewerRegion::requestPostCapability(const std::string &capName, LLSD &postData, httpCallback_t cbSuccess, httpCallback_t cbFailure) -{ - std::string url = getCapability(capName); - - if (url.empty()) - { - LL_WARNS("Region") << "Could not retrieve region " << getRegionID() - << " POST capability \"" << capName << "\"" << LL_ENDL; - return false; - } - - LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost(url, gAgent.getAgentPolicy(), postData, cbSuccess, cbFailure); - return true; -} - -bool LLViewerRegion::requestGetCapability(const std::string &capName, httpCallback_t cbSuccess, httpCallback_t cbFailure) -{ - std::string url; - - url = getCapability(capName); - - if (url.empty()) - { - LL_WARNS("Region") << "Could not retrieve region " << getRegionID() - << " GET capability \"" << capName << "\"" << LL_ENDL; - return false; - } - - LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpGet(url, gAgent.getAgentPolicy(), cbSuccess, cbFailure); - return true; -} - -bool LLViewerRegion::requestDelCapability(const std::string &capName, httpCallback_t cbSuccess, httpCallback_t cbFailure) -{ - std::string url; - - url = getCapability(capName); - - if (url.empty()) - { - LL_WARNS("Region") << "Could not retrieve region " << getRegionID() << " DEL capability \"" << capName << "\"" << LL_ENDL; - return false; - } - - LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpDel(url, gAgent.getAgentPolicy(), cbSuccess, cbFailure); - return true; -} - -void LLViewerRegion::setInterestListMode(const std::string &new_mode) -{ - if (new_mode != mInterestListMode) - { - mInterestListMode = new_mode; - - if (mInterestListMode != IL_MODE_DEFAULT && mInterestListMode != IL_MODE_360) - { - LL_WARNS("360Capture") << "Region " << getRegionID() << " setInterestListMode() invalid interest list mode: " - << mInterestListMode << ", setting to default" << LL_ENDL; - mInterestListMode = IL_MODE_DEFAULT; - } - - LLSD body; - body["mode"] = mInterestListMode; - if (requestPostCapability("InterestList", body, - [](const LLSD &response) { - LL_DEBUGS("360Capture") << "InterestList capability responded: \n" - << ll_pretty_print_sd(response) << LL_ENDL; - })) - { - LL_DEBUGS("360Capture") << "Region " << getRegionID() - << " Successfully posted an InterestList capability request with payload: \n" - << ll_pretty_print_sd(body) << LL_ENDL; - } - else - { - LL_WARNS("360Capture") << "Region " << getRegionID() - << " Unable to post an InterestList capability request with payload: \n" - << ll_pretty_print_sd(body) << LL_ENDL; - } - } - else - { - LL_DEBUGS("360Capture") << "Region " << getRegionID() << "No change, skipping Interest List mode POST to " - << new_mode << " mode" << LL_ENDL; - } -} - - -void LLViewerRegion::resetInterestList() -{ - if (requestDelCapability("InterestList", [](const LLSD &response) { - LL_DEBUGS("360Capture") << "InterestList capability DEL responded: \n" << ll_pretty_print_sd(response) << LL_ENDL; - })) - { - LL_DEBUGS("360Capture") << "Region " << getRegionID() << " Successfully reset InterestList capability" << LL_ENDL; - } - else - { - LL_WARNS("360Capture") << "Region " << getRegionID() << " Unable to DEL InterestList capability request" << LL_ENDL; - } -} - - -LLSpatialPartition *LLViewerRegion::getSpatialPartition(U32 type) -{ - if (type < mImpl->mObjectPartition.size() && type < PARTITION_VO_CACHE) - { - return (LLSpatialPartition*)mImpl->mObjectPartition[type]; - } - return NULL; -} - -LLVOCachePartition* LLViewerRegion::getVOCachePartition() -{ - if(PARTITION_VO_CACHE < mImpl->mObjectPartition.size()) - { - return (LLVOCachePartition*)mImpl->mObjectPartition[PARTITION_VO_CACHE]; - } - return NULL; -} - -// the viewer can not yet distinquish between normal- and estate-owned objects -// so we collapse these two bits and enable the UI if either are set -const U64 ALLOW_RETURN_ENCROACHING_OBJECT = REGION_FLAGS_ALLOW_RETURN_ENCROACHING_OBJECT - | REGION_FLAGS_ALLOW_RETURN_ENCROACHING_ESTATE_OBJECT; - -bool LLViewerRegion::objectIsReturnable(const LLVector3& pos, const std::vector& boxes) const -{ - return (mParcelOverlay != NULL) - && (mParcelOverlay->isOwnedSelf(pos) - || mParcelOverlay->isOwnedGroup(pos) - || (getRegionFlag(ALLOW_RETURN_ENCROACHING_OBJECT) - && mParcelOverlay->encroachesOwned(boxes)) ); -} - -bool LLViewerRegion::childrenObjectReturnable( const std::vector& boxes ) const -{ - bool result = false; - result = ( mParcelOverlay && mParcelOverlay->encroachesOnUnowned( boxes ) ) ? 1 : 0; - return result; -} - -bool LLViewerRegion::objectsCrossParcel(const std::vector& boxes) const -{ - return mParcelOverlay && mParcelOverlay->encroachesOnNearbyParcel(boxes); -} - -void LLViewerRegion::getNeighboringRegions( std::vector& uniqueRegions ) -{ - mImpl->mLandp->getNeighboringRegions( uniqueRegions ); -} -void LLViewerRegion::getNeighboringRegionsStatus( std::vector& regions ) -{ - mImpl->mLandp->getNeighboringRegionsStatus( regions ); -} -void LLViewerRegion::showReleaseNotes() -{ - std::string url = this->getCapability("ServerReleaseNotes"); - - if (url.empty()) { - // HACK haven't received the capability yet, we'll wait until - // it arives. - mReleaseNotesRequested = true; - return; - } - - LLWeb::loadURL(url); - mReleaseNotesRequested = false; -} - -std::string LLViewerRegion::getDescription() const -{ - return stringize(*this); -} - -bool LLViewerRegion::meshUploadEnabled() const -{ - return (mSimulatorFeatures.has("MeshUploadEnabled") && - mSimulatorFeatures["MeshUploadEnabled"].asBoolean()); -} - -bool LLViewerRegion::bakesOnMeshEnabled() const -{ - return (mSimulatorFeatures.has("BakesOnMeshEnabled") && - mSimulatorFeatures["BakesOnMeshEnabled"].asBoolean()); -} - -bool LLViewerRegion::meshRezEnabled() const -{ - return (mSimulatorFeatures.has("MeshRezEnabled") && - mSimulatorFeatures["MeshRezEnabled"].asBoolean()); -} - -bool LLViewerRegion::dynamicPathfindingEnabled() const -{ - return ( mSimulatorFeatures.has("DynamicPathfindingEnabled") && - mSimulatorFeatures["DynamicPathfindingEnabled"].asBoolean()); -} - -bool LLViewerRegion::avatarHoverHeightEnabled() const -{ - return ( mSimulatorFeatures.has("AvatarHoverHeightEnabled") && - mSimulatorFeatures["AvatarHoverHeightEnabled"].asBoolean()); -} -/* Static Functions */ - -void log_capabilities(const CapabilityMap &capmap) -{ - S32 count = 0; - CapabilityMap::const_iterator iter; - for (iter = capmap.begin(); iter != capmap.end(); ++iter, ++count) - { - if (!iter->second.empty()) - { - LL_INFOS() << "log_capabilities: " << iter->first << " URL is " << iter->second << LL_ENDL; - } - } - LL_INFOS() << "log_capabilities: Dumped " << count << " entries." << LL_ENDL; -} -void LLViewerRegion::resetMaterialsCapThrottle() -{ - F32 requests_per_sec = 1.0f; // original default; - if ( mSimulatorFeatures.has("RenderMaterialsCapability") - && mSimulatorFeatures["RenderMaterialsCapability"].isReal() ) - { - requests_per_sec = mSimulatorFeatures["RenderMaterialsCapability"].asReal(); - if ( requests_per_sec == 0.0f ) - { - requests_per_sec = 1.0f; - LL_WARNS("Materials") - << "region '" << getName() - << "' returned zero for RenderMaterialsCapability; using default " - << requests_per_sec << " per second" - << LL_ENDL; - } - LL_DEBUGS("Materials") << "region '" << getName() - << "' RenderMaterialsCapability " << requests_per_sec - << LL_ENDL; - } - else - { - LL_DEBUGS("Materials") - << "region '" << getName() - << "' did not return RenderMaterialsCapability, using default " - << requests_per_sec << " per second" - << LL_ENDL; - } - - mMaterialsCapThrottleTimer.resetWithExpiry( 1.0f / requests_per_sec ); -} - -U32 LLViewerRegion::getMaxMaterialsPerTransaction() const -{ - U32 max_entries = 50; // original hard coded default - if ( mSimulatorFeatures.has( "MaxMaterialsPerTransaction" ) - && mSimulatorFeatures[ "MaxMaterialsPerTransaction" ].isInteger()) - { - max_entries = mSimulatorFeatures[ "MaxMaterialsPerTransaction" ].asInteger(); - } - return max_entries; -} - -std::string LLViewerRegion::getSimHostName() -{ - if (mSimulatorFeaturesReceived) - { - return mSimulatorFeatures.has("HostName") ? mSimulatorFeatures["HostName"].asString() : getHost().getHostName(); - } - return std::string("..."); -} - -void LLViewerRegion::applyCacheMiscExtras(LLViewerObject* obj) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY; - llassert(obj); - - U32 local_id = obj->getLocalID(); - auto iter = mImpl->mGLTFOverridesLLSD.find(local_id); - if (iter != mImpl->mGLTFOverridesLLSD.end()) - { - llassert(iter->second.mGLTFMaterial.size() == iter->second.mSides.size()); - - for (auto& side : iter->second.mGLTFMaterial) - { - obj->setTEGLTFMaterialOverride(side.first, side.second); - } - } -} - +/** + * @file llviewerregion.cpp + * @brief Implementation of the LLViewerRegion class. + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010-2013, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llviewerregion.h" + +// linden libraries +#include "indra_constants.h" +#include "llaisapi.h" +#include "llavatarnamecache.h" // name lookup cap url +#include "llfloaterreg.h" +#include "llmath.h" +#include "llregionflags.h" +#include "llregionhandle.h" +#include "llsurface.h" +#include "message.h" +//#include "vmath.h" +#include "v3math.h" +#include "v4math.h" + +#include "llagent.h" +#include "llagentcamera.h" +#include "llappviewer.h" +#include "llavatarrenderinfoaccountant.h" +#include "llcallingcard.h" +#include "llcommandhandler.h" +#include "lldir.h" +#include "lleventpoll.h" +#include "llfloatergodtools.h" +#include "llfloaterreporter.h" +#include "llfloaterregioninfo.h" +#include "llgltfmateriallist.h" +#include "llhttpnode.h" +#include "llregioninfomodel.h" +#include "llsdutil.h" +#include "llstartup.h" +#include "lltrans.h" +#include "llurldispatcher.h" +#include "llviewerobjectlist.h" +#include "llviewerparceloverlay.h" +#include "llviewerstatsrecorder.h" +#include "llvlmanager.h" +#include "llvlcomposition.h" +#include "llvoavatarself.h" +#include "llvocache.h" +#include "llworld.h" +#include "llspatialpartition.h" +#include "stringize.h" +#include "llviewercontrol.h" +#include "llsdserialize.h" +#include "llfloaterperms.h" +#include "llvieweroctree.h" +#include "llviewerdisplay.h" +#include "llviewerwindow.h" +#include "llprogressview.h" +#include "llcoros.h" +#include "lleventcoro.h" +#include "llcorehttputil.h" +#include "llcallstack.h" +#include "llsettingsdaycycle.h" + +#include + +#ifdef LL_WINDOWS + #pragma warning(disable:4355) +#endif + +// When we receive a base grant of capabilities that has a different number of +// capabilities than the original base grant received for the region, print +// out the two lists of capabilities for analysis. +//#define DEBUG_CAPS_GRANTS + +// The server only keeps our pending agent info for 60 seconds. +// We want to allow for seed cap retry, but its not useful after that 60 seconds. +// Even though we gave up on login, keep trying for caps after we are logged in: +const S32 MAX_CAP_REQUEST_ATTEMPTS = 30; +const U32 DEFAULT_MAX_REGION_WIDE_PRIM_COUNT = 15000; + +bool LLViewerRegion::sVOCacheCullingEnabled = false; +S32 LLViewerRegion::sLastCameraUpdated = 0; +S32 LLViewerRegion::sNewObjectCreationThrottle = -1; +LLViewerRegion::vocache_entry_map_t LLViewerRegion::sRegionCacheCleanup; + +typedef std::map CapabilityMap; + +static void log_capabilities(const CapabilityMap &capmap); + +namespace +{ + +void newRegionEntry(LLViewerRegion& region) +{ + LL_INFOS("LLViewerRegion") << "Entering region [" << region.getName() << "]" << LL_ENDL; + gDebugInfo["CurrentRegion"] = region.getName(); + LLAppViewer::instance()->writeDebugInfo(); +} + +} // anonymous namespace + +// support for secondlife:///app/region/{REGION} SLapps +// N.B. this is defined to work exactly like the classic secondlife://{REGION} +// However, the later syntax cannot support spaces in the region name because +// spaces (and %20 chars) are illegal in the hostname of an http URL. Some +// browsers let you get away with this, but some do not (such as Qt's Webkit). +// Hence we introduced the newer secondlife:///app/region alternative. +class LLRegionHandler : public LLCommandHandler +{ +public: + // requests will be throttled from a non-trusted browser + LLRegionHandler() : LLCommandHandler("region", UNTRUSTED_THROTTLE) {} + + bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) + { + // make sure that we at least have a region name + int num_params = params.size(); + if (num_params < 1) + { + return false; + } + + // build a secondlife://{PLACE} SLurl from this SLapp + std::string url = "secondlife://"; + if (!grid.empty()) + { + url += grid + "/secondlife/"; + } + boost::regex name_rx("[A-Za-z0-9()_%]+"); + boost::regex coord_rx("[0-9]+"); + for (int i = 0; i < num_params; i++) + { + if (i > 0) + { + url += "/"; + } + if (!boost::regex_match(params[i].asString(), i > 0 ? coord_rx : name_rx)) + { + return false; + } + + url += params[i].asString(); + } + + // Process the SLapp as if it was a secondlife://{PLACE} SLurl + LLURLDispatcher::dispatch(url, LLCommandHandler::NAV_TYPE_CLICKED, web, true); + return true; + } + +}; +LLRegionHandler gRegionHandler; + + +class LLViewerRegionImpl +{ +public: + LLViewerRegionImpl(LLViewerRegion * region, LLHost const & host): + mHost(host), + mCompositionp(NULL), + mEventPoll(NULL), + mSeedCapMaxAttempts(MAX_CAP_REQUEST_ATTEMPTS), + mSeedCapAttempts(0), + mHttpResponderID(0), + mLastCameraUpdate(0), + mLastCameraOrigin(), + mVOCachePartition(NULL), + mLandp(NULL) + {} + + static void buildCapabilityNames(LLSD& capabilityNames); + + // The surfaces and other layers + LLSurface* mLandp; + + // Region geometry data + LLVector3d mOriginGlobal; // Location of southwest corner of region (meters) + LLVector3d mCenterGlobal; // Location of center in world space (meters) + LLHost mHost; + + // The unique ID for this region. + LLUUID mRegionID; + + // region/estate owner - usually null. + LLUUID mOwnerID; + + // Network statistics for the region's circuit... + LLTimer mLastNetUpdate; + + // Misc + LLVLComposition *mCompositionp; // Composition layer for the surface + + LLVOCacheEntry::vocache_entry_map_t mCacheMap; //all cached entries + LLVOCacheEntry::vocache_entry_set_t mActiveSet; //all active entries; + LLVOCacheEntry::vocache_entry_set_t mWaitingSet; //entries waiting for LLDrawable to be generated. + std::set< LLPointer > mVisibleGroups; //visible groupa + LLVOCachePartition* mVOCachePartition; + LLVOCacheEntry::vocache_entry_set_t mVisibleEntries; //must-be-created visible entries wait for objects creation. + LLVOCacheEntry::vocache_entry_priority_list_t mWaitingList; //transient list storing sorted visible entries waiting for object creation. + std::set mNonCacheableCreatedList; //list of local ids of all non-cacheable objects + LLVOCacheEntry::vocache_gltf_overrides_map_t mGLTFOverridesLLSD; // for materials + + // time? + // LRU info? + + // Cache ID is unique per-region, across renames, moving locations, + // etc. + LLUUID mCacheID; + + CapabilityMap mCapabilities; + CapabilityMap mSecondCapabilitiesTracker; + + LLEventPoll* mEventPoll; + + S32 mSeedCapMaxAttempts; + S32 mSeedCapAttempts; + + S32 mHttpResponderID; + + //spatial partitions for objects in this region + std::vector mObjectPartition; + + LLVector3 mLastCameraOrigin; + U32 mLastCameraUpdate; + + static void requestBaseCapabilitiesCoro(U64 regionHandle); + static void requestBaseCapabilitiesCompleteCoro(U64 regionHandle); + static void requestSimulatorFeatureCoro(std::string url, U64 regionHandle); +}; + +void LLViewerRegionImpl::requestBaseCapabilitiesCoro(U64 regionHandle) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("BaseCapabilitiesRequest", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD result; + LLViewerRegion *regionp = NULL; + + // This loop is used for retrying a capabilities request. + do + { + if (STATE_WORLD_INIT > LLStartUp::getStartupState()) + { + LL_INFOS("AppInit", "Capabilities") << "Aborting capabilities request, reason: returned to login screen" << LL_ENDL; + return; + } + + if (!LLWorld::instanceExists()) + { + LL_WARNS("AppInit", "Capabilities") << "Attempting to get capabilities, but world no longer exists!" << LL_ENDL; + return; + } + + regionp = LLWorld::getInstance()->getRegionFromHandle(regionHandle); + if (!regionp) //region was removed + { + LL_WARNS("AppInit", "Capabilities") << "Attempting to get capabilities for region that no longer exists!" << LL_ENDL; + return; // this error condition is not recoverable. + } + LLViewerRegionImpl* impl = regionp->getRegionImplNC(); + LL_DEBUGS("AppInit", "Capabilities") << "requesting seed caps for handle " << regionHandle + << " name " << regionp->getName() << LL_ENDL; + + std::string url = regionp->getCapability("Seed"); + if (url.empty()) + { + LL_WARNS("AppInit", "Capabilities") << "Failed to get seed capabilities, and can not determine url!" << LL_ENDL; + regionp->setCapabilitiesError(); + return; // this error condition is not recoverable. + } + + // record that we just entered a new region + newRegionEntry(*regionp); + + if (impl->mSeedCapAttempts > impl->mSeedCapMaxAttempts) + { + // *TODO: Give a user pop-up about this error? + LL_WARNS("AppInit", "Capabilities") << "Failed to get seed capabilities from '" << url << "' after " << impl->mSeedCapAttempts << " attempts. Giving up!" << LL_ENDL; + return; // this error condition is not recoverable. + } + + S32 id = ++(impl->mHttpResponderID); + + LLSD capabilityNames = LLSD::emptyArray(); + impl->buildCapabilityNames(capabilityNames); + + LL_INFOS("AppInit", "Capabilities") << "Requesting seed from " << url + << " region name " << regionp->getName() + << " region id " << regionp->getRegionID() + << " handle " << regionp->getHandle() + << " (attempt #" << impl->mSeedCapAttempts + 1 << ")" << LL_ENDL; + LL_DEBUGS("AppInit", "Capabilities") << "Capabilities requested: " << capabilityNames << LL_ENDL; + + regionp = NULL; + impl = NULL; + result = httpAdapter->postAndSuspend(httpRequest, url, capabilityNames); + + if (STATE_WORLD_INIT > LLStartUp::getStartupState()) + { + LL_INFOS("AppInit", "Capabilities") << "Aborting capabilities request, reason: returned to login screen" << LL_ENDL; + return; + } + + if (LLApp::isExiting() || gDisconnected) + { + LL_DEBUGS("AppInit", "Capabilities") << "Shutting down" << LL_ENDL; + return; + } + + if (!LLWorld::instanceExists()) + { + LL_WARNS("AppInit", "Capabilities") << "Received capabilities, but world no longer exists!" << LL_ENDL; + return; + } + + regionp = LLWorld::getInstance()->getRegionFromHandle(regionHandle); + if (!regionp) //region was removed + { + LL_WARNS("AppInit", "Capabilities") << "Received capabilities for region that no longer exists!" << LL_ENDL; + return; // this error condition is not recoverable. + } + + impl = regionp->getRegionImplNC(); + + ++(impl->mSeedCapAttempts); + + if (!result.isMap() || result.has("error")) + { + LL_WARNS("AppInit", "Capabilities") << "Malformed response" << LL_ENDL; + // setup for retry. + continue; + } + + LLSD httpResults = result["http_result"]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + if (!status) + { + LL_WARNS("AppInit", "Capabilities") << "HttpStatus error " << LL_ENDL; + // setup for retry. + continue; + } + + // remove the http_result from the llsd + result.erase("http_result"); + + if (id != impl->mHttpResponderID) // region is no longer referring to this request + { + LL_WARNS("AppInit", "Capabilities") << "Received results for a stale capabilities request!" << LL_ENDL; + // setup for retry. + continue; + } + + LLSD::map_const_iterator iter; + for (iter = result.beginMap(); iter != result.endMap(); ++iter) + { + regionp->setCapability(iter->first, iter->second); + + LL_DEBUGS("AppInit", "Capabilities") + << "Capability '" << iter->first << "' is '" << iter->second << "'" << LL_ENDL; + } + +#if 0 + log_capabilities(mCapabilities); +#endif + + LL_DEBUGS("AppInit", "Capabilities", "Teleport") << "received caps for handle " << regionHandle + << " region name " << regionp->getName() << LL_ENDL; + regionp->setCapabilitiesReceived(true); + + break; + } + while (true); + + if (regionp && regionp->isCapabilityAvailable("ServerReleaseNotes") && + regionp->getReleaseNotesRequested()) + { // *HACK: we're waiting for the ServerReleaseNotes + regionp->showReleaseNotes(); + } +} + + +void LLViewerRegionImpl::requestBaseCapabilitiesCompleteCoro(U64 regionHandle) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("BaseCapabilitiesRequest", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD result; + LLViewerRegion *regionp = NULL; + + // This loop is used for retrying a capabilities request. + do + { + LLWorld *world_inst = LLWorld::getInstance(); // Not a singleton! + if (!world_inst) + { + LL_WARNS("AppInit", "Capabilities") << "Attempting to get capabilities, but world no longer exists!" << LL_ENDL; + return; + } + + regionp = world_inst->getRegionFromHandle(regionHandle); + if (!regionp) //region was removed + { + LL_WARNS("AppInit", "Capabilities") << "Attempting to get capabilities for region that no longer exists!" << LL_ENDL; + break; // this error condition is not recoverable. + } + + std::string url = regionp->getCapabilityDebug("Seed"); + if (url.empty()) + { + LL_WARNS("AppInit", "Capabilities") << "Failed to get seed capabilities, and can not determine url!" << LL_ENDL; + if (regionp->getCapability("Seed").empty()) + { + // initial attempt failed to get this cap as well + regionp->setCapabilitiesError(); + } + break; // this error condition is not recoverable. + } + + // record that we just entered a new region + newRegionEntry(*regionp); + + LLSD capabilityNames = LLSD::emptyArray(); + buildCapabilityNames(capabilityNames); + + LL_INFOS("AppInit", "Capabilities") << "Requesting second Seed from " << url << " for region " << regionp->getRegionID() << LL_ENDL; + + regionp = NULL; + world_inst = NULL; + result = httpAdapter->postAndSuspend(httpRequest, url, capabilityNames); + + LLSD httpResults = result["http_result"]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + if (!status) + { + LL_WARNS("AppInit", "Capabilities") << "HttpStatus error " << LL_ENDL; + break; // no retry + } + + if (LLApp::isExiting() || gDisconnected) + { + break; + } + + world_inst = LLWorld::getInstance(); + if (!world_inst) + { + LL_WARNS("AppInit", "Capabilities") << "Received capabilities, but world no longer exists!" << LL_ENDL; + return; + } + + regionp = world_inst->getRegionFromHandle(regionHandle); + if (!regionp) //region was removed + { + LL_WARNS("AppInit", "Capabilities") << "Received capabilities for region that no longer exists!" << LL_ENDL; + break; // this error condition is not recoverable. + } + LLViewerRegionImpl* impl = regionp->getRegionImplNC(); + + // remove the http_result from the llsd + result.erase("http_result"); + + LLSD::map_const_iterator iter; + for (iter = result.beginMap(); iter != result.endMap(); ++iter) + { + regionp->setCapabilityDebug(iter->first, iter->second); + //LL_INFOS()<<"BaseCapabilitiesCompleteTracker New Caps "<first<<" "<< iter->second<mCapabilities); +#endif + + if (impl->mCapabilities.size() != impl->mSecondCapabilitiesTracker.size()) + { + LL_WARNS("AppInit", "Capabilities") + << "Sim sent duplicate base caps that differ in size from what we initially received - most likely content. " + << "mCapabilities == " << impl->mCapabilities.size() + << " mSecondCapabilitiesTracker == " << impl->mSecondCapabilitiesTracker.size() + << LL_ENDL; +#ifdef DEBUG_CAPS_GRANTS + LL_WARNS("AppInit", "Capabilities") + << "Initial Base capabilities: " << LL_ENDL; + + log_capabilities(impl->mCapabilities); + + LL_WARNS("AppInit", "Capabilities") + << "Latest base capabilities: " << LL_ENDL; + + log_capabilities(impl->mSecondCapabilitiesTracker); + +#endif + + if (impl->mSecondCapabilitiesTracker.size() > impl->mCapabilities.size()) + { + // *HACK Since we were granted more base capabilities in this grant request than the initial, replace + // the old with the new. This shouldn't happen i.e. we should always get the same capabilities from a + // sim. The simulator fix from SH-3895 should prevent it from happening, at least in the case of the + // inventory api capability grants. + + // Need to clear a std::map before copying into it because old keys take precedence. + impl->mCapabilities.clear(); + impl->mCapabilities = impl->mSecondCapabilitiesTracker; + } + } + else + { + LL_DEBUGS("CrossingCaps") << "Sim sent multiple base cap grants with matching sizes." << LL_ENDL; + } + impl->mSecondCapabilitiesTracker.clear(); + } + while (false); +} + +void LLViewerRegionImpl::requestSimulatorFeatureCoro(std::string url, U64 regionHandle) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("BaseCapabilitiesRequest", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLViewerRegion *regionp = NULL; + S32 attemptNumber = 0; + // This loop is used for retrying a capabilities request. + do + { + ++attemptNumber; + + if (attemptNumber > MAX_CAP_REQUEST_ATTEMPTS) + { + LL_WARNS("AppInit", "SimulatorFeatures") << "Retries count exceeded attempting to get Simulator feature from " + << url << LL_ENDL; + break; + } + + LLWorld *world_inst = LLWorld::getInstance(); // Not a singleton! + if (!world_inst) + { + LL_WARNS("AppInit", "Capabilities") << "Attempting to request Sim Feature, but world no longer exists!" << LL_ENDL; + return; + } + + regionp = world_inst->getRegionFromHandle(regionHandle); + if (!regionp) //region was removed + { + LL_WARNS("AppInit", "SimulatorFeatures") << "Attempting to request Sim Feature for region that no longer exists!" << LL_ENDL; + break; // this error condition is not recoverable. + } + + regionp = NULL; + world_inst = NULL; + LLSD result = httpAdapter->getAndSuspend(httpRequest, url); + + LLSD httpResults = result["http_result"]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + if (!status) + { + LL_WARNS("AppInit", "SimulatorFeatures") << "HttpStatus error retrying" << LL_ENDL; + continue; + } + + if (LLApp::isExiting() || gDisconnected) + { + break; + } + + // remove the http_result from the llsd + result.erase("http_result"); + + world_inst = LLWorld::getInstance(); + if (!world_inst) + { + LL_WARNS("AppInit", "Capabilities") << "Attempting to request Sim Feature, but world no longer exists!" << LL_ENDL; + return; + } + + regionp = world_inst->getRegionFromHandle(regionHandle); + if (!regionp) //region was removed + { + LL_WARNS("AppInit", "SimulatorFeatures") << "Attempting to set Sim Feature for region that no longer exists!" << LL_ENDL; + break; // this error condition is not recoverable. + } + + regionp->setSimulatorFeatures(result); + + break; + } + while (true); + +} + +LLViewerRegion::LLViewerRegion(const U64 &handle, + const LLHost &host, + const U32 grids_per_region_edge, + const U32 grids_per_patch_edge, + const F32 region_width_meters) +: mImpl(new LLViewerRegionImpl(this, host)), + mHandle(handle), + mTimeDilation(1.0f), + mName(""), + mZoning(""), + mIsEstateManager(false), + mRegionFlags( REGION_FLAGS_DEFAULT ), + mRegionProtocols( 0 ), + mSimAccess( SIM_ACCESS_MIN ), + mBillableFactor(1.0), + mMaxTasks(DEFAULT_MAX_REGION_WIDE_PRIM_COUNT), + mCentralBakeVersion(1), + mClassID(0), + mCPURatio(0), + mColoName("unknown"), + mProductSKU("unknown"), + mProductName("unknown"), + mViewerAssetUrl(""), + mCacheLoaded(false), + mCacheDirty(false), + mReleaseNotesRequested(false), + mCapabilitiesState(CAPABILITIES_STATE_INIT), + mSimulatorFeaturesReceived(false), + mBitsReceived(0.f), + mPacketsReceived(0.f), + mDead(false), + mLastVisitedEntry(NULL), + mInvisibilityCheckHistory(-1), + mPaused(false), + mRegionCacheHitCount(0), + mRegionCacheMissCount(0), + mInterestListMode(IL_MODE_DEFAULT) +{ + mWidth = region_width_meters; + mImpl->mOriginGlobal = from_region_handle(handle); + updateRenderMatrix(); + + mImpl->mLandp = new LLSurface('l', NULL); + + // Create the composition layer for the surface + mImpl->mCompositionp = + new LLVLComposition(mImpl->mLandp, + grids_per_region_edge, + region_width_meters / grids_per_region_edge); + mImpl->mCompositionp->setSurface(mImpl->mLandp); + + // Create the surfaces + mImpl->mLandp->setRegion(this); + mImpl->mLandp->create(grids_per_region_edge, + grids_per_patch_edge, + mImpl->mOriginGlobal, + mWidth); + + mParcelOverlay = new LLViewerParcelOverlay(this, region_width_meters); + + setOriginGlobal(from_region_handle(handle)); + calculateCenterGlobal(); + + // Create the object lists + initStats(); + + //create object partitions + //MUST MATCH declaration of eObjectPartitions + mImpl->mObjectPartition.push_back(new LLHUDPartition(this)); //PARTITION_HUD + mImpl->mObjectPartition.push_back(new LLTerrainPartition(this)); //PARTITION_TERRAIN + mImpl->mObjectPartition.push_back(new LLVoidWaterPartition(this)); //PARTITION_VOIDWATER + mImpl->mObjectPartition.push_back(new LLWaterPartition(this)); //PARTITION_WATER + mImpl->mObjectPartition.push_back(new LLTreePartition(this)); //PARTITION_TREE + mImpl->mObjectPartition.push_back(new LLParticlePartition(this)); //PARTITION_PARTICLE + mImpl->mObjectPartition.push_back(new LLGrassPartition(this)); //PARTITION_GRASS + mImpl->mObjectPartition.push_back(new LLVolumePartition(this)); //PARTITION_VOLUME + mImpl->mObjectPartition.push_back(new LLBridgePartition(this)); //PARTITION_BRIDGE + mImpl->mObjectPartition.push_back(new LLAvatarPartition(this)); //PARTITION_AVATAR + mImpl->mObjectPartition.push_back(new LLControlAVPartition(this)); //PARTITION_CONTROL_AV + mImpl->mObjectPartition.push_back(new LLHUDParticlePartition(this));//PARTITION_HUD_PARTICLE + mImpl->mObjectPartition.push_back(new LLVOCachePartition(this)); //PARTITION_VO_CACHE + mImpl->mObjectPartition.push_back(NULL); //PARTITION_NONE + mImpl->mVOCachePartition = getVOCachePartition(); + + setCapabilitiesReceivedCallback(boost::bind(&LLAvatarRenderInfoAccountant::scanNewRegion, _1)); +} + + +void LLViewerRegion::initStats() +{ + mImpl->mLastNetUpdate.reset(); + mPacketsIn = 0; + mBitsIn = (U32Bits)0; + mLastBitsIn = (U32Bits)0; + mLastPacketsIn = 0; + mPacketsOut = 0; + mLastPacketsOut = 0; + mPacketsLost = 0; + mLastPacketsLost = 0; + mPingDelay = (U32Seconds)0; + mAlive = false; // can become false if circuit disconnects +} + +static LLTrace::BlockTimerStatHandle FTM_CLEANUP_REGION_OBJECTS("Cleanup Region Objects"); +static LLTrace::BlockTimerStatHandle FTM_SAVE_REGION_CACHE("Save Region Cache"); + +LLViewerRegion::~LLViewerRegion() +{ + LL_PROFILE_ZONE_SCOPED; + mDead = true; + mImpl->mActiveSet.clear(); + mImpl->mVisibleEntries.clear(); + mImpl->mVisibleGroups.clear(); + mImpl->mWaitingSet.clear(); + + gVLManager.cleanupData(this); + // Can't do this on destruction, because the neighbor pointers might be invalid. + // This should be reference counted... + disconnectAllNeighbors(); + LLViewerPartSim::getInstance()->cleanupRegion(this); + + { + LL_RECORD_BLOCK_TIME(FTM_CLEANUP_REGION_OBJECTS); + gObjectList.killObjects(this); + } + + delete mImpl->mCompositionp; + delete mParcelOverlay; + delete mImpl->mLandp; + delete mImpl->mEventPoll; +#if 0 + LLHTTPSender::clearSender(mImpl->mHost); +#endif + std::for_each(mImpl->mObjectPartition.begin(), mImpl->mObjectPartition.end(), DeletePointer()); + + { + LL_RECORD_BLOCK_TIME(FTM_SAVE_REGION_CACHE); + saveObjectCache(); + } + + delete mImpl; + mImpl = NULL; +} + +/*virtual*/ +const LLHost& LLViewerRegion::getHost() const +{ + return mImpl->mHost; +} + +LLSurface & LLViewerRegion::getLand() const +{ + return *mImpl->mLandp; +} + +const LLUUID& LLViewerRegion::getRegionID() const +{ + return mImpl->mRegionID; +} + +void LLViewerRegion::setRegionID(const LLUUID& region_id) +{ + mImpl->mRegionID = region_id; +} + +void LLViewerRegion::loadObjectCache() +{ + if (mCacheLoaded) + { + return; + } + + // Presume success. If it fails, we don't want to try again. + mCacheLoaded = true; + + if(LLVOCache::instanceExists()) + { + LLVOCache & vocache = LLVOCache::instance(); + vocache.readFromCache(mHandle, mImpl->mCacheID, mImpl->mCacheMap); + vocache.readGenericExtrasFromCache(mHandle, mImpl->mCacheID, mImpl->mGLTFOverridesLLSD); + + if (mImpl->mCacheMap.empty()) + { + mCacheDirty = true; + } + } +} + + +void LLViewerRegion::saveObjectCache() +{ + if (!mCacheLoaded) + { + return; + } + + if (mImpl->mCacheMap.empty()) + { + return; + } + + if(LLVOCache::instanceExists()) + { + const F32 start_time_threshold = 600.0f; //seconds + bool removal_enabled = sVOCacheCullingEnabled && (mRegionTimer.getElapsedTimeF32() > start_time_threshold); //allow to remove invalid objects from object cache file. + + LLVOCache & instance = LLVOCache::instance(); + + instance.writeToCache(mHandle, mImpl->mCacheID, mImpl->mCacheMap, mCacheDirty, removal_enabled); + instance.writeGenericExtrasToCache(mHandle, mImpl->mCacheID, mImpl->mGLTFOverridesLLSD, mCacheDirty, removal_enabled); + mCacheDirty = false; + } + + if (LLAppViewer::instance()->isQuitting()) + { + mImpl->mCacheMap.clear(); + } + else + { + // Map of LLVOCacheEntry takes time to release, store map for cleanup on idle + sRegionCacheCleanup.insert(mImpl->mCacheMap.begin(), mImpl->mCacheMap.end()); + mImpl->mCacheMap.clear(); + // TODO - probably need to do the same for overrides cache + } +} + +void LLViewerRegion::sendMessage() +{ + gMessageSystem->sendMessage(mImpl->mHost); +} + +void LLViewerRegion::sendReliableMessage() +{ + gMessageSystem->sendReliable(mImpl->mHost); +} + +void LLViewerRegion::setWaterHeight(F32 water_level) +{ + mImpl->mLandp->setWaterHeight(water_level); +} + +F32 LLViewerRegion::getWaterHeight() const +{ + return mImpl->mLandp->getWaterHeight(); +} + +bool LLViewerRegion::isVoiceEnabled() const +{ + return getRegionFlag(REGION_FLAGS_ALLOW_VOICE); +} + +void LLViewerRegion::setRegionFlags(U64 flags) +{ + mRegionFlags = flags; +} + + +void LLViewerRegion::setOriginGlobal(const LLVector3d &origin_global) +{ + mImpl->mOriginGlobal = origin_global; + updateRenderMatrix(); + mImpl->mLandp->setOriginGlobal(origin_global); + mWind.setOriginGlobal(origin_global); + calculateCenterGlobal(); +} + +void LLViewerRegion::updateRenderMatrix() +{ + mRenderMatrix.setTranslation(getOriginAgent()); +} + +void LLViewerRegion::setTimeDilation(F32 time_dilation) +{ + mTimeDilation = time_dilation; +} + +const LLVector3d & LLViewerRegion::getOriginGlobal() const +{ + return mImpl->mOriginGlobal; +} + +LLVector3 LLViewerRegion::getOriginAgent() const +{ + return gAgent.getPosAgentFromGlobal(mImpl->mOriginGlobal); +} + +const LLVector3d & LLViewerRegion::getCenterGlobal() const +{ + return mImpl->mCenterGlobal; +} + +LLVector3 LLViewerRegion::getCenterAgent() const +{ + return gAgent.getPosAgentFromGlobal(mImpl->mCenterGlobal); +} + +void LLViewerRegion::setOwner(const LLUUID& owner_id) +{ + mImpl->mOwnerID = owner_id; +} + +const LLUUID& LLViewerRegion::getOwner() const +{ + return mImpl->mOwnerID; +} + +void LLViewerRegion::setRegionNameAndZone (const std::string& name_zone) +{ + std::string::size_type pipe_pos = name_zone.find('|'); + S32 length = name_zone.size(); + if (pipe_pos != std::string::npos) + { + mName = name_zone.substr(0, pipe_pos); + mZoning = name_zone.substr(pipe_pos+1, length-(pipe_pos+1)); + } + else + { + mName = name_zone; + mZoning = ""; + } + + LLStringUtil::stripNonprintable(mName); + LLStringUtil::stripNonprintable(mZoning); +} + +bool LLViewerRegion::canManageEstate() const +{ + return gAgent.isGodlike() + || isEstateManager() + || gAgent.getID() == getOwner(); +} + +const std::string LLViewerRegion::getSimAccessString() const +{ + return accessToString(mSimAccess); +} + +std::string LLViewerRegion::getLocalizedSimProductName() const +{ + std::string localized_spn; + return LLTrans::findString(localized_spn, mProductName) ? localized_spn : mProductName; +} + +// static +std::string LLViewerRegion::regionFlagsToString(U64 flags) +{ + std::string result; + + if (flags & REGION_FLAGS_SANDBOX) + { + result += "Sandbox"; + } + + if (flags & REGION_FLAGS_ALLOW_DAMAGE) + { + result += " Not Safe"; + } + + return result; +} + +// static +std::string LLViewerRegion::accessToString(U8 sim_access) +{ + switch(sim_access) + { + case SIM_ACCESS_PG: + return LLTrans::getString("SIM_ACCESS_PG"); + + case SIM_ACCESS_MATURE: + return LLTrans::getString("SIM_ACCESS_MATURE"); + + case SIM_ACCESS_ADULT: + return LLTrans::getString("SIM_ACCESS_ADULT"); + + case SIM_ACCESS_DOWN: + return LLTrans::getString("SIM_ACCESS_DOWN"); + + case SIM_ACCESS_MIN: + default: + return LLTrans::getString("SIM_ACCESS_MIN"); + } +} + +// static +std::string LLViewerRegion::getAccessIcon(U8 sim_access) +{ + switch(sim_access) + { + case SIM_ACCESS_MATURE: + return "Parcel_M_Dark"; + + case SIM_ACCESS_ADULT: + return "Parcel_R_Light"; + + case SIM_ACCESS_PG: + return "Parcel_PG_Light"; + + case SIM_ACCESS_MIN: + default: + return ""; + } +} + +// static +std::string LLViewerRegion::accessToShortString(U8 sim_access) +{ + switch(sim_access) /* Flawfinder: ignore */ + { + case SIM_ACCESS_PG: + return "PG"; + + case SIM_ACCESS_MATURE: + return "M"; + + case SIM_ACCESS_ADULT: + return "A"; + + case SIM_ACCESS_MIN: + default: + return "U"; + } +} + +// static +U8 LLViewerRegion::shortStringToAccess(const std::string &sim_access) +{ + U8 accessValue; + + if (LLStringUtil::compareStrings(sim_access, "PG") == 0) + { + accessValue = SIM_ACCESS_PG; + } + else if (LLStringUtil::compareStrings(sim_access, "M") == 0) + { + accessValue = SIM_ACCESS_MATURE; + } + else if (LLStringUtil::compareStrings(sim_access, "A") == 0) + { + accessValue = SIM_ACCESS_ADULT; + } + else + { + accessValue = SIM_ACCESS_MIN; + } + + return accessValue; +} + +// static +void LLViewerRegion::processRegionInfo(LLMessageSystem* msg, void**) +{ + // send it to 'observers' + // *TODO: switch the floaters to using LLRegionInfoModel + LL_INFOS() << "Processing region info" << LL_ENDL; + LLRegionInfoModel::instance().update(msg); + LLFloaterGodTools::processRegionInfo(msg); + LLFloaterRegionInfo::processRegionInfo(msg); +} + +void LLViewerRegion::setCacheID(const LLUUID& id) +{ + mImpl->mCacheID = id; +} + +void LLViewerRegion::renderPropertyLines() +{ + if (mParcelOverlay) + { + mParcelOverlay->renderPropertyLines(); + } +} + +void LLViewerRegion::renderPropertyLinesOnMinimap(F32 scale_pixels_per_meter, const F32 *parcel_outline_color) +{ + if (mParcelOverlay) + { + mParcelOverlay->renderPropertyLinesOnMinimap(scale_pixels_per_meter, parcel_outline_color); + } +} + + +// This gets called when the height field changes. +void LLViewerRegion::dirtyHeights() +{ + // Property lines need to be reconstructed when the land changes. + if (mParcelOverlay) + { + mParcelOverlay->setDirty(); + } +} + +//physically delete the cache entry +void LLViewerRegion::killCacheEntry(LLVOCacheEntry* entry, bool for_rendering) +{ + if(!entry || !entry->isValid()) + { + return; + } + + if(for_rendering && !entry->isState(LLVOCacheEntry::ACTIVE)) + { + addNewObject(entry); //force to add to rendering pipeline + } + + //remove from active list and waiting list + if(entry->isState(LLVOCacheEntry::ACTIVE)) + { + mImpl->mActiveSet.erase(entry); + } + else + { + if(entry->isState(LLVOCacheEntry::WAITING)) + { + mImpl->mWaitingSet.erase(entry); + } + + //remove from mVOCachePartition + removeFromVOCacheTree(entry); + } + + //remove from the forced visible list + mImpl->mVisibleEntries.erase(entry); + + //disconnect from parent if it is a child + if(entry->getParentID() > 0) + { + LLVOCacheEntry* parent = getCacheEntry(entry->getParentID()); + if(parent) + { + parent->removeChild(entry); + } + } + else if(entry->getNumOfChildren() > 0)//remove children from cache if has any + { + LLVOCacheEntry* child = entry->getChild(); + while(child != NULL) + { + killCacheEntry(child, for_rendering); + child = entry->getChild(); + } + } + + //will remove it from the object cache, real deletion + entry->setState(LLVOCacheEntry::INACTIVE); + entry->removeOctreeEntry(); + entry->setValid(false); + + // TODO kill extras/material overrides cache too +} + +//physically delete the cache entry +void LLViewerRegion::killCacheEntry(U32 local_id) +{ + killCacheEntry(getCacheEntry(local_id)); +} + +U32 LLViewerRegion::getNumOfActiveCachedObjects() const +{ + return mImpl->mActiveSet.size(); +} + +void LLViewerRegion::addActiveCacheEntry(LLVOCacheEntry* entry) +{ + if(!entry || mDead) + { + return; + } + if(entry->isState(LLVOCacheEntry::ACTIVE)) + { + return; //already inserted. + } + + if(entry->isState(LLVOCacheEntry::WAITING)) + { + mImpl->mWaitingSet.erase(entry); + } + + entry->setState(LLVOCacheEntry::ACTIVE); + entry->setVisible(); + + llassert(entry->getEntry()->hasDrawable()); + mImpl->mActiveSet.insert(entry); +} + +void LLViewerRegion::removeActiveCacheEntry(LLVOCacheEntry* entry, LLDrawable* drawablep) +{ + if(mDead || !entry || !entry->isValid()) + { + return; + } + if(!entry->isState(LLVOCacheEntry::ACTIVE)) + { + return; //not an active entry. + } + + //shift to the local regional space from agent space + if(drawablep != NULL && drawablep->getVObj().notNull()) + { + const LLVector3& pos = drawablep->getVObj()->getPositionRegion(); + LLVector4a shift; + shift.load3(pos.mV); + shift.sub(entry->getPositionGroup()); + entry->shift(shift); + } + + if(entry->getParentID() > 0) //is a child + { + LLVOCacheEntry* parent = getCacheEntry(entry->getParentID()); + if(parent) + { + parent->addChild(entry); + } + else //parent not in cache. + { + //this happens only when parent is not cacheable. + mOrphanMap[entry->getParentID()].push_back(entry->getLocalID()); + } + } + else //insert to vo cache tree. + { + entry->updateParentBoundingInfo(); + entry->saveBoundingSphere(); + addToVOCacheTree(entry); + } + + mImpl->mVisibleEntries.erase(entry); + mImpl->mActiveSet.erase(entry); + mImpl->mWaitingSet.erase(entry); + entry->setState(LLVOCacheEntry::INACTIVE); +} + +bool LLViewerRegion::addVisibleGroup(LLViewerOctreeGroup* group) +{ + if(mDead || group->isEmpty()) + { + return false; + } + + mImpl->mVisibleGroups.insert(group); + + return true; +} + +U32 LLViewerRegion::getNumOfVisibleGroups() const +{ + return mImpl ? mImpl->mVisibleGroups.size() : 0; +} + +void LLViewerRegion::updateReflectionProbes() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY; + const F32 probe_spacing = 32.f; + const F32 probe_radius = sqrtf((probe_spacing * 0.5f) * (probe_spacing * 0.5f) * 3.f); + const F32 hover_height = 2.f; + + F32 start = probe_spacing * 0.5f; + + U32 grid_width = REGION_WIDTH_METERS / probe_spacing; + + mReflectionMaps.resize(grid_width * grid_width); + + F32 water_height = getWaterHeight(); + LLVector3 origin = getOriginAgent(); + + for (U32 i = 0; i < grid_width; ++i) + { + F32 x = i * probe_spacing + start; + for (U32 j = 0; j < grid_width; ++j) + { + F32 y = j * probe_spacing + start; + + U32 idx = i * grid_width + j; + + if (mReflectionMaps[idx].isNull()) + { + mReflectionMaps[idx] = gPipeline.mReflectionMapManager.addProbe(); + } + + LLVector3 probe_origin = LLVector3(x, y, llmax(water_height, mImpl->mLandp->resolveHeightRegion(x, y))); + probe_origin.mV[2] += hover_height; + probe_origin += origin; + + mReflectionMaps[idx]->mOrigin.load3(probe_origin.mV); + mReflectionMaps[idx]->mRadius = probe_radius; + } + } +} + +void LLViewerRegion::addToVOCacheTree(LLVOCacheEntry* entry) +{ + if(!sVOCacheCullingEnabled) + { + return; + } + + if(mDead || !entry || !entry->getEntry() || !entry->isValid()) + { + return; + } + if(entry->getParentID() > 0) + { + return; //no child prim in cache octree. + } + + if(entry->hasState(LLVOCacheEntry::IN_VO_TREE)) + { + return; //already in the tree. + } + + llassert_always(!entry->getGroup()); //not in octree. + llassert(!entry->getEntry()->hasDrawable()); //not have drawables + + if(mImpl->mVOCachePartition->addEntry(entry->getEntry())) + { + entry->setState(LLVOCacheEntry::IN_VO_TREE); + } +} + +void LLViewerRegion::removeFromVOCacheTree(LLVOCacheEntry* entry) +{ + if(mDead || !entry || !entry->getEntry()) + { + return; + } + + if(!entry->hasState(LLVOCacheEntry::IN_VO_TREE)) + { + return; //not in the tree. + } + entry->clearState(LLVOCacheEntry::IN_VO_TREE); + + mImpl->mVOCachePartition->removeEntry(entry->getEntry()); +} + +//add child objects as visible entries +void LLViewerRegion::addVisibleChildCacheEntry(LLVOCacheEntry* parent, LLVOCacheEntry* child) +{ + if(mDead) + { + return; + } + + if(parent && (!parent->isValid() || !parent->isState(LLVOCacheEntry::ACTIVE))) + { + return; //parent must be valid and in rendering pipeline + } + + if(child && (!child->getEntry() || !child->isValid() || !child->isState(LLVOCacheEntry::INACTIVE))) + { + return; //child must be valid and not in the rendering pipeline + } + + if(child) + { + child->setState(LLVOCacheEntry::IN_QUEUE); + mImpl->mVisibleEntries.insert(child); + } + else if(parent && parent->getNumOfChildren() > 0) //add all children + { + child = parent->getChild(); + while(child != NULL) + { + addVisibleChildCacheEntry(NULL, child); + child = parent->getChild(); + } + } +} + +void LLViewerRegion::updateVisibleEntries(F32 max_time) +{ + if(mDead) + { + return; + } + + if(mImpl->mVisibleGroups.empty() && mImpl->mVisibleEntries.empty()) + { + return; + } + + if(!sNewObjectCreationThrottle) + { + return; + } + + const F32 LARGE_SCENE_CONTRIBUTION = 1000.f; //a large number to force to load the object. + const LLVector3 camera_origin = LLViewerCamera::getInstance()->getOrigin(); + const U32 cur_frame = LLViewerOctreeEntryData::getCurrentFrame(); + bool needs_update = ((cur_frame - mImpl->mLastCameraUpdate) > 5) && ((camera_origin - mImpl->mLastCameraOrigin).lengthSquared() > 10.f); + U32 last_update = mImpl->mLastCameraUpdate; + LLVector4a local_origin; + local_origin.load3((camera_origin - getOriginAgent()).mV); + + //process visible entries + for(LLVOCacheEntry::vocache_entry_set_t::iterator iter = mImpl->mVisibleEntries.begin(); iter != mImpl->mVisibleEntries.end();) + { + LLVOCacheEntry* vo_entry = *iter; + + if(vo_entry->isValid() && vo_entry->getState() < LLVOCacheEntry::WAITING) + { + //set a large number to force to load this object. + vo_entry->setSceneContribution(LARGE_SCENE_CONTRIBUTION); + + mImpl->mWaitingList.insert(vo_entry); + ++iter; + } + else + { + LLVOCacheEntry::vocache_entry_set_t::iterator next_iter = iter; + ++next_iter; + mImpl->mVisibleEntries.erase(iter); + iter = next_iter; + } + } + + // + //process visible groups + // + //object projected area threshold + F32 projection_threshold = LLVOCacheEntry::getSquaredPixelThreshold(mImpl->mVOCachePartition->isFrontCull()); + F32 dist_threshold = mImpl->mVOCachePartition->isFrontCull() ? gAgentCamera.mDrawDistance : LLVOCacheEntry::sRearFarRadius; + + std::set< LLPointer >::iterator group_iter = mImpl->mVisibleGroups.begin(); + for(; group_iter != mImpl->mVisibleGroups.end(); ++group_iter) + { + LLPointer group = *group_iter; + if(group->getNumRefs() < 3 || //group to be deleted + !group->getOctreeNode() || group->isEmpty()) //group empty + { + continue; + } + + for (LLViewerOctreeGroup::element_iter i = group->getDataBegin(); i != group->getDataEnd(); ++i) + { + if((*i)->hasVOCacheEntry()) + { + LLVOCacheEntry* vo_entry = (LLVOCacheEntry*)(*i)->getVOCacheEntry(); + + if(vo_entry->getParentID() > 0) //is a child + { + //child visibility depends on its parent. + continue; + } + if(!vo_entry->isValid()) + { + continue; //skip invalid entry. + } + + vo_entry->calcSceneContribution(local_origin, needs_update, last_update, dist_threshold); + if(vo_entry->getSceneContribution() > projection_threshold) + { + mImpl->mWaitingList.insert(vo_entry); + } + } + } + } + + if(needs_update) + { + mImpl->mLastCameraOrigin = camera_origin; + mImpl->mLastCameraUpdate = cur_frame; + } + + return; +} + +void LLViewerRegion::createVisibleObjects(F32 max_time) +{ + if(mDead) + { + return; + } + if(mImpl->mWaitingList.empty()) + { + mImpl->mVOCachePartition->setCullHistory(false); + return; + } + + S32 throttle = sNewObjectCreationThrottle; + bool has_new_obj = false; + LLTimer update_timer; + for(LLVOCacheEntry::vocache_entry_priority_list_t::iterator iter = mImpl->mWaitingList.begin(); + iter != mImpl->mWaitingList.end(); ++iter) + { + LLVOCacheEntry* vo_entry = *iter; + + if(vo_entry->getState() < LLVOCacheEntry::WAITING) + { + addNewObject(vo_entry); + has_new_obj = true; + if(throttle > 0 && !(--throttle) && update_timer.getElapsedTimeF32() > max_time) + { + break; + } + } + } + + mImpl->mVOCachePartition->setCullHistory(has_new_obj); + + return; +} + +void LLViewerRegion::clearCachedVisibleObjects() +{ + mImpl->mWaitingList.clear(); + mImpl->mVisibleGroups.clear(); + + //reset all occluders + mImpl->mVOCachePartition->resetOccluders(); + mPaused = true; + + //clean visible entries + for(LLVOCacheEntry::vocache_entry_set_t::iterator iter = mImpl->mVisibleEntries.begin(); iter != mImpl->mVisibleEntries.end();) + { + LLVOCacheEntry* entry = *iter; + LLVOCacheEntry* parent = getCacheEntry(entry->getParentID()); + + if(!entry->getParentID() || parent) //no child or parent is cache-able + { + if(parent) //has a cache-able parent + { + parent->addChild(entry); + } + + LLVOCacheEntry::vocache_entry_set_t::iterator next_iter = iter; + ++next_iter; + mImpl->mVisibleEntries.erase(iter); + iter = next_iter; + } + else //parent is not cache-able, leave it. + { + ++iter; + } + } + + //remove all visible entries. + mLastVisitedEntry = NULL; + std::vector delete_list; + for(LLVOCacheEntry::vocache_entry_set_t::iterator iter = mImpl->mActiveSet.begin(); + iter != mImpl->mActiveSet.end(); ++iter) + { + LLVOCacheEntry* vo_entry = *iter; + if (!vo_entry || !vo_entry->getEntry()) + { + continue; + } + LLDrawable* drawablep = (LLDrawable*)vo_entry->getEntry()->getDrawable(); + + if(drawablep && !drawablep->getParent()) + { + delete_list.push_back(drawablep); + } + } + + if(!delete_list.empty()) + { + for(S32 i = 0; i < delete_list.size(); i++) + { + gObjectList.killObject(delete_list[i]->getVObj()); + } + delete_list.clear(); + } + + return; +} + +//perform some necessary but very light updates. +//to replace the function idleUpdate(...) in case there is no enough time. +void LLViewerRegion::lightIdleUpdate() +{ + if(!sVOCacheCullingEnabled) + { + return; + } + if(mImpl->mCacheMap.empty()) + { + return; + } + + //reset all occluders + mImpl->mVOCachePartition->resetOccluders(); +} + +void LLViewerRegion::idleUpdate(F32 max_update_time) +{ + LL_PROFILE_ZONE_SCOPED; + LLTimer update_timer; + F32 max_time; + + mLastUpdate = LLViewerOctreeEntryData::getCurrentFrame(); + + mImpl->mLandp->idleUpdate(max_update_time); + + if (mParcelOverlay) + { + // Hopefully not a significant time sink... + mParcelOverlay->idleUpdate(); + } + + if(!sVOCacheCullingEnabled) + { + return; + } + if(mImpl->mCacheMap.empty()) + { + return; + } + if(mPaused) + { + mPaused = false; //unpause. + } + + LLViewerCamera::eCameraID old_camera_id = LLViewerCamera::sCurCameraID; + LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD; + + //reset all occluders + mImpl->mVOCachePartition->resetOccluders(); + + max_time = max_update_time - update_timer.getElapsedTimeF32(); + + //kill invisible objects + killInvisibleObjects(max_time * 0.4f); + max_time = max_update_time - update_timer.getElapsedTimeF32(); + + updateVisibleEntries(max_time); + max_time = max_update_time - update_timer.getElapsedTimeF32(); + + createVisibleObjects(max_time); + + mImpl->mWaitingList.clear(); + mImpl->mVisibleGroups.clear(); + + LLViewerCamera::sCurCameraID = old_camera_id; + return; +} + +// static +void LLViewerRegion::idleCleanup(F32 max_update_time) +{ + LLTimer update_timer; + while (!sRegionCacheCleanup.empty() && (max_update_time - update_timer.getElapsedTimeF32() > 0)) + { + sRegionCacheCleanup.erase(sRegionCacheCleanup.begin()); + } +} + +//update the throttling number for new object creation +void LLViewerRegion::calcNewObjectCreationThrottle() +{ + static LLCachedControl new_object_creation_throttle(gSavedSettings,"NewObjectCreationThrottle"); + static LLCachedControl throttle_delay_time(gSavedSettings,"NewObjectCreationThrottleDelayTime"); + static LLFrameTimer timer; + + // + //sNewObjectCreationThrottle = + //-2: throttle is disabled because either the screen is showing progress view, or immediate after the screen is not black + //-1: throttle is disabled by the debug setting + //0: no new object creation is allowed + //>0: valid throttling number + // + + if(gViewerWindow->getProgressView()->getVisible() && throttle_delay_time > 0.f) + { + sNewObjectCreationThrottle = -2; //cancel the throttling + timer.reset(); + } + else if(sNewObjectCreationThrottle < -1) //just recoved from the login/teleport screen + { + if(timer.getElapsedTimeF32() > throttle_delay_time) //wait for throttle_delay_time to reset the throttle + { + sNewObjectCreationThrottle = new_object_creation_throttle; //reset + if(sNewObjectCreationThrottle < -1) + { + sNewObjectCreationThrottle = -1; + } + } + } + + //update some LLVOCacheEntry debug setting factors. + LLVOCacheEntry::updateDebugSettings(); +} + +bool LLViewerRegion::isViewerCameraStatic() +{ + return sLastCameraUpdated < LLViewerOctreeEntryData::getCurrentFrame(); +} + +void LLViewerRegion::killInvisibleObjects(F32 max_time) +{ +#if 1 // TODO: kill this. This is ill-conceived, objects that aren't in the camera frustum should not be deleted from memory. + // because of this, every time you turn around the simulator sends a swarm of full object update messages from cache + // probe misses and objects have to be reloaded from scratch. From some reason, disabling this causes holes to + // appear in the scene when flying back and forth between regions + if(!sVOCacheCullingEnabled) + { + return; + } + if(mImpl->mActiveSet.empty()) + { + return; + } + if(sNewObjectCreationThrottle < 0) + { + return; + } + + LLTimer update_timer; + LLVector4a camera_origin; + camera_origin.load3(LLViewerCamera::getInstance()->getOrigin().mV); + LLVector4a local_origin; + local_origin.load3((LLViewerCamera::getInstance()->getOrigin() - getOriginAgent()).mV); + F32 back_threshold = LLVOCacheEntry::sRearFarRadius; + + size_t max_update = 64; + if(!mInvisibilityCheckHistory && isViewerCameraStatic()) + { + //history is clean, reduce number of checking + max_update /= 2; + } + + std::vector delete_list; + S32 update_counter = llmin(max_update, mImpl->mActiveSet.size()); + LLVOCacheEntry::vocache_entry_set_t::iterator iter = mImpl->mActiveSet.upper_bound(mLastVisitedEntry); + + for(; update_counter > 0; --update_counter, ++iter) + { + if(iter == mImpl->mActiveSet.end()) + { + iter = mImpl->mActiveSet.begin(); + } + if((*iter)->getParentID() > 0) + { + continue; //skip child objects, they are removed with their parent. + } + + LLVOCacheEntry* vo_entry = *iter; + if(!vo_entry->isAnyVisible(camera_origin, local_origin, back_threshold) && vo_entry->mLastCameraUpdated < sLastCameraUpdated) + { + killObject(vo_entry, delete_list); + } + + if(max_time < update_timer.getElapsedTimeF32()) //time out + { + break; + } + } + + if(iter == mImpl->mActiveSet.end()) + { + mLastVisitedEntry = NULL; + } + else + { + mLastVisitedEntry = *iter; + } + + mInvisibilityCheckHistory <<= 1; + if(!delete_list.empty()) + { + mInvisibilityCheckHistory |= 1; + S32 count = delete_list.size(); + for(S32 i = 0; i < count; i++) + { + gObjectList.killObject(delete_list[i]->getVObj()); + } + delete_list.clear(); + } + + return; +#endif +} + +void LLViewerRegion::killObject(LLVOCacheEntry* entry, std::vector& delete_list) +{ + //kill the object. + LLDrawable* drawablep = (LLDrawable*)entry->getEntry()->getDrawable(); + llassert(drawablep); + llassert(drawablep->getRegion() == this); + + if(drawablep && !drawablep->getParent()) + { + LLViewerObject* v_obj = drawablep->getVObj(); + if (v_obj->isSelected() + || (v_obj->flagAnimSource() && isAgentAvatarValid() && gAgentAvatarp->hasMotionFromSource(v_obj->getID()))) + { + // do not remove objects user is interacting with + ((LLViewerOctreeEntryData*)drawablep)->setVisible(); + return; + } + LLViewerObject::const_child_list_t& child_list = v_obj->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); iter++) + { + LLViewerObject* child = *iter; + if(child->mDrawable) + { + if( !child->mDrawable->getEntry() + || !child->mDrawable->getEntry()->hasVOCacheEntry() + || child->isSelected() + || (child->flagAnimSource() && isAgentAvatarValid() && gAgentAvatarp->hasMotionFromSource(child->getID()))) + { + //do not remove parent if any of its children non-cacheable, animating or selected + //especially for the case that an avatar sits on a cache-able object + ((LLViewerOctreeEntryData*)drawablep)->setVisible(); + return; + } + + LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*)child->mDrawable->getGroup(); + if(group && group->isAnyRecentlyVisible()) + { + //set the parent visible if any of its children visible. + ((LLViewerOctreeEntryData*)drawablep)->setVisible(); + return; + } + } + } + delete_list.push_back(drawablep); + } +} + +LLViewerObject* LLViewerRegion::addNewObject(LLVOCacheEntry* entry) +{ + if(!entry || !entry->getEntry()) + { + if(entry) + { + mImpl->mVisibleEntries.erase(entry); + entry->setState(LLVOCacheEntry::INACTIVE); + } + return NULL; + } + + LLViewerObject* obj = NULL; + if(!entry->getEntry()->hasDrawable()) //not added to the rendering pipeline yet + { + //add the object + obj = gObjectList.processObjectUpdateFromCache(entry, this); + if(obj) + { + if(!entry->isState(LLVOCacheEntry::ACTIVE)) + { + mImpl->mWaitingSet.insert(entry); + entry->setState(LLVOCacheEntry::WAITING); + } + } + } + else + { + LLViewerRegion* old_regionp = ((LLDrawable*)entry->getEntry()->getDrawable())->getRegion(); + if(old_regionp != this) + { + //this object exists in two regions at the same time; + //this case can be safely ignored here because + //server should soon send update message to remove one region for this object. + + LL_WARNS() << "Entry: " << entry->getLocalID() << " exists in two regions at the same time." << LL_ENDL; + return NULL; + } + + LL_WARNS() << "Entry: " << entry->getLocalID() << " in rendering pipeline but not set to be active." << LL_ENDL; + + //should not hit here any more, but does not hurt either, just put it back to active list + addActiveCacheEntry(entry); + } + + return obj; +} + +//update object cache if the object receives a full-update or terse update +//update_type == EObjectUpdateType::OUT_TERSE_IMPROVED or EObjectUpdateType::OUT_FULL +LLViewerObject* LLViewerRegion::updateCacheEntry(U32 local_id, LLViewerObject* objectp) +{ + LLVOCacheEntry* entry = getCacheEntry(local_id); + if (!entry) + { + return objectp; //not in the cache, do nothing. + } + if(!objectp) //object not created + { + //create a new object from cache. + objectp = addNewObject(entry); + } + + //remove from cache. + killCacheEntry(entry, true); + + return objectp; +} + +// As above, but forcibly do the update. +void LLViewerRegion::forceUpdate() +{ + mImpl->mLandp->idleUpdate(0.f); + + if (mParcelOverlay) + { + mParcelOverlay->idleUpdate(true); + } +} + +void LLViewerRegion::connectNeighbor(LLViewerRegion *neighborp, U32 direction) +{ + mImpl->mLandp->connectNeighbor(neighborp->mImpl->mLandp, direction); +} + + +void LLViewerRegion::disconnectAllNeighbors() +{ + mImpl->mLandp->disconnectAllNeighbors(); +} + +LLVLComposition * LLViewerRegion::getComposition() const +{ + return mImpl->mCompositionp; +} + +F32 LLViewerRegion::getCompositionXY(const S32 x, const S32 y) const +{ + if (x >= 256) + { + if (y >= 256) + { + LLVector3d center = getCenterGlobal() + LLVector3d(256.f, 256.f, 0.f); + LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromPosGlobal(center); + if (regionp) + { + // OK, we need to do some hackery here - different simulators no longer use + // the same composition values, necessarily. + // If we're attempting to blend, then we want to make the fractional part of + // this region match the fractional of the adjacent. For now, just minimize + // the delta. + F32 our_comp = getComposition()->getValueScaled(255, 255); + F32 adj_comp = regionp->getComposition()->getValueScaled(x - 256.f, y - 256.f); + while (llabs(our_comp - adj_comp) >= 1.f) + { + if (our_comp > adj_comp) + { + adj_comp += 1.f; + } + else + { + adj_comp -= 1.f; + } + } + return adj_comp; + } + } + else + { + LLVector3d center = getCenterGlobal() + LLVector3d(256.f, 0, 0.f); + LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromPosGlobal(center); + if (regionp) + { + // OK, we need to do some hackery here - different simulators no longer use + // the same composition values, necessarily. + // If we're attempting to blend, then we want to make the fractional part of + // this region match the fractional of the adjacent. For now, just minimize + // the delta. + F32 our_comp = getComposition()->getValueScaled(255.f, (F32)y); + F32 adj_comp = regionp->getComposition()->getValueScaled(x - 256.f, (F32)y); + while (llabs(our_comp - adj_comp) >= 1.f) + { + if (our_comp > adj_comp) + { + adj_comp += 1.f; + } + else + { + adj_comp -= 1.f; + } + } + return adj_comp; + } + } + } + else if (y >= 256) + { + LLVector3d center = getCenterGlobal() + LLVector3d(0.f, 256.f, 0.f); + LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromPosGlobal(center); + if (regionp) + { + // OK, we need to do some hackery here - different simulators no longer use + // the same composition values, necessarily. + // If we're attempting to blend, then we want to make the fractional part of + // this region match the fractional of the adjacent. For now, just minimize + // the delta. + F32 our_comp = getComposition()->getValueScaled((F32)x, 255.f); + F32 adj_comp = regionp->getComposition()->getValueScaled((F32)x, y - 256.f); + while (llabs(our_comp - adj_comp) >= 1.f) + { + if (our_comp > adj_comp) + { + adj_comp += 1.f; + } + else + { + adj_comp -= 1.f; + } + } + return adj_comp; + } + } + + return getComposition()->getValueScaled((F32)x, (F32)y); +} + +void LLViewerRegion::calculateCenterGlobal() +{ + mImpl->mCenterGlobal = mImpl->mOriginGlobal; + mImpl->mCenterGlobal.mdV[VX] += 0.5 * mWidth; + mImpl->mCenterGlobal.mdV[VY] += 0.5 * mWidth; + mImpl->mCenterGlobal.mdV[VZ] = 0.5 * mImpl->mLandp->getMinZ() + mImpl->mLandp->getMaxZ(); +} + +void LLViewerRegion::calculateCameraDistance() +{ + mCameraDistanceSquared = (F32)(gAgentCamera.getCameraPositionGlobal() - getCenterGlobal()).magVecSquared(); +} + +std::ostream& operator<<(std::ostream &s, const LLViewerRegion ®ion) +{ + s << "{ "; + s << region.mImpl->mHost; + s << " mOriginGlobal = " << region.getOriginGlobal()<< "\n"; + std::string name(region.getName()), zone(region.getZoning()); + if (! name.empty()) + { + s << " mName = " << name << '\n'; + } + if (! zone.empty()) + { + s << " mZoning = " << zone << '\n'; + } + s << "}"; + return s; +} + + +// ---------------- Protected Member Functions ---------------- + +void LLViewerRegion::updateNetStats() +{ + F32 dt = mImpl->mLastNetUpdate.getElapsedTimeAndResetF32(); + + LLCircuitData *cdp = gMessageSystem->mCircuitInfo.findCircuit(mImpl->mHost); + if (!cdp) + { + mAlive = false; + return; + } + + mAlive = true; + mDeltaTime = dt; + + mLastPacketsIn = mPacketsIn; + mLastBitsIn = mBitsIn; + mLastPacketsOut = mPacketsOut; + mLastPacketsLost = mPacketsLost; + + mPacketsIn = cdp->getPacketsIn(); + mBitsIn = 8 * cdp->getBytesIn(); + mPacketsOut = cdp->getPacketsOut(); + mPacketsLost = cdp->getPacketsLost(); + mPingDelay = cdp->getPingDelay(); + + mBitsReceived += mBitsIn - mLastBitsIn; + mPacketsReceived += mPacketsIn - mLastPacketsIn; +} + + +U32 LLViewerRegion::getPacketsLost() const +{ + LLCircuitData *cdp = gMessageSystem->mCircuitInfo.findCircuit(mImpl->mHost); + if (!cdp) + { + LL_INFOS() << "LLViewerRegion::getPacketsLost couldn't find circuit for " << mImpl->mHost << LL_ENDL; + return 0; + } + else + { + return cdp->getPacketsLost(); + } +} + +S32 LLViewerRegion::getHttpResponderID() const +{ + return mImpl->mHttpResponderID; +} + +bool LLViewerRegion::pointInRegionGlobal(const LLVector3d &point_global) const +{ + LLVector3 pos_region = getPosRegionFromGlobal(point_global); + + if (pos_region.mV[VX] < 0) + { + return false; + } + if (pos_region.mV[VX] >= mWidth) + { + return false; + } + if (pos_region.mV[VY] < 0) + { + return false; + } + if (pos_region.mV[VY] >= mWidth) + { + return false; + } + return true; +} + +LLVector3 LLViewerRegion::getPosRegionFromGlobal(const LLVector3d &point_global) const +{ + LLVector3 pos_region; + pos_region.setVec(point_global - mImpl->mOriginGlobal); + return pos_region; +} + +LLVector3d LLViewerRegion::getPosGlobalFromRegion(const LLVector3 &pos_region) const +{ + LLVector3d pos_region_d; + pos_region_d.setVec(pos_region); + return pos_region_d + mImpl->mOriginGlobal; +} + +LLVector3 LLViewerRegion::getPosAgentFromRegion(const LLVector3 &pos_region) const +{ + LLVector3d pos_global = getPosGlobalFromRegion(pos_region); + + return gAgent.getPosAgentFromGlobal(pos_global); +} + +LLVector3 LLViewerRegion::getPosRegionFromAgent(const LLVector3 &pos_agent) const +{ + return pos_agent - getOriginAgent(); +} + +F32 LLViewerRegion::getLandHeightRegion(const LLVector3& region_pos) +{ + return mImpl->mLandp->resolveHeightRegion( region_pos ); +} + +bool LLViewerRegion::isAlive() +{ + return mAlive; +} + +bool LLViewerRegion::isOwnedSelf(const LLVector3& pos) +{ + if (mParcelOverlay) + { + return mParcelOverlay->isOwnedSelf(pos); + } else { + return false; + } +} + +// Owned by a group you belong to? (officer or member) +bool LLViewerRegion::isOwnedGroup(const LLVector3& pos) +{ + if (mParcelOverlay) + { + return mParcelOverlay->isOwnedGroup(pos); + } else { + return false; + } +} + +// the new TCP coarse location handler node +class CoarseLocationUpdate : public LLHTTPNode +{ +public: + virtual void post( + ResponsePtr responder, + const LLSD& context, + const LLSD& input) const + { + LLHost host(input["sender"].asString()); + + LLWorld *world_inst = LLWorld::getInstance(); // Not a singleton! + if (!world_inst) + { + return; + } + + LLViewerRegion* region = world_inst->getRegion(host); + if( !region ) + { + return; + } + + S32 target_index = input["body"]["Index"][0]["Prey"].asInteger(); + S32 you_index = input["body"]["Index"][0]["You" ].asInteger(); + + std::vector* avatar_locs = ®ion->mMapAvatars; + std::vector* avatar_ids = ®ion->mMapAvatarIDs; + avatar_locs->clear(); + avatar_ids->clear(); + + //LL_INFOS() << "coarse locations agent[0] " << input["body"]["AgentData"][0]["AgentID"].asUUID() << LL_ENDL; + //LL_INFOS() << "my agent id = " << gAgent.getID() << LL_ENDL; + //LL_INFOS() << ll_pretty_print_sd(input) << LL_ENDL; + + LLSD + locs = input["body"]["Location"], + agents = input["body"]["AgentData"]; + LLSD::array_iterator + locs_it = locs.beginArray(), + agents_it = agents.beginArray(); + bool has_agent_data = input["body"].has("AgentData"); + + for(int i=0; + locs_it != locs.endArray(); + i++, locs_it++) + { + U8 + x = locs_it->get("X").asInteger(), + y = locs_it->get("Y").asInteger(), + z = locs_it->get("Z").asInteger(); + // treat the target specially for the map, and don't add you or the target + if(i == target_index) + { + LLVector3d global_pos(region->getOriginGlobal()); + global_pos.mdV[VX] += (F64)x; + global_pos.mdV[VY] += (F64)y; + global_pos.mdV[VZ] += (F64)z * 4.0; + LLAvatarTracker::instance().setTrackedCoarseLocation(global_pos); + } + else if( i != you_index) + { + U32 pos = 0x0; + pos |= x; + pos <<= 8; + pos |= y; + pos <<= 8; + pos |= z; + avatar_locs->push_back(pos); + //LL_INFOS() << "next pos: " << x << "," << y << "," << z << ": " << pos << LL_ENDL; + if(has_agent_data) // for backwards compatibility with old message format + { + LLUUID agent_id(agents_it->get("AgentID").asUUID()); + //LL_INFOS() << "next agent: " << agent_id.asString() << LL_ENDL; + avatar_ids->push_back(agent_id); + } + } + if (has_agent_data) + { + agents_it++; + } + } + } +}; + +// build the coarse location HTTP node under the "/message" URL +LLHTTPRegistration + gHTTPRegistrationCoarseLocationUpdate( + "/message/CoarseLocationUpdate"); + + +// the deprecated coarse location handler +void LLViewerRegion::updateCoarseLocations(LLMessageSystem* msg) +{ + //LL_INFOS() << "CoarseLocationUpdate" << LL_ENDL; + mMapAvatars.clear(); + mMapAvatarIDs.clear(); // only matters in a rare case but it's good to be safe. + + U8 x_pos = 0; + U8 y_pos = 0; + U8 z_pos = 0; + + U32 pos = 0x0; + + S16 agent_index; + S16 target_index; + msg->getS16Fast(_PREHASH_Index, _PREHASH_You, agent_index); + msg->getS16Fast(_PREHASH_Index, _PREHASH_Prey, target_index); + + bool has_agent_data = msg->has(_PREHASH_AgentData); + S32 count = msg->getNumberOfBlocksFast(_PREHASH_Location); + for(S32 i = 0; i < count; i++) + { + msg->getU8Fast(_PREHASH_Location, _PREHASH_X, x_pos, i); + msg->getU8Fast(_PREHASH_Location, _PREHASH_Y, y_pos, i); + msg->getU8Fast(_PREHASH_Location, _PREHASH_Z, z_pos, i); + LLUUID agent_id = LLUUID::null; + if(has_agent_data) + { + msg->getUUIDFast(_PREHASH_AgentData, _PREHASH_AgentID, agent_id, i); + } + + //LL_INFOS() << " object X: " << (S32)x_pos << " Y: " << (S32)y_pos + // << " Z: " << (S32)(z_pos * 4) + // << LL_ENDL; + + // treat the target specially for the map + if(i == target_index) + { + LLVector3d global_pos(mImpl->mOriginGlobal); + global_pos.mdV[VX] += (F64)(x_pos); + global_pos.mdV[VY] += (F64)(y_pos); + global_pos.mdV[VZ] += (F64)(z_pos) * 4.0; + LLAvatarTracker::instance().setTrackedCoarseLocation(global_pos); + } + + //don't add you + if( i != agent_index) + { + pos = 0x0; + pos |= x_pos; + pos <<= 8; + pos |= y_pos; + pos <<= 8; + pos |= z_pos; + mMapAvatars.push_back(pos); + if(has_agent_data) + { + mMapAvatarIDs.push_back(agent_id); + } + } + } +} + +void LLViewerRegion::getInfo(LLSD& info) +{ + info["Region"]["Host"] = getHost().getIPandPort(); + info["Region"]["Name"] = getName(); + U32 x, y; + from_region_handle(getHandle(), &x, &y); + info["Region"]["Handle"]["x"] = (LLSD::Integer)x; + info["Region"]["Handle"]["y"] = (LLSD::Integer)y; +} + +void LLViewerRegion::requestSimulatorFeatures() +{ + LL_DEBUGS("SimulatorFeatures") << "region " << getName() << " ptr " << this + << " trying to request SimulatorFeatures" << LL_ENDL; + // kick off a request for simulator features + std::string url = getCapability("SimulatorFeatures"); + if (!url.empty()) + { + std::string coroname = + LLCoros::instance().launch("LLViewerRegionImpl::requestSimulatorFeatureCoro", + boost::bind(&LLViewerRegionImpl::requestSimulatorFeatureCoro, url, getHandle())); + + // requestSimulatorFeatures can be called from other coros, + // launch() acts like a suspend() + // Make sure we are still good to do + LLCoros::checkStop(); + + LL_INFOS("AppInit", "SimulatorFeatures") << "Launching " << coroname << " requesting simulator features from " << url << " for region " << getRegionID() << LL_ENDL; + } + else + { + LL_WARNS("AppInit", "SimulatorFeatures") << "SimulatorFeatures cap not set" << LL_ENDL; + } +} + +boost::signals2::connection LLViewerRegion::setSimulatorFeaturesReceivedCallback(const caps_received_signal_t::slot_type& cb) +{ + return mSimulatorFeaturesReceivedSignal.connect(cb); +} + +void LLViewerRegion::setSimulatorFeaturesReceived(bool received) +{ + mSimulatorFeaturesReceived = received; + if (received) + { + mSimulatorFeaturesReceivedSignal(getRegionID(), this); + mSimulatorFeaturesReceivedSignal.disconnect_all_slots(); + } +} + +bool LLViewerRegion::simulatorFeaturesReceived() const +{ + return mSimulatorFeaturesReceived; +} + +void LLViewerRegion::getSimulatorFeatures(LLSD& sim_features) const +{ + sim_features = mSimulatorFeatures; + +} + +void LLViewerRegion::setSimulatorFeatures(const LLSD& sim_features) +{ + std::stringstream str; + + LLSDSerialize::toPrettyXML(sim_features, str); + LL_INFOS() << "region " << getName() << " " << str.str() << LL_ENDL; + mSimulatorFeatures = sim_features; + + setSimulatorFeaturesReceived(true); + +} + +//this is called when the parent is not cacheable. +//move all orphan children out of cache and insert to rendering octree. +void LLViewerRegion::findOrphans(U32 parent_id) +{ + orphan_list_t::iterator iter = mOrphanMap.find(parent_id); + if(iter != mOrphanMap.end()) + { + std::vector* children = &mOrphanMap[parent_id]; + for(S32 i = 0; i < children->size(); i++) + { + //parent is visible, so is the child. + addVisibleChildCacheEntry(NULL, getCacheEntry((*children)[i])); + } + children->clear(); + mOrphanMap.erase(parent_id); + } +} + +void LLViewerRegion::decodeBoundingInfo(LLVOCacheEntry* entry) +{ + if(!sVOCacheCullingEnabled) + { + gObjectList.processObjectUpdateFromCache(entry, this); + return; + } + if(!entry || !entry->isValid()) + { + return; + } + + if(!entry->getEntry()) + { + entry->setOctreeEntry(NULL); + } + + if(entry->getEntry()->hasDrawable()) //already in the rendering pipeline + { + LLViewerRegion* old_regionp = ((LLDrawable*)entry->getEntry()->getDrawable())->getRegion(); + if(old_regionp != this && old_regionp) + { + LLViewerObject* obj = ((LLDrawable*)entry->getEntry()->getDrawable())->getVObj(); + if(obj) + { + //remove from old region + old_regionp->killCacheEntry(obj->getLocalID()); + + //change region + obj->setRegion(this); + } + } + + addActiveCacheEntry(entry); + + //set parent id + U32 parent_id = 0; + if (entry->getDP()) // NULL if nothing cached + { + LLViewerObject::unpackParentID(entry->getDP(), parent_id); + } + if(parent_id != entry->getParentID()) + { + entry->setParentID(parent_id); + } + + //update the object + gObjectList.processObjectUpdateFromCache(entry, this); + return; //done + } + + //must not be active. + llassert_always(!entry->isState(LLVOCacheEntry::ACTIVE)); + removeFromVOCacheTree(entry); //remove from cache octree if it is in. + + LLVector3 pos; + LLVector3 scale; + LLQuaternion rot; + + //decode spatial info and parent info + U32 parent_id = entry->getDP() ? LLViewerObject::extractSpatialExtents(entry->getDP(), pos, scale, rot) : entry->getParentID(); + + U32 old_parent_id = entry->getParentID(); + bool same_old_parent = false; + if(parent_id != old_parent_id) //parent changed. + { + if(old_parent_id > 0) //has an old parent, disconnect it + { + LLVOCacheEntry* old_parent = getCacheEntry(old_parent_id); + if(old_parent) + { + old_parent->removeChild(entry); + if(!old_parent->isState(LLVOCacheEntry::INACTIVE)) + { + mImpl->mVisibleEntries.erase(entry); + entry->setState(LLVOCacheEntry::INACTIVE); + } + } + } + entry->setParentID(parent_id); + } + else + { + same_old_parent = true; + } + + if(parent_id > 0) //has a new parent + { + //1, find the parent in cache + LLVOCacheEntry* parent = getCacheEntry(parent_id); + + //2, parent is not in the cache, put into the orphan list. + if(!parent) + { + if(!same_old_parent) + { + //check if parent is non-cacheable and already created + if(isNonCacheableObjectCreated(parent_id)) + { + //parent is visible, so is the child. + addVisibleChildCacheEntry(NULL, entry); + } + else + { + entry->setBoundingInfo(pos, scale); + mOrphanMap[parent_id].push_back(entry->getLocalID()); + } + } + else + { + entry->setBoundingInfo(pos, scale); + } + } + else //parent in cache. + { + if(!parent->isState(LLVOCacheEntry::INACTIVE)) + { + //parent is visible, so is the child. + addVisibleChildCacheEntry(parent, entry); + } + else + { + entry->setBoundingInfo(pos, scale); + parent->addChild(entry); + + if(parent->getGroup()) //re-insert parent to vo-cache tree because its bounding info changed. + { + removeFromVOCacheTree(parent); + addToVOCacheTree(parent); + } + } + } + + return; + } + + // + //no parent + // + entry->setBoundingInfo(pos, scale); + + if(!parent_id) //a potential parent + { + //find all children and update their bounding info + orphan_list_t::iterator iter = mOrphanMap.find(entry->getLocalID()); + if(iter != mOrphanMap.end()) + { + std::vector* orphans = &mOrphanMap[entry->getLocalID()]; + S32 size = orphans->size(); + for(S32 i = 0; i < size; i++) + { + LLVOCacheEntry* child = getCacheEntry((*orphans)[i]); + if(child) + { + entry->addChild(child); + } + } + orphans->clear(); + mOrphanMap.erase(entry->getLocalID()); + } + } + + if(!entry->getGroup() && entry->isState(LLVOCacheEntry::INACTIVE)) + { + addToVOCacheTree(entry); + } + return ; +} + +LLViewerRegion::eCacheUpdateResult LLViewerRegion::cacheFullUpdate(LLDataPackerBinaryBuffer &dp, U32 flags) +{ + eCacheUpdateResult result; + U32 crc; + U32 local_id; + + LLViewerObject::unpackU32(&dp, local_id, "LocalID"); + LLViewerObject::unpackU32(&dp, crc, "CRC"); + + LLVOCacheEntry* entry = getCacheEntry(local_id, false); + + if (entry) + { + entry->setValid(); + + // we've seen this object before + if (entry->getCRC() == crc) + { + LL_DEBUGS("AnimatedObjects") << " got dupe for local_id " << local_id << LL_ENDL; + dumpStack("AnimatedObjectsStack"); + + // Record a hit + entry->recordDupe(); + result = CACHE_UPDATE_DUPE; + } + else //CRC changed + { + LL_DEBUGS("AnimatedObjects") << " got update for local_id " << local_id << LL_ENDL; + dumpStack("AnimatedObjectsStack"); + + // Update the cache entry + entry->updateEntry(crc, dp); + + decodeBoundingInfo(entry); + + result = CACHE_UPDATE_CHANGED; + } + } + else + { + LL_DEBUGS("AnimatedObjects") << " got first notification for local_id " << local_id << LL_ENDL; + dumpStack("AnimatedObjectsStack"); + + // we haven't seen this object before + // Create new entry and add to map + result = CACHE_UPDATE_ADDED; + entry = new LLVOCacheEntry(local_id, crc, dp); + record(LLStatViewer::OBJECT_CACHE_HIT_RATE, LLUnits::Ratio::fromValue(0)); + + mImpl->mCacheMap[local_id] = entry; + + decodeBoundingInfo(entry); + } + entry->setUpdateFlags(flags); + + return result; + } + +LLViewerRegion::eCacheUpdateResult LLViewerRegion::cacheFullUpdate(LLViewerObject* objectp, LLDataPackerBinaryBuffer &dp, U32 flags) +{ + eCacheUpdateResult result = cacheFullUpdate(dp, flags); + + return result; +} + +void LLViewerRegion::cacheFullUpdateGLTFOverride(const LLGLTFOverrideCacheEntry &override_data) +{ + U32 local_id = override_data.mLocalId; + mImpl->mGLTFOverridesLLSD[local_id] = override_data; +} + +LLVOCacheEntry* LLViewerRegion::getCacheEntryForOctree(U32 local_id) +{ + if(!sVOCacheCullingEnabled) + { + return NULL; + } + + LLVOCacheEntry* entry = getCacheEntry(local_id); + removeFromVOCacheTree(entry); + + return entry; +} + +LLVOCacheEntry* LLViewerRegion::getCacheEntry(U32 local_id, bool valid) +{ + LLVOCacheEntry::vocache_entry_map_t::iterator iter = mImpl->mCacheMap.find(local_id); + if(iter != mImpl->mCacheMap.end()) + { + if(!valid || iter->second->isValid()) + { + return iter->second; + } + } + return NULL; +} + +void LLViewerRegion::addCacheMiss(U32 id, LLViewerRegion::eCacheMissType cache_miss_type) +{ + mRegionCacheMissCount++; + mCacheMissList.push_back(CacheMissItem(id, cache_miss_type)); +} + +//check if a non-cacheable object is already created. +bool LLViewerRegion::isNonCacheableObjectCreated(U32 local_id) +{ + if(mImpl && local_id > 0 && mImpl->mNonCacheableCreatedList.find(local_id) != mImpl->mNonCacheableCreatedList.end()) + { + return true; + } + return false; +} + +void LLViewerRegion::removeFromCreatedList(U32 local_id) +{ + if(mImpl && local_id > 0) + { + std::set::iterator iter = mImpl->mNonCacheableCreatedList.find(local_id); + if(iter != mImpl->mNonCacheableCreatedList.end()) + { + mImpl->mNonCacheableCreatedList.erase(iter); + } + } + } + +void LLViewerRegion::addToCreatedList(U32 local_id) +{ + if(mImpl && local_id > 0) + { + mImpl->mNonCacheableCreatedList.insert(local_id); + } +} + +// Get data packer for this object, if we have cached data +// AND the CRC matches. JC +bool LLViewerRegion::probeCache(U32 local_id, U32 crc, U32 flags, U8 &cache_miss_type) +{ + //llassert(mCacheLoaded); This assert failes often, changing to early-out -- davep, 2010/10/18 + + LLVOCacheEntry* entry = getCacheEntry(local_id, false); + + if (entry) + { + // we've seen this object before + if (entry->getCRC() == crc) + { + // Record a hit + mRegionCacheHitCount++; + entry->recordHit(); + cache_miss_type = CACHE_MISS_TYPE_NONE; + entry->setUpdateFlags(flags); + + if(entry->isState(LLVOCacheEntry::ACTIVE)) + { + ((LLDrawable*)entry->getEntry()->getDrawable())->getVObj()->loadFlags(flags); + return true; + } + + if(entry->isValid()) + { + return true; //already probed + } + + entry->setValid(); + decodeBoundingInfo(entry); + + //loadCacheMiscExtras(local_id, entry, crc); + + return true; + } + else + { + // LL_INFOS() << "CRC miss for " << local_id << LL_ENDL; + + addCacheMiss(local_id, CACHE_MISS_TYPE_CRC); + cache_miss_type = CACHE_MISS_TYPE_CRC; + } + } + else + { // Total miss, don't have the object in cache + // LL_INFOS() << "Cache miss for " << local_id << LL_ENDL; + addCacheMiss(local_id, CACHE_MISS_TYPE_TOTAL); + cache_miss_type = CACHE_MISS_TYPE_TOTAL; + } + + return false; +} + +void LLViewerRegion::addCacheMissFull(const U32 local_id) +{ + addCacheMiss(local_id, CACHE_MISS_TYPE_TOTAL); +} + +void LLViewerRegion::requestCacheMisses() +{ + if (!mCacheMissList.size()) + { + return; + } + + LLMessageSystem* msg = gMessageSystem; + bool start_new_message = true; + S32 blocks = 0; + + //send requests for all cache-missed objects + for (CacheMissItem::cache_miss_list_t::iterator iter = mCacheMissList.begin(); iter != mCacheMissList.end(); ++iter) + { + if (start_new_message) + { + msg->newMessageFast(_PREHASH_RequestMultipleObjects); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + start_new_message = false; + } + + msg->nextBlockFast(_PREHASH_ObjectData); + msg->addU8Fast(_PREHASH_CacheMissType, (*iter).mType); + msg->addU32Fast(_PREHASH_ID, (*iter).mID); + + LL_DEBUGS("AnimatedObjects") << "Requesting cache missed object " << (*iter).mID << LL_ENDL; + + blocks++; + + if (blocks >= 255) + { + sendReliableMessage(); + start_new_message = true; + blocks = 0; + } + } + + // finish any pending message + if (!start_new_message) + { + sendReliableMessage(); + } + + mCacheDirty = true ; + // LL_INFOS() << "KILLDEBUG Sent cache miss full " << full_count << " crc " << crc_count << LL_ENDL; + LLViewerStatsRecorder::instance().requestCacheMissesEvent(mCacheMissList.size()); + + mCacheMissList.clear(); +} + +void LLViewerRegion::dumpCache() +{ + const S32 BINS = 4; + S32 hit_bin[BINS]; + S32 change_bin[BINS]; + + S32 i; + for (i = 0; i < BINS; ++i) + { + hit_bin[i] = 0; + change_bin[i] = 0; + } + + LLVOCacheEntry *entry; + for(LLVOCacheEntry::vocache_entry_map_t::iterator iter = mImpl->mCacheMap.begin(); iter != mImpl->mCacheMap.end(); ++iter) + { + entry = iter->second ; + + S32 hits = entry->getHitCount(); + S32 changes = entry->getCRCChangeCount(); + + hits = llclamp(hits, 0, BINS-1); + changes = llclamp(changes, 0, BINS-1); + + hit_bin[hits]++; + change_bin[changes]++; + } + + LL_INFOS() << "Count " << mImpl->mCacheMap.size() << LL_ENDL; + for (i = 0; i < BINS; i++) + { + LL_INFOS() << "Hits " << i << " " << hit_bin[i] << LL_ENDL; + } + for (i = 0; i < BINS; i++) + { + LL_INFOS() << "Changes " << i << " " << change_bin[i] << LL_ENDL; + } + // TODO - add overrides cache too +} + +void LLViewerRegion::unpackRegionHandshake() +{ + LLMessageSystem *msg = gMessageSystem; + + U64 region_flags = 0; + U64 region_protocols = 0; + U8 sim_access; + std::string sim_name; + LLUUID sim_owner; + bool is_estate_manager; + F32 water_height; + F32 billable_factor; + LLUUID cache_id; + + msg->getU8 ("RegionInfo", "SimAccess", sim_access); + msg->getString ("RegionInfo", "SimName", sim_name); + msg->getUUID ("RegionInfo", "SimOwner", sim_owner); + msg->getBOOL ("RegionInfo", "IsEstateManager", is_estate_manager); + msg->getF32 ("RegionInfo", "WaterHeight", water_height); + msg->getF32 ("RegionInfo", "BillableFactor", billable_factor); + msg->getUUID ("RegionInfo", "CacheID", cache_id ); + + if (msg->has(_PREHASH_RegionInfo4)) + { + msg->getU64Fast(_PREHASH_RegionInfo4, _PREHASH_RegionFlagsExtended, region_flags); + msg->getU64Fast(_PREHASH_RegionInfo4, _PREHASH_RegionProtocols, region_protocols); + } + else + { + U32 flags = 0; + msg->getU32Fast(_PREHASH_RegionInfo, _PREHASH_RegionFlags, flags); + region_flags = flags; + } + + setRegionFlags(region_flags); + setRegionProtocols(region_protocols); + setSimAccess(sim_access); + setRegionNameAndZone(sim_name); + setOwner(sim_owner); + setIsEstateManager(is_estate_manager); + setWaterHeight(water_height); + setBillableFactor(billable_factor); + setCacheID(cache_id); + + LLUUID region_id; + msg->getUUID("RegionInfo2", "RegionID", region_id); + setRegionID(region_id); + + // Retrieve the CR-53 (Homestead/Land SKU) information + S32 classID = 0; + S32 cpuRatio = 0; + std::string coloName; + std::string productSKU; + std::string productName; + + // the only reasonable way to decide if we actually have any data is to + // check to see if any of these fields have positive sizes + if (msg->getSize("RegionInfo3", "ColoName") > 0 || + msg->getSize("RegionInfo3", "ProductSKU") > 0 || + msg->getSize("RegionInfo3", "ProductName") > 0) + { + msg->getS32 ("RegionInfo3", "CPUClassID", classID); + msg->getS32 ("RegionInfo3", "CPURatio", cpuRatio); + msg->getString ("RegionInfo3", "ColoName", coloName); + msg->getString ("RegionInfo3", "ProductSKU", productSKU); + msg->getString ("RegionInfo3", "ProductName", productName); + + mClassID = classID; + mCPURatio = cpuRatio; + mColoName = coloName; + mProductSKU = productSKU; + mProductName = productName; + } + + mCentralBakeVersion = region_protocols & 1; // was (S32)gSavedSettings.getBOOL("UseServerTextureBaking"); + LLVLComposition *compp = getComposition(); + if (compp) + { + LLUUID tmp_id; + + bool changed = false; + + // Get the 4 textures for land + msg->getUUID("RegionInfo", "TerrainDetail0", tmp_id); + changed |= (tmp_id != compp->getDetailTextureID(0)); + compp->setDetailTextureID(0, tmp_id); + + msg->getUUID("RegionInfo", "TerrainDetail1", tmp_id); + changed |= (tmp_id != compp->getDetailTextureID(1)); + compp->setDetailTextureID(1, tmp_id); + + msg->getUUID("RegionInfo", "TerrainDetail2", tmp_id); + changed |= (tmp_id != compp->getDetailTextureID(2)); + compp->setDetailTextureID(2, tmp_id); + + msg->getUUID("RegionInfo", "TerrainDetail3", tmp_id); + changed |= (tmp_id != compp->getDetailTextureID(3)); + compp->setDetailTextureID(3, tmp_id); + + // Get the start altitude and range values for land textures + F32 tmp_f32; + msg->getF32("RegionInfo", "TerrainStartHeight00", tmp_f32); + changed |= (tmp_f32 != compp->getStartHeight(0)); + compp->setStartHeight(0, tmp_f32); + + msg->getF32("RegionInfo", "TerrainStartHeight01", tmp_f32); + changed |= (tmp_f32 != compp->getStartHeight(1)); + compp->setStartHeight(1, tmp_f32); + + msg->getF32("RegionInfo", "TerrainStartHeight10", tmp_f32); + changed |= (tmp_f32 != compp->getStartHeight(2)); + compp->setStartHeight(2, tmp_f32); + + msg->getF32("RegionInfo", "TerrainStartHeight11", tmp_f32); + changed |= (tmp_f32 != compp->getStartHeight(3)); + compp->setStartHeight(3, tmp_f32); + + + msg->getF32("RegionInfo", "TerrainHeightRange00", tmp_f32); + changed |= (tmp_f32 != compp->getHeightRange(0)); + compp->setHeightRange(0, tmp_f32); + + msg->getF32("RegionInfo", "TerrainHeightRange01", tmp_f32); + changed |= (tmp_f32 != compp->getHeightRange(1)); + compp->setHeightRange(1, tmp_f32); + + msg->getF32("RegionInfo", "TerrainHeightRange10", tmp_f32); + changed |= (tmp_f32 != compp->getHeightRange(2)); + compp->setHeightRange(2, tmp_f32); + + msg->getF32("RegionInfo", "TerrainHeightRange11", tmp_f32); + changed |= (tmp_f32 != compp->getHeightRange(3)); + compp->setHeightRange(3, tmp_f32); + + // If this is an UPDATE (params already ready, we need to regenerate + // all of our terrain stuff, by + if (compp->getParamsReady()) + { + // Update if the land changed + if (changed) + { + getLand().dirtyAllPatches(); + } + } + else + { + compp->setParamsReady(); + } + } + + + // Now that we have the name, we can load the cache file + // off disk. + loadObjectCache(); + + // After loading cache, signal that simulator can start + // sending data. + // TODO: Send all upstream viewer->sim handshake info here. + LLHost host = msg->getSender(); + msg->newMessage("RegionHandshakeReply"); + msg->nextBlock("AgentData"); + msg->addUUID("AgentID", gAgent.getID()); + msg->addUUID("SessionID", gAgent.getSessionID()); + msg->nextBlock("RegionInfo"); + + U32 flags = 0; + flags |= REGION_HANDSHAKE_SUPPORTS_SELF_APPEARANCE; + + if(sVOCacheCullingEnabled) + { + flags |= 0x00000001; //set the bit 0 to be 1 to ask sim to send all cacheable objects. + } + if(mImpl->mCacheMap.empty()) + { + flags |= 0x00000002; //set the bit 1 to be 1 to tell sim the cache file is empty, no need to send cache probes. + } + msg->addU32("Flags", flags ); + msg->sendReliable(host); + + mRegionTimer.reset(); //reset region timer. +} + +// static +void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames) +{ + capabilityNames.append("AbuseCategories"); + capabilityNames.append("AcceptFriendship"); + capabilityNames.append("AcceptGroupInvite"); // ReadOfflineMsgs recieved messages only!!! + capabilityNames.append("AgentPreferences"); + capabilityNames.append("AgentProfile"); + capabilityNames.append("AgentState"); + capabilityNames.append("AttachmentResources"); + capabilityNames.append("AvatarPickerSearch"); + capabilityNames.append("AvatarRenderInfo"); + capabilityNames.append("CharacterProperties"); + capabilityNames.append("ChatSessionRequest"); + capabilityNames.append("CopyInventoryFromNotecard"); + capabilityNames.append("CreateInventoryCategory"); + capabilityNames.append("DeclineFriendship"); + capabilityNames.append("DeclineGroupInvite"); // ReadOfflineMsgs recieved messages only!!! + capabilityNames.append("DispatchRegionInfo"); + capabilityNames.append("DirectDelivery"); + capabilityNames.append("EnvironmentSettings"); + capabilityNames.append("EstateAccess"); + capabilityNames.append("EstateChangeInfo"); + capabilityNames.append("EventQueueGet"); + capabilityNames.append("ExtEnvironment"); + + capabilityNames.append("FetchLib2"); + capabilityNames.append("FetchLibDescendents2"); + capabilityNames.append("FetchInventory2"); + capabilityNames.append("FetchInventoryDescendents2"); + capabilityNames.append("IncrementCOFVersion"); + AISAPI::getCapNames(capabilityNames); + + capabilityNames.append("InterestList"); + + capabilityNames.append("InventoryThumbnailUpload"); + capabilityNames.append("GetDisplayNames"); + capabilityNames.append("GetExperiences"); + capabilityNames.append("AgentExperiences"); + capabilityNames.append("FindExperienceByName"); + capabilityNames.append("GetExperienceInfo"); + capabilityNames.append("GetAdminExperiences"); + capabilityNames.append("GetCreatorExperiences"); + capabilityNames.append("ExperiencePreferences"); + capabilityNames.append("GroupExperiences"); + capabilityNames.append("UpdateExperience"); + capabilityNames.append("IsExperienceAdmin"); + capabilityNames.append("IsExperienceContributor"); + capabilityNames.append("RegionExperiences"); + capabilityNames.append("ExperienceQuery"); + capabilityNames.append("GetMetadata"); + capabilityNames.append("GetObjectCost"); + capabilityNames.append("GetObjectPhysicsData"); + capabilityNames.append("GroupAPIv1"); + capabilityNames.append("GroupMemberData"); + capabilityNames.append("GroupProposalBallot"); + capabilityNames.append("HomeLocation"); + capabilityNames.append("LandResources"); + capabilityNames.append("LSLSyntax"); + capabilityNames.append("MapLayer"); + capabilityNames.append("MapLayerGod"); + capabilityNames.append("MeshUploadFlag"); + capabilityNames.append("ModifyMaterialParams"); + capabilityNames.append("NavMeshGenerationStatus"); + capabilityNames.append("NewFileAgentInventory"); + capabilityNames.append("ObjectAnimation"); + capabilityNames.append("ObjectMedia"); + capabilityNames.append("ObjectMediaNavigate"); + capabilityNames.append("ObjectNavMeshProperties"); + capabilityNames.append("ParcelPropertiesUpdate"); + capabilityNames.append("ParcelVoiceInfoRequest"); + capabilityNames.append("ProductInfoRequest"); + capabilityNames.append("ProvisionVoiceAccountRequest"); + capabilityNames.append("ReadOfflineMsgs"); // Requires to respond reliably: AcceptFriendship, AcceptGroupInvite, DeclineFriendship, DeclineGroupInvite + capabilityNames.append("RegionObjects"); + capabilityNames.append("RemoteParcelRequest"); + capabilityNames.append("RenderMaterials"); + capabilityNames.append("RequestTextureDownload"); + capabilityNames.append("ResourceCostSelected"); + capabilityNames.append("RetrieveNavMeshSrc"); + capabilityNames.append("SearchStatRequest"); + capabilityNames.append("SearchStatTracking"); + capabilityNames.append("SendPostcard"); + capabilityNames.append("SendUserReport"); + capabilityNames.append("SendUserReportWithScreenshot"); + capabilityNames.append("ServerReleaseNotes"); + capabilityNames.append("SetDisplayName"); + capabilityNames.append("SimConsoleAsync"); + capabilityNames.append("SimulatorFeatures"); + capabilityNames.append("StartGroupProposal"); + capabilityNames.append("TerrainNavMeshProperties"); + capabilityNames.append("TextureStats"); + capabilityNames.append("UntrustedSimulatorMessage"); + capabilityNames.append("UpdateAgentInformation"); + capabilityNames.append("UpdateAgentLanguage"); + capabilityNames.append("UpdateAvatarAppearance"); + capabilityNames.append("UpdateGestureAgentInventory"); + capabilityNames.append("UpdateGestureTaskInventory"); + capabilityNames.append("UpdateNotecardAgentInventory"); + capabilityNames.append("UpdateNotecardTaskInventory"); + capabilityNames.append("UpdateScriptAgent"); + capabilityNames.append("UpdateScriptTask"); + capabilityNames.append("UpdateSettingsAgentInventory"); + capabilityNames.append("UpdateSettingsTaskInventory"); + capabilityNames.append("UploadAgentProfileImage"); + capabilityNames.append("UpdateMaterialAgentInventory"); + capabilityNames.append("UpdateMaterialTaskInventory"); + capabilityNames.append("UploadBakedTexture"); + capabilityNames.append("UserInfo"); + capabilityNames.append("ViewerAsset"); + capabilityNames.append("ViewerBenefits"); + capabilityNames.append("ViewerMetrics"); + capabilityNames.append("ViewerStartAuction"); + capabilityNames.append("ViewerStats"); + + // Please add new capabilities alphabetically to reduce + // merge conflicts. +} + +void LLViewerRegion::setSeedCapability(const std::string& url) +{ + if (getCapability("Seed") == url) + { + setCapabilityDebug("Seed", url); + LL_WARNS("CrossingCaps") << "Received duplicate seed capability for " << getRegionID() << ", posting to seed " << + url << LL_ENDL; + + //Instead of just returning we build up a second set of seed caps and compare them + //to the "original" seed cap received and determine why there is problem! + std::string coroname = + LLCoros::instance().launch("LLEnvironmentRequest::requestBaseCapabilitiesCompleteCoro", + boost::bind(&LLViewerRegionImpl::requestBaseCapabilitiesCompleteCoro, getHandle())); + + // setSeedCapability can be called from other coros, + // launch() acts like a suspend() + // Make sure we are still good to do + LLCoros::checkStop(); + + return; + } + + delete mImpl->mEventPoll; + mImpl->mEventPoll = NULL; + + mImpl->mCapabilities.clear(); + setCapability("Seed", url); + + std::string coroname = + LLCoros::instance().launch("LLViewerRegionImpl::requestBaseCapabilitiesCoro", + boost::bind(&LLViewerRegionImpl::requestBaseCapabilitiesCoro, getHandle())); + + // setSeedCapability can be called from other coros, + // launch() acts like a suspend() + // Make sure we are still good to do + LLCoros::checkStop(); + + LL_INFOS("AppInit", "Capabilities") << "Launching " << coroname << " requesting seed capabilities from " << url << " for region " << getRegionID() << LL_ENDL; +} + +S32 LLViewerRegion::getNumSeedCapRetries() +{ + return mImpl->mSeedCapAttempts; +} + +void LLViewerRegion::setCapability(const std::string& name, const std::string& url) +{ + if(name == "EventQueueGet") + { + delete mImpl->mEventPoll; + mImpl->mEventPoll = NULL; + mImpl->mEventPoll = new LLEventPoll(url, getHost()); + } + else if(name == "UntrustedSimulatorMessage") + { + mImpl->mHost.setUntrustedSimulatorCap(url); + } + else if (name == "SimulatorFeatures") + { + mImpl->mCapabilities["SimulatorFeatures"] = url; + requestSimulatorFeatures(); + } + else + { + mImpl->mCapabilities[name] = url; + if(name == "ViewerAsset") + { + /*==============================================================*/ + // The following inserted lines are a hack for testing MAINT-7081, + // which is why the indentation and formatting are left ugly. + const char* VIEWERASSET = getenv("VIEWERASSET"); + if (VIEWERASSET) + { + mImpl->mCapabilities[name] = VIEWERASSET; + mViewerAssetUrl = VIEWERASSET; + } + else + /*==============================================================*/ + mViewerAssetUrl = url; + } + } +} + +void LLViewerRegion::setCapabilityDebug(const std::string& name, const std::string& url) +{ + // Continue to not add certain caps, as we do in setCapability. This is so they match up when we check them later. + if ( ! ( name == "EventQueueGet" || name == "UntrustedSimulatorMessage" || name == "SimulatorFeatures" ) ) + { + mImpl->mSecondCapabilitiesTracker[name] = url; + if(name == "ViewerAsset") + { + /*==============================================================*/ + // The following inserted lines are a hack for testing MAINT-7081, + // which is why the indentation and formatting are left ugly. + const char* VIEWERASSET = getenv("VIEWERASSET"); + if (VIEWERASSET) + { + mImpl->mSecondCapabilitiesTracker[name] = VIEWERASSET; + mViewerAssetUrl = VIEWERASSET; + } + else + /*==============================================================*/ + mViewerAssetUrl = url; + } + } +} + +std::string LLViewerRegion::getCapabilityDebug(const std::string& name) const +{ + CapabilityMap::const_iterator iter = mImpl->mSecondCapabilitiesTracker.find(name); + if (iter == mImpl->mSecondCapabilitiesTracker.end()) + { + return ""; + } + + return iter->second; +} + + +bool LLViewerRegion::isSpecialCapabilityName(const std::string &name) +{ + return name == "EventQueueGet" || name == "UntrustedSimulatorMessage"; +} + +std::string LLViewerRegion::getCapability(const std::string& name) const +{ + if (!capabilitiesReceived() && (name!=std::string("Seed")) && (name!=std::string("ObjectMedia"))) + { + LL_WARNS() << "getCapability called before caps received for " << name << LL_ENDL; + } + + CapabilityMap::const_iterator iter = mImpl->mCapabilities.find(name); + if(iter == mImpl->mCapabilities.end()) + { + return ""; + } + + return iter->second; +} + +bool LLViewerRegion::isCapabilityAvailable(const std::string& name) const +{ + if (!capabilitiesReceived() && (name!=std::string("Seed")) && (name!=std::string("ObjectMedia"))) + { + LL_WARNS() << "isCapabilityAvailable called before caps received for " << name << LL_ENDL; + } + + CapabilityMap::const_iterator iter = mImpl->mCapabilities.find(name); + if(iter == mImpl->mCapabilities.end()) + { + return false; + } + + return true; +} + +bool LLViewerRegion::capabilitiesReceived() const +{ + return mCapabilitiesState == CAPABILITIES_STATE_RECEIVED; +} + +bool LLViewerRegion::capabilitiesError() const +{ + return mCapabilitiesState == CAPABILITIES_STATE_ERROR; +} + +void LLViewerRegion::setCapabilitiesReceived(bool received) +{ + mCapabilitiesState = received ? CAPABILITIES_STATE_RECEIVED : CAPABILITIES_STATE_INIT; + + // Tell interested parties that we've received capabilities, + // so that they can safely use getCapability(). + if (received) + { + mCapabilitiesReceivedSignal(getRegionID(), this); + + LLFloaterPermsDefault::sendInitialPerms(); + + // This is a single-shot signal. Forget callbacks to save resources. + mCapabilitiesReceivedSignal.disconnect_all_slots(); + + // Set the region to the desired interest list mode + setInterestListMode(gAgent.getInterestListMode()); + } +} + +void LLViewerRegion::setCapabilitiesError() +{ + mCapabilitiesState = CAPABILITIES_STATE_ERROR; +} + +boost::signals2::connection LLViewerRegion::setCapabilitiesReceivedCallback(const caps_received_signal_t::slot_type& cb) +{ + return mCapabilitiesReceivedSignal.connect(cb); +} + +void LLViewerRegion::logActiveCapabilities() const +{ + log_capabilities(mImpl->mCapabilities); +} + + +bool LLViewerRegion::requestPostCapability(const std::string &capName, LLSD &postData, httpCallback_t cbSuccess, httpCallback_t cbFailure) +{ + std::string url = getCapability(capName); + + if (url.empty()) + { + LL_WARNS("Region") << "Could not retrieve region " << getRegionID() + << " POST capability \"" << capName << "\"" << LL_ENDL; + return false; + } + + LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpPost(url, gAgent.getAgentPolicy(), postData, cbSuccess, cbFailure); + return true; +} + +bool LLViewerRegion::requestGetCapability(const std::string &capName, httpCallback_t cbSuccess, httpCallback_t cbFailure) +{ + std::string url; + + url = getCapability(capName); + + if (url.empty()) + { + LL_WARNS("Region") << "Could not retrieve region " << getRegionID() + << " GET capability \"" << capName << "\"" << LL_ENDL; + return false; + } + + LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpGet(url, gAgent.getAgentPolicy(), cbSuccess, cbFailure); + return true; +} + +bool LLViewerRegion::requestDelCapability(const std::string &capName, httpCallback_t cbSuccess, httpCallback_t cbFailure) +{ + std::string url; + + url = getCapability(capName); + + if (url.empty()) + { + LL_WARNS("Region") << "Could not retrieve region " << getRegionID() << " DEL capability \"" << capName << "\"" << LL_ENDL; + return false; + } + + LLCoreHttpUtil::HttpCoroutineAdapter::callbackHttpDel(url, gAgent.getAgentPolicy(), cbSuccess, cbFailure); + return true; +} + +void LLViewerRegion::setInterestListMode(const std::string &new_mode) +{ + if (new_mode != mInterestListMode) + { + mInterestListMode = new_mode; + + if (mInterestListMode != IL_MODE_DEFAULT && mInterestListMode != IL_MODE_360) + { + LL_WARNS("360Capture") << "Region " << getRegionID() << " setInterestListMode() invalid interest list mode: " + << mInterestListMode << ", setting to default" << LL_ENDL; + mInterestListMode = IL_MODE_DEFAULT; + } + + LLSD body; + body["mode"] = mInterestListMode; + if (requestPostCapability("InterestList", body, + [](const LLSD &response) { + LL_DEBUGS("360Capture") << "InterestList capability responded: \n" + << ll_pretty_print_sd(response) << LL_ENDL; + })) + { + LL_DEBUGS("360Capture") << "Region " << getRegionID() + << " Successfully posted an InterestList capability request with payload: \n" + << ll_pretty_print_sd(body) << LL_ENDL; + } + else + { + LL_WARNS("360Capture") << "Region " << getRegionID() + << " Unable to post an InterestList capability request with payload: \n" + << ll_pretty_print_sd(body) << LL_ENDL; + } + } + else + { + LL_DEBUGS("360Capture") << "Region " << getRegionID() << "No change, skipping Interest List mode POST to " + << new_mode << " mode" << LL_ENDL; + } +} + + +void LLViewerRegion::resetInterestList() +{ + if (requestDelCapability("InterestList", [](const LLSD &response) { + LL_DEBUGS("360Capture") << "InterestList capability DEL responded: \n" << ll_pretty_print_sd(response) << LL_ENDL; + })) + { + LL_DEBUGS("360Capture") << "Region " << getRegionID() << " Successfully reset InterestList capability" << LL_ENDL; + } + else + { + LL_WARNS("360Capture") << "Region " << getRegionID() << " Unable to DEL InterestList capability request" << LL_ENDL; + } +} + + +LLSpatialPartition *LLViewerRegion::getSpatialPartition(U32 type) +{ + if (type < mImpl->mObjectPartition.size() && type < PARTITION_VO_CACHE) + { + return (LLSpatialPartition*)mImpl->mObjectPartition[type]; + } + return NULL; +} + +LLVOCachePartition* LLViewerRegion::getVOCachePartition() +{ + if(PARTITION_VO_CACHE < mImpl->mObjectPartition.size()) + { + return (LLVOCachePartition*)mImpl->mObjectPartition[PARTITION_VO_CACHE]; + } + return NULL; +} + +// the viewer can not yet distinquish between normal- and estate-owned objects +// so we collapse these two bits and enable the UI if either are set +const U64 ALLOW_RETURN_ENCROACHING_OBJECT = REGION_FLAGS_ALLOW_RETURN_ENCROACHING_OBJECT + | REGION_FLAGS_ALLOW_RETURN_ENCROACHING_ESTATE_OBJECT; + +bool LLViewerRegion::objectIsReturnable(const LLVector3& pos, const std::vector& boxes) const +{ + return (mParcelOverlay != NULL) + && (mParcelOverlay->isOwnedSelf(pos) + || mParcelOverlay->isOwnedGroup(pos) + || (getRegionFlag(ALLOW_RETURN_ENCROACHING_OBJECT) + && mParcelOverlay->encroachesOwned(boxes)) ); +} + +bool LLViewerRegion::childrenObjectReturnable( const std::vector& boxes ) const +{ + bool result = false; + result = ( mParcelOverlay && mParcelOverlay->encroachesOnUnowned( boxes ) ) ? 1 : 0; + return result; +} + +bool LLViewerRegion::objectsCrossParcel(const std::vector& boxes) const +{ + return mParcelOverlay && mParcelOverlay->encroachesOnNearbyParcel(boxes); +} + +void LLViewerRegion::getNeighboringRegions( std::vector& uniqueRegions ) +{ + mImpl->mLandp->getNeighboringRegions( uniqueRegions ); +} +void LLViewerRegion::getNeighboringRegionsStatus( std::vector& regions ) +{ + mImpl->mLandp->getNeighboringRegionsStatus( regions ); +} +void LLViewerRegion::showReleaseNotes() +{ + std::string url = this->getCapability("ServerReleaseNotes"); + + if (url.empty()) { + // HACK haven't received the capability yet, we'll wait until + // it arives. + mReleaseNotesRequested = true; + return; + } + + LLWeb::loadURL(url); + mReleaseNotesRequested = false; +} + +std::string LLViewerRegion::getDescription() const +{ + return stringize(*this); +} + +bool LLViewerRegion::meshUploadEnabled() const +{ + return (mSimulatorFeatures.has("MeshUploadEnabled") && + mSimulatorFeatures["MeshUploadEnabled"].asBoolean()); +} + +bool LLViewerRegion::bakesOnMeshEnabled() const +{ + return (mSimulatorFeatures.has("BakesOnMeshEnabled") && + mSimulatorFeatures["BakesOnMeshEnabled"].asBoolean()); +} + +bool LLViewerRegion::meshRezEnabled() const +{ + return (mSimulatorFeatures.has("MeshRezEnabled") && + mSimulatorFeatures["MeshRezEnabled"].asBoolean()); +} + +bool LLViewerRegion::dynamicPathfindingEnabled() const +{ + return ( mSimulatorFeatures.has("DynamicPathfindingEnabled") && + mSimulatorFeatures["DynamicPathfindingEnabled"].asBoolean()); +} + +bool LLViewerRegion::avatarHoverHeightEnabled() const +{ + return ( mSimulatorFeatures.has("AvatarHoverHeightEnabled") && + mSimulatorFeatures["AvatarHoverHeightEnabled"].asBoolean()); +} +/* Static Functions */ + +void log_capabilities(const CapabilityMap &capmap) +{ + S32 count = 0; + CapabilityMap::const_iterator iter; + for (iter = capmap.begin(); iter != capmap.end(); ++iter, ++count) + { + if (!iter->second.empty()) + { + LL_INFOS() << "log_capabilities: " << iter->first << " URL is " << iter->second << LL_ENDL; + } + } + LL_INFOS() << "log_capabilities: Dumped " << count << " entries." << LL_ENDL; +} +void LLViewerRegion::resetMaterialsCapThrottle() +{ + F32 requests_per_sec = 1.0f; // original default; + if ( mSimulatorFeatures.has("RenderMaterialsCapability") + && mSimulatorFeatures["RenderMaterialsCapability"].isReal() ) + { + requests_per_sec = mSimulatorFeatures["RenderMaterialsCapability"].asReal(); + if ( requests_per_sec == 0.0f ) + { + requests_per_sec = 1.0f; + LL_WARNS("Materials") + << "region '" << getName() + << "' returned zero for RenderMaterialsCapability; using default " + << requests_per_sec << " per second" + << LL_ENDL; + } + LL_DEBUGS("Materials") << "region '" << getName() + << "' RenderMaterialsCapability " << requests_per_sec + << LL_ENDL; + } + else + { + LL_DEBUGS("Materials") + << "region '" << getName() + << "' did not return RenderMaterialsCapability, using default " + << requests_per_sec << " per second" + << LL_ENDL; + } + + mMaterialsCapThrottleTimer.resetWithExpiry( 1.0f / requests_per_sec ); +} + +U32 LLViewerRegion::getMaxMaterialsPerTransaction() const +{ + U32 max_entries = 50; // original hard coded default + if ( mSimulatorFeatures.has( "MaxMaterialsPerTransaction" ) + && mSimulatorFeatures[ "MaxMaterialsPerTransaction" ].isInteger()) + { + max_entries = mSimulatorFeatures[ "MaxMaterialsPerTransaction" ].asInteger(); + } + return max_entries; +} + +std::string LLViewerRegion::getSimHostName() +{ + if (mSimulatorFeaturesReceived) + { + return mSimulatorFeatures.has("HostName") ? mSimulatorFeatures["HostName"].asString() : getHost().getHostName(); + } + return std::string("..."); +} + +void LLViewerRegion::applyCacheMiscExtras(LLViewerObject* obj) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY; + llassert(obj); + + U32 local_id = obj->getLocalID(); + auto iter = mImpl->mGLTFOverridesLLSD.find(local_id); + if (iter != mImpl->mGLTFOverridesLLSD.end()) + { + llassert(iter->second.mGLTFMaterial.size() == iter->second.mSides.size()); + + for (auto& side : iter->second.mGLTFMaterial) + { + obj->setTEGLTFMaterialOverride(side.first, side.second); + } + } +} + diff --git a/indra/newview/llviewerregion.h b/indra/newview/llviewerregion.h index 6b0e5861af..650b52b118 100644 --- a/indra/newview/llviewerregion.h +++ b/indra/newview/llviewerregion.h @@ -1,711 +1,711 @@ -/** - * @file llviewerregion.h - * @brief Description of the LLViewerRegion class. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVIEWERREGION_H -#define LL_LLVIEWERREGION_H - -// A ViewerRegion is a class that contains a bunch of objects and surfaces -// that are in to a particular region. -#include -#include - -#include "llcorehttputil.h" -#include "llwind.h" -#include "v3dmath.h" -#include "llstring.h" -#include "llregionflags.h" -#include "lluuid.h" -#include "llweb.h" -#include "llcapabilityprovider.h" -#include "m4math.h" // LLMatrix4 -#include "llframetimer.h" -#include "llreflectionmap.h" - -// Surface id's -#define LAND 1 -#define WATER 2 -const U32 MAX_OBJECT_CACHE_ENTRIES = 50000; - -// Region handshake flags -const U32 REGION_HANDSHAKE_SUPPORTS_SELF_APPEARANCE = 1U << 2; - -// Interest list mode, -// in use by agent and region classes so must exist before region classes -const std::string IL_MODE_DEFAULT = "default"; -const std::string IL_MODE_360 = "360"; - -class LLEventPoll; -class LLVLComposition; -class LLViewerObject; -class LLMessageSystem; -class LLNetMap; -class LLViewerParcelOverlay; -class LLSurface; -class LLVOCache; -class LLVOCacheEntry; -class LLSpatialPartition; -class LLEventPump; -class LLDataPacker; -class LLDataPackerBinaryBuffer; -class LLHost; -class LLBBox; -class LLSpatialGroup; -class LLDrawable; -class LLGLTFOverrideCacheEntry; -class LLViewerRegionImpl; -class LLViewerOctreeGroup; -class LLVOCachePartition; - -class LLViewerRegion: public LLCapabilityProvider // implements this interface -{ -public: - //MUST MATCH THE ORDER OF DECLARATION IN CONSTRUCTOR - typedef enum - { - PARTITION_HUD=0, - PARTITION_TERRAIN, - PARTITION_VOIDWATER, - PARTITION_WATER, - PARTITION_TREE, - PARTITION_PARTICLE, - PARTITION_GRASS, - PARTITION_VOLUME, - PARTITION_BRIDGE, - PARTITION_AVATAR, - PARTITION_CONTROL_AV, // Animesh - PARTITION_HUD_PARTICLE, - PARTITION_VO_CACHE, - PARTITION_NONE, - NUM_PARTITIONS - } eObjectPartitions; - - typedef boost::signals2::signal caps_received_signal_t; - - LLViewerRegion(const U64 &handle, - const LLHost &host, - const U32 surface_grid_width, - const U32 patch_grid_width, - const F32 region_width_meters); - ~LLViewerRegion(); - - // Call this after you have the region name and handle. - void loadObjectCache(); - void saveObjectCache(); - - void sendMessage(); // Send the current message to this region's simulator - void sendReliableMessage(); // Send the current message to this region's simulator - - void setOriginGlobal(const LLVector3d &origin); - //void setAgentOffset(const LLVector3d &offset); - void updateRenderMatrix(); - - void setAllowDamage(bool b) { setRegionFlag(REGION_FLAGS_ALLOW_DAMAGE, b); } - void setAllowLandmark(bool b) { setRegionFlag(REGION_FLAGS_ALLOW_LANDMARK, b); } - void setAllowSetHome(bool b) { setRegionFlag(REGION_FLAGS_ALLOW_SET_HOME, b); } - void setResetHomeOnTeleport(bool b) { setRegionFlag(REGION_FLAGS_RESET_HOME_ON_TELEPORT, b); } - void setSunFixed(bool b) { setRegionFlag(REGION_FLAGS_SUN_FIXED, b); } - //void setBlockFly(bool b) { setRegionFlag(REGION_FLAGS_BLOCK_FLY, b); } Never used - void setAllowDirectTeleport(bool b) { setRegionFlag(REGION_FLAGS_ALLOW_DIRECT_TELEPORT, b); } - - - inline bool getAllowDamage() const; - inline bool getAllowLandmark() const; - inline bool getAllowSetHome() const; - inline bool getResetHomeOnTeleport() const; - inline bool getSunFixed() const; - inline bool getBlockFly() const; - inline bool getAllowDirectTeleport() const; - inline bool isPrelude() const; - inline bool getAllowTerraform() const; - inline bool getRestrictPushObject() const; - inline bool getAllowEnvironmentOverride() const; - inline bool getReleaseNotesRequested() const; - - bool isAlive(); // can become false if circuit disconnects - - void setWaterHeight(F32 water_level); - F32 getWaterHeight() const; - - bool isVoiceEnabled() const; - - void setBillableFactor(F32 billable_factor) { mBillableFactor = billable_factor; } - F32 getBillableFactor() const { return mBillableFactor; } - - // Maximum number of primitives allowed, regardless of object - // bonus factor. - U32 getMaxTasks() const { return mMaxTasks; } - void setMaxTasks(U32 max_tasks) { mMaxTasks = max_tasks; } - - // Draw lines in the dirt showing ownership. Return number of - // vertices drawn. - void renderPropertyLines(); - void renderPropertyLinesOnMinimap(F32 scale_pixels_per_meter, const F32* parcel_outline_color); - - - // Call this whenever you change the height data in the region. - // (Automatically called by LLSurfacePatch's update routine) - void dirtyHeights(); - - LLViewerParcelOverlay *getParcelOverlay() const - { return mParcelOverlay; } - - inline void setRegionFlag(U64 flag, bool on); - inline bool getRegionFlag(U64 flag) const; - void setRegionFlags(U64 flags); - U64 getRegionFlags() const { return mRegionFlags; } - - inline void setRegionProtocol(U64 protocol, bool on); - bool getRegionProtocol(U64 protocol) const; - void setRegionProtocols(U64 protocols) { mRegionProtocols = protocols; } - U64 getRegionProtocols() const { return mRegionProtocols; } - - void setTimeDilation(F32 time_dilation); - F32 getTimeDilation() const { return mTimeDilation; } - - // Origin height is at zero. - const LLVector3d &getOriginGlobal() const; - LLVector3 getOriginAgent() const; - - // Center is at the height of the water table. - const LLVector3d &getCenterGlobal() const; - LLVector3 getCenterAgent() const; - - void setRegionNameAndZone(const std::string& name_and_zone); - const std::string& getName() const { return mName; } - const std::string& getZoning() const { return mZoning; } - - void setOwner(const LLUUID& owner_id); - const LLUUID& getOwner() const; - - // Is the current agent on the estate manager list for this region? - void setIsEstateManager(bool b) { mIsEstateManager = b; } - bool isEstateManager() const { return mIsEstateManager; } - bool canManageEstate() const; - - void setSimAccess(U8 sim_access) { mSimAccess = sim_access; } - U8 getSimAccess() const { return mSimAccess; } - const std::string getSimAccessString() const; - - // Homestead-related getters; there are no setters as nobody should be - // setting them other than the individual message handler which is a member - S32 getSimClassID() const { return mClassID; } - S32 getSimCPURatio() const { return mCPURatio; } - const std::string& getSimColoName() const { return mColoName; } - const std::string& getSimProductSKU() const { return mProductSKU; } - std::string getLocalizedSimProductName() const; - - // Returns "Sandbox", "Expensive", etc. - static std::string regionFlagsToString(U64 flags); - - // Returns translated version of "Mature", "PG", "Adult", etc. - static std::string accessToString(U8 sim_access); - - // Returns "M", "PG", "A" etc. - static std::string accessToShortString(U8 sim_access); - static U8 shortStringToAccess(const std::string &sim_access); - - // Return access icon name - static std::string getAccessIcon(U8 sim_access); - - // helper function which just makes sure all interested parties - // can process the message. - static void processRegionInfo(LLMessageSystem* msg, void**); - - //check if the viewer camera is static - static bool isViewerCameraStatic(); - static void calcNewObjectCreationThrottle(); - - void setCacheID(const LLUUID& id); - - F32 getWidth() const { return mWidth; } - - // regions are expensive to release, this function gradually releases cache from memory - static void idleCleanup(F32 max_update_time); - - void idleUpdate(F32 max_update_time); - void lightIdleUpdate(); - bool addVisibleGroup(LLViewerOctreeGroup* group); - void addVisibleChildCacheEntry(LLVOCacheEntry* parent, LLVOCacheEntry* child); - void addActiveCacheEntry(LLVOCacheEntry* entry); - void removeActiveCacheEntry(LLVOCacheEntry* entry, LLDrawable* drawablep); - void killCacheEntry(U32 local_id); //physically delete the cache entry - - // Like idleUpdate, but forces everything to complete regardless of - // how long it takes. - void forceUpdate(); - - void connectNeighbor(LLViewerRegion *neighborp, U32 direction); - - void updateNetStats(); - - U32 getPacketsLost() const; - - S32 getHttpResponderID() const; - - // Get/set named capability URLs for this region. - void setSeedCapability(const std::string& url); - S32 getNumSeedCapRetries(); - void setCapability(const std::string& name, const std::string& url); - void setCapabilityDebug(const std::string& name, const std::string& url); - bool isCapabilityAvailable(const std::string& name) const; - // implements LLCapabilityProvider - virtual std::string getCapability(const std::string& name) const; - std::string getCapabilityDebug(const std::string& name) const; - - - // has region received its final (not seed) capability list? - bool capabilitiesReceived() const; - bool capabilitiesError() const; - void setCapabilitiesReceived(bool received); - void setCapabilitiesError(); - boost::signals2::connection setCapabilitiesReceivedCallback(const caps_received_signal_t::slot_type& cb); - - static bool isSpecialCapabilityName(const std::string &name); - void logActiveCapabilities() const; - - // Utilities to post and get via - // HTTP using the agent's policy settings and headers. - typedef LLCoreHttpUtil::HttpCoroutineAdapter::completionCallback_t httpCallback_t; - bool requestPostCapability(const std::string &capName, - LLSD &postData, - httpCallback_t cbSuccess = NULL, - httpCallback_t cbFailure = NULL); - bool requestGetCapability(const std::string &capName, httpCallback_t cbSuccess = NULL, httpCallback_t cbFailure = NULL); - bool requestDelCapability(const std::string &capName, httpCallback_t cbSuccess = NULL, httpCallback_t cbFailure = NULL); - - /// implements LLCapabilityProvider - /*virtual*/ const LLHost& getHost() const; - const U64 &getHandle() const { return mHandle; } - - LLSurface &getLand() const; - - // set and get the region id - const LLUUID& getRegionID() const; - void setRegionID(const LLUUID& region_id); - - bool pointInRegionGlobal(const LLVector3d &point_global) const; - LLVector3 getPosRegionFromGlobal(const LLVector3d &point_global) const; - LLVector3 getPosRegionFromAgent(const LLVector3 &agent_pos) const; - LLVector3 getPosAgentFromRegion(const LLVector3 ®ion_pos) const; - LLVector3d getPosGlobalFromRegion(const LLVector3 &offset) const; - - LLVLComposition *getComposition() const; - F32 getCompositionXY(const S32 x, const S32 y) const; - - bool isOwnedSelf(const LLVector3& pos); - - // Owned by a group you belong to? (officer OR member) - bool isOwnedGroup(const LLVector3& pos); - - // deal with map object updates in the world. - void updateCoarseLocations(LLMessageSystem* msg); - - F32 getLandHeightRegion(const LLVector3& region_pos); - - U8 getCentralBakeVersion() { return mCentralBakeVersion; } - - void getInfo(LLSD& info); - - bool meshRezEnabled() const; - bool meshUploadEnabled() const; - - bool bakesOnMeshEnabled() const; - - // has region received its simulator features list? Requires an additional query after caps received. - void requestSimulatorFeatures(); - void setSimulatorFeaturesReceived(bool); - bool simulatorFeaturesReceived() const; - boost::signals2::connection setSimulatorFeaturesReceivedCallback(const caps_received_signal_t::slot_type& cb); - - void getSimulatorFeatures(LLSD& info) const; - void setSimulatorFeatures(const LLSD& info); - - - bool dynamicPathfindingEnabled() const; - - bool avatarHoverHeightEnabled() const; - - typedef enum - { - CACHE_MISS_TYPE_TOTAL = 0, // total cache miss - object not in cache - CACHE_MISS_TYPE_CRC, // object in cache, but CRC doesn't match - CACHE_MISS_TYPE_NONE // not a miss: cache hit - } eCacheMissType; - - typedef enum - { - CACHE_UPDATE_DUPE = 0, - CACHE_UPDATE_CHANGED, - CACHE_UPDATE_ADDED, - CACHE_UPDATE_REPLACED - } eCacheUpdateResult; - - // handle a full update message - eCacheUpdateResult cacheFullUpdate(LLDataPackerBinaryBuffer &dp, U32 flags); - eCacheUpdateResult cacheFullUpdate(LLViewerObject* objectp, LLDataPackerBinaryBuffer &dp, U32 flags); - - void cacheFullUpdateGLTFOverride(const LLGLTFOverrideCacheEntry &override_data); - - LLVOCacheEntry* getCacheEntryForOctree(U32 local_id); - LLVOCacheEntry* getCacheEntry(U32 local_id, bool valid = true); - bool probeCache(U32 local_id, U32 crc, U32 flags, U8 &cache_miss_type); - U64 getRegionCacheHitCount() { return mRegionCacheHitCount; } - U64 getRegionCacheMissCount() { return mRegionCacheMissCount; } - void requestCacheMisses(); - void addCacheMissFull(const U32 local_id); - //update object cache if the object receives a full-update or terse update - LLViewerObject* updateCacheEntry(U32 local_id, LLViewerObject* objectp); - void findOrphans(U32 parent_id); - void clearCachedVisibleObjects(); - void dumpCache(); - - void unpackRegionHandshake(); - - void calculateCenterGlobal(); - void calculateCameraDistance(); - - friend std::ostream& operator<<(std::ostream &s, const LLViewerRegion ®ion); - /// implements LLCapabilityProvider - virtual std::string getDescription() const; - std::string getViewerAssetUrl() const { return mViewerAssetUrl; } - - U32 getNumOfVisibleGroups() const; - U32 getNumOfActiveCachedObjects() const; - LLSpatialPartition* getSpatialPartition(U32 type); - LLVOCachePartition* getVOCachePartition(); - - bool objectIsReturnable(const LLVector3& pos, const std::vector& boxes) const; - bool childrenObjectReturnable( const std::vector& boxes ) const; - bool objectsCrossParcel(const std::vector& boxes) const; - - void getNeighboringRegions( std::vector& uniqueRegions ); - void getNeighboringRegionsStatus( std::vector& regions ); - const LLViewerRegionImpl * getRegionImpl() const { return mImpl; } - LLViewerRegionImpl * getRegionImplNC() { return mImpl; } - - // implements the materials capability throttle - bool materialsCapThrottled() const { return !mMaterialsCapThrottleTimer.hasExpired(); } - void resetMaterialsCapThrottle(); - - U32 getMaxMaterialsPerTransaction() const; - - void removeFromCreatedList(U32 local_id); - void addToCreatedList(U32 local_id); - - bool isPaused() const {return mPaused;} - S32 getLastUpdate() const {return mLastUpdate;} - - std::string getSimHostName(); - - static bool isNewObjectCreationThrottleDisabled() {return sNewObjectCreationThrottle < 0;} - - // rebuild reflection probe list - void updateReflectionProbes(); - -private: - void addToVOCacheTree(LLVOCacheEntry* entry); - LLViewerObject* addNewObject(LLVOCacheEntry* entry); - void killObject(LLVOCacheEntry* entry, std::vector& delete_list); //adds entry into list if it is safe to move into cache - void removeFromVOCacheTree(LLVOCacheEntry* entry); - void killCacheEntry(LLVOCacheEntry* entry, bool for_rendering = false); //physically delete the cache entry - void killInvisibleObjects(F32 max_time); - void createVisibleObjects(F32 max_time); - void updateVisibleEntries(F32 max_time); //update visible entries - - void addCacheMiss(U32 id, LLViewerRegion::eCacheMissType miss_type); - void decodeBoundingInfo(LLVOCacheEntry* entry); - bool isNonCacheableObjectCreated(U32 local_id); - -public: - void applyCacheMiscExtras(LLViewerObject* obj); - - struct CompareDistance - { - bool operator()(const LLViewerRegion* const& lhs, const LLViewerRegion* const& rhs) - { - return lhs->mCameraDistanceSquared < rhs->mCameraDistanceSquared; - } - }; - - void showReleaseNotes(); - -protected: - void disconnectAllNeighbors(); - void initStats(); - -public: - LLWind mWind; - LLViewerParcelOverlay *mParcelOverlay; - - F32Bits mBitsReceived; - F32 mPacketsReceived; - - LLMatrix4 mRenderMatrix; - - // These arrays are maintained in parallel. Ideally they'd be combined into a - // single array of an aggrigate data type but for compatibility with the old - // messaging system in which the previous message only sends and parses the - // positions stored in the first array so they're maintained separately until - // we stop supporting the old CoarseLocationUpdate message. - std::vector mMapAvatars; - std::vector mMapAvatarIDs; - - static bool sVOCacheCullingEnabled; //vo cache culling enabled or not. - static S32 sLastCameraUpdated; - - LLFrameTimer & getRenderInfoRequestTimer() { return mRenderInfoRequestTimer; }; - LLFrameTimer & getRenderInfoReportTimer() { return mRenderInfoReportTimer; }; - - struct CompareRegionByLastUpdate - { - bool operator()(const LLViewerRegion* const& lhs, const LLViewerRegion* const& rhs) const - { - S32 lpa = lhs->getLastUpdate(); - S32 rpa = rhs->getLastUpdate(); - - //small mLastUpdate first - if(lpa < rpa) - { - return true; - } - else if(lpa > rpa) - { - return false; - } - else - { - return lhs < rhs; - } - } - }; - typedef std::set region_priority_list_t; - - void setInterestListMode(const std::string & new_mode); - const std::string & getInterestListMode() const { return mInterestListMode; } - - void resetInterestList(); - - private: - static S32 sNewObjectCreationThrottle; - LLViewerRegionImpl * mImpl; - LLFrameTimer mRegionTimer; - - F32 mWidth; // Width of region on a side (meters) - U64 mHandle; - F32 mTimeDilation; // time dilation of physics simulation on simulator - S32 mLastUpdate; //last time called idleUpdate() - - // simulator name - std::string mName; - std::string mZoning; - - // Is this agent on the estate managers list for this region? - bool mIsEstateManager; - - U32 mPacketsIn; - U32Bits mBitsIn, - mLastBitsIn; - U32 mLastPacketsIn; - U32 mPacketsOut; - U32 mLastPacketsOut; - S32 mPacketsLost; - S32 mLastPacketsLost; - U32Milliseconds mPingDelay; - F32 mDeltaTime; // Time since last measurement of lastPackets, Bits, etc - - U64 mRegionFlags; // includes damage flags - U64 mRegionProtocols; // protocols supported by this region - U8 mSimAccess; - F32 mBillableFactor; - U32 mMaxTasks; // max prim count - F32 mCameraDistanceSquared; // updated once per frame - U8 mCentralBakeVersion; - - LLVOCacheEntry* mLastVisitedEntry; - U32 mInvisibilityCheckHistory; - - // Information for Homestead / CR-53 - S32 mClassID; - S32 mCPURatio; - - std::string mColoName; - std::string mProductSKU; - std::string mProductName; - std::string mViewerAssetUrl ; - - // Maps local ids to cache entries. - // Regions can have order 10,000 objects, so assume - // a structure of size 2^14 = 16,000 - bool mCacheLoaded; - bool mCacheDirty; - bool mAlive; // can become false if circuit disconnects - bool mSimulatorFeaturesReceived; - bool mReleaseNotesRequested; - bool mDead; //if true, this region is in the process of deleting. - bool mPaused; //pause processing the objects in the region - - typedef enum - { - CAPABILITIES_STATE_INIT = 0, - CAPABILITIES_STATE_ERROR, - CAPABILITIES_STATE_RECEIVED - } eCababilitiesState; - - eCababilitiesState mCapabilitiesState; - - typedef std::map > orphan_list_t; - orphan_list_t mOrphanMap; - - class CacheMissItem - { - public: - CacheMissItem(U32 id, LLViewerRegion::eCacheMissType miss_type) : mID(id), mType(miss_type) {} - - U32 mID; //local object id - LLViewerRegion::eCacheMissType mType; // cache miss type - - typedef std::list cache_miss_list_t; - }; - CacheMissItem::cache_miss_list_t mCacheMissList; - U64 mRegionCacheHitCount; - U64 mRegionCacheMissCount; - - caps_received_signal_t mCapabilitiesReceivedSignal; - caps_received_signal_t mSimulatorFeaturesReceivedSignal; - - LLSD mSimulatorFeatures; - - typedef std::map > vocache_entry_map_t; - static vocache_entry_map_t sRegionCacheCleanup; - - // the materials capability throttle - LLFrameTimer mMaterialsCapThrottleTimer; - LLFrameTimer mRenderInfoRequestTimer; - LLFrameTimer mRenderInfoReportTimer; - - // how the server interest list works - std::string mInterestListMode; - - // list of reflection maps being managed by this llviewer region - std::vector > mReflectionMaps; - -}; - -inline bool LLViewerRegion::getRegionProtocol(U64 protocol) const -{ - return ((mRegionProtocols & protocol) != 0); -} - -inline void LLViewerRegion::setRegionProtocol(U64 protocol, bool on) -{ - if (on) - { - mRegionProtocols |= protocol; - } - else - { - mRegionProtocols &= ~protocol; - } -} - -inline bool LLViewerRegion::getRegionFlag(U64 flag) const -{ - return ((mRegionFlags & flag) != 0); -} - -inline void LLViewerRegion::setRegionFlag(U64 flag, bool on) -{ - if (on) - { - mRegionFlags |= flag; - } - else - { - mRegionFlags &= ~flag; - } -} - -inline bool LLViewerRegion::getAllowDamage() const -{ - return ((mRegionFlags & REGION_FLAGS_ALLOW_DAMAGE) !=0); -} - -inline bool LLViewerRegion::getAllowLandmark() const -{ - return ((mRegionFlags & REGION_FLAGS_ALLOW_LANDMARK) !=0); -} - -inline bool LLViewerRegion::getAllowSetHome() const -{ - return ((mRegionFlags & REGION_FLAGS_ALLOW_SET_HOME) != 0); -} - -inline bool LLViewerRegion::getResetHomeOnTeleport() const -{ - return ((mRegionFlags & REGION_FLAGS_RESET_HOME_ON_TELEPORT) !=0); -} - -inline bool LLViewerRegion::getSunFixed() const -{ - return ((mRegionFlags & REGION_FLAGS_SUN_FIXED) !=0); -} - -inline bool LLViewerRegion::getBlockFly() const -{ - return ((mRegionFlags & REGION_FLAGS_BLOCK_FLY) !=0); -} - -inline bool LLViewerRegion::getAllowDirectTeleport() const -{ - return ((mRegionFlags & REGION_FLAGS_ALLOW_DIRECT_TELEPORT) !=0); -} - -inline bool LLViewerRegion::isPrelude() const -{ - return is_prelude( mRegionFlags ); -} - -inline bool LLViewerRegion::getAllowTerraform() const -{ - return ((mRegionFlags & REGION_FLAGS_BLOCK_TERRAFORM) == 0); -} - -inline bool LLViewerRegion::getRestrictPushObject() const -{ - return ((mRegionFlags & REGION_FLAGS_RESTRICT_PUSHOBJECT) != 0); -} - -inline bool LLViewerRegion::getAllowEnvironmentOverride() const -{ - return ((mRegionFlags & REGION_FLAGS_ALLOW_ENVIRONMENT_OVERRIDE) != 0); -} - -inline bool LLViewerRegion::getReleaseNotesRequested() const -{ - return mReleaseNotesRequested; -} - -#endif +/** + * @file llviewerregion.h + * @brief Description of the LLViewerRegion class. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVIEWERREGION_H +#define LL_LLVIEWERREGION_H + +// A ViewerRegion is a class that contains a bunch of objects and surfaces +// that are in to a particular region. +#include +#include + +#include "llcorehttputil.h" +#include "llwind.h" +#include "v3dmath.h" +#include "llstring.h" +#include "llregionflags.h" +#include "lluuid.h" +#include "llweb.h" +#include "llcapabilityprovider.h" +#include "m4math.h" // LLMatrix4 +#include "llframetimer.h" +#include "llreflectionmap.h" + +// Surface id's +#define LAND 1 +#define WATER 2 +const U32 MAX_OBJECT_CACHE_ENTRIES = 50000; + +// Region handshake flags +const U32 REGION_HANDSHAKE_SUPPORTS_SELF_APPEARANCE = 1U << 2; + +// Interest list mode, +// in use by agent and region classes so must exist before region classes +const std::string IL_MODE_DEFAULT = "default"; +const std::string IL_MODE_360 = "360"; + +class LLEventPoll; +class LLVLComposition; +class LLViewerObject; +class LLMessageSystem; +class LLNetMap; +class LLViewerParcelOverlay; +class LLSurface; +class LLVOCache; +class LLVOCacheEntry; +class LLSpatialPartition; +class LLEventPump; +class LLDataPacker; +class LLDataPackerBinaryBuffer; +class LLHost; +class LLBBox; +class LLSpatialGroup; +class LLDrawable; +class LLGLTFOverrideCacheEntry; +class LLViewerRegionImpl; +class LLViewerOctreeGroup; +class LLVOCachePartition; + +class LLViewerRegion: public LLCapabilityProvider // implements this interface +{ +public: + //MUST MATCH THE ORDER OF DECLARATION IN CONSTRUCTOR + typedef enum + { + PARTITION_HUD=0, + PARTITION_TERRAIN, + PARTITION_VOIDWATER, + PARTITION_WATER, + PARTITION_TREE, + PARTITION_PARTICLE, + PARTITION_GRASS, + PARTITION_VOLUME, + PARTITION_BRIDGE, + PARTITION_AVATAR, + PARTITION_CONTROL_AV, // Animesh + PARTITION_HUD_PARTICLE, + PARTITION_VO_CACHE, + PARTITION_NONE, + NUM_PARTITIONS + } eObjectPartitions; + + typedef boost::signals2::signal caps_received_signal_t; + + LLViewerRegion(const U64 &handle, + const LLHost &host, + const U32 surface_grid_width, + const U32 patch_grid_width, + const F32 region_width_meters); + ~LLViewerRegion(); + + // Call this after you have the region name and handle. + void loadObjectCache(); + void saveObjectCache(); + + void sendMessage(); // Send the current message to this region's simulator + void sendReliableMessage(); // Send the current message to this region's simulator + + void setOriginGlobal(const LLVector3d &origin); + //void setAgentOffset(const LLVector3d &offset); + void updateRenderMatrix(); + + void setAllowDamage(bool b) { setRegionFlag(REGION_FLAGS_ALLOW_DAMAGE, b); } + void setAllowLandmark(bool b) { setRegionFlag(REGION_FLAGS_ALLOW_LANDMARK, b); } + void setAllowSetHome(bool b) { setRegionFlag(REGION_FLAGS_ALLOW_SET_HOME, b); } + void setResetHomeOnTeleport(bool b) { setRegionFlag(REGION_FLAGS_RESET_HOME_ON_TELEPORT, b); } + void setSunFixed(bool b) { setRegionFlag(REGION_FLAGS_SUN_FIXED, b); } + //void setBlockFly(bool b) { setRegionFlag(REGION_FLAGS_BLOCK_FLY, b); } Never used + void setAllowDirectTeleport(bool b) { setRegionFlag(REGION_FLAGS_ALLOW_DIRECT_TELEPORT, b); } + + + inline bool getAllowDamage() const; + inline bool getAllowLandmark() const; + inline bool getAllowSetHome() const; + inline bool getResetHomeOnTeleport() const; + inline bool getSunFixed() const; + inline bool getBlockFly() const; + inline bool getAllowDirectTeleport() const; + inline bool isPrelude() const; + inline bool getAllowTerraform() const; + inline bool getRestrictPushObject() const; + inline bool getAllowEnvironmentOverride() const; + inline bool getReleaseNotesRequested() const; + + bool isAlive(); // can become false if circuit disconnects + + void setWaterHeight(F32 water_level); + F32 getWaterHeight() const; + + bool isVoiceEnabled() const; + + void setBillableFactor(F32 billable_factor) { mBillableFactor = billable_factor; } + F32 getBillableFactor() const { return mBillableFactor; } + + // Maximum number of primitives allowed, regardless of object + // bonus factor. + U32 getMaxTasks() const { return mMaxTasks; } + void setMaxTasks(U32 max_tasks) { mMaxTasks = max_tasks; } + + // Draw lines in the dirt showing ownership. Return number of + // vertices drawn. + void renderPropertyLines(); + void renderPropertyLinesOnMinimap(F32 scale_pixels_per_meter, const F32* parcel_outline_color); + + + // Call this whenever you change the height data in the region. + // (Automatically called by LLSurfacePatch's update routine) + void dirtyHeights(); + + LLViewerParcelOverlay *getParcelOverlay() const + { return mParcelOverlay; } + + inline void setRegionFlag(U64 flag, bool on); + inline bool getRegionFlag(U64 flag) const; + void setRegionFlags(U64 flags); + U64 getRegionFlags() const { return mRegionFlags; } + + inline void setRegionProtocol(U64 protocol, bool on); + bool getRegionProtocol(U64 protocol) const; + void setRegionProtocols(U64 protocols) { mRegionProtocols = protocols; } + U64 getRegionProtocols() const { return mRegionProtocols; } + + void setTimeDilation(F32 time_dilation); + F32 getTimeDilation() const { return mTimeDilation; } + + // Origin height is at zero. + const LLVector3d &getOriginGlobal() const; + LLVector3 getOriginAgent() const; + + // Center is at the height of the water table. + const LLVector3d &getCenterGlobal() const; + LLVector3 getCenterAgent() const; + + void setRegionNameAndZone(const std::string& name_and_zone); + const std::string& getName() const { return mName; } + const std::string& getZoning() const { return mZoning; } + + void setOwner(const LLUUID& owner_id); + const LLUUID& getOwner() const; + + // Is the current agent on the estate manager list for this region? + void setIsEstateManager(bool b) { mIsEstateManager = b; } + bool isEstateManager() const { return mIsEstateManager; } + bool canManageEstate() const; + + void setSimAccess(U8 sim_access) { mSimAccess = sim_access; } + U8 getSimAccess() const { return mSimAccess; } + const std::string getSimAccessString() const; + + // Homestead-related getters; there are no setters as nobody should be + // setting them other than the individual message handler which is a member + S32 getSimClassID() const { return mClassID; } + S32 getSimCPURatio() const { return mCPURatio; } + const std::string& getSimColoName() const { return mColoName; } + const std::string& getSimProductSKU() const { return mProductSKU; } + std::string getLocalizedSimProductName() const; + + // Returns "Sandbox", "Expensive", etc. + static std::string regionFlagsToString(U64 flags); + + // Returns translated version of "Mature", "PG", "Adult", etc. + static std::string accessToString(U8 sim_access); + + // Returns "M", "PG", "A" etc. + static std::string accessToShortString(U8 sim_access); + static U8 shortStringToAccess(const std::string &sim_access); + + // Return access icon name + static std::string getAccessIcon(U8 sim_access); + + // helper function which just makes sure all interested parties + // can process the message. + static void processRegionInfo(LLMessageSystem* msg, void**); + + //check if the viewer camera is static + static bool isViewerCameraStatic(); + static void calcNewObjectCreationThrottle(); + + void setCacheID(const LLUUID& id); + + F32 getWidth() const { return mWidth; } + + // regions are expensive to release, this function gradually releases cache from memory + static void idleCleanup(F32 max_update_time); + + void idleUpdate(F32 max_update_time); + void lightIdleUpdate(); + bool addVisibleGroup(LLViewerOctreeGroup* group); + void addVisibleChildCacheEntry(LLVOCacheEntry* parent, LLVOCacheEntry* child); + void addActiveCacheEntry(LLVOCacheEntry* entry); + void removeActiveCacheEntry(LLVOCacheEntry* entry, LLDrawable* drawablep); + void killCacheEntry(U32 local_id); //physically delete the cache entry + + // Like idleUpdate, but forces everything to complete regardless of + // how long it takes. + void forceUpdate(); + + void connectNeighbor(LLViewerRegion *neighborp, U32 direction); + + void updateNetStats(); + + U32 getPacketsLost() const; + + S32 getHttpResponderID() const; + + // Get/set named capability URLs for this region. + void setSeedCapability(const std::string& url); + S32 getNumSeedCapRetries(); + void setCapability(const std::string& name, const std::string& url); + void setCapabilityDebug(const std::string& name, const std::string& url); + bool isCapabilityAvailable(const std::string& name) const; + // implements LLCapabilityProvider + virtual std::string getCapability(const std::string& name) const; + std::string getCapabilityDebug(const std::string& name) const; + + + // has region received its final (not seed) capability list? + bool capabilitiesReceived() const; + bool capabilitiesError() const; + void setCapabilitiesReceived(bool received); + void setCapabilitiesError(); + boost::signals2::connection setCapabilitiesReceivedCallback(const caps_received_signal_t::slot_type& cb); + + static bool isSpecialCapabilityName(const std::string &name); + void logActiveCapabilities() const; + + // Utilities to post and get via + // HTTP using the agent's policy settings and headers. + typedef LLCoreHttpUtil::HttpCoroutineAdapter::completionCallback_t httpCallback_t; + bool requestPostCapability(const std::string &capName, + LLSD &postData, + httpCallback_t cbSuccess = NULL, + httpCallback_t cbFailure = NULL); + bool requestGetCapability(const std::string &capName, httpCallback_t cbSuccess = NULL, httpCallback_t cbFailure = NULL); + bool requestDelCapability(const std::string &capName, httpCallback_t cbSuccess = NULL, httpCallback_t cbFailure = NULL); + + /// implements LLCapabilityProvider + /*virtual*/ const LLHost& getHost() const; + const U64 &getHandle() const { return mHandle; } + + LLSurface &getLand() const; + + // set and get the region id + const LLUUID& getRegionID() const; + void setRegionID(const LLUUID& region_id); + + bool pointInRegionGlobal(const LLVector3d &point_global) const; + LLVector3 getPosRegionFromGlobal(const LLVector3d &point_global) const; + LLVector3 getPosRegionFromAgent(const LLVector3 &agent_pos) const; + LLVector3 getPosAgentFromRegion(const LLVector3 ®ion_pos) const; + LLVector3d getPosGlobalFromRegion(const LLVector3 &offset) const; + + LLVLComposition *getComposition() const; + F32 getCompositionXY(const S32 x, const S32 y) const; + + bool isOwnedSelf(const LLVector3& pos); + + // Owned by a group you belong to? (officer OR member) + bool isOwnedGroup(const LLVector3& pos); + + // deal with map object updates in the world. + void updateCoarseLocations(LLMessageSystem* msg); + + F32 getLandHeightRegion(const LLVector3& region_pos); + + U8 getCentralBakeVersion() { return mCentralBakeVersion; } + + void getInfo(LLSD& info); + + bool meshRezEnabled() const; + bool meshUploadEnabled() const; + + bool bakesOnMeshEnabled() const; + + // has region received its simulator features list? Requires an additional query after caps received. + void requestSimulatorFeatures(); + void setSimulatorFeaturesReceived(bool); + bool simulatorFeaturesReceived() const; + boost::signals2::connection setSimulatorFeaturesReceivedCallback(const caps_received_signal_t::slot_type& cb); + + void getSimulatorFeatures(LLSD& info) const; + void setSimulatorFeatures(const LLSD& info); + + + bool dynamicPathfindingEnabled() const; + + bool avatarHoverHeightEnabled() const; + + typedef enum + { + CACHE_MISS_TYPE_TOTAL = 0, // total cache miss - object not in cache + CACHE_MISS_TYPE_CRC, // object in cache, but CRC doesn't match + CACHE_MISS_TYPE_NONE // not a miss: cache hit + } eCacheMissType; + + typedef enum + { + CACHE_UPDATE_DUPE = 0, + CACHE_UPDATE_CHANGED, + CACHE_UPDATE_ADDED, + CACHE_UPDATE_REPLACED + } eCacheUpdateResult; + + // handle a full update message + eCacheUpdateResult cacheFullUpdate(LLDataPackerBinaryBuffer &dp, U32 flags); + eCacheUpdateResult cacheFullUpdate(LLViewerObject* objectp, LLDataPackerBinaryBuffer &dp, U32 flags); + + void cacheFullUpdateGLTFOverride(const LLGLTFOverrideCacheEntry &override_data); + + LLVOCacheEntry* getCacheEntryForOctree(U32 local_id); + LLVOCacheEntry* getCacheEntry(U32 local_id, bool valid = true); + bool probeCache(U32 local_id, U32 crc, U32 flags, U8 &cache_miss_type); + U64 getRegionCacheHitCount() { return mRegionCacheHitCount; } + U64 getRegionCacheMissCount() { return mRegionCacheMissCount; } + void requestCacheMisses(); + void addCacheMissFull(const U32 local_id); + //update object cache if the object receives a full-update or terse update + LLViewerObject* updateCacheEntry(U32 local_id, LLViewerObject* objectp); + void findOrphans(U32 parent_id); + void clearCachedVisibleObjects(); + void dumpCache(); + + void unpackRegionHandshake(); + + void calculateCenterGlobal(); + void calculateCameraDistance(); + + friend std::ostream& operator<<(std::ostream &s, const LLViewerRegion ®ion); + /// implements LLCapabilityProvider + virtual std::string getDescription() const; + std::string getViewerAssetUrl() const { return mViewerAssetUrl; } + + U32 getNumOfVisibleGroups() const; + U32 getNumOfActiveCachedObjects() const; + LLSpatialPartition* getSpatialPartition(U32 type); + LLVOCachePartition* getVOCachePartition(); + + bool objectIsReturnable(const LLVector3& pos, const std::vector& boxes) const; + bool childrenObjectReturnable( const std::vector& boxes ) const; + bool objectsCrossParcel(const std::vector& boxes) const; + + void getNeighboringRegions( std::vector& uniqueRegions ); + void getNeighboringRegionsStatus( std::vector& regions ); + const LLViewerRegionImpl * getRegionImpl() const { return mImpl; } + LLViewerRegionImpl * getRegionImplNC() { return mImpl; } + + // implements the materials capability throttle + bool materialsCapThrottled() const { return !mMaterialsCapThrottleTimer.hasExpired(); } + void resetMaterialsCapThrottle(); + + U32 getMaxMaterialsPerTransaction() const; + + void removeFromCreatedList(U32 local_id); + void addToCreatedList(U32 local_id); + + bool isPaused() const {return mPaused;} + S32 getLastUpdate() const {return mLastUpdate;} + + std::string getSimHostName(); + + static bool isNewObjectCreationThrottleDisabled() {return sNewObjectCreationThrottle < 0;} + + // rebuild reflection probe list + void updateReflectionProbes(); + +private: + void addToVOCacheTree(LLVOCacheEntry* entry); + LLViewerObject* addNewObject(LLVOCacheEntry* entry); + void killObject(LLVOCacheEntry* entry, std::vector& delete_list); //adds entry into list if it is safe to move into cache + void removeFromVOCacheTree(LLVOCacheEntry* entry); + void killCacheEntry(LLVOCacheEntry* entry, bool for_rendering = false); //physically delete the cache entry + void killInvisibleObjects(F32 max_time); + void createVisibleObjects(F32 max_time); + void updateVisibleEntries(F32 max_time); //update visible entries + + void addCacheMiss(U32 id, LLViewerRegion::eCacheMissType miss_type); + void decodeBoundingInfo(LLVOCacheEntry* entry); + bool isNonCacheableObjectCreated(U32 local_id); + +public: + void applyCacheMiscExtras(LLViewerObject* obj); + + struct CompareDistance + { + bool operator()(const LLViewerRegion* const& lhs, const LLViewerRegion* const& rhs) + { + return lhs->mCameraDistanceSquared < rhs->mCameraDistanceSquared; + } + }; + + void showReleaseNotes(); + +protected: + void disconnectAllNeighbors(); + void initStats(); + +public: + LLWind mWind; + LLViewerParcelOverlay *mParcelOverlay; + + F32Bits mBitsReceived; + F32 mPacketsReceived; + + LLMatrix4 mRenderMatrix; + + // These arrays are maintained in parallel. Ideally they'd be combined into a + // single array of an aggrigate data type but for compatibility with the old + // messaging system in which the previous message only sends and parses the + // positions stored in the first array so they're maintained separately until + // we stop supporting the old CoarseLocationUpdate message. + std::vector mMapAvatars; + std::vector mMapAvatarIDs; + + static bool sVOCacheCullingEnabled; //vo cache culling enabled or not. + static S32 sLastCameraUpdated; + + LLFrameTimer & getRenderInfoRequestTimer() { return mRenderInfoRequestTimer; }; + LLFrameTimer & getRenderInfoReportTimer() { return mRenderInfoReportTimer; }; + + struct CompareRegionByLastUpdate + { + bool operator()(const LLViewerRegion* const& lhs, const LLViewerRegion* const& rhs) const + { + S32 lpa = lhs->getLastUpdate(); + S32 rpa = rhs->getLastUpdate(); + + //small mLastUpdate first + if(lpa < rpa) + { + return true; + } + else if(lpa > rpa) + { + return false; + } + else + { + return lhs < rhs; + } + } + }; + typedef std::set region_priority_list_t; + + void setInterestListMode(const std::string & new_mode); + const std::string & getInterestListMode() const { return mInterestListMode; } + + void resetInterestList(); + + private: + static S32 sNewObjectCreationThrottle; + LLViewerRegionImpl * mImpl; + LLFrameTimer mRegionTimer; + + F32 mWidth; // Width of region on a side (meters) + U64 mHandle; + F32 mTimeDilation; // time dilation of physics simulation on simulator + S32 mLastUpdate; //last time called idleUpdate() + + // simulator name + std::string mName; + std::string mZoning; + + // Is this agent on the estate managers list for this region? + bool mIsEstateManager; + + U32 mPacketsIn; + U32Bits mBitsIn, + mLastBitsIn; + U32 mLastPacketsIn; + U32 mPacketsOut; + U32 mLastPacketsOut; + S32 mPacketsLost; + S32 mLastPacketsLost; + U32Milliseconds mPingDelay; + F32 mDeltaTime; // Time since last measurement of lastPackets, Bits, etc + + U64 mRegionFlags; // includes damage flags + U64 mRegionProtocols; // protocols supported by this region + U8 mSimAccess; + F32 mBillableFactor; + U32 mMaxTasks; // max prim count + F32 mCameraDistanceSquared; // updated once per frame + U8 mCentralBakeVersion; + + LLVOCacheEntry* mLastVisitedEntry; + U32 mInvisibilityCheckHistory; + + // Information for Homestead / CR-53 + S32 mClassID; + S32 mCPURatio; + + std::string mColoName; + std::string mProductSKU; + std::string mProductName; + std::string mViewerAssetUrl ; + + // Maps local ids to cache entries. + // Regions can have order 10,000 objects, so assume + // a structure of size 2^14 = 16,000 + bool mCacheLoaded; + bool mCacheDirty; + bool mAlive; // can become false if circuit disconnects + bool mSimulatorFeaturesReceived; + bool mReleaseNotesRequested; + bool mDead; //if true, this region is in the process of deleting. + bool mPaused; //pause processing the objects in the region + + typedef enum + { + CAPABILITIES_STATE_INIT = 0, + CAPABILITIES_STATE_ERROR, + CAPABILITIES_STATE_RECEIVED + } eCababilitiesState; + + eCababilitiesState mCapabilitiesState; + + typedef std::map > orphan_list_t; + orphan_list_t mOrphanMap; + + class CacheMissItem + { + public: + CacheMissItem(U32 id, LLViewerRegion::eCacheMissType miss_type) : mID(id), mType(miss_type) {} + + U32 mID; //local object id + LLViewerRegion::eCacheMissType mType; // cache miss type + + typedef std::list cache_miss_list_t; + }; + CacheMissItem::cache_miss_list_t mCacheMissList; + U64 mRegionCacheHitCount; + U64 mRegionCacheMissCount; + + caps_received_signal_t mCapabilitiesReceivedSignal; + caps_received_signal_t mSimulatorFeaturesReceivedSignal; + + LLSD mSimulatorFeatures; + + typedef std::map > vocache_entry_map_t; + static vocache_entry_map_t sRegionCacheCleanup; + + // the materials capability throttle + LLFrameTimer mMaterialsCapThrottleTimer; + LLFrameTimer mRenderInfoRequestTimer; + LLFrameTimer mRenderInfoReportTimer; + + // how the server interest list works + std::string mInterestListMode; + + // list of reflection maps being managed by this llviewer region + std::vector > mReflectionMaps; + +}; + +inline bool LLViewerRegion::getRegionProtocol(U64 protocol) const +{ + return ((mRegionProtocols & protocol) != 0); +} + +inline void LLViewerRegion::setRegionProtocol(U64 protocol, bool on) +{ + if (on) + { + mRegionProtocols |= protocol; + } + else + { + mRegionProtocols &= ~protocol; + } +} + +inline bool LLViewerRegion::getRegionFlag(U64 flag) const +{ + return ((mRegionFlags & flag) != 0); +} + +inline void LLViewerRegion::setRegionFlag(U64 flag, bool on) +{ + if (on) + { + mRegionFlags |= flag; + } + else + { + mRegionFlags &= ~flag; + } +} + +inline bool LLViewerRegion::getAllowDamage() const +{ + return ((mRegionFlags & REGION_FLAGS_ALLOW_DAMAGE) !=0); +} + +inline bool LLViewerRegion::getAllowLandmark() const +{ + return ((mRegionFlags & REGION_FLAGS_ALLOW_LANDMARK) !=0); +} + +inline bool LLViewerRegion::getAllowSetHome() const +{ + return ((mRegionFlags & REGION_FLAGS_ALLOW_SET_HOME) != 0); +} + +inline bool LLViewerRegion::getResetHomeOnTeleport() const +{ + return ((mRegionFlags & REGION_FLAGS_RESET_HOME_ON_TELEPORT) !=0); +} + +inline bool LLViewerRegion::getSunFixed() const +{ + return ((mRegionFlags & REGION_FLAGS_SUN_FIXED) !=0); +} + +inline bool LLViewerRegion::getBlockFly() const +{ + return ((mRegionFlags & REGION_FLAGS_BLOCK_FLY) !=0); +} + +inline bool LLViewerRegion::getAllowDirectTeleport() const +{ + return ((mRegionFlags & REGION_FLAGS_ALLOW_DIRECT_TELEPORT) !=0); +} + +inline bool LLViewerRegion::isPrelude() const +{ + return is_prelude( mRegionFlags ); +} + +inline bool LLViewerRegion::getAllowTerraform() const +{ + return ((mRegionFlags & REGION_FLAGS_BLOCK_TERRAFORM) == 0); +} + +inline bool LLViewerRegion::getRestrictPushObject() const +{ + return ((mRegionFlags & REGION_FLAGS_RESTRICT_PUSHOBJECT) != 0); +} + +inline bool LLViewerRegion::getAllowEnvironmentOverride() const +{ + return ((mRegionFlags & REGION_FLAGS_ALLOW_ENVIRONMENT_OVERRIDE) != 0); +} + +inline bool LLViewerRegion::getReleaseNotesRequested() const +{ + return mReleaseNotesRequested; +} + +#endif diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp index 89286d0ba9..5090d53140 100644 --- a/indra/newview/llviewershadermgr.cpp +++ b/indra/newview/llviewershadermgr.cpp @@ -1,2789 +1,2789 @@ -/** - * @file llviewershadermgr.cpp - * @brief Viewer shader manager implementation. - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - - -#include "llviewerprecompiledheaders.h" - -#include - -#include "llfeaturemanager.h" -#include "llviewershadermgr.h" -#include "llviewercontrol.h" -#include "llversioninfo.h" - -#include "llrender.h" -#include "llenvironment.h" -#include "llerrorcontrol.h" -#include "llatmosphere.h" -#include "llworld.h" -#include "llsky.h" - -#include "pipeline.h" - -#include "llfile.h" -#include "llviewerwindow.h" -#include "llwindow.h" - -#include "lljoint.h" -#include "llskinningutil.h" - -static LLStaticHashedString sTexture0("texture0"); -static LLStaticHashedString sTexture1("texture1"); -static LLStaticHashedString sTex0("tex0"); -static LLStaticHashedString sTex1("tex1"); -static LLStaticHashedString sDitherTex("dither_tex"); -static LLStaticHashedString sGlowMap("glowMap"); -static LLStaticHashedString sScreenMap("screenMap"); - -// Lots of STL stuff in here, using namespace std to keep things more readable -using std::vector; -using std::pair; -using std::make_pair; -using std::string; - -bool LLViewerShaderMgr::sInitialized = false; -bool LLViewerShaderMgr::sSkipReload = false; - -LLVector4 gShinyOrigin; - -//utility shaders -LLGLSLShader gOcclusionProgram; -LLGLSLShader gSkinnedOcclusionProgram; -LLGLSLShader gOcclusionCubeProgram; -LLGLSLShader gGlowCombineProgram; -LLGLSLShader gReflectionMipProgram; -LLGLSLShader gGaussianProgram; -LLGLSLShader gRadianceGenProgram; -LLGLSLShader gIrradianceGenProgram; -LLGLSLShader gGlowCombineFXAAProgram; -LLGLSLShader gTwoTextureCompareProgram; -LLGLSLShader gOneTextureFilterProgram; -LLGLSLShader gDebugProgram; -LLGLSLShader gSkinnedDebugProgram; -LLGLSLShader gClipProgram; -LLGLSLShader gAlphaMaskProgram; -LLGLSLShader gBenchmarkProgram; -LLGLSLShader gReflectionProbeDisplayProgram; -LLGLSLShader gCopyProgram; -LLGLSLShader gCopyDepthProgram; - -//object shaders -LLGLSLShader gObjectPreviewProgram; -LLGLSLShader gSkinnedObjectPreviewProgram; -LLGLSLShader gPhysicsPreviewProgram; -LLGLSLShader gObjectFullbrightAlphaMaskProgram; -LLGLSLShader gSkinnedObjectFullbrightAlphaMaskProgram; -LLGLSLShader gObjectBumpProgram; -LLGLSLShader gSkinnedObjectBumpProgram; -LLGLSLShader gObjectAlphaMaskNoColorProgram; - -//environment shaders -LLGLSLShader gWaterProgram; -LLGLSLShader gWaterEdgeProgram; -LLGLSLShader gUnderWaterProgram; - -//interface shaders -LLGLSLShader gHighlightProgram; -LLGLSLShader gSkinnedHighlightProgram; -LLGLSLShader gHighlightNormalProgram; -LLGLSLShader gHighlightSpecularProgram; - -LLGLSLShader gDeferredHighlightProgram; - -LLGLSLShader gPathfindingProgram; -LLGLSLShader gPathfindingNoNormalsProgram; - -//avatar shader handles -LLGLSLShader gAvatarProgram; -LLGLSLShader gAvatarEyeballProgram; -LLGLSLShader gImpostorProgram; - -// Effects Shaders -LLGLSLShader gGlowProgram; -LLGLSLShader gGlowExtractProgram; -LLGLSLShader gPostScreenSpaceReflectionProgram; - -// Deferred rendering shaders -LLGLSLShader gDeferredImpostorProgram; -LLGLSLShader gDeferredDiffuseProgram; -LLGLSLShader gDeferredDiffuseAlphaMaskProgram; -LLGLSLShader gDeferredSkinnedDiffuseAlphaMaskProgram; -LLGLSLShader gDeferredNonIndexedDiffuseAlphaMaskProgram; -LLGLSLShader gDeferredNonIndexedDiffuseAlphaMaskNoColorProgram; -LLGLSLShader gDeferredSkinnedDiffuseProgram; -LLGLSLShader gDeferredSkinnedBumpProgram; -LLGLSLShader gDeferredBumpProgram; -LLGLSLShader gDeferredTerrainProgram; -LLGLSLShader gDeferredTreeProgram; -LLGLSLShader gDeferredTreeShadowProgram; -LLGLSLShader gDeferredSkinnedTreeShadowProgram; -LLGLSLShader gDeferredAvatarProgram; -LLGLSLShader gDeferredAvatarAlphaProgram; -LLGLSLShader gDeferredLightProgram; -LLGLSLShader gDeferredMultiLightProgram[16]; -LLGLSLShader gDeferredSpotLightProgram; -LLGLSLShader gDeferredMultiSpotLightProgram; -LLGLSLShader gDeferredSunProgram; -LLGLSLShader gHazeProgram; -LLGLSLShader gHazeWaterProgram; -LLGLSLShader gDeferredBlurLightProgram; -LLGLSLShader gDeferredSoftenProgram; -LLGLSLShader gDeferredShadowProgram; -LLGLSLShader gDeferredSkinnedShadowProgram; -LLGLSLShader gDeferredShadowCubeProgram; -LLGLSLShader gDeferredShadowAlphaMaskProgram; -LLGLSLShader gDeferredSkinnedShadowAlphaMaskProgram; -LLGLSLShader gDeferredShadowGLTFAlphaMaskProgram; -LLGLSLShader gDeferredSkinnedShadowGLTFAlphaMaskProgram; -LLGLSLShader gDeferredShadowGLTFAlphaBlendProgram; -LLGLSLShader gDeferredSkinnedShadowGLTFAlphaBlendProgram; -LLGLSLShader gDeferredShadowFullbrightAlphaMaskProgram; -LLGLSLShader gDeferredSkinnedShadowFullbrightAlphaMaskProgram; -LLGLSLShader gDeferredAvatarShadowProgram; -LLGLSLShader gDeferredAvatarAlphaShadowProgram; -LLGLSLShader gDeferredAvatarAlphaMaskShadowProgram; -LLGLSLShader gDeferredAlphaProgram; -LLGLSLShader gHUDAlphaProgram; -LLGLSLShader gDeferredSkinnedAlphaProgram; -LLGLSLShader gDeferredAlphaImpostorProgram; -LLGLSLShader gDeferredSkinnedAlphaImpostorProgram; -LLGLSLShader gDeferredAvatarEyesProgram; -LLGLSLShader gDeferredFullbrightProgram; -LLGLSLShader gHUDFullbrightProgram; -LLGLSLShader gDeferredFullbrightAlphaMaskProgram; -LLGLSLShader gHUDFullbrightAlphaMaskProgram; -LLGLSLShader gDeferredFullbrightAlphaMaskAlphaProgram; -LLGLSLShader gHUDFullbrightAlphaMaskAlphaProgram; -LLGLSLShader gDeferredEmissiveProgram; -LLGLSLShader gDeferredSkinnedEmissiveProgram; -LLGLSLShader gDeferredPostProgram; -LLGLSLShader gDeferredCoFProgram; -LLGLSLShader gDeferredDoFCombineProgram; -LLGLSLShader gDeferredPostGammaCorrectProgram; -LLGLSLShader gNoPostGammaCorrectProgram; -LLGLSLShader gLegacyPostGammaCorrectProgram; -LLGLSLShader gExposureProgram; -LLGLSLShader gLuminanceProgram; -LLGLSLShader gFXAAProgram; -LLGLSLShader gDeferredPostNoDoFProgram; -LLGLSLShader gDeferredWLSkyProgram; -LLGLSLShader gDeferredWLCloudProgram; -LLGLSLShader gDeferredWLSunProgram; -LLGLSLShader gDeferredWLMoonProgram; -LLGLSLShader gDeferredStarProgram; -LLGLSLShader gDeferredFullbrightShinyProgram; -LLGLSLShader gHUDFullbrightShinyProgram; -LLGLSLShader gDeferredSkinnedFullbrightShinyProgram; -LLGLSLShader gDeferredSkinnedFullbrightProgram; -LLGLSLShader gDeferredSkinnedFullbrightAlphaMaskProgram; -LLGLSLShader gDeferredSkinnedFullbrightAlphaMaskAlphaProgram; -LLGLSLShader gNormalMapGenProgram; -LLGLSLShader gDeferredGenBrdfLutProgram; -LLGLSLShader gDeferredBufferVisualProgram; - -// Deferred materials shaders -LLGLSLShader gDeferredMaterialProgram[LLMaterial::SHADER_COUNT*2]; -LLGLSLShader gHUDPBROpaqueProgram; -LLGLSLShader gPBRGlowProgram; -LLGLSLShader gPBRGlowSkinnedProgram; -LLGLSLShader gDeferredPBROpaqueProgram; -LLGLSLShader gDeferredSkinnedPBROpaqueProgram; -LLGLSLShader gHUDPBRAlphaProgram; -LLGLSLShader gDeferredPBRAlphaProgram; -LLGLSLShader gDeferredSkinnedPBRAlphaProgram; - -//helper for making a rigged variant of a given shader -bool make_rigged_variant(LLGLSLShader& shader, LLGLSLShader& riggedShader) -{ - riggedShader.mName = llformat("Skinned %s", shader.mName.c_str()); - riggedShader.mFeatures = shader.mFeatures; - riggedShader.mFeatures.hasObjectSkinning = true; - riggedShader.mDefines = shader.mDefines; // NOTE: Must come before addPermutation - riggedShader.addPermutation("HAS_SKIN", "1"); - riggedShader.mShaderFiles = shader.mShaderFiles; - riggedShader.mShaderLevel = shader.mShaderLevel; - riggedShader.mShaderGroup = shader.mShaderGroup; - - shader.mRiggedVariant = &riggedShader; - return riggedShader.createShader(NULL, NULL); -} - -LLViewerShaderMgr::LLViewerShaderMgr() : - mShaderLevel(SHADER_COUNT, 0), - mMaxAvatarShaderLevel(0) -{ - //ONLY shaders that need WL Param management should be added here - mShaderList.push_back(&gAvatarProgram); - mShaderList.push_back(&gWaterProgram); - mShaderList.push_back(&gWaterEdgeProgram); - mShaderList.push_back(&gAvatarEyeballProgram); - mShaderList.push_back(&gImpostorProgram); - mShaderList.push_back(&gObjectBumpProgram); - mShaderList.push_back(&gSkinnedObjectBumpProgram); - mShaderList.push_back(&gObjectFullbrightAlphaMaskProgram); - mShaderList.push_back(&gSkinnedObjectFullbrightAlphaMaskProgram); - mShaderList.push_back(&gObjectAlphaMaskNoColorProgram); - mShaderList.push_back(&gUnderWaterProgram); - mShaderList.push_back(&gDeferredSunProgram); - mShaderList.push_back(&gHazeProgram); - mShaderList.push_back(&gHazeWaterProgram); - mShaderList.push_back(&gDeferredSoftenProgram); - mShaderList.push_back(&gDeferredAlphaProgram); - mShaderList.push_back(&gHUDAlphaProgram); - mShaderList.push_back(&gDeferredSkinnedAlphaProgram); - mShaderList.push_back(&gDeferredAlphaImpostorProgram); - mShaderList.push_back(&gDeferredSkinnedAlphaImpostorProgram); - mShaderList.push_back(&gDeferredFullbrightProgram); - mShaderList.push_back(&gHUDFullbrightProgram); - mShaderList.push_back(&gDeferredFullbrightAlphaMaskProgram); - mShaderList.push_back(&gHUDFullbrightAlphaMaskProgram); - mShaderList.push_back(&gDeferredFullbrightAlphaMaskAlphaProgram); - mShaderList.push_back(&gHUDFullbrightAlphaMaskAlphaProgram); - mShaderList.push_back(&gDeferredFullbrightShinyProgram); - mShaderList.push_back(&gHUDFullbrightShinyProgram); - mShaderList.push_back(&gDeferredSkinnedFullbrightShinyProgram); - mShaderList.push_back(&gDeferredSkinnedFullbrightProgram); - mShaderList.push_back(&gDeferredSkinnedFullbrightAlphaMaskProgram); - mShaderList.push_back(&gDeferredSkinnedFullbrightAlphaMaskAlphaProgram); - mShaderList.push_back(&gDeferredEmissiveProgram); - mShaderList.push_back(&gDeferredSkinnedEmissiveProgram); - mShaderList.push_back(&gDeferredAvatarEyesProgram); - mShaderList.push_back(&gDeferredAvatarAlphaProgram); - mShaderList.push_back(&gDeferredWLSkyProgram); - mShaderList.push_back(&gDeferredWLCloudProgram); - mShaderList.push_back(&gDeferredWLMoonProgram); - mShaderList.push_back(&gDeferredWLSunProgram); - mShaderList.push_back(&gDeferredPBRAlphaProgram); - mShaderList.push_back(&gHUDPBRAlphaProgram); - mShaderList.push_back(&gDeferredSkinnedPBRAlphaProgram); - mShaderList.push_back(&gDeferredPostGammaCorrectProgram); // for gamma - mShaderList.push_back(&gNoPostGammaCorrectProgram); - mShaderList.push_back(&gLegacyPostGammaCorrectProgram); - -} - -LLViewerShaderMgr::~LLViewerShaderMgr() -{ - mShaderLevel.clear(); - mShaderList.clear(); -} - -// static -LLViewerShaderMgr * LLViewerShaderMgr::instance() -{ - if(NULL == sInstance) - { - sInstance = new LLViewerShaderMgr(); - } - - return static_cast(sInstance); -} - -// static -void LLViewerShaderMgr::releaseInstance() -{ - if (sInstance != NULL) - { - delete sInstance; - sInstance = NULL; - } -} - -void LLViewerShaderMgr::initAttribsAndUniforms(void) -{ - if (mReservedAttribs.empty()) - { - LLShaderMgr::initAttribsAndUniforms(); - } -} - - -//============================================================================ -// Set Levels - -S32 LLViewerShaderMgr::getShaderLevel(S32 type) -{ - return mShaderLevel[type]; -} - -//============================================================================ -// Shader Management - -void LLViewerShaderMgr::setShaders() -{ - LL_PROFILE_ZONE_SCOPED; - //setShaders might be called redundantly by gSavedSettings, so return on reentrance - static bool reentrance = false; - - if (!gPipeline.mInitialized || !sInitialized || reentrance || sSkipReload) - { - return; - } - - if (!gGLManager.mHasRequirements) - { - // Viewer will show 'hardware requirements' warning later - LL_INFOS("ShaderLoading") << "Not supported hardware/software" << LL_ENDL; - return; - } - - { - static LLCachedControl shader_cache_enabled(gSavedSettings, "RenderShaderCacheEnabled", true); - static LLUUID old_cache_version; - static LLUUID current_cache_version; - if (current_cache_version.isNull()) - { - HBXXH128 hash_obj; - hash_obj.update(LLVersionInfo::instance().getVersion()); - current_cache_version = hash_obj.digest(); - - old_cache_version = LLUUID(gSavedSettings.getString("RenderShaderCacheVersion")); - gSavedSettings.setString("RenderShaderCacheVersion", current_cache_version.asString()); - } - - initShaderCache(shader_cache_enabled, old_cache_version, current_cache_version); - } - - static LLCachedControl max_texture_index(gSavedSettings, "RenderMaxTextureIndex", 16); - - // when using indexed texture rendering, leave some texture units available for shadow and reflection maps - LLGLSLShader::sIndexedTextureChannels = llmax(llmin(gGLManager.mNumTextureImageUnits-12, (S32) max_texture_index), 1); - - reentrance = true; - - // Make sure the compiled shader map is cleared before we recompile shaders. - mVertexShaderObjects.clear(); - mFragmentShaderObjects.clear(); - - initAttribsAndUniforms(); - gPipeline.releaseGLBuffers(); - - unloadShaders(); - - LLPipeline::sRenderGlow = gSavedSettings.getBOOL("RenderGlow"); - - if (gViewerWindow) - { - gViewerWindow->setCursor(UI_CURSOR_WAIT); - } - - // Shaders - LL_INFOS("ShaderLoading") << "\n~~~~~~~~~~~~~~~~~~\n Loading Shaders:\n~~~~~~~~~~~~~~~~~~" << LL_ENDL; - LL_INFOS("ShaderLoading") << llformat("Using GLSL %d.%d", gGLManager.mGLSLVersionMajor, gGLManager.mGLSLVersionMinor) << LL_ENDL; - - for (S32 i = 0; i < SHADER_COUNT; i++) - { - mShaderLevel[i] = 0; - } - mMaxAvatarShaderLevel = 0; - - LLVertexBuffer::unbind(); - - llassert((gGLManager.mGLSLVersionMajor > 1 || gGLManager.mGLSLVersionMinor >= 10)); - - - S32 light_class = 3; - S32 interface_class = 2; - S32 env_class = 2; - S32 obj_class = 2; - S32 effect_class = 2; - S32 wl_class = 2; - S32 water_class = 3; - S32 deferred_class = 3; - - // Trigger a full rebuild of the fallback skybox / cubemap if we've toggled windlight shaders - if (!wl_class || (mShaderLevel[SHADER_WINDLIGHT] != wl_class && gSky.mVOSkyp.notNull())) - { - gSky.mVOSkyp->forceSkyUpdate(); - } - - // Load lighting shaders - mShaderLevel[SHADER_LIGHTING] = light_class; - mShaderLevel[SHADER_INTERFACE] = interface_class; - mShaderLevel[SHADER_ENVIRONMENT] = env_class; - mShaderLevel[SHADER_WATER] = water_class; - mShaderLevel[SHADER_OBJECT] = obj_class; - mShaderLevel[SHADER_EFFECT] = effect_class; - mShaderLevel[SHADER_WINDLIGHT] = wl_class; - mShaderLevel[SHADER_DEFERRED] = deferred_class; - - std::string shader_name = loadBasicShaders(); - if (shader_name.empty()) - { - LL_INFOS("Shader") << "Loaded basic shaders." << LL_ENDL; - } - else - { - // "ShaderLoading" and "Shader" need to be logged - LLError::ELevel lvl = LLError::getDefaultLevel(); - LLError::setDefaultLevel(LLError::LEVEL_DEBUG); - loadBasicShaders(); - LLError::setDefaultLevel(lvl); - LL_ERRS() << "Unable to load basic shader " << shader_name << ", verify graphics driver installed and current." << LL_ENDL; - reentrance = false; // For hygiene only, re-try probably helps nothing - return; - } - - gPipeline.mShadersLoaded = true; - - bool loaded = loadShadersWater(); - - if (loaded) - { - LL_INFOS() << "Loaded water shaders." << LL_ENDL; - } - else - { - LL_WARNS() << "Failed to load water shaders." << LL_ENDL; - llassert(loaded); - } - - if (loaded) - { - loaded = loadShadersEffects(); - if (loaded) - { - LL_INFOS() << "Loaded effects shaders." << LL_ENDL; - } - else - { - LL_WARNS() << "Failed to load effects shaders." << LL_ENDL; - llassert(loaded); - } - } - - if (loaded) - { - loaded = loadShadersInterface(); - if (loaded) - { - LL_INFOS() << "Loaded interface shaders." << LL_ENDL; - } - else - { - LL_WARNS() << "Failed to load interface shaders." << LL_ENDL; - llassert(loaded); - } - } - - if (loaded) - { - // Load max avatar shaders to set the max level - mShaderLevel[SHADER_AVATAR] = 3; - mMaxAvatarShaderLevel = 3; - - if (loadShadersObject()) - { //hardware skinning is enabled and rigged attachment shaders loaded correctly - // cloth is a class3 shader - S32 avatar_class = 1; - - // Set the actual level - mShaderLevel[SHADER_AVATAR] = avatar_class; - - loaded = loadShadersAvatar(); - llassert(loaded); - } - else - { //hardware skinning not possible, neither is deferred rendering - llassert(false); // SHOULD NOT BE POSSIBLE - } - } - - llassert(loaded); - loaded = loaded && loadShadersDeferred(); - llassert(loaded); - - persistShaderCacheMetadata(); - - if (gViewerWindow) - { - gViewerWindow->setCursor(UI_CURSOR_ARROW); - } - gPipeline.createGLBuffers(); - - reentrance = false; -} - -void LLViewerShaderMgr::unloadShaders() -{ - while (!LLGLSLShader::sInstances.empty()) - { - LLGLSLShader* shader = *(LLGLSLShader::sInstances.begin()); - shader->unload(); - } - - mShaderLevel[SHADER_LIGHTING] = 0; - mShaderLevel[SHADER_OBJECT] = 0; - mShaderLevel[SHADER_AVATAR] = 0; - mShaderLevel[SHADER_ENVIRONMENT] = 0; - mShaderLevel[SHADER_WATER] = 0; - mShaderLevel[SHADER_INTERFACE] = 0; - mShaderLevel[SHADER_EFFECT] = 0; - mShaderLevel[SHADER_WINDLIGHT] = 0; - - gPipeline.mShadersLoaded = false; -} - -std::string LLViewerShaderMgr::loadBasicShaders() -{ - // Load basic dependency shaders first - // All of these have to load for any shaders to function - - S32 sum_lights_class = 3; - -#if LL_DARWIN - // Work around driver crashes on older Macs when using deferred rendering - // NORSPEC-59 - // - if (gGLManager.mIsMobileGF) - sum_lights_class = 3; -#endif - - // Use the feature table to mask out the max light level to use. Also make sure it's at least 1. - S32 max_light_class = gSavedSettings.getS32("RenderShaderLightingMaxLevel"); - sum_lights_class = llclamp(sum_lights_class, 1, max_light_class); - - // Load the Basic Vertex Shaders at the appropriate level. - // (in order of shader function call depth for reference purposes, deepest level first) - - vector< pair > shaders; - shaders.push_back( make_pair( "windlight/atmosphericsVarsV.glsl", mShaderLevel[SHADER_WINDLIGHT] ) ); - shaders.push_back( make_pair( "windlight/atmosphericsHelpersV.glsl", mShaderLevel[SHADER_WINDLIGHT] ) ); - shaders.push_back( make_pair( "lighting/lightFuncV.glsl", mShaderLevel[SHADER_LIGHTING] ) ); - shaders.push_back( make_pair( "lighting/sumLightsV.glsl", sum_lights_class ) ); - shaders.push_back( make_pair( "lighting/lightV.glsl", mShaderLevel[SHADER_LIGHTING] ) ); - shaders.push_back( make_pair( "lighting/lightFuncSpecularV.glsl", mShaderLevel[SHADER_LIGHTING] ) ); - shaders.push_back( make_pair( "lighting/sumLightsSpecularV.glsl", sum_lights_class ) ); - shaders.push_back( make_pair( "lighting/lightSpecularV.glsl", mShaderLevel[SHADER_LIGHTING] ) ); - shaders.push_back( make_pair( "windlight/atmosphericsFuncs.glsl", mShaderLevel[SHADER_WINDLIGHT] ) ); - shaders.push_back( make_pair( "windlight/atmosphericsV.glsl", mShaderLevel[SHADER_WINDLIGHT] ) ); - shaders.push_back( make_pair( "environment/srgbF.glsl", 1 ) ); - shaders.push_back( make_pair( "avatar/avatarSkinV.glsl", 1 ) ); - shaders.push_back( make_pair( "avatar/objectSkinV.glsl", 1 ) ); - shaders.push_back( make_pair( "deferred/textureUtilV.glsl", 1 ) ); - if (gGLManager.mGLSLVersionMajor >= 2 || gGLManager.mGLSLVersionMinor >= 30) - { - shaders.push_back( make_pair( "objects/indexedTextureV.glsl", 1 ) ); - } - shaders.push_back( make_pair( "objects/nonindexedTextureV.glsl", 1 ) ); - - std::map attribs; - attribs["MAX_JOINTS_PER_MESH_OBJECT"] = - std::to_string(LLSkinningUtil::getMaxJointCount()); - - bool ssr = gSavedSettings.getBOOL("RenderScreenSpaceReflections"); - - bool has_reflection_probes = gSavedSettings.getBOOL("RenderReflectionsEnabled") && gGLManager.mGLVersion > 3.99f; - - S32 probe_level = llclamp(gSavedSettings.getS32("RenderReflectionProbeLevel"), 0, 3); - - S32 shadow_detail = gSavedSettings.getS32("RenderShadowDetail"); - - if (shadow_detail >= 1) - { - attribs["SUN_SHADOW"] = "1"; - - if (shadow_detail >= 2) - { - attribs["SPOT_SHADOW"] = "1"; - } - } - - if (ssr) - { - attribs["SSR"] = "1"; - } - - if (has_reflection_probes) - { - attribs["REFMAP_LEVEL"] = std::to_string(probe_level); - attribs["REF_SAMPLE_COUNT"] = "32"; - } - - LLGLSLShader::sGlobalDefines = attribs; - - // We no longer have to bind the shaders to global glhandles, they are automatically added to a map now. - for (U32 i = 0; i < shaders.size(); i++) - { - // Note usage of GL_VERTEX_SHADER - if (loadShaderFile(shaders[i].first, shaders[i].second, GL_VERTEX_SHADER, &attribs) == 0) - { - LL_WARNS("Shader") << "Failed to load vertex shader " << shaders[i].first << LL_ENDL; - return shaders[i].first; - } - } - - // Load the Basic Fragment Shaders at the appropriate level. - // (in order of shader function call depth for reference purposes, deepest level first) - - shaders.clear(); - S32 ch = 1; - - if (gGLManager.mGLSLVersionMajor > 1 || gGLManager.mGLSLVersionMinor >= 30) - { //use indexed texture rendering for GLSL >= 1.30 - ch = llmax(LLGLSLShader::sIndexedTextureChannels, 1); - } - - - std::vector index_channels; - index_channels.push_back(-1); shaders.push_back( make_pair( "windlight/atmosphericsVarsF.glsl", mShaderLevel[SHADER_WINDLIGHT] ) ); - index_channels.push_back(-1); shaders.push_back( make_pair( "windlight/atmosphericsHelpersF.glsl", mShaderLevel[SHADER_WINDLIGHT] ) ); - index_channels.push_back(-1); shaders.push_back( make_pair( "windlight/gammaF.glsl", mShaderLevel[SHADER_WINDLIGHT]) ); - index_channels.push_back(-1); shaders.push_back( make_pair( "windlight/atmosphericsFuncs.glsl", mShaderLevel[SHADER_WINDLIGHT] ) ); - index_channels.push_back(-1); shaders.push_back( make_pair( "windlight/atmosphericsF.glsl", mShaderLevel[SHADER_WINDLIGHT] ) ); - index_channels.push_back(-1); shaders.push_back( make_pair( "environment/waterFogF.glsl", mShaderLevel[SHADER_WATER] ) ); - index_channels.push_back(-1); shaders.push_back( make_pair( "environment/encodeNormF.glsl", mShaderLevel[SHADER_ENVIRONMENT] ) ); - index_channels.push_back(-1); shaders.push_back( make_pair( "environment/srgbF.glsl", mShaderLevel[SHADER_ENVIRONMENT] ) ); - index_channels.push_back(-1); shaders.push_back( make_pair( "deferred/deferredUtil.glsl", 1) ); - index_channels.push_back(-1); shaders.push_back( make_pair( "deferred/shadowUtil.glsl", 1) ); - index_channels.push_back(-1); shaders.push_back( make_pair( "deferred/aoUtil.glsl", 1) ); - index_channels.push_back(-1); shaders.push_back( make_pair( "deferred/reflectionProbeF.glsl", has_reflection_probes ? 3 : 2) ); - index_channels.push_back(-1); shaders.push_back( make_pair( "deferred/screenSpaceReflUtil.glsl", ssr ? 3 : 1) ); - index_channels.push_back(-1); shaders.push_back( make_pair( "lighting/lightNonIndexedF.glsl", mShaderLevel[SHADER_LIGHTING] ) ); - index_channels.push_back(-1); shaders.push_back( make_pair( "lighting/lightAlphaMaskNonIndexedF.glsl", mShaderLevel[SHADER_LIGHTING] ) ); - index_channels.push_back(ch); shaders.push_back( make_pair( "lighting/lightF.glsl", mShaderLevel[SHADER_LIGHTING] ) ); - index_channels.push_back(ch); shaders.push_back( make_pair( "lighting/lightAlphaMaskF.glsl", mShaderLevel[SHADER_LIGHTING] ) ); - - for (U32 i = 0; i < shaders.size(); i++) - { - // Note usage of GL_FRAGMENT_SHADER - if (loadShaderFile(shaders[i].first, shaders[i].second, GL_FRAGMENT_SHADER, &attribs, index_channels[i]) == 0) - { - LL_WARNS("Shader") << "Failed to load fragment shader " << shaders[i].first << LL_ENDL; - return shaders[i].first; - } - } - - return std::string(); -} - -bool LLViewerShaderMgr::loadShadersWater() -{ - LL_PROFILE_ZONE_SCOPED; - bool success = true; - bool terrainWaterSuccess = true; - - bool use_sun_shadow = mShaderLevel[SHADER_DEFERRED] > 1 && - gSavedSettings.getS32("RenderShadowDetail") > 0; - - if (mShaderLevel[SHADER_WATER] == 0) - { - gWaterProgram.unload(); - gWaterEdgeProgram.unload(); - gUnderWaterProgram.unload(); - return true; - } - - if (success) - { - // load water shader - gWaterProgram.mName = "Water Shader"; - gWaterProgram.mFeatures.calculatesAtmospherics = true; - gWaterProgram.mFeatures.hasAtmospherics = true; - gWaterProgram.mFeatures.hasGamma = true; - gWaterProgram.mFeatures.hasSrgb = true; - gWaterProgram.mFeatures.hasReflectionProbes = true; - gWaterProgram.mFeatures.hasShadows = use_sun_shadow; - gWaterProgram.mShaderFiles.clear(); - gWaterProgram.mShaderFiles.push_back(make_pair("environment/waterV.glsl", GL_VERTEX_SHADER)); - gWaterProgram.mShaderFiles.push_back(make_pair("environment/waterF.glsl", GL_FRAGMENT_SHADER)); - gWaterProgram.clearPermutations(); - if (LLPipeline::sRenderTransparentWater) - { - gWaterProgram.addPermutation("TRANSPARENT_WATER", "1"); - } - - if (use_sun_shadow) - { - gWaterProgram.addPermutation("HAS_SUN_SHADOW", "1"); - } - - gWaterProgram.mShaderGroup = LLGLSLShader::SG_WATER; - gWaterProgram.mShaderLevel = mShaderLevel[SHADER_WATER]; - success = gWaterProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - // load water shader - gWaterEdgeProgram.mName = "Water Edge Shader"; - gWaterEdgeProgram.mFeatures.calculatesAtmospherics = true; - gWaterEdgeProgram.mFeatures.hasAtmospherics = true; - gWaterEdgeProgram.mFeatures.hasGamma = true; - gWaterEdgeProgram.mFeatures.hasSrgb = true; - gWaterEdgeProgram.mFeatures.hasReflectionProbes = true; - gWaterEdgeProgram.mFeatures.hasShadows = use_sun_shadow; - gWaterEdgeProgram.mShaderFiles.clear(); - gWaterEdgeProgram.mShaderFiles.push_back(make_pair("environment/waterV.glsl", GL_VERTEX_SHADER)); - gWaterEdgeProgram.mShaderFiles.push_back(make_pair("environment/waterF.glsl", GL_FRAGMENT_SHADER)); - gWaterEdgeProgram.clearPermutations(); - gWaterEdgeProgram.addPermutation("WATER_EDGE", "1"); - if (LLPipeline::sRenderTransparentWater) - { - gWaterEdgeProgram.addPermutation("TRANSPARENT_WATER", "1"); - } - - if (use_sun_shadow) - { - gWaterEdgeProgram.addPermutation("HAS_SUN_SHADOW", "1"); - } - gWaterEdgeProgram.mShaderGroup = LLGLSLShader::SG_WATER; - gWaterEdgeProgram.mShaderLevel = mShaderLevel[SHADER_WATER]; - success = gWaterEdgeProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - //load under water vertex shader - gUnderWaterProgram.mName = "Underwater Shader"; - gUnderWaterProgram.mFeatures.calculatesAtmospherics = true; - gUnderWaterProgram.mFeatures.hasAtmospherics = true; - gUnderWaterProgram.mShaderFiles.clear(); - gUnderWaterProgram.mShaderFiles.push_back(make_pair("environment/waterV.glsl", GL_VERTEX_SHADER)); - gUnderWaterProgram.mShaderFiles.push_back(make_pair("environment/underWaterF.glsl", GL_FRAGMENT_SHADER)); - gUnderWaterProgram.mShaderLevel = mShaderLevel[SHADER_WATER]; - gUnderWaterProgram.mShaderGroup = LLGLSLShader::SG_WATER; - gUnderWaterProgram.clearPermutations(); - if (LLPipeline::sRenderTransparentWater) - { - gUnderWaterProgram.addPermutation("TRANSPARENT_WATER", "1"); - } - success = gUnderWaterProgram.createShader(NULL, NULL); - llassert(success); - } - - /// Keep track of water shader levels - if (gWaterProgram.mShaderLevel != mShaderLevel[SHADER_WATER] - || gUnderWaterProgram.mShaderLevel != mShaderLevel[SHADER_WATER]) - { - mShaderLevel[SHADER_WATER] = llmin(gWaterProgram.mShaderLevel, gUnderWaterProgram.mShaderLevel); - } - - if (!success) - { - mShaderLevel[SHADER_WATER] = 0; - return false; - } - - // if we failed to load the terrain water shaders and we need them (using class2 water), - // then drop down to class1 water. - if (mShaderLevel[SHADER_WATER] > 1 && !terrainWaterSuccess) - { - mShaderLevel[SHADER_WATER]--; - return loadShadersWater(); - } - - LLWorld::getInstance()->updateWaterObjects(); - - return true; -} - -bool LLViewerShaderMgr::loadShadersEffects() -{ - LL_PROFILE_ZONE_SCOPED; - bool success = true; - - if (mShaderLevel[SHADER_EFFECT] == 0) - { - gGlowProgram.unload(); - gGlowExtractProgram.unload(); - return true; - } - - if (success) - { - gGlowProgram.mName = "Glow Shader (Post)"; - gGlowProgram.mShaderFiles.clear(); - gGlowProgram.mShaderFiles.push_back(make_pair("effects/glowV.glsl", GL_VERTEX_SHADER)); - gGlowProgram.mShaderFiles.push_back(make_pair("effects/glowF.glsl", GL_FRAGMENT_SHADER)); - gGlowProgram.mShaderLevel = mShaderLevel[SHADER_EFFECT]; - success = gGlowProgram.createShader(NULL, NULL); - if (!success) - { - LLPipeline::sRenderGlow = false; - } - } - - if (success) - { - const bool use_glow_noise = gSavedSettings.getBOOL("RenderGlowNoise"); - const std::string glow_noise_label = use_glow_noise ? " (+Noise)" : ""; - - gGlowExtractProgram.mName = llformat("Glow Extract Shader (Post)%s", glow_noise_label.c_str()); - gGlowExtractProgram.mShaderFiles.clear(); - gGlowExtractProgram.mShaderFiles.push_back(make_pair("effects/glowExtractV.glsl", GL_VERTEX_SHADER)); - gGlowExtractProgram.mShaderFiles.push_back(make_pair("effects/glowExtractF.glsl", GL_FRAGMENT_SHADER)); - gGlowExtractProgram.mShaderLevel = mShaderLevel[SHADER_EFFECT]; - - if (use_glow_noise) - { - gGlowExtractProgram.addPermutation("HAS_NOISE", "1"); - } - - success = gGlowExtractProgram.createShader(NULL, NULL); - if (!success) - { - LLPipeline::sRenderGlow = false; - } - } - - return success; - -} - -bool LLViewerShaderMgr::loadShadersDeferred() -{ - LL_PROFILE_ZONE_SCOPED; - bool use_sun_shadow = mShaderLevel[SHADER_DEFERRED] > 1 && - gSavedSettings.getS32("RenderShadowDetail") > 0; - - if (mShaderLevel[SHADER_DEFERRED] == 0) - { - gDeferredTreeProgram.unload(); - gDeferredTreeShadowProgram.unload(); - gDeferredSkinnedTreeShadowProgram.unload(); - gDeferredDiffuseProgram.unload(); - gDeferredSkinnedDiffuseProgram.unload(); - gDeferredDiffuseAlphaMaskProgram.unload(); - gDeferredSkinnedDiffuseAlphaMaskProgram.unload(); - gDeferredNonIndexedDiffuseAlphaMaskProgram.unload(); - gDeferredNonIndexedDiffuseAlphaMaskNoColorProgram.unload(); - gDeferredBumpProgram.unload(); - gDeferredSkinnedBumpProgram.unload(); - gDeferredImpostorProgram.unload(); - gDeferredTerrainProgram.unload(); - gDeferredLightProgram.unload(); - for (U32 i = 0; i < LL_DEFERRED_MULTI_LIGHT_COUNT; ++i) - { - gDeferredMultiLightProgram[i].unload(); - } - gDeferredSpotLightProgram.unload(); - gDeferredMultiSpotLightProgram.unload(); - gDeferredSunProgram.unload(); - gDeferredBlurLightProgram.unload(); - gDeferredSoftenProgram.unload(); - gDeferredShadowProgram.unload(); - gDeferredSkinnedShadowProgram.unload(); - gDeferredShadowCubeProgram.unload(); - gDeferredShadowAlphaMaskProgram.unload(); - gDeferredSkinnedShadowAlphaMaskProgram.unload(); - gDeferredShadowGLTFAlphaMaskProgram.unload(); - gDeferredSkinnedShadowGLTFAlphaMaskProgram.unload(); - gDeferredShadowFullbrightAlphaMaskProgram.unload(); - gDeferredSkinnedShadowFullbrightAlphaMaskProgram.unload(); - gDeferredAvatarShadowProgram.unload(); - gDeferredAvatarAlphaShadowProgram.unload(); - gDeferredAvatarAlphaMaskShadowProgram.unload(); - gDeferredAvatarProgram.unload(); - gDeferredAvatarAlphaProgram.unload(); - gDeferredAlphaProgram.unload(); - gHUDAlphaProgram.unload(); - gDeferredSkinnedAlphaProgram.unload(); - gDeferredFullbrightProgram.unload(); - gHUDFullbrightProgram.unload(); - gDeferredFullbrightAlphaMaskProgram.unload(); - gHUDFullbrightAlphaMaskProgram.unload(); - gDeferredFullbrightAlphaMaskAlphaProgram.unload(); - gHUDFullbrightAlphaMaskAlphaProgram.unload(); - gDeferredEmissiveProgram.unload(); - gDeferredSkinnedEmissiveProgram.unload(); - gDeferredAvatarEyesProgram.unload(); - gDeferredPostProgram.unload(); - gDeferredCoFProgram.unload(); - gDeferredDoFCombineProgram.unload(); - gExposureProgram.unload(); - gLuminanceProgram.unload(); - gDeferredPostGammaCorrectProgram.unload(); - gNoPostGammaCorrectProgram.unload(); - gLegacyPostGammaCorrectProgram.unload(); - gFXAAProgram.unload(); - gDeferredWLSkyProgram.unload(); - gDeferredWLCloudProgram.unload(); - gDeferredWLSunProgram.unload(); - gDeferredWLMoonProgram.unload(); - gDeferredStarProgram.unload(); - gDeferredFullbrightShinyProgram.unload(); - gHUDFullbrightShinyProgram.unload(); - gDeferredSkinnedFullbrightShinyProgram.unload(); - gDeferredSkinnedFullbrightProgram.unload(); - gDeferredSkinnedFullbrightAlphaMaskProgram.unload(); - gDeferredSkinnedFullbrightAlphaMaskAlphaProgram.unload(); - - gDeferredHighlightProgram.unload(); - - gNormalMapGenProgram.unload(); - gDeferredGenBrdfLutProgram.unload(); - gDeferredBufferVisualProgram.unload(); - - for (U32 i = 0; i < LLMaterial::SHADER_COUNT*2; ++i) - { - gDeferredMaterialProgram[i].unload(); - } - - gHUDPBROpaqueProgram.unload(); - gPBRGlowProgram.unload(); - gDeferredPBROpaqueProgram.unload(); - gDeferredSkinnedPBROpaqueProgram.unload(); - gDeferredPBRAlphaProgram.unload(); - gDeferredSkinnedPBRAlphaProgram.unload(); - - return true; - } - - bool success = true; - - if (success) - { - gDeferredHighlightProgram.mName = "Deferred Highlight Shader"; - gDeferredHighlightProgram.mShaderFiles.clear(); - gDeferredHighlightProgram.mShaderFiles.push_back(make_pair("interface/highlightV.glsl", GL_VERTEX_SHADER)); - gDeferredHighlightProgram.mShaderFiles.push_back(make_pair("deferred/highlightF.glsl", GL_FRAGMENT_SHADER)); - gDeferredHighlightProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = gDeferredHighlightProgram.createShader(NULL, NULL); - } - - if (success) - { - gDeferredDiffuseProgram.mName = "Deferred Diffuse Shader"; - gDeferredDiffuseProgram.mFeatures.encodesNormal = true; - gDeferredDiffuseProgram.mFeatures.hasSrgb = true; - gDeferredDiffuseProgram.mShaderFiles.clear(); - gDeferredDiffuseProgram.mShaderFiles.push_back(make_pair("deferred/diffuseV.glsl", GL_VERTEX_SHADER)); - gDeferredDiffuseProgram.mShaderFiles.push_back(make_pair("deferred/diffuseIndexedF.glsl", GL_FRAGMENT_SHADER)); - gDeferredDiffuseProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; - gDeferredDiffuseProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = make_rigged_variant(gDeferredDiffuseProgram, gDeferredSkinnedDiffuseProgram); - success = success && gDeferredDiffuseProgram.createShader(NULL, NULL); - } - - if (success) - { - gDeferredDiffuseAlphaMaskProgram.mName = "Deferred Diffuse Alpha Mask Shader"; - gDeferredDiffuseAlphaMaskProgram.mFeatures.encodesNormal = true; - gDeferredDiffuseAlphaMaskProgram.mShaderFiles.clear(); - gDeferredDiffuseAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/diffuseV.glsl", GL_VERTEX_SHADER)); - gDeferredDiffuseAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/diffuseAlphaMaskIndexedF.glsl", GL_FRAGMENT_SHADER)); - gDeferredDiffuseAlphaMaskProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; - gDeferredDiffuseAlphaMaskProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = make_rigged_variant(gDeferredDiffuseAlphaMaskProgram, gDeferredSkinnedDiffuseAlphaMaskProgram); - success = success && gDeferredDiffuseAlphaMaskProgram.createShader(NULL, NULL); - } - - if (success) - { - gDeferredNonIndexedDiffuseAlphaMaskProgram.mName = "Deferred Diffuse Non-Indexed Alpha Mask Shader"; - gDeferredNonIndexedDiffuseAlphaMaskProgram.mFeatures.encodesNormal = true; - gDeferredNonIndexedDiffuseAlphaMaskProgram.mShaderFiles.clear(); - gDeferredNonIndexedDiffuseAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/diffuseV.glsl", GL_VERTEX_SHADER)); - gDeferredNonIndexedDiffuseAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/diffuseAlphaMaskF.glsl", GL_FRAGMENT_SHADER)); - gDeferredNonIndexedDiffuseAlphaMaskProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gDeferredNonIndexedDiffuseAlphaMaskProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredNonIndexedDiffuseAlphaMaskNoColorProgram.mName = "Deferred Diffuse Non-Indexed Alpha Mask No Color Shader"; - gDeferredNonIndexedDiffuseAlphaMaskNoColorProgram.mFeatures.encodesNormal = true; - gDeferredNonIndexedDiffuseAlphaMaskNoColorProgram.mShaderFiles.clear(); - gDeferredNonIndexedDiffuseAlphaMaskNoColorProgram.mShaderFiles.push_back(make_pair("deferred/diffuseNoColorV.glsl", GL_VERTEX_SHADER)); - gDeferredNonIndexedDiffuseAlphaMaskNoColorProgram.mShaderFiles.push_back(make_pair("deferred/diffuseAlphaMaskNoColorF.glsl", GL_FRAGMENT_SHADER)); - gDeferredNonIndexedDiffuseAlphaMaskNoColorProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gDeferredNonIndexedDiffuseAlphaMaskNoColorProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredBumpProgram.mName = "Deferred Bump Shader"; - gDeferredBumpProgram.mFeatures.encodesNormal = true; - gDeferredBumpProgram.mShaderFiles.clear(); - gDeferredBumpProgram.mShaderFiles.push_back(make_pair("deferred/bumpV.glsl", GL_VERTEX_SHADER)); - gDeferredBumpProgram.mShaderFiles.push_back(make_pair("deferred/bumpF.glsl", GL_FRAGMENT_SHADER)); - gDeferredBumpProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = make_rigged_variant(gDeferredBumpProgram, gDeferredSkinnedBumpProgram); - success = success && gDeferredBumpProgram.createShader(NULL, NULL); - llassert(success); - } - - gDeferredMaterialProgram[1].mFeatures.hasLighting = false; - gDeferredMaterialProgram[5].mFeatures.hasLighting = false; - gDeferredMaterialProgram[9].mFeatures.hasLighting = false; - gDeferredMaterialProgram[13].mFeatures.hasLighting = false; - gDeferredMaterialProgram[1+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = false; - gDeferredMaterialProgram[5+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = false; - gDeferredMaterialProgram[9+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = false; - gDeferredMaterialProgram[13+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = false; - - for (U32 i = 0; i < LLMaterial::SHADER_COUNT*2; ++i) - { - if (success) - { - mShaderList.push_back(&gDeferredMaterialProgram[i]); - - gDeferredMaterialProgram[i].mName = llformat("Deferred Material Shader %d", i); - - U32 alpha_mode = i & 0x3; - - gDeferredMaterialProgram[i].mShaderFiles.clear(); - gDeferredMaterialProgram[i].mShaderFiles.push_back(make_pair("deferred/materialV.glsl", GL_VERTEX_SHADER)); - gDeferredMaterialProgram[i].mShaderFiles.push_back(make_pair("deferred/materialF.glsl", GL_FRAGMENT_SHADER)); - gDeferredMaterialProgram[i].mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - - gDeferredMaterialProgram[i].clearPermutations(); - - bool has_normal_map = (i & 0x8) > 0; - bool has_specular_map = (i & 0x4) > 0; - - if (has_normal_map) - { - gDeferredMaterialProgram[i].addPermutation("HAS_NORMAL_MAP", "1"); - } - - if (has_specular_map) - { - gDeferredMaterialProgram[i].addPermutation("HAS_SPECULAR_MAP", "1"); - } - - gDeferredMaterialProgram[i].addPermutation("DIFFUSE_ALPHA_MODE", llformat("%d", alpha_mode)); - - if (alpha_mode != 0) - { - gDeferredMaterialProgram[i].mFeatures.hasAlphaMask = true; - gDeferredMaterialProgram[i].addPermutation("HAS_ALPHA_MASK", "1"); - } - - if (use_sun_shadow) - { - gDeferredMaterialProgram[i].addPermutation("HAS_SUN_SHADOW", "1"); - } - - bool has_skin = i & 0x10; - gDeferredMaterialProgram[i].mFeatures.hasSrgb = true; - gDeferredMaterialProgram[i].mFeatures.encodesNormal = true; - gDeferredMaterialProgram[i].mFeatures.calculatesAtmospherics = true; - gDeferredMaterialProgram[i].mFeatures.hasAtmospherics = true; - gDeferredMaterialProgram[i].mFeatures.hasGamma = true; - gDeferredMaterialProgram[i].mFeatures.hasShadows = use_sun_shadow; - gDeferredMaterialProgram[i].mFeatures.hasReflectionProbes = true; - - if (has_skin) - { - gDeferredMaterialProgram[i].addPermutation("HAS_SKIN", "1"); - gDeferredMaterialProgram[i].mFeatures.hasObjectSkinning = true; - } - else - { - gDeferredMaterialProgram[i].mRiggedVariant = &gDeferredMaterialProgram[i + 0x10]; - } - - success = gDeferredMaterialProgram[i].createShader(NULL, NULL); - llassert(success); - } - } - - gDeferredMaterialProgram[1].mFeatures.hasLighting = true; - gDeferredMaterialProgram[5].mFeatures.hasLighting = true; - gDeferredMaterialProgram[9].mFeatures.hasLighting = true; - gDeferredMaterialProgram[13].mFeatures.hasLighting = true; - gDeferredMaterialProgram[1+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = true; - gDeferredMaterialProgram[5+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = true; - gDeferredMaterialProgram[9+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = true; - gDeferredMaterialProgram[13+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = true; - - if (success) - { - gDeferredPBROpaqueProgram.mName = "Deferred PBR Opaque Shader"; - gDeferredPBROpaqueProgram.mFeatures.encodesNormal = true; - gDeferredPBROpaqueProgram.mFeatures.hasSrgb = true; - - gDeferredPBROpaqueProgram.mShaderFiles.clear(); - gDeferredPBROpaqueProgram.mShaderFiles.push_back(make_pair("deferred/pbropaqueV.glsl", GL_VERTEX_SHADER)); - gDeferredPBROpaqueProgram.mShaderFiles.push_back(make_pair("deferred/pbropaqueF.glsl", GL_FRAGMENT_SHADER)); - gDeferredPBROpaqueProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - gDeferredPBROpaqueProgram.clearPermutations(); - - success = make_rigged_variant(gDeferredPBROpaqueProgram, gDeferredSkinnedPBROpaqueProgram); - if (success) - { - success = gDeferredPBROpaqueProgram.createShader(NULL, NULL); - } - llassert(success); - } - - if (success) - { - gPBRGlowProgram.mName = " PBR Glow Shader"; - gPBRGlowProgram.mFeatures.hasSrgb = true; - gPBRGlowProgram.mShaderFiles.clear(); - gPBRGlowProgram.mShaderFiles.push_back(make_pair("deferred/pbrglowV.glsl", GL_VERTEX_SHADER)); - gPBRGlowProgram.mShaderFiles.push_back(make_pair("deferred/pbrglowF.glsl", GL_FRAGMENT_SHADER)); - gPBRGlowProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - - success = make_rigged_variant(gPBRGlowProgram, gPBRGlowSkinnedProgram); - if (success) - { - success = gPBRGlowProgram.createShader(NULL, NULL); - } - llassert(success); - } - - if (success) - { - gHUDPBROpaqueProgram.mName = "HUD PBR Opaque Shader"; - gHUDPBROpaqueProgram.mFeatures.hasSrgb = true; - gHUDPBROpaqueProgram.mShaderFiles.clear(); - gHUDPBROpaqueProgram.mShaderFiles.push_back(make_pair("deferred/pbropaqueV.glsl", GL_VERTEX_SHADER)); - gHUDPBROpaqueProgram.mShaderFiles.push_back(make_pair("deferred/pbropaqueF.glsl", GL_FRAGMENT_SHADER)); - gHUDPBROpaqueProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - gHUDPBROpaqueProgram.clearPermutations(); - gHUDPBROpaqueProgram.addPermutation("IS_HUD", "1"); - - success = gHUDPBROpaqueProgram.createShader(NULL, NULL); - - llassert(success); - } - - - - if (success) - { - LLGLSLShader* shader = &gDeferredPBRAlphaProgram; - shader->mName = "Deferred PBR Alpha Shader"; - - shader->mFeatures.calculatesLighting = false; - shader->mFeatures.hasLighting = false; - shader->mFeatures.isAlphaLighting = true; - shader->mFeatures.hasSrgb = true; - shader->mFeatures.encodesNormal = true; - shader->mFeatures.calculatesAtmospherics = true; - shader->mFeatures.hasAtmospherics = true; - shader->mFeatures.hasGamma = true; - shader->mFeatures.hasShadows = use_sun_shadow; - shader->mFeatures.isDeferred = true; // include deferredUtils - shader->mFeatures.hasReflectionProbes = mShaderLevel[SHADER_DEFERRED]; - - shader->mShaderFiles.clear(); - shader->mShaderFiles.push_back(make_pair("deferred/pbralphaV.glsl", GL_VERTEX_SHADER)); - shader->mShaderFiles.push_back(make_pair("deferred/pbralphaF.glsl", GL_FRAGMENT_SHADER)); - - shader->clearPermutations(); - - U32 alpha_mode = LLMaterial::DIFFUSE_ALPHA_MODE_BLEND; - shader->addPermutation("DIFFUSE_ALPHA_MODE", llformat("%d", alpha_mode)); - shader->addPermutation("HAS_NORMAL_MAP", "1"); - shader->addPermutation("HAS_SPECULAR_MAP", "1"); // PBR: Packed: Occlusion, Metal, Roughness - shader->addPermutation("HAS_EMISSIVE_MAP", "1"); - shader->addPermutation("USE_VERTEX_COLOR", "1"); - - if (use_sun_shadow) - { - shader->addPermutation("HAS_SUN_SHADOW", "1"); - } - - shader->mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = make_rigged_variant(*shader, gDeferredSkinnedPBRAlphaProgram); - if (success) - { - success = shader->createShader(NULL, NULL); - } - llassert(success); - - // Alpha Shader Hack - // See: LLRender::syncMatrices() - shader->mFeatures.calculatesLighting = true; - shader->mFeatures.hasLighting = true; - - shader->mRiggedVariant->mFeatures.calculatesLighting = true; - shader->mRiggedVariant->mFeatures.hasLighting = true; - } - - if (success) - { - LLGLSLShader* shader = &gHUDPBRAlphaProgram; - shader->mName = "HUD PBR Alpha Shader"; - - shader->mFeatures.hasSrgb = true; - - shader->mShaderFiles.clear(); - shader->mShaderFiles.push_back(make_pair("deferred/pbralphaV.glsl", GL_VERTEX_SHADER)); - shader->mShaderFiles.push_back(make_pair("deferred/pbralphaF.glsl", GL_FRAGMENT_SHADER)); - - shader->clearPermutations(); - - shader->addPermutation("IS_HUD", "1"); - - shader->mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = shader->createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredTreeProgram.mName = "Deferred Tree Shader"; - gDeferredTreeProgram.mShaderFiles.clear(); - gDeferredTreeProgram.mFeatures.encodesNormal = true; - gDeferredTreeProgram.mShaderFiles.push_back(make_pair("deferred/treeV.glsl", GL_VERTEX_SHADER)); - gDeferredTreeProgram.mShaderFiles.push_back(make_pair("deferred/treeF.glsl", GL_FRAGMENT_SHADER)); - gDeferredTreeProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gDeferredTreeProgram.createShader(NULL, NULL); - } - - if (success) - { - gDeferredTreeShadowProgram.mName = "Deferred Tree Shadow Shader"; - gDeferredTreeShadowProgram.mShaderFiles.clear(); - gDeferredTreeShadowProgram.mShaderFiles.push_back(make_pair("deferred/treeShadowV.glsl", GL_VERTEX_SHADER)); - gDeferredTreeShadowProgram.mShaderFiles.push_back(make_pair("deferred/treeShadowF.glsl", GL_FRAGMENT_SHADER)); - gDeferredTreeShadowProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - gDeferredTreeShadowProgram.mRiggedVariant = &gDeferredSkinnedTreeShadowProgram; - success = gDeferredTreeShadowProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredSkinnedTreeShadowProgram.mName = "Deferred Skinned Tree Shadow Shader"; - gDeferredSkinnedTreeShadowProgram.mShaderFiles.clear(); - gDeferredSkinnedTreeShadowProgram.mFeatures.hasObjectSkinning = true; - gDeferredSkinnedTreeShadowProgram.mShaderFiles.push_back(make_pair("deferred/treeShadowSkinnedV.glsl", GL_VERTEX_SHADER)); - gDeferredSkinnedTreeShadowProgram.mShaderFiles.push_back(make_pair("deferred/treeShadowF.glsl", GL_FRAGMENT_SHADER)); - gDeferredSkinnedTreeShadowProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gDeferredSkinnedTreeShadowProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredImpostorProgram.mName = "Deferred Impostor Shader"; - gDeferredImpostorProgram.mFeatures.hasSrgb = true; - gDeferredImpostorProgram.mFeatures.encodesNormal = true; - //gDeferredImpostorProgram.mFeatures.isDeferred = true; - gDeferredImpostorProgram.mShaderFiles.clear(); - gDeferredImpostorProgram.mShaderFiles.push_back(make_pair("deferred/impostorV.glsl", GL_VERTEX_SHADER)); - gDeferredImpostorProgram.mShaderFiles.push_back(make_pair("deferred/impostorF.glsl", GL_FRAGMENT_SHADER)); - gDeferredImpostorProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gDeferredImpostorProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredLightProgram.mName = "Deferred Light Shader"; - gDeferredLightProgram.mFeatures.isDeferred = true; - gDeferredLightProgram.mFeatures.hasShadows = true; - gDeferredLightProgram.mFeatures.hasSrgb = true; - - gDeferredLightProgram.mShaderFiles.clear(); - gDeferredLightProgram.mShaderFiles.push_back(make_pair("deferred/pointLightV.glsl", GL_VERTEX_SHADER)); - gDeferredLightProgram.mShaderFiles.push_back(make_pair("deferred/pointLightF.glsl", GL_FRAGMENT_SHADER)); - gDeferredLightProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - - gDeferredLightProgram.clearPermutations(); - - success = gDeferredLightProgram.createShader(NULL, NULL); - llassert(success); - } - - for (U32 i = 0; i < LL_DEFERRED_MULTI_LIGHT_COUNT; i++) - { - if (success) - { - gDeferredMultiLightProgram[i].mName = llformat("Deferred MultiLight Shader %d", i); - gDeferredMultiLightProgram[i].mFeatures.isDeferred = true; - gDeferredMultiLightProgram[i].mFeatures.hasShadows = true; - gDeferredMultiLightProgram[i].mFeatures.hasSrgb = true; - - gDeferredMultiLightProgram[i].clearPermutations(); - gDeferredMultiLightProgram[i].mShaderFiles.clear(); - gDeferredMultiLightProgram[i].mShaderFiles.push_back(make_pair("deferred/multiPointLightV.glsl", GL_VERTEX_SHADER)); - gDeferredMultiLightProgram[i].mShaderFiles.push_back(make_pair("deferred/multiPointLightF.glsl", GL_FRAGMENT_SHADER)); - gDeferredMultiLightProgram[i].mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - gDeferredMultiLightProgram[i].addPermutation("LIGHT_COUNT", llformat("%d", i+1)); - - success = gDeferredMultiLightProgram[i].createShader(NULL, NULL); - llassert(success); - } - } - - if (success) - { - gDeferredSpotLightProgram.mName = "Deferred SpotLight Shader"; - gDeferredSpotLightProgram.mShaderFiles.clear(); - gDeferredSpotLightProgram.mFeatures.hasSrgb = true; - gDeferredSpotLightProgram.mFeatures.isDeferred = true; - gDeferredSpotLightProgram.mFeatures.hasShadows = true; - - gDeferredSpotLightProgram.clearPermutations(); - gDeferredSpotLightProgram.mShaderFiles.push_back(make_pair("deferred/pointLightV.glsl", GL_VERTEX_SHADER)); - gDeferredSpotLightProgram.mShaderFiles.push_back(make_pair("deferred/spotLightF.glsl", GL_FRAGMENT_SHADER)); - gDeferredSpotLightProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - - success = gDeferredSpotLightProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredMultiSpotLightProgram.mName = "Deferred MultiSpotLight Shader"; - gDeferredMultiSpotLightProgram.mFeatures.hasSrgb = true; - gDeferredMultiSpotLightProgram.mFeatures.isDeferred = true; - gDeferredMultiSpotLightProgram.mFeatures.hasShadows = true; - - gDeferredMultiSpotLightProgram.clearPermutations(); - gDeferredMultiSpotLightProgram.addPermutation("MULTI_SPOTLIGHT", "1"); - gDeferredMultiSpotLightProgram.mShaderFiles.clear(); - gDeferredMultiSpotLightProgram.mShaderFiles.push_back(make_pair("deferred/multiPointLightV.glsl", GL_VERTEX_SHADER)); - gDeferredMultiSpotLightProgram.mShaderFiles.push_back(make_pair("deferred/spotLightF.glsl", GL_FRAGMENT_SHADER)); - gDeferredMultiSpotLightProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - - success = gDeferredMultiSpotLightProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - std::string fragment; - std::string vertex = "deferred/sunLightV.glsl"; - - bool use_ao = gSavedSettings.getBOOL("RenderDeferredSSAO"); - - if (use_ao) - { - fragment = "deferred/sunLightSSAOF.glsl"; - } - else - { - fragment = "deferred/sunLightF.glsl"; - if (mShaderLevel[SHADER_DEFERRED] == 1) - { //no shadows, no SSAO, no frag coord - vertex = "deferred/sunLightNoFragCoordV.glsl"; - } - } - - gDeferredSunProgram.mName = "Deferred Sun Shader"; - gDeferredSunProgram.mFeatures.isDeferred = true; - gDeferredSunProgram.mFeatures.hasShadows = true; - gDeferredSunProgram.mFeatures.hasAmbientOcclusion = use_ao; - - gDeferredSunProgram.mShaderFiles.clear(); - gDeferredSunProgram.mShaderFiles.push_back(make_pair(vertex, GL_VERTEX_SHADER)); - gDeferredSunProgram.mShaderFiles.push_back(make_pair(fragment, GL_FRAGMENT_SHADER)); - gDeferredSunProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - - success = gDeferredSunProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredBlurLightProgram.mName = "Deferred Blur Light Shader"; - gDeferredBlurLightProgram.mFeatures.isDeferred = true; - - gDeferredBlurLightProgram.mShaderFiles.clear(); - gDeferredBlurLightProgram.mShaderFiles.push_back(make_pair("deferred/blurLightV.glsl", GL_VERTEX_SHADER)); - gDeferredBlurLightProgram.mShaderFiles.push_back(make_pair("deferred/blurLightF.glsl", GL_FRAGMENT_SHADER)); - gDeferredBlurLightProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - - success = gDeferredBlurLightProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - for (int i = 0; i < 3 && success; ++i) - { - LLGLSLShader* shader = nullptr; - bool rigged = (i == 1); - bool hud = (i == 2); - - if (hud) - { - shader = &gHUDAlphaProgram; - shader->mName = "HUD Alpha Shader"; - } - else if (!rigged) - { - shader = &gDeferredAlphaProgram; - shader->mName = "Deferred Alpha Shader"; - shader->mRiggedVariant = &gDeferredSkinnedAlphaProgram; - } - else - { - shader = &gDeferredSkinnedAlphaProgram; - shader->mName = "Skinned Deferred Alpha Shader"; - shader->mFeatures.hasObjectSkinning = true; - } - - shader->mFeatures.calculatesLighting = false; - shader->mFeatures.hasLighting = false; - shader->mFeatures.isAlphaLighting = true; - shader->mFeatures.disableTextureIndex = true; //hack to disable auto-setup of texture channels - shader->mFeatures.hasSrgb = true; - shader->mFeatures.encodesNormal = true; - shader->mFeatures.calculatesAtmospherics = true; - shader->mFeatures.hasAtmospherics = true; - shader->mFeatures.hasGamma = true; - shader->mFeatures.hasShadows = use_sun_shadow; - shader->mFeatures.hasReflectionProbes = true; - shader->mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; - - shader->mShaderFiles.clear(); - shader->mShaderFiles.push_back(make_pair("deferred/alphaV.glsl", GL_VERTEX_SHADER)); - shader->mShaderFiles.push_back(make_pair("deferred/alphaF.glsl", GL_FRAGMENT_SHADER)); - - shader->clearPermutations(); - shader->addPermutation("USE_VERTEX_COLOR", "1"); - shader->addPermutation("HAS_ALPHA_MASK", "1"); - shader->addPermutation("USE_INDEXED_TEX", "1"); - if (use_sun_shadow) - { - shader->addPermutation("HAS_SUN_SHADOW", "1"); - } - - if (rigged) - { - shader->addPermutation("HAS_SKIN", "1"); - } - - if (hud) - { - shader->addPermutation("IS_HUD", "1"); - } - - shader->mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - - success = shader->createShader(NULL, NULL); - llassert(success); - - // Hack - shader->mFeatures.calculatesLighting = true; - shader->mFeatures.hasLighting = true; - } - } - - if (success) - { - LLGLSLShader* shaders[] = { - &gDeferredAlphaImpostorProgram, - &gDeferredSkinnedAlphaImpostorProgram - }; - - for (int i = 0; i < 2 && success; ++i) - { - bool rigged = i == 1; - LLGLSLShader* shader = shaders[i]; - - shader->mName = rigged ? "Skinned Deferred Alpha Impostor Shader" : "Deferred Alpha Impostor Shader"; - - // Begin Hack - shader->mFeatures.calculatesLighting = false; - shader->mFeatures.hasLighting = false; - - shader->mFeatures.hasSrgb = true; - shader->mFeatures.isAlphaLighting = true; - shader->mFeatures.encodesNormal = true; - shader->mFeatures.hasShadows = use_sun_shadow; - shader->mFeatures.hasReflectionProbes = true; - shader->mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; - - shader->mShaderFiles.clear(); - shader->mShaderFiles.push_back(make_pair("deferred/alphaV.glsl", GL_VERTEX_SHADER)); - shader->mShaderFiles.push_back(make_pair("deferred/alphaF.glsl", GL_FRAGMENT_SHADER)); - - shader->clearPermutations(); - shader->addPermutation("USE_INDEXED_TEX", "1"); - shader->addPermutation("FOR_IMPOSTOR", "1"); - shader->addPermutation("HAS_ALPHA_MASK", "1"); - shader->addPermutation("USE_VERTEX_COLOR", "1"); - if (rigged) - { - shader->mFeatures.hasObjectSkinning = true; - shader->addPermutation("HAS_SKIN", "1"); - } - - if (use_sun_shadow) - { - shader->addPermutation("HAS_SUN_SHADOW", "1"); - } - - shader->mRiggedVariant = &gDeferredSkinnedAlphaImpostorProgram; - shader->mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - if (!rigged) - { - shader->mRiggedVariant = shaders[1]; - } - success = shader->createShader(NULL, NULL); - llassert(success); - - // End Hack - shader->mFeatures.calculatesLighting = true; - shader->mFeatures.hasLighting = true; - } - } - - if (success) - { - gDeferredAvatarEyesProgram.mName = "Deferred Avatar Eyes Shader"; - gDeferredAvatarEyesProgram.mFeatures.calculatesAtmospherics = true; - gDeferredAvatarEyesProgram.mFeatures.hasGamma = true; - gDeferredAvatarEyesProgram.mFeatures.hasAtmospherics = true; - gDeferredAvatarEyesProgram.mFeatures.disableTextureIndex = true; - gDeferredAvatarEyesProgram.mFeatures.hasSrgb = true; - gDeferredAvatarEyesProgram.mFeatures.encodesNormal = true; - gDeferredAvatarEyesProgram.mFeatures.hasShadows = true; - - gDeferredAvatarEyesProgram.mShaderFiles.clear(); - gDeferredAvatarEyesProgram.mShaderFiles.push_back(make_pair("deferred/avatarEyesV.glsl", GL_VERTEX_SHADER)); - gDeferredAvatarEyesProgram.mShaderFiles.push_back(make_pair("deferred/diffuseF.glsl", GL_FRAGMENT_SHADER)); - gDeferredAvatarEyesProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gDeferredAvatarEyesProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredFullbrightProgram.mName = "Deferred Fullbright Shader"; - gDeferredFullbrightProgram.mFeatures.calculatesAtmospherics = true; - gDeferredFullbrightProgram.mFeatures.hasGamma = true; - gDeferredFullbrightProgram.mFeatures.hasAtmospherics = true; - gDeferredFullbrightProgram.mFeatures.hasSrgb = true; - gDeferredFullbrightProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; - gDeferredFullbrightProgram.mShaderFiles.clear(); - gDeferredFullbrightProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightV.glsl", GL_VERTEX_SHADER)); - gDeferredFullbrightProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightF.glsl", GL_FRAGMENT_SHADER)); - gDeferredFullbrightProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = make_rigged_variant(gDeferredFullbrightProgram, gDeferredSkinnedFullbrightProgram); - success = gDeferredFullbrightProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gHUDFullbrightProgram.mName = "HUD Fullbright Shader"; - gHUDFullbrightProgram.mFeatures.calculatesAtmospherics = true; - gHUDFullbrightProgram.mFeatures.hasGamma = true; - gHUDFullbrightProgram.mFeatures.hasAtmospherics = true; - gHUDFullbrightProgram.mFeatures.hasSrgb = true; - gHUDFullbrightProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; - gHUDFullbrightProgram.mShaderFiles.clear(); - gHUDFullbrightProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightV.glsl", GL_VERTEX_SHADER)); - gHUDFullbrightProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightF.glsl", GL_FRAGMENT_SHADER)); - gHUDFullbrightProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - gHUDFullbrightProgram.clearPermutations(); - gHUDFullbrightProgram.addPermutation("IS_HUD", "1"); - success = gHUDFullbrightProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredFullbrightAlphaMaskProgram.mName = "Deferred Fullbright Alpha Masking Shader"; - gDeferredFullbrightAlphaMaskProgram.mFeatures.calculatesAtmospherics = true; - gDeferredFullbrightAlphaMaskProgram.mFeatures.hasGamma = true; - gDeferredFullbrightAlphaMaskProgram.mFeatures.hasAtmospherics = true; - gDeferredFullbrightAlphaMaskProgram.mFeatures.hasSrgb = true; - gDeferredFullbrightAlphaMaskProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; - gDeferredFullbrightAlphaMaskProgram.mShaderFiles.clear(); - gDeferredFullbrightAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightV.glsl", GL_VERTEX_SHADER)); - gDeferredFullbrightAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightF.glsl", GL_FRAGMENT_SHADER)); - gDeferredFullbrightAlphaMaskProgram.clearPermutations(); - gDeferredFullbrightAlphaMaskProgram.addPermutation("HAS_ALPHA_MASK","1"); - gDeferredFullbrightAlphaMaskProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = make_rigged_variant(gDeferredFullbrightAlphaMaskProgram, gDeferredSkinnedFullbrightAlphaMaskProgram); - success = success && gDeferredFullbrightAlphaMaskProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gHUDFullbrightAlphaMaskProgram.mName = "HUD Fullbright Alpha Masking Shader"; - gHUDFullbrightAlphaMaskProgram.mFeatures.calculatesAtmospherics = true; - gHUDFullbrightAlphaMaskProgram.mFeatures.hasGamma = true; - gHUDFullbrightAlphaMaskProgram.mFeatures.hasAtmospherics = true; - gHUDFullbrightAlphaMaskProgram.mFeatures.hasSrgb = true; - gHUDFullbrightAlphaMaskProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; - gHUDFullbrightAlphaMaskProgram.mShaderFiles.clear(); - gHUDFullbrightAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightV.glsl", GL_VERTEX_SHADER)); - gHUDFullbrightAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightF.glsl", GL_FRAGMENT_SHADER)); - gHUDFullbrightAlphaMaskProgram.clearPermutations(); - gHUDFullbrightAlphaMaskProgram.addPermutation("HAS_ALPHA_MASK", "1"); - gHUDFullbrightAlphaMaskProgram.addPermutation("IS_HUD", "1"); - gHUDFullbrightAlphaMaskProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gHUDFullbrightAlphaMaskProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredFullbrightAlphaMaskAlphaProgram.mName = "Deferred Fullbright Alpha Masking Alpha Shader"; - gDeferredFullbrightAlphaMaskAlphaProgram.mFeatures.calculatesAtmospherics = true; - gDeferredFullbrightAlphaMaskAlphaProgram.mFeatures.hasGamma = true; - gDeferredFullbrightAlphaMaskAlphaProgram.mFeatures.hasAtmospherics = true; - gDeferredFullbrightAlphaMaskAlphaProgram.mFeatures.hasSrgb = true; - gDeferredFullbrightAlphaMaskAlphaProgram.mFeatures.isDeferred = true; - gDeferredFullbrightAlphaMaskAlphaProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; - gDeferredFullbrightAlphaMaskAlphaProgram.mShaderFiles.clear(); - gDeferredFullbrightAlphaMaskAlphaProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightV.glsl", GL_VERTEX_SHADER)); - gDeferredFullbrightAlphaMaskAlphaProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightF.glsl", GL_FRAGMENT_SHADER)); - gDeferredFullbrightAlphaMaskAlphaProgram.clearPermutations(); - gDeferredFullbrightAlphaMaskAlphaProgram.addPermutation("HAS_ALPHA_MASK", "1"); - gDeferredFullbrightAlphaMaskAlphaProgram.addPermutation("IS_ALPHA", "1"); - gDeferredFullbrightAlphaMaskAlphaProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = make_rigged_variant(gDeferredFullbrightAlphaMaskAlphaProgram, gDeferredSkinnedFullbrightAlphaMaskAlphaProgram); - success = success && gDeferredFullbrightAlphaMaskAlphaProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gHUDFullbrightAlphaMaskAlphaProgram.mName = "HUD Fullbright Alpha Masking Alpha Shader"; - gHUDFullbrightAlphaMaskAlphaProgram.mFeatures.calculatesAtmospherics = true; - gHUDFullbrightAlphaMaskAlphaProgram.mFeatures.hasGamma = true; - gHUDFullbrightAlphaMaskAlphaProgram.mFeatures.hasAtmospherics = true; - gHUDFullbrightAlphaMaskAlphaProgram.mFeatures.hasSrgb = true; - gHUDFullbrightAlphaMaskAlphaProgram.mFeatures.isDeferred = true; - gHUDFullbrightAlphaMaskAlphaProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; - gHUDFullbrightAlphaMaskAlphaProgram.mShaderFiles.clear(); - gHUDFullbrightAlphaMaskAlphaProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightV.glsl", GL_VERTEX_SHADER)); - gHUDFullbrightAlphaMaskAlphaProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightF.glsl", GL_FRAGMENT_SHADER)); - gHUDFullbrightAlphaMaskAlphaProgram.clearPermutations(); - gHUDFullbrightAlphaMaskAlphaProgram.addPermutation("HAS_ALPHA_MASK", "1"); - gHUDFullbrightAlphaMaskAlphaProgram.addPermutation("IS_ALPHA", "1"); - gHUDFullbrightAlphaMaskAlphaProgram.addPermutation("IS_HUD", "1"); - gHUDFullbrightAlphaMaskAlphaProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = success && gHUDFullbrightAlphaMaskAlphaProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredFullbrightShinyProgram.mName = "Deferred FullbrightShiny Shader"; - gDeferredFullbrightShinyProgram.mFeatures.calculatesAtmospherics = true; - gDeferredFullbrightShinyProgram.mFeatures.hasAtmospherics = true; - gDeferredFullbrightShinyProgram.mFeatures.hasGamma = true; - gDeferredFullbrightShinyProgram.mFeatures.hasSrgb = true; - gDeferredFullbrightShinyProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; - gDeferredFullbrightShinyProgram.mShaderFiles.clear(); - gDeferredFullbrightShinyProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightShinyV.glsl", GL_VERTEX_SHADER)); - gDeferredFullbrightShinyProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightShinyF.glsl", GL_FRAGMENT_SHADER)); - gDeferredFullbrightShinyProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - gDeferredFullbrightShinyProgram.mFeatures.hasReflectionProbes = true; - success = make_rigged_variant(gDeferredFullbrightShinyProgram, gDeferredSkinnedFullbrightShinyProgram); - success = success && gDeferredFullbrightShinyProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gHUDFullbrightShinyProgram.mName = "HUD FullbrightShiny Shader"; - gHUDFullbrightShinyProgram.mFeatures.calculatesAtmospherics = true; - gHUDFullbrightShinyProgram.mFeatures.hasAtmospherics = true; - gHUDFullbrightShinyProgram.mFeatures.hasGamma = true; - gHUDFullbrightShinyProgram.mFeatures.hasSrgb = true; - gHUDFullbrightShinyProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; - gHUDFullbrightShinyProgram.mShaderFiles.clear(); - gHUDFullbrightShinyProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightShinyV.glsl", GL_VERTEX_SHADER)); - gHUDFullbrightShinyProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightShinyF.glsl", GL_FRAGMENT_SHADER)); - gHUDFullbrightShinyProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - gHUDFullbrightShinyProgram.mFeatures.hasReflectionProbes = true; - gHUDFullbrightShinyProgram.clearPermutations(); - gHUDFullbrightShinyProgram.addPermutation("IS_HUD", "1"); - success = gHUDFullbrightShinyProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredEmissiveProgram.mName = "Deferred Emissive Shader"; - gDeferredEmissiveProgram.mFeatures.calculatesAtmospherics = true; - gDeferredEmissiveProgram.mFeatures.hasGamma = true; - gDeferredEmissiveProgram.mFeatures.hasAtmospherics = true; - gDeferredEmissiveProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; - gDeferredEmissiveProgram.mShaderFiles.clear(); - gDeferredEmissiveProgram.mShaderFiles.push_back(make_pair("deferred/emissiveV.glsl", GL_VERTEX_SHADER)); - gDeferredEmissiveProgram.mShaderFiles.push_back(make_pair("deferred/emissiveF.glsl", GL_FRAGMENT_SHADER)); - gDeferredEmissiveProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = make_rigged_variant(gDeferredEmissiveProgram, gDeferredSkinnedEmissiveProgram); - success = success && gDeferredEmissiveProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredSoftenProgram.mName = "Deferred Soften Shader"; - gDeferredSoftenProgram.mShaderFiles.clear(); - gDeferredSoftenProgram.mFeatures.hasSrgb = true; - gDeferredSoftenProgram.mFeatures.calculatesAtmospherics = true; - gDeferredSoftenProgram.mFeatures.hasAtmospherics = true; - gDeferredSoftenProgram.mFeatures.hasGamma = true; - gDeferredSoftenProgram.mFeatures.isDeferred = true; - gDeferredSoftenProgram.mFeatures.hasShadows = use_sun_shadow; - gDeferredSoftenProgram.mFeatures.hasReflectionProbes = mShaderLevel[SHADER_DEFERRED] > 2; - - gDeferredSoftenProgram.clearPermutations(); - gDeferredSoftenProgram.mShaderFiles.push_back(make_pair("deferred/softenLightV.glsl", GL_VERTEX_SHADER)); - gDeferredSoftenProgram.mShaderFiles.push_back(make_pair("deferred/softenLightF.glsl", GL_FRAGMENT_SHADER)); - - gDeferredSoftenProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - - if (use_sun_shadow) - { - gDeferredSoftenProgram.addPermutation("HAS_SUN_SHADOW", "1"); - } - - if (gSavedSettings.getBOOL("RenderDeferredSSAO")) - { //if using SSAO, take screen space light map into account as if shadows are enabled - gDeferredSoftenProgram.mShaderLevel = llmax(gDeferredSoftenProgram.mShaderLevel, 2); - gDeferredSoftenProgram.addPermutation("HAS_SSAO", "1"); - } - - success = gDeferredSoftenProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gHazeProgram.mName = "Haze Shader"; - gHazeProgram.mShaderFiles.clear(); - gHazeProgram.mFeatures.hasSrgb = true; - gHazeProgram.mFeatures.calculatesAtmospherics = true; - gHazeProgram.mFeatures.hasAtmospherics = true; - gHazeProgram.mFeatures.hasGamma = true; - gHazeProgram.mFeatures.isDeferred = true; - gHazeProgram.mFeatures.hasShadows = use_sun_shadow; - gHazeProgram.mFeatures.hasReflectionProbes = mShaderLevel[SHADER_DEFERRED] > 2; - - gHazeProgram.clearPermutations(); - gHazeProgram.mShaderFiles.push_back(make_pair("deferred/softenLightV.glsl", GL_VERTEX_SHADER)); - gHazeProgram.mShaderFiles.push_back(make_pair("deferred/hazeF.glsl", GL_FRAGMENT_SHADER)); - - gHazeProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - - success = gHazeProgram.createShader(NULL, NULL); - llassert(success); - } - - - if (success) - { - gHazeWaterProgram.mName = "Water Haze Shader"; - gHazeWaterProgram.mShaderFiles.clear(); - gHazeWaterProgram.mShaderGroup = LLGLSLShader::SG_WATER; - gHazeWaterProgram.mFeatures.hasSrgb = true; - gHazeWaterProgram.mFeatures.calculatesAtmospherics = true; - gHazeWaterProgram.mFeatures.hasAtmospherics = true; - gHazeWaterProgram.mFeatures.hasGamma = true; - gHazeWaterProgram.mFeatures.isDeferred = true; - gHazeWaterProgram.mFeatures.hasShadows = use_sun_shadow; - gHazeWaterProgram.mFeatures.hasReflectionProbes = mShaderLevel[SHADER_DEFERRED] > 2; - - gHazeWaterProgram.clearPermutations(); - gHazeWaterProgram.mShaderFiles.push_back(make_pair("deferred/waterHazeV.glsl", GL_VERTEX_SHADER)); - gHazeWaterProgram.mShaderFiles.push_back(make_pair("deferred/waterHazeF.glsl", GL_FRAGMENT_SHADER)); - - gHazeWaterProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - - success = gHazeWaterProgram.createShader(NULL, NULL); - llassert(success); - } - - - if (success) - { - gDeferredShadowProgram.mName = "Deferred Shadow Shader"; - gDeferredShadowProgram.mShaderFiles.clear(); - gDeferredShadowProgram.mShaderFiles.push_back(make_pair("deferred/shadowV.glsl", GL_VERTEX_SHADER)); - gDeferredShadowProgram.mShaderFiles.push_back(make_pair("deferred/shadowF.glsl", GL_FRAGMENT_SHADER)); - gDeferredShadowProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - gDeferredShadowProgram.mRiggedVariant = &gDeferredSkinnedShadowProgram; - success = gDeferredShadowProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredSkinnedShadowProgram.mName = "Deferred Skinned Shadow Shader"; - gDeferredSkinnedShadowProgram.mFeatures.isDeferred = true; - gDeferredSkinnedShadowProgram.mFeatures.hasShadows = true; - gDeferredSkinnedShadowProgram.mFeatures.hasObjectSkinning = true; - gDeferredSkinnedShadowProgram.mShaderFiles.clear(); - gDeferredSkinnedShadowProgram.mShaderFiles.push_back(make_pair("deferred/shadowSkinnedV.glsl", GL_VERTEX_SHADER)); - gDeferredSkinnedShadowProgram.mShaderFiles.push_back(make_pair("deferred/shadowF.glsl", GL_FRAGMENT_SHADER)); - gDeferredSkinnedShadowProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - // gDeferredSkinnedShadowProgram.addPermutation("DEPTH_CLAMP", "1"); // disable depth clamp for now - success = gDeferredSkinnedShadowProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredShadowCubeProgram.mName = "Deferred Shadow Cube Shader"; - gDeferredShadowCubeProgram.mFeatures.isDeferred = true; - gDeferredShadowCubeProgram.mFeatures.hasShadows = true; - gDeferredShadowCubeProgram.mShaderFiles.clear(); - gDeferredShadowCubeProgram.mShaderFiles.push_back(make_pair("deferred/shadowCubeV.glsl", GL_VERTEX_SHADER)); - gDeferredShadowCubeProgram.mShaderFiles.push_back(make_pair("deferred/shadowF.glsl", GL_FRAGMENT_SHADER)); - // gDeferredShadowCubeProgram.addPermutation("DEPTH_CLAMP", "1"); - gDeferredShadowCubeProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gDeferredShadowCubeProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredShadowFullbrightAlphaMaskProgram.mName = "Deferred Shadow Fullbright Alpha Mask Shader"; - gDeferredShadowFullbrightAlphaMaskProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; - - gDeferredShadowFullbrightAlphaMaskProgram.mShaderFiles.clear(); - gDeferredShadowFullbrightAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/shadowAlphaMaskV.glsl", GL_VERTEX_SHADER)); - gDeferredShadowFullbrightAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/shadowAlphaMaskF.glsl", GL_FRAGMENT_SHADER)); - - gDeferredShadowFullbrightAlphaMaskProgram.clearPermutations(); - gDeferredShadowFullbrightAlphaMaskProgram.addPermutation("DEPTH_CLAMP", "1"); - gDeferredShadowFullbrightAlphaMaskProgram.addPermutation("IS_FULLBRIGHT", "1"); - gDeferredShadowFullbrightAlphaMaskProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = make_rigged_variant(gDeferredShadowFullbrightAlphaMaskProgram, gDeferredSkinnedShadowFullbrightAlphaMaskProgram); - success = success && gDeferredShadowFullbrightAlphaMaskProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredShadowAlphaMaskProgram.mName = "Deferred Shadow Alpha Mask Shader"; - gDeferredShadowAlphaMaskProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; - - gDeferredShadowAlphaMaskProgram.mShaderFiles.clear(); - gDeferredShadowAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/shadowAlphaMaskV.glsl", GL_VERTEX_SHADER)); - gDeferredShadowAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/shadowAlphaMaskF.glsl", GL_FRAGMENT_SHADER)); - gDeferredShadowAlphaMaskProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = make_rigged_variant(gDeferredShadowAlphaMaskProgram, gDeferredSkinnedShadowAlphaMaskProgram); - success = success && gDeferredShadowAlphaMaskProgram.createShader(NULL, NULL); - llassert(success); - } - - - if (success) - { - gDeferredShadowGLTFAlphaMaskProgram.mName = "Deferred GLTF Shadow Alpha Mask Shader"; - gDeferredShadowGLTFAlphaMaskProgram.mShaderFiles.clear(); - gDeferredShadowGLTFAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/pbrShadowAlphaMaskV.glsl", GL_VERTEX_SHADER)); - gDeferredShadowGLTFAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/pbrShadowAlphaMaskF.glsl", GL_FRAGMENT_SHADER)); - gDeferredShadowGLTFAlphaMaskProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - gDeferredShadowGLTFAlphaMaskProgram.clearPermutations(); - success = make_rigged_variant(gDeferredShadowGLTFAlphaMaskProgram, gDeferredSkinnedShadowGLTFAlphaMaskProgram); - success = success && gDeferredShadowGLTFAlphaMaskProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredShadowGLTFAlphaBlendProgram.mName = "Deferred GLTF Shadow Alpha Blend Shader"; - gDeferredShadowGLTFAlphaBlendProgram.mShaderFiles.clear(); - gDeferredShadowGLTFAlphaBlendProgram.mShaderFiles.push_back(make_pair("deferred/pbrShadowAlphaMaskV.glsl", GL_VERTEX_SHADER)); - gDeferredShadowGLTFAlphaBlendProgram.mShaderFiles.push_back(make_pair("deferred/pbrShadowAlphaBlendF.glsl", GL_FRAGMENT_SHADER)); - gDeferredShadowGLTFAlphaBlendProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - gDeferredShadowGLTFAlphaBlendProgram.clearPermutations(); - success = make_rigged_variant(gDeferredShadowGLTFAlphaBlendProgram, gDeferredSkinnedShadowGLTFAlphaBlendProgram); - success = success && gDeferredShadowGLTFAlphaBlendProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredAvatarShadowProgram.mName = "Deferred Avatar Shadow Shader"; - gDeferredAvatarShadowProgram.mFeatures.hasSkinning = true; - - gDeferredAvatarShadowProgram.mShaderFiles.clear(); - gDeferredAvatarShadowProgram.mShaderFiles.push_back(make_pair("deferred/avatarShadowV.glsl", GL_VERTEX_SHADER)); - gDeferredAvatarShadowProgram.mShaderFiles.push_back(make_pair("deferred/avatarShadowF.glsl", GL_FRAGMENT_SHADER)); - gDeferredAvatarShadowProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gDeferredAvatarShadowProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredAvatarAlphaShadowProgram.mName = "Deferred Avatar Alpha Shadow Shader"; - gDeferredAvatarAlphaShadowProgram.mFeatures.hasSkinning = true; - gDeferredAvatarAlphaShadowProgram.mShaderFiles.clear(); - gDeferredAvatarAlphaShadowProgram.mShaderFiles.push_back(make_pair("deferred/avatarAlphaShadowV.glsl", GL_VERTEX_SHADER)); - gDeferredAvatarAlphaShadowProgram.mShaderFiles.push_back(make_pair("deferred/avatarAlphaShadowF.glsl", GL_FRAGMENT_SHADER)); - gDeferredAvatarAlphaShadowProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gDeferredAvatarAlphaShadowProgram.createShader(NULL, NULL); - llassert(success); - } - if (success) - { - gDeferredAvatarAlphaMaskShadowProgram.mName = "Deferred Avatar Alpha Mask Shadow Shader"; - gDeferredAvatarAlphaMaskShadowProgram.mFeatures.hasSkinning = true; - gDeferredAvatarAlphaMaskShadowProgram.mShaderFiles.clear(); - gDeferredAvatarAlphaMaskShadowProgram.mShaderFiles.push_back(make_pair("deferred/avatarAlphaShadowV.glsl", GL_VERTEX_SHADER)); - gDeferredAvatarAlphaMaskShadowProgram.mShaderFiles.push_back(make_pair("deferred/avatarAlphaMaskShadowF.glsl", GL_FRAGMENT_SHADER)); - gDeferredAvatarAlphaMaskShadowProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gDeferredAvatarAlphaMaskShadowProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredTerrainProgram.mName = "Deferred Terrain Shader"; - gDeferredTerrainProgram.mFeatures.encodesNormal = true; - gDeferredTerrainProgram.mFeatures.hasSrgb = true; - gDeferredTerrainProgram.mFeatures.calculatesLighting = false; - gDeferredTerrainProgram.mFeatures.hasLighting = false; - gDeferredTerrainProgram.mFeatures.isAlphaLighting = true; - gDeferredTerrainProgram.mFeatures.disableTextureIndex = true; //hack to disable auto-setup of texture channels - gDeferredTerrainProgram.mFeatures.calculatesAtmospherics = true; - gDeferredTerrainProgram.mFeatures.hasAtmospherics = true; - gDeferredTerrainProgram.mFeatures.hasGamma = true; - - gDeferredTerrainProgram.mShaderFiles.clear(); - gDeferredTerrainProgram.mShaderFiles.push_back(make_pair("deferred/terrainV.glsl", GL_VERTEX_SHADER)); - gDeferredTerrainProgram.mShaderFiles.push_back(make_pair("deferred/terrainF.glsl", GL_FRAGMENT_SHADER)); - gDeferredTerrainProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gDeferredTerrainProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredAvatarProgram.mName = "Deferred Avatar Shader"; - gDeferredAvatarProgram.mFeatures.hasSkinning = true; - gDeferredAvatarProgram.mFeatures.encodesNormal = true; - gDeferredAvatarProgram.mShaderFiles.clear(); - gDeferredAvatarProgram.mShaderFiles.push_back(make_pair("deferred/avatarV.glsl", GL_VERTEX_SHADER)); - gDeferredAvatarProgram.mShaderFiles.push_back(make_pair("deferred/avatarF.glsl", GL_FRAGMENT_SHADER)); - gDeferredAvatarProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gDeferredAvatarProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredAvatarAlphaProgram.mName = "Deferred Avatar Alpha Shader"; - gDeferredAvatarAlphaProgram.mFeatures.hasSkinning = true; - gDeferredAvatarAlphaProgram.mFeatures.calculatesLighting = false; - gDeferredAvatarAlphaProgram.mFeatures.hasLighting = false; - gDeferredAvatarAlphaProgram.mFeatures.isAlphaLighting = true; - gDeferredAvatarAlphaProgram.mFeatures.disableTextureIndex = true; - gDeferredAvatarAlphaProgram.mFeatures.hasSrgb = true; - gDeferredAvatarAlphaProgram.mFeatures.encodesNormal = true; - gDeferredAvatarAlphaProgram.mFeatures.calculatesAtmospherics = true; - gDeferredAvatarAlphaProgram.mFeatures.hasAtmospherics = true; - gDeferredAvatarAlphaProgram.mFeatures.hasGamma = true; - gDeferredAvatarAlphaProgram.mFeatures.isDeferred = true; - gDeferredAvatarAlphaProgram.mFeatures.hasShadows = true; - gDeferredAvatarAlphaProgram.mFeatures.hasReflectionProbes = true; - - gDeferredAvatarAlphaProgram.mShaderFiles.clear(); - gDeferredAvatarAlphaProgram.mShaderFiles.push_back(make_pair("deferred/alphaV.glsl", GL_VERTEX_SHADER)); - gDeferredAvatarAlphaProgram.mShaderFiles.push_back(make_pair("deferred/alphaF.glsl", GL_FRAGMENT_SHADER)); - - gDeferredAvatarAlphaProgram.clearPermutations(); - gDeferredAvatarAlphaProgram.addPermutation("USE_DIFFUSE_TEX", "1"); - gDeferredAvatarAlphaProgram.addPermutation("IS_AVATAR_SKIN", "1"); - if (use_sun_shadow) - { - gDeferredAvatarAlphaProgram.addPermutation("HAS_SUN_SHADOW", "1"); - } - - gDeferredAvatarAlphaProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - - success = gDeferredAvatarAlphaProgram.createShader(NULL, NULL); - llassert(success); - - gDeferredAvatarAlphaProgram.mFeatures.calculatesLighting = true; - gDeferredAvatarAlphaProgram.mFeatures.hasLighting = true; - } - - if (success) - { - gExposureProgram.mName = "Exposure"; - gExposureProgram.mFeatures.hasSrgb = true; - gExposureProgram.mFeatures.isDeferred = true; - gExposureProgram.mShaderFiles.clear(); - gExposureProgram.clearPermutations(); - gExposureProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER)); - gExposureProgram.mShaderFiles.push_back(make_pair("deferred/exposureF.glsl", GL_FRAGMENT_SHADER)); - gExposureProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gExposureProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gLuminanceProgram.mName = "Luminance"; - gLuminanceProgram.mShaderFiles.clear(); - gLuminanceProgram.clearPermutations(); - gLuminanceProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER)); - gLuminanceProgram.mShaderFiles.push_back(make_pair("deferred/luminanceF.glsl", GL_FRAGMENT_SHADER)); - gLuminanceProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gLuminanceProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredPostGammaCorrectProgram.mName = "Deferred Gamma Correction Post Process"; - gDeferredPostGammaCorrectProgram.mFeatures.hasSrgb = true; - gDeferredPostGammaCorrectProgram.mFeatures.isDeferred = true; - gDeferredPostGammaCorrectProgram.mShaderFiles.clear(); - gDeferredPostGammaCorrectProgram.clearPermutations(); - gDeferredPostGammaCorrectProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER)); - gDeferredPostGammaCorrectProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredGammaCorrect.glsl", GL_FRAGMENT_SHADER)); - gDeferredPostGammaCorrectProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gDeferredPostGammaCorrectProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gNoPostGammaCorrectProgram.mName = "No Post Gamma Correction Post Process"; - gNoPostGammaCorrectProgram.mFeatures.hasSrgb = true; - gNoPostGammaCorrectProgram.mFeatures.isDeferred = true; - gNoPostGammaCorrectProgram.mShaderFiles.clear(); - gNoPostGammaCorrectProgram.clearPermutations(); - gNoPostGammaCorrectProgram.addPermutation("NO_POST", "1"); - gNoPostGammaCorrectProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER)); - gNoPostGammaCorrectProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredGammaCorrect.glsl", GL_FRAGMENT_SHADER)); - gNoPostGammaCorrectProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gNoPostGammaCorrectProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gLegacyPostGammaCorrectProgram.mName = "Legacy Gamma Correction Post Process"; - gLegacyPostGammaCorrectProgram.mFeatures.hasSrgb = true; - gLegacyPostGammaCorrectProgram.mFeatures.isDeferred = true; - gLegacyPostGammaCorrectProgram.mShaderFiles.clear(); - gLegacyPostGammaCorrectProgram.clearPermutations(); - gLegacyPostGammaCorrectProgram.addPermutation("LEGACY_GAMMA", "1"); - gLegacyPostGammaCorrectProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER)); - gLegacyPostGammaCorrectProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredGammaCorrect.glsl", GL_FRAGMENT_SHADER)); - gLegacyPostGammaCorrectProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gLegacyPostGammaCorrectProgram.createShader(NULL, NULL); - llassert(success); - } - - - if (success && gGLManager.mGLVersion > 3.9f) - { - gFXAAProgram.mName = "FXAA Shader"; - gFXAAProgram.mFeatures.isDeferred = true; - gFXAAProgram.mShaderFiles.clear(); - gFXAAProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredV.glsl", GL_VERTEX_SHADER)); - gFXAAProgram.mShaderFiles.push_back(make_pair("deferred/fxaaF.glsl", GL_FRAGMENT_SHADER)); - gFXAAProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gFXAAProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredPostProgram.mName = "Deferred Post Shader"; - gDeferredPostProgram.mFeatures.isDeferred = true; - gDeferredPostProgram.mShaderFiles.clear(); - gDeferredPostProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER)); - gDeferredPostProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredF.glsl", GL_FRAGMENT_SHADER)); - gDeferredPostProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gDeferredPostProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredCoFProgram.mName = "Deferred CoF Shader"; - gDeferredCoFProgram.mShaderFiles.clear(); - gDeferredCoFProgram.mFeatures.isDeferred = true; - gDeferredCoFProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER)); - gDeferredCoFProgram.mShaderFiles.push_back(make_pair("deferred/cofF.glsl", GL_FRAGMENT_SHADER)); - gDeferredCoFProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gDeferredCoFProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredDoFCombineProgram.mName = "Deferred DoFCombine Shader"; - gDeferredDoFCombineProgram.mFeatures.isDeferred = true; - gDeferredDoFCombineProgram.mShaderFiles.clear(); - gDeferredDoFCombineProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER)); - gDeferredDoFCombineProgram.mShaderFiles.push_back(make_pair("deferred/dofCombineF.glsl", GL_FRAGMENT_SHADER)); - gDeferredDoFCombineProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gDeferredDoFCombineProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredPostNoDoFProgram.mName = "Deferred Post NoDoF Shader"; - gDeferredPostNoDoFProgram.mFeatures.isDeferred = true; - gDeferredPostNoDoFProgram.mShaderFiles.clear(); - gDeferredPostNoDoFProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER)); - gDeferredPostNoDoFProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoDoFF.glsl", GL_FRAGMENT_SHADER)); - gDeferredPostNoDoFProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gDeferredPostNoDoFProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredWLSkyProgram.mName = "Deferred Windlight Sky Shader"; - gDeferredWLSkyProgram.mShaderFiles.clear(); - gDeferredWLSkyProgram.mFeatures.calculatesAtmospherics = true; - gDeferredWLSkyProgram.mFeatures.hasAtmospherics = true; - gDeferredWLSkyProgram.mFeatures.hasGamma = true; - gDeferredWLSkyProgram.mFeatures.hasSrgb = true; - - gDeferredWLSkyProgram.mShaderFiles.push_back(make_pair("deferred/skyV.glsl", GL_VERTEX_SHADER)); - gDeferredWLSkyProgram.mShaderFiles.push_back(make_pair("deferred/skyF.glsl", GL_FRAGMENT_SHADER)); - gDeferredWLSkyProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - gDeferredWLSkyProgram.mShaderGroup = LLGLSLShader::SG_SKY; - - success = gDeferredWLSkyProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredWLCloudProgram.mName = "Deferred Windlight Cloud Program"; - gDeferredWLCloudProgram.mShaderFiles.clear(); - gDeferredWLCloudProgram.mFeatures.calculatesAtmospherics = true; - gDeferredWLCloudProgram.mFeatures.hasAtmospherics = true; - gDeferredWLCloudProgram.mFeatures.hasGamma = true; - gDeferredWLCloudProgram.mFeatures.hasSrgb = true; - - gDeferredWLCloudProgram.mShaderFiles.push_back(make_pair("deferred/cloudsV.glsl", GL_VERTEX_SHADER)); - gDeferredWLCloudProgram.mShaderFiles.push_back(make_pair("deferred/cloudsF.glsl", GL_FRAGMENT_SHADER)); - gDeferredWLCloudProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - gDeferredWLCloudProgram.mShaderGroup = LLGLSLShader::SG_SKY; - gDeferredWLCloudProgram.addConstant( LLGLSLShader::SHADER_CONST_CLOUD_MOON_DEPTH ); // SL-14113 - success = gDeferredWLCloudProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredWLSunProgram.mName = "Deferred Windlight Sun Program"; - gDeferredWLSunProgram.mFeatures.calculatesAtmospherics = true; - gDeferredWLSunProgram.mFeatures.hasAtmospherics = true; - gDeferredWLSunProgram.mFeatures.hasGamma = true; - gDeferredWLSunProgram.mFeatures.hasAtmospherics = true; - gDeferredWLSunProgram.mFeatures.disableTextureIndex = true; - gDeferredWLSunProgram.mFeatures.hasSrgb = true; - gDeferredWLSunProgram.mShaderFiles.clear(); - gDeferredWLSunProgram.mShaderFiles.push_back(make_pair("deferred/sunDiscV.glsl", GL_VERTEX_SHADER)); - gDeferredWLSunProgram.mShaderFiles.push_back(make_pair("deferred/sunDiscF.glsl", GL_FRAGMENT_SHADER)); - gDeferredWLSunProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - gDeferredWLSunProgram.mShaderGroup = LLGLSLShader::SG_SKY; - success = gDeferredWLSunProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredWLMoonProgram.mName = "Deferred Windlight Moon Program"; - gDeferredWLMoonProgram.mFeatures.calculatesAtmospherics = true; - gDeferredWLMoonProgram.mFeatures.hasAtmospherics = true; - gDeferredWLMoonProgram.mFeatures.hasGamma = true; - gDeferredWLMoonProgram.mFeatures.hasAtmospherics = true; - gDeferredWLMoonProgram.mFeatures.hasSrgb = true; - gDeferredWLMoonProgram.mFeatures.disableTextureIndex = true; - - gDeferredWLMoonProgram.mShaderFiles.clear(); - gDeferredWLMoonProgram.mShaderFiles.push_back(make_pair("deferred/moonV.glsl", GL_VERTEX_SHADER)); - gDeferredWLMoonProgram.mShaderFiles.push_back(make_pair("deferred/moonF.glsl", GL_FRAGMENT_SHADER)); - gDeferredWLMoonProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - gDeferredWLMoonProgram.mShaderGroup = LLGLSLShader::SG_SKY; - gDeferredWLMoonProgram.addConstant( LLGLSLShader::SHADER_CONST_CLOUD_MOON_DEPTH ); // SL-14113 - success = gDeferredWLMoonProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gDeferredStarProgram.mName = "Deferred Star Program"; - gDeferredStarProgram.mShaderFiles.clear(); - gDeferredStarProgram.mShaderFiles.push_back(make_pair("deferred/starsV.glsl", GL_VERTEX_SHADER)); - gDeferredStarProgram.mShaderFiles.push_back(make_pair("deferred/starsF.glsl", GL_FRAGMENT_SHADER)); - gDeferredStarProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - gDeferredStarProgram.mShaderGroup = LLGLSLShader::SG_SKY; - gDeferredStarProgram.addConstant( LLGLSLShader::SHADER_CONST_STAR_DEPTH ); // SL-14113 - success = gDeferredStarProgram.createShader(NULL, NULL); - llassert(success); - } - - if (success) - { - gNormalMapGenProgram.mName = "Normal Map Generation Program"; - gNormalMapGenProgram.mShaderFiles.clear(); - gNormalMapGenProgram.mShaderFiles.push_back(make_pair("deferred/normgenV.glsl", GL_VERTEX_SHADER)); - gNormalMapGenProgram.mShaderFiles.push_back(make_pair("deferred/normgenF.glsl", GL_FRAGMENT_SHADER)); - gNormalMapGenProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - gNormalMapGenProgram.mShaderGroup = LLGLSLShader::SG_SKY; - success = gNormalMapGenProgram.createShader(NULL, NULL); - } - - if (success) - { - gDeferredGenBrdfLutProgram.mName = "Brdf Gen Shader"; - gDeferredGenBrdfLutProgram.mShaderFiles.clear(); - gDeferredGenBrdfLutProgram.mShaderFiles.push_back(make_pair("deferred/genbrdflutV.glsl", GL_VERTEX_SHADER)); - gDeferredGenBrdfLutProgram.mShaderFiles.push_back(make_pair("deferred/genbrdflutF.glsl", GL_FRAGMENT_SHADER)); - gDeferredGenBrdfLutProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gDeferredGenBrdfLutProgram.createShader(NULL, NULL); - } - - if (success) { - gPostScreenSpaceReflectionProgram.mName = "Screen Space Reflection Post"; - gPostScreenSpaceReflectionProgram.mShaderFiles.clear(); - gPostScreenSpaceReflectionProgram.mShaderFiles.push_back(make_pair("deferred/screenSpaceReflPostV.glsl", GL_VERTEX_SHADER)); - gPostScreenSpaceReflectionProgram.mShaderFiles.push_back(make_pair("deferred/screenSpaceReflPostF.glsl", GL_FRAGMENT_SHADER)); - gPostScreenSpaceReflectionProgram.mFeatures.hasScreenSpaceReflections = true; - gPostScreenSpaceReflectionProgram.mFeatures.isDeferred = true; - gPostScreenSpaceReflectionProgram.mShaderLevel = 3; - success = gPostScreenSpaceReflectionProgram.createShader(NULL, NULL); - } - - if (success) { - gDeferredBufferVisualProgram.mName = "Deferred Buffer Visualization Shader"; - gDeferredBufferVisualProgram.mShaderFiles.clear(); - gDeferredBufferVisualProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER)); - gDeferredBufferVisualProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredVisualizeBuffers.glsl", GL_FRAGMENT_SHADER)); - gDeferredBufferVisualProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; - success = gDeferredBufferVisualProgram.createShader(NULL, NULL); - } - - return success; -} - -bool LLViewerShaderMgr::loadShadersObject() -{ - LL_PROFILE_ZONE_SCOPED; - bool success = true; - - if (success) - { - gObjectBumpProgram.mName = "Bump Shader"; - gObjectBumpProgram.mFeatures.encodesNormal = true; - gObjectBumpProgram.mShaderFiles.clear(); - gObjectBumpProgram.mShaderFiles.push_back(make_pair("objects/bumpV.glsl", GL_VERTEX_SHADER)); - gObjectBumpProgram.mShaderFiles.push_back(make_pair("objects/bumpF.glsl", GL_FRAGMENT_SHADER)); - gObjectBumpProgram.mShaderLevel = mShaderLevel[SHADER_OBJECT]; - success = make_rigged_variant(gObjectBumpProgram, gSkinnedObjectBumpProgram); - success = success && gObjectBumpProgram.createShader(NULL, NULL); - if (success) - { //lldrawpoolbump assumes "texture0" has channel 0 and "texture1" has channel 1 - LLGLSLShader* shader[] = { &gObjectBumpProgram, &gSkinnedObjectBumpProgram }; - for (int i = 0; i < 2; ++i) - { - shader[i]->bind(); - shader[i]->uniform1i(sTexture0, 0); - shader[i]->uniform1i(sTexture1, 1); - shader[i]->unbind(); - } - } - } - - if (success) - { - gObjectAlphaMaskNoColorProgram.mName = "No color alpha mask Shader"; - gObjectAlphaMaskNoColorProgram.mFeatures.calculatesLighting = true; - gObjectAlphaMaskNoColorProgram.mFeatures.calculatesAtmospherics = true; - gObjectAlphaMaskNoColorProgram.mFeatures.hasGamma = true; - gObjectAlphaMaskNoColorProgram.mFeatures.hasAtmospherics = true; - gObjectAlphaMaskNoColorProgram.mFeatures.hasLighting = true; - gObjectAlphaMaskNoColorProgram.mFeatures.disableTextureIndex = true; - gObjectAlphaMaskNoColorProgram.mFeatures.hasAlphaMask = true; - gObjectAlphaMaskNoColorProgram.mShaderFiles.clear(); - gObjectAlphaMaskNoColorProgram.mShaderFiles.push_back(make_pair("objects/simpleNoColorV.glsl", GL_VERTEX_SHADER)); - gObjectAlphaMaskNoColorProgram.mShaderFiles.push_back(make_pair("objects/simpleF.glsl", GL_FRAGMENT_SHADER)); - gObjectAlphaMaskNoColorProgram.mShaderLevel = mShaderLevel[SHADER_OBJECT]; - success = gObjectAlphaMaskNoColorProgram.createShader(NULL, NULL); - } - - if (success) - { - gImpostorProgram.mName = "Impostor Shader"; - gImpostorProgram.mFeatures.disableTextureIndex = true; - gImpostorProgram.mFeatures.hasSrgb = true; - gImpostorProgram.mShaderFiles.clear(); - gImpostorProgram.mShaderFiles.push_back(make_pair("objects/impostorV.glsl", GL_VERTEX_SHADER)); - gImpostorProgram.mShaderFiles.push_back(make_pair("objects/impostorF.glsl", GL_FRAGMENT_SHADER)); - gImpostorProgram.mShaderLevel = mShaderLevel[SHADER_OBJECT]; - success = gImpostorProgram.createShader(NULL, NULL); - } - - if (success) - { - gObjectPreviewProgram.mName = "Object Preview Shader"; - gObjectPreviewProgram.mFeatures.disableTextureIndex = true; - gObjectPreviewProgram.mShaderFiles.clear(); - gObjectPreviewProgram.mShaderFiles.push_back(make_pair("objects/previewV.glsl", GL_VERTEX_SHADER)); - gObjectPreviewProgram.mShaderFiles.push_back(make_pair("objects/previewF.glsl", GL_FRAGMENT_SHADER)); - gObjectPreviewProgram.mShaderLevel = mShaderLevel[SHADER_OBJECT]; - success = make_rigged_variant(gObjectPreviewProgram, gSkinnedObjectPreviewProgram); - success = gObjectPreviewProgram.createShader(NULL, NULL); - gObjectPreviewProgram.mFeatures.hasLighting = true; - gSkinnedObjectPreviewProgram.mFeatures.hasLighting = true; - } - - if (success) - { - gPhysicsPreviewProgram.mName = "Preview Physics Shader"; - gPhysicsPreviewProgram.mFeatures.calculatesLighting = false; - gPhysicsPreviewProgram.mFeatures.calculatesAtmospherics = false; - gPhysicsPreviewProgram.mFeatures.hasGamma = false; - gPhysicsPreviewProgram.mFeatures.hasAtmospherics = false; - gPhysicsPreviewProgram.mFeatures.hasLighting = false; - gPhysicsPreviewProgram.mFeatures.mIndexedTextureChannels = 0; - gPhysicsPreviewProgram.mFeatures.disableTextureIndex = true; - gPhysicsPreviewProgram.mShaderFiles.clear(); - gPhysicsPreviewProgram.mShaderFiles.push_back(make_pair("objects/previewPhysicsV.glsl", GL_VERTEX_SHADER)); - gPhysicsPreviewProgram.mShaderFiles.push_back(make_pair("objects/previewPhysicsF.glsl", GL_FRAGMENT_SHADER)); - gPhysicsPreviewProgram.mShaderLevel = mShaderLevel[SHADER_OBJECT]; - success = gPhysicsPreviewProgram.createShader(NULL, NULL); - gPhysicsPreviewProgram.mFeatures.hasLighting = false; - } - - if (!success) - { - mShaderLevel[SHADER_OBJECT] = 0; - return false; - } - - return true; -} - -bool LLViewerShaderMgr::loadShadersAvatar() -{ - LL_PROFILE_ZONE_SCOPED; -#if 1 // DEPRECATED -- forward rendering is deprecated - bool success = true; - - if (mShaderLevel[SHADER_AVATAR] == 0) - { - gAvatarProgram.unload(); - gAvatarEyeballProgram.unload(); - return true; - } - - if (success) - { - gAvatarProgram.mName = "Avatar Shader"; - gAvatarProgram.mFeatures.hasSkinning = true; - gAvatarProgram.mFeatures.calculatesAtmospherics = true; - gAvatarProgram.mFeatures.calculatesLighting = true; - gAvatarProgram.mFeatures.hasGamma = true; - gAvatarProgram.mFeatures.hasAtmospherics = true; - gAvatarProgram.mFeatures.hasLighting = true; - gAvatarProgram.mFeatures.hasAlphaMask = true; - gAvatarProgram.mFeatures.disableTextureIndex = true; - gAvatarProgram.mShaderFiles.clear(); - gAvatarProgram.mShaderFiles.push_back(make_pair("avatar/avatarV.glsl", GL_VERTEX_SHADER)); - gAvatarProgram.mShaderFiles.push_back(make_pair("avatar/avatarF.glsl", GL_FRAGMENT_SHADER)); - gAvatarProgram.mShaderLevel = mShaderLevel[SHADER_AVATAR]; - success = gAvatarProgram.createShader(NULL, NULL); - - /// Keep track of avatar levels - if (gAvatarProgram.mShaderLevel != mShaderLevel[SHADER_AVATAR]) - { - mMaxAvatarShaderLevel = mShaderLevel[SHADER_AVATAR] = gAvatarProgram.mShaderLevel; - } - } - - if (success) - { - gAvatarEyeballProgram.mName = "Avatar Eyeball Program"; - gAvatarEyeballProgram.mFeatures.calculatesLighting = true; - gAvatarEyeballProgram.mFeatures.isSpecular = true; - gAvatarEyeballProgram.mFeatures.calculatesAtmospherics = true; - gAvatarEyeballProgram.mFeatures.hasGamma = true; - gAvatarEyeballProgram.mFeatures.hasAtmospherics = true; - gAvatarEyeballProgram.mFeatures.hasLighting = true; - gAvatarEyeballProgram.mFeatures.hasAlphaMask = true; - gAvatarEyeballProgram.mFeatures.disableTextureIndex = true; - gAvatarEyeballProgram.mShaderFiles.clear(); - gAvatarEyeballProgram.mShaderFiles.push_back(make_pair("avatar/eyeballV.glsl", GL_VERTEX_SHADER)); - gAvatarEyeballProgram.mShaderFiles.push_back(make_pair("avatar/eyeballF.glsl", GL_FRAGMENT_SHADER)); - gAvatarEyeballProgram.mShaderLevel = mShaderLevel[SHADER_AVATAR]; - success = gAvatarEyeballProgram.createShader(NULL, NULL); - } - - if( !success ) - { - mShaderLevel[SHADER_AVATAR] = 0; - mMaxAvatarShaderLevel = 0; - return false; - } -#endif - return true; -} - -bool LLViewerShaderMgr::loadShadersInterface() -{ - LL_PROFILE_ZONE_SCOPED; - bool success = true; - - if (success) - { - gHighlightProgram.mName = "Highlight Shader"; - gHighlightProgram.mShaderFiles.clear(); - gHighlightProgram.mShaderFiles.push_back(make_pair("interface/highlightV.glsl", GL_VERTEX_SHADER)); - gHighlightProgram.mShaderFiles.push_back(make_pair("interface/highlightF.glsl", GL_FRAGMENT_SHADER)); - gHighlightProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = make_rigged_variant(gHighlightProgram, gSkinnedHighlightProgram); - success = success && gHighlightProgram.createShader(NULL, NULL); - } - - if (success) - { - gHighlightNormalProgram.mName = "Highlight Normals Shader"; - gHighlightNormalProgram.mShaderFiles.clear(); - gHighlightNormalProgram.mShaderFiles.push_back(make_pair("interface/highlightNormV.glsl", GL_VERTEX_SHADER)); - gHighlightNormalProgram.mShaderFiles.push_back(make_pair("interface/highlightF.glsl", GL_FRAGMENT_SHADER)); - gHighlightNormalProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = gHighlightNormalProgram.createShader(NULL, NULL); - } - - if (success) - { - gHighlightSpecularProgram.mName = "Highlight Spec Shader"; - gHighlightSpecularProgram.mShaderFiles.clear(); - gHighlightSpecularProgram.mShaderFiles.push_back(make_pair("interface/highlightSpecV.glsl", GL_VERTEX_SHADER)); - gHighlightSpecularProgram.mShaderFiles.push_back(make_pair("interface/highlightF.glsl", GL_FRAGMENT_SHADER)); - gHighlightSpecularProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = gHighlightSpecularProgram.createShader(NULL, NULL); - } - - if (success) - { - gUIProgram.mName = "UI Shader"; - gUIProgram.mShaderFiles.clear(); - gUIProgram.mShaderFiles.push_back(make_pair("interface/uiV.glsl", GL_VERTEX_SHADER)); - gUIProgram.mShaderFiles.push_back(make_pair("interface/uiF.glsl", GL_FRAGMENT_SHADER)); - gUIProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = gUIProgram.createShader(NULL, NULL); - } - - if (success) - { - gPathfindingProgram.mName = "Pathfinding Shader"; - gPathfindingProgram.mShaderFiles.clear(); - gPathfindingProgram.mShaderFiles.push_back(make_pair("interface/pathfindingV.glsl", GL_VERTEX_SHADER)); - gPathfindingProgram.mShaderFiles.push_back(make_pair("interface/pathfindingF.glsl", GL_FRAGMENT_SHADER)); - gPathfindingProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = gPathfindingProgram.createShader(NULL, NULL); - } - - if (success) - { - gPathfindingNoNormalsProgram.mName = "PathfindingNoNormals Shader"; - gPathfindingNoNormalsProgram.mShaderFiles.clear(); - gPathfindingNoNormalsProgram.mShaderFiles.push_back(make_pair("interface/pathfindingNoNormalV.glsl", GL_VERTEX_SHADER)); - gPathfindingNoNormalsProgram.mShaderFiles.push_back(make_pair("interface/pathfindingF.glsl", GL_FRAGMENT_SHADER)); - gPathfindingNoNormalsProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = gPathfindingNoNormalsProgram.createShader(NULL, NULL); - } - - if (success) - { - gGlowCombineProgram.mName = "Glow Combine Shader"; - gGlowCombineProgram.mShaderFiles.clear(); - gGlowCombineProgram.mShaderFiles.push_back(make_pair("interface/glowcombineV.glsl", GL_VERTEX_SHADER)); - gGlowCombineProgram.mShaderFiles.push_back(make_pair("interface/glowcombineF.glsl", GL_FRAGMENT_SHADER)); - gGlowCombineProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = gGlowCombineProgram.createShader(NULL, NULL); - if (success) - { - gGlowCombineProgram.bind(); - gGlowCombineProgram.uniform1i(sGlowMap, 0); - gGlowCombineProgram.uniform1i(sScreenMap, 1); - gGlowCombineProgram.unbind(); - } - } - - if (success) - { - gGlowCombineFXAAProgram.mName = "Glow CombineFXAA Shader"; - gGlowCombineFXAAProgram.mShaderFiles.clear(); - gGlowCombineFXAAProgram.mShaderFiles.push_back(make_pair("interface/glowcombineFXAAV.glsl", GL_VERTEX_SHADER)); - gGlowCombineFXAAProgram.mShaderFiles.push_back(make_pair("interface/glowcombineFXAAF.glsl", GL_FRAGMENT_SHADER)); - gGlowCombineFXAAProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = gGlowCombineFXAAProgram.createShader(NULL, NULL); - if (success) - { - gGlowCombineFXAAProgram.bind(); - gGlowCombineFXAAProgram.uniform1i(sGlowMap, 0); - gGlowCombineFXAAProgram.uniform1i(sScreenMap, 1); - gGlowCombineFXAAProgram.unbind(); - } - } - -#ifdef LL_WINDOWS - if (success) - { - gTwoTextureCompareProgram.mName = "Two Texture Compare Shader"; - gTwoTextureCompareProgram.mShaderFiles.clear(); - gTwoTextureCompareProgram.mShaderFiles.push_back(make_pair("interface/twotexturecompareV.glsl", GL_VERTEX_SHADER)); - gTwoTextureCompareProgram.mShaderFiles.push_back(make_pair("interface/twotexturecompareF.glsl", GL_FRAGMENT_SHADER)); - gTwoTextureCompareProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = gTwoTextureCompareProgram.createShader(NULL, NULL); - if (success) - { - gTwoTextureCompareProgram.bind(); - gTwoTextureCompareProgram.uniform1i(sTex0, 0); - gTwoTextureCompareProgram.uniform1i(sTex1, 1); - gTwoTextureCompareProgram.uniform1i(sDitherTex, 2); - } - } - - if (success) - { - gOneTextureFilterProgram.mName = "One Texture Filter Shader"; - gOneTextureFilterProgram.mShaderFiles.clear(); - gOneTextureFilterProgram.mShaderFiles.push_back(make_pair("interface/onetexturefilterV.glsl", GL_VERTEX_SHADER)); - gOneTextureFilterProgram.mShaderFiles.push_back(make_pair("interface/onetexturefilterF.glsl", GL_FRAGMENT_SHADER)); - gOneTextureFilterProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = gOneTextureFilterProgram.createShader(NULL, NULL); - if (success) - { - gOneTextureFilterProgram.bind(); - gOneTextureFilterProgram.uniform1i(sTex0, 0); - } - } -#endif - - if (success) - { - gSolidColorProgram.mName = "Solid Color Shader"; - gSolidColorProgram.mShaderFiles.clear(); - gSolidColorProgram.mShaderFiles.push_back(make_pair("interface/solidcolorV.glsl", GL_VERTEX_SHADER)); - gSolidColorProgram.mShaderFiles.push_back(make_pair("interface/solidcolorF.glsl", GL_FRAGMENT_SHADER)); - gSolidColorProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = gSolidColorProgram.createShader(NULL, NULL); - if (success) - { - gSolidColorProgram.bind(); - gSolidColorProgram.uniform1i(sTex0, 0); - gSolidColorProgram.unbind(); - } - } - - if (success) - { - gOcclusionProgram.mName = "Occlusion Shader"; - gOcclusionProgram.mShaderFiles.clear(); - gOcclusionProgram.mShaderFiles.push_back(make_pair("interface/occlusionV.glsl", GL_VERTEX_SHADER)); - gOcclusionProgram.mShaderFiles.push_back(make_pair("interface/occlusionF.glsl", GL_FRAGMENT_SHADER)); - gOcclusionProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - gOcclusionProgram.mRiggedVariant = &gSkinnedOcclusionProgram; - success = gOcclusionProgram.createShader(NULL, NULL); - } - - if (success) - { - gSkinnedOcclusionProgram.mName = "Skinned Occlusion Shader"; - gSkinnedOcclusionProgram.mFeatures.hasObjectSkinning = true; - gSkinnedOcclusionProgram.mShaderFiles.clear(); - gSkinnedOcclusionProgram.mShaderFiles.push_back(make_pair("interface/occlusionSkinnedV.glsl", GL_VERTEX_SHADER)); - gSkinnedOcclusionProgram.mShaderFiles.push_back(make_pair("interface/occlusionF.glsl", GL_FRAGMENT_SHADER)); - gSkinnedOcclusionProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = gSkinnedOcclusionProgram.createShader(NULL, NULL); - } - - if (success) - { - gOcclusionCubeProgram.mName = "Occlusion Cube Shader"; - gOcclusionCubeProgram.mShaderFiles.clear(); - gOcclusionCubeProgram.mShaderFiles.push_back(make_pair("interface/occlusionCubeV.glsl", GL_VERTEX_SHADER)); - gOcclusionCubeProgram.mShaderFiles.push_back(make_pair("interface/occlusionF.glsl", GL_FRAGMENT_SHADER)); - gOcclusionCubeProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = gOcclusionCubeProgram.createShader(NULL, NULL); - } - - if (success) - { - gDebugProgram.mName = "Debug Shader"; - gDebugProgram.mShaderFiles.clear(); - gDebugProgram.mShaderFiles.push_back(make_pair("interface/debugV.glsl", GL_VERTEX_SHADER)); - gDebugProgram.mShaderFiles.push_back(make_pair("interface/debugF.glsl", GL_FRAGMENT_SHADER)); - gDebugProgram.mRiggedVariant = &gSkinnedDebugProgram; - gDebugProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = make_rigged_variant(gDebugProgram, gSkinnedDebugProgram); - success = success && gDebugProgram.createShader(NULL, NULL); - } - - if (success) - { - gClipProgram.mName = "Clip Shader"; - gClipProgram.mShaderFiles.clear(); - gClipProgram.mShaderFiles.push_back(make_pair("interface/clipV.glsl", GL_VERTEX_SHADER)); - gClipProgram.mShaderFiles.push_back(make_pair("interface/clipF.glsl", GL_FRAGMENT_SHADER)); - gClipProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = gClipProgram.createShader(NULL, NULL); - } - - if (success) - { - gBenchmarkProgram.mName = "Benchmark Shader"; - gBenchmarkProgram.mShaderFiles.clear(); - gBenchmarkProgram.mShaderFiles.push_back(make_pair("interface/benchmarkV.glsl", GL_VERTEX_SHADER)); - gBenchmarkProgram.mShaderFiles.push_back(make_pair("interface/benchmarkF.glsl", GL_FRAGMENT_SHADER)); - gBenchmarkProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = gBenchmarkProgram.createShader(NULL, NULL); - } - - if (success) - { - gReflectionProbeDisplayProgram.mName = "Reflection Probe Display Shader"; - gReflectionProbeDisplayProgram.mFeatures.hasReflectionProbes = true; - gReflectionProbeDisplayProgram.mFeatures.hasSrgb = true; - gReflectionProbeDisplayProgram.mFeatures.calculatesAtmospherics = true; - gReflectionProbeDisplayProgram.mFeatures.hasAtmospherics = true; - gReflectionProbeDisplayProgram.mFeatures.hasGamma = true; - gReflectionProbeDisplayProgram.mFeatures.isDeferred = true; - gReflectionProbeDisplayProgram.mShaderFiles.clear(); - gReflectionProbeDisplayProgram.mShaderFiles.push_back(make_pair("interface/reflectionprobeV.glsl", GL_VERTEX_SHADER)); - gReflectionProbeDisplayProgram.mShaderFiles.push_back(make_pair("interface/reflectionprobeF.glsl", GL_FRAGMENT_SHADER)); - gReflectionProbeDisplayProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = gReflectionProbeDisplayProgram.createShader(NULL, NULL); - } - - if (success) - { - gCopyProgram.mName = "Copy Shader"; - gCopyProgram.mShaderFiles.clear(); - gCopyProgram.mShaderFiles.push_back(make_pair("interface/copyV.glsl", GL_VERTEX_SHADER)); - gCopyProgram.mShaderFiles.push_back(make_pair("interface/copyF.glsl", GL_FRAGMENT_SHADER)); - gCopyProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = gCopyProgram.createShader(NULL, NULL); - } - - if (success) - { - gCopyDepthProgram.mName = "Copy Depth Shader"; - gCopyDepthProgram.mShaderFiles.clear(); - gCopyDepthProgram.mShaderFiles.push_back(make_pair("interface/copyV.glsl", GL_VERTEX_SHADER)); - gCopyDepthProgram.mShaderFiles.push_back(make_pair("interface/copyF.glsl", GL_FRAGMENT_SHADER)); - gCopyDepthProgram.clearPermutations(); - gCopyDepthProgram.addPermutation("COPY_DEPTH", "1"); - gCopyDepthProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = gCopyDepthProgram.createShader(NULL, NULL); - } - - if (success) - { - gAlphaMaskProgram.mName = "Alpha Mask Shader"; - gAlphaMaskProgram.mShaderFiles.clear(); - gAlphaMaskProgram.mShaderFiles.push_back(make_pair("interface/alphamaskV.glsl", GL_VERTEX_SHADER)); - gAlphaMaskProgram.mShaderFiles.push_back(make_pair("interface/alphamaskF.glsl", GL_FRAGMENT_SHADER)); - gAlphaMaskProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = gAlphaMaskProgram.createShader(NULL, NULL); - } - - if (success) - { - gReflectionMipProgram.mName = "Reflection Mip Shader"; - gReflectionMipProgram.mFeatures.isDeferred = true; - gReflectionMipProgram.mFeatures.hasGamma = true; - gReflectionMipProgram.mFeatures.hasAtmospherics = true; - gReflectionMipProgram.mFeatures.calculatesAtmospherics = true; - gReflectionMipProgram.mShaderFiles.clear(); - gReflectionMipProgram.mShaderFiles.push_back(make_pair("interface/splattexturerectV.glsl", GL_VERTEX_SHADER)); - gReflectionMipProgram.mShaderFiles.push_back(make_pair("interface/reflectionmipF.glsl", GL_FRAGMENT_SHADER)); - gReflectionMipProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = gReflectionMipProgram.createShader(NULL, NULL); - } - - if (success) - { - gGaussianProgram.mName = "Reflection Mip Shader"; - gGaussianProgram.mFeatures.isDeferred = true; - gGaussianProgram.mFeatures.hasGamma = true; - gGaussianProgram.mFeatures.hasAtmospherics = true; - gGaussianProgram.mFeatures.calculatesAtmospherics = true; - gGaussianProgram.mShaderFiles.clear(); - gGaussianProgram.mShaderFiles.push_back(make_pair("interface/splattexturerectV.glsl", GL_VERTEX_SHADER)); - gGaussianProgram.mShaderFiles.push_back(make_pair("interface/gaussianF.glsl", GL_FRAGMENT_SHADER)); - gGaussianProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = gGaussianProgram.createShader(NULL, NULL); - } - - if (success && gGLManager.mHasCubeMapArray) - { - gRadianceGenProgram.mName = "Radiance Gen Shader"; - gRadianceGenProgram.mShaderFiles.clear(); - gRadianceGenProgram.mShaderFiles.push_back(make_pair("interface/radianceGenV.glsl", GL_VERTEX_SHADER)); - gRadianceGenProgram.mShaderFiles.push_back(make_pair("interface/radianceGenF.glsl", GL_FRAGMENT_SHADER)); - gRadianceGenProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = gRadianceGenProgram.createShader(NULL, NULL); - } - - if (success && gGLManager.mHasCubeMapArray) - { - gIrradianceGenProgram.mName = "Irradiance Gen Shader"; - gIrradianceGenProgram.mShaderFiles.clear(); - gIrradianceGenProgram.mShaderFiles.push_back(make_pair("interface/irradianceGenV.glsl", GL_VERTEX_SHADER)); - gIrradianceGenProgram.mShaderFiles.push_back(make_pair("interface/irradianceGenF.glsl", GL_FRAGMENT_SHADER)); - gIrradianceGenProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; - success = gIrradianceGenProgram.createShader(NULL, NULL); - } - - if( !success ) - { - mShaderLevel[SHADER_INTERFACE] = 0; - return false; - } - - return true; -} - - -std::string LLViewerShaderMgr::getShaderDirPrefix(void) -{ - return gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "shaders/class"); -} - -void LLViewerShaderMgr::updateShaderUniforms(LLGLSLShader * shader) -{ - LLEnvironment::instance().updateShaderUniforms(shader); -} - -LLViewerShaderMgr::shader_iter LLViewerShaderMgr::beginShaders() const -{ - return mShaderList.begin(); -} - -LLViewerShaderMgr::shader_iter LLViewerShaderMgr::endShaders() const -{ - return mShaderList.end(); -} - +/** + * @file llviewershadermgr.cpp + * @brief Viewer shader manager implementation. + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + + +#include "llviewerprecompiledheaders.h" + +#include + +#include "llfeaturemanager.h" +#include "llviewershadermgr.h" +#include "llviewercontrol.h" +#include "llversioninfo.h" + +#include "llrender.h" +#include "llenvironment.h" +#include "llerrorcontrol.h" +#include "llatmosphere.h" +#include "llworld.h" +#include "llsky.h" + +#include "pipeline.h" + +#include "llfile.h" +#include "llviewerwindow.h" +#include "llwindow.h" + +#include "lljoint.h" +#include "llskinningutil.h" + +static LLStaticHashedString sTexture0("texture0"); +static LLStaticHashedString sTexture1("texture1"); +static LLStaticHashedString sTex0("tex0"); +static LLStaticHashedString sTex1("tex1"); +static LLStaticHashedString sDitherTex("dither_tex"); +static LLStaticHashedString sGlowMap("glowMap"); +static LLStaticHashedString sScreenMap("screenMap"); + +// Lots of STL stuff in here, using namespace std to keep things more readable +using std::vector; +using std::pair; +using std::make_pair; +using std::string; + +bool LLViewerShaderMgr::sInitialized = false; +bool LLViewerShaderMgr::sSkipReload = false; + +LLVector4 gShinyOrigin; + +//utility shaders +LLGLSLShader gOcclusionProgram; +LLGLSLShader gSkinnedOcclusionProgram; +LLGLSLShader gOcclusionCubeProgram; +LLGLSLShader gGlowCombineProgram; +LLGLSLShader gReflectionMipProgram; +LLGLSLShader gGaussianProgram; +LLGLSLShader gRadianceGenProgram; +LLGLSLShader gIrradianceGenProgram; +LLGLSLShader gGlowCombineFXAAProgram; +LLGLSLShader gTwoTextureCompareProgram; +LLGLSLShader gOneTextureFilterProgram; +LLGLSLShader gDebugProgram; +LLGLSLShader gSkinnedDebugProgram; +LLGLSLShader gClipProgram; +LLGLSLShader gAlphaMaskProgram; +LLGLSLShader gBenchmarkProgram; +LLGLSLShader gReflectionProbeDisplayProgram; +LLGLSLShader gCopyProgram; +LLGLSLShader gCopyDepthProgram; + +//object shaders +LLGLSLShader gObjectPreviewProgram; +LLGLSLShader gSkinnedObjectPreviewProgram; +LLGLSLShader gPhysicsPreviewProgram; +LLGLSLShader gObjectFullbrightAlphaMaskProgram; +LLGLSLShader gSkinnedObjectFullbrightAlphaMaskProgram; +LLGLSLShader gObjectBumpProgram; +LLGLSLShader gSkinnedObjectBumpProgram; +LLGLSLShader gObjectAlphaMaskNoColorProgram; + +//environment shaders +LLGLSLShader gWaterProgram; +LLGLSLShader gWaterEdgeProgram; +LLGLSLShader gUnderWaterProgram; + +//interface shaders +LLGLSLShader gHighlightProgram; +LLGLSLShader gSkinnedHighlightProgram; +LLGLSLShader gHighlightNormalProgram; +LLGLSLShader gHighlightSpecularProgram; + +LLGLSLShader gDeferredHighlightProgram; + +LLGLSLShader gPathfindingProgram; +LLGLSLShader gPathfindingNoNormalsProgram; + +//avatar shader handles +LLGLSLShader gAvatarProgram; +LLGLSLShader gAvatarEyeballProgram; +LLGLSLShader gImpostorProgram; + +// Effects Shaders +LLGLSLShader gGlowProgram; +LLGLSLShader gGlowExtractProgram; +LLGLSLShader gPostScreenSpaceReflectionProgram; + +// Deferred rendering shaders +LLGLSLShader gDeferredImpostorProgram; +LLGLSLShader gDeferredDiffuseProgram; +LLGLSLShader gDeferredDiffuseAlphaMaskProgram; +LLGLSLShader gDeferredSkinnedDiffuseAlphaMaskProgram; +LLGLSLShader gDeferredNonIndexedDiffuseAlphaMaskProgram; +LLGLSLShader gDeferredNonIndexedDiffuseAlphaMaskNoColorProgram; +LLGLSLShader gDeferredSkinnedDiffuseProgram; +LLGLSLShader gDeferredSkinnedBumpProgram; +LLGLSLShader gDeferredBumpProgram; +LLGLSLShader gDeferredTerrainProgram; +LLGLSLShader gDeferredTreeProgram; +LLGLSLShader gDeferredTreeShadowProgram; +LLGLSLShader gDeferredSkinnedTreeShadowProgram; +LLGLSLShader gDeferredAvatarProgram; +LLGLSLShader gDeferredAvatarAlphaProgram; +LLGLSLShader gDeferredLightProgram; +LLGLSLShader gDeferredMultiLightProgram[16]; +LLGLSLShader gDeferredSpotLightProgram; +LLGLSLShader gDeferredMultiSpotLightProgram; +LLGLSLShader gDeferredSunProgram; +LLGLSLShader gHazeProgram; +LLGLSLShader gHazeWaterProgram; +LLGLSLShader gDeferredBlurLightProgram; +LLGLSLShader gDeferredSoftenProgram; +LLGLSLShader gDeferredShadowProgram; +LLGLSLShader gDeferredSkinnedShadowProgram; +LLGLSLShader gDeferredShadowCubeProgram; +LLGLSLShader gDeferredShadowAlphaMaskProgram; +LLGLSLShader gDeferredSkinnedShadowAlphaMaskProgram; +LLGLSLShader gDeferredShadowGLTFAlphaMaskProgram; +LLGLSLShader gDeferredSkinnedShadowGLTFAlphaMaskProgram; +LLGLSLShader gDeferredShadowGLTFAlphaBlendProgram; +LLGLSLShader gDeferredSkinnedShadowGLTFAlphaBlendProgram; +LLGLSLShader gDeferredShadowFullbrightAlphaMaskProgram; +LLGLSLShader gDeferredSkinnedShadowFullbrightAlphaMaskProgram; +LLGLSLShader gDeferredAvatarShadowProgram; +LLGLSLShader gDeferredAvatarAlphaShadowProgram; +LLGLSLShader gDeferredAvatarAlphaMaskShadowProgram; +LLGLSLShader gDeferredAlphaProgram; +LLGLSLShader gHUDAlphaProgram; +LLGLSLShader gDeferredSkinnedAlphaProgram; +LLGLSLShader gDeferredAlphaImpostorProgram; +LLGLSLShader gDeferredSkinnedAlphaImpostorProgram; +LLGLSLShader gDeferredAvatarEyesProgram; +LLGLSLShader gDeferredFullbrightProgram; +LLGLSLShader gHUDFullbrightProgram; +LLGLSLShader gDeferredFullbrightAlphaMaskProgram; +LLGLSLShader gHUDFullbrightAlphaMaskProgram; +LLGLSLShader gDeferredFullbrightAlphaMaskAlphaProgram; +LLGLSLShader gHUDFullbrightAlphaMaskAlphaProgram; +LLGLSLShader gDeferredEmissiveProgram; +LLGLSLShader gDeferredSkinnedEmissiveProgram; +LLGLSLShader gDeferredPostProgram; +LLGLSLShader gDeferredCoFProgram; +LLGLSLShader gDeferredDoFCombineProgram; +LLGLSLShader gDeferredPostGammaCorrectProgram; +LLGLSLShader gNoPostGammaCorrectProgram; +LLGLSLShader gLegacyPostGammaCorrectProgram; +LLGLSLShader gExposureProgram; +LLGLSLShader gLuminanceProgram; +LLGLSLShader gFXAAProgram; +LLGLSLShader gDeferredPostNoDoFProgram; +LLGLSLShader gDeferredWLSkyProgram; +LLGLSLShader gDeferredWLCloudProgram; +LLGLSLShader gDeferredWLSunProgram; +LLGLSLShader gDeferredWLMoonProgram; +LLGLSLShader gDeferredStarProgram; +LLGLSLShader gDeferredFullbrightShinyProgram; +LLGLSLShader gHUDFullbrightShinyProgram; +LLGLSLShader gDeferredSkinnedFullbrightShinyProgram; +LLGLSLShader gDeferredSkinnedFullbrightProgram; +LLGLSLShader gDeferredSkinnedFullbrightAlphaMaskProgram; +LLGLSLShader gDeferredSkinnedFullbrightAlphaMaskAlphaProgram; +LLGLSLShader gNormalMapGenProgram; +LLGLSLShader gDeferredGenBrdfLutProgram; +LLGLSLShader gDeferredBufferVisualProgram; + +// Deferred materials shaders +LLGLSLShader gDeferredMaterialProgram[LLMaterial::SHADER_COUNT*2]; +LLGLSLShader gHUDPBROpaqueProgram; +LLGLSLShader gPBRGlowProgram; +LLGLSLShader gPBRGlowSkinnedProgram; +LLGLSLShader gDeferredPBROpaqueProgram; +LLGLSLShader gDeferredSkinnedPBROpaqueProgram; +LLGLSLShader gHUDPBRAlphaProgram; +LLGLSLShader gDeferredPBRAlphaProgram; +LLGLSLShader gDeferredSkinnedPBRAlphaProgram; + +//helper for making a rigged variant of a given shader +bool make_rigged_variant(LLGLSLShader& shader, LLGLSLShader& riggedShader) +{ + riggedShader.mName = llformat("Skinned %s", shader.mName.c_str()); + riggedShader.mFeatures = shader.mFeatures; + riggedShader.mFeatures.hasObjectSkinning = true; + riggedShader.mDefines = shader.mDefines; // NOTE: Must come before addPermutation + riggedShader.addPermutation("HAS_SKIN", "1"); + riggedShader.mShaderFiles = shader.mShaderFiles; + riggedShader.mShaderLevel = shader.mShaderLevel; + riggedShader.mShaderGroup = shader.mShaderGroup; + + shader.mRiggedVariant = &riggedShader; + return riggedShader.createShader(NULL, NULL); +} + +LLViewerShaderMgr::LLViewerShaderMgr() : + mShaderLevel(SHADER_COUNT, 0), + mMaxAvatarShaderLevel(0) +{ + //ONLY shaders that need WL Param management should be added here + mShaderList.push_back(&gAvatarProgram); + mShaderList.push_back(&gWaterProgram); + mShaderList.push_back(&gWaterEdgeProgram); + mShaderList.push_back(&gAvatarEyeballProgram); + mShaderList.push_back(&gImpostorProgram); + mShaderList.push_back(&gObjectBumpProgram); + mShaderList.push_back(&gSkinnedObjectBumpProgram); + mShaderList.push_back(&gObjectFullbrightAlphaMaskProgram); + mShaderList.push_back(&gSkinnedObjectFullbrightAlphaMaskProgram); + mShaderList.push_back(&gObjectAlphaMaskNoColorProgram); + mShaderList.push_back(&gUnderWaterProgram); + mShaderList.push_back(&gDeferredSunProgram); + mShaderList.push_back(&gHazeProgram); + mShaderList.push_back(&gHazeWaterProgram); + mShaderList.push_back(&gDeferredSoftenProgram); + mShaderList.push_back(&gDeferredAlphaProgram); + mShaderList.push_back(&gHUDAlphaProgram); + mShaderList.push_back(&gDeferredSkinnedAlphaProgram); + mShaderList.push_back(&gDeferredAlphaImpostorProgram); + mShaderList.push_back(&gDeferredSkinnedAlphaImpostorProgram); + mShaderList.push_back(&gDeferredFullbrightProgram); + mShaderList.push_back(&gHUDFullbrightProgram); + mShaderList.push_back(&gDeferredFullbrightAlphaMaskProgram); + mShaderList.push_back(&gHUDFullbrightAlphaMaskProgram); + mShaderList.push_back(&gDeferredFullbrightAlphaMaskAlphaProgram); + mShaderList.push_back(&gHUDFullbrightAlphaMaskAlphaProgram); + mShaderList.push_back(&gDeferredFullbrightShinyProgram); + mShaderList.push_back(&gHUDFullbrightShinyProgram); + mShaderList.push_back(&gDeferredSkinnedFullbrightShinyProgram); + mShaderList.push_back(&gDeferredSkinnedFullbrightProgram); + mShaderList.push_back(&gDeferredSkinnedFullbrightAlphaMaskProgram); + mShaderList.push_back(&gDeferredSkinnedFullbrightAlphaMaskAlphaProgram); + mShaderList.push_back(&gDeferredEmissiveProgram); + mShaderList.push_back(&gDeferredSkinnedEmissiveProgram); + mShaderList.push_back(&gDeferredAvatarEyesProgram); + mShaderList.push_back(&gDeferredAvatarAlphaProgram); + mShaderList.push_back(&gDeferredWLSkyProgram); + mShaderList.push_back(&gDeferredWLCloudProgram); + mShaderList.push_back(&gDeferredWLMoonProgram); + mShaderList.push_back(&gDeferredWLSunProgram); + mShaderList.push_back(&gDeferredPBRAlphaProgram); + mShaderList.push_back(&gHUDPBRAlphaProgram); + mShaderList.push_back(&gDeferredSkinnedPBRAlphaProgram); + mShaderList.push_back(&gDeferredPostGammaCorrectProgram); // for gamma + mShaderList.push_back(&gNoPostGammaCorrectProgram); + mShaderList.push_back(&gLegacyPostGammaCorrectProgram); + +} + +LLViewerShaderMgr::~LLViewerShaderMgr() +{ + mShaderLevel.clear(); + mShaderList.clear(); +} + +// static +LLViewerShaderMgr * LLViewerShaderMgr::instance() +{ + if(NULL == sInstance) + { + sInstance = new LLViewerShaderMgr(); + } + + return static_cast(sInstance); +} + +// static +void LLViewerShaderMgr::releaseInstance() +{ + if (sInstance != NULL) + { + delete sInstance; + sInstance = NULL; + } +} + +void LLViewerShaderMgr::initAttribsAndUniforms(void) +{ + if (mReservedAttribs.empty()) + { + LLShaderMgr::initAttribsAndUniforms(); + } +} + + +//============================================================================ +// Set Levels + +S32 LLViewerShaderMgr::getShaderLevel(S32 type) +{ + return mShaderLevel[type]; +} + +//============================================================================ +// Shader Management + +void LLViewerShaderMgr::setShaders() +{ + LL_PROFILE_ZONE_SCOPED; + //setShaders might be called redundantly by gSavedSettings, so return on reentrance + static bool reentrance = false; + + if (!gPipeline.mInitialized || !sInitialized || reentrance || sSkipReload) + { + return; + } + + if (!gGLManager.mHasRequirements) + { + // Viewer will show 'hardware requirements' warning later + LL_INFOS("ShaderLoading") << "Not supported hardware/software" << LL_ENDL; + return; + } + + { + static LLCachedControl shader_cache_enabled(gSavedSettings, "RenderShaderCacheEnabled", true); + static LLUUID old_cache_version; + static LLUUID current_cache_version; + if (current_cache_version.isNull()) + { + HBXXH128 hash_obj; + hash_obj.update(LLVersionInfo::instance().getVersion()); + current_cache_version = hash_obj.digest(); + + old_cache_version = LLUUID(gSavedSettings.getString("RenderShaderCacheVersion")); + gSavedSettings.setString("RenderShaderCacheVersion", current_cache_version.asString()); + } + + initShaderCache(shader_cache_enabled, old_cache_version, current_cache_version); + } + + static LLCachedControl max_texture_index(gSavedSettings, "RenderMaxTextureIndex", 16); + + // when using indexed texture rendering, leave some texture units available for shadow and reflection maps + LLGLSLShader::sIndexedTextureChannels = llmax(llmin(gGLManager.mNumTextureImageUnits-12, (S32) max_texture_index), 1); + + reentrance = true; + + // Make sure the compiled shader map is cleared before we recompile shaders. + mVertexShaderObjects.clear(); + mFragmentShaderObjects.clear(); + + initAttribsAndUniforms(); + gPipeline.releaseGLBuffers(); + + unloadShaders(); + + LLPipeline::sRenderGlow = gSavedSettings.getBOOL("RenderGlow"); + + if (gViewerWindow) + { + gViewerWindow->setCursor(UI_CURSOR_WAIT); + } + + // Shaders + LL_INFOS("ShaderLoading") << "\n~~~~~~~~~~~~~~~~~~\n Loading Shaders:\n~~~~~~~~~~~~~~~~~~" << LL_ENDL; + LL_INFOS("ShaderLoading") << llformat("Using GLSL %d.%d", gGLManager.mGLSLVersionMajor, gGLManager.mGLSLVersionMinor) << LL_ENDL; + + for (S32 i = 0; i < SHADER_COUNT; i++) + { + mShaderLevel[i] = 0; + } + mMaxAvatarShaderLevel = 0; + + LLVertexBuffer::unbind(); + + llassert((gGLManager.mGLSLVersionMajor > 1 || gGLManager.mGLSLVersionMinor >= 10)); + + + S32 light_class = 3; + S32 interface_class = 2; + S32 env_class = 2; + S32 obj_class = 2; + S32 effect_class = 2; + S32 wl_class = 2; + S32 water_class = 3; + S32 deferred_class = 3; + + // Trigger a full rebuild of the fallback skybox / cubemap if we've toggled windlight shaders + if (!wl_class || (mShaderLevel[SHADER_WINDLIGHT] != wl_class && gSky.mVOSkyp.notNull())) + { + gSky.mVOSkyp->forceSkyUpdate(); + } + + // Load lighting shaders + mShaderLevel[SHADER_LIGHTING] = light_class; + mShaderLevel[SHADER_INTERFACE] = interface_class; + mShaderLevel[SHADER_ENVIRONMENT] = env_class; + mShaderLevel[SHADER_WATER] = water_class; + mShaderLevel[SHADER_OBJECT] = obj_class; + mShaderLevel[SHADER_EFFECT] = effect_class; + mShaderLevel[SHADER_WINDLIGHT] = wl_class; + mShaderLevel[SHADER_DEFERRED] = deferred_class; + + std::string shader_name = loadBasicShaders(); + if (shader_name.empty()) + { + LL_INFOS("Shader") << "Loaded basic shaders." << LL_ENDL; + } + else + { + // "ShaderLoading" and "Shader" need to be logged + LLError::ELevel lvl = LLError::getDefaultLevel(); + LLError::setDefaultLevel(LLError::LEVEL_DEBUG); + loadBasicShaders(); + LLError::setDefaultLevel(lvl); + LL_ERRS() << "Unable to load basic shader " << shader_name << ", verify graphics driver installed and current." << LL_ENDL; + reentrance = false; // For hygiene only, re-try probably helps nothing + return; + } + + gPipeline.mShadersLoaded = true; + + bool loaded = loadShadersWater(); + + if (loaded) + { + LL_INFOS() << "Loaded water shaders." << LL_ENDL; + } + else + { + LL_WARNS() << "Failed to load water shaders." << LL_ENDL; + llassert(loaded); + } + + if (loaded) + { + loaded = loadShadersEffects(); + if (loaded) + { + LL_INFOS() << "Loaded effects shaders." << LL_ENDL; + } + else + { + LL_WARNS() << "Failed to load effects shaders." << LL_ENDL; + llassert(loaded); + } + } + + if (loaded) + { + loaded = loadShadersInterface(); + if (loaded) + { + LL_INFOS() << "Loaded interface shaders." << LL_ENDL; + } + else + { + LL_WARNS() << "Failed to load interface shaders." << LL_ENDL; + llassert(loaded); + } + } + + if (loaded) + { + // Load max avatar shaders to set the max level + mShaderLevel[SHADER_AVATAR] = 3; + mMaxAvatarShaderLevel = 3; + + if (loadShadersObject()) + { //hardware skinning is enabled and rigged attachment shaders loaded correctly + // cloth is a class3 shader + S32 avatar_class = 1; + + // Set the actual level + mShaderLevel[SHADER_AVATAR] = avatar_class; + + loaded = loadShadersAvatar(); + llassert(loaded); + } + else + { //hardware skinning not possible, neither is deferred rendering + llassert(false); // SHOULD NOT BE POSSIBLE + } + } + + llassert(loaded); + loaded = loaded && loadShadersDeferred(); + llassert(loaded); + + persistShaderCacheMetadata(); + + if (gViewerWindow) + { + gViewerWindow->setCursor(UI_CURSOR_ARROW); + } + gPipeline.createGLBuffers(); + + reentrance = false; +} + +void LLViewerShaderMgr::unloadShaders() +{ + while (!LLGLSLShader::sInstances.empty()) + { + LLGLSLShader* shader = *(LLGLSLShader::sInstances.begin()); + shader->unload(); + } + + mShaderLevel[SHADER_LIGHTING] = 0; + mShaderLevel[SHADER_OBJECT] = 0; + mShaderLevel[SHADER_AVATAR] = 0; + mShaderLevel[SHADER_ENVIRONMENT] = 0; + mShaderLevel[SHADER_WATER] = 0; + mShaderLevel[SHADER_INTERFACE] = 0; + mShaderLevel[SHADER_EFFECT] = 0; + mShaderLevel[SHADER_WINDLIGHT] = 0; + + gPipeline.mShadersLoaded = false; +} + +std::string LLViewerShaderMgr::loadBasicShaders() +{ + // Load basic dependency shaders first + // All of these have to load for any shaders to function + + S32 sum_lights_class = 3; + +#if LL_DARWIN + // Work around driver crashes on older Macs when using deferred rendering + // NORSPEC-59 + // + if (gGLManager.mIsMobileGF) + sum_lights_class = 3; +#endif + + // Use the feature table to mask out the max light level to use. Also make sure it's at least 1. + S32 max_light_class = gSavedSettings.getS32("RenderShaderLightingMaxLevel"); + sum_lights_class = llclamp(sum_lights_class, 1, max_light_class); + + // Load the Basic Vertex Shaders at the appropriate level. + // (in order of shader function call depth for reference purposes, deepest level first) + + vector< pair > shaders; + shaders.push_back( make_pair( "windlight/atmosphericsVarsV.glsl", mShaderLevel[SHADER_WINDLIGHT] ) ); + shaders.push_back( make_pair( "windlight/atmosphericsHelpersV.glsl", mShaderLevel[SHADER_WINDLIGHT] ) ); + shaders.push_back( make_pair( "lighting/lightFuncV.glsl", mShaderLevel[SHADER_LIGHTING] ) ); + shaders.push_back( make_pair( "lighting/sumLightsV.glsl", sum_lights_class ) ); + shaders.push_back( make_pair( "lighting/lightV.glsl", mShaderLevel[SHADER_LIGHTING] ) ); + shaders.push_back( make_pair( "lighting/lightFuncSpecularV.glsl", mShaderLevel[SHADER_LIGHTING] ) ); + shaders.push_back( make_pair( "lighting/sumLightsSpecularV.glsl", sum_lights_class ) ); + shaders.push_back( make_pair( "lighting/lightSpecularV.glsl", mShaderLevel[SHADER_LIGHTING] ) ); + shaders.push_back( make_pair( "windlight/atmosphericsFuncs.glsl", mShaderLevel[SHADER_WINDLIGHT] ) ); + shaders.push_back( make_pair( "windlight/atmosphericsV.glsl", mShaderLevel[SHADER_WINDLIGHT] ) ); + shaders.push_back( make_pair( "environment/srgbF.glsl", 1 ) ); + shaders.push_back( make_pair( "avatar/avatarSkinV.glsl", 1 ) ); + shaders.push_back( make_pair( "avatar/objectSkinV.glsl", 1 ) ); + shaders.push_back( make_pair( "deferred/textureUtilV.glsl", 1 ) ); + if (gGLManager.mGLSLVersionMajor >= 2 || gGLManager.mGLSLVersionMinor >= 30) + { + shaders.push_back( make_pair( "objects/indexedTextureV.glsl", 1 ) ); + } + shaders.push_back( make_pair( "objects/nonindexedTextureV.glsl", 1 ) ); + + std::map attribs; + attribs["MAX_JOINTS_PER_MESH_OBJECT"] = + std::to_string(LLSkinningUtil::getMaxJointCount()); + + bool ssr = gSavedSettings.getBOOL("RenderScreenSpaceReflections"); + + bool has_reflection_probes = gSavedSettings.getBOOL("RenderReflectionsEnabled") && gGLManager.mGLVersion > 3.99f; + + S32 probe_level = llclamp(gSavedSettings.getS32("RenderReflectionProbeLevel"), 0, 3); + + S32 shadow_detail = gSavedSettings.getS32("RenderShadowDetail"); + + if (shadow_detail >= 1) + { + attribs["SUN_SHADOW"] = "1"; + + if (shadow_detail >= 2) + { + attribs["SPOT_SHADOW"] = "1"; + } + } + + if (ssr) + { + attribs["SSR"] = "1"; + } + + if (has_reflection_probes) + { + attribs["REFMAP_LEVEL"] = std::to_string(probe_level); + attribs["REF_SAMPLE_COUNT"] = "32"; + } + + LLGLSLShader::sGlobalDefines = attribs; + + // We no longer have to bind the shaders to global glhandles, they are automatically added to a map now. + for (U32 i = 0; i < shaders.size(); i++) + { + // Note usage of GL_VERTEX_SHADER + if (loadShaderFile(shaders[i].first, shaders[i].second, GL_VERTEX_SHADER, &attribs) == 0) + { + LL_WARNS("Shader") << "Failed to load vertex shader " << shaders[i].first << LL_ENDL; + return shaders[i].first; + } + } + + // Load the Basic Fragment Shaders at the appropriate level. + // (in order of shader function call depth for reference purposes, deepest level first) + + shaders.clear(); + S32 ch = 1; + + if (gGLManager.mGLSLVersionMajor > 1 || gGLManager.mGLSLVersionMinor >= 30) + { //use indexed texture rendering for GLSL >= 1.30 + ch = llmax(LLGLSLShader::sIndexedTextureChannels, 1); + } + + + std::vector index_channels; + index_channels.push_back(-1); shaders.push_back( make_pair( "windlight/atmosphericsVarsF.glsl", mShaderLevel[SHADER_WINDLIGHT] ) ); + index_channels.push_back(-1); shaders.push_back( make_pair( "windlight/atmosphericsHelpersF.glsl", mShaderLevel[SHADER_WINDLIGHT] ) ); + index_channels.push_back(-1); shaders.push_back( make_pair( "windlight/gammaF.glsl", mShaderLevel[SHADER_WINDLIGHT]) ); + index_channels.push_back(-1); shaders.push_back( make_pair( "windlight/atmosphericsFuncs.glsl", mShaderLevel[SHADER_WINDLIGHT] ) ); + index_channels.push_back(-1); shaders.push_back( make_pair( "windlight/atmosphericsF.glsl", mShaderLevel[SHADER_WINDLIGHT] ) ); + index_channels.push_back(-1); shaders.push_back( make_pair( "environment/waterFogF.glsl", mShaderLevel[SHADER_WATER] ) ); + index_channels.push_back(-1); shaders.push_back( make_pair( "environment/encodeNormF.glsl", mShaderLevel[SHADER_ENVIRONMENT] ) ); + index_channels.push_back(-1); shaders.push_back( make_pair( "environment/srgbF.glsl", mShaderLevel[SHADER_ENVIRONMENT] ) ); + index_channels.push_back(-1); shaders.push_back( make_pair( "deferred/deferredUtil.glsl", 1) ); + index_channels.push_back(-1); shaders.push_back( make_pair( "deferred/shadowUtil.glsl", 1) ); + index_channels.push_back(-1); shaders.push_back( make_pair( "deferred/aoUtil.glsl", 1) ); + index_channels.push_back(-1); shaders.push_back( make_pair( "deferred/reflectionProbeF.glsl", has_reflection_probes ? 3 : 2) ); + index_channels.push_back(-1); shaders.push_back( make_pair( "deferred/screenSpaceReflUtil.glsl", ssr ? 3 : 1) ); + index_channels.push_back(-1); shaders.push_back( make_pair( "lighting/lightNonIndexedF.glsl", mShaderLevel[SHADER_LIGHTING] ) ); + index_channels.push_back(-1); shaders.push_back( make_pair( "lighting/lightAlphaMaskNonIndexedF.glsl", mShaderLevel[SHADER_LIGHTING] ) ); + index_channels.push_back(ch); shaders.push_back( make_pair( "lighting/lightF.glsl", mShaderLevel[SHADER_LIGHTING] ) ); + index_channels.push_back(ch); shaders.push_back( make_pair( "lighting/lightAlphaMaskF.glsl", mShaderLevel[SHADER_LIGHTING] ) ); + + for (U32 i = 0; i < shaders.size(); i++) + { + // Note usage of GL_FRAGMENT_SHADER + if (loadShaderFile(shaders[i].first, shaders[i].second, GL_FRAGMENT_SHADER, &attribs, index_channels[i]) == 0) + { + LL_WARNS("Shader") << "Failed to load fragment shader " << shaders[i].first << LL_ENDL; + return shaders[i].first; + } + } + + return std::string(); +} + +bool LLViewerShaderMgr::loadShadersWater() +{ + LL_PROFILE_ZONE_SCOPED; + bool success = true; + bool terrainWaterSuccess = true; + + bool use_sun_shadow = mShaderLevel[SHADER_DEFERRED] > 1 && + gSavedSettings.getS32("RenderShadowDetail") > 0; + + if (mShaderLevel[SHADER_WATER] == 0) + { + gWaterProgram.unload(); + gWaterEdgeProgram.unload(); + gUnderWaterProgram.unload(); + return true; + } + + if (success) + { + // load water shader + gWaterProgram.mName = "Water Shader"; + gWaterProgram.mFeatures.calculatesAtmospherics = true; + gWaterProgram.mFeatures.hasAtmospherics = true; + gWaterProgram.mFeatures.hasGamma = true; + gWaterProgram.mFeatures.hasSrgb = true; + gWaterProgram.mFeatures.hasReflectionProbes = true; + gWaterProgram.mFeatures.hasShadows = use_sun_shadow; + gWaterProgram.mShaderFiles.clear(); + gWaterProgram.mShaderFiles.push_back(make_pair("environment/waterV.glsl", GL_VERTEX_SHADER)); + gWaterProgram.mShaderFiles.push_back(make_pair("environment/waterF.glsl", GL_FRAGMENT_SHADER)); + gWaterProgram.clearPermutations(); + if (LLPipeline::sRenderTransparentWater) + { + gWaterProgram.addPermutation("TRANSPARENT_WATER", "1"); + } + + if (use_sun_shadow) + { + gWaterProgram.addPermutation("HAS_SUN_SHADOW", "1"); + } + + gWaterProgram.mShaderGroup = LLGLSLShader::SG_WATER; + gWaterProgram.mShaderLevel = mShaderLevel[SHADER_WATER]; + success = gWaterProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + // load water shader + gWaterEdgeProgram.mName = "Water Edge Shader"; + gWaterEdgeProgram.mFeatures.calculatesAtmospherics = true; + gWaterEdgeProgram.mFeatures.hasAtmospherics = true; + gWaterEdgeProgram.mFeatures.hasGamma = true; + gWaterEdgeProgram.mFeatures.hasSrgb = true; + gWaterEdgeProgram.mFeatures.hasReflectionProbes = true; + gWaterEdgeProgram.mFeatures.hasShadows = use_sun_shadow; + gWaterEdgeProgram.mShaderFiles.clear(); + gWaterEdgeProgram.mShaderFiles.push_back(make_pair("environment/waterV.glsl", GL_VERTEX_SHADER)); + gWaterEdgeProgram.mShaderFiles.push_back(make_pair("environment/waterF.glsl", GL_FRAGMENT_SHADER)); + gWaterEdgeProgram.clearPermutations(); + gWaterEdgeProgram.addPermutation("WATER_EDGE", "1"); + if (LLPipeline::sRenderTransparentWater) + { + gWaterEdgeProgram.addPermutation("TRANSPARENT_WATER", "1"); + } + + if (use_sun_shadow) + { + gWaterEdgeProgram.addPermutation("HAS_SUN_SHADOW", "1"); + } + gWaterEdgeProgram.mShaderGroup = LLGLSLShader::SG_WATER; + gWaterEdgeProgram.mShaderLevel = mShaderLevel[SHADER_WATER]; + success = gWaterEdgeProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + //load under water vertex shader + gUnderWaterProgram.mName = "Underwater Shader"; + gUnderWaterProgram.mFeatures.calculatesAtmospherics = true; + gUnderWaterProgram.mFeatures.hasAtmospherics = true; + gUnderWaterProgram.mShaderFiles.clear(); + gUnderWaterProgram.mShaderFiles.push_back(make_pair("environment/waterV.glsl", GL_VERTEX_SHADER)); + gUnderWaterProgram.mShaderFiles.push_back(make_pair("environment/underWaterF.glsl", GL_FRAGMENT_SHADER)); + gUnderWaterProgram.mShaderLevel = mShaderLevel[SHADER_WATER]; + gUnderWaterProgram.mShaderGroup = LLGLSLShader::SG_WATER; + gUnderWaterProgram.clearPermutations(); + if (LLPipeline::sRenderTransparentWater) + { + gUnderWaterProgram.addPermutation("TRANSPARENT_WATER", "1"); + } + success = gUnderWaterProgram.createShader(NULL, NULL); + llassert(success); + } + + /// Keep track of water shader levels + if (gWaterProgram.mShaderLevel != mShaderLevel[SHADER_WATER] + || gUnderWaterProgram.mShaderLevel != mShaderLevel[SHADER_WATER]) + { + mShaderLevel[SHADER_WATER] = llmin(gWaterProgram.mShaderLevel, gUnderWaterProgram.mShaderLevel); + } + + if (!success) + { + mShaderLevel[SHADER_WATER] = 0; + return false; + } + + // if we failed to load the terrain water shaders and we need them (using class2 water), + // then drop down to class1 water. + if (mShaderLevel[SHADER_WATER] > 1 && !terrainWaterSuccess) + { + mShaderLevel[SHADER_WATER]--; + return loadShadersWater(); + } + + LLWorld::getInstance()->updateWaterObjects(); + + return true; +} + +bool LLViewerShaderMgr::loadShadersEffects() +{ + LL_PROFILE_ZONE_SCOPED; + bool success = true; + + if (mShaderLevel[SHADER_EFFECT] == 0) + { + gGlowProgram.unload(); + gGlowExtractProgram.unload(); + return true; + } + + if (success) + { + gGlowProgram.mName = "Glow Shader (Post)"; + gGlowProgram.mShaderFiles.clear(); + gGlowProgram.mShaderFiles.push_back(make_pair("effects/glowV.glsl", GL_VERTEX_SHADER)); + gGlowProgram.mShaderFiles.push_back(make_pair("effects/glowF.glsl", GL_FRAGMENT_SHADER)); + gGlowProgram.mShaderLevel = mShaderLevel[SHADER_EFFECT]; + success = gGlowProgram.createShader(NULL, NULL); + if (!success) + { + LLPipeline::sRenderGlow = false; + } + } + + if (success) + { + const bool use_glow_noise = gSavedSettings.getBOOL("RenderGlowNoise"); + const std::string glow_noise_label = use_glow_noise ? " (+Noise)" : ""; + + gGlowExtractProgram.mName = llformat("Glow Extract Shader (Post)%s", glow_noise_label.c_str()); + gGlowExtractProgram.mShaderFiles.clear(); + gGlowExtractProgram.mShaderFiles.push_back(make_pair("effects/glowExtractV.glsl", GL_VERTEX_SHADER)); + gGlowExtractProgram.mShaderFiles.push_back(make_pair("effects/glowExtractF.glsl", GL_FRAGMENT_SHADER)); + gGlowExtractProgram.mShaderLevel = mShaderLevel[SHADER_EFFECT]; + + if (use_glow_noise) + { + gGlowExtractProgram.addPermutation("HAS_NOISE", "1"); + } + + success = gGlowExtractProgram.createShader(NULL, NULL); + if (!success) + { + LLPipeline::sRenderGlow = false; + } + } + + return success; + +} + +bool LLViewerShaderMgr::loadShadersDeferred() +{ + LL_PROFILE_ZONE_SCOPED; + bool use_sun_shadow = mShaderLevel[SHADER_DEFERRED] > 1 && + gSavedSettings.getS32("RenderShadowDetail") > 0; + + if (mShaderLevel[SHADER_DEFERRED] == 0) + { + gDeferredTreeProgram.unload(); + gDeferredTreeShadowProgram.unload(); + gDeferredSkinnedTreeShadowProgram.unload(); + gDeferredDiffuseProgram.unload(); + gDeferredSkinnedDiffuseProgram.unload(); + gDeferredDiffuseAlphaMaskProgram.unload(); + gDeferredSkinnedDiffuseAlphaMaskProgram.unload(); + gDeferredNonIndexedDiffuseAlphaMaskProgram.unload(); + gDeferredNonIndexedDiffuseAlphaMaskNoColorProgram.unload(); + gDeferredBumpProgram.unload(); + gDeferredSkinnedBumpProgram.unload(); + gDeferredImpostorProgram.unload(); + gDeferredTerrainProgram.unload(); + gDeferredLightProgram.unload(); + for (U32 i = 0; i < LL_DEFERRED_MULTI_LIGHT_COUNT; ++i) + { + gDeferredMultiLightProgram[i].unload(); + } + gDeferredSpotLightProgram.unload(); + gDeferredMultiSpotLightProgram.unload(); + gDeferredSunProgram.unload(); + gDeferredBlurLightProgram.unload(); + gDeferredSoftenProgram.unload(); + gDeferredShadowProgram.unload(); + gDeferredSkinnedShadowProgram.unload(); + gDeferredShadowCubeProgram.unload(); + gDeferredShadowAlphaMaskProgram.unload(); + gDeferredSkinnedShadowAlphaMaskProgram.unload(); + gDeferredShadowGLTFAlphaMaskProgram.unload(); + gDeferredSkinnedShadowGLTFAlphaMaskProgram.unload(); + gDeferredShadowFullbrightAlphaMaskProgram.unload(); + gDeferredSkinnedShadowFullbrightAlphaMaskProgram.unload(); + gDeferredAvatarShadowProgram.unload(); + gDeferredAvatarAlphaShadowProgram.unload(); + gDeferredAvatarAlphaMaskShadowProgram.unload(); + gDeferredAvatarProgram.unload(); + gDeferredAvatarAlphaProgram.unload(); + gDeferredAlphaProgram.unload(); + gHUDAlphaProgram.unload(); + gDeferredSkinnedAlphaProgram.unload(); + gDeferredFullbrightProgram.unload(); + gHUDFullbrightProgram.unload(); + gDeferredFullbrightAlphaMaskProgram.unload(); + gHUDFullbrightAlphaMaskProgram.unload(); + gDeferredFullbrightAlphaMaskAlphaProgram.unload(); + gHUDFullbrightAlphaMaskAlphaProgram.unload(); + gDeferredEmissiveProgram.unload(); + gDeferredSkinnedEmissiveProgram.unload(); + gDeferredAvatarEyesProgram.unload(); + gDeferredPostProgram.unload(); + gDeferredCoFProgram.unload(); + gDeferredDoFCombineProgram.unload(); + gExposureProgram.unload(); + gLuminanceProgram.unload(); + gDeferredPostGammaCorrectProgram.unload(); + gNoPostGammaCorrectProgram.unload(); + gLegacyPostGammaCorrectProgram.unload(); + gFXAAProgram.unload(); + gDeferredWLSkyProgram.unload(); + gDeferredWLCloudProgram.unload(); + gDeferredWLSunProgram.unload(); + gDeferredWLMoonProgram.unload(); + gDeferredStarProgram.unload(); + gDeferredFullbrightShinyProgram.unload(); + gHUDFullbrightShinyProgram.unload(); + gDeferredSkinnedFullbrightShinyProgram.unload(); + gDeferredSkinnedFullbrightProgram.unload(); + gDeferredSkinnedFullbrightAlphaMaskProgram.unload(); + gDeferredSkinnedFullbrightAlphaMaskAlphaProgram.unload(); + + gDeferredHighlightProgram.unload(); + + gNormalMapGenProgram.unload(); + gDeferredGenBrdfLutProgram.unload(); + gDeferredBufferVisualProgram.unload(); + + for (U32 i = 0; i < LLMaterial::SHADER_COUNT*2; ++i) + { + gDeferredMaterialProgram[i].unload(); + } + + gHUDPBROpaqueProgram.unload(); + gPBRGlowProgram.unload(); + gDeferredPBROpaqueProgram.unload(); + gDeferredSkinnedPBROpaqueProgram.unload(); + gDeferredPBRAlphaProgram.unload(); + gDeferredSkinnedPBRAlphaProgram.unload(); + + return true; + } + + bool success = true; + + if (success) + { + gDeferredHighlightProgram.mName = "Deferred Highlight Shader"; + gDeferredHighlightProgram.mShaderFiles.clear(); + gDeferredHighlightProgram.mShaderFiles.push_back(make_pair("interface/highlightV.glsl", GL_VERTEX_SHADER)); + gDeferredHighlightProgram.mShaderFiles.push_back(make_pair("deferred/highlightF.glsl", GL_FRAGMENT_SHADER)); + gDeferredHighlightProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = gDeferredHighlightProgram.createShader(NULL, NULL); + } + + if (success) + { + gDeferredDiffuseProgram.mName = "Deferred Diffuse Shader"; + gDeferredDiffuseProgram.mFeatures.encodesNormal = true; + gDeferredDiffuseProgram.mFeatures.hasSrgb = true; + gDeferredDiffuseProgram.mShaderFiles.clear(); + gDeferredDiffuseProgram.mShaderFiles.push_back(make_pair("deferred/diffuseV.glsl", GL_VERTEX_SHADER)); + gDeferredDiffuseProgram.mShaderFiles.push_back(make_pair("deferred/diffuseIndexedF.glsl", GL_FRAGMENT_SHADER)); + gDeferredDiffuseProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; + gDeferredDiffuseProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = make_rigged_variant(gDeferredDiffuseProgram, gDeferredSkinnedDiffuseProgram); + success = success && gDeferredDiffuseProgram.createShader(NULL, NULL); + } + + if (success) + { + gDeferredDiffuseAlphaMaskProgram.mName = "Deferred Diffuse Alpha Mask Shader"; + gDeferredDiffuseAlphaMaskProgram.mFeatures.encodesNormal = true; + gDeferredDiffuseAlphaMaskProgram.mShaderFiles.clear(); + gDeferredDiffuseAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/diffuseV.glsl", GL_VERTEX_SHADER)); + gDeferredDiffuseAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/diffuseAlphaMaskIndexedF.glsl", GL_FRAGMENT_SHADER)); + gDeferredDiffuseAlphaMaskProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; + gDeferredDiffuseAlphaMaskProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = make_rigged_variant(gDeferredDiffuseAlphaMaskProgram, gDeferredSkinnedDiffuseAlphaMaskProgram); + success = success && gDeferredDiffuseAlphaMaskProgram.createShader(NULL, NULL); + } + + if (success) + { + gDeferredNonIndexedDiffuseAlphaMaskProgram.mName = "Deferred Diffuse Non-Indexed Alpha Mask Shader"; + gDeferredNonIndexedDiffuseAlphaMaskProgram.mFeatures.encodesNormal = true; + gDeferredNonIndexedDiffuseAlphaMaskProgram.mShaderFiles.clear(); + gDeferredNonIndexedDiffuseAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/diffuseV.glsl", GL_VERTEX_SHADER)); + gDeferredNonIndexedDiffuseAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/diffuseAlphaMaskF.glsl", GL_FRAGMENT_SHADER)); + gDeferredNonIndexedDiffuseAlphaMaskProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gDeferredNonIndexedDiffuseAlphaMaskProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredNonIndexedDiffuseAlphaMaskNoColorProgram.mName = "Deferred Diffuse Non-Indexed Alpha Mask No Color Shader"; + gDeferredNonIndexedDiffuseAlphaMaskNoColorProgram.mFeatures.encodesNormal = true; + gDeferredNonIndexedDiffuseAlphaMaskNoColorProgram.mShaderFiles.clear(); + gDeferredNonIndexedDiffuseAlphaMaskNoColorProgram.mShaderFiles.push_back(make_pair("deferred/diffuseNoColorV.glsl", GL_VERTEX_SHADER)); + gDeferredNonIndexedDiffuseAlphaMaskNoColorProgram.mShaderFiles.push_back(make_pair("deferred/diffuseAlphaMaskNoColorF.glsl", GL_FRAGMENT_SHADER)); + gDeferredNonIndexedDiffuseAlphaMaskNoColorProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gDeferredNonIndexedDiffuseAlphaMaskNoColorProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredBumpProgram.mName = "Deferred Bump Shader"; + gDeferredBumpProgram.mFeatures.encodesNormal = true; + gDeferredBumpProgram.mShaderFiles.clear(); + gDeferredBumpProgram.mShaderFiles.push_back(make_pair("deferred/bumpV.glsl", GL_VERTEX_SHADER)); + gDeferredBumpProgram.mShaderFiles.push_back(make_pair("deferred/bumpF.glsl", GL_FRAGMENT_SHADER)); + gDeferredBumpProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = make_rigged_variant(gDeferredBumpProgram, gDeferredSkinnedBumpProgram); + success = success && gDeferredBumpProgram.createShader(NULL, NULL); + llassert(success); + } + + gDeferredMaterialProgram[1].mFeatures.hasLighting = false; + gDeferredMaterialProgram[5].mFeatures.hasLighting = false; + gDeferredMaterialProgram[9].mFeatures.hasLighting = false; + gDeferredMaterialProgram[13].mFeatures.hasLighting = false; + gDeferredMaterialProgram[1+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = false; + gDeferredMaterialProgram[5+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = false; + gDeferredMaterialProgram[9+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = false; + gDeferredMaterialProgram[13+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = false; + + for (U32 i = 0; i < LLMaterial::SHADER_COUNT*2; ++i) + { + if (success) + { + mShaderList.push_back(&gDeferredMaterialProgram[i]); + + gDeferredMaterialProgram[i].mName = llformat("Deferred Material Shader %d", i); + + U32 alpha_mode = i & 0x3; + + gDeferredMaterialProgram[i].mShaderFiles.clear(); + gDeferredMaterialProgram[i].mShaderFiles.push_back(make_pair("deferred/materialV.glsl", GL_VERTEX_SHADER)); + gDeferredMaterialProgram[i].mShaderFiles.push_back(make_pair("deferred/materialF.glsl", GL_FRAGMENT_SHADER)); + gDeferredMaterialProgram[i].mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + + gDeferredMaterialProgram[i].clearPermutations(); + + bool has_normal_map = (i & 0x8) > 0; + bool has_specular_map = (i & 0x4) > 0; + + if (has_normal_map) + { + gDeferredMaterialProgram[i].addPermutation("HAS_NORMAL_MAP", "1"); + } + + if (has_specular_map) + { + gDeferredMaterialProgram[i].addPermutation("HAS_SPECULAR_MAP", "1"); + } + + gDeferredMaterialProgram[i].addPermutation("DIFFUSE_ALPHA_MODE", llformat("%d", alpha_mode)); + + if (alpha_mode != 0) + { + gDeferredMaterialProgram[i].mFeatures.hasAlphaMask = true; + gDeferredMaterialProgram[i].addPermutation("HAS_ALPHA_MASK", "1"); + } + + if (use_sun_shadow) + { + gDeferredMaterialProgram[i].addPermutation("HAS_SUN_SHADOW", "1"); + } + + bool has_skin = i & 0x10; + gDeferredMaterialProgram[i].mFeatures.hasSrgb = true; + gDeferredMaterialProgram[i].mFeatures.encodesNormal = true; + gDeferredMaterialProgram[i].mFeatures.calculatesAtmospherics = true; + gDeferredMaterialProgram[i].mFeatures.hasAtmospherics = true; + gDeferredMaterialProgram[i].mFeatures.hasGamma = true; + gDeferredMaterialProgram[i].mFeatures.hasShadows = use_sun_shadow; + gDeferredMaterialProgram[i].mFeatures.hasReflectionProbes = true; + + if (has_skin) + { + gDeferredMaterialProgram[i].addPermutation("HAS_SKIN", "1"); + gDeferredMaterialProgram[i].mFeatures.hasObjectSkinning = true; + } + else + { + gDeferredMaterialProgram[i].mRiggedVariant = &gDeferredMaterialProgram[i + 0x10]; + } + + success = gDeferredMaterialProgram[i].createShader(NULL, NULL); + llassert(success); + } + } + + gDeferredMaterialProgram[1].mFeatures.hasLighting = true; + gDeferredMaterialProgram[5].mFeatures.hasLighting = true; + gDeferredMaterialProgram[9].mFeatures.hasLighting = true; + gDeferredMaterialProgram[13].mFeatures.hasLighting = true; + gDeferredMaterialProgram[1+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = true; + gDeferredMaterialProgram[5+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = true; + gDeferredMaterialProgram[9+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = true; + gDeferredMaterialProgram[13+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = true; + + if (success) + { + gDeferredPBROpaqueProgram.mName = "Deferred PBR Opaque Shader"; + gDeferredPBROpaqueProgram.mFeatures.encodesNormal = true; + gDeferredPBROpaqueProgram.mFeatures.hasSrgb = true; + + gDeferredPBROpaqueProgram.mShaderFiles.clear(); + gDeferredPBROpaqueProgram.mShaderFiles.push_back(make_pair("deferred/pbropaqueV.glsl", GL_VERTEX_SHADER)); + gDeferredPBROpaqueProgram.mShaderFiles.push_back(make_pair("deferred/pbropaqueF.glsl", GL_FRAGMENT_SHADER)); + gDeferredPBROpaqueProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + gDeferredPBROpaqueProgram.clearPermutations(); + + success = make_rigged_variant(gDeferredPBROpaqueProgram, gDeferredSkinnedPBROpaqueProgram); + if (success) + { + success = gDeferredPBROpaqueProgram.createShader(NULL, NULL); + } + llassert(success); + } + + if (success) + { + gPBRGlowProgram.mName = " PBR Glow Shader"; + gPBRGlowProgram.mFeatures.hasSrgb = true; + gPBRGlowProgram.mShaderFiles.clear(); + gPBRGlowProgram.mShaderFiles.push_back(make_pair("deferred/pbrglowV.glsl", GL_VERTEX_SHADER)); + gPBRGlowProgram.mShaderFiles.push_back(make_pair("deferred/pbrglowF.glsl", GL_FRAGMENT_SHADER)); + gPBRGlowProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + + success = make_rigged_variant(gPBRGlowProgram, gPBRGlowSkinnedProgram); + if (success) + { + success = gPBRGlowProgram.createShader(NULL, NULL); + } + llassert(success); + } + + if (success) + { + gHUDPBROpaqueProgram.mName = "HUD PBR Opaque Shader"; + gHUDPBROpaqueProgram.mFeatures.hasSrgb = true; + gHUDPBROpaqueProgram.mShaderFiles.clear(); + gHUDPBROpaqueProgram.mShaderFiles.push_back(make_pair("deferred/pbropaqueV.glsl", GL_VERTEX_SHADER)); + gHUDPBROpaqueProgram.mShaderFiles.push_back(make_pair("deferred/pbropaqueF.glsl", GL_FRAGMENT_SHADER)); + gHUDPBROpaqueProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + gHUDPBROpaqueProgram.clearPermutations(); + gHUDPBROpaqueProgram.addPermutation("IS_HUD", "1"); + + success = gHUDPBROpaqueProgram.createShader(NULL, NULL); + + llassert(success); + } + + + + if (success) + { + LLGLSLShader* shader = &gDeferredPBRAlphaProgram; + shader->mName = "Deferred PBR Alpha Shader"; + + shader->mFeatures.calculatesLighting = false; + shader->mFeatures.hasLighting = false; + shader->mFeatures.isAlphaLighting = true; + shader->mFeatures.hasSrgb = true; + shader->mFeatures.encodesNormal = true; + shader->mFeatures.calculatesAtmospherics = true; + shader->mFeatures.hasAtmospherics = true; + shader->mFeatures.hasGamma = true; + shader->mFeatures.hasShadows = use_sun_shadow; + shader->mFeatures.isDeferred = true; // include deferredUtils + shader->mFeatures.hasReflectionProbes = mShaderLevel[SHADER_DEFERRED]; + + shader->mShaderFiles.clear(); + shader->mShaderFiles.push_back(make_pair("deferred/pbralphaV.glsl", GL_VERTEX_SHADER)); + shader->mShaderFiles.push_back(make_pair("deferred/pbralphaF.glsl", GL_FRAGMENT_SHADER)); + + shader->clearPermutations(); + + U32 alpha_mode = LLMaterial::DIFFUSE_ALPHA_MODE_BLEND; + shader->addPermutation("DIFFUSE_ALPHA_MODE", llformat("%d", alpha_mode)); + shader->addPermutation("HAS_NORMAL_MAP", "1"); + shader->addPermutation("HAS_SPECULAR_MAP", "1"); // PBR: Packed: Occlusion, Metal, Roughness + shader->addPermutation("HAS_EMISSIVE_MAP", "1"); + shader->addPermutation("USE_VERTEX_COLOR", "1"); + + if (use_sun_shadow) + { + shader->addPermutation("HAS_SUN_SHADOW", "1"); + } + + shader->mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = make_rigged_variant(*shader, gDeferredSkinnedPBRAlphaProgram); + if (success) + { + success = shader->createShader(NULL, NULL); + } + llassert(success); + + // Alpha Shader Hack + // See: LLRender::syncMatrices() + shader->mFeatures.calculatesLighting = true; + shader->mFeatures.hasLighting = true; + + shader->mRiggedVariant->mFeatures.calculatesLighting = true; + shader->mRiggedVariant->mFeatures.hasLighting = true; + } + + if (success) + { + LLGLSLShader* shader = &gHUDPBRAlphaProgram; + shader->mName = "HUD PBR Alpha Shader"; + + shader->mFeatures.hasSrgb = true; + + shader->mShaderFiles.clear(); + shader->mShaderFiles.push_back(make_pair("deferred/pbralphaV.glsl", GL_VERTEX_SHADER)); + shader->mShaderFiles.push_back(make_pair("deferred/pbralphaF.glsl", GL_FRAGMENT_SHADER)); + + shader->clearPermutations(); + + shader->addPermutation("IS_HUD", "1"); + + shader->mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = shader->createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredTreeProgram.mName = "Deferred Tree Shader"; + gDeferredTreeProgram.mShaderFiles.clear(); + gDeferredTreeProgram.mFeatures.encodesNormal = true; + gDeferredTreeProgram.mShaderFiles.push_back(make_pair("deferred/treeV.glsl", GL_VERTEX_SHADER)); + gDeferredTreeProgram.mShaderFiles.push_back(make_pair("deferred/treeF.glsl", GL_FRAGMENT_SHADER)); + gDeferredTreeProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gDeferredTreeProgram.createShader(NULL, NULL); + } + + if (success) + { + gDeferredTreeShadowProgram.mName = "Deferred Tree Shadow Shader"; + gDeferredTreeShadowProgram.mShaderFiles.clear(); + gDeferredTreeShadowProgram.mShaderFiles.push_back(make_pair("deferred/treeShadowV.glsl", GL_VERTEX_SHADER)); + gDeferredTreeShadowProgram.mShaderFiles.push_back(make_pair("deferred/treeShadowF.glsl", GL_FRAGMENT_SHADER)); + gDeferredTreeShadowProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + gDeferredTreeShadowProgram.mRiggedVariant = &gDeferredSkinnedTreeShadowProgram; + success = gDeferredTreeShadowProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredSkinnedTreeShadowProgram.mName = "Deferred Skinned Tree Shadow Shader"; + gDeferredSkinnedTreeShadowProgram.mShaderFiles.clear(); + gDeferredSkinnedTreeShadowProgram.mFeatures.hasObjectSkinning = true; + gDeferredSkinnedTreeShadowProgram.mShaderFiles.push_back(make_pair("deferred/treeShadowSkinnedV.glsl", GL_VERTEX_SHADER)); + gDeferredSkinnedTreeShadowProgram.mShaderFiles.push_back(make_pair("deferred/treeShadowF.glsl", GL_FRAGMENT_SHADER)); + gDeferredSkinnedTreeShadowProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gDeferredSkinnedTreeShadowProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredImpostorProgram.mName = "Deferred Impostor Shader"; + gDeferredImpostorProgram.mFeatures.hasSrgb = true; + gDeferredImpostorProgram.mFeatures.encodesNormal = true; + //gDeferredImpostorProgram.mFeatures.isDeferred = true; + gDeferredImpostorProgram.mShaderFiles.clear(); + gDeferredImpostorProgram.mShaderFiles.push_back(make_pair("deferred/impostorV.glsl", GL_VERTEX_SHADER)); + gDeferredImpostorProgram.mShaderFiles.push_back(make_pair("deferred/impostorF.glsl", GL_FRAGMENT_SHADER)); + gDeferredImpostorProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gDeferredImpostorProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredLightProgram.mName = "Deferred Light Shader"; + gDeferredLightProgram.mFeatures.isDeferred = true; + gDeferredLightProgram.mFeatures.hasShadows = true; + gDeferredLightProgram.mFeatures.hasSrgb = true; + + gDeferredLightProgram.mShaderFiles.clear(); + gDeferredLightProgram.mShaderFiles.push_back(make_pair("deferred/pointLightV.glsl", GL_VERTEX_SHADER)); + gDeferredLightProgram.mShaderFiles.push_back(make_pair("deferred/pointLightF.glsl", GL_FRAGMENT_SHADER)); + gDeferredLightProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + + gDeferredLightProgram.clearPermutations(); + + success = gDeferredLightProgram.createShader(NULL, NULL); + llassert(success); + } + + for (U32 i = 0; i < LL_DEFERRED_MULTI_LIGHT_COUNT; i++) + { + if (success) + { + gDeferredMultiLightProgram[i].mName = llformat("Deferred MultiLight Shader %d", i); + gDeferredMultiLightProgram[i].mFeatures.isDeferred = true; + gDeferredMultiLightProgram[i].mFeatures.hasShadows = true; + gDeferredMultiLightProgram[i].mFeatures.hasSrgb = true; + + gDeferredMultiLightProgram[i].clearPermutations(); + gDeferredMultiLightProgram[i].mShaderFiles.clear(); + gDeferredMultiLightProgram[i].mShaderFiles.push_back(make_pair("deferred/multiPointLightV.glsl", GL_VERTEX_SHADER)); + gDeferredMultiLightProgram[i].mShaderFiles.push_back(make_pair("deferred/multiPointLightF.glsl", GL_FRAGMENT_SHADER)); + gDeferredMultiLightProgram[i].mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + gDeferredMultiLightProgram[i].addPermutation("LIGHT_COUNT", llformat("%d", i+1)); + + success = gDeferredMultiLightProgram[i].createShader(NULL, NULL); + llassert(success); + } + } + + if (success) + { + gDeferredSpotLightProgram.mName = "Deferred SpotLight Shader"; + gDeferredSpotLightProgram.mShaderFiles.clear(); + gDeferredSpotLightProgram.mFeatures.hasSrgb = true; + gDeferredSpotLightProgram.mFeatures.isDeferred = true; + gDeferredSpotLightProgram.mFeatures.hasShadows = true; + + gDeferredSpotLightProgram.clearPermutations(); + gDeferredSpotLightProgram.mShaderFiles.push_back(make_pair("deferred/pointLightV.glsl", GL_VERTEX_SHADER)); + gDeferredSpotLightProgram.mShaderFiles.push_back(make_pair("deferred/spotLightF.glsl", GL_FRAGMENT_SHADER)); + gDeferredSpotLightProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + + success = gDeferredSpotLightProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredMultiSpotLightProgram.mName = "Deferred MultiSpotLight Shader"; + gDeferredMultiSpotLightProgram.mFeatures.hasSrgb = true; + gDeferredMultiSpotLightProgram.mFeatures.isDeferred = true; + gDeferredMultiSpotLightProgram.mFeatures.hasShadows = true; + + gDeferredMultiSpotLightProgram.clearPermutations(); + gDeferredMultiSpotLightProgram.addPermutation("MULTI_SPOTLIGHT", "1"); + gDeferredMultiSpotLightProgram.mShaderFiles.clear(); + gDeferredMultiSpotLightProgram.mShaderFiles.push_back(make_pair("deferred/multiPointLightV.glsl", GL_VERTEX_SHADER)); + gDeferredMultiSpotLightProgram.mShaderFiles.push_back(make_pair("deferred/spotLightF.glsl", GL_FRAGMENT_SHADER)); + gDeferredMultiSpotLightProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + + success = gDeferredMultiSpotLightProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + std::string fragment; + std::string vertex = "deferred/sunLightV.glsl"; + + bool use_ao = gSavedSettings.getBOOL("RenderDeferredSSAO"); + + if (use_ao) + { + fragment = "deferred/sunLightSSAOF.glsl"; + } + else + { + fragment = "deferred/sunLightF.glsl"; + if (mShaderLevel[SHADER_DEFERRED] == 1) + { //no shadows, no SSAO, no frag coord + vertex = "deferred/sunLightNoFragCoordV.glsl"; + } + } + + gDeferredSunProgram.mName = "Deferred Sun Shader"; + gDeferredSunProgram.mFeatures.isDeferred = true; + gDeferredSunProgram.mFeatures.hasShadows = true; + gDeferredSunProgram.mFeatures.hasAmbientOcclusion = use_ao; + + gDeferredSunProgram.mShaderFiles.clear(); + gDeferredSunProgram.mShaderFiles.push_back(make_pair(vertex, GL_VERTEX_SHADER)); + gDeferredSunProgram.mShaderFiles.push_back(make_pair(fragment, GL_FRAGMENT_SHADER)); + gDeferredSunProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + + success = gDeferredSunProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredBlurLightProgram.mName = "Deferred Blur Light Shader"; + gDeferredBlurLightProgram.mFeatures.isDeferred = true; + + gDeferredBlurLightProgram.mShaderFiles.clear(); + gDeferredBlurLightProgram.mShaderFiles.push_back(make_pair("deferred/blurLightV.glsl", GL_VERTEX_SHADER)); + gDeferredBlurLightProgram.mShaderFiles.push_back(make_pair("deferred/blurLightF.glsl", GL_FRAGMENT_SHADER)); + gDeferredBlurLightProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + + success = gDeferredBlurLightProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + for (int i = 0; i < 3 && success; ++i) + { + LLGLSLShader* shader = nullptr; + bool rigged = (i == 1); + bool hud = (i == 2); + + if (hud) + { + shader = &gHUDAlphaProgram; + shader->mName = "HUD Alpha Shader"; + } + else if (!rigged) + { + shader = &gDeferredAlphaProgram; + shader->mName = "Deferred Alpha Shader"; + shader->mRiggedVariant = &gDeferredSkinnedAlphaProgram; + } + else + { + shader = &gDeferredSkinnedAlphaProgram; + shader->mName = "Skinned Deferred Alpha Shader"; + shader->mFeatures.hasObjectSkinning = true; + } + + shader->mFeatures.calculatesLighting = false; + shader->mFeatures.hasLighting = false; + shader->mFeatures.isAlphaLighting = true; + shader->mFeatures.disableTextureIndex = true; //hack to disable auto-setup of texture channels + shader->mFeatures.hasSrgb = true; + shader->mFeatures.encodesNormal = true; + shader->mFeatures.calculatesAtmospherics = true; + shader->mFeatures.hasAtmospherics = true; + shader->mFeatures.hasGamma = true; + shader->mFeatures.hasShadows = use_sun_shadow; + shader->mFeatures.hasReflectionProbes = true; + shader->mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; + + shader->mShaderFiles.clear(); + shader->mShaderFiles.push_back(make_pair("deferred/alphaV.glsl", GL_VERTEX_SHADER)); + shader->mShaderFiles.push_back(make_pair("deferred/alphaF.glsl", GL_FRAGMENT_SHADER)); + + shader->clearPermutations(); + shader->addPermutation("USE_VERTEX_COLOR", "1"); + shader->addPermutation("HAS_ALPHA_MASK", "1"); + shader->addPermutation("USE_INDEXED_TEX", "1"); + if (use_sun_shadow) + { + shader->addPermutation("HAS_SUN_SHADOW", "1"); + } + + if (rigged) + { + shader->addPermutation("HAS_SKIN", "1"); + } + + if (hud) + { + shader->addPermutation("IS_HUD", "1"); + } + + shader->mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + + success = shader->createShader(NULL, NULL); + llassert(success); + + // Hack + shader->mFeatures.calculatesLighting = true; + shader->mFeatures.hasLighting = true; + } + } + + if (success) + { + LLGLSLShader* shaders[] = { + &gDeferredAlphaImpostorProgram, + &gDeferredSkinnedAlphaImpostorProgram + }; + + for (int i = 0; i < 2 && success; ++i) + { + bool rigged = i == 1; + LLGLSLShader* shader = shaders[i]; + + shader->mName = rigged ? "Skinned Deferred Alpha Impostor Shader" : "Deferred Alpha Impostor Shader"; + + // Begin Hack + shader->mFeatures.calculatesLighting = false; + shader->mFeatures.hasLighting = false; + + shader->mFeatures.hasSrgb = true; + shader->mFeatures.isAlphaLighting = true; + shader->mFeatures.encodesNormal = true; + shader->mFeatures.hasShadows = use_sun_shadow; + shader->mFeatures.hasReflectionProbes = true; + shader->mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; + + shader->mShaderFiles.clear(); + shader->mShaderFiles.push_back(make_pair("deferred/alphaV.glsl", GL_VERTEX_SHADER)); + shader->mShaderFiles.push_back(make_pair("deferred/alphaF.glsl", GL_FRAGMENT_SHADER)); + + shader->clearPermutations(); + shader->addPermutation("USE_INDEXED_TEX", "1"); + shader->addPermutation("FOR_IMPOSTOR", "1"); + shader->addPermutation("HAS_ALPHA_MASK", "1"); + shader->addPermutation("USE_VERTEX_COLOR", "1"); + if (rigged) + { + shader->mFeatures.hasObjectSkinning = true; + shader->addPermutation("HAS_SKIN", "1"); + } + + if (use_sun_shadow) + { + shader->addPermutation("HAS_SUN_SHADOW", "1"); + } + + shader->mRiggedVariant = &gDeferredSkinnedAlphaImpostorProgram; + shader->mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + if (!rigged) + { + shader->mRiggedVariant = shaders[1]; + } + success = shader->createShader(NULL, NULL); + llassert(success); + + // End Hack + shader->mFeatures.calculatesLighting = true; + shader->mFeatures.hasLighting = true; + } + } + + if (success) + { + gDeferredAvatarEyesProgram.mName = "Deferred Avatar Eyes Shader"; + gDeferredAvatarEyesProgram.mFeatures.calculatesAtmospherics = true; + gDeferredAvatarEyesProgram.mFeatures.hasGamma = true; + gDeferredAvatarEyesProgram.mFeatures.hasAtmospherics = true; + gDeferredAvatarEyesProgram.mFeatures.disableTextureIndex = true; + gDeferredAvatarEyesProgram.mFeatures.hasSrgb = true; + gDeferredAvatarEyesProgram.mFeatures.encodesNormal = true; + gDeferredAvatarEyesProgram.mFeatures.hasShadows = true; + + gDeferredAvatarEyesProgram.mShaderFiles.clear(); + gDeferredAvatarEyesProgram.mShaderFiles.push_back(make_pair("deferred/avatarEyesV.glsl", GL_VERTEX_SHADER)); + gDeferredAvatarEyesProgram.mShaderFiles.push_back(make_pair("deferred/diffuseF.glsl", GL_FRAGMENT_SHADER)); + gDeferredAvatarEyesProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gDeferredAvatarEyesProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredFullbrightProgram.mName = "Deferred Fullbright Shader"; + gDeferredFullbrightProgram.mFeatures.calculatesAtmospherics = true; + gDeferredFullbrightProgram.mFeatures.hasGamma = true; + gDeferredFullbrightProgram.mFeatures.hasAtmospherics = true; + gDeferredFullbrightProgram.mFeatures.hasSrgb = true; + gDeferredFullbrightProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; + gDeferredFullbrightProgram.mShaderFiles.clear(); + gDeferredFullbrightProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightV.glsl", GL_VERTEX_SHADER)); + gDeferredFullbrightProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightF.glsl", GL_FRAGMENT_SHADER)); + gDeferredFullbrightProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = make_rigged_variant(gDeferredFullbrightProgram, gDeferredSkinnedFullbrightProgram); + success = gDeferredFullbrightProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gHUDFullbrightProgram.mName = "HUD Fullbright Shader"; + gHUDFullbrightProgram.mFeatures.calculatesAtmospherics = true; + gHUDFullbrightProgram.mFeatures.hasGamma = true; + gHUDFullbrightProgram.mFeatures.hasAtmospherics = true; + gHUDFullbrightProgram.mFeatures.hasSrgb = true; + gHUDFullbrightProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; + gHUDFullbrightProgram.mShaderFiles.clear(); + gHUDFullbrightProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightV.glsl", GL_VERTEX_SHADER)); + gHUDFullbrightProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightF.glsl", GL_FRAGMENT_SHADER)); + gHUDFullbrightProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + gHUDFullbrightProgram.clearPermutations(); + gHUDFullbrightProgram.addPermutation("IS_HUD", "1"); + success = gHUDFullbrightProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredFullbrightAlphaMaskProgram.mName = "Deferred Fullbright Alpha Masking Shader"; + gDeferredFullbrightAlphaMaskProgram.mFeatures.calculatesAtmospherics = true; + gDeferredFullbrightAlphaMaskProgram.mFeatures.hasGamma = true; + gDeferredFullbrightAlphaMaskProgram.mFeatures.hasAtmospherics = true; + gDeferredFullbrightAlphaMaskProgram.mFeatures.hasSrgb = true; + gDeferredFullbrightAlphaMaskProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; + gDeferredFullbrightAlphaMaskProgram.mShaderFiles.clear(); + gDeferredFullbrightAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightV.glsl", GL_VERTEX_SHADER)); + gDeferredFullbrightAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightF.glsl", GL_FRAGMENT_SHADER)); + gDeferredFullbrightAlphaMaskProgram.clearPermutations(); + gDeferredFullbrightAlphaMaskProgram.addPermutation("HAS_ALPHA_MASK","1"); + gDeferredFullbrightAlphaMaskProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = make_rigged_variant(gDeferredFullbrightAlphaMaskProgram, gDeferredSkinnedFullbrightAlphaMaskProgram); + success = success && gDeferredFullbrightAlphaMaskProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gHUDFullbrightAlphaMaskProgram.mName = "HUD Fullbright Alpha Masking Shader"; + gHUDFullbrightAlphaMaskProgram.mFeatures.calculatesAtmospherics = true; + gHUDFullbrightAlphaMaskProgram.mFeatures.hasGamma = true; + gHUDFullbrightAlphaMaskProgram.mFeatures.hasAtmospherics = true; + gHUDFullbrightAlphaMaskProgram.mFeatures.hasSrgb = true; + gHUDFullbrightAlphaMaskProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; + gHUDFullbrightAlphaMaskProgram.mShaderFiles.clear(); + gHUDFullbrightAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightV.glsl", GL_VERTEX_SHADER)); + gHUDFullbrightAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightF.glsl", GL_FRAGMENT_SHADER)); + gHUDFullbrightAlphaMaskProgram.clearPermutations(); + gHUDFullbrightAlphaMaskProgram.addPermutation("HAS_ALPHA_MASK", "1"); + gHUDFullbrightAlphaMaskProgram.addPermutation("IS_HUD", "1"); + gHUDFullbrightAlphaMaskProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gHUDFullbrightAlphaMaskProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredFullbrightAlphaMaskAlphaProgram.mName = "Deferred Fullbright Alpha Masking Alpha Shader"; + gDeferredFullbrightAlphaMaskAlphaProgram.mFeatures.calculatesAtmospherics = true; + gDeferredFullbrightAlphaMaskAlphaProgram.mFeatures.hasGamma = true; + gDeferredFullbrightAlphaMaskAlphaProgram.mFeatures.hasAtmospherics = true; + gDeferredFullbrightAlphaMaskAlphaProgram.mFeatures.hasSrgb = true; + gDeferredFullbrightAlphaMaskAlphaProgram.mFeatures.isDeferred = true; + gDeferredFullbrightAlphaMaskAlphaProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; + gDeferredFullbrightAlphaMaskAlphaProgram.mShaderFiles.clear(); + gDeferredFullbrightAlphaMaskAlphaProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightV.glsl", GL_VERTEX_SHADER)); + gDeferredFullbrightAlphaMaskAlphaProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightF.glsl", GL_FRAGMENT_SHADER)); + gDeferredFullbrightAlphaMaskAlphaProgram.clearPermutations(); + gDeferredFullbrightAlphaMaskAlphaProgram.addPermutation("HAS_ALPHA_MASK", "1"); + gDeferredFullbrightAlphaMaskAlphaProgram.addPermutation("IS_ALPHA", "1"); + gDeferredFullbrightAlphaMaskAlphaProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = make_rigged_variant(gDeferredFullbrightAlphaMaskAlphaProgram, gDeferredSkinnedFullbrightAlphaMaskAlphaProgram); + success = success && gDeferredFullbrightAlphaMaskAlphaProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gHUDFullbrightAlphaMaskAlphaProgram.mName = "HUD Fullbright Alpha Masking Alpha Shader"; + gHUDFullbrightAlphaMaskAlphaProgram.mFeatures.calculatesAtmospherics = true; + gHUDFullbrightAlphaMaskAlphaProgram.mFeatures.hasGamma = true; + gHUDFullbrightAlphaMaskAlphaProgram.mFeatures.hasAtmospherics = true; + gHUDFullbrightAlphaMaskAlphaProgram.mFeatures.hasSrgb = true; + gHUDFullbrightAlphaMaskAlphaProgram.mFeatures.isDeferred = true; + gHUDFullbrightAlphaMaskAlphaProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; + gHUDFullbrightAlphaMaskAlphaProgram.mShaderFiles.clear(); + gHUDFullbrightAlphaMaskAlphaProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightV.glsl", GL_VERTEX_SHADER)); + gHUDFullbrightAlphaMaskAlphaProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightF.glsl", GL_FRAGMENT_SHADER)); + gHUDFullbrightAlphaMaskAlphaProgram.clearPermutations(); + gHUDFullbrightAlphaMaskAlphaProgram.addPermutation("HAS_ALPHA_MASK", "1"); + gHUDFullbrightAlphaMaskAlphaProgram.addPermutation("IS_ALPHA", "1"); + gHUDFullbrightAlphaMaskAlphaProgram.addPermutation("IS_HUD", "1"); + gHUDFullbrightAlphaMaskAlphaProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = success && gHUDFullbrightAlphaMaskAlphaProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredFullbrightShinyProgram.mName = "Deferred FullbrightShiny Shader"; + gDeferredFullbrightShinyProgram.mFeatures.calculatesAtmospherics = true; + gDeferredFullbrightShinyProgram.mFeatures.hasAtmospherics = true; + gDeferredFullbrightShinyProgram.mFeatures.hasGamma = true; + gDeferredFullbrightShinyProgram.mFeatures.hasSrgb = true; + gDeferredFullbrightShinyProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; + gDeferredFullbrightShinyProgram.mShaderFiles.clear(); + gDeferredFullbrightShinyProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightShinyV.glsl", GL_VERTEX_SHADER)); + gDeferredFullbrightShinyProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightShinyF.glsl", GL_FRAGMENT_SHADER)); + gDeferredFullbrightShinyProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + gDeferredFullbrightShinyProgram.mFeatures.hasReflectionProbes = true; + success = make_rigged_variant(gDeferredFullbrightShinyProgram, gDeferredSkinnedFullbrightShinyProgram); + success = success && gDeferredFullbrightShinyProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gHUDFullbrightShinyProgram.mName = "HUD FullbrightShiny Shader"; + gHUDFullbrightShinyProgram.mFeatures.calculatesAtmospherics = true; + gHUDFullbrightShinyProgram.mFeatures.hasAtmospherics = true; + gHUDFullbrightShinyProgram.mFeatures.hasGamma = true; + gHUDFullbrightShinyProgram.mFeatures.hasSrgb = true; + gHUDFullbrightShinyProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; + gHUDFullbrightShinyProgram.mShaderFiles.clear(); + gHUDFullbrightShinyProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightShinyV.glsl", GL_VERTEX_SHADER)); + gHUDFullbrightShinyProgram.mShaderFiles.push_back(make_pair("deferred/fullbrightShinyF.glsl", GL_FRAGMENT_SHADER)); + gHUDFullbrightShinyProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + gHUDFullbrightShinyProgram.mFeatures.hasReflectionProbes = true; + gHUDFullbrightShinyProgram.clearPermutations(); + gHUDFullbrightShinyProgram.addPermutation("IS_HUD", "1"); + success = gHUDFullbrightShinyProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredEmissiveProgram.mName = "Deferred Emissive Shader"; + gDeferredEmissiveProgram.mFeatures.calculatesAtmospherics = true; + gDeferredEmissiveProgram.mFeatures.hasGamma = true; + gDeferredEmissiveProgram.mFeatures.hasAtmospherics = true; + gDeferredEmissiveProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; + gDeferredEmissiveProgram.mShaderFiles.clear(); + gDeferredEmissiveProgram.mShaderFiles.push_back(make_pair("deferred/emissiveV.glsl", GL_VERTEX_SHADER)); + gDeferredEmissiveProgram.mShaderFiles.push_back(make_pair("deferred/emissiveF.glsl", GL_FRAGMENT_SHADER)); + gDeferredEmissiveProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = make_rigged_variant(gDeferredEmissiveProgram, gDeferredSkinnedEmissiveProgram); + success = success && gDeferredEmissiveProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredSoftenProgram.mName = "Deferred Soften Shader"; + gDeferredSoftenProgram.mShaderFiles.clear(); + gDeferredSoftenProgram.mFeatures.hasSrgb = true; + gDeferredSoftenProgram.mFeatures.calculatesAtmospherics = true; + gDeferredSoftenProgram.mFeatures.hasAtmospherics = true; + gDeferredSoftenProgram.mFeatures.hasGamma = true; + gDeferredSoftenProgram.mFeatures.isDeferred = true; + gDeferredSoftenProgram.mFeatures.hasShadows = use_sun_shadow; + gDeferredSoftenProgram.mFeatures.hasReflectionProbes = mShaderLevel[SHADER_DEFERRED] > 2; + + gDeferredSoftenProgram.clearPermutations(); + gDeferredSoftenProgram.mShaderFiles.push_back(make_pair("deferred/softenLightV.glsl", GL_VERTEX_SHADER)); + gDeferredSoftenProgram.mShaderFiles.push_back(make_pair("deferred/softenLightF.glsl", GL_FRAGMENT_SHADER)); + + gDeferredSoftenProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + + if (use_sun_shadow) + { + gDeferredSoftenProgram.addPermutation("HAS_SUN_SHADOW", "1"); + } + + if (gSavedSettings.getBOOL("RenderDeferredSSAO")) + { //if using SSAO, take screen space light map into account as if shadows are enabled + gDeferredSoftenProgram.mShaderLevel = llmax(gDeferredSoftenProgram.mShaderLevel, 2); + gDeferredSoftenProgram.addPermutation("HAS_SSAO", "1"); + } + + success = gDeferredSoftenProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gHazeProgram.mName = "Haze Shader"; + gHazeProgram.mShaderFiles.clear(); + gHazeProgram.mFeatures.hasSrgb = true; + gHazeProgram.mFeatures.calculatesAtmospherics = true; + gHazeProgram.mFeatures.hasAtmospherics = true; + gHazeProgram.mFeatures.hasGamma = true; + gHazeProgram.mFeatures.isDeferred = true; + gHazeProgram.mFeatures.hasShadows = use_sun_shadow; + gHazeProgram.mFeatures.hasReflectionProbes = mShaderLevel[SHADER_DEFERRED] > 2; + + gHazeProgram.clearPermutations(); + gHazeProgram.mShaderFiles.push_back(make_pair("deferred/softenLightV.glsl", GL_VERTEX_SHADER)); + gHazeProgram.mShaderFiles.push_back(make_pair("deferred/hazeF.glsl", GL_FRAGMENT_SHADER)); + + gHazeProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + + success = gHazeProgram.createShader(NULL, NULL); + llassert(success); + } + + + if (success) + { + gHazeWaterProgram.mName = "Water Haze Shader"; + gHazeWaterProgram.mShaderFiles.clear(); + gHazeWaterProgram.mShaderGroup = LLGLSLShader::SG_WATER; + gHazeWaterProgram.mFeatures.hasSrgb = true; + gHazeWaterProgram.mFeatures.calculatesAtmospherics = true; + gHazeWaterProgram.mFeatures.hasAtmospherics = true; + gHazeWaterProgram.mFeatures.hasGamma = true; + gHazeWaterProgram.mFeatures.isDeferred = true; + gHazeWaterProgram.mFeatures.hasShadows = use_sun_shadow; + gHazeWaterProgram.mFeatures.hasReflectionProbes = mShaderLevel[SHADER_DEFERRED] > 2; + + gHazeWaterProgram.clearPermutations(); + gHazeWaterProgram.mShaderFiles.push_back(make_pair("deferred/waterHazeV.glsl", GL_VERTEX_SHADER)); + gHazeWaterProgram.mShaderFiles.push_back(make_pair("deferred/waterHazeF.glsl", GL_FRAGMENT_SHADER)); + + gHazeWaterProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + + success = gHazeWaterProgram.createShader(NULL, NULL); + llassert(success); + } + + + if (success) + { + gDeferredShadowProgram.mName = "Deferred Shadow Shader"; + gDeferredShadowProgram.mShaderFiles.clear(); + gDeferredShadowProgram.mShaderFiles.push_back(make_pair("deferred/shadowV.glsl", GL_VERTEX_SHADER)); + gDeferredShadowProgram.mShaderFiles.push_back(make_pair("deferred/shadowF.glsl", GL_FRAGMENT_SHADER)); + gDeferredShadowProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + gDeferredShadowProgram.mRiggedVariant = &gDeferredSkinnedShadowProgram; + success = gDeferredShadowProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredSkinnedShadowProgram.mName = "Deferred Skinned Shadow Shader"; + gDeferredSkinnedShadowProgram.mFeatures.isDeferred = true; + gDeferredSkinnedShadowProgram.mFeatures.hasShadows = true; + gDeferredSkinnedShadowProgram.mFeatures.hasObjectSkinning = true; + gDeferredSkinnedShadowProgram.mShaderFiles.clear(); + gDeferredSkinnedShadowProgram.mShaderFiles.push_back(make_pair("deferred/shadowSkinnedV.glsl", GL_VERTEX_SHADER)); + gDeferredSkinnedShadowProgram.mShaderFiles.push_back(make_pair("deferred/shadowF.glsl", GL_FRAGMENT_SHADER)); + gDeferredSkinnedShadowProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + // gDeferredSkinnedShadowProgram.addPermutation("DEPTH_CLAMP", "1"); // disable depth clamp for now + success = gDeferredSkinnedShadowProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredShadowCubeProgram.mName = "Deferred Shadow Cube Shader"; + gDeferredShadowCubeProgram.mFeatures.isDeferred = true; + gDeferredShadowCubeProgram.mFeatures.hasShadows = true; + gDeferredShadowCubeProgram.mShaderFiles.clear(); + gDeferredShadowCubeProgram.mShaderFiles.push_back(make_pair("deferred/shadowCubeV.glsl", GL_VERTEX_SHADER)); + gDeferredShadowCubeProgram.mShaderFiles.push_back(make_pair("deferred/shadowF.glsl", GL_FRAGMENT_SHADER)); + // gDeferredShadowCubeProgram.addPermutation("DEPTH_CLAMP", "1"); + gDeferredShadowCubeProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gDeferredShadowCubeProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredShadowFullbrightAlphaMaskProgram.mName = "Deferred Shadow Fullbright Alpha Mask Shader"; + gDeferredShadowFullbrightAlphaMaskProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; + + gDeferredShadowFullbrightAlphaMaskProgram.mShaderFiles.clear(); + gDeferredShadowFullbrightAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/shadowAlphaMaskV.glsl", GL_VERTEX_SHADER)); + gDeferredShadowFullbrightAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/shadowAlphaMaskF.glsl", GL_FRAGMENT_SHADER)); + + gDeferredShadowFullbrightAlphaMaskProgram.clearPermutations(); + gDeferredShadowFullbrightAlphaMaskProgram.addPermutation("DEPTH_CLAMP", "1"); + gDeferredShadowFullbrightAlphaMaskProgram.addPermutation("IS_FULLBRIGHT", "1"); + gDeferredShadowFullbrightAlphaMaskProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = make_rigged_variant(gDeferredShadowFullbrightAlphaMaskProgram, gDeferredSkinnedShadowFullbrightAlphaMaskProgram); + success = success && gDeferredShadowFullbrightAlphaMaskProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredShadowAlphaMaskProgram.mName = "Deferred Shadow Alpha Mask Shader"; + gDeferredShadowAlphaMaskProgram.mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels; + + gDeferredShadowAlphaMaskProgram.mShaderFiles.clear(); + gDeferredShadowAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/shadowAlphaMaskV.glsl", GL_VERTEX_SHADER)); + gDeferredShadowAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/shadowAlphaMaskF.glsl", GL_FRAGMENT_SHADER)); + gDeferredShadowAlphaMaskProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = make_rigged_variant(gDeferredShadowAlphaMaskProgram, gDeferredSkinnedShadowAlphaMaskProgram); + success = success && gDeferredShadowAlphaMaskProgram.createShader(NULL, NULL); + llassert(success); + } + + + if (success) + { + gDeferredShadowGLTFAlphaMaskProgram.mName = "Deferred GLTF Shadow Alpha Mask Shader"; + gDeferredShadowGLTFAlphaMaskProgram.mShaderFiles.clear(); + gDeferredShadowGLTFAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/pbrShadowAlphaMaskV.glsl", GL_VERTEX_SHADER)); + gDeferredShadowGLTFAlphaMaskProgram.mShaderFiles.push_back(make_pair("deferred/pbrShadowAlphaMaskF.glsl", GL_FRAGMENT_SHADER)); + gDeferredShadowGLTFAlphaMaskProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + gDeferredShadowGLTFAlphaMaskProgram.clearPermutations(); + success = make_rigged_variant(gDeferredShadowGLTFAlphaMaskProgram, gDeferredSkinnedShadowGLTFAlphaMaskProgram); + success = success && gDeferredShadowGLTFAlphaMaskProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredShadowGLTFAlphaBlendProgram.mName = "Deferred GLTF Shadow Alpha Blend Shader"; + gDeferredShadowGLTFAlphaBlendProgram.mShaderFiles.clear(); + gDeferredShadowGLTFAlphaBlendProgram.mShaderFiles.push_back(make_pair("deferred/pbrShadowAlphaMaskV.glsl", GL_VERTEX_SHADER)); + gDeferredShadowGLTFAlphaBlendProgram.mShaderFiles.push_back(make_pair("deferred/pbrShadowAlphaBlendF.glsl", GL_FRAGMENT_SHADER)); + gDeferredShadowGLTFAlphaBlendProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + gDeferredShadowGLTFAlphaBlendProgram.clearPermutations(); + success = make_rigged_variant(gDeferredShadowGLTFAlphaBlendProgram, gDeferredSkinnedShadowGLTFAlphaBlendProgram); + success = success && gDeferredShadowGLTFAlphaBlendProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredAvatarShadowProgram.mName = "Deferred Avatar Shadow Shader"; + gDeferredAvatarShadowProgram.mFeatures.hasSkinning = true; + + gDeferredAvatarShadowProgram.mShaderFiles.clear(); + gDeferredAvatarShadowProgram.mShaderFiles.push_back(make_pair("deferred/avatarShadowV.glsl", GL_VERTEX_SHADER)); + gDeferredAvatarShadowProgram.mShaderFiles.push_back(make_pair("deferred/avatarShadowF.glsl", GL_FRAGMENT_SHADER)); + gDeferredAvatarShadowProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gDeferredAvatarShadowProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredAvatarAlphaShadowProgram.mName = "Deferred Avatar Alpha Shadow Shader"; + gDeferredAvatarAlphaShadowProgram.mFeatures.hasSkinning = true; + gDeferredAvatarAlphaShadowProgram.mShaderFiles.clear(); + gDeferredAvatarAlphaShadowProgram.mShaderFiles.push_back(make_pair("deferred/avatarAlphaShadowV.glsl", GL_VERTEX_SHADER)); + gDeferredAvatarAlphaShadowProgram.mShaderFiles.push_back(make_pair("deferred/avatarAlphaShadowF.glsl", GL_FRAGMENT_SHADER)); + gDeferredAvatarAlphaShadowProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gDeferredAvatarAlphaShadowProgram.createShader(NULL, NULL); + llassert(success); + } + if (success) + { + gDeferredAvatarAlphaMaskShadowProgram.mName = "Deferred Avatar Alpha Mask Shadow Shader"; + gDeferredAvatarAlphaMaskShadowProgram.mFeatures.hasSkinning = true; + gDeferredAvatarAlphaMaskShadowProgram.mShaderFiles.clear(); + gDeferredAvatarAlphaMaskShadowProgram.mShaderFiles.push_back(make_pair("deferred/avatarAlphaShadowV.glsl", GL_VERTEX_SHADER)); + gDeferredAvatarAlphaMaskShadowProgram.mShaderFiles.push_back(make_pair("deferred/avatarAlphaMaskShadowF.glsl", GL_FRAGMENT_SHADER)); + gDeferredAvatarAlphaMaskShadowProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gDeferredAvatarAlphaMaskShadowProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredTerrainProgram.mName = "Deferred Terrain Shader"; + gDeferredTerrainProgram.mFeatures.encodesNormal = true; + gDeferredTerrainProgram.mFeatures.hasSrgb = true; + gDeferredTerrainProgram.mFeatures.calculatesLighting = false; + gDeferredTerrainProgram.mFeatures.hasLighting = false; + gDeferredTerrainProgram.mFeatures.isAlphaLighting = true; + gDeferredTerrainProgram.mFeatures.disableTextureIndex = true; //hack to disable auto-setup of texture channels + gDeferredTerrainProgram.mFeatures.calculatesAtmospherics = true; + gDeferredTerrainProgram.mFeatures.hasAtmospherics = true; + gDeferredTerrainProgram.mFeatures.hasGamma = true; + + gDeferredTerrainProgram.mShaderFiles.clear(); + gDeferredTerrainProgram.mShaderFiles.push_back(make_pair("deferred/terrainV.glsl", GL_VERTEX_SHADER)); + gDeferredTerrainProgram.mShaderFiles.push_back(make_pair("deferred/terrainF.glsl", GL_FRAGMENT_SHADER)); + gDeferredTerrainProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gDeferredTerrainProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredAvatarProgram.mName = "Deferred Avatar Shader"; + gDeferredAvatarProgram.mFeatures.hasSkinning = true; + gDeferredAvatarProgram.mFeatures.encodesNormal = true; + gDeferredAvatarProgram.mShaderFiles.clear(); + gDeferredAvatarProgram.mShaderFiles.push_back(make_pair("deferred/avatarV.glsl", GL_VERTEX_SHADER)); + gDeferredAvatarProgram.mShaderFiles.push_back(make_pair("deferred/avatarF.glsl", GL_FRAGMENT_SHADER)); + gDeferredAvatarProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gDeferredAvatarProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredAvatarAlphaProgram.mName = "Deferred Avatar Alpha Shader"; + gDeferredAvatarAlphaProgram.mFeatures.hasSkinning = true; + gDeferredAvatarAlphaProgram.mFeatures.calculatesLighting = false; + gDeferredAvatarAlphaProgram.mFeatures.hasLighting = false; + gDeferredAvatarAlphaProgram.mFeatures.isAlphaLighting = true; + gDeferredAvatarAlphaProgram.mFeatures.disableTextureIndex = true; + gDeferredAvatarAlphaProgram.mFeatures.hasSrgb = true; + gDeferredAvatarAlphaProgram.mFeatures.encodesNormal = true; + gDeferredAvatarAlphaProgram.mFeatures.calculatesAtmospherics = true; + gDeferredAvatarAlphaProgram.mFeatures.hasAtmospherics = true; + gDeferredAvatarAlphaProgram.mFeatures.hasGamma = true; + gDeferredAvatarAlphaProgram.mFeatures.isDeferred = true; + gDeferredAvatarAlphaProgram.mFeatures.hasShadows = true; + gDeferredAvatarAlphaProgram.mFeatures.hasReflectionProbes = true; + + gDeferredAvatarAlphaProgram.mShaderFiles.clear(); + gDeferredAvatarAlphaProgram.mShaderFiles.push_back(make_pair("deferred/alphaV.glsl", GL_VERTEX_SHADER)); + gDeferredAvatarAlphaProgram.mShaderFiles.push_back(make_pair("deferred/alphaF.glsl", GL_FRAGMENT_SHADER)); + + gDeferredAvatarAlphaProgram.clearPermutations(); + gDeferredAvatarAlphaProgram.addPermutation("USE_DIFFUSE_TEX", "1"); + gDeferredAvatarAlphaProgram.addPermutation("IS_AVATAR_SKIN", "1"); + if (use_sun_shadow) + { + gDeferredAvatarAlphaProgram.addPermutation("HAS_SUN_SHADOW", "1"); + } + + gDeferredAvatarAlphaProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + + success = gDeferredAvatarAlphaProgram.createShader(NULL, NULL); + llassert(success); + + gDeferredAvatarAlphaProgram.mFeatures.calculatesLighting = true; + gDeferredAvatarAlphaProgram.mFeatures.hasLighting = true; + } + + if (success) + { + gExposureProgram.mName = "Exposure"; + gExposureProgram.mFeatures.hasSrgb = true; + gExposureProgram.mFeatures.isDeferred = true; + gExposureProgram.mShaderFiles.clear(); + gExposureProgram.clearPermutations(); + gExposureProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER)); + gExposureProgram.mShaderFiles.push_back(make_pair("deferred/exposureF.glsl", GL_FRAGMENT_SHADER)); + gExposureProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gExposureProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gLuminanceProgram.mName = "Luminance"; + gLuminanceProgram.mShaderFiles.clear(); + gLuminanceProgram.clearPermutations(); + gLuminanceProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER)); + gLuminanceProgram.mShaderFiles.push_back(make_pair("deferred/luminanceF.glsl", GL_FRAGMENT_SHADER)); + gLuminanceProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gLuminanceProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredPostGammaCorrectProgram.mName = "Deferred Gamma Correction Post Process"; + gDeferredPostGammaCorrectProgram.mFeatures.hasSrgb = true; + gDeferredPostGammaCorrectProgram.mFeatures.isDeferred = true; + gDeferredPostGammaCorrectProgram.mShaderFiles.clear(); + gDeferredPostGammaCorrectProgram.clearPermutations(); + gDeferredPostGammaCorrectProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER)); + gDeferredPostGammaCorrectProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredGammaCorrect.glsl", GL_FRAGMENT_SHADER)); + gDeferredPostGammaCorrectProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gDeferredPostGammaCorrectProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gNoPostGammaCorrectProgram.mName = "No Post Gamma Correction Post Process"; + gNoPostGammaCorrectProgram.mFeatures.hasSrgb = true; + gNoPostGammaCorrectProgram.mFeatures.isDeferred = true; + gNoPostGammaCorrectProgram.mShaderFiles.clear(); + gNoPostGammaCorrectProgram.clearPermutations(); + gNoPostGammaCorrectProgram.addPermutation("NO_POST", "1"); + gNoPostGammaCorrectProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER)); + gNoPostGammaCorrectProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredGammaCorrect.glsl", GL_FRAGMENT_SHADER)); + gNoPostGammaCorrectProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gNoPostGammaCorrectProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gLegacyPostGammaCorrectProgram.mName = "Legacy Gamma Correction Post Process"; + gLegacyPostGammaCorrectProgram.mFeatures.hasSrgb = true; + gLegacyPostGammaCorrectProgram.mFeatures.isDeferred = true; + gLegacyPostGammaCorrectProgram.mShaderFiles.clear(); + gLegacyPostGammaCorrectProgram.clearPermutations(); + gLegacyPostGammaCorrectProgram.addPermutation("LEGACY_GAMMA", "1"); + gLegacyPostGammaCorrectProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER)); + gLegacyPostGammaCorrectProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredGammaCorrect.glsl", GL_FRAGMENT_SHADER)); + gLegacyPostGammaCorrectProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gLegacyPostGammaCorrectProgram.createShader(NULL, NULL); + llassert(success); + } + + + if (success && gGLManager.mGLVersion > 3.9f) + { + gFXAAProgram.mName = "FXAA Shader"; + gFXAAProgram.mFeatures.isDeferred = true; + gFXAAProgram.mShaderFiles.clear(); + gFXAAProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredV.glsl", GL_VERTEX_SHADER)); + gFXAAProgram.mShaderFiles.push_back(make_pair("deferred/fxaaF.glsl", GL_FRAGMENT_SHADER)); + gFXAAProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gFXAAProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredPostProgram.mName = "Deferred Post Shader"; + gDeferredPostProgram.mFeatures.isDeferred = true; + gDeferredPostProgram.mShaderFiles.clear(); + gDeferredPostProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER)); + gDeferredPostProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredF.glsl", GL_FRAGMENT_SHADER)); + gDeferredPostProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gDeferredPostProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredCoFProgram.mName = "Deferred CoF Shader"; + gDeferredCoFProgram.mShaderFiles.clear(); + gDeferredCoFProgram.mFeatures.isDeferred = true; + gDeferredCoFProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER)); + gDeferredCoFProgram.mShaderFiles.push_back(make_pair("deferred/cofF.glsl", GL_FRAGMENT_SHADER)); + gDeferredCoFProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gDeferredCoFProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredDoFCombineProgram.mName = "Deferred DoFCombine Shader"; + gDeferredDoFCombineProgram.mFeatures.isDeferred = true; + gDeferredDoFCombineProgram.mShaderFiles.clear(); + gDeferredDoFCombineProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER)); + gDeferredDoFCombineProgram.mShaderFiles.push_back(make_pair("deferred/dofCombineF.glsl", GL_FRAGMENT_SHADER)); + gDeferredDoFCombineProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gDeferredDoFCombineProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredPostNoDoFProgram.mName = "Deferred Post NoDoF Shader"; + gDeferredPostNoDoFProgram.mFeatures.isDeferred = true; + gDeferredPostNoDoFProgram.mShaderFiles.clear(); + gDeferredPostNoDoFProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER)); + gDeferredPostNoDoFProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoDoFF.glsl", GL_FRAGMENT_SHADER)); + gDeferredPostNoDoFProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gDeferredPostNoDoFProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredWLSkyProgram.mName = "Deferred Windlight Sky Shader"; + gDeferredWLSkyProgram.mShaderFiles.clear(); + gDeferredWLSkyProgram.mFeatures.calculatesAtmospherics = true; + gDeferredWLSkyProgram.mFeatures.hasAtmospherics = true; + gDeferredWLSkyProgram.mFeatures.hasGamma = true; + gDeferredWLSkyProgram.mFeatures.hasSrgb = true; + + gDeferredWLSkyProgram.mShaderFiles.push_back(make_pair("deferred/skyV.glsl", GL_VERTEX_SHADER)); + gDeferredWLSkyProgram.mShaderFiles.push_back(make_pair("deferred/skyF.glsl", GL_FRAGMENT_SHADER)); + gDeferredWLSkyProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + gDeferredWLSkyProgram.mShaderGroup = LLGLSLShader::SG_SKY; + + success = gDeferredWLSkyProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredWLCloudProgram.mName = "Deferred Windlight Cloud Program"; + gDeferredWLCloudProgram.mShaderFiles.clear(); + gDeferredWLCloudProgram.mFeatures.calculatesAtmospherics = true; + gDeferredWLCloudProgram.mFeatures.hasAtmospherics = true; + gDeferredWLCloudProgram.mFeatures.hasGamma = true; + gDeferredWLCloudProgram.mFeatures.hasSrgb = true; + + gDeferredWLCloudProgram.mShaderFiles.push_back(make_pair("deferred/cloudsV.glsl", GL_VERTEX_SHADER)); + gDeferredWLCloudProgram.mShaderFiles.push_back(make_pair("deferred/cloudsF.glsl", GL_FRAGMENT_SHADER)); + gDeferredWLCloudProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + gDeferredWLCloudProgram.mShaderGroup = LLGLSLShader::SG_SKY; + gDeferredWLCloudProgram.addConstant( LLGLSLShader::SHADER_CONST_CLOUD_MOON_DEPTH ); // SL-14113 + success = gDeferredWLCloudProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredWLSunProgram.mName = "Deferred Windlight Sun Program"; + gDeferredWLSunProgram.mFeatures.calculatesAtmospherics = true; + gDeferredWLSunProgram.mFeatures.hasAtmospherics = true; + gDeferredWLSunProgram.mFeatures.hasGamma = true; + gDeferredWLSunProgram.mFeatures.hasAtmospherics = true; + gDeferredWLSunProgram.mFeatures.disableTextureIndex = true; + gDeferredWLSunProgram.mFeatures.hasSrgb = true; + gDeferredWLSunProgram.mShaderFiles.clear(); + gDeferredWLSunProgram.mShaderFiles.push_back(make_pair("deferred/sunDiscV.glsl", GL_VERTEX_SHADER)); + gDeferredWLSunProgram.mShaderFiles.push_back(make_pair("deferred/sunDiscF.glsl", GL_FRAGMENT_SHADER)); + gDeferredWLSunProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + gDeferredWLSunProgram.mShaderGroup = LLGLSLShader::SG_SKY; + success = gDeferredWLSunProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredWLMoonProgram.mName = "Deferred Windlight Moon Program"; + gDeferredWLMoonProgram.mFeatures.calculatesAtmospherics = true; + gDeferredWLMoonProgram.mFeatures.hasAtmospherics = true; + gDeferredWLMoonProgram.mFeatures.hasGamma = true; + gDeferredWLMoonProgram.mFeatures.hasAtmospherics = true; + gDeferredWLMoonProgram.mFeatures.hasSrgb = true; + gDeferredWLMoonProgram.mFeatures.disableTextureIndex = true; + + gDeferredWLMoonProgram.mShaderFiles.clear(); + gDeferredWLMoonProgram.mShaderFiles.push_back(make_pair("deferred/moonV.glsl", GL_VERTEX_SHADER)); + gDeferredWLMoonProgram.mShaderFiles.push_back(make_pair("deferred/moonF.glsl", GL_FRAGMENT_SHADER)); + gDeferredWLMoonProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + gDeferredWLMoonProgram.mShaderGroup = LLGLSLShader::SG_SKY; + gDeferredWLMoonProgram.addConstant( LLGLSLShader::SHADER_CONST_CLOUD_MOON_DEPTH ); // SL-14113 + success = gDeferredWLMoonProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gDeferredStarProgram.mName = "Deferred Star Program"; + gDeferredStarProgram.mShaderFiles.clear(); + gDeferredStarProgram.mShaderFiles.push_back(make_pair("deferred/starsV.glsl", GL_VERTEX_SHADER)); + gDeferredStarProgram.mShaderFiles.push_back(make_pair("deferred/starsF.glsl", GL_FRAGMENT_SHADER)); + gDeferredStarProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + gDeferredStarProgram.mShaderGroup = LLGLSLShader::SG_SKY; + gDeferredStarProgram.addConstant( LLGLSLShader::SHADER_CONST_STAR_DEPTH ); // SL-14113 + success = gDeferredStarProgram.createShader(NULL, NULL); + llassert(success); + } + + if (success) + { + gNormalMapGenProgram.mName = "Normal Map Generation Program"; + gNormalMapGenProgram.mShaderFiles.clear(); + gNormalMapGenProgram.mShaderFiles.push_back(make_pair("deferred/normgenV.glsl", GL_VERTEX_SHADER)); + gNormalMapGenProgram.mShaderFiles.push_back(make_pair("deferred/normgenF.glsl", GL_FRAGMENT_SHADER)); + gNormalMapGenProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + gNormalMapGenProgram.mShaderGroup = LLGLSLShader::SG_SKY; + success = gNormalMapGenProgram.createShader(NULL, NULL); + } + + if (success) + { + gDeferredGenBrdfLutProgram.mName = "Brdf Gen Shader"; + gDeferredGenBrdfLutProgram.mShaderFiles.clear(); + gDeferredGenBrdfLutProgram.mShaderFiles.push_back(make_pair("deferred/genbrdflutV.glsl", GL_VERTEX_SHADER)); + gDeferredGenBrdfLutProgram.mShaderFiles.push_back(make_pair("deferred/genbrdflutF.glsl", GL_FRAGMENT_SHADER)); + gDeferredGenBrdfLutProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gDeferredGenBrdfLutProgram.createShader(NULL, NULL); + } + + if (success) { + gPostScreenSpaceReflectionProgram.mName = "Screen Space Reflection Post"; + gPostScreenSpaceReflectionProgram.mShaderFiles.clear(); + gPostScreenSpaceReflectionProgram.mShaderFiles.push_back(make_pair("deferred/screenSpaceReflPostV.glsl", GL_VERTEX_SHADER)); + gPostScreenSpaceReflectionProgram.mShaderFiles.push_back(make_pair("deferred/screenSpaceReflPostF.glsl", GL_FRAGMENT_SHADER)); + gPostScreenSpaceReflectionProgram.mFeatures.hasScreenSpaceReflections = true; + gPostScreenSpaceReflectionProgram.mFeatures.isDeferred = true; + gPostScreenSpaceReflectionProgram.mShaderLevel = 3; + success = gPostScreenSpaceReflectionProgram.createShader(NULL, NULL); + } + + if (success) { + gDeferredBufferVisualProgram.mName = "Deferred Buffer Visualization Shader"; + gDeferredBufferVisualProgram.mShaderFiles.clear(); + gDeferredBufferVisualProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredNoTCV.glsl", GL_VERTEX_SHADER)); + gDeferredBufferVisualProgram.mShaderFiles.push_back(make_pair("deferred/postDeferredVisualizeBuffers.glsl", GL_FRAGMENT_SHADER)); + gDeferredBufferVisualProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + success = gDeferredBufferVisualProgram.createShader(NULL, NULL); + } + + return success; +} + +bool LLViewerShaderMgr::loadShadersObject() +{ + LL_PROFILE_ZONE_SCOPED; + bool success = true; + + if (success) + { + gObjectBumpProgram.mName = "Bump Shader"; + gObjectBumpProgram.mFeatures.encodesNormal = true; + gObjectBumpProgram.mShaderFiles.clear(); + gObjectBumpProgram.mShaderFiles.push_back(make_pair("objects/bumpV.glsl", GL_VERTEX_SHADER)); + gObjectBumpProgram.mShaderFiles.push_back(make_pair("objects/bumpF.glsl", GL_FRAGMENT_SHADER)); + gObjectBumpProgram.mShaderLevel = mShaderLevel[SHADER_OBJECT]; + success = make_rigged_variant(gObjectBumpProgram, gSkinnedObjectBumpProgram); + success = success && gObjectBumpProgram.createShader(NULL, NULL); + if (success) + { //lldrawpoolbump assumes "texture0" has channel 0 and "texture1" has channel 1 + LLGLSLShader* shader[] = { &gObjectBumpProgram, &gSkinnedObjectBumpProgram }; + for (int i = 0; i < 2; ++i) + { + shader[i]->bind(); + shader[i]->uniform1i(sTexture0, 0); + shader[i]->uniform1i(sTexture1, 1); + shader[i]->unbind(); + } + } + } + + if (success) + { + gObjectAlphaMaskNoColorProgram.mName = "No color alpha mask Shader"; + gObjectAlphaMaskNoColorProgram.mFeatures.calculatesLighting = true; + gObjectAlphaMaskNoColorProgram.mFeatures.calculatesAtmospherics = true; + gObjectAlphaMaskNoColorProgram.mFeatures.hasGamma = true; + gObjectAlphaMaskNoColorProgram.mFeatures.hasAtmospherics = true; + gObjectAlphaMaskNoColorProgram.mFeatures.hasLighting = true; + gObjectAlphaMaskNoColorProgram.mFeatures.disableTextureIndex = true; + gObjectAlphaMaskNoColorProgram.mFeatures.hasAlphaMask = true; + gObjectAlphaMaskNoColorProgram.mShaderFiles.clear(); + gObjectAlphaMaskNoColorProgram.mShaderFiles.push_back(make_pair("objects/simpleNoColorV.glsl", GL_VERTEX_SHADER)); + gObjectAlphaMaskNoColorProgram.mShaderFiles.push_back(make_pair("objects/simpleF.glsl", GL_FRAGMENT_SHADER)); + gObjectAlphaMaskNoColorProgram.mShaderLevel = mShaderLevel[SHADER_OBJECT]; + success = gObjectAlphaMaskNoColorProgram.createShader(NULL, NULL); + } + + if (success) + { + gImpostorProgram.mName = "Impostor Shader"; + gImpostorProgram.mFeatures.disableTextureIndex = true; + gImpostorProgram.mFeatures.hasSrgb = true; + gImpostorProgram.mShaderFiles.clear(); + gImpostorProgram.mShaderFiles.push_back(make_pair("objects/impostorV.glsl", GL_VERTEX_SHADER)); + gImpostorProgram.mShaderFiles.push_back(make_pair("objects/impostorF.glsl", GL_FRAGMENT_SHADER)); + gImpostorProgram.mShaderLevel = mShaderLevel[SHADER_OBJECT]; + success = gImpostorProgram.createShader(NULL, NULL); + } + + if (success) + { + gObjectPreviewProgram.mName = "Object Preview Shader"; + gObjectPreviewProgram.mFeatures.disableTextureIndex = true; + gObjectPreviewProgram.mShaderFiles.clear(); + gObjectPreviewProgram.mShaderFiles.push_back(make_pair("objects/previewV.glsl", GL_VERTEX_SHADER)); + gObjectPreviewProgram.mShaderFiles.push_back(make_pair("objects/previewF.glsl", GL_FRAGMENT_SHADER)); + gObjectPreviewProgram.mShaderLevel = mShaderLevel[SHADER_OBJECT]; + success = make_rigged_variant(gObjectPreviewProgram, gSkinnedObjectPreviewProgram); + success = gObjectPreviewProgram.createShader(NULL, NULL); + gObjectPreviewProgram.mFeatures.hasLighting = true; + gSkinnedObjectPreviewProgram.mFeatures.hasLighting = true; + } + + if (success) + { + gPhysicsPreviewProgram.mName = "Preview Physics Shader"; + gPhysicsPreviewProgram.mFeatures.calculatesLighting = false; + gPhysicsPreviewProgram.mFeatures.calculatesAtmospherics = false; + gPhysicsPreviewProgram.mFeatures.hasGamma = false; + gPhysicsPreviewProgram.mFeatures.hasAtmospherics = false; + gPhysicsPreviewProgram.mFeatures.hasLighting = false; + gPhysicsPreviewProgram.mFeatures.mIndexedTextureChannels = 0; + gPhysicsPreviewProgram.mFeatures.disableTextureIndex = true; + gPhysicsPreviewProgram.mShaderFiles.clear(); + gPhysicsPreviewProgram.mShaderFiles.push_back(make_pair("objects/previewPhysicsV.glsl", GL_VERTEX_SHADER)); + gPhysicsPreviewProgram.mShaderFiles.push_back(make_pair("objects/previewPhysicsF.glsl", GL_FRAGMENT_SHADER)); + gPhysicsPreviewProgram.mShaderLevel = mShaderLevel[SHADER_OBJECT]; + success = gPhysicsPreviewProgram.createShader(NULL, NULL); + gPhysicsPreviewProgram.mFeatures.hasLighting = false; + } + + if (!success) + { + mShaderLevel[SHADER_OBJECT] = 0; + return false; + } + + return true; +} + +bool LLViewerShaderMgr::loadShadersAvatar() +{ + LL_PROFILE_ZONE_SCOPED; +#if 1 // DEPRECATED -- forward rendering is deprecated + bool success = true; + + if (mShaderLevel[SHADER_AVATAR] == 0) + { + gAvatarProgram.unload(); + gAvatarEyeballProgram.unload(); + return true; + } + + if (success) + { + gAvatarProgram.mName = "Avatar Shader"; + gAvatarProgram.mFeatures.hasSkinning = true; + gAvatarProgram.mFeatures.calculatesAtmospherics = true; + gAvatarProgram.mFeatures.calculatesLighting = true; + gAvatarProgram.mFeatures.hasGamma = true; + gAvatarProgram.mFeatures.hasAtmospherics = true; + gAvatarProgram.mFeatures.hasLighting = true; + gAvatarProgram.mFeatures.hasAlphaMask = true; + gAvatarProgram.mFeatures.disableTextureIndex = true; + gAvatarProgram.mShaderFiles.clear(); + gAvatarProgram.mShaderFiles.push_back(make_pair("avatar/avatarV.glsl", GL_VERTEX_SHADER)); + gAvatarProgram.mShaderFiles.push_back(make_pair("avatar/avatarF.glsl", GL_FRAGMENT_SHADER)); + gAvatarProgram.mShaderLevel = mShaderLevel[SHADER_AVATAR]; + success = gAvatarProgram.createShader(NULL, NULL); + + /// Keep track of avatar levels + if (gAvatarProgram.mShaderLevel != mShaderLevel[SHADER_AVATAR]) + { + mMaxAvatarShaderLevel = mShaderLevel[SHADER_AVATAR] = gAvatarProgram.mShaderLevel; + } + } + + if (success) + { + gAvatarEyeballProgram.mName = "Avatar Eyeball Program"; + gAvatarEyeballProgram.mFeatures.calculatesLighting = true; + gAvatarEyeballProgram.mFeatures.isSpecular = true; + gAvatarEyeballProgram.mFeatures.calculatesAtmospherics = true; + gAvatarEyeballProgram.mFeatures.hasGamma = true; + gAvatarEyeballProgram.mFeatures.hasAtmospherics = true; + gAvatarEyeballProgram.mFeatures.hasLighting = true; + gAvatarEyeballProgram.mFeatures.hasAlphaMask = true; + gAvatarEyeballProgram.mFeatures.disableTextureIndex = true; + gAvatarEyeballProgram.mShaderFiles.clear(); + gAvatarEyeballProgram.mShaderFiles.push_back(make_pair("avatar/eyeballV.glsl", GL_VERTEX_SHADER)); + gAvatarEyeballProgram.mShaderFiles.push_back(make_pair("avatar/eyeballF.glsl", GL_FRAGMENT_SHADER)); + gAvatarEyeballProgram.mShaderLevel = mShaderLevel[SHADER_AVATAR]; + success = gAvatarEyeballProgram.createShader(NULL, NULL); + } + + if( !success ) + { + mShaderLevel[SHADER_AVATAR] = 0; + mMaxAvatarShaderLevel = 0; + return false; + } +#endif + return true; +} + +bool LLViewerShaderMgr::loadShadersInterface() +{ + LL_PROFILE_ZONE_SCOPED; + bool success = true; + + if (success) + { + gHighlightProgram.mName = "Highlight Shader"; + gHighlightProgram.mShaderFiles.clear(); + gHighlightProgram.mShaderFiles.push_back(make_pair("interface/highlightV.glsl", GL_VERTEX_SHADER)); + gHighlightProgram.mShaderFiles.push_back(make_pair("interface/highlightF.glsl", GL_FRAGMENT_SHADER)); + gHighlightProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = make_rigged_variant(gHighlightProgram, gSkinnedHighlightProgram); + success = success && gHighlightProgram.createShader(NULL, NULL); + } + + if (success) + { + gHighlightNormalProgram.mName = "Highlight Normals Shader"; + gHighlightNormalProgram.mShaderFiles.clear(); + gHighlightNormalProgram.mShaderFiles.push_back(make_pair("interface/highlightNormV.glsl", GL_VERTEX_SHADER)); + gHighlightNormalProgram.mShaderFiles.push_back(make_pair("interface/highlightF.glsl", GL_FRAGMENT_SHADER)); + gHighlightNormalProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = gHighlightNormalProgram.createShader(NULL, NULL); + } + + if (success) + { + gHighlightSpecularProgram.mName = "Highlight Spec Shader"; + gHighlightSpecularProgram.mShaderFiles.clear(); + gHighlightSpecularProgram.mShaderFiles.push_back(make_pair("interface/highlightSpecV.glsl", GL_VERTEX_SHADER)); + gHighlightSpecularProgram.mShaderFiles.push_back(make_pair("interface/highlightF.glsl", GL_FRAGMENT_SHADER)); + gHighlightSpecularProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = gHighlightSpecularProgram.createShader(NULL, NULL); + } + + if (success) + { + gUIProgram.mName = "UI Shader"; + gUIProgram.mShaderFiles.clear(); + gUIProgram.mShaderFiles.push_back(make_pair("interface/uiV.glsl", GL_VERTEX_SHADER)); + gUIProgram.mShaderFiles.push_back(make_pair("interface/uiF.glsl", GL_FRAGMENT_SHADER)); + gUIProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = gUIProgram.createShader(NULL, NULL); + } + + if (success) + { + gPathfindingProgram.mName = "Pathfinding Shader"; + gPathfindingProgram.mShaderFiles.clear(); + gPathfindingProgram.mShaderFiles.push_back(make_pair("interface/pathfindingV.glsl", GL_VERTEX_SHADER)); + gPathfindingProgram.mShaderFiles.push_back(make_pair("interface/pathfindingF.glsl", GL_FRAGMENT_SHADER)); + gPathfindingProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = gPathfindingProgram.createShader(NULL, NULL); + } + + if (success) + { + gPathfindingNoNormalsProgram.mName = "PathfindingNoNormals Shader"; + gPathfindingNoNormalsProgram.mShaderFiles.clear(); + gPathfindingNoNormalsProgram.mShaderFiles.push_back(make_pair("interface/pathfindingNoNormalV.glsl", GL_VERTEX_SHADER)); + gPathfindingNoNormalsProgram.mShaderFiles.push_back(make_pair("interface/pathfindingF.glsl", GL_FRAGMENT_SHADER)); + gPathfindingNoNormalsProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = gPathfindingNoNormalsProgram.createShader(NULL, NULL); + } + + if (success) + { + gGlowCombineProgram.mName = "Glow Combine Shader"; + gGlowCombineProgram.mShaderFiles.clear(); + gGlowCombineProgram.mShaderFiles.push_back(make_pair("interface/glowcombineV.glsl", GL_VERTEX_SHADER)); + gGlowCombineProgram.mShaderFiles.push_back(make_pair("interface/glowcombineF.glsl", GL_FRAGMENT_SHADER)); + gGlowCombineProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = gGlowCombineProgram.createShader(NULL, NULL); + if (success) + { + gGlowCombineProgram.bind(); + gGlowCombineProgram.uniform1i(sGlowMap, 0); + gGlowCombineProgram.uniform1i(sScreenMap, 1); + gGlowCombineProgram.unbind(); + } + } + + if (success) + { + gGlowCombineFXAAProgram.mName = "Glow CombineFXAA Shader"; + gGlowCombineFXAAProgram.mShaderFiles.clear(); + gGlowCombineFXAAProgram.mShaderFiles.push_back(make_pair("interface/glowcombineFXAAV.glsl", GL_VERTEX_SHADER)); + gGlowCombineFXAAProgram.mShaderFiles.push_back(make_pair("interface/glowcombineFXAAF.glsl", GL_FRAGMENT_SHADER)); + gGlowCombineFXAAProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = gGlowCombineFXAAProgram.createShader(NULL, NULL); + if (success) + { + gGlowCombineFXAAProgram.bind(); + gGlowCombineFXAAProgram.uniform1i(sGlowMap, 0); + gGlowCombineFXAAProgram.uniform1i(sScreenMap, 1); + gGlowCombineFXAAProgram.unbind(); + } + } + +#ifdef LL_WINDOWS + if (success) + { + gTwoTextureCompareProgram.mName = "Two Texture Compare Shader"; + gTwoTextureCompareProgram.mShaderFiles.clear(); + gTwoTextureCompareProgram.mShaderFiles.push_back(make_pair("interface/twotexturecompareV.glsl", GL_VERTEX_SHADER)); + gTwoTextureCompareProgram.mShaderFiles.push_back(make_pair("interface/twotexturecompareF.glsl", GL_FRAGMENT_SHADER)); + gTwoTextureCompareProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = gTwoTextureCompareProgram.createShader(NULL, NULL); + if (success) + { + gTwoTextureCompareProgram.bind(); + gTwoTextureCompareProgram.uniform1i(sTex0, 0); + gTwoTextureCompareProgram.uniform1i(sTex1, 1); + gTwoTextureCompareProgram.uniform1i(sDitherTex, 2); + } + } + + if (success) + { + gOneTextureFilterProgram.mName = "One Texture Filter Shader"; + gOneTextureFilterProgram.mShaderFiles.clear(); + gOneTextureFilterProgram.mShaderFiles.push_back(make_pair("interface/onetexturefilterV.glsl", GL_VERTEX_SHADER)); + gOneTextureFilterProgram.mShaderFiles.push_back(make_pair("interface/onetexturefilterF.glsl", GL_FRAGMENT_SHADER)); + gOneTextureFilterProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = gOneTextureFilterProgram.createShader(NULL, NULL); + if (success) + { + gOneTextureFilterProgram.bind(); + gOneTextureFilterProgram.uniform1i(sTex0, 0); + } + } +#endif + + if (success) + { + gSolidColorProgram.mName = "Solid Color Shader"; + gSolidColorProgram.mShaderFiles.clear(); + gSolidColorProgram.mShaderFiles.push_back(make_pair("interface/solidcolorV.glsl", GL_VERTEX_SHADER)); + gSolidColorProgram.mShaderFiles.push_back(make_pair("interface/solidcolorF.glsl", GL_FRAGMENT_SHADER)); + gSolidColorProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = gSolidColorProgram.createShader(NULL, NULL); + if (success) + { + gSolidColorProgram.bind(); + gSolidColorProgram.uniform1i(sTex0, 0); + gSolidColorProgram.unbind(); + } + } + + if (success) + { + gOcclusionProgram.mName = "Occlusion Shader"; + gOcclusionProgram.mShaderFiles.clear(); + gOcclusionProgram.mShaderFiles.push_back(make_pair("interface/occlusionV.glsl", GL_VERTEX_SHADER)); + gOcclusionProgram.mShaderFiles.push_back(make_pair("interface/occlusionF.glsl", GL_FRAGMENT_SHADER)); + gOcclusionProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + gOcclusionProgram.mRiggedVariant = &gSkinnedOcclusionProgram; + success = gOcclusionProgram.createShader(NULL, NULL); + } + + if (success) + { + gSkinnedOcclusionProgram.mName = "Skinned Occlusion Shader"; + gSkinnedOcclusionProgram.mFeatures.hasObjectSkinning = true; + gSkinnedOcclusionProgram.mShaderFiles.clear(); + gSkinnedOcclusionProgram.mShaderFiles.push_back(make_pair("interface/occlusionSkinnedV.glsl", GL_VERTEX_SHADER)); + gSkinnedOcclusionProgram.mShaderFiles.push_back(make_pair("interface/occlusionF.glsl", GL_FRAGMENT_SHADER)); + gSkinnedOcclusionProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = gSkinnedOcclusionProgram.createShader(NULL, NULL); + } + + if (success) + { + gOcclusionCubeProgram.mName = "Occlusion Cube Shader"; + gOcclusionCubeProgram.mShaderFiles.clear(); + gOcclusionCubeProgram.mShaderFiles.push_back(make_pair("interface/occlusionCubeV.glsl", GL_VERTEX_SHADER)); + gOcclusionCubeProgram.mShaderFiles.push_back(make_pair("interface/occlusionF.glsl", GL_FRAGMENT_SHADER)); + gOcclusionCubeProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = gOcclusionCubeProgram.createShader(NULL, NULL); + } + + if (success) + { + gDebugProgram.mName = "Debug Shader"; + gDebugProgram.mShaderFiles.clear(); + gDebugProgram.mShaderFiles.push_back(make_pair("interface/debugV.glsl", GL_VERTEX_SHADER)); + gDebugProgram.mShaderFiles.push_back(make_pair("interface/debugF.glsl", GL_FRAGMENT_SHADER)); + gDebugProgram.mRiggedVariant = &gSkinnedDebugProgram; + gDebugProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = make_rigged_variant(gDebugProgram, gSkinnedDebugProgram); + success = success && gDebugProgram.createShader(NULL, NULL); + } + + if (success) + { + gClipProgram.mName = "Clip Shader"; + gClipProgram.mShaderFiles.clear(); + gClipProgram.mShaderFiles.push_back(make_pair("interface/clipV.glsl", GL_VERTEX_SHADER)); + gClipProgram.mShaderFiles.push_back(make_pair("interface/clipF.glsl", GL_FRAGMENT_SHADER)); + gClipProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = gClipProgram.createShader(NULL, NULL); + } + + if (success) + { + gBenchmarkProgram.mName = "Benchmark Shader"; + gBenchmarkProgram.mShaderFiles.clear(); + gBenchmarkProgram.mShaderFiles.push_back(make_pair("interface/benchmarkV.glsl", GL_VERTEX_SHADER)); + gBenchmarkProgram.mShaderFiles.push_back(make_pair("interface/benchmarkF.glsl", GL_FRAGMENT_SHADER)); + gBenchmarkProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = gBenchmarkProgram.createShader(NULL, NULL); + } + + if (success) + { + gReflectionProbeDisplayProgram.mName = "Reflection Probe Display Shader"; + gReflectionProbeDisplayProgram.mFeatures.hasReflectionProbes = true; + gReflectionProbeDisplayProgram.mFeatures.hasSrgb = true; + gReflectionProbeDisplayProgram.mFeatures.calculatesAtmospherics = true; + gReflectionProbeDisplayProgram.mFeatures.hasAtmospherics = true; + gReflectionProbeDisplayProgram.mFeatures.hasGamma = true; + gReflectionProbeDisplayProgram.mFeatures.isDeferred = true; + gReflectionProbeDisplayProgram.mShaderFiles.clear(); + gReflectionProbeDisplayProgram.mShaderFiles.push_back(make_pair("interface/reflectionprobeV.glsl", GL_VERTEX_SHADER)); + gReflectionProbeDisplayProgram.mShaderFiles.push_back(make_pair("interface/reflectionprobeF.glsl", GL_FRAGMENT_SHADER)); + gReflectionProbeDisplayProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = gReflectionProbeDisplayProgram.createShader(NULL, NULL); + } + + if (success) + { + gCopyProgram.mName = "Copy Shader"; + gCopyProgram.mShaderFiles.clear(); + gCopyProgram.mShaderFiles.push_back(make_pair("interface/copyV.glsl", GL_VERTEX_SHADER)); + gCopyProgram.mShaderFiles.push_back(make_pair("interface/copyF.glsl", GL_FRAGMENT_SHADER)); + gCopyProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = gCopyProgram.createShader(NULL, NULL); + } + + if (success) + { + gCopyDepthProgram.mName = "Copy Depth Shader"; + gCopyDepthProgram.mShaderFiles.clear(); + gCopyDepthProgram.mShaderFiles.push_back(make_pair("interface/copyV.glsl", GL_VERTEX_SHADER)); + gCopyDepthProgram.mShaderFiles.push_back(make_pair("interface/copyF.glsl", GL_FRAGMENT_SHADER)); + gCopyDepthProgram.clearPermutations(); + gCopyDepthProgram.addPermutation("COPY_DEPTH", "1"); + gCopyDepthProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = gCopyDepthProgram.createShader(NULL, NULL); + } + + if (success) + { + gAlphaMaskProgram.mName = "Alpha Mask Shader"; + gAlphaMaskProgram.mShaderFiles.clear(); + gAlphaMaskProgram.mShaderFiles.push_back(make_pair("interface/alphamaskV.glsl", GL_VERTEX_SHADER)); + gAlphaMaskProgram.mShaderFiles.push_back(make_pair("interface/alphamaskF.glsl", GL_FRAGMENT_SHADER)); + gAlphaMaskProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = gAlphaMaskProgram.createShader(NULL, NULL); + } + + if (success) + { + gReflectionMipProgram.mName = "Reflection Mip Shader"; + gReflectionMipProgram.mFeatures.isDeferred = true; + gReflectionMipProgram.mFeatures.hasGamma = true; + gReflectionMipProgram.mFeatures.hasAtmospherics = true; + gReflectionMipProgram.mFeatures.calculatesAtmospherics = true; + gReflectionMipProgram.mShaderFiles.clear(); + gReflectionMipProgram.mShaderFiles.push_back(make_pair("interface/splattexturerectV.glsl", GL_VERTEX_SHADER)); + gReflectionMipProgram.mShaderFiles.push_back(make_pair("interface/reflectionmipF.glsl", GL_FRAGMENT_SHADER)); + gReflectionMipProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = gReflectionMipProgram.createShader(NULL, NULL); + } + + if (success) + { + gGaussianProgram.mName = "Reflection Mip Shader"; + gGaussianProgram.mFeatures.isDeferred = true; + gGaussianProgram.mFeatures.hasGamma = true; + gGaussianProgram.mFeatures.hasAtmospherics = true; + gGaussianProgram.mFeatures.calculatesAtmospherics = true; + gGaussianProgram.mShaderFiles.clear(); + gGaussianProgram.mShaderFiles.push_back(make_pair("interface/splattexturerectV.glsl", GL_VERTEX_SHADER)); + gGaussianProgram.mShaderFiles.push_back(make_pair("interface/gaussianF.glsl", GL_FRAGMENT_SHADER)); + gGaussianProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = gGaussianProgram.createShader(NULL, NULL); + } + + if (success && gGLManager.mHasCubeMapArray) + { + gRadianceGenProgram.mName = "Radiance Gen Shader"; + gRadianceGenProgram.mShaderFiles.clear(); + gRadianceGenProgram.mShaderFiles.push_back(make_pair("interface/radianceGenV.glsl", GL_VERTEX_SHADER)); + gRadianceGenProgram.mShaderFiles.push_back(make_pair("interface/radianceGenF.glsl", GL_FRAGMENT_SHADER)); + gRadianceGenProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = gRadianceGenProgram.createShader(NULL, NULL); + } + + if (success && gGLManager.mHasCubeMapArray) + { + gIrradianceGenProgram.mName = "Irradiance Gen Shader"; + gIrradianceGenProgram.mShaderFiles.clear(); + gIrradianceGenProgram.mShaderFiles.push_back(make_pair("interface/irradianceGenV.glsl", GL_VERTEX_SHADER)); + gIrradianceGenProgram.mShaderFiles.push_back(make_pair("interface/irradianceGenF.glsl", GL_FRAGMENT_SHADER)); + gIrradianceGenProgram.mShaderLevel = mShaderLevel[SHADER_INTERFACE]; + success = gIrradianceGenProgram.createShader(NULL, NULL); + } + + if( !success ) + { + mShaderLevel[SHADER_INTERFACE] = 0; + return false; + } + + return true; +} + + +std::string LLViewerShaderMgr::getShaderDirPrefix(void) +{ + return gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "shaders/class"); +} + +void LLViewerShaderMgr::updateShaderUniforms(LLGLSLShader * shader) +{ + LLEnvironment::instance().updateShaderUniforms(shader); +} + +LLViewerShaderMgr::shader_iter LLViewerShaderMgr::beginShaders() const +{ + return mShaderList.begin(); +} + +LLViewerShaderMgr::shader_iter LLViewerShaderMgr::endShaders() const +{ + return mShaderList.end(); +} + diff --git a/indra/newview/llviewershadermgr.h b/indra/newview/llviewershadermgr.h index 7155b7c5bd..c3b9ddbff1 100644 --- a/indra/newview/llviewershadermgr.h +++ b/indra/newview/llviewershadermgr.h @@ -1,275 +1,275 @@ -/** - * @file llviewershadermgr.h - * @brief Viewer Shader Manager - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_VIEWER_SHADER_MGR_H -#define LL_VIEWER_SHADER_MGR_H - -#include "llshadermgr.h" -#include "llmaterial.h" - -#define LL_DEFERRED_MULTI_LIGHT_COUNT 16 - -class LLViewerShaderMgr: public LLShaderMgr -{ -public: - static bool sInitialized; - static bool sSkipReload; - - LLViewerShaderMgr(); - /* virtual */ ~LLViewerShaderMgr(); - - // singleton pattern implementation - static LLViewerShaderMgr * instance(); - static void releaseInstance(); - - void initAttribsAndUniforms(void); - void setShaders(); - void unloadShaders(); - S32 getShaderLevel(S32 type); - - // loadBasicShaders in case of a failure returns - // name of a file error happened at, otherwise - // returns an empty string - std::string loadBasicShaders(); - bool loadShadersEffects(); - bool loadShadersDeferred(); - bool loadShadersObject(); - bool loadShadersAvatar(); - bool loadShadersWater(); - bool loadShadersInterface(); - - std::vector mShaderLevel; - S32 mMaxAvatarShaderLevel; - - enum EShaderClass - { - SHADER_LIGHTING, - SHADER_OBJECT, - SHADER_AVATAR, - SHADER_ENVIRONMENT, - SHADER_INTERFACE, - SHADER_EFFECT, - SHADER_WINDLIGHT, - SHADER_WATER, - SHADER_DEFERRED, - SHADER_COUNT - }; - - // simple model of forward iterator - // http://www.sgi.com/tech/stl/ForwardIterator.html - class shader_iter - { - private: - friend bool operator == (shader_iter const & a, shader_iter const & b); - friend bool operator != (shader_iter const & a, shader_iter const & b); - - typedef std::vector::const_iterator base_iter_t; - public: - shader_iter() - { - } - - shader_iter(base_iter_t iter) : mIter(iter) - { - } - - LLGLSLShader & operator * () const - { - return **mIter; - } - - LLGLSLShader * operator -> () const - { - return *mIter; - } - - shader_iter & operator++ () - { - ++mIter; - return *this; - } - - shader_iter operator++ (int) - { - return mIter++; - } - - private: - base_iter_t mIter; - }; - - shader_iter beginShaders() const; - shader_iter endShaders() const; - - /* virtual */ std::string getShaderDirPrefix(void); - - /* virtual */ void updateShaderUniforms(LLGLSLShader * shader); - -private: - // the list of shaders we need to propagate parameters to. - std::vector mShaderList; - -}; //LLViewerShaderMgr - -inline bool operator == (LLViewerShaderMgr::shader_iter const & a, LLViewerShaderMgr::shader_iter const & b) -{ - return a.mIter == b.mIter; -} - -inline bool operator != (LLViewerShaderMgr::shader_iter const & a, LLViewerShaderMgr::shader_iter const & b) -{ - return a.mIter != b.mIter; -} - -extern LLVector4 gShinyOrigin; - -//utility shaders -extern LLGLSLShader gOcclusionProgram; -extern LLGLSLShader gOcclusionCubeProgram; -extern LLGLSLShader gGlowCombineProgram; -extern LLGLSLShader gReflectionMipProgram; -extern LLGLSLShader gGaussianProgram; -extern LLGLSLShader gRadianceGenProgram; -extern LLGLSLShader gIrradianceGenProgram; -extern LLGLSLShader gGlowCombineFXAAProgram; -extern LLGLSLShader gDebugProgram; -extern LLGLSLShader gClipProgram; -extern LLGLSLShader gBenchmarkProgram; -extern LLGLSLShader gReflectionProbeDisplayProgram; -extern LLGLSLShader gCopyProgram; -extern LLGLSLShader gCopyDepthProgram; - -//output tex0[tc0] - tex1[tc1] -extern LLGLSLShader gTwoTextureCompareProgram; -//discard some fragments based on user-set color tolerance -extern LLGLSLShader gOneTextureFilterProgram; - - -//object shaders -extern LLGLSLShader gObjectPreviewProgram; -extern LLGLSLShader gPhysicsPreviewProgram; -extern LLGLSLShader gSkinnedObjectFullbrightAlphaMaskProgram; -extern LLGLSLShader gObjectBumpProgram; -extern LLGLSLShader gSkinnedObjectBumpProgram; -extern LLGLSLShader gObjectAlphaMaskNoColorProgram; - -//environment shaders -extern LLGLSLShader gWaterProgram; -extern LLGLSLShader gWaterEdgeProgram; -extern LLGLSLShader gUnderWaterProgram; -extern LLGLSLShader gGlowProgram; -extern LLGLSLShader gGlowExtractProgram; - -//interface shaders -extern LLGLSLShader gHighlightProgram; -extern LLGLSLShader gHighlightNormalProgram; -extern LLGLSLShader gHighlightSpecularProgram; - -extern LLGLSLShader gDeferredHighlightProgram; - -extern LLGLSLShader gPathfindingProgram; -extern LLGLSLShader gPathfindingNoNormalsProgram; - -// avatar shader handles -extern LLGLSLShader gAvatarProgram; -extern LLGLSLShader gAvatarEyeballProgram; -extern LLGLSLShader gImpostorProgram; - -// Post Process Shaders -extern LLGLSLShader gPostScreenSpaceReflectionProgram; - -// Deferred rendering shaders -extern LLGLSLShader gDeferredImpostorProgram; -extern LLGLSLShader gDeferredDiffuseProgram; -extern LLGLSLShader gDeferredDiffuseAlphaMaskProgram; -extern LLGLSLShader gDeferredNonIndexedDiffuseAlphaMaskProgram; -extern LLGLSLShader gDeferredNonIndexedDiffuseAlphaMaskNoColorProgram; -extern LLGLSLShader gDeferredNonIndexedDiffuseProgram; -extern LLGLSLShader gDeferredBumpProgram; -extern LLGLSLShader gDeferredTerrainProgram; -extern LLGLSLShader gDeferredTreeProgram; -extern LLGLSLShader gDeferredTreeShadowProgram; -extern LLGLSLShader gDeferredLightProgram; -extern LLGLSLShader gDeferredMultiLightProgram[LL_DEFERRED_MULTI_LIGHT_COUNT]; -extern LLGLSLShader gDeferredSpotLightProgram; -extern LLGLSLShader gDeferredMultiSpotLightProgram; -extern LLGLSLShader gDeferredSunProgram; -extern LLGLSLShader gHazeProgram; -extern LLGLSLShader gHazeWaterProgram; -extern LLGLSLShader gDeferredBlurLightProgram; -extern LLGLSLShader gDeferredAvatarProgram; -extern LLGLSLShader gDeferredSoftenProgram; -extern LLGLSLShader gDeferredShadowProgram; -extern LLGLSLShader gDeferredShadowCubeProgram; -extern LLGLSLShader gDeferredShadowAlphaMaskProgram; -extern LLGLSLShader gDeferredShadowGLTFAlphaMaskProgram; -extern LLGLSLShader gDeferredShadowGLTFAlphaBlendProgram; -extern LLGLSLShader gDeferredShadowFullbrightAlphaMaskProgram; -extern LLGLSLShader gDeferredPostProgram; -extern LLGLSLShader gDeferredCoFProgram; -extern LLGLSLShader gDeferredDoFCombineProgram; -extern LLGLSLShader gFXAAProgram; -extern LLGLSLShader gDeferredPostNoDoFProgram; -extern LLGLSLShader gDeferredPostGammaCorrectProgram; -extern LLGLSLShader gNoPostGammaCorrectProgram; -extern LLGLSLShader gLegacyPostGammaCorrectProgram; -extern LLGLSLShader gExposureProgram; -extern LLGLSLShader gLuminanceProgram; -extern LLGLSLShader gDeferredAvatarShadowProgram; -extern LLGLSLShader gDeferredAvatarAlphaShadowProgram; -extern LLGLSLShader gDeferredAvatarAlphaMaskShadowProgram; -extern LLGLSLShader gDeferredAlphaProgram; -extern LLGLSLShader gHUDAlphaProgram; -extern LLGLSLShader gDeferredAlphaImpostorProgram; -extern LLGLSLShader gDeferredFullbrightProgram; -extern LLGLSLShader gHUDFullbrightProgram; -extern LLGLSLShader gDeferredFullbrightAlphaMaskProgram; -extern LLGLSLShader gHUDFullbrightAlphaMaskProgram; -extern LLGLSLShader gDeferredFullbrightAlphaMaskAlphaProgram; -extern LLGLSLShader gHUDFullbrightAlphaMaskAlphaProgram; -extern LLGLSLShader gDeferredEmissiveProgram; -extern LLGLSLShader gDeferredAvatarEyesProgram; -extern LLGLSLShader gDeferredAvatarAlphaProgram; -extern LLGLSLShader gDeferredWLSkyProgram; -extern LLGLSLShader gDeferredWLCloudProgram; -extern LLGLSLShader gDeferredWLSunProgram; -extern LLGLSLShader gDeferredWLMoonProgram; -extern LLGLSLShader gDeferredStarProgram; -extern LLGLSLShader gDeferredFullbrightShinyProgram; -extern LLGLSLShader gHUDFullbrightShinyProgram; -extern LLGLSLShader gNormalMapGenProgram; -extern LLGLSLShader gDeferredGenBrdfLutProgram; -extern LLGLSLShader gDeferredBufferVisualProgram; - -// Deferred materials shaders -extern LLGLSLShader gDeferredMaterialProgram[LLMaterial::SHADER_COUNT*2]; - -extern LLGLSLShader gHUDPBROpaqueProgram; -extern LLGLSLShader gPBRGlowProgram; -extern LLGLSLShader gDeferredPBROpaqueProgram; -extern LLGLSLShader gDeferredPBRAlphaProgram; -extern LLGLSLShader gHUDPBRAlphaProgram; -#endif +/** + * @file llviewershadermgr.h + * @brief Viewer Shader Manager + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_VIEWER_SHADER_MGR_H +#define LL_VIEWER_SHADER_MGR_H + +#include "llshadermgr.h" +#include "llmaterial.h" + +#define LL_DEFERRED_MULTI_LIGHT_COUNT 16 + +class LLViewerShaderMgr: public LLShaderMgr +{ +public: + static bool sInitialized; + static bool sSkipReload; + + LLViewerShaderMgr(); + /* virtual */ ~LLViewerShaderMgr(); + + // singleton pattern implementation + static LLViewerShaderMgr * instance(); + static void releaseInstance(); + + void initAttribsAndUniforms(void); + void setShaders(); + void unloadShaders(); + S32 getShaderLevel(S32 type); + + // loadBasicShaders in case of a failure returns + // name of a file error happened at, otherwise + // returns an empty string + std::string loadBasicShaders(); + bool loadShadersEffects(); + bool loadShadersDeferred(); + bool loadShadersObject(); + bool loadShadersAvatar(); + bool loadShadersWater(); + bool loadShadersInterface(); + + std::vector mShaderLevel; + S32 mMaxAvatarShaderLevel; + + enum EShaderClass + { + SHADER_LIGHTING, + SHADER_OBJECT, + SHADER_AVATAR, + SHADER_ENVIRONMENT, + SHADER_INTERFACE, + SHADER_EFFECT, + SHADER_WINDLIGHT, + SHADER_WATER, + SHADER_DEFERRED, + SHADER_COUNT + }; + + // simple model of forward iterator + // http://www.sgi.com/tech/stl/ForwardIterator.html + class shader_iter + { + private: + friend bool operator == (shader_iter const & a, shader_iter const & b); + friend bool operator != (shader_iter const & a, shader_iter const & b); + + typedef std::vector::const_iterator base_iter_t; + public: + shader_iter() + { + } + + shader_iter(base_iter_t iter) : mIter(iter) + { + } + + LLGLSLShader & operator * () const + { + return **mIter; + } + + LLGLSLShader * operator -> () const + { + return *mIter; + } + + shader_iter & operator++ () + { + ++mIter; + return *this; + } + + shader_iter operator++ (int) + { + return mIter++; + } + + private: + base_iter_t mIter; + }; + + shader_iter beginShaders() const; + shader_iter endShaders() const; + + /* virtual */ std::string getShaderDirPrefix(void); + + /* virtual */ void updateShaderUniforms(LLGLSLShader * shader); + +private: + // the list of shaders we need to propagate parameters to. + std::vector mShaderList; + +}; //LLViewerShaderMgr + +inline bool operator == (LLViewerShaderMgr::shader_iter const & a, LLViewerShaderMgr::shader_iter const & b) +{ + return a.mIter == b.mIter; +} + +inline bool operator != (LLViewerShaderMgr::shader_iter const & a, LLViewerShaderMgr::shader_iter const & b) +{ + return a.mIter != b.mIter; +} + +extern LLVector4 gShinyOrigin; + +//utility shaders +extern LLGLSLShader gOcclusionProgram; +extern LLGLSLShader gOcclusionCubeProgram; +extern LLGLSLShader gGlowCombineProgram; +extern LLGLSLShader gReflectionMipProgram; +extern LLGLSLShader gGaussianProgram; +extern LLGLSLShader gRadianceGenProgram; +extern LLGLSLShader gIrradianceGenProgram; +extern LLGLSLShader gGlowCombineFXAAProgram; +extern LLGLSLShader gDebugProgram; +extern LLGLSLShader gClipProgram; +extern LLGLSLShader gBenchmarkProgram; +extern LLGLSLShader gReflectionProbeDisplayProgram; +extern LLGLSLShader gCopyProgram; +extern LLGLSLShader gCopyDepthProgram; + +//output tex0[tc0] - tex1[tc1] +extern LLGLSLShader gTwoTextureCompareProgram; +//discard some fragments based on user-set color tolerance +extern LLGLSLShader gOneTextureFilterProgram; + + +//object shaders +extern LLGLSLShader gObjectPreviewProgram; +extern LLGLSLShader gPhysicsPreviewProgram; +extern LLGLSLShader gSkinnedObjectFullbrightAlphaMaskProgram; +extern LLGLSLShader gObjectBumpProgram; +extern LLGLSLShader gSkinnedObjectBumpProgram; +extern LLGLSLShader gObjectAlphaMaskNoColorProgram; + +//environment shaders +extern LLGLSLShader gWaterProgram; +extern LLGLSLShader gWaterEdgeProgram; +extern LLGLSLShader gUnderWaterProgram; +extern LLGLSLShader gGlowProgram; +extern LLGLSLShader gGlowExtractProgram; + +//interface shaders +extern LLGLSLShader gHighlightProgram; +extern LLGLSLShader gHighlightNormalProgram; +extern LLGLSLShader gHighlightSpecularProgram; + +extern LLGLSLShader gDeferredHighlightProgram; + +extern LLGLSLShader gPathfindingProgram; +extern LLGLSLShader gPathfindingNoNormalsProgram; + +// avatar shader handles +extern LLGLSLShader gAvatarProgram; +extern LLGLSLShader gAvatarEyeballProgram; +extern LLGLSLShader gImpostorProgram; + +// Post Process Shaders +extern LLGLSLShader gPostScreenSpaceReflectionProgram; + +// Deferred rendering shaders +extern LLGLSLShader gDeferredImpostorProgram; +extern LLGLSLShader gDeferredDiffuseProgram; +extern LLGLSLShader gDeferredDiffuseAlphaMaskProgram; +extern LLGLSLShader gDeferredNonIndexedDiffuseAlphaMaskProgram; +extern LLGLSLShader gDeferredNonIndexedDiffuseAlphaMaskNoColorProgram; +extern LLGLSLShader gDeferredNonIndexedDiffuseProgram; +extern LLGLSLShader gDeferredBumpProgram; +extern LLGLSLShader gDeferredTerrainProgram; +extern LLGLSLShader gDeferredTreeProgram; +extern LLGLSLShader gDeferredTreeShadowProgram; +extern LLGLSLShader gDeferredLightProgram; +extern LLGLSLShader gDeferredMultiLightProgram[LL_DEFERRED_MULTI_LIGHT_COUNT]; +extern LLGLSLShader gDeferredSpotLightProgram; +extern LLGLSLShader gDeferredMultiSpotLightProgram; +extern LLGLSLShader gDeferredSunProgram; +extern LLGLSLShader gHazeProgram; +extern LLGLSLShader gHazeWaterProgram; +extern LLGLSLShader gDeferredBlurLightProgram; +extern LLGLSLShader gDeferredAvatarProgram; +extern LLGLSLShader gDeferredSoftenProgram; +extern LLGLSLShader gDeferredShadowProgram; +extern LLGLSLShader gDeferredShadowCubeProgram; +extern LLGLSLShader gDeferredShadowAlphaMaskProgram; +extern LLGLSLShader gDeferredShadowGLTFAlphaMaskProgram; +extern LLGLSLShader gDeferredShadowGLTFAlphaBlendProgram; +extern LLGLSLShader gDeferredShadowFullbrightAlphaMaskProgram; +extern LLGLSLShader gDeferredPostProgram; +extern LLGLSLShader gDeferredCoFProgram; +extern LLGLSLShader gDeferredDoFCombineProgram; +extern LLGLSLShader gFXAAProgram; +extern LLGLSLShader gDeferredPostNoDoFProgram; +extern LLGLSLShader gDeferredPostGammaCorrectProgram; +extern LLGLSLShader gNoPostGammaCorrectProgram; +extern LLGLSLShader gLegacyPostGammaCorrectProgram; +extern LLGLSLShader gExposureProgram; +extern LLGLSLShader gLuminanceProgram; +extern LLGLSLShader gDeferredAvatarShadowProgram; +extern LLGLSLShader gDeferredAvatarAlphaShadowProgram; +extern LLGLSLShader gDeferredAvatarAlphaMaskShadowProgram; +extern LLGLSLShader gDeferredAlphaProgram; +extern LLGLSLShader gHUDAlphaProgram; +extern LLGLSLShader gDeferredAlphaImpostorProgram; +extern LLGLSLShader gDeferredFullbrightProgram; +extern LLGLSLShader gHUDFullbrightProgram; +extern LLGLSLShader gDeferredFullbrightAlphaMaskProgram; +extern LLGLSLShader gHUDFullbrightAlphaMaskProgram; +extern LLGLSLShader gDeferredFullbrightAlphaMaskAlphaProgram; +extern LLGLSLShader gHUDFullbrightAlphaMaskAlphaProgram; +extern LLGLSLShader gDeferredEmissiveProgram; +extern LLGLSLShader gDeferredAvatarEyesProgram; +extern LLGLSLShader gDeferredAvatarAlphaProgram; +extern LLGLSLShader gDeferredWLSkyProgram; +extern LLGLSLShader gDeferredWLCloudProgram; +extern LLGLSLShader gDeferredWLSunProgram; +extern LLGLSLShader gDeferredWLMoonProgram; +extern LLGLSLShader gDeferredStarProgram; +extern LLGLSLShader gDeferredFullbrightShinyProgram; +extern LLGLSLShader gHUDFullbrightShinyProgram; +extern LLGLSLShader gNormalMapGenProgram; +extern LLGLSLShader gDeferredGenBrdfLutProgram; +extern LLGLSLShader gDeferredBufferVisualProgram; + +// Deferred materials shaders +extern LLGLSLShader gDeferredMaterialProgram[LLMaterial::SHADER_COUNT*2]; + +extern LLGLSLShader gHUDPBROpaqueProgram; +extern LLGLSLShader gPBRGlowProgram; +extern LLGLSLShader gDeferredPBROpaqueProgram; +extern LLGLSLShader gDeferredPBRAlphaProgram; +extern LLGLSLShader gHUDPBRAlphaProgram; +#endif diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp index 5304c7baf0..3499c7eb7d 100644 --- a/indra/newview/llviewerstats.cpp +++ b/indra/newview/llviewerstats.cpp @@ -1,942 +1,942 @@ -/** - * @file llviewerstats.cpp - * @brief LLViewerStats class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llviewerstats.h" -#include "llviewerthrottle.h" - -#include "message.h" -#include "llfloaterreg.h" -#include "llmemory.h" -#include "lltimer.h" - -#include "llappviewer.h" - -#include "pipeline.h" -#include "lltexturefetch.h" -#include "llviewerobjectlist.h" -#include "llviewertexturelist.h" -#include "lltexlayer.h" -#include "lltexlayerparams.h" -#include "llsurface.h" -#include "llvlmanager.h" -#include "llagent.h" -#include "llagentcamera.h" -#include "llviewercontrol.h" -#include "llversioninfo.h" -#include "llfloatertools.h" -#include "lldebugview.h" -#include "llfasttimerview.h" -#include "llviewerregion.h" -#include "llvoavatar.h" -#include "llvoavatarself.h" -#include "llworld.h" -#include "llfeaturemanager.h" -#include "llviewernetwork.h" -#include "llmeshrepository.h" //for LLMeshRepository::sBytesReceived -#include "llperfstats.h" -#include "llsdserialize.h" -#include "llsdutil.h" -#include "llcorehttputil.h" -#include "llvoicevivox.h" -#include "llinventorymodel.h" -#include "lluiusage.h" -#include "lltranslate.h" - -// "Minimal Vulkan" to get max API Version - -// Calls - #if defined(_WIN32) - #define VKAPI_ATTR - #define VKAPI_CALL __stdcall - #define VKAPI_PTR VKAPI_CALL - #else - #define VKAPI_ATTR - #define VKAPI_CALL - #define VKAPI_PTR - #endif // _WIN32 - -// Macros - // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - // |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10| 9| 8| 7| 6| 5| 4| 3| 2| 1| 0| - // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ - // <-------major-------><-----------minor-----------> <--------------patch--------------> - // 0x7 0x7F 0x3FF 0xFFF - #define VK_API_VERSION_MAJOR( version) (((uint32_t)(version) >> 22) & 0x07FU) // 7 bits - #define VK_API_VERSION_MINOR( version) (((uint32_t)(version) >> 12) & 0x3FFU) // 10 bits - #define VK_API_VERSION_PATCH( version) (((uint32_t)(version) ) & 0xFFFU) // 12 bits - #define VK_API_VERSION_VARIANT(version) (((uint32_t)(version) >> 29) & 0x007U) // 3 bits - - // NOTE: variant is first parameter! This is to match vulkan/vulkan_core.h - #define VK_MAKE_API_VERSION(variant, major, minor, patch) (0\ - | (((uint32_t)(major & 0x07FU)) << 22) \ - | (((uint32_t)(minor & 0x3FFU)) << 12) \ - | (((uint32_t)(patch & 0xFFFU)) ) \ - | (((uint32_t)(variant & 0x007U)) << 29) ) - - #define VK_DEFINE_HANDLE(object) typedef struct object##_T* object; - -// Types - VK_DEFINE_HANDLE(VkInstance); - - typedef enum VkResult - { - VK_SUCCESS = 0, - VK_RESULT_MAX_ENUM = 0x7FFFFFFF - } VkResult; - -// Prototypes - typedef void (VKAPI_PTR *PFN_vkVoidFunction )(void); - typedef PFN_vkVoidFunction (VKAPI_PTR *PFN_vkGetInstanceProcAddr )(VkInstance instance, const char* pName); - typedef VkResult (VKAPI_PTR *PFN_vkEnumerateInstanceVersion)(uint32_t* pApiVersion); - -namespace LLStatViewer -{ - -LLTrace::CountStatHandle<> FPS("FPS", "Frames rendered"), - PACKETS_IN("Packets In", "Packets received"), - PACKETS_LOST("packetsloststat", "Packets lost"), - PACKETS_OUT("packetsoutstat", "Packets sent"), - TEXTURE_PACKETS("texturepacketsstat", "Texture data packets received"), - CHAT_COUNT("chatcount", "Chat messages sent"), - IM_COUNT("imcount", "IMs sent"), - OBJECT_CREATE("objectcreate", "Number of objects created"), - OBJECT_REZ("objectrez", "Object rez count"), - LOGIN_TIMEOUTS("logintimeouts", "Number of login attempts that timed out"), - LSL_SAVES("lslsaves", "Number of times user has saved a script"), - ANIMATION_UPLOADS("animationuploads", "Animations uploaded"), - FLY("fly", "Fly count"), - TELEPORT("teleport", "Teleport count"), - DELETE_OBJECT("deleteobject", "Objects deleted"), - SNAPSHOT("snapshot", "Snapshots taken"), - UPLOAD_SOUND("uploadsound", "Sounds uploaded"), - UPLOAD_TEXTURE("uploadtexture", "Textures uploaded"), - EDIT_TEXTURE("edittexture", "Changes to textures on objects"), - KILLED("killed", "Number of times killed"), - FRAMETIME_DOUBLED("frametimedoubled", "Ratio of frames 2x longer than previous"), - TEX_BAKES("texbakes", "Number of times avatar textures have been baked"), - TEX_REBAKES("texrebakes", "Number of times avatar textures have been forced to rebake"), - NUM_NEW_OBJECTS("numnewobjectsstat", "Number of objects in scene that were not previously in cache"); - -LLTrace::CountStatHandle > - TRIANGLES_DRAWN("trianglesdrawnstat"); - -LLTrace::EventStatHandle > - TRIANGLES_DRAWN_PER_FRAME("trianglesdrawnperframestat"); - -LLTrace::CountStatHandle - ACTIVE_MESSAGE_DATA_RECEIVED("activemessagedatareceived", "Message system data received on all active regions"), - LAYERS_NETWORK_DATA_RECEIVED("layersdatareceived", "Network data received for layer data (terrain)"), - OBJECT_NETWORK_DATA_RECEIVED("objectdatareceived", "Network data received for objects"), - ASSET_UDP_DATA_RECEIVED("assetudpdatareceived", "Network data received for assets (animations, sounds) over UDP message system"), - TEXTURE_NETWORK_DATA_RECEIVED("texturedatareceived", "Network data received for textures"), - MESSAGE_SYSTEM_DATA_IN("messagedatain", "Incoming message system network data"), - MESSAGE_SYSTEM_DATA_OUT("messagedataout", "Outgoing message system network data"); - -LLTrace::CountStatHandle - SIM_20_FPS_TIME("sim20fpstime", "Seconds with sim FPS below 20"), - SIM_PHYSICS_20_FPS_TIME("simphysics20fpstime", "Seconds with physics FPS below 20"), - LOSS_5_PERCENT_TIME("loss5percenttime", "Seconds with packet loss > 5%"); - -SimMeasurement<> SIM_TIME_DILATION("simtimedilation", "Simulator time scale", LL_SIM_STAT_TIME_DILATION), - SIM_FPS("simfps", "Simulator framerate", LL_SIM_STAT_FPS), - SIM_PHYSICS_FPS("simphysicsfps", "Simulator physics framerate", LL_SIM_STAT_PHYSFPS), - SIM_AGENT_UPS("simagentups", "", LL_SIM_STAT_AGENTUPS), - SIM_SCRIPT_EPS("simscripteps", "", LL_SIM_STAT_SCRIPT_EPS), - SIM_SKIPPED_SILHOUETTE("simsimskippedsilhouettesteps", "", LL_SIM_STAT_SKIPPEDAISILSTEPS_PS), - SIM_MAIN_AGENTS("simmainagents", "Number of avatars in current region", LL_SIM_STAT_NUMAGENTMAIN), - SIM_CHILD_AGENTS("simchildagents", "Number of avatars in neighboring regions", LL_SIM_STAT_NUMAGENTCHILD), - SIM_OBJECTS("simobjects", "", LL_SIM_STAT_NUMTASKS), - SIM_ACTIVE_OBJECTS("simactiveobjects", "Number of scripted and/or moving objects", LL_SIM_STAT_NUMTASKSACTIVE), - SIM_ACTIVE_SCRIPTS("simactivescripts", "Number of scripted objects", LL_SIM_STAT_NUMSCRIPTSACTIVE), - SIM_IN_PACKETS_PER_SEC("siminpps", "", LL_SIM_STAT_INPPS), - SIM_OUT_PACKETS_PER_SEC("simoutpps", "", LL_SIM_STAT_OUTPPS), - SIM_PENDING_DOWNLOADS("simpendingdownloads", "", LL_SIM_STAT_PENDING_DOWNLOADS), - SIM_PENDING_UPLOADS("simpendinguploads", "", LL_SIM_STAT_PENDING_UPLOADS), - SIM_PENDING_LOCAL_UPLOADS("simpendinglocaluploads", "", LL_SIM_STAT_PENDING_LOCAL_UPLOADS), - SIM_PHYSICS_PINNED_TASKS("physicspinnedtasks", "", LL_SIM_STAT_PHYSICS_PINNED_TASKS), - SIM_PHYSICS_LOD_TASKS("physicslodtasks", "", LL_SIM_STAT_PHYSICS_LOD_TASKS); - -SimMeasurement > - SIM_PERCENTAGE_SCRIPTS_RUN("simpctscriptsrun", "", LL_SIM_STAT_PCTSCRIPTSRUN), - SIM_SKIPPED_CHARACTERS_PERCENTAGE("simsimpctsteppedcharacters", "", LL_SIM_STAT_PCTSTEPPEDCHARACTERS); - -LLTrace::SampleStatHandle<> FPS_SAMPLE("fpssample"), - NUM_IMAGES("numimagesstat"), - NUM_RAW_IMAGES("numrawimagesstat"), - NUM_MATERIALS("nummaterials"), - NUM_OBJECTS("numobjectsstat"), - NUM_ACTIVE_OBJECTS("numactiveobjectsstat"), - ENABLE_VBO("enablevbo", "Vertex Buffers Enabled"), - VISIBLE_AVATARS("visibleavatars", "Visible Avatars"), - SHADER_OBJECTS("shaderobjects", "Object Shaders"), - DRAW_DISTANCE("drawdistance", "Draw Distance"), - WINDOW_WIDTH("windowwidth", "Window width"), - WINDOW_HEIGHT("windowheight", "Window height"); - -LLTrace::SampleStatHandle > - PACKETS_LOST_PERCENT("packetslostpercentstat"); - -static LLTrace::SampleStatHandle - CHAT_BUBBLES("chatbubbles", "Chat Bubbles Enabled"); - -LLTrace::SampleStatHandle FORMATTED_MEM("formattedmemstat"); -LLTrace::SampleStatHandle DELTA_BANDWIDTH("deltabandwidth", "Increase/Decrease in bandwidth based on packet loss"), - MAX_BANDWIDTH("maxbandwidth", "Max bandwidth setting"); - - -SimMeasurement SIM_FRAME_TIME("simframemsec", "", LL_SIM_STAT_FRAMEMS), - SIM_NET_TIME("simnetmsec", "", LL_SIM_STAT_NETMS), - SIM_OTHER_TIME("simsimothermsec", "", LL_SIM_STAT_SIMOTHERMS), - SIM_PHYSICS_TIME("simsimphysicsmsec", "", LL_SIM_STAT_SIMPHYSICSMS), - SIM_PHYSICS_STEP_TIME("simsimphysicsstepmsec", "", LL_SIM_STAT_SIMPHYSICSSTEPMS), - SIM_PHYSICS_SHAPE_UPDATE_TIME("simsimphysicsshapeupdatemsec", "", LL_SIM_STAT_SIMPHYSICSSHAPEMS), - SIM_PHYSICS_OTHER_TIME("simsimphysicsothermsec", "", LL_SIM_STAT_SIMPHYSICSOTHERMS), - SIM_AI_TIME("simsimaistepmsec", "", LL_SIM_STAT_SIMAISTEPTIMEMS), - SIM_AGENTS_TIME("simagentmsec", "", LL_SIM_STAT_AGENTMS), - SIM_IMAGES_TIME("simimagesmsec", "", LL_SIM_STAT_IMAGESMS), - SIM_SCRIPTS_TIME("simscriptmsec", "", LL_SIM_STAT_SCRIPTMS), - SIM_SPARE_TIME("simsparemsec", "", LL_SIM_STAT_SIMSPARETIME), - SIM_SLEEP_TIME("simsleepmsec", "", LL_SIM_STAT_SIMSLEEPTIME), - SIM_PUMP_IO_TIME("simpumpiomsec", "", LL_SIM_STAT_IOPUMPTIME); - -SimMeasurement SIM_UNACKED_BYTES("simtotalunackedbytes", "", LL_SIM_STAT_TOTAL_UNACKED_BYTES); -SimMeasurement SIM_PHYSICS_MEM("physicsmemoryallocated", "", LL_SIM_STAT_SIMPHYSICSMEMORY); - -LLTrace::SampleStatHandle FRAMETIME_JITTER("frametimejitter", "Average delta between successive frame times"), - FRAMETIME_SLEW("frametimeslew", "Average delta between frame time and mean"), - FRAMETIME("frametime", "Measured frame time"), - SIM_PING("simpingstat"); - -LLTrace::EventStatHandle > AGENT_POSITION_SNAP("agentpositionsnap", "agent position corrections"); - -LLTrace::EventStatHandle<> LOADING_WEARABLES_LONG_DELAY("loadingwearableslongdelay", "Wearables took too long to load"); - -LLTrace::EventStatHandle REGION_CROSSING_TIME("regioncrossingtime", "CROSSING_AVG"), - FRAME_STACKTIME("framestacktime", "FRAME_SECS"), - UPDATE_STACKTIME("updatestacktime", "UPDATE_SECS"), - NETWORK_STACKTIME("networkstacktime", "NETWORK_SECS"), - IMAGE_STACKTIME("imagestacktime", "IMAGE_SECS"), - REBUILD_STACKTIME("rebuildstacktime", "REBUILD_SECS"), - RENDER_STACKTIME("renderstacktime", "RENDER_SECS"); - -LLTrace::EventStatHandle AVATAR_EDIT_TIME("avataredittime", "Seconds in Edit Appearance"), - TOOLBOX_TIME("toolboxtime", "Seconds using Toolbox"), - MOUSELOOK_TIME("mouselooktime", "Seconds in Mouselook"), - FPS_10_TIME("fps10time", "Seconds below 10 FPS"), - FPS_8_TIME("fps8time", "Seconds below 8 FPS"), - FPS_2_TIME("fps2time", "Seconds below 2 FPS"); - -LLTrace::EventStatHandle > OBJECT_CACHE_HIT_RATE("object_cache_hits"); - -LLTrace::EventStatHandle TEXTURE_FETCH_TIME("texture_fetch_time"); - -LLTrace::SampleStatHandle > SCENERY_FRAME_PCT("scenery_frame_pct"); -LLTrace::SampleStatHandle > AVATAR_FRAME_PCT("avatar_frame_pct"); -LLTrace::SampleStatHandle > HUDS_FRAME_PCT("huds_frame_pct"); -LLTrace::SampleStatHandle > UI_FRAME_PCT("ui_frame_pct"); -LLTrace::SampleStatHandle > SWAP_FRAME_PCT("swap_frame_pct"); -LLTrace::SampleStatHandle > IDLE_FRAME_PCT("idle_frame_pct"); -} - -LLViewerStats::LLViewerStats() -: mLastTimeDiff(0.0) -{ - getRecording().start(); -} - -LLViewerStats::~LLViewerStats() -{} - -void LLViewerStats::resetStats() -{ - getRecording().reset(); -} - -void LLViewerStats::updateFrameStats(const F64Seconds time_diff) -{ - if (getRecording().getLastValue(LLStatViewer::PACKETS_LOST_PERCENT) > F32Percent(5.0)) - { - add(LLStatViewer::LOSS_5_PERCENT_TIME, time_diff); - } - - F32 sim_fps = getRecording().getLastValue(LLStatViewer::SIM_FPS); - if (0.f < sim_fps && sim_fps < 20.f) - { - add(LLStatViewer::SIM_20_FPS_TIME, time_diff); - } - - F32 sim_physics_fps = getRecording().getLastValue(LLStatViewer::SIM_PHYSICS_FPS); - - if (0.f < sim_physics_fps && sim_physics_fps < 20.f) - { - add(LLStatViewer::SIM_PHYSICS_20_FPS_TIME, time_diff); - } - - if (time_diff >= (F64Seconds)0.5) - { - record(LLStatViewer::FPS_2_TIME, time_diff); - } - if (time_diff >= (F64Seconds)0.125) - { - record(LLStatViewer::FPS_8_TIME, time_diff); - } - if (time_diff >= (F64Seconds)0.1) - { - record(LLStatViewer::FPS_10_TIME, time_diff); - } - - if (gFrameCount && mLastTimeDiff > (F64Seconds)0.0) - { - // new "stutter" meter - add(LLStatViewer::FRAMETIME_DOUBLED, time_diff >= 2.0 * mLastTimeDiff ? 1 : 0); - - sample(LLStatViewer::FRAMETIME, time_diff); - - // old stats that were never really used - F64Seconds jit = (F64Seconds) std::fabs((mLastTimeDiff - time_diff)); - sample(LLStatViewer::FRAMETIME_JITTER, jit); - - F32Seconds average_frametime = gRenderStartTime.getElapsedTimeF32() / (F32)gFrameCount; - sample(LLStatViewer::FRAMETIME_SLEW, F64Milliseconds (average_frametime - time_diff)); - - F32 max_bandwidth = gViewerThrottle.getMaxBandwidth(); - F32 delta_bandwidth = gViewerThrottle.getCurrentBandwidth() - max_bandwidth; - sample(LLStatViewer::DELTA_BANDWIDTH, F64Bits(delta_bandwidth)); - sample(LLStatViewer::MAX_BANDWIDTH, F64Bits(max_bandwidth)); - } - - mLastTimeDiff = time_diff; -} - -void LLViewerStats::addToMessage(LLSD &body) -{ - LLSD &misc = body["misc"]; - - misc["Version"] = true; - //TODO RN: get last value, not mean - misc["Vertex Buffers Enabled"] = getRecording().getMean(LLStatViewer::ENABLE_VBO); - - body["AgentPositionSnaps"] = getRecording().getSum(LLStatViewer::AGENT_POSITION_SNAP).value(); //mAgentPositionSnaps.asLLSD(); - LL_INFOS() << "STAT: AgentPositionSnaps: Mean = " << getRecording().getMean(LLStatViewer::AGENT_POSITION_SNAP).value() << "; StdDev = " << getRecording().getStandardDeviation(LLStatViewer::AGENT_POSITION_SNAP).value() - << "; Count = " << getRecording().getSampleCount(LLStatViewer::AGENT_POSITION_SNAP) << LL_ENDL; -} - -// *NOTE:Mani The following methods used to exist in viewer.cpp -// Moving them here, but not merging them into LLViewerStats yet. -U32 gTotalLandIn = 0, - gTotalLandOut = 0, - gTotalWaterIn = 0, - gTotalWaterOut = 0; - -F32 gAveLandCompression = 0.f, - gAveWaterCompression = 0.f, - gBestLandCompression = 1.f, - gBestWaterCompression = 1.f, - gWorstLandCompression = 0.f, - gWorstWaterCompression = 0.f; - -U32Bytes gTotalWorldData, - gTotalObjectData, - gTotalTextureData; -U32 gSimPingCount = 0; -U32Bits gObjectData; -F32Milliseconds gAvgSimPing(0.f); -// rely on default initialization -U32Bytes gTotalTextureBytesPerBoostLevel[LLViewerTexture::MAX_GL_IMAGE_CATEGORY]; - -extern U32 gVisCompared; -extern U32 gVisTested; - -void update_statistics() -{ - LL_PROFILE_ZONE_SCOPED; - - gTotalWorldData += gVLManager.getTotalBytes(); - gTotalObjectData += gObjectData; - - // make sure we have a valid time delta for this frame - if (gFrameIntervalSeconds > 0.f) - { - if (gAgentCamera.getCameraMode() == CAMERA_MODE_MOUSELOOK) - { - record(LLStatViewer::MOUSELOOK_TIME, gFrameIntervalSeconds); - } - else if (gAgentCamera.getCameraMode() == CAMERA_MODE_CUSTOMIZE_AVATAR) - { - record(LLStatViewer::AVATAR_EDIT_TIME, gFrameIntervalSeconds); - } - else if (LLFloaterReg::instanceVisible("build")) - { - record(LLStatViewer::TOOLBOX_TIME, gFrameIntervalSeconds); - } - } - - LLTrace::Recording& last_frame_recording = LLTrace::get_frame_recording().getLastRecording(); - - record(LLStatViewer::TRIANGLES_DRAWN_PER_FRAME, last_frame_recording.getSum(LLStatViewer::TRIANGLES_DRAWN)); - - sample(LLStatViewer::ENABLE_VBO, (F64)gSavedSettings.getBOOL("RenderVBOEnable")); - sample(LLStatViewer::DRAW_DISTANCE, (F64)gSavedSettings.getF32("RenderFarClip")); - sample(LLStatViewer::CHAT_BUBBLES, gSavedSettings.getBOOL("UseChatBubbles")); - - typedef LLTrace::StatType::instance_tracker_t stat_type_t; - - record(LLStatViewer::FRAME_STACKTIME, last_frame_recording.getSum(*stat_type_t::getInstance("Frame"))); - - if (gAgent.getRegion() && isAgentAvatarValid()) - { - LLCircuitData *cdp = gMessageSystem->mCircuitInfo.findCircuit(gAgent.getRegion()->getHost()); - if (cdp) - { - sample(LLStatViewer::SIM_PING, F64Milliseconds(cdp->getPingDelay())); - gAvgSimPing = ((gAvgSimPing * gSimPingCount) + cdp->getPingDelay()) / (gSimPingCount + 1); - gSimPingCount++; - } - else - { - sample(LLStatViewer::SIM_PING, U32Seconds(10)); - } - } - - if (LLViewerStats::instance().getRecording().getSum(LLStatViewer::FPS)) - { - sample(LLStatViewer::FPS_SAMPLE, LLTrace::get_frame_recording().getPeriodMeanPerSec(LLStatViewer::FPS)); - } - add(LLStatViewer::FPS, 1); - - F64Bits layer_bits = gVLManager.getLandBits() + gVLManager.getWindBits() + gVLManager.getCloudBits(); - add(LLStatViewer::LAYERS_NETWORK_DATA_RECEIVED, layer_bits); - add(LLStatViewer::OBJECT_NETWORK_DATA_RECEIVED, gObjectData); - add(LLStatViewer::ASSET_UDP_DATA_RECEIVED, F64Bits(gTransferManager.getTransferBitsIn(LLTCT_ASSET))); - gTransferManager.resetTransferBitsIn(LLTCT_ASSET); - - sample(LLStatViewer::VISIBLE_AVATARS, LLVOAvatar::sNumVisibleAvatars); - LLWorld *world = LLWorld::getInstance(); // not LLSingleton - if (world) - { - world->updateNetStats(); - world->requestCacheMisses(); - } - - // Reset all of these values. - gVLManager.resetBitCounts(); - gObjectData = (U32Bytes)0; -// gDecodedBits = 0; - - // Only update texture stats periodically so that they are less noisy - { - static const F32 texture_stats_freq = 10.f; - static LLFrameTimer texture_stats_timer; - if (texture_stats_timer.getElapsedTimeF32() >= texture_stats_freq) - { - gTotalTextureData = LLViewerStats::instance().getRecording().getSum(LLStatViewer::TEXTURE_NETWORK_DATA_RECEIVED); - texture_stats_timer.reset(); - } - } - - if (LLFloaterReg::instanceVisible("scene_load_stats")) - { - static const F32 perf_stats_freq = 1; - static LLFrameTimer perf_stats_timer; - if (perf_stats_timer.getElapsedTimeF32() >= perf_stats_freq) - { - LLStringUtil::format_map_t args; - LLPerfStats::bufferToggleLock.lock(); // prevent toggle for a moment - - auto tot_frame_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_FRAME); - // cumulative avatar time (includes idle processing, attachments and base av) - auto tot_avatar_time_raw = LLPerfStats::us_to_raw(LLVOAvatar::getTotalGPURenderTime()); - // cumulative avatar render specific time (a bit arbitrary as the processing is too.) - // auto tot_av_idle_time_raw = LLPerfStats::StatsRecorder::getSum(AvType, LLPerfStats::StatType_t::RENDER_IDLE); - // auto tot_avatar_render_time_raw = tot_avatar_time_raw - tot_av_idle_time_raw; - // the time spent this frame on the "display()" call. Treated as "tot time rendering" - auto tot_render_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_DISPLAY); - // sleep time is basically forced sleep when window out of focus - auto tot_sleep_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_SLEEP); - // time spent on UI - auto tot_ui_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_UI); - // cumulative time spent rendering HUDS - auto tot_huds_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_HUDS); - // "idle" time. This is the time spent in the idle poll section of the main loop - auto tot_idle_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_IDLE); - // similar to sleep time, induced by FPS limit - //auto tot_limit_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_FPSLIMIT); - // swap time is time spent in swap buffer - auto tot_swap_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_SWAP); - - LLPerfStats::bufferToggleLock.unlock(); - - auto tot_frame_time_ns = LLPerfStats::raw_to_ns(tot_frame_time_raw); - auto tot_avatar_time_ns = LLPerfStats::raw_to_ns(tot_avatar_time_raw); - auto tot_huds_time_ns = LLPerfStats::raw_to_ns(tot_huds_time_raw); - // UI time includes HUD time so dedut that before we calc percentages - auto tot_ui_time_ns = LLPerfStats::raw_to_ns(tot_ui_time_raw - tot_huds_time_raw); - - // auto tot_sleep_time_ns = LLPerfStats::raw_to_ns( tot_sleep_time_raw ); - // auto tot_limit_time_ns = LLPerfStats::raw_to_ns( tot_limit_time_raw ); - - // auto tot_render_time_ns = LLPerfStats::raw_to_ns( tot_render_time_raw ); - auto tot_idle_time_ns = LLPerfStats::raw_to_ns(tot_idle_time_raw); - auto tot_swap_time_ns = LLPerfStats::raw_to_ns(tot_swap_time_raw); - auto tot_scene_time_ns = LLPerfStats::raw_to_ns(tot_render_time_raw - tot_avatar_time_raw - tot_swap_time_raw - tot_ui_time_raw); - // auto tot_overhead_time_ns = LLPerfStats::raw_to_ns( tot_frame_time_raw - tot_render_time_raw - tot_idle_time_raw ); - - // // remove time spent sleeping for fps limit or out of focus. - // tot_frame_time_ns -= tot_limit_time_ns; - // tot_frame_time_ns -= tot_sleep_time_ns; - - if (tot_frame_time_ns != 0) - { - auto pct_avatar_time = (tot_avatar_time_ns * 100) / tot_frame_time_ns; - auto pct_huds_time = (tot_huds_time_ns * 100) / tot_frame_time_ns; - auto pct_ui_time = (tot_ui_time_ns * 100) / tot_frame_time_ns; - auto pct_idle_time = (tot_idle_time_ns * 100) / tot_frame_time_ns; - auto pct_swap_time = (tot_swap_time_ns * 100) / tot_frame_time_ns; - auto pct_scene_render_time = (tot_scene_time_ns * 100) / tot_frame_time_ns; - pct_avatar_time = llclamp(pct_avatar_time, 0., 100.); - pct_huds_time = llclamp(pct_huds_time, 0., 100.); - pct_ui_time = llclamp(pct_ui_time, 0., 100.); - pct_idle_time = llclamp(pct_idle_time, 0., 100.); - pct_swap_time = llclamp(pct_swap_time, 0., 100.); - pct_scene_render_time = llclamp(pct_scene_render_time, 0., 100.); - if (tot_sleep_time_raw == 0) - { - sample(LLStatViewer::SCENERY_FRAME_PCT, (U32)llround(pct_scene_render_time)); - sample(LLStatViewer::AVATAR_FRAME_PCT, (U32)llround(pct_avatar_time)); - sample(LLStatViewer::HUDS_FRAME_PCT, (U32)llround(pct_huds_time)); - sample(LLStatViewer::UI_FRAME_PCT, (U32)llround(pct_ui_time)); - sample(LLStatViewer::SWAP_FRAME_PCT, (U32)llround(pct_swap_time)); - sample(LLStatViewer::IDLE_FRAME_PCT, (U32)llround(pct_idle_time)); - } - } - else - { - LL_WARNS("performance") << "Scene time 0. Skipping til we have data." << LL_ENDL; - } - perf_stats_timer.reset(); - } - } -} - -/* - * The sim-side LLSD is in newsim/llagentinfo.cpp:forwardViewerStats. - * - * There's also a compatibility shim for the old fixed-format sim - * stats in newsim/llagentinfo.cpp:processViewerStats. - * - * If you move stats around here, make the corresponding changes in - * those locations, too. - */ -void send_viewer_stats(bool include_preferences) -{ - // IW 9/23/02 I elected not to move this into LLViewerStats - // because it depends on too many viewer.cpp globals. - // Someday we may want to merge all our stats into a central place - // but that day is not today. - - // Only send stats if the agent is connected to a region. - if (!gAgent.getRegion()) - { - return; - } - - LLSD body; - std::string url = gAgent.getRegion()->getCapability("ViewerStats"); - - if (url.empty()) { - LL_WARNS() << "Could not get ViewerStats capability" << LL_ENDL; - return; - } - - LLViewerStats::instance().getRecording().pause(); - - LLSD &agent = body["agent"]; - - time_t ltime; - time(<ime); - F32 run_time = F32(LLFrameTimer::getElapsedSeconds()); - - agent["start_time"] = S32(ltime - S32(run_time)); - - // The first stat set must have a 0 run time if it doesn't actually - // contain useful data in terms of FPS, etc. We use half the - // SEND_STATS_PERIOD seconds as the point at which these statistics become - // valid. Data warehouse uses a 0 value here to easily discard these - // records with non-useful FPS values etc. - if (run_time < (SEND_STATS_PERIOD / 2)) - { - agent["run_time"] = 0.0f; - } - else - { - agent["run_time"] = run_time; - } - - // send fps only for time app spends in foreground - agent["fps"] = (F32)gForegroundFrameCount / gForegroundTime.getElapsedTimeF32(); - agent["version"] = LLVersionInfo::instance().getChannelAndVersion(); - std::string language = LLUI::getLanguage(); - agent["language"] = language; - - agent["sim_fps"] = ((F32) gFrameCount - gSimFrames) / - (F32) (gRenderStartTime.getElapsedTimeF32() - gSimLastTime); - - gSimLastTime = gRenderStartTime.getElapsedTimeF32(); - gSimFrames = (F32) gFrameCount; - - agent["agents_in_view"] = LLVOAvatar::sNumVisibleAvatars; - agent["ping"] = gAvgSimPing.value(); - agent["meters_traveled"] = gAgent.getDistanceTraveled(); - agent["regions_visited"] = gAgent.getRegionsVisited(); - agent["mem_use"] = LLMemory::getCurrentRSS() / 1024.0; - agent["translation"] = LLTranslate::instance().asLLSD(); - - LLSD &system = body["system"]; - - system["ram"] = (S32) gSysMemory.getPhysicalMemoryKB().value(); - system["os"] = LLOSInfo::instance().getOSStringSimple(); - system["cpu"] = gSysCPU.getCPUString(); - system["cpu_sse"] = gSysCPU.getSSEVersions(); - system["address_size"] = ADDRESS_SIZE; - system["os_bitness"] = LLOSInfo::instance().getOSBitness(); - system["hardware_concurrency"] = (LLSD::Integer) std::thread::hardware_concurrency(); - unsigned char MACAddress[MAC_ADDRESS_BYTES]; - LLUUID::getNodeID(MACAddress); - std::string macAddressString = llformat("%02x-%02x-%02x-%02x-%02x-%02x", - MACAddress[0],MACAddress[1],MACAddress[2], - MACAddress[3],MACAddress[4],MACAddress[5]); - system["mac_address"] = macAddressString; - system["serial_number"] = LLAppViewer::instance()->getSerialNumber(); - std::string gpu_desc = llformat( - "%-6s Class %d ", - gGLManager.mGLVendorShort.substr(0,6).c_str(), - (S32)LLFeatureManager::getInstance()->getGPUClass()) - + gGLManager.getRawGLString(); - - system["gpu"] = gpu_desc; - system["gpu_class"] = (S32)LLFeatureManager::getInstance()->getGPUClass(); - system["gpu_memory_bandwidth"] = LLFeatureManager::getInstance()->getGPUMemoryBandwidth(); - system["gpu_vendor"] = gGLManager.mGLVendorShort; - system["gpu_version"] = gGLManager.mDriverVersionVendorString; - system["opengl_version"] = gGLManager.mGLVersionString; - - gGLManager.asLLSD(system["gl"]); - - - S32 shader_level = 0; - if (LLPipeline::sRenderDeferred) - { - if (LLPipeline::RenderShadowDetail > 0) - { - shader_level = 5; - } - else if (LLPipeline::RenderDeferredSSAO) - { - shader_level = 4; - } - else - { - shader_level = 3; - } - } - else - { - shader_level = 2; - } - - - - system["shader_level"] = shader_level; - - LLSD &download = body["downloads"]; - - download["world_kbytes"] = F64Kilobytes(gTotalWorldData).value(); - download["object_kbytes"] = F64Kilobytes(gTotalObjectData).value(); - download["texture_kbytes"] = F64Kilobytes(gTotalTextureData).value(); - download["mesh_kbytes"] = LLMeshRepository::sBytesReceived/1024.0; - - LLSD &in = body["stats"]["net"]["in"]; - - in["kbytes"] = gMessageSystem->mTotalBytesIn / 1024.0; - in["packets"] = (S32) gMessageSystem->mPacketsIn; - in["compressed_packets"] = (S32) gMessageSystem->mCompressedPacketsIn; - in["savings"] = (gMessageSystem->mUncompressedBytesIn - - gMessageSystem->mCompressedBytesIn) / 1024.0; - - LLSD &out = body["stats"]["net"]["out"]; - - out["kbytes"] = gMessageSystem->mTotalBytesOut / 1024.0; - out["packets"] = (S32) gMessageSystem->mPacketsOut; - out["compressed_packets"] = (S32) gMessageSystem->mCompressedPacketsOut; - out["savings"] = (gMessageSystem->mUncompressedBytesOut - - gMessageSystem->mCompressedBytesOut) / 1024.0; - - LLSD &fail = body["stats"]["failures"]; - - fail["send_packet"] = (S32) gMessageSystem->mSendPacketFailureCount; - fail["dropped"] = (S32) gMessageSystem->mDroppedPackets; - fail["resent"] = (S32) gMessageSystem->mResentPackets; - fail["failed_resends"] = (S32) gMessageSystem->mFailedResendPackets; - fail["off_circuit"] = (S32) gMessageSystem->mOffCircuitPackets; - fail["invalid"] = (S32) gMessageSystem->mInvalidOnCircuitPackets; - fail["missing_updater"] = (S32) LLAppViewer::instance()->isUpdaterMissing(); - - LLSD &inventory = body["inventory"]; - inventory["usable"] = gInventory.isInventoryUsable(); - LLSD& validation_info = inventory["validation_info"]; - gInventory.mValidationInfo->asLLSD(validation_info); - - body["ui"] = LLUIUsage::instance().asLLSD(); - - body["stats"]["voice"] = LLVoiceVivoxStats::getInstance()->read(); - - // Misc stats, two strings and two ints - // These are not expecticed to persist across multiple releases - // Comment any changes with your name and the expected release revision - // If the current revision is recent, ping the previous author before overriding - LLSD &misc = body["stats"]["misc"]; - -#ifdef LL_WINDOWS - // Probe for Vulkan capability (Dave Houlton 05/2020) - // - // Check for presense of a Vulkan loader dll, as a proxy for a Vulkan-capable gpu. - // False-positives and false-negatives are possible, but unlikely. We'll get a good - // approximation of Vulkan capability within current user systems from this. More - // detailed information on versions and extensions can come later. - static bool vulkan_oneshot = false; - static bool vulkan_detected = false; - static std::string vulkan_max_api_version( "0.0" ); // Unknown/None - - if (!vulkan_oneshot) - { - // The 32-bit and 64-bit versions normally exist in: - // C:\Windows\System32 - // C:\Windows\SysWOW64 - HMODULE vulkan_loader = LoadLibraryA("vulkan-1.dll"); - if (NULL != vulkan_loader) - { - vulkan_detected = true; - vulkan_max_api_version = "1.0"; // We have at least 1.0. See the note about vkEnumerateInstanceVersion() below. - - // We use Run-Time Dynamic Linking (via GetProcAddress()) instead of Load-Time Dynamic Linking (via directly calling vkGetInstanceProcAddr()). - // This allows us to: - // a) not need the header: #include - // (and not need to set the corresponding "Additional Include Directories" as long as we provide the equivalent Vulkan types/prototypes/etc.) - // b) not need to link to: vulkan-1.lib - // (and not need to set the corresponding "Additional Library Directories") - // The former will allow Second Life to start and run even if the vulkan.dll is missing. - // The latter will require us to: - // a) link with vulkan-1.lib - // b) cause a System Error at startup if the .dll is not found: - // "The code execution cannot proceed because vulkan-1.dll was not found." - // - // See: - // https://docs.microsoft.com/en-us/windows/win32/dlls/using-run-time-dynamic-linking - // https://docs.microsoft.com/en-us/windows/win32/dlls/run-time-dynamic-linking - - // NOTE: Technically we can use GetProcAddress() as a replacement for vkGetInstanceProcAddr() - // but the canonical recommendation (mandate?) is to use vkGetInstanceProcAddr(). - PFN_vkGetInstanceProcAddr pGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr) GetProcAddress(vulkan_loader, "vkGetInstanceProcAddr"); - if(pGetInstanceProcAddr) - { - // Check for vkEnumerateInstanceVersion. If it exists then we have at least 1.1 and can query the max API version. - // NOTE: Each VkPhysicalDevice that supports Vulkan has its own VkPhysicalDeviceProperties.apiVersion which is separate from the max API version! - // See: https://www.lunarg.com/wp-content/uploads/2019/02/Vulkan-1.1-Compatibility-Statement_01_19.pdf - PFN_vkEnumerateInstanceVersion pEnumerateInstanceVersion = (PFN_vkEnumerateInstanceVersion) pGetInstanceProcAddr(NULL, "vkEnumerateInstanceVersion"); - if(pEnumerateInstanceVersion) - { - uint32_t version = VK_MAKE_API_VERSION(0,1,1,0); // e.g. 4202631 = 1.2.135.0 - VkResult status = pEnumerateInstanceVersion( &version ); - if (status != VK_SUCCESS) - { - LL_INFOS("Vulkan") << "Failed to get Vulkan version. Assuming 1.0" << LL_ENDL; - } - else - { - // https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#extendingvulkan-coreversions-versionnumbers - int major = VK_API_VERSION_MAJOR ( version ); - int minor = VK_API_VERSION_MINOR ( version ); - int patch = VK_API_VERSION_PATCH ( version ); - int variant = VK_API_VERSION_VARIANT( version ); - - vulkan_max_api_version = llformat( "%d.%d.%d.%d", major, minor, patch, variant ); - LL_INFOS("Vulkan") << "Vulkan API version: " << vulkan_max_api_version << ", Raw version: " << version << LL_ENDL; - } - } - } - else - { - LL_WARNS("Vulkan") << "FAILED to get Vulkan vkGetInstanceProcAddr()!" << LL_ENDL; - } - FreeLibrary(vulkan_loader); - } - vulkan_oneshot = true; - } - - misc["string_1"] = vulkan_detected ? llformat("Vulkan driver is detected") : llformat("No Vulkan driver detected"); - misc["VulkanMaxApiVersion"] = vulkan_max_api_version; - -#else - misc["string_1"] = llformat("Unused"); -#endif // LL_WINDOWS - - misc["string_2"] = llformat("Unused"); - misc["int_1"] = LLSD::Integer(0); - misc["int_2"] = LLSD::Integer(0); - - LL_INFOS() << "Misc Stats: int_1: " << misc["int_1"] << " int_2: " << misc["int_2"] << LL_ENDL; - LL_INFOS() << "Misc Stats: string_1: " << misc["string_1"] << " string_2: " << misc["string_2"] << LL_ENDL; - - body["DisplayNamesEnabled"] = gSavedSettings.getBOOL("UseDisplayNames"); - body["DisplayNamesShowUsername"] = gSavedSettings.getBOOL("NameTagShowUsernames"); - - // Preferences - if (include_preferences) - { - bool diffs_only = true; // only log preferences that differ from default - body["preferences"]["settings"] = gSavedSettings.asLLSD(diffs_only); - body["preferences"]["settings_per_account"] = gSavedPerAccountSettings.asLLSD(diffs_only); - } - - body["MinimalSkin"] = false; - - - LL_INFOS("LogViewerStatsPacket") << "Sending viewer statistics: " << body << LL_ENDL; - LL_DEBUGS("LogViewerStatsPacket"); - std::string filename("viewer_stats_packet.xml"); - llofstream of(filename.c_str()); - LLSDSerialize::toPrettyXML(body,of); - LL_ENDL; - - // The session ID token must never appear in logs - body["session_id"] = gAgentSessionID; - - LLViewerStats::getInstance()->addToMessage(body); - - LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, body, - "Statistics posted to sim", "Failed to post statistics to sim"); - LLViewerStats::instance().getRecording().resume(); -} - -LLTimer& LLViewerStats::PhaseMap::getPhaseTimer(const std::string& phase_name) -{ - phase_map_t::iterator iter = mPhaseMap.find(phase_name); - if (iter == mPhaseMap.end()) - { - LLTimer timer; - mPhaseMap[phase_name] = timer; - } - LLTimer& timer = mPhaseMap[phase_name]; - return timer; -} - -void LLViewerStats::PhaseMap::startPhase(const std::string& phase_name) -{ - LLTimer& timer = getPhaseTimer(phase_name); - timer.start(); - //LL_DEBUGS("Avatar") << "startPhase " << phase_name << LL_ENDL; -} - -void LLViewerStats::PhaseMap::clearPhases() -{ - //LL_DEBUGS("Avatar") << "clearPhases" << LL_ENDL; - - mPhaseMap.clear(); -} - -LLSD LLViewerStats::PhaseMap::asLLSD() -{ - LLSD result; - for (phase_map_t::iterator iter = mPhaseMap.begin(); iter != mPhaseMap.end(); ++iter) - { - const std::string& phase_name = iter->first; - result[phase_name]["completed"] = LLSD::Integer(!(iter->second.getStarted())); - result[phase_name]["elapsed"] = iter->second.getElapsedTimeF32(); - } - return result; -} - -// static initializer -//static -LLViewerStats::phase_stats_t LLViewerStats::PhaseMap::sStats; - -LLViewerStats::PhaseMap::PhaseMap() -{ -} - -void LLViewerStats::PhaseMap::stopPhase(const std::string& phase_name) -{ - phase_map_t::iterator iter = mPhaseMap.find(phase_name); - if (iter != mPhaseMap.end()) - { - if (iter->second.getStarted()) - { - // Going from started to stopped state - record stats. - iter->second.stop(); - } - } -} - -// static -LLViewerStats::StatsAccumulator& LLViewerStats::PhaseMap::getPhaseStats(const std::string& phase_name) -{ - phase_stats_t::iterator it = sStats.find(phase_name); - if (it == sStats.end()) - { - LLViewerStats::StatsAccumulator new_stats; - sStats[phase_name] = new_stats; - } - return sStats[phase_name]; -} - -// static -void LLViewerStats::PhaseMap::recordPhaseStat(const std::string& phase_name, F32 value) -{ - LLViewerStats::StatsAccumulator& stats = getPhaseStats(phase_name); - stats.push(value); -} - - -bool LLViewerStats::PhaseMap::getPhaseValues(const std::string& phase_name, F32& elapsed, bool& completed) -{ - phase_map_t::iterator iter = mPhaseMap.find(phase_name); - bool found = false; - if (iter != mPhaseMap.end()) - { - found = true; - elapsed = iter->second.getElapsedTimeF32(); - completed = !iter->second.getStarted(); - //LL_DEBUGS("Avatar") << " phase_name " << phase_name << " elapsed " << elapsed << " completed " << completed << " timer addr " << (S32)(&iter->second) << LL_ENDL; - } - else - { - //LL_DEBUGS("Avatar") << " phase_name " << phase_name << " NOT FOUND" << LL_ENDL; - } - - return found; -} +/** + * @file llviewerstats.cpp + * @brief LLViewerStats class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llviewerstats.h" +#include "llviewerthrottle.h" + +#include "message.h" +#include "llfloaterreg.h" +#include "llmemory.h" +#include "lltimer.h" + +#include "llappviewer.h" + +#include "pipeline.h" +#include "lltexturefetch.h" +#include "llviewerobjectlist.h" +#include "llviewertexturelist.h" +#include "lltexlayer.h" +#include "lltexlayerparams.h" +#include "llsurface.h" +#include "llvlmanager.h" +#include "llagent.h" +#include "llagentcamera.h" +#include "llviewercontrol.h" +#include "llversioninfo.h" +#include "llfloatertools.h" +#include "lldebugview.h" +#include "llfasttimerview.h" +#include "llviewerregion.h" +#include "llvoavatar.h" +#include "llvoavatarself.h" +#include "llworld.h" +#include "llfeaturemanager.h" +#include "llviewernetwork.h" +#include "llmeshrepository.h" //for LLMeshRepository::sBytesReceived +#include "llperfstats.h" +#include "llsdserialize.h" +#include "llsdutil.h" +#include "llcorehttputil.h" +#include "llvoicevivox.h" +#include "llinventorymodel.h" +#include "lluiusage.h" +#include "lltranslate.h" + +// "Minimal Vulkan" to get max API Version + +// Calls + #if defined(_WIN32) + #define VKAPI_ATTR + #define VKAPI_CALL __stdcall + #define VKAPI_PTR VKAPI_CALL + #else + #define VKAPI_ATTR + #define VKAPI_CALL + #define VKAPI_PTR + #endif // _WIN32 + +// Macros + // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + // |31|30|29|28|27|26|25|24|23|22|21|20|19|18|17|16|15|14|13|12|11|10| 9| 8| 7| 6| 5| 4| 3| 2| 1| 0| + // +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ + // <-------major-------><-----------minor-----------> <--------------patch--------------> + // 0x7 0x7F 0x3FF 0xFFF + #define VK_API_VERSION_MAJOR( version) (((uint32_t)(version) >> 22) & 0x07FU) // 7 bits + #define VK_API_VERSION_MINOR( version) (((uint32_t)(version) >> 12) & 0x3FFU) // 10 bits + #define VK_API_VERSION_PATCH( version) (((uint32_t)(version) ) & 0xFFFU) // 12 bits + #define VK_API_VERSION_VARIANT(version) (((uint32_t)(version) >> 29) & 0x007U) // 3 bits + + // NOTE: variant is first parameter! This is to match vulkan/vulkan_core.h + #define VK_MAKE_API_VERSION(variant, major, minor, patch) (0\ + | (((uint32_t)(major & 0x07FU)) << 22) \ + | (((uint32_t)(minor & 0x3FFU)) << 12) \ + | (((uint32_t)(patch & 0xFFFU)) ) \ + | (((uint32_t)(variant & 0x007U)) << 29) ) + + #define VK_DEFINE_HANDLE(object) typedef struct object##_T* object; + +// Types + VK_DEFINE_HANDLE(VkInstance); + + typedef enum VkResult + { + VK_SUCCESS = 0, + VK_RESULT_MAX_ENUM = 0x7FFFFFFF + } VkResult; + +// Prototypes + typedef void (VKAPI_PTR *PFN_vkVoidFunction )(void); + typedef PFN_vkVoidFunction (VKAPI_PTR *PFN_vkGetInstanceProcAddr )(VkInstance instance, const char* pName); + typedef VkResult (VKAPI_PTR *PFN_vkEnumerateInstanceVersion)(uint32_t* pApiVersion); + +namespace LLStatViewer +{ + +LLTrace::CountStatHandle<> FPS("FPS", "Frames rendered"), + PACKETS_IN("Packets In", "Packets received"), + PACKETS_LOST("packetsloststat", "Packets lost"), + PACKETS_OUT("packetsoutstat", "Packets sent"), + TEXTURE_PACKETS("texturepacketsstat", "Texture data packets received"), + CHAT_COUNT("chatcount", "Chat messages sent"), + IM_COUNT("imcount", "IMs sent"), + OBJECT_CREATE("objectcreate", "Number of objects created"), + OBJECT_REZ("objectrez", "Object rez count"), + LOGIN_TIMEOUTS("logintimeouts", "Number of login attempts that timed out"), + LSL_SAVES("lslsaves", "Number of times user has saved a script"), + ANIMATION_UPLOADS("animationuploads", "Animations uploaded"), + FLY("fly", "Fly count"), + TELEPORT("teleport", "Teleport count"), + DELETE_OBJECT("deleteobject", "Objects deleted"), + SNAPSHOT("snapshot", "Snapshots taken"), + UPLOAD_SOUND("uploadsound", "Sounds uploaded"), + UPLOAD_TEXTURE("uploadtexture", "Textures uploaded"), + EDIT_TEXTURE("edittexture", "Changes to textures on objects"), + KILLED("killed", "Number of times killed"), + FRAMETIME_DOUBLED("frametimedoubled", "Ratio of frames 2x longer than previous"), + TEX_BAKES("texbakes", "Number of times avatar textures have been baked"), + TEX_REBAKES("texrebakes", "Number of times avatar textures have been forced to rebake"), + NUM_NEW_OBJECTS("numnewobjectsstat", "Number of objects in scene that were not previously in cache"); + +LLTrace::CountStatHandle > + TRIANGLES_DRAWN("trianglesdrawnstat"); + +LLTrace::EventStatHandle > + TRIANGLES_DRAWN_PER_FRAME("trianglesdrawnperframestat"); + +LLTrace::CountStatHandle + ACTIVE_MESSAGE_DATA_RECEIVED("activemessagedatareceived", "Message system data received on all active regions"), + LAYERS_NETWORK_DATA_RECEIVED("layersdatareceived", "Network data received for layer data (terrain)"), + OBJECT_NETWORK_DATA_RECEIVED("objectdatareceived", "Network data received for objects"), + ASSET_UDP_DATA_RECEIVED("assetudpdatareceived", "Network data received for assets (animations, sounds) over UDP message system"), + TEXTURE_NETWORK_DATA_RECEIVED("texturedatareceived", "Network data received for textures"), + MESSAGE_SYSTEM_DATA_IN("messagedatain", "Incoming message system network data"), + MESSAGE_SYSTEM_DATA_OUT("messagedataout", "Outgoing message system network data"); + +LLTrace::CountStatHandle + SIM_20_FPS_TIME("sim20fpstime", "Seconds with sim FPS below 20"), + SIM_PHYSICS_20_FPS_TIME("simphysics20fpstime", "Seconds with physics FPS below 20"), + LOSS_5_PERCENT_TIME("loss5percenttime", "Seconds with packet loss > 5%"); + +SimMeasurement<> SIM_TIME_DILATION("simtimedilation", "Simulator time scale", LL_SIM_STAT_TIME_DILATION), + SIM_FPS("simfps", "Simulator framerate", LL_SIM_STAT_FPS), + SIM_PHYSICS_FPS("simphysicsfps", "Simulator physics framerate", LL_SIM_STAT_PHYSFPS), + SIM_AGENT_UPS("simagentups", "", LL_SIM_STAT_AGENTUPS), + SIM_SCRIPT_EPS("simscripteps", "", LL_SIM_STAT_SCRIPT_EPS), + SIM_SKIPPED_SILHOUETTE("simsimskippedsilhouettesteps", "", LL_SIM_STAT_SKIPPEDAISILSTEPS_PS), + SIM_MAIN_AGENTS("simmainagents", "Number of avatars in current region", LL_SIM_STAT_NUMAGENTMAIN), + SIM_CHILD_AGENTS("simchildagents", "Number of avatars in neighboring regions", LL_SIM_STAT_NUMAGENTCHILD), + SIM_OBJECTS("simobjects", "", LL_SIM_STAT_NUMTASKS), + SIM_ACTIVE_OBJECTS("simactiveobjects", "Number of scripted and/or moving objects", LL_SIM_STAT_NUMTASKSACTIVE), + SIM_ACTIVE_SCRIPTS("simactivescripts", "Number of scripted objects", LL_SIM_STAT_NUMSCRIPTSACTIVE), + SIM_IN_PACKETS_PER_SEC("siminpps", "", LL_SIM_STAT_INPPS), + SIM_OUT_PACKETS_PER_SEC("simoutpps", "", LL_SIM_STAT_OUTPPS), + SIM_PENDING_DOWNLOADS("simpendingdownloads", "", LL_SIM_STAT_PENDING_DOWNLOADS), + SIM_PENDING_UPLOADS("simpendinguploads", "", LL_SIM_STAT_PENDING_UPLOADS), + SIM_PENDING_LOCAL_UPLOADS("simpendinglocaluploads", "", LL_SIM_STAT_PENDING_LOCAL_UPLOADS), + SIM_PHYSICS_PINNED_TASKS("physicspinnedtasks", "", LL_SIM_STAT_PHYSICS_PINNED_TASKS), + SIM_PHYSICS_LOD_TASKS("physicslodtasks", "", LL_SIM_STAT_PHYSICS_LOD_TASKS); + +SimMeasurement > + SIM_PERCENTAGE_SCRIPTS_RUN("simpctscriptsrun", "", LL_SIM_STAT_PCTSCRIPTSRUN), + SIM_SKIPPED_CHARACTERS_PERCENTAGE("simsimpctsteppedcharacters", "", LL_SIM_STAT_PCTSTEPPEDCHARACTERS); + +LLTrace::SampleStatHandle<> FPS_SAMPLE("fpssample"), + NUM_IMAGES("numimagesstat"), + NUM_RAW_IMAGES("numrawimagesstat"), + NUM_MATERIALS("nummaterials"), + NUM_OBJECTS("numobjectsstat"), + NUM_ACTIVE_OBJECTS("numactiveobjectsstat"), + ENABLE_VBO("enablevbo", "Vertex Buffers Enabled"), + VISIBLE_AVATARS("visibleavatars", "Visible Avatars"), + SHADER_OBJECTS("shaderobjects", "Object Shaders"), + DRAW_DISTANCE("drawdistance", "Draw Distance"), + WINDOW_WIDTH("windowwidth", "Window width"), + WINDOW_HEIGHT("windowheight", "Window height"); + +LLTrace::SampleStatHandle > + PACKETS_LOST_PERCENT("packetslostpercentstat"); + +static LLTrace::SampleStatHandle + CHAT_BUBBLES("chatbubbles", "Chat Bubbles Enabled"); + +LLTrace::SampleStatHandle FORMATTED_MEM("formattedmemstat"); +LLTrace::SampleStatHandle DELTA_BANDWIDTH("deltabandwidth", "Increase/Decrease in bandwidth based on packet loss"), + MAX_BANDWIDTH("maxbandwidth", "Max bandwidth setting"); + + +SimMeasurement SIM_FRAME_TIME("simframemsec", "", LL_SIM_STAT_FRAMEMS), + SIM_NET_TIME("simnetmsec", "", LL_SIM_STAT_NETMS), + SIM_OTHER_TIME("simsimothermsec", "", LL_SIM_STAT_SIMOTHERMS), + SIM_PHYSICS_TIME("simsimphysicsmsec", "", LL_SIM_STAT_SIMPHYSICSMS), + SIM_PHYSICS_STEP_TIME("simsimphysicsstepmsec", "", LL_SIM_STAT_SIMPHYSICSSTEPMS), + SIM_PHYSICS_SHAPE_UPDATE_TIME("simsimphysicsshapeupdatemsec", "", LL_SIM_STAT_SIMPHYSICSSHAPEMS), + SIM_PHYSICS_OTHER_TIME("simsimphysicsothermsec", "", LL_SIM_STAT_SIMPHYSICSOTHERMS), + SIM_AI_TIME("simsimaistepmsec", "", LL_SIM_STAT_SIMAISTEPTIMEMS), + SIM_AGENTS_TIME("simagentmsec", "", LL_SIM_STAT_AGENTMS), + SIM_IMAGES_TIME("simimagesmsec", "", LL_SIM_STAT_IMAGESMS), + SIM_SCRIPTS_TIME("simscriptmsec", "", LL_SIM_STAT_SCRIPTMS), + SIM_SPARE_TIME("simsparemsec", "", LL_SIM_STAT_SIMSPARETIME), + SIM_SLEEP_TIME("simsleepmsec", "", LL_SIM_STAT_SIMSLEEPTIME), + SIM_PUMP_IO_TIME("simpumpiomsec", "", LL_SIM_STAT_IOPUMPTIME); + +SimMeasurement SIM_UNACKED_BYTES("simtotalunackedbytes", "", LL_SIM_STAT_TOTAL_UNACKED_BYTES); +SimMeasurement SIM_PHYSICS_MEM("physicsmemoryallocated", "", LL_SIM_STAT_SIMPHYSICSMEMORY); + +LLTrace::SampleStatHandle FRAMETIME_JITTER("frametimejitter", "Average delta between successive frame times"), + FRAMETIME_SLEW("frametimeslew", "Average delta between frame time and mean"), + FRAMETIME("frametime", "Measured frame time"), + SIM_PING("simpingstat"); + +LLTrace::EventStatHandle > AGENT_POSITION_SNAP("agentpositionsnap", "agent position corrections"); + +LLTrace::EventStatHandle<> LOADING_WEARABLES_LONG_DELAY("loadingwearableslongdelay", "Wearables took too long to load"); + +LLTrace::EventStatHandle REGION_CROSSING_TIME("regioncrossingtime", "CROSSING_AVG"), + FRAME_STACKTIME("framestacktime", "FRAME_SECS"), + UPDATE_STACKTIME("updatestacktime", "UPDATE_SECS"), + NETWORK_STACKTIME("networkstacktime", "NETWORK_SECS"), + IMAGE_STACKTIME("imagestacktime", "IMAGE_SECS"), + REBUILD_STACKTIME("rebuildstacktime", "REBUILD_SECS"), + RENDER_STACKTIME("renderstacktime", "RENDER_SECS"); + +LLTrace::EventStatHandle AVATAR_EDIT_TIME("avataredittime", "Seconds in Edit Appearance"), + TOOLBOX_TIME("toolboxtime", "Seconds using Toolbox"), + MOUSELOOK_TIME("mouselooktime", "Seconds in Mouselook"), + FPS_10_TIME("fps10time", "Seconds below 10 FPS"), + FPS_8_TIME("fps8time", "Seconds below 8 FPS"), + FPS_2_TIME("fps2time", "Seconds below 2 FPS"); + +LLTrace::EventStatHandle > OBJECT_CACHE_HIT_RATE("object_cache_hits"); + +LLTrace::EventStatHandle TEXTURE_FETCH_TIME("texture_fetch_time"); + +LLTrace::SampleStatHandle > SCENERY_FRAME_PCT("scenery_frame_pct"); +LLTrace::SampleStatHandle > AVATAR_FRAME_PCT("avatar_frame_pct"); +LLTrace::SampleStatHandle > HUDS_FRAME_PCT("huds_frame_pct"); +LLTrace::SampleStatHandle > UI_FRAME_PCT("ui_frame_pct"); +LLTrace::SampleStatHandle > SWAP_FRAME_PCT("swap_frame_pct"); +LLTrace::SampleStatHandle > IDLE_FRAME_PCT("idle_frame_pct"); +} + +LLViewerStats::LLViewerStats() +: mLastTimeDiff(0.0) +{ + getRecording().start(); +} + +LLViewerStats::~LLViewerStats() +{} + +void LLViewerStats::resetStats() +{ + getRecording().reset(); +} + +void LLViewerStats::updateFrameStats(const F64Seconds time_diff) +{ + if (getRecording().getLastValue(LLStatViewer::PACKETS_LOST_PERCENT) > F32Percent(5.0)) + { + add(LLStatViewer::LOSS_5_PERCENT_TIME, time_diff); + } + + F32 sim_fps = getRecording().getLastValue(LLStatViewer::SIM_FPS); + if (0.f < sim_fps && sim_fps < 20.f) + { + add(LLStatViewer::SIM_20_FPS_TIME, time_diff); + } + + F32 sim_physics_fps = getRecording().getLastValue(LLStatViewer::SIM_PHYSICS_FPS); + + if (0.f < sim_physics_fps && sim_physics_fps < 20.f) + { + add(LLStatViewer::SIM_PHYSICS_20_FPS_TIME, time_diff); + } + + if (time_diff >= (F64Seconds)0.5) + { + record(LLStatViewer::FPS_2_TIME, time_diff); + } + if (time_diff >= (F64Seconds)0.125) + { + record(LLStatViewer::FPS_8_TIME, time_diff); + } + if (time_diff >= (F64Seconds)0.1) + { + record(LLStatViewer::FPS_10_TIME, time_diff); + } + + if (gFrameCount && mLastTimeDiff > (F64Seconds)0.0) + { + // new "stutter" meter + add(LLStatViewer::FRAMETIME_DOUBLED, time_diff >= 2.0 * mLastTimeDiff ? 1 : 0); + + sample(LLStatViewer::FRAMETIME, time_diff); + + // old stats that were never really used + F64Seconds jit = (F64Seconds) std::fabs((mLastTimeDiff - time_diff)); + sample(LLStatViewer::FRAMETIME_JITTER, jit); + + F32Seconds average_frametime = gRenderStartTime.getElapsedTimeF32() / (F32)gFrameCount; + sample(LLStatViewer::FRAMETIME_SLEW, F64Milliseconds (average_frametime - time_diff)); + + F32 max_bandwidth = gViewerThrottle.getMaxBandwidth(); + F32 delta_bandwidth = gViewerThrottle.getCurrentBandwidth() - max_bandwidth; + sample(LLStatViewer::DELTA_BANDWIDTH, F64Bits(delta_bandwidth)); + sample(LLStatViewer::MAX_BANDWIDTH, F64Bits(max_bandwidth)); + } + + mLastTimeDiff = time_diff; +} + +void LLViewerStats::addToMessage(LLSD &body) +{ + LLSD &misc = body["misc"]; + + misc["Version"] = true; + //TODO RN: get last value, not mean + misc["Vertex Buffers Enabled"] = getRecording().getMean(LLStatViewer::ENABLE_VBO); + + body["AgentPositionSnaps"] = getRecording().getSum(LLStatViewer::AGENT_POSITION_SNAP).value(); //mAgentPositionSnaps.asLLSD(); + LL_INFOS() << "STAT: AgentPositionSnaps: Mean = " << getRecording().getMean(LLStatViewer::AGENT_POSITION_SNAP).value() << "; StdDev = " << getRecording().getStandardDeviation(LLStatViewer::AGENT_POSITION_SNAP).value() + << "; Count = " << getRecording().getSampleCount(LLStatViewer::AGENT_POSITION_SNAP) << LL_ENDL; +} + +// *NOTE:Mani The following methods used to exist in viewer.cpp +// Moving them here, but not merging them into LLViewerStats yet. +U32 gTotalLandIn = 0, + gTotalLandOut = 0, + gTotalWaterIn = 0, + gTotalWaterOut = 0; + +F32 gAveLandCompression = 0.f, + gAveWaterCompression = 0.f, + gBestLandCompression = 1.f, + gBestWaterCompression = 1.f, + gWorstLandCompression = 0.f, + gWorstWaterCompression = 0.f; + +U32Bytes gTotalWorldData, + gTotalObjectData, + gTotalTextureData; +U32 gSimPingCount = 0; +U32Bits gObjectData; +F32Milliseconds gAvgSimPing(0.f); +// rely on default initialization +U32Bytes gTotalTextureBytesPerBoostLevel[LLViewerTexture::MAX_GL_IMAGE_CATEGORY]; + +extern U32 gVisCompared; +extern U32 gVisTested; + +void update_statistics() +{ + LL_PROFILE_ZONE_SCOPED; + + gTotalWorldData += gVLManager.getTotalBytes(); + gTotalObjectData += gObjectData; + + // make sure we have a valid time delta for this frame + if (gFrameIntervalSeconds > 0.f) + { + if (gAgentCamera.getCameraMode() == CAMERA_MODE_MOUSELOOK) + { + record(LLStatViewer::MOUSELOOK_TIME, gFrameIntervalSeconds); + } + else if (gAgentCamera.getCameraMode() == CAMERA_MODE_CUSTOMIZE_AVATAR) + { + record(LLStatViewer::AVATAR_EDIT_TIME, gFrameIntervalSeconds); + } + else if (LLFloaterReg::instanceVisible("build")) + { + record(LLStatViewer::TOOLBOX_TIME, gFrameIntervalSeconds); + } + } + + LLTrace::Recording& last_frame_recording = LLTrace::get_frame_recording().getLastRecording(); + + record(LLStatViewer::TRIANGLES_DRAWN_PER_FRAME, last_frame_recording.getSum(LLStatViewer::TRIANGLES_DRAWN)); + + sample(LLStatViewer::ENABLE_VBO, (F64)gSavedSettings.getBOOL("RenderVBOEnable")); + sample(LLStatViewer::DRAW_DISTANCE, (F64)gSavedSettings.getF32("RenderFarClip")); + sample(LLStatViewer::CHAT_BUBBLES, gSavedSettings.getBOOL("UseChatBubbles")); + + typedef LLTrace::StatType::instance_tracker_t stat_type_t; + + record(LLStatViewer::FRAME_STACKTIME, last_frame_recording.getSum(*stat_type_t::getInstance("Frame"))); + + if (gAgent.getRegion() && isAgentAvatarValid()) + { + LLCircuitData *cdp = gMessageSystem->mCircuitInfo.findCircuit(gAgent.getRegion()->getHost()); + if (cdp) + { + sample(LLStatViewer::SIM_PING, F64Milliseconds(cdp->getPingDelay())); + gAvgSimPing = ((gAvgSimPing * gSimPingCount) + cdp->getPingDelay()) / (gSimPingCount + 1); + gSimPingCount++; + } + else + { + sample(LLStatViewer::SIM_PING, U32Seconds(10)); + } + } + + if (LLViewerStats::instance().getRecording().getSum(LLStatViewer::FPS)) + { + sample(LLStatViewer::FPS_SAMPLE, LLTrace::get_frame_recording().getPeriodMeanPerSec(LLStatViewer::FPS)); + } + add(LLStatViewer::FPS, 1); + + F64Bits layer_bits = gVLManager.getLandBits() + gVLManager.getWindBits() + gVLManager.getCloudBits(); + add(LLStatViewer::LAYERS_NETWORK_DATA_RECEIVED, layer_bits); + add(LLStatViewer::OBJECT_NETWORK_DATA_RECEIVED, gObjectData); + add(LLStatViewer::ASSET_UDP_DATA_RECEIVED, F64Bits(gTransferManager.getTransferBitsIn(LLTCT_ASSET))); + gTransferManager.resetTransferBitsIn(LLTCT_ASSET); + + sample(LLStatViewer::VISIBLE_AVATARS, LLVOAvatar::sNumVisibleAvatars); + LLWorld *world = LLWorld::getInstance(); // not LLSingleton + if (world) + { + world->updateNetStats(); + world->requestCacheMisses(); + } + + // Reset all of these values. + gVLManager.resetBitCounts(); + gObjectData = (U32Bytes)0; +// gDecodedBits = 0; + + // Only update texture stats periodically so that they are less noisy + { + static const F32 texture_stats_freq = 10.f; + static LLFrameTimer texture_stats_timer; + if (texture_stats_timer.getElapsedTimeF32() >= texture_stats_freq) + { + gTotalTextureData = LLViewerStats::instance().getRecording().getSum(LLStatViewer::TEXTURE_NETWORK_DATA_RECEIVED); + texture_stats_timer.reset(); + } + } + + if (LLFloaterReg::instanceVisible("scene_load_stats")) + { + static const F32 perf_stats_freq = 1; + static LLFrameTimer perf_stats_timer; + if (perf_stats_timer.getElapsedTimeF32() >= perf_stats_freq) + { + LLStringUtil::format_map_t args; + LLPerfStats::bufferToggleLock.lock(); // prevent toggle for a moment + + auto tot_frame_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_FRAME); + // cumulative avatar time (includes idle processing, attachments and base av) + auto tot_avatar_time_raw = LLPerfStats::us_to_raw(LLVOAvatar::getTotalGPURenderTime()); + // cumulative avatar render specific time (a bit arbitrary as the processing is too.) + // auto tot_av_idle_time_raw = LLPerfStats::StatsRecorder::getSum(AvType, LLPerfStats::StatType_t::RENDER_IDLE); + // auto tot_avatar_render_time_raw = tot_avatar_time_raw - tot_av_idle_time_raw; + // the time spent this frame on the "display()" call. Treated as "tot time rendering" + auto tot_render_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_DISPLAY); + // sleep time is basically forced sleep when window out of focus + auto tot_sleep_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_SLEEP); + // time spent on UI + auto tot_ui_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_UI); + // cumulative time spent rendering HUDS + auto tot_huds_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_HUDS); + // "idle" time. This is the time spent in the idle poll section of the main loop + auto tot_idle_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_IDLE); + // similar to sleep time, induced by FPS limit + //auto tot_limit_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_FPSLIMIT); + // swap time is time spent in swap buffer + auto tot_swap_time_raw = LLPerfStats::StatsRecorder::getSceneStat(LLPerfStats::StatType_t::RENDER_SWAP); + + LLPerfStats::bufferToggleLock.unlock(); + + auto tot_frame_time_ns = LLPerfStats::raw_to_ns(tot_frame_time_raw); + auto tot_avatar_time_ns = LLPerfStats::raw_to_ns(tot_avatar_time_raw); + auto tot_huds_time_ns = LLPerfStats::raw_to_ns(tot_huds_time_raw); + // UI time includes HUD time so dedut that before we calc percentages + auto tot_ui_time_ns = LLPerfStats::raw_to_ns(tot_ui_time_raw - tot_huds_time_raw); + + // auto tot_sleep_time_ns = LLPerfStats::raw_to_ns( tot_sleep_time_raw ); + // auto tot_limit_time_ns = LLPerfStats::raw_to_ns( tot_limit_time_raw ); + + // auto tot_render_time_ns = LLPerfStats::raw_to_ns( tot_render_time_raw ); + auto tot_idle_time_ns = LLPerfStats::raw_to_ns(tot_idle_time_raw); + auto tot_swap_time_ns = LLPerfStats::raw_to_ns(tot_swap_time_raw); + auto tot_scene_time_ns = LLPerfStats::raw_to_ns(tot_render_time_raw - tot_avatar_time_raw - tot_swap_time_raw - tot_ui_time_raw); + // auto tot_overhead_time_ns = LLPerfStats::raw_to_ns( tot_frame_time_raw - tot_render_time_raw - tot_idle_time_raw ); + + // // remove time spent sleeping for fps limit or out of focus. + // tot_frame_time_ns -= tot_limit_time_ns; + // tot_frame_time_ns -= tot_sleep_time_ns; + + if (tot_frame_time_ns != 0) + { + auto pct_avatar_time = (tot_avatar_time_ns * 100) / tot_frame_time_ns; + auto pct_huds_time = (tot_huds_time_ns * 100) / tot_frame_time_ns; + auto pct_ui_time = (tot_ui_time_ns * 100) / tot_frame_time_ns; + auto pct_idle_time = (tot_idle_time_ns * 100) / tot_frame_time_ns; + auto pct_swap_time = (tot_swap_time_ns * 100) / tot_frame_time_ns; + auto pct_scene_render_time = (tot_scene_time_ns * 100) / tot_frame_time_ns; + pct_avatar_time = llclamp(pct_avatar_time, 0., 100.); + pct_huds_time = llclamp(pct_huds_time, 0., 100.); + pct_ui_time = llclamp(pct_ui_time, 0., 100.); + pct_idle_time = llclamp(pct_idle_time, 0., 100.); + pct_swap_time = llclamp(pct_swap_time, 0., 100.); + pct_scene_render_time = llclamp(pct_scene_render_time, 0., 100.); + if (tot_sleep_time_raw == 0) + { + sample(LLStatViewer::SCENERY_FRAME_PCT, (U32)llround(pct_scene_render_time)); + sample(LLStatViewer::AVATAR_FRAME_PCT, (U32)llround(pct_avatar_time)); + sample(LLStatViewer::HUDS_FRAME_PCT, (U32)llround(pct_huds_time)); + sample(LLStatViewer::UI_FRAME_PCT, (U32)llround(pct_ui_time)); + sample(LLStatViewer::SWAP_FRAME_PCT, (U32)llround(pct_swap_time)); + sample(LLStatViewer::IDLE_FRAME_PCT, (U32)llround(pct_idle_time)); + } + } + else + { + LL_WARNS("performance") << "Scene time 0. Skipping til we have data." << LL_ENDL; + } + perf_stats_timer.reset(); + } + } +} + +/* + * The sim-side LLSD is in newsim/llagentinfo.cpp:forwardViewerStats. + * + * There's also a compatibility shim for the old fixed-format sim + * stats in newsim/llagentinfo.cpp:processViewerStats. + * + * If you move stats around here, make the corresponding changes in + * those locations, too. + */ +void send_viewer_stats(bool include_preferences) +{ + // IW 9/23/02 I elected not to move this into LLViewerStats + // because it depends on too many viewer.cpp globals. + // Someday we may want to merge all our stats into a central place + // but that day is not today. + + // Only send stats if the agent is connected to a region. + if (!gAgent.getRegion()) + { + return; + } + + LLSD body; + std::string url = gAgent.getRegion()->getCapability("ViewerStats"); + + if (url.empty()) { + LL_WARNS() << "Could not get ViewerStats capability" << LL_ENDL; + return; + } + + LLViewerStats::instance().getRecording().pause(); + + LLSD &agent = body["agent"]; + + time_t ltime; + time(<ime); + F32 run_time = F32(LLFrameTimer::getElapsedSeconds()); + + agent["start_time"] = S32(ltime - S32(run_time)); + + // The first stat set must have a 0 run time if it doesn't actually + // contain useful data in terms of FPS, etc. We use half the + // SEND_STATS_PERIOD seconds as the point at which these statistics become + // valid. Data warehouse uses a 0 value here to easily discard these + // records with non-useful FPS values etc. + if (run_time < (SEND_STATS_PERIOD / 2)) + { + agent["run_time"] = 0.0f; + } + else + { + agent["run_time"] = run_time; + } + + // send fps only for time app spends in foreground + agent["fps"] = (F32)gForegroundFrameCount / gForegroundTime.getElapsedTimeF32(); + agent["version"] = LLVersionInfo::instance().getChannelAndVersion(); + std::string language = LLUI::getLanguage(); + agent["language"] = language; + + agent["sim_fps"] = ((F32) gFrameCount - gSimFrames) / + (F32) (gRenderStartTime.getElapsedTimeF32() - gSimLastTime); + + gSimLastTime = gRenderStartTime.getElapsedTimeF32(); + gSimFrames = (F32) gFrameCount; + + agent["agents_in_view"] = LLVOAvatar::sNumVisibleAvatars; + agent["ping"] = gAvgSimPing.value(); + agent["meters_traveled"] = gAgent.getDistanceTraveled(); + agent["regions_visited"] = gAgent.getRegionsVisited(); + agent["mem_use"] = LLMemory::getCurrentRSS() / 1024.0; + agent["translation"] = LLTranslate::instance().asLLSD(); + + LLSD &system = body["system"]; + + system["ram"] = (S32) gSysMemory.getPhysicalMemoryKB().value(); + system["os"] = LLOSInfo::instance().getOSStringSimple(); + system["cpu"] = gSysCPU.getCPUString(); + system["cpu_sse"] = gSysCPU.getSSEVersions(); + system["address_size"] = ADDRESS_SIZE; + system["os_bitness"] = LLOSInfo::instance().getOSBitness(); + system["hardware_concurrency"] = (LLSD::Integer) std::thread::hardware_concurrency(); + unsigned char MACAddress[MAC_ADDRESS_BYTES]; + LLUUID::getNodeID(MACAddress); + std::string macAddressString = llformat("%02x-%02x-%02x-%02x-%02x-%02x", + MACAddress[0],MACAddress[1],MACAddress[2], + MACAddress[3],MACAddress[4],MACAddress[5]); + system["mac_address"] = macAddressString; + system["serial_number"] = LLAppViewer::instance()->getSerialNumber(); + std::string gpu_desc = llformat( + "%-6s Class %d ", + gGLManager.mGLVendorShort.substr(0,6).c_str(), + (S32)LLFeatureManager::getInstance()->getGPUClass()) + + gGLManager.getRawGLString(); + + system["gpu"] = gpu_desc; + system["gpu_class"] = (S32)LLFeatureManager::getInstance()->getGPUClass(); + system["gpu_memory_bandwidth"] = LLFeatureManager::getInstance()->getGPUMemoryBandwidth(); + system["gpu_vendor"] = gGLManager.mGLVendorShort; + system["gpu_version"] = gGLManager.mDriverVersionVendorString; + system["opengl_version"] = gGLManager.mGLVersionString; + + gGLManager.asLLSD(system["gl"]); + + + S32 shader_level = 0; + if (LLPipeline::sRenderDeferred) + { + if (LLPipeline::RenderShadowDetail > 0) + { + shader_level = 5; + } + else if (LLPipeline::RenderDeferredSSAO) + { + shader_level = 4; + } + else + { + shader_level = 3; + } + } + else + { + shader_level = 2; + } + + + + system["shader_level"] = shader_level; + + LLSD &download = body["downloads"]; + + download["world_kbytes"] = F64Kilobytes(gTotalWorldData).value(); + download["object_kbytes"] = F64Kilobytes(gTotalObjectData).value(); + download["texture_kbytes"] = F64Kilobytes(gTotalTextureData).value(); + download["mesh_kbytes"] = LLMeshRepository::sBytesReceived/1024.0; + + LLSD &in = body["stats"]["net"]["in"]; + + in["kbytes"] = gMessageSystem->mTotalBytesIn / 1024.0; + in["packets"] = (S32) gMessageSystem->mPacketsIn; + in["compressed_packets"] = (S32) gMessageSystem->mCompressedPacketsIn; + in["savings"] = (gMessageSystem->mUncompressedBytesIn - + gMessageSystem->mCompressedBytesIn) / 1024.0; + + LLSD &out = body["stats"]["net"]["out"]; + + out["kbytes"] = gMessageSystem->mTotalBytesOut / 1024.0; + out["packets"] = (S32) gMessageSystem->mPacketsOut; + out["compressed_packets"] = (S32) gMessageSystem->mCompressedPacketsOut; + out["savings"] = (gMessageSystem->mUncompressedBytesOut - + gMessageSystem->mCompressedBytesOut) / 1024.0; + + LLSD &fail = body["stats"]["failures"]; + + fail["send_packet"] = (S32) gMessageSystem->mSendPacketFailureCount; + fail["dropped"] = (S32) gMessageSystem->mDroppedPackets; + fail["resent"] = (S32) gMessageSystem->mResentPackets; + fail["failed_resends"] = (S32) gMessageSystem->mFailedResendPackets; + fail["off_circuit"] = (S32) gMessageSystem->mOffCircuitPackets; + fail["invalid"] = (S32) gMessageSystem->mInvalidOnCircuitPackets; + fail["missing_updater"] = (S32) LLAppViewer::instance()->isUpdaterMissing(); + + LLSD &inventory = body["inventory"]; + inventory["usable"] = gInventory.isInventoryUsable(); + LLSD& validation_info = inventory["validation_info"]; + gInventory.mValidationInfo->asLLSD(validation_info); + + body["ui"] = LLUIUsage::instance().asLLSD(); + + body["stats"]["voice"] = LLVoiceVivoxStats::getInstance()->read(); + + // Misc stats, two strings and two ints + // These are not expecticed to persist across multiple releases + // Comment any changes with your name and the expected release revision + // If the current revision is recent, ping the previous author before overriding + LLSD &misc = body["stats"]["misc"]; + +#ifdef LL_WINDOWS + // Probe for Vulkan capability (Dave Houlton 05/2020) + // + // Check for presense of a Vulkan loader dll, as a proxy for a Vulkan-capable gpu. + // False-positives and false-negatives are possible, but unlikely. We'll get a good + // approximation of Vulkan capability within current user systems from this. More + // detailed information on versions and extensions can come later. + static bool vulkan_oneshot = false; + static bool vulkan_detected = false; + static std::string vulkan_max_api_version( "0.0" ); // Unknown/None + + if (!vulkan_oneshot) + { + // The 32-bit and 64-bit versions normally exist in: + // C:\Windows\System32 + // C:\Windows\SysWOW64 + HMODULE vulkan_loader = LoadLibraryA("vulkan-1.dll"); + if (NULL != vulkan_loader) + { + vulkan_detected = true; + vulkan_max_api_version = "1.0"; // We have at least 1.0. See the note about vkEnumerateInstanceVersion() below. + + // We use Run-Time Dynamic Linking (via GetProcAddress()) instead of Load-Time Dynamic Linking (via directly calling vkGetInstanceProcAddr()). + // This allows us to: + // a) not need the header: #include + // (and not need to set the corresponding "Additional Include Directories" as long as we provide the equivalent Vulkan types/prototypes/etc.) + // b) not need to link to: vulkan-1.lib + // (and not need to set the corresponding "Additional Library Directories") + // The former will allow Second Life to start and run even if the vulkan.dll is missing. + // The latter will require us to: + // a) link with vulkan-1.lib + // b) cause a System Error at startup if the .dll is not found: + // "The code execution cannot proceed because vulkan-1.dll was not found." + // + // See: + // https://docs.microsoft.com/en-us/windows/win32/dlls/using-run-time-dynamic-linking + // https://docs.microsoft.com/en-us/windows/win32/dlls/run-time-dynamic-linking + + // NOTE: Technically we can use GetProcAddress() as a replacement for vkGetInstanceProcAddr() + // but the canonical recommendation (mandate?) is to use vkGetInstanceProcAddr(). + PFN_vkGetInstanceProcAddr pGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr) GetProcAddress(vulkan_loader, "vkGetInstanceProcAddr"); + if(pGetInstanceProcAddr) + { + // Check for vkEnumerateInstanceVersion. If it exists then we have at least 1.1 and can query the max API version. + // NOTE: Each VkPhysicalDevice that supports Vulkan has its own VkPhysicalDeviceProperties.apiVersion which is separate from the max API version! + // See: https://www.lunarg.com/wp-content/uploads/2019/02/Vulkan-1.1-Compatibility-Statement_01_19.pdf + PFN_vkEnumerateInstanceVersion pEnumerateInstanceVersion = (PFN_vkEnumerateInstanceVersion) pGetInstanceProcAddr(NULL, "vkEnumerateInstanceVersion"); + if(pEnumerateInstanceVersion) + { + uint32_t version = VK_MAKE_API_VERSION(0,1,1,0); // e.g. 4202631 = 1.2.135.0 + VkResult status = pEnumerateInstanceVersion( &version ); + if (status != VK_SUCCESS) + { + LL_INFOS("Vulkan") << "Failed to get Vulkan version. Assuming 1.0" << LL_ENDL; + } + else + { + // https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/vkspec.html#extendingvulkan-coreversions-versionnumbers + int major = VK_API_VERSION_MAJOR ( version ); + int minor = VK_API_VERSION_MINOR ( version ); + int patch = VK_API_VERSION_PATCH ( version ); + int variant = VK_API_VERSION_VARIANT( version ); + + vulkan_max_api_version = llformat( "%d.%d.%d.%d", major, minor, patch, variant ); + LL_INFOS("Vulkan") << "Vulkan API version: " << vulkan_max_api_version << ", Raw version: " << version << LL_ENDL; + } + } + } + else + { + LL_WARNS("Vulkan") << "FAILED to get Vulkan vkGetInstanceProcAddr()!" << LL_ENDL; + } + FreeLibrary(vulkan_loader); + } + vulkan_oneshot = true; + } + + misc["string_1"] = vulkan_detected ? llformat("Vulkan driver is detected") : llformat("No Vulkan driver detected"); + misc["VulkanMaxApiVersion"] = vulkan_max_api_version; + +#else + misc["string_1"] = llformat("Unused"); +#endif // LL_WINDOWS + + misc["string_2"] = llformat("Unused"); + misc["int_1"] = LLSD::Integer(0); + misc["int_2"] = LLSD::Integer(0); + + LL_INFOS() << "Misc Stats: int_1: " << misc["int_1"] << " int_2: " << misc["int_2"] << LL_ENDL; + LL_INFOS() << "Misc Stats: string_1: " << misc["string_1"] << " string_2: " << misc["string_2"] << LL_ENDL; + + body["DisplayNamesEnabled"] = gSavedSettings.getBOOL("UseDisplayNames"); + body["DisplayNamesShowUsername"] = gSavedSettings.getBOOL("NameTagShowUsernames"); + + // Preferences + if (include_preferences) + { + bool diffs_only = true; // only log preferences that differ from default + body["preferences"]["settings"] = gSavedSettings.asLLSD(diffs_only); + body["preferences"]["settings_per_account"] = gSavedPerAccountSettings.asLLSD(diffs_only); + } + + body["MinimalSkin"] = false; + + + LL_INFOS("LogViewerStatsPacket") << "Sending viewer statistics: " << body << LL_ENDL; + LL_DEBUGS("LogViewerStatsPacket"); + std::string filename("viewer_stats_packet.xml"); + llofstream of(filename.c_str()); + LLSDSerialize::toPrettyXML(body,of); + LL_ENDL; + + // The session ID token must never appear in logs + body["session_id"] = gAgentSessionID; + + LLViewerStats::getInstance()->addToMessage(body); + + LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, body, + "Statistics posted to sim", "Failed to post statistics to sim"); + LLViewerStats::instance().getRecording().resume(); +} + +LLTimer& LLViewerStats::PhaseMap::getPhaseTimer(const std::string& phase_name) +{ + phase_map_t::iterator iter = mPhaseMap.find(phase_name); + if (iter == mPhaseMap.end()) + { + LLTimer timer; + mPhaseMap[phase_name] = timer; + } + LLTimer& timer = mPhaseMap[phase_name]; + return timer; +} + +void LLViewerStats::PhaseMap::startPhase(const std::string& phase_name) +{ + LLTimer& timer = getPhaseTimer(phase_name); + timer.start(); + //LL_DEBUGS("Avatar") << "startPhase " << phase_name << LL_ENDL; +} + +void LLViewerStats::PhaseMap::clearPhases() +{ + //LL_DEBUGS("Avatar") << "clearPhases" << LL_ENDL; + + mPhaseMap.clear(); +} + +LLSD LLViewerStats::PhaseMap::asLLSD() +{ + LLSD result; + for (phase_map_t::iterator iter = mPhaseMap.begin(); iter != mPhaseMap.end(); ++iter) + { + const std::string& phase_name = iter->first; + result[phase_name]["completed"] = LLSD::Integer(!(iter->second.getStarted())); + result[phase_name]["elapsed"] = iter->second.getElapsedTimeF32(); + } + return result; +} + +// static initializer +//static +LLViewerStats::phase_stats_t LLViewerStats::PhaseMap::sStats; + +LLViewerStats::PhaseMap::PhaseMap() +{ +} + +void LLViewerStats::PhaseMap::stopPhase(const std::string& phase_name) +{ + phase_map_t::iterator iter = mPhaseMap.find(phase_name); + if (iter != mPhaseMap.end()) + { + if (iter->second.getStarted()) + { + // Going from started to stopped state - record stats. + iter->second.stop(); + } + } +} + +// static +LLViewerStats::StatsAccumulator& LLViewerStats::PhaseMap::getPhaseStats(const std::string& phase_name) +{ + phase_stats_t::iterator it = sStats.find(phase_name); + if (it == sStats.end()) + { + LLViewerStats::StatsAccumulator new_stats; + sStats[phase_name] = new_stats; + } + return sStats[phase_name]; +} + +// static +void LLViewerStats::PhaseMap::recordPhaseStat(const std::string& phase_name, F32 value) +{ + LLViewerStats::StatsAccumulator& stats = getPhaseStats(phase_name); + stats.push(value); +} + + +bool LLViewerStats::PhaseMap::getPhaseValues(const std::string& phase_name, F32& elapsed, bool& completed) +{ + phase_map_t::iterator iter = mPhaseMap.find(phase_name); + bool found = false; + if (iter != mPhaseMap.end()) + { + found = true; + elapsed = iter->second.getElapsedTimeF32(); + completed = !iter->second.getStarted(); + //LL_DEBUGS("Avatar") << " phase_name " << phase_name << " elapsed " << elapsed << " completed " << completed << " timer addr " << (S32)(&iter->second) << LL_ENDL; + } + else + { + //LL_DEBUGS("Avatar") << " phase_name " << phase_name << " NOT FOUND" << LL_ENDL; + } + + return found; +} diff --git a/indra/newview/llviewertexlayer.cpp b/indra/newview/llviewertexlayer.cpp index 6e5afc3652..10380fecee 100644 --- a/indra/newview/llviewertexlayer.cpp +++ b/indra/newview/llviewertexlayer.cpp @@ -1,353 +1,353 @@ -/** - * @file llviewertexlayer.cpp - * @brief Viewer texture layer. Used for avatars. - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llviewertexlayer.h" - -#include "llagent.h" -#include "llimagej2c.h" -#include "llnotificationsutil.h" -#include "llviewerregion.h" -#include "llglslshader.h" -#include "llvoavatarself.h" -#include "pipeline.h" -#include "llviewercontrol.h" - -// runway consolidate -extern std::string self_av_string(); - -//----------------------------------------------------------------------------- -// LLViewerTexLayerSetBuffer -// The composite image that a LLViewerTexLayerSet writes to. Each LLViewerTexLayerSet has one. -//----------------------------------------------------------------------------- - -// static -S32 LLViewerTexLayerSetBuffer::sGLByteCount = 0; - -LLViewerTexLayerSetBuffer::LLViewerTexLayerSetBuffer(LLTexLayerSet* const owner, - S32 width, S32 height) : - // ORDER_LAST => must render these after the hints are created. - LLTexLayerSetBuffer(owner), - LLViewerDynamicTexture(width, height, 4, LLViewerDynamicTexture::ORDER_LAST, false), - mNeedsUpdate(true), - mNumLowresUpdates(0) -{ - mGLTexturep->setNeedsAlphaAndPickMask(false); - - LLViewerTexLayerSetBuffer::sGLByteCount += getSize(); - mNeedsUpdateTimer.start(); -} - -LLViewerTexLayerSetBuffer::~LLViewerTexLayerSetBuffer() -{ - LLViewerTexLayerSetBuffer::sGLByteCount -= getSize(); - destroyGLTexture(); - for( S32 order = 0; order < ORDER_COUNT; order++ ) - { - LLViewerDynamicTexture::sInstances[order].erase(this); // will fail in all but one case. - } -} - -//virtual -S8 LLViewerTexLayerSetBuffer::getType() const -{ - return LLViewerDynamicTexture::LL_TEX_LAYER_SET_BUFFER ; -} - -//virtual -void LLViewerTexLayerSetBuffer::restoreGLTexture() -{ - LLViewerDynamicTexture::restoreGLTexture() ; -} - -//virtual -void LLViewerTexLayerSetBuffer::destroyGLTexture() -{ - LLViewerDynamicTexture::destroyGLTexture() ; -} - -// static -void LLViewerTexLayerSetBuffer::dumpTotalByteCount() -{ - LL_INFOS() << "Composite System GL Buffers: " << (LLViewerTexLayerSetBuffer::sGLByteCount/1024) << "KB" << LL_ENDL; -} - -void LLViewerTexLayerSetBuffer::requestUpdate() -{ - restartUpdateTimer(); - mNeedsUpdate = true; - mNumLowresUpdates = 0; -} - -void LLViewerTexLayerSetBuffer::restartUpdateTimer() -{ - mNeedsUpdateTimer.reset(); - mNeedsUpdateTimer.start(); -} - -// virtual -bool LLViewerTexLayerSetBuffer::needsRender() -{ - llassert(mTexLayerSet->getAvatarAppearance() == gAgentAvatarp); - if (!isAgentAvatarValid()) return false; - - const bool update_now = mNeedsUpdate && isReadyToUpdate(); - - // Don't render if we don't want to (or aren't ready to) update. - if (!update_now) - { - return false; - } - - // Don't render if we're animating our appearance. - if (gAgentAvatarp->getIsAppearanceAnimating()) - { - return false; - } - - // Don't render if we are trying to create a skirt texture but aren't wearing a skirt. - if (gAgentAvatarp->getBakedTE(getViewerTexLayerSet()) == LLAvatarAppearanceDefines::TEX_SKIRT_BAKED && - !gAgentAvatarp->isWearingWearableType(LLWearableType::WT_SKIRT)) - { - return false; - } - - // Render if we have at least minimal level of detail for each local texture. - return getViewerTexLayerSet()->isLocalTextureDataAvailable(); -} - -// virtual -void LLViewerTexLayerSetBuffer::preRenderTexLayerSet() -{ - LLTexLayerSetBuffer::preRenderTexLayerSet(); - - // keep depth buffer, we don't need to clear it - LLViewerDynamicTexture::preRender(false); -} - -// virtual -void LLViewerTexLayerSetBuffer::postRenderTexLayerSet(bool success) -{ - - LLTexLayerSetBuffer::postRenderTexLayerSet(success); - LLViewerDynamicTexture::postRender(success); -} - -// virtual -void LLViewerTexLayerSetBuffer::midRenderTexLayerSet(bool success) -{ - const bool update_now = mNeedsUpdate && isReadyToUpdate(); - if (update_now) - { - doUpdate(); - } - - // *TODO: Old logic does not check success before setGLTextureCreated - // we have valid texture data now - mGLTexturep->setGLTextureCreated(true); -} - -bool LLViewerTexLayerSetBuffer::isInitialized(void) const -{ - return mGLTexturep.notNull() && mGLTexturep->isGLTextureCreated(); -} - -bool LLViewerTexLayerSetBuffer::isReadyToUpdate() const -{ - // If we requested an update and have the final LOD ready, then update. - if (getViewerTexLayerSet()->isLocalTextureDataFinal()) return true; - - // If we haven't done an update yet, then just do one now regardless of state of textures. - if (mNumLowresUpdates == 0) return true; - - // Update if we've hit a timeout. Unlike for uploads, we can make this timeout fairly small - // since render unnecessarily doesn't cost much. - const U32 TEXTURE_TIMEOUT = 10; - if (TEXTURE_TIMEOUT != 0) - { - // If we hit our timeout and have textures available at even lower resolution, then update. - const bool is_update_textures_timeout = mNeedsUpdateTimer.getElapsedTimeF32() >= TEXTURE_TIMEOUT; - const bool has_lower_lod = getViewerTexLayerSet()->isLocalTextureDataAvailable(); - if (has_lower_lod && is_update_textures_timeout) return true; - } - - return false; -} - -bool LLViewerTexLayerSetBuffer::requestUpdateImmediate() -{ - mNeedsUpdate = true; - bool result = false; - - if (needsRender()) - { - preRender(false); - result = render(); - postRender(result); - } - - return result; -} - -// Mostly bookkeeping; don't need to actually "do" anything since -// render() will actually do the update. -void LLViewerTexLayerSetBuffer::doUpdate() -{ - LLViewerTexLayerSet* layer_set = getViewerTexLayerSet(); - const bool highest_lod = layer_set->isLocalTextureDataFinal(); - if (highest_lod) - { - mNeedsUpdate = false; - } - else - { - mNumLowresUpdates++; - } - - restartUpdateTimer(); - - // need to switch to using this layerset if this is the first update - // after getting the lowest LOD - layer_set->getAvatar()->updateMeshTextures(); - - // Print out notification that we updated this texture. - if (gSavedSettings.getBOOL("DebugAvatarRezTime")) - { - const bool highest_lod = layer_set->isLocalTextureDataFinal(); - const std::string lod_str = highest_lod ? "HighRes" : "LowRes"; - LLSD args; - args["EXISTENCE"] = llformat("%d",(U32)layer_set->getAvatar()->debugGetExistenceTimeElapsedF32()); - args["TIME"] = llformat("%d",(U32)mNeedsUpdateTimer.getElapsedTimeF32()); - args["BODYREGION"] = layer_set->getBodyRegionName(); - args["RESOLUTION"] = lod_str; - LLNotificationsUtil::add("AvatarRezSelfBakedTextureUpdateNotification",args); - LL_DEBUGS("Avatar") << self_av_string() << "Locally updating [ name: " << layer_set->getBodyRegionName() << " res:" << lod_str << " time:" << (U32)mNeedsUpdateTimer.getElapsedTimeF32() << " ]" << LL_ENDL; - } -} - -//----------------------------------------------------------------------------- -// LLViewerTexLayerSet -// An ordered set of texture layers that get composited into a single texture. -//----------------------------------------------------------------------------- - -LLViewerTexLayerSet::LLViewerTexLayerSet(LLAvatarAppearance* const appearance) : - LLTexLayerSet(appearance), - mUpdatesEnabled( false ) -{ -} - -// virtual -LLViewerTexLayerSet::~LLViewerTexLayerSet() -{ -} - -// Returns true if at least one packet of data has been received for each of the textures that this layerset depends on. -bool LLViewerTexLayerSet::isLocalTextureDataAvailable() const -{ - if (!mAvatarAppearance->isSelf()) return false; - return getAvatar()->isLocalTextureDataAvailable(this); -} - - -// Returns true if all of the data for the textures that this layerset depends on have arrived. -bool LLViewerTexLayerSet::isLocalTextureDataFinal() const -{ - if (!mAvatarAppearance->isSelf()) return false; - return getAvatar()->isLocalTextureDataFinal(this); -} - -// virtual -void LLViewerTexLayerSet::requestUpdate() -{ - if( mUpdatesEnabled ) - { - createComposite(); - getViewerComposite()->requestUpdate(); - } -} - -void LLViewerTexLayerSet::updateComposite() -{ - createComposite(); - getViewerComposite()->requestUpdateImmediate(); -} - -// virtual -void LLViewerTexLayerSet::createComposite() -{ - if(!mComposite) - { - S32 width = mInfo->getWidth(); - S32 height = mInfo->getHeight(); - // Composite other avatars at reduced resolution - if( !mAvatarAppearance->isSelf() ) - { - LL_ERRS() << "composites should not be created for non-self avatars!" << LL_ENDL; - } - mComposite = new LLViewerTexLayerSetBuffer( this, width, height ); - } -} - -void LLViewerTexLayerSet::setUpdatesEnabled( bool b ) -{ - mUpdatesEnabled = b; -} - -LLVOAvatarSelf* LLViewerTexLayerSet::getAvatar() -{ - return dynamic_cast (mAvatarAppearance); -} - -const LLVOAvatarSelf* LLViewerTexLayerSet::getAvatar() const -{ - return dynamic_cast (mAvatarAppearance); -} - -LLViewerTexLayerSetBuffer* LLViewerTexLayerSet::getViewerComposite() -{ - return dynamic_cast (getComposite()); -} - -const LLViewerTexLayerSetBuffer* LLViewerTexLayerSet::getViewerComposite() const -{ - return dynamic_cast (getComposite()); -} - - -const std::string LLViewerTexLayerSetBuffer::dumpTextureInfo() const -{ - if (!isAgentAvatarValid()) return ""; - - const bool is_high_res = true; - const U32 num_low_res = 0; - const std::string local_texture_info = gAgentAvatarp->debugDumpLocalTextureDataInfo(getViewerTexLayerSet()); - - std::string text = llformat("[HiRes:%d LoRes:%d] %s", - is_high_res, num_low_res, - local_texture_info.c_str()); - return text; -} +/** + * @file llviewertexlayer.cpp + * @brief Viewer texture layer. Used for avatars. + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llviewertexlayer.h" + +#include "llagent.h" +#include "llimagej2c.h" +#include "llnotificationsutil.h" +#include "llviewerregion.h" +#include "llglslshader.h" +#include "llvoavatarself.h" +#include "pipeline.h" +#include "llviewercontrol.h" + +// runway consolidate +extern std::string self_av_string(); + +//----------------------------------------------------------------------------- +// LLViewerTexLayerSetBuffer +// The composite image that a LLViewerTexLayerSet writes to. Each LLViewerTexLayerSet has one. +//----------------------------------------------------------------------------- + +// static +S32 LLViewerTexLayerSetBuffer::sGLByteCount = 0; + +LLViewerTexLayerSetBuffer::LLViewerTexLayerSetBuffer(LLTexLayerSet* const owner, + S32 width, S32 height) : + // ORDER_LAST => must render these after the hints are created. + LLTexLayerSetBuffer(owner), + LLViewerDynamicTexture(width, height, 4, LLViewerDynamicTexture::ORDER_LAST, false), + mNeedsUpdate(true), + mNumLowresUpdates(0) +{ + mGLTexturep->setNeedsAlphaAndPickMask(false); + + LLViewerTexLayerSetBuffer::sGLByteCount += getSize(); + mNeedsUpdateTimer.start(); +} + +LLViewerTexLayerSetBuffer::~LLViewerTexLayerSetBuffer() +{ + LLViewerTexLayerSetBuffer::sGLByteCount -= getSize(); + destroyGLTexture(); + for( S32 order = 0; order < ORDER_COUNT; order++ ) + { + LLViewerDynamicTexture::sInstances[order].erase(this); // will fail in all but one case. + } +} + +//virtual +S8 LLViewerTexLayerSetBuffer::getType() const +{ + return LLViewerDynamicTexture::LL_TEX_LAYER_SET_BUFFER ; +} + +//virtual +void LLViewerTexLayerSetBuffer::restoreGLTexture() +{ + LLViewerDynamicTexture::restoreGLTexture() ; +} + +//virtual +void LLViewerTexLayerSetBuffer::destroyGLTexture() +{ + LLViewerDynamicTexture::destroyGLTexture() ; +} + +// static +void LLViewerTexLayerSetBuffer::dumpTotalByteCount() +{ + LL_INFOS() << "Composite System GL Buffers: " << (LLViewerTexLayerSetBuffer::sGLByteCount/1024) << "KB" << LL_ENDL; +} + +void LLViewerTexLayerSetBuffer::requestUpdate() +{ + restartUpdateTimer(); + mNeedsUpdate = true; + mNumLowresUpdates = 0; +} + +void LLViewerTexLayerSetBuffer::restartUpdateTimer() +{ + mNeedsUpdateTimer.reset(); + mNeedsUpdateTimer.start(); +} + +// virtual +bool LLViewerTexLayerSetBuffer::needsRender() +{ + llassert(mTexLayerSet->getAvatarAppearance() == gAgentAvatarp); + if (!isAgentAvatarValid()) return false; + + const bool update_now = mNeedsUpdate && isReadyToUpdate(); + + // Don't render if we don't want to (or aren't ready to) update. + if (!update_now) + { + return false; + } + + // Don't render if we're animating our appearance. + if (gAgentAvatarp->getIsAppearanceAnimating()) + { + return false; + } + + // Don't render if we are trying to create a skirt texture but aren't wearing a skirt. + if (gAgentAvatarp->getBakedTE(getViewerTexLayerSet()) == LLAvatarAppearanceDefines::TEX_SKIRT_BAKED && + !gAgentAvatarp->isWearingWearableType(LLWearableType::WT_SKIRT)) + { + return false; + } + + // Render if we have at least minimal level of detail for each local texture. + return getViewerTexLayerSet()->isLocalTextureDataAvailable(); +} + +// virtual +void LLViewerTexLayerSetBuffer::preRenderTexLayerSet() +{ + LLTexLayerSetBuffer::preRenderTexLayerSet(); + + // keep depth buffer, we don't need to clear it + LLViewerDynamicTexture::preRender(false); +} + +// virtual +void LLViewerTexLayerSetBuffer::postRenderTexLayerSet(bool success) +{ + + LLTexLayerSetBuffer::postRenderTexLayerSet(success); + LLViewerDynamicTexture::postRender(success); +} + +// virtual +void LLViewerTexLayerSetBuffer::midRenderTexLayerSet(bool success) +{ + const bool update_now = mNeedsUpdate && isReadyToUpdate(); + if (update_now) + { + doUpdate(); + } + + // *TODO: Old logic does not check success before setGLTextureCreated + // we have valid texture data now + mGLTexturep->setGLTextureCreated(true); +} + +bool LLViewerTexLayerSetBuffer::isInitialized(void) const +{ + return mGLTexturep.notNull() && mGLTexturep->isGLTextureCreated(); +} + +bool LLViewerTexLayerSetBuffer::isReadyToUpdate() const +{ + // If we requested an update and have the final LOD ready, then update. + if (getViewerTexLayerSet()->isLocalTextureDataFinal()) return true; + + // If we haven't done an update yet, then just do one now regardless of state of textures. + if (mNumLowresUpdates == 0) return true; + + // Update if we've hit a timeout. Unlike for uploads, we can make this timeout fairly small + // since render unnecessarily doesn't cost much. + const U32 TEXTURE_TIMEOUT = 10; + if (TEXTURE_TIMEOUT != 0) + { + // If we hit our timeout and have textures available at even lower resolution, then update. + const bool is_update_textures_timeout = mNeedsUpdateTimer.getElapsedTimeF32() >= TEXTURE_TIMEOUT; + const bool has_lower_lod = getViewerTexLayerSet()->isLocalTextureDataAvailable(); + if (has_lower_lod && is_update_textures_timeout) return true; + } + + return false; +} + +bool LLViewerTexLayerSetBuffer::requestUpdateImmediate() +{ + mNeedsUpdate = true; + bool result = false; + + if (needsRender()) + { + preRender(false); + result = render(); + postRender(result); + } + + return result; +} + +// Mostly bookkeeping; don't need to actually "do" anything since +// render() will actually do the update. +void LLViewerTexLayerSetBuffer::doUpdate() +{ + LLViewerTexLayerSet* layer_set = getViewerTexLayerSet(); + const bool highest_lod = layer_set->isLocalTextureDataFinal(); + if (highest_lod) + { + mNeedsUpdate = false; + } + else + { + mNumLowresUpdates++; + } + + restartUpdateTimer(); + + // need to switch to using this layerset if this is the first update + // after getting the lowest LOD + layer_set->getAvatar()->updateMeshTextures(); + + // Print out notification that we updated this texture. + if (gSavedSettings.getBOOL("DebugAvatarRezTime")) + { + const bool highest_lod = layer_set->isLocalTextureDataFinal(); + const std::string lod_str = highest_lod ? "HighRes" : "LowRes"; + LLSD args; + args["EXISTENCE"] = llformat("%d",(U32)layer_set->getAvatar()->debugGetExistenceTimeElapsedF32()); + args["TIME"] = llformat("%d",(U32)mNeedsUpdateTimer.getElapsedTimeF32()); + args["BODYREGION"] = layer_set->getBodyRegionName(); + args["RESOLUTION"] = lod_str; + LLNotificationsUtil::add("AvatarRezSelfBakedTextureUpdateNotification",args); + LL_DEBUGS("Avatar") << self_av_string() << "Locally updating [ name: " << layer_set->getBodyRegionName() << " res:" << lod_str << " time:" << (U32)mNeedsUpdateTimer.getElapsedTimeF32() << " ]" << LL_ENDL; + } +} + +//----------------------------------------------------------------------------- +// LLViewerTexLayerSet +// An ordered set of texture layers that get composited into a single texture. +//----------------------------------------------------------------------------- + +LLViewerTexLayerSet::LLViewerTexLayerSet(LLAvatarAppearance* const appearance) : + LLTexLayerSet(appearance), + mUpdatesEnabled( false ) +{ +} + +// virtual +LLViewerTexLayerSet::~LLViewerTexLayerSet() +{ +} + +// Returns true if at least one packet of data has been received for each of the textures that this layerset depends on. +bool LLViewerTexLayerSet::isLocalTextureDataAvailable() const +{ + if (!mAvatarAppearance->isSelf()) return false; + return getAvatar()->isLocalTextureDataAvailable(this); +} + + +// Returns true if all of the data for the textures that this layerset depends on have arrived. +bool LLViewerTexLayerSet::isLocalTextureDataFinal() const +{ + if (!mAvatarAppearance->isSelf()) return false; + return getAvatar()->isLocalTextureDataFinal(this); +} + +// virtual +void LLViewerTexLayerSet::requestUpdate() +{ + if( mUpdatesEnabled ) + { + createComposite(); + getViewerComposite()->requestUpdate(); + } +} + +void LLViewerTexLayerSet::updateComposite() +{ + createComposite(); + getViewerComposite()->requestUpdateImmediate(); +} + +// virtual +void LLViewerTexLayerSet::createComposite() +{ + if(!mComposite) + { + S32 width = mInfo->getWidth(); + S32 height = mInfo->getHeight(); + // Composite other avatars at reduced resolution + if( !mAvatarAppearance->isSelf() ) + { + LL_ERRS() << "composites should not be created for non-self avatars!" << LL_ENDL; + } + mComposite = new LLViewerTexLayerSetBuffer( this, width, height ); + } +} + +void LLViewerTexLayerSet::setUpdatesEnabled( bool b ) +{ + mUpdatesEnabled = b; +} + +LLVOAvatarSelf* LLViewerTexLayerSet::getAvatar() +{ + return dynamic_cast (mAvatarAppearance); +} + +const LLVOAvatarSelf* LLViewerTexLayerSet::getAvatar() const +{ + return dynamic_cast (mAvatarAppearance); +} + +LLViewerTexLayerSetBuffer* LLViewerTexLayerSet::getViewerComposite() +{ + return dynamic_cast (getComposite()); +} + +const LLViewerTexLayerSetBuffer* LLViewerTexLayerSet::getViewerComposite() const +{ + return dynamic_cast (getComposite()); +} + + +const std::string LLViewerTexLayerSetBuffer::dumpTextureInfo() const +{ + if (!isAgentAvatarValid()) return ""; + + const bool is_high_res = true; + const U32 num_low_res = 0; + const std::string local_texture_info = gAgentAvatarp->debugDumpLocalTextureDataInfo(getViewerTexLayerSet()); + + std::string text = llformat("[HiRes:%d LoRes:%d] %s", + is_high_res, num_low_res, + local_texture_info.c_str()); + return text; +} diff --git a/indra/newview/llviewertexlayer.h b/indra/newview/llviewertexlayer.h index 31d9b006c1..6f95fcac6f 100644 --- a/indra/newview/llviewertexlayer.h +++ b/indra/newview/llviewertexlayer.h @@ -1,133 +1,133 @@ -/** - * @file llviewertexlayer.h - * @brief Viewer Texture layer classes. Used for avatars. - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_VIEWER_TEXLAYER_H -#define LL_VIEWER_TEXLAYER_H - -#include "lldynamictexture.h" -#include "llextendedstatus.h" -#include "lltexlayer.h" - -class LLVOAvatarSelf; -class LLViewerTexLayerSetBuffer; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// LLViewerTexLayerSet -// -// An ordered set of texture layers that gets composited into a single texture. -// Only exists for llavatarappearanceself. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLViewerTexLayerSet : public LLTexLayerSet -{ -public: - LLViewerTexLayerSet(LLAvatarAppearance* const appearance); - virtual ~LLViewerTexLayerSet(); - - /*virtual*/void requestUpdate(); - bool isLocalTextureDataAvailable() const; - bool isLocalTextureDataFinal() const; - void updateComposite(); - /*virtual*/void createComposite(); - void setUpdatesEnabled(bool b); - bool getUpdatesEnabled() const { return mUpdatesEnabled; } - - LLVOAvatarSelf* getAvatar(); - const LLVOAvatarSelf* getAvatar() const; - LLViewerTexLayerSetBuffer* getViewerComposite(); - const LLViewerTexLayerSetBuffer* getViewerComposite() const; - -private: - bool mUpdatesEnabled; - -}; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// LLViewerTexLayerSetBuffer -// -// The composite image that a LLViewerTexLayerSet writes to. Each LLViewerTexLayerSet has one. -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLViewerTexLayerSetBuffer : public LLTexLayerSetBuffer, public LLViewerDynamicTexture -{ - LOG_CLASS(LLViewerTexLayerSetBuffer); - -public: - LLViewerTexLayerSetBuffer(LLTexLayerSet* const owner, S32 width, S32 height); - virtual ~LLViewerTexLayerSetBuffer(); - -public: - /*virtual*/ S8 getType() const; - bool isInitialized(void) const; - static void dumpTotalByteCount(); - const std::string dumpTextureInfo() const; - virtual void restoreGLTexture(); - virtual void destroyGLTexture(); -private: - LLViewerTexLayerSet* getViewerTexLayerSet() - { return dynamic_cast (mTexLayerSet); } - const LLViewerTexLayerSet* getViewerTexLayerSet() const - { return dynamic_cast (mTexLayerSet); } - static S32 sGLByteCount; - - //-------------------------------------------------------------------- - // Tex Layer Render - //-------------------------------------------------------------------- - virtual void preRenderTexLayerSet(); - virtual void midRenderTexLayerSet(bool success); - virtual void postRenderTexLayerSet(bool success); - virtual S32 getCompositeOriginX() const { return getOriginX(); } - virtual S32 getCompositeOriginY() const { return getOriginY(); } - virtual S32 getCompositeWidth() const { return getFullWidth(); } - virtual S32 getCompositeHeight() const { return getFullHeight(); } - - //-------------------------------------------------------------------- - // Dynamic Texture Interface - //-------------------------------------------------------------------- -public: - /*virtual*/ bool needsRender(); -protected: - // Pass these along for tex layer rendering. - virtual void preRender(bool clear_depth) { preRenderTexLayerSet(); } - virtual void postRender(bool success) { postRenderTexLayerSet(success); } - virtual bool render() { return renderTexLayerSet(mBoundTarget); } - - //-------------------------------------------------------------------- - // Updates - //-------------------------------------------------------------------- -public: - void requestUpdate(); - bool requestUpdateImmediate(); -protected: - bool isReadyToUpdate() const; - void doUpdate(); - void restartUpdateTimer(); -private: - bool mNeedsUpdate; // Whether we need to locally update our baked textures - U32 mNumLowresUpdates; // Number of times we've locally updated with lowres version of our baked textures - LLFrameTimer mNeedsUpdateTimer; // Tracks time since update was requested and performed. -}; - -#endif // LL_VIEWER_TEXLAYER_H - +/** + * @file llviewertexlayer.h + * @brief Viewer Texture layer classes. Used for avatars. + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_VIEWER_TEXLAYER_H +#define LL_VIEWER_TEXLAYER_H + +#include "lldynamictexture.h" +#include "llextendedstatus.h" +#include "lltexlayer.h" + +class LLVOAvatarSelf; +class LLViewerTexLayerSetBuffer; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// LLViewerTexLayerSet +// +// An ordered set of texture layers that gets composited into a single texture. +// Only exists for llavatarappearanceself. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLViewerTexLayerSet : public LLTexLayerSet +{ +public: + LLViewerTexLayerSet(LLAvatarAppearance* const appearance); + virtual ~LLViewerTexLayerSet(); + + /*virtual*/void requestUpdate(); + bool isLocalTextureDataAvailable() const; + bool isLocalTextureDataFinal() const; + void updateComposite(); + /*virtual*/void createComposite(); + void setUpdatesEnabled(bool b); + bool getUpdatesEnabled() const { return mUpdatesEnabled; } + + LLVOAvatarSelf* getAvatar(); + const LLVOAvatarSelf* getAvatar() const; + LLViewerTexLayerSetBuffer* getViewerComposite(); + const LLViewerTexLayerSetBuffer* getViewerComposite() const; + +private: + bool mUpdatesEnabled; + +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// LLViewerTexLayerSetBuffer +// +// The composite image that a LLViewerTexLayerSet writes to. Each LLViewerTexLayerSet has one. +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLViewerTexLayerSetBuffer : public LLTexLayerSetBuffer, public LLViewerDynamicTexture +{ + LOG_CLASS(LLViewerTexLayerSetBuffer); + +public: + LLViewerTexLayerSetBuffer(LLTexLayerSet* const owner, S32 width, S32 height); + virtual ~LLViewerTexLayerSetBuffer(); + +public: + /*virtual*/ S8 getType() const; + bool isInitialized(void) const; + static void dumpTotalByteCount(); + const std::string dumpTextureInfo() const; + virtual void restoreGLTexture(); + virtual void destroyGLTexture(); +private: + LLViewerTexLayerSet* getViewerTexLayerSet() + { return dynamic_cast (mTexLayerSet); } + const LLViewerTexLayerSet* getViewerTexLayerSet() const + { return dynamic_cast (mTexLayerSet); } + static S32 sGLByteCount; + + //-------------------------------------------------------------------- + // Tex Layer Render + //-------------------------------------------------------------------- + virtual void preRenderTexLayerSet(); + virtual void midRenderTexLayerSet(bool success); + virtual void postRenderTexLayerSet(bool success); + virtual S32 getCompositeOriginX() const { return getOriginX(); } + virtual S32 getCompositeOriginY() const { return getOriginY(); } + virtual S32 getCompositeWidth() const { return getFullWidth(); } + virtual S32 getCompositeHeight() const { return getFullHeight(); } + + //-------------------------------------------------------------------- + // Dynamic Texture Interface + //-------------------------------------------------------------------- +public: + /*virtual*/ bool needsRender(); +protected: + // Pass these along for tex layer rendering. + virtual void preRender(bool clear_depth) { preRenderTexLayerSet(); } + virtual void postRender(bool success) { postRenderTexLayerSet(success); } + virtual bool render() { return renderTexLayerSet(mBoundTarget); } + + //-------------------------------------------------------------------- + // Updates + //-------------------------------------------------------------------- +public: + void requestUpdate(); + bool requestUpdateImmediate(); +protected: + bool isReadyToUpdate() const; + void doUpdate(); + void restartUpdateTimer(); +private: + bool mNeedsUpdate; // Whether we need to locally update our baked textures + U32 mNumLowresUpdates; // Number of times we've locally updated with lowres version of our baked textures + LLFrameTimer mNeedsUpdateTimer; // Tracks time since update was requested and performed. +}; + +#endif // LL_VIEWER_TEXLAYER_H + diff --git a/indra/newview/llviewertexteditor.cpp b/indra/newview/llviewertexteditor.cpp index bb8b5dc837..82d0caecb5 100644 --- a/indra/newview/llviewertexteditor.cpp +++ b/indra/newview/llviewertexteditor.cpp @@ -1,1383 +1,1383 @@ -/** - * @file llviewertexteditor.cpp - * @brief Text editor widget to let users enter a multi-line document. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llviewertexteditor.h" - -#include "llagent.h" -#include "llaudioengine.h" -#include "llavataractions.h" -#include "llenvironment.h" -#include "llfloaterreg.h" -#include "llfloatersidepanelcontainer.h" -#include "llfloaterworldmap.h" -#include "llfocusmgr.h" -#include "llinspecttexture.h" -#include "llinventorybridge.h" -#include "llinventorydefines.h" -#include "llinventorymodel.h" -#include "lllandmark.h" -#include "lllandmarkactions.h" -#include "lllandmarklist.h" -#include "llmaterialeditor.h" -#include "llmemorystream.h" -#include "llmenugl.h" -#include "llnotecard.h" -#include "llnotificationsutil.h" -#include "llpanelplaces.h" -#include "llpreview.h" -#include "llpreviewnotecard.h" -#include "llpreviewtexture.h" -#include "llscrollbar.h" -#include "llscrollcontainer.h" -#include "lltooldraganddrop.h" -#include "lltooltip.h" -#include "lltrans.h" -#include "lluictrlfactory.h" -#include "llviewerassettype.h" -#include "llviewercontrol.h" -#include "llviewerinventory.h" -#include "llviewertexturelist.h" -#include "llviewerwindow.h" - -static LLDefaultChildRegistry::Register r("text_editor"); - -///----------------------------------------------------------------------- -/// Class LLEmbeddedLandmarkCopied -///----------------------------------------------------------------------- -class LLEmbeddedLandmarkCopied: public LLInventoryCallback -{ -public: - - LLEmbeddedLandmarkCopied(){} - void fire(const LLUUID& inv_item) - { - showInfo(inv_item); - } - static void showInfo(const LLUUID& landmark_inv_id) - { - LLSD key; - key["type"] = "landmark"; - key["id"] = landmark_inv_id; - LLFloaterSidePanelContainer::showPanel("places", key); - } - static void processForeignLandmark(LLLandmark* landmark, - const LLUUID& object_id, const LLUUID& notecard_inventory_id, - LLPointer item_ptr) - { - LLVector3d global_pos; - landmark->getGlobalPos(global_pos); - LLViewerInventoryItem* agent_landmark = - LLLandmarkActions::findLandmarkForGlobalPos(global_pos); - - if (agent_landmark) - { - showInfo(agent_landmark->getUUID()); - } - else - { - if (item_ptr.isNull()) - { - // check to prevent a crash. See EXT-8459. - LL_WARNS() << "Passed handle contains a dead inventory item. Most likely notecard has been closed and embedded item was destroyed." << LL_ENDL; - } - else - { - LLInventoryItem* item = item_ptr.get(); - LLPointer cb = new LLEmbeddedLandmarkCopied(); - copy_inventory_from_notecard(get_folder_by_itemtype(item), - object_id, - notecard_inventory_id, - item, - gInventoryCallbacks.registerCB(cb)); - } - } - } -}; -///---------------------------------------------------------------------------- -/// Class LLEmbeddedNotecardOpener -///---------------------------------------------------------------------------- -class LLEmbeddedNotecardOpener : public LLInventoryCallback -{ - LLViewerTextEditor* mTextEditor; - -public: - LLEmbeddedNotecardOpener() - : mTextEditor(NULL) - { - } - - void setEditor(LLViewerTextEditor* e) {mTextEditor = e;} - - // override - void fire(const LLUUID& inv_item) - { - if(!mTextEditor) - { - // The parent text editor may have vanished by now. - // In that case just quit. - return; - } - - LLInventoryItem* item = gInventory.getItem(inv_item); - if(!item) - { - LL_WARNS() << "Item add reported, but not found in inventory!: " << inv_item << LL_ENDL; - } - else - { - if(!gSavedSettings.getBOOL("ShowNewInventory")) - { - LLFloaterReg::showInstance("preview_notecard", LLSD(item->getUUID()), TAKE_FOCUS_YES); - } - } - } -}; - -// -// class LLEmbeddedItemSegment -// - -const S32 EMBEDDED_ITEM_LABEL_PADDING = 2; - -class LLEmbeddedItemSegment : public LLTextSegment -{ -public: - LLEmbeddedItemSegment(S32 pos, LLUIImagePtr image, LLPointer inv_item, LLTextEditor& editor) - : LLTextSegment(pos, pos + 1), - mImage(image), - mLabel(utf8str_to_wstring(inv_item->getName())), - mItem(inv_item), - mEditor(editor) - { - - mStyle = new LLStyle(LLStyle::Params().font(LLFontGL::getFontSansSerif())); - mToolTip = inv_item->getName() + '\n' + inv_item->getDescription(); - } - - /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const - { - if (num_chars == 0) - { - width = 0; - height = 0; - } - else - { - width = EMBEDDED_ITEM_LABEL_PADDING + mImage->getWidth() + mStyle->getFont()->getWidthF32(mLabel.c_str()); - height = llmax(mImage->getHeight(), mStyle->getFont()->getLineHeight()); - } - return false; - } - - /*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const - { - // always draw at beginning of line - if (line_offset == 0) - { - return 1; - } - else - { - S32 width, height; - getDimensions(mStart, 1, width, height); - if (width > num_pixels) - { - return 0; - } - else - { - return 1; - } - } - } - /*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) - { - LLRectf image_rect = draw_rect; - image_rect.mRight = image_rect.mLeft + mImage->getWidth(); - image_rect.mTop = image_rect.mBottom + mImage->getHeight(); - mImage->draw(LLRect(image_rect.mLeft, image_rect.mTop, image_rect.mRight, image_rect.mBottom)); - - LLColor4 color; - if (mEditor.getReadOnly()) - { - color = LLUIColorTable::instance().getColor("TextEmbeddedItemReadOnlyColor"); - } - else - { - color = LLUIColorTable::instance().getColor("TextEmbeddedItemColor"); - } - - F32 right_x; - mStyle->getFont()->render(mLabel, 0, image_rect.mRight + EMBEDDED_ITEM_LABEL_PADDING, draw_rect.mTop, color, LLFontGL::LEFT, LLFontGL::TOP, LLFontGL::UNDERLINE, LLFontGL::NO_SHADOW, mLabel.length(), S32_MAX, &right_x); - return right_x; - } - - /*virtual*/ bool canEdit() const { return false; } - - - /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask) - { - LLUI::getInstance()->getWindow()->setCursor(UI_CURSOR_HAND); - return true; - } - virtual bool handleToolTip(S32 x, S32 y, MASK mask ) - { - if (mItem->getThumbnailUUID().notNull()) - { - LLSD params; - params["inv_type"] = mItem->getInventoryType(); - params["thumbnail_id"] = mItem->getThumbnailUUID(); - params["asset_id"] = mItem->getAssetUUID(); - - LLToolTipMgr::instance().show(LLToolTip::Params() - .message(mToolTip) - .create_callback(boost::bind(&LLInspectTextureUtil::createInventoryToolTip, _1)) - .create_params(params)); - - return true; - } - - if (!mToolTip.empty()) - { - LLToolTipMgr::instance().show(mToolTip); - return true; - } - return false; - } - - /*virtual*/ LLStyleConstSP getStyle() const { return mStyle; } - -private: - LLUIImagePtr mImage; - LLWString mLabel; - LLStyleSP mStyle; - std::string mToolTip; - LLPointer mItem; - LLTextEditor& mEditor; -}; - - - -//////////////////////////////////////////////////////////// -// LLEmbeddedItems -// -// Embedded items are stored as: -// * A global map of llwchar to LLInventoryItem -// ** This is unique for each item embedded in any notecard -// to support copy/paste across notecards -// * A per-notecard set of embeded llwchars for easy removal -// from the global list -// * A per-notecard vector of embedded lwchars for mapping from -// old style 0x80 + item format notechards - -class LLEmbeddedItems -{ -public: - LLEmbeddedItems(const LLViewerTextEditor* editor); - ~LLEmbeddedItems(); - void clear(); - - // return true if there are no embedded items. - bool empty(); - - bool insertEmbeddedItem(LLInventoryItem* item, llwchar* value, bool is_new); - bool removeEmbeddedItem( llwchar ext_char ); - - bool hasEmbeddedItem(llwchar ext_char); // returns true if /this/ editor has an entry for this item - LLUIImagePtr getItemImage(llwchar ext_char) const; - - void getEmbeddedItemList( std::vector >& items ); - void addItems(const std::vector >& items); - - llwchar getEmbeddedCharFromIndex(S32 index); - - void removeUnusedChars(); - void copyUsedCharsToIndexed(); - S32 getIndexFromEmbeddedChar(llwchar wch); - - void markSaved(); - - static LLPointer getEmbeddedItemPtr(llwchar ext_char); // returns pointer to item from static list - static bool getEmbeddedItemSaved(llwchar ext_char); // returns whether item from static list is saved - -private: - - struct embedded_info_t - { - LLPointer mItemPtr; - bool mSaved; - }; - typedef std::map item_map_t; - static item_map_t sEntries; - static std::stack sFreeEntries; - - std::set mEmbeddedUsedChars; // list of used llwchars - std::vector mEmbeddedIndexedChars; // index -> wchar for 0x80 + index format - const LLViewerTextEditor* mEditor; -}; - -//statics -LLEmbeddedItems::item_map_t LLEmbeddedItems::sEntries; -std::stack LLEmbeddedItems::sFreeEntries; - -LLEmbeddedItems::LLEmbeddedItems(const LLViewerTextEditor* editor) - : mEditor(editor) -{ -} - -LLEmbeddedItems::~LLEmbeddedItems() -{ - clear(); -} - -void LLEmbeddedItems::clear() -{ - // Remove entries for this editor from static list - for (std::set::iterator iter = mEmbeddedUsedChars.begin(); - iter != mEmbeddedUsedChars.end();) - { - std::set::iterator nextiter = iter++; - removeEmbeddedItem(*nextiter); - } - mEmbeddedUsedChars.clear(); - mEmbeddedIndexedChars.clear(); -} - -bool LLEmbeddedItems::empty() -{ - removeUnusedChars(); - return mEmbeddedUsedChars.empty(); -} - -// Inserts a new unique entry -bool LLEmbeddedItems::insertEmbeddedItem( LLInventoryItem* item, llwchar* ext_char, bool is_new) -{ - // Now insert a new one - llwchar wc_emb; - if (!sFreeEntries.empty()) - { - wc_emb = sFreeEntries.top(); - sFreeEntries.pop(); - } - else if (sEntries.empty()) - { - wc_emb = LLTextEditor::FIRST_EMBEDDED_CHAR; - } - else - { - item_map_t::iterator last = sEntries.end(); - --last; - wc_emb = last->first; - if (wc_emb >= LLTextEditor::LAST_EMBEDDED_CHAR) - { - return false; - } - ++wc_emb; - } - - sEntries[wc_emb].mItemPtr = item; - sEntries[wc_emb].mSaved = !is_new; - *ext_char = wc_emb; - mEmbeddedUsedChars.insert(wc_emb); - return true; -} - -// Removes an entry (all entries are unique) -bool LLEmbeddedItems::removeEmbeddedItem( llwchar ext_char ) -{ - mEmbeddedUsedChars.erase(ext_char); - item_map_t::iterator iter = sEntries.find(ext_char); - if (iter != sEntries.end()) - { - sEntries.erase(ext_char); - sFreeEntries.push(ext_char); - return true; - } - return false; -} - -// static -LLPointer LLEmbeddedItems::getEmbeddedItemPtr(llwchar ext_char) -{ - if( ext_char >= LLTextEditor::FIRST_EMBEDDED_CHAR && ext_char <= LLTextEditor::LAST_EMBEDDED_CHAR ) - { - item_map_t::iterator iter = sEntries.find(ext_char); - if (iter != sEntries.end()) - { - return iter->second.mItemPtr; - } - } - return NULL; -} - -// static -bool LLEmbeddedItems::getEmbeddedItemSaved(llwchar ext_char) -{ - if( ext_char >= LLTextEditor::FIRST_EMBEDDED_CHAR && ext_char <= LLTextEditor::LAST_EMBEDDED_CHAR ) - { - item_map_t::iterator iter = sEntries.find(ext_char); - if (iter != sEntries.end()) - { - return iter->second.mSaved; - } - } - return false; -} - -llwchar LLEmbeddedItems::getEmbeddedCharFromIndex(S32 index) -{ - if (index >= (S32)mEmbeddedIndexedChars.size()) - { - LL_WARNS() << "No item for embedded char " << index << " using LL_UNKNOWN_CHAR" << LL_ENDL; - return LL_UNKNOWN_CHAR; - } - return mEmbeddedIndexedChars[index]; -} - -void LLEmbeddedItems::removeUnusedChars() -{ - std::set used = mEmbeddedUsedChars; - const LLWString& wtext = mEditor->getWText(); - for (S32 i=0; i<(S32)wtext.size(); i++) - { - llwchar wc = wtext[i]; - if( wc >= LLTextEditor::FIRST_EMBEDDED_CHAR && wc <= LLTextEditor::LAST_EMBEDDED_CHAR ) - { - used.erase(wc); - } - } - // Remove chars not actually used - for (std::set::iterator iter = used.begin(); - iter != used.end(); ++iter) - { - removeEmbeddedItem(*iter); - } -} - -void LLEmbeddedItems::copyUsedCharsToIndexed() -{ - // Prune unused items - removeUnusedChars(); - - // Copy all used llwchars to mEmbeddedIndexedChars - mEmbeddedIndexedChars.clear(); - for (std::set::iterator iter = mEmbeddedUsedChars.begin(); - iter != mEmbeddedUsedChars.end(); ++iter) - { - mEmbeddedIndexedChars.push_back(*iter); - } -} - -S32 LLEmbeddedItems::getIndexFromEmbeddedChar(llwchar wch) -{ - S32 idx = 0; - for (std::vector::iterator iter = mEmbeddedIndexedChars.begin(); - iter != mEmbeddedIndexedChars.end(); ++iter) - { - if (wch == *iter) - break; - ++idx; - } - if (idx < (S32)mEmbeddedIndexedChars.size()) - { - return idx; - } - else - { - LL_WARNS() << "Embedded char " << wch << " not found, using 0" << LL_ENDL; - return 0; - } -} - -bool LLEmbeddedItems::hasEmbeddedItem(llwchar ext_char) -{ - std::set::iterator iter = mEmbeddedUsedChars.find(ext_char); - if (iter != mEmbeddedUsedChars.end()) - { - return true; - } - return false; -} - - -LLUIImagePtr LLEmbeddedItems::getItemImage(llwchar ext_char) const -{ - LLInventoryItem* item = getEmbeddedItemPtr(ext_char); - if (item) - { - const char* img_name = ""; - switch( item->getType() ) - { - case LLAssetType::AT_TEXTURE: - if(item->getInventoryType() == LLInventoryType::IT_SNAPSHOT) - { - img_name = "Inv_Snapshot"; - } - else - { - img_name = "Inv_Texture"; - } - - break; - case LLAssetType::AT_SOUND: img_name = "Inv_Sound"; break; - case LLAssetType::AT_CLOTHING: img_name = "Inv_Clothing"; break; - case LLAssetType::AT_OBJECT: - img_name = LLInventoryItemFlags::II_FLAGS_OBJECT_HAS_MULTIPLE_ITEMS & item->getFlags() ? - "Inv_Object_Multi" : "Inv_Object"; - break; - case LLAssetType::AT_CALLINGCARD: img_name = "Inv_CallingCard"; break; - case LLAssetType::AT_LANDMARK: img_name = "Inv_Landmark"; break; - case LLAssetType::AT_NOTECARD: img_name = "Inv_Notecard"; break; - case LLAssetType::AT_LSL_TEXT: img_name = "Inv_Script"; break; - case LLAssetType::AT_BODYPART: img_name = "Inv_Skin"; break; - case LLAssetType::AT_ANIMATION: img_name = "Inv_Animation"; break; - case LLAssetType::AT_GESTURE: img_name = "Inv_Gesture"; break; - case LLAssetType::AT_MESH: img_name = "Inv_Mesh"; break; - case LLAssetType::AT_SETTINGS: img_name = "Inv_Settings"; break; - case LLAssetType::AT_MATERIAL: img_name = "Inv_Material"; break; - default: img_name = "Inv_Invalid"; break; // use the Inv_Invalid icon for undefined object types (see MAINT-3981) - - } - - return LLUI::getUIImage(img_name); - } - return LLUIImagePtr(); -} - - -void LLEmbeddedItems::addItems(const std::vector >& items) -{ - for (std::vector >::const_iterator iter = items.begin(); - iter != items.end(); ++iter) - { - LLInventoryItem* item = *iter; - if (item) - { - llwchar wc; - if (!insertEmbeddedItem( item, &wc, false )) - { - break; - } - mEmbeddedIndexedChars.push_back(wc); - } - } -} - -void LLEmbeddedItems::getEmbeddedItemList( std::vector >& items ) -{ - for (std::set::iterator iter = mEmbeddedUsedChars.begin(); iter != mEmbeddedUsedChars.end(); ++iter) - { - llwchar wc = *iter; - LLPointer item = getEmbeddedItemPtr(wc); - if (item) - { - items.push_back(item); - } - } -} - -void LLEmbeddedItems::markSaved() -{ - for (std::set::iterator iter = mEmbeddedUsedChars.begin(); iter != mEmbeddedUsedChars.end(); ++iter) - { - llwchar wc = *iter; - sEntries[wc].mSaved = true; - } -} - -/////////////////////////////////////////////////////////////////// - -class LLViewerTextEditor::TextCmdInsertEmbeddedItem : public LLTextBase::TextCmd -{ -public: - TextCmdInsertEmbeddedItem( S32 pos, LLInventoryItem* item ) - : TextCmd(pos, false), - mExtCharValue(0) - { - mItem = item; - } - - virtual bool execute( LLTextBase* editor, S32* delta ) - { - LLViewerTextEditor* viewer_editor = (LLViewerTextEditor*)editor; - // Take this opportunity to remove any unused embedded items from this editor - viewer_editor->mEmbeddedItemList->removeUnusedChars(); - if(viewer_editor->mEmbeddedItemList->insertEmbeddedItem( mItem, &mExtCharValue, true ) ) - { - LLWString ws; - ws.assign(1, mExtCharValue); - *delta = insert(editor, getPosition(), ws ); - return (*delta != 0); - } - return false; - } - - virtual S32 undo( LLTextBase* editor ) - { - remove(editor, getPosition(), 1); - return getPosition(); - } - - virtual S32 redo( LLTextBase* editor ) - { - LLWString ws; - ws += mExtCharValue; - insert(editor, getPosition(), ws ); - return getPosition() + 1; - } - virtual bool hasExtCharValue( llwchar value ) const - { - return (value == mExtCharValue); - } - -private: - LLPointer mItem; - llwchar mExtCharValue; -}; - -struct LLNotecardCopyInfo -{ - LLNotecardCopyInfo(LLViewerTextEditor *ed, LLInventoryItem *item) - : mTextEd(ed) - { - mItem = item; - } - - LLViewerTextEditor* mTextEd; - // need to make this be a copy (not a * here) because it isn't stable. - // I wish we had passed LLPointers all the way down, but we didn't - LLPointer mItem; -}; - -//---------------------------------------------------------------------------- - -// -// Member functions -// -LLViewerTextEditor::LLViewerTextEditor(const LLViewerTextEditor::Params& p) -: LLTextEditor(p), - mDragItemChar(0), - mDragItemSaved(false), - mInventoryCallback(new LLEmbeddedNotecardOpener) -{ - mEmbeddedItemList = new LLEmbeddedItems(this); - mInventoryCallback->setEditor(this); -} - -LLViewerTextEditor::~LLViewerTextEditor() -{ - delete mEmbeddedItemList; - - - // The inventory callback may still be in use by gInventoryCallbackManager... - // so set its reference to this to null. - mInventoryCallback->setEditor(NULL); -} - -/////////////////////////////////////////////////////////////////// -// virtual -void LLViewerTextEditor::makePristine() -{ - mEmbeddedItemList->markSaved(); - LLTextEditor::makePristine(); -} - -void LLViewerTextEditor::onVisibilityChange( bool new_visibility ) -{ - LLUICtrl::onVisibilityChange(new_visibility); -} - -bool LLViewerTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - // Let scrollbar have first dibs - handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL; - - if( !handled) - { - if( allowsEmbeddedItems() ) - { - setCursorAtLocalPos( x, y, false ); - llwchar wc = 0; - if (mCursorPos < getLength()) - { - wc = getWText()[mCursorPos]; - } - LLPointer item_at_pos = LLEmbeddedItems::getEmbeddedItemPtr(wc); - if (item_at_pos) - { - mDragItem = item_at_pos; - mDragItemChar = wc; - mDragItemSaved = LLEmbeddedItems::getEmbeddedItemSaved(wc); - gFocusMgr.setMouseCapture( this ); - mMouseDownX = x; - mMouseDownY = y; - S32 screen_x; - S32 screen_y; - localPointToScreen(x, y, &screen_x, &screen_y ); - LLToolDragAndDrop::getInstance()->setDragStart( screen_x, screen_y ); - - if (hasTabStop()) - { - setFocus( true ); - } - - handled = true; - } - else - { - mDragItem = NULL; - } - } - - if (!handled) - { - handled = LLTextEditor::handleMouseDown(x, y, mask); - } - } - - return handled; -} - - -bool LLViewerTextEditor::handleHover(S32 x, S32 y, MASK mask) -{ - bool handled = LLTextEditor::handleHover(x, y, mask); - - if(hasMouseCapture() && mDragItem) - { - S32 screen_x; - S32 screen_y; - localPointToScreen(x, y, &screen_x, &screen_y ); - - mScroller->autoScroll(x, y); - - if( LLToolDragAndDrop::getInstance()->isOverThreshold( screen_x, screen_y ) ) - { - LLToolDragAndDrop::getInstance()->beginDrag( - LLViewerAssetType::lookupDragAndDropType( mDragItem->getType() ), - mDragItem->getUUID(), - LLToolDragAndDrop::SOURCE_NOTECARD, - mPreviewID, mObjectID); - return LLToolDragAndDrop::getInstance()->handleHover( x, y, mask ); - } - getWindow()->setCursor(UI_CURSOR_HAND); - handled = true; - } - - return handled; -} - - -bool LLViewerTextEditor::handleMouseUp(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - if( hasMouseCapture() ) - { - if (mDragItem) - { - // mouse down was on an item - S32 dx = x - mMouseDownX; - S32 dy = y - mMouseDownY; - if (-2 < dx && dx < 2 && -2 < dy && dy < 2) - { - if(mDragItemSaved) - { - openEmbeddedItem(mDragItem, mDragItemChar); - } - else - { - showUnsavedAlertDialog(mDragItem); - } - } - } - mDragItem = NULL; - } - - handled = LLTextEditor::handleMouseUp(x,y,mask); - - return handled; -} - -bool LLViewerTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - bool handled = false; - - // let scrollbar have first dibs - handled = LLView::childrenHandleDoubleClick(x, y, mask) != NULL; - - if( !handled) - { - if( allowsEmbeddedItems() ) - { - S32 doc_index = getDocIndexFromLocalCoord(x, y, false); - llwchar doc_char = getWText()[doc_index]; - if (mEmbeddedItemList->hasEmbeddedItem(doc_char)) - { - if( openEmbeddedItemAtPos( doc_index )) - { - deselect(); - setFocus( false ); - return true; - } - } - } - handled = LLTextEditor::handleDoubleClick(x, y, mask); - } - return handled; -} - - -// virtual -bool LLViewerTextEditor::handleDragAndDrop(S32 x, S32 y, MASK mask, - bool drop, EDragAndDropType cargo_type, void *cargo_data, - EAcceptance *accept, - std::string& tooltip_msg) -{ - bool handled = false; - - LLToolDragAndDrop::ESource source = LLToolDragAndDrop::getInstance()->getSource(); - if (LLToolDragAndDrop::SOURCE_NOTECARD == source) - { - // We currently do not handle dragging items from one notecard to another - // since items in a notecard must be in Inventory to be verified. See DEV-2891. - return false; - } - - if (getEnabled() && acceptsTextInput()) - { - bool supported = false; - switch( cargo_type ) - { - case DAD_SETTINGS: - { - supported = LLEnvironment::instance().isExtendedEnvironmentEnabled(); - if (!supported && tooltip_msg.empty()) - { - tooltip_msg.assign(LLTrans::getString("TooltipNotecardNotAllowedTypeDrop")); - } - break; - } - case DAD_CALLINGCARD: - case DAD_TEXTURE: - case DAD_SOUND: - case DAD_LANDMARK: - case DAD_SCRIPT: - case DAD_CLOTHING: - case DAD_OBJECT: - case DAD_NOTECARD: - case DAD_BODYPART: - case DAD_ANIMATION: - case DAD_GESTURE: - case DAD_MESH: - case DAD_MATERIAL: - { - supported = true; - break; - } - - default: - supported = false; - break; - } - - LLInventoryItem *item = (LLInventoryItem *)cargo_data; - if (item && allowsEmbeddedItems() && supported) - { - U32 mask_next = item->getPermissions().getMaskNextOwner(); - if((mask_next & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED) - { - if( drop ) - { - deselect(); - S32 old_cursor = mCursorPos; - setCursorAtLocalPos( x, y, true ); - S32 insert_pos = mCursorPos; - setCursorPos(old_cursor); - bool inserted = insertEmbeddedItem( insert_pos, item ); - if( inserted && (old_cursor > mCursorPos) ) - { - setCursorPos(mCursorPos + 1); - } - - needsReflow(); - } - *accept = ACCEPT_YES_COPY_MULTI; - } - else - { - *accept = ACCEPT_NO; - if (tooltip_msg.empty()) - { - tooltip_msg.assign(LLTrans::getString("TooltipNotecardOwnerRestrictedDrop")); - } - } - } - else - { - *accept = ACCEPT_NO; - } - } - else - { - // Not enabled - *accept = ACCEPT_NO; - } - - handled = true; - LL_DEBUGS("UserInput") << "dragAndDrop handled by LLViewerTextEditor " << getName() << LL_ENDL; - - return handled; -} - -void LLViewerTextEditor::setASCIIEmbeddedText(const std::string& instr) -{ - LLWString wtext; - const U8* buffer = (U8*)(instr.c_str()); - while (*buffer) - { - llwchar wch; - U8 c = *buffer++; - if (c >= 0x80) - { - S32 index = (S32)(c - 0x80); - wch = mEmbeddedItemList->getEmbeddedCharFromIndex(index); - } - else - { - wch = (llwchar)c; - } - wtext.push_back(wch); - } - setWText(wtext); -} - -void LLViewerTextEditor::setEmbeddedText(const std::string& instr) -{ - LLWString wtext = utf8str_to_wstring(instr); - for (S32 i=0; i<(S32)wtext.size(); i++) - { - llwchar wch = wtext[i]; - if( wch >= FIRST_EMBEDDED_CHAR && wch <= LAST_EMBEDDED_CHAR ) - { - S32 index = wch - FIRST_EMBEDDED_CHAR; - wtext[i] = mEmbeddedItemList->getEmbeddedCharFromIndex(index); - } - } - setWText(wtext); -} - -std::string LLViewerTextEditor::getEmbeddedText() -{ -#if 1 - // New version (Version 2) - mEmbeddedItemList->copyUsedCharsToIndexed(); - LLWString outtextw; - for (S32 i=0; i<(S32)getWText().size(); i++) - { - llwchar wch = getWText()[i]; - if( wch >= FIRST_EMBEDDED_CHAR && wch <= LAST_EMBEDDED_CHAR ) - { - S32 index = mEmbeddedItemList->getIndexFromEmbeddedChar(wch); - wch = FIRST_EMBEDDED_CHAR + index; - } - outtextw.push_back(wch); - } - std::string outtext = wstring_to_utf8str(outtextw); - return outtext; -#else - // Old version (Version 1) - mEmbeddedItemList->copyUsedCharsToIndexed(); - std::string outtext; - for (S32 i=0; i<(S32)mWText.size(); i++) - { - llwchar wch = mWText[i]; - if( wch >= FIRST_EMBEDDED_CHAR && wch <= LAST_EMBEDDED_CHAR ) - { - S32 index = mEmbeddedItemList->getIndexFromEmbeddedChar(wch); - wch = 0x80 | index % 128; - } - else if (wch >= 0x80) - { - wch = LL_UNKNOWN_CHAR; - } - outtext.push_back((U8)wch); - } - return outtext; -#endif -} - -std::string LLViewerTextEditor::appendTime(bool prepend_newline) -{ - time_t utc_time; - utc_time = time_corrected(); - std::string timeStr ="[["+ LLTrans::getString("TimeHour")+"]:[" - +LLTrans::getString("TimeMin")+"]] "; - - LLSD substitution; - - substitution["datetime"] = (S32) utc_time; - LLStringUtil::format (timeStr, substitution); - appendText(timeStr, prepend_newline, LLStyle::Params().color(LLColor4::grey)); - blockUndo(); - - return timeStr; -} - -//---------------------------------------------------------------------------- -//---------------------------------------------------------------------------- - -llwchar LLViewerTextEditor::pasteEmbeddedItem(llwchar ext_char) -{ - if (mEmbeddedItemList->hasEmbeddedItem(ext_char)) - { - return ext_char; // already exists in my list - } - LLInventoryItem* item = LLEmbeddedItems::getEmbeddedItemPtr(ext_char); - if (item) - { - // Add item to my list and return new llwchar associated with it - llwchar new_wc; - if (mEmbeddedItemList->insertEmbeddedItem( item, &new_wc, true )) - { - return new_wc; - } - } - return LL_UNKNOWN_CHAR; // item not found or list full -} - -void LLViewerTextEditor::onValueChange(S32 start, S32 end) -{ - updateSegments(); - updateLinkSegments(); - findEmbeddedItemSegments(start, end); -} - -void LLViewerTextEditor::findEmbeddedItemSegments(S32 start, S32 end) -{ - LLWString text = getWText(); - - // Start with i just after the first embedded item - for(S32 idx = start; idx < end; idx++ ) - { - llwchar embedded_char = text[idx]; - if( embedded_char >= FIRST_EMBEDDED_CHAR - && embedded_char <= LAST_EMBEDDED_CHAR - && mEmbeddedItemList->hasEmbeddedItem(embedded_char) ) - { - LLInventoryItem* itemp = mEmbeddedItemList->getEmbeddedItemPtr(embedded_char); - LLUIImagePtr image = mEmbeddedItemList->getItemImage(embedded_char); - insertSegment(new LLEmbeddedItemSegment(idx, image, itemp, *this)); - } - } -} - -bool LLViewerTextEditor::openEmbeddedItemAtPos(S32 pos) -{ - if( pos < getLength()) - { - llwchar wc = getWText()[pos]; - LLPointer item = LLEmbeddedItems::getEmbeddedItemPtr( wc ); - if( item ) - { - bool saved = LLEmbeddedItems::getEmbeddedItemSaved( wc ); - if (saved) - { - return openEmbeddedItem(item, wc); - } - else - { - showUnsavedAlertDialog(item); - } - } - } - return false; -} - - -bool LLViewerTextEditor::openEmbeddedItem(LLPointer item, llwchar wc) -{ - - switch( item->getType() ) - { - case LLAssetType::AT_TEXTURE: - openEmbeddedTexture( item, wc ); - return true; - - case LLAssetType::AT_SOUND: - openEmbeddedSound( item, wc ); - return true; - - case LLAssetType::AT_LANDMARK: - openEmbeddedLandmark( item, wc ); - return true; - - case LLAssetType::AT_CALLINGCARD: - openEmbeddedCallingcard( item, wc ); - return true; - case LLAssetType::AT_SETTINGS: - openEmbeddedSetting(item, wc); - return true; - case LLAssetType::AT_MATERIAL: - openEmbeddedGLTFMaterial(item, wc); - return true; - case LLAssetType::AT_NOTECARD: - case LLAssetType::AT_LSL_TEXT: - case LLAssetType::AT_CLOTHING: - case LLAssetType::AT_OBJECT: - case LLAssetType::AT_BODYPART: - case LLAssetType::AT_ANIMATION: - case LLAssetType::AT_GESTURE: - showCopyToInvDialog( item, wc ); - return true; - default: - return false; - } - -} - - -void LLViewerTextEditor::openEmbeddedTexture( LLInventoryItem* item, llwchar wc ) -{ - // *NOTE: Just for embedded Texture , we should use getAssetUUID(), - // not getUUID(), because LLPreviewTexture pass in AssetUUID into - // LLPreview constructor ItemUUID parameter. - if (!item) - return; - LLPreviewTexture* preview = LLFloaterReg::showTypedInstance("preview_texture", LLSD(item->getAssetUUID()), TAKE_FOCUS_YES); - if (preview) - { - preview->setAuxItem( item ); - preview->setNotecardInfo(mNotecardInventoryID, mObjectID); - if (preview->hasString("Title")) - { - LLStringUtil::format_map_t args; - args["[NAME]"] = item->getName(); - LLUIString title = preview->getString("Title", args); - preview->setTitle(title.getString()); - } - preview->getChild("desc")->setValue(item->getDescription()); - } -} - -void LLViewerTextEditor::openEmbeddedSound( LLInventoryItem* item, llwchar wc ) -{ - // Play sound locally - LLVector3d lpos_global = gAgent.getPositionGlobal(); - const F32 SOUND_GAIN = 1.0f; - if(gAudiop) - { - gAudiop->triggerSound(item->getAssetUUID(), gAgentID, SOUND_GAIN, LLAudioEngine::AUDIO_TYPE_UI, lpos_global); - } - showCopyToInvDialog( item, wc ); -} - - -void LLViewerTextEditor::openEmbeddedLandmark( LLPointer item_ptr, llwchar wc ) -{ - if (item_ptr.isNull()) - return; - - LLLandmark* landmark = gLandmarkList.getAsset(item_ptr->getAssetUUID(), - boost::bind(&LLEmbeddedLandmarkCopied::processForeignLandmark, _1, mObjectID, mNotecardInventoryID, item_ptr)); - if (landmark) - { - LLEmbeddedLandmarkCopied::processForeignLandmark(landmark, mObjectID, - mNotecardInventoryID, item_ptr); - } -} - -void LLViewerTextEditor::openEmbeddedCallingcard( LLInventoryItem* item, llwchar wc ) -{ - if (item && !item->getDescription().empty()) - { - LLAvatarActions::showProfile(LLUUID(item->getDescription())); - } - else if (item && !item->getCreatorUUID().isNull()) - { - LLAvatarActions::showProfile(item->getCreatorUUID()); - } -} - -void LLViewerTextEditor::openEmbeddedSetting(LLInventoryItem* item, llwchar wc) -{ - if (LLEnvironment::instance().isInventoryEnabled()) - { - showCopyToInvDialog(item, wc); - } - else - { - LLNotificationsUtil::add("NoEnvironmentSettings"); - } -} - -void LLViewerTextEditor::openEmbeddedGLTFMaterial(LLInventoryItem* item, llwchar wc) -{ - if (!item) - { - return; - } - - LLSD floater_key; - floater_key["objectid"] = mObjectID; - floater_key["notecardid"] = mNotecardInventoryID; - LLMaterialEditor* preview = LLFloaterReg::getTypedInstance("material_editor", floater_key); - if (preview) - { - preview->setAuxItem(item); - preview->setNotecardInfo(mNotecardInventoryID, mObjectID); - preview->openFloater(floater_key); - preview->setFocus(true); - } -} - -void LLViewerTextEditor::showUnsavedAlertDialog( LLInventoryItem* item ) -{ - LLSD payload; - payload["item_id"] = item->getUUID(); - payload["notecard_id"] = mNotecardInventoryID; - LLNotificationsUtil::add( "ConfirmNotecardSave", LLSD(), payload, LLViewerTextEditor::onNotecardDialog); -} - -// static -bool LLViewerTextEditor::onNotecardDialog(const LLSD& notification, const LLSD& response ) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if( option == 0 ) - { - LLPreviewNotecard* preview = LLFloaterReg::findTypedInstance("preview_notecard", notification["payload"]["notecard_id"]);; - if (preview) - { - preview->saveItem(); - } - } - return false; -} - - - -void LLViewerTextEditor::showCopyToInvDialog( LLInventoryItem* item, llwchar wc ) -{ - LLSD payload; - LLUUID item_id = item->getUUID(); - payload["item_id"] = item_id; - payload["item_wc"] = LLSD::Integer(wc); - LLNotificationsUtil::add( "ConfirmItemCopy", LLSD(), payload, - boost::bind(&LLViewerTextEditor::onCopyToInvDialog, this, _1, _2)); -} - -bool LLViewerTextEditor::onCopyToInvDialog(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - if( 0 == option ) - { - llwchar wc = llwchar(notification["payload"]["item_wc"].asInteger()); - LLInventoryItem* itemp = LLEmbeddedItems::getEmbeddedItemPtr(wc); - if (itemp) - copyInventory(itemp); - } - return false; -} - - - -// Returns change in number of characters in mWText -S32 LLViewerTextEditor::insertEmbeddedItem( S32 pos, LLInventoryItem* item ) -{ - return execute( new TextCmdInsertEmbeddedItem( pos, item ) ); -} - -bool LLViewerTextEditor::importStream(std::istream& str) -{ - LLNotecard nc(LLNotecard::MAX_SIZE); - bool success = nc.importStream(str); - if (success) - { - mEmbeddedItemList->clear(); - const std::vector >& items = nc.getItems(); - mEmbeddedItemList->addItems(items); - // Actually set the text - if (allowsEmbeddedItems()) - { - if (nc.getVersion() == 1) - setASCIIEmbeddedText( nc.getText() ); - else - setEmbeddedText( nc.getText() ); - } - else - { - setText( nc.getText() ); - } - } - return success; -} - -void LLViewerTextEditor::copyInventory(const LLInventoryItem* item, U32 callback_id) -{ - copy_inventory_from_notecard(LLUUID::null, // Don't specify a destination -- let the sim do that - mObjectID, - mNotecardInventoryID, - item, - callback_id); -} - -bool LLViewerTextEditor::hasEmbeddedInventory() -{ - return ! mEmbeddedItemList->empty(); -} - -//////////////////////////////////////////////////////////////////////////// - -bool LLViewerTextEditor::importBuffer( const char* buffer, S32 length ) -{ - LLMemoryStream str((U8*)buffer, length); - return importStream(str); -} - -bool LLViewerTextEditor::exportBuffer( std::string& buffer ) -{ - LLNotecard nc(LLNotecard::MAX_SIZE); - - // Get the embedded text and update the item list to just be the used items - nc.setText(getEmbeddedText()); - - // Now get the used items and copy the list to the notecard - std::vector > embedded_items; - mEmbeddedItemList->getEmbeddedItemList(embedded_items); - nc.setItems(embedded_items); - - std::stringstream out_stream; - nc.exportStream(out_stream); - - buffer = out_stream.str(); - - return true; -} - +/** + * @file llviewertexteditor.cpp + * @brief Text editor widget to let users enter a multi-line document. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llviewertexteditor.h" + +#include "llagent.h" +#include "llaudioengine.h" +#include "llavataractions.h" +#include "llenvironment.h" +#include "llfloaterreg.h" +#include "llfloatersidepanelcontainer.h" +#include "llfloaterworldmap.h" +#include "llfocusmgr.h" +#include "llinspecttexture.h" +#include "llinventorybridge.h" +#include "llinventorydefines.h" +#include "llinventorymodel.h" +#include "lllandmark.h" +#include "lllandmarkactions.h" +#include "lllandmarklist.h" +#include "llmaterialeditor.h" +#include "llmemorystream.h" +#include "llmenugl.h" +#include "llnotecard.h" +#include "llnotificationsutil.h" +#include "llpanelplaces.h" +#include "llpreview.h" +#include "llpreviewnotecard.h" +#include "llpreviewtexture.h" +#include "llscrollbar.h" +#include "llscrollcontainer.h" +#include "lltooldraganddrop.h" +#include "lltooltip.h" +#include "lltrans.h" +#include "lluictrlfactory.h" +#include "llviewerassettype.h" +#include "llviewercontrol.h" +#include "llviewerinventory.h" +#include "llviewertexturelist.h" +#include "llviewerwindow.h" + +static LLDefaultChildRegistry::Register r("text_editor"); + +///----------------------------------------------------------------------- +/// Class LLEmbeddedLandmarkCopied +///----------------------------------------------------------------------- +class LLEmbeddedLandmarkCopied: public LLInventoryCallback +{ +public: + + LLEmbeddedLandmarkCopied(){} + void fire(const LLUUID& inv_item) + { + showInfo(inv_item); + } + static void showInfo(const LLUUID& landmark_inv_id) + { + LLSD key; + key["type"] = "landmark"; + key["id"] = landmark_inv_id; + LLFloaterSidePanelContainer::showPanel("places", key); + } + static void processForeignLandmark(LLLandmark* landmark, + const LLUUID& object_id, const LLUUID& notecard_inventory_id, + LLPointer item_ptr) + { + LLVector3d global_pos; + landmark->getGlobalPos(global_pos); + LLViewerInventoryItem* agent_landmark = + LLLandmarkActions::findLandmarkForGlobalPos(global_pos); + + if (agent_landmark) + { + showInfo(agent_landmark->getUUID()); + } + else + { + if (item_ptr.isNull()) + { + // check to prevent a crash. See EXT-8459. + LL_WARNS() << "Passed handle contains a dead inventory item. Most likely notecard has been closed and embedded item was destroyed." << LL_ENDL; + } + else + { + LLInventoryItem* item = item_ptr.get(); + LLPointer cb = new LLEmbeddedLandmarkCopied(); + copy_inventory_from_notecard(get_folder_by_itemtype(item), + object_id, + notecard_inventory_id, + item, + gInventoryCallbacks.registerCB(cb)); + } + } + } +}; +///---------------------------------------------------------------------------- +/// Class LLEmbeddedNotecardOpener +///---------------------------------------------------------------------------- +class LLEmbeddedNotecardOpener : public LLInventoryCallback +{ + LLViewerTextEditor* mTextEditor; + +public: + LLEmbeddedNotecardOpener() + : mTextEditor(NULL) + { + } + + void setEditor(LLViewerTextEditor* e) {mTextEditor = e;} + + // override + void fire(const LLUUID& inv_item) + { + if(!mTextEditor) + { + // The parent text editor may have vanished by now. + // In that case just quit. + return; + } + + LLInventoryItem* item = gInventory.getItem(inv_item); + if(!item) + { + LL_WARNS() << "Item add reported, but not found in inventory!: " << inv_item << LL_ENDL; + } + else + { + if(!gSavedSettings.getBOOL("ShowNewInventory")) + { + LLFloaterReg::showInstance("preview_notecard", LLSD(item->getUUID()), TAKE_FOCUS_YES); + } + } + } +}; + +// +// class LLEmbeddedItemSegment +// + +const S32 EMBEDDED_ITEM_LABEL_PADDING = 2; + +class LLEmbeddedItemSegment : public LLTextSegment +{ +public: + LLEmbeddedItemSegment(S32 pos, LLUIImagePtr image, LLPointer inv_item, LLTextEditor& editor) + : LLTextSegment(pos, pos + 1), + mImage(image), + mLabel(utf8str_to_wstring(inv_item->getName())), + mItem(inv_item), + mEditor(editor) + { + + mStyle = new LLStyle(LLStyle::Params().font(LLFontGL::getFontSansSerif())); + mToolTip = inv_item->getName() + '\n' + inv_item->getDescription(); + } + + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const + { + if (num_chars == 0) + { + width = 0; + height = 0; + } + else + { + width = EMBEDDED_ITEM_LABEL_PADDING + mImage->getWidth() + mStyle->getFont()->getWidthF32(mLabel.c_str()); + height = llmax(mImage->getHeight(), mStyle->getFont()->getLineHeight()); + } + return false; + } + + /*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const + { + // always draw at beginning of line + if (line_offset == 0) + { + return 1; + } + else + { + S32 width, height; + getDimensions(mStart, 1, width, height); + if (width > num_pixels) + { + return 0; + } + else + { + return 1; + } + } + } + /*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) + { + LLRectf image_rect = draw_rect; + image_rect.mRight = image_rect.mLeft + mImage->getWidth(); + image_rect.mTop = image_rect.mBottom + mImage->getHeight(); + mImage->draw(LLRect(image_rect.mLeft, image_rect.mTop, image_rect.mRight, image_rect.mBottom)); + + LLColor4 color; + if (mEditor.getReadOnly()) + { + color = LLUIColorTable::instance().getColor("TextEmbeddedItemReadOnlyColor"); + } + else + { + color = LLUIColorTable::instance().getColor("TextEmbeddedItemColor"); + } + + F32 right_x; + mStyle->getFont()->render(mLabel, 0, image_rect.mRight + EMBEDDED_ITEM_LABEL_PADDING, draw_rect.mTop, color, LLFontGL::LEFT, LLFontGL::TOP, LLFontGL::UNDERLINE, LLFontGL::NO_SHADOW, mLabel.length(), S32_MAX, &right_x); + return right_x; + } + + /*virtual*/ bool canEdit() const { return false; } + + + /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask) + { + LLUI::getInstance()->getWindow()->setCursor(UI_CURSOR_HAND); + return true; + } + virtual bool handleToolTip(S32 x, S32 y, MASK mask ) + { + if (mItem->getThumbnailUUID().notNull()) + { + LLSD params; + params["inv_type"] = mItem->getInventoryType(); + params["thumbnail_id"] = mItem->getThumbnailUUID(); + params["asset_id"] = mItem->getAssetUUID(); + + LLToolTipMgr::instance().show(LLToolTip::Params() + .message(mToolTip) + .create_callback(boost::bind(&LLInspectTextureUtil::createInventoryToolTip, _1)) + .create_params(params)); + + return true; + } + + if (!mToolTip.empty()) + { + LLToolTipMgr::instance().show(mToolTip); + return true; + } + return false; + } + + /*virtual*/ LLStyleConstSP getStyle() const { return mStyle; } + +private: + LLUIImagePtr mImage; + LLWString mLabel; + LLStyleSP mStyle; + std::string mToolTip; + LLPointer mItem; + LLTextEditor& mEditor; +}; + + + +//////////////////////////////////////////////////////////// +// LLEmbeddedItems +// +// Embedded items are stored as: +// * A global map of llwchar to LLInventoryItem +// ** This is unique for each item embedded in any notecard +// to support copy/paste across notecards +// * A per-notecard set of embeded llwchars for easy removal +// from the global list +// * A per-notecard vector of embedded lwchars for mapping from +// old style 0x80 + item format notechards + +class LLEmbeddedItems +{ +public: + LLEmbeddedItems(const LLViewerTextEditor* editor); + ~LLEmbeddedItems(); + void clear(); + + // return true if there are no embedded items. + bool empty(); + + bool insertEmbeddedItem(LLInventoryItem* item, llwchar* value, bool is_new); + bool removeEmbeddedItem( llwchar ext_char ); + + bool hasEmbeddedItem(llwchar ext_char); // returns true if /this/ editor has an entry for this item + LLUIImagePtr getItemImage(llwchar ext_char) const; + + void getEmbeddedItemList( std::vector >& items ); + void addItems(const std::vector >& items); + + llwchar getEmbeddedCharFromIndex(S32 index); + + void removeUnusedChars(); + void copyUsedCharsToIndexed(); + S32 getIndexFromEmbeddedChar(llwchar wch); + + void markSaved(); + + static LLPointer getEmbeddedItemPtr(llwchar ext_char); // returns pointer to item from static list + static bool getEmbeddedItemSaved(llwchar ext_char); // returns whether item from static list is saved + +private: + + struct embedded_info_t + { + LLPointer mItemPtr; + bool mSaved; + }; + typedef std::map item_map_t; + static item_map_t sEntries; + static std::stack sFreeEntries; + + std::set mEmbeddedUsedChars; // list of used llwchars + std::vector mEmbeddedIndexedChars; // index -> wchar for 0x80 + index format + const LLViewerTextEditor* mEditor; +}; + +//statics +LLEmbeddedItems::item_map_t LLEmbeddedItems::sEntries; +std::stack LLEmbeddedItems::sFreeEntries; + +LLEmbeddedItems::LLEmbeddedItems(const LLViewerTextEditor* editor) + : mEditor(editor) +{ +} + +LLEmbeddedItems::~LLEmbeddedItems() +{ + clear(); +} + +void LLEmbeddedItems::clear() +{ + // Remove entries for this editor from static list + for (std::set::iterator iter = mEmbeddedUsedChars.begin(); + iter != mEmbeddedUsedChars.end();) + { + std::set::iterator nextiter = iter++; + removeEmbeddedItem(*nextiter); + } + mEmbeddedUsedChars.clear(); + mEmbeddedIndexedChars.clear(); +} + +bool LLEmbeddedItems::empty() +{ + removeUnusedChars(); + return mEmbeddedUsedChars.empty(); +} + +// Inserts a new unique entry +bool LLEmbeddedItems::insertEmbeddedItem( LLInventoryItem* item, llwchar* ext_char, bool is_new) +{ + // Now insert a new one + llwchar wc_emb; + if (!sFreeEntries.empty()) + { + wc_emb = sFreeEntries.top(); + sFreeEntries.pop(); + } + else if (sEntries.empty()) + { + wc_emb = LLTextEditor::FIRST_EMBEDDED_CHAR; + } + else + { + item_map_t::iterator last = sEntries.end(); + --last; + wc_emb = last->first; + if (wc_emb >= LLTextEditor::LAST_EMBEDDED_CHAR) + { + return false; + } + ++wc_emb; + } + + sEntries[wc_emb].mItemPtr = item; + sEntries[wc_emb].mSaved = !is_new; + *ext_char = wc_emb; + mEmbeddedUsedChars.insert(wc_emb); + return true; +} + +// Removes an entry (all entries are unique) +bool LLEmbeddedItems::removeEmbeddedItem( llwchar ext_char ) +{ + mEmbeddedUsedChars.erase(ext_char); + item_map_t::iterator iter = sEntries.find(ext_char); + if (iter != sEntries.end()) + { + sEntries.erase(ext_char); + sFreeEntries.push(ext_char); + return true; + } + return false; +} + +// static +LLPointer LLEmbeddedItems::getEmbeddedItemPtr(llwchar ext_char) +{ + if( ext_char >= LLTextEditor::FIRST_EMBEDDED_CHAR && ext_char <= LLTextEditor::LAST_EMBEDDED_CHAR ) + { + item_map_t::iterator iter = sEntries.find(ext_char); + if (iter != sEntries.end()) + { + return iter->second.mItemPtr; + } + } + return NULL; +} + +// static +bool LLEmbeddedItems::getEmbeddedItemSaved(llwchar ext_char) +{ + if( ext_char >= LLTextEditor::FIRST_EMBEDDED_CHAR && ext_char <= LLTextEditor::LAST_EMBEDDED_CHAR ) + { + item_map_t::iterator iter = sEntries.find(ext_char); + if (iter != sEntries.end()) + { + return iter->second.mSaved; + } + } + return false; +} + +llwchar LLEmbeddedItems::getEmbeddedCharFromIndex(S32 index) +{ + if (index >= (S32)mEmbeddedIndexedChars.size()) + { + LL_WARNS() << "No item for embedded char " << index << " using LL_UNKNOWN_CHAR" << LL_ENDL; + return LL_UNKNOWN_CHAR; + } + return mEmbeddedIndexedChars[index]; +} + +void LLEmbeddedItems::removeUnusedChars() +{ + std::set used = mEmbeddedUsedChars; + const LLWString& wtext = mEditor->getWText(); + for (S32 i=0; i<(S32)wtext.size(); i++) + { + llwchar wc = wtext[i]; + if( wc >= LLTextEditor::FIRST_EMBEDDED_CHAR && wc <= LLTextEditor::LAST_EMBEDDED_CHAR ) + { + used.erase(wc); + } + } + // Remove chars not actually used + for (std::set::iterator iter = used.begin(); + iter != used.end(); ++iter) + { + removeEmbeddedItem(*iter); + } +} + +void LLEmbeddedItems::copyUsedCharsToIndexed() +{ + // Prune unused items + removeUnusedChars(); + + // Copy all used llwchars to mEmbeddedIndexedChars + mEmbeddedIndexedChars.clear(); + for (std::set::iterator iter = mEmbeddedUsedChars.begin(); + iter != mEmbeddedUsedChars.end(); ++iter) + { + mEmbeddedIndexedChars.push_back(*iter); + } +} + +S32 LLEmbeddedItems::getIndexFromEmbeddedChar(llwchar wch) +{ + S32 idx = 0; + for (std::vector::iterator iter = mEmbeddedIndexedChars.begin(); + iter != mEmbeddedIndexedChars.end(); ++iter) + { + if (wch == *iter) + break; + ++idx; + } + if (idx < (S32)mEmbeddedIndexedChars.size()) + { + return idx; + } + else + { + LL_WARNS() << "Embedded char " << wch << " not found, using 0" << LL_ENDL; + return 0; + } +} + +bool LLEmbeddedItems::hasEmbeddedItem(llwchar ext_char) +{ + std::set::iterator iter = mEmbeddedUsedChars.find(ext_char); + if (iter != mEmbeddedUsedChars.end()) + { + return true; + } + return false; +} + + +LLUIImagePtr LLEmbeddedItems::getItemImage(llwchar ext_char) const +{ + LLInventoryItem* item = getEmbeddedItemPtr(ext_char); + if (item) + { + const char* img_name = ""; + switch( item->getType() ) + { + case LLAssetType::AT_TEXTURE: + if(item->getInventoryType() == LLInventoryType::IT_SNAPSHOT) + { + img_name = "Inv_Snapshot"; + } + else + { + img_name = "Inv_Texture"; + } + + break; + case LLAssetType::AT_SOUND: img_name = "Inv_Sound"; break; + case LLAssetType::AT_CLOTHING: img_name = "Inv_Clothing"; break; + case LLAssetType::AT_OBJECT: + img_name = LLInventoryItemFlags::II_FLAGS_OBJECT_HAS_MULTIPLE_ITEMS & item->getFlags() ? + "Inv_Object_Multi" : "Inv_Object"; + break; + case LLAssetType::AT_CALLINGCARD: img_name = "Inv_CallingCard"; break; + case LLAssetType::AT_LANDMARK: img_name = "Inv_Landmark"; break; + case LLAssetType::AT_NOTECARD: img_name = "Inv_Notecard"; break; + case LLAssetType::AT_LSL_TEXT: img_name = "Inv_Script"; break; + case LLAssetType::AT_BODYPART: img_name = "Inv_Skin"; break; + case LLAssetType::AT_ANIMATION: img_name = "Inv_Animation"; break; + case LLAssetType::AT_GESTURE: img_name = "Inv_Gesture"; break; + case LLAssetType::AT_MESH: img_name = "Inv_Mesh"; break; + case LLAssetType::AT_SETTINGS: img_name = "Inv_Settings"; break; + case LLAssetType::AT_MATERIAL: img_name = "Inv_Material"; break; + default: img_name = "Inv_Invalid"; break; // use the Inv_Invalid icon for undefined object types (see MAINT-3981) + + } + + return LLUI::getUIImage(img_name); + } + return LLUIImagePtr(); +} + + +void LLEmbeddedItems::addItems(const std::vector >& items) +{ + for (std::vector >::const_iterator iter = items.begin(); + iter != items.end(); ++iter) + { + LLInventoryItem* item = *iter; + if (item) + { + llwchar wc; + if (!insertEmbeddedItem( item, &wc, false )) + { + break; + } + mEmbeddedIndexedChars.push_back(wc); + } + } +} + +void LLEmbeddedItems::getEmbeddedItemList( std::vector >& items ) +{ + for (std::set::iterator iter = mEmbeddedUsedChars.begin(); iter != mEmbeddedUsedChars.end(); ++iter) + { + llwchar wc = *iter; + LLPointer item = getEmbeddedItemPtr(wc); + if (item) + { + items.push_back(item); + } + } +} + +void LLEmbeddedItems::markSaved() +{ + for (std::set::iterator iter = mEmbeddedUsedChars.begin(); iter != mEmbeddedUsedChars.end(); ++iter) + { + llwchar wc = *iter; + sEntries[wc].mSaved = true; + } +} + +/////////////////////////////////////////////////////////////////// + +class LLViewerTextEditor::TextCmdInsertEmbeddedItem : public LLTextBase::TextCmd +{ +public: + TextCmdInsertEmbeddedItem( S32 pos, LLInventoryItem* item ) + : TextCmd(pos, false), + mExtCharValue(0) + { + mItem = item; + } + + virtual bool execute( LLTextBase* editor, S32* delta ) + { + LLViewerTextEditor* viewer_editor = (LLViewerTextEditor*)editor; + // Take this opportunity to remove any unused embedded items from this editor + viewer_editor->mEmbeddedItemList->removeUnusedChars(); + if(viewer_editor->mEmbeddedItemList->insertEmbeddedItem( mItem, &mExtCharValue, true ) ) + { + LLWString ws; + ws.assign(1, mExtCharValue); + *delta = insert(editor, getPosition(), ws ); + return (*delta != 0); + } + return false; + } + + virtual S32 undo( LLTextBase* editor ) + { + remove(editor, getPosition(), 1); + return getPosition(); + } + + virtual S32 redo( LLTextBase* editor ) + { + LLWString ws; + ws += mExtCharValue; + insert(editor, getPosition(), ws ); + return getPosition() + 1; + } + virtual bool hasExtCharValue( llwchar value ) const + { + return (value == mExtCharValue); + } + +private: + LLPointer mItem; + llwchar mExtCharValue; +}; + +struct LLNotecardCopyInfo +{ + LLNotecardCopyInfo(LLViewerTextEditor *ed, LLInventoryItem *item) + : mTextEd(ed) + { + mItem = item; + } + + LLViewerTextEditor* mTextEd; + // need to make this be a copy (not a * here) because it isn't stable. + // I wish we had passed LLPointers all the way down, but we didn't + LLPointer mItem; +}; + +//---------------------------------------------------------------------------- + +// +// Member functions +// +LLViewerTextEditor::LLViewerTextEditor(const LLViewerTextEditor::Params& p) +: LLTextEditor(p), + mDragItemChar(0), + mDragItemSaved(false), + mInventoryCallback(new LLEmbeddedNotecardOpener) +{ + mEmbeddedItemList = new LLEmbeddedItems(this); + mInventoryCallback->setEditor(this); +} + +LLViewerTextEditor::~LLViewerTextEditor() +{ + delete mEmbeddedItemList; + + + // The inventory callback may still be in use by gInventoryCallbackManager... + // so set its reference to this to null. + mInventoryCallback->setEditor(NULL); +} + +/////////////////////////////////////////////////////////////////// +// virtual +void LLViewerTextEditor::makePristine() +{ + mEmbeddedItemList->markSaved(); + LLTextEditor::makePristine(); +} + +void LLViewerTextEditor::onVisibilityChange( bool new_visibility ) +{ + LLUICtrl::onVisibilityChange(new_visibility); +} + +bool LLViewerTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + // Let scrollbar have first dibs + handled = LLView::childrenHandleMouseDown(x, y, mask) != NULL; + + if( !handled) + { + if( allowsEmbeddedItems() ) + { + setCursorAtLocalPos( x, y, false ); + llwchar wc = 0; + if (mCursorPos < getLength()) + { + wc = getWText()[mCursorPos]; + } + LLPointer item_at_pos = LLEmbeddedItems::getEmbeddedItemPtr(wc); + if (item_at_pos) + { + mDragItem = item_at_pos; + mDragItemChar = wc; + mDragItemSaved = LLEmbeddedItems::getEmbeddedItemSaved(wc); + gFocusMgr.setMouseCapture( this ); + mMouseDownX = x; + mMouseDownY = y; + S32 screen_x; + S32 screen_y; + localPointToScreen(x, y, &screen_x, &screen_y ); + LLToolDragAndDrop::getInstance()->setDragStart( screen_x, screen_y ); + + if (hasTabStop()) + { + setFocus( true ); + } + + handled = true; + } + else + { + mDragItem = NULL; + } + } + + if (!handled) + { + handled = LLTextEditor::handleMouseDown(x, y, mask); + } + } + + return handled; +} + + +bool LLViewerTextEditor::handleHover(S32 x, S32 y, MASK mask) +{ + bool handled = LLTextEditor::handleHover(x, y, mask); + + if(hasMouseCapture() && mDragItem) + { + S32 screen_x; + S32 screen_y; + localPointToScreen(x, y, &screen_x, &screen_y ); + + mScroller->autoScroll(x, y); + + if( LLToolDragAndDrop::getInstance()->isOverThreshold( screen_x, screen_y ) ) + { + LLToolDragAndDrop::getInstance()->beginDrag( + LLViewerAssetType::lookupDragAndDropType( mDragItem->getType() ), + mDragItem->getUUID(), + LLToolDragAndDrop::SOURCE_NOTECARD, + mPreviewID, mObjectID); + return LLToolDragAndDrop::getInstance()->handleHover( x, y, mask ); + } + getWindow()->setCursor(UI_CURSOR_HAND); + handled = true; + } + + return handled; +} + + +bool LLViewerTextEditor::handleMouseUp(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + if( hasMouseCapture() ) + { + if (mDragItem) + { + // mouse down was on an item + S32 dx = x - mMouseDownX; + S32 dy = y - mMouseDownY; + if (-2 < dx && dx < 2 && -2 < dy && dy < 2) + { + if(mDragItemSaved) + { + openEmbeddedItem(mDragItem, mDragItemChar); + } + else + { + showUnsavedAlertDialog(mDragItem); + } + } + } + mDragItem = NULL; + } + + handled = LLTextEditor::handleMouseUp(x,y,mask); + + return handled; +} + +bool LLViewerTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + bool handled = false; + + // let scrollbar have first dibs + handled = LLView::childrenHandleDoubleClick(x, y, mask) != NULL; + + if( !handled) + { + if( allowsEmbeddedItems() ) + { + S32 doc_index = getDocIndexFromLocalCoord(x, y, false); + llwchar doc_char = getWText()[doc_index]; + if (mEmbeddedItemList->hasEmbeddedItem(doc_char)) + { + if( openEmbeddedItemAtPos( doc_index )) + { + deselect(); + setFocus( false ); + return true; + } + } + } + handled = LLTextEditor::handleDoubleClick(x, y, mask); + } + return handled; +} + + +// virtual +bool LLViewerTextEditor::handleDragAndDrop(S32 x, S32 y, MASK mask, + bool drop, EDragAndDropType cargo_type, void *cargo_data, + EAcceptance *accept, + std::string& tooltip_msg) +{ + bool handled = false; + + LLToolDragAndDrop::ESource source = LLToolDragAndDrop::getInstance()->getSource(); + if (LLToolDragAndDrop::SOURCE_NOTECARD == source) + { + // We currently do not handle dragging items from one notecard to another + // since items in a notecard must be in Inventory to be verified. See DEV-2891. + return false; + } + + if (getEnabled() && acceptsTextInput()) + { + bool supported = false; + switch( cargo_type ) + { + case DAD_SETTINGS: + { + supported = LLEnvironment::instance().isExtendedEnvironmentEnabled(); + if (!supported && tooltip_msg.empty()) + { + tooltip_msg.assign(LLTrans::getString("TooltipNotecardNotAllowedTypeDrop")); + } + break; + } + case DAD_CALLINGCARD: + case DAD_TEXTURE: + case DAD_SOUND: + case DAD_LANDMARK: + case DAD_SCRIPT: + case DAD_CLOTHING: + case DAD_OBJECT: + case DAD_NOTECARD: + case DAD_BODYPART: + case DAD_ANIMATION: + case DAD_GESTURE: + case DAD_MESH: + case DAD_MATERIAL: + { + supported = true; + break; + } + + default: + supported = false; + break; + } + + LLInventoryItem *item = (LLInventoryItem *)cargo_data; + if (item && allowsEmbeddedItems() && supported) + { + U32 mask_next = item->getPermissions().getMaskNextOwner(); + if((mask_next & PERM_ITEM_UNRESTRICTED) == PERM_ITEM_UNRESTRICTED) + { + if( drop ) + { + deselect(); + S32 old_cursor = mCursorPos; + setCursorAtLocalPos( x, y, true ); + S32 insert_pos = mCursorPos; + setCursorPos(old_cursor); + bool inserted = insertEmbeddedItem( insert_pos, item ); + if( inserted && (old_cursor > mCursorPos) ) + { + setCursorPos(mCursorPos + 1); + } + + needsReflow(); + } + *accept = ACCEPT_YES_COPY_MULTI; + } + else + { + *accept = ACCEPT_NO; + if (tooltip_msg.empty()) + { + tooltip_msg.assign(LLTrans::getString("TooltipNotecardOwnerRestrictedDrop")); + } + } + } + else + { + *accept = ACCEPT_NO; + } + } + else + { + // Not enabled + *accept = ACCEPT_NO; + } + + handled = true; + LL_DEBUGS("UserInput") << "dragAndDrop handled by LLViewerTextEditor " << getName() << LL_ENDL; + + return handled; +} + +void LLViewerTextEditor::setASCIIEmbeddedText(const std::string& instr) +{ + LLWString wtext; + const U8* buffer = (U8*)(instr.c_str()); + while (*buffer) + { + llwchar wch; + U8 c = *buffer++; + if (c >= 0x80) + { + S32 index = (S32)(c - 0x80); + wch = mEmbeddedItemList->getEmbeddedCharFromIndex(index); + } + else + { + wch = (llwchar)c; + } + wtext.push_back(wch); + } + setWText(wtext); +} + +void LLViewerTextEditor::setEmbeddedText(const std::string& instr) +{ + LLWString wtext = utf8str_to_wstring(instr); + for (S32 i=0; i<(S32)wtext.size(); i++) + { + llwchar wch = wtext[i]; + if( wch >= FIRST_EMBEDDED_CHAR && wch <= LAST_EMBEDDED_CHAR ) + { + S32 index = wch - FIRST_EMBEDDED_CHAR; + wtext[i] = mEmbeddedItemList->getEmbeddedCharFromIndex(index); + } + } + setWText(wtext); +} + +std::string LLViewerTextEditor::getEmbeddedText() +{ +#if 1 + // New version (Version 2) + mEmbeddedItemList->copyUsedCharsToIndexed(); + LLWString outtextw; + for (S32 i=0; i<(S32)getWText().size(); i++) + { + llwchar wch = getWText()[i]; + if( wch >= FIRST_EMBEDDED_CHAR && wch <= LAST_EMBEDDED_CHAR ) + { + S32 index = mEmbeddedItemList->getIndexFromEmbeddedChar(wch); + wch = FIRST_EMBEDDED_CHAR + index; + } + outtextw.push_back(wch); + } + std::string outtext = wstring_to_utf8str(outtextw); + return outtext; +#else + // Old version (Version 1) + mEmbeddedItemList->copyUsedCharsToIndexed(); + std::string outtext; + for (S32 i=0; i<(S32)mWText.size(); i++) + { + llwchar wch = mWText[i]; + if( wch >= FIRST_EMBEDDED_CHAR && wch <= LAST_EMBEDDED_CHAR ) + { + S32 index = mEmbeddedItemList->getIndexFromEmbeddedChar(wch); + wch = 0x80 | index % 128; + } + else if (wch >= 0x80) + { + wch = LL_UNKNOWN_CHAR; + } + outtext.push_back((U8)wch); + } + return outtext; +#endif +} + +std::string LLViewerTextEditor::appendTime(bool prepend_newline) +{ + time_t utc_time; + utc_time = time_corrected(); + std::string timeStr ="[["+ LLTrans::getString("TimeHour")+"]:[" + +LLTrans::getString("TimeMin")+"]] "; + + LLSD substitution; + + substitution["datetime"] = (S32) utc_time; + LLStringUtil::format (timeStr, substitution); + appendText(timeStr, prepend_newline, LLStyle::Params().color(LLColor4::grey)); + blockUndo(); + + return timeStr; +} + +//---------------------------------------------------------------------------- +//---------------------------------------------------------------------------- + +llwchar LLViewerTextEditor::pasteEmbeddedItem(llwchar ext_char) +{ + if (mEmbeddedItemList->hasEmbeddedItem(ext_char)) + { + return ext_char; // already exists in my list + } + LLInventoryItem* item = LLEmbeddedItems::getEmbeddedItemPtr(ext_char); + if (item) + { + // Add item to my list and return new llwchar associated with it + llwchar new_wc; + if (mEmbeddedItemList->insertEmbeddedItem( item, &new_wc, true )) + { + return new_wc; + } + } + return LL_UNKNOWN_CHAR; // item not found or list full +} + +void LLViewerTextEditor::onValueChange(S32 start, S32 end) +{ + updateSegments(); + updateLinkSegments(); + findEmbeddedItemSegments(start, end); +} + +void LLViewerTextEditor::findEmbeddedItemSegments(S32 start, S32 end) +{ + LLWString text = getWText(); + + // Start with i just after the first embedded item + for(S32 idx = start; idx < end; idx++ ) + { + llwchar embedded_char = text[idx]; + if( embedded_char >= FIRST_EMBEDDED_CHAR + && embedded_char <= LAST_EMBEDDED_CHAR + && mEmbeddedItemList->hasEmbeddedItem(embedded_char) ) + { + LLInventoryItem* itemp = mEmbeddedItemList->getEmbeddedItemPtr(embedded_char); + LLUIImagePtr image = mEmbeddedItemList->getItemImage(embedded_char); + insertSegment(new LLEmbeddedItemSegment(idx, image, itemp, *this)); + } + } +} + +bool LLViewerTextEditor::openEmbeddedItemAtPos(S32 pos) +{ + if( pos < getLength()) + { + llwchar wc = getWText()[pos]; + LLPointer item = LLEmbeddedItems::getEmbeddedItemPtr( wc ); + if( item ) + { + bool saved = LLEmbeddedItems::getEmbeddedItemSaved( wc ); + if (saved) + { + return openEmbeddedItem(item, wc); + } + else + { + showUnsavedAlertDialog(item); + } + } + } + return false; +} + + +bool LLViewerTextEditor::openEmbeddedItem(LLPointer item, llwchar wc) +{ + + switch( item->getType() ) + { + case LLAssetType::AT_TEXTURE: + openEmbeddedTexture( item, wc ); + return true; + + case LLAssetType::AT_SOUND: + openEmbeddedSound( item, wc ); + return true; + + case LLAssetType::AT_LANDMARK: + openEmbeddedLandmark( item, wc ); + return true; + + case LLAssetType::AT_CALLINGCARD: + openEmbeddedCallingcard( item, wc ); + return true; + case LLAssetType::AT_SETTINGS: + openEmbeddedSetting(item, wc); + return true; + case LLAssetType::AT_MATERIAL: + openEmbeddedGLTFMaterial(item, wc); + return true; + case LLAssetType::AT_NOTECARD: + case LLAssetType::AT_LSL_TEXT: + case LLAssetType::AT_CLOTHING: + case LLAssetType::AT_OBJECT: + case LLAssetType::AT_BODYPART: + case LLAssetType::AT_ANIMATION: + case LLAssetType::AT_GESTURE: + showCopyToInvDialog( item, wc ); + return true; + default: + return false; + } + +} + + +void LLViewerTextEditor::openEmbeddedTexture( LLInventoryItem* item, llwchar wc ) +{ + // *NOTE: Just for embedded Texture , we should use getAssetUUID(), + // not getUUID(), because LLPreviewTexture pass in AssetUUID into + // LLPreview constructor ItemUUID parameter. + if (!item) + return; + LLPreviewTexture* preview = LLFloaterReg::showTypedInstance("preview_texture", LLSD(item->getAssetUUID()), TAKE_FOCUS_YES); + if (preview) + { + preview->setAuxItem( item ); + preview->setNotecardInfo(mNotecardInventoryID, mObjectID); + if (preview->hasString("Title")) + { + LLStringUtil::format_map_t args; + args["[NAME]"] = item->getName(); + LLUIString title = preview->getString("Title", args); + preview->setTitle(title.getString()); + } + preview->getChild("desc")->setValue(item->getDescription()); + } +} + +void LLViewerTextEditor::openEmbeddedSound( LLInventoryItem* item, llwchar wc ) +{ + // Play sound locally + LLVector3d lpos_global = gAgent.getPositionGlobal(); + const F32 SOUND_GAIN = 1.0f; + if(gAudiop) + { + gAudiop->triggerSound(item->getAssetUUID(), gAgentID, SOUND_GAIN, LLAudioEngine::AUDIO_TYPE_UI, lpos_global); + } + showCopyToInvDialog( item, wc ); +} + + +void LLViewerTextEditor::openEmbeddedLandmark( LLPointer item_ptr, llwchar wc ) +{ + if (item_ptr.isNull()) + return; + + LLLandmark* landmark = gLandmarkList.getAsset(item_ptr->getAssetUUID(), + boost::bind(&LLEmbeddedLandmarkCopied::processForeignLandmark, _1, mObjectID, mNotecardInventoryID, item_ptr)); + if (landmark) + { + LLEmbeddedLandmarkCopied::processForeignLandmark(landmark, mObjectID, + mNotecardInventoryID, item_ptr); + } +} + +void LLViewerTextEditor::openEmbeddedCallingcard( LLInventoryItem* item, llwchar wc ) +{ + if (item && !item->getDescription().empty()) + { + LLAvatarActions::showProfile(LLUUID(item->getDescription())); + } + else if (item && !item->getCreatorUUID().isNull()) + { + LLAvatarActions::showProfile(item->getCreatorUUID()); + } +} + +void LLViewerTextEditor::openEmbeddedSetting(LLInventoryItem* item, llwchar wc) +{ + if (LLEnvironment::instance().isInventoryEnabled()) + { + showCopyToInvDialog(item, wc); + } + else + { + LLNotificationsUtil::add("NoEnvironmentSettings"); + } +} + +void LLViewerTextEditor::openEmbeddedGLTFMaterial(LLInventoryItem* item, llwchar wc) +{ + if (!item) + { + return; + } + + LLSD floater_key; + floater_key["objectid"] = mObjectID; + floater_key["notecardid"] = mNotecardInventoryID; + LLMaterialEditor* preview = LLFloaterReg::getTypedInstance("material_editor", floater_key); + if (preview) + { + preview->setAuxItem(item); + preview->setNotecardInfo(mNotecardInventoryID, mObjectID); + preview->openFloater(floater_key); + preview->setFocus(true); + } +} + +void LLViewerTextEditor::showUnsavedAlertDialog( LLInventoryItem* item ) +{ + LLSD payload; + payload["item_id"] = item->getUUID(); + payload["notecard_id"] = mNotecardInventoryID; + LLNotificationsUtil::add( "ConfirmNotecardSave", LLSD(), payload, LLViewerTextEditor::onNotecardDialog); +} + +// static +bool LLViewerTextEditor::onNotecardDialog(const LLSD& notification, const LLSD& response ) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if( option == 0 ) + { + LLPreviewNotecard* preview = LLFloaterReg::findTypedInstance("preview_notecard", notification["payload"]["notecard_id"]);; + if (preview) + { + preview->saveItem(); + } + } + return false; +} + + + +void LLViewerTextEditor::showCopyToInvDialog( LLInventoryItem* item, llwchar wc ) +{ + LLSD payload; + LLUUID item_id = item->getUUID(); + payload["item_id"] = item_id; + payload["item_wc"] = LLSD::Integer(wc); + LLNotificationsUtil::add( "ConfirmItemCopy", LLSD(), payload, + boost::bind(&LLViewerTextEditor::onCopyToInvDialog, this, _1, _2)); +} + +bool LLViewerTextEditor::onCopyToInvDialog(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if( 0 == option ) + { + llwchar wc = llwchar(notification["payload"]["item_wc"].asInteger()); + LLInventoryItem* itemp = LLEmbeddedItems::getEmbeddedItemPtr(wc); + if (itemp) + copyInventory(itemp); + } + return false; +} + + + +// Returns change in number of characters in mWText +S32 LLViewerTextEditor::insertEmbeddedItem( S32 pos, LLInventoryItem* item ) +{ + return execute( new TextCmdInsertEmbeddedItem( pos, item ) ); +} + +bool LLViewerTextEditor::importStream(std::istream& str) +{ + LLNotecard nc(LLNotecard::MAX_SIZE); + bool success = nc.importStream(str); + if (success) + { + mEmbeddedItemList->clear(); + const std::vector >& items = nc.getItems(); + mEmbeddedItemList->addItems(items); + // Actually set the text + if (allowsEmbeddedItems()) + { + if (nc.getVersion() == 1) + setASCIIEmbeddedText( nc.getText() ); + else + setEmbeddedText( nc.getText() ); + } + else + { + setText( nc.getText() ); + } + } + return success; +} + +void LLViewerTextEditor::copyInventory(const LLInventoryItem* item, U32 callback_id) +{ + copy_inventory_from_notecard(LLUUID::null, // Don't specify a destination -- let the sim do that + mObjectID, + mNotecardInventoryID, + item, + callback_id); +} + +bool LLViewerTextEditor::hasEmbeddedInventory() +{ + return ! mEmbeddedItemList->empty(); +} + +//////////////////////////////////////////////////////////////////////////// + +bool LLViewerTextEditor::importBuffer( const char* buffer, S32 length ) +{ + LLMemoryStream str((U8*)buffer, length); + return importStream(str); +} + +bool LLViewerTextEditor::exportBuffer( std::string& buffer ) +{ + LLNotecard nc(LLNotecard::MAX_SIZE); + + // Get the embedded text and update the item list to just be the used items + nc.setText(getEmbeddedText()); + + // Now get the used items and copy the list to the notecard + std::vector > embedded_items; + mEmbeddedItemList->getEmbeddedItemList(embedded_items); + nc.setItems(embedded_items); + + std::stringstream out_stream; + nc.exportStream(out_stream); + + buffer = out_stream.str(); + + return true; +} + diff --git a/indra/newview/llviewertexteditor.h b/indra/newview/llviewertexteditor.h index d83c5118d0..42f69dccf3 100644 --- a/indra/newview/llviewertexteditor.h +++ b/indra/newview/llviewertexteditor.h @@ -1,137 +1,137 @@ -/** - * @file llviewertexteditor.h - * @brief Text editor widget to let users enter a multi-line document// - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_VIEWERTEXTEDITOR_H -#define LL_VIEWERTEXTEDITOR_H - -#include "lltexteditor.h" - -// -// Classes -// -class LLViewerTextEditor : public LLTextEditor -{ -public: - struct Params : public LLInitParam::Block - {}; - -protected: - LLViewerTextEditor(const Params&); - friend class LLUICtrlFactory; - -public: - virtual ~LLViewerTextEditor(); - - virtual void makePristine(); - - /*virtual*/ void onVisibilityChange( bool new_visibility ); - - // mousehandler overrides - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - virtual bool handleHover(S32 x, S32 y, MASK mask); - virtual bool handleDoubleClick(S32 x, S32 y, MASK mask ); - - virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, - bool drop, EDragAndDropType cargo_type, - void *cargo_data, EAcceptance *accept, std::string& tooltip_msg); - - const class LLInventoryItem* getDragItem() const { return mDragItem; } - virtual bool importBuffer(const char* buffer, S32 length); - virtual bool importStream(std::istream& str); - virtual bool exportBuffer(std::string& buffer); - virtual void onValueChange(S32 start, S32 end); - - void setNotecardInfo(const LLUUID& notecard_item_id, const LLUUID& object_id, const LLUUID& preview_id) - { - mNotecardInventoryID = notecard_item_id; - mObjectID = object_id; - mPreviewID = preview_id; - } - void setNotecardObjectID(const LLUUID& object_id){ mObjectID = object_id;} - - void setASCIIEmbeddedText(const std::string& instr); - void setEmbeddedText(const std::string& instr); - std::string getEmbeddedText(); - - // Appends Second Life time, small font, grey. - // If this starts a line, you need to prepend a newline. - std::string appendTime(bool prepend_newline); - - void copyInventory(const LLInventoryItem* item, U32 callback_id = 0); - - // returns true if there is embedded inventory. - // *HACK: This is only useful because the notecard verifier may - // change the asset if there is embedded inventory. This mechanism - // should be changed to get a different asset id from the verifier - // rather than checking if a re-load is necessary. Phoenix 2007-02-27 - bool hasEmbeddedInventory(); - -private: - // Embedded object operations - void findEmbeddedItemSegments(S32 start, S32 end); - virtual llwchar pasteEmbeddedItem(llwchar ext_char); - - bool openEmbeddedItemAtPos( S32 pos ); - bool openEmbeddedItem(LLPointer item, llwchar wc); - - S32 insertEmbeddedItem(S32 pos, LLInventoryItem* item); - - // *NOTE: most of openEmbeddedXXX methods except openEmbeddedLandmark take pointer to LLInventoryItem. - // Be sure they don't bind it to callback function to avoid situation when it gets invalid when - // callback is trigged after text editor is closed. See EXT-8459. - void openEmbeddedTexture( LLInventoryItem* item, llwchar wc ); - void openEmbeddedSound( LLInventoryItem* item, llwchar wc ); - void openEmbeddedLandmark( LLPointer item_ptr, llwchar wc ); - void openEmbeddedCallingcard( LLInventoryItem* item, llwchar wc); - void openEmbeddedSetting(LLInventoryItem* item, llwchar wc); - void openEmbeddedGLTFMaterial(LLInventoryItem* item, llwchar wc); - void showCopyToInvDialog( LLInventoryItem* item, llwchar wc ); - void showUnsavedAlertDialog( LLInventoryItem* item ); - - bool onCopyToInvDialog(const LLSD& notification, const LLSD& response ); - static bool onNotecardDialog(const LLSD& notification, const LLSD& response ); - - LLPointer mDragItem; - LLTextSegment* mDragSegment; - llwchar mDragItemChar; - bool mDragItemSaved; - class LLEmbeddedItems* mEmbeddedItemList; - - LLUUID mObjectID; - LLUUID mNotecardInventoryID; - LLUUID mPreviewID; - - LLPointer mInventoryCallback; - - // - // Inner classes - // - - class TextCmdInsertEmbeddedItem; -}; - -#endif // LL_VIEWERTEXTEDITOR_H +/** + * @file llviewertexteditor.h + * @brief Text editor widget to let users enter a multi-line document// + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_VIEWERTEXTEDITOR_H +#define LL_VIEWERTEXTEDITOR_H + +#include "lltexteditor.h" + +// +// Classes +// +class LLViewerTextEditor : public LLTextEditor +{ +public: + struct Params : public LLInitParam::Block + {}; + +protected: + LLViewerTextEditor(const Params&); + friend class LLUICtrlFactory; + +public: + virtual ~LLViewerTextEditor(); + + virtual void makePristine(); + + /*virtual*/ void onVisibilityChange( bool new_visibility ); + + // mousehandler overrides + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + virtual bool handleHover(S32 x, S32 y, MASK mask); + virtual bool handleDoubleClick(S32 x, S32 y, MASK mask ); + + virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, + bool drop, EDragAndDropType cargo_type, + void *cargo_data, EAcceptance *accept, std::string& tooltip_msg); + + const class LLInventoryItem* getDragItem() const { return mDragItem; } + virtual bool importBuffer(const char* buffer, S32 length); + virtual bool importStream(std::istream& str); + virtual bool exportBuffer(std::string& buffer); + virtual void onValueChange(S32 start, S32 end); + + void setNotecardInfo(const LLUUID& notecard_item_id, const LLUUID& object_id, const LLUUID& preview_id) + { + mNotecardInventoryID = notecard_item_id; + mObjectID = object_id; + mPreviewID = preview_id; + } + void setNotecardObjectID(const LLUUID& object_id){ mObjectID = object_id;} + + void setASCIIEmbeddedText(const std::string& instr); + void setEmbeddedText(const std::string& instr); + std::string getEmbeddedText(); + + // Appends Second Life time, small font, grey. + // If this starts a line, you need to prepend a newline. + std::string appendTime(bool prepend_newline); + + void copyInventory(const LLInventoryItem* item, U32 callback_id = 0); + + // returns true if there is embedded inventory. + // *HACK: This is only useful because the notecard verifier may + // change the asset if there is embedded inventory. This mechanism + // should be changed to get a different asset id from the verifier + // rather than checking if a re-load is necessary. Phoenix 2007-02-27 + bool hasEmbeddedInventory(); + +private: + // Embedded object operations + void findEmbeddedItemSegments(S32 start, S32 end); + virtual llwchar pasteEmbeddedItem(llwchar ext_char); + + bool openEmbeddedItemAtPos( S32 pos ); + bool openEmbeddedItem(LLPointer item, llwchar wc); + + S32 insertEmbeddedItem(S32 pos, LLInventoryItem* item); + + // *NOTE: most of openEmbeddedXXX methods except openEmbeddedLandmark take pointer to LLInventoryItem. + // Be sure they don't bind it to callback function to avoid situation when it gets invalid when + // callback is trigged after text editor is closed. See EXT-8459. + void openEmbeddedTexture( LLInventoryItem* item, llwchar wc ); + void openEmbeddedSound( LLInventoryItem* item, llwchar wc ); + void openEmbeddedLandmark( LLPointer item_ptr, llwchar wc ); + void openEmbeddedCallingcard( LLInventoryItem* item, llwchar wc); + void openEmbeddedSetting(LLInventoryItem* item, llwchar wc); + void openEmbeddedGLTFMaterial(LLInventoryItem* item, llwchar wc); + void showCopyToInvDialog( LLInventoryItem* item, llwchar wc ); + void showUnsavedAlertDialog( LLInventoryItem* item ); + + bool onCopyToInvDialog(const LLSD& notification, const LLSD& response ); + static bool onNotecardDialog(const LLSD& notification, const LLSD& response ); + + LLPointer mDragItem; + LLTextSegment* mDragSegment; + llwchar mDragItemChar; + bool mDragItemSaved; + class LLEmbeddedItems* mEmbeddedItemList; + + LLUUID mObjectID; + LLUUID mNotecardInventoryID; + LLUUID mPreviewID; + + LLPointer mInventoryCallback; + + // + // Inner classes + // + + class TextCmdInsertEmbeddedItem; +}; + +#endif // LL_VIEWERTEXTEDITOR_H diff --git a/indra/newview/llviewertexture.cpp b/indra/newview/llviewertexture.cpp index 24b7a6586f..fbb423358b 100644 --- a/indra/newview/llviewertexture.cpp +++ b/indra/newview/llviewertexture.cpp @@ -1,4108 +1,4108 @@ - -/** - * @file llviewertexture.cpp - * @brief Object which handles a received image (and associated texture(s)) - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llviewertexture.h" - -// Library includes -#include "llmath.h" -#include "llerror.h" -#include "llgl.h" -#include "llglheaders.h" -#include "llhost.h" -#include "llimage.h" -#include "llimagebmp.h" -#include "llimagej2c.h" -#include "llimagetga.h" -#include "llstl.h" -#include "message.h" -#include "lltimer.h" - -// viewer includes -#include "llimagegl.h" -#include "lldrawpool.h" -#include "lltexturefetch.h" -#include "llviewertexturelist.h" -#include "llviewercontrol.h" -#include "pipeline.h" -#include "llappviewer.h" -#include "llface.h" -#include "llviewercamera.h" -#include "lltextureentry.h" -#include "lltexturemanagerbridge.h" -#include "llmediaentry.h" -#include "llvovolume.h" -#include "llviewermedia.h" -#include "lltexturecache.h" -#include "llviewerwindow.h" -#include "llwindow.h" -/////////////////////////////////////////////////////////////////////////////// - -// extern -const S32Megabytes gMinVideoRam(32); -const S32Megabytes gMaxVideoRam(512); - - -// statics -LLPointer LLViewerTexture::sNullImagep = NULL; -LLPointer LLViewerTexture::sBlackImagep = NULL; -LLPointer LLViewerTexture::sCheckerBoardImagep = NULL; -LLPointer LLViewerFetchedTexture::sMissingAssetImagep = NULL; -LLPointer LLViewerFetchedTexture::sWhiteImagep = NULL; -LLPointer LLViewerFetchedTexture::sDefaultImagep = NULL; -LLPointer LLViewerFetchedTexture::sSmokeImagep = NULL; -LLPointer LLViewerFetchedTexture::sFlatNormalImagep = NULL; -LLPointer LLViewerFetchedTexture::sDefaultIrradiancePBRp; -LLViewerMediaTexture::media_map_t LLViewerMediaTexture::sMediaMap; -LLTexturePipelineTester* LLViewerTextureManager::sTesterp = NULL; -F32 LLViewerFetchedTexture::sMaxVirtualSize = 8192.f*8192.f; - -const std::string sTesterName("TextureTester"); - -S32 LLViewerTexture::sImageCount = 0; -S32 LLViewerTexture::sRawCount = 0; -S32 LLViewerTexture::sAuxCount = 0; -LLFrameTimer LLViewerTexture::sEvaluationTimer; -F32 LLViewerTexture::sDesiredDiscardBias = 0.f; -F32 LLViewerTexture::sDesiredDiscardScale = 1.1f; -S32 LLViewerTexture::sMaxSculptRez = 128; //max sculpt image size -const S32 MAX_CACHED_RAW_IMAGE_AREA = 64 * 64; -const S32 MAX_CACHED_RAW_SCULPT_IMAGE_AREA = LLViewerTexture::sMaxSculptRez * LLViewerTexture::sMaxSculptRez; -const S32 MAX_CACHED_RAW_TERRAIN_IMAGE_AREA = 128 * 128; -const S32 DEFAULT_ICON_DIMENSIONS = 32; -const S32 DEFAULT_THUMBNAIL_DIMENSIONS = 256; -U32 LLViewerTexture::sMinLargeImageSize = 65536; //256 * 256. -U32 LLViewerTexture::sMaxSmallImageSize = MAX_CACHED_RAW_IMAGE_AREA; -bool LLViewerTexture::sFreezeImageUpdates = false; -F32 LLViewerTexture::sCurrentTime = 0.0f; - -constexpr F32 MIN_VRAM_BUDGET = 768.f; -F32 LLViewerTexture::sFreeVRAMMegabytes = MIN_VRAM_BUDGET; - -LLViewerTexture::EDebugTexels LLViewerTexture::sDebugTexelsMode = LLViewerTexture::DEBUG_TEXELS_OFF; - -const F64 log_2 = log(2.0); - -#if ADDRESS_SIZE == 32 -const U32 DESIRED_NORMAL_TEXTURE_SIZE = (U32)LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT / 2; -#else -const U32 DESIRED_NORMAL_TEXTURE_SIZE = (U32)LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT; -#endif - -//---------------------------------------------------------------------------------------------- -//namespace: LLViewerTextureAccess -//---------------------------------------------------------------------------------------------- - -LLLoadedCallbackEntry::LLLoadedCallbackEntry(loaded_callback_func cb, - S32 discard_level, - bool need_imageraw, // Needs image raw for the callback - void* userdata, - LLLoadedCallbackEntry::source_callback_list_t* src_callback_list, - LLViewerFetchedTexture* target, - bool pause) - : mCallback(cb), - mLastUsedDiscard(MAX_DISCARD_LEVEL+1), - mDesiredDiscard(discard_level), - mNeedsImageRaw(need_imageraw), - mUserData(userdata), - mSourceCallbackList(src_callback_list), - mPaused(pause) -{ - if(mSourceCallbackList) - { - mSourceCallbackList->insert(LLTextureKey(target->getID(), (ETexListType)target->getTextureListType())); - } -} - -LLLoadedCallbackEntry::~LLLoadedCallbackEntry() -{ -} - -void LLLoadedCallbackEntry::removeTexture(LLViewerFetchedTexture* tex) -{ - if (mSourceCallbackList && tex) - { - mSourceCallbackList->erase(LLTextureKey(tex->getID(), (ETexListType)tex->getTextureListType())); - } -} - -//static -void LLLoadedCallbackEntry::cleanUpCallbackList(LLLoadedCallbackEntry::source_callback_list_t* callback_list) -{ - //clear texture callbacks. - if(callback_list && !callback_list->empty()) - { - for(LLLoadedCallbackEntry::source_callback_list_t::iterator iter = callback_list->begin(); - iter != callback_list->end(); ++iter) - { - LLViewerFetchedTexture* tex = gTextureList.findImage(*iter); - if(tex) - { - tex->deleteCallbackEntry(callback_list); - } - } - callback_list->clear(); - } -} - -LLViewerMediaTexture* LLViewerTextureManager::createMediaTexture(const LLUUID &media_id, bool usemipmaps, LLImageGL* gl_image) -{ - return new LLViewerMediaTexture(media_id, usemipmaps, gl_image); -} - -void LLViewerTextureManager::findFetchedTextures(const LLUUID& id, std::vector &output) -{ - return gTextureList.findTexturesByID(id, output); -} - -void LLViewerTextureManager::findTextures(const LLUUID& id, std::vector &output) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - std::vector fetched_output; - gTextureList.findTexturesByID(id, fetched_output); - std::vector::iterator iter = fetched_output.begin(); - while (iter != fetched_output.end()) - { - output.push_back(*iter); - iter++; - } - - //search media texture list - if (output.empty()) - { - LLViewerTexture* tex; - tex = LLViewerTextureManager::findMediaTexture(id); - if (tex) - { - output.push_back(tex); - } - } - -} - -LLViewerFetchedTexture* LLViewerTextureManager::findFetchedTexture(const LLUUID& id, S32 tex_type) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - return gTextureList.findImage(id, (ETexListType)tex_type); -} - -LLViewerMediaTexture* LLViewerTextureManager::findMediaTexture(const LLUUID &media_id) -{ - return LLViewerMediaTexture::findMediaTexture(media_id); -} - -LLViewerMediaTexture* LLViewerTextureManager::getMediaTexture(const LLUUID& id, bool usemipmaps, LLImageGL* gl_image) -{ - LLViewerMediaTexture* tex = LLViewerMediaTexture::findMediaTexture(id); - if(!tex) - { - tex = LLViewerTextureManager::createMediaTexture(id, usemipmaps, gl_image); - } - - tex->initVirtualSize(); - - return tex; -} - -LLViewerFetchedTexture* LLViewerTextureManager::staticCastToFetchedTexture(LLTexture* tex, bool report_error) -{ - if(!tex) - { - return NULL; - } - - S8 type = tex->getType(); - if(type == LLViewerTexture::FETCHED_TEXTURE || type == LLViewerTexture::LOD_TEXTURE) - { - return static_cast(tex); - } - - if(report_error) - { - LL_ERRS() << "not a fetched texture type: " << type << LL_ENDL; - } - - return NULL; -} - -LLPointer LLViewerTextureManager::getLocalTexture(bool usemipmaps, bool generate_gl_tex) -{ - LLPointer tex = new LLViewerTexture(usemipmaps); - if(generate_gl_tex) - { - tex->generateGLTexture(); - tex->setCategory(LLGLTexture::LOCAL); - } - return tex; -} -LLPointer LLViewerTextureManager::getLocalTexture(const LLUUID& id, bool usemipmaps, bool generate_gl_tex) -{ - LLPointer tex = new LLViewerTexture(id, usemipmaps); - if(generate_gl_tex) - { - tex->generateGLTexture(); - tex->setCategory(LLGLTexture::LOCAL); - } - return tex; -} -LLPointer LLViewerTextureManager::getLocalTexture(const LLImageRaw* raw, bool usemipmaps) -{ - LLPointer tex = new LLViewerTexture(raw, usemipmaps); - tex->setCategory(LLGLTexture::LOCAL); - return tex; -} -LLPointer LLViewerTextureManager::getLocalTexture(const U32 width, const U32 height, const U8 components, bool usemipmaps, bool generate_gl_tex) -{ - LLPointer tex = new LLViewerTexture(width, height, components, usemipmaps); - if(generate_gl_tex) - { - tex->generateGLTexture(); - tex->setCategory(LLGLTexture::LOCAL); - } - return tex; -} - -LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTexture(const LLImageRaw* raw, FTType type, bool usemipmaps) -{ - LLImageDataSharedLock lock(raw); - LLViewerFetchedTexture* ret = new LLViewerFetchedTexture(raw, type, usemipmaps); - gTextureList.addImage(ret, TEX_LIST_STANDARD); - return ret; -} - -LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTexture( - const LLUUID &image_id, - FTType f_type, - bool usemipmaps, - LLViewerTexture::EBoostLevel boost_priority, - S8 texture_type, - LLGLint internal_format, - LLGLenum primary_format, - LLHost request_from_host) -{ - return gTextureList.getImage(image_id, f_type, usemipmaps, boost_priority, texture_type, internal_format, primary_format, request_from_host); -} - -LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTextureFromFile( - const std::string& filename, - FTType f_type, - bool usemipmaps, - LLViewerTexture::EBoostLevel boost_priority, - S8 texture_type, - LLGLint internal_format, - LLGLenum primary_format, - const LLUUID& force_id) -{ - return gTextureList.getImageFromFile(filename, f_type, usemipmaps, boost_priority, texture_type, internal_format, primary_format, force_id); -} - -//static -LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTextureFromUrl(const std::string& url, - FTType f_type, - bool usemipmaps, - LLViewerTexture::EBoostLevel boost_priority, - S8 texture_type, - LLGLint internal_format, - LLGLenum primary_format, - const LLUUID& force_id - ) -{ - return gTextureList.getImageFromUrl(url, f_type, usemipmaps, boost_priority, texture_type, internal_format, primary_format, force_id); -} - -LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTextureFromHost(const LLUUID& image_id, FTType f_type, LLHost host) -{ - return gTextureList.getImageFromHost(image_id, f_type, host); -} - -// Create a bridge to the viewer texture manager. -class LLViewerTextureManagerBridge : public LLTextureManagerBridge -{ - /*virtual*/ LLPointer getLocalTexture(bool usemipmaps = true, bool generate_gl_tex = true) - { - return LLViewerTextureManager::getLocalTexture(usemipmaps, generate_gl_tex); - } - - /*virtual*/ LLPointer getLocalTexture(const U32 width, const U32 height, const U8 components, bool usemipmaps, bool generate_gl_tex = true) - { - return LLViewerTextureManager::getLocalTexture(width, height, components, usemipmaps, generate_gl_tex); - } - - /*virtual*/ LLGLTexture* getFetchedTexture(const LLUUID &image_id) - { - return LLViewerTextureManager::getFetchedTexture(image_id); - } -}; - - -void LLViewerTextureManager::init() -{ - { - LLPointer raw = new LLImageRaw(1,1,3); - raw->clear(0x77, 0x77, 0x77, 0xFF); - LLViewerTexture::sNullImagep = LLViewerTextureManager::getLocalTexture(raw.get(), true); - } - - const S32 dim = 128; - LLPointer image_raw = new LLImageRaw(dim,dim,3); - U8* data = image_raw->getData(); - - memset(data, 0, dim * dim * 3); - LLViewerTexture::sBlackImagep = LLViewerTextureManager::getLocalTexture(image_raw.get(), true); - -#if 1 - LLPointer imagep = LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT); - LLViewerFetchedTexture::sDefaultImagep = imagep; - - for (S32 i = 0; i=(dim-border) || j>=(dim-border)) - { - *data++ = 0xff; - *data++ = 0xff; - *data++ = 0xff; - } - else -#endif - { - *data++ = 0x7f; - *data++ = 0x7f; - *data++ = 0x7f; - } - } - } - imagep->createGLTexture(0, image_raw); - //cache the raw image - imagep->setCachedRawImage(0, image_raw); - image_raw = NULL; -#else - LLViewerFetchedTexture::sDefaultImagep = LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT, true, LLGLTexture::BOOST_UI); -#endif - LLViewerFetchedTexture::sDefaultImagep->dontDiscard(); - LLViewerFetchedTexture::sDefaultImagep->setCategory(LLGLTexture::OTHER); - - image_raw = new LLImageRaw(32,32,3); - data = image_raw->getData(); - - for (S32 i = 0; i < (32*32*3); i+=3) - { - S32 x = (i % (32*3)) / (3*16); - S32 y = i / (32*3*16); - U8 color = ((x + y) % 2) * 255; - data[i] = color; - data[i+1] = color; - data[i+2] = color; - } - - LLViewerTexture::sCheckerBoardImagep = LLViewerTextureManager::getLocalTexture(image_raw.get(), true); - - LLViewerTexture::initClass(); - - // Create a texture manager bridge. - gTextureManagerBridgep = new LLViewerTextureManagerBridge; - - if (LLMetricPerformanceTesterBasic::isMetricLogRequested(sTesterName) && !LLMetricPerformanceTesterBasic::getTester(sTesterName)) - { - sTesterp = new LLTexturePipelineTester(); - if (!sTesterp->isValid()) - { - delete sTesterp; - sTesterp = NULL; - } - } -} - -void LLViewerTextureManager::cleanup() -{ - stop_glerror(); - - delete gTextureManagerBridgep; - LLImageGL::sDefaultGLTexture = NULL; - LLViewerTexture::sNullImagep = NULL; - LLViewerTexture::sBlackImagep = NULL; - LLViewerTexture::sCheckerBoardImagep = NULL; - LLViewerFetchedTexture::sDefaultImagep = NULL; - LLViewerFetchedTexture::sSmokeImagep = NULL; - LLViewerFetchedTexture::sMissingAssetImagep = NULL; - LLTexUnit::sWhiteTexture = 0; - LLViewerFetchedTexture::sWhiteImagep = NULL; - - LLViewerFetchedTexture::sFlatNormalImagep = NULL; - LLViewerFetchedTexture::sDefaultIrradiancePBRp = NULL; - - LLViewerMediaTexture::cleanUpClass(); -} - -//---------------------------------------------------------------------------------------------- -//---------------------------------------------------------------------------------------------- -//start of LLViewerTexture -//---------------------------------------------------------------------------------------------- -// static -void LLViewerTexture::initClass() -{ - LLImageGL::sDefaultGLTexture = LLViewerFetchedTexture::sDefaultImagep->getGLTexture(); -} - -// non-const (used externally -F32 texmem_lower_bound_scale = 0.85f; -F32 texmem_middle_bound_scale = 0.925f; - -//static -void LLViewerTexture::updateClass() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - sCurrentTime = gFrameTimeSeconds; - - LLTexturePipelineTester* tester = (LLTexturePipelineTester*)LLMetricPerformanceTesterBasic::getTester(sTesterName); - if (tester) - { - tester->update(); - } - - LLViewerMediaTexture::updateClass(); - - static LLCachedControl max_vram_budget(gSavedSettings, "RenderMaxVRAMBudget", 0); - - F64 texture_bytes_alloc = LLImageGL::getTextureBytesAllocated() / 1024.0 / 512.0; - F64 vertex_bytes_alloc = LLVertexBuffer::getBytesAllocated() / 1024.0 / 512.0; - F64 render_bytes_alloc = LLRenderTarget::sBytesAllocated / 1024.0 / 512.0; - - // get an estimate of how much video memory we're using - // NOTE: our metrics miss about half the vram we use, so this biases high but turns out to typically be within 5% of the real number - F32 used = (F32)ll_round(texture_bytes_alloc + vertex_bytes_alloc + render_bytes_alloc); - - F32 budget = max_vram_budget == 0 ? gGLManager.mVRAM : max_vram_budget; - - // try to leave half a GB for everyone else, but keep at least 768MB for ourselves - F32 target = llmax(budget - 512.f, MIN_VRAM_BUDGET); - sFreeVRAMMegabytes = target - used; - - F32 over_pct = llmax((used-target) / target, 0.f); - sDesiredDiscardBias = llmax(sDesiredDiscardBias, 1.f + over_pct); - - if (sDesiredDiscardBias > 1.f) - { - sDesiredDiscardBias -= gFrameIntervalSeconds * 0.01; - } - - LLViewerTexture::sFreezeImageUpdates = false; // sDesiredDiscardBias > (desired_discard_bias_max - 1.0f); -} - -//end of static functions -//------------------------------------------------------------------------------------------- -const U32 LLViewerTexture::sCurrentFileVersion = 1; - -LLViewerTexture::LLViewerTexture(bool usemipmaps) : - LLGLTexture(usemipmaps) -{ - init(true); - - mID.generate(); - sImageCount++; -} - -LLViewerTexture::LLViewerTexture(const LLUUID& id, bool usemipmaps) : - LLGLTexture(usemipmaps), - mID(id) -{ - init(true); - - sImageCount++; -} - -LLViewerTexture::LLViewerTexture(const U32 width, const U32 height, const U8 components, bool usemipmaps) : - LLGLTexture(width, height, components, usemipmaps) -{ - init(true); - - mID.generate(); - sImageCount++; -} - -LLViewerTexture::LLViewerTexture(const LLImageRaw* raw, bool usemipmaps) : - LLGLTexture(raw, usemipmaps) -{ - init(true); - - mID.generate(); - sImageCount++; -} - -LLViewerTexture::~LLViewerTexture() -{ - // LL_DEBUGS("Avatar") << mID << LL_ENDL; - cleanup(); - sImageCount--; -} - -// virtual -void LLViewerTexture::init(bool firstinit) -{ - mMaxVirtualSize = 0.f; - mMaxVirtualSizeResetInterval = 1; - mMaxVirtualSizeResetCounter = mMaxVirtualSizeResetInterval; - mParcelMedia = NULL; - - memset(&mNumVolumes, 0, sizeof(U32)* LLRender::NUM_VOLUME_TEXTURE_CHANNELS); - mFaceList[LLRender::DIFFUSE_MAP].clear(); - mFaceList[LLRender::NORMAL_MAP].clear(); - mFaceList[LLRender::SPECULAR_MAP].clear(); - mNumFaces[LLRender::DIFFUSE_MAP] = - mNumFaces[LLRender::NORMAL_MAP] = - mNumFaces[LLRender::SPECULAR_MAP] = 0; - - mVolumeList[LLRender::LIGHT_TEX].clear(); - mVolumeList[LLRender::SCULPT_TEX].clear(); - - mMainQueue = LL::WorkQueue::getInstance("mainloop"); - mImageQueue = LL::WorkQueue::getInstance("LLImageGL"); -} - -//virtual -S8 LLViewerTexture::getType() const -{ - return LLViewerTexture::LOCAL_TEXTURE; -} - -void LLViewerTexture::cleanup() -{ - if (LLAppViewer::getTextureFetch()) - { - LLAppViewer::getTextureFetch()->updateRequestPriority(mID, 0.f); - } - - mFaceList[LLRender::DIFFUSE_MAP].clear(); - mFaceList[LLRender::NORMAL_MAP].clear(); - mFaceList[LLRender::SPECULAR_MAP].clear(); - mVolumeList[LLRender::LIGHT_TEX].clear(); - mVolumeList[LLRender::SCULPT_TEX].clear(); -} - -// virtual -void LLViewerTexture::dump() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - LLGLTexture::dump(); - - LL_INFOS() << "LLViewerTexture" - << " mID " << mID - << LL_ENDL; -} - -void LLViewerTexture::setBoostLevel(S32 level) -{ - if(mBoostLevel != level) - { - mBoostLevel = level; - if(mBoostLevel != LLViewerTexture::BOOST_NONE && - mBoostLevel != LLViewerTexture::BOOST_SELECTED && - mBoostLevel != LLViewerTexture::BOOST_ICON && - mBoostLevel != LLViewerTexture::BOOST_THUMBNAIL) - { - setNoDelete(); - } - } - - // strongly encourage anything boosted to load at full res - if (mBoostLevel >= LLViewerTexture::BOOST_HIGH) - { - mMaxVirtualSize = 2048.f * 2048.f; - } -} - -bool LLViewerTexture::isActiveFetching() -{ - return false; -} - -bool LLViewerTexture::bindDebugImage(const S32 stage) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if (stage < 0) return false; - - bool res = true; - if (LLViewerTexture::sCheckerBoardImagep.notNull() && (this != LLViewerTexture::sCheckerBoardImagep.get())) - { - res = gGL.getTexUnit(stage)->bind(LLViewerTexture::sCheckerBoardImagep); - } - - if(!res) - { - return bindDefaultImage(stage); - } - - return res; -} - -bool LLViewerTexture::bindDefaultImage(S32 stage) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if (stage < 0) return false; - - bool res = true; - if (LLViewerFetchedTexture::sDefaultImagep.notNull() && (this != LLViewerFetchedTexture::sDefaultImagep.get())) - { - // use default if we've got it - res = gGL.getTexUnit(stage)->bind(LLViewerFetchedTexture::sDefaultImagep); - } - if (!res && LLViewerTexture::sNullImagep.notNull() && (this != LLViewerTexture::sNullImagep)) - { - res = gGL.getTexUnit(stage)->bind(LLViewerTexture::sNullImagep); - } - if (!res) - { - LL_WARNS() << "LLViewerTexture::bindDefaultImage failed." << LL_ENDL; - } - stop_glerror(); - - //check if there is cached raw image and switch to it if possible - switchToCachedImage(); - - LLTexturePipelineTester* tester = (LLTexturePipelineTester*)LLMetricPerformanceTesterBasic::getTester(sTesterName); - if (tester) - { - tester->updateGrayTextureBinding(); - } - return res; -} - -//virtual -bool LLViewerTexture::isMissingAsset()const -{ - return false; -} - -//virtual -void LLViewerTexture::forceImmediateUpdate() -{ -} - -void LLViewerTexture::addTextureStats(F32 virtual_size, bool needs_gltexture) const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if(needs_gltexture) - { - mNeedsGLTexture = true; - } - - virtual_size = llmin(virtual_size, LLViewerFetchedTexture::sMaxVirtualSize); - - if (virtual_size > mMaxVirtualSize) - { - mMaxVirtualSize = virtual_size; - } -} - -void LLViewerTexture::resetTextureStats() -{ - mMaxVirtualSize = 0.0f; - mMaxVirtualSizeResetCounter = 0; -} - -//virtual -F32 LLViewerTexture::getMaxVirtualSize() -{ - return mMaxVirtualSize; -} - -//virtual -void LLViewerTexture::setKnownDrawSize(S32 width, S32 height) -{ - //nothing here. -} - -//virtual -void LLViewerTexture::addFace(U32 ch, LLFace* facep) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); - - if(mNumFaces[ch] >= mFaceList[ch].size()) - { - mFaceList[ch].resize(2 * mNumFaces[ch] + 1); - } - mFaceList[ch][mNumFaces[ch]] = facep; - facep->setIndexInTex(ch, mNumFaces[ch]); - mNumFaces[ch]++; - mLastFaceListUpdateTimer.reset(); -} - -//virtual -void LLViewerTexture::removeFace(U32 ch, LLFace* facep) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); - - if(mNumFaces[ch] > 1) - { - S32 index = facep->getIndexInTex(ch); - llassert(index < mFaceList[ch].size()); - llassert(index < mNumFaces[ch]); - mFaceList[ch][index] = mFaceList[ch][--mNumFaces[ch]]; - mFaceList[ch][index]->setIndexInTex(ch, index); - } - else - { - mFaceList[ch].clear(); - mNumFaces[ch] = 0; - } - mLastFaceListUpdateTimer.reset(); -} - -S32 LLViewerTexture::getTotalNumFaces() const -{ - S32 ret = 0; - - for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) - { - ret += mNumFaces[i]; - } - - return ret; -} - -S32 LLViewerTexture::getNumFaces(U32 ch) const -{ - llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); - return mNumFaces[ch]; -} - - -//virtual -void LLViewerTexture::addVolume(U32 ch, LLVOVolume* volumep) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if (mNumVolumes[ch] >= mVolumeList[ch].size()) - { - mVolumeList[ch].resize(2 * mNumVolumes[ch] + 1); - } - mVolumeList[ch][mNumVolumes[ch]] = volumep; - volumep->setIndexInTex(ch, mNumVolumes[ch]); - mNumVolumes[ch]++; - mLastVolumeListUpdateTimer.reset(); -} - -//virtual -void LLViewerTexture::removeVolume(U32 ch, LLVOVolume* volumep) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if (mNumVolumes[ch] > 1) - { - S32 index = volumep->getIndexInTex(ch); - llassert(index < mVolumeList[ch].size()); - llassert(index < mNumVolumes[ch]); - mVolumeList[ch][index] = mVolumeList[ch][--mNumVolumes[ch]]; - mVolumeList[ch][index]->setIndexInTex(ch, index); - } - else - { - mVolumeList[ch].clear(); - mNumVolumes[ch] = 0; - } - mLastVolumeListUpdateTimer.reset(); -} - -S32 LLViewerTexture::getNumVolumes(U32 ch) const -{ - return mNumVolumes[ch]; -} - -void LLViewerTexture::reorganizeFaceList() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - static const F32 MAX_WAIT_TIME = 20.f; // seconds - static const U32 MAX_EXTRA_BUFFER_SIZE = 4; - - if(mLastFaceListUpdateTimer.getElapsedTimeF32() < MAX_WAIT_TIME) - { - return; - } - - for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) - { - if(mNumFaces[i] + MAX_EXTRA_BUFFER_SIZE > mFaceList[i].size()) - { - return; - } - - mFaceList[i].erase(mFaceList[i].begin() + mNumFaces[i], mFaceList[i].end()); - } - - mLastFaceListUpdateTimer.reset(); -} - -void LLViewerTexture::reorganizeVolumeList() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - static const F32 MAX_WAIT_TIME = 20.f; // seconds - static const U32 MAX_EXTRA_BUFFER_SIZE = 4; - - - for (U32 i = 0; i < LLRender::NUM_VOLUME_TEXTURE_CHANNELS; ++i) - { - if (mNumVolumes[i] + MAX_EXTRA_BUFFER_SIZE > mVolumeList[i].size()) - { - return; - } - } - - if(mLastVolumeListUpdateTimer.getElapsedTimeF32() < MAX_WAIT_TIME) - { - return; - } - - mLastVolumeListUpdateTimer.reset(); - for (U32 i = 0; i < LLRender::NUM_VOLUME_TEXTURE_CHANNELS; ++i) - { - mVolumeList[i].erase(mVolumeList[i].begin() + mNumVolumes[i], mVolumeList[i].end()); - } -} - -//virtual -void LLViewerTexture::switchToCachedImage() -{ - //nothing here. -} - -//virtual -void LLViewerTexture::setCachedRawImage(S32 discard_level, LLImageRaw* imageraw) -{ - //nothing here. -} - -bool LLViewerTexture::isLargeImage() -{ - return (S32)mTexelsPerImage > LLViewerTexture::sMinLargeImageSize; -} - -//virtual -void LLViewerTexture::updateBindStatsForTester() -{ - LLTexturePipelineTester* tester = (LLTexturePipelineTester*)LLMetricPerformanceTesterBasic::getTester(sTesterName); - if (tester) - { - tester->updateTextureBindingStats(this); - } -} - -//---------------------------------------------------------------------------------------------- -//end of LLViewerTexture -//---------------------------------------------------------------------------------------------- - -const std::string& fttype_to_string(const FTType& fttype) -{ - static const std::string ftt_unknown("FTT_UNKNOWN"); - static const std::string ftt_default("FTT_DEFAULT"); - static const std::string ftt_server_bake("FTT_SERVER_BAKE"); - static const std::string ftt_host_bake("FTT_HOST_BAKE"); - static const std::string ftt_map_tile("FTT_MAP_TILE"); - static const std::string ftt_local_file("FTT_LOCAL_FILE"); - static const std::string ftt_error("FTT_ERROR"); - switch(fttype) - { - case FTT_UNKNOWN: return ftt_unknown; break; - case FTT_DEFAULT: return ftt_default; break; - case FTT_SERVER_BAKE: return ftt_server_bake; break; - case FTT_HOST_BAKE: return ftt_host_bake; break; - case FTT_MAP_TILE: return ftt_map_tile; break; - case FTT_LOCAL_FILE: return ftt_local_file; break; - } - return ftt_error; -} - -//---------------------------------------------------------------------------------------------- -//start of LLViewerFetchedTexture -//---------------------------------------------------------------------------------------------- - -//static -LLViewerFetchedTexture* LLViewerFetchedTexture::getSmokeImage() -{ - if (sSmokeImagep.isNull()) - { - sSmokeImagep = LLViewerTextureManager::getFetchedTexture(IMG_SMOKE); - } - - sSmokeImagep->addTextureStats(1024.f * 1024.f); - - return sSmokeImagep; -} - -LLViewerFetchedTexture::LLViewerFetchedTexture(const LLUUID& id, FTType f_type, const LLHost& host, bool usemipmaps) - : LLViewerTexture(id, usemipmaps), - mTargetHost(host) -{ - init(true); - mFTType = f_type; - if (mFTType == FTT_HOST_BAKE) - { - LL_WARNS() << "Unsupported fetch type " << mFTType << LL_ENDL; - } - generateGLTexture(); -} - -LLViewerFetchedTexture::LLViewerFetchedTexture(const LLImageRaw* raw, FTType f_type, bool usemipmaps) - : LLViewerTexture(raw, usemipmaps) -{ - init(true); - mFTType = f_type; -} - -LLViewerFetchedTexture::LLViewerFetchedTexture(const std::string& url, FTType f_type, const LLUUID& id, bool usemipmaps) - : LLViewerTexture(id, usemipmaps), - mUrl(url) -{ - init(true); - mFTType = f_type; - generateGLTexture(); -} - -void LLViewerFetchedTexture::init(bool firstinit) -{ - mOrigWidth = 0; - mOrigHeight = 0; - mHasAux = false; - mNeedsAux = false; - mRequestedDiscardLevel = -1; - mRequestedDownloadPriority = 0.f; - mFullyLoaded = false; - mCanUseHTTP = true; - mDesiredDiscardLevel = MAX_DISCARD_LEVEL + 1; - mMinDesiredDiscardLevel = MAX_DISCARD_LEVEL + 1; - - mDecodingAux = false; - - mKnownDrawWidth = 0; - mKnownDrawHeight = 0; - mKnownDrawSizeChanged = false; - - if (firstinit) - { - mInImageList = 0; - } - - // Only set mIsMissingAsset true when we know for certain that the database - // does not contain this image. - mIsMissingAsset = false; - - mLoadedCallbackDesiredDiscardLevel = S8_MAX; - mPauseLoadedCallBacks = false; - - mNeedsCreateTexture = false; - - mIsRawImageValid = false; - mRawDiscardLevel = INVALID_DISCARD_LEVEL; - mMinDiscardLevel = 0; - - mHasFetcher = false; - mIsFetching = false; - mFetchState = 0; - mFetchPriority = 0; - mDownloadProgress = 0.f; - mFetchDeltaTime = 999999.f; - mRequestDeltaTime = 0.f; - mForSculpt = false; - mIsFetched = false; - mInFastCacheList = false; - - mCachedRawImage = NULL; - mCachedRawDiscardLevel = -1; - mCachedRawImageReady = false; - - mSavedRawImage = NULL; - mForceToSaveRawImage = false; - mSaveRawImage = false; - mSavedRawDiscardLevel = -1; - mDesiredSavedRawDiscardLevel = -1; - mLastReferencedSavedRawImageTime = 0.0f; - mKeptSavedRawImageTime = 0.f; - mLastCallBackActiveTime = 0.f; - mForceCallbackFetch = false; - mInDebug = false; - mUnremovable = false; - - mFTType = FTT_UNKNOWN; -} - -LLViewerFetchedTexture::~LLViewerFetchedTexture() -{ - assert_main_thread(); - //*NOTE getTextureFetch can return NULL when Viewer is shutting down. - // This is due to LLWearableList is singleton and is destroyed after - // LLAppViewer::cleanup() was called. (see ticket EXT-177) - if (mHasFetcher && LLAppViewer::getTextureFetch()) - { - LLAppViewer::getTextureFetch()->deleteRequest(getID(), true); - } - cleanup(); -} - -//virtual -S8 LLViewerFetchedTexture::getType() const -{ - return LLViewerTexture::FETCHED_TEXTURE; -} - -FTType LLViewerFetchedTexture::getFTType() const -{ - return mFTType; -} - -void LLViewerFetchedTexture::cleanup() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); - iter != mLoadedCallbackList.end(); ) - { - LLLoadedCallbackEntry *entryp = *iter++; - // We never finished loading the image. Indicate failure. - // Note: this allows mLoadedCallbackUserData to be cleaned up. - entryp->mCallback( false, this, NULL, NULL, 0, true, entryp->mUserData ); - entryp->removeTexture(this); - delete entryp; - } - mLoadedCallbackList.clear(); - mNeedsAux = false; - - // Clean up image data - destroyRawImage(); - mCachedRawImage = NULL; - mCachedRawDiscardLevel = -1; - mCachedRawImageReady = false; - mSavedRawImage = NULL; - mSavedRawDiscardLevel = -1; -} - -//access the fast cache -void LLViewerFetchedTexture::loadFromFastCache() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if(!mInFastCacheList) - { - return; //no need to access the fast cache. - } - mInFastCacheList = false; - - add(LLTextureFetch::sCacheAttempt, 1.0); - - LLTimer fastCacheTimer; - mRawImage = LLAppViewer::getTextureCache()->readFromFastCache(getID(), mRawDiscardLevel); - if(mRawImage.notNull()) - { - F32 cachReadTime = fastCacheTimer.getElapsedTimeF32(); - - add(LLTextureFetch::sCacheHit, 1.0); - record(LLTextureFetch::sCacheHitRate, LLUnits::Ratio::fromValue(1)); - sample(LLTextureFetch::sCacheReadLatency, cachReadTime); - - mFullWidth = mRawImage->getWidth() << mRawDiscardLevel; - mFullHeight = mRawImage->getHeight() << mRawDiscardLevel; - setTexelsPerImage(); - - if(mFullWidth > MAX_IMAGE_SIZE || mFullHeight > MAX_IMAGE_SIZE) - { - //discard all oversized textures. - destroyRawImage(); - LL_WARNS() << "oversized, setting as missing" << LL_ENDL; - setIsMissingAsset(); - mRawDiscardLevel = INVALID_DISCARD_LEVEL; - } - else - { - if (mBoostLevel == LLGLTexture::BOOST_ICON) - { - // Shouldn't do anything usefull since texures in fast cache are 16x16, - // it is here in case fast cache changes. - S32 expected_width = mKnownDrawWidth > 0 ? mKnownDrawWidth : DEFAULT_ICON_DIMENSIONS; - S32 expected_height = mKnownDrawHeight > 0 ? mKnownDrawHeight : DEFAULT_ICON_DIMENSIONS; - if (mRawImage && (mRawImage->getWidth() > expected_width || mRawImage->getHeight() > expected_height)) - { - // scale oversized icon, no need to give more work to gl - mRawImage->scale(expected_width, expected_height); - } - } - - if (mBoostLevel == LLGLTexture::BOOST_THUMBNAIL) - { - S32 expected_width = mKnownDrawWidth > 0 ? mKnownDrawWidth : DEFAULT_THUMBNAIL_DIMENSIONS; - S32 expected_height = mKnownDrawHeight > 0 ? mKnownDrawHeight : DEFAULT_THUMBNAIL_DIMENSIONS; - if (mRawImage && (mRawImage->getWidth() > expected_width || mRawImage->getHeight() > expected_height)) - { - // scale oversized icon, no need to give more work to gl - mRawImage->scale(expected_width, expected_height); - } - } - - mRequestedDiscardLevel = mDesiredDiscardLevel + 1; - mIsRawImageValid = true; - addToCreateTexture(); - } - } - else - { - record(LLTextureFetch::sCacheHitRate, LLUnits::Ratio::fromValue(0)); - } -} - -void LLViewerFetchedTexture::setForSculpt() -{ - static const S32 MAX_INTERVAL = 8; //frames - - mForSculpt = true; - if(isForSculptOnly() && hasGLTexture() && !getBoundRecently()) - { - destroyGLTexture(); //sculpt image does not need gl texture. - mTextureState = ACTIVE; - } - checkCachedRawSculptImage(); - setMaxVirtualSizeResetInterval(MAX_INTERVAL); -} - -bool LLViewerFetchedTexture::isForSculptOnly() const -{ - return mForSculpt && !mNeedsGLTexture; -} - -bool LLViewerFetchedTexture::isDeleted() -{ - return mTextureState == DELETED; -} - -bool LLViewerFetchedTexture::isInactive() -{ - return mTextureState == INACTIVE; -} - -bool LLViewerFetchedTexture::isDeletionCandidate() -{ - return mTextureState == DELETION_CANDIDATE; -} - -void LLViewerFetchedTexture::setDeletionCandidate() -{ - if(mGLTexturep.notNull() && mGLTexturep->getTexName() && (mTextureState == INACTIVE)) - { - mTextureState = DELETION_CANDIDATE; - } -} - -//set the texture inactive -void LLViewerFetchedTexture::setInactive() -{ - if(mTextureState == ACTIVE && mGLTexturep.notNull() && mGLTexturep->getTexName() && !mGLTexturep->getBoundRecently()) - { - mTextureState = INACTIVE; - } -} - -bool LLViewerFetchedTexture::isFullyLoaded() const -{ - // Unfortunately, the boolean "mFullyLoaded" is never updated correctly so we use that logic - // to check if the texture is there and completely downloaded - return (mFullWidth != 0) && (mFullHeight != 0) && !mIsFetching && !mHasFetcher; -} - - -// virtual -void LLViewerFetchedTexture::dump() -{ - LLViewerTexture::dump(); - - LL_INFOS() << "Dump : " << mID - << ", mIsMissingAsset = " << (S32)mIsMissingAsset - << ", mFullWidth = " << (S32)mFullWidth - << ", mFullHeight = " << (S32)mFullHeight - << ", mOrigWidth = " << (S32)mOrigWidth - << ", mOrigHeight = " << (S32)mOrigHeight - << LL_ENDL; - LL_INFOS() << " : " - << " mFullyLoaded = " << (S32)mFullyLoaded - << ", mFetchState = " << (S32)mFetchState - << ", mFetchPriority = " << (S32)mFetchPriority - << ", mDownloadProgress = " << (F32)mDownloadProgress - << LL_ENDL; - LL_INFOS() << " : " - << " mHasFetcher = " << (S32)mHasFetcher - << ", mIsFetching = " << (S32)mIsFetching - << ", mIsFetched = " << (S32)mIsFetched - << ", mBoostLevel = " << (S32)mBoostLevel - << LL_ENDL; -} - -/////////////////////////////////////////////////////////////////////////////// -// ONLY called from LLViewerFetchedTextureList -void LLViewerFetchedTexture::destroyTexture() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - - if (mNeedsCreateTexture)//return if in the process of generating a new texture. - { - return; - } - - //LL_DEBUGS("Avatar") << mID << LL_ENDL; - destroyGLTexture(); - mFullyLoaded = false; -} - -void LLViewerFetchedTexture::addToCreateTexture() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - bool force_update = false; - if (getComponents() != mRawImage->getComponents()) - { - // We've changed the number of components, so we need to move any - // objects using this pool to a different pool. - mComponents = mRawImage->getComponents(); - mGLTexturep->setComponents(mComponents); - force_update = true; - - for (U32 j = 0; j < LLRender::NUM_TEXTURE_CHANNELS; ++j) - { - llassert(mNumFaces[j] <= mFaceList[j].size()); - - for(U32 i = 0; i < mNumFaces[j]; i++) - { - mFaceList[j][i]->dirtyTexture(); - } - } - - //discard the cached raw image and the saved raw image - mCachedRawImageReady = false; - mCachedRawDiscardLevel = -1; - mCachedRawImage = NULL; - mSavedRawDiscardLevel = -1; - mSavedRawImage = NULL; - } - - if(isForSculptOnly()) - { - //just update some variables, not to create a real GL texture. - createGLTexture(mRawDiscardLevel, mRawImage, 0, false); - mNeedsCreateTexture = false; - destroyRawImage(); - } - else if(!force_update && getDiscardLevel() > -1 && getDiscardLevel() <= mRawDiscardLevel) - { - mNeedsCreateTexture = false; - destroyRawImage(); - } - else - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; -#if 1 - // - //if mRequestedDiscardLevel > mDesiredDiscardLevel, we assume the required image res keep going up, - //so do not scale down the over qualified image. - //Note: scaling down image is expensensive. Do it only when very necessary. - // - if(mRequestedDiscardLevel <= mDesiredDiscardLevel && !mForceToSaveRawImage) - { - S32 w = mFullWidth >> mRawDiscardLevel; - S32 h = mFullHeight >> mRawDiscardLevel; - - //if big image, do not load extra data - //scale it down to size >= LLViewerTexture::sMinLargeImageSize - if(w * h > LLViewerTexture::sMinLargeImageSize) - { - S32 d_level = llmin(mRequestedDiscardLevel, (S32)mDesiredDiscardLevel) - mRawDiscardLevel; - - if(d_level > 0) - { - S32 i = 0; - while((d_level > 0) && ((w >> i) * (h >> i) > LLViewerTexture::sMinLargeImageSize)) - { - i++; - d_level--; - } - if(i > 0) - { - mRawDiscardLevel += i; - if(mRawDiscardLevel >= getDiscardLevel() && getDiscardLevel() > 0) - { - mNeedsCreateTexture = false; - destroyRawImage(); - return; - } - - { - //make a duplicate in case somebody else is using this raw image - mRawImage = mRawImage->scaled(w >> i, h >> i); - } - } - } - } - } -#endif - scheduleCreateTexture(); - } - return; -} - -// ONLY called from LLViewerTextureList -bool LLViewerFetchedTexture::preCreateTexture(S32 usename/*= 0*/) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; -#if LL_IMAGEGL_THREAD_CHECK - mGLTexturep->checkActiveThread(); -#endif - - if (!mNeedsCreateTexture) - { - destroyRawImage(); - return false; - } - mNeedsCreateTexture = false; - - if (mRawImage.isNull()) - { - LL_ERRS() << "LLViewerTexture trying to create texture with no Raw Image" << LL_ENDL; - } - if (mRawImage->isBufferInvalid()) - { - LL_WARNS() << "Can't create a texture: invalid image data" << LL_ENDL; - destroyRawImage(); - return false; - } - // LL_INFOS() << llformat("IMAGE Creating (%d) [%d x %d] Bytes: %d ", - // mRawDiscardLevel, - // mRawImage->getWidth(), mRawImage->getHeight(),mRawImage->getDataSize()) - // << mID.getString() << LL_ENDL; - bool res = true; - - // store original size only for locally-sourced images - if (mUrl.compare(0, 7, "file://") == 0) - { - mOrigWidth = mRawImage->getWidth(); - mOrigHeight = mRawImage->getHeight(); - - // This is only safe because it's a local image and fetcher doesn't use raw data - // from local images, but this might become unsafe in case of changes to fetcher - if (mBoostLevel == BOOST_PREVIEW) - { - mRawImage->biasedScaleToPowerOfTwo(1024); - } - else - { // leave black border, do not scale image content - mRawImage->expandToPowerOfTwo(MAX_IMAGE_SIZE, false); - } - - mFullWidth = mRawImage->getWidth(); - mFullHeight = mRawImage->getHeight(); - setTexelsPerImage(); - } - else - { - mOrigWidth = mFullWidth; - mOrigHeight = mFullHeight; - } - - bool size_okay = true; - - S32 discard_level = mRawDiscardLevel; - if (mRawDiscardLevel < 0) - { - LL_DEBUGS() << "Negative raw discard level when creating image: " << mRawDiscardLevel << LL_ENDL; - discard_level = 0; - } - - U32 raw_width = mRawImage->getWidth() << discard_level; - U32 raw_height = mRawImage->getHeight() << discard_level; - - if (raw_width > MAX_IMAGE_SIZE || raw_height > MAX_IMAGE_SIZE) - { - LL_INFOS() << "Width or height is greater than " << MAX_IMAGE_SIZE << ": (" << raw_width << "," << raw_height << ")" << LL_ENDL; - size_okay = false; - } - - if (!LLImageGL::checkSize(mRawImage->getWidth(), mRawImage->getHeight())) - { - // A non power-of-two image was uploaded (through a non standard client) - LL_INFOS() << "Non power of two width or height: (" << mRawImage->getWidth() << "," << mRawImage->getHeight() << ")" << LL_ENDL; - size_okay = false; - } - - if (!size_okay) - { - // An inappropriately-sized image was uploaded (through a non standard client) - // We treat these images as missing assets which causes them to - // be renderd as 'missing image' and to stop requesting data - LL_WARNS() << "!size_ok, setting as missing" << LL_ENDL; - setIsMissingAsset(); - destroyRawImage(); - return false; - } - - if (mGLTexturep->getHasExplicitFormat()) - { - LLGLenum format = mGLTexturep->getPrimaryFormat(); - S8 components = mRawImage->getComponents(); - if ((format == GL_RGBA && components < 4) - || (format == GL_RGB && components < 3)) - { - LL_WARNS() << "Can't create a texture " << mID << ": invalid image format " << std::hex << format << " vs components " << (U32)components << LL_ENDL; - // Was expecting specific format but raw texture has insufficient components for - // such format, using such texture will result in crash or will display wrongly - // if we change format. Texture might be corrupted server side, so just set as - // missing and clear cashed texture (do not cause reload loop, will retry&recover - // during new session) - setIsMissingAsset(); - destroyRawImage(); - LLAppViewer::getTextureCache()->removeFromCache(mID); - return false; - } - } - - return res; -} - -bool LLViewerFetchedTexture::createTexture(S32 usename/*= 0*/) -{ - if (!mNeedsCreateTexture) - { - return false; - } - - bool res = mGLTexturep->createGLTexture(mRawDiscardLevel, mRawImage, usename, true, mBoostLevel); - - return res; -} - -void LLViewerFetchedTexture::postCreateTexture() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if (!mNeedsCreateTexture) - { - return; - } -#if LL_IMAGEGL_THREAD_CHECK - mGLTexturep->checkActiveThread(); -#endif - - setActive(); - - if (!needsToSaveRawImage()) - { - mNeedsAux = false; - destroyRawImage(); - } - - mNeedsCreateTexture = false; -} - -void LLViewerFetchedTexture::scheduleCreateTexture() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - - if (!mNeedsCreateTexture) - { - mNeedsCreateTexture = true; - if (preCreateTexture()) - { -#if LL_IMAGEGL_THREAD_CHECK - //grab a copy of the raw image data to make sure it isn't modified pending texture creation - U8* data = mRawImage->getData(); - U8* data_copy = nullptr; - S32 size = mRawImage->getDataSize(); - if (data != nullptr && size > 0) - { - data_copy = new U8[size]; - memcpy(data_copy, data, size); - } -#endif - mNeedsCreateTexture = true; - auto mainq = LLImageGLThread::sEnabledTextures ? mMainQueue.lock() : nullptr; - if (mainq) - { - ref(); - mainq->postTo( - mImageQueue, - // work to be done on LLImageGL worker thread -#if LL_IMAGEGL_THREAD_CHECK - [this, data, data_copy, size]() - { - mGLTexturep->mActiveThread = LLThread::currentID(); - //verify data is unmodified - llassert(data == mRawImage->getData()); - llassert(mRawImage->getDataSize() == size); - llassert(memcmp(data, data_copy, size) == 0); -#else - [this]() - { -#endif - //actually create the texture on a background thread - createTexture(); - -#if LL_IMAGEGL_THREAD_CHECK - //verify data is unmodified - llassert(data == mRawImage->getData()); - llassert(mRawImage->getDataSize() == size); - llassert(memcmp(data, data_copy, size) == 0); -#endif - }, - // callback to be run on main thread -#if LL_IMAGEGL_THREAD_CHECK - [this, data, data_copy, size]() - { - mGLTexturep->mActiveThread = LLThread::currentID(); - llassert(data == mRawImage->getData()); - llassert(mRawImage->getDataSize() == size); - llassert(memcmp(data, data_copy, size) == 0); - delete[] data_copy; -#else - [this]() - { -#endif - //finalize on main thread - postCreateTexture(); - unref(); - }); - } - else - { - gTextureList.mCreateTextureList.insert(this); - } - } - } -} - -// Call with 0,0 to turn this feature off. -//virtual -void LLViewerFetchedTexture::setKnownDrawSize(S32 width, S32 height) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if(mKnownDrawWidth < width || mKnownDrawHeight < height) - { - mKnownDrawWidth = llmax(mKnownDrawWidth, width); - mKnownDrawHeight = llmax(mKnownDrawHeight, height); - - mKnownDrawSizeChanged = true; - mFullyLoaded = false; - } - addTextureStats((F32)(mKnownDrawWidth * mKnownDrawHeight)); -} - -void LLViewerFetchedTexture::setDebugText(const std::string& text) -{ - for (U32 ch = 0; ch < LLRender::NUM_TEXTURE_CHANNELS; ++ch) - { - llassert(mNumFaces[ch] <= mFaceList[ch].size()); - - for (U32 i = 0; i < mNumFaces[ch]; i++) - { - LLFace* facep = mFaceList[ch][i]; - if (facep) - { - LLDrawable* drawable = facep->getDrawable(); - if (drawable) - { - drawable->getVObj()->setDebugText(text); - } - } - } - } -} - -//virtual -void LLViewerFetchedTexture::processTextureStats() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if(mFullyLoaded) - { - if(mDesiredDiscardLevel > mMinDesiredDiscardLevel)//need to load more - { - mDesiredDiscardLevel = llmin(mDesiredDiscardLevel, mMinDesiredDiscardLevel); - mFullyLoaded = false; - } - //setDebugText("fully loaded"); - } - else - { - updateVirtualSize(); - - static LLCachedControl textures_fullres(gSavedSettings,"TextureLoadFullRes", false); - - if (textures_fullres) - { - mDesiredDiscardLevel = 0; - } - else if (mDontDiscard && (mBoostLevel == LLGLTexture::BOOST_ICON || mBoostLevel == LLGLTexture::BOOST_THUMBNAIL)) - { - if (mFullWidth > MAX_IMAGE_SIZE_DEFAULT || mFullHeight > MAX_IMAGE_SIZE_DEFAULT) - { - mDesiredDiscardLevel = 1; // MAX_IMAGE_SIZE_DEFAULT = 1024 and max size ever is 2048 - } - else - { - mDesiredDiscardLevel = 0; - } - } - else if(!mFullWidth || !mFullHeight) - { - mDesiredDiscardLevel = llmin(getMaxDiscardLevel(), (S32)mLoadedCallbackDesiredDiscardLevel); - } - else - { - U32 desired_size = MAX_IMAGE_SIZE_DEFAULT; // MAX_IMAGE_SIZE_DEFAULT = 1024 and max size ever is 2048 - if(!mKnownDrawWidth || !mKnownDrawHeight || mFullWidth <= mKnownDrawWidth || mFullHeight <= mKnownDrawHeight) - { - if (mFullWidth > desired_size || mFullHeight > desired_size) - { - mDesiredDiscardLevel = 1; - } - else - { - mDesiredDiscardLevel = 0; - } - } - else if(mKnownDrawSizeChanged)//known draw size is set - { - mDesiredDiscardLevel = (S8)llmin(log((F32)mFullWidth / mKnownDrawWidth) / log_2, - log((F32)mFullHeight / mKnownDrawHeight) / log_2); - mDesiredDiscardLevel = llclamp(mDesiredDiscardLevel, (S8)0, (S8)getMaxDiscardLevel()); - mDesiredDiscardLevel = llmin(mDesiredDiscardLevel, mMinDesiredDiscardLevel); - } - mKnownDrawSizeChanged = false; - - if(getDiscardLevel() >= 0 && (getDiscardLevel() <= mDesiredDiscardLevel)) - { - mFullyLoaded = true; - } - } - } - - if(mForceToSaveRawImage && mDesiredSavedRawDiscardLevel >= 0) //force to refetch the texture. - { - mDesiredDiscardLevel = llmin(mDesiredDiscardLevel, (S8)mDesiredSavedRawDiscardLevel); - if(getDiscardLevel() < 0 || getDiscardLevel() > mDesiredDiscardLevel) - { - mFullyLoaded = false; - } - } -} - -//============================================================================ - -void LLViewerFetchedTexture::updateVirtualSize() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - reorganizeFaceList(); - reorganizeVolumeList(); -} - -S32 LLViewerFetchedTexture::getCurrentDiscardLevelForFetching() -{ - S32 current_discard = getDiscardLevel(); - if(mForceToSaveRawImage) - { - if(mSavedRawDiscardLevel < 0 || current_discard < 0) - { - current_discard = -1; - } - else - { - current_discard = llmax(current_discard, mSavedRawDiscardLevel); - } - } - - return current_discard; -} - -bool LLViewerFetchedTexture::setDebugFetching(S32 debug_level) -{ - if(debug_level < 0) - { - mInDebug = false; - return false; - } - mInDebug = true; - - mDesiredDiscardLevel = debug_level; - - return true; -} - -bool LLViewerFetchedTexture::isActiveFetching() -{ - static LLCachedControl monitor_enabled(gSavedSettings,"DebugShowTextureInfo"); - - return mFetchState > 7 && mFetchState < 10 && monitor_enabled; //in state of WAIT_HTTP_REQ or DECODE_IMAGE. -} - -void LLViewerFetchedTexture::setBoostLevel(S32 level) -{ - LLViewerTexture::setBoostLevel(level); - - if (level >= LLViewerTexture::BOOST_HIGH) - { - mDesiredDiscardLevel = 0; - } -} - -bool LLViewerFetchedTexture::updateFetch() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - static LLCachedControl textures_decode_disabled(gSavedSettings,"TextureDecodeDisabled", false); - - if(textures_decode_disabled) // don't fetch the surface textures in wireframe mode - { - return false; - } - - mFetchState = 0; - mFetchPriority = 0; - mFetchDeltaTime = 999999.f; - mRequestDeltaTime = 999999.f; - -#ifndef LL_RELEASE_FOR_DOWNLOAD - if (mID == LLAppViewer::getTextureFetch()->mDebugID) - { - LLAppViewer::getTextureFetch()->mDebugCount++; // for setting breakpoints - } -#endif - - if (mNeedsCreateTexture) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - needs create"); - // We may be fetching still (e.g. waiting on write) - // but don't check until we've processed the raw data we have - return false; - } - if (mIsMissingAsset) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - missing asset"); - llassert(!mHasFetcher); - return false; // skip - } - if (!mLoadedCallbackList.empty() && mRawImage.notNull()) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - callback pending"); - return false; // process any raw image data in callbacks before replacing - } - if(mInFastCacheList) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - in fast cache"); - return false; - } - if (mGLTexturep.isNull()) - { // fix for crash inside getCurrentDiscardLevelForFetching (shouldn't happen but appears to be happening) - llassert(false); - return false; - } - - S32 current_discard = getCurrentDiscardLevelForFetching(); - S32 desired_discard = getDesiredDiscardLevel(); - F32 decode_priority = mMaxVirtualSize; - - if (mIsFetching) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - is fetching"); - // Sets mRawDiscardLevel, mRawImage, mAuxRawImage - S32 fetch_discard = current_discard; - - if (mRawImage.notNull()) sRawCount--; - if (mAuxRawImage.notNull()) sAuxCount--; - // keep in mind that fetcher still might need raw image, don't modify original - bool finished = LLAppViewer::getTextureFetch()->getRequestFinished(getID(), fetch_discard, mRawImage, mAuxRawImage, - mLastHttpGetStatus); - if (mRawImage.notNull()) sRawCount++; - if (mAuxRawImage.notNull()) - { - mHasAux = true; - sAuxCount++; - } - if (finished) - { - mIsFetching = false; - mLastFetchState = -1; - mLastPacketTimer.reset(); - } - else - { - mFetchState = LLAppViewer::getTextureFetch()->getFetchState(mID, mDownloadProgress, mRequestedDownloadPriority, - mFetchPriority, mFetchDeltaTime, mRequestDeltaTime, mCanUseHTTP); - } - - // We may have data ready regardless of whether or not we are finished (e.g. waiting on write) - if (mRawImage.notNull()) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - has raw image"); - LLTexturePipelineTester* tester = (LLTexturePipelineTester*)LLMetricPerformanceTesterBasic::getTester(sTesterName); - if (tester) - { - mIsFetched = true; - tester->updateTextureLoadingStats(this, mRawImage, LLAppViewer::getTextureFetch()->isFromLocalCache(mID)); - } - mRawDiscardLevel = fetch_discard; - if ((mRawImage->getDataSize() > 0 && mRawDiscardLevel >= 0) && - (current_discard < 0 || mRawDiscardLevel < current_discard)) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - data good"); - mFullWidth = mRawImage->getWidth() << mRawDiscardLevel; - mFullHeight = mRawImage->getHeight() << mRawDiscardLevel; - setTexelsPerImage(); - - if(mFullWidth > MAX_IMAGE_SIZE || mFullHeight > MAX_IMAGE_SIZE) - { - //discard all oversized textures. - destroyRawImage(); - LL_WARNS() << "oversize, setting as missing" << LL_ENDL; - setIsMissingAsset(); - mRawDiscardLevel = INVALID_DISCARD_LEVEL; - mIsFetching = false; - mLastPacketTimer.reset(); - } - else - { - mIsRawImageValid = true; - addToCreateTexture(); - } - - if (mBoostLevel == LLGLTexture::BOOST_ICON) - { - S32 expected_width = mKnownDrawWidth > 0 ? mKnownDrawWidth : DEFAULT_ICON_DIMENSIONS; - S32 expected_height = mKnownDrawHeight > 0 ? mKnownDrawHeight : DEFAULT_ICON_DIMENSIONS; - if (mRawImage && (mRawImage->getWidth() > expected_width || mRawImage->getHeight() > expected_height)) - { - // scale oversized icon, no need to give more work to gl - // since we got mRawImage from thread worker and image may be in use (ex: writing cache), make a copy - mRawImage = mRawImage->scaled(expected_width, expected_height); - } - } - - if (mBoostLevel == LLGLTexture::BOOST_THUMBNAIL) - { - S32 expected_width = mKnownDrawWidth > 0 ? mKnownDrawWidth : DEFAULT_THUMBNAIL_DIMENSIONS; - S32 expected_height = mKnownDrawHeight > 0 ? mKnownDrawHeight : DEFAULT_THUMBNAIL_DIMENSIONS; - if (mRawImage && (mRawImage->getWidth() > expected_width || mRawImage->getHeight() > expected_height)) - { - // scale oversized icon, no need to give more work to gl - // since we got mRawImage from thread worker and image may be in use (ex: writing cache), make a copy - mRawImage = mRawImage->scaled(expected_width, expected_height); - } - } - - return true; - } - else - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - data not needed"); - // Data is ready but we don't need it - // (received it already while fetcher was writing to disk) - destroyRawImage(); - return false; // done - } - } - - if (!mIsFetching) - { - if ((decode_priority > 0) && (mRawDiscardLevel < 0 || mRawDiscardLevel == INVALID_DISCARD_LEVEL)) - { - // We finished but received no data - if (getDiscardLevel() < 0) - { - if (getFTType() != FTT_MAP_TILE) - { - LL_WARNS() << mID - << " Fetch failure, setting as missing, decode_priority " << decode_priority - << " mRawDiscardLevel " << mRawDiscardLevel - << " current_discard " << current_discard - << " stats " << mLastHttpGetStatus.toHex() - << LL_ENDL; - } - setIsMissingAsset(); - desired_discard = -1; - } - else - { - //LL_WARNS() << mID << ": Setting min discard to " << current_discard << LL_ENDL; - if(current_discard >= 0) - { - mMinDiscardLevel = current_discard; - //desired_discard = current_discard; - } - else - { - S32 dis_level = getDiscardLevel(); - mMinDiscardLevel = dis_level; - //desired_discard = dis_level; - } - } - destroyRawImage(); - } - else if (mRawImage.notNull()) - { - // We have data, but our fetch failed to return raw data - // *TODO: FIgure out why this is happening and fix it - destroyRawImage(); - } - } - else - { - static const F32 MAX_HOLD_TIME = 5.0f; //seconds to wait before canceling fecthing if decode_priority is 0.f. - if(decode_priority > 0.0f || mStopFetchingTimer.getElapsedTimeF32() > MAX_HOLD_TIME) - { - mStopFetchingTimer.reset(); - LLAppViewer::getTextureFetch()->updateRequestPriority(mID, decode_priority); - } - } - } - - desired_discard = llmin(desired_discard, getMaxDiscardLevel()); - - bool make_request = true; - if (decode_priority <= 0) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - priority <= 0"); - make_request = false; - } - else if(mDesiredDiscardLevel > getMaxDiscardLevel()) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - desired > max"); - make_request = false; - } - else if (mNeedsCreateTexture || mIsMissingAsset) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - create or missing"); - make_request = false; - } - else if (current_discard >= 0 && current_discard <= mMinDiscardLevel) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - current < min"); - make_request = false; - } - else if(mCachedRawImage.notNull() // can be empty - && mCachedRawImageReady - && (current_discard < 0 || current_discard > mCachedRawDiscardLevel)) - { - make_request = false; - switchToCachedImage(); //use the cached raw data first - } - - if (make_request) - { - if (mIsFetching) - { - // already requested a higher resolution mip - if (mRequestedDiscardLevel <= desired_discard) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - requested < desired"); - make_request = false; - } - } - else - { - // already at a higher resolution mip, don't discard - if (current_discard >= 0 && current_discard <= desired_discard) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - current <= desired"); - make_request = false; - } - } - } - - if (make_request) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - make request"); - S32 w=0, h=0, c=0; - if (getDiscardLevel() >= 0) - { - w = mGLTexturep->getWidth(0); - h = mGLTexturep->getHeight(0); - c = mComponents; - } - - const U32 override_tex_discard_level = gSavedSettings.getU32("TextureDiscardLevel"); - if (override_tex_discard_level != 0) - { - desired_discard = override_tex_discard_level; - } - - // bypass texturefetch directly by pulling from LLTextureCache - S32 fetch_request_discard = -1; - fetch_request_discard = LLAppViewer::getTextureFetch()->createRequest(mFTType, mUrl, getID(), getTargetHost(), decode_priority, - w, h, c, desired_discard, needsAux(), mCanUseHTTP); - - if (fetch_request_discard >= 0) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - request created"); - mHasFetcher = true; - mIsFetching = true; - // in some cases createRequest can modify discard, as an example - // bake textures are always at discard 0 - mRequestedDiscardLevel = llmin(desired_discard, fetch_request_discard); - mFetchState = LLAppViewer::getTextureFetch()->getFetchState(mID, mDownloadProgress, mRequestedDownloadPriority, - mFetchPriority, mFetchDeltaTime, mRequestDeltaTime, mCanUseHTTP); - } - - // If createRequest() failed, that means one of two things: - // 1. We're finishing up a request for this UUID, so we - // should wait for it to complete - // 2. We've failed a request for this UUID, so there is - // no need to create another request - } - else if (mHasFetcher && !mIsFetching) - { - // Only delete requests that haven't received any network data - // for a while. Note - this is the normal mechanism for - // deleting requests, not just a place to handle timeouts. - const F32 FETCH_IDLE_TIME = 0.1f; - if (mLastPacketTimer.getElapsedTimeF32() > FETCH_IDLE_TIME) - { - LL_DEBUGS("Texture") << "exceeded idle time " << FETCH_IDLE_TIME << ", deleting request: " << getID() << LL_ENDL; - LLAppViewer::getTextureFetch()->deleteRequest(getID(), true); - mHasFetcher = false; - } - } - - return mIsFetching; -} - -void LLViewerFetchedTexture::clearFetchedResults() -{ - if(mNeedsCreateTexture || mIsFetching) - { - return; - } - - cleanup(); - destroyGLTexture(); - - if(getDiscardLevel() >= 0) //sculpty texture, force to invalidate - { - mGLTexturep->forceToInvalidateGLTexture(); - } -} - -void LLViewerFetchedTexture::forceToDeleteRequest() -{ - if (mHasFetcher) - { - mHasFetcher = false; - mIsFetching = false; - } - - resetTextureStats(); - - mDesiredDiscardLevel = getMaxDiscardLevel() + 1; -} - -void LLViewerFetchedTexture::setIsMissingAsset(bool is_missing) -{ - if (is_missing == mIsMissingAsset) - { - return; - } - if (is_missing) - { - if (mUrl.empty()) - { - LL_WARNS() << mID << ": Marking image as missing" << LL_ENDL; - } - else - { - // This may or may not be an error - it is normal to have no - // map tile on an empty region, but bad if we're failing on a - // server bake texture. - if (getFTType() != FTT_MAP_TILE) - { - LL_WARNS() << mUrl << ": Marking image as missing" << LL_ENDL; - } - } - if (mHasFetcher) - { - LLAppViewer::getTextureFetch()->deleteRequest(getID(), true); - mHasFetcher = false; - mIsFetching = false; - mLastPacketTimer.reset(); - mFetchState = 0; - mFetchPriority = 0; - } - } - else - { - LL_INFOS() << mID << ": un-flagging missing asset" << LL_ENDL; - } - mIsMissingAsset = is_missing; -} - -void LLViewerFetchedTexture::setLoadedCallback( loaded_callback_func loaded_callback, - S32 discard_level, bool keep_imageraw, bool needs_aux, void* userdata, - LLLoadedCallbackEntry::source_callback_list_t* src_callback_list, bool pause) -{ - // - // Don't do ANYTHING here, just add it to the global callback list - // - if (mLoadedCallbackList.empty()) - { - // Put in list to call this->doLoadedCallbacks() periodically - gTextureList.mCallbackList.insert(this); - mLoadedCallbackDesiredDiscardLevel = (S8)discard_level; - } - else - { - mLoadedCallbackDesiredDiscardLevel = llmin(mLoadedCallbackDesiredDiscardLevel, (S8)discard_level); - } - - if(mPauseLoadedCallBacks) - { - if(!pause) - { - unpauseLoadedCallbacks(src_callback_list); - } - } - else if(pause) - { - pauseLoadedCallbacks(src_callback_list); - } - - LLLoadedCallbackEntry* entryp = new LLLoadedCallbackEntry(loaded_callback, discard_level, keep_imageraw, userdata, src_callback_list, this, pause); - mLoadedCallbackList.push_back(entryp); - - mNeedsAux |= needs_aux; - if(keep_imageraw) - { - mSaveRawImage = true; - } - if (mNeedsAux && mAuxRawImage.isNull() && getDiscardLevel() >= 0) - { - if(mHasAux) - { - //trigger a refetch - forceToRefetchTexture(); - } - else - { - // We need aux data, but we've already loaded the image, and it didn't have any - LL_WARNS() << "No aux data available for callback for image:" << getID() << LL_ENDL; - } - } - mLastCallBackActiveTime = sCurrentTime ; - mLastReferencedSavedRawImageTime = sCurrentTime; -} - -void LLViewerFetchedTexture::clearCallbackEntryList() -{ - if(mLoadedCallbackList.empty()) - { - return; - } - - for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); - iter != mLoadedCallbackList.end(); ) - { - LLLoadedCallbackEntry *entryp = *iter; - - // We never finished loading the image. Indicate failure. - // Note: this allows mLoadedCallbackUserData to be cleaned up. - entryp->mCallback(false, this, NULL, NULL, 0, true, entryp->mUserData); - iter = mLoadedCallbackList.erase(iter); - delete entryp; - } - gTextureList.mCallbackList.erase(this); - - mLoadedCallbackDesiredDiscardLevel = S8_MAX; - if(needsToSaveRawImage()) - { - destroySavedRawImage(); - } - - return; -} - -void LLViewerFetchedTexture::deleteCallbackEntry(const LLLoadedCallbackEntry::source_callback_list_t* callback_list) -{ - if(mLoadedCallbackList.empty() || !callback_list) - { - return; - } - - S32 desired_discard = S8_MAX; - S32 desired_raw_discard = INVALID_DISCARD_LEVEL; - for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); - iter != mLoadedCallbackList.end(); ) - { - LLLoadedCallbackEntry *entryp = *iter; - if(entryp->mSourceCallbackList == callback_list) - { - // We never finished loading the image. Indicate failure. - // Note: this allows mLoadedCallbackUserData to be cleaned up. - entryp->mCallback(false, this, NULL, NULL, 0, true, entryp->mUserData); - iter = mLoadedCallbackList.erase(iter); - delete entryp; - } - else - { - ++iter; - - desired_discard = llmin(desired_discard, entryp->mDesiredDiscard); - if(entryp->mNeedsImageRaw) - { - desired_raw_discard = llmin(desired_raw_discard, entryp->mDesiredDiscard); - } - } - } - - mLoadedCallbackDesiredDiscardLevel = desired_discard; - if (mLoadedCallbackList.empty()) - { - // If we have no callbacks, take us off of the image callback list. - gTextureList.mCallbackList.erase(this); - - if(needsToSaveRawImage()) - { - destroySavedRawImage(); - } - } - else if(needsToSaveRawImage() && mBoostLevel != LLGLTexture::BOOST_PREVIEW) - { - if(desired_raw_discard != INVALID_DISCARD_LEVEL) - { - mDesiredSavedRawDiscardLevel = desired_raw_discard; - } - else - { - destroySavedRawImage(); - } - } -} - -void LLViewerFetchedTexture::unpauseLoadedCallbacks(const LLLoadedCallbackEntry::source_callback_list_t* callback_list) -{ - if(!callback_list) -{ - mPauseLoadedCallBacks = false; - return; - } - - bool need_raw = false; - for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); - iter != mLoadedCallbackList.end(); ) - { - LLLoadedCallbackEntry *entryp = *iter++; - if(entryp->mSourceCallbackList == callback_list) - { - entryp->mPaused = false; - if(entryp->mNeedsImageRaw) - { - need_raw = true; - } - } - } - mPauseLoadedCallBacks = false ; - mLastCallBackActiveTime = sCurrentTime ; - mForceCallbackFetch = true; - if(need_raw) - { - mSaveRawImage = true; - } -} - -void LLViewerFetchedTexture::pauseLoadedCallbacks(const LLLoadedCallbackEntry::source_callback_list_t* callback_list) -{ - if(!callback_list) -{ - return; - } - - bool paused = true; - - for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); - iter != mLoadedCallbackList.end(); ) - { - LLLoadedCallbackEntry *entryp = *iter++; - if(entryp->mSourceCallbackList == callback_list) - { - entryp->mPaused = true; - } - else if(!entryp->mPaused) - { - paused = false; - } - } - - if(paused) - { - mPauseLoadedCallBacks = true;//when set, loaded callback is paused. - resetTextureStats(); - mSaveRawImage = false; - } -} - -bool LLViewerFetchedTexture::doLoadedCallbacks() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - static const F32 MAX_INACTIVE_TIME = 900.f ; //seconds - static const F32 MAX_IDLE_WAIT_TIME = 5.f ; //seconds - - if (mNeedsCreateTexture) - { - return false; - } - if(mPauseLoadedCallBacks) - { - destroyRawImage(); - return false; //paused - } - if(sCurrentTime - mLastCallBackActiveTime > MAX_INACTIVE_TIME && !mIsFetching) - { - if (mFTType == FTT_SERVER_BAKE) - { - //output some debug info - LL_INFOS() << "baked texture: " << mID << "clears all call backs due to inactivity." << LL_ENDL; - LL_INFOS() << mUrl << LL_ENDL; - LL_INFOS() << "current discard: " << getDiscardLevel() << " current discard for fetch: " << getCurrentDiscardLevelForFetching() << - " Desired discard: " << getDesiredDiscardLevel() << "decode Pri: " << mMaxVirtualSize << LL_ENDL; - } - - clearCallbackEntryList() ; //remove all callbacks. - return false ; - } - - bool res = false; - - if (isMissingAsset()) - { - if (mFTType == FTT_SERVER_BAKE) - { - //output some debug info - LL_INFOS() << "baked texture: " << mID << "is missing." << LL_ENDL; - LL_INFOS() << mUrl << LL_ENDL; - } - - for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); - iter != mLoadedCallbackList.end(); ) - { - LLLoadedCallbackEntry *entryp = *iter++; - // We never finished loading the image. Indicate failure. - // Note: this allows mLoadedCallbackUserData to be cleaned up. - entryp->mCallback(false, this, NULL, NULL, 0, true, entryp->mUserData); - delete entryp; - } - mLoadedCallbackList.clear(); - - // Remove ourself from the global list of textures with callbacks - gTextureList.mCallbackList.erase(this); - return false; - } - - S32 gl_discard = getDiscardLevel(); - - // If we don't have a legit GL image, set it to be lower than the worst discard level - if (gl_discard == -1) - { - gl_discard = MAX_DISCARD_LEVEL + 1; - } - - // - // Determine the quality levels of textures that we can provide to callbacks - // and whether we need to do decompression/readback to get it - // - S32 current_raw_discard = MAX_DISCARD_LEVEL + 1; // We can always do a readback to get a raw discard - S32 best_raw_discard = gl_discard; // Current GL quality level - S32 current_aux_discard = MAX_DISCARD_LEVEL + 1; - S32 best_aux_discard = MAX_DISCARD_LEVEL + 1; - - if (mIsRawImageValid) - { - // If we have an existing raw image, we have a baseline for the raw and auxiliary quality levels. - best_raw_discard = llmin(best_raw_discard, mRawDiscardLevel); - best_aux_discard = llmin(best_aux_discard, mRawDiscardLevel); // We always decode the aux when we decode the base raw - current_aux_discard = llmin(current_aux_discard, best_aux_discard); - } - else - { - // We have no data at all, we need to get it - // Do this by forcing the best aux discard to be 0. - best_aux_discard = 0; - } - - - // - // See if any of the callbacks would actually run using the data that we can provide, - // and also determine if we need to perform any readbacks or decodes. - // - bool run_gl_callbacks = false; - bool run_raw_callbacks = false; - bool need_readback = false; - - for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); - iter != mLoadedCallbackList.end(); ) - { - LLLoadedCallbackEntry *entryp = *iter++; - - if (entryp->mNeedsImageRaw) - { - if (mNeedsAux) - { - // - // Need raw and auxiliary channels - // - if (entryp->mLastUsedDiscard > current_aux_discard) - { - // We have useful data, run the callbacks - run_raw_callbacks = true; - } - } - else - { - if (entryp->mLastUsedDiscard > current_raw_discard) - { - // We have useful data, just run the callbacks - run_raw_callbacks = true; - } - else if (entryp->mLastUsedDiscard > best_raw_discard) - { - // We can readback data, and then run the callbacks - need_readback = true; - run_raw_callbacks = true; - } - } - } - else - { - // Needs just GL - if (entryp->mLastUsedDiscard > gl_discard) - { - // We have enough data, run this callback requiring GL data - run_gl_callbacks = true; - } - } - } - - // - // Do a readback if required, OR start off a texture decode - // - if (need_readback && (getMaxDiscardLevel() > gl_discard)) - { - // Do a readback to get the GL data into the raw image - // We have GL data. - - destroyRawImage(); - reloadRawImage(mLoadedCallbackDesiredDiscardLevel); - llassert(mRawImage.notNull()); - llassert(!mNeedsAux || mAuxRawImage.notNull()); - } - - // - // Run raw/auxiliary data callbacks - // - if (run_raw_callbacks && mIsRawImageValid && (mRawDiscardLevel <= getMaxDiscardLevel())) - { - // Do callbacks which require raw image data. - //LL_INFOS() << "doLoadedCallbacks raw for " << getID() << LL_ENDL; - - // Call each party interested in the raw data. - for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); - iter != mLoadedCallbackList.end(); ) - { - callback_list_t::iterator curiter = iter++; - LLLoadedCallbackEntry *entryp = *curiter; - if (entryp->mNeedsImageRaw && (entryp->mLastUsedDiscard > mRawDiscardLevel)) - { - // If we've loaded all the data there is to load or we've loaded enough - // to satisfy the interested party, then this is the last time that - // we're going to call them. - - mLastCallBackActiveTime = sCurrentTime; - if(mNeedsAux && mAuxRawImage.isNull()) - { - LL_WARNS() << "Raw Image with no Aux Data for callback" << LL_ENDL; - } - bool final = mRawDiscardLevel <= entryp->mDesiredDiscard; - //LL_INFOS() << "Running callback for " << getID() << LL_ENDL; - //LL_INFOS() << mRawImage->getWidth() << "x" << mRawImage->getHeight() << LL_ENDL; - entryp->mLastUsedDiscard = mRawDiscardLevel; - entryp->mCallback(true, this, mRawImage, mAuxRawImage, mRawDiscardLevel, final, entryp->mUserData); - if (final) - { - iter = mLoadedCallbackList.erase(curiter); - delete entryp; - } - res = true; - } - } - } - - // - // Run GL callbacks - // - if (run_gl_callbacks && (gl_discard <= getMaxDiscardLevel())) - { - //LL_INFOS() << "doLoadedCallbacks GL for " << getID() << LL_ENDL; - - // Call the callbacks interested in GL data. - for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); - iter != mLoadedCallbackList.end(); ) - { - callback_list_t::iterator curiter = iter++; - LLLoadedCallbackEntry *entryp = *curiter; - if (!entryp->mNeedsImageRaw && (entryp->mLastUsedDiscard > gl_discard)) - { - mLastCallBackActiveTime = sCurrentTime; - bool final = gl_discard <= entryp->mDesiredDiscard; - entryp->mLastUsedDiscard = gl_discard; - entryp->mCallback(true, this, NULL, NULL, gl_discard, final, entryp->mUserData); - if (final) - { - iter = mLoadedCallbackList.erase(curiter); - delete entryp; - } - res = true; - } - } - } - - // Done with any raw image data at this point (will be re-created if we still have callbacks) - destroyRawImage(); - - // - // If we have no callbacks, take us off of the image callback list. - // - if (mLoadedCallbackList.empty()) - { - gTextureList.mCallbackList.erase(this); - } - else if(!res && mForceCallbackFetch && sCurrentTime - mLastCallBackActiveTime > MAX_IDLE_WAIT_TIME && !mIsFetching) - { - //wait for long enough but no fetching request issued, force one. - forceToRefetchTexture(mLoadedCallbackDesiredDiscardLevel, 5.f); - mForceCallbackFetch = false; //fire once. - } - - return res; -} - -//virtual -void LLViewerFetchedTexture::forceImmediateUpdate() -{ - //only immediately update a deleted texture which is now being re-used. - if(!isDeleted()) - { - return; - } - //if already called forceImmediateUpdate() - if(mInImageList && mMaxVirtualSize == LLViewerFetchedTexture::sMaxVirtualSize) - { - return; - } - - gTextureList.forceImmediateUpdate(this); - return; -} - -LLImageRaw* LLViewerFetchedTexture::reloadRawImage(S8 discard_level) -{ - llassert(mGLTexturep.notNull()); - llassert(discard_level >= 0); - llassert(mComponents > 0); - - if (mRawImage.notNull()) - { - //mRawImage is in use by somebody else, do not delete it. - return NULL; - } - - if(mSavedRawDiscardLevel >= 0 && mSavedRawDiscardLevel <= discard_level) - { - if (mSavedRawDiscardLevel != discard_level - && mBoostLevel != BOOST_ICON - && mBoostLevel != BOOST_THUMBNAIL) - { - mRawImage = new LLImageRaw(getWidth(discard_level), getHeight(discard_level), getComponents()); - mRawImage->copy(getSavedRawImage()); - } - else - { - mRawImage = getSavedRawImage(); - } - mRawDiscardLevel = discard_level; - } - else - { - //force to fetch raw image again if cached raw image is not good enough. - if(mCachedRawDiscardLevel > discard_level) - { - mRawImage = mCachedRawImage; - mRawDiscardLevel = mCachedRawDiscardLevel; - } - else //cached raw image is good enough, copy it. - { - if(mCachedRawDiscardLevel != discard_level) - { - mRawImage = new LLImageRaw(getWidth(discard_level), getHeight(discard_level), getComponents()); - mRawImage->copy(mCachedRawImage); - } - else - { - mRawImage = mCachedRawImage; - } - mRawDiscardLevel = discard_level; - } - } - mIsRawImageValid = true; - sRawCount++; - - return mRawImage; -} - -bool LLViewerFetchedTexture::needsToSaveRawImage() -{ - return mForceToSaveRawImage || mSaveRawImage; -} - -void LLViewerFetchedTexture::destroyRawImage() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if (mAuxRawImage.notNull() && !needsToSaveRawImage()) - { - sAuxCount--; - mAuxRawImage = NULL; - } - - if (mRawImage.notNull()) - { - sRawCount--; - - if(mIsRawImageValid) - { - if(needsToSaveRawImage()) - { - saveRawImage(); - } - setCachedRawImage(); - } - - mRawImage = NULL; - - mIsRawImageValid = false; - mRawDiscardLevel = INVALID_DISCARD_LEVEL; - } -} - -//use the mCachedRawImage to (re)generate the gl texture. -//virtual -void LLViewerFetchedTexture::switchToCachedImage() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if(mCachedRawImage.notNull() && - !mNeedsCreateTexture) // <--- texture creation is pending, don't step on it - { - mRawImage = mCachedRawImage; - - if (getComponents() != mRawImage->getComponents()) - { - // We've changed the number of components, so we need to move any - // objects using this pool to a different pool. - mComponents = mRawImage->getComponents(); - mGLTexturep->setComponents(mComponents); - gTextureList.dirtyImage(this); - } - - mIsRawImageValid = true; - mRawDiscardLevel = mCachedRawDiscardLevel; - - scheduleCreateTexture(); - } -} - -//cache the imageraw forcefully. -//virtual -void LLViewerFetchedTexture::setCachedRawImage(S32 discard_level, LLImageRaw* imageraw) -{ - if(imageraw != mRawImage.get()) - { - if (mBoostLevel == LLGLTexture::BOOST_ICON) - { - S32 expected_width = mKnownDrawWidth > 0 ? mKnownDrawWidth : DEFAULT_ICON_DIMENSIONS; - S32 expected_height = mKnownDrawHeight > 0 ? mKnownDrawHeight : DEFAULT_ICON_DIMENSIONS; - if (mRawImage->getWidth() > expected_width || mRawImage->getHeight() > expected_height) - { - mCachedRawImage = new LLImageRaw(expected_width, expected_height, imageraw->getComponents()); - mCachedRawImage->copyScaled(imageraw); - } - else - { - mCachedRawImage = imageraw; - } - } - else if (mBoostLevel == LLGLTexture::BOOST_THUMBNAIL) - { - S32 expected_width = mKnownDrawWidth > 0 ? mKnownDrawWidth : DEFAULT_THUMBNAIL_DIMENSIONS; - S32 expected_height = mKnownDrawHeight > 0 ? mKnownDrawHeight : DEFAULT_THUMBNAIL_DIMENSIONS; - if (mRawImage->getWidth() > expected_width || mRawImage->getHeight() > expected_height) - { - mCachedRawImage = new LLImageRaw(expected_width, expected_height, imageraw->getComponents()); - mCachedRawImage->copyScaled(imageraw); - } - else - { - mCachedRawImage = imageraw; - } - } - else - { - mCachedRawImage = imageraw; - } - mCachedRawDiscardLevel = discard_level; - mCachedRawImageReady = true; - } -} - -void LLViewerFetchedTexture::setCachedRawImage() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if(mRawImage == mCachedRawImage) - { - return; - } - if(!mIsRawImageValid) - { - return; - } - - if(mCachedRawImageReady) - { - return; - } - - if(mCachedRawDiscardLevel < 0 || mCachedRawDiscardLevel > mRawDiscardLevel) - { - S32 i = 0; - S32 w = mRawImage->getWidth(); - S32 h = mRawImage->getHeight(); - - S32 max_size = MAX_CACHED_RAW_IMAGE_AREA; - if(LLGLTexture::BOOST_TERRAIN == mBoostLevel) - { - max_size = MAX_CACHED_RAW_TERRAIN_IMAGE_AREA; - } - if(mForSculpt) - { - max_size = MAX_CACHED_RAW_SCULPT_IMAGE_AREA; - mCachedRawImageReady = !mRawDiscardLevel; - } - else - { - mCachedRawImageReady = (!mRawDiscardLevel || ((w * h) >= max_size)); - } - - while(((w >> i) * (h >> i)) > max_size) - { - ++i; - } - - if(i) - { - if(!(w >> i) || !(h >> i)) - { - --i; - } - - { - //make a duplicate in case somebody else is using this raw image - mRawImage = mRawImage->scaled(w >> i, h >> i); - } - } - mCachedRawImage = mRawImage; - mRawDiscardLevel += i; - mCachedRawDiscardLevel = mRawDiscardLevel; - } -} - -void LLViewerFetchedTexture::checkCachedRawSculptImage() -{ - if(mCachedRawImageReady && mCachedRawDiscardLevel > 0) - { - if(getDiscardLevel() != 0) - { - mCachedRawImageReady = false; - } - else if(isForSculptOnly()) - { - resetTextureStats(); //do not update this image any more. - } - } -} - -void LLViewerFetchedTexture::saveRawImage() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if(mRawImage.isNull() || mRawImage == mSavedRawImage || (mSavedRawDiscardLevel >= 0 && mSavedRawDiscardLevel <= mRawDiscardLevel)) - { - return; - } - - LLImageDataSharedLock lock(mRawImage); - - mSavedRawDiscardLevel = mRawDiscardLevel; - if (mBoostLevel == LLGLTexture::BOOST_ICON) - { - S32 expected_width = mKnownDrawWidth > 0 ? mKnownDrawWidth : DEFAULT_ICON_DIMENSIONS; - S32 expected_height = mKnownDrawHeight > 0 ? mKnownDrawHeight : DEFAULT_ICON_DIMENSIONS; - if (mRawImage->getWidth() > expected_width || mRawImage->getHeight() > expected_height) - { - mSavedRawImage = new LLImageRaw(expected_width, expected_height, mRawImage->getComponents()); - mSavedRawImage->copyScaled(mRawImage); - } - else - { - mSavedRawImage = new LLImageRaw(mRawImage->getData(), mRawImage->getWidth(), mRawImage->getHeight(), mRawImage->getComponents()); - } - } - else if (mBoostLevel == LLGLTexture::BOOST_THUMBNAIL) - { - S32 expected_width = mKnownDrawWidth > 0 ? mKnownDrawWidth : DEFAULT_THUMBNAIL_DIMENSIONS; - S32 expected_height = mKnownDrawHeight > 0 ? mKnownDrawHeight : DEFAULT_THUMBNAIL_DIMENSIONS; - if (mRawImage->getWidth() > expected_width || mRawImage->getHeight() > expected_height) - { - mSavedRawImage = new LLImageRaw(expected_width, expected_height, mRawImage->getComponents()); - mSavedRawImage->copyScaled(mRawImage); - } - else - { - mSavedRawImage = new LLImageRaw(mRawImage->getData(), mRawImage->getWidth(), mRawImage->getHeight(), mRawImage->getComponents()); - } - } - else - { - mSavedRawImage = new LLImageRaw(mRawImage->getData(), mRawImage->getWidth(), mRawImage->getHeight(), mRawImage->getComponents()); - } - - if(mForceToSaveRawImage && mSavedRawDiscardLevel <= mDesiredSavedRawDiscardLevel) - { - mForceToSaveRawImage = false; - } - - mLastReferencedSavedRawImageTime = sCurrentTime; -} - -//force to refetch the texture to the discard level -void LLViewerFetchedTexture::forceToRefetchTexture(S32 desired_discard, F32 kept_time) -{ - if(mForceToSaveRawImage) - { - desired_discard = llmin(desired_discard, mDesiredSavedRawDiscardLevel); - kept_time = llmax(kept_time, mKeptSavedRawImageTime); - } - - //trigger a new fetch. - mForceToSaveRawImage = true ; - mDesiredSavedRawDiscardLevel = desired_discard ; - mKeptSavedRawImageTime = kept_time ; - mLastReferencedSavedRawImageTime = sCurrentTime ; - mSavedRawImage = NULL ; - mSavedRawDiscardLevel = -1 ; -} - -void LLViewerFetchedTexture::forceToSaveRawImage(S32 desired_discard, F32 kept_time) -{ - mKeptSavedRawImageTime = kept_time; - mLastReferencedSavedRawImageTime = sCurrentTime; - - if(mSavedRawDiscardLevel > -1 && mSavedRawDiscardLevel <= desired_discard) - { - return; //raw imge is ready. - } - - if(!mForceToSaveRawImage || mDesiredSavedRawDiscardLevel < 0 || mDesiredSavedRawDiscardLevel > desired_discard) - { - mForceToSaveRawImage = true; - mDesiredSavedRawDiscardLevel = desired_discard; - - //copy from the cached raw image if exists. - if(mCachedRawImage.notNull() && mRawImage.isNull() ) - { - mRawImage = mCachedRawImage; - mRawDiscardLevel = mCachedRawDiscardLevel; - - saveRawImage(); - - mRawImage = NULL; - mRawDiscardLevel = INVALID_DISCARD_LEVEL; - } - } -} -void LLViewerFetchedTexture::destroySavedRawImage() -{ - if(mLastReferencedSavedRawImageTime < mKeptSavedRawImageTime) - { - return; //keep the saved raw image. - } - - mForceToSaveRawImage = false; - mSaveRawImage = false; - - clearCallbackEntryList(); - - mSavedRawImage = NULL ; - mForceToSaveRawImage = false ; - mSaveRawImage = false ; - mSavedRawDiscardLevel = -1 ; - mDesiredSavedRawDiscardLevel = -1 ; - mLastReferencedSavedRawImageTime = 0.0f ; - mKeptSavedRawImageTime = 0.f ; - - if(mAuxRawImage.notNull()) - { - sAuxCount--; - mAuxRawImage = NULL; - } -} - -LLImageRaw* LLViewerFetchedTexture::getSavedRawImage() -{ - mLastReferencedSavedRawImageTime = sCurrentTime; - - return mSavedRawImage; -} - -bool LLViewerFetchedTexture::hasSavedRawImage() const -{ - return mSavedRawImage.notNull(); -} - -F32 LLViewerFetchedTexture::getElapsedLastReferencedSavedRawImageTime() const -{ - return sCurrentTime - mLastReferencedSavedRawImageTime; -} - -//---------------------------------------------------------------------------------------------- -//end of LLViewerFetchedTexture -//---------------------------------------------------------------------------------------------- - -//---------------------------------------------------------------------------------------------- -//start of LLViewerLODTexture -//---------------------------------------------------------------------------------------------- -LLViewerLODTexture::LLViewerLODTexture(const LLUUID& id, FTType f_type, const LLHost& host, bool usemipmaps) - : LLViewerFetchedTexture(id, f_type, host, usemipmaps) -{ - init(true); -} - -LLViewerLODTexture::LLViewerLODTexture(const std::string& url, FTType f_type, const LLUUID& id, bool usemipmaps) - : LLViewerFetchedTexture(url, f_type, id, usemipmaps) -{ - init(true); -} - -void LLViewerLODTexture::init(bool firstinit) -{ - mTexelsPerImage = 64.f*64.f; - mDiscardVirtualSize = 0.f; - mCalculatedDiscardLevel = -1.f; -} - -//virtual -S8 LLViewerLODTexture::getType() const -{ - return LLViewerTexture::LOD_TEXTURE; -} - -bool LLViewerLODTexture::isUpdateFrozen() -{ - return LLViewerTexture::sFreezeImageUpdates; -} - -// This is gauranteed to get called periodically for every texture -//virtual -void LLViewerLODTexture::processTextureStats() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - updateVirtualSize(); - - static LLCachedControl textures_fullres(gSavedSettings,"TextureLoadFullRes", false); - - if (textures_fullres) - { - mDesiredDiscardLevel = 0; - } - // Generate the request priority and render priority - else if (mDontDiscard || !mUseMipMaps) - { - mDesiredDiscardLevel = 0; - if (mFullWidth > MAX_IMAGE_SIZE_DEFAULT || mFullHeight > MAX_IMAGE_SIZE_DEFAULT) - mDesiredDiscardLevel = 1; // MAX_IMAGE_SIZE_DEFAULT = 1024 and max size ever is 2048 - } - else if (mBoostLevel < LLGLTexture::BOOST_HIGH && mMaxVirtualSize <= 10.f) - { - // If the image has not been significantly visible in a while, we don't want it - mDesiredDiscardLevel = llmin(mMinDesiredDiscardLevel, (S8)(MAX_DISCARD_LEVEL + 1)); - } - else if (!mFullWidth || !mFullHeight) - { - mDesiredDiscardLevel = getMaxDiscardLevel(); - } - else - { - //static const F64 log_2 = log(2.0); - static const F64 log_4 = log(4.0); - - F32 discard_level = 0.f; - - // If we know the output width and height, we can force the discard - // level to the correct value, and thus not decode more texture - // data than we need to. - if (mKnownDrawWidth && mKnownDrawHeight) - { - S32 draw_texels = mKnownDrawWidth * mKnownDrawHeight; - draw_texels = llclamp(draw_texels, MIN_IMAGE_AREA, MAX_IMAGE_AREA); - - // Use log_4 because we're in square-pixel space, so an image - // with twice the width and twice the height will have mTexelsPerImage - // 4 * draw_size - discard_level = (F32)(log(mTexelsPerImage / draw_texels) / log_4); - } - else - { - // Calculate the required scale factor of the image using pixels per texel - discard_level = (F32)(log(mTexelsPerImage / mMaxVirtualSize) / log_4); - mDiscardVirtualSize = mMaxVirtualSize; - mCalculatedDiscardLevel = discard_level; - } - if (mBoostLevel < LLGLTexture::BOOST_SCULPTED) - { - discard_level *= sDesiredDiscardScale; // scale (default 1.1f) - } - discard_level = floorf(discard_level); - - F32 min_discard = 0.f; - U32 desired_size = MAX_IMAGE_SIZE_DEFAULT; // MAX_IMAGE_SIZE_DEFAULT = 1024 and max size ever is 2048 - if (mBoostLevel <= LLGLTexture::BOOST_SCULPTED) - { - desired_size = DESIRED_NORMAL_TEXTURE_SIZE; - } - if (mFullWidth > desired_size || mFullHeight > desired_size) - min_discard = 1.f; - - discard_level = llclamp(discard_level, min_discard, (F32)MAX_DISCARD_LEVEL); - - // Can't go higher than the max discard level - mDesiredDiscardLevel = llmin(getMaxDiscardLevel() + 1, (S32)discard_level); - // Clamp to min desired discard - mDesiredDiscardLevel = llmin(mMinDesiredDiscardLevel, mDesiredDiscardLevel); - - // - // At this point we've calculated the quality level that we want, - // if possible. Now we check to see if we have it, and take the - // proper action if we don't. - // - - S32 current_discard = getDiscardLevel(); - if (mBoostLevel < LLGLTexture::BOOST_AVATAR_BAKED && - current_discard >= 0) - { - if (current_discard < (mDesiredDiscardLevel-1) && !mForceToSaveRawImage) - { // should scale down - scaleDown(); - } - } - - if (isUpdateFrozen() // we are out of memory and nearing max allowed bias - && mBoostLevel < LLGLTexture::BOOST_SCULPTED - && mDesiredDiscardLevel < current_discard) - { - // stop requesting more - mDesiredDiscardLevel = current_discard; - } - } - - if(mForceToSaveRawImage && mDesiredSavedRawDiscardLevel >= 0) - { - mDesiredDiscardLevel = llmin(mDesiredDiscardLevel, (S8)mDesiredSavedRawDiscardLevel); - } - - // decay max virtual size over time - mMaxVirtualSize *= 0.8f; - - // selection manager will immediately reset BOOST_SELECTED but never unsets it - // unset it immediately after we consume it - if (getBoostLevel() == BOOST_SELECTED) - { - setBoostLevel(BOOST_NONE); - } -} - -bool LLViewerLODTexture::scaleDown() -{ - if(hasGLTexture() && mCachedRawDiscardLevel > getDiscardLevel()) - { - switchToCachedImage(); - - LLTexturePipelineTester* tester = (LLTexturePipelineTester*)LLMetricPerformanceTesterBasic::getTester(sTesterName); - if (tester) - { - tester->setStablizingTime(); - } - - return true; - } - return false; -} -//---------------------------------------------------------------------------------------------- -//end of LLViewerLODTexture -//---------------------------------------------------------------------------------------------- - -//---------------------------------------------------------------------------------------------- -//start of LLViewerMediaTexture -//---------------------------------------------------------------------------------------------- -//static -void LLViewerMediaTexture::updateClass() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - static const F32 MAX_INACTIVE_TIME = 30.f; - -#if 0 - //force to play media. - gSavedSettings.setBOOL("AudioStreamingMedia", true); -#endif - - for(media_map_t::iterator iter = sMediaMap.begin(); iter != sMediaMap.end(); ) - { - LLViewerMediaTexture* mediap = iter->second; - - if(mediap->getNumRefs() == 1) //one reference by sMediaMap - { - // - //Note: delay some time to delete the media textures to stop endlessly creating and immediately removing media texture. - // - if(mediap->getLastReferencedTimer()->getElapsedTimeF32() > MAX_INACTIVE_TIME) - { - media_map_t::iterator cur = iter++; - sMediaMap.erase(cur); - continue; - } - } - ++iter; - } -} - -//static -void LLViewerMediaTexture::removeMediaImplFromTexture(const LLUUID& media_id) -{ - LLViewerMediaTexture* media_tex = findMediaTexture(media_id); - if(media_tex) - { - media_tex->invalidateMediaImpl(); - } -} - -//static -void LLViewerMediaTexture::cleanUpClass() -{ - sMediaMap.clear(); -} - -//static -LLViewerMediaTexture* LLViewerMediaTexture::findMediaTexture(const LLUUID& media_id) -{ - media_map_t::iterator iter = sMediaMap.find(media_id); - if(iter == sMediaMap.end()) - { - return NULL; - } - - LLViewerMediaTexture* media_tex = iter->second; - media_tex->setMediaImpl(); - media_tex->getLastReferencedTimer()->reset(); - - return media_tex; -} - -LLViewerMediaTexture::LLViewerMediaTexture(const LLUUID& id, bool usemipmaps, LLImageGL* gl_image) - : LLViewerTexture(id, usemipmaps), - mMediaImplp(NULL), - mUpdateVirtualSizeTime(0) -{ - sMediaMap.insert(std::make_pair(id, this)); - - mGLTexturep = gl_image; - - if(mGLTexturep.isNull()) - { - generateGLTexture(); - } - - mGLTexturep->setAllowCompression(false); - - mGLTexturep->setNeedsAlphaAndPickMask(false); - - mIsPlaying = false; - - setMediaImpl(); - - setCategory(LLGLTexture::MEDIA); - - LLViewerTexture* tex = gTextureList.findImage(mID, TEX_LIST_STANDARD); - if(tex) //this media is a parcel media for tex. - { - tex->setParcelMedia(this); - } -} - -//virtual -LLViewerMediaTexture::~LLViewerMediaTexture() -{ - LLViewerTexture* tex = gTextureList.findImage(mID, TEX_LIST_STANDARD); - if(tex) //this media is a parcel media for tex. - { - tex->setParcelMedia(NULL); - } -} - -void LLViewerMediaTexture::reinit(bool usemipmaps /* = true */) -{ - llassert(mGLTexturep.notNull()); - - mUseMipMaps = usemipmaps; - getLastReferencedTimer()->reset(); - mGLTexturep->setUseMipMaps(mUseMipMaps); - mGLTexturep->setNeedsAlphaAndPickMask(false); -} - -void LLViewerMediaTexture::setUseMipMaps(bool mipmap) -{ - mUseMipMaps = mipmap; - - if(mGLTexturep.notNull()) - { - mGLTexturep->setUseMipMaps(mipmap); - } -} - -//virtual -S8 LLViewerMediaTexture::getType() const -{ - return LLViewerTexture::MEDIA_TEXTURE; -} - -void LLViewerMediaTexture::invalidateMediaImpl() -{ - mMediaImplp = NULL; -} - -void LLViewerMediaTexture::setMediaImpl() -{ - if(!mMediaImplp) - { - mMediaImplp = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mID); - } -} - -//return true if all faces to reference to this media texture are found -//Note: mMediaFaceList is valid only for the current instant -// because it does not check the face validity after the current frame. -bool LLViewerMediaTexture::findFaces() -{ - mMediaFaceList.clear(); - - bool ret = true; - - LLViewerTexture* tex = gTextureList.findImage(mID, TEX_LIST_STANDARD); - if(tex) //this media is a parcel media for tex. - { - for (U32 ch = 0; ch < LLRender::NUM_TEXTURE_CHANNELS; ++ch) - { - const ll_face_list_t* face_list = tex->getFaceList(ch); - U32 end = tex->getNumFaces(ch); - for(U32 i = 0; i < end; i++) - { - if ((*face_list)[i]->isMediaAllowed()) - { - mMediaFaceList.push_back((*face_list)[i]); - } - } - } - } - - if(!mMediaImplp) - { - return true; - } - - //for media on a face. - const std::list< LLVOVolume* >* obj_list = mMediaImplp->getObjectList(); - std::list< LLVOVolume* >::const_iterator iter = obj_list->begin(); - for(; iter != obj_list->end(); ++iter) - { - LLVOVolume* obj = *iter; - if (obj->isDead()) - { - // Isn't supposed to happen, objects are supposed to detach - // themselves on markDead() - // If this happens, viewer is likely to crash - llassert(0); - LL_WARNS() << "Dead object in mMediaImplp's object list" << LL_ENDL; - ret = false; - continue; - } - - if (obj->mDrawable.isNull() || obj->mDrawable->isDead()) - { - ret = false; - continue; - } - - S32 face_id = -1; - S32 num_faces = obj->mDrawable->getNumFaces(); - while((face_id = obj->getFaceIndexWithMediaImpl(mMediaImplp, face_id)) > -1 && face_id < num_faces) - { - LLFace* facep = obj->mDrawable->getFace(face_id); - if(facep) - { - mMediaFaceList.push_back(facep); - } - else - { - ret = false; - } - } - } - - return ret; -} - -void LLViewerMediaTexture::initVirtualSize() -{ - if(mIsPlaying) - { - return; - } - - findFaces(); - for(std::list< LLFace* >::iterator iter = mMediaFaceList.begin(); iter!= mMediaFaceList.end(); ++iter) - { - addTextureStats((*iter)->getVirtualSize()); - } -} - -void LLViewerMediaTexture::addMediaToFace(LLFace* facep) -{ - if(facep) - { - facep->setHasMedia(true); - } - if(!mIsPlaying) - { - return; //no need to add the face because the media is not in playing. - } - - switchTexture(LLRender::DIFFUSE_MAP, facep); -} - -void LLViewerMediaTexture::removeMediaFromFace(LLFace* facep) -{ - if(!facep) - { - return; - } - facep->setHasMedia(false); - - if(!mIsPlaying) - { - return; //no need to remove the face because the media is not in playing. - } - - mIsPlaying = false; //set to remove the media from the face. - switchTexture(LLRender::DIFFUSE_MAP, facep); - mIsPlaying = true; //set the flag back. - - if(getTotalNumFaces() < 1) //no face referencing to this media - { - stopPlaying(); - } -} - -//virtual -void LLViewerMediaTexture::addFace(U32 ch, LLFace* facep) -{ - LLViewerTexture::addFace(ch, facep); - - const LLTextureEntry* te = facep->getTextureEntry(); - if(te && te->getID().notNull()) - { - LLViewerTexture* tex = gTextureList.findImage(te->getID(), TEX_LIST_STANDARD); - if(tex) - { - mTextureList.push_back(tex);//increase the reference number by one for tex to avoid deleting it. - return; - } - } - - //check if it is a parcel media - if(facep->getTexture() && facep->getTexture() != this && facep->getTexture()->getID() == mID) - { - mTextureList.push_back(facep->getTexture()); //a parcel media. - return; - } - - if(te && te->getID().notNull()) //should have a texture - { - LL_WARNS_ONCE() << "The face's texture " << te->getID() << " is not valid. Face must have a valid texture before media texture." << LL_ENDL; - // This might break the object, but it likely isn't a 'recoverable' situation. - LLViewerFetchedTexture* tex = LLViewerTextureManager::getFetchedTexture(te->getID()); - mTextureList.push_back(tex); - } -} - -//virtual -void LLViewerMediaTexture::removeFace(U32 ch, LLFace* facep) -{ - LLViewerTexture::removeFace(ch, facep); - - const LLTextureEntry* te = facep->getTextureEntry(); - if(te && te->getID().notNull()) - { - LLViewerTexture* tex = gTextureList.findImage(te->getID(), TEX_LIST_STANDARD); - if(tex) - { - for(std::list< LLPointer >::iterator iter = mTextureList.begin(); - iter != mTextureList.end(); ++iter) - { - if(*iter == tex) - { - mTextureList.erase(iter); //decrease the reference number for tex by one. - return; - } - } - - std::vector te_list; - - for (U32 ch = 0; ch < 3; ++ch) - { - // - //we have some trouble here: the texture of the face is changed. - //we need to find the former texture, and remove it from the list to avoid memory leaking. - - llassert(mNumFaces[ch] <= mFaceList[ch].size()); - - for(U32 j = 0; j < mNumFaces[ch]; j++) - { - te_list.push_back(mFaceList[ch][j]->getTextureEntry());//all textures are in use. - } - } - - if (te_list.empty()) - { - mTextureList.clear(); - return; - } - - S32 end = te_list.size(); - - for(std::list< LLPointer >::iterator iter = mTextureList.begin(); - iter != mTextureList.end(); ++iter) - { - S32 i = 0; - - for(i = 0; i < end; i++) - { - if(te_list[i] && te_list[i]->getID() == (*iter)->getID())//the texture is in use. - { - te_list[i] = NULL; - break; - } - } - if(i == end) //no hit for this texture, remove it. - { - mTextureList.erase(iter); //decrease the reference number for tex by one. - return; - } - } - } - } - - //check if it is a parcel media - for(std::list< LLPointer >::iterator iter = mTextureList.begin(); - iter != mTextureList.end(); ++iter) - { - if((*iter)->getID() == mID) - { - mTextureList.erase(iter); //decrease the reference number for tex by one. - return; - } - } - - if(te && te->getID().notNull()) //should have a texture but none found - { - LL_ERRS() << "mTextureList texture reference number is corrupted. Texture id: " << te->getID() << " List size: " << (U32)mTextureList.size() << LL_ENDL; - } -} - -void LLViewerMediaTexture::stopPlaying() -{ - // Don't stop the media impl playing here -- this breaks non-inworld media (login screen, search, and media browser). -// if(mMediaImplp) -// { -// mMediaImplp->stop(); -// } - mIsPlaying = false; -} - -void LLViewerMediaTexture::switchTexture(U32 ch, LLFace* facep) -{ - if(facep) - { - //check if another media is playing on this face. - if(facep->getTexture() && facep->getTexture() != this - && facep->getTexture()->getType() == LLViewerTexture::MEDIA_TEXTURE) - { - if(mID == facep->getTexture()->getID()) //this is a parcel media - { - return; //let the prim media win. - } - } - - if(mIsPlaying) //old textures switch to the media texture - { - facep->switchTexture(ch, this); - } - else //switch to old textures. - { - const LLTextureEntry* te = facep->getTextureEntry(); - if(te) - { - LLViewerTexture* tex = te->getID().notNull() ? gTextureList.findImage(te->getID(), TEX_LIST_STANDARD) : NULL; - if(!tex && te->getID() != mID)//try parcel media. - { - tex = gTextureList.findImage(mID, TEX_LIST_STANDARD); - } - if(!tex) - { - tex = LLViewerFetchedTexture::sDefaultImagep; - } - facep->switchTexture(ch, tex); - } - } - } -} - -void LLViewerMediaTexture::setPlaying(bool playing) -{ - if(!mMediaImplp) - { - return; - } - if(!playing && !mIsPlaying) - { - return; //media is already off - } - - if(playing == mIsPlaying && !mMediaImplp->isUpdated()) - { - return; //nothing has changed since last time. - } - - mIsPlaying = playing; - if(mIsPlaying) //is about to play this media - { - if(findFaces()) - { - //about to update all faces. - mMediaImplp->setUpdated(false); - } - - if(mMediaFaceList.empty())//no face pointing to this media - { - stopPlaying(); - return; - } - - for(std::list< LLFace* >::iterator iter = mMediaFaceList.begin(); iter!= mMediaFaceList.end(); ++iter) - { - switchTexture(LLRender::DIFFUSE_MAP, *iter); - } - } - else //stop playing this media - { - U32 ch = LLRender::DIFFUSE_MAP; - - llassert(mNumFaces[ch] <= mFaceList[ch].size()); - for(U32 i = mNumFaces[ch]; i; i--) - { - switchTexture(ch, mFaceList[ch][i - 1]); //current face could be removed in this function. - } - } - return; -} - -//virtual -F32 LLViewerMediaTexture::getMaxVirtualSize() -{ - if(LLFrameTimer::getFrameCount() == mUpdateVirtualSizeTime) - { - return mMaxVirtualSize; - } - mUpdateVirtualSizeTime = LLFrameTimer::getFrameCount(); - - if(!mMaxVirtualSizeResetCounter) - { - addTextureStats(0.f, false);//reset - } - - if(mIsPlaying) //media is playing - { - for (U32 ch = 0; ch < LLRender::NUM_TEXTURE_CHANNELS; ++ch) - { - llassert(mNumFaces[ch] <= mFaceList[ch].size()); - for(U32 i = 0; i < mNumFaces[ch]; i++) - { - LLFace* facep = mFaceList[ch][i]; - if(facep->getDrawable()->isRecentlyVisible()) - { - addTextureStats(facep->getVirtualSize()); - } - } - } - } - else //media is not in playing - { - findFaces(); - - if(!mMediaFaceList.empty()) - { - for(std::list< LLFace* >::iterator iter = mMediaFaceList.begin(); iter!= mMediaFaceList.end(); ++iter) - { - LLFace* facep = *iter; - if(facep->getDrawable()->isRecentlyVisible()) - { - addTextureStats(facep->getVirtualSize()); - } - } - } - } - - if(mMaxVirtualSizeResetCounter > 0) - { - mMaxVirtualSizeResetCounter--; - } - reorganizeFaceList(); - reorganizeVolumeList(); - - return mMaxVirtualSize; -} -//---------------------------------------------------------------------------------------------- -//end of LLViewerMediaTexture -//---------------------------------------------------------------------------------------------- - -//---------------------------------------------------------------------------------------------- -//start of LLTexturePipelineTester -//---------------------------------------------------------------------------------------------- -LLTexturePipelineTester::LLTexturePipelineTester() : LLMetricPerformanceTesterWithSession(sTesterName) -{ - addMetric("TotalBytesLoaded"); - addMetric("TotalBytesLoadedFromCache"); - addMetric("TotalBytesLoadedForLargeImage"); - addMetric("TotalBytesLoadedForSculpties"); - addMetric("StartFetchingTime"); - addMetric("TotalGrayTime"); - addMetric("TotalStablizingTime"); - addMetric("StartTimeLoadingSculpties"); - addMetric("EndTimeLoadingSculpties"); - - addMetric("Time"); - addMetric("TotalBytesBound"); - addMetric("TotalBytesBoundForLargeImage"); - addMetric("PercentageBytesBound"); - - mTotalBytesLoaded = (S32Bytes)0; - mTotalBytesLoadedFromCache = (S32Bytes)0; - mTotalBytesLoadedForLargeImage = (S32Bytes)0; - mTotalBytesLoadedForSculpties = (S32Bytes)0; - - reset(); -} - -LLTexturePipelineTester::~LLTexturePipelineTester() -{ - LLViewerTextureManager::sTesterp = NULL; -} - -void LLTexturePipelineTester::update() -{ - mLastTotalBytesUsed = mTotalBytesUsed; - mLastTotalBytesUsedForLargeImage = mTotalBytesUsedForLargeImage; - mTotalBytesUsed = (S32Bytes)0; - mTotalBytesUsedForLargeImage = (S32Bytes)0; - - if(LLAppViewer::getTextureFetch()->getNumRequests() > 0) //fetching list is not empty - { - if(mPause) - { - //start a new fetching session - reset(); - mStartFetchingTime = LLImageGL::sLastFrameTime; - mPause = false; - } - - //update total gray time - if(mUsingDefaultTexture) - { - mUsingDefaultTexture = false; - mTotalGrayTime = LLImageGL::sLastFrameTime - mStartFetchingTime; - } - - //update the stablizing timer. - updateStablizingTime(); - - outputTestResults(); - } - else if(!mPause) - { - //stop the current fetching session - mPause = true; - outputTestResults(); - reset(); - } -} - -void LLTexturePipelineTester::reset() -{ - mPause = true; - - mUsingDefaultTexture = false; - mStartStablizingTime = 0.0f; - mEndStablizingTime = 0.0f; - - mTotalBytesUsed = (S32Bytes)0; - mTotalBytesUsedForLargeImage = (S32Bytes)0; - mLastTotalBytesUsed = (S32Bytes)0; - mLastTotalBytesUsedForLargeImage = (S32Bytes)0; - - mStartFetchingTime = 0.0f; - - mTotalGrayTime = 0.0f; - mTotalStablizingTime = 0.0f; - - mStartTimeLoadingSculpties = 1.0f; - mEndTimeLoadingSculpties = 0.0f; -} - -//virtual -void LLTexturePipelineTester::outputTestRecord(LLSD *sd) -{ - std::string currentLabel = getCurrentLabelName(); - (*sd)[currentLabel]["TotalBytesLoaded"] = (LLSD::Integer)mTotalBytesLoaded.value(); - (*sd)[currentLabel]["TotalBytesLoadedFromCache"] = (LLSD::Integer)mTotalBytesLoadedFromCache.value(); - (*sd)[currentLabel]["TotalBytesLoadedForLargeImage"] = (LLSD::Integer)mTotalBytesLoadedForLargeImage.value(); - (*sd)[currentLabel]["TotalBytesLoadedForSculpties"] = (LLSD::Integer)mTotalBytesLoadedForSculpties.value(); - - (*sd)[currentLabel]["StartFetchingTime"] = (LLSD::Real)mStartFetchingTime; - (*sd)[currentLabel]["TotalGrayTime"] = (LLSD::Real)mTotalGrayTime; - (*sd)[currentLabel]["TotalStablizingTime"] = (LLSD::Real)mTotalStablizingTime; - - (*sd)[currentLabel]["StartTimeLoadingSculpties"] = (LLSD::Real)mStartTimeLoadingSculpties; - (*sd)[currentLabel]["EndTimeLoadingSculpties"] = (LLSD::Real)mEndTimeLoadingSculpties; - - (*sd)[currentLabel]["Time"] = LLImageGL::sLastFrameTime; - (*sd)[currentLabel]["TotalBytesBound"] = (LLSD::Integer)mLastTotalBytesUsed.value(); - (*sd)[currentLabel]["TotalBytesBoundForLargeImage"] = (LLSD::Integer)mLastTotalBytesUsedForLargeImage.value(); - (*sd)[currentLabel]["PercentageBytesBound"] = (LLSD::Real)(100.f * mLastTotalBytesUsed / mTotalBytesLoaded); -} - -void LLTexturePipelineTester::updateTextureBindingStats(const LLViewerTexture* imagep) -{ - U32Bytes mem_size = imagep->getTextureMemory(); - mTotalBytesUsed += mem_size; - - if(MIN_LARGE_IMAGE_AREA <= (U32)(mem_size.value() / (U32)imagep->getComponents())) - { - mTotalBytesUsedForLargeImage += mem_size; - } -} - -void LLTexturePipelineTester::updateTextureLoadingStats(const LLViewerFetchedTexture* imagep, const LLImageRaw* raw_imagep, bool from_cache) -{ - U32Bytes data_size = (U32Bytes)raw_imagep->getDataSize(); - mTotalBytesLoaded += data_size; - - if(from_cache) - { - mTotalBytesLoadedFromCache += data_size; - } - - if(MIN_LARGE_IMAGE_AREA <= (U32)(data_size.value() / (U32)raw_imagep->getComponents())) - { - mTotalBytesLoadedForLargeImage += data_size; - } - - if(imagep->forSculpt()) - { - mTotalBytesLoadedForSculpties += data_size; - - if(mStartTimeLoadingSculpties > mEndTimeLoadingSculpties) - { - mStartTimeLoadingSculpties = LLImageGL::sLastFrameTime; - } - mEndTimeLoadingSculpties = LLImageGL::sLastFrameTime; - } -} - -void LLTexturePipelineTester::updateGrayTextureBinding() -{ - mUsingDefaultTexture = true; -} - -void LLTexturePipelineTester::setStablizingTime() -{ - if(mStartStablizingTime <= mStartFetchingTime) - { - mStartStablizingTime = LLImageGL::sLastFrameTime; - } - mEndStablizingTime = LLImageGL::sLastFrameTime; -} - -void LLTexturePipelineTester::updateStablizingTime() -{ - if(mStartStablizingTime > mStartFetchingTime) - { - F32 t = mEndStablizingTime - mStartStablizingTime; - - if(t > F_ALMOST_ZERO && (t - mTotalStablizingTime) < F_ALMOST_ZERO) - { - //already stablized - mTotalStablizingTime = LLImageGL::sLastFrameTime - mStartStablizingTime; - - //cancel the timer - mStartStablizingTime = 0.f; - mEndStablizingTime = 0.f; - } - else - { - mTotalStablizingTime = t; - } - } - mTotalStablizingTime = 0.f; -} - -//virtual -void LLTexturePipelineTester::compareTestSessions(llofstream* os) -{ - LLTexturePipelineTester::LLTextureTestSession* base_sessionp = dynamic_cast(mBaseSessionp); - LLTexturePipelineTester::LLTextureTestSession* current_sessionp = dynamic_cast(mCurrentSessionp); - if(!base_sessionp || !current_sessionp) - { - LL_ERRS() << "type of test session does not match!" << LL_ENDL; - } - - //compare and output the comparison - *os << llformat("%s\n", getTesterName().c_str()); - *os << llformat("AggregateResults\n"); - - compareTestResults(os, "TotalGrayTime", base_sessionp->mTotalGrayTime, current_sessionp->mTotalGrayTime); - compareTestResults(os, "TotalStablizingTime", base_sessionp->mTotalStablizingTime, current_sessionp->mTotalStablizingTime); - compareTestResults(os, "StartTimeLoadingSculpties", base_sessionp->mStartTimeLoadingSculpties, current_sessionp->mStartTimeLoadingSculpties); - compareTestResults(os, "TotalTimeLoadingSculpties", base_sessionp->mTotalTimeLoadingSculpties, current_sessionp->mTotalTimeLoadingSculpties); - - compareTestResults(os, "TotalBytesLoaded", base_sessionp->mTotalBytesLoaded, current_sessionp->mTotalBytesLoaded); - compareTestResults(os, "TotalBytesLoadedFromCache", base_sessionp->mTotalBytesLoadedFromCache, current_sessionp->mTotalBytesLoadedFromCache); - compareTestResults(os, "TotalBytesLoadedForLargeImage", base_sessionp->mTotalBytesLoadedForLargeImage, current_sessionp->mTotalBytesLoadedForLargeImage); - compareTestResults(os, "TotalBytesLoadedForSculpties", base_sessionp->mTotalBytesLoadedForSculpties, current_sessionp->mTotalBytesLoadedForSculpties); - - *os << llformat("InstantResults\n"); - S32 size = llmin(base_sessionp->mInstantPerformanceListCounter, current_sessionp->mInstantPerformanceListCounter); - for(S32 i = 0; i < size; i++) - { - *os << llformat("Time(B-T)-%.4f-%.4f\n", base_sessionp->mInstantPerformanceList[i].mTime, current_sessionp->mInstantPerformanceList[i].mTime); - - compareTestResults(os, "AverageBytesUsedPerSecond", base_sessionp->mInstantPerformanceList[i].mAverageBytesUsedPerSecond, - current_sessionp->mInstantPerformanceList[i].mAverageBytesUsedPerSecond); - - compareTestResults(os, "AverageBytesUsedForLargeImagePerSecond", base_sessionp->mInstantPerformanceList[i].mAverageBytesUsedForLargeImagePerSecond, - current_sessionp->mInstantPerformanceList[i].mAverageBytesUsedForLargeImagePerSecond); - - compareTestResults(os, "AveragePercentageBytesUsedPerSecond", base_sessionp->mInstantPerformanceList[i].mAveragePercentageBytesUsedPerSecond, - current_sessionp->mInstantPerformanceList[i].mAveragePercentageBytesUsedPerSecond); - } - - if(size < base_sessionp->mInstantPerformanceListCounter) - { - for(S32 i = size; i < base_sessionp->mInstantPerformanceListCounter; i++) - { - *os << llformat("Time(B-T)-%.4f- \n", base_sessionp->mInstantPerformanceList[i].mTime); - - *os << llformat(", AverageBytesUsedPerSecond, %d, N/A \n", base_sessionp->mInstantPerformanceList[i].mAverageBytesUsedPerSecond); - *os << llformat(", AverageBytesUsedForLargeImagePerSecond, %d, N/A \n", base_sessionp->mInstantPerformanceList[i].mAverageBytesUsedForLargeImagePerSecond); - *os << llformat(", AveragePercentageBytesUsedPerSecond, %.4f, N/A \n", base_sessionp->mInstantPerformanceList[i].mAveragePercentageBytesUsedPerSecond); - } - } - else if(size < current_sessionp->mInstantPerformanceListCounter) - { - for(S32 i = size; i < current_sessionp->mInstantPerformanceListCounter; i++) - { - *os << llformat("Time(B-T)- -%.4f\n", current_sessionp->mInstantPerformanceList[i].mTime); - - *os << llformat(", AverageBytesUsedPerSecond, N/A, %d\n", current_sessionp->mInstantPerformanceList[i].mAverageBytesUsedPerSecond); - *os << llformat(", AverageBytesUsedForLargeImagePerSecond, N/A, %d\n", current_sessionp->mInstantPerformanceList[i].mAverageBytesUsedForLargeImagePerSecond); - *os << llformat(", AveragePercentageBytesUsedPerSecond, N/A, %.4f\n", current_sessionp->mInstantPerformanceList[i].mAveragePercentageBytesUsedPerSecond); - } - } -} - -//virtual -LLMetricPerformanceTesterWithSession::LLTestSession* LLTexturePipelineTester::loadTestSession(LLSD* log) -{ - LLTexturePipelineTester::LLTextureTestSession* sessionp = new LLTexturePipelineTester::LLTextureTestSession(); - if(!sessionp) - { - return NULL; - } - - F32 total_gray_time = 0.f; - F32 total_stablizing_time = 0.f; - F32 total_loading_sculpties_time = 0.f; - - F32 start_fetching_time = -1.f; - F32 start_fetching_sculpties_time = 0.f; - - F32 last_time = 0.0f; - S32 frame_count = 0; - - sessionp->mInstantPerformanceListCounter = 0; - sessionp->mInstantPerformanceList.resize(128); - sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedPerSecond = 0; - sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedForLargeImagePerSecond = 0; - sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAveragePercentageBytesUsedPerSecond = 0.f; - sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mTime = 0.f; - - //load a session - std::string currentLabel = getCurrentLabelName(); - bool in_log = (*log).has(currentLabel); - while (in_log) - { - LLSD::String label = currentLabel; - - if(sessionp->mInstantPerformanceListCounter >= (S32)sessionp->mInstantPerformanceList.size()) - { - sessionp->mInstantPerformanceList.resize(sessionp->mInstantPerformanceListCounter + 128); - } - - //time - F32 start_time = (*log)[label]["StartFetchingTime"].asReal(); - F32 cur_time = (*log)[label]["Time"].asReal(); - if(start_time - start_fetching_time > F_ALMOST_ZERO) //fetching has paused for a while - { - sessionp->mTotalGrayTime += total_gray_time; - sessionp->mTotalStablizingTime += total_stablizing_time; - - sessionp->mStartTimeLoadingSculpties = start_fetching_sculpties_time; - sessionp->mTotalTimeLoadingSculpties += total_loading_sculpties_time; - - start_fetching_time = start_time; - total_gray_time = 0.f; - total_stablizing_time = 0.f; - total_loading_sculpties_time = 0.f; - } - else - { - total_gray_time = (*log)[label]["TotalGrayTime"].asReal(); - total_stablizing_time = (*log)[label]["TotalStablizingTime"].asReal(); - - total_loading_sculpties_time = (*log)[label]["EndTimeLoadingSculpties"].asReal() - (*log)[label]["StartTimeLoadingSculpties"].asReal(); - if(start_fetching_sculpties_time < 0.f && total_loading_sculpties_time > 0.f) - { - start_fetching_sculpties_time = (*log)[label]["StartTimeLoadingSculpties"].asReal(); - } - } - - //total loaded bytes - sessionp->mTotalBytesLoaded = (*log)[label]["TotalBytesLoaded"].asInteger(); - sessionp->mTotalBytesLoadedFromCache = (*log)[label]["TotalBytesLoadedFromCache"].asInteger(); - sessionp->mTotalBytesLoadedForLargeImage = (*log)[label]["TotalBytesLoadedForLargeImage"].asInteger(); - sessionp->mTotalBytesLoadedForSculpties = (*log)[label]["TotalBytesLoadedForSculpties"].asInteger(); - - //instant metrics - sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedPerSecond += - (*log)[label]["TotalBytesBound"].asInteger(); - sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedForLargeImagePerSecond += - (*log)[label]["TotalBytesBoundForLargeImage"].asInteger(); - sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAveragePercentageBytesUsedPerSecond += - (*log)[label]["PercentageBytesBound"].asReal(); - frame_count++; - if(cur_time - last_time >= 1.0f) - { - sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedPerSecond /= frame_count; - sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedForLargeImagePerSecond /= frame_count; - sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAveragePercentageBytesUsedPerSecond /= frame_count; - sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mTime = last_time; - - frame_count = 0; - last_time = cur_time; - sessionp->mInstantPerformanceListCounter++; - sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedPerSecond = 0; - sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedForLargeImagePerSecond = 0; - sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAveragePercentageBytesUsedPerSecond = 0.f; - sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mTime = 0.f; - } - // Next label - incrementCurrentCount(); - currentLabel = getCurrentLabelName(); - in_log = (*log).has(currentLabel); - } - - sessionp->mTotalGrayTime += total_gray_time; - sessionp->mTotalStablizingTime += total_stablizing_time; - - if(sessionp->mStartTimeLoadingSculpties < 0.f) - { - sessionp->mStartTimeLoadingSculpties = start_fetching_sculpties_time; - } - sessionp->mTotalTimeLoadingSculpties += total_loading_sculpties_time; - - return sessionp; -} - -LLTexturePipelineTester::LLTextureTestSession::LLTextureTestSession() -{ - reset(); -} -LLTexturePipelineTester::LLTextureTestSession::~LLTextureTestSession() -{ -} -void LLTexturePipelineTester::LLTextureTestSession::reset() -{ - mTotalGrayTime = 0.0f; - mTotalStablizingTime = 0.0f; - - mStartTimeLoadingSculpties = 0.0f; - mTotalTimeLoadingSculpties = 0.0f; - - mTotalBytesLoaded = 0; - mTotalBytesLoadedFromCache = 0; - mTotalBytesLoadedForLargeImage = 0; - mTotalBytesLoadedForSculpties = 0; - - mInstantPerformanceListCounter = 0; -} -//---------------------------------------------------------------------------------------------- -//end of LLTexturePipelineTester -//---------------------------------------------------------------------------------------------- - + +/** + * @file llviewertexture.cpp + * @brief Object which handles a received image (and associated texture(s)) + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llviewertexture.h" + +// Library includes +#include "llmath.h" +#include "llerror.h" +#include "llgl.h" +#include "llglheaders.h" +#include "llhost.h" +#include "llimage.h" +#include "llimagebmp.h" +#include "llimagej2c.h" +#include "llimagetga.h" +#include "llstl.h" +#include "message.h" +#include "lltimer.h" + +// viewer includes +#include "llimagegl.h" +#include "lldrawpool.h" +#include "lltexturefetch.h" +#include "llviewertexturelist.h" +#include "llviewercontrol.h" +#include "pipeline.h" +#include "llappviewer.h" +#include "llface.h" +#include "llviewercamera.h" +#include "lltextureentry.h" +#include "lltexturemanagerbridge.h" +#include "llmediaentry.h" +#include "llvovolume.h" +#include "llviewermedia.h" +#include "lltexturecache.h" +#include "llviewerwindow.h" +#include "llwindow.h" +/////////////////////////////////////////////////////////////////////////////// + +// extern +const S32Megabytes gMinVideoRam(32); +const S32Megabytes gMaxVideoRam(512); + + +// statics +LLPointer LLViewerTexture::sNullImagep = NULL; +LLPointer LLViewerTexture::sBlackImagep = NULL; +LLPointer LLViewerTexture::sCheckerBoardImagep = NULL; +LLPointer LLViewerFetchedTexture::sMissingAssetImagep = NULL; +LLPointer LLViewerFetchedTexture::sWhiteImagep = NULL; +LLPointer LLViewerFetchedTexture::sDefaultImagep = NULL; +LLPointer LLViewerFetchedTexture::sSmokeImagep = NULL; +LLPointer LLViewerFetchedTexture::sFlatNormalImagep = NULL; +LLPointer LLViewerFetchedTexture::sDefaultIrradiancePBRp; +LLViewerMediaTexture::media_map_t LLViewerMediaTexture::sMediaMap; +LLTexturePipelineTester* LLViewerTextureManager::sTesterp = NULL; +F32 LLViewerFetchedTexture::sMaxVirtualSize = 8192.f*8192.f; + +const std::string sTesterName("TextureTester"); + +S32 LLViewerTexture::sImageCount = 0; +S32 LLViewerTexture::sRawCount = 0; +S32 LLViewerTexture::sAuxCount = 0; +LLFrameTimer LLViewerTexture::sEvaluationTimer; +F32 LLViewerTexture::sDesiredDiscardBias = 0.f; +F32 LLViewerTexture::sDesiredDiscardScale = 1.1f; +S32 LLViewerTexture::sMaxSculptRez = 128; //max sculpt image size +const S32 MAX_CACHED_RAW_IMAGE_AREA = 64 * 64; +const S32 MAX_CACHED_RAW_SCULPT_IMAGE_AREA = LLViewerTexture::sMaxSculptRez * LLViewerTexture::sMaxSculptRez; +const S32 MAX_CACHED_RAW_TERRAIN_IMAGE_AREA = 128 * 128; +const S32 DEFAULT_ICON_DIMENSIONS = 32; +const S32 DEFAULT_THUMBNAIL_DIMENSIONS = 256; +U32 LLViewerTexture::sMinLargeImageSize = 65536; //256 * 256. +U32 LLViewerTexture::sMaxSmallImageSize = MAX_CACHED_RAW_IMAGE_AREA; +bool LLViewerTexture::sFreezeImageUpdates = false; +F32 LLViewerTexture::sCurrentTime = 0.0f; + +constexpr F32 MIN_VRAM_BUDGET = 768.f; +F32 LLViewerTexture::sFreeVRAMMegabytes = MIN_VRAM_BUDGET; + +LLViewerTexture::EDebugTexels LLViewerTexture::sDebugTexelsMode = LLViewerTexture::DEBUG_TEXELS_OFF; + +const F64 log_2 = log(2.0); + +#if ADDRESS_SIZE == 32 +const U32 DESIRED_NORMAL_TEXTURE_SIZE = (U32)LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT / 2; +#else +const U32 DESIRED_NORMAL_TEXTURE_SIZE = (U32)LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT; +#endif + +//---------------------------------------------------------------------------------------------- +//namespace: LLViewerTextureAccess +//---------------------------------------------------------------------------------------------- + +LLLoadedCallbackEntry::LLLoadedCallbackEntry(loaded_callback_func cb, + S32 discard_level, + bool need_imageraw, // Needs image raw for the callback + void* userdata, + LLLoadedCallbackEntry::source_callback_list_t* src_callback_list, + LLViewerFetchedTexture* target, + bool pause) + : mCallback(cb), + mLastUsedDiscard(MAX_DISCARD_LEVEL+1), + mDesiredDiscard(discard_level), + mNeedsImageRaw(need_imageraw), + mUserData(userdata), + mSourceCallbackList(src_callback_list), + mPaused(pause) +{ + if(mSourceCallbackList) + { + mSourceCallbackList->insert(LLTextureKey(target->getID(), (ETexListType)target->getTextureListType())); + } +} + +LLLoadedCallbackEntry::~LLLoadedCallbackEntry() +{ +} + +void LLLoadedCallbackEntry::removeTexture(LLViewerFetchedTexture* tex) +{ + if (mSourceCallbackList && tex) + { + mSourceCallbackList->erase(LLTextureKey(tex->getID(), (ETexListType)tex->getTextureListType())); + } +} + +//static +void LLLoadedCallbackEntry::cleanUpCallbackList(LLLoadedCallbackEntry::source_callback_list_t* callback_list) +{ + //clear texture callbacks. + if(callback_list && !callback_list->empty()) + { + for(LLLoadedCallbackEntry::source_callback_list_t::iterator iter = callback_list->begin(); + iter != callback_list->end(); ++iter) + { + LLViewerFetchedTexture* tex = gTextureList.findImage(*iter); + if(tex) + { + tex->deleteCallbackEntry(callback_list); + } + } + callback_list->clear(); + } +} + +LLViewerMediaTexture* LLViewerTextureManager::createMediaTexture(const LLUUID &media_id, bool usemipmaps, LLImageGL* gl_image) +{ + return new LLViewerMediaTexture(media_id, usemipmaps, gl_image); +} + +void LLViewerTextureManager::findFetchedTextures(const LLUUID& id, std::vector &output) +{ + return gTextureList.findTexturesByID(id, output); +} + +void LLViewerTextureManager::findTextures(const LLUUID& id, std::vector &output) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + std::vector fetched_output; + gTextureList.findTexturesByID(id, fetched_output); + std::vector::iterator iter = fetched_output.begin(); + while (iter != fetched_output.end()) + { + output.push_back(*iter); + iter++; + } + + //search media texture list + if (output.empty()) + { + LLViewerTexture* tex; + tex = LLViewerTextureManager::findMediaTexture(id); + if (tex) + { + output.push_back(tex); + } + } + +} + +LLViewerFetchedTexture* LLViewerTextureManager::findFetchedTexture(const LLUUID& id, S32 tex_type) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + return gTextureList.findImage(id, (ETexListType)tex_type); +} + +LLViewerMediaTexture* LLViewerTextureManager::findMediaTexture(const LLUUID &media_id) +{ + return LLViewerMediaTexture::findMediaTexture(media_id); +} + +LLViewerMediaTexture* LLViewerTextureManager::getMediaTexture(const LLUUID& id, bool usemipmaps, LLImageGL* gl_image) +{ + LLViewerMediaTexture* tex = LLViewerMediaTexture::findMediaTexture(id); + if(!tex) + { + tex = LLViewerTextureManager::createMediaTexture(id, usemipmaps, gl_image); + } + + tex->initVirtualSize(); + + return tex; +} + +LLViewerFetchedTexture* LLViewerTextureManager::staticCastToFetchedTexture(LLTexture* tex, bool report_error) +{ + if(!tex) + { + return NULL; + } + + S8 type = tex->getType(); + if(type == LLViewerTexture::FETCHED_TEXTURE || type == LLViewerTexture::LOD_TEXTURE) + { + return static_cast(tex); + } + + if(report_error) + { + LL_ERRS() << "not a fetched texture type: " << type << LL_ENDL; + } + + return NULL; +} + +LLPointer LLViewerTextureManager::getLocalTexture(bool usemipmaps, bool generate_gl_tex) +{ + LLPointer tex = new LLViewerTexture(usemipmaps); + if(generate_gl_tex) + { + tex->generateGLTexture(); + tex->setCategory(LLGLTexture::LOCAL); + } + return tex; +} +LLPointer LLViewerTextureManager::getLocalTexture(const LLUUID& id, bool usemipmaps, bool generate_gl_tex) +{ + LLPointer tex = new LLViewerTexture(id, usemipmaps); + if(generate_gl_tex) + { + tex->generateGLTexture(); + tex->setCategory(LLGLTexture::LOCAL); + } + return tex; +} +LLPointer LLViewerTextureManager::getLocalTexture(const LLImageRaw* raw, bool usemipmaps) +{ + LLPointer tex = new LLViewerTexture(raw, usemipmaps); + tex->setCategory(LLGLTexture::LOCAL); + return tex; +} +LLPointer LLViewerTextureManager::getLocalTexture(const U32 width, const U32 height, const U8 components, bool usemipmaps, bool generate_gl_tex) +{ + LLPointer tex = new LLViewerTexture(width, height, components, usemipmaps); + if(generate_gl_tex) + { + tex->generateGLTexture(); + tex->setCategory(LLGLTexture::LOCAL); + } + return tex; +} + +LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTexture(const LLImageRaw* raw, FTType type, bool usemipmaps) +{ + LLImageDataSharedLock lock(raw); + LLViewerFetchedTexture* ret = new LLViewerFetchedTexture(raw, type, usemipmaps); + gTextureList.addImage(ret, TEX_LIST_STANDARD); + return ret; +} + +LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTexture( + const LLUUID &image_id, + FTType f_type, + bool usemipmaps, + LLViewerTexture::EBoostLevel boost_priority, + S8 texture_type, + LLGLint internal_format, + LLGLenum primary_format, + LLHost request_from_host) +{ + return gTextureList.getImage(image_id, f_type, usemipmaps, boost_priority, texture_type, internal_format, primary_format, request_from_host); +} + +LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTextureFromFile( + const std::string& filename, + FTType f_type, + bool usemipmaps, + LLViewerTexture::EBoostLevel boost_priority, + S8 texture_type, + LLGLint internal_format, + LLGLenum primary_format, + const LLUUID& force_id) +{ + return gTextureList.getImageFromFile(filename, f_type, usemipmaps, boost_priority, texture_type, internal_format, primary_format, force_id); +} + +//static +LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTextureFromUrl(const std::string& url, + FTType f_type, + bool usemipmaps, + LLViewerTexture::EBoostLevel boost_priority, + S8 texture_type, + LLGLint internal_format, + LLGLenum primary_format, + const LLUUID& force_id + ) +{ + return gTextureList.getImageFromUrl(url, f_type, usemipmaps, boost_priority, texture_type, internal_format, primary_format, force_id); +} + +LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTextureFromHost(const LLUUID& image_id, FTType f_type, LLHost host) +{ + return gTextureList.getImageFromHost(image_id, f_type, host); +} + +// Create a bridge to the viewer texture manager. +class LLViewerTextureManagerBridge : public LLTextureManagerBridge +{ + /*virtual*/ LLPointer getLocalTexture(bool usemipmaps = true, bool generate_gl_tex = true) + { + return LLViewerTextureManager::getLocalTexture(usemipmaps, generate_gl_tex); + } + + /*virtual*/ LLPointer getLocalTexture(const U32 width, const U32 height, const U8 components, bool usemipmaps, bool generate_gl_tex = true) + { + return LLViewerTextureManager::getLocalTexture(width, height, components, usemipmaps, generate_gl_tex); + } + + /*virtual*/ LLGLTexture* getFetchedTexture(const LLUUID &image_id) + { + return LLViewerTextureManager::getFetchedTexture(image_id); + } +}; + + +void LLViewerTextureManager::init() +{ + { + LLPointer raw = new LLImageRaw(1,1,3); + raw->clear(0x77, 0x77, 0x77, 0xFF); + LLViewerTexture::sNullImagep = LLViewerTextureManager::getLocalTexture(raw.get(), true); + } + + const S32 dim = 128; + LLPointer image_raw = new LLImageRaw(dim,dim,3); + U8* data = image_raw->getData(); + + memset(data, 0, dim * dim * 3); + LLViewerTexture::sBlackImagep = LLViewerTextureManager::getLocalTexture(image_raw.get(), true); + +#if 1 + LLPointer imagep = LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT); + LLViewerFetchedTexture::sDefaultImagep = imagep; + + for (S32 i = 0; i=(dim-border) || j>=(dim-border)) + { + *data++ = 0xff; + *data++ = 0xff; + *data++ = 0xff; + } + else +#endif + { + *data++ = 0x7f; + *data++ = 0x7f; + *data++ = 0x7f; + } + } + } + imagep->createGLTexture(0, image_raw); + //cache the raw image + imagep->setCachedRawImage(0, image_raw); + image_raw = NULL; +#else + LLViewerFetchedTexture::sDefaultImagep = LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT, true, LLGLTexture::BOOST_UI); +#endif + LLViewerFetchedTexture::sDefaultImagep->dontDiscard(); + LLViewerFetchedTexture::sDefaultImagep->setCategory(LLGLTexture::OTHER); + + image_raw = new LLImageRaw(32,32,3); + data = image_raw->getData(); + + for (S32 i = 0; i < (32*32*3); i+=3) + { + S32 x = (i % (32*3)) / (3*16); + S32 y = i / (32*3*16); + U8 color = ((x + y) % 2) * 255; + data[i] = color; + data[i+1] = color; + data[i+2] = color; + } + + LLViewerTexture::sCheckerBoardImagep = LLViewerTextureManager::getLocalTexture(image_raw.get(), true); + + LLViewerTexture::initClass(); + + // Create a texture manager bridge. + gTextureManagerBridgep = new LLViewerTextureManagerBridge; + + if (LLMetricPerformanceTesterBasic::isMetricLogRequested(sTesterName) && !LLMetricPerformanceTesterBasic::getTester(sTesterName)) + { + sTesterp = new LLTexturePipelineTester(); + if (!sTesterp->isValid()) + { + delete sTesterp; + sTesterp = NULL; + } + } +} + +void LLViewerTextureManager::cleanup() +{ + stop_glerror(); + + delete gTextureManagerBridgep; + LLImageGL::sDefaultGLTexture = NULL; + LLViewerTexture::sNullImagep = NULL; + LLViewerTexture::sBlackImagep = NULL; + LLViewerTexture::sCheckerBoardImagep = NULL; + LLViewerFetchedTexture::sDefaultImagep = NULL; + LLViewerFetchedTexture::sSmokeImagep = NULL; + LLViewerFetchedTexture::sMissingAssetImagep = NULL; + LLTexUnit::sWhiteTexture = 0; + LLViewerFetchedTexture::sWhiteImagep = NULL; + + LLViewerFetchedTexture::sFlatNormalImagep = NULL; + LLViewerFetchedTexture::sDefaultIrradiancePBRp = NULL; + + LLViewerMediaTexture::cleanUpClass(); +} + +//---------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------- +//start of LLViewerTexture +//---------------------------------------------------------------------------------------------- +// static +void LLViewerTexture::initClass() +{ + LLImageGL::sDefaultGLTexture = LLViewerFetchedTexture::sDefaultImagep->getGLTexture(); +} + +// non-const (used externally +F32 texmem_lower_bound_scale = 0.85f; +F32 texmem_middle_bound_scale = 0.925f; + +//static +void LLViewerTexture::updateClass() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + sCurrentTime = gFrameTimeSeconds; + + LLTexturePipelineTester* tester = (LLTexturePipelineTester*)LLMetricPerformanceTesterBasic::getTester(sTesterName); + if (tester) + { + tester->update(); + } + + LLViewerMediaTexture::updateClass(); + + static LLCachedControl max_vram_budget(gSavedSettings, "RenderMaxVRAMBudget", 0); + + F64 texture_bytes_alloc = LLImageGL::getTextureBytesAllocated() / 1024.0 / 512.0; + F64 vertex_bytes_alloc = LLVertexBuffer::getBytesAllocated() / 1024.0 / 512.0; + F64 render_bytes_alloc = LLRenderTarget::sBytesAllocated / 1024.0 / 512.0; + + // get an estimate of how much video memory we're using + // NOTE: our metrics miss about half the vram we use, so this biases high but turns out to typically be within 5% of the real number + F32 used = (F32)ll_round(texture_bytes_alloc + vertex_bytes_alloc + render_bytes_alloc); + + F32 budget = max_vram_budget == 0 ? gGLManager.mVRAM : max_vram_budget; + + // try to leave half a GB for everyone else, but keep at least 768MB for ourselves + F32 target = llmax(budget - 512.f, MIN_VRAM_BUDGET); + sFreeVRAMMegabytes = target - used; + + F32 over_pct = llmax((used-target) / target, 0.f); + sDesiredDiscardBias = llmax(sDesiredDiscardBias, 1.f + over_pct); + + if (sDesiredDiscardBias > 1.f) + { + sDesiredDiscardBias -= gFrameIntervalSeconds * 0.01; + } + + LLViewerTexture::sFreezeImageUpdates = false; // sDesiredDiscardBias > (desired_discard_bias_max - 1.0f); +} + +//end of static functions +//------------------------------------------------------------------------------------------- +const U32 LLViewerTexture::sCurrentFileVersion = 1; + +LLViewerTexture::LLViewerTexture(bool usemipmaps) : + LLGLTexture(usemipmaps) +{ + init(true); + + mID.generate(); + sImageCount++; +} + +LLViewerTexture::LLViewerTexture(const LLUUID& id, bool usemipmaps) : + LLGLTexture(usemipmaps), + mID(id) +{ + init(true); + + sImageCount++; +} + +LLViewerTexture::LLViewerTexture(const U32 width, const U32 height, const U8 components, bool usemipmaps) : + LLGLTexture(width, height, components, usemipmaps) +{ + init(true); + + mID.generate(); + sImageCount++; +} + +LLViewerTexture::LLViewerTexture(const LLImageRaw* raw, bool usemipmaps) : + LLGLTexture(raw, usemipmaps) +{ + init(true); + + mID.generate(); + sImageCount++; +} + +LLViewerTexture::~LLViewerTexture() +{ + // LL_DEBUGS("Avatar") << mID << LL_ENDL; + cleanup(); + sImageCount--; +} + +// virtual +void LLViewerTexture::init(bool firstinit) +{ + mMaxVirtualSize = 0.f; + mMaxVirtualSizeResetInterval = 1; + mMaxVirtualSizeResetCounter = mMaxVirtualSizeResetInterval; + mParcelMedia = NULL; + + memset(&mNumVolumes, 0, sizeof(U32)* LLRender::NUM_VOLUME_TEXTURE_CHANNELS); + mFaceList[LLRender::DIFFUSE_MAP].clear(); + mFaceList[LLRender::NORMAL_MAP].clear(); + mFaceList[LLRender::SPECULAR_MAP].clear(); + mNumFaces[LLRender::DIFFUSE_MAP] = + mNumFaces[LLRender::NORMAL_MAP] = + mNumFaces[LLRender::SPECULAR_MAP] = 0; + + mVolumeList[LLRender::LIGHT_TEX].clear(); + mVolumeList[LLRender::SCULPT_TEX].clear(); + + mMainQueue = LL::WorkQueue::getInstance("mainloop"); + mImageQueue = LL::WorkQueue::getInstance("LLImageGL"); +} + +//virtual +S8 LLViewerTexture::getType() const +{ + return LLViewerTexture::LOCAL_TEXTURE; +} + +void LLViewerTexture::cleanup() +{ + if (LLAppViewer::getTextureFetch()) + { + LLAppViewer::getTextureFetch()->updateRequestPriority(mID, 0.f); + } + + mFaceList[LLRender::DIFFUSE_MAP].clear(); + mFaceList[LLRender::NORMAL_MAP].clear(); + mFaceList[LLRender::SPECULAR_MAP].clear(); + mVolumeList[LLRender::LIGHT_TEX].clear(); + mVolumeList[LLRender::SCULPT_TEX].clear(); +} + +// virtual +void LLViewerTexture::dump() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + LLGLTexture::dump(); + + LL_INFOS() << "LLViewerTexture" + << " mID " << mID + << LL_ENDL; +} + +void LLViewerTexture::setBoostLevel(S32 level) +{ + if(mBoostLevel != level) + { + mBoostLevel = level; + if(mBoostLevel != LLViewerTexture::BOOST_NONE && + mBoostLevel != LLViewerTexture::BOOST_SELECTED && + mBoostLevel != LLViewerTexture::BOOST_ICON && + mBoostLevel != LLViewerTexture::BOOST_THUMBNAIL) + { + setNoDelete(); + } + } + + // strongly encourage anything boosted to load at full res + if (mBoostLevel >= LLViewerTexture::BOOST_HIGH) + { + mMaxVirtualSize = 2048.f * 2048.f; + } +} + +bool LLViewerTexture::isActiveFetching() +{ + return false; +} + +bool LLViewerTexture::bindDebugImage(const S32 stage) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if (stage < 0) return false; + + bool res = true; + if (LLViewerTexture::sCheckerBoardImagep.notNull() && (this != LLViewerTexture::sCheckerBoardImagep.get())) + { + res = gGL.getTexUnit(stage)->bind(LLViewerTexture::sCheckerBoardImagep); + } + + if(!res) + { + return bindDefaultImage(stage); + } + + return res; +} + +bool LLViewerTexture::bindDefaultImage(S32 stage) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if (stage < 0) return false; + + bool res = true; + if (LLViewerFetchedTexture::sDefaultImagep.notNull() && (this != LLViewerFetchedTexture::sDefaultImagep.get())) + { + // use default if we've got it + res = gGL.getTexUnit(stage)->bind(LLViewerFetchedTexture::sDefaultImagep); + } + if (!res && LLViewerTexture::sNullImagep.notNull() && (this != LLViewerTexture::sNullImagep)) + { + res = gGL.getTexUnit(stage)->bind(LLViewerTexture::sNullImagep); + } + if (!res) + { + LL_WARNS() << "LLViewerTexture::bindDefaultImage failed." << LL_ENDL; + } + stop_glerror(); + + //check if there is cached raw image and switch to it if possible + switchToCachedImage(); + + LLTexturePipelineTester* tester = (LLTexturePipelineTester*)LLMetricPerformanceTesterBasic::getTester(sTesterName); + if (tester) + { + tester->updateGrayTextureBinding(); + } + return res; +} + +//virtual +bool LLViewerTexture::isMissingAsset()const +{ + return false; +} + +//virtual +void LLViewerTexture::forceImmediateUpdate() +{ +} + +void LLViewerTexture::addTextureStats(F32 virtual_size, bool needs_gltexture) const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if(needs_gltexture) + { + mNeedsGLTexture = true; + } + + virtual_size = llmin(virtual_size, LLViewerFetchedTexture::sMaxVirtualSize); + + if (virtual_size > mMaxVirtualSize) + { + mMaxVirtualSize = virtual_size; + } +} + +void LLViewerTexture::resetTextureStats() +{ + mMaxVirtualSize = 0.0f; + mMaxVirtualSizeResetCounter = 0; +} + +//virtual +F32 LLViewerTexture::getMaxVirtualSize() +{ + return mMaxVirtualSize; +} + +//virtual +void LLViewerTexture::setKnownDrawSize(S32 width, S32 height) +{ + //nothing here. +} + +//virtual +void LLViewerTexture::addFace(U32 ch, LLFace* facep) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); + + if(mNumFaces[ch] >= mFaceList[ch].size()) + { + mFaceList[ch].resize(2 * mNumFaces[ch] + 1); + } + mFaceList[ch][mNumFaces[ch]] = facep; + facep->setIndexInTex(ch, mNumFaces[ch]); + mNumFaces[ch]++; + mLastFaceListUpdateTimer.reset(); +} + +//virtual +void LLViewerTexture::removeFace(U32 ch, LLFace* facep) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); + + if(mNumFaces[ch] > 1) + { + S32 index = facep->getIndexInTex(ch); + llassert(index < mFaceList[ch].size()); + llassert(index < mNumFaces[ch]); + mFaceList[ch][index] = mFaceList[ch][--mNumFaces[ch]]; + mFaceList[ch][index]->setIndexInTex(ch, index); + } + else + { + mFaceList[ch].clear(); + mNumFaces[ch] = 0; + } + mLastFaceListUpdateTimer.reset(); +} + +S32 LLViewerTexture::getTotalNumFaces() const +{ + S32 ret = 0; + + for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) + { + ret += mNumFaces[i]; + } + + return ret; +} + +S32 LLViewerTexture::getNumFaces(U32 ch) const +{ + llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); + return mNumFaces[ch]; +} + + +//virtual +void LLViewerTexture::addVolume(U32 ch, LLVOVolume* volumep) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if (mNumVolumes[ch] >= mVolumeList[ch].size()) + { + mVolumeList[ch].resize(2 * mNumVolumes[ch] + 1); + } + mVolumeList[ch][mNumVolumes[ch]] = volumep; + volumep->setIndexInTex(ch, mNumVolumes[ch]); + mNumVolumes[ch]++; + mLastVolumeListUpdateTimer.reset(); +} + +//virtual +void LLViewerTexture::removeVolume(U32 ch, LLVOVolume* volumep) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if (mNumVolumes[ch] > 1) + { + S32 index = volumep->getIndexInTex(ch); + llassert(index < mVolumeList[ch].size()); + llassert(index < mNumVolumes[ch]); + mVolumeList[ch][index] = mVolumeList[ch][--mNumVolumes[ch]]; + mVolumeList[ch][index]->setIndexInTex(ch, index); + } + else + { + mVolumeList[ch].clear(); + mNumVolumes[ch] = 0; + } + mLastVolumeListUpdateTimer.reset(); +} + +S32 LLViewerTexture::getNumVolumes(U32 ch) const +{ + return mNumVolumes[ch]; +} + +void LLViewerTexture::reorganizeFaceList() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + static const F32 MAX_WAIT_TIME = 20.f; // seconds + static const U32 MAX_EXTRA_BUFFER_SIZE = 4; + + if(mLastFaceListUpdateTimer.getElapsedTimeF32() < MAX_WAIT_TIME) + { + return; + } + + for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) + { + if(mNumFaces[i] + MAX_EXTRA_BUFFER_SIZE > mFaceList[i].size()) + { + return; + } + + mFaceList[i].erase(mFaceList[i].begin() + mNumFaces[i], mFaceList[i].end()); + } + + mLastFaceListUpdateTimer.reset(); +} + +void LLViewerTexture::reorganizeVolumeList() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + static const F32 MAX_WAIT_TIME = 20.f; // seconds + static const U32 MAX_EXTRA_BUFFER_SIZE = 4; + + + for (U32 i = 0; i < LLRender::NUM_VOLUME_TEXTURE_CHANNELS; ++i) + { + if (mNumVolumes[i] + MAX_EXTRA_BUFFER_SIZE > mVolumeList[i].size()) + { + return; + } + } + + if(mLastVolumeListUpdateTimer.getElapsedTimeF32() < MAX_WAIT_TIME) + { + return; + } + + mLastVolumeListUpdateTimer.reset(); + for (U32 i = 0; i < LLRender::NUM_VOLUME_TEXTURE_CHANNELS; ++i) + { + mVolumeList[i].erase(mVolumeList[i].begin() + mNumVolumes[i], mVolumeList[i].end()); + } +} + +//virtual +void LLViewerTexture::switchToCachedImage() +{ + //nothing here. +} + +//virtual +void LLViewerTexture::setCachedRawImage(S32 discard_level, LLImageRaw* imageraw) +{ + //nothing here. +} + +bool LLViewerTexture::isLargeImage() +{ + return (S32)mTexelsPerImage > LLViewerTexture::sMinLargeImageSize; +} + +//virtual +void LLViewerTexture::updateBindStatsForTester() +{ + LLTexturePipelineTester* tester = (LLTexturePipelineTester*)LLMetricPerformanceTesterBasic::getTester(sTesterName); + if (tester) + { + tester->updateTextureBindingStats(this); + } +} + +//---------------------------------------------------------------------------------------------- +//end of LLViewerTexture +//---------------------------------------------------------------------------------------------- + +const std::string& fttype_to_string(const FTType& fttype) +{ + static const std::string ftt_unknown("FTT_UNKNOWN"); + static const std::string ftt_default("FTT_DEFAULT"); + static const std::string ftt_server_bake("FTT_SERVER_BAKE"); + static const std::string ftt_host_bake("FTT_HOST_BAKE"); + static const std::string ftt_map_tile("FTT_MAP_TILE"); + static const std::string ftt_local_file("FTT_LOCAL_FILE"); + static const std::string ftt_error("FTT_ERROR"); + switch(fttype) + { + case FTT_UNKNOWN: return ftt_unknown; break; + case FTT_DEFAULT: return ftt_default; break; + case FTT_SERVER_BAKE: return ftt_server_bake; break; + case FTT_HOST_BAKE: return ftt_host_bake; break; + case FTT_MAP_TILE: return ftt_map_tile; break; + case FTT_LOCAL_FILE: return ftt_local_file; break; + } + return ftt_error; +} + +//---------------------------------------------------------------------------------------------- +//start of LLViewerFetchedTexture +//---------------------------------------------------------------------------------------------- + +//static +LLViewerFetchedTexture* LLViewerFetchedTexture::getSmokeImage() +{ + if (sSmokeImagep.isNull()) + { + sSmokeImagep = LLViewerTextureManager::getFetchedTexture(IMG_SMOKE); + } + + sSmokeImagep->addTextureStats(1024.f * 1024.f); + + return sSmokeImagep; +} + +LLViewerFetchedTexture::LLViewerFetchedTexture(const LLUUID& id, FTType f_type, const LLHost& host, bool usemipmaps) + : LLViewerTexture(id, usemipmaps), + mTargetHost(host) +{ + init(true); + mFTType = f_type; + if (mFTType == FTT_HOST_BAKE) + { + LL_WARNS() << "Unsupported fetch type " << mFTType << LL_ENDL; + } + generateGLTexture(); +} + +LLViewerFetchedTexture::LLViewerFetchedTexture(const LLImageRaw* raw, FTType f_type, bool usemipmaps) + : LLViewerTexture(raw, usemipmaps) +{ + init(true); + mFTType = f_type; +} + +LLViewerFetchedTexture::LLViewerFetchedTexture(const std::string& url, FTType f_type, const LLUUID& id, bool usemipmaps) + : LLViewerTexture(id, usemipmaps), + mUrl(url) +{ + init(true); + mFTType = f_type; + generateGLTexture(); +} + +void LLViewerFetchedTexture::init(bool firstinit) +{ + mOrigWidth = 0; + mOrigHeight = 0; + mHasAux = false; + mNeedsAux = false; + mRequestedDiscardLevel = -1; + mRequestedDownloadPriority = 0.f; + mFullyLoaded = false; + mCanUseHTTP = true; + mDesiredDiscardLevel = MAX_DISCARD_LEVEL + 1; + mMinDesiredDiscardLevel = MAX_DISCARD_LEVEL + 1; + + mDecodingAux = false; + + mKnownDrawWidth = 0; + mKnownDrawHeight = 0; + mKnownDrawSizeChanged = false; + + if (firstinit) + { + mInImageList = 0; + } + + // Only set mIsMissingAsset true when we know for certain that the database + // does not contain this image. + mIsMissingAsset = false; + + mLoadedCallbackDesiredDiscardLevel = S8_MAX; + mPauseLoadedCallBacks = false; + + mNeedsCreateTexture = false; + + mIsRawImageValid = false; + mRawDiscardLevel = INVALID_DISCARD_LEVEL; + mMinDiscardLevel = 0; + + mHasFetcher = false; + mIsFetching = false; + mFetchState = 0; + mFetchPriority = 0; + mDownloadProgress = 0.f; + mFetchDeltaTime = 999999.f; + mRequestDeltaTime = 0.f; + mForSculpt = false; + mIsFetched = false; + mInFastCacheList = false; + + mCachedRawImage = NULL; + mCachedRawDiscardLevel = -1; + mCachedRawImageReady = false; + + mSavedRawImage = NULL; + mForceToSaveRawImage = false; + mSaveRawImage = false; + mSavedRawDiscardLevel = -1; + mDesiredSavedRawDiscardLevel = -1; + mLastReferencedSavedRawImageTime = 0.0f; + mKeptSavedRawImageTime = 0.f; + mLastCallBackActiveTime = 0.f; + mForceCallbackFetch = false; + mInDebug = false; + mUnremovable = false; + + mFTType = FTT_UNKNOWN; +} + +LLViewerFetchedTexture::~LLViewerFetchedTexture() +{ + assert_main_thread(); + //*NOTE getTextureFetch can return NULL when Viewer is shutting down. + // This is due to LLWearableList is singleton and is destroyed after + // LLAppViewer::cleanup() was called. (see ticket EXT-177) + if (mHasFetcher && LLAppViewer::getTextureFetch()) + { + LLAppViewer::getTextureFetch()->deleteRequest(getID(), true); + } + cleanup(); +} + +//virtual +S8 LLViewerFetchedTexture::getType() const +{ + return LLViewerTexture::FETCHED_TEXTURE; +} + +FTType LLViewerFetchedTexture::getFTType() const +{ + return mFTType; +} + +void LLViewerFetchedTexture::cleanup() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); + iter != mLoadedCallbackList.end(); ) + { + LLLoadedCallbackEntry *entryp = *iter++; + // We never finished loading the image. Indicate failure. + // Note: this allows mLoadedCallbackUserData to be cleaned up. + entryp->mCallback( false, this, NULL, NULL, 0, true, entryp->mUserData ); + entryp->removeTexture(this); + delete entryp; + } + mLoadedCallbackList.clear(); + mNeedsAux = false; + + // Clean up image data + destroyRawImage(); + mCachedRawImage = NULL; + mCachedRawDiscardLevel = -1; + mCachedRawImageReady = false; + mSavedRawImage = NULL; + mSavedRawDiscardLevel = -1; +} + +//access the fast cache +void LLViewerFetchedTexture::loadFromFastCache() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if(!mInFastCacheList) + { + return; //no need to access the fast cache. + } + mInFastCacheList = false; + + add(LLTextureFetch::sCacheAttempt, 1.0); + + LLTimer fastCacheTimer; + mRawImage = LLAppViewer::getTextureCache()->readFromFastCache(getID(), mRawDiscardLevel); + if(mRawImage.notNull()) + { + F32 cachReadTime = fastCacheTimer.getElapsedTimeF32(); + + add(LLTextureFetch::sCacheHit, 1.0); + record(LLTextureFetch::sCacheHitRate, LLUnits::Ratio::fromValue(1)); + sample(LLTextureFetch::sCacheReadLatency, cachReadTime); + + mFullWidth = mRawImage->getWidth() << mRawDiscardLevel; + mFullHeight = mRawImage->getHeight() << mRawDiscardLevel; + setTexelsPerImage(); + + if(mFullWidth > MAX_IMAGE_SIZE || mFullHeight > MAX_IMAGE_SIZE) + { + //discard all oversized textures. + destroyRawImage(); + LL_WARNS() << "oversized, setting as missing" << LL_ENDL; + setIsMissingAsset(); + mRawDiscardLevel = INVALID_DISCARD_LEVEL; + } + else + { + if (mBoostLevel == LLGLTexture::BOOST_ICON) + { + // Shouldn't do anything usefull since texures in fast cache are 16x16, + // it is here in case fast cache changes. + S32 expected_width = mKnownDrawWidth > 0 ? mKnownDrawWidth : DEFAULT_ICON_DIMENSIONS; + S32 expected_height = mKnownDrawHeight > 0 ? mKnownDrawHeight : DEFAULT_ICON_DIMENSIONS; + if (mRawImage && (mRawImage->getWidth() > expected_width || mRawImage->getHeight() > expected_height)) + { + // scale oversized icon, no need to give more work to gl + mRawImage->scale(expected_width, expected_height); + } + } + + if (mBoostLevel == LLGLTexture::BOOST_THUMBNAIL) + { + S32 expected_width = mKnownDrawWidth > 0 ? mKnownDrawWidth : DEFAULT_THUMBNAIL_DIMENSIONS; + S32 expected_height = mKnownDrawHeight > 0 ? mKnownDrawHeight : DEFAULT_THUMBNAIL_DIMENSIONS; + if (mRawImage && (mRawImage->getWidth() > expected_width || mRawImage->getHeight() > expected_height)) + { + // scale oversized icon, no need to give more work to gl + mRawImage->scale(expected_width, expected_height); + } + } + + mRequestedDiscardLevel = mDesiredDiscardLevel + 1; + mIsRawImageValid = true; + addToCreateTexture(); + } + } + else + { + record(LLTextureFetch::sCacheHitRate, LLUnits::Ratio::fromValue(0)); + } +} + +void LLViewerFetchedTexture::setForSculpt() +{ + static const S32 MAX_INTERVAL = 8; //frames + + mForSculpt = true; + if(isForSculptOnly() && hasGLTexture() && !getBoundRecently()) + { + destroyGLTexture(); //sculpt image does not need gl texture. + mTextureState = ACTIVE; + } + checkCachedRawSculptImage(); + setMaxVirtualSizeResetInterval(MAX_INTERVAL); +} + +bool LLViewerFetchedTexture::isForSculptOnly() const +{ + return mForSculpt && !mNeedsGLTexture; +} + +bool LLViewerFetchedTexture::isDeleted() +{ + return mTextureState == DELETED; +} + +bool LLViewerFetchedTexture::isInactive() +{ + return mTextureState == INACTIVE; +} + +bool LLViewerFetchedTexture::isDeletionCandidate() +{ + return mTextureState == DELETION_CANDIDATE; +} + +void LLViewerFetchedTexture::setDeletionCandidate() +{ + if(mGLTexturep.notNull() && mGLTexturep->getTexName() && (mTextureState == INACTIVE)) + { + mTextureState = DELETION_CANDIDATE; + } +} + +//set the texture inactive +void LLViewerFetchedTexture::setInactive() +{ + if(mTextureState == ACTIVE && mGLTexturep.notNull() && mGLTexturep->getTexName() && !mGLTexturep->getBoundRecently()) + { + mTextureState = INACTIVE; + } +} + +bool LLViewerFetchedTexture::isFullyLoaded() const +{ + // Unfortunately, the boolean "mFullyLoaded" is never updated correctly so we use that logic + // to check if the texture is there and completely downloaded + return (mFullWidth != 0) && (mFullHeight != 0) && !mIsFetching && !mHasFetcher; +} + + +// virtual +void LLViewerFetchedTexture::dump() +{ + LLViewerTexture::dump(); + + LL_INFOS() << "Dump : " << mID + << ", mIsMissingAsset = " << (S32)mIsMissingAsset + << ", mFullWidth = " << (S32)mFullWidth + << ", mFullHeight = " << (S32)mFullHeight + << ", mOrigWidth = " << (S32)mOrigWidth + << ", mOrigHeight = " << (S32)mOrigHeight + << LL_ENDL; + LL_INFOS() << " : " + << " mFullyLoaded = " << (S32)mFullyLoaded + << ", mFetchState = " << (S32)mFetchState + << ", mFetchPriority = " << (S32)mFetchPriority + << ", mDownloadProgress = " << (F32)mDownloadProgress + << LL_ENDL; + LL_INFOS() << " : " + << " mHasFetcher = " << (S32)mHasFetcher + << ", mIsFetching = " << (S32)mIsFetching + << ", mIsFetched = " << (S32)mIsFetched + << ", mBoostLevel = " << (S32)mBoostLevel + << LL_ENDL; +} + +/////////////////////////////////////////////////////////////////////////////// +// ONLY called from LLViewerFetchedTextureList +void LLViewerFetchedTexture::destroyTexture() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + + if (mNeedsCreateTexture)//return if in the process of generating a new texture. + { + return; + } + + //LL_DEBUGS("Avatar") << mID << LL_ENDL; + destroyGLTexture(); + mFullyLoaded = false; +} + +void LLViewerFetchedTexture::addToCreateTexture() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + bool force_update = false; + if (getComponents() != mRawImage->getComponents()) + { + // We've changed the number of components, so we need to move any + // objects using this pool to a different pool. + mComponents = mRawImage->getComponents(); + mGLTexturep->setComponents(mComponents); + force_update = true; + + for (U32 j = 0; j < LLRender::NUM_TEXTURE_CHANNELS; ++j) + { + llassert(mNumFaces[j] <= mFaceList[j].size()); + + for(U32 i = 0; i < mNumFaces[j]; i++) + { + mFaceList[j][i]->dirtyTexture(); + } + } + + //discard the cached raw image and the saved raw image + mCachedRawImageReady = false; + mCachedRawDiscardLevel = -1; + mCachedRawImage = NULL; + mSavedRawDiscardLevel = -1; + mSavedRawImage = NULL; + } + + if(isForSculptOnly()) + { + //just update some variables, not to create a real GL texture. + createGLTexture(mRawDiscardLevel, mRawImage, 0, false); + mNeedsCreateTexture = false; + destroyRawImage(); + } + else if(!force_update && getDiscardLevel() > -1 && getDiscardLevel() <= mRawDiscardLevel) + { + mNeedsCreateTexture = false; + destroyRawImage(); + } + else + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; +#if 1 + // + //if mRequestedDiscardLevel > mDesiredDiscardLevel, we assume the required image res keep going up, + //so do not scale down the over qualified image. + //Note: scaling down image is expensensive. Do it only when very necessary. + // + if(mRequestedDiscardLevel <= mDesiredDiscardLevel && !mForceToSaveRawImage) + { + S32 w = mFullWidth >> mRawDiscardLevel; + S32 h = mFullHeight >> mRawDiscardLevel; + + //if big image, do not load extra data + //scale it down to size >= LLViewerTexture::sMinLargeImageSize + if(w * h > LLViewerTexture::sMinLargeImageSize) + { + S32 d_level = llmin(mRequestedDiscardLevel, (S32)mDesiredDiscardLevel) - mRawDiscardLevel; + + if(d_level > 0) + { + S32 i = 0; + while((d_level > 0) && ((w >> i) * (h >> i) > LLViewerTexture::sMinLargeImageSize)) + { + i++; + d_level--; + } + if(i > 0) + { + mRawDiscardLevel += i; + if(mRawDiscardLevel >= getDiscardLevel() && getDiscardLevel() > 0) + { + mNeedsCreateTexture = false; + destroyRawImage(); + return; + } + + { + //make a duplicate in case somebody else is using this raw image + mRawImage = mRawImage->scaled(w >> i, h >> i); + } + } + } + } + } +#endif + scheduleCreateTexture(); + } + return; +} + +// ONLY called from LLViewerTextureList +bool LLViewerFetchedTexture::preCreateTexture(S32 usename/*= 0*/) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; +#if LL_IMAGEGL_THREAD_CHECK + mGLTexturep->checkActiveThread(); +#endif + + if (!mNeedsCreateTexture) + { + destroyRawImage(); + return false; + } + mNeedsCreateTexture = false; + + if (mRawImage.isNull()) + { + LL_ERRS() << "LLViewerTexture trying to create texture with no Raw Image" << LL_ENDL; + } + if (mRawImage->isBufferInvalid()) + { + LL_WARNS() << "Can't create a texture: invalid image data" << LL_ENDL; + destroyRawImage(); + return false; + } + // LL_INFOS() << llformat("IMAGE Creating (%d) [%d x %d] Bytes: %d ", + // mRawDiscardLevel, + // mRawImage->getWidth(), mRawImage->getHeight(),mRawImage->getDataSize()) + // << mID.getString() << LL_ENDL; + bool res = true; + + // store original size only for locally-sourced images + if (mUrl.compare(0, 7, "file://") == 0) + { + mOrigWidth = mRawImage->getWidth(); + mOrigHeight = mRawImage->getHeight(); + + // This is only safe because it's a local image and fetcher doesn't use raw data + // from local images, but this might become unsafe in case of changes to fetcher + if (mBoostLevel == BOOST_PREVIEW) + { + mRawImage->biasedScaleToPowerOfTwo(1024); + } + else + { // leave black border, do not scale image content + mRawImage->expandToPowerOfTwo(MAX_IMAGE_SIZE, false); + } + + mFullWidth = mRawImage->getWidth(); + mFullHeight = mRawImage->getHeight(); + setTexelsPerImage(); + } + else + { + mOrigWidth = mFullWidth; + mOrigHeight = mFullHeight; + } + + bool size_okay = true; + + S32 discard_level = mRawDiscardLevel; + if (mRawDiscardLevel < 0) + { + LL_DEBUGS() << "Negative raw discard level when creating image: " << mRawDiscardLevel << LL_ENDL; + discard_level = 0; + } + + U32 raw_width = mRawImage->getWidth() << discard_level; + U32 raw_height = mRawImage->getHeight() << discard_level; + + if (raw_width > MAX_IMAGE_SIZE || raw_height > MAX_IMAGE_SIZE) + { + LL_INFOS() << "Width or height is greater than " << MAX_IMAGE_SIZE << ": (" << raw_width << "," << raw_height << ")" << LL_ENDL; + size_okay = false; + } + + if (!LLImageGL::checkSize(mRawImage->getWidth(), mRawImage->getHeight())) + { + // A non power-of-two image was uploaded (through a non standard client) + LL_INFOS() << "Non power of two width or height: (" << mRawImage->getWidth() << "," << mRawImage->getHeight() << ")" << LL_ENDL; + size_okay = false; + } + + if (!size_okay) + { + // An inappropriately-sized image was uploaded (through a non standard client) + // We treat these images as missing assets which causes them to + // be renderd as 'missing image' and to stop requesting data + LL_WARNS() << "!size_ok, setting as missing" << LL_ENDL; + setIsMissingAsset(); + destroyRawImage(); + return false; + } + + if (mGLTexturep->getHasExplicitFormat()) + { + LLGLenum format = mGLTexturep->getPrimaryFormat(); + S8 components = mRawImage->getComponents(); + if ((format == GL_RGBA && components < 4) + || (format == GL_RGB && components < 3)) + { + LL_WARNS() << "Can't create a texture " << mID << ": invalid image format " << std::hex << format << " vs components " << (U32)components << LL_ENDL; + // Was expecting specific format but raw texture has insufficient components for + // such format, using such texture will result in crash or will display wrongly + // if we change format. Texture might be corrupted server side, so just set as + // missing and clear cashed texture (do not cause reload loop, will retry&recover + // during new session) + setIsMissingAsset(); + destroyRawImage(); + LLAppViewer::getTextureCache()->removeFromCache(mID); + return false; + } + } + + return res; +} + +bool LLViewerFetchedTexture::createTexture(S32 usename/*= 0*/) +{ + if (!mNeedsCreateTexture) + { + return false; + } + + bool res = mGLTexturep->createGLTexture(mRawDiscardLevel, mRawImage, usename, true, mBoostLevel); + + return res; +} + +void LLViewerFetchedTexture::postCreateTexture() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if (!mNeedsCreateTexture) + { + return; + } +#if LL_IMAGEGL_THREAD_CHECK + mGLTexturep->checkActiveThread(); +#endif + + setActive(); + + if (!needsToSaveRawImage()) + { + mNeedsAux = false; + destroyRawImage(); + } + + mNeedsCreateTexture = false; +} + +void LLViewerFetchedTexture::scheduleCreateTexture() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + + if (!mNeedsCreateTexture) + { + mNeedsCreateTexture = true; + if (preCreateTexture()) + { +#if LL_IMAGEGL_THREAD_CHECK + //grab a copy of the raw image data to make sure it isn't modified pending texture creation + U8* data = mRawImage->getData(); + U8* data_copy = nullptr; + S32 size = mRawImage->getDataSize(); + if (data != nullptr && size > 0) + { + data_copy = new U8[size]; + memcpy(data_copy, data, size); + } +#endif + mNeedsCreateTexture = true; + auto mainq = LLImageGLThread::sEnabledTextures ? mMainQueue.lock() : nullptr; + if (mainq) + { + ref(); + mainq->postTo( + mImageQueue, + // work to be done on LLImageGL worker thread +#if LL_IMAGEGL_THREAD_CHECK + [this, data, data_copy, size]() + { + mGLTexturep->mActiveThread = LLThread::currentID(); + //verify data is unmodified + llassert(data == mRawImage->getData()); + llassert(mRawImage->getDataSize() == size); + llassert(memcmp(data, data_copy, size) == 0); +#else + [this]() + { +#endif + //actually create the texture on a background thread + createTexture(); + +#if LL_IMAGEGL_THREAD_CHECK + //verify data is unmodified + llassert(data == mRawImage->getData()); + llassert(mRawImage->getDataSize() == size); + llassert(memcmp(data, data_copy, size) == 0); +#endif + }, + // callback to be run on main thread +#if LL_IMAGEGL_THREAD_CHECK + [this, data, data_copy, size]() + { + mGLTexturep->mActiveThread = LLThread::currentID(); + llassert(data == mRawImage->getData()); + llassert(mRawImage->getDataSize() == size); + llassert(memcmp(data, data_copy, size) == 0); + delete[] data_copy; +#else + [this]() + { +#endif + //finalize on main thread + postCreateTexture(); + unref(); + }); + } + else + { + gTextureList.mCreateTextureList.insert(this); + } + } + } +} + +// Call with 0,0 to turn this feature off. +//virtual +void LLViewerFetchedTexture::setKnownDrawSize(S32 width, S32 height) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if(mKnownDrawWidth < width || mKnownDrawHeight < height) + { + mKnownDrawWidth = llmax(mKnownDrawWidth, width); + mKnownDrawHeight = llmax(mKnownDrawHeight, height); + + mKnownDrawSizeChanged = true; + mFullyLoaded = false; + } + addTextureStats((F32)(mKnownDrawWidth * mKnownDrawHeight)); +} + +void LLViewerFetchedTexture::setDebugText(const std::string& text) +{ + for (U32 ch = 0; ch < LLRender::NUM_TEXTURE_CHANNELS; ++ch) + { + llassert(mNumFaces[ch] <= mFaceList[ch].size()); + + for (U32 i = 0; i < mNumFaces[ch]; i++) + { + LLFace* facep = mFaceList[ch][i]; + if (facep) + { + LLDrawable* drawable = facep->getDrawable(); + if (drawable) + { + drawable->getVObj()->setDebugText(text); + } + } + } + } +} + +//virtual +void LLViewerFetchedTexture::processTextureStats() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if(mFullyLoaded) + { + if(mDesiredDiscardLevel > mMinDesiredDiscardLevel)//need to load more + { + mDesiredDiscardLevel = llmin(mDesiredDiscardLevel, mMinDesiredDiscardLevel); + mFullyLoaded = false; + } + //setDebugText("fully loaded"); + } + else + { + updateVirtualSize(); + + static LLCachedControl textures_fullres(gSavedSettings,"TextureLoadFullRes", false); + + if (textures_fullres) + { + mDesiredDiscardLevel = 0; + } + else if (mDontDiscard && (mBoostLevel == LLGLTexture::BOOST_ICON || mBoostLevel == LLGLTexture::BOOST_THUMBNAIL)) + { + if (mFullWidth > MAX_IMAGE_SIZE_DEFAULT || mFullHeight > MAX_IMAGE_SIZE_DEFAULT) + { + mDesiredDiscardLevel = 1; // MAX_IMAGE_SIZE_DEFAULT = 1024 and max size ever is 2048 + } + else + { + mDesiredDiscardLevel = 0; + } + } + else if(!mFullWidth || !mFullHeight) + { + mDesiredDiscardLevel = llmin(getMaxDiscardLevel(), (S32)mLoadedCallbackDesiredDiscardLevel); + } + else + { + U32 desired_size = MAX_IMAGE_SIZE_DEFAULT; // MAX_IMAGE_SIZE_DEFAULT = 1024 and max size ever is 2048 + if(!mKnownDrawWidth || !mKnownDrawHeight || mFullWidth <= mKnownDrawWidth || mFullHeight <= mKnownDrawHeight) + { + if (mFullWidth > desired_size || mFullHeight > desired_size) + { + mDesiredDiscardLevel = 1; + } + else + { + mDesiredDiscardLevel = 0; + } + } + else if(mKnownDrawSizeChanged)//known draw size is set + { + mDesiredDiscardLevel = (S8)llmin(log((F32)mFullWidth / mKnownDrawWidth) / log_2, + log((F32)mFullHeight / mKnownDrawHeight) / log_2); + mDesiredDiscardLevel = llclamp(mDesiredDiscardLevel, (S8)0, (S8)getMaxDiscardLevel()); + mDesiredDiscardLevel = llmin(mDesiredDiscardLevel, mMinDesiredDiscardLevel); + } + mKnownDrawSizeChanged = false; + + if(getDiscardLevel() >= 0 && (getDiscardLevel() <= mDesiredDiscardLevel)) + { + mFullyLoaded = true; + } + } + } + + if(mForceToSaveRawImage && mDesiredSavedRawDiscardLevel >= 0) //force to refetch the texture. + { + mDesiredDiscardLevel = llmin(mDesiredDiscardLevel, (S8)mDesiredSavedRawDiscardLevel); + if(getDiscardLevel() < 0 || getDiscardLevel() > mDesiredDiscardLevel) + { + mFullyLoaded = false; + } + } +} + +//============================================================================ + +void LLViewerFetchedTexture::updateVirtualSize() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + reorganizeFaceList(); + reorganizeVolumeList(); +} + +S32 LLViewerFetchedTexture::getCurrentDiscardLevelForFetching() +{ + S32 current_discard = getDiscardLevel(); + if(mForceToSaveRawImage) + { + if(mSavedRawDiscardLevel < 0 || current_discard < 0) + { + current_discard = -1; + } + else + { + current_discard = llmax(current_discard, mSavedRawDiscardLevel); + } + } + + return current_discard; +} + +bool LLViewerFetchedTexture::setDebugFetching(S32 debug_level) +{ + if(debug_level < 0) + { + mInDebug = false; + return false; + } + mInDebug = true; + + mDesiredDiscardLevel = debug_level; + + return true; +} + +bool LLViewerFetchedTexture::isActiveFetching() +{ + static LLCachedControl monitor_enabled(gSavedSettings,"DebugShowTextureInfo"); + + return mFetchState > 7 && mFetchState < 10 && monitor_enabled; //in state of WAIT_HTTP_REQ or DECODE_IMAGE. +} + +void LLViewerFetchedTexture::setBoostLevel(S32 level) +{ + LLViewerTexture::setBoostLevel(level); + + if (level >= LLViewerTexture::BOOST_HIGH) + { + mDesiredDiscardLevel = 0; + } +} + +bool LLViewerFetchedTexture::updateFetch() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + static LLCachedControl textures_decode_disabled(gSavedSettings,"TextureDecodeDisabled", false); + + if(textures_decode_disabled) // don't fetch the surface textures in wireframe mode + { + return false; + } + + mFetchState = 0; + mFetchPriority = 0; + mFetchDeltaTime = 999999.f; + mRequestDeltaTime = 999999.f; + +#ifndef LL_RELEASE_FOR_DOWNLOAD + if (mID == LLAppViewer::getTextureFetch()->mDebugID) + { + LLAppViewer::getTextureFetch()->mDebugCount++; // for setting breakpoints + } +#endif + + if (mNeedsCreateTexture) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - needs create"); + // We may be fetching still (e.g. waiting on write) + // but don't check until we've processed the raw data we have + return false; + } + if (mIsMissingAsset) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - missing asset"); + llassert(!mHasFetcher); + return false; // skip + } + if (!mLoadedCallbackList.empty() && mRawImage.notNull()) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - callback pending"); + return false; // process any raw image data in callbacks before replacing + } + if(mInFastCacheList) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - in fast cache"); + return false; + } + if (mGLTexturep.isNull()) + { // fix for crash inside getCurrentDiscardLevelForFetching (shouldn't happen but appears to be happening) + llassert(false); + return false; + } + + S32 current_discard = getCurrentDiscardLevelForFetching(); + S32 desired_discard = getDesiredDiscardLevel(); + F32 decode_priority = mMaxVirtualSize; + + if (mIsFetching) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - is fetching"); + // Sets mRawDiscardLevel, mRawImage, mAuxRawImage + S32 fetch_discard = current_discard; + + if (mRawImage.notNull()) sRawCount--; + if (mAuxRawImage.notNull()) sAuxCount--; + // keep in mind that fetcher still might need raw image, don't modify original + bool finished = LLAppViewer::getTextureFetch()->getRequestFinished(getID(), fetch_discard, mRawImage, mAuxRawImage, + mLastHttpGetStatus); + if (mRawImage.notNull()) sRawCount++; + if (mAuxRawImage.notNull()) + { + mHasAux = true; + sAuxCount++; + } + if (finished) + { + mIsFetching = false; + mLastFetchState = -1; + mLastPacketTimer.reset(); + } + else + { + mFetchState = LLAppViewer::getTextureFetch()->getFetchState(mID, mDownloadProgress, mRequestedDownloadPriority, + mFetchPriority, mFetchDeltaTime, mRequestDeltaTime, mCanUseHTTP); + } + + // We may have data ready regardless of whether or not we are finished (e.g. waiting on write) + if (mRawImage.notNull()) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - has raw image"); + LLTexturePipelineTester* tester = (LLTexturePipelineTester*)LLMetricPerformanceTesterBasic::getTester(sTesterName); + if (tester) + { + mIsFetched = true; + tester->updateTextureLoadingStats(this, mRawImage, LLAppViewer::getTextureFetch()->isFromLocalCache(mID)); + } + mRawDiscardLevel = fetch_discard; + if ((mRawImage->getDataSize() > 0 && mRawDiscardLevel >= 0) && + (current_discard < 0 || mRawDiscardLevel < current_discard)) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - data good"); + mFullWidth = mRawImage->getWidth() << mRawDiscardLevel; + mFullHeight = mRawImage->getHeight() << mRawDiscardLevel; + setTexelsPerImage(); + + if(mFullWidth > MAX_IMAGE_SIZE || mFullHeight > MAX_IMAGE_SIZE) + { + //discard all oversized textures. + destroyRawImage(); + LL_WARNS() << "oversize, setting as missing" << LL_ENDL; + setIsMissingAsset(); + mRawDiscardLevel = INVALID_DISCARD_LEVEL; + mIsFetching = false; + mLastPacketTimer.reset(); + } + else + { + mIsRawImageValid = true; + addToCreateTexture(); + } + + if (mBoostLevel == LLGLTexture::BOOST_ICON) + { + S32 expected_width = mKnownDrawWidth > 0 ? mKnownDrawWidth : DEFAULT_ICON_DIMENSIONS; + S32 expected_height = mKnownDrawHeight > 0 ? mKnownDrawHeight : DEFAULT_ICON_DIMENSIONS; + if (mRawImage && (mRawImage->getWidth() > expected_width || mRawImage->getHeight() > expected_height)) + { + // scale oversized icon, no need to give more work to gl + // since we got mRawImage from thread worker and image may be in use (ex: writing cache), make a copy + mRawImage = mRawImage->scaled(expected_width, expected_height); + } + } + + if (mBoostLevel == LLGLTexture::BOOST_THUMBNAIL) + { + S32 expected_width = mKnownDrawWidth > 0 ? mKnownDrawWidth : DEFAULT_THUMBNAIL_DIMENSIONS; + S32 expected_height = mKnownDrawHeight > 0 ? mKnownDrawHeight : DEFAULT_THUMBNAIL_DIMENSIONS; + if (mRawImage && (mRawImage->getWidth() > expected_width || mRawImage->getHeight() > expected_height)) + { + // scale oversized icon, no need to give more work to gl + // since we got mRawImage from thread worker and image may be in use (ex: writing cache), make a copy + mRawImage = mRawImage->scaled(expected_width, expected_height); + } + } + + return true; + } + else + { + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - data not needed"); + // Data is ready but we don't need it + // (received it already while fetcher was writing to disk) + destroyRawImage(); + return false; // done + } + } + + if (!mIsFetching) + { + if ((decode_priority > 0) && (mRawDiscardLevel < 0 || mRawDiscardLevel == INVALID_DISCARD_LEVEL)) + { + // We finished but received no data + if (getDiscardLevel() < 0) + { + if (getFTType() != FTT_MAP_TILE) + { + LL_WARNS() << mID + << " Fetch failure, setting as missing, decode_priority " << decode_priority + << " mRawDiscardLevel " << mRawDiscardLevel + << " current_discard " << current_discard + << " stats " << mLastHttpGetStatus.toHex() + << LL_ENDL; + } + setIsMissingAsset(); + desired_discard = -1; + } + else + { + //LL_WARNS() << mID << ": Setting min discard to " << current_discard << LL_ENDL; + if(current_discard >= 0) + { + mMinDiscardLevel = current_discard; + //desired_discard = current_discard; + } + else + { + S32 dis_level = getDiscardLevel(); + mMinDiscardLevel = dis_level; + //desired_discard = dis_level; + } + } + destroyRawImage(); + } + else if (mRawImage.notNull()) + { + // We have data, but our fetch failed to return raw data + // *TODO: FIgure out why this is happening and fix it + destroyRawImage(); + } + } + else + { + static const F32 MAX_HOLD_TIME = 5.0f; //seconds to wait before canceling fecthing if decode_priority is 0.f. + if(decode_priority > 0.0f || mStopFetchingTimer.getElapsedTimeF32() > MAX_HOLD_TIME) + { + mStopFetchingTimer.reset(); + LLAppViewer::getTextureFetch()->updateRequestPriority(mID, decode_priority); + } + } + } + + desired_discard = llmin(desired_discard, getMaxDiscardLevel()); + + bool make_request = true; + if (decode_priority <= 0) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - priority <= 0"); + make_request = false; + } + else if(mDesiredDiscardLevel > getMaxDiscardLevel()) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - desired > max"); + make_request = false; + } + else if (mNeedsCreateTexture || mIsMissingAsset) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - create or missing"); + make_request = false; + } + else if (current_discard >= 0 && current_discard <= mMinDiscardLevel) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - current < min"); + make_request = false; + } + else if(mCachedRawImage.notNull() // can be empty + && mCachedRawImageReady + && (current_discard < 0 || current_discard > mCachedRawDiscardLevel)) + { + make_request = false; + switchToCachedImage(); //use the cached raw data first + } + + if (make_request) + { + if (mIsFetching) + { + // already requested a higher resolution mip + if (mRequestedDiscardLevel <= desired_discard) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - requested < desired"); + make_request = false; + } + } + else + { + // already at a higher resolution mip, don't discard + if (current_discard >= 0 && current_discard <= desired_discard) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - current <= desired"); + make_request = false; + } + } + } + + if (make_request) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - make request"); + S32 w=0, h=0, c=0; + if (getDiscardLevel() >= 0) + { + w = mGLTexturep->getWidth(0); + h = mGLTexturep->getHeight(0); + c = mComponents; + } + + const U32 override_tex_discard_level = gSavedSettings.getU32("TextureDiscardLevel"); + if (override_tex_discard_level != 0) + { + desired_discard = override_tex_discard_level; + } + + // bypass texturefetch directly by pulling from LLTextureCache + S32 fetch_request_discard = -1; + fetch_request_discard = LLAppViewer::getTextureFetch()->createRequest(mFTType, mUrl, getID(), getTargetHost(), decode_priority, + w, h, c, desired_discard, needsAux(), mCanUseHTTP); + + if (fetch_request_discard >= 0) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vftuf - request created"); + mHasFetcher = true; + mIsFetching = true; + // in some cases createRequest can modify discard, as an example + // bake textures are always at discard 0 + mRequestedDiscardLevel = llmin(desired_discard, fetch_request_discard); + mFetchState = LLAppViewer::getTextureFetch()->getFetchState(mID, mDownloadProgress, mRequestedDownloadPriority, + mFetchPriority, mFetchDeltaTime, mRequestDeltaTime, mCanUseHTTP); + } + + // If createRequest() failed, that means one of two things: + // 1. We're finishing up a request for this UUID, so we + // should wait for it to complete + // 2. We've failed a request for this UUID, so there is + // no need to create another request + } + else if (mHasFetcher && !mIsFetching) + { + // Only delete requests that haven't received any network data + // for a while. Note - this is the normal mechanism for + // deleting requests, not just a place to handle timeouts. + const F32 FETCH_IDLE_TIME = 0.1f; + if (mLastPacketTimer.getElapsedTimeF32() > FETCH_IDLE_TIME) + { + LL_DEBUGS("Texture") << "exceeded idle time " << FETCH_IDLE_TIME << ", deleting request: " << getID() << LL_ENDL; + LLAppViewer::getTextureFetch()->deleteRequest(getID(), true); + mHasFetcher = false; + } + } + + return mIsFetching; +} + +void LLViewerFetchedTexture::clearFetchedResults() +{ + if(mNeedsCreateTexture || mIsFetching) + { + return; + } + + cleanup(); + destroyGLTexture(); + + if(getDiscardLevel() >= 0) //sculpty texture, force to invalidate + { + mGLTexturep->forceToInvalidateGLTexture(); + } +} + +void LLViewerFetchedTexture::forceToDeleteRequest() +{ + if (mHasFetcher) + { + mHasFetcher = false; + mIsFetching = false; + } + + resetTextureStats(); + + mDesiredDiscardLevel = getMaxDiscardLevel() + 1; +} + +void LLViewerFetchedTexture::setIsMissingAsset(bool is_missing) +{ + if (is_missing == mIsMissingAsset) + { + return; + } + if (is_missing) + { + if (mUrl.empty()) + { + LL_WARNS() << mID << ": Marking image as missing" << LL_ENDL; + } + else + { + // This may or may not be an error - it is normal to have no + // map tile on an empty region, but bad if we're failing on a + // server bake texture. + if (getFTType() != FTT_MAP_TILE) + { + LL_WARNS() << mUrl << ": Marking image as missing" << LL_ENDL; + } + } + if (mHasFetcher) + { + LLAppViewer::getTextureFetch()->deleteRequest(getID(), true); + mHasFetcher = false; + mIsFetching = false; + mLastPacketTimer.reset(); + mFetchState = 0; + mFetchPriority = 0; + } + } + else + { + LL_INFOS() << mID << ": un-flagging missing asset" << LL_ENDL; + } + mIsMissingAsset = is_missing; +} + +void LLViewerFetchedTexture::setLoadedCallback( loaded_callback_func loaded_callback, + S32 discard_level, bool keep_imageraw, bool needs_aux, void* userdata, + LLLoadedCallbackEntry::source_callback_list_t* src_callback_list, bool pause) +{ + // + // Don't do ANYTHING here, just add it to the global callback list + // + if (mLoadedCallbackList.empty()) + { + // Put in list to call this->doLoadedCallbacks() periodically + gTextureList.mCallbackList.insert(this); + mLoadedCallbackDesiredDiscardLevel = (S8)discard_level; + } + else + { + mLoadedCallbackDesiredDiscardLevel = llmin(mLoadedCallbackDesiredDiscardLevel, (S8)discard_level); + } + + if(mPauseLoadedCallBacks) + { + if(!pause) + { + unpauseLoadedCallbacks(src_callback_list); + } + } + else if(pause) + { + pauseLoadedCallbacks(src_callback_list); + } + + LLLoadedCallbackEntry* entryp = new LLLoadedCallbackEntry(loaded_callback, discard_level, keep_imageraw, userdata, src_callback_list, this, pause); + mLoadedCallbackList.push_back(entryp); + + mNeedsAux |= needs_aux; + if(keep_imageraw) + { + mSaveRawImage = true; + } + if (mNeedsAux && mAuxRawImage.isNull() && getDiscardLevel() >= 0) + { + if(mHasAux) + { + //trigger a refetch + forceToRefetchTexture(); + } + else + { + // We need aux data, but we've already loaded the image, and it didn't have any + LL_WARNS() << "No aux data available for callback for image:" << getID() << LL_ENDL; + } + } + mLastCallBackActiveTime = sCurrentTime ; + mLastReferencedSavedRawImageTime = sCurrentTime; +} + +void LLViewerFetchedTexture::clearCallbackEntryList() +{ + if(mLoadedCallbackList.empty()) + { + return; + } + + for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); + iter != mLoadedCallbackList.end(); ) + { + LLLoadedCallbackEntry *entryp = *iter; + + // We never finished loading the image. Indicate failure. + // Note: this allows mLoadedCallbackUserData to be cleaned up. + entryp->mCallback(false, this, NULL, NULL, 0, true, entryp->mUserData); + iter = mLoadedCallbackList.erase(iter); + delete entryp; + } + gTextureList.mCallbackList.erase(this); + + mLoadedCallbackDesiredDiscardLevel = S8_MAX; + if(needsToSaveRawImage()) + { + destroySavedRawImage(); + } + + return; +} + +void LLViewerFetchedTexture::deleteCallbackEntry(const LLLoadedCallbackEntry::source_callback_list_t* callback_list) +{ + if(mLoadedCallbackList.empty() || !callback_list) + { + return; + } + + S32 desired_discard = S8_MAX; + S32 desired_raw_discard = INVALID_DISCARD_LEVEL; + for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); + iter != mLoadedCallbackList.end(); ) + { + LLLoadedCallbackEntry *entryp = *iter; + if(entryp->mSourceCallbackList == callback_list) + { + // We never finished loading the image. Indicate failure. + // Note: this allows mLoadedCallbackUserData to be cleaned up. + entryp->mCallback(false, this, NULL, NULL, 0, true, entryp->mUserData); + iter = mLoadedCallbackList.erase(iter); + delete entryp; + } + else + { + ++iter; + + desired_discard = llmin(desired_discard, entryp->mDesiredDiscard); + if(entryp->mNeedsImageRaw) + { + desired_raw_discard = llmin(desired_raw_discard, entryp->mDesiredDiscard); + } + } + } + + mLoadedCallbackDesiredDiscardLevel = desired_discard; + if (mLoadedCallbackList.empty()) + { + // If we have no callbacks, take us off of the image callback list. + gTextureList.mCallbackList.erase(this); + + if(needsToSaveRawImage()) + { + destroySavedRawImage(); + } + } + else if(needsToSaveRawImage() && mBoostLevel != LLGLTexture::BOOST_PREVIEW) + { + if(desired_raw_discard != INVALID_DISCARD_LEVEL) + { + mDesiredSavedRawDiscardLevel = desired_raw_discard; + } + else + { + destroySavedRawImage(); + } + } +} + +void LLViewerFetchedTexture::unpauseLoadedCallbacks(const LLLoadedCallbackEntry::source_callback_list_t* callback_list) +{ + if(!callback_list) +{ + mPauseLoadedCallBacks = false; + return; + } + + bool need_raw = false; + for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); + iter != mLoadedCallbackList.end(); ) + { + LLLoadedCallbackEntry *entryp = *iter++; + if(entryp->mSourceCallbackList == callback_list) + { + entryp->mPaused = false; + if(entryp->mNeedsImageRaw) + { + need_raw = true; + } + } + } + mPauseLoadedCallBacks = false ; + mLastCallBackActiveTime = sCurrentTime ; + mForceCallbackFetch = true; + if(need_raw) + { + mSaveRawImage = true; + } +} + +void LLViewerFetchedTexture::pauseLoadedCallbacks(const LLLoadedCallbackEntry::source_callback_list_t* callback_list) +{ + if(!callback_list) +{ + return; + } + + bool paused = true; + + for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); + iter != mLoadedCallbackList.end(); ) + { + LLLoadedCallbackEntry *entryp = *iter++; + if(entryp->mSourceCallbackList == callback_list) + { + entryp->mPaused = true; + } + else if(!entryp->mPaused) + { + paused = false; + } + } + + if(paused) + { + mPauseLoadedCallBacks = true;//when set, loaded callback is paused. + resetTextureStats(); + mSaveRawImage = false; + } +} + +bool LLViewerFetchedTexture::doLoadedCallbacks() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + static const F32 MAX_INACTIVE_TIME = 900.f ; //seconds + static const F32 MAX_IDLE_WAIT_TIME = 5.f ; //seconds + + if (mNeedsCreateTexture) + { + return false; + } + if(mPauseLoadedCallBacks) + { + destroyRawImage(); + return false; //paused + } + if(sCurrentTime - mLastCallBackActiveTime > MAX_INACTIVE_TIME && !mIsFetching) + { + if (mFTType == FTT_SERVER_BAKE) + { + //output some debug info + LL_INFOS() << "baked texture: " << mID << "clears all call backs due to inactivity." << LL_ENDL; + LL_INFOS() << mUrl << LL_ENDL; + LL_INFOS() << "current discard: " << getDiscardLevel() << " current discard for fetch: " << getCurrentDiscardLevelForFetching() << + " Desired discard: " << getDesiredDiscardLevel() << "decode Pri: " << mMaxVirtualSize << LL_ENDL; + } + + clearCallbackEntryList() ; //remove all callbacks. + return false ; + } + + bool res = false; + + if (isMissingAsset()) + { + if (mFTType == FTT_SERVER_BAKE) + { + //output some debug info + LL_INFOS() << "baked texture: " << mID << "is missing." << LL_ENDL; + LL_INFOS() << mUrl << LL_ENDL; + } + + for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); + iter != mLoadedCallbackList.end(); ) + { + LLLoadedCallbackEntry *entryp = *iter++; + // We never finished loading the image. Indicate failure. + // Note: this allows mLoadedCallbackUserData to be cleaned up. + entryp->mCallback(false, this, NULL, NULL, 0, true, entryp->mUserData); + delete entryp; + } + mLoadedCallbackList.clear(); + + // Remove ourself from the global list of textures with callbacks + gTextureList.mCallbackList.erase(this); + return false; + } + + S32 gl_discard = getDiscardLevel(); + + // If we don't have a legit GL image, set it to be lower than the worst discard level + if (gl_discard == -1) + { + gl_discard = MAX_DISCARD_LEVEL + 1; + } + + // + // Determine the quality levels of textures that we can provide to callbacks + // and whether we need to do decompression/readback to get it + // + S32 current_raw_discard = MAX_DISCARD_LEVEL + 1; // We can always do a readback to get a raw discard + S32 best_raw_discard = gl_discard; // Current GL quality level + S32 current_aux_discard = MAX_DISCARD_LEVEL + 1; + S32 best_aux_discard = MAX_DISCARD_LEVEL + 1; + + if (mIsRawImageValid) + { + // If we have an existing raw image, we have a baseline for the raw and auxiliary quality levels. + best_raw_discard = llmin(best_raw_discard, mRawDiscardLevel); + best_aux_discard = llmin(best_aux_discard, mRawDiscardLevel); // We always decode the aux when we decode the base raw + current_aux_discard = llmin(current_aux_discard, best_aux_discard); + } + else + { + // We have no data at all, we need to get it + // Do this by forcing the best aux discard to be 0. + best_aux_discard = 0; + } + + + // + // See if any of the callbacks would actually run using the data that we can provide, + // and also determine if we need to perform any readbacks or decodes. + // + bool run_gl_callbacks = false; + bool run_raw_callbacks = false; + bool need_readback = false; + + for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); + iter != mLoadedCallbackList.end(); ) + { + LLLoadedCallbackEntry *entryp = *iter++; + + if (entryp->mNeedsImageRaw) + { + if (mNeedsAux) + { + // + // Need raw and auxiliary channels + // + if (entryp->mLastUsedDiscard > current_aux_discard) + { + // We have useful data, run the callbacks + run_raw_callbacks = true; + } + } + else + { + if (entryp->mLastUsedDiscard > current_raw_discard) + { + // We have useful data, just run the callbacks + run_raw_callbacks = true; + } + else if (entryp->mLastUsedDiscard > best_raw_discard) + { + // We can readback data, and then run the callbacks + need_readback = true; + run_raw_callbacks = true; + } + } + } + else + { + // Needs just GL + if (entryp->mLastUsedDiscard > gl_discard) + { + // We have enough data, run this callback requiring GL data + run_gl_callbacks = true; + } + } + } + + // + // Do a readback if required, OR start off a texture decode + // + if (need_readback && (getMaxDiscardLevel() > gl_discard)) + { + // Do a readback to get the GL data into the raw image + // We have GL data. + + destroyRawImage(); + reloadRawImage(mLoadedCallbackDesiredDiscardLevel); + llassert(mRawImage.notNull()); + llassert(!mNeedsAux || mAuxRawImage.notNull()); + } + + // + // Run raw/auxiliary data callbacks + // + if (run_raw_callbacks && mIsRawImageValid && (mRawDiscardLevel <= getMaxDiscardLevel())) + { + // Do callbacks which require raw image data. + //LL_INFOS() << "doLoadedCallbacks raw for " << getID() << LL_ENDL; + + // Call each party interested in the raw data. + for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); + iter != mLoadedCallbackList.end(); ) + { + callback_list_t::iterator curiter = iter++; + LLLoadedCallbackEntry *entryp = *curiter; + if (entryp->mNeedsImageRaw && (entryp->mLastUsedDiscard > mRawDiscardLevel)) + { + // If we've loaded all the data there is to load or we've loaded enough + // to satisfy the interested party, then this is the last time that + // we're going to call them. + + mLastCallBackActiveTime = sCurrentTime; + if(mNeedsAux && mAuxRawImage.isNull()) + { + LL_WARNS() << "Raw Image with no Aux Data for callback" << LL_ENDL; + } + bool final = mRawDiscardLevel <= entryp->mDesiredDiscard; + //LL_INFOS() << "Running callback for " << getID() << LL_ENDL; + //LL_INFOS() << mRawImage->getWidth() << "x" << mRawImage->getHeight() << LL_ENDL; + entryp->mLastUsedDiscard = mRawDiscardLevel; + entryp->mCallback(true, this, mRawImage, mAuxRawImage, mRawDiscardLevel, final, entryp->mUserData); + if (final) + { + iter = mLoadedCallbackList.erase(curiter); + delete entryp; + } + res = true; + } + } + } + + // + // Run GL callbacks + // + if (run_gl_callbacks && (gl_discard <= getMaxDiscardLevel())) + { + //LL_INFOS() << "doLoadedCallbacks GL for " << getID() << LL_ENDL; + + // Call the callbacks interested in GL data. + for(callback_list_t::iterator iter = mLoadedCallbackList.begin(); + iter != mLoadedCallbackList.end(); ) + { + callback_list_t::iterator curiter = iter++; + LLLoadedCallbackEntry *entryp = *curiter; + if (!entryp->mNeedsImageRaw && (entryp->mLastUsedDiscard > gl_discard)) + { + mLastCallBackActiveTime = sCurrentTime; + bool final = gl_discard <= entryp->mDesiredDiscard; + entryp->mLastUsedDiscard = gl_discard; + entryp->mCallback(true, this, NULL, NULL, gl_discard, final, entryp->mUserData); + if (final) + { + iter = mLoadedCallbackList.erase(curiter); + delete entryp; + } + res = true; + } + } + } + + // Done with any raw image data at this point (will be re-created if we still have callbacks) + destroyRawImage(); + + // + // If we have no callbacks, take us off of the image callback list. + // + if (mLoadedCallbackList.empty()) + { + gTextureList.mCallbackList.erase(this); + } + else if(!res && mForceCallbackFetch && sCurrentTime - mLastCallBackActiveTime > MAX_IDLE_WAIT_TIME && !mIsFetching) + { + //wait for long enough but no fetching request issued, force one. + forceToRefetchTexture(mLoadedCallbackDesiredDiscardLevel, 5.f); + mForceCallbackFetch = false; //fire once. + } + + return res; +} + +//virtual +void LLViewerFetchedTexture::forceImmediateUpdate() +{ + //only immediately update a deleted texture which is now being re-used. + if(!isDeleted()) + { + return; + } + //if already called forceImmediateUpdate() + if(mInImageList && mMaxVirtualSize == LLViewerFetchedTexture::sMaxVirtualSize) + { + return; + } + + gTextureList.forceImmediateUpdate(this); + return; +} + +LLImageRaw* LLViewerFetchedTexture::reloadRawImage(S8 discard_level) +{ + llassert(mGLTexturep.notNull()); + llassert(discard_level >= 0); + llassert(mComponents > 0); + + if (mRawImage.notNull()) + { + //mRawImage is in use by somebody else, do not delete it. + return NULL; + } + + if(mSavedRawDiscardLevel >= 0 && mSavedRawDiscardLevel <= discard_level) + { + if (mSavedRawDiscardLevel != discard_level + && mBoostLevel != BOOST_ICON + && mBoostLevel != BOOST_THUMBNAIL) + { + mRawImage = new LLImageRaw(getWidth(discard_level), getHeight(discard_level), getComponents()); + mRawImage->copy(getSavedRawImage()); + } + else + { + mRawImage = getSavedRawImage(); + } + mRawDiscardLevel = discard_level; + } + else + { + //force to fetch raw image again if cached raw image is not good enough. + if(mCachedRawDiscardLevel > discard_level) + { + mRawImage = mCachedRawImage; + mRawDiscardLevel = mCachedRawDiscardLevel; + } + else //cached raw image is good enough, copy it. + { + if(mCachedRawDiscardLevel != discard_level) + { + mRawImage = new LLImageRaw(getWidth(discard_level), getHeight(discard_level), getComponents()); + mRawImage->copy(mCachedRawImage); + } + else + { + mRawImage = mCachedRawImage; + } + mRawDiscardLevel = discard_level; + } + } + mIsRawImageValid = true; + sRawCount++; + + return mRawImage; +} + +bool LLViewerFetchedTexture::needsToSaveRawImage() +{ + return mForceToSaveRawImage || mSaveRawImage; +} + +void LLViewerFetchedTexture::destroyRawImage() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if (mAuxRawImage.notNull() && !needsToSaveRawImage()) + { + sAuxCount--; + mAuxRawImage = NULL; + } + + if (mRawImage.notNull()) + { + sRawCount--; + + if(mIsRawImageValid) + { + if(needsToSaveRawImage()) + { + saveRawImage(); + } + setCachedRawImage(); + } + + mRawImage = NULL; + + mIsRawImageValid = false; + mRawDiscardLevel = INVALID_DISCARD_LEVEL; + } +} + +//use the mCachedRawImage to (re)generate the gl texture. +//virtual +void LLViewerFetchedTexture::switchToCachedImage() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if(mCachedRawImage.notNull() && + !mNeedsCreateTexture) // <--- texture creation is pending, don't step on it + { + mRawImage = mCachedRawImage; + + if (getComponents() != mRawImage->getComponents()) + { + // We've changed the number of components, so we need to move any + // objects using this pool to a different pool. + mComponents = mRawImage->getComponents(); + mGLTexturep->setComponents(mComponents); + gTextureList.dirtyImage(this); + } + + mIsRawImageValid = true; + mRawDiscardLevel = mCachedRawDiscardLevel; + + scheduleCreateTexture(); + } +} + +//cache the imageraw forcefully. +//virtual +void LLViewerFetchedTexture::setCachedRawImage(S32 discard_level, LLImageRaw* imageraw) +{ + if(imageraw != mRawImage.get()) + { + if (mBoostLevel == LLGLTexture::BOOST_ICON) + { + S32 expected_width = mKnownDrawWidth > 0 ? mKnownDrawWidth : DEFAULT_ICON_DIMENSIONS; + S32 expected_height = mKnownDrawHeight > 0 ? mKnownDrawHeight : DEFAULT_ICON_DIMENSIONS; + if (mRawImage->getWidth() > expected_width || mRawImage->getHeight() > expected_height) + { + mCachedRawImage = new LLImageRaw(expected_width, expected_height, imageraw->getComponents()); + mCachedRawImage->copyScaled(imageraw); + } + else + { + mCachedRawImage = imageraw; + } + } + else if (mBoostLevel == LLGLTexture::BOOST_THUMBNAIL) + { + S32 expected_width = mKnownDrawWidth > 0 ? mKnownDrawWidth : DEFAULT_THUMBNAIL_DIMENSIONS; + S32 expected_height = mKnownDrawHeight > 0 ? mKnownDrawHeight : DEFAULT_THUMBNAIL_DIMENSIONS; + if (mRawImage->getWidth() > expected_width || mRawImage->getHeight() > expected_height) + { + mCachedRawImage = new LLImageRaw(expected_width, expected_height, imageraw->getComponents()); + mCachedRawImage->copyScaled(imageraw); + } + else + { + mCachedRawImage = imageraw; + } + } + else + { + mCachedRawImage = imageraw; + } + mCachedRawDiscardLevel = discard_level; + mCachedRawImageReady = true; + } +} + +void LLViewerFetchedTexture::setCachedRawImage() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if(mRawImage == mCachedRawImage) + { + return; + } + if(!mIsRawImageValid) + { + return; + } + + if(mCachedRawImageReady) + { + return; + } + + if(mCachedRawDiscardLevel < 0 || mCachedRawDiscardLevel > mRawDiscardLevel) + { + S32 i = 0; + S32 w = mRawImage->getWidth(); + S32 h = mRawImage->getHeight(); + + S32 max_size = MAX_CACHED_RAW_IMAGE_AREA; + if(LLGLTexture::BOOST_TERRAIN == mBoostLevel) + { + max_size = MAX_CACHED_RAW_TERRAIN_IMAGE_AREA; + } + if(mForSculpt) + { + max_size = MAX_CACHED_RAW_SCULPT_IMAGE_AREA; + mCachedRawImageReady = !mRawDiscardLevel; + } + else + { + mCachedRawImageReady = (!mRawDiscardLevel || ((w * h) >= max_size)); + } + + while(((w >> i) * (h >> i)) > max_size) + { + ++i; + } + + if(i) + { + if(!(w >> i) || !(h >> i)) + { + --i; + } + + { + //make a duplicate in case somebody else is using this raw image + mRawImage = mRawImage->scaled(w >> i, h >> i); + } + } + mCachedRawImage = mRawImage; + mRawDiscardLevel += i; + mCachedRawDiscardLevel = mRawDiscardLevel; + } +} + +void LLViewerFetchedTexture::checkCachedRawSculptImage() +{ + if(mCachedRawImageReady && mCachedRawDiscardLevel > 0) + { + if(getDiscardLevel() != 0) + { + mCachedRawImageReady = false; + } + else if(isForSculptOnly()) + { + resetTextureStats(); //do not update this image any more. + } + } +} + +void LLViewerFetchedTexture::saveRawImage() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if(mRawImage.isNull() || mRawImage == mSavedRawImage || (mSavedRawDiscardLevel >= 0 && mSavedRawDiscardLevel <= mRawDiscardLevel)) + { + return; + } + + LLImageDataSharedLock lock(mRawImage); + + mSavedRawDiscardLevel = mRawDiscardLevel; + if (mBoostLevel == LLGLTexture::BOOST_ICON) + { + S32 expected_width = mKnownDrawWidth > 0 ? mKnownDrawWidth : DEFAULT_ICON_DIMENSIONS; + S32 expected_height = mKnownDrawHeight > 0 ? mKnownDrawHeight : DEFAULT_ICON_DIMENSIONS; + if (mRawImage->getWidth() > expected_width || mRawImage->getHeight() > expected_height) + { + mSavedRawImage = new LLImageRaw(expected_width, expected_height, mRawImage->getComponents()); + mSavedRawImage->copyScaled(mRawImage); + } + else + { + mSavedRawImage = new LLImageRaw(mRawImage->getData(), mRawImage->getWidth(), mRawImage->getHeight(), mRawImage->getComponents()); + } + } + else if (mBoostLevel == LLGLTexture::BOOST_THUMBNAIL) + { + S32 expected_width = mKnownDrawWidth > 0 ? mKnownDrawWidth : DEFAULT_THUMBNAIL_DIMENSIONS; + S32 expected_height = mKnownDrawHeight > 0 ? mKnownDrawHeight : DEFAULT_THUMBNAIL_DIMENSIONS; + if (mRawImage->getWidth() > expected_width || mRawImage->getHeight() > expected_height) + { + mSavedRawImage = new LLImageRaw(expected_width, expected_height, mRawImage->getComponents()); + mSavedRawImage->copyScaled(mRawImage); + } + else + { + mSavedRawImage = new LLImageRaw(mRawImage->getData(), mRawImage->getWidth(), mRawImage->getHeight(), mRawImage->getComponents()); + } + } + else + { + mSavedRawImage = new LLImageRaw(mRawImage->getData(), mRawImage->getWidth(), mRawImage->getHeight(), mRawImage->getComponents()); + } + + if(mForceToSaveRawImage && mSavedRawDiscardLevel <= mDesiredSavedRawDiscardLevel) + { + mForceToSaveRawImage = false; + } + + mLastReferencedSavedRawImageTime = sCurrentTime; +} + +//force to refetch the texture to the discard level +void LLViewerFetchedTexture::forceToRefetchTexture(S32 desired_discard, F32 kept_time) +{ + if(mForceToSaveRawImage) + { + desired_discard = llmin(desired_discard, mDesiredSavedRawDiscardLevel); + kept_time = llmax(kept_time, mKeptSavedRawImageTime); + } + + //trigger a new fetch. + mForceToSaveRawImage = true ; + mDesiredSavedRawDiscardLevel = desired_discard ; + mKeptSavedRawImageTime = kept_time ; + mLastReferencedSavedRawImageTime = sCurrentTime ; + mSavedRawImage = NULL ; + mSavedRawDiscardLevel = -1 ; +} + +void LLViewerFetchedTexture::forceToSaveRawImage(S32 desired_discard, F32 kept_time) +{ + mKeptSavedRawImageTime = kept_time; + mLastReferencedSavedRawImageTime = sCurrentTime; + + if(mSavedRawDiscardLevel > -1 && mSavedRawDiscardLevel <= desired_discard) + { + return; //raw imge is ready. + } + + if(!mForceToSaveRawImage || mDesiredSavedRawDiscardLevel < 0 || mDesiredSavedRawDiscardLevel > desired_discard) + { + mForceToSaveRawImage = true; + mDesiredSavedRawDiscardLevel = desired_discard; + + //copy from the cached raw image if exists. + if(mCachedRawImage.notNull() && mRawImage.isNull() ) + { + mRawImage = mCachedRawImage; + mRawDiscardLevel = mCachedRawDiscardLevel; + + saveRawImage(); + + mRawImage = NULL; + mRawDiscardLevel = INVALID_DISCARD_LEVEL; + } + } +} +void LLViewerFetchedTexture::destroySavedRawImage() +{ + if(mLastReferencedSavedRawImageTime < mKeptSavedRawImageTime) + { + return; //keep the saved raw image. + } + + mForceToSaveRawImage = false; + mSaveRawImage = false; + + clearCallbackEntryList(); + + mSavedRawImage = NULL ; + mForceToSaveRawImage = false ; + mSaveRawImage = false ; + mSavedRawDiscardLevel = -1 ; + mDesiredSavedRawDiscardLevel = -1 ; + mLastReferencedSavedRawImageTime = 0.0f ; + mKeptSavedRawImageTime = 0.f ; + + if(mAuxRawImage.notNull()) + { + sAuxCount--; + mAuxRawImage = NULL; + } +} + +LLImageRaw* LLViewerFetchedTexture::getSavedRawImage() +{ + mLastReferencedSavedRawImageTime = sCurrentTime; + + return mSavedRawImage; +} + +bool LLViewerFetchedTexture::hasSavedRawImage() const +{ + return mSavedRawImage.notNull(); +} + +F32 LLViewerFetchedTexture::getElapsedLastReferencedSavedRawImageTime() const +{ + return sCurrentTime - mLastReferencedSavedRawImageTime; +} + +//---------------------------------------------------------------------------------------------- +//end of LLViewerFetchedTexture +//---------------------------------------------------------------------------------------------- + +//---------------------------------------------------------------------------------------------- +//start of LLViewerLODTexture +//---------------------------------------------------------------------------------------------- +LLViewerLODTexture::LLViewerLODTexture(const LLUUID& id, FTType f_type, const LLHost& host, bool usemipmaps) + : LLViewerFetchedTexture(id, f_type, host, usemipmaps) +{ + init(true); +} + +LLViewerLODTexture::LLViewerLODTexture(const std::string& url, FTType f_type, const LLUUID& id, bool usemipmaps) + : LLViewerFetchedTexture(url, f_type, id, usemipmaps) +{ + init(true); +} + +void LLViewerLODTexture::init(bool firstinit) +{ + mTexelsPerImage = 64.f*64.f; + mDiscardVirtualSize = 0.f; + mCalculatedDiscardLevel = -1.f; +} + +//virtual +S8 LLViewerLODTexture::getType() const +{ + return LLViewerTexture::LOD_TEXTURE; +} + +bool LLViewerLODTexture::isUpdateFrozen() +{ + return LLViewerTexture::sFreezeImageUpdates; +} + +// This is gauranteed to get called periodically for every texture +//virtual +void LLViewerLODTexture::processTextureStats() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + updateVirtualSize(); + + static LLCachedControl textures_fullres(gSavedSettings,"TextureLoadFullRes", false); + + if (textures_fullres) + { + mDesiredDiscardLevel = 0; + } + // Generate the request priority and render priority + else if (mDontDiscard || !mUseMipMaps) + { + mDesiredDiscardLevel = 0; + if (mFullWidth > MAX_IMAGE_SIZE_DEFAULT || mFullHeight > MAX_IMAGE_SIZE_DEFAULT) + mDesiredDiscardLevel = 1; // MAX_IMAGE_SIZE_DEFAULT = 1024 and max size ever is 2048 + } + else if (mBoostLevel < LLGLTexture::BOOST_HIGH && mMaxVirtualSize <= 10.f) + { + // If the image has not been significantly visible in a while, we don't want it + mDesiredDiscardLevel = llmin(mMinDesiredDiscardLevel, (S8)(MAX_DISCARD_LEVEL + 1)); + } + else if (!mFullWidth || !mFullHeight) + { + mDesiredDiscardLevel = getMaxDiscardLevel(); + } + else + { + //static const F64 log_2 = log(2.0); + static const F64 log_4 = log(4.0); + + F32 discard_level = 0.f; + + // If we know the output width and height, we can force the discard + // level to the correct value, and thus not decode more texture + // data than we need to. + if (mKnownDrawWidth && mKnownDrawHeight) + { + S32 draw_texels = mKnownDrawWidth * mKnownDrawHeight; + draw_texels = llclamp(draw_texels, MIN_IMAGE_AREA, MAX_IMAGE_AREA); + + // Use log_4 because we're in square-pixel space, so an image + // with twice the width and twice the height will have mTexelsPerImage + // 4 * draw_size + discard_level = (F32)(log(mTexelsPerImage / draw_texels) / log_4); + } + else + { + // Calculate the required scale factor of the image using pixels per texel + discard_level = (F32)(log(mTexelsPerImage / mMaxVirtualSize) / log_4); + mDiscardVirtualSize = mMaxVirtualSize; + mCalculatedDiscardLevel = discard_level; + } + if (mBoostLevel < LLGLTexture::BOOST_SCULPTED) + { + discard_level *= sDesiredDiscardScale; // scale (default 1.1f) + } + discard_level = floorf(discard_level); + + F32 min_discard = 0.f; + U32 desired_size = MAX_IMAGE_SIZE_DEFAULT; // MAX_IMAGE_SIZE_DEFAULT = 1024 and max size ever is 2048 + if (mBoostLevel <= LLGLTexture::BOOST_SCULPTED) + { + desired_size = DESIRED_NORMAL_TEXTURE_SIZE; + } + if (mFullWidth > desired_size || mFullHeight > desired_size) + min_discard = 1.f; + + discard_level = llclamp(discard_level, min_discard, (F32)MAX_DISCARD_LEVEL); + + // Can't go higher than the max discard level + mDesiredDiscardLevel = llmin(getMaxDiscardLevel() + 1, (S32)discard_level); + // Clamp to min desired discard + mDesiredDiscardLevel = llmin(mMinDesiredDiscardLevel, mDesiredDiscardLevel); + + // + // At this point we've calculated the quality level that we want, + // if possible. Now we check to see if we have it, and take the + // proper action if we don't. + // + + S32 current_discard = getDiscardLevel(); + if (mBoostLevel < LLGLTexture::BOOST_AVATAR_BAKED && + current_discard >= 0) + { + if (current_discard < (mDesiredDiscardLevel-1) && !mForceToSaveRawImage) + { // should scale down + scaleDown(); + } + } + + if (isUpdateFrozen() // we are out of memory and nearing max allowed bias + && mBoostLevel < LLGLTexture::BOOST_SCULPTED + && mDesiredDiscardLevel < current_discard) + { + // stop requesting more + mDesiredDiscardLevel = current_discard; + } + } + + if(mForceToSaveRawImage && mDesiredSavedRawDiscardLevel >= 0) + { + mDesiredDiscardLevel = llmin(mDesiredDiscardLevel, (S8)mDesiredSavedRawDiscardLevel); + } + + // decay max virtual size over time + mMaxVirtualSize *= 0.8f; + + // selection manager will immediately reset BOOST_SELECTED but never unsets it + // unset it immediately after we consume it + if (getBoostLevel() == BOOST_SELECTED) + { + setBoostLevel(BOOST_NONE); + } +} + +bool LLViewerLODTexture::scaleDown() +{ + if(hasGLTexture() && mCachedRawDiscardLevel > getDiscardLevel()) + { + switchToCachedImage(); + + LLTexturePipelineTester* tester = (LLTexturePipelineTester*)LLMetricPerformanceTesterBasic::getTester(sTesterName); + if (tester) + { + tester->setStablizingTime(); + } + + return true; + } + return false; +} +//---------------------------------------------------------------------------------------------- +//end of LLViewerLODTexture +//---------------------------------------------------------------------------------------------- + +//---------------------------------------------------------------------------------------------- +//start of LLViewerMediaTexture +//---------------------------------------------------------------------------------------------- +//static +void LLViewerMediaTexture::updateClass() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + static const F32 MAX_INACTIVE_TIME = 30.f; + +#if 0 + //force to play media. + gSavedSettings.setBOOL("AudioStreamingMedia", true); +#endif + + for(media_map_t::iterator iter = sMediaMap.begin(); iter != sMediaMap.end(); ) + { + LLViewerMediaTexture* mediap = iter->second; + + if(mediap->getNumRefs() == 1) //one reference by sMediaMap + { + // + //Note: delay some time to delete the media textures to stop endlessly creating and immediately removing media texture. + // + if(mediap->getLastReferencedTimer()->getElapsedTimeF32() > MAX_INACTIVE_TIME) + { + media_map_t::iterator cur = iter++; + sMediaMap.erase(cur); + continue; + } + } + ++iter; + } +} + +//static +void LLViewerMediaTexture::removeMediaImplFromTexture(const LLUUID& media_id) +{ + LLViewerMediaTexture* media_tex = findMediaTexture(media_id); + if(media_tex) + { + media_tex->invalidateMediaImpl(); + } +} + +//static +void LLViewerMediaTexture::cleanUpClass() +{ + sMediaMap.clear(); +} + +//static +LLViewerMediaTexture* LLViewerMediaTexture::findMediaTexture(const LLUUID& media_id) +{ + media_map_t::iterator iter = sMediaMap.find(media_id); + if(iter == sMediaMap.end()) + { + return NULL; + } + + LLViewerMediaTexture* media_tex = iter->second; + media_tex->setMediaImpl(); + media_tex->getLastReferencedTimer()->reset(); + + return media_tex; +} + +LLViewerMediaTexture::LLViewerMediaTexture(const LLUUID& id, bool usemipmaps, LLImageGL* gl_image) + : LLViewerTexture(id, usemipmaps), + mMediaImplp(NULL), + mUpdateVirtualSizeTime(0) +{ + sMediaMap.insert(std::make_pair(id, this)); + + mGLTexturep = gl_image; + + if(mGLTexturep.isNull()) + { + generateGLTexture(); + } + + mGLTexturep->setAllowCompression(false); + + mGLTexturep->setNeedsAlphaAndPickMask(false); + + mIsPlaying = false; + + setMediaImpl(); + + setCategory(LLGLTexture::MEDIA); + + LLViewerTexture* tex = gTextureList.findImage(mID, TEX_LIST_STANDARD); + if(tex) //this media is a parcel media for tex. + { + tex->setParcelMedia(this); + } +} + +//virtual +LLViewerMediaTexture::~LLViewerMediaTexture() +{ + LLViewerTexture* tex = gTextureList.findImage(mID, TEX_LIST_STANDARD); + if(tex) //this media is a parcel media for tex. + { + tex->setParcelMedia(NULL); + } +} + +void LLViewerMediaTexture::reinit(bool usemipmaps /* = true */) +{ + llassert(mGLTexturep.notNull()); + + mUseMipMaps = usemipmaps; + getLastReferencedTimer()->reset(); + mGLTexturep->setUseMipMaps(mUseMipMaps); + mGLTexturep->setNeedsAlphaAndPickMask(false); +} + +void LLViewerMediaTexture::setUseMipMaps(bool mipmap) +{ + mUseMipMaps = mipmap; + + if(mGLTexturep.notNull()) + { + mGLTexturep->setUseMipMaps(mipmap); + } +} + +//virtual +S8 LLViewerMediaTexture::getType() const +{ + return LLViewerTexture::MEDIA_TEXTURE; +} + +void LLViewerMediaTexture::invalidateMediaImpl() +{ + mMediaImplp = NULL; +} + +void LLViewerMediaTexture::setMediaImpl() +{ + if(!mMediaImplp) + { + mMediaImplp = LLViewerMedia::getInstance()->getMediaImplFromTextureID(mID); + } +} + +//return true if all faces to reference to this media texture are found +//Note: mMediaFaceList is valid only for the current instant +// because it does not check the face validity after the current frame. +bool LLViewerMediaTexture::findFaces() +{ + mMediaFaceList.clear(); + + bool ret = true; + + LLViewerTexture* tex = gTextureList.findImage(mID, TEX_LIST_STANDARD); + if(tex) //this media is a parcel media for tex. + { + for (U32 ch = 0; ch < LLRender::NUM_TEXTURE_CHANNELS; ++ch) + { + const ll_face_list_t* face_list = tex->getFaceList(ch); + U32 end = tex->getNumFaces(ch); + for(U32 i = 0; i < end; i++) + { + if ((*face_list)[i]->isMediaAllowed()) + { + mMediaFaceList.push_back((*face_list)[i]); + } + } + } + } + + if(!mMediaImplp) + { + return true; + } + + //for media on a face. + const std::list< LLVOVolume* >* obj_list = mMediaImplp->getObjectList(); + std::list< LLVOVolume* >::const_iterator iter = obj_list->begin(); + for(; iter != obj_list->end(); ++iter) + { + LLVOVolume* obj = *iter; + if (obj->isDead()) + { + // Isn't supposed to happen, objects are supposed to detach + // themselves on markDead() + // If this happens, viewer is likely to crash + llassert(0); + LL_WARNS() << "Dead object in mMediaImplp's object list" << LL_ENDL; + ret = false; + continue; + } + + if (obj->mDrawable.isNull() || obj->mDrawable->isDead()) + { + ret = false; + continue; + } + + S32 face_id = -1; + S32 num_faces = obj->mDrawable->getNumFaces(); + while((face_id = obj->getFaceIndexWithMediaImpl(mMediaImplp, face_id)) > -1 && face_id < num_faces) + { + LLFace* facep = obj->mDrawable->getFace(face_id); + if(facep) + { + mMediaFaceList.push_back(facep); + } + else + { + ret = false; + } + } + } + + return ret; +} + +void LLViewerMediaTexture::initVirtualSize() +{ + if(mIsPlaying) + { + return; + } + + findFaces(); + for(std::list< LLFace* >::iterator iter = mMediaFaceList.begin(); iter!= mMediaFaceList.end(); ++iter) + { + addTextureStats((*iter)->getVirtualSize()); + } +} + +void LLViewerMediaTexture::addMediaToFace(LLFace* facep) +{ + if(facep) + { + facep->setHasMedia(true); + } + if(!mIsPlaying) + { + return; //no need to add the face because the media is not in playing. + } + + switchTexture(LLRender::DIFFUSE_MAP, facep); +} + +void LLViewerMediaTexture::removeMediaFromFace(LLFace* facep) +{ + if(!facep) + { + return; + } + facep->setHasMedia(false); + + if(!mIsPlaying) + { + return; //no need to remove the face because the media is not in playing. + } + + mIsPlaying = false; //set to remove the media from the face. + switchTexture(LLRender::DIFFUSE_MAP, facep); + mIsPlaying = true; //set the flag back. + + if(getTotalNumFaces() < 1) //no face referencing to this media + { + stopPlaying(); + } +} + +//virtual +void LLViewerMediaTexture::addFace(U32 ch, LLFace* facep) +{ + LLViewerTexture::addFace(ch, facep); + + const LLTextureEntry* te = facep->getTextureEntry(); + if(te && te->getID().notNull()) + { + LLViewerTexture* tex = gTextureList.findImage(te->getID(), TEX_LIST_STANDARD); + if(tex) + { + mTextureList.push_back(tex);//increase the reference number by one for tex to avoid deleting it. + return; + } + } + + //check if it is a parcel media + if(facep->getTexture() && facep->getTexture() != this && facep->getTexture()->getID() == mID) + { + mTextureList.push_back(facep->getTexture()); //a parcel media. + return; + } + + if(te && te->getID().notNull()) //should have a texture + { + LL_WARNS_ONCE() << "The face's texture " << te->getID() << " is not valid. Face must have a valid texture before media texture." << LL_ENDL; + // This might break the object, but it likely isn't a 'recoverable' situation. + LLViewerFetchedTexture* tex = LLViewerTextureManager::getFetchedTexture(te->getID()); + mTextureList.push_back(tex); + } +} + +//virtual +void LLViewerMediaTexture::removeFace(U32 ch, LLFace* facep) +{ + LLViewerTexture::removeFace(ch, facep); + + const LLTextureEntry* te = facep->getTextureEntry(); + if(te && te->getID().notNull()) + { + LLViewerTexture* tex = gTextureList.findImage(te->getID(), TEX_LIST_STANDARD); + if(tex) + { + for(std::list< LLPointer >::iterator iter = mTextureList.begin(); + iter != mTextureList.end(); ++iter) + { + if(*iter == tex) + { + mTextureList.erase(iter); //decrease the reference number for tex by one. + return; + } + } + + std::vector te_list; + + for (U32 ch = 0; ch < 3; ++ch) + { + // + //we have some trouble here: the texture of the face is changed. + //we need to find the former texture, and remove it from the list to avoid memory leaking. + + llassert(mNumFaces[ch] <= mFaceList[ch].size()); + + for(U32 j = 0; j < mNumFaces[ch]; j++) + { + te_list.push_back(mFaceList[ch][j]->getTextureEntry());//all textures are in use. + } + } + + if (te_list.empty()) + { + mTextureList.clear(); + return; + } + + S32 end = te_list.size(); + + for(std::list< LLPointer >::iterator iter = mTextureList.begin(); + iter != mTextureList.end(); ++iter) + { + S32 i = 0; + + for(i = 0; i < end; i++) + { + if(te_list[i] && te_list[i]->getID() == (*iter)->getID())//the texture is in use. + { + te_list[i] = NULL; + break; + } + } + if(i == end) //no hit for this texture, remove it. + { + mTextureList.erase(iter); //decrease the reference number for tex by one. + return; + } + } + } + } + + //check if it is a parcel media + for(std::list< LLPointer >::iterator iter = mTextureList.begin(); + iter != mTextureList.end(); ++iter) + { + if((*iter)->getID() == mID) + { + mTextureList.erase(iter); //decrease the reference number for tex by one. + return; + } + } + + if(te && te->getID().notNull()) //should have a texture but none found + { + LL_ERRS() << "mTextureList texture reference number is corrupted. Texture id: " << te->getID() << " List size: " << (U32)mTextureList.size() << LL_ENDL; + } +} + +void LLViewerMediaTexture::stopPlaying() +{ + // Don't stop the media impl playing here -- this breaks non-inworld media (login screen, search, and media browser). +// if(mMediaImplp) +// { +// mMediaImplp->stop(); +// } + mIsPlaying = false; +} + +void LLViewerMediaTexture::switchTexture(U32 ch, LLFace* facep) +{ + if(facep) + { + //check if another media is playing on this face. + if(facep->getTexture() && facep->getTexture() != this + && facep->getTexture()->getType() == LLViewerTexture::MEDIA_TEXTURE) + { + if(mID == facep->getTexture()->getID()) //this is a parcel media + { + return; //let the prim media win. + } + } + + if(mIsPlaying) //old textures switch to the media texture + { + facep->switchTexture(ch, this); + } + else //switch to old textures. + { + const LLTextureEntry* te = facep->getTextureEntry(); + if(te) + { + LLViewerTexture* tex = te->getID().notNull() ? gTextureList.findImage(te->getID(), TEX_LIST_STANDARD) : NULL; + if(!tex && te->getID() != mID)//try parcel media. + { + tex = gTextureList.findImage(mID, TEX_LIST_STANDARD); + } + if(!tex) + { + tex = LLViewerFetchedTexture::sDefaultImagep; + } + facep->switchTexture(ch, tex); + } + } + } +} + +void LLViewerMediaTexture::setPlaying(bool playing) +{ + if(!mMediaImplp) + { + return; + } + if(!playing && !mIsPlaying) + { + return; //media is already off + } + + if(playing == mIsPlaying && !mMediaImplp->isUpdated()) + { + return; //nothing has changed since last time. + } + + mIsPlaying = playing; + if(mIsPlaying) //is about to play this media + { + if(findFaces()) + { + //about to update all faces. + mMediaImplp->setUpdated(false); + } + + if(mMediaFaceList.empty())//no face pointing to this media + { + stopPlaying(); + return; + } + + for(std::list< LLFace* >::iterator iter = mMediaFaceList.begin(); iter!= mMediaFaceList.end(); ++iter) + { + switchTexture(LLRender::DIFFUSE_MAP, *iter); + } + } + else //stop playing this media + { + U32 ch = LLRender::DIFFUSE_MAP; + + llassert(mNumFaces[ch] <= mFaceList[ch].size()); + for(U32 i = mNumFaces[ch]; i; i--) + { + switchTexture(ch, mFaceList[ch][i - 1]); //current face could be removed in this function. + } + } + return; +} + +//virtual +F32 LLViewerMediaTexture::getMaxVirtualSize() +{ + if(LLFrameTimer::getFrameCount() == mUpdateVirtualSizeTime) + { + return mMaxVirtualSize; + } + mUpdateVirtualSizeTime = LLFrameTimer::getFrameCount(); + + if(!mMaxVirtualSizeResetCounter) + { + addTextureStats(0.f, false);//reset + } + + if(mIsPlaying) //media is playing + { + for (U32 ch = 0; ch < LLRender::NUM_TEXTURE_CHANNELS; ++ch) + { + llassert(mNumFaces[ch] <= mFaceList[ch].size()); + for(U32 i = 0; i < mNumFaces[ch]; i++) + { + LLFace* facep = mFaceList[ch][i]; + if(facep->getDrawable()->isRecentlyVisible()) + { + addTextureStats(facep->getVirtualSize()); + } + } + } + } + else //media is not in playing + { + findFaces(); + + if(!mMediaFaceList.empty()) + { + for(std::list< LLFace* >::iterator iter = mMediaFaceList.begin(); iter!= mMediaFaceList.end(); ++iter) + { + LLFace* facep = *iter; + if(facep->getDrawable()->isRecentlyVisible()) + { + addTextureStats(facep->getVirtualSize()); + } + } + } + } + + if(mMaxVirtualSizeResetCounter > 0) + { + mMaxVirtualSizeResetCounter--; + } + reorganizeFaceList(); + reorganizeVolumeList(); + + return mMaxVirtualSize; +} +//---------------------------------------------------------------------------------------------- +//end of LLViewerMediaTexture +//---------------------------------------------------------------------------------------------- + +//---------------------------------------------------------------------------------------------- +//start of LLTexturePipelineTester +//---------------------------------------------------------------------------------------------- +LLTexturePipelineTester::LLTexturePipelineTester() : LLMetricPerformanceTesterWithSession(sTesterName) +{ + addMetric("TotalBytesLoaded"); + addMetric("TotalBytesLoadedFromCache"); + addMetric("TotalBytesLoadedForLargeImage"); + addMetric("TotalBytesLoadedForSculpties"); + addMetric("StartFetchingTime"); + addMetric("TotalGrayTime"); + addMetric("TotalStablizingTime"); + addMetric("StartTimeLoadingSculpties"); + addMetric("EndTimeLoadingSculpties"); + + addMetric("Time"); + addMetric("TotalBytesBound"); + addMetric("TotalBytesBoundForLargeImage"); + addMetric("PercentageBytesBound"); + + mTotalBytesLoaded = (S32Bytes)0; + mTotalBytesLoadedFromCache = (S32Bytes)0; + mTotalBytesLoadedForLargeImage = (S32Bytes)0; + mTotalBytesLoadedForSculpties = (S32Bytes)0; + + reset(); +} + +LLTexturePipelineTester::~LLTexturePipelineTester() +{ + LLViewerTextureManager::sTesterp = NULL; +} + +void LLTexturePipelineTester::update() +{ + mLastTotalBytesUsed = mTotalBytesUsed; + mLastTotalBytesUsedForLargeImage = mTotalBytesUsedForLargeImage; + mTotalBytesUsed = (S32Bytes)0; + mTotalBytesUsedForLargeImage = (S32Bytes)0; + + if(LLAppViewer::getTextureFetch()->getNumRequests() > 0) //fetching list is not empty + { + if(mPause) + { + //start a new fetching session + reset(); + mStartFetchingTime = LLImageGL::sLastFrameTime; + mPause = false; + } + + //update total gray time + if(mUsingDefaultTexture) + { + mUsingDefaultTexture = false; + mTotalGrayTime = LLImageGL::sLastFrameTime - mStartFetchingTime; + } + + //update the stablizing timer. + updateStablizingTime(); + + outputTestResults(); + } + else if(!mPause) + { + //stop the current fetching session + mPause = true; + outputTestResults(); + reset(); + } +} + +void LLTexturePipelineTester::reset() +{ + mPause = true; + + mUsingDefaultTexture = false; + mStartStablizingTime = 0.0f; + mEndStablizingTime = 0.0f; + + mTotalBytesUsed = (S32Bytes)0; + mTotalBytesUsedForLargeImage = (S32Bytes)0; + mLastTotalBytesUsed = (S32Bytes)0; + mLastTotalBytesUsedForLargeImage = (S32Bytes)0; + + mStartFetchingTime = 0.0f; + + mTotalGrayTime = 0.0f; + mTotalStablizingTime = 0.0f; + + mStartTimeLoadingSculpties = 1.0f; + mEndTimeLoadingSculpties = 0.0f; +} + +//virtual +void LLTexturePipelineTester::outputTestRecord(LLSD *sd) +{ + std::string currentLabel = getCurrentLabelName(); + (*sd)[currentLabel]["TotalBytesLoaded"] = (LLSD::Integer)mTotalBytesLoaded.value(); + (*sd)[currentLabel]["TotalBytesLoadedFromCache"] = (LLSD::Integer)mTotalBytesLoadedFromCache.value(); + (*sd)[currentLabel]["TotalBytesLoadedForLargeImage"] = (LLSD::Integer)mTotalBytesLoadedForLargeImage.value(); + (*sd)[currentLabel]["TotalBytesLoadedForSculpties"] = (LLSD::Integer)mTotalBytesLoadedForSculpties.value(); + + (*sd)[currentLabel]["StartFetchingTime"] = (LLSD::Real)mStartFetchingTime; + (*sd)[currentLabel]["TotalGrayTime"] = (LLSD::Real)mTotalGrayTime; + (*sd)[currentLabel]["TotalStablizingTime"] = (LLSD::Real)mTotalStablizingTime; + + (*sd)[currentLabel]["StartTimeLoadingSculpties"] = (LLSD::Real)mStartTimeLoadingSculpties; + (*sd)[currentLabel]["EndTimeLoadingSculpties"] = (LLSD::Real)mEndTimeLoadingSculpties; + + (*sd)[currentLabel]["Time"] = LLImageGL::sLastFrameTime; + (*sd)[currentLabel]["TotalBytesBound"] = (LLSD::Integer)mLastTotalBytesUsed.value(); + (*sd)[currentLabel]["TotalBytesBoundForLargeImage"] = (LLSD::Integer)mLastTotalBytesUsedForLargeImage.value(); + (*sd)[currentLabel]["PercentageBytesBound"] = (LLSD::Real)(100.f * mLastTotalBytesUsed / mTotalBytesLoaded); +} + +void LLTexturePipelineTester::updateTextureBindingStats(const LLViewerTexture* imagep) +{ + U32Bytes mem_size = imagep->getTextureMemory(); + mTotalBytesUsed += mem_size; + + if(MIN_LARGE_IMAGE_AREA <= (U32)(mem_size.value() / (U32)imagep->getComponents())) + { + mTotalBytesUsedForLargeImage += mem_size; + } +} + +void LLTexturePipelineTester::updateTextureLoadingStats(const LLViewerFetchedTexture* imagep, const LLImageRaw* raw_imagep, bool from_cache) +{ + U32Bytes data_size = (U32Bytes)raw_imagep->getDataSize(); + mTotalBytesLoaded += data_size; + + if(from_cache) + { + mTotalBytesLoadedFromCache += data_size; + } + + if(MIN_LARGE_IMAGE_AREA <= (U32)(data_size.value() / (U32)raw_imagep->getComponents())) + { + mTotalBytesLoadedForLargeImage += data_size; + } + + if(imagep->forSculpt()) + { + mTotalBytesLoadedForSculpties += data_size; + + if(mStartTimeLoadingSculpties > mEndTimeLoadingSculpties) + { + mStartTimeLoadingSculpties = LLImageGL::sLastFrameTime; + } + mEndTimeLoadingSculpties = LLImageGL::sLastFrameTime; + } +} + +void LLTexturePipelineTester::updateGrayTextureBinding() +{ + mUsingDefaultTexture = true; +} + +void LLTexturePipelineTester::setStablizingTime() +{ + if(mStartStablizingTime <= mStartFetchingTime) + { + mStartStablizingTime = LLImageGL::sLastFrameTime; + } + mEndStablizingTime = LLImageGL::sLastFrameTime; +} + +void LLTexturePipelineTester::updateStablizingTime() +{ + if(mStartStablizingTime > mStartFetchingTime) + { + F32 t = mEndStablizingTime - mStartStablizingTime; + + if(t > F_ALMOST_ZERO && (t - mTotalStablizingTime) < F_ALMOST_ZERO) + { + //already stablized + mTotalStablizingTime = LLImageGL::sLastFrameTime - mStartStablizingTime; + + //cancel the timer + mStartStablizingTime = 0.f; + mEndStablizingTime = 0.f; + } + else + { + mTotalStablizingTime = t; + } + } + mTotalStablizingTime = 0.f; +} + +//virtual +void LLTexturePipelineTester::compareTestSessions(llofstream* os) +{ + LLTexturePipelineTester::LLTextureTestSession* base_sessionp = dynamic_cast(mBaseSessionp); + LLTexturePipelineTester::LLTextureTestSession* current_sessionp = dynamic_cast(mCurrentSessionp); + if(!base_sessionp || !current_sessionp) + { + LL_ERRS() << "type of test session does not match!" << LL_ENDL; + } + + //compare and output the comparison + *os << llformat("%s\n", getTesterName().c_str()); + *os << llformat("AggregateResults\n"); + + compareTestResults(os, "TotalGrayTime", base_sessionp->mTotalGrayTime, current_sessionp->mTotalGrayTime); + compareTestResults(os, "TotalStablizingTime", base_sessionp->mTotalStablizingTime, current_sessionp->mTotalStablizingTime); + compareTestResults(os, "StartTimeLoadingSculpties", base_sessionp->mStartTimeLoadingSculpties, current_sessionp->mStartTimeLoadingSculpties); + compareTestResults(os, "TotalTimeLoadingSculpties", base_sessionp->mTotalTimeLoadingSculpties, current_sessionp->mTotalTimeLoadingSculpties); + + compareTestResults(os, "TotalBytesLoaded", base_sessionp->mTotalBytesLoaded, current_sessionp->mTotalBytesLoaded); + compareTestResults(os, "TotalBytesLoadedFromCache", base_sessionp->mTotalBytesLoadedFromCache, current_sessionp->mTotalBytesLoadedFromCache); + compareTestResults(os, "TotalBytesLoadedForLargeImage", base_sessionp->mTotalBytesLoadedForLargeImage, current_sessionp->mTotalBytesLoadedForLargeImage); + compareTestResults(os, "TotalBytesLoadedForSculpties", base_sessionp->mTotalBytesLoadedForSculpties, current_sessionp->mTotalBytesLoadedForSculpties); + + *os << llformat("InstantResults\n"); + S32 size = llmin(base_sessionp->mInstantPerformanceListCounter, current_sessionp->mInstantPerformanceListCounter); + for(S32 i = 0; i < size; i++) + { + *os << llformat("Time(B-T)-%.4f-%.4f\n", base_sessionp->mInstantPerformanceList[i].mTime, current_sessionp->mInstantPerformanceList[i].mTime); + + compareTestResults(os, "AverageBytesUsedPerSecond", base_sessionp->mInstantPerformanceList[i].mAverageBytesUsedPerSecond, + current_sessionp->mInstantPerformanceList[i].mAverageBytesUsedPerSecond); + + compareTestResults(os, "AverageBytesUsedForLargeImagePerSecond", base_sessionp->mInstantPerformanceList[i].mAverageBytesUsedForLargeImagePerSecond, + current_sessionp->mInstantPerformanceList[i].mAverageBytesUsedForLargeImagePerSecond); + + compareTestResults(os, "AveragePercentageBytesUsedPerSecond", base_sessionp->mInstantPerformanceList[i].mAveragePercentageBytesUsedPerSecond, + current_sessionp->mInstantPerformanceList[i].mAveragePercentageBytesUsedPerSecond); + } + + if(size < base_sessionp->mInstantPerformanceListCounter) + { + for(S32 i = size; i < base_sessionp->mInstantPerformanceListCounter; i++) + { + *os << llformat("Time(B-T)-%.4f- \n", base_sessionp->mInstantPerformanceList[i].mTime); + + *os << llformat(", AverageBytesUsedPerSecond, %d, N/A \n", base_sessionp->mInstantPerformanceList[i].mAverageBytesUsedPerSecond); + *os << llformat(", AverageBytesUsedForLargeImagePerSecond, %d, N/A \n", base_sessionp->mInstantPerformanceList[i].mAverageBytesUsedForLargeImagePerSecond); + *os << llformat(", AveragePercentageBytesUsedPerSecond, %.4f, N/A \n", base_sessionp->mInstantPerformanceList[i].mAveragePercentageBytesUsedPerSecond); + } + } + else if(size < current_sessionp->mInstantPerformanceListCounter) + { + for(S32 i = size; i < current_sessionp->mInstantPerformanceListCounter; i++) + { + *os << llformat("Time(B-T)- -%.4f\n", current_sessionp->mInstantPerformanceList[i].mTime); + + *os << llformat(", AverageBytesUsedPerSecond, N/A, %d\n", current_sessionp->mInstantPerformanceList[i].mAverageBytesUsedPerSecond); + *os << llformat(", AverageBytesUsedForLargeImagePerSecond, N/A, %d\n", current_sessionp->mInstantPerformanceList[i].mAverageBytesUsedForLargeImagePerSecond); + *os << llformat(", AveragePercentageBytesUsedPerSecond, N/A, %.4f\n", current_sessionp->mInstantPerformanceList[i].mAveragePercentageBytesUsedPerSecond); + } + } +} + +//virtual +LLMetricPerformanceTesterWithSession::LLTestSession* LLTexturePipelineTester::loadTestSession(LLSD* log) +{ + LLTexturePipelineTester::LLTextureTestSession* sessionp = new LLTexturePipelineTester::LLTextureTestSession(); + if(!sessionp) + { + return NULL; + } + + F32 total_gray_time = 0.f; + F32 total_stablizing_time = 0.f; + F32 total_loading_sculpties_time = 0.f; + + F32 start_fetching_time = -1.f; + F32 start_fetching_sculpties_time = 0.f; + + F32 last_time = 0.0f; + S32 frame_count = 0; + + sessionp->mInstantPerformanceListCounter = 0; + sessionp->mInstantPerformanceList.resize(128); + sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedPerSecond = 0; + sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedForLargeImagePerSecond = 0; + sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAveragePercentageBytesUsedPerSecond = 0.f; + sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mTime = 0.f; + + //load a session + std::string currentLabel = getCurrentLabelName(); + bool in_log = (*log).has(currentLabel); + while (in_log) + { + LLSD::String label = currentLabel; + + if(sessionp->mInstantPerformanceListCounter >= (S32)sessionp->mInstantPerformanceList.size()) + { + sessionp->mInstantPerformanceList.resize(sessionp->mInstantPerformanceListCounter + 128); + } + + //time + F32 start_time = (*log)[label]["StartFetchingTime"].asReal(); + F32 cur_time = (*log)[label]["Time"].asReal(); + if(start_time - start_fetching_time > F_ALMOST_ZERO) //fetching has paused for a while + { + sessionp->mTotalGrayTime += total_gray_time; + sessionp->mTotalStablizingTime += total_stablizing_time; + + sessionp->mStartTimeLoadingSculpties = start_fetching_sculpties_time; + sessionp->mTotalTimeLoadingSculpties += total_loading_sculpties_time; + + start_fetching_time = start_time; + total_gray_time = 0.f; + total_stablizing_time = 0.f; + total_loading_sculpties_time = 0.f; + } + else + { + total_gray_time = (*log)[label]["TotalGrayTime"].asReal(); + total_stablizing_time = (*log)[label]["TotalStablizingTime"].asReal(); + + total_loading_sculpties_time = (*log)[label]["EndTimeLoadingSculpties"].asReal() - (*log)[label]["StartTimeLoadingSculpties"].asReal(); + if(start_fetching_sculpties_time < 0.f && total_loading_sculpties_time > 0.f) + { + start_fetching_sculpties_time = (*log)[label]["StartTimeLoadingSculpties"].asReal(); + } + } + + //total loaded bytes + sessionp->mTotalBytesLoaded = (*log)[label]["TotalBytesLoaded"].asInteger(); + sessionp->mTotalBytesLoadedFromCache = (*log)[label]["TotalBytesLoadedFromCache"].asInteger(); + sessionp->mTotalBytesLoadedForLargeImage = (*log)[label]["TotalBytesLoadedForLargeImage"].asInteger(); + sessionp->mTotalBytesLoadedForSculpties = (*log)[label]["TotalBytesLoadedForSculpties"].asInteger(); + + //instant metrics + sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedPerSecond += + (*log)[label]["TotalBytesBound"].asInteger(); + sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedForLargeImagePerSecond += + (*log)[label]["TotalBytesBoundForLargeImage"].asInteger(); + sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAveragePercentageBytesUsedPerSecond += + (*log)[label]["PercentageBytesBound"].asReal(); + frame_count++; + if(cur_time - last_time >= 1.0f) + { + sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedPerSecond /= frame_count; + sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedForLargeImagePerSecond /= frame_count; + sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAveragePercentageBytesUsedPerSecond /= frame_count; + sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mTime = last_time; + + frame_count = 0; + last_time = cur_time; + sessionp->mInstantPerformanceListCounter++; + sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedPerSecond = 0; + sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAverageBytesUsedForLargeImagePerSecond = 0; + sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mAveragePercentageBytesUsedPerSecond = 0.f; + sessionp->mInstantPerformanceList[sessionp->mInstantPerformanceListCounter].mTime = 0.f; + } + // Next label + incrementCurrentCount(); + currentLabel = getCurrentLabelName(); + in_log = (*log).has(currentLabel); + } + + sessionp->mTotalGrayTime += total_gray_time; + sessionp->mTotalStablizingTime += total_stablizing_time; + + if(sessionp->mStartTimeLoadingSculpties < 0.f) + { + sessionp->mStartTimeLoadingSculpties = start_fetching_sculpties_time; + } + sessionp->mTotalTimeLoadingSculpties += total_loading_sculpties_time; + + return sessionp; +} + +LLTexturePipelineTester::LLTextureTestSession::LLTextureTestSession() +{ + reset(); +} +LLTexturePipelineTester::LLTextureTestSession::~LLTextureTestSession() +{ +} +void LLTexturePipelineTester::LLTextureTestSession::reset() +{ + mTotalGrayTime = 0.0f; + mTotalStablizingTime = 0.0f; + + mStartTimeLoadingSculpties = 0.0f; + mTotalTimeLoadingSculpties = 0.0f; + + mTotalBytesLoaded = 0; + mTotalBytesLoadedFromCache = 0; + mTotalBytesLoadedForLargeImage = 0; + mTotalBytesLoadedForSculpties = 0; + + mInstantPerformanceListCounter = 0; +} +//---------------------------------------------------------------------------------------------- +//end of LLTexturePipelineTester +//---------------------------------------------------------------------------------------------- + diff --git a/indra/newview/llviewertexture.h b/indra/newview/llviewertexture.h index 57618b8a46..063e4255d0 100644 --- a/indra/newview/llviewertexture.h +++ b/indra/newview/llviewertexture.h @@ -1,792 +1,792 @@ -/** - * @file llviewertexture.h - * @brief Object for managing images and their textures - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVIEWERTEXTURE_H -#define LL_LLVIEWERTEXTURE_H - -#include "llatomic.h" -#include "llgltexture.h" -#include "lltimer.h" -#include "llframetimer.h" -#include "llhost.h" -#include "llgltypes.h" -#include "llrender.h" -#include "llmetricperformancetester.h" -#include "httpcommon.h" -#include "workqueue.h" - -#include -#include - -extern const S32Megabytes gMinVideoRam; -extern const S32Megabytes gMaxVideoRam; - -class LLFace; -class LLImageGL ; -class LLImageRaw; -class LLViewerObject; -class LLViewerTexture; -class LLViewerFetchedTexture ; -class LLViewerMediaTexture ; -class LLTexturePipelineTester ; - - -typedef void (*loaded_callback_func)( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* src_aux, S32 discard_level, bool final, void* userdata ); - -class LLFileSystem; -class LLMessageSystem; -class LLViewerMediaImpl ; -class LLVOVolume ; -struct LLTextureKey; - -class LLLoadedCallbackEntry -{ -public: - typedef std::set< LLTextureKey > source_callback_list_t; - -public: - LLLoadedCallbackEntry(loaded_callback_func cb, - S32 discard_level, - bool need_imageraw, // Needs image raw for the callback - void* userdata, - source_callback_list_t* src_callback_list, - LLViewerFetchedTexture* target, - bool pause); - ~LLLoadedCallbackEntry(); - void removeTexture(LLViewerFetchedTexture* tex) ; - - loaded_callback_func mCallback; - S32 mLastUsedDiscard; - S32 mDesiredDiscard; - bool mNeedsImageRaw; - bool mPaused; - void* mUserData; - source_callback_list_t* mSourceCallbackList; - -public: - static void cleanUpCallbackList(LLLoadedCallbackEntry::source_callback_list_t* callback_list) ; -}; - -class LLTextureBar; - -class LLViewerTexture : public LLGLTexture -{ -public: - enum - { - LOCAL_TEXTURE, - MEDIA_TEXTURE, - DYNAMIC_TEXTURE, - FETCHED_TEXTURE, - LOD_TEXTURE, - ATLAS_TEXTURE, - INVALID_TEXTURE_TYPE - }; - - typedef std::vector ll_face_list_t; - typedef std::vector ll_volume_list_t; - - -protected: - virtual ~LLViewerTexture(); - LOG_CLASS(LLViewerTexture); - -public: - static void initClass(); - static void updateClass(); - - LLViewerTexture(bool usemipmaps = true); - LLViewerTexture(const LLUUID& id, bool usemipmaps) ; - LLViewerTexture(const LLImageRaw* raw, bool usemipmaps) ; - LLViewerTexture(const U32 width, const U32 height, const U8 components, bool usemipmaps) ; - - virtual S8 getType() const; - virtual bool isMissingAsset() const ; - virtual void dump(); // debug info to LL_INFOS() - - virtual bool isViewerMediaTexture() const { return false; } - - /*virtual*/ bool bindDefaultImage(const S32 stage = 0) ; - /*virtual*/ bool bindDebugImage(const S32 stage = 0) ; - /*virtual*/ void forceImmediateUpdate() ; - /*virtual*/ bool isActiveFetching(); - - /*virtual*/ const LLUUID& getID() const { return mID; } - virtual void setBoostLevel(S32 level); - S32 getBoostLevel() { return mBoostLevel; } - void setTextureListType(S32 tex_type) { mTextureListType = tex_type; } - S32 getTextureListType() { return mTextureListType; } - - void addTextureStats(F32 virtual_size, bool needs_gltexture = true) const; - void resetTextureStats(); - void setMaxVirtualSizeResetInterval(S32 interval)const {mMaxVirtualSizeResetInterval = interval;} - void resetMaxVirtualSizeResetCounter()const {mMaxVirtualSizeResetCounter = mMaxVirtualSizeResetInterval;} - S32 getMaxVirtualSizeResetCounter() const { return mMaxVirtualSizeResetCounter; } - - virtual F32 getMaxVirtualSize() ; - - LLFrameTimer* getLastReferencedTimer() {return &mLastReferencedTimer ;} - - S32 getFullWidth() const { return mFullWidth; } - S32 getFullHeight() const { return mFullHeight; } - /*virtual*/ void setKnownDrawSize(S32 width, S32 height); - - virtual void addFace(U32 channel, LLFace* facep) ; - virtual void removeFace(U32 channel, LLFace* facep) ; - S32 getTotalNumFaces() const; - S32 getNumFaces(U32 ch) const; - const ll_face_list_t* getFaceList(U32 channel) const {llassert(channel < LLRender::NUM_TEXTURE_CHANNELS); return &mFaceList[channel];} - - virtual void addVolume(U32 channel, LLVOVolume* volumep); - virtual void removeVolume(U32 channel, LLVOVolume* volumep); - S32 getNumVolumes(U32 channel) const; - const ll_volume_list_t* getVolumeList(U32 channel) const { return &mVolumeList[channel]; } - - - virtual void setCachedRawImage(S32 discard_level, LLImageRaw* imageraw) ; - bool isLargeImage() ; - - void setParcelMedia(LLViewerMediaTexture* media) {mParcelMedia = media;} - bool hasParcelMedia() const { return mParcelMedia != NULL;} - LLViewerMediaTexture* getParcelMedia() const { return mParcelMedia;} - - /*virtual*/ void updateBindStatsForTester() ; -protected: - void cleanup() ; - void init(bool firstinit) ; - void reorganizeFaceList() ; - void reorganizeVolumeList(); - -private: - friend class LLBumpImageList; - friend class LLUIImageList; - - virtual void switchToCachedImage(); - -protected: - friend class LLViewerTextureList; - LLUUID mID; - S32 mTextureListType; // along with mID identifies where to search for this texture in TextureList - - mutable F32 mMaxVirtualSize = 0.f; // The largest virtual size of the image, in pixels - how much data to we need? - mutable S32 mMaxVirtualSizeResetCounter; - mutable S32 mMaxVirtualSizeResetInterval; - LLFrameTimer mLastReferencedTimer; - - ll_face_list_t mFaceList[LLRender::NUM_TEXTURE_CHANNELS]; //reverse pointer pointing to the faces using this image as texture - U32 mNumFaces[LLRender::NUM_TEXTURE_CHANNELS]; - LLFrameTimer mLastFaceListUpdateTimer ; - - ll_volume_list_t mVolumeList[LLRender::NUM_VOLUME_TEXTURE_CHANNELS]; - U32 mNumVolumes[LLRender::NUM_VOLUME_TEXTURE_CHANNELS]; - LLFrameTimer mLastVolumeListUpdateTimer; - - //do not use LLPointer here. - LLViewerMediaTexture* mParcelMedia ; - - LL::WorkQueue::weak_t mMainQueue; - LL::WorkQueue::weak_t mImageQueue; - -public: - static const U32 sCurrentFileVersion; - static S32 sImageCount; - static S32 sRawCount; - static S32 sAuxCount; - static LLFrameTimer sEvaluationTimer; - static F32 sDesiredDiscardBias; - static F32 sDesiredDiscardScale; - static S32 sMaxSculptRez ; - static U32 sMinLargeImageSize ; - static U32 sMaxSmallImageSize ; - static bool sFreezeImageUpdates; - static F32 sCurrentTime ; - - // estimated free memory for textures, by bias calculation - static F32 sFreeVRAMMegabytes; - - enum EDebugTexels - { - DEBUG_TEXELS_OFF, - DEBUG_TEXELS_CURRENT, - DEBUG_TEXELS_DESIRED, - DEBUG_TEXELS_FULL - }; - - static EDebugTexels sDebugTexelsMode; - - static LLPointer sNullImagep; // Null texture for non-textured objects. - static LLPointer sBlackImagep; // Texture to show NOTHING (pure black) - static LLPointer sCheckerBoardImagep; // Texture to show NOTHING (pure black) -}; - - -enum FTType -{ - FTT_UNKNOWN = -1, - FTT_DEFAULT = 0, // standard texture fetched by id. - FTT_SERVER_BAKE, // texture produced by appearance service and fetched from there. - FTT_HOST_BAKE, // old-style baked texture uploaded by viewer and fetched from avatar's host. - FTT_MAP_TILE, // tiles are fetched from map server directly. - FTT_LOCAL_FILE // fetch directly from a local file. -}; - -const std::string& fttype_to_string(const FTType& fttype); - -// -//textures are managed in gTextureList. -//raw image data is fetched from remote or local cache -//but the raw image this texture pointing to is fixed. -// -class LLViewerFetchedTexture : public LLViewerTexture -{ - friend class LLTextureBar; // debug info only - friend class LLTextureView; // debug info only - -protected: - /*virtual*/ ~LLViewerFetchedTexture(); -public: - LLViewerFetchedTexture(const LLUUID& id, FTType f_type, const LLHost& host = LLHost(), bool usemipmaps = true); - LLViewerFetchedTexture(const LLImageRaw* raw, FTType f_type, bool usemipmaps); - LLViewerFetchedTexture(const std::string& url, FTType f_type, const LLUUID& id, bool usemipmaps = true); - -public: - - struct Compare - { - // lhs < rhs - bool operator()(const LLPointer &lhs, const LLPointer &rhs) const - { - const LLViewerFetchedTexture* lhsp = (const LLViewerFetchedTexture*)lhs; - const LLViewerFetchedTexture* rhsp = (const LLViewerFetchedTexture*)rhs; - - // greater priority is "less" - const F32 lpriority = lhsp->mMaxVirtualSize; - const F32 rpriority = rhsp->mMaxVirtualSize; - if (lpriority > rpriority) // higher priority - return true; - if (lpriority < rpriority) - return false; - return lhsp < rhsp; - } - }; - -public: - /*virtual*/ S8 getType() const override; - FTType getFTType() const; - /*virtual*/ void forceImmediateUpdate() override; - /*virtual*/ void dump() override; - - // Set callbacks to get called when the image gets updated with higher - // resolution versions. - void setLoadedCallback(loaded_callback_func cb, - S32 discard_level, bool keep_imageraw, bool needs_aux, - void* userdata, LLLoadedCallbackEntry::source_callback_list_t* src_callback_list, bool pause = false); - bool hasCallbacks() { return !mLoadedCallbackList.empty(); } - void pauseLoadedCallbacks(const LLLoadedCallbackEntry::source_callback_list_t* callback_list); - void unpauseLoadedCallbacks(const LLLoadedCallbackEntry::source_callback_list_t* callback_list); - bool doLoadedCallbacks(); - void deleteCallbackEntry(const LLLoadedCallbackEntry::source_callback_list_t* callback_list); - void clearCallbackEntryList() ; - - void addToCreateTexture(); - - //call to determine if createTexture is necessary - bool preCreateTexture(S32 usename = 0); - // ONLY call from LLViewerTextureList or ImageGL background thread - bool createTexture(S32 usename = 0); - void postCreateTexture(); - void scheduleCreateTexture(); - - void destroyTexture() ; - - virtual void processTextureStats() ; - - bool needsAux() const { return mNeedsAux; } - - // Host we think might have this image, used for baked av textures. - void setTargetHost(LLHost host) { mTargetHost = host; } - LLHost getTargetHost() const { return mTargetHost; } - - void updateVirtualSize() ; - - S32 getDesiredDiscardLevel() { return mDesiredDiscardLevel; } - void setMinDiscardLevel(S32 discard) { mMinDesiredDiscardLevel = llmin(mMinDesiredDiscardLevel,(S8)discard); } - - void setBoostLevel(S32 level) override; - bool updateFetch(); - bool setDebugFetching(S32 debug_level); - bool isInDebug() const { return mInDebug; } - - void setUnremovable(bool value) { mUnremovable = value; } - bool isUnremovable() const { return mUnremovable; } - - void clearFetchedResults(); //clear all fetched results, for debug use. - - // Override the computation of discard levels if we know the exact output - // size of the image. Used for UI textures to not decode, even if we have - // more data. - /*virtual*/ void setKnownDrawSize(S32 width, S32 height) override; - - // Set the debug text of all Viewer Objects associated with this texture - // to the specified text - void setDebugText(const std::string& text); - - void setIsMissingAsset(bool is_missing = true); - /*virtual*/ bool isMissingAsset() const override { return mIsMissingAsset; } - - // returns dimensions of original image for local files (before power of two scaling) - // and returns 0 for all asset system images - S32 getOriginalWidth() { return mOrigWidth; } - S32 getOriginalHeight() { return mOrigHeight; } - - bool isInImageList() const {return mInImageList ;} - void setInImageList(bool flag) {mInImageList = flag ;} - - LLFrameTimer* getLastPacketTimer() {return &mLastPacketTimer;} - - U32 getFetchPriority() const { return mFetchPriority ;} - F32 getDownloadProgress() const {return mDownloadProgress ;} - - LLImageRaw* reloadRawImage(S8 discard_level) ; - void destroyRawImage(); - bool needsToSaveRawImage(); - - const std::string& getUrl() const {return mUrl;} - //--------------- - bool isDeleted() ; - bool isInactive() ; - bool isDeletionCandidate(); - void setDeletionCandidate() ; - void setInactive() ; - bool getUseDiscard() const { return mUseMipMaps && !mDontDiscard; } - //--------------- - - void setForSculpt(); - bool forSculpt() const {return mForSculpt;} - bool isForSculptOnly() const; - - //raw image management - void checkCachedRawSculptImage() ; - LLImageRaw* getRawImage()const { return mRawImage ;} - S32 getRawImageLevel() const {return mRawDiscardLevel;} - LLImageRaw* getCachedRawImage() const { return mCachedRawImage ;} - S32 getCachedRawImageLevel() const {return mCachedRawDiscardLevel;} - bool isCachedRawImageReady() const {return mCachedRawImageReady ;} - bool isRawImageValid()const { return mIsRawImageValid ; } - void forceToSaveRawImage(S32 desired_discard = 0, F32 kept_time = 0.f) ; - void forceToRefetchTexture(S32 desired_discard = 0, F32 kept_time = 60.f); - /*virtual*/ void setCachedRawImage(S32 discard_level, LLImageRaw* imageraw) override; - void destroySavedRawImage() ; - LLImageRaw* getSavedRawImage() ; - bool hasSavedRawImage() const ; - F32 getElapsedLastReferencedSavedRawImageTime() const ; - bool isFullyLoaded() const; - - bool hasFetcher() const { return mHasFetcher;} - bool isFetching() const { return mIsFetching;} - void setCanUseHTTP(bool can_use_http) {mCanUseHTTP = can_use_http;} - - void forceToDeleteRequest(); - void loadFromFastCache(); - void setInFastCacheList(bool in_list) { mInFastCacheList = in_list; } - bool isInFastCacheList() { return mInFastCacheList; } - - /*virtual*/bool isActiveFetching() override; //is actively in fetching by the fetching pipeline. - -protected: - /*virtual*/ void switchToCachedImage() override; - S32 getCurrentDiscardLevelForFetching() ; - -private: - void init(bool firstinit) ; - void cleanup() ; - - void saveRawImage() ; - void setCachedRawImage() ; - - //for atlas - void resetFaceAtlas() ; - void invalidateAtlas(bool rebuild_geom) ; - bool insertToAtlas() ; - -private: - bool mFullyLoaded; - bool mInDebug; - bool mUnremovable; - bool mInFastCacheList; - bool mForceCallbackFetch; - -protected: - std::string mLocalFileName; - - S32 mOrigWidth; - S32 mOrigHeight; - - // Override the computation of discard levels if we know the exact output size of the image. - // Used for UI textures to not decode, even if we have more data. - S32 mKnownDrawWidth; - S32 mKnownDrawHeight; - bool mKnownDrawSizeChanged ; - std::string mUrl; - - S32 mRequestedDiscardLevel; - F32 mRequestedDownloadPriority; - S32 mFetchState; - S32 mLastFetchState = -1; // DEBUG - U32 mFetchPriority; - F32 mDownloadProgress; - F32 mFetchDeltaTime; - F32 mRequestDeltaTime; - S32 mMinDiscardLevel; - S8 mDesiredDiscardLevel; // The discard level we'd LIKE to have - if we have it and there's space - S8 mMinDesiredDiscardLevel; // The minimum discard level we'd like to have - - bool mNeedsAux; // We need to decode the auxiliary channels - bool mHasAux; // We have aux channels - bool mDecodingAux; // Are we decoding high components - bool mIsRawImageValid; - bool mHasFetcher; // We've made a fecth request - bool mIsFetching; // Fetch request is active - bool mCanUseHTTP; //This texture can be fetched through http if true. - LLCore::HttpStatus mLastHttpGetStatus; // Result of the most recently completed http request for this texture. - - FTType mFTType; // What category of image is this - map tile, server bake, etc? - mutable bool mIsMissingAsset; // True if we know that there is no image asset with this image id in the database. - - typedef std::list callback_list_t; - S8 mLoadedCallbackDesiredDiscardLevel; - bool mPauseLoadedCallBacks; - callback_list_t mLoadedCallbackList; - F32 mLastCallBackActiveTime; - - LLPointer mRawImage; - S32 mRawDiscardLevel = -1; - - // Used ONLY for cloth meshes right now. Make SURE you know what you're - // doing if you use it for anything else! - djs - LLPointer mAuxRawImage; - - //keep a copy of mRawImage for some special purposes - //when mForceToSaveRawImage is set. - bool mForceToSaveRawImage ; - bool mSaveRawImage; - LLPointer mSavedRawImage; - S32 mSavedRawDiscardLevel; - S32 mDesiredSavedRawDiscardLevel; - F32 mLastReferencedSavedRawImageTime ; - F32 mKeptSavedRawImageTime ; - - //a small version of the copy of the raw image (<= 64 * 64) - LLPointer mCachedRawImage; - S32 mCachedRawDiscardLevel; - bool mCachedRawImageReady; //the rez of the mCachedRawImage reaches the upper limit. - - LLHost mTargetHost; // if invalid, just request from agent's simulator - - // Timers - LLFrameTimer mLastPacketTimer; // Time since last packet. - LLFrameTimer mStopFetchingTimer; // Time since mDecodePriority == 0.f. - - bool mInImageList; // true if image is in list (in which case don't reset priority!) - // This needs to be atomic, since it is written both in the main thread - // and in the GL image worker thread... HB - LLAtomicBool mNeedsCreateTexture; - - bool mForSculpt ; //a flag if the texture is used as sculpt data. - bool mIsFetched ; //is loaded from remote or from cache, not generated locally. - -public: - static F32 sMaxVirtualSize; //maximum possible value of mMaxVirtualSize - static LLPointer sMissingAssetImagep; // Texture to show for an image asset that is not in the database - static LLPointer sWhiteImagep; // Texture to show NOTHING (whiteness) - static LLPointer sDefaultImagep; // "Default" texture for error cases, the only case of fetched texture which is generated in local. - static LLPointer sFlatNormalImagep; // Flat normal map denoting no bumpiness on a surface - static LLPointer sDefaultIrradiancePBRp; // PBR: irradiance - - // not sure why, but something is iffy about the loading of this particular texture, use the accessor instead of accessing directly - static LLPointer sSmokeImagep; // Old "Default" translucent texture - static LLViewerFetchedTexture* getSmokeImage(); -}; - -// -//the image data is fetched from remote or from local cache -//the resolution of the texture is adjustable: depends on the view-dependent parameters. -// -class LLViewerLODTexture : public LLViewerFetchedTexture -{ -protected: - /*virtual*/ ~LLViewerLODTexture(){} - -public: - LLViewerLODTexture(const LLUUID& id, FTType f_type, const LLHost& host = LLHost(), bool usemipmaps = true); - LLViewerLODTexture(const std::string& url, FTType f_type, const LLUUID& id, bool usemipmaps = true); - - /*virtual*/ S8 getType() const; - // Process image stats to determine priority/quality requirements. - /*virtual*/ void processTextureStats(); - bool isUpdateFrozen() ; - -private: - void init(bool firstinit) ; - bool scaleDown() ; - -private: - F32 mDiscardVirtualSize; // Virtual size used to calculate desired discard - F32 mCalculatedDiscardLevel; // Last calculated discard level -}; - -// -//the image data is fetched from the media pipeline periodically -//the resolution of the texture is also adjusted by the media pipeline -// -class LLViewerMediaTexture : public LLViewerTexture -{ -protected: - /*virtual*/ ~LLViewerMediaTexture() ; - -public: - LLViewerMediaTexture(const LLUUID& id, bool usemipmaps = true, LLImageGL* gl_image = NULL) ; - - /*virtual*/ S8 getType() const; - void reinit(bool usemipmaps = true); - - bool getUseMipMaps() {return mUseMipMaps ; } - void setUseMipMaps(bool mipmap) ; - - void setPlaying(bool playing) ; - bool isPlaying() const {return mIsPlaying;} - void setMediaImpl() ; - - virtual bool isViewerMediaTexture() const { return true; } - - void initVirtualSize() ; - void invalidateMediaImpl() ; - - void addMediaToFace(LLFace* facep) ; - void removeMediaFromFace(LLFace* facep) ; - - /*virtual*/ void addFace(U32 ch, LLFace* facep) ; - /*virtual*/ void removeFace(U32 ch, LLFace* facep) ; - - /*virtual*/ F32 getMaxVirtualSize() ; -private: - void switchTexture(U32 ch, LLFace* facep) ; - bool findFaces() ; - void stopPlaying() ; - -private: - // - //an instant list, recording all faces referencing or can reference to this media texture. - //NOTE: it is NOT thread safe. - // - std::list< LLFace* > mMediaFaceList ; - - //an instant list keeping all textures which are replaced by the current media texture, - //is only used to avoid the removal of those textures from memory. - std::list< LLPointer > mTextureList ; - - LLViewerMediaImpl* mMediaImplp ; - bool mIsPlaying ; - U32 mUpdateVirtualSizeTime ; - -public: - static void updateClass() ; - static void cleanUpClass() ; - - static LLViewerMediaTexture* findMediaTexture(const LLUUID& media_id) ; - static void removeMediaImplFromTexture(const LLUUID& media_id) ; - -private: - typedef std::map< LLUUID, LLPointer > media_map_t ; - static media_map_t sMediaMap ; -}; - -//just an interface class, do not create instance from this class. -class LLViewerTextureManager -{ -private: - //make the constructor private to preclude creating instances from this class. - LLViewerTextureManager(){} - -public: - //texture pipeline tester - static LLTexturePipelineTester* sTesterp ; - - //returns NULL if tex is not a LLViewerFetchedTexture nor derived from LLViewerFetchedTexture. - static LLViewerFetchedTexture* staticCastToFetchedTexture(LLTexture* tex, bool report_error = false) ; - - // - //"find-texture" just check if the texture exists, if yes, return it, otherwise return null. - // - static void findFetchedTextures(const LLUUID& id, std::vector &output); - static void findTextures(const LLUUID& id, std::vector &output); - static LLViewerFetchedTexture* findFetchedTexture(const LLUUID& id, S32 tex_type); - static LLViewerMediaTexture* findMediaTexture(const LLUUID& id) ; - - static LLViewerMediaTexture* createMediaTexture(const LLUUID& id, bool usemipmaps = true, LLImageGL* gl_image = NULL) ; - - // - //"get-texture" will create a new texture if the texture does not exist. - // - static LLViewerMediaTexture* getMediaTexture(const LLUUID& id, bool usemipmaps = true, LLImageGL* gl_image = NULL) ; - - static LLPointer getLocalTexture(bool usemipmaps = true, bool generate_gl_tex = true); - static LLPointer getLocalTexture(const LLUUID& id, bool usemipmaps, bool generate_gl_tex = true) ; - static LLPointer getLocalTexture(const LLImageRaw* raw, bool usemipmaps) ; - static LLPointer getLocalTexture(const U32 width, const U32 height, const U8 components, bool usemipmaps, bool generate_gl_tex = true) ; - - static LLViewerFetchedTexture* getFetchedTexture(const LLImageRaw* raw, FTType type, bool usemipmaps); - - static LLViewerFetchedTexture* getFetchedTexture(const LLUUID &image_id, - FTType f_type = FTT_DEFAULT, - bool usemipmap = true, - LLViewerTexture::EBoostLevel boost_priority = LLGLTexture::BOOST_NONE, // Get the requested level immediately upon creation. - S8 texture_type = LLViewerTexture::FETCHED_TEXTURE, - LLGLint internal_format = 0, - LLGLenum primary_format = 0, - LLHost request_from_host = LLHost() - ); - - static LLViewerFetchedTexture* getFetchedTextureFromFile(const std::string& filename, - FTType f_type = FTT_LOCAL_FILE, - bool usemipmap = true, - LLViewerTexture::EBoostLevel boost_priority = LLGLTexture::BOOST_NONE, - S8 texture_type = LLViewerTexture::FETCHED_TEXTURE, - LLGLint internal_format = 0, - LLGLenum primary_format = 0, - const LLUUID& force_id = LLUUID::null - ); - - static LLViewerFetchedTexture* getFetchedTextureFromUrl(const std::string& url, - FTType f_type, - bool usemipmap = true, - LLViewerTexture::EBoostLevel boost_priority = LLGLTexture::BOOST_NONE, - S8 texture_type = LLViewerTexture::FETCHED_TEXTURE, - LLGLint internal_format = 0, - LLGLenum primary_format = 0, - const LLUUID& force_id = LLUUID::null - ); - - static LLViewerFetchedTexture* getFetchedTextureFromHost(const LLUUID& image_id, FTType f_type, LLHost host) ; - - static void init() ; - static void cleanup() ; -}; -// -//this class is used for test/debug only -//it tracks the activities of the texture pipeline -//records them, and outputs them to log files -// -class LLTexturePipelineTester : public LLMetricPerformanceTesterWithSession -{ - enum - { - MIN_LARGE_IMAGE_AREA = 262144 //512 * 512 - }; -public: - LLTexturePipelineTester() ; - ~LLTexturePipelineTester() ; - - void update(); - void updateTextureBindingStats(const LLViewerTexture* imagep) ; - void updateTextureLoadingStats(const LLViewerFetchedTexture* imagep, const LLImageRaw* raw_imagep, bool from_cache) ; - void updateGrayTextureBinding() ; - void setStablizingTime() ; - -private: - void reset() ; - void updateStablizingTime() ; - - /*virtual*/ void outputTestRecord(LLSD* sd) ; - -private: - bool mPause ; -private: - bool mUsingDefaultTexture; //if set, some textures are still gray. - - U32Bytes mTotalBytesUsed ; //total bytes of textures bound/used for the current frame. - U32Bytes mTotalBytesUsedForLargeImage ; //total bytes of textures bound/used for the current frame for images larger than 256 * 256. - U32Bytes mLastTotalBytesUsed ; //total bytes of textures bound/used for the previous frame. - U32Bytes mLastTotalBytesUsedForLargeImage ; //total bytes of textures bound/used for the previous frame for images larger than 256 * 256. - - // - //data size - // - U32Bytes mTotalBytesLoaded ; //total bytes fetched by texture pipeline - U32Bytes mTotalBytesLoadedFromCache ; //total bytes fetched by texture pipeline from local cache - U32Bytes mTotalBytesLoadedForLargeImage ; //total bytes fetched by texture pipeline for images larger than 256 * 256. - U32Bytes mTotalBytesLoadedForSculpties ; //total bytes fetched by texture pipeline for sculpties - - // - //time - //NOTE: the error tolerances of the following timers is one frame time. - // - F32 mStartFetchingTime ; - F32 mTotalGrayTime ; //total loading time when no gray textures. - F32 mTotalStablizingTime ; //total stablizing time when texture memory overflows - F32 mStartTimeLoadingSculpties ; //the start moment of loading sculpty images. - F32 mEndTimeLoadingSculpties ; //the end moment of loading sculpty images. - F32 mStartStablizingTime ; - F32 mEndStablizingTime ; - -private: - // - //The following members are used for performance analyzing - // - class LLTextureTestSession : public LLTestSession - { - public: - LLTextureTestSession() ; - /*virtual*/ ~LLTextureTestSession() ; - - void reset() ; - - F32 mTotalGrayTime ; - F32 mTotalStablizingTime ; - F32 mStartTimeLoadingSculpties ; - F32 mTotalTimeLoadingSculpties ; - - S32 mTotalBytesLoaded ; - S32 mTotalBytesLoadedFromCache ; - S32 mTotalBytesLoadedForLargeImage ; - S32 mTotalBytesLoadedForSculpties ; - - typedef struct _texture_instant_preformance_t - { - S32 mAverageBytesUsedPerSecond ; - S32 mAverageBytesUsedForLargeImagePerSecond ; - F32 mAveragePercentageBytesUsedPerSecond ; - F32 mTime ; - }texture_instant_preformance_t ; - std::vector mInstantPerformanceList ; - S32 mInstantPerformanceListCounter ; - }; - - /*virtual*/ LLMetricPerformanceTesterWithSession::LLTestSession* loadTestSession(LLSD* log) ; - /*virtual*/ void compareTestSessions(llofstream* os) ; -}; - -#endif +/** + * @file llviewertexture.h + * @brief Object for managing images and their textures + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVIEWERTEXTURE_H +#define LL_LLVIEWERTEXTURE_H + +#include "llatomic.h" +#include "llgltexture.h" +#include "lltimer.h" +#include "llframetimer.h" +#include "llhost.h" +#include "llgltypes.h" +#include "llrender.h" +#include "llmetricperformancetester.h" +#include "httpcommon.h" +#include "workqueue.h" + +#include +#include + +extern const S32Megabytes gMinVideoRam; +extern const S32Megabytes gMaxVideoRam; + +class LLFace; +class LLImageGL ; +class LLImageRaw; +class LLViewerObject; +class LLViewerTexture; +class LLViewerFetchedTexture ; +class LLViewerMediaTexture ; +class LLTexturePipelineTester ; + + +typedef void (*loaded_callback_func)( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* src_aux, S32 discard_level, bool final, void* userdata ); + +class LLFileSystem; +class LLMessageSystem; +class LLViewerMediaImpl ; +class LLVOVolume ; +struct LLTextureKey; + +class LLLoadedCallbackEntry +{ +public: + typedef std::set< LLTextureKey > source_callback_list_t; + +public: + LLLoadedCallbackEntry(loaded_callback_func cb, + S32 discard_level, + bool need_imageraw, // Needs image raw for the callback + void* userdata, + source_callback_list_t* src_callback_list, + LLViewerFetchedTexture* target, + bool pause); + ~LLLoadedCallbackEntry(); + void removeTexture(LLViewerFetchedTexture* tex) ; + + loaded_callback_func mCallback; + S32 mLastUsedDiscard; + S32 mDesiredDiscard; + bool mNeedsImageRaw; + bool mPaused; + void* mUserData; + source_callback_list_t* mSourceCallbackList; + +public: + static void cleanUpCallbackList(LLLoadedCallbackEntry::source_callback_list_t* callback_list) ; +}; + +class LLTextureBar; + +class LLViewerTexture : public LLGLTexture +{ +public: + enum + { + LOCAL_TEXTURE, + MEDIA_TEXTURE, + DYNAMIC_TEXTURE, + FETCHED_TEXTURE, + LOD_TEXTURE, + ATLAS_TEXTURE, + INVALID_TEXTURE_TYPE + }; + + typedef std::vector ll_face_list_t; + typedef std::vector ll_volume_list_t; + + +protected: + virtual ~LLViewerTexture(); + LOG_CLASS(LLViewerTexture); + +public: + static void initClass(); + static void updateClass(); + + LLViewerTexture(bool usemipmaps = true); + LLViewerTexture(const LLUUID& id, bool usemipmaps) ; + LLViewerTexture(const LLImageRaw* raw, bool usemipmaps) ; + LLViewerTexture(const U32 width, const U32 height, const U8 components, bool usemipmaps) ; + + virtual S8 getType() const; + virtual bool isMissingAsset() const ; + virtual void dump(); // debug info to LL_INFOS() + + virtual bool isViewerMediaTexture() const { return false; } + + /*virtual*/ bool bindDefaultImage(const S32 stage = 0) ; + /*virtual*/ bool bindDebugImage(const S32 stage = 0) ; + /*virtual*/ void forceImmediateUpdate() ; + /*virtual*/ bool isActiveFetching(); + + /*virtual*/ const LLUUID& getID() const { return mID; } + virtual void setBoostLevel(S32 level); + S32 getBoostLevel() { return mBoostLevel; } + void setTextureListType(S32 tex_type) { mTextureListType = tex_type; } + S32 getTextureListType() { return mTextureListType; } + + void addTextureStats(F32 virtual_size, bool needs_gltexture = true) const; + void resetTextureStats(); + void setMaxVirtualSizeResetInterval(S32 interval)const {mMaxVirtualSizeResetInterval = interval;} + void resetMaxVirtualSizeResetCounter()const {mMaxVirtualSizeResetCounter = mMaxVirtualSizeResetInterval;} + S32 getMaxVirtualSizeResetCounter() const { return mMaxVirtualSizeResetCounter; } + + virtual F32 getMaxVirtualSize() ; + + LLFrameTimer* getLastReferencedTimer() {return &mLastReferencedTimer ;} + + S32 getFullWidth() const { return mFullWidth; } + S32 getFullHeight() const { return mFullHeight; } + /*virtual*/ void setKnownDrawSize(S32 width, S32 height); + + virtual void addFace(U32 channel, LLFace* facep) ; + virtual void removeFace(U32 channel, LLFace* facep) ; + S32 getTotalNumFaces() const; + S32 getNumFaces(U32 ch) const; + const ll_face_list_t* getFaceList(U32 channel) const {llassert(channel < LLRender::NUM_TEXTURE_CHANNELS); return &mFaceList[channel];} + + virtual void addVolume(U32 channel, LLVOVolume* volumep); + virtual void removeVolume(U32 channel, LLVOVolume* volumep); + S32 getNumVolumes(U32 channel) const; + const ll_volume_list_t* getVolumeList(U32 channel) const { return &mVolumeList[channel]; } + + + virtual void setCachedRawImage(S32 discard_level, LLImageRaw* imageraw) ; + bool isLargeImage() ; + + void setParcelMedia(LLViewerMediaTexture* media) {mParcelMedia = media;} + bool hasParcelMedia() const { return mParcelMedia != NULL;} + LLViewerMediaTexture* getParcelMedia() const { return mParcelMedia;} + + /*virtual*/ void updateBindStatsForTester() ; +protected: + void cleanup() ; + void init(bool firstinit) ; + void reorganizeFaceList() ; + void reorganizeVolumeList(); + +private: + friend class LLBumpImageList; + friend class LLUIImageList; + + virtual void switchToCachedImage(); + +protected: + friend class LLViewerTextureList; + LLUUID mID; + S32 mTextureListType; // along with mID identifies where to search for this texture in TextureList + + mutable F32 mMaxVirtualSize = 0.f; // The largest virtual size of the image, in pixels - how much data to we need? + mutable S32 mMaxVirtualSizeResetCounter; + mutable S32 mMaxVirtualSizeResetInterval; + LLFrameTimer mLastReferencedTimer; + + ll_face_list_t mFaceList[LLRender::NUM_TEXTURE_CHANNELS]; //reverse pointer pointing to the faces using this image as texture + U32 mNumFaces[LLRender::NUM_TEXTURE_CHANNELS]; + LLFrameTimer mLastFaceListUpdateTimer ; + + ll_volume_list_t mVolumeList[LLRender::NUM_VOLUME_TEXTURE_CHANNELS]; + U32 mNumVolumes[LLRender::NUM_VOLUME_TEXTURE_CHANNELS]; + LLFrameTimer mLastVolumeListUpdateTimer; + + //do not use LLPointer here. + LLViewerMediaTexture* mParcelMedia ; + + LL::WorkQueue::weak_t mMainQueue; + LL::WorkQueue::weak_t mImageQueue; + +public: + static const U32 sCurrentFileVersion; + static S32 sImageCount; + static S32 sRawCount; + static S32 sAuxCount; + static LLFrameTimer sEvaluationTimer; + static F32 sDesiredDiscardBias; + static F32 sDesiredDiscardScale; + static S32 sMaxSculptRez ; + static U32 sMinLargeImageSize ; + static U32 sMaxSmallImageSize ; + static bool sFreezeImageUpdates; + static F32 sCurrentTime ; + + // estimated free memory for textures, by bias calculation + static F32 sFreeVRAMMegabytes; + + enum EDebugTexels + { + DEBUG_TEXELS_OFF, + DEBUG_TEXELS_CURRENT, + DEBUG_TEXELS_DESIRED, + DEBUG_TEXELS_FULL + }; + + static EDebugTexels sDebugTexelsMode; + + static LLPointer sNullImagep; // Null texture for non-textured objects. + static LLPointer sBlackImagep; // Texture to show NOTHING (pure black) + static LLPointer sCheckerBoardImagep; // Texture to show NOTHING (pure black) +}; + + +enum FTType +{ + FTT_UNKNOWN = -1, + FTT_DEFAULT = 0, // standard texture fetched by id. + FTT_SERVER_BAKE, // texture produced by appearance service and fetched from there. + FTT_HOST_BAKE, // old-style baked texture uploaded by viewer and fetched from avatar's host. + FTT_MAP_TILE, // tiles are fetched from map server directly. + FTT_LOCAL_FILE // fetch directly from a local file. +}; + +const std::string& fttype_to_string(const FTType& fttype); + +// +//textures are managed in gTextureList. +//raw image data is fetched from remote or local cache +//but the raw image this texture pointing to is fixed. +// +class LLViewerFetchedTexture : public LLViewerTexture +{ + friend class LLTextureBar; // debug info only + friend class LLTextureView; // debug info only + +protected: + /*virtual*/ ~LLViewerFetchedTexture(); +public: + LLViewerFetchedTexture(const LLUUID& id, FTType f_type, const LLHost& host = LLHost(), bool usemipmaps = true); + LLViewerFetchedTexture(const LLImageRaw* raw, FTType f_type, bool usemipmaps); + LLViewerFetchedTexture(const std::string& url, FTType f_type, const LLUUID& id, bool usemipmaps = true); + +public: + + struct Compare + { + // lhs < rhs + bool operator()(const LLPointer &lhs, const LLPointer &rhs) const + { + const LLViewerFetchedTexture* lhsp = (const LLViewerFetchedTexture*)lhs; + const LLViewerFetchedTexture* rhsp = (const LLViewerFetchedTexture*)rhs; + + // greater priority is "less" + const F32 lpriority = lhsp->mMaxVirtualSize; + const F32 rpriority = rhsp->mMaxVirtualSize; + if (lpriority > rpriority) // higher priority + return true; + if (lpriority < rpriority) + return false; + return lhsp < rhsp; + } + }; + +public: + /*virtual*/ S8 getType() const override; + FTType getFTType() const; + /*virtual*/ void forceImmediateUpdate() override; + /*virtual*/ void dump() override; + + // Set callbacks to get called when the image gets updated with higher + // resolution versions. + void setLoadedCallback(loaded_callback_func cb, + S32 discard_level, bool keep_imageraw, bool needs_aux, + void* userdata, LLLoadedCallbackEntry::source_callback_list_t* src_callback_list, bool pause = false); + bool hasCallbacks() { return !mLoadedCallbackList.empty(); } + void pauseLoadedCallbacks(const LLLoadedCallbackEntry::source_callback_list_t* callback_list); + void unpauseLoadedCallbacks(const LLLoadedCallbackEntry::source_callback_list_t* callback_list); + bool doLoadedCallbacks(); + void deleteCallbackEntry(const LLLoadedCallbackEntry::source_callback_list_t* callback_list); + void clearCallbackEntryList() ; + + void addToCreateTexture(); + + //call to determine if createTexture is necessary + bool preCreateTexture(S32 usename = 0); + // ONLY call from LLViewerTextureList or ImageGL background thread + bool createTexture(S32 usename = 0); + void postCreateTexture(); + void scheduleCreateTexture(); + + void destroyTexture() ; + + virtual void processTextureStats() ; + + bool needsAux() const { return mNeedsAux; } + + // Host we think might have this image, used for baked av textures. + void setTargetHost(LLHost host) { mTargetHost = host; } + LLHost getTargetHost() const { return mTargetHost; } + + void updateVirtualSize() ; + + S32 getDesiredDiscardLevel() { return mDesiredDiscardLevel; } + void setMinDiscardLevel(S32 discard) { mMinDesiredDiscardLevel = llmin(mMinDesiredDiscardLevel,(S8)discard); } + + void setBoostLevel(S32 level) override; + bool updateFetch(); + bool setDebugFetching(S32 debug_level); + bool isInDebug() const { return mInDebug; } + + void setUnremovable(bool value) { mUnremovable = value; } + bool isUnremovable() const { return mUnremovable; } + + void clearFetchedResults(); //clear all fetched results, for debug use. + + // Override the computation of discard levels if we know the exact output + // size of the image. Used for UI textures to not decode, even if we have + // more data. + /*virtual*/ void setKnownDrawSize(S32 width, S32 height) override; + + // Set the debug text of all Viewer Objects associated with this texture + // to the specified text + void setDebugText(const std::string& text); + + void setIsMissingAsset(bool is_missing = true); + /*virtual*/ bool isMissingAsset() const override { return mIsMissingAsset; } + + // returns dimensions of original image for local files (before power of two scaling) + // and returns 0 for all asset system images + S32 getOriginalWidth() { return mOrigWidth; } + S32 getOriginalHeight() { return mOrigHeight; } + + bool isInImageList() const {return mInImageList ;} + void setInImageList(bool flag) {mInImageList = flag ;} + + LLFrameTimer* getLastPacketTimer() {return &mLastPacketTimer;} + + U32 getFetchPriority() const { return mFetchPriority ;} + F32 getDownloadProgress() const {return mDownloadProgress ;} + + LLImageRaw* reloadRawImage(S8 discard_level) ; + void destroyRawImage(); + bool needsToSaveRawImage(); + + const std::string& getUrl() const {return mUrl;} + //--------------- + bool isDeleted() ; + bool isInactive() ; + bool isDeletionCandidate(); + void setDeletionCandidate() ; + void setInactive() ; + bool getUseDiscard() const { return mUseMipMaps && !mDontDiscard; } + //--------------- + + void setForSculpt(); + bool forSculpt() const {return mForSculpt;} + bool isForSculptOnly() const; + + //raw image management + void checkCachedRawSculptImage() ; + LLImageRaw* getRawImage()const { return mRawImage ;} + S32 getRawImageLevel() const {return mRawDiscardLevel;} + LLImageRaw* getCachedRawImage() const { return mCachedRawImage ;} + S32 getCachedRawImageLevel() const {return mCachedRawDiscardLevel;} + bool isCachedRawImageReady() const {return mCachedRawImageReady ;} + bool isRawImageValid()const { return mIsRawImageValid ; } + void forceToSaveRawImage(S32 desired_discard = 0, F32 kept_time = 0.f) ; + void forceToRefetchTexture(S32 desired_discard = 0, F32 kept_time = 60.f); + /*virtual*/ void setCachedRawImage(S32 discard_level, LLImageRaw* imageraw) override; + void destroySavedRawImage() ; + LLImageRaw* getSavedRawImage() ; + bool hasSavedRawImage() const ; + F32 getElapsedLastReferencedSavedRawImageTime() const ; + bool isFullyLoaded() const; + + bool hasFetcher() const { return mHasFetcher;} + bool isFetching() const { return mIsFetching;} + void setCanUseHTTP(bool can_use_http) {mCanUseHTTP = can_use_http;} + + void forceToDeleteRequest(); + void loadFromFastCache(); + void setInFastCacheList(bool in_list) { mInFastCacheList = in_list; } + bool isInFastCacheList() { return mInFastCacheList; } + + /*virtual*/bool isActiveFetching() override; //is actively in fetching by the fetching pipeline. + +protected: + /*virtual*/ void switchToCachedImage() override; + S32 getCurrentDiscardLevelForFetching() ; + +private: + void init(bool firstinit) ; + void cleanup() ; + + void saveRawImage() ; + void setCachedRawImage() ; + + //for atlas + void resetFaceAtlas() ; + void invalidateAtlas(bool rebuild_geom) ; + bool insertToAtlas() ; + +private: + bool mFullyLoaded; + bool mInDebug; + bool mUnremovable; + bool mInFastCacheList; + bool mForceCallbackFetch; + +protected: + std::string mLocalFileName; + + S32 mOrigWidth; + S32 mOrigHeight; + + // Override the computation of discard levels if we know the exact output size of the image. + // Used for UI textures to not decode, even if we have more data. + S32 mKnownDrawWidth; + S32 mKnownDrawHeight; + bool mKnownDrawSizeChanged ; + std::string mUrl; + + S32 mRequestedDiscardLevel; + F32 mRequestedDownloadPriority; + S32 mFetchState; + S32 mLastFetchState = -1; // DEBUG + U32 mFetchPriority; + F32 mDownloadProgress; + F32 mFetchDeltaTime; + F32 mRequestDeltaTime; + S32 mMinDiscardLevel; + S8 mDesiredDiscardLevel; // The discard level we'd LIKE to have - if we have it and there's space + S8 mMinDesiredDiscardLevel; // The minimum discard level we'd like to have + + bool mNeedsAux; // We need to decode the auxiliary channels + bool mHasAux; // We have aux channels + bool mDecodingAux; // Are we decoding high components + bool mIsRawImageValid; + bool mHasFetcher; // We've made a fecth request + bool mIsFetching; // Fetch request is active + bool mCanUseHTTP; //This texture can be fetched through http if true. + LLCore::HttpStatus mLastHttpGetStatus; // Result of the most recently completed http request for this texture. + + FTType mFTType; // What category of image is this - map tile, server bake, etc? + mutable bool mIsMissingAsset; // True if we know that there is no image asset with this image id in the database. + + typedef std::list callback_list_t; + S8 mLoadedCallbackDesiredDiscardLevel; + bool mPauseLoadedCallBacks; + callback_list_t mLoadedCallbackList; + F32 mLastCallBackActiveTime; + + LLPointer mRawImage; + S32 mRawDiscardLevel = -1; + + // Used ONLY for cloth meshes right now. Make SURE you know what you're + // doing if you use it for anything else! - djs + LLPointer mAuxRawImage; + + //keep a copy of mRawImage for some special purposes + //when mForceToSaveRawImage is set. + bool mForceToSaveRawImage ; + bool mSaveRawImage; + LLPointer mSavedRawImage; + S32 mSavedRawDiscardLevel; + S32 mDesiredSavedRawDiscardLevel; + F32 mLastReferencedSavedRawImageTime ; + F32 mKeptSavedRawImageTime ; + + //a small version of the copy of the raw image (<= 64 * 64) + LLPointer mCachedRawImage; + S32 mCachedRawDiscardLevel; + bool mCachedRawImageReady; //the rez of the mCachedRawImage reaches the upper limit. + + LLHost mTargetHost; // if invalid, just request from agent's simulator + + // Timers + LLFrameTimer mLastPacketTimer; // Time since last packet. + LLFrameTimer mStopFetchingTimer; // Time since mDecodePriority == 0.f. + + bool mInImageList; // true if image is in list (in which case don't reset priority!) + // This needs to be atomic, since it is written both in the main thread + // and in the GL image worker thread... HB + LLAtomicBool mNeedsCreateTexture; + + bool mForSculpt ; //a flag if the texture is used as sculpt data. + bool mIsFetched ; //is loaded from remote or from cache, not generated locally. + +public: + static F32 sMaxVirtualSize; //maximum possible value of mMaxVirtualSize + static LLPointer sMissingAssetImagep; // Texture to show for an image asset that is not in the database + static LLPointer sWhiteImagep; // Texture to show NOTHING (whiteness) + static LLPointer sDefaultImagep; // "Default" texture for error cases, the only case of fetched texture which is generated in local. + static LLPointer sFlatNormalImagep; // Flat normal map denoting no bumpiness on a surface + static LLPointer sDefaultIrradiancePBRp; // PBR: irradiance + + // not sure why, but something is iffy about the loading of this particular texture, use the accessor instead of accessing directly + static LLPointer sSmokeImagep; // Old "Default" translucent texture + static LLViewerFetchedTexture* getSmokeImage(); +}; + +// +//the image data is fetched from remote or from local cache +//the resolution of the texture is adjustable: depends on the view-dependent parameters. +// +class LLViewerLODTexture : public LLViewerFetchedTexture +{ +protected: + /*virtual*/ ~LLViewerLODTexture(){} + +public: + LLViewerLODTexture(const LLUUID& id, FTType f_type, const LLHost& host = LLHost(), bool usemipmaps = true); + LLViewerLODTexture(const std::string& url, FTType f_type, const LLUUID& id, bool usemipmaps = true); + + /*virtual*/ S8 getType() const; + // Process image stats to determine priority/quality requirements. + /*virtual*/ void processTextureStats(); + bool isUpdateFrozen() ; + +private: + void init(bool firstinit) ; + bool scaleDown() ; + +private: + F32 mDiscardVirtualSize; // Virtual size used to calculate desired discard + F32 mCalculatedDiscardLevel; // Last calculated discard level +}; + +// +//the image data is fetched from the media pipeline periodically +//the resolution of the texture is also adjusted by the media pipeline +// +class LLViewerMediaTexture : public LLViewerTexture +{ +protected: + /*virtual*/ ~LLViewerMediaTexture() ; + +public: + LLViewerMediaTexture(const LLUUID& id, bool usemipmaps = true, LLImageGL* gl_image = NULL) ; + + /*virtual*/ S8 getType() const; + void reinit(bool usemipmaps = true); + + bool getUseMipMaps() {return mUseMipMaps ; } + void setUseMipMaps(bool mipmap) ; + + void setPlaying(bool playing) ; + bool isPlaying() const {return mIsPlaying;} + void setMediaImpl() ; + + virtual bool isViewerMediaTexture() const { return true; } + + void initVirtualSize() ; + void invalidateMediaImpl() ; + + void addMediaToFace(LLFace* facep) ; + void removeMediaFromFace(LLFace* facep) ; + + /*virtual*/ void addFace(U32 ch, LLFace* facep) ; + /*virtual*/ void removeFace(U32 ch, LLFace* facep) ; + + /*virtual*/ F32 getMaxVirtualSize() ; +private: + void switchTexture(U32 ch, LLFace* facep) ; + bool findFaces() ; + void stopPlaying() ; + +private: + // + //an instant list, recording all faces referencing or can reference to this media texture. + //NOTE: it is NOT thread safe. + // + std::list< LLFace* > mMediaFaceList ; + + //an instant list keeping all textures which are replaced by the current media texture, + //is only used to avoid the removal of those textures from memory. + std::list< LLPointer > mTextureList ; + + LLViewerMediaImpl* mMediaImplp ; + bool mIsPlaying ; + U32 mUpdateVirtualSizeTime ; + +public: + static void updateClass() ; + static void cleanUpClass() ; + + static LLViewerMediaTexture* findMediaTexture(const LLUUID& media_id) ; + static void removeMediaImplFromTexture(const LLUUID& media_id) ; + +private: + typedef std::map< LLUUID, LLPointer > media_map_t ; + static media_map_t sMediaMap ; +}; + +//just an interface class, do not create instance from this class. +class LLViewerTextureManager +{ +private: + //make the constructor private to preclude creating instances from this class. + LLViewerTextureManager(){} + +public: + //texture pipeline tester + static LLTexturePipelineTester* sTesterp ; + + //returns NULL if tex is not a LLViewerFetchedTexture nor derived from LLViewerFetchedTexture. + static LLViewerFetchedTexture* staticCastToFetchedTexture(LLTexture* tex, bool report_error = false) ; + + // + //"find-texture" just check if the texture exists, if yes, return it, otherwise return null. + // + static void findFetchedTextures(const LLUUID& id, std::vector &output); + static void findTextures(const LLUUID& id, std::vector &output); + static LLViewerFetchedTexture* findFetchedTexture(const LLUUID& id, S32 tex_type); + static LLViewerMediaTexture* findMediaTexture(const LLUUID& id) ; + + static LLViewerMediaTexture* createMediaTexture(const LLUUID& id, bool usemipmaps = true, LLImageGL* gl_image = NULL) ; + + // + //"get-texture" will create a new texture if the texture does not exist. + // + static LLViewerMediaTexture* getMediaTexture(const LLUUID& id, bool usemipmaps = true, LLImageGL* gl_image = NULL) ; + + static LLPointer getLocalTexture(bool usemipmaps = true, bool generate_gl_tex = true); + static LLPointer getLocalTexture(const LLUUID& id, bool usemipmaps, bool generate_gl_tex = true) ; + static LLPointer getLocalTexture(const LLImageRaw* raw, bool usemipmaps) ; + static LLPointer getLocalTexture(const U32 width, const U32 height, const U8 components, bool usemipmaps, bool generate_gl_tex = true) ; + + static LLViewerFetchedTexture* getFetchedTexture(const LLImageRaw* raw, FTType type, bool usemipmaps); + + static LLViewerFetchedTexture* getFetchedTexture(const LLUUID &image_id, + FTType f_type = FTT_DEFAULT, + bool usemipmap = true, + LLViewerTexture::EBoostLevel boost_priority = LLGLTexture::BOOST_NONE, // Get the requested level immediately upon creation. + S8 texture_type = LLViewerTexture::FETCHED_TEXTURE, + LLGLint internal_format = 0, + LLGLenum primary_format = 0, + LLHost request_from_host = LLHost() + ); + + static LLViewerFetchedTexture* getFetchedTextureFromFile(const std::string& filename, + FTType f_type = FTT_LOCAL_FILE, + bool usemipmap = true, + LLViewerTexture::EBoostLevel boost_priority = LLGLTexture::BOOST_NONE, + S8 texture_type = LLViewerTexture::FETCHED_TEXTURE, + LLGLint internal_format = 0, + LLGLenum primary_format = 0, + const LLUUID& force_id = LLUUID::null + ); + + static LLViewerFetchedTexture* getFetchedTextureFromUrl(const std::string& url, + FTType f_type, + bool usemipmap = true, + LLViewerTexture::EBoostLevel boost_priority = LLGLTexture::BOOST_NONE, + S8 texture_type = LLViewerTexture::FETCHED_TEXTURE, + LLGLint internal_format = 0, + LLGLenum primary_format = 0, + const LLUUID& force_id = LLUUID::null + ); + + static LLViewerFetchedTexture* getFetchedTextureFromHost(const LLUUID& image_id, FTType f_type, LLHost host) ; + + static void init() ; + static void cleanup() ; +}; +// +//this class is used for test/debug only +//it tracks the activities of the texture pipeline +//records them, and outputs them to log files +// +class LLTexturePipelineTester : public LLMetricPerformanceTesterWithSession +{ + enum + { + MIN_LARGE_IMAGE_AREA = 262144 //512 * 512 + }; +public: + LLTexturePipelineTester() ; + ~LLTexturePipelineTester() ; + + void update(); + void updateTextureBindingStats(const LLViewerTexture* imagep) ; + void updateTextureLoadingStats(const LLViewerFetchedTexture* imagep, const LLImageRaw* raw_imagep, bool from_cache) ; + void updateGrayTextureBinding() ; + void setStablizingTime() ; + +private: + void reset() ; + void updateStablizingTime() ; + + /*virtual*/ void outputTestRecord(LLSD* sd) ; + +private: + bool mPause ; +private: + bool mUsingDefaultTexture; //if set, some textures are still gray. + + U32Bytes mTotalBytesUsed ; //total bytes of textures bound/used for the current frame. + U32Bytes mTotalBytesUsedForLargeImage ; //total bytes of textures bound/used for the current frame for images larger than 256 * 256. + U32Bytes mLastTotalBytesUsed ; //total bytes of textures bound/used for the previous frame. + U32Bytes mLastTotalBytesUsedForLargeImage ; //total bytes of textures bound/used for the previous frame for images larger than 256 * 256. + + // + //data size + // + U32Bytes mTotalBytesLoaded ; //total bytes fetched by texture pipeline + U32Bytes mTotalBytesLoadedFromCache ; //total bytes fetched by texture pipeline from local cache + U32Bytes mTotalBytesLoadedForLargeImage ; //total bytes fetched by texture pipeline for images larger than 256 * 256. + U32Bytes mTotalBytesLoadedForSculpties ; //total bytes fetched by texture pipeline for sculpties + + // + //time + //NOTE: the error tolerances of the following timers is one frame time. + // + F32 mStartFetchingTime ; + F32 mTotalGrayTime ; //total loading time when no gray textures. + F32 mTotalStablizingTime ; //total stablizing time when texture memory overflows + F32 mStartTimeLoadingSculpties ; //the start moment of loading sculpty images. + F32 mEndTimeLoadingSculpties ; //the end moment of loading sculpty images. + F32 mStartStablizingTime ; + F32 mEndStablizingTime ; + +private: + // + //The following members are used for performance analyzing + // + class LLTextureTestSession : public LLTestSession + { + public: + LLTextureTestSession() ; + /*virtual*/ ~LLTextureTestSession() ; + + void reset() ; + + F32 mTotalGrayTime ; + F32 mTotalStablizingTime ; + F32 mStartTimeLoadingSculpties ; + F32 mTotalTimeLoadingSculpties ; + + S32 mTotalBytesLoaded ; + S32 mTotalBytesLoadedFromCache ; + S32 mTotalBytesLoadedForLargeImage ; + S32 mTotalBytesLoadedForSculpties ; + + typedef struct _texture_instant_preformance_t + { + S32 mAverageBytesUsedPerSecond ; + S32 mAverageBytesUsedForLargeImagePerSecond ; + F32 mAveragePercentageBytesUsedPerSecond ; + F32 mTime ; + }texture_instant_preformance_t ; + std::vector mInstantPerformanceList ; + S32 mInstantPerformanceListCounter ; + }; + + /*virtual*/ LLMetricPerformanceTesterWithSession::LLTestSession* loadTestSession(LLSD* log) ; + /*virtual*/ void compareTestSessions(llofstream* os) ; +}; + +#endif diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index bbdb1812c1..0016ba6155 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -1,1777 +1,1777 @@ -/** - * @file llviewertexturelist.cpp - * @brief Object for managing the list of images within a region - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include - -#include "llviewertexturelist.h" - -#include "llgl.h" // fot gathering stats from GL -#include "llimagegl.h" -#include "llimagebmp.h" -#include "llimagej2c.h" -#include "llimagetga.h" -#include "llimagejpeg.h" -#include "llimagepng.h" -#include "llimageworker.h" - -#include "llsdserialize.h" -#include "llsys.h" -#include "llfilesystem.h" -#include "llxmltree.h" -#include "message.h" - -#include "lldrawpoolbump.h" // to init bumpmap images -#include "lltexturecache.h" -#include "lltexturefetch.h" -#include "llviewercontrol.h" -#include "llviewertexture.h" -#include "llviewermedia.h" -#include "llviewernetwork.h" -#include "llviewerregion.h" -#include "llviewerstats.h" -#include "pipeline.h" -#include "llappviewer.h" -#include "llxuiparser.h" -#include "lltracerecording.h" -#include "llviewerdisplay.h" -#include "llviewerwindow.h" -#include "llprogressview.h" - -//////////////////////////////////////////////////////////////////////////// - -void (*LLViewerTextureList::sUUIDCallback)(void **, const LLUUID&) = NULL; - -S32 LLViewerTextureList::sNumImages = 0; - -LLViewerTextureList gTextureList; - -ETexListType get_element_type(S32 priority) -{ - return (priority == LLViewerFetchedTexture::BOOST_ICON || priority == LLViewerFetchedTexture::BOOST_THUMBNAIL) ? TEX_LIST_SCALE : TEX_LIST_STANDARD; -} - -/////////////////////////////////////////////////////////////////////////////// - -LLTextureKey::LLTextureKey() -: textureId(LLUUID::null), -textureType(TEX_LIST_STANDARD) -{ -} - -LLTextureKey::LLTextureKey(LLUUID id, ETexListType tex_type) -: textureId(id), textureType(tex_type) -{ -} - -/////////////////////////////////////////////////////////////////////////////// - -LLViewerTextureList::LLViewerTextureList() - : mForceResetTextureStats(false), - mInitialized(false) -{ -} - -void LLViewerTextureList::init() -{ - mInitialized = true ; - sNumImages = 0; - doPreloadImages(); -} - - -void LLViewerTextureList::doPreloadImages() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - LL_DEBUGS("ViewerImages") << "Preloading images..." << LL_ENDL; - - llassert_always(mInitialized) ; - llassert_always(mImageList.empty()) ; - llassert_always(mUUIDMap.empty()) ; - - // Set the "missing asset" image - LLViewerFetchedTexture::sMissingAssetImagep = LLViewerTextureManager::getFetchedTextureFromFile("missing_asset.tga", FTT_LOCAL_FILE, MIPMAP_NO, LLViewerFetchedTexture::BOOST_UI); - - // Set the "white" image - LLViewerFetchedTexture::sWhiteImagep = LLViewerTextureManager::getFetchedTextureFromFile("white.tga", FTT_LOCAL_FILE, MIPMAP_NO, LLViewerFetchedTexture::BOOST_UI); - LLTexUnit::sWhiteTexture = LLViewerFetchedTexture::sWhiteImagep->getTexName(); - LLUIImageList* image_list = LLUIImageList::getInstance(); - - // Set the default flat normal map - // BLANK_OBJECT_NORMAL has a version on dataserver, but it has compression artifacts - LLViewerFetchedTexture::sFlatNormalImagep = - LLViewerTextureManager::getFetchedTextureFromFile("flatnormal.tga", - FTT_LOCAL_FILE, - MIPMAP_NO, - LLViewerFetchedTexture::BOOST_BUMP, - LLViewerTexture::FETCHED_TEXTURE, - 0, - 0, - BLANK_OBJECT_NORMAL); - - // PBR: irradiance - LLViewerFetchedTexture::sDefaultIrradiancePBRp = LLViewerTextureManager::getFetchedTextureFromFile("default_irradiance.png", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI); - - image_list->initFromFile(); - - // turn off clamping and bilinear filtering for uv picking images - //LLViewerFetchedTexture* uv_test = preloadUIImage("uv_test1.tga", LLUUID::null, false); - //uv_test->setClamp(false, false); - //uv_test->setMipFilterNearest(true, true); - //uv_test = preloadUIImage("uv_test2.tga", LLUUID::null, false); - //uv_test->setClamp(false, false); - //uv_test->setMipFilterNearest(true, true); - - LLViewerFetchedTexture* image = LLViewerTextureManager::getFetchedTextureFromFile("silhouette.j2c", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI); - if (image) - { - image->setAddressMode(LLTexUnit::TAM_WRAP); - mImagePreloads.insert(image); - } - image = LLViewerTextureManager::getFetchedTextureFromFile("world/NoEntryLines.png", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI); - if (image) - { - image->setAddressMode(LLTexUnit::TAM_WRAP); - mImagePreloads.insert(image); - } - image = LLViewerTextureManager::getFetchedTextureFromFile("world/NoEntryPassLines.png", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI); - if (image) - { - image->setAddressMode(LLTexUnit::TAM_WRAP); - mImagePreloads.insert(image); - } - image = LLViewerTextureManager::getFetchedTextureFromFile("transparent.j2c", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI, LLViewerTexture::FETCHED_TEXTURE, - 0, 0, IMG_TRANSPARENT); - if (image) - { - image->setAddressMode(LLTexUnit::TAM_WRAP); - mImagePreloads.insert(image); - } - image = LLViewerTextureManager::getFetchedTextureFromFile("alpha_gradient.tga", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI, LLViewerTexture::FETCHED_TEXTURE, - GL_ALPHA8, GL_ALPHA, IMG_ALPHA_GRAD); - if (image) - { - image->setAddressMode(LLTexUnit::TAM_CLAMP); - mImagePreloads.insert(image); - } - image = LLViewerTextureManager::getFetchedTextureFromFile("alpha_gradient_2d.j2c", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI, LLViewerTexture::FETCHED_TEXTURE, - GL_ALPHA8, GL_ALPHA, IMG_ALPHA_GRAD_2D); - if (image) - { - image->setAddressMode(LLTexUnit::TAM_CLAMP); - mImagePreloads.insert(image); - } - - LLPointer img_blak_square_tex(new LLImageRaw(2, 2, 3)); - memset(img_blak_square_tex->getData(), 0, img_blak_square_tex->getDataSize()); - LLPointer img_blak_square(new LLViewerFetchedTexture(img_blak_square_tex, FTT_DEFAULT, false)); - gBlackSquareID = img_blak_square->getID(); - img_blak_square->setUnremovable(true); - addImage(img_blak_square, TEX_LIST_STANDARD); -} - -static std::string get_texture_list_name() -{ - if (LLGridManager::getInstance()->isInProductionGrid()) - { - return gDirUtilp->getExpandedFilename(LL_PATH_CACHE, - "texture_list_" + gSavedSettings.getString("LoginLocation") + "." + gDirUtilp->getUserName() + ".xml"); - } - else - { - const std::string& grid_id_str = LLGridManager::getInstance()->getGridId(); - const std::string& grid_id_lower = utf8str_tolower(grid_id_str); - return gDirUtilp->getExpandedFilename(LL_PATH_CACHE, - "texture_list_" + gSavedSettings.getString("LoginLocation") + "." + gDirUtilp->getUserName() + "." + grid_id_lower + ".xml"); - } -} - -void LLViewerTextureList::doPrefetchImages() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - - // todo: do not load without getViewerAssetUrl() - // either fail login without caps or provide this - // in some other way, textures won't load otherwise - LLViewerFetchedTexture *imagep = findImage(DEFAULT_WATER_NORMAL, TEX_LIST_STANDARD); - if (!imagep) - { - // add it to mImagePreloads only once - imagep = LLViewerTextureManager::getFetchedTexture(DEFAULT_WATER_NORMAL, FTT_DEFAULT, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI); - if (imagep) - { - imagep->setAddressMode(LLTexUnit::TAM_WRAP); - mImagePreloads.insert(imagep); - } - } - - LLViewerTextureManager::getFetchedTexture(IMG_SHOT); - LLViewerTextureManager::getFetchedTexture(IMG_SMOKE_POOF); - LLViewerFetchedTexture::sSmokeImagep = LLViewerTextureManager::getFetchedTexture(IMG_SMOKE, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); - LLViewerFetchedTexture::sSmokeImagep->setNoDelete(); - - LLStandardBumpmap::addstandard(); - - if (LLAppViewer::instance()->getPurgeCache()) - { - // cache was purged, no point - return; - } - - // Pre-fetch textures from last logout - LLSD imagelist; - std::string filename = get_texture_list_name(); - llifstream file; - file.open(filename.c_str()); - if (file.is_open()) - { - if ( ! LLSDSerialize::fromXML(imagelist, file) ) - { - file.close(); - LL_WARNS() << "XML parse error reading texture list '" << filename << "'" << LL_ENDL; - LL_WARNS() << "Removing invalid texture list '" << filename << "'" << LL_ENDL; - LLFile::remove(filename); - return; - } - file.close(); - } - S32 texture_count = 0; - for (LLSD::array_iterator iter = imagelist.beginArray(); - iter != imagelist.endArray(); ++iter) - { - LLSD imagesd = *iter; - LLUUID uuid = imagesd["uuid"]; - S32 pixel_area = imagesd["area"]; - S32 texture_type = imagesd["type"]; - - if(LLViewerTexture::FETCHED_TEXTURE == texture_type || LLViewerTexture::LOD_TEXTURE == texture_type) - { - LLViewerFetchedTexture* image = LLViewerTextureManager::getFetchedTexture(uuid, FTT_DEFAULT, MIPMAP_TRUE, LLGLTexture::BOOST_NONE, texture_type); - if (image) - { - texture_count += 1; - image->addTextureStats((F32)pixel_area); - } - } - } - LL_DEBUGS() << "fetched " << texture_count << " images from " << filename << LL_ENDL; -} - -/////////////////////////////////////////////////////////////////////////////// - -LLViewerTextureList::~LLViewerTextureList() -{ -} - -void LLViewerTextureList::shutdown() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - // clear out preloads - mImagePreloads.clear(); - - // Write out list of currently loaded textures for precaching on startup - typedef std::set > image_area_list_t; - image_area_list_t image_area_list; - for (image_priority_list_t::iterator iter = mImageList.begin(); - iter != mImageList.end(); ++iter) - { - LLViewerFetchedTexture* image = *iter; - if (!image->hasGLTexture() || - !image->getUseDiscard() || - image->needsAux() || - !image->getTargetHost().isInvalid() || - !image->getUrl().empty() - ) - { - continue; // avoid UI, baked, and other special images - } - if(!image->getBoundRecently()) - { - continue ; - } - S32 desired = image->getDesiredDiscardLevel(); - if (desired >= 0 && desired < MAX_DISCARD_LEVEL) - { - S32 pixel_area = image->getWidth(desired) * image->getHeight(desired); - image_area_list.insert(std::make_pair(pixel_area, image)); - } - } - - LLSD imagelist; - const S32 max_count = 1000; - S32 count = 0; - S32 image_type ; - for (image_area_list_t::reverse_iterator riter = image_area_list.rbegin(); - riter != image_area_list.rend(); ++riter) - { - LLViewerFetchedTexture* image = riter->second; - image_type = (S32)image->getType() ; - imagelist[count]["area"] = riter->first; - imagelist[count]["uuid"] = image->getID(); - imagelist[count]["type"] = image_type; - if (++count >= max_count) - break; - } - - if (count > 0 && !gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "").empty()) - { - std::string filename = get_texture_list_name(); - llofstream file; - file.open(filename.c_str()); - LL_DEBUGS() << "saving " << imagelist.size() << " image list entries" << LL_ENDL; - LLSDSerialize::toPrettyXML(imagelist, file); - } - - // - // Clean up "loaded" callbacks. - // - mCallbackList.clear(); - - // Flush all of the references - mLoadingStreamList.clear(); - mCreateTextureList.clear(); - mFastCacheList.clear(); - - mUUIDMap.clear(); - - mImageList.clear(); - - mInitialized = false; //prevent loading textures again. -} - -void LLViewerTextureList::dump() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - LL_INFOS() << "LLViewerTextureList::dump()" << LL_ENDL; - for (image_priority_list_t::iterator it = mImageList.begin(); it != mImageList.end(); ++it) - { - LLViewerFetchedTexture* image = *it; - - LL_INFOS() << "priority " << image->getMaxVirtualSize() - << " boost " << image->getBoostLevel() - << " size " << image->getWidth() << "x" << image->getHeight() - << " discard " << image->getDiscardLevel() - << " desired " << image->getDesiredDiscardLevel() - << " http://asset.siva.lindenlab.com/" << image->getID() << ".texture" - << LL_ENDL; - } -} - -void LLViewerTextureList::destroyGL(bool save_state) -{ - LLImageGL::destroyGL(save_state); -} - -void LLViewerTextureList::restoreGL() -{ - llassert_always(mInitialized) ; - LLImageGL::restoreGL(); -} - -/* Vertical tab container button image IDs - Seem to not decode when running app in debug. - - const LLUUID BAD_IMG_ONE("1097dcb3-aef9-8152-f471-431d840ea89e"); - const LLUUID BAD_IMG_TWO("bea77041-5835-1661-f298-47e2d32b7a70"); - */ - -/////////////////////////////////////////////////////////////////////////////// - -LLViewerFetchedTexture* LLViewerTextureList::getImageFromFile(const std::string& filename, - FTType f_type, - bool usemipmaps, - LLViewerTexture::EBoostLevel boost_priority, - S8 texture_type, - LLGLint internal_format, - LLGLenum primary_format, - const LLUUID& force_id) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if(!mInitialized) - { - return NULL ; - } - - std::string full_path = gDirUtilp->findSkinnedFilename("textures", filename); - if (full_path.empty()) - { - LL_WARNS() << "Failed to find local image file: " << filename << LL_ENDL; - LLViewerTexture::EBoostLevel priority = LLGLTexture::BOOST_UI; - return LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT, FTT_DEFAULT, true, priority); - } - - std::string url = "file://" + full_path; - - return getImageFromUrl(url, f_type, usemipmaps, boost_priority, texture_type, internal_format, primary_format, force_id); -} - -LLViewerFetchedTexture* LLViewerTextureList::getImageFromUrl(const std::string& url, - FTType f_type, - bool usemipmaps, - LLViewerTexture::EBoostLevel boost_priority, - S8 texture_type, - LLGLint internal_format, - LLGLenum primary_format, - const LLUUID& force_id) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if(!mInitialized) - { - return NULL ; - } - - // generate UUID based on hash of filename - LLUUID new_id; - if (force_id.notNull()) - { - new_id = force_id; - } - else - { - new_id.generate(url); - } - - LLPointer imagep = findImage(new_id, get_element_type(boost_priority)); - - if (!imagep.isNull()) - { - LLViewerFetchedTexture *texture = imagep.get(); - if (texture->getUrl().empty()) - { - LL_WARNS() << "Requested texture " << new_id << " already exists but does not have a URL" << LL_ENDL; - } - else if (texture->getUrl() != url) - { - // This is not an error as long as the images really match - - // e.g. could be two avatars wearing the same outfit. - LL_DEBUGS("Avatar") << "Requested texture " << new_id - << " already exists with a different url, requested: " << url - << " current: " << texture->getUrl() << LL_ENDL; - } - - } - if (imagep.isNull()) - { - switch(texture_type) - { - case LLViewerTexture::FETCHED_TEXTURE: - imagep = new LLViewerFetchedTexture(url, f_type, new_id, usemipmaps); - break ; - case LLViewerTexture::LOD_TEXTURE: - imagep = new LLViewerLODTexture(url, f_type, new_id, usemipmaps); - break ; - default: - LL_ERRS() << "Invalid texture type " << texture_type << LL_ENDL ; - } - - if (internal_format && primary_format) - { - imagep->setExplicitFormat(internal_format, primary_format); - } - - addImage(imagep, get_element_type(boost_priority)); - - if (boost_priority != 0) - { - if (boost_priority == LLViewerFetchedTexture::BOOST_UI) - { - imagep->dontDiscard(); - } - if (boost_priority == LLViewerFetchedTexture::BOOST_ICON - || boost_priority == LLViewerFetchedTexture::BOOST_THUMBNAIL) - { - // Agent and group Icons are downloadable content, nothing manages - // icon deletion yet, so they should not persist - imagep->dontDiscard(); - imagep->forceActive(); - } - imagep->setBoostLevel(boost_priority); - } - } - - imagep->setGLTextureCreated(true); - - return imagep; -} - - -LLViewerFetchedTexture* LLViewerTextureList::getImage(const LLUUID &image_id, - FTType f_type, - bool usemipmaps, - LLViewerTexture::EBoostLevel boost_priority, - S8 texture_type, - LLGLint internal_format, - LLGLenum primary_format, - LLHost request_from_host) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if(!mInitialized) - { - return NULL ; - } - - // Return the image with ID image_id - // If the image is not found, creates new image and - // enqueues a request for transmission - - if (image_id.isNull()) - { - return (LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT, FTT_DEFAULT, true, LLGLTexture::BOOST_UI)); - } - - LLPointer imagep = findImage(image_id, get_element_type(boost_priority)); - if (!imagep.isNull()) - { - LLViewerFetchedTexture *texture = imagep.get(); - if (request_from_host.isOk() && - !texture->getTargetHost().isOk()) - { - LL_WARNS() << "Requested texture " << image_id << " already exists but does not have a host" << LL_ENDL; - } - else if (request_from_host.isOk() && - texture->getTargetHost().isOk() && - request_from_host != texture->getTargetHost()) - { - LL_WARNS() << "Requested texture " << image_id << " already exists with a different target host, requested: " - << request_from_host << " current: " << texture->getTargetHost() << LL_ENDL; - } - if (f_type != FTT_DEFAULT && imagep->getFTType() != f_type) - { - LL_WARNS() << "FTType mismatch: requested " << f_type << " image has " << imagep->getFTType() << LL_ENDL; - } - - } - if (imagep.isNull()) - { - imagep = createImage(image_id, f_type, usemipmaps, boost_priority, texture_type, internal_format, primary_format, request_from_host) ; - } - - imagep->setGLTextureCreated(true); - - return imagep; -} - -//when this function is called, there is no such texture in the gTextureList with image_id. -LLViewerFetchedTexture* LLViewerTextureList::createImage(const LLUUID &image_id, - FTType f_type, - bool usemipmaps, - LLViewerTexture::EBoostLevel boost_priority, - S8 texture_type, - LLGLint internal_format, - LLGLenum primary_format, - LLHost request_from_host) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - - LLPointer imagep ; - switch(texture_type) - { - case LLViewerTexture::FETCHED_TEXTURE: - imagep = new LLViewerFetchedTexture(image_id, f_type, request_from_host, usemipmaps); - break ; - case LLViewerTexture::LOD_TEXTURE: - imagep = new LLViewerLODTexture(image_id, f_type, request_from_host, usemipmaps); - break ; - default: - LL_ERRS() << "Invalid texture type " << texture_type << LL_ENDL ; - } - - if (internal_format && primary_format) - { - imagep->setExplicitFormat(internal_format, primary_format); - } - - addImage(imagep, get_element_type(boost_priority)); - - if (boost_priority != 0) - { - if (boost_priority == LLViewerFetchedTexture::BOOST_UI) - { - imagep->dontDiscard(); - } - if (boost_priority == LLViewerFetchedTexture::BOOST_ICON - || boost_priority == LLViewerFetchedTexture::BOOST_THUMBNAIL) - { - // Agent and group Icons are downloadable content, nothing manages - // icon deletion yet, so they should not persist. - imagep->dontDiscard(); - imagep->forceActive(); - } - imagep->setBoostLevel(boost_priority); - } - else - { - //by default, the texture can not be removed from memory even if it is not used. - //here turn this off - //if this texture should be set to NO_DELETE, call setNoDelete() afterwards. - imagep->forceActive() ; - } - - mFastCacheList.insert(imagep); - imagep->setInFastCacheList(true); - - return imagep ; -} - -void LLViewerTextureList::findTexturesByID(const LLUUID &image_id, std::vector &output) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - LLTextureKey search_key(image_id, TEX_LIST_STANDARD); - uuid_map_t::iterator iter = mUUIDMap.lower_bound(search_key); - while (iter != mUUIDMap.end() && iter->first.textureId == image_id) - { - output.push_back(iter->second); - iter++; - } -} - -LLViewerFetchedTexture *LLViewerTextureList::findImage(const LLTextureKey &search_key) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - uuid_map_t::iterator iter = mUUIDMap.find(search_key); - if (iter == mUUIDMap.end()) - return NULL; - return iter->second; -} - -LLViewerFetchedTexture *LLViewerTextureList::findImage(const LLUUID &image_id, ETexListType tex_type) -{ - return findImage(LLTextureKey(image_id, tex_type)); -} - -void LLViewerTextureList::addImageToList(LLViewerFetchedTexture *image) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - assert_main_thread(); - llassert_always(mInitialized) ; - llassert(image); - if (image->isInImageList()) - { // Flag is already set? - LL_WARNS() << "LLViewerTextureList::addImageToList - image " << image->getID() << " already in list" << LL_ENDL; - } - else - { - if (!(mImageList.insert(image)).second) - { - LL_WARNS() << "Error happens when insert image " << image->getID() << " into mImageList!" << LL_ENDL ; - } - image->setInImageList(true); - } -} - -void LLViewerTextureList::removeImageFromList(LLViewerFetchedTexture *image) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - assert_main_thread(); - llassert_always(mInitialized) ; - llassert(image); - - S32 count = 0; - if (image->isInImageList()) - { - count = mImageList.erase(image) ; - if(count != 1) - { - LL_INFOS() << "Image " << image->getID() - << " had mInImageList set but mImageList.erase() returned " << count - << LL_ENDL; - } - } - else - { // Something is wrong, image is expected in list or callers should check first - LL_INFOS() << "Calling removeImageFromList() for " << image->getID() - << " but doesn't have mInImageList set" - << " ref count is " << image->getNumRefs() - << LL_ENDL; - uuid_map_t::iterator iter = mUUIDMap.find(LLTextureKey(image->getID(), (ETexListType)image->getTextureListType())); - if(iter == mUUIDMap.end()) - { - LL_INFOS() << "Image " << image->getID() << " is also not in mUUIDMap!" << LL_ENDL ; - } - else if (iter->second != image) - { - LL_INFOS() << "Image " << image->getID() << " was in mUUIDMap but with different pointer" << LL_ENDL ; - } - else - { - LL_INFOS() << "Image " << image->getID() << " was in mUUIDMap with same pointer" << LL_ENDL ; - } - count = mImageList.erase(image) ; - llassert(count != 0); - if(count != 0) - { // it was in the list already? - LL_WARNS() << "Image " << image->getID() - << " had mInImageList false but mImageList.erase() returned " << count - << LL_ENDL; - } - } - - image->setInImageList(false) ; -} - -void LLViewerTextureList::addImage(LLViewerFetchedTexture *new_image, ETexListType tex_type) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if (!new_image) - { - return; - } - //llassert(new_image); - LLUUID image_id = new_image->getID(); - LLTextureKey key(image_id, tex_type); - - LLViewerFetchedTexture *image = findImage(key); - if (image) - { - LL_INFOS() << "Image with ID " << image_id << " already in list" << LL_ENDL; - } - sNumImages++; - - addImageToList(new_image); - mUUIDMap[key] = new_image; - new_image->setTextureListType(tex_type); -} - - -void LLViewerTextureList::deleteImage(LLViewerFetchedTexture *image) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if( image) - { - if (image->hasCallbacks()) - { - mCallbackList.erase(image); - } - LLTextureKey key(image->getID(), (ETexListType)image->getTextureListType()); - llverify(mUUIDMap.erase(key) == 1); - sNumImages--; - removeImageFromList(image); - } -} - -/////////////////////////////////////////////////////////////////////////////// - - -//////////////////////////////////////////////////////////////////////////// - -void LLViewerTextureList::dirtyImage(LLViewerFetchedTexture *image) -{ - mDirtyTextureList.insert(image); -} - -//////////////////////////////////////////////////////////////////////////// - -void LLViewerTextureList::updateImages(F32 max_time) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - static bool cleared = false; - if(gTeleportDisplay) - { - if(!cleared) - { - clearFetchingRequests(); - gPipeline.clearRebuildGroups(); - cleared = true; - } - return; - } - cleared = false; - - LLAppViewer::getTextureFetch()->setTextureBandwidth(LLTrace::get_frame_recording().getPeriodMeanPerSec(LLStatViewer::TEXTURE_NETWORK_DATA_RECEIVED).value()); - - { - using namespace LLStatViewer; - sample(NUM_IMAGES, sNumImages); - sample(NUM_RAW_IMAGES, LLImageRaw::sRawImageCount); - sample(FORMATTED_MEM, F64Bytes(LLImageFormatted::sGlobalFormattedMemory)); - } - - // make sure each call below gets at least its "fair share" of time - F32 min_time = max_time * 0.33f; - F32 remaining_time = max_time; - - //loading from fast cache - remaining_time -= updateImagesLoadingFastCache(remaining_time); - remaining_time = llmax(remaining_time, min_time); - - //dispatch to texture fetch threads - remaining_time -= updateImagesFetchTextures(remaining_time); - remaining_time = llmax(remaining_time, min_time); - - //handle results from decode threads - updateImagesCreateTextures(remaining_time); - - if (!mDirtyTextureList.empty()) - { - gPipeline.dirtyPoolObjectTextures(mDirtyTextureList); - mDirtyTextureList.clear(); - } - - bool didone = false; - for (image_list_t::iterator iter = mCallbackList.begin(); - iter != mCallbackList.end(); ) - { - //trigger loaded callbacks on local textures immediately - LLViewerFetchedTexture* image = *iter++; - if (!image->getUrl().empty()) - { - // Do stuff to handle callbacks, update priorities, etc. - didone = image->doLoadedCallbacks(); - } - else if (!didone) - { - // Do stuff to handle callbacks, update priorities, etc. - didone = image->doLoadedCallbacks(); - } - } - - updateImagesUpdateStats(); -} - -void LLViewerTextureList::clearFetchingRequests() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if (LLAppViewer::getTextureFetch()->getNumRequests() == 0) - { - return; - } - - LLAppViewer::getTextureFetch()->deleteAllRequests(); - - for (image_priority_list_t::iterator iter = mImageList.begin(); - iter != mImageList.end(); ++iter) - { - LLViewerFetchedTexture* imagep = *iter; - imagep->forceToDeleteRequest() ; - } -} - -static void touch_texture(LLViewerFetchedTexture* tex, F32 vsize) -{ - if (tex) - { - tex->addTextureStats(vsize); - } -} - -extern bool gCubeSnapshot; - -void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imagep) -{ - if (imagep->isInDebug() || imagep->isUnremovable()) - { - //update_counter--; - return; //is in debug, ignore. - } - - llassert(!gCubeSnapshot); - - static LLCachedControl bias_distance_scale(gSavedSettings, "TextureBiasDistanceScale", 1.f); - static LLCachedControl texture_scale_min(gSavedSettings, "TextureScaleMinAreaFactor", 0.04f); - static LLCachedControl texture_scale_max(gSavedSettings, "TextureScaleMaxAreaFactor", 25.f); - - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE - { - for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) - { - for (U32 fi = 0; fi < imagep->getNumFaces(i); ++fi) - { - LLFace* face = (*(imagep->getFaceList(i)))[fi]; - - if (face && face->getViewerObject() && face->getTextureEntry()) - { - F32 vsize = face->getPixelArea(); - - // Scale desired texture resolution higher or lower depending on texture scale - // - // Minimum usage examples: a 1024x1024 texture with aplhabet, runing string - // shows one letter at a time - // - // Maximum usage examples: huge chunk of terrain repeats texture - const LLTextureEntry* te = face->getTextureEntry(); - F32 min_scale = te ? llmin(fabsf(te->getScaleS()), fabsf(te->getScaleT())) : 1.f; - min_scale = llclamp(min_scale*min_scale, texture_scale_min(), texture_scale_max()); - - vsize /= min_scale; - vsize /= LLViewerTexture::sDesiredDiscardBias; - vsize /= llmax(1.f, (LLViewerTexture::sDesiredDiscardBias-1.f) * (1.f + face->getDrawable()->mDistanceWRTCamera * bias_distance_scale)); - - F32 radius; - F32 cos_angle_to_view_dir; - bool in_frustum = face->calcPixelArea(cos_angle_to_view_dir, radius); - if (!in_frustum || !face->getDrawable()->isVisible()) - { // further reduce by discard bias when off screen or occluded - vsize /= LLViewerTexture::sDesiredDiscardBias; - } - // if a GLTF material is present, ignore that face - // as far as this texture stats go, but update the GLTF material - // stats - LLFetchedGLTFMaterial* mat = te ? (LLFetchedGLTFMaterial*)te->getGLTFRenderMaterial() : nullptr; - llassert(mat == nullptr || dynamic_cast(te->getGLTFRenderMaterial()) != nullptr); - if (mat) - { - touch_texture(mat->mBaseColorTexture, vsize); - touch_texture(mat->mNormalTexture, vsize); - touch_texture(mat->mMetallicRoughnessTexture, vsize); - touch_texture(mat->mEmissiveTexture, vsize); - } - else - { - imagep->addTextureStats(vsize); - } - } - } - } - } - - //imagep->setDebugText(llformat("%.3f - %d", sqrtf(imagep->getMaxVirtualSize()), imagep->getBoostLevel())); - - F32 lazy_flush_timeout = 30.f; // stop decoding - F32 max_inactive_time = 20.f; // actually delete - S32 min_refs = 3; // 1 for mImageList, 1 for mUUIDMap, 1 for local reference - - // - // Flush formatted images using a lazy flush - // - S32 num_refs = imagep->getNumRefs(); - if (num_refs == min_refs) - { - if (imagep->getLastReferencedTimer()->getElapsedTimeF32() > lazy_flush_timeout) - { - // Remove the unused image from the image list - deleteImage(imagep); - imagep = NULL; // should destroy the image - } - return; - } - else - { - if (imagep->hasSavedRawImage()) - { - if (imagep->getElapsedLastReferencedSavedRawImageTime() > max_inactive_time) - { - imagep->destroySavedRawImage(); - } - } - - if (imagep->isDeleted()) - { - return; - } - else if (imagep->isDeletionCandidate()) - { - imagep->destroyTexture(); - return; - } - else if (imagep->isInactive()) - { - if (imagep->getLastReferencedTimer()->getElapsedTimeF32() > max_inactive_time) - { - imagep->setDeletionCandidate(); - } - return; - } - else - { - imagep->getLastReferencedTimer()->reset(); - - //reset texture state. - imagep->setInactive(); - } - } - - if (!imagep->isInImageList()) - { - return; - } - if (imagep->isInFastCacheList()) - { - return; //wait for loading from the fast cache. - } - - imagep->processTextureStats(); -} - -void LLViewerTextureList::setDebugFetching(LLViewerFetchedTexture* tex, S32 debug_level) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if(!tex->setDebugFetching(debug_level)) - { - return; - } - - const F32 DEBUG_PRIORITY = 100000.f; - removeImageFromList(tex); - tex->mMaxVirtualSize = DEBUG_PRIORITY; - addImageToList(tex); -} - -F32 LLViewerTextureList::updateImagesCreateTextures(F32 max_time) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if (gGLManager.mIsDisabled) return 0.0f; - - // - // Create GL textures for all textures that need them (images which have been - // decoded, but haven't been pushed into GL). - // - - LLTimer create_timer; - image_list_t::iterator enditer = mCreateTextureList.begin(); - for (image_list_t::iterator iter = mCreateTextureList.begin(); - iter != mCreateTextureList.end();) - { - image_list_t::iterator curiter = iter++; - enditer = iter; - LLViewerFetchedTexture *imagep = *curiter; - imagep->createTexture(); - imagep->postCreateTexture(); - - if (create_timer.getElapsedTimeF32() > max_time) - { - break; - } - } - mCreateTextureList.erase(mCreateTextureList.begin(), enditer); - return create_timer.getElapsedTimeF32(); -} - -F32 LLViewerTextureList::updateImagesLoadingFastCache(F32 max_time) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if (gGLManager.mIsDisabled) return 0.0f; - if(mFastCacheList.empty()) - { - return 0.f; - } - - // - // loading texture raw data from the fast cache directly. - // - - LLTimer timer; - image_list_t::iterator enditer = mFastCacheList.begin(); - for (image_list_t::iterator iter = mFastCacheList.begin(); - iter != mFastCacheList.end();) - { - image_list_t::iterator curiter = iter++; - enditer = iter; - LLViewerFetchedTexture *imagep = *curiter; - imagep->loadFromFastCache(); - } - mFastCacheList.erase(mFastCacheList.begin(), enditer); - return timer.getElapsedTimeF32(); -} - -void LLViewerTextureList::forceImmediateUpdate(LLViewerFetchedTexture* imagep) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if(!imagep) - { - return ; - } - if(imagep->isInImageList()) - { - removeImageFromList(imagep); - } - - imagep->processTextureStats(); - imagep->sMaxVirtualSize = LLViewerFetchedTexture::sMaxVirtualSize; - addImageToList(imagep); - - return ; -} - -F32 LLViewerTextureList::updateImagesFetchTextures(F32 max_time) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - typedef std::vector > entries_list_t; - entries_list_t entries; - - // update N textures at beginning of mImageList - U32 update_count = 0; - static const S32 MIN_UPDATE_COUNT = gSavedSettings.getS32("TextureFetchUpdateMinCount"); // default: 32 - // WIP -- dumb code here - //update MIN_UPDATE_COUNT or 5% of other textures, whichever is greater - update_count = llmax((U32) MIN_UPDATE_COUNT, (U32) mUUIDMap.size()/20); - update_count = llmin(update_count, (U32) mUUIDMap.size()); - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vtluift - copy"); - - // copy entries out of UUID map for updating - entries.reserve(update_count); - uuid_map_t::iterator iter = mUUIDMap.upper_bound(mLastUpdateKey); - while (update_count-- > 0) - { - if (iter == mUUIDMap.end()) - { - iter = mUUIDMap.begin(); - } - - if (iter->second->getGLTexture()) - { - entries.push_back(iter->second); - } - ++iter; - } - } - - LLTimer timer; - - LLPointer last_imagep = nullptr; - - for (auto& imagep : entries) - { - if (imagep->getNumRefs() > 1) // make sure this image hasn't been deleted before attempting to update (may happen as a side effect of some other image updating) - - { - updateImageDecodePriority(imagep); - imagep->updateFetch(); - } - - last_imagep = imagep; - - if (timer.getElapsedTimeF32() > max_time) - { - break; - } - } - - if (last_imagep) - { - mLastUpdateKey = LLTextureKey(last_imagep->getID(), (ETexListType)last_imagep->getTextureListType()); - } - - return timer.getElapsedTimeF32(); -} - -void LLViewerTextureList::updateImagesUpdateStats() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if (mForceResetTextureStats) - { - for (image_priority_list_t::iterator iter = mImageList.begin(); - iter != mImageList.end(); ) - { - LLViewerFetchedTexture* imagep = *iter++; - imagep->resetTextureStats(); - } - mForceResetTextureStats = false; - } -} - -void LLViewerTextureList::decodeAllImages(F32 max_time) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - LLTimer timer; - - //loading from fast cache - updateImagesLoadingFastCache(max_time); - - // Update texture stats and priorities - std::vector > image_list; - for (image_priority_list_t::iterator iter = mImageList.begin(); - iter != mImageList.end(); ) - { - LLViewerFetchedTexture* imagep = *iter++; - image_list.push_back(imagep); - imagep->setInImageList(false) ; - } - - llassert_always(image_list.size() == mImageList.size()) ; - mImageList.clear(); - for (std::vector >::iterator iter = image_list.begin(); - iter != image_list.end(); ++iter) - { - LLViewerFetchedTexture* imagep = *iter; - imagep->processTextureStats(); - addImageToList(imagep); - } - image_list.clear(); - - // Update fetch (decode) - for (image_priority_list_t::iterator iter = mImageList.begin(); - iter != mImageList.end(); ) - { - LLViewerFetchedTexture* imagep = *iter++; - imagep->updateFetch(); - } - std::shared_ptr main_queue = LLImageGLThread::sEnabledTextures ? LL::WorkQueue::getInstance("mainloop") : NULL; - // Run threads - S32 fetch_pending = 0; - while (1) - { - LLAppViewer::instance()->getTextureCache()->update(1); // unpauses the texture cache thread - LLAppViewer::instance()->getImageDecodeThread()->update(1); // unpauses the image thread - fetch_pending = LLAppViewer::instance()->getTextureFetch()->update(1); // unpauses the texture fetch thread - - if (LLImageGLThread::sEnabledTextures) - { - main_queue->runFor(std::chrono::milliseconds(1)); - fetch_pending += main_queue->size(); - } - - if (fetch_pending == 0 || timer.getElapsedTimeF32() > max_time) - { - break; - } - } - // Update fetch again - for (image_priority_list_t::iterator iter = mImageList.begin(); - iter != mImageList.end(); ) - { - LLViewerFetchedTexture* imagep = *iter++; - imagep->updateFetch(); - } - max_time -= timer.getElapsedTimeF32(); - max_time = llmax(max_time, .001f); - F32 create_time = updateImagesCreateTextures(max_time); - - LL_DEBUGS("ViewerImages") << "decodeAllImages() took " << timer.getElapsedTimeF32() << " seconds. " - << " fetch_pending " << fetch_pending - << " create_time " << create_time - << LL_ENDL; -} - -bool LLViewerTextureList::createUploadFile(LLPointer raw_image, - const std::string& out_filename, - const S32 max_image_dimentions, - const S32 min_image_dimentions) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - - LLImageDataSharedLock lock(raw_image); - - // make a copy, since convertToUploadFile scales raw image - LLPointer scale_image = new LLImageRaw( - raw_image->getData(), - raw_image->getWidth(), - raw_image->getHeight(), - raw_image->getComponents()); - - LLPointer compressedImage = LLViewerTextureList::convertToUploadFile(scale_image, max_image_dimentions); - if (compressedImage->getWidth() < min_image_dimentions || compressedImage->getHeight() < min_image_dimentions) - { - std::string reason = llformat("Images below %d x %d pixels are not allowed. Actual size: %d x %dpx", - min_image_dimentions, - min_image_dimentions, - compressedImage->getWidth(), - compressedImage->getHeight()); - compressedImage->setLastError(reason); - return false; - } - if (compressedImage.isNull()) - { - compressedImage->setLastError("Couldn't convert the image to jpeg2000."); - LL_INFOS() << "Couldn't convert to j2c, file : " << out_filename << LL_ENDL; - return false; - } - if (!compressedImage->save(out_filename)) - { - compressedImage->setLastError("Couldn't create the jpeg2000 image for upload."); - LL_INFOS() << "Couldn't create output file : " << out_filename << LL_ENDL; - return false; - } - return true; -} - -bool LLViewerTextureList::createUploadFile(const std::string& filename, - const std::string& out_filename, - const U8 codec, - const S32 max_image_dimentions, - const S32 min_image_dimentions, - bool force_square) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - try - { - // Load the image - LLPointer image = LLImageFormatted::createFromType(codec); - if (image.isNull()) - { - LL_WARNS() << "Couldn't open the image to be uploaded." << LL_ENDL; - return false; - } - if (!image->load(filename)) - { - image->setLastError("Couldn't load the image to be uploaded."); - return false; - } - // Decompress or expand it in a raw image structure - LLPointer raw_image = new LLImageRaw; - if (!image->decode(raw_image, 0.0f)) - { - image->setLastError("Couldn't decode the image to be uploaded."); - return false; - } - // Check the image constraints - if ((image->getComponents() != 3) && (image->getComponents() != 4)) - { - image->setLastError("Image files with less than 3 or more than 4 components are not supported."); - return false; - } - if (image->getWidth() < min_image_dimentions || image->getHeight() < min_image_dimentions) - { - std::string reason = llformat("Images below %d x %d pixels are not allowed. Actual size: %d x %dpx", - min_image_dimentions, - min_image_dimentions, - image->getWidth(), - image->getHeight()); - image->setLastError(reason); - return false; - } - // Convert to j2c (JPEG2000) and save the file locally - LLPointer compressedImage = convertToUploadFile(raw_image, max_image_dimentions, force_square); - if (compressedImage.isNull()) - { - image->setLastError("Couldn't convert the image to jpeg2000."); - LL_INFOS() << "Couldn't convert to j2c, file : " << filename << LL_ENDL; - return false; - } - if (!compressedImage->save(out_filename)) - { - image->setLastError("Couldn't create the jpeg2000 image for upload."); - LL_INFOS() << "Couldn't create output file : " << out_filename << LL_ENDL; - return false; - } - // Test to see if the encode and save worked - LLPointer integrity_test = new LLImageJ2C; - if (!integrity_test->loadAndValidate(out_filename)) - { - image->setLastError("The created jpeg2000 image is corrupt."); - LL_INFOS() << "Image file : " << out_filename << " is corrupt" << LL_ENDL; - return false; - } - } - catch (...) - { - LOG_UNHANDLED_EXCEPTION(""); - return false; - } - return true; -} - -// note: modifies the argument raw_image!!!! -LLPointer LLViewerTextureList::convertToUploadFile(LLPointer raw_image, const S32 max_image_dimentions, bool force_square, bool force_lossless) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - LLImageDataLock lock(raw_image); - - if (force_square) - { - S32 biggest_side = llmax(raw_image->getWidth(), raw_image->getHeight()); - S32 square_size = raw_image->biasedDimToPowerOfTwo(biggest_side, max_image_dimentions); - - raw_image->scale(square_size, square_size); - } - else - { - raw_image->biasedScaleToPowerOfTwo(max_image_dimentions); - } - LLPointer compressedImage = new LLImageJ2C(); - - if (force_lossless || - (gSavedSettings.getBOOL("LosslessJ2CUpload") && - (raw_image->getWidth() * raw_image->getHeight() <= LL_IMAGE_REZ_LOSSLESS_CUTOFF * LL_IMAGE_REZ_LOSSLESS_CUTOFF))) - { - compressedImage->setReversible(true); - } - - - if (gSavedSettings.getBOOL("Jpeg2000AdvancedCompression")) - { - // This test option will create jpeg2000 images with precincts for each level, RPCL ordering - // and PLT markers. The block size is also optionally modifiable. - // Note: the images hence created are compatible with older versions of the viewer. - // Read the blocks and precincts size settings - S32 block_size = gSavedSettings.getS32("Jpeg2000BlocksSize"); - S32 precinct_size = gSavedSettings.getS32("Jpeg2000PrecinctsSize"); - LL_INFOS() << "Advanced JPEG2000 Compression: precinct = " << precinct_size << ", block = " << block_size << LL_ENDL; - compressedImage->initEncode(*raw_image, block_size, precinct_size, 0); - } - - if (!compressedImage->encode(raw_image, 0.0f)) - { - LL_INFOS() << "convertToUploadFile : encode returns with error!!" << LL_ENDL; - // Clear up the pointer so we don't leak that one - compressedImage = NULL; - } - - return compressedImage; -} - -/////////////////////////////////////////////////////////////////////////////// - -// We've been that the asset server does not contain the requested image id. -// static -void LLViewerTextureList::processImageNotInDatabase(LLMessageSystem *msg,void **user_data) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - LLUUID image_id; - msg->getUUIDFast(_PREHASH_ImageID, _PREHASH_ID, image_id); - - LLViewerFetchedTexture* image = gTextureList.findImage( image_id, TEX_LIST_STANDARD); - if( image ) - { - LL_WARNS() << "Image not in db" << LL_ENDL; - image->setIsMissingAsset(); - } - - image = gTextureList.findImage(image_id, TEX_LIST_SCALE); - if (image) - { - LL_WARNS() << "Icon not in db" << LL_ENDL; - image->setIsMissingAsset(); - } -} - - -/////////////////////////////////////////////////////////////////////////////// - -// explicitly cleanup resources, as this is a singleton class with process -// lifetime so ability to perform std::map operations in destructor is not -// guaranteed. -void LLUIImageList::cleanUp() -{ - mUIImages.clear(); - mUITextureList.clear() ; -} - -LLUIImagePtr LLUIImageList::getUIImageByID(const LLUUID& image_id, S32 priority) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - // use id as image name - std::string image_name = image_id.asString(); - - // look for existing image - uuid_ui_image_map_t::iterator found_it = mUIImages.find(image_name); - if (found_it != mUIImages.end()) - { - return found_it->second; - } - - const bool use_mips = false; - const LLRect scale_rect = LLRect::null; - const LLRect clip_rect = LLRect::null; - return loadUIImageByID(image_id, use_mips, scale_rect, clip_rect, (LLViewerTexture::EBoostLevel)priority); -} - -LLUIImagePtr LLUIImageList::getUIImage(const std::string& image_name, S32 priority) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - // look for existing image - uuid_ui_image_map_t::iterator found_it = mUIImages.find(image_name); - if (found_it != mUIImages.end()) - { - return found_it->second; - } - - const bool use_mips = false; - const LLRect scale_rect = LLRect::null; - const LLRect clip_rect = LLRect::null; - return loadUIImageByName(image_name, image_name, use_mips, scale_rect, clip_rect, (LLViewerTexture::EBoostLevel)priority); -} - -LLUIImagePtr LLUIImageList::loadUIImageByName(const std::string& name, const std::string& filename, - bool use_mips, const LLRect& scale_rect, const LLRect& clip_rect, LLViewerTexture::EBoostLevel boost_priority, - LLUIImage::EScaleStyle scale_style) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if (boost_priority == LLGLTexture::BOOST_NONE) - { - boost_priority = LLGLTexture::BOOST_UI; - } - LLViewerFetchedTexture* imagep = LLViewerTextureManager::getFetchedTextureFromFile(filename, FTT_LOCAL_FILE, MIPMAP_NO, boost_priority); - return loadUIImage(imagep, name, use_mips, scale_rect, clip_rect, scale_style); -} - -LLUIImagePtr LLUIImageList::loadUIImageByID(const LLUUID& id, - bool use_mips, const LLRect& scale_rect, const LLRect& clip_rect, LLViewerTexture::EBoostLevel boost_priority, - LLUIImage::EScaleStyle scale_style) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if (boost_priority == LLGLTexture::BOOST_NONE) - { - boost_priority = LLGLTexture::BOOST_UI; - } - LLViewerFetchedTexture* imagep = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, MIPMAP_NO, boost_priority); - return loadUIImage(imagep, id.asString(), use_mips, scale_rect, clip_rect, scale_style); -} - -LLUIImagePtr LLUIImageList::loadUIImage(LLViewerFetchedTexture* imagep, const std::string& name, bool use_mips, const LLRect& scale_rect, const LLRect& clip_rect, - LLUIImage::EScaleStyle scale_style) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if (!imagep) return NULL; - - imagep->setAddressMode(LLTexUnit::TAM_CLAMP); - - //don't compress UI images - imagep->getGLTexture()->setAllowCompression(false); - - LLUIImagePtr new_imagep = new LLUIImage(name, imagep); - new_imagep->setScaleStyle(scale_style); - - if (imagep->getBoostLevel() != LLGLTexture::BOOST_ICON - && imagep->getBoostLevel() != LLGLTexture::BOOST_THUMBNAIL - && imagep->getBoostLevel() != LLGLTexture::BOOST_PREVIEW) - { - // Don't add downloadable content into this list - // all UI images are non-deletable and list does not support deletion - imagep->setNoDelete(); - mUIImages.insert(std::make_pair(name, new_imagep)); - mUITextureList.push_back(imagep); - } - - //Note: - //Some other textures such as ICON also through this flow to be fetched. - //But only UI textures need to set this callback. - if(imagep->getBoostLevel() == LLGLTexture::BOOST_UI) - { - LLUIImageLoadData* datap = new LLUIImageLoadData; - datap->mImageName = name; - datap->mImageScaleRegion = scale_rect; - datap->mImageClipRegion = clip_rect; - - imagep->setLoadedCallback(onUIImageLoaded, 0, false, false, datap, NULL); - } - return new_imagep; -} - -LLUIImagePtr LLUIImageList::preloadUIImage(const std::string& name, const std::string& filename, bool use_mips, const LLRect& scale_rect, const LLRect& clip_rect, LLUIImage::EScaleStyle scale_style) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - // look for existing image - uuid_ui_image_map_t::iterator found_it = mUIImages.find(name); - if (found_it != mUIImages.end()) - { - // image already loaded! - LL_ERRS() << "UI Image " << name << " already loaded." << LL_ENDL; - } - - return loadUIImageByName(name, filename, use_mips, scale_rect, clip_rect, LLGLTexture::BOOST_UI, scale_style); -} - -//static -void LLUIImageList::onUIImageLoaded( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* src_aux, S32 discard_level, bool final, void* user_data ) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - if(!success || !user_data) - { - return; - } - - LLUIImageLoadData* image_datap = (LLUIImageLoadData*)user_data; - std::string ui_image_name = image_datap->mImageName; - LLRect scale_rect = image_datap->mImageScaleRegion; - LLRect clip_rect = image_datap->mImageClipRegion; - if (final) - { - delete image_datap; - } - - LLUIImageList* instance = getInstance(); - - uuid_ui_image_map_t::iterator found_it = instance->mUIImages.find(ui_image_name); - if (found_it != instance->mUIImages.end()) - { - LLUIImagePtr imagep = found_it->second; - - // for images grabbed from local files, apply clipping rectangle to restore original dimensions - // from power-of-2 gl image - if (success && imagep.notNull() && src_vi && (src_vi->getUrl().compare(0, 7, "file://")==0)) - { - F32 full_width = (F32)src_vi->getFullWidth(); - F32 full_height = (F32)src_vi->getFullHeight(); - F32 clip_x = (F32)src_vi->getOriginalWidth() / full_width; - F32 clip_y = (F32)src_vi->getOriginalHeight() / full_height; - if (clip_rect != LLRect::null) - { - imagep->setClipRegion(LLRectf(llclamp((F32)clip_rect.mLeft / full_width, 0.f, 1.f), - llclamp((F32)clip_rect.mTop / full_height, 0.f, 1.f), - llclamp((F32)clip_rect.mRight / full_width, 0.f, 1.f), - llclamp((F32)clip_rect.mBottom / full_height, 0.f, 1.f))); - } - else - { - imagep->setClipRegion(LLRectf(0.f, clip_y, clip_x, 0.f)); - } - if (scale_rect != LLRect::null) - { - imagep->setScaleRegion( - LLRectf(llclamp((F32)scale_rect.mLeft / (F32)imagep->getWidth(), 0.f, 1.f), - llclamp((F32)scale_rect.mTop / (F32)imagep->getHeight(), 0.f, 1.f), - llclamp((F32)scale_rect.mRight / (F32)imagep->getWidth(), 0.f, 1.f), - llclamp((F32)scale_rect.mBottom / (F32)imagep->getHeight(), 0.f, 1.f))); - } - - imagep->onImageLoaded(); - } - } -} - -namespace LLInitParam -{ - template<> - struct TypeValues : public TypeValuesHelper - { - static void declareValues() - { - declare("scale_inner", LLUIImage::SCALE_INNER); - declare("scale_outer", LLUIImage::SCALE_OUTER); - } - }; -} - -struct UIImageDeclaration : public LLInitParam::Block -{ - Mandatory name; - Optional file_name; - Optional preload; - Optional scale; - Optional clip; - Optional use_mips; - Optional scale_type; - - UIImageDeclaration() - : name("name"), - file_name("file_name"), - preload("preload", false), - scale("scale"), - clip("clip"), - use_mips("use_mips", false), - scale_type("scale_type", LLUIImage::SCALE_INNER) - {} -}; - -struct UIImageDeclarations : public LLInitParam::Block -{ - Mandatory version; - Multiple textures; - - UIImageDeclarations() - : version("version"), - textures("texture") - {} -}; - -bool LLUIImageList::initFromFile() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - // Look for textures.xml in all the right places. Pass - // constraint=LLDir::ALL_SKINS because we want to overlay textures.xml - // from all the skins directories. - std::vector textures_paths = - gDirUtilp->findSkinnedFilenames(LLDir::TEXTURES, "textures.xml", LLDir::ALL_SKINS); - std::vector::const_iterator pi(textures_paths.begin()), pend(textures_paths.end()); - if (pi == pend) - { - LL_WARNS() << "No textures.xml found in skins directories" << LL_ENDL; - return false; - } - - // The first (most generic) file gets special validations - LLXMLNodePtr root; - if (!LLXMLNode::parseFile(*pi, root, NULL)) - { - LL_WARNS() << "Unable to parse UI image list file " << *pi << LL_ENDL; - return false; - } - if (!root->hasAttribute("version")) - { - LL_WARNS() << "No valid version number in UI image list file " << *pi << LL_ENDL; - return false; - } - - UIImageDeclarations images; - LLXUIParser parser; - parser.readXUI(root, images, *pi); - - // add components defined in the rest of the skin paths - while (++pi != pend) - { - LLXMLNodePtr update_root; - if (LLXMLNode::parseFile(*pi, update_root, NULL)) - { - parser.readXUI(update_root, images, *pi); - } - } - - if (!images.validateBlock()) return false; - - std::map merged_declarations; - for (LLInitParam::ParamIterator::const_iterator image_it = images.textures.begin(); - image_it != images.textures.end(); - ++image_it) - { - merged_declarations[image_it->name].overwriteFrom(*image_it); - } - - enum e_decode_pass - { - PASS_DECODE_NOW, - PASS_DECODE_LATER, - NUM_PASSES - }; - - for (S32 cur_pass = PASS_DECODE_NOW; cur_pass < NUM_PASSES; cur_pass++) - { - for (std::map::const_iterator image_it = merged_declarations.begin(); - image_it != merged_declarations.end(); - ++image_it) - { - const UIImageDeclaration& image = image_it->second; - std::string file_name = image.file_name.isProvided() ? image.file_name() : image.name(); - - // load high priority textures on first pass (to kick off decode) - enum e_decode_pass decode_pass = image.preload ? PASS_DECODE_NOW : PASS_DECODE_LATER; - if (decode_pass != cur_pass) - { - continue; - } - preloadUIImage(image.name, file_name, image.use_mips, image.scale, image.clip, image.scale_type); - } - - if (!gSavedSettings.getBOOL("NoPreload")) - { - if (cur_pass == PASS_DECODE_NOW) - { - // init fetching and decoding of preloaded images - gTextureList.decodeAllImages(9.f); - } - else - { - // decodeAllImages needs two passes to refresh stats and priorities on second pass - gTextureList.decodeAllImages(1.f); - } - } - } - return true; -} - - +/** + * @file llviewertexturelist.cpp + * @brief Object for managing the list of images within a region + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include + +#include "llviewertexturelist.h" + +#include "llgl.h" // fot gathering stats from GL +#include "llimagegl.h" +#include "llimagebmp.h" +#include "llimagej2c.h" +#include "llimagetga.h" +#include "llimagejpeg.h" +#include "llimagepng.h" +#include "llimageworker.h" + +#include "llsdserialize.h" +#include "llsys.h" +#include "llfilesystem.h" +#include "llxmltree.h" +#include "message.h" + +#include "lldrawpoolbump.h" // to init bumpmap images +#include "lltexturecache.h" +#include "lltexturefetch.h" +#include "llviewercontrol.h" +#include "llviewertexture.h" +#include "llviewermedia.h" +#include "llviewernetwork.h" +#include "llviewerregion.h" +#include "llviewerstats.h" +#include "pipeline.h" +#include "llappviewer.h" +#include "llxuiparser.h" +#include "lltracerecording.h" +#include "llviewerdisplay.h" +#include "llviewerwindow.h" +#include "llprogressview.h" + +//////////////////////////////////////////////////////////////////////////// + +void (*LLViewerTextureList::sUUIDCallback)(void **, const LLUUID&) = NULL; + +S32 LLViewerTextureList::sNumImages = 0; + +LLViewerTextureList gTextureList; + +ETexListType get_element_type(S32 priority) +{ + return (priority == LLViewerFetchedTexture::BOOST_ICON || priority == LLViewerFetchedTexture::BOOST_THUMBNAIL) ? TEX_LIST_SCALE : TEX_LIST_STANDARD; +} + +/////////////////////////////////////////////////////////////////////////////// + +LLTextureKey::LLTextureKey() +: textureId(LLUUID::null), +textureType(TEX_LIST_STANDARD) +{ +} + +LLTextureKey::LLTextureKey(LLUUID id, ETexListType tex_type) +: textureId(id), textureType(tex_type) +{ +} + +/////////////////////////////////////////////////////////////////////////////// + +LLViewerTextureList::LLViewerTextureList() + : mForceResetTextureStats(false), + mInitialized(false) +{ +} + +void LLViewerTextureList::init() +{ + mInitialized = true ; + sNumImages = 0; + doPreloadImages(); +} + + +void LLViewerTextureList::doPreloadImages() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + LL_DEBUGS("ViewerImages") << "Preloading images..." << LL_ENDL; + + llassert_always(mInitialized) ; + llassert_always(mImageList.empty()) ; + llassert_always(mUUIDMap.empty()) ; + + // Set the "missing asset" image + LLViewerFetchedTexture::sMissingAssetImagep = LLViewerTextureManager::getFetchedTextureFromFile("missing_asset.tga", FTT_LOCAL_FILE, MIPMAP_NO, LLViewerFetchedTexture::BOOST_UI); + + // Set the "white" image + LLViewerFetchedTexture::sWhiteImagep = LLViewerTextureManager::getFetchedTextureFromFile("white.tga", FTT_LOCAL_FILE, MIPMAP_NO, LLViewerFetchedTexture::BOOST_UI); + LLTexUnit::sWhiteTexture = LLViewerFetchedTexture::sWhiteImagep->getTexName(); + LLUIImageList* image_list = LLUIImageList::getInstance(); + + // Set the default flat normal map + // BLANK_OBJECT_NORMAL has a version on dataserver, but it has compression artifacts + LLViewerFetchedTexture::sFlatNormalImagep = + LLViewerTextureManager::getFetchedTextureFromFile("flatnormal.tga", + FTT_LOCAL_FILE, + MIPMAP_NO, + LLViewerFetchedTexture::BOOST_BUMP, + LLViewerTexture::FETCHED_TEXTURE, + 0, + 0, + BLANK_OBJECT_NORMAL); + + // PBR: irradiance + LLViewerFetchedTexture::sDefaultIrradiancePBRp = LLViewerTextureManager::getFetchedTextureFromFile("default_irradiance.png", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI); + + image_list->initFromFile(); + + // turn off clamping and bilinear filtering for uv picking images + //LLViewerFetchedTexture* uv_test = preloadUIImage("uv_test1.tga", LLUUID::null, false); + //uv_test->setClamp(false, false); + //uv_test->setMipFilterNearest(true, true); + //uv_test = preloadUIImage("uv_test2.tga", LLUUID::null, false); + //uv_test->setClamp(false, false); + //uv_test->setMipFilterNearest(true, true); + + LLViewerFetchedTexture* image = LLViewerTextureManager::getFetchedTextureFromFile("silhouette.j2c", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI); + if (image) + { + image->setAddressMode(LLTexUnit::TAM_WRAP); + mImagePreloads.insert(image); + } + image = LLViewerTextureManager::getFetchedTextureFromFile("world/NoEntryLines.png", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI); + if (image) + { + image->setAddressMode(LLTexUnit::TAM_WRAP); + mImagePreloads.insert(image); + } + image = LLViewerTextureManager::getFetchedTextureFromFile("world/NoEntryPassLines.png", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI); + if (image) + { + image->setAddressMode(LLTexUnit::TAM_WRAP); + mImagePreloads.insert(image); + } + image = LLViewerTextureManager::getFetchedTextureFromFile("transparent.j2c", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI, LLViewerTexture::FETCHED_TEXTURE, + 0, 0, IMG_TRANSPARENT); + if (image) + { + image->setAddressMode(LLTexUnit::TAM_WRAP); + mImagePreloads.insert(image); + } + image = LLViewerTextureManager::getFetchedTextureFromFile("alpha_gradient.tga", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI, LLViewerTexture::FETCHED_TEXTURE, + GL_ALPHA8, GL_ALPHA, IMG_ALPHA_GRAD); + if (image) + { + image->setAddressMode(LLTexUnit::TAM_CLAMP); + mImagePreloads.insert(image); + } + image = LLViewerTextureManager::getFetchedTextureFromFile("alpha_gradient_2d.j2c", FTT_LOCAL_FILE, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI, LLViewerTexture::FETCHED_TEXTURE, + GL_ALPHA8, GL_ALPHA, IMG_ALPHA_GRAD_2D); + if (image) + { + image->setAddressMode(LLTexUnit::TAM_CLAMP); + mImagePreloads.insert(image); + } + + LLPointer img_blak_square_tex(new LLImageRaw(2, 2, 3)); + memset(img_blak_square_tex->getData(), 0, img_blak_square_tex->getDataSize()); + LLPointer img_blak_square(new LLViewerFetchedTexture(img_blak_square_tex, FTT_DEFAULT, false)); + gBlackSquareID = img_blak_square->getID(); + img_blak_square->setUnremovable(true); + addImage(img_blak_square, TEX_LIST_STANDARD); +} + +static std::string get_texture_list_name() +{ + if (LLGridManager::getInstance()->isInProductionGrid()) + { + return gDirUtilp->getExpandedFilename(LL_PATH_CACHE, + "texture_list_" + gSavedSettings.getString("LoginLocation") + "." + gDirUtilp->getUserName() + ".xml"); + } + else + { + const std::string& grid_id_str = LLGridManager::getInstance()->getGridId(); + const std::string& grid_id_lower = utf8str_tolower(grid_id_str); + return gDirUtilp->getExpandedFilename(LL_PATH_CACHE, + "texture_list_" + gSavedSettings.getString("LoginLocation") + "." + gDirUtilp->getUserName() + "." + grid_id_lower + ".xml"); + } +} + +void LLViewerTextureList::doPrefetchImages() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + + // todo: do not load without getViewerAssetUrl() + // either fail login without caps or provide this + // in some other way, textures won't load otherwise + LLViewerFetchedTexture *imagep = findImage(DEFAULT_WATER_NORMAL, TEX_LIST_STANDARD); + if (!imagep) + { + // add it to mImagePreloads only once + imagep = LLViewerTextureManager::getFetchedTexture(DEFAULT_WATER_NORMAL, FTT_DEFAULT, MIPMAP_YES, LLViewerFetchedTexture::BOOST_UI); + if (imagep) + { + imagep->setAddressMode(LLTexUnit::TAM_WRAP); + mImagePreloads.insert(imagep); + } + } + + LLViewerTextureManager::getFetchedTexture(IMG_SHOT); + LLViewerTextureManager::getFetchedTexture(IMG_SMOKE_POOF); + LLViewerFetchedTexture::sSmokeImagep = LLViewerTextureManager::getFetchedTexture(IMG_SMOKE, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); + LLViewerFetchedTexture::sSmokeImagep->setNoDelete(); + + LLStandardBumpmap::addstandard(); + + if (LLAppViewer::instance()->getPurgeCache()) + { + // cache was purged, no point + return; + } + + // Pre-fetch textures from last logout + LLSD imagelist; + std::string filename = get_texture_list_name(); + llifstream file; + file.open(filename.c_str()); + if (file.is_open()) + { + if ( ! LLSDSerialize::fromXML(imagelist, file) ) + { + file.close(); + LL_WARNS() << "XML parse error reading texture list '" << filename << "'" << LL_ENDL; + LL_WARNS() << "Removing invalid texture list '" << filename << "'" << LL_ENDL; + LLFile::remove(filename); + return; + } + file.close(); + } + S32 texture_count = 0; + for (LLSD::array_iterator iter = imagelist.beginArray(); + iter != imagelist.endArray(); ++iter) + { + LLSD imagesd = *iter; + LLUUID uuid = imagesd["uuid"]; + S32 pixel_area = imagesd["area"]; + S32 texture_type = imagesd["type"]; + + if(LLViewerTexture::FETCHED_TEXTURE == texture_type || LLViewerTexture::LOD_TEXTURE == texture_type) + { + LLViewerFetchedTexture* image = LLViewerTextureManager::getFetchedTexture(uuid, FTT_DEFAULT, MIPMAP_TRUE, LLGLTexture::BOOST_NONE, texture_type); + if (image) + { + texture_count += 1; + image->addTextureStats((F32)pixel_area); + } + } + } + LL_DEBUGS() << "fetched " << texture_count << " images from " << filename << LL_ENDL; +} + +/////////////////////////////////////////////////////////////////////////////// + +LLViewerTextureList::~LLViewerTextureList() +{ +} + +void LLViewerTextureList::shutdown() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + // clear out preloads + mImagePreloads.clear(); + + // Write out list of currently loaded textures for precaching on startup + typedef std::set > image_area_list_t; + image_area_list_t image_area_list; + for (image_priority_list_t::iterator iter = mImageList.begin(); + iter != mImageList.end(); ++iter) + { + LLViewerFetchedTexture* image = *iter; + if (!image->hasGLTexture() || + !image->getUseDiscard() || + image->needsAux() || + !image->getTargetHost().isInvalid() || + !image->getUrl().empty() + ) + { + continue; // avoid UI, baked, and other special images + } + if(!image->getBoundRecently()) + { + continue ; + } + S32 desired = image->getDesiredDiscardLevel(); + if (desired >= 0 && desired < MAX_DISCARD_LEVEL) + { + S32 pixel_area = image->getWidth(desired) * image->getHeight(desired); + image_area_list.insert(std::make_pair(pixel_area, image)); + } + } + + LLSD imagelist; + const S32 max_count = 1000; + S32 count = 0; + S32 image_type ; + for (image_area_list_t::reverse_iterator riter = image_area_list.rbegin(); + riter != image_area_list.rend(); ++riter) + { + LLViewerFetchedTexture* image = riter->second; + image_type = (S32)image->getType() ; + imagelist[count]["area"] = riter->first; + imagelist[count]["uuid"] = image->getID(); + imagelist[count]["type"] = image_type; + if (++count >= max_count) + break; + } + + if (count > 0 && !gDirUtilp->getExpandedFilename(LL_PATH_CACHE, "").empty()) + { + std::string filename = get_texture_list_name(); + llofstream file; + file.open(filename.c_str()); + LL_DEBUGS() << "saving " << imagelist.size() << " image list entries" << LL_ENDL; + LLSDSerialize::toPrettyXML(imagelist, file); + } + + // + // Clean up "loaded" callbacks. + // + mCallbackList.clear(); + + // Flush all of the references + mLoadingStreamList.clear(); + mCreateTextureList.clear(); + mFastCacheList.clear(); + + mUUIDMap.clear(); + + mImageList.clear(); + + mInitialized = false; //prevent loading textures again. +} + +void LLViewerTextureList::dump() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + LL_INFOS() << "LLViewerTextureList::dump()" << LL_ENDL; + for (image_priority_list_t::iterator it = mImageList.begin(); it != mImageList.end(); ++it) + { + LLViewerFetchedTexture* image = *it; + + LL_INFOS() << "priority " << image->getMaxVirtualSize() + << " boost " << image->getBoostLevel() + << " size " << image->getWidth() << "x" << image->getHeight() + << " discard " << image->getDiscardLevel() + << " desired " << image->getDesiredDiscardLevel() + << " http://asset.siva.lindenlab.com/" << image->getID() << ".texture" + << LL_ENDL; + } +} + +void LLViewerTextureList::destroyGL(bool save_state) +{ + LLImageGL::destroyGL(save_state); +} + +void LLViewerTextureList::restoreGL() +{ + llassert_always(mInitialized) ; + LLImageGL::restoreGL(); +} + +/* Vertical tab container button image IDs + Seem to not decode when running app in debug. + + const LLUUID BAD_IMG_ONE("1097dcb3-aef9-8152-f471-431d840ea89e"); + const LLUUID BAD_IMG_TWO("bea77041-5835-1661-f298-47e2d32b7a70"); + */ + +/////////////////////////////////////////////////////////////////////////////// + +LLViewerFetchedTexture* LLViewerTextureList::getImageFromFile(const std::string& filename, + FTType f_type, + bool usemipmaps, + LLViewerTexture::EBoostLevel boost_priority, + S8 texture_type, + LLGLint internal_format, + LLGLenum primary_format, + const LLUUID& force_id) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if(!mInitialized) + { + return NULL ; + } + + std::string full_path = gDirUtilp->findSkinnedFilename("textures", filename); + if (full_path.empty()) + { + LL_WARNS() << "Failed to find local image file: " << filename << LL_ENDL; + LLViewerTexture::EBoostLevel priority = LLGLTexture::BOOST_UI; + return LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT, FTT_DEFAULT, true, priority); + } + + std::string url = "file://" + full_path; + + return getImageFromUrl(url, f_type, usemipmaps, boost_priority, texture_type, internal_format, primary_format, force_id); +} + +LLViewerFetchedTexture* LLViewerTextureList::getImageFromUrl(const std::string& url, + FTType f_type, + bool usemipmaps, + LLViewerTexture::EBoostLevel boost_priority, + S8 texture_type, + LLGLint internal_format, + LLGLenum primary_format, + const LLUUID& force_id) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if(!mInitialized) + { + return NULL ; + } + + // generate UUID based on hash of filename + LLUUID new_id; + if (force_id.notNull()) + { + new_id = force_id; + } + else + { + new_id.generate(url); + } + + LLPointer imagep = findImage(new_id, get_element_type(boost_priority)); + + if (!imagep.isNull()) + { + LLViewerFetchedTexture *texture = imagep.get(); + if (texture->getUrl().empty()) + { + LL_WARNS() << "Requested texture " << new_id << " already exists but does not have a URL" << LL_ENDL; + } + else if (texture->getUrl() != url) + { + // This is not an error as long as the images really match - + // e.g. could be two avatars wearing the same outfit. + LL_DEBUGS("Avatar") << "Requested texture " << new_id + << " already exists with a different url, requested: " << url + << " current: " << texture->getUrl() << LL_ENDL; + } + + } + if (imagep.isNull()) + { + switch(texture_type) + { + case LLViewerTexture::FETCHED_TEXTURE: + imagep = new LLViewerFetchedTexture(url, f_type, new_id, usemipmaps); + break ; + case LLViewerTexture::LOD_TEXTURE: + imagep = new LLViewerLODTexture(url, f_type, new_id, usemipmaps); + break ; + default: + LL_ERRS() << "Invalid texture type " << texture_type << LL_ENDL ; + } + + if (internal_format && primary_format) + { + imagep->setExplicitFormat(internal_format, primary_format); + } + + addImage(imagep, get_element_type(boost_priority)); + + if (boost_priority != 0) + { + if (boost_priority == LLViewerFetchedTexture::BOOST_UI) + { + imagep->dontDiscard(); + } + if (boost_priority == LLViewerFetchedTexture::BOOST_ICON + || boost_priority == LLViewerFetchedTexture::BOOST_THUMBNAIL) + { + // Agent and group Icons are downloadable content, nothing manages + // icon deletion yet, so they should not persist + imagep->dontDiscard(); + imagep->forceActive(); + } + imagep->setBoostLevel(boost_priority); + } + } + + imagep->setGLTextureCreated(true); + + return imagep; +} + + +LLViewerFetchedTexture* LLViewerTextureList::getImage(const LLUUID &image_id, + FTType f_type, + bool usemipmaps, + LLViewerTexture::EBoostLevel boost_priority, + S8 texture_type, + LLGLint internal_format, + LLGLenum primary_format, + LLHost request_from_host) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if(!mInitialized) + { + return NULL ; + } + + // Return the image with ID image_id + // If the image is not found, creates new image and + // enqueues a request for transmission + + if (image_id.isNull()) + { + return (LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT, FTT_DEFAULT, true, LLGLTexture::BOOST_UI)); + } + + LLPointer imagep = findImage(image_id, get_element_type(boost_priority)); + if (!imagep.isNull()) + { + LLViewerFetchedTexture *texture = imagep.get(); + if (request_from_host.isOk() && + !texture->getTargetHost().isOk()) + { + LL_WARNS() << "Requested texture " << image_id << " already exists but does not have a host" << LL_ENDL; + } + else if (request_from_host.isOk() && + texture->getTargetHost().isOk() && + request_from_host != texture->getTargetHost()) + { + LL_WARNS() << "Requested texture " << image_id << " already exists with a different target host, requested: " + << request_from_host << " current: " << texture->getTargetHost() << LL_ENDL; + } + if (f_type != FTT_DEFAULT && imagep->getFTType() != f_type) + { + LL_WARNS() << "FTType mismatch: requested " << f_type << " image has " << imagep->getFTType() << LL_ENDL; + } + + } + if (imagep.isNull()) + { + imagep = createImage(image_id, f_type, usemipmaps, boost_priority, texture_type, internal_format, primary_format, request_from_host) ; + } + + imagep->setGLTextureCreated(true); + + return imagep; +} + +//when this function is called, there is no such texture in the gTextureList with image_id. +LLViewerFetchedTexture* LLViewerTextureList::createImage(const LLUUID &image_id, + FTType f_type, + bool usemipmaps, + LLViewerTexture::EBoostLevel boost_priority, + S8 texture_type, + LLGLint internal_format, + LLGLenum primary_format, + LLHost request_from_host) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + + LLPointer imagep ; + switch(texture_type) + { + case LLViewerTexture::FETCHED_TEXTURE: + imagep = new LLViewerFetchedTexture(image_id, f_type, request_from_host, usemipmaps); + break ; + case LLViewerTexture::LOD_TEXTURE: + imagep = new LLViewerLODTexture(image_id, f_type, request_from_host, usemipmaps); + break ; + default: + LL_ERRS() << "Invalid texture type " << texture_type << LL_ENDL ; + } + + if (internal_format && primary_format) + { + imagep->setExplicitFormat(internal_format, primary_format); + } + + addImage(imagep, get_element_type(boost_priority)); + + if (boost_priority != 0) + { + if (boost_priority == LLViewerFetchedTexture::BOOST_UI) + { + imagep->dontDiscard(); + } + if (boost_priority == LLViewerFetchedTexture::BOOST_ICON + || boost_priority == LLViewerFetchedTexture::BOOST_THUMBNAIL) + { + // Agent and group Icons are downloadable content, nothing manages + // icon deletion yet, so they should not persist. + imagep->dontDiscard(); + imagep->forceActive(); + } + imagep->setBoostLevel(boost_priority); + } + else + { + //by default, the texture can not be removed from memory even if it is not used. + //here turn this off + //if this texture should be set to NO_DELETE, call setNoDelete() afterwards. + imagep->forceActive() ; + } + + mFastCacheList.insert(imagep); + imagep->setInFastCacheList(true); + + return imagep ; +} + +void LLViewerTextureList::findTexturesByID(const LLUUID &image_id, std::vector &output) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + LLTextureKey search_key(image_id, TEX_LIST_STANDARD); + uuid_map_t::iterator iter = mUUIDMap.lower_bound(search_key); + while (iter != mUUIDMap.end() && iter->first.textureId == image_id) + { + output.push_back(iter->second); + iter++; + } +} + +LLViewerFetchedTexture *LLViewerTextureList::findImage(const LLTextureKey &search_key) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + uuid_map_t::iterator iter = mUUIDMap.find(search_key); + if (iter == mUUIDMap.end()) + return NULL; + return iter->second; +} + +LLViewerFetchedTexture *LLViewerTextureList::findImage(const LLUUID &image_id, ETexListType tex_type) +{ + return findImage(LLTextureKey(image_id, tex_type)); +} + +void LLViewerTextureList::addImageToList(LLViewerFetchedTexture *image) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + assert_main_thread(); + llassert_always(mInitialized) ; + llassert(image); + if (image->isInImageList()) + { // Flag is already set? + LL_WARNS() << "LLViewerTextureList::addImageToList - image " << image->getID() << " already in list" << LL_ENDL; + } + else + { + if (!(mImageList.insert(image)).second) + { + LL_WARNS() << "Error happens when insert image " << image->getID() << " into mImageList!" << LL_ENDL ; + } + image->setInImageList(true); + } +} + +void LLViewerTextureList::removeImageFromList(LLViewerFetchedTexture *image) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + assert_main_thread(); + llassert_always(mInitialized) ; + llassert(image); + + S32 count = 0; + if (image->isInImageList()) + { + count = mImageList.erase(image) ; + if(count != 1) + { + LL_INFOS() << "Image " << image->getID() + << " had mInImageList set but mImageList.erase() returned " << count + << LL_ENDL; + } + } + else + { // Something is wrong, image is expected in list or callers should check first + LL_INFOS() << "Calling removeImageFromList() for " << image->getID() + << " but doesn't have mInImageList set" + << " ref count is " << image->getNumRefs() + << LL_ENDL; + uuid_map_t::iterator iter = mUUIDMap.find(LLTextureKey(image->getID(), (ETexListType)image->getTextureListType())); + if(iter == mUUIDMap.end()) + { + LL_INFOS() << "Image " << image->getID() << " is also not in mUUIDMap!" << LL_ENDL ; + } + else if (iter->second != image) + { + LL_INFOS() << "Image " << image->getID() << " was in mUUIDMap but with different pointer" << LL_ENDL ; + } + else + { + LL_INFOS() << "Image " << image->getID() << " was in mUUIDMap with same pointer" << LL_ENDL ; + } + count = mImageList.erase(image) ; + llassert(count != 0); + if(count != 0) + { // it was in the list already? + LL_WARNS() << "Image " << image->getID() + << " had mInImageList false but mImageList.erase() returned " << count + << LL_ENDL; + } + } + + image->setInImageList(false) ; +} + +void LLViewerTextureList::addImage(LLViewerFetchedTexture *new_image, ETexListType tex_type) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if (!new_image) + { + return; + } + //llassert(new_image); + LLUUID image_id = new_image->getID(); + LLTextureKey key(image_id, tex_type); + + LLViewerFetchedTexture *image = findImage(key); + if (image) + { + LL_INFOS() << "Image with ID " << image_id << " already in list" << LL_ENDL; + } + sNumImages++; + + addImageToList(new_image); + mUUIDMap[key] = new_image; + new_image->setTextureListType(tex_type); +} + + +void LLViewerTextureList::deleteImage(LLViewerFetchedTexture *image) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if( image) + { + if (image->hasCallbacks()) + { + mCallbackList.erase(image); + } + LLTextureKey key(image->getID(), (ETexListType)image->getTextureListType()); + llverify(mUUIDMap.erase(key) == 1); + sNumImages--; + removeImageFromList(image); + } +} + +/////////////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////////// + +void LLViewerTextureList::dirtyImage(LLViewerFetchedTexture *image) +{ + mDirtyTextureList.insert(image); +} + +//////////////////////////////////////////////////////////////////////////// + +void LLViewerTextureList::updateImages(F32 max_time) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + static bool cleared = false; + if(gTeleportDisplay) + { + if(!cleared) + { + clearFetchingRequests(); + gPipeline.clearRebuildGroups(); + cleared = true; + } + return; + } + cleared = false; + + LLAppViewer::getTextureFetch()->setTextureBandwidth(LLTrace::get_frame_recording().getPeriodMeanPerSec(LLStatViewer::TEXTURE_NETWORK_DATA_RECEIVED).value()); + + { + using namespace LLStatViewer; + sample(NUM_IMAGES, sNumImages); + sample(NUM_RAW_IMAGES, LLImageRaw::sRawImageCount); + sample(FORMATTED_MEM, F64Bytes(LLImageFormatted::sGlobalFormattedMemory)); + } + + // make sure each call below gets at least its "fair share" of time + F32 min_time = max_time * 0.33f; + F32 remaining_time = max_time; + + //loading from fast cache + remaining_time -= updateImagesLoadingFastCache(remaining_time); + remaining_time = llmax(remaining_time, min_time); + + //dispatch to texture fetch threads + remaining_time -= updateImagesFetchTextures(remaining_time); + remaining_time = llmax(remaining_time, min_time); + + //handle results from decode threads + updateImagesCreateTextures(remaining_time); + + if (!mDirtyTextureList.empty()) + { + gPipeline.dirtyPoolObjectTextures(mDirtyTextureList); + mDirtyTextureList.clear(); + } + + bool didone = false; + for (image_list_t::iterator iter = mCallbackList.begin(); + iter != mCallbackList.end(); ) + { + //trigger loaded callbacks on local textures immediately + LLViewerFetchedTexture* image = *iter++; + if (!image->getUrl().empty()) + { + // Do stuff to handle callbacks, update priorities, etc. + didone = image->doLoadedCallbacks(); + } + else if (!didone) + { + // Do stuff to handle callbacks, update priorities, etc. + didone = image->doLoadedCallbacks(); + } + } + + updateImagesUpdateStats(); +} + +void LLViewerTextureList::clearFetchingRequests() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if (LLAppViewer::getTextureFetch()->getNumRequests() == 0) + { + return; + } + + LLAppViewer::getTextureFetch()->deleteAllRequests(); + + for (image_priority_list_t::iterator iter = mImageList.begin(); + iter != mImageList.end(); ++iter) + { + LLViewerFetchedTexture* imagep = *iter; + imagep->forceToDeleteRequest() ; + } +} + +static void touch_texture(LLViewerFetchedTexture* tex, F32 vsize) +{ + if (tex) + { + tex->addTextureStats(vsize); + } +} + +extern bool gCubeSnapshot; + +void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imagep) +{ + if (imagep->isInDebug() || imagep->isUnremovable()) + { + //update_counter--; + return; //is in debug, ignore. + } + + llassert(!gCubeSnapshot); + + static LLCachedControl bias_distance_scale(gSavedSettings, "TextureBiasDistanceScale", 1.f); + static LLCachedControl texture_scale_min(gSavedSettings, "TextureScaleMinAreaFactor", 0.04f); + static LLCachedControl texture_scale_max(gSavedSettings, "TextureScaleMaxAreaFactor", 25.f); + + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE + { + for (U32 i = 0; i < LLRender::NUM_TEXTURE_CHANNELS; ++i) + { + for (U32 fi = 0; fi < imagep->getNumFaces(i); ++fi) + { + LLFace* face = (*(imagep->getFaceList(i)))[fi]; + + if (face && face->getViewerObject() && face->getTextureEntry()) + { + F32 vsize = face->getPixelArea(); + + // Scale desired texture resolution higher or lower depending on texture scale + // + // Minimum usage examples: a 1024x1024 texture with aplhabet, runing string + // shows one letter at a time + // + // Maximum usage examples: huge chunk of terrain repeats texture + const LLTextureEntry* te = face->getTextureEntry(); + F32 min_scale = te ? llmin(fabsf(te->getScaleS()), fabsf(te->getScaleT())) : 1.f; + min_scale = llclamp(min_scale*min_scale, texture_scale_min(), texture_scale_max()); + + vsize /= min_scale; + vsize /= LLViewerTexture::sDesiredDiscardBias; + vsize /= llmax(1.f, (LLViewerTexture::sDesiredDiscardBias-1.f) * (1.f + face->getDrawable()->mDistanceWRTCamera * bias_distance_scale)); + + F32 radius; + F32 cos_angle_to_view_dir; + bool in_frustum = face->calcPixelArea(cos_angle_to_view_dir, radius); + if (!in_frustum || !face->getDrawable()->isVisible()) + { // further reduce by discard bias when off screen or occluded + vsize /= LLViewerTexture::sDesiredDiscardBias; + } + // if a GLTF material is present, ignore that face + // as far as this texture stats go, but update the GLTF material + // stats + LLFetchedGLTFMaterial* mat = te ? (LLFetchedGLTFMaterial*)te->getGLTFRenderMaterial() : nullptr; + llassert(mat == nullptr || dynamic_cast(te->getGLTFRenderMaterial()) != nullptr); + if (mat) + { + touch_texture(mat->mBaseColorTexture, vsize); + touch_texture(mat->mNormalTexture, vsize); + touch_texture(mat->mMetallicRoughnessTexture, vsize); + touch_texture(mat->mEmissiveTexture, vsize); + } + else + { + imagep->addTextureStats(vsize); + } + } + } + } + } + + //imagep->setDebugText(llformat("%.3f - %d", sqrtf(imagep->getMaxVirtualSize()), imagep->getBoostLevel())); + + F32 lazy_flush_timeout = 30.f; // stop decoding + F32 max_inactive_time = 20.f; // actually delete + S32 min_refs = 3; // 1 for mImageList, 1 for mUUIDMap, 1 for local reference + + // + // Flush formatted images using a lazy flush + // + S32 num_refs = imagep->getNumRefs(); + if (num_refs == min_refs) + { + if (imagep->getLastReferencedTimer()->getElapsedTimeF32() > lazy_flush_timeout) + { + // Remove the unused image from the image list + deleteImage(imagep); + imagep = NULL; // should destroy the image + } + return; + } + else + { + if (imagep->hasSavedRawImage()) + { + if (imagep->getElapsedLastReferencedSavedRawImageTime() > max_inactive_time) + { + imagep->destroySavedRawImage(); + } + } + + if (imagep->isDeleted()) + { + return; + } + else if (imagep->isDeletionCandidate()) + { + imagep->destroyTexture(); + return; + } + else if (imagep->isInactive()) + { + if (imagep->getLastReferencedTimer()->getElapsedTimeF32() > max_inactive_time) + { + imagep->setDeletionCandidate(); + } + return; + } + else + { + imagep->getLastReferencedTimer()->reset(); + + //reset texture state. + imagep->setInactive(); + } + } + + if (!imagep->isInImageList()) + { + return; + } + if (imagep->isInFastCacheList()) + { + return; //wait for loading from the fast cache. + } + + imagep->processTextureStats(); +} + +void LLViewerTextureList::setDebugFetching(LLViewerFetchedTexture* tex, S32 debug_level) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if(!tex->setDebugFetching(debug_level)) + { + return; + } + + const F32 DEBUG_PRIORITY = 100000.f; + removeImageFromList(tex); + tex->mMaxVirtualSize = DEBUG_PRIORITY; + addImageToList(tex); +} + +F32 LLViewerTextureList::updateImagesCreateTextures(F32 max_time) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if (gGLManager.mIsDisabled) return 0.0f; + + // + // Create GL textures for all textures that need them (images which have been + // decoded, but haven't been pushed into GL). + // + + LLTimer create_timer; + image_list_t::iterator enditer = mCreateTextureList.begin(); + for (image_list_t::iterator iter = mCreateTextureList.begin(); + iter != mCreateTextureList.end();) + { + image_list_t::iterator curiter = iter++; + enditer = iter; + LLViewerFetchedTexture *imagep = *curiter; + imagep->createTexture(); + imagep->postCreateTexture(); + + if (create_timer.getElapsedTimeF32() > max_time) + { + break; + } + } + mCreateTextureList.erase(mCreateTextureList.begin(), enditer); + return create_timer.getElapsedTimeF32(); +} + +F32 LLViewerTextureList::updateImagesLoadingFastCache(F32 max_time) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if (gGLManager.mIsDisabled) return 0.0f; + if(mFastCacheList.empty()) + { + return 0.f; + } + + // + // loading texture raw data from the fast cache directly. + // + + LLTimer timer; + image_list_t::iterator enditer = mFastCacheList.begin(); + for (image_list_t::iterator iter = mFastCacheList.begin(); + iter != mFastCacheList.end();) + { + image_list_t::iterator curiter = iter++; + enditer = iter; + LLViewerFetchedTexture *imagep = *curiter; + imagep->loadFromFastCache(); + } + mFastCacheList.erase(mFastCacheList.begin(), enditer); + return timer.getElapsedTimeF32(); +} + +void LLViewerTextureList::forceImmediateUpdate(LLViewerFetchedTexture* imagep) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if(!imagep) + { + return ; + } + if(imagep->isInImageList()) + { + removeImageFromList(imagep); + } + + imagep->processTextureStats(); + imagep->sMaxVirtualSize = LLViewerFetchedTexture::sMaxVirtualSize; + addImageToList(imagep); + + return ; +} + +F32 LLViewerTextureList::updateImagesFetchTextures(F32 max_time) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + typedef std::vector > entries_list_t; + entries_list_t entries; + + // update N textures at beginning of mImageList + U32 update_count = 0; + static const S32 MIN_UPDATE_COUNT = gSavedSettings.getS32("TextureFetchUpdateMinCount"); // default: 32 + // WIP -- dumb code here + //update MIN_UPDATE_COUNT or 5% of other textures, whichever is greater + update_count = llmax((U32) MIN_UPDATE_COUNT, (U32) mUUIDMap.size()/20); + update_count = llmin(update_count, (U32) mUUIDMap.size()); + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_TEXTURE("vtluift - copy"); + + // copy entries out of UUID map for updating + entries.reserve(update_count); + uuid_map_t::iterator iter = mUUIDMap.upper_bound(mLastUpdateKey); + while (update_count-- > 0) + { + if (iter == mUUIDMap.end()) + { + iter = mUUIDMap.begin(); + } + + if (iter->second->getGLTexture()) + { + entries.push_back(iter->second); + } + ++iter; + } + } + + LLTimer timer; + + LLPointer last_imagep = nullptr; + + for (auto& imagep : entries) + { + if (imagep->getNumRefs() > 1) // make sure this image hasn't been deleted before attempting to update (may happen as a side effect of some other image updating) + + { + updateImageDecodePriority(imagep); + imagep->updateFetch(); + } + + last_imagep = imagep; + + if (timer.getElapsedTimeF32() > max_time) + { + break; + } + } + + if (last_imagep) + { + mLastUpdateKey = LLTextureKey(last_imagep->getID(), (ETexListType)last_imagep->getTextureListType()); + } + + return timer.getElapsedTimeF32(); +} + +void LLViewerTextureList::updateImagesUpdateStats() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if (mForceResetTextureStats) + { + for (image_priority_list_t::iterator iter = mImageList.begin(); + iter != mImageList.end(); ) + { + LLViewerFetchedTexture* imagep = *iter++; + imagep->resetTextureStats(); + } + mForceResetTextureStats = false; + } +} + +void LLViewerTextureList::decodeAllImages(F32 max_time) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + LLTimer timer; + + //loading from fast cache + updateImagesLoadingFastCache(max_time); + + // Update texture stats and priorities + std::vector > image_list; + for (image_priority_list_t::iterator iter = mImageList.begin(); + iter != mImageList.end(); ) + { + LLViewerFetchedTexture* imagep = *iter++; + image_list.push_back(imagep); + imagep->setInImageList(false) ; + } + + llassert_always(image_list.size() == mImageList.size()) ; + mImageList.clear(); + for (std::vector >::iterator iter = image_list.begin(); + iter != image_list.end(); ++iter) + { + LLViewerFetchedTexture* imagep = *iter; + imagep->processTextureStats(); + addImageToList(imagep); + } + image_list.clear(); + + // Update fetch (decode) + for (image_priority_list_t::iterator iter = mImageList.begin(); + iter != mImageList.end(); ) + { + LLViewerFetchedTexture* imagep = *iter++; + imagep->updateFetch(); + } + std::shared_ptr main_queue = LLImageGLThread::sEnabledTextures ? LL::WorkQueue::getInstance("mainloop") : NULL; + // Run threads + S32 fetch_pending = 0; + while (1) + { + LLAppViewer::instance()->getTextureCache()->update(1); // unpauses the texture cache thread + LLAppViewer::instance()->getImageDecodeThread()->update(1); // unpauses the image thread + fetch_pending = LLAppViewer::instance()->getTextureFetch()->update(1); // unpauses the texture fetch thread + + if (LLImageGLThread::sEnabledTextures) + { + main_queue->runFor(std::chrono::milliseconds(1)); + fetch_pending += main_queue->size(); + } + + if (fetch_pending == 0 || timer.getElapsedTimeF32() > max_time) + { + break; + } + } + // Update fetch again + for (image_priority_list_t::iterator iter = mImageList.begin(); + iter != mImageList.end(); ) + { + LLViewerFetchedTexture* imagep = *iter++; + imagep->updateFetch(); + } + max_time -= timer.getElapsedTimeF32(); + max_time = llmax(max_time, .001f); + F32 create_time = updateImagesCreateTextures(max_time); + + LL_DEBUGS("ViewerImages") << "decodeAllImages() took " << timer.getElapsedTimeF32() << " seconds. " + << " fetch_pending " << fetch_pending + << " create_time " << create_time + << LL_ENDL; +} + +bool LLViewerTextureList::createUploadFile(LLPointer raw_image, + const std::string& out_filename, + const S32 max_image_dimentions, + const S32 min_image_dimentions) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + + LLImageDataSharedLock lock(raw_image); + + // make a copy, since convertToUploadFile scales raw image + LLPointer scale_image = new LLImageRaw( + raw_image->getData(), + raw_image->getWidth(), + raw_image->getHeight(), + raw_image->getComponents()); + + LLPointer compressedImage = LLViewerTextureList::convertToUploadFile(scale_image, max_image_dimentions); + if (compressedImage->getWidth() < min_image_dimentions || compressedImage->getHeight() < min_image_dimentions) + { + std::string reason = llformat("Images below %d x %d pixels are not allowed. Actual size: %d x %dpx", + min_image_dimentions, + min_image_dimentions, + compressedImage->getWidth(), + compressedImage->getHeight()); + compressedImage->setLastError(reason); + return false; + } + if (compressedImage.isNull()) + { + compressedImage->setLastError("Couldn't convert the image to jpeg2000."); + LL_INFOS() << "Couldn't convert to j2c, file : " << out_filename << LL_ENDL; + return false; + } + if (!compressedImage->save(out_filename)) + { + compressedImage->setLastError("Couldn't create the jpeg2000 image for upload."); + LL_INFOS() << "Couldn't create output file : " << out_filename << LL_ENDL; + return false; + } + return true; +} + +bool LLViewerTextureList::createUploadFile(const std::string& filename, + const std::string& out_filename, + const U8 codec, + const S32 max_image_dimentions, + const S32 min_image_dimentions, + bool force_square) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + try + { + // Load the image + LLPointer image = LLImageFormatted::createFromType(codec); + if (image.isNull()) + { + LL_WARNS() << "Couldn't open the image to be uploaded." << LL_ENDL; + return false; + } + if (!image->load(filename)) + { + image->setLastError("Couldn't load the image to be uploaded."); + return false; + } + // Decompress or expand it in a raw image structure + LLPointer raw_image = new LLImageRaw; + if (!image->decode(raw_image, 0.0f)) + { + image->setLastError("Couldn't decode the image to be uploaded."); + return false; + } + // Check the image constraints + if ((image->getComponents() != 3) && (image->getComponents() != 4)) + { + image->setLastError("Image files with less than 3 or more than 4 components are not supported."); + return false; + } + if (image->getWidth() < min_image_dimentions || image->getHeight() < min_image_dimentions) + { + std::string reason = llformat("Images below %d x %d pixels are not allowed. Actual size: %d x %dpx", + min_image_dimentions, + min_image_dimentions, + image->getWidth(), + image->getHeight()); + image->setLastError(reason); + return false; + } + // Convert to j2c (JPEG2000) and save the file locally + LLPointer compressedImage = convertToUploadFile(raw_image, max_image_dimentions, force_square); + if (compressedImage.isNull()) + { + image->setLastError("Couldn't convert the image to jpeg2000."); + LL_INFOS() << "Couldn't convert to j2c, file : " << filename << LL_ENDL; + return false; + } + if (!compressedImage->save(out_filename)) + { + image->setLastError("Couldn't create the jpeg2000 image for upload."); + LL_INFOS() << "Couldn't create output file : " << out_filename << LL_ENDL; + return false; + } + // Test to see if the encode and save worked + LLPointer integrity_test = new LLImageJ2C; + if (!integrity_test->loadAndValidate(out_filename)) + { + image->setLastError("The created jpeg2000 image is corrupt."); + LL_INFOS() << "Image file : " << out_filename << " is corrupt" << LL_ENDL; + return false; + } + } + catch (...) + { + LOG_UNHANDLED_EXCEPTION(""); + return false; + } + return true; +} + +// note: modifies the argument raw_image!!!! +LLPointer LLViewerTextureList::convertToUploadFile(LLPointer raw_image, const S32 max_image_dimentions, bool force_square, bool force_lossless) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + LLImageDataLock lock(raw_image); + + if (force_square) + { + S32 biggest_side = llmax(raw_image->getWidth(), raw_image->getHeight()); + S32 square_size = raw_image->biasedDimToPowerOfTwo(biggest_side, max_image_dimentions); + + raw_image->scale(square_size, square_size); + } + else + { + raw_image->biasedScaleToPowerOfTwo(max_image_dimentions); + } + LLPointer compressedImage = new LLImageJ2C(); + + if (force_lossless || + (gSavedSettings.getBOOL("LosslessJ2CUpload") && + (raw_image->getWidth() * raw_image->getHeight() <= LL_IMAGE_REZ_LOSSLESS_CUTOFF * LL_IMAGE_REZ_LOSSLESS_CUTOFF))) + { + compressedImage->setReversible(true); + } + + + if (gSavedSettings.getBOOL("Jpeg2000AdvancedCompression")) + { + // This test option will create jpeg2000 images with precincts for each level, RPCL ordering + // and PLT markers. The block size is also optionally modifiable. + // Note: the images hence created are compatible with older versions of the viewer. + // Read the blocks and precincts size settings + S32 block_size = gSavedSettings.getS32("Jpeg2000BlocksSize"); + S32 precinct_size = gSavedSettings.getS32("Jpeg2000PrecinctsSize"); + LL_INFOS() << "Advanced JPEG2000 Compression: precinct = " << precinct_size << ", block = " << block_size << LL_ENDL; + compressedImage->initEncode(*raw_image, block_size, precinct_size, 0); + } + + if (!compressedImage->encode(raw_image, 0.0f)) + { + LL_INFOS() << "convertToUploadFile : encode returns with error!!" << LL_ENDL; + // Clear up the pointer so we don't leak that one + compressedImage = NULL; + } + + return compressedImage; +} + +/////////////////////////////////////////////////////////////////////////////// + +// We've been that the asset server does not contain the requested image id. +// static +void LLViewerTextureList::processImageNotInDatabase(LLMessageSystem *msg,void **user_data) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + LLUUID image_id; + msg->getUUIDFast(_PREHASH_ImageID, _PREHASH_ID, image_id); + + LLViewerFetchedTexture* image = gTextureList.findImage( image_id, TEX_LIST_STANDARD); + if( image ) + { + LL_WARNS() << "Image not in db" << LL_ENDL; + image->setIsMissingAsset(); + } + + image = gTextureList.findImage(image_id, TEX_LIST_SCALE); + if (image) + { + LL_WARNS() << "Icon not in db" << LL_ENDL; + image->setIsMissingAsset(); + } +} + + +/////////////////////////////////////////////////////////////////////////////// + +// explicitly cleanup resources, as this is a singleton class with process +// lifetime so ability to perform std::map operations in destructor is not +// guaranteed. +void LLUIImageList::cleanUp() +{ + mUIImages.clear(); + mUITextureList.clear() ; +} + +LLUIImagePtr LLUIImageList::getUIImageByID(const LLUUID& image_id, S32 priority) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + // use id as image name + std::string image_name = image_id.asString(); + + // look for existing image + uuid_ui_image_map_t::iterator found_it = mUIImages.find(image_name); + if (found_it != mUIImages.end()) + { + return found_it->second; + } + + const bool use_mips = false; + const LLRect scale_rect = LLRect::null; + const LLRect clip_rect = LLRect::null; + return loadUIImageByID(image_id, use_mips, scale_rect, clip_rect, (LLViewerTexture::EBoostLevel)priority); +} + +LLUIImagePtr LLUIImageList::getUIImage(const std::string& image_name, S32 priority) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + // look for existing image + uuid_ui_image_map_t::iterator found_it = mUIImages.find(image_name); + if (found_it != mUIImages.end()) + { + return found_it->second; + } + + const bool use_mips = false; + const LLRect scale_rect = LLRect::null; + const LLRect clip_rect = LLRect::null; + return loadUIImageByName(image_name, image_name, use_mips, scale_rect, clip_rect, (LLViewerTexture::EBoostLevel)priority); +} + +LLUIImagePtr LLUIImageList::loadUIImageByName(const std::string& name, const std::string& filename, + bool use_mips, const LLRect& scale_rect, const LLRect& clip_rect, LLViewerTexture::EBoostLevel boost_priority, + LLUIImage::EScaleStyle scale_style) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if (boost_priority == LLGLTexture::BOOST_NONE) + { + boost_priority = LLGLTexture::BOOST_UI; + } + LLViewerFetchedTexture* imagep = LLViewerTextureManager::getFetchedTextureFromFile(filename, FTT_LOCAL_FILE, MIPMAP_NO, boost_priority); + return loadUIImage(imagep, name, use_mips, scale_rect, clip_rect, scale_style); +} + +LLUIImagePtr LLUIImageList::loadUIImageByID(const LLUUID& id, + bool use_mips, const LLRect& scale_rect, const LLRect& clip_rect, LLViewerTexture::EBoostLevel boost_priority, + LLUIImage::EScaleStyle scale_style) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if (boost_priority == LLGLTexture::BOOST_NONE) + { + boost_priority = LLGLTexture::BOOST_UI; + } + LLViewerFetchedTexture* imagep = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, MIPMAP_NO, boost_priority); + return loadUIImage(imagep, id.asString(), use_mips, scale_rect, clip_rect, scale_style); +} + +LLUIImagePtr LLUIImageList::loadUIImage(LLViewerFetchedTexture* imagep, const std::string& name, bool use_mips, const LLRect& scale_rect, const LLRect& clip_rect, + LLUIImage::EScaleStyle scale_style) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if (!imagep) return NULL; + + imagep->setAddressMode(LLTexUnit::TAM_CLAMP); + + //don't compress UI images + imagep->getGLTexture()->setAllowCompression(false); + + LLUIImagePtr new_imagep = new LLUIImage(name, imagep); + new_imagep->setScaleStyle(scale_style); + + if (imagep->getBoostLevel() != LLGLTexture::BOOST_ICON + && imagep->getBoostLevel() != LLGLTexture::BOOST_THUMBNAIL + && imagep->getBoostLevel() != LLGLTexture::BOOST_PREVIEW) + { + // Don't add downloadable content into this list + // all UI images are non-deletable and list does not support deletion + imagep->setNoDelete(); + mUIImages.insert(std::make_pair(name, new_imagep)); + mUITextureList.push_back(imagep); + } + + //Note: + //Some other textures such as ICON also through this flow to be fetched. + //But only UI textures need to set this callback. + if(imagep->getBoostLevel() == LLGLTexture::BOOST_UI) + { + LLUIImageLoadData* datap = new LLUIImageLoadData; + datap->mImageName = name; + datap->mImageScaleRegion = scale_rect; + datap->mImageClipRegion = clip_rect; + + imagep->setLoadedCallback(onUIImageLoaded, 0, false, false, datap, NULL); + } + return new_imagep; +} + +LLUIImagePtr LLUIImageList::preloadUIImage(const std::string& name, const std::string& filename, bool use_mips, const LLRect& scale_rect, const LLRect& clip_rect, LLUIImage::EScaleStyle scale_style) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + // look for existing image + uuid_ui_image_map_t::iterator found_it = mUIImages.find(name); + if (found_it != mUIImages.end()) + { + // image already loaded! + LL_ERRS() << "UI Image " << name << " already loaded." << LL_ENDL; + } + + return loadUIImageByName(name, filename, use_mips, scale_rect, clip_rect, LLGLTexture::BOOST_UI, scale_style); +} + +//static +void LLUIImageList::onUIImageLoaded( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* src_aux, S32 discard_level, bool final, void* user_data ) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + if(!success || !user_data) + { + return; + } + + LLUIImageLoadData* image_datap = (LLUIImageLoadData*)user_data; + std::string ui_image_name = image_datap->mImageName; + LLRect scale_rect = image_datap->mImageScaleRegion; + LLRect clip_rect = image_datap->mImageClipRegion; + if (final) + { + delete image_datap; + } + + LLUIImageList* instance = getInstance(); + + uuid_ui_image_map_t::iterator found_it = instance->mUIImages.find(ui_image_name); + if (found_it != instance->mUIImages.end()) + { + LLUIImagePtr imagep = found_it->second; + + // for images grabbed from local files, apply clipping rectangle to restore original dimensions + // from power-of-2 gl image + if (success && imagep.notNull() && src_vi && (src_vi->getUrl().compare(0, 7, "file://")==0)) + { + F32 full_width = (F32)src_vi->getFullWidth(); + F32 full_height = (F32)src_vi->getFullHeight(); + F32 clip_x = (F32)src_vi->getOriginalWidth() / full_width; + F32 clip_y = (F32)src_vi->getOriginalHeight() / full_height; + if (clip_rect != LLRect::null) + { + imagep->setClipRegion(LLRectf(llclamp((F32)clip_rect.mLeft / full_width, 0.f, 1.f), + llclamp((F32)clip_rect.mTop / full_height, 0.f, 1.f), + llclamp((F32)clip_rect.mRight / full_width, 0.f, 1.f), + llclamp((F32)clip_rect.mBottom / full_height, 0.f, 1.f))); + } + else + { + imagep->setClipRegion(LLRectf(0.f, clip_y, clip_x, 0.f)); + } + if (scale_rect != LLRect::null) + { + imagep->setScaleRegion( + LLRectf(llclamp((F32)scale_rect.mLeft / (F32)imagep->getWidth(), 0.f, 1.f), + llclamp((F32)scale_rect.mTop / (F32)imagep->getHeight(), 0.f, 1.f), + llclamp((F32)scale_rect.mRight / (F32)imagep->getWidth(), 0.f, 1.f), + llclamp((F32)scale_rect.mBottom / (F32)imagep->getHeight(), 0.f, 1.f))); + } + + imagep->onImageLoaded(); + } + } +} + +namespace LLInitParam +{ + template<> + struct TypeValues : public TypeValuesHelper + { + static void declareValues() + { + declare("scale_inner", LLUIImage::SCALE_INNER); + declare("scale_outer", LLUIImage::SCALE_OUTER); + } + }; +} + +struct UIImageDeclaration : public LLInitParam::Block +{ + Mandatory name; + Optional file_name; + Optional preload; + Optional scale; + Optional clip; + Optional use_mips; + Optional scale_type; + + UIImageDeclaration() + : name("name"), + file_name("file_name"), + preload("preload", false), + scale("scale"), + clip("clip"), + use_mips("use_mips", false), + scale_type("scale_type", LLUIImage::SCALE_INNER) + {} +}; + +struct UIImageDeclarations : public LLInitParam::Block +{ + Mandatory version; + Multiple textures; + + UIImageDeclarations() + : version("version"), + textures("texture") + {} +}; + +bool LLUIImageList::initFromFile() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + // Look for textures.xml in all the right places. Pass + // constraint=LLDir::ALL_SKINS because we want to overlay textures.xml + // from all the skins directories. + std::vector textures_paths = + gDirUtilp->findSkinnedFilenames(LLDir::TEXTURES, "textures.xml", LLDir::ALL_SKINS); + std::vector::const_iterator pi(textures_paths.begin()), pend(textures_paths.end()); + if (pi == pend) + { + LL_WARNS() << "No textures.xml found in skins directories" << LL_ENDL; + return false; + } + + // The first (most generic) file gets special validations + LLXMLNodePtr root; + if (!LLXMLNode::parseFile(*pi, root, NULL)) + { + LL_WARNS() << "Unable to parse UI image list file " << *pi << LL_ENDL; + return false; + } + if (!root->hasAttribute("version")) + { + LL_WARNS() << "No valid version number in UI image list file " << *pi << LL_ENDL; + return false; + } + + UIImageDeclarations images; + LLXUIParser parser; + parser.readXUI(root, images, *pi); + + // add components defined in the rest of the skin paths + while (++pi != pend) + { + LLXMLNodePtr update_root; + if (LLXMLNode::parseFile(*pi, update_root, NULL)) + { + parser.readXUI(update_root, images, *pi); + } + } + + if (!images.validateBlock()) return false; + + std::map merged_declarations; + for (LLInitParam::ParamIterator::const_iterator image_it = images.textures.begin(); + image_it != images.textures.end(); + ++image_it) + { + merged_declarations[image_it->name].overwriteFrom(*image_it); + } + + enum e_decode_pass + { + PASS_DECODE_NOW, + PASS_DECODE_LATER, + NUM_PASSES + }; + + for (S32 cur_pass = PASS_DECODE_NOW; cur_pass < NUM_PASSES; cur_pass++) + { + for (std::map::const_iterator image_it = merged_declarations.begin(); + image_it != merged_declarations.end(); + ++image_it) + { + const UIImageDeclaration& image = image_it->second; + std::string file_name = image.file_name.isProvided() ? image.file_name() : image.name(); + + // load high priority textures on first pass (to kick off decode) + enum e_decode_pass decode_pass = image.preload ? PASS_DECODE_NOW : PASS_DECODE_LATER; + if (decode_pass != cur_pass) + { + continue; + } + preloadUIImage(image.name, file_name, image.use_mips, image.scale, image.clip, image.scale_type); + } + + if (!gSavedSettings.getBOOL("NoPreload")) + { + if (cur_pass == PASS_DECODE_NOW) + { + // init fetching and decoding of preloaded images + gTextureList.decodeAllImages(9.f); + } + else + { + // decodeAllImages needs two passes to refresh stats and priorities on second pass + gTextureList.decodeAllImages(1.f); + } + } + } + return true; +} + + diff --git a/indra/newview/llviewertexturelist.h b/indra/newview/llviewertexturelist.h index 2cd5f2f117..da1c013335 100644 --- a/indra/newview/llviewertexturelist.h +++ b/indra/newview/llviewertexturelist.h @@ -1,294 +1,294 @@ -/** - * @file llviewertexturelist.h - * @brief Object for managing the list of images within a region - * - * $LicenseInfo:firstyear=2022&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2022, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVIEWERTEXTURELIST_H -#define LL_LLVIEWERTEXTURELIST_H - -#include "lluuid.h" -//#include "message.h" -#include "llgl.h" -#include "llviewertexture.h" -#include "llui.h" -#include -#include -#include "lluiimage.h" - -const U32 LL_IMAGE_REZ_LOSSLESS_CUTOFF = 128; - -const bool MIPMAP_YES = true; -const bool MIPMAP_NO = false; - -const bool GL_TEXTURE_YES = true; -const bool GL_TEXTURE_NO = false; - -const bool IMMEDIATE_YES = true; -const bool IMMEDIATE_NO = false; - -class LLImageJ2C; -class LLMessageSystem; -class LLTextureView; - -typedef void (*LLImageCallback)(bool success, - LLViewerFetchedTexture *src_vi, - LLImageRaw* src, - LLImageRaw* src_aux, - S32 discard_level, - bool final, - void* userdata); - -enum ETexListType -{ - TEX_LIST_STANDARD = 0, - TEX_LIST_SCALE -}; - -struct LLTextureKey -{ - LLTextureKey(); - LLTextureKey(LLUUID id, ETexListType tex_type); - LLUUID textureId; - ETexListType textureType; - - friend bool operator<(const LLTextureKey& key1, const LLTextureKey& key2) - { - if (key1.textureId != key2.textureId) - { - return key1.textureId < key2.textureId; - } - else - { - return key1.textureType < key2.textureType; - } - } -}; - -class LLViewerTextureList -{ - friend class LLTextureView; - friend class LLViewerTextureManager; - friend class LLLocalBitmap; - -public: - static bool createUploadFile(LLPointer raw_image, - const std::string& out_filename, - const S32 max_image_dimentions = LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT, - const S32 min_image_dimentions = 0); - static bool createUploadFile(const std::string& filename, - const std::string& out_filename, - const U8 codec, - const S32 max_image_dimentions = LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT, - const S32 min_image_dimentions = 0, - bool force_square = false); - static LLPointer convertToUploadFile(LLPointer raw_image, - const S32 max_image_dimentions = LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT, - bool force_square = false, - bool force_lossless = false); - static void processImageNotInDatabase( LLMessageSystem *msg, void **user_data ); - -public: - LLViewerTextureList(); - ~LLViewerTextureList(); - - void init(); - void shutdown(); - void dump(); - void destroyGL(bool save_state = true); - void restoreGL(); - bool isInitialized() const {return mInitialized;} - - void findTexturesByID(const LLUUID &image_id, std::vector &output); - LLViewerFetchedTexture *findImage(const LLUUID &image_id, ETexListType tex_type); - LLViewerFetchedTexture *findImage(const LLTextureKey &search_key); - - void dirtyImage(LLViewerFetchedTexture *image); - - // Using image stats, determine what images are necessary, and perform image updates. - void updateImages(F32 max_time); - void forceImmediateUpdate(LLViewerFetchedTexture* imagep) ; - - // Decode and create textures for all images currently in list. - void decodeAllImages(F32 max_decode_time); - - void handleIRCallback(void **data, const S32 number); - - S32 getNumImages() { return mImageList.size(); } - - // Local UI images - // Local UI images - void doPreloadImages(); - // Network images. Needs caps and cache to work - void doPrefetchImages(); - - void clearFetchingRequests(); - void setDebugFetching(LLViewerFetchedTexture* tex, S32 debug_level); - -private: - // do some book keeping on the specified texture - // - updates decode priority - // - updates desired discard level - // - cleans up textures that haven't been referenced in awhile - void updateImageDecodePriority(LLViewerFetchedTexture* imagep); - F32 updateImagesCreateTextures(F32 max_time); - F32 updateImagesFetchTextures(F32 max_time); - void updateImagesUpdateStats(); - F32 updateImagesLoadingFastCache(F32 max_time); - - void addImage(LLViewerFetchedTexture *image, ETexListType tex_type); - void deleteImage(LLViewerFetchedTexture *image); - - void addImageToList(LLViewerFetchedTexture *image); - void removeImageFromList(LLViewerFetchedTexture *image); - - LLViewerFetchedTexture * getImage(const LLUUID &image_id, - FTType f_type = FTT_DEFAULT, - bool usemipmap = true, - LLViewerTexture::EBoostLevel boost_priority = LLGLTexture::BOOST_NONE, // Get the requested level immediately upon creation. - S8 texture_type = LLViewerTexture::FETCHED_TEXTURE, - LLGLint internal_format = 0, - LLGLenum primary_format = 0, - LLHost request_from_host = LLHost() - ); - - LLViewerFetchedTexture * getImageFromFile(const std::string& filename, - FTType f_type = FTT_LOCAL_FILE, - bool usemipmap = true, - LLViewerTexture::EBoostLevel boost_priority = LLGLTexture::BOOST_NONE, // Get the requested level immediately upon creation. - S8 texture_type = LLViewerTexture::FETCHED_TEXTURE, - LLGLint internal_format = 0, - LLGLenum primary_format = 0, - const LLUUID& force_id = LLUUID::null - ); - - LLViewerFetchedTexture* getImageFromUrl(const std::string& url, - FTType f_type, - bool usemipmap = true, - LLViewerTexture::EBoostLevel boost_priority = LLGLTexture::BOOST_NONE, // Get the requested level immediately upon creation. - S8 texture_type = LLViewerTexture::FETCHED_TEXTURE, - LLGLint internal_format = 0, - LLGLenum primary_format = 0, - const LLUUID& force_id = LLUUID::null - ); - - LLViewerFetchedTexture* createImage(const LLUUID &image_id, - FTType f_type, - bool usemipmap = true, - LLViewerTexture::EBoostLevel boost_priority = LLGLTexture::BOOST_NONE, // Get the requested level immediately upon creation. - S8 texture_type = LLViewerTexture::FETCHED_TEXTURE, - LLGLint internal_format = 0, - LLGLenum primary_format = 0, - LLHost request_from_host = LLHost() - ); - - // Request image from a specific host, used for baked avatar textures. - // Implemented in header in case someone changes default params above. JC - LLViewerFetchedTexture* getImageFromHost(const LLUUID& image_id, FTType f_type, LLHost host) - { return getImage(image_id, f_type, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE, 0, 0, host); } - -public: - typedef std::set > image_list_t; - image_list_t mLoadingStreamList; - image_list_t mCreateTextureList; - image_list_t mCallbackList; - image_list_t mFastCacheList; - - // Note: just raw pointers because they are never referenced, just compared against - std::set mDirtyTextureList; - - bool mForceResetTextureStats; - -private: - typedef std::map< LLTextureKey, LLPointer > uuid_map_t; - uuid_map_t mUUIDMap; - LLTextureKey mLastUpdateKey; - - typedef std::set < LLPointer > image_priority_list_t; - image_priority_list_t mImageList; - - // simply holds on to LLViewerFetchedTexture references to stop them from being purged too soon - std::set > mImagePreloads; - - bool mInitialized ; - LLFrameTimer mForceDecodeTimer; - -private: - static S32 sNumImages; - static void (*sUUIDCallback)(void**, const LLUUID &); - LOG_CLASS(LLViewerTextureList); -}; - -class LLUIImageList : public LLImageProviderInterface, public LLSingleton -{ - LLSINGLETON_EMPTY_CTOR(LLUIImageList); -public: - // LLImageProviderInterface - /*virtual*/ LLPointer getUIImageByID(const LLUUID& id, S32 priority) override; - /*virtual*/ LLPointer getUIImage(const std::string& name, S32 priority) override; - void cleanUp() override; - - bool initFromFile(); - - LLPointer preloadUIImage(const std::string& name, const std::string& filename, bool use_mips, const LLRect& scale_rect, const LLRect& clip_rect, LLUIImage::EScaleStyle stype); - - static void onUIImageLoaded( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* src_aux, S32 discard_level, bool final, void* userdata ); -private: - LLPointer loadUIImageByName(const std::string& name, const std::string& filename, - bool use_mips = false, const LLRect& scale_rect = LLRect::null, - const LLRect& clip_rect = LLRect::null, - LLViewerTexture::EBoostLevel boost_priority = LLGLTexture::BOOST_UI, - LLUIImage::EScaleStyle = LLUIImage::SCALE_INNER); - LLPointer loadUIImageByID(const LLUUID& id, - bool use_mips = false, const LLRect& scale_rect = LLRect::null, - const LLRect& clip_rect = LLRect::null, - LLViewerTexture::EBoostLevel boost_priority = LLGLTexture::BOOST_UI, - LLUIImage::EScaleStyle = LLUIImage::SCALE_INNER); - - LLPointer loadUIImage(LLViewerFetchedTexture* imagep, const std::string& name, bool use_mips = false, const LLRect& scale_rect = LLRect::null, const LLRect& clip_rect = LLRect::null, LLUIImage::EScaleStyle = LLUIImage::SCALE_INNER); - - - struct LLUIImageLoadData - { - std::string mImageName; - LLRect mImageScaleRegion; - LLRect mImageClipRegion; - }; - - typedef std::map< std::string, LLPointer > uuid_ui_image_map_t; - uuid_ui_image_map_t mUIImages; - - // - //keep a copy of UI textures to prevent them to be deleted. - //mGLTexturep of each UI texture equals to some LLUIImage.mImage. - std::list< LLPointer > mUITextureList ; -}; - -const bool GLTEXTURE_TRUE = true; -const bool GLTEXTURE_FALSE = false; -const bool MIPMAP_TRUE = true; -const bool MIPMAP_FALSE = false; - -extern LLViewerTextureList gTextureList; - -#endif +/** + * @file llviewertexturelist.h + * @brief Object for managing the list of images within a region + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2022, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVIEWERTEXTURELIST_H +#define LL_LLVIEWERTEXTURELIST_H + +#include "lluuid.h" +//#include "message.h" +#include "llgl.h" +#include "llviewertexture.h" +#include "llui.h" +#include +#include +#include "lluiimage.h" + +const U32 LL_IMAGE_REZ_LOSSLESS_CUTOFF = 128; + +const bool MIPMAP_YES = true; +const bool MIPMAP_NO = false; + +const bool GL_TEXTURE_YES = true; +const bool GL_TEXTURE_NO = false; + +const bool IMMEDIATE_YES = true; +const bool IMMEDIATE_NO = false; + +class LLImageJ2C; +class LLMessageSystem; +class LLTextureView; + +typedef void (*LLImageCallback)(bool success, + LLViewerFetchedTexture *src_vi, + LLImageRaw* src, + LLImageRaw* src_aux, + S32 discard_level, + bool final, + void* userdata); + +enum ETexListType +{ + TEX_LIST_STANDARD = 0, + TEX_LIST_SCALE +}; + +struct LLTextureKey +{ + LLTextureKey(); + LLTextureKey(LLUUID id, ETexListType tex_type); + LLUUID textureId; + ETexListType textureType; + + friend bool operator<(const LLTextureKey& key1, const LLTextureKey& key2) + { + if (key1.textureId != key2.textureId) + { + return key1.textureId < key2.textureId; + } + else + { + return key1.textureType < key2.textureType; + } + } +}; + +class LLViewerTextureList +{ + friend class LLTextureView; + friend class LLViewerTextureManager; + friend class LLLocalBitmap; + +public: + static bool createUploadFile(LLPointer raw_image, + const std::string& out_filename, + const S32 max_image_dimentions = LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT, + const S32 min_image_dimentions = 0); + static bool createUploadFile(const std::string& filename, + const std::string& out_filename, + const U8 codec, + const S32 max_image_dimentions = LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT, + const S32 min_image_dimentions = 0, + bool force_square = false); + static LLPointer convertToUploadFile(LLPointer raw_image, + const S32 max_image_dimentions = LLViewerFetchedTexture::MAX_IMAGE_SIZE_DEFAULT, + bool force_square = false, + bool force_lossless = false); + static void processImageNotInDatabase( LLMessageSystem *msg, void **user_data ); + +public: + LLViewerTextureList(); + ~LLViewerTextureList(); + + void init(); + void shutdown(); + void dump(); + void destroyGL(bool save_state = true); + void restoreGL(); + bool isInitialized() const {return mInitialized;} + + void findTexturesByID(const LLUUID &image_id, std::vector &output); + LLViewerFetchedTexture *findImage(const LLUUID &image_id, ETexListType tex_type); + LLViewerFetchedTexture *findImage(const LLTextureKey &search_key); + + void dirtyImage(LLViewerFetchedTexture *image); + + // Using image stats, determine what images are necessary, and perform image updates. + void updateImages(F32 max_time); + void forceImmediateUpdate(LLViewerFetchedTexture* imagep) ; + + // Decode and create textures for all images currently in list. + void decodeAllImages(F32 max_decode_time); + + void handleIRCallback(void **data, const S32 number); + + S32 getNumImages() { return mImageList.size(); } + + // Local UI images + // Local UI images + void doPreloadImages(); + // Network images. Needs caps and cache to work + void doPrefetchImages(); + + void clearFetchingRequests(); + void setDebugFetching(LLViewerFetchedTexture* tex, S32 debug_level); + +private: + // do some book keeping on the specified texture + // - updates decode priority + // - updates desired discard level + // - cleans up textures that haven't been referenced in awhile + void updateImageDecodePriority(LLViewerFetchedTexture* imagep); + F32 updateImagesCreateTextures(F32 max_time); + F32 updateImagesFetchTextures(F32 max_time); + void updateImagesUpdateStats(); + F32 updateImagesLoadingFastCache(F32 max_time); + + void addImage(LLViewerFetchedTexture *image, ETexListType tex_type); + void deleteImage(LLViewerFetchedTexture *image); + + void addImageToList(LLViewerFetchedTexture *image); + void removeImageFromList(LLViewerFetchedTexture *image); + + LLViewerFetchedTexture * getImage(const LLUUID &image_id, + FTType f_type = FTT_DEFAULT, + bool usemipmap = true, + LLViewerTexture::EBoostLevel boost_priority = LLGLTexture::BOOST_NONE, // Get the requested level immediately upon creation. + S8 texture_type = LLViewerTexture::FETCHED_TEXTURE, + LLGLint internal_format = 0, + LLGLenum primary_format = 0, + LLHost request_from_host = LLHost() + ); + + LLViewerFetchedTexture * getImageFromFile(const std::string& filename, + FTType f_type = FTT_LOCAL_FILE, + bool usemipmap = true, + LLViewerTexture::EBoostLevel boost_priority = LLGLTexture::BOOST_NONE, // Get the requested level immediately upon creation. + S8 texture_type = LLViewerTexture::FETCHED_TEXTURE, + LLGLint internal_format = 0, + LLGLenum primary_format = 0, + const LLUUID& force_id = LLUUID::null + ); + + LLViewerFetchedTexture* getImageFromUrl(const std::string& url, + FTType f_type, + bool usemipmap = true, + LLViewerTexture::EBoostLevel boost_priority = LLGLTexture::BOOST_NONE, // Get the requested level immediately upon creation. + S8 texture_type = LLViewerTexture::FETCHED_TEXTURE, + LLGLint internal_format = 0, + LLGLenum primary_format = 0, + const LLUUID& force_id = LLUUID::null + ); + + LLViewerFetchedTexture* createImage(const LLUUID &image_id, + FTType f_type, + bool usemipmap = true, + LLViewerTexture::EBoostLevel boost_priority = LLGLTexture::BOOST_NONE, // Get the requested level immediately upon creation. + S8 texture_type = LLViewerTexture::FETCHED_TEXTURE, + LLGLint internal_format = 0, + LLGLenum primary_format = 0, + LLHost request_from_host = LLHost() + ); + + // Request image from a specific host, used for baked avatar textures. + // Implemented in header in case someone changes default params above. JC + LLViewerFetchedTexture* getImageFromHost(const LLUUID& image_id, FTType f_type, LLHost host) + { return getImage(image_id, f_type, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE, 0, 0, host); } + +public: + typedef std::set > image_list_t; + image_list_t mLoadingStreamList; + image_list_t mCreateTextureList; + image_list_t mCallbackList; + image_list_t mFastCacheList; + + // Note: just raw pointers because they are never referenced, just compared against + std::set mDirtyTextureList; + + bool mForceResetTextureStats; + +private: + typedef std::map< LLTextureKey, LLPointer > uuid_map_t; + uuid_map_t mUUIDMap; + LLTextureKey mLastUpdateKey; + + typedef std::set < LLPointer > image_priority_list_t; + image_priority_list_t mImageList; + + // simply holds on to LLViewerFetchedTexture references to stop them from being purged too soon + std::set > mImagePreloads; + + bool mInitialized ; + LLFrameTimer mForceDecodeTimer; + +private: + static S32 sNumImages; + static void (*sUUIDCallback)(void**, const LLUUID &); + LOG_CLASS(LLViewerTextureList); +}; + +class LLUIImageList : public LLImageProviderInterface, public LLSingleton +{ + LLSINGLETON_EMPTY_CTOR(LLUIImageList); +public: + // LLImageProviderInterface + /*virtual*/ LLPointer getUIImageByID(const LLUUID& id, S32 priority) override; + /*virtual*/ LLPointer getUIImage(const std::string& name, S32 priority) override; + void cleanUp() override; + + bool initFromFile(); + + LLPointer preloadUIImage(const std::string& name, const std::string& filename, bool use_mips, const LLRect& scale_rect, const LLRect& clip_rect, LLUIImage::EScaleStyle stype); + + static void onUIImageLoaded( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* src_aux, S32 discard_level, bool final, void* userdata ); +private: + LLPointer loadUIImageByName(const std::string& name, const std::string& filename, + bool use_mips = false, const LLRect& scale_rect = LLRect::null, + const LLRect& clip_rect = LLRect::null, + LLViewerTexture::EBoostLevel boost_priority = LLGLTexture::BOOST_UI, + LLUIImage::EScaleStyle = LLUIImage::SCALE_INNER); + LLPointer loadUIImageByID(const LLUUID& id, + bool use_mips = false, const LLRect& scale_rect = LLRect::null, + const LLRect& clip_rect = LLRect::null, + LLViewerTexture::EBoostLevel boost_priority = LLGLTexture::BOOST_UI, + LLUIImage::EScaleStyle = LLUIImage::SCALE_INNER); + + LLPointer loadUIImage(LLViewerFetchedTexture* imagep, const std::string& name, bool use_mips = false, const LLRect& scale_rect = LLRect::null, const LLRect& clip_rect = LLRect::null, LLUIImage::EScaleStyle = LLUIImage::SCALE_INNER); + + + struct LLUIImageLoadData + { + std::string mImageName; + LLRect mImageScaleRegion; + LLRect mImageClipRegion; + }; + + typedef std::map< std::string, LLPointer > uuid_ui_image_map_t; + uuid_ui_image_map_t mUIImages; + + // + //keep a copy of UI textures to prevent them to be deleted. + //mGLTexturep of each UI texture equals to some LLUIImage.mImage. + std::list< LLPointer > mUITextureList ; +}; + +const bool GLTEXTURE_TRUE = true; +const bool GLTEXTURE_FALSE = false; +const bool MIPMAP_TRUE = true; +const bool MIPMAP_FALSE = false; + +extern LLViewerTextureList gTextureList; + +#endif diff --git a/indra/newview/llviewerthrottle.cpp b/indra/newview/llviewerthrottle.cpp index b6fff70234..485dd683be 100644 --- a/indra/newview/llviewerthrottle.cpp +++ b/indra/newview/llviewerthrottle.cpp @@ -1,334 +1,334 @@ -/** - * @file llviewerthrottle.cpp - * @brief LLViewerThrottle class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llviewerthrottle.h" - -#include "llviewercontrol.h" -#include "message.h" -#include "llagent.h" -#include "llframetimer.h" -#include "llviewerstats.h" -#include "lldatapacker.h" - -using namespace LLOldEvents; - -// consts - -// The viewer is allowed to set the under-the-hood bandwidth to 50% -// greater than the prefs UI shows, under the assumption that the -// viewer won't receive all the different message types at once. -// I didn't design this, don't know who did. JC -const F32 MAX_FRACTIONAL = 1.5f; -const F32 MIN_FRACTIONAL = 0.2f; - -const F32 MIN_BANDWIDTH = 50.f; -const F32 MAX_BANDWIDTH = 6000.f; -const F32 STEP_FRACTIONAL = 0.1f; -const LLUnit TIGHTEN_THROTTLE_THRESHOLD(3.0f); // packet loss % per s -const LLUnit EASE_THROTTLE_THRESHOLD(0.5f); // packet loss % per s -const F32 DYNAMIC_UPDATE_DURATION = 5.0f; // seconds - -LLViewerThrottle gViewerThrottle; - -// static -const std:: string LLViewerThrottle::sNames[TC_EOF] = { - "Resend", - "Land", - "Wind", - "Cloud", - "Task", - "Texture", - "Asset" - }; - - -// Bandwidth settings for different bit rates, they're interpolated/extrapolated. -// Resend Land Wind Cloud Task Texture Asset -const F32 BW_PRESET_50[TC_EOF] = { 5, 10, 3, 3, 10, 10, 9 }; -const F32 BW_PRESET_300[TC_EOF] = { 30, 40, 9, 9, 86, 86, 40 }; -const F32 BW_PRESET_500[TC_EOF] = { 50, 70, 14, 14, 136, 136, 80 }; -const F32 BW_PRESET_1000[TC_EOF] = { 100, 100, 20, 20, 310, 310, 140 }; - -LLViewerThrottleGroup::LLViewerThrottleGroup() -{ - S32 i; - for (i = 0; i < TC_EOF; i++) - { - mThrottles[i] = 0.f; - } - mThrottleTotal = 0.f; -} - - -LLViewerThrottleGroup::LLViewerThrottleGroup(const F32 settings[TC_EOF]) -{ - mThrottleTotal = 0.f; - S32 i; - for (i = 0; i < TC_EOF; i++) - { - mThrottles[i] = settings[i]; - mThrottleTotal += settings[i]; - } -} - - -LLViewerThrottleGroup LLViewerThrottleGroup::operator*(const F32 frac) const -{ - LLViewerThrottleGroup res; - res.mThrottleTotal = 0.f; - - S32 i; - for (i = 0; i < TC_EOF; i++) - { - res.mThrottles[i] = mThrottles[i] * frac; - res.mThrottleTotal += res.mThrottles[i]; - } - - return res; -} - - -LLViewerThrottleGroup LLViewerThrottleGroup::operator+(const LLViewerThrottleGroup &b) const -{ - LLViewerThrottleGroup res; - res.mThrottleTotal = 0.f; - - S32 i; - for (i = 0; i < TC_EOF; i++) - { - res.mThrottles[i] = mThrottles[i] + b.mThrottles[i]; - res.mThrottleTotal += res.mThrottles[i]; - } - - return res; -} - - -LLViewerThrottleGroup LLViewerThrottleGroup::operator-(const LLViewerThrottleGroup &b) const -{ - LLViewerThrottleGroup res; - res.mThrottleTotal = 0.f; - - S32 i; - for (i = 0; i < TC_EOF; i++) - { - res.mThrottles[i] = mThrottles[i] - b.mThrottles[i]; - res.mThrottleTotal += res.mThrottles[i]; - } - - return res; -} - - -void LLViewerThrottleGroup::sendToSim() const -{ - LL_INFOS() << "Sending throttle settings, total BW " << mThrottleTotal << LL_ENDL; - LLMessageSystem* msg = gMessageSystem; - - msg->newMessageFast(_PREHASH_AgentThrottle); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addU32Fast(_PREHASH_CircuitCode, msg->mOurCircuitCode); - - msg->nextBlockFast(_PREHASH_Throttle); - msg->addU32Fast(_PREHASH_GenCounter, 0); - - // Pack up the throttle data - U8 tmp[64]; - LLDataPackerBinaryBuffer dp(tmp, MAX_THROTTLE_SIZE); - S32 i; - for (i = 0; i < TC_EOF; i++) - { - //sim wants BPS, not KBPS - dp.packF32(mThrottles[i] * 1024.0f, "Throttle"); - } - S32 len = dp.getCurrentSize(); - msg->addBinaryDataFast(_PREHASH_Throttles, tmp, len); - - gAgent.sendReliableMessage(); -} - - -void LLViewerThrottleGroup::dump() -{ - S32 i; - for (i = 0; i < TC_EOF; i++) - { - LL_DEBUGS("Throttle") << LLViewerThrottle::sNames[i] << ": " << mThrottles[i] << LL_ENDL; - } - LL_DEBUGS("Throttle") << "Total: " << mThrottleTotal << LL_ENDL; -} - -class LLBPSListener : public LLSimpleListener -{ -public: - virtual bool handleEvent(LLPointer event, const LLSD& userdata) - { - gViewerThrottle.setMaxBandwidth((F32) event->getValue().asReal()*1024); - return true; - } -}; - -LLViewerThrottle::LLViewerThrottle() : - mMaxBandwidth(0.f), - mCurrentBandwidth(0.f), - mThrottleFrac(1.f) -{ - // Need to be pushed on in bandwidth order - mPresets.push_back(LLViewerThrottleGroup(BW_PRESET_50)); - mPresets.push_back(LLViewerThrottleGroup(BW_PRESET_300)); - mPresets.push_back(LLViewerThrottleGroup(BW_PRESET_500)); - mPresets.push_back(LLViewerThrottleGroup(BW_PRESET_1000)); -} - - -void LLViewerThrottle::setMaxBandwidth(F32 kbits_per_second, bool from_event) -{ - if (!from_event) - { - gSavedSettings.setF32("ThrottleBandwidthKBPS", kbits_per_second); - } - gViewerThrottle.load(); - - if (gAgent.getRegion()) - { - gViewerThrottle.sendToSim(); - } -} - -void LLViewerThrottle::load() -{ - mMaxBandwidth = gSavedSettings.getF32("ThrottleBandwidthKBPS")*1024; - resetDynamicThrottle(); - mCurrent.dump(); -} - - -void LLViewerThrottle::save() const -{ - gSavedSettings.setF32("ThrottleBandwidthKBPS", mMaxBandwidth/1024); -} - - -void LLViewerThrottle::sendToSim() const -{ - mCurrent.sendToSim(); -} - - -LLViewerThrottleGroup LLViewerThrottle::getThrottleGroup(const F32 bandwidth_kbps) -{ - //Clamp the bandwidth users can set. - F32 set_bandwidth = llclamp(bandwidth_kbps, MIN_BANDWIDTH, MAX_BANDWIDTH); - - S32 count = mPresets.size(); - - S32 i; - for (i = 0; i < count; i++) - { - if (mPresets[i].getTotal() > set_bandwidth) - { - break; - } - } - - if (i == 0) - { - // We return the minimum if it's less than the minimum - return mPresets[0]; - } - else if (i == count) - { - // Higher than the highest preset, we extrapolate out based on the - // last two presets. This allows us to keep certain throttle channels from - // growing in bandwidth - F32 delta_bw = set_bandwidth - mPresets[count-1].getTotal(); - LLViewerThrottleGroup delta_throttle = mPresets[count - 1] - mPresets[count - 2]; - F32 delta_total = delta_throttle.getTotal(); - F32 delta_frac = delta_bw / delta_total; - delta_throttle = delta_throttle * delta_frac; - return mPresets[count-1] + delta_throttle; - } - else - { - // In between two presets, just interpolate - F32 delta_bw = set_bandwidth - mPresets[i - 1].getTotal(); - LLViewerThrottleGroup delta_throttle = mPresets[i] - mPresets[i - 1]; - F32 delta_total = delta_throttle.getTotal(); - F32 delta_frac = delta_bw / delta_total; - delta_throttle = delta_throttle * delta_frac; - return mPresets[i - 1] + delta_throttle; - } -} - - -// static -void LLViewerThrottle::resetDynamicThrottle() -{ - mThrottleFrac = MAX_FRACTIONAL; - - mCurrentBandwidth = mMaxBandwidth*MAX_FRACTIONAL; - mCurrent = getThrottleGroup(mCurrentBandwidth / 1024.0f); -} - -void LLViewerThrottle::updateDynamicThrottle() -{ - if (mUpdateTimer.getElapsedTimeF32() < DYNAMIC_UPDATE_DURATION) - { - return; - } - mUpdateTimer.reset(); - - LLUnit mean_packets_lost = LLViewerStats::instance().getRecording().getMean(LLStatViewer::PACKETS_LOST_PERCENT); - if (mean_packets_lost > TIGHTEN_THROTTLE_THRESHOLD) - { - if (mThrottleFrac <= MIN_FRACTIONAL || mCurrentBandwidth / 1024.0f <= MIN_BANDWIDTH) - { - return; - } - mThrottleFrac -= STEP_FRACTIONAL; - mThrottleFrac = llmax(MIN_FRACTIONAL, mThrottleFrac); - mCurrentBandwidth = mMaxBandwidth * mThrottleFrac; - mCurrent = getThrottleGroup(mCurrentBandwidth / 1024.0f); - mCurrent.sendToSim(); - LL_INFOS() << "Tightening network throttle to " << mCurrentBandwidth << LL_ENDL; - } - else if (mean_packets_lost <= EASE_THROTTLE_THRESHOLD) - { - if (mThrottleFrac >= MAX_FRACTIONAL || mCurrentBandwidth / 1024.0f >= MAX_BANDWIDTH) - { - return; - } - mThrottleFrac += STEP_FRACTIONAL; - mThrottleFrac = llmin(MAX_FRACTIONAL, mThrottleFrac); - mCurrentBandwidth = mMaxBandwidth * mThrottleFrac; - mCurrent = getThrottleGroup(mCurrentBandwidth/1024.0f); - mCurrent.sendToSim(); - LL_INFOS() << "Easing network throttle to " << mCurrentBandwidth << LL_ENDL; - } -} +/** + * @file llviewerthrottle.cpp + * @brief LLViewerThrottle class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llviewerthrottle.h" + +#include "llviewercontrol.h" +#include "message.h" +#include "llagent.h" +#include "llframetimer.h" +#include "llviewerstats.h" +#include "lldatapacker.h" + +using namespace LLOldEvents; + +// consts + +// The viewer is allowed to set the under-the-hood bandwidth to 50% +// greater than the prefs UI shows, under the assumption that the +// viewer won't receive all the different message types at once. +// I didn't design this, don't know who did. JC +const F32 MAX_FRACTIONAL = 1.5f; +const F32 MIN_FRACTIONAL = 0.2f; + +const F32 MIN_BANDWIDTH = 50.f; +const F32 MAX_BANDWIDTH = 6000.f; +const F32 STEP_FRACTIONAL = 0.1f; +const LLUnit TIGHTEN_THROTTLE_THRESHOLD(3.0f); // packet loss % per s +const LLUnit EASE_THROTTLE_THRESHOLD(0.5f); // packet loss % per s +const F32 DYNAMIC_UPDATE_DURATION = 5.0f; // seconds + +LLViewerThrottle gViewerThrottle; + +// static +const std:: string LLViewerThrottle::sNames[TC_EOF] = { + "Resend", + "Land", + "Wind", + "Cloud", + "Task", + "Texture", + "Asset" + }; + + +// Bandwidth settings for different bit rates, they're interpolated/extrapolated. +// Resend Land Wind Cloud Task Texture Asset +const F32 BW_PRESET_50[TC_EOF] = { 5, 10, 3, 3, 10, 10, 9 }; +const F32 BW_PRESET_300[TC_EOF] = { 30, 40, 9, 9, 86, 86, 40 }; +const F32 BW_PRESET_500[TC_EOF] = { 50, 70, 14, 14, 136, 136, 80 }; +const F32 BW_PRESET_1000[TC_EOF] = { 100, 100, 20, 20, 310, 310, 140 }; + +LLViewerThrottleGroup::LLViewerThrottleGroup() +{ + S32 i; + for (i = 0; i < TC_EOF; i++) + { + mThrottles[i] = 0.f; + } + mThrottleTotal = 0.f; +} + + +LLViewerThrottleGroup::LLViewerThrottleGroup(const F32 settings[TC_EOF]) +{ + mThrottleTotal = 0.f; + S32 i; + for (i = 0; i < TC_EOF; i++) + { + mThrottles[i] = settings[i]; + mThrottleTotal += settings[i]; + } +} + + +LLViewerThrottleGroup LLViewerThrottleGroup::operator*(const F32 frac) const +{ + LLViewerThrottleGroup res; + res.mThrottleTotal = 0.f; + + S32 i; + for (i = 0; i < TC_EOF; i++) + { + res.mThrottles[i] = mThrottles[i] * frac; + res.mThrottleTotal += res.mThrottles[i]; + } + + return res; +} + + +LLViewerThrottleGroup LLViewerThrottleGroup::operator+(const LLViewerThrottleGroup &b) const +{ + LLViewerThrottleGroup res; + res.mThrottleTotal = 0.f; + + S32 i; + for (i = 0; i < TC_EOF; i++) + { + res.mThrottles[i] = mThrottles[i] + b.mThrottles[i]; + res.mThrottleTotal += res.mThrottles[i]; + } + + return res; +} + + +LLViewerThrottleGroup LLViewerThrottleGroup::operator-(const LLViewerThrottleGroup &b) const +{ + LLViewerThrottleGroup res; + res.mThrottleTotal = 0.f; + + S32 i; + for (i = 0; i < TC_EOF; i++) + { + res.mThrottles[i] = mThrottles[i] - b.mThrottles[i]; + res.mThrottleTotal += res.mThrottles[i]; + } + + return res; +} + + +void LLViewerThrottleGroup::sendToSim() const +{ + LL_INFOS() << "Sending throttle settings, total BW " << mThrottleTotal << LL_ENDL; + LLMessageSystem* msg = gMessageSystem; + + msg->newMessageFast(_PREHASH_AgentThrottle); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addU32Fast(_PREHASH_CircuitCode, msg->mOurCircuitCode); + + msg->nextBlockFast(_PREHASH_Throttle); + msg->addU32Fast(_PREHASH_GenCounter, 0); + + // Pack up the throttle data + U8 tmp[64]; + LLDataPackerBinaryBuffer dp(tmp, MAX_THROTTLE_SIZE); + S32 i; + for (i = 0; i < TC_EOF; i++) + { + //sim wants BPS, not KBPS + dp.packF32(mThrottles[i] * 1024.0f, "Throttle"); + } + S32 len = dp.getCurrentSize(); + msg->addBinaryDataFast(_PREHASH_Throttles, tmp, len); + + gAgent.sendReliableMessage(); +} + + +void LLViewerThrottleGroup::dump() +{ + S32 i; + for (i = 0; i < TC_EOF; i++) + { + LL_DEBUGS("Throttle") << LLViewerThrottle::sNames[i] << ": " << mThrottles[i] << LL_ENDL; + } + LL_DEBUGS("Throttle") << "Total: " << mThrottleTotal << LL_ENDL; +} + +class LLBPSListener : public LLSimpleListener +{ +public: + virtual bool handleEvent(LLPointer event, const LLSD& userdata) + { + gViewerThrottle.setMaxBandwidth((F32) event->getValue().asReal()*1024); + return true; + } +}; + +LLViewerThrottle::LLViewerThrottle() : + mMaxBandwidth(0.f), + mCurrentBandwidth(0.f), + mThrottleFrac(1.f) +{ + // Need to be pushed on in bandwidth order + mPresets.push_back(LLViewerThrottleGroup(BW_PRESET_50)); + mPresets.push_back(LLViewerThrottleGroup(BW_PRESET_300)); + mPresets.push_back(LLViewerThrottleGroup(BW_PRESET_500)); + mPresets.push_back(LLViewerThrottleGroup(BW_PRESET_1000)); +} + + +void LLViewerThrottle::setMaxBandwidth(F32 kbits_per_second, bool from_event) +{ + if (!from_event) + { + gSavedSettings.setF32("ThrottleBandwidthKBPS", kbits_per_second); + } + gViewerThrottle.load(); + + if (gAgent.getRegion()) + { + gViewerThrottle.sendToSim(); + } +} + +void LLViewerThrottle::load() +{ + mMaxBandwidth = gSavedSettings.getF32("ThrottleBandwidthKBPS")*1024; + resetDynamicThrottle(); + mCurrent.dump(); +} + + +void LLViewerThrottle::save() const +{ + gSavedSettings.setF32("ThrottleBandwidthKBPS", mMaxBandwidth/1024); +} + + +void LLViewerThrottle::sendToSim() const +{ + mCurrent.sendToSim(); +} + + +LLViewerThrottleGroup LLViewerThrottle::getThrottleGroup(const F32 bandwidth_kbps) +{ + //Clamp the bandwidth users can set. + F32 set_bandwidth = llclamp(bandwidth_kbps, MIN_BANDWIDTH, MAX_BANDWIDTH); + + S32 count = mPresets.size(); + + S32 i; + for (i = 0; i < count; i++) + { + if (mPresets[i].getTotal() > set_bandwidth) + { + break; + } + } + + if (i == 0) + { + // We return the minimum if it's less than the minimum + return mPresets[0]; + } + else if (i == count) + { + // Higher than the highest preset, we extrapolate out based on the + // last two presets. This allows us to keep certain throttle channels from + // growing in bandwidth + F32 delta_bw = set_bandwidth - mPresets[count-1].getTotal(); + LLViewerThrottleGroup delta_throttle = mPresets[count - 1] - mPresets[count - 2]; + F32 delta_total = delta_throttle.getTotal(); + F32 delta_frac = delta_bw / delta_total; + delta_throttle = delta_throttle * delta_frac; + return mPresets[count-1] + delta_throttle; + } + else + { + // In between two presets, just interpolate + F32 delta_bw = set_bandwidth - mPresets[i - 1].getTotal(); + LLViewerThrottleGroup delta_throttle = mPresets[i] - mPresets[i - 1]; + F32 delta_total = delta_throttle.getTotal(); + F32 delta_frac = delta_bw / delta_total; + delta_throttle = delta_throttle * delta_frac; + return mPresets[i - 1] + delta_throttle; + } +} + + +// static +void LLViewerThrottle::resetDynamicThrottle() +{ + mThrottleFrac = MAX_FRACTIONAL; + + mCurrentBandwidth = mMaxBandwidth*MAX_FRACTIONAL; + mCurrent = getThrottleGroup(mCurrentBandwidth / 1024.0f); +} + +void LLViewerThrottle::updateDynamicThrottle() +{ + if (mUpdateTimer.getElapsedTimeF32() < DYNAMIC_UPDATE_DURATION) + { + return; + } + mUpdateTimer.reset(); + + LLUnit mean_packets_lost = LLViewerStats::instance().getRecording().getMean(LLStatViewer::PACKETS_LOST_PERCENT); + if (mean_packets_lost > TIGHTEN_THROTTLE_THRESHOLD) + { + if (mThrottleFrac <= MIN_FRACTIONAL || mCurrentBandwidth / 1024.0f <= MIN_BANDWIDTH) + { + return; + } + mThrottleFrac -= STEP_FRACTIONAL; + mThrottleFrac = llmax(MIN_FRACTIONAL, mThrottleFrac); + mCurrentBandwidth = mMaxBandwidth * mThrottleFrac; + mCurrent = getThrottleGroup(mCurrentBandwidth / 1024.0f); + mCurrent.sendToSim(); + LL_INFOS() << "Tightening network throttle to " << mCurrentBandwidth << LL_ENDL; + } + else if (mean_packets_lost <= EASE_THROTTLE_THRESHOLD) + { + if (mThrottleFrac >= MAX_FRACTIONAL || mCurrentBandwidth / 1024.0f >= MAX_BANDWIDTH) + { + return; + } + mThrottleFrac += STEP_FRACTIONAL; + mThrottleFrac = llmin(MAX_FRACTIONAL, mThrottleFrac); + mCurrentBandwidth = mMaxBandwidth * mThrottleFrac; + mCurrent = getThrottleGroup(mCurrentBandwidth/1024.0f); + mCurrent.sendToSim(); + LL_INFOS() << "Easing network throttle to " << mCurrentBandwidth << LL_ENDL; + } +} diff --git a/indra/newview/llviewerthrottle.h b/indra/newview/llviewerthrottle.h index 179134f839..28a24d04fc 100644 --- a/indra/newview/llviewerthrottle.h +++ b/indra/newview/llviewerthrottle.h @@ -1,90 +1,90 @@ -/** - * @file llviewerthrottle.h - * @brief LLViewerThrottle class header file - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVIEWERTHROTTLE_H -#define LL_LLVIEWERTHROTTLE_H - -#include - -#include "llstring.h" -#include "llframetimer.h" -#include "llthrottle.h" - -class LLViewerThrottleGroup -{ - LLViewerThrottleGroup(); - LLViewerThrottleGroup(const F32 settings[TC_EOF]); - - LLViewerThrottleGroup operator*(const F32 frac) const; - LLViewerThrottleGroup operator+(const LLViewerThrottleGroup &b) const; - LLViewerThrottleGroup operator-(const LLViewerThrottleGroup &b) const; - - F32 getTotal() { return mThrottleTotal; } - void sendToSim() const; - - void dump(); - - friend class LLViewerThrottle; -protected: - F32 mThrottles[TC_EOF]; - F32 mThrottleTotal; -}; - -class LLViewerThrottle -{ -public: - LLViewerThrottle(); - - void setMaxBandwidth(F32 kbits_per_second, bool from_event = false); - - void load(); - void save() const; - void sendToSim() const; - - F32 getMaxBandwidth()const { return mMaxBandwidth; } - F32 getCurrentBandwidth() const { return mCurrentBandwidth; } - - void updateDynamicThrottle(); - void resetDynamicThrottle(); - - LLViewerThrottleGroup getThrottleGroup(const F32 bandwidth_kbps); - - static const std::string sNames[TC_EOF]; -protected: - F32 mMaxBandwidth; - F32 mCurrentBandwidth; - - LLViewerThrottleGroup mCurrent; - - std::vector mPresets; - - LLFrameTimer mUpdateTimer; - F32 mThrottleFrac; -}; - -extern LLViewerThrottle gViewerThrottle; - -#endif // LL_LLVIEWERTHROTTLE_H +/** + * @file llviewerthrottle.h + * @brief LLViewerThrottle class header file + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVIEWERTHROTTLE_H +#define LL_LLVIEWERTHROTTLE_H + +#include + +#include "llstring.h" +#include "llframetimer.h" +#include "llthrottle.h" + +class LLViewerThrottleGroup +{ + LLViewerThrottleGroup(); + LLViewerThrottleGroup(const F32 settings[TC_EOF]); + + LLViewerThrottleGroup operator*(const F32 frac) const; + LLViewerThrottleGroup operator+(const LLViewerThrottleGroup &b) const; + LLViewerThrottleGroup operator-(const LLViewerThrottleGroup &b) const; + + F32 getTotal() { return mThrottleTotal; } + void sendToSim() const; + + void dump(); + + friend class LLViewerThrottle; +protected: + F32 mThrottles[TC_EOF]; + F32 mThrottleTotal; +}; + +class LLViewerThrottle +{ +public: + LLViewerThrottle(); + + void setMaxBandwidth(F32 kbits_per_second, bool from_event = false); + + void load(); + void save() const; + void sendToSim() const; + + F32 getMaxBandwidth()const { return mMaxBandwidth; } + F32 getCurrentBandwidth() const { return mCurrentBandwidth; } + + void updateDynamicThrottle(); + void resetDynamicThrottle(); + + LLViewerThrottleGroup getThrottleGroup(const F32 bandwidth_kbps); + + static const std::string sNames[TC_EOF]; +protected: + F32 mMaxBandwidth; + F32 mCurrentBandwidth; + + LLViewerThrottleGroup mCurrent; + + std::vector mPresets; + + LLFrameTimer mUpdateTimer; + F32 mThrottleFrac; +}; + +extern LLViewerThrottle gViewerThrottle; + +#endif // LL_LLVIEWERTHROTTLE_H diff --git a/indra/newview/llviewerwearable.cpp b/indra/newview/llviewerwearable.cpp index 835a4e876e..583fb25330 100644 --- a/indra/newview/llviewerwearable.cpp +++ b/indra/newview/llviewerwearable.cpp @@ -1,623 +1,623 @@ -/** - * @file llviewerwearable.cpp - * @brief LLViewerWearable class implementation - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llagent.h" -#include "llagentcamera.h" -#include "llagentwearables.h" -#include "llfloatersidepanelcontainer.h" -#include "lllocaltextureobject.h" -#include "llnotificationsutil.h" -#include "llsidepanelappearance.h" -#include "lltextureentry.h" -#include "llviewertexlayer.h" -#include "llvoavatarself.h" -#include "llavatarappearancedefines.h" -#include "llviewerwearable.h" -#include "llviewercontrol.h" -#include "llviewerregion.h" - -using namespace LLAvatarAppearanceDefines; - -// support class - remove for 2.1 (hackity hack hack) -class LLOverrideBakedTextureUpdate -{ -public: - LLOverrideBakedTextureUpdate(bool temp_state) - { - U32 num_bakes = (U32) LLAvatarAppearanceDefines::BAKED_NUM_INDICES; - for( U32 index = 0; index < num_bakes; ++index ) - { - composite_enabled[index] = gAgentAvatarp->isCompositeUpdateEnabled(index); - } - gAgentAvatarp->setCompositeUpdatesEnabled(temp_state); - } - - ~LLOverrideBakedTextureUpdate() - { - U32 num_bakes = (U32)LLAvatarAppearanceDefines::BAKED_NUM_INDICES; - for( U32 index = 0; index < num_bakes; ++index ) - { - gAgentAvatarp->setCompositeUpdatesEnabled(index, composite_enabled[index]); - } - } -private: - bool composite_enabled[LLAvatarAppearanceDefines::BAKED_NUM_INDICES]; -}; - -// Private local functions -static std::string asset_id_to_filename(const LLUUID &asset_id, const ELLPath dir_spec); - -LLViewerWearable::LLViewerWearable(const LLTransactionID& transaction_id) : - LLWearable(), - mVolatile(false) -{ - mTransactionID = transaction_id; - mAssetID = mTransactionID.makeAssetID(gAgent.getSecureSessionID()); -} - -LLViewerWearable::LLViewerWearable(const LLAssetID& asset_id) : - LLWearable(), - mVolatile(false) -{ - mAssetID = asset_id; - mTransactionID.setNull(); -} - -// virtual -LLViewerWearable::~LLViewerWearable() -{ -} - -// virtual -LLWearable::EImportResult LLViewerWearable::importStream( std::istream& input_stream, LLAvatarAppearance* avatarp ) -{ - // suppress texlayerset updates while wearables are being imported. Layersets will be updated - // when the wearables are "worn", not loaded. Note state will be restored when this object is destroyed. - LLOverrideBakedTextureUpdate stop_bakes(false); - - LLWearable::EImportResult result = LLWearable::importStream(input_stream, avatarp); - if (LLWearable::FAILURE == result) return result; - if (LLWearable::BAD_HEADER == result) - { - // Shouldn't really log the asset id for security reasons, but - // we need it in this case. - LL_WARNS() << "Bad Wearable asset header: " << mAssetID << LL_ENDL; - return result; - } - - LLStringUtil::truncate(mName, DB_INV_ITEM_NAME_STR_LEN ); - LLStringUtil::truncate(mDescription, DB_INV_ITEM_DESC_STR_LEN ); - - te_map_t::const_iterator iter = mTEMap.begin(); - te_map_t::const_iterator end = mTEMap.end(); - for (; iter != end; ++iter) - { - S32 te = iter->first; - LLLocalTextureObject* lto = iter->second; - LLUUID textureid = LLUUID::null; - if (lto) - { - textureid = lto->getID(); - } - - LLViewerFetchedTexture* image = LLViewerTextureManager::getFetchedTexture( textureid ); - if(gSavedSettings.getBOOL("DebugAvatarLocalTexLoadedTime")) - { - image->setLoadedCallback(LLVOAvatarSelf::debugOnTimingLocalTexLoaded,0,true,false, new LLVOAvatarSelf::LLAvatarTexData(textureid, (LLAvatarAppearanceDefines::ETextureIndex)te), NULL); - } - } - - return result; -} - - -// Avatar parameter and texture definitions can change over time. -// This function returns true if parameters or textures have been added or removed -// since this wearable was created. -bool LLViewerWearable::isOldVersion() const -{ - if (!isAgentAvatarValid()) return false; - - if( LLWearable::sCurrentDefinitionVersion < mDefinitionVersion ) - { - LL_WARNS() << "Wearable asset has newer version (" << mDefinitionVersion << ") than XML (" << LLWearable::sCurrentDefinitionVersion << ")" << LL_ENDL; - llassert(0); - } - - if( LLWearable::sCurrentDefinitionVersion != mDefinitionVersion ) - { - return true; - } - - S32 param_count = 0; - for( LLViewerVisualParam* param = (LLViewerVisualParam*) gAgentAvatarp->getFirstVisualParam(); - param; - param = (LLViewerVisualParam*) gAgentAvatarp->getNextVisualParam() ) - { - if( (param->getWearableType() == mType) && (param->isTweakable() ) ) - { - param_count++; - if( !is_in_map(mVisualParamIndexMap, param->getID() ) ) - { - return true; - } - } - } - if( param_count != mVisualParamIndexMap.size() ) - { - return true; - } - - - S32 te_count = 0; - for( S32 te = 0; te < TEX_NUM_INDICES; te++ ) - { - if (LLAvatarAppearance::getDictionary()->getTEWearableType((ETextureIndex) te) == mType) - { - te_count++; - if( !is_in_map(mTEMap, te ) ) - { - return true; - } - } - } - if( te_count != mTEMap.size() ) - { - return true; - } - - return false; -} - -// Avatar parameter and texture definitions can change over time. -// * If parameters or textures have been REMOVED since the wearable was created, -// they're just ignored, so we consider the wearable clean even though isOldVersion() -// will return true. -// * If parameters or textures have been ADDED since the wearable was created, -// they are taken to have default values, so we consider the wearable clean -// only if those values are the same as the defaults. -bool LLViewerWearable::isDirty() const -{ - if (!isAgentAvatarValid()) return false; - - for( LLViewerVisualParam* param = (LLViewerVisualParam*) gAgentAvatarp->getFirstVisualParam(); - param; - param = (LLViewerVisualParam*) gAgentAvatarp->getNextVisualParam() ) - { - if( (param->getWearableType() == mType) - && (param->isTweakable() ) - && !param->getCrossWearable()) - { - F32 current_weight = getVisualParamWeight(param->getID()); - current_weight = llclamp( current_weight, param->getMinWeight(), param->getMaxWeight() ); - F32 saved_weight = get_if_there(mSavedVisualParamMap, param->getID(), param->getDefaultWeight()); - saved_weight = llclamp( saved_weight, param->getMinWeight(), param->getMaxWeight() ); - - U8 a = F32_to_U8( saved_weight, param->getMinWeight(), param->getMaxWeight() ); - U8 b = F32_to_U8( current_weight, param->getMinWeight(), param->getMaxWeight() ); - if( a != b ) - { - return true; - } - } - } - - for( S32 te = 0; te < TEX_NUM_INDICES; te++ ) - { - if (LLAvatarAppearance::getDictionary()->getTEWearableType((ETextureIndex) te) == mType) - { - te_map_t::const_iterator current_iter = mTEMap.find(te); - if(current_iter != mTEMap.end()) - { - const LLUUID& current_image_id = current_iter->second->getID(); - te_map_t::const_iterator saved_iter = mSavedTEMap.find(te); - if(saved_iter != mSavedTEMap.end()) - { - const LLUUID& saved_image_id = saved_iter->second->getID(); - if (saved_image_id != current_image_id) - { - // saved vs current images are different, wearable is dirty - return true; - } - } - else - { - // image found in current image list but not saved image list - return true; - } - } - } - } - - return false; -} - - -void LLViewerWearable::setParamsToDefaults() -{ - if (!isAgentAvatarValid()) return; - - for( LLVisualParam* param = gAgentAvatarp->getFirstVisualParam(); param; param = gAgentAvatarp->getNextVisualParam() ) - { - if( (((LLViewerVisualParam*)param)->getWearableType() == mType ) && (param->isTweakable() ) ) - { - setVisualParamWeight(param->getID(),param->getDefaultWeight()); - } - } -} - -void LLViewerWearable::setTexturesToDefaults() -{ - for( S32 te = 0; te < TEX_NUM_INDICES; te++ ) - { - if (LLAvatarAppearance::getDictionary()->getTEWearableType((ETextureIndex) te) == mType) - { - LLUUID id = getDefaultTextureImageID((ETextureIndex) te); - LLViewerFetchedTexture * image = LLViewerTextureManager::getFetchedTexture( id ); - if( mTEMap.find(te) == mTEMap.end() ) - { - mTEMap[te] = new LLLocalTextureObject(image, id); - createLayers(te, gAgentAvatarp); - } - else - { - // Local Texture Object already created, just set image and UUID - LLLocalTextureObject *lto = mTEMap[te]; - lto->setID(id); - lto->setImage(image); - } - } - } -} - - -// virtual -LLUUID LLViewerWearable::getDefaultTextureImageID(ETextureIndex index) const -{ - const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = LLAvatarAppearance::getDictionary()->getTexture(index); - const std::string &default_image_name = texture_dict ? texture_dict->mDefaultImageName : ""; - if (default_image_name == "") - { - return IMG_DEFAULT_AVATAR; - } - else - { - return LLUUID(gSavedSettings.getString(default_image_name)); - } -} - - -// Updates the user's avatar's appearance -//virtual -void LLViewerWearable::writeToAvatar(LLAvatarAppearance *avatarp) -{ - LLVOAvatarSelf* viewer_avatar = dynamic_cast(avatarp); - - if (!avatarp || !viewer_avatar) return; - - if (!viewer_avatar->isValid()) return; - - ESex old_sex = avatarp->getSex(); - - LLWearable::writeToAvatar(avatarp); - - - // Pull texture entries - for( S32 te = 0; te < TEX_NUM_INDICES; te++ ) - { - if (LLAvatarAppearance::getDictionary()->getTEWearableType((ETextureIndex) te) == mType) - { - te_map_t::const_iterator iter = mTEMap.find(te); - LLUUID image_id; - if(iter != mTEMap.end()) - { - image_id = iter->second->getID(); - } - else - { - image_id = getDefaultTextureImageID((ETextureIndex) te); - } - LLViewerTexture* image = LLViewerTextureManager::getFetchedTexture( image_id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE ); - // MULTI-WEARABLE: assume index 0 will be used when writing to avatar. TODO: eliminate the need for this. - viewer_avatar->setLocalTextureTE(te, image, 0); - } - } - - ESex new_sex = avatarp->getSex(); - if( old_sex != new_sex ) - { - viewer_avatar->updateSexDependentLayerSets(); - } -} - - -// Updates the user's avatar's appearance, replacing this wearables' parameters and textures with default values. -// static -void LLViewerWearable::removeFromAvatar( LLWearableType::EType type) -{ - if (!isAgentAvatarValid()) return; - - // You can't just remove body parts. - if( (type == LLWearableType::WT_SHAPE) || - (type == LLWearableType::WT_SKIN) || - (type == LLWearableType::WT_HAIR) || - (type == LLWearableType::WT_EYES) ) - { - return; - } - - // Pull params - for( LLVisualParam* param = gAgentAvatarp->getFirstVisualParam(); param; param = gAgentAvatarp->getNextVisualParam() ) - { - if( (((LLViewerVisualParam*)param)->getWearableType() == type) && (param->isTweakable() ) ) - { - S32 param_id = param->getID(); - gAgentAvatarp->setVisualParamWeight( param_id, param->getDefaultWeight()); - } - } - - if(gAgentCamera.cameraCustomizeAvatar()) - { - LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "edit_outfit")); - } - - gAgentAvatarp->updateVisualParams(); - gAgentAvatarp->wearableUpdated(type); -} - -// Does not copy mAssetID. -// Definition version is current: removes obsolete enties and creates default values for new ones. -void LLViewerWearable::copyDataFrom(const LLViewerWearable* src) -{ - if (!isAgentAvatarValid()) return; - - mDefinitionVersion = LLWearable::sCurrentDefinitionVersion; - - mName = src->mName; - mDescription = src->mDescription; - mPermissions = src->mPermissions; - mSaleInfo = src->mSaleInfo; - - setType(src->mType, gAgentAvatarp); - - mSavedVisualParamMap.clear(); - // Deep copy of mVisualParamMap (copies only those params that are current, filling in defaults where needed) - for (LLViewerVisualParam* param = (LLViewerVisualParam*) gAgentAvatarp->getFirstVisualParam(); - param; - param = (LLViewerVisualParam*) gAgentAvatarp->getNextVisualParam() ) - { - if( (param->getWearableType() == mType) ) - { - S32 id = param->getID(); - F32 weight = src->getVisualParamWeight(id); - mSavedVisualParamMap[id] = weight; - } - } - - destroyTextures(); - // Deep copy of mTEMap (copies only those tes that are current, filling in defaults where needed) - for (S32 te = 0; te < TEX_NUM_INDICES; te++) - { - if (LLAvatarAppearance::getDictionary()->getTEWearableType((ETextureIndex) te) == mType) - { - te_map_t::const_iterator iter = src->mTEMap.find(te); - LLUUID image_id; - LLViewerFetchedTexture *image = NULL; - if(iter != src->mTEMap.end()) - { - image = dynamic_cast (src->getLocalTextureObject(te)->getImage()); - image_id = src->getLocalTextureObject(te)->getID(); - mTEMap[te] = new LLLocalTextureObject(image, image_id); - mSavedTEMap[te] = new LLLocalTextureObject(image, image_id); - mTEMap[te]->setBakedReady(src->getLocalTextureObject(te)->getBakedReady()); - mTEMap[te]->setDiscard(src->getLocalTextureObject(te)->getDiscard()); - } - else - { - image_id = getDefaultTextureImageID((ETextureIndex) te); - image = LLViewerTextureManager::getFetchedTexture( image_id ); - mTEMap[te] = new LLLocalTextureObject(image, image_id); - mSavedTEMap[te] = new LLLocalTextureObject(image, image_id); - } - createLayers(te, gAgentAvatarp); - } - } - - // Probably reduntant, but ensure that the newly created wearable is not dirty by setting current value of params in new wearable - // to be the same as the saved values (which were loaded from src at param->cloneParam(this)) - revertValuesWithoutUpdate(); -} - -void LLViewerWearable::setItemID(const LLUUID& item_id) -{ - mItemID = item_id; -} - -void LLViewerWearable::revertValues() -{ - LLWearable::revertValues(); - - LLSidepanelAppearance *panel = dynamic_cast(LLFloaterSidePanelContainer::findPanel("appearance")); - if( panel ) - { - panel->updateScrollingPanelList(); - } -} - -void LLViewerWearable::revertValuesWithoutUpdate() -{ - LLWearable::revertValues(); -} - -void LLViewerWearable::saveValues() -{ - LLWearable::saveValues(); - - LLSidepanelAppearance *panel = dynamic_cast(LLFloaterSidePanelContainer::findPanel("appearance")); - if( panel ) - { - panel->updateScrollingPanelList(); - } -} - -// virtual -void LLViewerWearable::setUpdated() const -{ - gInventory.addChangedMask(LLInventoryObserver::LABEL, getItemID()); -} - -void LLViewerWearable::refreshName() -{ - LLUUID item_id = getItemID(); - LLInventoryItem* item = gInventory.getItem(item_id); - if( item ) - { - mName = item->getName(); - } -} - -struct LLWearableSaveData -{ - LLWearableType::EType mType; -}; - -void LLViewerWearable::saveNewAsset() const -{ -// LL_INFOS() << "LLViewerWearable::saveNewAsset() type: " << getTypeName() << LL_ENDL; - //LL_INFOS() << *this << LL_ENDL; - - const std::string filename = asset_id_to_filename(mAssetID, LL_PATH_CACHE); - if(! exportFile(filename)) - { - std::string buffer = llformat("Unable to save '%s' to wearable file.", mName.c_str()); - LL_WARNS() << buffer << LL_ENDL; - - LLSD args; - args["NAME"] = mName; - LLNotificationsUtil::add("CannotSaveWearableOutOfSpace", args); - return; - } - - if (gSavedSettings.getBOOL("LogWearableAssetSave")) - { - const std::string log_filename = asset_id_to_filename(mAssetID, LL_PATH_LOGS); - exportFile(log_filename); - } - - // save it out to database - if( gAssetStorage ) - { - /* - std::string url = gAgent.getRegion()->getCapability("NewAgentInventory"); - if (!url.empty()) - { - LL_INFOS() << "Update Agent Inventory via capability" << LL_ENDL; - LLSD body; - body["folder_id"] = gInventory.findCategoryUUIDForType(LLFolderType::assetToFolderType(getAssetType())); - body["asset_type"] = LLAssetType::lookup(getAssetType()); - body["inventory_type"] = LLInventoryType::lookup(LLInventoryType::IT_WEARABLE); - body["name"] = getName(); - body["description"] = getDescription(); - LLHTTPClient::post(url, body, new LLNewAgentInventoryResponder(body, filename)); - } - else - { - } - */ - LLWearableSaveData* data = new LLWearableSaveData; - data->mType = mType; - gAssetStorage->storeAssetData(filename, mTransactionID, getAssetType(), - &LLViewerWearable::onSaveNewAssetComplete, - (void*)data); - } -} - -// static -void LLViewerWearable::onSaveNewAssetComplete(const LLUUID& new_asset_id, void* userdata, S32 status, LLExtStat ext_status) // StoreAssetData callback (fixed) -{ - LLWearableSaveData* data = (LLWearableSaveData*)userdata; - const std::string& type_name = LLWearableType::getInstance()->getTypeName(data->mType); - if(0 == status) - { - // Success - LL_INFOS() << "Saved wearable " << type_name << LL_ENDL; - } - else - { - std::string buffer = llformat("Unable to save %s to central asset store.", type_name.c_str()); - LL_WARNS() << buffer << " Status: " << status << LL_ENDL; - LLSD args; - args["NAME"] = type_name; - LLNotificationsUtil::add("CannotSaveToAssetStore", args); - } - - // Delete temp file - const std::string src_filename = asset_id_to_filename(new_asset_id, LL_PATH_CACHE); - LLFile::remove(src_filename); - - // delete the context data - delete data; - -} - -std::ostream& operator<<(std::ostream &s, const LLViewerWearable &w) -{ - s << "wearable " << LLWearableType::getInstance()->getTypeName(w.mType) << "\n"; - s << " Name: " << w.mName << "\n"; - s << " Desc: " << w.mDescription << "\n"; - //w.mPermissions - //w.mSaleInfo - - s << " Params:" << "\n"; - for (LLWearable::visual_param_index_map_t::const_iterator iter = w.mVisualParamIndexMap.begin(); - iter != w.mVisualParamIndexMap.end(); ++iter) - { - S32 param_id = iter->first; - LLVisualParam *wearable_param = iter->second; - F32 param_weight = wearable_param->getWeight(); - s << " " << param_id << " " << param_weight << "\n"; - } - - s << " Textures:" << "\n"; - for (LLViewerWearable::te_map_t::const_iterator iter = w.mTEMap.begin(); - iter != w.mTEMap.end(); ++iter) - { - S32 te = iter->first; - const LLUUID& image_id = iter->second->getID(); - s << " " << te << " " << image_id << "\n"; - } - return s; -} - -std::string asset_id_to_filename(const LLUUID &asset_id, const ELLPath dir_spec) -{ - std::string asset_id_string; - asset_id.toString(asset_id_string); - std::string filename = gDirUtilp->getExpandedFilename(dir_spec,asset_id_string) + ".wbl"; - return filename; -} +/** + * @file llviewerwearable.cpp + * @brief LLViewerWearable class implementation + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llagent.h" +#include "llagentcamera.h" +#include "llagentwearables.h" +#include "llfloatersidepanelcontainer.h" +#include "lllocaltextureobject.h" +#include "llnotificationsutil.h" +#include "llsidepanelappearance.h" +#include "lltextureentry.h" +#include "llviewertexlayer.h" +#include "llvoavatarself.h" +#include "llavatarappearancedefines.h" +#include "llviewerwearable.h" +#include "llviewercontrol.h" +#include "llviewerregion.h" + +using namespace LLAvatarAppearanceDefines; + +// support class - remove for 2.1 (hackity hack hack) +class LLOverrideBakedTextureUpdate +{ +public: + LLOverrideBakedTextureUpdate(bool temp_state) + { + U32 num_bakes = (U32) LLAvatarAppearanceDefines::BAKED_NUM_INDICES; + for( U32 index = 0; index < num_bakes; ++index ) + { + composite_enabled[index] = gAgentAvatarp->isCompositeUpdateEnabled(index); + } + gAgentAvatarp->setCompositeUpdatesEnabled(temp_state); + } + + ~LLOverrideBakedTextureUpdate() + { + U32 num_bakes = (U32)LLAvatarAppearanceDefines::BAKED_NUM_INDICES; + for( U32 index = 0; index < num_bakes; ++index ) + { + gAgentAvatarp->setCompositeUpdatesEnabled(index, composite_enabled[index]); + } + } +private: + bool composite_enabled[LLAvatarAppearanceDefines::BAKED_NUM_INDICES]; +}; + +// Private local functions +static std::string asset_id_to_filename(const LLUUID &asset_id, const ELLPath dir_spec); + +LLViewerWearable::LLViewerWearable(const LLTransactionID& transaction_id) : + LLWearable(), + mVolatile(false) +{ + mTransactionID = transaction_id; + mAssetID = mTransactionID.makeAssetID(gAgent.getSecureSessionID()); +} + +LLViewerWearable::LLViewerWearable(const LLAssetID& asset_id) : + LLWearable(), + mVolatile(false) +{ + mAssetID = asset_id; + mTransactionID.setNull(); +} + +// virtual +LLViewerWearable::~LLViewerWearable() +{ +} + +// virtual +LLWearable::EImportResult LLViewerWearable::importStream( std::istream& input_stream, LLAvatarAppearance* avatarp ) +{ + // suppress texlayerset updates while wearables are being imported. Layersets will be updated + // when the wearables are "worn", not loaded. Note state will be restored when this object is destroyed. + LLOverrideBakedTextureUpdate stop_bakes(false); + + LLWearable::EImportResult result = LLWearable::importStream(input_stream, avatarp); + if (LLWearable::FAILURE == result) return result; + if (LLWearable::BAD_HEADER == result) + { + // Shouldn't really log the asset id for security reasons, but + // we need it in this case. + LL_WARNS() << "Bad Wearable asset header: " << mAssetID << LL_ENDL; + return result; + } + + LLStringUtil::truncate(mName, DB_INV_ITEM_NAME_STR_LEN ); + LLStringUtil::truncate(mDescription, DB_INV_ITEM_DESC_STR_LEN ); + + te_map_t::const_iterator iter = mTEMap.begin(); + te_map_t::const_iterator end = mTEMap.end(); + for (; iter != end; ++iter) + { + S32 te = iter->first; + LLLocalTextureObject* lto = iter->second; + LLUUID textureid = LLUUID::null; + if (lto) + { + textureid = lto->getID(); + } + + LLViewerFetchedTexture* image = LLViewerTextureManager::getFetchedTexture( textureid ); + if(gSavedSettings.getBOOL("DebugAvatarLocalTexLoadedTime")) + { + image->setLoadedCallback(LLVOAvatarSelf::debugOnTimingLocalTexLoaded,0,true,false, new LLVOAvatarSelf::LLAvatarTexData(textureid, (LLAvatarAppearanceDefines::ETextureIndex)te), NULL); + } + } + + return result; +} + + +// Avatar parameter and texture definitions can change over time. +// This function returns true if parameters or textures have been added or removed +// since this wearable was created. +bool LLViewerWearable::isOldVersion() const +{ + if (!isAgentAvatarValid()) return false; + + if( LLWearable::sCurrentDefinitionVersion < mDefinitionVersion ) + { + LL_WARNS() << "Wearable asset has newer version (" << mDefinitionVersion << ") than XML (" << LLWearable::sCurrentDefinitionVersion << ")" << LL_ENDL; + llassert(0); + } + + if( LLWearable::sCurrentDefinitionVersion != mDefinitionVersion ) + { + return true; + } + + S32 param_count = 0; + for( LLViewerVisualParam* param = (LLViewerVisualParam*) gAgentAvatarp->getFirstVisualParam(); + param; + param = (LLViewerVisualParam*) gAgentAvatarp->getNextVisualParam() ) + { + if( (param->getWearableType() == mType) && (param->isTweakable() ) ) + { + param_count++; + if( !is_in_map(mVisualParamIndexMap, param->getID() ) ) + { + return true; + } + } + } + if( param_count != mVisualParamIndexMap.size() ) + { + return true; + } + + + S32 te_count = 0; + for( S32 te = 0; te < TEX_NUM_INDICES; te++ ) + { + if (LLAvatarAppearance::getDictionary()->getTEWearableType((ETextureIndex) te) == mType) + { + te_count++; + if( !is_in_map(mTEMap, te ) ) + { + return true; + } + } + } + if( te_count != mTEMap.size() ) + { + return true; + } + + return false; +} + +// Avatar parameter and texture definitions can change over time. +// * If parameters or textures have been REMOVED since the wearable was created, +// they're just ignored, so we consider the wearable clean even though isOldVersion() +// will return true. +// * If parameters or textures have been ADDED since the wearable was created, +// they are taken to have default values, so we consider the wearable clean +// only if those values are the same as the defaults. +bool LLViewerWearable::isDirty() const +{ + if (!isAgentAvatarValid()) return false; + + for( LLViewerVisualParam* param = (LLViewerVisualParam*) gAgentAvatarp->getFirstVisualParam(); + param; + param = (LLViewerVisualParam*) gAgentAvatarp->getNextVisualParam() ) + { + if( (param->getWearableType() == mType) + && (param->isTweakable() ) + && !param->getCrossWearable()) + { + F32 current_weight = getVisualParamWeight(param->getID()); + current_weight = llclamp( current_weight, param->getMinWeight(), param->getMaxWeight() ); + F32 saved_weight = get_if_there(mSavedVisualParamMap, param->getID(), param->getDefaultWeight()); + saved_weight = llclamp( saved_weight, param->getMinWeight(), param->getMaxWeight() ); + + U8 a = F32_to_U8( saved_weight, param->getMinWeight(), param->getMaxWeight() ); + U8 b = F32_to_U8( current_weight, param->getMinWeight(), param->getMaxWeight() ); + if( a != b ) + { + return true; + } + } + } + + for( S32 te = 0; te < TEX_NUM_INDICES; te++ ) + { + if (LLAvatarAppearance::getDictionary()->getTEWearableType((ETextureIndex) te) == mType) + { + te_map_t::const_iterator current_iter = mTEMap.find(te); + if(current_iter != mTEMap.end()) + { + const LLUUID& current_image_id = current_iter->second->getID(); + te_map_t::const_iterator saved_iter = mSavedTEMap.find(te); + if(saved_iter != mSavedTEMap.end()) + { + const LLUUID& saved_image_id = saved_iter->second->getID(); + if (saved_image_id != current_image_id) + { + // saved vs current images are different, wearable is dirty + return true; + } + } + else + { + // image found in current image list but not saved image list + return true; + } + } + } + } + + return false; +} + + +void LLViewerWearable::setParamsToDefaults() +{ + if (!isAgentAvatarValid()) return; + + for( LLVisualParam* param = gAgentAvatarp->getFirstVisualParam(); param; param = gAgentAvatarp->getNextVisualParam() ) + { + if( (((LLViewerVisualParam*)param)->getWearableType() == mType ) && (param->isTweakable() ) ) + { + setVisualParamWeight(param->getID(),param->getDefaultWeight()); + } + } +} + +void LLViewerWearable::setTexturesToDefaults() +{ + for( S32 te = 0; te < TEX_NUM_INDICES; te++ ) + { + if (LLAvatarAppearance::getDictionary()->getTEWearableType((ETextureIndex) te) == mType) + { + LLUUID id = getDefaultTextureImageID((ETextureIndex) te); + LLViewerFetchedTexture * image = LLViewerTextureManager::getFetchedTexture( id ); + if( mTEMap.find(te) == mTEMap.end() ) + { + mTEMap[te] = new LLLocalTextureObject(image, id); + createLayers(te, gAgentAvatarp); + } + else + { + // Local Texture Object already created, just set image and UUID + LLLocalTextureObject *lto = mTEMap[te]; + lto->setID(id); + lto->setImage(image); + } + } + } +} + + +// virtual +LLUUID LLViewerWearable::getDefaultTextureImageID(ETextureIndex index) const +{ + const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = LLAvatarAppearance::getDictionary()->getTexture(index); + const std::string &default_image_name = texture_dict ? texture_dict->mDefaultImageName : ""; + if (default_image_name == "") + { + return IMG_DEFAULT_AVATAR; + } + else + { + return LLUUID(gSavedSettings.getString(default_image_name)); + } +} + + +// Updates the user's avatar's appearance +//virtual +void LLViewerWearable::writeToAvatar(LLAvatarAppearance *avatarp) +{ + LLVOAvatarSelf* viewer_avatar = dynamic_cast(avatarp); + + if (!avatarp || !viewer_avatar) return; + + if (!viewer_avatar->isValid()) return; + + ESex old_sex = avatarp->getSex(); + + LLWearable::writeToAvatar(avatarp); + + + // Pull texture entries + for( S32 te = 0; te < TEX_NUM_INDICES; te++ ) + { + if (LLAvatarAppearance::getDictionary()->getTEWearableType((ETextureIndex) te) == mType) + { + te_map_t::const_iterator iter = mTEMap.find(te); + LLUUID image_id; + if(iter != mTEMap.end()) + { + image_id = iter->second->getID(); + } + else + { + image_id = getDefaultTextureImageID((ETextureIndex) te); + } + LLViewerTexture* image = LLViewerTextureManager::getFetchedTexture( image_id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE ); + // MULTI-WEARABLE: assume index 0 will be used when writing to avatar. TODO: eliminate the need for this. + viewer_avatar->setLocalTextureTE(te, image, 0); + } + } + + ESex new_sex = avatarp->getSex(); + if( old_sex != new_sex ) + { + viewer_avatar->updateSexDependentLayerSets(); + } +} + + +// Updates the user's avatar's appearance, replacing this wearables' parameters and textures with default values. +// static +void LLViewerWearable::removeFromAvatar( LLWearableType::EType type) +{ + if (!isAgentAvatarValid()) return; + + // You can't just remove body parts. + if( (type == LLWearableType::WT_SHAPE) || + (type == LLWearableType::WT_SKIN) || + (type == LLWearableType::WT_HAIR) || + (type == LLWearableType::WT_EYES) ) + { + return; + } + + // Pull params + for( LLVisualParam* param = gAgentAvatarp->getFirstVisualParam(); param; param = gAgentAvatarp->getNextVisualParam() ) + { + if( (((LLViewerVisualParam*)param)->getWearableType() == type) && (param->isTweakable() ) ) + { + S32 param_id = param->getID(); + gAgentAvatarp->setVisualParamWeight( param_id, param->getDefaultWeight()); + } + } + + if(gAgentCamera.cameraCustomizeAvatar()) + { + LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "edit_outfit")); + } + + gAgentAvatarp->updateVisualParams(); + gAgentAvatarp->wearableUpdated(type); +} + +// Does not copy mAssetID. +// Definition version is current: removes obsolete enties and creates default values for new ones. +void LLViewerWearable::copyDataFrom(const LLViewerWearable* src) +{ + if (!isAgentAvatarValid()) return; + + mDefinitionVersion = LLWearable::sCurrentDefinitionVersion; + + mName = src->mName; + mDescription = src->mDescription; + mPermissions = src->mPermissions; + mSaleInfo = src->mSaleInfo; + + setType(src->mType, gAgentAvatarp); + + mSavedVisualParamMap.clear(); + // Deep copy of mVisualParamMap (copies only those params that are current, filling in defaults where needed) + for (LLViewerVisualParam* param = (LLViewerVisualParam*) gAgentAvatarp->getFirstVisualParam(); + param; + param = (LLViewerVisualParam*) gAgentAvatarp->getNextVisualParam() ) + { + if( (param->getWearableType() == mType) ) + { + S32 id = param->getID(); + F32 weight = src->getVisualParamWeight(id); + mSavedVisualParamMap[id] = weight; + } + } + + destroyTextures(); + // Deep copy of mTEMap (copies only those tes that are current, filling in defaults where needed) + for (S32 te = 0; te < TEX_NUM_INDICES; te++) + { + if (LLAvatarAppearance::getDictionary()->getTEWearableType((ETextureIndex) te) == mType) + { + te_map_t::const_iterator iter = src->mTEMap.find(te); + LLUUID image_id; + LLViewerFetchedTexture *image = NULL; + if(iter != src->mTEMap.end()) + { + image = dynamic_cast (src->getLocalTextureObject(te)->getImage()); + image_id = src->getLocalTextureObject(te)->getID(); + mTEMap[te] = new LLLocalTextureObject(image, image_id); + mSavedTEMap[te] = new LLLocalTextureObject(image, image_id); + mTEMap[te]->setBakedReady(src->getLocalTextureObject(te)->getBakedReady()); + mTEMap[te]->setDiscard(src->getLocalTextureObject(te)->getDiscard()); + } + else + { + image_id = getDefaultTextureImageID((ETextureIndex) te); + image = LLViewerTextureManager::getFetchedTexture( image_id ); + mTEMap[te] = new LLLocalTextureObject(image, image_id); + mSavedTEMap[te] = new LLLocalTextureObject(image, image_id); + } + createLayers(te, gAgentAvatarp); + } + } + + // Probably reduntant, but ensure that the newly created wearable is not dirty by setting current value of params in new wearable + // to be the same as the saved values (which were loaded from src at param->cloneParam(this)) + revertValuesWithoutUpdate(); +} + +void LLViewerWearable::setItemID(const LLUUID& item_id) +{ + mItemID = item_id; +} + +void LLViewerWearable::revertValues() +{ + LLWearable::revertValues(); + + LLSidepanelAppearance *panel = dynamic_cast(LLFloaterSidePanelContainer::findPanel("appearance")); + if( panel ) + { + panel->updateScrollingPanelList(); + } +} + +void LLViewerWearable::revertValuesWithoutUpdate() +{ + LLWearable::revertValues(); +} + +void LLViewerWearable::saveValues() +{ + LLWearable::saveValues(); + + LLSidepanelAppearance *panel = dynamic_cast(LLFloaterSidePanelContainer::findPanel("appearance")); + if( panel ) + { + panel->updateScrollingPanelList(); + } +} + +// virtual +void LLViewerWearable::setUpdated() const +{ + gInventory.addChangedMask(LLInventoryObserver::LABEL, getItemID()); +} + +void LLViewerWearable::refreshName() +{ + LLUUID item_id = getItemID(); + LLInventoryItem* item = gInventory.getItem(item_id); + if( item ) + { + mName = item->getName(); + } +} + +struct LLWearableSaveData +{ + LLWearableType::EType mType; +}; + +void LLViewerWearable::saveNewAsset() const +{ +// LL_INFOS() << "LLViewerWearable::saveNewAsset() type: " << getTypeName() << LL_ENDL; + //LL_INFOS() << *this << LL_ENDL; + + const std::string filename = asset_id_to_filename(mAssetID, LL_PATH_CACHE); + if(! exportFile(filename)) + { + std::string buffer = llformat("Unable to save '%s' to wearable file.", mName.c_str()); + LL_WARNS() << buffer << LL_ENDL; + + LLSD args; + args["NAME"] = mName; + LLNotificationsUtil::add("CannotSaveWearableOutOfSpace", args); + return; + } + + if (gSavedSettings.getBOOL("LogWearableAssetSave")) + { + const std::string log_filename = asset_id_to_filename(mAssetID, LL_PATH_LOGS); + exportFile(log_filename); + } + + // save it out to database + if( gAssetStorage ) + { + /* + std::string url = gAgent.getRegion()->getCapability("NewAgentInventory"); + if (!url.empty()) + { + LL_INFOS() << "Update Agent Inventory via capability" << LL_ENDL; + LLSD body; + body["folder_id"] = gInventory.findCategoryUUIDForType(LLFolderType::assetToFolderType(getAssetType())); + body["asset_type"] = LLAssetType::lookup(getAssetType()); + body["inventory_type"] = LLInventoryType::lookup(LLInventoryType::IT_WEARABLE); + body["name"] = getName(); + body["description"] = getDescription(); + LLHTTPClient::post(url, body, new LLNewAgentInventoryResponder(body, filename)); + } + else + { + } + */ + LLWearableSaveData* data = new LLWearableSaveData; + data->mType = mType; + gAssetStorage->storeAssetData(filename, mTransactionID, getAssetType(), + &LLViewerWearable::onSaveNewAssetComplete, + (void*)data); + } +} + +// static +void LLViewerWearable::onSaveNewAssetComplete(const LLUUID& new_asset_id, void* userdata, S32 status, LLExtStat ext_status) // StoreAssetData callback (fixed) +{ + LLWearableSaveData* data = (LLWearableSaveData*)userdata; + const std::string& type_name = LLWearableType::getInstance()->getTypeName(data->mType); + if(0 == status) + { + // Success + LL_INFOS() << "Saved wearable " << type_name << LL_ENDL; + } + else + { + std::string buffer = llformat("Unable to save %s to central asset store.", type_name.c_str()); + LL_WARNS() << buffer << " Status: " << status << LL_ENDL; + LLSD args; + args["NAME"] = type_name; + LLNotificationsUtil::add("CannotSaveToAssetStore", args); + } + + // Delete temp file + const std::string src_filename = asset_id_to_filename(new_asset_id, LL_PATH_CACHE); + LLFile::remove(src_filename); + + // delete the context data + delete data; + +} + +std::ostream& operator<<(std::ostream &s, const LLViewerWearable &w) +{ + s << "wearable " << LLWearableType::getInstance()->getTypeName(w.mType) << "\n"; + s << " Name: " << w.mName << "\n"; + s << " Desc: " << w.mDescription << "\n"; + //w.mPermissions + //w.mSaleInfo + + s << " Params:" << "\n"; + for (LLWearable::visual_param_index_map_t::const_iterator iter = w.mVisualParamIndexMap.begin(); + iter != w.mVisualParamIndexMap.end(); ++iter) + { + S32 param_id = iter->first; + LLVisualParam *wearable_param = iter->second; + F32 param_weight = wearable_param->getWeight(); + s << " " << param_id << " " << param_weight << "\n"; + } + + s << " Textures:" << "\n"; + for (LLViewerWearable::te_map_t::const_iterator iter = w.mTEMap.begin(); + iter != w.mTEMap.end(); ++iter) + { + S32 te = iter->first; + const LLUUID& image_id = iter->second->getID(); + s << " " << te << " " << image_id << "\n"; + } + return s; +} + +std::string asset_id_to_filename(const LLUUID &asset_id, const ELLPath dir_spec) +{ + std::string asset_id_string; + asset_id.toString(asset_id_string); + std::string filename = gDirUtilp->getExpandedFilename(dir_spec,asset_id_string) + ".wbl"; + return filename; +} diff --git a/indra/newview/llviewerwearable.h b/indra/newview/llviewerwearable.h index 1eb2626a74..233c65aefb 100644 --- a/indra/newview/llviewerwearable.h +++ b/indra/newview/llviewerwearable.h @@ -1,108 +1,108 @@ -/** - * @file llviewerwearable.h - * @brief LLViewerWearable class header file - * - * $LicenseInfo:firstyear=2012&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_VIEWER_WEARABLE_H -#define LL_VIEWER_WEARABLE_H - -#include "llwearable.h" -#include "llavatarappearancedefines.h" -#include "llextendedstatus.h" - -class LLVOAvatar; - -class LLViewerWearable : public LLWearable -{ - friend class LLWearableList; - - //-------------------------------------------------------------------- - // Constructors and destructors - //-------------------------------------------------------------------- -private: - // Private constructors used by LLViewerWearableList - LLViewerWearable(const LLTransactionID& transactionID); - LLViewerWearable(const LLAssetID& assetID); -public: - virtual ~LLViewerWearable(); - - //-------------------------------------------------------------------- - // Accessors - //-------------------------------------------------------------------- -public: - const LLUUID& getItemID() const { return mItemID; } - const LLAssetID& getAssetID() const { return mAssetID; } - const LLTransactionID& getTransactionID() const { return mTransactionID; } - void setItemID(const LLUUID& item_id); - -public: - - bool isDirty() const; - bool isOldVersion() const; - - /*virtual*/ void writeToAvatar(LLAvatarAppearance *avatarp); - void removeFromAvatar() { LLViewerWearable::removeFromAvatar( mType); } - static void removeFromAvatar( LLWearableType::EType type); - - /*virtual*/ EImportResult importStream( std::istream& input_stream, LLAvatarAppearance* avatarp ); - - void setParamsToDefaults(); - void setTexturesToDefaults(); - void setVolatile(bool is_volatile) { mVolatile = is_volatile; } // true when doing preview renders, some updates will be suppressed. - bool getVolatile() { return mVolatile; } - - /*virtual*/ LLUUID getDefaultTextureImageID(LLAvatarAppearanceDefines::ETextureIndex index) const; - - - void saveNewAsset() const; - static void onSaveNewAssetComplete( const LLUUID& asset_uuid, void* user_data, S32 status, LLExtStat ext_status ); - - void copyDataFrom(const LLViewerWearable* src); - - friend std::ostream& operator<<(std::ostream &s, const LLViewerWearable &w); - - /*virtual*/ void revertValues(); - /*virtual*/ void saveValues(); - - void revertValuesWithoutUpdate(); - - // Something happened that requires the wearable's label to be updated (e.g. worn/unworn). - /*virtual*/void setUpdated() const; - - // the wearable was worn. make sure the name of the wearable object matches the LLViewerInventoryItem, - // not the wearable asset itself. - void refreshName(); - -protected: - LLAssetID mAssetID; - LLTransactionID mTransactionID; - - bool mVolatile; // True when rendering preview images. Can suppress some updates. - - LLUUID mItemID; // ID of the inventory item in the agent's inventory -}; - - -#endif // LL_VIEWER_WEARABLE_H - +/** + * @file llviewerwearable.h + * @brief LLViewerWearable class header file + * + * $LicenseInfo:firstyear=2012&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_VIEWER_WEARABLE_H +#define LL_VIEWER_WEARABLE_H + +#include "llwearable.h" +#include "llavatarappearancedefines.h" +#include "llextendedstatus.h" + +class LLVOAvatar; + +class LLViewerWearable : public LLWearable +{ + friend class LLWearableList; + + //-------------------------------------------------------------------- + // Constructors and destructors + //-------------------------------------------------------------------- +private: + // Private constructors used by LLViewerWearableList + LLViewerWearable(const LLTransactionID& transactionID); + LLViewerWearable(const LLAssetID& assetID); +public: + virtual ~LLViewerWearable(); + + //-------------------------------------------------------------------- + // Accessors + //-------------------------------------------------------------------- +public: + const LLUUID& getItemID() const { return mItemID; } + const LLAssetID& getAssetID() const { return mAssetID; } + const LLTransactionID& getTransactionID() const { return mTransactionID; } + void setItemID(const LLUUID& item_id); + +public: + + bool isDirty() const; + bool isOldVersion() const; + + /*virtual*/ void writeToAvatar(LLAvatarAppearance *avatarp); + void removeFromAvatar() { LLViewerWearable::removeFromAvatar( mType); } + static void removeFromAvatar( LLWearableType::EType type); + + /*virtual*/ EImportResult importStream( std::istream& input_stream, LLAvatarAppearance* avatarp ); + + void setParamsToDefaults(); + void setTexturesToDefaults(); + void setVolatile(bool is_volatile) { mVolatile = is_volatile; } // true when doing preview renders, some updates will be suppressed. + bool getVolatile() { return mVolatile; } + + /*virtual*/ LLUUID getDefaultTextureImageID(LLAvatarAppearanceDefines::ETextureIndex index) const; + + + void saveNewAsset() const; + static void onSaveNewAssetComplete( const LLUUID& asset_uuid, void* user_data, S32 status, LLExtStat ext_status ); + + void copyDataFrom(const LLViewerWearable* src); + + friend std::ostream& operator<<(std::ostream &s, const LLViewerWearable &w); + + /*virtual*/ void revertValues(); + /*virtual*/ void saveValues(); + + void revertValuesWithoutUpdate(); + + // Something happened that requires the wearable's label to be updated (e.g. worn/unworn). + /*virtual*/void setUpdated() const; + + // the wearable was worn. make sure the name of the wearable object matches the LLViewerInventoryItem, + // not the wearable asset itself. + void refreshName(); + +protected: + LLAssetID mAssetID; + LLTransactionID mTransactionID; + + bool mVolatile; // True when rendering preview images. Can suppress some updates. + + LLUUID mItemID; // ID of the inventory item in the agent's inventory +}; + + +#endif // LL_VIEWER_WEARABLE_H + diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index f607ffdbb5..17ac0a97b7 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -1,6407 +1,6407 @@ -/** - * @file llviewerwindow.cpp - * @brief Implementation of the LLViewerWindow class. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llviewerwindow.h" - - -// system library includes -#include -#include -#include -#include -#include -#include -#include - -#include "llagent.h" -#include "llagentcamera.h" -#include "llcommandhandler.h" -#include "llcommunicationchannel.h" -#include "llfloaterreg.h" -#include "llhudicon.h" -#include "llmeshrepository.h" -#include "llnotificationhandler.h" -#include "llpanellogin.h" -#include "llsetkeybinddialog.h" -#include "llviewerinput.h" -#include "llviewermenu.h" - -#include "llviewquery.h" -#include "llxmltree.h" -#include "llslurl.h" -#include "llrender.h" - -#include "stringize.h" - -// -// TODO: Many of these includes are unnecessary. Remove them. -// - -// linden library includes -#include "llaudioengine.h" // mute on minimize -#include "llchatentry.h" -#include "indra_constants.h" -#include "llassetstorage.h" -#include "llerrorcontrol.h" -#include "llfontgl.h" -#include "llmousehandler.h" -#include "llrect.h" -#include "llsky.h" -#include "llstring.h" -#include "llui.h" -#include "lluuid.h" -#include "llview.h" -#include "llxfermanager.h" -#include "message.h" -#include "object_flags.h" -#include "lltimer.h" -#include "llviewermenu.h" -#include "lltooltip.h" -#include "llmediaentry.h" -#include "llurldispatcher.h" -#include "raytrace.h" - -// newview includes -#include "llagent.h" -#include "llbox.h" -#include "llchicletbar.h" -#include "llconsole.h" -#include "llviewercontrol.h" -#include "llcylinder.h" -#include "lldebugview.h" -#include "lldir.h" -#include "lldrawable.h" -#include "lldrawpoolalpha.h" -#include "lldrawpoolbump.h" -#include "lldrawpoolwater.h" -#include "llmaniptranslate.h" -#include "llface.h" -#include "llfeaturemanager.h" -#include "llfilepicker.h" -#include "llfirstuse.h" -#include "llfloater.h" -#include "llfloaterbuyland.h" -#include "llfloatercamera.h" -#include "llfloaterland.h" -#include "llfloaterinspect.h" -#include "llfloatermap.h" -#include "llfloaternamedesc.h" -#include "llfloaterpreference.h" -#include "llfloatersnapshot.h" -#include "llfloatertools.h" -#include "llfloaterworldmap.h" -#include "llfocusmgr.h" -#include "llfontfreetype.h" -#include "llgesturemgr.h" -#include "llglheaders.h" -#include "lltooltip.h" -#include "llhudmanager.h" -#include "llhudobject.h" -#include "llhudview.h" -#include "llimage.h" -#include "llimagej2c.h" -#include "llimageworker.h" -#include "llkeyboard.h" -#include "lllineeditor.h" -#include "llmenugl.h" -#include "llmenuoptionpathfindingrebakenavmesh.h" -#include "llmodaldialog.h" -#include "llmorphview.h" -#include "llmoveview.h" -#include "llnavigationbar.h" -#include "llnotificationhandler.h" -#include "llpaneltopinfobar.h" -#include "llpopupview.h" -#include "llpreviewtexture.h" -#include "llprogressview.h" -#include "llresmgr.h" -#include "llselectmgr.h" -#include "llrootview.h" -#include "llrendersphere.h" -#include "llstartup.h" -#include "llstatusbar.h" -#include "llstatview.h" -#include "llsurface.h" -#include "llsurfacepatch.h" -#include "lltexlayer.h" -#include "lltextbox.h" -#include "lltexturecache.h" -#include "lltexturefetch.h" -#include "lltextureview.h" -#include "lltoast.h" -#include "lltool.h" -#include "lltoolbarview.h" -#include "lltoolcomp.h" -#include "lltooldraganddrop.h" -#include "lltoolface.h" -#include "lltoolfocus.h" -#include "lltoolgrab.h" -#include "lltoolmgr.h" -#include "lltoolmorph.h" -#include "lltoolpie.h" -#include "lltoolselectland.h" -#include "lltrans.h" -#include "lluictrlfactory.h" -#include "llurldispatcher.h" // SLURL from other app instance -#include "llversioninfo.h" -#include "llvieweraudio.h" -#include "llviewercamera.h" -#include "llviewergesture.h" -#include "llviewertexturelist.h" -#include "llviewerinventory.h" -#include "llviewerinput.h" -#include "llviewermedia.h" -#include "llviewermediafocus.h" -#include "llviewermenu.h" -#include "llviewermessage.h" -#include "llviewerobjectlist.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" -#include "llviewershadermgr.h" -#include "llviewerstats.h" -#include "llvoavatarself.h" -#include "llvopartgroup.h" -#include "llvovolume.h" -#include "llworld.h" -#include "llworldmapview.h" -#include "pipeline.h" -#include "llappviewer.h" -#include "llviewerdisplay.h" -#include "llspatialpartition.h" -#include "llviewerjoystick.h" -#include "llviewermenufile.h" // LLFilePickerReplyThread -#include "llviewernetwork.h" -#include "llpostprocess.h" -#include "llfloaterimnearbychat.h" -#include "llagentui.h" -#include "llwearablelist.h" - -#include "llviewereventrecorder.h" - -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "llnotificationmanager.h" - -#include "llfloaternotificationsconsole.h" - -#include "llwindowlistener.h" -#include "llviewerwindowlistener.h" -#include "llpaneltopinfobar.h" -#include "llcleanup.h" - -#if LL_WINDOWS -#include // For Unicode conversion methods -#include "llwindowwin32.h" // For AltGr handling -#endif - -// -// Globals -// -void render_ui(F32 zoom_factor = 1.f, int subfield = 0); -void swap(); - -extern bool gDebugClicks; -extern bool gDisplaySwapBuffers; -extern bool gDepthDirty; -extern bool gResizeScreenTexture; -extern bool gCubeSnapshot; -extern bool gSnapshotNoPost; - -LLViewerWindow *gViewerWindow = NULL; - -LLFrameTimer gAwayTimer; -LLFrameTimer gAwayTriggerTimer; - -bool gShowOverlayTitle = false; - -LLViewerObject* gDebugRaycastObject = NULL; -LLVOPartGroup* gDebugRaycastParticle = NULL; -LLVector4a gDebugRaycastIntersection; -LLVector4a gDebugRaycastParticleIntersection; -LLVector2 gDebugRaycastTexCoord; -LLVector4a gDebugRaycastNormal; -LLVector4a gDebugRaycastTangent; -S32 gDebugRaycastFaceHit; -LLVector4a gDebugRaycastStart; -LLVector4a gDebugRaycastEnd; - -// HUD display lines in lower right -bool gDisplayWindInfo = false; -bool gDisplayCameraPos = false; -bool gDisplayFOV = false; -bool gDisplayBadge = false; - -static const U8 NO_FACE = 255; -bool gQuietSnapshot = false; - -// Minimum value for UIScaleFactor, also defined in preferences, ui_scale_slider -static const F32 MIN_UI_SCALE = 0.75f; -// 4.0 in preferences, but win10 supports larger scaling and value is used more as -// sanity check, so leaving space for larger values from DPI updates. -static const F32 MAX_UI_SCALE = 7.0f; -static const F32 MIN_DISPLAY_SCALE = 0.75f; - -static const char KEY_MOUSELOOK = 'M'; - -static LLCachedControl sSnapshotBaseName(LLCachedControl(gSavedPerAccountSettings, "SnapshotBaseName", "Snapshot")); -static LLCachedControl sSnapshotDir(LLCachedControl(gSavedPerAccountSettings, "SnapshotBaseDir", "")); - -LLTrace::SampleStatHandle<> LLViewerWindow::sMouseVelocityStat("Mouse Velocity"); - - -class RecordToChatConsoleRecorder : public LLError::Recorder -{ -public: - virtual void recordMessage(LLError::ELevel level, - const std::string& message) - { - //FIXME: this is NOT thread safe, and will do bad things when a warning is issued from a non-UI thread - - // only log warnings to chat console - //if (level == LLError::LEVEL_WARN) - //{ - //LLFloaterChat* chat_floater = LLFloaterReg::findTypedInstance("chat"); - //if (chat_floater && gSavedSettings.getBOOL("WarningsAsChat")) - //{ - // LLChat chat; - // chat.mText = message; - // chat.mSourceType = CHAT_SOURCE_SYSTEM; - - // chat_floater->addChat(chat, false, false); - //} - //} - } -}; - -class RecordToChatConsole : public LLSingleton -{ - LLSINGLETON(RecordToChatConsole); -public: - void startRecorder() { LLError::addRecorder(mRecorder); } - void stopRecorder() { LLError::removeRecorder(mRecorder); } - -private: - LLError::RecorderPtr mRecorder; -}; - -RecordToChatConsole::RecordToChatConsole(): - mRecorder(new RecordToChatConsoleRecorder()) -{ - mRecorder->showTags(false); - mRecorder->showLocation(false); - mRecorder->showMultiline(true); -} - -//////////////////////////////////////////////////////////////////////////// -// -// Print Utility -// - -// Convert a normalized float (-1.0 <= x <= +1.0) to a fixed 1.4 format string: -// -// s#.#### -// -// Where: -// s sign character; space if x is positiv, minus if negative -// # decimal digits -// -// This is similar to printf("%+.4f") except positive numbers are NOT cluttered with a leading '+' sign. -// NOTE: This does NOT null terminate the output -void normalized_float_to_string(const float x, char *out_str) -{ - static const unsigned char DECIMAL_BCD2[] = - { - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, - 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, - 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, - 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, - 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, - 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, - 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99 - }; - - int neg = (x < 0); - int rem = neg - ? (int)(x * -10000.) - : (int)(x * 10000.); - - int d10 = rem % 100; rem /= 100; - int d32 = rem % 100; rem /= 100; - - out_str[6] = '0' + ((DECIMAL_BCD2[ d10 ] >> 0) & 0xF); - out_str[5] = '0' + ((DECIMAL_BCD2[ d10 ] >> 4) & 0xF); - out_str[4] = '0' + ((DECIMAL_BCD2[ d32 ] >> 0) & 0xF); - out_str[3] = '0' + ((DECIMAL_BCD2[ d32 ] >> 4) & 0xF); - out_str[2] = '.'; - out_str[1] = '0' + (rem & 1); - out_str[0] = " -"[neg]; // Could always show '+' for positive but this clutters up the common case -} - -// normalized float -// printf("%-.4f %-.4f %-.4f") -// Params: -// float &matrix_row[4] -// int matrix_cell_index -// string out_buffer (size 32) -// Note: The buffer is assumed to be pre-filled with spaces -#define MATRIX_ROW_N32_TO_STR(matrix_row, i, out_buffer) \ - normalized_float_to_string(matrix_row[i+0], out_buffer + 0); \ - normalized_float_to_string(matrix_row[i+1], out_buffer + 11); \ - normalized_float_to_string(matrix_row[i+2], out_buffer + 22); \ - out_buffer[31] = 0; - - -// regular float -// sprintf(buffer, "%-8.2f %-8.2f %-8.2f", matrix_row[i+0], matrix_row[i+1], matrix_row[i+2]); -// Params: -// float &matrix_row[4] -// int matrix_cell_index -// char out_buffer[32] -// Note: The buffer is assumed to be pre-filled with spaces -#define MATRIX_ROW_F32_TO_STR(matrix_row, i, out_buffer) { \ - static const char *format[3] = { \ - "%-8.2f" , /* 0 */ \ - "> 99K ", /* 1 */ \ - "< -99K " /* 2 */ \ - }; \ - \ - F32 temp_0 = matrix_row[i+0]; \ - F32 temp_1 = matrix_row[i+1]; \ - F32 temp_2 = matrix_row[i+2]; \ - \ - U8 flag_0 = (((U8)(temp_0 < -99999.99)) << 1) | ((U8)(temp_0 > 99999.99)); \ - U8 flag_1 = (((U8)(temp_1 < -99999.99)) << 1) | ((U8)(temp_1 > 99999.99)); \ - U8 flag_2 = (((U8)(temp_2 < -99999.99)) << 1) | ((U8)(temp_2 > 99999.99)); \ - \ - if (temp_0 < 0.f) out_buffer[ 0] = '-'; \ - if (temp_1 < 0.f) out_buffer[11] = '-'; \ - if (temp_2 < 0.f) out_buffer[22] = '-'; \ - \ - sprintf(out_buffer+ 1,format[flag_0],fabsf(temp_0)); out_buffer[ 1+8] = ' '; \ - sprintf(out_buffer+12,format[flag_1],fabsf(temp_1)); out_buffer[12+8] = ' '; \ - sprintf(out_buffer+23,format[flag_2],fabsf(temp_2)); out_buffer[23+8] = 0 ; \ -} - -//////////////////////////////////////////////////////////////////////////// -// -// LLDebugText -// - -static LLTrace::BlockTimerStatHandle FTM_DISPLAY_DEBUG_TEXT("Display Debug Text"); - -class LLDebugText -{ -private: - struct Line - { - Line(const std::string& in_text, S32 in_x, S32 in_y) : text(in_text), x(in_x), y(in_y) {} - std::string text; - S32 x,y; - }; - - LLViewerWindow *mWindow; - - typedef std::vector line_list_t; - line_list_t mLineList; - LLColor4 mTextColor; - - LLColor4 mBackColor; - LLRect mBackRectCamera1; - LLRect mBackRectCamera2; - - void addText(S32 x, S32 y, const std::string &text) - { - mLineList.push_back(Line(text, x, y)); - } - - void clearText() { mLineList.clear(); } - -public: - LLDebugText(LLViewerWindow* window) : mWindow(window) {} - - void update() - { - if (!gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) - { - clearText(); - return; - } - - static LLCachedControl log_texture_traffic(gSavedSettings,"LogTextureNetworkTraffic", false) ; - - std::string wind_vel_text; - std::string wind_vector_text; - std::string rwind_vel_text; - std::string rwind_vector_text; - std::string audio_text; - - static const std::string beacon_particle = LLTrans::getString("BeaconParticle"); - static const std::string beacon_physical = LLTrans::getString("BeaconPhysical"); - static const std::string beacon_scripted = LLTrans::getString("BeaconScripted"); - static const std::string beacon_scripted_touch = LLTrans::getString("BeaconScriptedTouch"); - static const std::string beacon_sound = LLTrans::getString("BeaconSound"); - static const std::string beacon_media = LLTrans::getString("BeaconMedia"); - static const std::string beacon_sun = LLTrans::getString("BeaconSun"); - static const std::string beacon_moon = LLTrans::getString("BeaconMoon"); - static const std::string particle_hiding = LLTrans::getString("ParticleHiding"); - - // Draw the statistics in a light gray - // and in a thin font - mTextColor = LLColor4( 0.86f, 0.86f, 0.86f, 1.f ); - - // Draw stuff growing up from right lower corner of screen - S32 x_right = mWindow->getWorldViewWidthScaled(); - S32 xpos = x_right - 400; - xpos = llmax(xpos, 0); - S32 ypos = 64; - const S32 y_inc = 20; - - // Camera matrix text is hard to see again a white background - // Add a dark background underneath the matrices for readability (contrast) - mBackRectCamera1.mLeft = xpos; - mBackRectCamera1.mRight = x_right; - mBackRectCamera1.mTop = -1; - mBackRectCamera1.mBottom = -1; - mBackRectCamera2 = mBackRectCamera1; - - mBackColor = LLUIColorTable::instance().getColor( "MenuDefaultBgColor" ); - - clearText(); - - if (gSavedSettings.getBOOL("DebugShowTime")) - { - F32 time = gFrameTimeSeconds; - S32 hours = (S32)(time / (60*60)); - S32 mins = (S32)((time - hours*(60*60)) / 60); - S32 secs = (S32)((time - hours*(60*60) - mins*60)); - addText(xpos, ypos, llformat("Time: %d:%02d:%02d", hours,mins,secs)); ypos += y_inc; - } - - if (gSavedSettings.getBOOL("DebugShowMemory")) - { - addText(xpos, ypos, - STRINGIZE("Memory: " << (LLMemory::getCurrentRSS() / 1024) << " (KB)")); - ypos += y_inc; - } - - if (gDisplayCameraPos) - { - std::string camera_view_text; - std::string camera_center_text; - std::string agent_view_text; - std::string agent_left_text; - std::string agent_center_text; - std::string agent_root_center_text; - - LLVector3d tvector; // Temporary vector to hold data for printing. - - // Update camera center, camera view, wind info every other frame - tvector = gAgent.getPositionGlobal(); - agent_center_text = llformat("AgentCenter %f %f %f", - (F32)(tvector.mdV[VX]), (F32)(tvector.mdV[VY]), (F32)(tvector.mdV[VZ])); - - if (isAgentAvatarValid()) - { - tvector = gAgent.getPosGlobalFromAgent(gAgentAvatarp->mRoot->getWorldPosition()); - agent_root_center_text = llformat("AgentRootCenter %f %f %f", - (F32)(tvector.mdV[VX]), (F32)(tvector.mdV[VY]), (F32)(tvector.mdV[VZ])); - } - else - { - agent_root_center_text = "---"; - } - - - tvector = LLVector4(gAgent.getFrameAgent().getAtAxis()); - agent_view_text = llformat("AgentAtAxis %f %f %f", - (F32)(tvector.mdV[VX]), (F32)(tvector.mdV[VY]), (F32)(tvector.mdV[VZ])); - - tvector = LLVector4(gAgent.getFrameAgent().getLeftAxis()); - agent_left_text = llformat("AgentLeftAxis %f %f %f", - (F32)(tvector.mdV[VX]), (F32)(tvector.mdV[VY]), (F32)(tvector.mdV[VZ])); - - tvector = gAgentCamera.getCameraPositionGlobal(); - camera_center_text = llformat("CameraCenter %f %f %f", - (F32)(tvector.mdV[VX]), (F32)(tvector.mdV[VY]), (F32)(tvector.mdV[VZ])); - - tvector = LLVector4(LLViewerCamera::getInstance()->getAtAxis()); - camera_view_text = llformat("CameraAtAxis %f %f %f", - (F32)(tvector.mdV[VX]), (F32)(tvector.mdV[VY]), (F32)(tvector.mdV[VZ])); - - addText(xpos, ypos, agent_center_text); ypos += y_inc; - addText(xpos, ypos, agent_root_center_text); ypos += y_inc; - addText(xpos, ypos, agent_view_text); ypos += y_inc; - addText(xpos, ypos, agent_left_text); ypos += y_inc; - addText(xpos, ypos, camera_center_text); ypos += y_inc; - addText(xpos, ypos, camera_view_text); ypos += y_inc; - } - - if (gDisplayWindInfo) - { - wind_vel_text = llformat("Wind velocity %.2f m/s", gWindVec.magVec()); - wind_vector_text = llformat("Wind vector %.2f %.2f %.2f", gWindVec.mV[0], gWindVec.mV[1], gWindVec.mV[2]); - rwind_vel_text = llformat("RWind vel %.2f m/s", gRelativeWindVec.magVec()); - rwind_vector_text = llformat("RWind vec %.2f %.2f %.2f", gRelativeWindVec.mV[0], gRelativeWindVec.mV[1], gRelativeWindVec.mV[2]); - - addText(xpos, ypos, wind_vel_text); ypos += y_inc; - addText(xpos, ypos, wind_vector_text); ypos += y_inc; - addText(xpos, ypos, rwind_vel_text); ypos += y_inc; - addText(xpos, ypos, rwind_vector_text); ypos += y_inc; - } - if (gDisplayWindInfo) - { - audio_text = llformat("Audio for wind: %d", gAudiop ? gAudiop->isWindEnabled() : -1); - addText(xpos, ypos, audio_text); ypos += y_inc; - } - if (gDisplayFOV) - { - addText(xpos, ypos, llformat("FOV: %2.1f deg", RAD_TO_DEG * LLViewerCamera::getInstance()->getView())); - ypos += y_inc; - } - if (gDisplayBadge) - { - addText(xpos, ypos+(y_inc/2), llformat("Hippos!", RAD_TO_DEG * LLViewerCamera::getInstance()->getView())); - ypos += y_inc * 2; - } - - /*if (LLViewerJoystick::getInstance()->getOverrideCamera()) - { - addText(xpos + 200, ypos, llformat("Flycam")); - ypos += y_inc; - }*/ - - if (gSavedSettings.getBOOL("DebugShowRenderInfo")) - { - LLTrace::Recording& last_frame_recording = LLTrace::get_frame_recording().getLastRecording(); - - //show streaming cost/triangle count of known prims in current region OR selection - { - F32 cost = 0.f; - S32 count = 0; - S32 vcount = 0; - S32 object_count = 0; - S32 total_bytes = 0; - S32 visible_bytes = 0; - - const char* label = "Region"; - if (LLSelectMgr::getInstance()->getSelection()->getObjectCount() == 0) - { //region - LLViewerRegion* region = gAgent.getRegion(); - if (region) - { - for (U32 i = 0; i < gObjectList.getNumObjects(); ++i) - { - LLViewerObject* object = gObjectList.getObject(i); - if (object && - object->getRegion() == region && - object->getVolume()) - { - object_count++; - S32 bytes = 0; - S32 visible = 0; - cost += object->getStreamingCost(); - LLMeshCostData costs; - if (object->getCostData(costs)) - { - bytes = costs.getSizeTotal(); - visible = costs.getSizeByLOD(object->getLOD()); - } - - S32 vt = 0; - count += object->getTriangleCount(&vt); - vcount += vt; - total_bytes += bytes; - visible_bytes += visible; - } - } - } - } - else - { - label = "Selection"; - cost = LLSelectMgr::getInstance()->getSelection()->getSelectedObjectStreamingCost(&total_bytes, &visible_bytes); - count = LLSelectMgr::getInstance()->getSelection()->getSelectedObjectTriangleCount(&vcount); - object_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount(); - } - - addText(xpos,ypos, llformat("%s streaming cost: %.1f", label, cost)); - ypos += y_inc; - - addText(xpos, ypos, llformat(" %.3f KTris, %.3f KVerts, %.1f/%.1f KB, %d objects", - count/1000.f, vcount/1000.f, visible_bytes/1024.f, total_bytes/1024.f, object_count)); - ypos += y_inc; - - } - - addText(xpos, ypos, llformat("%d Texture Binds", LLImageGL::sBindCount)); - ypos += y_inc; - - addText(xpos, ypos, llformat("%d Unique Textures", LLImageGL::sUniqueCount)); - ypos += y_inc; - - addText(xpos, ypos, llformat("%d Render Calls", (U32)last_frame_recording.getSampleCount(LLPipeline::sStatBatchSize))); - ypos += y_inc; - - addText(xpos, ypos, llformat("%d/%d Objects Active", gObjectList.getNumActiveObjects(), gObjectList.getNumObjects())); - ypos += y_inc; - - addText(xpos, ypos, llformat("%d Matrix Ops", gPipeline.mMatrixOpCount)); - ypos += y_inc; - - addText(xpos, ypos, llformat("%d Texture Matrix Ops", gPipeline.mTextureMatrixOps)); - ypos += y_inc; - - gPipeline.mTextureMatrixOps = 0; - gPipeline.mMatrixOpCount = 0; - - if (last_frame_recording.getSampleCount(LLPipeline::sStatBatchSize) > 0) - { - addText(xpos, ypos, llformat("Batch min/max/mean: %d/%d/%d", (U32)last_frame_recording.getMin(LLPipeline::sStatBatchSize), (U32)last_frame_recording.getMax(LLPipeline::sStatBatchSize), (U32)last_frame_recording.getMean(LLPipeline::sStatBatchSize))); - } - ypos += y_inc; - - addText(xpos, ypos, llformat("UI Verts/Calls: %d/%d", LLRender::sUIVerts, LLRender::sUICalls)); - LLRender::sUICalls = LLRender::sUIVerts = 0; - ypos += y_inc; - - addText(xpos,ypos, llformat("%d/%d Nodes visible", gPipeline.mNumVisibleNodes, LLSpatialGroup::sNodeCount)); - - ypos += y_inc; - - if (!LLOcclusionCullingGroup::sPendingQueries.empty()) - { - addText(xpos,ypos, llformat("%d Queries pending", LLOcclusionCullingGroup::sPendingQueries.size())); - ypos += y_inc; - } - - - addText(xpos,ypos, llformat("%d Avatars visible", LLVOAvatar::sNumVisibleAvatars)); - - ypos += y_inc; - - addText(xpos,ypos, llformat("%d Lights visible", LLPipeline::sVisibleLightCount)); - - ypos += y_inc; - - if (gMeshRepo.meshRezEnabled()) - { - addText(xpos, ypos, llformat("%.3f MB Mesh Data Received", LLMeshRepository::sBytesReceived/(1024.f*1024.f))); - - ypos += y_inc; - - addText(xpos, ypos, llformat("%d/%d Mesh HTTP Requests/Retries", LLMeshRepository::sHTTPRequestCount, - LLMeshRepository::sHTTPRetryCount)); - ypos += y_inc; - - addText(xpos, ypos, llformat("%d/%d Mesh LOD Pending/Processing", LLMeshRepository::sLODPending, LLMeshRepository::sLODProcessing)); - ypos += y_inc; - - addText(xpos, ypos, llformat("%.3f/%.3f MB Mesh Cache Read/Write ", LLMeshRepository::sCacheBytesRead/(1024.f*1024.f), LLMeshRepository::sCacheBytesWritten/(1024.f*1024.f))); - ypos += y_inc; - - addText(xpos, ypos, llformat("%.3f/%.3f MB Mesh Skins/Decompositions Memory", LLMeshRepository::sCacheBytesSkins / (1024.f*1024.f), LLMeshRepository::sCacheBytesDecomps / (1024.f*1024.f))); - ypos += y_inc; - - addText(xpos, ypos, llformat("%.3f MB Mesh Headers Memory", LLMeshRepository::sCacheBytesHeaders / (1024.f*1024.f))); - - ypos += y_inc; - } - - gPipeline.mNumVisibleNodes = LLPipeline::sVisibleLightCount = 0; - } - if (gSavedSettings.getBOOL("DebugShowAvatarRenderInfo")) - { - std::map sorted_avs; - - std::vector::iterator sort_iter = LLCharacter::sInstances.begin(); - while (sort_iter != LLCharacter::sInstances.end()) - { - LLVOAvatar* avatar = dynamic_cast(*sort_iter); - if (avatar && - !avatar->isDead()) // Not dead yet - { - // Stuff into a sorted map so the display is ordered - sorted_avs[avatar->getFullname()] = avatar; - } - sort_iter++; - } - - std::string trunc_name; - std::map::reverse_iterator av_iter = sorted_avs.rbegin(); // Put "A" at the top - while (av_iter != sorted_avs.rend()) - { - LLVOAvatar* avatar = av_iter->second; - - avatar->calculateUpdateRenderComplexity(); // Make sure the numbers are up-to-date - - trunc_name = utf8str_truncate(avatar->getFullname(), 16); - addText(xpos, ypos, llformat("%s : %s, complexity %d, area %.2f", - trunc_name.c_str(), - LLVOAvatar::rezStatusToString(avatar->getRezzedStatus()).c_str(), - avatar->getVisualComplexity(), - avatar->getAttachmentSurfaceArea())); - ypos += y_inc; - av_iter++; - } - } - if (gSavedSettings.getBOOL("DebugShowRenderMatrices")) - { - char camera_lines[8][32]; - memset(camera_lines, ' ', sizeof(camera_lines)); - - // Projection last column is always <0,0,-1.0001,0> - // Projection last row is always <0,0,-0.2> - mBackRectCamera1.mBottom = ypos - y_inc + 2; - MATRIX_ROW_N32_TO_STR(gGLProjection, 12,camera_lines[7]); addText(xpos, ypos, std::string(camera_lines[7])); ypos += y_inc; - MATRIX_ROW_N32_TO_STR(gGLProjection, 8,camera_lines[6]); addText(xpos, ypos, std::string(camera_lines[6])); ypos += y_inc; - MATRIX_ROW_N32_TO_STR(gGLProjection, 4,camera_lines[5]); addText(xpos, ypos, std::string(camera_lines[5])); ypos += y_inc; mBackRectCamera1.mTop = ypos + 2; - MATRIX_ROW_N32_TO_STR(gGLProjection, 0,camera_lines[4]); addText(xpos, ypos, std::string(camera_lines[4])); ypos += y_inc; mBackRectCamera2.mBottom = ypos + 2; - - addText(xpos, ypos, "Projection Matrix"); - ypos += y_inc; - - // View last column is always <0,0,0,1> - MATRIX_ROW_F32_TO_STR(gGLModelView, 12,camera_lines[3]); addText(xpos, ypos, std::string(camera_lines[3])); ypos += y_inc; - MATRIX_ROW_N32_TO_STR(gGLModelView, 8,camera_lines[2]); addText(xpos, ypos, std::string(camera_lines[2])); ypos += y_inc; - MATRIX_ROW_N32_TO_STR(gGLModelView, 4,camera_lines[1]); addText(xpos, ypos, std::string(camera_lines[1])); ypos += y_inc; mBackRectCamera2.mTop = ypos + 2; - MATRIX_ROW_N32_TO_STR(gGLModelView, 0,camera_lines[0]); addText(xpos, ypos, std::string(camera_lines[0])); ypos += y_inc; - - addText(xpos, ypos, "View Matrix"); - ypos += y_inc; - } - // disable use of glReadPixels which messes up nVidia nSight graphics debugging - if (gSavedSettings.getBOOL("DebugShowColor") && !LLRender::sNsightDebugSupport) - { - U8 color[4]; - LLCoordGL coord = gViewerWindow->getCurrentMouse(); - - // Convert x,y to raw pixel coords - S32 x_raw = llround(coord.mX * gViewerWindow->getWindowWidthRaw() / (F32) gViewerWindow->getWindowWidthScaled()); - S32 y_raw = llround(coord.mY * gViewerWindow->getWindowHeightRaw() / (F32) gViewerWindow->getWindowHeightScaled()); - - glReadPixels(x_raw, y_raw, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, color); - addText(xpos, ypos, llformat("Pixel <%1d, %1d> R:%1d G:%1d B:%1d A:%1d", x_raw, y_raw, color[0], color[1], color[2], color[3])); - ypos += y_inc; - } - - // only display these messages if we are actually rendering beacons at this moment - if (LLPipeline::getRenderBeacons() && LLFloaterReg::instanceVisible("beacons")) - { - if (LLPipeline::getRenderMOAPBeacons()) - { - addText(xpos, ypos, "Viewing media beacons (white)"); - ypos += y_inc; - } - - if (LLPipeline::toggleRenderTypeControlNegated(LLPipeline::RENDER_TYPE_PARTICLES)) - { - addText(xpos, ypos, particle_hiding); - ypos += y_inc; - } - - if (LLPipeline::getRenderParticleBeacons()) - { - addText(xpos, ypos, "Viewing particle beacons (blue)"); - ypos += y_inc; - } - - if (LLPipeline::getRenderSoundBeacons()) - { - addText(xpos, ypos, "Viewing sound beacons (yellow)"); - ypos += y_inc; - } - - if (LLPipeline::getRenderScriptedBeacons()) - { - addText(xpos, ypos, beacon_scripted); - ypos += y_inc; - } - else - if (LLPipeline::getRenderScriptedTouchBeacons()) - { - addText(xpos, ypos, beacon_scripted_touch); - ypos += y_inc; - } - - if (LLPipeline::getRenderPhysicalBeacons()) - { - addText(xpos, ypos, "Viewing physical object beacons (green)"); - ypos += y_inc; - } - } - - static LLUICachedControl show_sun_beacon("sunbeacon", false); - static LLUICachedControl show_moon_beacon("moonbeacon", false); - - if (show_sun_beacon) - { - addText(xpos, ypos, beacon_sun); - ypos += y_inc; - } - if (show_moon_beacon) - { - addText(xpos, ypos, beacon_moon); - ypos += y_inc; - } - - if(log_texture_traffic) - { - U32 old_y = ypos ; - for(S32 i = LLViewerTexture::BOOST_NONE; i < LLViewerTexture::MAX_GL_IMAGE_CATEGORY; i++) - { - if(gTotalTextureBytesPerBoostLevel[i] > (S32Bytes)0) - { - addText(xpos, ypos, llformat("Boost_Level %d: %.3f MB", i, F32Megabytes(gTotalTextureBytesPerBoostLevel[i]).value())); - ypos += y_inc; - } - } - if(ypos != old_y) - { - addText(xpos, ypos, "Network traffic for textures:"); - ypos += y_inc; - } - } - - if (gSavedSettings.getBOOL("DebugShowTextureInfo")) - { - LLViewerObject* objectp = NULL ; - - LLSelectNode* nodep = LLSelectMgr::instance().getHoverNode(); - if (nodep) - { - objectp = nodep->getObject(); - } - - if (objectp && !objectp->isDead()) - { - S32 num_faces = objectp->mDrawable->getNumFaces() ; - std::set tex_list; - - for(S32 i = 0 ; i < num_faces; i++) - { - LLFace* facep = objectp->mDrawable->getFace(i) ; - if(facep) - { - LLViewerFetchedTexture* tex = dynamic_cast(facep->getTexture()) ; - if(tex) - { - if(tex_list.find(tex) != tex_list.end()) - { - continue ; //already displayed. - } - tex_list.insert(tex); - - std::string uuid_str; - tex->getID().toString(uuid_str); - uuid_str = uuid_str.substr(0,7); - - addText(xpos, ypos, llformat("ID: %s v_size: %.3f", uuid_str.c_str(), tex->getMaxVirtualSize())); - ypos += y_inc; - - addText(xpos, ypos, llformat("discard level: %d desired level: %d Missing: %s", tex->getDiscardLevel(), - tex->getDesiredDiscardLevel(), tex->isMissingAsset() ? "Y" : "N")); - ypos += y_inc; - } - } - } - } - } - } - - void draw() - { - LL_RECORD_BLOCK_TIME(FTM_DISPLAY_DEBUG_TEXT); - - // Camera matrix text is hard to see again a white background - // Add a dark background underneath the matrices for readability (contrast) - if (mBackRectCamera1.mTop >= 0) - { - mBackColor.setAlpha( 0.75f ); - gl_rect_2d(mBackRectCamera1, mBackColor, true); - - mBackColor.setAlpha( 0.66f ); - gl_rect_2d(mBackRectCamera2, mBackColor, true); - } - - for (line_list_t::iterator iter = mLineList.begin(); - iter != mLineList.end(); ++iter) - { - const Line& line = *iter; - LLFontGL::getFontMonospace()->renderUTF8(line.text, 0, (F32)line.x, (F32)line.y, mTextColor, - LLFontGL::LEFT, LLFontGL::TOP, LLFontGL::NORMAL, LLFontGL::NO_SHADOW); - } - } - -}; - -void LLViewerWindow::updateDebugText() -{ - mDebugText->update(); -} - -//////////////////////////////////////////////////////////////////////////// -// -// LLViewerWindow -// - -LLViewerWindow::Params::Params() -: title("title"), - name("name"), - x("x"), - y("y"), - width("width"), - height("height"), - min_width("min_width"), - min_height("min_height"), - fullscreen("fullscreen", false), - ignore_pixel_depth("ignore_pixel_depth", false) -{} - - -void LLViewerWindow::handlePieMenu(S32 x, S32 y, MASK mask) -{ - if (CAMERA_MODE_CUSTOMIZE_AVATAR != gAgentCamera.getCameraMode() && LLToolMgr::getInstance()->getCurrentTool() != LLToolPie::getInstance() && gAgent.isInitialized()) - { - // If the current tool didn't process the click, we should show - // the pie menu. This can be done by passing the event to the pie - // menu tool. - LLToolPie::getInstance()->handleRightMouseDown(x, y, mask); - } -} - -bool LLViewerWindow::handleAnyMouseClick(LLWindow *window, LLCoordGL pos, MASK mask, EMouseClickType clicktype, bool down, bool& is_toolmgr_action) -{ - const char* buttonname = ""; - const char* buttonstatestr = ""; - S32 x = pos.mX; - S32 y = pos.mY; - x = ll_round((F32)x / mDisplayScale.mV[VX]); - y = ll_round((F32)y / mDisplayScale.mV[VY]); - - // Handle non-consuming global keybindings, like voice - gViewerInput.handleGlobalBindsMouse(clicktype, mask, down); - - // only send mouse clicks to UI if UI is visible - if(gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) - { - - if (down) - { - buttonstatestr = "down" ; - } - else - { - buttonstatestr = "up" ; - } - - switch (clicktype) - { - case CLICK_LEFT: - mLeftMouseDown = down; - buttonname = "Left"; - break; - case CLICK_RIGHT: - mRightMouseDown = down; - buttonname = "Right"; - break; - case CLICK_MIDDLE: - mMiddleMouseDown = down; - buttonname = "Middle"; - break; - case CLICK_DOUBLELEFT: - mLeftMouseDown = down; - buttonname = "Left Double Click"; - break; - case CLICK_BUTTON4: - buttonname = "Button 4"; - break; - case CLICK_BUTTON5: - buttonname = "Button 5"; - break; - default: - break; // COUNT and NONE - } - - LLView::sMouseHandlerMessage.clear(); - - if (gMenuBarView) - { - // stop ALT-key access to menu - gMenuBarView->resetMenuTrigger(); - } - - if (gDebugClicks) - { - LL_INFOS() << "ViewerWindow " << buttonname << " mouse " << buttonstatestr << " at " << x << "," << y << LL_ENDL; - } - - // Make sure we get a corresponding mouseup event, even if the mouse leaves the window - if (down) - mWindow->captureMouse(); - else - mWindow->releaseMouse(); - - // Indicate mouse was active - LLUI::getInstance()->resetMouseIdleTimer(); - - // Don't let the user move the mouse out of the window until mouse up. - if( LLToolMgr::getInstance()->getCurrentTool()->clipMouseWhenDown() ) - { - mWindow->setMouseClipping(down); - } - - LLMouseHandler* mouse_captor = gFocusMgr.getMouseCapture(); - if( mouse_captor ) - { - S32 local_x; - S32 local_y; - mouse_captor->screenPointToLocal( x, y, &local_x, &local_y ); - if (LLView::sDebugMouseHandling) - { - LL_INFOS() << buttonname << " Mouse " << buttonstatestr << " handled by captor " << mouse_captor->getName() << LL_ENDL; - } - - bool r = mouse_captor->handleAnyMouseClick(local_x, local_y, mask, clicktype, down); - if (r) { - - LL_DEBUGS() << "LLViewerWindow::handleAnyMouseClick viewer with mousecaptor calling updatemouseeventinfo - local_x|global x "<< local_x << " " << x << "local/global y " << local_y << " " << y << LL_ENDL; - - LLViewerEventRecorder::instance().setMouseGlobalCoords(x,y); - LLViewerEventRecorder::instance().logMouseEvent(std::string(buttonstatestr),std::string(buttonname)); - - } - else if (down && clicktype == CLICK_RIGHT) - { - handlePieMenu(x, y, mask); - r = true; - } - return r; - } - - // Mark the click as handled and return if we aren't within the root view to avoid spurious bugs - if( !mRootView->pointInView(x, y) ) - { - return true; - } - // Give the UI views a chance to process the click - - bool r= mRootView->handleAnyMouseClick(x, y, mask, clicktype, down) ; - if (r) - { - - LL_DEBUGS() << "LLViewerWindow::handleAnyMouseClick calling updatemouseeventinfo - global x "<< " " << x << "global y " << y << "buttonstate: " << buttonstatestr << " buttonname " << buttonname << LL_ENDL; - - LLViewerEventRecorder::instance().setMouseGlobalCoords(x,y); - - // Clear local coords - this was a click on root window so these are not needed - // By not including them, this allows the test skeleton generation tool to be smarter when generating code - // the code generator can be smarter because when local coords are present it can try the xui path with local coords - // and fallback to global coordinates only if needed. - // The drawback to this approach is sometimes a valid xui path will appear to work fine, but NOT interact with the UI element - // (VITA support not implemented yet or not visible to VITA due to widget further up xui path not being visible to VITA) - // For this reason it's best to provide hints where possible here by leaving out local coordinates - LLViewerEventRecorder::instance().setMouseLocalCoords(-1,-1); - LLViewerEventRecorder::instance().logMouseEvent(buttonstatestr,buttonname); - - if (LLView::sDebugMouseHandling) - { - LL_INFOS() << buttonname << " Mouse " << buttonstatestr << " " << LLViewerEventRecorder::instance().get_xui() << LL_ENDL; - } - return true; - } else if (LLView::sDebugMouseHandling) - { - LL_INFOS() << buttonname << " Mouse " << buttonstatestr << " not handled by view" << LL_ENDL; - } - } - - // Do not allow tool manager to handle mouseclicks if we have disconnected - if(!gDisconnected && LLToolMgr::getInstance()->getCurrentTool()->handleAnyMouseClick( x, y, mask, clicktype, down ) ) - { - LLViewerEventRecorder::instance().clear_xui(); - is_toolmgr_action = true; - return true; - } - - if (down && clicktype == CLICK_RIGHT) - { - handlePieMenu(x, y, mask); - return true; - } - - // If we got this far on a down-click, it wasn't handled. - // Up-clicks, though, are always handled as far as the OS is concerned. - bool default_rtn = !down; - return default_rtn; -} - -bool LLViewerWindow::handleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask) -{ - mAllowMouseDragging = false; - if (!mMouseDownTimer.getStarted()) - { - mMouseDownTimer.start(); - } - else - { - mMouseDownTimer.reset(); - } - bool down = true; - //handleMouse() loops back to LLViewerWindow::handleAnyMouseClick - return gViewerInput.handleMouse(window, pos, mask, CLICK_LEFT, down); -} - -bool LLViewerWindow::handleDoubleClick(LLWindow *window, LLCoordGL pos, MASK mask) -{ - // try handling as a double-click first, then a single-click if that - // wasn't handled. - bool down = true; - if (gViewerInput.handleMouse(window, pos, mask, CLICK_DOUBLELEFT, down)) - { - return true; - } - return handleMouseDown(window, pos, mask); -} - -bool LLViewerWindow::handleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask) -{ - if (mMouseDownTimer.getStarted()) - { - mMouseDownTimer.stop(); - } - bool down = false; - return gViewerInput.handleMouse(window, pos, mask, CLICK_LEFT, down); -} -bool LLViewerWindow::handleRightMouseDown(LLWindow *window, LLCoordGL pos, MASK mask) -{ - bool down = true; - return gViewerInput.handleMouse(window, pos, mask, CLICK_RIGHT, down); -} - -bool LLViewerWindow::handleRightMouseUp(LLWindow *window, LLCoordGL pos, MASK mask) -{ - bool down = false; - return gViewerInput.handleMouse(window, pos, mask, CLICK_RIGHT, down); -} - -bool LLViewerWindow::handleMiddleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask) -{ - bool down = true; - gViewerInput.handleMouse(window, pos, mask, CLICK_MIDDLE, down); - - // Always handled as far as the OS is concerned. - return true; -} - -LLWindowCallbacks::DragNDropResult LLViewerWindow::handleDragNDrop( LLWindow *window, LLCoordGL pos, MASK mask, LLWindowCallbacks::DragNDropAction action, std::string data) -{ - LLWindowCallbacks::DragNDropResult result = LLWindowCallbacks::DND_NONE; - - const bool prim_media_dnd_enabled = gSavedSettings.getBOOL("PrimMediaDragNDrop"); - const bool slurl_dnd_enabled = gSavedSettings.getBOOL("SLURLDragNDrop"); - - if ( prim_media_dnd_enabled || slurl_dnd_enabled ) - { - switch(action) - { - // Much of the handling for these two cases is the same. - case LLWindowCallbacks::DNDA_TRACK: - case LLWindowCallbacks::DNDA_DROPPED: - case LLWindowCallbacks::DNDA_START_TRACKING: - { - bool drop = (LLWindowCallbacks::DNDA_DROPPED == action); - - if (slurl_dnd_enabled) - { - LLSLURL dropped_slurl(data); - if(dropped_slurl.isSpatial()) - { - if (drop) - { - LLURLDispatcher::dispatch( dropped_slurl.getSLURLString(), LLCommandHandler::NAV_TYPE_CLICKED, NULL, true ); - return LLWindowCallbacks::DND_MOVE; - } - return LLWindowCallbacks::DND_COPY; - } - } - - if (prim_media_dnd_enabled) - { - LLPickInfo pick_info = pickImmediate( pos.mX, pos.mY, - true /* pick_transparent */, - false /* pick_rigged */); - - S32 object_face = pick_info.mObjectFace; - std::string url = data; - - LL_DEBUGS() << "Object: picked at " << pos.mX << ", " << pos.mY << " - face = " << object_face << " - URL = " << url << LL_ENDL; - - LLVOVolume *obj = dynamic_cast(static_cast(pick_info.getObject())); - - if (obj && !obj->getRegion()->getCapability("ObjectMedia").empty()) - { - LLTextureEntry *te = obj->getTE(object_face); - - // can modify URL if we can modify the object or we have navigate permissions - bool allow_modify_url = obj->permModify() || obj->hasMediaPermission( te->getMediaData(), LLVOVolume::MEDIA_PERM_INTERACT ); - - if (te && allow_modify_url ) - { - if (drop) - { - // object does NOT have media already - if ( ! te->hasMedia() ) - { - // we are allowed to modify the object - if ( obj->permModify() ) - { - // Create new media entry - LLSD media_data; - // XXX Should we really do Home URL too? - media_data[LLMediaEntry::HOME_URL_KEY] = url; - media_data[LLMediaEntry::CURRENT_URL_KEY] = url; - media_data[LLMediaEntry::AUTO_PLAY_KEY] = true; - obj->syncMediaData(object_face, media_data, true, true); - // XXX This shouldn't be necessary, should it ?!? - if (obj->getMediaImpl(object_face)) - obj->getMediaImpl(object_face)->navigateReload(); - obj->sendMediaDataUpdate(); - - result = LLWindowCallbacks::DND_COPY; - } - } - else - // object HAS media already - { - // URL passes the whitelist - if (te->getMediaData()->checkCandidateUrl( url ) ) - { - // just navigate to the URL - if (obj->getMediaImpl(object_face)) - { - obj->getMediaImpl(object_face)->navigateTo(url); - } - else - { - // This is very strange. Navigation should - // happen via the Impl, but we don't have one. - // This sends it to the server, which /should/ - // trigger us getting it. Hopefully. - LLSD media_data; - media_data[LLMediaEntry::CURRENT_URL_KEY] = url; - obj->syncMediaData(object_face, media_data, true, true); - obj->sendMediaDataUpdate(); - } - result = LLWindowCallbacks::DND_LINK; - - } - } - LLSelectMgr::getInstance()->unhighlightObjectOnly(mDragHoveredObject); - mDragHoveredObject = NULL; - - } - else - { - // Check the whitelist, if there's media (otherwise just show it) - if (te->getMediaData() == NULL || te->getMediaData()->checkCandidateUrl(url)) - { - if ( obj != mDragHoveredObject) - { - // Highlight the dragged object - LLSelectMgr::getInstance()->unhighlightObjectOnly(mDragHoveredObject); - mDragHoveredObject = obj; - LLSelectMgr::getInstance()->highlightObjectOnly(mDragHoveredObject); - } - result = (! te->hasMedia()) ? LLWindowCallbacks::DND_COPY : LLWindowCallbacks::DND_LINK; - - } - } - } - } - } - } - break; - - case LLWindowCallbacks::DNDA_STOP_TRACKING: - // The cleanup case below will make sure things are unhilighted if necessary. - break; - } - - if (prim_media_dnd_enabled && - result == LLWindowCallbacks::DND_NONE && !mDragHoveredObject.isNull()) - { - LLSelectMgr::getInstance()->unhighlightObjectOnly(mDragHoveredObject); - mDragHoveredObject = NULL; - } - } - - return result; -} - -bool LLViewerWindow::handleMiddleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask) -{ - bool down = false; - gViewerInput.handleMouse(window, pos, mask, CLICK_MIDDLE, down); - - // Always handled as far as the OS is concerned. - return true; -} - -bool LLViewerWindow::handleOtherMouse(LLWindow *window, LLCoordGL pos, MASK mask, S32 button, bool down) -{ - switch (button) - { - case 4: - gViewerInput.handleMouse(window, pos, mask, CLICK_BUTTON4, down); - break; - case 5: - gViewerInput.handleMouse(window, pos, mask, CLICK_BUTTON5, down); - break; - default: - break; - } - - // Always handled as far as the OS is concerned. - return true; -} - -bool LLViewerWindow::handleOtherMouseDown(LLWindow *window, LLCoordGL pos, MASK mask, S32 button) -{ - return handleOtherMouse(window, pos, mask, button, true); -} - -bool LLViewerWindow::handleOtherMouseUp(LLWindow *window, LLCoordGL pos, MASK mask, S32 button) -{ - return handleOtherMouse(window, pos, mask, button, false); -} - -// WARNING: this is potentially called multiple times per frame -void LLViewerWindow::handleMouseMove(LLWindow *window, LLCoordGL pos, MASK mask) -{ - S32 x = pos.mX; - S32 y = pos.mY; - - x = ll_round((F32)x / mDisplayScale.mV[VX]); - y = ll_round((F32)y / mDisplayScale.mV[VY]); - - mMouseInWindow = true; - - // Save mouse point for access during idle() and display() - - LLCoordGL mouse_point(x, y); - - if (mouse_point != mCurrentMousePoint) - { - LLUI::getInstance()->resetMouseIdleTimer(); - } - - saveLastMouse(mouse_point); - - mWindow->showCursorFromMouseMove(); - - if (gAwayTimer.getElapsedTimeF32() > LLAgent::MIN_AFK_TIME - && !gDisconnected) - { - gAgent.clearAFK(); - } -} - -void LLViewerWindow::handleMouseDragged(LLWindow *window, LLCoordGL pos, MASK mask) -{ - if (mMouseDownTimer.getStarted()) - { - if (mMouseDownTimer.getElapsedTimeF32() > 0.1) - { - mAllowMouseDragging = true; - mMouseDownTimer.stop(); - } - } - if(mAllowMouseDragging || !LLToolCamera::getInstance()->hasMouseCapture()) - { - handleMouseMove(window, pos, mask); - } -} - -void LLViewerWindow::handleMouseLeave(LLWindow *window) -{ - // Note: we won't get this if we have captured the mouse. - llassert( gFocusMgr.getMouseCapture() == NULL ); - mMouseInWindow = false; - LLToolTipMgr::instance().blockToolTips(); -} - -bool LLViewerWindow::handleCloseRequest(LLWindow *window) -{ - // User has indicated they want to close, but we may need to ask - // about modified documents. - LLAppViewer::instance()->userQuit(); - // Don't quit immediately - return false; -} - -void LLViewerWindow::handleQuit(LLWindow *window) -{ - if (gNonInteractive) - { - LLAppViewer::instance()->requestQuit(); - } - else - { - LL_INFOS() << "Window forced quit" << LL_ENDL; - LLAppViewer::instance()->forceQuit(); - } -} - -void LLViewerWindow::handleResize(LLWindow *window, S32 width, S32 height) -{ - reshape(width, height); - mResDirty = true; -} - -// The top-level window has gained focus (e.g. via ALT-TAB) -void LLViewerWindow::handleFocus(LLWindow *window) -{ - gFocusMgr.setAppHasFocus(true); - LLModalDialog::onAppFocusGained(); - - gAgent.onAppFocusGained(); - LLToolMgr::getInstance()->onAppFocusGained(); - - // See if we're coming in with modifier keys held down - if (gKeyboard) - { - gKeyboard->resetMaskKeys(); - } - - // resume foreground running timer - // since we artifically limit framerate when not frontmost - gForegroundTime.unpause(); -} - -// The top-level window has lost focus (e.g. via ALT-TAB) -void LLViewerWindow::handleFocusLost(LLWindow *window) -{ - gFocusMgr.setAppHasFocus(false); - //LLModalDialog::onAppFocusLost(); - LLToolMgr::getInstance()->onAppFocusLost(); - gFocusMgr.setMouseCapture( NULL ); - - if (gMenuBarView) - { - // stop ALT-key access to menu - gMenuBarView->resetMenuTrigger(); - } - - // restore mouse cursor - showCursor(); - getWindow()->setMouseClipping(false); - - // If losing focus while keys are down, handle them as - // an 'up' to correctly release states, then reset states - if (gKeyboard) - { - gKeyboard->resetKeyDownAndHandle(); - gKeyboard->resetKeys(); - } - - // pause timer that tracks total foreground running time - gForegroundTime.pause(); -} - - -bool LLViewerWindow::handleTranslatedKeyDown(KEY key, MASK mask, bool repeated) -{ - // Handle non-consuming global keybindings, like voice - // Never affects event processing. - gViewerInput.handleGlobalBindsKeyDown(key, mask); - - if (gAwayTimer.getElapsedTimeF32() > LLAgent::MIN_AFK_TIME) - { - gAgent.clearAFK(); - } - - // *NOTE: We want to interpret KEY_RETURN later when it arrives as - // a Unicode char, not as a keydown. Otherwise when client frame - // rate is really low, hitting return sends your chat text before - // it's all entered/processed. - if (key == KEY_RETURN && mask == MASK_NONE) - { - // RIDER: although, at times some of the controlls (in particular the CEF viewer - // would like to know about the KEYDOWN for an enter key... so ask and pass it along. - LLFocusableElement* keyboard_focus = gFocusMgr.getKeyboardFocus(); - if (keyboard_focus && !keyboard_focus->wantsReturnKey()) - return false; - } - - // remaps, handles ignored cases and returns back to viewer window. - return gViewerInput.handleKey(key, mask, repeated); -} - -bool LLViewerWindow::handleTranslatedKeyUp(KEY key, MASK mask) -{ - // Handle non-consuming global keybindings, like voice - // Never affects event processing. - gViewerInput.handleGlobalBindsKeyUp(key, mask); - - // Let the inspect tool code check for ALT key to set LLToolSelectRect active instead LLToolCamera - LLToolCompInspect * tool_inspectp = LLToolCompInspect::getInstance(); - if (LLToolMgr::getInstance()->getCurrentTool() == tool_inspectp) - { - tool_inspectp->keyUp(key, mask); - } - - return gViewerInput.handleKeyUp(key, mask); -} - -void LLViewerWindow::handleScanKey(KEY key, bool key_down, bool key_up, bool key_level) -{ - LLViewerJoystick::getInstance()->setCameraNeedsUpdate(true); - gViewerInput.scanKey(key, key_down, key_up, key_level); - return; // Be clear this function returns nothing -} - - - - -bool LLViewerWindow::handleActivate(LLWindow *window, bool activated) -{ - if (activated) - { - mActive = true; - send_agent_resume(); - gAgent.clearAFK(); - - // Unmute audio - audio_update_volume(); - } - else - { - mActive = false; - - // if the user has chosen to go Away automatically after some time, then go Away when minimizing - if (gSavedSettings.getS32("AFKTimeout")) - { - gAgent.setAFK(); - } - - // SL-53351: Make sure we're not in mouselook when minimised, to prevent control issues - if (gAgentCamera.getCameraMode() == CAMERA_MODE_MOUSELOOK) - { - gAgentCamera.changeCameraToDefault(); - } - - send_agent_pause(); - - // Mute audio - audio_update_volume(); - } - return true; -} - -bool LLViewerWindow::handleActivateApp(LLWindow *window, bool activating) -{ - //if (!activating) gAgentCamera.changeCameraToDefault(); - - LLViewerJoystick::getInstance()->setNeedsReset(true); - return false; -} - - -void LLViewerWindow::handleMenuSelect(LLWindow *window, S32 menu_item) -{ -} - - -bool LLViewerWindow::handlePaint(LLWindow *window, S32 x, S32 y, S32 width, S32 height) -{ - // *TODO: Enable similar information output for other platforms? DK 2011-02-18 -#if LL_WINDOWS - if (gHeadlessClient) - { - HWND window_handle = (HWND)window->getPlatformWindow(); - PAINTSTRUCT ps; - HDC hdc; - - RECT wnd_rect; - wnd_rect.left = 0; - wnd_rect.top = 0; - wnd_rect.bottom = 200; - wnd_rect.right = 500; - - hdc = BeginPaint(window_handle, &ps); - //SetBKColor(hdc, RGB(255, 255, 255)); - FillRect(hdc, &wnd_rect, CreateSolidBrush(RGB(255, 255, 255))); - - std::string temp_str; - LLTrace::Recording& recording = LLViewerStats::instance().getRecording(); - temp_str = llformat( "FPS %3.1f Phy FPS %2.1f Time Dil %1.3f", /* Flawfinder: ignore */ - recording.getPerSec(LLStatViewer::FPS), //mFPSStat.getMeanPerSec(), - recording.getLastValue(LLStatViewer::SIM_PHYSICS_FPS), - recording.getLastValue(LLStatViewer::SIM_TIME_DILATION)); - S32 len = temp_str.length(); - TextOutA(hdc, 0, 0, temp_str.c_str(), len); - - - LLVector3d pos_global = gAgent.getPositionGlobal(); - temp_str = llformat( "Avatar pos %6.1lf %6.1lf %6.1lf", pos_global.mdV[0], pos_global.mdV[1], pos_global.mdV[2]); - len = temp_str.length(); - TextOutA(hdc, 0, 25, temp_str.c_str(), len); - - TextOutA(hdc, 0, 50, "Set \"HeadlessClient FALSE\" in settings.ini file to reenable", 61); - EndPaint(window_handle, &ps); - return true; - } -#endif - return false; -} - - -void LLViewerWindow::handleScrollWheel(LLWindow *window, S32 clicks) -{ - handleScrollWheel( clicks ); -} - -void LLViewerWindow::handleScrollHWheel(LLWindow *window, S32 clicks) -{ - handleScrollHWheel(clicks); -} - -void LLViewerWindow::handleWindowBlock(LLWindow *window) -{ - send_agent_pause(); -} - -void LLViewerWindow::handleWindowUnblock(LLWindow *window) -{ - send_agent_resume(); -} - -void LLViewerWindow::handleDataCopy(LLWindow *window, S32 data_type, void *data) -{ - const S32 SLURL_MESSAGE_TYPE = 0; - switch (data_type) - { - case SLURL_MESSAGE_TYPE: - // received URL - std::string url = (const char*)data; - LLMediaCtrl* web = NULL; - const bool trusted_browser = false; - // don't treat slapps coming from external browsers as "clicks" as this would bypass throttling - if (LLURLDispatcher::dispatch(url, LLCommandHandler::NAV_TYPE_EXTERNAL, web, trusted_browser)) - { - // bring window to foreground, as it has just been "launched" from a URL - mWindow->bringToFront(); - } - break; - } -} - -bool LLViewerWindow::handleTimerEvent(LLWindow *window) -{ - //TODO: just call this every frame from gatherInput instead of using a convoluted 30fps timer callback - if (LLViewerJoystick::getInstance()->getOverrideCamera()) - { - LLViewerJoystick::getInstance()->updateStatus(); - return true; - } - return false; -} - -bool LLViewerWindow::handleDeviceChange(LLWindow *window) -{ - // give a chance to use a joystick after startup (hot-plugging) - if (!LLViewerJoystick::getInstance()->isJoystickInitialized() ) - { - LLViewerJoystick::getInstance()->init(true); - return true; - } - return false; -} - -bool LLViewerWindow::handleDPIChanged(LLWindow *window, F32 ui_scale_factor, S32 window_width, S32 window_height) -{ - if (ui_scale_factor >= MIN_UI_SCALE && ui_scale_factor <= MAX_UI_SCALE) - { - LLViewerWindow::reshape(window_width, window_height); - mResDirty = true; - return true; - } - else - { - LL_WARNS() << "DPI change caused UI scale to go out of bounds: " << ui_scale_factor << LL_ENDL; - return false; - } -} - -bool LLViewerWindow::handleWindowDidChangeScreen(LLWindow *window) -{ - LLCoordScreen window_rect; - mWindow->getSize(&window_rect); - reshape(window_rect.mX, window_rect.mY); - return true; -} - -void LLViewerWindow::handlePingWatchdog(LLWindow *window, const char * msg) -{ - LLAppViewer::instance()->pingMainloopTimeout(msg); -} - - -void LLViewerWindow::handleResumeWatchdog(LLWindow *window) -{ - LLAppViewer::instance()->resumeMainloopTimeout(); -} - -void LLViewerWindow::handlePauseWatchdog(LLWindow *window) -{ - LLAppViewer::instance()->pauseMainloopTimeout(); -} - -//virtual -std::string LLViewerWindow::translateString(const char* tag) -{ - return LLTrans::getString( std::string(tag) ); -} - -//virtual -std::string LLViewerWindow::translateString(const char* tag, - const std::map& args) -{ - // LLTrans uses a special subclass of std::string for format maps, - // but we must use std::map<> in these callbacks, otherwise we create - // a dependency between LLWindow and LLFormatMapString. So copy the data. - LLStringUtil::format_map_t args_copy; - std::map::const_iterator it = args.begin(); - for ( ; it != args.end(); ++it) - { - args_copy[it->first] = it->second; - } - return LLTrans::getString( std::string(tag), args_copy); -} - -// -// Classes -// -LLViewerWindow::LLViewerWindow(const Params& p) -: mWindow(NULL), - mActive(true), - mUIVisible(true), - mWindowRectRaw(0, p.height, p.width, 0), - mWindowRectScaled(0, p.height, p.width, 0), - mWorldViewRectRaw(0, p.height, p.width, 0), - mLeftMouseDown(false), - mMiddleMouseDown(false), - mRightMouseDown(false), - mMouseInWindow( false ), - mAllowMouseDragging(true), - mMouseDownTimer(), - mLastMask( MASK_NONE ), - mToolStored( NULL ), - mHideCursorPermanent( false ), - mCursorHidden(false), - mIgnoreActivate( false ), - mResDirty(false), - mStatesDirty(false), - mCurrResolutionIndex(0), - mProgressView(NULL) -{ - // gKeyboard is still NULL, so it doesn't do LLWindowListener any good to - // pass its value right now. Instead, pass it a nullary function that - // will, when we later need it, return the value of gKeyboard. - // boost::lambda::var() constructs such a functor on the fly. - mWindowListener.reset(new LLWindowListener(this, boost::lambda::var(gKeyboard))); - mViewerWindowListener.reset(new LLViewerWindowListener(this)); - - mSystemChannel.reset(new LLNotificationChannel("System", "Visible", LLNotificationFilters::includeEverything)); - mCommunicationChannel.reset(new LLCommunicationChannel("Communication", "Visible")); - mAlertsChannel.reset(new LLNotificationsUI::LLViewerAlertHandler("VW_alerts", "alert")); - mModalAlertsChannel.reset(new LLNotificationsUI::LLViewerAlertHandler("VW_alertmodal", "alertmodal")); - - bool ignore = gSavedSettings.getBOOL("IgnoreAllNotifications"); - LLNotifications::instance().setIgnoreAllNotifications(ignore); - if (ignore) - { - LL_INFOS() << "NOTE: ALL NOTIFICATIONS THAT OCCUR WILL GET ADDED TO IGNORE LIST FOR LATER RUNS." << LL_ENDL; - } - - - /* - LLWindowCallbacks* callbacks, - const std::string& title, const std::string& name, S32 x, S32 y, S32 width, S32 height, U32 flags, - bool fullscreen, - bool clearBg, - bool disable_vsync, - bool ignore_pixel_depth, - U32 fsaa_samples) - */ - // create window - - U32 max_core_count = gSavedSettings.getU32("EmulateCoreCount"); - F32 max_gl_version = gSavedSettings.getF32("RenderMaxOpenGLVersion"); - - LLControlVariable* vram_control = gSavedSettings.getControl("RenderMaxVRAMBudget"); - U32 max_vram = vram_control->getValue().asInteger(); - mMaxVRAMControlConnection = vram_control->getSignal()->connect( - [this](LLControlVariable* control, const LLSD& new_val, const LLSD& old_val) - { - if (mWindow) mWindow->setMaxVRAMMegabytes(new_val.asInteger()); - }); - - - mWindow = LLWindowManager::createWindow(this, - p.title, p.name, p.x, p.y, p.width, p.height, 0, - p.fullscreen, - gHeadlessClient, - gSavedSettings.getBOOL("RenderVSyncEnable"), - !gHeadlessClient, - p.ignore_pixel_depth, - 0, - max_core_count, - max_vram, - max_gl_version); //don't use window level anti-aliasing - - if (NULL == mWindow) - { - LLSplashScreen::update(LLTrans::getString("StartupRequireDriverUpdate")); - - LL_WARNS("Window") << "Failed to create window, to be shutting Down, be sure your graphics driver is updated." << LL_ENDL ; - - ms_sleep(5000) ; //wait for 5 seconds. - - LLSplashScreen::update(LLTrans::getString("ShuttingDown")); -#if LL_LINUX - LL_WARNS() << "Unable to create window, be sure screen is set at 32-bit color and your graphics driver is configured correctly. See README-linux.txt for further information." - << LL_ENDL; -#else - LL_WARNS("Window") << "Unable to create window, be sure screen is set at 32-bit color in Control Panels->Display->Settings" - << LL_ENDL; -#endif - LLAppViewer::instance()->fastQuit(1); - } - else if (!LLViewerShaderMgr::sInitialized) - { - //immediately initialize shaders - LLViewerShaderMgr::sInitialized = true; - LLViewerShaderMgr::instance()->setShaders(); - } - - if (!LLAppViewer::instance()->restoreErrorTrap()) - { - // this always happens, so downgrading it to INFO - LL_INFOS("Window") << " Someone took over my signal/exception handler (post createWindow; normal)" << LL_ENDL; - } - - const bool do_not_enforce = false; - mWindow->setMinSize(p.min_width, p.min_height, do_not_enforce); // root view not set - LLCoordScreen scr; - mWindow->getSize(&scr); - - // Reset UI scale factor on first run if OS's display scaling is not 100% - if (gSavedSettings.getBOOL("ResetUIScaleOnFirstRun")) - { - if (mWindow->getSystemUISize() != 1.f) - { - gSavedSettings.setF32("UIScaleFactor", 1.f); - } - gSavedSettings.setBOOL("ResetUIScaleOnFirstRun", false); - } - - // Get the real window rect the window was created with (since there are various OS-dependent reasons why - // the size of a window or fullscreen context may have been adjusted slightly...) - F32 ui_scale_factor = llclamp(gSavedSettings.getF32("UIScaleFactor") * mWindow->getSystemUISize(), MIN_UI_SCALE, MAX_UI_SCALE); - - mDisplayScale.setVec(llmax(1.f / mWindow->getPixelAspectRatio(), 1.f), llmax(mWindow->getPixelAspectRatio(), 1.f)); - mDisplayScale *= ui_scale_factor; - LLUI::setScaleFactor(mDisplayScale); - - { - LLCoordWindow size; - mWindow->getSize(&size); - mWindowRectRaw.set(0, size.mY, size.mX, 0); - mWindowRectScaled.set(0, ll_round((F32)size.mY / mDisplayScale.mV[VY]), ll_round((F32)size.mX / mDisplayScale.mV[VX]), 0); - } - - LLFontManager::initClass(); - // Init font system, load default fonts and generate basic glyphs - // currently it takes aprox. 0.5 sec and we would load these fonts anyway - // before login screen. - LLFontGL::initClass( gSavedSettings.getF32("FontScreenDPI"), - mDisplayScale.mV[VX], - mDisplayScale.mV[VY], - gDirUtilp->getAppRODataDir()); - - // - // We want to set this stuff up BEFORE we initialize the pipeline, so we can turn off - // stuff like AGP if we think that it'll crash the viewer. - // - LL_DEBUGS("Window") << "Loading feature tables." << LL_ENDL; - - // Initialize OpenGL Renderer - LLVertexBuffer::initClass(mWindow); - LL_INFOS("RenderInit") << "LLVertexBuffer initialization done." << LL_ENDL ; - if (!gGL.init(true)) - { - LLError::LLUserWarningMsg::show(LLTrans::getString("MBVideoDrvErr")); - LL_ERRS() << "gGL not initialized" << LL_ENDL; - } - - if (LLFeatureManager::getInstance()->isSafe() - || (gSavedSettings.getS32("LastFeatureVersion") != LLFeatureManager::getInstance()->getVersion()) - || (gSavedSettings.getString("LastGPUString") != LLFeatureManager::getInstance()->getGPUString()) - || (gSavedSettings.getBOOL("ProbeHardwareOnStartup"))) - { - LLFeatureManager::getInstance()->applyRecommendedSettings(); - gSavedSettings.setBOOL("ProbeHardwareOnStartup", false); - } - - // If we crashed while initializng GL stuff last time, disable certain features - if (gSavedSettings.getBOOL("RenderInitError")) - { - mInitAlert = "DisplaySettingsNoShaders"; - LLFeatureManager::getInstance()->setGraphicsLevel(0, false); - gSavedSettings.setU32("RenderQualityPerformance", 0); - } - - // Init the image list. Must happen after GL is initialized and before the images that - // LLViewerWindow needs are requested, as well as before LLViewerMedia starts updating images. - LLImageGL::initClass(mWindow, LLViewerTexture::MAX_GL_IMAGE_CATEGORY, false, gSavedSettings.getBOOL("RenderGLMultiThreadedTextures"), gSavedSettings.getBOOL("RenderGLMultiThreadedMedia")); - gTextureList.init(); - LLViewerTextureManager::init() ; - gBumpImageList.init(); - - // Create container for all sub-views - LLView::Params rvp; - rvp.name("root"); - rvp.rect(mWindowRectScaled); - rvp.mouse_opaque(false); - rvp.follows.flags(FOLLOWS_NONE); - mRootView = LLUICtrlFactory::create(rvp); - LLUI::getInstance()->setRootView(mRootView); - - // Make avatar head look forward at start - mCurrentMousePoint.mX = getWindowWidthScaled() / 2; - mCurrentMousePoint.mY = getWindowHeightScaled() / 2; - - gShowOverlayTitle = gSavedSettings.getBOOL("ShowOverlayTitle"); - mOverlayTitle = gSavedSettings.getString("OverlayTitle"); - // Can't have spaces in settings.ini strings, so use underscores instead and convert them. - LLStringUtil::replaceChar(mOverlayTitle, '_', ' '); - - mDebugText = new LLDebugText(this); - - mWorldViewRectScaled = calcScaledRect(mWorldViewRectRaw, mDisplayScale); -} - -std::string LLViewerWindow::getLastSnapshotDir() -{ - return sSnapshotDir; -} - -void LLViewerWindow::initGLDefaults() -{ - // RN: Need this for translation and stretch manip. - gBox.prerender(); -} - -struct MainPanel : public LLPanel -{ -}; - -void LLViewerWindow::initBase() -{ - S32 height = getWindowHeightScaled(); - S32 width = getWindowWidthScaled(); - - LLRect full_window(0, height, width, 0); - - //////////////////// - // - // Set the gamma - // - - F32 gamma = gSavedSettings.getF32("RenderGamma"); - if (gamma != 0.0f) - { - getWindow()->setGamma(gamma); - } - - // Create global views - - // Login screen and main_view.xml need edit menus for preferences and browser - LL_DEBUGS("AppInit") << "initializing edit menu" << LL_ENDL; - initialize_edit_menu(); - - LLFontGL::loadCommonFonts(); - - // Create the floater view at the start so that other views can add children to it. - // (But wait to add it as a child of the root view so that it will be in front of the - // other views.) - MainPanel* main_view = new MainPanel(); - if (!main_view->buildFromFile("main_view.xml")) - { - LL_ERRS() << "Failed to initialize viewer: Viewer couldn't process file main_view.xml, " - << "if this problem happens again, please validate your installation." << LL_ENDL; - } - main_view->setShape(full_window); - getRootView()->addChild(main_view); - - // placeholder widget that controls where "world" is rendered - mWorldViewPlaceholder = main_view->getChildView("world_view_rect")->getHandle(); - mPopupView = main_view->getChild("popup_holder"); - mHintHolder = main_view->getChild("hint_holder")->getHandle(); - mLoginPanelHolder = main_view->getChild("login_panel_holder")->getHandle(); - - // Create the toolbar view - // Get a pointer to the toolbar view holder - LLPanel* panel_holder = main_view->getChild("toolbar_view_holder"); - // Load the toolbar view from file - gToolBarView = LLUICtrlFactory::getInstance()->createFromFile("panel_toolbar_view.xml", panel_holder, LLDefaultChildRegistry::instance()); - if (!gToolBarView) - { - LL_ERRS() << "Failed to initialize viewer: Viewer couldn't process file panel_toolbar_view.xml, " - << "if this problem happens again, please validate your installation." << LL_ENDL; - } - gToolBarView->setShape(panel_holder->getLocalRect()); - // Hide the toolbars for the moment: we'll make them visible after logging in world (see LLViewerWindow::initWorldUI()) - gToolBarView->setVisible(false); - - // Constrain floaters to inside the menu and status bar regions. - gFloaterView = main_view->getChild("Floater View"); - for (S32 i = 0; i < LLToolBarEnums::TOOLBAR_COUNT; ++i) - { - LLToolBar * toolbarp = gToolBarView->getToolbar((LLToolBarEnums::EToolBarLocation)i); - if (toolbarp) - { - toolbarp->getCenterLayoutPanel()->setReshapeCallback(boost::bind(&LLFloaterView::setToolbarRect, gFloaterView, _1, _2)); - } - } - gFloaterView->setFloaterSnapView(main_view->getChild("floater_snap_region")->getHandle()); - gSnapshotFloaterView = main_view->getChild("Snapshot Floater View"); - - const F32 CHAT_PERSIST_TIME = 20.f; - - // Console - llassert( !gConsole ); - LLConsole::Params cp; - cp.name("console"); - cp.max_lines(gSavedSettings.getS32("ConsoleBufferSize")); - cp.rect(getChatConsoleRect()); - cp.persist_time(CHAT_PERSIST_TIME); - cp.font_size_index(gSavedSettings.getS32("ChatFontSize")); - cp.follows.flags(FOLLOWS_LEFT | FOLLOWS_RIGHT | FOLLOWS_BOTTOM); - gConsole = LLUICtrlFactory::create(cp); - getRootView()->addChild(gConsole); - - // optionally forward warnings to chat console/chat floater - // for qa runs and dev builds -#if !LL_RELEASE_FOR_DOWNLOAD - RecordToChatConsole::getInstance()->startRecorder(); -#else - if(gSavedSettings.getBOOL("QAMode")) - { - RecordToChatConsole::getInstance()->startRecorder(); - } -#endif - - gDebugView = getRootView()->getChild("DebugView"); - gDebugView->init(); - gToolTipView = getRootView()->getChild("tooltip view"); - - // Initialize do not disturb response message when logged in - LLAppViewer::instance()->setOnLoginCompletedCallback(boost::bind(&LLFloaterPreference::initDoNotDisturbResponse)); - - // Add the progress bar view (startup view), which overrides everything - mProgressView = getRootView()->findChild("progress_view"); - setShowProgress(false); - setProgressCancelButtonVisible(false); - - gMenuHolder = getRootView()->getChild("Menu Holder"); - LLMenuGL::sMenuContainer = gMenuHolder; -} - -void LLViewerWindow::initWorldUI() -{ - if (gNonInteractive) - { - gIMMgr = LLIMMgr::getInstance(); - LLNavigationBar::getInstance(); - gFloaterView->pushVisibleAll(false); - return; - } - - S32 height = mRootView->getRect().getHeight(); - S32 width = mRootView->getRect().getWidth(); - LLRect full_window(0, height, width, 0); - - - gIMMgr = LLIMMgr::getInstance(); - - //getRootView()->sendChildToFront(gFloaterView); - //getRootView()->sendChildToFront(gSnapshotFloaterView); - - if (!gNonInteractive) - { - LLPanel* chiclet_container = getRootView()->getChild("chiclet_container"); - LLChicletBar* chiclet_bar = LLChicletBar::getInstance(); - chiclet_bar->setShape(chiclet_container->getLocalRect()); - chiclet_bar->setFollowsAll(); - chiclet_container->addChild(chiclet_bar); - chiclet_container->setVisible(true); - } - - LLRect morph_view_rect = full_window; - morph_view_rect.stretch( -STATUS_BAR_HEIGHT ); - morph_view_rect.mTop = full_window.mTop - 32; - LLMorphView::Params mvp; - mvp.name("MorphView"); - mvp.rect(morph_view_rect); - mvp.visible(false); - gMorphView = LLUICtrlFactory::create(mvp); - getRootView()->addChild(gMorphView); - - LLWorldMapView::initClass(); - - // Force gFloaterWorldMap to initialize - LLFloaterReg::getInstance("world_map"); - - // Force gFloaterTools to initialize - LLFloaterReg::getInstance("build"); - - LLNavigationBar* navbar = LLNavigationBar::getInstance(); - if (!gStatusBar) - { - // Status bar - LLPanel* status_bar_container = getRootView()->getChild("status_bar_container"); - gStatusBar = new LLStatusBar(status_bar_container->getLocalRect()); - gStatusBar->setFollows(FOLLOWS_LEFT | FOLLOWS_TOP | FOLLOWS_RIGHT); - gStatusBar->setShape(status_bar_container->getLocalRect()); - // sync bg color with menu bar - gStatusBar->setBackgroundColor(gMenuBarView->getBackgroundColor().get()); - // add InBack so that gStatusBar won't be drawn over menu - status_bar_container->addChildInBack(gStatusBar, 2/*tab order, after menu*/); - status_bar_container->setVisible(true); - - // Navigation bar - LLView* nav_bar_container = getRootView()->getChild("nav_bar_container"); - - navbar->setShape(nav_bar_container->getLocalRect()); - navbar->setBackgroundColor(gMenuBarView->getBackgroundColor().get()); - nav_bar_container->addChild(navbar); - nav_bar_container->setVisible(true); - } - else - { - LLPanel* status_bar_container = getRootView()->getChild("status_bar_container"); - LLView* nav_bar_container = getRootView()->getChild("nav_bar_container"); - status_bar_container->setVisible(true); - nav_bar_container->setVisible(true); - } - - if (!gSavedSettings.getBOOL("ShowNavbarNavigationPanel")) - { - navbar->setVisible(false); - } - else - { - reshapeStatusBarContainer(); - } - - - // Top Info bar - LLPanel* topinfo_bar_container = getRootView()->getChild("topinfo_bar_container"); - LLPanelTopInfoBar* topinfo_bar = LLPanelTopInfoBar::getInstance(); - - topinfo_bar->setShape(topinfo_bar_container->getLocalRect()); - - topinfo_bar_container->addChild(topinfo_bar); - topinfo_bar_container->setVisible(true); - - if (!gSavedSettings.getBOOL("ShowMiniLocationPanel")) - { - topinfo_bar->setVisible(false); - } - - if ( gHUDView == NULL ) - { - LLRect hud_rect = full_window; - hud_rect.mBottom += 50; - if (gMenuBarView && gMenuBarView->isInVisibleChain()) - { - hud_rect.mTop -= gMenuBarView->getRect().getHeight(); - } - gHUDView = new LLHUDView(hud_rect); - getRootView()->addChild(gHUDView); - getRootView()->sendChildToBack(gHUDView); - } - - LLPanel* panel_ssf_container = getRootView()->getChild("state_management_buttons_container"); - - LLPanelStandStopFlying* panel_stand_stop_flying = LLPanelStandStopFlying::getInstance(); - panel_ssf_container->addChild(panel_stand_stop_flying); - - LLPanelHideBeacon* panel_hide_beacon = LLPanelHideBeacon::getInstance(); - panel_ssf_container->addChild(panel_hide_beacon); - - panel_ssf_container->setVisible(true); - - LLMenuOptionPathfindingRebakeNavmesh::getInstance()->initialize(); - - // Load and make the toolbars visible - // Note: we need to load the toolbars only *after* the user is logged in and IW - if (gToolBarView) - { - gToolBarView->loadToolbars(); - gToolBarView->setVisible(true); - } - - if (!gNonInteractive) - { - LLMediaCtrl* destinations = LLFloaterReg::getInstance("destinations")->getChild("destination_guide_contents"); - if (destinations) - { - destinations->setErrorPageURL(gSavedSettings.getString("GenericErrorPageURL")); - std::string url = gSavedSettings.getString("DestinationGuideURL"); - url = LLWeb::expandURLSubstitutions(url, LLSD()); - destinations->navigateTo(url, HTTP_CONTENT_TEXT_HTML); - } - LLMediaCtrl* avatar_picker = LLFloaterReg::getInstance("avatar")->findChild("avatar_picker_contents"); - if (avatar_picker) - { - avatar_picker->setErrorPageURL(gSavedSettings.getString("GenericErrorPageURL")); - std::string url = gSavedSettings.getString("AvatarPickerURL"); - url = LLWeb::expandURLSubstitutions(url, LLSD()); - avatar_picker->navigateTo(url, HTTP_CONTENT_TEXT_HTML); - } - } -} - -// Destroy the UI -void LLViewerWindow::shutdownViews() -{ - // clean up warning logger - RecordToChatConsole::getInstance()->stopRecorder(); - LL_INFOS() << "Warning logger is cleaned." << LL_ENDL ; - - gFocusMgr.unlockFocus(); - gFocusMgr.setMouseCapture(NULL); - gFocusMgr.setKeyboardFocus(NULL); - gFocusMgr.setTopCtrl(NULL); - if (mWindow) - { - mWindow->allowLanguageTextInput(NULL, false); - } - - delete mDebugText; - mDebugText = NULL; - - LL_INFOS() << "DebugText deleted." << LL_ENDL ; - - // Cleanup global views - if (gMorphView) - { - gMorphView->setVisible(false); - } - LL_INFOS() << "Global views cleaned." << LL_ENDL ; - - LLNotificationsUI::LLToast::cleanupToasts(); - LL_INFOS() << "Leftover toast cleaned up." << LL_ENDL; - - // DEV-40930: Clear sModalStack. Otherwise, any LLModalDialog left open - // will crump with LL_ERRS. - LLModalDialog::shutdownModals(); - LL_INFOS() << "LLModalDialog shut down." << LL_ENDL; - - // destroy the nav bar, not currently part of gViewerWindow - // *TODO: Make LLNavigationBar part of gViewerWindow - LLNavigationBar::deleteSingleton(); - LL_INFOS() << "LLNavigationBar destroyed." << LL_ENDL ; - - // destroy menus after instantiating navbar above, as it needs - // access to gMenuHolder - cleanup_menus(); - LL_INFOS() << "menus destroyed." << LL_ENDL ; - - view_listener_t::cleanup(); - LL_INFOS() << "view listeners destroyed." << LL_ENDL ; - - // Clean up pointers that are going to be invalid. (todo: check sMenuContainer) - mProgressView = NULL; - mPopupView = NULL; - - // Delete all child views. - delete mRootView; - mRootView = NULL; - LL_INFOS() << "RootView deleted." << LL_ENDL ; - - LLMenuOptionPathfindingRebakeNavmesh::getInstance()->quit(); - - // Automatically deleted as children of mRootView. Fix the globals. - gStatusBar = NULL; - gIMMgr = NULL; - gToolTipView = NULL; - - gToolBarView = NULL; - gFloaterView = NULL; - gMorphView = NULL; - - gHUDView = NULL; -} - -void LLViewerWindow::shutdownGL() -{ - //-------------------------------------------------------- - // Shutdown GL cleanly. Order is very important here. - //-------------------------------------------------------- - LLFontGL::destroyDefaultFonts(); - SUBSYSTEM_CLEANUP(LLFontManager); - stop_glerror(); - - gSky.cleanup(); - stop_glerror(); - - LL_INFOS() << "Cleaning up pipeline" << LL_ENDL; - gPipeline.cleanup(); - stop_glerror(); - - //MUST clean up pipeline before cleaning up wearables - LL_INFOS() << "Cleaning up wearables" << LL_ENDL; - LLWearableList::instance().cleanup() ; - - gTextureList.shutdown(); - stop_glerror(); - - gBumpImageList.shutdown(); - stop_glerror(); - - LLWorldMapView::cleanupTextures(); - - LLViewerTextureManager::cleanup() ; - SUBSYSTEM_CLEANUP(LLImageGL) ; - - LL_INFOS() << "All textures and llimagegl images are destroyed!" << LL_ENDL ; - - LL_INFOS() << "Cleaning up select manager" << LL_ENDL; - LLSelectMgr::getInstance()->cleanup(); - - LL_INFOS() << "Stopping GL during shutdown" << LL_ENDL; - stopGL(false); - stop_glerror(); - - gGL.shutdown(); - - SUBSYSTEM_CLEANUP(LLVertexBuffer); - - LL_INFOS() << "LLVertexBuffer cleaned." << LL_ENDL ; -} - -// shutdownViews() and shutdownGL() need to be called first -LLViewerWindow::~LLViewerWindow() -{ - LL_INFOS() << "Destroying Window" << LL_ENDL; - destroyWindow(); - - delete mDebugText; - mDebugText = NULL; - - if (LLViewerShaderMgr::sInitialized) - { - LLViewerShaderMgr::releaseInstance(); - LLViewerShaderMgr::sInitialized = false; - } - - mMaxVRAMControlConnection.disconnect(); -} - - -void LLViewerWindow::setCursor( ECursorType c ) -{ - mWindow->setCursor( c ); -} - -void LLViewerWindow::showCursor() -{ - mWindow->showCursor(); - - mCursorHidden = false; -} - -void LLViewerWindow::hideCursor() -{ - // And hide the cursor - mWindow->hideCursor(); - - mCursorHidden = true; -} - -void LLViewerWindow::sendShapeToSim() -{ - LLMessageSystem* msg = gMessageSystem; - if(!msg) return; - msg->newMessageFast(_PREHASH_AgentHeightWidth); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addU32Fast(_PREHASH_CircuitCode, gMessageSystem->mOurCircuitCode); - msg->nextBlockFast(_PREHASH_HeightWidthBlock); - msg->addU32Fast(_PREHASH_GenCounter, 0); - U16 height16 = (U16) mWorldViewRectRaw.getHeight(); - U16 width16 = (U16) mWorldViewRectRaw.getWidth(); - msg->addU16Fast(_PREHASH_Height, height16); - msg->addU16Fast(_PREHASH_Width, width16); - gAgent.sendReliableMessage(); -} - -// Must be called after window is created to set up agent -// camera variables and UI variables. -void LLViewerWindow::reshape(S32 width, S32 height) -{ - // Destroying the window at quit time generates spurious - // reshape messages. We don't care about these, and we - // don't want to send messages because the message system - // may have been destructed. - if (!LLApp::isExiting()) - { - gWindowResized = true; - - // update our window rectangle - mWindowRectRaw.mRight = mWindowRectRaw.mLeft + width; - mWindowRectRaw.mTop = mWindowRectRaw.mBottom + height; - - //glViewport(0, 0, width, height ); - - LLViewerCamera * camera = LLViewerCamera::getInstance(); // simpleton, might not exist - if (height > 0 && camera) - { - camera->setViewHeightInPixels( mWorldViewRectRaw.getHeight() ); - camera->setAspect( getWorldViewAspectRatio() ); - } - - calcDisplayScale(); - - bool display_scale_changed = mDisplayScale != LLUI::getScaleFactor(); - LLUI::setScaleFactor(mDisplayScale); - - // update our window rectangle - mWindowRectScaled.mRight = mWindowRectScaled.mLeft + ll_round((F32)width / mDisplayScale.mV[VX]); - mWindowRectScaled.mTop = mWindowRectScaled.mBottom + ll_round((F32)height / mDisplayScale.mV[VY]); - - setup2DViewport(); - - // Inform lower views of the change - // round up when converting coordinates to make sure there are no gaps at edge of window - LLView::sForceReshape = display_scale_changed; - mRootView->reshape(llceil((F32)width / mDisplayScale.mV[VX]), llceil((F32)height / mDisplayScale.mV[VY])); - if (display_scale_changed) - { - // Needs only a 'scale change' update, everything else gets handled by LLLayoutStack::updateClass() - LLPanelLogin::reshapePanel(); - } - LLView::sForceReshape = false; - - // clear font width caches - if (display_scale_changed) - { - LLHUDObject::reshapeAll(); - } - - sendShapeToSim(); - - // store new settings for the mode we are in, regardless - bool maximized = mWindow->getMaximized(); - gSavedSettings.setBOOL("WindowMaximized", maximized); - - if (!maximized) - { - U32 min_window_width=gSavedSettings.getU32("MinWindowWidth"); - U32 min_window_height=gSavedSettings.getU32("MinWindowHeight"); - // tell the OS specific window code about min window size - mWindow->setMinSize(min_window_width, min_window_height); - - LLCoordScreen window_rect; - if (!gNonInteractive && mWindow->getSize(&window_rect)) - { - // Only save size if not maximized - gSavedSettings.setU32("WindowWidth", window_rect.mX); - gSavedSettings.setU32("WindowHeight", window_rect.mY); - } - } - - sample(LLStatViewer::WINDOW_WIDTH, width); - sample(LLStatViewer::WINDOW_HEIGHT, height); - - LLLayoutStack::updateClass(); - } -} - - -// Hide normal UI when a logon fails -void LLViewerWindow::setNormalControlsVisible( bool visible ) -{ - if(LLChicletBar::instanceExists()) - { - LLChicletBar::getInstance()->setVisible(visible); - LLChicletBar::getInstance()->setEnabled(visible); - } - - if ( gMenuBarView ) - { - gMenuBarView->setVisible( visible ); - gMenuBarView->setEnabled( visible ); - - // ...and set the menu color appropriately. - setMenuBackgroundColor(gAgent.getGodLevel() > GOD_NOT, - LLGridManager::getInstance()->isInProductionGrid()); - } - - if ( gStatusBar ) - { - gStatusBar->setVisible( visible ); - gStatusBar->setEnabled( visible ); - } - - LLNavigationBar* navbarp = LLUI::getInstance()->getRootView()->findChild("navigation_bar"); - if (navbarp) - { - // when it's time to show navigation bar we need to ensure that the user wants to see it - // i.e. ShowNavbarNavigationPanel option is true - navbarp->setVisible( visible && gSavedSettings.getBOOL("ShowNavbarNavigationPanel") ); - } -} - -void LLViewerWindow::setMenuBackgroundColor(bool god_mode, bool dev_grid) -{ - LLSD args; - LLColor4 new_bg_color; - - // god more important than project, proj more important than grid - if ( god_mode ) - { - if ( LLGridManager::getInstance()->isInProductionGrid() ) - { - new_bg_color = LLUIColorTable::instance().getColor( "MenuBarGodBgColor" ); - } - else - { - new_bg_color = LLUIColorTable::instance().getColor( "MenuNonProductionGodBgColor" ); - } - } - else - { - switch (LLVersionInfo::instance().getViewerMaturity()) - { - case LLVersionInfo::TEST_VIEWER: - new_bg_color = LLUIColorTable::instance().getColor( "MenuBarTestBgColor" ); - break; - - case LLVersionInfo::PROJECT_VIEWER: - new_bg_color = LLUIColorTable::instance().getColor( "MenuBarProjectBgColor" ); - break; - - case LLVersionInfo::BETA_VIEWER: - new_bg_color = LLUIColorTable::instance().getColor( "MenuBarBetaBgColor" ); - break; - - case LLVersionInfo::RELEASE_VIEWER: - if(!LLGridManager::getInstance()->isInProductionGrid()) - { - new_bg_color = LLUIColorTable::instance().getColor( "MenuNonProductionBgColor" ); - } - else - { - new_bg_color = LLUIColorTable::instance().getColor( "MenuBarBgColor" ); - } - break; - } - } - - if(gMenuBarView) - { - gMenuBarView->setBackgroundColor( new_bg_color ); - } - - if(gStatusBar) - { - gStatusBar->setBackgroundColor( new_bg_color ); - } -} - -void LLViewerWindow::drawDebugText() -{ - gUIProgram.bind(); - gGL.color4f(1,1,1,1); - gGL.pushMatrix(); - gGL.pushUIMatrix(); - { - // scale view by UI global scale factor and aspect ratio correction factor - gGL.scaleUI(mDisplayScale.mV[VX], mDisplayScale.mV[VY], 1.f); - mDebugText->draw(); - } - gGL.popUIMatrix(); - gGL.popMatrix(); - - gGL.flush(); - gUIProgram.unbind(); -} - -void LLViewerWindow::draw() -{ - -//#if LL_DEBUG - LLView::sIsDrawing = true; -//#endif - stop_glerror(); - - LLUI::setLineWidth(1.f); - - LLUI::setLineWidth(1.f); - // Reset any left-over transforms - gGL.matrixMode(LLRender::MM_MODELVIEW); - - gGL.loadIdentity(); - - //S32 screen_x, screen_y; - - if (!gSavedSettings.getBOOL("RenderUIBuffer")) - { - LLView::sDirtyRect = getWindowRectScaled(); - } - - // HACK for timecode debugging - if (gSavedSettings.getBOOL("DisplayTimecode")) - { - // draw timecode block - std::string text; - - gGL.loadIdentity(); - - microsecondsToTimecodeString(gFrameTime,text); - const LLFontGL* font = LLFontGL::getFontSansSerif(); - font->renderUTF8(text, 0, - ll_round((getWindowWidthScaled()/2)-100.f), - ll_round((getWindowHeightScaled()-60.f)), - LLColor4( 1.f, 1.f, 1.f, 1.f ), - LLFontGL::LEFT, LLFontGL::TOP); - } - - // Draw all nested UI views. - // No translation needed, this view is glued to 0,0 - - gUIProgram.bind(); - gGL.color4f(1, 1, 1, 1); - - gGL.pushMatrix(); - LLUI::pushMatrix(); - { - - // scale view by UI global scale factor and aspect ratio correction factor - gGL.scaleUI(mDisplayScale.mV[VX], mDisplayScale.mV[VY], 1.f); - - LLVector2 old_scale_factor = LLUI::getScaleFactor(); - // apply camera zoom transform (for high res screenshots) - F32 zoom_factor = LLViewerCamera::getInstance()->getZoomFactor(); - S16 sub_region = LLViewerCamera::getInstance()->getZoomSubRegion(); - if (zoom_factor > 1.f) - { - //decompose subregion number to x and y values - int pos_y = sub_region / llceil(zoom_factor); - int pos_x = sub_region - (pos_y*llceil(zoom_factor)); - // offset for this tile - gGL.translatef((F32)getWindowWidthScaled() * -(F32)pos_x, - (F32)getWindowHeightScaled() * -(F32)pos_y, - 0.f); - gGL.scalef(zoom_factor, zoom_factor, 1.f); - LLUI::getScaleFactor() *= zoom_factor; - } - - // Draw tool specific overlay on world - LLToolMgr::getInstance()->getCurrentTool()->draw(); - - if( gAgentCamera.cameraMouselook() || LLFloaterCamera::inFreeCameraMode() ) - { - drawMouselookInstructions(); - stop_glerror(); - } - - // Draw all nested UI views. - // No translation needed, this view is glued to 0,0 - mRootView->draw(); - - if (LLView::sDebugRects) - { - gToolTipView->drawStickyRect(); - } - - // Draw optional on-top-of-everyone view - LLUICtrl* top_ctrl = gFocusMgr.getTopCtrl(); - if (top_ctrl && top_ctrl->getVisible()) - { - S32 screen_x, screen_y; - top_ctrl->localPointToScreen(0, 0, &screen_x, &screen_y); - - gGL.matrixMode(LLRender::MM_MODELVIEW); - LLUI::pushMatrix(); - LLUI::translate( (F32) screen_x, (F32) screen_y); - top_ctrl->draw(); - LLUI::popMatrix(); - } - - - if( gShowOverlayTitle && !mOverlayTitle.empty() ) - { - // Used for special titles such as "Second Life - Special E3 2003 Beta" - const S32 DIST_FROM_TOP = 20; - LLFontGL::getFontSansSerifBig()->renderUTF8( - mOverlayTitle, 0, - ll_round( getWindowWidthScaled() * 0.5f), - getWindowHeightScaled() - DIST_FROM_TOP, - LLColor4(1, 1, 1, 0.4f), - LLFontGL::HCENTER, LLFontGL::TOP); - } - - LLUI::setScaleFactor(old_scale_factor); - } - LLUI::popMatrix(); - gGL.popMatrix(); - - gUIProgram.unbind(); - - LLView::sIsDrawing = false; -} - -// Takes a single keyup event, usually when UI is visible -bool LLViewerWindow::handleKeyUp(KEY key, MASK mask) -{ - if (LLSetKeyBindDialog::recordKey(key, mask, false)) - { - LL_DEBUGS() << "KeyUp handled by LLSetKeyBindDialog" << LL_ENDL; - LLViewerEventRecorder::instance().logKeyEvent(key, mask); - return true; - } - - LLFocusableElement* keyboard_focus = gFocusMgr.getKeyboardFocus(); - - if (keyboard_focus - && !(mask & (MASK_CONTROL | MASK_ALT)) - && !gFocusMgr.getKeystrokesOnly()) - { - // We have keyboard focus, and it's not an accelerator - if (keyboard_focus && keyboard_focus->wantsKeyUpKeyDown()) - { - return keyboard_focus->handleKeyUp(key, mask, false); - } - else if (key < 0x80) - { - // Not a special key, so likely (we hope) to generate a character. Let it fall through to character handler first. - return (gFocusMgr.getKeyboardFocus() != NULL); - } - } - - if (keyboard_focus) - { - if (keyboard_focus->handleKeyUp(key, mask, false)) - { - LL_DEBUGS() << "LLviewerWindow::handleKeyUp - in 'traverse up' - no loops seen... just called keyboard_focus->handleKeyUp an it returned true" << LL_ENDL; - LLViewerEventRecorder::instance().logKeyEvent(key, mask); - return true; - } - else { - LL_DEBUGS() << "LLviewerWindow::handleKeyUp - in 'traverse up' - no loops seen... just called keyboard_focus->handleKeyUp an it returned false" << LL_ENDL; - } - } - - // Try for a new-format gesture - if (LLGestureMgr::instance().triggerGestureRelease(key, mask)) - { - LL_DEBUGS() << "LLviewerWindow::handleKey new gesture release feature" << LL_ENDL; - LLViewerEventRecorder::instance().logKeyEvent(key,mask); - return true; - } - //Old format gestures do not support this, so no need to implement it. - - // don't pass keys on to world when something in ui has focus - return gFocusMgr.childHasKeyboardFocus(mRootView) - || LLMenuGL::getKeyboardMode() - || (gMenuBarView && gMenuBarView->getHighlightedItem() && gMenuBarView->getHighlightedItem()->isActive()); -} - -// Takes a single keydown event, usually when UI is visible -bool LLViewerWindow::handleKey(KEY key, MASK mask) -{ - // hide tooltips on keypress - LLToolTipMgr::instance().blockToolTips(); - - // Menus get handled on key down instead of key up - // so keybindings have to be recorded before that - if (LLSetKeyBindDialog::recordKey(key, mask, true)) - { - LL_DEBUGS() << "Key handled by LLSetKeyBindDialog" << LL_ENDL; - LLViewerEventRecorder::instance().logKeyEvent(key,mask); - return true; - } - - LLFocusableElement* keyboard_focus = gFocusMgr.getKeyboardFocus(); - - if (keyboard_focus - && !gFocusMgr.getKeystrokesOnly()) - { - //Most things should fall through, but mouselook is an exception, - //don't switch to mouselook if any floater has focus - if ((key == KEY_MOUSELOOK) && !(mask & (MASK_CONTROL | MASK_ALT))) - { - return true; - } - - LLUICtrl* cur_focus = dynamic_cast(keyboard_focus); - if (cur_focus && cur_focus->acceptsTextInput()) - { -#ifdef LL_WINDOWS - // On windows Alt Gr key generates additional Ctrl event, as result handling situations - // like 'AltGr + D' will result in 'Alt+Ctrl+D'. If it results in WM_CHAR, don't let it - // pass into menu or it will trigger 'develop' menu assigned to this combination on top - // of character handling. - // Alt Gr can be additionally modified by Shift - const MASK alt_gr = MASK_CONTROL | MASK_ALT; - LLWindowWin32 *window = static_cast(mWindow); - U32 raw_key = window->getRawWParam(); - if ((mask & alt_gr) != 0 - && ((raw_key >= 0x30 && raw_key <= 0x5A) //0-9, plus normal chartacters - || (raw_key >= 0xBA && raw_key <= 0xE4)) // Misc/OEM characters that can be covered by AltGr, ex: -, =, ~ - && (GetKeyState(VK_RMENU) & 0x8000) != 0 - && (GetKeyState(VK_RCONTROL) & 0x8000) == 0) // ensure right control is not pressed, only left one - { - // Alt Gr key is represented as right alt and left control. - // Any alt+ctrl combination is treated as Alt Gr by TranslateMessage() and - // will generate a WM_CHAR message, but here we only treat virtual Alt Graph - // key by checking if this specific combination has unicode char. - // - // I decided to handle only virtual RAlt+LCtrl==AltGr combination to minimize - // impact on menu, but the right way might be to handle all Alt+Ctrl calls. - - BYTE keyboard_state[256]; - if (GetKeyboardState(keyboard_state)) - { - const int char_count = 6; - wchar_t chars[char_count]; - HKL layout = GetKeyboardLayout(0); - // ToUnicodeEx changes buffer state on OS below Win10, which is undesirable, - // but since we already did a TranslateMessage() in gatherInput(), this - // should have no negative effect - // ToUnicodeEx works with virtual key codes - int res = ToUnicodeEx(raw_key, 0, keyboard_state, chars, char_count, 1 << 2 /*do not modify buffer flag*/, layout); - if (res == 1 && chars[0] >= 0x20) - { - // Let it fall through to character handler and get a WM_CHAR. - return true; - } - } - } -#endif - - if (!(mask & (MASK_CONTROL | MASK_ALT))) - { - // We have keyboard focus, and it's not an accelerator - if (keyboard_focus && keyboard_focus->wantsKeyUpKeyDown()) - { - return keyboard_focus->handleKey(key, mask, false); - } - else if (key < 0x80) - { - // Not a special key, so likely (we hope) to generate a character. Let it fall through to character handler first. - return true; - } - } - } - } - - // let menus handle navigation keys for navigation - if ((gMenuBarView && gMenuBarView->handleKey(key, mask, true)) - ||(gLoginMenuBarView && gLoginMenuBarView->handleKey(key, mask, true)) - ||(gMenuHolder && gMenuHolder->handleKey(key, mask, true))) - { - LL_DEBUGS() << "LLviewerWindow::handleKey handle nav keys for nav" << LL_ENDL; - LLViewerEventRecorder::instance().logKeyEvent(key,mask); - return true; - } - - - // give menus a chance to handle modified (Ctrl, Alt) shortcut keys before current focus - // as long as focus isn't locked - if (mask & (MASK_CONTROL | MASK_ALT) && !gFocusMgr.focusLocked()) - { - // Check the current floater's menu first, if it has one. - if (gFocusMgr.keyboardFocusHasAccelerators() - && keyboard_focus - && keyboard_focus->handleKey(key,mask,false)) - { - LLViewerEventRecorder::instance().logKeyEvent(key,mask); - return true; - } - - if (gAgent.isInitialized() - && (gAgent.getTeleportState() == LLAgent::TELEPORT_NONE || gAgent.getTeleportState() == LLAgent::TELEPORT_LOCAL) - && gMenuBarView - && gMenuBarView->handleAcceleratorKey(key, mask)) - { - LLViewerEventRecorder::instance().logKeyEvent(key, mask); - return true; - } - - if (gLoginMenuBarView && gLoginMenuBarView->handleAcceleratorKey(key, mask)) - { - LLViewerEventRecorder::instance().logKeyEvent(key,mask); - return true; - } - } - - // give floaters first chance to handle TAB key - // so frontmost floater gets focus - // if nothing has focus, go to first or last UI element as appropriate - if (key == KEY_TAB && (mask & MASK_CONTROL || keyboard_focus == NULL)) - { - LL_WARNS() << "LLviewerWindow::handleKey give floaters first chance at tab key " << LL_ENDL; - if (gMenuHolder) gMenuHolder->hideMenus(); - - // if CTRL-tabbing (and not just TAB with no focus), go into window cycle mode - gFloaterView->setCycleMode((mask & MASK_CONTROL) != 0); - - // do CTRL-TAB and CTRL-SHIFT-TAB logic - if (mask & MASK_SHIFT) - { - mRootView->focusPrevRoot(); - } - else - { - mRootView->focusNextRoot(); - } - LLViewerEventRecorder::instance().logKeyEvent(key,mask); - return true; - } - // hidden edit menu for cut/copy/paste - if (gEditMenu && gEditMenu->handleAcceleratorKey(key, mask)) - { - LLViewerEventRecorder::instance().logKeyEvent(key,mask); - return true; - } - - LLFloater* focused_floaterp = gFloaterView->getFocusedFloater(); - std::string focusedFloaterName = (focused_floaterp ? focused_floaterp->getInstanceName() : ""); - - if( keyboard_focus ) - { - if ((focusedFloaterName == "nearby_chat") || (focusedFloaterName == "im_container") || (focusedFloaterName == "impanel")) - { - if (gSavedSettings.getBOOL("ArrowKeysAlwaysMove")) - { - // let Control-Up and Control-Down through for chat line history, - if (!(key == KEY_UP && mask == MASK_CONTROL) - && !(key == KEY_DOWN && mask == MASK_CONTROL) - && !(key == KEY_UP && mask == MASK_ALT) - && !(key == KEY_DOWN && mask == MASK_ALT)) - { - switch(key) - { - case KEY_LEFT: - case KEY_RIGHT: - case KEY_UP: - case KEY_DOWN: - case KEY_PAGE_UP: - case KEY_PAGE_DOWN: - case KEY_HOME: - case KEY_END: - // when chatbar is empty or ArrowKeysAlwaysMove set, - // pass arrow keys on to avatar... - return false; - default: - break; - } - } - } - } - - if (keyboard_focus->handleKey(key, mask, false)) - { - - LL_DEBUGS() << "LLviewerWindow::handleKey - in 'traverse up' - no loops seen... just called keyboard_focus->handleKey an it returned true" << LL_ENDL; - LLViewerEventRecorder::instance().logKeyEvent(key,mask); - return true; - } else { - LL_DEBUGS() << "LLviewerWindow::handleKey - in 'traverse up' - no loops seen... just called keyboard_focus->handleKey an it returned false" << LL_ENDL; - } - } - - if( LLToolMgr::getInstance()->getCurrentTool()->handleKey(key, mask) ) - { - LL_DEBUGS() << "LLviewerWindow::handleKey toolbar handling?" << LL_ENDL; - LLViewerEventRecorder::instance().logKeyEvent(key,mask); - return true; - } - - // Try for a new-format gesture - if (LLGestureMgr::instance().triggerGesture(key, mask)) - { - LL_DEBUGS() << "LLviewerWindow::handleKey new gesture feature" << LL_ENDL; - LLViewerEventRecorder::instance().logKeyEvent(key,mask); - return true; - } - - // See if this is a gesture trigger. If so, eat the key and - // don't pass it down to the menus. - if (gGestureList.trigger(key, mask)) - { - LL_DEBUGS() << "LLviewerWindow::handleKey check gesture trigger" << LL_ENDL; - LLViewerEventRecorder::instance().logKeyEvent(key,mask); - return true; - } - - // If "Pressing letter keys starts local chat" option is selected, we are not in mouselook, - // no view has keyboard focus, this is a printable character key (and no modifier key is - // pressed except shift), then give focus to nearby chat (STORM-560) - if ( LLStartUp::getStartupState() >= STATE_STARTED && - gSavedSettings.getS32("LetterKeysFocusChatBar") && !gAgentCamera.cameraMouselook() && - !keyboard_focus && key < 0x80 && (mask == MASK_NONE || mask == MASK_SHIFT) ) - { - // Initialize nearby chat if it's missing - LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); - if (!nearby_chat) - { - LLSD name("im_container"); - LLFloaterReg::toggleInstanceOrBringToFront(name); - } - - LLChatEntry* chat_editor = LLFloaterReg::findTypedInstance("nearby_chat")->getChatBox(); - if (chat_editor) - { - // passing NULL here, character will be added later when it is handled by character handler. - nearby_chat->startChat(NULL); - return true; - } - } - - // give menus a chance to handle unmodified accelerator keys - if (gAgent.isInitialized() - && (gAgent.getTeleportState() == LLAgent::TELEPORT_NONE || gAgent.getTeleportState() == LLAgent::TELEPORT_LOCAL) - && gMenuBarView - && gMenuBarView->handleAcceleratorKey(key, mask)) - { - LLViewerEventRecorder::instance().logKeyEvent(key, mask); - return true; - } - - if (gLoginMenuBarView && gLoginMenuBarView->handleAcceleratorKey(key, mask)) - { - return true; - } - - // don't pass keys on to world when something in ui has focus - return gFocusMgr.childHasKeyboardFocus(mRootView) - || LLMenuGL::getKeyboardMode() - || (gMenuBarView && gMenuBarView->getHighlightedItem() && gMenuBarView->getHighlightedItem()->isActive()); -} - - -bool LLViewerWindow::handleUnicodeChar(llwchar uni_char, MASK mask) -{ - // HACK: We delay processing of return keys until they arrive as a Unicode char, - // so that if you're typing chat text at low frame rate, we don't send the chat - // until all keystrokes have been entered. JC - // HACK: Numeric keypad on Mac is Unicode 3 - // HACK: Control-M on Windows is Unicode 13 - if ((uni_char == 13 && mask != MASK_CONTROL) - || (uni_char == 3 && mask == MASK_NONE) ) - { - if (mask != MASK_ALT) - { - // remaps, handles ignored cases and returns back to viewer window. - return gViewerInput.handleKey(KEY_RETURN, mask, gKeyboard->getKeyRepeated(KEY_RETURN)); - } - } - - // let menus handle navigation (jump) keys - if (gMenuBarView && gMenuBarView->handleUnicodeChar(uni_char, true)) - { - return true; - } - - // Traverses up the hierarchy - LLFocusableElement* keyboard_focus = gFocusMgr.getKeyboardFocus(); - if( keyboard_focus ) - { - if (keyboard_focus->handleUnicodeChar(uni_char, false)) - { - return true; - } - - return true; - } - - return false; -} - - -void LLViewerWindow::handleScrollWheel(S32 clicks) -{ - LLUI::getInstance()->resetMouseIdleTimer(); - - LLMouseHandler* mouse_captor = gFocusMgr.getMouseCapture(); - if( mouse_captor ) - { - S32 local_x; - S32 local_y; - mouse_captor->screenPointToLocal( mCurrentMousePoint.mX, mCurrentMousePoint.mY, &local_x, &local_y ); - mouse_captor->handleScrollWheel(local_x, local_y, clicks); - if (LLView::sDebugMouseHandling) - { - LL_INFOS() << "Scroll Wheel handled by captor " << mouse_captor->getName() << LL_ENDL; - } - return; - } - - LLUICtrl* top_ctrl = gFocusMgr.getTopCtrl(); - if (top_ctrl) - { - S32 local_x; - S32 local_y; - top_ctrl->screenPointToLocal( mCurrentMousePoint.mX, mCurrentMousePoint.mY, &local_x, &local_y ); - if (top_ctrl->handleScrollWheel(local_x, local_y, clicks)) return; - } - - if (mRootView->handleScrollWheel(mCurrentMousePoint.mX, mCurrentMousePoint.mY, clicks) ) - { - if (LLView::sDebugMouseHandling) - { - LL_INFOS() << "Scroll Wheel" << LLView::sMouseHandlerMessage << LL_ENDL; - } - return; - } - else if (LLView::sDebugMouseHandling) - { - LL_INFOS() << "Scroll Wheel not handled by view" << LL_ENDL; - } - - // Zoom the camera in and out behavior - - if(top_ctrl == 0 - && getWorldViewRectScaled().pointInRect(mCurrentMousePoint.mX, mCurrentMousePoint.mY) - && gAgentCamera.isInitialized()) - gAgentCamera.handleScrollWheel(clicks); - - return; -} - -void LLViewerWindow::handleScrollHWheel(S32 clicks) -{ - if (LLAppViewer::instance()->quitRequested()) - { - return; - } - - LLUI::getInstance()->resetMouseIdleTimer(); - - LLMouseHandler* mouse_captor = gFocusMgr.getMouseCapture(); - if (mouse_captor) - { - S32 local_x; - S32 local_y; - mouse_captor->screenPointToLocal(mCurrentMousePoint.mX, mCurrentMousePoint.mY, &local_x, &local_y); - mouse_captor->handleScrollHWheel(local_x, local_y, clicks); - if (LLView::sDebugMouseHandling) - { - LL_INFOS() << "Scroll Horizontal Wheel handled by captor " << mouse_captor->getName() << LL_ENDL; - } - return; - } - - LLUICtrl* top_ctrl = gFocusMgr.getTopCtrl(); - if (top_ctrl) - { - S32 local_x; - S32 local_y; - top_ctrl->screenPointToLocal(mCurrentMousePoint.mX, mCurrentMousePoint.mY, &local_x, &local_y); - if (top_ctrl->handleScrollHWheel(local_x, local_y, clicks)) return; - } - - if (mRootView->handleScrollHWheel(mCurrentMousePoint.mX, mCurrentMousePoint.mY, clicks)) - { - if (LLView::sDebugMouseHandling) - { - LL_INFOS() << "Scroll Horizontal Wheel" << LLView::sMouseHandlerMessage << LL_ENDL; - } - return; - } - else if (LLView::sDebugMouseHandling) - { - LL_INFOS() << "Scroll Horizontal Wheel not handled by view" << LL_ENDL; - } - - return; -} - -void LLViewerWindow::addPopup(LLView* popup) -{ - if (mPopupView) - { - mPopupView->addPopup(popup); - } -} - -void LLViewerWindow::removePopup(LLView* popup) -{ - if (mPopupView) - { - mPopupView->removePopup(popup); - } -} - -void LLViewerWindow::clearPopups() -{ - if (mPopupView) - { - mPopupView->clearPopups(); - } -} - -void LLViewerWindow::moveCursorToCenter() -{ - if (! gSavedSettings.getBOOL("DisableMouseWarp")) - { - S32 x = getWorldViewWidthScaled() / 2; - S32 y = getWorldViewHeightScaled() / 2; - - LLUI::getInstance()->setMousePositionScreen(x, y); - - //on a forced move, all deltas get zeroed out to prevent jumping - mCurrentMousePoint.set(x,y); - mLastMousePoint.set(x,y); - mCurrentMouseDelta.set(0,0); - } -} - - -////////////////////////////////////////////////////////////////////// -// -// Hover handlers -// - -void append_xui_tooltip(LLView* viewp, LLToolTip::Params& params) -{ - if (viewp) - { - if (!params.styled_message.empty()) - { - params.styled_message.add().text("\n---------\n"); - } - LLView::root_to_view_iterator_t end_tooltip_it = viewp->endRootToView(); - // NOTE: we skip "root" since it is assumed - for (LLView::root_to_view_iterator_t tooltip_it = ++viewp->beginRootToView(); - tooltip_it != end_tooltip_it; - ++tooltip_it) - { - LLView* viewp = *tooltip_it; - - params.styled_message.add().text(viewp->getName()); - - LLPanel* panelp = dynamic_cast(viewp); - if (panelp && !panelp->getXMLFilename().empty()) - { - params.styled_message.add() - .text("(" + panelp->getXMLFilename() + ")") - .style.color(LLColor4(0.7f, 0.7f, 1.f, 1.f)); - } - params.styled_message.add().text("/"); - } - } -} - -static LLTrace::BlockTimerStatHandle ftm("Update UI"); - -// Update UI based on stored mouse position from mouse-move -// event processing. -void LLViewerWindow::updateUI() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; //LL_RECORD_BLOCK_TIME(ftm); - - static std::string last_handle_msg; - - if (gLoggedInTime.getStarted()) - { - const F32 DESTINATION_GUIDE_HINT_TIMEOUT = 1200.f; - const F32 SIDE_PANEL_HINT_TIMEOUT = 300.f; - if (gLoggedInTime.getElapsedTimeF32() > DESTINATION_GUIDE_HINT_TIMEOUT) - { - LLFirstUse::notUsingDestinationGuide(); - } - if (gLoggedInTime.getElapsedTimeF32() > SIDE_PANEL_HINT_TIMEOUT) - { - LLFirstUse::notUsingSidePanel(); - } - } - - LLConsole::updateClass(); - - // animate layout stacks so we have up to date rect for world view - LLLayoutStack::updateClass(); - - // use full window for world view when not rendering UI - bool world_view_uses_full_window = gAgentCamera.cameraMouselook() || !gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI); - updateWorldViewRect(world_view_uses_full_window); - - LLView::sMouseHandlerMessage.clear(); - - S32 x = mCurrentMousePoint.mX; - S32 y = mCurrentMousePoint.mY; - - MASK mask = gKeyboard->currentMask(true); - - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_RAYCAST)) - { - gDebugRaycastFaceHit = -1; - gDebugRaycastObject = cursorIntersect(-1, -1, 512.f, NULL, -1, false, false, true, false, - &gDebugRaycastFaceHit, - &gDebugRaycastIntersection, - &gDebugRaycastTexCoord, - &gDebugRaycastNormal, - &gDebugRaycastTangent, - &gDebugRaycastStart, - &gDebugRaycastEnd); - - gDebugRaycastParticle = gPipeline.lineSegmentIntersectParticle(gDebugRaycastStart, gDebugRaycastEnd, &gDebugRaycastParticleIntersection, NULL); - } - - updateMouseDelta(); - updateKeyboardFocus(); - - bool handled = false; - - LLUICtrl* top_ctrl = gFocusMgr.getTopCtrl(); - LLMouseHandler* mouse_captor = gFocusMgr.getMouseCapture(); - LLView* captor_view = dynamic_cast(mouse_captor); - - //FIXME: only include captor and captor's ancestors if mouse is truly over them --RN - - //build set of views containing mouse cursor by traversing UI hierarchy and testing - //screen rect against mouse cursor - view_handle_set_t mouse_hover_set; - - // constraint mouse enter events to children of mouse captor - LLView* root_view = captor_view; - - // if mouse captor doesn't exist or isn't a LLView - // then allow mouse enter events on entire UI hierarchy - if (!root_view) - { - root_view = mRootView; - } - - static LLCachedControl dump_menu_holder(gSavedSettings, "DumpMenuHolderSize", false); - if (dump_menu_holder) - { - static bool init = false; - static LLFrameTimer child_count_timer; - static std::vector child_vec; - if (!init) - { - child_count_timer.resetWithExpiry(5.f); - init = true; - } - if (child_count_timer.hasExpired()) - { - LL_INFOS() << "gMenuHolder child count: " << gMenuHolder->getChildCount() << LL_ENDL; - std::vector local_child_vec; - LLView::child_list_t child_list = *gMenuHolder->getChildList(); - for (auto child : child_list) - { - local_child_vec.emplace_back(child->getName()); - } - if (!local_child_vec.empty() && local_child_vec != child_vec) - { - std::vector out_vec; - std::sort(local_child_vec.begin(), local_child_vec.end()); - std::sort(child_vec.begin(), child_vec.end()); - std::set_difference(child_vec.begin(), child_vec.end(), local_child_vec.begin(), local_child_vec.end(), std::inserter(out_vec, out_vec.begin())); - if (!out_vec.empty()) - { - LL_INFOS() << "gMenuHolder removal diff size: '"<getParent(); - while(captor_parent_view) - { - mouse_hover_set.insert(captor_parent_view->getHandle()); - captor_parent_view = captor_parent_view->getParent(); - } - } - - // aggregate visible views that contain mouse cursor in display order - LLPopupView::popup_list_t popups = mPopupView->getCurrentPopups(); - - for(LLPopupView::popup_list_t::iterator popup_it = popups.begin(); popup_it != popups.end(); ++popup_it) - { - LLView* popup = popup_it->get(); - if (popup && popup->calcScreenBoundingRect().pointInRect(x, y)) - { - // iterator over contents of top_ctrl, and throw into mouse_hover_set - for (LLView::tree_iterator_t it = popup->beginTreeDFS(); - it != popup->endTreeDFS(); - ++it) - { - LLView* viewp = *it; - if (viewp->getVisible() - && viewp->calcScreenBoundingRect().pointInRect(x, y)) - { - // we have a view that contains the mouse, add it to the set - mouse_hover_set.insert(viewp->getHandle()); - } - else - { - // skip this view and all of its children - it.skipDescendants(); - } - } - } - } - - // while the top_ctrl contains the mouse cursor, only it and its descendants will receive onMouseEnter events - if (top_ctrl && top_ctrl->calcScreenBoundingRect().pointInRect(x, y)) - { - // iterator over contents of top_ctrl, and throw into mouse_hover_set - for (LLView::tree_iterator_t it = top_ctrl->beginTreeDFS(); - it != top_ctrl->endTreeDFS(); - ++it) - { - LLView* viewp = *it; - if (viewp->getVisible() - && viewp->calcScreenBoundingRect().pointInRect(x, y)) - { - // we have a view that contains the mouse, add it to the set - mouse_hover_set.insert(viewp->getHandle()); - } - else - { - // skip this view and all of its children - it.skipDescendants(); - } - } - } - else - { - // walk UI tree in depth-first order - for (LLView::tree_iterator_t it = root_view->beginTreeDFS(); - it != root_view->endTreeDFS(); - ++it) - { - LLView* viewp = *it; - // calculating the screen rect involves traversing the parent, so this is less than optimal - if (viewp->getVisible() - && viewp->calcScreenBoundingRect().pointInRect(x, y)) - { - - // if this view is mouse opaque, nothing behind it should be in mouse_hover_set - if (viewp->getMouseOpaque()) - { - // constrain further iteration to children of this widget - it = viewp->beginTreeDFS(); - } - - // we have a view that contains the mouse, add it to the set - mouse_hover_set.insert(viewp->getHandle()); - } - else - { - // skip this view and all of its children - it.skipDescendants(); - } - } - } - } - - typedef std::vector > view_handle_list_t; - - // call onMouseEnter() on all views which contain the mouse cursor but did not before - view_handle_list_t mouse_enter_views; - std::set_difference(mouse_hover_set.begin(), mouse_hover_set.end(), - mMouseHoverViews.begin(), mMouseHoverViews.end(), - std::back_inserter(mouse_enter_views)); - for (view_handle_list_t::iterator it = mouse_enter_views.begin(); - it != mouse_enter_views.end(); - ++it) - { - LLView* viewp = it->get(); - if (viewp) - { - LLRect view_screen_rect = viewp->calcScreenRect(); - viewp->onMouseEnter(x - view_screen_rect.mLeft, y - view_screen_rect.mBottom, mask); - } - } - - // call onMouseLeave() on all views which no longer contain the mouse cursor - view_handle_list_t mouse_leave_views; - std::set_difference(mMouseHoverViews.begin(), mMouseHoverViews.end(), - mouse_hover_set.begin(), mouse_hover_set.end(), - std::back_inserter(mouse_leave_views)); - for (view_handle_list_t::iterator it = mouse_leave_views.begin(); - it != mouse_leave_views.end(); - ++it) - { - LLView* viewp = it->get(); - if (viewp) - { - LLRect view_screen_rect = viewp->calcScreenRect(); - viewp->onMouseLeave(x - view_screen_rect.mLeft, y - view_screen_rect.mBottom, mask); - } - } - - // store resulting hover set for next frame - swap(mMouseHoverViews, mouse_hover_set); - - // only handle hover events when UI is enabled - if (gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) - { - - if( mouse_captor ) - { - // Pass hover events to object capturing mouse events. - S32 local_x; - S32 local_y; - mouse_captor->screenPointToLocal( x, y, &local_x, &local_y ); - handled = mouse_captor->handleHover(local_x, local_y, mask); - if (LLView::sDebugMouseHandling) - { - LL_INFOS() << "Hover handled by captor " << mouse_captor->getName() << LL_ENDL; - } - - if( !handled ) - { - LL_DEBUGS("UserInput") << "hover not handled by mouse captor" << LL_ENDL; - } - } - else - { - if (top_ctrl) - { - S32 local_x, local_y; - top_ctrl->screenPointToLocal( x, y, &local_x, &local_y ); - handled = top_ctrl->pointInView(local_x, local_y) && top_ctrl->handleHover(local_x, local_y, mask); - } - - if ( !handled ) - { - // x and y are from last time mouse was in window - // mMouseInWindow tracks *actual* mouse location - if (mMouseInWindow && mRootView->handleHover(x, y, mask) ) - { - if (LLView::sDebugMouseHandling && LLView::sMouseHandlerMessage != last_handle_msg) - { - last_handle_msg = LLView::sMouseHandlerMessage; - LL_INFOS() << "Hover" << LLView::sMouseHandlerMessage << LL_ENDL; - } - handled = true; - } - else if (LLView::sDebugMouseHandling) - { - if (last_handle_msg != LLStringUtil::null) - { - last_handle_msg.clear(); - LL_INFOS() << "Hover not handled by view" << LL_ENDL; - } - } - } - - if (!handled) - { - LLTool *tool = LLToolMgr::getInstance()->getCurrentTool(); - - if(mMouseInWindow && tool) - { - handled = tool->handleHover(x, y, mask); - } - } - } - - // Show a new tool tip (or update one that is already shown) - bool tool_tip_handled = false; - std::string tool_tip_msg; - if( handled - && !mWindow->isCursorHidden()) - { - LLRect screen_sticky_rect = mRootView->getLocalRect(); - S32 local_x, local_y; - - static LLCachedControl debug_show_xui_names(gSavedSettings, "DebugShowXUINames", 0); - if (debug_show_xui_names) - { - LLToolTip::Params params; - - LLView* tooltip_view = mRootView; - LLView::tree_iterator_t end_it = mRootView->endTreeDFS(); - for (LLView::tree_iterator_t it = mRootView->beginTreeDFS(); it != end_it; ++it) - { - LLView* viewp = *it; - LLRect screen_rect; - viewp->localRectToScreen(viewp->getLocalRect(), &screen_rect); - if (!(viewp->getVisible() - && screen_rect.pointInRect(x, y))) - { - it.skipDescendants(); - } - // only report xui names for LLUICtrls, - // and blacklist the various containers we don't care about - else if (dynamic_cast(viewp) - && viewp != gMenuHolder - && viewp != gFloaterView - && viewp != gConsole) - { - if (dynamic_cast(viewp)) - { - // constrain search to descendants of this (frontmost) floater - // by resetting iterator - it = viewp->beginTreeDFS(); - } - - // if we are in a new part of the tree (not a descendent of current tooltip_view) - // then push the results for tooltip_view and start with a new potential view - // NOTE: this emulates visiting only the leaf nodes that meet our criteria - if (!viewp->hasAncestor(tooltip_view)) - { - append_xui_tooltip(tooltip_view, params); - screen_sticky_rect.intersectWith(tooltip_view->calcScreenRect()); - } - tooltip_view = viewp; - } - } - - append_xui_tooltip(tooltip_view, params); - params.styled_message.add().text("\n"); - - screen_sticky_rect.intersectWith(tooltip_view->calcScreenRect()); - - params.sticky_rect = screen_sticky_rect; - params.max_width = 400; - - LLToolTipMgr::instance().show(params); - } - // if there is a mouse captor, nothing else gets a tooltip - else if (mouse_captor) - { - mouse_captor->screenPointToLocal(x, y, &local_x, &local_y); - tool_tip_handled = mouse_captor->handleToolTip(local_x, local_y, mask); - } - else - { - // next is top_ctrl - if (!tool_tip_handled && top_ctrl) - { - top_ctrl->screenPointToLocal(x, y, &local_x, &local_y); - tool_tip_handled = top_ctrl->handleToolTip(local_x, local_y, mask ); - } - - if (!tool_tip_handled) - { - local_x = x; local_y = y; - tool_tip_handled = mRootView->handleToolTip(local_x, local_y, mask ); - } - - LLTool* current_tool = LLToolMgr::getInstance()->getCurrentTool(); - if (!tool_tip_handled && current_tool) - { - current_tool->screenPointToLocal(x, y, &local_x, &local_y); - tool_tip_handled = current_tool->handleToolTip(local_x, local_y, mask ); - } - } - } - } - else - { // just have tools handle hover when UI is turned off - LLTool *tool = LLToolMgr::getInstance()->getCurrentTool(); - - if(mMouseInWindow && tool) - { - handled = tool->handleHover(x, y, mask); - } - } - - updateLayout(); - - mLastMousePoint = mCurrentMousePoint; - - // cleanup unused selections when no modal dialogs are open - if (LLModalDialog::activeCount() == 0) - { - LLViewerParcelMgr::getInstance()->deselectUnused(); - } - - if (LLModalDialog::activeCount() == 0) - { - LLSelectMgr::getInstance()->deselectUnused(); - } -} - - -void LLViewerWindow::updateLayout() -{ - LLTool* tool = LLToolMgr::getInstance()->getCurrentTool(); - if (gFloaterTools != NULL - && tool != NULL - && tool != gToolNull - && tool != LLToolCompInspect::getInstance() - && tool != LLToolDragAndDrop::getInstance() - && !gSavedSettings.getBOOL("FreezeTime")) - { - // Suppress the toolbox view if our source tool was the pie tool, - // and we've overridden to something else. - bool suppress_toolbox = - (LLToolMgr::getInstance()->getBaseTool() == LLToolPie::getInstance()) && - (LLToolMgr::getInstance()->getCurrentTool() != LLToolPie::getInstance()); - - LLMouseHandler *captor = gFocusMgr.getMouseCapture(); - // With the null, inspect, or drag and drop tool, don't muck - // with visibility. - - if (gFloaterTools->isMinimized() - || (tool != LLToolPie::getInstance() // not default tool - && tool != LLToolCompGun::getInstance() // not coming out of mouselook - && !suppress_toolbox // not override in third person - && LLToolMgr::getInstance()->getCurrentToolset()->isShowFloaterTools() - && (!captor || dynamic_cast(captor) != NULL))) // not dragging - { - // Force floater tools to be visible (unless minimized) - if (!gFloaterTools->getVisible()) - { - gFloaterTools->openFloater(); - } - // Update the location of the blue box tool popup - LLCoordGL select_center_screen; - MASK mask = gKeyboard->currentMask(true); - gFloaterTools->updatePopup( select_center_screen, mask ); - } - else - { - gFloaterTools->setVisible(false); - } - //gMenuBarView->setItemVisible("BuildTools", gFloaterTools->getVisible()); - } - - // Always update console - if(gConsole) - { - LLRect console_rect = getChatConsoleRect(); - gConsole->reshape(console_rect.getWidth(), console_rect.getHeight()); - gConsole->setRect(console_rect); - } -} - -void LLViewerWindow::updateMouseDelta() -{ -#if LL_WINDOWS - LLCoordCommon delta; - mWindow->getCursorDelta(&delta); - S32 dx = delta.mX; - S32 dy = delta.mY; -#else - S32 dx = lltrunc((F32) (mCurrentMousePoint.mX - mLastMousePoint.mX) * LLUI::getScaleFactor().mV[VX]); - S32 dy = lltrunc((F32) (mCurrentMousePoint.mY - mLastMousePoint.mY) * LLUI::getScaleFactor().mV[VY]); -#endif - - //RN: fix for asynchronous notification of mouse leaving window not working - LLCoordWindow mouse_pos; - mWindow->getCursorPosition(&mouse_pos); - if (mouse_pos.mX < 0 || - mouse_pos.mY < 0 || - mouse_pos.mX > mWindowRectRaw.getWidth() || - mouse_pos.mY > mWindowRectRaw.getHeight()) - { - mMouseInWindow = false; - } - else - { - mMouseInWindow = true; - } - - LLVector2 mouse_vel; - - if (gSavedSettings.getBOOL("MouseSmooth")) - { - static F32 fdx = 0.f; - static F32 fdy = 0.f; - - F32 amount = 16.f; - fdx = fdx + ((F32) dx - fdx) * llmin(gFrameIntervalSeconds.value()*amount,1.f); - fdy = fdy + ((F32) dy - fdy) * llmin(gFrameIntervalSeconds.value()*amount,1.f); - - mCurrentMouseDelta.set(ll_round(fdx), ll_round(fdy)); - mouse_vel.setVec(fdx,fdy); - } - else - { - mCurrentMouseDelta.set(dx, dy); - mouse_vel.setVec((F32) dx, (F32) dy); - } - - sample(sMouseVelocityStat, mouse_vel.magVec()); -} - -void LLViewerWindow::updateKeyboardFocus() -{ - if (!gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) - { - gFocusMgr.setKeyboardFocus(NULL); - } - - // clean up current focus - LLUICtrl* cur_focus = dynamic_cast(gFocusMgr.getKeyboardFocus()); - if (cur_focus) - { - if (!cur_focus->isInVisibleChain() || !cur_focus->isInEnabledChain()) - { - // don't release focus, just reassign so that if being given - // to a sibling won't call onFocusLost on all the ancestors - // gFocusMgr.releaseFocusIfNeeded(cur_focus); - - LLUICtrl* parent = cur_focus->getParentUICtrl(); - const LLUICtrl* focus_root = cur_focus->findRootMostFocusRoot(); - bool new_focus_found = false; - while(parent) - { - if (parent->isCtrl() - && (parent->hasTabStop() || parent == focus_root) - && !parent->getIsChrome() - && parent->isInVisibleChain() - && parent->isInEnabledChain()) - { - if (!parent->focusFirstItem()) - { - parent->setFocus(true); - } - new_focus_found = true; - break; - } - parent = parent->getParentUICtrl(); - } - - // if we didn't find a better place to put focus, just release it - // hasFocus() will return true if and only if we didn't touch focus since we - // are only moving focus higher in the hierarchy - if (!new_focus_found) - { - cur_focus->setFocus(false); - } - } - else if (cur_focus->isFocusRoot()) - { - // focus roots keep trying to delegate focus to their first valid descendant - // this assumes that focus roots are not valid focus holders on their own - cur_focus->focusFirstItem(); - } - } - - // last ditch force of edit menu to selection manager - if (LLEditMenuHandler::gEditMenuHandler == NULL && LLSelectMgr::getInstance()->getSelection()->getObjectCount()) - { - LLEditMenuHandler::gEditMenuHandler = LLSelectMgr::getInstance(); - } - - if (gFloaterView->getCycleMode()) - { - // sync all floaters with their focus state - gFloaterView->highlightFocusedFloater(); - gSnapshotFloaterView->highlightFocusedFloater(); - MASK mask = gKeyboard->currentMask(true); - if ((mask & MASK_CONTROL) == 0) - { - // control key no longer held down, finish cycle mode - gFloaterView->setCycleMode(false); - - gFloaterView->syncFloaterTabOrder(); - } - else - { - // user holding down CTRL, don't update tab order of floaters - } - } - else - { - // update focused floater - gFloaterView->highlightFocusedFloater(); - gSnapshotFloaterView->highlightFocusedFloater(); - // make sure floater visible order is in sync with tab order - gFloaterView->syncFloaterTabOrder(); - } -} - -static LLTrace::BlockTimerStatHandle FTM_UPDATE_WORLD_VIEW("Update World View"); -void LLViewerWindow::updateWorldViewRect(bool use_full_window) -{ - LL_RECORD_BLOCK_TIME(FTM_UPDATE_WORLD_VIEW); - - // start off using whole window to render world - LLRect new_world_rect = mWindowRectRaw; - - if (!use_full_window && mWorldViewPlaceholder.get()) - { - new_world_rect = mWorldViewPlaceholder.get()->calcScreenRect(); - // clamp to at least a 1x1 rect so we don't try to allocate zero width gl buffers - new_world_rect.mTop = llmax(new_world_rect.mTop, new_world_rect.mBottom + 1); - new_world_rect.mRight = llmax(new_world_rect.mRight, new_world_rect.mLeft + 1); - - new_world_rect.mLeft = ll_round((F32)new_world_rect.mLeft * mDisplayScale.mV[VX]); - new_world_rect.mRight = ll_round((F32)new_world_rect.mRight * mDisplayScale.mV[VX]); - new_world_rect.mBottom = ll_round((F32)new_world_rect.mBottom * mDisplayScale.mV[VY]); - new_world_rect.mTop = ll_round((F32)new_world_rect.mTop * mDisplayScale.mV[VY]); - } - - if (mWorldViewRectRaw != new_world_rect) - { - mWorldViewRectRaw = new_world_rect; - gResizeScreenTexture = true; - LLViewerCamera::getInstance()->setViewHeightInPixels( mWorldViewRectRaw.getHeight() ); - LLViewerCamera::getInstance()->setAspect( getWorldViewAspectRatio() ); - - LLRect old_world_rect_scaled = mWorldViewRectScaled; - mWorldViewRectScaled = calcScaledRect(mWorldViewRectRaw, mDisplayScale); - - // sending a signal with a new WorldView rect - mOnWorldViewRectUpdated(old_world_rect_scaled, mWorldViewRectScaled); - } -} - -void LLViewerWindow::saveLastMouse(const LLCoordGL &point) -{ - // Store last mouse location. - // If mouse leaves window, pretend last point was on edge of window - - if (point.mX < 0) - { - mCurrentMousePoint.mX = 0; - } - else if (point.mX > getWindowWidthScaled()) - { - mCurrentMousePoint.mX = getWindowWidthScaled(); - } - else - { - mCurrentMousePoint.mX = point.mX; - } - - if (point.mY < 0) - { - mCurrentMousePoint.mY = 0; - } - else if (point.mY > getWindowHeightScaled() ) - { - mCurrentMousePoint.mY = getWindowHeightScaled(); - } - else - { - mCurrentMousePoint.mY = point.mY; - } -} - - -// Draws the selection outlines for the currently selected objects -// Must be called after displayObjects is called, which sets the mGLName parameter -// NOTE: This function gets called 3 times: -// render_ui_3d: false, false, true -// render_hud_elements: false, false, false -void LLViewerWindow::renderSelections( bool for_gl_pick, bool pick_parcel_walls, bool for_hud ) -{ - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - - if (!for_hud && !for_gl_pick) - { - // Call this once and only once - LLSelectMgr::getInstance()->updateSilhouettes(); - } - - // Draw fence around land selections - if (for_gl_pick) - { - if (pick_parcel_walls) - { - LLViewerParcelMgr::getInstance()->renderParcelCollision(); - } - } - else if (( for_hud && selection->getSelectType() == SELECT_TYPE_HUD) || - (!for_hud && selection->getSelectType() != SELECT_TYPE_HUD)) - { - LLSelectMgr::getInstance()->renderSilhouettes(for_hud); - - stop_glerror(); - - // setup HUD render - if (selection->getSelectType() == SELECT_TYPE_HUD && LLSelectMgr::getInstance()->getSelection()->getObjectCount()) - { - LLBBox hud_bbox = gAgentAvatarp->getHUDBBox(); - - // set up transform to encompass bounding box of HUD - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.pushMatrix(); - gGL.loadIdentity(); - F32 depth = llmax(1.f, hud_bbox.getExtentLocal().mV[VX] * 1.1f); - gGL.ortho(-0.5f * LLViewerCamera::getInstance()->getAspect(), 0.5f * LLViewerCamera::getInstance()->getAspect(), -0.5f, 0.5f, 0.f, depth); - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - gGL.loadIdentity(); - gGL.loadMatrix(OGL_TO_CFR_ROTATION); // Load Cory's favorite reference frame - gGL.translatef(-hud_bbox.getCenterLocal().mV[VX] + (depth *0.5f), 0.f, 0.f); - } - - // Render light for editing - if (LLSelectMgr::sRenderLightRadius && LLToolMgr::getInstance()->inEdit()) - { - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - LLGLEnable gls_blend(GL_BLEND); - LLGLEnable gls_cull(GL_CULL_FACE); - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - if (selection->getSelectType() == SELECT_TYPE_HUD) - { - F32 zoom = gAgentCamera.mHUDCurZoom; - gGL.scalef(zoom, zoom, zoom); - } - - struct f : public LLSelectedObjectFunctor - { - virtual bool apply(LLViewerObject* object) - { - LLDrawable* drawable = object->mDrawable; - if (drawable && drawable->isLight()) - { - LLVOVolume* vovolume = drawable->getVOVolume(); - gGL.pushMatrix(); - - LLVector3 center = drawable->getPositionAgent(); - gGL.translatef(center[0], center[1], center[2]); - F32 scale = vovolume->getLightRadius(); - gGL.scalef(scale, scale, scale); - - LLColor4 color(vovolume->getLightSRGBColor(), .5f); - gGL.color4fv(color.mV); - - //F32 pixel_area = 100000.f; - // Render Outside - gSphere.render(); - - // Render Inside - glCullFace(GL_FRONT); - gSphere.render(); - glCullFace(GL_BACK); - - gGL.popMatrix(); - } - return true; - } - } func; - LLSelectMgr::getInstance()->getSelection()->applyToObjects(&func); - - gGL.popMatrix(); - } - - // NOTE: The average position for the axis arrows of the selected objects should - // not be recalculated at this time. If they are, then group rotations will break. - - // Draw arrows at average center of all selected objects - LLTool* tool = LLToolMgr::getInstance()->getCurrentTool(); - if (tool) - { - if(tool->isAlwaysRendered()) - { - tool->render(); - } - else - { - if( !LLSelectMgr::getInstance()->getSelection()->isEmpty() ) - { - bool all_selected_objects_move; - bool all_selected_objects_modify; - // Note: This might be costly to do on each frame and when a lot of objects are selected - // we might be better off with some kind of memory for selection and/or states, consider - // optimizing, perhaps even some kind of selection generation at level of LLSelectMgr to - // make whole viewer benefit. - LLSelectMgr::getInstance()->selectGetEditMoveLinksetPermissions(all_selected_objects_move, all_selected_objects_modify); - - bool draw_handles = true; - - if (tool == LLToolCompTranslate::getInstance() && !all_selected_objects_move && !LLSelectMgr::getInstance()->isMovableAvatarSelected()) - { - draw_handles = false; - } - - if (tool == LLToolCompRotate::getInstance() && !all_selected_objects_move && !LLSelectMgr::getInstance()->isMovableAvatarSelected()) - { - draw_handles = false; - } - - if ( !all_selected_objects_modify && tool == LLToolCompScale::getInstance() ) - { - draw_handles = false; - } - - if( draw_handles ) - { - tool->render(); - } - } - } - if (selection->getSelectType() == SELECT_TYPE_HUD && selection->getObjectCount()) - { - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.popMatrix(); - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.popMatrix(); - stop_glerror(); - } - } - } -} - -// Return a point near the clicked object representative of the place the object was clicked. -LLVector3d LLViewerWindow::clickPointInWorldGlobal(S32 x, S32 y_from_bot, LLViewerObject* clicked_object) const -{ - // create a normalized vector pointing from the camera center into the - // world at the location of the mouse click - LLVector3 mouse_direction_global = mouseDirectionGlobal( x, y_from_bot ); - - LLVector3d relative_object = clicked_object->getPositionGlobal() - gAgentCamera.getCameraPositionGlobal(); - - // make mouse vector as long as object vector, so it touchs a point near - // where the user clicked on the object - mouse_direction_global *= (F32) relative_object.magVec(); - - LLVector3d new_pos; - new_pos.setVec(mouse_direction_global); - // transform mouse vector back to world coords - new_pos += gAgentCamera.getCameraPositionGlobal(); - - return new_pos; -} - - -bool LLViewerWindow::clickPointOnSurfaceGlobal(const S32 x, const S32 y, LLViewerObject *objectp, LLVector3d &point_global) const -{ - bool intersect = false; - -// U8 shape = objectp->mPrimitiveCode & LL_PCODE_BASE_MASK; - if (!intersect) - { - point_global = clickPointInWorldGlobal(x, y, objectp); - LL_INFOS() << "approx intersection at " << (objectp->getPositionGlobal() - point_global) << LL_ENDL; - } - else - { - LL_INFOS() << "good intersection at " << (objectp->getPositionGlobal() - point_global) << LL_ENDL; - } - - return intersect; -} - -void LLViewerWindow::pickAsync( S32 x, - S32 y_from_bot, - MASK mask, - void (*callback)(const LLPickInfo& info), - bool pick_transparent, - bool pick_rigged, - bool pick_unselectable, - bool pick_reflection_probes) -{ - // "Show Debug Alpha" means no object actually transparent - bool in_build_mode = LLFloaterReg::instanceVisible("build"); - if (LLDrawPoolAlpha::sShowDebugAlpha - || (in_build_mode && gSavedSettings.getBOOL("SelectInvisibleObjects"))) - { - pick_transparent = true; - } - - LLPickInfo pick_info(LLCoordGL(x, y_from_bot), mask, pick_transparent, pick_rigged, false, pick_reflection_probes, pick_unselectable, true, callback); - schedulePick(pick_info); -} - -void LLViewerWindow::schedulePick(LLPickInfo& pick_info) -{ - if (mPicks.size() >= 1024 || mWindow->getMinimized()) - { //something went wrong, picks are being scheduled but not processed - - if (pick_info.mPickCallback) - { - pick_info.mPickCallback(pick_info); - } - - return; - } - mPicks.push_back(pick_info); - - // delay further event processing until we receive results of pick - // only do this for async picks so that handleMouseUp won't be called - // until the pick triggered in handleMouseDown has been processed, for example - mWindow->delayInputProcessing(); -} - - -void LLViewerWindow::performPick() -{ - if (!mPicks.empty()) - { - std::vector::iterator pick_it; - for (pick_it = mPicks.begin(); pick_it != mPicks.end(); ++pick_it) - { - pick_it->fetchResults(); - } - - mLastPick = mPicks.back(); - mPicks.clear(); - } -} - -void LLViewerWindow::returnEmptyPicks() -{ - std::vector::iterator pick_it; - for (pick_it = mPicks.begin(); pick_it != mPicks.end(); ++pick_it) - { - mLastPick = *pick_it; - // just trigger callback with empty results - if (pick_it->mPickCallback) - { - pick_it->mPickCallback(*pick_it); - } - } - mPicks.clear(); -} - -// Performs the GL object/land pick. -LLPickInfo LLViewerWindow::pickImmediate(S32 x, S32 y_from_bot, bool pick_transparent, bool pick_rigged, bool pick_particle, bool pick_unselectable, bool pick_reflection_probe) -{ - bool in_build_mode = LLFloaterReg::instanceVisible("build"); - if ((in_build_mode && gSavedSettings.getBOOL("SelectInvisibleObjects")) || LLDrawPoolAlpha::sShowDebugAlpha) - { - // build mode allows interaction with all transparent objects - // "Show Debug Alpha" means no object actually transparent - pick_transparent = true; - } - - // shortcut queueing in mPicks and just update mLastPick in place - MASK key_mask = gKeyboard->currentMask(true); - mLastPick = LLPickInfo(LLCoordGL(x, y_from_bot), key_mask, pick_transparent, pick_rigged, pick_particle, pick_reflection_probe, true, false, NULL); - mLastPick.fetchResults(); - - return mLastPick; -} - -LLHUDIcon* LLViewerWindow::cursorIntersectIcon(S32 mouse_x, S32 mouse_y, F32 depth, - LLVector4a* intersection) -{ - S32 x = mouse_x; - S32 y = mouse_y; - - if ((mouse_x == -1) && (mouse_y == -1)) // use current mouse position - { - x = getCurrentMouseX(); - y = getCurrentMouseY(); - } - - // world coordinates of mouse - // VECTORIZE THIS - LLVector3 mouse_direction_global = mouseDirectionGlobal(x,y); - LLVector3 mouse_point_global = LLViewerCamera::getInstance()->getOrigin(); - LLVector3 mouse_world_start = mouse_point_global; - LLVector3 mouse_world_end = mouse_point_global + mouse_direction_global * depth; - - LLVector4a start, end; - start.load3(mouse_world_start.mV); - end.load3(mouse_world_end.mV); - - return LLHUDIcon::lineSegmentIntersectAll(start, end, intersection); -} - -LLViewerObject* LLViewerWindow::cursorIntersect(S32 mouse_x, S32 mouse_y, F32 depth, - LLViewerObject *this_object, - S32 this_face, - bool pick_transparent, - bool pick_rigged, - bool pick_unselectable, - bool pick_reflection_probe, - S32* face_hit, - LLVector4a *intersection, - LLVector2 *uv, - LLVector4a *normal, - LLVector4a *tangent, - LLVector4a* start, - LLVector4a* end) -{ - S32 x = mouse_x; - S32 y = mouse_y; - - if ((mouse_x == -1) && (mouse_y == -1)) // use current mouse position - { - x = getCurrentMouseX(); - y = getCurrentMouseY(); - } - - // HUD coordinates of mouse - LLVector3 mouse_point_hud = mousePointHUD(x, y); - LLVector3 mouse_hud_start = mouse_point_hud - LLVector3(depth, 0, 0); - LLVector3 mouse_hud_end = mouse_point_hud + LLVector3(depth, 0, 0); - - // world coordinates of mouse - LLVector3 mouse_direction_global = mouseDirectionGlobal(x,y); - LLVector3 mouse_point_global = LLViewerCamera::getInstance()->getOrigin(); - - //get near clip plane - LLVector3 n = LLViewerCamera::getInstance()->getAtAxis(); - LLVector3 p = mouse_point_global + n * LLViewerCamera::getInstance()->getNear(); - - //project mouse point onto plane - LLVector3 pos; - line_plane(mouse_point_global, mouse_direction_global, p, n, pos); - mouse_point_global = pos; - - LLVector3 mouse_world_start = mouse_point_global; - LLVector3 mouse_world_end = mouse_point_global + mouse_direction_global * depth; - - if (!LLViewerJoystick::getInstance()->getOverrideCamera()) - { //always set raycast intersection to mouse_world_end unless - //flycam is on (for DoF effect) - gDebugRaycastIntersection.load3(mouse_world_end.mV); - } - - LLVector4a mw_start; - mw_start.load3(mouse_world_start.mV); - LLVector4a mw_end; - mw_end.load3(mouse_world_end.mV); - - LLVector4a mh_start; - mh_start.load3(mouse_hud_start.mV); - LLVector4a mh_end; - mh_end.load3(mouse_hud_end.mV); - - if (start) - { - *start = mw_start; - } - - if (end) - { - *end = mw_end; - } - - LLViewerObject* found = NULL; - - if (this_object) // check only this object - { - if (this_object->isHUDAttachment()) // is a HUD object? - { - if (this_object->lineSegmentIntersect(mh_start, mh_end, this_face, pick_transparent, pick_rigged, pick_unselectable, - face_hit, intersection, uv, normal, tangent)) - { - found = this_object; - } - } - else // is a world object - { - if ((pick_reflection_probe || !this_object->isReflectionProbe()) - && this_object->lineSegmentIntersect(mw_start, mw_end, this_face, pick_transparent, pick_rigged, pick_unselectable, - face_hit, intersection, uv, normal, tangent)) - { - found = this_object; - } - } - } - else // check ALL objects - { - found = gPipeline.lineSegmentIntersectInHUD(mh_start, mh_end, pick_transparent, - face_hit, intersection, uv, normal, tangent); - - if (!found) // if not found in HUD, look in world: - { - found = gPipeline.lineSegmentIntersectInWorld(mw_start, mw_end, pick_transparent, pick_rigged, pick_unselectable, pick_reflection_probe, - face_hit, intersection, uv, normal, tangent); - if (found && !pick_transparent) - { - gDebugRaycastIntersection = *intersection; - } - } - } - - return found; -} - -// Returns unit vector relative to camera -// indicating direction of point on screen x,y -LLVector3 LLViewerWindow::mouseDirectionGlobal(const S32 x, const S32 y) const -{ - // find vertical field of view - F32 fov = LLViewerCamera::getInstance()->getView(); - - // find world view center in scaled ui coordinates - F32 center_x = getWorldViewRectScaled().getCenterX(); - F32 center_y = getWorldViewRectScaled().getCenterY(); - - // calculate pixel distance to screen - F32 distance = ((F32)getWorldViewHeightScaled() * 0.5f) / (tan(fov / 2.f)); - - // calculate click point relative to middle of screen - F32 click_x = x - center_x; - F32 click_y = y - center_y; - - // compute mouse vector - LLVector3 mouse_vector = distance * LLViewerCamera::getInstance()->getAtAxis() - - click_x * LLViewerCamera::getInstance()->getLeftAxis() - + click_y * LLViewerCamera::getInstance()->getUpAxis(); - - mouse_vector.normVec(); - - return mouse_vector; -} - -LLVector3 LLViewerWindow::mousePointHUD(const S32 x, const S32 y) const -{ - // find screen resolution - S32 height = getWorldViewHeightScaled(); - - // find world view center - F32 center_x = getWorldViewRectScaled().getCenterX(); - F32 center_y = getWorldViewRectScaled().getCenterY(); - - // remap with uniform scale (1/height) so that top is -0.5, bottom is +0.5 - F32 hud_x = -((F32)x - center_x) / height; - F32 hud_y = ((F32)y - center_y) / height; - - return LLVector3(0.f, hud_x/gAgentCamera.mHUDCurZoom, hud_y/gAgentCamera.mHUDCurZoom); -} - -// Returns unit vector relative to camera in camera space -// indicating direction of point on screen x,y -LLVector3 LLViewerWindow::mouseDirectionCamera(const S32 x, const S32 y) const -{ - // find vertical field of view - F32 fov_height = LLViewerCamera::getInstance()->getView(); - F32 fov_width = fov_height * LLViewerCamera::getInstance()->getAspect(); - - // find screen resolution - S32 height = getWorldViewHeightScaled(); - S32 width = getWorldViewWidthScaled(); - - // find world view center - F32 center_x = getWorldViewRectScaled().getCenterX(); - F32 center_y = getWorldViewRectScaled().getCenterY(); - - // calculate click point relative to middle of screen - F32 click_x = (((F32)x - center_x) / (F32)width) * fov_width * -1.f; - F32 click_y = (((F32)y - center_y) / (F32)height) * fov_height; - - // compute mouse vector - LLVector3 mouse_vector = LLVector3(0.f, 0.f, -1.f); - LLQuaternion mouse_rotate; - mouse_rotate.setQuat(click_y, click_x, 0.f); - - mouse_vector = mouse_vector * mouse_rotate; - // project to z = -1 plane; - mouse_vector = mouse_vector * (-1.f / mouse_vector.mV[VZ]); - - return mouse_vector; -} - - - -bool LLViewerWindow::mousePointOnPlaneGlobal(LLVector3d& point, const S32 x, const S32 y, - const LLVector3d &plane_point_global, - const LLVector3 &plane_normal_global) -{ - LLVector3d mouse_direction_global_d; - - mouse_direction_global_d.setVec(mouseDirectionGlobal(x,y)); - LLVector3d plane_normal_global_d; - plane_normal_global_d.setVec(plane_normal_global); - F64 plane_mouse_dot = (plane_normal_global_d * mouse_direction_global_d); - LLVector3d plane_origin_camera_rel = plane_point_global - gAgentCamera.getCameraPositionGlobal(); - F64 mouse_look_at_scale = (plane_normal_global_d * plane_origin_camera_rel) - / plane_mouse_dot; - if (llabs(plane_mouse_dot) < 0.00001) - { - // if mouse is parallel to plane, return closest point on line through plane origin - // that is parallel to camera plane by scaling mouse direction vector - // by distance to plane origin, modulated by deviation of mouse direction from plane origin - LLVector3d plane_origin_dir = plane_origin_camera_rel; - plane_origin_dir.normVec(); - - mouse_look_at_scale = plane_origin_camera_rel.magVec() / (plane_origin_dir * mouse_direction_global_d); - } - - point = gAgentCamera.getCameraPositionGlobal() + mouse_look_at_scale * mouse_direction_global_d; - - return mouse_look_at_scale > 0.0; -} - - -// Returns global position -bool LLViewerWindow::mousePointOnLandGlobal(const S32 x, const S32 y, LLVector3d *land_position_global, bool ignore_distance) -{ - LLVector3 mouse_direction_global = mouseDirectionGlobal(x,y); - F32 mouse_dir_scale; - bool hit_land = false; - LLViewerRegion *regionp; - F32 land_z; - const F32 FIRST_PASS_STEP = 1.0f; // meters - const F32 SECOND_PASS_STEP = 0.1f; // meters - const F32 draw_distance = ignore_distance ? MAX_FAR_CLIP : gAgentCamera.mDrawDistance; - LLVector3d camera_pos_global; - - camera_pos_global = gAgentCamera.getCameraPositionGlobal(); - LLVector3d probe_point_global; - LLVector3 probe_point_region; - - // walk forwards to find the point - for (mouse_dir_scale = FIRST_PASS_STEP; mouse_dir_scale < draw_distance; mouse_dir_scale += FIRST_PASS_STEP) - { - LLVector3d mouse_direction_global_d; - mouse_direction_global_d.setVec(mouse_direction_global * mouse_dir_scale); - probe_point_global = camera_pos_global + mouse_direction_global_d; - - regionp = LLWorld::getInstance()->resolveRegionGlobal(probe_point_region, probe_point_global); - - if (!regionp) - { - // ...we're outside the world somehow - continue; - } - - S32 i = (S32) (probe_point_region.mV[VX]/regionp->getLand().getMetersPerGrid()); - S32 j = (S32) (probe_point_region.mV[VY]/regionp->getLand().getMetersPerGrid()); - S32 grids_per_edge = (S32) regionp->getLand().mGridsPerEdge; - if ((i >= grids_per_edge) || (j >= grids_per_edge)) - { - //LL_INFOS() << "LLViewerWindow::mousePointOnLand probe_point is out of region" << LL_ENDL; - continue; - } - - land_z = regionp->getLand().resolveHeightRegion(probe_point_region); - - //LL_INFOS() << "mousePointOnLand initial z " << land_z << LL_ENDL; - - if (probe_point_region.mV[VZ] < land_z) - { - // ...just went under land - - // cout << "under land at " << probe_point << " scale " << mouse_vec_scale << endl; - - hit_land = true; - break; - } - } - - - if (hit_land) - { - // Don't go more than one step beyond where we stopped above. - // This can't just be "mouse_vec_scale" because floating point error - // will stop the loop before the last increment.... X - 1.0 + 0.1 + 0.1 + ... + 0.1 != X - F32 stop_mouse_dir_scale = mouse_dir_scale + FIRST_PASS_STEP; - - // take a step backwards, then walk forwards again to refine position - for ( mouse_dir_scale -= FIRST_PASS_STEP; mouse_dir_scale <= stop_mouse_dir_scale; mouse_dir_scale += SECOND_PASS_STEP) - { - LLVector3d mouse_direction_global_d; - mouse_direction_global_d.setVec(mouse_direction_global * mouse_dir_scale); - probe_point_global = camera_pos_global + mouse_direction_global_d; - - regionp = LLWorld::getInstance()->resolveRegionGlobal(probe_point_region, probe_point_global); - - if (!regionp) - { - // ...we're outside the world somehow - continue; - } - - /* - i = (S32) (local_probe_point.mV[VX]/regionp->getLand().getMetersPerGrid()); - j = (S32) (local_probe_point.mV[VY]/regionp->getLand().getMetersPerGrid()); - if ((i >= regionp->getLand().mGridsPerEdge) || (j >= regionp->getLand().mGridsPerEdge)) - { - // LL_INFOS() << "LLViewerWindow::mousePointOnLand probe_point is out of region" << LL_ENDL; - continue; - } - land_z = regionp->getLand().mSurfaceZ[ i + j * (regionp->getLand().mGridsPerEdge) ]; - */ - - land_z = regionp->getLand().resolveHeightRegion(probe_point_region); - - //LL_INFOS() << "mousePointOnLand refine z " << land_z << LL_ENDL; - - if (probe_point_region.mV[VZ] < land_z) - { - // ...just went under land again - - *land_position_global = probe_point_global; - return true; - } - } - } - - return false; -} - -// Saves an image to the harddrive as "SnapshotX" where X >= 1. -void LLViewerWindow::saveImageNumbered(LLImageFormatted *image, bool force_picker, const snapshot_saved_signal_t::slot_type& success_cb, const snapshot_saved_signal_t::slot_type& failure_cb) -{ - if (!image) - { - LL_WARNS() << "No image to save" << LL_ENDL; - return; - } - std::string extension("." + image->getExtension()); - LLImageFormatted* formatted_image = image; - // Get a base file location if needed. - if (force_picker || !isSnapshotLocSet()) - { - std::string proposed_name(sSnapshotBaseName); - - // getSaveFile will append an appropriate extension to the proposed name, based on the ESaveFilter constant passed in. - LLFilePicker::ESaveFilter pick_type; - - if (extension == ".j2c") - pick_type = LLFilePicker::FFSAVE_J2C; - else if (extension == ".bmp") - pick_type = LLFilePicker::FFSAVE_BMP; - else if (extension == ".jpg") - pick_type = LLFilePicker::FFSAVE_JPEG; - else if (extension == ".png") - pick_type = LLFilePicker::FFSAVE_PNG; - else if (extension == ".tga") - pick_type = LLFilePicker::FFSAVE_TGA; - else - pick_type = LLFilePicker::FFSAVE_ALL; - - LLFilePickerReplyThread::startPicker(boost::bind(&LLViewerWindow::onDirectorySelected, this, _1, formatted_image, success_cb, failure_cb), pick_type, proposed_name, - boost::bind(&LLViewerWindow::onSelectionFailure, this, failure_cb)); - } - else - { - saveImageLocal(formatted_image, success_cb, failure_cb); - } -} - -void LLViewerWindow::onDirectorySelected(const std::vector& filenames, LLImageFormatted *image, const snapshot_saved_signal_t::slot_type& success_cb, const snapshot_saved_signal_t::slot_type& failure_cb) -{ - // Copy the directory + file name - std::string filepath = filenames[0]; - - gSavedPerAccountSettings.setString("SnapshotBaseName", gDirUtilp->getBaseFileName(filepath, true)); - gSavedPerAccountSettings.setString("SnapshotBaseDir", gDirUtilp->getDirName(filepath)); - saveImageLocal(image, success_cb, failure_cb); -} - -void LLViewerWindow::onSelectionFailure(const snapshot_saved_signal_t::slot_type& failure_cb) -{ - failure_cb(); -} - - -void LLViewerWindow::saveImageLocal(LLImageFormatted *image, const snapshot_saved_signal_t::slot_type& success_cb, const snapshot_saved_signal_t::slot_type& failure_cb) -{ - std::string lastSnapshotDir = LLViewerWindow::getLastSnapshotDir(); - if (lastSnapshotDir.empty()) - { - failure_cb(); - return; - } - -// Check if there is enough free space to save snapshot -#ifdef LL_WINDOWS - boost::filesystem::path b_path(utf8str_to_utf16str(lastSnapshotDir)); -#else - boost::filesystem::path b_path(lastSnapshotDir); -#endif - if (!boost::filesystem::is_directory(b_path)) - { - LLSD args; - args["PATH"] = lastSnapshotDir; - LLNotificationsUtil::add("SnapshotToLocalDirNotExist", args); - resetSnapshotLoc(); - failure_cb(); - return; - } - boost::filesystem::space_info b_space = boost::filesystem::space(b_path); - if (b_space.free < image->getDataSize()) - { - LLSD args; - args["PATH"] = lastSnapshotDir; - - std::string needM_bytes_string; - LLResMgr::getInstance()->getIntegerString(needM_bytes_string, (image->getDataSize()) >> 10); - args["NEED_MEMORY"] = needM_bytes_string; - - std::string freeM_bytes_string; - LLResMgr::getInstance()->getIntegerString(freeM_bytes_string, (b_space.free) >> 10); - args["FREE_MEMORY"] = freeM_bytes_string; - - LLNotificationsUtil::add("SnapshotToComputerFailed", args); - - failure_cb(); - } - - // Look for an unused file name - bool is_snapshot_name_loc_set = isSnapshotLocSet(); - std::string filepath; - S32 i = 1; - S32 err = 0; - std::string extension("." + image->getExtension()); - do - { - filepath = sSnapshotDir; - filepath += gDirUtilp->getDirDelimiter(); - filepath += sSnapshotBaseName; - - if (is_snapshot_name_loc_set) - { - filepath += llformat("_%.3d",i); - } - - filepath += extension; - - llstat stat_info; - err = LLFile::stat( filepath, &stat_info ); - i++; - } - while( -1 != err // Search until the file is not found (i.e., stat() gives an error). - && is_snapshot_name_loc_set); // Or stop if we are rewriting. - - LL_INFOS() << "Saving snapshot to " << filepath << LL_ENDL; - if (image->save(filepath)) - { - playSnapshotAnimAndSound(); - success_cb(); - } - else - { - failure_cb(); - } -} - -void LLViewerWindow::resetSnapshotLoc() -{ - gSavedPerAccountSettings.setString("SnapshotBaseDir", std::string()); -} - -// static -void LLViewerWindow::movieSize(S32 new_width, S32 new_height) -{ - LLCoordWindow size; - LLCoordWindow new_size(new_width, new_height); - gViewerWindow->getWindow()->getSize(&size); - if ( size != new_size ) - { - gViewerWindow->getWindow()->setSize(new_size); - } -} - -bool LLViewerWindow::saveSnapshot(const std::string& filepath, S32 image_width, S32 image_height, bool show_ui, bool show_hud, bool do_rebuild, LLSnapshotModel::ESnapshotLayerType type, LLSnapshotModel::ESnapshotFormat format) -{ - LL_INFOS() << "Saving snapshot to: " << filepath << LL_ENDL; - - LLPointer raw = new LLImageRaw; - bool success = rawSnapshot(raw, image_width, image_height, true, false, show_ui, show_hud, do_rebuild); - - if (success) - { - U8 image_codec = IMG_CODEC_BMP; - switch (format) - { - case LLSnapshotModel::SNAPSHOT_FORMAT_PNG: - image_codec = IMG_CODEC_PNG; - break; - case LLSnapshotModel::SNAPSHOT_FORMAT_JPEG: - image_codec = IMG_CODEC_JPEG; - break; - default: - image_codec = IMG_CODEC_BMP; - break; - } - - LLPointer formated_image = LLImageFormatted::createFromType(image_codec); - success = formated_image->encode(raw, 0.0f); - if (success) - { - success = formated_image->save(filepath); - } - else - { - LL_WARNS() << "Unable to encode snapshot of format " << format << LL_ENDL; - } - } - else - { - LL_WARNS() << "Unable to capture raw snapshot" << LL_ENDL; - } - - return success; -} - - -void LLViewerWindow::playSnapshotAnimAndSound() -{ - if (gSavedSettings.getBOOL("QuietSnapshotsToDisk")) - { - return; - } - gAgent.sendAnimationRequest(ANIM_AGENT_SNAPSHOT, ANIM_REQUEST_START); - send_sound_trigger(LLUUID(gSavedSettings.getString("UISndSnapshot")), 1.0f); -} - -bool LLViewerWindow::isSnapshotLocSet() const -{ - std::string snapshot_dir = sSnapshotDir; - return !snapshot_dir.empty(); -} - -void LLViewerWindow::resetSnapshotLoc() const -{ - gSavedPerAccountSettings.setString("SnapshotBaseDir", std::string()); -} - -bool LLViewerWindow::thumbnailSnapshot(LLImageRaw *raw, S32 preview_width, S32 preview_height, bool show_ui, bool show_hud, bool do_rebuild, bool no_post, LLSnapshotModel::ESnapshotLayerType type) -{ - return rawSnapshot(raw, preview_width, preview_height, false, false, show_ui, show_hud, do_rebuild, no_post, type); -} - -// Saves the image from the screen to a raw image -// Since the required size might be bigger than the available screen, this method rerenders the scene in parts (called subimages) and copy -// the results over to the final raw image. -bool LLViewerWindow::rawSnapshot(LLImageRaw *raw, S32 image_width, S32 image_height, - bool keep_window_aspect, bool is_texture, bool show_ui, bool show_hud, bool do_rebuild, bool no_post, LLSnapshotModel::ESnapshotLayerType type, S32 max_size) -{ - if (!raw) - { - return false; - } - - //check if there is enough memory for the snapshot image - if(image_width * image_height > (1 << 22)) //if snapshot image is larger than 2K by 2K - { - if(!LLMemory::tryToAlloc(NULL, image_width * image_height * 3)) - { - LL_WARNS() << "No enough memory to take the snapshot with size (w : h): " << image_width << " : " << image_height << LL_ENDL ; - return false ; //there is no enough memory for taking this snapshot. - } - } - - // PRE SNAPSHOT - gSnapshotNoPost = no_post; - gDisplaySwapBuffers = false; - - glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); // stencil buffer is deprecated | GL_STENCIL_BUFFER_BIT); - setCursor(UI_CURSOR_WAIT); - - // Hide all the UI widgets first and draw a frame - bool prev_draw_ui = gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI); - - if ( prev_draw_ui != show_ui) - { - LLPipeline::toggleRenderDebugFeature(LLPipeline::RENDER_DEBUG_FEATURE_UI); - } - - bool hide_hud = !show_hud && LLPipeline::sShowHUDAttachments; - if (hide_hud) - { - LLPipeline::sShowHUDAttachments = false; - } - - // if not showing ui, use full window to render world view - updateWorldViewRect(!show_ui); - - // Copy screen to a buffer - // crop sides or top and bottom, if taking a snapshot of different aspect ratio - // from window - LLRect window_rect = show_ui ? getWindowRectRaw() : getWorldViewRectRaw(); - - S32 snapshot_width = window_rect.getWidth(); - S32 snapshot_height = window_rect.getHeight(); - // SNAPSHOT - S32 window_width = snapshot_width; - S32 window_height = snapshot_height; - - // Note: Scaling of the UI is currently *not* supported so we limit the output size if UI is requested - if (show_ui) - { - // If the user wants the UI, limit the output size to the available screen size - image_width = llmin(image_width, window_width); - image_height = llmin(image_height, window_height); - } - - S32 original_width = 0; - S32 original_height = 0; - bool reset_deferred = false; - - LLRenderTarget scratch_space; - - F32 scale_factor = 1.0f ; - if (!keep_window_aspect || (image_width > window_width) || (image_height > window_height)) - { - if ((image_width <= gGLManager.mGLMaxTextureSize && image_height <= gGLManager.mGLMaxTextureSize) && - (image_width > window_width || image_height > window_height) && LLPipeline::sRenderDeferred && !show_ui) - { - U32 color_fmt = type == LLSnapshotModel::SNAPSHOT_TYPE_DEPTH ? GL_DEPTH_COMPONENT : GL_RGBA; - if (scratch_space.allocate(image_width, image_height, color_fmt, true)) - { - original_width = gPipeline.mRT->deferredScreen.getWidth(); - original_height = gPipeline.mRT->deferredScreen.getHeight(); - - if (gPipeline.allocateScreenBuffer(image_width, image_height)) - { - window_width = image_width; - window_height = image_height; - snapshot_width = image_width; - snapshot_height = image_height; - reset_deferred = true; - mWorldViewRectRaw.set(0, image_height, image_width, 0); - LLViewerCamera::getInstance()->setViewHeightInPixels( mWorldViewRectRaw.getHeight() ); - LLViewerCamera::getInstance()->setAspect( getWorldViewAspectRatio() ); - scratch_space.bindTarget(); - } - else - { - scratch_space.release(); - gPipeline.allocateScreenBuffer(original_width, original_height); - } - } - } - - if (!reset_deferred) - { - // if image cropping or need to enlarge the scene, compute a scale_factor - F32 ratio = llmin( (F32)window_width / image_width , (F32)window_height / image_height) ; - snapshot_width = (S32)(ratio * image_width) ; - snapshot_height = (S32)(ratio * image_height) ; - scale_factor = llmax(1.0f, 1.0f / ratio) ; - } - } - - if (show_ui && scale_factor > 1.f) - { - // Note: we should never get there... - LL_WARNS() << "over scaling UI not supported." << LL_ENDL; - } - - S32 buffer_x_offset = llfloor(((window_width - snapshot_width) * scale_factor) / 2.f); - S32 buffer_y_offset = llfloor(((window_height - snapshot_height) * scale_factor) / 2.f); - - S32 image_buffer_x = llfloor(snapshot_width * scale_factor) ; - S32 image_buffer_y = llfloor(snapshot_height * scale_factor) ; - - if ((image_buffer_x > max_size) || (image_buffer_y > max_size)) // boundary check to avoid memory overflow - { - scale_factor *= llmin((F32)max_size / image_buffer_x, (F32)max_size / image_buffer_y) ; - image_buffer_x = llfloor(snapshot_width * scale_factor) ; - image_buffer_y = llfloor(snapshot_height * scale_factor) ; - } - - LLImageDataLock lock(raw); - - if ((image_buffer_x > 0) && (image_buffer_y > 0)) - { - raw->resize(image_buffer_x, image_buffer_y, 3); - } - else - { - return false; - } - - if (raw->isBufferInvalid()) - { - return false; - } - - bool high_res = scale_factor >= 2.f; // Font scaling is slow, only do so if rez is much higher - if (high_res && show_ui) - { - // Note: we should never get there... - LL_WARNS() << "High res UI snapshot not supported. " << LL_ENDL; - /*send_agent_pause(); - //rescale fonts - initFonts(scale_factor); - LLHUDObject::reshapeAll();*/ - } - - S32 output_buffer_offset_y = 0; - - F32 depth_conversion_factor_1 = (LLViewerCamera::getInstance()->getFar() + LLViewerCamera::getInstance()->getNear()) / (2.f * LLViewerCamera::getInstance()->getFar() * LLViewerCamera::getInstance()->getNear()); - F32 depth_conversion_factor_2 = (LLViewerCamera::getInstance()->getFar() - LLViewerCamera::getInstance()->getNear()) / (2.f * LLViewerCamera::getInstance()->getFar() * LLViewerCamera::getInstance()->getNear()); - - // Subimages are in fact partial rendering of the final view. This happens when the final view is bigger than the screen. - // In most common cases, scale_factor is 1 and there's no more than 1 iteration on x and y - for (int subimage_y = 0; subimage_y < scale_factor; ++subimage_y) - { - S32 subimage_y_offset = llclamp(buffer_y_offset - (subimage_y * window_height), 0, window_height);; - // handle fractional columns - U32 read_height = llmax(0, (window_height - subimage_y_offset) - - llmax(0, (window_height * (subimage_y + 1)) - (buffer_y_offset + raw->getHeight()))); - - S32 output_buffer_offset_x = 0; - for (int subimage_x = 0; subimage_x < scale_factor; ++subimage_x) - { - gDisplaySwapBuffers = false; - gDepthDirty = true; - - S32 subimage_x_offset = llclamp(buffer_x_offset - (subimage_x * window_width), 0, window_width); - // handle fractional rows - U32 read_width = llmax(0, (window_width - subimage_x_offset) - - llmax(0, (window_width * (subimage_x + 1)) - (buffer_x_offset + raw->getWidth()))); - - // Skip rendering and sampling altogether if either width or height is degenerated to 0 (common in cropping cases) - if (read_width && read_height) - { - const U32 subfield = subimage_x+(subimage_y*llceil(scale_factor)); - display(do_rebuild, scale_factor, subfield, true); - - if (!LLPipeline::sRenderDeferred) - { - // Required for showing the GUI in snapshots and performing bloom composite overlay - // Call even if show_ui is false - render_ui(scale_factor, subfield); - swap(); - } - - for (U32 out_y = 0; out_y < read_height ; out_y++) - { - S32 output_buffer_offset = ( - (out_y * (raw->getWidth())) // ...plus iterated y... - + (window_width * subimage_x) // ...plus subimage start in x... - + (raw->getWidth() * window_height * subimage_y) // ...plus subimage start in y... - - output_buffer_offset_x // ...minus buffer padding x... - - (output_buffer_offset_y * (raw->getWidth())) // ...minus buffer padding y... - ) * raw->getComponents(); - - // Ping the watchdog thread every 100 lines to keep us alive (arbitrary number, feel free to change) - if (out_y % 100 == 0) - { - LLAppViewer::instance()->pingMainloopTimeout("LLViewerWindow::rawSnapshot"); - } - // disable use of glReadPixels when doing nVidia nSight graphics debugging - if (!LLRender::sNsightDebugSupport) - { - if (type == LLSnapshotModel::SNAPSHOT_TYPE_COLOR) - { - glReadPixels( - subimage_x_offset, out_y + subimage_y_offset, - read_width, 1, - GL_RGB, GL_UNSIGNED_BYTE, - raw->getData() + output_buffer_offset - ); - } - else // LLSnapshotModel::SNAPSHOT_TYPE_DEPTH - { - LLPointer depth_line_buffer = new LLImageRaw(read_width, 1, sizeof(GL_FLOAT)); // need to store floating point values - glReadPixels( - subimage_x_offset, out_y + subimage_y_offset, - read_width, 1, - GL_DEPTH_COMPONENT, GL_FLOAT, - depth_line_buffer->getData()// current output pixel is beginning of buffer... - ); - - for (S32 i = 0; i < (S32)read_width; i++) - { - F32 depth_float = *(F32*)(depth_line_buffer->getData() + (i * sizeof(F32))); - - F32 linear_depth_float = 1.f / (depth_conversion_factor_1 - (depth_float * depth_conversion_factor_2)); - U8 depth_byte = F32_to_U8(linear_depth_float, LLViewerCamera::getInstance()->getNear(), LLViewerCamera::getInstance()->getFar()); - // write converted scanline out to result image - for (S32 j = 0; j < raw->getComponents(); j++) - { - *(raw->getData() + output_buffer_offset + (i * raw->getComponents()) + j) = depth_byte; - } - } - } - } - } - } - output_buffer_offset_x += subimage_x_offset; - stop_glerror(); - } - output_buffer_offset_y += subimage_y_offset; - } - - gDisplaySwapBuffers = false; - gSnapshotNoPost = false; - gDepthDirty = true; - - // POST SNAPSHOT - if (!gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) - { - LLPipeline::toggleRenderDebugFeature(LLPipeline::RENDER_DEBUG_FEATURE_UI); - } - - if (hide_hud) - { - LLPipeline::sShowHUDAttachments = true; - } - - /*if (high_res) - { - initFonts(1.f); - LLHUDObject::reshapeAll(); - }*/ - - // Pre-pad image to number of pixels such that the line length is a multiple of 4 bytes (for BMP encoding) - // Note: this formula depends on the number of components being 3. Not obvious, but it's correct. - image_width += (image_width * 3) % 4; - - bool ret = true ; - // Resize image - if(llabs(image_width - image_buffer_x) > 4 || llabs(image_height - image_buffer_y) > 4) - { - ret = raw->scale( image_width, image_height ); - } - else if(image_width != image_buffer_x || image_height != image_buffer_y) - { - ret = raw->scale( image_width, image_height, false ); - } - - setCursor(UI_CURSOR_ARROW); - - if (do_rebuild) - { - // If we had to do a rebuild, that means that the lists of drawables to be rendered - // was empty before we started. - // Need to reset these, otherwise we call state sort on it again when render gets called the next time - // and we stand a good chance of crashing on rebuild because the render drawable arrays have multiple copies of - // objects on them. - gPipeline.resetDrawOrders(); - } - - if (reset_deferred) - { - mWorldViewRectRaw = window_rect; - LLViewerCamera::getInstance()->setViewHeightInPixels( mWorldViewRectRaw.getHeight() ); - LLViewerCamera::getInstance()->setAspect( getWorldViewAspectRatio() ); - scratch_space.flush(); - scratch_space.release(); - gPipeline.allocateScreenBuffer(original_width, original_height); - - } - - if (high_res) - { - send_agent_resume(); - } - - return ret; -} - -bool LLViewerWindow::simpleSnapshot(LLImageRaw* raw, S32 image_width, S32 image_height, const int num_render_passes) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_APP; - gDisplaySwapBuffers = false; - - glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); // stencil buffer is deprecated | GL_STENCIL_BUFFER_BIT); - setCursor(UI_CURSOR_WAIT); - - bool prev_draw_ui = gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI); - if (prev_draw_ui) - { - LLPipeline::toggleRenderDebugFeature(LLPipeline::RENDER_DEBUG_FEATURE_UI); - } - - bool hide_hud = LLPipeline::sShowHUDAttachments; - if (hide_hud) - { - LLPipeline::sShowHUDAttachments = false; - } - - LLRect window_rect = getWorldViewRectRaw(); - - S32 original_width = LLPipeline::sRenderDeferred ? gPipeline.mRT->deferredScreen.getWidth() : gViewerWindow->getWorldViewWidthRaw(); - S32 original_height = LLPipeline::sRenderDeferred ? gPipeline.mRT->deferredScreen.getHeight() : gViewerWindow->getWorldViewHeightRaw(); - - LLRenderTarget scratch_space; - U32 color_fmt = GL_RGBA; - if (scratch_space.allocate(image_width, image_height, color_fmt, true)) - { - if (gPipeline.allocateScreenBuffer(image_width, image_height)) - { - mWorldViewRectRaw.set(0, image_height, image_width, 0); - - scratch_space.bindTarget(); - } - else - { - scratch_space.release(); - gPipeline.allocateScreenBuffer(original_width, original_height); - } - } - - // we render the scene more than once since this helps - // greatly with the objects not being drawn in the - // snapshot when they are drawn in the scene. This is - // evident when you set this value via the debug setting - // called 360CaptureNumRenderPasses to 1. The theory is - // that the missing objects are caused by the sUseOcclusion - // property in pipeline but that the use in pipeline.cpp - // lags by a frame or two so rendering more than once - // appears to help a lot. - for (int i = 0; i < num_render_passes; ++i) - { - // turning this flag off here prohibits the screen swap - // to present the new page to the viewer - this stops - // the black flash in between captures when the number - // of render passes is more than 1. We need to also - // set it here because code in LLViewerDisplay resets - // it to true each time. - gDisplaySwapBuffers = false; - - // actually render the scene - const U32 subfield = 0; - const bool do_rebuild = true; - const F32 zoom = 1.0; - const bool for_snapshot = true; - display(do_rebuild, zoom, subfield, for_snapshot); - } - - LLImageDataSharedLock lock(raw); - - glReadPixels( - 0, 0, - image_width, - image_height, - GL_RGB, GL_UNSIGNED_BYTE, - raw->getData() - ); - stop_glerror(); - - gDisplaySwapBuffers = false; - gDepthDirty = true; - - if (!gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) - { - if (prev_draw_ui) - { - LLPipeline::toggleRenderDebugFeature(LLPipeline::RENDER_DEBUG_FEATURE_UI); - } - } - - if (hide_hud) - { - LLPipeline::sShowHUDAttachments = true; - } - - setCursor(UI_CURSOR_ARROW); - - gPipeline.resetDrawOrders(); - mWorldViewRectRaw = window_rect; - scratch_space.flush(); - scratch_space.release(); - gPipeline.allocateScreenBuffer(original_width, original_height); - - return true; -} - -void display_cube_face(); - -bool LLViewerWindow::cubeSnapshot(const LLVector3& origin, LLCubeMapArray* cubearray, S32 cubeIndex, S32 face, F32 near_clip, bool dynamic_render) -{ - // NOTE: implementation derived from LLFloater360Capture::capture360Images() and simpleSnapshot - LL_PROFILE_ZONE_SCOPED_CATEGORY_APP; - LL_PROFILE_GPU_ZONE("cubeSnapshot"); - llassert(LLPipeline::sRenderDeferred); - llassert(!gCubeSnapshot); //assert a snapshot isn't already in progress - - U32 res = gPipeline.mRT->deferredScreen.getWidth(); - - //llassert(res <= gPipeline.mRT->deferredScreen.getWidth()); - //llassert(res <= gPipeline.mRT->deferredScreen.getHeight()); - - // save current view/camera settings so we can restore them afterwards - S32 old_occlusion = LLPipeline::sUseOcclusion; - - // set new parameters specific to the 360 requirements - LLPipeline::sUseOcclusion = 0; - LLViewerCamera* camera = LLViewerCamera::getInstance(); - - LLViewerCamera saved_camera = LLViewerCamera::instance(); - glh::matrix4f saved_proj = get_current_projection(); - glh::matrix4f saved_mod = get_current_modelview(); - - // camera constants for the square, cube map capture image - camera->setAspect(1.0); // must set aspect ratio first to avoid undesirable clamping of vertical FoV - camera->setViewNoBroadcast(F_PI_BY_TWO); - camera->yaw(0.0); - camera->setOrigin(origin); - camera->setNear(near_clip); - - glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); // stencil buffer is deprecated | GL_STENCIL_BUFFER_BIT); - - U32 dynamic_render_types[] = { - LLPipeline::RENDER_TYPE_AVATAR, - LLPipeline::RENDER_TYPE_CONTROL_AV, - LLPipeline::RENDER_TYPE_PARTICLES - }; - constexpr U32 dynamic_render_type_count = sizeof(dynamic_render_types) / sizeof(U32); - bool prev_dynamic_render_type[dynamic_render_type_count]; - - - if (!dynamic_render) - { - for (int i = 0; i < dynamic_render_type_count; ++i) - { - prev_dynamic_render_type[i] = gPipeline.hasRenderType(dynamic_render_types[i]); - if (prev_dynamic_render_type[i]) - { - gPipeline.toggleRenderType(dynamic_render_types[i]); - } - } - } - - bool prev_draw_ui = gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI); - if (prev_draw_ui) - { - LLPipeline::toggleRenderDebugFeature(LLPipeline::RENDER_DEBUG_FEATURE_UI); - } - - bool hide_hud = LLPipeline::sShowHUDAttachments; - if (hide_hud) - { - LLPipeline::sShowHUDAttachments = false; - } - LLRect window_rect = getWorldViewRectRaw(); - - mWorldViewRectRaw.set(0, res, res, 0); - - // these are the 6 directions we will point the camera, see LLCubeMapArray::sTargets - LLVector3 look_dirs[6] = { - LLVector3(1, 0, 0), - LLVector3(-1, 0, 0), - LLVector3(0, 1, 0), - LLVector3(0, -1, 0), - LLVector3(0, 0, 1), - LLVector3(0, 0, -1) - }; - - LLVector3 look_upvecs[6] = { - LLVector3(0, -1, 0), - LLVector3(0, -1, 0), - LLVector3(0, 0, 1), - LLVector3(0, 0, -1), - LLVector3(0, -1, 0), - LLVector3(0, -1, 0) - }; - - // for each of six sides of cubemap - //for (int i = 0; i < 6; ++i) - int i = face; - { - // set up camera to look in each direction - camera->lookDir(look_dirs[i], look_upvecs[i]); - - // turning this flag off here prohibits the screen swap - // to present the new page to the viewer - this stops - // the black flash in between captures when the number - // of render passes is more than 1. We need to also - // set it here because code in LLViewerDisplay resets - // it to true each time. - gDisplaySwapBuffers = false; - - // actually render the scene - gCubeSnapshot = true; - display_cube_face(); - gCubeSnapshot = false; - } - - gDisplaySwapBuffers = true; - - if (!gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) - { - if (prev_draw_ui) - { - LLPipeline::toggleRenderDebugFeature(LLPipeline::RENDER_DEBUG_FEATURE_UI); - } - } - - if (!dynamic_render) - { - for (int i = 0; i < dynamic_render_type_count; ++i) - { - if (prev_dynamic_render_type[i]) - { - gPipeline.toggleRenderType(dynamic_render_types[i]); - } - } - } - - if (hide_hud) - { - LLPipeline::sShowHUDAttachments = true; - } - - gPipeline.resetDrawOrders(); - mWorldViewRectRaw = window_rect; - - // restore original view/camera/avatar settings settings - *camera = saved_camera; - set_current_modelview(saved_mod); - set_current_projection(saved_proj); - setup3DViewport(); - LLPipeline::sUseOcclusion = old_occlusion; - - // ==================================================== - return true; -} - -void LLViewerWindow::destroyWindow() -{ - if (mWindow) - { - LLWindowManager::destroyWindow(mWindow); - } - mWindow = NULL; -} - - -void LLViewerWindow::drawMouselookInstructions() -{ - // Draw instructions for mouselook ("Press ESC to return to World View" partially transparent at the bottom of the screen.) - const std::string instructions = LLTrans::getString("LeaveMouselook"); - const LLFontGL* font = LLFontGL::getFont(LLFontDescriptor("SansSerif", "Large", LLFontGL::BOLD)); - - //to be on top of Bottom bar when it is opened - const S32 INSTRUCTIONS_PAD = 50; - - font->renderUTF8( - instructions, 0, - getWorldViewRectScaled().getCenterX(), - getWorldViewRectScaled().mBottom + INSTRUCTIONS_PAD, - LLColor4( 1.0f, 1.0f, 1.0f, 0.5f ), - LLFontGL::HCENTER, LLFontGL::TOP, - LLFontGL::NORMAL,LLFontGL::DROP_SHADOW); -} - -void* LLViewerWindow::getPlatformWindow() const -{ - return mWindow->getPlatformWindow(); -} - -void* LLViewerWindow::getMediaWindow() const -{ - return mWindow->getMediaWindow(); -} - -void LLViewerWindow::focusClient() const -{ - return mWindow->focusClient(); -} - -LLRootView* LLViewerWindow::getRootView() const -{ - return mRootView; -} - -LLRect LLViewerWindow::getWorldViewRectScaled() const -{ - return mWorldViewRectScaled; -} - -S32 LLViewerWindow::getWorldViewHeightScaled() const -{ - return mWorldViewRectScaled.getHeight(); -} - -S32 LLViewerWindow::getWorldViewWidthScaled() const -{ - return mWorldViewRectScaled.getWidth(); -} - - -S32 LLViewerWindow::getWorldViewHeightRaw() const -{ - return mWorldViewRectRaw.getHeight(); -} - -S32 LLViewerWindow::getWorldViewWidthRaw() const -{ - return mWorldViewRectRaw.getWidth(); -} - -S32 LLViewerWindow::getWindowHeightScaled() const -{ - return mWindowRectScaled.getHeight(); -} - -S32 LLViewerWindow::getWindowWidthScaled() const -{ - return mWindowRectScaled.getWidth(); -} - -S32 LLViewerWindow::getWindowHeightRaw() const -{ - return mWindowRectRaw.getHeight(); -} - -S32 LLViewerWindow::getWindowWidthRaw() const -{ - return mWindowRectRaw.getWidth(); -} - -void LLViewerWindow::setup2DRender() -{ - // setup ortho camera - gl_state_for_2d(mWindowRectRaw.getWidth(), mWindowRectRaw.getHeight()); - setup2DViewport(); -} - -void LLViewerWindow::setup2DViewport(S32 x_offset, S32 y_offset) -{ - gGLViewport[0] = mWindowRectRaw.mLeft + x_offset; - gGLViewport[1] = mWindowRectRaw.mBottom + y_offset; - gGLViewport[2] = mWindowRectRaw.getWidth(); - gGLViewport[3] = mWindowRectRaw.getHeight(); - glViewport(gGLViewport[0], gGLViewport[1], gGLViewport[2], gGLViewport[3]); -} - - -void LLViewerWindow::setup3DRender() -{ - // setup perspective camera - LLViewerCamera::getInstance()->setPerspective(NOT_FOR_SELECTION, mWorldViewRectRaw.mLeft, mWorldViewRectRaw.mBottom, mWorldViewRectRaw.getWidth(), mWorldViewRectRaw.getHeight(), false, LLViewerCamera::getInstance()->getNear(), MAX_FAR_CLIP*2.f); - setup3DViewport(); -} - -void LLViewerWindow::setup3DViewport(S32 x_offset, S32 y_offset) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_UI - gGLViewport[0] = mWorldViewRectRaw.mLeft + x_offset; - gGLViewport[1] = mWorldViewRectRaw.mBottom + y_offset; - gGLViewport[2] = mWorldViewRectRaw.getWidth(); - gGLViewport[3] = mWorldViewRectRaw.getHeight(); - glViewport(gGLViewport[0], gGLViewport[1], gGLViewport[2], gGLViewport[3]); -} - -void LLViewerWindow::revealIntroPanel() -{ - if (mProgressView) - { - mProgressView->revealIntroPanel(); - } -} - -void LLViewerWindow::initTextures(S32 location_id) -{ - if (mProgressView) - { - mProgressView->initTextures(location_id, LLGridManager::getInstance()->isInProductionGrid()); - } -} - -void LLViewerWindow::setShowProgress(const bool show) -{ - if (mProgressView) - { - mProgressView->setVisible(show); - } -} - -void LLViewerWindow::setStartupComplete() -{ - if (mProgressView) - { - mProgressView->setStartupComplete(); - } -} - -bool LLViewerWindow::getShowProgress() const -{ - return (mProgressView && mProgressView->getVisible()); -} - -void LLViewerWindow::setProgressString(const std::string& string) -{ - if (mProgressView) - { - mProgressView->setText(string); - } -} - -void LLViewerWindow::setProgressMessage(const std::string& msg) -{ - if(mProgressView) - { - mProgressView->setMessage(msg); - } -} - -void LLViewerWindow::setProgressPercent(const F32 percent) -{ - if (mProgressView) - { - mProgressView->setPercent(percent); - } -} - -void LLViewerWindow::setProgressCancelButtonVisible( bool b, const std::string& label ) -{ - if (mProgressView) - { - mProgressView->setCancelButtonVisible( b, label ); - } -} - -LLProgressView *LLViewerWindow::getProgressView() const -{ - return mProgressView; -} - -void LLViewerWindow::dumpState() -{ - LL_INFOS() << "LLViewerWindow Active " << S32(mActive) << LL_ENDL; - LL_INFOS() << "mWindow visible " << S32(mWindow->getVisible()) - << " minimized " << S32(mWindow->getMinimized()) - << LL_ENDL; -} - -void LLViewerWindow::stopGL(bool save_state) -{ - //Note: --bao - //if not necessary, do not change the order of the function calls in this function. - //if change something, make sure it will not break anything. - //especially be careful to put anything behind gTextureList.destroyGL(save_state); - if (!gGLManager.mIsDisabled) - { - LL_INFOS() << "Shutting down GL..." << LL_ENDL; - - // Pause texture decode threads (will get unpaused during main loop) - LLAppViewer::getTextureCache()->pause(); - LLAppViewer::getTextureFetch()->pause(); - - gSky.destroyGL(); - stop_glerror(); - - LLManipTranslate::destroyGL() ; - stop_glerror(); - - gBumpImageList.destroyGL(); - stop_glerror(); - - LLFontGL::destroyAllGL(); - stop_glerror(); - - LLVOAvatar::destroyGL(); - stop_glerror(); - - LLVOPartGroup::destroyGL(); - - LLViewerDynamicTexture::destroyGL(); - stop_glerror(); - - if (gPipeline.isInit()) - { - gPipeline.destroyGL(); - } - - gBox.cleanupGL(); - - if(gPostProcess) - { - gPostProcess->invalidate(); - } - - gTextureList.destroyGL(save_state); - stop_glerror(); - - gGLManager.mIsDisabled = true; - stop_glerror(); - - //unload shader's - while (LLGLSLShader::sInstances.size()) - { - LLGLSLShader* shader = *(LLGLSLShader::sInstances.begin()); - shader->unload(); - } - } -} - -void LLViewerWindow::restoreGL(const std::string& progress_message) -{ - //Note: --bao - //if not necessary, do not change the order of the function calls in this function. - //if change something, make sure it will not break anything. - //especially, be careful to put something before gTextureList.restoreGL(); - if (gGLManager.mIsDisabled) - { - LL_INFOS() << "Restoring GL..." << LL_ENDL; - gGLManager.mIsDisabled = false; - - initGLDefaults(); - LLGLState::restoreGL(); - - gTextureList.restoreGL(); - - // for future support of non-square pixels, and fonts that are properly stretched - //LLFontGL::destroyDefaultFonts(); - initFonts(); - - gSky.restoreGL(); - gPipeline.restoreGL(); - LLManipTranslate::restoreGL(); - - gBumpImageList.restoreGL(); - LLViewerDynamicTexture::restoreGL(); - LLVOAvatar::restoreGL(); - LLVOPartGroup::restoreGL(); - - gResizeScreenTexture = true; - gWindowResized = true; - - if (isAgentAvatarValid() && gAgentAvatarp->isEditingAppearance()) - { - LLVisualParamHint::requestHintUpdates(); - } - - if (!progress_message.empty()) - { - gRestoreGLTimer.reset(); - gRestoreGL = true; - setShowProgress(true); - setProgressString(progress_message); - } - LL_INFOS() << "...Restoring GL done" << LL_ENDL; - if(!LLAppViewer::instance()->restoreErrorTrap()) - { - LL_WARNS() << " Someone took over my signal/exception handler (post restoreGL)!" << LL_ENDL; - } - - } -} - -void LLViewerWindow::initFonts(F32 zoom_factor) -{ - LLFontGL::destroyAllGL(); - // Initialize with possibly different zoom factor - - LLFontManager::initClass(); - - LLFontGL::initClass( gSavedSettings.getF32("FontScreenDPI"), - mDisplayScale.mV[VX] * zoom_factor, - mDisplayScale.mV[VY] * zoom_factor, - gDirUtilp->getAppRODataDir()); -} - -void LLViewerWindow::requestResolutionUpdate() -{ - mResDirty = true; -} - -static LLTrace::BlockTimerStatHandle FTM_WINDOW_CHECK_SETTINGS("Window Settings"); - -void LLViewerWindow::checkSettings() -{ - LL_RECORD_BLOCK_TIME(FTM_WINDOW_CHECK_SETTINGS); - if (mStatesDirty) - { - gGL.refreshState(); - LLViewerShaderMgr::instance()->setShaders(); - mStatesDirty = false; - } - - // We want to update the resolution AFTER the states getting refreshed not before. - if (mResDirty) - { - reshape(getWindowWidthRaw(), getWindowHeightRaw()); - mResDirty = false; - } -} - -void LLViewerWindow::restartDisplay(bool show_progress_bar) -{ - LL_INFOS() << "Restaring GL" << LL_ENDL; - stopGL(); - if (show_progress_bar) - { - restoreGL(LLTrans::getString("ProgressChangingResolution")); - } - else - { - restoreGL(); - } -} - -bool LLViewerWindow::changeDisplaySettings(LLCoordScreen size, bool enable_vsync, bool show_progress_bar) -{ - //bool was_maximized = gSavedSettings.getBOOL("WindowMaximized"); - - //gResizeScreenTexture = true; - - - //U32 fsaa = gSavedSettings.getU32("RenderFSAASamples"); - //U32 old_fsaa = mWindow->getFSAASamples(); - - // if not maximized, use the request size - if (!mWindow->getMaximized()) - { - mWindow->setSize(size); - } - - //if (fsaa == old_fsaa) - { - return true; - } - -/* - - // Close floaters that don't handle settings change - LLFloaterReg::hideInstance("snapshot"); - - bool result_first_try = false; - bool result_second_try = false; - - LLFocusableElement* keyboard_focus = gFocusMgr.getKeyboardFocus(); - send_agent_pause(); - LL_INFOS() << "Stopping GL during changeDisplaySettings" << LL_ENDL; - stopGL(); - mIgnoreActivate = true; - LLCoordScreen old_size; - LLCoordScreen old_pos; - mWindow->getSize(&old_size); - - //mWindow->setFSAASamples(fsaa); - - result_first_try = mWindow->switchContext(false, size, disable_vsync); - if (!result_first_try) - { - // try to switch back - //mWindow->setFSAASamples(old_fsaa); - result_second_try = mWindow->switchContext(false, old_size, disable_vsync); - - if (!result_second_try) - { - // we are stuck...try once again with a minimal resolution? - send_agent_resume(); - mIgnoreActivate = false; - return false; - } - } - send_agent_resume(); - - LL_INFOS() << "Restoring GL during resolution change" << LL_ENDL; - if (show_progress_bar) - { - restoreGL(LLTrans::getString("ProgressChangingResolution")); - } - else - { - restoreGL(); - } - - if (!result_first_try) - { - LLSD args; - args["RESX"] = llformat("%d",size.mX); - args["RESY"] = llformat("%d",size.mY); - LLNotificationsUtil::add("ResolutionSwitchFail", args); - size = old_size; // for reshape below - } - - bool success = result_first_try || result_second_try; - - if (success) - { - // maximize window if was maximized, else reposition - if (was_maximized) - { - mWindow->maximize(); - } - else - { - S32 windowX = gSavedSettings.getS32("WindowX"); - S32 windowY = gSavedSettings.getS32("WindowY"); - - mWindow->setPosition(LLCoordScreen ( windowX, windowY ) ); - } - } - - mIgnoreActivate = false; - gFocusMgr.setKeyboardFocus(keyboard_focus); - - return success; - - */ -} - -F32 LLViewerWindow::getWorldViewAspectRatio() const -{ - F32 world_aspect = (F32)mWorldViewRectRaw.getWidth() / (F32)mWorldViewRectRaw.getHeight(); - return world_aspect; -} - -void LLViewerWindow::calcDisplayScale() -{ - F32 ui_scale_factor = llclamp(gSavedSettings.getF32("UIScaleFactor") * mWindow->getSystemUISize(), MIN_UI_SCALE, MAX_UI_SCALE); - LLVector2 display_scale; - display_scale.setVec(llmax(1.f / mWindow->getPixelAspectRatio(), 1.f), llmax(mWindow->getPixelAspectRatio(), 1.f)); - display_scale *= ui_scale_factor; - - // limit minimum display scale - if (display_scale.mV[VX] < MIN_DISPLAY_SCALE || display_scale.mV[VY] < MIN_DISPLAY_SCALE) - { - display_scale *= MIN_DISPLAY_SCALE / llmin(display_scale.mV[VX], display_scale.mV[VY]); - } - - if (display_scale != mDisplayScale) - { - LL_INFOS() << "Setting display scale to " << display_scale << " for ui scale: " << ui_scale_factor << LL_ENDL; - - mDisplayScale = display_scale; - // Init default fonts - initFonts(); - } -} - -//static -LLRect LLViewerWindow::calcScaledRect(const LLRect & rect, const LLVector2& display_scale) -{ - LLRect res = rect; - res.mLeft = ll_round((F32)res.mLeft / display_scale.mV[VX]); - res.mRight = ll_round((F32)res.mRight / display_scale.mV[VX]); - res.mBottom = ll_round((F32)res.mBottom / display_scale.mV[VY]); - res.mTop = ll_round((F32)res.mTop / display_scale.mV[VY]); - - return res; -} - -S32 LLViewerWindow::getChatConsoleBottomPad() -{ - S32 offset = 0; - - if(gToolBarView) - offset += gToolBarView->getBottomToolbar()->getRect().getHeight(); - - return offset; -} - -LLRect LLViewerWindow::getChatConsoleRect() -{ - LLRect full_window(0, getWindowHeightScaled(), getWindowWidthScaled(), 0); - LLRect console_rect = full_window; - - const S32 CONSOLE_PADDING_TOP = 24; - const S32 CONSOLE_PADDING_LEFT = 24; - const S32 CONSOLE_PADDING_RIGHT = 10; - - console_rect.mTop -= CONSOLE_PADDING_TOP; - console_rect.mBottom += getChatConsoleBottomPad(); - - console_rect.mLeft += CONSOLE_PADDING_LEFT; - - static const bool CHAT_FULL_WIDTH = gSavedSettings.getBOOL("ChatFullWidth"); - - if (CHAT_FULL_WIDTH) - { - console_rect.mRight -= CONSOLE_PADDING_RIGHT; - } - else - { - // Make console rect somewhat narrow so having inventory open is - // less of a problem. - console_rect.mRight = console_rect.mLeft + 2 * getWindowWidthScaled() / 3; - } - - return console_rect; -} - -void LLViewerWindow::reshapeStatusBarContainer() -{ - LLPanel* status_bar_container = getRootView()->getChild("status_bar_container"); - LLView* nav_bar_container = getRootView()->getChild("nav_bar_container"); - - S32 new_height = status_bar_container->getRect().getHeight(); - S32 new_width = status_bar_container->getRect().getWidth(); - - if (gSavedSettings.getBOOL("ShowNavbarNavigationPanel")) - { - // Navigation bar is outside visible area, expand status_bar_container to show it - new_height += nav_bar_container->getRect().getHeight(); - } - else - { - // collapse status_bar_container - new_height -= nav_bar_container->getRect().getHeight(); - } - status_bar_container->reshape(new_width, new_height, true); -} - -void LLViewerWindow::resetStatusBarContainer() -{ - LLNavigationBar* navbar = LLNavigationBar::getInstance(); - if (gSavedSettings.getBOOL("ShowNavbarNavigationPanel") || navbar->getVisible()) - { - // was previously showing navigation bar - LLView* nav_bar_container = getRootView()->getChild("nav_bar_container"); - LLPanel* status_bar_container = getRootView()->getChild("status_bar_container"); - S32 new_height = status_bar_container->getRect().getHeight(); - S32 new_width = status_bar_container->getRect().getWidth(); - new_height -= nav_bar_container->getRect().getHeight(); - status_bar_container->reshape(new_width, new_height, true); - } -} -//---------------------------------------------------------------------------- - - -void LLViewerWindow::setUIVisibility(bool visible) -{ - mUIVisible = visible; - - if (!visible) - { - gAgentCamera.changeCameraToThirdPerson(false); - gFloaterView->hideAllFloaters(); - } - else - { - gFloaterView->showHiddenFloaters(); - } - - if (gToolBarView) - { - gToolBarView->setToolBarsVisible(visible); - } - - LLNavigationBar::getInstance()->setVisible(visible ? gSavedSettings.getBOOL("ShowNavbarNavigationPanel") : false); - LLPanelTopInfoBar::getInstance()->setVisible(visible? gSavedSettings.getBOOL("ShowMiniLocationPanel") : false); - mRootView->getChildView("status_bar_container")->setVisible(visible); -} - -bool LLViewerWindow::getUIVisibility() -{ - return mUIVisible; -} - -//////////////////////////////////////////////////////////////////////////// -// -// LLPickInfo -// -LLPickInfo::LLPickInfo() - : mKeyMask(MASK_NONE), - mPickCallback(NULL), - mPickType(PICK_INVALID), - mWantSurfaceInfo(false), - mObjectFace(-1), - mUVCoords(-1.f, -1.f), - mSTCoords(-1.f, -1.f), - mXYCoords(-1, -1), - mIntersection(), - mNormal(), - mTangent(), - mBinormal(), - mHUDIcon(NULL), - mPickTransparent(false), - mPickRigged(false), - mPickParticle(false) -{ -} - -LLPickInfo::LLPickInfo(const LLCoordGL& mouse_pos, - MASK keyboard_mask, - bool pick_transparent, - bool pick_rigged, - bool pick_particle, - bool pick_reflection_probe, - bool pick_uv_coords, - bool pick_unselectable, - void (*pick_callback)(const LLPickInfo& pick_info)) - : mMousePt(mouse_pos), - mKeyMask(keyboard_mask), - mPickCallback(pick_callback), - mPickType(PICK_INVALID), - mWantSurfaceInfo(pick_uv_coords), - mObjectFace(-1), - mUVCoords(-1.f, -1.f), - mSTCoords(-1.f, -1.f), - mXYCoords(-1, -1), - mNormal(), - mTangent(), - mBinormal(), - mHUDIcon(NULL), - mPickTransparent(pick_transparent), - mPickRigged(pick_rigged), - mPickParticle(pick_particle), - mPickReflectionProbe(pick_reflection_probe), - mPickUnselectable(pick_unselectable) -{ -} - -void LLPickInfo::fetchResults() -{ - - S32 face_hit = -1; - LLVector4a intersection, normal; - LLVector4a tangent; - - LLVector2 uv; - - LLHUDIcon* hit_icon = gViewerWindow->cursorIntersectIcon(mMousePt.mX, mMousePt.mY, 512.f, &intersection); - - LLVector4a origin; - origin.load3(LLViewerCamera::getInstance()->getOrigin().mV); - F32 icon_dist = 0.f; - LLVector4a start; - LLVector4a end; - LLVector4a particle_end; - - if (hit_icon) - { - LLVector4a delta; - delta.setSub(intersection, origin); - icon_dist = delta.getLength3().getF32(); - } - - LLViewerObject* hit_object = gViewerWindow->cursorIntersect(mMousePt.mX, mMousePt.mY, 512.f, - NULL, -1, mPickTransparent, mPickRigged, mPickUnselectable, mPickReflectionProbe, &face_hit, - &intersection, &uv, &normal, &tangent, &start, &end); - - mPickPt = mMousePt; - - U32 te_offset = face_hit > -1 ? face_hit : 0; - - if (mPickParticle) - { //get the end point of line segement to use for particle raycast - if (hit_object) - { - particle_end = intersection; - } - else - { - particle_end = end; - } - } - - LLViewerObject* objectp = hit_object; - - - LLVector4a delta; - delta.setSub(origin, intersection); - - if (hit_icon && - (!objectp || - icon_dist < delta.getLength3().getF32())) - { - // was this name referring to a hud icon? - mHUDIcon = hit_icon; - mPickType = PICK_ICON; - mPosGlobal = mHUDIcon->getPositionGlobal(); - - } - else if (objectp) - { - if( objectp->getPCode() == LLViewerObject::LL_VO_SURFACE_PATCH ) - { - // Hit land - mPickType = PICK_LAND; - mObjectID.setNull(); // land has no id - - // put global position into land_pos - LLVector3d land_pos; - if (!gViewerWindow->mousePointOnLandGlobal(mPickPt.mX, mPickPt.mY, &land_pos, mPickUnselectable)) - { - // The selected point is beyond the draw distance or is otherwise - // not selectable. Return before calling mPickCallback(). - return; - } - - // Fudge the land focus a little bit above ground. - mPosGlobal = land_pos + LLVector3d::z_axis * 0.1f; - } - else - { - if(isFlora(objectp)) - { - mPickType = PICK_FLORA; - } - else - { - mPickType = PICK_OBJECT; - } - - LLVector3 v_intersection(intersection.getF32ptr()); - - mObjectOffset = gAgentCamera.calcFocusOffset(objectp, v_intersection, mPickPt.mX, mPickPt.mY); - mObjectID = objectp->mID; - mObjectFace = (te_offset == NO_FACE) ? -1 : (S32)te_offset; - - - - mPosGlobal = gAgent.getPosGlobalFromAgent(v_intersection); - - if (mWantSurfaceInfo) - { - getSurfaceInfo(); - } - } - } - - if (mPickParticle) - { //search for closest particle to click origin out to intersection point - S32 part_face = -1; - - LLVOPartGroup* group = gPipeline.lineSegmentIntersectParticle(start, particle_end, NULL, &part_face); - if (group) - { - mParticleOwnerID = group->getPartOwner(part_face); - mParticleSourceID = group->getPartSource(part_face); - } - } - - if (mPickCallback) - { - mPickCallback(*this); - } -} - -LLPointer LLPickInfo::getObject() const -{ - return gObjectList.findObject( mObjectID ); -} - -void LLPickInfo::updateXYCoords() -{ - if (mObjectFace > -1) - { - const LLTextureEntry* tep = getObject()->getTE(mObjectFace); - LLPointer imagep = LLViewerTextureManager::getFetchedTexture(tep->getID()); - if(mUVCoords.mV[VX] >= 0.f && mUVCoords.mV[VY] >= 0.f && imagep.notNull()) - { - mXYCoords.mX = ll_round(mUVCoords.mV[VX] * (F32)imagep->getWidth()); - mXYCoords.mY = ll_round((1.f - mUVCoords.mV[VY]) * (F32)imagep->getHeight()); - } - } -} - -void LLPickInfo::getSurfaceInfo() -{ - // set values to uninitialized - this is what we return if no intersection is found - mObjectFace = -1; - mUVCoords = LLVector2(-1, -1); - mSTCoords = LLVector2(-1, -1); - mXYCoords = LLCoordScreen(-1, -1); - mIntersection = LLVector3(0,0,0); - mNormal = LLVector3(0,0,0); - mBinormal = LLVector3(0,0,0); - mTangent = LLVector4(0,0,0,0); - - LLVector4a tangent; - LLVector4a intersection; - LLVector4a normal; - - tangent.clear(); - normal.clear(); - intersection.clear(); - - LLViewerObject* objectp = getObject(); - - if (objectp) - { - if (gViewerWindow->cursorIntersect(ll_round((F32)mMousePt.mX), ll_round((F32)mMousePt.mY), 1024.f, - objectp, -1, mPickTransparent, mPickRigged, mPickUnselectable, mPickReflectionProbe, - &mObjectFace, - &intersection, - &mSTCoords, - &normal, - &tangent)) - { - // if we succeeded with the intersect above, compute the texture coordinates: - - if (objectp->mDrawable.notNull() && mObjectFace > -1) - { - LLFace* facep = objectp->mDrawable->getFace(mObjectFace); - if (facep) - { - mUVCoords = facep->surfaceToTexture(mSTCoords, intersection, normal); - } - } - - mIntersection.set(intersection.getF32ptr()); - mNormal.set(normal.getF32ptr()); - mTangent.set(tangent.getF32ptr()); - - //extrapoloate binormal from normal and tangent - - LLVector4a binormal; - binormal.setCross3(normal, tangent); - binormal.mul(tangent.getF32ptr()[3]); - - mBinormal.set(binormal.getF32ptr()); - - mBinormal.normalize(); - mNormal.normalize(); - mTangent.normalize(); - - // and XY coords: - updateXYCoords(); - - } - } -} - -//static -bool LLPickInfo::isFlora(LLViewerObject* object) -{ - if (!object) return false; - - LLPCode pcode = object->getPCode(); - - if( (LL_PCODE_LEGACY_GRASS == pcode) - || (LL_PCODE_LEGACY_TREE == pcode) - || (LL_PCODE_TREE_NEW == pcode)) - { - return true; - } - return false; -} +/** + * @file llviewerwindow.cpp + * @brief Implementation of the LLViewerWindow class. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llviewerwindow.h" + + +// system library includes +#include +#include +#include +#include +#include +#include +#include + +#include "llagent.h" +#include "llagentcamera.h" +#include "llcommandhandler.h" +#include "llcommunicationchannel.h" +#include "llfloaterreg.h" +#include "llhudicon.h" +#include "llmeshrepository.h" +#include "llnotificationhandler.h" +#include "llpanellogin.h" +#include "llsetkeybinddialog.h" +#include "llviewerinput.h" +#include "llviewermenu.h" + +#include "llviewquery.h" +#include "llxmltree.h" +#include "llslurl.h" +#include "llrender.h" + +#include "stringize.h" + +// +// TODO: Many of these includes are unnecessary. Remove them. +// + +// linden library includes +#include "llaudioengine.h" // mute on minimize +#include "llchatentry.h" +#include "indra_constants.h" +#include "llassetstorage.h" +#include "llerrorcontrol.h" +#include "llfontgl.h" +#include "llmousehandler.h" +#include "llrect.h" +#include "llsky.h" +#include "llstring.h" +#include "llui.h" +#include "lluuid.h" +#include "llview.h" +#include "llxfermanager.h" +#include "message.h" +#include "object_flags.h" +#include "lltimer.h" +#include "llviewermenu.h" +#include "lltooltip.h" +#include "llmediaentry.h" +#include "llurldispatcher.h" +#include "raytrace.h" + +// newview includes +#include "llagent.h" +#include "llbox.h" +#include "llchicletbar.h" +#include "llconsole.h" +#include "llviewercontrol.h" +#include "llcylinder.h" +#include "lldebugview.h" +#include "lldir.h" +#include "lldrawable.h" +#include "lldrawpoolalpha.h" +#include "lldrawpoolbump.h" +#include "lldrawpoolwater.h" +#include "llmaniptranslate.h" +#include "llface.h" +#include "llfeaturemanager.h" +#include "llfilepicker.h" +#include "llfirstuse.h" +#include "llfloater.h" +#include "llfloaterbuyland.h" +#include "llfloatercamera.h" +#include "llfloaterland.h" +#include "llfloaterinspect.h" +#include "llfloatermap.h" +#include "llfloaternamedesc.h" +#include "llfloaterpreference.h" +#include "llfloatersnapshot.h" +#include "llfloatertools.h" +#include "llfloaterworldmap.h" +#include "llfocusmgr.h" +#include "llfontfreetype.h" +#include "llgesturemgr.h" +#include "llglheaders.h" +#include "lltooltip.h" +#include "llhudmanager.h" +#include "llhudobject.h" +#include "llhudview.h" +#include "llimage.h" +#include "llimagej2c.h" +#include "llimageworker.h" +#include "llkeyboard.h" +#include "lllineeditor.h" +#include "llmenugl.h" +#include "llmenuoptionpathfindingrebakenavmesh.h" +#include "llmodaldialog.h" +#include "llmorphview.h" +#include "llmoveview.h" +#include "llnavigationbar.h" +#include "llnotificationhandler.h" +#include "llpaneltopinfobar.h" +#include "llpopupview.h" +#include "llpreviewtexture.h" +#include "llprogressview.h" +#include "llresmgr.h" +#include "llselectmgr.h" +#include "llrootview.h" +#include "llrendersphere.h" +#include "llstartup.h" +#include "llstatusbar.h" +#include "llstatview.h" +#include "llsurface.h" +#include "llsurfacepatch.h" +#include "lltexlayer.h" +#include "lltextbox.h" +#include "lltexturecache.h" +#include "lltexturefetch.h" +#include "lltextureview.h" +#include "lltoast.h" +#include "lltool.h" +#include "lltoolbarview.h" +#include "lltoolcomp.h" +#include "lltooldraganddrop.h" +#include "lltoolface.h" +#include "lltoolfocus.h" +#include "lltoolgrab.h" +#include "lltoolmgr.h" +#include "lltoolmorph.h" +#include "lltoolpie.h" +#include "lltoolselectland.h" +#include "lltrans.h" +#include "lluictrlfactory.h" +#include "llurldispatcher.h" // SLURL from other app instance +#include "llversioninfo.h" +#include "llvieweraudio.h" +#include "llviewercamera.h" +#include "llviewergesture.h" +#include "llviewertexturelist.h" +#include "llviewerinventory.h" +#include "llviewerinput.h" +#include "llviewermedia.h" +#include "llviewermediafocus.h" +#include "llviewermenu.h" +#include "llviewermessage.h" +#include "llviewerobjectlist.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "llviewershadermgr.h" +#include "llviewerstats.h" +#include "llvoavatarself.h" +#include "llvopartgroup.h" +#include "llvovolume.h" +#include "llworld.h" +#include "llworldmapview.h" +#include "pipeline.h" +#include "llappviewer.h" +#include "llviewerdisplay.h" +#include "llspatialpartition.h" +#include "llviewerjoystick.h" +#include "llviewermenufile.h" // LLFilePickerReplyThread +#include "llviewernetwork.h" +#include "llpostprocess.h" +#include "llfloaterimnearbychat.h" +#include "llagentui.h" +#include "llwearablelist.h" + +#include "llviewereventrecorder.h" + +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "llnotificationmanager.h" + +#include "llfloaternotificationsconsole.h" + +#include "llwindowlistener.h" +#include "llviewerwindowlistener.h" +#include "llpaneltopinfobar.h" +#include "llcleanup.h" + +#if LL_WINDOWS +#include // For Unicode conversion methods +#include "llwindowwin32.h" // For AltGr handling +#endif + +// +// Globals +// +void render_ui(F32 zoom_factor = 1.f, int subfield = 0); +void swap(); + +extern bool gDebugClicks; +extern bool gDisplaySwapBuffers; +extern bool gDepthDirty; +extern bool gResizeScreenTexture; +extern bool gCubeSnapshot; +extern bool gSnapshotNoPost; + +LLViewerWindow *gViewerWindow = NULL; + +LLFrameTimer gAwayTimer; +LLFrameTimer gAwayTriggerTimer; + +bool gShowOverlayTitle = false; + +LLViewerObject* gDebugRaycastObject = NULL; +LLVOPartGroup* gDebugRaycastParticle = NULL; +LLVector4a gDebugRaycastIntersection; +LLVector4a gDebugRaycastParticleIntersection; +LLVector2 gDebugRaycastTexCoord; +LLVector4a gDebugRaycastNormal; +LLVector4a gDebugRaycastTangent; +S32 gDebugRaycastFaceHit; +LLVector4a gDebugRaycastStart; +LLVector4a gDebugRaycastEnd; + +// HUD display lines in lower right +bool gDisplayWindInfo = false; +bool gDisplayCameraPos = false; +bool gDisplayFOV = false; +bool gDisplayBadge = false; + +static const U8 NO_FACE = 255; +bool gQuietSnapshot = false; + +// Minimum value for UIScaleFactor, also defined in preferences, ui_scale_slider +static const F32 MIN_UI_SCALE = 0.75f; +// 4.0 in preferences, but win10 supports larger scaling and value is used more as +// sanity check, so leaving space for larger values from DPI updates. +static const F32 MAX_UI_SCALE = 7.0f; +static const F32 MIN_DISPLAY_SCALE = 0.75f; + +static const char KEY_MOUSELOOK = 'M'; + +static LLCachedControl sSnapshotBaseName(LLCachedControl(gSavedPerAccountSettings, "SnapshotBaseName", "Snapshot")); +static LLCachedControl sSnapshotDir(LLCachedControl(gSavedPerAccountSettings, "SnapshotBaseDir", "")); + +LLTrace::SampleStatHandle<> LLViewerWindow::sMouseVelocityStat("Mouse Velocity"); + + +class RecordToChatConsoleRecorder : public LLError::Recorder +{ +public: + virtual void recordMessage(LLError::ELevel level, + const std::string& message) + { + //FIXME: this is NOT thread safe, and will do bad things when a warning is issued from a non-UI thread + + // only log warnings to chat console + //if (level == LLError::LEVEL_WARN) + //{ + //LLFloaterChat* chat_floater = LLFloaterReg::findTypedInstance("chat"); + //if (chat_floater && gSavedSettings.getBOOL("WarningsAsChat")) + //{ + // LLChat chat; + // chat.mText = message; + // chat.mSourceType = CHAT_SOURCE_SYSTEM; + + // chat_floater->addChat(chat, false, false); + //} + //} + } +}; + +class RecordToChatConsole : public LLSingleton +{ + LLSINGLETON(RecordToChatConsole); +public: + void startRecorder() { LLError::addRecorder(mRecorder); } + void stopRecorder() { LLError::removeRecorder(mRecorder); } + +private: + LLError::RecorderPtr mRecorder; +}; + +RecordToChatConsole::RecordToChatConsole(): + mRecorder(new RecordToChatConsoleRecorder()) +{ + mRecorder->showTags(false); + mRecorder->showLocation(false); + mRecorder->showMultiline(true); +} + +//////////////////////////////////////////////////////////////////////////// +// +// Print Utility +// + +// Convert a normalized float (-1.0 <= x <= +1.0) to a fixed 1.4 format string: +// +// s#.#### +// +// Where: +// s sign character; space if x is positiv, minus if negative +// # decimal digits +// +// This is similar to printf("%+.4f") except positive numbers are NOT cluttered with a leading '+' sign. +// NOTE: This does NOT null terminate the output +void normalized_float_to_string(const float x, char *out_str) +{ + static const unsigned char DECIMAL_BCD2[] = + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99 + }; + + int neg = (x < 0); + int rem = neg + ? (int)(x * -10000.) + : (int)(x * 10000.); + + int d10 = rem % 100; rem /= 100; + int d32 = rem % 100; rem /= 100; + + out_str[6] = '0' + ((DECIMAL_BCD2[ d10 ] >> 0) & 0xF); + out_str[5] = '0' + ((DECIMAL_BCD2[ d10 ] >> 4) & 0xF); + out_str[4] = '0' + ((DECIMAL_BCD2[ d32 ] >> 0) & 0xF); + out_str[3] = '0' + ((DECIMAL_BCD2[ d32 ] >> 4) & 0xF); + out_str[2] = '.'; + out_str[1] = '0' + (rem & 1); + out_str[0] = " -"[neg]; // Could always show '+' for positive but this clutters up the common case +} + +// normalized float +// printf("%-.4f %-.4f %-.4f") +// Params: +// float &matrix_row[4] +// int matrix_cell_index +// string out_buffer (size 32) +// Note: The buffer is assumed to be pre-filled with spaces +#define MATRIX_ROW_N32_TO_STR(matrix_row, i, out_buffer) \ + normalized_float_to_string(matrix_row[i+0], out_buffer + 0); \ + normalized_float_to_string(matrix_row[i+1], out_buffer + 11); \ + normalized_float_to_string(matrix_row[i+2], out_buffer + 22); \ + out_buffer[31] = 0; + + +// regular float +// sprintf(buffer, "%-8.2f %-8.2f %-8.2f", matrix_row[i+0], matrix_row[i+1], matrix_row[i+2]); +// Params: +// float &matrix_row[4] +// int matrix_cell_index +// char out_buffer[32] +// Note: The buffer is assumed to be pre-filled with spaces +#define MATRIX_ROW_F32_TO_STR(matrix_row, i, out_buffer) { \ + static const char *format[3] = { \ + "%-8.2f" , /* 0 */ \ + "> 99K ", /* 1 */ \ + "< -99K " /* 2 */ \ + }; \ + \ + F32 temp_0 = matrix_row[i+0]; \ + F32 temp_1 = matrix_row[i+1]; \ + F32 temp_2 = matrix_row[i+2]; \ + \ + U8 flag_0 = (((U8)(temp_0 < -99999.99)) << 1) | ((U8)(temp_0 > 99999.99)); \ + U8 flag_1 = (((U8)(temp_1 < -99999.99)) << 1) | ((U8)(temp_1 > 99999.99)); \ + U8 flag_2 = (((U8)(temp_2 < -99999.99)) << 1) | ((U8)(temp_2 > 99999.99)); \ + \ + if (temp_0 < 0.f) out_buffer[ 0] = '-'; \ + if (temp_1 < 0.f) out_buffer[11] = '-'; \ + if (temp_2 < 0.f) out_buffer[22] = '-'; \ + \ + sprintf(out_buffer+ 1,format[flag_0],fabsf(temp_0)); out_buffer[ 1+8] = ' '; \ + sprintf(out_buffer+12,format[flag_1],fabsf(temp_1)); out_buffer[12+8] = ' '; \ + sprintf(out_buffer+23,format[flag_2],fabsf(temp_2)); out_buffer[23+8] = 0 ; \ +} + +//////////////////////////////////////////////////////////////////////////// +// +// LLDebugText +// + +static LLTrace::BlockTimerStatHandle FTM_DISPLAY_DEBUG_TEXT("Display Debug Text"); + +class LLDebugText +{ +private: + struct Line + { + Line(const std::string& in_text, S32 in_x, S32 in_y) : text(in_text), x(in_x), y(in_y) {} + std::string text; + S32 x,y; + }; + + LLViewerWindow *mWindow; + + typedef std::vector line_list_t; + line_list_t mLineList; + LLColor4 mTextColor; + + LLColor4 mBackColor; + LLRect mBackRectCamera1; + LLRect mBackRectCamera2; + + void addText(S32 x, S32 y, const std::string &text) + { + mLineList.push_back(Line(text, x, y)); + } + + void clearText() { mLineList.clear(); } + +public: + LLDebugText(LLViewerWindow* window) : mWindow(window) {} + + void update() + { + if (!gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) + { + clearText(); + return; + } + + static LLCachedControl log_texture_traffic(gSavedSettings,"LogTextureNetworkTraffic", false) ; + + std::string wind_vel_text; + std::string wind_vector_text; + std::string rwind_vel_text; + std::string rwind_vector_text; + std::string audio_text; + + static const std::string beacon_particle = LLTrans::getString("BeaconParticle"); + static const std::string beacon_physical = LLTrans::getString("BeaconPhysical"); + static const std::string beacon_scripted = LLTrans::getString("BeaconScripted"); + static const std::string beacon_scripted_touch = LLTrans::getString("BeaconScriptedTouch"); + static const std::string beacon_sound = LLTrans::getString("BeaconSound"); + static const std::string beacon_media = LLTrans::getString("BeaconMedia"); + static const std::string beacon_sun = LLTrans::getString("BeaconSun"); + static const std::string beacon_moon = LLTrans::getString("BeaconMoon"); + static const std::string particle_hiding = LLTrans::getString("ParticleHiding"); + + // Draw the statistics in a light gray + // and in a thin font + mTextColor = LLColor4( 0.86f, 0.86f, 0.86f, 1.f ); + + // Draw stuff growing up from right lower corner of screen + S32 x_right = mWindow->getWorldViewWidthScaled(); + S32 xpos = x_right - 400; + xpos = llmax(xpos, 0); + S32 ypos = 64; + const S32 y_inc = 20; + + // Camera matrix text is hard to see again a white background + // Add a dark background underneath the matrices for readability (contrast) + mBackRectCamera1.mLeft = xpos; + mBackRectCamera1.mRight = x_right; + mBackRectCamera1.mTop = -1; + mBackRectCamera1.mBottom = -1; + mBackRectCamera2 = mBackRectCamera1; + + mBackColor = LLUIColorTable::instance().getColor( "MenuDefaultBgColor" ); + + clearText(); + + if (gSavedSettings.getBOOL("DebugShowTime")) + { + F32 time = gFrameTimeSeconds; + S32 hours = (S32)(time / (60*60)); + S32 mins = (S32)((time - hours*(60*60)) / 60); + S32 secs = (S32)((time - hours*(60*60) - mins*60)); + addText(xpos, ypos, llformat("Time: %d:%02d:%02d", hours,mins,secs)); ypos += y_inc; + } + + if (gSavedSettings.getBOOL("DebugShowMemory")) + { + addText(xpos, ypos, + STRINGIZE("Memory: " << (LLMemory::getCurrentRSS() / 1024) << " (KB)")); + ypos += y_inc; + } + + if (gDisplayCameraPos) + { + std::string camera_view_text; + std::string camera_center_text; + std::string agent_view_text; + std::string agent_left_text; + std::string agent_center_text; + std::string agent_root_center_text; + + LLVector3d tvector; // Temporary vector to hold data for printing. + + // Update camera center, camera view, wind info every other frame + tvector = gAgent.getPositionGlobal(); + agent_center_text = llformat("AgentCenter %f %f %f", + (F32)(tvector.mdV[VX]), (F32)(tvector.mdV[VY]), (F32)(tvector.mdV[VZ])); + + if (isAgentAvatarValid()) + { + tvector = gAgent.getPosGlobalFromAgent(gAgentAvatarp->mRoot->getWorldPosition()); + agent_root_center_text = llformat("AgentRootCenter %f %f %f", + (F32)(tvector.mdV[VX]), (F32)(tvector.mdV[VY]), (F32)(tvector.mdV[VZ])); + } + else + { + agent_root_center_text = "---"; + } + + + tvector = LLVector4(gAgent.getFrameAgent().getAtAxis()); + agent_view_text = llformat("AgentAtAxis %f %f %f", + (F32)(tvector.mdV[VX]), (F32)(tvector.mdV[VY]), (F32)(tvector.mdV[VZ])); + + tvector = LLVector4(gAgent.getFrameAgent().getLeftAxis()); + agent_left_text = llformat("AgentLeftAxis %f %f %f", + (F32)(tvector.mdV[VX]), (F32)(tvector.mdV[VY]), (F32)(tvector.mdV[VZ])); + + tvector = gAgentCamera.getCameraPositionGlobal(); + camera_center_text = llformat("CameraCenter %f %f %f", + (F32)(tvector.mdV[VX]), (F32)(tvector.mdV[VY]), (F32)(tvector.mdV[VZ])); + + tvector = LLVector4(LLViewerCamera::getInstance()->getAtAxis()); + camera_view_text = llformat("CameraAtAxis %f %f %f", + (F32)(tvector.mdV[VX]), (F32)(tvector.mdV[VY]), (F32)(tvector.mdV[VZ])); + + addText(xpos, ypos, agent_center_text); ypos += y_inc; + addText(xpos, ypos, agent_root_center_text); ypos += y_inc; + addText(xpos, ypos, agent_view_text); ypos += y_inc; + addText(xpos, ypos, agent_left_text); ypos += y_inc; + addText(xpos, ypos, camera_center_text); ypos += y_inc; + addText(xpos, ypos, camera_view_text); ypos += y_inc; + } + + if (gDisplayWindInfo) + { + wind_vel_text = llformat("Wind velocity %.2f m/s", gWindVec.magVec()); + wind_vector_text = llformat("Wind vector %.2f %.2f %.2f", gWindVec.mV[0], gWindVec.mV[1], gWindVec.mV[2]); + rwind_vel_text = llformat("RWind vel %.2f m/s", gRelativeWindVec.magVec()); + rwind_vector_text = llformat("RWind vec %.2f %.2f %.2f", gRelativeWindVec.mV[0], gRelativeWindVec.mV[1], gRelativeWindVec.mV[2]); + + addText(xpos, ypos, wind_vel_text); ypos += y_inc; + addText(xpos, ypos, wind_vector_text); ypos += y_inc; + addText(xpos, ypos, rwind_vel_text); ypos += y_inc; + addText(xpos, ypos, rwind_vector_text); ypos += y_inc; + } + if (gDisplayWindInfo) + { + audio_text = llformat("Audio for wind: %d", gAudiop ? gAudiop->isWindEnabled() : -1); + addText(xpos, ypos, audio_text); ypos += y_inc; + } + if (gDisplayFOV) + { + addText(xpos, ypos, llformat("FOV: %2.1f deg", RAD_TO_DEG * LLViewerCamera::getInstance()->getView())); + ypos += y_inc; + } + if (gDisplayBadge) + { + addText(xpos, ypos+(y_inc/2), llformat("Hippos!", RAD_TO_DEG * LLViewerCamera::getInstance()->getView())); + ypos += y_inc * 2; + } + + /*if (LLViewerJoystick::getInstance()->getOverrideCamera()) + { + addText(xpos + 200, ypos, llformat("Flycam")); + ypos += y_inc; + }*/ + + if (gSavedSettings.getBOOL("DebugShowRenderInfo")) + { + LLTrace::Recording& last_frame_recording = LLTrace::get_frame_recording().getLastRecording(); + + //show streaming cost/triangle count of known prims in current region OR selection + { + F32 cost = 0.f; + S32 count = 0; + S32 vcount = 0; + S32 object_count = 0; + S32 total_bytes = 0; + S32 visible_bytes = 0; + + const char* label = "Region"; + if (LLSelectMgr::getInstance()->getSelection()->getObjectCount() == 0) + { //region + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + for (U32 i = 0; i < gObjectList.getNumObjects(); ++i) + { + LLViewerObject* object = gObjectList.getObject(i); + if (object && + object->getRegion() == region && + object->getVolume()) + { + object_count++; + S32 bytes = 0; + S32 visible = 0; + cost += object->getStreamingCost(); + LLMeshCostData costs; + if (object->getCostData(costs)) + { + bytes = costs.getSizeTotal(); + visible = costs.getSizeByLOD(object->getLOD()); + } + + S32 vt = 0; + count += object->getTriangleCount(&vt); + vcount += vt; + total_bytes += bytes; + visible_bytes += visible; + } + } + } + } + else + { + label = "Selection"; + cost = LLSelectMgr::getInstance()->getSelection()->getSelectedObjectStreamingCost(&total_bytes, &visible_bytes); + count = LLSelectMgr::getInstance()->getSelection()->getSelectedObjectTriangleCount(&vcount); + object_count = LLSelectMgr::getInstance()->getSelection()->getObjectCount(); + } + + addText(xpos,ypos, llformat("%s streaming cost: %.1f", label, cost)); + ypos += y_inc; + + addText(xpos, ypos, llformat(" %.3f KTris, %.3f KVerts, %.1f/%.1f KB, %d objects", + count/1000.f, vcount/1000.f, visible_bytes/1024.f, total_bytes/1024.f, object_count)); + ypos += y_inc; + + } + + addText(xpos, ypos, llformat("%d Texture Binds", LLImageGL::sBindCount)); + ypos += y_inc; + + addText(xpos, ypos, llformat("%d Unique Textures", LLImageGL::sUniqueCount)); + ypos += y_inc; + + addText(xpos, ypos, llformat("%d Render Calls", (U32)last_frame_recording.getSampleCount(LLPipeline::sStatBatchSize))); + ypos += y_inc; + + addText(xpos, ypos, llformat("%d/%d Objects Active", gObjectList.getNumActiveObjects(), gObjectList.getNumObjects())); + ypos += y_inc; + + addText(xpos, ypos, llformat("%d Matrix Ops", gPipeline.mMatrixOpCount)); + ypos += y_inc; + + addText(xpos, ypos, llformat("%d Texture Matrix Ops", gPipeline.mTextureMatrixOps)); + ypos += y_inc; + + gPipeline.mTextureMatrixOps = 0; + gPipeline.mMatrixOpCount = 0; + + if (last_frame_recording.getSampleCount(LLPipeline::sStatBatchSize) > 0) + { + addText(xpos, ypos, llformat("Batch min/max/mean: %d/%d/%d", (U32)last_frame_recording.getMin(LLPipeline::sStatBatchSize), (U32)last_frame_recording.getMax(LLPipeline::sStatBatchSize), (U32)last_frame_recording.getMean(LLPipeline::sStatBatchSize))); + } + ypos += y_inc; + + addText(xpos, ypos, llformat("UI Verts/Calls: %d/%d", LLRender::sUIVerts, LLRender::sUICalls)); + LLRender::sUICalls = LLRender::sUIVerts = 0; + ypos += y_inc; + + addText(xpos,ypos, llformat("%d/%d Nodes visible", gPipeline.mNumVisibleNodes, LLSpatialGroup::sNodeCount)); + + ypos += y_inc; + + if (!LLOcclusionCullingGroup::sPendingQueries.empty()) + { + addText(xpos,ypos, llformat("%d Queries pending", LLOcclusionCullingGroup::sPendingQueries.size())); + ypos += y_inc; + } + + + addText(xpos,ypos, llformat("%d Avatars visible", LLVOAvatar::sNumVisibleAvatars)); + + ypos += y_inc; + + addText(xpos,ypos, llformat("%d Lights visible", LLPipeline::sVisibleLightCount)); + + ypos += y_inc; + + if (gMeshRepo.meshRezEnabled()) + { + addText(xpos, ypos, llformat("%.3f MB Mesh Data Received", LLMeshRepository::sBytesReceived/(1024.f*1024.f))); + + ypos += y_inc; + + addText(xpos, ypos, llformat("%d/%d Mesh HTTP Requests/Retries", LLMeshRepository::sHTTPRequestCount, + LLMeshRepository::sHTTPRetryCount)); + ypos += y_inc; + + addText(xpos, ypos, llformat("%d/%d Mesh LOD Pending/Processing", LLMeshRepository::sLODPending, LLMeshRepository::sLODProcessing)); + ypos += y_inc; + + addText(xpos, ypos, llformat("%.3f/%.3f MB Mesh Cache Read/Write ", LLMeshRepository::sCacheBytesRead/(1024.f*1024.f), LLMeshRepository::sCacheBytesWritten/(1024.f*1024.f))); + ypos += y_inc; + + addText(xpos, ypos, llformat("%.3f/%.3f MB Mesh Skins/Decompositions Memory", LLMeshRepository::sCacheBytesSkins / (1024.f*1024.f), LLMeshRepository::sCacheBytesDecomps / (1024.f*1024.f))); + ypos += y_inc; + + addText(xpos, ypos, llformat("%.3f MB Mesh Headers Memory", LLMeshRepository::sCacheBytesHeaders / (1024.f*1024.f))); + + ypos += y_inc; + } + + gPipeline.mNumVisibleNodes = LLPipeline::sVisibleLightCount = 0; + } + if (gSavedSettings.getBOOL("DebugShowAvatarRenderInfo")) + { + std::map sorted_avs; + + std::vector::iterator sort_iter = LLCharacter::sInstances.begin(); + while (sort_iter != LLCharacter::sInstances.end()) + { + LLVOAvatar* avatar = dynamic_cast(*sort_iter); + if (avatar && + !avatar->isDead()) // Not dead yet + { + // Stuff into a sorted map so the display is ordered + sorted_avs[avatar->getFullname()] = avatar; + } + sort_iter++; + } + + std::string trunc_name; + std::map::reverse_iterator av_iter = sorted_avs.rbegin(); // Put "A" at the top + while (av_iter != sorted_avs.rend()) + { + LLVOAvatar* avatar = av_iter->second; + + avatar->calculateUpdateRenderComplexity(); // Make sure the numbers are up-to-date + + trunc_name = utf8str_truncate(avatar->getFullname(), 16); + addText(xpos, ypos, llformat("%s : %s, complexity %d, area %.2f", + trunc_name.c_str(), + LLVOAvatar::rezStatusToString(avatar->getRezzedStatus()).c_str(), + avatar->getVisualComplexity(), + avatar->getAttachmentSurfaceArea())); + ypos += y_inc; + av_iter++; + } + } + if (gSavedSettings.getBOOL("DebugShowRenderMatrices")) + { + char camera_lines[8][32]; + memset(camera_lines, ' ', sizeof(camera_lines)); + + // Projection last column is always <0,0,-1.0001,0> + // Projection last row is always <0,0,-0.2> + mBackRectCamera1.mBottom = ypos - y_inc + 2; + MATRIX_ROW_N32_TO_STR(gGLProjection, 12,camera_lines[7]); addText(xpos, ypos, std::string(camera_lines[7])); ypos += y_inc; + MATRIX_ROW_N32_TO_STR(gGLProjection, 8,camera_lines[6]); addText(xpos, ypos, std::string(camera_lines[6])); ypos += y_inc; + MATRIX_ROW_N32_TO_STR(gGLProjection, 4,camera_lines[5]); addText(xpos, ypos, std::string(camera_lines[5])); ypos += y_inc; mBackRectCamera1.mTop = ypos + 2; + MATRIX_ROW_N32_TO_STR(gGLProjection, 0,camera_lines[4]); addText(xpos, ypos, std::string(camera_lines[4])); ypos += y_inc; mBackRectCamera2.mBottom = ypos + 2; + + addText(xpos, ypos, "Projection Matrix"); + ypos += y_inc; + + // View last column is always <0,0,0,1> + MATRIX_ROW_F32_TO_STR(gGLModelView, 12,camera_lines[3]); addText(xpos, ypos, std::string(camera_lines[3])); ypos += y_inc; + MATRIX_ROW_N32_TO_STR(gGLModelView, 8,camera_lines[2]); addText(xpos, ypos, std::string(camera_lines[2])); ypos += y_inc; + MATRIX_ROW_N32_TO_STR(gGLModelView, 4,camera_lines[1]); addText(xpos, ypos, std::string(camera_lines[1])); ypos += y_inc; mBackRectCamera2.mTop = ypos + 2; + MATRIX_ROW_N32_TO_STR(gGLModelView, 0,camera_lines[0]); addText(xpos, ypos, std::string(camera_lines[0])); ypos += y_inc; + + addText(xpos, ypos, "View Matrix"); + ypos += y_inc; + } + // disable use of glReadPixels which messes up nVidia nSight graphics debugging + if (gSavedSettings.getBOOL("DebugShowColor") && !LLRender::sNsightDebugSupport) + { + U8 color[4]; + LLCoordGL coord = gViewerWindow->getCurrentMouse(); + + // Convert x,y to raw pixel coords + S32 x_raw = llround(coord.mX * gViewerWindow->getWindowWidthRaw() / (F32) gViewerWindow->getWindowWidthScaled()); + S32 y_raw = llround(coord.mY * gViewerWindow->getWindowHeightRaw() / (F32) gViewerWindow->getWindowHeightScaled()); + + glReadPixels(x_raw, y_raw, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, color); + addText(xpos, ypos, llformat("Pixel <%1d, %1d> R:%1d G:%1d B:%1d A:%1d", x_raw, y_raw, color[0], color[1], color[2], color[3])); + ypos += y_inc; + } + + // only display these messages if we are actually rendering beacons at this moment + if (LLPipeline::getRenderBeacons() && LLFloaterReg::instanceVisible("beacons")) + { + if (LLPipeline::getRenderMOAPBeacons()) + { + addText(xpos, ypos, "Viewing media beacons (white)"); + ypos += y_inc; + } + + if (LLPipeline::toggleRenderTypeControlNegated(LLPipeline::RENDER_TYPE_PARTICLES)) + { + addText(xpos, ypos, particle_hiding); + ypos += y_inc; + } + + if (LLPipeline::getRenderParticleBeacons()) + { + addText(xpos, ypos, "Viewing particle beacons (blue)"); + ypos += y_inc; + } + + if (LLPipeline::getRenderSoundBeacons()) + { + addText(xpos, ypos, "Viewing sound beacons (yellow)"); + ypos += y_inc; + } + + if (LLPipeline::getRenderScriptedBeacons()) + { + addText(xpos, ypos, beacon_scripted); + ypos += y_inc; + } + else + if (LLPipeline::getRenderScriptedTouchBeacons()) + { + addText(xpos, ypos, beacon_scripted_touch); + ypos += y_inc; + } + + if (LLPipeline::getRenderPhysicalBeacons()) + { + addText(xpos, ypos, "Viewing physical object beacons (green)"); + ypos += y_inc; + } + } + + static LLUICachedControl show_sun_beacon("sunbeacon", false); + static LLUICachedControl show_moon_beacon("moonbeacon", false); + + if (show_sun_beacon) + { + addText(xpos, ypos, beacon_sun); + ypos += y_inc; + } + if (show_moon_beacon) + { + addText(xpos, ypos, beacon_moon); + ypos += y_inc; + } + + if(log_texture_traffic) + { + U32 old_y = ypos ; + for(S32 i = LLViewerTexture::BOOST_NONE; i < LLViewerTexture::MAX_GL_IMAGE_CATEGORY; i++) + { + if(gTotalTextureBytesPerBoostLevel[i] > (S32Bytes)0) + { + addText(xpos, ypos, llformat("Boost_Level %d: %.3f MB", i, F32Megabytes(gTotalTextureBytesPerBoostLevel[i]).value())); + ypos += y_inc; + } + } + if(ypos != old_y) + { + addText(xpos, ypos, "Network traffic for textures:"); + ypos += y_inc; + } + } + + if (gSavedSettings.getBOOL("DebugShowTextureInfo")) + { + LLViewerObject* objectp = NULL ; + + LLSelectNode* nodep = LLSelectMgr::instance().getHoverNode(); + if (nodep) + { + objectp = nodep->getObject(); + } + + if (objectp && !objectp->isDead()) + { + S32 num_faces = objectp->mDrawable->getNumFaces() ; + std::set tex_list; + + for(S32 i = 0 ; i < num_faces; i++) + { + LLFace* facep = objectp->mDrawable->getFace(i) ; + if(facep) + { + LLViewerFetchedTexture* tex = dynamic_cast(facep->getTexture()) ; + if(tex) + { + if(tex_list.find(tex) != tex_list.end()) + { + continue ; //already displayed. + } + tex_list.insert(tex); + + std::string uuid_str; + tex->getID().toString(uuid_str); + uuid_str = uuid_str.substr(0,7); + + addText(xpos, ypos, llformat("ID: %s v_size: %.3f", uuid_str.c_str(), tex->getMaxVirtualSize())); + ypos += y_inc; + + addText(xpos, ypos, llformat("discard level: %d desired level: %d Missing: %s", tex->getDiscardLevel(), + tex->getDesiredDiscardLevel(), tex->isMissingAsset() ? "Y" : "N")); + ypos += y_inc; + } + } + } + } + } + } + + void draw() + { + LL_RECORD_BLOCK_TIME(FTM_DISPLAY_DEBUG_TEXT); + + // Camera matrix text is hard to see again a white background + // Add a dark background underneath the matrices for readability (contrast) + if (mBackRectCamera1.mTop >= 0) + { + mBackColor.setAlpha( 0.75f ); + gl_rect_2d(mBackRectCamera1, mBackColor, true); + + mBackColor.setAlpha( 0.66f ); + gl_rect_2d(mBackRectCamera2, mBackColor, true); + } + + for (line_list_t::iterator iter = mLineList.begin(); + iter != mLineList.end(); ++iter) + { + const Line& line = *iter; + LLFontGL::getFontMonospace()->renderUTF8(line.text, 0, (F32)line.x, (F32)line.y, mTextColor, + LLFontGL::LEFT, LLFontGL::TOP, LLFontGL::NORMAL, LLFontGL::NO_SHADOW); + } + } + +}; + +void LLViewerWindow::updateDebugText() +{ + mDebugText->update(); +} + +//////////////////////////////////////////////////////////////////////////// +// +// LLViewerWindow +// + +LLViewerWindow::Params::Params() +: title("title"), + name("name"), + x("x"), + y("y"), + width("width"), + height("height"), + min_width("min_width"), + min_height("min_height"), + fullscreen("fullscreen", false), + ignore_pixel_depth("ignore_pixel_depth", false) +{} + + +void LLViewerWindow::handlePieMenu(S32 x, S32 y, MASK mask) +{ + if (CAMERA_MODE_CUSTOMIZE_AVATAR != gAgentCamera.getCameraMode() && LLToolMgr::getInstance()->getCurrentTool() != LLToolPie::getInstance() && gAgent.isInitialized()) + { + // If the current tool didn't process the click, we should show + // the pie menu. This can be done by passing the event to the pie + // menu tool. + LLToolPie::getInstance()->handleRightMouseDown(x, y, mask); + } +} + +bool LLViewerWindow::handleAnyMouseClick(LLWindow *window, LLCoordGL pos, MASK mask, EMouseClickType clicktype, bool down, bool& is_toolmgr_action) +{ + const char* buttonname = ""; + const char* buttonstatestr = ""; + S32 x = pos.mX; + S32 y = pos.mY; + x = ll_round((F32)x / mDisplayScale.mV[VX]); + y = ll_round((F32)y / mDisplayScale.mV[VY]); + + // Handle non-consuming global keybindings, like voice + gViewerInput.handleGlobalBindsMouse(clicktype, mask, down); + + // only send mouse clicks to UI if UI is visible + if(gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) + { + + if (down) + { + buttonstatestr = "down" ; + } + else + { + buttonstatestr = "up" ; + } + + switch (clicktype) + { + case CLICK_LEFT: + mLeftMouseDown = down; + buttonname = "Left"; + break; + case CLICK_RIGHT: + mRightMouseDown = down; + buttonname = "Right"; + break; + case CLICK_MIDDLE: + mMiddleMouseDown = down; + buttonname = "Middle"; + break; + case CLICK_DOUBLELEFT: + mLeftMouseDown = down; + buttonname = "Left Double Click"; + break; + case CLICK_BUTTON4: + buttonname = "Button 4"; + break; + case CLICK_BUTTON5: + buttonname = "Button 5"; + break; + default: + break; // COUNT and NONE + } + + LLView::sMouseHandlerMessage.clear(); + + if (gMenuBarView) + { + // stop ALT-key access to menu + gMenuBarView->resetMenuTrigger(); + } + + if (gDebugClicks) + { + LL_INFOS() << "ViewerWindow " << buttonname << " mouse " << buttonstatestr << " at " << x << "," << y << LL_ENDL; + } + + // Make sure we get a corresponding mouseup event, even if the mouse leaves the window + if (down) + mWindow->captureMouse(); + else + mWindow->releaseMouse(); + + // Indicate mouse was active + LLUI::getInstance()->resetMouseIdleTimer(); + + // Don't let the user move the mouse out of the window until mouse up. + if( LLToolMgr::getInstance()->getCurrentTool()->clipMouseWhenDown() ) + { + mWindow->setMouseClipping(down); + } + + LLMouseHandler* mouse_captor = gFocusMgr.getMouseCapture(); + if( mouse_captor ) + { + S32 local_x; + S32 local_y; + mouse_captor->screenPointToLocal( x, y, &local_x, &local_y ); + if (LLView::sDebugMouseHandling) + { + LL_INFOS() << buttonname << " Mouse " << buttonstatestr << " handled by captor " << mouse_captor->getName() << LL_ENDL; + } + + bool r = mouse_captor->handleAnyMouseClick(local_x, local_y, mask, clicktype, down); + if (r) { + + LL_DEBUGS() << "LLViewerWindow::handleAnyMouseClick viewer with mousecaptor calling updatemouseeventinfo - local_x|global x "<< local_x << " " << x << "local/global y " << local_y << " " << y << LL_ENDL; + + LLViewerEventRecorder::instance().setMouseGlobalCoords(x,y); + LLViewerEventRecorder::instance().logMouseEvent(std::string(buttonstatestr),std::string(buttonname)); + + } + else if (down && clicktype == CLICK_RIGHT) + { + handlePieMenu(x, y, mask); + r = true; + } + return r; + } + + // Mark the click as handled and return if we aren't within the root view to avoid spurious bugs + if( !mRootView->pointInView(x, y) ) + { + return true; + } + // Give the UI views a chance to process the click + + bool r= mRootView->handleAnyMouseClick(x, y, mask, clicktype, down) ; + if (r) + { + + LL_DEBUGS() << "LLViewerWindow::handleAnyMouseClick calling updatemouseeventinfo - global x "<< " " << x << "global y " << y << "buttonstate: " << buttonstatestr << " buttonname " << buttonname << LL_ENDL; + + LLViewerEventRecorder::instance().setMouseGlobalCoords(x,y); + + // Clear local coords - this was a click on root window so these are not needed + // By not including them, this allows the test skeleton generation tool to be smarter when generating code + // the code generator can be smarter because when local coords are present it can try the xui path with local coords + // and fallback to global coordinates only if needed. + // The drawback to this approach is sometimes a valid xui path will appear to work fine, but NOT interact with the UI element + // (VITA support not implemented yet or not visible to VITA due to widget further up xui path not being visible to VITA) + // For this reason it's best to provide hints where possible here by leaving out local coordinates + LLViewerEventRecorder::instance().setMouseLocalCoords(-1,-1); + LLViewerEventRecorder::instance().logMouseEvent(buttonstatestr,buttonname); + + if (LLView::sDebugMouseHandling) + { + LL_INFOS() << buttonname << " Mouse " << buttonstatestr << " " << LLViewerEventRecorder::instance().get_xui() << LL_ENDL; + } + return true; + } else if (LLView::sDebugMouseHandling) + { + LL_INFOS() << buttonname << " Mouse " << buttonstatestr << " not handled by view" << LL_ENDL; + } + } + + // Do not allow tool manager to handle mouseclicks if we have disconnected + if(!gDisconnected && LLToolMgr::getInstance()->getCurrentTool()->handleAnyMouseClick( x, y, mask, clicktype, down ) ) + { + LLViewerEventRecorder::instance().clear_xui(); + is_toolmgr_action = true; + return true; + } + + if (down && clicktype == CLICK_RIGHT) + { + handlePieMenu(x, y, mask); + return true; + } + + // If we got this far on a down-click, it wasn't handled. + // Up-clicks, though, are always handled as far as the OS is concerned. + bool default_rtn = !down; + return default_rtn; +} + +bool LLViewerWindow::handleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask) +{ + mAllowMouseDragging = false; + if (!mMouseDownTimer.getStarted()) + { + mMouseDownTimer.start(); + } + else + { + mMouseDownTimer.reset(); + } + bool down = true; + //handleMouse() loops back to LLViewerWindow::handleAnyMouseClick + return gViewerInput.handleMouse(window, pos, mask, CLICK_LEFT, down); +} + +bool LLViewerWindow::handleDoubleClick(LLWindow *window, LLCoordGL pos, MASK mask) +{ + // try handling as a double-click first, then a single-click if that + // wasn't handled. + bool down = true; + if (gViewerInput.handleMouse(window, pos, mask, CLICK_DOUBLELEFT, down)) + { + return true; + } + return handleMouseDown(window, pos, mask); +} + +bool LLViewerWindow::handleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask) +{ + if (mMouseDownTimer.getStarted()) + { + mMouseDownTimer.stop(); + } + bool down = false; + return gViewerInput.handleMouse(window, pos, mask, CLICK_LEFT, down); +} +bool LLViewerWindow::handleRightMouseDown(LLWindow *window, LLCoordGL pos, MASK mask) +{ + bool down = true; + return gViewerInput.handleMouse(window, pos, mask, CLICK_RIGHT, down); +} + +bool LLViewerWindow::handleRightMouseUp(LLWindow *window, LLCoordGL pos, MASK mask) +{ + bool down = false; + return gViewerInput.handleMouse(window, pos, mask, CLICK_RIGHT, down); +} + +bool LLViewerWindow::handleMiddleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask) +{ + bool down = true; + gViewerInput.handleMouse(window, pos, mask, CLICK_MIDDLE, down); + + // Always handled as far as the OS is concerned. + return true; +} + +LLWindowCallbacks::DragNDropResult LLViewerWindow::handleDragNDrop( LLWindow *window, LLCoordGL pos, MASK mask, LLWindowCallbacks::DragNDropAction action, std::string data) +{ + LLWindowCallbacks::DragNDropResult result = LLWindowCallbacks::DND_NONE; + + const bool prim_media_dnd_enabled = gSavedSettings.getBOOL("PrimMediaDragNDrop"); + const bool slurl_dnd_enabled = gSavedSettings.getBOOL("SLURLDragNDrop"); + + if ( prim_media_dnd_enabled || slurl_dnd_enabled ) + { + switch(action) + { + // Much of the handling for these two cases is the same. + case LLWindowCallbacks::DNDA_TRACK: + case LLWindowCallbacks::DNDA_DROPPED: + case LLWindowCallbacks::DNDA_START_TRACKING: + { + bool drop = (LLWindowCallbacks::DNDA_DROPPED == action); + + if (slurl_dnd_enabled) + { + LLSLURL dropped_slurl(data); + if(dropped_slurl.isSpatial()) + { + if (drop) + { + LLURLDispatcher::dispatch( dropped_slurl.getSLURLString(), LLCommandHandler::NAV_TYPE_CLICKED, NULL, true ); + return LLWindowCallbacks::DND_MOVE; + } + return LLWindowCallbacks::DND_COPY; + } + } + + if (prim_media_dnd_enabled) + { + LLPickInfo pick_info = pickImmediate( pos.mX, pos.mY, + true /* pick_transparent */, + false /* pick_rigged */); + + S32 object_face = pick_info.mObjectFace; + std::string url = data; + + LL_DEBUGS() << "Object: picked at " << pos.mX << ", " << pos.mY << " - face = " << object_face << " - URL = " << url << LL_ENDL; + + LLVOVolume *obj = dynamic_cast(static_cast(pick_info.getObject())); + + if (obj && !obj->getRegion()->getCapability("ObjectMedia").empty()) + { + LLTextureEntry *te = obj->getTE(object_face); + + // can modify URL if we can modify the object or we have navigate permissions + bool allow_modify_url = obj->permModify() || obj->hasMediaPermission( te->getMediaData(), LLVOVolume::MEDIA_PERM_INTERACT ); + + if (te && allow_modify_url ) + { + if (drop) + { + // object does NOT have media already + if ( ! te->hasMedia() ) + { + // we are allowed to modify the object + if ( obj->permModify() ) + { + // Create new media entry + LLSD media_data; + // XXX Should we really do Home URL too? + media_data[LLMediaEntry::HOME_URL_KEY] = url; + media_data[LLMediaEntry::CURRENT_URL_KEY] = url; + media_data[LLMediaEntry::AUTO_PLAY_KEY] = true; + obj->syncMediaData(object_face, media_data, true, true); + // XXX This shouldn't be necessary, should it ?!? + if (obj->getMediaImpl(object_face)) + obj->getMediaImpl(object_face)->navigateReload(); + obj->sendMediaDataUpdate(); + + result = LLWindowCallbacks::DND_COPY; + } + } + else + // object HAS media already + { + // URL passes the whitelist + if (te->getMediaData()->checkCandidateUrl( url ) ) + { + // just navigate to the URL + if (obj->getMediaImpl(object_face)) + { + obj->getMediaImpl(object_face)->navigateTo(url); + } + else + { + // This is very strange. Navigation should + // happen via the Impl, but we don't have one. + // This sends it to the server, which /should/ + // trigger us getting it. Hopefully. + LLSD media_data; + media_data[LLMediaEntry::CURRENT_URL_KEY] = url; + obj->syncMediaData(object_face, media_data, true, true); + obj->sendMediaDataUpdate(); + } + result = LLWindowCallbacks::DND_LINK; + + } + } + LLSelectMgr::getInstance()->unhighlightObjectOnly(mDragHoveredObject); + mDragHoveredObject = NULL; + + } + else + { + // Check the whitelist, if there's media (otherwise just show it) + if (te->getMediaData() == NULL || te->getMediaData()->checkCandidateUrl(url)) + { + if ( obj != mDragHoveredObject) + { + // Highlight the dragged object + LLSelectMgr::getInstance()->unhighlightObjectOnly(mDragHoveredObject); + mDragHoveredObject = obj; + LLSelectMgr::getInstance()->highlightObjectOnly(mDragHoveredObject); + } + result = (! te->hasMedia()) ? LLWindowCallbacks::DND_COPY : LLWindowCallbacks::DND_LINK; + + } + } + } + } + } + } + break; + + case LLWindowCallbacks::DNDA_STOP_TRACKING: + // The cleanup case below will make sure things are unhilighted if necessary. + break; + } + + if (prim_media_dnd_enabled && + result == LLWindowCallbacks::DND_NONE && !mDragHoveredObject.isNull()) + { + LLSelectMgr::getInstance()->unhighlightObjectOnly(mDragHoveredObject); + mDragHoveredObject = NULL; + } + } + + return result; +} + +bool LLViewerWindow::handleMiddleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask) +{ + bool down = false; + gViewerInput.handleMouse(window, pos, mask, CLICK_MIDDLE, down); + + // Always handled as far as the OS is concerned. + return true; +} + +bool LLViewerWindow::handleOtherMouse(LLWindow *window, LLCoordGL pos, MASK mask, S32 button, bool down) +{ + switch (button) + { + case 4: + gViewerInput.handleMouse(window, pos, mask, CLICK_BUTTON4, down); + break; + case 5: + gViewerInput.handleMouse(window, pos, mask, CLICK_BUTTON5, down); + break; + default: + break; + } + + // Always handled as far as the OS is concerned. + return true; +} + +bool LLViewerWindow::handleOtherMouseDown(LLWindow *window, LLCoordGL pos, MASK mask, S32 button) +{ + return handleOtherMouse(window, pos, mask, button, true); +} + +bool LLViewerWindow::handleOtherMouseUp(LLWindow *window, LLCoordGL pos, MASK mask, S32 button) +{ + return handleOtherMouse(window, pos, mask, button, false); +} + +// WARNING: this is potentially called multiple times per frame +void LLViewerWindow::handleMouseMove(LLWindow *window, LLCoordGL pos, MASK mask) +{ + S32 x = pos.mX; + S32 y = pos.mY; + + x = ll_round((F32)x / mDisplayScale.mV[VX]); + y = ll_round((F32)y / mDisplayScale.mV[VY]); + + mMouseInWindow = true; + + // Save mouse point for access during idle() and display() + + LLCoordGL mouse_point(x, y); + + if (mouse_point != mCurrentMousePoint) + { + LLUI::getInstance()->resetMouseIdleTimer(); + } + + saveLastMouse(mouse_point); + + mWindow->showCursorFromMouseMove(); + + if (gAwayTimer.getElapsedTimeF32() > LLAgent::MIN_AFK_TIME + && !gDisconnected) + { + gAgent.clearAFK(); + } +} + +void LLViewerWindow::handleMouseDragged(LLWindow *window, LLCoordGL pos, MASK mask) +{ + if (mMouseDownTimer.getStarted()) + { + if (mMouseDownTimer.getElapsedTimeF32() > 0.1) + { + mAllowMouseDragging = true; + mMouseDownTimer.stop(); + } + } + if(mAllowMouseDragging || !LLToolCamera::getInstance()->hasMouseCapture()) + { + handleMouseMove(window, pos, mask); + } +} + +void LLViewerWindow::handleMouseLeave(LLWindow *window) +{ + // Note: we won't get this if we have captured the mouse. + llassert( gFocusMgr.getMouseCapture() == NULL ); + mMouseInWindow = false; + LLToolTipMgr::instance().blockToolTips(); +} + +bool LLViewerWindow::handleCloseRequest(LLWindow *window) +{ + // User has indicated they want to close, but we may need to ask + // about modified documents. + LLAppViewer::instance()->userQuit(); + // Don't quit immediately + return false; +} + +void LLViewerWindow::handleQuit(LLWindow *window) +{ + if (gNonInteractive) + { + LLAppViewer::instance()->requestQuit(); + } + else + { + LL_INFOS() << "Window forced quit" << LL_ENDL; + LLAppViewer::instance()->forceQuit(); + } +} + +void LLViewerWindow::handleResize(LLWindow *window, S32 width, S32 height) +{ + reshape(width, height); + mResDirty = true; +} + +// The top-level window has gained focus (e.g. via ALT-TAB) +void LLViewerWindow::handleFocus(LLWindow *window) +{ + gFocusMgr.setAppHasFocus(true); + LLModalDialog::onAppFocusGained(); + + gAgent.onAppFocusGained(); + LLToolMgr::getInstance()->onAppFocusGained(); + + // See if we're coming in with modifier keys held down + if (gKeyboard) + { + gKeyboard->resetMaskKeys(); + } + + // resume foreground running timer + // since we artifically limit framerate when not frontmost + gForegroundTime.unpause(); +} + +// The top-level window has lost focus (e.g. via ALT-TAB) +void LLViewerWindow::handleFocusLost(LLWindow *window) +{ + gFocusMgr.setAppHasFocus(false); + //LLModalDialog::onAppFocusLost(); + LLToolMgr::getInstance()->onAppFocusLost(); + gFocusMgr.setMouseCapture( NULL ); + + if (gMenuBarView) + { + // stop ALT-key access to menu + gMenuBarView->resetMenuTrigger(); + } + + // restore mouse cursor + showCursor(); + getWindow()->setMouseClipping(false); + + // If losing focus while keys are down, handle them as + // an 'up' to correctly release states, then reset states + if (gKeyboard) + { + gKeyboard->resetKeyDownAndHandle(); + gKeyboard->resetKeys(); + } + + // pause timer that tracks total foreground running time + gForegroundTime.pause(); +} + + +bool LLViewerWindow::handleTranslatedKeyDown(KEY key, MASK mask, bool repeated) +{ + // Handle non-consuming global keybindings, like voice + // Never affects event processing. + gViewerInput.handleGlobalBindsKeyDown(key, mask); + + if (gAwayTimer.getElapsedTimeF32() > LLAgent::MIN_AFK_TIME) + { + gAgent.clearAFK(); + } + + // *NOTE: We want to interpret KEY_RETURN later when it arrives as + // a Unicode char, not as a keydown. Otherwise when client frame + // rate is really low, hitting return sends your chat text before + // it's all entered/processed. + if (key == KEY_RETURN && mask == MASK_NONE) + { + // RIDER: although, at times some of the controlls (in particular the CEF viewer + // would like to know about the KEYDOWN for an enter key... so ask and pass it along. + LLFocusableElement* keyboard_focus = gFocusMgr.getKeyboardFocus(); + if (keyboard_focus && !keyboard_focus->wantsReturnKey()) + return false; + } + + // remaps, handles ignored cases and returns back to viewer window. + return gViewerInput.handleKey(key, mask, repeated); +} + +bool LLViewerWindow::handleTranslatedKeyUp(KEY key, MASK mask) +{ + // Handle non-consuming global keybindings, like voice + // Never affects event processing. + gViewerInput.handleGlobalBindsKeyUp(key, mask); + + // Let the inspect tool code check for ALT key to set LLToolSelectRect active instead LLToolCamera + LLToolCompInspect * tool_inspectp = LLToolCompInspect::getInstance(); + if (LLToolMgr::getInstance()->getCurrentTool() == tool_inspectp) + { + tool_inspectp->keyUp(key, mask); + } + + return gViewerInput.handleKeyUp(key, mask); +} + +void LLViewerWindow::handleScanKey(KEY key, bool key_down, bool key_up, bool key_level) +{ + LLViewerJoystick::getInstance()->setCameraNeedsUpdate(true); + gViewerInput.scanKey(key, key_down, key_up, key_level); + return; // Be clear this function returns nothing +} + + + + +bool LLViewerWindow::handleActivate(LLWindow *window, bool activated) +{ + if (activated) + { + mActive = true; + send_agent_resume(); + gAgent.clearAFK(); + + // Unmute audio + audio_update_volume(); + } + else + { + mActive = false; + + // if the user has chosen to go Away automatically after some time, then go Away when minimizing + if (gSavedSettings.getS32("AFKTimeout")) + { + gAgent.setAFK(); + } + + // SL-53351: Make sure we're not in mouselook when minimised, to prevent control issues + if (gAgentCamera.getCameraMode() == CAMERA_MODE_MOUSELOOK) + { + gAgentCamera.changeCameraToDefault(); + } + + send_agent_pause(); + + // Mute audio + audio_update_volume(); + } + return true; +} + +bool LLViewerWindow::handleActivateApp(LLWindow *window, bool activating) +{ + //if (!activating) gAgentCamera.changeCameraToDefault(); + + LLViewerJoystick::getInstance()->setNeedsReset(true); + return false; +} + + +void LLViewerWindow::handleMenuSelect(LLWindow *window, S32 menu_item) +{ +} + + +bool LLViewerWindow::handlePaint(LLWindow *window, S32 x, S32 y, S32 width, S32 height) +{ + // *TODO: Enable similar information output for other platforms? DK 2011-02-18 +#if LL_WINDOWS + if (gHeadlessClient) + { + HWND window_handle = (HWND)window->getPlatformWindow(); + PAINTSTRUCT ps; + HDC hdc; + + RECT wnd_rect; + wnd_rect.left = 0; + wnd_rect.top = 0; + wnd_rect.bottom = 200; + wnd_rect.right = 500; + + hdc = BeginPaint(window_handle, &ps); + //SetBKColor(hdc, RGB(255, 255, 255)); + FillRect(hdc, &wnd_rect, CreateSolidBrush(RGB(255, 255, 255))); + + std::string temp_str; + LLTrace::Recording& recording = LLViewerStats::instance().getRecording(); + temp_str = llformat( "FPS %3.1f Phy FPS %2.1f Time Dil %1.3f", /* Flawfinder: ignore */ + recording.getPerSec(LLStatViewer::FPS), //mFPSStat.getMeanPerSec(), + recording.getLastValue(LLStatViewer::SIM_PHYSICS_FPS), + recording.getLastValue(LLStatViewer::SIM_TIME_DILATION)); + S32 len = temp_str.length(); + TextOutA(hdc, 0, 0, temp_str.c_str(), len); + + + LLVector3d pos_global = gAgent.getPositionGlobal(); + temp_str = llformat( "Avatar pos %6.1lf %6.1lf %6.1lf", pos_global.mdV[0], pos_global.mdV[1], pos_global.mdV[2]); + len = temp_str.length(); + TextOutA(hdc, 0, 25, temp_str.c_str(), len); + + TextOutA(hdc, 0, 50, "Set \"HeadlessClient FALSE\" in settings.ini file to reenable", 61); + EndPaint(window_handle, &ps); + return true; + } +#endif + return false; +} + + +void LLViewerWindow::handleScrollWheel(LLWindow *window, S32 clicks) +{ + handleScrollWheel( clicks ); +} + +void LLViewerWindow::handleScrollHWheel(LLWindow *window, S32 clicks) +{ + handleScrollHWheel(clicks); +} + +void LLViewerWindow::handleWindowBlock(LLWindow *window) +{ + send_agent_pause(); +} + +void LLViewerWindow::handleWindowUnblock(LLWindow *window) +{ + send_agent_resume(); +} + +void LLViewerWindow::handleDataCopy(LLWindow *window, S32 data_type, void *data) +{ + const S32 SLURL_MESSAGE_TYPE = 0; + switch (data_type) + { + case SLURL_MESSAGE_TYPE: + // received URL + std::string url = (const char*)data; + LLMediaCtrl* web = NULL; + const bool trusted_browser = false; + // don't treat slapps coming from external browsers as "clicks" as this would bypass throttling + if (LLURLDispatcher::dispatch(url, LLCommandHandler::NAV_TYPE_EXTERNAL, web, trusted_browser)) + { + // bring window to foreground, as it has just been "launched" from a URL + mWindow->bringToFront(); + } + break; + } +} + +bool LLViewerWindow::handleTimerEvent(LLWindow *window) +{ + //TODO: just call this every frame from gatherInput instead of using a convoluted 30fps timer callback + if (LLViewerJoystick::getInstance()->getOverrideCamera()) + { + LLViewerJoystick::getInstance()->updateStatus(); + return true; + } + return false; +} + +bool LLViewerWindow::handleDeviceChange(LLWindow *window) +{ + // give a chance to use a joystick after startup (hot-plugging) + if (!LLViewerJoystick::getInstance()->isJoystickInitialized() ) + { + LLViewerJoystick::getInstance()->init(true); + return true; + } + return false; +} + +bool LLViewerWindow::handleDPIChanged(LLWindow *window, F32 ui_scale_factor, S32 window_width, S32 window_height) +{ + if (ui_scale_factor >= MIN_UI_SCALE && ui_scale_factor <= MAX_UI_SCALE) + { + LLViewerWindow::reshape(window_width, window_height); + mResDirty = true; + return true; + } + else + { + LL_WARNS() << "DPI change caused UI scale to go out of bounds: " << ui_scale_factor << LL_ENDL; + return false; + } +} + +bool LLViewerWindow::handleWindowDidChangeScreen(LLWindow *window) +{ + LLCoordScreen window_rect; + mWindow->getSize(&window_rect); + reshape(window_rect.mX, window_rect.mY); + return true; +} + +void LLViewerWindow::handlePingWatchdog(LLWindow *window, const char * msg) +{ + LLAppViewer::instance()->pingMainloopTimeout(msg); +} + + +void LLViewerWindow::handleResumeWatchdog(LLWindow *window) +{ + LLAppViewer::instance()->resumeMainloopTimeout(); +} + +void LLViewerWindow::handlePauseWatchdog(LLWindow *window) +{ + LLAppViewer::instance()->pauseMainloopTimeout(); +} + +//virtual +std::string LLViewerWindow::translateString(const char* tag) +{ + return LLTrans::getString( std::string(tag) ); +} + +//virtual +std::string LLViewerWindow::translateString(const char* tag, + const std::map& args) +{ + // LLTrans uses a special subclass of std::string for format maps, + // but we must use std::map<> in these callbacks, otherwise we create + // a dependency between LLWindow and LLFormatMapString. So copy the data. + LLStringUtil::format_map_t args_copy; + std::map::const_iterator it = args.begin(); + for ( ; it != args.end(); ++it) + { + args_copy[it->first] = it->second; + } + return LLTrans::getString( std::string(tag), args_copy); +} + +// +// Classes +// +LLViewerWindow::LLViewerWindow(const Params& p) +: mWindow(NULL), + mActive(true), + mUIVisible(true), + mWindowRectRaw(0, p.height, p.width, 0), + mWindowRectScaled(0, p.height, p.width, 0), + mWorldViewRectRaw(0, p.height, p.width, 0), + mLeftMouseDown(false), + mMiddleMouseDown(false), + mRightMouseDown(false), + mMouseInWindow( false ), + mAllowMouseDragging(true), + mMouseDownTimer(), + mLastMask( MASK_NONE ), + mToolStored( NULL ), + mHideCursorPermanent( false ), + mCursorHidden(false), + mIgnoreActivate( false ), + mResDirty(false), + mStatesDirty(false), + mCurrResolutionIndex(0), + mProgressView(NULL) +{ + // gKeyboard is still NULL, so it doesn't do LLWindowListener any good to + // pass its value right now. Instead, pass it a nullary function that + // will, when we later need it, return the value of gKeyboard. + // boost::lambda::var() constructs such a functor on the fly. + mWindowListener.reset(new LLWindowListener(this, boost::lambda::var(gKeyboard))); + mViewerWindowListener.reset(new LLViewerWindowListener(this)); + + mSystemChannel.reset(new LLNotificationChannel("System", "Visible", LLNotificationFilters::includeEverything)); + mCommunicationChannel.reset(new LLCommunicationChannel("Communication", "Visible")); + mAlertsChannel.reset(new LLNotificationsUI::LLViewerAlertHandler("VW_alerts", "alert")); + mModalAlertsChannel.reset(new LLNotificationsUI::LLViewerAlertHandler("VW_alertmodal", "alertmodal")); + + bool ignore = gSavedSettings.getBOOL("IgnoreAllNotifications"); + LLNotifications::instance().setIgnoreAllNotifications(ignore); + if (ignore) + { + LL_INFOS() << "NOTE: ALL NOTIFICATIONS THAT OCCUR WILL GET ADDED TO IGNORE LIST FOR LATER RUNS." << LL_ENDL; + } + + + /* + LLWindowCallbacks* callbacks, + const std::string& title, const std::string& name, S32 x, S32 y, S32 width, S32 height, U32 flags, + bool fullscreen, + bool clearBg, + bool disable_vsync, + bool ignore_pixel_depth, + U32 fsaa_samples) + */ + // create window + + U32 max_core_count = gSavedSettings.getU32("EmulateCoreCount"); + F32 max_gl_version = gSavedSettings.getF32("RenderMaxOpenGLVersion"); + + LLControlVariable* vram_control = gSavedSettings.getControl("RenderMaxVRAMBudget"); + U32 max_vram = vram_control->getValue().asInteger(); + mMaxVRAMControlConnection = vram_control->getSignal()->connect( + [this](LLControlVariable* control, const LLSD& new_val, const LLSD& old_val) + { + if (mWindow) mWindow->setMaxVRAMMegabytes(new_val.asInteger()); + }); + + + mWindow = LLWindowManager::createWindow(this, + p.title, p.name, p.x, p.y, p.width, p.height, 0, + p.fullscreen, + gHeadlessClient, + gSavedSettings.getBOOL("RenderVSyncEnable"), + !gHeadlessClient, + p.ignore_pixel_depth, + 0, + max_core_count, + max_vram, + max_gl_version); //don't use window level anti-aliasing + + if (NULL == mWindow) + { + LLSplashScreen::update(LLTrans::getString("StartupRequireDriverUpdate")); + + LL_WARNS("Window") << "Failed to create window, to be shutting Down, be sure your graphics driver is updated." << LL_ENDL ; + + ms_sleep(5000) ; //wait for 5 seconds. + + LLSplashScreen::update(LLTrans::getString("ShuttingDown")); +#if LL_LINUX + LL_WARNS() << "Unable to create window, be sure screen is set at 32-bit color and your graphics driver is configured correctly. See README-linux.txt for further information." + << LL_ENDL; +#else + LL_WARNS("Window") << "Unable to create window, be sure screen is set at 32-bit color in Control Panels->Display->Settings" + << LL_ENDL; +#endif + LLAppViewer::instance()->fastQuit(1); + } + else if (!LLViewerShaderMgr::sInitialized) + { + //immediately initialize shaders + LLViewerShaderMgr::sInitialized = true; + LLViewerShaderMgr::instance()->setShaders(); + } + + if (!LLAppViewer::instance()->restoreErrorTrap()) + { + // this always happens, so downgrading it to INFO + LL_INFOS("Window") << " Someone took over my signal/exception handler (post createWindow; normal)" << LL_ENDL; + } + + const bool do_not_enforce = false; + mWindow->setMinSize(p.min_width, p.min_height, do_not_enforce); // root view not set + LLCoordScreen scr; + mWindow->getSize(&scr); + + // Reset UI scale factor on first run if OS's display scaling is not 100% + if (gSavedSettings.getBOOL("ResetUIScaleOnFirstRun")) + { + if (mWindow->getSystemUISize() != 1.f) + { + gSavedSettings.setF32("UIScaleFactor", 1.f); + } + gSavedSettings.setBOOL("ResetUIScaleOnFirstRun", false); + } + + // Get the real window rect the window was created with (since there are various OS-dependent reasons why + // the size of a window or fullscreen context may have been adjusted slightly...) + F32 ui_scale_factor = llclamp(gSavedSettings.getF32("UIScaleFactor") * mWindow->getSystemUISize(), MIN_UI_SCALE, MAX_UI_SCALE); + + mDisplayScale.setVec(llmax(1.f / mWindow->getPixelAspectRatio(), 1.f), llmax(mWindow->getPixelAspectRatio(), 1.f)); + mDisplayScale *= ui_scale_factor; + LLUI::setScaleFactor(mDisplayScale); + + { + LLCoordWindow size; + mWindow->getSize(&size); + mWindowRectRaw.set(0, size.mY, size.mX, 0); + mWindowRectScaled.set(0, ll_round((F32)size.mY / mDisplayScale.mV[VY]), ll_round((F32)size.mX / mDisplayScale.mV[VX]), 0); + } + + LLFontManager::initClass(); + // Init font system, load default fonts and generate basic glyphs + // currently it takes aprox. 0.5 sec and we would load these fonts anyway + // before login screen. + LLFontGL::initClass( gSavedSettings.getF32("FontScreenDPI"), + mDisplayScale.mV[VX], + mDisplayScale.mV[VY], + gDirUtilp->getAppRODataDir()); + + // + // We want to set this stuff up BEFORE we initialize the pipeline, so we can turn off + // stuff like AGP if we think that it'll crash the viewer. + // + LL_DEBUGS("Window") << "Loading feature tables." << LL_ENDL; + + // Initialize OpenGL Renderer + LLVertexBuffer::initClass(mWindow); + LL_INFOS("RenderInit") << "LLVertexBuffer initialization done." << LL_ENDL ; + if (!gGL.init(true)) + { + LLError::LLUserWarningMsg::show(LLTrans::getString("MBVideoDrvErr")); + LL_ERRS() << "gGL not initialized" << LL_ENDL; + } + + if (LLFeatureManager::getInstance()->isSafe() + || (gSavedSettings.getS32("LastFeatureVersion") != LLFeatureManager::getInstance()->getVersion()) + || (gSavedSettings.getString("LastGPUString") != LLFeatureManager::getInstance()->getGPUString()) + || (gSavedSettings.getBOOL("ProbeHardwareOnStartup"))) + { + LLFeatureManager::getInstance()->applyRecommendedSettings(); + gSavedSettings.setBOOL("ProbeHardwareOnStartup", false); + } + + // If we crashed while initializng GL stuff last time, disable certain features + if (gSavedSettings.getBOOL("RenderInitError")) + { + mInitAlert = "DisplaySettingsNoShaders"; + LLFeatureManager::getInstance()->setGraphicsLevel(0, false); + gSavedSettings.setU32("RenderQualityPerformance", 0); + } + + // Init the image list. Must happen after GL is initialized and before the images that + // LLViewerWindow needs are requested, as well as before LLViewerMedia starts updating images. + LLImageGL::initClass(mWindow, LLViewerTexture::MAX_GL_IMAGE_CATEGORY, false, gSavedSettings.getBOOL("RenderGLMultiThreadedTextures"), gSavedSettings.getBOOL("RenderGLMultiThreadedMedia")); + gTextureList.init(); + LLViewerTextureManager::init() ; + gBumpImageList.init(); + + // Create container for all sub-views + LLView::Params rvp; + rvp.name("root"); + rvp.rect(mWindowRectScaled); + rvp.mouse_opaque(false); + rvp.follows.flags(FOLLOWS_NONE); + mRootView = LLUICtrlFactory::create(rvp); + LLUI::getInstance()->setRootView(mRootView); + + // Make avatar head look forward at start + mCurrentMousePoint.mX = getWindowWidthScaled() / 2; + mCurrentMousePoint.mY = getWindowHeightScaled() / 2; + + gShowOverlayTitle = gSavedSettings.getBOOL("ShowOverlayTitle"); + mOverlayTitle = gSavedSettings.getString("OverlayTitle"); + // Can't have spaces in settings.ini strings, so use underscores instead and convert them. + LLStringUtil::replaceChar(mOverlayTitle, '_', ' '); + + mDebugText = new LLDebugText(this); + + mWorldViewRectScaled = calcScaledRect(mWorldViewRectRaw, mDisplayScale); +} + +std::string LLViewerWindow::getLastSnapshotDir() +{ + return sSnapshotDir; +} + +void LLViewerWindow::initGLDefaults() +{ + // RN: Need this for translation and stretch manip. + gBox.prerender(); +} + +struct MainPanel : public LLPanel +{ +}; + +void LLViewerWindow::initBase() +{ + S32 height = getWindowHeightScaled(); + S32 width = getWindowWidthScaled(); + + LLRect full_window(0, height, width, 0); + + //////////////////// + // + // Set the gamma + // + + F32 gamma = gSavedSettings.getF32("RenderGamma"); + if (gamma != 0.0f) + { + getWindow()->setGamma(gamma); + } + + // Create global views + + // Login screen and main_view.xml need edit menus for preferences and browser + LL_DEBUGS("AppInit") << "initializing edit menu" << LL_ENDL; + initialize_edit_menu(); + + LLFontGL::loadCommonFonts(); + + // Create the floater view at the start so that other views can add children to it. + // (But wait to add it as a child of the root view so that it will be in front of the + // other views.) + MainPanel* main_view = new MainPanel(); + if (!main_view->buildFromFile("main_view.xml")) + { + LL_ERRS() << "Failed to initialize viewer: Viewer couldn't process file main_view.xml, " + << "if this problem happens again, please validate your installation." << LL_ENDL; + } + main_view->setShape(full_window); + getRootView()->addChild(main_view); + + // placeholder widget that controls where "world" is rendered + mWorldViewPlaceholder = main_view->getChildView("world_view_rect")->getHandle(); + mPopupView = main_view->getChild("popup_holder"); + mHintHolder = main_view->getChild("hint_holder")->getHandle(); + mLoginPanelHolder = main_view->getChild("login_panel_holder")->getHandle(); + + // Create the toolbar view + // Get a pointer to the toolbar view holder + LLPanel* panel_holder = main_view->getChild("toolbar_view_holder"); + // Load the toolbar view from file + gToolBarView = LLUICtrlFactory::getInstance()->createFromFile("panel_toolbar_view.xml", panel_holder, LLDefaultChildRegistry::instance()); + if (!gToolBarView) + { + LL_ERRS() << "Failed to initialize viewer: Viewer couldn't process file panel_toolbar_view.xml, " + << "if this problem happens again, please validate your installation." << LL_ENDL; + } + gToolBarView->setShape(panel_holder->getLocalRect()); + // Hide the toolbars for the moment: we'll make them visible after logging in world (see LLViewerWindow::initWorldUI()) + gToolBarView->setVisible(false); + + // Constrain floaters to inside the menu and status bar regions. + gFloaterView = main_view->getChild("Floater View"); + for (S32 i = 0; i < LLToolBarEnums::TOOLBAR_COUNT; ++i) + { + LLToolBar * toolbarp = gToolBarView->getToolbar((LLToolBarEnums::EToolBarLocation)i); + if (toolbarp) + { + toolbarp->getCenterLayoutPanel()->setReshapeCallback(boost::bind(&LLFloaterView::setToolbarRect, gFloaterView, _1, _2)); + } + } + gFloaterView->setFloaterSnapView(main_view->getChild("floater_snap_region")->getHandle()); + gSnapshotFloaterView = main_view->getChild("Snapshot Floater View"); + + const F32 CHAT_PERSIST_TIME = 20.f; + + // Console + llassert( !gConsole ); + LLConsole::Params cp; + cp.name("console"); + cp.max_lines(gSavedSettings.getS32("ConsoleBufferSize")); + cp.rect(getChatConsoleRect()); + cp.persist_time(CHAT_PERSIST_TIME); + cp.font_size_index(gSavedSettings.getS32("ChatFontSize")); + cp.follows.flags(FOLLOWS_LEFT | FOLLOWS_RIGHT | FOLLOWS_BOTTOM); + gConsole = LLUICtrlFactory::create(cp); + getRootView()->addChild(gConsole); + + // optionally forward warnings to chat console/chat floater + // for qa runs and dev builds +#if !LL_RELEASE_FOR_DOWNLOAD + RecordToChatConsole::getInstance()->startRecorder(); +#else + if(gSavedSettings.getBOOL("QAMode")) + { + RecordToChatConsole::getInstance()->startRecorder(); + } +#endif + + gDebugView = getRootView()->getChild("DebugView"); + gDebugView->init(); + gToolTipView = getRootView()->getChild("tooltip view"); + + // Initialize do not disturb response message when logged in + LLAppViewer::instance()->setOnLoginCompletedCallback(boost::bind(&LLFloaterPreference::initDoNotDisturbResponse)); + + // Add the progress bar view (startup view), which overrides everything + mProgressView = getRootView()->findChild("progress_view"); + setShowProgress(false); + setProgressCancelButtonVisible(false); + + gMenuHolder = getRootView()->getChild("Menu Holder"); + LLMenuGL::sMenuContainer = gMenuHolder; +} + +void LLViewerWindow::initWorldUI() +{ + if (gNonInteractive) + { + gIMMgr = LLIMMgr::getInstance(); + LLNavigationBar::getInstance(); + gFloaterView->pushVisibleAll(false); + return; + } + + S32 height = mRootView->getRect().getHeight(); + S32 width = mRootView->getRect().getWidth(); + LLRect full_window(0, height, width, 0); + + + gIMMgr = LLIMMgr::getInstance(); + + //getRootView()->sendChildToFront(gFloaterView); + //getRootView()->sendChildToFront(gSnapshotFloaterView); + + if (!gNonInteractive) + { + LLPanel* chiclet_container = getRootView()->getChild("chiclet_container"); + LLChicletBar* chiclet_bar = LLChicletBar::getInstance(); + chiclet_bar->setShape(chiclet_container->getLocalRect()); + chiclet_bar->setFollowsAll(); + chiclet_container->addChild(chiclet_bar); + chiclet_container->setVisible(true); + } + + LLRect morph_view_rect = full_window; + morph_view_rect.stretch( -STATUS_BAR_HEIGHT ); + morph_view_rect.mTop = full_window.mTop - 32; + LLMorphView::Params mvp; + mvp.name("MorphView"); + mvp.rect(morph_view_rect); + mvp.visible(false); + gMorphView = LLUICtrlFactory::create(mvp); + getRootView()->addChild(gMorphView); + + LLWorldMapView::initClass(); + + // Force gFloaterWorldMap to initialize + LLFloaterReg::getInstance("world_map"); + + // Force gFloaterTools to initialize + LLFloaterReg::getInstance("build"); + + LLNavigationBar* navbar = LLNavigationBar::getInstance(); + if (!gStatusBar) + { + // Status bar + LLPanel* status_bar_container = getRootView()->getChild("status_bar_container"); + gStatusBar = new LLStatusBar(status_bar_container->getLocalRect()); + gStatusBar->setFollows(FOLLOWS_LEFT | FOLLOWS_TOP | FOLLOWS_RIGHT); + gStatusBar->setShape(status_bar_container->getLocalRect()); + // sync bg color with menu bar + gStatusBar->setBackgroundColor(gMenuBarView->getBackgroundColor().get()); + // add InBack so that gStatusBar won't be drawn over menu + status_bar_container->addChildInBack(gStatusBar, 2/*tab order, after menu*/); + status_bar_container->setVisible(true); + + // Navigation bar + LLView* nav_bar_container = getRootView()->getChild("nav_bar_container"); + + navbar->setShape(nav_bar_container->getLocalRect()); + navbar->setBackgroundColor(gMenuBarView->getBackgroundColor().get()); + nav_bar_container->addChild(navbar); + nav_bar_container->setVisible(true); + } + else + { + LLPanel* status_bar_container = getRootView()->getChild("status_bar_container"); + LLView* nav_bar_container = getRootView()->getChild("nav_bar_container"); + status_bar_container->setVisible(true); + nav_bar_container->setVisible(true); + } + + if (!gSavedSettings.getBOOL("ShowNavbarNavigationPanel")) + { + navbar->setVisible(false); + } + else + { + reshapeStatusBarContainer(); + } + + + // Top Info bar + LLPanel* topinfo_bar_container = getRootView()->getChild("topinfo_bar_container"); + LLPanelTopInfoBar* topinfo_bar = LLPanelTopInfoBar::getInstance(); + + topinfo_bar->setShape(topinfo_bar_container->getLocalRect()); + + topinfo_bar_container->addChild(topinfo_bar); + topinfo_bar_container->setVisible(true); + + if (!gSavedSettings.getBOOL("ShowMiniLocationPanel")) + { + topinfo_bar->setVisible(false); + } + + if ( gHUDView == NULL ) + { + LLRect hud_rect = full_window; + hud_rect.mBottom += 50; + if (gMenuBarView && gMenuBarView->isInVisibleChain()) + { + hud_rect.mTop -= gMenuBarView->getRect().getHeight(); + } + gHUDView = new LLHUDView(hud_rect); + getRootView()->addChild(gHUDView); + getRootView()->sendChildToBack(gHUDView); + } + + LLPanel* panel_ssf_container = getRootView()->getChild("state_management_buttons_container"); + + LLPanelStandStopFlying* panel_stand_stop_flying = LLPanelStandStopFlying::getInstance(); + panel_ssf_container->addChild(panel_stand_stop_flying); + + LLPanelHideBeacon* panel_hide_beacon = LLPanelHideBeacon::getInstance(); + panel_ssf_container->addChild(panel_hide_beacon); + + panel_ssf_container->setVisible(true); + + LLMenuOptionPathfindingRebakeNavmesh::getInstance()->initialize(); + + // Load and make the toolbars visible + // Note: we need to load the toolbars only *after* the user is logged in and IW + if (gToolBarView) + { + gToolBarView->loadToolbars(); + gToolBarView->setVisible(true); + } + + if (!gNonInteractive) + { + LLMediaCtrl* destinations = LLFloaterReg::getInstance("destinations")->getChild("destination_guide_contents"); + if (destinations) + { + destinations->setErrorPageURL(gSavedSettings.getString("GenericErrorPageURL")); + std::string url = gSavedSettings.getString("DestinationGuideURL"); + url = LLWeb::expandURLSubstitutions(url, LLSD()); + destinations->navigateTo(url, HTTP_CONTENT_TEXT_HTML); + } + LLMediaCtrl* avatar_picker = LLFloaterReg::getInstance("avatar")->findChild("avatar_picker_contents"); + if (avatar_picker) + { + avatar_picker->setErrorPageURL(gSavedSettings.getString("GenericErrorPageURL")); + std::string url = gSavedSettings.getString("AvatarPickerURL"); + url = LLWeb::expandURLSubstitutions(url, LLSD()); + avatar_picker->navigateTo(url, HTTP_CONTENT_TEXT_HTML); + } + } +} + +// Destroy the UI +void LLViewerWindow::shutdownViews() +{ + // clean up warning logger + RecordToChatConsole::getInstance()->stopRecorder(); + LL_INFOS() << "Warning logger is cleaned." << LL_ENDL ; + + gFocusMgr.unlockFocus(); + gFocusMgr.setMouseCapture(NULL); + gFocusMgr.setKeyboardFocus(NULL); + gFocusMgr.setTopCtrl(NULL); + if (mWindow) + { + mWindow->allowLanguageTextInput(NULL, false); + } + + delete mDebugText; + mDebugText = NULL; + + LL_INFOS() << "DebugText deleted." << LL_ENDL ; + + // Cleanup global views + if (gMorphView) + { + gMorphView->setVisible(false); + } + LL_INFOS() << "Global views cleaned." << LL_ENDL ; + + LLNotificationsUI::LLToast::cleanupToasts(); + LL_INFOS() << "Leftover toast cleaned up." << LL_ENDL; + + // DEV-40930: Clear sModalStack. Otherwise, any LLModalDialog left open + // will crump with LL_ERRS. + LLModalDialog::shutdownModals(); + LL_INFOS() << "LLModalDialog shut down." << LL_ENDL; + + // destroy the nav bar, not currently part of gViewerWindow + // *TODO: Make LLNavigationBar part of gViewerWindow + LLNavigationBar::deleteSingleton(); + LL_INFOS() << "LLNavigationBar destroyed." << LL_ENDL ; + + // destroy menus after instantiating navbar above, as it needs + // access to gMenuHolder + cleanup_menus(); + LL_INFOS() << "menus destroyed." << LL_ENDL ; + + view_listener_t::cleanup(); + LL_INFOS() << "view listeners destroyed." << LL_ENDL ; + + // Clean up pointers that are going to be invalid. (todo: check sMenuContainer) + mProgressView = NULL; + mPopupView = NULL; + + // Delete all child views. + delete mRootView; + mRootView = NULL; + LL_INFOS() << "RootView deleted." << LL_ENDL ; + + LLMenuOptionPathfindingRebakeNavmesh::getInstance()->quit(); + + // Automatically deleted as children of mRootView. Fix the globals. + gStatusBar = NULL; + gIMMgr = NULL; + gToolTipView = NULL; + + gToolBarView = NULL; + gFloaterView = NULL; + gMorphView = NULL; + + gHUDView = NULL; +} + +void LLViewerWindow::shutdownGL() +{ + //-------------------------------------------------------- + // Shutdown GL cleanly. Order is very important here. + //-------------------------------------------------------- + LLFontGL::destroyDefaultFonts(); + SUBSYSTEM_CLEANUP(LLFontManager); + stop_glerror(); + + gSky.cleanup(); + stop_glerror(); + + LL_INFOS() << "Cleaning up pipeline" << LL_ENDL; + gPipeline.cleanup(); + stop_glerror(); + + //MUST clean up pipeline before cleaning up wearables + LL_INFOS() << "Cleaning up wearables" << LL_ENDL; + LLWearableList::instance().cleanup() ; + + gTextureList.shutdown(); + stop_glerror(); + + gBumpImageList.shutdown(); + stop_glerror(); + + LLWorldMapView::cleanupTextures(); + + LLViewerTextureManager::cleanup() ; + SUBSYSTEM_CLEANUP(LLImageGL) ; + + LL_INFOS() << "All textures and llimagegl images are destroyed!" << LL_ENDL ; + + LL_INFOS() << "Cleaning up select manager" << LL_ENDL; + LLSelectMgr::getInstance()->cleanup(); + + LL_INFOS() << "Stopping GL during shutdown" << LL_ENDL; + stopGL(false); + stop_glerror(); + + gGL.shutdown(); + + SUBSYSTEM_CLEANUP(LLVertexBuffer); + + LL_INFOS() << "LLVertexBuffer cleaned." << LL_ENDL ; +} + +// shutdownViews() and shutdownGL() need to be called first +LLViewerWindow::~LLViewerWindow() +{ + LL_INFOS() << "Destroying Window" << LL_ENDL; + destroyWindow(); + + delete mDebugText; + mDebugText = NULL; + + if (LLViewerShaderMgr::sInitialized) + { + LLViewerShaderMgr::releaseInstance(); + LLViewerShaderMgr::sInitialized = false; + } + + mMaxVRAMControlConnection.disconnect(); +} + + +void LLViewerWindow::setCursor( ECursorType c ) +{ + mWindow->setCursor( c ); +} + +void LLViewerWindow::showCursor() +{ + mWindow->showCursor(); + + mCursorHidden = false; +} + +void LLViewerWindow::hideCursor() +{ + // And hide the cursor + mWindow->hideCursor(); + + mCursorHidden = true; +} + +void LLViewerWindow::sendShapeToSim() +{ + LLMessageSystem* msg = gMessageSystem; + if(!msg) return; + msg->newMessageFast(_PREHASH_AgentHeightWidth); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addU32Fast(_PREHASH_CircuitCode, gMessageSystem->mOurCircuitCode); + msg->nextBlockFast(_PREHASH_HeightWidthBlock); + msg->addU32Fast(_PREHASH_GenCounter, 0); + U16 height16 = (U16) mWorldViewRectRaw.getHeight(); + U16 width16 = (U16) mWorldViewRectRaw.getWidth(); + msg->addU16Fast(_PREHASH_Height, height16); + msg->addU16Fast(_PREHASH_Width, width16); + gAgent.sendReliableMessage(); +} + +// Must be called after window is created to set up agent +// camera variables and UI variables. +void LLViewerWindow::reshape(S32 width, S32 height) +{ + // Destroying the window at quit time generates spurious + // reshape messages. We don't care about these, and we + // don't want to send messages because the message system + // may have been destructed. + if (!LLApp::isExiting()) + { + gWindowResized = true; + + // update our window rectangle + mWindowRectRaw.mRight = mWindowRectRaw.mLeft + width; + mWindowRectRaw.mTop = mWindowRectRaw.mBottom + height; + + //glViewport(0, 0, width, height ); + + LLViewerCamera * camera = LLViewerCamera::getInstance(); // simpleton, might not exist + if (height > 0 && camera) + { + camera->setViewHeightInPixels( mWorldViewRectRaw.getHeight() ); + camera->setAspect( getWorldViewAspectRatio() ); + } + + calcDisplayScale(); + + bool display_scale_changed = mDisplayScale != LLUI::getScaleFactor(); + LLUI::setScaleFactor(mDisplayScale); + + // update our window rectangle + mWindowRectScaled.mRight = mWindowRectScaled.mLeft + ll_round((F32)width / mDisplayScale.mV[VX]); + mWindowRectScaled.mTop = mWindowRectScaled.mBottom + ll_round((F32)height / mDisplayScale.mV[VY]); + + setup2DViewport(); + + // Inform lower views of the change + // round up when converting coordinates to make sure there are no gaps at edge of window + LLView::sForceReshape = display_scale_changed; + mRootView->reshape(llceil((F32)width / mDisplayScale.mV[VX]), llceil((F32)height / mDisplayScale.mV[VY])); + if (display_scale_changed) + { + // Needs only a 'scale change' update, everything else gets handled by LLLayoutStack::updateClass() + LLPanelLogin::reshapePanel(); + } + LLView::sForceReshape = false; + + // clear font width caches + if (display_scale_changed) + { + LLHUDObject::reshapeAll(); + } + + sendShapeToSim(); + + // store new settings for the mode we are in, regardless + bool maximized = mWindow->getMaximized(); + gSavedSettings.setBOOL("WindowMaximized", maximized); + + if (!maximized) + { + U32 min_window_width=gSavedSettings.getU32("MinWindowWidth"); + U32 min_window_height=gSavedSettings.getU32("MinWindowHeight"); + // tell the OS specific window code about min window size + mWindow->setMinSize(min_window_width, min_window_height); + + LLCoordScreen window_rect; + if (!gNonInteractive && mWindow->getSize(&window_rect)) + { + // Only save size if not maximized + gSavedSettings.setU32("WindowWidth", window_rect.mX); + gSavedSettings.setU32("WindowHeight", window_rect.mY); + } + } + + sample(LLStatViewer::WINDOW_WIDTH, width); + sample(LLStatViewer::WINDOW_HEIGHT, height); + + LLLayoutStack::updateClass(); + } +} + + +// Hide normal UI when a logon fails +void LLViewerWindow::setNormalControlsVisible( bool visible ) +{ + if(LLChicletBar::instanceExists()) + { + LLChicletBar::getInstance()->setVisible(visible); + LLChicletBar::getInstance()->setEnabled(visible); + } + + if ( gMenuBarView ) + { + gMenuBarView->setVisible( visible ); + gMenuBarView->setEnabled( visible ); + + // ...and set the menu color appropriately. + setMenuBackgroundColor(gAgent.getGodLevel() > GOD_NOT, + LLGridManager::getInstance()->isInProductionGrid()); + } + + if ( gStatusBar ) + { + gStatusBar->setVisible( visible ); + gStatusBar->setEnabled( visible ); + } + + LLNavigationBar* navbarp = LLUI::getInstance()->getRootView()->findChild("navigation_bar"); + if (navbarp) + { + // when it's time to show navigation bar we need to ensure that the user wants to see it + // i.e. ShowNavbarNavigationPanel option is true + navbarp->setVisible( visible && gSavedSettings.getBOOL("ShowNavbarNavigationPanel") ); + } +} + +void LLViewerWindow::setMenuBackgroundColor(bool god_mode, bool dev_grid) +{ + LLSD args; + LLColor4 new_bg_color; + + // god more important than project, proj more important than grid + if ( god_mode ) + { + if ( LLGridManager::getInstance()->isInProductionGrid() ) + { + new_bg_color = LLUIColorTable::instance().getColor( "MenuBarGodBgColor" ); + } + else + { + new_bg_color = LLUIColorTable::instance().getColor( "MenuNonProductionGodBgColor" ); + } + } + else + { + switch (LLVersionInfo::instance().getViewerMaturity()) + { + case LLVersionInfo::TEST_VIEWER: + new_bg_color = LLUIColorTable::instance().getColor( "MenuBarTestBgColor" ); + break; + + case LLVersionInfo::PROJECT_VIEWER: + new_bg_color = LLUIColorTable::instance().getColor( "MenuBarProjectBgColor" ); + break; + + case LLVersionInfo::BETA_VIEWER: + new_bg_color = LLUIColorTable::instance().getColor( "MenuBarBetaBgColor" ); + break; + + case LLVersionInfo::RELEASE_VIEWER: + if(!LLGridManager::getInstance()->isInProductionGrid()) + { + new_bg_color = LLUIColorTable::instance().getColor( "MenuNonProductionBgColor" ); + } + else + { + new_bg_color = LLUIColorTable::instance().getColor( "MenuBarBgColor" ); + } + break; + } + } + + if(gMenuBarView) + { + gMenuBarView->setBackgroundColor( new_bg_color ); + } + + if(gStatusBar) + { + gStatusBar->setBackgroundColor( new_bg_color ); + } +} + +void LLViewerWindow::drawDebugText() +{ + gUIProgram.bind(); + gGL.color4f(1,1,1,1); + gGL.pushMatrix(); + gGL.pushUIMatrix(); + { + // scale view by UI global scale factor and aspect ratio correction factor + gGL.scaleUI(mDisplayScale.mV[VX], mDisplayScale.mV[VY], 1.f); + mDebugText->draw(); + } + gGL.popUIMatrix(); + gGL.popMatrix(); + + gGL.flush(); + gUIProgram.unbind(); +} + +void LLViewerWindow::draw() +{ + +//#if LL_DEBUG + LLView::sIsDrawing = true; +//#endif + stop_glerror(); + + LLUI::setLineWidth(1.f); + + LLUI::setLineWidth(1.f); + // Reset any left-over transforms + gGL.matrixMode(LLRender::MM_MODELVIEW); + + gGL.loadIdentity(); + + //S32 screen_x, screen_y; + + if (!gSavedSettings.getBOOL("RenderUIBuffer")) + { + LLView::sDirtyRect = getWindowRectScaled(); + } + + // HACK for timecode debugging + if (gSavedSettings.getBOOL("DisplayTimecode")) + { + // draw timecode block + std::string text; + + gGL.loadIdentity(); + + microsecondsToTimecodeString(gFrameTime,text); + const LLFontGL* font = LLFontGL::getFontSansSerif(); + font->renderUTF8(text, 0, + ll_round((getWindowWidthScaled()/2)-100.f), + ll_round((getWindowHeightScaled()-60.f)), + LLColor4( 1.f, 1.f, 1.f, 1.f ), + LLFontGL::LEFT, LLFontGL::TOP); + } + + // Draw all nested UI views. + // No translation needed, this view is glued to 0,0 + + gUIProgram.bind(); + gGL.color4f(1, 1, 1, 1); + + gGL.pushMatrix(); + LLUI::pushMatrix(); + { + + // scale view by UI global scale factor and aspect ratio correction factor + gGL.scaleUI(mDisplayScale.mV[VX], mDisplayScale.mV[VY], 1.f); + + LLVector2 old_scale_factor = LLUI::getScaleFactor(); + // apply camera zoom transform (for high res screenshots) + F32 zoom_factor = LLViewerCamera::getInstance()->getZoomFactor(); + S16 sub_region = LLViewerCamera::getInstance()->getZoomSubRegion(); + if (zoom_factor > 1.f) + { + //decompose subregion number to x and y values + int pos_y = sub_region / llceil(zoom_factor); + int pos_x = sub_region - (pos_y*llceil(zoom_factor)); + // offset for this tile + gGL.translatef((F32)getWindowWidthScaled() * -(F32)pos_x, + (F32)getWindowHeightScaled() * -(F32)pos_y, + 0.f); + gGL.scalef(zoom_factor, zoom_factor, 1.f); + LLUI::getScaleFactor() *= zoom_factor; + } + + // Draw tool specific overlay on world + LLToolMgr::getInstance()->getCurrentTool()->draw(); + + if( gAgentCamera.cameraMouselook() || LLFloaterCamera::inFreeCameraMode() ) + { + drawMouselookInstructions(); + stop_glerror(); + } + + // Draw all nested UI views. + // No translation needed, this view is glued to 0,0 + mRootView->draw(); + + if (LLView::sDebugRects) + { + gToolTipView->drawStickyRect(); + } + + // Draw optional on-top-of-everyone view + LLUICtrl* top_ctrl = gFocusMgr.getTopCtrl(); + if (top_ctrl && top_ctrl->getVisible()) + { + S32 screen_x, screen_y; + top_ctrl->localPointToScreen(0, 0, &screen_x, &screen_y); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + LLUI::pushMatrix(); + LLUI::translate( (F32) screen_x, (F32) screen_y); + top_ctrl->draw(); + LLUI::popMatrix(); + } + + + if( gShowOverlayTitle && !mOverlayTitle.empty() ) + { + // Used for special titles such as "Second Life - Special E3 2003 Beta" + const S32 DIST_FROM_TOP = 20; + LLFontGL::getFontSansSerifBig()->renderUTF8( + mOverlayTitle, 0, + ll_round( getWindowWidthScaled() * 0.5f), + getWindowHeightScaled() - DIST_FROM_TOP, + LLColor4(1, 1, 1, 0.4f), + LLFontGL::HCENTER, LLFontGL::TOP); + } + + LLUI::setScaleFactor(old_scale_factor); + } + LLUI::popMatrix(); + gGL.popMatrix(); + + gUIProgram.unbind(); + + LLView::sIsDrawing = false; +} + +// Takes a single keyup event, usually when UI is visible +bool LLViewerWindow::handleKeyUp(KEY key, MASK mask) +{ + if (LLSetKeyBindDialog::recordKey(key, mask, false)) + { + LL_DEBUGS() << "KeyUp handled by LLSetKeyBindDialog" << LL_ENDL; + LLViewerEventRecorder::instance().logKeyEvent(key, mask); + return true; + } + + LLFocusableElement* keyboard_focus = gFocusMgr.getKeyboardFocus(); + + if (keyboard_focus + && !(mask & (MASK_CONTROL | MASK_ALT)) + && !gFocusMgr.getKeystrokesOnly()) + { + // We have keyboard focus, and it's not an accelerator + if (keyboard_focus && keyboard_focus->wantsKeyUpKeyDown()) + { + return keyboard_focus->handleKeyUp(key, mask, false); + } + else if (key < 0x80) + { + // Not a special key, so likely (we hope) to generate a character. Let it fall through to character handler first. + return (gFocusMgr.getKeyboardFocus() != NULL); + } + } + + if (keyboard_focus) + { + if (keyboard_focus->handleKeyUp(key, mask, false)) + { + LL_DEBUGS() << "LLviewerWindow::handleKeyUp - in 'traverse up' - no loops seen... just called keyboard_focus->handleKeyUp an it returned true" << LL_ENDL; + LLViewerEventRecorder::instance().logKeyEvent(key, mask); + return true; + } + else { + LL_DEBUGS() << "LLviewerWindow::handleKeyUp - in 'traverse up' - no loops seen... just called keyboard_focus->handleKeyUp an it returned false" << LL_ENDL; + } + } + + // Try for a new-format gesture + if (LLGestureMgr::instance().triggerGestureRelease(key, mask)) + { + LL_DEBUGS() << "LLviewerWindow::handleKey new gesture release feature" << LL_ENDL; + LLViewerEventRecorder::instance().logKeyEvent(key,mask); + return true; + } + //Old format gestures do not support this, so no need to implement it. + + // don't pass keys on to world when something in ui has focus + return gFocusMgr.childHasKeyboardFocus(mRootView) + || LLMenuGL::getKeyboardMode() + || (gMenuBarView && gMenuBarView->getHighlightedItem() && gMenuBarView->getHighlightedItem()->isActive()); +} + +// Takes a single keydown event, usually when UI is visible +bool LLViewerWindow::handleKey(KEY key, MASK mask) +{ + // hide tooltips on keypress + LLToolTipMgr::instance().blockToolTips(); + + // Menus get handled on key down instead of key up + // so keybindings have to be recorded before that + if (LLSetKeyBindDialog::recordKey(key, mask, true)) + { + LL_DEBUGS() << "Key handled by LLSetKeyBindDialog" << LL_ENDL; + LLViewerEventRecorder::instance().logKeyEvent(key,mask); + return true; + } + + LLFocusableElement* keyboard_focus = gFocusMgr.getKeyboardFocus(); + + if (keyboard_focus + && !gFocusMgr.getKeystrokesOnly()) + { + //Most things should fall through, but mouselook is an exception, + //don't switch to mouselook if any floater has focus + if ((key == KEY_MOUSELOOK) && !(mask & (MASK_CONTROL | MASK_ALT))) + { + return true; + } + + LLUICtrl* cur_focus = dynamic_cast(keyboard_focus); + if (cur_focus && cur_focus->acceptsTextInput()) + { +#ifdef LL_WINDOWS + // On windows Alt Gr key generates additional Ctrl event, as result handling situations + // like 'AltGr + D' will result in 'Alt+Ctrl+D'. If it results in WM_CHAR, don't let it + // pass into menu or it will trigger 'develop' menu assigned to this combination on top + // of character handling. + // Alt Gr can be additionally modified by Shift + const MASK alt_gr = MASK_CONTROL | MASK_ALT; + LLWindowWin32 *window = static_cast(mWindow); + U32 raw_key = window->getRawWParam(); + if ((mask & alt_gr) != 0 + && ((raw_key >= 0x30 && raw_key <= 0x5A) //0-9, plus normal chartacters + || (raw_key >= 0xBA && raw_key <= 0xE4)) // Misc/OEM characters that can be covered by AltGr, ex: -, =, ~ + && (GetKeyState(VK_RMENU) & 0x8000) != 0 + && (GetKeyState(VK_RCONTROL) & 0x8000) == 0) // ensure right control is not pressed, only left one + { + // Alt Gr key is represented as right alt and left control. + // Any alt+ctrl combination is treated as Alt Gr by TranslateMessage() and + // will generate a WM_CHAR message, but here we only treat virtual Alt Graph + // key by checking if this specific combination has unicode char. + // + // I decided to handle only virtual RAlt+LCtrl==AltGr combination to minimize + // impact on menu, but the right way might be to handle all Alt+Ctrl calls. + + BYTE keyboard_state[256]; + if (GetKeyboardState(keyboard_state)) + { + const int char_count = 6; + wchar_t chars[char_count]; + HKL layout = GetKeyboardLayout(0); + // ToUnicodeEx changes buffer state on OS below Win10, which is undesirable, + // but since we already did a TranslateMessage() in gatherInput(), this + // should have no negative effect + // ToUnicodeEx works with virtual key codes + int res = ToUnicodeEx(raw_key, 0, keyboard_state, chars, char_count, 1 << 2 /*do not modify buffer flag*/, layout); + if (res == 1 && chars[0] >= 0x20) + { + // Let it fall through to character handler and get a WM_CHAR. + return true; + } + } + } +#endif + + if (!(mask & (MASK_CONTROL | MASK_ALT))) + { + // We have keyboard focus, and it's not an accelerator + if (keyboard_focus && keyboard_focus->wantsKeyUpKeyDown()) + { + return keyboard_focus->handleKey(key, mask, false); + } + else if (key < 0x80) + { + // Not a special key, so likely (we hope) to generate a character. Let it fall through to character handler first. + return true; + } + } + } + } + + // let menus handle navigation keys for navigation + if ((gMenuBarView && gMenuBarView->handleKey(key, mask, true)) + ||(gLoginMenuBarView && gLoginMenuBarView->handleKey(key, mask, true)) + ||(gMenuHolder && gMenuHolder->handleKey(key, mask, true))) + { + LL_DEBUGS() << "LLviewerWindow::handleKey handle nav keys for nav" << LL_ENDL; + LLViewerEventRecorder::instance().logKeyEvent(key,mask); + return true; + } + + + // give menus a chance to handle modified (Ctrl, Alt) shortcut keys before current focus + // as long as focus isn't locked + if (mask & (MASK_CONTROL | MASK_ALT) && !gFocusMgr.focusLocked()) + { + // Check the current floater's menu first, if it has one. + if (gFocusMgr.keyboardFocusHasAccelerators() + && keyboard_focus + && keyboard_focus->handleKey(key,mask,false)) + { + LLViewerEventRecorder::instance().logKeyEvent(key,mask); + return true; + } + + if (gAgent.isInitialized() + && (gAgent.getTeleportState() == LLAgent::TELEPORT_NONE || gAgent.getTeleportState() == LLAgent::TELEPORT_LOCAL) + && gMenuBarView + && gMenuBarView->handleAcceleratorKey(key, mask)) + { + LLViewerEventRecorder::instance().logKeyEvent(key, mask); + return true; + } + + if (gLoginMenuBarView && gLoginMenuBarView->handleAcceleratorKey(key, mask)) + { + LLViewerEventRecorder::instance().logKeyEvent(key,mask); + return true; + } + } + + // give floaters first chance to handle TAB key + // so frontmost floater gets focus + // if nothing has focus, go to first or last UI element as appropriate + if (key == KEY_TAB && (mask & MASK_CONTROL || keyboard_focus == NULL)) + { + LL_WARNS() << "LLviewerWindow::handleKey give floaters first chance at tab key " << LL_ENDL; + if (gMenuHolder) gMenuHolder->hideMenus(); + + // if CTRL-tabbing (and not just TAB with no focus), go into window cycle mode + gFloaterView->setCycleMode((mask & MASK_CONTROL) != 0); + + // do CTRL-TAB and CTRL-SHIFT-TAB logic + if (mask & MASK_SHIFT) + { + mRootView->focusPrevRoot(); + } + else + { + mRootView->focusNextRoot(); + } + LLViewerEventRecorder::instance().logKeyEvent(key,mask); + return true; + } + // hidden edit menu for cut/copy/paste + if (gEditMenu && gEditMenu->handleAcceleratorKey(key, mask)) + { + LLViewerEventRecorder::instance().logKeyEvent(key,mask); + return true; + } + + LLFloater* focused_floaterp = gFloaterView->getFocusedFloater(); + std::string focusedFloaterName = (focused_floaterp ? focused_floaterp->getInstanceName() : ""); + + if( keyboard_focus ) + { + if ((focusedFloaterName == "nearby_chat") || (focusedFloaterName == "im_container") || (focusedFloaterName == "impanel")) + { + if (gSavedSettings.getBOOL("ArrowKeysAlwaysMove")) + { + // let Control-Up and Control-Down through for chat line history, + if (!(key == KEY_UP && mask == MASK_CONTROL) + && !(key == KEY_DOWN && mask == MASK_CONTROL) + && !(key == KEY_UP && mask == MASK_ALT) + && !(key == KEY_DOWN && mask == MASK_ALT)) + { + switch(key) + { + case KEY_LEFT: + case KEY_RIGHT: + case KEY_UP: + case KEY_DOWN: + case KEY_PAGE_UP: + case KEY_PAGE_DOWN: + case KEY_HOME: + case KEY_END: + // when chatbar is empty or ArrowKeysAlwaysMove set, + // pass arrow keys on to avatar... + return false; + default: + break; + } + } + } + } + + if (keyboard_focus->handleKey(key, mask, false)) + { + + LL_DEBUGS() << "LLviewerWindow::handleKey - in 'traverse up' - no loops seen... just called keyboard_focus->handleKey an it returned true" << LL_ENDL; + LLViewerEventRecorder::instance().logKeyEvent(key,mask); + return true; + } else { + LL_DEBUGS() << "LLviewerWindow::handleKey - in 'traverse up' - no loops seen... just called keyboard_focus->handleKey an it returned false" << LL_ENDL; + } + } + + if( LLToolMgr::getInstance()->getCurrentTool()->handleKey(key, mask) ) + { + LL_DEBUGS() << "LLviewerWindow::handleKey toolbar handling?" << LL_ENDL; + LLViewerEventRecorder::instance().logKeyEvent(key,mask); + return true; + } + + // Try for a new-format gesture + if (LLGestureMgr::instance().triggerGesture(key, mask)) + { + LL_DEBUGS() << "LLviewerWindow::handleKey new gesture feature" << LL_ENDL; + LLViewerEventRecorder::instance().logKeyEvent(key,mask); + return true; + } + + // See if this is a gesture trigger. If so, eat the key and + // don't pass it down to the menus. + if (gGestureList.trigger(key, mask)) + { + LL_DEBUGS() << "LLviewerWindow::handleKey check gesture trigger" << LL_ENDL; + LLViewerEventRecorder::instance().logKeyEvent(key,mask); + return true; + } + + // If "Pressing letter keys starts local chat" option is selected, we are not in mouselook, + // no view has keyboard focus, this is a printable character key (and no modifier key is + // pressed except shift), then give focus to nearby chat (STORM-560) + if ( LLStartUp::getStartupState() >= STATE_STARTED && + gSavedSettings.getS32("LetterKeysFocusChatBar") && !gAgentCamera.cameraMouselook() && + !keyboard_focus && key < 0x80 && (mask == MASK_NONE || mask == MASK_SHIFT) ) + { + // Initialize nearby chat if it's missing + LLFloaterIMNearbyChat* nearby_chat = LLFloaterReg::findTypedInstance("nearby_chat"); + if (!nearby_chat) + { + LLSD name("im_container"); + LLFloaterReg::toggleInstanceOrBringToFront(name); + } + + LLChatEntry* chat_editor = LLFloaterReg::findTypedInstance("nearby_chat")->getChatBox(); + if (chat_editor) + { + // passing NULL here, character will be added later when it is handled by character handler. + nearby_chat->startChat(NULL); + return true; + } + } + + // give menus a chance to handle unmodified accelerator keys + if (gAgent.isInitialized() + && (gAgent.getTeleportState() == LLAgent::TELEPORT_NONE || gAgent.getTeleportState() == LLAgent::TELEPORT_LOCAL) + && gMenuBarView + && gMenuBarView->handleAcceleratorKey(key, mask)) + { + LLViewerEventRecorder::instance().logKeyEvent(key, mask); + return true; + } + + if (gLoginMenuBarView && gLoginMenuBarView->handleAcceleratorKey(key, mask)) + { + return true; + } + + // don't pass keys on to world when something in ui has focus + return gFocusMgr.childHasKeyboardFocus(mRootView) + || LLMenuGL::getKeyboardMode() + || (gMenuBarView && gMenuBarView->getHighlightedItem() && gMenuBarView->getHighlightedItem()->isActive()); +} + + +bool LLViewerWindow::handleUnicodeChar(llwchar uni_char, MASK mask) +{ + // HACK: We delay processing of return keys until they arrive as a Unicode char, + // so that if you're typing chat text at low frame rate, we don't send the chat + // until all keystrokes have been entered. JC + // HACK: Numeric keypad on Mac is Unicode 3 + // HACK: Control-M on Windows is Unicode 13 + if ((uni_char == 13 && mask != MASK_CONTROL) + || (uni_char == 3 && mask == MASK_NONE) ) + { + if (mask != MASK_ALT) + { + // remaps, handles ignored cases and returns back to viewer window. + return gViewerInput.handleKey(KEY_RETURN, mask, gKeyboard->getKeyRepeated(KEY_RETURN)); + } + } + + // let menus handle navigation (jump) keys + if (gMenuBarView && gMenuBarView->handleUnicodeChar(uni_char, true)) + { + return true; + } + + // Traverses up the hierarchy + LLFocusableElement* keyboard_focus = gFocusMgr.getKeyboardFocus(); + if( keyboard_focus ) + { + if (keyboard_focus->handleUnicodeChar(uni_char, false)) + { + return true; + } + + return true; + } + + return false; +} + + +void LLViewerWindow::handleScrollWheel(S32 clicks) +{ + LLUI::getInstance()->resetMouseIdleTimer(); + + LLMouseHandler* mouse_captor = gFocusMgr.getMouseCapture(); + if( mouse_captor ) + { + S32 local_x; + S32 local_y; + mouse_captor->screenPointToLocal( mCurrentMousePoint.mX, mCurrentMousePoint.mY, &local_x, &local_y ); + mouse_captor->handleScrollWheel(local_x, local_y, clicks); + if (LLView::sDebugMouseHandling) + { + LL_INFOS() << "Scroll Wheel handled by captor " << mouse_captor->getName() << LL_ENDL; + } + return; + } + + LLUICtrl* top_ctrl = gFocusMgr.getTopCtrl(); + if (top_ctrl) + { + S32 local_x; + S32 local_y; + top_ctrl->screenPointToLocal( mCurrentMousePoint.mX, mCurrentMousePoint.mY, &local_x, &local_y ); + if (top_ctrl->handleScrollWheel(local_x, local_y, clicks)) return; + } + + if (mRootView->handleScrollWheel(mCurrentMousePoint.mX, mCurrentMousePoint.mY, clicks) ) + { + if (LLView::sDebugMouseHandling) + { + LL_INFOS() << "Scroll Wheel" << LLView::sMouseHandlerMessage << LL_ENDL; + } + return; + } + else if (LLView::sDebugMouseHandling) + { + LL_INFOS() << "Scroll Wheel not handled by view" << LL_ENDL; + } + + // Zoom the camera in and out behavior + + if(top_ctrl == 0 + && getWorldViewRectScaled().pointInRect(mCurrentMousePoint.mX, mCurrentMousePoint.mY) + && gAgentCamera.isInitialized()) + gAgentCamera.handleScrollWheel(clicks); + + return; +} + +void LLViewerWindow::handleScrollHWheel(S32 clicks) +{ + if (LLAppViewer::instance()->quitRequested()) + { + return; + } + + LLUI::getInstance()->resetMouseIdleTimer(); + + LLMouseHandler* mouse_captor = gFocusMgr.getMouseCapture(); + if (mouse_captor) + { + S32 local_x; + S32 local_y; + mouse_captor->screenPointToLocal(mCurrentMousePoint.mX, mCurrentMousePoint.mY, &local_x, &local_y); + mouse_captor->handleScrollHWheel(local_x, local_y, clicks); + if (LLView::sDebugMouseHandling) + { + LL_INFOS() << "Scroll Horizontal Wheel handled by captor " << mouse_captor->getName() << LL_ENDL; + } + return; + } + + LLUICtrl* top_ctrl = gFocusMgr.getTopCtrl(); + if (top_ctrl) + { + S32 local_x; + S32 local_y; + top_ctrl->screenPointToLocal(mCurrentMousePoint.mX, mCurrentMousePoint.mY, &local_x, &local_y); + if (top_ctrl->handleScrollHWheel(local_x, local_y, clicks)) return; + } + + if (mRootView->handleScrollHWheel(mCurrentMousePoint.mX, mCurrentMousePoint.mY, clicks)) + { + if (LLView::sDebugMouseHandling) + { + LL_INFOS() << "Scroll Horizontal Wheel" << LLView::sMouseHandlerMessage << LL_ENDL; + } + return; + } + else if (LLView::sDebugMouseHandling) + { + LL_INFOS() << "Scroll Horizontal Wheel not handled by view" << LL_ENDL; + } + + return; +} + +void LLViewerWindow::addPopup(LLView* popup) +{ + if (mPopupView) + { + mPopupView->addPopup(popup); + } +} + +void LLViewerWindow::removePopup(LLView* popup) +{ + if (mPopupView) + { + mPopupView->removePopup(popup); + } +} + +void LLViewerWindow::clearPopups() +{ + if (mPopupView) + { + mPopupView->clearPopups(); + } +} + +void LLViewerWindow::moveCursorToCenter() +{ + if (! gSavedSettings.getBOOL("DisableMouseWarp")) + { + S32 x = getWorldViewWidthScaled() / 2; + S32 y = getWorldViewHeightScaled() / 2; + + LLUI::getInstance()->setMousePositionScreen(x, y); + + //on a forced move, all deltas get zeroed out to prevent jumping + mCurrentMousePoint.set(x,y); + mLastMousePoint.set(x,y); + mCurrentMouseDelta.set(0,0); + } +} + + +////////////////////////////////////////////////////////////////////// +// +// Hover handlers +// + +void append_xui_tooltip(LLView* viewp, LLToolTip::Params& params) +{ + if (viewp) + { + if (!params.styled_message.empty()) + { + params.styled_message.add().text("\n---------\n"); + } + LLView::root_to_view_iterator_t end_tooltip_it = viewp->endRootToView(); + // NOTE: we skip "root" since it is assumed + for (LLView::root_to_view_iterator_t tooltip_it = ++viewp->beginRootToView(); + tooltip_it != end_tooltip_it; + ++tooltip_it) + { + LLView* viewp = *tooltip_it; + + params.styled_message.add().text(viewp->getName()); + + LLPanel* panelp = dynamic_cast(viewp); + if (panelp && !panelp->getXMLFilename().empty()) + { + params.styled_message.add() + .text("(" + panelp->getXMLFilename() + ")") + .style.color(LLColor4(0.7f, 0.7f, 1.f, 1.f)); + } + params.styled_message.add().text("/"); + } + } +} + +static LLTrace::BlockTimerStatHandle ftm("Update UI"); + +// Update UI based on stored mouse position from mouse-move +// event processing. +void LLViewerWindow::updateUI() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; //LL_RECORD_BLOCK_TIME(ftm); + + static std::string last_handle_msg; + + if (gLoggedInTime.getStarted()) + { + const F32 DESTINATION_GUIDE_HINT_TIMEOUT = 1200.f; + const F32 SIDE_PANEL_HINT_TIMEOUT = 300.f; + if (gLoggedInTime.getElapsedTimeF32() > DESTINATION_GUIDE_HINT_TIMEOUT) + { + LLFirstUse::notUsingDestinationGuide(); + } + if (gLoggedInTime.getElapsedTimeF32() > SIDE_PANEL_HINT_TIMEOUT) + { + LLFirstUse::notUsingSidePanel(); + } + } + + LLConsole::updateClass(); + + // animate layout stacks so we have up to date rect for world view + LLLayoutStack::updateClass(); + + // use full window for world view when not rendering UI + bool world_view_uses_full_window = gAgentCamera.cameraMouselook() || !gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI); + updateWorldViewRect(world_view_uses_full_window); + + LLView::sMouseHandlerMessage.clear(); + + S32 x = mCurrentMousePoint.mX; + S32 y = mCurrentMousePoint.mY; + + MASK mask = gKeyboard->currentMask(true); + + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_RAYCAST)) + { + gDebugRaycastFaceHit = -1; + gDebugRaycastObject = cursorIntersect(-1, -1, 512.f, NULL, -1, false, false, true, false, + &gDebugRaycastFaceHit, + &gDebugRaycastIntersection, + &gDebugRaycastTexCoord, + &gDebugRaycastNormal, + &gDebugRaycastTangent, + &gDebugRaycastStart, + &gDebugRaycastEnd); + + gDebugRaycastParticle = gPipeline.lineSegmentIntersectParticle(gDebugRaycastStart, gDebugRaycastEnd, &gDebugRaycastParticleIntersection, NULL); + } + + updateMouseDelta(); + updateKeyboardFocus(); + + bool handled = false; + + LLUICtrl* top_ctrl = gFocusMgr.getTopCtrl(); + LLMouseHandler* mouse_captor = gFocusMgr.getMouseCapture(); + LLView* captor_view = dynamic_cast(mouse_captor); + + //FIXME: only include captor and captor's ancestors if mouse is truly over them --RN + + //build set of views containing mouse cursor by traversing UI hierarchy and testing + //screen rect against mouse cursor + view_handle_set_t mouse_hover_set; + + // constraint mouse enter events to children of mouse captor + LLView* root_view = captor_view; + + // if mouse captor doesn't exist or isn't a LLView + // then allow mouse enter events on entire UI hierarchy + if (!root_view) + { + root_view = mRootView; + } + + static LLCachedControl dump_menu_holder(gSavedSettings, "DumpMenuHolderSize", false); + if (dump_menu_holder) + { + static bool init = false; + static LLFrameTimer child_count_timer; + static std::vector child_vec; + if (!init) + { + child_count_timer.resetWithExpiry(5.f); + init = true; + } + if (child_count_timer.hasExpired()) + { + LL_INFOS() << "gMenuHolder child count: " << gMenuHolder->getChildCount() << LL_ENDL; + std::vector local_child_vec; + LLView::child_list_t child_list = *gMenuHolder->getChildList(); + for (auto child : child_list) + { + local_child_vec.emplace_back(child->getName()); + } + if (!local_child_vec.empty() && local_child_vec != child_vec) + { + std::vector out_vec; + std::sort(local_child_vec.begin(), local_child_vec.end()); + std::sort(child_vec.begin(), child_vec.end()); + std::set_difference(child_vec.begin(), child_vec.end(), local_child_vec.begin(), local_child_vec.end(), std::inserter(out_vec, out_vec.begin())); + if (!out_vec.empty()) + { + LL_INFOS() << "gMenuHolder removal diff size: '"<getParent(); + while(captor_parent_view) + { + mouse_hover_set.insert(captor_parent_view->getHandle()); + captor_parent_view = captor_parent_view->getParent(); + } + } + + // aggregate visible views that contain mouse cursor in display order + LLPopupView::popup_list_t popups = mPopupView->getCurrentPopups(); + + for(LLPopupView::popup_list_t::iterator popup_it = popups.begin(); popup_it != popups.end(); ++popup_it) + { + LLView* popup = popup_it->get(); + if (popup && popup->calcScreenBoundingRect().pointInRect(x, y)) + { + // iterator over contents of top_ctrl, and throw into mouse_hover_set + for (LLView::tree_iterator_t it = popup->beginTreeDFS(); + it != popup->endTreeDFS(); + ++it) + { + LLView* viewp = *it; + if (viewp->getVisible() + && viewp->calcScreenBoundingRect().pointInRect(x, y)) + { + // we have a view that contains the mouse, add it to the set + mouse_hover_set.insert(viewp->getHandle()); + } + else + { + // skip this view and all of its children + it.skipDescendants(); + } + } + } + } + + // while the top_ctrl contains the mouse cursor, only it and its descendants will receive onMouseEnter events + if (top_ctrl && top_ctrl->calcScreenBoundingRect().pointInRect(x, y)) + { + // iterator over contents of top_ctrl, and throw into mouse_hover_set + for (LLView::tree_iterator_t it = top_ctrl->beginTreeDFS(); + it != top_ctrl->endTreeDFS(); + ++it) + { + LLView* viewp = *it; + if (viewp->getVisible() + && viewp->calcScreenBoundingRect().pointInRect(x, y)) + { + // we have a view that contains the mouse, add it to the set + mouse_hover_set.insert(viewp->getHandle()); + } + else + { + // skip this view and all of its children + it.skipDescendants(); + } + } + } + else + { + // walk UI tree in depth-first order + for (LLView::tree_iterator_t it = root_view->beginTreeDFS(); + it != root_view->endTreeDFS(); + ++it) + { + LLView* viewp = *it; + // calculating the screen rect involves traversing the parent, so this is less than optimal + if (viewp->getVisible() + && viewp->calcScreenBoundingRect().pointInRect(x, y)) + { + + // if this view is mouse opaque, nothing behind it should be in mouse_hover_set + if (viewp->getMouseOpaque()) + { + // constrain further iteration to children of this widget + it = viewp->beginTreeDFS(); + } + + // we have a view that contains the mouse, add it to the set + mouse_hover_set.insert(viewp->getHandle()); + } + else + { + // skip this view and all of its children + it.skipDescendants(); + } + } + } + } + + typedef std::vector > view_handle_list_t; + + // call onMouseEnter() on all views which contain the mouse cursor but did not before + view_handle_list_t mouse_enter_views; + std::set_difference(mouse_hover_set.begin(), mouse_hover_set.end(), + mMouseHoverViews.begin(), mMouseHoverViews.end(), + std::back_inserter(mouse_enter_views)); + for (view_handle_list_t::iterator it = mouse_enter_views.begin(); + it != mouse_enter_views.end(); + ++it) + { + LLView* viewp = it->get(); + if (viewp) + { + LLRect view_screen_rect = viewp->calcScreenRect(); + viewp->onMouseEnter(x - view_screen_rect.mLeft, y - view_screen_rect.mBottom, mask); + } + } + + // call onMouseLeave() on all views which no longer contain the mouse cursor + view_handle_list_t mouse_leave_views; + std::set_difference(mMouseHoverViews.begin(), mMouseHoverViews.end(), + mouse_hover_set.begin(), mouse_hover_set.end(), + std::back_inserter(mouse_leave_views)); + for (view_handle_list_t::iterator it = mouse_leave_views.begin(); + it != mouse_leave_views.end(); + ++it) + { + LLView* viewp = it->get(); + if (viewp) + { + LLRect view_screen_rect = viewp->calcScreenRect(); + viewp->onMouseLeave(x - view_screen_rect.mLeft, y - view_screen_rect.mBottom, mask); + } + } + + // store resulting hover set for next frame + swap(mMouseHoverViews, mouse_hover_set); + + // only handle hover events when UI is enabled + if (gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) + { + + if( mouse_captor ) + { + // Pass hover events to object capturing mouse events. + S32 local_x; + S32 local_y; + mouse_captor->screenPointToLocal( x, y, &local_x, &local_y ); + handled = mouse_captor->handleHover(local_x, local_y, mask); + if (LLView::sDebugMouseHandling) + { + LL_INFOS() << "Hover handled by captor " << mouse_captor->getName() << LL_ENDL; + } + + if( !handled ) + { + LL_DEBUGS("UserInput") << "hover not handled by mouse captor" << LL_ENDL; + } + } + else + { + if (top_ctrl) + { + S32 local_x, local_y; + top_ctrl->screenPointToLocal( x, y, &local_x, &local_y ); + handled = top_ctrl->pointInView(local_x, local_y) && top_ctrl->handleHover(local_x, local_y, mask); + } + + if ( !handled ) + { + // x and y are from last time mouse was in window + // mMouseInWindow tracks *actual* mouse location + if (mMouseInWindow && mRootView->handleHover(x, y, mask) ) + { + if (LLView::sDebugMouseHandling && LLView::sMouseHandlerMessage != last_handle_msg) + { + last_handle_msg = LLView::sMouseHandlerMessage; + LL_INFOS() << "Hover" << LLView::sMouseHandlerMessage << LL_ENDL; + } + handled = true; + } + else if (LLView::sDebugMouseHandling) + { + if (last_handle_msg != LLStringUtil::null) + { + last_handle_msg.clear(); + LL_INFOS() << "Hover not handled by view" << LL_ENDL; + } + } + } + + if (!handled) + { + LLTool *tool = LLToolMgr::getInstance()->getCurrentTool(); + + if(mMouseInWindow && tool) + { + handled = tool->handleHover(x, y, mask); + } + } + } + + // Show a new tool tip (or update one that is already shown) + bool tool_tip_handled = false; + std::string tool_tip_msg; + if( handled + && !mWindow->isCursorHidden()) + { + LLRect screen_sticky_rect = mRootView->getLocalRect(); + S32 local_x, local_y; + + static LLCachedControl debug_show_xui_names(gSavedSettings, "DebugShowXUINames", 0); + if (debug_show_xui_names) + { + LLToolTip::Params params; + + LLView* tooltip_view = mRootView; + LLView::tree_iterator_t end_it = mRootView->endTreeDFS(); + for (LLView::tree_iterator_t it = mRootView->beginTreeDFS(); it != end_it; ++it) + { + LLView* viewp = *it; + LLRect screen_rect; + viewp->localRectToScreen(viewp->getLocalRect(), &screen_rect); + if (!(viewp->getVisible() + && screen_rect.pointInRect(x, y))) + { + it.skipDescendants(); + } + // only report xui names for LLUICtrls, + // and blacklist the various containers we don't care about + else if (dynamic_cast(viewp) + && viewp != gMenuHolder + && viewp != gFloaterView + && viewp != gConsole) + { + if (dynamic_cast(viewp)) + { + // constrain search to descendants of this (frontmost) floater + // by resetting iterator + it = viewp->beginTreeDFS(); + } + + // if we are in a new part of the tree (not a descendent of current tooltip_view) + // then push the results for tooltip_view and start with a new potential view + // NOTE: this emulates visiting only the leaf nodes that meet our criteria + if (!viewp->hasAncestor(tooltip_view)) + { + append_xui_tooltip(tooltip_view, params); + screen_sticky_rect.intersectWith(tooltip_view->calcScreenRect()); + } + tooltip_view = viewp; + } + } + + append_xui_tooltip(tooltip_view, params); + params.styled_message.add().text("\n"); + + screen_sticky_rect.intersectWith(tooltip_view->calcScreenRect()); + + params.sticky_rect = screen_sticky_rect; + params.max_width = 400; + + LLToolTipMgr::instance().show(params); + } + // if there is a mouse captor, nothing else gets a tooltip + else if (mouse_captor) + { + mouse_captor->screenPointToLocal(x, y, &local_x, &local_y); + tool_tip_handled = mouse_captor->handleToolTip(local_x, local_y, mask); + } + else + { + // next is top_ctrl + if (!tool_tip_handled && top_ctrl) + { + top_ctrl->screenPointToLocal(x, y, &local_x, &local_y); + tool_tip_handled = top_ctrl->handleToolTip(local_x, local_y, mask ); + } + + if (!tool_tip_handled) + { + local_x = x; local_y = y; + tool_tip_handled = mRootView->handleToolTip(local_x, local_y, mask ); + } + + LLTool* current_tool = LLToolMgr::getInstance()->getCurrentTool(); + if (!tool_tip_handled && current_tool) + { + current_tool->screenPointToLocal(x, y, &local_x, &local_y); + tool_tip_handled = current_tool->handleToolTip(local_x, local_y, mask ); + } + } + } + } + else + { // just have tools handle hover when UI is turned off + LLTool *tool = LLToolMgr::getInstance()->getCurrentTool(); + + if(mMouseInWindow && tool) + { + handled = tool->handleHover(x, y, mask); + } + } + + updateLayout(); + + mLastMousePoint = mCurrentMousePoint; + + // cleanup unused selections when no modal dialogs are open + if (LLModalDialog::activeCount() == 0) + { + LLViewerParcelMgr::getInstance()->deselectUnused(); + } + + if (LLModalDialog::activeCount() == 0) + { + LLSelectMgr::getInstance()->deselectUnused(); + } +} + + +void LLViewerWindow::updateLayout() +{ + LLTool* tool = LLToolMgr::getInstance()->getCurrentTool(); + if (gFloaterTools != NULL + && tool != NULL + && tool != gToolNull + && tool != LLToolCompInspect::getInstance() + && tool != LLToolDragAndDrop::getInstance() + && !gSavedSettings.getBOOL("FreezeTime")) + { + // Suppress the toolbox view if our source tool was the pie tool, + // and we've overridden to something else. + bool suppress_toolbox = + (LLToolMgr::getInstance()->getBaseTool() == LLToolPie::getInstance()) && + (LLToolMgr::getInstance()->getCurrentTool() != LLToolPie::getInstance()); + + LLMouseHandler *captor = gFocusMgr.getMouseCapture(); + // With the null, inspect, or drag and drop tool, don't muck + // with visibility. + + if (gFloaterTools->isMinimized() + || (tool != LLToolPie::getInstance() // not default tool + && tool != LLToolCompGun::getInstance() // not coming out of mouselook + && !suppress_toolbox // not override in third person + && LLToolMgr::getInstance()->getCurrentToolset()->isShowFloaterTools() + && (!captor || dynamic_cast(captor) != NULL))) // not dragging + { + // Force floater tools to be visible (unless minimized) + if (!gFloaterTools->getVisible()) + { + gFloaterTools->openFloater(); + } + // Update the location of the blue box tool popup + LLCoordGL select_center_screen; + MASK mask = gKeyboard->currentMask(true); + gFloaterTools->updatePopup( select_center_screen, mask ); + } + else + { + gFloaterTools->setVisible(false); + } + //gMenuBarView->setItemVisible("BuildTools", gFloaterTools->getVisible()); + } + + // Always update console + if(gConsole) + { + LLRect console_rect = getChatConsoleRect(); + gConsole->reshape(console_rect.getWidth(), console_rect.getHeight()); + gConsole->setRect(console_rect); + } +} + +void LLViewerWindow::updateMouseDelta() +{ +#if LL_WINDOWS + LLCoordCommon delta; + mWindow->getCursorDelta(&delta); + S32 dx = delta.mX; + S32 dy = delta.mY; +#else + S32 dx = lltrunc((F32) (mCurrentMousePoint.mX - mLastMousePoint.mX) * LLUI::getScaleFactor().mV[VX]); + S32 dy = lltrunc((F32) (mCurrentMousePoint.mY - mLastMousePoint.mY) * LLUI::getScaleFactor().mV[VY]); +#endif + + //RN: fix for asynchronous notification of mouse leaving window not working + LLCoordWindow mouse_pos; + mWindow->getCursorPosition(&mouse_pos); + if (mouse_pos.mX < 0 || + mouse_pos.mY < 0 || + mouse_pos.mX > mWindowRectRaw.getWidth() || + mouse_pos.mY > mWindowRectRaw.getHeight()) + { + mMouseInWindow = false; + } + else + { + mMouseInWindow = true; + } + + LLVector2 mouse_vel; + + if (gSavedSettings.getBOOL("MouseSmooth")) + { + static F32 fdx = 0.f; + static F32 fdy = 0.f; + + F32 amount = 16.f; + fdx = fdx + ((F32) dx - fdx) * llmin(gFrameIntervalSeconds.value()*amount,1.f); + fdy = fdy + ((F32) dy - fdy) * llmin(gFrameIntervalSeconds.value()*amount,1.f); + + mCurrentMouseDelta.set(ll_round(fdx), ll_round(fdy)); + mouse_vel.setVec(fdx,fdy); + } + else + { + mCurrentMouseDelta.set(dx, dy); + mouse_vel.setVec((F32) dx, (F32) dy); + } + + sample(sMouseVelocityStat, mouse_vel.magVec()); +} + +void LLViewerWindow::updateKeyboardFocus() +{ + if (!gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) + { + gFocusMgr.setKeyboardFocus(NULL); + } + + // clean up current focus + LLUICtrl* cur_focus = dynamic_cast(gFocusMgr.getKeyboardFocus()); + if (cur_focus) + { + if (!cur_focus->isInVisibleChain() || !cur_focus->isInEnabledChain()) + { + // don't release focus, just reassign so that if being given + // to a sibling won't call onFocusLost on all the ancestors + // gFocusMgr.releaseFocusIfNeeded(cur_focus); + + LLUICtrl* parent = cur_focus->getParentUICtrl(); + const LLUICtrl* focus_root = cur_focus->findRootMostFocusRoot(); + bool new_focus_found = false; + while(parent) + { + if (parent->isCtrl() + && (parent->hasTabStop() || parent == focus_root) + && !parent->getIsChrome() + && parent->isInVisibleChain() + && parent->isInEnabledChain()) + { + if (!parent->focusFirstItem()) + { + parent->setFocus(true); + } + new_focus_found = true; + break; + } + parent = parent->getParentUICtrl(); + } + + // if we didn't find a better place to put focus, just release it + // hasFocus() will return true if and only if we didn't touch focus since we + // are only moving focus higher in the hierarchy + if (!new_focus_found) + { + cur_focus->setFocus(false); + } + } + else if (cur_focus->isFocusRoot()) + { + // focus roots keep trying to delegate focus to their first valid descendant + // this assumes that focus roots are not valid focus holders on their own + cur_focus->focusFirstItem(); + } + } + + // last ditch force of edit menu to selection manager + if (LLEditMenuHandler::gEditMenuHandler == NULL && LLSelectMgr::getInstance()->getSelection()->getObjectCount()) + { + LLEditMenuHandler::gEditMenuHandler = LLSelectMgr::getInstance(); + } + + if (gFloaterView->getCycleMode()) + { + // sync all floaters with their focus state + gFloaterView->highlightFocusedFloater(); + gSnapshotFloaterView->highlightFocusedFloater(); + MASK mask = gKeyboard->currentMask(true); + if ((mask & MASK_CONTROL) == 0) + { + // control key no longer held down, finish cycle mode + gFloaterView->setCycleMode(false); + + gFloaterView->syncFloaterTabOrder(); + } + else + { + // user holding down CTRL, don't update tab order of floaters + } + } + else + { + // update focused floater + gFloaterView->highlightFocusedFloater(); + gSnapshotFloaterView->highlightFocusedFloater(); + // make sure floater visible order is in sync with tab order + gFloaterView->syncFloaterTabOrder(); + } +} + +static LLTrace::BlockTimerStatHandle FTM_UPDATE_WORLD_VIEW("Update World View"); +void LLViewerWindow::updateWorldViewRect(bool use_full_window) +{ + LL_RECORD_BLOCK_TIME(FTM_UPDATE_WORLD_VIEW); + + // start off using whole window to render world + LLRect new_world_rect = mWindowRectRaw; + + if (!use_full_window && mWorldViewPlaceholder.get()) + { + new_world_rect = mWorldViewPlaceholder.get()->calcScreenRect(); + // clamp to at least a 1x1 rect so we don't try to allocate zero width gl buffers + new_world_rect.mTop = llmax(new_world_rect.mTop, new_world_rect.mBottom + 1); + new_world_rect.mRight = llmax(new_world_rect.mRight, new_world_rect.mLeft + 1); + + new_world_rect.mLeft = ll_round((F32)new_world_rect.mLeft * mDisplayScale.mV[VX]); + new_world_rect.mRight = ll_round((F32)new_world_rect.mRight * mDisplayScale.mV[VX]); + new_world_rect.mBottom = ll_round((F32)new_world_rect.mBottom * mDisplayScale.mV[VY]); + new_world_rect.mTop = ll_round((F32)new_world_rect.mTop * mDisplayScale.mV[VY]); + } + + if (mWorldViewRectRaw != new_world_rect) + { + mWorldViewRectRaw = new_world_rect; + gResizeScreenTexture = true; + LLViewerCamera::getInstance()->setViewHeightInPixels( mWorldViewRectRaw.getHeight() ); + LLViewerCamera::getInstance()->setAspect( getWorldViewAspectRatio() ); + + LLRect old_world_rect_scaled = mWorldViewRectScaled; + mWorldViewRectScaled = calcScaledRect(mWorldViewRectRaw, mDisplayScale); + + // sending a signal with a new WorldView rect + mOnWorldViewRectUpdated(old_world_rect_scaled, mWorldViewRectScaled); + } +} + +void LLViewerWindow::saveLastMouse(const LLCoordGL &point) +{ + // Store last mouse location. + // If mouse leaves window, pretend last point was on edge of window + + if (point.mX < 0) + { + mCurrentMousePoint.mX = 0; + } + else if (point.mX > getWindowWidthScaled()) + { + mCurrentMousePoint.mX = getWindowWidthScaled(); + } + else + { + mCurrentMousePoint.mX = point.mX; + } + + if (point.mY < 0) + { + mCurrentMousePoint.mY = 0; + } + else if (point.mY > getWindowHeightScaled() ) + { + mCurrentMousePoint.mY = getWindowHeightScaled(); + } + else + { + mCurrentMousePoint.mY = point.mY; + } +} + + +// Draws the selection outlines for the currently selected objects +// Must be called after displayObjects is called, which sets the mGLName parameter +// NOTE: This function gets called 3 times: +// render_ui_3d: false, false, true +// render_hud_elements: false, false, false +void LLViewerWindow::renderSelections( bool for_gl_pick, bool pick_parcel_walls, bool for_hud ) +{ + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + + if (!for_hud && !for_gl_pick) + { + // Call this once and only once + LLSelectMgr::getInstance()->updateSilhouettes(); + } + + // Draw fence around land selections + if (for_gl_pick) + { + if (pick_parcel_walls) + { + LLViewerParcelMgr::getInstance()->renderParcelCollision(); + } + } + else if (( for_hud && selection->getSelectType() == SELECT_TYPE_HUD) || + (!for_hud && selection->getSelectType() != SELECT_TYPE_HUD)) + { + LLSelectMgr::getInstance()->renderSilhouettes(for_hud); + + stop_glerror(); + + // setup HUD render + if (selection->getSelectType() == SELECT_TYPE_HUD && LLSelectMgr::getInstance()->getSelection()->getObjectCount()) + { + LLBBox hud_bbox = gAgentAvatarp->getHUDBBox(); + + // set up transform to encompass bounding box of HUD + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.pushMatrix(); + gGL.loadIdentity(); + F32 depth = llmax(1.f, hud_bbox.getExtentLocal().mV[VX] * 1.1f); + gGL.ortho(-0.5f * LLViewerCamera::getInstance()->getAspect(), 0.5f * LLViewerCamera::getInstance()->getAspect(), -0.5f, 0.5f, 0.f, depth); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + gGL.loadIdentity(); + gGL.loadMatrix(OGL_TO_CFR_ROTATION); // Load Cory's favorite reference frame + gGL.translatef(-hud_bbox.getCenterLocal().mV[VX] + (depth *0.5f), 0.f, 0.f); + } + + // Render light for editing + if (LLSelectMgr::sRenderLightRadius && LLToolMgr::getInstance()->inEdit()) + { + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + LLGLEnable gls_blend(GL_BLEND); + LLGLEnable gls_cull(GL_CULL_FACE); + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + if (selection->getSelectType() == SELECT_TYPE_HUD) + { + F32 zoom = gAgentCamera.mHUDCurZoom; + gGL.scalef(zoom, zoom, zoom); + } + + struct f : public LLSelectedObjectFunctor + { + virtual bool apply(LLViewerObject* object) + { + LLDrawable* drawable = object->mDrawable; + if (drawable && drawable->isLight()) + { + LLVOVolume* vovolume = drawable->getVOVolume(); + gGL.pushMatrix(); + + LLVector3 center = drawable->getPositionAgent(); + gGL.translatef(center[0], center[1], center[2]); + F32 scale = vovolume->getLightRadius(); + gGL.scalef(scale, scale, scale); + + LLColor4 color(vovolume->getLightSRGBColor(), .5f); + gGL.color4fv(color.mV); + + //F32 pixel_area = 100000.f; + // Render Outside + gSphere.render(); + + // Render Inside + glCullFace(GL_FRONT); + gSphere.render(); + glCullFace(GL_BACK); + + gGL.popMatrix(); + } + return true; + } + } func; + LLSelectMgr::getInstance()->getSelection()->applyToObjects(&func); + + gGL.popMatrix(); + } + + // NOTE: The average position for the axis arrows of the selected objects should + // not be recalculated at this time. If they are, then group rotations will break. + + // Draw arrows at average center of all selected objects + LLTool* tool = LLToolMgr::getInstance()->getCurrentTool(); + if (tool) + { + if(tool->isAlwaysRendered()) + { + tool->render(); + } + else + { + if( !LLSelectMgr::getInstance()->getSelection()->isEmpty() ) + { + bool all_selected_objects_move; + bool all_selected_objects_modify; + // Note: This might be costly to do on each frame and when a lot of objects are selected + // we might be better off with some kind of memory for selection and/or states, consider + // optimizing, perhaps even some kind of selection generation at level of LLSelectMgr to + // make whole viewer benefit. + LLSelectMgr::getInstance()->selectGetEditMoveLinksetPermissions(all_selected_objects_move, all_selected_objects_modify); + + bool draw_handles = true; + + if (tool == LLToolCompTranslate::getInstance() && !all_selected_objects_move && !LLSelectMgr::getInstance()->isMovableAvatarSelected()) + { + draw_handles = false; + } + + if (tool == LLToolCompRotate::getInstance() && !all_selected_objects_move && !LLSelectMgr::getInstance()->isMovableAvatarSelected()) + { + draw_handles = false; + } + + if ( !all_selected_objects_modify && tool == LLToolCompScale::getInstance() ) + { + draw_handles = false; + } + + if( draw_handles ) + { + tool->render(); + } + } + } + if (selection->getSelectType() == SELECT_TYPE_HUD && selection->getObjectCount()) + { + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.popMatrix(); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.popMatrix(); + stop_glerror(); + } + } + } +} + +// Return a point near the clicked object representative of the place the object was clicked. +LLVector3d LLViewerWindow::clickPointInWorldGlobal(S32 x, S32 y_from_bot, LLViewerObject* clicked_object) const +{ + // create a normalized vector pointing from the camera center into the + // world at the location of the mouse click + LLVector3 mouse_direction_global = mouseDirectionGlobal( x, y_from_bot ); + + LLVector3d relative_object = clicked_object->getPositionGlobal() - gAgentCamera.getCameraPositionGlobal(); + + // make mouse vector as long as object vector, so it touchs a point near + // where the user clicked on the object + mouse_direction_global *= (F32) relative_object.magVec(); + + LLVector3d new_pos; + new_pos.setVec(mouse_direction_global); + // transform mouse vector back to world coords + new_pos += gAgentCamera.getCameraPositionGlobal(); + + return new_pos; +} + + +bool LLViewerWindow::clickPointOnSurfaceGlobal(const S32 x, const S32 y, LLViewerObject *objectp, LLVector3d &point_global) const +{ + bool intersect = false; + +// U8 shape = objectp->mPrimitiveCode & LL_PCODE_BASE_MASK; + if (!intersect) + { + point_global = clickPointInWorldGlobal(x, y, objectp); + LL_INFOS() << "approx intersection at " << (objectp->getPositionGlobal() - point_global) << LL_ENDL; + } + else + { + LL_INFOS() << "good intersection at " << (objectp->getPositionGlobal() - point_global) << LL_ENDL; + } + + return intersect; +} + +void LLViewerWindow::pickAsync( S32 x, + S32 y_from_bot, + MASK mask, + void (*callback)(const LLPickInfo& info), + bool pick_transparent, + bool pick_rigged, + bool pick_unselectable, + bool pick_reflection_probes) +{ + // "Show Debug Alpha" means no object actually transparent + bool in_build_mode = LLFloaterReg::instanceVisible("build"); + if (LLDrawPoolAlpha::sShowDebugAlpha + || (in_build_mode && gSavedSettings.getBOOL("SelectInvisibleObjects"))) + { + pick_transparent = true; + } + + LLPickInfo pick_info(LLCoordGL(x, y_from_bot), mask, pick_transparent, pick_rigged, false, pick_reflection_probes, pick_unselectable, true, callback); + schedulePick(pick_info); +} + +void LLViewerWindow::schedulePick(LLPickInfo& pick_info) +{ + if (mPicks.size() >= 1024 || mWindow->getMinimized()) + { //something went wrong, picks are being scheduled but not processed + + if (pick_info.mPickCallback) + { + pick_info.mPickCallback(pick_info); + } + + return; + } + mPicks.push_back(pick_info); + + // delay further event processing until we receive results of pick + // only do this for async picks so that handleMouseUp won't be called + // until the pick triggered in handleMouseDown has been processed, for example + mWindow->delayInputProcessing(); +} + + +void LLViewerWindow::performPick() +{ + if (!mPicks.empty()) + { + std::vector::iterator pick_it; + for (pick_it = mPicks.begin(); pick_it != mPicks.end(); ++pick_it) + { + pick_it->fetchResults(); + } + + mLastPick = mPicks.back(); + mPicks.clear(); + } +} + +void LLViewerWindow::returnEmptyPicks() +{ + std::vector::iterator pick_it; + for (pick_it = mPicks.begin(); pick_it != mPicks.end(); ++pick_it) + { + mLastPick = *pick_it; + // just trigger callback with empty results + if (pick_it->mPickCallback) + { + pick_it->mPickCallback(*pick_it); + } + } + mPicks.clear(); +} + +// Performs the GL object/land pick. +LLPickInfo LLViewerWindow::pickImmediate(S32 x, S32 y_from_bot, bool pick_transparent, bool pick_rigged, bool pick_particle, bool pick_unselectable, bool pick_reflection_probe) +{ + bool in_build_mode = LLFloaterReg::instanceVisible("build"); + if ((in_build_mode && gSavedSettings.getBOOL("SelectInvisibleObjects")) || LLDrawPoolAlpha::sShowDebugAlpha) + { + // build mode allows interaction with all transparent objects + // "Show Debug Alpha" means no object actually transparent + pick_transparent = true; + } + + // shortcut queueing in mPicks and just update mLastPick in place + MASK key_mask = gKeyboard->currentMask(true); + mLastPick = LLPickInfo(LLCoordGL(x, y_from_bot), key_mask, pick_transparent, pick_rigged, pick_particle, pick_reflection_probe, true, false, NULL); + mLastPick.fetchResults(); + + return mLastPick; +} + +LLHUDIcon* LLViewerWindow::cursorIntersectIcon(S32 mouse_x, S32 mouse_y, F32 depth, + LLVector4a* intersection) +{ + S32 x = mouse_x; + S32 y = mouse_y; + + if ((mouse_x == -1) && (mouse_y == -1)) // use current mouse position + { + x = getCurrentMouseX(); + y = getCurrentMouseY(); + } + + // world coordinates of mouse + // VECTORIZE THIS + LLVector3 mouse_direction_global = mouseDirectionGlobal(x,y); + LLVector3 mouse_point_global = LLViewerCamera::getInstance()->getOrigin(); + LLVector3 mouse_world_start = mouse_point_global; + LLVector3 mouse_world_end = mouse_point_global + mouse_direction_global * depth; + + LLVector4a start, end; + start.load3(mouse_world_start.mV); + end.load3(mouse_world_end.mV); + + return LLHUDIcon::lineSegmentIntersectAll(start, end, intersection); +} + +LLViewerObject* LLViewerWindow::cursorIntersect(S32 mouse_x, S32 mouse_y, F32 depth, + LLViewerObject *this_object, + S32 this_face, + bool pick_transparent, + bool pick_rigged, + bool pick_unselectable, + bool pick_reflection_probe, + S32* face_hit, + LLVector4a *intersection, + LLVector2 *uv, + LLVector4a *normal, + LLVector4a *tangent, + LLVector4a* start, + LLVector4a* end) +{ + S32 x = mouse_x; + S32 y = mouse_y; + + if ((mouse_x == -1) && (mouse_y == -1)) // use current mouse position + { + x = getCurrentMouseX(); + y = getCurrentMouseY(); + } + + // HUD coordinates of mouse + LLVector3 mouse_point_hud = mousePointHUD(x, y); + LLVector3 mouse_hud_start = mouse_point_hud - LLVector3(depth, 0, 0); + LLVector3 mouse_hud_end = mouse_point_hud + LLVector3(depth, 0, 0); + + // world coordinates of mouse + LLVector3 mouse_direction_global = mouseDirectionGlobal(x,y); + LLVector3 mouse_point_global = LLViewerCamera::getInstance()->getOrigin(); + + //get near clip plane + LLVector3 n = LLViewerCamera::getInstance()->getAtAxis(); + LLVector3 p = mouse_point_global + n * LLViewerCamera::getInstance()->getNear(); + + //project mouse point onto plane + LLVector3 pos; + line_plane(mouse_point_global, mouse_direction_global, p, n, pos); + mouse_point_global = pos; + + LLVector3 mouse_world_start = mouse_point_global; + LLVector3 mouse_world_end = mouse_point_global + mouse_direction_global * depth; + + if (!LLViewerJoystick::getInstance()->getOverrideCamera()) + { //always set raycast intersection to mouse_world_end unless + //flycam is on (for DoF effect) + gDebugRaycastIntersection.load3(mouse_world_end.mV); + } + + LLVector4a mw_start; + mw_start.load3(mouse_world_start.mV); + LLVector4a mw_end; + mw_end.load3(mouse_world_end.mV); + + LLVector4a mh_start; + mh_start.load3(mouse_hud_start.mV); + LLVector4a mh_end; + mh_end.load3(mouse_hud_end.mV); + + if (start) + { + *start = mw_start; + } + + if (end) + { + *end = mw_end; + } + + LLViewerObject* found = NULL; + + if (this_object) // check only this object + { + if (this_object->isHUDAttachment()) // is a HUD object? + { + if (this_object->lineSegmentIntersect(mh_start, mh_end, this_face, pick_transparent, pick_rigged, pick_unselectable, + face_hit, intersection, uv, normal, tangent)) + { + found = this_object; + } + } + else // is a world object + { + if ((pick_reflection_probe || !this_object->isReflectionProbe()) + && this_object->lineSegmentIntersect(mw_start, mw_end, this_face, pick_transparent, pick_rigged, pick_unselectable, + face_hit, intersection, uv, normal, tangent)) + { + found = this_object; + } + } + } + else // check ALL objects + { + found = gPipeline.lineSegmentIntersectInHUD(mh_start, mh_end, pick_transparent, + face_hit, intersection, uv, normal, tangent); + + if (!found) // if not found in HUD, look in world: + { + found = gPipeline.lineSegmentIntersectInWorld(mw_start, mw_end, pick_transparent, pick_rigged, pick_unselectable, pick_reflection_probe, + face_hit, intersection, uv, normal, tangent); + if (found && !pick_transparent) + { + gDebugRaycastIntersection = *intersection; + } + } + } + + return found; +} + +// Returns unit vector relative to camera +// indicating direction of point on screen x,y +LLVector3 LLViewerWindow::mouseDirectionGlobal(const S32 x, const S32 y) const +{ + // find vertical field of view + F32 fov = LLViewerCamera::getInstance()->getView(); + + // find world view center in scaled ui coordinates + F32 center_x = getWorldViewRectScaled().getCenterX(); + F32 center_y = getWorldViewRectScaled().getCenterY(); + + // calculate pixel distance to screen + F32 distance = ((F32)getWorldViewHeightScaled() * 0.5f) / (tan(fov / 2.f)); + + // calculate click point relative to middle of screen + F32 click_x = x - center_x; + F32 click_y = y - center_y; + + // compute mouse vector + LLVector3 mouse_vector = distance * LLViewerCamera::getInstance()->getAtAxis() + - click_x * LLViewerCamera::getInstance()->getLeftAxis() + + click_y * LLViewerCamera::getInstance()->getUpAxis(); + + mouse_vector.normVec(); + + return mouse_vector; +} + +LLVector3 LLViewerWindow::mousePointHUD(const S32 x, const S32 y) const +{ + // find screen resolution + S32 height = getWorldViewHeightScaled(); + + // find world view center + F32 center_x = getWorldViewRectScaled().getCenterX(); + F32 center_y = getWorldViewRectScaled().getCenterY(); + + // remap with uniform scale (1/height) so that top is -0.5, bottom is +0.5 + F32 hud_x = -((F32)x - center_x) / height; + F32 hud_y = ((F32)y - center_y) / height; + + return LLVector3(0.f, hud_x/gAgentCamera.mHUDCurZoom, hud_y/gAgentCamera.mHUDCurZoom); +} + +// Returns unit vector relative to camera in camera space +// indicating direction of point on screen x,y +LLVector3 LLViewerWindow::mouseDirectionCamera(const S32 x, const S32 y) const +{ + // find vertical field of view + F32 fov_height = LLViewerCamera::getInstance()->getView(); + F32 fov_width = fov_height * LLViewerCamera::getInstance()->getAspect(); + + // find screen resolution + S32 height = getWorldViewHeightScaled(); + S32 width = getWorldViewWidthScaled(); + + // find world view center + F32 center_x = getWorldViewRectScaled().getCenterX(); + F32 center_y = getWorldViewRectScaled().getCenterY(); + + // calculate click point relative to middle of screen + F32 click_x = (((F32)x - center_x) / (F32)width) * fov_width * -1.f; + F32 click_y = (((F32)y - center_y) / (F32)height) * fov_height; + + // compute mouse vector + LLVector3 mouse_vector = LLVector3(0.f, 0.f, -1.f); + LLQuaternion mouse_rotate; + mouse_rotate.setQuat(click_y, click_x, 0.f); + + mouse_vector = mouse_vector * mouse_rotate; + // project to z = -1 plane; + mouse_vector = mouse_vector * (-1.f / mouse_vector.mV[VZ]); + + return mouse_vector; +} + + + +bool LLViewerWindow::mousePointOnPlaneGlobal(LLVector3d& point, const S32 x, const S32 y, + const LLVector3d &plane_point_global, + const LLVector3 &plane_normal_global) +{ + LLVector3d mouse_direction_global_d; + + mouse_direction_global_d.setVec(mouseDirectionGlobal(x,y)); + LLVector3d plane_normal_global_d; + plane_normal_global_d.setVec(plane_normal_global); + F64 plane_mouse_dot = (plane_normal_global_d * mouse_direction_global_d); + LLVector3d plane_origin_camera_rel = plane_point_global - gAgentCamera.getCameraPositionGlobal(); + F64 mouse_look_at_scale = (plane_normal_global_d * plane_origin_camera_rel) + / plane_mouse_dot; + if (llabs(plane_mouse_dot) < 0.00001) + { + // if mouse is parallel to plane, return closest point on line through plane origin + // that is parallel to camera plane by scaling mouse direction vector + // by distance to plane origin, modulated by deviation of mouse direction from plane origin + LLVector3d plane_origin_dir = plane_origin_camera_rel; + plane_origin_dir.normVec(); + + mouse_look_at_scale = plane_origin_camera_rel.magVec() / (plane_origin_dir * mouse_direction_global_d); + } + + point = gAgentCamera.getCameraPositionGlobal() + mouse_look_at_scale * mouse_direction_global_d; + + return mouse_look_at_scale > 0.0; +} + + +// Returns global position +bool LLViewerWindow::mousePointOnLandGlobal(const S32 x, const S32 y, LLVector3d *land_position_global, bool ignore_distance) +{ + LLVector3 mouse_direction_global = mouseDirectionGlobal(x,y); + F32 mouse_dir_scale; + bool hit_land = false; + LLViewerRegion *regionp; + F32 land_z; + const F32 FIRST_PASS_STEP = 1.0f; // meters + const F32 SECOND_PASS_STEP = 0.1f; // meters + const F32 draw_distance = ignore_distance ? MAX_FAR_CLIP : gAgentCamera.mDrawDistance; + LLVector3d camera_pos_global; + + camera_pos_global = gAgentCamera.getCameraPositionGlobal(); + LLVector3d probe_point_global; + LLVector3 probe_point_region; + + // walk forwards to find the point + for (mouse_dir_scale = FIRST_PASS_STEP; mouse_dir_scale < draw_distance; mouse_dir_scale += FIRST_PASS_STEP) + { + LLVector3d mouse_direction_global_d; + mouse_direction_global_d.setVec(mouse_direction_global * mouse_dir_scale); + probe_point_global = camera_pos_global + mouse_direction_global_d; + + regionp = LLWorld::getInstance()->resolveRegionGlobal(probe_point_region, probe_point_global); + + if (!regionp) + { + // ...we're outside the world somehow + continue; + } + + S32 i = (S32) (probe_point_region.mV[VX]/regionp->getLand().getMetersPerGrid()); + S32 j = (S32) (probe_point_region.mV[VY]/regionp->getLand().getMetersPerGrid()); + S32 grids_per_edge = (S32) regionp->getLand().mGridsPerEdge; + if ((i >= grids_per_edge) || (j >= grids_per_edge)) + { + //LL_INFOS() << "LLViewerWindow::mousePointOnLand probe_point is out of region" << LL_ENDL; + continue; + } + + land_z = regionp->getLand().resolveHeightRegion(probe_point_region); + + //LL_INFOS() << "mousePointOnLand initial z " << land_z << LL_ENDL; + + if (probe_point_region.mV[VZ] < land_z) + { + // ...just went under land + + // cout << "under land at " << probe_point << " scale " << mouse_vec_scale << endl; + + hit_land = true; + break; + } + } + + + if (hit_land) + { + // Don't go more than one step beyond where we stopped above. + // This can't just be "mouse_vec_scale" because floating point error + // will stop the loop before the last increment.... X - 1.0 + 0.1 + 0.1 + ... + 0.1 != X + F32 stop_mouse_dir_scale = mouse_dir_scale + FIRST_PASS_STEP; + + // take a step backwards, then walk forwards again to refine position + for ( mouse_dir_scale -= FIRST_PASS_STEP; mouse_dir_scale <= stop_mouse_dir_scale; mouse_dir_scale += SECOND_PASS_STEP) + { + LLVector3d mouse_direction_global_d; + mouse_direction_global_d.setVec(mouse_direction_global * mouse_dir_scale); + probe_point_global = camera_pos_global + mouse_direction_global_d; + + regionp = LLWorld::getInstance()->resolveRegionGlobal(probe_point_region, probe_point_global); + + if (!regionp) + { + // ...we're outside the world somehow + continue; + } + + /* + i = (S32) (local_probe_point.mV[VX]/regionp->getLand().getMetersPerGrid()); + j = (S32) (local_probe_point.mV[VY]/regionp->getLand().getMetersPerGrid()); + if ((i >= regionp->getLand().mGridsPerEdge) || (j >= regionp->getLand().mGridsPerEdge)) + { + // LL_INFOS() << "LLViewerWindow::mousePointOnLand probe_point is out of region" << LL_ENDL; + continue; + } + land_z = regionp->getLand().mSurfaceZ[ i + j * (regionp->getLand().mGridsPerEdge) ]; + */ + + land_z = regionp->getLand().resolveHeightRegion(probe_point_region); + + //LL_INFOS() << "mousePointOnLand refine z " << land_z << LL_ENDL; + + if (probe_point_region.mV[VZ] < land_z) + { + // ...just went under land again + + *land_position_global = probe_point_global; + return true; + } + } + } + + return false; +} + +// Saves an image to the harddrive as "SnapshotX" where X >= 1. +void LLViewerWindow::saveImageNumbered(LLImageFormatted *image, bool force_picker, const snapshot_saved_signal_t::slot_type& success_cb, const snapshot_saved_signal_t::slot_type& failure_cb) +{ + if (!image) + { + LL_WARNS() << "No image to save" << LL_ENDL; + return; + } + std::string extension("." + image->getExtension()); + LLImageFormatted* formatted_image = image; + // Get a base file location if needed. + if (force_picker || !isSnapshotLocSet()) + { + std::string proposed_name(sSnapshotBaseName); + + // getSaveFile will append an appropriate extension to the proposed name, based on the ESaveFilter constant passed in. + LLFilePicker::ESaveFilter pick_type; + + if (extension == ".j2c") + pick_type = LLFilePicker::FFSAVE_J2C; + else if (extension == ".bmp") + pick_type = LLFilePicker::FFSAVE_BMP; + else if (extension == ".jpg") + pick_type = LLFilePicker::FFSAVE_JPEG; + else if (extension == ".png") + pick_type = LLFilePicker::FFSAVE_PNG; + else if (extension == ".tga") + pick_type = LLFilePicker::FFSAVE_TGA; + else + pick_type = LLFilePicker::FFSAVE_ALL; + + LLFilePickerReplyThread::startPicker(boost::bind(&LLViewerWindow::onDirectorySelected, this, _1, formatted_image, success_cb, failure_cb), pick_type, proposed_name, + boost::bind(&LLViewerWindow::onSelectionFailure, this, failure_cb)); + } + else + { + saveImageLocal(formatted_image, success_cb, failure_cb); + } +} + +void LLViewerWindow::onDirectorySelected(const std::vector& filenames, LLImageFormatted *image, const snapshot_saved_signal_t::slot_type& success_cb, const snapshot_saved_signal_t::slot_type& failure_cb) +{ + // Copy the directory + file name + std::string filepath = filenames[0]; + + gSavedPerAccountSettings.setString("SnapshotBaseName", gDirUtilp->getBaseFileName(filepath, true)); + gSavedPerAccountSettings.setString("SnapshotBaseDir", gDirUtilp->getDirName(filepath)); + saveImageLocal(image, success_cb, failure_cb); +} + +void LLViewerWindow::onSelectionFailure(const snapshot_saved_signal_t::slot_type& failure_cb) +{ + failure_cb(); +} + + +void LLViewerWindow::saveImageLocal(LLImageFormatted *image, const snapshot_saved_signal_t::slot_type& success_cb, const snapshot_saved_signal_t::slot_type& failure_cb) +{ + std::string lastSnapshotDir = LLViewerWindow::getLastSnapshotDir(); + if (lastSnapshotDir.empty()) + { + failure_cb(); + return; + } + +// Check if there is enough free space to save snapshot +#ifdef LL_WINDOWS + boost::filesystem::path b_path(utf8str_to_utf16str(lastSnapshotDir)); +#else + boost::filesystem::path b_path(lastSnapshotDir); +#endif + if (!boost::filesystem::is_directory(b_path)) + { + LLSD args; + args["PATH"] = lastSnapshotDir; + LLNotificationsUtil::add("SnapshotToLocalDirNotExist", args); + resetSnapshotLoc(); + failure_cb(); + return; + } + boost::filesystem::space_info b_space = boost::filesystem::space(b_path); + if (b_space.free < image->getDataSize()) + { + LLSD args; + args["PATH"] = lastSnapshotDir; + + std::string needM_bytes_string; + LLResMgr::getInstance()->getIntegerString(needM_bytes_string, (image->getDataSize()) >> 10); + args["NEED_MEMORY"] = needM_bytes_string; + + std::string freeM_bytes_string; + LLResMgr::getInstance()->getIntegerString(freeM_bytes_string, (b_space.free) >> 10); + args["FREE_MEMORY"] = freeM_bytes_string; + + LLNotificationsUtil::add("SnapshotToComputerFailed", args); + + failure_cb(); + } + + // Look for an unused file name + bool is_snapshot_name_loc_set = isSnapshotLocSet(); + std::string filepath; + S32 i = 1; + S32 err = 0; + std::string extension("." + image->getExtension()); + do + { + filepath = sSnapshotDir; + filepath += gDirUtilp->getDirDelimiter(); + filepath += sSnapshotBaseName; + + if (is_snapshot_name_loc_set) + { + filepath += llformat("_%.3d",i); + } + + filepath += extension; + + llstat stat_info; + err = LLFile::stat( filepath, &stat_info ); + i++; + } + while( -1 != err // Search until the file is not found (i.e., stat() gives an error). + && is_snapshot_name_loc_set); // Or stop if we are rewriting. + + LL_INFOS() << "Saving snapshot to " << filepath << LL_ENDL; + if (image->save(filepath)) + { + playSnapshotAnimAndSound(); + success_cb(); + } + else + { + failure_cb(); + } +} + +void LLViewerWindow::resetSnapshotLoc() +{ + gSavedPerAccountSettings.setString("SnapshotBaseDir", std::string()); +} + +// static +void LLViewerWindow::movieSize(S32 new_width, S32 new_height) +{ + LLCoordWindow size; + LLCoordWindow new_size(new_width, new_height); + gViewerWindow->getWindow()->getSize(&size); + if ( size != new_size ) + { + gViewerWindow->getWindow()->setSize(new_size); + } +} + +bool LLViewerWindow::saveSnapshot(const std::string& filepath, S32 image_width, S32 image_height, bool show_ui, bool show_hud, bool do_rebuild, LLSnapshotModel::ESnapshotLayerType type, LLSnapshotModel::ESnapshotFormat format) +{ + LL_INFOS() << "Saving snapshot to: " << filepath << LL_ENDL; + + LLPointer raw = new LLImageRaw; + bool success = rawSnapshot(raw, image_width, image_height, true, false, show_ui, show_hud, do_rebuild); + + if (success) + { + U8 image_codec = IMG_CODEC_BMP; + switch (format) + { + case LLSnapshotModel::SNAPSHOT_FORMAT_PNG: + image_codec = IMG_CODEC_PNG; + break; + case LLSnapshotModel::SNAPSHOT_FORMAT_JPEG: + image_codec = IMG_CODEC_JPEG; + break; + default: + image_codec = IMG_CODEC_BMP; + break; + } + + LLPointer formated_image = LLImageFormatted::createFromType(image_codec); + success = formated_image->encode(raw, 0.0f); + if (success) + { + success = formated_image->save(filepath); + } + else + { + LL_WARNS() << "Unable to encode snapshot of format " << format << LL_ENDL; + } + } + else + { + LL_WARNS() << "Unable to capture raw snapshot" << LL_ENDL; + } + + return success; +} + + +void LLViewerWindow::playSnapshotAnimAndSound() +{ + if (gSavedSettings.getBOOL("QuietSnapshotsToDisk")) + { + return; + } + gAgent.sendAnimationRequest(ANIM_AGENT_SNAPSHOT, ANIM_REQUEST_START); + send_sound_trigger(LLUUID(gSavedSettings.getString("UISndSnapshot")), 1.0f); +} + +bool LLViewerWindow::isSnapshotLocSet() const +{ + std::string snapshot_dir = sSnapshotDir; + return !snapshot_dir.empty(); +} + +void LLViewerWindow::resetSnapshotLoc() const +{ + gSavedPerAccountSettings.setString("SnapshotBaseDir", std::string()); +} + +bool LLViewerWindow::thumbnailSnapshot(LLImageRaw *raw, S32 preview_width, S32 preview_height, bool show_ui, bool show_hud, bool do_rebuild, bool no_post, LLSnapshotModel::ESnapshotLayerType type) +{ + return rawSnapshot(raw, preview_width, preview_height, false, false, show_ui, show_hud, do_rebuild, no_post, type); +} + +// Saves the image from the screen to a raw image +// Since the required size might be bigger than the available screen, this method rerenders the scene in parts (called subimages) and copy +// the results over to the final raw image. +bool LLViewerWindow::rawSnapshot(LLImageRaw *raw, S32 image_width, S32 image_height, + bool keep_window_aspect, bool is_texture, bool show_ui, bool show_hud, bool do_rebuild, bool no_post, LLSnapshotModel::ESnapshotLayerType type, S32 max_size) +{ + if (!raw) + { + return false; + } + + //check if there is enough memory for the snapshot image + if(image_width * image_height > (1 << 22)) //if snapshot image is larger than 2K by 2K + { + if(!LLMemory::tryToAlloc(NULL, image_width * image_height * 3)) + { + LL_WARNS() << "No enough memory to take the snapshot with size (w : h): " << image_width << " : " << image_height << LL_ENDL ; + return false ; //there is no enough memory for taking this snapshot. + } + } + + // PRE SNAPSHOT + gSnapshotNoPost = no_post; + gDisplaySwapBuffers = false; + + glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); // stencil buffer is deprecated | GL_STENCIL_BUFFER_BIT); + setCursor(UI_CURSOR_WAIT); + + // Hide all the UI widgets first and draw a frame + bool prev_draw_ui = gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI); + + if ( prev_draw_ui != show_ui) + { + LLPipeline::toggleRenderDebugFeature(LLPipeline::RENDER_DEBUG_FEATURE_UI); + } + + bool hide_hud = !show_hud && LLPipeline::sShowHUDAttachments; + if (hide_hud) + { + LLPipeline::sShowHUDAttachments = false; + } + + // if not showing ui, use full window to render world view + updateWorldViewRect(!show_ui); + + // Copy screen to a buffer + // crop sides or top and bottom, if taking a snapshot of different aspect ratio + // from window + LLRect window_rect = show_ui ? getWindowRectRaw() : getWorldViewRectRaw(); + + S32 snapshot_width = window_rect.getWidth(); + S32 snapshot_height = window_rect.getHeight(); + // SNAPSHOT + S32 window_width = snapshot_width; + S32 window_height = snapshot_height; + + // Note: Scaling of the UI is currently *not* supported so we limit the output size if UI is requested + if (show_ui) + { + // If the user wants the UI, limit the output size to the available screen size + image_width = llmin(image_width, window_width); + image_height = llmin(image_height, window_height); + } + + S32 original_width = 0; + S32 original_height = 0; + bool reset_deferred = false; + + LLRenderTarget scratch_space; + + F32 scale_factor = 1.0f ; + if (!keep_window_aspect || (image_width > window_width) || (image_height > window_height)) + { + if ((image_width <= gGLManager.mGLMaxTextureSize && image_height <= gGLManager.mGLMaxTextureSize) && + (image_width > window_width || image_height > window_height) && LLPipeline::sRenderDeferred && !show_ui) + { + U32 color_fmt = type == LLSnapshotModel::SNAPSHOT_TYPE_DEPTH ? GL_DEPTH_COMPONENT : GL_RGBA; + if (scratch_space.allocate(image_width, image_height, color_fmt, true)) + { + original_width = gPipeline.mRT->deferredScreen.getWidth(); + original_height = gPipeline.mRT->deferredScreen.getHeight(); + + if (gPipeline.allocateScreenBuffer(image_width, image_height)) + { + window_width = image_width; + window_height = image_height; + snapshot_width = image_width; + snapshot_height = image_height; + reset_deferred = true; + mWorldViewRectRaw.set(0, image_height, image_width, 0); + LLViewerCamera::getInstance()->setViewHeightInPixels( mWorldViewRectRaw.getHeight() ); + LLViewerCamera::getInstance()->setAspect( getWorldViewAspectRatio() ); + scratch_space.bindTarget(); + } + else + { + scratch_space.release(); + gPipeline.allocateScreenBuffer(original_width, original_height); + } + } + } + + if (!reset_deferred) + { + // if image cropping or need to enlarge the scene, compute a scale_factor + F32 ratio = llmin( (F32)window_width / image_width , (F32)window_height / image_height) ; + snapshot_width = (S32)(ratio * image_width) ; + snapshot_height = (S32)(ratio * image_height) ; + scale_factor = llmax(1.0f, 1.0f / ratio) ; + } + } + + if (show_ui && scale_factor > 1.f) + { + // Note: we should never get there... + LL_WARNS() << "over scaling UI not supported." << LL_ENDL; + } + + S32 buffer_x_offset = llfloor(((window_width - snapshot_width) * scale_factor) / 2.f); + S32 buffer_y_offset = llfloor(((window_height - snapshot_height) * scale_factor) / 2.f); + + S32 image_buffer_x = llfloor(snapshot_width * scale_factor) ; + S32 image_buffer_y = llfloor(snapshot_height * scale_factor) ; + + if ((image_buffer_x > max_size) || (image_buffer_y > max_size)) // boundary check to avoid memory overflow + { + scale_factor *= llmin((F32)max_size / image_buffer_x, (F32)max_size / image_buffer_y) ; + image_buffer_x = llfloor(snapshot_width * scale_factor) ; + image_buffer_y = llfloor(snapshot_height * scale_factor) ; + } + + LLImageDataLock lock(raw); + + if ((image_buffer_x > 0) && (image_buffer_y > 0)) + { + raw->resize(image_buffer_x, image_buffer_y, 3); + } + else + { + return false; + } + + if (raw->isBufferInvalid()) + { + return false; + } + + bool high_res = scale_factor >= 2.f; // Font scaling is slow, only do so if rez is much higher + if (high_res && show_ui) + { + // Note: we should never get there... + LL_WARNS() << "High res UI snapshot not supported. " << LL_ENDL; + /*send_agent_pause(); + //rescale fonts + initFonts(scale_factor); + LLHUDObject::reshapeAll();*/ + } + + S32 output_buffer_offset_y = 0; + + F32 depth_conversion_factor_1 = (LLViewerCamera::getInstance()->getFar() + LLViewerCamera::getInstance()->getNear()) / (2.f * LLViewerCamera::getInstance()->getFar() * LLViewerCamera::getInstance()->getNear()); + F32 depth_conversion_factor_2 = (LLViewerCamera::getInstance()->getFar() - LLViewerCamera::getInstance()->getNear()) / (2.f * LLViewerCamera::getInstance()->getFar() * LLViewerCamera::getInstance()->getNear()); + + // Subimages are in fact partial rendering of the final view. This happens when the final view is bigger than the screen. + // In most common cases, scale_factor is 1 and there's no more than 1 iteration on x and y + for (int subimage_y = 0; subimage_y < scale_factor; ++subimage_y) + { + S32 subimage_y_offset = llclamp(buffer_y_offset - (subimage_y * window_height), 0, window_height);; + // handle fractional columns + U32 read_height = llmax(0, (window_height - subimage_y_offset) - + llmax(0, (window_height * (subimage_y + 1)) - (buffer_y_offset + raw->getHeight()))); + + S32 output_buffer_offset_x = 0; + for (int subimage_x = 0; subimage_x < scale_factor; ++subimage_x) + { + gDisplaySwapBuffers = false; + gDepthDirty = true; + + S32 subimage_x_offset = llclamp(buffer_x_offset - (subimage_x * window_width), 0, window_width); + // handle fractional rows + U32 read_width = llmax(0, (window_width - subimage_x_offset) - + llmax(0, (window_width * (subimage_x + 1)) - (buffer_x_offset + raw->getWidth()))); + + // Skip rendering and sampling altogether if either width or height is degenerated to 0 (common in cropping cases) + if (read_width && read_height) + { + const U32 subfield = subimage_x+(subimage_y*llceil(scale_factor)); + display(do_rebuild, scale_factor, subfield, true); + + if (!LLPipeline::sRenderDeferred) + { + // Required for showing the GUI in snapshots and performing bloom composite overlay + // Call even if show_ui is false + render_ui(scale_factor, subfield); + swap(); + } + + for (U32 out_y = 0; out_y < read_height ; out_y++) + { + S32 output_buffer_offset = ( + (out_y * (raw->getWidth())) // ...plus iterated y... + + (window_width * subimage_x) // ...plus subimage start in x... + + (raw->getWidth() * window_height * subimage_y) // ...plus subimage start in y... + - output_buffer_offset_x // ...minus buffer padding x... + - (output_buffer_offset_y * (raw->getWidth())) // ...minus buffer padding y... + ) * raw->getComponents(); + + // Ping the watchdog thread every 100 lines to keep us alive (arbitrary number, feel free to change) + if (out_y % 100 == 0) + { + LLAppViewer::instance()->pingMainloopTimeout("LLViewerWindow::rawSnapshot"); + } + // disable use of glReadPixels when doing nVidia nSight graphics debugging + if (!LLRender::sNsightDebugSupport) + { + if (type == LLSnapshotModel::SNAPSHOT_TYPE_COLOR) + { + glReadPixels( + subimage_x_offset, out_y + subimage_y_offset, + read_width, 1, + GL_RGB, GL_UNSIGNED_BYTE, + raw->getData() + output_buffer_offset + ); + } + else // LLSnapshotModel::SNAPSHOT_TYPE_DEPTH + { + LLPointer depth_line_buffer = new LLImageRaw(read_width, 1, sizeof(GL_FLOAT)); // need to store floating point values + glReadPixels( + subimage_x_offset, out_y + subimage_y_offset, + read_width, 1, + GL_DEPTH_COMPONENT, GL_FLOAT, + depth_line_buffer->getData()// current output pixel is beginning of buffer... + ); + + for (S32 i = 0; i < (S32)read_width; i++) + { + F32 depth_float = *(F32*)(depth_line_buffer->getData() + (i * sizeof(F32))); + + F32 linear_depth_float = 1.f / (depth_conversion_factor_1 - (depth_float * depth_conversion_factor_2)); + U8 depth_byte = F32_to_U8(linear_depth_float, LLViewerCamera::getInstance()->getNear(), LLViewerCamera::getInstance()->getFar()); + // write converted scanline out to result image + for (S32 j = 0; j < raw->getComponents(); j++) + { + *(raw->getData() + output_buffer_offset + (i * raw->getComponents()) + j) = depth_byte; + } + } + } + } + } + } + output_buffer_offset_x += subimage_x_offset; + stop_glerror(); + } + output_buffer_offset_y += subimage_y_offset; + } + + gDisplaySwapBuffers = false; + gSnapshotNoPost = false; + gDepthDirty = true; + + // POST SNAPSHOT + if (!gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) + { + LLPipeline::toggleRenderDebugFeature(LLPipeline::RENDER_DEBUG_FEATURE_UI); + } + + if (hide_hud) + { + LLPipeline::sShowHUDAttachments = true; + } + + /*if (high_res) + { + initFonts(1.f); + LLHUDObject::reshapeAll(); + }*/ + + // Pre-pad image to number of pixels such that the line length is a multiple of 4 bytes (for BMP encoding) + // Note: this formula depends on the number of components being 3. Not obvious, but it's correct. + image_width += (image_width * 3) % 4; + + bool ret = true ; + // Resize image + if(llabs(image_width - image_buffer_x) > 4 || llabs(image_height - image_buffer_y) > 4) + { + ret = raw->scale( image_width, image_height ); + } + else if(image_width != image_buffer_x || image_height != image_buffer_y) + { + ret = raw->scale( image_width, image_height, false ); + } + + setCursor(UI_CURSOR_ARROW); + + if (do_rebuild) + { + // If we had to do a rebuild, that means that the lists of drawables to be rendered + // was empty before we started. + // Need to reset these, otherwise we call state sort on it again when render gets called the next time + // and we stand a good chance of crashing on rebuild because the render drawable arrays have multiple copies of + // objects on them. + gPipeline.resetDrawOrders(); + } + + if (reset_deferred) + { + mWorldViewRectRaw = window_rect; + LLViewerCamera::getInstance()->setViewHeightInPixels( mWorldViewRectRaw.getHeight() ); + LLViewerCamera::getInstance()->setAspect( getWorldViewAspectRatio() ); + scratch_space.flush(); + scratch_space.release(); + gPipeline.allocateScreenBuffer(original_width, original_height); + + } + + if (high_res) + { + send_agent_resume(); + } + + return ret; +} + +bool LLViewerWindow::simpleSnapshot(LLImageRaw* raw, S32 image_width, S32 image_height, const int num_render_passes) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_APP; + gDisplaySwapBuffers = false; + + glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); // stencil buffer is deprecated | GL_STENCIL_BUFFER_BIT); + setCursor(UI_CURSOR_WAIT); + + bool prev_draw_ui = gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI); + if (prev_draw_ui) + { + LLPipeline::toggleRenderDebugFeature(LLPipeline::RENDER_DEBUG_FEATURE_UI); + } + + bool hide_hud = LLPipeline::sShowHUDAttachments; + if (hide_hud) + { + LLPipeline::sShowHUDAttachments = false; + } + + LLRect window_rect = getWorldViewRectRaw(); + + S32 original_width = LLPipeline::sRenderDeferred ? gPipeline.mRT->deferredScreen.getWidth() : gViewerWindow->getWorldViewWidthRaw(); + S32 original_height = LLPipeline::sRenderDeferred ? gPipeline.mRT->deferredScreen.getHeight() : gViewerWindow->getWorldViewHeightRaw(); + + LLRenderTarget scratch_space; + U32 color_fmt = GL_RGBA; + if (scratch_space.allocate(image_width, image_height, color_fmt, true)) + { + if (gPipeline.allocateScreenBuffer(image_width, image_height)) + { + mWorldViewRectRaw.set(0, image_height, image_width, 0); + + scratch_space.bindTarget(); + } + else + { + scratch_space.release(); + gPipeline.allocateScreenBuffer(original_width, original_height); + } + } + + // we render the scene more than once since this helps + // greatly with the objects not being drawn in the + // snapshot when they are drawn in the scene. This is + // evident when you set this value via the debug setting + // called 360CaptureNumRenderPasses to 1. The theory is + // that the missing objects are caused by the sUseOcclusion + // property in pipeline but that the use in pipeline.cpp + // lags by a frame or two so rendering more than once + // appears to help a lot. + for (int i = 0; i < num_render_passes; ++i) + { + // turning this flag off here prohibits the screen swap + // to present the new page to the viewer - this stops + // the black flash in between captures when the number + // of render passes is more than 1. We need to also + // set it here because code in LLViewerDisplay resets + // it to true each time. + gDisplaySwapBuffers = false; + + // actually render the scene + const U32 subfield = 0; + const bool do_rebuild = true; + const F32 zoom = 1.0; + const bool for_snapshot = true; + display(do_rebuild, zoom, subfield, for_snapshot); + } + + LLImageDataSharedLock lock(raw); + + glReadPixels( + 0, 0, + image_width, + image_height, + GL_RGB, GL_UNSIGNED_BYTE, + raw->getData() + ); + stop_glerror(); + + gDisplaySwapBuffers = false; + gDepthDirty = true; + + if (!gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) + { + if (prev_draw_ui) + { + LLPipeline::toggleRenderDebugFeature(LLPipeline::RENDER_DEBUG_FEATURE_UI); + } + } + + if (hide_hud) + { + LLPipeline::sShowHUDAttachments = true; + } + + setCursor(UI_CURSOR_ARROW); + + gPipeline.resetDrawOrders(); + mWorldViewRectRaw = window_rect; + scratch_space.flush(); + scratch_space.release(); + gPipeline.allocateScreenBuffer(original_width, original_height); + + return true; +} + +void display_cube_face(); + +bool LLViewerWindow::cubeSnapshot(const LLVector3& origin, LLCubeMapArray* cubearray, S32 cubeIndex, S32 face, F32 near_clip, bool dynamic_render) +{ + // NOTE: implementation derived from LLFloater360Capture::capture360Images() and simpleSnapshot + LL_PROFILE_ZONE_SCOPED_CATEGORY_APP; + LL_PROFILE_GPU_ZONE("cubeSnapshot"); + llassert(LLPipeline::sRenderDeferred); + llassert(!gCubeSnapshot); //assert a snapshot isn't already in progress + + U32 res = gPipeline.mRT->deferredScreen.getWidth(); + + //llassert(res <= gPipeline.mRT->deferredScreen.getWidth()); + //llassert(res <= gPipeline.mRT->deferredScreen.getHeight()); + + // save current view/camera settings so we can restore them afterwards + S32 old_occlusion = LLPipeline::sUseOcclusion; + + // set new parameters specific to the 360 requirements + LLPipeline::sUseOcclusion = 0; + LLViewerCamera* camera = LLViewerCamera::getInstance(); + + LLViewerCamera saved_camera = LLViewerCamera::instance(); + glh::matrix4f saved_proj = get_current_projection(); + glh::matrix4f saved_mod = get_current_modelview(); + + // camera constants for the square, cube map capture image + camera->setAspect(1.0); // must set aspect ratio first to avoid undesirable clamping of vertical FoV + camera->setViewNoBroadcast(F_PI_BY_TWO); + camera->yaw(0.0); + camera->setOrigin(origin); + camera->setNear(near_clip); + + glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); // stencil buffer is deprecated | GL_STENCIL_BUFFER_BIT); + + U32 dynamic_render_types[] = { + LLPipeline::RENDER_TYPE_AVATAR, + LLPipeline::RENDER_TYPE_CONTROL_AV, + LLPipeline::RENDER_TYPE_PARTICLES + }; + constexpr U32 dynamic_render_type_count = sizeof(dynamic_render_types) / sizeof(U32); + bool prev_dynamic_render_type[dynamic_render_type_count]; + + + if (!dynamic_render) + { + for (int i = 0; i < dynamic_render_type_count; ++i) + { + prev_dynamic_render_type[i] = gPipeline.hasRenderType(dynamic_render_types[i]); + if (prev_dynamic_render_type[i]) + { + gPipeline.toggleRenderType(dynamic_render_types[i]); + } + } + } + + bool prev_draw_ui = gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI); + if (prev_draw_ui) + { + LLPipeline::toggleRenderDebugFeature(LLPipeline::RENDER_DEBUG_FEATURE_UI); + } + + bool hide_hud = LLPipeline::sShowHUDAttachments; + if (hide_hud) + { + LLPipeline::sShowHUDAttachments = false; + } + LLRect window_rect = getWorldViewRectRaw(); + + mWorldViewRectRaw.set(0, res, res, 0); + + // these are the 6 directions we will point the camera, see LLCubeMapArray::sTargets + LLVector3 look_dirs[6] = { + LLVector3(1, 0, 0), + LLVector3(-1, 0, 0), + LLVector3(0, 1, 0), + LLVector3(0, -1, 0), + LLVector3(0, 0, 1), + LLVector3(0, 0, -1) + }; + + LLVector3 look_upvecs[6] = { + LLVector3(0, -1, 0), + LLVector3(0, -1, 0), + LLVector3(0, 0, 1), + LLVector3(0, 0, -1), + LLVector3(0, -1, 0), + LLVector3(0, -1, 0) + }; + + // for each of six sides of cubemap + //for (int i = 0; i < 6; ++i) + int i = face; + { + // set up camera to look in each direction + camera->lookDir(look_dirs[i], look_upvecs[i]); + + // turning this flag off here prohibits the screen swap + // to present the new page to the viewer - this stops + // the black flash in between captures when the number + // of render passes is more than 1. We need to also + // set it here because code in LLViewerDisplay resets + // it to true each time. + gDisplaySwapBuffers = false; + + // actually render the scene + gCubeSnapshot = true; + display_cube_face(); + gCubeSnapshot = false; + } + + gDisplaySwapBuffers = true; + + if (!gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) + { + if (prev_draw_ui) + { + LLPipeline::toggleRenderDebugFeature(LLPipeline::RENDER_DEBUG_FEATURE_UI); + } + } + + if (!dynamic_render) + { + for (int i = 0; i < dynamic_render_type_count; ++i) + { + if (prev_dynamic_render_type[i]) + { + gPipeline.toggleRenderType(dynamic_render_types[i]); + } + } + } + + if (hide_hud) + { + LLPipeline::sShowHUDAttachments = true; + } + + gPipeline.resetDrawOrders(); + mWorldViewRectRaw = window_rect; + + // restore original view/camera/avatar settings settings + *camera = saved_camera; + set_current_modelview(saved_mod); + set_current_projection(saved_proj); + setup3DViewport(); + LLPipeline::sUseOcclusion = old_occlusion; + + // ==================================================== + return true; +} + +void LLViewerWindow::destroyWindow() +{ + if (mWindow) + { + LLWindowManager::destroyWindow(mWindow); + } + mWindow = NULL; +} + + +void LLViewerWindow::drawMouselookInstructions() +{ + // Draw instructions for mouselook ("Press ESC to return to World View" partially transparent at the bottom of the screen.) + const std::string instructions = LLTrans::getString("LeaveMouselook"); + const LLFontGL* font = LLFontGL::getFont(LLFontDescriptor("SansSerif", "Large", LLFontGL::BOLD)); + + //to be on top of Bottom bar when it is opened + const S32 INSTRUCTIONS_PAD = 50; + + font->renderUTF8( + instructions, 0, + getWorldViewRectScaled().getCenterX(), + getWorldViewRectScaled().mBottom + INSTRUCTIONS_PAD, + LLColor4( 1.0f, 1.0f, 1.0f, 0.5f ), + LLFontGL::HCENTER, LLFontGL::TOP, + LLFontGL::NORMAL,LLFontGL::DROP_SHADOW); +} + +void* LLViewerWindow::getPlatformWindow() const +{ + return mWindow->getPlatformWindow(); +} + +void* LLViewerWindow::getMediaWindow() const +{ + return mWindow->getMediaWindow(); +} + +void LLViewerWindow::focusClient() const +{ + return mWindow->focusClient(); +} + +LLRootView* LLViewerWindow::getRootView() const +{ + return mRootView; +} + +LLRect LLViewerWindow::getWorldViewRectScaled() const +{ + return mWorldViewRectScaled; +} + +S32 LLViewerWindow::getWorldViewHeightScaled() const +{ + return mWorldViewRectScaled.getHeight(); +} + +S32 LLViewerWindow::getWorldViewWidthScaled() const +{ + return mWorldViewRectScaled.getWidth(); +} + + +S32 LLViewerWindow::getWorldViewHeightRaw() const +{ + return mWorldViewRectRaw.getHeight(); +} + +S32 LLViewerWindow::getWorldViewWidthRaw() const +{ + return mWorldViewRectRaw.getWidth(); +} + +S32 LLViewerWindow::getWindowHeightScaled() const +{ + return mWindowRectScaled.getHeight(); +} + +S32 LLViewerWindow::getWindowWidthScaled() const +{ + return mWindowRectScaled.getWidth(); +} + +S32 LLViewerWindow::getWindowHeightRaw() const +{ + return mWindowRectRaw.getHeight(); +} + +S32 LLViewerWindow::getWindowWidthRaw() const +{ + return mWindowRectRaw.getWidth(); +} + +void LLViewerWindow::setup2DRender() +{ + // setup ortho camera + gl_state_for_2d(mWindowRectRaw.getWidth(), mWindowRectRaw.getHeight()); + setup2DViewport(); +} + +void LLViewerWindow::setup2DViewport(S32 x_offset, S32 y_offset) +{ + gGLViewport[0] = mWindowRectRaw.mLeft + x_offset; + gGLViewport[1] = mWindowRectRaw.mBottom + y_offset; + gGLViewport[2] = mWindowRectRaw.getWidth(); + gGLViewport[3] = mWindowRectRaw.getHeight(); + glViewport(gGLViewport[0], gGLViewport[1], gGLViewport[2], gGLViewport[3]); +} + + +void LLViewerWindow::setup3DRender() +{ + // setup perspective camera + LLViewerCamera::getInstance()->setPerspective(NOT_FOR_SELECTION, mWorldViewRectRaw.mLeft, mWorldViewRectRaw.mBottom, mWorldViewRectRaw.getWidth(), mWorldViewRectRaw.getHeight(), false, LLViewerCamera::getInstance()->getNear(), MAX_FAR_CLIP*2.f); + setup3DViewport(); +} + +void LLViewerWindow::setup3DViewport(S32 x_offset, S32 y_offset) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI + gGLViewport[0] = mWorldViewRectRaw.mLeft + x_offset; + gGLViewport[1] = mWorldViewRectRaw.mBottom + y_offset; + gGLViewport[2] = mWorldViewRectRaw.getWidth(); + gGLViewport[3] = mWorldViewRectRaw.getHeight(); + glViewport(gGLViewport[0], gGLViewport[1], gGLViewport[2], gGLViewport[3]); +} + +void LLViewerWindow::revealIntroPanel() +{ + if (mProgressView) + { + mProgressView->revealIntroPanel(); + } +} + +void LLViewerWindow::initTextures(S32 location_id) +{ + if (mProgressView) + { + mProgressView->initTextures(location_id, LLGridManager::getInstance()->isInProductionGrid()); + } +} + +void LLViewerWindow::setShowProgress(const bool show) +{ + if (mProgressView) + { + mProgressView->setVisible(show); + } +} + +void LLViewerWindow::setStartupComplete() +{ + if (mProgressView) + { + mProgressView->setStartupComplete(); + } +} + +bool LLViewerWindow::getShowProgress() const +{ + return (mProgressView && mProgressView->getVisible()); +} + +void LLViewerWindow::setProgressString(const std::string& string) +{ + if (mProgressView) + { + mProgressView->setText(string); + } +} + +void LLViewerWindow::setProgressMessage(const std::string& msg) +{ + if(mProgressView) + { + mProgressView->setMessage(msg); + } +} + +void LLViewerWindow::setProgressPercent(const F32 percent) +{ + if (mProgressView) + { + mProgressView->setPercent(percent); + } +} + +void LLViewerWindow::setProgressCancelButtonVisible( bool b, const std::string& label ) +{ + if (mProgressView) + { + mProgressView->setCancelButtonVisible( b, label ); + } +} + +LLProgressView *LLViewerWindow::getProgressView() const +{ + return mProgressView; +} + +void LLViewerWindow::dumpState() +{ + LL_INFOS() << "LLViewerWindow Active " << S32(mActive) << LL_ENDL; + LL_INFOS() << "mWindow visible " << S32(mWindow->getVisible()) + << " minimized " << S32(mWindow->getMinimized()) + << LL_ENDL; +} + +void LLViewerWindow::stopGL(bool save_state) +{ + //Note: --bao + //if not necessary, do not change the order of the function calls in this function. + //if change something, make sure it will not break anything. + //especially be careful to put anything behind gTextureList.destroyGL(save_state); + if (!gGLManager.mIsDisabled) + { + LL_INFOS() << "Shutting down GL..." << LL_ENDL; + + // Pause texture decode threads (will get unpaused during main loop) + LLAppViewer::getTextureCache()->pause(); + LLAppViewer::getTextureFetch()->pause(); + + gSky.destroyGL(); + stop_glerror(); + + LLManipTranslate::destroyGL() ; + stop_glerror(); + + gBumpImageList.destroyGL(); + stop_glerror(); + + LLFontGL::destroyAllGL(); + stop_glerror(); + + LLVOAvatar::destroyGL(); + stop_glerror(); + + LLVOPartGroup::destroyGL(); + + LLViewerDynamicTexture::destroyGL(); + stop_glerror(); + + if (gPipeline.isInit()) + { + gPipeline.destroyGL(); + } + + gBox.cleanupGL(); + + if(gPostProcess) + { + gPostProcess->invalidate(); + } + + gTextureList.destroyGL(save_state); + stop_glerror(); + + gGLManager.mIsDisabled = true; + stop_glerror(); + + //unload shader's + while (LLGLSLShader::sInstances.size()) + { + LLGLSLShader* shader = *(LLGLSLShader::sInstances.begin()); + shader->unload(); + } + } +} + +void LLViewerWindow::restoreGL(const std::string& progress_message) +{ + //Note: --bao + //if not necessary, do not change the order of the function calls in this function. + //if change something, make sure it will not break anything. + //especially, be careful to put something before gTextureList.restoreGL(); + if (gGLManager.mIsDisabled) + { + LL_INFOS() << "Restoring GL..." << LL_ENDL; + gGLManager.mIsDisabled = false; + + initGLDefaults(); + LLGLState::restoreGL(); + + gTextureList.restoreGL(); + + // for future support of non-square pixels, and fonts that are properly stretched + //LLFontGL::destroyDefaultFonts(); + initFonts(); + + gSky.restoreGL(); + gPipeline.restoreGL(); + LLManipTranslate::restoreGL(); + + gBumpImageList.restoreGL(); + LLViewerDynamicTexture::restoreGL(); + LLVOAvatar::restoreGL(); + LLVOPartGroup::restoreGL(); + + gResizeScreenTexture = true; + gWindowResized = true; + + if (isAgentAvatarValid() && gAgentAvatarp->isEditingAppearance()) + { + LLVisualParamHint::requestHintUpdates(); + } + + if (!progress_message.empty()) + { + gRestoreGLTimer.reset(); + gRestoreGL = true; + setShowProgress(true); + setProgressString(progress_message); + } + LL_INFOS() << "...Restoring GL done" << LL_ENDL; + if(!LLAppViewer::instance()->restoreErrorTrap()) + { + LL_WARNS() << " Someone took over my signal/exception handler (post restoreGL)!" << LL_ENDL; + } + + } +} + +void LLViewerWindow::initFonts(F32 zoom_factor) +{ + LLFontGL::destroyAllGL(); + // Initialize with possibly different zoom factor + + LLFontManager::initClass(); + + LLFontGL::initClass( gSavedSettings.getF32("FontScreenDPI"), + mDisplayScale.mV[VX] * zoom_factor, + mDisplayScale.mV[VY] * zoom_factor, + gDirUtilp->getAppRODataDir()); +} + +void LLViewerWindow::requestResolutionUpdate() +{ + mResDirty = true; +} + +static LLTrace::BlockTimerStatHandle FTM_WINDOW_CHECK_SETTINGS("Window Settings"); + +void LLViewerWindow::checkSettings() +{ + LL_RECORD_BLOCK_TIME(FTM_WINDOW_CHECK_SETTINGS); + if (mStatesDirty) + { + gGL.refreshState(); + LLViewerShaderMgr::instance()->setShaders(); + mStatesDirty = false; + } + + // We want to update the resolution AFTER the states getting refreshed not before. + if (mResDirty) + { + reshape(getWindowWidthRaw(), getWindowHeightRaw()); + mResDirty = false; + } +} + +void LLViewerWindow::restartDisplay(bool show_progress_bar) +{ + LL_INFOS() << "Restaring GL" << LL_ENDL; + stopGL(); + if (show_progress_bar) + { + restoreGL(LLTrans::getString("ProgressChangingResolution")); + } + else + { + restoreGL(); + } +} + +bool LLViewerWindow::changeDisplaySettings(LLCoordScreen size, bool enable_vsync, bool show_progress_bar) +{ + //bool was_maximized = gSavedSettings.getBOOL("WindowMaximized"); + + //gResizeScreenTexture = true; + + + //U32 fsaa = gSavedSettings.getU32("RenderFSAASamples"); + //U32 old_fsaa = mWindow->getFSAASamples(); + + // if not maximized, use the request size + if (!mWindow->getMaximized()) + { + mWindow->setSize(size); + } + + //if (fsaa == old_fsaa) + { + return true; + } + +/* + + // Close floaters that don't handle settings change + LLFloaterReg::hideInstance("snapshot"); + + bool result_first_try = false; + bool result_second_try = false; + + LLFocusableElement* keyboard_focus = gFocusMgr.getKeyboardFocus(); + send_agent_pause(); + LL_INFOS() << "Stopping GL during changeDisplaySettings" << LL_ENDL; + stopGL(); + mIgnoreActivate = true; + LLCoordScreen old_size; + LLCoordScreen old_pos; + mWindow->getSize(&old_size); + + //mWindow->setFSAASamples(fsaa); + + result_first_try = mWindow->switchContext(false, size, disable_vsync); + if (!result_first_try) + { + // try to switch back + //mWindow->setFSAASamples(old_fsaa); + result_second_try = mWindow->switchContext(false, old_size, disable_vsync); + + if (!result_second_try) + { + // we are stuck...try once again with a minimal resolution? + send_agent_resume(); + mIgnoreActivate = false; + return false; + } + } + send_agent_resume(); + + LL_INFOS() << "Restoring GL during resolution change" << LL_ENDL; + if (show_progress_bar) + { + restoreGL(LLTrans::getString("ProgressChangingResolution")); + } + else + { + restoreGL(); + } + + if (!result_first_try) + { + LLSD args; + args["RESX"] = llformat("%d",size.mX); + args["RESY"] = llformat("%d",size.mY); + LLNotificationsUtil::add("ResolutionSwitchFail", args); + size = old_size; // for reshape below + } + + bool success = result_first_try || result_second_try; + + if (success) + { + // maximize window if was maximized, else reposition + if (was_maximized) + { + mWindow->maximize(); + } + else + { + S32 windowX = gSavedSettings.getS32("WindowX"); + S32 windowY = gSavedSettings.getS32("WindowY"); + + mWindow->setPosition(LLCoordScreen ( windowX, windowY ) ); + } + } + + mIgnoreActivate = false; + gFocusMgr.setKeyboardFocus(keyboard_focus); + + return success; + + */ +} + +F32 LLViewerWindow::getWorldViewAspectRatio() const +{ + F32 world_aspect = (F32)mWorldViewRectRaw.getWidth() / (F32)mWorldViewRectRaw.getHeight(); + return world_aspect; +} + +void LLViewerWindow::calcDisplayScale() +{ + F32 ui_scale_factor = llclamp(gSavedSettings.getF32("UIScaleFactor") * mWindow->getSystemUISize(), MIN_UI_SCALE, MAX_UI_SCALE); + LLVector2 display_scale; + display_scale.setVec(llmax(1.f / mWindow->getPixelAspectRatio(), 1.f), llmax(mWindow->getPixelAspectRatio(), 1.f)); + display_scale *= ui_scale_factor; + + // limit minimum display scale + if (display_scale.mV[VX] < MIN_DISPLAY_SCALE || display_scale.mV[VY] < MIN_DISPLAY_SCALE) + { + display_scale *= MIN_DISPLAY_SCALE / llmin(display_scale.mV[VX], display_scale.mV[VY]); + } + + if (display_scale != mDisplayScale) + { + LL_INFOS() << "Setting display scale to " << display_scale << " for ui scale: " << ui_scale_factor << LL_ENDL; + + mDisplayScale = display_scale; + // Init default fonts + initFonts(); + } +} + +//static +LLRect LLViewerWindow::calcScaledRect(const LLRect & rect, const LLVector2& display_scale) +{ + LLRect res = rect; + res.mLeft = ll_round((F32)res.mLeft / display_scale.mV[VX]); + res.mRight = ll_round((F32)res.mRight / display_scale.mV[VX]); + res.mBottom = ll_round((F32)res.mBottom / display_scale.mV[VY]); + res.mTop = ll_round((F32)res.mTop / display_scale.mV[VY]); + + return res; +} + +S32 LLViewerWindow::getChatConsoleBottomPad() +{ + S32 offset = 0; + + if(gToolBarView) + offset += gToolBarView->getBottomToolbar()->getRect().getHeight(); + + return offset; +} + +LLRect LLViewerWindow::getChatConsoleRect() +{ + LLRect full_window(0, getWindowHeightScaled(), getWindowWidthScaled(), 0); + LLRect console_rect = full_window; + + const S32 CONSOLE_PADDING_TOP = 24; + const S32 CONSOLE_PADDING_LEFT = 24; + const S32 CONSOLE_PADDING_RIGHT = 10; + + console_rect.mTop -= CONSOLE_PADDING_TOP; + console_rect.mBottom += getChatConsoleBottomPad(); + + console_rect.mLeft += CONSOLE_PADDING_LEFT; + + static const bool CHAT_FULL_WIDTH = gSavedSettings.getBOOL("ChatFullWidth"); + + if (CHAT_FULL_WIDTH) + { + console_rect.mRight -= CONSOLE_PADDING_RIGHT; + } + else + { + // Make console rect somewhat narrow so having inventory open is + // less of a problem. + console_rect.mRight = console_rect.mLeft + 2 * getWindowWidthScaled() / 3; + } + + return console_rect; +} + +void LLViewerWindow::reshapeStatusBarContainer() +{ + LLPanel* status_bar_container = getRootView()->getChild("status_bar_container"); + LLView* nav_bar_container = getRootView()->getChild("nav_bar_container"); + + S32 new_height = status_bar_container->getRect().getHeight(); + S32 new_width = status_bar_container->getRect().getWidth(); + + if (gSavedSettings.getBOOL("ShowNavbarNavigationPanel")) + { + // Navigation bar is outside visible area, expand status_bar_container to show it + new_height += nav_bar_container->getRect().getHeight(); + } + else + { + // collapse status_bar_container + new_height -= nav_bar_container->getRect().getHeight(); + } + status_bar_container->reshape(new_width, new_height, true); +} + +void LLViewerWindow::resetStatusBarContainer() +{ + LLNavigationBar* navbar = LLNavigationBar::getInstance(); + if (gSavedSettings.getBOOL("ShowNavbarNavigationPanel") || navbar->getVisible()) + { + // was previously showing navigation bar + LLView* nav_bar_container = getRootView()->getChild("nav_bar_container"); + LLPanel* status_bar_container = getRootView()->getChild("status_bar_container"); + S32 new_height = status_bar_container->getRect().getHeight(); + S32 new_width = status_bar_container->getRect().getWidth(); + new_height -= nav_bar_container->getRect().getHeight(); + status_bar_container->reshape(new_width, new_height, true); + } +} +//---------------------------------------------------------------------------- + + +void LLViewerWindow::setUIVisibility(bool visible) +{ + mUIVisible = visible; + + if (!visible) + { + gAgentCamera.changeCameraToThirdPerson(false); + gFloaterView->hideAllFloaters(); + } + else + { + gFloaterView->showHiddenFloaters(); + } + + if (gToolBarView) + { + gToolBarView->setToolBarsVisible(visible); + } + + LLNavigationBar::getInstance()->setVisible(visible ? gSavedSettings.getBOOL("ShowNavbarNavigationPanel") : false); + LLPanelTopInfoBar::getInstance()->setVisible(visible? gSavedSettings.getBOOL("ShowMiniLocationPanel") : false); + mRootView->getChildView("status_bar_container")->setVisible(visible); +} + +bool LLViewerWindow::getUIVisibility() +{ + return mUIVisible; +} + +//////////////////////////////////////////////////////////////////////////// +// +// LLPickInfo +// +LLPickInfo::LLPickInfo() + : mKeyMask(MASK_NONE), + mPickCallback(NULL), + mPickType(PICK_INVALID), + mWantSurfaceInfo(false), + mObjectFace(-1), + mUVCoords(-1.f, -1.f), + mSTCoords(-1.f, -1.f), + mXYCoords(-1, -1), + mIntersection(), + mNormal(), + mTangent(), + mBinormal(), + mHUDIcon(NULL), + mPickTransparent(false), + mPickRigged(false), + mPickParticle(false) +{ +} + +LLPickInfo::LLPickInfo(const LLCoordGL& mouse_pos, + MASK keyboard_mask, + bool pick_transparent, + bool pick_rigged, + bool pick_particle, + bool pick_reflection_probe, + bool pick_uv_coords, + bool pick_unselectable, + void (*pick_callback)(const LLPickInfo& pick_info)) + : mMousePt(mouse_pos), + mKeyMask(keyboard_mask), + mPickCallback(pick_callback), + mPickType(PICK_INVALID), + mWantSurfaceInfo(pick_uv_coords), + mObjectFace(-1), + mUVCoords(-1.f, -1.f), + mSTCoords(-1.f, -1.f), + mXYCoords(-1, -1), + mNormal(), + mTangent(), + mBinormal(), + mHUDIcon(NULL), + mPickTransparent(pick_transparent), + mPickRigged(pick_rigged), + mPickParticle(pick_particle), + mPickReflectionProbe(pick_reflection_probe), + mPickUnselectable(pick_unselectable) +{ +} + +void LLPickInfo::fetchResults() +{ + + S32 face_hit = -1; + LLVector4a intersection, normal; + LLVector4a tangent; + + LLVector2 uv; + + LLHUDIcon* hit_icon = gViewerWindow->cursorIntersectIcon(mMousePt.mX, mMousePt.mY, 512.f, &intersection); + + LLVector4a origin; + origin.load3(LLViewerCamera::getInstance()->getOrigin().mV); + F32 icon_dist = 0.f; + LLVector4a start; + LLVector4a end; + LLVector4a particle_end; + + if (hit_icon) + { + LLVector4a delta; + delta.setSub(intersection, origin); + icon_dist = delta.getLength3().getF32(); + } + + LLViewerObject* hit_object = gViewerWindow->cursorIntersect(mMousePt.mX, mMousePt.mY, 512.f, + NULL, -1, mPickTransparent, mPickRigged, mPickUnselectable, mPickReflectionProbe, &face_hit, + &intersection, &uv, &normal, &tangent, &start, &end); + + mPickPt = mMousePt; + + U32 te_offset = face_hit > -1 ? face_hit : 0; + + if (mPickParticle) + { //get the end point of line segement to use for particle raycast + if (hit_object) + { + particle_end = intersection; + } + else + { + particle_end = end; + } + } + + LLViewerObject* objectp = hit_object; + + + LLVector4a delta; + delta.setSub(origin, intersection); + + if (hit_icon && + (!objectp || + icon_dist < delta.getLength3().getF32())) + { + // was this name referring to a hud icon? + mHUDIcon = hit_icon; + mPickType = PICK_ICON; + mPosGlobal = mHUDIcon->getPositionGlobal(); + + } + else if (objectp) + { + if( objectp->getPCode() == LLViewerObject::LL_VO_SURFACE_PATCH ) + { + // Hit land + mPickType = PICK_LAND; + mObjectID.setNull(); // land has no id + + // put global position into land_pos + LLVector3d land_pos; + if (!gViewerWindow->mousePointOnLandGlobal(mPickPt.mX, mPickPt.mY, &land_pos, mPickUnselectable)) + { + // The selected point is beyond the draw distance or is otherwise + // not selectable. Return before calling mPickCallback(). + return; + } + + // Fudge the land focus a little bit above ground. + mPosGlobal = land_pos + LLVector3d::z_axis * 0.1f; + } + else + { + if(isFlora(objectp)) + { + mPickType = PICK_FLORA; + } + else + { + mPickType = PICK_OBJECT; + } + + LLVector3 v_intersection(intersection.getF32ptr()); + + mObjectOffset = gAgentCamera.calcFocusOffset(objectp, v_intersection, mPickPt.mX, mPickPt.mY); + mObjectID = objectp->mID; + mObjectFace = (te_offset == NO_FACE) ? -1 : (S32)te_offset; + + + + mPosGlobal = gAgent.getPosGlobalFromAgent(v_intersection); + + if (mWantSurfaceInfo) + { + getSurfaceInfo(); + } + } + } + + if (mPickParticle) + { //search for closest particle to click origin out to intersection point + S32 part_face = -1; + + LLVOPartGroup* group = gPipeline.lineSegmentIntersectParticle(start, particle_end, NULL, &part_face); + if (group) + { + mParticleOwnerID = group->getPartOwner(part_face); + mParticleSourceID = group->getPartSource(part_face); + } + } + + if (mPickCallback) + { + mPickCallback(*this); + } +} + +LLPointer LLPickInfo::getObject() const +{ + return gObjectList.findObject( mObjectID ); +} + +void LLPickInfo::updateXYCoords() +{ + if (mObjectFace > -1) + { + const LLTextureEntry* tep = getObject()->getTE(mObjectFace); + LLPointer imagep = LLViewerTextureManager::getFetchedTexture(tep->getID()); + if(mUVCoords.mV[VX] >= 0.f && mUVCoords.mV[VY] >= 0.f && imagep.notNull()) + { + mXYCoords.mX = ll_round(mUVCoords.mV[VX] * (F32)imagep->getWidth()); + mXYCoords.mY = ll_round((1.f - mUVCoords.mV[VY]) * (F32)imagep->getHeight()); + } + } +} + +void LLPickInfo::getSurfaceInfo() +{ + // set values to uninitialized - this is what we return if no intersection is found + mObjectFace = -1; + mUVCoords = LLVector2(-1, -1); + mSTCoords = LLVector2(-1, -1); + mXYCoords = LLCoordScreen(-1, -1); + mIntersection = LLVector3(0,0,0); + mNormal = LLVector3(0,0,0); + mBinormal = LLVector3(0,0,0); + mTangent = LLVector4(0,0,0,0); + + LLVector4a tangent; + LLVector4a intersection; + LLVector4a normal; + + tangent.clear(); + normal.clear(); + intersection.clear(); + + LLViewerObject* objectp = getObject(); + + if (objectp) + { + if (gViewerWindow->cursorIntersect(ll_round((F32)mMousePt.mX), ll_round((F32)mMousePt.mY), 1024.f, + objectp, -1, mPickTransparent, mPickRigged, mPickUnselectable, mPickReflectionProbe, + &mObjectFace, + &intersection, + &mSTCoords, + &normal, + &tangent)) + { + // if we succeeded with the intersect above, compute the texture coordinates: + + if (objectp->mDrawable.notNull() && mObjectFace > -1) + { + LLFace* facep = objectp->mDrawable->getFace(mObjectFace); + if (facep) + { + mUVCoords = facep->surfaceToTexture(mSTCoords, intersection, normal); + } + } + + mIntersection.set(intersection.getF32ptr()); + mNormal.set(normal.getF32ptr()); + mTangent.set(tangent.getF32ptr()); + + //extrapoloate binormal from normal and tangent + + LLVector4a binormal; + binormal.setCross3(normal, tangent); + binormal.mul(tangent.getF32ptr()[3]); + + mBinormal.set(binormal.getF32ptr()); + + mBinormal.normalize(); + mNormal.normalize(); + mTangent.normalize(); + + // and XY coords: + updateXYCoords(); + + } + } +} + +//static +bool LLPickInfo::isFlora(LLViewerObject* object) +{ + if (!object) return false; + + LLPCode pcode = object->getPCode(); + + if( (LL_PCODE_LEGACY_GRASS == pcode) + || (LL_PCODE_LEGACY_TREE == pcode) + || (LL_PCODE_TREE_NEW == pcode)) + { + return true; + } + return false; +} diff --git a/indra/newview/llviewerwindow.h b/indra/newview/llviewerwindow.h index fc3bb1bd3d..f4682780cb 100644 --- a/indra/newview/llviewerwindow.h +++ b/indra/newview/llviewerwindow.h @@ -1,578 +1,578 @@ -/** - * @file llviewerwindow.h - * @brief Description of the LLViewerWindow class. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -// -// A note about X,Y coordinates: -// -// X coordinates are in pixels, from the left edge of the window client area -// Y coordinates are in pixels, from the BOTTOM edge of the window client area -// -// The Y coordinates therefore match OpenGL window coords, not Windows(tm) window coords. -// If Y is from the top, the variable will be called "y_from_top" - -#ifndef LL_LLVIEWERWINDOW_H -#define LL_LLVIEWERWINDOW_H - -#include "v3dmath.h" -#include "v2math.h" -#include "llcursortypes.h" -#include "llwindowcallbacks.h" -#include "lltimer.h" -#include "llmousehandler.h" -#include "llnotifications.h" -#include "llhandle.h" -#include "llinitparam.h" -#include "lltrace.h" -#include "llsnapshotmodel.h" - -#include -#include -#include - -class LLView; -class LLViewerObject; -class LLUUID; -class LLProgressView; -class LLTool; -class LLVelocityBar; -class LLPanel; -class LLImageRaw; -class LLImageFormatted; -class LLHUDIcon; -class LLWindow; -class LLRootView; -class LLWindowListener; -class LLViewerWindowListener; -class LLVOPartGroup; -class LLPopupView; -class LLCubeMap; -class LLCubeMapArray; - -#define PICK_HALF_WIDTH 5 -#define PICK_DIAMETER (2 * PICK_HALF_WIDTH + 1) - -class LLPickInfo -{ -public: - typedef enum - { - PICK_OBJECT, - PICK_FLORA, - PICK_LAND, - PICK_ICON, - PICK_PARCEL_WALL, - PICK_INVALID - } EPickType; - -public: - LLPickInfo(); - LLPickInfo(const LLCoordGL& mouse_pos, - MASK keyboard_mask, - bool pick_transparent, - bool pick_rigged, - bool pick_particle, - bool pick_reflection_probe, - bool pick_surface_info, - bool pick_unselectable, - void (*pick_callback)(const LLPickInfo& pick_info)); - - void fetchResults(); - LLPointer getObject() const; - LLUUID getObjectID() const { return mObjectID; } - bool isValid() const { return mPickType != PICK_INVALID; } - - static bool isFlora(LLViewerObject* object); - -public: - LLCoordGL mMousePt; - MASK mKeyMask; - void (*mPickCallback)(const LLPickInfo& pick_info); - - EPickType mPickType; - LLCoordGL mPickPt; - LLVector3d mPosGlobal; - LLVector3 mObjectOffset; - LLUUID mObjectID; - LLUUID mParticleOwnerID; - LLUUID mParticleSourceID; - S32 mObjectFace; - LLHUDIcon* mHUDIcon; - LLVector3 mIntersection; - LLVector2 mUVCoords; - LLVector2 mSTCoords; - LLCoordScreen mXYCoords; - LLVector3 mNormal; - LLVector4 mTangent; - LLVector3 mBinormal; - bool mPickTransparent; - bool mPickRigged; - bool mPickParticle; - bool mPickUnselectable; - bool mPickReflectionProbe = false; - void getSurfaceInfo(); - -private: - void updateXYCoords(); - - bool mWantSurfaceInfo; // do we populate mUVCoord, mNormal, mBinormal? - -}; - -static const U32 MAX_SNAPSHOT_IMAGE_SIZE = 7680; // max snapshot image size 7680 * 7680 UHDTV2 - -class LLViewerWindow : public LLWindowCallbacks -{ -public: - // - // CREATORS - // - struct Params : public LLInitParam::Block - { - Mandatory title, - name; - Mandatory x, - y, - width, - height, - min_width, - min_height; - Optional fullscreen, - ignore_pixel_depth, - first_run; - - Params(); - }; - - LLViewerWindow(const Params& p); - virtual ~LLViewerWindow(); - - void shutdownViews(); - void shutdownGL(); - - void initGLDefaults(); - void initBase(); - void adjustRectanglesForFirstUse(const LLRect& window); - void adjustControlRectanglesForFirstUse(const LLRect& window); - void initWorldUI(); - void setUIVisibility(bool); - bool getUIVisibility(); - void handlePieMenu(S32 x, S32 y, MASK mask); - - void reshapeStatusBarContainer(); - void resetStatusBarContainer(); // undo changes done by resetStatusBarContainer on initWorldUI() - - bool handleAnyMouseClick(LLWindow *window, LLCoordGL pos, MASK mask, EMouseClickType clicktype, bool down, bool &is_toolmgr_action); - - // - // LLWindowCallback interface implementation - // - /*virtual*/ bool handleTranslatedKeyDown(KEY key, MASK mask, bool repeated); - /*virtual*/ bool handleTranslatedKeyUp(KEY key, MASK mask); - /*virtual*/ void handleScanKey(KEY key, bool key_down, bool key_up, bool key_level); - /*virtual*/ bool handleUnicodeChar(llwchar uni_char, MASK mask); // NOT going to handle extended - /*virtual*/ bool handleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask); - /*virtual*/ bool handleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask); - /*virtual*/ bool handleCloseRequest(LLWindow *window); - /*virtual*/ void handleQuit(LLWindow *window); - /*virtual*/ bool handleRightMouseDown(LLWindow *window, LLCoordGL pos, MASK mask); - /*virtual*/ bool handleRightMouseUp(LLWindow *window, LLCoordGL pos, MASK mask); - /*virtual*/ bool handleMiddleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask); - /*virtual*/ bool handleMiddleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask); - /*virtual*/ bool handleOtherMouseDown(LLWindow *window, LLCoordGL pos, MASK mask, S32 button); - /*virtual*/ bool handleOtherMouseUp(LLWindow *window, LLCoordGL pos, MASK mask, S32 button); - bool handleOtherMouse(LLWindow *window, LLCoordGL pos, MASK mask, S32 button, bool down); - /*virtual*/ LLWindowCallbacks::DragNDropResult handleDragNDrop(LLWindow *window, LLCoordGL pos, MASK mask, LLWindowCallbacks::DragNDropAction action, std::string data); - void handleMouseMove(LLWindow *window, LLCoordGL pos, MASK mask); - void handleMouseDragged(LLWindow *window, LLCoordGL pos, MASK mask); - /*virtual*/ void handleMouseLeave(LLWindow *window); - /*virtual*/ void handleResize(LLWindow *window, S32 x, S32 y); - /*virtual*/ void handleFocus(LLWindow *window); - /*virtual*/ void handleFocusLost(LLWindow *window); - /*virtual*/ bool handleActivate(LLWindow *window, bool activated); - /*virtual*/ bool handleActivateApp(LLWindow *window, bool activating); - /*virtual*/ void handleMenuSelect(LLWindow *window, S32 menu_item); - /*virtual*/ bool handlePaint(LLWindow *window, S32 x, S32 y, S32 width, S32 height); - /*virtual*/ void handleScrollWheel(LLWindow *window, S32 clicks); - /*virtual*/ void handleScrollHWheel(LLWindow *window, S32 clicks); - /*virtual*/ bool handleDoubleClick(LLWindow *window, LLCoordGL pos, MASK mask); - /*virtual*/ void handleWindowBlock(LLWindow *window); - /*virtual*/ void handleWindowUnblock(LLWindow *window); - /*virtual*/ void handleDataCopy(LLWindow *window, S32 data_type, void *data); - /*virtual*/ bool handleTimerEvent(LLWindow *window); - /*virtual*/ bool handleDeviceChange(LLWindow *window); - /*virtual*/ bool handleDPIChanged(LLWindow *window, F32 ui_scale_factor, S32 window_width, S32 window_height); - /*virtual*/ bool handleWindowDidChangeScreen(LLWindow *window); - - /*virtual*/ void handlePingWatchdog(LLWindow *window, const char * msg); - /*virtual*/ void handlePauseWatchdog(LLWindow *window); - /*virtual*/ void handleResumeWatchdog(LLWindow *window); - /*virtual*/ std::string translateString(const char* tag); - /*virtual*/ std::string translateString(const char* tag, - const std::map& args); - - // signal on update of WorldView rect - typedef boost::function world_rect_callback_t; - typedef boost::signals2::signal world_rect_signal_t; - world_rect_signal_t mOnWorldViewRectUpdated; - boost::signals2::connection setOnWorldViewRectUpdated(world_rect_callback_t cb) { return mOnWorldViewRectUpdated.connect(cb); } - - // - // ACCESSORS - // - LLRootView* getRootView() const; - - // 3D world area in scaled pixels (via UI scale), use for most UI computations - LLRect getWorldViewRectScaled() const; - S32 getWorldViewHeightScaled() const; - S32 getWorldViewWidthScaled() const; - - // 3D world area, in raw unscaled pixels - LLRect getWorldViewRectRaw() const { return mWorldViewRectRaw; } - S32 getWorldViewHeightRaw() const; - S32 getWorldViewWidthRaw() const; - - // Window in scaled pixels (via UI scale), use for most UI computations - LLRect getWindowRectScaled() const { return mWindowRectScaled; } - S32 getWindowHeightScaled() const; - S32 getWindowWidthScaled() const; - - // Window in raw pixels as seen on screen. - LLRect getWindowRectRaw() const { return mWindowRectRaw; } - S32 getWindowHeightRaw() const; - S32 getWindowWidthRaw() const; - - LLWindow* getWindow() const { return mWindow; } - void* getPlatformWindow() const; - void* getMediaWindow() const; - void focusClient() const; - - LLCoordGL getLastMouse() const { return mLastMousePoint; } - S32 getLastMouseX() const { return mLastMousePoint.mX; } - S32 getLastMouseY() const { return mLastMousePoint.mY; } - LLCoordGL getCurrentMouse() const { return mCurrentMousePoint; } - S32 getCurrentMouseX() const { return mCurrentMousePoint.mX; } - S32 getCurrentMouseY() const { return mCurrentMousePoint.mY; } - S32 getCurrentMouseDX() const { return mCurrentMouseDelta.mX; } - S32 getCurrentMouseDY() const { return mCurrentMouseDelta.mY; } - LLCoordGL getCurrentMouseDelta() const { return mCurrentMouseDelta; } - static LLTrace::SampleStatHandle<>* getMouseVelocityStat() { return &sMouseVelocityStat; } - bool getLeftMouseDown() const { return mLeftMouseDown; } - bool getMiddleMouseDown() const { return mMiddleMouseDown; } - bool getRightMouseDown() const { return mRightMouseDown; } - - const LLPickInfo& getLastPick() const { return mLastPick; } - - void setup2DViewport(S32 x_offset = 0, S32 y_offset = 0); - void setup3DViewport(S32 x_offset = 0, S32 y_offset = 0); - void setup3DRender(); - void setup2DRender(); - - LLVector3 mouseDirectionGlobal(const S32 x, const S32 y) const; - LLVector3 mouseDirectionCamera(const S32 x, const S32 y) const; - LLVector3 mousePointHUD(const S32 x, const S32 y) const; - - - // Is window of our application frontmost? - bool getActive() const { return mActive; } - - const std::string& getInitAlert() { return mInitAlert; } - - // - // MANIPULATORS - // - void saveLastMouse(const LLCoordGL &point); - - void setCursor( ECursorType c ); - void showCursor(); - void hideCursor(); - bool getCursorHidden() { return mCursorHidden; } - void moveCursorToCenter(); // move to center of window - - void initTextures(S32 location_id); - void setShowProgress(const bool show); - bool getShowProgress() const; - void setProgressString(const std::string& string); - void setProgressPercent(const F32 percent); - void setProgressMessage(const std::string& msg); - void setProgressCancelButtonVisible( bool b, const std::string& label = LLStringUtil::null ); - LLProgressView *getProgressView() const; - void revealIntroPanel(); - void setStartupComplete(); - - void updateObjectUnderCursor(); - - void updateUI(); // Once per frame, update UI based on mouse position, calls following update* functions - void updateLayout(); - void updateMouseDelta(); - void updateKeyboardFocus(); - - void updateWorldViewRect(bool use_full_window=false); - LLView* getToolBarHolder() { return mToolBarHolder.get(); } - LLView* getHintHolder() { return mHintHolder.get(); } - LLView* getLoginPanelHolder() { return mLoginPanelHolder.get(); } - bool handleKey(KEY key, MASK mask); - bool handleKeyUp(KEY key, MASK mask); - void handleScrollWheel (S32 clicks); - void handleScrollHWheel (S32 clicks); - - // add and remove views from "popup" layer - void addPopup(LLView* popup); - void removePopup(LLView* popup); - void clearPopups(); - - // Hide normal UI when a logon fails, re-show everything when logon is attempted again - void setNormalControlsVisible( bool visible ); - void setMenuBackgroundColor(bool god_mode = false, bool dev_grid = false); - - void reshape(S32 width, S32 height); - void sendShapeToSim(); - - void draw(); - void updateDebugText(); - void drawDebugText(); - - static void loadUserImage(void **cb_data, const LLUUID &uuid); - - static void movieSize(S32 new_width, S32 new_height); - - // snapshot functionality. - // perhaps some of this should move to llfloatershapshot? -MG - - bool saveSnapshot(const std::string& filename, S32 image_width, S32 image_height, bool show_ui = true, bool show_hud = true, bool do_rebuild = false, LLSnapshotModel::ESnapshotLayerType type = LLSnapshotModel::SNAPSHOT_TYPE_COLOR, LLSnapshotModel::ESnapshotFormat format = LLSnapshotModel::SNAPSHOT_FORMAT_BMP); - bool rawSnapshot(LLImageRaw *raw, S32 image_width, S32 image_height, bool keep_window_aspect = true, bool is_texture = false, - bool show_ui = true, bool show_hud = true, bool do_rebuild = false, bool no_post = false, LLSnapshotModel::ESnapshotLayerType type = LLSnapshotModel::SNAPSHOT_TYPE_COLOR, S32 max_size = MAX_SNAPSHOT_IMAGE_SIZE); - - bool simpleSnapshot(LLImageRaw *raw, S32 image_width, S32 image_height, const int num_render_passes); - - - - // take a cubemap snapshot - // origin - vantage point to take the snapshot from - // cubearray - cubemap array for storing the results - // index - cube index in the array to use (cube index, not face-layer) - // face - which cube face to update - // near_clip - near clip setting to use - bool cubeSnapshot(const LLVector3& origin, LLCubeMapArray* cubearray, S32 index, S32 face, F32 near_clip, bool render_avatars); - - - // special implementation of simpleSnapshot for reflection maps - bool reflectionSnapshot(LLImageRaw* raw, S32 image_width, S32 image_height, const int num_render_passes); - - bool thumbnailSnapshot(LLImageRaw *raw, S32 preview_width, S32 preview_height, bool show_ui, bool show_hud, bool do_rebuild, bool no_post, LLSnapshotModel::ESnapshotLayerType type); - bool isSnapshotLocSet() const; - void resetSnapshotLoc() const; - - typedef boost::signals2::signal snapshot_saved_signal_t; - - void saveImageNumbered(LLImageFormatted *image, bool force_picker, const snapshot_saved_signal_t::slot_type& success_cb, const snapshot_saved_signal_t::slot_type& failure_cb); - void onDirectorySelected(const std::vector& filenames, LLImageFormatted *image, const snapshot_saved_signal_t::slot_type& success_cb, const snapshot_saved_signal_t::slot_type& failure_cb); - void saveImageLocal(LLImageFormatted *image, const snapshot_saved_signal_t::slot_type& success_cb, const snapshot_saved_signal_t::slot_type& failure_cb); - void onSelectionFailure(const snapshot_saved_signal_t::slot_type& failure_cb); - - // Reset the directory where snapshots are saved. - // Client will open directory picker on next snapshot save. - void resetSnapshotLoc(); - - void playSnapshotAnimAndSound(); - - // draws selection boxes around selected objects, must call displayObjects first - void renderSelections( bool for_gl_pick, bool pick_parcel_walls, bool for_hud ); - void performPick(); - void returnEmptyPicks(); - - void pickAsync( S32 x, - S32 y_from_bot, - MASK mask, - void (*callback)(const LLPickInfo& pick_info), - bool pick_transparent = false, - bool pick_rigged = false, - bool pick_unselectable = false, - bool pick_reflection_probes = false); - LLPickInfo pickImmediate(S32 x, S32 y, bool pick_transparent, bool pick_rigged = false, bool pick_particle = false, bool pick_unselectable = true, bool pick_reflection_probe = false); - LLHUDIcon* cursorIntersectIcon(S32 mouse_x, S32 mouse_y, F32 depth, - LLVector4a* intersection); - - LLViewerObject* cursorIntersect(S32 mouse_x = -1, S32 mouse_y = -1, F32 depth = 512.f, - LLViewerObject *this_object = NULL, - S32 this_face = -1, - bool pick_transparent = false, - bool pick_rigged = false, - bool pick_unselectable = true, - bool pick_reflection_probe = true, - S32* face_hit = NULL, - LLVector4a *intersection = NULL, - LLVector2 *uv = NULL, - LLVector4a *normal = NULL, - LLVector4a *tangent = NULL, - LLVector4a* start = NULL, - LLVector4a* end = NULL); - - - // Returns a pointer to the last object hit - //LLViewerObject *getObject(); - //LLViewerObject *lastNonFloraObjectHit(); - - //const LLVector3d& getObjectOffset(); - //const LLVector3d& lastNonFloraObjectHitOffset(); - - // mousePointOnLand() returns true if found point - bool mousePointOnLandGlobal(const S32 x, const S32 y, LLVector3d *land_pos_global, bool ignore_distance = false); - bool mousePointOnPlaneGlobal(LLVector3d& point, const S32 x, const S32 y, const LLVector3d &plane_point, const LLVector3 &plane_normal); - LLVector3d clickPointInWorldGlobal(const S32 x, const S32 y_from_bot, LLViewerObject* clicked_object) const; - bool clickPointOnSurfaceGlobal(const S32 x, const S32 y, LLViewerObject *objectp, LLVector3d &point_global) const; - - // Prints window implementation details - void dumpState(); - - // handle shutting down GL and bringing it back up - void requestResolutionUpdate(); - void checkSettings(); - void restartDisplay(bool show_progress_bar); - bool changeDisplaySettings(LLCoordScreen size, bool enable_vsync, bool show_progress_bar); - bool getIgnoreDestroyWindow() { return mIgnoreActivate; } - F32 getWorldViewAspectRatio() const; - const LLVector2& getDisplayScale() const { return mDisplayScale; } - void calcDisplayScale(); - static LLRect calcScaledRect(const LLRect & rect, const LLVector2& display_scale); - - static std::string getLastSnapshotDir(); - -private: - bool shouldShowToolTipFor(LLMouseHandler *mh); - - void switchToolByMask(MASK mask); - void destroyWindow(); - void drawMouselookInstructions(); - void stopGL(bool save_state = true); - void restoreGL(const std::string& progress_message = LLStringUtil::null); - void initFonts(F32 zoom_factor = 1.f); - void schedulePick(LLPickInfo& pick_info); - S32 getChatConsoleBottomPad(); // Vertical padding for child console rect, varied by bottom clutter - LLRect getChatConsoleRect(); // Get optimal cosole rect. - -private: - LLWindow* mWindow; // graphical window object - bool mActive; - bool mUIVisible; - - LLNotificationChannelPtr mSystemChannel; - LLNotificationChannelPtr mCommunicationChannel; - LLNotificationChannelPtr mAlertsChannel; - LLNotificationChannelPtr mModalAlertsChannel; - - LLRect mWindowRectRaw; // whole window, including UI - LLRect mWindowRectScaled; // whole window, scaled by UI size - LLRect mWorldViewRectRaw; // area of screen for 3D world - LLRect mWorldViewRectScaled; // area of screen for 3D world scaled by UI size - LLRootView* mRootView; // a view of size mWindowRectRaw, containing all child views - LLVector2 mDisplayScale; - - LLCoordGL mCurrentMousePoint; // last mouse position in GL coords - LLCoordGL mLastMousePoint; // Mouse point at last frame. - LLCoordGL mCurrentMouseDelta; //amount mouse moved this frame - bool mLeftMouseDown; - bool mMiddleMouseDown; - bool mRightMouseDown; - - LLProgressView *mProgressView; - - LLFrameTimer mToolTipFadeTimer; - LLPanel* mToolTip; - std::string mLastToolTipMessage; - LLRect mToolTipStickyRect; // Once a tool tip is shown, it will stay visible until the mouse leaves this rect. - - bool mMouseInWindow; // True if the mouse is over our window or if we have captured the mouse. - bool mFocusCycleMode; - bool mAllowMouseDragging; - LLFrameTimer mMouseDownTimer; - typedef std::set > view_handle_set_t; - view_handle_set_t mMouseHoverViews; - - // Variables used for tool override switching based on modifier keys. JC - MASK mLastMask; // used to detect changes in modifier mask - LLTool* mToolStored; // the tool we're overriding - bool mHideCursorPermanent; // true during drags, mouselook - bool mCursorHidden; - LLPickInfo mLastPick; - std::vector mPicks; - LLRect mPickScreenRegion; // area of frame buffer for rendering pick frames (generally follows mouse to avoid going offscreen) - LLTimer mPickTimer; // timer for scheduling n picks per second - - std::string mOverlayTitle; // Used for special titles such as "Second Life - Special E3 2003 Beta" - - bool mIgnoreActivate; - - std::string mInitAlert; // Window / GL initialization requires an alert - - LLHandle mWorldViewPlaceholder; // widget that spans the portion of screen dedicated to rendering the 3d world - LLHandle mToolBarHolder; // container for toolbars - LLHandle mHintHolder; // container for hints - LLHandle mLoginPanelHolder; // container for login panel - LLPopupView* mPopupView; // container for transient popups - - class LLDebugText* mDebugText; // Internal class for debug text - - bool mResDirty; - bool mStatesDirty; - U32 mCurrResolutionIndex; - - std::unique_ptr mWindowListener; - std::unique_ptr mViewerWindowListener; - - // Object temporarily hovered over while dragging - LLPointer mDragHoveredObject; - - boost::signals2::connection mMaxVRAMControlConnection; - - static LLTrace::SampleStatHandle<> sMouseVelocityStat; -}; - -// -// Globals -// - -extern LLViewerWindow* gViewerWindow; - -extern LLFrameTimer gAwayTimer; // tracks time before setting the avatar away state to true -extern LLFrameTimer gAwayTriggerTimer; // how long the avatar has been away - -extern LLViewerObject* gDebugRaycastObject; -extern LLVector4a gDebugRaycastIntersection; -extern LLVOPartGroup* gDebugRaycastParticle; -extern LLVector4a gDebugRaycastParticleIntersection; -extern LLVector2 gDebugRaycastTexCoord; -extern LLVector4a gDebugRaycastNormal; -extern LLVector4a gDebugRaycastTangent; -extern S32 gDebugRaycastFaceHit; -extern LLVector4a gDebugRaycastStart; -extern LLVector4a gDebugRaycastEnd; - -extern bool gDisplayCameraPos; -extern bool gDisplayWindInfo; -extern bool gDisplayFOV; -extern bool gDisplayBadge; - -#endif +/** + * @file llviewerwindow.h + * @brief Description of the LLViewerWindow class. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// +// A note about X,Y coordinates: +// +// X coordinates are in pixels, from the left edge of the window client area +// Y coordinates are in pixels, from the BOTTOM edge of the window client area +// +// The Y coordinates therefore match OpenGL window coords, not Windows(tm) window coords. +// If Y is from the top, the variable will be called "y_from_top" + +#ifndef LL_LLVIEWERWINDOW_H +#define LL_LLVIEWERWINDOW_H + +#include "v3dmath.h" +#include "v2math.h" +#include "llcursortypes.h" +#include "llwindowcallbacks.h" +#include "lltimer.h" +#include "llmousehandler.h" +#include "llnotifications.h" +#include "llhandle.h" +#include "llinitparam.h" +#include "lltrace.h" +#include "llsnapshotmodel.h" + +#include +#include +#include + +class LLView; +class LLViewerObject; +class LLUUID; +class LLProgressView; +class LLTool; +class LLVelocityBar; +class LLPanel; +class LLImageRaw; +class LLImageFormatted; +class LLHUDIcon; +class LLWindow; +class LLRootView; +class LLWindowListener; +class LLViewerWindowListener; +class LLVOPartGroup; +class LLPopupView; +class LLCubeMap; +class LLCubeMapArray; + +#define PICK_HALF_WIDTH 5 +#define PICK_DIAMETER (2 * PICK_HALF_WIDTH + 1) + +class LLPickInfo +{ +public: + typedef enum + { + PICK_OBJECT, + PICK_FLORA, + PICK_LAND, + PICK_ICON, + PICK_PARCEL_WALL, + PICK_INVALID + } EPickType; + +public: + LLPickInfo(); + LLPickInfo(const LLCoordGL& mouse_pos, + MASK keyboard_mask, + bool pick_transparent, + bool pick_rigged, + bool pick_particle, + bool pick_reflection_probe, + bool pick_surface_info, + bool pick_unselectable, + void (*pick_callback)(const LLPickInfo& pick_info)); + + void fetchResults(); + LLPointer getObject() const; + LLUUID getObjectID() const { return mObjectID; } + bool isValid() const { return mPickType != PICK_INVALID; } + + static bool isFlora(LLViewerObject* object); + +public: + LLCoordGL mMousePt; + MASK mKeyMask; + void (*mPickCallback)(const LLPickInfo& pick_info); + + EPickType mPickType; + LLCoordGL mPickPt; + LLVector3d mPosGlobal; + LLVector3 mObjectOffset; + LLUUID mObjectID; + LLUUID mParticleOwnerID; + LLUUID mParticleSourceID; + S32 mObjectFace; + LLHUDIcon* mHUDIcon; + LLVector3 mIntersection; + LLVector2 mUVCoords; + LLVector2 mSTCoords; + LLCoordScreen mXYCoords; + LLVector3 mNormal; + LLVector4 mTangent; + LLVector3 mBinormal; + bool mPickTransparent; + bool mPickRigged; + bool mPickParticle; + bool mPickUnselectable; + bool mPickReflectionProbe = false; + void getSurfaceInfo(); + +private: + void updateXYCoords(); + + bool mWantSurfaceInfo; // do we populate mUVCoord, mNormal, mBinormal? + +}; + +static const U32 MAX_SNAPSHOT_IMAGE_SIZE = 7680; // max snapshot image size 7680 * 7680 UHDTV2 + +class LLViewerWindow : public LLWindowCallbacks +{ +public: + // + // CREATORS + // + struct Params : public LLInitParam::Block + { + Mandatory title, + name; + Mandatory x, + y, + width, + height, + min_width, + min_height; + Optional fullscreen, + ignore_pixel_depth, + first_run; + + Params(); + }; + + LLViewerWindow(const Params& p); + virtual ~LLViewerWindow(); + + void shutdownViews(); + void shutdownGL(); + + void initGLDefaults(); + void initBase(); + void adjustRectanglesForFirstUse(const LLRect& window); + void adjustControlRectanglesForFirstUse(const LLRect& window); + void initWorldUI(); + void setUIVisibility(bool); + bool getUIVisibility(); + void handlePieMenu(S32 x, S32 y, MASK mask); + + void reshapeStatusBarContainer(); + void resetStatusBarContainer(); // undo changes done by resetStatusBarContainer on initWorldUI() + + bool handleAnyMouseClick(LLWindow *window, LLCoordGL pos, MASK mask, EMouseClickType clicktype, bool down, bool &is_toolmgr_action); + + // + // LLWindowCallback interface implementation + // + /*virtual*/ bool handleTranslatedKeyDown(KEY key, MASK mask, bool repeated); + /*virtual*/ bool handleTranslatedKeyUp(KEY key, MASK mask); + /*virtual*/ void handleScanKey(KEY key, bool key_down, bool key_up, bool key_level); + /*virtual*/ bool handleUnicodeChar(llwchar uni_char, MASK mask); // NOT going to handle extended + /*virtual*/ bool handleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask); + /*virtual*/ bool handleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask); + /*virtual*/ bool handleCloseRequest(LLWindow *window); + /*virtual*/ void handleQuit(LLWindow *window); + /*virtual*/ bool handleRightMouseDown(LLWindow *window, LLCoordGL pos, MASK mask); + /*virtual*/ bool handleRightMouseUp(LLWindow *window, LLCoordGL pos, MASK mask); + /*virtual*/ bool handleMiddleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask); + /*virtual*/ bool handleMiddleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask); + /*virtual*/ bool handleOtherMouseDown(LLWindow *window, LLCoordGL pos, MASK mask, S32 button); + /*virtual*/ bool handleOtherMouseUp(LLWindow *window, LLCoordGL pos, MASK mask, S32 button); + bool handleOtherMouse(LLWindow *window, LLCoordGL pos, MASK mask, S32 button, bool down); + /*virtual*/ LLWindowCallbacks::DragNDropResult handleDragNDrop(LLWindow *window, LLCoordGL pos, MASK mask, LLWindowCallbacks::DragNDropAction action, std::string data); + void handleMouseMove(LLWindow *window, LLCoordGL pos, MASK mask); + void handleMouseDragged(LLWindow *window, LLCoordGL pos, MASK mask); + /*virtual*/ void handleMouseLeave(LLWindow *window); + /*virtual*/ void handleResize(LLWindow *window, S32 x, S32 y); + /*virtual*/ void handleFocus(LLWindow *window); + /*virtual*/ void handleFocusLost(LLWindow *window); + /*virtual*/ bool handleActivate(LLWindow *window, bool activated); + /*virtual*/ bool handleActivateApp(LLWindow *window, bool activating); + /*virtual*/ void handleMenuSelect(LLWindow *window, S32 menu_item); + /*virtual*/ bool handlePaint(LLWindow *window, S32 x, S32 y, S32 width, S32 height); + /*virtual*/ void handleScrollWheel(LLWindow *window, S32 clicks); + /*virtual*/ void handleScrollHWheel(LLWindow *window, S32 clicks); + /*virtual*/ bool handleDoubleClick(LLWindow *window, LLCoordGL pos, MASK mask); + /*virtual*/ void handleWindowBlock(LLWindow *window); + /*virtual*/ void handleWindowUnblock(LLWindow *window); + /*virtual*/ void handleDataCopy(LLWindow *window, S32 data_type, void *data); + /*virtual*/ bool handleTimerEvent(LLWindow *window); + /*virtual*/ bool handleDeviceChange(LLWindow *window); + /*virtual*/ bool handleDPIChanged(LLWindow *window, F32 ui_scale_factor, S32 window_width, S32 window_height); + /*virtual*/ bool handleWindowDidChangeScreen(LLWindow *window); + + /*virtual*/ void handlePingWatchdog(LLWindow *window, const char * msg); + /*virtual*/ void handlePauseWatchdog(LLWindow *window); + /*virtual*/ void handleResumeWatchdog(LLWindow *window); + /*virtual*/ std::string translateString(const char* tag); + /*virtual*/ std::string translateString(const char* tag, + const std::map& args); + + // signal on update of WorldView rect + typedef boost::function world_rect_callback_t; + typedef boost::signals2::signal world_rect_signal_t; + world_rect_signal_t mOnWorldViewRectUpdated; + boost::signals2::connection setOnWorldViewRectUpdated(world_rect_callback_t cb) { return mOnWorldViewRectUpdated.connect(cb); } + + // + // ACCESSORS + // + LLRootView* getRootView() const; + + // 3D world area in scaled pixels (via UI scale), use for most UI computations + LLRect getWorldViewRectScaled() const; + S32 getWorldViewHeightScaled() const; + S32 getWorldViewWidthScaled() const; + + // 3D world area, in raw unscaled pixels + LLRect getWorldViewRectRaw() const { return mWorldViewRectRaw; } + S32 getWorldViewHeightRaw() const; + S32 getWorldViewWidthRaw() const; + + // Window in scaled pixels (via UI scale), use for most UI computations + LLRect getWindowRectScaled() const { return mWindowRectScaled; } + S32 getWindowHeightScaled() const; + S32 getWindowWidthScaled() const; + + // Window in raw pixels as seen on screen. + LLRect getWindowRectRaw() const { return mWindowRectRaw; } + S32 getWindowHeightRaw() const; + S32 getWindowWidthRaw() const; + + LLWindow* getWindow() const { return mWindow; } + void* getPlatformWindow() const; + void* getMediaWindow() const; + void focusClient() const; + + LLCoordGL getLastMouse() const { return mLastMousePoint; } + S32 getLastMouseX() const { return mLastMousePoint.mX; } + S32 getLastMouseY() const { return mLastMousePoint.mY; } + LLCoordGL getCurrentMouse() const { return mCurrentMousePoint; } + S32 getCurrentMouseX() const { return mCurrentMousePoint.mX; } + S32 getCurrentMouseY() const { return mCurrentMousePoint.mY; } + S32 getCurrentMouseDX() const { return mCurrentMouseDelta.mX; } + S32 getCurrentMouseDY() const { return mCurrentMouseDelta.mY; } + LLCoordGL getCurrentMouseDelta() const { return mCurrentMouseDelta; } + static LLTrace::SampleStatHandle<>* getMouseVelocityStat() { return &sMouseVelocityStat; } + bool getLeftMouseDown() const { return mLeftMouseDown; } + bool getMiddleMouseDown() const { return mMiddleMouseDown; } + bool getRightMouseDown() const { return mRightMouseDown; } + + const LLPickInfo& getLastPick() const { return mLastPick; } + + void setup2DViewport(S32 x_offset = 0, S32 y_offset = 0); + void setup3DViewport(S32 x_offset = 0, S32 y_offset = 0); + void setup3DRender(); + void setup2DRender(); + + LLVector3 mouseDirectionGlobal(const S32 x, const S32 y) const; + LLVector3 mouseDirectionCamera(const S32 x, const S32 y) const; + LLVector3 mousePointHUD(const S32 x, const S32 y) const; + + + // Is window of our application frontmost? + bool getActive() const { return mActive; } + + const std::string& getInitAlert() { return mInitAlert; } + + // + // MANIPULATORS + // + void saveLastMouse(const LLCoordGL &point); + + void setCursor( ECursorType c ); + void showCursor(); + void hideCursor(); + bool getCursorHidden() { return mCursorHidden; } + void moveCursorToCenter(); // move to center of window + + void initTextures(S32 location_id); + void setShowProgress(const bool show); + bool getShowProgress() const; + void setProgressString(const std::string& string); + void setProgressPercent(const F32 percent); + void setProgressMessage(const std::string& msg); + void setProgressCancelButtonVisible( bool b, const std::string& label = LLStringUtil::null ); + LLProgressView *getProgressView() const; + void revealIntroPanel(); + void setStartupComplete(); + + void updateObjectUnderCursor(); + + void updateUI(); // Once per frame, update UI based on mouse position, calls following update* functions + void updateLayout(); + void updateMouseDelta(); + void updateKeyboardFocus(); + + void updateWorldViewRect(bool use_full_window=false); + LLView* getToolBarHolder() { return mToolBarHolder.get(); } + LLView* getHintHolder() { return mHintHolder.get(); } + LLView* getLoginPanelHolder() { return mLoginPanelHolder.get(); } + bool handleKey(KEY key, MASK mask); + bool handleKeyUp(KEY key, MASK mask); + void handleScrollWheel (S32 clicks); + void handleScrollHWheel (S32 clicks); + + // add and remove views from "popup" layer + void addPopup(LLView* popup); + void removePopup(LLView* popup); + void clearPopups(); + + // Hide normal UI when a logon fails, re-show everything when logon is attempted again + void setNormalControlsVisible( bool visible ); + void setMenuBackgroundColor(bool god_mode = false, bool dev_grid = false); + + void reshape(S32 width, S32 height); + void sendShapeToSim(); + + void draw(); + void updateDebugText(); + void drawDebugText(); + + static void loadUserImage(void **cb_data, const LLUUID &uuid); + + static void movieSize(S32 new_width, S32 new_height); + + // snapshot functionality. + // perhaps some of this should move to llfloatershapshot? -MG + + bool saveSnapshot(const std::string& filename, S32 image_width, S32 image_height, bool show_ui = true, bool show_hud = true, bool do_rebuild = false, LLSnapshotModel::ESnapshotLayerType type = LLSnapshotModel::SNAPSHOT_TYPE_COLOR, LLSnapshotModel::ESnapshotFormat format = LLSnapshotModel::SNAPSHOT_FORMAT_BMP); + bool rawSnapshot(LLImageRaw *raw, S32 image_width, S32 image_height, bool keep_window_aspect = true, bool is_texture = false, + bool show_ui = true, bool show_hud = true, bool do_rebuild = false, bool no_post = false, LLSnapshotModel::ESnapshotLayerType type = LLSnapshotModel::SNAPSHOT_TYPE_COLOR, S32 max_size = MAX_SNAPSHOT_IMAGE_SIZE); + + bool simpleSnapshot(LLImageRaw *raw, S32 image_width, S32 image_height, const int num_render_passes); + + + + // take a cubemap snapshot + // origin - vantage point to take the snapshot from + // cubearray - cubemap array for storing the results + // index - cube index in the array to use (cube index, not face-layer) + // face - which cube face to update + // near_clip - near clip setting to use + bool cubeSnapshot(const LLVector3& origin, LLCubeMapArray* cubearray, S32 index, S32 face, F32 near_clip, bool render_avatars); + + + // special implementation of simpleSnapshot for reflection maps + bool reflectionSnapshot(LLImageRaw* raw, S32 image_width, S32 image_height, const int num_render_passes); + + bool thumbnailSnapshot(LLImageRaw *raw, S32 preview_width, S32 preview_height, bool show_ui, bool show_hud, bool do_rebuild, bool no_post, LLSnapshotModel::ESnapshotLayerType type); + bool isSnapshotLocSet() const; + void resetSnapshotLoc() const; + + typedef boost::signals2::signal snapshot_saved_signal_t; + + void saveImageNumbered(LLImageFormatted *image, bool force_picker, const snapshot_saved_signal_t::slot_type& success_cb, const snapshot_saved_signal_t::slot_type& failure_cb); + void onDirectorySelected(const std::vector& filenames, LLImageFormatted *image, const snapshot_saved_signal_t::slot_type& success_cb, const snapshot_saved_signal_t::slot_type& failure_cb); + void saveImageLocal(LLImageFormatted *image, const snapshot_saved_signal_t::slot_type& success_cb, const snapshot_saved_signal_t::slot_type& failure_cb); + void onSelectionFailure(const snapshot_saved_signal_t::slot_type& failure_cb); + + // Reset the directory where snapshots are saved. + // Client will open directory picker on next snapshot save. + void resetSnapshotLoc(); + + void playSnapshotAnimAndSound(); + + // draws selection boxes around selected objects, must call displayObjects first + void renderSelections( bool for_gl_pick, bool pick_parcel_walls, bool for_hud ); + void performPick(); + void returnEmptyPicks(); + + void pickAsync( S32 x, + S32 y_from_bot, + MASK mask, + void (*callback)(const LLPickInfo& pick_info), + bool pick_transparent = false, + bool pick_rigged = false, + bool pick_unselectable = false, + bool pick_reflection_probes = false); + LLPickInfo pickImmediate(S32 x, S32 y, bool pick_transparent, bool pick_rigged = false, bool pick_particle = false, bool pick_unselectable = true, bool pick_reflection_probe = false); + LLHUDIcon* cursorIntersectIcon(S32 mouse_x, S32 mouse_y, F32 depth, + LLVector4a* intersection); + + LLViewerObject* cursorIntersect(S32 mouse_x = -1, S32 mouse_y = -1, F32 depth = 512.f, + LLViewerObject *this_object = NULL, + S32 this_face = -1, + bool pick_transparent = false, + bool pick_rigged = false, + bool pick_unselectable = true, + bool pick_reflection_probe = true, + S32* face_hit = NULL, + LLVector4a *intersection = NULL, + LLVector2 *uv = NULL, + LLVector4a *normal = NULL, + LLVector4a *tangent = NULL, + LLVector4a* start = NULL, + LLVector4a* end = NULL); + + + // Returns a pointer to the last object hit + //LLViewerObject *getObject(); + //LLViewerObject *lastNonFloraObjectHit(); + + //const LLVector3d& getObjectOffset(); + //const LLVector3d& lastNonFloraObjectHitOffset(); + + // mousePointOnLand() returns true if found point + bool mousePointOnLandGlobal(const S32 x, const S32 y, LLVector3d *land_pos_global, bool ignore_distance = false); + bool mousePointOnPlaneGlobal(LLVector3d& point, const S32 x, const S32 y, const LLVector3d &plane_point, const LLVector3 &plane_normal); + LLVector3d clickPointInWorldGlobal(const S32 x, const S32 y_from_bot, LLViewerObject* clicked_object) const; + bool clickPointOnSurfaceGlobal(const S32 x, const S32 y, LLViewerObject *objectp, LLVector3d &point_global) const; + + // Prints window implementation details + void dumpState(); + + // handle shutting down GL and bringing it back up + void requestResolutionUpdate(); + void checkSettings(); + void restartDisplay(bool show_progress_bar); + bool changeDisplaySettings(LLCoordScreen size, bool enable_vsync, bool show_progress_bar); + bool getIgnoreDestroyWindow() { return mIgnoreActivate; } + F32 getWorldViewAspectRatio() const; + const LLVector2& getDisplayScale() const { return mDisplayScale; } + void calcDisplayScale(); + static LLRect calcScaledRect(const LLRect & rect, const LLVector2& display_scale); + + static std::string getLastSnapshotDir(); + +private: + bool shouldShowToolTipFor(LLMouseHandler *mh); + + void switchToolByMask(MASK mask); + void destroyWindow(); + void drawMouselookInstructions(); + void stopGL(bool save_state = true); + void restoreGL(const std::string& progress_message = LLStringUtil::null); + void initFonts(F32 zoom_factor = 1.f); + void schedulePick(LLPickInfo& pick_info); + S32 getChatConsoleBottomPad(); // Vertical padding for child console rect, varied by bottom clutter + LLRect getChatConsoleRect(); // Get optimal cosole rect. + +private: + LLWindow* mWindow; // graphical window object + bool mActive; + bool mUIVisible; + + LLNotificationChannelPtr mSystemChannel; + LLNotificationChannelPtr mCommunicationChannel; + LLNotificationChannelPtr mAlertsChannel; + LLNotificationChannelPtr mModalAlertsChannel; + + LLRect mWindowRectRaw; // whole window, including UI + LLRect mWindowRectScaled; // whole window, scaled by UI size + LLRect mWorldViewRectRaw; // area of screen for 3D world + LLRect mWorldViewRectScaled; // area of screen for 3D world scaled by UI size + LLRootView* mRootView; // a view of size mWindowRectRaw, containing all child views + LLVector2 mDisplayScale; + + LLCoordGL mCurrentMousePoint; // last mouse position in GL coords + LLCoordGL mLastMousePoint; // Mouse point at last frame. + LLCoordGL mCurrentMouseDelta; //amount mouse moved this frame + bool mLeftMouseDown; + bool mMiddleMouseDown; + bool mRightMouseDown; + + LLProgressView *mProgressView; + + LLFrameTimer mToolTipFadeTimer; + LLPanel* mToolTip; + std::string mLastToolTipMessage; + LLRect mToolTipStickyRect; // Once a tool tip is shown, it will stay visible until the mouse leaves this rect. + + bool mMouseInWindow; // True if the mouse is over our window or if we have captured the mouse. + bool mFocusCycleMode; + bool mAllowMouseDragging; + LLFrameTimer mMouseDownTimer; + typedef std::set > view_handle_set_t; + view_handle_set_t mMouseHoverViews; + + // Variables used for tool override switching based on modifier keys. JC + MASK mLastMask; // used to detect changes in modifier mask + LLTool* mToolStored; // the tool we're overriding + bool mHideCursorPermanent; // true during drags, mouselook + bool mCursorHidden; + LLPickInfo mLastPick; + std::vector mPicks; + LLRect mPickScreenRegion; // area of frame buffer for rendering pick frames (generally follows mouse to avoid going offscreen) + LLTimer mPickTimer; // timer for scheduling n picks per second + + std::string mOverlayTitle; // Used for special titles such as "Second Life - Special E3 2003 Beta" + + bool mIgnoreActivate; + + std::string mInitAlert; // Window / GL initialization requires an alert + + LLHandle mWorldViewPlaceholder; // widget that spans the portion of screen dedicated to rendering the 3d world + LLHandle mToolBarHolder; // container for toolbars + LLHandle mHintHolder; // container for hints + LLHandle mLoginPanelHolder; // container for login panel + LLPopupView* mPopupView; // container for transient popups + + class LLDebugText* mDebugText; // Internal class for debug text + + bool mResDirty; + bool mStatesDirty; + U32 mCurrResolutionIndex; + + std::unique_ptr mWindowListener; + std::unique_ptr mViewerWindowListener; + + // Object temporarily hovered over while dragging + LLPointer mDragHoveredObject; + + boost::signals2::connection mMaxVRAMControlConnection; + + static LLTrace::SampleStatHandle<> sMouseVelocityStat; +}; + +// +// Globals +// + +extern LLViewerWindow* gViewerWindow; + +extern LLFrameTimer gAwayTimer; // tracks time before setting the avatar away state to true +extern LLFrameTimer gAwayTriggerTimer; // how long the avatar has been away + +extern LLViewerObject* gDebugRaycastObject; +extern LLVector4a gDebugRaycastIntersection; +extern LLVOPartGroup* gDebugRaycastParticle; +extern LLVector4a gDebugRaycastParticleIntersection; +extern LLVector2 gDebugRaycastTexCoord; +extern LLVector4a gDebugRaycastNormal; +extern LLVector4a gDebugRaycastTangent; +extern S32 gDebugRaycastFaceHit; +extern LLVector4a gDebugRaycastStart; +extern LLVector4a gDebugRaycastEnd; + +extern bool gDisplayCameraPos; +extern bool gDisplayWindInfo; +extern bool gDisplayFOV; +extern bool gDisplayBadge; + +#endif diff --git a/indra/newview/llvlcomposition.cpp b/indra/newview/llvlcomposition.cpp index a79c2cb477..efae321c85 100644 --- a/indra/newview/llvlcomposition.cpp +++ b/indra/newview/llvlcomposition.cpp @@ -1,500 +1,500 @@ -/** - * @file llvlcomposition.cpp - * @brief Viewer-side representation of a composition layer... - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llvlcomposition.h" - -#include "llerror.h" -#include "v3math.h" -#include "llsurface.h" -#include "lltextureview.h" -#include "llviewertexture.h" -#include "llviewertexturelist.h" -#include "llviewerregion.h" -#include "noise.h" -#include "llregionhandle.h" // for from_region_handle -#include "llviewercontrol.h" - - - -F32 bilinear(const F32 v00, const F32 v01, const F32 v10, const F32 v11, const F32 x_frac, const F32 y_frac) -{ - // Not sure if this is the right math... - // Take weighted average of all four points (bilinear interpolation) - F32 result; - - const F32 inv_x_frac = 1.f - x_frac; - const F32 inv_y_frac = 1.f - y_frac; - result = inv_x_frac*inv_y_frac*v00 - + x_frac*inv_y_frac*v10 - + inv_x_frac*y_frac*v01 - + x_frac*y_frac*v11; - - return result; -} - - -LLVLComposition::LLVLComposition(LLSurface *surfacep, const U32 width, const F32 scale) : - LLViewerLayer(width, scale), - mParamsReady(false) -{ - mSurfacep = surfacep; - - // Load Terrain Textures - Original ones - setDetailTextureID(0, TERRAIN_DIRT_DETAIL); - setDetailTextureID(1, TERRAIN_GRASS_DETAIL); - setDetailTextureID(2, TERRAIN_MOUNTAIN_DETAIL); - setDetailTextureID(3, TERRAIN_ROCK_DETAIL); - - // Initialize the texture matrix to defaults. - for (S32 i = 0; i < CORNER_COUNT; ++i) - { - mStartHeight[i] = gSavedSettings.getF32("TerrainColorStartHeight"); - mHeightRange[i] = gSavedSettings.getF32("TerrainColorHeightRange"); - } - mTexScaleX = 16.f; - mTexScaleY = 16.f; - mTexturesLoaded = false; -} - - -LLVLComposition::~LLVLComposition() -{ -} - - -void LLVLComposition::setSurface(LLSurface *surfacep) -{ - mSurfacep = surfacep; -} - - -void LLVLComposition::setDetailTextureID(S32 corner, const LLUUID& id) -{ - if(id.isNull()) - { - return; - } - // This is terrain texture, but we are not setting it as BOOST_TERRAIN - // since we will be manipulating it later as needed. - mDetailTextures[corner] = LLViewerTextureManager::getFetchedTexture(id); - mDetailTextures[corner]->setNoDelete() ; - mRawImages[corner] = NULL; -} - -bool LLVLComposition::generateHeights(const F32 x, const F32 y, - const F32 width, const F32 height) -{ - if (!mParamsReady) - { - // All the parameters haven't been set yet (we haven't gotten the message from the sim) - return false; - } - - llassert(mSurfacep); - - if (!mSurfacep || !mSurfacep->getRegion()) - { - // We don't always have the region yet here.... - return false; - } - - S32 x_begin, y_begin, x_end, y_end; - - x_begin = ll_round( x * mScaleInv ); - y_begin = ll_round( y * mScaleInv ); - x_end = ll_round( (x + width) * mScaleInv ); - y_end = ll_round( (y + width) * mScaleInv ); - - if (x_end > mWidth) - { - x_end = mWidth; - } - if (y_end > mWidth) - { - y_end = mWidth; - } - - LLVector3d origin_global = from_region_handle(mSurfacep->getRegion()->getHandle()); - - // For perlin noise generation... - const F32 slope_squared = 1.5f*1.5f; - const F32 xyScale = 4.9215f; //0.93284f; - const F32 zScale = 4; //0.92165f; - const F32 z_offset = 0.f; - const F32 noise_magnitude = 2.f; // Degree to which noise modulates composition layer (versus - // simple height) - - // Heights map into textures as 0-1 = first, 1-2 = second, etc. - // So we need to compress heights into this range. - const S32 NUM_TEXTURES = 4; - - const F32 xyScaleInv = (1.f / xyScale); - const F32 zScaleInv = (1.f / zScale); - - const F32 inv_width = 1.f/mWidth; - - // OK, for now, just have the composition value equal the height at the point. - for (S32 j = y_begin; j < y_end; j++) - { - for (S32 i = x_begin; i < x_end; i++) - { - - F32 vec[3]; - F32 vec1[3]; - F32 twiddle; - - // Bilinearly interpolate the start height and height range of the textures - F32 start_height = bilinear(mStartHeight[SOUTHWEST], - mStartHeight[SOUTHEAST], - mStartHeight[NORTHWEST], - mStartHeight[NORTHEAST], - i*inv_width, j*inv_width); // These will be bilinearly interpolated - F32 height_range = bilinear(mHeightRange[SOUTHWEST], - mHeightRange[SOUTHEAST], - mHeightRange[NORTHWEST], - mHeightRange[NORTHEAST], - i*inv_width, j*inv_width); // These will be bilinearly interpolated - - LLVector3 location(i*mScale, j*mScale, 0.f); - - F32 height = mSurfacep->resolveHeightRegion(location) + z_offset; - - // Step 0: Measure the exact height at this texel - vec[0] = (F32)(origin_global.mdV[VX]+location.mV[VX])*xyScaleInv; // Adjust to non-integer lattice - vec[1] = (F32)(origin_global.mdV[VY]+location.mV[VY])*xyScaleInv; - vec[2] = height*zScaleInv; - // - // Choose material value by adding to the exact height a random value - // - vec1[0] = vec[0]*(0.2222222222f); - vec1[1] = vec[1]*(0.2222222222f); - vec1[2] = vec[2]*(0.2222222222f); - twiddle = noise2(vec1)*6.5f; // Low freq component for large divisions - - twiddle += turbulence2(vec, 2)*slope_squared; // High frequency component - twiddle *= noise_magnitude; - - F32 scaled_noisy_height = (height + twiddle - start_height) * F32(NUM_TEXTURES) / height_range; - - scaled_noisy_height = llmax(0.f, scaled_noisy_height); - scaled_noisy_height = llmin(3.f, scaled_noisy_height); - *(mDatap + i + j*mWidth) = scaled_noisy_height; - } - } - return true; -} - -static const U32 BASE_SIZE = 128; - -bool LLVLComposition::generateComposition() -{ - - if (!mParamsReady) - { - // All the parameters haven't been set yet (we haven't gotten the message from the sim) - return false; - } - - for (S32 i = 0; i < 4; i++) - { - if (mDetailTextures[i]->getDiscardLevel() < 0) - { - mDetailTextures[i]->setBoostLevel(LLGLTexture::BOOST_TERRAIN); // in case we are at low detail - mDetailTextures[i]->addTextureStats(BASE_SIZE*BASE_SIZE); - return false; - } - if ((mDetailTextures[i]->getDiscardLevel() != 0 && - (mDetailTextures[i]->getWidth() < BASE_SIZE || - mDetailTextures[i]->getHeight() < BASE_SIZE))) - { - S32 width = mDetailTextures[i]->getFullWidth(); - S32 height = mDetailTextures[i]->getFullHeight(); - S32 min_dim = llmin(width, height); - S32 ddiscard = 0; - while (min_dim > BASE_SIZE && ddiscard < MAX_DISCARD_LEVEL) - { - ddiscard++; - min_dim /= 2; - } - mDetailTextures[i]->setBoostLevel(LLGLTexture::BOOST_TERRAIN); // in case we are at low detail - mDetailTextures[i]->setMinDiscardLevel(ddiscard); - mDetailTextures[i]->addTextureStats(BASE_SIZE*BASE_SIZE); // priority - return false; - } - } - - return true; -} - -bool LLVLComposition::generateTexture(const F32 x, const F32 y, - const F32 width, const F32 height) -{ - LL_PROFILE_ZONE_SCOPED - llassert(mSurfacep); - llassert(x >= 0.f); - llassert(y >= 0.f); - - LLTimer gen_timer; - - /////////////////////////// - // - // Generate raw data arrays for surface textures - // - // - - // These have already been validated by generateComposition. - U8* st_data[4]; - S32 st_data_size[4]; // for debugging - - for (S32 i = 0; i < 4; i++) - { - if (mRawImages[i].isNull()) - { - // Read back a raw image for this discard level, if it exists - S32 min_dim = llmin(mDetailTextures[i]->getFullWidth(), mDetailTextures[i]->getFullHeight()); - S32 ddiscard = 0; - while (min_dim > BASE_SIZE && ddiscard < MAX_DISCARD_LEVEL) - { - ddiscard++; - min_dim /= 2; - } - - bool delete_raw = (mDetailTextures[i]->reloadRawImage(ddiscard) != NULL) ; - if(mDetailTextures[i]->getRawImageLevel() != ddiscard)//raw iamge is not ready, will enter here again later. - { - if (mDetailTextures[i]->getFetchPriority() <= 0.0f && !mDetailTextures[i]->hasSavedRawImage()) - { - mDetailTextures[i]->setBoostLevel(LLGLTexture::BOOST_MAP); - mDetailTextures[i]->forceToRefetchTexture(ddiscard); - } - - if(delete_raw) - { - mDetailTextures[i]->destroyRawImage() ; - } - LL_DEBUGS("Terrain") << "cached raw data for terrain detail texture is not ready yet: " << mDetailTextures[i]->getID() << " Discard: " << ddiscard << LL_ENDL; - return false; - } - - mRawImages[i] = mDetailTextures[i]->getRawImage() ; - if(delete_raw) - { - mDetailTextures[i]->destroyRawImage() ; - } - if (mDetailTextures[i]->getWidth(ddiscard) != BASE_SIZE || - mDetailTextures[i]->getHeight(ddiscard) != BASE_SIZE || - mDetailTextures[i]->getComponents() != 3) - { - LLPointer newraw = new LLImageRaw(BASE_SIZE, BASE_SIZE, 3); - newraw->composite(mRawImages[i]); - mRawImages[i] = newraw; // deletes old - } - } - st_data[i] = mRawImages[i]->getData(); - st_data_size[i] = mRawImages[i]->getDataSize(); - } - - /////////////////////////////////////// - // - // Generate and clamp x/y bounding box. - // - // - - S32 x_begin, y_begin, x_end, y_end; - x_begin = (S32)(x * mScaleInv); - y_begin = (S32)(y * mScaleInv); - x_end = ll_round( (x + width) * mScaleInv ); - y_end = ll_round( (y + width) * mScaleInv ); - - if (x_end > mWidth) - { - LL_WARNS("Terrain") << "x end > width" << LL_ENDL; - x_end = mWidth; - } - if (y_end > mWidth) - { - LL_WARNS("Terrain") << "y end > width" << LL_ENDL; - y_end = mWidth; - } - - - /////////////////////////////////////////// - // - // Generate target texture information, stride ratios. - // - // - - LLViewerTexture *texturep; - U32 tex_width, tex_height, tex_comps; - U32 tex_stride; - F32 tex_x_scalef, tex_y_scalef; - S32 tex_x_begin, tex_y_begin, tex_x_end, tex_y_end; - F32 tex_x_ratiof, tex_y_ratiof; - - texturep = mSurfacep->getSTexture(); - tex_width = texturep->getWidth(); - tex_height = texturep->getHeight(); - tex_comps = texturep->getComponents(); - tex_stride = tex_width * tex_comps; - - U32 st_comps = 3; - U32 st_width = BASE_SIZE; - U32 st_height = BASE_SIZE; - - if (tex_comps != st_comps) - { - LL_WARNS("Terrain") << "Base texture comps != input texture comps" << LL_ENDL; - return false; - } - - tex_x_scalef = (F32)tex_width / (F32)mWidth; - tex_y_scalef = (F32)tex_height / (F32)mWidth; - tex_x_begin = (S32)((F32)x_begin * tex_x_scalef); - tex_y_begin = (S32)((F32)y_begin * tex_y_scalef); - tex_x_end = (S32)((F32)x_end * tex_x_scalef); - tex_y_end = (S32)((F32)y_end * tex_y_scalef); - - tex_x_ratiof = (F32)mWidth*mScale / (F32)tex_width; - tex_y_ratiof = (F32)mWidth*mScale / (F32)tex_height; - - LLPointer raw = new LLImageRaw(tex_width, tex_height, tex_comps); - U8 *rawp = raw->getData(); - - F32 st_x_stride, st_y_stride; - st_x_stride = ((F32)st_width / (F32)mTexScaleX)*((F32)mWidth / (F32)tex_width); - st_y_stride = ((F32)st_height / (F32)mTexScaleY)*((F32)mWidth / (F32)tex_height); - - llassert(st_x_stride > 0.f); - llassert(st_y_stride > 0.f); - //////////////////////////////// - // - // Iterate through the target texture, striding through the - // subtextures and interpolating appropriately. - // - // - - F32 sti, stj; - S32 st_offset; - sti = (tex_x_begin * st_x_stride) - st_width*(llfloor((tex_x_begin * st_x_stride)/st_width)); - stj = (tex_y_begin * st_y_stride) - st_height*(llfloor((tex_y_begin * st_y_stride)/st_height)); - - st_offset = (llfloor(stj * st_width) + llfloor(sti)) * st_comps; - for (S32 j = tex_y_begin; j < tex_y_end; j++) - { - U32 offset = j * tex_stride + tex_x_begin * tex_comps; - sti = (tex_x_begin * st_x_stride) - st_width*((U32)(tex_x_begin * st_x_stride)/st_width); - for (S32 i = tex_x_begin; i < tex_x_end; i++) - { - S32 tex0, tex1; - F32 composition = getValueScaled(i*tex_x_ratiof, j*tex_y_ratiof); - - tex0 = llfloor( composition ); - tex0 = llclamp(tex0, 0, 3); - composition -= tex0; - tex1 = tex0 + 1; - tex1 = llclamp(tex1, 0, 3); - - st_offset = (lltrunc(sti) + lltrunc(stj)*st_width) * st_comps; - for (U32 k = 0; k < tex_comps; k++) - { - // Linearly interpolate based on composition. - if (st_offset >= st_data_size[tex0] || st_offset >= st_data_size[tex1]) - { - // SJB: This shouldn't be happening, but does... Rounding error? - //LL_WARNS() << "offset 0 [" << tex0 << "] =" << st_offset << " >= size=" << st_data_size[tex0] << LL_ENDL; - //LL_WARNS() << "offset 1 [" << tex1 << "] =" << st_offset << " >= size=" << st_data_size[tex1] << LL_ENDL; - } - else - { - F32 a = *(st_data[tex0] + st_offset); - F32 b = *(st_data[tex1] + st_offset); - rawp[ offset ] = (U8)lltrunc( a + composition * (b - a) ); - } - offset++; - st_offset++; - } - - sti += st_x_stride; - if (sti >= st_width) - { - sti -= st_width; - } - } - - stj += st_y_stride; - if (stj >= st_height) - { - stj -= st_height; - } - } - - if (!texturep->hasGLTexture()) - { - texturep->createGLTexture(0, raw); - } - texturep->setSubImage(raw, tex_x_begin, tex_y_begin, tex_x_end - tex_x_begin, tex_y_end - tex_y_begin); - - for (S32 i = 0; i < 4; i++) - { - // Un-boost detatil textures (will get re-boosted if rendering in high detail) - mDetailTextures[i]->setBoostLevel(LLGLTexture::BOOST_NONE); - mDetailTextures[i]->setMinDiscardLevel(MAX_DISCARD_LEVEL + 1); - } - - return true; -} - -LLUUID LLVLComposition::getDetailTextureID(S32 corner) -{ - return mDetailTextures[corner]->getID(); -} - -LLViewerFetchedTexture* LLVLComposition::getDetailTexture(S32 corner) -{ - return mDetailTextures[corner]; -} - -F32 LLVLComposition::getStartHeight(S32 corner) -{ - return mStartHeight[corner]; -} - -void LLVLComposition::setStartHeight(S32 corner, const F32 start_height) -{ - mStartHeight[corner] = start_height; -} - -F32 LLVLComposition::getHeightRange(S32 corner) -{ - return mHeightRange[corner]; -} - -void LLVLComposition::setHeightRange(S32 corner, const F32 range) -{ - mHeightRange[corner] = range; -} +/** + * @file llvlcomposition.cpp + * @brief Viewer-side representation of a composition layer... + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llvlcomposition.h" + +#include "llerror.h" +#include "v3math.h" +#include "llsurface.h" +#include "lltextureview.h" +#include "llviewertexture.h" +#include "llviewertexturelist.h" +#include "llviewerregion.h" +#include "noise.h" +#include "llregionhandle.h" // for from_region_handle +#include "llviewercontrol.h" + + + +F32 bilinear(const F32 v00, const F32 v01, const F32 v10, const F32 v11, const F32 x_frac, const F32 y_frac) +{ + // Not sure if this is the right math... + // Take weighted average of all four points (bilinear interpolation) + F32 result; + + const F32 inv_x_frac = 1.f - x_frac; + const F32 inv_y_frac = 1.f - y_frac; + result = inv_x_frac*inv_y_frac*v00 + + x_frac*inv_y_frac*v10 + + inv_x_frac*y_frac*v01 + + x_frac*y_frac*v11; + + return result; +} + + +LLVLComposition::LLVLComposition(LLSurface *surfacep, const U32 width, const F32 scale) : + LLViewerLayer(width, scale), + mParamsReady(false) +{ + mSurfacep = surfacep; + + // Load Terrain Textures - Original ones + setDetailTextureID(0, TERRAIN_DIRT_DETAIL); + setDetailTextureID(1, TERRAIN_GRASS_DETAIL); + setDetailTextureID(2, TERRAIN_MOUNTAIN_DETAIL); + setDetailTextureID(3, TERRAIN_ROCK_DETAIL); + + // Initialize the texture matrix to defaults. + for (S32 i = 0; i < CORNER_COUNT; ++i) + { + mStartHeight[i] = gSavedSettings.getF32("TerrainColorStartHeight"); + mHeightRange[i] = gSavedSettings.getF32("TerrainColorHeightRange"); + } + mTexScaleX = 16.f; + mTexScaleY = 16.f; + mTexturesLoaded = false; +} + + +LLVLComposition::~LLVLComposition() +{ +} + + +void LLVLComposition::setSurface(LLSurface *surfacep) +{ + mSurfacep = surfacep; +} + + +void LLVLComposition::setDetailTextureID(S32 corner, const LLUUID& id) +{ + if(id.isNull()) + { + return; + } + // This is terrain texture, but we are not setting it as BOOST_TERRAIN + // since we will be manipulating it later as needed. + mDetailTextures[corner] = LLViewerTextureManager::getFetchedTexture(id); + mDetailTextures[corner]->setNoDelete() ; + mRawImages[corner] = NULL; +} + +bool LLVLComposition::generateHeights(const F32 x, const F32 y, + const F32 width, const F32 height) +{ + if (!mParamsReady) + { + // All the parameters haven't been set yet (we haven't gotten the message from the sim) + return false; + } + + llassert(mSurfacep); + + if (!mSurfacep || !mSurfacep->getRegion()) + { + // We don't always have the region yet here.... + return false; + } + + S32 x_begin, y_begin, x_end, y_end; + + x_begin = ll_round( x * mScaleInv ); + y_begin = ll_round( y * mScaleInv ); + x_end = ll_round( (x + width) * mScaleInv ); + y_end = ll_round( (y + width) * mScaleInv ); + + if (x_end > mWidth) + { + x_end = mWidth; + } + if (y_end > mWidth) + { + y_end = mWidth; + } + + LLVector3d origin_global = from_region_handle(mSurfacep->getRegion()->getHandle()); + + // For perlin noise generation... + const F32 slope_squared = 1.5f*1.5f; + const F32 xyScale = 4.9215f; //0.93284f; + const F32 zScale = 4; //0.92165f; + const F32 z_offset = 0.f; + const F32 noise_magnitude = 2.f; // Degree to which noise modulates composition layer (versus + // simple height) + + // Heights map into textures as 0-1 = first, 1-2 = second, etc. + // So we need to compress heights into this range. + const S32 NUM_TEXTURES = 4; + + const F32 xyScaleInv = (1.f / xyScale); + const F32 zScaleInv = (1.f / zScale); + + const F32 inv_width = 1.f/mWidth; + + // OK, for now, just have the composition value equal the height at the point. + for (S32 j = y_begin; j < y_end; j++) + { + for (S32 i = x_begin; i < x_end; i++) + { + + F32 vec[3]; + F32 vec1[3]; + F32 twiddle; + + // Bilinearly interpolate the start height and height range of the textures + F32 start_height = bilinear(mStartHeight[SOUTHWEST], + mStartHeight[SOUTHEAST], + mStartHeight[NORTHWEST], + mStartHeight[NORTHEAST], + i*inv_width, j*inv_width); // These will be bilinearly interpolated + F32 height_range = bilinear(mHeightRange[SOUTHWEST], + mHeightRange[SOUTHEAST], + mHeightRange[NORTHWEST], + mHeightRange[NORTHEAST], + i*inv_width, j*inv_width); // These will be bilinearly interpolated + + LLVector3 location(i*mScale, j*mScale, 0.f); + + F32 height = mSurfacep->resolveHeightRegion(location) + z_offset; + + // Step 0: Measure the exact height at this texel + vec[0] = (F32)(origin_global.mdV[VX]+location.mV[VX])*xyScaleInv; // Adjust to non-integer lattice + vec[1] = (F32)(origin_global.mdV[VY]+location.mV[VY])*xyScaleInv; + vec[2] = height*zScaleInv; + // + // Choose material value by adding to the exact height a random value + // + vec1[0] = vec[0]*(0.2222222222f); + vec1[1] = vec[1]*(0.2222222222f); + vec1[2] = vec[2]*(0.2222222222f); + twiddle = noise2(vec1)*6.5f; // Low freq component for large divisions + + twiddle += turbulence2(vec, 2)*slope_squared; // High frequency component + twiddle *= noise_magnitude; + + F32 scaled_noisy_height = (height + twiddle - start_height) * F32(NUM_TEXTURES) / height_range; + + scaled_noisy_height = llmax(0.f, scaled_noisy_height); + scaled_noisy_height = llmin(3.f, scaled_noisy_height); + *(mDatap + i + j*mWidth) = scaled_noisy_height; + } + } + return true; +} + +static const U32 BASE_SIZE = 128; + +bool LLVLComposition::generateComposition() +{ + + if (!mParamsReady) + { + // All the parameters haven't been set yet (we haven't gotten the message from the sim) + return false; + } + + for (S32 i = 0; i < 4; i++) + { + if (mDetailTextures[i]->getDiscardLevel() < 0) + { + mDetailTextures[i]->setBoostLevel(LLGLTexture::BOOST_TERRAIN); // in case we are at low detail + mDetailTextures[i]->addTextureStats(BASE_SIZE*BASE_SIZE); + return false; + } + if ((mDetailTextures[i]->getDiscardLevel() != 0 && + (mDetailTextures[i]->getWidth() < BASE_SIZE || + mDetailTextures[i]->getHeight() < BASE_SIZE))) + { + S32 width = mDetailTextures[i]->getFullWidth(); + S32 height = mDetailTextures[i]->getFullHeight(); + S32 min_dim = llmin(width, height); + S32 ddiscard = 0; + while (min_dim > BASE_SIZE && ddiscard < MAX_DISCARD_LEVEL) + { + ddiscard++; + min_dim /= 2; + } + mDetailTextures[i]->setBoostLevel(LLGLTexture::BOOST_TERRAIN); // in case we are at low detail + mDetailTextures[i]->setMinDiscardLevel(ddiscard); + mDetailTextures[i]->addTextureStats(BASE_SIZE*BASE_SIZE); // priority + return false; + } + } + + return true; +} + +bool LLVLComposition::generateTexture(const F32 x, const F32 y, + const F32 width, const F32 height) +{ + LL_PROFILE_ZONE_SCOPED + llassert(mSurfacep); + llassert(x >= 0.f); + llassert(y >= 0.f); + + LLTimer gen_timer; + + /////////////////////////// + // + // Generate raw data arrays for surface textures + // + // + + // These have already been validated by generateComposition. + U8* st_data[4]; + S32 st_data_size[4]; // for debugging + + for (S32 i = 0; i < 4; i++) + { + if (mRawImages[i].isNull()) + { + // Read back a raw image for this discard level, if it exists + S32 min_dim = llmin(mDetailTextures[i]->getFullWidth(), mDetailTextures[i]->getFullHeight()); + S32 ddiscard = 0; + while (min_dim > BASE_SIZE && ddiscard < MAX_DISCARD_LEVEL) + { + ddiscard++; + min_dim /= 2; + } + + bool delete_raw = (mDetailTextures[i]->reloadRawImage(ddiscard) != NULL) ; + if(mDetailTextures[i]->getRawImageLevel() != ddiscard)//raw iamge is not ready, will enter here again later. + { + if (mDetailTextures[i]->getFetchPriority() <= 0.0f && !mDetailTextures[i]->hasSavedRawImage()) + { + mDetailTextures[i]->setBoostLevel(LLGLTexture::BOOST_MAP); + mDetailTextures[i]->forceToRefetchTexture(ddiscard); + } + + if(delete_raw) + { + mDetailTextures[i]->destroyRawImage() ; + } + LL_DEBUGS("Terrain") << "cached raw data for terrain detail texture is not ready yet: " << mDetailTextures[i]->getID() << " Discard: " << ddiscard << LL_ENDL; + return false; + } + + mRawImages[i] = mDetailTextures[i]->getRawImage() ; + if(delete_raw) + { + mDetailTextures[i]->destroyRawImage() ; + } + if (mDetailTextures[i]->getWidth(ddiscard) != BASE_SIZE || + mDetailTextures[i]->getHeight(ddiscard) != BASE_SIZE || + mDetailTextures[i]->getComponents() != 3) + { + LLPointer newraw = new LLImageRaw(BASE_SIZE, BASE_SIZE, 3); + newraw->composite(mRawImages[i]); + mRawImages[i] = newraw; // deletes old + } + } + st_data[i] = mRawImages[i]->getData(); + st_data_size[i] = mRawImages[i]->getDataSize(); + } + + /////////////////////////////////////// + // + // Generate and clamp x/y bounding box. + // + // + + S32 x_begin, y_begin, x_end, y_end; + x_begin = (S32)(x * mScaleInv); + y_begin = (S32)(y * mScaleInv); + x_end = ll_round( (x + width) * mScaleInv ); + y_end = ll_round( (y + width) * mScaleInv ); + + if (x_end > mWidth) + { + LL_WARNS("Terrain") << "x end > width" << LL_ENDL; + x_end = mWidth; + } + if (y_end > mWidth) + { + LL_WARNS("Terrain") << "y end > width" << LL_ENDL; + y_end = mWidth; + } + + + /////////////////////////////////////////// + // + // Generate target texture information, stride ratios. + // + // + + LLViewerTexture *texturep; + U32 tex_width, tex_height, tex_comps; + U32 tex_stride; + F32 tex_x_scalef, tex_y_scalef; + S32 tex_x_begin, tex_y_begin, tex_x_end, tex_y_end; + F32 tex_x_ratiof, tex_y_ratiof; + + texturep = mSurfacep->getSTexture(); + tex_width = texturep->getWidth(); + tex_height = texturep->getHeight(); + tex_comps = texturep->getComponents(); + tex_stride = tex_width * tex_comps; + + U32 st_comps = 3; + U32 st_width = BASE_SIZE; + U32 st_height = BASE_SIZE; + + if (tex_comps != st_comps) + { + LL_WARNS("Terrain") << "Base texture comps != input texture comps" << LL_ENDL; + return false; + } + + tex_x_scalef = (F32)tex_width / (F32)mWidth; + tex_y_scalef = (F32)tex_height / (F32)mWidth; + tex_x_begin = (S32)((F32)x_begin * tex_x_scalef); + tex_y_begin = (S32)((F32)y_begin * tex_y_scalef); + tex_x_end = (S32)((F32)x_end * tex_x_scalef); + tex_y_end = (S32)((F32)y_end * tex_y_scalef); + + tex_x_ratiof = (F32)mWidth*mScale / (F32)tex_width; + tex_y_ratiof = (F32)mWidth*mScale / (F32)tex_height; + + LLPointer raw = new LLImageRaw(tex_width, tex_height, tex_comps); + U8 *rawp = raw->getData(); + + F32 st_x_stride, st_y_stride; + st_x_stride = ((F32)st_width / (F32)mTexScaleX)*((F32)mWidth / (F32)tex_width); + st_y_stride = ((F32)st_height / (F32)mTexScaleY)*((F32)mWidth / (F32)tex_height); + + llassert(st_x_stride > 0.f); + llassert(st_y_stride > 0.f); + //////////////////////////////// + // + // Iterate through the target texture, striding through the + // subtextures and interpolating appropriately. + // + // + + F32 sti, stj; + S32 st_offset; + sti = (tex_x_begin * st_x_stride) - st_width*(llfloor((tex_x_begin * st_x_stride)/st_width)); + stj = (tex_y_begin * st_y_stride) - st_height*(llfloor((tex_y_begin * st_y_stride)/st_height)); + + st_offset = (llfloor(stj * st_width) + llfloor(sti)) * st_comps; + for (S32 j = tex_y_begin; j < tex_y_end; j++) + { + U32 offset = j * tex_stride + tex_x_begin * tex_comps; + sti = (tex_x_begin * st_x_stride) - st_width*((U32)(tex_x_begin * st_x_stride)/st_width); + for (S32 i = tex_x_begin; i < tex_x_end; i++) + { + S32 tex0, tex1; + F32 composition = getValueScaled(i*tex_x_ratiof, j*tex_y_ratiof); + + tex0 = llfloor( composition ); + tex0 = llclamp(tex0, 0, 3); + composition -= tex0; + tex1 = tex0 + 1; + tex1 = llclamp(tex1, 0, 3); + + st_offset = (lltrunc(sti) + lltrunc(stj)*st_width) * st_comps; + for (U32 k = 0; k < tex_comps; k++) + { + // Linearly interpolate based on composition. + if (st_offset >= st_data_size[tex0] || st_offset >= st_data_size[tex1]) + { + // SJB: This shouldn't be happening, but does... Rounding error? + //LL_WARNS() << "offset 0 [" << tex0 << "] =" << st_offset << " >= size=" << st_data_size[tex0] << LL_ENDL; + //LL_WARNS() << "offset 1 [" << tex1 << "] =" << st_offset << " >= size=" << st_data_size[tex1] << LL_ENDL; + } + else + { + F32 a = *(st_data[tex0] + st_offset); + F32 b = *(st_data[tex1] + st_offset); + rawp[ offset ] = (U8)lltrunc( a + composition * (b - a) ); + } + offset++; + st_offset++; + } + + sti += st_x_stride; + if (sti >= st_width) + { + sti -= st_width; + } + } + + stj += st_y_stride; + if (stj >= st_height) + { + stj -= st_height; + } + } + + if (!texturep->hasGLTexture()) + { + texturep->createGLTexture(0, raw); + } + texturep->setSubImage(raw, tex_x_begin, tex_y_begin, tex_x_end - tex_x_begin, tex_y_end - tex_y_begin); + + for (S32 i = 0; i < 4; i++) + { + // Un-boost detatil textures (will get re-boosted if rendering in high detail) + mDetailTextures[i]->setBoostLevel(LLGLTexture::BOOST_NONE); + mDetailTextures[i]->setMinDiscardLevel(MAX_DISCARD_LEVEL + 1); + } + + return true; +} + +LLUUID LLVLComposition::getDetailTextureID(S32 corner) +{ + return mDetailTextures[corner]->getID(); +} + +LLViewerFetchedTexture* LLVLComposition::getDetailTexture(S32 corner) +{ + return mDetailTextures[corner]; +} + +F32 LLVLComposition::getStartHeight(S32 corner) +{ + return mStartHeight[corner]; +} + +void LLVLComposition::setStartHeight(S32 corner, const F32 start_height) +{ + mStartHeight[corner] = start_height; +} + +F32 LLVLComposition::getHeightRange(S32 corner) +{ + return mHeightRange[corner]; +} + +void LLVLComposition::setHeightRange(S32 corner, const F32 range) +{ + mHeightRange[corner] = range; +} diff --git a/indra/newview/llvlcomposition.h b/indra/newview/llvlcomposition.h index 26449a80bf..bab0e0043a 100644 --- a/indra/newview/llvlcomposition.h +++ b/indra/newview/llvlcomposition.h @@ -1,86 +1,86 @@ -/** - * @file llvlcomposition.h - * @brief Viewer-side representation of a composition layer... - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVLCOMPOSITION_H -#define LL_LLVLCOMPOSITION_H - -#include "llviewerlayer.h" -#include "llviewertexture.h" - -class LLSurface; - -class LLVLComposition : public LLViewerLayer -{ -public: - LLVLComposition(LLSurface *surfacep, const U32 width, const F32 scale); - /*virtual*/ ~LLVLComposition(); - - void setSurface(LLSurface *surfacep); - - // Viewer side hack to generate composition values - bool generateHeights(const F32 x, const F32 y, const F32 width, const F32 height); - bool generateComposition(); - // Generate texture from composition values. - bool generateTexture(const F32 x, const F32 y, const F32 width, const F32 height); - - // Use these as indeces ito the get/setters below that use 'corner' - enum ECorner - { - SOUTHWEST = 0, - SOUTHEAST = 1, - NORTHWEST = 2, - NORTHEAST = 3, - CORNER_COUNT = 4 - }; - LLUUID getDetailTextureID(S32 corner); - LLViewerFetchedTexture* getDetailTexture(S32 corner); - F32 getStartHeight(S32 corner); - F32 getHeightRange(S32 corner); - - void setDetailTextureID(S32 corner, const LLUUID& id); - void setStartHeight(S32 corner, F32 start_height); - void setHeightRange(S32 corner, F32 range); - - friend class LLVOSurfacePatch; - friend class LLDrawPoolTerrain; - void setParamsReady() { mParamsReady = true; } - bool getParamsReady() const { return mParamsReady; } -protected: - bool mParamsReady; - LLSurface *mSurfacep; - bool mTexturesLoaded; - - LLPointer mDetailTextures[CORNER_COUNT]; - LLPointer mRawImages[CORNER_COUNT]; - - F32 mStartHeight[CORNER_COUNT]; - F32 mHeightRange[CORNER_COUNT]; - - F32 mTexScaleX; - F32 mTexScaleY; -}; - -#endif //LL_LLVLCOMPOSITION_H +/** + * @file llvlcomposition.h + * @brief Viewer-side representation of a composition layer... + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVLCOMPOSITION_H +#define LL_LLVLCOMPOSITION_H + +#include "llviewerlayer.h" +#include "llviewertexture.h" + +class LLSurface; + +class LLVLComposition : public LLViewerLayer +{ +public: + LLVLComposition(LLSurface *surfacep, const U32 width, const F32 scale); + /*virtual*/ ~LLVLComposition(); + + void setSurface(LLSurface *surfacep); + + // Viewer side hack to generate composition values + bool generateHeights(const F32 x, const F32 y, const F32 width, const F32 height); + bool generateComposition(); + // Generate texture from composition values. + bool generateTexture(const F32 x, const F32 y, const F32 width, const F32 height); + + // Use these as indeces ito the get/setters below that use 'corner' + enum ECorner + { + SOUTHWEST = 0, + SOUTHEAST = 1, + NORTHWEST = 2, + NORTHEAST = 3, + CORNER_COUNT = 4 + }; + LLUUID getDetailTextureID(S32 corner); + LLViewerFetchedTexture* getDetailTexture(S32 corner); + F32 getStartHeight(S32 corner); + F32 getHeightRange(S32 corner); + + void setDetailTextureID(S32 corner, const LLUUID& id); + void setStartHeight(S32 corner, F32 start_height); + void setHeightRange(S32 corner, F32 range); + + friend class LLVOSurfacePatch; + friend class LLDrawPoolTerrain; + void setParamsReady() { mParamsReady = true; } + bool getParamsReady() const { return mParamsReady; } +protected: + bool mParamsReady; + LLSurface *mSurfacep; + bool mTexturesLoaded; + + LLPointer mDetailTextures[CORNER_COUNT]; + LLPointer mRawImages[CORNER_COUNT]; + + F32 mStartHeight[CORNER_COUNT]; + F32 mHeightRange[CORNER_COUNT]; + + F32 mTexScaleX; + F32 mTexScaleY; +}; + +#endif //LL_LLVLCOMPOSITION_H diff --git a/indra/newview/llvlmanager.cpp b/indra/newview/llvlmanager.cpp index cbaeb66e59..c2bcd32921 100644 --- a/indra/newview/llvlmanager.cpp +++ b/indra/newview/llvlmanager.cpp @@ -1,168 +1,168 @@ -/** - * @file llvlmanager.cpp - * @brief LLVLManager class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llvlmanager.h" - -#include "indra_constants.h" -#include "patch_code.h" -#include "patch_dct.h" -#include "llviewerregion.h" -#include "llframetimer.h" -#include "llsurface.h" -#include "llbitpack.h" - -const char LAND_LAYER_CODE = 'L'; -const char WIND_LAYER_CODE = '7'; -const char CLOUD_LAYER_CODE = '8'; - -LLVLManager gVLManager; - -LLVLManager::~LLVLManager() -{ - S32 i; - for (i = 0; i < mPacketData.size(); i++) - { - delete mPacketData[i]; - } - mPacketData.clear(); -} - -void LLVLManager::addLayerData(LLVLData *vl_datap, const S32Bytes mesg_size) -{ - if (LAND_LAYER_CODE == vl_datap->mType) - { - mLandBits += mesg_size; - } - else if (WIND_LAYER_CODE == vl_datap->mType) - { - mWindBits += mesg_size; - } - else if (CLOUD_LAYER_CODE == vl_datap->mType) - { - mCloudBits += mesg_size; - } - else - { - LL_ERRS() << "Unknown layer type!" << (S32)vl_datap->mType << LL_ENDL; - } - - mPacketData.push_back(vl_datap); -} - -void LLVLManager::unpackData(const S32 num_packets) -{ - static LLFrameTimer decode_timer; - - S32 i; - for (i = 0; i < mPacketData.size(); i++) - { - LLVLData *datap = mPacketData[i]; - - LLBitPack bit_pack(datap->mData, datap->mSize); - LLGroupHeader goph; - - decode_patch_group_header(bit_pack, &goph); - if (LAND_LAYER_CODE == datap->mType) - { - datap->mRegionp->getLand().decompressDCTPatch(bit_pack, &goph, false); - } - else if (WIND_LAYER_CODE == datap->mType) - { - datap->mRegionp->mWind.decompress(bit_pack, &goph); - - } - else if (CLOUD_LAYER_CODE == datap->mType) - { - - } - } - - for (i = 0; i < mPacketData.size(); i++) - { - delete mPacketData[i]; - } - mPacketData.clear(); - -} - -void LLVLManager::resetBitCounts() -{ - mLandBits = mWindBits = mCloudBits = (S32Bits)0; -} - -U32Bits LLVLManager::getLandBits() const -{ - return mLandBits; -} - -U32Bits LLVLManager::getWindBits() const -{ - return mWindBits; -} - -U32Bits LLVLManager::getCloudBits() const -{ - return mCloudBits; -} - -S32Bytes LLVLManager::getTotalBytes() const -{ - return mLandBits + mWindBits + mCloudBits; -} - -void LLVLManager::cleanupData(LLViewerRegion *regionp) -{ - S32 cur = 0; - while (cur < mPacketData.size()) - { - if (mPacketData[cur]->mRegionp == regionp) - { - delete mPacketData[cur]; - mPacketData.erase(mPacketData.begin() + cur); - } - else - { - cur++; - } - } -} - -LLVLData::LLVLData(LLViewerRegion *regionp, const S8 type, U8 *data, const S32 size) -{ - mType = type; - mData = data; - mRegionp = regionp; - mSize = size; -} - -LLVLData::~LLVLData() -{ - delete [] mData; - mData = NULL; - mRegionp = NULL; -} +/** + * @file llvlmanager.cpp + * @brief LLVLManager class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llvlmanager.h" + +#include "indra_constants.h" +#include "patch_code.h" +#include "patch_dct.h" +#include "llviewerregion.h" +#include "llframetimer.h" +#include "llsurface.h" +#include "llbitpack.h" + +const char LAND_LAYER_CODE = 'L'; +const char WIND_LAYER_CODE = '7'; +const char CLOUD_LAYER_CODE = '8'; + +LLVLManager gVLManager; + +LLVLManager::~LLVLManager() +{ + S32 i; + for (i = 0; i < mPacketData.size(); i++) + { + delete mPacketData[i]; + } + mPacketData.clear(); +} + +void LLVLManager::addLayerData(LLVLData *vl_datap, const S32Bytes mesg_size) +{ + if (LAND_LAYER_CODE == vl_datap->mType) + { + mLandBits += mesg_size; + } + else if (WIND_LAYER_CODE == vl_datap->mType) + { + mWindBits += mesg_size; + } + else if (CLOUD_LAYER_CODE == vl_datap->mType) + { + mCloudBits += mesg_size; + } + else + { + LL_ERRS() << "Unknown layer type!" << (S32)vl_datap->mType << LL_ENDL; + } + + mPacketData.push_back(vl_datap); +} + +void LLVLManager::unpackData(const S32 num_packets) +{ + static LLFrameTimer decode_timer; + + S32 i; + for (i = 0; i < mPacketData.size(); i++) + { + LLVLData *datap = mPacketData[i]; + + LLBitPack bit_pack(datap->mData, datap->mSize); + LLGroupHeader goph; + + decode_patch_group_header(bit_pack, &goph); + if (LAND_LAYER_CODE == datap->mType) + { + datap->mRegionp->getLand().decompressDCTPatch(bit_pack, &goph, false); + } + else if (WIND_LAYER_CODE == datap->mType) + { + datap->mRegionp->mWind.decompress(bit_pack, &goph); + + } + else if (CLOUD_LAYER_CODE == datap->mType) + { + + } + } + + for (i = 0; i < mPacketData.size(); i++) + { + delete mPacketData[i]; + } + mPacketData.clear(); + +} + +void LLVLManager::resetBitCounts() +{ + mLandBits = mWindBits = mCloudBits = (S32Bits)0; +} + +U32Bits LLVLManager::getLandBits() const +{ + return mLandBits; +} + +U32Bits LLVLManager::getWindBits() const +{ + return mWindBits; +} + +U32Bits LLVLManager::getCloudBits() const +{ + return mCloudBits; +} + +S32Bytes LLVLManager::getTotalBytes() const +{ + return mLandBits + mWindBits + mCloudBits; +} + +void LLVLManager::cleanupData(LLViewerRegion *regionp) +{ + S32 cur = 0; + while (cur < mPacketData.size()) + { + if (mPacketData[cur]->mRegionp == regionp) + { + delete mPacketData[cur]; + mPacketData.erase(mPacketData.begin() + cur); + } + else + { + cur++; + } + } +} + +LLVLData::LLVLData(LLViewerRegion *regionp, const S8 type, U8 *data, const S32 size) +{ + mType = type; + mData = data; + mRegionp = regionp; + mSize = size; +} + +LLVLData::~LLVLData() +{ + delete [] mData; + mData = NULL; + mRegionp = NULL; +} diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index f2b623f1ee..9a798e3b01 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -1,11741 +1,11741 @@ -/** - * @File llvoavatar.cpp - * @brief Implementation of LLVOAvatar class which is a derivation of LLViewerObject - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llvoavatar.h" - -#include -#include -#include - -#include "llaudioengine.h" -#include "noise.h" -#include "sound_ids.h" -#include "raytrace.h" - -#include "llagent.h" // Get state values from here -#include "llagentbenefits.h" -#include "llagentcamera.h" -#include "llagentwearables.h" -#include "llanimationstates.h" -#include "llavatarnamecache.h" -#include "llavatarpropertiesprocessor.h" -#include "llavatarrendernotifier.h" -#include "llcontrolavatar.h" -#include "llexperiencecache.h" -#include "llphysicsmotion.h" -#include "llviewercontrol.h" -#include "llcallingcard.h" // IDEVO for LLAvatarTracker -#include "lldrawpoolavatar.h" -#include "lldriverparam.h" -#include "llpolyskeletaldistortion.h" -#include "lleditingmotion.h" -#include "llemote.h" -#include "llfloatertools.h" -#include "llheadrotmotion.h" -#include "llhudeffecttrail.h" -#include "llhudmanager.h" -#include "llhudnametag.h" -#include "llhudtext.h" // for mText/mDebugText -#include "llimview.h" -#include "llinitparam.h" -#include "llkeyframefallmotion.h" -#include "llkeyframestandmotion.h" -#include "llkeyframewalkmotion.h" -#include "llmanipscale.h" // for get_default_max_prim_scale() -#include "llmeshrepository.h" -#include "llmutelist.h" -#include "llmoveview.h" -#include "llnotificationsutil.h" -#include "llphysicsshapebuilderutil.h" -#include "llquantize.h" -#include "llrand.h" -#include "llregionhandle.h" -#include "llresmgr.h" -#include "llselectmgr.h" -#include "llsprite.h" -#include "lltargetingmotion.h" -#include "lltoolmgr.h" -#include "lltoolmorph.h" -#include "llviewercamera.h" -#include "llviewertexlayer.h" -#include "llviewertexturelist.h" -#include "llviewermenu.h" -#include "llviewerobjectlist.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" -#include "llviewershadermgr.h" -#include "llviewerstats.h" -#include "llviewerwearable.h" -#include "llvoavatarself.h" -#include "llvovolume.h" -#include "llworld.h" -#include "pipeline.h" -#include "llviewershadermgr.h" -#include "llsky.h" -#include "llanimstatelabels.h" -#include "lltrans.h" -#include "llappearancemgr.h" - -#include "llgesturemgr.h" //needed to trigger the voice gesticulations -#include "llvoiceclient.h" -#include "llvoicevisualizer.h" // Ventrella - -#include "lldebugmessagebox.h" -#include "llsdutil.h" -#include "llscenemonitor.h" -#include "llsdserialize.h" -#include "llcallstack.h" -#include "llrendersphere.h" -#include "llskinningutil.h" - -#include "llperfstats.h" - -#include - -extern F32 SPEED_ADJUST_MAX; -extern F32 SPEED_ADJUST_MAX_SEC; -extern F32 ANIM_SPEED_MAX; -extern F32 ANIM_SPEED_MIN; -extern U32 JOINT_COUNT_REQUIRED_FOR_FULLRIG; - -const F32 MAX_HOVER_Z = 2.0; -const F32 MIN_HOVER_Z = -2.0; - -const F32 MIN_ATTACHMENT_COMPLEXITY = 0.f; -const F32 DEFAULT_MAX_ATTACHMENT_COMPLEXITY = 1.0e6f; - -// Unlike with 'self' avatar, server doesn't inform viewer about -// expected attachments so viewer has to wait to see if anything -// else will arrive -const F32 FIRST_APPEARANCE_CLOUD_MIN_DELAY = 3.f; // seconds -const F32 FIRST_APPEARANCE_CLOUD_MAX_DELAY = 15.f; -const F32 FIRST_APPEARANCE_CLOUD_IMPOSTOR_MODIFIER = 1.25f; - -using namespace LLAvatarAppearanceDefines; - -//----------------------------------------------------------------------------- -// Global constants -//----------------------------------------------------------------------------- -const LLUUID ANIM_AGENT_BODY_NOISE = LLUUID("9aa8b0a6-0c6f-9518-c7c3-4f41f2c001ad"); //"body_noise" -const LLUUID ANIM_AGENT_BREATHE_ROT = LLUUID("4c5a103e-b830-2f1c-16bc-224aa0ad5bc8"); //"breathe_rot" -const LLUUID ANIM_AGENT_EDITING = LLUUID("2a8eba1d-a7f8-5596-d44a-b4977bf8c8bb"); //"editing" -const LLUUID ANIM_AGENT_EYE = LLUUID("5c780ea8-1cd1-c463-a128-48c023f6fbea"); //"eye" -const LLUUID ANIM_AGENT_FLY_ADJUST = LLUUID("db95561f-f1b0-9f9a-7224-b12f71af126e"); //"fly_adjust" -const LLUUID ANIM_AGENT_HAND_MOTION = LLUUID("ce986325-0ba7-6e6e-cc24-b17c4b795578"); //"hand_motion" -const LLUUID ANIM_AGENT_HEAD_ROT = LLUUID("e6e8d1dd-e643-fff7-b238-c6b4b056a68d"); //"head_rot" -const LLUUID ANIM_AGENT_PELVIS_FIX = LLUUID("0c5dd2a2-514d-8893-d44d-05beffad208b"); //"pelvis_fix" -const LLUUID ANIM_AGENT_TARGET = LLUUID("0e4896cb-fba4-926c-f355-8720189d5b55"); //"target" -const LLUUID ANIM_AGENT_WALK_ADJUST = LLUUID("829bc85b-02fc-ec41-be2e-74cc6dd7215d"); //"walk_adjust" -const LLUUID ANIM_AGENT_PHYSICS_MOTION = LLUUID("7360e029-3cb8-ebc4-863e-212df440d987"); //"physics_motion" - - -//----------------------------------------------------------------------------- -// Constants -//----------------------------------------------------------------------------- -const F32 DELTA_TIME_MIN = 0.01f; // we clamp measured delta_time to this -const F32 DELTA_TIME_MAX = 0.2f; // range to insure stability of computations. - -const F32 PELVIS_LAG_FLYING = 0.22f;// pelvis follow half life while flying -const F32 PELVIS_LAG_WALKING = 0.4f; // ...while walking -const F32 PELVIS_LAG_MOUSELOOK = 0.15f; -const F32 MOUSELOOK_PELVIS_FOLLOW_FACTOR = 0.5f; -const F32 TORSO_NOISE_AMOUNT = 1.0f; // Amount of deviation from up-axis, in degrees -const F32 TORSO_NOISE_SPEED = 0.2f; // Time scale factor on torso noise. - -const F32 BREATHE_ROT_MOTION_STRENGTH = 0.05f; - -const S32 MIN_REQUIRED_PIXEL_AREA_BODY_NOISE = 10000; -const S32 MIN_REQUIRED_PIXEL_AREA_BREATHE = 10000; -const S32 MIN_REQUIRED_PIXEL_AREA_PELVIS_FIX = 40; - -const S32 TEX_IMAGE_SIZE_OTHER = 512 / 4; // The size of local textures for other (!isSelf()) avatars - -const F32 HEAD_MOVEMENT_AVG_TIME = 0.9f; - -const S32 MORPH_MASK_REQUESTED_DISCARD = 0; - -const F32 MAX_STANDOFF_FROM_ORIGIN = 3; -const F32 MAX_STANDOFF_DISTANCE_CHANGE = 32; - -// Discard level at which to switch to baked textures -// Should probably be 4 or 3, but didn't want to change it while change other logic - SJB -const S32 SWITCH_TO_BAKED_DISCARD = 5; - -const F32 HOVER_EFFECT_MAX_SPEED = 3.f; -const F32 HOVER_EFFECT_STRENGTH = 0.f; -const F32 UNDERWATER_EFFECT_STRENGTH = 0.1f; -const F32 UNDERWATER_FREQUENCY_DAMP = 0.33f; -const F32 APPEARANCE_MORPH_TIME = 0.65f; -const F32 TIME_BEFORE_MESH_CLEANUP = 5.f; // seconds -const S32 AVATAR_RELEASE_THRESHOLD = 10; // number of avatar instances before releasing memory -const F32 FOOT_GROUND_COLLISION_TOLERANCE = 0.25f; -const F32 AVATAR_LOD_TWEAK_RANGE = 0.7f; -const S32 MAX_BUBBLE_CHAT_LENGTH = DB_CHAT_MSG_STR_LEN; -const S32 MAX_BUBBLE_CHAT_UTTERANCES = 12; -const F32 CHAT_FADE_TIME = 8.0; -const F32 BUBBLE_CHAT_TIME = CHAT_FADE_TIME * 3.f; -const F32 NAMETAG_UPDATE_THRESHOLD = 0.3f; -const F32 NAMETAG_VERTICAL_SCREEN_OFFSET = 25.f; -const F32 NAMETAG_VERT_OFFSET_WEIGHT = 0.17f; - -const U32 LLVOAvatar::VISUAL_COMPLEXITY_UNKNOWN = 0; -const F64 HUD_OVERSIZED_TEXTURE_DATA_SIZE = 1024 * 1024; - -const F32 MAX_TEXTURE_WAIT_TIME_SEC = 60; -const F32 MAX_ATTACHMENT_WAIT_TIME_SEC = 60; - -const S32 MIN_NONTUNED_AVS = 5; - -enum ERenderName -{ - RENDER_NAME_NEVER, - RENDER_NAME_ALWAYS, - RENDER_NAME_FADE -}; - -#define JELLYDOLLS_SHOULD_IMPOSTOR - -//----------------------------------------------------------------------------- -// Callback data -//----------------------------------------------------------------------------- - -struct LLTextureMaskData -{ - LLTextureMaskData( const LLUUID& id ) : - mAvatarID(id), - mLastDiscardLevel(S32_MAX) - {} - LLUUID mAvatarID; - S32 mLastDiscardLevel; -}; - -/********************************************************************************* - ** ** - ** Begin private LLVOAvatar Support classes - ** - **/ - - -struct LLAppearanceMessageContents: public LLRefCount -{ - LLAppearanceMessageContents(): - mAppearanceVersion(-1), - mParamAppearanceVersion(-1), - mCOFVersion(LLViewerInventoryCategory::VERSION_UNKNOWN) - { - } - LLTEContents mTEContents; - S32 mAppearanceVersion; - S32 mParamAppearanceVersion; - S32 mCOFVersion; - // For future use: - //U32 appearance_flags = 0; - std::vector mParamWeights; - std::vector mParams; - LLVector3 mHoverOffset; - bool mHoverOffsetWasSet; -}; - - -//----------------------------------------------------------------------------- -// class LLBodyNoiseMotion -//----------------------------------------------------------------------------- -class LLBodyNoiseMotion : - public LLMotion -{ -public: - // Constructor - LLBodyNoiseMotion(const LLUUID &id) - : LLMotion(id) - { - mName = "body_noise"; - mTorsoState = new LLJointState; - } - - // Destructor - virtual ~LLBodyNoiseMotion() { } - -public: - //------------------------------------------------------------------------- - // functions to support MotionController and MotionRegistry - //------------------------------------------------------------------------- - // static constructor - // all subclasses must implement such a function and register it - static LLMotion *create(const LLUUID &id) { return new LLBodyNoiseMotion(id); } - -public: - //------------------------------------------------------------------------- - // animation callbacks to be implemented by subclasses - //------------------------------------------------------------------------- - - // motions must specify whether or not they loop - virtual bool getLoop() { return true; } - - // motions must report their total duration - virtual F32 getDuration() { return 0.0; } - - // motions must report their "ease in" duration - virtual F32 getEaseInDuration() { return 0.0; } - - // motions must report their "ease out" duration. - virtual F32 getEaseOutDuration() { return 0.0; } - - // motions must report their priority - virtual LLJoint::JointPriority getPriority() { return LLJoint::HIGH_PRIORITY; } - - virtual LLMotionBlendType getBlendType() { return ADDITIVE_BLEND; } - - // called to determine when a motion should be activated/deactivated based on avatar pixel coverage - virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_BODY_NOISE; } - - // run-time (post constructor) initialization, - // called after parameters have been set - // must return true to indicate success and be available for activation - virtual LLMotionInitStatus onInitialize(LLCharacter *character) - { - if( !mTorsoState->setJoint( character->getJoint("mTorso") )) - { - return STATUS_FAILURE; - } - - mTorsoState->setUsage(LLJointState::ROT); - - addJointState( mTorsoState ); - return STATUS_SUCCESS; - } - - // called when a motion is activated - // must return true to indicate success, or else - // it will be deactivated - virtual bool onActivate() { return true; } - - // called per time step - // must return true while it is active, and - // must return false when the motion is completed. - virtual bool onUpdate(F32 time, U8* joint_mask) - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - F32 nx[2]; - nx[0]=time*TORSO_NOISE_SPEED; - nx[1]=0.0f; - F32 ny[2]; - ny[0]=0.0f; - ny[1]=time*TORSO_NOISE_SPEED; - F32 noiseX = noise2(nx); - F32 noiseY = noise2(ny); - - F32 rx = TORSO_NOISE_AMOUNT * DEG_TO_RAD * noiseX / 0.42f; - F32 ry = TORSO_NOISE_AMOUNT * DEG_TO_RAD * noiseY / 0.42f; - LLQuaternion tQn; - tQn.setQuat( rx, ry, 0.0f ); - mTorsoState->setRotation( tQn ); - - return true; - } - - // called when a motion is deactivated - virtual void onDeactivate() {} - -private: - //------------------------------------------------------------------------- - // joint states to be animated - //------------------------------------------------------------------------- - LLPointer mTorsoState; -}; - -//----------------------------------------------------------------------------- -// class LLBreatheMotionRot -//----------------------------------------------------------------------------- -class LLBreatheMotionRot : - public LLMotion -{ -public: - // Constructor - LLBreatheMotionRot(const LLUUID &id) : - LLMotion(id), - mBreatheRate(1.f), - mCharacter(NULL) - { - mName = "breathe_rot"; - mChestState = new LLJointState; - } - - // Destructor - virtual ~LLBreatheMotionRot() {} - -public: - //------------------------------------------------------------------------- - // functions to support MotionController and MotionRegistry - //------------------------------------------------------------------------- - // static constructor - // all subclasses must implement such a function and register it - static LLMotion *create(const LLUUID &id) { return new LLBreatheMotionRot(id); } - -public: - //------------------------------------------------------------------------- - // animation callbacks to be implemented by subclasses - //------------------------------------------------------------------------- - - // motions must specify whether or not they loop - virtual bool getLoop() { return true; } - - // motions must report their total duration - virtual F32 getDuration() { return 0.0; } - - // motions must report their "ease in" duration - virtual F32 getEaseInDuration() { return 0.0; } - - // motions must report their "ease out" duration. - virtual F32 getEaseOutDuration() { return 0.0; } - - // motions must report their priority - virtual LLJoint::JointPriority getPriority() { return LLJoint::MEDIUM_PRIORITY; } - - virtual LLMotionBlendType getBlendType() { return NORMAL_BLEND; } - - // called to determine when a motion should be activated/deactivated based on avatar pixel coverage - virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_BREATHE; } - - // run-time (post constructor) initialization, - // called after parameters have been set - // must return true to indicate success and be available for activation - virtual LLMotionInitStatus onInitialize(LLCharacter *character) - { - mCharacter = character; - bool success = true; - - if ( !mChestState->setJoint( character->getJoint( "mChest" ) ) ) - { - success = false; - } - - if ( success ) - { - mChestState->setUsage(LLJointState::ROT); - addJointState( mChestState ); - } - - if ( success ) - { - return STATUS_SUCCESS; - } - else - { - return STATUS_FAILURE; - } - } - - // called when a motion is activated - // must return true to indicate success, or else - // it will be deactivated - virtual bool onActivate() { return true; } - - // called per time step - // must return true while it is active, and - // must return false when the motion is completed. - virtual bool onUpdate(F32 time, U8* joint_mask) - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - mBreatheRate = 1.f; - - F32 breathe_amt = (sinf(mBreatheRate * time) * BREATHE_ROT_MOTION_STRENGTH); - - mChestState->setRotation(LLQuaternion(breathe_amt, LLVector3(0.f, 1.f, 0.f))); - - return true; - } - - // called when a motion is deactivated - virtual void onDeactivate() {} - -private: - //------------------------------------------------------------------------- - // joint states to be animated - //------------------------------------------------------------------------- - LLPointer mChestState; - F32 mBreatheRate; - LLCharacter* mCharacter; -}; - -//----------------------------------------------------------------------------- -// class LLPelvisFixMotion -//----------------------------------------------------------------------------- -class LLPelvisFixMotion : - public LLMotion -{ -public: - // Constructor - LLPelvisFixMotion(const LLUUID &id) - : LLMotion(id), mCharacter(NULL) - { - mName = "pelvis_fix"; - - mPelvisState = new LLJointState; - } - - // Destructor - virtual ~LLPelvisFixMotion() { } - -public: - //------------------------------------------------------------------------- - // functions to support MotionController and MotionRegistry - //------------------------------------------------------------------------- - // static constructor - // all subclasses must implement such a function and register it - static LLMotion *create(const LLUUID& id) { return new LLPelvisFixMotion(id); } - -public: - //------------------------------------------------------------------------- - // animation callbacks to be implemented by subclasses - //------------------------------------------------------------------------- - - // motions must specify whether or not they loop - virtual bool getLoop() { return true; } - - // motions must report their total duration - virtual F32 getDuration() { return 0.0; } - - // motions must report their "ease in" duration - virtual F32 getEaseInDuration() { return 0.5f; } - - // motions must report their "ease out" duration. - virtual F32 getEaseOutDuration() { return 0.5f; } - - // motions must report their priority - virtual LLJoint::JointPriority getPriority() { return LLJoint::LOW_PRIORITY; } - - virtual LLMotionBlendType getBlendType() { return NORMAL_BLEND; } - - // called to determine when a motion should be activated/deactivated based on avatar pixel coverage - virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_PELVIS_FIX; } - - // run-time (post constructor) initialization, - // called after parameters have been set - // must return true to indicate success and be available for activation - virtual LLMotionInitStatus onInitialize(LLCharacter *character) - { - mCharacter = character; - - if (!mPelvisState->setJoint( character->getJoint("mPelvis"))) - { - return STATUS_FAILURE; - } - - mPelvisState->setUsage(LLJointState::POS); - - addJointState( mPelvisState ); - return STATUS_SUCCESS; - } - - // called when a motion is activated - // must return true to indicate success, or else - // it will be deactivated - virtual bool onActivate() { return true; } - - // called per time step - // must return true while it is active, and - // must return false when the motion is completed. - virtual bool onUpdate(F32 time, U8* joint_mask) - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - mPelvisState->setPosition(LLVector3::zero); - - return true; - } - - // called when a motion is deactivated - virtual void onDeactivate() {} - -private: - //------------------------------------------------------------------------- - // joint states to be animated - //------------------------------------------------------------------------- - LLPointer mPelvisState; - LLCharacter* mCharacter; -}; - -/** - ** - ** End LLVOAvatar Support classes - ** ** - *********************************************************************************/ - - -//----------------------------------------------------------------------------- -// Static Data -//----------------------------------------------------------------------------- -U32 LLVOAvatar::sMaxNonImpostors = 12; // Set from RenderAvatarMaxNonImpostors -bool LLVOAvatar::sLimitNonImpostors = false; // True unless RenderAvatarMaxNonImpostors is 0 (unlimited) -F32 LLVOAvatar::sRenderDistance = 256.f; -S32 LLVOAvatar::sNumVisibleAvatars = 0; -S32 LLVOAvatar::sNumLODChangesThisFrame = 0; - -const LLUUID LLVOAvatar::sStepSoundOnLand("e8af4a28-aa83-4310-a7c4-c047e15ea0df"); -const LLUUID LLVOAvatar::sStepSounds[LL_MCODE_END] = -{ - SND_STONE_RUBBER, - SND_METAL_RUBBER, - SND_GLASS_RUBBER, - SND_WOOD_RUBBER, - SND_FLESH_RUBBER, - SND_RUBBER_PLASTIC, - SND_RUBBER_RUBBER -}; - -S32 LLVOAvatar::sRenderName = RENDER_NAME_ALWAYS; -bool LLVOAvatar::sRenderGroupTitles = true; -S32 LLVOAvatar::sNumVisibleChatBubbles = 0; -bool LLVOAvatar::sDebugInvisible = false; -bool LLVOAvatar::sShowAttachmentPoints = false; -bool LLVOAvatar::sShowAnimationDebug = false; -bool LLVOAvatar::sVisibleInFirstPerson = false; -F32 LLVOAvatar::sLODFactor = 1.f; -F32 LLVOAvatar::sPhysicsLODFactor = 1.f; -bool LLVOAvatar::sJointDebug = false; -F32 LLVOAvatar::sUnbakedTime = 0.f; -F32 LLVOAvatar::sUnbakedUpdateTime = 0.f; -F32 LLVOAvatar::sGreyTime = 0.f; -F32 LLVOAvatar::sGreyUpdateTime = 0.f; -LLPointer LLVOAvatar::sCloudTexture = NULL; -std::vector LLVOAvatar::sAVsIgnoringARTLimit; -S32 LLVOAvatar::sAvatarsNearby = 0; - -//----------------------------------------------------------------------------- -// Helper functions -//----------------------------------------------------------------------------- -static F32 calc_bouncy_animation(F32 x); - -//----------------------------------------------------------------------------- -// LLVOAvatar() -//----------------------------------------------------------------------------- -LLVOAvatar::LLVOAvatar(const LLUUID& id, - const LLPCode pcode, - LLViewerRegion* regionp) : - LLAvatarAppearance(&gAgentWearables), - LLViewerObject(id, pcode, regionp), - mSpecialRenderMode(0), - mAttachmentSurfaceArea(0.f), - mAttachmentVisibleTriangleCount(0), - mAttachmentEstTriangleCount(0.f), - mReportedVisualComplexity(VISUAL_COMPLEXITY_UNKNOWN), - mTurning(false), - mLastSkeletonSerialNum( 0 ), - mIsSitting(false), - mTimeVisible(), - mTyping(false), - mMeshValid(false), - mVisible(false), - mLastImpostorUpdateFrameTime(0.f), - mLastImpostorUpdateReason(0), - mWindFreq(0.f), - mRipplePhase( 0.f ), - mBelowWater(false), - mLastAppearanceBlendTime(0.f), - mAppearanceAnimating(false), - mNameIsSet(false), - mTitle(), - mNameAway(false), - mNameDoNotDisturb(false), - mNameMute(false), - mNameAppearance(false), - mNameFriend(false), - mNameAlpha(0.f), - mRenderGroupTitles(sRenderGroupTitles), - mNameCloud(false), - mFirstTEMessageReceived( false ), - mFirstAppearanceMessageReceived( false ), - mCulled( false ), - mVisibilityRank(0), - mNeedsSkin(false), - mLastSkinTime(0.f), - mUpdatePeriod(1), - mOverallAppearance(AOA_INVISIBLE), - mVisualComplexityStale(true), - mVisuallyMuteSetting(AV_RENDER_NORMALLY), - mMutedAVColor(LLColor4::white /* used for "uninitialize" */), - mFirstFullyVisible(true), - mFirstDecloudTime(-1.f), - mFullyLoaded(false), - mPreviousFullyLoaded(false), - mFullyLoadedInitialized(false), - mLastCloudAttachmentCount(0), - mVisualComplexity(VISUAL_COMPLEXITY_UNKNOWN), - mLoadedCallbacksPaused(false), - mLoadedCallbackTextures(0), - mRenderUnloadedAvatar(LLCachedControl(gSavedSettings, "RenderUnloadedAvatar", false)), - mLastRezzedStatus(-1), - mIsEditingAppearance(false), - mUseLocalAppearance(false), - mLastUpdateRequestCOFVersion(-1), - mLastUpdateReceivedCOFVersion(-1), - mCachedMuteListUpdateTime(0), - mCachedInMuteList(false), - mIsControlAvatar(false), - mIsUIAvatar(false), - mEnableDefaultMotions(true) -{ - LL_DEBUGS("AvatarRender") << "LLVOAvatar Constructor (0x" << this << ") id:" << mID << LL_ENDL; - - //VTResume(); // VTune - setHoverOffset(LLVector3(0.0, 0.0, 0.0)); - - // mVoiceVisualizer is created by the hud effects manager and uses the HUD Effects pipeline - const bool needsSendToSim = false; // currently, this HUD effect doesn't need to pack and unpack data to do its job - mVoiceVisualizer = ( LLVoiceVisualizer *)LLHUDManager::getInstance()->createViewerEffect( LLHUDObject::LL_HUD_EFFECT_VOICE_VISUALIZER, needsSendToSim ); - - LL_DEBUGS("Avatar","Message") << "LLVOAvatar Constructor (0x" << this << ") id:" << mID << LL_ENDL; - mPelvisp = NULL; - - mDirtyMesh = 2; // Dirty geometry, need to regenerate. - mMeshTexturesDirty = false; - mHeadp = NULL; - - - // set up animation variables - mSpeed = 0.f; - setAnimationData("Speed", &mSpeed); - - mNeedsImpostorUpdate = true; - mLastImpostorUpdateReason = 0; - mNeedsAnimUpdate = true; - - mNeedsExtentUpdate = true; - - mImpostorDistance = 0; - mImpostorPixelArea = 0; - - setNumTEs(TEX_NUM_INDICES); - - mbCanSelect = true; - - mSignaledAnimations.clear(); - mPlayingAnimations.clear(); - - mWasOnGroundLeft = false; - mWasOnGroundRight = false; - - mTimeLast = 0.0f; - mSpeedAccum = 0.0f; - - mRippleTimeLast = 0.f; - - mInAir = false; - - mStepOnLand = true; - mStepMaterial = 0; - - mLipSyncActive = false; - mOohMorph = NULL; - mAahMorph = NULL; - - mCurrentGesticulationLevel = 0; - - mFirstAppearanceMessageTimer.reset(); - mRuthTimer.reset(); - mRuthDebugTimer.reset(); - mDebugExistenceTimer.reset(); - mLastAppearanceMessageTimer.reset(); - - if(LLSceneMonitor::getInstance()->isEnabled()) - { - LLSceneMonitor::getInstance()->freezeAvatar((LLCharacter*)this); - } - - mVisuallyMuteSetting = LLVOAvatar::VisualMuteSettings(LLRenderMuteList::getInstance()->getSavedVisualMuteSetting(getID())); -} - -std::string LLVOAvatar::avString() const -{ - if (isControlAvatar()) - { - return " " + getFullname() + " "; - } - else - { - std::string viz_string = LLVOAvatar::rezStatusToString(getRezzedStatus()); - return " Avatar '" + getFullname() + "' " + viz_string + " "; - } -} - -void LLVOAvatar::debugAvatarRezTime(std::string notification_name, std::string comment) -{ - if (gDisconnected) - { - // If we disconected, these values are likely to be invalid and - // avString() might crash due to a dead sAvatarDictionary - return; - } - - LL_INFOS("Avatar") << "REZTIME: [ " << (U32)mDebugExistenceTimer.getElapsedTimeF32() - << "sec ]" - << avString() - << "RuthTimer " << (U32)mRuthDebugTimer.getElapsedTimeF32() - << " Notification " << notification_name - << " : " << comment - << LL_ENDL; - - if (gSavedSettings.getBOOL("DebugAvatarRezTime")) - { - LLSD args; - args["EXISTENCE"] = llformat("%d",(U32)mDebugExistenceTimer.getElapsedTimeF32()); - args["TIME"] = llformat("%d",(U32)mRuthDebugTimer.getElapsedTimeF32()); - args["NAME"] = getFullname(); - LLNotificationsUtil::add(notification_name,args); - } -} - -//------------------------------------------------------------------------ -// LLVOAvatar::~LLVOAvatar() -//------------------------------------------------------------------------ -LLVOAvatar::~LLVOAvatar() -{ - if (!mFullyLoaded) - { - debugAvatarRezTime("AvatarRezLeftCloudNotification","left after ruth seconds as cloud"); - } - else - { - debugAvatarRezTime("AvatarRezLeftNotification","left sometime after declouding"); - } - - if(mTuned) - { - LLPerfStats::tunedAvatars--; - mTuned = false; - } - sAVsIgnoringARTLimit.erase(std::remove(sAVsIgnoringARTLimit.begin(), sAVsIgnoringARTLimit.end(), mID), sAVsIgnoringARTLimit.end()); - - - logPendingPhases(); - - LL_DEBUGS("Avatar") << "LLVOAvatar Destructor (0x" << this << ") id:" << mID << LL_ENDL; - - std::for_each(mAttachmentPoints.begin(), mAttachmentPoints.end(), DeletePairedPointer()); - mAttachmentPoints.clear(); - - mDead = true; - - mAnimationSources.clear(); - LLLoadedCallbackEntry::cleanUpCallbackList(&mCallbackTextureList) ; - - getPhases().clearPhases(); - - LL_DEBUGS() << "LLVOAvatar Destructor end" << LL_ENDL; -} - -void LLVOAvatar::markDead() -{ - if (mNameText) - { - mNameText->markDead(); - mNameText = NULL; - sNumVisibleChatBubbles--; - } - mVoiceVisualizer->markDead(); - LLLoadedCallbackEntry::cleanUpCallbackList(&mCallbackTextureList) ; - LLViewerObject::markDead(); -} - - -bool LLVOAvatar::isFullyBaked() -{ - if (mIsDummy) return true; - if (getNumTEs() == 0) return false; - - for (U32 i = 0; i < mBakedTextureDatas.size(); i++) - { - if (!isTextureDefined(mBakedTextureDatas[i].mTextureIndex) - && ((i != BAKED_SKIRT) || isWearingWearableType(LLWearableType::WT_SKIRT)) - && (i != BAKED_LEFT_ARM) && (i != BAKED_LEFT_LEG) && (i != BAKED_AUX1) && (i != BAKED_AUX2) && (i != BAKED_AUX3)) - { - return false; - } - } - return true; -} - -bool LLVOAvatar::isFullyTextured() const -{ - for (S32 i = 0; i < mMeshLOD.size(); i++) - { - LLAvatarJoint* joint = mMeshLOD[i]; - if (i==MESH_ID_SKIRT && !isWearingWearableType(LLWearableType::WT_SKIRT)) - { - continue; // don't care about skirt textures if we're not wearing one. - } - if (!joint) - { - continue; // nonexistent LOD OK. - } - avatar_joint_mesh_list_t::iterator meshIter = joint->mMeshParts.begin(); - if (meshIter != joint->mMeshParts.end()) - { - LLAvatarJointMesh *mesh = (*meshIter); - if (!mesh) - { - continue; // nonexistent mesh OK - } - if (mesh->hasGLTexture()) - { - continue; // Mesh exists and has a baked texture. - } - if (mesh->hasComposite()) - { - continue; // Mesh exists and has a composite texture. - } - // Fail - return false; - } - } - return true; -} - -bool LLVOAvatar::hasGray() const -{ - return !getIsCloud() && !isFullyTextured(); -} - -S32 LLVOAvatar::getRezzedStatus() const -{ - if (getIsCloud()) return 0; - bool textured = isFullyTextured(); - bool all_baked_loaded = allBakedTexturesCompletelyDownloaded(); - if (textured && all_baked_loaded && getAttachmentCount() == mSimAttachments.size()) return 4; - if (textured && all_baked_loaded) return 3; - if (textured) return 2; - llassert(hasGray()); - return 1; // gray -} - -void LLVOAvatar::deleteLayerSetCaches(bool clearAll) -{ - for (U32 i = 0; i < mBakedTextureDatas.size(); i++) - { - if (mBakedTextureDatas[i].mTexLayerSet) - { - // ! BACKWARDS COMPATIBILITY ! - // Can be removed after hair baking is mandatory on the grid - if ((i != BAKED_HAIR || isSelf()) && !clearAll) - { - mBakedTextureDatas[i].mTexLayerSet->deleteCaches(); - } - } - if (mBakedTextureDatas[i].mMaskTexName) - { - LLImageGL::deleteTextures(1, (GLuint*)&(mBakedTextureDatas[i].mMaskTexName)); - mBakedTextureDatas[i].mMaskTexName = 0 ; - } - } -} - -// static -bool LLVOAvatar::areAllNearbyInstancesBaked(S32& grey_avatars) -{ - bool res = true; - grey_avatars = 0; - for (std::vector::iterator iter = LLCharacter::sInstances.begin(); - iter != LLCharacter::sInstances.end(); ++iter) - { - LLVOAvatar* inst = (LLVOAvatar*) *iter; - if( inst->isDead() ) - { - continue; - } - else if( !inst->isFullyBaked() ) - { - res = false; - if (inst->mHasGrey) - { - ++grey_avatars; - } - } - } - return res; -} - -// static -void LLVOAvatar::getNearbyRezzedStats(std::vector& counts, F32& avg_cloud_time, S32& cloud_avatars) -{ - counts.clear(); - counts.resize(5); - avg_cloud_time = 0; - cloud_avatars = 0; - S32 count_avg = 0; - for (std::vector::iterator iter = LLCharacter::sInstances.begin(); - iter != LLCharacter::sInstances.end(); ++iter) - { - LLVOAvatar* inst = (LLVOAvatar*) *iter; - if (inst) - { - S32 rez_status = inst->getRezzedStatus(); - counts[rez_status]++; - F32 time = inst->getFirstDecloudTime(); - if (time >= 0) - { - avg_cloud_time+=time; - count_avg++; - } - if (!inst->isFullyLoaded() || time < 0) - { - // still renders as cloud - cloud_avatars++; - } - } - } - if (count_avg > 0) - { - avg_cloud_time /= count_avg; - } -} - -// static -std::string LLVOAvatar::rezStatusToString(S32 rez_status) -{ - if (rez_status==0) return "cloud"; - if (rez_status==1) return "gray"; - if (rez_status==2) return "downloading baked"; - if (rez_status==3) return "loading attachments"; - if (rez_status==4) return "full"; - return "unknown"; -} - -// static -void LLVOAvatar::dumpBakedStatus() -{ - LLVector3d camera_pos_global = gAgentCamera.getCameraPositionGlobal(); - - for (std::vector::iterator iter = LLCharacter::sInstances.begin(); - iter != LLCharacter::sInstances.end(); ++iter) - { - LLVOAvatar* inst = (LLVOAvatar*) *iter; - LL_INFOS() << "Avatar "; - - LLNameValue* firstname = inst->getNVPair("FirstName"); - LLNameValue* lastname = inst->getNVPair("LastName"); - - if( firstname ) - { - LL_CONT << firstname->getString(); - } - if( lastname ) - { - LL_CONT << " " << lastname->getString(); - } - - LL_CONT << " " << inst->mID; - - if( inst->isDead() ) - { - LL_CONT << " DEAD ("<< inst->getNumRefs() << " refs)"; - } - - if( inst->isSelf() ) - { - LL_CONT << " (self)"; - } - - - F64 dist_to_camera = (inst->getPositionGlobal() - camera_pos_global).length(); - LL_CONT << " " << dist_to_camera << "m "; - - LL_CONT << " " << inst->mPixelArea << " pixels"; - - if( inst->isVisible() ) - { - LL_CONT << " (visible)"; - } - else - { - LL_CONT << " (not visible)"; - } - - if( inst->isFullyBaked() ) - { - LL_CONT << " Baked"; - } - else - { - LL_CONT << " Unbaked ("; - - for (LLAvatarAppearanceDictionary::BakedTextures::const_iterator iter = LLAvatarAppearance::getDictionary()->getBakedTextures().begin(); - iter != LLAvatarAppearance::getDictionary()->getBakedTextures().end(); - ++iter) - { - const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = iter->second; - const ETextureIndex index = baked_dict->mTextureIndex; - if (!inst->isTextureDefined(index)) - { - LL_CONT << " " << (LLAvatarAppearance::getDictionary()->getTexture(index) ? LLAvatarAppearance::getDictionary()->getTexture(index)->mName : ""); - } - } - LL_CONT << " ) " << inst->getUnbakedPixelAreaRank(); - if( inst->isCulled() ) - { - LL_CONT << " culled"; - } - } - LL_CONT << LL_ENDL; - } -} - -//static -void LLVOAvatar::restoreGL() -{ - if (!isAgentAvatarValid()) return; - - gAgentAvatarp->setCompositeUpdatesEnabled(true); - for (U32 i = 0; i < gAgentAvatarp->mBakedTextureDatas.size(); i++) - { - gAgentAvatarp->invalidateComposite(gAgentAvatarp->getTexLayerSet(i)); - } - gAgentAvatarp->updateMeshTextures(); -} - -//static -void LLVOAvatar::destroyGL() -{ - deleteCachedImages(); - - resetImpostors(); -} - -//static -void LLVOAvatar::resetImpostors() -{ - for (std::vector::iterator iter = LLCharacter::sInstances.begin(); - iter != LLCharacter::sInstances.end(); ++iter) - { - LLVOAvatar* avatar = (LLVOAvatar*) *iter; - avatar->mImpostor.release(); - avatar->mNeedsImpostorUpdate = true; - avatar->mLastImpostorUpdateReason = 1; - } -} - -// static -void LLVOAvatar::deleteCachedImages(bool clearAll) -{ - if (LLViewerTexLayerSet::sHasCaches) - { - for (std::vector::iterator iter = LLCharacter::sInstances.begin(); - iter != LLCharacter::sInstances.end(); ++iter) - { - LLVOAvatar* inst = (LLVOAvatar*) *iter; - inst->deleteLayerSetCaches(clearAll); - } - LLViewerTexLayerSet::sHasCaches = false; - } - LLVOAvatarSelf::deleteScratchTextures(); - LLTexLayerStaticImageList::getInstance()->deleteCachedImages(); -} - - -//------------------------------------------------------------------------ -// static -// LLVOAvatar::initClass() -//------------------------------------------------------------------------ -void LLVOAvatar::initClass() -{ - gAnimLibrary.animStateSetString(ANIM_AGENT_BODY_NOISE,"body_noise"); - gAnimLibrary.animStateSetString(ANIM_AGENT_BREATHE_ROT,"breathe_rot"); - gAnimLibrary.animStateSetString(ANIM_AGENT_PHYSICS_MOTION,"physics_motion"); - gAnimLibrary.animStateSetString(ANIM_AGENT_EDITING,"editing"); - gAnimLibrary.animStateSetString(ANIM_AGENT_EYE,"eye"); - gAnimLibrary.animStateSetString(ANIM_AGENT_FLY_ADJUST,"fly_adjust"); - gAnimLibrary.animStateSetString(ANIM_AGENT_HAND_MOTION,"hand_motion"); - gAnimLibrary.animStateSetString(ANIM_AGENT_HEAD_ROT,"head_rot"); - gAnimLibrary.animStateSetString(ANIM_AGENT_PELVIS_FIX,"pelvis_fix"); - gAnimLibrary.animStateSetString(ANIM_AGENT_TARGET,"target"); - gAnimLibrary.animStateSetString(ANIM_AGENT_WALK_ADJUST,"walk_adjust"); - - // Where should this be set initially? - LLJoint::setDebugJointNames(gSavedSettings.getString("DebugAvatarJoints")); - - LLControlAvatar::sRegionChangedSlot = gAgent.addRegionChangedCallback(&LLControlAvatar::onRegionChanged); - - sCloudTexture = LLViewerTextureManager::getFetchedTextureFromFile("cloud-particle.j2c"); -} - - -void LLVOAvatar::cleanupClass() -{ -} - -// virtual -void LLVOAvatar::initInstance() -{ - //------------------------------------------------------------------------- - // register motions - //------------------------------------------------------------------------- - if (LLCharacter::sInstances.size() == 1) - { - registerMotion( ANIM_AGENT_DO_NOT_DISTURB, LLNullMotion::create ); - registerMotion( ANIM_AGENT_CROUCH, LLKeyframeStandMotion::create ); - registerMotion( ANIM_AGENT_CROUCHWALK, LLKeyframeWalkMotion::create ); - registerMotion( ANIM_AGENT_EXPRESS_AFRAID, LLEmote::create ); - registerMotion( ANIM_AGENT_EXPRESS_ANGER, LLEmote::create ); - registerMotion( ANIM_AGENT_EXPRESS_BORED, LLEmote::create ); - registerMotion( ANIM_AGENT_EXPRESS_CRY, LLEmote::create ); - registerMotion( ANIM_AGENT_EXPRESS_DISDAIN, LLEmote::create ); - registerMotion( ANIM_AGENT_EXPRESS_EMBARRASSED, LLEmote::create ); - registerMotion( ANIM_AGENT_EXPRESS_FROWN, LLEmote::create ); - registerMotion( ANIM_AGENT_EXPRESS_KISS, LLEmote::create ); - registerMotion( ANIM_AGENT_EXPRESS_LAUGH, LLEmote::create ); - registerMotion( ANIM_AGENT_EXPRESS_OPEN_MOUTH, LLEmote::create ); - registerMotion( ANIM_AGENT_EXPRESS_REPULSED, LLEmote::create ); - registerMotion( ANIM_AGENT_EXPRESS_SAD, LLEmote::create ); - registerMotion( ANIM_AGENT_EXPRESS_SHRUG, LLEmote::create ); - registerMotion( ANIM_AGENT_EXPRESS_SMILE, LLEmote::create ); - registerMotion( ANIM_AGENT_EXPRESS_SURPRISE, LLEmote::create ); - registerMotion( ANIM_AGENT_EXPRESS_TONGUE_OUT, LLEmote::create ); - registerMotion( ANIM_AGENT_EXPRESS_TOOTHSMILE, LLEmote::create ); - registerMotion( ANIM_AGENT_EXPRESS_WINK, LLEmote::create ); - registerMotion( ANIM_AGENT_EXPRESS_WORRY, LLEmote::create ); - registerMotion( ANIM_AGENT_FEMALE_RUN_NEW, LLKeyframeWalkMotion::create ); - registerMotion( ANIM_AGENT_FEMALE_WALK, LLKeyframeWalkMotion::create ); - registerMotion( ANIM_AGENT_FEMALE_WALK_NEW, LLKeyframeWalkMotion::create ); - registerMotion( ANIM_AGENT_RUN, LLKeyframeWalkMotion::create ); - registerMotion( ANIM_AGENT_RUN_NEW, LLKeyframeWalkMotion::create ); - registerMotion( ANIM_AGENT_STAND, LLKeyframeStandMotion::create ); - registerMotion( ANIM_AGENT_STAND_1, LLKeyframeStandMotion::create ); - registerMotion( ANIM_AGENT_STAND_2, LLKeyframeStandMotion::create ); - registerMotion( ANIM_AGENT_STAND_3, LLKeyframeStandMotion::create ); - registerMotion( ANIM_AGENT_STAND_4, LLKeyframeStandMotion::create ); - registerMotion( ANIM_AGENT_STANDUP, LLKeyframeFallMotion::create ); - registerMotion( ANIM_AGENT_TURNLEFT, LLKeyframeWalkMotion::create ); - registerMotion( ANIM_AGENT_TURNRIGHT, LLKeyframeWalkMotion::create ); - registerMotion( ANIM_AGENT_WALK, LLKeyframeWalkMotion::create ); - registerMotion( ANIM_AGENT_WALK_NEW, LLKeyframeWalkMotion::create ); - - // motions without a start/stop bit - registerMotion( ANIM_AGENT_BODY_NOISE, LLBodyNoiseMotion::create ); - registerMotion( ANIM_AGENT_BREATHE_ROT, LLBreatheMotionRot::create ); - registerMotion( ANIM_AGENT_PHYSICS_MOTION, LLPhysicsMotionController::create ); - registerMotion( ANIM_AGENT_EDITING, LLEditingMotion::create ); - registerMotion( ANIM_AGENT_EYE, LLEyeMotion::create ); - registerMotion( ANIM_AGENT_FEMALE_WALK, LLKeyframeWalkMotion::create ); - registerMotion( ANIM_AGENT_FLY_ADJUST, LLFlyAdjustMotion::create ); - registerMotion( ANIM_AGENT_HAND_MOTION, LLHandMotion::create ); - registerMotion( ANIM_AGENT_HEAD_ROT, LLHeadRotMotion::create ); - registerMotion( ANIM_AGENT_PELVIS_FIX, LLPelvisFixMotion::create ); - registerMotion( ANIM_AGENT_SIT_FEMALE, LLKeyframeMotion::create ); - registerMotion( ANIM_AGENT_TARGET, LLTargetingMotion::create ); - registerMotion( ANIM_AGENT_WALK_ADJUST, LLWalkAdjustMotion::create ); - } - - LLAvatarAppearance::initInstance(); - - // preload specific motions here - createMotion( ANIM_AGENT_CUSTOMIZE); - createMotion( ANIM_AGENT_CUSTOMIZE_DONE); - - //VTPause(); // VTune - - mVoiceVisualizer->setVoiceEnabled( LLVoiceClient::getInstance()->getVoiceEnabled( mID ) ); - - mInitFlags |= 1<<1; -} - -// virtual -LLAvatarJoint* LLVOAvatar::createAvatarJoint() -{ - return new LLViewerJoint(); -} - -// virtual -LLAvatarJoint* LLVOAvatar::createAvatarJoint(S32 joint_num) -{ - return new LLViewerJoint(joint_num); -} - -// virtual -LLAvatarJointMesh* LLVOAvatar::createAvatarJointMesh() -{ - return new LLViewerJointMesh(); -} - -// virtual -LLTexLayerSet* LLVOAvatar::createTexLayerSet() -{ - return new LLViewerTexLayerSet(this); -} - -const LLVector3 LLVOAvatar::getRenderPosition() const -{ - - if (mDrawable.isNull() || mDrawable->getGeneration() < 0) - { - return getPositionAgent(); - } - else if (isRoot()) - { - F32 fixup; - if ( hasPelvisFixup( fixup) ) - { - //Apply a pelvis fixup (as defined by the avs skin) - LLVector3 pos = mDrawable->getPositionAgent(); - pos[VZ] += fixup; - return pos; - } - else - { - return mDrawable->getPositionAgent(); - } - } - else - { - return getPosition() * mDrawable->getParent()->getRenderMatrix(); - } -} - -void LLVOAvatar::updateDrawable(bool force_damped) -{ - clearChanged(SHIFTED); -} - -void LLVOAvatar::onShift(const LLVector4a& shift_vector) -{ - const LLVector3& shift = reinterpret_cast(shift_vector); - mLastAnimExtents[0] += shift; - mLastAnimExtents[1] += shift; -} - -void LLVOAvatar::updateSpatialExtents(LLVector4a& newMin, LLVector4a &newMax) -{ - if (mDrawable.isNull()) - { - return; - } - - if (mNeedsExtentUpdate) - { - calculateSpatialExtents(newMin,newMax); - mLastAnimExtents[0].set(newMin.getF32ptr()); - mLastAnimExtents[1].set(newMax.getF32ptr()); - mLastAnimBasePos = mPelvisp->getWorldPosition(); - mNeedsExtentUpdate = false; - } - else - { - LLVector3 new_base_pos = mPelvisp->getWorldPosition(); - LLVector3 shift = new_base_pos-mLastAnimBasePos; - mLastAnimExtents[0] += shift; - mLastAnimExtents[1] += shift; - mLastAnimBasePos = new_base_pos; - - } - - if (isImpostor() && !needsImpostorUpdate()) - { - LLVector3 delta = getRenderPosition() - - ((LLVector3(mDrawable->getPositionGroup().getF32ptr())-mImpostorOffset)); - - newMin.load3( (mLastAnimExtents[0] + delta).mV); - newMax.load3( (mLastAnimExtents[1] + delta).mV); - } - else - { - newMin.load3(mLastAnimExtents[0].mV); - newMax.load3(mLastAnimExtents[1].mV); - LLVector4a pos_group; - pos_group.setAdd(newMin,newMax); - pos_group.mul(0.5f); - mImpostorOffset = LLVector3(pos_group.getF32ptr())-getRenderPosition(); - mDrawable->setPositionGroup(pos_group); - } -} - - -void LLVOAvatar::calculateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - - const S32 BOX_DETAIL_DEFAULT = 3; - S32 box_detail = BOX_DETAIL_DEFAULT; - if (getOverallAppearance() != AOA_NORMAL) - { - if (isControlAvatar()) - { - // Animated objects don't show system avatar but do need to include rigged meshes in their bounding box. - box_detail = 3; - } - else - { - // Jellydolled avatars ignore attachments, etc, use only system avatar. - box_detail = 1; - } - } - - // FIXME the update_min_max function used below assumes there is a - // known starting point, but in general there isn't. Ideally the - // box update logic should be modified to handle the no-point-yet - // case. For most models, starting with the pelvis is safe though. - LLVector3 zero_pos; - LLVector4a pos; - if (dist_vec(zero_pos, mPelvisp->getWorldPosition())<0.001) - { - // Don't use pelvis until av initialized - pos.load3(getRenderPosition().mV); - } - else - { - pos.load3(mPelvisp->getWorldPosition().mV); - } - newMin = pos; - newMax = pos; - - if (box_detail>=1 && !isControlAvatar()) - { - //stretch bounding box by joint positions. Doing this for - //control avs, where the polymeshes aren't maintained or - //displayed, can give inaccurate boxes due to joints stuck at (0,0,0). - for (polymesh_map_t::iterator i = mPolyMeshes.begin(); i != mPolyMeshes.end(); ++i) - { - LLPolyMesh* mesh = i->second; - for (S32 joint_num = 0; joint_num < mesh->mJointRenderData.size(); joint_num++) - { - LLVector4a trans; - trans.load3( mesh->mJointRenderData[joint_num]->mWorldMatrix->getTranslation().mV); - update_min_max(newMin, newMax, trans); - } - } - } - - // Pad bounding box for starting joint, plus polymesh if - // applicable. Subsequent calcs should be accurate enough to not - // need padding. - LLVector4a padding(0.25); - newMin.sub(padding); - newMax.add(padding); - - - //stretch bounding box by static attachments - if (box_detail >= 2) - { - float max_attachment_span = get_default_max_prim_scale() * 5.0f; - - for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - - if (attachment->getValid()) - { - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - // Don't we need to look at children of attached_object as well? - const LLViewerObject* attached_object = attachment_iter->get(); - if (attached_object && !attached_object->isHUDAttachment()) - { - const LLVOVolume *vol = dynamic_cast(attached_object); - if (vol && vol->isAnimatedObject()) - { - // Animated objects already have a bounding box in their control av, use that. - // Could lag by a frame if there's no guarantee on order of processing for avatars. - LLControlAvatar *cav = vol->getControlAvatar(); - if (cav) - { - LLVector4a cav_min; - cav_min.load3(cav->mLastAnimExtents[0].mV); - LLVector4a cav_max; - cav_max.load3(cav->mLastAnimExtents[1].mV); - update_min_max(newMin,newMax,cav_min); - update_min_max(newMin,newMax,cav_max); - continue; - } - } - if (vol && vol->isRiggedMeshFast()) - { - continue; - } - LLDrawable* drawable = attached_object->mDrawable; - if (drawable && !drawable->isState(LLDrawable::RIGGED | LLDrawable::RIGGED_CHILD)) // <-- don't extend bounding box if any rigged objects are present - { - LLSpatialBridge* bridge = drawable->getSpatialBridge(); - if (bridge) - { - const LLVector4a* ext = bridge->getSpatialExtents(); - LLVector4a distance; - distance.setSub(ext[1], ext[0]); - LLVector4a max_span(max_attachment_span); - - S32 lt = distance.lessThan(max_span).getGatheredBits() & 0x7; - - // Only add the prim to spatial extents calculations if it isn't a megaprim. - // max_attachment_span calculated at the start of the function - // (currently 5 times our max prim size) - if (lt == 0x7) - { - update_min_max(newMin,newMax,ext[0]); - update_min_max(newMin,newMax,ext[1]); - } - } - } - } - } - } - } - } - - // Stretch bounding box by rigged mesh joint boxes - if (box_detail>=3) - { - updateRiggingInfo(); - for (S32 joint_num = 0; joint_num < LL_CHARACTER_MAX_ANIMATED_JOINTS; joint_num++) - { - LLJoint *joint = getJoint(joint_num); - LLJointRiggingInfo *rig_info = NULL; - if (joint_num < mJointRiggingInfoTab.size()) - { - rig_info = &mJointRiggingInfoTab[joint_num]; - } - - if (joint && rig_info && rig_info->isRiggedTo()) - { - LLViewerJointAttachment *as_joint_attach = dynamic_cast(joint); - if (as_joint_attach && as_joint_attach->getIsHUDAttachment()) - { - // Ignore bounding box of HUD joints - continue; - } - LLMatrix4a mat; - LLVector4a new_extents[2]; - mat.loadu(joint->getWorldMatrix()); - matMulBoundBox(mat, rig_info->getRiggedExtents(), new_extents); - update_min_max(newMin, newMax, new_extents[0]); - update_min_max(newMin, newMax, new_extents[1]); - //if (isSelf()) - //{ - // LL_INFOS() << joint->getName() << " extents " << new_extents[0] << "," << new_extents[1] << LL_ENDL; - // LL_INFOS() << joint->getName() << " av box is " << newMin << "," << newMax << LL_ENDL; - //} - } - } - } - - // Update pixel area - LLVector4a center, size; - center.setAdd(newMin, newMax); - center.mul(0.5f); - - size.setSub(newMax,newMin); - size.mul(0.5f); - - mPixelArea = LLPipeline::calcPixelArea(center, size, *LLViewerCamera::getInstance()); -} - -void render_sphere_and_line(const LLVector3& begin_pos, const LLVector3& end_pos, F32 sphere_scale, const LLVector3& occ_color, const LLVector3& visible_color) -{ - // Unoccluded bone portions - LLGLDepthTest normal_depth(GL_TRUE); - - // Draw line segment for unoccluded joint - gGL.diffuseColor3f(visible_color[0], visible_color[1], visible_color[2]); - - gGL.begin(LLRender::LINES); - gGL.vertex3fv(begin_pos.mV); - gGL.vertex3fv(end_pos.mV); - gGL.end(); - - - // Draw sphere representing joint pos - gGL.pushMatrix(); - gGL.scalef(sphere_scale, sphere_scale, sphere_scale); - gSphere.renderGGL(); - gGL.popMatrix(); - - LLGLDepthTest depth_under(GL_TRUE, GL_FALSE, GL_GREATER); - - // Occluded bone portions - gGL.diffuseColor3f(occ_color[0], occ_color[1], occ_color[2]); - - gGL.begin(LLRender::LINES); - gGL.vertex3fv(begin_pos.mV); - gGL.vertex3fv(end_pos.mV); - gGL.end(); - - // Draw sphere representing joint pos - gGL.pushMatrix(); - gGL.scalef(sphere_scale, sphere_scale, sphere_scale); - gSphere.renderGGL(); - gGL.popMatrix(); -} - -//----------------------------------------------------------------------------- -// renderCollisionVolumes() -//----------------------------------------------------------------------------- -void LLVOAvatar::renderCollisionVolumes() -{ - std::ostringstream ostr; - - for (S32 i = 0; i < mNumCollisionVolumes; i++) - { - ostr << mCollisionVolumes[i].getName() << ", "; - - LLAvatarJointCollisionVolume& collision_volume = mCollisionVolumes[i]; - - collision_volume.updateWorldMatrix(); - - gGL.pushMatrix(); - gGL.multMatrix( &collision_volume.getXform()->getWorldMatrix().mMatrix[0][0] ); - - LLVector3 begin_pos(0,0,0); - LLVector3 end_pos(collision_volume.getEnd()); - static F32 sphere_scale = 1.0f; - static F32 center_dot_scale = 0.05f; - - static LLVector3 BLUE(0.0f, 0.0f, 1.0f); - static LLVector3 PASTEL_BLUE(0.5f, 0.5f, 1.0f); - static LLVector3 RED(1.0f, 0.0f, 0.0f); - static LLVector3 PASTEL_RED(1.0f, 0.5f, 0.5f); - static LLVector3 WHITE(1.0f, 1.0f, 1.0f); - - - LLVector3 cv_color_occluded; - LLVector3 cv_color_visible; - LLVector3 dot_color_occluded(WHITE); - LLVector3 dot_color_visible(WHITE); - if (isControlAvatar()) - { - cv_color_occluded = RED; - cv_color_visible = PASTEL_RED; - } - else - { - cv_color_occluded = BLUE; - cv_color_visible = PASTEL_BLUE; - } - render_sphere_and_line(begin_pos, end_pos, sphere_scale, cv_color_occluded, cv_color_visible); - render_sphere_and_line(begin_pos, end_pos, center_dot_scale, dot_color_occluded, dot_color_visible); - - gGL.popMatrix(); - } - - - if (mNameText.notNull()) - { - LLVector4a unused; - - mNameText->lineSegmentIntersect(unused, unused, unused, true); - } -} - -void LLVOAvatar::renderBones(const std::string &selected_joint) -{ - LLGLEnable blend(GL_BLEND); - - avatar_joint_list_t::iterator iter = mSkeleton.begin(); - avatar_joint_list_t::iterator end = mSkeleton.end(); - - // For selected joints - static LLVector3 SELECTED_COLOR_OCCLUDED(1.0f, 1.0f, 0.0f); - static LLVector3 SELECTED_COLOR_VISIBLE(0.5f, 0.5f, 0.5f); - // For bones with position overrides defined - static LLVector3 OVERRIDE_COLOR_OCCLUDED(1.0f, 0.0f, 0.0f); - static LLVector3 OVERRIDE_COLOR_VISIBLE(0.5f, 0.5f, 0.5f); - // For bones which are rigged to by at least one attachment - static LLVector3 RIGGED_COLOR_OCCLUDED(0.0f, 1.0f, 1.0f); - static LLVector3 RIGGED_COLOR_VISIBLE(0.5f, 0.5f, 0.5f); - // For bones not otherwise colored - static LLVector3 OTHER_COLOR_OCCLUDED(0.0f, 1.0f, 0.0f); - static LLVector3 OTHER_COLOR_VISIBLE(0.5f, 0.5f, 0.5f); - - static F32 SPHERE_SCALEF = 0.001f; - - for (; iter != end; ++iter) - { - LLJoint* jointp = *iter; - if (!jointp) - { - continue; - } - - jointp->updateWorldMatrix(); - - LLVector3 occ_color, visible_color; - - LLVector3 pos; - LLUUID mesh_id; - F32 sphere_scale = SPHERE_SCALEF; - - // We are in render, so it is preferable to implement selection - // in a different way, but since this is for debug/preview, this - // is low priority - if (jointp->getName() == selected_joint) - { - sphere_scale *= 16; - occ_color = SELECTED_COLOR_OCCLUDED; - visible_color = SELECTED_COLOR_VISIBLE; - } - else if (jointp->hasAttachmentPosOverride(pos,mesh_id)) - { - occ_color = OVERRIDE_COLOR_OCCLUDED; - visible_color = OVERRIDE_COLOR_VISIBLE; - } - else - { - if (jointIsRiggedTo(jointp)) - { - occ_color = RIGGED_COLOR_OCCLUDED; - visible_color = RIGGED_COLOR_VISIBLE; - } - else - { - occ_color = OTHER_COLOR_OCCLUDED; - visible_color = OTHER_COLOR_VISIBLE; - } - } - LLVector3 begin_pos(0,0,0); - LLVector3 end_pos(jointp->getEnd()); - - - gGL.pushMatrix(); - gGL.multMatrix( &jointp->getXform()->getWorldMatrix().mMatrix[0][0] ); - - render_sphere_and_line(begin_pos, end_pos, sphere_scale, occ_color, visible_color); - - gGL.popMatrix(); - } -} - - -void LLVOAvatar::renderJoints() -{ - std::ostringstream ostr; - std::ostringstream nullstr; - - for (joint_map_t::iterator iter = mJointMap.begin(); iter != mJointMap.end(); ++iter) - { - LLJoint* jointp = iter->second; - if (!jointp) - { - nullstr << iter->first << " is NULL" << std::endl; - continue; - } - - ostr << jointp->getName() << ", "; - - jointp->updateWorldMatrix(); - - gGL.pushMatrix(); - gGL.multMatrix( &jointp->getXform()->getWorldMatrix().mMatrix[0][0] ); - - gGL.diffuseColor3f( 1.f, 0.f, 1.f ); - - gGL.begin(LLRender::LINES); - - LLVector3 v[] = - { - LLVector3(1,0,0), - LLVector3(-1,0,0), - LLVector3(0,1,0), - LLVector3(0,-1,0), - - LLVector3(0,0,-1), - LLVector3(0,0,1), - }; - - //sides - gGL.vertex3fv(v[0].mV); - gGL.vertex3fv(v[2].mV); - - gGL.vertex3fv(v[0].mV); - gGL.vertex3fv(v[3].mV); - - gGL.vertex3fv(v[1].mV); - gGL.vertex3fv(v[2].mV); - - gGL.vertex3fv(v[1].mV); - gGL.vertex3fv(v[3].mV); - - - //top - gGL.vertex3fv(v[0].mV); - gGL.vertex3fv(v[4].mV); - - gGL.vertex3fv(v[1].mV); - gGL.vertex3fv(v[4].mV); - - gGL.vertex3fv(v[2].mV); - gGL.vertex3fv(v[4].mV); - - gGL.vertex3fv(v[3].mV); - gGL.vertex3fv(v[4].mV); - - - //bottom - gGL.vertex3fv(v[0].mV); - gGL.vertex3fv(v[5].mV); - - gGL.vertex3fv(v[1].mV); - gGL.vertex3fv(v[5].mV); - - gGL.vertex3fv(v[2].mV); - gGL.vertex3fv(v[5].mV); - - gGL.vertex3fv(v[3].mV); - gGL.vertex3fv(v[5].mV); - - gGL.end(); - - gGL.popMatrix(); - } - - mDebugText.clear(); - addDebugText(ostr.str()); - addDebugText(nullstr.str()); -} - -bool LLVOAvatar::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, - S32 face, - bool pick_transparent, - bool pick_rigged, - bool pick_unselectable, - S32* face_hit, - LLVector4a* intersection, - LLVector2* tex_coord, - LLVector4a* normal, - LLVector4a* tangent) -{ - if ((isSelf() && !gAgent.needsRenderAvatar()) || !LLPipeline::sPickAvatar) - { - return false; - } - - if (isControlAvatar()) - { - return false; - } - - if (lineSegmentBoundingBox(start, end)) - { - for (S32 i = 0; i < mNumCollisionVolumes; ++i) - { - mCollisionVolumes[i].updateWorldMatrix(); - - glh::matrix4f mat((F32*) mCollisionVolumes[i].getXform()->getWorldMatrix().mMatrix); - glh::matrix4f inverse = mat.inverse(); - glh::matrix4f norm_mat = inverse.transpose(); - - glh::vec3f p1(start.getF32ptr()); - glh::vec3f p2(end.getF32ptr()); - - inverse.mult_matrix_vec(p1); - inverse.mult_matrix_vec(p2); - - LLVector3 position; - LLVector3 norm; - - if (linesegment_sphere(LLVector3(p1.v), LLVector3(p2.v), LLVector3(0,0,0), 1.f, position, norm)) - { - glh::vec3f res_pos(position.mV); - mat.mult_matrix_vec(res_pos); - - norm.normalize(); - glh::vec3f res_norm(norm.mV); - norm_mat.mult_matrix_dir(res_norm); - - if (intersection) - { - intersection->load3(res_pos.v); - } - - if (normal) - { - normal->load3(res_norm.v); - } - - return true; - } - } - - if (isSelf()) - { - for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - LLViewerObject* attached_object = attachment_iter->get(); - - if (attached_object && !attached_object->isDead() && attachment->getValid()) - { - LLDrawable* drawable = attached_object->mDrawable; - if (drawable->isState(LLDrawable::RIGGED)) - { //regenerate octree for rigged attachment - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_RIGGED); - } - } - } - } - } - } - - - - LLVector4a position; - if (mNameText.notNull() && mNameText->lineSegmentIntersect(start, end, position)) - { - if (intersection) - { - *intersection = position; - } - - return true; - } - - return false; -} - -// virtual -LLViewerObject* LLVOAvatar::lineSegmentIntersectRiggedAttachments(const LLVector4a& start, const LLVector4a& end, - S32 face, - bool pick_transparent, - bool pick_rigged, - bool pick_unselectable, - S32* face_hit, - LLVector4a* intersection, - LLVector2* tex_coord, - LLVector4a* normal, - LLVector4a* tangent) -{ - if (isSelf() && !gAgent.needsRenderAvatar()) - { - return NULL; - } - - LLViewerObject* hit = NULL; - - if (lineSegmentBoundingBox(start, end)) - { - LLVector4a local_end = end; - LLVector4a local_intersection; - - for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - LLViewerObject* attached_object = attachment_iter->get(); - - if (attached_object->lineSegmentIntersect(start, local_end, face, pick_transparent, pick_rigged, pick_unselectable, face_hit, &local_intersection, tex_coord, normal, tangent)) - { - local_end = local_intersection; - if (intersection) - { - *intersection = local_intersection; - } - - hit = attached_object; - } - } - } - } - - return hit; -} - - -LLVOAvatar* LLVOAvatar::asAvatar() -{ - return this; -} - -//----------------------------------------------------------------------------- -// LLVOAvatar::startDefaultMotions() -//----------------------------------------------------------------------------- -void LLVOAvatar::startDefaultMotions() -{ - //------------------------------------------------------------------------- - // start default motions - //------------------------------------------------------------------------- - startMotion( ANIM_AGENT_HEAD_ROT ); - startMotion( ANIM_AGENT_EYE ); - startMotion( ANIM_AGENT_BODY_NOISE ); - startMotion( ANIM_AGENT_BREATHE_ROT ); - startMotion( ANIM_AGENT_PHYSICS_MOTION ); - startMotion( ANIM_AGENT_HAND_MOTION ); - startMotion( ANIM_AGENT_PELVIS_FIX ); - - //------------------------------------------------------------------------- - // restart any currently active motions - //------------------------------------------------------------------------- - processAnimationStateChanges(); -} - -//----------------------------------------------------------------------------- -// LLVOAvatar::buildCharacter() -// Deferred initialization and rebuild of the avatar. -//----------------------------------------------------------------------------- -// virtual -void LLVOAvatar::buildCharacter() -{ - LLAvatarAppearance::buildCharacter(); - - // Not done building yet; more to do. - mIsBuilt = false; - - //------------------------------------------------------------------------- - // set head offset from pelvis - //------------------------------------------------------------------------- - updateHeadOffset(); - - //------------------------------------------------------------------------- - // initialize lip sync morph pointers - //------------------------------------------------------------------------- - mOohMorph = getVisualParam( "Lipsync_Ooh" ); - mAahMorph = getVisualParam( "Lipsync_Aah" ); - - // If we don't have the Ooh morph, use the Kiss morph - if (!mOohMorph) - { - LL_WARNS() << "Missing 'Ooh' morph for lipsync, using fallback." << LL_ENDL; - mOohMorph = getVisualParam( "Express_Kiss" ); - } - - // If we don't have the Aah morph, use the Open Mouth morph - if (!mAahMorph) - { - LL_WARNS() << "Missing 'Aah' morph for lipsync, using fallback." << LL_ENDL; - mAahMorph = getVisualParam( "Express_Open_Mouth" ); - } - - // Currently disabled for control avatars (animated objects), enabled for all others. - if (mEnableDefaultMotions) - { - startDefaultMotions(); - } - - //------------------------------------------------------------------------- - // restart any currently active motions - //------------------------------------------------------------------------- - processAnimationStateChanges(); - - mIsBuilt = true; - stop_glerror(); - - mMeshValid = true; -} - -//----------------------------------------------------------------------------- -// resetVisualParams() -//----------------------------------------------------------------------------- -void LLVOAvatar::resetVisualParams() -{ - // Skeletal params - { - LLAvatarXmlInfo::skeletal_distortion_info_list_t::iterator iter; - for (iter = sAvatarXmlInfo->mSkeletalDistortionInfoList.begin(); - iter != sAvatarXmlInfo->mSkeletalDistortionInfoList.end(); - ++iter) - { - LLPolySkeletalDistortionInfo *info = (LLPolySkeletalDistortionInfo*)*iter; - LLPolySkeletalDistortion *param = dynamic_cast(getVisualParam(info->getID())); - *param = LLPolySkeletalDistortion(this); - llassert(param); - if (!param->setInfo(info)) - { - llassert(false); - } - } - } - - // Driver parameters - for (LLAvatarXmlInfo::driver_info_list_t::iterator iter = sAvatarXmlInfo->mDriverInfoList.begin(); - iter != sAvatarXmlInfo->mDriverInfoList.end(); - ++iter) - { - LLDriverParamInfo *info = *iter; - LLDriverParam *param = dynamic_cast(getVisualParam(info->getID())); - LLDriverParam::entry_list_t driven_list = param->getDrivenList(); - *param = LLDriverParam(this); - llassert(param); - if (!param->setInfo(info)) - { - llassert(false); - } - param->setDrivenList(driven_list); - } -} - -void LLVOAvatar::applyDefaultParams() -{ - // These are params from avs with newly created copies of shape, - // skin, hair, eyes, plus gender set as noted. Run arche_tool.py - // to get params from some other xml appearance dump. - std::map male_params = { - {1,33}, {2,61}, {4,85}, {5,23}, {6,58}, {7,127}, {8,63}, {10,85}, {11,63}, {12,42}, {13,0}, {14,85}, {15,63}, {16,36}, {17,85}, {18,95}, {19,153}, {20,63}, {21,34}, {22,0}, {23,63}, {24,109}, {25,88}, {27,132}, {31,63}, {33,136}, {34,81}, {35,85}, {36,103}, {37,136}, {38,127}, {80,255}, {93,203}, {98,0}, {99,0}, {105,127}, {108,0}, {110,0}, {111,127}, {112,0}, {113,0}, {114,127}, {115,0}, {116,0}, {117,0}, {119,127}, {130,114}, {131,127}, {132,99}, {133,63}, {134,127}, {135,140}, {136,127}, {137,127}, {140,0}, {141,0}, {142,0}, {143,191}, {150,0}, {155,104}, {157,0}, {162,0}, {163,0}, {165,0}, {166,0}, {167,0}, {168,0}, {169,0}, {177,0}, {181,145}, {182,216}, {183,133}, {184,0}, {185,127}, {192,0}, {193,127}, {196,170}, {198,0}, {503,0}, {505,127}, {506,127}, {507,109}, {508,85}, {513,127}, {514,127}, {515,63}, {517,85}, {518,42}, {603,100}, {604,216}, {605,214}, {606,204}, {607,204}, {608,204}, {609,51}, {616,25}, {617,89}, {619,76}, {624,204}, {625,0}, {629,127}, {637,0}, {638,0}, {646,144}, {647,85}, {649,127}, {650,132}, {652,127}, {653,85}, {654,0}, {656,127}, {659,127}, {662,127}, {663,127}, {664,127}, {665,127}, {674,59}, {675,127}, {676,85}, {678,127}, {682,127}, {683,106}, {684,47}, {685,79}, {690,127}, {692,127}, {693,204}, {700,63}, {701,0}, {702,0}, {703,0}, {704,0}, {705,127}, {706,127}, {707,0}, {708,0}, {709,0}, {710,0}, {711,127}, {712,0}, {713,159}, {714,0}, {715,0}, {750,178}, {752,127}, {753,36}, {754,85}, {755,131}, {756,127}, {757,127}, {758,127}, {759,153}, {760,95}, {762,0}, {763,140}, {764,74}, {765,27}, {769,127}, {773,127}, {775,0}, {779,214}, {780,204}, {781,198}, {785,0}, {789,0}, {795,63}, {796,30}, {799,127}, {800,226}, {801,255}, {802,198}, {803,255}, {804,255}, {805,255}, {806,255}, {807,255}, {808,255}, {812,255}, {813,255}, {814,255}, {815,204}, {816,0}, {817,255}, {818,255}, {819,255}, {820,255}, {821,255}, {822,255}, {823,255}, {824,255}, {825,255}, {826,255}, {827,255}, {828,0}, {829,255}, {830,255}, {834,255}, {835,255}, {836,255}, {840,0}, {841,127}, {842,127}, {844,255}, {848,25}, {858,100}, {859,255}, {860,255}, {861,255}, {862,255}, {863,84}, {868,0}, {869,0}, {877,0}, {879,51}, {880,132}, {921,255}, {922,255}, {923,255}, {10000,0}, {10001,0}, {10002,25}, {10003,0}, {10004,25}, {10005,23}, {10006,51}, {10007,0}, {10008,25}, {10009,23}, {10010,51}, {10011,0}, {10012,0}, {10013,25}, {10014,0}, {10015,25}, {10016,23}, {10017,51}, {10018,0}, {10019,0}, {10020,25}, {10021,0}, {10022,25}, {10023,23}, {10024,51}, {10025,0}, {10026,25}, {10027,23}, {10028,51}, {10029,0}, {10030,25}, {10031,23}, {10032,51}, {11000,1}, {11001,127} - }; - std::map female_params = { - {1,33}, {2,61}, {4,85}, {5,23}, {6,58}, {7,127}, {8,63}, {10,85}, {11,63}, {12,42}, {13,0}, {14,85}, {15,63}, {16,36}, {17,85}, {18,95}, {19,153}, {20,63}, {21,34}, {22,0}, {23,63}, {24,109}, {25,88}, {27,132}, {31,63}, {33,136}, {34,81}, {35,85}, {36,103}, {37,136}, {38,127}, {80,0}, {93,203}, {98,0}, {99,0}, {105,127}, {108,0}, {110,0}, {111,127}, {112,0}, {113,0}, {114,127}, {115,0}, {116,0}, {117,0}, {119,127}, {130,114}, {131,127}, {132,99}, {133,63}, {134,127}, {135,140}, {136,127}, {137,127}, {140,0}, {141,0}, {142,0}, {143,191}, {150,0}, {155,104}, {157,0}, {162,0}, {163,0}, {165,0}, {166,0}, {167,0}, {168,0}, {169,0}, {177,0}, {181,145}, {182,216}, {183,133}, {184,0}, {185,127}, {192,0}, {193,127}, {196,170}, {198,0}, {503,0}, {505,127}, {506,127}, {507,109}, {508,85}, {513,127}, {514,127}, {515,63}, {517,85}, {518,42}, {603,100}, {604,216}, {605,214}, {606,204}, {607,204}, {608,204}, {609,51}, {616,25}, {617,89}, {619,76}, {624,204}, {625,0}, {629,127}, {637,0}, {638,0}, {646,144}, {647,85}, {649,127}, {650,132}, {652,127}, {653,85}, {654,0}, {656,127}, {659,127}, {662,127}, {663,127}, {664,127}, {665,127}, {674,59}, {675,127}, {676,85}, {678,127}, {682,127}, {683,106}, {684,47}, {685,79}, {690,127}, {692,127}, {693,204}, {700,63}, {701,0}, {702,0}, {703,0}, {704,0}, {705,127}, {706,127}, {707,0}, {708,0}, {709,0}, {710,0}, {711,127}, {712,0}, {713,159}, {714,0}, {715,0}, {750,178}, {752,127}, {753,36}, {754,85}, {755,131}, {756,127}, {757,127}, {758,127}, {759,153}, {760,95}, {762,0}, {763,140}, {764,74}, {765,27}, {769,127}, {773,127}, {775,0}, {779,214}, {780,204}, {781,198}, {785,0}, {789,0}, {795,63}, {796,30}, {799,127}, {800,226}, {801,255}, {802,198}, {803,255}, {804,255}, {805,255}, {806,255}, {807,255}, {808,255}, {812,255}, {813,255}, {814,255}, {815,204}, {816,0}, {817,255}, {818,255}, {819,255}, {820,255}, {821,255}, {822,255}, {823,255}, {824,255}, {825,255}, {826,255}, {827,255}, {828,0}, {829,255}, {830,255}, {834,255}, {835,255}, {836,255}, {840,0}, {841,127}, {842,127}, {844,255}, {848,25}, {858,100}, {859,255}, {860,255}, {861,255}, {862,255}, {863,84}, {868,0}, {869,0}, {877,0}, {879,51}, {880,132}, {921,255}, {922,255}, {923,255}, {10000,0}, {10001,0}, {10002,25}, {10003,0}, {10004,25}, {10005,23}, {10006,51}, {10007,0}, {10008,25}, {10009,23}, {10010,51}, {10011,0}, {10012,0}, {10013,25}, {10014,0}, {10015,25}, {10016,23}, {10017,51}, {10018,0}, {10019,0}, {10020,25}, {10021,0}, {10022,25}, {10023,23}, {10024,51}, {10025,0}, {10026,25}, {10027,23}, {10028,51}, {10029,0}, {10030,25}, {10031,23}, {10032,51}, {11000,1}, {11001,127} - }; - std::map *params = NULL; - if (getSex() == SEX_MALE) - params = &male_params; - else - params = &female_params; - - for( auto it = params->begin(); it != params->end(); ++it) - { - LLVisualParam* param = getVisualParam(it->first); - if( !param ) - { - // invalid id - break; - } - - U8 value = it->second; - F32 newWeight = U8_to_F32(value, param->getMinWeight(), param->getMaxWeight()); - param->setWeight(newWeight); - } -} - -//----------------------------------------------------------------------------- -// resetSkeleton() -//----------------------------------------------------------------------------- -void LLVOAvatar::resetSkeleton(bool reset_animations) -{ - LL_DEBUGS("Avatar") << avString() << " reset starts" << LL_ENDL; - if (!isControlAvatar() && !mLastProcessedAppearance) - { - LL_WARNS() << "Can't reset avatar " << getID() << "; no appearance message has been received yet." << LL_ENDL; - return; - } - - // Save mPelvis state - //LLVector3 pelvis_pos = getJoint("mPelvis")->getPosition(); - //LLQuaternion pelvis_rot = getJoint("mPelvis")->getRotation(); - - // Clear all attachment pos and scale overrides - clearAttachmentOverrides(); - - // Note that we call buildSkeleton twice in this function. The first time is - // just to get the right scale for the collision volumes, because - // this will be used in setting the mJointScales for the - // LLPolySkeletalDistortions of which the CVs are children. - if( !buildSkeleton(sAvatarSkeletonInfo) ) - { - LL_ERRS() << "Error resetting skeleton" << LL_ENDL; - } - - // Reset some params to default state, without propagating changes downstream. - resetVisualParams(); - - // Now we have to reset the skeleton again, because its state - // got clobbered by the resetVisualParams() calls - // above. - if( !buildSkeleton(sAvatarSkeletonInfo) ) - { - LL_ERRS() << "Error resetting skeleton" << LL_ENDL; - } - - // Reset attachment points - // BuildSkeleton only does bones and CVs but we still need to reinit huds - // since huds can be animated. - bool ignore_hud_joints = !isSelf(); - initAttachmentPoints(ignore_hud_joints); - - // Fix up collision volumes - for (LLVisualParam *param = getFirstVisualParam(); - param; - param = getNextVisualParam()) - { - LLPolyMorphTarget *poly_morph = dynamic_cast(param); - if (poly_morph) - { - // This is a kludgy way to correct for the fact that the - // collision volumes have been reset out from under the - // poly morph sliders. - F32 delta_weight = poly_morph->getLastWeight() - poly_morph->getDefaultWeight(); - poly_morph->applyVolumeChanges(delta_weight); - } - } - - // Reset tweakable params to preserved state - if (getOverallAppearance() == AOA_NORMAL) - { - if (mLastProcessedAppearance) - { - bool slam_params = true; - applyParsedAppearanceMessage(*mLastProcessedAppearance, slam_params); - } - } - else - { - // Stripped down approximation of - // applyParsedAppearanceMessage, but with alternative default - // (jellydoll) params - setCompositeUpdatesEnabled( false ); - gPipeline.markGLRebuild(this); - applyDefaultParams(); - setCompositeUpdatesEnabled( true ); - updateMeshTextures(); - updateMeshVisibility(); - } - updateVisualParams(); - - // Restore attachment pos overrides - updateAttachmentOverrides(); - - // Animations - if (reset_animations) - { - if (isSelf()) - { - // This is equivalent to "Stop Animating Me". Will reset - // all animations and propagate the changes to other - // viewers. - gAgent.stopCurrentAnimations(); - } - else - { - // Local viewer-side reset for non-self avatars. - resetAnimations(); - } - } - - LL_DEBUGS("Avatar") << avString() << " reset ends" << LL_ENDL; -} - -//----------------------------------------------------------------------------- -// releaseMeshData() -//----------------------------------------------------------------------------- -void LLVOAvatar::releaseMeshData() -{ - if (sInstances.size() < AVATAR_RELEASE_THRESHOLD || isUIAvatar()) - { - return; - } - - // cleanup mesh data - for (avatar_joint_list_t::iterator iter = mMeshLOD.begin(); - iter != mMeshLOD.end(); - ++iter) - { - LLAvatarJoint* joint = (*iter); - joint->setValid(false, true); - } - - //cleanup data - if (mDrawable.notNull()) - { - LLFace* facep = mDrawable->getFace(0); - if (facep) - { - facep->setSize(0, 0); - for(S32 i = mNumInitFaces ; i < mDrawable->getNumFaces(); i++) - { - facep = mDrawable->getFace(i); - if (facep) - { - facep->setSize(0, 0); - } - } - } - } - - for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - if (!attachment->getIsHUDAttachment()) - { - attachment->setAttachmentVisibility(false); - } - } - mMeshValid = false; -} - -//----------------------------------------------------------------------------- -// restoreMeshData() -//----------------------------------------------------------------------------- -// virtual -void LLVOAvatar::restoreMeshData() -{ - llassert(!isSelf()); - if (mDrawable.isNull()) - { - return; - } - - //LL_INFOS() << "Restoring" << LL_ENDL; - mMeshValid = true; - updateJointLODs(); - - for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - if (!attachment->getIsHUDAttachment()) - { - attachment->setAttachmentVisibility(true); - } - } - - // force mesh update as LOD might not have changed to trigger this - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_GEOMETRY); -} - -//----------------------------------------------------------------------------- -// updateMeshData() -//----------------------------------------------------------------------------- -void LLVOAvatar::updateMeshData() -{ - if (mDrawable.notNull()) - { - stop_glerror(); - - S32 f_num = 0 ; - const U32 VERTEX_NUMBER_THRESHOLD = 128 ;//small number of this means each part of an avatar has its own vertex buffer. - const S32 num_parts = mMeshLOD.size(); - - // this order is determined by number of LODS - // if a mesh earlier in this list changed LODs while a later mesh doesn't, - // the later mesh's index offset will be inaccurate - for(S32 part_index = 0 ; part_index < num_parts ;) - { - S32 j = part_index ; - U32 last_v_num = 0, num_vertices = 0 ; - U32 last_i_num = 0, num_indices = 0 ; - - while(part_index < num_parts && num_vertices < VERTEX_NUMBER_THRESHOLD) - { - last_v_num = num_vertices ; - last_i_num = num_indices ; - - LLViewerJoint* part_mesh = getViewerJoint(part_index++); - if (part_mesh) - { - part_mesh->updateFaceSizes(num_vertices, num_indices, mAdjustedPixelArea); - } - } - if(num_vertices < 1)//skip empty meshes - { - continue ; - } - if(last_v_num > 0)//put the last inserted part into next vertex buffer. - { - num_vertices = last_v_num ; - num_indices = last_i_num ; - part_index-- ; - } - - LLFace* facep = NULL; - if(f_num < mDrawable->getNumFaces()) - { - facep = mDrawable->getFace(f_num); - } - else - { - facep = mDrawable->getFace(0); - if (facep) - { - facep = mDrawable->addFace(facep->getPool(), facep->getTexture()) ; - } - } - if (!facep) continue; - - // resize immediately - facep->setSize(num_vertices, num_indices); - - bool terse_update = false; - - facep->setGeomIndex(0); - facep->setIndicesIndex(0); - - LLVertexBuffer* buff = facep->getVertexBuffer(); - if(!facep->getVertexBuffer()) - { - buff = new LLVertexBuffer(LLDrawPoolAvatar::VERTEX_DATA_MASK); - if (!buff->allocateBuffer(num_vertices, num_indices)) - { - LL_WARNS() << "Failed to allocate Vertex Buffer for Mesh to " - << num_vertices << " vertices and " - << num_indices << " indices" << LL_ENDL; - // Attempt to create a dummy triangle (one vertex, 3 indices, all 0) - facep->setSize(1, 3); - buff->allocateBuffer(1, 3); - memset((U8*) buff->getMappedData(), 0, buff->getSize()); - memset((U8*) buff->getMappedIndices(), 0, buff->getIndicesSize()); - } - facep->setVertexBuffer(buff); - } - else - { - if (buff->getNumIndices() == num_indices && - buff->getNumVerts() == num_vertices) - { - terse_update = true; - } - else - { - buff = new LLVertexBuffer(buff->getTypeMask()); - if (!buff->allocateBuffer(num_vertices, num_indices)) - { - LL_WARNS() << "Failed to allocate vertex buffer for Mesh, Substituting" << LL_ENDL; - // Attempt to create a dummy triangle (one vertex, 3 indices, all 0) - facep->setSize(1, 3); - buff->allocateBuffer(1, 3); - memset((U8*) buff->getMappedData(), 0, buff->getSize()); - memset((U8*) buff->getMappedIndices(), 0, buff->getIndicesSize()); - } - } - } - - - // This is a hack! Avatars have their own pool, so we are detecting - // the case of more than one avatar in the pool (thus > 0 instead of >= 0) - if (facep->getGeomIndex() > 0) - { - LL_ERRS() << "non-zero geom index: " << facep->getGeomIndex() << " in LLVOAvatar::restoreMeshData" << LL_ENDL; - } - - if (num_vertices == buff->getNumVerts() && num_indices == buff->getNumIndices()) - { - for(S32 k = j ; k < part_index ; k++) - { - bool rigid = false; - if (k == MESH_ID_EYEBALL_LEFT || - k == MESH_ID_EYEBALL_RIGHT) - { - //eyeballs can't have terse updates since they're never rendered with - //the hardware skinning shader - rigid = true; - } - - LLViewerJoint* mesh = getViewerJoint(k); - if (mesh) - { - mesh->updateFaceData(facep, mAdjustedPixelArea, k == MESH_ID_HAIR, terse_update && !rigid); - } - } - } - - stop_glerror(); - buff->unmapBuffer(); - - if(!f_num) - { - f_num += mNumInitFaces ; - } - else - { - f_num++ ; - } - } - } -} - -//------------------------------------------------------------------------ - -//------------------------------------------------------------------------ -// LLVOAvatar::processUpdateMessage() -//------------------------------------------------------------------------ -U32 LLVOAvatar::processUpdateMessage(LLMessageSystem *mesgsys, - void **user_data, - U32 block_num, const EObjectUpdateType update_type, - LLDataPacker *dp) -{ - const bool had_no_name = !getNVPair("FirstName"); - - // Do base class updates... - U32 retval = LLViewerObject::processUpdateMessage(mesgsys, user_data, block_num, update_type, dp); - - // Print out arrival information once we have name of avatar. - const bool has_name = getNVPair("FirstName"); - if (had_no_name && has_name) - { - mDebugExistenceTimer.reset(); - debugAvatarRezTime("AvatarRezArrivedNotification", "avatar arrived"); - } - - if (retval & LLViewerObject::INVALID_UPDATE) - { - if (isSelf()) - { - //tell sim to cancel this update - gAgent.teleportViaLocation(gAgent.getPositionGlobal()); - } - } - - return retval; -} - -LLViewerFetchedTexture *LLVOAvatar::getBakedTextureImage(const U8 te, const LLUUID& uuid) -{ - LLViewerFetchedTexture *result = NULL; - - if (uuid == IMG_DEFAULT_AVATAR || - uuid == IMG_DEFAULT || - uuid == IMG_INVISIBLE) - { - // Should already exist, don't need to find it on sim or baked-texture host. - result = gTextureList.findImage(uuid, TEX_LIST_STANDARD); - } - if (!result) - { - const std::string url = getImageURL(te,uuid); - - if (url.empty()) - { - LL_WARNS() << "unable to determine URL for te " << te << " uuid " << uuid << LL_ENDL; - return NULL; - } - LL_DEBUGS("Avatar") << avString() << "get server-bake image from URL " << url << LL_ENDL; - result = LLViewerTextureManager::getFetchedTextureFromUrl( - url, FTT_SERVER_BAKE, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE, 0, 0, uuid); - if (result->isMissingAsset()) - { - result->setIsMissingAsset(false); - } - - } - return result; -} - -// virtual -S32 LLVOAvatar::setTETexture(const U8 te, const LLUUID& uuid) -{ - if (!isIndexBakedTexture((ETextureIndex)te)) - { - // Sim still sends some uuids for non-baked slots sometimes - ignore. - return LLViewerObject::setTETexture(te, LLUUID::null); - } - - LLViewerFetchedTexture *image = getBakedTextureImage(te,uuid); - llassert(image); - return setTETextureCore(te, image); -} - -//------------------------------------------------------------------------ -// LLVOAvatar::dumpAnimationState() -//------------------------------------------------------------------------ -void LLVOAvatar::dumpAnimationState() -{ - LL_INFOS() << "==============================================" << LL_ENDL; - for (LLVOAvatar::AnimIterator it = mSignaledAnimations.begin(); it != mSignaledAnimations.end(); ++it) - { - LLUUID id = it->first; - std::string playtag = ""; - if (mPlayingAnimations.find(id) != mPlayingAnimations.end()) - { - playtag = "*"; - } - LL_INFOS() << gAnimLibrary.animationName(id) << playtag << LL_ENDL; - } - for (LLVOAvatar::AnimIterator it = mPlayingAnimations.begin(); it != mPlayingAnimations.end(); ++it) - { - LLUUID id = it->first; - bool is_signaled = mSignaledAnimations.find(id) != mSignaledAnimations.end(); - if (!is_signaled) - { - LL_INFOS() << gAnimLibrary.animationName(id) << "!S" << LL_ENDL; - } - } -} - -//------------------------------------------------------------------------ -// idleUpdate() -//------------------------------------------------------------------------ -void LLVOAvatar::idleUpdate(LLAgent &agent, const F64 &time) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - - if (isDead()) - { - LL_INFOS() << "Warning! Idle on dead avatar" << LL_ENDL; - return; - } - - LLCachedControl friends_only(gSavedSettings, "RenderAvatarFriendsOnly", false); - if (friends_only() - && !isUIAvatar() - && !isControlAvatar() - && !isSelf() - && !isBuddy()) - { - if (mNameText) - { - mNameIsSet = false; - mNameText->markDead(); - mNameText = NULL; - sNumVisibleChatBubbles--; - } - deleteParticleSource(); - mVoiceVisualizer->setVoiceEnabled(false); - - return; - } - - // record time and refresh "tooSlow" status - updateTooSlow(); - - static LLCachedControl disable_all_render_types(gSavedSettings, "DisableAllRenderTypes"); - if (!(gPipeline.hasRenderType(mIsControlAvatar ? LLPipeline::RENDER_TYPE_CONTROL_AV : LLPipeline::RENDER_TYPE_AVATAR)) - && !disable_all_render_types && !isSelf()) - { - if (!mIsControlAvatar) - { - idleUpdateNameTag(idleCalcNameTagPosition(mLastRootPos)); - } - return; - } - - // Update should be happening max once per frame. - if ((mLastAnimExtents[0]==LLVector3())|| - (mLastAnimExtents[1])==LLVector3()) - { - mNeedsExtentUpdate = true; - } - else - { - const S32 upd_freq = 4; // force update every upd_freq frames. - mNeedsExtentUpdate = ((LLDrawable::getCurrentFrame()+mID.mData[0])%upd_freq==0); - } - - LLScopedContextString str("avatar_idle_update " + getFullname()); - - checkTextureLoading() ; - - // force immediate pixel area update on avatars using last frames data (before drawable or camera updates) - setPixelAreaAndAngle(gAgent); - - // force asynchronous drawable update - if(mDrawable.notNull()) - { - if (isSitting() && getParent()) - { - LLViewerObject *root_object = (LLViewerObject*)getRoot(); - LLDrawable* drawablep = root_object->mDrawable; - // if this object hasn't already been updated by another avatar... - if (drawablep) // && !drawablep->isState(LLDrawable::EARLY_MOVE)) - { - if (root_object->isSelected()) - { - gPipeline.updateMoveNormalAsync(drawablep); - } - else - { - gPipeline.updateMoveDampedAsync(drawablep); - } - } - } - else - { - gPipeline.updateMoveDampedAsync(mDrawable); - } - } - - //-------------------------------------------------------------------- - // set alpha flag depending on state - //-------------------------------------------------------------------- - - if (isSelf()) - { - LLViewerObject::idleUpdate(agent, time); - - // trigger fidget anims - if (isAnyAnimationSignaled(AGENT_STAND_ANIMS, NUM_AGENT_STAND_ANIMS)) - { - agent.fidget(); - } - } - else - { - // Should override the idleUpdate stuff and leave out the angular update part. - LLQuaternion rotation = getRotation(); - LLViewerObject::idleUpdate(agent, time); - setRotation(rotation); - } - - // attach objects that were waiting for a drawable - lazyAttach(); - - // animate the character - // store off last frame's root position to be consistent with camera position - mLastRootPos = mRoot->getWorldPosition(); - bool detailed_update = updateCharacter(agent); - - static LLUICachedControl visualizers_in_calls("ShowVoiceVisualizersInCalls", false); - bool voice_enabled = (visualizers_in_calls || LLVoiceClient::getInstance()->inProximalChannel()) && - LLVoiceClient::getInstance()->getVoiceEnabled(mID); - - LLVector3 hud_name_pos = idleCalcNameTagPosition(mLastRootPos); - - idleUpdateVoiceVisualizer(voice_enabled, hud_name_pos); - idleUpdateMisc( detailed_update ); - idleUpdateAppearanceAnimation(); - if (detailed_update) - { - idleUpdateLipSync( voice_enabled ); - idleUpdateLoadingEffect(); - idleUpdateBelowWater(); // wind effect uses this - idleUpdateWindEffect(); - } - - idleUpdateNameTag(hud_name_pos); - - // Complexity has stale mechanics, but updates still can be very rapid - // so spread avatar complexity calculations over frames to lesen load from - // rapid updates and to make sure all avatars are not calculated at once. - S32 compl_upd_freq = 20; - if (isControlAvatar()) - { - // animeshes do not (or won't) have impostors nor change outfis, - // no need for high frequency - compl_upd_freq = 100; - } - else if (mLastRezzedStatus <= 0) //cloud or init - { - compl_upd_freq = 60; - } - else if (isSelf()) - { - compl_upd_freq = 5; - } - else if (mLastRezzedStatus == 1) //'grey', not fully loaded - { - compl_upd_freq = 40; - } - else if (isInMuteList()) //cheap, buffers value from search - { - compl_upd_freq = 100; - } - - if ((LLFrameTimer::getFrameCount() + mID.mData[0]) % compl_upd_freq == 0) - { - // DEPRECATED - // replace with LLPipeline::profileAvatar? - // Avatar profile takes ~ 0.5ms while idleUpdateRenderComplexity takes ~5ms - // (both are unacceptably costly) - idleUpdateRenderComplexity(); - } - idleUpdateDebugInfo(); -} - -void LLVOAvatar::idleUpdateVoiceVisualizer(bool voice_enabled, const LLVector3 &position) -{ - bool render_visualizer = voice_enabled; - - // Don't render the user's own voice visualizer when in mouselook, or when opening the mic is disabled. - if(isSelf()) - { - static LLCachedControl voice_disable_mic(gSavedSettings, "VoiceDisableMic"); - if(gAgentCamera.cameraMouselook() || voice_disable_mic) - { - render_visualizer = false; - } - } - - mVoiceVisualizer->setVoiceEnabled(render_visualizer); - - if ( voice_enabled ) - { - //---------------------------------------------------------------- - // Only do gesture triggering for your own avatar, and only when you're in a proximal channel. - //---------------------------------------------------------------- - if( isSelf() ) - { - //---------------------------------------------------------------------------------------- - // The following takes the voice signal and uses that to trigger gesticulations. - //---------------------------------------------------------------------------------------- - int lastGesticulationLevel = mCurrentGesticulationLevel; - mCurrentGesticulationLevel = mVoiceVisualizer->getCurrentGesticulationLevel(); - - //--------------------------------------------------------------------------------------------------- - // If "current gesticulation level" changes, we catch this, and trigger the new gesture - //--------------------------------------------------------------------------------------------------- - if ( lastGesticulationLevel != mCurrentGesticulationLevel ) - { - if ( mCurrentGesticulationLevel != VOICE_GESTICULATION_LEVEL_OFF ) - { - std::string gestureString = "unInitialized"; - if ( mCurrentGesticulationLevel == 0 ) { gestureString = "/voicelevel1"; } - else if ( mCurrentGesticulationLevel == 1 ) { gestureString = "/voicelevel2"; } - else if ( mCurrentGesticulationLevel == 2 ) { gestureString = "/voicelevel3"; } - else { LL_INFOS() << "oops - CurrentGesticulationLevel can be only 0, 1, or 2" << LL_ENDL; } - - // this is the call that Karl S. created for triggering gestures from within the code. - LLGestureMgr::instance().triggerAndReviseString( gestureString ); - } - } - - } //if( isSelf() ) - - //----------------------------------------------------------------------------------------------------------------- - // If the avatar is speaking, then the voice amplitude signal is passed to the voice visualizer. - // Also, here we trigger voice visualizer start and stop speaking, so it can animate the voice symbol. - // - // Notice the calls to "gAwayTimer.reset()". This resets the timer that determines how long the avatar has been - // "away", so that the avatar doesn't lapse into away-mode (and slump over) while the user is still talking. - //----------------------------------------------------------------------------------------------------------------- - if (LLVoiceClient::getInstance()->getIsSpeaking( mID )) - { - if (!mVoiceVisualizer->getCurrentlySpeaking()) - { - mVoiceVisualizer->setStartSpeaking(); - - //printf( "gAwayTimer.reset();\n" ); - } - - mVoiceVisualizer->setSpeakingAmplitude( LLVoiceClient::getInstance()->getCurrentPower( mID ) ); - - if( isSelf() ) - { - gAgent.clearAFK(); - } - } - else - { - if ( mVoiceVisualizer->getCurrentlySpeaking() ) - { - mVoiceVisualizer->setStopSpeaking(); - - if ( mLipSyncActive ) - { - if( mOohMorph ) mOohMorph->setWeight(mOohMorph->getMinWeight()); - if( mAahMorph ) mAahMorph->setWeight(mAahMorph->getMinWeight()); - - mLipSyncActive = false; - LLCharacter::updateVisualParams(); - dirtyMesh(); - } - } - } - mVoiceVisualizer->setPositionAgent(position); - }//if ( voiceEnabled ) -} - -static void override_bbox(LLDrawable* drawable, LLVector4a* extents) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_SPATIAL; - drawable->setSpatialExtents(extents[0], extents[1]); - drawable->setPositionGroup(LLVector4a(0, 0, 0)); - drawable->movePartition(); -} - -void LLVOAvatar::idleUpdateMisc(bool detailed_update) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - if (LLVOAvatar::sJointDebug) - { - LL_INFOS() << getFullname() << ": joint touches: " << LLJoint::sNumTouches << " updates: " << LLJoint::sNumUpdates << LL_ENDL; - } - - LLJoint::sNumUpdates = 0; - LLJoint::sNumTouches = 0; - - bool visible = isVisible() || mNeedsAnimUpdate; - - // update attachments positions - if (detailed_update) - { - U32 draw_order = 0; - S32 attachment_selected = LLSelectMgr::getInstance()->getSelection()->getObjectCount() && LLSelectMgr::getInstance()->getSelection()->isAttachment(); - for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - LLViewerObject* attached_object = attachment_iter->get(); - if (!attached_object - || attached_object->isDead() - || !attachment->getValid() - || attached_object->mDrawable.isNull()) - { - continue; - } - - LLSpatialBridge* bridge = attached_object->mDrawable->getSpatialBridge(); - - if (visible || !(bridge && bridge->getRadius() < 2.0)) - { - //override rigged attachments' octree spatial extents with this avatar's bounding box - bool rigged = false; - if (bridge) - { - //transform avatar bounding box into attachment's coordinate frame - LLVector4a extents[2]; - bridge->transformExtents(mDrawable->getSpatialExtents(), extents); - - if (attached_object->mDrawable->isState(LLDrawable::RIGGED | LLDrawable::RIGGED_CHILD)) - { - rigged = true; - override_bbox(attached_object->mDrawable, extents); - } - } - - // if selecting any attachments, update all of them as non-damped - if (attachment_selected) - { - gPipeline.updateMoveNormalAsync(attached_object->mDrawable); - } - else - { - // Note: SL-17415; While most objects follow joints, - // some objects get position updates from server - gPipeline.updateMoveDampedAsync(attached_object->mDrawable); - } - - // override_bbox calls movePartition() and getSpatialPartition(), - // so bridge might no longer be valid, get it again. - // ex: animesh stops being an animesh - bridge = attached_object->mDrawable->getSpatialBridge(); - if (bridge) - { - if (!rigged) - { - gPipeline.updateMoveNormalAsync(bridge); - } - else - { - //specialized impl of updateMoveNormalAsync just for rigged attachment SpatialBridge - bridge->setState(LLDrawable::MOVE_UNDAMPED); - bridge->updateMove(); - bridge->setState(LLDrawable::EARLY_MOVE); - - LLSpatialGroup* group = attached_object->mDrawable->getSpatialGroup(); - if (group) - { //set draw order of group - group->mAvatarp = this; - group->mRenderOrder = draw_order++; - } - } - } - - attached_object->updateText(); - } - } - } - } - - mNeedsAnimUpdate = false; - - if (isImpostor() && !mNeedsImpostorUpdate) - { - LL_ALIGN_16(LLVector4a ext[2]); - F32 distance; - LLVector3 angle; - - getImpostorValues(ext, angle, distance); - - for (U32 i = 0; i < 3 && !mNeedsImpostorUpdate; i++) - { - F32 cur_angle = angle.mV[i]; - F32 old_angle = mImpostorAngle.mV[i]; - F32 angle_diff = fabsf(cur_angle-old_angle); - - if (angle_diff > F_PI/512.f*distance*mUpdatePeriod) - { - mNeedsImpostorUpdate = true; - mLastImpostorUpdateReason = 2; - } - } - - if (detailed_update && !mNeedsImpostorUpdate) - { //update impostor if view angle, distance, or bounding box change - //significantly - - F32 dist_diff = fabsf(distance-mImpostorDistance); - if (dist_diff/mImpostorDistance > 0.1f) - { - mNeedsImpostorUpdate = true; - mLastImpostorUpdateReason = 3; - } - else - { - ext[0].load3(mLastAnimExtents[0].mV); - ext[1].load3(mLastAnimExtents[1].mV); - // Expensive. Just call this once per frame, in updateSpatialExtents(); - //calculateSpatialExtents(ext[0], ext[1]); - LLVector4a diff; - diff.setSub(ext[1], mImpostorExtents[1]); - if (diff.getLength3().getF32() > 0.05f) - { - mNeedsImpostorUpdate = true; - mLastImpostorUpdateReason = 4; - } - else - { - diff.setSub(ext[0], mImpostorExtents[0]); - if (diff.getLength3().getF32() > 0.05f) - { - mNeedsImpostorUpdate = true; - mLastImpostorUpdateReason = 5; - } - } - } - } - } - - if (mDrawable.notNull()) - { - mDrawable->movePartition(); - - //force a move if sitting on an active object - if (getParent() && ((LLViewerObject*) getParent())->mDrawable->isActive()) - { - gPipeline.markMoved(mDrawable, true); - } - } -} - -void LLVOAvatar::idleUpdateAppearanceAnimation() -{ - // update morphing params - if (mAppearanceAnimating) - { - ESex avatar_sex = getSex(); - F32 appearance_anim_time = mAppearanceMorphTimer.getElapsedTimeF32(); - if (appearance_anim_time >= APPEARANCE_MORPH_TIME) - { - mAppearanceAnimating = false; - for (LLVisualParam *param = getFirstVisualParam(); - param; - param = getNextVisualParam()) - { - if (param->isTweakable()) - { - param->stopAnimating(); - } - } - updateVisualParams(); - } - else - { - F32 morph_amt = calcMorphAmount(); - LLVisualParam *param; - - if (!isSelf()) - { - // animate only top level params for non-self avatars - for (param = getFirstVisualParam(); - param; - param = getNextVisualParam()) - { - if (param->isTweakable()) - { - param->animate(morph_amt); - } - } - } - - // apply all params - for (param = getFirstVisualParam(); - param; - param = getNextVisualParam()) - { - param->apply(avatar_sex); - } - - mLastAppearanceBlendTime = appearance_anim_time; - } - dirtyMesh(); - } -} - -F32 LLVOAvatar::calcMorphAmount() -{ - F32 appearance_anim_time = mAppearanceMorphTimer.getElapsedTimeF32(); - F32 blend_frac = calc_bouncy_animation(appearance_anim_time / APPEARANCE_MORPH_TIME); - F32 last_blend_frac = calc_bouncy_animation(mLastAppearanceBlendTime / APPEARANCE_MORPH_TIME); - - F32 morph_amt; - if (last_blend_frac == 1.f) - { - morph_amt = 1.f; - } - else - { - morph_amt = (blend_frac - last_blend_frac) / (1.f - last_blend_frac); - } - - return morph_amt; -} - -void LLVOAvatar::idleUpdateLipSync(bool voice_enabled) -{ - // Use the Lipsync_Ooh and Lipsync_Aah morphs for lip sync - if ( voice_enabled - && mLastRezzedStatus > 0 // no point updating lip-sync for clouds - && (LLVoiceClient::getInstance()->lipSyncEnabled()) - && LLVoiceClient::getInstance()->getIsSpeaking( mID ) ) - { - F32 ooh_morph_amount = 0.0f; - F32 aah_morph_amount = 0.0f; - - mVoiceVisualizer->lipSyncOohAah( ooh_morph_amount, aah_morph_amount ); - - if( mOohMorph ) - { - F32 ooh_weight = mOohMorph->getMinWeight() - + ooh_morph_amount * (mOohMorph->getMaxWeight() - mOohMorph->getMinWeight()); - - mOohMorph->setWeight( ooh_weight); - } - - if( mAahMorph ) - { - F32 aah_weight = mAahMorph->getMinWeight() - + aah_morph_amount * (mAahMorph->getMaxWeight() - mAahMorph->getMinWeight()); - - mAahMorph->setWeight( aah_weight); - } - - mLipSyncActive = true; - LLCharacter::updateVisualParams(); - dirtyMesh(); - } -} - -void LLVOAvatar::idleUpdateLoadingEffect() -{ - // update visibility when avatar is partially loaded - if (updateIsFullyLoaded()) // changed? - { - if (isFullyLoaded()) - { - if (mFirstFullyVisible) - { - mFirstFullyVisible = false; - mFirstDecloudTime = mFirstAppearanceMessageTimer.getElapsedTimeF32(); - if (isSelf()) - { - LL_INFOS("Avatar") << avString() << "self isFullyLoaded, mFirstFullyVisible after " << mFirstDecloudTime << LL_ENDL; - LLAppearanceMgr::instance().onFirstFullyVisible(); - } - else - { - LL_INFOS("Avatar") << avString() << "other isFullyLoaded, mFirstFullyVisible after " << mFirstDecloudTime << LL_ENDL; - } - } - - deleteParticleSource(); - updateLOD(); - } - else - { - LLPartSysData particle_parameters; - - // fancy particle cloud designed by Brent - particle_parameters.mPartData.mMaxAge = 4.f; - particle_parameters.mPartData.mStartScale.mV[VX] = 0.8f; - particle_parameters.mPartData.mStartScale.mV[VX] = 0.8f; - particle_parameters.mPartData.mStartScale.mV[VY] = 1.0f; - particle_parameters.mPartData.mEndScale.mV[VX] = 0.02f; - particle_parameters.mPartData.mEndScale.mV[VY] = 0.02f; - particle_parameters.mPartData.mStartColor = LLColor4(1, 1, 1, 0.5f); - particle_parameters.mPartData.mEndColor = LLColor4(1, 1, 1, 0.0f); - particle_parameters.mPartData.mStartScale.mV[VX] = 0.8f; - particle_parameters.mPartImageID = sCloudTexture->getID(); - particle_parameters.mMaxAge = 0.f; - particle_parameters.mPattern = LLPartSysData::LL_PART_SRC_PATTERN_ANGLE_CONE; - particle_parameters.mInnerAngle = F_PI; - particle_parameters.mOuterAngle = 0.f; - particle_parameters.mBurstRate = 0.02f; - particle_parameters.mBurstRadius = 0.0f; - particle_parameters.mBurstPartCount = 1; - particle_parameters.mBurstSpeedMin = 0.1f; - particle_parameters.mBurstSpeedMax = 1.f; - particle_parameters.mPartData.mFlags = ( LLPartData::LL_PART_INTERP_COLOR_MASK | LLPartData::LL_PART_INTERP_SCALE_MASK | - LLPartData::LL_PART_EMISSIVE_MASK | // LLPartData::LL_PART_FOLLOW_SRC_MASK | - LLPartData::LL_PART_TARGET_POS_MASK ); - - // do not generate particles for dummy or overly-complex avatars - if (!mIsDummy && !isTooComplex() && !isTooSlow()) - { - setParticleSource(particle_parameters, getID()); - } - } - } -} - -void LLVOAvatar::idleUpdateWindEffect() -{ - // update wind effect - if ((LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_AVATAR) >= LLDrawPoolAvatar::SHADER_LEVEL_CLOTH)) - { - F32 hover_strength = 0.f; - F32 time_delta = mRippleTimer.getElapsedTimeF32() - mRippleTimeLast; - mRippleTimeLast = mRippleTimer.getElapsedTimeF32(); - LLVector3 velocity = getVelocity(); - F32 speed = velocity.length(); - //RN: velocity varies too much frame to frame for this to work - mRippleAccel.clearVec();//lerp(mRippleAccel, (velocity - mLastVel) * time_delta, LLSmoothInterpolation::getInterpolant(0.02f)); - mLastVel = velocity; - LLVector4 wind; - wind.setVec(getRegion()->mWind.getVelocityNoisy(getPositionAgent(), 4.f) - velocity); - - if (mInAir) - { - hover_strength = HOVER_EFFECT_STRENGTH * llmax(0.f, HOVER_EFFECT_MAX_SPEED - speed); - } - - if (mBelowWater) - { - // TODO: make cloth flow more gracefully when underwater - hover_strength += UNDERWATER_EFFECT_STRENGTH; - } - - wind.mV[VZ] += hover_strength; - wind.normalize(); - - wind.mV[VW] = llmin(0.025f + (speed * 0.015f) + hover_strength, 0.5f); - F32 interp; - if (wind.mV[VW] > mWindVec.mV[VW]) - { - interp = LLSmoothInterpolation::getInterpolant(0.2f); - } - else - { - interp = LLSmoothInterpolation::getInterpolant(0.4f); - } - mWindVec = lerp(mWindVec, wind, interp); - - F32 wind_freq = hover_strength + llclamp(8.f + (speed * 0.7f) + (noise1(mRipplePhase) * 4.f), 8.f, 25.f); - mWindFreq = lerp(mWindFreq, wind_freq, interp); - - if (mBelowWater) - { - mWindFreq *= UNDERWATER_FREQUENCY_DAMP; - } - - mRipplePhase += (time_delta * mWindFreq); - if (mRipplePhase > F_TWO_PI) - { - mRipplePhase = fmodf(mRipplePhase, F_TWO_PI); - } - } -} - -void LLVOAvatar::idleUpdateNameTag(const LLVector3& root_pos_last) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - - // update chat bubble - //-------------------------------------------------------------------- - // draw text label over character's head - //-------------------------------------------------------------------- - if (mChatTimer.getElapsedTimeF32() > BUBBLE_CHAT_TIME) - { - mChats.clear(); - } - - const F32 time_visible = mTimeVisible.getElapsedTimeF32(); - - static LLCachedControl NAME_SHOW_TIME(gSavedSettings, "RenderNameShowTime"); // seconds - static LLCachedControl FADE_DURATION(gSavedSettings, "RenderNameFadeDuration"); // seconds - static LLCachedControl use_chat_bubbles(gSavedSettings, "UseChatBubbles"); - - bool visible_chat = use_chat_bubbles && (mChats.size() || mTyping); - bool render_name = visible_chat || - (((sRenderName == RENDER_NAME_ALWAYS) || - (sRenderName == RENDER_NAME_FADE && time_visible < NAME_SHOW_TIME))); - // If it's your own avatar, don't draw in mouselook, and don't - // draw if we're specifically hiding our own name. - if (isSelf()) - { - static LLCachedControl render_name_show_self(gSavedSettings, "RenderNameShowSelf"); - static LLCachedControl name_tag_mode(gSavedSettings, "AvatarNameTagMode"); - render_name = render_name - && !gAgentCamera.cameraMouselook() - && (visible_chat || (render_name_show_self && name_tag_mode)); - } - - if ( !render_name ) - { - if (mNameText) - { - // ...clean up old name tag - mNameText->markDead(); - mNameText = NULL; - sNumVisibleChatBubbles--; - } - return; - } - - bool new_name = false; - if (visible_chat != mVisibleChat) - { - mVisibleChat = visible_chat; - new_name = true; - } - - if (sRenderGroupTitles != mRenderGroupTitles) - { - mRenderGroupTitles = sRenderGroupTitles; - new_name = true; - } - - // First Calculate Alpha - // If alpha > 0, create mNameText if necessary, otherwise delete it - F32 alpha = 0.f; - if (mAppAngle > 5.f) - { - const F32 START_FADE_TIME = NAME_SHOW_TIME - FADE_DURATION; - if (!visible_chat && sRenderName == RENDER_NAME_FADE && time_visible > START_FADE_TIME) - { - alpha = 1.f - (time_visible - START_FADE_TIME) / FADE_DURATION; - } - else - { - // ...not fading, full alpha - alpha = 1.f; - } - } - else if (mAppAngle > 2.f) - { - // far away is faded out also - alpha = (mAppAngle-2.f)/3.f; - } - - if (alpha <= 0.f) - { - if (mNameText) - { - mNameText->markDead(); - mNameText = NULL; - sNumVisibleChatBubbles--; - } - return; - } - - if (!mNameText) - { - mNameText = static_cast( LLHUDObject::addHUDObject( - LLHUDObject::LL_HUD_NAME_TAG) ); - //mNameText->setMass(10.f); - mNameText->setSourceObject(this); - mNameText->setVertAlignment(LLHUDNameTag::ALIGN_VERT_TOP); - mNameText->setVisibleOffScreen(true); - mNameText->setMaxLines(11); - mNameText->setFadeDistance(CHAT_NORMAL_RADIUS, 5.f); - sNumVisibleChatBubbles++; - new_name = true; - } - - mNameText->setPositionAgent(root_pos_last); - - idleUpdateNameTagText(new_name); - idleUpdateNameTagAlpha(new_name, alpha); -} - -void LLVOAvatar::idleUpdateNameTagText(bool new_name) -{ - LLNameValue *title = getNVPair("Title"); - LLNameValue* firstname = getNVPair("FirstName"); - LLNameValue* lastname = getNVPair("LastName"); - - // Avatars must have a first and last name - if (!firstname || !lastname) return; - - bool is_away = mSignaledAnimations.find(ANIM_AGENT_AWAY) != mSignaledAnimations.end(); - bool is_do_not_disturb = mSignaledAnimations.find(ANIM_AGENT_DO_NOT_DISTURB) != mSignaledAnimations.end(); - bool is_appearance = mSignaledAnimations.find(ANIM_AGENT_CUSTOMIZE) != mSignaledAnimations.end(); - bool is_muted; - if (isSelf()) - { - is_muted = false; - } - else - { - is_muted = isInMuteList(); - } - bool is_friend = LLAvatarTracker::instance().isBuddy(getID()); - bool is_cloud = getIsCloud(); - - if (is_appearance != mNameAppearance) - { - if (is_appearance) - { - debugAvatarRezTime("AvatarRezEnteredAppearanceNotification","entered appearance mode"); - } - else - { - debugAvatarRezTime("AvatarRezLeftAppearanceNotification","left appearance mode"); - } - } - - // Rebuild name tag if state change detected - if (!mNameIsSet - || new_name - || (!title && !mTitle.empty()) - || (title && mTitle != title->getString()) - || is_away != mNameAway - || is_do_not_disturb != mNameDoNotDisturb - || is_muted != mNameMute - || is_appearance != mNameAppearance - || is_friend != mNameFriend - || is_cloud != mNameCloud) - { - LLColor4 name_tag_color = getNameTagColor(is_friend); - - clearNameTag(); - - if (is_away || is_muted || is_do_not_disturb || is_appearance) - { - std::string line; - if (is_away) - { - line += LLTrans::getString("AvatarAway"); - line += ", "; - } - if (is_do_not_disturb) - { - line += LLTrans::getString("AvatarDoNotDisturb"); - line += ", "; - } - if (is_muted) - { - line += LLTrans::getString("AvatarMuted"); - line += ", "; - } - if (is_appearance) - { - line += LLTrans::getString("AvatarEditingAppearance"); - line += ", "; - } - if (is_cloud) - { - line += LLTrans::getString("LoadingData"); - line += ", "; - } - // trim last ", " - line.resize( line.length() - 2 ); - addNameTagLine(line, name_tag_color, LLFontGL::NORMAL, - LLFontGL::getFontSansSerifSmall()); - } - - if (sRenderGroupTitles - && title && title->getString() && title->getString()[0] != '\0') - { - std::string title_str = title->getString(); - LLStringFn::replace_ascii_controlchars(title_str,LL_UNKNOWN_CHAR); - addNameTagLine(title_str, name_tag_color, LLFontGL::NORMAL, - LLFontGL::getFontSansSerifSmall(), true); - } - - static LLUICachedControl show_display_names("NameTagShowDisplayNames", true); - static LLUICachedControl show_usernames("NameTagShowUsernames", true); - static LLUICachedControl show_rez_status("NameTagDebugAVRezState", false); - - if (LLAvatarName::useDisplayNames()) - { - LLAvatarName av_name; - if (!LLAvatarNameCache::get(getID(), &av_name)) - { - // Force a rebuild at next idle - // Note: do not connect a callback on idle(). - clearNameTag(); - } - - // Might be blank if name not available yet, that's OK - if (show_display_names) - { - addNameTagLine(av_name.getDisplayName(), name_tag_color, LLFontGL::NORMAL, - LLFontGL::getFontSansSerif(), true); - } - // Suppress SLID display if display name matches exactly (ugh) - if (show_usernames && !av_name.isDisplayNameDefault()) - { - // *HACK: Desaturate the color - LLColor4 username_color = name_tag_color * 0.83f; - addNameTagLine(av_name.getUserName(), username_color, LLFontGL::NORMAL, - LLFontGL::getFontSansSerifSmall(), true); - } - } - else - { - const LLFontGL* font = LLFontGL::getFontSansSerif(); - std::string full_name = LLCacheName::buildFullName( firstname->getString(), lastname->getString() ); - addNameTagLine(full_name, name_tag_color, LLFontGL::NORMAL, font, true); - } - - if (show_rez_status) - { - std::string av_string = LLVOAvatar::rezStatusToString(mLastRezzedStatus); - addNameTagLine(av_string, name_tag_color, LLFontGL::NORMAL, LLFontGL::getFontSansSerifSmall(), true); - } - - mNameAway = is_away; - mNameDoNotDisturb = is_do_not_disturb; - mNameMute = is_muted; - mNameAppearance = is_appearance; - mNameFriend = is_friend; - mNameCloud = is_cloud; - mTitle = title ? title->getString() : ""; - LLStringFn::replace_ascii_controlchars(mTitle,LL_UNKNOWN_CHAR); - new_name = true; - } - - if (mVisibleChat) - { - mNameText->setFont(LLFontGL::getFontSansSerif()); - mNameText->setTextAlignment(LLHUDNameTag::ALIGN_TEXT_LEFT); - mNameText->setFadeDistance(CHAT_NORMAL_RADIUS * 2.f, 5.f); - - std::deque::iterator chat_iter = mChats.begin(); - mNameText->clearString(); - - LLColor4 new_chat = LLUIColorTable::instance().getColor( isSelf() ? "UserChatColor" : "AgentChatColor" ); - LLColor4 normal_chat = lerp(new_chat, LLColor4(0.8f, 0.8f, 0.8f, 1.f), 0.7f); - LLColor4 old_chat = lerp(normal_chat, LLColor4(0.6f, 0.6f, 0.6f, 1.f), 0.7f); - if (mTyping && mChats.size() >= MAX_BUBBLE_CHAT_UTTERANCES) - { - ++chat_iter; - } - - for(; chat_iter != mChats.end(); ++chat_iter) - { - F32 chat_fade_amt = llclamp((F32)((LLFrameTimer::getElapsedSeconds() - chat_iter->mTime) / CHAT_FADE_TIME), 0.f, 4.f); - LLFontGL::StyleFlags style; - switch(chat_iter->mChatType) - { - case CHAT_TYPE_WHISPER: - style = LLFontGL::ITALIC; - break; - case CHAT_TYPE_SHOUT: - style = LLFontGL::BOLD; - break; - default: - style = LLFontGL::NORMAL; - break; - } - if (chat_fade_amt < 1.f) - { - F32 u = clamp_rescale(chat_fade_amt, 0.9f, 1.f, 0.f, 1.f); - mNameText->addLine(chat_iter->mText, lerp(new_chat, normal_chat, u), style); - } - else if (chat_fade_amt < 2.f) - { - F32 u = clamp_rescale(chat_fade_amt, 1.9f, 2.f, 0.f, 1.f); - mNameText->addLine(chat_iter->mText, lerp(normal_chat, old_chat, u), style); - } - else if (chat_fade_amt < 3.f) - { - // *NOTE: only remove lines down to minimum number - mNameText->addLine(chat_iter->mText, old_chat, style); - } - } - mNameText->setVisibleOffScreen(true); - - if (mTyping) - { - S32 dot_count = (llfloor(mTypingTimer.getElapsedTimeF32() * 3.f) + 2) % 3 + 1; - switch(dot_count) - { - case 1: - mNameText->addLine(".", new_chat); - break; - case 2: - mNameText->addLine("..", new_chat); - break; - case 3: - mNameText->addLine("...", new_chat); - break; - } - - } - } - else - { - // ...not using chat bubbles, just names - mNameText->setTextAlignment(LLHUDNameTag::ALIGN_TEXT_CENTER); - mNameText->setFadeDistance(CHAT_NORMAL_RADIUS, 5.f); - mNameText->setVisibleOffScreen(false); - } -} - -void LLVOAvatar::addNameTagLine(const std::string& line, const LLColor4& color, S32 style, const LLFontGL* font, const bool use_ellipses) -{ - // extra width (NAMETAG_MAX_WIDTH) is for names only, not for chat - llassert(mNameText); - if (mVisibleChat) - { - mNameText->addLabel(line, LLHUDNameTag::NAMETAG_MAX_WIDTH); - } - else - { - mNameText->addLine(line, color, (LLFontGL::StyleFlags)style, font, use_ellipses, LLHUDNameTag::NAMETAG_MAX_WIDTH); - } - mNameIsSet |= !line.empty(); -} - -void LLVOAvatar::clearNameTag() -{ - mNameIsSet = false; - if (mNameText) - { - mNameText->setLabel(""); - mNameText->setString(""); - } - mTimeVisible.reset(); -} - -//static -void LLVOAvatar::invalidateNameTag(const LLUUID& agent_id) -{ - LLViewerObject* obj = gObjectList.findObject(agent_id); - if (!obj) return; - - LLVOAvatar* avatar = dynamic_cast(obj); - if (!avatar) return; - - avatar->clearNameTag(); -} - -//static -void LLVOAvatar::invalidateNameTags() -{ - std::vector::iterator it = LLCharacter::sInstances.begin(); - for ( ; it != LLCharacter::sInstances.end(); ++it) - { - LLVOAvatar* avatar = dynamic_cast(*it); - if (!avatar) continue; - if (avatar->isDead()) continue; - - avatar->clearNameTag(); - } -} - -// Compute name tag position during idle update -LLVector3 LLVOAvatar::idleCalcNameTagPosition(const LLVector3 &root_pos_last) -{ - LLQuaternion root_rot = mRoot->getWorldRotation(); - LLQuaternion inv_root_rot = ~root_rot; - LLVector3 pixel_right_vec; - LLVector3 pixel_up_vec; - LLViewerCamera::getInstance()->getPixelVectors(root_pos_last, pixel_up_vec, pixel_right_vec); - LLVector3 camera_to_av = root_pos_last - LLViewerCamera::getInstance()->getOrigin(); - camera_to_av.normalize(); - LLVector3 local_camera_at = camera_to_av * inv_root_rot; - LLVector3 local_camera_up = camera_to_av % LLViewerCamera::getInstance()->getLeftAxis(); - local_camera_up.normalize(); - local_camera_up = local_camera_up * inv_root_rot; - - // position is based on head position, does not require mAvatarOffset here. - Nyx - LLVector3 avatar_ellipsoid(mBodySize.mV[VX] * 0.4f, - mBodySize.mV[VY] * 0.4f, - mBodySize.mV[VZ] * NAMETAG_VERT_OFFSET_WEIGHT); - - local_camera_up.scaleVec(avatar_ellipsoid); - local_camera_at.scaleVec(avatar_ellipsoid); - - LLVector3 head_offset = (mHeadp->getLastWorldPosition() - mRoot->getLastWorldPosition()) * inv_root_rot; - - if (dist_vec(head_offset, mTargetRootToHeadOffset) > NAMETAG_UPDATE_THRESHOLD) - { - mTargetRootToHeadOffset = head_offset; - } - - mCurRootToHeadOffset = lerp(mCurRootToHeadOffset, mTargetRootToHeadOffset, LLSmoothInterpolation::getInterpolant(0.2f)); - - LLVector3 name_position = mRoot->getLastWorldPosition() + (mCurRootToHeadOffset * root_rot); - name_position += (local_camera_up * root_rot) - (projected_vec(local_camera_at * root_rot, camera_to_av)); - name_position += pixel_up_vec * NAMETAG_VERTICAL_SCREEN_OFFSET; - - const F32 water_height = getRegion()->getWaterHeight(); - static const F32 WATER_HEIGHT_DELTA = 0.25f; - if (name_position[VZ] < water_height + WATER_HEIGHT_DELTA) - { - if (LLViewerCamera::getInstance()->getOrigin()[VZ] >= water_height) - { - name_position[VZ] = water_height; - } - else if (mNameText) // both camera and HUD are below watermark - { - F32 name_world_height = mNameText->getWorldHeight(); - F32 max_z_position = water_height - name_world_height; - if (name_position[VZ] > max_z_position) - { - name_position[VZ] = max_z_position; - } - } - } - - return name_position; -} - -void LLVOAvatar::idleUpdateNameTagAlpha(bool new_name, F32 alpha) -{ - llassert(mNameText); - - if (new_name - || alpha != mNameAlpha) - { - mNameText->setAlpha(alpha); - mNameAlpha = alpha; - } -} - -LLColor4 LLVOAvatar::getNameTagColor(bool is_friend) -{ - static LLUICachedControl show_friends("NameTagShowFriends", false); - const char* color_name; - if (show_friends && is_friend) - { - color_name = "NameTagFriend"; - } - else if (LLAvatarName::useDisplayNames()) - { - // ...color based on whether username "matches" a computed display name - LLAvatarName av_name; - if (LLAvatarNameCache::get(getID(), &av_name) && av_name.isDisplayNameDefault()) - { - color_name = "NameTagMatch"; - } - else - { - color_name = "NameTagMismatch"; - } - } - else - { - // ...not using display names - color_name = "NameTagLegacy"; - } - return LLUIColorTable::getInstance()->getColor( color_name ); -} - -void LLVOAvatar::idleUpdateBelowWater() -{ - F32 avatar_height = (F32)(getPositionGlobal().mdV[VZ]); - - F32 water_height; - water_height = getRegion()->getWaterHeight(); - - mBelowWater = avatar_height < water_height; -} - -void LLVOAvatar::slamPosition() -{ - gAgent.setPositionAgent(getPositionAgent()); - // SL-315 - mRoot->setWorldPosition(getPositionAgent()); // teleport - setChanged(TRANSLATED); - if (mDrawable.notNull()) - { - gPipeline.updateMoveNormalAsync(mDrawable); - } - mRoot->updateWorldMatrixChildren(); -} - -bool LLVOAvatar::isVisuallyMuted() -{ - bool muted = false; - - // Priority order (highest priority first) - // * own avatar is never visually muted - // * if on the "always draw normally" list, draw them normally - // * if on the "always visually mute" list, mute them - // * check against the render cost and attachment limits - if (!isSelf()) - { - if (mVisuallyMuteSetting == AV_ALWAYS_RENDER) - { - muted = false; - } - else if (mVisuallyMuteSetting == AV_DO_NOT_RENDER) - { -#ifdef JELLYDOLLS_SHOULD_IMPOSTOR - muted = true; - // Always want to see this AV as an impostor -#else - muted = false; -#endif - } - else if (isInMuteList()) - { - muted = true; - } - else if (mIsControlAvatar) - { - muted = isTooSlow(); - } - else - { - muted = isTooComplex() || isTooSlow(); - } - } - - return muted; -} - -bool LLVOAvatar::isInMuteList() const -{ - bool muted = false; - F64 now = LLFrameTimer::getTotalSeconds(); - if (now < mCachedMuteListUpdateTime) - { - muted = mCachedInMuteList; - } - else - { - muted = LLMuteList::getInstance()->isMuted(getID()); - - const F64 SECONDS_BETWEEN_MUTE_UPDATES = 1; - mCachedMuteListUpdateTime = now + SECONDS_BETWEEN_MUTE_UPDATES; - mCachedInMuteList = muted; - } - return muted; -} - -void LLVOAvatar::updateAppearanceMessageDebugText() -{ - S32 central_bake_version = -1; - if (getRegion()) - { - central_bake_version = getRegion()->getCentralBakeVersion(); - } - bool all_baked_downloaded = allBakedTexturesCompletelyDownloaded(); - bool all_local_downloaded = allLocalTexturesCompletelyDownloaded(); - std::string debug_line = llformat("%s%s - mLocal: %d, mEdit: %d, mUSB: %d, CBV: %d", - isSelf() ? (all_local_downloaded ? "L" : "l") : "-", - all_baked_downloaded ? "B" : "b", - mUseLocalAppearance, mIsEditingAppearance, - 1, central_bake_version); - std::string origin_string = bakedTextureOriginInfo(); - debug_line += " [" + origin_string + "]"; - S32 curr_cof_version = LLAppearanceMgr::instance().getCOFVersion(); - S32 last_request_cof_version = mLastUpdateRequestCOFVersion; - S32 last_received_cof_version = mLastUpdateReceivedCOFVersion; - if (isSelf()) - { - debug_line += llformat(" - cof: %d req: %d rcv:%d", - curr_cof_version, last_request_cof_version, last_received_cof_version); - static LLCachedControl debug_force_failure(gSavedSettings, "DebugForceAppearanceRequestFailure"); - if (debug_force_failure) - { - debug_line += " FORCING ERRS"; - } - } - else - { - debug_line += llformat(" - cof rcv:%d", last_received_cof_version); - } - debug_line += llformat(" bsz-z: %.3f", mBodySize[2]); - if (mAvatarOffset[2] != 0.0f) - { - debug_line += llformat("avofs-z: %.3f", mAvatarOffset[2]); - } - bool hover_enabled = getRegion() && getRegion()->avatarHoverHeightEnabled(); - debug_line += hover_enabled ? " H" : " h"; - const LLVector3& hover_offset = getHoverOffset(); - if (hover_offset[2] != 0.0) - { - debug_line += llformat(" hov_z: %.3f", hover_offset[2]); - debug_line += llformat(" %s", (isSitting() ? "S" : "T")); - debug_line += llformat("%s", (isMotionActive(ANIM_AGENT_SIT_GROUND_CONSTRAINED) ? "G" : "-")); - } - if (mInAir) - { - debug_line += " A"; - - } - - LLVector3 ankle_right_pos_agent = mFootRightp->getWorldPosition(); - LLVector3 normal; - LLVector3 ankle_right_ground_agent = ankle_right_pos_agent; - resolveHeightAgent(ankle_right_pos_agent, ankle_right_ground_agent, normal); - F32 rightElev = llmax(-0.2f, ankle_right_pos_agent.mV[VZ] - ankle_right_ground_agent.mV[VZ]); - debug_line += llformat(" relev %.3f", rightElev); - - LLVector3 root_pos = mRoot->getPosition(); - LLVector3 pelvis_pos = mPelvisp->getPosition(); - debug_line += llformat(" rp %.3f pp %.3f", root_pos[2], pelvis_pos[2]); - - const LLVector3& scale = getScale(); - debug_line += llformat(" scale-z %.3f", scale[2]); - S32 is_visible = (S32) isVisible(); - S32 is_m_visible = (S32) mVisible; - debug_line += llformat(" v %d/%d", is_visible, is_m_visible); - - AvatarOverallAppearance aoa = getOverallAppearance(); - if (aoa == AOA_NORMAL) - { - debug_line += " N"; - } - else if (aoa == AOA_JELLYDOLL) - { - debug_line += " J"; - } - else - { - debug_line += " I"; - } - - if (mMeshValid) - { - debug_line += "m"; - } - else - { - debug_line += "-"; - } - if (isImpostor()) - { - debug_line += " Imp" + llformat("%d[%d]:%.1f", mUpdatePeriod, mLastImpostorUpdateReason, ((F32)(gFrameTimeSeconds-mLastImpostorUpdateFrameTime))); - } - - addDebugText(debug_line); -} - -LLViewerInventoryItem* getObjectInventoryItem(LLViewerObject *vobj, LLUUID asset_id) -{ - LLViewerInventoryItem *item = NULL; - - if (vobj) - { - if (vobj->getInventorySerial()<=0) - { - vobj->requestInventory(); - } - item = vobj->getInventoryItemByAsset(asset_id); - } - return item; -} - -LLViewerInventoryItem* recursiveGetObjectInventoryItem(LLViewerObject *vobj, LLUUID asset_id) -{ - LLViewerInventoryItem *item = getObjectInventoryItem(vobj, asset_id); - if (!item) - { - LLViewerObject::const_child_list_t& children = vobj->getChildren(); - for (LLViewerObject::const_child_list_t::const_iterator it = children.begin(); - it != children.end(); ++it) - { - LLViewerObject *childp = *it; - item = getObjectInventoryItem(childp, asset_id); - if (item) - { - break; - } - } - } - return item; -} - -void LLVOAvatar::updateAnimationDebugText() -{ - for (LLMotionController::motion_list_t::iterator iter = mMotionController.getActiveMotions().begin(); - iter != mMotionController.getActiveMotions().end(); ++iter) - { - LLMotion* motionp = *iter; - if (motionp->getMinPixelArea() < getPixelArea()) - { - std::string output; - std::string motion_name = motionp->getName(); - if (motion_name.empty()) - { - if (isControlAvatar()) - { - LLControlAvatar *control_av = dynamic_cast(this); - // Try to get name from inventory of associated object - LLVOVolume *volp = control_av->mRootVolp; - LLViewerInventoryItem *item = recursiveGetObjectInventoryItem(volp,motionp->getID()); - if (item) - { - motion_name = item->getName(); - } - } - } - if (motion_name.empty()) - { - std::string name; - if (gAgent.isGodlikeWithoutAdminMenuFakery() || isSelf()) - { - name = motionp->getID().asString(); - LLVOAvatar::AnimSourceIterator anim_it = mAnimationSources.begin(); - for (; anim_it != mAnimationSources.end(); ++anim_it) - { - if (anim_it->second == motionp->getID()) - { - LLViewerObject* object = gObjectList.findObject(anim_it->first); - if (!object) - { - break; - } - if (object->isAvatar()) - { - if (mMotionController.mIsSelf) - { - // Searching inventory by asset id is really long - // so just mark as inventory - // Also item is likely to be named by LLPreviewAnim - name += "(inventory)"; - } - } - else - { - LLViewerInventoryItem* item = NULL; - if (!object->isInventoryDirty()) - { - item = object->getInventoryItemByAsset(motionp->getID()); - } - if (item) - { - name = item->getName(); - } - else if (object->isAttachment()) - { - name += "(att:" + getAttachmentItemName() + ")"; - } - else - { - // in-world object, name or content unknown - name += "(in-world)"; - } - } - break; - } - } - } - else - { - name = LLUUID::null.asString(); - } - motion_name = name; - } - std::string motion_tag = ""; - if (mPlayingAnimations.find(motionp->getID()) != mPlayingAnimations.end()) - { - motion_tag = "*"; - } - output = llformat("%s%s - %d", - motion_name.c_str(), - motion_tag.c_str(), - (U32)motionp->getPriority()); - addDebugText(output); - } - } -} - -void LLVOAvatar::updateDebugText() -{ - // Leave mDebugText uncleared here, in case a derived class has added some state first - - if (gSavedSettings.getBOOL("DebugAvatarAppearanceMessage")) - { - updateAppearanceMessageDebugText(); - } - - if (gSavedSettings.getBOOL("DebugAvatarCompositeBaked")) - { - if (!mBakedTextureDebugText.empty()) - addDebugText(mBakedTextureDebugText); - } - - // Develop -> Avatar -> Animation Info - if (LLVOAvatar::sShowAnimationDebug) - { - updateAnimationDebugText(); - } - - if (!mDebugText.size() && mText.notNull()) - { - mText->markDead(); - mText = NULL; - } - else if (mDebugText.size()) - { - setDebugText(mDebugText); - } - mDebugText.clear(); -} - -//------------------------------------------------------------------------ -// updateFootstepSounds -// Factored out from updateCharacter() -// Generate footstep sounds when feet hit the ground -//------------------------------------------------------------------------ -void LLVOAvatar::updateFootstepSounds() -{ - if (mIsDummy) - { - return; - } - - //------------------------------------------------------------------------- - // Find the ground under each foot, these are used for a variety - // of things that follow - //------------------------------------------------------------------------- - LLVector3 ankle_left_pos_agent = mFootLeftp->getWorldPosition(); - LLVector3 ankle_right_pos_agent = mFootRightp->getWorldPosition(); - - LLVector3 ankle_left_ground_agent = ankle_left_pos_agent; - LLVector3 ankle_right_ground_agent = ankle_right_pos_agent; - LLVector3 normal; - resolveHeightAgent(ankle_left_pos_agent, ankle_left_ground_agent, normal); - resolveHeightAgent(ankle_right_pos_agent, ankle_right_ground_agent, normal); - - F32 leftElev = llmax(-0.2f, ankle_left_pos_agent.mV[VZ] - ankle_left_ground_agent.mV[VZ]); - F32 rightElev = llmax(-0.2f, ankle_right_pos_agent.mV[VZ] - ankle_right_ground_agent.mV[VZ]); - - if (!isSitting()) - { - //------------------------------------------------------------------------- - // Figure out which foot is on ground - //------------------------------------------------------------------------- - if (!mInAir) - { - if ((leftElev < 0.0f) || (rightElev < 0.0f)) - { - ankle_left_pos_agent = mFootLeftp->getWorldPosition(); - ankle_right_pos_agent = mFootRightp->getWorldPosition(); - leftElev = ankle_left_pos_agent.mV[VZ] - ankle_left_ground_agent.mV[VZ]; - rightElev = ankle_right_pos_agent.mV[VZ] - ankle_right_ground_agent.mV[VZ]; - } - } - } - - const LLUUID AGENT_FOOTSTEP_ANIMS[] = {ANIM_AGENT_WALK, ANIM_AGENT_RUN, ANIM_AGENT_LAND}; - const S32 NUM_AGENT_FOOTSTEP_ANIMS = LL_ARRAY_SIZE(AGENT_FOOTSTEP_ANIMS); - - if ( gAudiop && isAnyAnimationSignaled(AGENT_FOOTSTEP_ANIMS, NUM_AGENT_FOOTSTEP_ANIMS) ) - { - bool playSound = false; - LLVector3 foot_pos_agent; - - bool onGroundLeft = (leftElev <= 0.05f); - bool onGroundRight = (rightElev <= 0.05f); - - // did left foot hit the ground? - if ( onGroundLeft && !mWasOnGroundLeft ) - { - foot_pos_agent = ankle_left_pos_agent; - playSound = true; - } - - // did right foot hit the ground? - if ( onGroundRight && !mWasOnGroundRight ) - { - foot_pos_agent = ankle_right_pos_agent; - playSound = true; - } - - mWasOnGroundLeft = onGroundLeft; - mWasOnGroundRight = onGroundRight; - - if ( playSound ) - { - const F32 STEP_VOLUME = 0.1f; - const LLUUID& step_sound_id = getStepSound(); - - LLVector3d foot_pos_global = gAgent.getPosGlobalFromAgent(foot_pos_agent); - - if (LLViewerParcelMgr::getInstance()->canHearSound(foot_pos_global) - && !LLMuteList::getInstance()->isMuted(getID(), LLMute::flagObjectSounds)) - { - gAudiop->triggerSound(step_sound_id, getID(), STEP_VOLUME, LLAudioEngine::AUDIO_TYPE_AMBIENT, foot_pos_global); - } - } - } -} - -//------------------------------------------------------------------------ -// computeUpdatePeriod() -// Factored out from updateCharacter() -// Set new value for mUpdatePeriod based on distance and various other factors. -// -// Note 10-2020: it turns out that none of these update period -// calculations have been having any effect, because -// mNeedsImpostorUpdate was not being set in updateCharacter(). So -// it's really open to question whether we want to enable time based updates, and if -// so, at what rate. Leaving the rates as given would lead to -// drastically more frequent impostor updates than we've been doing all these years. -// ------------------------------------------------------------------------ -void LLVOAvatar::computeUpdatePeriod() -{ - bool visually_muted = isVisuallyMuted(); - if (mDrawable.notNull() - && isVisible() - && (!isSelf() || visually_muted) - && !isUIAvatar() - && (sLimitNonImpostors || visually_muted) - && !mNeedsAnimUpdate) - { - const LLVector4a* ext = mDrawable->getSpatialExtents(); - LLVector4a size; - size.setSub(ext[1],ext[0]); - F32 mag = size.getLength3().getF32()*0.5f; - - const S32 UPDATE_RATE_SLOW = 64; - const S32 UPDATE_RATE_MED = 48; - const S32 UPDATE_RATE_FAST = 32; - - if (visually_muted) - { // visually muted avatars update at lowest rate - mUpdatePeriod = UPDATE_RATE_SLOW; - } - else if (! shouldImpostor() - || mDrawable->mDistanceWRTCamera < 1.f + mag) - { // first 25% of max visible avatars are not impostored - // also, don't impostor avatars whose bounding box may be penetrating the - // impostor camera near clip plane - mUpdatePeriod = 1; - } - else if ( shouldImpostor(4.0) ) - { //background avatars are REALLY slow updating impostors - mUpdatePeriod = UPDATE_RATE_SLOW; - } - else if (mLastRezzedStatus <= 0) - { - // Don't update cloud avatars too often - mUpdatePeriod = UPDATE_RATE_SLOW; - } - else if ( shouldImpostor(3.0) ) - { //back 25% of max visible avatars are slow updating impostors - mUpdatePeriod = UPDATE_RATE_MED; - } - else - { - //nearby avatars, update the impostors more frequently. - mUpdatePeriod = UPDATE_RATE_FAST; - } - } - else - { - mUpdatePeriod = 1; - } -} - -//------------------------------------------------------------------------ -// updateOrientation() -// Factored out from updateCharacter() -// This is used by updateCharacter() to update the avatar's orientation: -// - updates mTurning state -// - updates rotation of the mRoot joint in the skeleton -// - for self, calls setControlFlags() to notify the simulator about any turns -//------------------------------------------------------------------------ -void LLVOAvatar::updateOrientation(LLAgent& agent, F32 speed, F32 delta_time) -{ - LLQuaternion iQ; - LLVector3 upDir( 0.0f, 0.0f, 1.0f ); - - // Compute a forward direction vector derived from the primitive rotation - // and the velocity vector. When walking or jumping, don't let body deviate - // more than 90 from the view, if necessary, flip the velocity vector. - - LLVector3 primDir; - if (isSelf()) - { - primDir = agent.getAtAxis() - projected_vec(agent.getAtAxis(), agent.getReferenceUpVector()); - primDir.normalize(); - } - else - { - primDir = getRotation().getMatrix3().getFwdRow(); - } - LLVector3 velDir = getVelocity(); - velDir.normalize(); - if ( mSignaledAnimations.find(ANIM_AGENT_WALK) != mSignaledAnimations.end()) - { - F32 vpD = velDir * primDir; - if (vpD < -0.5f) - { - velDir *= -1.0f; - } - } - LLVector3 fwdDir = lerp(primDir, velDir, clamp_rescale(speed, 0.5f, 2.0f, 0.0f, 1.0f)); - if (isSelf() && gAgentCamera.cameraMouselook()) - { - // make sure fwdDir stays in same general direction as primdir - if (gAgent.getFlying()) - { - fwdDir = LLViewerCamera::getInstance()->getAtAxis(); - } - else - { - LLVector3 at_axis = LLViewerCamera::getInstance()->getAtAxis(); - LLVector3 up_vector = gAgent.getReferenceUpVector(); - at_axis -= up_vector * (at_axis * up_vector); - at_axis.normalize(); - - F32 dot = fwdDir * at_axis; - if (dot < 0.f) - { - fwdDir -= 2.f * at_axis * dot; - fwdDir.normalize(); - } - } - } - - LLQuaternion root_rotation = mRoot->getWorldMatrix().quaternion(); - F32 root_roll, root_pitch, root_yaw; - root_rotation.getEulerAngles(&root_roll, &root_pitch, &root_yaw); - - // When moving very slow, the pelvis is allowed to deviate from the - // forward direction to allow it to hold its position while the torso - // and head turn. Once in motion, it must conform however. - bool self_in_mouselook = isSelf() && gAgentCamera.cameraMouselook(); - - LLVector3 pelvisDir( mRoot->getWorldMatrix().getFwdRow4().mV ); - - const F32 AVATAR_PELVIS_ROTATE_THRESHOLD_SLOW = 60.0f; - const F32 AVATAR_PELVIS_ROTATE_THRESHOLD_FAST = 2.0f; - - F32 pelvis_rot_threshold = clamp_rescale(speed, 0.1f, 1.0f, AVATAR_PELVIS_ROTATE_THRESHOLD_SLOW, AVATAR_PELVIS_ROTATE_THRESHOLD_FAST); - - if (self_in_mouselook) - { - pelvis_rot_threshold *= MOUSELOOK_PELVIS_FOLLOW_FACTOR; - } - pelvis_rot_threshold *= DEG_TO_RAD; - - F32 angle = angle_between( pelvisDir, fwdDir ); - - // The avatar's root is allowed to have a yaw that deviates widely - // from the forward direction, but if roll or pitch are off even - // a little bit we need to correct the rotation. - if(root_roll < 1.f * DEG_TO_RAD - && root_pitch < 5.f * DEG_TO_RAD) - { - // smaller correction vector means pelvis follows prim direction more closely - if (!mTurning && angle > pelvis_rot_threshold*0.75f) - { - mTurning = true; - } - - // use tighter threshold when turning - if (mTurning) - { - pelvis_rot_threshold *= 0.4f; - // account for fps, assume that above value is for ~60fps - constexpr F32 default_frame_sec = 0.016f; - F32 prev_frame_sec = LLFrameTimer::getFrameDeltaTimeF32(); - if (default_frame_sec > prev_frame_sec) - { - // reduce threshold since turn rate per second is constant, - // shorter frame means shorter turn. - pelvis_rot_threshold *= prev_frame_sec/default_frame_sec; - } - } - - // am I done turning? - if (angle < pelvis_rot_threshold) - { - mTurning = false; - } - - LLVector3 correction_vector = (pelvisDir - fwdDir) * clamp_rescale(angle, pelvis_rot_threshold*0.75f, pelvis_rot_threshold, 1.0f, 0.0f); - fwdDir += correction_vector; - } - else - { - mTurning = false; - } - - // Now compute the full world space rotation for the whole body (wQv) - LLVector3 leftDir = upDir % fwdDir; - leftDir.normalize(); - fwdDir = leftDir % upDir; - LLQuaternion wQv( fwdDir, leftDir, upDir ); - - if (isSelf() && mTurning) - { - if ((fwdDir % pelvisDir) * upDir > 0.f) - { - gAgent.setControlFlags(AGENT_CONTROL_TURN_RIGHT); - } - else - { - gAgent.setControlFlags(AGENT_CONTROL_TURN_LEFT); - } - } - - // Set the root rotation, but do so incrementally so that it - // lags in time by some fixed amount. - //F32 u = LLSmoothInterpolation::getInterpolant(PELVIS_LAG); - F32 pelvis_lag_time = 0.f; - if (self_in_mouselook) - { - pelvis_lag_time = PELVIS_LAG_MOUSELOOK; - } - else if (mInAir) - { - pelvis_lag_time = PELVIS_LAG_FLYING; - // increase pelvis lag time when moving slowly - pelvis_lag_time *= clamp_rescale(mSpeedAccum, 0.f, 15.f, 3.f, 1.f); - } - else - { - pelvis_lag_time = PELVIS_LAG_WALKING; - } - - F32 u = llclamp((delta_time / pelvis_lag_time), 0.0f, 1.0f); - - mRoot->setWorldRotation( slerp(u, mRoot->getWorldRotation(), wQv) ); -} - -//------------------------------------------------------------------------ -// updateTimeStep() -// Factored out from updateCharacter(). -// -// Updates the time step used by the motion controller, based on area -// and avatar count criteria. This will also stop the -// ANIM_AGENT_WALK_ADJUST animation under some circumstances. -// ------------------------------------------------------------------------ -void LLVOAvatar::updateTimeStep() -{ - if (!isSelf() && !isUIAvatar()) // ie, non-self avatars, and animated objects will be affected. - { - // Note that sInstances counts animated objects and - // standard avatars in the same bucket. Is this desirable? - F32 time_quantum = clamp_rescale((F32)sInstances.size(), 10.f, 35.f, 0.f, 0.25f); - F32 pixel_area_scale = clamp_rescale(mPixelArea, 100, 5000, 1.f, 0.f); - F32 time_step = time_quantum * pixel_area_scale; - // Extrema: - // If number of avs is 10 or less, time_step is unmodified (flagged with 0.0). - // If area of av is 5000 or greater, time_step is unmodified (flagged with 0.0). - // If number of avs is 35 or greater, and area of av is 100 or less, - // time_step takes the maximum possible value of 0.25. - // Other situations will give values within the (0, 0.25) range. - if (time_step != 0.f) - { - // disable walk motion servo controller as it doesn't work with motion timesteps - stopMotion(ANIM_AGENT_WALK_ADJUST); - removeAnimationData("Walk Speed"); - } - // See SL-763 - playback with altered time step does not - // appear to work correctly, odd behavior for distant avatars. - // As of 11-2017, LLMotionController::updateMotions() will - // ignore the value here. Need to re-enable if it's every - // fixed. - mMotionController.setTimeStep(time_step); - } -} - -void LLVOAvatar::updateRootPositionAndRotation(LLAgent& agent, F32 speed, bool was_sit_ground_constrained) -{ - if (!(isSitting() && getParent())) - { - // This case includes all configurations except sitting on an - // object, so does include ground sit. - - //-------------------------------------------------------------------- - // get timing info - // handle initial condition case - //-------------------------------------------------------------------- - F32 animation_time = mAnimTimer.getElapsedTimeF32(); - if (mTimeLast == 0.0f) - { - mTimeLast = animation_time; - - // Initially put the pelvis at slaved position/mRotation - // SL-315 - mRoot->setWorldPosition( getPositionAgent() ); // first frame - mRoot->setWorldRotation( getRotation() ); - } - - //-------------------------------------------------------------------- - // dont' let dT get larger than 1/5th of a second - //-------------------------------------------------------------------- - F32 delta_time = animation_time - mTimeLast; - - delta_time = llclamp( delta_time, DELTA_TIME_MIN, DELTA_TIME_MAX ); - mTimeLast = animation_time; - - mSpeedAccum = (mSpeedAccum * 0.95f) + (speed * 0.05f); - - //-------------------------------------------------------------------- - // compute the position of the avatar's root - //-------------------------------------------------------------------- - LLVector3d root_pos; - LLVector3d ground_under_pelvis; - - if (isSelf()) - { - gAgent.setPositionAgent(getRenderPosition()); - } - - root_pos = gAgent.getPosGlobalFromAgent(getRenderPosition()); - root_pos.mdV[VZ] += getVisualParamWeight(AVATAR_HOVER); - - LLVector3 normal; - resolveHeightGlobal(root_pos, ground_under_pelvis, normal); - F32 foot_to_ground = (F32) (root_pos.mdV[VZ] - mPelvisToFoot - ground_under_pelvis.mdV[VZ]); - bool in_air = ((!LLWorld::getInstance()->getRegionFromPosGlobal(ground_under_pelvis)) || - foot_to_ground > FOOT_GROUND_COLLISION_TOLERANCE); - - if (in_air && !mInAir) - { - mTimeInAir.reset(); - } - mInAir = in_air; - - // SL-402: with the ability to animate the position of joints - // that affect the body size calculation, computed body size - // can get stale much more easily. Simplest fix is to update - // it frequently. - // SL-427: this appears to be too frequent, moving to only do on animation state change. - //computeBodySize(); - - // correct for the fact that the pelvis is not necessarily the center - // of the agent's physical representation - root_pos.mdV[VZ] -= (0.5f * mBodySize.mV[VZ]) - mPelvisToFoot; - if (!isSitting() && !was_sit_ground_constrained) - { - root_pos += LLVector3d(getHoverOffset()); - if (getOverallAppearance() == AOA_JELLYDOLL) - { - F32 offz = -0.5 * (getScale()[VZ] - mBodySize.mV[VZ]); - root_pos[2] += offz; - // if (!isSelf() && !isControlAvatar()) - // { - // LL_DEBUGS("Avatar") << "av " << getFullname() - // << " frame " << LLFrameTimer::getFrameCount() - // << " root adjust offz " << offz - // << " scalez " << getScale()[VZ] - // << " bsz " << mBodySize.mV[VZ] - // << LL_ENDL; - // } - } - } - // if (!isSelf() && !isControlAvatar()) - // { - // LL_DEBUGS("Avatar") << "av " << getFullname() << " aoa " << (S32) getOverallAppearance() - // << " frame " << LLFrameTimer::getFrameCount() - // << " scalez " << getScale()[VZ] - // << " bsz " << mBodySize.mV[VZ] - // << " root pos " << root_pos[2] - // << " curr rootz " << mRoot->getPosition()[2] - // << " pp-z " << mPelvisp->getPosition()[2] - // << " renderpos " << getRenderPosition() - // << LL_ENDL; - // } - - LLControlAvatar *cav = dynamic_cast(this); - if (cav) - { - // SL-1350: Moved to LLDrawable::updateXform() - cav->matchVolumeTransform(); - } - else - { - LLVector3 newPosition = gAgent.getPosAgentFromGlobal(root_pos); - // if (!isSelf() && !isControlAvatar()) - // { - // LL_DEBUGS("Avatar") << "av " << getFullname() - // << " frame " << LLFrameTimer::getFrameCount() - // << " newPosition " << newPosition - // << " renderpos " << getRenderPosition() - // << LL_ENDL; - // } - if (newPosition != mRoot->getXform()->getWorldPosition()) - { - mRoot->touch(); - // SL-315 - mRoot->setWorldPosition( newPosition ); // regular update - } - } - - //-------------------------------------------------------------------- - // Propagate viewer object rotation to root of avatar - //-------------------------------------------------------------------- - if (!isControlAvatar() && !isAnyAnimationSignaled(AGENT_NO_ROTATE_ANIMS, NUM_AGENT_NO_ROTATE_ANIMS)) - { - // Rotation fixups for avatars in motion. - // Skip for animated objects. - updateOrientation(agent, speed, delta_time); - } - } - else if (mDrawable.notNull()) - { - // Sitting on an object - mRoot is slaved to mDrawable orientation. - LLVector3 pos = mDrawable->getPosition(); - pos += getHoverOffset() * mDrawable->getRotation(); - // SL-315 - mRoot->setPosition(pos); - mRoot->setRotation(mDrawable->getRotation()); - } -} - -//------------------------------------------------------------------------ -// LLVOAvatar::computeNeedsUpdate() -// -// Most of the logic here is to figure out when to periodically update impostors. -// Non-impostors have mUpdatePeriod == 1 and will need update every frame. -//------------------------------------------------------------------------ -bool LLVOAvatar::computeNeedsUpdate() -{ - const F32 MAX_IMPOSTOR_INTERVAL = 4.0f; - computeUpdatePeriod(); - - bool needs_update_by_frame_count = ((LLDrawable::getCurrentFrame()+mID.mData[0])%mUpdatePeriod == 0); - - bool needs_update_by_max_time = ((gFrameTimeSeconds-mLastImpostorUpdateFrameTime)> MAX_IMPOSTOR_INTERVAL); - bool needs_update = needs_update_by_frame_count || needs_update_by_max_time; - - if (needs_update && !isSelf()) - { - if (needs_update_by_max_time) - { - mNeedsImpostorUpdate = true; - mLastImpostorUpdateReason = 11; - } - else - { - //mNeedsImpostorUpdate = true; - //mLastImpostorUpdateReason = 10; - } - } - return needs_update; -} - -// updateCharacter() -// -// This is called for all avatars, so there are 4 possible situations: -// -// 1) Avatar is your own. In this case the class is LLVOAvatarSelf, -// isSelf() is true, and agent specifies the corresponding agent -// information for you. In all the other cases, agent is irrelevant -// and it would be less confusing if it were null or something. -// -// 2) Avatar is controlled by another resident. Class is LLVOAvatar, -// and isSelf() is false. -// -// 3) Avatar is the controller for an animated object. Class is -// LLControlAvatar and mIsDummy is true. Avatar is a purely -// viewer-side entity with no representation on the simulator. -// -// 4) Avatar is a UI avatar used in some areas of the UI, such as when -// previewing uploaded animations. Class is LLUIAvatar, and mIsDummy -// is true. Avatar is purely viewer-side with no representation on the -// simulator. -// -//------------------------------------------------------------------------ -bool LLVOAvatar::updateCharacter(LLAgent &agent) -{ - updateDebugText(); - - if (!mIsBuilt) - { - return false; - } - - bool visible = isVisible(); - bool is_control_avatar = isControlAvatar(); // capture state to simplify tracing - bool is_attachment = false; - - if (is_control_avatar) - { - LLControlAvatar *cav = dynamic_cast(this); - is_attachment = cav && cav->mRootVolp && cav->mRootVolp->isAttachment(); // For attached animated objects - } - - LLScopedContextString str("updateCharacter " + getFullname() + " is_control_avatar " - + boost::lexical_cast(is_control_avatar) - + " is_attachment " + boost::lexical_cast(is_attachment)); - - // For fading out the names above heads, only let the timer - // run if we're visible. - if (mDrawable.notNull() && !visible) - { - mTimeVisible.reset(); - } - - //-------------------------------------------------------------------- - // The rest should only be done occasionally for far away avatars. - // Set mUpdatePeriod and visible based on distance and other criteria, - // and flag for impostor update if needed. - //-------------------------------------------------------------------- - bool needs_update = computeNeedsUpdate(); - - //-------------------------------------------------------------------- - // Early out if does not need update and not self - // don't early out for your own avatar, as we rely on your animations playing reliably - // for example, the "turn around" animation when entering customize avatar needs to trigger - // even when your avatar is offscreen - //-------------------------------------------------------------------- - if (!needs_update && !isSelf()) - { - updateMotions(LLCharacter::HIDDEN_UPDATE); - return false; - } - - //-------------------------------------------------------------------- - // Handle transitions between regular rendering, jellydoll, or invisible. - // Can trigger skeleton reset or animation changes - //-------------------------------------------------------------------- - updateOverallAppearance(); - - //-------------------------------------------------------------------- - // change animation time quanta based on avatar render load - //-------------------------------------------------------------------- - // SL-763 the time step quantization does not currently work. - //updateTimeStep(); - - //-------------------------------------------------------------------- - // Update sitting state based on parent and active animation info. - //-------------------------------------------------------------------- - if (getParent() && !isSitting()) - { - sitOnObject((LLViewerObject*)getParent()); - } - else if (!getParent() && isSitting() && !isMotionActive(ANIM_AGENT_SIT_GROUND_CONSTRAINED)) - { - // If we are starting up, motion might be loading - LLMotion *motionp = mMotionController.findMotion(ANIM_AGENT_SIT_GROUND_CONSTRAINED); - if (!motionp || !mMotionController.isMotionLoading(motionp)) - { - getOffObject(); - } - } - - //-------------------------------------------------------------------- - // create local variables in world coords for region position values - //-------------------------------------------------------------------- - LLVector3 xyVel = getVelocity(); - xyVel.mV[VZ] = 0.0f; - F32 speed = xyVel.length(); - // remembering the value here prevents a display glitch if the - // animation gets toggled during this update. - bool was_sit_ground_constrained = isMotionActive(ANIM_AGENT_SIT_GROUND_CONSTRAINED); - - //-------------------------------------------------------------------- - // This does a bunch of state updating, including figuring out - // whether av is in the air, setting mRoot position and rotation - // In some cases, calls updateOrientation() for a lot of the - // work - // -------------------------------------------------------------------- - updateRootPositionAndRotation(agent, speed, was_sit_ground_constrained); - - //------------------------------------------------------------------------- - // Update character motions - //------------------------------------------------------------------------- - // store data relevant to motions - mSpeed = speed; - - // update animations - if (!visible) - { - updateMotions(LLCharacter::HIDDEN_UPDATE); - } - else if (mSpecialRenderMode == 1) // Animation Preview - { - updateMotions(LLCharacter::FORCE_UPDATE); - } - else - { - // Might be better to do HIDDEN_UPDATE if cloud - updateMotions(LLCharacter::NORMAL_UPDATE); - } - - // Special handling for sitting on ground. - if (!getParent() && (isSitting() || was_sit_ground_constrained)) - { - - F32 off_z = LLVector3d(getHoverOffset()).mdV[VZ]; - if (off_z != 0.0) - { - LLVector3 pos = mRoot->getWorldPosition(); - pos.mV[VZ] += off_z; - mRoot->touch(); - // SL-315 - mRoot->setWorldPosition(pos); - } - } - - // update head position - updateHeadOffset(); - - // Generate footstep sounds when feet hit the ground - updateFootstepSounds(); - - // Update child joints as needed. - mRoot->updateWorldMatrixChildren(); - - if (visible) - { - // System avatar mesh vertices need to be reskinned. - mNeedsSkin = true; - } - - return visible; -} - -//----------------------------------------------------------------------------- -// updateHeadOffset() -//----------------------------------------------------------------------------- -void LLVOAvatar::updateHeadOffset() -{ - // since we only care about Z, just grab one of the eyes - LLVector3 midEyePt = mEyeLeftp->getWorldPosition(); - midEyePt -= mDrawable.notNull() ? mDrawable->getWorldPosition() : mRoot->getWorldPosition(); - midEyePt.mV[VZ] = llmax(-mPelvisToFoot + LLViewerCamera::getInstance()->getNear(), midEyePt.mV[VZ]); - - if (mDrawable.notNull()) - { - midEyePt = midEyePt * ~mDrawable->getWorldRotation(); - } - if (isSitting()) - { - mHeadOffset = midEyePt; - } - else - { - F32 u = llmax(0.f, HEAD_MOVEMENT_AVG_TIME - (1.f / gFPSClamped)); - mHeadOffset = lerp(midEyePt, mHeadOffset, u); - } -} - -void LLVOAvatar::debugBodySize() const -{ - LLVector3 pelvis_scale = mPelvisp->getScale(); - - // some of the joints have not been cached - LLVector3 skull = mSkullp->getPosition(); - LL_DEBUGS("Avatar") << "skull pos " << skull << LL_ENDL; - //LLVector3 skull_scale = mSkullp->getScale(); - - LLVector3 neck = mNeckp->getPosition(); - LLVector3 neck_scale = mNeckp->getScale(); - LL_DEBUGS("Avatar") << "neck pos " << neck << " neck_scale " << neck_scale << LL_ENDL; - - LLVector3 chest = mChestp->getPosition(); - LLVector3 chest_scale = mChestp->getScale(); - LL_DEBUGS("Avatar") << "chest pos " << chest << " chest_scale " << chest_scale << LL_ENDL; - - // the rest of the joints have been cached - LLVector3 head = mHeadp->getPosition(); - LLVector3 head_scale = mHeadp->getScale(); - LL_DEBUGS("Avatar") << "head pos " << head << " head_scale " << head_scale << LL_ENDL; - - LLVector3 torso = mTorsop->getPosition(); - LLVector3 torso_scale = mTorsop->getScale(); - LL_DEBUGS("Avatar") << "torso pos " << torso << " torso_scale " << torso_scale << LL_ENDL; - - LLVector3 hip = mHipLeftp->getPosition(); - LLVector3 hip_scale = mHipLeftp->getScale(); - LL_DEBUGS("Avatar") << "hip pos " << hip << " hip_scale " << hip_scale << LL_ENDL; - - LLVector3 knee = mKneeLeftp->getPosition(); - LLVector3 knee_scale = mKneeLeftp->getScale(); - LL_DEBUGS("Avatar") << "knee pos " << knee << " knee_scale " << knee_scale << LL_ENDL; - - LLVector3 ankle = mAnkleLeftp->getPosition(); - LLVector3 ankle_scale = mAnkleLeftp->getScale(); - LL_DEBUGS("Avatar") << "ankle pos " << ankle << " ankle_scale " << ankle_scale << LL_ENDL; - - LLVector3 foot = mFootLeftp->getPosition(); - LL_DEBUGS("Avatar") << "foot pos " << foot << LL_ENDL; - - F32 new_offset = (const_cast(this))->getVisualParamWeight(AVATAR_HOVER); - LL_DEBUGS("Avatar") << "new_offset " << new_offset << LL_ENDL; - - F32 new_pelvis_to_foot = hip.mV[VZ] * pelvis_scale.mV[VZ] - - knee.mV[VZ] * hip_scale.mV[VZ] - - ankle.mV[VZ] * knee_scale.mV[VZ] - - foot.mV[VZ] * ankle_scale.mV[VZ]; - LL_DEBUGS("Avatar") << "new_pelvis_to_foot " << new_pelvis_to_foot << LL_ENDL; - - LLVector3 new_body_size; - new_body_size.mV[VZ] = new_pelvis_to_foot + - // the sqrt(2) correction below is an approximate - // correction to get to the top of the head - F_SQRT2 * (skull.mV[VZ] * head_scale.mV[VZ]) + - head.mV[VZ] * neck_scale.mV[VZ] + - neck.mV[VZ] * chest_scale.mV[VZ] + - chest.mV[VZ] * torso_scale.mV[VZ] + - torso.mV[VZ] * pelvis_scale.mV[VZ]; - - // TODO -- measure the real depth and width - new_body_size.mV[VX] = DEFAULT_AGENT_DEPTH; - new_body_size.mV[VY] = DEFAULT_AGENT_WIDTH; - - LL_DEBUGS("Avatar") << "new_body_size " << new_body_size << LL_ENDL; -} - -//------------------------------------------------------------------------ -// postPelvisSetRecalc -//------------------------------------------------------------------------ -void LLVOAvatar::postPelvisSetRecalc() -{ - mRoot->updateWorldMatrixChildren(); - computeBodySize(); - dirtyMesh(2); -} -//------------------------------------------------------------------------ -// updateVisibility() -//------------------------------------------------------------------------ -void LLVOAvatar::updateVisibility() -{ - bool visible = false; - - if (mIsDummy) - { - visible = false; - } - else if (mDrawable.isNull()) - { - visible = false; - } - else - { - if (!mDrawable->getSpatialGroup() || mDrawable->getSpatialGroup()->isVisible()) - { - visible = true; - } - else - { - visible = false; - } - - if(isSelf()) - { - if (!gAgentWearables.areWearablesLoaded()) - { - visible = false; - } - } - else if( !mFirstAppearanceMessageReceived ) - { - visible = false; - } - - if (sDebugInvisible) - { - LLNameValue* firstname = getNVPair("FirstName"); - if (firstname) - { - LL_DEBUGS("Avatar") << avString() << " updating visibility" << LL_ENDL; - } - else - { - LL_INFOS() << "Avatar " << this << " updating visiblity" << LL_ENDL; - } - - if (visible) - { - LL_INFOS() << "Visible" << LL_ENDL; - } - else - { - LL_INFOS() << "Not visible" << LL_ENDL; - } - - /*if (avatar_in_frustum) - { - LL_INFOS() << "Avatar in frustum" << LL_ENDL; - } - else - { - LL_INFOS() << "Avatar not in frustum" << LL_ENDL; - }*/ - - /*if (LLViewerCamera::getInstance()->sphereInFrustum(sel_pos_agent, 2.0f)) - { - LL_INFOS() << "Sel pos visible" << LL_ENDL; - } - if (LLViewerCamera::getInstance()->sphereInFrustum(wrist_right_pos_agent, 0.2f)) - { - LL_INFOS() << "Wrist pos visible" << LL_ENDL; - } - if (LLViewerCamera::getInstance()->sphereInFrustum(getPositionAgent(), getMaxScale()*2.f)) - { - LL_INFOS() << "Agent visible" << LL_ENDL; - }*/ - LL_INFOS() << "PA: " << getPositionAgent() << LL_ENDL; - /*LL_INFOS() << "SPA: " << sel_pos_agent << LL_ENDL; - LL_INFOS() << "WPA: " << wrist_right_pos_agent << LL_ENDL;*/ - for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - if (LLViewerObject *attached_object = attachment_iter->get()) - { - if(attached_object->mDrawable->isVisible()) - { - LL_INFOS() << attachment->getName() << " visible" << LL_ENDL; - } - else - { - LL_INFOS() << attachment->getName() << " not visible at " << mDrawable->getWorldPosition() << " and radius " << mDrawable->getRadius() << LL_ENDL; - } - } - } - } - } - } - - if (!visible && mVisible) - { - mMeshInvisibleTime.reset(); - } - - if (visible) - { - if (!mMeshValid) - { - restoreMeshData(); - } - } - else - { - if (mMeshValid && - (isControlAvatar() || mMeshInvisibleTime.getElapsedTimeF32() > TIME_BEFORE_MESH_CLEANUP)) - { - releaseMeshData(); - } - } - - if ( visible != mVisible ) - { - LL_DEBUGS("AvatarRender") << "visible was " << mVisible << " now " << visible << LL_ENDL; - } - mVisible = visible; -} - -// private -bool LLVOAvatar::shouldAlphaMask() -{ - const bool should_alpha_mask = !LLDrawPoolAlpha::sShowDebugAlpha // Don't alpha mask if "Highlight Transparent" checked - && !LLDrawPoolAvatar::sSkipTransparent; - - return should_alpha_mask; - -} - -//----------------------------------------------------------------------------- -// renderSkinned() -//----------------------------------------------------------------------------- -U32 LLVOAvatar::renderSkinned() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - - U32 num_indices = 0; - - if (!mIsBuilt) - { - return num_indices; - } - - if (mDrawable.isNull()) - { - return num_indices; - } - - LLFace* face = mDrawable->getFace(0); - - bool needs_rebuild = !face || !face->getVertexBuffer() || mDrawable->isState(LLDrawable::REBUILD_GEOMETRY); - - if (needs_rebuild || mDirtyMesh) - { //LOD changed or new mesh created, allocate new vertex buffer if needed - if (needs_rebuild || mDirtyMesh >= 2 || mVisibilityRank <= 4) - { - updateMeshData(); - mDirtyMesh = 0; - mNeedsSkin = true; - mDrawable->clearState(LLDrawable::REBUILD_GEOMETRY); - } - } - - if (LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_AVATAR) <= 0) - { - if (mNeedsSkin) - { - //generate animated mesh - LLViewerJoint* lower_mesh = getViewerJoint(MESH_ID_LOWER_BODY); - LLViewerJoint* upper_mesh = getViewerJoint(MESH_ID_UPPER_BODY); - LLViewerJoint* skirt_mesh = getViewerJoint(MESH_ID_SKIRT); - LLViewerJoint* eyelash_mesh = getViewerJoint(MESH_ID_EYELASH); - LLViewerJoint* head_mesh = getViewerJoint(MESH_ID_HEAD); - LLViewerJoint* hair_mesh = getViewerJoint(MESH_ID_HAIR); - - if(upper_mesh) - { - upper_mesh->updateJointGeometry(); - } - if (lower_mesh) - { - lower_mesh->updateJointGeometry(); - } - - if( isWearingWearableType( LLWearableType::WT_SKIRT ) ) - { - if(skirt_mesh) - { - skirt_mesh->updateJointGeometry(); - } - } - - if (!isSelf() || gAgent.needsRenderHead() || LLPipeline::sShadowRender) - { - if(eyelash_mesh) - { - eyelash_mesh->updateJointGeometry(); - } - if(head_mesh) - { - head_mesh->updateJointGeometry(); - } - if(hair_mesh) - { - hair_mesh->updateJointGeometry(); - } - } - mNeedsSkin = false; - mLastSkinTime = gFrameTimeSeconds; - - LLFace * face = mDrawable->getFace(0); - if (face) - { - LLVertexBuffer* vb = face->getVertexBuffer(); - if (vb) - { - vb->unmapBuffer(); - } - } - } - } - else - { - mNeedsSkin = false; - } - - if (sDebugInvisible) - { - LLNameValue* firstname = getNVPair("FirstName"); - if (firstname) - { - LL_DEBUGS("Avatar") << avString() << " in render" << LL_ENDL; - } - else - { - LL_INFOS() << "Avatar " << this << " in render" << LL_ENDL; - } - if (!mIsBuilt) - { - LL_INFOS() << "Not built!" << LL_ENDL; - } - else if (!gAgent.needsRenderAvatar()) - { - LL_INFOS() << "Doesn't need avatar render!" << LL_ENDL; - } - else - { - LL_INFOS() << "Rendering!" << LL_ENDL; - } - } - - if (!mIsBuilt) - { - return num_indices; - } - - if (isSelf() && !gAgent.needsRenderAvatar()) - { - return num_indices; - } - - //-------------------------------------------------------------------- - // render all geometry attached to the skeleton - //-------------------------------------------------------------------- - - bool first_pass = true; - if (!LLDrawPoolAvatar::sSkipOpaque) - { - if (isUIAvatar() && mIsDummy) - { - LLViewerJoint* hair_mesh = getViewerJoint(MESH_ID_HAIR); - if (hair_mesh) - { - num_indices += hair_mesh->render(mAdjustedPixelArea, first_pass, mIsDummy); - } - first_pass = false; - } - if (!isSelf() || gAgent.needsRenderHead() || LLPipeline::sShadowRender) - { - - if (isTextureVisible(TEX_HEAD_BAKED) || (getOverallAppearance() == AOA_JELLYDOLL && !isControlAvatar()) || isUIAvatar()) - { - LLViewerJoint* head_mesh = getViewerJoint(MESH_ID_HEAD); - if (head_mesh) - { - num_indices += head_mesh->render(mAdjustedPixelArea, first_pass, mIsDummy); - } - first_pass = false; - } - } - if (isTextureVisible(TEX_UPPER_BAKED) || (getOverallAppearance() == AOA_JELLYDOLL && !isControlAvatar()) || isUIAvatar()) - { - LLViewerJoint* upper_mesh = getViewerJoint(MESH_ID_UPPER_BODY); - if (upper_mesh) - { - num_indices += upper_mesh->render(mAdjustedPixelArea, first_pass, mIsDummy); - } - first_pass = false; - } - - if (isTextureVisible(TEX_LOWER_BAKED) || (getOverallAppearance() == AOA_JELLYDOLL && !isControlAvatar()) || isUIAvatar()) - { - LLViewerJoint* lower_mesh = getViewerJoint(MESH_ID_LOWER_BODY); - if (lower_mesh) - { - num_indices += lower_mesh->render(mAdjustedPixelArea, first_pass, mIsDummy); - } - first_pass = false; - } - } - - if (!LLDrawPoolAvatar::sSkipTransparent || LLPipeline::sImpostorRender) - { - LLGLState blend(GL_BLEND, !mIsDummy); - num_indices += renderTransparent(first_pass); - } - - return num_indices; -} - -U32 LLVOAvatar::renderTransparent(bool first_pass) -{ - U32 num_indices = 0; - if( isWearingWearableType( LLWearableType::WT_SKIRT ) && (isUIAvatar() || isTextureVisible(TEX_SKIRT_BAKED)) ) - { - gGL.flush(); - LLViewerJoint* skirt_mesh = getViewerJoint(MESH_ID_SKIRT); - if (skirt_mesh) - { - num_indices += skirt_mesh->render(mAdjustedPixelArea, false); - } - first_pass = false; - gGL.flush(); - } - - if (!isSelf() || gAgent.needsRenderHead() || LLPipeline::sShadowRender) - { - if (LLPipeline::sImpostorRender) - { - gGL.flush(); - } - - if (isTextureVisible(TEX_HEAD_BAKED)) - { - LLViewerJoint* eyelash_mesh = getViewerJoint(MESH_ID_EYELASH); - if (eyelash_mesh) - { - num_indices += eyelash_mesh->render(mAdjustedPixelArea, first_pass, mIsDummy); - } - first_pass = false; - } - if (isTextureVisible(TEX_HAIR_BAKED) && (getOverallAppearance() != AOA_JELLYDOLL)) - { - LLViewerJoint* hair_mesh = getViewerJoint(MESH_ID_HAIR); - if (hair_mesh) - { - num_indices += hair_mesh->render(mAdjustedPixelArea, first_pass, mIsDummy); - } - first_pass = false; - } - if (LLPipeline::sImpostorRender) - { - gGL.flush(); - } - } - - return num_indices; -} - -//----------------------------------------------------------------------------- -// renderRigid() -//----------------------------------------------------------------------------- -U32 LLVOAvatar::renderRigid() -{ - U32 num_indices = 0; - - if (!mIsBuilt) - { - return 0; - } - - if (isSelf() && (!gAgent.needsRenderAvatar() || !gAgent.needsRenderHead())) - { - return 0; - } - - bool should_alpha_mask = shouldAlphaMask(); - LLGLState test(GL_ALPHA_TEST, should_alpha_mask); - - if (isTextureVisible(TEX_EYES_BAKED) || (getOverallAppearance() == AOA_JELLYDOLL && !isControlAvatar()) || isUIAvatar()) - { - LLViewerJoint* eyeball_left = getViewerJoint(MESH_ID_EYEBALL_LEFT); - LLViewerJoint* eyeball_right = getViewerJoint(MESH_ID_EYEBALL_RIGHT); - if (eyeball_left) - { - num_indices += eyeball_left->render(mAdjustedPixelArea, true, mIsDummy); - } - if(eyeball_right) - { - num_indices += eyeball_right->render(mAdjustedPixelArea, true, mIsDummy); - } - } - - return num_indices; -} - -U32 LLVOAvatar::renderImpostor(LLColor4U color, S32 diffuse_channel) -{ - if (!mImpostor.isComplete()) - { - return 0; - } - - LLVector3 pos(getRenderPosition()+mImpostorOffset); - LLVector3 at = (pos - LLViewerCamera::getInstance()->getOrigin()); - at.normalize(); - LLVector3 left = LLViewerCamera::getInstance()->getUpAxis() % at; - LLVector3 up = at%left; - - left *= mImpostorDim.mV[0]; - up *= mImpostorDim.mV[1]; - - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_IMPOSTORS)) - { - LLGLEnable blend(GL_BLEND); - gGL.setSceneBlendType(LLRender::BT_ADD); - gGL.getTexUnit(diffuse_channel)->unbind(LLTexUnit::TT_TEXTURE); - - // gGL.begin(LLRender::QUADS); - // gGL.vertex3fv((pos+left-up).mV); - // gGL.vertex3fv((pos-left-up).mV); - // gGL.vertex3fv((pos-left+up).mV); - // gGL.vertex3fv((pos+left+up).mV); - // gGL.end(); - - - gGL.begin(LLRender::LINES); - gGL.color4f(1.f,1.f,1.f,1.f); - F32 thickness = llmax(F32(5.0f-5.0f*(gFrameTimeSeconds-mLastImpostorUpdateFrameTime)),1.0f); - glLineWidth(thickness); - gGL.vertex3fv((pos+left-up).mV); - gGL.vertex3fv((pos-left-up).mV); - gGL.vertex3fv((pos-left-up).mV); - gGL.vertex3fv((pos-left+up).mV); - gGL.vertex3fv((pos-left+up).mV); - gGL.vertex3fv((pos+left+up).mV); - gGL.vertex3fv((pos+left+up).mV); - gGL.vertex3fv((pos+left-up).mV); - gGL.end(); - gGL.flush(); - } - { - gGL.flush(); - - gGL.color4ubv(color.mV); - gGL.getTexUnit(diffuse_channel)->bind(&mImpostor); - gGL.begin(LLRender::QUADS); - gGL.texCoord2f(0,0); - gGL.vertex3fv((pos+left-up).mV); - gGL.texCoord2f(1,0); - gGL.vertex3fv((pos-left-up).mV); - gGL.texCoord2f(1,1); - gGL.vertex3fv((pos-left+up).mV); - gGL.texCoord2f(0,1); - gGL.vertex3fv((pos+left+up).mV); - gGL.end(); - gGL.flush(); - } - - return 6; -} - -bool LLVOAvatar::allTexturesCompletelyDownloaded(std::set& ids) const -{ - for (std::set::const_iterator it = ids.begin(); it != ids.end(); ++it) - { - LLViewerFetchedTexture *imagep = gTextureList.findImage(*it, TEX_LIST_STANDARD); - if (imagep && imagep->getDiscardLevel()!=0) - { - return false; - } - } - return true; -} - -bool LLVOAvatar::allLocalTexturesCompletelyDownloaded() const -{ - std::set local_ids; - collectLocalTextureUUIDs(local_ids); - return allTexturesCompletelyDownloaded(local_ids); -} - -bool LLVOAvatar::allBakedTexturesCompletelyDownloaded() const -{ - std::set baked_ids; - collectBakedTextureUUIDs(baked_ids); - return allTexturesCompletelyDownloaded(baked_ids); -} - -std::string LLVOAvatar::bakedTextureOriginInfo() -{ - std::string result; - - std::set baked_ids; - collectBakedTextureUUIDs(baked_ids); - for (U32 i = 0; i < mBakedTextureDatas.size(); i++) - { - ETextureIndex texture_index = mBakedTextureDatas[i].mTextureIndex; - LLViewerFetchedTexture *imagep = - LLViewerTextureManager::staticCastToFetchedTexture(getImage(texture_index,0), true); - if (!imagep || - imagep->getID() == IMG_DEFAULT || - imagep->getID() == IMG_DEFAULT_AVATAR) - - { - result += "-"; - } - else - { - bool has_url = false, has_host = false; - if (!imagep->getUrl().empty()) - { - has_url = true; - } - if (imagep->getTargetHost().isOk()) - { - has_host = true; - } - S32 discard = imagep->getDiscardLevel(); - if (has_url && !has_host) result += discard ? "u" : "U"; // server-bake texture with url - else if (has_host && !has_url) result += discard ? "h" : "H"; // old-style texture on sim - else if (has_host && has_url) result += discard ? "x" : "X"; // both origins? - else if (!has_host && !has_url) result += discard ? "n" : "N"; // no origin? - if (discard != 0) - { - result += llformat("(%d/%d)",discard,imagep->getDesiredDiscardLevel()); - } - } - - } - return result; -} - -S32Bytes LLVOAvatar::totalTextureMemForUUIDS(std::set& ids) -{ - S32Bytes result(0); - for (std::set::const_iterator it = ids.begin(); it != ids.end(); ++it) - { - LLViewerFetchedTexture *imagep = gTextureList.findImage(*it, TEX_LIST_STANDARD); - if (imagep) - { - result += imagep->getTextureMemory(); - } - } - return result; -} - -void LLVOAvatar::collectLocalTextureUUIDs(std::set& ids) const -{ - for (U32 texture_index = 0; texture_index < getNumTEs(); texture_index++) - { - LLWearableType::EType wearable_type = LLAvatarAppearance::getDictionary()->getTEWearableType((ETextureIndex)texture_index); - U32 num_wearables = gAgentWearables.getWearableCount(wearable_type); - - LLViewerFetchedTexture *imagep = NULL; - for (U32 wearable_index = 0; wearable_index < num_wearables; wearable_index++) - { - imagep = LLViewerTextureManager::staticCastToFetchedTexture(getImage(texture_index, wearable_index), true); - if (imagep) - { - const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = LLAvatarAppearance::getDictionary()->getTexture((ETextureIndex)texture_index); - if (texture_dict && texture_dict->mIsLocalTexture) - { - ids.insert(imagep->getID()); - } - } - } - } - ids.erase(IMG_DEFAULT); - ids.erase(IMG_DEFAULT_AVATAR); - ids.erase(IMG_INVISIBLE); -} - -void LLVOAvatar::collectBakedTextureUUIDs(std::set& ids) const -{ - for (U32 texture_index = 0; texture_index < getNumTEs(); texture_index++) - { - LLViewerFetchedTexture *imagep = NULL; - if (isIndexBakedTexture((ETextureIndex) texture_index)) - { - imagep = LLViewerTextureManager::staticCastToFetchedTexture(getImage(texture_index,0), true); - if (imagep) - { - ids.insert(imagep->getID()); - } - } - } - ids.erase(IMG_DEFAULT); - ids.erase(IMG_DEFAULT_AVATAR); - ids.erase(IMG_INVISIBLE); -} - -void LLVOAvatar::collectTextureUUIDs(std::set& ids) -{ - collectLocalTextureUUIDs(ids); - collectBakedTextureUUIDs(ids); -} - -void LLVOAvatar::releaseOldTextures() -{ - S32Bytes current_texture_mem; - - // Any textures that we used to be using but are no longer using should no longer be flagged as "NO_DELETE" - std::set baked_texture_ids; - collectBakedTextureUUIDs(baked_texture_ids); - S32Bytes new_baked_mem = totalTextureMemForUUIDS(baked_texture_ids); - - std::set local_texture_ids; - collectLocalTextureUUIDs(local_texture_ids); - //S32 new_local_mem = totalTextureMemForUUIDS(local_texture_ids); - - std::set new_texture_ids; - new_texture_ids.insert(baked_texture_ids.begin(),baked_texture_ids.end()); - new_texture_ids.insert(local_texture_ids.begin(),local_texture_ids.end()); - S32Bytes new_total_mem = totalTextureMemForUUIDS(new_texture_ids); - - //S32 old_total_mem = totalTextureMemForUUIDS(mTextureIDs); - //LL_DEBUGS("Avatar") << getFullname() << " old_total_mem: " << old_total_mem << " new_total_mem (L/B): " << new_total_mem << " (" << new_local_mem <<", " << new_baked_mem << ")" << LL_ENDL; - if (!isSelf() && new_total_mem > new_baked_mem) - { - LL_WARNS() << "extra local textures stored for non-self av" << LL_ENDL; - } - for (std::set::iterator it = mTextureIDs.begin(); it != mTextureIDs.end(); ++it) - { - if (new_texture_ids.find(*it) == new_texture_ids.end()) - { - LLViewerFetchedTexture *imagep = gTextureList.findImage(*it, TEX_LIST_STANDARD); - if (imagep) - { - current_texture_mem += imagep->getTextureMemory(); - if (imagep->getTextureState() == LLGLTexture::NO_DELETE) - { - // This will allow the texture to be deleted if not in use. - imagep->forceActive(); - - // This resets the clock to texture being flagged - // as unused, preventing the texture from being - // deleted immediately. If other avatars or - // objects are using it, it can still be flagged - // no-delete by them. - imagep->forceUpdateBindStats(); - } - } - } - } - mTextureIDs = new_texture_ids; -} - -void LLVOAvatar::updateTextures() -{ - releaseOldTextures(); - - bool render_avatar = true; - - if (mIsDummy) - { - return; - } - - if( isSelf() ) - { - render_avatar = true; - } - else - { - if(!isVisible()) - { - return ;//do not update for invisible avatar. - } - - render_avatar = !mCulled; //visible and not culled. - } - - std::vector layer_baked; - // GL NOT ACTIVE HERE - *TODO - for (U32 i = 0; i < mBakedTextureDatas.size(); i++) - { - layer_baked.push_back(isTextureDefined(mBakedTextureDatas[i].mTextureIndex)); - // bind the texture so that they'll be decoded slightly - // inefficient, we can short-circuit this if we have to - if (render_avatar && !gGLManager.mIsDisabled) - { - if (layer_baked[i] && !mBakedTextureDatas[i].mIsLoaded) - { - gGL.getTexUnit(0)->bind(getImage( mBakedTextureDatas[i].mTextureIndex, 0 )); - } - } - } - - mMaxPixelArea = 0.f; - mMinPixelArea = 99999999.f; - mHasGrey = false; // debug - for (U32 texture_index = 0; texture_index < getNumTEs(); texture_index++) - { - LLWearableType::EType wearable_type = LLAvatarAppearance::getDictionary()->getTEWearableType((ETextureIndex)texture_index); - U32 num_wearables = gAgentWearables.getWearableCount(wearable_type); - const LLTextureEntry *te = getTE(texture_index); - - // getTE can return 0. - // Not sure yet why it does, but of course it crashes when te->mScale? gets used. - // Put safeguard in place so this corner case get better handling and does not result in a crash. - F32 texel_area_ratio = 1.0f; - if( te ) - { - texel_area_ratio = fabs(te->mScaleS * te->mScaleT); - } - else - { - LL_WARNS() << "getTE( " << texture_index << " ) returned 0" <getTexture((ETextureIndex)texture_index); - const EBakedTextureIndex baked_index = texture_dict ? texture_dict->mBakedTextureIndex : EBakedTextureIndex::BAKED_NUM_INDICES; - if (texture_dict && texture_dict->mIsLocalTexture) - { - addLocalTextureStats((ETextureIndex)texture_index, imagep, texel_area_ratio, render_avatar, mBakedTextureDatas[baked_index].mIsUsed); - } - } - } - if (isIndexBakedTexture((ETextureIndex) texture_index) && render_avatar) - { - const S32 boost_level = getAvatarBakedBoostLevel(); - imagep = LLViewerTextureManager::staticCastToFetchedTexture(getImage(texture_index,0), true); - addBakedTextureStats( imagep, mPixelArea, texel_area_ratio, boost_level ); - } - } - - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_AREA)) - { - setDebugText(llformat("%4.0f:%4.0f", (F32) sqrt(mMinPixelArea),(F32) sqrt(mMaxPixelArea))); - } -} - - -void LLVOAvatar::addLocalTextureStats( ETextureIndex idx, LLViewerFetchedTexture* imagep, - F32 texel_area_ratio, bool render_avatar, bool covered_by_baked) -{ - // No local texture stats for non-self avatars - return; -} - -const S32 MAX_TEXTURE_UPDATE_INTERVAL = 64 ; //need to call updateTextures() at least every 32 frames. -const S32 MAX_TEXTURE_VIRTUAL_SIZE_RESET_INTERVAL = S32_MAX ; //frames -void LLVOAvatar::checkTextureLoading() -{ - static const F32 MAX_INVISIBLE_WAITING_TIME = 15.f ; //seconds - - bool pause = !isVisible() ; - if(!pause) - { - mInvisibleTimer.reset() ; - } - if(mLoadedCallbacksPaused == pause) - { - if (!pause && mFirstFullyVisible && mLoadedCallbackTextures < mCallbackTextureList.size()) - { - // We still need to update 'loaded' textures count to decide on 'cloud' visibility - // Alternatively this can be done on TextureLoaded callbacks, but is harder to properly track - mLoadedCallbackTextures = 0; - for (LLLoadedCallbackEntry::source_callback_list_t::iterator iter = mCallbackTextureList.begin(); - iter != mCallbackTextureList.end(); ++iter) - { - LLViewerFetchedTexture* tex = gTextureList.findImage(*iter); - if (tex && (tex->getDiscardLevel() >= 0 || tex->isMissingAsset())) - { - mLoadedCallbackTextures++; - } - } - } - return ; - } - - if(mCallbackTextureList.empty()) //when is self or no callbacks. Note: this list for self is always empty. - { - mLoadedCallbacksPaused = pause ; - mLoadedCallbackTextures = 0; - return ; //nothing to check. - } - - if(pause && mInvisibleTimer.getElapsedTimeF32() < MAX_INVISIBLE_WAITING_TIME) - { - return ; //have not been invisible for enough time. - } - - mLoadedCallbackTextures = pause ? mCallbackTextureList.size() : 0; - - for(LLLoadedCallbackEntry::source_callback_list_t::iterator iter = mCallbackTextureList.begin(); - iter != mCallbackTextureList.end(); ++iter) - { - LLViewerFetchedTexture* tex = gTextureList.findImage(*iter) ; - if(tex) - { - if(pause)//pause texture fetching. - { - tex->pauseLoadedCallbacks(&mCallbackTextureList) ; - - //set to terminate texture fetching after MAX_TEXTURE_UPDATE_INTERVAL frames. - tex->setMaxVirtualSizeResetInterval(MAX_TEXTURE_UPDATE_INTERVAL); - tex->resetMaxVirtualSizeResetCounter() ; - } - else//unpause - { - static const F32 START_AREA = 100.f ; - - tex->unpauseLoadedCallbacks(&mCallbackTextureList) ; - tex->addTextureStats(START_AREA); //jump start the fetching again - - // technically shouldn't need to account for missing, but callback might not have happened yet - if (tex->getDiscardLevel() >= 0 || tex->isMissingAsset()) - { - mLoadedCallbackTextures++; // consider it loaded (we have at least some data) - } - } - } - } - - if(!pause) - { - updateTextures() ; //refresh texture stats. - } - mLoadedCallbacksPaused = pause ; - return ; -} - -const F32 SELF_ADDITIONAL_PRI = 0.75f ; -void LLVOAvatar::addBakedTextureStats( LLViewerFetchedTexture* imagep, F32 pixel_area, F32 texel_area_ratio, S32 boost_level) -{ - //Note: - //if this function is not called for the last MAX_TEXTURE_VIRTUAL_SIZE_RESET_INTERVAL frames, - //the texture pipeline will stop fetching this texture. - - imagep->resetTextureStats(); - imagep->setMaxVirtualSizeResetInterval(MAX_TEXTURE_VIRTUAL_SIZE_RESET_INTERVAL); - imagep->resetMaxVirtualSizeResetCounter() ; - - mMaxPixelArea = llmax(pixel_area, mMaxPixelArea); - mMinPixelArea = llmin(pixel_area, mMinPixelArea); - imagep->addTextureStats(pixel_area / texel_area_ratio); - imagep->setBoostLevel(boost_level); -} - -//virtual -void LLVOAvatar::setImage(const U8 te, LLViewerTexture *imagep, const U32 index) -{ - setTEImage(te, imagep); -} - -//virtual -LLViewerTexture* LLVOAvatar::getImage(const U8 te, const U32 index) const -{ - return getTEImage(te); -} -//virtual -const LLTextureEntry* LLVOAvatar::getTexEntry(const U8 te_num) const -{ - return getTE(te_num); -} - -//virtual -void LLVOAvatar::setTexEntry(const U8 index, const LLTextureEntry &te) -{ - setTE(index, te); -} - -const std::string LLVOAvatar::getImageURL(const U8 te, const LLUUID &uuid) -{ - llassert(isIndexBakedTexture(ETextureIndex(te))); - std::string url = ""; - const std::string& appearance_service_url = LLAppearanceMgr::instance().getAppearanceServiceURL(); - if (appearance_service_url.empty()) - { - // Probably a server-side issue if we get here: - LL_WARNS() << "AgentAppearanceServiceURL not set - Baked texture requests will fail" << LL_ENDL; - return url; - } - - const LLAvatarAppearanceDictionary::TextureEntry* texture_entry = LLAvatarAppearance::getDictionary()->getTexture((ETextureIndex)te); - if (texture_entry != NULL) - { - url = appearance_service_url + "texture/" + getID().asString() + "/" + texture_entry->mDefaultImageName + "/" + uuid.asString(); - //LL_INFOS() << "baked texture url: " << url << LL_ENDL; - } - return url; -} - -//----------------------------------------------------------------------------- -// resolveHeight() -//----------------------------------------------------------------------------- - -void LLVOAvatar::resolveHeightAgent(const LLVector3 &in_pos_agent, LLVector3 &out_pos_agent, LLVector3 &out_norm) -{ - LLVector3d in_pos_global, out_pos_global; - - in_pos_global = gAgent.getPosGlobalFromAgent(in_pos_agent); - resolveHeightGlobal(in_pos_global, out_pos_global, out_norm); - out_pos_agent = gAgent.getPosAgentFromGlobal(out_pos_global); -} - - -void LLVOAvatar::resolveRayCollisionAgent(const LLVector3d start_pt, const LLVector3d end_pt, LLVector3d &out_pos, LLVector3 &out_norm) -{ - LLViewerObject *obj; - LLWorld::getInstance()->resolveStepHeightGlobal(this, start_pt, end_pt, out_pos, out_norm, &obj); -} - -void LLVOAvatar::resolveHeightGlobal(const LLVector3d &inPos, LLVector3d &outPos, LLVector3 &outNorm) -{ - LLVector3d zVec(0.0f, 0.0f, 0.5f); - LLVector3d p0 = inPos + zVec; - LLVector3d p1 = inPos - zVec; - LLViewerObject *obj; - LLWorld::getInstance()->resolveStepHeightGlobal(this, p0, p1, outPos, outNorm, &obj); - if (!obj) - { - mStepOnLand = true; - mStepMaterial = 0; - mStepObjectVelocity.setVec(0.0f, 0.0f, 0.0f); - } - else - { - mStepOnLand = false; - mStepMaterial = obj->getMaterial(); - - // We want the primitive velocity, not our velocity... (which actually subtracts the - // step object velocity) - LLVector3 angularVelocity = obj->getAngularVelocity(); - LLVector3 relativePos = gAgent.getPosAgentFromGlobal(outPos) - obj->getPositionAgent(); - - LLVector3 linearComponent = angularVelocity % relativePos; -// LL_INFOS() << "Linear Component of Rotation Velocity " << linearComponent << LL_ENDL; - mStepObjectVelocity = obj->getVelocity() + linearComponent; - } -} - - -//----------------------------------------------------------------------------- -// getStepSound() -//----------------------------------------------------------------------------- -const LLUUID& LLVOAvatar::getStepSound() const -{ - if ( mStepOnLand ) - { - return sStepSoundOnLand; - } - - return sStepSounds[mStepMaterial]; -} - - -//----------------------------------------------------------------------------- -// processAnimationStateChanges() -//----------------------------------------------------------------------------- -void LLVOAvatar::processAnimationStateChanges() -{ - if ( isAnyAnimationSignaled(AGENT_WALK_ANIMS, NUM_AGENT_WALK_ANIMS) ) - { - startMotion(ANIM_AGENT_WALK_ADJUST); - stopMotion(ANIM_AGENT_FLY_ADJUST); - } - else if (mInAir && !isSitting()) - { - stopMotion(ANIM_AGENT_WALK_ADJUST); - if (mEnableDefaultMotions) - { - startMotion(ANIM_AGENT_FLY_ADJUST); - } - } - else - { - stopMotion(ANIM_AGENT_WALK_ADJUST); - stopMotion(ANIM_AGENT_FLY_ADJUST); - } - - if ( isAnyAnimationSignaled(AGENT_GUN_AIM_ANIMS, NUM_AGENT_GUN_AIM_ANIMS) ) - { - if (mEnableDefaultMotions) - { - startMotion(ANIM_AGENT_TARGET); - } - stopMotion(ANIM_AGENT_BODY_NOISE); - } - else - { - stopMotion(ANIM_AGENT_TARGET); - if (mEnableDefaultMotions) - { - startMotion(ANIM_AGENT_BODY_NOISE); - } - } - - // clear all current animations - AnimIterator anim_it; - for (anim_it = mPlayingAnimations.begin(); anim_it != mPlayingAnimations.end();) - { - AnimIterator found_anim = mSignaledAnimations.find(anim_it->first); - - // playing, but not signaled, so stop - if (found_anim == mSignaledAnimations.end()) - { - processSingleAnimationStateChange(anim_it->first, false); - mPlayingAnimations.erase(anim_it++); - continue; - } - - ++anim_it; - } - - // if jellydolled, shelve all playing animations - if (getOverallAppearance() != AOA_NORMAL) - { - mPlayingAnimations.clear(); - } - - // start up all new anims - if (getOverallAppearance() == AOA_NORMAL) - { - for (anim_it = mSignaledAnimations.begin(); anim_it != mSignaledAnimations.end();) - { - AnimIterator found_anim = mPlayingAnimations.find(anim_it->first); - - // signaled but not playing, or different sequence id, start motion - if (found_anim == mPlayingAnimations.end() || found_anim->second != anim_it->second) - { - if (processSingleAnimationStateChange(anim_it->first, true)) - { - mPlayingAnimations[anim_it->first] = anim_it->second; - ++anim_it; - continue; - } - } - - ++anim_it; - } - } - - // clear source information for animations which have been stopped - if (isSelf()) - { - AnimSourceIterator source_it = mAnimationSources.begin(); - - for (source_it = mAnimationSources.begin(); source_it != mAnimationSources.end();) - { - if (mSignaledAnimations.find(source_it->second) == mSignaledAnimations.end()) - { - mAnimationSources.erase(source_it++); - } - else - { - ++source_it; - } - } - } - - stop_glerror(); -} - - -//----------------------------------------------------------------------------- -// processSingleAnimationStateChange(); -//----------------------------------------------------------------------------- -bool LLVOAvatar::processSingleAnimationStateChange( const LLUUID& anim_id, bool start ) -{ - // SL-402, SL-427 - we need to update body size often enough to - // keep appearances in sync, but not so often that animations - // cause constant jiggling of the body or camera. Possible - // compromise is to do it on animation changes: - computeBodySize(); - - bool result = false; - - if ( start ) // start animation - { - if (anim_id == ANIM_AGENT_TYPE) - { - if (gAudiop) - { - LLVector3d char_pos_global = gAgent.getPosGlobalFromAgent(getCharacterPosition()); - if (LLViewerParcelMgr::getInstance()->canHearSound(char_pos_global) - && !LLMuteList::getInstance()->isMuted(getID(), LLMute::flagObjectSounds)) - { - // RN: uncomment this to play on typing sound at fixed volume once sound engine is fixed - // to support both spatialized and non-spatialized instances of the same sound - //if (isSelf()) - //{ - // gAudiop->triggerSound(LLUUID(gSavedSettings.getString("UISndTyping")), 1.0f, LLAudioEngine::AUDIO_TYPE_UI); - //} - //else - { - static LLCachedControl ui_snd_string(gSavedSettings, "UISndTyping"); - LLUUID sound_id = LLUUID(ui_snd_string); - gAudiop->triggerSound(sound_id, getID(), 1.0f, LLAudioEngine::AUDIO_TYPE_SFX, char_pos_global); - } - } - } - } - else if (anim_id == ANIM_AGENT_SIT_GROUND_CONSTRAINED) - { - sitDown(true); - } - - - if (startMotion(anim_id)) - { - result = true; - } - else - { - LL_WARNS("Motion") << "Failed to start motion!" << LL_ENDL; - } - } - else //stop animation - { - if (anim_id == ANIM_AGENT_SIT_GROUND_CONSTRAINED) - { - sitDown(false); - } - if ((anim_id == ANIM_AGENT_DO_NOT_DISTURB) && gAgent.isDoNotDisturb()) - { - // re-assert DND tag animation - gAgent.sendAnimationRequest(ANIM_AGENT_DO_NOT_DISTURB, ANIM_REQUEST_START); - return result; - } - stopMotion(anim_id); - result = true; - } - - return result; -} - -//----------------------------------------------------------------------------- -// isAnyAnimationSignaled() -//----------------------------------------------------------------------------- -bool LLVOAvatar::isAnyAnimationSignaled(const LLUUID *anim_array, const S32 num_anims) const -{ - for (S32 i = 0; i < num_anims; i++) - { - if(mSignaledAnimations.find(anim_array[i]) != mSignaledAnimations.end()) - { - return true; - } - } - return false; -} - -//----------------------------------------------------------------------------- -// resetAnimations() -//----------------------------------------------------------------------------- -void LLVOAvatar::resetAnimations() -{ - LLKeyframeMotion::flushKeyframeCache(); - flushAllMotions(); -} - -// Override selectively based on avatar sex and whether we're using new -// animations. -LLUUID LLVOAvatar::remapMotionID(const LLUUID& id) -{ - static LLCachedControl use_new_walk_run(gSavedSettings, "UseNewWalkRun"); - LLUUID result = id; - - // start special case female walk for female avatars - if (getSex() == SEX_FEMALE) - { - if (id == ANIM_AGENT_WALK) - { - if (use_new_walk_run) - result = ANIM_AGENT_FEMALE_WALK_NEW; - else - result = ANIM_AGENT_FEMALE_WALK; - } - else if (id == ANIM_AGENT_RUN) - { - // There is no old female run animation, so only override - // in one case. - if (use_new_walk_run) - result = ANIM_AGENT_FEMALE_RUN_NEW; - } - else if (id == ANIM_AGENT_SIT) - { - result = ANIM_AGENT_SIT_FEMALE; - } - } - else - { - // Male avatar. - if (id == ANIM_AGENT_WALK) - { - if (use_new_walk_run) - result = ANIM_AGENT_WALK_NEW; - } - else if (id == ANIM_AGENT_RUN) - { - if (use_new_walk_run) - result = ANIM_AGENT_RUN_NEW; - } - // keeps in sync with setSex() related code (viewer controls sit's sex) - else if (id == ANIM_AGENT_SIT_FEMALE) - { - result = ANIM_AGENT_SIT; - } - - } - - return result; - -} - -//----------------------------------------------------------------------------- -// startMotion() -// id is the asset if of the animation to start -// time_offset is the offset into the animation at which to start playing -//----------------------------------------------------------------------------- -bool LLVOAvatar::startMotion(const LLUUID& id, F32 time_offset) -{ - LL_DEBUGS("Motion") << "motion requested " << id.asString() << " " << gAnimLibrary.animationName(id) << LL_ENDL; - - LLUUID remap_id = remapMotionID(id); - - if (remap_id != id) - { - LL_DEBUGS("Motion") << "motion resultant " << remap_id.asString() << " " << gAnimLibrary.animationName(remap_id) << LL_ENDL; - } - - if (isSelf() && remap_id == ANIM_AGENT_AWAY) - { - gAgent.setAFK(); - } - - return LLCharacter::startMotion(remap_id, time_offset); -} - -//----------------------------------------------------------------------------- -// stopMotion() -//----------------------------------------------------------------------------- -bool LLVOAvatar::stopMotion(const LLUUID& id, bool stop_immediate) -{ - LL_DEBUGS("Motion") << "Motion requested " << id.asString() << " " << gAnimLibrary.animationName(id) << LL_ENDL; - - LLUUID remap_id = remapMotionID(id); - - if (remap_id != id) - { - LL_DEBUGS("Motion") << "motion resultant " << remap_id.asString() << " " << gAnimLibrary.animationName(remap_id) << LL_ENDL; - } - - if (isSelf()) - { - gAgent.onAnimStop(remap_id); - } - - return LLCharacter::stopMotion(remap_id, stop_immediate); -} - -//----------------------------------------------------------------------------- -// hasMotionFromSource() -//----------------------------------------------------------------------------- -// virtual -bool LLVOAvatar::hasMotionFromSource(const LLUUID& source_id) -{ - return false; -} - -//----------------------------------------------------------------------------- -// stopMotionFromSource() -//----------------------------------------------------------------------------- -// virtual -void LLVOAvatar::stopMotionFromSource(const LLUUID& source_id) -{ -} - -//----------------------------------------------------------------------------- -// addDebugText() -//----------------------------------------------------------------------------- -void LLVOAvatar::addDebugText(const std::string& text) -{ - mDebugText.append(1, '\n'); - mDebugText.append(text); -} - -//----------------------------------------------------------------------------- -// getID() -//----------------------------------------------------------------------------- -const LLUUID& LLVOAvatar::getID() const -{ - return mID; -} - -//----------------------------------------------------------------------------- -// getJoint() -//----------------------------------------------------------------------------- -// RN: avatar joints are multi-rooted to include screen-based attachments -LLJoint *LLVOAvatar::getJoint( const std::string &name ) -{ - joint_map_t::iterator iter = mJointMap.find(name); - - LLJoint* jointp = NULL; - - if (iter == mJointMap.end() || iter->second == NULL) - { //search for joint and cache found joint in lookup table - if (mJointAliasMap.empty()) - { - getJointAliases(); - } - joint_alias_map_t::const_iterator alias_iter = mJointAliasMap.find(name); - std::string canonical_name; - if (alias_iter != mJointAliasMap.end()) - { - canonical_name = alias_iter->second; - } - else - { - canonical_name = name; - } - jointp = mRoot->findJoint(canonical_name); - mJointMap[name] = jointp; - } - else - { //return cached pointer - jointp = iter->second; - } - -#ifndef LL_RELEASE_FOR_DOWNLOAD - if (jointp && jointp->getName()!="mScreen" && jointp->getName()!="mRoot") - { - llassert(getJoint(jointp->getJointNum())==jointp); - } -#endif - return jointp; -} - -LLJoint *LLVOAvatar::getJoint( S32 joint_num ) -{ - LLJoint *pJoint = NULL; - if (joint_num >= 0) - { - if (joint_num < mNumBones) - { - pJoint = mSkeleton[joint_num]; - } - else if (joint_num < mNumBones + mNumCollisionVolumes) - { - S32 collision_id = joint_num - mNumBones; - pJoint = &mCollisionVolumes[collision_id]; - } - else - { - // Attachment IDs start at 1 - S32 attachment_id = joint_num - (mNumBones + mNumCollisionVolumes) + 1; - attachment_map_t::iterator iter = mAttachmentPoints.find(attachment_id); - if (iter != mAttachmentPoints.end()) - { - pJoint = iter->second; - } - } - } - - llassert(!pJoint || pJoint->getJointNum() == joint_num); - return pJoint; -} - -//----------------------------------------------------------------------------- -// getRiggedMeshID -// -// If viewer object is a rigged mesh, set the mesh id and return true. -// Otherwise, null out the id and return false. -//----------------------------------------------------------------------------- -// static -bool LLVOAvatar::getRiggedMeshID(LLViewerObject* pVO, LLUUID& mesh_id) -{ - mesh_id.setNull(); - - //If a VO has a skin that we'll reset the joint positions to their default - if ( pVO && pVO->mDrawable ) - { - LLVOVolume* pVObj = pVO->mDrawable->getVOVolume(); - if ( pVObj ) - { - const LLMeshSkinInfo* pSkinData = pVObj->getSkinInfo(); - if (pSkinData - && pSkinData->mJointNames.size() > JOINT_COUNT_REQUIRED_FOR_FULLRIG // full rig - && pSkinData->mAlternateBindMatrix.size() > 0 ) - { - mesh_id = pSkinData->mMeshID; - return true; - } - } - } - return false; -} - -bool LLVOAvatar::jointIsRiggedTo(const LLJoint *joint) const -{ - if (joint) - { - const LLJointRiggingInfoTab& tab = mJointRiggingInfoTab; - S32 joint_num = joint->getJointNum(); - if (joint_num < tab.size() && tab[joint_num].isRiggedTo()) - { - return true; - } - } - return false; -} - -void LLVOAvatar::clearAttachmentOverrides() -{ - LLScopedContextString str("clearAttachmentOverrides " + getFullname()); - - for (S32 i=0; iclearAttachmentPosOverrides(); - pJoint->clearAttachmentScaleOverrides(); - } - } - - if (mPelvisFixups.count()>0) - { - mPelvisFixups.clear(); - LLJoint* pJointPelvis = getJoint("mPelvis"); - if (pJointPelvis) - { - pJointPelvis->setPosition( LLVector3( 0.0f, 0.0f, 0.0f) ); - } - postPelvisSetRecalc(); - } - - mActiveOverrideMeshes.clear(); - onActiveOverrideMeshesChanged(); -} - -//----------------------------------------------------------------------------- -// rebuildAttachmentOverrides -//----------------------------------------------------------------------------- -void LLVOAvatar::rebuildAttachmentOverrides() -{ - LLScopedContextString str("rebuildAttachmentOverrides " + getFullname()); - - LL_DEBUGS("AnimatedObjects") << "rebuilding" << LL_ENDL; - dumpStack("AnimatedObjectsStack"); - - clearAttachmentOverrides(); - - // Handle the case that we're resetting the skeleton of an animated object. - LLControlAvatar *control_av = dynamic_cast(this); - if (control_av) - { - LLVOVolume *volp = control_av->mRootVolp; - if (volp) - { - LL_DEBUGS("Avatar") << volp->getID() << " adding attachment overrides for root vol, prim count " - << (S32) (1+volp->numChildren()) << LL_ENDL; - addAttachmentOverridesForObject(volp); - } - } - - // Attached objects - for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment *attachment_pt = (*iter).second; - if (attachment_pt) - { - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator at_it = attachment_pt->mAttachedObjects.begin(); - at_it != attachment_pt->mAttachedObjects.end(); ++at_it) - { - LLViewerObject *vo = at_it->get(); - // Attached animated objects affect joints in their control - // avs, not the avs to which they are attached. - if (vo && !vo->isAnimatedObject()) - { - addAttachmentOverridesForObject(vo); - } - } - } - } -} - -//----------------------------------------------------------------------------- -// updateAttachmentOverrides -// -// This is intended to give the same results as -// rebuildAttachmentOverrides(), while avoiding redundant work. -// ----------------------------------------------------------------------------- -void LLVOAvatar::updateAttachmentOverrides() -{ - LLScopedContextString str("updateAttachmentOverrides " + getFullname()); - - LL_DEBUGS("AnimatedObjects") << "updating" << LL_ENDL; - dumpStack("AnimatedObjectsStack"); - - std::set meshes_seen; - - // Handle the case that we're updating the skeleton of an animated object. - LLControlAvatar *control_av = dynamic_cast(this); - if (control_av) - { - LLVOVolume *volp = control_av->mRootVolp; - if (volp) - { - LL_DEBUGS("Avatar") << volp->getID() << " adding attachment overrides for root vol, prim count " - << (S32) (1+volp->numChildren()) << LL_ENDL; - addAttachmentOverridesForObject(volp, &meshes_seen); - } - } - - // Attached objects - for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment *attachment_pt = (*iter).second; - if (attachment_pt) - { - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator at_it = attachment_pt->mAttachedObjects.begin(); - at_it != attachment_pt->mAttachedObjects.end(); ++at_it) - { - LLViewerObject *vo = at_it->get(); - // Attached animated objects affect joints in their control - // avs, not the avs to which they are attached. - if (vo && !vo->isAnimatedObject()) - { - addAttachmentOverridesForObject(vo, &meshes_seen); - } - } - } - } - // Remove meshes that are no longer present on the skeleton - - // have to work with a copy because removeAttachmentOverrides() will change mActiveOverrideMeshes. - std::set active_override_meshes = mActiveOverrideMeshes; - for (std::set::iterator it = active_override_meshes.begin(); it != active_override_meshes.end(); ++it) - { - if (meshes_seen.find(*it) == meshes_seen.end()) - { - removeAttachmentOverridesForObject(*it); - } - } - - -#ifdef ATTACHMENT_OVERRIDE_VALIDATION - { - std::vector pos_overrides_by_joint; - std::vector scale_overrides_by_joint; - LLVector3OverrideMap pelvis_fixups; - - // Capture snapshot of override state after update - for (S32 joint_num = 0; joint_num < LL_CHARACTER_MAX_ANIMATED_JOINTS; joint_num++) - { - LLVector3OverrideMap pos_overrides; - LLJoint *joint = getJoint(joint_num); - if (joint) - { - pos_overrides_by_joint.push_back(joint->m_attachmentPosOverrides); - scale_overrides_by_joint.push_back(joint->m_attachmentScaleOverrides); - } - else - { - // No joint, use default constructed empty maps - pos_overrides_by_joint.push_back(LLVector3OverrideMap()); - scale_overrides_by_joint.push_back(LLVector3OverrideMap()); - } - } - pelvis_fixups = mPelvisFixups; - //dumpArchetypeXML(getFullname() + "_paranoid_updated"); - - // Rebuild and compare - rebuildAttachmentOverrides(); - //dumpArchetypeXML(getFullname() + "_paranoid_rebuilt"); - bool mismatched = false; - for (S32 joint_num = 0; joint_num < LL_CHARACTER_MAX_ANIMATED_JOINTS; joint_num++) - { - LLJoint *joint = getJoint(joint_num); - if (joint) - { - if (pos_overrides_by_joint[joint_num] != joint->m_attachmentPosOverrides) - { - mismatched = true; - } - if (scale_overrides_by_joint[joint_num] != joint->m_attachmentScaleOverrides) - { - mismatched = true; - } - } - } - if (pelvis_fixups != mPelvisFixups) - { - mismatched = true; - } - if (mismatched) - { - LL_WARNS() << "MISMATCHED ATTACHMENT OVERRIDES" << LL_ENDL; - } - } -#endif -} - -void LLVOAvatar::notifyAttachmentMeshLoaded() -{ - if (!isFullyLoaded()) - { - // We just received mesh or skin info - // Reset timer to wait for more potential meshes or changes - mFullyLoadedTimer.reset(); - } -} - -//----------------------------------------------------------------------------- -// addAttachmentOverridesForObject -//----------------------------------------------------------------------------- -void LLVOAvatar::addAttachmentOverridesForObject(LLViewerObject *vo, std::set* meshes_seen, bool recursive) -{ - if (vo->getAvatar() != this && vo->getAvatarAncestor() != this) - { - LL_WARNS("Avatar") << "called with invalid avatar" << LL_ENDL; - return; - } - - LLScopedContextString str("addAttachmentOverridesForObject " + getFullname()); - - if (getOverallAppearance() != AOA_NORMAL) - { - return; - } - - LL_DEBUGS("AnimatedObjects") << "adding" << LL_ENDL; - dumpStack("AnimatedObjectsStack"); - - // Process all children - if (recursive) - { - LLViewerObject::const_child_list_t& children = vo->getChildren(); - for (LLViewerObject::const_child_list_t::const_iterator it = children.begin(); - it != children.end(); ++it) - { - LLViewerObject *childp = *it; - addAttachmentOverridesForObject(childp, meshes_seen, true); - } - } - - LLVOVolume *vobj = dynamic_cast(vo); - bool pelvisGotSet = false; - - if (!vobj) - { - return; - } - - LLViewerObject *root_object = (LLViewerObject*)vobj->getRoot(); - LL_DEBUGS("AnimatedObjects") << "trying to add attachment overrides for root object " << root_object->getID() << " prim is " << vobj << LL_ENDL; - if (vobj->isMesh() && - ((vobj->getVolume() && !vobj->getVolume()->isMeshAssetLoaded()) || !gMeshRepo.meshRezEnabled())) - { - LL_DEBUGS("AnimatedObjects") << "failed to add attachment overrides for root object " << root_object->getID() << " mesh asset not loaded" << LL_ENDL; - return; - } - const LLMeshSkinInfo* pSkinData = vobj->getSkinInfo(); - - if ( vobj && vobj->isMesh() && pSkinData ) - { - const int bindCnt = pSkinData->mAlternateBindMatrix.size(); - const int jointCnt = pSkinData->mJointNames.size(); - if ((bindCnt > 0) && (bindCnt != jointCnt)) - { - LL_WARNS_ONCE() << "invalid mesh, bindCnt " << bindCnt << "!= jointCnt " << jointCnt << ", joint overrides will be ignored." << LL_ENDL; - } - if ((bindCnt > 0) && (bindCnt == jointCnt)) - { - const F32 pelvisZOffset = pSkinData->mPelvisOffset; - const LLUUID& mesh_id = pSkinData->mMeshID; - - if (meshes_seen) - { - meshes_seen->insert(mesh_id); - } - bool mesh_overrides_loaded = (mActiveOverrideMeshes.find(mesh_id) != mActiveOverrideMeshes.end()); - if (mesh_overrides_loaded) - { - LL_DEBUGS("AnimatedObjects") << "skipping add attachment overrides for " << mesh_id - << " to root object " << root_object->getID() - << ", already loaded" - << LL_ENDL; - } - else - { - LL_DEBUGS("AnimatedObjects") << "adding attachment overrides for " << mesh_id - << " to root object " << root_object->getID() << LL_ENDL; - } - bool fullRig = jointCnt>=JOINT_COUNT_REQUIRED_FOR_FULLRIG; - if ( fullRig && !mesh_overrides_loaded ) - { - for ( int i=0; imJointNames[i].c_str(); - LLJoint* pJoint = getJoint( lookingForJoint ); - if (pJoint) - { - const LLVector3& jointPos = LLVector3(pSkinData->mAlternateBindMatrix[i].getTranslation()); - if (pJoint->aboveJointPosThreshold(jointPos)) - { - bool override_changed; - pJoint->addAttachmentPosOverride( jointPos, mesh_id, avString(), override_changed ); - - if (override_changed) - { - //If joint is a pelvis then handle old/new pelvis to foot values - if ( lookingForJoint == "mPelvis" ) - { - pelvisGotSet = true; - } - } - if (pSkinData->mLockScaleIfJointPosition) - { - // Note that unlike positions, there's no threshold check here, - // just a lock at the default value. - pJoint->addAttachmentScaleOverride(pJoint->getDefaultScale(), mesh_id, avString()); - } - } - } - } - - if (pelvisZOffset != 0.0F) - { - F32 pelvis_fixup_before; - bool has_fixup_before = hasPelvisFixup(pelvis_fixup_before); - addPelvisFixup( pelvisZOffset, mesh_id ); - F32 pelvis_fixup_after; - hasPelvisFixup(pelvis_fixup_after); // Don't have to check bool here because we just added it... - if (!has_fixup_before || (pelvis_fixup_before != pelvis_fixup_after)) - { - pelvisGotSet = true; - } - - } - mActiveOverrideMeshes.insert(mesh_id); - onActiveOverrideMeshesChanged(); - } - } - } - else - { - LL_DEBUGS("AnimatedObjects") << "failed to add attachment overrides for root object " << root_object->getID() << " not mesh or no pSkinData" << LL_ENDL; - } - - //Rebuild body data if we altered joints/pelvis - if ( pelvisGotSet ) - { - postPelvisSetRecalc(); - } -} - -//----------------------------------------------------------------------------- -// getAttachmentOverrideNames -//----------------------------------------------------------------------------- -void LLVOAvatar::getAttachmentOverrideNames(std::set& pos_names, std::set& scale_names) const -{ - LLVector3 pos; - LLVector3 scale; - LLUUID mesh_id; - - // Bones - for (avatar_joint_list_t::const_iterator iter = mSkeleton.begin(); - iter != mSkeleton.end(); ++iter) - { - const LLJoint* pJoint = (*iter); - if (pJoint && pJoint->hasAttachmentPosOverride(pos,mesh_id)) - { - pos_names.insert(pJoint->getName()); - } - if (pJoint && pJoint->hasAttachmentScaleOverride(scale,mesh_id)) - { - scale_names.insert(pJoint->getName()); - } - } - - // Attachment points - for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - const LLViewerJointAttachment *attachment_pt = (*iter).second; - if (attachment_pt && attachment_pt->hasAttachmentPosOverride(pos,mesh_id)) - { - pos_names.insert(attachment_pt->getName()); - } - // Attachment points don't have scales. - } -} - -//----------------------------------------------------------------------------- -// showAttachmentOverrides -//----------------------------------------------------------------------------- -void LLVOAvatar::showAttachmentOverrides(bool verbose) const -{ - std::set pos_names, scale_names; - getAttachmentOverrideNames(pos_names, scale_names); - if (pos_names.size()) - { - std::stringstream ss; - std::copy(pos_names.begin(), pos_names.end(), std::ostream_iterator(ss, ",")); - LL_INFOS() << getFullname() << " attachment positions defined for joints: " << ss.str() << "\n" << LL_ENDL; - } - else - { - LL_DEBUGS("Avatar") << getFullname() << " no attachment positions defined for any joints" << "\n" << LL_ENDL; - } - - if (scale_names.size()) - { - std::stringstream ss; - std::copy(scale_names.begin(), scale_names.end(), std::ostream_iterator(ss, ",")); - LL_INFOS() << getFullname() << " attachment scales defined for joints: " << ss.str() << "\n" << LL_ENDL; - } - else - { - LL_INFOS() << getFullname() << " no attachment scales defined for any joints" << "\n" << LL_ENDL; - } - - if (!verbose) - { - return; - } - - LLVector3 pos, scale; - LLUUID mesh_id; - S32 count = 0; - - // Bones - for (avatar_joint_list_t::const_iterator iter = mSkeleton.begin(); - iter != mSkeleton.end(); ++iter) - { - const LLJoint* pJoint = (*iter); - if (pJoint && pJoint->hasAttachmentPosOverride(pos,mesh_id)) - { - pJoint->showAttachmentPosOverrides(getFullname()); - count++; - } - if (pJoint && pJoint->hasAttachmentScaleOverride(scale,mesh_id)) - { - pJoint->showAttachmentScaleOverrides(getFullname()); - count++; - } - } - - // Attachment points - for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - const LLViewerJointAttachment *attachment_pt = (*iter).second; - if (attachment_pt && attachment_pt->hasAttachmentPosOverride(pos,mesh_id)) - { - attachment_pt->showAttachmentPosOverrides(getFullname()); - count++; - } - } - - if (count) - { - LL_DEBUGS("Avatar") << avString() << " end of pos, scale overrides" << LL_ENDL; - LL_DEBUGS("Avatar") << "=================================" << LL_ENDL; - } -} - -//----------------------------------------------------------------------------- -// removeAttachmentOverridesForObject -//----------------------------------------------------------------------------- -void LLVOAvatar::removeAttachmentOverridesForObject(LLViewerObject *vo) -{ - if (vo->getAvatar() != this && vo->getAvatarAncestor() != this) - { - LL_WARNS("Avatar") << "called with invalid avatar" << LL_ENDL; - return; - } - - // Process all children - LLViewerObject::const_child_list_t& children = vo->getChildren(); - for (LLViewerObject::const_child_list_t::const_iterator it = children.begin(); - it != children.end(); ++it) - { - LLViewerObject *childp = *it; - removeAttachmentOverridesForObject(childp); - } - - // Process self. - LLUUID mesh_id; - if (getRiggedMeshID(vo,mesh_id)) - { - removeAttachmentOverridesForObject(mesh_id); - } -} - -//----------------------------------------------------------------------------- -// removeAttachmentOverridesForObject -//----------------------------------------------------------------------------- -void LLVOAvatar::removeAttachmentOverridesForObject(const LLUUID& mesh_id) -{ - LLJoint* pJointPelvis = getJoint("mPelvis"); - const std::string av_string = avString(); - for (S32 joint_num = 0; joint_num < LL_CHARACTER_MAX_ANIMATED_JOINTS; joint_num++) - { - LLJoint *pJoint = getJoint(joint_num); - if (pJoint) - { - bool dummy; // unused - pJoint->removeAttachmentPosOverride(mesh_id, av_string, dummy); - pJoint->removeAttachmentScaleOverride(mesh_id, av_string); - } - if (pJoint && pJoint == pJointPelvis) - { - removePelvisFixup(mesh_id); - // SL-315 - pJoint->setPosition(LLVector3( 0.0f, 0.0f, 0.0f)); - } - } - - postPelvisSetRecalc(); - - mActiveOverrideMeshes.erase(mesh_id); - onActiveOverrideMeshesChanged(); -} - -//----------------------------------------------------------------------------- -// getCharacterPosition() -//----------------------------------------------------------------------------- -LLVector3 LLVOAvatar::getCharacterPosition() -{ - if (mDrawable.notNull()) - { - return mDrawable->getPositionAgent(); - } - else - { - return getPositionAgent(); - } -} - -//----------------------------------------------------------------------------- -// LLVOAvatar::getCharacterRotation() -//----------------------------------------------------------------------------- -LLQuaternion LLVOAvatar::getCharacterRotation() -{ - return getRotation(); -} - -//----------------------------------------------------------------------------- -// LLVOAvatar::getCharacterVelocity() -//----------------------------------------------------------------------------- -LLVector3 LLVOAvatar::getCharacterVelocity() -{ - return getVelocity() - mStepObjectVelocity; -} - -//----------------------------------------------------------------------------- -// LLVOAvatar::getCharacterAngularVelocity() -//----------------------------------------------------------------------------- -LLVector3 LLVOAvatar::getCharacterAngularVelocity() -{ - return getAngularVelocity(); -} - -//----------------------------------------------------------------------------- -// LLVOAvatar::getGround() -//----------------------------------------------------------------------------- -void LLVOAvatar::getGround(const LLVector3 &in_pos_agent, LLVector3 &out_pos_agent, LLVector3 &outNorm) -{ - LLVector3d z_vec(0.0f, 0.0f, 1.0f); - LLVector3d p0_global, p1_global; - - if (isUIAvatar()) - { - outNorm.setVec(z_vec); - out_pos_agent = in_pos_agent; - return; - } - - p0_global = gAgent.getPosGlobalFromAgent(in_pos_agent) + z_vec; - p1_global = gAgent.getPosGlobalFromAgent(in_pos_agent) - z_vec; - LLViewerObject *obj; - LLVector3d out_pos_global; - LLWorld::getInstance()->resolveStepHeightGlobal(this, p0_global, p1_global, out_pos_global, outNorm, &obj); - out_pos_agent = gAgent.getPosAgentFromGlobal(out_pos_global); -} - -//----------------------------------------------------------------------------- -// LLVOAvatar::getTimeDilation() -//----------------------------------------------------------------------------- -F32 LLVOAvatar::getTimeDilation() -{ - return mRegionp ? mRegionp->getTimeDilation() : 1.f; -} - -//----------------------------------------------------------------------------- -// LLVOAvatar::getPixelArea() -//----------------------------------------------------------------------------- -F32 LLVOAvatar::getPixelArea() const -{ - if (isUIAvatar()) - { - return 100000.f; - } - return mPixelArea; -} - -//----------------------------------------------------------------------------- -// LLVOAvatar::getPosGlobalFromAgent() -//----------------------------------------------------------------------------- -LLVector3d LLVOAvatar::getPosGlobalFromAgent(const LLVector3 &position) -{ - return gAgent.getPosGlobalFromAgent(position); -} - -//----------------------------------------------------------------------------- -// getPosAgentFromGlobal() -//----------------------------------------------------------------------------- -LLVector3 LLVOAvatar::getPosAgentFromGlobal(const LLVector3d &position) -{ - return gAgent.getPosAgentFromGlobal(position); -} - -//----------------------------------------------------------------------------- -// requestStopMotion() -//----------------------------------------------------------------------------- -// virtual -void LLVOAvatar::requestStopMotion( LLMotion* motion ) -{ - // Only agent avatars should handle the stop motion notifications. -} - -//----------------------------------------------------------------------------- -// loadSkeletonNode(): loads node from XML tree -//----------------------------------------------------------------------------- -//virtual -bool LLVOAvatar::loadSkeletonNode () -{ - if (!LLAvatarAppearance::loadSkeletonNode()) - { - return false; - } - - bool ignore_hud_joints = false; - initAttachmentPoints(ignore_hud_joints); - - return true; -} - -//----------------------------------------------------------------------------- -// initAttachmentPoints(): creates attachment points if needed, sets state based on avatar_lad.xml. -//----------------------------------------------------------------------------- -void LLVOAvatar::initAttachmentPoints(bool ignore_hud_joints) -{ - LLAvatarXmlInfo::attachment_info_list_t::iterator iter; - for (iter = sAvatarXmlInfo->mAttachmentInfoList.begin(); - iter != sAvatarXmlInfo->mAttachmentInfoList.end(); - ++iter) - { - LLAvatarXmlInfo::LLAvatarAttachmentInfo *info = *iter; - if (info->mIsHUDAttachment && (!isSelf() || ignore_hud_joints)) - { - //don't process hud joint for other avatars. - continue; - } - - S32 attachmentID = info->mAttachmentID; - if (attachmentID < 1 || attachmentID > 255) - { - LL_WARNS() << "Attachment point out of range [1-255]: " << attachmentID << " on attachment point " << info->mName << LL_ENDL; - continue; - } - - LLViewerJointAttachment* attachment = NULL; - bool newly_created = false; - if (mAttachmentPoints.find(attachmentID) == mAttachmentPoints.end()) - { - attachment = new LLViewerJointAttachment(); - newly_created = true; - } - else - { - attachment = mAttachmentPoints[attachmentID]; - } - - attachment->setName(info->mName); - LLJoint *parent_joint = getJoint(info->mJointName); - if (!parent_joint) - { - // If the intended parent for attachment point is unavailable, avatar_lad.xml is corrupt. - LL_WARNS() << "No parent joint by name " << info->mJointName << " found for attachment point " << info->mName << LL_ENDL; - LL_ERRS() << "Invalid avatar_lad.xml file" << LL_ENDL; - } - - if (info->mHasPosition) - { - attachment->setOriginalPosition(info->mPosition); - attachment->setDefaultPosition(info->mPosition); - } - - if (info->mHasRotation) - { - LLQuaternion rotation; - rotation.setQuat(info->mRotationEuler.mV[VX] * DEG_TO_RAD, - info->mRotationEuler.mV[VY] * DEG_TO_RAD, - info->mRotationEuler.mV[VZ] * DEG_TO_RAD); - attachment->setRotation(rotation); - } - - int group = info->mGroup; - if (group >= 0) - { - if (group < 0 || group > 9) - { - LL_WARNS() << "Invalid group number (" << group << ") for attachment point " << info->mName << LL_ENDL; - } - else - { - attachment->setGroup(group); - } - } - - attachment->setPieSlice(info->mPieMenuSlice); - attachment->setVisibleInFirstPerson(info->mVisibleFirstPerson); - attachment->setIsHUDAttachment(info->mIsHUDAttachment); - // attachment can potentially be animated, needs a number. - attachment->setJointNum(mNumBones + mNumCollisionVolumes + attachmentID - 1); - - if (newly_created) - { - mAttachmentPoints[attachmentID] = attachment; - - // now add attachment joint - parent_joint->addChild(attachment); - } - } -} - -//----------------------------------------------------------------------------- -// updateVisualParams() -//----------------------------------------------------------------------------- -void LLVOAvatar::updateVisualParams() -{ - ESex avatar_sex = (getVisualParamWeight("male") > 0.5f) ? SEX_MALE : SEX_FEMALE; - if (getSex() != avatar_sex) - { - if (mIsSitting && findMotion(avatar_sex == SEX_MALE ? ANIM_AGENT_SIT_FEMALE : ANIM_AGENT_SIT) != NULL) - { - // In some cases of gender change server changes sit motion with motion message, - // but in case of some avatars (legacy?) there is no update from server side, - // likely because server doesn't know about difference between motions - // (female and male sit ids are same server side, so it is likely unaware that it - // need to send update) - // Make sure motion is up to date - stopMotion(ANIM_AGENT_SIT); - setSex(avatar_sex); - startMotion(ANIM_AGENT_SIT); - } - else - { - setSex(avatar_sex); - } - } - - LLCharacter::updateVisualParams(); - - if (mLastSkeletonSerialNum != mSkeletonSerialNum) - { - computeBodySize(); - mLastSkeletonSerialNum = mSkeletonSerialNum; - mRoot->updateWorldMatrixChildren(); - } - - dirtyMesh(); - updateHeadOffset(); -} -//----------------------------------------------------------------------------- -// isActive() -//----------------------------------------------------------------------------- -bool LLVOAvatar::isActive() const -{ - return true; -} - -//----------------------------------------------------------------------------- -// setPixelAreaAndAngle() -//----------------------------------------------------------------------------- -void LLVOAvatar::setPixelAreaAndAngle(LLAgent &agent) -{ - if (mDrawable.isNull()) - { - return; - } - - const LLVector4a* ext = mDrawable->getSpatialExtents(); - LLVector4a center; - center.setAdd(ext[1], ext[0]); - center.mul(0.5f); - LLVector4a size; - size.setSub(ext[1], ext[0]); - size.mul(0.5f); - - mImpostorPixelArea = LLPipeline::calcPixelArea(center, size, *LLViewerCamera::getInstance()); - mPixelArea = mImpostorPixelArea; - - F32 range = mDrawable->mDistanceWRTCamera; - - if (range < 0.001f) // range == zero - { - mAppAngle = 180.f; - } - else - { - F32 radius = size.getLength3().getF32(); - mAppAngle = (F32) atan2( radius, range) * RAD_TO_DEG; - } - - // We always want to look good to ourselves - if( isSelf() ) - { - mPixelArea = llmax( mPixelArea, F32(getTexImageSize() / 16) ); - } -} - -//----------------------------------------------------------------------------- -// updateJointLODs() -//----------------------------------------------------------------------------- -bool LLVOAvatar::updateJointLODs() -{ - const F32 MAX_PIXEL_AREA = 100000000.f; - F32 lod_factor = (sLODFactor * AVATAR_LOD_TWEAK_RANGE + (1.f - AVATAR_LOD_TWEAK_RANGE)); - F32 avatar_num_min_factor = clamp_rescale(sLODFactor, 0.f, 1.f, 0.25f, 0.6f); - F32 avatar_num_factor = clamp_rescale((F32)sNumVisibleAvatars, 8, 25, 1.f, avatar_num_min_factor); - F32 area_scale = 0.16f; - - if (isSelf()) - { - if(gAgentCamera.cameraCustomizeAvatar() || gAgentCamera.cameraMouselook()) - { - mAdjustedPixelArea = MAX_PIXEL_AREA; - } - else - { - mAdjustedPixelArea = mPixelArea*area_scale; - } - } - else if (mIsDummy) - { - mAdjustedPixelArea = MAX_PIXEL_AREA; - } - else - { - // reported avatar pixel area is dependent on avatar render load, based on number of visible avatars - mAdjustedPixelArea = (F32)mPixelArea * area_scale * lod_factor * lod_factor * avatar_num_factor * avatar_num_factor; - } - - // now select meshes to render based on adjusted pixel area - LLViewerJoint* root = dynamic_cast(mRoot); - bool res = false; - if (root) - { - res = root->updateLOD(mAdjustedPixelArea, true); - } - if (res) - { - sNumLODChangesThisFrame++; - dirtyMesh(2); - return true; - } - - return false; -} - -//----------------------------------------------------------------------------- -// createDrawable() -//----------------------------------------------------------------------------- -LLDrawable *LLVOAvatar::createDrawable(LLPipeline *pipeline) -{ - pipeline->allocDrawable(this); - mDrawable->setLit(false); - - LLDrawPoolAvatar *poolp = (LLDrawPoolAvatar*)gPipeline.getPool(mIsControlAvatar ? LLDrawPool::POOL_CONTROL_AV : LLDrawPool::POOL_AVATAR); - - // Only a single face (one per avatar) - //this face will be splitted into several if its vertex buffer is too long. - mDrawable->setState(LLDrawable::ACTIVE); - mDrawable->addFace(poolp, NULL); - mDrawable->setRenderType(mIsControlAvatar ? LLPipeline::RENDER_TYPE_CONTROL_AV : LLPipeline::RENDER_TYPE_AVATAR); - - mNumInitFaces = mDrawable->getNumFaces() ; - - dirtyMesh(2); - return mDrawable; -} - - -void LLVOAvatar::updateGL() -{ - if (mMeshTexturesDirty) - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR - updateMeshTextures(); - mMeshTexturesDirty = false; - } -} - -//----------------------------------------------------------------------------- -// updateGeometry() -//----------------------------------------------------------------------------- -bool LLVOAvatar::updateGeometry(LLDrawable *drawable) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - if (!(gPipeline.hasRenderType(mIsControlAvatar ? LLPipeline::RENDER_TYPE_CONTROL_AV : LLPipeline::RENDER_TYPE_AVATAR))) - { - return true; - } - - if (!mMeshValid) - { - return true; - } - - if (!drawable) - { - LL_ERRS() << "LLVOAvatar::updateGeometry() called with NULL drawable" << LL_ENDL; - } - - return true; -} - -//----------------------------------------------------------------------------- -// updateSexDependentLayerSets() -//----------------------------------------------------------------------------- -void LLVOAvatar::updateSexDependentLayerSets() -{ - invalidateComposite( mBakedTextureDatas[BAKED_HEAD].mTexLayerSet); - invalidateComposite( mBakedTextureDatas[BAKED_UPPER].mTexLayerSet); - invalidateComposite( mBakedTextureDatas[BAKED_LOWER].mTexLayerSet); -} - -//----------------------------------------------------------------------------- -// dirtyMesh() -//----------------------------------------------------------------------------- -void LLVOAvatar::dirtyMesh() -{ - dirtyMesh(1); -} -void LLVOAvatar::dirtyMesh(S32 priority) -{ - mDirtyMesh = llmax(mDirtyMesh, priority); -} - -//----------------------------------------------------------------------------- -// getViewerJoint() -//----------------------------------------------------------------------------- -LLViewerJoint* LLVOAvatar::getViewerJoint(S32 idx) -{ - return dynamic_cast(mMeshLOD[idx]); -} - -//----------------------------------------------------------------------------- -// hideHair() -//----------------------------------------------------------------------------- -void LLVOAvatar::hideHair() -{ - mMeshLOD[MESH_ID_HAIR]->setVisible(false, true); -} - -//----------------------------------------------------------------------------- -// hideSkirt() -//----------------------------------------------------------------------------- -void LLVOAvatar::hideSkirt() -{ - mMeshLOD[MESH_ID_SKIRT]->setVisible(false, true); -} - -bool LLVOAvatar::setParent(LLViewerObject* parent) -{ - bool ret ; - if (parent == NULL) - { - getOffObject(); - ret = LLViewerObject::setParent(parent); - if (isSelf()) - { - gAgentCamera.resetCamera(); - } - } - else - { - ret = LLViewerObject::setParent(parent); - if(ret) - { - sitOnObject(parent); - } - } - return ret ; -} - -void LLVOAvatar::addChild(LLViewerObject *childp) -{ - childp->extractAttachmentItemID(); // find the inventory item this object is associated with. - if (isSelf()) - { - const LLUUID& item_id = childp->getAttachmentItemID(); - LLViewerInventoryItem *item = gInventory.getItem(item_id); - LL_DEBUGS("Avatar") << "ATT attachment child added " << (item ? item->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL; - - } - - LLViewerObject::addChild(childp); - if (childp->mDrawable) - { - if (!attachObject(childp)) - { - LL_WARNS() << "ATT addChild() failed for " - << childp->getID() - << " item " << childp->getAttachmentItemID() - << LL_ENDL; - // MAINT-3312 backout - // mPendingAttachment.push_back(childp); - } - } - else - { - mPendingAttachment.push_back(childp); - } -} - -void LLVOAvatar::removeChild(LLViewerObject *childp) -{ - LLViewerObject::removeChild(childp); - if (!detachObject(childp)) - { - LL_WARNS() << "Calling detach on non-attached object " << LL_ENDL; - } -} - -LLViewerJointAttachment* LLVOAvatar::getTargetAttachmentPoint(LLViewerObject* viewer_object) -{ - S32 attachmentID = ATTACHMENT_ID_FROM_STATE(viewer_object->getAttachmentState()); - - // This should never happen unless the server didn't process the attachment point - // correctly, but putting this check in here to be safe. - if (attachmentID & ATTACHMENT_ADD) - { - LL_WARNS() << "Got an attachment with ATTACHMENT_ADD mask, removing ( attach pt:" << attachmentID << " )" << LL_ENDL; - attachmentID &= ~ATTACHMENT_ADD; - } - - LLViewerJointAttachment* attachment = get_if_there(mAttachmentPoints, attachmentID, (LLViewerJointAttachment*)NULL); - - if (!attachment) - { - LL_WARNS() << "Object attachment point invalid: " << attachmentID - << " trying to use 1 (chest)" - << LL_ENDL; - - attachment = get_if_there(mAttachmentPoints, 1, (LLViewerJointAttachment*)NULL); // Arbitrary using 1 (chest) - if (attachment) - { - LL_WARNS() << "Object attachment point invalid: " << attachmentID - << " on object " << viewer_object->getID() - << " attachment item " << viewer_object->getAttachmentItemID() - << " falling back to 1 (chest)" - << LL_ENDL; - } - else - { - LL_WARNS() << "Object attachment point invalid: " << attachmentID - << " on object " << viewer_object->getID() - << " attachment item " << viewer_object->getAttachmentItemID() - << "Unable to use fallback attachment point 1 (chest)" - << LL_ENDL; - } - } - - return attachment; -} - -//----------------------------------------------------------------------------- -// attachObject() -//----------------------------------------------------------------------------- -const LLViewerJointAttachment *LLVOAvatar::attachObject(LLViewerObject *viewer_object) -{ - if (isSelf()) - { - const LLUUID& item_id = viewer_object->getAttachmentItemID(); - LLViewerInventoryItem *item = gInventory.getItem(item_id); - LL_DEBUGS("Avatar") << "ATT attaching object " - << (item ? item->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL; - } - LLViewerJointAttachment* attachment = getTargetAttachmentPoint(viewer_object); - - if (!attachment || !attachment->addObject(viewer_object)) - { - const LLUUID& item_id = viewer_object->getAttachmentItemID(); - LLViewerInventoryItem *item = gInventory.getItem(item_id); - LL_WARNS("Avatar") << "ATT attach failed " - << (item ? item->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL; - return 0; - } - - if (!viewer_object->isAnimatedObject()) - { - updateAttachmentOverrides(); - } - - updateVisualComplexity(); - - if (viewer_object->isSelected()) - { - LLSelectMgr::getInstance()->updateSelectionCenter(); - LLSelectMgr::getInstance()->updatePointAt(); - } - - viewer_object->refreshBakeTexture(); - - - LLViewerObject::const_child_list_t& child_list = viewer_object->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); ++iter) - { - LLViewerObject* objectp = *iter; - if (objectp) - { - objectp->refreshBakeTexture(); - } - } - - updateMeshVisibility(); - - return attachment; -} - -//----------------------------------------------------------------------------- -// getNumAttachments() -//----------------------------------------------------------------------------- -U32 LLVOAvatar::getNumAttachments() const -{ - U32 num_attachments = 0; - for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - const LLViewerJointAttachment *attachment_pt = (*iter).second; - num_attachments += attachment_pt->getNumObjects(); - } - return num_attachments; -} - -//----------------------------------------------------------------------------- -// getMaxAttachments() -//----------------------------------------------------------------------------- -S32 LLVOAvatar::getMaxAttachments() const -{ - return LLAgentBenefitsMgr::current().getAttachmentLimit(); -} - -//----------------------------------------------------------------------------- -// canAttachMoreObjects() -// Returns true if we can attach more objects. -//----------------------------------------------------------------------------- -bool LLVOAvatar::canAttachMoreObjects(U32 n) const -{ - return (getNumAttachments() + n) <= getMaxAttachments(); -} - -//----------------------------------------------------------------------------- -// getNumAnimatedObjectAttachments() -//----------------------------------------------------------------------------- -U32 LLVOAvatar::getNumAnimatedObjectAttachments() const -{ - U32 num_attachments = 0; - for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - const LLViewerJointAttachment *attachment_pt = (*iter).second; - num_attachments += attachment_pt->getNumAnimatedObjects(); - } - return num_attachments; -} - -//----------------------------------------------------------------------------- -// getMaxAnimatedObjectAttachments() -// Gets from simulator feature if available, otherwise 0. -//----------------------------------------------------------------------------- -S32 LLVOAvatar::getMaxAnimatedObjectAttachments() const -{ - return LLAgentBenefitsMgr::current().getAnimatedObjectLimit(); -} - -//----------------------------------------------------------------------------- -// canAttachMoreAnimatedObjects() -// Returns true if we can attach more animated objects. -//----------------------------------------------------------------------------- -bool LLVOAvatar::canAttachMoreAnimatedObjects(U32 n) const -{ - return (getNumAnimatedObjectAttachments() + n) <= getMaxAnimatedObjectAttachments(); -} - -//----------------------------------------------------------------------------- -// lazyAttach() -//----------------------------------------------------------------------------- -void LLVOAvatar::lazyAttach() -{ - std::vector > still_pending; - - for (U32 i = 0; i < mPendingAttachment.size(); i++) - { - LLPointer cur_attachment = mPendingAttachment[i]; - // Object might have died while we were waiting for drawable - if (!cur_attachment->isDead()) - { - if (cur_attachment->mDrawable) - { - if (isSelf()) - { - const LLUUID& item_id = cur_attachment->getAttachmentItemID(); - LLViewerInventoryItem *item = gInventory.getItem(item_id); - LL_DEBUGS("Avatar") << "ATT attaching object " - << (item ? item->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL; - } - if (!attachObject(cur_attachment)) - { // Drop it - LL_WARNS() << "attachObject() failed for " - << cur_attachment->getID() - << " item " << cur_attachment->getAttachmentItemID() - << LL_ENDL; - // MAINT-3312 backout - //still_pending.push_back(cur_attachment); - } - } - else - { - still_pending.push_back(cur_attachment); - } - } - } - - mPendingAttachment = still_pending; -} - -void LLVOAvatar::resetHUDAttachments() -{ - - for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - if (attachment->getIsHUDAttachment()) - { - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - const LLViewerObject* attached_object = attachment_iter->get(); - if (attached_object && attached_object->mDrawable.notNull()) - { - gPipeline.markMoved(attached_object->mDrawable); - } - } - } - } -} - -void LLVOAvatar::rebuildRiggedAttachments( void ) -{ - for ( attachment_map_t::iterator iter = mAttachmentPoints.begin(); iter != mAttachmentPoints.end(); ++iter ) - { - LLViewerJointAttachment* pAttachment = iter->second; - LLViewerJointAttachment::attachedobjs_vec_t::iterator attachmentIterEnd = pAttachment->mAttachedObjects.end(); - - for ( LLViewerJointAttachment::attachedobjs_vec_t::iterator attachmentIter = pAttachment->mAttachedObjects.begin(); - attachmentIter != attachmentIterEnd; ++attachmentIter) - { - const LLViewerObject* pAttachedObject = *attachmentIter; - if ( pAttachment && pAttachedObject->mDrawable.notNull() ) - { - gPipeline.markRebuild(pAttachedObject->mDrawable); - } - } - } -} -//----------------------------------------------------------------------------- -// cleanupAttachedMesh() -//----------------------------------------------------------------------------- -void LLVOAvatar::cleanupAttachedMesh( LLViewerObject* pVO ) -{ - LLUUID mesh_id; - if (getRiggedMeshID(pVO, mesh_id)) - { - // FIXME this seems like an odd place for this code. - if ( gAgentCamera.cameraCustomizeAvatar() ) - { - gAgent.unpauseAnimation(); - //Still want to refocus on head bone - gAgentCamera.changeCameraToCustomizeAvatar(); - } - } -} - -bool LLVOAvatar::hasPendingAttachedMeshes() -{ - for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - if (attachment) - { - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - LLViewerObject* objectp = attachment_iter->get(); - if (objectp) - { - LLViewerObject::const_child_list_t& child_list = objectp->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter1 = child_list.begin(); - iter1 != child_list.end(); ++iter1) - { - LLViewerObject* objectchild = *iter1; - if (objectchild && objectchild->getVolume()) - { - const LLUUID& mesh_id = objectchild->getVolume()->getParams().getSculptID(); - if (mesh_id.isNull()) - { - // No mesh nor skin info needed - continue; - } - - if (objectchild->getVolume()->isMeshAssetUnavaliable()) - { - // Mesh failed to load, do not expect it - continue; - } - - if (objectchild->mDrawable) - { - LLVOVolume* pvobj = objectchild->mDrawable->getVOVolume(); - if (pvobj) - { - if (!pvobj->isMesh()) - { - // Not a mesh - continue; - } - - if (!objectchild->getVolume()->isMeshAssetLoaded()) - { - // Waiting for mesh - return true; - } - - const LLMeshSkinInfo* skin_data = pvobj->getSkinInfo(); - if (skin_data) - { - // Skin info present, done - continue; - } - - if (pvobj->isSkinInfoUnavaliable()) - { - // Load failed or info not present, don't expect it - continue; - } - } - - // objectchild is not ready - return true; - } - } - } - } - } - } - } - return false; -} - -//----------------------------------------------------------------------------- -// detachObject() -//----------------------------------------------------------------------------- -bool LLVOAvatar::detachObject(LLViewerObject *viewer_object) -{ - for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - - if (attachment->isObjectAttached(viewer_object)) - { - updateVisualComplexity(); - bool is_animated_object = viewer_object->isAnimatedObject(); - cleanupAttachedMesh(viewer_object); - - attachment->removeObject(viewer_object); - if (!is_animated_object) - { - updateAttachmentOverrides(); - } - viewer_object->refreshBakeTexture(); - - LLViewerObject::const_child_list_t& child_list = viewer_object->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter1 = child_list.begin(); - iter1 != child_list.end(); ++iter1) - { - LLViewerObject* objectp = *iter1; - if (objectp) - { - objectp->refreshBakeTexture(); - } - } - - updateMeshVisibility(); - - LL_DEBUGS() << "Detaching object " << viewer_object->mID << " from " << attachment->getName() << LL_ENDL; - return true; - } - } - - std::vector >::iterator iter = std::find(mPendingAttachment.begin(), mPendingAttachment.end(), viewer_object); - if (iter != mPendingAttachment.end()) - { - mPendingAttachment.erase(iter); - return true; - } - - return false; -} - -//----------------------------------------------------------------------------- -// sitDown() -//----------------------------------------------------------------------------- -void LLVOAvatar::sitDown(bool bSitting) -{ - mIsSitting = bSitting; - if (isSelf()) - { - // Update Movement Controls according to own Sitting mode - LLFloaterMove::setSittingMode(bSitting); - } -} - -//----------------------------------------------------------------------------- -// sitOnObject() -//----------------------------------------------------------------------------- -void LLVOAvatar::sitOnObject(LLViewerObject *sit_object) -{ - if (isSelf()) - { - // Might be first sit - //LLFirstUse::useSit(); - - gAgent.setFlying(false); - gAgentCamera.setThirdPersonHeadOffset(LLVector3::zero); - //interpolate to new camera position - gAgentCamera.startCameraAnimation(); - // make sure we are not trying to autopilot - gAgent.stopAutoPilot(); - gAgentCamera.setupSitCamera(); - if (gAgentCamera.getForceMouselook()) - { - gAgentCamera.changeCameraToMouselook(); - } - - if (gAgentCamera.getFocusOnAvatar() && LLToolMgr::getInstance()->inEdit()) - { - LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(); - if (node && node->mValid) - { - LLViewerObject* root_object = node->getObject(); - if (root_object == sit_object) - { - LLFloaterTools::sPreviousFocusOnAvatar = true; - } - } - } - } - - if (mDrawable.isNull()) - { - return; - } - LLQuaternion inv_obj_rot = ~sit_object->getRenderRotation(); - LLVector3 obj_pos = sit_object->getRenderPosition(); - - LLVector3 rel_pos = getRenderPosition() - obj_pos; - rel_pos.rotVec(inv_obj_rot); - - mDrawable->mXform.setPosition(rel_pos); - mDrawable->mXform.setRotation(mDrawable->getWorldRotation() * inv_obj_rot); - - gPipeline.markMoved(mDrawable, true); - // Notice that removing sitDown() from here causes avatars sitting on - // objects to be not rendered for new arrivals. See EXT-6835 and EXT-1655. - sitDown(true); - mRoot->getXform()->setParent(&sit_object->mDrawable->mXform); // LLVOAvatar::sitOnObject - // SL-315 - mRoot->setPosition(getPosition()); - mRoot->updateWorldMatrixChildren(); - - stopMotion(ANIM_AGENT_BODY_NOISE); - - gAgentCamera.setInitSitRot(gAgent.getFrameAgent().getQuaternion()); -} - -//----------------------------------------------------------------------------- -// getOffObject() -//----------------------------------------------------------------------------- -void LLVOAvatar::getOffObject() -{ - if (mDrawable.isNull()) - { - return; - } - - LLViewerObject* sit_object = (LLViewerObject*)getParent(); - - if (sit_object) - { - stopMotionFromSource(sit_object->getID()); - LLFollowCamMgr::getInstance()->setCameraActive(sit_object->getID(), false); - - LLViewerObject::const_child_list_t& child_list = sit_object->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); ++iter) - { - LLViewerObject* child_objectp = *iter; - - stopMotionFromSource(child_objectp->getID()); - LLFollowCamMgr::getInstance()->setCameraActive(child_objectp->getID(), false); - } - } - - // assumes that transform will not be updated with drawable still having a parent - // or that drawable had no parent from the start - LLVector3 cur_position_world = mDrawable->getWorldPosition(); - LLQuaternion cur_rotation_world = mDrawable->getWorldRotation(); - - if (mLastRootPos.length() >= MAX_STANDOFF_FROM_ORIGIN - && (cur_position_world.length() < MAX_STANDOFF_FROM_ORIGIN - || dist_vec(cur_position_world, mLastRootPos) > MAX_STANDOFF_DISTANCE_CHANGE)) - { - // Most likely drawable got updated too early or some updates were missed - we got relative position to non-existing parent - // restore coordinates from cache - cur_position_world = mLastRootPos; - } - - // set *local* position based on last *world* position, since we're unparenting the avatar - mDrawable->mXform.setPosition(cur_position_world); - mDrawable->mXform.setRotation(cur_rotation_world); - - gPipeline.markMoved(mDrawable, true); - - sitDown(false); - - mRoot->getXform()->setParent(NULL); // LLVOAvatar::getOffObject - // SL-315 - mRoot->setPosition(cur_position_world); - mRoot->setRotation(cur_rotation_world); - mRoot->getXform()->update(); - - if (mEnableDefaultMotions) - { - startMotion(ANIM_AGENT_BODY_NOISE); - } - - if (isSelf()) - { - LLQuaternion av_rot = gAgent.getFrameAgent().getQuaternion(); - LLQuaternion obj_rot = sit_object ? sit_object->getRenderRotation() : LLQuaternion::DEFAULT; - av_rot = av_rot * obj_rot; - LLVector3 at_axis = LLVector3::x_axis; - at_axis = at_axis * av_rot; - at_axis.mV[VZ] = 0.f; - at_axis.normalize(); - gAgent.resetAxes(at_axis); - gAgentCamera.setThirdPersonHeadOffset(LLVector3(0.f, 0.f, 1.f)); - gAgentCamera.setSitCamera(LLUUID::null); - } -} - -//----------------------------------------------------------------------------- -// findAvatarFromAttachment() -//----------------------------------------------------------------------------- -// static -LLVOAvatar* LLVOAvatar::findAvatarFromAttachment( LLViewerObject* obj ) -{ - if( obj->isAttachment() ) - { - do - { - obj = (LLViewerObject*) obj->getParent(); - } - while( obj && !obj->isAvatar() ); - - if( obj && !obj->isDead() ) - { - return (LLVOAvatar*)obj; - } - } - return NULL; -} - -S32 LLVOAvatar::getAttachmentCount() const -{ - S32 count = 0; - - for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); iter != mAttachmentPoints.end(); ++iter) - { - LLViewerJointAttachment* pAttachment = iter->second; - count += pAttachment->mAttachedObjects.size(); - } - - return count; -} - -bool LLVOAvatar::isWearingWearableType(LLWearableType::EType type) const -{ - if (mIsDummy) return true; - - if (isSelf()) - { - return LLAvatarAppearance::isWearingWearableType(type); - } - - switch(type) - { - case LLWearableType::WT_SHAPE: - case LLWearableType::WT_SKIN: - case LLWearableType::WT_HAIR: - case LLWearableType::WT_EYES: - return true; // everyone has all bodyparts - default: - break; // Do nothing - } - - for (LLAvatarAppearanceDictionary::Textures::const_iterator tex_iter = LLAvatarAppearance::getDictionary()->getTextures().begin(); - tex_iter != LLAvatarAppearance::getDictionary()->getTextures().end(); - ++tex_iter) - { - const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = tex_iter->second; - if (texture_dict->mWearableType == type) - { - // Thus, you must check to see if the corresponding baked texture is defined. - // NOTE: this is a poor substitute if you actually want to know about individual pieces of clothing - // this works for detecting a skirt (most important), but is ineffective at any piece of clothing that - // gets baked into a texture that always exists (upper or lower). - if (texture_dict->mIsUsedByBakedTexture) - { - const EBakedTextureIndex baked_index = texture_dict->mBakedTextureIndex; - return isTextureDefined(LLAvatarAppearance::getDictionary()->getBakedTexture(baked_index)->mTextureIndex); - } - return false; - } - } - return false; -} - -LLViewerObject * LLVOAvatar::findAttachmentByID( const LLUUID & target_id ) const -{ - for(attachment_map_t::const_iterator attachment_points_iter = mAttachmentPoints.begin(); - attachment_points_iter != gAgentAvatarp->mAttachmentPoints.end(); - ++attachment_points_iter) - { - LLViewerJointAttachment* attachment = attachment_points_iter->second; - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - LLViewerObject *attached_object = attachment_iter->get(); - if (attached_object && - attached_object->getID() == target_id) - { - return attached_object; - } - } - } - - return NULL; -} - -// virtual -void LLVOAvatar::invalidateComposite( LLTexLayerSet* layerset) -{ -} - -void LLVOAvatar::invalidateAll() -{ -} - -// virtual -void LLVOAvatar::onGlobalColorChanged(const LLTexGlobalColor* global_color) -{ - if (global_color == mTexSkinColor) - { - invalidateComposite( mBakedTextureDatas[BAKED_HEAD].mTexLayerSet); - invalidateComposite( mBakedTextureDatas[BAKED_UPPER].mTexLayerSet); - invalidateComposite( mBakedTextureDatas[BAKED_LOWER].mTexLayerSet); - } - else if (global_color == mTexHairColor) - { - invalidateComposite( mBakedTextureDatas[BAKED_HEAD].mTexLayerSet); - invalidateComposite( mBakedTextureDatas[BAKED_HAIR].mTexLayerSet); - - // ! BACKWARDS COMPATIBILITY ! - // Fix for dealing with avatars from viewers that don't bake hair. - if (!isTextureDefined(mBakedTextureDatas[BAKED_HAIR].mTextureIndex)) - { - LLColor4 color = mTexHairColor->getColor(); - avatar_joint_mesh_list_t::iterator iter = mBakedTextureDatas[BAKED_HAIR].mJointMeshes.begin(); - avatar_joint_mesh_list_t::iterator end = mBakedTextureDatas[BAKED_HAIR].mJointMeshes.end(); - for (; iter != end; ++iter) - { - LLAvatarJointMesh* mesh = (*iter); - if (mesh) - { - mesh->setColor( color ); - } - } - } - } - else if (global_color == mTexEyeColor) - { - // LL_INFOS() << "invalidateComposite cause: onGlobalColorChanged( eyecolor )" << LL_ENDL; - invalidateComposite( mBakedTextureDatas[BAKED_EYES].mTexLayerSet); - } - updateMeshTextures(); -} - -// virtual -// Do rigged mesh attachments display with this av? -bool LLVOAvatar::shouldRenderRigged() const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - - if (getOverallAppearance() == AOA_NORMAL) - { - return true; - } - // TBD - render for AOA_JELLYDOLL? - return false; -} - -// FIXME: We have an mVisible member, set in updateVisibility(), but this -// function doesn't return it! isVisible() and mVisible are used -// different places for different purposes. mVisible seems to be more -// related to whether the actual avatar mesh is shown, and isVisible() -// to whether anything about the avatar is displayed in the scene. -// Maybe better naming could make this clearer? -bool LLVOAvatar::isVisible() const -{ - return mDrawable.notNull() - && (!mOrphaned || isSelf()) - && (mDrawable->isVisible() || mIsDummy); -} - -// Determine if we have enough avatar data to render -bool LLVOAvatar::getIsCloud() const -{ - if (mIsDummy) - { - return false; - } - - return ( ((const_cast(this))->visualParamWeightsAreDefault())// Do we have a shape? - || ( !isTextureDefined(TEX_LOWER_BAKED) - || !isTextureDefined(TEX_UPPER_BAKED) - || !isTextureDefined(TEX_HEAD_BAKED) - ) - ); -} - -void LLVOAvatar::updateRezzedStatusTimers(S32 rez_status) -{ - // State machine for rezzed status. Statuses are -1 on startup, 0 - // = cloud, 1 = gray, 2 = downloading, 3 = waiting for attachments, 4 = full. - // Purpose is to collect time data for each it takes avatar to reach - // various loading landmarks: gray, textured (partial), textured fully. - - if (rez_status != mLastRezzedStatus) - { - LL_DEBUGS("Avatar") << avString() << "rez state change: " << mLastRezzedStatus << " -> " << rez_status << LL_ENDL; - - if (mLastRezzedStatus == -1 && rez_status != -1) - { - // First time initialization, start all timers. - for (S32 i = 1; i < 4; i++) - { - startPhase("load_" + LLVOAvatar::rezStatusToString(i)); - startPhase("first_load_" + LLVOAvatar::rezStatusToString(i)); - } - } - if (rez_status < mLastRezzedStatus) - { - // load level has decreased. start phase timers for higher load levels. - for (S32 i = rez_status+1; i <= mLastRezzedStatus; i++) - { - startPhase("load_" + LLVOAvatar::rezStatusToString(i)); - } - } - else if (rez_status > mLastRezzedStatus) - { - // load level has increased. stop phase timers for lower and equal load levels. - for (S32 i = llmax(mLastRezzedStatus+1,1); i <= rez_status; i++) - { - stopPhase("load_" + LLVOAvatar::rezStatusToString(i)); - stopPhase("first_load_" + LLVOAvatar::rezStatusToString(i), false); - } - if (rez_status == 4) - { - // "fully loaded", mark any pending appearance change complete. - selfStopPhase("update_appearance_from_cof"); - selfStopPhase("wear_inventory_category", false); - selfStopPhase("process_initial_wearables_update", false); - - updateVisualComplexity(); - } - } - mLastRezzedStatus = rez_status; - - static LLUICachedControl show_rez_status("NameTagDebugAVRezState", false); - if (show_rez_status) - { - mNameIsSet = false; - } - } -} - -void LLVOAvatar::clearPhases() -{ - getPhases().clearPhases(); -} - -void LLVOAvatar::startPhase(const std::string& phase_name) -{ - F32 elapsed = 0.0; - bool completed = false; - bool found = getPhases().getPhaseValues(phase_name, elapsed, completed); - //LL_DEBUGS("Avatar") << avString() << " phase state " << phase_name - // << " found " << found << " elapsed " << elapsed << " completed " << completed << LL_ENDL; - if (found) - { - if (!completed) - { - LL_DEBUGS("Avatar") << avString() << "no-op, start when started already for " << phase_name << LL_ENDL; - return; - } - } - LL_DEBUGS("Avatar") << "started phase " << phase_name << LL_ENDL; - getPhases().startPhase(phase_name); -} - -void LLVOAvatar::stopPhase(const std::string& phase_name, bool err_check) -{ - F32 elapsed = 0.0; - bool completed = false; - if (getPhases().getPhaseValues(phase_name, elapsed, completed)) - { - if (!completed) - { - getPhases().stopPhase(phase_name); - completed = true; - logMetricsTimerRecord(phase_name, elapsed, completed); - LL_DEBUGS("Avatar") << avString() << "stopped phase " << phase_name << " elapsed " << elapsed << LL_ENDL; - } - else - { - if (err_check) - { - LL_DEBUGS("Avatar") << "no-op, stop when stopped already for " << phase_name << LL_ENDL; - } - } - } - else - { - if (err_check) - { - LL_DEBUGS("Avatar") << "no-op, stop when not started for " << phase_name << LL_ENDL; - } - } -} - -void LLVOAvatar::logPendingPhases() -{ - if (!isAgentAvatarValid()) - { - return; - } - - for (LLViewerStats::phase_map_t::iterator it = getPhases().begin(); - it != getPhases().end(); - ++it) - { - const std::string& phase_name = it->first; - F32 elapsed; - bool completed; - if (getPhases().getPhaseValues(phase_name, elapsed, completed)) - { - if (!completed) - { - logMetricsTimerRecord(phase_name, elapsed, completed); - } - } - } -} - -//static -void LLVOAvatar::logPendingPhasesAllAvatars() -{ - for (std::vector::iterator iter = LLCharacter::sInstances.begin(); - iter != LLCharacter::sInstances.end(); ++iter) - { - LLVOAvatar* inst = (LLVOAvatar*) *iter; - if( inst->isDead() ) - { - continue; - } - inst->logPendingPhases(); - } -} - -void LLVOAvatar::logMetricsTimerRecord(const std::string& phase_name, F32 elapsed, bool completed) -{ - if (!isAgentAvatarValid()) - { - return; - } - - LLSD record; - record["timer_name"] = phase_name; - record["avatar_id"] = getID(); - record["elapsed"] = elapsed; - record["completed"] = completed; - U32 grid_x(0), grid_y(0); - if (getRegion() && LLWorld::instance().isRegionListed(getRegion())) - { - record["central_bake_version"] = LLSD::Integer(getRegion()->getCentralBakeVersion()); - grid_from_region_handle(getRegion()->getHandle(), &grid_x, &grid_y); - } - record["grid_x"] = LLSD::Integer(grid_x); - record["grid_y"] = LLSD::Integer(grid_y); - record["is_using_server_bakes"] = true; - record["is_self"] = isSelf(); - - if (isAgentAvatarValid()) - { - gAgentAvatarp->addMetricsTimerRecord(record); - } -} - -// call periodically to keep isFullyLoaded up to date. -// returns true if the value has changed. -bool LLVOAvatar::updateIsFullyLoaded() -{ - S32 rez_status = getRezzedStatus(); - bool loading = rez_status == 0; - if (mFirstFullyVisible && !mIsControlAvatar) - { - loading = ((rez_status < 2) - // Wait at least 60s for unfinished textures to finish on first load, - // don't wait forever, it might fail. Even if it will eventually load by - // itself and update mLoadedCallbackTextures (or fail and clean the list), - // avatars are more time-sensitive than textures and can't wait that long. - || (mLoadedCallbackTextures < mCallbackTextureList.size() && mLastTexCallbackAddedTime.getElapsedTimeF32() < MAX_TEXTURE_WAIT_TIME_SEC) - || !mPendingAttachment.empty() - || (rez_status < 3 && !isFullyBaked()) - || hasPendingAttachedMeshes() - ); - - // compare amount of attachments to one reported by simulator - if (!loading && !isSelf() && rez_status < 4 && mLastCloudAttachmentCount < mSimAttachments.size()) - { - S32 attachment_count = getAttachmentCount(); - if (mLastCloudAttachmentCount != attachment_count) - { - mLastCloudAttachmentCount = attachment_count; - if (attachment_count != mSimAttachments.size()) - { - // attachment count changed, but still below desired, wait for more updates - mLastCloudAttachmentChangeTime.reset(); - loading = true; - } - } - else if (mLastCloudAttachmentChangeTime.getElapsedTimeF32() < MAX_ATTACHMENT_WAIT_TIME_SEC) - { - // waiting - loading = true; - } - } - } - updateRezzedStatusTimers(rez_status); - updateRuthTimer(loading); - return processFullyLoadedChange(loading); -} - -void LLVOAvatar::updateRuthTimer(bool loading) -{ - if (isSelf() || !loading) - { - return; - } - - if (mPreviousFullyLoaded) - { - mRuthTimer.reset(); - debugAvatarRezTime("AvatarRezCloudNotification","became cloud"); - } - - const F32 LOADING_TIMEOUT__SECONDS = 120.f; - if (mRuthTimer.getElapsedTimeF32() > LOADING_TIMEOUT__SECONDS) - { - LL_DEBUGS("Avatar") << avString() - << "Ruth Timer timeout: Missing texture data for '" << getFullname() << "' " - << "( Params loaded : " << !visualParamWeightsAreDefault() << " ) " - << "( Lower : " << isTextureDefined(TEX_LOWER_BAKED) << " ) " - << "( Upper : " << isTextureDefined(TEX_UPPER_BAKED) << " ) " - << "( Head : " << isTextureDefined(TEX_HEAD_BAKED) << " )." - << LL_ENDL; - - LLAvatarPropertiesProcessor::getInstance()->sendAvatarTexturesRequest(getID()); - mRuthTimer.reset(); - } -} - -bool LLVOAvatar::processFullyLoadedChange(bool loading) -{ - // We wait a little bit before giving the 'all clear', to let things to - // settle down: models to snap into place, textures to get first packets, - // LODs to load. - const F32 LOADED_DELAY = 1.f; - - if (loading) - { - mFullyLoadedTimer.reset(); - } - - if (mFirstFullyVisible) - { - F32 first_use_delay = FIRST_APPEARANCE_CLOUD_MIN_DELAY; - if (!isSelf() && loading) - { - // Note that textures can causes 60s delay on thier own - // so this delay might end up on top of textures' delay - first_use_delay = llclamp( - mFirstAppearanceMessageTimer.getElapsedTimeF32(), - FIRST_APPEARANCE_CLOUD_MIN_DELAY, - FIRST_APPEARANCE_CLOUD_MAX_DELAY); - - if (shouldImpostor()) - { - // Impostors are less of a priority, - // let them stay cloud longer - first_use_delay *= FIRST_APPEARANCE_CLOUD_IMPOSTOR_MODIFIER; - } - } - mFullyLoaded = (mFullyLoadedTimer.getElapsedTimeF32() > first_use_delay); - } - else - { - mFullyLoaded = (mFullyLoadedTimer.getElapsedTimeF32() > LOADED_DELAY); - } - - if (!mPreviousFullyLoaded && !loading && mFullyLoaded) - { - debugAvatarRezTime("AvatarRezNotification", "fully loaded"); - } - - // did our loading state "change" from last call? - // FIXME runway - why are we updating every 30 calls even if nothing has changed? - // This causes updateLOD() to run every 30 frames, among other things. - const S32 UPDATE_RATE = 30; - bool changed = - ((mFullyLoaded != mPreviousFullyLoaded) || // if the value is different from the previous call - (!mFullyLoadedInitialized) || // if we've never been called before - (mFullyLoadedFrameCounter % UPDATE_RATE == 0)); // every now and then issue a change - bool fully_loaded_changed = (mFullyLoaded != mPreviousFullyLoaded); - - mPreviousFullyLoaded = mFullyLoaded; - mFullyLoadedInitialized = true; - mFullyLoadedFrameCounter++; - - if (changed && isSelf()) - { - // to know about outfit switching - LLAvatarRenderNotifier::getInstance()->updateNotificationState(); - } - - if (fully_loaded_changed && !isSelf() && mFullyLoaded && isImpostor()) - { - // Fix for jellydoll initially invisible - mNeedsImpostorUpdate = true; - mLastImpostorUpdateReason = 6; - } - return changed; -} - -bool LLVOAvatar::isFullyLoaded() const -{ - return (mRenderUnloadedAvatar || mFullyLoaded); -} - -bool LLVOAvatar::isTooComplex() const -{ - bool too_complex; - static LLCachedControl compelxity_render_mode(gSavedSettings, "RenderAvatarComplexityMode"); - bool render_friend = (LLAvatarTracker::instance().isBuddy(getID()) && compelxity_render_mode > AV_RENDER_LIMIT_BY_COMPLEXITY); - - if (isSelf() || render_friend || mVisuallyMuteSetting == AV_ALWAYS_RENDER) - { - too_complex = false; - } - else if (compelxity_render_mode == AV_RENDER_ONLY_SHOW_FRIENDS && !mIsControlAvatar) - { - too_complex = true; - } - else - { - // Determine if visually muted or not - static LLCachedControl max_render_cost(gSavedSettings, "RenderAvatarMaxComplexity", 0U); - static LLCachedControl max_attachment_area(gSavedSettings, "RenderAutoMuteSurfaceAreaLimit", 1000.0f); - // If the user has chosen unlimited max complexity, we also disregard max attachment area - // so that unlimited will completely disable the overly complex impostor rendering - // yes, this leaves them vulnerable to griefing objects... their choice - too_complex = ( max_render_cost > 0 - && (mVisualComplexity > max_render_cost - || (max_attachment_area > 0.0f && mAttachmentSurfaceArea > max_attachment_area) - )); - } - - return too_complex; -} - -bool LLVOAvatar::isTooSlow() const -{ - static LLCachedControl compelxity_render_mode(gSavedSettings, "RenderAvatarComplexityMode"); - bool render_friend = (LLAvatarTracker::instance().isBuddy(getID()) && compelxity_render_mode > AV_RENDER_LIMIT_BY_COMPLEXITY); - - if (render_friend || mVisuallyMuteSetting == AV_ALWAYS_RENDER) - { - return false; - } - else if (compelxity_render_mode == AV_RENDER_ONLY_SHOW_FRIENDS && !mIsControlAvatar) - { - return true; - } - return mTooSlow; -} - -// Udpate Avatar state based on render time -void LLVOAvatar::updateTooSlow() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - static LLCachedControl compelxity_render_mode(gSavedSettings, "RenderAvatarComplexityMode"); - static LLCachedControl allowSelfImpostor(gSavedSettings, "AllowSelfImpostor"); - const auto id = getID(); - - // mTooSlow - Is the avatar flagged as being slow (includes shadow time) - // mTooSlowWithoutShadows - Is the avatar flagged as being slow even with shadows removed. - - // get max render time in ms - F32 max_art_ms = (F32) (LLPerfStats::renderAvatarMaxART_ns / 1000000.0); - - bool autotune = LLPerfStats::tunables.userAutoTuneEnabled && !mIsControlAvatar && !isSelf(); - - bool ignore_tune = false; - if (autotune && sAVsIgnoringARTLimit.size() > 0) - { - auto it = std::find(sAVsIgnoringARTLimit.begin(), sAVsIgnoringARTLimit.end(), mID); - if (it != sAVsIgnoringARTLimit.end()) - { - S32 index = it - sAVsIgnoringARTLimit.begin(); - ignore_tune = (index < (MIN_NONTUNED_AVS - sAvatarsNearby + 1 + LLPerfStats::tunedAvatars)); - } - } - - bool exceeds_max_ART = - ((LLPerfStats::renderAvatarMaxART_ns > 0) && - (mGPURenderTime >= max_art_ms)); // NOTE: don't use getGPURenderTime accessor here to avoid "isTooSlow" feedback loop - - if (exceeds_max_ART && !ignore_tune) - { - mTooSlow = true; - - if(!mTooSlowWithoutShadows) // if we were not previously above the full impostor cap - { - bool always_render_friends = compelxity_render_mode > AV_RENDER_LIMIT_BY_COMPLEXITY; - bool render_friend_or_exception = (always_render_friends && LLAvatarTracker::instance().isBuddy( id ) ) || - ( getVisualMuteSettings() == LLVOAvatar::AV_ALWAYS_RENDER ); - if( (!isSelf() || allowSelfImpostor) && !render_friend_or_exception) - { - // Note: slow rendering Friends still get their shadows zapped. - mTooSlowWithoutShadows = (getGPURenderTime()*2.f >= max_art_ms) // NOTE: assumes shadow rendering doubles render time - || (compelxity_render_mode == AV_RENDER_ONLY_SHOW_FRIENDS && !mIsControlAvatar); - } - } - } - else - { - mTooSlow = false; - mTooSlowWithoutShadows = false; - - if (ignore_tune) - { - return; - } - } - if(mTooSlow && !mTuned) - { - LLPerfStats::tunedAvatars++; // increment the number of avatars that have been tweaked. - mTuned = true; - } - else if(!mTooSlow && mTuned) - { - LLPerfStats::tunedAvatars--; - mTuned = false; - } -} - -//----------------------------------------------------------------------------- -// findMotion() -//----------------------------------------------------------------------------- -LLMotion* LLVOAvatar::findMotion(const LLUUID& id) const -{ - return mMotionController.findMotion(id); -} - -// This is a semi-deprecated debugging tool - meshes will not show as -// colorized if using deferred rendering. -void LLVOAvatar::debugColorizeSubMeshes(U32 i, const LLColor4& color) -{ - if (gSavedSettings.getBOOL("DebugAvatarCompositeBaked")) - { - avatar_joint_mesh_list_t::iterator iter = mBakedTextureDatas[i].mJointMeshes.begin(); - avatar_joint_mesh_list_t::iterator end = mBakedTextureDatas[i].mJointMeshes.end(); - for (; iter != end; ++iter) - { - LLAvatarJointMesh* mesh = (*iter); - if (mesh) - { - mesh->setColor(color); - } - } - } -} - - -//----------------------------------------------------------------------------- -// updateMeshVisibility() -// Hide the mesh joints if attachments are using baked textures -//----------------------------------------------------------------------------- -void LLVOAvatar::updateMeshVisibility() -{ - bool bake_flag[BAKED_NUM_INDICES]; - memset(bake_flag, 0, BAKED_NUM_INDICES*sizeof(bool)); - - if (getOverallAppearance() == AOA_NORMAL) - { - for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - if (attachment) - { - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - LLViewerObject *objectp = attachment_iter->get(); - if (objectp) - { - for (int face_index = 0; face_index < objectp->getNumTEs(); face_index++) - { - LLTextureEntry* tex_entry = objectp->getTE(face_index); - bake_flag[BAKED_HEAD] |= (tex_entry->getID() == IMG_USE_BAKED_HEAD); - bake_flag[BAKED_EYES] |= (tex_entry->getID() == IMG_USE_BAKED_EYES); - bake_flag[BAKED_HAIR] |= (tex_entry->getID() == IMG_USE_BAKED_HAIR); - bake_flag[BAKED_LOWER] |= (tex_entry->getID() == IMG_USE_BAKED_LOWER); - bake_flag[BAKED_UPPER] |= (tex_entry->getID() == IMG_USE_BAKED_UPPER); - bake_flag[BAKED_SKIRT] |= (tex_entry->getID() == IMG_USE_BAKED_SKIRT); - bake_flag[BAKED_LEFT_ARM] |= (tex_entry->getID() == IMG_USE_BAKED_LEFTARM); - bake_flag[BAKED_LEFT_LEG] |= (tex_entry->getID() == IMG_USE_BAKED_LEFTLEG); - bake_flag[BAKED_AUX1] |= (tex_entry->getID() == IMG_USE_BAKED_AUX1); - bake_flag[BAKED_AUX2] |= (tex_entry->getID() == IMG_USE_BAKED_AUX2); - bake_flag[BAKED_AUX3] |= (tex_entry->getID() == IMG_USE_BAKED_AUX3); - } - } - - LLViewerObject::const_child_list_t& child_list = objectp->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter1 = child_list.begin(); - iter1 != child_list.end(); ++iter1) - { - LLViewerObject* objectchild = *iter1; - if (objectchild) - { - for (int face_index = 0; face_index < objectchild->getNumTEs(); face_index++) - { - LLTextureEntry* tex_entry = objectchild->getTE(face_index); - bake_flag[BAKED_HEAD] |= (tex_entry->getID() == IMG_USE_BAKED_HEAD); - bake_flag[BAKED_EYES] |= (tex_entry->getID() == IMG_USE_BAKED_EYES); - bake_flag[BAKED_HAIR] |= (tex_entry->getID() == IMG_USE_BAKED_HAIR); - bake_flag[BAKED_LOWER] |= (tex_entry->getID() == IMG_USE_BAKED_LOWER); - bake_flag[BAKED_UPPER] |= (tex_entry->getID() == IMG_USE_BAKED_UPPER); - bake_flag[BAKED_SKIRT] |= (tex_entry->getID() == IMG_USE_BAKED_SKIRT); - bake_flag[BAKED_LEFT_ARM] |= (tex_entry->getID() == IMG_USE_BAKED_LEFTARM); - bake_flag[BAKED_LEFT_LEG] |= (tex_entry->getID() == IMG_USE_BAKED_LEFTLEG); - bake_flag[BAKED_AUX1] |= (tex_entry->getID() == IMG_USE_BAKED_AUX1); - bake_flag[BAKED_AUX2] |= (tex_entry->getID() == IMG_USE_BAKED_AUX2); - bake_flag[BAKED_AUX3] |= (tex_entry->getID() == IMG_USE_BAKED_AUX3); - } - } - } - } - } - } - } - - //LL_INFOS() << "head " << bake_flag[BAKED_HEAD] << "eyes " << bake_flag[BAKED_EYES] << "hair " << bake_flag[BAKED_HAIR] << "lower " << bake_flag[BAKED_LOWER] << "upper " << bake_flag[BAKED_UPPER] << "skirt " << bake_flag[BAKED_SKIRT] << LL_ENDL; - - for (S32 i = 0; i < mMeshLOD.size(); i++) - { - LLAvatarJoint* joint = mMeshLOD[i]; - if (i == MESH_ID_HAIR) - { - joint->setVisible(!bake_flag[BAKED_HAIR], true); - } - else if (i == MESH_ID_HEAD) - { - joint->setVisible(!bake_flag[BAKED_HEAD], true); - } - else if (i == MESH_ID_SKIRT) - { - joint->setVisible(!bake_flag[BAKED_SKIRT], true); - } - else if (i == MESH_ID_UPPER_BODY) - { - joint->setVisible(!bake_flag[BAKED_UPPER], true); - } - else if (i == MESH_ID_LOWER_BODY) - { - joint->setVisible(!bake_flag[BAKED_LOWER], true); - } - else if (i == MESH_ID_EYEBALL_LEFT) - { - joint->setVisible(!bake_flag[BAKED_EYES], true); - } - else if (i == MESH_ID_EYEBALL_RIGHT) - { - joint->setVisible(!bake_flag[BAKED_EYES], true); - } - else if (i == MESH_ID_EYELASH) - { - joint->setVisible(!bake_flag[BAKED_HEAD], true); - } - } -} - -//----------------------------------------------------------------------------- -// updateMeshTextures() -// Uses the current TE values to set the meshes' and layersets' textures. -//----------------------------------------------------------------------------- -// virtual -void LLVOAvatar::updateMeshTextures() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR - static S32 update_counter = 0; - mBakedTextureDebugText.clear(); - - // if user has never specified a texture, assign the default - for (U32 i=0; i < getNumTEs(); i++) - { - const LLViewerTexture* te_image = getImage(i, 0); - if(!te_image || te_image->getID().isNull() || (te_image->getID() == IMG_DEFAULT)) - { - // IMG_DEFAULT_AVATAR = a special texture that's never rendered. - const LLUUID& image_id = (i == TEX_HAIR ? IMG_DEFAULT : IMG_DEFAULT_AVATAR); - setImage(i, LLViewerTextureManager::getFetchedTexture(image_id), 0); - } - } - - const bool other_culled = !isSelf() && mCulled; - LLLoadedCallbackEntry::source_callback_list_t* src_callback_list = NULL ; - bool paused = false; - if(!isSelf()) - { - src_callback_list = &mCallbackTextureList ; - paused = !isVisible(); - } - - std::vector is_layer_baked; - is_layer_baked.resize(mBakedTextureDatas.size(), false); - - std::vector use_lkg_baked_layer; // lkg = "last known good" - use_lkg_baked_layer.resize(mBakedTextureDatas.size(), false); - - mBakedTextureDebugText += llformat("%06d\n",update_counter++); - mBakedTextureDebugText += "indx layerset linvld ltda ilb ulkg ltid\n"; - for (U32 i=0; i < mBakedTextureDatas.size(); i++) - { - is_layer_baked[i] = isTextureDefined(mBakedTextureDatas[i].mTextureIndex); - LLViewerTexLayerSet* layerset = NULL; - bool layerset_invalid = false; - if (!other_culled) - { - // When an avatar is changing clothes and not in Appearance mode, - // use the last-known good baked texture until it finishes the first - // render of the new layerset. - layerset = getTexLayerSet(i); - layerset_invalid = layerset && ( !layerset->getViewerComposite()->isInitialized() - || !layerset->isLocalTextureDataAvailable() ); - use_lkg_baked_layer[i] = (!is_layer_baked[i] - && (mBakedTextureDatas[i].mLastTextureID != IMG_DEFAULT_AVATAR) - && layerset_invalid); - if (use_lkg_baked_layer[i]) - { - layerset->setUpdatesEnabled(true); - } - } - else - { - use_lkg_baked_layer[i] = (!is_layer_baked[i] - && mBakedTextureDatas[i].mLastTextureID != IMG_DEFAULT_AVATAR); - } - - std::string last_id_string; - if (mBakedTextureDatas[i].mLastTextureID == IMG_DEFAULT_AVATAR) - last_id_string = "A"; - else if (mBakedTextureDatas[i].mLastTextureID == IMG_DEFAULT) - last_id_string = "D"; - else if (mBakedTextureDatas[i].mLastTextureID == IMG_INVISIBLE) - last_id_string = "I"; - else - last_id_string = "*"; - bool is_ltda = layerset - && layerset->getViewerComposite()->isInitialized() - && layerset->isLocalTextureDataAvailable(); - mBakedTextureDebugText += llformat("%4d %4s %4d %4d %4d %4d %4s\n", - i, - (layerset?"*":"0"), - layerset_invalid, - is_ltda, - is_layer_baked[i], - use_lkg_baked_layer[i], - last_id_string.c_str()); - } - - for (U32 i=0; i < mBakedTextureDatas.size(); i++) - { - debugColorizeSubMeshes(i, LLColor4::white); - - LLViewerTexLayerSet* layerset = getTexLayerSet(i); - if (use_lkg_baked_layer[i] && !isUsingLocalAppearance() ) - { - // use last known good layer (no new one) - LLViewerFetchedTexture* baked_img = LLViewerTextureManager::getFetchedTexture(mBakedTextureDatas[i].mLastTextureID); - mBakedTextureDatas[i].mIsUsed = true; - - debugColorizeSubMeshes(i,LLColor4::red); - - avatar_joint_mesh_list_t::iterator iter = mBakedTextureDatas[i].mJointMeshes.begin(); - avatar_joint_mesh_list_t::iterator end = mBakedTextureDatas[i].mJointMeshes.end(); - for (; iter != end; ++iter) - { - LLAvatarJointMesh* mesh = (*iter); - if (mesh) - { - mesh->setTexture( baked_img ); - } - } - } - else if (!isUsingLocalAppearance() && is_layer_baked[i]) - { - // use new layer - LLViewerFetchedTexture* baked_img = - LLViewerTextureManager::staticCastToFetchedTexture( - getImage( mBakedTextureDatas[i].mTextureIndex, 0 ), true) ; - if( baked_img->getID() == mBakedTextureDatas[i].mLastTextureID ) - { - // Even though the file may not be finished loading, - // we'll consider it loaded and use it (rather than - // doing compositing). - useBakedTexture( baked_img->getID() ); - mLoadedCallbacksPaused |= !isVisible(); - checkTextureLoading(); - } - else - { - mBakedTextureDatas[i].mIsLoaded = false; - if ( (baked_img->getID() != IMG_INVISIBLE) && - ((i == BAKED_HEAD) || (i == BAKED_UPPER) || (i == BAKED_LOWER)) ) - { - baked_img->setLoadedCallback(onBakedTextureMasksLoaded, MORPH_MASK_REQUESTED_DISCARD, true, true, new LLTextureMaskData( mID ), - src_callback_list, paused); - } - baked_img->setLoadedCallback(onBakedTextureLoaded, SWITCH_TO_BAKED_DISCARD, false, false, new LLUUID( mID ), - src_callback_list, paused ); - if (baked_img->getDiscardLevel() < 0 && !paused) - { - // mLoadedCallbackTextures will be updated by checkTextureLoading() below - mLastTexCallbackAddedTime.reset(); - } - - // this could add paused texture callbacks - mLoadedCallbacksPaused |= paused; - checkTextureLoading(); - } - } - else if (layerset && isUsingLocalAppearance()) - { - debugColorizeSubMeshes(i,LLColor4::yellow ); - - layerset->createComposite(); - layerset->setUpdatesEnabled( true ); - mBakedTextureDatas[i].mIsUsed = false; - - avatar_joint_mesh_list_t::iterator iter = mBakedTextureDatas[i].mJointMeshes.begin(); - avatar_joint_mesh_list_t::iterator end = mBakedTextureDatas[i].mJointMeshes.end(); - for (; iter != end; ++iter) - { - LLAvatarJointMesh* mesh = (*iter); - if (mesh) - { - mesh->setLayerSet( layerset ); - } - } - } - else - { - debugColorizeSubMeshes(i,LLColor4::blue); - } - } - - // set texture and color of hair manually if we are not using a baked image. - // This can happen while loading hair for yourself, or for clients that did not - // bake a hair texture. Still needed for yourself after 1.22 is depricated. - if (!is_layer_baked[BAKED_HAIR]) - { - const LLColor4 color = mTexHairColor ? mTexHairColor->getColor() : LLColor4(1,1,1,1); - LLViewerTexture* hair_img = getImage( TEX_HAIR, 0 ); - avatar_joint_mesh_list_t::iterator iter = mBakedTextureDatas[BAKED_HAIR].mJointMeshes.begin(); - avatar_joint_mesh_list_t::iterator end = mBakedTextureDatas[BAKED_HAIR].mJointMeshes.end(); - for (; iter != end; ++iter) - { - LLAvatarJointMesh* mesh = (*iter); - if (mesh) - { - mesh->setColor( color ); - mesh->setTexture( hair_img ); - } - } - } - - - for (LLAvatarAppearanceDictionary::BakedTextures::const_iterator baked_iter = - LLAvatarAppearance::getDictionary()->getBakedTextures().begin(); - baked_iter != LLAvatarAppearance::getDictionary()->getBakedTextures().end(); - ++baked_iter) - { - const EBakedTextureIndex baked_index = baked_iter->first; - const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = baked_iter->second; - - for (texture_vec_t::const_iterator local_tex_iter = baked_dict->mLocalTextures.begin(); - local_tex_iter != baked_dict->mLocalTextures.end(); - ++local_tex_iter) - { - const ETextureIndex texture_index = *local_tex_iter; - const bool is_baked_ready = (is_layer_baked[baked_index] && mBakedTextureDatas[baked_index].mIsLoaded) || other_culled; - if (isSelf()) - { - setBakedReady(texture_index, is_baked_ready); - } - } - } - - // removeMissingBakedTextures() will call back into this rountine if something is removed, and can blow up the stack - static bool call_remove_missing = true; - if (call_remove_missing) - { - call_remove_missing = false; - removeMissingBakedTextures(); // May call back into this function if anything is removed - call_remove_missing = true; - } - - //refresh bakes on any attached objects - for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - LLViewerObject* attached_object = attachment_iter->get(); - if (attached_object && !attached_object->isDead()) - { - attached_object->refreshBakeTexture(); - - LLViewerObject::const_child_list_t& child_list = attached_object->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); ++iter) - { - LLViewerObject* objectp = *iter; - if (objectp && !objectp->isDead()) - { - objectp->refreshBakeTexture(); - } - } - } - } - } - - - -} - -// virtual -//----------------------------------------------------------------------------- -// setLocalTexture() -//----------------------------------------------------------------------------- -void LLVOAvatar::setLocalTexture( ETextureIndex type, LLViewerTexture* in_tex, bool baked_version_ready, U32 index ) -{ - // invalid for anyone but self - llassert(0); -} - -//virtual -void LLVOAvatar::setBakedReady(LLAvatarAppearanceDefines::ETextureIndex type, bool baked_version_exists, U32 index) -{ - // invalid for anyone but self - llassert(0); -} - -void LLVOAvatar::addChat(const LLChat& chat) -{ - std::deque::iterator chat_iter; - - mChats.push_back(chat); - - S32 chat_length = 0; - for( chat_iter = mChats.begin(); chat_iter != mChats.end(); ++chat_iter) - { - chat_length += chat_iter->mText.size(); - } - - // remove any excess chat - chat_iter = mChats.begin(); - while ((chat_length > MAX_BUBBLE_CHAT_LENGTH || mChats.size() > MAX_BUBBLE_CHAT_UTTERANCES) && chat_iter != mChats.end()) - { - chat_length -= chat_iter->mText.size(); - mChats.pop_front(); - chat_iter = mChats.begin(); - } - - mChatTimer.reset(); -} - -void LLVOAvatar::clearChat() -{ - mChats.clear(); -} - - -void LLVOAvatar::applyMorphMask(const U8* tex_data, S32 width, S32 height, S32 num_components, LLAvatarAppearanceDefines::EBakedTextureIndex index) -{ - if (index >= BAKED_NUM_INDICES) - { - LL_WARNS() << "invalid baked texture index passed to applyMorphMask" << LL_ENDL; - return; - } - - for (morph_list_t::const_iterator iter = mBakedTextureDatas[index].mMaskedMorphs.begin(); - iter != mBakedTextureDatas[index].mMaskedMorphs.end(); ++iter) - { - const LLMaskedMorph* maskedMorph = (*iter); - LLPolyMorphTarget* morph_target = dynamic_cast(maskedMorph->mMorphTarget); - if (morph_target) - { - morph_target->applyMask(tex_data, width, height, num_components, maskedMorph->mInvert); - } - } -} - -// returns true if morph masks are present and not valid for a given baked texture, false otherwise -bool LLVOAvatar::morphMaskNeedsUpdate(LLAvatarAppearanceDefines::EBakedTextureIndex index) -{ - if (index >= BAKED_NUM_INDICES) - { - return false; - } - - if (!mBakedTextureDatas[index].mMaskedMorphs.empty()) - { - if (isSelf()) - { - LLViewerTexLayerSet *layer_set = getTexLayerSet(index); - if (layer_set) - { - return !layer_set->isMorphValid(); - } - } - else - { - return false; - } - } - - return false; -} - -//----------------------------------------------------------------------------- -// releaseComponentTextures() -// release any component texture UUIDs for which we have a baked texture -// ! BACKWARDS COMPATIBILITY ! -// This is only called for non-self avatars, it can be taken out once component -// textures aren't communicated by non-self avatars. -//----------------------------------------------------------------------------- -void LLVOAvatar::releaseComponentTextures() -{ - // ! BACKWARDS COMPATIBILITY ! - // Detect if the baked hair texture actually wasn't sent, and if so set to default - if (isTextureDefined(TEX_HAIR_BAKED) && getImage(TEX_HAIR_BAKED,0)->getID() == getImage(TEX_SKIRT_BAKED,0)->getID()) - { - if (getImage(TEX_HAIR_BAKED,0)->getID() != IMG_INVISIBLE) - { - // Regression case of messaging system. Expected 21 textures, received 20. last texture is not valid so set to default - setTETexture(TEX_HAIR_BAKED, IMG_DEFAULT_AVATAR); - } - } - - for (U8 baked_index = 0; baked_index < BAKED_NUM_INDICES; baked_index++) - { - const LLAvatarAppearanceDictionary::BakedEntry * bakedDicEntry = LLAvatarAppearance::getDictionary()->getBakedTexture((EBakedTextureIndex)baked_index); - // skip if this is a skirt and av is not wearing one, or if we don't have a baked texture UUID - if (!isTextureDefined(bakedDicEntry->mTextureIndex) - && ( (baked_index != BAKED_SKIRT) || isWearingWearableType(LLWearableType::WT_SKIRT) )) - { - continue; - } - - for (U8 texture = 0; texture < bakedDicEntry->mLocalTextures.size(); texture++) - { - const U8 te = (ETextureIndex)bakedDicEntry->mLocalTextures[texture]; - setTETexture(te, IMG_DEFAULT_AVATAR); - } - } -} - -void LLVOAvatar::dumpAvatarTEs( const std::string& context ) const -{ - LL_DEBUGS("Avatar") << avString() << (isSelf() ? "Self: " : "Other: ") << context << LL_ENDL; - for (LLAvatarAppearanceDictionary::Textures::const_iterator iter = LLAvatarAppearance::getDictionary()->getTextures().begin(); - iter != LLAvatarAppearance::getDictionary()->getTextures().end(); - ++iter) - { - const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = iter->second; - // TODO: MULTI-WEARABLE: handle multiple textures for self - const LLViewerTexture* te_image = getImage(iter->first,0); - if( !te_image ) - { - LL_DEBUGS("Avatar") << avString() << " " << texture_dict->mName << ": null ptr" << LL_ENDL; - } - else if( te_image->getID().isNull() ) - { - LL_DEBUGS("Avatar") << avString() << " " << texture_dict->mName << ": null UUID" << LL_ENDL; - } - else if( te_image->getID() == IMG_DEFAULT ) - { - LL_DEBUGS("Avatar") << avString() << " " << texture_dict->mName << ": IMG_DEFAULT" << LL_ENDL; - } - else if( te_image->getID() == IMG_DEFAULT_AVATAR ) - { - LL_DEBUGS("Avatar") << avString() << " " << texture_dict->mName << ": IMG_DEFAULT_AVATAR" << LL_ENDL; - } - else - { - LL_DEBUGS("Avatar") << avString() << " " << texture_dict->mName << ": " << te_image->getID() << LL_ENDL; - } - } -} - -//----------------------------------------------------------------------------- -// clampAttachmentPositions() -//----------------------------------------------------------------------------- -void LLVOAvatar::clampAttachmentPositions() -{ - if (isDead()) - { - return; - } - for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - if (attachment) - { - attachment->clampObjectPosition(); - } - } -} - -bool LLVOAvatar::hasHUDAttachment() const -{ - for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - if (attachment->getIsHUDAttachment() && attachment->getNumObjects() > 0) - { - return true; - } - } - return false; -} - -LLBBox LLVOAvatar::getHUDBBox() const -{ - LLBBox bbox; - for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - if (attachment->getIsHUDAttachment()) - { - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - const LLViewerObject* attached_object = attachment_iter->get(); - if (attached_object == NULL) - { - LL_WARNS() << "HUD attached object is NULL!" << LL_ENDL; - continue; - } - // initialize bounding box to contain identity orientation and center point for attached object - bbox.addPointLocal(attached_object->getPosition()); - // add rotated bounding box for attached object - bbox.addBBoxAgent(attached_object->getBoundingBoxAgent()); - LLViewerObject::const_child_list_t& child_list = attached_object->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); - ++iter) - { - const LLViewerObject* child_objectp = *iter; - bbox.addBBoxAgent(child_objectp->getBoundingBoxAgent()); - } - } - } - } - - return bbox; -} - -//----------------------------------------------------------------------------- -// onFirstTEMessageReceived() -//----------------------------------------------------------------------------- -void LLVOAvatar::onFirstTEMessageReceived() -{ - LL_DEBUGS("Avatar") << avString() << LL_ENDL; - if( !mFirstTEMessageReceived ) - { - mFirstTEMessageReceived = true; - - LLLoadedCallbackEntry::source_callback_list_t* src_callback_list = NULL ; - bool paused = false ; - if(!isSelf()) - { - src_callback_list = &mCallbackTextureList ; - paused = !isVisible(); - } - - for (U32 i = 0; i < mBakedTextureDatas.size(); i++) - { - const bool layer_baked = isTextureDefined(mBakedTextureDatas[i].mTextureIndex); - - // Use any baked textures that we have even if they haven't downloaded yet. - // (That is, don't do a transition from unbaked to baked.) - if (layer_baked) - { - LLViewerFetchedTexture* image = LLViewerTextureManager::staticCastToFetchedTexture(getImage( mBakedTextureDatas[i].mTextureIndex, 0 ), true) ; - mBakedTextureDatas[i].mLastTextureID = image->getID(); - // If we have more than one texture for the other baked layers, we'll want to call this for them too. - if ( (image->getID() != IMG_INVISIBLE) && ((i == BAKED_HEAD) || (i == BAKED_UPPER) || (i == BAKED_LOWER)) ) - { - image->setLoadedCallback( onBakedTextureMasksLoaded, MORPH_MASK_REQUESTED_DISCARD, true, true, new LLTextureMaskData( mID ), - src_callback_list, paused); - } - LL_DEBUGS("Avatar") << avString() << "layer_baked, setting onInitialBakedTextureLoaded as callback" << LL_ENDL; - image->setLoadedCallback( onInitialBakedTextureLoaded, MAX_DISCARD_LEVEL, false, false, new LLUUID( mID ), - src_callback_list, paused ); - if (image->getDiscardLevel() < 0 && !paused) - { - mLastTexCallbackAddedTime.reset(); - } - // this could add paused texture callbacks - mLoadedCallbacksPaused |= paused; - } - } - - mMeshTexturesDirty = true; - gPipeline.markGLRebuild(this); - - mFirstAppearanceMessageTimer.reset(); - mFullyLoadedTimer.reset(); - } -} - -//----------------------------------------------------------------------------- -// bool visualParamWeightsAreDefault() -//----------------------------------------------------------------------------- -bool LLVOAvatar::visualParamWeightsAreDefault() -{ - bool rtn = true; - - bool is_wearing_skirt = isWearingWearableType(LLWearableType::WT_SKIRT); - for (LLVisualParam *param = getFirstVisualParam(); - param; - param = getNextVisualParam()) - { - if (param->isTweakable()) - { - LLViewerVisualParam* vparam = dynamic_cast(param); - llassert(vparam); - bool is_skirt_param = vparam && - LLWearableType::WT_SKIRT == vparam->getWearableType(); - if (param->getWeight() != param->getDefaultWeight() && - // we have to not care whether skirt weights are default, if we're not actually wearing a skirt - (is_wearing_skirt || !is_skirt_param)) - { - //LL_INFOS() << "param '" << param->getName() << "'=" << param->getWeight() << " which differs from default=" << param->getDefaultWeight() << LL_ENDL; - rtn = false; - break; - } - } - } - - //LL_INFOS() << "params are default ? " << int(rtn) << LL_ENDL; - - return rtn; -} - -void dump_visual_param(apr_file_t* file, LLVisualParam* viewer_param, F32 value) -{ - std::string type_string = "unknown"; - if (dynamic_cast(viewer_param)) - type_string = "param_alpha"; - if (dynamic_cast(viewer_param)) - type_string = "param_color"; - if (dynamic_cast(viewer_param)) - type_string = "param_driver"; - if (dynamic_cast(viewer_param)) - type_string = "param_morph"; - if (dynamic_cast(viewer_param)) - type_string = "param_skeleton"; - S32 wtype = -1; - LLViewerVisualParam *vparam = dynamic_cast(viewer_param); - if (vparam) - { - wtype = vparam->getWearableType(); - } - S32 u8_value = F32_to_U8(value,viewer_param->getMinWeight(),viewer_param->getMaxWeight()); - apr_file_printf(file, "\t\t\n", - viewer_param->getID(), viewer_param->getName().c_str(), viewer_param->getDisplayName().c_str(), value, u8_value, type_string.c_str(), - LLWearableType::getInstance()->getTypeName(LLWearableType::EType(wtype)).c_str(), - viewer_param->getGroup()); - } - - -void LLVOAvatar::dumpAppearanceMsgParams( const std::string& dump_prefix, - const LLAppearanceMessageContents& contents) -{ - std::string outfilename = get_sequential_numbered_file_name(dump_prefix,".xml"); - const std::vector& params_for_dump = contents.mParamWeights; - const LLTEContents& tec = contents.mTEContents; - - LLAPRFile outfile; - std::string fullpath = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,outfilename); - outfile.open(fullpath, LL_APR_WB ); - apr_file_t* file = outfile.getFileHandle(); - if (!file) - { - return; - } - else - { - LL_DEBUGS("Avatar") << "dumping appearance message to " << fullpath << LL_ENDL; - } - - apr_file_printf(file, "
\n"); - apr_file_printf(file, "\t\t\n", contents.mCOFVersion); - apr_file_printf(file, "\t\t\n", contents.mAppearanceVersion); - apr_file_printf(file, "
\n"); - - apr_file_printf(file, "\n\n"); - LLVisualParam* param = getFirstVisualParam(); - for (S32 i = 0; i < params_for_dump.size(); i++) - { - while( param && ((param->getGroup() != VISUAL_PARAM_GROUP_TWEAKABLE) && - (param->getGroup() != VISUAL_PARAM_GROUP_TRANSMIT_NOT_TWEAKABLE)) ) // should not be any of group VISUAL_PARAM_GROUP_TWEAKABLE_NO_TRANSMIT - { - param = getNextVisualParam(); - } - LLViewerVisualParam* viewer_param = (LLViewerVisualParam*)param; - F32 value = params_for_dump[i]; - dump_visual_param(file, viewer_param, value); - param = getNextVisualParam(); - } - apr_file_printf(file, "\n"); - - apr_file_printf(file, "\n\n"); - for (U32 i = 0; i < tec.face_count; i++) - { - std::string uuid_str; - ((LLUUID*)tec.image_data)[i].toString(uuid_str); - apr_file_printf( file, "\t\t\n", i, uuid_str.c_str()); - } - apr_file_printf(file, "\n"); -} - -void LLVOAvatar::parseAppearanceMessage(LLMessageSystem* mesgsys, LLAppearanceMessageContents& contents) -{ - parseTEMessage(mesgsys, _PREHASH_ObjectData, -1, contents.mTEContents); - - // Parse the AppearanceData field, if any. - if (mesgsys->has(_PREHASH_AppearanceData)) - { - U8 av_u8; - mesgsys->getU8Fast(_PREHASH_AppearanceData, _PREHASH_AppearanceVersion, av_u8, 0); - contents.mAppearanceVersion = av_u8; - //LL_DEBUGS("Avatar") << "appversion set by AppearanceData field: " << contents.mAppearanceVersion << LL_ENDL; - mesgsys->getS32Fast(_PREHASH_AppearanceData, _PREHASH_CofVersion, contents.mCOFVersion, 0); - // For future use: - //mesgsys->getU32Fast(_PREHASH_AppearanceData, _PREHASH_Flags, appearance_flags, 0); - } - - // Parse the AppearanceHover field, if any. - contents.mHoverOffsetWasSet = false; - if (mesgsys->has(_PREHASH_AppearanceHover)) - { - LLVector3 hover; - mesgsys->getVector3Fast(_PREHASH_AppearanceHover, _PREHASH_HoverHeight, hover); - //LL_DEBUGS("Avatar") << avString() << " hover received " << hover.mV[ VX ] << "," << hover.mV[ VY ] << "," << hover.mV[ VZ ] << LL_ENDL; - contents.mHoverOffset = hover; - contents.mHoverOffsetWasSet = true; - } - - // Get attachment info, if sent - LLUUID attachment_id; - U8 attach_point; - S32 attach_count = mesgsys->getNumberOfBlocksFast(_PREHASH_AttachmentBlock); - LL_DEBUGS("AVAppearanceAttachments") << "Agent " << getID() << " has " - << attach_count << " attachments" << LL_ENDL; - size_t old_size = mSimAttachments.size(); - mSimAttachments.clear(); - for (S32 attach_i = 0; attach_i < attach_count; attach_i++) - { - mesgsys->getUUIDFast(_PREHASH_AttachmentBlock, _PREHASH_ID, attachment_id, attach_i); - mesgsys->getU8Fast(_PREHASH_AttachmentBlock, _PREHASH_AttachmentPoint, attach_point, attach_i); - LL_DEBUGS("AVAppearanceAttachments") << "AV " << getID() << " has attachment " << attach_i << " " - << (attachment_id.isNull() ? "pending" : attachment_id.asString()) - << " on point " << (S32)attach_point << LL_ENDL; - - if (attachment_id.notNull()) - { - mSimAttachments[attachment_id] = attach_point; - } - else - { - // at the moment viewer is only interested in non-null attachments - LL_DEBUGS("AVAppearanceAttachments") << "AV " << getID() - << " has null attachment on point " << (S32)attach_point - << ", discarding" << LL_ENDL; - } - } - - // todo? Doesn't detect if attachments were switched - if (old_size != mSimAttachments.size()) - { - mLastCloudAttachmentCount = 0; - mLastCloudAttachmentChangeTime.reset(); - if (!isFullyLoaded()) - { - mFullyLoadedTimer.reset(); - } - } - - // Parse visual params, if any. - S32 num_blocks = mesgsys->getNumberOfBlocksFast(_PREHASH_VisualParam); - if( num_blocks > 1) - { - //LL_DEBUGS("Avatar") << avString() << " handle visual params, num_blocks " << num_blocks << LL_ENDL; - - LLVisualParam* param = getFirstVisualParam(); - llassert(param); // if this ever fires, we should do the same as when num_blocks<=1 - if (!param) - { - LL_WARNS() << "No visual params!" << LL_ENDL; - } - else - { - for( S32 i = 0; i < num_blocks; i++ ) - { - while( param && ((param->getGroup() != VISUAL_PARAM_GROUP_TWEAKABLE) && - (param->getGroup() != VISUAL_PARAM_GROUP_TRANSMIT_NOT_TWEAKABLE)) ) // should not be any of group VISUAL_PARAM_GROUP_TWEAKABLE_NO_TRANSMIT - { - param = getNextVisualParam(); - } - - if( !param ) - { - // more visual params supplied than expected - just process what we know about - break; - } - - U8 value; - mesgsys->getU8Fast(_PREHASH_VisualParam, _PREHASH_ParamValue, value, i); - F32 newWeight = U8_to_F32(value, param->getMinWeight(), param->getMaxWeight()); - contents.mParamWeights.push_back(newWeight); - contents.mParams.push_back(param); - - param = getNextVisualParam(); - } - } - - const S32 expected_tweakable_count = getVisualParamCountInGroup(VISUAL_PARAM_GROUP_TWEAKABLE) + - getVisualParamCountInGroup(VISUAL_PARAM_GROUP_TRANSMIT_NOT_TWEAKABLE); // don't worry about VISUAL_PARAM_GROUP_TWEAKABLE_NO_TRANSMIT - if (num_blocks != expected_tweakable_count) - { - LL_DEBUGS("Avatar") << "Number of params in AvatarAppearance msg (" << num_blocks << ") does not match number of tweakable params in avatar xml file (" << expected_tweakable_count << "). Processing what we can. object: " << getID() << LL_ENDL; - } - } - else - { - LL_DEBUGS("Avatar") << "AvatarAppearance msg received without any parameters, object: " << getID() << LL_ENDL; - } - - LLVisualParam* appearance_version_param = getVisualParam(11000); - if (appearance_version_param) - { - std::vector::iterator it = std::find(contents.mParams.begin(), contents.mParams.end(),appearance_version_param); - if (it != contents.mParams.end()) - { - S32 index = it - contents.mParams.begin(); - contents.mParamAppearanceVersion = ll_round(contents.mParamWeights[index]); - //LL_DEBUGS("Avatar") << "appversion req by appearance_version param: " << contents.mParamAppearanceVersion << LL_ENDL; - } - } -} - -bool resolve_appearance_version(const LLAppearanceMessageContents& contents, S32& appearance_version) -{ - appearance_version = -1; - - if ((contents.mAppearanceVersion) >= 0 && - (contents.mParamAppearanceVersion >= 0) && - (contents.mAppearanceVersion != contents.mParamAppearanceVersion)) - { - LL_WARNS() << "inconsistent appearance_version settings - field: " << - contents.mAppearanceVersion << ", param: " << contents.mParamAppearanceVersion << LL_ENDL; - return false; - } - if (contents.mParamAppearanceVersion >= 0) // use visual param if available. - { - appearance_version = contents.mParamAppearanceVersion; - } - else if (contents.mAppearanceVersion > 0) - { - appearance_version = contents.mAppearanceVersion; - } - else // still not set, go with 1. - { - appearance_version = 1; - } - //LL_DEBUGS("Avatar") << "appearance version info - field " << contents.mAppearanceVersion - // << " param: " << contents.mParamAppearanceVersion - // << " final: " << appearance_version << LL_ENDL; - return true; -} - -//----------------------------------------------------------------------------- -// processAvatarAppearance() -//----------------------------------------------------------------------------- -void LLVOAvatar::processAvatarAppearance( LLMessageSystem* mesgsys ) -{ - LL_DEBUGS("Avatar") << "starts" << LL_ENDL; - - static LLCachedControl enable_verbose_dumps(gSavedSettings, "DebugAvatarAppearanceMessage"); - static LLCachedControl block_avatar_appearance_messages(gSavedSettings, "BlockAvatarAppearanceMessages"); - - std::string dump_prefix = getFullname() + "_" + (isSelf()?"s":"o") + "_"; - if (block_avatar_appearance_messages) - { - LL_WARNS() << "Blocking AvatarAppearance message" << LL_ENDL; - return; - } - - mLastAppearanceMessageTimer.reset(); - - LLPointer contents(new LLAppearanceMessageContents); - parseAppearanceMessage(mesgsys, *contents); - if (enable_verbose_dumps) - { - dumpAppearanceMsgParams(dump_prefix + "appearance_msg", *contents); - } - - S32 appearance_version; - if (!resolve_appearance_version(*contents, appearance_version)) - { - LL_WARNS() << "bad appearance version info, discarding" << LL_ENDL; - return; - } - llassert(appearance_version > 0); - if (appearance_version > 1) - { - LL_WARNS() << "unsupported appearance version " << appearance_version << ", discarding appearance message" << LL_ENDL; - return; - } - - S32 thisAppearanceVersion(contents->mCOFVersion); - if (isSelf()) - { // In the past this was considered to be the canonical COF version, - // that is no longer the case. The canonical version is maintained - // by the AIS code and should match the COF version there. Even so, - // we must prevent rolling this one backwards backwards or processing - // stale versions. - - S32 aisCOFVersion(LLAppearanceMgr::instance().getCOFVersion()); - - LL_DEBUGS("Avatar") << "handling self appearance message #" << thisAppearanceVersion << - " (highest seen #" << mLastUpdateReceivedCOFVersion << - ") (AISCOF=#" << aisCOFVersion << ")" << LL_ENDL; - - if (mLastUpdateReceivedCOFVersion >= thisAppearanceVersion) - { - LL_WARNS("Avatar") << "Stale appearance received #" << thisAppearanceVersion << - " attempt to roll back from #" << mLastUpdateReceivedCOFVersion << - "... dropping." << LL_ENDL; - return; - } - if (isEditingAppearance()) - { - LL_DEBUGS("Avatar") << "Editing appearance. Dropping appearance update." << LL_ENDL; - return; - } - - } - - // SUNSHINE CLEANUP - is this case OK now? - S32 num_params = contents->mParamWeights.size(); - if (num_params <= 1) - { - // In this case, we have no reliable basis for knowing - // appearance version, which may cause us to look for baked - // textures in the wrong place and flag them as missing - // assets. - LL_DEBUGS("Avatar") << "ignoring appearance message due to lack of params" << LL_ENDL; - return; - } - - // No backsies zone - if we get here, the message should be valid and usable, will be processed. - // Note: - // RequestAgentUpdateAppearanceResponder::onRequestRequested() - // assumes that cof version is only updated with server-bake - // appearance messages. - if (isSelf()) - { - LL_INFOS("Avatar") << "Processing appearance message version " << thisAppearanceVersion << LL_ENDL; - } - else - { - LL_INFOS("Avatar") << "Processing appearance message for " << getID() << ", version " << thisAppearanceVersion << LL_ENDL; - } - - // Note: - // locally the COF is maintained via LLInventoryModel::accountForUpdate - // which is called from various places. This should match the simhost's - // idea of what the COF version is. AIS however maintains its own version - // of the COF that should be considered canonical. - mLastUpdateReceivedCOFVersion = thisAppearanceVersion; - - mLastProcessedAppearance = contents; - - bool slam_params = false; - applyParsedAppearanceMessage(*contents, slam_params); - if (getOverallAppearance() != AOA_NORMAL) - { - resetSkeleton(false); - } -} - -void LLVOAvatar::applyParsedAppearanceMessage(LLAppearanceMessageContents& contents, bool slam_params) -{ - S32 num_params = contents.mParamWeights.size(); - ESex old_sex = getSex(); - - if (applyParsedTEMessage(contents.mTEContents) > 0 && isChanged(TEXTURE)) - { - updateVisualComplexity(); - } - - // prevent the overwriting of valid baked textures with invalid baked textures - for (U8 baked_index = 0; baked_index < mBakedTextureDatas.size(); baked_index++) - { - if (!isTextureDefined(mBakedTextureDatas[baked_index].mTextureIndex) - && mBakedTextureDatas[baked_index].mLastTextureID != IMG_DEFAULT - && baked_index != BAKED_SKIRT && baked_index != BAKED_LEFT_ARM && baked_index != BAKED_LEFT_LEG && baked_index != BAKED_AUX1 && baked_index != BAKED_AUX2 && baked_index != BAKED_AUX3) - { - LL_DEBUGS("Avatar") << avString() << " baked_index " << (S32) baked_index << " using mLastTextureID " << mBakedTextureDatas[baked_index].mLastTextureID << LL_ENDL; - setTEImage(mBakedTextureDatas[baked_index].mTextureIndex, - LLViewerTextureManager::getFetchedTexture(mBakedTextureDatas[baked_index].mLastTextureID, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE)); - } - else - { - LL_DEBUGS("Avatar") << avString() << " baked_index " << (S32) baked_index << " using texture id " - << getTE(mBakedTextureDatas[baked_index].mTextureIndex)->getID() << LL_ENDL; - } - } - - // runway - was - // if (!is_first_appearance_message ) - // which means it would be called on second appearance message - probably wrong. - bool is_first_appearance_message = !mFirstAppearanceMessageReceived; - mFirstAppearanceMessageReceived = true; - - //LL_DEBUGS("Avatar") << avString() << "processAvatarAppearance start " << mID - // << " first? " << is_first_appearance_message << " self? " << isSelf() << LL_ENDL; - - if (is_first_appearance_message ) - { - onFirstTEMessageReceived(); - } - - setCompositeUpdatesEnabled( false ); - gPipeline.markGLRebuild(this); - - // Apply visual params - if( num_params > 1) - { - //LL_DEBUGS("Avatar") << avString() << " handle visual params, num_params " << num_params << LL_ENDL; - bool params_changed = false; - bool interp_params = false; - S32 params_changed_count = 0; - - for( S32 i = 0; i < num_params; i++ ) - { - LLVisualParam* param = contents.mParams[i]; - F32 newWeight = contents.mParamWeights[i]; - - if (slam_params || is_first_appearance_message || (param->getWeight() != newWeight)) - { - params_changed = true; - params_changed_count++; - - if(is_first_appearance_message || slam_params) - { - //LL_DEBUGS("Avatar") << "param slam " << i << " " << newWeight << LL_ENDL; - param->setWeight(newWeight); - } - else - { - interp_params = true; - param->setAnimationTarget(newWeight); - } - } - } - const S32 expected_tweakable_count = getVisualParamCountInGroup(VISUAL_PARAM_GROUP_TWEAKABLE) + - getVisualParamCountInGroup(VISUAL_PARAM_GROUP_TRANSMIT_NOT_TWEAKABLE); // don't worry about VISUAL_PARAM_GROUP_TWEAKABLE_NO_TRANSMIT - if (num_params != expected_tweakable_count) - { - LL_DEBUGS("Avatar") << "Number of params in AvatarAppearance msg (" << num_params << ") does not match number of tweakable params in avatar xml file (" << expected_tweakable_count << "). Processing what we can. object: " << getID() << LL_ENDL; - } - - LL_DEBUGS("Avatar") << "Changed " << params_changed_count << " params" << LL_ENDL; - if (params_changed) - { - if (interp_params) - { - startAppearanceAnimation(); - } - updateVisualParams(); - - ESex new_sex = getSex(); - if( old_sex != new_sex ) - { - updateSexDependentLayerSets(); - } - } - - llassert( getSex() == ((getVisualParamWeight( "male" ) > 0.5f) ? SEX_MALE : SEX_FEMALE) ); - } - else - { - // AvatarAppearance message arrived without visual params - LL_DEBUGS("Avatar") << avString() << "no visual params" << LL_ENDL; - - const F32 LOADING_TIMEOUT_SECONDS = 60.f; - // this isn't really a problem if we already have a non-default shape - if (visualParamWeightsAreDefault() && mRuthTimer.getElapsedTimeF32() > LOADING_TIMEOUT_SECONDS) - { - // re-request appearance, hoping that it comes back with a shape next time - LL_INFOS() << "Re-requesting AvatarAppearance for object: " << getID() << LL_ENDL; - LLAvatarPropertiesProcessor::getInstance()->sendAvatarTexturesRequest(getID()); - mRuthTimer.reset(); - } - else - { - LL_INFOS() << "That's okay, we already have a non-default shape for object: " << getID() << LL_ENDL; - // we don't really care. - } - } - - if (contents.mHoverOffsetWasSet && !isSelf()) - { - // Got an update for some other avatar - // Ignore updates for self, because we have a more authoritative value in the preferences. - setHoverOffset(contents.mHoverOffset); - LL_DEBUGS("Avatar") << avString() << "setting hover to " << contents.mHoverOffset[2] << LL_ENDL; - } - - if (!contents.mHoverOffsetWasSet && !isSelf()) - { - // If we don't get a value at all, we are presumably in a - // region that does not support hover height. - LL_WARNS() << avString() << "zeroing hover because not defined in appearance message" << LL_ENDL; - setHoverOffset(LLVector3(0.0, 0.0, 0.0)); - } - - setCompositeUpdatesEnabled( true ); - - // If all of the avatars are completely baked, release the global image caches to conserve memory. - LLVOAvatar::cullAvatarsByPixelArea(); - - if (isSelf()) - { - mUseLocalAppearance = false; - } - - updateMeshTextures(); - updateMeshVisibility(); - -} - -LLViewerTexture* LLVOAvatar::getBakedTexture(const U8 te) -{ - if (te < 0 || te >= BAKED_NUM_INDICES) - { - return NULL; - } - - bool is_layer_baked = isTextureDefined(mBakedTextureDatas[te].mTextureIndex); - - LLViewerTexLayerSet* layerset = NULL; - layerset = getTexLayerSet(te); - - - if (!isEditingAppearance() && is_layer_baked) - { - LLViewerFetchedTexture* baked_img = LLViewerTextureManager::staticCastToFetchedTexture(getImage(mBakedTextureDatas[te].mTextureIndex, 0), true); - return baked_img; - } - else if (layerset && isEditingAppearance()) - { - layerset->createComposite(); - layerset->setUpdatesEnabled(true); - - return layerset->getViewerComposite(); - } - - return NULL; - - -} - -const LLVOAvatar::MatrixPaletteCache& LLVOAvatar::updateSkinInfoMatrixPalette(const LLMeshSkinInfo* skin) -{ - U64 hash = skin->mHash; - MatrixPaletteCache& entry = mMatrixPaletteCache[hash]; - - if (entry.mFrame != gFrameCount) - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - - entry.mFrame = gFrameCount; - - //build matrix palette - U32 count = LLSkinningUtil::getMeshJointCount(skin); - entry.mMatrixPalette.resize(count); - LLSkinningUtil::initSkinningMatrixPalette(&(entry.mMatrixPalette[0]), count, skin, this); - - const LLMatrix4a* mat = &(entry.mMatrixPalette[0]); - - entry.mGLMp.resize(count * 12); - - F32* mp = &(entry.mGLMp[0]); - - for (U32 i = 0; i < count; ++i) - { - F32* m = (F32*)mat[i].mMatrix[0].getF32ptr(); - - U32 idx = i * 12; - - mp[idx + 0] = m[0]; - mp[idx + 1] = m[1]; - mp[idx + 2] = m[2]; - mp[idx + 3] = m[12]; - - mp[idx + 4] = m[4]; - mp[idx + 5] = m[5]; - mp[idx + 6] = m[6]; - mp[idx + 7] = m[13]; - - mp[idx + 8] = m[8]; - mp[idx + 9] = m[9]; - mp[idx + 10] = m[10]; - mp[idx + 11] = m[14]; - } - } - - return entry; -} - -// static -void LLVOAvatar::getAnimLabels( std::vector* labels ) -{ - S32 i; - labels->reserve(gUserAnimStatesCount); - for( i = 0; i < gUserAnimStatesCount; i++ ) - { - labels->push_back( LLAnimStateLabels::getStateLabel( gUserAnimStates[i].mName ) ); - } - - // Special case to trigger away (AFK) state - labels->push_back( "Away From Keyboard" ); -} - -// static -void LLVOAvatar::getAnimNames( std::vector* names ) -{ - S32 i; - - names->reserve(gUserAnimStatesCount); - for( i = 0; i < gUserAnimStatesCount; i++ ) - { - names->push_back( std::string(gUserAnimStates[i].mName) ); - } - - // Special case to trigger away (AFK) state - names->push_back( "enter_away_from_keyboard_state" ); -} - -// static -void LLVOAvatar::onBakedTextureMasksLoaded( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata ) -{ - if (!userdata) return; - - //LL_INFOS() << "onBakedTextureMasksLoaded: " << src_vi->getID() << LL_ENDL; - const LLUUID id = src_vi->getID(); - - LLTextureMaskData* maskData = (LLTextureMaskData*) userdata; - LLVOAvatar* self = (LLVOAvatar*) gObjectList.findObject( maskData->mAvatarID ); - - // if discard level is 2 less than last discard level we processed, or we hit 0, - // then generate morph masks - if(self && success && (discard_level < maskData->mLastDiscardLevel - 2 || discard_level == 0)) - { - if(aux_src && aux_src->getComponents() == 1) - { - LLImageDataSharedLock lock(aux_src); - - if (!aux_src->getData()) - { - LL_ERRS() << "No auxiliary source (morph mask) data for image id " << id << LL_ENDL; - return; - } - - U32 gl_name; - LLImageGL::generateTextures(1, &gl_name ); - stop_glerror(); - - gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, gl_name); - stop_glerror(); - - LLImageGL::setManualImage( - GL_TEXTURE_2D, 0, GL_ALPHA8, - aux_src->getWidth(), aux_src->getHeight(), - GL_ALPHA, GL_UNSIGNED_BYTE, aux_src->getData()); - stop_glerror(); - - gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_BILINEAR); - - /* if( id == head_baked->getID() ) - if (self->mBakedTextureDatas[BAKED_HEAD].mTexLayerSet) - //LL_INFOS() << "onBakedTextureMasksLoaded for head " << id << " discard = " << discard_level << LL_ENDL; - self->mBakedTextureDatas[BAKED_HEAD].mTexLayerSet->applyMorphMask(aux_src->getData(), aux_src->getWidth(), aux_src->getHeight(), 1); - maskData->mLastDiscardLevel = discard_level; */ - bool found_texture_id = false; - for (LLAvatarAppearanceDictionary::Textures::const_iterator iter = LLAvatarAppearance::getDictionary()->getTextures().begin(); - iter != LLAvatarAppearance::getDictionary()->getTextures().end(); - ++iter) - { - - const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = iter->second; - if (texture_dict->mIsUsedByBakedTexture) - { - const ETextureIndex texture_index = iter->first; - const LLViewerTexture *baked_img = self->getImage(texture_index, 0); - if (baked_img && id == baked_img->getID()) - { - const EBakedTextureIndex baked_index = texture_dict->mBakedTextureIndex; - self->applyMorphMask(aux_src->getData(), aux_src->getWidth(), aux_src->getHeight(), 1, baked_index); - maskData->mLastDiscardLevel = discard_level; - if (self->mBakedTextureDatas[baked_index].mMaskTexName) - { - LLImageGL::deleteTextures(1, &(self->mBakedTextureDatas[baked_index].mMaskTexName)); - } - self->mBakedTextureDatas[baked_index].mMaskTexName = gl_name; - found_texture_id = true; - break; - } - } - } - if (!found_texture_id) - { - LL_INFOS() << "unexpected image id: " << id << LL_ENDL; - } - self->dirtyMesh(); - } - else - { - // this can happen when someone uses an old baked texture possibly provided by - // viewer-side baked texture caching - LL_WARNS() << "Masks loaded callback but NO aux source, id " << id << LL_ENDL; - } - } - - if (final || !success) - { - delete maskData; - } -} - -// static -void LLVOAvatar::onInitialBakedTextureLoaded( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata ) -{ - LLUUID *avatar_idp = (LLUUID *)userdata; - LLVOAvatar *selfp = (LLVOAvatar *)gObjectList.findObject(*avatar_idp); - - if (selfp) - { - //LL_DEBUGS("Avatar") << selfp->avString() << "discard_level " << discard_level << " success " << success << " final " << final << LL_ENDL; - } - - if (!success && selfp) - { - selfp->removeMissingBakedTextures(); - } - if (final || !success ) - { - delete avatar_idp; - } -} - -// Static -void LLVOAvatar::onBakedTextureLoaded(bool success, - LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, - S32 discard_level, bool final, void* userdata) -{ - //LL_DEBUGS("Avatar") << "onBakedTextureLoaded: " << src_vi->getID() << LL_ENDL; - - LLUUID id = src_vi->getID(); - LLUUID *avatar_idp = (LLUUID *)userdata; - LLVOAvatar *selfp = (LLVOAvatar *)gObjectList.findObject(*avatar_idp); - if (selfp) - { - //LL_DEBUGS("Avatar") << selfp->avString() << "discard_level " << discard_level << " success " << success << " final " << final << " id " << src_vi->getID() << LL_ENDL; - } - - if (selfp && !success) - { - selfp->removeMissingBakedTextures(); - } - - if( final || !success ) - { - delete avatar_idp; - } - - if( selfp && success && final ) - { - selfp->useBakedTexture( id ); - } -} - - -// Called when baked texture is loaded and also when we start up with a baked texture -void LLVOAvatar::useBakedTexture( const LLUUID& id ) -{ - for (U32 i = 0; i < mBakedTextureDatas.size(); i++) - { - LLViewerTexture* image_baked = getImage( mBakedTextureDatas[i].mTextureIndex, 0 ); - if (id == image_baked->getID()) - { - //LL_DEBUGS("Avatar") << avString() << " i " << i << " id " << id << LL_ENDL; - mBakedTextureDatas[i].mIsLoaded = true; - mBakedTextureDatas[i].mLastTextureID = id; - mBakedTextureDatas[i].mIsUsed = true; - - if (isUsingLocalAppearance()) - { - LL_INFOS() << "not changing to baked texture while isUsingLocalAppearance" << LL_ENDL; - } - else - { - debugColorizeSubMeshes(i,LLColor4::green); - - avatar_joint_mesh_list_t::iterator iter = mBakedTextureDatas[i].mJointMeshes.begin(); - avatar_joint_mesh_list_t::iterator end = mBakedTextureDatas[i].mJointMeshes.end(); - for (; iter != end; ++iter) - { - LLAvatarJointMesh* mesh = (*iter); - if (mesh) - { - mesh->setTexture( image_baked ); - } - } - } - - const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = - LLAvatarAppearance::getDictionary()->getBakedTexture((EBakedTextureIndex)i); - for (texture_vec_t::const_iterator local_tex_iter = baked_dict->mLocalTextures.begin(); - local_tex_iter != baked_dict->mLocalTextures.end(); - ++local_tex_iter) - { - if (isSelf()) setBakedReady(*local_tex_iter, true); - } - - // ! BACKWARDS COMPATIBILITY ! - // Workaround for viewing avatars from old viewers that haven't baked hair textures. - // This is paired with similar code in updateMeshTextures that sets hair mesh color. - if (i == BAKED_HAIR) - { - avatar_joint_mesh_list_t::iterator iter = mBakedTextureDatas[i].mJointMeshes.begin(); - avatar_joint_mesh_list_t::iterator end = mBakedTextureDatas[i].mJointMeshes.end(); - for (; iter != end; ++iter) - { - LLAvatarJointMesh* mesh = (*iter); - if (mesh) - { - mesh->setColor( LLColor4::white ); - } - } - } - } - } - - dirtyMesh(); -} - -std::string get_sequential_numbered_file_name(const std::string& prefix, - const std::string& suffix) -{ - typedef std::map file_num_type; - static file_num_type file_nums; - file_num_type::iterator it = file_nums.find(prefix); - S32 num = 0; - if (it != file_nums.end()) - { - num = it->second; - } - file_nums[prefix] = num+1; - std::string outfilename = prefix + " " + llformat("%04d",num) + ".xml"; - std::replace(outfilename.begin(),outfilename.end(),' ','_'); - return outfilename; -} - -void dump_sequential_xml(const std::string outprefix, const LLSD& content) -{ - std::string outfilename = get_sequential_numbered_file_name(outprefix,".xml"); - std::string fullpath = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,outfilename); - llofstream ofs(fullpath.c_str(), std::ios_base::out); - ofs << LLSDOStreamer(content, LLSDFormatter::OPTIONS_PRETTY); - LL_DEBUGS("Avatar") << "results saved to: " << fullpath << LL_ENDL; -} - -void LLVOAvatar::getSortedJointNames(S32 joint_type, std::vector& result) const -{ - result.clear(); - if (joint_type==0) - { - avatar_joint_list_t::const_iterator iter = mSkeleton.begin(); - avatar_joint_list_t::const_iterator end = mSkeleton.end(); - for (; iter != end; ++iter) - { - LLJoint* pJoint = (*iter); - result.push_back(pJoint->getName()); - } - } - else if (joint_type==1) - { - for (S32 i = 0; i < mNumCollisionVolumes; i++) - { - LLAvatarJointCollisionVolume* pJoint = &mCollisionVolumes[i]; - result.push_back(pJoint->getName()); - } - } - else if (joint_type==2) - { - for (LLVOAvatar::attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); ++iter) - { - LLViewerJointAttachment* pJoint = iter->second; - if (!pJoint) continue; - result.push_back(pJoint->getName()); - } - } - std::sort(result.begin(), result.end()); -} - -void LLVOAvatar::dumpArchetypeXML(const std::string& prefix, bool group_by_wearables ) -{ - std::string outprefix(prefix); - if (outprefix.empty()) - { - outprefix = getFullname() + (isSelf()?"_s":"_o"); - } - if (outprefix.empty()) - { - outprefix = std::string("new_archetype"); - } - std::string outfilename = get_sequential_numbered_file_name(outprefix,".xml"); - - LLAPRFile outfile; - LLWearableType *wr_inst = LLWearableType::getInstance(); - std::string fullpath = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,outfilename); - if (APR_SUCCESS == outfile.open(fullpath, LL_APR_WB )) - { - apr_file_t* file = outfile.getFileHandle(); - LL_INFOS() << "xmlfile write handle obtained : " << fullpath << LL_ENDL; - - apr_file_printf( file, "\n" ); - apr_file_printf( file, "\n" ); - apr_file_printf( file, "\n\t\n" ); - - bool agent_is_godlike = gAgent.isGodlikeWithoutAdminMenuFakery(); - - if (group_by_wearables) - { - for (S32 type = LLWearableType::WT_SHAPE; type < LLWearableType::WT_COUNT; type++) - { - const std::string& wearable_name = wr_inst->getTypeName((LLWearableType::EType)type); - apr_file_printf( file, "\n\t\t\n", wearable_name.c_str() ); - - for (LLVisualParam* param = getFirstVisualParam(); param; param = getNextVisualParam()) - { - LLViewerVisualParam* viewer_param = (LLViewerVisualParam*)param; - if( (viewer_param->getWearableType() == type) && - (viewer_param->isTweakable() ) ) - { - dump_visual_param(file, viewer_param, viewer_param->getWeight()); - } - } - - for (U8 te = 0; te < TEX_NUM_INDICES; te++) - { - if (LLAvatarAppearance::getDictionary()->getTEWearableType((ETextureIndex)te) == type) - { - // MULTIPLE_WEARABLES: extend to multiple wearables? - LLViewerTexture* te_image = getImage((ETextureIndex)te, 0); - if( te_image ) - { - std::string uuid_str = LLUUID().asString(); - if (agent_is_godlike) - { - te_image->getID().toString(uuid_str); - } - apr_file_printf( file, "\t\t\n", te, uuid_str.c_str()); - } - } - } - } - } - else - { - // Just dump all params sequentially. - for (LLVisualParam* param = getFirstVisualParam(); param; param = getNextVisualParam()) - { - LLViewerVisualParam* viewer_param = (LLViewerVisualParam*)param; - dump_visual_param(file, viewer_param, viewer_param->getWeight()); - } - - for (U8 te = 0; te < TEX_NUM_INDICES; te++) - { - // MULTIPLE_WEARABLES: extend to multiple wearables? - LLViewerTexture* te_image = getImage((ETextureIndex)te, 0); - if( te_image ) - { - std::string uuid_str = LLUUID().asString(); - if (agent_is_godlike) - { - te_image->getID().toString(uuid_str); - } - apr_file_printf( file, "\t\t\n", te, uuid_str.c_str()); - } - } - } - - // Root joint - const LLVector3& pos = mRoot->getPosition(); - const LLVector3& scale = mRoot->getScale(); - apr_file_printf( file, "\t\t\n", - mRoot->getName().c_str(), pos[0], pos[1], pos[2], scale[0], scale[1], scale[2]); - - // Bones - std::vector bone_names, cv_names, attach_names, all_names; - getSortedJointNames(0, bone_names); - getSortedJointNames(1, cv_names); - getSortedJointNames(2, attach_names); - all_names.insert(all_names.end(), bone_names.begin(), bone_names.end()); - all_names.insert(all_names.end(), cv_names.begin(), cv_names.end()); - all_names.insert(all_names.end(), attach_names.begin(), attach_names.end()); - - for (std::vector::iterator name_iter = bone_names.begin(); - name_iter != bone_names.end(); ++name_iter) - { - LLJoint *pJoint = getJoint(*name_iter); - const LLVector3& pos = pJoint->getPosition(); - const LLVector3& scale = pJoint->getScale(); - apr_file_printf( file, "\t\t\n", - pJoint->getName().c_str(), pos[0], pos[1], pos[2], scale[0], scale[1], scale[2]); - } - - // Collision volumes - for (std::vector::iterator name_iter = cv_names.begin(); - name_iter != cv_names.end(); ++name_iter) - { - LLJoint *pJoint = getJoint(*name_iter); - const LLVector3& pos = pJoint->getPosition(); - const LLVector3& scale = pJoint->getScale(); - apr_file_printf( file, "\t\t\n", - pJoint->getName().c_str(), pos[0], pos[1], pos[2], scale[0], scale[1], scale[2]); - } - - // Attachment joints - for (std::vector::iterator name_iter = attach_names.begin(); - name_iter != attach_names.end(); ++name_iter) - { - LLJoint *pJoint = getJoint(*name_iter); - if (!pJoint) continue; - const LLVector3& pos = pJoint->getPosition(); - const LLVector3& scale = pJoint->getScale(); - apr_file_printf( file, "\t\t\n", - pJoint->getName().c_str(), pos[0], pos[1], pos[2], scale[0], scale[1], scale[2]); - } - - // Joint pos overrides - for (std::vector::iterator name_iter = all_names.begin(); - name_iter != all_names.end(); ++name_iter) - { - LLJoint *pJoint = getJoint(*name_iter); - - LLVector3 pos; - LLUUID mesh_id; - - if (pJoint && pJoint->hasAttachmentPosOverride(pos,mesh_id)) - { - S32 num_pos_overrides; - std::set distinct_pos_overrides; - pJoint->getAllAttachmentPosOverrides(num_pos_overrides, distinct_pos_overrides); - apr_file_printf( file, "\t\t\n", - pJoint->getName().c_str(), pos[0], pos[1], pos[2], mesh_id.asString().c_str(), - num_pos_overrides, (S32) distinct_pos_overrides.size()); - } - } - // Joint scale overrides - for (std::vector::iterator name_iter = all_names.begin(); - name_iter != all_names.end(); ++name_iter) - { - LLJoint *pJoint = getJoint(*name_iter); - - LLVector3 scale; - LLUUID mesh_id; - - if (pJoint && pJoint->hasAttachmentScaleOverride(scale,mesh_id)) - { - S32 num_scale_overrides; - std::set distinct_scale_overrides; - pJoint->getAllAttachmentPosOverrides(num_scale_overrides, distinct_scale_overrides); - apr_file_printf( file, "\t\t\n", - pJoint->getName().c_str(), scale[0], scale[1], scale[2], mesh_id.asString().c_str(), - num_scale_overrides, (S32) distinct_scale_overrides.size()); - } - } - F32 pelvis_fixup; - LLUUID mesh_id; - if (hasPelvisFixup(pelvis_fixup, mesh_id)) - { - apr_file_printf( file, "\t\t\n", - pelvis_fixup, mesh_id.asString().c_str()); - } - - LLVector3 rp = getRootJoint()->getWorldPosition(); - LLVector4a rpv; - rpv.load3(rp.mV); - - for (S32 joint_num = 0; joint_num < LL_CHARACTER_MAX_ANIMATED_JOINTS; joint_num++) - { - LLJoint *joint = getJoint(joint_num); - if (joint_num < mJointRiggingInfoTab.size()) - { - LLJointRiggingInfo& rig_info = mJointRiggingInfoTab[joint_num]; - if (rig_info.isRiggedTo()) - { - LLMatrix4a mat; - LLVector4a new_extents[2]; - mat.loadu(joint->getWorldMatrix()); - matMulBoundBox(mat, rig_info.getRiggedExtents(), new_extents); - LLVector4a rrp[2]; - rrp[0].setSub(new_extents[0],rpv); - rrp[1].setSub(new_extents[1],rpv); - apr_file_printf( file, "\t\t\n", - joint_num, - joint->getName().c_str(), - rig_info.getRiggedExtents()[0][0], - rig_info.getRiggedExtents()[0][1], - rig_info.getRiggedExtents()[0][2], - rig_info.getRiggedExtents()[1][0], - rig_info.getRiggedExtents()[1][1], - rig_info.getRiggedExtents()[1][2], - rrp[0][0], - rrp[0][1], - rrp[0][2], - rrp[1][0], - rrp[1][1], - rrp[1][2] ); - } - } - } - - bool ultra_verbose = false; - if (isSelf() && ultra_verbose) - { - // show the cloned params inside the wearables as well. - gAgentAvatarp->dumpWearableInfo(outfile); - } - - apr_file_printf( file, "\t\n" ); - apr_file_printf( file, "\n\n" ); - - LLSD args; - args["PATH"] = fullpath; - LLNotificationsUtil::add("AppearanceToXMLSaved", args); - } - else - { - LLNotificationsUtil::add("AppearanceToXMLFailed"); - } - // File will close when handle goes out of scope -} - - -void LLVOAvatar::setVisibilityRank(U32 rank) -{ - if (mDrawable.isNull() || mDrawable->isDead()) - { - // do nothing - return; - } - mVisibilityRank = rank; -} - -// Assumes LLVOAvatar::sInstances has already been sorted. -S32 LLVOAvatar::getUnbakedPixelAreaRank() -{ - S32 rank = 1; - for (std::vector::iterator iter = LLCharacter::sInstances.begin(); - iter != LLCharacter::sInstances.end(); ++iter) - { - LLVOAvatar* inst = (LLVOAvatar*) *iter; - if (inst == this) - { - return rank; - } - else if (!inst->isDead() && !inst->isFullyBaked()) - { - rank++; - } - } - - llassert(0); - return 0; -} - -struct CompareScreenAreaGreater -{ - bool operator()(const LLCharacter* const& lhs, const LLCharacter* const& rhs) - { - return lhs->getPixelArea() > rhs->getPixelArea(); - } -}; - -// static -void LLVOAvatar::cullAvatarsByPixelArea() -{ - std::sort(LLCharacter::sInstances.begin(), LLCharacter::sInstances.end(), CompareScreenAreaGreater()); - - // Update the avatars that have changed status - U32 rank = 2; //1 is reserved for self. - for (std::vector::iterator iter = LLCharacter::sInstances.begin(); - iter != LLCharacter::sInstances.end(); ++iter) - { - LLVOAvatar* inst = (LLVOAvatar*) *iter; - bool culled; - if (inst->isSelf() || inst->isFullyBaked()) - { - culled = false; - } - else - { - culled = true; - } - - if (inst->mCulled != culled) - { - inst->mCulled = culled; - LL_DEBUGS() << "avatar " << inst->getID() << (culled ? " start culled" : " start not culled" ) << LL_ENDL; - inst->updateMeshTextures(); - } - - if (inst->isSelf()) - { - inst->setVisibilityRank(1); - } - else if (inst->mDrawable.notNull() && inst->mDrawable->isVisible()) - { - inst->setVisibilityRank(rank++); - } - } - - // runway - this doesn't really detect gray/grey state. - S32 grey_avatars = 0; - if (!LLVOAvatar::areAllNearbyInstancesBaked(grey_avatars)) - { - if (gFrameTimeSeconds != sUnbakedUpdateTime) // only update once per frame - { - sUnbakedUpdateTime = gFrameTimeSeconds; - sUnbakedTime += gFrameIntervalSeconds.value(); - } - if (grey_avatars > 0) - { - if (gFrameTimeSeconds != sGreyUpdateTime) // only update once per frame - { - sGreyUpdateTime = gFrameTimeSeconds; - sGreyTime += gFrameIntervalSeconds.value(); - } - } - } -} - -void LLVOAvatar::startAppearanceAnimation() -{ - if(!mAppearanceAnimating) - { - mAppearanceAnimating = true; - mAppearanceMorphTimer.reset(); - mLastAppearanceBlendTime = 0.f; - } -} - -// virtual -void LLVOAvatar::removeMissingBakedTextures() -{ -} - -//virtual -void LLVOAvatar::updateRegion(LLViewerRegion *regionp) -{ - LLViewerObject::updateRegion(regionp); -} - -// virtual -std::string LLVOAvatar::getFullname() const -{ - std::string name; - - LLNameValue* first = getNVPair("FirstName"); - LLNameValue* last = getNVPair("LastName"); - if (first && last) - { - name = LLCacheName::buildFullName( first->getString(), last->getString() ); - } - - return name; -} - -LLHost LLVOAvatar::getObjectHost() const -{ - LLViewerRegion* region = getRegion(); - if (region && !isDead()) - { - return region->getHost(); - } - else - { - return LLHost(); - } -} - -bool LLVOAvatar::updateLOD() -{ - if (mDrawable.isNull()) - { - return false; - } - - if (!LLPipeline::sImpostorRender && isImpostor() && 0 != mDrawable->getNumFaces() && mDrawable->getFace(0)->hasGeometry()) - { - return true; - } - - bool res = updateJointLODs(); - - LLFace* facep = mDrawable->getFace(0); - if (!facep || !facep->getVertexBuffer()) - { - dirtyMesh(2); - } - - if (mDirtyMesh >= 2 || mDrawable->isState(LLDrawable::REBUILD_GEOMETRY)) - { //LOD changed or new mesh created, allocate new vertex buffer if needed - updateMeshData(); - mDirtyMesh = 0; - mNeedsSkin = true; - mDrawable->clearState(LLDrawable::REBUILD_GEOMETRY); - } - updateVisibility(); - - return res; -} - -void LLVOAvatar::updateLODRiggedAttachments() -{ - updateLOD(); - rebuildRiggedAttachments(); -} - -void showRigInfoTabExtents(LLVOAvatar *avatar, LLJointRiggingInfoTab& tab, S32& count_rigged, S32& count_box) -{ - count_rigged = count_box = 0; - LLVector4a zero_vec; - zero_vec.clear(); - for (S32 i=0; igetJoint(i); - LL_DEBUGS("RigSpam") << "joint " << i << " name " << joint->getName() << " box " - << tab[i].getRiggedExtents()[0] << ", " << tab[i].getRiggedExtents()[1] << LL_ENDL; - if ((!tab[i].getRiggedExtents()[0].equals3(zero_vec)) || - (!tab[i].getRiggedExtents()[1].equals3(zero_vec))) - { - count_box++; - } - } - } -} - -void LLVOAvatar::getAssociatedVolumes(std::vector& volumes) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - for ( LLVOAvatar::attachment_map_t::iterator iter = mAttachmentPoints.begin(); iter != mAttachmentPoints.end(); ++iter ) - { - LLViewerJointAttachment* attachment = iter->second; - LLViewerJointAttachment::attachedobjs_vec_t::iterator attach_end = attachment->mAttachedObjects.end(); - - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attach_iter = attachment->mAttachedObjects.begin(); - attach_iter != attach_end; ++attach_iter) - { - LLViewerObject* attached_object = attach_iter->get(); - LLVOVolume *volume = dynamic_cast(attached_object); - if (volume) - { - volumes.push_back(volume); - if (volume->isAnimatedObject()) - { - // For animated object attachment, don't need - // the children. Will just get bounding box - // from the control avatar. - continue; - } - } - LLViewerObject::const_child_list_t& children = attached_object->getChildren(); - for (LLViewerObject::const_child_list_t::const_iterator it = children.begin(); - it != children.end(); ++it) - { - LLViewerObject *childp = *it; - LLVOVolume *volume = dynamic_cast(childp); - if (volume) - { - volumes.push_back(volume); - } - } - } - } - - LLControlAvatar *control_av = dynamic_cast(this); - if (control_av) - { - LLVOVolume *volp = control_av->mRootVolp; - if (volp) - { - volumes.push_back(volp); - LLViewerObject::const_child_list_t& children = volp->getChildren(); - for (LLViewerObject::const_child_list_t::const_iterator it = children.begin(); - it != children.end(); ++it) - { - LLViewerObject *childp = *it; - LLVOVolume *volume = dynamic_cast(childp); - if (volume) - { - volumes.push_back(volume); - } - } - } - } -} - -// virtual -void LLVOAvatar::updateRiggingInfo() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - - LL_DEBUGS("RigSpammish") << getFullname() << " updating rig tab" << LL_ENDL; - - std::vector volumes; - - getAssociatedVolumes(volumes); - - std::map curr_rigging_info_key; - { - // Get current rigging info key - for (std::vector::iterator it = volumes.begin(); it != volumes.end(); ++it) - { - LLVOVolume *vol = *it; - if (vol->isMesh() && vol->getVolume()) - { - const LLUUID& mesh_id = vol->getVolume()->getParams().getSculptID(); - S32 max_lod = llmax(vol->getLOD(), vol->mLastRiggingInfoLOD); - curr_rigging_info_key[mesh_id] = max_lod; - } - } - - // Check for key change, which indicates some change in volume composition or LOD. - if (curr_rigging_info_key == mLastRiggingInfoKey) - { - return; - } - } - - // Something changed. Update. - mLastRiggingInfoKey = curr_rigging_info_key; - mJointRiggingInfoTab.clear(); - for (std::vector::iterator it = volumes.begin(); it != volumes.end(); ++it) - { - LLVOVolume *vol = *it; - vol->updateRiggingInfo(); - mJointRiggingInfoTab.merge(vol->mJointRiggingInfoTab); - } - - //LL_INFOS() << "done update rig count is " << countRigInfoTab(mJointRiggingInfoTab) << LL_ENDL; - LL_DEBUGS("RigSpammish") << getFullname() << " after update rig tab:" << LL_ENDL; - S32 joint_count, box_count; - showRigInfoTabExtents(this, mJointRiggingInfoTab, joint_count, box_count); - LL_DEBUGS("RigSpammish") << "uses " << joint_count << " joints " << " nonzero boxes: " << box_count << LL_ENDL; -} - -// virtual -void LLVOAvatar::onActiveOverrideMeshesChanged() -{ - mJointRiggingInfoTab.setNeedsUpdate(true); -} - -U32 LLVOAvatar::getPartitionType() const -{ - // Avatars merely exist as drawables in the bridge partition - return mIsControlAvatar ? LLViewerRegion::PARTITION_CONTROL_AV : LLViewerRegion::PARTITION_AVATAR; -} - -//static -void LLVOAvatar::updateImpostors() -{ - LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD; - - std::vector instances_copy = LLCharacter::sInstances; - for (std::vector::iterator iter = instances_copy.begin(); - iter != instances_copy.end(); ++iter) - { - LLVOAvatar* avatar = (LLVOAvatar*) *iter; - if (!avatar->isDead() - && avatar->isVisible() - && avatar->isImpostor() - && avatar->needsImpostorUpdate()) - { - avatar->calcMutedAVColor(); - gPipeline.generateImpostor(avatar); - } - } - - LLCharacter::sAllowInstancesChange = true; -} - -// virtual -bool LLVOAvatar::isImpostor() -{ - return isVisuallyMuted() || (sLimitNonImpostors && (mUpdatePeriod > 1)); -} - -bool LLVOAvatar::shouldImpostor(const F32 rank_factor) -{ - if (isSelf()) - { - return false; - } - if (isVisuallyMuted()) - { - return true; - } - return sLimitNonImpostors && (mVisibilityRank > sMaxNonImpostors * rank_factor); -} - -bool LLVOAvatar::needsImpostorUpdate() const -{ - return mNeedsImpostorUpdate; -} - -const LLVector3& LLVOAvatar::getImpostorOffset() const -{ - return mImpostorOffset; -} - -const LLVector2& LLVOAvatar::getImpostorDim() const -{ - return mImpostorDim; -} - -void LLVOAvatar::setImpostorDim(const LLVector2& dim) -{ - mImpostorDim = dim; -} - -void LLVOAvatar::cacheImpostorValues() -{ - getImpostorValues(mImpostorExtents, mImpostorAngle, mImpostorDistance); -} - -void LLVOAvatar::getImpostorValues(LLVector4a* extents, LLVector3& angle, F32& distance) const -{ - const LLVector4a* ext = mDrawable->getSpatialExtents(); - extents[0] = ext[0]; - extents[1] = ext[1]; - - LLVector3 at = LLViewerCamera::getInstance()->getOrigin()-(getRenderPosition()+mImpostorOffset); - distance = at.normalize(); - F32 da = 1.f - (at*LLViewerCamera::getInstance()->getAtAxis()); - angle.mV[0] = LLViewerCamera::getInstance()->getYaw()*da; - angle.mV[1] = LLViewerCamera::getInstance()->getPitch()*da; - angle.mV[2] = da; -} - -// static -const U32 LLVOAvatar::NON_IMPOSTORS_MAX_SLIDER = 66; /* Must equal the maximum allowed the RenderAvatarMaxNonImpostors - * slider in panel_preferences_graphics1.xml */ - -// static -void LLVOAvatar::updateImpostorRendering(U32 newMaxNonImpostorsValue) -{ - U32 oldmax = sMaxNonImpostors; - bool oldflg = sLimitNonImpostors; - - if (NON_IMPOSTORS_MAX_SLIDER <= newMaxNonImpostorsValue) - { - sMaxNonImpostors = 0; - } - else - { - sMaxNonImpostors = newMaxNonImpostorsValue; - } - // the sLimitNonImpostors flag depends on whether or not sMaxNonImpostors is set to the no-limit value (0) - sLimitNonImpostors = (0 != sMaxNonImpostors); - if ( oldflg != sLimitNonImpostors ) - { - LL_DEBUGS("AvatarRender") - << "was " << (oldflg ? "use" : "don't use" ) << " impostors (max " << oldmax << "); " - << "now " << (sLimitNonImpostors ? "use" : "don't use" ) << " impostors (max " << sMaxNonImpostors << "); " - << LL_ENDL; - } -} - - -void LLVOAvatar::idleUpdateRenderComplexity() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - if (isControlAvatar()) - { - LLControlAvatar *cav = dynamic_cast(this); - bool is_attachment = cav && cav->mRootVolp && cav->mRootVolp->isAttachment(); // For attached animated objects - if (is_attachment) - { - // ARC for animated object attachments is accounted with the avatar they're attached to. - return; - } - } - - // Render Complexity - calculateUpdateRenderComplexity(); // Update mVisualComplexity if needed - - bool autotune = LLPerfStats::tunables.userAutoTuneEnabled && !mIsControlAvatar && !isSelf(); - if (autotune && !isDead()) - { - static LLCachedControl render_far_clip(gSavedSettings, "RenderFarClip", 64); - F32 radius = render_far_clip * render_far_clip; - - bool is_nearby = true; - if ((dist_vec_squared(getPositionGlobal(), gAgent.getPositionGlobal()) > radius) && - (dist_vec_squared(getPositionGlobal(), gAgentCamera.getCameraPositionGlobal()) > radius)) - { - is_nearby = false; - } - - if (is_nearby && (sAVsIgnoringARTLimit.size() < MIN_NONTUNED_AVS)) - { - if (std::count(sAVsIgnoringARTLimit.begin(), sAVsIgnoringARTLimit.end(), mID) == 0) - { - sAVsIgnoringARTLimit.push_back(mID); - } - } - else if (!is_nearby) - { - sAVsIgnoringARTLimit.erase(std::remove(sAVsIgnoringARTLimit.begin(), sAVsIgnoringARTLimit.end(), mID), - sAVsIgnoringARTLimit.end()); - } - updateNearbyAvatarCount(); - } -} - -void LLVOAvatar::updateNearbyAvatarCount() -{ - static LLFrameTimer agent_update_timer; - - if (agent_update_timer.getElapsedTimeF32() > 1.0f) - { - S32 avs_nearby = 0; - static LLCachedControl render_far_clip(gSavedSettings, "RenderFarClip", 64); - F32 radius = render_far_clip * render_far_clip; - std::vector::iterator char_iter = LLCharacter::sInstances.begin(); - while (char_iter != LLCharacter::sInstances.end()) - { - LLVOAvatar *avatar = dynamic_cast(*char_iter); - if (avatar && !avatar->isDead() && !avatar->isControlAvatar()) - { - if ((dist_vec_squared(avatar->getPositionGlobal(), gAgent.getPositionGlobal()) > radius) && - (dist_vec_squared(avatar->getPositionGlobal(), gAgentCamera.getCameraPositionGlobal()) > radius)) - { - char_iter++; - continue; - } - avs_nearby++; - } - char_iter++; - } - sAvatarsNearby = avs_nearby; - agent_update_timer.reset(); - } -} - -void LLVOAvatar::idleUpdateDebugInfo() -{ - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_AVATAR_DRAW_INFO)) - { - std::string info_line; - F32 red_level; - F32 green_level; - LLColor4 info_color; - LLFontGL::StyleFlags info_style; - - if ( !mText ) - { - initHudText(); - mText->setFadeDistance(20.0, 5.0); // limit clutter in large crowds - } - else - { - mText->clearString(); // clear debug text - } - - /* - * NOTE: the logic for whether or not each of the values below - * controls muting MUST match that in the isVisuallyMuted and isTooComplex methods. - */ - - static LLCachedControl max_render_cost(gSavedSettings, "RenderAvatarMaxComplexity", 0); - info_line = llformat("%d Complexity", mVisualComplexity); - - if (max_render_cost != 0) // zero means don't care, so don't bother coloring based on this - { - green_level = 1.f-llclamp(((F32) mVisualComplexity-(F32)max_render_cost)/(F32)max_render_cost, 0.f, 1.f); - red_level = llmin((F32) mVisualComplexity/(F32)max_render_cost, 1.f); - info_color.set(red_level, green_level, 0.0, 1.0); - info_style = ( mVisualComplexity > max_render_cost - ? LLFontGL::BOLD : LLFontGL::NORMAL ); - } - else - { - info_color.set(LLColor4::grey); - info_style = LLFontGL::NORMAL; - } - mText->addLine(info_line, info_color, info_style); - - // Visual rank - info_line = llformat("%d rank", mVisibilityRank); - // Use grey for imposters, white for normal rendering or no impostors - info_color.set(isImpostor() ? LLColor4::grey : (isControlAvatar() ? LLColor4::yellow : LLColor4::white)); - info_style = LLFontGL::NORMAL; - mText->addLine(info_line, info_color, info_style); - - // Triangle count - mText->addLine(std::string("VisTris ") + LLStringOps::getReadableNumber(mAttachmentVisibleTriangleCount), - info_color, info_style); - mText->addLine(std::string("EstMaxTris ") + LLStringOps::getReadableNumber(mAttachmentEstTriangleCount), - info_color, info_style); - - // Attachment Surface Area - static LLCachedControl max_attachment_area(gSavedSettings, "RenderAutoMuteSurfaceAreaLimit", 1000.0f); - info_line = llformat("%.0f m^2", mAttachmentSurfaceArea); - - if (max_render_cost != 0 && max_attachment_area != 0) // zero means don't care, so don't bother coloring based on this - { - green_level = 1.f-llclamp((mAttachmentSurfaceArea-max_attachment_area)/max_attachment_area, 0.f, 1.f); - red_level = llmin(mAttachmentSurfaceArea/max_attachment_area, 1.f); - info_color.set(red_level, green_level, 0.0, 1.0); - info_style = ( mAttachmentSurfaceArea > max_attachment_area - ? LLFontGL::BOLD : LLFontGL::NORMAL ); - - } - else - { - info_color.set(LLColor4::grey); - info_style = LLFontGL::NORMAL; - } - - mText->addLine(info_line, info_color, info_style); - - updateText(); // corrects position - } -} - -void LLVOAvatar::updateVisualComplexity() -{ - LL_DEBUGS("AvatarRender") << "avatar " << getID() << " appearance changed" << LL_ENDL; - // Set the cache time to in the past so it's updated ASAP - mVisualComplexityStale = true; -} - - -// Account for the complexity of a single top-level object associated -// with an avatar. This will be either an attached object or an animated -// object. -void LLVOAvatar::accountRenderComplexityForObject( - LLViewerObject *attached_object, - const F32 max_attachment_complexity, - LLVOVolume::texture_cost_t& textures, - U32& cost, - hud_complexity_list_t& hud_complexity_list, - object_complexity_list_t& object_complexity_list) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - if (attached_object && !attached_object->isHUDAttachment()) - { - mAttachmentVisibleTriangleCount += attached_object->recursiveGetTriangleCount(); - mAttachmentEstTriangleCount += attached_object->recursiveGetEstTrianglesMax(); - mAttachmentSurfaceArea += attached_object->recursiveGetScaledSurfaceArea(); - - textures.clear(); - const LLDrawable* drawable = attached_object->mDrawable; - if (drawable) - { - const LLVOVolume* volume = drawable->getVOVolume(); - if (volume) - { - F32 attachment_total_cost = 0; - F32 attachment_volume_cost = 0; - F32 attachment_texture_cost = 0; - F32 attachment_children_cost = 0; - const F32 animated_object_attachment_surcharge = 1000; - - if (volume->isAnimatedObjectFast()) - { - attachment_volume_cost += animated_object_attachment_surcharge; - } - attachment_volume_cost += volume->getRenderCost(textures); - - const_child_list_t children = volume->getChildren(); - for (const_child_list_t::const_iterator child_iter = children.begin(); - child_iter != children.end(); - ++child_iter) - { - LLViewerObject* child_obj = *child_iter; - LLVOVolume* child = dynamic_cast(child_obj); - if (child) - { - attachment_children_cost += child->getRenderCost(textures); - } - } - - for (LLVOVolume::texture_cost_t::iterator volume_texture = textures.begin(); - volume_texture != textures.end(); - ++volume_texture) - { - // add the cost of each individual texture in the linkset - attachment_texture_cost += LLVOVolume::getTextureCost(*volume_texture); - } - attachment_total_cost = attachment_volume_cost + attachment_texture_cost + attachment_children_cost; - LL_DEBUGS("ARCdetail") << "Attachment costs " << attached_object->getAttachmentItemID() - << " total: " << attachment_total_cost - << ", volume: " << attachment_volume_cost - << ", " << textures.size() - << " textures: " << attachment_texture_cost - << ", " << volume->numChildren() - << " children: " << attachment_children_cost - << LL_ENDL; - // Limit attachment complexity to avoid signed integer flipping of the wearer's ACI - cost += (U32)llclamp(attachment_total_cost, MIN_ATTACHMENT_COMPLEXITY, max_attachment_complexity); - - if (isSelf()) - { - LLObjectComplexity object_complexity; - object_complexity.objectName = attached_object->getAttachmentItemName(); - object_complexity.objectId = attached_object->getAttachmentItemID(); - object_complexity.objectCost = attachment_total_cost; - object_complexity_list.push_back(object_complexity); - } - } - } - } - if (isSelf() - && attached_object - && attached_object->isHUDAttachment() - && !attached_object->isTempAttachment() - && attached_object->mDrawable) - { - textures.clear(); - mAttachmentSurfaceArea += attached_object->recursiveGetScaledSurfaceArea(); - - const LLVOVolume* volume = attached_object->mDrawable->getVOVolume(); - if (volume) - { - bool is_rigged_mesh = volume->isRiggedMeshFast(); - LLHUDComplexity hud_object_complexity; - hud_object_complexity.objectName = attached_object->getAttachmentItemName(); - hud_object_complexity.objectId = attached_object->getAttachmentItemID(); - std::string joint_name; - gAgentAvatarp->getAttachedPointName(attached_object->getAttachmentItemID(), joint_name); - hud_object_complexity.jointName = joint_name; - // get cost and individual textures - hud_object_complexity.objectsCost += volume->getRenderCost(textures); - hud_object_complexity.objectsCount++; - - LLViewerObject::const_child_list_t& child_list = attached_object->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); ++iter) - { - LLViewerObject* childp = *iter; - const LLVOVolume* chld_volume = dynamic_cast(childp); - if (chld_volume) - { - is_rigged_mesh = is_rigged_mesh || chld_volume->isRiggedMeshFast(); - // get cost and individual textures - hud_object_complexity.objectsCost += chld_volume->getRenderCost(textures); - hud_object_complexity.objectsCount++; - } - } - if (is_rigged_mesh && !attached_object->mRiggedAttachedWarned) - { - LLSD args; - LLViewerInventoryItem* itemp = gInventory.getItem(attached_object->getAttachmentItemID()); - args["NAME"] = itemp ? itemp->getName() : LLTrans::getString("Unknown"); - args["POINT"] = LLTrans::getString(getTargetAttachmentPoint(attached_object)->getName()); - LLNotificationsUtil::add("RiggedMeshAttachedToHUD", args); - - attached_object->mRiggedAttachedWarned = true; - } - - hud_object_complexity.texturesCount += textures.size(); - - for (LLVOVolume::texture_cost_t::iterator volume_texture = textures.begin(); - volume_texture != textures.end(); - ++volume_texture) - { - // add the cost of each individual texture (ignores duplicates) - hud_object_complexity.texturesCost += LLVOVolume::getTextureCost(*volume_texture); - const LLViewerTexture* img = *volume_texture; - if (img->getType() == LLViewerTexture::FETCHED_TEXTURE) - { - LLViewerFetchedTexture* tex = (LLViewerFetchedTexture*)img; - // Note: Texture memory might be incorect since texture might be still loading. - hud_object_complexity.texturesMemoryTotal += tex->getTextureMemory(); - if (tex->getOriginalHeight() * tex->getOriginalWidth() >= HUD_OVERSIZED_TEXTURE_DATA_SIZE) - { - hud_object_complexity.largeTexturesCount++; - } - } - } - hud_complexity_list.push_back(hud_object_complexity); - } - } -} - -// Calculations for mVisualComplexity value -void LLVOAvatar::calculateUpdateRenderComplexity() -{ - /***************************************************************** - * This calculation should not be modified by third party viewers, - * since it is used to limit rendering and should be uniform for - * everyone. If you have suggested improvements, submit them to - * the official viewer for consideration. - *****************************************************************/ - if (mVisualComplexityStale) - { - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - - static const U32 COMPLEXITY_BODY_PART_COST = 200; - static LLCachedControl max_complexity_setting(gSavedSettings, "MaxAttachmentComplexity"); - F32 max_attachment_complexity = max_complexity_setting; - max_attachment_complexity = llmax(max_attachment_complexity, DEFAULT_MAX_ATTACHMENT_COMPLEXITY); - - // Diagnostic list of all textures on our avatar - static std::unordered_set all_textures; - - U32 cost = VISUAL_COMPLEXITY_UNKNOWN; - LLVOVolume::texture_cost_t textures; - hud_complexity_list_t hud_complexity_list; - object_complexity_list_t object_complexity_list; - - for (U8 baked_index = 0; baked_index < BAKED_NUM_INDICES; baked_index++) - { - const LLAvatarAppearanceDictionary::BakedEntry *baked_dict - = LLAvatarAppearance::getDictionary()->getBakedTexture((EBakedTextureIndex)baked_index); - ETextureIndex tex_index = baked_dict->mTextureIndex; - if ((tex_index != TEX_SKIRT_BAKED) || (isWearingWearableType(LLWearableType::WT_SKIRT))) - { - // Same as isTextureVisible(), but doesn't account for isSelf to ensure identical numbers for all avatars - if (isIndexLocalTexture(tex_index)) - { - if (isTextureDefined(tex_index, 0)) - { - cost += COMPLEXITY_BODY_PART_COST; - } - } - else - { - // baked textures can use TE images directly - if (isTextureDefined(tex_index) - && (getTEImage(tex_index)->getID() != IMG_INVISIBLE || LLDrawPoolAlpha::sShowDebugAlpha)) - { - cost += COMPLEXITY_BODY_PART_COST; - } - } - } - } - LL_DEBUGS("ARCdetail") << "Avatar body parts complexity: " << cost << LL_ENDL; - - mAttachmentVisibleTriangleCount = 0; - mAttachmentEstTriangleCount = 0.f; - mAttachmentSurfaceArea = 0.f; - - // A standalone animated object needs to be accounted for - // using its associated volume. Attached animated objects - // will be covered by the subsequent loop over attachments. - LLControlAvatar *control_av = dynamic_cast(this); - if (control_av) - { - LLVOVolume *volp = control_av->mRootVolp; - if (volp && !volp->isAttachment()) - { - accountRenderComplexityForObject(volp, max_attachment_complexity, - textures, cost, hud_complexity_list, object_complexity_list); - } - } - - // Account for complexity of all attachments. - for (attachment_map_t::const_iterator attachment_point = mAttachmentPoints.begin(); - attachment_point != mAttachmentPoints.end(); - ++attachment_point) - { - LLViewerJointAttachment* attachment = attachment_point->second; - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - LLViewerObject* attached_object = attachment_iter->get(); - accountRenderComplexityForObject(attached_object, max_attachment_complexity, - textures, cost, hud_complexity_list, object_complexity_list); - } - } - - if ( cost != mVisualComplexity ) - { - LL_DEBUGS("AvatarRender") << "Avatar "<< getID() - << " complexity updated was " << mVisualComplexity << " now " << cost - << " reported " << mReportedVisualComplexity - << LL_ENDL; - } - else - { - LL_DEBUGS("AvatarRender") << "Avatar "<< getID() - << " complexity updated no change " << mVisualComplexity - << " reported " << mReportedVisualComplexity - << LL_ENDL; - } - mVisualComplexity = cost; - mVisualComplexityStale = false; - - static LLCachedControl show_my_complexity_changes(gSavedSettings, "ShowMyComplexityChanges", 20); - - if (isSelf() && show_my_complexity_changes) - { - // Avatar complexity - LLAvatarRenderNotifier::getInstance()->updateNotificationAgent(mVisualComplexity); - LLAvatarRenderNotifier::getInstance()->setObjectComplexityList(object_complexity_list); - // HUD complexity - LLHUDRenderNotifier::getInstance()->updateNotificationHUD(hud_complexity_list); - } - - //schedule an update to ART next frame if needed - if (LLPerfStats::tunables.userAutoTuneEnabled && - LLPerfStats::tunables.userFPSTuningStrategy != LLPerfStats::TUNE_SCENE_ONLY && - !isVisuallyMuted()) - { - LLUUID id = getID(); // <== use id to make sure this avatar didn't get deleted between frames - LL::WorkQueue::getInstance("mainloop")->post([this, id]() - { - if (gObjectList.findObject(id) != nullptr) - { - gPipeline.profileAvatar(this); - } - }); - } - } -} - -void LLVOAvatar::setVisualMuteSettings(VisualMuteSettings set) -{ - mVisuallyMuteSetting = set; - mNeedsImpostorUpdate = true; - mLastImpostorUpdateReason = 7; - - LLRenderMuteList::getInstance()->saveVisualMuteSetting(getID(), S32(set)); -} - - -void LLVOAvatar::setOverallAppearanceNormal() -{ - if (isControlAvatar()) - return; - - LLVector3 pelvis_pos = getJoint("mPelvis")->getPosition(); - if (isControlAvatar() || mLastProcessedAppearance) - { - resetSkeleton(false); - } - getJoint("mPelvis")->setPosition(pelvis_pos); - - for (auto it = mJellyAnims.begin(); it != mJellyAnims.end(); ++it) - { - bool is_playing = (mPlayingAnimations.find(*it) != mPlayingAnimations.end()); - LL_DEBUGS("Avatar") << "jelly anim " << *it << " " << is_playing << LL_ENDL; - if (!is_playing) - { - // Anim was not requested for this av by sim, but may be playing locally - stopMotion(*it); - } - } - mJellyAnims.clear(); - - processAnimationStateChanges(); -} - -void LLVOAvatar::setOverallAppearanceJellyDoll() -{ - if (isControlAvatar()) - return; - - // stop current animations - { - for ( LLVOAvatar::AnimIterator anim_it= mPlayingAnimations.begin(); - anim_it != mPlayingAnimations.end(); - ++anim_it) - { - { - stopMotion(anim_it->first, true); - } - } - } - processAnimationStateChanges(); - - // Start any needed anims for jellydoll - updateOverallAppearanceAnimations(); - - LLVector3 pelvis_pos = getJoint("mPelvis")->getPosition(); - resetSkeleton(false); - getJoint("mPelvis")->setPosition(pelvis_pos); - -} - -void LLVOAvatar::setOverallAppearanceInvisible() -{ -} - -void LLVOAvatar::updateOverallAppearance() -{ - AvatarOverallAppearance new_overall = getOverallAppearance(); - if (new_overall != mOverallAppearance) - { - switch (new_overall) - { - case AOA_NORMAL: - setOverallAppearanceNormal(); - break; - case AOA_JELLYDOLL: - setOverallAppearanceJellyDoll(); - break; - case AOA_INVISIBLE: - setOverallAppearanceInvisible(); - break; - } - mOverallAppearance = new_overall; - if (!isSelf()) - { - mNeedsImpostorUpdate = true; - mLastImpostorUpdateReason = 8; - } - updateMeshVisibility(); - } - - // This needs to be done even if overall appearance has not - // changed, since sit/stand status can be different. - updateOverallAppearanceAnimations(); -} - -void LLVOAvatar::updateOverallAppearanceAnimations() -{ - if (isControlAvatar()) - return; - - if (getOverallAppearance() == AOA_JELLYDOLL) - { - LLUUID motion_id; - if (isSitting() && getParent()) // sitting on object - { - motion_id = ANIM_AGENT_SIT_FEMALE; - } - else if (isSitting()) // sitting on ground - { - motion_id = ANIM_AGENT_SIT_GROUND_CONSTRAINED; - } - else // standing - { - motion_id = ANIM_AGENT_STAND; - } - if (mJellyAnims.find(motion_id) == mJellyAnims.end()) - { - for (auto it = mJellyAnims.begin(); it != mJellyAnims.end(); ++it) - { - bool is_playing = (mPlayingAnimations.find(*it) != mPlayingAnimations.end()); - LL_DEBUGS("Avatar") << "jelly anim " << *it << " " << is_playing << LL_ENDL; - if (!is_playing) - { - // Anim was not requested for this av by sim, but may be playing locally - stopMotion(*it, true); - } - } - mJellyAnims.clear(); - - startMotion(motion_id); - mJellyAnims.insert(motion_id); - - processAnimationStateChanges(); - } - } -} - -// Based on isVisuallyMuted(), but has 3 possible results. -LLVOAvatar::AvatarOverallAppearance LLVOAvatar::getOverallAppearance() const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - AvatarOverallAppearance result = AOA_NORMAL; - - // Priority order (highest priority first) - // * own avatar is always drawn normally - // * if on the "always draw normally" list, draw them normally - // * if on the "always visually mute" list, show as jellydoll - // * if explicitly muted (blocked), show as invisible - // * check against the render cost and attachment limits - if too complex, show as jellydoll - if (isSelf()) - { - result = AOA_NORMAL; - } - else // !isSelf() - { - if (isInMuteList()) - { - result = AOA_INVISIBLE; - } - else if (mVisuallyMuteSetting == AV_ALWAYS_RENDER) - { - result = AOA_NORMAL; - } - else if (mVisuallyMuteSetting == AV_DO_NOT_RENDER) - { // Always want to see this AV as an impostor - result = AOA_JELLYDOLL; - } - else if (isTooComplex() || isTooSlow()) - { - result = AOA_JELLYDOLL; - } - } - - return result; -} - -void LLVOAvatar::calcMutedAVColor() -{ - LLColor4 new_color(mMutedAVColor); - std::string change_msg; - LLUUID av_id(getID()); - - if (getVisualMuteSettings() == AV_DO_NOT_RENDER) - { - // explicitly not-rendered avatars are light grey - new_color = LLColor4::grey4; - change_msg = " not rendered: color is grey4"; - } - else if (LLMuteList::getInstance()->isMuted(av_id)) // the user blocked them - { - // blocked avatars are dark grey - new_color = LLColor4::grey4; - change_msg = " blocked: color is grey4"; - } - else if (!isTooComplex() && !isTooSlow()) - { - new_color = LLColor4::white; - change_msg = " simple imposter "; - } -#ifdef COLORIZE_JELLYDOLLS - else if ( mMutedAVColor == LLColor4::white || mMutedAVColor == LLColor4::grey3 || mMutedAVColor == LLColor4::grey4 ) - { - // select a color based on the first byte of the agents uuid so any muted agent is always the same color - F32 color_value = (F32) (av_id.mData[0]); - F32 spectrum = (color_value / 256.0); // spectrum is between 0 and 1.f - - // Array of colors. These are arranged so only one RGB color changes between each step, - // and it loops back to red so there is an even distribution. It is not a heat map - const S32 NUM_SPECTRUM_COLORS = 7; - static LLColor4 * spectrum_color[NUM_SPECTRUM_COLORS] = { &LLColor4::red, &LLColor4::magenta, &LLColor4::blue, &LLColor4::cyan, &LLColor4::green, &LLColor4::yellow, &LLColor4::red }; - - spectrum = spectrum * (NUM_SPECTRUM_COLORS - 1); // Scale to range of number of colors - S32 spectrum_index_1 = floor(spectrum); // Desired color will be after this index - S32 spectrum_index_2 = spectrum_index_1 + 1; // and before this index (inclusive) - F32 fractBetween = spectrum - (F32)(spectrum_index_1); // distance between the two indexes (0-1) - - new_color = lerp(*spectrum_color[spectrum_index_1], *spectrum_color[spectrum_index_2], fractBetween); - new_color.normalize(); - new_color *= 0.28f; // Tone it down - } -#endif - else - { - new_color = LLColor4::grey4; - change_msg = " over limit color "; - } - - if (mMutedAVColor != new_color) - { - LL_DEBUGS("AvatarRender") << "avatar "<< av_id << change_msg << std::setprecision(3) << new_color << LL_ENDL; - mMutedAVColor = new_color; - } -} - -// static -bool LLVOAvatar::isIndexLocalTexture(ETextureIndex index) -{ - return (index < 0 || index >= TEX_NUM_INDICES) - ? false - : LLAvatarAppearance::getDictionary()->getTexture(index)->mIsLocalTexture; -} - -// static -bool LLVOAvatar::isIndexBakedTexture(ETextureIndex index) -{ - return (index < 0 || index >= TEX_NUM_INDICES) - ? false - : LLAvatarAppearance::getDictionary()->getTexture(index)->mIsBakedTexture; -} - -const std::string LLVOAvatar::getBakedStatusForPrintout() const -{ - std::string line; - - for (LLAvatarAppearanceDictionary::Textures::const_iterator iter = LLAvatarAppearance::getDictionary()->getTextures().begin(); - iter != LLAvatarAppearance::getDictionary()->getTextures().end(); - ++iter) - { - const ETextureIndex index = iter->first; - const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = iter->second; - if (texture_dict->mIsBakedTexture) - { - line += texture_dict->mName; - if (isTextureDefined(index)) - { - line += "_baked"; - } - line += " "; - } - } - return line; -} - - - -//virtual -S32 LLVOAvatar::getTexImageSize() const -{ - return TEX_IMAGE_SIZE_OTHER; -} - -//----------------------------------------------------------------------------- -// Utility functions -//----------------------------------------------------------------------------- - -F32 calc_bouncy_animation(F32 x) -{ - return -(cosf(x * F_PI * 2.5f - F_PI_BY_TWO))*(0.4f + x * -0.1f) + x * 1.3f; -} - -//virtual -bool LLVOAvatar::isTextureDefined(LLAvatarAppearanceDefines::ETextureIndex te, U32 index ) const -{ - if (isIndexLocalTexture(te)) - { - return false; - } - - LLViewerTexture* tex = getImage(te, index); - if (!tex) - { - LL_WARNS() << "getImage( " << te << ", " << index << " ) returned 0" << LL_ENDL; - return false; - } - - return (tex->getID() != IMG_DEFAULT_AVATAR && - tex->getID() != IMG_DEFAULT); -} - -//virtual -bool LLVOAvatar::isTextureVisible(LLAvatarAppearanceDefines::ETextureIndex type, U32 index) const -{ - if (isIndexLocalTexture(type)) - { - return isTextureDefined(type, index); - } - - // baked textures can use TE images directly - return ((isTextureDefined(type) || isSelf()) && - (getTEImage(type)->getID() != IMG_INVISIBLE || LLDrawPoolAlpha::sShowDebugAlpha)); -} - -//virtual -bool LLVOAvatar::isTextureVisible(LLAvatarAppearanceDefines::ETextureIndex type, LLViewerWearable *wearable) const -{ - // non-self avatars don't have wearables - return false; -} - -void LLVOAvatar::placeProfileQuery() -{ - if (mGPUTimerQuery == 0) - { - glGenQueries(1, &mGPUTimerQuery); - } - - glBeginQuery(GL_TIME_ELAPSED, mGPUTimerQuery); -} - -void LLVOAvatar::readProfileQuery(S32 retries) -{ - if (!mGPUProfilePending) - { - glEndQuery(GL_TIME_ELAPSED); - mGPUProfilePending = true; - } - - GLuint64 result = 0; - glGetQueryObjectui64v(mGPUTimerQuery, GL_QUERY_RESULT_AVAILABLE, &result); - - if (result == GL_TRUE || --retries <= 0) - { // query available, readback result - GLuint64 time_elapsed = 0; - glGetQueryObjectui64v(mGPUTimerQuery, GL_QUERY_RESULT, &time_elapsed); - mGPURenderTime = time_elapsed / 1000000.f; - mGPUProfilePending = false; - - setDebugText(llformat("%d", (S32)(mGPURenderTime * 1000.f))); - - } - else - { // wait until next frame - LLUUID id = getID(); - - LL::WorkQueue::getInstance("mainloop")->post([id, retries] { - LLVOAvatar* avatar = (LLVOAvatar*) gObjectList.findObject(id); - if(avatar) - { - avatar->readProfileQuery(retries); - } - }); - } -} - - -F32 LLVOAvatar::getGPURenderTime() -{ - return isVisuallyMuted() ? 0.f : mGPURenderTime; -} - -// static -F32 LLVOAvatar::getTotalGPURenderTime() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - - F32 ret = 0.f; - - for (LLCharacter* iter : LLCharacter::sInstances) - { - LLVOAvatar* inst = (LLVOAvatar*) iter; - ret += inst->getGPURenderTime(); - } - - return ret; -} - -F32 LLVOAvatar::getMaxGPURenderTime() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - - F32 ret = 0.f; - - for (LLCharacter* iter : LLCharacter::sInstances) - { - LLVOAvatar* inst = (LLVOAvatar*)iter; - ret = llmax(inst->getGPURenderTime(), ret); - } - - return ret; -} - -F32 LLVOAvatar::getAverageGPURenderTime() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - - F32 ret = 0.f; - - S32 count = 0; - - for (LLCharacter* iter : LLCharacter::sInstances) - { - LLVOAvatar* inst = (LLVOAvatar*)iter; - if (!inst->isTooSlow()) - { - ret += inst->getGPURenderTime(); - ++count; - } - } - - if (count > 0) - { - ret /= count; - } - - return ret; -} -bool LLVOAvatar::isBuddy() const -{ - return LLAvatarTracker::instance().isBuddy(getID()); -} - +/** + * @File llvoavatar.cpp + * @brief Implementation of LLVOAvatar class which is a derivation of LLViewerObject + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llvoavatar.h" + +#include +#include +#include + +#include "llaudioengine.h" +#include "noise.h" +#include "sound_ids.h" +#include "raytrace.h" + +#include "llagent.h" // Get state values from here +#include "llagentbenefits.h" +#include "llagentcamera.h" +#include "llagentwearables.h" +#include "llanimationstates.h" +#include "llavatarnamecache.h" +#include "llavatarpropertiesprocessor.h" +#include "llavatarrendernotifier.h" +#include "llcontrolavatar.h" +#include "llexperiencecache.h" +#include "llphysicsmotion.h" +#include "llviewercontrol.h" +#include "llcallingcard.h" // IDEVO for LLAvatarTracker +#include "lldrawpoolavatar.h" +#include "lldriverparam.h" +#include "llpolyskeletaldistortion.h" +#include "lleditingmotion.h" +#include "llemote.h" +#include "llfloatertools.h" +#include "llheadrotmotion.h" +#include "llhudeffecttrail.h" +#include "llhudmanager.h" +#include "llhudnametag.h" +#include "llhudtext.h" // for mText/mDebugText +#include "llimview.h" +#include "llinitparam.h" +#include "llkeyframefallmotion.h" +#include "llkeyframestandmotion.h" +#include "llkeyframewalkmotion.h" +#include "llmanipscale.h" // for get_default_max_prim_scale() +#include "llmeshrepository.h" +#include "llmutelist.h" +#include "llmoveview.h" +#include "llnotificationsutil.h" +#include "llphysicsshapebuilderutil.h" +#include "llquantize.h" +#include "llrand.h" +#include "llregionhandle.h" +#include "llresmgr.h" +#include "llselectmgr.h" +#include "llsprite.h" +#include "lltargetingmotion.h" +#include "lltoolmgr.h" +#include "lltoolmorph.h" +#include "llviewercamera.h" +#include "llviewertexlayer.h" +#include "llviewertexturelist.h" +#include "llviewermenu.h" +#include "llviewerobjectlist.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "llviewershadermgr.h" +#include "llviewerstats.h" +#include "llviewerwearable.h" +#include "llvoavatarself.h" +#include "llvovolume.h" +#include "llworld.h" +#include "pipeline.h" +#include "llviewershadermgr.h" +#include "llsky.h" +#include "llanimstatelabels.h" +#include "lltrans.h" +#include "llappearancemgr.h" + +#include "llgesturemgr.h" //needed to trigger the voice gesticulations +#include "llvoiceclient.h" +#include "llvoicevisualizer.h" // Ventrella + +#include "lldebugmessagebox.h" +#include "llsdutil.h" +#include "llscenemonitor.h" +#include "llsdserialize.h" +#include "llcallstack.h" +#include "llrendersphere.h" +#include "llskinningutil.h" + +#include "llperfstats.h" + +#include + +extern F32 SPEED_ADJUST_MAX; +extern F32 SPEED_ADJUST_MAX_SEC; +extern F32 ANIM_SPEED_MAX; +extern F32 ANIM_SPEED_MIN; +extern U32 JOINT_COUNT_REQUIRED_FOR_FULLRIG; + +const F32 MAX_HOVER_Z = 2.0; +const F32 MIN_HOVER_Z = -2.0; + +const F32 MIN_ATTACHMENT_COMPLEXITY = 0.f; +const F32 DEFAULT_MAX_ATTACHMENT_COMPLEXITY = 1.0e6f; + +// Unlike with 'self' avatar, server doesn't inform viewer about +// expected attachments so viewer has to wait to see if anything +// else will arrive +const F32 FIRST_APPEARANCE_CLOUD_MIN_DELAY = 3.f; // seconds +const F32 FIRST_APPEARANCE_CLOUD_MAX_DELAY = 15.f; +const F32 FIRST_APPEARANCE_CLOUD_IMPOSTOR_MODIFIER = 1.25f; + +using namespace LLAvatarAppearanceDefines; + +//----------------------------------------------------------------------------- +// Global constants +//----------------------------------------------------------------------------- +const LLUUID ANIM_AGENT_BODY_NOISE = LLUUID("9aa8b0a6-0c6f-9518-c7c3-4f41f2c001ad"); //"body_noise" +const LLUUID ANIM_AGENT_BREATHE_ROT = LLUUID("4c5a103e-b830-2f1c-16bc-224aa0ad5bc8"); //"breathe_rot" +const LLUUID ANIM_AGENT_EDITING = LLUUID("2a8eba1d-a7f8-5596-d44a-b4977bf8c8bb"); //"editing" +const LLUUID ANIM_AGENT_EYE = LLUUID("5c780ea8-1cd1-c463-a128-48c023f6fbea"); //"eye" +const LLUUID ANIM_AGENT_FLY_ADJUST = LLUUID("db95561f-f1b0-9f9a-7224-b12f71af126e"); //"fly_adjust" +const LLUUID ANIM_AGENT_HAND_MOTION = LLUUID("ce986325-0ba7-6e6e-cc24-b17c4b795578"); //"hand_motion" +const LLUUID ANIM_AGENT_HEAD_ROT = LLUUID("e6e8d1dd-e643-fff7-b238-c6b4b056a68d"); //"head_rot" +const LLUUID ANIM_AGENT_PELVIS_FIX = LLUUID("0c5dd2a2-514d-8893-d44d-05beffad208b"); //"pelvis_fix" +const LLUUID ANIM_AGENT_TARGET = LLUUID("0e4896cb-fba4-926c-f355-8720189d5b55"); //"target" +const LLUUID ANIM_AGENT_WALK_ADJUST = LLUUID("829bc85b-02fc-ec41-be2e-74cc6dd7215d"); //"walk_adjust" +const LLUUID ANIM_AGENT_PHYSICS_MOTION = LLUUID("7360e029-3cb8-ebc4-863e-212df440d987"); //"physics_motion" + + +//----------------------------------------------------------------------------- +// Constants +//----------------------------------------------------------------------------- +const F32 DELTA_TIME_MIN = 0.01f; // we clamp measured delta_time to this +const F32 DELTA_TIME_MAX = 0.2f; // range to insure stability of computations. + +const F32 PELVIS_LAG_FLYING = 0.22f;// pelvis follow half life while flying +const F32 PELVIS_LAG_WALKING = 0.4f; // ...while walking +const F32 PELVIS_LAG_MOUSELOOK = 0.15f; +const F32 MOUSELOOK_PELVIS_FOLLOW_FACTOR = 0.5f; +const F32 TORSO_NOISE_AMOUNT = 1.0f; // Amount of deviation from up-axis, in degrees +const F32 TORSO_NOISE_SPEED = 0.2f; // Time scale factor on torso noise. + +const F32 BREATHE_ROT_MOTION_STRENGTH = 0.05f; + +const S32 MIN_REQUIRED_PIXEL_AREA_BODY_NOISE = 10000; +const S32 MIN_REQUIRED_PIXEL_AREA_BREATHE = 10000; +const S32 MIN_REQUIRED_PIXEL_AREA_PELVIS_FIX = 40; + +const S32 TEX_IMAGE_SIZE_OTHER = 512 / 4; // The size of local textures for other (!isSelf()) avatars + +const F32 HEAD_MOVEMENT_AVG_TIME = 0.9f; + +const S32 MORPH_MASK_REQUESTED_DISCARD = 0; + +const F32 MAX_STANDOFF_FROM_ORIGIN = 3; +const F32 MAX_STANDOFF_DISTANCE_CHANGE = 32; + +// Discard level at which to switch to baked textures +// Should probably be 4 or 3, but didn't want to change it while change other logic - SJB +const S32 SWITCH_TO_BAKED_DISCARD = 5; + +const F32 HOVER_EFFECT_MAX_SPEED = 3.f; +const F32 HOVER_EFFECT_STRENGTH = 0.f; +const F32 UNDERWATER_EFFECT_STRENGTH = 0.1f; +const F32 UNDERWATER_FREQUENCY_DAMP = 0.33f; +const F32 APPEARANCE_MORPH_TIME = 0.65f; +const F32 TIME_BEFORE_MESH_CLEANUP = 5.f; // seconds +const S32 AVATAR_RELEASE_THRESHOLD = 10; // number of avatar instances before releasing memory +const F32 FOOT_GROUND_COLLISION_TOLERANCE = 0.25f; +const F32 AVATAR_LOD_TWEAK_RANGE = 0.7f; +const S32 MAX_BUBBLE_CHAT_LENGTH = DB_CHAT_MSG_STR_LEN; +const S32 MAX_BUBBLE_CHAT_UTTERANCES = 12; +const F32 CHAT_FADE_TIME = 8.0; +const F32 BUBBLE_CHAT_TIME = CHAT_FADE_TIME * 3.f; +const F32 NAMETAG_UPDATE_THRESHOLD = 0.3f; +const F32 NAMETAG_VERTICAL_SCREEN_OFFSET = 25.f; +const F32 NAMETAG_VERT_OFFSET_WEIGHT = 0.17f; + +const U32 LLVOAvatar::VISUAL_COMPLEXITY_UNKNOWN = 0; +const F64 HUD_OVERSIZED_TEXTURE_DATA_SIZE = 1024 * 1024; + +const F32 MAX_TEXTURE_WAIT_TIME_SEC = 60; +const F32 MAX_ATTACHMENT_WAIT_TIME_SEC = 60; + +const S32 MIN_NONTUNED_AVS = 5; + +enum ERenderName +{ + RENDER_NAME_NEVER, + RENDER_NAME_ALWAYS, + RENDER_NAME_FADE +}; + +#define JELLYDOLLS_SHOULD_IMPOSTOR + +//----------------------------------------------------------------------------- +// Callback data +//----------------------------------------------------------------------------- + +struct LLTextureMaskData +{ + LLTextureMaskData( const LLUUID& id ) : + mAvatarID(id), + mLastDiscardLevel(S32_MAX) + {} + LLUUID mAvatarID; + S32 mLastDiscardLevel; +}; + +/********************************************************************************* + ** ** + ** Begin private LLVOAvatar Support classes + ** + **/ + + +struct LLAppearanceMessageContents: public LLRefCount +{ + LLAppearanceMessageContents(): + mAppearanceVersion(-1), + mParamAppearanceVersion(-1), + mCOFVersion(LLViewerInventoryCategory::VERSION_UNKNOWN) + { + } + LLTEContents mTEContents; + S32 mAppearanceVersion; + S32 mParamAppearanceVersion; + S32 mCOFVersion; + // For future use: + //U32 appearance_flags = 0; + std::vector mParamWeights; + std::vector mParams; + LLVector3 mHoverOffset; + bool mHoverOffsetWasSet; +}; + + +//----------------------------------------------------------------------------- +// class LLBodyNoiseMotion +//----------------------------------------------------------------------------- +class LLBodyNoiseMotion : + public LLMotion +{ +public: + // Constructor + LLBodyNoiseMotion(const LLUUID &id) + : LLMotion(id) + { + mName = "body_noise"; + mTorsoState = new LLJointState; + } + + // Destructor + virtual ~LLBodyNoiseMotion() { } + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return new LLBodyNoiseMotion(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + + // motions must specify whether or not they loop + virtual bool getLoop() { return true; } + + // motions must report their total duration + virtual F32 getDuration() { return 0.0; } + + // motions must report their "ease in" duration + virtual F32 getEaseInDuration() { return 0.0; } + + // motions must report their "ease out" duration. + virtual F32 getEaseOutDuration() { return 0.0; } + + // motions must report their priority + virtual LLJoint::JointPriority getPriority() { return LLJoint::HIGH_PRIORITY; } + + virtual LLMotionBlendType getBlendType() { return ADDITIVE_BLEND; } + + // called to determine when a motion should be activated/deactivated based on avatar pixel coverage + virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_BODY_NOISE; } + + // run-time (post constructor) initialization, + // called after parameters have been set + // must return true to indicate success and be available for activation + virtual LLMotionInitStatus onInitialize(LLCharacter *character) + { + if( !mTorsoState->setJoint( character->getJoint("mTorso") )) + { + return STATUS_FAILURE; + } + + mTorsoState->setUsage(LLJointState::ROT); + + addJointState( mTorsoState ); + return STATUS_SUCCESS; + } + + // called when a motion is activated + // must return true to indicate success, or else + // it will be deactivated + virtual bool onActivate() { return true; } + + // called per time step + // must return true while it is active, and + // must return false when the motion is completed. + virtual bool onUpdate(F32 time, U8* joint_mask) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + F32 nx[2]; + nx[0]=time*TORSO_NOISE_SPEED; + nx[1]=0.0f; + F32 ny[2]; + ny[0]=0.0f; + ny[1]=time*TORSO_NOISE_SPEED; + F32 noiseX = noise2(nx); + F32 noiseY = noise2(ny); + + F32 rx = TORSO_NOISE_AMOUNT * DEG_TO_RAD * noiseX / 0.42f; + F32 ry = TORSO_NOISE_AMOUNT * DEG_TO_RAD * noiseY / 0.42f; + LLQuaternion tQn; + tQn.setQuat( rx, ry, 0.0f ); + mTorsoState->setRotation( tQn ); + + return true; + } + + // called when a motion is deactivated + virtual void onDeactivate() {} + +private: + //------------------------------------------------------------------------- + // joint states to be animated + //------------------------------------------------------------------------- + LLPointer mTorsoState; +}; + +//----------------------------------------------------------------------------- +// class LLBreatheMotionRot +//----------------------------------------------------------------------------- +class LLBreatheMotionRot : + public LLMotion +{ +public: + // Constructor + LLBreatheMotionRot(const LLUUID &id) : + LLMotion(id), + mBreatheRate(1.f), + mCharacter(NULL) + { + mName = "breathe_rot"; + mChestState = new LLJointState; + } + + // Destructor + virtual ~LLBreatheMotionRot() {} + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID &id) { return new LLBreatheMotionRot(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + + // motions must specify whether or not they loop + virtual bool getLoop() { return true; } + + // motions must report their total duration + virtual F32 getDuration() { return 0.0; } + + // motions must report their "ease in" duration + virtual F32 getEaseInDuration() { return 0.0; } + + // motions must report their "ease out" duration. + virtual F32 getEaseOutDuration() { return 0.0; } + + // motions must report their priority + virtual LLJoint::JointPriority getPriority() { return LLJoint::MEDIUM_PRIORITY; } + + virtual LLMotionBlendType getBlendType() { return NORMAL_BLEND; } + + // called to determine when a motion should be activated/deactivated based on avatar pixel coverage + virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_BREATHE; } + + // run-time (post constructor) initialization, + // called after parameters have been set + // must return true to indicate success and be available for activation + virtual LLMotionInitStatus onInitialize(LLCharacter *character) + { + mCharacter = character; + bool success = true; + + if ( !mChestState->setJoint( character->getJoint( "mChest" ) ) ) + { + success = false; + } + + if ( success ) + { + mChestState->setUsage(LLJointState::ROT); + addJointState( mChestState ); + } + + if ( success ) + { + return STATUS_SUCCESS; + } + else + { + return STATUS_FAILURE; + } + } + + // called when a motion is activated + // must return true to indicate success, or else + // it will be deactivated + virtual bool onActivate() { return true; } + + // called per time step + // must return true while it is active, and + // must return false when the motion is completed. + virtual bool onUpdate(F32 time, U8* joint_mask) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + mBreatheRate = 1.f; + + F32 breathe_amt = (sinf(mBreatheRate * time) * BREATHE_ROT_MOTION_STRENGTH); + + mChestState->setRotation(LLQuaternion(breathe_amt, LLVector3(0.f, 1.f, 0.f))); + + return true; + } + + // called when a motion is deactivated + virtual void onDeactivate() {} + +private: + //------------------------------------------------------------------------- + // joint states to be animated + //------------------------------------------------------------------------- + LLPointer mChestState; + F32 mBreatheRate; + LLCharacter* mCharacter; +}; + +//----------------------------------------------------------------------------- +// class LLPelvisFixMotion +//----------------------------------------------------------------------------- +class LLPelvisFixMotion : + public LLMotion +{ +public: + // Constructor + LLPelvisFixMotion(const LLUUID &id) + : LLMotion(id), mCharacter(NULL) + { + mName = "pelvis_fix"; + + mPelvisState = new LLJointState; + } + + // Destructor + virtual ~LLPelvisFixMotion() { } + +public: + //------------------------------------------------------------------------- + // functions to support MotionController and MotionRegistry + //------------------------------------------------------------------------- + // static constructor + // all subclasses must implement such a function and register it + static LLMotion *create(const LLUUID& id) { return new LLPelvisFixMotion(id); } + +public: + //------------------------------------------------------------------------- + // animation callbacks to be implemented by subclasses + //------------------------------------------------------------------------- + + // motions must specify whether or not they loop + virtual bool getLoop() { return true; } + + // motions must report their total duration + virtual F32 getDuration() { return 0.0; } + + // motions must report their "ease in" duration + virtual F32 getEaseInDuration() { return 0.5f; } + + // motions must report their "ease out" duration. + virtual F32 getEaseOutDuration() { return 0.5f; } + + // motions must report their priority + virtual LLJoint::JointPriority getPriority() { return LLJoint::LOW_PRIORITY; } + + virtual LLMotionBlendType getBlendType() { return NORMAL_BLEND; } + + // called to determine when a motion should be activated/deactivated based on avatar pixel coverage + virtual F32 getMinPixelArea() { return MIN_REQUIRED_PIXEL_AREA_PELVIS_FIX; } + + // run-time (post constructor) initialization, + // called after parameters have been set + // must return true to indicate success and be available for activation + virtual LLMotionInitStatus onInitialize(LLCharacter *character) + { + mCharacter = character; + + if (!mPelvisState->setJoint( character->getJoint("mPelvis"))) + { + return STATUS_FAILURE; + } + + mPelvisState->setUsage(LLJointState::POS); + + addJointState( mPelvisState ); + return STATUS_SUCCESS; + } + + // called when a motion is activated + // must return true to indicate success, or else + // it will be deactivated + virtual bool onActivate() { return true; } + + // called per time step + // must return true while it is active, and + // must return false when the motion is completed. + virtual bool onUpdate(F32 time, U8* joint_mask) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + mPelvisState->setPosition(LLVector3::zero); + + return true; + } + + // called when a motion is deactivated + virtual void onDeactivate() {} + +private: + //------------------------------------------------------------------------- + // joint states to be animated + //------------------------------------------------------------------------- + LLPointer mPelvisState; + LLCharacter* mCharacter; +}; + +/** + ** + ** End LLVOAvatar Support classes + ** ** + *********************************************************************************/ + + +//----------------------------------------------------------------------------- +// Static Data +//----------------------------------------------------------------------------- +U32 LLVOAvatar::sMaxNonImpostors = 12; // Set from RenderAvatarMaxNonImpostors +bool LLVOAvatar::sLimitNonImpostors = false; // True unless RenderAvatarMaxNonImpostors is 0 (unlimited) +F32 LLVOAvatar::sRenderDistance = 256.f; +S32 LLVOAvatar::sNumVisibleAvatars = 0; +S32 LLVOAvatar::sNumLODChangesThisFrame = 0; + +const LLUUID LLVOAvatar::sStepSoundOnLand("e8af4a28-aa83-4310-a7c4-c047e15ea0df"); +const LLUUID LLVOAvatar::sStepSounds[LL_MCODE_END] = +{ + SND_STONE_RUBBER, + SND_METAL_RUBBER, + SND_GLASS_RUBBER, + SND_WOOD_RUBBER, + SND_FLESH_RUBBER, + SND_RUBBER_PLASTIC, + SND_RUBBER_RUBBER +}; + +S32 LLVOAvatar::sRenderName = RENDER_NAME_ALWAYS; +bool LLVOAvatar::sRenderGroupTitles = true; +S32 LLVOAvatar::sNumVisibleChatBubbles = 0; +bool LLVOAvatar::sDebugInvisible = false; +bool LLVOAvatar::sShowAttachmentPoints = false; +bool LLVOAvatar::sShowAnimationDebug = false; +bool LLVOAvatar::sVisibleInFirstPerson = false; +F32 LLVOAvatar::sLODFactor = 1.f; +F32 LLVOAvatar::sPhysicsLODFactor = 1.f; +bool LLVOAvatar::sJointDebug = false; +F32 LLVOAvatar::sUnbakedTime = 0.f; +F32 LLVOAvatar::sUnbakedUpdateTime = 0.f; +F32 LLVOAvatar::sGreyTime = 0.f; +F32 LLVOAvatar::sGreyUpdateTime = 0.f; +LLPointer LLVOAvatar::sCloudTexture = NULL; +std::vector LLVOAvatar::sAVsIgnoringARTLimit; +S32 LLVOAvatar::sAvatarsNearby = 0; + +//----------------------------------------------------------------------------- +// Helper functions +//----------------------------------------------------------------------------- +static F32 calc_bouncy_animation(F32 x); + +//----------------------------------------------------------------------------- +// LLVOAvatar() +//----------------------------------------------------------------------------- +LLVOAvatar::LLVOAvatar(const LLUUID& id, + const LLPCode pcode, + LLViewerRegion* regionp) : + LLAvatarAppearance(&gAgentWearables), + LLViewerObject(id, pcode, regionp), + mSpecialRenderMode(0), + mAttachmentSurfaceArea(0.f), + mAttachmentVisibleTriangleCount(0), + mAttachmentEstTriangleCount(0.f), + mReportedVisualComplexity(VISUAL_COMPLEXITY_UNKNOWN), + mTurning(false), + mLastSkeletonSerialNum( 0 ), + mIsSitting(false), + mTimeVisible(), + mTyping(false), + mMeshValid(false), + mVisible(false), + mLastImpostorUpdateFrameTime(0.f), + mLastImpostorUpdateReason(0), + mWindFreq(0.f), + mRipplePhase( 0.f ), + mBelowWater(false), + mLastAppearanceBlendTime(0.f), + mAppearanceAnimating(false), + mNameIsSet(false), + mTitle(), + mNameAway(false), + mNameDoNotDisturb(false), + mNameMute(false), + mNameAppearance(false), + mNameFriend(false), + mNameAlpha(0.f), + mRenderGroupTitles(sRenderGroupTitles), + mNameCloud(false), + mFirstTEMessageReceived( false ), + mFirstAppearanceMessageReceived( false ), + mCulled( false ), + mVisibilityRank(0), + mNeedsSkin(false), + mLastSkinTime(0.f), + mUpdatePeriod(1), + mOverallAppearance(AOA_INVISIBLE), + mVisualComplexityStale(true), + mVisuallyMuteSetting(AV_RENDER_NORMALLY), + mMutedAVColor(LLColor4::white /* used for "uninitialize" */), + mFirstFullyVisible(true), + mFirstDecloudTime(-1.f), + mFullyLoaded(false), + mPreviousFullyLoaded(false), + mFullyLoadedInitialized(false), + mLastCloudAttachmentCount(0), + mVisualComplexity(VISUAL_COMPLEXITY_UNKNOWN), + mLoadedCallbacksPaused(false), + mLoadedCallbackTextures(0), + mRenderUnloadedAvatar(LLCachedControl(gSavedSettings, "RenderUnloadedAvatar", false)), + mLastRezzedStatus(-1), + mIsEditingAppearance(false), + mUseLocalAppearance(false), + mLastUpdateRequestCOFVersion(-1), + mLastUpdateReceivedCOFVersion(-1), + mCachedMuteListUpdateTime(0), + mCachedInMuteList(false), + mIsControlAvatar(false), + mIsUIAvatar(false), + mEnableDefaultMotions(true) +{ + LL_DEBUGS("AvatarRender") << "LLVOAvatar Constructor (0x" << this << ") id:" << mID << LL_ENDL; + + //VTResume(); // VTune + setHoverOffset(LLVector3(0.0, 0.0, 0.0)); + + // mVoiceVisualizer is created by the hud effects manager and uses the HUD Effects pipeline + const bool needsSendToSim = false; // currently, this HUD effect doesn't need to pack and unpack data to do its job + mVoiceVisualizer = ( LLVoiceVisualizer *)LLHUDManager::getInstance()->createViewerEffect( LLHUDObject::LL_HUD_EFFECT_VOICE_VISUALIZER, needsSendToSim ); + + LL_DEBUGS("Avatar","Message") << "LLVOAvatar Constructor (0x" << this << ") id:" << mID << LL_ENDL; + mPelvisp = NULL; + + mDirtyMesh = 2; // Dirty geometry, need to regenerate. + mMeshTexturesDirty = false; + mHeadp = NULL; + + + // set up animation variables + mSpeed = 0.f; + setAnimationData("Speed", &mSpeed); + + mNeedsImpostorUpdate = true; + mLastImpostorUpdateReason = 0; + mNeedsAnimUpdate = true; + + mNeedsExtentUpdate = true; + + mImpostorDistance = 0; + mImpostorPixelArea = 0; + + setNumTEs(TEX_NUM_INDICES); + + mbCanSelect = true; + + mSignaledAnimations.clear(); + mPlayingAnimations.clear(); + + mWasOnGroundLeft = false; + mWasOnGroundRight = false; + + mTimeLast = 0.0f; + mSpeedAccum = 0.0f; + + mRippleTimeLast = 0.f; + + mInAir = false; + + mStepOnLand = true; + mStepMaterial = 0; + + mLipSyncActive = false; + mOohMorph = NULL; + mAahMorph = NULL; + + mCurrentGesticulationLevel = 0; + + mFirstAppearanceMessageTimer.reset(); + mRuthTimer.reset(); + mRuthDebugTimer.reset(); + mDebugExistenceTimer.reset(); + mLastAppearanceMessageTimer.reset(); + + if(LLSceneMonitor::getInstance()->isEnabled()) + { + LLSceneMonitor::getInstance()->freezeAvatar((LLCharacter*)this); + } + + mVisuallyMuteSetting = LLVOAvatar::VisualMuteSettings(LLRenderMuteList::getInstance()->getSavedVisualMuteSetting(getID())); +} + +std::string LLVOAvatar::avString() const +{ + if (isControlAvatar()) + { + return " " + getFullname() + " "; + } + else + { + std::string viz_string = LLVOAvatar::rezStatusToString(getRezzedStatus()); + return " Avatar '" + getFullname() + "' " + viz_string + " "; + } +} + +void LLVOAvatar::debugAvatarRezTime(std::string notification_name, std::string comment) +{ + if (gDisconnected) + { + // If we disconected, these values are likely to be invalid and + // avString() might crash due to a dead sAvatarDictionary + return; + } + + LL_INFOS("Avatar") << "REZTIME: [ " << (U32)mDebugExistenceTimer.getElapsedTimeF32() + << "sec ]" + << avString() + << "RuthTimer " << (U32)mRuthDebugTimer.getElapsedTimeF32() + << " Notification " << notification_name + << " : " << comment + << LL_ENDL; + + if (gSavedSettings.getBOOL("DebugAvatarRezTime")) + { + LLSD args; + args["EXISTENCE"] = llformat("%d",(U32)mDebugExistenceTimer.getElapsedTimeF32()); + args["TIME"] = llformat("%d",(U32)mRuthDebugTimer.getElapsedTimeF32()); + args["NAME"] = getFullname(); + LLNotificationsUtil::add(notification_name,args); + } +} + +//------------------------------------------------------------------------ +// LLVOAvatar::~LLVOAvatar() +//------------------------------------------------------------------------ +LLVOAvatar::~LLVOAvatar() +{ + if (!mFullyLoaded) + { + debugAvatarRezTime("AvatarRezLeftCloudNotification","left after ruth seconds as cloud"); + } + else + { + debugAvatarRezTime("AvatarRezLeftNotification","left sometime after declouding"); + } + + if(mTuned) + { + LLPerfStats::tunedAvatars--; + mTuned = false; + } + sAVsIgnoringARTLimit.erase(std::remove(sAVsIgnoringARTLimit.begin(), sAVsIgnoringARTLimit.end(), mID), sAVsIgnoringARTLimit.end()); + + + logPendingPhases(); + + LL_DEBUGS("Avatar") << "LLVOAvatar Destructor (0x" << this << ") id:" << mID << LL_ENDL; + + std::for_each(mAttachmentPoints.begin(), mAttachmentPoints.end(), DeletePairedPointer()); + mAttachmentPoints.clear(); + + mDead = true; + + mAnimationSources.clear(); + LLLoadedCallbackEntry::cleanUpCallbackList(&mCallbackTextureList) ; + + getPhases().clearPhases(); + + LL_DEBUGS() << "LLVOAvatar Destructor end" << LL_ENDL; +} + +void LLVOAvatar::markDead() +{ + if (mNameText) + { + mNameText->markDead(); + mNameText = NULL; + sNumVisibleChatBubbles--; + } + mVoiceVisualizer->markDead(); + LLLoadedCallbackEntry::cleanUpCallbackList(&mCallbackTextureList) ; + LLViewerObject::markDead(); +} + + +bool LLVOAvatar::isFullyBaked() +{ + if (mIsDummy) return true; + if (getNumTEs() == 0) return false; + + for (U32 i = 0; i < mBakedTextureDatas.size(); i++) + { + if (!isTextureDefined(mBakedTextureDatas[i].mTextureIndex) + && ((i != BAKED_SKIRT) || isWearingWearableType(LLWearableType::WT_SKIRT)) + && (i != BAKED_LEFT_ARM) && (i != BAKED_LEFT_LEG) && (i != BAKED_AUX1) && (i != BAKED_AUX2) && (i != BAKED_AUX3)) + { + return false; + } + } + return true; +} + +bool LLVOAvatar::isFullyTextured() const +{ + for (S32 i = 0; i < mMeshLOD.size(); i++) + { + LLAvatarJoint* joint = mMeshLOD[i]; + if (i==MESH_ID_SKIRT && !isWearingWearableType(LLWearableType::WT_SKIRT)) + { + continue; // don't care about skirt textures if we're not wearing one. + } + if (!joint) + { + continue; // nonexistent LOD OK. + } + avatar_joint_mesh_list_t::iterator meshIter = joint->mMeshParts.begin(); + if (meshIter != joint->mMeshParts.end()) + { + LLAvatarJointMesh *mesh = (*meshIter); + if (!mesh) + { + continue; // nonexistent mesh OK + } + if (mesh->hasGLTexture()) + { + continue; // Mesh exists and has a baked texture. + } + if (mesh->hasComposite()) + { + continue; // Mesh exists and has a composite texture. + } + // Fail + return false; + } + } + return true; +} + +bool LLVOAvatar::hasGray() const +{ + return !getIsCloud() && !isFullyTextured(); +} + +S32 LLVOAvatar::getRezzedStatus() const +{ + if (getIsCloud()) return 0; + bool textured = isFullyTextured(); + bool all_baked_loaded = allBakedTexturesCompletelyDownloaded(); + if (textured && all_baked_loaded && getAttachmentCount() == mSimAttachments.size()) return 4; + if (textured && all_baked_loaded) return 3; + if (textured) return 2; + llassert(hasGray()); + return 1; // gray +} + +void LLVOAvatar::deleteLayerSetCaches(bool clearAll) +{ + for (U32 i = 0; i < mBakedTextureDatas.size(); i++) + { + if (mBakedTextureDatas[i].mTexLayerSet) + { + // ! BACKWARDS COMPATIBILITY ! + // Can be removed after hair baking is mandatory on the grid + if ((i != BAKED_HAIR || isSelf()) && !clearAll) + { + mBakedTextureDatas[i].mTexLayerSet->deleteCaches(); + } + } + if (mBakedTextureDatas[i].mMaskTexName) + { + LLImageGL::deleteTextures(1, (GLuint*)&(mBakedTextureDatas[i].mMaskTexName)); + mBakedTextureDatas[i].mMaskTexName = 0 ; + } + } +} + +// static +bool LLVOAvatar::areAllNearbyInstancesBaked(S32& grey_avatars) +{ + bool res = true; + grey_avatars = 0; + for (std::vector::iterator iter = LLCharacter::sInstances.begin(); + iter != LLCharacter::sInstances.end(); ++iter) + { + LLVOAvatar* inst = (LLVOAvatar*) *iter; + if( inst->isDead() ) + { + continue; + } + else if( !inst->isFullyBaked() ) + { + res = false; + if (inst->mHasGrey) + { + ++grey_avatars; + } + } + } + return res; +} + +// static +void LLVOAvatar::getNearbyRezzedStats(std::vector& counts, F32& avg_cloud_time, S32& cloud_avatars) +{ + counts.clear(); + counts.resize(5); + avg_cloud_time = 0; + cloud_avatars = 0; + S32 count_avg = 0; + for (std::vector::iterator iter = LLCharacter::sInstances.begin(); + iter != LLCharacter::sInstances.end(); ++iter) + { + LLVOAvatar* inst = (LLVOAvatar*) *iter; + if (inst) + { + S32 rez_status = inst->getRezzedStatus(); + counts[rez_status]++; + F32 time = inst->getFirstDecloudTime(); + if (time >= 0) + { + avg_cloud_time+=time; + count_avg++; + } + if (!inst->isFullyLoaded() || time < 0) + { + // still renders as cloud + cloud_avatars++; + } + } + } + if (count_avg > 0) + { + avg_cloud_time /= count_avg; + } +} + +// static +std::string LLVOAvatar::rezStatusToString(S32 rez_status) +{ + if (rez_status==0) return "cloud"; + if (rez_status==1) return "gray"; + if (rez_status==2) return "downloading baked"; + if (rez_status==3) return "loading attachments"; + if (rez_status==4) return "full"; + return "unknown"; +} + +// static +void LLVOAvatar::dumpBakedStatus() +{ + LLVector3d camera_pos_global = gAgentCamera.getCameraPositionGlobal(); + + for (std::vector::iterator iter = LLCharacter::sInstances.begin(); + iter != LLCharacter::sInstances.end(); ++iter) + { + LLVOAvatar* inst = (LLVOAvatar*) *iter; + LL_INFOS() << "Avatar "; + + LLNameValue* firstname = inst->getNVPair("FirstName"); + LLNameValue* lastname = inst->getNVPair("LastName"); + + if( firstname ) + { + LL_CONT << firstname->getString(); + } + if( lastname ) + { + LL_CONT << " " << lastname->getString(); + } + + LL_CONT << " " << inst->mID; + + if( inst->isDead() ) + { + LL_CONT << " DEAD ("<< inst->getNumRefs() << " refs)"; + } + + if( inst->isSelf() ) + { + LL_CONT << " (self)"; + } + + + F64 dist_to_camera = (inst->getPositionGlobal() - camera_pos_global).length(); + LL_CONT << " " << dist_to_camera << "m "; + + LL_CONT << " " << inst->mPixelArea << " pixels"; + + if( inst->isVisible() ) + { + LL_CONT << " (visible)"; + } + else + { + LL_CONT << " (not visible)"; + } + + if( inst->isFullyBaked() ) + { + LL_CONT << " Baked"; + } + else + { + LL_CONT << " Unbaked ("; + + for (LLAvatarAppearanceDictionary::BakedTextures::const_iterator iter = LLAvatarAppearance::getDictionary()->getBakedTextures().begin(); + iter != LLAvatarAppearance::getDictionary()->getBakedTextures().end(); + ++iter) + { + const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = iter->second; + const ETextureIndex index = baked_dict->mTextureIndex; + if (!inst->isTextureDefined(index)) + { + LL_CONT << " " << (LLAvatarAppearance::getDictionary()->getTexture(index) ? LLAvatarAppearance::getDictionary()->getTexture(index)->mName : ""); + } + } + LL_CONT << " ) " << inst->getUnbakedPixelAreaRank(); + if( inst->isCulled() ) + { + LL_CONT << " culled"; + } + } + LL_CONT << LL_ENDL; + } +} + +//static +void LLVOAvatar::restoreGL() +{ + if (!isAgentAvatarValid()) return; + + gAgentAvatarp->setCompositeUpdatesEnabled(true); + for (U32 i = 0; i < gAgentAvatarp->mBakedTextureDatas.size(); i++) + { + gAgentAvatarp->invalidateComposite(gAgentAvatarp->getTexLayerSet(i)); + } + gAgentAvatarp->updateMeshTextures(); +} + +//static +void LLVOAvatar::destroyGL() +{ + deleteCachedImages(); + + resetImpostors(); +} + +//static +void LLVOAvatar::resetImpostors() +{ + for (std::vector::iterator iter = LLCharacter::sInstances.begin(); + iter != LLCharacter::sInstances.end(); ++iter) + { + LLVOAvatar* avatar = (LLVOAvatar*) *iter; + avatar->mImpostor.release(); + avatar->mNeedsImpostorUpdate = true; + avatar->mLastImpostorUpdateReason = 1; + } +} + +// static +void LLVOAvatar::deleteCachedImages(bool clearAll) +{ + if (LLViewerTexLayerSet::sHasCaches) + { + for (std::vector::iterator iter = LLCharacter::sInstances.begin(); + iter != LLCharacter::sInstances.end(); ++iter) + { + LLVOAvatar* inst = (LLVOAvatar*) *iter; + inst->deleteLayerSetCaches(clearAll); + } + LLViewerTexLayerSet::sHasCaches = false; + } + LLVOAvatarSelf::deleteScratchTextures(); + LLTexLayerStaticImageList::getInstance()->deleteCachedImages(); +} + + +//------------------------------------------------------------------------ +// static +// LLVOAvatar::initClass() +//------------------------------------------------------------------------ +void LLVOAvatar::initClass() +{ + gAnimLibrary.animStateSetString(ANIM_AGENT_BODY_NOISE,"body_noise"); + gAnimLibrary.animStateSetString(ANIM_AGENT_BREATHE_ROT,"breathe_rot"); + gAnimLibrary.animStateSetString(ANIM_AGENT_PHYSICS_MOTION,"physics_motion"); + gAnimLibrary.animStateSetString(ANIM_AGENT_EDITING,"editing"); + gAnimLibrary.animStateSetString(ANIM_AGENT_EYE,"eye"); + gAnimLibrary.animStateSetString(ANIM_AGENT_FLY_ADJUST,"fly_adjust"); + gAnimLibrary.animStateSetString(ANIM_AGENT_HAND_MOTION,"hand_motion"); + gAnimLibrary.animStateSetString(ANIM_AGENT_HEAD_ROT,"head_rot"); + gAnimLibrary.animStateSetString(ANIM_AGENT_PELVIS_FIX,"pelvis_fix"); + gAnimLibrary.animStateSetString(ANIM_AGENT_TARGET,"target"); + gAnimLibrary.animStateSetString(ANIM_AGENT_WALK_ADJUST,"walk_adjust"); + + // Where should this be set initially? + LLJoint::setDebugJointNames(gSavedSettings.getString("DebugAvatarJoints")); + + LLControlAvatar::sRegionChangedSlot = gAgent.addRegionChangedCallback(&LLControlAvatar::onRegionChanged); + + sCloudTexture = LLViewerTextureManager::getFetchedTextureFromFile("cloud-particle.j2c"); +} + + +void LLVOAvatar::cleanupClass() +{ +} + +// virtual +void LLVOAvatar::initInstance() +{ + //------------------------------------------------------------------------- + // register motions + //------------------------------------------------------------------------- + if (LLCharacter::sInstances.size() == 1) + { + registerMotion( ANIM_AGENT_DO_NOT_DISTURB, LLNullMotion::create ); + registerMotion( ANIM_AGENT_CROUCH, LLKeyframeStandMotion::create ); + registerMotion( ANIM_AGENT_CROUCHWALK, LLKeyframeWalkMotion::create ); + registerMotion( ANIM_AGENT_EXPRESS_AFRAID, LLEmote::create ); + registerMotion( ANIM_AGENT_EXPRESS_ANGER, LLEmote::create ); + registerMotion( ANIM_AGENT_EXPRESS_BORED, LLEmote::create ); + registerMotion( ANIM_AGENT_EXPRESS_CRY, LLEmote::create ); + registerMotion( ANIM_AGENT_EXPRESS_DISDAIN, LLEmote::create ); + registerMotion( ANIM_AGENT_EXPRESS_EMBARRASSED, LLEmote::create ); + registerMotion( ANIM_AGENT_EXPRESS_FROWN, LLEmote::create ); + registerMotion( ANIM_AGENT_EXPRESS_KISS, LLEmote::create ); + registerMotion( ANIM_AGENT_EXPRESS_LAUGH, LLEmote::create ); + registerMotion( ANIM_AGENT_EXPRESS_OPEN_MOUTH, LLEmote::create ); + registerMotion( ANIM_AGENT_EXPRESS_REPULSED, LLEmote::create ); + registerMotion( ANIM_AGENT_EXPRESS_SAD, LLEmote::create ); + registerMotion( ANIM_AGENT_EXPRESS_SHRUG, LLEmote::create ); + registerMotion( ANIM_AGENT_EXPRESS_SMILE, LLEmote::create ); + registerMotion( ANIM_AGENT_EXPRESS_SURPRISE, LLEmote::create ); + registerMotion( ANIM_AGENT_EXPRESS_TONGUE_OUT, LLEmote::create ); + registerMotion( ANIM_AGENT_EXPRESS_TOOTHSMILE, LLEmote::create ); + registerMotion( ANIM_AGENT_EXPRESS_WINK, LLEmote::create ); + registerMotion( ANIM_AGENT_EXPRESS_WORRY, LLEmote::create ); + registerMotion( ANIM_AGENT_FEMALE_RUN_NEW, LLKeyframeWalkMotion::create ); + registerMotion( ANIM_AGENT_FEMALE_WALK, LLKeyframeWalkMotion::create ); + registerMotion( ANIM_AGENT_FEMALE_WALK_NEW, LLKeyframeWalkMotion::create ); + registerMotion( ANIM_AGENT_RUN, LLKeyframeWalkMotion::create ); + registerMotion( ANIM_AGENT_RUN_NEW, LLKeyframeWalkMotion::create ); + registerMotion( ANIM_AGENT_STAND, LLKeyframeStandMotion::create ); + registerMotion( ANIM_AGENT_STAND_1, LLKeyframeStandMotion::create ); + registerMotion( ANIM_AGENT_STAND_2, LLKeyframeStandMotion::create ); + registerMotion( ANIM_AGENT_STAND_3, LLKeyframeStandMotion::create ); + registerMotion( ANIM_AGENT_STAND_4, LLKeyframeStandMotion::create ); + registerMotion( ANIM_AGENT_STANDUP, LLKeyframeFallMotion::create ); + registerMotion( ANIM_AGENT_TURNLEFT, LLKeyframeWalkMotion::create ); + registerMotion( ANIM_AGENT_TURNRIGHT, LLKeyframeWalkMotion::create ); + registerMotion( ANIM_AGENT_WALK, LLKeyframeWalkMotion::create ); + registerMotion( ANIM_AGENT_WALK_NEW, LLKeyframeWalkMotion::create ); + + // motions without a start/stop bit + registerMotion( ANIM_AGENT_BODY_NOISE, LLBodyNoiseMotion::create ); + registerMotion( ANIM_AGENT_BREATHE_ROT, LLBreatheMotionRot::create ); + registerMotion( ANIM_AGENT_PHYSICS_MOTION, LLPhysicsMotionController::create ); + registerMotion( ANIM_AGENT_EDITING, LLEditingMotion::create ); + registerMotion( ANIM_AGENT_EYE, LLEyeMotion::create ); + registerMotion( ANIM_AGENT_FEMALE_WALK, LLKeyframeWalkMotion::create ); + registerMotion( ANIM_AGENT_FLY_ADJUST, LLFlyAdjustMotion::create ); + registerMotion( ANIM_AGENT_HAND_MOTION, LLHandMotion::create ); + registerMotion( ANIM_AGENT_HEAD_ROT, LLHeadRotMotion::create ); + registerMotion( ANIM_AGENT_PELVIS_FIX, LLPelvisFixMotion::create ); + registerMotion( ANIM_AGENT_SIT_FEMALE, LLKeyframeMotion::create ); + registerMotion( ANIM_AGENT_TARGET, LLTargetingMotion::create ); + registerMotion( ANIM_AGENT_WALK_ADJUST, LLWalkAdjustMotion::create ); + } + + LLAvatarAppearance::initInstance(); + + // preload specific motions here + createMotion( ANIM_AGENT_CUSTOMIZE); + createMotion( ANIM_AGENT_CUSTOMIZE_DONE); + + //VTPause(); // VTune + + mVoiceVisualizer->setVoiceEnabled( LLVoiceClient::getInstance()->getVoiceEnabled( mID ) ); + + mInitFlags |= 1<<1; +} + +// virtual +LLAvatarJoint* LLVOAvatar::createAvatarJoint() +{ + return new LLViewerJoint(); +} + +// virtual +LLAvatarJoint* LLVOAvatar::createAvatarJoint(S32 joint_num) +{ + return new LLViewerJoint(joint_num); +} + +// virtual +LLAvatarJointMesh* LLVOAvatar::createAvatarJointMesh() +{ + return new LLViewerJointMesh(); +} + +// virtual +LLTexLayerSet* LLVOAvatar::createTexLayerSet() +{ + return new LLViewerTexLayerSet(this); +} + +const LLVector3 LLVOAvatar::getRenderPosition() const +{ + + if (mDrawable.isNull() || mDrawable->getGeneration() < 0) + { + return getPositionAgent(); + } + else if (isRoot()) + { + F32 fixup; + if ( hasPelvisFixup( fixup) ) + { + //Apply a pelvis fixup (as defined by the avs skin) + LLVector3 pos = mDrawable->getPositionAgent(); + pos[VZ] += fixup; + return pos; + } + else + { + return mDrawable->getPositionAgent(); + } + } + else + { + return getPosition() * mDrawable->getParent()->getRenderMatrix(); + } +} + +void LLVOAvatar::updateDrawable(bool force_damped) +{ + clearChanged(SHIFTED); +} + +void LLVOAvatar::onShift(const LLVector4a& shift_vector) +{ + const LLVector3& shift = reinterpret_cast(shift_vector); + mLastAnimExtents[0] += shift; + mLastAnimExtents[1] += shift; +} + +void LLVOAvatar::updateSpatialExtents(LLVector4a& newMin, LLVector4a &newMax) +{ + if (mDrawable.isNull()) + { + return; + } + + if (mNeedsExtentUpdate) + { + calculateSpatialExtents(newMin,newMax); + mLastAnimExtents[0].set(newMin.getF32ptr()); + mLastAnimExtents[1].set(newMax.getF32ptr()); + mLastAnimBasePos = mPelvisp->getWorldPosition(); + mNeedsExtentUpdate = false; + } + else + { + LLVector3 new_base_pos = mPelvisp->getWorldPosition(); + LLVector3 shift = new_base_pos-mLastAnimBasePos; + mLastAnimExtents[0] += shift; + mLastAnimExtents[1] += shift; + mLastAnimBasePos = new_base_pos; + + } + + if (isImpostor() && !needsImpostorUpdate()) + { + LLVector3 delta = getRenderPosition() - + ((LLVector3(mDrawable->getPositionGroup().getF32ptr())-mImpostorOffset)); + + newMin.load3( (mLastAnimExtents[0] + delta).mV); + newMax.load3( (mLastAnimExtents[1] + delta).mV); + } + else + { + newMin.load3(mLastAnimExtents[0].mV); + newMax.load3(mLastAnimExtents[1].mV); + LLVector4a pos_group; + pos_group.setAdd(newMin,newMax); + pos_group.mul(0.5f); + mImpostorOffset = LLVector3(pos_group.getF32ptr())-getRenderPosition(); + mDrawable->setPositionGroup(pos_group); + } +} + + +void LLVOAvatar::calculateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + + const S32 BOX_DETAIL_DEFAULT = 3; + S32 box_detail = BOX_DETAIL_DEFAULT; + if (getOverallAppearance() != AOA_NORMAL) + { + if (isControlAvatar()) + { + // Animated objects don't show system avatar but do need to include rigged meshes in their bounding box. + box_detail = 3; + } + else + { + // Jellydolled avatars ignore attachments, etc, use only system avatar. + box_detail = 1; + } + } + + // FIXME the update_min_max function used below assumes there is a + // known starting point, but in general there isn't. Ideally the + // box update logic should be modified to handle the no-point-yet + // case. For most models, starting with the pelvis is safe though. + LLVector3 zero_pos; + LLVector4a pos; + if (dist_vec(zero_pos, mPelvisp->getWorldPosition())<0.001) + { + // Don't use pelvis until av initialized + pos.load3(getRenderPosition().mV); + } + else + { + pos.load3(mPelvisp->getWorldPosition().mV); + } + newMin = pos; + newMax = pos; + + if (box_detail>=1 && !isControlAvatar()) + { + //stretch bounding box by joint positions. Doing this for + //control avs, where the polymeshes aren't maintained or + //displayed, can give inaccurate boxes due to joints stuck at (0,0,0). + for (polymesh_map_t::iterator i = mPolyMeshes.begin(); i != mPolyMeshes.end(); ++i) + { + LLPolyMesh* mesh = i->second; + for (S32 joint_num = 0; joint_num < mesh->mJointRenderData.size(); joint_num++) + { + LLVector4a trans; + trans.load3( mesh->mJointRenderData[joint_num]->mWorldMatrix->getTranslation().mV); + update_min_max(newMin, newMax, trans); + } + } + } + + // Pad bounding box for starting joint, plus polymesh if + // applicable. Subsequent calcs should be accurate enough to not + // need padding. + LLVector4a padding(0.25); + newMin.sub(padding); + newMax.add(padding); + + + //stretch bounding box by static attachments + if (box_detail >= 2) + { + float max_attachment_span = get_default_max_prim_scale() * 5.0f; + + for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + + if (attachment->getValid()) + { + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + // Don't we need to look at children of attached_object as well? + const LLViewerObject* attached_object = attachment_iter->get(); + if (attached_object && !attached_object->isHUDAttachment()) + { + const LLVOVolume *vol = dynamic_cast(attached_object); + if (vol && vol->isAnimatedObject()) + { + // Animated objects already have a bounding box in their control av, use that. + // Could lag by a frame if there's no guarantee on order of processing for avatars. + LLControlAvatar *cav = vol->getControlAvatar(); + if (cav) + { + LLVector4a cav_min; + cav_min.load3(cav->mLastAnimExtents[0].mV); + LLVector4a cav_max; + cav_max.load3(cav->mLastAnimExtents[1].mV); + update_min_max(newMin,newMax,cav_min); + update_min_max(newMin,newMax,cav_max); + continue; + } + } + if (vol && vol->isRiggedMeshFast()) + { + continue; + } + LLDrawable* drawable = attached_object->mDrawable; + if (drawable && !drawable->isState(LLDrawable::RIGGED | LLDrawable::RIGGED_CHILD)) // <-- don't extend bounding box if any rigged objects are present + { + LLSpatialBridge* bridge = drawable->getSpatialBridge(); + if (bridge) + { + const LLVector4a* ext = bridge->getSpatialExtents(); + LLVector4a distance; + distance.setSub(ext[1], ext[0]); + LLVector4a max_span(max_attachment_span); + + S32 lt = distance.lessThan(max_span).getGatheredBits() & 0x7; + + // Only add the prim to spatial extents calculations if it isn't a megaprim. + // max_attachment_span calculated at the start of the function + // (currently 5 times our max prim size) + if (lt == 0x7) + { + update_min_max(newMin,newMax,ext[0]); + update_min_max(newMin,newMax,ext[1]); + } + } + } + } + } + } + } + } + + // Stretch bounding box by rigged mesh joint boxes + if (box_detail>=3) + { + updateRiggingInfo(); + for (S32 joint_num = 0; joint_num < LL_CHARACTER_MAX_ANIMATED_JOINTS; joint_num++) + { + LLJoint *joint = getJoint(joint_num); + LLJointRiggingInfo *rig_info = NULL; + if (joint_num < mJointRiggingInfoTab.size()) + { + rig_info = &mJointRiggingInfoTab[joint_num]; + } + + if (joint && rig_info && rig_info->isRiggedTo()) + { + LLViewerJointAttachment *as_joint_attach = dynamic_cast(joint); + if (as_joint_attach && as_joint_attach->getIsHUDAttachment()) + { + // Ignore bounding box of HUD joints + continue; + } + LLMatrix4a mat; + LLVector4a new_extents[2]; + mat.loadu(joint->getWorldMatrix()); + matMulBoundBox(mat, rig_info->getRiggedExtents(), new_extents); + update_min_max(newMin, newMax, new_extents[0]); + update_min_max(newMin, newMax, new_extents[1]); + //if (isSelf()) + //{ + // LL_INFOS() << joint->getName() << " extents " << new_extents[0] << "," << new_extents[1] << LL_ENDL; + // LL_INFOS() << joint->getName() << " av box is " << newMin << "," << newMax << LL_ENDL; + //} + } + } + } + + // Update pixel area + LLVector4a center, size; + center.setAdd(newMin, newMax); + center.mul(0.5f); + + size.setSub(newMax,newMin); + size.mul(0.5f); + + mPixelArea = LLPipeline::calcPixelArea(center, size, *LLViewerCamera::getInstance()); +} + +void render_sphere_and_line(const LLVector3& begin_pos, const LLVector3& end_pos, F32 sphere_scale, const LLVector3& occ_color, const LLVector3& visible_color) +{ + // Unoccluded bone portions + LLGLDepthTest normal_depth(GL_TRUE); + + // Draw line segment for unoccluded joint + gGL.diffuseColor3f(visible_color[0], visible_color[1], visible_color[2]); + + gGL.begin(LLRender::LINES); + gGL.vertex3fv(begin_pos.mV); + gGL.vertex3fv(end_pos.mV); + gGL.end(); + + + // Draw sphere representing joint pos + gGL.pushMatrix(); + gGL.scalef(sphere_scale, sphere_scale, sphere_scale); + gSphere.renderGGL(); + gGL.popMatrix(); + + LLGLDepthTest depth_under(GL_TRUE, GL_FALSE, GL_GREATER); + + // Occluded bone portions + gGL.diffuseColor3f(occ_color[0], occ_color[1], occ_color[2]); + + gGL.begin(LLRender::LINES); + gGL.vertex3fv(begin_pos.mV); + gGL.vertex3fv(end_pos.mV); + gGL.end(); + + // Draw sphere representing joint pos + gGL.pushMatrix(); + gGL.scalef(sphere_scale, sphere_scale, sphere_scale); + gSphere.renderGGL(); + gGL.popMatrix(); +} + +//----------------------------------------------------------------------------- +// renderCollisionVolumes() +//----------------------------------------------------------------------------- +void LLVOAvatar::renderCollisionVolumes() +{ + std::ostringstream ostr; + + for (S32 i = 0; i < mNumCollisionVolumes; i++) + { + ostr << mCollisionVolumes[i].getName() << ", "; + + LLAvatarJointCollisionVolume& collision_volume = mCollisionVolumes[i]; + + collision_volume.updateWorldMatrix(); + + gGL.pushMatrix(); + gGL.multMatrix( &collision_volume.getXform()->getWorldMatrix().mMatrix[0][0] ); + + LLVector3 begin_pos(0,0,0); + LLVector3 end_pos(collision_volume.getEnd()); + static F32 sphere_scale = 1.0f; + static F32 center_dot_scale = 0.05f; + + static LLVector3 BLUE(0.0f, 0.0f, 1.0f); + static LLVector3 PASTEL_BLUE(0.5f, 0.5f, 1.0f); + static LLVector3 RED(1.0f, 0.0f, 0.0f); + static LLVector3 PASTEL_RED(1.0f, 0.5f, 0.5f); + static LLVector3 WHITE(1.0f, 1.0f, 1.0f); + + + LLVector3 cv_color_occluded; + LLVector3 cv_color_visible; + LLVector3 dot_color_occluded(WHITE); + LLVector3 dot_color_visible(WHITE); + if (isControlAvatar()) + { + cv_color_occluded = RED; + cv_color_visible = PASTEL_RED; + } + else + { + cv_color_occluded = BLUE; + cv_color_visible = PASTEL_BLUE; + } + render_sphere_and_line(begin_pos, end_pos, sphere_scale, cv_color_occluded, cv_color_visible); + render_sphere_and_line(begin_pos, end_pos, center_dot_scale, dot_color_occluded, dot_color_visible); + + gGL.popMatrix(); + } + + + if (mNameText.notNull()) + { + LLVector4a unused; + + mNameText->lineSegmentIntersect(unused, unused, unused, true); + } +} + +void LLVOAvatar::renderBones(const std::string &selected_joint) +{ + LLGLEnable blend(GL_BLEND); + + avatar_joint_list_t::iterator iter = mSkeleton.begin(); + avatar_joint_list_t::iterator end = mSkeleton.end(); + + // For selected joints + static LLVector3 SELECTED_COLOR_OCCLUDED(1.0f, 1.0f, 0.0f); + static LLVector3 SELECTED_COLOR_VISIBLE(0.5f, 0.5f, 0.5f); + // For bones with position overrides defined + static LLVector3 OVERRIDE_COLOR_OCCLUDED(1.0f, 0.0f, 0.0f); + static LLVector3 OVERRIDE_COLOR_VISIBLE(0.5f, 0.5f, 0.5f); + // For bones which are rigged to by at least one attachment + static LLVector3 RIGGED_COLOR_OCCLUDED(0.0f, 1.0f, 1.0f); + static LLVector3 RIGGED_COLOR_VISIBLE(0.5f, 0.5f, 0.5f); + // For bones not otherwise colored + static LLVector3 OTHER_COLOR_OCCLUDED(0.0f, 1.0f, 0.0f); + static LLVector3 OTHER_COLOR_VISIBLE(0.5f, 0.5f, 0.5f); + + static F32 SPHERE_SCALEF = 0.001f; + + for (; iter != end; ++iter) + { + LLJoint* jointp = *iter; + if (!jointp) + { + continue; + } + + jointp->updateWorldMatrix(); + + LLVector3 occ_color, visible_color; + + LLVector3 pos; + LLUUID mesh_id; + F32 sphere_scale = SPHERE_SCALEF; + + // We are in render, so it is preferable to implement selection + // in a different way, but since this is for debug/preview, this + // is low priority + if (jointp->getName() == selected_joint) + { + sphere_scale *= 16; + occ_color = SELECTED_COLOR_OCCLUDED; + visible_color = SELECTED_COLOR_VISIBLE; + } + else if (jointp->hasAttachmentPosOverride(pos,mesh_id)) + { + occ_color = OVERRIDE_COLOR_OCCLUDED; + visible_color = OVERRIDE_COLOR_VISIBLE; + } + else + { + if (jointIsRiggedTo(jointp)) + { + occ_color = RIGGED_COLOR_OCCLUDED; + visible_color = RIGGED_COLOR_VISIBLE; + } + else + { + occ_color = OTHER_COLOR_OCCLUDED; + visible_color = OTHER_COLOR_VISIBLE; + } + } + LLVector3 begin_pos(0,0,0); + LLVector3 end_pos(jointp->getEnd()); + + + gGL.pushMatrix(); + gGL.multMatrix( &jointp->getXform()->getWorldMatrix().mMatrix[0][0] ); + + render_sphere_and_line(begin_pos, end_pos, sphere_scale, occ_color, visible_color); + + gGL.popMatrix(); + } +} + + +void LLVOAvatar::renderJoints() +{ + std::ostringstream ostr; + std::ostringstream nullstr; + + for (joint_map_t::iterator iter = mJointMap.begin(); iter != mJointMap.end(); ++iter) + { + LLJoint* jointp = iter->second; + if (!jointp) + { + nullstr << iter->first << " is NULL" << std::endl; + continue; + } + + ostr << jointp->getName() << ", "; + + jointp->updateWorldMatrix(); + + gGL.pushMatrix(); + gGL.multMatrix( &jointp->getXform()->getWorldMatrix().mMatrix[0][0] ); + + gGL.diffuseColor3f( 1.f, 0.f, 1.f ); + + gGL.begin(LLRender::LINES); + + LLVector3 v[] = + { + LLVector3(1,0,0), + LLVector3(-1,0,0), + LLVector3(0,1,0), + LLVector3(0,-1,0), + + LLVector3(0,0,-1), + LLVector3(0,0,1), + }; + + //sides + gGL.vertex3fv(v[0].mV); + gGL.vertex3fv(v[2].mV); + + gGL.vertex3fv(v[0].mV); + gGL.vertex3fv(v[3].mV); + + gGL.vertex3fv(v[1].mV); + gGL.vertex3fv(v[2].mV); + + gGL.vertex3fv(v[1].mV); + gGL.vertex3fv(v[3].mV); + + + //top + gGL.vertex3fv(v[0].mV); + gGL.vertex3fv(v[4].mV); + + gGL.vertex3fv(v[1].mV); + gGL.vertex3fv(v[4].mV); + + gGL.vertex3fv(v[2].mV); + gGL.vertex3fv(v[4].mV); + + gGL.vertex3fv(v[3].mV); + gGL.vertex3fv(v[4].mV); + + + //bottom + gGL.vertex3fv(v[0].mV); + gGL.vertex3fv(v[5].mV); + + gGL.vertex3fv(v[1].mV); + gGL.vertex3fv(v[5].mV); + + gGL.vertex3fv(v[2].mV); + gGL.vertex3fv(v[5].mV); + + gGL.vertex3fv(v[3].mV); + gGL.vertex3fv(v[5].mV); + + gGL.end(); + + gGL.popMatrix(); + } + + mDebugText.clear(); + addDebugText(ostr.str()); + addDebugText(nullstr.str()); +} + +bool LLVOAvatar::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, + S32 face, + bool pick_transparent, + bool pick_rigged, + bool pick_unselectable, + S32* face_hit, + LLVector4a* intersection, + LLVector2* tex_coord, + LLVector4a* normal, + LLVector4a* tangent) +{ + if ((isSelf() && !gAgent.needsRenderAvatar()) || !LLPipeline::sPickAvatar) + { + return false; + } + + if (isControlAvatar()) + { + return false; + } + + if (lineSegmentBoundingBox(start, end)) + { + for (S32 i = 0; i < mNumCollisionVolumes; ++i) + { + mCollisionVolumes[i].updateWorldMatrix(); + + glh::matrix4f mat((F32*) mCollisionVolumes[i].getXform()->getWorldMatrix().mMatrix); + glh::matrix4f inverse = mat.inverse(); + glh::matrix4f norm_mat = inverse.transpose(); + + glh::vec3f p1(start.getF32ptr()); + glh::vec3f p2(end.getF32ptr()); + + inverse.mult_matrix_vec(p1); + inverse.mult_matrix_vec(p2); + + LLVector3 position; + LLVector3 norm; + + if (linesegment_sphere(LLVector3(p1.v), LLVector3(p2.v), LLVector3(0,0,0), 1.f, position, norm)) + { + glh::vec3f res_pos(position.mV); + mat.mult_matrix_vec(res_pos); + + norm.normalize(); + glh::vec3f res_norm(norm.mV); + norm_mat.mult_matrix_dir(res_norm); + + if (intersection) + { + intersection->load3(res_pos.v); + } + + if (normal) + { + normal->load3(res_norm.v); + } + + return true; + } + } + + if (isSelf()) + { + for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + LLViewerObject* attached_object = attachment_iter->get(); + + if (attached_object && !attached_object->isDead() && attachment->getValid()) + { + LLDrawable* drawable = attached_object->mDrawable; + if (drawable->isState(LLDrawable::RIGGED)) + { //regenerate octree for rigged attachment + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_RIGGED); + } + } + } + } + } + } + + + + LLVector4a position; + if (mNameText.notNull() && mNameText->lineSegmentIntersect(start, end, position)) + { + if (intersection) + { + *intersection = position; + } + + return true; + } + + return false; +} + +// virtual +LLViewerObject* LLVOAvatar::lineSegmentIntersectRiggedAttachments(const LLVector4a& start, const LLVector4a& end, + S32 face, + bool pick_transparent, + bool pick_rigged, + bool pick_unselectable, + S32* face_hit, + LLVector4a* intersection, + LLVector2* tex_coord, + LLVector4a* normal, + LLVector4a* tangent) +{ + if (isSelf() && !gAgent.needsRenderAvatar()) + { + return NULL; + } + + LLViewerObject* hit = NULL; + + if (lineSegmentBoundingBox(start, end)) + { + LLVector4a local_end = end; + LLVector4a local_intersection; + + for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + LLViewerObject* attached_object = attachment_iter->get(); + + if (attached_object->lineSegmentIntersect(start, local_end, face, pick_transparent, pick_rigged, pick_unselectable, face_hit, &local_intersection, tex_coord, normal, tangent)) + { + local_end = local_intersection; + if (intersection) + { + *intersection = local_intersection; + } + + hit = attached_object; + } + } + } + } + + return hit; +} + + +LLVOAvatar* LLVOAvatar::asAvatar() +{ + return this; +} + +//----------------------------------------------------------------------------- +// LLVOAvatar::startDefaultMotions() +//----------------------------------------------------------------------------- +void LLVOAvatar::startDefaultMotions() +{ + //------------------------------------------------------------------------- + // start default motions + //------------------------------------------------------------------------- + startMotion( ANIM_AGENT_HEAD_ROT ); + startMotion( ANIM_AGENT_EYE ); + startMotion( ANIM_AGENT_BODY_NOISE ); + startMotion( ANIM_AGENT_BREATHE_ROT ); + startMotion( ANIM_AGENT_PHYSICS_MOTION ); + startMotion( ANIM_AGENT_HAND_MOTION ); + startMotion( ANIM_AGENT_PELVIS_FIX ); + + //------------------------------------------------------------------------- + // restart any currently active motions + //------------------------------------------------------------------------- + processAnimationStateChanges(); +} + +//----------------------------------------------------------------------------- +// LLVOAvatar::buildCharacter() +// Deferred initialization and rebuild of the avatar. +//----------------------------------------------------------------------------- +// virtual +void LLVOAvatar::buildCharacter() +{ + LLAvatarAppearance::buildCharacter(); + + // Not done building yet; more to do. + mIsBuilt = false; + + //------------------------------------------------------------------------- + // set head offset from pelvis + //------------------------------------------------------------------------- + updateHeadOffset(); + + //------------------------------------------------------------------------- + // initialize lip sync morph pointers + //------------------------------------------------------------------------- + mOohMorph = getVisualParam( "Lipsync_Ooh" ); + mAahMorph = getVisualParam( "Lipsync_Aah" ); + + // If we don't have the Ooh morph, use the Kiss morph + if (!mOohMorph) + { + LL_WARNS() << "Missing 'Ooh' morph for lipsync, using fallback." << LL_ENDL; + mOohMorph = getVisualParam( "Express_Kiss" ); + } + + // If we don't have the Aah morph, use the Open Mouth morph + if (!mAahMorph) + { + LL_WARNS() << "Missing 'Aah' morph for lipsync, using fallback." << LL_ENDL; + mAahMorph = getVisualParam( "Express_Open_Mouth" ); + } + + // Currently disabled for control avatars (animated objects), enabled for all others. + if (mEnableDefaultMotions) + { + startDefaultMotions(); + } + + //------------------------------------------------------------------------- + // restart any currently active motions + //------------------------------------------------------------------------- + processAnimationStateChanges(); + + mIsBuilt = true; + stop_glerror(); + + mMeshValid = true; +} + +//----------------------------------------------------------------------------- +// resetVisualParams() +//----------------------------------------------------------------------------- +void LLVOAvatar::resetVisualParams() +{ + // Skeletal params + { + LLAvatarXmlInfo::skeletal_distortion_info_list_t::iterator iter; + for (iter = sAvatarXmlInfo->mSkeletalDistortionInfoList.begin(); + iter != sAvatarXmlInfo->mSkeletalDistortionInfoList.end(); + ++iter) + { + LLPolySkeletalDistortionInfo *info = (LLPolySkeletalDistortionInfo*)*iter; + LLPolySkeletalDistortion *param = dynamic_cast(getVisualParam(info->getID())); + *param = LLPolySkeletalDistortion(this); + llassert(param); + if (!param->setInfo(info)) + { + llassert(false); + } + } + } + + // Driver parameters + for (LLAvatarXmlInfo::driver_info_list_t::iterator iter = sAvatarXmlInfo->mDriverInfoList.begin(); + iter != sAvatarXmlInfo->mDriverInfoList.end(); + ++iter) + { + LLDriverParamInfo *info = *iter; + LLDriverParam *param = dynamic_cast(getVisualParam(info->getID())); + LLDriverParam::entry_list_t driven_list = param->getDrivenList(); + *param = LLDriverParam(this); + llassert(param); + if (!param->setInfo(info)) + { + llassert(false); + } + param->setDrivenList(driven_list); + } +} + +void LLVOAvatar::applyDefaultParams() +{ + // These are params from avs with newly created copies of shape, + // skin, hair, eyes, plus gender set as noted. Run arche_tool.py + // to get params from some other xml appearance dump. + std::map male_params = { + {1,33}, {2,61}, {4,85}, {5,23}, {6,58}, {7,127}, {8,63}, {10,85}, {11,63}, {12,42}, {13,0}, {14,85}, {15,63}, {16,36}, {17,85}, {18,95}, {19,153}, {20,63}, {21,34}, {22,0}, {23,63}, {24,109}, {25,88}, {27,132}, {31,63}, {33,136}, {34,81}, {35,85}, {36,103}, {37,136}, {38,127}, {80,255}, {93,203}, {98,0}, {99,0}, {105,127}, {108,0}, {110,0}, {111,127}, {112,0}, {113,0}, {114,127}, {115,0}, {116,0}, {117,0}, {119,127}, {130,114}, {131,127}, {132,99}, {133,63}, {134,127}, {135,140}, {136,127}, {137,127}, {140,0}, {141,0}, {142,0}, {143,191}, {150,0}, {155,104}, {157,0}, {162,0}, {163,0}, {165,0}, {166,0}, {167,0}, {168,0}, {169,0}, {177,0}, {181,145}, {182,216}, {183,133}, {184,0}, {185,127}, {192,0}, {193,127}, {196,170}, {198,0}, {503,0}, {505,127}, {506,127}, {507,109}, {508,85}, {513,127}, {514,127}, {515,63}, {517,85}, {518,42}, {603,100}, {604,216}, {605,214}, {606,204}, {607,204}, {608,204}, {609,51}, {616,25}, {617,89}, {619,76}, {624,204}, {625,0}, {629,127}, {637,0}, {638,0}, {646,144}, {647,85}, {649,127}, {650,132}, {652,127}, {653,85}, {654,0}, {656,127}, {659,127}, {662,127}, {663,127}, {664,127}, {665,127}, {674,59}, {675,127}, {676,85}, {678,127}, {682,127}, {683,106}, {684,47}, {685,79}, {690,127}, {692,127}, {693,204}, {700,63}, {701,0}, {702,0}, {703,0}, {704,0}, {705,127}, {706,127}, {707,0}, {708,0}, {709,0}, {710,0}, {711,127}, {712,0}, {713,159}, {714,0}, {715,0}, {750,178}, {752,127}, {753,36}, {754,85}, {755,131}, {756,127}, {757,127}, {758,127}, {759,153}, {760,95}, {762,0}, {763,140}, {764,74}, {765,27}, {769,127}, {773,127}, {775,0}, {779,214}, {780,204}, {781,198}, {785,0}, {789,0}, {795,63}, {796,30}, {799,127}, {800,226}, {801,255}, {802,198}, {803,255}, {804,255}, {805,255}, {806,255}, {807,255}, {808,255}, {812,255}, {813,255}, {814,255}, {815,204}, {816,0}, {817,255}, {818,255}, {819,255}, {820,255}, {821,255}, {822,255}, {823,255}, {824,255}, {825,255}, {826,255}, {827,255}, {828,0}, {829,255}, {830,255}, {834,255}, {835,255}, {836,255}, {840,0}, {841,127}, {842,127}, {844,255}, {848,25}, {858,100}, {859,255}, {860,255}, {861,255}, {862,255}, {863,84}, {868,0}, {869,0}, {877,0}, {879,51}, {880,132}, {921,255}, {922,255}, {923,255}, {10000,0}, {10001,0}, {10002,25}, {10003,0}, {10004,25}, {10005,23}, {10006,51}, {10007,0}, {10008,25}, {10009,23}, {10010,51}, {10011,0}, {10012,0}, {10013,25}, {10014,0}, {10015,25}, {10016,23}, {10017,51}, {10018,0}, {10019,0}, {10020,25}, {10021,0}, {10022,25}, {10023,23}, {10024,51}, {10025,0}, {10026,25}, {10027,23}, {10028,51}, {10029,0}, {10030,25}, {10031,23}, {10032,51}, {11000,1}, {11001,127} + }; + std::map female_params = { + {1,33}, {2,61}, {4,85}, {5,23}, {6,58}, {7,127}, {8,63}, {10,85}, {11,63}, {12,42}, {13,0}, {14,85}, {15,63}, {16,36}, {17,85}, {18,95}, {19,153}, {20,63}, {21,34}, {22,0}, {23,63}, {24,109}, {25,88}, {27,132}, {31,63}, {33,136}, {34,81}, {35,85}, {36,103}, {37,136}, {38,127}, {80,0}, {93,203}, {98,0}, {99,0}, {105,127}, {108,0}, {110,0}, {111,127}, {112,0}, {113,0}, {114,127}, {115,0}, {116,0}, {117,0}, {119,127}, {130,114}, {131,127}, {132,99}, {133,63}, {134,127}, {135,140}, {136,127}, {137,127}, {140,0}, {141,0}, {142,0}, {143,191}, {150,0}, {155,104}, {157,0}, {162,0}, {163,0}, {165,0}, {166,0}, {167,0}, {168,0}, {169,0}, {177,0}, {181,145}, {182,216}, {183,133}, {184,0}, {185,127}, {192,0}, {193,127}, {196,170}, {198,0}, {503,0}, {505,127}, {506,127}, {507,109}, {508,85}, {513,127}, {514,127}, {515,63}, {517,85}, {518,42}, {603,100}, {604,216}, {605,214}, {606,204}, {607,204}, {608,204}, {609,51}, {616,25}, {617,89}, {619,76}, {624,204}, {625,0}, {629,127}, {637,0}, {638,0}, {646,144}, {647,85}, {649,127}, {650,132}, {652,127}, {653,85}, {654,0}, {656,127}, {659,127}, {662,127}, {663,127}, {664,127}, {665,127}, {674,59}, {675,127}, {676,85}, {678,127}, {682,127}, {683,106}, {684,47}, {685,79}, {690,127}, {692,127}, {693,204}, {700,63}, {701,0}, {702,0}, {703,0}, {704,0}, {705,127}, {706,127}, {707,0}, {708,0}, {709,0}, {710,0}, {711,127}, {712,0}, {713,159}, {714,0}, {715,0}, {750,178}, {752,127}, {753,36}, {754,85}, {755,131}, {756,127}, {757,127}, {758,127}, {759,153}, {760,95}, {762,0}, {763,140}, {764,74}, {765,27}, {769,127}, {773,127}, {775,0}, {779,214}, {780,204}, {781,198}, {785,0}, {789,0}, {795,63}, {796,30}, {799,127}, {800,226}, {801,255}, {802,198}, {803,255}, {804,255}, {805,255}, {806,255}, {807,255}, {808,255}, {812,255}, {813,255}, {814,255}, {815,204}, {816,0}, {817,255}, {818,255}, {819,255}, {820,255}, {821,255}, {822,255}, {823,255}, {824,255}, {825,255}, {826,255}, {827,255}, {828,0}, {829,255}, {830,255}, {834,255}, {835,255}, {836,255}, {840,0}, {841,127}, {842,127}, {844,255}, {848,25}, {858,100}, {859,255}, {860,255}, {861,255}, {862,255}, {863,84}, {868,0}, {869,0}, {877,0}, {879,51}, {880,132}, {921,255}, {922,255}, {923,255}, {10000,0}, {10001,0}, {10002,25}, {10003,0}, {10004,25}, {10005,23}, {10006,51}, {10007,0}, {10008,25}, {10009,23}, {10010,51}, {10011,0}, {10012,0}, {10013,25}, {10014,0}, {10015,25}, {10016,23}, {10017,51}, {10018,0}, {10019,0}, {10020,25}, {10021,0}, {10022,25}, {10023,23}, {10024,51}, {10025,0}, {10026,25}, {10027,23}, {10028,51}, {10029,0}, {10030,25}, {10031,23}, {10032,51}, {11000,1}, {11001,127} + }; + std::map *params = NULL; + if (getSex() == SEX_MALE) + params = &male_params; + else + params = &female_params; + + for( auto it = params->begin(); it != params->end(); ++it) + { + LLVisualParam* param = getVisualParam(it->first); + if( !param ) + { + // invalid id + break; + } + + U8 value = it->second; + F32 newWeight = U8_to_F32(value, param->getMinWeight(), param->getMaxWeight()); + param->setWeight(newWeight); + } +} + +//----------------------------------------------------------------------------- +// resetSkeleton() +//----------------------------------------------------------------------------- +void LLVOAvatar::resetSkeleton(bool reset_animations) +{ + LL_DEBUGS("Avatar") << avString() << " reset starts" << LL_ENDL; + if (!isControlAvatar() && !mLastProcessedAppearance) + { + LL_WARNS() << "Can't reset avatar " << getID() << "; no appearance message has been received yet." << LL_ENDL; + return; + } + + // Save mPelvis state + //LLVector3 pelvis_pos = getJoint("mPelvis")->getPosition(); + //LLQuaternion pelvis_rot = getJoint("mPelvis")->getRotation(); + + // Clear all attachment pos and scale overrides + clearAttachmentOverrides(); + + // Note that we call buildSkeleton twice in this function. The first time is + // just to get the right scale for the collision volumes, because + // this will be used in setting the mJointScales for the + // LLPolySkeletalDistortions of which the CVs are children. + if( !buildSkeleton(sAvatarSkeletonInfo) ) + { + LL_ERRS() << "Error resetting skeleton" << LL_ENDL; + } + + // Reset some params to default state, without propagating changes downstream. + resetVisualParams(); + + // Now we have to reset the skeleton again, because its state + // got clobbered by the resetVisualParams() calls + // above. + if( !buildSkeleton(sAvatarSkeletonInfo) ) + { + LL_ERRS() << "Error resetting skeleton" << LL_ENDL; + } + + // Reset attachment points + // BuildSkeleton only does bones and CVs but we still need to reinit huds + // since huds can be animated. + bool ignore_hud_joints = !isSelf(); + initAttachmentPoints(ignore_hud_joints); + + // Fix up collision volumes + for (LLVisualParam *param = getFirstVisualParam(); + param; + param = getNextVisualParam()) + { + LLPolyMorphTarget *poly_morph = dynamic_cast(param); + if (poly_morph) + { + // This is a kludgy way to correct for the fact that the + // collision volumes have been reset out from under the + // poly morph sliders. + F32 delta_weight = poly_morph->getLastWeight() - poly_morph->getDefaultWeight(); + poly_morph->applyVolumeChanges(delta_weight); + } + } + + // Reset tweakable params to preserved state + if (getOverallAppearance() == AOA_NORMAL) + { + if (mLastProcessedAppearance) + { + bool slam_params = true; + applyParsedAppearanceMessage(*mLastProcessedAppearance, slam_params); + } + } + else + { + // Stripped down approximation of + // applyParsedAppearanceMessage, but with alternative default + // (jellydoll) params + setCompositeUpdatesEnabled( false ); + gPipeline.markGLRebuild(this); + applyDefaultParams(); + setCompositeUpdatesEnabled( true ); + updateMeshTextures(); + updateMeshVisibility(); + } + updateVisualParams(); + + // Restore attachment pos overrides + updateAttachmentOverrides(); + + // Animations + if (reset_animations) + { + if (isSelf()) + { + // This is equivalent to "Stop Animating Me". Will reset + // all animations and propagate the changes to other + // viewers. + gAgent.stopCurrentAnimations(); + } + else + { + // Local viewer-side reset for non-self avatars. + resetAnimations(); + } + } + + LL_DEBUGS("Avatar") << avString() << " reset ends" << LL_ENDL; +} + +//----------------------------------------------------------------------------- +// releaseMeshData() +//----------------------------------------------------------------------------- +void LLVOAvatar::releaseMeshData() +{ + if (sInstances.size() < AVATAR_RELEASE_THRESHOLD || isUIAvatar()) + { + return; + } + + // cleanup mesh data + for (avatar_joint_list_t::iterator iter = mMeshLOD.begin(); + iter != mMeshLOD.end(); + ++iter) + { + LLAvatarJoint* joint = (*iter); + joint->setValid(false, true); + } + + //cleanup data + if (mDrawable.notNull()) + { + LLFace* facep = mDrawable->getFace(0); + if (facep) + { + facep->setSize(0, 0); + for(S32 i = mNumInitFaces ; i < mDrawable->getNumFaces(); i++) + { + facep = mDrawable->getFace(i); + if (facep) + { + facep->setSize(0, 0); + } + } + } + } + + for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + if (!attachment->getIsHUDAttachment()) + { + attachment->setAttachmentVisibility(false); + } + } + mMeshValid = false; +} + +//----------------------------------------------------------------------------- +// restoreMeshData() +//----------------------------------------------------------------------------- +// virtual +void LLVOAvatar::restoreMeshData() +{ + llassert(!isSelf()); + if (mDrawable.isNull()) + { + return; + } + + //LL_INFOS() << "Restoring" << LL_ENDL; + mMeshValid = true; + updateJointLODs(); + + for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + if (!attachment->getIsHUDAttachment()) + { + attachment->setAttachmentVisibility(true); + } + } + + // force mesh update as LOD might not have changed to trigger this + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_GEOMETRY); +} + +//----------------------------------------------------------------------------- +// updateMeshData() +//----------------------------------------------------------------------------- +void LLVOAvatar::updateMeshData() +{ + if (mDrawable.notNull()) + { + stop_glerror(); + + S32 f_num = 0 ; + const U32 VERTEX_NUMBER_THRESHOLD = 128 ;//small number of this means each part of an avatar has its own vertex buffer. + const S32 num_parts = mMeshLOD.size(); + + // this order is determined by number of LODS + // if a mesh earlier in this list changed LODs while a later mesh doesn't, + // the later mesh's index offset will be inaccurate + for(S32 part_index = 0 ; part_index < num_parts ;) + { + S32 j = part_index ; + U32 last_v_num = 0, num_vertices = 0 ; + U32 last_i_num = 0, num_indices = 0 ; + + while(part_index < num_parts && num_vertices < VERTEX_NUMBER_THRESHOLD) + { + last_v_num = num_vertices ; + last_i_num = num_indices ; + + LLViewerJoint* part_mesh = getViewerJoint(part_index++); + if (part_mesh) + { + part_mesh->updateFaceSizes(num_vertices, num_indices, mAdjustedPixelArea); + } + } + if(num_vertices < 1)//skip empty meshes + { + continue ; + } + if(last_v_num > 0)//put the last inserted part into next vertex buffer. + { + num_vertices = last_v_num ; + num_indices = last_i_num ; + part_index-- ; + } + + LLFace* facep = NULL; + if(f_num < mDrawable->getNumFaces()) + { + facep = mDrawable->getFace(f_num); + } + else + { + facep = mDrawable->getFace(0); + if (facep) + { + facep = mDrawable->addFace(facep->getPool(), facep->getTexture()) ; + } + } + if (!facep) continue; + + // resize immediately + facep->setSize(num_vertices, num_indices); + + bool terse_update = false; + + facep->setGeomIndex(0); + facep->setIndicesIndex(0); + + LLVertexBuffer* buff = facep->getVertexBuffer(); + if(!facep->getVertexBuffer()) + { + buff = new LLVertexBuffer(LLDrawPoolAvatar::VERTEX_DATA_MASK); + if (!buff->allocateBuffer(num_vertices, num_indices)) + { + LL_WARNS() << "Failed to allocate Vertex Buffer for Mesh to " + << num_vertices << " vertices and " + << num_indices << " indices" << LL_ENDL; + // Attempt to create a dummy triangle (one vertex, 3 indices, all 0) + facep->setSize(1, 3); + buff->allocateBuffer(1, 3); + memset((U8*) buff->getMappedData(), 0, buff->getSize()); + memset((U8*) buff->getMappedIndices(), 0, buff->getIndicesSize()); + } + facep->setVertexBuffer(buff); + } + else + { + if (buff->getNumIndices() == num_indices && + buff->getNumVerts() == num_vertices) + { + terse_update = true; + } + else + { + buff = new LLVertexBuffer(buff->getTypeMask()); + if (!buff->allocateBuffer(num_vertices, num_indices)) + { + LL_WARNS() << "Failed to allocate vertex buffer for Mesh, Substituting" << LL_ENDL; + // Attempt to create a dummy triangle (one vertex, 3 indices, all 0) + facep->setSize(1, 3); + buff->allocateBuffer(1, 3); + memset((U8*) buff->getMappedData(), 0, buff->getSize()); + memset((U8*) buff->getMappedIndices(), 0, buff->getIndicesSize()); + } + } + } + + + // This is a hack! Avatars have their own pool, so we are detecting + // the case of more than one avatar in the pool (thus > 0 instead of >= 0) + if (facep->getGeomIndex() > 0) + { + LL_ERRS() << "non-zero geom index: " << facep->getGeomIndex() << " in LLVOAvatar::restoreMeshData" << LL_ENDL; + } + + if (num_vertices == buff->getNumVerts() && num_indices == buff->getNumIndices()) + { + for(S32 k = j ; k < part_index ; k++) + { + bool rigid = false; + if (k == MESH_ID_EYEBALL_LEFT || + k == MESH_ID_EYEBALL_RIGHT) + { + //eyeballs can't have terse updates since they're never rendered with + //the hardware skinning shader + rigid = true; + } + + LLViewerJoint* mesh = getViewerJoint(k); + if (mesh) + { + mesh->updateFaceData(facep, mAdjustedPixelArea, k == MESH_ID_HAIR, terse_update && !rigid); + } + } + } + + stop_glerror(); + buff->unmapBuffer(); + + if(!f_num) + { + f_num += mNumInitFaces ; + } + else + { + f_num++ ; + } + } + } +} + +//------------------------------------------------------------------------ + +//------------------------------------------------------------------------ +// LLVOAvatar::processUpdateMessage() +//------------------------------------------------------------------------ +U32 LLVOAvatar::processUpdateMessage(LLMessageSystem *mesgsys, + void **user_data, + U32 block_num, const EObjectUpdateType update_type, + LLDataPacker *dp) +{ + const bool had_no_name = !getNVPair("FirstName"); + + // Do base class updates... + U32 retval = LLViewerObject::processUpdateMessage(mesgsys, user_data, block_num, update_type, dp); + + // Print out arrival information once we have name of avatar. + const bool has_name = getNVPair("FirstName"); + if (had_no_name && has_name) + { + mDebugExistenceTimer.reset(); + debugAvatarRezTime("AvatarRezArrivedNotification", "avatar arrived"); + } + + if (retval & LLViewerObject::INVALID_UPDATE) + { + if (isSelf()) + { + //tell sim to cancel this update + gAgent.teleportViaLocation(gAgent.getPositionGlobal()); + } + } + + return retval; +} + +LLViewerFetchedTexture *LLVOAvatar::getBakedTextureImage(const U8 te, const LLUUID& uuid) +{ + LLViewerFetchedTexture *result = NULL; + + if (uuid == IMG_DEFAULT_AVATAR || + uuid == IMG_DEFAULT || + uuid == IMG_INVISIBLE) + { + // Should already exist, don't need to find it on sim or baked-texture host. + result = gTextureList.findImage(uuid, TEX_LIST_STANDARD); + } + if (!result) + { + const std::string url = getImageURL(te,uuid); + + if (url.empty()) + { + LL_WARNS() << "unable to determine URL for te " << te << " uuid " << uuid << LL_ENDL; + return NULL; + } + LL_DEBUGS("Avatar") << avString() << "get server-bake image from URL " << url << LL_ENDL; + result = LLViewerTextureManager::getFetchedTextureFromUrl( + url, FTT_SERVER_BAKE, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE, 0, 0, uuid); + if (result->isMissingAsset()) + { + result->setIsMissingAsset(false); + } + + } + return result; +} + +// virtual +S32 LLVOAvatar::setTETexture(const U8 te, const LLUUID& uuid) +{ + if (!isIndexBakedTexture((ETextureIndex)te)) + { + // Sim still sends some uuids for non-baked slots sometimes - ignore. + return LLViewerObject::setTETexture(te, LLUUID::null); + } + + LLViewerFetchedTexture *image = getBakedTextureImage(te,uuid); + llassert(image); + return setTETextureCore(te, image); +} + +//------------------------------------------------------------------------ +// LLVOAvatar::dumpAnimationState() +//------------------------------------------------------------------------ +void LLVOAvatar::dumpAnimationState() +{ + LL_INFOS() << "==============================================" << LL_ENDL; + for (LLVOAvatar::AnimIterator it = mSignaledAnimations.begin(); it != mSignaledAnimations.end(); ++it) + { + LLUUID id = it->first; + std::string playtag = ""; + if (mPlayingAnimations.find(id) != mPlayingAnimations.end()) + { + playtag = "*"; + } + LL_INFOS() << gAnimLibrary.animationName(id) << playtag << LL_ENDL; + } + for (LLVOAvatar::AnimIterator it = mPlayingAnimations.begin(); it != mPlayingAnimations.end(); ++it) + { + LLUUID id = it->first; + bool is_signaled = mSignaledAnimations.find(id) != mSignaledAnimations.end(); + if (!is_signaled) + { + LL_INFOS() << gAnimLibrary.animationName(id) << "!S" << LL_ENDL; + } + } +} + +//------------------------------------------------------------------------ +// idleUpdate() +//------------------------------------------------------------------------ +void LLVOAvatar::idleUpdate(LLAgent &agent, const F64 &time) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + + if (isDead()) + { + LL_INFOS() << "Warning! Idle on dead avatar" << LL_ENDL; + return; + } + + LLCachedControl friends_only(gSavedSettings, "RenderAvatarFriendsOnly", false); + if (friends_only() + && !isUIAvatar() + && !isControlAvatar() + && !isSelf() + && !isBuddy()) + { + if (mNameText) + { + mNameIsSet = false; + mNameText->markDead(); + mNameText = NULL; + sNumVisibleChatBubbles--; + } + deleteParticleSource(); + mVoiceVisualizer->setVoiceEnabled(false); + + return; + } + + // record time and refresh "tooSlow" status + updateTooSlow(); + + static LLCachedControl disable_all_render_types(gSavedSettings, "DisableAllRenderTypes"); + if (!(gPipeline.hasRenderType(mIsControlAvatar ? LLPipeline::RENDER_TYPE_CONTROL_AV : LLPipeline::RENDER_TYPE_AVATAR)) + && !disable_all_render_types && !isSelf()) + { + if (!mIsControlAvatar) + { + idleUpdateNameTag(idleCalcNameTagPosition(mLastRootPos)); + } + return; + } + + // Update should be happening max once per frame. + if ((mLastAnimExtents[0]==LLVector3())|| + (mLastAnimExtents[1])==LLVector3()) + { + mNeedsExtentUpdate = true; + } + else + { + const S32 upd_freq = 4; // force update every upd_freq frames. + mNeedsExtentUpdate = ((LLDrawable::getCurrentFrame()+mID.mData[0])%upd_freq==0); + } + + LLScopedContextString str("avatar_idle_update " + getFullname()); + + checkTextureLoading() ; + + // force immediate pixel area update on avatars using last frames data (before drawable or camera updates) + setPixelAreaAndAngle(gAgent); + + // force asynchronous drawable update + if(mDrawable.notNull()) + { + if (isSitting() && getParent()) + { + LLViewerObject *root_object = (LLViewerObject*)getRoot(); + LLDrawable* drawablep = root_object->mDrawable; + // if this object hasn't already been updated by another avatar... + if (drawablep) // && !drawablep->isState(LLDrawable::EARLY_MOVE)) + { + if (root_object->isSelected()) + { + gPipeline.updateMoveNormalAsync(drawablep); + } + else + { + gPipeline.updateMoveDampedAsync(drawablep); + } + } + } + else + { + gPipeline.updateMoveDampedAsync(mDrawable); + } + } + + //-------------------------------------------------------------------- + // set alpha flag depending on state + //-------------------------------------------------------------------- + + if (isSelf()) + { + LLViewerObject::idleUpdate(agent, time); + + // trigger fidget anims + if (isAnyAnimationSignaled(AGENT_STAND_ANIMS, NUM_AGENT_STAND_ANIMS)) + { + agent.fidget(); + } + } + else + { + // Should override the idleUpdate stuff and leave out the angular update part. + LLQuaternion rotation = getRotation(); + LLViewerObject::idleUpdate(agent, time); + setRotation(rotation); + } + + // attach objects that were waiting for a drawable + lazyAttach(); + + // animate the character + // store off last frame's root position to be consistent with camera position + mLastRootPos = mRoot->getWorldPosition(); + bool detailed_update = updateCharacter(agent); + + static LLUICachedControl visualizers_in_calls("ShowVoiceVisualizersInCalls", false); + bool voice_enabled = (visualizers_in_calls || LLVoiceClient::getInstance()->inProximalChannel()) && + LLVoiceClient::getInstance()->getVoiceEnabled(mID); + + LLVector3 hud_name_pos = idleCalcNameTagPosition(mLastRootPos); + + idleUpdateVoiceVisualizer(voice_enabled, hud_name_pos); + idleUpdateMisc( detailed_update ); + idleUpdateAppearanceAnimation(); + if (detailed_update) + { + idleUpdateLipSync( voice_enabled ); + idleUpdateLoadingEffect(); + idleUpdateBelowWater(); // wind effect uses this + idleUpdateWindEffect(); + } + + idleUpdateNameTag(hud_name_pos); + + // Complexity has stale mechanics, but updates still can be very rapid + // so spread avatar complexity calculations over frames to lesen load from + // rapid updates and to make sure all avatars are not calculated at once. + S32 compl_upd_freq = 20; + if (isControlAvatar()) + { + // animeshes do not (or won't) have impostors nor change outfis, + // no need for high frequency + compl_upd_freq = 100; + } + else if (mLastRezzedStatus <= 0) //cloud or init + { + compl_upd_freq = 60; + } + else if (isSelf()) + { + compl_upd_freq = 5; + } + else if (mLastRezzedStatus == 1) //'grey', not fully loaded + { + compl_upd_freq = 40; + } + else if (isInMuteList()) //cheap, buffers value from search + { + compl_upd_freq = 100; + } + + if ((LLFrameTimer::getFrameCount() + mID.mData[0]) % compl_upd_freq == 0) + { + // DEPRECATED + // replace with LLPipeline::profileAvatar? + // Avatar profile takes ~ 0.5ms while idleUpdateRenderComplexity takes ~5ms + // (both are unacceptably costly) + idleUpdateRenderComplexity(); + } + idleUpdateDebugInfo(); +} + +void LLVOAvatar::idleUpdateVoiceVisualizer(bool voice_enabled, const LLVector3 &position) +{ + bool render_visualizer = voice_enabled; + + // Don't render the user's own voice visualizer when in mouselook, or when opening the mic is disabled. + if(isSelf()) + { + static LLCachedControl voice_disable_mic(gSavedSettings, "VoiceDisableMic"); + if(gAgentCamera.cameraMouselook() || voice_disable_mic) + { + render_visualizer = false; + } + } + + mVoiceVisualizer->setVoiceEnabled(render_visualizer); + + if ( voice_enabled ) + { + //---------------------------------------------------------------- + // Only do gesture triggering for your own avatar, and only when you're in a proximal channel. + //---------------------------------------------------------------- + if( isSelf() ) + { + //---------------------------------------------------------------------------------------- + // The following takes the voice signal and uses that to trigger gesticulations. + //---------------------------------------------------------------------------------------- + int lastGesticulationLevel = mCurrentGesticulationLevel; + mCurrentGesticulationLevel = mVoiceVisualizer->getCurrentGesticulationLevel(); + + //--------------------------------------------------------------------------------------------------- + // If "current gesticulation level" changes, we catch this, and trigger the new gesture + //--------------------------------------------------------------------------------------------------- + if ( lastGesticulationLevel != mCurrentGesticulationLevel ) + { + if ( mCurrentGesticulationLevel != VOICE_GESTICULATION_LEVEL_OFF ) + { + std::string gestureString = "unInitialized"; + if ( mCurrentGesticulationLevel == 0 ) { gestureString = "/voicelevel1"; } + else if ( mCurrentGesticulationLevel == 1 ) { gestureString = "/voicelevel2"; } + else if ( mCurrentGesticulationLevel == 2 ) { gestureString = "/voicelevel3"; } + else { LL_INFOS() << "oops - CurrentGesticulationLevel can be only 0, 1, or 2" << LL_ENDL; } + + // this is the call that Karl S. created for triggering gestures from within the code. + LLGestureMgr::instance().triggerAndReviseString( gestureString ); + } + } + + } //if( isSelf() ) + + //----------------------------------------------------------------------------------------------------------------- + // If the avatar is speaking, then the voice amplitude signal is passed to the voice visualizer. + // Also, here we trigger voice visualizer start and stop speaking, so it can animate the voice symbol. + // + // Notice the calls to "gAwayTimer.reset()". This resets the timer that determines how long the avatar has been + // "away", so that the avatar doesn't lapse into away-mode (and slump over) while the user is still talking. + //----------------------------------------------------------------------------------------------------------------- + if (LLVoiceClient::getInstance()->getIsSpeaking( mID )) + { + if (!mVoiceVisualizer->getCurrentlySpeaking()) + { + mVoiceVisualizer->setStartSpeaking(); + + //printf( "gAwayTimer.reset();\n" ); + } + + mVoiceVisualizer->setSpeakingAmplitude( LLVoiceClient::getInstance()->getCurrentPower( mID ) ); + + if( isSelf() ) + { + gAgent.clearAFK(); + } + } + else + { + if ( mVoiceVisualizer->getCurrentlySpeaking() ) + { + mVoiceVisualizer->setStopSpeaking(); + + if ( mLipSyncActive ) + { + if( mOohMorph ) mOohMorph->setWeight(mOohMorph->getMinWeight()); + if( mAahMorph ) mAahMorph->setWeight(mAahMorph->getMinWeight()); + + mLipSyncActive = false; + LLCharacter::updateVisualParams(); + dirtyMesh(); + } + } + } + mVoiceVisualizer->setPositionAgent(position); + }//if ( voiceEnabled ) +} + +static void override_bbox(LLDrawable* drawable, LLVector4a* extents) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_SPATIAL; + drawable->setSpatialExtents(extents[0], extents[1]); + drawable->setPositionGroup(LLVector4a(0, 0, 0)); + drawable->movePartition(); +} + +void LLVOAvatar::idleUpdateMisc(bool detailed_update) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + if (LLVOAvatar::sJointDebug) + { + LL_INFOS() << getFullname() << ": joint touches: " << LLJoint::sNumTouches << " updates: " << LLJoint::sNumUpdates << LL_ENDL; + } + + LLJoint::sNumUpdates = 0; + LLJoint::sNumTouches = 0; + + bool visible = isVisible() || mNeedsAnimUpdate; + + // update attachments positions + if (detailed_update) + { + U32 draw_order = 0; + S32 attachment_selected = LLSelectMgr::getInstance()->getSelection()->getObjectCount() && LLSelectMgr::getInstance()->getSelection()->isAttachment(); + for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + LLViewerObject* attached_object = attachment_iter->get(); + if (!attached_object + || attached_object->isDead() + || !attachment->getValid() + || attached_object->mDrawable.isNull()) + { + continue; + } + + LLSpatialBridge* bridge = attached_object->mDrawable->getSpatialBridge(); + + if (visible || !(bridge && bridge->getRadius() < 2.0)) + { + //override rigged attachments' octree spatial extents with this avatar's bounding box + bool rigged = false; + if (bridge) + { + //transform avatar bounding box into attachment's coordinate frame + LLVector4a extents[2]; + bridge->transformExtents(mDrawable->getSpatialExtents(), extents); + + if (attached_object->mDrawable->isState(LLDrawable::RIGGED | LLDrawable::RIGGED_CHILD)) + { + rigged = true; + override_bbox(attached_object->mDrawable, extents); + } + } + + // if selecting any attachments, update all of them as non-damped + if (attachment_selected) + { + gPipeline.updateMoveNormalAsync(attached_object->mDrawable); + } + else + { + // Note: SL-17415; While most objects follow joints, + // some objects get position updates from server + gPipeline.updateMoveDampedAsync(attached_object->mDrawable); + } + + // override_bbox calls movePartition() and getSpatialPartition(), + // so bridge might no longer be valid, get it again. + // ex: animesh stops being an animesh + bridge = attached_object->mDrawable->getSpatialBridge(); + if (bridge) + { + if (!rigged) + { + gPipeline.updateMoveNormalAsync(bridge); + } + else + { + //specialized impl of updateMoveNormalAsync just for rigged attachment SpatialBridge + bridge->setState(LLDrawable::MOVE_UNDAMPED); + bridge->updateMove(); + bridge->setState(LLDrawable::EARLY_MOVE); + + LLSpatialGroup* group = attached_object->mDrawable->getSpatialGroup(); + if (group) + { //set draw order of group + group->mAvatarp = this; + group->mRenderOrder = draw_order++; + } + } + } + + attached_object->updateText(); + } + } + } + } + + mNeedsAnimUpdate = false; + + if (isImpostor() && !mNeedsImpostorUpdate) + { + LL_ALIGN_16(LLVector4a ext[2]); + F32 distance; + LLVector3 angle; + + getImpostorValues(ext, angle, distance); + + for (U32 i = 0; i < 3 && !mNeedsImpostorUpdate; i++) + { + F32 cur_angle = angle.mV[i]; + F32 old_angle = mImpostorAngle.mV[i]; + F32 angle_diff = fabsf(cur_angle-old_angle); + + if (angle_diff > F_PI/512.f*distance*mUpdatePeriod) + { + mNeedsImpostorUpdate = true; + mLastImpostorUpdateReason = 2; + } + } + + if (detailed_update && !mNeedsImpostorUpdate) + { //update impostor if view angle, distance, or bounding box change + //significantly + + F32 dist_diff = fabsf(distance-mImpostorDistance); + if (dist_diff/mImpostorDistance > 0.1f) + { + mNeedsImpostorUpdate = true; + mLastImpostorUpdateReason = 3; + } + else + { + ext[0].load3(mLastAnimExtents[0].mV); + ext[1].load3(mLastAnimExtents[1].mV); + // Expensive. Just call this once per frame, in updateSpatialExtents(); + //calculateSpatialExtents(ext[0], ext[1]); + LLVector4a diff; + diff.setSub(ext[1], mImpostorExtents[1]); + if (diff.getLength3().getF32() > 0.05f) + { + mNeedsImpostorUpdate = true; + mLastImpostorUpdateReason = 4; + } + else + { + diff.setSub(ext[0], mImpostorExtents[0]); + if (diff.getLength3().getF32() > 0.05f) + { + mNeedsImpostorUpdate = true; + mLastImpostorUpdateReason = 5; + } + } + } + } + } + + if (mDrawable.notNull()) + { + mDrawable->movePartition(); + + //force a move if sitting on an active object + if (getParent() && ((LLViewerObject*) getParent())->mDrawable->isActive()) + { + gPipeline.markMoved(mDrawable, true); + } + } +} + +void LLVOAvatar::idleUpdateAppearanceAnimation() +{ + // update morphing params + if (mAppearanceAnimating) + { + ESex avatar_sex = getSex(); + F32 appearance_anim_time = mAppearanceMorphTimer.getElapsedTimeF32(); + if (appearance_anim_time >= APPEARANCE_MORPH_TIME) + { + mAppearanceAnimating = false; + for (LLVisualParam *param = getFirstVisualParam(); + param; + param = getNextVisualParam()) + { + if (param->isTweakable()) + { + param->stopAnimating(); + } + } + updateVisualParams(); + } + else + { + F32 morph_amt = calcMorphAmount(); + LLVisualParam *param; + + if (!isSelf()) + { + // animate only top level params for non-self avatars + for (param = getFirstVisualParam(); + param; + param = getNextVisualParam()) + { + if (param->isTweakable()) + { + param->animate(morph_amt); + } + } + } + + // apply all params + for (param = getFirstVisualParam(); + param; + param = getNextVisualParam()) + { + param->apply(avatar_sex); + } + + mLastAppearanceBlendTime = appearance_anim_time; + } + dirtyMesh(); + } +} + +F32 LLVOAvatar::calcMorphAmount() +{ + F32 appearance_anim_time = mAppearanceMorphTimer.getElapsedTimeF32(); + F32 blend_frac = calc_bouncy_animation(appearance_anim_time / APPEARANCE_MORPH_TIME); + F32 last_blend_frac = calc_bouncy_animation(mLastAppearanceBlendTime / APPEARANCE_MORPH_TIME); + + F32 morph_amt; + if (last_blend_frac == 1.f) + { + morph_amt = 1.f; + } + else + { + morph_amt = (blend_frac - last_blend_frac) / (1.f - last_blend_frac); + } + + return morph_amt; +} + +void LLVOAvatar::idleUpdateLipSync(bool voice_enabled) +{ + // Use the Lipsync_Ooh and Lipsync_Aah morphs for lip sync + if ( voice_enabled + && mLastRezzedStatus > 0 // no point updating lip-sync for clouds + && (LLVoiceClient::getInstance()->lipSyncEnabled()) + && LLVoiceClient::getInstance()->getIsSpeaking( mID ) ) + { + F32 ooh_morph_amount = 0.0f; + F32 aah_morph_amount = 0.0f; + + mVoiceVisualizer->lipSyncOohAah( ooh_morph_amount, aah_morph_amount ); + + if( mOohMorph ) + { + F32 ooh_weight = mOohMorph->getMinWeight() + + ooh_morph_amount * (mOohMorph->getMaxWeight() - mOohMorph->getMinWeight()); + + mOohMorph->setWeight( ooh_weight); + } + + if( mAahMorph ) + { + F32 aah_weight = mAahMorph->getMinWeight() + + aah_morph_amount * (mAahMorph->getMaxWeight() - mAahMorph->getMinWeight()); + + mAahMorph->setWeight( aah_weight); + } + + mLipSyncActive = true; + LLCharacter::updateVisualParams(); + dirtyMesh(); + } +} + +void LLVOAvatar::idleUpdateLoadingEffect() +{ + // update visibility when avatar is partially loaded + if (updateIsFullyLoaded()) // changed? + { + if (isFullyLoaded()) + { + if (mFirstFullyVisible) + { + mFirstFullyVisible = false; + mFirstDecloudTime = mFirstAppearanceMessageTimer.getElapsedTimeF32(); + if (isSelf()) + { + LL_INFOS("Avatar") << avString() << "self isFullyLoaded, mFirstFullyVisible after " << mFirstDecloudTime << LL_ENDL; + LLAppearanceMgr::instance().onFirstFullyVisible(); + } + else + { + LL_INFOS("Avatar") << avString() << "other isFullyLoaded, mFirstFullyVisible after " << mFirstDecloudTime << LL_ENDL; + } + } + + deleteParticleSource(); + updateLOD(); + } + else + { + LLPartSysData particle_parameters; + + // fancy particle cloud designed by Brent + particle_parameters.mPartData.mMaxAge = 4.f; + particle_parameters.mPartData.mStartScale.mV[VX] = 0.8f; + particle_parameters.mPartData.mStartScale.mV[VX] = 0.8f; + particle_parameters.mPartData.mStartScale.mV[VY] = 1.0f; + particle_parameters.mPartData.mEndScale.mV[VX] = 0.02f; + particle_parameters.mPartData.mEndScale.mV[VY] = 0.02f; + particle_parameters.mPartData.mStartColor = LLColor4(1, 1, 1, 0.5f); + particle_parameters.mPartData.mEndColor = LLColor4(1, 1, 1, 0.0f); + particle_parameters.mPartData.mStartScale.mV[VX] = 0.8f; + particle_parameters.mPartImageID = sCloudTexture->getID(); + particle_parameters.mMaxAge = 0.f; + particle_parameters.mPattern = LLPartSysData::LL_PART_SRC_PATTERN_ANGLE_CONE; + particle_parameters.mInnerAngle = F_PI; + particle_parameters.mOuterAngle = 0.f; + particle_parameters.mBurstRate = 0.02f; + particle_parameters.mBurstRadius = 0.0f; + particle_parameters.mBurstPartCount = 1; + particle_parameters.mBurstSpeedMin = 0.1f; + particle_parameters.mBurstSpeedMax = 1.f; + particle_parameters.mPartData.mFlags = ( LLPartData::LL_PART_INTERP_COLOR_MASK | LLPartData::LL_PART_INTERP_SCALE_MASK | + LLPartData::LL_PART_EMISSIVE_MASK | // LLPartData::LL_PART_FOLLOW_SRC_MASK | + LLPartData::LL_PART_TARGET_POS_MASK ); + + // do not generate particles for dummy or overly-complex avatars + if (!mIsDummy && !isTooComplex() && !isTooSlow()) + { + setParticleSource(particle_parameters, getID()); + } + } + } +} + +void LLVOAvatar::idleUpdateWindEffect() +{ + // update wind effect + if ((LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_AVATAR) >= LLDrawPoolAvatar::SHADER_LEVEL_CLOTH)) + { + F32 hover_strength = 0.f; + F32 time_delta = mRippleTimer.getElapsedTimeF32() - mRippleTimeLast; + mRippleTimeLast = mRippleTimer.getElapsedTimeF32(); + LLVector3 velocity = getVelocity(); + F32 speed = velocity.length(); + //RN: velocity varies too much frame to frame for this to work + mRippleAccel.clearVec();//lerp(mRippleAccel, (velocity - mLastVel) * time_delta, LLSmoothInterpolation::getInterpolant(0.02f)); + mLastVel = velocity; + LLVector4 wind; + wind.setVec(getRegion()->mWind.getVelocityNoisy(getPositionAgent(), 4.f) - velocity); + + if (mInAir) + { + hover_strength = HOVER_EFFECT_STRENGTH * llmax(0.f, HOVER_EFFECT_MAX_SPEED - speed); + } + + if (mBelowWater) + { + // TODO: make cloth flow more gracefully when underwater + hover_strength += UNDERWATER_EFFECT_STRENGTH; + } + + wind.mV[VZ] += hover_strength; + wind.normalize(); + + wind.mV[VW] = llmin(0.025f + (speed * 0.015f) + hover_strength, 0.5f); + F32 interp; + if (wind.mV[VW] > mWindVec.mV[VW]) + { + interp = LLSmoothInterpolation::getInterpolant(0.2f); + } + else + { + interp = LLSmoothInterpolation::getInterpolant(0.4f); + } + mWindVec = lerp(mWindVec, wind, interp); + + F32 wind_freq = hover_strength + llclamp(8.f + (speed * 0.7f) + (noise1(mRipplePhase) * 4.f), 8.f, 25.f); + mWindFreq = lerp(mWindFreq, wind_freq, interp); + + if (mBelowWater) + { + mWindFreq *= UNDERWATER_FREQUENCY_DAMP; + } + + mRipplePhase += (time_delta * mWindFreq); + if (mRipplePhase > F_TWO_PI) + { + mRipplePhase = fmodf(mRipplePhase, F_TWO_PI); + } + } +} + +void LLVOAvatar::idleUpdateNameTag(const LLVector3& root_pos_last) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + + // update chat bubble + //-------------------------------------------------------------------- + // draw text label over character's head + //-------------------------------------------------------------------- + if (mChatTimer.getElapsedTimeF32() > BUBBLE_CHAT_TIME) + { + mChats.clear(); + } + + const F32 time_visible = mTimeVisible.getElapsedTimeF32(); + + static LLCachedControl NAME_SHOW_TIME(gSavedSettings, "RenderNameShowTime"); // seconds + static LLCachedControl FADE_DURATION(gSavedSettings, "RenderNameFadeDuration"); // seconds + static LLCachedControl use_chat_bubbles(gSavedSettings, "UseChatBubbles"); + + bool visible_chat = use_chat_bubbles && (mChats.size() || mTyping); + bool render_name = visible_chat || + (((sRenderName == RENDER_NAME_ALWAYS) || + (sRenderName == RENDER_NAME_FADE && time_visible < NAME_SHOW_TIME))); + // If it's your own avatar, don't draw in mouselook, and don't + // draw if we're specifically hiding our own name. + if (isSelf()) + { + static LLCachedControl render_name_show_self(gSavedSettings, "RenderNameShowSelf"); + static LLCachedControl name_tag_mode(gSavedSettings, "AvatarNameTagMode"); + render_name = render_name + && !gAgentCamera.cameraMouselook() + && (visible_chat || (render_name_show_self && name_tag_mode)); + } + + if ( !render_name ) + { + if (mNameText) + { + // ...clean up old name tag + mNameText->markDead(); + mNameText = NULL; + sNumVisibleChatBubbles--; + } + return; + } + + bool new_name = false; + if (visible_chat != mVisibleChat) + { + mVisibleChat = visible_chat; + new_name = true; + } + + if (sRenderGroupTitles != mRenderGroupTitles) + { + mRenderGroupTitles = sRenderGroupTitles; + new_name = true; + } + + // First Calculate Alpha + // If alpha > 0, create mNameText if necessary, otherwise delete it + F32 alpha = 0.f; + if (mAppAngle > 5.f) + { + const F32 START_FADE_TIME = NAME_SHOW_TIME - FADE_DURATION; + if (!visible_chat && sRenderName == RENDER_NAME_FADE && time_visible > START_FADE_TIME) + { + alpha = 1.f - (time_visible - START_FADE_TIME) / FADE_DURATION; + } + else + { + // ...not fading, full alpha + alpha = 1.f; + } + } + else if (mAppAngle > 2.f) + { + // far away is faded out also + alpha = (mAppAngle-2.f)/3.f; + } + + if (alpha <= 0.f) + { + if (mNameText) + { + mNameText->markDead(); + mNameText = NULL; + sNumVisibleChatBubbles--; + } + return; + } + + if (!mNameText) + { + mNameText = static_cast( LLHUDObject::addHUDObject( + LLHUDObject::LL_HUD_NAME_TAG) ); + //mNameText->setMass(10.f); + mNameText->setSourceObject(this); + mNameText->setVertAlignment(LLHUDNameTag::ALIGN_VERT_TOP); + mNameText->setVisibleOffScreen(true); + mNameText->setMaxLines(11); + mNameText->setFadeDistance(CHAT_NORMAL_RADIUS, 5.f); + sNumVisibleChatBubbles++; + new_name = true; + } + + mNameText->setPositionAgent(root_pos_last); + + idleUpdateNameTagText(new_name); + idleUpdateNameTagAlpha(new_name, alpha); +} + +void LLVOAvatar::idleUpdateNameTagText(bool new_name) +{ + LLNameValue *title = getNVPair("Title"); + LLNameValue* firstname = getNVPair("FirstName"); + LLNameValue* lastname = getNVPair("LastName"); + + // Avatars must have a first and last name + if (!firstname || !lastname) return; + + bool is_away = mSignaledAnimations.find(ANIM_AGENT_AWAY) != mSignaledAnimations.end(); + bool is_do_not_disturb = mSignaledAnimations.find(ANIM_AGENT_DO_NOT_DISTURB) != mSignaledAnimations.end(); + bool is_appearance = mSignaledAnimations.find(ANIM_AGENT_CUSTOMIZE) != mSignaledAnimations.end(); + bool is_muted; + if (isSelf()) + { + is_muted = false; + } + else + { + is_muted = isInMuteList(); + } + bool is_friend = LLAvatarTracker::instance().isBuddy(getID()); + bool is_cloud = getIsCloud(); + + if (is_appearance != mNameAppearance) + { + if (is_appearance) + { + debugAvatarRezTime("AvatarRezEnteredAppearanceNotification","entered appearance mode"); + } + else + { + debugAvatarRezTime("AvatarRezLeftAppearanceNotification","left appearance mode"); + } + } + + // Rebuild name tag if state change detected + if (!mNameIsSet + || new_name + || (!title && !mTitle.empty()) + || (title && mTitle != title->getString()) + || is_away != mNameAway + || is_do_not_disturb != mNameDoNotDisturb + || is_muted != mNameMute + || is_appearance != mNameAppearance + || is_friend != mNameFriend + || is_cloud != mNameCloud) + { + LLColor4 name_tag_color = getNameTagColor(is_friend); + + clearNameTag(); + + if (is_away || is_muted || is_do_not_disturb || is_appearance) + { + std::string line; + if (is_away) + { + line += LLTrans::getString("AvatarAway"); + line += ", "; + } + if (is_do_not_disturb) + { + line += LLTrans::getString("AvatarDoNotDisturb"); + line += ", "; + } + if (is_muted) + { + line += LLTrans::getString("AvatarMuted"); + line += ", "; + } + if (is_appearance) + { + line += LLTrans::getString("AvatarEditingAppearance"); + line += ", "; + } + if (is_cloud) + { + line += LLTrans::getString("LoadingData"); + line += ", "; + } + // trim last ", " + line.resize( line.length() - 2 ); + addNameTagLine(line, name_tag_color, LLFontGL::NORMAL, + LLFontGL::getFontSansSerifSmall()); + } + + if (sRenderGroupTitles + && title && title->getString() && title->getString()[0] != '\0') + { + std::string title_str = title->getString(); + LLStringFn::replace_ascii_controlchars(title_str,LL_UNKNOWN_CHAR); + addNameTagLine(title_str, name_tag_color, LLFontGL::NORMAL, + LLFontGL::getFontSansSerifSmall(), true); + } + + static LLUICachedControl show_display_names("NameTagShowDisplayNames", true); + static LLUICachedControl show_usernames("NameTagShowUsernames", true); + static LLUICachedControl show_rez_status("NameTagDebugAVRezState", false); + + if (LLAvatarName::useDisplayNames()) + { + LLAvatarName av_name; + if (!LLAvatarNameCache::get(getID(), &av_name)) + { + // Force a rebuild at next idle + // Note: do not connect a callback on idle(). + clearNameTag(); + } + + // Might be blank if name not available yet, that's OK + if (show_display_names) + { + addNameTagLine(av_name.getDisplayName(), name_tag_color, LLFontGL::NORMAL, + LLFontGL::getFontSansSerif(), true); + } + // Suppress SLID display if display name matches exactly (ugh) + if (show_usernames && !av_name.isDisplayNameDefault()) + { + // *HACK: Desaturate the color + LLColor4 username_color = name_tag_color * 0.83f; + addNameTagLine(av_name.getUserName(), username_color, LLFontGL::NORMAL, + LLFontGL::getFontSansSerifSmall(), true); + } + } + else + { + const LLFontGL* font = LLFontGL::getFontSansSerif(); + std::string full_name = LLCacheName::buildFullName( firstname->getString(), lastname->getString() ); + addNameTagLine(full_name, name_tag_color, LLFontGL::NORMAL, font, true); + } + + if (show_rez_status) + { + std::string av_string = LLVOAvatar::rezStatusToString(mLastRezzedStatus); + addNameTagLine(av_string, name_tag_color, LLFontGL::NORMAL, LLFontGL::getFontSansSerifSmall(), true); + } + + mNameAway = is_away; + mNameDoNotDisturb = is_do_not_disturb; + mNameMute = is_muted; + mNameAppearance = is_appearance; + mNameFriend = is_friend; + mNameCloud = is_cloud; + mTitle = title ? title->getString() : ""; + LLStringFn::replace_ascii_controlchars(mTitle,LL_UNKNOWN_CHAR); + new_name = true; + } + + if (mVisibleChat) + { + mNameText->setFont(LLFontGL::getFontSansSerif()); + mNameText->setTextAlignment(LLHUDNameTag::ALIGN_TEXT_LEFT); + mNameText->setFadeDistance(CHAT_NORMAL_RADIUS * 2.f, 5.f); + + std::deque::iterator chat_iter = mChats.begin(); + mNameText->clearString(); + + LLColor4 new_chat = LLUIColorTable::instance().getColor( isSelf() ? "UserChatColor" : "AgentChatColor" ); + LLColor4 normal_chat = lerp(new_chat, LLColor4(0.8f, 0.8f, 0.8f, 1.f), 0.7f); + LLColor4 old_chat = lerp(normal_chat, LLColor4(0.6f, 0.6f, 0.6f, 1.f), 0.7f); + if (mTyping && mChats.size() >= MAX_BUBBLE_CHAT_UTTERANCES) + { + ++chat_iter; + } + + for(; chat_iter != mChats.end(); ++chat_iter) + { + F32 chat_fade_amt = llclamp((F32)((LLFrameTimer::getElapsedSeconds() - chat_iter->mTime) / CHAT_FADE_TIME), 0.f, 4.f); + LLFontGL::StyleFlags style; + switch(chat_iter->mChatType) + { + case CHAT_TYPE_WHISPER: + style = LLFontGL::ITALIC; + break; + case CHAT_TYPE_SHOUT: + style = LLFontGL::BOLD; + break; + default: + style = LLFontGL::NORMAL; + break; + } + if (chat_fade_amt < 1.f) + { + F32 u = clamp_rescale(chat_fade_amt, 0.9f, 1.f, 0.f, 1.f); + mNameText->addLine(chat_iter->mText, lerp(new_chat, normal_chat, u), style); + } + else if (chat_fade_amt < 2.f) + { + F32 u = clamp_rescale(chat_fade_amt, 1.9f, 2.f, 0.f, 1.f); + mNameText->addLine(chat_iter->mText, lerp(normal_chat, old_chat, u), style); + } + else if (chat_fade_amt < 3.f) + { + // *NOTE: only remove lines down to minimum number + mNameText->addLine(chat_iter->mText, old_chat, style); + } + } + mNameText->setVisibleOffScreen(true); + + if (mTyping) + { + S32 dot_count = (llfloor(mTypingTimer.getElapsedTimeF32() * 3.f) + 2) % 3 + 1; + switch(dot_count) + { + case 1: + mNameText->addLine(".", new_chat); + break; + case 2: + mNameText->addLine("..", new_chat); + break; + case 3: + mNameText->addLine("...", new_chat); + break; + } + + } + } + else + { + // ...not using chat bubbles, just names + mNameText->setTextAlignment(LLHUDNameTag::ALIGN_TEXT_CENTER); + mNameText->setFadeDistance(CHAT_NORMAL_RADIUS, 5.f); + mNameText->setVisibleOffScreen(false); + } +} + +void LLVOAvatar::addNameTagLine(const std::string& line, const LLColor4& color, S32 style, const LLFontGL* font, const bool use_ellipses) +{ + // extra width (NAMETAG_MAX_WIDTH) is for names only, not for chat + llassert(mNameText); + if (mVisibleChat) + { + mNameText->addLabel(line, LLHUDNameTag::NAMETAG_MAX_WIDTH); + } + else + { + mNameText->addLine(line, color, (LLFontGL::StyleFlags)style, font, use_ellipses, LLHUDNameTag::NAMETAG_MAX_WIDTH); + } + mNameIsSet |= !line.empty(); +} + +void LLVOAvatar::clearNameTag() +{ + mNameIsSet = false; + if (mNameText) + { + mNameText->setLabel(""); + mNameText->setString(""); + } + mTimeVisible.reset(); +} + +//static +void LLVOAvatar::invalidateNameTag(const LLUUID& agent_id) +{ + LLViewerObject* obj = gObjectList.findObject(agent_id); + if (!obj) return; + + LLVOAvatar* avatar = dynamic_cast(obj); + if (!avatar) return; + + avatar->clearNameTag(); +} + +//static +void LLVOAvatar::invalidateNameTags() +{ + std::vector::iterator it = LLCharacter::sInstances.begin(); + for ( ; it != LLCharacter::sInstances.end(); ++it) + { + LLVOAvatar* avatar = dynamic_cast(*it); + if (!avatar) continue; + if (avatar->isDead()) continue; + + avatar->clearNameTag(); + } +} + +// Compute name tag position during idle update +LLVector3 LLVOAvatar::idleCalcNameTagPosition(const LLVector3 &root_pos_last) +{ + LLQuaternion root_rot = mRoot->getWorldRotation(); + LLQuaternion inv_root_rot = ~root_rot; + LLVector3 pixel_right_vec; + LLVector3 pixel_up_vec; + LLViewerCamera::getInstance()->getPixelVectors(root_pos_last, pixel_up_vec, pixel_right_vec); + LLVector3 camera_to_av = root_pos_last - LLViewerCamera::getInstance()->getOrigin(); + camera_to_av.normalize(); + LLVector3 local_camera_at = camera_to_av * inv_root_rot; + LLVector3 local_camera_up = camera_to_av % LLViewerCamera::getInstance()->getLeftAxis(); + local_camera_up.normalize(); + local_camera_up = local_camera_up * inv_root_rot; + + // position is based on head position, does not require mAvatarOffset here. - Nyx + LLVector3 avatar_ellipsoid(mBodySize.mV[VX] * 0.4f, + mBodySize.mV[VY] * 0.4f, + mBodySize.mV[VZ] * NAMETAG_VERT_OFFSET_WEIGHT); + + local_camera_up.scaleVec(avatar_ellipsoid); + local_camera_at.scaleVec(avatar_ellipsoid); + + LLVector3 head_offset = (mHeadp->getLastWorldPosition() - mRoot->getLastWorldPosition()) * inv_root_rot; + + if (dist_vec(head_offset, mTargetRootToHeadOffset) > NAMETAG_UPDATE_THRESHOLD) + { + mTargetRootToHeadOffset = head_offset; + } + + mCurRootToHeadOffset = lerp(mCurRootToHeadOffset, mTargetRootToHeadOffset, LLSmoothInterpolation::getInterpolant(0.2f)); + + LLVector3 name_position = mRoot->getLastWorldPosition() + (mCurRootToHeadOffset * root_rot); + name_position += (local_camera_up * root_rot) - (projected_vec(local_camera_at * root_rot, camera_to_av)); + name_position += pixel_up_vec * NAMETAG_VERTICAL_SCREEN_OFFSET; + + const F32 water_height = getRegion()->getWaterHeight(); + static const F32 WATER_HEIGHT_DELTA = 0.25f; + if (name_position[VZ] < water_height + WATER_HEIGHT_DELTA) + { + if (LLViewerCamera::getInstance()->getOrigin()[VZ] >= water_height) + { + name_position[VZ] = water_height; + } + else if (mNameText) // both camera and HUD are below watermark + { + F32 name_world_height = mNameText->getWorldHeight(); + F32 max_z_position = water_height - name_world_height; + if (name_position[VZ] > max_z_position) + { + name_position[VZ] = max_z_position; + } + } + } + + return name_position; +} + +void LLVOAvatar::idleUpdateNameTagAlpha(bool new_name, F32 alpha) +{ + llassert(mNameText); + + if (new_name + || alpha != mNameAlpha) + { + mNameText->setAlpha(alpha); + mNameAlpha = alpha; + } +} + +LLColor4 LLVOAvatar::getNameTagColor(bool is_friend) +{ + static LLUICachedControl show_friends("NameTagShowFriends", false); + const char* color_name; + if (show_friends && is_friend) + { + color_name = "NameTagFriend"; + } + else if (LLAvatarName::useDisplayNames()) + { + // ...color based on whether username "matches" a computed display name + LLAvatarName av_name; + if (LLAvatarNameCache::get(getID(), &av_name) && av_name.isDisplayNameDefault()) + { + color_name = "NameTagMatch"; + } + else + { + color_name = "NameTagMismatch"; + } + } + else + { + // ...not using display names + color_name = "NameTagLegacy"; + } + return LLUIColorTable::getInstance()->getColor( color_name ); +} + +void LLVOAvatar::idleUpdateBelowWater() +{ + F32 avatar_height = (F32)(getPositionGlobal().mdV[VZ]); + + F32 water_height; + water_height = getRegion()->getWaterHeight(); + + mBelowWater = avatar_height < water_height; +} + +void LLVOAvatar::slamPosition() +{ + gAgent.setPositionAgent(getPositionAgent()); + // SL-315 + mRoot->setWorldPosition(getPositionAgent()); // teleport + setChanged(TRANSLATED); + if (mDrawable.notNull()) + { + gPipeline.updateMoveNormalAsync(mDrawable); + } + mRoot->updateWorldMatrixChildren(); +} + +bool LLVOAvatar::isVisuallyMuted() +{ + bool muted = false; + + // Priority order (highest priority first) + // * own avatar is never visually muted + // * if on the "always draw normally" list, draw them normally + // * if on the "always visually mute" list, mute them + // * check against the render cost and attachment limits + if (!isSelf()) + { + if (mVisuallyMuteSetting == AV_ALWAYS_RENDER) + { + muted = false; + } + else if (mVisuallyMuteSetting == AV_DO_NOT_RENDER) + { +#ifdef JELLYDOLLS_SHOULD_IMPOSTOR + muted = true; + // Always want to see this AV as an impostor +#else + muted = false; +#endif + } + else if (isInMuteList()) + { + muted = true; + } + else if (mIsControlAvatar) + { + muted = isTooSlow(); + } + else + { + muted = isTooComplex() || isTooSlow(); + } + } + + return muted; +} + +bool LLVOAvatar::isInMuteList() const +{ + bool muted = false; + F64 now = LLFrameTimer::getTotalSeconds(); + if (now < mCachedMuteListUpdateTime) + { + muted = mCachedInMuteList; + } + else + { + muted = LLMuteList::getInstance()->isMuted(getID()); + + const F64 SECONDS_BETWEEN_MUTE_UPDATES = 1; + mCachedMuteListUpdateTime = now + SECONDS_BETWEEN_MUTE_UPDATES; + mCachedInMuteList = muted; + } + return muted; +} + +void LLVOAvatar::updateAppearanceMessageDebugText() +{ + S32 central_bake_version = -1; + if (getRegion()) + { + central_bake_version = getRegion()->getCentralBakeVersion(); + } + bool all_baked_downloaded = allBakedTexturesCompletelyDownloaded(); + bool all_local_downloaded = allLocalTexturesCompletelyDownloaded(); + std::string debug_line = llformat("%s%s - mLocal: %d, mEdit: %d, mUSB: %d, CBV: %d", + isSelf() ? (all_local_downloaded ? "L" : "l") : "-", + all_baked_downloaded ? "B" : "b", + mUseLocalAppearance, mIsEditingAppearance, + 1, central_bake_version); + std::string origin_string = bakedTextureOriginInfo(); + debug_line += " [" + origin_string + "]"; + S32 curr_cof_version = LLAppearanceMgr::instance().getCOFVersion(); + S32 last_request_cof_version = mLastUpdateRequestCOFVersion; + S32 last_received_cof_version = mLastUpdateReceivedCOFVersion; + if (isSelf()) + { + debug_line += llformat(" - cof: %d req: %d rcv:%d", + curr_cof_version, last_request_cof_version, last_received_cof_version); + static LLCachedControl debug_force_failure(gSavedSettings, "DebugForceAppearanceRequestFailure"); + if (debug_force_failure) + { + debug_line += " FORCING ERRS"; + } + } + else + { + debug_line += llformat(" - cof rcv:%d", last_received_cof_version); + } + debug_line += llformat(" bsz-z: %.3f", mBodySize[2]); + if (mAvatarOffset[2] != 0.0f) + { + debug_line += llformat("avofs-z: %.3f", mAvatarOffset[2]); + } + bool hover_enabled = getRegion() && getRegion()->avatarHoverHeightEnabled(); + debug_line += hover_enabled ? " H" : " h"; + const LLVector3& hover_offset = getHoverOffset(); + if (hover_offset[2] != 0.0) + { + debug_line += llformat(" hov_z: %.3f", hover_offset[2]); + debug_line += llformat(" %s", (isSitting() ? "S" : "T")); + debug_line += llformat("%s", (isMotionActive(ANIM_AGENT_SIT_GROUND_CONSTRAINED) ? "G" : "-")); + } + if (mInAir) + { + debug_line += " A"; + + } + + LLVector3 ankle_right_pos_agent = mFootRightp->getWorldPosition(); + LLVector3 normal; + LLVector3 ankle_right_ground_agent = ankle_right_pos_agent; + resolveHeightAgent(ankle_right_pos_agent, ankle_right_ground_agent, normal); + F32 rightElev = llmax(-0.2f, ankle_right_pos_agent.mV[VZ] - ankle_right_ground_agent.mV[VZ]); + debug_line += llformat(" relev %.3f", rightElev); + + LLVector3 root_pos = mRoot->getPosition(); + LLVector3 pelvis_pos = mPelvisp->getPosition(); + debug_line += llformat(" rp %.3f pp %.3f", root_pos[2], pelvis_pos[2]); + + const LLVector3& scale = getScale(); + debug_line += llformat(" scale-z %.3f", scale[2]); + S32 is_visible = (S32) isVisible(); + S32 is_m_visible = (S32) mVisible; + debug_line += llformat(" v %d/%d", is_visible, is_m_visible); + + AvatarOverallAppearance aoa = getOverallAppearance(); + if (aoa == AOA_NORMAL) + { + debug_line += " N"; + } + else if (aoa == AOA_JELLYDOLL) + { + debug_line += " J"; + } + else + { + debug_line += " I"; + } + + if (mMeshValid) + { + debug_line += "m"; + } + else + { + debug_line += "-"; + } + if (isImpostor()) + { + debug_line += " Imp" + llformat("%d[%d]:%.1f", mUpdatePeriod, mLastImpostorUpdateReason, ((F32)(gFrameTimeSeconds-mLastImpostorUpdateFrameTime))); + } + + addDebugText(debug_line); +} + +LLViewerInventoryItem* getObjectInventoryItem(LLViewerObject *vobj, LLUUID asset_id) +{ + LLViewerInventoryItem *item = NULL; + + if (vobj) + { + if (vobj->getInventorySerial()<=0) + { + vobj->requestInventory(); + } + item = vobj->getInventoryItemByAsset(asset_id); + } + return item; +} + +LLViewerInventoryItem* recursiveGetObjectInventoryItem(LLViewerObject *vobj, LLUUID asset_id) +{ + LLViewerInventoryItem *item = getObjectInventoryItem(vobj, asset_id); + if (!item) + { + LLViewerObject::const_child_list_t& children = vobj->getChildren(); + for (LLViewerObject::const_child_list_t::const_iterator it = children.begin(); + it != children.end(); ++it) + { + LLViewerObject *childp = *it; + item = getObjectInventoryItem(childp, asset_id); + if (item) + { + break; + } + } + } + return item; +} + +void LLVOAvatar::updateAnimationDebugText() +{ + for (LLMotionController::motion_list_t::iterator iter = mMotionController.getActiveMotions().begin(); + iter != mMotionController.getActiveMotions().end(); ++iter) + { + LLMotion* motionp = *iter; + if (motionp->getMinPixelArea() < getPixelArea()) + { + std::string output; + std::string motion_name = motionp->getName(); + if (motion_name.empty()) + { + if (isControlAvatar()) + { + LLControlAvatar *control_av = dynamic_cast(this); + // Try to get name from inventory of associated object + LLVOVolume *volp = control_av->mRootVolp; + LLViewerInventoryItem *item = recursiveGetObjectInventoryItem(volp,motionp->getID()); + if (item) + { + motion_name = item->getName(); + } + } + } + if (motion_name.empty()) + { + std::string name; + if (gAgent.isGodlikeWithoutAdminMenuFakery() || isSelf()) + { + name = motionp->getID().asString(); + LLVOAvatar::AnimSourceIterator anim_it = mAnimationSources.begin(); + for (; anim_it != mAnimationSources.end(); ++anim_it) + { + if (anim_it->second == motionp->getID()) + { + LLViewerObject* object = gObjectList.findObject(anim_it->first); + if (!object) + { + break; + } + if (object->isAvatar()) + { + if (mMotionController.mIsSelf) + { + // Searching inventory by asset id is really long + // so just mark as inventory + // Also item is likely to be named by LLPreviewAnim + name += "(inventory)"; + } + } + else + { + LLViewerInventoryItem* item = NULL; + if (!object->isInventoryDirty()) + { + item = object->getInventoryItemByAsset(motionp->getID()); + } + if (item) + { + name = item->getName(); + } + else if (object->isAttachment()) + { + name += "(att:" + getAttachmentItemName() + ")"; + } + else + { + // in-world object, name or content unknown + name += "(in-world)"; + } + } + break; + } + } + } + else + { + name = LLUUID::null.asString(); + } + motion_name = name; + } + std::string motion_tag = ""; + if (mPlayingAnimations.find(motionp->getID()) != mPlayingAnimations.end()) + { + motion_tag = "*"; + } + output = llformat("%s%s - %d", + motion_name.c_str(), + motion_tag.c_str(), + (U32)motionp->getPriority()); + addDebugText(output); + } + } +} + +void LLVOAvatar::updateDebugText() +{ + // Leave mDebugText uncleared here, in case a derived class has added some state first + + if (gSavedSettings.getBOOL("DebugAvatarAppearanceMessage")) + { + updateAppearanceMessageDebugText(); + } + + if (gSavedSettings.getBOOL("DebugAvatarCompositeBaked")) + { + if (!mBakedTextureDebugText.empty()) + addDebugText(mBakedTextureDebugText); + } + + // Develop -> Avatar -> Animation Info + if (LLVOAvatar::sShowAnimationDebug) + { + updateAnimationDebugText(); + } + + if (!mDebugText.size() && mText.notNull()) + { + mText->markDead(); + mText = NULL; + } + else if (mDebugText.size()) + { + setDebugText(mDebugText); + } + mDebugText.clear(); +} + +//------------------------------------------------------------------------ +// updateFootstepSounds +// Factored out from updateCharacter() +// Generate footstep sounds when feet hit the ground +//------------------------------------------------------------------------ +void LLVOAvatar::updateFootstepSounds() +{ + if (mIsDummy) + { + return; + } + + //------------------------------------------------------------------------- + // Find the ground under each foot, these are used for a variety + // of things that follow + //------------------------------------------------------------------------- + LLVector3 ankle_left_pos_agent = mFootLeftp->getWorldPosition(); + LLVector3 ankle_right_pos_agent = mFootRightp->getWorldPosition(); + + LLVector3 ankle_left_ground_agent = ankle_left_pos_agent; + LLVector3 ankle_right_ground_agent = ankle_right_pos_agent; + LLVector3 normal; + resolveHeightAgent(ankle_left_pos_agent, ankle_left_ground_agent, normal); + resolveHeightAgent(ankle_right_pos_agent, ankle_right_ground_agent, normal); + + F32 leftElev = llmax(-0.2f, ankle_left_pos_agent.mV[VZ] - ankle_left_ground_agent.mV[VZ]); + F32 rightElev = llmax(-0.2f, ankle_right_pos_agent.mV[VZ] - ankle_right_ground_agent.mV[VZ]); + + if (!isSitting()) + { + //------------------------------------------------------------------------- + // Figure out which foot is on ground + //------------------------------------------------------------------------- + if (!mInAir) + { + if ((leftElev < 0.0f) || (rightElev < 0.0f)) + { + ankle_left_pos_agent = mFootLeftp->getWorldPosition(); + ankle_right_pos_agent = mFootRightp->getWorldPosition(); + leftElev = ankle_left_pos_agent.mV[VZ] - ankle_left_ground_agent.mV[VZ]; + rightElev = ankle_right_pos_agent.mV[VZ] - ankle_right_ground_agent.mV[VZ]; + } + } + } + + const LLUUID AGENT_FOOTSTEP_ANIMS[] = {ANIM_AGENT_WALK, ANIM_AGENT_RUN, ANIM_AGENT_LAND}; + const S32 NUM_AGENT_FOOTSTEP_ANIMS = LL_ARRAY_SIZE(AGENT_FOOTSTEP_ANIMS); + + if ( gAudiop && isAnyAnimationSignaled(AGENT_FOOTSTEP_ANIMS, NUM_AGENT_FOOTSTEP_ANIMS) ) + { + bool playSound = false; + LLVector3 foot_pos_agent; + + bool onGroundLeft = (leftElev <= 0.05f); + bool onGroundRight = (rightElev <= 0.05f); + + // did left foot hit the ground? + if ( onGroundLeft && !mWasOnGroundLeft ) + { + foot_pos_agent = ankle_left_pos_agent; + playSound = true; + } + + // did right foot hit the ground? + if ( onGroundRight && !mWasOnGroundRight ) + { + foot_pos_agent = ankle_right_pos_agent; + playSound = true; + } + + mWasOnGroundLeft = onGroundLeft; + mWasOnGroundRight = onGroundRight; + + if ( playSound ) + { + const F32 STEP_VOLUME = 0.1f; + const LLUUID& step_sound_id = getStepSound(); + + LLVector3d foot_pos_global = gAgent.getPosGlobalFromAgent(foot_pos_agent); + + if (LLViewerParcelMgr::getInstance()->canHearSound(foot_pos_global) + && !LLMuteList::getInstance()->isMuted(getID(), LLMute::flagObjectSounds)) + { + gAudiop->triggerSound(step_sound_id, getID(), STEP_VOLUME, LLAudioEngine::AUDIO_TYPE_AMBIENT, foot_pos_global); + } + } + } +} + +//------------------------------------------------------------------------ +// computeUpdatePeriod() +// Factored out from updateCharacter() +// Set new value for mUpdatePeriod based on distance and various other factors. +// +// Note 10-2020: it turns out that none of these update period +// calculations have been having any effect, because +// mNeedsImpostorUpdate was not being set in updateCharacter(). So +// it's really open to question whether we want to enable time based updates, and if +// so, at what rate. Leaving the rates as given would lead to +// drastically more frequent impostor updates than we've been doing all these years. +// ------------------------------------------------------------------------ +void LLVOAvatar::computeUpdatePeriod() +{ + bool visually_muted = isVisuallyMuted(); + if (mDrawable.notNull() + && isVisible() + && (!isSelf() || visually_muted) + && !isUIAvatar() + && (sLimitNonImpostors || visually_muted) + && !mNeedsAnimUpdate) + { + const LLVector4a* ext = mDrawable->getSpatialExtents(); + LLVector4a size; + size.setSub(ext[1],ext[0]); + F32 mag = size.getLength3().getF32()*0.5f; + + const S32 UPDATE_RATE_SLOW = 64; + const S32 UPDATE_RATE_MED = 48; + const S32 UPDATE_RATE_FAST = 32; + + if (visually_muted) + { // visually muted avatars update at lowest rate + mUpdatePeriod = UPDATE_RATE_SLOW; + } + else if (! shouldImpostor() + || mDrawable->mDistanceWRTCamera < 1.f + mag) + { // first 25% of max visible avatars are not impostored + // also, don't impostor avatars whose bounding box may be penetrating the + // impostor camera near clip plane + mUpdatePeriod = 1; + } + else if ( shouldImpostor(4.0) ) + { //background avatars are REALLY slow updating impostors + mUpdatePeriod = UPDATE_RATE_SLOW; + } + else if (mLastRezzedStatus <= 0) + { + // Don't update cloud avatars too often + mUpdatePeriod = UPDATE_RATE_SLOW; + } + else if ( shouldImpostor(3.0) ) + { //back 25% of max visible avatars are slow updating impostors + mUpdatePeriod = UPDATE_RATE_MED; + } + else + { + //nearby avatars, update the impostors more frequently. + mUpdatePeriod = UPDATE_RATE_FAST; + } + } + else + { + mUpdatePeriod = 1; + } +} + +//------------------------------------------------------------------------ +// updateOrientation() +// Factored out from updateCharacter() +// This is used by updateCharacter() to update the avatar's orientation: +// - updates mTurning state +// - updates rotation of the mRoot joint in the skeleton +// - for self, calls setControlFlags() to notify the simulator about any turns +//------------------------------------------------------------------------ +void LLVOAvatar::updateOrientation(LLAgent& agent, F32 speed, F32 delta_time) +{ + LLQuaternion iQ; + LLVector3 upDir( 0.0f, 0.0f, 1.0f ); + + // Compute a forward direction vector derived from the primitive rotation + // and the velocity vector. When walking or jumping, don't let body deviate + // more than 90 from the view, if necessary, flip the velocity vector. + + LLVector3 primDir; + if (isSelf()) + { + primDir = agent.getAtAxis() - projected_vec(agent.getAtAxis(), agent.getReferenceUpVector()); + primDir.normalize(); + } + else + { + primDir = getRotation().getMatrix3().getFwdRow(); + } + LLVector3 velDir = getVelocity(); + velDir.normalize(); + if ( mSignaledAnimations.find(ANIM_AGENT_WALK) != mSignaledAnimations.end()) + { + F32 vpD = velDir * primDir; + if (vpD < -0.5f) + { + velDir *= -1.0f; + } + } + LLVector3 fwdDir = lerp(primDir, velDir, clamp_rescale(speed, 0.5f, 2.0f, 0.0f, 1.0f)); + if (isSelf() && gAgentCamera.cameraMouselook()) + { + // make sure fwdDir stays in same general direction as primdir + if (gAgent.getFlying()) + { + fwdDir = LLViewerCamera::getInstance()->getAtAxis(); + } + else + { + LLVector3 at_axis = LLViewerCamera::getInstance()->getAtAxis(); + LLVector3 up_vector = gAgent.getReferenceUpVector(); + at_axis -= up_vector * (at_axis * up_vector); + at_axis.normalize(); + + F32 dot = fwdDir * at_axis; + if (dot < 0.f) + { + fwdDir -= 2.f * at_axis * dot; + fwdDir.normalize(); + } + } + } + + LLQuaternion root_rotation = mRoot->getWorldMatrix().quaternion(); + F32 root_roll, root_pitch, root_yaw; + root_rotation.getEulerAngles(&root_roll, &root_pitch, &root_yaw); + + // When moving very slow, the pelvis is allowed to deviate from the + // forward direction to allow it to hold its position while the torso + // and head turn. Once in motion, it must conform however. + bool self_in_mouselook = isSelf() && gAgentCamera.cameraMouselook(); + + LLVector3 pelvisDir( mRoot->getWorldMatrix().getFwdRow4().mV ); + + const F32 AVATAR_PELVIS_ROTATE_THRESHOLD_SLOW = 60.0f; + const F32 AVATAR_PELVIS_ROTATE_THRESHOLD_FAST = 2.0f; + + F32 pelvis_rot_threshold = clamp_rescale(speed, 0.1f, 1.0f, AVATAR_PELVIS_ROTATE_THRESHOLD_SLOW, AVATAR_PELVIS_ROTATE_THRESHOLD_FAST); + + if (self_in_mouselook) + { + pelvis_rot_threshold *= MOUSELOOK_PELVIS_FOLLOW_FACTOR; + } + pelvis_rot_threshold *= DEG_TO_RAD; + + F32 angle = angle_between( pelvisDir, fwdDir ); + + // The avatar's root is allowed to have a yaw that deviates widely + // from the forward direction, but if roll or pitch are off even + // a little bit we need to correct the rotation. + if(root_roll < 1.f * DEG_TO_RAD + && root_pitch < 5.f * DEG_TO_RAD) + { + // smaller correction vector means pelvis follows prim direction more closely + if (!mTurning && angle > pelvis_rot_threshold*0.75f) + { + mTurning = true; + } + + // use tighter threshold when turning + if (mTurning) + { + pelvis_rot_threshold *= 0.4f; + // account for fps, assume that above value is for ~60fps + constexpr F32 default_frame_sec = 0.016f; + F32 prev_frame_sec = LLFrameTimer::getFrameDeltaTimeF32(); + if (default_frame_sec > prev_frame_sec) + { + // reduce threshold since turn rate per second is constant, + // shorter frame means shorter turn. + pelvis_rot_threshold *= prev_frame_sec/default_frame_sec; + } + } + + // am I done turning? + if (angle < pelvis_rot_threshold) + { + mTurning = false; + } + + LLVector3 correction_vector = (pelvisDir - fwdDir) * clamp_rescale(angle, pelvis_rot_threshold*0.75f, pelvis_rot_threshold, 1.0f, 0.0f); + fwdDir += correction_vector; + } + else + { + mTurning = false; + } + + // Now compute the full world space rotation for the whole body (wQv) + LLVector3 leftDir = upDir % fwdDir; + leftDir.normalize(); + fwdDir = leftDir % upDir; + LLQuaternion wQv( fwdDir, leftDir, upDir ); + + if (isSelf() && mTurning) + { + if ((fwdDir % pelvisDir) * upDir > 0.f) + { + gAgent.setControlFlags(AGENT_CONTROL_TURN_RIGHT); + } + else + { + gAgent.setControlFlags(AGENT_CONTROL_TURN_LEFT); + } + } + + // Set the root rotation, but do so incrementally so that it + // lags in time by some fixed amount. + //F32 u = LLSmoothInterpolation::getInterpolant(PELVIS_LAG); + F32 pelvis_lag_time = 0.f; + if (self_in_mouselook) + { + pelvis_lag_time = PELVIS_LAG_MOUSELOOK; + } + else if (mInAir) + { + pelvis_lag_time = PELVIS_LAG_FLYING; + // increase pelvis lag time when moving slowly + pelvis_lag_time *= clamp_rescale(mSpeedAccum, 0.f, 15.f, 3.f, 1.f); + } + else + { + pelvis_lag_time = PELVIS_LAG_WALKING; + } + + F32 u = llclamp((delta_time / pelvis_lag_time), 0.0f, 1.0f); + + mRoot->setWorldRotation( slerp(u, mRoot->getWorldRotation(), wQv) ); +} + +//------------------------------------------------------------------------ +// updateTimeStep() +// Factored out from updateCharacter(). +// +// Updates the time step used by the motion controller, based on area +// and avatar count criteria. This will also stop the +// ANIM_AGENT_WALK_ADJUST animation under some circumstances. +// ------------------------------------------------------------------------ +void LLVOAvatar::updateTimeStep() +{ + if (!isSelf() && !isUIAvatar()) // ie, non-self avatars, and animated objects will be affected. + { + // Note that sInstances counts animated objects and + // standard avatars in the same bucket. Is this desirable? + F32 time_quantum = clamp_rescale((F32)sInstances.size(), 10.f, 35.f, 0.f, 0.25f); + F32 pixel_area_scale = clamp_rescale(mPixelArea, 100, 5000, 1.f, 0.f); + F32 time_step = time_quantum * pixel_area_scale; + // Extrema: + // If number of avs is 10 or less, time_step is unmodified (flagged with 0.0). + // If area of av is 5000 or greater, time_step is unmodified (flagged with 0.0). + // If number of avs is 35 or greater, and area of av is 100 or less, + // time_step takes the maximum possible value of 0.25. + // Other situations will give values within the (0, 0.25) range. + if (time_step != 0.f) + { + // disable walk motion servo controller as it doesn't work with motion timesteps + stopMotion(ANIM_AGENT_WALK_ADJUST); + removeAnimationData("Walk Speed"); + } + // See SL-763 - playback with altered time step does not + // appear to work correctly, odd behavior for distant avatars. + // As of 11-2017, LLMotionController::updateMotions() will + // ignore the value here. Need to re-enable if it's every + // fixed. + mMotionController.setTimeStep(time_step); + } +} + +void LLVOAvatar::updateRootPositionAndRotation(LLAgent& agent, F32 speed, bool was_sit_ground_constrained) +{ + if (!(isSitting() && getParent())) + { + // This case includes all configurations except sitting on an + // object, so does include ground sit. + + //-------------------------------------------------------------------- + // get timing info + // handle initial condition case + //-------------------------------------------------------------------- + F32 animation_time = mAnimTimer.getElapsedTimeF32(); + if (mTimeLast == 0.0f) + { + mTimeLast = animation_time; + + // Initially put the pelvis at slaved position/mRotation + // SL-315 + mRoot->setWorldPosition( getPositionAgent() ); // first frame + mRoot->setWorldRotation( getRotation() ); + } + + //-------------------------------------------------------------------- + // dont' let dT get larger than 1/5th of a second + //-------------------------------------------------------------------- + F32 delta_time = animation_time - mTimeLast; + + delta_time = llclamp( delta_time, DELTA_TIME_MIN, DELTA_TIME_MAX ); + mTimeLast = animation_time; + + mSpeedAccum = (mSpeedAccum * 0.95f) + (speed * 0.05f); + + //-------------------------------------------------------------------- + // compute the position of the avatar's root + //-------------------------------------------------------------------- + LLVector3d root_pos; + LLVector3d ground_under_pelvis; + + if (isSelf()) + { + gAgent.setPositionAgent(getRenderPosition()); + } + + root_pos = gAgent.getPosGlobalFromAgent(getRenderPosition()); + root_pos.mdV[VZ] += getVisualParamWeight(AVATAR_HOVER); + + LLVector3 normal; + resolveHeightGlobal(root_pos, ground_under_pelvis, normal); + F32 foot_to_ground = (F32) (root_pos.mdV[VZ] - mPelvisToFoot - ground_under_pelvis.mdV[VZ]); + bool in_air = ((!LLWorld::getInstance()->getRegionFromPosGlobal(ground_under_pelvis)) || + foot_to_ground > FOOT_GROUND_COLLISION_TOLERANCE); + + if (in_air && !mInAir) + { + mTimeInAir.reset(); + } + mInAir = in_air; + + // SL-402: with the ability to animate the position of joints + // that affect the body size calculation, computed body size + // can get stale much more easily. Simplest fix is to update + // it frequently. + // SL-427: this appears to be too frequent, moving to only do on animation state change. + //computeBodySize(); + + // correct for the fact that the pelvis is not necessarily the center + // of the agent's physical representation + root_pos.mdV[VZ] -= (0.5f * mBodySize.mV[VZ]) - mPelvisToFoot; + if (!isSitting() && !was_sit_ground_constrained) + { + root_pos += LLVector3d(getHoverOffset()); + if (getOverallAppearance() == AOA_JELLYDOLL) + { + F32 offz = -0.5 * (getScale()[VZ] - mBodySize.mV[VZ]); + root_pos[2] += offz; + // if (!isSelf() && !isControlAvatar()) + // { + // LL_DEBUGS("Avatar") << "av " << getFullname() + // << " frame " << LLFrameTimer::getFrameCount() + // << " root adjust offz " << offz + // << " scalez " << getScale()[VZ] + // << " bsz " << mBodySize.mV[VZ] + // << LL_ENDL; + // } + } + } + // if (!isSelf() && !isControlAvatar()) + // { + // LL_DEBUGS("Avatar") << "av " << getFullname() << " aoa " << (S32) getOverallAppearance() + // << " frame " << LLFrameTimer::getFrameCount() + // << " scalez " << getScale()[VZ] + // << " bsz " << mBodySize.mV[VZ] + // << " root pos " << root_pos[2] + // << " curr rootz " << mRoot->getPosition()[2] + // << " pp-z " << mPelvisp->getPosition()[2] + // << " renderpos " << getRenderPosition() + // << LL_ENDL; + // } + + LLControlAvatar *cav = dynamic_cast(this); + if (cav) + { + // SL-1350: Moved to LLDrawable::updateXform() + cav->matchVolumeTransform(); + } + else + { + LLVector3 newPosition = gAgent.getPosAgentFromGlobal(root_pos); + // if (!isSelf() && !isControlAvatar()) + // { + // LL_DEBUGS("Avatar") << "av " << getFullname() + // << " frame " << LLFrameTimer::getFrameCount() + // << " newPosition " << newPosition + // << " renderpos " << getRenderPosition() + // << LL_ENDL; + // } + if (newPosition != mRoot->getXform()->getWorldPosition()) + { + mRoot->touch(); + // SL-315 + mRoot->setWorldPosition( newPosition ); // regular update + } + } + + //-------------------------------------------------------------------- + // Propagate viewer object rotation to root of avatar + //-------------------------------------------------------------------- + if (!isControlAvatar() && !isAnyAnimationSignaled(AGENT_NO_ROTATE_ANIMS, NUM_AGENT_NO_ROTATE_ANIMS)) + { + // Rotation fixups for avatars in motion. + // Skip for animated objects. + updateOrientation(agent, speed, delta_time); + } + } + else if (mDrawable.notNull()) + { + // Sitting on an object - mRoot is slaved to mDrawable orientation. + LLVector3 pos = mDrawable->getPosition(); + pos += getHoverOffset() * mDrawable->getRotation(); + // SL-315 + mRoot->setPosition(pos); + mRoot->setRotation(mDrawable->getRotation()); + } +} + +//------------------------------------------------------------------------ +// LLVOAvatar::computeNeedsUpdate() +// +// Most of the logic here is to figure out when to periodically update impostors. +// Non-impostors have mUpdatePeriod == 1 and will need update every frame. +//------------------------------------------------------------------------ +bool LLVOAvatar::computeNeedsUpdate() +{ + const F32 MAX_IMPOSTOR_INTERVAL = 4.0f; + computeUpdatePeriod(); + + bool needs_update_by_frame_count = ((LLDrawable::getCurrentFrame()+mID.mData[0])%mUpdatePeriod == 0); + + bool needs_update_by_max_time = ((gFrameTimeSeconds-mLastImpostorUpdateFrameTime)> MAX_IMPOSTOR_INTERVAL); + bool needs_update = needs_update_by_frame_count || needs_update_by_max_time; + + if (needs_update && !isSelf()) + { + if (needs_update_by_max_time) + { + mNeedsImpostorUpdate = true; + mLastImpostorUpdateReason = 11; + } + else + { + //mNeedsImpostorUpdate = true; + //mLastImpostorUpdateReason = 10; + } + } + return needs_update; +} + +// updateCharacter() +// +// This is called for all avatars, so there are 4 possible situations: +// +// 1) Avatar is your own. In this case the class is LLVOAvatarSelf, +// isSelf() is true, and agent specifies the corresponding agent +// information for you. In all the other cases, agent is irrelevant +// and it would be less confusing if it were null or something. +// +// 2) Avatar is controlled by another resident. Class is LLVOAvatar, +// and isSelf() is false. +// +// 3) Avatar is the controller for an animated object. Class is +// LLControlAvatar and mIsDummy is true. Avatar is a purely +// viewer-side entity with no representation on the simulator. +// +// 4) Avatar is a UI avatar used in some areas of the UI, such as when +// previewing uploaded animations. Class is LLUIAvatar, and mIsDummy +// is true. Avatar is purely viewer-side with no representation on the +// simulator. +// +//------------------------------------------------------------------------ +bool LLVOAvatar::updateCharacter(LLAgent &agent) +{ + updateDebugText(); + + if (!mIsBuilt) + { + return false; + } + + bool visible = isVisible(); + bool is_control_avatar = isControlAvatar(); // capture state to simplify tracing + bool is_attachment = false; + + if (is_control_avatar) + { + LLControlAvatar *cav = dynamic_cast(this); + is_attachment = cav && cav->mRootVolp && cav->mRootVolp->isAttachment(); // For attached animated objects + } + + LLScopedContextString str("updateCharacter " + getFullname() + " is_control_avatar " + + boost::lexical_cast(is_control_avatar) + + " is_attachment " + boost::lexical_cast(is_attachment)); + + // For fading out the names above heads, only let the timer + // run if we're visible. + if (mDrawable.notNull() && !visible) + { + mTimeVisible.reset(); + } + + //-------------------------------------------------------------------- + // The rest should only be done occasionally for far away avatars. + // Set mUpdatePeriod and visible based on distance and other criteria, + // and flag for impostor update if needed. + //-------------------------------------------------------------------- + bool needs_update = computeNeedsUpdate(); + + //-------------------------------------------------------------------- + // Early out if does not need update and not self + // don't early out for your own avatar, as we rely on your animations playing reliably + // for example, the "turn around" animation when entering customize avatar needs to trigger + // even when your avatar is offscreen + //-------------------------------------------------------------------- + if (!needs_update && !isSelf()) + { + updateMotions(LLCharacter::HIDDEN_UPDATE); + return false; + } + + //-------------------------------------------------------------------- + // Handle transitions between regular rendering, jellydoll, or invisible. + // Can trigger skeleton reset or animation changes + //-------------------------------------------------------------------- + updateOverallAppearance(); + + //-------------------------------------------------------------------- + // change animation time quanta based on avatar render load + //-------------------------------------------------------------------- + // SL-763 the time step quantization does not currently work. + //updateTimeStep(); + + //-------------------------------------------------------------------- + // Update sitting state based on parent and active animation info. + //-------------------------------------------------------------------- + if (getParent() && !isSitting()) + { + sitOnObject((LLViewerObject*)getParent()); + } + else if (!getParent() && isSitting() && !isMotionActive(ANIM_AGENT_SIT_GROUND_CONSTRAINED)) + { + // If we are starting up, motion might be loading + LLMotion *motionp = mMotionController.findMotion(ANIM_AGENT_SIT_GROUND_CONSTRAINED); + if (!motionp || !mMotionController.isMotionLoading(motionp)) + { + getOffObject(); + } + } + + //-------------------------------------------------------------------- + // create local variables in world coords for region position values + //-------------------------------------------------------------------- + LLVector3 xyVel = getVelocity(); + xyVel.mV[VZ] = 0.0f; + F32 speed = xyVel.length(); + // remembering the value here prevents a display glitch if the + // animation gets toggled during this update. + bool was_sit_ground_constrained = isMotionActive(ANIM_AGENT_SIT_GROUND_CONSTRAINED); + + //-------------------------------------------------------------------- + // This does a bunch of state updating, including figuring out + // whether av is in the air, setting mRoot position and rotation + // In some cases, calls updateOrientation() for a lot of the + // work + // -------------------------------------------------------------------- + updateRootPositionAndRotation(agent, speed, was_sit_ground_constrained); + + //------------------------------------------------------------------------- + // Update character motions + //------------------------------------------------------------------------- + // store data relevant to motions + mSpeed = speed; + + // update animations + if (!visible) + { + updateMotions(LLCharacter::HIDDEN_UPDATE); + } + else if (mSpecialRenderMode == 1) // Animation Preview + { + updateMotions(LLCharacter::FORCE_UPDATE); + } + else + { + // Might be better to do HIDDEN_UPDATE if cloud + updateMotions(LLCharacter::NORMAL_UPDATE); + } + + // Special handling for sitting on ground. + if (!getParent() && (isSitting() || was_sit_ground_constrained)) + { + + F32 off_z = LLVector3d(getHoverOffset()).mdV[VZ]; + if (off_z != 0.0) + { + LLVector3 pos = mRoot->getWorldPosition(); + pos.mV[VZ] += off_z; + mRoot->touch(); + // SL-315 + mRoot->setWorldPosition(pos); + } + } + + // update head position + updateHeadOffset(); + + // Generate footstep sounds when feet hit the ground + updateFootstepSounds(); + + // Update child joints as needed. + mRoot->updateWorldMatrixChildren(); + + if (visible) + { + // System avatar mesh vertices need to be reskinned. + mNeedsSkin = true; + } + + return visible; +} + +//----------------------------------------------------------------------------- +// updateHeadOffset() +//----------------------------------------------------------------------------- +void LLVOAvatar::updateHeadOffset() +{ + // since we only care about Z, just grab one of the eyes + LLVector3 midEyePt = mEyeLeftp->getWorldPosition(); + midEyePt -= mDrawable.notNull() ? mDrawable->getWorldPosition() : mRoot->getWorldPosition(); + midEyePt.mV[VZ] = llmax(-mPelvisToFoot + LLViewerCamera::getInstance()->getNear(), midEyePt.mV[VZ]); + + if (mDrawable.notNull()) + { + midEyePt = midEyePt * ~mDrawable->getWorldRotation(); + } + if (isSitting()) + { + mHeadOffset = midEyePt; + } + else + { + F32 u = llmax(0.f, HEAD_MOVEMENT_AVG_TIME - (1.f / gFPSClamped)); + mHeadOffset = lerp(midEyePt, mHeadOffset, u); + } +} + +void LLVOAvatar::debugBodySize() const +{ + LLVector3 pelvis_scale = mPelvisp->getScale(); + + // some of the joints have not been cached + LLVector3 skull = mSkullp->getPosition(); + LL_DEBUGS("Avatar") << "skull pos " << skull << LL_ENDL; + //LLVector3 skull_scale = mSkullp->getScale(); + + LLVector3 neck = mNeckp->getPosition(); + LLVector3 neck_scale = mNeckp->getScale(); + LL_DEBUGS("Avatar") << "neck pos " << neck << " neck_scale " << neck_scale << LL_ENDL; + + LLVector3 chest = mChestp->getPosition(); + LLVector3 chest_scale = mChestp->getScale(); + LL_DEBUGS("Avatar") << "chest pos " << chest << " chest_scale " << chest_scale << LL_ENDL; + + // the rest of the joints have been cached + LLVector3 head = mHeadp->getPosition(); + LLVector3 head_scale = mHeadp->getScale(); + LL_DEBUGS("Avatar") << "head pos " << head << " head_scale " << head_scale << LL_ENDL; + + LLVector3 torso = mTorsop->getPosition(); + LLVector3 torso_scale = mTorsop->getScale(); + LL_DEBUGS("Avatar") << "torso pos " << torso << " torso_scale " << torso_scale << LL_ENDL; + + LLVector3 hip = mHipLeftp->getPosition(); + LLVector3 hip_scale = mHipLeftp->getScale(); + LL_DEBUGS("Avatar") << "hip pos " << hip << " hip_scale " << hip_scale << LL_ENDL; + + LLVector3 knee = mKneeLeftp->getPosition(); + LLVector3 knee_scale = mKneeLeftp->getScale(); + LL_DEBUGS("Avatar") << "knee pos " << knee << " knee_scale " << knee_scale << LL_ENDL; + + LLVector3 ankle = mAnkleLeftp->getPosition(); + LLVector3 ankle_scale = mAnkleLeftp->getScale(); + LL_DEBUGS("Avatar") << "ankle pos " << ankle << " ankle_scale " << ankle_scale << LL_ENDL; + + LLVector3 foot = mFootLeftp->getPosition(); + LL_DEBUGS("Avatar") << "foot pos " << foot << LL_ENDL; + + F32 new_offset = (const_cast(this))->getVisualParamWeight(AVATAR_HOVER); + LL_DEBUGS("Avatar") << "new_offset " << new_offset << LL_ENDL; + + F32 new_pelvis_to_foot = hip.mV[VZ] * pelvis_scale.mV[VZ] - + knee.mV[VZ] * hip_scale.mV[VZ] - + ankle.mV[VZ] * knee_scale.mV[VZ] - + foot.mV[VZ] * ankle_scale.mV[VZ]; + LL_DEBUGS("Avatar") << "new_pelvis_to_foot " << new_pelvis_to_foot << LL_ENDL; + + LLVector3 new_body_size; + new_body_size.mV[VZ] = new_pelvis_to_foot + + // the sqrt(2) correction below is an approximate + // correction to get to the top of the head + F_SQRT2 * (skull.mV[VZ] * head_scale.mV[VZ]) + + head.mV[VZ] * neck_scale.mV[VZ] + + neck.mV[VZ] * chest_scale.mV[VZ] + + chest.mV[VZ] * torso_scale.mV[VZ] + + torso.mV[VZ] * pelvis_scale.mV[VZ]; + + // TODO -- measure the real depth and width + new_body_size.mV[VX] = DEFAULT_AGENT_DEPTH; + new_body_size.mV[VY] = DEFAULT_AGENT_WIDTH; + + LL_DEBUGS("Avatar") << "new_body_size " << new_body_size << LL_ENDL; +} + +//------------------------------------------------------------------------ +// postPelvisSetRecalc +//------------------------------------------------------------------------ +void LLVOAvatar::postPelvisSetRecalc() +{ + mRoot->updateWorldMatrixChildren(); + computeBodySize(); + dirtyMesh(2); +} +//------------------------------------------------------------------------ +// updateVisibility() +//------------------------------------------------------------------------ +void LLVOAvatar::updateVisibility() +{ + bool visible = false; + + if (mIsDummy) + { + visible = false; + } + else if (mDrawable.isNull()) + { + visible = false; + } + else + { + if (!mDrawable->getSpatialGroup() || mDrawable->getSpatialGroup()->isVisible()) + { + visible = true; + } + else + { + visible = false; + } + + if(isSelf()) + { + if (!gAgentWearables.areWearablesLoaded()) + { + visible = false; + } + } + else if( !mFirstAppearanceMessageReceived ) + { + visible = false; + } + + if (sDebugInvisible) + { + LLNameValue* firstname = getNVPair("FirstName"); + if (firstname) + { + LL_DEBUGS("Avatar") << avString() << " updating visibility" << LL_ENDL; + } + else + { + LL_INFOS() << "Avatar " << this << " updating visiblity" << LL_ENDL; + } + + if (visible) + { + LL_INFOS() << "Visible" << LL_ENDL; + } + else + { + LL_INFOS() << "Not visible" << LL_ENDL; + } + + /*if (avatar_in_frustum) + { + LL_INFOS() << "Avatar in frustum" << LL_ENDL; + } + else + { + LL_INFOS() << "Avatar not in frustum" << LL_ENDL; + }*/ + + /*if (LLViewerCamera::getInstance()->sphereInFrustum(sel_pos_agent, 2.0f)) + { + LL_INFOS() << "Sel pos visible" << LL_ENDL; + } + if (LLViewerCamera::getInstance()->sphereInFrustum(wrist_right_pos_agent, 0.2f)) + { + LL_INFOS() << "Wrist pos visible" << LL_ENDL; + } + if (LLViewerCamera::getInstance()->sphereInFrustum(getPositionAgent(), getMaxScale()*2.f)) + { + LL_INFOS() << "Agent visible" << LL_ENDL; + }*/ + LL_INFOS() << "PA: " << getPositionAgent() << LL_ENDL; + /*LL_INFOS() << "SPA: " << sel_pos_agent << LL_ENDL; + LL_INFOS() << "WPA: " << wrist_right_pos_agent << LL_ENDL;*/ + for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + if (LLViewerObject *attached_object = attachment_iter->get()) + { + if(attached_object->mDrawable->isVisible()) + { + LL_INFOS() << attachment->getName() << " visible" << LL_ENDL; + } + else + { + LL_INFOS() << attachment->getName() << " not visible at " << mDrawable->getWorldPosition() << " and radius " << mDrawable->getRadius() << LL_ENDL; + } + } + } + } + } + } + + if (!visible && mVisible) + { + mMeshInvisibleTime.reset(); + } + + if (visible) + { + if (!mMeshValid) + { + restoreMeshData(); + } + } + else + { + if (mMeshValid && + (isControlAvatar() || mMeshInvisibleTime.getElapsedTimeF32() > TIME_BEFORE_MESH_CLEANUP)) + { + releaseMeshData(); + } + } + + if ( visible != mVisible ) + { + LL_DEBUGS("AvatarRender") << "visible was " << mVisible << " now " << visible << LL_ENDL; + } + mVisible = visible; +} + +// private +bool LLVOAvatar::shouldAlphaMask() +{ + const bool should_alpha_mask = !LLDrawPoolAlpha::sShowDebugAlpha // Don't alpha mask if "Highlight Transparent" checked + && !LLDrawPoolAvatar::sSkipTransparent; + + return should_alpha_mask; + +} + +//----------------------------------------------------------------------------- +// renderSkinned() +//----------------------------------------------------------------------------- +U32 LLVOAvatar::renderSkinned() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + + U32 num_indices = 0; + + if (!mIsBuilt) + { + return num_indices; + } + + if (mDrawable.isNull()) + { + return num_indices; + } + + LLFace* face = mDrawable->getFace(0); + + bool needs_rebuild = !face || !face->getVertexBuffer() || mDrawable->isState(LLDrawable::REBUILD_GEOMETRY); + + if (needs_rebuild || mDirtyMesh) + { //LOD changed or new mesh created, allocate new vertex buffer if needed + if (needs_rebuild || mDirtyMesh >= 2 || mVisibilityRank <= 4) + { + updateMeshData(); + mDirtyMesh = 0; + mNeedsSkin = true; + mDrawable->clearState(LLDrawable::REBUILD_GEOMETRY); + } + } + + if (LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_AVATAR) <= 0) + { + if (mNeedsSkin) + { + //generate animated mesh + LLViewerJoint* lower_mesh = getViewerJoint(MESH_ID_LOWER_BODY); + LLViewerJoint* upper_mesh = getViewerJoint(MESH_ID_UPPER_BODY); + LLViewerJoint* skirt_mesh = getViewerJoint(MESH_ID_SKIRT); + LLViewerJoint* eyelash_mesh = getViewerJoint(MESH_ID_EYELASH); + LLViewerJoint* head_mesh = getViewerJoint(MESH_ID_HEAD); + LLViewerJoint* hair_mesh = getViewerJoint(MESH_ID_HAIR); + + if(upper_mesh) + { + upper_mesh->updateJointGeometry(); + } + if (lower_mesh) + { + lower_mesh->updateJointGeometry(); + } + + if( isWearingWearableType( LLWearableType::WT_SKIRT ) ) + { + if(skirt_mesh) + { + skirt_mesh->updateJointGeometry(); + } + } + + if (!isSelf() || gAgent.needsRenderHead() || LLPipeline::sShadowRender) + { + if(eyelash_mesh) + { + eyelash_mesh->updateJointGeometry(); + } + if(head_mesh) + { + head_mesh->updateJointGeometry(); + } + if(hair_mesh) + { + hair_mesh->updateJointGeometry(); + } + } + mNeedsSkin = false; + mLastSkinTime = gFrameTimeSeconds; + + LLFace * face = mDrawable->getFace(0); + if (face) + { + LLVertexBuffer* vb = face->getVertexBuffer(); + if (vb) + { + vb->unmapBuffer(); + } + } + } + } + else + { + mNeedsSkin = false; + } + + if (sDebugInvisible) + { + LLNameValue* firstname = getNVPair("FirstName"); + if (firstname) + { + LL_DEBUGS("Avatar") << avString() << " in render" << LL_ENDL; + } + else + { + LL_INFOS() << "Avatar " << this << " in render" << LL_ENDL; + } + if (!mIsBuilt) + { + LL_INFOS() << "Not built!" << LL_ENDL; + } + else if (!gAgent.needsRenderAvatar()) + { + LL_INFOS() << "Doesn't need avatar render!" << LL_ENDL; + } + else + { + LL_INFOS() << "Rendering!" << LL_ENDL; + } + } + + if (!mIsBuilt) + { + return num_indices; + } + + if (isSelf() && !gAgent.needsRenderAvatar()) + { + return num_indices; + } + + //-------------------------------------------------------------------- + // render all geometry attached to the skeleton + //-------------------------------------------------------------------- + + bool first_pass = true; + if (!LLDrawPoolAvatar::sSkipOpaque) + { + if (isUIAvatar() && mIsDummy) + { + LLViewerJoint* hair_mesh = getViewerJoint(MESH_ID_HAIR); + if (hair_mesh) + { + num_indices += hair_mesh->render(mAdjustedPixelArea, first_pass, mIsDummy); + } + first_pass = false; + } + if (!isSelf() || gAgent.needsRenderHead() || LLPipeline::sShadowRender) + { + + if (isTextureVisible(TEX_HEAD_BAKED) || (getOverallAppearance() == AOA_JELLYDOLL && !isControlAvatar()) || isUIAvatar()) + { + LLViewerJoint* head_mesh = getViewerJoint(MESH_ID_HEAD); + if (head_mesh) + { + num_indices += head_mesh->render(mAdjustedPixelArea, first_pass, mIsDummy); + } + first_pass = false; + } + } + if (isTextureVisible(TEX_UPPER_BAKED) || (getOverallAppearance() == AOA_JELLYDOLL && !isControlAvatar()) || isUIAvatar()) + { + LLViewerJoint* upper_mesh = getViewerJoint(MESH_ID_UPPER_BODY); + if (upper_mesh) + { + num_indices += upper_mesh->render(mAdjustedPixelArea, first_pass, mIsDummy); + } + first_pass = false; + } + + if (isTextureVisible(TEX_LOWER_BAKED) || (getOverallAppearance() == AOA_JELLYDOLL && !isControlAvatar()) || isUIAvatar()) + { + LLViewerJoint* lower_mesh = getViewerJoint(MESH_ID_LOWER_BODY); + if (lower_mesh) + { + num_indices += lower_mesh->render(mAdjustedPixelArea, first_pass, mIsDummy); + } + first_pass = false; + } + } + + if (!LLDrawPoolAvatar::sSkipTransparent || LLPipeline::sImpostorRender) + { + LLGLState blend(GL_BLEND, !mIsDummy); + num_indices += renderTransparent(first_pass); + } + + return num_indices; +} + +U32 LLVOAvatar::renderTransparent(bool first_pass) +{ + U32 num_indices = 0; + if( isWearingWearableType( LLWearableType::WT_SKIRT ) && (isUIAvatar() || isTextureVisible(TEX_SKIRT_BAKED)) ) + { + gGL.flush(); + LLViewerJoint* skirt_mesh = getViewerJoint(MESH_ID_SKIRT); + if (skirt_mesh) + { + num_indices += skirt_mesh->render(mAdjustedPixelArea, false); + } + first_pass = false; + gGL.flush(); + } + + if (!isSelf() || gAgent.needsRenderHead() || LLPipeline::sShadowRender) + { + if (LLPipeline::sImpostorRender) + { + gGL.flush(); + } + + if (isTextureVisible(TEX_HEAD_BAKED)) + { + LLViewerJoint* eyelash_mesh = getViewerJoint(MESH_ID_EYELASH); + if (eyelash_mesh) + { + num_indices += eyelash_mesh->render(mAdjustedPixelArea, first_pass, mIsDummy); + } + first_pass = false; + } + if (isTextureVisible(TEX_HAIR_BAKED) && (getOverallAppearance() != AOA_JELLYDOLL)) + { + LLViewerJoint* hair_mesh = getViewerJoint(MESH_ID_HAIR); + if (hair_mesh) + { + num_indices += hair_mesh->render(mAdjustedPixelArea, first_pass, mIsDummy); + } + first_pass = false; + } + if (LLPipeline::sImpostorRender) + { + gGL.flush(); + } + } + + return num_indices; +} + +//----------------------------------------------------------------------------- +// renderRigid() +//----------------------------------------------------------------------------- +U32 LLVOAvatar::renderRigid() +{ + U32 num_indices = 0; + + if (!mIsBuilt) + { + return 0; + } + + if (isSelf() && (!gAgent.needsRenderAvatar() || !gAgent.needsRenderHead())) + { + return 0; + } + + bool should_alpha_mask = shouldAlphaMask(); + LLGLState test(GL_ALPHA_TEST, should_alpha_mask); + + if (isTextureVisible(TEX_EYES_BAKED) || (getOverallAppearance() == AOA_JELLYDOLL && !isControlAvatar()) || isUIAvatar()) + { + LLViewerJoint* eyeball_left = getViewerJoint(MESH_ID_EYEBALL_LEFT); + LLViewerJoint* eyeball_right = getViewerJoint(MESH_ID_EYEBALL_RIGHT); + if (eyeball_left) + { + num_indices += eyeball_left->render(mAdjustedPixelArea, true, mIsDummy); + } + if(eyeball_right) + { + num_indices += eyeball_right->render(mAdjustedPixelArea, true, mIsDummy); + } + } + + return num_indices; +} + +U32 LLVOAvatar::renderImpostor(LLColor4U color, S32 diffuse_channel) +{ + if (!mImpostor.isComplete()) + { + return 0; + } + + LLVector3 pos(getRenderPosition()+mImpostorOffset); + LLVector3 at = (pos - LLViewerCamera::getInstance()->getOrigin()); + at.normalize(); + LLVector3 left = LLViewerCamera::getInstance()->getUpAxis() % at; + LLVector3 up = at%left; + + left *= mImpostorDim.mV[0]; + up *= mImpostorDim.mV[1]; + + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_IMPOSTORS)) + { + LLGLEnable blend(GL_BLEND); + gGL.setSceneBlendType(LLRender::BT_ADD); + gGL.getTexUnit(diffuse_channel)->unbind(LLTexUnit::TT_TEXTURE); + + // gGL.begin(LLRender::QUADS); + // gGL.vertex3fv((pos+left-up).mV); + // gGL.vertex3fv((pos-left-up).mV); + // gGL.vertex3fv((pos-left+up).mV); + // gGL.vertex3fv((pos+left+up).mV); + // gGL.end(); + + + gGL.begin(LLRender::LINES); + gGL.color4f(1.f,1.f,1.f,1.f); + F32 thickness = llmax(F32(5.0f-5.0f*(gFrameTimeSeconds-mLastImpostorUpdateFrameTime)),1.0f); + glLineWidth(thickness); + gGL.vertex3fv((pos+left-up).mV); + gGL.vertex3fv((pos-left-up).mV); + gGL.vertex3fv((pos-left-up).mV); + gGL.vertex3fv((pos-left+up).mV); + gGL.vertex3fv((pos-left+up).mV); + gGL.vertex3fv((pos+left+up).mV); + gGL.vertex3fv((pos+left+up).mV); + gGL.vertex3fv((pos+left-up).mV); + gGL.end(); + gGL.flush(); + } + { + gGL.flush(); + + gGL.color4ubv(color.mV); + gGL.getTexUnit(diffuse_channel)->bind(&mImpostor); + gGL.begin(LLRender::QUADS); + gGL.texCoord2f(0,0); + gGL.vertex3fv((pos+left-up).mV); + gGL.texCoord2f(1,0); + gGL.vertex3fv((pos-left-up).mV); + gGL.texCoord2f(1,1); + gGL.vertex3fv((pos-left+up).mV); + gGL.texCoord2f(0,1); + gGL.vertex3fv((pos+left+up).mV); + gGL.end(); + gGL.flush(); + } + + return 6; +} + +bool LLVOAvatar::allTexturesCompletelyDownloaded(std::set& ids) const +{ + for (std::set::const_iterator it = ids.begin(); it != ids.end(); ++it) + { + LLViewerFetchedTexture *imagep = gTextureList.findImage(*it, TEX_LIST_STANDARD); + if (imagep && imagep->getDiscardLevel()!=0) + { + return false; + } + } + return true; +} + +bool LLVOAvatar::allLocalTexturesCompletelyDownloaded() const +{ + std::set local_ids; + collectLocalTextureUUIDs(local_ids); + return allTexturesCompletelyDownloaded(local_ids); +} + +bool LLVOAvatar::allBakedTexturesCompletelyDownloaded() const +{ + std::set baked_ids; + collectBakedTextureUUIDs(baked_ids); + return allTexturesCompletelyDownloaded(baked_ids); +} + +std::string LLVOAvatar::bakedTextureOriginInfo() +{ + std::string result; + + std::set baked_ids; + collectBakedTextureUUIDs(baked_ids); + for (U32 i = 0; i < mBakedTextureDatas.size(); i++) + { + ETextureIndex texture_index = mBakedTextureDatas[i].mTextureIndex; + LLViewerFetchedTexture *imagep = + LLViewerTextureManager::staticCastToFetchedTexture(getImage(texture_index,0), true); + if (!imagep || + imagep->getID() == IMG_DEFAULT || + imagep->getID() == IMG_DEFAULT_AVATAR) + + { + result += "-"; + } + else + { + bool has_url = false, has_host = false; + if (!imagep->getUrl().empty()) + { + has_url = true; + } + if (imagep->getTargetHost().isOk()) + { + has_host = true; + } + S32 discard = imagep->getDiscardLevel(); + if (has_url && !has_host) result += discard ? "u" : "U"; // server-bake texture with url + else if (has_host && !has_url) result += discard ? "h" : "H"; // old-style texture on sim + else if (has_host && has_url) result += discard ? "x" : "X"; // both origins? + else if (!has_host && !has_url) result += discard ? "n" : "N"; // no origin? + if (discard != 0) + { + result += llformat("(%d/%d)",discard,imagep->getDesiredDiscardLevel()); + } + } + + } + return result; +} + +S32Bytes LLVOAvatar::totalTextureMemForUUIDS(std::set& ids) +{ + S32Bytes result(0); + for (std::set::const_iterator it = ids.begin(); it != ids.end(); ++it) + { + LLViewerFetchedTexture *imagep = gTextureList.findImage(*it, TEX_LIST_STANDARD); + if (imagep) + { + result += imagep->getTextureMemory(); + } + } + return result; +} + +void LLVOAvatar::collectLocalTextureUUIDs(std::set& ids) const +{ + for (U32 texture_index = 0; texture_index < getNumTEs(); texture_index++) + { + LLWearableType::EType wearable_type = LLAvatarAppearance::getDictionary()->getTEWearableType((ETextureIndex)texture_index); + U32 num_wearables = gAgentWearables.getWearableCount(wearable_type); + + LLViewerFetchedTexture *imagep = NULL; + for (U32 wearable_index = 0; wearable_index < num_wearables; wearable_index++) + { + imagep = LLViewerTextureManager::staticCastToFetchedTexture(getImage(texture_index, wearable_index), true); + if (imagep) + { + const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = LLAvatarAppearance::getDictionary()->getTexture((ETextureIndex)texture_index); + if (texture_dict && texture_dict->mIsLocalTexture) + { + ids.insert(imagep->getID()); + } + } + } + } + ids.erase(IMG_DEFAULT); + ids.erase(IMG_DEFAULT_AVATAR); + ids.erase(IMG_INVISIBLE); +} + +void LLVOAvatar::collectBakedTextureUUIDs(std::set& ids) const +{ + for (U32 texture_index = 0; texture_index < getNumTEs(); texture_index++) + { + LLViewerFetchedTexture *imagep = NULL; + if (isIndexBakedTexture((ETextureIndex) texture_index)) + { + imagep = LLViewerTextureManager::staticCastToFetchedTexture(getImage(texture_index,0), true); + if (imagep) + { + ids.insert(imagep->getID()); + } + } + } + ids.erase(IMG_DEFAULT); + ids.erase(IMG_DEFAULT_AVATAR); + ids.erase(IMG_INVISIBLE); +} + +void LLVOAvatar::collectTextureUUIDs(std::set& ids) +{ + collectLocalTextureUUIDs(ids); + collectBakedTextureUUIDs(ids); +} + +void LLVOAvatar::releaseOldTextures() +{ + S32Bytes current_texture_mem; + + // Any textures that we used to be using but are no longer using should no longer be flagged as "NO_DELETE" + std::set baked_texture_ids; + collectBakedTextureUUIDs(baked_texture_ids); + S32Bytes new_baked_mem = totalTextureMemForUUIDS(baked_texture_ids); + + std::set local_texture_ids; + collectLocalTextureUUIDs(local_texture_ids); + //S32 new_local_mem = totalTextureMemForUUIDS(local_texture_ids); + + std::set new_texture_ids; + new_texture_ids.insert(baked_texture_ids.begin(),baked_texture_ids.end()); + new_texture_ids.insert(local_texture_ids.begin(),local_texture_ids.end()); + S32Bytes new_total_mem = totalTextureMemForUUIDS(new_texture_ids); + + //S32 old_total_mem = totalTextureMemForUUIDS(mTextureIDs); + //LL_DEBUGS("Avatar") << getFullname() << " old_total_mem: " << old_total_mem << " new_total_mem (L/B): " << new_total_mem << " (" << new_local_mem <<", " << new_baked_mem << ")" << LL_ENDL; + if (!isSelf() && new_total_mem > new_baked_mem) + { + LL_WARNS() << "extra local textures stored for non-self av" << LL_ENDL; + } + for (std::set::iterator it = mTextureIDs.begin(); it != mTextureIDs.end(); ++it) + { + if (new_texture_ids.find(*it) == new_texture_ids.end()) + { + LLViewerFetchedTexture *imagep = gTextureList.findImage(*it, TEX_LIST_STANDARD); + if (imagep) + { + current_texture_mem += imagep->getTextureMemory(); + if (imagep->getTextureState() == LLGLTexture::NO_DELETE) + { + // This will allow the texture to be deleted if not in use. + imagep->forceActive(); + + // This resets the clock to texture being flagged + // as unused, preventing the texture from being + // deleted immediately. If other avatars or + // objects are using it, it can still be flagged + // no-delete by them. + imagep->forceUpdateBindStats(); + } + } + } + } + mTextureIDs = new_texture_ids; +} + +void LLVOAvatar::updateTextures() +{ + releaseOldTextures(); + + bool render_avatar = true; + + if (mIsDummy) + { + return; + } + + if( isSelf() ) + { + render_avatar = true; + } + else + { + if(!isVisible()) + { + return ;//do not update for invisible avatar. + } + + render_avatar = !mCulled; //visible and not culled. + } + + std::vector layer_baked; + // GL NOT ACTIVE HERE - *TODO + for (U32 i = 0; i < mBakedTextureDatas.size(); i++) + { + layer_baked.push_back(isTextureDefined(mBakedTextureDatas[i].mTextureIndex)); + // bind the texture so that they'll be decoded slightly + // inefficient, we can short-circuit this if we have to + if (render_avatar && !gGLManager.mIsDisabled) + { + if (layer_baked[i] && !mBakedTextureDatas[i].mIsLoaded) + { + gGL.getTexUnit(0)->bind(getImage( mBakedTextureDatas[i].mTextureIndex, 0 )); + } + } + } + + mMaxPixelArea = 0.f; + mMinPixelArea = 99999999.f; + mHasGrey = false; // debug + for (U32 texture_index = 0; texture_index < getNumTEs(); texture_index++) + { + LLWearableType::EType wearable_type = LLAvatarAppearance::getDictionary()->getTEWearableType((ETextureIndex)texture_index); + U32 num_wearables = gAgentWearables.getWearableCount(wearable_type); + const LLTextureEntry *te = getTE(texture_index); + + // getTE can return 0. + // Not sure yet why it does, but of course it crashes when te->mScale? gets used. + // Put safeguard in place so this corner case get better handling and does not result in a crash. + F32 texel_area_ratio = 1.0f; + if( te ) + { + texel_area_ratio = fabs(te->mScaleS * te->mScaleT); + } + else + { + LL_WARNS() << "getTE( " << texture_index << " ) returned 0" <getTexture((ETextureIndex)texture_index); + const EBakedTextureIndex baked_index = texture_dict ? texture_dict->mBakedTextureIndex : EBakedTextureIndex::BAKED_NUM_INDICES; + if (texture_dict && texture_dict->mIsLocalTexture) + { + addLocalTextureStats((ETextureIndex)texture_index, imagep, texel_area_ratio, render_avatar, mBakedTextureDatas[baked_index].mIsUsed); + } + } + } + if (isIndexBakedTexture((ETextureIndex) texture_index) && render_avatar) + { + const S32 boost_level = getAvatarBakedBoostLevel(); + imagep = LLViewerTextureManager::staticCastToFetchedTexture(getImage(texture_index,0), true); + addBakedTextureStats( imagep, mPixelArea, texel_area_ratio, boost_level ); + } + } + + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_AREA)) + { + setDebugText(llformat("%4.0f:%4.0f", (F32) sqrt(mMinPixelArea),(F32) sqrt(mMaxPixelArea))); + } +} + + +void LLVOAvatar::addLocalTextureStats( ETextureIndex idx, LLViewerFetchedTexture* imagep, + F32 texel_area_ratio, bool render_avatar, bool covered_by_baked) +{ + // No local texture stats for non-self avatars + return; +} + +const S32 MAX_TEXTURE_UPDATE_INTERVAL = 64 ; //need to call updateTextures() at least every 32 frames. +const S32 MAX_TEXTURE_VIRTUAL_SIZE_RESET_INTERVAL = S32_MAX ; //frames +void LLVOAvatar::checkTextureLoading() +{ + static const F32 MAX_INVISIBLE_WAITING_TIME = 15.f ; //seconds + + bool pause = !isVisible() ; + if(!pause) + { + mInvisibleTimer.reset() ; + } + if(mLoadedCallbacksPaused == pause) + { + if (!pause && mFirstFullyVisible && mLoadedCallbackTextures < mCallbackTextureList.size()) + { + // We still need to update 'loaded' textures count to decide on 'cloud' visibility + // Alternatively this can be done on TextureLoaded callbacks, but is harder to properly track + mLoadedCallbackTextures = 0; + for (LLLoadedCallbackEntry::source_callback_list_t::iterator iter = mCallbackTextureList.begin(); + iter != mCallbackTextureList.end(); ++iter) + { + LLViewerFetchedTexture* tex = gTextureList.findImage(*iter); + if (tex && (tex->getDiscardLevel() >= 0 || tex->isMissingAsset())) + { + mLoadedCallbackTextures++; + } + } + } + return ; + } + + if(mCallbackTextureList.empty()) //when is self or no callbacks. Note: this list for self is always empty. + { + mLoadedCallbacksPaused = pause ; + mLoadedCallbackTextures = 0; + return ; //nothing to check. + } + + if(pause && mInvisibleTimer.getElapsedTimeF32() < MAX_INVISIBLE_WAITING_TIME) + { + return ; //have not been invisible for enough time. + } + + mLoadedCallbackTextures = pause ? mCallbackTextureList.size() : 0; + + for(LLLoadedCallbackEntry::source_callback_list_t::iterator iter = mCallbackTextureList.begin(); + iter != mCallbackTextureList.end(); ++iter) + { + LLViewerFetchedTexture* tex = gTextureList.findImage(*iter) ; + if(tex) + { + if(pause)//pause texture fetching. + { + tex->pauseLoadedCallbacks(&mCallbackTextureList) ; + + //set to terminate texture fetching after MAX_TEXTURE_UPDATE_INTERVAL frames. + tex->setMaxVirtualSizeResetInterval(MAX_TEXTURE_UPDATE_INTERVAL); + tex->resetMaxVirtualSizeResetCounter() ; + } + else//unpause + { + static const F32 START_AREA = 100.f ; + + tex->unpauseLoadedCallbacks(&mCallbackTextureList) ; + tex->addTextureStats(START_AREA); //jump start the fetching again + + // technically shouldn't need to account for missing, but callback might not have happened yet + if (tex->getDiscardLevel() >= 0 || tex->isMissingAsset()) + { + mLoadedCallbackTextures++; // consider it loaded (we have at least some data) + } + } + } + } + + if(!pause) + { + updateTextures() ; //refresh texture stats. + } + mLoadedCallbacksPaused = pause ; + return ; +} + +const F32 SELF_ADDITIONAL_PRI = 0.75f ; +void LLVOAvatar::addBakedTextureStats( LLViewerFetchedTexture* imagep, F32 pixel_area, F32 texel_area_ratio, S32 boost_level) +{ + //Note: + //if this function is not called for the last MAX_TEXTURE_VIRTUAL_SIZE_RESET_INTERVAL frames, + //the texture pipeline will stop fetching this texture. + + imagep->resetTextureStats(); + imagep->setMaxVirtualSizeResetInterval(MAX_TEXTURE_VIRTUAL_SIZE_RESET_INTERVAL); + imagep->resetMaxVirtualSizeResetCounter() ; + + mMaxPixelArea = llmax(pixel_area, mMaxPixelArea); + mMinPixelArea = llmin(pixel_area, mMinPixelArea); + imagep->addTextureStats(pixel_area / texel_area_ratio); + imagep->setBoostLevel(boost_level); +} + +//virtual +void LLVOAvatar::setImage(const U8 te, LLViewerTexture *imagep, const U32 index) +{ + setTEImage(te, imagep); +} + +//virtual +LLViewerTexture* LLVOAvatar::getImage(const U8 te, const U32 index) const +{ + return getTEImage(te); +} +//virtual +const LLTextureEntry* LLVOAvatar::getTexEntry(const U8 te_num) const +{ + return getTE(te_num); +} + +//virtual +void LLVOAvatar::setTexEntry(const U8 index, const LLTextureEntry &te) +{ + setTE(index, te); +} + +const std::string LLVOAvatar::getImageURL(const U8 te, const LLUUID &uuid) +{ + llassert(isIndexBakedTexture(ETextureIndex(te))); + std::string url = ""; + const std::string& appearance_service_url = LLAppearanceMgr::instance().getAppearanceServiceURL(); + if (appearance_service_url.empty()) + { + // Probably a server-side issue if we get here: + LL_WARNS() << "AgentAppearanceServiceURL not set - Baked texture requests will fail" << LL_ENDL; + return url; + } + + const LLAvatarAppearanceDictionary::TextureEntry* texture_entry = LLAvatarAppearance::getDictionary()->getTexture((ETextureIndex)te); + if (texture_entry != NULL) + { + url = appearance_service_url + "texture/" + getID().asString() + "/" + texture_entry->mDefaultImageName + "/" + uuid.asString(); + //LL_INFOS() << "baked texture url: " << url << LL_ENDL; + } + return url; +} + +//----------------------------------------------------------------------------- +// resolveHeight() +//----------------------------------------------------------------------------- + +void LLVOAvatar::resolveHeightAgent(const LLVector3 &in_pos_agent, LLVector3 &out_pos_agent, LLVector3 &out_norm) +{ + LLVector3d in_pos_global, out_pos_global; + + in_pos_global = gAgent.getPosGlobalFromAgent(in_pos_agent); + resolveHeightGlobal(in_pos_global, out_pos_global, out_norm); + out_pos_agent = gAgent.getPosAgentFromGlobal(out_pos_global); +} + + +void LLVOAvatar::resolveRayCollisionAgent(const LLVector3d start_pt, const LLVector3d end_pt, LLVector3d &out_pos, LLVector3 &out_norm) +{ + LLViewerObject *obj; + LLWorld::getInstance()->resolveStepHeightGlobal(this, start_pt, end_pt, out_pos, out_norm, &obj); +} + +void LLVOAvatar::resolveHeightGlobal(const LLVector3d &inPos, LLVector3d &outPos, LLVector3 &outNorm) +{ + LLVector3d zVec(0.0f, 0.0f, 0.5f); + LLVector3d p0 = inPos + zVec; + LLVector3d p1 = inPos - zVec; + LLViewerObject *obj; + LLWorld::getInstance()->resolveStepHeightGlobal(this, p0, p1, outPos, outNorm, &obj); + if (!obj) + { + mStepOnLand = true; + mStepMaterial = 0; + mStepObjectVelocity.setVec(0.0f, 0.0f, 0.0f); + } + else + { + mStepOnLand = false; + mStepMaterial = obj->getMaterial(); + + // We want the primitive velocity, not our velocity... (which actually subtracts the + // step object velocity) + LLVector3 angularVelocity = obj->getAngularVelocity(); + LLVector3 relativePos = gAgent.getPosAgentFromGlobal(outPos) - obj->getPositionAgent(); + + LLVector3 linearComponent = angularVelocity % relativePos; +// LL_INFOS() << "Linear Component of Rotation Velocity " << linearComponent << LL_ENDL; + mStepObjectVelocity = obj->getVelocity() + linearComponent; + } +} + + +//----------------------------------------------------------------------------- +// getStepSound() +//----------------------------------------------------------------------------- +const LLUUID& LLVOAvatar::getStepSound() const +{ + if ( mStepOnLand ) + { + return sStepSoundOnLand; + } + + return sStepSounds[mStepMaterial]; +} + + +//----------------------------------------------------------------------------- +// processAnimationStateChanges() +//----------------------------------------------------------------------------- +void LLVOAvatar::processAnimationStateChanges() +{ + if ( isAnyAnimationSignaled(AGENT_WALK_ANIMS, NUM_AGENT_WALK_ANIMS) ) + { + startMotion(ANIM_AGENT_WALK_ADJUST); + stopMotion(ANIM_AGENT_FLY_ADJUST); + } + else if (mInAir && !isSitting()) + { + stopMotion(ANIM_AGENT_WALK_ADJUST); + if (mEnableDefaultMotions) + { + startMotion(ANIM_AGENT_FLY_ADJUST); + } + } + else + { + stopMotion(ANIM_AGENT_WALK_ADJUST); + stopMotion(ANIM_AGENT_FLY_ADJUST); + } + + if ( isAnyAnimationSignaled(AGENT_GUN_AIM_ANIMS, NUM_AGENT_GUN_AIM_ANIMS) ) + { + if (mEnableDefaultMotions) + { + startMotion(ANIM_AGENT_TARGET); + } + stopMotion(ANIM_AGENT_BODY_NOISE); + } + else + { + stopMotion(ANIM_AGENT_TARGET); + if (mEnableDefaultMotions) + { + startMotion(ANIM_AGENT_BODY_NOISE); + } + } + + // clear all current animations + AnimIterator anim_it; + for (anim_it = mPlayingAnimations.begin(); anim_it != mPlayingAnimations.end();) + { + AnimIterator found_anim = mSignaledAnimations.find(anim_it->first); + + // playing, but not signaled, so stop + if (found_anim == mSignaledAnimations.end()) + { + processSingleAnimationStateChange(anim_it->first, false); + mPlayingAnimations.erase(anim_it++); + continue; + } + + ++anim_it; + } + + // if jellydolled, shelve all playing animations + if (getOverallAppearance() != AOA_NORMAL) + { + mPlayingAnimations.clear(); + } + + // start up all new anims + if (getOverallAppearance() == AOA_NORMAL) + { + for (anim_it = mSignaledAnimations.begin(); anim_it != mSignaledAnimations.end();) + { + AnimIterator found_anim = mPlayingAnimations.find(anim_it->first); + + // signaled but not playing, or different sequence id, start motion + if (found_anim == mPlayingAnimations.end() || found_anim->second != anim_it->second) + { + if (processSingleAnimationStateChange(anim_it->first, true)) + { + mPlayingAnimations[anim_it->first] = anim_it->second; + ++anim_it; + continue; + } + } + + ++anim_it; + } + } + + // clear source information for animations which have been stopped + if (isSelf()) + { + AnimSourceIterator source_it = mAnimationSources.begin(); + + for (source_it = mAnimationSources.begin(); source_it != mAnimationSources.end();) + { + if (mSignaledAnimations.find(source_it->second) == mSignaledAnimations.end()) + { + mAnimationSources.erase(source_it++); + } + else + { + ++source_it; + } + } + } + + stop_glerror(); +} + + +//----------------------------------------------------------------------------- +// processSingleAnimationStateChange(); +//----------------------------------------------------------------------------- +bool LLVOAvatar::processSingleAnimationStateChange( const LLUUID& anim_id, bool start ) +{ + // SL-402, SL-427 - we need to update body size often enough to + // keep appearances in sync, but not so often that animations + // cause constant jiggling of the body or camera. Possible + // compromise is to do it on animation changes: + computeBodySize(); + + bool result = false; + + if ( start ) // start animation + { + if (anim_id == ANIM_AGENT_TYPE) + { + if (gAudiop) + { + LLVector3d char_pos_global = gAgent.getPosGlobalFromAgent(getCharacterPosition()); + if (LLViewerParcelMgr::getInstance()->canHearSound(char_pos_global) + && !LLMuteList::getInstance()->isMuted(getID(), LLMute::flagObjectSounds)) + { + // RN: uncomment this to play on typing sound at fixed volume once sound engine is fixed + // to support both spatialized and non-spatialized instances of the same sound + //if (isSelf()) + //{ + // gAudiop->triggerSound(LLUUID(gSavedSettings.getString("UISndTyping")), 1.0f, LLAudioEngine::AUDIO_TYPE_UI); + //} + //else + { + static LLCachedControl ui_snd_string(gSavedSettings, "UISndTyping"); + LLUUID sound_id = LLUUID(ui_snd_string); + gAudiop->triggerSound(sound_id, getID(), 1.0f, LLAudioEngine::AUDIO_TYPE_SFX, char_pos_global); + } + } + } + } + else if (anim_id == ANIM_AGENT_SIT_GROUND_CONSTRAINED) + { + sitDown(true); + } + + + if (startMotion(anim_id)) + { + result = true; + } + else + { + LL_WARNS("Motion") << "Failed to start motion!" << LL_ENDL; + } + } + else //stop animation + { + if (anim_id == ANIM_AGENT_SIT_GROUND_CONSTRAINED) + { + sitDown(false); + } + if ((anim_id == ANIM_AGENT_DO_NOT_DISTURB) && gAgent.isDoNotDisturb()) + { + // re-assert DND tag animation + gAgent.sendAnimationRequest(ANIM_AGENT_DO_NOT_DISTURB, ANIM_REQUEST_START); + return result; + } + stopMotion(anim_id); + result = true; + } + + return result; +} + +//----------------------------------------------------------------------------- +// isAnyAnimationSignaled() +//----------------------------------------------------------------------------- +bool LLVOAvatar::isAnyAnimationSignaled(const LLUUID *anim_array, const S32 num_anims) const +{ + for (S32 i = 0; i < num_anims; i++) + { + if(mSignaledAnimations.find(anim_array[i]) != mSignaledAnimations.end()) + { + return true; + } + } + return false; +} + +//----------------------------------------------------------------------------- +// resetAnimations() +//----------------------------------------------------------------------------- +void LLVOAvatar::resetAnimations() +{ + LLKeyframeMotion::flushKeyframeCache(); + flushAllMotions(); +} + +// Override selectively based on avatar sex and whether we're using new +// animations. +LLUUID LLVOAvatar::remapMotionID(const LLUUID& id) +{ + static LLCachedControl use_new_walk_run(gSavedSettings, "UseNewWalkRun"); + LLUUID result = id; + + // start special case female walk for female avatars + if (getSex() == SEX_FEMALE) + { + if (id == ANIM_AGENT_WALK) + { + if (use_new_walk_run) + result = ANIM_AGENT_FEMALE_WALK_NEW; + else + result = ANIM_AGENT_FEMALE_WALK; + } + else if (id == ANIM_AGENT_RUN) + { + // There is no old female run animation, so only override + // in one case. + if (use_new_walk_run) + result = ANIM_AGENT_FEMALE_RUN_NEW; + } + else if (id == ANIM_AGENT_SIT) + { + result = ANIM_AGENT_SIT_FEMALE; + } + } + else + { + // Male avatar. + if (id == ANIM_AGENT_WALK) + { + if (use_new_walk_run) + result = ANIM_AGENT_WALK_NEW; + } + else if (id == ANIM_AGENT_RUN) + { + if (use_new_walk_run) + result = ANIM_AGENT_RUN_NEW; + } + // keeps in sync with setSex() related code (viewer controls sit's sex) + else if (id == ANIM_AGENT_SIT_FEMALE) + { + result = ANIM_AGENT_SIT; + } + + } + + return result; + +} + +//----------------------------------------------------------------------------- +// startMotion() +// id is the asset if of the animation to start +// time_offset is the offset into the animation at which to start playing +//----------------------------------------------------------------------------- +bool LLVOAvatar::startMotion(const LLUUID& id, F32 time_offset) +{ + LL_DEBUGS("Motion") << "motion requested " << id.asString() << " " << gAnimLibrary.animationName(id) << LL_ENDL; + + LLUUID remap_id = remapMotionID(id); + + if (remap_id != id) + { + LL_DEBUGS("Motion") << "motion resultant " << remap_id.asString() << " " << gAnimLibrary.animationName(remap_id) << LL_ENDL; + } + + if (isSelf() && remap_id == ANIM_AGENT_AWAY) + { + gAgent.setAFK(); + } + + return LLCharacter::startMotion(remap_id, time_offset); +} + +//----------------------------------------------------------------------------- +// stopMotion() +//----------------------------------------------------------------------------- +bool LLVOAvatar::stopMotion(const LLUUID& id, bool stop_immediate) +{ + LL_DEBUGS("Motion") << "Motion requested " << id.asString() << " " << gAnimLibrary.animationName(id) << LL_ENDL; + + LLUUID remap_id = remapMotionID(id); + + if (remap_id != id) + { + LL_DEBUGS("Motion") << "motion resultant " << remap_id.asString() << " " << gAnimLibrary.animationName(remap_id) << LL_ENDL; + } + + if (isSelf()) + { + gAgent.onAnimStop(remap_id); + } + + return LLCharacter::stopMotion(remap_id, stop_immediate); +} + +//----------------------------------------------------------------------------- +// hasMotionFromSource() +//----------------------------------------------------------------------------- +// virtual +bool LLVOAvatar::hasMotionFromSource(const LLUUID& source_id) +{ + return false; +} + +//----------------------------------------------------------------------------- +// stopMotionFromSource() +//----------------------------------------------------------------------------- +// virtual +void LLVOAvatar::stopMotionFromSource(const LLUUID& source_id) +{ +} + +//----------------------------------------------------------------------------- +// addDebugText() +//----------------------------------------------------------------------------- +void LLVOAvatar::addDebugText(const std::string& text) +{ + mDebugText.append(1, '\n'); + mDebugText.append(text); +} + +//----------------------------------------------------------------------------- +// getID() +//----------------------------------------------------------------------------- +const LLUUID& LLVOAvatar::getID() const +{ + return mID; +} + +//----------------------------------------------------------------------------- +// getJoint() +//----------------------------------------------------------------------------- +// RN: avatar joints are multi-rooted to include screen-based attachments +LLJoint *LLVOAvatar::getJoint( const std::string &name ) +{ + joint_map_t::iterator iter = mJointMap.find(name); + + LLJoint* jointp = NULL; + + if (iter == mJointMap.end() || iter->second == NULL) + { //search for joint and cache found joint in lookup table + if (mJointAliasMap.empty()) + { + getJointAliases(); + } + joint_alias_map_t::const_iterator alias_iter = mJointAliasMap.find(name); + std::string canonical_name; + if (alias_iter != mJointAliasMap.end()) + { + canonical_name = alias_iter->second; + } + else + { + canonical_name = name; + } + jointp = mRoot->findJoint(canonical_name); + mJointMap[name] = jointp; + } + else + { //return cached pointer + jointp = iter->second; + } + +#ifndef LL_RELEASE_FOR_DOWNLOAD + if (jointp && jointp->getName()!="mScreen" && jointp->getName()!="mRoot") + { + llassert(getJoint(jointp->getJointNum())==jointp); + } +#endif + return jointp; +} + +LLJoint *LLVOAvatar::getJoint( S32 joint_num ) +{ + LLJoint *pJoint = NULL; + if (joint_num >= 0) + { + if (joint_num < mNumBones) + { + pJoint = mSkeleton[joint_num]; + } + else if (joint_num < mNumBones + mNumCollisionVolumes) + { + S32 collision_id = joint_num - mNumBones; + pJoint = &mCollisionVolumes[collision_id]; + } + else + { + // Attachment IDs start at 1 + S32 attachment_id = joint_num - (mNumBones + mNumCollisionVolumes) + 1; + attachment_map_t::iterator iter = mAttachmentPoints.find(attachment_id); + if (iter != mAttachmentPoints.end()) + { + pJoint = iter->second; + } + } + } + + llassert(!pJoint || pJoint->getJointNum() == joint_num); + return pJoint; +} + +//----------------------------------------------------------------------------- +// getRiggedMeshID +// +// If viewer object is a rigged mesh, set the mesh id and return true. +// Otherwise, null out the id and return false. +//----------------------------------------------------------------------------- +// static +bool LLVOAvatar::getRiggedMeshID(LLViewerObject* pVO, LLUUID& mesh_id) +{ + mesh_id.setNull(); + + //If a VO has a skin that we'll reset the joint positions to their default + if ( pVO && pVO->mDrawable ) + { + LLVOVolume* pVObj = pVO->mDrawable->getVOVolume(); + if ( pVObj ) + { + const LLMeshSkinInfo* pSkinData = pVObj->getSkinInfo(); + if (pSkinData + && pSkinData->mJointNames.size() > JOINT_COUNT_REQUIRED_FOR_FULLRIG // full rig + && pSkinData->mAlternateBindMatrix.size() > 0 ) + { + mesh_id = pSkinData->mMeshID; + return true; + } + } + } + return false; +} + +bool LLVOAvatar::jointIsRiggedTo(const LLJoint *joint) const +{ + if (joint) + { + const LLJointRiggingInfoTab& tab = mJointRiggingInfoTab; + S32 joint_num = joint->getJointNum(); + if (joint_num < tab.size() && tab[joint_num].isRiggedTo()) + { + return true; + } + } + return false; +} + +void LLVOAvatar::clearAttachmentOverrides() +{ + LLScopedContextString str("clearAttachmentOverrides " + getFullname()); + + for (S32 i=0; iclearAttachmentPosOverrides(); + pJoint->clearAttachmentScaleOverrides(); + } + } + + if (mPelvisFixups.count()>0) + { + mPelvisFixups.clear(); + LLJoint* pJointPelvis = getJoint("mPelvis"); + if (pJointPelvis) + { + pJointPelvis->setPosition( LLVector3( 0.0f, 0.0f, 0.0f) ); + } + postPelvisSetRecalc(); + } + + mActiveOverrideMeshes.clear(); + onActiveOverrideMeshesChanged(); +} + +//----------------------------------------------------------------------------- +// rebuildAttachmentOverrides +//----------------------------------------------------------------------------- +void LLVOAvatar::rebuildAttachmentOverrides() +{ + LLScopedContextString str("rebuildAttachmentOverrides " + getFullname()); + + LL_DEBUGS("AnimatedObjects") << "rebuilding" << LL_ENDL; + dumpStack("AnimatedObjectsStack"); + + clearAttachmentOverrides(); + + // Handle the case that we're resetting the skeleton of an animated object. + LLControlAvatar *control_av = dynamic_cast(this); + if (control_av) + { + LLVOVolume *volp = control_av->mRootVolp; + if (volp) + { + LL_DEBUGS("Avatar") << volp->getID() << " adding attachment overrides for root vol, prim count " + << (S32) (1+volp->numChildren()) << LL_ENDL; + addAttachmentOverridesForObject(volp); + } + } + + // Attached objects + for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment *attachment_pt = (*iter).second; + if (attachment_pt) + { + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator at_it = attachment_pt->mAttachedObjects.begin(); + at_it != attachment_pt->mAttachedObjects.end(); ++at_it) + { + LLViewerObject *vo = at_it->get(); + // Attached animated objects affect joints in their control + // avs, not the avs to which they are attached. + if (vo && !vo->isAnimatedObject()) + { + addAttachmentOverridesForObject(vo); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// updateAttachmentOverrides +// +// This is intended to give the same results as +// rebuildAttachmentOverrides(), while avoiding redundant work. +// ----------------------------------------------------------------------------- +void LLVOAvatar::updateAttachmentOverrides() +{ + LLScopedContextString str("updateAttachmentOverrides " + getFullname()); + + LL_DEBUGS("AnimatedObjects") << "updating" << LL_ENDL; + dumpStack("AnimatedObjectsStack"); + + std::set meshes_seen; + + // Handle the case that we're updating the skeleton of an animated object. + LLControlAvatar *control_av = dynamic_cast(this); + if (control_av) + { + LLVOVolume *volp = control_av->mRootVolp; + if (volp) + { + LL_DEBUGS("Avatar") << volp->getID() << " adding attachment overrides for root vol, prim count " + << (S32) (1+volp->numChildren()) << LL_ENDL; + addAttachmentOverridesForObject(volp, &meshes_seen); + } + } + + // Attached objects + for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment *attachment_pt = (*iter).second; + if (attachment_pt) + { + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator at_it = attachment_pt->mAttachedObjects.begin(); + at_it != attachment_pt->mAttachedObjects.end(); ++at_it) + { + LLViewerObject *vo = at_it->get(); + // Attached animated objects affect joints in their control + // avs, not the avs to which they are attached. + if (vo && !vo->isAnimatedObject()) + { + addAttachmentOverridesForObject(vo, &meshes_seen); + } + } + } + } + // Remove meshes that are no longer present on the skeleton + + // have to work with a copy because removeAttachmentOverrides() will change mActiveOverrideMeshes. + std::set active_override_meshes = mActiveOverrideMeshes; + for (std::set::iterator it = active_override_meshes.begin(); it != active_override_meshes.end(); ++it) + { + if (meshes_seen.find(*it) == meshes_seen.end()) + { + removeAttachmentOverridesForObject(*it); + } + } + + +#ifdef ATTACHMENT_OVERRIDE_VALIDATION + { + std::vector pos_overrides_by_joint; + std::vector scale_overrides_by_joint; + LLVector3OverrideMap pelvis_fixups; + + // Capture snapshot of override state after update + for (S32 joint_num = 0; joint_num < LL_CHARACTER_MAX_ANIMATED_JOINTS; joint_num++) + { + LLVector3OverrideMap pos_overrides; + LLJoint *joint = getJoint(joint_num); + if (joint) + { + pos_overrides_by_joint.push_back(joint->m_attachmentPosOverrides); + scale_overrides_by_joint.push_back(joint->m_attachmentScaleOverrides); + } + else + { + // No joint, use default constructed empty maps + pos_overrides_by_joint.push_back(LLVector3OverrideMap()); + scale_overrides_by_joint.push_back(LLVector3OverrideMap()); + } + } + pelvis_fixups = mPelvisFixups; + //dumpArchetypeXML(getFullname() + "_paranoid_updated"); + + // Rebuild and compare + rebuildAttachmentOverrides(); + //dumpArchetypeXML(getFullname() + "_paranoid_rebuilt"); + bool mismatched = false; + for (S32 joint_num = 0; joint_num < LL_CHARACTER_MAX_ANIMATED_JOINTS; joint_num++) + { + LLJoint *joint = getJoint(joint_num); + if (joint) + { + if (pos_overrides_by_joint[joint_num] != joint->m_attachmentPosOverrides) + { + mismatched = true; + } + if (scale_overrides_by_joint[joint_num] != joint->m_attachmentScaleOverrides) + { + mismatched = true; + } + } + } + if (pelvis_fixups != mPelvisFixups) + { + mismatched = true; + } + if (mismatched) + { + LL_WARNS() << "MISMATCHED ATTACHMENT OVERRIDES" << LL_ENDL; + } + } +#endif +} + +void LLVOAvatar::notifyAttachmentMeshLoaded() +{ + if (!isFullyLoaded()) + { + // We just received mesh or skin info + // Reset timer to wait for more potential meshes or changes + mFullyLoadedTimer.reset(); + } +} + +//----------------------------------------------------------------------------- +// addAttachmentOverridesForObject +//----------------------------------------------------------------------------- +void LLVOAvatar::addAttachmentOverridesForObject(LLViewerObject *vo, std::set* meshes_seen, bool recursive) +{ + if (vo->getAvatar() != this && vo->getAvatarAncestor() != this) + { + LL_WARNS("Avatar") << "called with invalid avatar" << LL_ENDL; + return; + } + + LLScopedContextString str("addAttachmentOverridesForObject " + getFullname()); + + if (getOverallAppearance() != AOA_NORMAL) + { + return; + } + + LL_DEBUGS("AnimatedObjects") << "adding" << LL_ENDL; + dumpStack("AnimatedObjectsStack"); + + // Process all children + if (recursive) + { + LLViewerObject::const_child_list_t& children = vo->getChildren(); + for (LLViewerObject::const_child_list_t::const_iterator it = children.begin(); + it != children.end(); ++it) + { + LLViewerObject *childp = *it; + addAttachmentOverridesForObject(childp, meshes_seen, true); + } + } + + LLVOVolume *vobj = dynamic_cast(vo); + bool pelvisGotSet = false; + + if (!vobj) + { + return; + } + + LLViewerObject *root_object = (LLViewerObject*)vobj->getRoot(); + LL_DEBUGS("AnimatedObjects") << "trying to add attachment overrides for root object " << root_object->getID() << " prim is " << vobj << LL_ENDL; + if (vobj->isMesh() && + ((vobj->getVolume() && !vobj->getVolume()->isMeshAssetLoaded()) || !gMeshRepo.meshRezEnabled())) + { + LL_DEBUGS("AnimatedObjects") << "failed to add attachment overrides for root object " << root_object->getID() << " mesh asset not loaded" << LL_ENDL; + return; + } + const LLMeshSkinInfo* pSkinData = vobj->getSkinInfo(); + + if ( vobj && vobj->isMesh() && pSkinData ) + { + const int bindCnt = pSkinData->mAlternateBindMatrix.size(); + const int jointCnt = pSkinData->mJointNames.size(); + if ((bindCnt > 0) && (bindCnt != jointCnt)) + { + LL_WARNS_ONCE() << "invalid mesh, bindCnt " << bindCnt << "!= jointCnt " << jointCnt << ", joint overrides will be ignored." << LL_ENDL; + } + if ((bindCnt > 0) && (bindCnt == jointCnt)) + { + const F32 pelvisZOffset = pSkinData->mPelvisOffset; + const LLUUID& mesh_id = pSkinData->mMeshID; + + if (meshes_seen) + { + meshes_seen->insert(mesh_id); + } + bool mesh_overrides_loaded = (mActiveOverrideMeshes.find(mesh_id) != mActiveOverrideMeshes.end()); + if (mesh_overrides_loaded) + { + LL_DEBUGS("AnimatedObjects") << "skipping add attachment overrides for " << mesh_id + << " to root object " << root_object->getID() + << ", already loaded" + << LL_ENDL; + } + else + { + LL_DEBUGS("AnimatedObjects") << "adding attachment overrides for " << mesh_id + << " to root object " << root_object->getID() << LL_ENDL; + } + bool fullRig = jointCnt>=JOINT_COUNT_REQUIRED_FOR_FULLRIG; + if ( fullRig && !mesh_overrides_loaded ) + { + for ( int i=0; imJointNames[i].c_str(); + LLJoint* pJoint = getJoint( lookingForJoint ); + if (pJoint) + { + const LLVector3& jointPos = LLVector3(pSkinData->mAlternateBindMatrix[i].getTranslation()); + if (pJoint->aboveJointPosThreshold(jointPos)) + { + bool override_changed; + pJoint->addAttachmentPosOverride( jointPos, mesh_id, avString(), override_changed ); + + if (override_changed) + { + //If joint is a pelvis then handle old/new pelvis to foot values + if ( lookingForJoint == "mPelvis" ) + { + pelvisGotSet = true; + } + } + if (pSkinData->mLockScaleIfJointPosition) + { + // Note that unlike positions, there's no threshold check here, + // just a lock at the default value. + pJoint->addAttachmentScaleOverride(pJoint->getDefaultScale(), mesh_id, avString()); + } + } + } + } + + if (pelvisZOffset != 0.0F) + { + F32 pelvis_fixup_before; + bool has_fixup_before = hasPelvisFixup(pelvis_fixup_before); + addPelvisFixup( pelvisZOffset, mesh_id ); + F32 pelvis_fixup_after; + hasPelvisFixup(pelvis_fixup_after); // Don't have to check bool here because we just added it... + if (!has_fixup_before || (pelvis_fixup_before != pelvis_fixup_after)) + { + pelvisGotSet = true; + } + + } + mActiveOverrideMeshes.insert(mesh_id); + onActiveOverrideMeshesChanged(); + } + } + } + else + { + LL_DEBUGS("AnimatedObjects") << "failed to add attachment overrides for root object " << root_object->getID() << " not mesh or no pSkinData" << LL_ENDL; + } + + //Rebuild body data if we altered joints/pelvis + if ( pelvisGotSet ) + { + postPelvisSetRecalc(); + } +} + +//----------------------------------------------------------------------------- +// getAttachmentOverrideNames +//----------------------------------------------------------------------------- +void LLVOAvatar::getAttachmentOverrideNames(std::set& pos_names, std::set& scale_names) const +{ + LLVector3 pos; + LLVector3 scale; + LLUUID mesh_id; + + // Bones + for (avatar_joint_list_t::const_iterator iter = mSkeleton.begin(); + iter != mSkeleton.end(); ++iter) + { + const LLJoint* pJoint = (*iter); + if (pJoint && pJoint->hasAttachmentPosOverride(pos,mesh_id)) + { + pos_names.insert(pJoint->getName()); + } + if (pJoint && pJoint->hasAttachmentScaleOverride(scale,mesh_id)) + { + scale_names.insert(pJoint->getName()); + } + } + + // Attachment points + for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + const LLViewerJointAttachment *attachment_pt = (*iter).second; + if (attachment_pt && attachment_pt->hasAttachmentPosOverride(pos,mesh_id)) + { + pos_names.insert(attachment_pt->getName()); + } + // Attachment points don't have scales. + } +} + +//----------------------------------------------------------------------------- +// showAttachmentOverrides +//----------------------------------------------------------------------------- +void LLVOAvatar::showAttachmentOverrides(bool verbose) const +{ + std::set pos_names, scale_names; + getAttachmentOverrideNames(pos_names, scale_names); + if (pos_names.size()) + { + std::stringstream ss; + std::copy(pos_names.begin(), pos_names.end(), std::ostream_iterator(ss, ",")); + LL_INFOS() << getFullname() << " attachment positions defined for joints: " << ss.str() << "\n" << LL_ENDL; + } + else + { + LL_DEBUGS("Avatar") << getFullname() << " no attachment positions defined for any joints" << "\n" << LL_ENDL; + } + + if (scale_names.size()) + { + std::stringstream ss; + std::copy(scale_names.begin(), scale_names.end(), std::ostream_iterator(ss, ",")); + LL_INFOS() << getFullname() << " attachment scales defined for joints: " << ss.str() << "\n" << LL_ENDL; + } + else + { + LL_INFOS() << getFullname() << " no attachment scales defined for any joints" << "\n" << LL_ENDL; + } + + if (!verbose) + { + return; + } + + LLVector3 pos, scale; + LLUUID mesh_id; + S32 count = 0; + + // Bones + for (avatar_joint_list_t::const_iterator iter = mSkeleton.begin(); + iter != mSkeleton.end(); ++iter) + { + const LLJoint* pJoint = (*iter); + if (pJoint && pJoint->hasAttachmentPosOverride(pos,mesh_id)) + { + pJoint->showAttachmentPosOverrides(getFullname()); + count++; + } + if (pJoint && pJoint->hasAttachmentScaleOverride(scale,mesh_id)) + { + pJoint->showAttachmentScaleOverrides(getFullname()); + count++; + } + } + + // Attachment points + for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + const LLViewerJointAttachment *attachment_pt = (*iter).second; + if (attachment_pt && attachment_pt->hasAttachmentPosOverride(pos,mesh_id)) + { + attachment_pt->showAttachmentPosOverrides(getFullname()); + count++; + } + } + + if (count) + { + LL_DEBUGS("Avatar") << avString() << " end of pos, scale overrides" << LL_ENDL; + LL_DEBUGS("Avatar") << "=================================" << LL_ENDL; + } +} + +//----------------------------------------------------------------------------- +// removeAttachmentOverridesForObject +//----------------------------------------------------------------------------- +void LLVOAvatar::removeAttachmentOverridesForObject(LLViewerObject *vo) +{ + if (vo->getAvatar() != this && vo->getAvatarAncestor() != this) + { + LL_WARNS("Avatar") << "called with invalid avatar" << LL_ENDL; + return; + } + + // Process all children + LLViewerObject::const_child_list_t& children = vo->getChildren(); + for (LLViewerObject::const_child_list_t::const_iterator it = children.begin(); + it != children.end(); ++it) + { + LLViewerObject *childp = *it; + removeAttachmentOverridesForObject(childp); + } + + // Process self. + LLUUID mesh_id; + if (getRiggedMeshID(vo,mesh_id)) + { + removeAttachmentOverridesForObject(mesh_id); + } +} + +//----------------------------------------------------------------------------- +// removeAttachmentOverridesForObject +//----------------------------------------------------------------------------- +void LLVOAvatar::removeAttachmentOverridesForObject(const LLUUID& mesh_id) +{ + LLJoint* pJointPelvis = getJoint("mPelvis"); + const std::string av_string = avString(); + for (S32 joint_num = 0; joint_num < LL_CHARACTER_MAX_ANIMATED_JOINTS; joint_num++) + { + LLJoint *pJoint = getJoint(joint_num); + if (pJoint) + { + bool dummy; // unused + pJoint->removeAttachmentPosOverride(mesh_id, av_string, dummy); + pJoint->removeAttachmentScaleOverride(mesh_id, av_string); + } + if (pJoint && pJoint == pJointPelvis) + { + removePelvisFixup(mesh_id); + // SL-315 + pJoint->setPosition(LLVector3( 0.0f, 0.0f, 0.0f)); + } + } + + postPelvisSetRecalc(); + + mActiveOverrideMeshes.erase(mesh_id); + onActiveOverrideMeshesChanged(); +} + +//----------------------------------------------------------------------------- +// getCharacterPosition() +//----------------------------------------------------------------------------- +LLVector3 LLVOAvatar::getCharacterPosition() +{ + if (mDrawable.notNull()) + { + return mDrawable->getPositionAgent(); + } + else + { + return getPositionAgent(); + } +} + +//----------------------------------------------------------------------------- +// LLVOAvatar::getCharacterRotation() +//----------------------------------------------------------------------------- +LLQuaternion LLVOAvatar::getCharacterRotation() +{ + return getRotation(); +} + +//----------------------------------------------------------------------------- +// LLVOAvatar::getCharacterVelocity() +//----------------------------------------------------------------------------- +LLVector3 LLVOAvatar::getCharacterVelocity() +{ + return getVelocity() - mStepObjectVelocity; +} + +//----------------------------------------------------------------------------- +// LLVOAvatar::getCharacterAngularVelocity() +//----------------------------------------------------------------------------- +LLVector3 LLVOAvatar::getCharacterAngularVelocity() +{ + return getAngularVelocity(); +} + +//----------------------------------------------------------------------------- +// LLVOAvatar::getGround() +//----------------------------------------------------------------------------- +void LLVOAvatar::getGround(const LLVector3 &in_pos_agent, LLVector3 &out_pos_agent, LLVector3 &outNorm) +{ + LLVector3d z_vec(0.0f, 0.0f, 1.0f); + LLVector3d p0_global, p1_global; + + if (isUIAvatar()) + { + outNorm.setVec(z_vec); + out_pos_agent = in_pos_agent; + return; + } + + p0_global = gAgent.getPosGlobalFromAgent(in_pos_agent) + z_vec; + p1_global = gAgent.getPosGlobalFromAgent(in_pos_agent) - z_vec; + LLViewerObject *obj; + LLVector3d out_pos_global; + LLWorld::getInstance()->resolveStepHeightGlobal(this, p0_global, p1_global, out_pos_global, outNorm, &obj); + out_pos_agent = gAgent.getPosAgentFromGlobal(out_pos_global); +} + +//----------------------------------------------------------------------------- +// LLVOAvatar::getTimeDilation() +//----------------------------------------------------------------------------- +F32 LLVOAvatar::getTimeDilation() +{ + return mRegionp ? mRegionp->getTimeDilation() : 1.f; +} + +//----------------------------------------------------------------------------- +// LLVOAvatar::getPixelArea() +//----------------------------------------------------------------------------- +F32 LLVOAvatar::getPixelArea() const +{ + if (isUIAvatar()) + { + return 100000.f; + } + return mPixelArea; +} + +//----------------------------------------------------------------------------- +// LLVOAvatar::getPosGlobalFromAgent() +//----------------------------------------------------------------------------- +LLVector3d LLVOAvatar::getPosGlobalFromAgent(const LLVector3 &position) +{ + return gAgent.getPosGlobalFromAgent(position); +} + +//----------------------------------------------------------------------------- +// getPosAgentFromGlobal() +//----------------------------------------------------------------------------- +LLVector3 LLVOAvatar::getPosAgentFromGlobal(const LLVector3d &position) +{ + return gAgent.getPosAgentFromGlobal(position); +} + +//----------------------------------------------------------------------------- +// requestStopMotion() +//----------------------------------------------------------------------------- +// virtual +void LLVOAvatar::requestStopMotion( LLMotion* motion ) +{ + // Only agent avatars should handle the stop motion notifications. +} + +//----------------------------------------------------------------------------- +// loadSkeletonNode(): loads node from XML tree +//----------------------------------------------------------------------------- +//virtual +bool LLVOAvatar::loadSkeletonNode () +{ + if (!LLAvatarAppearance::loadSkeletonNode()) + { + return false; + } + + bool ignore_hud_joints = false; + initAttachmentPoints(ignore_hud_joints); + + return true; +} + +//----------------------------------------------------------------------------- +// initAttachmentPoints(): creates attachment points if needed, sets state based on avatar_lad.xml. +//----------------------------------------------------------------------------- +void LLVOAvatar::initAttachmentPoints(bool ignore_hud_joints) +{ + LLAvatarXmlInfo::attachment_info_list_t::iterator iter; + for (iter = sAvatarXmlInfo->mAttachmentInfoList.begin(); + iter != sAvatarXmlInfo->mAttachmentInfoList.end(); + ++iter) + { + LLAvatarXmlInfo::LLAvatarAttachmentInfo *info = *iter; + if (info->mIsHUDAttachment && (!isSelf() || ignore_hud_joints)) + { + //don't process hud joint for other avatars. + continue; + } + + S32 attachmentID = info->mAttachmentID; + if (attachmentID < 1 || attachmentID > 255) + { + LL_WARNS() << "Attachment point out of range [1-255]: " << attachmentID << " on attachment point " << info->mName << LL_ENDL; + continue; + } + + LLViewerJointAttachment* attachment = NULL; + bool newly_created = false; + if (mAttachmentPoints.find(attachmentID) == mAttachmentPoints.end()) + { + attachment = new LLViewerJointAttachment(); + newly_created = true; + } + else + { + attachment = mAttachmentPoints[attachmentID]; + } + + attachment->setName(info->mName); + LLJoint *parent_joint = getJoint(info->mJointName); + if (!parent_joint) + { + // If the intended parent for attachment point is unavailable, avatar_lad.xml is corrupt. + LL_WARNS() << "No parent joint by name " << info->mJointName << " found for attachment point " << info->mName << LL_ENDL; + LL_ERRS() << "Invalid avatar_lad.xml file" << LL_ENDL; + } + + if (info->mHasPosition) + { + attachment->setOriginalPosition(info->mPosition); + attachment->setDefaultPosition(info->mPosition); + } + + if (info->mHasRotation) + { + LLQuaternion rotation; + rotation.setQuat(info->mRotationEuler.mV[VX] * DEG_TO_RAD, + info->mRotationEuler.mV[VY] * DEG_TO_RAD, + info->mRotationEuler.mV[VZ] * DEG_TO_RAD); + attachment->setRotation(rotation); + } + + int group = info->mGroup; + if (group >= 0) + { + if (group < 0 || group > 9) + { + LL_WARNS() << "Invalid group number (" << group << ") for attachment point " << info->mName << LL_ENDL; + } + else + { + attachment->setGroup(group); + } + } + + attachment->setPieSlice(info->mPieMenuSlice); + attachment->setVisibleInFirstPerson(info->mVisibleFirstPerson); + attachment->setIsHUDAttachment(info->mIsHUDAttachment); + // attachment can potentially be animated, needs a number. + attachment->setJointNum(mNumBones + mNumCollisionVolumes + attachmentID - 1); + + if (newly_created) + { + mAttachmentPoints[attachmentID] = attachment; + + // now add attachment joint + parent_joint->addChild(attachment); + } + } +} + +//----------------------------------------------------------------------------- +// updateVisualParams() +//----------------------------------------------------------------------------- +void LLVOAvatar::updateVisualParams() +{ + ESex avatar_sex = (getVisualParamWeight("male") > 0.5f) ? SEX_MALE : SEX_FEMALE; + if (getSex() != avatar_sex) + { + if (mIsSitting && findMotion(avatar_sex == SEX_MALE ? ANIM_AGENT_SIT_FEMALE : ANIM_AGENT_SIT) != NULL) + { + // In some cases of gender change server changes sit motion with motion message, + // but in case of some avatars (legacy?) there is no update from server side, + // likely because server doesn't know about difference between motions + // (female and male sit ids are same server side, so it is likely unaware that it + // need to send update) + // Make sure motion is up to date + stopMotion(ANIM_AGENT_SIT); + setSex(avatar_sex); + startMotion(ANIM_AGENT_SIT); + } + else + { + setSex(avatar_sex); + } + } + + LLCharacter::updateVisualParams(); + + if (mLastSkeletonSerialNum != mSkeletonSerialNum) + { + computeBodySize(); + mLastSkeletonSerialNum = mSkeletonSerialNum; + mRoot->updateWorldMatrixChildren(); + } + + dirtyMesh(); + updateHeadOffset(); +} +//----------------------------------------------------------------------------- +// isActive() +//----------------------------------------------------------------------------- +bool LLVOAvatar::isActive() const +{ + return true; +} + +//----------------------------------------------------------------------------- +// setPixelAreaAndAngle() +//----------------------------------------------------------------------------- +void LLVOAvatar::setPixelAreaAndAngle(LLAgent &agent) +{ + if (mDrawable.isNull()) + { + return; + } + + const LLVector4a* ext = mDrawable->getSpatialExtents(); + LLVector4a center; + center.setAdd(ext[1], ext[0]); + center.mul(0.5f); + LLVector4a size; + size.setSub(ext[1], ext[0]); + size.mul(0.5f); + + mImpostorPixelArea = LLPipeline::calcPixelArea(center, size, *LLViewerCamera::getInstance()); + mPixelArea = mImpostorPixelArea; + + F32 range = mDrawable->mDistanceWRTCamera; + + if (range < 0.001f) // range == zero + { + mAppAngle = 180.f; + } + else + { + F32 radius = size.getLength3().getF32(); + mAppAngle = (F32) atan2( radius, range) * RAD_TO_DEG; + } + + // We always want to look good to ourselves + if( isSelf() ) + { + mPixelArea = llmax( mPixelArea, F32(getTexImageSize() / 16) ); + } +} + +//----------------------------------------------------------------------------- +// updateJointLODs() +//----------------------------------------------------------------------------- +bool LLVOAvatar::updateJointLODs() +{ + const F32 MAX_PIXEL_AREA = 100000000.f; + F32 lod_factor = (sLODFactor * AVATAR_LOD_TWEAK_RANGE + (1.f - AVATAR_LOD_TWEAK_RANGE)); + F32 avatar_num_min_factor = clamp_rescale(sLODFactor, 0.f, 1.f, 0.25f, 0.6f); + F32 avatar_num_factor = clamp_rescale((F32)sNumVisibleAvatars, 8, 25, 1.f, avatar_num_min_factor); + F32 area_scale = 0.16f; + + if (isSelf()) + { + if(gAgentCamera.cameraCustomizeAvatar() || gAgentCamera.cameraMouselook()) + { + mAdjustedPixelArea = MAX_PIXEL_AREA; + } + else + { + mAdjustedPixelArea = mPixelArea*area_scale; + } + } + else if (mIsDummy) + { + mAdjustedPixelArea = MAX_PIXEL_AREA; + } + else + { + // reported avatar pixel area is dependent on avatar render load, based on number of visible avatars + mAdjustedPixelArea = (F32)mPixelArea * area_scale * lod_factor * lod_factor * avatar_num_factor * avatar_num_factor; + } + + // now select meshes to render based on adjusted pixel area + LLViewerJoint* root = dynamic_cast(mRoot); + bool res = false; + if (root) + { + res = root->updateLOD(mAdjustedPixelArea, true); + } + if (res) + { + sNumLODChangesThisFrame++; + dirtyMesh(2); + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// createDrawable() +//----------------------------------------------------------------------------- +LLDrawable *LLVOAvatar::createDrawable(LLPipeline *pipeline) +{ + pipeline->allocDrawable(this); + mDrawable->setLit(false); + + LLDrawPoolAvatar *poolp = (LLDrawPoolAvatar*)gPipeline.getPool(mIsControlAvatar ? LLDrawPool::POOL_CONTROL_AV : LLDrawPool::POOL_AVATAR); + + // Only a single face (one per avatar) + //this face will be splitted into several if its vertex buffer is too long. + mDrawable->setState(LLDrawable::ACTIVE); + mDrawable->addFace(poolp, NULL); + mDrawable->setRenderType(mIsControlAvatar ? LLPipeline::RENDER_TYPE_CONTROL_AV : LLPipeline::RENDER_TYPE_AVATAR); + + mNumInitFaces = mDrawable->getNumFaces() ; + + dirtyMesh(2); + return mDrawable; +} + + +void LLVOAvatar::updateGL() +{ + if (mMeshTexturesDirty) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR + updateMeshTextures(); + mMeshTexturesDirty = false; + } +} + +//----------------------------------------------------------------------------- +// updateGeometry() +//----------------------------------------------------------------------------- +bool LLVOAvatar::updateGeometry(LLDrawable *drawable) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + if (!(gPipeline.hasRenderType(mIsControlAvatar ? LLPipeline::RENDER_TYPE_CONTROL_AV : LLPipeline::RENDER_TYPE_AVATAR))) + { + return true; + } + + if (!mMeshValid) + { + return true; + } + + if (!drawable) + { + LL_ERRS() << "LLVOAvatar::updateGeometry() called with NULL drawable" << LL_ENDL; + } + + return true; +} + +//----------------------------------------------------------------------------- +// updateSexDependentLayerSets() +//----------------------------------------------------------------------------- +void LLVOAvatar::updateSexDependentLayerSets() +{ + invalidateComposite( mBakedTextureDatas[BAKED_HEAD].mTexLayerSet); + invalidateComposite( mBakedTextureDatas[BAKED_UPPER].mTexLayerSet); + invalidateComposite( mBakedTextureDatas[BAKED_LOWER].mTexLayerSet); +} + +//----------------------------------------------------------------------------- +// dirtyMesh() +//----------------------------------------------------------------------------- +void LLVOAvatar::dirtyMesh() +{ + dirtyMesh(1); +} +void LLVOAvatar::dirtyMesh(S32 priority) +{ + mDirtyMesh = llmax(mDirtyMesh, priority); +} + +//----------------------------------------------------------------------------- +// getViewerJoint() +//----------------------------------------------------------------------------- +LLViewerJoint* LLVOAvatar::getViewerJoint(S32 idx) +{ + return dynamic_cast(mMeshLOD[idx]); +} + +//----------------------------------------------------------------------------- +// hideHair() +//----------------------------------------------------------------------------- +void LLVOAvatar::hideHair() +{ + mMeshLOD[MESH_ID_HAIR]->setVisible(false, true); +} + +//----------------------------------------------------------------------------- +// hideSkirt() +//----------------------------------------------------------------------------- +void LLVOAvatar::hideSkirt() +{ + mMeshLOD[MESH_ID_SKIRT]->setVisible(false, true); +} + +bool LLVOAvatar::setParent(LLViewerObject* parent) +{ + bool ret ; + if (parent == NULL) + { + getOffObject(); + ret = LLViewerObject::setParent(parent); + if (isSelf()) + { + gAgentCamera.resetCamera(); + } + } + else + { + ret = LLViewerObject::setParent(parent); + if(ret) + { + sitOnObject(parent); + } + } + return ret ; +} + +void LLVOAvatar::addChild(LLViewerObject *childp) +{ + childp->extractAttachmentItemID(); // find the inventory item this object is associated with. + if (isSelf()) + { + const LLUUID& item_id = childp->getAttachmentItemID(); + LLViewerInventoryItem *item = gInventory.getItem(item_id); + LL_DEBUGS("Avatar") << "ATT attachment child added " << (item ? item->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL; + + } + + LLViewerObject::addChild(childp); + if (childp->mDrawable) + { + if (!attachObject(childp)) + { + LL_WARNS() << "ATT addChild() failed for " + << childp->getID() + << " item " << childp->getAttachmentItemID() + << LL_ENDL; + // MAINT-3312 backout + // mPendingAttachment.push_back(childp); + } + } + else + { + mPendingAttachment.push_back(childp); + } +} + +void LLVOAvatar::removeChild(LLViewerObject *childp) +{ + LLViewerObject::removeChild(childp); + if (!detachObject(childp)) + { + LL_WARNS() << "Calling detach on non-attached object " << LL_ENDL; + } +} + +LLViewerJointAttachment* LLVOAvatar::getTargetAttachmentPoint(LLViewerObject* viewer_object) +{ + S32 attachmentID = ATTACHMENT_ID_FROM_STATE(viewer_object->getAttachmentState()); + + // This should never happen unless the server didn't process the attachment point + // correctly, but putting this check in here to be safe. + if (attachmentID & ATTACHMENT_ADD) + { + LL_WARNS() << "Got an attachment with ATTACHMENT_ADD mask, removing ( attach pt:" << attachmentID << " )" << LL_ENDL; + attachmentID &= ~ATTACHMENT_ADD; + } + + LLViewerJointAttachment* attachment = get_if_there(mAttachmentPoints, attachmentID, (LLViewerJointAttachment*)NULL); + + if (!attachment) + { + LL_WARNS() << "Object attachment point invalid: " << attachmentID + << " trying to use 1 (chest)" + << LL_ENDL; + + attachment = get_if_there(mAttachmentPoints, 1, (LLViewerJointAttachment*)NULL); // Arbitrary using 1 (chest) + if (attachment) + { + LL_WARNS() << "Object attachment point invalid: " << attachmentID + << " on object " << viewer_object->getID() + << " attachment item " << viewer_object->getAttachmentItemID() + << " falling back to 1 (chest)" + << LL_ENDL; + } + else + { + LL_WARNS() << "Object attachment point invalid: " << attachmentID + << " on object " << viewer_object->getID() + << " attachment item " << viewer_object->getAttachmentItemID() + << "Unable to use fallback attachment point 1 (chest)" + << LL_ENDL; + } + } + + return attachment; +} + +//----------------------------------------------------------------------------- +// attachObject() +//----------------------------------------------------------------------------- +const LLViewerJointAttachment *LLVOAvatar::attachObject(LLViewerObject *viewer_object) +{ + if (isSelf()) + { + const LLUUID& item_id = viewer_object->getAttachmentItemID(); + LLViewerInventoryItem *item = gInventory.getItem(item_id); + LL_DEBUGS("Avatar") << "ATT attaching object " + << (item ? item->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL; + } + LLViewerJointAttachment* attachment = getTargetAttachmentPoint(viewer_object); + + if (!attachment || !attachment->addObject(viewer_object)) + { + const LLUUID& item_id = viewer_object->getAttachmentItemID(); + LLViewerInventoryItem *item = gInventory.getItem(item_id); + LL_WARNS("Avatar") << "ATT attach failed " + << (item ? item->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL; + return 0; + } + + if (!viewer_object->isAnimatedObject()) + { + updateAttachmentOverrides(); + } + + updateVisualComplexity(); + + if (viewer_object->isSelected()) + { + LLSelectMgr::getInstance()->updateSelectionCenter(); + LLSelectMgr::getInstance()->updatePointAt(); + } + + viewer_object->refreshBakeTexture(); + + + LLViewerObject::const_child_list_t& child_list = viewer_object->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); ++iter) + { + LLViewerObject* objectp = *iter; + if (objectp) + { + objectp->refreshBakeTexture(); + } + } + + updateMeshVisibility(); + + return attachment; +} + +//----------------------------------------------------------------------------- +// getNumAttachments() +//----------------------------------------------------------------------------- +U32 LLVOAvatar::getNumAttachments() const +{ + U32 num_attachments = 0; + for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + const LLViewerJointAttachment *attachment_pt = (*iter).second; + num_attachments += attachment_pt->getNumObjects(); + } + return num_attachments; +} + +//----------------------------------------------------------------------------- +// getMaxAttachments() +//----------------------------------------------------------------------------- +S32 LLVOAvatar::getMaxAttachments() const +{ + return LLAgentBenefitsMgr::current().getAttachmentLimit(); +} + +//----------------------------------------------------------------------------- +// canAttachMoreObjects() +// Returns true if we can attach more objects. +//----------------------------------------------------------------------------- +bool LLVOAvatar::canAttachMoreObjects(U32 n) const +{ + return (getNumAttachments() + n) <= getMaxAttachments(); +} + +//----------------------------------------------------------------------------- +// getNumAnimatedObjectAttachments() +//----------------------------------------------------------------------------- +U32 LLVOAvatar::getNumAnimatedObjectAttachments() const +{ + U32 num_attachments = 0; + for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + const LLViewerJointAttachment *attachment_pt = (*iter).second; + num_attachments += attachment_pt->getNumAnimatedObjects(); + } + return num_attachments; +} + +//----------------------------------------------------------------------------- +// getMaxAnimatedObjectAttachments() +// Gets from simulator feature if available, otherwise 0. +//----------------------------------------------------------------------------- +S32 LLVOAvatar::getMaxAnimatedObjectAttachments() const +{ + return LLAgentBenefitsMgr::current().getAnimatedObjectLimit(); +} + +//----------------------------------------------------------------------------- +// canAttachMoreAnimatedObjects() +// Returns true if we can attach more animated objects. +//----------------------------------------------------------------------------- +bool LLVOAvatar::canAttachMoreAnimatedObjects(U32 n) const +{ + return (getNumAnimatedObjectAttachments() + n) <= getMaxAnimatedObjectAttachments(); +} + +//----------------------------------------------------------------------------- +// lazyAttach() +//----------------------------------------------------------------------------- +void LLVOAvatar::lazyAttach() +{ + std::vector > still_pending; + + for (U32 i = 0; i < mPendingAttachment.size(); i++) + { + LLPointer cur_attachment = mPendingAttachment[i]; + // Object might have died while we were waiting for drawable + if (!cur_attachment->isDead()) + { + if (cur_attachment->mDrawable) + { + if (isSelf()) + { + const LLUUID& item_id = cur_attachment->getAttachmentItemID(); + LLViewerInventoryItem *item = gInventory.getItem(item_id); + LL_DEBUGS("Avatar") << "ATT attaching object " + << (item ? item->getName() : "UNKNOWN") << " id " << item_id << LL_ENDL; + } + if (!attachObject(cur_attachment)) + { // Drop it + LL_WARNS() << "attachObject() failed for " + << cur_attachment->getID() + << " item " << cur_attachment->getAttachmentItemID() + << LL_ENDL; + // MAINT-3312 backout + //still_pending.push_back(cur_attachment); + } + } + else + { + still_pending.push_back(cur_attachment); + } + } + } + + mPendingAttachment = still_pending; +} + +void LLVOAvatar::resetHUDAttachments() +{ + + for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + if (attachment->getIsHUDAttachment()) + { + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + const LLViewerObject* attached_object = attachment_iter->get(); + if (attached_object && attached_object->mDrawable.notNull()) + { + gPipeline.markMoved(attached_object->mDrawable); + } + } + } + } +} + +void LLVOAvatar::rebuildRiggedAttachments( void ) +{ + for ( attachment_map_t::iterator iter = mAttachmentPoints.begin(); iter != mAttachmentPoints.end(); ++iter ) + { + LLViewerJointAttachment* pAttachment = iter->second; + LLViewerJointAttachment::attachedobjs_vec_t::iterator attachmentIterEnd = pAttachment->mAttachedObjects.end(); + + for ( LLViewerJointAttachment::attachedobjs_vec_t::iterator attachmentIter = pAttachment->mAttachedObjects.begin(); + attachmentIter != attachmentIterEnd; ++attachmentIter) + { + const LLViewerObject* pAttachedObject = *attachmentIter; + if ( pAttachment && pAttachedObject->mDrawable.notNull() ) + { + gPipeline.markRebuild(pAttachedObject->mDrawable); + } + } + } +} +//----------------------------------------------------------------------------- +// cleanupAttachedMesh() +//----------------------------------------------------------------------------- +void LLVOAvatar::cleanupAttachedMesh( LLViewerObject* pVO ) +{ + LLUUID mesh_id; + if (getRiggedMeshID(pVO, mesh_id)) + { + // FIXME this seems like an odd place for this code. + if ( gAgentCamera.cameraCustomizeAvatar() ) + { + gAgent.unpauseAnimation(); + //Still want to refocus on head bone + gAgentCamera.changeCameraToCustomizeAvatar(); + } + } +} + +bool LLVOAvatar::hasPendingAttachedMeshes() +{ + for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + if (attachment) + { + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + LLViewerObject* objectp = attachment_iter->get(); + if (objectp) + { + LLViewerObject::const_child_list_t& child_list = objectp->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter1 = child_list.begin(); + iter1 != child_list.end(); ++iter1) + { + LLViewerObject* objectchild = *iter1; + if (objectchild && objectchild->getVolume()) + { + const LLUUID& mesh_id = objectchild->getVolume()->getParams().getSculptID(); + if (mesh_id.isNull()) + { + // No mesh nor skin info needed + continue; + } + + if (objectchild->getVolume()->isMeshAssetUnavaliable()) + { + // Mesh failed to load, do not expect it + continue; + } + + if (objectchild->mDrawable) + { + LLVOVolume* pvobj = objectchild->mDrawable->getVOVolume(); + if (pvobj) + { + if (!pvobj->isMesh()) + { + // Not a mesh + continue; + } + + if (!objectchild->getVolume()->isMeshAssetLoaded()) + { + // Waiting for mesh + return true; + } + + const LLMeshSkinInfo* skin_data = pvobj->getSkinInfo(); + if (skin_data) + { + // Skin info present, done + continue; + } + + if (pvobj->isSkinInfoUnavaliable()) + { + // Load failed or info not present, don't expect it + continue; + } + } + + // objectchild is not ready + return true; + } + } + } + } + } + } + } + return false; +} + +//----------------------------------------------------------------------------- +// detachObject() +//----------------------------------------------------------------------------- +bool LLVOAvatar::detachObject(LLViewerObject *viewer_object) +{ + for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + + if (attachment->isObjectAttached(viewer_object)) + { + updateVisualComplexity(); + bool is_animated_object = viewer_object->isAnimatedObject(); + cleanupAttachedMesh(viewer_object); + + attachment->removeObject(viewer_object); + if (!is_animated_object) + { + updateAttachmentOverrides(); + } + viewer_object->refreshBakeTexture(); + + LLViewerObject::const_child_list_t& child_list = viewer_object->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter1 = child_list.begin(); + iter1 != child_list.end(); ++iter1) + { + LLViewerObject* objectp = *iter1; + if (objectp) + { + objectp->refreshBakeTexture(); + } + } + + updateMeshVisibility(); + + LL_DEBUGS() << "Detaching object " << viewer_object->mID << " from " << attachment->getName() << LL_ENDL; + return true; + } + } + + std::vector >::iterator iter = std::find(mPendingAttachment.begin(), mPendingAttachment.end(), viewer_object); + if (iter != mPendingAttachment.end()) + { + mPendingAttachment.erase(iter); + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// sitDown() +//----------------------------------------------------------------------------- +void LLVOAvatar::sitDown(bool bSitting) +{ + mIsSitting = bSitting; + if (isSelf()) + { + // Update Movement Controls according to own Sitting mode + LLFloaterMove::setSittingMode(bSitting); + } +} + +//----------------------------------------------------------------------------- +// sitOnObject() +//----------------------------------------------------------------------------- +void LLVOAvatar::sitOnObject(LLViewerObject *sit_object) +{ + if (isSelf()) + { + // Might be first sit + //LLFirstUse::useSit(); + + gAgent.setFlying(false); + gAgentCamera.setThirdPersonHeadOffset(LLVector3::zero); + //interpolate to new camera position + gAgentCamera.startCameraAnimation(); + // make sure we are not trying to autopilot + gAgent.stopAutoPilot(); + gAgentCamera.setupSitCamera(); + if (gAgentCamera.getForceMouselook()) + { + gAgentCamera.changeCameraToMouselook(); + } + + if (gAgentCamera.getFocusOnAvatar() && LLToolMgr::getInstance()->inEdit()) + { + LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstRootNode(); + if (node && node->mValid) + { + LLViewerObject* root_object = node->getObject(); + if (root_object == sit_object) + { + LLFloaterTools::sPreviousFocusOnAvatar = true; + } + } + } + } + + if (mDrawable.isNull()) + { + return; + } + LLQuaternion inv_obj_rot = ~sit_object->getRenderRotation(); + LLVector3 obj_pos = sit_object->getRenderPosition(); + + LLVector3 rel_pos = getRenderPosition() - obj_pos; + rel_pos.rotVec(inv_obj_rot); + + mDrawable->mXform.setPosition(rel_pos); + mDrawable->mXform.setRotation(mDrawable->getWorldRotation() * inv_obj_rot); + + gPipeline.markMoved(mDrawable, true); + // Notice that removing sitDown() from here causes avatars sitting on + // objects to be not rendered for new arrivals. See EXT-6835 and EXT-1655. + sitDown(true); + mRoot->getXform()->setParent(&sit_object->mDrawable->mXform); // LLVOAvatar::sitOnObject + // SL-315 + mRoot->setPosition(getPosition()); + mRoot->updateWorldMatrixChildren(); + + stopMotion(ANIM_AGENT_BODY_NOISE); + + gAgentCamera.setInitSitRot(gAgent.getFrameAgent().getQuaternion()); +} + +//----------------------------------------------------------------------------- +// getOffObject() +//----------------------------------------------------------------------------- +void LLVOAvatar::getOffObject() +{ + if (mDrawable.isNull()) + { + return; + } + + LLViewerObject* sit_object = (LLViewerObject*)getParent(); + + if (sit_object) + { + stopMotionFromSource(sit_object->getID()); + LLFollowCamMgr::getInstance()->setCameraActive(sit_object->getID(), false); + + LLViewerObject::const_child_list_t& child_list = sit_object->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); ++iter) + { + LLViewerObject* child_objectp = *iter; + + stopMotionFromSource(child_objectp->getID()); + LLFollowCamMgr::getInstance()->setCameraActive(child_objectp->getID(), false); + } + } + + // assumes that transform will not be updated with drawable still having a parent + // or that drawable had no parent from the start + LLVector3 cur_position_world = mDrawable->getWorldPosition(); + LLQuaternion cur_rotation_world = mDrawable->getWorldRotation(); + + if (mLastRootPos.length() >= MAX_STANDOFF_FROM_ORIGIN + && (cur_position_world.length() < MAX_STANDOFF_FROM_ORIGIN + || dist_vec(cur_position_world, mLastRootPos) > MAX_STANDOFF_DISTANCE_CHANGE)) + { + // Most likely drawable got updated too early or some updates were missed - we got relative position to non-existing parent + // restore coordinates from cache + cur_position_world = mLastRootPos; + } + + // set *local* position based on last *world* position, since we're unparenting the avatar + mDrawable->mXform.setPosition(cur_position_world); + mDrawable->mXform.setRotation(cur_rotation_world); + + gPipeline.markMoved(mDrawable, true); + + sitDown(false); + + mRoot->getXform()->setParent(NULL); // LLVOAvatar::getOffObject + // SL-315 + mRoot->setPosition(cur_position_world); + mRoot->setRotation(cur_rotation_world); + mRoot->getXform()->update(); + + if (mEnableDefaultMotions) + { + startMotion(ANIM_AGENT_BODY_NOISE); + } + + if (isSelf()) + { + LLQuaternion av_rot = gAgent.getFrameAgent().getQuaternion(); + LLQuaternion obj_rot = sit_object ? sit_object->getRenderRotation() : LLQuaternion::DEFAULT; + av_rot = av_rot * obj_rot; + LLVector3 at_axis = LLVector3::x_axis; + at_axis = at_axis * av_rot; + at_axis.mV[VZ] = 0.f; + at_axis.normalize(); + gAgent.resetAxes(at_axis); + gAgentCamera.setThirdPersonHeadOffset(LLVector3(0.f, 0.f, 1.f)); + gAgentCamera.setSitCamera(LLUUID::null); + } +} + +//----------------------------------------------------------------------------- +// findAvatarFromAttachment() +//----------------------------------------------------------------------------- +// static +LLVOAvatar* LLVOAvatar::findAvatarFromAttachment( LLViewerObject* obj ) +{ + if( obj->isAttachment() ) + { + do + { + obj = (LLViewerObject*) obj->getParent(); + } + while( obj && !obj->isAvatar() ); + + if( obj && !obj->isDead() ) + { + return (LLVOAvatar*)obj; + } + } + return NULL; +} + +S32 LLVOAvatar::getAttachmentCount() const +{ + S32 count = 0; + + for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); iter != mAttachmentPoints.end(); ++iter) + { + LLViewerJointAttachment* pAttachment = iter->second; + count += pAttachment->mAttachedObjects.size(); + } + + return count; +} + +bool LLVOAvatar::isWearingWearableType(LLWearableType::EType type) const +{ + if (mIsDummy) return true; + + if (isSelf()) + { + return LLAvatarAppearance::isWearingWearableType(type); + } + + switch(type) + { + case LLWearableType::WT_SHAPE: + case LLWearableType::WT_SKIN: + case LLWearableType::WT_HAIR: + case LLWearableType::WT_EYES: + return true; // everyone has all bodyparts + default: + break; // Do nothing + } + + for (LLAvatarAppearanceDictionary::Textures::const_iterator tex_iter = LLAvatarAppearance::getDictionary()->getTextures().begin(); + tex_iter != LLAvatarAppearance::getDictionary()->getTextures().end(); + ++tex_iter) + { + const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = tex_iter->second; + if (texture_dict->mWearableType == type) + { + // Thus, you must check to see if the corresponding baked texture is defined. + // NOTE: this is a poor substitute if you actually want to know about individual pieces of clothing + // this works for detecting a skirt (most important), but is ineffective at any piece of clothing that + // gets baked into a texture that always exists (upper or lower). + if (texture_dict->mIsUsedByBakedTexture) + { + const EBakedTextureIndex baked_index = texture_dict->mBakedTextureIndex; + return isTextureDefined(LLAvatarAppearance::getDictionary()->getBakedTexture(baked_index)->mTextureIndex); + } + return false; + } + } + return false; +} + +LLViewerObject * LLVOAvatar::findAttachmentByID( const LLUUID & target_id ) const +{ + for(attachment_map_t::const_iterator attachment_points_iter = mAttachmentPoints.begin(); + attachment_points_iter != gAgentAvatarp->mAttachmentPoints.end(); + ++attachment_points_iter) + { + LLViewerJointAttachment* attachment = attachment_points_iter->second; + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + LLViewerObject *attached_object = attachment_iter->get(); + if (attached_object && + attached_object->getID() == target_id) + { + return attached_object; + } + } + } + + return NULL; +} + +// virtual +void LLVOAvatar::invalidateComposite( LLTexLayerSet* layerset) +{ +} + +void LLVOAvatar::invalidateAll() +{ +} + +// virtual +void LLVOAvatar::onGlobalColorChanged(const LLTexGlobalColor* global_color) +{ + if (global_color == mTexSkinColor) + { + invalidateComposite( mBakedTextureDatas[BAKED_HEAD].mTexLayerSet); + invalidateComposite( mBakedTextureDatas[BAKED_UPPER].mTexLayerSet); + invalidateComposite( mBakedTextureDatas[BAKED_LOWER].mTexLayerSet); + } + else if (global_color == mTexHairColor) + { + invalidateComposite( mBakedTextureDatas[BAKED_HEAD].mTexLayerSet); + invalidateComposite( mBakedTextureDatas[BAKED_HAIR].mTexLayerSet); + + // ! BACKWARDS COMPATIBILITY ! + // Fix for dealing with avatars from viewers that don't bake hair. + if (!isTextureDefined(mBakedTextureDatas[BAKED_HAIR].mTextureIndex)) + { + LLColor4 color = mTexHairColor->getColor(); + avatar_joint_mesh_list_t::iterator iter = mBakedTextureDatas[BAKED_HAIR].mJointMeshes.begin(); + avatar_joint_mesh_list_t::iterator end = mBakedTextureDatas[BAKED_HAIR].mJointMeshes.end(); + for (; iter != end; ++iter) + { + LLAvatarJointMesh* mesh = (*iter); + if (mesh) + { + mesh->setColor( color ); + } + } + } + } + else if (global_color == mTexEyeColor) + { + // LL_INFOS() << "invalidateComposite cause: onGlobalColorChanged( eyecolor )" << LL_ENDL; + invalidateComposite( mBakedTextureDatas[BAKED_EYES].mTexLayerSet); + } + updateMeshTextures(); +} + +// virtual +// Do rigged mesh attachments display with this av? +bool LLVOAvatar::shouldRenderRigged() const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + + if (getOverallAppearance() == AOA_NORMAL) + { + return true; + } + // TBD - render for AOA_JELLYDOLL? + return false; +} + +// FIXME: We have an mVisible member, set in updateVisibility(), but this +// function doesn't return it! isVisible() and mVisible are used +// different places for different purposes. mVisible seems to be more +// related to whether the actual avatar mesh is shown, and isVisible() +// to whether anything about the avatar is displayed in the scene. +// Maybe better naming could make this clearer? +bool LLVOAvatar::isVisible() const +{ + return mDrawable.notNull() + && (!mOrphaned || isSelf()) + && (mDrawable->isVisible() || mIsDummy); +} + +// Determine if we have enough avatar data to render +bool LLVOAvatar::getIsCloud() const +{ + if (mIsDummy) + { + return false; + } + + return ( ((const_cast(this))->visualParamWeightsAreDefault())// Do we have a shape? + || ( !isTextureDefined(TEX_LOWER_BAKED) + || !isTextureDefined(TEX_UPPER_BAKED) + || !isTextureDefined(TEX_HEAD_BAKED) + ) + ); +} + +void LLVOAvatar::updateRezzedStatusTimers(S32 rez_status) +{ + // State machine for rezzed status. Statuses are -1 on startup, 0 + // = cloud, 1 = gray, 2 = downloading, 3 = waiting for attachments, 4 = full. + // Purpose is to collect time data for each it takes avatar to reach + // various loading landmarks: gray, textured (partial), textured fully. + + if (rez_status != mLastRezzedStatus) + { + LL_DEBUGS("Avatar") << avString() << "rez state change: " << mLastRezzedStatus << " -> " << rez_status << LL_ENDL; + + if (mLastRezzedStatus == -1 && rez_status != -1) + { + // First time initialization, start all timers. + for (S32 i = 1; i < 4; i++) + { + startPhase("load_" + LLVOAvatar::rezStatusToString(i)); + startPhase("first_load_" + LLVOAvatar::rezStatusToString(i)); + } + } + if (rez_status < mLastRezzedStatus) + { + // load level has decreased. start phase timers for higher load levels. + for (S32 i = rez_status+1; i <= mLastRezzedStatus; i++) + { + startPhase("load_" + LLVOAvatar::rezStatusToString(i)); + } + } + else if (rez_status > mLastRezzedStatus) + { + // load level has increased. stop phase timers for lower and equal load levels. + for (S32 i = llmax(mLastRezzedStatus+1,1); i <= rez_status; i++) + { + stopPhase("load_" + LLVOAvatar::rezStatusToString(i)); + stopPhase("first_load_" + LLVOAvatar::rezStatusToString(i), false); + } + if (rez_status == 4) + { + // "fully loaded", mark any pending appearance change complete. + selfStopPhase("update_appearance_from_cof"); + selfStopPhase("wear_inventory_category", false); + selfStopPhase("process_initial_wearables_update", false); + + updateVisualComplexity(); + } + } + mLastRezzedStatus = rez_status; + + static LLUICachedControl show_rez_status("NameTagDebugAVRezState", false); + if (show_rez_status) + { + mNameIsSet = false; + } + } +} + +void LLVOAvatar::clearPhases() +{ + getPhases().clearPhases(); +} + +void LLVOAvatar::startPhase(const std::string& phase_name) +{ + F32 elapsed = 0.0; + bool completed = false; + bool found = getPhases().getPhaseValues(phase_name, elapsed, completed); + //LL_DEBUGS("Avatar") << avString() << " phase state " << phase_name + // << " found " << found << " elapsed " << elapsed << " completed " << completed << LL_ENDL; + if (found) + { + if (!completed) + { + LL_DEBUGS("Avatar") << avString() << "no-op, start when started already for " << phase_name << LL_ENDL; + return; + } + } + LL_DEBUGS("Avatar") << "started phase " << phase_name << LL_ENDL; + getPhases().startPhase(phase_name); +} + +void LLVOAvatar::stopPhase(const std::string& phase_name, bool err_check) +{ + F32 elapsed = 0.0; + bool completed = false; + if (getPhases().getPhaseValues(phase_name, elapsed, completed)) + { + if (!completed) + { + getPhases().stopPhase(phase_name); + completed = true; + logMetricsTimerRecord(phase_name, elapsed, completed); + LL_DEBUGS("Avatar") << avString() << "stopped phase " << phase_name << " elapsed " << elapsed << LL_ENDL; + } + else + { + if (err_check) + { + LL_DEBUGS("Avatar") << "no-op, stop when stopped already for " << phase_name << LL_ENDL; + } + } + } + else + { + if (err_check) + { + LL_DEBUGS("Avatar") << "no-op, stop when not started for " << phase_name << LL_ENDL; + } + } +} + +void LLVOAvatar::logPendingPhases() +{ + if (!isAgentAvatarValid()) + { + return; + } + + for (LLViewerStats::phase_map_t::iterator it = getPhases().begin(); + it != getPhases().end(); + ++it) + { + const std::string& phase_name = it->first; + F32 elapsed; + bool completed; + if (getPhases().getPhaseValues(phase_name, elapsed, completed)) + { + if (!completed) + { + logMetricsTimerRecord(phase_name, elapsed, completed); + } + } + } +} + +//static +void LLVOAvatar::logPendingPhasesAllAvatars() +{ + for (std::vector::iterator iter = LLCharacter::sInstances.begin(); + iter != LLCharacter::sInstances.end(); ++iter) + { + LLVOAvatar* inst = (LLVOAvatar*) *iter; + if( inst->isDead() ) + { + continue; + } + inst->logPendingPhases(); + } +} + +void LLVOAvatar::logMetricsTimerRecord(const std::string& phase_name, F32 elapsed, bool completed) +{ + if (!isAgentAvatarValid()) + { + return; + } + + LLSD record; + record["timer_name"] = phase_name; + record["avatar_id"] = getID(); + record["elapsed"] = elapsed; + record["completed"] = completed; + U32 grid_x(0), grid_y(0); + if (getRegion() && LLWorld::instance().isRegionListed(getRegion())) + { + record["central_bake_version"] = LLSD::Integer(getRegion()->getCentralBakeVersion()); + grid_from_region_handle(getRegion()->getHandle(), &grid_x, &grid_y); + } + record["grid_x"] = LLSD::Integer(grid_x); + record["grid_y"] = LLSD::Integer(grid_y); + record["is_using_server_bakes"] = true; + record["is_self"] = isSelf(); + + if (isAgentAvatarValid()) + { + gAgentAvatarp->addMetricsTimerRecord(record); + } +} + +// call periodically to keep isFullyLoaded up to date. +// returns true if the value has changed. +bool LLVOAvatar::updateIsFullyLoaded() +{ + S32 rez_status = getRezzedStatus(); + bool loading = rez_status == 0; + if (mFirstFullyVisible && !mIsControlAvatar) + { + loading = ((rez_status < 2) + // Wait at least 60s for unfinished textures to finish on first load, + // don't wait forever, it might fail. Even if it will eventually load by + // itself and update mLoadedCallbackTextures (or fail and clean the list), + // avatars are more time-sensitive than textures and can't wait that long. + || (mLoadedCallbackTextures < mCallbackTextureList.size() && mLastTexCallbackAddedTime.getElapsedTimeF32() < MAX_TEXTURE_WAIT_TIME_SEC) + || !mPendingAttachment.empty() + || (rez_status < 3 && !isFullyBaked()) + || hasPendingAttachedMeshes() + ); + + // compare amount of attachments to one reported by simulator + if (!loading && !isSelf() && rez_status < 4 && mLastCloudAttachmentCount < mSimAttachments.size()) + { + S32 attachment_count = getAttachmentCount(); + if (mLastCloudAttachmentCount != attachment_count) + { + mLastCloudAttachmentCount = attachment_count; + if (attachment_count != mSimAttachments.size()) + { + // attachment count changed, but still below desired, wait for more updates + mLastCloudAttachmentChangeTime.reset(); + loading = true; + } + } + else if (mLastCloudAttachmentChangeTime.getElapsedTimeF32() < MAX_ATTACHMENT_WAIT_TIME_SEC) + { + // waiting + loading = true; + } + } + } + updateRezzedStatusTimers(rez_status); + updateRuthTimer(loading); + return processFullyLoadedChange(loading); +} + +void LLVOAvatar::updateRuthTimer(bool loading) +{ + if (isSelf() || !loading) + { + return; + } + + if (mPreviousFullyLoaded) + { + mRuthTimer.reset(); + debugAvatarRezTime("AvatarRezCloudNotification","became cloud"); + } + + const F32 LOADING_TIMEOUT__SECONDS = 120.f; + if (mRuthTimer.getElapsedTimeF32() > LOADING_TIMEOUT__SECONDS) + { + LL_DEBUGS("Avatar") << avString() + << "Ruth Timer timeout: Missing texture data for '" << getFullname() << "' " + << "( Params loaded : " << !visualParamWeightsAreDefault() << " ) " + << "( Lower : " << isTextureDefined(TEX_LOWER_BAKED) << " ) " + << "( Upper : " << isTextureDefined(TEX_UPPER_BAKED) << " ) " + << "( Head : " << isTextureDefined(TEX_HEAD_BAKED) << " )." + << LL_ENDL; + + LLAvatarPropertiesProcessor::getInstance()->sendAvatarTexturesRequest(getID()); + mRuthTimer.reset(); + } +} + +bool LLVOAvatar::processFullyLoadedChange(bool loading) +{ + // We wait a little bit before giving the 'all clear', to let things to + // settle down: models to snap into place, textures to get first packets, + // LODs to load. + const F32 LOADED_DELAY = 1.f; + + if (loading) + { + mFullyLoadedTimer.reset(); + } + + if (mFirstFullyVisible) + { + F32 first_use_delay = FIRST_APPEARANCE_CLOUD_MIN_DELAY; + if (!isSelf() && loading) + { + // Note that textures can causes 60s delay on thier own + // so this delay might end up on top of textures' delay + first_use_delay = llclamp( + mFirstAppearanceMessageTimer.getElapsedTimeF32(), + FIRST_APPEARANCE_CLOUD_MIN_DELAY, + FIRST_APPEARANCE_CLOUD_MAX_DELAY); + + if (shouldImpostor()) + { + // Impostors are less of a priority, + // let them stay cloud longer + first_use_delay *= FIRST_APPEARANCE_CLOUD_IMPOSTOR_MODIFIER; + } + } + mFullyLoaded = (mFullyLoadedTimer.getElapsedTimeF32() > first_use_delay); + } + else + { + mFullyLoaded = (mFullyLoadedTimer.getElapsedTimeF32() > LOADED_DELAY); + } + + if (!mPreviousFullyLoaded && !loading && mFullyLoaded) + { + debugAvatarRezTime("AvatarRezNotification", "fully loaded"); + } + + // did our loading state "change" from last call? + // FIXME runway - why are we updating every 30 calls even if nothing has changed? + // This causes updateLOD() to run every 30 frames, among other things. + const S32 UPDATE_RATE = 30; + bool changed = + ((mFullyLoaded != mPreviousFullyLoaded) || // if the value is different from the previous call + (!mFullyLoadedInitialized) || // if we've never been called before + (mFullyLoadedFrameCounter % UPDATE_RATE == 0)); // every now and then issue a change + bool fully_loaded_changed = (mFullyLoaded != mPreviousFullyLoaded); + + mPreviousFullyLoaded = mFullyLoaded; + mFullyLoadedInitialized = true; + mFullyLoadedFrameCounter++; + + if (changed && isSelf()) + { + // to know about outfit switching + LLAvatarRenderNotifier::getInstance()->updateNotificationState(); + } + + if (fully_loaded_changed && !isSelf() && mFullyLoaded && isImpostor()) + { + // Fix for jellydoll initially invisible + mNeedsImpostorUpdate = true; + mLastImpostorUpdateReason = 6; + } + return changed; +} + +bool LLVOAvatar::isFullyLoaded() const +{ + return (mRenderUnloadedAvatar || mFullyLoaded); +} + +bool LLVOAvatar::isTooComplex() const +{ + bool too_complex; + static LLCachedControl compelxity_render_mode(gSavedSettings, "RenderAvatarComplexityMode"); + bool render_friend = (LLAvatarTracker::instance().isBuddy(getID()) && compelxity_render_mode > AV_RENDER_LIMIT_BY_COMPLEXITY); + + if (isSelf() || render_friend || mVisuallyMuteSetting == AV_ALWAYS_RENDER) + { + too_complex = false; + } + else if (compelxity_render_mode == AV_RENDER_ONLY_SHOW_FRIENDS && !mIsControlAvatar) + { + too_complex = true; + } + else + { + // Determine if visually muted or not + static LLCachedControl max_render_cost(gSavedSettings, "RenderAvatarMaxComplexity", 0U); + static LLCachedControl max_attachment_area(gSavedSettings, "RenderAutoMuteSurfaceAreaLimit", 1000.0f); + // If the user has chosen unlimited max complexity, we also disregard max attachment area + // so that unlimited will completely disable the overly complex impostor rendering + // yes, this leaves them vulnerable to griefing objects... their choice + too_complex = ( max_render_cost > 0 + && (mVisualComplexity > max_render_cost + || (max_attachment_area > 0.0f && mAttachmentSurfaceArea > max_attachment_area) + )); + } + + return too_complex; +} + +bool LLVOAvatar::isTooSlow() const +{ + static LLCachedControl compelxity_render_mode(gSavedSettings, "RenderAvatarComplexityMode"); + bool render_friend = (LLAvatarTracker::instance().isBuddy(getID()) && compelxity_render_mode > AV_RENDER_LIMIT_BY_COMPLEXITY); + + if (render_friend || mVisuallyMuteSetting == AV_ALWAYS_RENDER) + { + return false; + } + else if (compelxity_render_mode == AV_RENDER_ONLY_SHOW_FRIENDS && !mIsControlAvatar) + { + return true; + } + return mTooSlow; +} + +// Udpate Avatar state based on render time +void LLVOAvatar::updateTooSlow() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + static LLCachedControl compelxity_render_mode(gSavedSettings, "RenderAvatarComplexityMode"); + static LLCachedControl allowSelfImpostor(gSavedSettings, "AllowSelfImpostor"); + const auto id = getID(); + + // mTooSlow - Is the avatar flagged as being slow (includes shadow time) + // mTooSlowWithoutShadows - Is the avatar flagged as being slow even with shadows removed. + + // get max render time in ms + F32 max_art_ms = (F32) (LLPerfStats::renderAvatarMaxART_ns / 1000000.0); + + bool autotune = LLPerfStats::tunables.userAutoTuneEnabled && !mIsControlAvatar && !isSelf(); + + bool ignore_tune = false; + if (autotune && sAVsIgnoringARTLimit.size() > 0) + { + auto it = std::find(sAVsIgnoringARTLimit.begin(), sAVsIgnoringARTLimit.end(), mID); + if (it != sAVsIgnoringARTLimit.end()) + { + S32 index = it - sAVsIgnoringARTLimit.begin(); + ignore_tune = (index < (MIN_NONTUNED_AVS - sAvatarsNearby + 1 + LLPerfStats::tunedAvatars)); + } + } + + bool exceeds_max_ART = + ((LLPerfStats::renderAvatarMaxART_ns > 0) && + (mGPURenderTime >= max_art_ms)); // NOTE: don't use getGPURenderTime accessor here to avoid "isTooSlow" feedback loop + + if (exceeds_max_ART && !ignore_tune) + { + mTooSlow = true; + + if(!mTooSlowWithoutShadows) // if we were not previously above the full impostor cap + { + bool always_render_friends = compelxity_render_mode > AV_RENDER_LIMIT_BY_COMPLEXITY; + bool render_friend_or_exception = (always_render_friends && LLAvatarTracker::instance().isBuddy( id ) ) || + ( getVisualMuteSettings() == LLVOAvatar::AV_ALWAYS_RENDER ); + if( (!isSelf() || allowSelfImpostor) && !render_friend_or_exception) + { + // Note: slow rendering Friends still get their shadows zapped. + mTooSlowWithoutShadows = (getGPURenderTime()*2.f >= max_art_ms) // NOTE: assumes shadow rendering doubles render time + || (compelxity_render_mode == AV_RENDER_ONLY_SHOW_FRIENDS && !mIsControlAvatar); + } + } + } + else + { + mTooSlow = false; + mTooSlowWithoutShadows = false; + + if (ignore_tune) + { + return; + } + } + if(mTooSlow && !mTuned) + { + LLPerfStats::tunedAvatars++; // increment the number of avatars that have been tweaked. + mTuned = true; + } + else if(!mTooSlow && mTuned) + { + LLPerfStats::tunedAvatars--; + mTuned = false; + } +} + +//----------------------------------------------------------------------------- +// findMotion() +//----------------------------------------------------------------------------- +LLMotion* LLVOAvatar::findMotion(const LLUUID& id) const +{ + return mMotionController.findMotion(id); +} + +// This is a semi-deprecated debugging tool - meshes will not show as +// colorized if using deferred rendering. +void LLVOAvatar::debugColorizeSubMeshes(U32 i, const LLColor4& color) +{ + if (gSavedSettings.getBOOL("DebugAvatarCompositeBaked")) + { + avatar_joint_mesh_list_t::iterator iter = mBakedTextureDatas[i].mJointMeshes.begin(); + avatar_joint_mesh_list_t::iterator end = mBakedTextureDatas[i].mJointMeshes.end(); + for (; iter != end; ++iter) + { + LLAvatarJointMesh* mesh = (*iter); + if (mesh) + { + mesh->setColor(color); + } + } + } +} + + +//----------------------------------------------------------------------------- +// updateMeshVisibility() +// Hide the mesh joints if attachments are using baked textures +//----------------------------------------------------------------------------- +void LLVOAvatar::updateMeshVisibility() +{ + bool bake_flag[BAKED_NUM_INDICES]; + memset(bake_flag, 0, BAKED_NUM_INDICES*sizeof(bool)); + + if (getOverallAppearance() == AOA_NORMAL) + { + for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + if (attachment) + { + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + LLViewerObject *objectp = attachment_iter->get(); + if (objectp) + { + for (int face_index = 0; face_index < objectp->getNumTEs(); face_index++) + { + LLTextureEntry* tex_entry = objectp->getTE(face_index); + bake_flag[BAKED_HEAD] |= (tex_entry->getID() == IMG_USE_BAKED_HEAD); + bake_flag[BAKED_EYES] |= (tex_entry->getID() == IMG_USE_BAKED_EYES); + bake_flag[BAKED_HAIR] |= (tex_entry->getID() == IMG_USE_BAKED_HAIR); + bake_flag[BAKED_LOWER] |= (tex_entry->getID() == IMG_USE_BAKED_LOWER); + bake_flag[BAKED_UPPER] |= (tex_entry->getID() == IMG_USE_BAKED_UPPER); + bake_flag[BAKED_SKIRT] |= (tex_entry->getID() == IMG_USE_BAKED_SKIRT); + bake_flag[BAKED_LEFT_ARM] |= (tex_entry->getID() == IMG_USE_BAKED_LEFTARM); + bake_flag[BAKED_LEFT_LEG] |= (tex_entry->getID() == IMG_USE_BAKED_LEFTLEG); + bake_flag[BAKED_AUX1] |= (tex_entry->getID() == IMG_USE_BAKED_AUX1); + bake_flag[BAKED_AUX2] |= (tex_entry->getID() == IMG_USE_BAKED_AUX2); + bake_flag[BAKED_AUX3] |= (tex_entry->getID() == IMG_USE_BAKED_AUX3); + } + } + + LLViewerObject::const_child_list_t& child_list = objectp->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter1 = child_list.begin(); + iter1 != child_list.end(); ++iter1) + { + LLViewerObject* objectchild = *iter1; + if (objectchild) + { + for (int face_index = 0; face_index < objectchild->getNumTEs(); face_index++) + { + LLTextureEntry* tex_entry = objectchild->getTE(face_index); + bake_flag[BAKED_HEAD] |= (tex_entry->getID() == IMG_USE_BAKED_HEAD); + bake_flag[BAKED_EYES] |= (tex_entry->getID() == IMG_USE_BAKED_EYES); + bake_flag[BAKED_HAIR] |= (tex_entry->getID() == IMG_USE_BAKED_HAIR); + bake_flag[BAKED_LOWER] |= (tex_entry->getID() == IMG_USE_BAKED_LOWER); + bake_flag[BAKED_UPPER] |= (tex_entry->getID() == IMG_USE_BAKED_UPPER); + bake_flag[BAKED_SKIRT] |= (tex_entry->getID() == IMG_USE_BAKED_SKIRT); + bake_flag[BAKED_LEFT_ARM] |= (tex_entry->getID() == IMG_USE_BAKED_LEFTARM); + bake_flag[BAKED_LEFT_LEG] |= (tex_entry->getID() == IMG_USE_BAKED_LEFTLEG); + bake_flag[BAKED_AUX1] |= (tex_entry->getID() == IMG_USE_BAKED_AUX1); + bake_flag[BAKED_AUX2] |= (tex_entry->getID() == IMG_USE_BAKED_AUX2); + bake_flag[BAKED_AUX3] |= (tex_entry->getID() == IMG_USE_BAKED_AUX3); + } + } + } + } + } + } + } + + //LL_INFOS() << "head " << bake_flag[BAKED_HEAD] << "eyes " << bake_flag[BAKED_EYES] << "hair " << bake_flag[BAKED_HAIR] << "lower " << bake_flag[BAKED_LOWER] << "upper " << bake_flag[BAKED_UPPER] << "skirt " << bake_flag[BAKED_SKIRT] << LL_ENDL; + + for (S32 i = 0; i < mMeshLOD.size(); i++) + { + LLAvatarJoint* joint = mMeshLOD[i]; + if (i == MESH_ID_HAIR) + { + joint->setVisible(!bake_flag[BAKED_HAIR], true); + } + else if (i == MESH_ID_HEAD) + { + joint->setVisible(!bake_flag[BAKED_HEAD], true); + } + else if (i == MESH_ID_SKIRT) + { + joint->setVisible(!bake_flag[BAKED_SKIRT], true); + } + else if (i == MESH_ID_UPPER_BODY) + { + joint->setVisible(!bake_flag[BAKED_UPPER], true); + } + else if (i == MESH_ID_LOWER_BODY) + { + joint->setVisible(!bake_flag[BAKED_LOWER], true); + } + else if (i == MESH_ID_EYEBALL_LEFT) + { + joint->setVisible(!bake_flag[BAKED_EYES], true); + } + else if (i == MESH_ID_EYEBALL_RIGHT) + { + joint->setVisible(!bake_flag[BAKED_EYES], true); + } + else if (i == MESH_ID_EYELASH) + { + joint->setVisible(!bake_flag[BAKED_HEAD], true); + } + } +} + +//----------------------------------------------------------------------------- +// updateMeshTextures() +// Uses the current TE values to set the meshes' and layersets' textures. +//----------------------------------------------------------------------------- +// virtual +void LLVOAvatar::updateMeshTextures() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR + static S32 update_counter = 0; + mBakedTextureDebugText.clear(); + + // if user has never specified a texture, assign the default + for (U32 i=0; i < getNumTEs(); i++) + { + const LLViewerTexture* te_image = getImage(i, 0); + if(!te_image || te_image->getID().isNull() || (te_image->getID() == IMG_DEFAULT)) + { + // IMG_DEFAULT_AVATAR = a special texture that's never rendered. + const LLUUID& image_id = (i == TEX_HAIR ? IMG_DEFAULT : IMG_DEFAULT_AVATAR); + setImage(i, LLViewerTextureManager::getFetchedTexture(image_id), 0); + } + } + + const bool other_culled = !isSelf() && mCulled; + LLLoadedCallbackEntry::source_callback_list_t* src_callback_list = NULL ; + bool paused = false; + if(!isSelf()) + { + src_callback_list = &mCallbackTextureList ; + paused = !isVisible(); + } + + std::vector is_layer_baked; + is_layer_baked.resize(mBakedTextureDatas.size(), false); + + std::vector use_lkg_baked_layer; // lkg = "last known good" + use_lkg_baked_layer.resize(mBakedTextureDatas.size(), false); + + mBakedTextureDebugText += llformat("%06d\n",update_counter++); + mBakedTextureDebugText += "indx layerset linvld ltda ilb ulkg ltid\n"; + for (U32 i=0; i < mBakedTextureDatas.size(); i++) + { + is_layer_baked[i] = isTextureDefined(mBakedTextureDatas[i].mTextureIndex); + LLViewerTexLayerSet* layerset = NULL; + bool layerset_invalid = false; + if (!other_culled) + { + // When an avatar is changing clothes and not in Appearance mode, + // use the last-known good baked texture until it finishes the first + // render of the new layerset. + layerset = getTexLayerSet(i); + layerset_invalid = layerset && ( !layerset->getViewerComposite()->isInitialized() + || !layerset->isLocalTextureDataAvailable() ); + use_lkg_baked_layer[i] = (!is_layer_baked[i] + && (mBakedTextureDatas[i].mLastTextureID != IMG_DEFAULT_AVATAR) + && layerset_invalid); + if (use_lkg_baked_layer[i]) + { + layerset->setUpdatesEnabled(true); + } + } + else + { + use_lkg_baked_layer[i] = (!is_layer_baked[i] + && mBakedTextureDatas[i].mLastTextureID != IMG_DEFAULT_AVATAR); + } + + std::string last_id_string; + if (mBakedTextureDatas[i].mLastTextureID == IMG_DEFAULT_AVATAR) + last_id_string = "A"; + else if (mBakedTextureDatas[i].mLastTextureID == IMG_DEFAULT) + last_id_string = "D"; + else if (mBakedTextureDatas[i].mLastTextureID == IMG_INVISIBLE) + last_id_string = "I"; + else + last_id_string = "*"; + bool is_ltda = layerset + && layerset->getViewerComposite()->isInitialized() + && layerset->isLocalTextureDataAvailable(); + mBakedTextureDebugText += llformat("%4d %4s %4d %4d %4d %4d %4s\n", + i, + (layerset?"*":"0"), + layerset_invalid, + is_ltda, + is_layer_baked[i], + use_lkg_baked_layer[i], + last_id_string.c_str()); + } + + for (U32 i=0; i < mBakedTextureDatas.size(); i++) + { + debugColorizeSubMeshes(i, LLColor4::white); + + LLViewerTexLayerSet* layerset = getTexLayerSet(i); + if (use_lkg_baked_layer[i] && !isUsingLocalAppearance() ) + { + // use last known good layer (no new one) + LLViewerFetchedTexture* baked_img = LLViewerTextureManager::getFetchedTexture(mBakedTextureDatas[i].mLastTextureID); + mBakedTextureDatas[i].mIsUsed = true; + + debugColorizeSubMeshes(i,LLColor4::red); + + avatar_joint_mesh_list_t::iterator iter = mBakedTextureDatas[i].mJointMeshes.begin(); + avatar_joint_mesh_list_t::iterator end = mBakedTextureDatas[i].mJointMeshes.end(); + for (; iter != end; ++iter) + { + LLAvatarJointMesh* mesh = (*iter); + if (mesh) + { + mesh->setTexture( baked_img ); + } + } + } + else if (!isUsingLocalAppearance() && is_layer_baked[i]) + { + // use new layer + LLViewerFetchedTexture* baked_img = + LLViewerTextureManager::staticCastToFetchedTexture( + getImage( mBakedTextureDatas[i].mTextureIndex, 0 ), true) ; + if( baked_img->getID() == mBakedTextureDatas[i].mLastTextureID ) + { + // Even though the file may not be finished loading, + // we'll consider it loaded and use it (rather than + // doing compositing). + useBakedTexture( baked_img->getID() ); + mLoadedCallbacksPaused |= !isVisible(); + checkTextureLoading(); + } + else + { + mBakedTextureDatas[i].mIsLoaded = false; + if ( (baked_img->getID() != IMG_INVISIBLE) && + ((i == BAKED_HEAD) || (i == BAKED_UPPER) || (i == BAKED_LOWER)) ) + { + baked_img->setLoadedCallback(onBakedTextureMasksLoaded, MORPH_MASK_REQUESTED_DISCARD, true, true, new LLTextureMaskData( mID ), + src_callback_list, paused); + } + baked_img->setLoadedCallback(onBakedTextureLoaded, SWITCH_TO_BAKED_DISCARD, false, false, new LLUUID( mID ), + src_callback_list, paused ); + if (baked_img->getDiscardLevel() < 0 && !paused) + { + // mLoadedCallbackTextures will be updated by checkTextureLoading() below + mLastTexCallbackAddedTime.reset(); + } + + // this could add paused texture callbacks + mLoadedCallbacksPaused |= paused; + checkTextureLoading(); + } + } + else if (layerset && isUsingLocalAppearance()) + { + debugColorizeSubMeshes(i,LLColor4::yellow ); + + layerset->createComposite(); + layerset->setUpdatesEnabled( true ); + mBakedTextureDatas[i].mIsUsed = false; + + avatar_joint_mesh_list_t::iterator iter = mBakedTextureDatas[i].mJointMeshes.begin(); + avatar_joint_mesh_list_t::iterator end = mBakedTextureDatas[i].mJointMeshes.end(); + for (; iter != end; ++iter) + { + LLAvatarJointMesh* mesh = (*iter); + if (mesh) + { + mesh->setLayerSet( layerset ); + } + } + } + else + { + debugColorizeSubMeshes(i,LLColor4::blue); + } + } + + // set texture and color of hair manually if we are not using a baked image. + // This can happen while loading hair for yourself, or for clients that did not + // bake a hair texture. Still needed for yourself after 1.22 is depricated. + if (!is_layer_baked[BAKED_HAIR]) + { + const LLColor4 color = mTexHairColor ? mTexHairColor->getColor() : LLColor4(1,1,1,1); + LLViewerTexture* hair_img = getImage( TEX_HAIR, 0 ); + avatar_joint_mesh_list_t::iterator iter = mBakedTextureDatas[BAKED_HAIR].mJointMeshes.begin(); + avatar_joint_mesh_list_t::iterator end = mBakedTextureDatas[BAKED_HAIR].mJointMeshes.end(); + for (; iter != end; ++iter) + { + LLAvatarJointMesh* mesh = (*iter); + if (mesh) + { + mesh->setColor( color ); + mesh->setTexture( hair_img ); + } + } + } + + + for (LLAvatarAppearanceDictionary::BakedTextures::const_iterator baked_iter = + LLAvatarAppearance::getDictionary()->getBakedTextures().begin(); + baked_iter != LLAvatarAppearance::getDictionary()->getBakedTextures().end(); + ++baked_iter) + { + const EBakedTextureIndex baked_index = baked_iter->first; + const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = baked_iter->second; + + for (texture_vec_t::const_iterator local_tex_iter = baked_dict->mLocalTextures.begin(); + local_tex_iter != baked_dict->mLocalTextures.end(); + ++local_tex_iter) + { + const ETextureIndex texture_index = *local_tex_iter; + const bool is_baked_ready = (is_layer_baked[baked_index] && mBakedTextureDatas[baked_index].mIsLoaded) || other_culled; + if (isSelf()) + { + setBakedReady(texture_index, is_baked_ready); + } + } + } + + // removeMissingBakedTextures() will call back into this rountine if something is removed, and can blow up the stack + static bool call_remove_missing = true; + if (call_remove_missing) + { + call_remove_missing = false; + removeMissingBakedTextures(); // May call back into this function if anything is removed + call_remove_missing = true; + } + + //refresh bakes on any attached objects + for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + LLViewerObject* attached_object = attachment_iter->get(); + if (attached_object && !attached_object->isDead()) + { + attached_object->refreshBakeTexture(); + + LLViewerObject::const_child_list_t& child_list = attached_object->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); ++iter) + { + LLViewerObject* objectp = *iter; + if (objectp && !objectp->isDead()) + { + objectp->refreshBakeTexture(); + } + } + } + } + } + + + +} + +// virtual +//----------------------------------------------------------------------------- +// setLocalTexture() +//----------------------------------------------------------------------------- +void LLVOAvatar::setLocalTexture( ETextureIndex type, LLViewerTexture* in_tex, bool baked_version_ready, U32 index ) +{ + // invalid for anyone but self + llassert(0); +} + +//virtual +void LLVOAvatar::setBakedReady(LLAvatarAppearanceDefines::ETextureIndex type, bool baked_version_exists, U32 index) +{ + // invalid for anyone but self + llassert(0); +} + +void LLVOAvatar::addChat(const LLChat& chat) +{ + std::deque::iterator chat_iter; + + mChats.push_back(chat); + + S32 chat_length = 0; + for( chat_iter = mChats.begin(); chat_iter != mChats.end(); ++chat_iter) + { + chat_length += chat_iter->mText.size(); + } + + // remove any excess chat + chat_iter = mChats.begin(); + while ((chat_length > MAX_BUBBLE_CHAT_LENGTH || mChats.size() > MAX_BUBBLE_CHAT_UTTERANCES) && chat_iter != mChats.end()) + { + chat_length -= chat_iter->mText.size(); + mChats.pop_front(); + chat_iter = mChats.begin(); + } + + mChatTimer.reset(); +} + +void LLVOAvatar::clearChat() +{ + mChats.clear(); +} + + +void LLVOAvatar::applyMorphMask(const U8* tex_data, S32 width, S32 height, S32 num_components, LLAvatarAppearanceDefines::EBakedTextureIndex index) +{ + if (index >= BAKED_NUM_INDICES) + { + LL_WARNS() << "invalid baked texture index passed to applyMorphMask" << LL_ENDL; + return; + } + + for (morph_list_t::const_iterator iter = mBakedTextureDatas[index].mMaskedMorphs.begin(); + iter != mBakedTextureDatas[index].mMaskedMorphs.end(); ++iter) + { + const LLMaskedMorph* maskedMorph = (*iter); + LLPolyMorphTarget* morph_target = dynamic_cast(maskedMorph->mMorphTarget); + if (morph_target) + { + morph_target->applyMask(tex_data, width, height, num_components, maskedMorph->mInvert); + } + } +} + +// returns true if morph masks are present and not valid for a given baked texture, false otherwise +bool LLVOAvatar::morphMaskNeedsUpdate(LLAvatarAppearanceDefines::EBakedTextureIndex index) +{ + if (index >= BAKED_NUM_INDICES) + { + return false; + } + + if (!mBakedTextureDatas[index].mMaskedMorphs.empty()) + { + if (isSelf()) + { + LLViewerTexLayerSet *layer_set = getTexLayerSet(index); + if (layer_set) + { + return !layer_set->isMorphValid(); + } + } + else + { + return false; + } + } + + return false; +} + +//----------------------------------------------------------------------------- +// releaseComponentTextures() +// release any component texture UUIDs for which we have a baked texture +// ! BACKWARDS COMPATIBILITY ! +// This is only called for non-self avatars, it can be taken out once component +// textures aren't communicated by non-self avatars. +//----------------------------------------------------------------------------- +void LLVOAvatar::releaseComponentTextures() +{ + // ! BACKWARDS COMPATIBILITY ! + // Detect if the baked hair texture actually wasn't sent, and if so set to default + if (isTextureDefined(TEX_HAIR_BAKED) && getImage(TEX_HAIR_BAKED,0)->getID() == getImage(TEX_SKIRT_BAKED,0)->getID()) + { + if (getImage(TEX_HAIR_BAKED,0)->getID() != IMG_INVISIBLE) + { + // Regression case of messaging system. Expected 21 textures, received 20. last texture is not valid so set to default + setTETexture(TEX_HAIR_BAKED, IMG_DEFAULT_AVATAR); + } + } + + for (U8 baked_index = 0; baked_index < BAKED_NUM_INDICES; baked_index++) + { + const LLAvatarAppearanceDictionary::BakedEntry * bakedDicEntry = LLAvatarAppearance::getDictionary()->getBakedTexture((EBakedTextureIndex)baked_index); + // skip if this is a skirt and av is not wearing one, or if we don't have a baked texture UUID + if (!isTextureDefined(bakedDicEntry->mTextureIndex) + && ( (baked_index != BAKED_SKIRT) || isWearingWearableType(LLWearableType::WT_SKIRT) )) + { + continue; + } + + for (U8 texture = 0; texture < bakedDicEntry->mLocalTextures.size(); texture++) + { + const U8 te = (ETextureIndex)bakedDicEntry->mLocalTextures[texture]; + setTETexture(te, IMG_DEFAULT_AVATAR); + } + } +} + +void LLVOAvatar::dumpAvatarTEs( const std::string& context ) const +{ + LL_DEBUGS("Avatar") << avString() << (isSelf() ? "Self: " : "Other: ") << context << LL_ENDL; + for (LLAvatarAppearanceDictionary::Textures::const_iterator iter = LLAvatarAppearance::getDictionary()->getTextures().begin(); + iter != LLAvatarAppearance::getDictionary()->getTextures().end(); + ++iter) + { + const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = iter->second; + // TODO: MULTI-WEARABLE: handle multiple textures for self + const LLViewerTexture* te_image = getImage(iter->first,0); + if( !te_image ) + { + LL_DEBUGS("Avatar") << avString() << " " << texture_dict->mName << ": null ptr" << LL_ENDL; + } + else if( te_image->getID().isNull() ) + { + LL_DEBUGS("Avatar") << avString() << " " << texture_dict->mName << ": null UUID" << LL_ENDL; + } + else if( te_image->getID() == IMG_DEFAULT ) + { + LL_DEBUGS("Avatar") << avString() << " " << texture_dict->mName << ": IMG_DEFAULT" << LL_ENDL; + } + else if( te_image->getID() == IMG_DEFAULT_AVATAR ) + { + LL_DEBUGS("Avatar") << avString() << " " << texture_dict->mName << ": IMG_DEFAULT_AVATAR" << LL_ENDL; + } + else + { + LL_DEBUGS("Avatar") << avString() << " " << texture_dict->mName << ": " << te_image->getID() << LL_ENDL; + } + } +} + +//----------------------------------------------------------------------------- +// clampAttachmentPositions() +//----------------------------------------------------------------------------- +void LLVOAvatar::clampAttachmentPositions() +{ + if (isDead()) + { + return; + } + for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + if (attachment) + { + attachment->clampObjectPosition(); + } + } +} + +bool LLVOAvatar::hasHUDAttachment() const +{ + for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + if (attachment->getIsHUDAttachment() && attachment->getNumObjects() > 0) + { + return true; + } + } + return false; +} + +LLBBox LLVOAvatar::getHUDBBox() const +{ + LLBBox bbox; + for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + if (attachment->getIsHUDAttachment()) + { + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + const LLViewerObject* attached_object = attachment_iter->get(); + if (attached_object == NULL) + { + LL_WARNS() << "HUD attached object is NULL!" << LL_ENDL; + continue; + } + // initialize bounding box to contain identity orientation and center point for attached object + bbox.addPointLocal(attached_object->getPosition()); + // add rotated bounding box for attached object + bbox.addBBoxAgent(attached_object->getBoundingBoxAgent()); + LLViewerObject::const_child_list_t& child_list = attached_object->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); + ++iter) + { + const LLViewerObject* child_objectp = *iter; + bbox.addBBoxAgent(child_objectp->getBoundingBoxAgent()); + } + } + } + } + + return bbox; +} + +//----------------------------------------------------------------------------- +// onFirstTEMessageReceived() +//----------------------------------------------------------------------------- +void LLVOAvatar::onFirstTEMessageReceived() +{ + LL_DEBUGS("Avatar") << avString() << LL_ENDL; + if( !mFirstTEMessageReceived ) + { + mFirstTEMessageReceived = true; + + LLLoadedCallbackEntry::source_callback_list_t* src_callback_list = NULL ; + bool paused = false ; + if(!isSelf()) + { + src_callback_list = &mCallbackTextureList ; + paused = !isVisible(); + } + + for (U32 i = 0; i < mBakedTextureDatas.size(); i++) + { + const bool layer_baked = isTextureDefined(mBakedTextureDatas[i].mTextureIndex); + + // Use any baked textures that we have even if they haven't downloaded yet. + // (That is, don't do a transition from unbaked to baked.) + if (layer_baked) + { + LLViewerFetchedTexture* image = LLViewerTextureManager::staticCastToFetchedTexture(getImage( mBakedTextureDatas[i].mTextureIndex, 0 ), true) ; + mBakedTextureDatas[i].mLastTextureID = image->getID(); + // If we have more than one texture for the other baked layers, we'll want to call this for them too. + if ( (image->getID() != IMG_INVISIBLE) && ((i == BAKED_HEAD) || (i == BAKED_UPPER) || (i == BAKED_LOWER)) ) + { + image->setLoadedCallback( onBakedTextureMasksLoaded, MORPH_MASK_REQUESTED_DISCARD, true, true, new LLTextureMaskData( mID ), + src_callback_list, paused); + } + LL_DEBUGS("Avatar") << avString() << "layer_baked, setting onInitialBakedTextureLoaded as callback" << LL_ENDL; + image->setLoadedCallback( onInitialBakedTextureLoaded, MAX_DISCARD_LEVEL, false, false, new LLUUID( mID ), + src_callback_list, paused ); + if (image->getDiscardLevel() < 0 && !paused) + { + mLastTexCallbackAddedTime.reset(); + } + // this could add paused texture callbacks + mLoadedCallbacksPaused |= paused; + } + } + + mMeshTexturesDirty = true; + gPipeline.markGLRebuild(this); + + mFirstAppearanceMessageTimer.reset(); + mFullyLoadedTimer.reset(); + } +} + +//----------------------------------------------------------------------------- +// bool visualParamWeightsAreDefault() +//----------------------------------------------------------------------------- +bool LLVOAvatar::visualParamWeightsAreDefault() +{ + bool rtn = true; + + bool is_wearing_skirt = isWearingWearableType(LLWearableType::WT_SKIRT); + for (LLVisualParam *param = getFirstVisualParam(); + param; + param = getNextVisualParam()) + { + if (param->isTweakable()) + { + LLViewerVisualParam* vparam = dynamic_cast(param); + llassert(vparam); + bool is_skirt_param = vparam && + LLWearableType::WT_SKIRT == vparam->getWearableType(); + if (param->getWeight() != param->getDefaultWeight() && + // we have to not care whether skirt weights are default, if we're not actually wearing a skirt + (is_wearing_skirt || !is_skirt_param)) + { + //LL_INFOS() << "param '" << param->getName() << "'=" << param->getWeight() << " which differs from default=" << param->getDefaultWeight() << LL_ENDL; + rtn = false; + break; + } + } + } + + //LL_INFOS() << "params are default ? " << int(rtn) << LL_ENDL; + + return rtn; +} + +void dump_visual_param(apr_file_t* file, LLVisualParam* viewer_param, F32 value) +{ + std::string type_string = "unknown"; + if (dynamic_cast(viewer_param)) + type_string = "param_alpha"; + if (dynamic_cast(viewer_param)) + type_string = "param_color"; + if (dynamic_cast(viewer_param)) + type_string = "param_driver"; + if (dynamic_cast(viewer_param)) + type_string = "param_morph"; + if (dynamic_cast(viewer_param)) + type_string = "param_skeleton"; + S32 wtype = -1; + LLViewerVisualParam *vparam = dynamic_cast(viewer_param); + if (vparam) + { + wtype = vparam->getWearableType(); + } + S32 u8_value = F32_to_U8(value,viewer_param->getMinWeight(),viewer_param->getMaxWeight()); + apr_file_printf(file, "\t\t\n", + viewer_param->getID(), viewer_param->getName().c_str(), viewer_param->getDisplayName().c_str(), value, u8_value, type_string.c_str(), + LLWearableType::getInstance()->getTypeName(LLWearableType::EType(wtype)).c_str(), + viewer_param->getGroup()); + } + + +void LLVOAvatar::dumpAppearanceMsgParams( const std::string& dump_prefix, + const LLAppearanceMessageContents& contents) +{ + std::string outfilename = get_sequential_numbered_file_name(dump_prefix,".xml"); + const std::vector& params_for_dump = contents.mParamWeights; + const LLTEContents& tec = contents.mTEContents; + + LLAPRFile outfile; + std::string fullpath = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,outfilename); + outfile.open(fullpath, LL_APR_WB ); + apr_file_t* file = outfile.getFileHandle(); + if (!file) + { + return; + } + else + { + LL_DEBUGS("Avatar") << "dumping appearance message to " << fullpath << LL_ENDL; + } + + apr_file_printf(file, "
\n"); + apr_file_printf(file, "\t\t\n", contents.mCOFVersion); + apr_file_printf(file, "\t\t\n", contents.mAppearanceVersion); + apr_file_printf(file, "
\n"); + + apr_file_printf(file, "\n\n"); + LLVisualParam* param = getFirstVisualParam(); + for (S32 i = 0; i < params_for_dump.size(); i++) + { + while( param && ((param->getGroup() != VISUAL_PARAM_GROUP_TWEAKABLE) && + (param->getGroup() != VISUAL_PARAM_GROUP_TRANSMIT_NOT_TWEAKABLE)) ) // should not be any of group VISUAL_PARAM_GROUP_TWEAKABLE_NO_TRANSMIT + { + param = getNextVisualParam(); + } + LLViewerVisualParam* viewer_param = (LLViewerVisualParam*)param; + F32 value = params_for_dump[i]; + dump_visual_param(file, viewer_param, value); + param = getNextVisualParam(); + } + apr_file_printf(file, "\n"); + + apr_file_printf(file, "\n\n"); + for (U32 i = 0; i < tec.face_count; i++) + { + std::string uuid_str; + ((LLUUID*)tec.image_data)[i].toString(uuid_str); + apr_file_printf( file, "\t\t\n", i, uuid_str.c_str()); + } + apr_file_printf(file, "\n"); +} + +void LLVOAvatar::parseAppearanceMessage(LLMessageSystem* mesgsys, LLAppearanceMessageContents& contents) +{ + parseTEMessage(mesgsys, _PREHASH_ObjectData, -1, contents.mTEContents); + + // Parse the AppearanceData field, if any. + if (mesgsys->has(_PREHASH_AppearanceData)) + { + U8 av_u8; + mesgsys->getU8Fast(_PREHASH_AppearanceData, _PREHASH_AppearanceVersion, av_u8, 0); + contents.mAppearanceVersion = av_u8; + //LL_DEBUGS("Avatar") << "appversion set by AppearanceData field: " << contents.mAppearanceVersion << LL_ENDL; + mesgsys->getS32Fast(_PREHASH_AppearanceData, _PREHASH_CofVersion, contents.mCOFVersion, 0); + // For future use: + //mesgsys->getU32Fast(_PREHASH_AppearanceData, _PREHASH_Flags, appearance_flags, 0); + } + + // Parse the AppearanceHover field, if any. + contents.mHoverOffsetWasSet = false; + if (mesgsys->has(_PREHASH_AppearanceHover)) + { + LLVector3 hover; + mesgsys->getVector3Fast(_PREHASH_AppearanceHover, _PREHASH_HoverHeight, hover); + //LL_DEBUGS("Avatar") << avString() << " hover received " << hover.mV[ VX ] << "," << hover.mV[ VY ] << "," << hover.mV[ VZ ] << LL_ENDL; + contents.mHoverOffset = hover; + contents.mHoverOffsetWasSet = true; + } + + // Get attachment info, if sent + LLUUID attachment_id; + U8 attach_point; + S32 attach_count = mesgsys->getNumberOfBlocksFast(_PREHASH_AttachmentBlock); + LL_DEBUGS("AVAppearanceAttachments") << "Agent " << getID() << " has " + << attach_count << " attachments" << LL_ENDL; + size_t old_size = mSimAttachments.size(); + mSimAttachments.clear(); + for (S32 attach_i = 0; attach_i < attach_count; attach_i++) + { + mesgsys->getUUIDFast(_PREHASH_AttachmentBlock, _PREHASH_ID, attachment_id, attach_i); + mesgsys->getU8Fast(_PREHASH_AttachmentBlock, _PREHASH_AttachmentPoint, attach_point, attach_i); + LL_DEBUGS("AVAppearanceAttachments") << "AV " << getID() << " has attachment " << attach_i << " " + << (attachment_id.isNull() ? "pending" : attachment_id.asString()) + << " on point " << (S32)attach_point << LL_ENDL; + + if (attachment_id.notNull()) + { + mSimAttachments[attachment_id] = attach_point; + } + else + { + // at the moment viewer is only interested in non-null attachments + LL_DEBUGS("AVAppearanceAttachments") << "AV " << getID() + << " has null attachment on point " << (S32)attach_point + << ", discarding" << LL_ENDL; + } + } + + // todo? Doesn't detect if attachments were switched + if (old_size != mSimAttachments.size()) + { + mLastCloudAttachmentCount = 0; + mLastCloudAttachmentChangeTime.reset(); + if (!isFullyLoaded()) + { + mFullyLoadedTimer.reset(); + } + } + + // Parse visual params, if any. + S32 num_blocks = mesgsys->getNumberOfBlocksFast(_PREHASH_VisualParam); + if( num_blocks > 1) + { + //LL_DEBUGS("Avatar") << avString() << " handle visual params, num_blocks " << num_blocks << LL_ENDL; + + LLVisualParam* param = getFirstVisualParam(); + llassert(param); // if this ever fires, we should do the same as when num_blocks<=1 + if (!param) + { + LL_WARNS() << "No visual params!" << LL_ENDL; + } + else + { + for( S32 i = 0; i < num_blocks; i++ ) + { + while( param && ((param->getGroup() != VISUAL_PARAM_GROUP_TWEAKABLE) && + (param->getGroup() != VISUAL_PARAM_GROUP_TRANSMIT_NOT_TWEAKABLE)) ) // should not be any of group VISUAL_PARAM_GROUP_TWEAKABLE_NO_TRANSMIT + { + param = getNextVisualParam(); + } + + if( !param ) + { + // more visual params supplied than expected - just process what we know about + break; + } + + U8 value; + mesgsys->getU8Fast(_PREHASH_VisualParam, _PREHASH_ParamValue, value, i); + F32 newWeight = U8_to_F32(value, param->getMinWeight(), param->getMaxWeight()); + contents.mParamWeights.push_back(newWeight); + contents.mParams.push_back(param); + + param = getNextVisualParam(); + } + } + + const S32 expected_tweakable_count = getVisualParamCountInGroup(VISUAL_PARAM_GROUP_TWEAKABLE) + + getVisualParamCountInGroup(VISUAL_PARAM_GROUP_TRANSMIT_NOT_TWEAKABLE); // don't worry about VISUAL_PARAM_GROUP_TWEAKABLE_NO_TRANSMIT + if (num_blocks != expected_tweakable_count) + { + LL_DEBUGS("Avatar") << "Number of params in AvatarAppearance msg (" << num_blocks << ") does not match number of tweakable params in avatar xml file (" << expected_tweakable_count << "). Processing what we can. object: " << getID() << LL_ENDL; + } + } + else + { + LL_DEBUGS("Avatar") << "AvatarAppearance msg received without any parameters, object: " << getID() << LL_ENDL; + } + + LLVisualParam* appearance_version_param = getVisualParam(11000); + if (appearance_version_param) + { + std::vector::iterator it = std::find(contents.mParams.begin(), contents.mParams.end(),appearance_version_param); + if (it != contents.mParams.end()) + { + S32 index = it - contents.mParams.begin(); + contents.mParamAppearanceVersion = ll_round(contents.mParamWeights[index]); + //LL_DEBUGS("Avatar") << "appversion req by appearance_version param: " << contents.mParamAppearanceVersion << LL_ENDL; + } + } +} + +bool resolve_appearance_version(const LLAppearanceMessageContents& contents, S32& appearance_version) +{ + appearance_version = -1; + + if ((contents.mAppearanceVersion) >= 0 && + (contents.mParamAppearanceVersion >= 0) && + (contents.mAppearanceVersion != contents.mParamAppearanceVersion)) + { + LL_WARNS() << "inconsistent appearance_version settings - field: " << + contents.mAppearanceVersion << ", param: " << contents.mParamAppearanceVersion << LL_ENDL; + return false; + } + if (contents.mParamAppearanceVersion >= 0) // use visual param if available. + { + appearance_version = contents.mParamAppearanceVersion; + } + else if (contents.mAppearanceVersion > 0) + { + appearance_version = contents.mAppearanceVersion; + } + else // still not set, go with 1. + { + appearance_version = 1; + } + //LL_DEBUGS("Avatar") << "appearance version info - field " << contents.mAppearanceVersion + // << " param: " << contents.mParamAppearanceVersion + // << " final: " << appearance_version << LL_ENDL; + return true; +} + +//----------------------------------------------------------------------------- +// processAvatarAppearance() +//----------------------------------------------------------------------------- +void LLVOAvatar::processAvatarAppearance( LLMessageSystem* mesgsys ) +{ + LL_DEBUGS("Avatar") << "starts" << LL_ENDL; + + static LLCachedControl enable_verbose_dumps(gSavedSettings, "DebugAvatarAppearanceMessage"); + static LLCachedControl block_avatar_appearance_messages(gSavedSettings, "BlockAvatarAppearanceMessages"); + + std::string dump_prefix = getFullname() + "_" + (isSelf()?"s":"o") + "_"; + if (block_avatar_appearance_messages) + { + LL_WARNS() << "Blocking AvatarAppearance message" << LL_ENDL; + return; + } + + mLastAppearanceMessageTimer.reset(); + + LLPointer contents(new LLAppearanceMessageContents); + parseAppearanceMessage(mesgsys, *contents); + if (enable_verbose_dumps) + { + dumpAppearanceMsgParams(dump_prefix + "appearance_msg", *contents); + } + + S32 appearance_version; + if (!resolve_appearance_version(*contents, appearance_version)) + { + LL_WARNS() << "bad appearance version info, discarding" << LL_ENDL; + return; + } + llassert(appearance_version > 0); + if (appearance_version > 1) + { + LL_WARNS() << "unsupported appearance version " << appearance_version << ", discarding appearance message" << LL_ENDL; + return; + } + + S32 thisAppearanceVersion(contents->mCOFVersion); + if (isSelf()) + { // In the past this was considered to be the canonical COF version, + // that is no longer the case. The canonical version is maintained + // by the AIS code and should match the COF version there. Even so, + // we must prevent rolling this one backwards backwards or processing + // stale versions. + + S32 aisCOFVersion(LLAppearanceMgr::instance().getCOFVersion()); + + LL_DEBUGS("Avatar") << "handling self appearance message #" << thisAppearanceVersion << + " (highest seen #" << mLastUpdateReceivedCOFVersion << + ") (AISCOF=#" << aisCOFVersion << ")" << LL_ENDL; + + if (mLastUpdateReceivedCOFVersion >= thisAppearanceVersion) + { + LL_WARNS("Avatar") << "Stale appearance received #" << thisAppearanceVersion << + " attempt to roll back from #" << mLastUpdateReceivedCOFVersion << + "... dropping." << LL_ENDL; + return; + } + if (isEditingAppearance()) + { + LL_DEBUGS("Avatar") << "Editing appearance. Dropping appearance update." << LL_ENDL; + return; + } + + } + + // SUNSHINE CLEANUP - is this case OK now? + S32 num_params = contents->mParamWeights.size(); + if (num_params <= 1) + { + // In this case, we have no reliable basis for knowing + // appearance version, which may cause us to look for baked + // textures in the wrong place and flag them as missing + // assets. + LL_DEBUGS("Avatar") << "ignoring appearance message due to lack of params" << LL_ENDL; + return; + } + + // No backsies zone - if we get here, the message should be valid and usable, will be processed. + // Note: + // RequestAgentUpdateAppearanceResponder::onRequestRequested() + // assumes that cof version is only updated with server-bake + // appearance messages. + if (isSelf()) + { + LL_INFOS("Avatar") << "Processing appearance message version " << thisAppearanceVersion << LL_ENDL; + } + else + { + LL_INFOS("Avatar") << "Processing appearance message for " << getID() << ", version " << thisAppearanceVersion << LL_ENDL; + } + + // Note: + // locally the COF is maintained via LLInventoryModel::accountForUpdate + // which is called from various places. This should match the simhost's + // idea of what the COF version is. AIS however maintains its own version + // of the COF that should be considered canonical. + mLastUpdateReceivedCOFVersion = thisAppearanceVersion; + + mLastProcessedAppearance = contents; + + bool slam_params = false; + applyParsedAppearanceMessage(*contents, slam_params); + if (getOverallAppearance() != AOA_NORMAL) + { + resetSkeleton(false); + } +} + +void LLVOAvatar::applyParsedAppearanceMessage(LLAppearanceMessageContents& contents, bool slam_params) +{ + S32 num_params = contents.mParamWeights.size(); + ESex old_sex = getSex(); + + if (applyParsedTEMessage(contents.mTEContents) > 0 && isChanged(TEXTURE)) + { + updateVisualComplexity(); + } + + // prevent the overwriting of valid baked textures with invalid baked textures + for (U8 baked_index = 0; baked_index < mBakedTextureDatas.size(); baked_index++) + { + if (!isTextureDefined(mBakedTextureDatas[baked_index].mTextureIndex) + && mBakedTextureDatas[baked_index].mLastTextureID != IMG_DEFAULT + && baked_index != BAKED_SKIRT && baked_index != BAKED_LEFT_ARM && baked_index != BAKED_LEFT_LEG && baked_index != BAKED_AUX1 && baked_index != BAKED_AUX2 && baked_index != BAKED_AUX3) + { + LL_DEBUGS("Avatar") << avString() << " baked_index " << (S32) baked_index << " using mLastTextureID " << mBakedTextureDatas[baked_index].mLastTextureID << LL_ENDL; + setTEImage(mBakedTextureDatas[baked_index].mTextureIndex, + LLViewerTextureManager::getFetchedTexture(mBakedTextureDatas[baked_index].mLastTextureID, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE)); + } + else + { + LL_DEBUGS("Avatar") << avString() << " baked_index " << (S32) baked_index << " using texture id " + << getTE(mBakedTextureDatas[baked_index].mTextureIndex)->getID() << LL_ENDL; + } + } + + // runway - was + // if (!is_first_appearance_message ) + // which means it would be called on second appearance message - probably wrong. + bool is_first_appearance_message = !mFirstAppearanceMessageReceived; + mFirstAppearanceMessageReceived = true; + + //LL_DEBUGS("Avatar") << avString() << "processAvatarAppearance start " << mID + // << " first? " << is_first_appearance_message << " self? " << isSelf() << LL_ENDL; + + if (is_first_appearance_message ) + { + onFirstTEMessageReceived(); + } + + setCompositeUpdatesEnabled( false ); + gPipeline.markGLRebuild(this); + + // Apply visual params + if( num_params > 1) + { + //LL_DEBUGS("Avatar") << avString() << " handle visual params, num_params " << num_params << LL_ENDL; + bool params_changed = false; + bool interp_params = false; + S32 params_changed_count = 0; + + for( S32 i = 0; i < num_params; i++ ) + { + LLVisualParam* param = contents.mParams[i]; + F32 newWeight = contents.mParamWeights[i]; + + if (slam_params || is_first_appearance_message || (param->getWeight() != newWeight)) + { + params_changed = true; + params_changed_count++; + + if(is_first_appearance_message || slam_params) + { + //LL_DEBUGS("Avatar") << "param slam " << i << " " << newWeight << LL_ENDL; + param->setWeight(newWeight); + } + else + { + interp_params = true; + param->setAnimationTarget(newWeight); + } + } + } + const S32 expected_tweakable_count = getVisualParamCountInGroup(VISUAL_PARAM_GROUP_TWEAKABLE) + + getVisualParamCountInGroup(VISUAL_PARAM_GROUP_TRANSMIT_NOT_TWEAKABLE); // don't worry about VISUAL_PARAM_GROUP_TWEAKABLE_NO_TRANSMIT + if (num_params != expected_tweakable_count) + { + LL_DEBUGS("Avatar") << "Number of params in AvatarAppearance msg (" << num_params << ") does not match number of tweakable params in avatar xml file (" << expected_tweakable_count << "). Processing what we can. object: " << getID() << LL_ENDL; + } + + LL_DEBUGS("Avatar") << "Changed " << params_changed_count << " params" << LL_ENDL; + if (params_changed) + { + if (interp_params) + { + startAppearanceAnimation(); + } + updateVisualParams(); + + ESex new_sex = getSex(); + if( old_sex != new_sex ) + { + updateSexDependentLayerSets(); + } + } + + llassert( getSex() == ((getVisualParamWeight( "male" ) > 0.5f) ? SEX_MALE : SEX_FEMALE) ); + } + else + { + // AvatarAppearance message arrived without visual params + LL_DEBUGS("Avatar") << avString() << "no visual params" << LL_ENDL; + + const F32 LOADING_TIMEOUT_SECONDS = 60.f; + // this isn't really a problem if we already have a non-default shape + if (visualParamWeightsAreDefault() && mRuthTimer.getElapsedTimeF32() > LOADING_TIMEOUT_SECONDS) + { + // re-request appearance, hoping that it comes back with a shape next time + LL_INFOS() << "Re-requesting AvatarAppearance for object: " << getID() << LL_ENDL; + LLAvatarPropertiesProcessor::getInstance()->sendAvatarTexturesRequest(getID()); + mRuthTimer.reset(); + } + else + { + LL_INFOS() << "That's okay, we already have a non-default shape for object: " << getID() << LL_ENDL; + // we don't really care. + } + } + + if (contents.mHoverOffsetWasSet && !isSelf()) + { + // Got an update for some other avatar + // Ignore updates for self, because we have a more authoritative value in the preferences. + setHoverOffset(contents.mHoverOffset); + LL_DEBUGS("Avatar") << avString() << "setting hover to " << contents.mHoverOffset[2] << LL_ENDL; + } + + if (!contents.mHoverOffsetWasSet && !isSelf()) + { + // If we don't get a value at all, we are presumably in a + // region that does not support hover height. + LL_WARNS() << avString() << "zeroing hover because not defined in appearance message" << LL_ENDL; + setHoverOffset(LLVector3(0.0, 0.0, 0.0)); + } + + setCompositeUpdatesEnabled( true ); + + // If all of the avatars are completely baked, release the global image caches to conserve memory. + LLVOAvatar::cullAvatarsByPixelArea(); + + if (isSelf()) + { + mUseLocalAppearance = false; + } + + updateMeshTextures(); + updateMeshVisibility(); + +} + +LLViewerTexture* LLVOAvatar::getBakedTexture(const U8 te) +{ + if (te < 0 || te >= BAKED_NUM_INDICES) + { + return NULL; + } + + bool is_layer_baked = isTextureDefined(mBakedTextureDatas[te].mTextureIndex); + + LLViewerTexLayerSet* layerset = NULL; + layerset = getTexLayerSet(te); + + + if (!isEditingAppearance() && is_layer_baked) + { + LLViewerFetchedTexture* baked_img = LLViewerTextureManager::staticCastToFetchedTexture(getImage(mBakedTextureDatas[te].mTextureIndex, 0), true); + return baked_img; + } + else if (layerset && isEditingAppearance()) + { + layerset->createComposite(); + layerset->setUpdatesEnabled(true); + + return layerset->getViewerComposite(); + } + + return NULL; + + +} + +const LLVOAvatar::MatrixPaletteCache& LLVOAvatar::updateSkinInfoMatrixPalette(const LLMeshSkinInfo* skin) +{ + U64 hash = skin->mHash; + MatrixPaletteCache& entry = mMatrixPaletteCache[hash]; + + if (entry.mFrame != gFrameCount) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + + entry.mFrame = gFrameCount; + + //build matrix palette + U32 count = LLSkinningUtil::getMeshJointCount(skin); + entry.mMatrixPalette.resize(count); + LLSkinningUtil::initSkinningMatrixPalette(&(entry.mMatrixPalette[0]), count, skin, this); + + const LLMatrix4a* mat = &(entry.mMatrixPalette[0]); + + entry.mGLMp.resize(count * 12); + + F32* mp = &(entry.mGLMp[0]); + + for (U32 i = 0; i < count; ++i) + { + F32* m = (F32*)mat[i].mMatrix[0].getF32ptr(); + + U32 idx = i * 12; + + mp[idx + 0] = m[0]; + mp[idx + 1] = m[1]; + mp[idx + 2] = m[2]; + mp[idx + 3] = m[12]; + + mp[idx + 4] = m[4]; + mp[idx + 5] = m[5]; + mp[idx + 6] = m[6]; + mp[idx + 7] = m[13]; + + mp[idx + 8] = m[8]; + mp[idx + 9] = m[9]; + mp[idx + 10] = m[10]; + mp[idx + 11] = m[14]; + } + } + + return entry; +} + +// static +void LLVOAvatar::getAnimLabels( std::vector* labels ) +{ + S32 i; + labels->reserve(gUserAnimStatesCount); + for( i = 0; i < gUserAnimStatesCount; i++ ) + { + labels->push_back( LLAnimStateLabels::getStateLabel( gUserAnimStates[i].mName ) ); + } + + // Special case to trigger away (AFK) state + labels->push_back( "Away From Keyboard" ); +} + +// static +void LLVOAvatar::getAnimNames( std::vector* names ) +{ + S32 i; + + names->reserve(gUserAnimStatesCount); + for( i = 0; i < gUserAnimStatesCount; i++ ) + { + names->push_back( std::string(gUserAnimStates[i].mName) ); + } + + // Special case to trigger away (AFK) state + names->push_back( "enter_away_from_keyboard_state" ); +} + +// static +void LLVOAvatar::onBakedTextureMasksLoaded( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata ) +{ + if (!userdata) return; + + //LL_INFOS() << "onBakedTextureMasksLoaded: " << src_vi->getID() << LL_ENDL; + const LLUUID id = src_vi->getID(); + + LLTextureMaskData* maskData = (LLTextureMaskData*) userdata; + LLVOAvatar* self = (LLVOAvatar*) gObjectList.findObject( maskData->mAvatarID ); + + // if discard level is 2 less than last discard level we processed, or we hit 0, + // then generate morph masks + if(self && success && (discard_level < maskData->mLastDiscardLevel - 2 || discard_level == 0)) + { + if(aux_src && aux_src->getComponents() == 1) + { + LLImageDataSharedLock lock(aux_src); + + if (!aux_src->getData()) + { + LL_ERRS() << "No auxiliary source (morph mask) data for image id " << id << LL_ENDL; + return; + } + + U32 gl_name; + LLImageGL::generateTextures(1, &gl_name ); + stop_glerror(); + + gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, gl_name); + stop_glerror(); + + LLImageGL::setManualImage( + GL_TEXTURE_2D, 0, GL_ALPHA8, + aux_src->getWidth(), aux_src->getHeight(), + GL_ALPHA, GL_UNSIGNED_BYTE, aux_src->getData()); + stop_glerror(); + + gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_BILINEAR); + + /* if( id == head_baked->getID() ) + if (self->mBakedTextureDatas[BAKED_HEAD].mTexLayerSet) + //LL_INFOS() << "onBakedTextureMasksLoaded for head " << id << " discard = " << discard_level << LL_ENDL; + self->mBakedTextureDatas[BAKED_HEAD].mTexLayerSet->applyMorphMask(aux_src->getData(), aux_src->getWidth(), aux_src->getHeight(), 1); + maskData->mLastDiscardLevel = discard_level; */ + bool found_texture_id = false; + for (LLAvatarAppearanceDictionary::Textures::const_iterator iter = LLAvatarAppearance::getDictionary()->getTextures().begin(); + iter != LLAvatarAppearance::getDictionary()->getTextures().end(); + ++iter) + { + + const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = iter->second; + if (texture_dict->mIsUsedByBakedTexture) + { + const ETextureIndex texture_index = iter->first; + const LLViewerTexture *baked_img = self->getImage(texture_index, 0); + if (baked_img && id == baked_img->getID()) + { + const EBakedTextureIndex baked_index = texture_dict->mBakedTextureIndex; + self->applyMorphMask(aux_src->getData(), aux_src->getWidth(), aux_src->getHeight(), 1, baked_index); + maskData->mLastDiscardLevel = discard_level; + if (self->mBakedTextureDatas[baked_index].mMaskTexName) + { + LLImageGL::deleteTextures(1, &(self->mBakedTextureDatas[baked_index].mMaskTexName)); + } + self->mBakedTextureDatas[baked_index].mMaskTexName = gl_name; + found_texture_id = true; + break; + } + } + } + if (!found_texture_id) + { + LL_INFOS() << "unexpected image id: " << id << LL_ENDL; + } + self->dirtyMesh(); + } + else + { + // this can happen when someone uses an old baked texture possibly provided by + // viewer-side baked texture caching + LL_WARNS() << "Masks loaded callback but NO aux source, id " << id << LL_ENDL; + } + } + + if (final || !success) + { + delete maskData; + } +} + +// static +void LLVOAvatar::onInitialBakedTextureLoaded( bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata ) +{ + LLUUID *avatar_idp = (LLUUID *)userdata; + LLVOAvatar *selfp = (LLVOAvatar *)gObjectList.findObject(*avatar_idp); + + if (selfp) + { + //LL_DEBUGS("Avatar") << selfp->avString() << "discard_level " << discard_level << " success " << success << " final " << final << LL_ENDL; + } + + if (!success && selfp) + { + selfp->removeMissingBakedTextures(); + } + if (final || !success ) + { + delete avatar_idp; + } +} + +// Static +void LLVOAvatar::onBakedTextureLoaded(bool success, + LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, + S32 discard_level, bool final, void* userdata) +{ + //LL_DEBUGS("Avatar") << "onBakedTextureLoaded: " << src_vi->getID() << LL_ENDL; + + LLUUID id = src_vi->getID(); + LLUUID *avatar_idp = (LLUUID *)userdata; + LLVOAvatar *selfp = (LLVOAvatar *)gObjectList.findObject(*avatar_idp); + if (selfp) + { + //LL_DEBUGS("Avatar") << selfp->avString() << "discard_level " << discard_level << " success " << success << " final " << final << " id " << src_vi->getID() << LL_ENDL; + } + + if (selfp && !success) + { + selfp->removeMissingBakedTextures(); + } + + if( final || !success ) + { + delete avatar_idp; + } + + if( selfp && success && final ) + { + selfp->useBakedTexture( id ); + } +} + + +// Called when baked texture is loaded and also when we start up with a baked texture +void LLVOAvatar::useBakedTexture( const LLUUID& id ) +{ + for (U32 i = 0; i < mBakedTextureDatas.size(); i++) + { + LLViewerTexture* image_baked = getImage( mBakedTextureDatas[i].mTextureIndex, 0 ); + if (id == image_baked->getID()) + { + //LL_DEBUGS("Avatar") << avString() << " i " << i << " id " << id << LL_ENDL; + mBakedTextureDatas[i].mIsLoaded = true; + mBakedTextureDatas[i].mLastTextureID = id; + mBakedTextureDatas[i].mIsUsed = true; + + if (isUsingLocalAppearance()) + { + LL_INFOS() << "not changing to baked texture while isUsingLocalAppearance" << LL_ENDL; + } + else + { + debugColorizeSubMeshes(i,LLColor4::green); + + avatar_joint_mesh_list_t::iterator iter = mBakedTextureDatas[i].mJointMeshes.begin(); + avatar_joint_mesh_list_t::iterator end = mBakedTextureDatas[i].mJointMeshes.end(); + for (; iter != end; ++iter) + { + LLAvatarJointMesh* mesh = (*iter); + if (mesh) + { + mesh->setTexture( image_baked ); + } + } + } + + const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = + LLAvatarAppearance::getDictionary()->getBakedTexture((EBakedTextureIndex)i); + for (texture_vec_t::const_iterator local_tex_iter = baked_dict->mLocalTextures.begin(); + local_tex_iter != baked_dict->mLocalTextures.end(); + ++local_tex_iter) + { + if (isSelf()) setBakedReady(*local_tex_iter, true); + } + + // ! BACKWARDS COMPATIBILITY ! + // Workaround for viewing avatars from old viewers that haven't baked hair textures. + // This is paired with similar code in updateMeshTextures that sets hair mesh color. + if (i == BAKED_HAIR) + { + avatar_joint_mesh_list_t::iterator iter = mBakedTextureDatas[i].mJointMeshes.begin(); + avatar_joint_mesh_list_t::iterator end = mBakedTextureDatas[i].mJointMeshes.end(); + for (; iter != end; ++iter) + { + LLAvatarJointMesh* mesh = (*iter); + if (mesh) + { + mesh->setColor( LLColor4::white ); + } + } + } + } + } + + dirtyMesh(); +} + +std::string get_sequential_numbered_file_name(const std::string& prefix, + const std::string& suffix) +{ + typedef std::map file_num_type; + static file_num_type file_nums; + file_num_type::iterator it = file_nums.find(prefix); + S32 num = 0; + if (it != file_nums.end()) + { + num = it->second; + } + file_nums[prefix] = num+1; + std::string outfilename = prefix + " " + llformat("%04d",num) + ".xml"; + std::replace(outfilename.begin(),outfilename.end(),' ','_'); + return outfilename; +} + +void dump_sequential_xml(const std::string outprefix, const LLSD& content) +{ + std::string outfilename = get_sequential_numbered_file_name(outprefix,".xml"); + std::string fullpath = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,outfilename); + llofstream ofs(fullpath.c_str(), std::ios_base::out); + ofs << LLSDOStreamer(content, LLSDFormatter::OPTIONS_PRETTY); + LL_DEBUGS("Avatar") << "results saved to: " << fullpath << LL_ENDL; +} + +void LLVOAvatar::getSortedJointNames(S32 joint_type, std::vector& result) const +{ + result.clear(); + if (joint_type==0) + { + avatar_joint_list_t::const_iterator iter = mSkeleton.begin(); + avatar_joint_list_t::const_iterator end = mSkeleton.end(); + for (; iter != end; ++iter) + { + LLJoint* pJoint = (*iter); + result.push_back(pJoint->getName()); + } + } + else if (joint_type==1) + { + for (S32 i = 0; i < mNumCollisionVolumes; i++) + { + LLAvatarJointCollisionVolume* pJoint = &mCollisionVolumes[i]; + result.push_back(pJoint->getName()); + } + } + else if (joint_type==2) + { + for (LLVOAvatar::attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); ++iter) + { + LLViewerJointAttachment* pJoint = iter->second; + if (!pJoint) continue; + result.push_back(pJoint->getName()); + } + } + std::sort(result.begin(), result.end()); +} + +void LLVOAvatar::dumpArchetypeXML(const std::string& prefix, bool group_by_wearables ) +{ + std::string outprefix(prefix); + if (outprefix.empty()) + { + outprefix = getFullname() + (isSelf()?"_s":"_o"); + } + if (outprefix.empty()) + { + outprefix = std::string("new_archetype"); + } + std::string outfilename = get_sequential_numbered_file_name(outprefix,".xml"); + + LLAPRFile outfile; + LLWearableType *wr_inst = LLWearableType::getInstance(); + std::string fullpath = gDirUtilp->getExpandedFilename(LL_PATH_LOGS,outfilename); + if (APR_SUCCESS == outfile.open(fullpath, LL_APR_WB )) + { + apr_file_t* file = outfile.getFileHandle(); + LL_INFOS() << "xmlfile write handle obtained : " << fullpath << LL_ENDL; + + apr_file_printf( file, "\n" ); + apr_file_printf( file, "\n" ); + apr_file_printf( file, "\n\t\n" ); + + bool agent_is_godlike = gAgent.isGodlikeWithoutAdminMenuFakery(); + + if (group_by_wearables) + { + for (S32 type = LLWearableType::WT_SHAPE; type < LLWearableType::WT_COUNT; type++) + { + const std::string& wearable_name = wr_inst->getTypeName((LLWearableType::EType)type); + apr_file_printf( file, "\n\t\t\n", wearable_name.c_str() ); + + for (LLVisualParam* param = getFirstVisualParam(); param; param = getNextVisualParam()) + { + LLViewerVisualParam* viewer_param = (LLViewerVisualParam*)param; + if( (viewer_param->getWearableType() == type) && + (viewer_param->isTweakable() ) ) + { + dump_visual_param(file, viewer_param, viewer_param->getWeight()); + } + } + + for (U8 te = 0; te < TEX_NUM_INDICES; te++) + { + if (LLAvatarAppearance::getDictionary()->getTEWearableType((ETextureIndex)te) == type) + { + // MULTIPLE_WEARABLES: extend to multiple wearables? + LLViewerTexture* te_image = getImage((ETextureIndex)te, 0); + if( te_image ) + { + std::string uuid_str = LLUUID().asString(); + if (agent_is_godlike) + { + te_image->getID().toString(uuid_str); + } + apr_file_printf( file, "\t\t\n", te, uuid_str.c_str()); + } + } + } + } + } + else + { + // Just dump all params sequentially. + for (LLVisualParam* param = getFirstVisualParam(); param; param = getNextVisualParam()) + { + LLViewerVisualParam* viewer_param = (LLViewerVisualParam*)param; + dump_visual_param(file, viewer_param, viewer_param->getWeight()); + } + + for (U8 te = 0; te < TEX_NUM_INDICES; te++) + { + // MULTIPLE_WEARABLES: extend to multiple wearables? + LLViewerTexture* te_image = getImage((ETextureIndex)te, 0); + if( te_image ) + { + std::string uuid_str = LLUUID().asString(); + if (agent_is_godlike) + { + te_image->getID().toString(uuid_str); + } + apr_file_printf( file, "\t\t\n", te, uuid_str.c_str()); + } + } + } + + // Root joint + const LLVector3& pos = mRoot->getPosition(); + const LLVector3& scale = mRoot->getScale(); + apr_file_printf( file, "\t\t\n", + mRoot->getName().c_str(), pos[0], pos[1], pos[2], scale[0], scale[1], scale[2]); + + // Bones + std::vector bone_names, cv_names, attach_names, all_names; + getSortedJointNames(0, bone_names); + getSortedJointNames(1, cv_names); + getSortedJointNames(2, attach_names); + all_names.insert(all_names.end(), bone_names.begin(), bone_names.end()); + all_names.insert(all_names.end(), cv_names.begin(), cv_names.end()); + all_names.insert(all_names.end(), attach_names.begin(), attach_names.end()); + + for (std::vector::iterator name_iter = bone_names.begin(); + name_iter != bone_names.end(); ++name_iter) + { + LLJoint *pJoint = getJoint(*name_iter); + const LLVector3& pos = pJoint->getPosition(); + const LLVector3& scale = pJoint->getScale(); + apr_file_printf( file, "\t\t\n", + pJoint->getName().c_str(), pos[0], pos[1], pos[2], scale[0], scale[1], scale[2]); + } + + // Collision volumes + for (std::vector::iterator name_iter = cv_names.begin(); + name_iter != cv_names.end(); ++name_iter) + { + LLJoint *pJoint = getJoint(*name_iter); + const LLVector3& pos = pJoint->getPosition(); + const LLVector3& scale = pJoint->getScale(); + apr_file_printf( file, "\t\t\n", + pJoint->getName().c_str(), pos[0], pos[1], pos[2], scale[0], scale[1], scale[2]); + } + + // Attachment joints + for (std::vector::iterator name_iter = attach_names.begin(); + name_iter != attach_names.end(); ++name_iter) + { + LLJoint *pJoint = getJoint(*name_iter); + if (!pJoint) continue; + const LLVector3& pos = pJoint->getPosition(); + const LLVector3& scale = pJoint->getScale(); + apr_file_printf( file, "\t\t\n", + pJoint->getName().c_str(), pos[0], pos[1], pos[2], scale[0], scale[1], scale[2]); + } + + // Joint pos overrides + for (std::vector::iterator name_iter = all_names.begin(); + name_iter != all_names.end(); ++name_iter) + { + LLJoint *pJoint = getJoint(*name_iter); + + LLVector3 pos; + LLUUID mesh_id; + + if (pJoint && pJoint->hasAttachmentPosOverride(pos,mesh_id)) + { + S32 num_pos_overrides; + std::set distinct_pos_overrides; + pJoint->getAllAttachmentPosOverrides(num_pos_overrides, distinct_pos_overrides); + apr_file_printf( file, "\t\t\n", + pJoint->getName().c_str(), pos[0], pos[1], pos[2], mesh_id.asString().c_str(), + num_pos_overrides, (S32) distinct_pos_overrides.size()); + } + } + // Joint scale overrides + for (std::vector::iterator name_iter = all_names.begin(); + name_iter != all_names.end(); ++name_iter) + { + LLJoint *pJoint = getJoint(*name_iter); + + LLVector3 scale; + LLUUID mesh_id; + + if (pJoint && pJoint->hasAttachmentScaleOverride(scale,mesh_id)) + { + S32 num_scale_overrides; + std::set distinct_scale_overrides; + pJoint->getAllAttachmentPosOverrides(num_scale_overrides, distinct_scale_overrides); + apr_file_printf( file, "\t\t\n", + pJoint->getName().c_str(), scale[0], scale[1], scale[2], mesh_id.asString().c_str(), + num_scale_overrides, (S32) distinct_scale_overrides.size()); + } + } + F32 pelvis_fixup; + LLUUID mesh_id; + if (hasPelvisFixup(pelvis_fixup, mesh_id)) + { + apr_file_printf( file, "\t\t\n", + pelvis_fixup, mesh_id.asString().c_str()); + } + + LLVector3 rp = getRootJoint()->getWorldPosition(); + LLVector4a rpv; + rpv.load3(rp.mV); + + for (S32 joint_num = 0; joint_num < LL_CHARACTER_MAX_ANIMATED_JOINTS; joint_num++) + { + LLJoint *joint = getJoint(joint_num); + if (joint_num < mJointRiggingInfoTab.size()) + { + LLJointRiggingInfo& rig_info = mJointRiggingInfoTab[joint_num]; + if (rig_info.isRiggedTo()) + { + LLMatrix4a mat; + LLVector4a new_extents[2]; + mat.loadu(joint->getWorldMatrix()); + matMulBoundBox(mat, rig_info.getRiggedExtents(), new_extents); + LLVector4a rrp[2]; + rrp[0].setSub(new_extents[0],rpv); + rrp[1].setSub(new_extents[1],rpv); + apr_file_printf( file, "\t\t\n", + joint_num, + joint->getName().c_str(), + rig_info.getRiggedExtents()[0][0], + rig_info.getRiggedExtents()[0][1], + rig_info.getRiggedExtents()[0][2], + rig_info.getRiggedExtents()[1][0], + rig_info.getRiggedExtents()[1][1], + rig_info.getRiggedExtents()[1][2], + rrp[0][0], + rrp[0][1], + rrp[0][2], + rrp[1][0], + rrp[1][1], + rrp[1][2] ); + } + } + } + + bool ultra_verbose = false; + if (isSelf() && ultra_verbose) + { + // show the cloned params inside the wearables as well. + gAgentAvatarp->dumpWearableInfo(outfile); + } + + apr_file_printf( file, "\t\n" ); + apr_file_printf( file, "\n\n" ); + + LLSD args; + args["PATH"] = fullpath; + LLNotificationsUtil::add("AppearanceToXMLSaved", args); + } + else + { + LLNotificationsUtil::add("AppearanceToXMLFailed"); + } + // File will close when handle goes out of scope +} + + +void LLVOAvatar::setVisibilityRank(U32 rank) +{ + if (mDrawable.isNull() || mDrawable->isDead()) + { + // do nothing + return; + } + mVisibilityRank = rank; +} + +// Assumes LLVOAvatar::sInstances has already been sorted. +S32 LLVOAvatar::getUnbakedPixelAreaRank() +{ + S32 rank = 1; + for (std::vector::iterator iter = LLCharacter::sInstances.begin(); + iter != LLCharacter::sInstances.end(); ++iter) + { + LLVOAvatar* inst = (LLVOAvatar*) *iter; + if (inst == this) + { + return rank; + } + else if (!inst->isDead() && !inst->isFullyBaked()) + { + rank++; + } + } + + llassert(0); + return 0; +} + +struct CompareScreenAreaGreater +{ + bool operator()(const LLCharacter* const& lhs, const LLCharacter* const& rhs) + { + return lhs->getPixelArea() > rhs->getPixelArea(); + } +}; + +// static +void LLVOAvatar::cullAvatarsByPixelArea() +{ + std::sort(LLCharacter::sInstances.begin(), LLCharacter::sInstances.end(), CompareScreenAreaGreater()); + + // Update the avatars that have changed status + U32 rank = 2; //1 is reserved for self. + for (std::vector::iterator iter = LLCharacter::sInstances.begin(); + iter != LLCharacter::sInstances.end(); ++iter) + { + LLVOAvatar* inst = (LLVOAvatar*) *iter; + bool culled; + if (inst->isSelf() || inst->isFullyBaked()) + { + culled = false; + } + else + { + culled = true; + } + + if (inst->mCulled != culled) + { + inst->mCulled = culled; + LL_DEBUGS() << "avatar " << inst->getID() << (culled ? " start culled" : " start not culled" ) << LL_ENDL; + inst->updateMeshTextures(); + } + + if (inst->isSelf()) + { + inst->setVisibilityRank(1); + } + else if (inst->mDrawable.notNull() && inst->mDrawable->isVisible()) + { + inst->setVisibilityRank(rank++); + } + } + + // runway - this doesn't really detect gray/grey state. + S32 grey_avatars = 0; + if (!LLVOAvatar::areAllNearbyInstancesBaked(grey_avatars)) + { + if (gFrameTimeSeconds != sUnbakedUpdateTime) // only update once per frame + { + sUnbakedUpdateTime = gFrameTimeSeconds; + sUnbakedTime += gFrameIntervalSeconds.value(); + } + if (grey_avatars > 0) + { + if (gFrameTimeSeconds != sGreyUpdateTime) // only update once per frame + { + sGreyUpdateTime = gFrameTimeSeconds; + sGreyTime += gFrameIntervalSeconds.value(); + } + } + } +} + +void LLVOAvatar::startAppearanceAnimation() +{ + if(!mAppearanceAnimating) + { + mAppearanceAnimating = true; + mAppearanceMorphTimer.reset(); + mLastAppearanceBlendTime = 0.f; + } +} + +// virtual +void LLVOAvatar::removeMissingBakedTextures() +{ +} + +//virtual +void LLVOAvatar::updateRegion(LLViewerRegion *regionp) +{ + LLViewerObject::updateRegion(regionp); +} + +// virtual +std::string LLVOAvatar::getFullname() const +{ + std::string name; + + LLNameValue* first = getNVPair("FirstName"); + LLNameValue* last = getNVPair("LastName"); + if (first && last) + { + name = LLCacheName::buildFullName( first->getString(), last->getString() ); + } + + return name; +} + +LLHost LLVOAvatar::getObjectHost() const +{ + LLViewerRegion* region = getRegion(); + if (region && !isDead()) + { + return region->getHost(); + } + else + { + return LLHost(); + } +} + +bool LLVOAvatar::updateLOD() +{ + if (mDrawable.isNull()) + { + return false; + } + + if (!LLPipeline::sImpostorRender && isImpostor() && 0 != mDrawable->getNumFaces() && mDrawable->getFace(0)->hasGeometry()) + { + return true; + } + + bool res = updateJointLODs(); + + LLFace* facep = mDrawable->getFace(0); + if (!facep || !facep->getVertexBuffer()) + { + dirtyMesh(2); + } + + if (mDirtyMesh >= 2 || mDrawable->isState(LLDrawable::REBUILD_GEOMETRY)) + { //LOD changed or new mesh created, allocate new vertex buffer if needed + updateMeshData(); + mDirtyMesh = 0; + mNeedsSkin = true; + mDrawable->clearState(LLDrawable::REBUILD_GEOMETRY); + } + updateVisibility(); + + return res; +} + +void LLVOAvatar::updateLODRiggedAttachments() +{ + updateLOD(); + rebuildRiggedAttachments(); +} + +void showRigInfoTabExtents(LLVOAvatar *avatar, LLJointRiggingInfoTab& tab, S32& count_rigged, S32& count_box) +{ + count_rigged = count_box = 0; + LLVector4a zero_vec; + zero_vec.clear(); + for (S32 i=0; igetJoint(i); + LL_DEBUGS("RigSpam") << "joint " << i << " name " << joint->getName() << " box " + << tab[i].getRiggedExtents()[0] << ", " << tab[i].getRiggedExtents()[1] << LL_ENDL; + if ((!tab[i].getRiggedExtents()[0].equals3(zero_vec)) || + (!tab[i].getRiggedExtents()[1].equals3(zero_vec))) + { + count_box++; + } + } + } +} + +void LLVOAvatar::getAssociatedVolumes(std::vector& volumes) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + for ( LLVOAvatar::attachment_map_t::iterator iter = mAttachmentPoints.begin(); iter != mAttachmentPoints.end(); ++iter ) + { + LLViewerJointAttachment* attachment = iter->second; + LLViewerJointAttachment::attachedobjs_vec_t::iterator attach_end = attachment->mAttachedObjects.end(); + + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attach_iter = attachment->mAttachedObjects.begin(); + attach_iter != attach_end; ++attach_iter) + { + LLViewerObject* attached_object = attach_iter->get(); + LLVOVolume *volume = dynamic_cast(attached_object); + if (volume) + { + volumes.push_back(volume); + if (volume->isAnimatedObject()) + { + // For animated object attachment, don't need + // the children. Will just get bounding box + // from the control avatar. + continue; + } + } + LLViewerObject::const_child_list_t& children = attached_object->getChildren(); + for (LLViewerObject::const_child_list_t::const_iterator it = children.begin(); + it != children.end(); ++it) + { + LLViewerObject *childp = *it; + LLVOVolume *volume = dynamic_cast(childp); + if (volume) + { + volumes.push_back(volume); + } + } + } + } + + LLControlAvatar *control_av = dynamic_cast(this); + if (control_av) + { + LLVOVolume *volp = control_av->mRootVolp; + if (volp) + { + volumes.push_back(volp); + LLViewerObject::const_child_list_t& children = volp->getChildren(); + for (LLViewerObject::const_child_list_t::const_iterator it = children.begin(); + it != children.end(); ++it) + { + LLViewerObject *childp = *it; + LLVOVolume *volume = dynamic_cast(childp); + if (volume) + { + volumes.push_back(volume); + } + } + } + } +} + +// virtual +void LLVOAvatar::updateRiggingInfo() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + + LL_DEBUGS("RigSpammish") << getFullname() << " updating rig tab" << LL_ENDL; + + std::vector volumes; + + getAssociatedVolumes(volumes); + + std::map curr_rigging_info_key; + { + // Get current rigging info key + for (std::vector::iterator it = volumes.begin(); it != volumes.end(); ++it) + { + LLVOVolume *vol = *it; + if (vol->isMesh() && vol->getVolume()) + { + const LLUUID& mesh_id = vol->getVolume()->getParams().getSculptID(); + S32 max_lod = llmax(vol->getLOD(), vol->mLastRiggingInfoLOD); + curr_rigging_info_key[mesh_id] = max_lod; + } + } + + // Check for key change, which indicates some change in volume composition or LOD. + if (curr_rigging_info_key == mLastRiggingInfoKey) + { + return; + } + } + + // Something changed. Update. + mLastRiggingInfoKey = curr_rigging_info_key; + mJointRiggingInfoTab.clear(); + for (std::vector::iterator it = volumes.begin(); it != volumes.end(); ++it) + { + LLVOVolume *vol = *it; + vol->updateRiggingInfo(); + mJointRiggingInfoTab.merge(vol->mJointRiggingInfoTab); + } + + //LL_INFOS() << "done update rig count is " << countRigInfoTab(mJointRiggingInfoTab) << LL_ENDL; + LL_DEBUGS("RigSpammish") << getFullname() << " after update rig tab:" << LL_ENDL; + S32 joint_count, box_count; + showRigInfoTabExtents(this, mJointRiggingInfoTab, joint_count, box_count); + LL_DEBUGS("RigSpammish") << "uses " << joint_count << " joints " << " nonzero boxes: " << box_count << LL_ENDL; +} + +// virtual +void LLVOAvatar::onActiveOverrideMeshesChanged() +{ + mJointRiggingInfoTab.setNeedsUpdate(true); +} + +U32 LLVOAvatar::getPartitionType() const +{ + // Avatars merely exist as drawables in the bridge partition + return mIsControlAvatar ? LLViewerRegion::PARTITION_CONTROL_AV : LLViewerRegion::PARTITION_AVATAR; +} + +//static +void LLVOAvatar::updateImpostors() +{ + LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD; + + std::vector instances_copy = LLCharacter::sInstances; + for (std::vector::iterator iter = instances_copy.begin(); + iter != instances_copy.end(); ++iter) + { + LLVOAvatar* avatar = (LLVOAvatar*) *iter; + if (!avatar->isDead() + && avatar->isVisible() + && avatar->isImpostor() + && avatar->needsImpostorUpdate()) + { + avatar->calcMutedAVColor(); + gPipeline.generateImpostor(avatar); + } + } + + LLCharacter::sAllowInstancesChange = true; +} + +// virtual +bool LLVOAvatar::isImpostor() +{ + return isVisuallyMuted() || (sLimitNonImpostors && (mUpdatePeriod > 1)); +} + +bool LLVOAvatar::shouldImpostor(const F32 rank_factor) +{ + if (isSelf()) + { + return false; + } + if (isVisuallyMuted()) + { + return true; + } + return sLimitNonImpostors && (mVisibilityRank > sMaxNonImpostors * rank_factor); +} + +bool LLVOAvatar::needsImpostorUpdate() const +{ + return mNeedsImpostorUpdate; +} + +const LLVector3& LLVOAvatar::getImpostorOffset() const +{ + return mImpostorOffset; +} + +const LLVector2& LLVOAvatar::getImpostorDim() const +{ + return mImpostorDim; +} + +void LLVOAvatar::setImpostorDim(const LLVector2& dim) +{ + mImpostorDim = dim; +} + +void LLVOAvatar::cacheImpostorValues() +{ + getImpostorValues(mImpostorExtents, mImpostorAngle, mImpostorDistance); +} + +void LLVOAvatar::getImpostorValues(LLVector4a* extents, LLVector3& angle, F32& distance) const +{ + const LLVector4a* ext = mDrawable->getSpatialExtents(); + extents[0] = ext[0]; + extents[1] = ext[1]; + + LLVector3 at = LLViewerCamera::getInstance()->getOrigin()-(getRenderPosition()+mImpostorOffset); + distance = at.normalize(); + F32 da = 1.f - (at*LLViewerCamera::getInstance()->getAtAxis()); + angle.mV[0] = LLViewerCamera::getInstance()->getYaw()*da; + angle.mV[1] = LLViewerCamera::getInstance()->getPitch()*da; + angle.mV[2] = da; +} + +// static +const U32 LLVOAvatar::NON_IMPOSTORS_MAX_SLIDER = 66; /* Must equal the maximum allowed the RenderAvatarMaxNonImpostors + * slider in panel_preferences_graphics1.xml */ + +// static +void LLVOAvatar::updateImpostorRendering(U32 newMaxNonImpostorsValue) +{ + U32 oldmax = sMaxNonImpostors; + bool oldflg = sLimitNonImpostors; + + if (NON_IMPOSTORS_MAX_SLIDER <= newMaxNonImpostorsValue) + { + sMaxNonImpostors = 0; + } + else + { + sMaxNonImpostors = newMaxNonImpostorsValue; + } + // the sLimitNonImpostors flag depends on whether or not sMaxNonImpostors is set to the no-limit value (0) + sLimitNonImpostors = (0 != sMaxNonImpostors); + if ( oldflg != sLimitNonImpostors ) + { + LL_DEBUGS("AvatarRender") + << "was " << (oldflg ? "use" : "don't use" ) << " impostors (max " << oldmax << "); " + << "now " << (sLimitNonImpostors ? "use" : "don't use" ) << " impostors (max " << sMaxNonImpostors << "); " + << LL_ENDL; + } +} + + +void LLVOAvatar::idleUpdateRenderComplexity() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + if (isControlAvatar()) + { + LLControlAvatar *cav = dynamic_cast(this); + bool is_attachment = cav && cav->mRootVolp && cav->mRootVolp->isAttachment(); // For attached animated objects + if (is_attachment) + { + // ARC for animated object attachments is accounted with the avatar they're attached to. + return; + } + } + + // Render Complexity + calculateUpdateRenderComplexity(); // Update mVisualComplexity if needed + + bool autotune = LLPerfStats::tunables.userAutoTuneEnabled && !mIsControlAvatar && !isSelf(); + if (autotune && !isDead()) + { + static LLCachedControl render_far_clip(gSavedSettings, "RenderFarClip", 64); + F32 radius = render_far_clip * render_far_clip; + + bool is_nearby = true; + if ((dist_vec_squared(getPositionGlobal(), gAgent.getPositionGlobal()) > radius) && + (dist_vec_squared(getPositionGlobal(), gAgentCamera.getCameraPositionGlobal()) > radius)) + { + is_nearby = false; + } + + if (is_nearby && (sAVsIgnoringARTLimit.size() < MIN_NONTUNED_AVS)) + { + if (std::count(sAVsIgnoringARTLimit.begin(), sAVsIgnoringARTLimit.end(), mID) == 0) + { + sAVsIgnoringARTLimit.push_back(mID); + } + } + else if (!is_nearby) + { + sAVsIgnoringARTLimit.erase(std::remove(sAVsIgnoringARTLimit.begin(), sAVsIgnoringARTLimit.end(), mID), + sAVsIgnoringARTLimit.end()); + } + updateNearbyAvatarCount(); + } +} + +void LLVOAvatar::updateNearbyAvatarCount() +{ + static LLFrameTimer agent_update_timer; + + if (agent_update_timer.getElapsedTimeF32() > 1.0f) + { + S32 avs_nearby = 0; + static LLCachedControl render_far_clip(gSavedSettings, "RenderFarClip", 64); + F32 radius = render_far_clip * render_far_clip; + std::vector::iterator char_iter = LLCharacter::sInstances.begin(); + while (char_iter != LLCharacter::sInstances.end()) + { + LLVOAvatar *avatar = dynamic_cast(*char_iter); + if (avatar && !avatar->isDead() && !avatar->isControlAvatar()) + { + if ((dist_vec_squared(avatar->getPositionGlobal(), gAgent.getPositionGlobal()) > radius) && + (dist_vec_squared(avatar->getPositionGlobal(), gAgentCamera.getCameraPositionGlobal()) > radius)) + { + char_iter++; + continue; + } + avs_nearby++; + } + char_iter++; + } + sAvatarsNearby = avs_nearby; + agent_update_timer.reset(); + } +} + +void LLVOAvatar::idleUpdateDebugInfo() +{ + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_AVATAR_DRAW_INFO)) + { + std::string info_line; + F32 red_level; + F32 green_level; + LLColor4 info_color; + LLFontGL::StyleFlags info_style; + + if ( !mText ) + { + initHudText(); + mText->setFadeDistance(20.0, 5.0); // limit clutter in large crowds + } + else + { + mText->clearString(); // clear debug text + } + + /* + * NOTE: the logic for whether or not each of the values below + * controls muting MUST match that in the isVisuallyMuted and isTooComplex methods. + */ + + static LLCachedControl max_render_cost(gSavedSettings, "RenderAvatarMaxComplexity", 0); + info_line = llformat("%d Complexity", mVisualComplexity); + + if (max_render_cost != 0) // zero means don't care, so don't bother coloring based on this + { + green_level = 1.f-llclamp(((F32) mVisualComplexity-(F32)max_render_cost)/(F32)max_render_cost, 0.f, 1.f); + red_level = llmin((F32) mVisualComplexity/(F32)max_render_cost, 1.f); + info_color.set(red_level, green_level, 0.0, 1.0); + info_style = ( mVisualComplexity > max_render_cost + ? LLFontGL::BOLD : LLFontGL::NORMAL ); + } + else + { + info_color.set(LLColor4::grey); + info_style = LLFontGL::NORMAL; + } + mText->addLine(info_line, info_color, info_style); + + // Visual rank + info_line = llformat("%d rank", mVisibilityRank); + // Use grey for imposters, white for normal rendering or no impostors + info_color.set(isImpostor() ? LLColor4::grey : (isControlAvatar() ? LLColor4::yellow : LLColor4::white)); + info_style = LLFontGL::NORMAL; + mText->addLine(info_line, info_color, info_style); + + // Triangle count + mText->addLine(std::string("VisTris ") + LLStringOps::getReadableNumber(mAttachmentVisibleTriangleCount), + info_color, info_style); + mText->addLine(std::string("EstMaxTris ") + LLStringOps::getReadableNumber(mAttachmentEstTriangleCount), + info_color, info_style); + + // Attachment Surface Area + static LLCachedControl max_attachment_area(gSavedSettings, "RenderAutoMuteSurfaceAreaLimit", 1000.0f); + info_line = llformat("%.0f m^2", mAttachmentSurfaceArea); + + if (max_render_cost != 0 && max_attachment_area != 0) // zero means don't care, so don't bother coloring based on this + { + green_level = 1.f-llclamp((mAttachmentSurfaceArea-max_attachment_area)/max_attachment_area, 0.f, 1.f); + red_level = llmin(mAttachmentSurfaceArea/max_attachment_area, 1.f); + info_color.set(red_level, green_level, 0.0, 1.0); + info_style = ( mAttachmentSurfaceArea > max_attachment_area + ? LLFontGL::BOLD : LLFontGL::NORMAL ); + + } + else + { + info_color.set(LLColor4::grey); + info_style = LLFontGL::NORMAL; + } + + mText->addLine(info_line, info_color, info_style); + + updateText(); // corrects position + } +} + +void LLVOAvatar::updateVisualComplexity() +{ + LL_DEBUGS("AvatarRender") << "avatar " << getID() << " appearance changed" << LL_ENDL; + // Set the cache time to in the past so it's updated ASAP + mVisualComplexityStale = true; +} + + +// Account for the complexity of a single top-level object associated +// with an avatar. This will be either an attached object or an animated +// object. +void LLVOAvatar::accountRenderComplexityForObject( + LLViewerObject *attached_object, + const F32 max_attachment_complexity, + LLVOVolume::texture_cost_t& textures, + U32& cost, + hud_complexity_list_t& hud_complexity_list, + object_complexity_list_t& object_complexity_list) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + if (attached_object && !attached_object->isHUDAttachment()) + { + mAttachmentVisibleTriangleCount += attached_object->recursiveGetTriangleCount(); + mAttachmentEstTriangleCount += attached_object->recursiveGetEstTrianglesMax(); + mAttachmentSurfaceArea += attached_object->recursiveGetScaledSurfaceArea(); + + textures.clear(); + const LLDrawable* drawable = attached_object->mDrawable; + if (drawable) + { + const LLVOVolume* volume = drawable->getVOVolume(); + if (volume) + { + F32 attachment_total_cost = 0; + F32 attachment_volume_cost = 0; + F32 attachment_texture_cost = 0; + F32 attachment_children_cost = 0; + const F32 animated_object_attachment_surcharge = 1000; + + if (volume->isAnimatedObjectFast()) + { + attachment_volume_cost += animated_object_attachment_surcharge; + } + attachment_volume_cost += volume->getRenderCost(textures); + + const_child_list_t children = volume->getChildren(); + for (const_child_list_t::const_iterator child_iter = children.begin(); + child_iter != children.end(); + ++child_iter) + { + LLViewerObject* child_obj = *child_iter; + LLVOVolume* child = dynamic_cast(child_obj); + if (child) + { + attachment_children_cost += child->getRenderCost(textures); + } + } + + for (LLVOVolume::texture_cost_t::iterator volume_texture = textures.begin(); + volume_texture != textures.end(); + ++volume_texture) + { + // add the cost of each individual texture in the linkset + attachment_texture_cost += LLVOVolume::getTextureCost(*volume_texture); + } + attachment_total_cost = attachment_volume_cost + attachment_texture_cost + attachment_children_cost; + LL_DEBUGS("ARCdetail") << "Attachment costs " << attached_object->getAttachmentItemID() + << " total: " << attachment_total_cost + << ", volume: " << attachment_volume_cost + << ", " << textures.size() + << " textures: " << attachment_texture_cost + << ", " << volume->numChildren() + << " children: " << attachment_children_cost + << LL_ENDL; + // Limit attachment complexity to avoid signed integer flipping of the wearer's ACI + cost += (U32)llclamp(attachment_total_cost, MIN_ATTACHMENT_COMPLEXITY, max_attachment_complexity); + + if (isSelf()) + { + LLObjectComplexity object_complexity; + object_complexity.objectName = attached_object->getAttachmentItemName(); + object_complexity.objectId = attached_object->getAttachmentItemID(); + object_complexity.objectCost = attachment_total_cost; + object_complexity_list.push_back(object_complexity); + } + } + } + } + if (isSelf() + && attached_object + && attached_object->isHUDAttachment() + && !attached_object->isTempAttachment() + && attached_object->mDrawable) + { + textures.clear(); + mAttachmentSurfaceArea += attached_object->recursiveGetScaledSurfaceArea(); + + const LLVOVolume* volume = attached_object->mDrawable->getVOVolume(); + if (volume) + { + bool is_rigged_mesh = volume->isRiggedMeshFast(); + LLHUDComplexity hud_object_complexity; + hud_object_complexity.objectName = attached_object->getAttachmentItemName(); + hud_object_complexity.objectId = attached_object->getAttachmentItemID(); + std::string joint_name; + gAgentAvatarp->getAttachedPointName(attached_object->getAttachmentItemID(), joint_name); + hud_object_complexity.jointName = joint_name; + // get cost and individual textures + hud_object_complexity.objectsCost += volume->getRenderCost(textures); + hud_object_complexity.objectsCount++; + + LLViewerObject::const_child_list_t& child_list = attached_object->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); ++iter) + { + LLViewerObject* childp = *iter; + const LLVOVolume* chld_volume = dynamic_cast(childp); + if (chld_volume) + { + is_rigged_mesh = is_rigged_mesh || chld_volume->isRiggedMeshFast(); + // get cost and individual textures + hud_object_complexity.objectsCost += chld_volume->getRenderCost(textures); + hud_object_complexity.objectsCount++; + } + } + if (is_rigged_mesh && !attached_object->mRiggedAttachedWarned) + { + LLSD args; + LLViewerInventoryItem* itemp = gInventory.getItem(attached_object->getAttachmentItemID()); + args["NAME"] = itemp ? itemp->getName() : LLTrans::getString("Unknown"); + args["POINT"] = LLTrans::getString(getTargetAttachmentPoint(attached_object)->getName()); + LLNotificationsUtil::add("RiggedMeshAttachedToHUD", args); + + attached_object->mRiggedAttachedWarned = true; + } + + hud_object_complexity.texturesCount += textures.size(); + + for (LLVOVolume::texture_cost_t::iterator volume_texture = textures.begin(); + volume_texture != textures.end(); + ++volume_texture) + { + // add the cost of each individual texture (ignores duplicates) + hud_object_complexity.texturesCost += LLVOVolume::getTextureCost(*volume_texture); + const LLViewerTexture* img = *volume_texture; + if (img->getType() == LLViewerTexture::FETCHED_TEXTURE) + { + LLViewerFetchedTexture* tex = (LLViewerFetchedTexture*)img; + // Note: Texture memory might be incorect since texture might be still loading. + hud_object_complexity.texturesMemoryTotal += tex->getTextureMemory(); + if (tex->getOriginalHeight() * tex->getOriginalWidth() >= HUD_OVERSIZED_TEXTURE_DATA_SIZE) + { + hud_object_complexity.largeTexturesCount++; + } + } + } + hud_complexity_list.push_back(hud_object_complexity); + } + } +} + +// Calculations for mVisualComplexity value +void LLVOAvatar::calculateUpdateRenderComplexity() +{ + /***************************************************************** + * This calculation should not be modified by third party viewers, + * since it is used to limit rendering and should be uniform for + * everyone. If you have suggested improvements, submit them to + * the official viewer for consideration. + *****************************************************************/ + if (mVisualComplexityStale) + { + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + + static const U32 COMPLEXITY_BODY_PART_COST = 200; + static LLCachedControl max_complexity_setting(gSavedSettings, "MaxAttachmentComplexity"); + F32 max_attachment_complexity = max_complexity_setting; + max_attachment_complexity = llmax(max_attachment_complexity, DEFAULT_MAX_ATTACHMENT_COMPLEXITY); + + // Diagnostic list of all textures on our avatar + static std::unordered_set all_textures; + + U32 cost = VISUAL_COMPLEXITY_UNKNOWN; + LLVOVolume::texture_cost_t textures; + hud_complexity_list_t hud_complexity_list; + object_complexity_list_t object_complexity_list; + + for (U8 baked_index = 0; baked_index < BAKED_NUM_INDICES; baked_index++) + { + const LLAvatarAppearanceDictionary::BakedEntry *baked_dict + = LLAvatarAppearance::getDictionary()->getBakedTexture((EBakedTextureIndex)baked_index); + ETextureIndex tex_index = baked_dict->mTextureIndex; + if ((tex_index != TEX_SKIRT_BAKED) || (isWearingWearableType(LLWearableType::WT_SKIRT))) + { + // Same as isTextureVisible(), but doesn't account for isSelf to ensure identical numbers for all avatars + if (isIndexLocalTexture(tex_index)) + { + if (isTextureDefined(tex_index, 0)) + { + cost += COMPLEXITY_BODY_PART_COST; + } + } + else + { + // baked textures can use TE images directly + if (isTextureDefined(tex_index) + && (getTEImage(tex_index)->getID() != IMG_INVISIBLE || LLDrawPoolAlpha::sShowDebugAlpha)) + { + cost += COMPLEXITY_BODY_PART_COST; + } + } + } + } + LL_DEBUGS("ARCdetail") << "Avatar body parts complexity: " << cost << LL_ENDL; + + mAttachmentVisibleTriangleCount = 0; + mAttachmentEstTriangleCount = 0.f; + mAttachmentSurfaceArea = 0.f; + + // A standalone animated object needs to be accounted for + // using its associated volume. Attached animated objects + // will be covered by the subsequent loop over attachments. + LLControlAvatar *control_av = dynamic_cast(this); + if (control_av) + { + LLVOVolume *volp = control_av->mRootVolp; + if (volp && !volp->isAttachment()) + { + accountRenderComplexityForObject(volp, max_attachment_complexity, + textures, cost, hud_complexity_list, object_complexity_list); + } + } + + // Account for complexity of all attachments. + for (attachment_map_t::const_iterator attachment_point = mAttachmentPoints.begin(); + attachment_point != mAttachmentPoints.end(); + ++attachment_point) + { + LLViewerJointAttachment* attachment = attachment_point->second; + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + LLViewerObject* attached_object = attachment_iter->get(); + accountRenderComplexityForObject(attached_object, max_attachment_complexity, + textures, cost, hud_complexity_list, object_complexity_list); + } + } + + if ( cost != mVisualComplexity ) + { + LL_DEBUGS("AvatarRender") << "Avatar "<< getID() + << " complexity updated was " << mVisualComplexity << " now " << cost + << " reported " << mReportedVisualComplexity + << LL_ENDL; + } + else + { + LL_DEBUGS("AvatarRender") << "Avatar "<< getID() + << " complexity updated no change " << mVisualComplexity + << " reported " << mReportedVisualComplexity + << LL_ENDL; + } + mVisualComplexity = cost; + mVisualComplexityStale = false; + + static LLCachedControl show_my_complexity_changes(gSavedSettings, "ShowMyComplexityChanges", 20); + + if (isSelf() && show_my_complexity_changes) + { + // Avatar complexity + LLAvatarRenderNotifier::getInstance()->updateNotificationAgent(mVisualComplexity); + LLAvatarRenderNotifier::getInstance()->setObjectComplexityList(object_complexity_list); + // HUD complexity + LLHUDRenderNotifier::getInstance()->updateNotificationHUD(hud_complexity_list); + } + + //schedule an update to ART next frame if needed + if (LLPerfStats::tunables.userAutoTuneEnabled && + LLPerfStats::tunables.userFPSTuningStrategy != LLPerfStats::TUNE_SCENE_ONLY && + !isVisuallyMuted()) + { + LLUUID id = getID(); // <== use id to make sure this avatar didn't get deleted between frames + LL::WorkQueue::getInstance("mainloop")->post([this, id]() + { + if (gObjectList.findObject(id) != nullptr) + { + gPipeline.profileAvatar(this); + } + }); + } + } +} + +void LLVOAvatar::setVisualMuteSettings(VisualMuteSettings set) +{ + mVisuallyMuteSetting = set; + mNeedsImpostorUpdate = true; + mLastImpostorUpdateReason = 7; + + LLRenderMuteList::getInstance()->saveVisualMuteSetting(getID(), S32(set)); +} + + +void LLVOAvatar::setOverallAppearanceNormal() +{ + if (isControlAvatar()) + return; + + LLVector3 pelvis_pos = getJoint("mPelvis")->getPosition(); + if (isControlAvatar() || mLastProcessedAppearance) + { + resetSkeleton(false); + } + getJoint("mPelvis")->setPosition(pelvis_pos); + + for (auto it = mJellyAnims.begin(); it != mJellyAnims.end(); ++it) + { + bool is_playing = (mPlayingAnimations.find(*it) != mPlayingAnimations.end()); + LL_DEBUGS("Avatar") << "jelly anim " << *it << " " << is_playing << LL_ENDL; + if (!is_playing) + { + // Anim was not requested for this av by sim, but may be playing locally + stopMotion(*it); + } + } + mJellyAnims.clear(); + + processAnimationStateChanges(); +} + +void LLVOAvatar::setOverallAppearanceJellyDoll() +{ + if (isControlAvatar()) + return; + + // stop current animations + { + for ( LLVOAvatar::AnimIterator anim_it= mPlayingAnimations.begin(); + anim_it != mPlayingAnimations.end(); + ++anim_it) + { + { + stopMotion(anim_it->first, true); + } + } + } + processAnimationStateChanges(); + + // Start any needed anims for jellydoll + updateOverallAppearanceAnimations(); + + LLVector3 pelvis_pos = getJoint("mPelvis")->getPosition(); + resetSkeleton(false); + getJoint("mPelvis")->setPosition(pelvis_pos); + +} + +void LLVOAvatar::setOverallAppearanceInvisible() +{ +} + +void LLVOAvatar::updateOverallAppearance() +{ + AvatarOverallAppearance new_overall = getOverallAppearance(); + if (new_overall != mOverallAppearance) + { + switch (new_overall) + { + case AOA_NORMAL: + setOverallAppearanceNormal(); + break; + case AOA_JELLYDOLL: + setOverallAppearanceJellyDoll(); + break; + case AOA_INVISIBLE: + setOverallAppearanceInvisible(); + break; + } + mOverallAppearance = new_overall; + if (!isSelf()) + { + mNeedsImpostorUpdate = true; + mLastImpostorUpdateReason = 8; + } + updateMeshVisibility(); + } + + // This needs to be done even if overall appearance has not + // changed, since sit/stand status can be different. + updateOverallAppearanceAnimations(); +} + +void LLVOAvatar::updateOverallAppearanceAnimations() +{ + if (isControlAvatar()) + return; + + if (getOverallAppearance() == AOA_JELLYDOLL) + { + LLUUID motion_id; + if (isSitting() && getParent()) // sitting on object + { + motion_id = ANIM_AGENT_SIT_FEMALE; + } + else if (isSitting()) // sitting on ground + { + motion_id = ANIM_AGENT_SIT_GROUND_CONSTRAINED; + } + else // standing + { + motion_id = ANIM_AGENT_STAND; + } + if (mJellyAnims.find(motion_id) == mJellyAnims.end()) + { + for (auto it = mJellyAnims.begin(); it != mJellyAnims.end(); ++it) + { + bool is_playing = (mPlayingAnimations.find(*it) != mPlayingAnimations.end()); + LL_DEBUGS("Avatar") << "jelly anim " << *it << " " << is_playing << LL_ENDL; + if (!is_playing) + { + // Anim was not requested for this av by sim, but may be playing locally + stopMotion(*it, true); + } + } + mJellyAnims.clear(); + + startMotion(motion_id); + mJellyAnims.insert(motion_id); + + processAnimationStateChanges(); + } + } +} + +// Based on isVisuallyMuted(), but has 3 possible results. +LLVOAvatar::AvatarOverallAppearance LLVOAvatar::getOverallAppearance() const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + AvatarOverallAppearance result = AOA_NORMAL; + + // Priority order (highest priority first) + // * own avatar is always drawn normally + // * if on the "always draw normally" list, draw them normally + // * if on the "always visually mute" list, show as jellydoll + // * if explicitly muted (blocked), show as invisible + // * check against the render cost and attachment limits - if too complex, show as jellydoll + if (isSelf()) + { + result = AOA_NORMAL; + } + else // !isSelf() + { + if (isInMuteList()) + { + result = AOA_INVISIBLE; + } + else if (mVisuallyMuteSetting == AV_ALWAYS_RENDER) + { + result = AOA_NORMAL; + } + else if (mVisuallyMuteSetting == AV_DO_NOT_RENDER) + { // Always want to see this AV as an impostor + result = AOA_JELLYDOLL; + } + else if (isTooComplex() || isTooSlow()) + { + result = AOA_JELLYDOLL; + } + } + + return result; +} + +void LLVOAvatar::calcMutedAVColor() +{ + LLColor4 new_color(mMutedAVColor); + std::string change_msg; + LLUUID av_id(getID()); + + if (getVisualMuteSettings() == AV_DO_NOT_RENDER) + { + // explicitly not-rendered avatars are light grey + new_color = LLColor4::grey4; + change_msg = " not rendered: color is grey4"; + } + else if (LLMuteList::getInstance()->isMuted(av_id)) // the user blocked them + { + // blocked avatars are dark grey + new_color = LLColor4::grey4; + change_msg = " blocked: color is grey4"; + } + else if (!isTooComplex() && !isTooSlow()) + { + new_color = LLColor4::white; + change_msg = " simple imposter "; + } +#ifdef COLORIZE_JELLYDOLLS + else if ( mMutedAVColor == LLColor4::white || mMutedAVColor == LLColor4::grey3 || mMutedAVColor == LLColor4::grey4 ) + { + // select a color based on the first byte of the agents uuid so any muted agent is always the same color + F32 color_value = (F32) (av_id.mData[0]); + F32 spectrum = (color_value / 256.0); // spectrum is between 0 and 1.f + + // Array of colors. These are arranged so only one RGB color changes between each step, + // and it loops back to red so there is an even distribution. It is not a heat map + const S32 NUM_SPECTRUM_COLORS = 7; + static LLColor4 * spectrum_color[NUM_SPECTRUM_COLORS] = { &LLColor4::red, &LLColor4::magenta, &LLColor4::blue, &LLColor4::cyan, &LLColor4::green, &LLColor4::yellow, &LLColor4::red }; + + spectrum = spectrum * (NUM_SPECTRUM_COLORS - 1); // Scale to range of number of colors + S32 spectrum_index_1 = floor(spectrum); // Desired color will be after this index + S32 spectrum_index_2 = spectrum_index_1 + 1; // and before this index (inclusive) + F32 fractBetween = spectrum - (F32)(spectrum_index_1); // distance between the two indexes (0-1) + + new_color = lerp(*spectrum_color[spectrum_index_1], *spectrum_color[spectrum_index_2], fractBetween); + new_color.normalize(); + new_color *= 0.28f; // Tone it down + } +#endif + else + { + new_color = LLColor4::grey4; + change_msg = " over limit color "; + } + + if (mMutedAVColor != new_color) + { + LL_DEBUGS("AvatarRender") << "avatar "<< av_id << change_msg << std::setprecision(3) << new_color << LL_ENDL; + mMutedAVColor = new_color; + } +} + +// static +bool LLVOAvatar::isIndexLocalTexture(ETextureIndex index) +{ + return (index < 0 || index >= TEX_NUM_INDICES) + ? false + : LLAvatarAppearance::getDictionary()->getTexture(index)->mIsLocalTexture; +} + +// static +bool LLVOAvatar::isIndexBakedTexture(ETextureIndex index) +{ + return (index < 0 || index >= TEX_NUM_INDICES) + ? false + : LLAvatarAppearance::getDictionary()->getTexture(index)->mIsBakedTexture; +} + +const std::string LLVOAvatar::getBakedStatusForPrintout() const +{ + std::string line; + + for (LLAvatarAppearanceDictionary::Textures::const_iterator iter = LLAvatarAppearance::getDictionary()->getTextures().begin(); + iter != LLAvatarAppearance::getDictionary()->getTextures().end(); + ++iter) + { + const ETextureIndex index = iter->first; + const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = iter->second; + if (texture_dict->mIsBakedTexture) + { + line += texture_dict->mName; + if (isTextureDefined(index)) + { + line += "_baked"; + } + line += " "; + } + } + return line; +} + + + +//virtual +S32 LLVOAvatar::getTexImageSize() const +{ + return TEX_IMAGE_SIZE_OTHER; +} + +//----------------------------------------------------------------------------- +// Utility functions +//----------------------------------------------------------------------------- + +F32 calc_bouncy_animation(F32 x) +{ + return -(cosf(x * F_PI * 2.5f - F_PI_BY_TWO))*(0.4f + x * -0.1f) + x * 1.3f; +} + +//virtual +bool LLVOAvatar::isTextureDefined(LLAvatarAppearanceDefines::ETextureIndex te, U32 index ) const +{ + if (isIndexLocalTexture(te)) + { + return false; + } + + LLViewerTexture* tex = getImage(te, index); + if (!tex) + { + LL_WARNS() << "getImage( " << te << ", " << index << " ) returned 0" << LL_ENDL; + return false; + } + + return (tex->getID() != IMG_DEFAULT_AVATAR && + tex->getID() != IMG_DEFAULT); +} + +//virtual +bool LLVOAvatar::isTextureVisible(LLAvatarAppearanceDefines::ETextureIndex type, U32 index) const +{ + if (isIndexLocalTexture(type)) + { + return isTextureDefined(type, index); + } + + // baked textures can use TE images directly + return ((isTextureDefined(type) || isSelf()) && + (getTEImage(type)->getID() != IMG_INVISIBLE || LLDrawPoolAlpha::sShowDebugAlpha)); +} + +//virtual +bool LLVOAvatar::isTextureVisible(LLAvatarAppearanceDefines::ETextureIndex type, LLViewerWearable *wearable) const +{ + // non-self avatars don't have wearables + return false; +} + +void LLVOAvatar::placeProfileQuery() +{ + if (mGPUTimerQuery == 0) + { + glGenQueries(1, &mGPUTimerQuery); + } + + glBeginQuery(GL_TIME_ELAPSED, mGPUTimerQuery); +} + +void LLVOAvatar::readProfileQuery(S32 retries) +{ + if (!mGPUProfilePending) + { + glEndQuery(GL_TIME_ELAPSED); + mGPUProfilePending = true; + } + + GLuint64 result = 0; + glGetQueryObjectui64v(mGPUTimerQuery, GL_QUERY_RESULT_AVAILABLE, &result); + + if (result == GL_TRUE || --retries <= 0) + { // query available, readback result + GLuint64 time_elapsed = 0; + glGetQueryObjectui64v(mGPUTimerQuery, GL_QUERY_RESULT, &time_elapsed); + mGPURenderTime = time_elapsed / 1000000.f; + mGPUProfilePending = false; + + setDebugText(llformat("%d", (S32)(mGPURenderTime * 1000.f))); + + } + else + { // wait until next frame + LLUUID id = getID(); + + LL::WorkQueue::getInstance("mainloop")->post([id, retries] { + LLVOAvatar* avatar = (LLVOAvatar*) gObjectList.findObject(id); + if(avatar) + { + avatar->readProfileQuery(retries); + } + }); + } +} + + +F32 LLVOAvatar::getGPURenderTime() +{ + return isVisuallyMuted() ? 0.f : mGPURenderTime; +} + +// static +F32 LLVOAvatar::getTotalGPURenderTime() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + + F32 ret = 0.f; + + for (LLCharacter* iter : LLCharacter::sInstances) + { + LLVOAvatar* inst = (LLVOAvatar*) iter; + ret += inst->getGPURenderTime(); + } + + return ret; +} + +F32 LLVOAvatar::getMaxGPURenderTime() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + + F32 ret = 0.f; + + for (LLCharacter* iter : LLCharacter::sInstances) + { + LLVOAvatar* inst = (LLVOAvatar*)iter; + ret = llmax(inst->getGPURenderTime(), ret); + } + + return ret; +} + +F32 LLVOAvatar::getAverageGPURenderTime() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + + F32 ret = 0.f; + + S32 count = 0; + + for (LLCharacter* iter : LLCharacter::sInstances) + { + LLVOAvatar* inst = (LLVOAvatar*)iter; + if (!inst->isTooSlow()) + { + ret += inst->getGPURenderTime(); + ++count; + } + } + + if (count > 0) + { + ret /= count; + } + + return ret; +} +bool LLVOAvatar::isBuddy() const +{ + return LLAvatarTracker::instance().isBuddy(getID()); +} + diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index b10675ac4e..1ef0e6e768 100644 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -1,1256 +1,1256 @@ -/** - * @file llvoavatar.h - * @brief Declaration of LLVOAvatar class which is a derivation of - * LLViewerObject - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_VOAVATAR_H -#define LL_VOAVATAR_H - -#include -#include -#include -#include - -#include - -#include "llavatarappearance.h" -#include "llchat.h" -#include "lldrawpoolalpha.h" -#include "llviewerobject.h" -#include "llcharacter.h" -#include "llcontrol.h" -#include "llviewerjointmesh.h" -#include "llviewerjointattachment.h" -#include "llrendertarget.h" -#include "llavatarappearancedefines.h" -#include "lltexglobalcolor.h" -#include "lldriverparam.h" -#include "llviewertexlayer.h" -#include "material_codes.h" // LL_MCODE_END -#include "llrigginginfo.h" -#include "llviewerstats.h" -#include "llvovolume.h" -#include "llavatarrendernotifier.h" -#include "llmodel.h" - -extern const LLUUID ANIM_AGENT_BODY_NOISE; -extern const LLUUID ANIM_AGENT_BREATHE_ROT; -extern const LLUUID ANIM_AGENT_PHYSICS_MOTION; -extern const LLUUID ANIM_AGENT_EDITING; -extern const LLUUID ANIM_AGENT_EYE; -extern const LLUUID ANIM_AGENT_FLY_ADJUST; -extern const LLUUID ANIM_AGENT_HAND_MOTION; -extern const LLUUID ANIM_AGENT_HEAD_ROT; -extern const LLUUID ANIM_AGENT_PELVIS_FIX; -extern const LLUUID ANIM_AGENT_TARGET; -extern const LLUUID ANIM_AGENT_WALK_ADJUST; - -class LLViewerWearable; -class LLVoiceVisualizer; -class LLHUDNameTag; -class LLHUDEffectSpiral; -class LLTexGlobalColor; - -struct LLAppearanceMessageContents; -class LLViewerJointMesh; - -const F32 MAX_AVATAR_LOD_FACTOR = 1.0f; - -extern U32 gFrameCount; - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// LLVOAvatar -// -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLVOAvatar : - public LLAvatarAppearance, - public LLViewerObject, - public boost::signals2::trackable -{ - LL_ALIGN_NEW; - LOG_CLASS(LLVOAvatar); - -public: - friend class LLVOAvatarSelf; - friend class LLAvatarCheckImpostorMode; - -/******************************************************************************** - ** ** - ** INITIALIZATION - **/ - -public: - LLVOAvatar(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); - virtual void markDead(); - static void initClass(); // Initialize data that's only init'd once per class. - static void cleanupClass(); // Cleanup data that's only init'd once per class. - virtual void initInstance(); // Called after construction to initialize the class. -protected: - virtual ~LLVOAvatar(); - -/** Initialization - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** INHERITED - **/ - - //-------------------------------------------------------------------- - // LLViewerObject interface and related - //-------------------------------------------------------------------- -public: - /*virtual*/ void updateGL(); - /*virtual*/ LLVOAvatar* asAvatar(); - - virtual U32 processUpdateMessage(LLMessageSystem *mesgsys, - void **user_data, - U32 block_num, - const EObjectUpdateType update_type, - LLDataPacker *dp); - virtual void idleUpdate(LLAgent &agent, const F64 &time); - /*virtual*/ bool updateLOD(); - bool updateJointLODs(); - void updateLODRiggedAttachments( void ); - /*virtual*/ bool isActive() const; // Whether this object needs to do an idleUpdate. - S32Bytes totalTextureMemForUUIDS(std::set& ids); - bool allTexturesCompletelyDownloaded(std::set& ids) const; - bool allLocalTexturesCompletelyDownloaded() const; - bool allBakedTexturesCompletelyDownloaded() const; - void bakedTextureOriginCounts(S32 &sb_count, S32 &host_count, - S32 &both_count, S32 &neither_count); - std::string bakedTextureOriginInfo(); - void collectLocalTextureUUIDs(std::set& ids) const; - void collectBakedTextureUUIDs(std::set& ids) const; - void collectTextureUUIDs(std::set& ids); - void releaseOldTextures(); - /*virtual*/ void updateTextures(); - LLViewerFetchedTexture* getBakedTextureImage(const U8 te, const LLUUID& uuid); - /*virtual*/ S32 setTETexture(const U8 te, const LLUUID& uuid); // If setting a baked texture, need to request it from a non-local sim. - /*virtual*/ void onShift(const LLVector4a& shift_vector); - /*virtual*/ U32 getPartitionType() const; - /*virtual*/ const LLVector3 getRenderPosition() const; - /*virtual*/ void updateDrawable(bool force_damped); - /*virtual*/ LLDrawable* createDrawable(LLPipeline *pipeline); - /*virtual*/ bool updateGeometry(LLDrawable *drawable); - /*virtual*/ void setPixelAreaAndAngle(LLAgent &agent); - /*virtual*/ void updateRegion(LLViewerRegion *regionp); - /*virtual*/ void updateSpatialExtents(LLVector4a& newMin, LLVector4a &newMax); - void calculateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax); - /*virtual*/ bool lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, - S32 face = -1, // which face to check, -1 = ALL_SIDES - bool pick_transparent = false, - bool pick_rigged = false, - bool pick_unselectable = true, - S32* face_hit = NULL, // which face was hit - LLVector4a* intersection = NULL, // return the intersection point - LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point - LLVector4a* normal = NULL, // return the surface normal at the intersection point - LLVector4a* tangent = NULL); // return the surface tangent at the intersection point - virtual LLViewerObject* lineSegmentIntersectRiggedAttachments( - const LLVector4a& start, const LLVector4a& end, - S32 face = -1, // which face to check, -1 = ALL_SIDES - bool pick_transparent = false, - bool pick_rigged = false, - bool pick_unselectable = true, - S32* face_hit = NULL, // which face was hit - LLVector4a* intersection = NULL, // return the intersection point - LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point - LLVector4a* normal = NULL, // return the surface normal at the intersection point - LLVector4a* tangent = NULL); // return the surface tangent at the intersection point - - //-------------------------------------------------------------------- - // LLCharacter interface and related - //-------------------------------------------------------------------- -public: - /*virtual*/ LLVector3 getCharacterPosition(); - /*virtual*/ LLQuaternion getCharacterRotation(); - /*virtual*/ LLVector3 getCharacterVelocity(); - /*virtual*/ LLVector3 getCharacterAngularVelocity(); - - /*virtual*/ LLUUID remapMotionID(const LLUUID& id); - /*virtual*/ bool startMotion(const LLUUID& id, F32 time_offset = 0.f); - /*virtual*/ bool stopMotion(const LLUUID& id, bool stop_immediate = false); - virtual bool hasMotionFromSource(const LLUUID& source_id); - virtual void stopMotionFromSource(const LLUUID& source_id); - virtual void requestStopMotion(LLMotion* motion); - LLMotion* findMotion(const LLUUID& id) const; - void startDefaultMotions(); - void dumpAnimationState(); - - virtual LLJoint* getJoint(const std::string &name); - LLJoint* getJoint(S32 num); - - //if you KNOW joint_num is a valid animated joint index, use getSkeletonJoint for efficiency - inline LLJoint* getSkeletonJoint(S32 joint_num) { return mSkeleton[joint_num]; } - inline size_t getSkeletonJointCount() const { return mSkeleton.size(); } - - void notifyAttachmentMeshLoaded(); - void addAttachmentOverridesForObject(LLViewerObject *vo, std::set* meshes_seen = NULL, bool recursive = true); - void removeAttachmentOverridesForObject(const LLUUID& mesh_id); - void removeAttachmentOverridesForObject(LLViewerObject *vo); - bool jointIsRiggedTo(const LLJoint *joint) const; - void clearAttachmentOverrides(); - void rebuildAttachmentOverrides(); - void updateAttachmentOverrides(); - void showAttachmentOverrides(bool verbose = false) const; - void getAttachmentOverrideNames(std::set& pos_names, - std::set& scale_names) const; - - void getAssociatedVolumes(std::vector& volumes); - - // virtual - void updateRiggingInfo(); - // This encodes mesh id and LOD, so we can see whether display is up-to-date. - std::map mLastRiggingInfoKey; - - std::set mActiveOverrideMeshes; - virtual void onActiveOverrideMeshesChanged(); - - /*virtual*/ const LLUUID& getID() const; - /*virtual*/ void addDebugText(const std::string& text); - /*virtual*/ F32 getTimeDilation(); - /*virtual*/ void getGround(const LLVector3 &inPos, LLVector3 &outPos, LLVector3 &outNorm); - /*virtual*/ F32 getPixelArea() const; - /*virtual*/ LLVector3d getPosGlobalFromAgent(const LLVector3 &position); - /*virtual*/ LLVector3 getPosAgentFromGlobal(const LLVector3d &position); - virtual void updateVisualParams(); - -/** Inherited - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** STATE - **/ - -public: - virtual bool isSelf() const { return false; } // True if this avatar is for this viewer's agent - - virtual bool isControlAvatar() const { return mIsControlAvatar; } // True if this avatar is a control av (no associated user) - virtual bool isUIAvatar() const { return mIsUIAvatar; } // True if this avatar is a supplemental av used in some UI views (no associated user) - virtual bool isBuddy() const; - - // If this is an attachment, return the avatar it is attached to. Otherwise NULL. - virtual const LLVOAvatar *getAttachedAvatar() const { return NULL; } - virtual LLVOAvatar *getAttachedAvatar() { return NULL; } - - -private: //aligned members - LL_ALIGN_16(LLVector4a mImpostorExtents[2]); - - //-------------------------------------------------------------------- - // Updates - //-------------------------------------------------------------------- -public: - void updateAppearanceMessageDebugText(); - void updateAnimationDebugText(); - virtual void updateDebugText(); - virtual bool computeNeedsUpdate(); - virtual bool updateCharacter(LLAgent &agent); - void updateFootstepSounds(); - void computeUpdatePeriod(); - void updateOrientation(LLAgent &agent, F32 speed, F32 delta_time); - void updateTimeStep(); - void updateRootPositionAndRotation(LLAgent &agent, F32 speed, bool was_sit_ground_constrained); - - void idleUpdateVoiceVisualizer(bool voice_enabled, const LLVector3 &position); - void idleUpdateMisc(bool detailed_update); - virtual void idleUpdateAppearanceAnimation(); - void idleUpdateLipSync(bool voice_enabled); - void idleUpdateLoadingEffect(); - void idleUpdateWindEffect(); - void idleUpdateNameTag(const LLVector3& root_pos_last); - void idleUpdateNameTagText(bool new_name); - void idleUpdateNameTagAlpha(bool new_name, F32 alpha); - LLColor4 getNameTagColor(bool is_friend); - void clearNameTag(); - static void invalidateNameTag(const LLUUID& agent_id); - // force all name tags to rebuild, useful when display names turned on/off - static void invalidateNameTags(); - void addNameTagLine(const std::string& line, const LLColor4& color, S32 style, const LLFontGL* font, const bool use_ellipses = false); - void idleUpdateRenderComplexity(); - void idleUpdateDebugInfo(); - void accountRenderComplexityForObject(LLViewerObject *attached_object, - const F32 max_attachment_complexity, - LLVOVolume::texture_cost_t& textures, - U32& cost, - hud_complexity_list_t& hud_complexity_list, - object_complexity_list_t& object_complexity_list); - void calculateUpdateRenderComplexity(); - static const U32 VISUAL_COMPLEXITY_UNKNOWN; - void updateVisualComplexity(); - - void placeProfileQuery(); - void readProfileQuery(S32 retries); - - // get the GPU time in ms of rendering this avatar including all attachments - // returns 0.f if this avatar has not been profiled using gPipeline.profileAvatar - // or the avatar is visually muted - F32 getGPURenderTime(); - - // get the total GPU render time in ms of all avatars that have been benched - static F32 getTotalGPURenderTime(); - - // get the max GPU render time in ms of all avatars that have been benched - static F32 getMaxGPURenderTime(); - - // get the average GPU render time in ms of all avatars that have been benched - static F32 getAverageGPURenderTime(); - - // get the CPU time in ms of rendering this avatar including all attachments - // return 0.f if this avatar has not been profiled using gPipeline.mProfileAvatar - F32 getCPURenderTime() { return mCPURenderTime; } - - - // avatar render cost - U32 getVisualComplexity() { return mVisualComplexity; }; - - // surface area calculation - F32 getAttachmentSurfaceArea() { return mAttachmentSurfaceArea; }; - - U32 getReportedVisualComplexity() { return mReportedVisualComplexity; }; // Numbers as reported by the SL server - void setReportedVisualComplexity(U32 value) { mReportedVisualComplexity = value; }; - - S32 getUpdatePeriod() { return mUpdatePeriod; }; - const LLColor4 & getMutedAVColor() { return mMutedAVColor; }; - static void updateImpostorRendering(U32 newMaxNonImpostorsValue); - - void idleUpdateBelowWater(); - - static void updateNearbyAvatarCount(); - - LLVector3 idleCalcNameTagPosition(const LLVector3 &root_pos_last); - - //-------------------------------------------------------------------- - // Static preferences (controlled by user settings/menus) - //-------------------------------------------------------------------- -public: - static S32 sRenderName; - static bool sRenderGroupTitles; - static const U32 NON_IMPOSTORS_MAX_SLIDER; /* Must equal the maximum allowed the RenderAvatarMaxNonImpostors - * slider in panel_preferences_graphics1.xml */ - static U32 sMaxNonImpostors; // affected by control "RenderAvatarMaxNonImpostors" - static bool sLimitNonImpostors; // use impostors for far away avatars - static F32 sRenderDistance; // distance at which avatars will render. - static bool sShowAnimationDebug; // show animation debug info - static bool sShowCollisionVolumes; // show skeletal collision volumes - static bool sVisibleInFirstPerson; - static S32 sNumLODChangesThisFrame; - static S32 sNumVisibleChatBubbles; - static bool sDebugInvisible; - static bool sShowAttachmentPoints; - static F32 sLODFactor; // user-settable LOD factor - static F32 sPhysicsLODFactor; // user-settable physics LOD factor - static bool sJointDebug; // output total number of joints being touched for each avatar - - static LLPointer sCloudTexture; - - static std::vector sAVsIgnoringARTLimit; - static S32 sAvatarsNearby; - - //-------------------------------------------------------------------- - // Region state - //-------------------------------------------------------------------- -public: - LLHost getObjectHost() const; - - //-------------------------------------------------------------------- - // Loading state - //-------------------------------------------------------------------- -public: - bool isFullyLoaded() const; - F32 getFirstDecloudTime() const {return mFirstDecloudTime;} - - // check and return current state relative to limits - // default will test only the geometry (combined=false). - // this allows us to disable shadows separately on complex avatars. - - inline bool isTooSlowWithoutShadows() const {return mTooSlowWithoutShadows;}; - bool isTooSlow() const; - - void updateTooSlow(); - - bool isTooComplex() const; - bool visualParamWeightsAreDefault(); - virtual bool getIsCloud() const; - bool isFullyTextured() const; - bool hasGray() const; - S32 getRezzedStatus() const; // 0 = cloud, 1 = gray, 2 = textured, 3 = textured and fully downloaded. - void updateRezzedStatusTimers(S32 status); - - S32 mLastRezzedStatus; - - - void startPhase(const std::string& phase_name); - void stopPhase(const std::string& phase_name, bool err_check = true); - void clearPhases(); - void logPendingPhases(); - static void logPendingPhasesAllAvatars(); - void logMetricsTimerRecord(const std::string& phase_name, F32 elapsed, bool completed); - - void calcMutedAVColor(); - -protected: - LLViewerStats::PhaseMap& getPhases() { return mPhases; } - bool updateIsFullyLoaded(); - bool processFullyLoadedChange(bool loading); - void updateRuthTimer(bool loading); - F32 calcMorphAmount(); - -private: - bool mFirstFullyVisible; - F32 mFirstDecloudTime; - LLFrameTimer mFirstAppearanceMessageTimer; - - bool mFullyLoaded; - bool mPreviousFullyLoaded; - bool mFullyLoadedInitialized; - S32 mFullyLoadedFrameCounter; - LLColor4 mMutedAVColor; - LLFrameTimer mFullyLoadedTimer; - LLFrameTimer mRuthTimer; - - // variables to hold "slowness" status - bool mTooSlow{false}; - bool mTooSlowWithoutShadows{false}; - - bool mTuned{false}; - -private: - LLViewerStats::PhaseMap mPhases; - -protected: - LLFrameTimer mInvisibleTimer; - -/** State - ** ** - *******************************************************************************/ -/******************************************************************************** - ** ** - ** SKELETON - **/ - -protected: - /*virtual*/ LLAvatarJoint* createAvatarJoint(); // Returns LLViewerJoint - /*virtual*/ LLAvatarJoint* createAvatarJoint(S32 joint_num); // Returns LLViewerJoint - /*virtual*/ LLAvatarJointMesh* createAvatarJointMesh(); // Returns LLViewerJointMesh -public: - void updateHeadOffset(); - void debugBodySize() const; - void postPelvisSetRecalc( void ); - - /*virtual*/ bool loadSkeletonNode(); - void initAttachmentPoints(bool ignore_hud_joints = false); - /*virtual*/ void buildCharacter(); - void resetVisualParams(); - void applyDefaultParams(); - void resetSkeleton(bool reset_animations); - - LLVector3 mCurRootToHeadOffset; - LLVector3 mTargetRootToHeadOffset; - - S32 mLastSkeletonSerialNum; - - -/** Skeleton - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** RENDERING - **/ - -public: - U32 renderImpostor(LLColor4U color = LLColor4U(255,255,255,255), S32 diffuse_channel = 0); - bool isVisuallyMuted(); - bool isInMuteList() const; - - // states for RenderAvatarComplexityMode - enum ERenderComplexityMode - { - AV_RENDER_LIMIT_BY_COMPLEXITY = 0, - AV_RENDER_ALWAYS_SHOW_FRIENDS = 1, - AV_RENDER_ONLY_SHOW_FRIENDS = 2 - }; - - // Visual Mute Setting is an input. Does not necessarily determine - // what the avatar looks like, because it interacts with other - // settings like muting, complexity threshold. Should be private or protected. - enum VisualMuteSettings - { - AV_RENDER_NORMALLY = 0, - AV_DO_NOT_RENDER = 1, - AV_ALWAYS_RENDER = 2 - }; - void setVisualMuteSettings(VisualMuteSettings set); - -protected: - // If you think you need to access this outside LLVOAvatar, you probably want getOverallAppearance() - VisualMuteSettings getVisualMuteSettings() { return mVisuallyMuteSetting; }; - -public: - - // Overall Appearance is an output. Depending on whether the - // avatar is blocked/muted, whether it exceeds the complexity - // threshold, etc, avatar will want to be displayed in one of - // these ways. Rendering code that wants to know how to display an - // avatar should be looking at this value, NOT the visual mute - // settings - enum AvatarOverallAppearance - { - AOA_NORMAL, - AOA_JELLYDOLL, - AOA_INVISIBLE - }; - - AvatarOverallAppearance getOverallAppearance() const; - void setOverallAppearanceNormal(); - void setOverallAppearanceJellyDoll(); - void setOverallAppearanceInvisible(); - - void updateOverallAppearance(); - void updateOverallAppearanceAnimations(); - - std::set mJellyAnims; - - U32 renderRigid(); - U32 renderSkinned(); - F32 getLastSkinTime() { return mLastSkinTime; } - U32 renderTransparent(bool first_pass); - void renderCollisionVolumes(); - void renderBones(const std::string &selected_joint = std::string()); - void renderJoints(); - static void deleteCachedImages(bool clearAll=true); - static void destroyGL(); - static void restoreGL(); - S32 mSpecialRenderMode; // special lighting - -private: - friend class LLPipeline; - AvatarOverallAppearance mOverallAppearance; - F32 mAttachmentSurfaceArea; //estimated surface area of attachments - U32 mAttachmentVisibleTriangleCount; - F32 mAttachmentEstTriangleCount; - bool shouldAlphaMask(); - - bool mNeedsSkin; // avatar has been animated and verts have not been updated - F32 mLastSkinTime; //value of gFrameTimeSeconds at last skin update - - S32 mUpdatePeriod; - S32 mNumInitFaces; //number of faces generated when creating the avatar drawable, does not inculde splitted faces due to long vertex buffer. - - // profile handle - U32 mGPUTimerQuery = 0; - - // profile results - - // GPU render time in ms - F32 mGPURenderTime = 0.f; - bool mGPUProfilePending = false; - - // CPU render time in ms - F32 mCPURenderTime = 0.f; - - // the isTooComplex method uses these mutable values to avoid recalculating too frequently - // DEPRECATED -- obsolete avatar render cost values - mutable U32 mVisualComplexity; - mutable bool mVisualComplexityStale; - U32 mReportedVisualComplexity; // from other viewers through the simulator - - mutable bool mCachedInMuteList; - mutable F64 mCachedMuteListUpdateTime; - - VisualMuteSettings mVisuallyMuteSetting; // Always or never visually mute this AV - - //-------------------------------------------------------------------- - // animated object status - //-------------------------------------------------------------------- -public: - bool mIsControlAvatar; - bool mIsUIAvatar; - bool mEnableDefaultMotions; - - //-------------------------------------------------------------------- - // Morph masks - //-------------------------------------------------------------------- -public: - /*virtual*/ void applyMorphMask(const U8* tex_data, S32 width, S32 height, S32 num_components, LLAvatarAppearanceDefines::EBakedTextureIndex index = LLAvatarAppearanceDefines::BAKED_NUM_INDICES); - bool morphMaskNeedsUpdate(LLAvatarAppearanceDefines::EBakedTextureIndex index = LLAvatarAppearanceDefines::BAKED_NUM_INDICES); - - - //-------------------------------------------------------------------- - // Global colors - //-------------------------------------------------------------------- -public: - /*virtual*/void onGlobalColorChanged(const LLTexGlobalColor* global_color); - - //-------------------------------------------------------------------- - // Visibility - //-------------------------------------------------------------------- -protected: - void updateVisibility(); -private: - U32 mVisibilityRank; - bool mVisible; - - //-------------------------------------------------------------------- - // Shadowing - //-------------------------------------------------------------------- -public: - void updateShadowFaces(); - LLDrawable* mShadow; -private: - LLFace* mShadow0Facep; - LLFace* mShadow1Facep; - LLPointer mShadowImagep; - - //-------------------------------------------------------------------- - // Impostors - //-------------------------------------------------------------------- -public: - virtual bool isImpostor(); - bool shouldImpostor(const F32 rank_factor = 1.0); - bool needsImpostorUpdate() const; - const LLVector3& getImpostorOffset() const; - const LLVector2& getImpostorDim() const; - void getImpostorValues(LLVector4a* extents, LLVector3& angle, F32& distance) const; - void cacheImpostorValues(); - void setImpostorDim(const LLVector2& dim); - static void resetImpostors(); - static void updateImpostors(); - LLRenderTarget mImpostor; - bool mNeedsImpostorUpdate; - S32 mLastImpostorUpdateReason; - F32SecondsImplicit mLastImpostorUpdateFrameTime; - const LLVector3* getLastAnimExtents() const { return mLastAnimExtents; } - void setNeedsExtentUpdate(bool val) { mNeedsExtentUpdate = val; } - -private: - LLVector3 mImpostorOffset; - LLVector2 mImpostorDim; - // This becomes true in the constructor and false after the first - // idleUpdateMisc(). Not clear it serves any purpose. - bool mNeedsAnimUpdate; - bool mNeedsExtentUpdate; - LLVector3 mImpostorAngle; - F32 mImpostorDistance; - F32 mImpostorPixelArea; - LLVector3 mLastAnimExtents[2]; - LLVector3 mLastAnimBasePos; - - LLCachedControl mRenderUnloadedAvatar; - - //-------------------------------------------------------------------- - // Wind rippling in clothes - //-------------------------------------------------------------------- -public: - LLVector4 mWindVec; - F32 mRipplePhase; - bool mBelowWater; -private: - F32 mWindFreq; - LLFrameTimer mRippleTimer; - F32 mRippleTimeLast; - LLVector3 mRippleAccel; - LLVector3 mLastVel; - - //-------------------------------------------------------------------- - // Culling - //-------------------------------------------------------------------- -public: - static void cullAvatarsByPixelArea(); - bool isCulled() const { return mCulled; } -private: - bool mCulled; - - //-------------------------------------------------------------------- - // Constants - //-------------------------------------------------------------------- -public: - virtual LLViewerTexture::EBoostLevel getAvatarBoostLevel() const { return LLGLTexture::BOOST_AVATAR; } - virtual LLViewerTexture::EBoostLevel getAvatarBakedBoostLevel() const { return LLGLTexture::BOOST_AVATAR_BAKED; } - virtual S32 getTexImageSize() const; - /*virtual*/ S32 getTexImageArea() const { return getTexImageSize()*getTexImageSize(); } - -/** Rendering - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** TEXTURES - **/ - - //-------------------------------------------------------------------- - // Loading status - //-------------------------------------------------------------------- -public: - virtual bool isTextureDefined(LLAvatarAppearanceDefines::ETextureIndex type, U32 index = 0) const; - virtual bool isTextureVisible(LLAvatarAppearanceDefines::ETextureIndex type, U32 index = 0) const; - virtual bool isTextureVisible(LLAvatarAppearanceDefines::ETextureIndex type, LLViewerWearable *wearable) const; - - bool isFullyBaked(); - static bool areAllNearbyInstancesBaked(S32& grey_avatars); - static void getNearbyRezzedStats(std::vector& counts, F32& avg_cloud_time, S32& cloud_avatars); - static std::string rezStatusToString(S32 status); - - //-------------------------------------------------------------------- - // Baked textures - //-------------------------------------------------------------------- -public: - /*virtual*/ LLTexLayerSet* createTexLayerSet(); // Return LLViewerTexLayerSet - void releaseComponentTextures(); // ! BACKWARDS COMPATIBILITY ! - -protected: - static void onBakedTextureMasksLoaded(bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata); - static void onInitialBakedTextureLoaded(bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata); - static void onBakedTextureLoaded(bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata); - virtual void removeMissingBakedTextures(); - void useBakedTexture(const LLUUID& id); - LLViewerTexLayerSet* getTexLayerSet(const U32 index) const { return dynamic_cast(mBakedTextureDatas[index].mTexLayerSet); } - - - LLLoadedCallbackEntry::source_callback_list_t mCallbackTextureList ; - bool mLoadedCallbacksPaused; - S32 mLoadedCallbackTextures; // count of 'loaded' baked textures, filled from mCallbackTextureList - LLFrameTimer mLastTexCallbackAddedTime; - std::set mTextureIDs; - //-------------------------------------------------------------------- - // Local Textures - //-------------------------------------------------------------------- -protected: - virtual void setLocalTexture(LLAvatarAppearanceDefines::ETextureIndex type, LLViewerTexture* tex, bool baked_version_exits, U32 index = 0); - virtual void addLocalTextureStats(LLAvatarAppearanceDefines::ETextureIndex type, LLViewerFetchedTexture* imagep, F32 texel_area_ratio, bool rendered, bool covered_by_baked); - // MULTI-WEARABLE: make self-only? - virtual void setBakedReady(LLAvatarAppearanceDefines::ETextureIndex type, bool baked_version_exists, U32 index = 0); - - //-------------------------------------------------------------------- - // Texture accessors - //-------------------------------------------------------------------- -private: - virtual void setImage(const U8 te, LLViewerTexture *imagep, const U32 index); - virtual LLViewerTexture* getImage(const U8 te, const U32 index) const; - const std::string getImageURL(const U8 te, const LLUUID &uuid); - - virtual const LLTextureEntry* getTexEntry(const U8 te_num) const; - virtual void setTexEntry(const U8 index, const LLTextureEntry &te); - - void checkTextureLoading() ; - //-------------------------------------------------------------------- - // Layers - //-------------------------------------------------------------------- -protected: - void deleteLayerSetCaches(bool clearAll = true); - void addBakedTextureStats(LLViewerFetchedTexture* imagep, F32 pixel_area, F32 texel_area_ratio, S32 boost_level); - - //-------------------------------------------------------------------- - // Composites - //-------------------------------------------------------------------- -public: - virtual void invalidateComposite(LLTexLayerSet* layerset); - virtual void invalidateAll(); - virtual void setCompositeUpdatesEnabled(bool b) {} - virtual void setCompositeUpdatesEnabled(U32 index, bool b) {} - virtual bool isCompositeUpdateEnabled(U32 index) { return false; } - - //-------------------------------------------------------------------- - // Static texture/mesh/baked dictionary - //-------------------------------------------------------------------- -public: - static bool isIndexLocalTexture(LLAvatarAppearanceDefines::ETextureIndex i); - static bool isIndexBakedTexture(LLAvatarAppearanceDefines::ETextureIndex i); - - //-------------------------------------------------------------------- - // Messaging - //-------------------------------------------------------------------- -public: - void onFirstTEMessageReceived(); -private: - bool mFirstTEMessageReceived; - bool mFirstAppearanceMessageReceived; - -/** Textures - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** MESHES - **/ - -public: - void debugColorizeSubMeshes(U32 i, const LLColor4& color); - virtual void updateMeshTextures(); - void updateSexDependentLayerSets(); - virtual void dirtyMesh(); // Dirty the avatar mesh - void updateMeshData(); - void updateMeshVisibility(); - LLViewerTexture* getBakedTexture(const U8 te); - - // Matrix palette cache entry - class alignas(16) MatrixPaletteCache - { - public: - // Last frame this entry was updated - U32 mFrame; - - // List of Matrix4a's for this entry - LLMeshSkinInfo::matrix_list_t mMatrixPalette; - - // Float array ready to be sent to GL - std::vector mGLMp; - - MatrixPaletteCache() : - mFrame(gFrameCount - 1) - { - } - }; - - // Accessor for Matrix Palette Cache - // Will do a map lookup for the entry associated with the given MeshSkinInfo - // Will update said entry if it hasn't been updated yet this frame - const MatrixPaletteCache& updateSkinInfoMatrixPalette(const LLMeshSkinInfo* skinInfo); - - // Map of LLMeshSkinInfo::mHash to MatrixPaletteCache - typedef std::unordered_map matrix_palette_cache_t; - matrix_palette_cache_t mMatrixPaletteCache; - -protected: - void releaseMeshData(); - virtual void restoreMeshData(); -private: - virtual void dirtyMesh(S32 priority); // Dirty the avatar mesh, with priority - LLViewerJoint* getViewerJoint(S32 idx); - S32 mDirtyMesh; // 0 -- not dirty, 1 -- morphed, 2 -- LOD - bool mMeshTexturesDirty; - - //-------------------------------------------------------------------- - // Destroy invisible mesh - //-------------------------------------------------------------------- -protected: - bool mMeshValid; - LLFrameTimer mMeshInvisibleTime; - -/** Meshes - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** APPEARANCE - **/ - - LLPointer mLastProcessedAppearance; - -public: - void parseAppearanceMessage(LLMessageSystem* mesgsys, LLAppearanceMessageContents& msg); - void processAvatarAppearance(LLMessageSystem* mesgsys); - void applyParsedAppearanceMessage(LLAppearanceMessageContents& contents, bool slam_params); - void hideHair(); - void hideSkirt(); - void startAppearanceAnimation(); - - //-------------------------------------------------------------------- - // Appearance morphing - //-------------------------------------------------------------------- -public: - bool getIsAppearanceAnimating() const { return mAppearanceAnimating; } - - // True if we are computing our appearance via local compositing - // instead of baked textures, as for example during wearable - // editing or when waiting for a subsequent server rebake. - /*virtual*/ bool isUsingLocalAppearance() const { return mUseLocalAppearance; } - - // True if we are currently in appearance editing mode. Often but - // not always the same as isUsingLocalAppearance(). - /*virtual*/ bool isEditingAppearance() const { return mIsEditingAppearance; } - - // FIXME review isUsingLocalAppearance uses, some should be isEditing instead. - -private: - bool mAppearanceAnimating; - LLFrameTimer mAppearanceMorphTimer; - F32 mLastAppearanceBlendTime; - bool mIsEditingAppearance; // flag for if we're actively in appearance editing mode - bool mUseLocalAppearance; // flag for if we're using a local composite - - //-------------------------------------------------------------------- - // Visibility - //-------------------------------------------------------------------- -public: - bool isVisible() const; - virtual bool shouldRenderRigged() const; - void setVisibilityRank(U32 rank); - U32 getVisibilityRank() const { return mVisibilityRank; } - static S32 sNumVisibleAvatars; // Number of instances of this class -/** Appearance - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** WEARABLES - **/ - - //-------------------------------------------------------------------- - // Attachments - //-------------------------------------------------------------------- -public: - void clampAttachmentPositions(); - virtual const LLViewerJointAttachment* attachObject(LLViewerObject *viewer_object); - virtual bool detachObject(LLViewerObject *viewer_object); - static bool getRiggedMeshID( LLViewerObject* pVO, LLUUID& mesh_id ); - void cleanupAttachedMesh( LLViewerObject* pVO ); - bool hasPendingAttachedMeshes(); - static LLVOAvatar* findAvatarFromAttachment(LLViewerObject* obj); - /*virtual*/ bool isWearingWearableType(LLWearableType::EType type ) const; - LLViewerObject * findAttachmentByID( const LLUUID & target_id ) const; - LLViewerJointAttachment* getTargetAttachmentPoint(LLViewerObject* viewer_object); - -protected: - void lazyAttach(); - void rebuildRiggedAttachments( void ); - - //-------------------------------------------------------------------- - // Map of attachment points, by ID - //-------------------------------------------------------------------- -public: - S32 getAttachmentCount() const; // Warning: order(N) not order(1) - typedef std::map attachment_map_t; - attachment_map_t mAttachmentPoints; - std::vector > mPendingAttachment; - - // List of attachments' ids with attach points from simulator. - // we need this info to know when all attachments are present. - std::map mSimAttachments; - S32 mLastCloudAttachmentCount; - LLFrameTimer mLastCloudAttachmentChangeTime; - - //-------------------------------------------------------------------- - // HUD functions - //-------------------------------------------------------------------- -public: - bool hasHUDAttachment() const; - LLBBox getHUDBBox() const; - void resetHUDAttachments(); - S32 getMaxAttachments() const; - bool canAttachMoreObjects(U32 n=1) const; - S32 getMaxAnimatedObjectAttachments() const; - bool canAttachMoreAnimatedObjects(U32 n=1) const; -protected: - U32 getNumAttachments() const; // O(N), not O(1) - U32 getNumAnimatedObjectAttachments() const; // O(N), not O(1) - -/** Wearables - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** ACTIONS - **/ - - //-------------------------------------------------------------------- - // Animations - //-------------------------------------------------------------------- -public: - bool isAnyAnimationSignaled(const LLUUID *anim_array, const S32 num_anims) const; - void processAnimationStateChanges(); -protected: - bool processSingleAnimationStateChange(const LLUUID &anim_id, bool start); - void resetAnimations(); -private: - LLTimer mAnimTimer; - F32 mTimeLast; - - //-------------------------------------------------------------------- - // Animation state data - //-------------------------------------------------------------------- -public: - typedef std::map::iterator AnimIterator; - std::map mSignaledAnimations; // requested state of Animation name/value - std::map mPlayingAnimations; // current state of Animation name/value - - typedef std::multimap AnimationSourceMap; - typedef AnimationSourceMap::iterator AnimSourceIterator; - AnimationSourceMap mAnimationSources; // object ids that triggered anim ids - - //-------------------------------------------------------------------- - // Chat - //-------------------------------------------------------------------- -public: - void addChat(const LLChat& chat); - void clearChat(); - void startTyping() { mTyping = true; mTypingTimer.reset(); } - void stopTyping() { mTyping = false; } -private: - bool mVisibleChat; - - //-------------------------------------------------------------------- - // Lip synch morphs - //-------------------------------------------------------------------- -private: - bool mLipSyncActive; // we're morphing for lip sync - LLVisualParam* mOohMorph; // cached pointers morphs for lip sync - LLVisualParam* mAahMorph; // cached pointers morphs for lip sync - - //-------------------------------------------------------------------- - // Flight - //-------------------------------------------------------------------- -public: - bool mInAir; - LLFrameTimer mTimeInAir; - -/** Actions - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** PHYSICS - **/ - -private: - F32 mSpeedAccum; // measures speed (for diagnostics mostly). - bool mTurning; // controls hysteresis on avatar rotation - F32 mSpeed; // misc. animation repeated state - - //-------------------------------------------------------------------- - // Dimensions - //-------------------------------------------------------------------- -public: - void resolveHeightGlobal(const LLVector3d &inPos, LLVector3d &outPos, LLVector3 &outNorm); - bool distanceToGround( const LLVector3d &startPoint, LLVector3d &collisionPoint, F32 distToIntersectionAlongRay ); - void resolveHeightAgent(const LLVector3 &inPos, LLVector3 &outPos, LLVector3 &outNorm); - void resolveRayCollisionAgent(const LLVector3d start_pt, const LLVector3d end_pt, LLVector3d &out_pos, LLVector3 &out_norm); - void slamPosition(); // Slam position to transmitted position (for teleport); -protected: - - //-------------------------------------------------------------------- - // Material being stepped on - //-------------------------------------------------------------------- -private: - bool mStepOnLand; - U8 mStepMaterial; - LLVector3 mStepObjectVelocity; - -/** Physics - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** HIERARCHY - **/ - -public: - /*virtual*/ bool setParent(LLViewerObject* parent); - /*virtual*/ void addChild(LLViewerObject *childp); - /*virtual*/ void removeChild(LLViewerObject *childp); - - //-------------------------------------------------------------------- - // Sitting - //-------------------------------------------------------------------- -public: - void sitDown(bool bSitting); - bool isSitting(){return mIsSitting;} - void sitOnObject(LLViewerObject *sit_object); - void getOffObject(); -private: - // set this property only with LLVOAvatar::sitDown method - bool mIsSitting; - // position backup in case of missing data - LLVector3 mLastRootPos; - -/** Hierarchy - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** NAME - **/ - -public: - virtual std::string getFullname() const; // Returns "FirstName LastName" - std::string avString() const; // Frequently used string in log messages "Avatar '* labels); - static void getAnimNames(std::vector* names); -private: - bool mNameIsSet; - std::string mTitle; - bool mNameAway; - bool mNameDoNotDisturb; - bool mNameMute; - bool mNameAppearance; - bool mNameFriend; - bool mNameCloud; - F32 mNameAlpha; - bool mRenderGroupTitles; - - //-------------------------------------------------------------------- - // Display the name (then optionally fade it out) - //-------------------------------------------------------------------- -public: - LLFrameTimer mChatTimer; - LLPointer mNameText; -private: - LLFrameTimer mTimeVisible; - std::deque mChats; - bool mTyping; - LLFrameTimer mTypingTimer; - -/** Name - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** SOUNDS - **/ - - //-------------------------------------------------------------------- - // Voice visualizer - //-------------------------------------------------------------------- -public: - // Responsible for detecting the user's voice signal (and when the - // user speaks, it puts a voice symbol over the avatar's head) and gesticulations - LLPointer mVoiceVisualizer; - int mCurrentGesticulationLevel; - - //-------------------------------------------------------------------- - // Step sound - //-------------------------------------------------------------------- -protected: - const LLUUID& getStepSound() const; -private: - // Global table of sound ids per material, and the ground - const static LLUUID sStepSounds[LL_MCODE_END]; - const static LLUUID sStepSoundOnLand; - - //-------------------------------------------------------------------- - // Foot step state (for generating sounds) - //-------------------------------------------------------------------- -public: - void setFootPlane(const LLVector4 &plane) { mFootPlane = plane; } - LLVector4 mFootPlane; -private: - bool mWasOnGroundLeft; - bool mWasOnGroundRight; - -/** Sounds - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** DIAGNOSTICS - **/ - - //-------------------------------------------------------------------- - // General - //-------------------------------------------------------------------- -public: - void getSortedJointNames(S32 joint_type, std::vector& result) const; - void dumpArchetypeXML(const std::string& prefix, bool group_by_wearables = false); - void dumpAppearanceMsgParams( const std::string& dump_prefix, - const LLAppearanceMessageContents& contents); - static void dumpBakedStatus(); - const std::string getBakedStatusForPrintout() const; - void dumpAvatarTEs(const std::string& context) const; - - static F32 sUnbakedTime; // Total seconds with >=1 unbaked avatars - static F32 sUnbakedUpdateTime; // Last time stats were updated (to prevent multiple updates per frame) - static F32 sGreyTime; // Total seconds with >=1 grey avatars - static F32 sGreyUpdateTime; // Last time stats were updated (to prevent multiple updates per frame) -protected: - S32 getUnbakedPixelAreaRank(); - bool mHasGrey; -private: - F32 mMinPixelArea; - F32 mMaxPixelArea; - F32 mAdjustedPixelArea; - std::string mDebugText; - std::string mBakedTextureDebugText; - - - //-------------------------------------------------------------------- - // Avatar Rez Metrics - //-------------------------------------------------------------------- -public: - void debugAvatarRezTime(std::string notification_name, std::string comment = ""); - F32 debugGetExistenceTimeElapsedF32() const { return mDebugExistenceTimer.getElapsedTimeF32(); } - -protected: - LLFrameTimer mRuthDebugTimer; // For tracking how long it takes for av to rez - LLFrameTimer mDebugExistenceTimer; // Debugging for how long the avatar has been in memory. - LLFrameTimer mLastAppearanceMessageTimer; // Time since last appearance message received. - - //-------------------------------------------------------------------- - // COF monitoring - //-------------------------------------------------------------------- - -public: - // COF version of last viewer-initiated appearance update request. For non-self avs, this will remain at default. - S32 mLastUpdateRequestCOFVersion; - - // COF version of last appearance message received for this av. - S32 mLastUpdateReceivedCOFVersion; - -/** Diagnostics - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** SUPPORT CLASSES - **/ - -protected: // Shared with LLVOAvatarSelf - - -/** Support classes - ** ** - *******************************************************************************/ - -}; // LLVOAvatar -extern const F32 SELF_ADDITIONAL_PRI; -extern const S32 MAX_TEXTURE_VIRTUAL_SIZE_RESET_INTERVAL; - -extern const F32 MAX_HOVER_Z; -extern const F32 MIN_HOVER_Z; - -std::string get_sequential_numbered_file_name(const std::string& prefix, - const std::string& suffix); -void dump_sequential_xml(const std::string outprefix, const LLSD& content); -void dump_visual_param(apr_file_t* file, LLVisualParam* viewer_param, F32 value); - -#endif // LL_VOAVATAR_H - +/** + * @file llvoavatar.h + * @brief Declaration of LLVOAvatar class which is a derivation of + * LLViewerObject + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_VOAVATAR_H +#define LL_VOAVATAR_H + +#include +#include +#include +#include + +#include + +#include "llavatarappearance.h" +#include "llchat.h" +#include "lldrawpoolalpha.h" +#include "llviewerobject.h" +#include "llcharacter.h" +#include "llcontrol.h" +#include "llviewerjointmesh.h" +#include "llviewerjointattachment.h" +#include "llrendertarget.h" +#include "llavatarappearancedefines.h" +#include "lltexglobalcolor.h" +#include "lldriverparam.h" +#include "llviewertexlayer.h" +#include "material_codes.h" // LL_MCODE_END +#include "llrigginginfo.h" +#include "llviewerstats.h" +#include "llvovolume.h" +#include "llavatarrendernotifier.h" +#include "llmodel.h" + +extern const LLUUID ANIM_AGENT_BODY_NOISE; +extern const LLUUID ANIM_AGENT_BREATHE_ROT; +extern const LLUUID ANIM_AGENT_PHYSICS_MOTION; +extern const LLUUID ANIM_AGENT_EDITING; +extern const LLUUID ANIM_AGENT_EYE; +extern const LLUUID ANIM_AGENT_FLY_ADJUST; +extern const LLUUID ANIM_AGENT_HAND_MOTION; +extern const LLUUID ANIM_AGENT_HEAD_ROT; +extern const LLUUID ANIM_AGENT_PELVIS_FIX; +extern const LLUUID ANIM_AGENT_TARGET; +extern const LLUUID ANIM_AGENT_WALK_ADJUST; + +class LLViewerWearable; +class LLVoiceVisualizer; +class LLHUDNameTag; +class LLHUDEffectSpiral; +class LLTexGlobalColor; + +struct LLAppearanceMessageContents; +class LLViewerJointMesh; + +const F32 MAX_AVATAR_LOD_FACTOR = 1.0f; + +extern U32 gFrameCount; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// LLVOAvatar +// +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLVOAvatar : + public LLAvatarAppearance, + public LLViewerObject, + public boost::signals2::trackable +{ + LL_ALIGN_NEW; + LOG_CLASS(LLVOAvatar); + +public: + friend class LLVOAvatarSelf; + friend class LLAvatarCheckImpostorMode; + +/******************************************************************************** + ** ** + ** INITIALIZATION + **/ + +public: + LLVOAvatar(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); + virtual void markDead(); + static void initClass(); // Initialize data that's only init'd once per class. + static void cleanupClass(); // Cleanup data that's only init'd once per class. + virtual void initInstance(); // Called after construction to initialize the class. +protected: + virtual ~LLVOAvatar(); + +/** Initialization + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** INHERITED + **/ + + //-------------------------------------------------------------------- + // LLViewerObject interface and related + //-------------------------------------------------------------------- +public: + /*virtual*/ void updateGL(); + /*virtual*/ LLVOAvatar* asAvatar(); + + virtual U32 processUpdateMessage(LLMessageSystem *mesgsys, + void **user_data, + U32 block_num, + const EObjectUpdateType update_type, + LLDataPacker *dp); + virtual void idleUpdate(LLAgent &agent, const F64 &time); + /*virtual*/ bool updateLOD(); + bool updateJointLODs(); + void updateLODRiggedAttachments( void ); + /*virtual*/ bool isActive() const; // Whether this object needs to do an idleUpdate. + S32Bytes totalTextureMemForUUIDS(std::set& ids); + bool allTexturesCompletelyDownloaded(std::set& ids) const; + bool allLocalTexturesCompletelyDownloaded() const; + bool allBakedTexturesCompletelyDownloaded() const; + void bakedTextureOriginCounts(S32 &sb_count, S32 &host_count, + S32 &both_count, S32 &neither_count); + std::string bakedTextureOriginInfo(); + void collectLocalTextureUUIDs(std::set& ids) const; + void collectBakedTextureUUIDs(std::set& ids) const; + void collectTextureUUIDs(std::set& ids); + void releaseOldTextures(); + /*virtual*/ void updateTextures(); + LLViewerFetchedTexture* getBakedTextureImage(const U8 te, const LLUUID& uuid); + /*virtual*/ S32 setTETexture(const U8 te, const LLUUID& uuid); // If setting a baked texture, need to request it from a non-local sim. + /*virtual*/ void onShift(const LLVector4a& shift_vector); + /*virtual*/ U32 getPartitionType() const; + /*virtual*/ const LLVector3 getRenderPosition() const; + /*virtual*/ void updateDrawable(bool force_damped); + /*virtual*/ LLDrawable* createDrawable(LLPipeline *pipeline); + /*virtual*/ bool updateGeometry(LLDrawable *drawable); + /*virtual*/ void setPixelAreaAndAngle(LLAgent &agent); + /*virtual*/ void updateRegion(LLViewerRegion *regionp); + /*virtual*/ void updateSpatialExtents(LLVector4a& newMin, LLVector4a &newMax); + void calculateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax); + /*virtual*/ bool lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, + S32 face = -1, // which face to check, -1 = ALL_SIDES + bool pick_transparent = false, + bool pick_rigged = false, + bool pick_unselectable = true, + S32* face_hit = NULL, // which face was hit + LLVector4a* intersection = NULL, // return the intersection point + LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point + LLVector4a* normal = NULL, // return the surface normal at the intersection point + LLVector4a* tangent = NULL); // return the surface tangent at the intersection point + virtual LLViewerObject* lineSegmentIntersectRiggedAttachments( + const LLVector4a& start, const LLVector4a& end, + S32 face = -1, // which face to check, -1 = ALL_SIDES + bool pick_transparent = false, + bool pick_rigged = false, + bool pick_unselectable = true, + S32* face_hit = NULL, // which face was hit + LLVector4a* intersection = NULL, // return the intersection point + LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point + LLVector4a* normal = NULL, // return the surface normal at the intersection point + LLVector4a* tangent = NULL); // return the surface tangent at the intersection point + + //-------------------------------------------------------------------- + // LLCharacter interface and related + //-------------------------------------------------------------------- +public: + /*virtual*/ LLVector3 getCharacterPosition(); + /*virtual*/ LLQuaternion getCharacterRotation(); + /*virtual*/ LLVector3 getCharacterVelocity(); + /*virtual*/ LLVector3 getCharacterAngularVelocity(); + + /*virtual*/ LLUUID remapMotionID(const LLUUID& id); + /*virtual*/ bool startMotion(const LLUUID& id, F32 time_offset = 0.f); + /*virtual*/ bool stopMotion(const LLUUID& id, bool stop_immediate = false); + virtual bool hasMotionFromSource(const LLUUID& source_id); + virtual void stopMotionFromSource(const LLUUID& source_id); + virtual void requestStopMotion(LLMotion* motion); + LLMotion* findMotion(const LLUUID& id) const; + void startDefaultMotions(); + void dumpAnimationState(); + + virtual LLJoint* getJoint(const std::string &name); + LLJoint* getJoint(S32 num); + + //if you KNOW joint_num is a valid animated joint index, use getSkeletonJoint for efficiency + inline LLJoint* getSkeletonJoint(S32 joint_num) { return mSkeleton[joint_num]; } + inline size_t getSkeletonJointCount() const { return mSkeleton.size(); } + + void notifyAttachmentMeshLoaded(); + void addAttachmentOverridesForObject(LLViewerObject *vo, std::set* meshes_seen = NULL, bool recursive = true); + void removeAttachmentOverridesForObject(const LLUUID& mesh_id); + void removeAttachmentOverridesForObject(LLViewerObject *vo); + bool jointIsRiggedTo(const LLJoint *joint) const; + void clearAttachmentOverrides(); + void rebuildAttachmentOverrides(); + void updateAttachmentOverrides(); + void showAttachmentOverrides(bool verbose = false) const; + void getAttachmentOverrideNames(std::set& pos_names, + std::set& scale_names) const; + + void getAssociatedVolumes(std::vector& volumes); + + // virtual + void updateRiggingInfo(); + // This encodes mesh id and LOD, so we can see whether display is up-to-date. + std::map mLastRiggingInfoKey; + + std::set mActiveOverrideMeshes; + virtual void onActiveOverrideMeshesChanged(); + + /*virtual*/ const LLUUID& getID() const; + /*virtual*/ void addDebugText(const std::string& text); + /*virtual*/ F32 getTimeDilation(); + /*virtual*/ void getGround(const LLVector3 &inPos, LLVector3 &outPos, LLVector3 &outNorm); + /*virtual*/ F32 getPixelArea() const; + /*virtual*/ LLVector3d getPosGlobalFromAgent(const LLVector3 &position); + /*virtual*/ LLVector3 getPosAgentFromGlobal(const LLVector3d &position); + virtual void updateVisualParams(); + +/** Inherited + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** STATE + **/ + +public: + virtual bool isSelf() const { return false; } // True if this avatar is for this viewer's agent + + virtual bool isControlAvatar() const { return mIsControlAvatar; } // True if this avatar is a control av (no associated user) + virtual bool isUIAvatar() const { return mIsUIAvatar; } // True if this avatar is a supplemental av used in some UI views (no associated user) + virtual bool isBuddy() const; + + // If this is an attachment, return the avatar it is attached to. Otherwise NULL. + virtual const LLVOAvatar *getAttachedAvatar() const { return NULL; } + virtual LLVOAvatar *getAttachedAvatar() { return NULL; } + + +private: //aligned members + LL_ALIGN_16(LLVector4a mImpostorExtents[2]); + + //-------------------------------------------------------------------- + // Updates + //-------------------------------------------------------------------- +public: + void updateAppearanceMessageDebugText(); + void updateAnimationDebugText(); + virtual void updateDebugText(); + virtual bool computeNeedsUpdate(); + virtual bool updateCharacter(LLAgent &agent); + void updateFootstepSounds(); + void computeUpdatePeriod(); + void updateOrientation(LLAgent &agent, F32 speed, F32 delta_time); + void updateTimeStep(); + void updateRootPositionAndRotation(LLAgent &agent, F32 speed, bool was_sit_ground_constrained); + + void idleUpdateVoiceVisualizer(bool voice_enabled, const LLVector3 &position); + void idleUpdateMisc(bool detailed_update); + virtual void idleUpdateAppearanceAnimation(); + void idleUpdateLipSync(bool voice_enabled); + void idleUpdateLoadingEffect(); + void idleUpdateWindEffect(); + void idleUpdateNameTag(const LLVector3& root_pos_last); + void idleUpdateNameTagText(bool new_name); + void idleUpdateNameTagAlpha(bool new_name, F32 alpha); + LLColor4 getNameTagColor(bool is_friend); + void clearNameTag(); + static void invalidateNameTag(const LLUUID& agent_id); + // force all name tags to rebuild, useful when display names turned on/off + static void invalidateNameTags(); + void addNameTagLine(const std::string& line, const LLColor4& color, S32 style, const LLFontGL* font, const bool use_ellipses = false); + void idleUpdateRenderComplexity(); + void idleUpdateDebugInfo(); + void accountRenderComplexityForObject(LLViewerObject *attached_object, + const F32 max_attachment_complexity, + LLVOVolume::texture_cost_t& textures, + U32& cost, + hud_complexity_list_t& hud_complexity_list, + object_complexity_list_t& object_complexity_list); + void calculateUpdateRenderComplexity(); + static const U32 VISUAL_COMPLEXITY_UNKNOWN; + void updateVisualComplexity(); + + void placeProfileQuery(); + void readProfileQuery(S32 retries); + + // get the GPU time in ms of rendering this avatar including all attachments + // returns 0.f if this avatar has not been profiled using gPipeline.profileAvatar + // or the avatar is visually muted + F32 getGPURenderTime(); + + // get the total GPU render time in ms of all avatars that have been benched + static F32 getTotalGPURenderTime(); + + // get the max GPU render time in ms of all avatars that have been benched + static F32 getMaxGPURenderTime(); + + // get the average GPU render time in ms of all avatars that have been benched + static F32 getAverageGPURenderTime(); + + // get the CPU time in ms of rendering this avatar including all attachments + // return 0.f if this avatar has not been profiled using gPipeline.mProfileAvatar + F32 getCPURenderTime() { return mCPURenderTime; } + + + // avatar render cost + U32 getVisualComplexity() { return mVisualComplexity; }; + + // surface area calculation + F32 getAttachmentSurfaceArea() { return mAttachmentSurfaceArea; }; + + U32 getReportedVisualComplexity() { return mReportedVisualComplexity; }; // Numbers as reported by the SL server + void setReportedVisualComplexity(U32 value) { mReportedVisualComplexity = value; }; + + S32 getUpdatePeriod() { return mUpdatePeriod; }; + const LLColor4 & getMutedAVColor() { return mMutedAVColor; }; + static void updateImpostorRendering(U32 newMaxNonImpostorsValue); + + void idleUpdateBelowWater(); + + static void updateNearbyAvatarCount(); + + LLVector3 idleCalcNameTagPosition(const LLVector3 &root_pos_last); + + //-------------------------------------------------------------------- + // Static preferences (controlled by user settings/menus) + //-------------------------------------------------------------------- +public: + static S32 sRenderName; + static bool sRenderGroupTitles; + static const U32 NON_IMPOSTORS_MAX_SLIDER; /* Must equal the maximum allowed the RenderAvatarMaxNonImpostors + * slider in panel_preferences_graphics1.xml */ + static U32 sMaxNonImpostors; // affected by control "RenderAvatarMaxNonImpostors" + static bool sLimitNonImpostors; // use impostors for far away avatars + static F32 sRenderDistance; // distance at which avatars will render. + static bool sShowAnimationDebug; // show animation debug info + static bool sShowCollisionVolumes; // show skeletal collision volumes + static bool sVisibleInFirstPerson; + static S32 sNumLODChangesThisFrame; + static S32 sNumVisibleChatBubbles; + static bool sDebugInvisible; + static bool sShowAttachmentPoints; + static F32 sLODFactor; // user-settable LOD factor + static F32 sPhysicsLODFactor; // user-settable physics LOD factor + static bool sJointDebug; // output total number of joints being touched for each avatar + + static LLPointer sCloudTexture; + + static std::vector sAVsIgnoringARTLimit; + static S32 sAvatarsNearby; + + //-------------------------------------------------------------------- + // Region state + //-------------------------------------------------------------------- +public: + LLHost getObjectHost() const; + + //-------------------------------------------------------------------- + // Loading state + //-------------------------------------------------------------------- +public: + bool isFullyLoaded() const; + F32 getFirstDecloudTime() const {return mFirstDecloudTime;} + + // check and return current state relative to limits + // default will test only the geometry (combined=false). + // this allows us to disable shadows separately on complex avatars. + + inline bool isTooSlowWithoutShadows() const {return mTooSlowWithoutShadows;}; + bool isTooSlow() const; + + void updateTooSlow(); + + bool isTooComplex() const; + bool visualParamWeightsAreDefault(); + virtual bool getIsCloud() const; + bool isFullyTextured() const; + bool hasGray() const; + S32 getRezzedStatus() const; // 0 = cloud, 1 = gray, 2 = textured, 3 = textured and fully downloaded. + void updateRezzedStatusTimers(S32 status); + + S32 mLastRezzedStatus; + + + void startPhase(const std::string& phase_name); + void stopPhase(const std::string& phase_name, bool err_check = true); + void clearPhases(); + void logPendingPhases(); + static void logPendingPhasesAllAvatars(); + void logMetricsTimerRecord(const std::string& phase_name, F32 elapsed, bool completed); + + void calcMutedAVColor(); + +protected: + LLViewerStats::PhaseMap& getPhases() { return mPhases; } + bool updateIsFullyLoaded(); + bool processFullyLoadedChange(bool loading); + void updateRuthTimer(bool loading); + F32 calcMorphAmount(); + +private: + bool mFirstFullyVisible; + F32 mFirstDecloudTime; + LLFrameTimer mFirstAppearanceMessageTimer; + + bool mFullyLoaded; + bool mPreviousFullyLoaded; + bool mFullyLoadedInitialized; + S32 mFullyLoadedFrameCounter; + LLColor4 mMutedAVColor; + LLFrameTimer mFullyLoadedTimer; + LLFrameTimer mRuthTimer; + + // variables to hold "slowness" status + bool mTooSlow{false}; + bool mTooSlowWithoutShadows{false}; + + bool mTuned{false}; + +private: + LLViewerStats::PhaseMap mPhases; + +protected: + LLFrameTimer mInvisibleTimer; + +/** State + ** ** + *******************************************************************************/ +/******************************************************************************** + ** ** + ** SKELETON + **/ + +protected: + /*virtual*/ LLAvatarJoint* createAvatarJoint(); // Returns LLViewerJoint + /*virtual*/ LLAvatarJoint* createAvatarJoint(S32 joint_num); // Returns LLViewerJoint + /*virtual*/ LLAvatarJointMesh* createAvatarJointMesh(); // Returns LLViewerJointMesh +public: + void updateHeadOffset(); + void debugBodySize() const; + void postPelvisSetRecalc( void ); + + /*virtual*/ bool loadSkeletonNode(); + void initAttachmentPoints(bool ignore_hud_joints = false); + /*virtual*/ void buildCharacter(); + void resetVisualParams(); + void applyDefaultParams(); + void resetSkeleton(bool reset_animations); + + LLVector3 mCurRootToHeadOffset; + LLVector3 mTargetRootToHeadOffset; + + S32 mLastSkeletonSerialNum; + + +/** Skeleton + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** RENDERING + **/ + +public: + U32 renderImpostor(LLColor4U color = LLColor4U(255,255,255,255), S32 diffuse_channel = 0); + bool isVisuallyMuted(); + bool isInMuteList() const; + + // states for RenderAvatarComplexityMode + enum ERenderComplexityMode + { + AV_RENDER_LIMIT_BY_COMPLEXITY = 0, + AV_RENDER_ALWAYS_SHOW_FRIENDS = 1, + AV_RENDER_ONLY_SHOW_FRIENDS = 2 + }; + + // Visual Mute Setting is an input. Does not necessarily determine + // what the avatar looks like, because it interacts with other + // settings like muting, complexity threshold. Should be private or protected. + enum VisualMuteSettings + { + AV_RENDER_NORMALLY = 0, + AV_DO_NOT_RENDER = 1, + AV_ALWAYS_RENDER = 2 + }; + void setVisualMuteSettings(VisualMuteSettings set); + +protected: + // If you think you need to access this outside LLVOAvatar, you probably want getOverallAppearance() + VisualMuteSettings getVisualMuteSettings() { return mVisuallyMuteSetting; }; + +public: + + // Overall Appearance is an output. Depending on whether the + // avatar is blocked/muted, whether it exceeds the complexity + // threshold, etc, avatar will want to be displayed in one of + // these ways. Rendering code that wants to know how to display an + // avatar should be looking at this value, NOT the visual mute + // settings + enum AvatarOverallAppearance + { + AOA_NORMAL, + AOA_JELLYDOLL, + AOA_INVISIBLE + }; + + AvatarOverallAppearance getOverallAppearance() const; + void setOverallAppearanceNormal(); + void setOverallAppearanceJellyDoll(); + void setOverallAppearanceInvisible(); + + void updateOverallAppearance(); + void updateOverallAppearanceAnimations(); + + std::set mJellyAnims; + + U32 renderRigid(); + U32 renderSkinned(); + F32 getLastSkinTime() { return mLastSkinTime; } + U32 renderTransparent(bool first_pass); + void renderCollisionVolumes(); + void renderBones(const std::string &selected_joint = std::string()); + void renderJoints(); + static void deleteCachedImages(bool clearAll=true); + static void destroyGL(); + static void restoreGL(); + S32 mSpecialRenderMode; // special lighting + +private: + friend class LLPipeline; + AvatarOverallAppearance mOverallAppearance; + F32 mAttachmentSurfaceArea; //estimated surface area of attachments + U32 mAttachmentVisibleTriangleCount; + F32 mAttachmentEstTriangleCount; + bool shouldAlphaMask(); + + bool mNeedsSkin; // avatar has been animated and verts have not been updated + F32 mLastSkinTime; //value of gFrameTimeSeconds at last skin update + + S32 mUpdatePeriod; + S32 mNumInitFaces; //number of faces generated when creating the avatar drawable, does not inculde splitted faces due to long vertex buffer. + + // profile handle + U32 mGPUTimerQuery = 0; + + // profile results + + // GPU render time in ms + F32 mGPURenderTime = 0.f; + bool mGPUProfilePending = false; + + // CPU render time in ms + F32 mCPURenderTime = 0.f; + + // the isTooComplex method uses these mutable values to avoid recalculating too frequently + // DEPRECATED -- obsolete avatar render cost values + mutable U32 mVisualComplexity; + mutable bool mVisualComplexityStale; + U32 mReportedVisualComplexity; // from other viewers through the simulator + + mutable bool mCachedInMuteList; + mutable F64 mCachedMuteListUpdateTime; + + VisualMuteSettings mVisuallyMuteSetting; // Always or never visually mute this AV + + //-------------------------------------------------------------------- + // animated object status + //-------------------------------------------------------------------- +public: + bool mIsControlAvatar; + bool mIsUIAvatar; + bool mEnableDefaultMotions; + + //-------------------------------------------------------------------- + // Morph masks + //-------------------------------------------------------------------- +public: + /*virtual*/ void applyMorphMask(const U8* tex_data, S32 width, S32 height, S32 num_components, LLAvatarAppearanceDefines::EBakedTextureIndex index = LLAvatarAppearanceDefines::BAKED_NUM_INDICES); + bool morphMaskNeedsUpdate(LLAvatarAppearanceDefines::EBakedTextureIndex index = LLAvatarAppearanceDefines::BAKED_NUM_INDICES); + + + //-------------------------------------------------------------------- + // Global colors + //-------------------------------------------------------------------- +public: + /*virtual*/void onGlobalColorChanged(const LLTexGlobalColor* global_color); + + //-------------------------------------------------------------------- + // Visibility + //-------------------------------------------------------------------- +protected: + void updateVisibility(); +private: + U32 mVisibilityRank; + bool mVisible; + + //-------------------------------------------------------------------- + // Shadowing + //-------------------------------------------------------------------- +public: + void updateShadowFaces(); + LLDrawable* mShadow; +private: + LLFace* mShadow0Facep; + LLFace* mShadow1Facep; + LLPointer mShadowImagep; + + //-------------------------------------------------------------------- + // Impostors + //-------------------------------------------------------------------- +public: + virtual bool isImpostor(); + bool shouldImpostor(const F32 rank_factor = 1.0); + bool needsImpostorUpdate() const; + const LLVector3& getImpostorOffset() const; + const LLVector2& getImpostorDim() const; + void getImpostorValues(LLVector4a* extents, LLVector3& angle, F32& distance) const; + void cacheImpostorValues(); + void setImpostorDim(const LLVector2& dim); + static void resetImpostors(); + static void updateImpostors(); + LLRenderTarget mImpostor; + bool mNeedsImpostorUpdate; + S32 mLastImpostorUpdateReason; + F32SecondsImplicit mLastImpostorUpdateFrameTime; + const LLVector3* getLastAnimExtents() const { return mLastAnimExtents; } + void setNeedsExtentUpdate(bool val) { mNeedsExtentUpdate = val; } + +private: + LLVector3 mImpostorOffset; + LLVector2 mImpostorDim; + // This becomes true in the constructor and false after the first + // idleUpdateMisc(). Not clear it serves any purpose. + bool mNeedsAnimUpdate; + bool mNeedsExtentUpdate; + LLVector3 mImpostorAngle; + F32 mImpostorDistance; + F32 mImpostorPixelArea; + LLVector3 mLastAnimExtents[2]; + LLVector3 mLastAnimBasePos; + + LLCachedControl mRenderUnloadedAvatar; + + //-------------------------------------------------------------------- + // Wind rippling in clothes + //-------------------------------------------------------------------- +public: + LLVector4 mWindVec; + F32 mRipplePhase; + bool mBelowWater; +private: + F32 mWindFreq; + LLFrameTimer mRippleTimer; + F32 mRippleTimeLast; + LLVector3 mRippleAccel; + LLVector3 mLastVel; + + //-------------------------------------------------------------------- + // Culling + //-------------------------------------------------------------------- +public: + static void cullAvatarsByPixelArea(); + bool isCulled() const { return mCulled; } +private: + bool mCulled; + + //-------------------------------------------------------------------- + // Constants + //-------------------------------------------------------------------- +public: + virtual LLViewerTexture::EBoostLevel getAvatarBoostLevel() const { return LLGLTexture::BOOST_AVATAR; } + virtual LLViewerTexture::EBoostLevel getAvatarBakedBoostLevel() const { return LLGLTexture::BOOST_AVATAR_BAKED; } + virtual S32 getTexImageSize() const; + /*virtual*/ S32 getTexImageArea() const { return getTexImageSize()*getTexImageSize(); } + +/** Rendering + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** TEXTURES + **/ + + //-------------------------------------------------------------------- + // Loading status + //-------------------------------------------------------------------- +public: + virtual bool isTextureDefined(LLAvatarAppearanceDefines::ETextureIndex type, U32 index = 0) const; + virtual bool isTextureVisible(LLAvatarAppearanceDefines::ETextureIndex type, U32 index = 0) const; + virtual bool isTextureVisible(LLAvatarAppearanceDefines::ETextureIndex type, LLViewerWearable *wearable) const; + + bool isFullyBaked(); + static bool areAllNearbyInstancesBaked(S32& grey_avatars); + static void getNearbyRezzedStats(std::vector& counts, F32& avg_cloud_time, S32& cloud_avatars); + static std::string rezStatusToString(S32 status); + + //-------------------------------------------------------------------- + // Baked textures + //-------------------------------------------------------------------- +public: + /*virtual*/ LLTexLayerSet* createTexLayerSet(); // Return LLViewerTexLayerSet + void releaseComponentTextures(); // ! BACKWARDS COMPATIBILITY ! + +protected: + static void onBakedTextureMasksLoaded(bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata); + static void onInitialBakedTextureLoaded(bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata); + static void onBakedTextureLoaded(bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata); + virtual void removeMissingBakedTextures(); + void useBakedTexture(const LLUUID& id); + LLViewerTexLayerSet* getTexLayerSet(const U32 index) const { return dynamic_cast(mBakedTextureDatas[index].mTexLayerSet); } + + + LLLoadedCallbackEntry::source_callback_list_t mCallbackTextureList ; + bool mLoadedCallbacksPaused; + S32 mLoadedCallbackTextures; // count of 'loaded' baked textures, filled from mCallbackTextureList + LLFrameTimer mLastTexCallbackAddedTime; + std::set mTextureIDs; + //-------------------------------------------------------------------- + // Local Textures + //-------------------------------------------------------------------- +protected: + virtual void setLocalTexture(LLAvatarAppearanceDefines::ETextureIndex type, LLViewerTexture* tex, bool baked_version_exits, U32 index = 0); + virtual void addLocalTextureStats(LLAvatarAppearanceDefines::ETextureIndex type, LLViewerFetchedTexture* imagep, F32 texel_area_ratio, bool rendered, bool covered_by_baked); + // MULTI-WEARABLE: make self-only? + virtual void setBakedReady(LLAvatarAppearanceDefines::ETextureIndex type, bool baked_version_exists, U32 index = 0); + + //-------------------------------------------------------------------- + // Texture accessors + //-------------------------------------------------------------------- +private: + virtual void setImage(const U8 te, LLViewerTexture *imagep, const U32 index); + virtual LLViewerTexture* getImage(const U8 te, const U32 index) const; + const std::string getImageURL(const U8 te, const LLUUID &uuid); + + virtual const LLTextureEntry* getTexEntry(const U8 te_num) const; + virtual void setTexEntry(const U8 index, const LLTextureEntry &te); + + void checkTextureLoading() ; + //-------------------------------------------------------------------- + // Layers + //-------------------------------------------------------------------- +protected: + void deleteLayerSetCaches(bool clearAll = true); + void addBakedTextureStats(LLViewerFetchedTexture* imagep, F32 pixel_area, F32 texel_area_ratio, S32 boost_level); + + //-------------------------------------------------------------------- + // Composites + //-------------------------------------------------------------------- +public: + virtual void invalidateComposite(LLTexLayerSet* layerset); + virtual void invalidateAll(); + virtual void setCompositeUpdatesEnabled(bool b) {} + virtual void setCompositeUpdatesEnabled(U32 index, bool b) {} + virtual bool isCompositeUpdateEnabled(U32 index) { return false; } + + //-------------------------------------------------------------------- + // Static texture/mesh/baked dictionary + //-------------------------------------------------------------------- +public: + static bool isIndexLocalTexture(LLAvatarAppearanceDefines::ETextureIndex i); + static bool isIndexBakedTexture(LLAvatarAppearanceDefines::ETextureIndex i); + + //-------------------------------------------------------------------- + // Messaging + //-------------------------------------------------------------------- +public: + void onFirstTEMessageReceived(); +private: + bool mFirstTEMessageReceived; + bool mFirstAppearanceMessageReceived; + +/** Textures + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** MESHES + **/ + +public: + void debugColorizeSubMeshes(U32 i, const LLColor4& color); + virtual void updateMeshTextures(); + void updateSexDependentLayerSets(); + virtual void dirtyMesh(); // Dirty the avatar mesh + void updateMeshData(); + void updateMeshVisibility(); + LLViewerTexture* getBakedTexture(const U8 te); + + // Matrix palette cache entry + class alignas(16) MatrixPaletteCache + { + public: + // Last frame this entry was updated + U32 mFrame; + + // List of Matrix4a's for this entry + LLMeshSkinInfo::matrix_list_t mMatrixPalette; + + // Float array ready to be sent to GL + std::vector mGLMp; + + MatrixPaletteCache() : + mFrame(gFrameCount - 1) + { + } + }; + + // Accessor for Matrix Palette Cache + // Will do a map lookup for the entry associated with the given MeshSkinInfo + // Will update said entry if it hasn't been updated yet this frame + const MatrixPaletteCache& updateSkinInfoMatrixPalette(const LLMeshSkinInfo* skinInfo); + + // Map of LLMeshSkinInfo::mHash to MatrixPaletteCache + typedef std::unordered_map matrix_palette_cache_t; + matrix_palette_cache_t mMatrixPaletteCache; + +protected: + void releaseMeshData(); + virtual void restoreMeshData(); +private: + virtual void dirtyMesh(S32 priority); // Dirty the avatar mesh, with priority + LLViewerJoint* getViewerJoint(S32 idx); + S32 mDirtyMesh; // 0 -- not dirty, 1 -- morphed, 2 -- LOD + bool mMeshTexturesDirty; + + //-------------------------------------------------------------------- + // Destroy invisible mesh + //-------------------------------------------------------------------- +protected: + bool mMeshValid; + LLFrameTimer mMeshInvisibleTime; + +/** Meshes + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** APPEARANCE + **/ + + LLPointer mLastProcessedAppearance; + +public: + void parseAppearanceMessage(LLMessageSystem* mesgsys, LLAppearanceMessageContents& msg); + void processAvatarAppearance(LLMessageSystem* mesgsys); + void applyParsedAppearanceMessage(LLAppearanceMessageContents& contents, bool slam_params); + void hideHair(); + void hideSkirt(); + void startAppearanceAnimation(); + + //-------------------------------------------------------------------- + // Appearance morphing + //-------------------------------------------------------------------- +public: + bool getIsAppearanceAnimating() const { return mAppearanceAnimating; } + + // True if we are computing our appearance via local compositing + // instead of baked textures, as for example during wearable + // editing or when waiting for a subsequent server rebake. + /*virtual*/ bool isUsingLocalAppearance() const { return mUseLocalAppearance; } + + // True if we are currently in appearance editing mode. Often but + // not always the same as isUsingLocalAppearance(). + /*virtual*/ bool isEditingAppearance() const { return mIsEditingAppearance; } + + // FIXME review isUsingLocalAppearance uses, some should be isEditing instead. + +private: + bool mAppearanceAnimating; + LLFrameTimer mAppearanceMorphTimer; + F32 mLastAppearanceBlendTime; + bool mIsEditingAppearance; // flag for if we're actively in appearance editing mode + bool mUseLocalAppearance; // flag for if we're using a local composite + + //-------------------------------------------------------------------- + // Visibility + //-------------------------------------------------------------------- +public: + bool isVisible() const; + virtual bool shouldRenderRigged() const; + void setVisibilityRank(U32 rank); + U32 getVisibilityRank() const { return mVisibilityRank; } + static S32 sNumVisibleAvatars; // Number of instances of this class +/** Appearance + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** WEARABLES + **/ + + //-------------------------------------------------------------------- + // Attachments + //-------------------------------------------------------------------- +public: + void clampAttachmentPositions(); + virtual const LLViewerJointAttachment* attachObject(LLViewerObject *viewer_object); + virtual bool detachObject(LLViewerObject *viewer_object); + static bool getRiggedMeshID( LLViewerObject* pVO, LLUUID& mesh_id ); + void cleanupAttachedMesh( LLViewerObject* pVO ); + bool hasPendingAttachedMeshes(); + static LLVOAvatar* findAvatarFromAttachment(LLViewerObject* obj); + /*virtual*/ bool isWearingWearableType(LLWearableType::EType type ) const; + LLViewerObject * findAttachmentByID( const LLUUID & target_id ) const; + LLViewerJointAttachment* getTargetAttachmentPoint(LLViewerObject* viewer_object); + +protected: + void lazyAttach(); + void rebuildRiggedAttachments( void ); + + //-------------------------------------------------------------------- + // Map of attachment points, by ID + //-------------------------------------------------------------------- +public: + S32 getAttachmentCount() const; // Warning: order(N) not order(1) + typedef std::map attachment_map_t; + attachment_map_t mAttachmentPoints; + std::vector > mPendingAttachment; + + // List of attachments' ids with attach points from simulator. + // we need this info to know when all attachments are present. + std::map mSimAttachments; + S32 mLastCloudAttachmentCount; + LLFrameTimer mLastCloudAttachmentChangeTime; + + //-------------------------------------------------------------------- + // HUD functions + //-------------------------------------------------------------------- +public: + bool hasHUDAttachment() const; + LLBBox getHUDBBox() const; + void resetHUDAttachments(); + S32 getMaxAttachments() const; + bool canAttachMoreObjects(U32 n=1) const; + S32 getMaxAnimatedObjectAttachments() const; + bool canAttachMoreAnimatedObjects(U32 n=1) const; +protected: + U32 getNumAttachments() const; // O(N), not O(1) + U32 getNumAnimatedObjectAttachments() const; // O(N), not O(1) + +/** Wearables + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** ACTIONS + **/ + + //-------------------------------------------------------------------- + // Animations + //-------------------------------------------------------------------- +public: + bool isAnyAnimationSignaled(const LLUUID *anim_array, const S32 num_anims) const; + void processAnimationStateChanges(); +protected: + bool processSingleAnimationStateChange(const LLUUID &anim_id, bool start); + void resetAnimations(); +private: + LLTimer mAnimTimer; + F32 mTimeLast; + + //-------------------------------------------------------------------- + // Animation state data + //-------------------------------------------------------------------- +public: + typedef std::map::iterator AnimIterator; + std::map mSignaledAnimations; // requested state of Animation name/value + std::map mPlayingAnimations; // current state of Animation name/value + + typedef std::multimap AnimationSourceMap; + typedef AnimationSourceMap::iterator AnimSourceIterator; + AnimationSourceMap mAnimationSources; // object ids that triggered anim ids + + //-------------------------------------------------------------------- + // Chat + //-------------------------------------------------------------------- +public: + void addChat(const LLChat& chat); + void clearChat(); + void startTyping() { mTyping = true; mTypingTimer.reset(); } + void stopTyping() { mTyping = false; } +private: + bool mVisibleChat; + + //-------------------------------------------------------------------- + // Lip synch morphs + //-------------------------------------------------------------------- +private: + bool mLipSyncActive; // we're morphing for lip sync + LLVisualParam* mOohMorph; // cached pointers morphs for lip sync + LLVisualParam* mAahMorph; // cached pointers morphs for lip sync + + //-------------------------------------------------------------------- + // Flight + //-------------------------------------------------------------------- +public: + bool mInAir; + LLFrameTimer mTimeInAir; + +/** Actions + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** PHYSICS + **/ + +private: + F32 mSpeedAccum; // measures speed (for diagnostics mostly). + bool mTurning; // controls hysteresis on avatar rotation + F32 mSpeed; // misc. animation repeated state + + //-------------------------------------------------------------------- + // Dimensions + //-------------------------------------------------------------------- +public: + void resolveHeightGlobal(const LLVector3d &inPos, LLVector3d &outPos, LLVector3 &outNorm); + bool distanceToGround( const LLVector3d &startPoint, LLVector3d &collisionPoint, F32 distToIntersectionAlongRay ); + void resolveHeightAgent(const LLVector3 &inPos, LLVector3 &outPos, LLVector3 &outNorm); + void resolveRayCollisionAgent(const LLVector3d start_pt, const LLVector3d end_pt, LLVector3d &out_pos, LLVector3 &out_norm); + void slamPosition(); // Slam position to transmitted position (for teleport); +protected: + + //-------------------------------------------------------------------- + // Material being stepped on + //-------------------------------------------------------------------- +private: + bool mStepOnLand; + U8 mStepMaterial; + LLVector3 mStepObjectVelocity; + +/** Physics + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** HIERARCHY + **/ + +public: + /*virtual*/ bool setParent(LLViewerObject* parent); + /*virtual*/ void addChild(LLViewerObject *childp); + /*virtual*/ void removeChild(LLViewerObject *childp); + + //-------------------------------------------------------------------- + // Sitting + //-------------------------------------------------------------------- +public: + void sitDown(bool bSitting); + bool isSitting(){return mIsSitting;} + void sitOnObject(LLViewerObject *sit_object); + void getOffObject(); +private: + // set this property only with LLVOAvatar::sitDown method + bool mIsSitting; + // position backup in case of missing data + LLVector3 mLastRootPos; + +/** Hierarchy + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** NAME + **/ + +public: + virtual std::string getFullname() const; // Returns "FirstName LastName" + std::string avString() const; // Frequently used string in log messages "Avatar '* labels); + static void getAnimNames(std::vector* names); +private: + bool mNameIsSet; + std::string mTitle; + bool mNameAway; + bool mNameDoNotDisturb; + bool mNameMute; + bool mNameAppearance; + bool mNameFriend; + bool mNameCloud; + F32 mNameAlpha; + bool mRenderGroupTitles; + + //-------------------------------------------------------------------- + // Display the name (then optionally fade it out) + //-------------------------------------------------------------------- +public: + LLFrameTimer mChatTimer; + LLPointer mNameText; +private: + LLFrameTimer mTimeVisible; + std::deque mChats; + bool mTyping; + LLFrameTimer mTypingTimer; + +/** Name + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** SOUNDS + **/ + + //-------------------------------------------------------------------- + // Voice visualizer + //-------------------------------------------------------------------- +public: + // Responsible for detecting the user's voice signal (and when the + // user speaks, it puts a voice symbol over the avatar's head) and gesticulations + LLPointer mVoiceVisualizer; + int mCurrentGesticulationLevel; + + //-------------------------------------------------------------------- + // Step sound + //-------------------------------------------------------------------- +protected: + const LLUUID& getStepSound() const; +private: + // Global table of sound ids per material, and the ground + const static LLUUID sStepSounds[LL_MCODE_END]; + const static LLUUID sStepSoundOnLand; + + //-------------------------------------------------------------------- + // Foot step state (for generating sounds) + //-------------------------------------------------------------------- +public: + void setFootPlane(const LLVector4 &plane) { mFootPlane = plane; } + LLVector4 mFootPlane; +private: + bool mWasOnGroundLeft; + bool mWasOnGroundRight; + +/** Sounds + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** DIAGNOSTICS + **/ + + //-------------------------------------------------------------------- + // General + //-------------------------------------------------------------------- +public: + void getSortedJointNames(S32 joint_type, std::vector& result) const; + void dumpArchetypeXML(const std::string& prefix, bool group_by_wearables = false); + void dumpAppearanceMsgParams( const std::string& dump_prefix, + const LLAppearanceMessageContents& contents); + static void dumpBakedStatus(); + const std::string getBakedStatusForPrintout() const; + void dumpAvatarTEs(const std::string& context) const; + + static F32 sUnbakedTime; // Total seconds with >=1 unbaked avatars + static F32 sUnbakedUpdateTime; // Last time stats were updated (to prevent multiple updates per frame) + static F32 sGreyTime; // Total seconds with >=1 grey avatars + static F32 sGreyUpdateTime; // Last time stats were updated (to prevent multiple updates per frame) +protected: + S32 getUnbakedPixelAreaRank(); + bool mHasGrey; +private: + F32 mMinPixelArea; + F32 mMaxPixelArea; + F32 mAdjustedPixelArea; + std::string mDebugText; + std::string mBakedTextureDebugText; + + + //-------------------------------------------------------------------- + // Avatar Rez Metrics + //-------------------------------------------------------------------- +public: + void debugAvatarRezTime(std::string notification_name, std::string comment = ""); + F32 debugGetExistenceTimeElapsedF32() const { return mDebugExistenceTimer.getElapsedTimeF32(); } + +protected: + LLFrameTimer mRuthDebugTimer; // For tracking how long it takes for av to rez + LLFrameTimer mDebugExistenceTimer; // Debugging for how long the avatar has been in memory. + LLFrameTimer mLastAppearanceMessageTimer; // Time since last appearance message received. + + //-------------------------------------------------------------------- + // COF monitoring + //-------------------------------------------------------------------- + +public: + // COF version of last viewer-initiated appearance update request. For non-self avs, this will remain at default. + S32 mLastUpdateRequestCOFVersion; + + // COF version of last appearance message received for this av. + S32 mLastUpdateReceivedCOFVersion; + +/** Diagnostics + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** SUPPORT CLASSES + **/ + +protected: // Shared with LLVOAvatarSelf + + +/** Support classes + ** ** + *******************************************************************************/ + +}; // LLVOAvatar +extern const F32 SELF_ADDITIONAL_PRI; +extern const S32 MAX_TEXTURE_VIRTUAL_SIZE_RESET_INTERVAL; + +extern const F32 MAX_HOVER_Z; +extern const F32 MIN_HOVER_Z; + +std::string get_sequential_numbered_file_name(const std::string& prefix, + const std::string& suffix); +void dump_sequential_xml(const std::string outprefix, const LLSD& content); +void dump_visual_param(apr_file_t* file, LLVisualParam* viewer_param, F32 value); + +#endif // LL_VOAVATAR_H + diff --git a/indra/newview/llvoavatarself.cpp b/indra/newview/llvoavatarself.cpp index 753f105736..2e347920f5 100644 --- a/indra/newview/llvoavatarself.cpp +++ b/indra/newview/llvoavatarself.cpp @@ -1,2901 +1,2901 @@ -/** - * @file llvoavatar.cpp - * @brief Implementation of LLVOAvatar class which is a derivation fo LLViewerObject - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#if LL_MSVC -// disable warning about boost::lexical_cast returning uninitialized data -// when it fails to parse the string -#pragma warning (disable:4701) -#endif - -#include "llviewerprecompiledheaders.h" - -#include "llvoavatarself.h" -#include "llvoavatar.h" - -#include "pipeline.h" - -#include "llagent.h" // Get state values from here -#include "llattachmentsmgr.h" -#include "llagentcamera.h" -#include "llagentwearables.h" -#include "llhudeffecttrail.h" -#include "llhudmanager.h" -#include "llinventoryfunctions.h" -#include "lllocaltextureobject.h" -#include "llnotificationsutil.h" -#include "llselectmgr.h" -#include "lltoolgrab.h" // for needsRenderBeam -#include "lltoolmgr.h" // for needsRenderBeam -#include "lltoolmorph.h" -#include "lltrans.h" -#include "llviewercamera.h" -#include "llviewercontrol.h" -#include "llviewermenu.h" -#include "llviewerobjectlist.h" -#include "llviewerstats.h" -#include "llviewerregion.h" -#include "llviewertexlayer.h" -#include "llviewerwearable.h" -#include "llappearancemgr.h" -#include "llmeshrepository.h" -#include "llvovolume.h" -#include "llsdutil.h" -#include "llstartup.h" -#include "llsdserialize.h" -#include "llcallstack.h" -#include "llcorehttputil.h" -#include "lluiusage.h" - -#if LL_MSVC -// disable boost::lexical_cast warning -#pragma warning (disable:4702) -#endif - -#include - -LLPointer gAgentAvatarp = NULL; - -bool isAgentAvatarValid() -{ - return (gAgentAvatarp.notNull() && gAgentAvatarp->isValid()); -} - -void selfStartPhase(const std::string& phase_name) -{ - if (isAgentAvatarValid()) - { - gAgentAvatarp->startPhase(phase_name); - } -} - -void selfStopPhase(const std::string& phase_name, bool err_check) -{ - if (isAgentAvatarValid()) - { - gAgentAvatarp->stopPhase(phase_name, err_check); - } -} - -void selfClearPhases() -{ - if (isAgentAvatarValid()) - { - gAgentAvatarp->clearPhases(); - } -} - -using namespace LLAvatarAppearanceDefines; - - -LLSD summarize_by_buckets(std::vector in_records, std::vector by_fields, std::string val_field); - -/********************************************************************************* - ** ** - ** Begin private LLVOAvatarSelf Support classes - ** - **/ - -struct LocalTextureData -{ - LocalTextureData() : - mIsBakedReady(false), - mDiscard(MAX_DISCARD_LEVEL+1), - mImage(NULL), - mWearableID(IMG_DEFAULT_AVATAR), - mTexEntry(NULL) - {} - LLPointer mImage; - bool mIsBakedReady; - S32 mDiscard; - LLUUID mWearableID; // UUID of the wearable that this texture belongs to, not of the image itself - LLTextureEntry *mTexEntry; -}; - -//----------------------------------------------------------------------------- -// Callback data -//----------------------------------------------------------------------------- - - -/** - ** - ** End LLVOAvatarSelf Support classes - ** ** - *********************************************************************************/ - - -//----------------------------------------------------------------------------- -// Static Data -//----------------------------------------------------------------------------- -S32Bytes LLVOAvatarSelf::sScratchTexBytes(0); -std::map< LLGLenum, LLGLuint*> LLVOAvatarSelf::sScratchTexNames; - - -/********************************************************************************* - ** ** - ** Begin LLVOAvatarSelf Constructor routines - ** - **/ - -LLVOAvatarSelf::LLVOAvatarSelf(const LLUUID& id, - const LLPCode pcode, - LLViewerRegion* regionp) : - LLVOAvatar(id, pcode, regionp), - mScreenp(NULL), - mLastRegionHandle(0), - mRegionCrossingCount(0), - // Value outside legal range, so will always be a mismatch the - // first time through. - mLastHoverOffsetSent(LLVector3(0.0f, 0.0f, -999.0f)), - mInitialMetric(true), - mMetricSequence(0) -{ - mMotionController.mIsSelf = true; - - LL_DEBUGS() << "Marking avatar as self " << id << LL_ENDL; -} - -// Called periodically for diagnostics, return true when done. -bool output_self_av_texture_diagnostics() -{ - if (!isAgentAvatarValid()) - return true; // done checking - - gAgentAvatarp->outputRezDiagnostics(); - - return false; -} - -bool update_avatar_rez_metrics() -{ - if (!isAgentAvatarValid()) - return true; - - gAgentAvatarp->updateAvatarRezMetrics(false); - - return false; -} - -void LLVOAvatarSelf::initInstance() -{ - bool status = true; - // creates hud joint(mScreen) among other things - status &= loadAvatarSelf(); - - // adds attachment points to mScreen among other things - LLVOAvatar::initInstance(); - - LL_INFOS() << "Self avatar object created. Starting timer." << LL_ENDL; - mDebugSelfLoadTimer.reset(); - // clear all times to -1 for debugging - for (U32 i =0; i < LLAvatarAppearanceDefines::TEX_NUM_INDICES; ++i) - { - for (U32 j = 0; j <= MAX_DISCARD_LEVEL; ++j) - { - mDebugTextureLoadTimes[i][j] = -1.0f; - } - } - - for (U32 i =0; i < LLAvatarAppearanceDefines::BAKED_NUM_INDICES; ++i) - { - mDebugBakedTextureTimes[i][0] = -1.0f; - mDebugBakedTextureTimes[i][1] = -1.0f; - } - - status &= buildMenus(); - if (!status) - { - LL_ERRS() << "Unable to load user's avatar" << LL_ENDL; - return; - } - - setHoverIfRegionEnabled(); - - //doPeriodically(output_self_av_texture_diagnostics, 30.0); - doPeriodically(update_avatar_rez_metrics, 5.0); - doPeriodically(boost::bind(&LLVOAvatarSelf::checkStuckAppearance, this), 30.0); - - mInitFlags |= 1<<2; -} - -void LLVOAvatarSelf::setHoverIfRegionEnabled() -{ - if (getRegion() && getRegion()->simulatorFeaturesReceived()) - { - if (getRegion()->avatarHoverHeightEnabled()) - { - F32 hover_z = gSavedPerAccountSettings.getF32("AvatarHoverOffsetZ"); - setHoverOffset(LLVector3(0.0, 0.0, llclamp(hover_z,MIN_HOVER_Z,MAX_HOVER_Z))); - LL_INFOS("Avatar") << avString() << " set hover height from debug setting " << hover_z << LL_ENDL; - } - else - { - setHoverOffset(LLVector3(0.0, 0.0, 0.0)); - LL_INFOS("Avatar") << avString() << " zeroing hover height, region does not support" << LL_ENDL; - } - } - else - { - LL_INFOS("Avatar") << avString() << " region or simulator features not known, no change on hover" << LL_ENDL; - if (getRegion()) - { - getRegion()->setSimulatorFeaturesReceivedCallback(boost::bind(&LLVOAvatarSelf::onSimulatorFeaturesReceived,this,_1)); - } - - } -} - -bool LLVOAvatarSelf::checkStuckAppearance() -{ - const F32 CONDITIONAL_UNSTICK_INTERVAL = 300.0; - const F32 UNCONDITIONAL_UNSTICK_INTERVAL = 600.0; - - if (gAgentWearables.isCOFChangeInProgress()) - { - LL_DEBUGS("Avatar") << "checking for stuck appearance" << LL_ENDL; - F32 change_time = gAgentWearables.getCOFChangeTime(); - LL_DEBUGS("Avatar") << "change in progress for " << change_time << " seconds" << LL_ENDL; - S32 active_hp = LLAppearanceMgr::instance().countActiveHoldingPatterns(); - LL_DEBUGS("Avatar") << "active holding patterns " << active_hp << " seconds" << LL_ENDL; - S32 active_copies = LLAppearanceMgr::instance().getActiveCopyOperations(); - LL_DEBUGS("Avatar") << "active copy operations " << active_copies << LL_ENDL; - - if ((change_time > CONDITIONAL_UNSTICK_INTERVAL && active_copies == 0) || - (change_time > UNCONDITIONAL_UNSTICK_INTERVAL)) - { - gAgentWearables.notifyLoadingFinished(); - } - } - - // Return false to continue running check periodically. - return LLApp::isExiting(); -} - -// virtual -void LLVOAvatarSelf::markDead() -{ - mBeam = NULL; - LLVOAvatar::markDead(); -} - -/*virtual*/ bool LLVOAvatarSelf::loadAvatar() -{ - bool success = LLVOAvatar::loadAvatar(); - - // set all parameters stored directly in the avatar to have - // the isSelfParam to be true - this is used to prevent - // them from being animated or trigger accidental rebakes - // when we copy params from the wearable to the base avatar. - for (LLViewerVisualParam* param = (LLViewerVisualParam*) getFirstVisualParam(); - param; - param = (LLViewerVisualParam*) getNextVisualParam()) - { - if (param->getWearableType() != LLWearableType::WT_INVALID) - { - param->setIsDummy(true); - } - } - - return success; -} - - -bool LLVOAvatarSelf::loadAvatarSelf() -{ - bool success = true; - // avatar_skeleton.xml - if (!buildSkeletonSelf(sAvatarSkeletonInfo)) - { - LL_WARNS() << "avatar file: buildSkeleton() failed" << LL_ENDL; - return false; - } - - return success; -} - -bool LLVOAvatarSelf::buildSkeletonSelf(const LLAvatarSkeletonInfo *info) -{ - // add special-purpose "screen" joint - mScreenp = new LLViewerJoint("mScreen", NULL); - // for now, put screen at origin, as it is only used during special - // HUD rendering mode - F32 aspect = LLViewerCamera::getInstance()->getAspect(); - LLVector3 scale(1.f, aspect, 1.f); - mScreenp->setScale(scale); - // SL-315 - mScreenp->setWorldPosition(LLVector3::zero); - // need to update screen agressively when sidebar opens/closes, for example - mScreenp->mUpdateXform = true; - return true; -} - -bool LLVOAvatarSelf::buildMenus() -{ - //------------------------------------------------------------------------- - // build the attach and detach menus - //------------------------------------------------------------------------- - gAttachBodyPartPieMenus[0] = NULL; - - LLContextMenu::Params params; - params.label(LLTrans::getString("BodyPartsRightArm")); - params.name(params.label); - params.visible(false); - gAttachBodyPartPieMenus[1] = LLUICtrlFactory::create (params); - - params.label(LLTrans::getString("BodyPartsHead")); - params.name(params.label); - gAttachBodyPartPieMenus[2] = LLUICtrlFactory::create (params); - - params.label(LLTrans::getString("BodyPartsLeftArm")); - params.name(params.label); - gAttachBodyPartPieMenus[3] = LLUICtrlFactory::create (params); - - gAttachBodyPartPieMenus[4] = NULL; - - params.label(LLTrans::getString("BodyPartsLeftLeg")); - params.name(params.label); - gAttachBodyPartPieMenus[5] = LLUICtrlFactory::create (params); - - params.label(LLTrans::getString("BodyPartsTorso")); - params.name(params.label); - gAttachBodyPartPieMenus[6] = LLUICtrlFactory::create (params); - - params.label(LLTrans::getString("BodyPartsRightLeg")); - params.name(params.label); - gAttachBodyPartPieMenus[7] = LLUICtrlFactory::create (params); - - params.label(LLTrans::getString("BodyPartsEnhancedSkeleton")); - params.name(params.label); - gAttachBodyPartPieMenus[8] = LLUICtrlFactory::create(params); - - gDetachBodyPartPieMenus[0] = NULL; - - params.label(LLTrans::getString("BodyPartsRightArm")); - params.name(params.label); - gDetachBodyPartPieMenus[1] = LLUICtrlFactory::create (params); - - params.label(LLTrans::getString("BodyPartsHead")); - params.name(params.label); - gDetachBodyPartPieMenus[2] = LLUICtrlFactory::create (params); - - params.label(LLTrans::getString("BodyPartsLeftArm")); - params.name(params.label); - gDetachBodyPartPieMenus[3] = LLUICtrlFactory::create (params); - - gDetachBodyPartPieMenus[4] = NULL; - - params.label(LLTrans::getString("BodyPartsLeftLeg")); - params.name(params.label); - gDetachBodyPartPieMenus[5] = LLUICtrlFactory::create (params); - - params.label(LLTrans::getString("BodyPartsTorso")); - params.name(params.label); - gDetachBodyPartPieMenus[6] = LLUICtrlFactory::create (params); - - params.label(LLTrans::getString("BodyPartsRightLeg")); - params.name(params.label); - gDetachBodyPartPieMenus[7] = LLUICtrlFactory::create (params); - - params.label(LLTrans::getString("BodyPartsEnhancedSkeleton")); - params.name(params.label); - gDetachBodyPartPieMenus[8] = LLUICtrlFactory::create(params); - - for (S32 i = 0; i < 9; i++) - { - if (gAttachBodyPartPieMenus[i]) - { - gAttachPieMenu->appendContextSubMenu( gAttachBodyPartPieMenus[i] ); - } - else - { - for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - if (attachment && attachment->getGroup() == i) - { - LLMenuItemCallGL::Params item_params; - - std::string sub_piemenu_name = attachment->getName(); - if (LLTrans::getString(sub_piemenu_name) != "") - { - item_params.label = LLTrans::getString(sub_piemenu_name); - } - else - { - item_params.label = sub_piemenu_name; - } - item_params.name =(item_params.label ); - item_params.on_click.function_name = "Object.AttachToAvatar"; - item_params.on_click.parameter = iter->first; - item_params.on_enable.function_name = "Object.EnableWear"; - item_params.on_enable.parameter = iter->first; - LLMenuItemCallGL* item = LLUICtrlFactory::create(item_params); - - gAttachPieMenu->addChild(item); - - break; - - } - } - } - - if (gDetachBodyPartPieMenus[i]) - { - gDetachPieMenu->appendContextSubMenu( gDetachBodyPartPieMenus[i] ); - gDetachAttSelfMenu->appendContextSubMenu(gDetachBodyPartPieMenus[i]); - gDetachAvatarMenu->appendContextSubMenu(gDetachBodyPartPieMenus[i]); - } - else - { - for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - if (attachment && attachment->getGroup() == i) - { - LLMenuItemCallGL::Params item_params; - std::string sub_piemenu_name = attachment->getName(); - if (LLTrans::getString(sub_piemenu_name) != "") - { - item_params.label = LLTrans::getString(sub_piemenu_name); - } - else - { - item_params.label = sub_piemenu_name; - } - item_params.name =(item_params.label ); - item_params.on_click.function_name = "Attachment.DetachFromPoint"; - item_params.on_click.parameter = iter->first; - item_params.on_enable.function_name = "Attachment.PointFilled"; - item_params.on_enable.parameter = iter->first; - LLMenuItemCallGL* item = LLUICtrlFactory::create(item_params); - - gDetachPieMenu->addChild(item); - gDetachAttSelfMenu->addChild(LLUICtrlFactory::create(item_params)); - gDetachAvatarMenu->addChild(LLUICtrlFactory::create(item_params)); - break; - } - } - } - } - - - // add screen attachments - for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - if (attachment->getGroup() == 9) - { - LLMenuItemCallGL::Params item_params; - std::string sub_piemenu_name = attachment->getName(); - if (LLTrans::getString(sub_piemenu_name) != "") - { - item_params.label = LLTrans::getString(sub_piemenu_name); - } - else - { - item_params.label = sub_piemenu_name; - } - item_params.name =(item_params.label ); - item_params.on_click.function_name = "Object.AttachToAvatar"; - item_params.on_click.parameter = iter->first; - item_params.on_enable.function_name = "Object.EnableWear"; - item_params.on_enable.parameter = iter->first; - LLMenuItemCallGL* item = LLUICtrlFactory::create(item_params); - gAttachScreenPieMenu->addChild(item); - - item_params.on_click.function_name = "Attachment.DetachFromPoint"; - item_params.on_click.parameter = iter->first; - item_params.on_enable.function_name = "Attachment.PointFilled"; - item_params.on_enable.parameter = iter->first; - item = LLUICtrlFactory::create(item_params); - gDetachScreenPieMenu->addChild(item); - gDetachHUDAttSelfMenu->addChild(LLUICtrlFactory::create(item_params)); - gDetachHUDAvatarMenu->addChild(LLUICtrlFactory::create(item_params)); - } - } - - for (S32 pass = 0; pass < 2; pass++) - { - // *TODO: Skinning - gAttachSubMenu is an awful, awful hack - if (!gAttachSubMenu) - { - break; - } - for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - if (attachment->getIsHUDAttachment() != (pass == 1)) - { - continue; - } - LLMenuItemCallGL::Params item_params; - std::string sub_piemenu_name = attachment->getName(); - if (LLTrans::getString(sub_piemenu_name) != "") - { - item_params.label = LLTrans::getString(sub_piemenu_name); - } - else - { - item_params.label = sub_piemenu_name; - } - item_params.name =(item_params.label ); - item_params.on_click.function_name = "Object.AttachToAvatar"; - item_params.on_click.parameter = iter->first; - item_params.on_enable.function_name = "Object.EnableWear"; - item_params.on_enable.parameter = iter->first; - //* TODO: Skinning: - //LLSD params; - //params["index"] = iter->first; - //params["label"] = attachment->getName(); - //item->addEventHandler("on_enable", LLMenuItemCallGL::MenuCallback().function_name("Attachment.Label").parameter(params)); - - LLMenuItemCallGL* item = LLUICtrlFactory::create(item_params); - gAttachSubMenu->addChild(item); - - item_params.on_click.function_name = "Attachment.DetachFromPoint"; - item_params.on_click.parameter = iter->first; - item_params.on_enable.function_name = "Attachment.PointFilled"; - item_params.on_enable.parameter = iter->first; - //* TODO: Skinning: item->addEventHandler("on_enable", LLMenuItemCallGL::MenuCallback().function_name("Attachment.Label").parameter(params)); - - item = LLUICtrlFactory::create(item_params); - gDetachSubMenu->addChild(item); - } - if (pass == 0) - { - // put separator between non-hud and hud attachments - gAttachSubMenu->addSeparator(); - gDetachSubMenu->addSeparator(); - } - } - - for (S32 group = 0; group < 9; group++) - { - // skip over groups that don't have sub menus - if (!gAttachBodyPartPieMenus[group] || !gDetachBodyPartPieMenus[group]) - { - continue; - } - - std::multimap attachment_pie_menu_map; - - // gather up all attachment points assigned to this group, and throw into map sorted by pie slice number - for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - if(attachment && attachment->getGroup() == group) - { - // use multimap to provide a partial order off of the pie slice key - S32 pie_index = attachment->getPieSlice(); - attachment_pie_menu_map.insert(std::make_pair(pie_index, iter->first)); - } - } - - // add in requested order to pie menu, inserting separators as necessary - for (std::multimap::iterator attach_it = attachment_pie_menu_map.begin(); - attach_it != attachment_pie_menu_map.end(); ++attach_it) - { - S32 attach_index = attach_it->second; - - LLViewerJointAttachment* attachment = get_if_there(mAttachmentPoints, attach_index, (LLViewerJointAttachment*)NULL); - if (attachment) - { - LLMenuItemCallGL::Params item_params; - item_params.name = attachment->getName(); - item_params.label = LLTrans::getString(attachment->getName()); - item_params.on_click.function_name = "Object.AttachToAvatar"; - item_params.on_click.parameter = attach_index; - item_params.on_enable.function_name = "Object.EnableWear"; - item_params.on_enable.parameter = attach_index; - - LLMenuItemCallGL* item = LLUICtrlFactory::create(item_params); - gAttachBodyPartPieMenus[group]->addChild(item); - - item_params.on_click.function_name = "Attachment.DetachFromPoint"; - item_params.on_click.parameter = attach_index; - item_params.on_enable.function_name = "Attachment.PointFilled"; - item_params.on_enable.parameter = attach_index; - item = LLUICtrlFactory::create(item_params); - gDetachBodyPartPieMenus[group]->addChild(item); - } - } - } - return true; -} - -void LLVOAvatarSelf::cleanup() -{ - markDead(); - delete mScreenp; - mScreenp = NULL; - mRegionp = NULL; -} - -LLVOAvatarSelf::~LLVOAvatarSelf() -{ - cleanup(); -} - -/** - ** - ** End LLVOAvatarSelf Constructor routines - ** ** - *********************************************************************************/ - -// virtual -bool LLVOAvatarSelf::updateCharacter(LLAgent &agent) -{ - // update screen joint size - if (mScreenp) - { - F32 aspect = LLViewerCamera::getInstance()->getAspect(); - LLVector3 scale(1.f, aspect, 1.f); - mScreenp->setScale(scale); - mScreenp->updateWorldMatrixChildren(); - resetHUDAttachments(); - } - - return LLVOAvatar::updateCharacter(agent); -} - -// virtual -bool LLVOAvatarSelf::isValid() const -{ - return ((getRegion() != NULL) && !isDead()); -} - -// virtual -void LLVOAvatarSelf::idleUpdate(LLAgent &agent, const F64 &time) -{ - if (isValid()) - { - LLVOAvatar::idleUpdate(agent, time); - idleUpdateTractorBeam(); - } -} - -// virtual -LLJoint *LLVOAvatarSelf::getJoint(const std::string &name) -{ - LLJoint *jointp = NULL; - jointp = LLVOAvatar::getJoint(name); - if (!jointp && mScreenp) - { - jointp = mScreenp->findJoint(name); - if (jointp) - { - mJointMap[name] = jointp; - } - } - if (jointp && jointp != mScreenp && jointp != mRoot) - { - llassert(LLVOAvatar::getJoint((S32)jointp->getJointNum())==jointp); - } - return jointp; -} - -// virtual -bool LLVOAvatarSelf::setVisualParamWeight(const LLVisualParam *which_param, F32 weight) -{ - if (!which_param) - { - return false; - } - LLViewerVisualParam *param = (LLViewerVisualParam*) LLCharacter::getVisualParam(which_param->getID()); - return setParamWeight(param,weight); -} - -// virtual -bool LLVOAvatarSelf::setVisualParamWeight(const char* param_name, F32 weight) -{ - if (!param_name) - { - return false; - } - LLViewerVisualParam *param = (LLViewerVisualParam*) LLCharacter::getVisualParam(param_name); - return setParamWeight(param,weight); -} - -// virtual -bool LLVOAvatarSelf::setVisualParamWeight(S32 index, F32 weight) -{ - LLViewerVisualParam *param = (LLViewerVisualParam*) LLCharacter::getVisualParam(index); - return setParamWeight(param,weight); -} - -bool LLVOAvatarSelf::setParamWeight(const LLViewerVisualParam *param, F32 weight) -{ - if (!param) - { - return false; - } - - if (param->getCrossWearable()) - { - LLWearableType::EType type = (LLWearableType::EType)param->getWearableType(); - U32 size = gAgentWearables.getWearableCount(type); - for (U32 count = 0; count < size; ++count) - { - LLViewerWearable *wearable = gAgentWearables.getViewerWearable(type,count); - if (wearable) - { - wearable->setVisualParamWeight(param->getID(), weight); - } - } - } - - return LLCharacter::setVisualParamWeight(param,weight); -} - -/*virtual*/ -void LLVOAvatarSelf::updateVisualParams() -{ - LLVOAvatar::updateVisualParams(); -} - -void LLVOAvatarSelf::writeWearablesToAvatar() -{ - for (U32 type = 0; type < LLWearableType::WT_COUNT; type++) - { - LLWearable *wearable = gAgentWearables.getTopWearable((LLWearableType::EType)type); - if (wearable) - { - wearable->writeToAvatar(this); - } - } - -} - -/*virtual*/ -void LLVOAvatarSelf::idleUpdateAppearanceAnimation() -{ - // Animate all top-level wearable visual parameters - gAgentWearables.animateAllWearableParams(calcMorphAmount()); - - // Apply wearable visual params to avatar - writeWearablesToAvatar(); - - //allow avatar to process updates - LLVOAvatar::idleUpdateAppearanceAnimation(); - -} - -// virtual -void LLVOAvatarSelf::requestStopMotion(LLMotion* motion) -{ - // Only agent avatars should handle the stop motion notifications. - - // Notify agent that motion has stopped - gAgent.requestStopMotion(motion); -} - -// virtual -bool LLVOAvatarSelf::hasMotionFromSource(const LLUUID& source_id) -{ - AnimSourceIterator motion_it = mAnimationSources.find(source_id); - return motion_it != mAnimationSources.end(); -} - -// virtual -void LLVOAvatarSelf::stopMotionFromSource(const LLUUID& source_id) -{ - for (AnimSourceIterator motion_it = mAnimationSources.find(source_id); motion_it != mAnimationSources.end(); ) - { - gAgent.sendAnimationRequest(motion_it->second, ANIM_REQUEST_STOP); - mAnimationSources.erase(motion_it); - // Must find() after each erase() to deal with potential iterator invalidation - // This also ensures that we don't go past the end of this source's animations - // into those of another source. - motion_it = mAnimationSources.find(source_id); - } - - - LLViewerObject* object = gObjectList.findObject(source_id); - if (object) - { - object->setFlagsWithoutUpdate(FLAGS_ANIM_SOURCE, false); - } -} - -void LLVOAvatarSelf::setLocalTextureTE(U8 te, LLViewerTexture* image, U32 index) -{ - if (te >= TEX_NUM_INDICES) - { - llassert(0); - return; - } - - if (getTEImage(te)->getID() == image->getID()) - { - return; - } - - if (isIndexBakedTexture((ETextureIndex)te)) - { - llassert(0); - return; - } - - setTEImage(te, image); -} - -//virtual -void LLVOAvatarSelf::removeMissingBakedTextures() -{ - bool removed = false; - for (U32 i = 0; i < mBakedTextureDatas.size(); i++) - { - const S32 te = mBakedTextureDatas[i].mTextureIndex; - const LLViewerTexture* tex = getTEImage(te); - - // Replace with default if we can't find the asset, assuming the - // default is actually valid (which it should be unless something - // is seriously wrong). - if (!tex || tex->isMissingAsset()) - { - LLViewerTexture *imagep = LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT_AVATAR); - if (imagep && imagep != tex) - { - setTEImage(te, imagep); - removed = true; - } - } - } - - if (removed) - { - for (U32 i = 0; i < mBakedTextureDatas.size(); i++) - { - LLViewerTexLayerSet *layerset = getTexLayerSet(i); - layerset->setUpdatesEnabled(true); - invalidateComposite(layerset); - } - updateMeshTextures(); - } -} - -void LLVOAvatarSelf::onSimulatorFeaturesReceived(const LLUUID& region_id) -{ - LL_INFOS("Avatar") << "simulator features received, setting hover based on region props" << LL_ENDL; - setHoverIfRegionEnabled(); -} - -//virtual -void LLVOAvatarSelf::updateRegion(LLViewerRegion *regionp) -{ - // Save the global position - LLVector3d global_pos_from_old_region = getPositionGlobal(); - - // Change the region - setRegion(regionp); - - if (regionp) - { // Set correct region-relative position from global coordinates - setPositionGlobal(global_pos_from_old_region); - - // Diagnostic info - //LLVector3d pos_from_new_region = getPositionGlobal(); - //LL_INFOS() << "pos_from_old_region is " << global_pos_from_old_region - // << " while pos_from_new_region is " << pos_from_new_region - // << LL_ENDL; - - // Update hover height, or schedule callback, based on whether - // it's supported in this region. - if (regionp->simulatorFeaturesReceived()) - { - setHoverIfRegionEnabled(); - } - else - { - regionp->setSimulatorFeaturesReceivedCallback(boost::bind(&LLVOAvatarSelf::onSimulatorFeaturesReceived,this,_1)); - } - } - - if (!regionp || (regionp->getHandle() != mLastRegionHandle)) - { - if (mLastRegionHandle != 0) - { - ++mRegionCrossingCount; - F64Seconds delta(mRegionCrossingTimer.getElapsedTimeF32()); - record(LLStatViewer::REGION_CROSSING_TIME, delta); - - // Diagnostics - LL_INFOS() << "Region crossing took " << (F32)(delta * 1000.0).value() << " ms " << LL_ENDL; - } - if (regionp) - { - mLastRegionHandle = regionp->getHandle(); - } - } - mRegionCrossingTimer.reset(); - LLViewerObject::updateRegion(regionp); -} - -//-------------------------------------------------------------------- -// draw tractor (selection) beam when editing objects -//-------------------------------------------------------------------- -//virtual -void LLVOAvatarSelf::idleUpdateTractorBeam() -{ - // This is only done for yourself (maybe it should be in the agent?) - if (!needsRenderBeam() || !isBuilt()) - { - mBeam = NULL; - } - else if (!mBeam || mBeam->isDead()) - { - // VEFFECT: Tractor Beam - mBeam = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM); - mBeam->setColor(LLColor4U(gAgent.getEffectColor())); - mBeam->setSourceObject(this); - mBeamTimer.reset(); - } - - if (!mBeam.isNull()) - { - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - - if (gAgentCamera.mPointAt.notNull()) - { - // get point from pointat effect - mBeam->setPositionGlobal(gAgentCamera.mPointAt->getPointAtPosGlobal()); - mBeam->triggerLocal(); - } - else if (selection->getFirstRootObject() && - selection->getSelectType() != SELECT_TYPE_HUD) - { - LLViewerObject* objectp = selection->getFirstRootObject(); - mBeam->setTargetObject(objectp); - } - else - { - mBeam->setTargetObject(NULL); - LLTool *tool = LLToolMgr::getInstance()->getCurrentTool(); - if (tool->isEditing()) - { - if (tool->getEditingObject()) - { - mBeam->setTargetObject(tool->getEditingObject()); - } - else - { - mBeam->setPositionGlobal(tool->getEditingPointGlobal()); - } - } - else - { - const LLPickInfo& pick = gViewerWindow->getLastPick(); - mBeam->setPositionGlobal(pick.mPosGlobal); - } - - } - if (mBeamTimer.getElapsedTimeF32() > 0.25f) - { - mBeam->setColor(LLColor4U(gAgent.getEffectColor())); - mBeam->setNeedsSendToSim(true); - mBeamTimer.reset(); - } - } -} - -//----------------------------------------------------------------------------- -// restoreMeshData() -//----------------------------------------------------------------------------- -// virtual -void LLVOAvatarSelf::restoreMeshData() -{ - //LL_INFOS() << "Restoring" << LL_ENDL; - mMeshValid = true; - updateJointLODs(); - updateAttachmentVisibility(gAgentCamera.getCameraMode()); - - // force mesh update as LOD might not have changed to trigger this - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_GEOMETRY); -} - - - -//----------------------------------------------------------------------------- -// updateAttachmentVisibility() -//----------------------------------------------------------------------------- -void LLVOAvatarSelf::updateAttachmentVisibility(U32 camera_mode) -{ - for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - if (attachment->getIsHUDAttachment()) - { - attachment->setAttachmentVisibility(true); - } - else - { - switch (camera_mode) - { - case CAMERA_MODE_MOUSELOOK: - if (LLVOAvatar::sVisibleInFirstPerson && attachment->getVisibleInFirstPerson()) - { - attachment->setAttachmentVisibility(true); - } - else - { - attachment->setAttachmentVisibility(false); - } - break; - default: - attachment->setAttachmentVisibility(true); - break; - } - } - } -} - -//----------------------------------------------------------------------------- -// updatedWearable( LLWearableType::EType type ) -// forces an update to any baked textures relevant to type. -// will force an upload of the resulting bake if the second parameter is true -//----------------------------------------------------------------------------- -void LLVOAvatarSelf::wearableUpdated(LLWearableType::EType type) -{ - for (LLAvatarAppearanceDictionary::BakedTextures::const_iterator baked_iter = sAvatarDictionary->getBakedTextures().begin(); - baked_iter != sAvatarDictionary->getBakedTextures().end(); - ++baked_iter) - { - const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = baked_iter->second; - const LLAvatarAppearanceDefines::EBakedTextureIndex index = baked_iter->first; - - if (baked_dict) - { - for (LLAvatarAppearanceDefines::wearables_vec_t::const_iterator type_iter = baked_dict->mWearables.begin(); - type_iter != baked_dict->mWearables.end(); - ++type_iter) - { - const LLWearableType::EType comp_type = *type_iter; - if (comp_type == type) - { - LLViewerTexLayerSet *layerset = getLayerSet(index); - if (layerset) - { - layerset->setUpdatesEnabled(true); - invalidateComposite(layerset); - } - break; - } - } - } - } -} - -//----------------------------------------------------------------------------- -// isWearingAttachment() -//----------------------------------------------------------------------------- -bool LLVOAvatarSelf::isWearingAttachment(const LLUUID& inv_item_id) const -{ - const LLUUID& base_inv_item_id = gInventory.getLinkedItemID(inv_item_id); - for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - const LLViewerJointAttachment* attachment = iter->second; - if (attachment->getAttachedObject(base_inv_item_id)) - { - return true; - } - } - return false; -} - -//----------------------------------------------------------------------------- -// getWornAttachment() -//----------------------------------------------------------------------------- -LLViewerObject* LLVOAvatarSelf::getWornAttachment(const LLUUID& inv_item_id) -{ - const LLUUID& base_inv_item_id = gInventory.getLinkedItemID(inv_item_id); - for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - if (LLViewerObject *attached_object = attachment->getAttachedObject(base_inv_item_id)) - { - return attached_object; - } - } - return NULL; -} - -bool LLVOAvatarSelf::getAttachedPointName(const LLUUID& inv_item_id, std::string& name) const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; - if (!gInventory.getItem(inv_item_id)) - { - name = "ATTACHMENT_MISSING_ITEM"; - return false; - } - const LLUUID& base_inv_item_id = gInventory.getLinkedItemID(inv_item_id); - if (!gInventory.getItem(base_inv_item_id)) - { - name = "ATTACHMENT_MISSING_BASE_ITEM"; - return false; - } - for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); - iter != mAttachmentPoints.end(); - ++iter) - { - const LLViewerJointAttachment* attachment = iter->second; - if (attachment->getAttachedObject(base_inv_item_id)) - { - name = attachment->getName(); - return true; - } - } - - name = "ATTACHMENT_NOT_ATTACHED"; - return false; -} - -//virtual -const LLViewerJointAttachment *LLVOAvatarSelf::attachObject(LLViewerObject *viewer_object) -{ - const LLViewerJointAttachment *attachment = LLVOAvatar::attachObject(viewer_object); - if (!attachment) - { - return 0; - } - - updateAttachmentVisibility(gAgentCamera.getCameraMode()); - - // Then make sure the inventory is in sync with the avatar. - - // Should just be the last object added - if (attachment->isObjectAttached(viewer_object)) - { - const LLUUID& attachment_id = viewer_object->getAttachmentItemID(); - LLAppearanceMgr::instance().registerAttachment(attachment_id); - updateLODRiggedAttachments(); - } - - return attachment; -} - -//virtual -bool LLVOAvatarSelf::detachObject(LLViewerObject *viewer_object) -{ - const LLUUID attachment_id = viewer_object->getAttachmentItemID(); - if ( LLVOAvatar::detachObject(viewer_object) ) - { - // the simulator should automatically handle permission revocation - - stopMotionFromSource(attachment_id); - LLFollowCamMgr::getInstance()->setCameraActive(viewer_object->getID(), false); - - LLViewerObject::const_child_list_t& child_list = viewer_object->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); - ++iter) - { - LLViewerObject* child_objectp = *iter; - // the simulator should automatically handle - // permissions revocation - - stopMotionFromSource(child_objectp->getID()); - LLFollowCamMgr::getInstance()->setCameraActive(child_objectp->getID(), false); - } - - // Make sure the inventory is in sync with the avatar. - - // Update COF contents, don't trigger appearance update. - if (!isValid()) - { - LL_INFOS() << "removeItemLinks skipped, avatar is under destruction" << LL_ENDL; - } - else - { - LLAppearanceMgr::instance().unregisterAttachment(attachment_id); - } - - return true; - } - return false; -} - -bool LLVOAvatarSelf::hasAttachmentsInTrash() -{ - const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); - - for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); iter != mAttachmentPoints.end(); ++iter) - { - LLViewerJointAttachment *attachment = iter->second; - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - LLViewerObject *attached_object = attachment_iter->get(); - if (attached_object && gInventory.isObjectDescendentOf(attached_object->getAttachmentItemID(), trash_id)) - { - return true; - } - } - } - return false; -} - -// static -bool LLVOAvatarSelf::detachAttachmentIntoInventory(const LLUUID &item_id) -{ - LLInventoryItem* item = gInventory.getItem(item_id); - if (item) - { - gMessageSystem->newMessageFast(_PREHASH_DetachAttachmentIntoInv); - gMessageSystem->nextBlockFast(_PREHASH_ObjectData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - gMessageSystem->addUUIDFast(_PREHASH_ItemID, item_id); - gMessageSystem->sendReliable(gAgent.getRegionHost()); - - // This object might have been selected, so let the selection manager know it's gone now - LLViewerObject *found_obj = gObjectList.findObject(item_id); - if (found_obj) - { - LLSelectMgr::getInstance()->remove(found_obj); - } - - // Error checking in case this object was attached to an invalid point - // In that case, just remove the item from COF preemptively since detach - // will fail. - if (isAgentAvatarValid()) - { - const LLViewerObject *attached_obj = gAgentAvatarp->getWornAttachment(item_id); - if (!attached_obj) - { - LLAppearanceMgr::instance().removeCOFItemLinks(item_id); - } - } - return true; - } - return false; -} - -U32 LLVOAvatarSelf::getNumWearables(LLAvatarAppearanceDefines::ETextureIndex i) const -{ - LLWearableType::EType type = sAvatarDictionary->getTEWearableType(i); - return gAgentWearables.getWearableCount(type); -} - -// virtual -void LLVOAvatarSelf::localTextureLoaded(bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src_raw, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata) -{ - - const LLUUID& src_id = src_vi->getID(); - LLAvatarTexData *data = (LLAvatarTexData *)userdata; - ETextureIndex index = data->mIndex; - if (!isIndexLocalTexture(index)) return; - - LLLocalTextureObject *local_tex_obj = getLocalTextureObject(index, 0); - - // fix for EXT-268. Preventing using of NULL pointer - if(NULL == local_tex_obj) - { - LL_WARNS("TAG") << "There is no Local Texture Object with index: " << index - << ", final: " << final - << LL_ENDL; - return; - } - if (success) - { - if (!local_tex_obj->getBakedReady() && - local_tex_obj->getImage() != NULL && - (local_tex_obj->getID() == src_id) && - discard_level < local_tex_obj->getDiscard()) - { - local_tex_obj->setDiscard(discard_level); - requestLayerSetUpdate(index); - if (isEditingAppearance()) - { - LLVisualParamHint::requestHintUpdates(); - } - updateMeshTextures(); - } - } - else if (final) - { - // Failed: asset is missing - if (!local_tex_obj->getBakedReady() && - local_tex_obj->getImage() != NULL && - local_tex_obj->getImage()->getID() == src_id) - { - local_tex_obj->setDiscard(0); - requestLayerSetUpdate(index); - updateMeshTextures(); - } - } -} - -// virtual -bool LLVOAvatarSelf::getLocalTextureGL(ETextureIndex type, LLViewerTexture** tex_pp, U32 index) const -{ - *tex_pp = NULL; - - if (!isIndexLocalTexture(type)) return false; - if (getLocalTextureID(type, index) == IMG_DEFAULT_AVATAR) return true; - - const LLLocalTextureObject *local_tex_obj = getLocalTextureObject(type, index); - if (!local_tex_obj) - { - return false; - } - *tex_pp = dynamic_cast (local_tex_obj->getImage()); - return true; -} - -LLViewerFetchedTexture* LLVOAvatarSelf::getLocalTextureGL(LLAvatarAppearanceDefines::ETextureIndex type, U32 index) const -{ - if (!isIndexLocalTexture(type)) - { - return NULL; - } - - const LLLocalTextureObject *local_tex_obj = getLocalTextureObject(type, index); - if (!local_tex_obj) - { - return NULL; - } - if (local_tex_obj->getID() == IMG_DEFAULT_AVATAR) - { - return LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT_AVATAR); - } - return dynamic_cast (local_tex_obj->getImage()); -} - -const LLUUID& LLVOAvatarSelf::getLocalTextureID(ETextureIndex type, U32 index) const -{ - if (!isIndexLocalTexture(type)) return IMG_DEFAULT_AVATAR; - - const LLLocalTextureObject *local_tex_obj = getLocalTextureObject(type, index); - if (local_tex_obj && local_tex_obj->getImage() != NULL) - { - return local_tex_obj->getImage()->getID(); - } - return IMG_DEFAULT_AVATAR; -} - - -//----------------------------------------------------------------------------- -// isLocalTextureDataAvailable() -// Returns true if at least the lowest quality discard level exists for every texture -// in the layerset. -//----------------------------------------------------------------------------- -bool LLVOAvatarSelf::isLocalTextureDataAvailable(const LLViewerTexLayerSet* layerset) const -{ - /* if (layerset == mBakedTextureDatas[BAKED_HEAD].mTexLayerSet) - return getLocalDiscardLevel(TEX_HEAD_BODYPAINT) >= 0; */ - for (LLAvatarAppearanceDictionary::BakedTextures::const_iterator baked_iter = sAvatarDictionary->getBakedTextures().begin(); - baked_iter != sAvatarDictionary->getBakedTextures().end(); - ++baked_iter) - { - const EBakedTextureIndex baked_index = baked_iter->first; - if (layerset == mBakedTextureDatas[baked_index].mTexLayerSet) - { - bool ret = true; - const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = baked_iter->second; - for (texture_vec_t::const_iterator local_tex_iter = baked_dict->mLocalTextures.begin(); - local_tex_iter != baked_dict->mLocalTextures.end(); - ++local_tex_iter) - { - const ETextureIndex tex_index = *local_tex_iter; - const LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(tex_index); - const U32 wearable_count = gAgentWearables.getWearableCount(wearable_type); - for (U32 wearable_index = 0; wearable_index < wearable_count; wearable_index++) - { - bool tex_avail = (getLocalDiscardLevel(tex_index, wearable_index) >= 0); - ret &= tex_avail; - } - } - return ret; - } - } - llassert(0); - return false; -} - -//----------------------------------------------------------------------------- -// virtual -// isLocalTextureDataFinal() -// Returns true if the highest quality discard level exists for every texture -// in the layerset. -//----------------------------------------------------------------------------- -bool LLVOAvatarSelf::isLocalTextureDataFinal(const LLViewerTexLayerSet* layerset) const -{ - const U32 desired_tex_discard_level = gSavedSettings.getU32("TextureDiscardLevel"); - // const U32 desired_tex_discard_level = 0; // hack to not bake textures on lower discard levels. - - for (U32 i = 0; i < mBakedTextureDatas.size(); i++) - { - if (layerset == mBakedTextureDatas[i].mTexLayerSet) - { - const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = sAvatarDictionary->getBakedTexture((EBakedTextureIndex)i); - for (texture_vec_t::const_iterator local_tex_iter = baked_dict->mLocalTextures.begin(); - local_tex_iter != baked_dict->mLocalTextures.end(); - ++local_tex_iter) - { - const ETextureIndex tex_index = *local_tex_iter; - const LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(tex_index); - const U32 wearable_count = gAgentWearables.getWearableCount(wearable_type); - for (U32 wearable_index = 0; wearable_index < wearable_count; wearable_index++) - { - S32 local_discard_level = getLocalDiscardLevel(*local_tex_iter, wearable_index); - if ((local_discard_level > (S32)(desired_tex_discard_level)) || - (local_discard_level < 0 )) - { - return false; - } - } - } - return true; - } - } - llassert(0); - return false; -} - - -bool LLVOAvatarSelf::isAllLocalTextureDataFinal() const -{ - const U32 desired_tex_discard_level = gSavedSettings.getU32("TextureDiscardLevel"); - // const U32 desired_tex_discard_level = 0; // hack to not bake textures on lower discard levels - - for (U32 i = 0; i < mBakedTextureDatas.size(); i++) - { - const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = sAvatarDictionary->getBakedTexture((EBakedTextureIndex)i); - for (texture_vec_t::const_iterator local_tex_iter = baked_dict->mLocalTextures.begin(); - local_tex_iter != baked_dict->mLocalTextures.end(); - ++local_tex_iter) - { - const ETextureIndex tex_index = *local_tex_iter; - const LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(tex_index); - const U32 wearable_count = gAgentWearables.getWearableCount(wearable_type); - for (U32 wearable_index = 0; wearable_index < wearable_count; wearable_index++) - { - S32 local_discard_level = getLocalDiscardLevel(*local_tex_iter, wearable_index); - if ((local_discard_level > (S32)(desired_tex_discard_level)) || - (local_discard_level < 0 )) - { - return false; - } - } - } - } - return true; -} - -bool LLVOAvatarSelf::isTextureDefined(LLAvatarAppearanceDefines::ETextureIndex type, U32 index) const -{ - LLUUID id; - bool isDefined = true; - if (isIndexLocalTexture(type)) - { - const LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(type); - const U32 wearable_count = gAgentWearables.getWearableCount(wearable_type); - if (index >= wearable_count) - { - // invalid index passed in. check all textures of a given type - for (U32 wearable_index = 0; wearable_index < wearable_count; wearable_index++) - { - id = getLocalTextureID(type, wearable_index); - isDefined &= (id != IMG_DEFAULT_AVATAR && id != IMG_DEFAULT); - } - } - else - { - id = getLocalTextureID(type, index); - isDefined &= (id != IMG_DEFAULT_AVATAR && id != IMG_DEFAULT); - } - } - else - { - id = getTEImage(type)->getID(); - isDefined &= (id != IMG_DEFAULT_AVATAR && id != IMG_DEFAULT); - } - - return isDefined; -} - -//virtual -bool LLVOAvatarSelf::isTextureVisible(LLAvatarAppearanceDefines::ETextureIndex type, U32 index) const -{ - if (isIndexBakedTexture(type)) - { - return LLVOAvatar::isTextureVisible(type, (U32)0); - } - - LLUUID tex_id = getLocalTextureID(type,index); - return (tex_id != IMG_INVISIBLE) - || (LLDrawPoolAlpha::sShowDebugAlpha); -} - -//virtual -bool LLVOAvatarSelf::isTextureVisible(LLAvatarAppearanceDefines::ETextureIndex type, LLViewerWearable *wearable) const -{ - if (isIndexBakedTexture(type)) - { - return LLVOAvatar::isTextureVisible(type); - } - - U32 index; - if (gAgentWearables.getWearableIndex(wearable,index)) - { - return isTextureVisible(type,index); - } - else - { - LL_WARNS() << "Wearable not found" << LL_ENDL; - return false; - } -} - -bool LLVOAvatarSelf::areTexturesCurrent() const -{ - return gAgentWearables.areWearablesLoaded(); -} - -void LLVOAvatarSelf::invalidateComposite( LLTexLayerSet* layerset) -{ - LLViewerTexLayerSet *layer_set = dynamic_cast(layerset); - if( !layer_set || !layer_set->getUpdatesEnabled() ) - { - return; - } - // LL_INFOS() << "LLVOAvatar::invalidComposite() " << layerset->getBodyRegionName() << LL_ENDL; - - layer_set->requestUpdate(); - layer_set->invalidateMorphMasks(); -} - -void LLVOAvatarSelf::invalidateAll() -{ - for (U32 i = 0; i < mBakedTextureDatas.size(); i++) - { - LLViewerTexLayerSet *layerset = getTexLayerSet(i); - invalidateComposite(layerset); - } - //mDebugSelfLoadTimer.reset(); -} - -//----------------------------------------------------------------------------- -// setCompositeUpdatesEnabled() -//----------------------------------------------------------------------------- -void LLVOAvatarSelf::setCompositeUpdatesEnabled( bool b ) -{ - for (U32 i = 0; i < mBakedTextureDatas.size(); i++) - { - setCompositeUpdatesEnabled(i, b); - } -} - -void LLVOAvatarSelf::setCompositeUpdatesEnabled(U32 index, bool b) -{ - LLViewerTexLayerSet *layerset = getTexLayerSet(index); - if (layerset ) - { - layerset->setUpdatesEnabled( b ); - } -} - -bool LLVOAvatarSelf::isCompositeUpdateEnabled(U32 index) -{ - LLViewerTexLayerSet *layerset = getTexLayerSet(index); - if (layerset) - { - return layerset->getUpdatesEnabled(); - } - return false; -} - -void LLVOAvatarSelf::setupComposites() -{ - for (U32 i = 0; i < mBakedTextureDatas.size(); i++) - { - ETextureIndex tex_index = mBakedTextureDatas[i].mTextureIndex; - bool layer_baked = isTextureDefined(tex_index, gAgentWearables.getWearableCount(tex_index)); - LLViewerTexLayerSet *layerset = getTexLayerSet(i); - if (layerset) - { - layerset->setUpdatesEnabled(!layer_baked); - } - } -} - -void LLVOAvatarSelf::updateComposites() -{ - for (U32 i = 0; i < mBakedTextureDatas.size(); i++) - { - LLViewerTexLayerSet *layerset = getTexLayerSet(i); - if (layerset - && ((i != BAKED_SKIRT) || isWearingWearableType(LLWearableType::WT_SKIRT))) - { - layerset->updateComposite(); - } - } -} - -// virtual -S32 LLVOAvatarSelf::getLocalDiscardLevel(ETextureIndex type, U32 wearable_index) const -{ - if (!isIndexLocalTexture(type)) return false; - - const LLLocalTextureObject *local_tex_obj = getLocalTextureObject(type, wearable_index); - if (local_tex_obj) - { - const LLViewerFetchedTexture* image = dynamic_cast( local_tex_obj->getImage() ); - if (type >= 0 - && local_tex_obj->getID() != IMG_DEFAULT_AVATAR - && !image->isMissingAsset()) - { - return image->getDiscardLevel(); - } - else - { - // We don't care about this (no image associated with the layer) treat as fully loaded. - return 0; - } - } - return 0; -} - -// virtual -// Counts the memory footprint of local textures. -void LLVOAvatarSelf::getLocalTextureByteCount(S32* gl_bytes) const -{ - *gl_bytes = 0; - for (S32 type = 0; type < TEX_NUM_INDICES; type++) - { - if (!isIndexLocalTexture((ETextureIndex)type)) continue; - U32 max_tex = getNumWearables((ETextureIndex) type); - for (U32 num = 0; num < max_tex; num++) - { - const LLLocalTextureObject *local_tex_obj = getLocalTextureObject((ETextureIndex) type, num); - if (local_tex_obj) - { - const LLViewerFetchedTexture* image_gl = dynamic_cast( local_tex_obj->getImage() ); - if (image_gl) - { - S32 bytes = (S32)image_gl->getWidth() * image_gl->getHeight() * image_gl->getComponents(); - - if (image_gl->hasGLTexture()) - { - *gl_bytes += bytes; - } - } - } - } - } -} - -// virtual -void LLVOAvatarSelf::setLocalTexture(ETextureIndex type, LLViewerTexture* src_tex, bool baked_version_ready, U32 index) -{ - if (!isIndexLocalTexture(type)) return; - - LLViewerFetchedTexture* tex = LLViewerTextureManager::staticCastToFetchedTexture(src_tex, true) ; - if(!tex) - { - return ; - } - - S32 desired_discard = isSelf() ? 0 : 2; - LLLocalTextureObject *local_tex_obj = getLocalTextureObject(type,index); - if (!local_tex_obj) - { - if (type >= TEX_NUM_INDICES) - { - LL_ERRS() << "Tried to set local texture with invalid type: (" << (U32) type << ", " << index << ")" << LL_ENDL; - return; - } - LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(type); - if (!gAgentWearables.getViewerWearable(wearable_type,index)) - { - // no wearable is loaded, cannot set the texture. - return; - } - gAgentWearables.addLocalTextureObject(wearable_type,type,index); - local_tex_obj = getLocalTextureObject(type,index); - if (!local_tex_obj) - { - LL_ERRS() << "Unable to create LocalTextureObject for wearable type & index: (" << (U32) wearable_type << ", " << index << ")" << LL_ENDL; - return; - } - - LLViewerTexLayerSet *layer_set = getLayerSet(type); - if (layer_set) - { - layer_set->cloneTemplates(local_tex_obj, type, gAgentWearables.getViewerWearable(wearable_type,index)); - } - - } - if (!baked_version_ready) - { - if (tex != local_tex_obj->getImage() || local_tex_obj->getBakedReady()) - { - local_tex_obj->setDiscard(MAX_DISCARD_LEVEL+1); - } - if (tex->getID() != IMG_DEFAULT_AVATAR) - { - if (local_tex_obj->getDiscard() > desired_discard) - { - S32 tex_discard = tex->getDiscardLevel(); - if (tex_discard >= 0 && tex_discard <= desired_discard) - { - local_tex_obj->setDiscard(tex_discard); - if (isSelf()) - { - requestLayerSetUpdate(type); - if (isEditingAppearance()) - { - LLVisualParamHint::requestHintUpdates(); - } - } - } - else - { - tex->setLoadedCallback(onLocalTextureLoaded, desired_discard, true, false, new LLAvatarTexData(getID(), type), NULL); - } - } - tex->setMinDiscardLevel(desired_discard); - } - } - local_tex_obj->setImage(tex); - local_tex_obj->setID(tex->getID()); - setBakedReady(type,baked_version_ready,index); -} - -//virtual -void LLVOAvatarSelf::setBakedReady(LLAvatarAppearanceDefines::ETextureIndex type, bool baked_version_exists, U32 index) -{ - if (!isIndexLocalTexture(type)) return; - LLLocalTextureObject *local_tex_obj = getLocalTextureObject(type,index); - if (local_tex_obj) - { - local_tex_obj->setBakedReady( baked_version_exists ); - } -} - - -// virtual -void LLVOAvatarSelf::dumpLocalTextures() const -{ - LL_INFOS() << "Local Textures:" << LL_ENDL; - - /* ETextureIndex baked_equiv[] = { - TEX_UPPER_BAKED, - if (isTextureDefined(baked_equiv[i])) */ - for (LLAvatarAppearanceDictionary::Textures::const_iterator iter = sAvatarDictionary->getTextures().begin(); - iter != sAvatarDictionary->getTextures().end(); - ++iter) - { - const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = iter->second; - if (!texture_dict->mIsLocalTexture || !texture_dict->mIsUsedByBakedTexture) - continue; - - const EBakedTextureIndex baked_index = texture_dict->mBakedTextureIndex; - const ETextureIndex baked_equiv = sAvatarDictionary->getBakedTexture(baked_index)->mTextureIndex; - - const std::string &name = texture_dict->mName; - const LLLocalTextureObject *local_tex_obj = getLocalTextureObject(iter->first, 0); - // index is baked texture - index is not relevant. putting in 0 as placeholder - if (isTextureDefined(baked_equiv, 0)) - { -#if LL_RELEASE_FOR_DOWNLOAD - // End users don't get to trivially see avatar texture IDs, makes textures - // easier to steal. JC - LL_INFOS() << "LocTex " << name << ": Baked " << LL_ENDL; -#else - LL_INFOS() << "LocTex " << name << ": Baked " << getTEImage(baked_equiv)->getID() << LL_ENDL; -#endif - } - else if (local_tex_obj && local_tex_obj->getImage() != NULL) - { - if (local_tex_obj->getImage()->getID() == IMG_DEFAULT_AVATAR) - { - LL_INFOS() << "LocTex " << name << ": None" << LL_ENDL; - } - else - { - LLViewerFetchedTexture* image = dynamic_cast( local_tex_obj->getImage() ); - - LL_INFOS() << "LocTex " << name << ": " - << "Discard " << image->getDiscardLevel() << ", " - << "(" << image->getWidth() << ", " << image->getHeight() << ") " -#if !LL_RELEASE_FOR_DOWNLOAD - // End users don't get to trivially see avatar texture IDs, - // makes textures easier to steal - << image->getID() << " " -#endif - << "Priority: " << image->getMaxVirtualSize() - << LL_ENDL; - } - } - else - { - LL_INFOS() << "LocTex " << name << ": No LLViewerTexture" << LL_ENDL; - } - } -} - -//----------------------------------------------------------------------------- -// static -// onLocalTextureLoaded() -//----------------------------------------------------------------------------- - -void LLVOAvatarSelf::onLocalTextureLoaded(bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src_raw, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata) -{ - LLAvatarTexData *data = (LLAvatarTexData *)userdata; - LLVOAvatarSelf *self = (LLVOAvatarSelf *)gObjectList.findObject(data->mAvatarID); - if (self) - { - // We should only be handling local textures for ourself - self->localTextureLoaded(success, src_vi, src_raw, aux_src, discard_level, final, userdata); - } - // ensure data is cleaned up - if (final || !success) - { - delete data; - } -} - -/*virtual*/ void LLVOAvatarSelf::setImage(const U8 te, LLViewerTexture *imagep, const U32 index) -{ - if (isIndexLocalTexture((ETextureIndex)te)) - { - setLocalTexture((ETextureIndex)te, imagep, false ,index); - } - else - { - setTEImage(te,imagep); - } -} - -/*virtual*/ LLViewerTexture* LLVOAvatarSelf::getImage(const U8 te, const U32 index) const -{ - if (isIndexLocalTexture((ETextureIndex)te)) - { - return getLocalTextureGL((ETextureIndex)te,index); - } - else - { - return getTEImage(te); - } -} - - -// static -void LLVOAvatarSelf::dumpTotalLocalTextureByteCount() -{ - S32 gl_bytes = 0; - gAgentAvatarp->getLocalTextureByteCount(&gl_bytes); - LL_INFOS() << "Total Avatar LocTex GL:" << (gl_bytes/1024) << "KB" << LL_ENDL; -} - -bool LLVOAvatarSelf::getIsCloud() const -{ - // Let people know why they're clouded without spamming them into oblivion. - bool do_warn = false; - static LLTimer time_since_notice; - F32 update_freq = 30.0; - if (time_since_notice.getElapsedTimeF32() > update_freq) - { - time_since_notice.reset(); - do_warn = true; - } - - // do we have our body parts? - S32 shape_count = gAgentWearables.getWearableCount(LLWearableType::WT_SHAPE); - S32 hair_count = gAgentWearables.getWearableCount(LLWearableType::WT_HAIR); - S32 eye_count = gAgentWearables.getWearableCount(LLWearableType::WT_EYES); - S32 skin_count = gAgentWearables.getWearableCount(LLWearableType::WT_SKIN); - if (!shape_count || !hair_count || !eye_count || !skin_count) - { - if (do_warn) - { - LL_INFOS() << "Self is clouded due to missing one or more required body parts: " - << (shape_count ? "" : "SHAPE ") - << (hair_count ? "" : "HAIR ") - << (eye_count ? "" : "EYES ") - << (skin_count ? "" : "SKIN ") - << LL_ENDL; - } - return true; - } - - if (!isTextureDefined(TEX_HAIR, 0)) - { - if (do_warn) - { - LL_INFOS() << "Self is clouded because of no hair texture" << LL_ENDL; - } - return true; - } - - if (!mPreviousFullyLoaded) - { - if (!isLocalTextureDataAvailable(getLayerSet(BAKED_LOWER)) && - (!isTextureDefined(TEX_LOWER_BAKED, 0))) - { - if (do_warn) - { - LL_INFOS() << "Self is clouded because lower textures not baked" << LL_ENDL; - } - return true; - } - - if (!isLocalTextureDataAvailable(getLayerSet(BAKED_UPPER)) && - (!isTextureDefined(TEX_UPPER_BAKED, 0))) - { - if (do_warn) - { - LL_INFOS() << "Self is clouded because upper textures not baked" << LL_ENDL; - } - return true; - } - - for (U32 i = 0; i < mBakedTextureDatas.size(); i++) - { - if (i == BAKED_SKIRT && !isWearingWearableType(LLWearableType::WT_SKIRT)) - continue; - - const BakedTextureData& texture_data = mBakedTextureDatas[i]; - if (!isTextureDefined(texture_data.mTextureIndex, 0)) - continue; - - // Check for the case that texture is defined but not sufficiently loaded to display anything. - const LLViewerTexture* baked_img = getImage( texture_data.mTextureIndex, 0 ); - if (!baked_img || !baked_img->hasGLTexture()) - { - if (do_warn) - { - LL_INFOS() << "Self is clouded because texture at index " << i - << " (texture index is " << texture_data.mTextureIndex << ") is not loaded" << LL_ENDL; - } - return true; - } - } - - LL_DEBUGS() << "Avatar de-clouded" << LL_ENDL; - } - return false; -} - -/*static*/ -void LLVOAvatarSelf::debugOnTimingLocalTexLoaded(bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata) -{ - if (gAgentAvatarp.notNull()) - { - gAgentAvatarp->debugTimingLocalTexLoaded(success, src_vi, src, aux_src, discard_level, final, userdata); - } -} - -void LLVOAvatarSelf::debugTimingLocalTexLoaded(bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata) -{ - LLAvatarTexData *data = (LLAvatarTexData *)userdata; - if (!data) - { - return; - } - - ETextureIndex index = data->mIndex; - - if (index < 0 || index >= TEX_NUM_INDICES) - { - return; - } - - if (discard_level >=0 && discard_level <= MAX_DISCARD_LEVEL) // ignore discard level -1, as it means we have no data. - { - mDebugTextureLoadTimes[(U32)index][(U32)discard_level] = mDebugSelfLoadTimer.getElapsedTimeF32(); - } - if (final) - { - delete data; - } -} - -void LLVOAvatarSelf::debugBakedTextureUpload(EBakedTextureIndex index, bool finished) -{ - U32 done = 0; - if (finished) - { - done = 1; - } - mDebugBakedTextureTimes[index][done] = mDebugSelfLoadTimer.getElapsedTimeF32(); -} - -const std::string LLVOAvatarSelf::verboseDebugDumpLocalTextureDataInfo(const LLViewerTexLayerSet* layerset) const -{ - std::ostringstream outbuf; - LLWearableType *wr_inst = LLWearableType::getInstance(); - for (LLAvatarAppearanceDictionary::BakedTextures::const_iterator baked_iter = - sAvatarDictionary->getBakedTextures().begin(); - baked_iter != sAvatarDictionary->getBakedTextures().end(); - ++baked_iter) - { - const EBakedTextureIndex baked_index = baked_iter->first; - if (layerset == mBakedTextureDatas[baked_index].mTexLayerSet) - { - outbuf << "baked_index: " << baked_index << "\n"; - const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = baked_iter->second; - for (texture_vec_t::const_iterator local_tex_iter = baked_dict->mLocalTextures.begin(); - local_tex_iter != baked_dict->mLocalTextures.end(); - ++local_tex_iter) - { - const ETextureIndex tex_index = *local_tex_iter; - const std::string tex_name = sAvatarDictionary->getTexture(tex_index)->mName; - outbuf << " tex_index " << (S32) tex_index << " name " << tex_name << "\n"; - const LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(tex_index); - const U32 wearable_count = gAgentWearables.getWearableCount(wearable_type); - if (wearable_count > 0) - { - for (U32 wearable_index = 0; wearable_index < wearable_count; wearable_index++) - { - outbuf << " " << wr_inst->getTypeName(wearable_type) << " " << wearable_index << ":"; - const LLLocalTextureObject *local_tex_obj = getLocalTextureObject(tex_index, wearable_index); - if (local_tex_obj) - { - LLViewerFetchedTexture* image = dynamic_cast( local_tex_obj->getImage() ); - if (tex_index >= 0 - && local_tex_obj->getID() != IMG_DEFAULT_AVATAR - && !image->isMissingAsset()) - { - outbuf << " id: " << image->getID() - << " refs: " << image->getNumRefs() - << " glocdisc: " << getLocalDiscardLevel(tex_index, wearable_index) - << " discard: " << image->getDiscardLevel() - << " desired: " << image->getDesiredDiscardLevel() - << " vsize: " << image->getMaxVirtualSize() - << " ts: " << image->getTextureState() - << " bl: " << image->getBoostLevel() - << " fl: " << image->isFullyLoaded() // this is not an accessor for mFullyLoaded - see comment there. - << " cl: " << (image->isFullyLoaded() && image->getDiscardLevel()==0) // "completely loaded" - << " mvs: " << image->getMaxVirtualSize() - << " mvsc: " << image->getMaxVirtualSizeResetCounter() - << " mem: " << image->getTextureMemory(); - } - } - outbuf << "\n"; - } - } - } - break; - } - } - return outbuf.str(); -} - -void LLVOAvatarSelf::dumpAllTextures() const -{ - std::string vd_text = "Local textures per baked index and wearable:\n"; - for (LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::BakedTextures::const_iterator baked_iter = sAvatarDictionary->getBakedTextures().begin(); - baked_iter != sAvatarDictionary->getBakedTextures().end(); - ++baked_iter) - { - const LLAvatarAppearanceDefines::EBakedTextureIndex baked_index = baked_iter->first; - const LLViewerTexLayerSet *layerset = debugGetLayerSet(baked_index); - if (!layerset) continue; - const LLViewerTexLayerSetBuffer *layerset_buffer = layerset->getViewerComposite(); - if (!layerset_buffer) continue; - vd_text += verboseDebugDumpLocalTextureDataInfo(layerset); - } - LL_DEBUGS("Avatar") << vd_text << LL_ENDL; -} - -const std::string LLVOAvatarSelf::debugDumpLocalTextureDataInfo(const LLViewerTexLayerSet* layerset) const -{ - std::string text=""; - LLWearableType *wr_inst = LLWearableType::getInstance(); - - text = llformat("[Final:%d Avail:%d] ",isLocalTextureDataFinal(layerset), isLocalTextureDataAvailable(layerset)); - - /* if (layerset == mBakedTextureDatas[BAKED_HEAD].mTexLayerSet) - return getLocalDiscardLevel(TEX_HEAD_BODYPAINT) >= 0; */ - for (LLAvatarAppearanceDictionary::BakedTextures::const_iterator baked_iter = sAvatarDictionary->getBakedTextures().begin(); - baked_iter != sAvatarDictionary->getBakedTextures().end(); - ++baked_iter) - { - const EBakedTextureIndex baked_index = baked_iter->first; - if (layerset == mBakedTextureDatas[baked_index].mTexLayerSet) - { - const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = baked_iter->second; - text += llformat("%d-%s ( ",baked_index, baked_dict->mName.c_str()); - for (texture_vec_t::const_iterator local_tex_iter = baked_dict->mLocalTextures.begin(); - local_tex_iter != baked_dict->mLocalTextures.end(); - ++local_tex_iter) - { - const ETextureIndex tex_index = *local_tex_iter; - const LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(tex_index); - const U32 wearable_count = gAgentWearables.getWearableCount(wearable_type); - if (wearable_count > 0) - { - text += wr_inst->getTypeName(wearable_type) + ":"; - for (U32 wearable_index = 0; wearable_index < wearable_count; wearable_index++) - { - const U32 discard_level = getLocalDiscardLevel(tex_index, wearable_index); - std::string discard_str = llformat("%d ",discard_level); - text += llformat("%d ",discard_level); - } - } - } - text += ")"; - break; - } - } - return text; -} - -const std::string LLVOAvatarSelf::debugDumpAllLocalTextureDataInfo() const -{ - std::string text; - const U32 override_tex_discard_level = gSavedSettings.getU32("TextureDiscardLevel"); - - for (U32 i = 0; i < mBakedTextureDatas.size(); i++) - { - const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = sAvatarDictionary->getBakedTexture((EBakedTextureIndex)i); - bool is_texture_final = true; - for (texture_vec_t::const_iterator local_tex_iter = baked_dict->mLocalTextures.begin(); - local_tex_iter != baked_dict->mLocalTextures.end(); - ++local_tex_iter) - { - const ETextureIndex tex_index = *local_tex_iter; - const LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(tex_index); - const U32 wearable_count = gAgentWearables.getWearableCount(wearable_type); - for (U32 wearable_index = 0; wearable_index < wearable_count; wearable_index++) - { - is_texture_final &= (getLocalDiscardLevel(*local_tex_iter, wearable_index) <= (S32)(override_tex_discard_level)); - } - } - text += llformat("%s:%d ",baked_dict->mName.c_str(),is_texture_final); - } - return text; -} - -void LLVOAvatarSelf::appearanceChangeMetricsCoro(std::string url) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("appearanceChangeMetrics", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); - - S32 currentSequence = mMetricSequence; - if (S32_MAX == ++mMetricSequence) - mMetricSequence = 0; - - LLSD msg; - msg["message"] = "ViewerAppearanceChangeMetrics"; - msg["session_id"] = gAgentSessionID; - msg["agent_id"] = gAgentID; - msg["sequence"] = currentSequence; - msg["initial"] = mInitialMetric; - msg["break"] = false; - msg["duration"] = mTimeSinceLastRezMessage.getElapsedTimeF32(); - - // Status of our own rezzing. - msg["rez_status"] = LLVOAvatar::rezStatusToString(getRezzedStatus()); - msg["first_decloud_time"] = getFirstDecloudTime(); - - // Status of all nearby avs including ourself. - msg["nearby"] = LLSD::emptyArray(); - std::vector rez_counts; - F32 avg_time; - S32 total_cloud_avatars; - LLVOAvatar::getNearbyRezzedStats(rez_counts, avg_time, total_cloud_avatars); - for (S32 rez_stat = 0; rez_stat < rez_counts.size(); ++rez_stat) - { - std::string rez_status_name = LLVOAvatar::rezStatusToString(rez_stat); - msg["nearby"][rez_status_name] = rez_counts[rez_stat]; - } - msg["nearby"]["avg_decloud_time"] = avg_time; - msg["nearby"]["cloud_total"] = total_cloud_avatars; - - // std::vector bucket_fields("timer_name","is_self","grid_x","grid_y","is_using_server_bake"); - std::vector by_fields; - by_fields.push_back("timer_name"); - by_fields.push_back("completed"); - by_fields.push_back("grid_x"); - by_fields.push_back("grid_y"); - by_fields.push_back("is_using_server_bakes"); - by_fields.push_back("is_self"); - by_fields.push_back("central_bake_version"); - LLSD summary = summarize_by_buckets(mPendingTimerRecords, by_fields, std::string("elapsed")); - msg["timers"] = summary; - - mPendingTimerRecords.clear(); - - LL_DEBUGS("Avatar") << avString() << "message: " << ll_pretty_print_sd(msg) << LL_ENDL; - - gPendingMetricsUploads++; - - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, msg); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - gPendingMetricsUploads--; - - if (!status) - { - LL_WARNS("Avatar") << "Unable to upload statistics" << LL_ENDL; - return; - } - else - { - LL_INFOS("Avatar") << "Statistics upload OK" << LL_ENDL; - mInitialMetric = false; - } -} - -bool LLVOAvatarSelf::updateAvatarRezMetrics(bool force_send) -{ - const F32 AV_METRICS_INTERVAL_QA = 30.0; - F32 send_period = 300.0; - - static LLCachedControl qa_mode_metrics(gSavedSettings,"QAModeMetrics"); - if (qa_mode_metrics) - { - send_period = AV_METRICS_INTERVAL_QA; - } - - if (force_send || mTimeSinceLastRezMessage.getElapsedTimeF32() > send_period) - { - // Stats for completed phases have been getting logged as they - // complete. This will give us stats for any timers that - // haven't finished as of the metric's being sent. - - if (force_send) - { - LLVOAvatar::logPendingPhasesAllAvatars(); - } - sendViewerAppearanceChangeMetrics(); - } - - return false; -} - -void LLVOAvatarSelf::addMetricsTimerRecord(const LLSD& record) -{ - mPendingTimerRecords.push_back(record); -} - -bool operator<(const LLSD& a, const LLSD& b) -{ - std::ostringstream aout, bout; - aout << LLSDNotationStreamer(a); - bout << LLSDNotationStreamer(b); - std::string astring = aout.str(); - std::string bstring = bout.str(); - - return astring < bstring; - -} - -// Given a vector of LLSD records, return an LLSD array of bucketed stats for val_field. -LLSD summarize_by_buckets(std::vector in_records, - std::vector by_fields, - std::string val_field) -{ - LLSD result = LLSD::emptyArray(); - std::map accum; - for (std::vector::iterator in_record_iter = in_records.begin(); - in_record_iter != in_records.end(); ++in_record_iter) - { - LLSD& record = *in_record_iter; - LLSD key; - for (std::vector::iterator field_iter = by_fields.begin(); - field_iter != by_fields.end(); ++field_iter) - { - const std::string& field = *field_iter; - key[field] = record[field]; - } - LLViewerStats::StatsAccumulator& stats = accum[key]; - F32 value = record[val_field].asReal(); - stats.push(value); - } - for (std::map::iterator accum_it = accum.begin(); - accum_it != accum.end(); ++accum_it) - { - LLSD out_record = accum_it->first; - out_record["stats"] = accum_it->second.asLLSD(); - result.append(out_record); - } - return result; -} - -void LLVOAvatarSelf::sendViewerAppearanceChangeMetrics() -{ - std::string caps_url; - if (getRegion()) - { - // runway - change here to activate. - caps_url = getRegion()->getCapability("ViewerMetrics"); - } - if (!caps_url.empty()) - { - - LLCoros::instance().launch("LLVOAvatarSelf::appearanceChangeMetricsCoro", - boost::bind(&LLVOAvatarSelf::appearanceChangeMetricsCoro, this, caps_url)); - mTimeSinceLastRezMessage.reset(); - } -} - -const LLUUID& LLVOAvatarSelf::grabBakedTexture(EBakedTextureIndex baked_index) const -{ - if (canGrabBakedTexture(baked_index)) - { - ETextureIndex tex_index = sAvatarDictionary->bakedToLocalTextureIndex(baked_index); - if (tex_index == TEX_NUM_INDICES) - { - return LLUUID::null; - } - return getTEImage( tex_index )->getID(); - } - return LLUUID::null; -} - -bool LLVOAvatarSelf::canGrabBakedTexture(EBakedTextureIndex baked_index) const -{ - ETextureIndex tex_index = sAvatarDictionary->bakedToLocalTextureIndex(baked_index); - if (tex_index == TEX_NUM_INDICES) - { - return false; - } - // Check if the texture hasn't been baked yet. - if (!isTextureDefined(tex_index, 0)) - { - LL_DEBUGS() << "getTEImage( " << (U32) tex_index << " )->getID() == IMG_DEFAULT_AVATAR" << LL_ENDL; - return false; - } - - if (gAgent.isGodlikeWithoutAdminMenuFakery()) - return true; - - // Check permissions of textures that show up in the - // baked texture. We don't want people copying people's - // work via baked textures. - - const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = sAvatarDictionary->getBakedTexture(baked_index); - for (texture_vec_t::const_iterator iter = baked_dict->mLocalTextures.begin(); - iter != baked_dict->mLocalTextures.end(); - ++iter) - { - const ETextureIndex t_index = (*iter); - LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(t_index); - U32 count = gAgentWearables.getWearableCount(wearable_type); - LL_DEBUGS() << "Checking index " << (U32) t_index << " count: " << count << LL_ENDL; - - for (U32 wearable_index = 0; wearable_index < count; ++wearable_index) - { - LLViewerWearable *wearable = gAgentWearables.getViewerWearable(wearable_type, wearable_index); - if (wearable) - { - const LLLocalTextureObject *texture = wearable->getLocalTextureObject((S32)t_index); - const LLUUID& texture_id = texture->getID(); - if (texture_id != IMG_DEFAULT_AVATAR) - { - // Search inventory for this texture. - LLViewerInventoryCategory::cat_array_t cats; - LLViewerInventoryItem::item_array_t items; - LLAssetIDMatches asset_id_matches(texture_id); - gInventory.collectDescendentsIf(LLUUID::null, - cats, - items, - LLInventoryModel::INCLUDE_TRASH, - asset_id_matches); - - bool can_grab = false; - LL_DEBUGS() << "item count for asset " << texture_id << ": " << items.size() << LL_ENDL; - if (items.size()) - { - // search for full permissions version - for (S32 i = 0; i < items.size(); i++) - { - LLViewerInventoryItem* itemp = items[i]; - if (itemp->getIsFullPerm()) - { - can_grab = true; - break; - } - } - } - if (!can_grab) return false; - } - } - } - } - - return true; -} - -void LLVOAvatarSelf::addLocalTextureStats( ETextureIndex type, LLViewerFetchedTexture* imagep, - F32 texel_area_ratio, bool render_avatar, bool covered_by_baked) -{ - if (!isIndexLocalTexture(type)) return; - - // Sunshine - ignoring covered_by_baked will force local textures - // to always load. Fix for SH-4001 and many related issues. Do - // not restore this without some more targetted fix for the local - // textures failing to load issue. - //if (!covered_by_baked) - { - if (imagep->getID() != IMG_DEFAULT_AVATAR) - { - imagep->setNoDelete(); - if (imagep->getDiscardLevel() != 0) - { - F32 desired_pixels; - desired_pixels = llmin(mPixelArea, (F32)getTexImageArea()); - - imagep->setBoostLevel(getAvatarBoostLevel()); - imagep->resetTextureStats(); - imagep->setMaxVirtualSizeResetInterval(MAX_TEXTURE_VIRTUAL_SIZE_RESET_INTERVAL); - imagep->addTextureStats( desired_pixels / texel_area_ratio ); - imagep->forceUpdateBindStats() ; - if (imagep->getDiscardLevel() < 0) - { - mHasGrey = true; // for statistics gathering - } - } - } - else - { - // texture asset is missing - mHasGrey = true; // for statistics gathering - } - } -} - -LLLocalTextureObject* LLVOAvatarSelf::getLocalTextureObject(LLAvatarAppearanceDefines::ETextureIndex i, U32 wearable_index) const -{ - LLWearableType::EType type = sAvatarDictionary->getTEWearableType(i); - LLViewerWearable* wearable = gAgentWearables.getViewerWearable(type, wearable_index); - if (wearable) - { - return wearable->getLocalTextureObject(i); - } - - return NULL; -} - -//----------------------------------------------------------------------------- -// getBakedTE() -// Used by the LayerSet. (Layer sets don't in general know what textures depend on them.) -//----------------------------------------------------------------------------- -ETextureIndex LLVOAvatarSelf::getBakedTE( const LLViewerTexLayerSet* layerset ) const -{ - for (U32 i = 0; i < mBakedTextureDatas.size(); i++) - { - if (layerset == mBakedTextureDatas[i].mTexLayerSet ) - { - return mBakedTextureDatas[i].mTextureIndex; - } - } - llassert(0); - return TEX_HEAD_BAKED; -} - -// FIXME: This is not called consistently. Something may be broken. -void LLVOAvatarSelf::outputRezDiagnostics() const -{ - if(!gSavedSettings.getBOOL("DebugAvatarLocalTexLoadedTime")) - { - return ; - } - - const F32 final_time = mDebugSelfLoadTimer.getElapsedTimeF32(); - LL_DEBUGS("Avatar") << "REZTIME: Myself rez stats:" << LL_ENDL; - LL_DEBUGS("Avatar") << "\t Time from avatar creation to load wearables: " << (S32)mDebugTimeWearablesLoaded << LL_ENDL; - LL_DEBUGS("Avatar") << "\t Time from avatar creation to de-cloud: " << (S32)mDebugTimeAvatarVisible << LL_ENDL; - LL_DEBUGS("Avatar") << "\t Time from avatar creation to de-cloud for others: " << (S32)final_time << LL_ENDL; - LL_DEBUGS("Avatar") << "\t Load time for each texture: " << LL_ENDL; - for (U32 i = 0; i < LLAvatarAppearanceDefines::TEX_NUM_INDICES; ++i) - { - std::stringstream out; - out << "\t\t (" << i << ") "; - U32 j=0; - for (j=0; j <= MAX_DISCARD_LEVEL; j++) - { - out << "\t"; - S32 load_time = (S32)mDebugTextureLoadTimes[i][j]; - if (load_time == -1) - { - out << "*"; - if (j == 0) - break; - } - else - { - out << load_time; - } - } - - // Don't print out non-existent textures. - if (j != 0) - { - LL_DEBUGS("Avatar") << out.str() << LL_ENDL; - } - } - LL_DEBUGS("Avatar") << "\t Time points for each upload (start / finish)" << LL_ENDL; - for (U32 i = 0; i < LLAvatarAppearanceDefines::BAKED_NUM_INDICES; ++i) - { - LL_DEBUGS("Avatar") << "\t\t (" << i << ") \t" << (S32)mDebugBakedTextureTimes[i][0] << " / " << (S32)mDebugBakedTextureTimes[i][1] << LL_ENDL; - } - - for (LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::BakedTextures::const_iterator baked_iter = sAvatarDictionary->getBakedTextures().begin(); - baked_iter != sAvatarDictionary->getBakedTextures().end(); - ++baked_iter) - { - const LLAvatarAppearanceDefines::EBakedTextureIndex baked_index = baked_iter->first; - const LLViewerTexLayerSet *layerset = debugGetLayerSet(baked_index); - if (!layerset) continue; - const LLViewerTexLayerSetBuffer *layerset_buffer = layerset->getViewerComposite(); - if (!layerset_buffer) continue; - LL_DEBUGS("Avatar") << layerset_buffer->dumpTextureInfo() << LL_ENDL; - } - - dumpAllTextures(); -} - -void LLVOAvatarSelf::outputRezTiming(const std::string& msg) const -{ - LL_DEBUGS("Avatar") - << avString() - << llformat("%s. Time from avatar creation: %.2f", msg.c_str(), mDebugSelfLoadTimer.getElapsedTimeF32()) - << LL_ENDL; -} - -void LLVOAvatarSelf::reportAvatarRezTime() const -{ - // TODO: report mDebugSelfLoadTimer.getElapsedTimeF32() somehow. -} - -// SUNSHINE CLEANUP - not clear we need any of this, may be sufficient to request server appearance in llviewermenu.cpp:handle_rebake_textures() -void LLVOAvatarSelf::forceBakeAllTextures(bool slam_for_debug) -{ - LL_INFOS() << "TAT: forced full rebake. " << LL_ENDL; - - for (U32 i = 0; i < mBakedTextureDatas.size(); i++) - { - ETextureIndex baked_index = mBakedTextureDatas[i].mTextureIndex; - LLViewerTexLayerSet* layer_set = getLayerSet(baked_index); - if (layer_set) - { - if (slam_for_debug) - { - layer_set->setUpdatesEnabled(true); - } - - invalidateComposite(layer_set); - add(LLStatViewer::TEX_REBAKES, 1); - } - else - { - LL_WARNS() << "TAT: NO LAYER SET FOR " << (S32)baked_index << LL_ENDL; - } - } - - // Don't know if this is needed - updateMeshTextures(); -} - -//----------------------------------------------------------------------------- -// requestLayerSetUpdate() -//----------------------------------------------------------------------------- -void LLVOAvatarSelf::requestLayerSetUpdate(ETextureIndex index ) -{ - /* switch(index) - case LOCTEX_UPPER_BODYPAINT: - case LOCTEX_UPPER_SHIRT: - if( mUpperBodyLayerSet ) - mUpperBodyLayerSet->requestUpdate(); */ - const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = sAvatarDictionary->getTexture(index); - if (!texture_dict) - return; - if (!texture_dict->mIsLocalTexture || !texture_dict->mIsUsedByBakedTexture) - return; - const EBakedTextureIndex baked_index = texture_dict->mBakedTextureIndex; - if (mBakedTextureDatas[baked_index].mTexLayerSet) - { - mBakedTextureDatas[baked_index].mTexLayerSet->requestUpdate(); - } -} - -LLViewerTexLayerSet* LLVOAvatarSelf::getLayerSet(ETextureIndex index) const -{ - /* switch(index) - case TEX_HEAD_BAKED: - case TEX_HEAD_BODYPAINT: - return mHeadLayerSet; */ - const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = sAvatarDictionary->getTexture(index); - if (texture_dict && texture_dict->mIsUsedByBakedTexture) - { - const EBakedTextureIndex baked_index = texture_dict->mBakedTextureIndex; - return getLayerSet(baked_index); - } - return NULL; -} - -LLViewerTexLayerSet* LLVOAvatarSelf::getLayerSet(EBakedTextureIndex baked_index) const -{ - /* switch(index) - case TEX_HEAD_BAKED: - case TEX_HEAD_BODYPAINT: - return mHeadLayerSet; */ - if (baked_index >= 0 && baked_index < BAKED_NUM_INDICES) - { - return getTexLayerSet(baked_index); - } - return NULL; -} - - - - -// static -void LLVOAvatarSelf::onCustomizeStart(bool disable_camera_switch) -{ - if (isAgentAvatarValid()) - { - LLUIUsage::instance().logCommand("Avatar.CustomizeStart"); - if (!gAgentAvatarp->mEndCustomizeCallback.get()) - { - gAgentAvatarp->mEndCustomizeCallback = new LLUpdateAppearanceOnDestroy; - } - - gAgentAvatarp->mIsEditingAppearance = true; - gAgentAvatarp->mUseLocalAppearance = true; - - if (gSavedSettings.getBOOL("AppearanceCameraMovement") && !disable_camera_switch) - { - gAgentCamera.changeCameraToCustomizeAvatar(); - } - - gAgentAvatarp->invalidateAll(); // mark all bakes as dirty, request updates - gAgentAvatarp->updateMeshTextures(); // make sure correct textures are applied to the avatar mesh. - gAgentAvatarp->updateTextures(); // call updateTextureStats - } -} - -// static -void LLVOAvatarSelf::onCustomizeEnd(bool disable_camera_switch) -{ - - if (isAgentAvatarValid()) - { - gAgentAvatarp->mIsEditingAppearance = false; - gAgentAvatarp->invalidateAll(); - - if (gSavedSettings.getBOOL("AppearanceCameraMovement") && !disable_camera_switch) - { - gAgentCamera.changeCameraToDefault(); - gAgentCamera.resetView(); - } - - // Dereferencing the previous callback will cause - // updateAppearanceFromCOF to be called, whenever all refs - // have resolved. - gAgentAvatarp->mEndCustomizeCallback = NULL; - } -} - -// virtual -bool LLVOAvatarSelf::shouldRenderRigged() const -{ - return gAgent.needsRenderAvatar(); -} - -// HACK: this will null out the avatar's local texture IDs before the TE message is sent -// to ensure local texture IDs are not sent to other clients in the area. -// this is a short-term solution. The long term solution will be to not set the texture -// IDs in the avatar object, and keep them only in the wearable. -// This will involve further refactoring that is too risky for the initial release of 2.0. -bool LLVOAvatarSelf::sendAppearanceMessage(LLMessageSystem *mesgsys) const -{ - LLUUID texture_id[TEX_NUM_INDICES]; - // pack away current TEs to make sure we don't send them out - for (LLAvatarAppearanceDictionary::Textures::const_iterator iter = sAvatarDictionary->getTextures().begin(); - iter != sAvatarDictionary->getTextures().end(); - ++iter) - { - const ETextureIndex index = iter->first; - const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = iter->second; - if (!texture_dict->mIsBakedTexture) - { - LLTextureEntry* entry = getTE((U8) index); - texture_id[index] = entry->getID(); - entry->setID(IMG_DEFAULT_AVATAR); - } - } - - bool success = packTEMessage(mesgsys); - - // unpack TEs to make sure we don't re-trigger a bake - for (LLAvatarAppearanceDictionary::Textures::const_iterator iter = sAvatarDictionary->getTextures().begin(); - iter != sAvatarDictionary->getTextures().end(); - ++iter) - { - const ETextureIndex index = iter->first; - const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = iter->second; - if (!texture_dict->mIsBakedTexture) - { - LLTextureEntry* entry = getTE((U8) index); - entry->setID(texture_id[index]); - } - } - - return success; -} - -//------------------------------------------------------------------------ -// sendHoverHeight() -//------------------------------------------------------------------------ -void LLVOAvatarSelf::sendHoverHeight() const -{ - std::string url = gAgent.getRegionCapability("AgentPreferences"); - - if (!url.empty()) - { - LLSD update = LLSD::emptyMap(); - const LLVector3& hover_offset = getHoverOffset(); - update["hover_height"] = hover_offset[2]; - - LL_DEBUGS("Avatar") << avString() << "sending hover height value " << hover_offset[2] << LL_ENDL; - - // *TODO: - this class doesn't really do anything, could just use a base - // class responder if nothing else gets added. - // (comment from removed Responder) - LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, update, - "Hover height sent to sim", "Hover height not sent to sim"); - mLastHoverOffsetSent = hover_offset; - } -} - -void LLVOAvatarSelf::setHoverOffset(const LLVector3& hover_offset, bool send_update) -{ - if (getHoverOffset() != hover_offset) - { - LL_INFOS("Avatar") << avString() << " setting hover due to change " << hover_offset[2] << LL_ENDL; - LLVOAvatar::setHoverOffset(hover_offset, send_update); - } - if (send_update && (hover_offset != mLastHoverOffsetSent)) - { - LL_INFOS("Avatar") << avString() << " sending hover due to change " << hover_offset[2] << LL_ENDL; - sendHoverHeight(); - } -} - -//------------------------------------------------------------------------ -// needsRenderBeam() -//------------------------------------------------------------------------ -bool LLVOAvatarSelf::needsRenderBeam() -{ - LLTool *tool = LLToolMgr::getInstance()->getCurrentTool(); - - bool is_touching_or_grabbing = (tool == LLToolGrab::getInstance() && LLToolGrab::getInstance()->isEditing()); - LLViewerObject* objp = LLToolGrab::getInstance()->getEditingObject(); - if (objp // might need to be "!objp ||" instead of "objp &&". - && (objp->isAttachment() || objp->isAvatar())) - { - // don't render grab tool's selection beam on hud objects, - // attachments or avatars - is_touching_or_grabbing = false; - } - return is_touching_or_grabbing || (getAttachmentState() & AGENT_STATE_EDITING && LLSelectMgr::getInstance()->shouldShowSelection()); -} - -// static -void LLVOAvatarSelf::deleteScratchTextures() -{ - for(std::map< LLGLenum, LLGLuint*>::iterator it = sScratchTexNames.begin(), end_it = sScratchTexNames.end(); - it != end_it; - ++it) - { - LLImageGL::deleteTextures(1, (U32 *)it->second ); - stop_glerror(); - } - - if( sScratchTexBytes.value() ) - { - LL_DEBUGS() << "Clearing Scratch Textures " << (S32Kilobytes)sScratchTexBytes << LL_ENDL; - - delete_and_clear(sScratchTexNames); - sScratchTexBytes = S32Bytes(0); - } -} - -// static -void LLVOAvatarSelf::dumpScratchTextureByteCount() -{ - LL_INFOS() << "Scratch Texture GL: " << (sScratchTexBytes/1024) << "KB" << LL_ENDL; -} - -void LLVOAvatarSelf::dumpWearableInfo(LLAPRFile& outfile) -{ - apr_file_t* file = outfile.getFileHandle(); - if (!file) - { - return; - } - - - apr_file_printf( file, "\n\n" ); - - LLWearableData *wd = getWearableData(); - LLWearableType *wr_inst = LLWearableType::getInstance(); - for (S32 type = 0; type < LLWearableType::WT_COUNT; type++) - { - const std::string& type_name = wr_inst->getTypeName((LLWearableType::EType)type); - for (U32 j=0; j< wd->getWearableCount((LLWearableType::EType)type); j++) - { - LLViewerWearable *wearable = gAgentWearables.getViewerWearable((LLWearableType::EType)type,j); - apr_file_printf( file, "\n\t \n", - type_name.c_str(), wearable->getName().c_str() ); - LLWearable::visual_param_vec_t v_params; - wearable->getVisualParams(v_params); - for (LLWearable::visual_param_vec_t::iterator it = v_params.begin(); - it != v_params.end(); ++it) - { - LLVisualParam *param = *it; - dump_visual_param(file, param, param->getWeight()); - } - } - } - apr_file_printf( file, "\n\n" ); -} +/** + * @file llvoavatar.cpp + * @brief Implementation of LLVOAvatar class which is a derivation fo LLViewerObject + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#if LL_MSVC +// disable warning about boost::lexical_cast returning uninitialized data +// when it fails to parse the string +#pragma warning (disable:4701) +#endif + +#include "llviewerprecompiledheaders.h" + +#include "llvoavatarself.h" +#include "llvoavatar.h" + +#include "pipeline.h" + +#include "llagent.h" // Get state values from here +#include "llattachmentsmgr.h" +#include "llagentcamera.h" +#include "llagentwearables.h" +#include "llhudeffecttrail.h" +#include "llhudmanager.h" +#include "llinventoryfunctions.h" +#include "lllocaltextureobject.h" +#include "llnotificationsutil.h" +#include "llselectmgr.h" +#include "lltoolgrab.h" // for needsRenderBeam +#include "lltoolmgr.h" // for needsRenderBeam +#include "lltoolmorph.h" +#include "lltrans.h" +#include "llviewercamera.h" +#include "llviewercontrol.h" +#include "llviewermenu.h" +#include "llviewerobjectlist.h" +#include "llviewerstats.h" +#include "llviewerregion.h" +#include "llviewertexlayer.h" +#include "llviewerwearable.h" +#include "llappearancemgr.h" +#include "llmeshrepository.h" +#include "llvovolume.h" +#include "llsdutil.h" +#include "llstartup.h" +#include "llsdserialize.h" +#include "llcallstack.h" +#include "llcorehttputil.h" +#include "lluiusage.h" + +#if LL_MSVC +// disable boost::lexical_cast warning +#pragma warning (disable:4702) +#endif + +#include + +LLPointer gAgentAvatarp = NULL; + +bool isAgentAvatarValid() +{ + return (gAgentAvatarp.notNull() && gAgentAvatarp->isValid()); +} + +void selfStartPhase(const std::string& phase_name) +{ + if (isAgentAvatarValid()) + { + gAgentAvatarp->startPhase(phase_name); + } +} + +void selfStopPhase(const std::string& phase_name, bool err_check) +{ + if (isAgentAvatarValid()) + { + gAgentAvatarp->stopPhase(phase_name, err_check); + } +} + +void selfClearPhases() +{ + if (isAgentAvatarValid()) + { + gAgentAvatarp->clearPhases(); + } +} + +using namespace LLAvatarAppearanceDefines; + + +LLSD summarize_by_buckets(std::vector in_records, std::vector by_fields, std::string val_field); + +/********************************************************************************* + ** ** + ** Begin private LLVOAvatarSelf Support classes + ** + **/ + +struct LocalTextureData +{ + LocalTextureData() : + mIsBakedReady(false), + mDiscard(MAX_DISCARD_LEVEL+1), + mImage(NULL), + mWearableID(IMG_DEFAULT_AVATAR), + mTexEntry(NULL) + {} + LLPointer mImage; + bool mIsBakedReady; + S32 mDiscard; + LLUUID mWearableID; // UUID of the wearable that this texture belongs to, not of the image itself + LLTextureEntry *mTexEntry; +}; + +//----------------------------------------------------------------------------- +// Callback data +//----------------------------------------------------------------------------- + + +/** + ** + ** End LLVOAvatarSelf Support classes + ** ** + *********************************************************************************/ + + +//----------------------------------------------------------------------------- +// Static Data +//----------------------------------------------------------------------------- +S32Bytes LLVOAvatarSelf::sScratchTexBytes(0); +std::map< LLGLenum, LLGLuint*> LLVOAvatarSelf::sScratchTexNames; + + +/********************************************************************************* + ** ** + ** Begin LLVOAvatarSelf Constructor routines + ** + **/ + +LLVOAvatarSelf::LLVOAvatarSelf(const LLUUID& id, + const LLPCode pcode, + LLViewerRegion* regionp) : + LLVOAvatar(id, pcode, regionp), + mScreenp(NULL), + mLastRegionHandle(0), + mRegionCrossingCount(0), + // Value outside legal range, so will always be a mismatch the + // first time through. + mLastHoverOffsetSent(LLVector3(0.0f, 0.0f, -999.0f)), + mInitialMetric(true), + mMetricSequence(0) +{ + mMotionController.mIsSelf = true; + + LL_DEBUGS() << "Marking avatar as self " << id << LL_ENDL; +} + +// Called periodically for diagnostics, return true when done. +bool output_self_av_texture_diagnostics() +{ + if (!isAgentAvatarValid()) + return true; // done checking + + gAgentAvatarp->outputRezDiagnostics(); + + return false; +} + +bool update_avatar_rez_metrics() +{ + if (!isAgentAvatarValid()) + return true; + + gAgentAvatarp->updateAvatarRezMetrics(false); + + return false; +} + +void LLVOAvatarSelf::initInstance() +{ + bool status = true; + // creates hud joint(mScreen) among other things + status &= loadAvatarSelf(); + + // adds attachment points to mScreen among other things + LLVOAvatar::initInstance(); + + LL_INFOS() << "Self avatar object created. Starting timer." << LL_ENDL; + mDebugSelfLoadTimer.reset(); + // clear all times to -1 for debugging + for (U32 i =0; i < LLAvatarAppearanceDefines::TEX_NUM_INDICES; ++i) + { + for (U32 j = 0; j <= MAX_DISCARD_LEVEL; ++j) + { + mDebugTextureLoadTimes[i][j] = -1.0f; + } + } + + for (U32 i =0; i < LLAvatarAppearanceDefines::BAKED_NUM_INDICES; ++i) + { + mDebugBakedTextureTimes[i][0] = -1.0f; + mDebugBakedTextureTimes[i][1] = -1.0f; + } + + status &= buildMenus(); + if (!status) + { + LL_ERRS() << "Unable to load user's avatar" << LL_ENDL; + return; + } + + setHoverIfRegionEnabled(); + + //doPeriodically(output_self_av_texture_diagnostics, 30.0); + doPeriodically(update_avatar_rez_metrics, 5.0); + doPeriodically(boost::bind(&LLVOAvatarSelf::checkStuckAppearance, this), 30.0); + + mInitFlags |= 1<<2; +} + +void LLVOAvatarSelf::setHoverIfRegionEnabled() +{ + if (getRegion() && getRegion()->simulatorFeaturesReceived()) + { + if (getRegion()->avatarHoverHeightEnabled()) + { + F32 hover_z = gSavedPerAccountSettings.getF32("AvatarHoverOffsetZ"); + setHoverOffset(LLVector3(0.0, 0.0, llclamp(hover_z,MIN_HOVER_Z,MAX_HOVER_Z))); + LL_INFOS("Avatar") << avString() << " set hover height from debug setting " << hover_z << LL_ENDL; + } + else + { + setHoverOffset(LLVector3(0.0, 0.0, 0.0)); + LL_INFOS("Avatar") << avString() << " zeroing hover height, region does not support" << LL_ENDL; + } + } + else + { + LL_INFOS("Avatar") << avString() << " region or simulator features not known, no change on hover" << LL_ENDL; + if (getRegion()) + { + getRegion()->setSimulatorFeaturesReceivedCallback(boost::bind(&LLVOAvatarSelf::onSimulatorFeaturesReceived,this,_1)); + } + + } +} + +bool LLVOAvatarSelf::checkStuckAppearance() +{ + const F32 CONDITIONAL_UNSTICK_INTERVAL = 300.0; + const F32 UNCONDITIONAL_UNSTICK_INTERVAL = 600.0; + + if (gAgentWearables.isCOFChangeInProgress()) + { + LL_DEBUGS("Avatar") << "checking for stuck appearance" << LL_ENDL; + F32 change_time = gAgentWearables.getCOFChangeTime(); + LL_DEBUGS("Avatar") << "change in progress for " << change_time << " seconds" << LL_ENDL; + S32 active_hp = LLAppearanceMgr::instance().countActiveHoldingPatterns(); + LL_DEBUGS("Avatar") << "active holding patterns " << active_hp << " seconds" << LL_ENDL; + S32 active_copies = LLAppearanceMgr::instance().getActiveCopyOperations(); + LL_DEBUGS("Avatar") << "active copy operations " << active_copies << LL_ENDL; + + if ((change_time > CONDITIONAL_UNSTICK_INTERVAL && active_copies == 0) || + (change_time > UNCONDITIONAL_UNSTICK_INTERVAL)) + { + gAgentWearables.notifyLoadingFinished(); + } + } + + // Return false to continue running check periodically. + return LLApp::isExiting(); +} + +// virtual +void LLVOAvatarSelf::markDead() +{ + mBeam = NULL; + LLVOAvatar::markDead(); +} + +/*virtual*/ bool LLVOAvatarSelf::loadAvatar() +{ + bool success = LLVOAvatar::loadAvatar(); + + // set all parameters stored directly in the avatar to have + // the isSelfParam to be true - this is used to prevent + // them from being animated or trigger accidental rebakes + // when we copy params from the wearable to the base avatar. + for (LLViewerVisualParam* param = (LLViewerVisualParam*) getFirstVisualParam(); + param; + param = (LLViewerVisualParam*) getNextVisualParam()) + { + if (param->getWearableType() != LLWearableType::WT_INVALID) + { + param->setIsDummy(true); + } + } + + return success; +} + + +bool LLVOAvatarSelf::loadAvatarSelf() +{ + bool success = true; + // avatar_skeleton.xml + if (!buildSkeletonSelf(sAvatarSkeletonInfo)) + { + LL_WARNS() << "avatar file: buildSkeleton() failed" << LL_ENDL; + return false; + } + + return success; +} + +bool LLVOAvatarSelf::buildSkeletonSelf(const LLAvatarSkeletonInfo *info) +{ + // add special-purpose "screen" joint + mScreenp = new LLViewerJoint("mScreen", NULL); + // for now, put screen at origin, as it is only used during special + // HUD rendering mode + F32 aspect = LLViewerCamera::getInstance()->getAspect(); + LLVector3 scale(1.f, aspect, 1.f); + mScreenp->setScale(scale); + // SL-315 + mScreenp->setWorldPosition(LLVector3::zero); + // need to update screen agressively when sidebar opens/closes, for example + mScreenp->mUpdateXform = true; + return true; +} + +bool LLVOAvatarSelf::buildMenus() +{ + //------------------------------------------------------------------------- + // build the attach and detach menus + //------------------------------------------------------------------------- + gAttachBodyPartPieMenus[0] = NULL; + + LLContextMenu::Params params; + params.label(LLTrans::getString("BodyPartsRightArm")); + params.name(params.label); + params.visible(false); + gAttachBodyPartPieMenus[1] = LLUICtrlFactory::create (params); + + params.label(LLTrans::getString("BodyPartsHead")); + params.name(params.label); + gAttachBodyPartPieMenus[2] = LLUICtrlFactory::create (params); + + params.label(LLTrans::getString("BodyPartsLeftArm")); + params.name(params.label); + gAttachBodyPartPieMenus[3] = LLUICtrlFactory::create (params); + + gAttachBodyPartPieMenus[4] = NULL; + + params.label(LLTrans::getString("BodyPartsLeftLeg")); + params.name(params.label); + gAttachBodyPartPieMenus[5] = LLUICtrlFactory::create (params); + + params.label(LLTrans::getString("BodyPartsTorso")); + params.name(params.label); + gAttachBodyPartPieMenus[6] = LLUICtrlFactory::create (params); + + params.label(LLTrans::getString("BodyPartsRightLeg")); + params.name(params.label); + gAttachBodyPartPieMenus[7] = LLUICtrlFactory::create (params); + + params.label(LLTrans::getString("BodyPartsEnhancedSkeleton")); + params.name(params.label); + gAttachBodyPartPieMenus[8] = LLUICtrlFactory::create(params); + + gDetachBodyPartPieMenus[0] = NULL; + + params.label(LLTrans::getString("BodyPartsRightArm")); + params.name(params.label); + gDetachBodyPartPieMenus[1] = LLUICtrlFactory::create (params); + + params.label(LLTrans::getString("BodyPartsHead")); + params.name(params.label); + gDetachBodyPartPieMenus[2] = LLUICtrlFactory::create (params); + + params.label(LLTrans::getString("BodyPartsLeftArm")); + params.name(params.label); + gDetachBodyPartPieMenus[3] = LLUICtrlFactory::create (params); + + gDetachBodyPartPieMenus[4] = NULL; + + params.label(LLTrans::getString("BodyPartsLeftLeg")); + params.name(params.label); + gDetachBodyPartPieMenus[5] = LLUICtrlFactory::create (params); + + params.label(LLTrans::getString("BodyPartsTorso")); + params.name(params.label); + gDetachBodyPartPieMenus[6] = LLUICtrlFactory::create (params); + + params.label(LLTrans::getString("BodyPartsRightLeg")); + params.name(params.label); + gDetachBodyPartPieMenus[7] = LLUICtrlFactory::create (params); + + params.label(LLTrans::getString("BodyPartsEnhancedSkeleton")); + params.name(params.label); + gDetachBodyPartPieMenus[8] = LLUICtrlFactory::create(params); + + for (S32 i = 0; i < 9; i++) + { + if (gAttachBodyPartPieMenus[i]) + { + gAttachPieMenu->appendContextSubMenu( gAttachBodyPartPieMenus[i] ); + } + else + { + for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + if (attachment && attachment->getGroup() == i) + { + LLMenuItemCallGL::Params item_params; + + std::string sub_piemenu_name = attachment->getName(); + if (LLTrans::getString(sub_piemenu_name) != "") + { + item_params.label = LLTrans::getString(sub_piemenu_name); + } + else + { + item_params.label = sub_piemenu_name; + } + item_params.name =(item_params.label ); + item_params.on_click.function_name = "Object.AttachToAvatar"; + item_params.on_click.parameter = iter->first; + item_params.on_enable.function_name = "Object.EnableWear"; + item_params.on_enable.parameter = iter->first; + LLMenuItemCallGL* item = LLUICtrlFactory::create(item_params); + + gAttachPieMenu->addChild(item); + + break; + + } + } + } + + if (gDetachBodyPartPieMenus[i]) + { + gDetachPieMenu->appendContextSubMenu( gDetachBodyPartPieMenus[i] ); + gDetachAttSelfMenu->appendContextSubMenu(gDetachBodyPartPieMenus[i]); + gDetachAvatarMenu->appendContextSubMenu(gDetachBodyPartPieMenus[i]); + } + else + { + for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + if (attachment && attachment->getGroup() == i) + { + LLMenuItemCallGL::Params item_params; + std::string sub_piemenu_name = attachment->getName(); + if (LLTrans::getString(sub_piemenu_name) != "") + { + item_params.label = LLTrans::getString(sub_piemenu_name); + } + else + { + item_params.label = sub_piemenu_name; + } + item_params.name =(item_params.label ); + item_params.on_click.function_name = "Attachment.DetachFromPoint"; + item_params.on_click.parameter = iter->first; + item_params.on_enable.function_name = "Attachment.PointFilled"; + item_params.on_enable.parameter = iter->first; + LLMenuItemCallGL* item = LLUICtrlFactory::create(item_params); + + gDetachPieMenu->addChild(item); + gDetachAttSelfMenu->addChild(LLUICtrlFactory::create(item_params)); + gDetachAvatarMenu->addChild(LLUICtrlFactory::create(item_params)); + break; + } + } + } + } + + + // add screen attachments + for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + if (attachment->getGroup() == 9) + { + LLMenuItemCallGL::Params item_params; + std::string sub_piemenu_name = attachment->getName(); + if (LLTrans::getString(sub_piemenu_name) != "") + { + item_params.label = LLTrans::getString(sub_piemenu_name); + } + else + { + item_params.label = sub_piemenu_name; + } + item_params.name =(item_params.label ); + item_params.on_click.function_name = "Object.AttachToAvatar"; + item_params.on_click.parameter = iter->first; + item_params.on_enable.function_name = "Object.EnableWear"; + item_params.on_enable.parameter = iter->first; + LLMenuItemCallGL* item = LLUICtrlFactory::create(item_params); + gAttachScreenPieMenu->addChild(item); + + item_params.on_click.function_name = "Attachment.DetachFromPoint"; + item_params.on_click.parameter = iter->first; + item_params.on_enable.function_name = "Attachment.PointFilled"; + item_params.on_enable.parameter = iter->first; + item = LLUICtrlFactory::create(item_params); + gDetachScreenPieMenu->addChild(item); + gDetachHUDAttSelfMenu->addChild(LLUICtrlFactory::create(item_params)); + gDetachHUDAvatarMenu->addChild(LLUICtrlFactory::create(item_params)); + } + } + + for (S32 pass = 0; pass < 2; pass++) + { + // *TODO: Skinning - gAttachSubMenu is an awful, awful hack + if (!gAttachSubMenu) + { + break; + } + for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + if (attachment->getIsHUDAttachment() != (pass == 1)) + { + continue; + } + LLMenuItemCallGL::Params item_params; + std::string sub_piemenu_name = attachment->getName(); + if (LLTrans::getString(sub_piemenu_name) != "") + { + item_params.label = LLTrans::getString(sub_piemenu_name); + } + else + { + item_params.label = sub_piemenu_name; + } + item_params.name =(item_params.label ); + item_params.on_click.function_name = "Object.AttachToAvatar"; + item_params.on_click.parameter = iter->first; + item_params.on_enable.function_name = "Object.EnableWear"; + item_params.on_enable.parameter = iter->first; + //* TODO: Skinning: + //LLSD params; + //params["index"] = iter->first; + //params["label"] = attachment->getName(); + //item->addEventHandler("on_enable", LLMenuItemCallGL::MenuCallback().function_name("Attachment.Label").parameter(params)); + + LLMenuItemCallGL* item = LLUICtrlFactory::create(item_params); + gAttachSubMenu->addChild(item); + + item_params.on_click.function_name = "Attachment.DetachFromPoint"; + item_params.on_click.parameter = iter->first; + item_params.on_enable.function_name = "Attachment.PointFilled"; + item_params.on_enable.parameter = iter->first; + //* TODO: Skinning: item->addEventHandler("on_enable", LLMenuItemCallGL::MenuCallback().function_name("Attachment.Label").parameter(params)); + + item = LLUICtrlFactory::create(item_params); + gDetachSubMenu->addChild(item); + } + if (pass == 0) + { + // put separator between non-hud and hud attachments + gAttachSubMenu->addSeparator(); + gDetachSubMenu->addSeparator(); + } + } + + for (S32 group = 0; group < 9; group++) + { + // skip over groups that don't have sub menus + if (!gAttachBodyPartPieMenus[group] || !gDetachBodyPartPieMenus[group]) + { + continue; + } + + std::multimap attachment_pie_menu_map; + + // gather up all attachment points assigned to this group, and throw into map sorted by pie slice number + for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + if(attachment && attachment->getGroup() == group) + { + // use multimap to provide a partial order off of the pie slice key + S32 pie_index = attachment->getPieSlice(); + attachment_pie_menu_map.insert(std::make_pair(pie_index, iter->first)); + } + } + + // add in requested order to pie menu, inserting separators as necessary + for (std::multimap::iterator attach_it = attachment_pie_menu_map.begin(); + attach_it != attachment_pie_menu_map.end(); ++attach_it) + { + S32 attach_index = attach_it->second; + + LLViewerJointAttachment* attachment = get_if_there(mAttachmentPoints, attach_index, (LLViewerJointAttachment*)NULL); + if (attachment) + { + LLMenuItemCallGL::Params item_params; + item_params.name = attachment->getName(); + item_params.label = LLTrans::getString(attachment->getName()); + item_params.on_click.function_name = "Object.AttachToAvatar"; + item_params.on_click.parameter = attach_index; + item_params.on_enable.function_name = "Object.EnableWear"; + item_params.on_enable.parameter = attach_index; + + LLMenuItemCallGL* item = LLUICtrlFactory::create(item_params); + gAttachBodyPartPieMenus[group]->addChild(item); + + item_params.on_click.function_name = "Attachment.DetachFromPoint"; + item_params.on_click.parameter = attach_index; + item_params.on_enable.function_name = "Attachment.PointFilled"; + item_params.on_enable.parameter = attach_index; + item = LLUICtrlFactory::create(item_params); + gDetachBodyPartPieMenus[group]->addChild(item); + } + } + } + return true; +} + +void LLVOAvatarSelf::cleanup() +{ + markDead(); + delete mScreenp; + mScreenp = NULL; + mRegionp = NULL; +} + +LLVOAvatarSelf::~LLVOAvatarSelf() +{ + cleanup(); +} + +/** + ** + ** End LLVOAvatarSelf Constructor routines + ** ** + *********************************************************************************/ + +// virtual +bool LLVOAvatarSelf::updateCharacter(LLAgent &agent) +{ + // update screen joint size + if (mScreenp) + { + F32 aspect = LLViewerCamera::getInstance()->getAspect(); + LLVector3 scale(1.f, aspect, 1.f); + mScreenp->setScale(scale); + mScreenp->updateWorldMatrixChildren(); + resetHUDAttachments(); + } + + return LLVOAvatar::updateCharacter(agent); +} + +// virtual +bool LLVOAvatarSelf::isValid() const +{ + return ((getRegion() != NULL) && !isDead()); +} + +// virtual +void LLVOAvatarSelf::idleUpdate(LLAgent &agent, const F64 &time) +{ + if (isValid()) + { + LLVOAvatar::idleUpdate(agent, time); + idleUpdateTractorBeam(); + } +} + +// virtual +LLJoint *LLVOAvatarSelf::getJoint(const std::string &name) +{ + LLJoint *jointp = NULL; + jointp = LLVOAvatar::getJoint(name); + if (!jointp && mScreenp) + { + jointp = mScreenp->findJoint(name); + if (jointp) + { + mJointMap[name] = jointp; + } + } + if (jointp && jointp != mScreenp && jointp != mRoot) + { + llassert(LLVOAvatar::getJoint((S32)jointp->getJointNum())==jointp); + } + return jointp; +} + +// virtual +bool LLVOAvatarSelf::setVisualParamWeight(const LLVisualParam *which_param, F32 weight) +{ + if (!which_param) + { + return false; + } + LLViewerVisualParam *param = (LLViewerVisualParam*) LLCharacter::getVisualParam(which_param->getID()); + return setParamWeight(param,weight); +} + +// virtual +bool LLVOAvatarSelf::setVisualParamWeight(const char* param_name, F32 weight) +{ + if (!param_name) + { + return false; + } + LLViewerVisualParam *param = (LLViewerVisualParam*) LLCharacter::getVisualParam(param_name); + return setParamWeight(param,weight); +} + +// virtual +bool LLVOAvatarSelf::setVisualParamWeight(S32 index, F32 weight) +{ + LLViewerVisualParam *param = (LLViewerVisualParam*) LLCharacter::getVisualParam(index); + return setParamWeight(param,weight); +} + +bool LLVOAvatarSelf::setParamWeight(const LLViewerVisualParam *param, F32 weight) +{ + if (!param) + { + return false; + } + + if (param->getCrossWearable()) + { + LLWearableType::EType type = (LLWearableType::EType)param->getWearableType(); + U32 size = gAgentWearables.getWearableCount(type); + for (U32 count = 0; count < size; ++count) + { + LLViewerWearable *wearable = gAgentWearables.getViewerWearable(type,count); + if (wearable) + { + wearable->setVisualParamWeight(param->getID(), weight); + } + } + } + + return LLCharacter::setVisualParamWeight(param,weight); +} + +/*virtual*/ +void LLVOAvatarSelf::updateVisualParams() +{ + LLVOAvatar::updateVisualParams(); +} + +void LLVOAvatarSelf::writeWearablesToAvatar() +{ + for (U32 type = 0; type < LLWearableType::WT_COUNT; type++) + { + LLWearable *wearable = gAgentWearables.getTopWearable((LLWearableType::EType)type); + if (wearable) + { + wearable->writeToAvatar(this); + } + } + +} + +/*virtual*/ +void LLVOAvatarSelf::idleUpdateAppearanceAnimation() +{ + // Animate all top-level wearable visual parameters + gAgentWearables.animateAllWearableParams(calcMorphAmount()); + + // Apply wearable visual params to avatar + writeWearablesToAvatar(); + + //allow avatar to process updates + LLVOAvatar::idleUpdateAppearanceAnimation(); + +} + +// virtual +void LLVOAvatarSelf::requestStopMotion(LLMotion* motion) +{ + // Only agent avatars should handle the stop motion notifications. + + // Notify agent that motion has stopped + gAgent.requestStopMotion(motion); +} + +// virtual +bool LLVOAvatarSelf::hasMotionFromSource(const LLUUID& source_id) +{ + AnimSourceIterator motion_it = mAnimationSources.find(source_id); + return motion_it != mAnimationSources.end(); +} + +// virtual +void LLVOAvatarSelf::stopMotionFromSource(const LLUUID& source_id) +{ + for (AnimSourceIterator motion_it = mAnimationSources.find(source_id); motion_it != mAnimationSources.end(); ) + { + gAgent.sendAnimationRequest(motion_it->second, ANIM_REQUEST_STOP); + mAnimationSources.erase(motion_it); + // Must find() after each erase() to deal with potential iterator invalidation + // This also ensures that we don't go past the end of this source's animations + // into those of another source. + motion_it = mAnimationSources.find(source_id); + } + + + LLViewerObject* object = gObjectList.findObject(source_id); + if (object) + { + object->setFlagsWithoutUpdate(FLAGS_ANIM_SOURCE, false); + } +} + +void LLVOAvatarSelf::setLocalTextureTE(U8 te, LLViewerTexture* image, U32 index) +{ + if (te >= TEX_NUM_INDICES) + { + llassert(0); + return; + } + + if (getTEImage(te)->getID() == image->getID()) + { + return; + } + + if (isIndexBakedTexture((ETextureIndex)te)) + { + llassert(0); + return; + } + + setTEImage(te, image); +} + +//virtual +void LLVOAvatarSelf::removeMissingBakedTextures() +{ + bool removed = false; + for (U32 i = 0; i < mBakedTextureDatas.size(); i++) + { + const S32 te = mBakedTextureDatas[i].mTextureIndex; + const LLViewerTexture* tex = getTEImage(te); + + // Replace with default if we can't find the asset, assuming the + // default is actually valid (which it should be unless something + // is seriously wrong). + if (!tex || tex->isMissingAsset()) + { + LLViewerTexture *imagep = LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT_AVATAR); + if (imagep && imagep != tex) + { + setTEImage(te, imagep); + removed = true; + } + } + } + + if (removed) + { + for (U32 i = 0; i < mBakedTextureDatas.size(); i++) + { + LLViewerTexLayerSet *layerset = getTexLayerSet(i); + layerset->setUpdatesEnabled(true); + invalidateComposite(layerset); + } + updateMeshTextures(); + } +} + +void LLVOAvatarSelf::onSimulatorFeaturesReceived(const LLUUID& region_id) +{ + LL_INFOS("Avatar") << "simulator features received, setting hover based on region props" << LL_ENDL; + setHoverIfRegionEnabled(); +} + +//virtual +void LLVOAvatarSelf::updateRegion(LLViewerRegion *regionp) +{ + // Save the global position + LLVector3d global_pos_from_old_region = getPositionGlobal(); + + // Change the region + setRegion(regionp); + + if (regionp) + { // Set correct region-relative position from global coordinates + setPositionGlobal(global_pos_from_old_region); + + // Diagnostic info + //LLVector3d pos_from_new_region = getPositionGlobal(); + //LL_INFOS() << "pos_from_old_region is " << global_pos_from_old_region + // << " while pos_from_new_region is " << pos_from_new_region + // << LL_ENDL; + + // Update hover height, or schedule callback, based on whether + // it's supported in this region. + if (regionp->simulatorFeaturesReceived()) + { + setHoverIfRegionEnabled(); + } + else + { + regionp->setSimulatorFeaturesReceivedCallback(boost::bind(&LLVOAvatarSelf::onSimulatorFeaturesReceived,this,_1)); + } + } + + if (!regionp || (regionp->getHandle() != mLastRegionHandle)) + { + if (mLastRegionHandle != 0) + { + ++mRegionCrossingCount; + F64Seconds delta(mRegionCrossingTimer.getElapsedTimeF32()); + record(LLStatViewer::REGION_CROSSING_TIME, delta); + + // Diagnostics + LL_INFOS() << "Region crossing took " << (F32)(delta * 1000.0).value() << " ms " << LL_ENDL; + } + if (regionp) + { + mLastRegionHandle = regionp->getHandle(); + } + } + mRegionCrossingTimer.reset(); + LLViewerObject::updateRegion(regionp); +} + +//-------------------------------------------------------------------- +// draw tractor (selection) beam when editing objects +//-------------------------------------------------------------------- +//virtual +void LLVOAvatarSelf::idleUpdateTractorBeam() +{ + // This is only done for yourself (maybe it should be in the agent?) + if (!needsRenderBeam() || !isBuilt()) + { + mBeam = NULL; + } + else if (!mBeam || mBeam->isDead()) + { + // VEFFECT: Tractor Beam + mBeam = (LLHUDEffectSpiral *)LLHUDManager::getInstance()->createViewerEffect(LLHUDObject::LL_HUD_EFFECT_BEAM); + mBeam->setColor(LLColor4U(gAgent.getEffectColor())); + mBeam->setSourceObject(this); + mBeamTimer.reset(); + } + + if (!mBeam.isNull()) + { + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + + if (gAgentCamera.mPointAt.notNull()) + { + // get point from pointat effect + mBeam->setPositionGlobal(gAgentCamera.mPointAt->getPointAtPosGlobal()); + mBeam->triggerLocal(); + } + else if (selection->getFirstRootObject() && + selection->getSelectType() != SELECT_TYPE_HUD) + { + LLViewerObject* objectp = selection->getFirstRootObject(); + mBeam->setTargetObject(objectp); + } + else + { + mBeam->setTargetObject(NULL); + LLTool *tool = LLToolMgr::getInstance()->getCurrentTool(); + if (tool->isEditing()) + { + if (tool->getEditingObject()) + { + mBeam->setTargetObject(tool->getEditingObject()); + } + else + { + mBeam->setPositionGlobal(tool->getEditingPointGlobal()); + } + } + else + { + const LLPickInfo& pick = gViewerWindow->getLastPick(); + mBeam->setPositionGlobal(pick.mPosGlobal); + } + + } + if (mBeamTimer.getElapsedTimeF32() > 0.25f) + { + mBeam->setColor(LLColor4U(gAgent.getEffectColor())); + mBeam->setNeedsSendToSim(true); + mBeamTimer.reset(); + } + } +} + +//----------------------------------------------------------------------------- +// restoreMeshData() +//----------------------------------------------------------------------------- +// virtual +void LLVOAvatarSelf::restoreMeshData() +{ + //LL_INFOS() << "Restoring" << LL_ENDL; + mMeshValid = true; + updateJointLODs(); + updateAttachmentVisibility(gAgentCamera.getCameraMode()); + + // force mesh update as LOD might not have changed to trigger this + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_GEOMETRY); +} + + + +//----------------------------------------------------------------------------- +// updateAttachmentVisibility() +//----------------------------------------------------------------------------- +void LLVOAvatarSelf::updateAttachmentVisibility(U32 camera_mode) +{ + for (attachment_map_t::iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + if (attachment->getIsHUDAttachment()) + { + attachment->setAttachmentVisibility(true); + } + else + { + switch (camera_mode) + { + case CAMERA_MODE_MOUSELOOK: + if (LLVOAvatar::sVisibleInFirstPerson && attachment->getVisibleInFirstPerson()) + { + attachment->setAttachmentVisibility(true); + } + else + { + attachment->setAttachmentVisibility(false); + } + break; + default: + attachment->setAttachmentVisibility(true); + break; + } + } + } +} + +//----------------------------------------------------------------------------- +// updatedWearable( LLWearableType::EType type ) +// forces an update to any baked textures relevant to type. +// will force an upload of the resulting bake if the second parameter is true +//----------------------------------------------------------------------------- +void LLVOAvatarSelf::wearableUpdated(LLWearableType::EType type) +{ + for (LLAvatarAppearanceDictionary::BakedTextures::const_iterator baked_iter = sAvatarDictionary->getBakedTextures().begin(); + baked_iter != sAvatarDictionary->getBakedTextures().end(); + ++baked_iter) + { + const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = baked_iter->second; + const LLAvatarAppearanceDefines::EBakedTextureIndex index = baked_iter->first; + + if (baked_dict) + { + for (LLAvatarAppearanceDefines::wearables_vec_t::const_iterator type_iter = baked_dict->mWearables.begin(); + type_iter != baked_dict->mWearables.end(); + ++type_iter) + { + const LLWearableType::EType comp_type = *type_iter; + if (comp_type == type) + { + LLViewerTexLayerSet *layerset = getLayerSet(index); + if (layerset) + { + layerset->setUpdatesEnabled(true); + invalidateComposite(layerset); + } + break; + } + } + } + } +} + +//----------------------------------------------------------------------------- +// isWearingAttachment() +//----------------------------------------------------------------------------- +bool LLVOAvatarSelf::isWearingAttachment(const LLUUID& inv_item_id) const +{ + const LLUUID& base_inv_item_id = gInventory.getLinkedItemID(inv_item_id); + for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + const LLViewerJointAttachment* attachment = iter->second; + if (attachment->getAttachedObject(base_inv_item_id)) + { + return true; + } + } + return false; +} + +//----------------------------------------------------------------------------- +// getWornAttachment() +//----------------------------------------------------------------------------- +LLViewerObject* LLVOAvatarSelf::getWornAttachment(const LLUUID& inv_item_id) +{ + const LLUUID& base_inv_item_id = gInventory.getLinkedItemID(inv_item_id); + for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + if (LLViewerObject *attached_object = attachment->getAttachedObject(base_inv_item_id)) + { + return attached_object; + } + } + return NULL; +} + +bool LLVOAvatarSelf::getAttachedPointName(const LLUUID& inv_item_id, std::string& name) const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; + if (!gInventory.getItem(inv_item_id)) + { + name = "ATTACHMENT_MISSING_ITEM"; + return false; + } + const LLUUID& base_inv_item_id = gInventory.getLinkedItemID(inv_item_id); + if (!gInventory.getItem(base_inv_item_id)) + { + name = "ATTACHMENT_MISSING_BASE_ITEM"; + return false; + } + for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); + iter != mAttachmentPoints.end(); + ++iter) + { + const LLViewerJointAttachment* attachment = iter->second; + if (attachment->getAttachedObject(base_inv_item_id)) + { + name = attachment->getName(); + return true; + } + } + + name = "ATTACHMENT_NOT_ATTACHED"; + return false; +} + +//virtual +const LLViewerJointAttachment *LLVOAvatarSelf::attachObject(LLViewerObject *viewer_object) +{ + const LLViewerJointAttachment *attachment = LLVOAvatar::attachObject(viewer_object); + if (!attachment) + { + return 0; + } + + updateAttachmentVisibility(gAgentCamera.getCameraMode()); + + // Then make sure the inventory is in sync with the avatar. + + // Should just be the last object added + if (attachment->isObjectAttached(viewer_object)) + { + const LLUUID& attachment_id = viewer_object->getAttachmentItemID(); + LLAppearanceMgr::instance().registerAttachment(attachment_id); + updateLODRiggedAttachments(); + } + + return attachment; +} + +//virtual +bool LLVOAvatarSelf::detachObject(LLViewerObject *viewer_object) +{ + const LLUUID attachment_id = viewer_object->getAttachmentItemID(); + if ( LLVOAvatar::detachObject(viewer_object) ) + { + // the simulator should automatically handle permission revocation + + stopMotionFromSource(attachment_id); + LLFollowCamMgr::getInstance()->setCameraActive(viewer_object->getID(), false); + + LLViewerObject::const_child_list_t& child_list = viewer_object->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); + ++iter) + { + LLViewerObject* child_objectp = *iter; + // the simulator should automatically handle + // permissions revocation + + stopMotionFromSource(child_objectp->getID()); + LLFollowCamMgr::getInstance()->setCameraActive(child_objectp->getID(), false); + } + + // Make sure the inventory is in sync with the avatar. + + // Update COF contents, don't trigger appearance update. + if (!isValid()) + { + LL_INFOS() << "removeItemLinks skipped, avatar is under destruction" << LL_ENDL; + } + else + { + LLAppearanceMgr::instance().unregisterAttachment(attachment_id); + } + + return true; + } + return false; +} + +bool LLVOAvatarSelf::hasAttachmentsInTrash() +{ + const LLUUID trash_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_TRASH); + + for (attachment_map_t::const_iterator iter = mAttachmentPoints.begin(); iter != mAttachmentPoints.end(); ++iter) + { + LLViewerJointAttachment *attachment = iter->second; + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + LLViewerObject *attached_object = attachment_iter->get(); + if (attached_object && gInventory.isObjectDescendentOf(attached_object->getAttachmentItemID(), trash_id)) + { + return true; + } + } + } + return false; +} + +// static +bool LLVOAvatarSelf::detachAttachmentIntoInventory(const LLUUID &item_id) +{ + LLInventoryItem* item = gInventory.getItem(item_id); + if (item) + { + gMessageSystem->newMessageFast(_PREHASH_DetachAttachmentIntoInv); + gMessageSystem->nextBlockFast(_PREHASH_ObjectData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + gMessageSystem->addUUIDFast(_PREHASH_ItemID, item_id); + gMessageSystem->sendReliable(gAgent.getRegionHost()); + + // This object might have been selected, so let the selection manager know it's gone now + LLViewerObject *found_obj = gObjectList.findObject(item_id); + if (found_obj) + { + LLSelectMgr::getInstance()->remove(found_obj); + } + + // Error checking in case this object was attached to an invalid point + // In that case, just remove the item from COF preemptively since detach + // will fail. + if (isAgentAvatarValid()) + { + const LLViewerObject *attached_obj = gAgentAvatarp->getWornAttachment(item_id); + if (!attached_obj) + { + LLAppearanceMgr::instance().removeCOFItemLinks(item_id); + } + } + return true; + } + return false; +} + +U32 LLVOAvatarSelf::getNumWearables(LLAvatarAppearanceDefines::ETextureIndex i) const +{ + LLWearableType::EType type = sAvatarDictionary->getTEWearableType(i); + return gAgentWearables.getWearableCount(type); +} + +// virtual +void LLVOAvatarSelf::localTextureLoaded(bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src_raw, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata) +{ + + const LLUUID& src_id = src_vi->getID(); + LLAvatarTexData *data = (LLAvatarTexData *)userdata; + ETextureIndex index = data->mIndex; + if (!isIndexLocalTexture(index)) return; + + LLLocalTextureObject *local_tex_obj = getLocalTextureObject(index, 0); + + // fix for EXT-268. Preventing using of NULL pointer + if(NULL == local_tex_obj) + { + LL_WARNS("TAG") << "There is no Local Texture Object with index: " << index + << ", final: " << final + << LL_ENDL; + return; + } + if (success) + { + if (!local_tex_obj->getBakedReady() && + local_tex_obj->getImage() != NULL && + (local_tex_obj->getID() == src_id) && + discard_level < local_tex_obj->getDiscard()) + { + local_tex_obj->setDiscard(discard_level); + requestLayerSetUpdate(index); + if (isEditingAppearance()) + { + LLVisualParamHint::requestHintUpdates(); + } + updateMeshTextures(); + } + } + else if (final) + { + // Failed: asset is missing + if (!local_tex_obj->getBakedReady() && + local_tex_obj->getImage() != NULL && + local_tex_obj->getImage()->getID() == src_id) + { + local_tex_obj->setDiscard(0); + requestLayerSetUpdate(index); + updateMeshTextures(); + } + } +} + +// virtual +bool LLVOAvatarSelf::getLocalTextureGL(ETextureIndex type, LLViewerTexture** tex_pp, U32 index) const +{ + *tex_pp = NULL; + + if (!isIndexLocalTexture(type)) return false; + if (getLocalTextureID(type, index) == IMG_DEFAULT_AVATAR) return true; + + const LLLocalTextureObject *local_tex_obj = getLocalTextureObject(type, index); + if (!local_tex_obj) + { + return false; + } + *tex_pp = dynamic_cast (local_tex_obj->getImage()); + return true; +} + +LLViewerFetchedTexture* LLVOAvatarSelf::getLocalTextureGL(LLAvatarAppearanceDefines::ETextureIndex type, U32 index) const +{ + if (!isIndexLocalTexture(type)) + { + return NULL; + } + + const LLLocalTextureObject *local_tex_obj = getLocalTextureObject(type, index); + if (!local_tex_obj) + { + return NULL; + } + if (local_tex_obj->getID() == IMG_DEFAULT_AVATAR) + { + return LLViewerTextureManager::getFetchedTexture(IMG_DEFAULT_AVATAR); + } + return dynamic_cast (local_tex_obj->getImage()); +} + +const LLUUID& LLVOAvatarSelf::getLocalTextureID(ETextureIndex type, U32 index) const +{ + if (!isIndexLocalTexture(type)) return IMG_DEFAULT_AVATAR; + + const LLLocalTextureObject *local_tex_obj = getLocalTextureObject(type, index); + if (local_tex_obj && local_tex_obj->getImage() != NULL) + { + return local_tex_obj->getImage()->getID(); + } + return IMG_DEFAULT_AVATAR; +} + + +//----------------------------------------------------------------------------- +// isLocalTextureDataAvailable() +// Returns true if at least the lowest quality discard level exists for every texture +// in the layerset. +//----------------------------------------------------------------------------- +bool LLVOAvatarSelf::isLocalTextureDataAvailable(const LLViewerTexLayerSet* layerset) const +{ + /* if (layerset == mBakedTextureDatas[BAKED_HEAD].mTexLayerSet) + return getLocalDiscardLevel(TEX_HEAD_BODYPAINT) >= 0; */ + for (LLAvatarAppearanceDictionary::BakedTextures::const_iterator baked_iter = sAvatarDictionary->getBakedTextures().begin(); + baked_iter != sAvatarDictionary->getBakedTextures().end(); + ++baked_iter) + { + const EBakedTextureIndex baked_index = baked_iter->first; + if (layerset == mBakedTextureDatas[baked_index].mTexLayerSet) + { + bool ret = true; + const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = baked_iter->second; + for (texture_vec_t::const_iterator local_tex_iter = baked_dict->mLocalTextures.begin(); + local_tex_iter != baked_dict->mLocalTextures.end(); + ++local_tex_iter) + { + const ETextureIndex tex_index = *local_tex_iter; + const LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(tex_index); + const U32 wearable_count = gAgentWearables.getWearableCount(wearable_type); + for (U32 wearable_index = 0; wearable_index < wearable_count; wearable_index++) + { + bool tex_avail = (getLocalDiscardLevel(tex_index, wearable_index) >= 0); + ret &= tex_avail; + } + } + return ret; + } + } + llassert(0); + return false; +} + +//----------------------------------------------------------------------------- +// virtual +// isLocalTextureDataFinal() +// Returns true if the highest quality discard level exists for every texture +// in the layerset. +//----------------------------------------------------------------------------- +bool LLVOAvatarSelf::isLocalTextureDataFinal(const LLViewerTexLayerSet* layerset) const +{ + const U32 desired_tex_discard_level = gSavedSettings.getU32("TextureDiscardLevel"); + // const U32 desired_tex_discard_level = 0; // hack to not bake textures on lower discard levels. + + for (U32 i = 0; i < mBakedTextureDatas.size(); i++) + { + if (layerset == mBakedTextureDatas[i].mTexLayerSet) + { + const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = sAvatarDictionary->getBakedTexture((EBakedTextureIndex)i); + for (texture_vec_t::const_iterator local_tex_iter = baked_dict->mLocalTextures.begin(); + local_tex_iter != baked_dict->mLocalTextures.end(); + ++local_tex_iter) + { + const ETextureIndex tex_index = *local_tex_iter; + const LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(tex_index); + const U32 wearable_count = gAgentWearables.getWearableCount(wearable_type); + for (U32 wearable_index = 0; wearable_index < wearable_count; wearable_index++) + { + S32 local_discard_level = getLocalDiscardLevel(*local_tex_iter, wearable_index); + if ((local_discard_level > (S32)(desired_tex_discard_level)) || + (local_discard_level < 0 )) + { + return false; + } + } + } + return true; + } + } + llassert(0); + return false; +} + + +bool LLVOAvatarSelf::isAllLocalTextureDataFinal() const +{ + const U32 desired_tex_discard_level = gSavedSettings.getU32("TextureDiscardLevel"); + // const U32 desired_tex_discard_level = 0; // hack to not bake textures on lower discard levels + + for (U32 i = 0; i < mBakedTextureDatas.size(); i++) + { + const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = sAvatarDictionary->getBakedTexture((EBakedTextureIndex)i); + for (texture_vec_t::const_iterator local_tex_iter = baked_dict->mLocalTextures.begin(); + local_tex_iter != baked_dict->mLocalTextures.end(); + ++local_tex_iter) + { + const ETextureIndex tex_index = *local_tex_iter; + const LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(tex_index); + const U32 wearable_count = gAgentWearables.getWearableCount(wearable_type); + for (U32 wearable_index = 0; wearable_index < wearable_count; wearable_index++) + { + S32 local_discard_level = getLocalDiscardLevel(*local_tex_iter, wearable_index); + if ((local_discard_level > (S32)(desired_tex_discard_level)) || + (local_discard_level < 0 )) + { + return false; + } + } + } + } + return true; +} + +bool LLVOAvatarSelf::isTextureDefined(LLAvatarAppearanceDefines::ETextureIndex type, U32 index) const +{ + LLUUID id; + bool isDefined = true; + if (isIndexLocalTexture(type)) + { + const LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(type); + const U32 wearable_count = gAgentWearables.getWearableCount(wearable_type); + if (index >= wearable_count) + { + // invalid index passed in. check all textures of a given type + for (U32 wearable_index = 0; wearable_index < wearable_count; wearable_index++) + { + id = getLocalTextureID(type, wearable_index); + isDefined &= (id != IMG_DEFAULT_AVATAR && id != IMG_DEFAULT); + } + } + else + { + id = getLocalTextureID(type, index); + isDefined &= (id != IMG_DEFAULT_AVATAR && id != IMG_DEFAULT); + } + } + else + { + id = getTEImage(type)->getID(); + isDefined &= (id != IMG_DEFAULT_AVATAR && id != IMG_DEFAULT); + } + + return isDefined; +} + +//virtual +bool LLVOAvatarSelf::isTextureVisible(LLAvatarAppearanceDefines::ETextureIndex type, U32 index) const +{ + if (isIndexBakedTexture(type)) + { + return LLVOAvatar::isTextureVisible(type, (U32)0); + } + + LLUUID tex_id = getLocalTextureID(type,index); + return (tex_id != IMG_INVISIBLE) + || (LLDrawPoolAlpha::sShowDebugAlpha); +} + +//virtual +bool LLVOAvatarSelf::isTextureVisible(LLAvatarAppearanceDefines::ETextureIndex type, LLViewerWearable *wearable) const +{ + if (isIndexBakedTexture(type)) + { + return LLVOAvatar::isTextureVisible(type); + } + + U32 index; + if (gAgentWearables.getWearableIndex(wearable,index)) + { + return isTextureVisible(type,index); + } + else + { + LL_WARNS() << "Wearable not found" << LL_ENDL; + return false; + } +} + +bool LLVOAvatarSelf::areTexturesCurrent() const +{ + return gAgentWearables.areWearablesLoaded(); +} + +void LLVOAvatarSelf::invalidateComposite( LLTexLayerSet* layerset) +{ + LLViewerTexLayerSet *layer_set = dynamic_cast(layerset); + if( !layer_set || !layer_set->getUpdatesEnabled() ) + { + return; + } + // LL_INFOS() << "LLVOAvatar::invalidComposite() " << layerset->getBodyRegionName() << LL_ENDL; + + layer_set->requestUpdate(); + layer_set->invalidateMorphMasks(); +} + +void LLVOAvatarSelf::invalidateAll() +{ + for (U32 i = 0; i < mBakedTextureDatas.size(); i++) + { + LLViewerTexLayerSet *layerset = getTexLayerSet(i); + invalidateComposite(layerset); + } + //mDebugSelfLoadTimer.reset(); +} + +//----------------------------------------------------------------------------- +// setCompositeUpdatesEnabled() +//----------------------------------------------------------------------------- +void LLVOAvatarSelf::setCompositeUpdatesEnabled( bool b ) +{ + for (U32 i = 0; i < mBakedTextureDatas.size(); i++) + { + setCompositeUpdatesEnabled(i, b); + } +} + +void LLVOAvatarSelf::setCompositeUpdatesEnabled(U32 index, bool b) +{ + LLViewerTexLayerSet *layerset = getTexLayerSet(index); + if (layerset ) + { + layerset->setUpdatesEnabled( b ); + } +} + +bool LLVOAvatarSelf::isCompositeUpdateEnabled(U32 index) +{ + LLViewerTexLayerSet *layerset = getTexLayerSet(index); + if (layerset) + { + return layerset->getUpdatesEnabled(); + } + return false; +} + +void LLVOAvatarSelf::setupComposites() +{ + for (U32 i = 0; i < mBakedTextureDatas.size(); i++) + { + ETextureIndex tex_index = mBakedTextureDatas[i].mTextureIndex; + bool layer_baked = isTextureDefined(tex_index, gAgentWearables.getWearableCount(tex_index)); + LLViewerTexLayerSet *layerset = getTexLayerSet(i); + if (layerset) + { + layerset->setUpdatesEnabled(!layer_baked); + } + } +} + +void LLVOAvatarSelf::updateComposites() +{ + for (U32 i = 0; i < mBakedTextureDatas.size(); i++) + { + LLViewerTexLayerSet *layerset = getTexLayerSet(i); + if (layerset + && ((i != BAKED_SKIRT) || isWearingWearableType(LLWearableType::WT_SKIRT))) + { + layerset->updateComposite(); + } + } +} + +// virtual +S32 LLVOAvatarSelf::getLocalDiscardLevel(ETextureIndex type, U32 wearable_index) const +{ + if (!isIndexLocalTexture(type)) return false; + + const LLLocalTextureObject *local_tex_obj = getLocalTextureObject(type, wearable_index); + if (local_tex_obj) + { + const LLViewerFetchedTexture* image = dynamic_cast( local_tex_obj->getImage() ); + if (type >= 0 + && local_tex_obj->getID() != IMG_DEFAULT_AVATAR + && !image->isMissingAsset()) + { + return image->getDiscardLevel(); + } + else + { + // We don't care about this (no image associated with the layer) treat as fully loaded. + return 0; + } + } + return 0; +} + +// virtual +// Counts the memory footprint of local textures. +void LLVOAvatarSelf::getLocalTextureByteCount(S32* gl_bytes) const +{ + *gl_bytes = 0; + for (S32 type = 0; type < TEX_NUM_INDICES; type++) + { + if (!isIndexLocalTexture((ETextureIndex)type)) continue; + U32 max_tex = getNumWearables((ETextureIndex) type); + for (U32 num = 0; num < max_tex; num++) + { + const LLLocalTextureObject *local_tex_obj = getLocalTextureObject((ETextureIndex) type, num); + if (local_tex_obj) + { + const LLViewerFetchedTexture* image_gl = dynamic_cast( local_tex_obj->getImage() ); + if (image_gl) + { + S32 bytes = (S32)image_gl->getWidth() * image_gl->getHeight() * image_gl->getComponents(); + + if (image_gl->hasGLTexture()) + { + *gl_bytes += bytes; + } + } + } + } + } +} + +// virtual +void LLVOAvatarSelf::setLocalTexture(ETextureIndex type, LLViewerTexture* src_tex, bool baked_version_ready, U32 index) +{ + if (!isIndexLocalTexture(type)) return; + + LLViewerFetchedTexture* tex = LLViewerTextureManager::staticCastToFetchedTexture(src_tex, true) ; + if(!tex) + { + return ; + } + + S32 desired_discard = isSelf() ? 0 : 2; + LLLocalTextureObject *local_tex_obj = getLocalTextureObject(type,index); + if (!local_tex_obj) + { + if (type >= TEX_NUM_INDICES) + { + LL_ERRS() << "Tried to set local texture with invalid type: (" << (U32) type << ", " << index << ")" << LL_ENDL; + return; + } + LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(type); + if (!gAgentWearables.getViewerWearable(wearable_type,index)) + { + // no wearable is loaded, cannot set the texture. + return; + } + gAgentWearables.addLocalTextureObject(wearable_type,type,index); + local_tex_obj = getLocalTextureObject(type,index); + if (!local_tex_obj) + { + LL_ERRS() << "Unable to create LocalTextureObject for wearable type & index: (" << (U32) wearable_type << ", " << index << ")" << LL_ENDL; + return; + } + + LLViewerTexLayerSet *layer_set = getLayerSet(type); + if (layer_set) + { + layer_set->cloneTemplates(local_tex_obj, type, gAgentWearables.getViewerWearable(wearable_type,index)); + } + + } + if (!baked_version_ready) + { + if (tex != local_tex_obj->getImage() || local_tex_obj->getBakedReady()) + { + local_tex_obj->setDiscard(MAX_DISCARD_LEVEL+1); + } + if (tex->getID() != IMG_DEFAULT_AVATAR) + { + if (local_tex_obj->getDiscard() > desired_discard) + { + S32 tex_discard = tex->getDiscardLevel(); + if (tex_discard >= 0 && tex_discard <= desired_discard) + { + local_tex_obj->setDiscard(tex_discard); + if (isSelf()) + { + requestLayerSetUpdate(type); + if (isEditingAppearance()) + { + LLVisualParamHint::requestHintUpdates(); + } + } + } + else + { + tex->setLoadedCallback(onLocalTextureLoaded, desired_discard, true, false, new LLAvatarTexData(getID(), type), NULL); + } + } + tex->setMinDiscardLevel(desired_discard); + } + } + local_tex_obj->setImage(tex); + local_tex_obj->setID(tex->getID()); + setBakedReady(type,baked_version_ready,index); +} + +//virtual +void LLVOAvatarSelf::setBakedReady(LLAvatarAppearanceDefines::ETextureIndex type, bool baked_version_exists, U32 index) +{ + if (!isIndexLocalTexture(type)) return; + LLLocalTextureObject *local_tex_obj = getLocalTextureObject(type,index); + if (local_tex_obj) + { + local_tex_obj->setBakedReady( baked_version_exists ); + } +} + + +// virtual +void LLVOAvatarSelf::dumpLocalTextures() const +{ + LL_INFOS() << "Local Textures:" << LL_ENDL; + + /* ETextureIndex baked_equiv[] = { + TEX_UPPER_BAKED, + if (isTextureDefined(baked_equiv[i])) */ + for (LLAvatarAppearanceDictionary::Textures::const_iterator iter = sAvatarDictionary->getTextures().begin(); + iter != sAvatarDictionary->getTextures().end(); + ++iter) + { + const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = iter->second; + if (!texture_dict->mIsLocalTexture || !texture_dict->mIsUsedByBakedTexture) + continue; + + const EBakedTextureIndex baked_index = texture_dict->mBakedTextureIndex; + const ETextureIndex baked_equiv = sAvatarDictionary->getBakedTexture(baked_index)->mTextureIndex; + + const std::string &name = texture_dict->mName; + const LLLocalTextureObject *local_tex_obj = getLocalTextureObject(iter->first, 0); + // index is baked texture - index is not relevant. putting in 0 as placeholder + if (isTextureDefined(baked_equiv, 0)) + { +#if LL_RELEASE_FOR_DOWNLOAD + // End users don't get to trivially see avatar texture IDs, makes textures + // easier to steal. JC + LL_INFOS() << "LocTex " << name << ": Baked " << LL_ENDL; +#else + LL_INFOS() << "LocTex " << name << ": Baked " << getTEImage(baked_equiv)->getID() << LL_ENDL; +#endif + } + else if (local_tex_obj && local_tex_obj->getImage() != NULL) + { + if (local_tex_obj->getImage()->getID() == IMG_DEFAULT_AVATAR) + { + LL_INFOS() << "LocTex " << name << ": None" << LL_ENDL; + } + else + { + LLViewerFetchedTexture* image = dynamic_cast( local_tex_obj->getImage() ); + + LL_INFOS() << "LocTex " << name << ": " + << "Discard " << image->getDiscardLevel() << ", " + << "(" << image->getWidth() << ", " << image->getHeight() << ") " +#if !LL_RELEASE_FOR_DOWNLOAD + // End users don't get to trivially see avatar texture IDs, + // makes textures easier to steal + << image->getID() << " " +#endif + << "Priority: " << image->getMaxVirtualSize() + << LL_ENDL; + } + } + else + { + LL_INFOS() << "LocTex " << name << ": No LLViewerTexture" << LL_ENDL; + } + } +} + +//----------------------------------------------------------------------------- +// static +// onLocalTextureLoaded() +//----------------------------------------------------------------------------- + +void LLVOAvatarSelf::onLocalTextureLoaded(bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src_raw, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata) +{ + LLAvatarTexData *data = (LLAvatarTexData *)userdata; + LLVOAvatarSelf *self = (LLVOAvatarSelf *)gObjectList.findObject(data->mAvatarID); + if (self) + { + // We should only be handling local textures for ourself + self->localTextureLoaded(success, src_vi, src_raw, aux_src, discard_level, final, userdata); + } + // ensure data is cleaned up + if (final || !success) + { + delete data; + } +} + +/*virtual*/ void LLVOAvatarSelf::setImage(const U8 te, LLViewerTexture *imagep, const U32 index) +{ + if (isIndexLocalTexture((ETextureIndex)te)) + { + setLocalTexture((ETextureIndex)te, imagep, false ,index); + } + else + { + setTEImage(te,imagep); + } +} + +/*virtual*/ LLViewerTexture* LLVOAvatarSelf::getImage(const U8 te, const U32 index) const +{ + if (isIndexLocalTexture((ETextureIndex)te)) + { + return getLocalTextureGL((ETextureIndex)te,index); + } + else + { + return getTEImage(te); + } +} + + +// static +void LLVOAvatarSelf::dumpTotalLocalTextureByteCount() +{ + S32 gl_bytes = 0; + gAgentAvatarp->getLocalTextureByteCount(&gl_bytes); + LL_INFOS() << "Total Avatar LocTex GL:" << (gl_bytes/1024) << "KB" << LL_ENDL; +} + +bool LLVOAvatarSelf::getIsCloud() const +{ + // Let people know why they're clouded without spamming them into oblivion. + bool do_warn = false; + static LLTimer time_since_notice; + F32 update_freq = 30.0; + if (time_since_notice.getElapsedTimeF32() > update_freq) + { + time_since_notice.reset(); + do_warn = true; + } + + // do we have our body parts? + S32 shape_count = gAgentWearables.getWearableCount(LLWearableType::WT_SHAPE); + S32 hair_count = gAgentWearables.getWearableCount(LLWearableType::WT_HAIR); + S32 eye_count = gAgentWearables.getWearableCount(LLWearableType::WT_EYES); + S32 skin_count = gAgentWearables.getWearableCount(LLWearableType::WT_SKIN); + if (!shape_count || !hair_count || !eye_count || !skin_count) + { + if (do_warn) + { + LL_INFOS() << "Self is clouded due to missing one or more required body parts: " + << (shape_count ? "" : "SHAPE ") + << (hair_count ? "" : "HAIR ") + << (eye_count ? "" : "EYES ") + << (skin_count ? "" : "SKIN ") + << LL_ENDL; + } + return true; + } + + if (!isTextureDefined(TEX_HAIR, 0)) + { + if (do_warn) + { + LL_INFOS() << "Self is clouded because of no hair texture" << LL_ENDL; + } + return true; + } + + if (!mPreviousFullyLoaded) + { + if (!isLocalTextureDataAvailable(getLayerSet(BAKED_LOWER)) && + (!isTextureDefined(TEX_LOWER_BAKED, 0))) + { + if (do_warn) + { + LL_INFOS() << "Self is clouded because lower textures not baked" << LL_ENDL; + } + return true; + } + + if (!isLocalTextureDataAvailable(getLayerSet(BAKED_UPPER)) && + (!isTextureDefined(TEX_UPPER_BAKED, 0))) + { + if (do_warn) + { + LL_INFOS() << "Self is clouded because upper textures not baked" << LL_ENDL; + } + return true; + } + + for (U32 i = 0; i < mBakedTextureDatas.size(); i++) + { + if (i == BAKED_SKIRT && !isWearingWearableType(LLWearableType::WT_SKIRT)) + continue; + + const BakedTextureData& texture_data = mBakedTextureDatas[i]; + if (!isTextureDefined(texture_data.mTextureIndex, 0)) + continue; + + // Check for the case that texture is defined but not sufficiently loaded to display anything. + const LLViewerTexture* baked_img = getImage( texture_data.mTextureIndex, 0 ); + if (!baked_img || !baked_img->hasGLTexture()) + { + if (do_warn) + { + LL_INFOS() << "Self is clouded because texture at index " << i + << " (texture index is " << texture_data.mTextureIndex << ") is not loaded" << LL_ENDL; + } + return true; + } + } + + LL_DEBUGS() << "Avatar de-clouded" << LL_ENDL; + } + return false; +} + +/*static*/ +void LLVOAvatarSelf::debugOnTimingLocalTexLoaded(bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata) +{ + if (gAgentAvatarp.notNull()) + { + gAgentAvatarp->debugTimingLocalTexLoaded(success, src_vi, src, aux_src, discard_level, final, userdata); + } +} + +void LLVOAvatarSelf::debugTimingLocalTexLoaded(bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata) +{ + LLAvatarTexData *data = (LLAvatarTexData *)userdata; + if (!data) + { + return; + } + + ETextureIndex index = data->mIndex; + + if (index < 0 || index >= TEX_NUM_INDICES) + { + return; + } + + if (discard_level >=0 && discard_level <= MAX_DISCARD_LEVEL) // ignore discard level -1, as it means we have no data. + { + mDebugTextureLoadTimes[(U32)index][(U32)discard_level] = mDebugSelfLoadTimer.getElapsedTimeF32(); + } + if (final) + { + delete data; + } +} + +void LLVOAvatarSelf::debugBakedTextureUpload(EBakedTextureIndex index, bool finished) +{ + U32 done = 0; + if (finished) + { + done = 1; + } + mDebugBakedTextureTimes[index][done] = mDebugSelfLoadTimer.getElapsedTimeF32(); +} + +const std::string LLVOAvatarSelf::verboseDebugDumpLocalTextureDataInfo(const LLViewerTexLayerSet* layerset) const +{ + std::ostringstream outbuf; + LLWearableType *wr_inst = LLWearableType::getInstance(); + for (LLAvatarAppearanceDictionary::BakedTextures::const_iterator baked_iter = + sAvatarDictionary->getBakedTextures().begin(); + baked_iter != sAvatarDictionary->getBakedTextures().end(); + ++baked_iter) + { + const EBakedTextureIndex baked_index = baked_iter->first; + if (layerset == mBakedTextureDatas[baked_index].mTexLayerSet) + { + outbuf << "baked_index: " << baked_index << "\n"; + const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = baked_iter->second; + for (texture_vec_t::const_iterator local_tex_iter = baked_dict->mLocalTextures.begin(); + local_tex_iter != baked_dict->mLocalTextures.end(); + ++local_tex_iter) + { + const ETextureIndex tex_index = *local_tex_iter; + const std::string tex_name = sAvatarDictionary->getTexture(tex_index)->mName; + outbuf << " tex_index " << (S32) tex_index << " name " << tex_name << "\n"; + const LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(tex_index); + const U32 wearable_count = gAgentWearables.getWearableCount(wearable_type); + if (wearable_count > 0) + { + for (U32 wearable_index = 0; wearable_index < wearable_count; wearable_index++) + { + outbuf << " " << wr_inst->getTypeName(wearable_type) << " " << wearable_index << ":"; + const LLLocalTextureObject *local_tex_obj = getLocalTextureObject(tex_index, wearable_index); + if (local_tex_obj) + { + LLViewerFetchedTexture* image = dynamic_cast( local_tex_obj->getImage() ); + if (tex_index >= 0 + && local_tex_obj->getID() != IMG_DEFAULT_AVATAR + && !image->isMissingAsset()) + { + outbuf << " id: " << image->getID() + << " refs: " << image->getNumRefs() + << " glocdisc: " << getLocalDiscardLevel(tex_index, wearable_index) + << " discard: " << image->getDiscardLevel() + << " desired: " << image->getDesiredDiscardLevel() + << " vsize: " << image->getMaxVirtualSize() + << " ts: " << image->getTextureState() + << " bl: " << image->getBoostLevel() + << " fl: " << image->isFullyLoaded() // this is not an accessor for mFullyLoaded - see comment there. + << " cl: " << (image->isFullyLoaded() && image->getDiscardLevel()==0) // "completely loaded" + << " mvs: " << image->getMaxVirtualSize() + << " mvsc: " << image->getMaxVirtualSizeResetCounter() + << " mem: " << image->getTextureMemory(); + } + } + outbuf << "\n"; + } + } + } + break; + } + } + return outbuf.str(); +} + +void LLVOAvatarSelf::dumpAllTextures() const +{ + std::string vd_text = "Local textures per baked index and wearable:\n"; + for (LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::BakedTextures::const_iterator baked_iter = sAvatarDictionary->getBakedTextures().begin(); + baked_iter != sAvatarDictionary->getBakedTextures().end(); + ++baked_iter) + { + const LLAvatarAppearanceDefines::EBakedTextureIndex baked_index = baked_iter->first; + const LLViewerTexLayerSet *layerset = debugGetLayerSet(baked_index); + if (!layerset) continue; + const LLViewerTexLayerSetBuffer *layerset_buffer = layerset->getViewerComposite(); + if (!layerset_buffer) continue; + vd_text += verboseDebugDumpLocalTextureDataInfo(layerset); + } + LL_DEBUGS("Avatar") << vd_text << LL_ENDL; +} + +const std::string LLVOAvatarSelf::debugDumpLocalTextureDataInfo(const LLViewerTexLayerSet* layerset) const +{ + std::string text=""; + LLWearableType *wr_inst = LLWearableType::getInstance(); + + text = llformat("[Final:%d Avail:%d] ",isLocalTextureDataFinal(layerset), isLocalTextureDataAvailable(layerset)); + + /* if (layerset == mBakedTextureDatas[BAKED_HEAD].mTexLayerSet) + return getLocalDiscardLevel(TEX_HEAD_BODYPAINT) >= 0; */ + for (LLAvatarAppearanceDictionary::BakedTextures::const_iterator baked_iter = sAvatarDictionary->getBakedTextures().begin(); + baked_iter != sAvatarDictionary->getBakedTextures().end(); + ++baked_iter) + { + const EBakedTextureIndex baked_index = baked_iter->first; + if (layerset == mBakedTextureDatas[baked_index].mTexLayerSet) + { + const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = baked_iter->second; + text += llformat("%d-%s ( ",baked_index, baked_dict->mName.c_str()); + for (texture_vec_t::const_iterator local_tex_iter = baked_dict->mLocalTextures.begin(); + local_tex_iter != baked_dict->mLocalTextures.end(); + ++local_tex_iter) + { + const ETextureIndex tex_index = *local_tex_iter; + const LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(tex_index); + const U32 wearable_count = gAgentWearables.getWearableCount(wearable_type); + if (wearable_count > 0) + { + text += wr_inst->getTypeName(wearable_type) + ":"; + for (U32 wearable_index = 0; wearable_index < wearable_count; wearable_index++) + { + const U32 discard_level = getLocalDiscardLevel(tex_index, wearable_index); + std::string discard_str = llformat("%d ",discard_level); + text += llformat("%d ",discard_level); + } + } + } + text += ")"; + break; + } + } + return text; +} + +const std::string LLVOAvatarSelf::debugDumpAllLocalTextureDataInfo() const +{ + std::string text; + const U32 override_tex_discard_level = gSavedSettings.getU32("TextureDiscardLevel"); + + for (U32 i = 0; i < mBakedTextureDatas.size(); i++) + { + const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = sAvatarDictionary->getBakedTexture((EBakedTextureIndex)i); + bool is_texture_final = true; + for (texture_vec_t::const_iterator local_tex_iter = baked_dict->mLocalTextures.begin(); + local_tex_iter != baked_dict->mLocalTextures.end(); + ++local_tex_iter) + { + const ETextureIndex tex_index = *local_tex_iter; + const LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(tex_index); + const U32 wearable_count = gAgentWearables.getWearableCount(wearable_type); + for (U32 wearable_index = 0; wearable_index < wearable_count; wearable_index++) + { + is_texture_final &= (getLocalDiscardLevel(*local_tex_iter, wearable_index) <= (S32)(override_tex_discard_level)); + } + } + text += llformat("%s:%d ",baked_dict->mName.c_str(),is_texture_final); + } + return text; +} + +void LLVOAvatarSelf::appearanceChangeMetricsCoro(std::string url) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("appearanceChangeMetrics", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); + + S32 currentSequence = mMetricSequence; + if (S32_MAX == ++mMetricSequence) + mMetricSequence = 0; + + LLSD msg; + msg["message"] = "ViewerAppearanceChangeMetrics"; + msg["session_id"] = gAgentSessionID; + msg["agent_id"] = gAgentID; + msg["sequence"] = currentSequence; + msg["initial"] = mInitialMetric; + msg["break"] = false; + msg["duration"] = mTimeSinceLastRezMessage.getElapsedTimeF32(); + + // Status of our own rezzing. + msg["rez_status"] = LLVOAvatar::rezStatusToString(getRezzedStatus()); + msg["first_decloud_time"] = getFirstDecloudTime(); + + // Status of all nearby avs including ourself. + msg["nearby"] = LLSD::emptyArray(); + std::vector rez_counts; + F32 avg_time; + S32 total_cloud_avatars; + LLVOAvatar::getNearbyRezzedStats(rez_counts, avg_time, total_cloud_avatars); + for (S32 rez_stat = 0; rez_stat < rez_counts.size(); ++rez_stat) + { + std::string rez_status_name = LLVOAvatar::rezStatusToString(rez_stat); + msg["nearby"][rez_status_name] = rez_counts[rez_stat]; + } + msg["nearby"]["avg_decloud_time"] = avg_time; + msg["nearby"]["cloud_total"] = total_cloud_avatars; + + // std::vector bucket_fields("timer_name","is_self","grid_x","grid_y","is_using_server_bake"); + std::vector by_fields; + by_fields.push_back("timer_name"); + by_fields.push_back("completed"); + by_fields.push_back("grid_x"); + by_fields.push_back("grid_y"); + by_fields.push_back("is_using_server_bakes"); + by_fields.push_back("is_self"); + by_fields.push_back("central_bake_version"); + LLSD summary = summarize_by_buckets(mPendingTimerRecords, by_fields, std::string("elapsed")); + msg["timers"] = summary; + + mPendingTimerRecords.clear(); + + LL_DEBUGS("Avatar") << avString() << "message: " << ll_pretty_print_sd(msg) << LL_ENDL; + + gPendingMetricsUploads++; + + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, msg); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + gPendingMetricsUploads--; + + if (!status) + { + LL_WARNS("Avatar") << "Unable to upload statistics" << LL_ENDL; + return; + } + else + { + LL_INFOS("Avatar") << "Statistics upload OK" << LL_ENDL; + mInitialMetric = false; + } +} + +bool LLVOAvatarSelf::updateAvatarRezMetrics(bool force_send) +{ + const F32 AV_METRICS_INTERVAL_QA = 30.0; + F32 send_period = 300.0; + + static LLCachedControl qa_mode_metrics(gSavedSettings,"QAModeMetrics"); + if (qa_mode_metrics) + { + send_period = AV_METRICS_INTERVAL_QA; + } + + if (force_send || mTimeSinceLastRezMessage.getElapsedTimeF32() > send_period) + { + // Stats for completed phases have been getting logged as they + // complete. This will give us stats for any timers that + // haven't finished as of the metric's being sent. + + if (force_send) + { + LLVOAvatar::logPendingPhasesAllAvatars(); + } + sendViewerAppearanceChangeMetrics(); + } + + return false; +} + +void LLVOAvatarSelf::addMetricsTimerRecord(const LLSD& record) +{ + mPendingTimerRecords.push_back(record); +} + +bool operator<(const LLSD& a, const LLSD& b) +{ + std::ostringstream aout, bout; + aout << LLSDNotationStreamer(a); + bout << LLSDNotationStreamer(b); + std::string astring = aout.str(); + std::string bstring = bout.str(); + + return astring < bstring; + +} + +// Given a vector of LLSD records, return an LLSD array of bucketed stats for val_field. +LLSD summarize_by_buckets(std::vector in_records, + std::vector by_fields, + std::string val_field) +{ + LLSD result = LLSD::emptyArray(); + std::map accum; + for (std::vector::iterator in_record_iter = in_records.begin(); + in_record_iter != in_records.end(); ++in_record_iter) + { + LLSD& record = *in_record_iter; + LLSD key; + for (std::vector::iterator field_iter = by_fields.begin(); + field_iter != by_fields.end(); ++field_iter) + { + const std::string& field = *field_iter; + key[field] = record[field]; + } + LLViewerStats::StatsAccumulator& stats = accum[key]; + F32 value = record[val_field].asReal(); + stats.push(value); + } + for (std::map::iterator accum_it = accum.begin(); + accum_it != accum.end(); ++accum_it) + { + LLSD out_record = accum_it->first; + out_record["stats"] = accum_it->second.asLLSD(); + result.append(out_record); + } + return result; +} + +void LLVOAvatarSelf::sendViewerAppearanceChangeMetrics() +{ + std::string caps_url; + if (getRegion()) + { + // runway - change here to activate. + caps_url = getRegion()->getCapability("ViewerMetrics"); + } + if (!caps_url.empty()) + { + + LLCoros::instance().launch("LLVOAvatarSelf::appearanceChangeMetricsCoro", + boost::bind(&LLVOAvatarSelf::appearanceChangeMetricsCoro, this, caps_url)); + mTimeSinceLastRezMessage.reset(); + } +} + +const LLUUID& LLVOAvatarSelf::grabBakedTexture(EBakedTextureIndex baked_index) const +{ + if (canGrabBakedTexture(baked_index)) + { + ETextureIndex tex_index = sAvatarDictionary->bakedToLocalTextureIndex(baked_index); + if (tex_index == TEX_NUM_INDICES) + { + return LLUUID::null; + } + return getTEImage( tex_index )->getID(); + } + return LLUUID::null; +} + +bool LLVOAvatarSelf::canGrabBakedTexture(EBakedTextureIndex baked_index) const +{ + ETextureIndex tex_index = sAvatarDictionary->bakedToLocalTextureIndex(baked_index); + if (tex_index == TEX_NUM_INDICES) + { + return false; + } + // Check if the texture hasn't been baked yet. + if (!isTextureDefined(tex_index, 0)) + { + LL_DEBUGS() << "getTEImage( " << (U32) tex_index << " )->getID() == IMG_DEFAULT_AVATAR" << LL_ENDL; + return false; + } + + if (gAgent.isGodlikeWithoutAdminMenuFakery()) + return true; + + // Check permissions of textures that show up in the + // baked texture. We don't want people copying people's + // work via baked textures. + + const LLAvatarAppearanceDictionary::BakedEntry *baked_dict = sAvatarDictionary->getBakedTexture(baked_index); + for (texture_vec_t::const_iterator iter = baked_dict->mLocalTextures.begin(); + iter != baked_dict->mLocalTextures.end(); + ++iter) + { + const ETextureIndex t_index = (*iter); + LLWearableType::EType wearable_type = sAvatarDictionary->getTEWearableType(t_index); + U32 count = gAgentWearables.getWearableCount(wearable_type); + LL_DEBUGS() << "Checking index " << (U32) t_index << " count: " << count << LL_ENDL; + + for (U32 wearable_index = 0; wearable_index < count; ++wearable_index) + { + LLViewerWearable *wearable = gAgentWearables.getViewerWearable(wearable_type, wearable_index); + if (wearable) + { + const LLLocalTextureObject *texture = wearable->getLocalTextureObject((S32)t_index); + const LLUUID& texture_id = texture->getID(); + if (texture_id != IMG_DEFAULT_AVATAR) + { + // Search inventory for this texture. + LLViewerInventoryCategory::cat_array_t cats; + LLViewerInventoryItem::item_array_t items; + LLAssetIDMatches asset_id_matches(texture_id); + gInventory.collectDescendentsIf(LLUUID::null, + cats, + items, + LLInventoryModel::INCLUDE_TRASH, + asset_id_matches); + + bool can_grab = false; + LL_DEBUGS() << "item count for asset " << texture_id << ": " << items.size() << LL_ENDL; + if (items.size()) + { + // search for full permissions version + for (S32 i = 0; i < items.size(); i++) + { + LLViewerInventoryItem* itemp = items[i]; + if (itemp->getIsFullPerm()) + { + can_grab = true; + break; + } + } + } + if (!can_grab) return false; + } + } + } + } + + return true; +} + +void LLVOAvatarSelf::addLocalTextureStats( ETextureIndex type, LLViewerFetchedTexture* imagep, + F32 texel_area_ratio, bool render_avatar, bool covered_by_baked) +{ + if (!isIndexLocalTexture(type)) return; + + // Sunshine - ignoring covered_by_baked will force local textures + // to always load. Fix for SH-4001 and many related issues. Do + // not restore this without some more targetted fix for the local + // textures failing to load issue. + //if (!covered_by_baked) + { + if (imagep->getID() != IMG_DEFAULT_AVATAR) + { + imagep->setNoDelete(); + if (imagep->getDiscardLevel() != 0) + { + F32 desired_pixels; + desired_pixels = llmin(mPixelArea, (F32)getTexImageArea()); + + imagep->setBoostLevel(getAvatarBoostLevel()); + imagep->resetTextureStats(); + imagep->setMaxVirtualSizeResetInterval(MAX_TEXTURE_VIRTUAL_SIZE_RESET_INTERVAL); + imagep->addTextureStats( desired_pixels / texel_area_ratio ); + imagep->forceUpdateBindStats() ; + if (imagep->getDiscardLevel() < 0) + { + mHasGrey = true; // for statistics gathering + } + } + } + else + { + // texture asset is missing + mHasGrey = true; // for statistics gathering + } + } +} + +LLLocalTextureObject* LLVOAvatarSelf::getLocalTextureObject(LLAvatarAppearanceDefines::ETextureIndex i, U32 wearable_index) const +{ + LLWearableType::EType type = sAvatarDictionary->getTEWearableType(i); + LLViewerWearable* wearable = gAgentWearables.getViewerWearable(type, wearable_index); + if (wearable) + { + return wearable->getLocalTextureObject(i); + } + + return NULL; +} + +//----------------------------------------------------------------------------- +// getBakedTE() +// Used by the LayerSet. (Layer sets don't in general know what textures depend on them.) +//----------------------------------------------------------------------------- +ETextureIndex LLVOAvatarSelf::getBakedTE( const LLViewerTexLayerSet* layerset ) const +{ + for (U32 i = 0; i < mBakedTextureDatas.size(); i++) + { + if (layerset == mBakedTextureDatas[i].mTexLayerSet ) + { + return mBakedTextureDatas[i].mTextureIndex; + } + } + llassert(0); + return TEX_HEAD_BAKED; +} + +// FIXME: This is not called consistently. Something may be broken. +void LLVOAvatarSelf::outputRezDiagnostics() const +{ + if(!gSavedSettings.getBOOL("DebugAvatarLocalTexLoadedTime")) + { + return ; + } + + const F32 final_time = mDebugSelfLoadTimer.getElapsedTimeF32(); + LL_DEBUGS("Avatar") << "REZTIME: Myself rez stats:" << LL_ENDL; + LL_DEBUGS("Avatar") << "\t Time from avatar creation to load wearables: " << (S32)mDebugTimeWearablesLoaded << LL_ENDL; + LL_DEBUGS("Avatar") << "\t Time from avatar creation to de-cloud: " << (S32)mDebugTimeAvatarVisible << LL_ENDL; + LL_DEBUGS("Avatar") << "\t Time from avatar creation to de-cloud for others: " << (S32)final_time << LL_ENDL; + LL_DEBUGS("Avatar") << "\t Load time for each texture: " << LL_ENDL; + for (U32 i = 0; i < LLAvatarAppearanceDefines::TEX_NUM_INDICES; ++i) + { + std::stringstream out; + out << "\t\t (" << i << ") "; + U32 j=0; + for (j=0; j <= MAX_DISCARD_LEVEL; j++) + { + out << "\t"; + S32 load_time = (S32)mDebugTextureLoadTimes[i][j]; + if (load_time == -1) + { + out << "*"; + if (j == 0) + break; + } + else + { + out << load_time; + } + } + + // Don't print out non-existent textures. + if (j != 0) + { + LL_DEBUGS("Avatar") << out.str() << LL_ENDL; + } + } + LL_DEBUGS("Avatar") << "\t Time points for each upload (start / finish)" << LL_ENDL; + for (U32 i = 0; i < LLAvatarAppearanceDefines::BAKED_NUM_INDICES; ++i) + { + LL_DEBUGS("Avatar") << "\t\t (" << i << ") \t" << (S32)mDebugBakedTextureTimes[i][0] << " / " << (S32)mDebugBakedTextureTimes[i][1] << LL_ENDL; + } + + for (LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::BakedTextures::const_iterator baked_iter = sAvatarDictionary->getBakedTextures().begin(); + baked_iter != sAvatarDictionary->getBakedTextures().end(); + ++baked_iter) + { + const LLAvatarAppearanceDefines::EBakedTextureIndex baked_index = baked_iter->first; + const LLViewerTexLayerSet *layerset = debugGetLayerSet(baked_index); + if (!layerset) continue; + const LLViewerTexLayerSetBuffer *layerset_buffer = layerset->getViewerComposite(); + if (!layerset_buffer) continue; + LL_DEBUGS("Avatar") << layerset_buffer->dumpTextureInfo() << LL_ENDL; + } + + dumpAllTextures(); +} + +void LLVOAvatarSelf::outputRezTiming(const std::string& msg) const +{ + LL_DEBUGS("Avatar") + << avString() + << llformat("%s. Time from avatar creation: %.2f", msg.c_str(), mDebugSelfLoadTimer.getElapsedTimeF32()) + << LL_ENDL; +} + +void LLVOAvatarSelf::reportAvatarRezTime() const +{ + // TODO: report mDebugSelfLoadTimer.getElapsedTimeF32() somehow. +} + +// SUNSHINE CLEANUP - not clear we need any of this, may be sufficient to request server appearance in llviewermenu.cpp:handle_rebake_textures() +void LLVOAvatarSelf::forceBakeAllTextures(bool slam_for_debug) +{ + LL_INFOS() << "TAT: forced full rebake. " << LL_ENDL; + + for (U32 i = 0; i < mBakedTextureDatas.size(); i++) + { + ETextureIndex baked_index = mBakedTextureDatas[i].mTextureIndex; + LLViewerTexLayerSet* layer_set = getLayerSet(baked_index); + if (layer_set) + { + if (slam_for_debug) + { + layer_set->setUpdatesEnabled(true); + } + + invalidateComposite(layer_set); + add(LLStatViewer::TEX_REBAKES, 1); + } + else + { + LL_WARNS() << "TAT: NO LAYER SET FOR " << (S32)baked_index << LL_ENDL; + } + } + + // Don't know if this is needed + updateMeshTextures(); +} + +//----------------------------------------------------------------------------- +// requestLayerSetUpdate() +//----------------------------------------------------------------------------- +void LLVOAvatarSelf::requestLayerSetUpdate(ETextureIndex index ) +{ + /* switch(index) + case LOCTEX_UPPER_BODYPAINT: + case LOCTEX_UPPER_SHIRT: + if( mUpperBodyLayerSet ) + mUpperBodyLayerSet->requestUpdate(); */ + const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = sAvatarDictionary->getTexture(index); + if (!texture_dict) + return; + if (!texture_dict->mIsLocalTexture || !texture_dict->mIsUsedByBakedTexture) + return; + const EBakedTextureIndex baked_index = texture_dict->mBakedTextureIndex; + if (mBakedTextureDatas[baked_index].mTexLayerSet) + { + mBakedTextureDatas[baked_index].mTexLayerSet->requestUpdate(); + } +} + +LLViewerTexLayerSet* LLVOAvatarSelf::getLayerSet(ETextureIndex index) const +{ + /* switch(index) + case TEX_HEAD_BAKED: + case TEX_HEAD_BODYPAINT: + return mHeadLayerSet; */ + const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = sAvatarDictionary->getTexture(index); + if (texture_dict && texture_dict->mIsUsedByBakedTexture) + { + const EBakedTextureIndex baked_index = texture_dict->mBakedTextureIndex; + return getLayerSet(baked_index); + } + return NULL; +} + +LLViewerTexLayerSet* LLVOAvatarSelf::getLayerSet(EBakedTextureIndex baked_index) const +{ + /* switch(index) + case TEX_HEAD_BAKED: + case TEX_HEAD_BODYPAINT: + return mHeadLayerSet; */ + if (baked_index >= 0 && baked_index < BAKED_NUM_INDICES) + { + return getTexLayerSet(baked_index); + } + return NULL; +} + + + + +// static +void LLVOAvatarSelf::onCustomizeStart(bool disable_camera_switch) +{ + if (isAgentAvatarValid()) + { + LLUIUsage::instance().logCommand("Avatar.CustomizeStart"); + if (!gAgentAvatarp->mEndCustomizeCallback.get()) + { + gAgentAvatarp->mEndCustomizeCallback = new LLUpdateAppearanceOnDestroy; + } + + gAgentAvatarp->mIsEditingAppearance = true; + gAgentAvatarp->mUseLocalAppearance = true; + + if (gSavedSettings.getBOOL("AppearanceCameraMovement") && !disable_camera_switch) + { + gAgentCamera.changeCameraToCustomizeAvatar(); + } + + gAgentAvatarp->invalidateAll(); // mark all bakes as dirty, request updates + gAgentAvatarp->updateMeshTextures(); // make sure correct textures are applied to the avatar mesh. + gAgentAvatarp->updateTextures(); // call updateTextureStats + } +} + +// static +void LLVOAvatarSelf::onCustomizeEnd(bool disable_camera_switch) +{ + + if (isAgentAvatarValid()) + { + gAgentAvatarp->mIsEditingAppearance = false; + gAgentAvatarp->invalidateAll(); + + if (gSavedSettings.getBOOL("AppearanceCameraMovement") && !disable_camera_switch) + { + gAgentCamera.changeCameraToDefault(); + gAgentCamera.resetView(); + } + + // Dereferencing the previous callback will cause + // updateAppearanceFromCOF to be called, whenever all refs + // have resolved. + gAgentAvatarp->mEndCustomizeCallback = NULL; + } +} + +// virtual +bool LLVOAvatarSelf::shouldRenderRigged() const +{ + return gAgent.needsRenderAvatar(); +} + +// HACK: this will null out the avatar's local texture IDs before the TE message is sent +// to ensure local texture IDs are not sent to other clients in the area. +// this is a short-term solution. The long term solution will be to not set the texture +// IDs in the avatar object, and keep them only in the wearable. +// This will involve further refactoring that is too risky for the initial release of 2.0. +bool LLVOAvatarSelf::sendAppearanceMessage(LLMessageSystem *mesgsys) const +{ + LLUUID texture_id[TEX_NUM_INDICES]; + // pack away current TEs to make sure we don't send them out + for (LLAvatarAppearanceDictionary::Textures::const_iterator iter = sAvatarDictionary->getTextures().begin(); + iter != sAvatarDictionary->getTextures().end(); + ++iter) + { + const ETextureIndex index = iter->first; + const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = iter->second; + if (!texture_dict->mIsBakedTexture) + { + LLTextureEntry* entry = getTE((U8) index); + texture_id[index] = entry->getID(); + entry->setID(IMG_DEFAULT_AVATAR); + } + } + + bool success = packTEMessage(mesgsys); + + // unpack TEs to make sure we don't re-trigger a bake + for (LLAvatarAppearanceDictionary::Textures::const_iterator iter = sAvatarDictionary->getTextures().begin(); + iter != sAvatarDictionary->getTextures().end(); + ++iter) + { + const ETextureIndex index = iter->first; + const LLAvatarAppearanceDictionary::TextureEntry *texture_dict = iter->second; + if (!texture_dict->mIsBakedTexture) + { + LLTextureEntry* entry = getTE((U8) index); + entry->setID(texture_id[index]); + } + } + + return success; +} + +//------------------------------------------------------------------------ +// sendHoverHeight() +//------------------------------------------------------------------------ +void LLVOAvatarSelf::sendHoverHeight() const +{ + std::string url = gAgent.getRegionCapability("AgentPreferences"); + + if (!url.empty()) + { + LLSD update = LLSD::emptyMap(); + const LLVector3& hover_offset = getHoverOffset(); + update["hover_height"] = hover_offset[2]; + + LL_DEBUGS("Avatar") << avString() << "sending hover height value " << hover_offset[2] << LL_ENDL; + + // *TODO: - this class doesn't really do anything, could just use a base + // class responder if nothing else gets added. + // (comment from removed Responder) + LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, update, + "Hover height sent to sim", "Hover height not sent to sim"); + mLastHoverOffsetSent = hover_offset; + } +} + +void LLVOAvatarSelf::setHoverOffset(const LLVector3& hover_offset, bool send_update) +{ + if (getHoverOffset() != hover_offset) + { + LL_INFOS("Avatar") << avString() << " setting hover due to change " << hover_offset[2] << LL_ENDL; + LLVOAvatar::setHoverOffset(hover_offset, send_update); + } + if (send_update && (hover_offset != mLastHoverOffsetSent)) + { + LL_INFOS("Avatar") << avString() << " sending hover due to change " << hover_offset[2] << LL_ENDL; + sendHoverHeight(); + } +} + +//------------------------------------------------------------------------ +// needsRenderBeam() +//------------------------------------------------------------------------ +bool LLVOAvatarSelf::needsRenderBeam() +{ + LLTool *tool = LLToolMgr::getInstance()->getCurrentTool(); + + bool is_touching_or_grabbing = (tool == LLToolGrab::getInstance() && LLToolGrab::getInstance()->isEditing()); + LLViewerObject* objp = LLToolGrab::getInstance()->getEditingObject(); + if (objp // might need to be "!objp ||" instead of "objp &&". + && (objp->isAttachment() || objp->isAvatar())) + { + // don't render grab tool's selection beam on hud objects, + // attachments or avatars + is_touching_or_grabbing = false; + } + return is_touching_or_grabbing || (getAttachmentState() & AGENT_STATE_EDITING && LLSelectMgr::getInstance()->shouldShowSelection()); +} + +// static +void LLVOAvatarSelf::deleteScratchTextures() +{ + for(std::map< LLGLenum, LLGLuint*>::iterator it = sScratchTexNames.begin(), end_it = sScratchTexNames.end(); + it != end_it; + ++it) + { + LLImageGL::deleteTextures(1, (U32 *)it->second ); + stop_glerror(); + } + + if( sScratchTexBytes.value() ) + { + LL_DEBUGS() << "Clearing Scratch Textures " << (S32Kilobytes)sScratchTexBytes << LL_ENDL; + + delete_and_clear(sScratchTexNames); + sScratchTexBytes = S32Bytes(0); + } +} + +// static +void LLVOAvatarSelf::dumpScratchTextureByteCount() +{ + LL_INFOS() << "Scratch Texture GL: " << (sScratchTexBytes/1024) << "KB" << LL_ENDL; +} + +void LLVOAvatarSelf::dumpWearableInfo(LLAPRFile& outfile) +{ + apr_file_t* file = outfile.getFileHandle(); + if (!file) + { + return; + } + + + apr_file_printf( file, "\n\n" ); + + LLWearableData *wd = getWearableData(); + LLWearableType *wr_inst = LLWearableType::getInstance(); + for (S32 type = 0; type < LLWearableType::WT_COUNT; type++) + { + const std::string& type_name = wr_inst->getTypeName((LLWearableType::EType)type); + for (U32 j=0; j< wd->getWearableCount((LLWearableType::EType)type); j++) + { + LLViewerWearable *wearable = gAgentWearables.getViewerWearable((LLWearableType::EType)type,j); + apr_file_printf( file, "\n\t \n", + type_name.c_str(), wearable->getName().c_str() ); + LLWearable::visual_param_vec_t v_params; + wearable->getVisualParams(v_params); + for (LLWearable::visual_param_vec_t::iterator it = v_params.begin(); + it != v_params.end(); ++it) + { + LLVisualParam *param = *it; + dump_visual_param(file, param, param->getWeight()); + } + } + } + apr_file_printf( file, "\n\n" ); +} diff --git a/indra/newview/llvoavatarself.h b/indra/newview/llvoavatarself.h index 4bc414785e..77556c8412 100644 --- a/indra/newview/llvoavatarself.h +++ b/indra/newview/llvoavatarself.h @@ -1,411 +1,411 @@ -/** - * @file llvoavatarself.h - * @brief Declaration of LLVOAvatar class which is a derivation fo - * LLViewerObject - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVOAVATARSELF_H -#define LL_LLVOAVATARSELF_H - -#include "llviewertexture.h" -#include "llvoavatar.h" -#include -#include "lleventcoro.h" -#include "llcoros.h" - -struct LocalTextureData; -class LLInventoryCallback; - - -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// LLVOAvatarSelf -// -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLVOAvatarSelf : - public LLVOAvatar -{ - LOG_CLASS(LLVOAvatarSelf); - -/******************************************************************************** - ** ** - ** INITIALIZATION - **/ - -public: - LLVOAvatarSelf(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); - virtual ~LLVOAvatarSelf(); - virtual void markDead(); - virtual void initInstance(); // Called after construction to initialize the class. - void cleanup(); -protected: - /*virtual*/ bool loadAvatar(); - bool loadAvatarSelf(); - bool buildSkeletonSelf(const LLAvatarSkeletonInfo *info); - bool buildMenus(); - -/** Initialization - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** INHERITED - **/ - - //-------------------------------------------------------------------- - // LLViewerObject interface and related - //-------------------------------------------------------------------- -public: - boost::signals2::connection mRegionChangedSlot; - - void onSimulatorFeaturesReceived(const LLUUID& region_id); - /*virtual*/ void updateRegion(LLViewerRegion *regionp); - /*virtual*/ void idleUpdate(LLAgent &agent, const F64 &time); - - //-------------------------------------------------------------------- - // LLCharacter interface and related - //-------------------------------------------------------------------- -public: - /*virtual*/ bool hasMotionFromSource(const LLUUID& source_id); - /*virtual*/ void stopMotionFromSource(const LLUUID& source_id); - /*virtual*/ void requestStopMotion(LLMotion* motion); - /*virtual*/ LLJoint* getJoint(const std::string &name); - - /*virtual*/ bool setVisualParamWeight(const LLVisualParam *which_param, F32 weight); - /*virtual*/ bool setVisualParamWeight(const char* param_name, F32 weight); - /*virtual*/ bool setVisualParamWeight(S32 index, F32 weight); - /*virtual*/ void updateVisualParams(); - void writeWearablesToAvatar(); - /*virtual*/ void idleUpdateAppearanceAnimation(); - -private: - // helper function. Passed in param is assumed to be in avatar's parameter list. - bool setParamWeight(const LLViewerVisualParam *param, F32 weight); - -/******************************************************************************** - ** ** - ** STATE - **/ - -public: - /*virtual*/ bool isSelf() const { return true; } - virtual bool isBuddy() const { return false; } - /*virtual*/ bool isValid() const; - - //-------------------------------------------------------------------- - // Updates - //-------------------------------------------------------------------- -public: - /*virtual*/ bool updateCharacter(LLAgent &agent); - /*virtual*/ void idleUpdateTractorBeam(); - bool checkStuckAppearance(); - - //-------------------------------------------------------------------- - // Loading state - //-------------------------------------------------------------------- -public: - /*virtual*/ bool getIsCloud() const; - - //-------------------------------------------------------------------- - // Region state - //-------------------------------------------------------------------- - void resetRegionCrossingTimer() { mRegionCrossingTimer.reset(); } - -private: - U64 mLastRegionHandle; - LLFrameTimer mRegionCrossingTimer; - S32 mRegionCrossingCount; - -/** State - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** RENDERING - **/ - - //-------------------------------------------------------------------- - // Render beam - //-------------------------------------------------------------------- -protected: - bool needsRenderBeam(); -private: - LLPointer mBeam; - LLFrameTimer mBeamTimer; - - //-------------------------------------------------------------------- - // LLVOAvatar Constants - //-------------------------------------------------------------------- -public: - /*virtual*/ LLViewerTexture::EBoostLevel getAvatarBoostLevel() const { return LLGLTexture::BOOST_AVATAR_SELF; } - /*virtual*/ LLViewerTexture::EBoostLevel getAvatarBakedBoostLevel() const { return LLGLTexture::BOOST_AVATAR_BAKED_SELF; } - /*virtual*/ S32 getTexImageSize() const { return LLVOAvatar::getTexImageSize()*4; } - -/** Rendering - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** TEXTURES - **/ - - //-------------------------------------------------------------------- - // Loading status - //-------------------------------------------------------------------- -public: - S32 getLocalDiscardLevel(LLAvatarAppearanceDefines::ETextureIndex type, U32 index) const; - bool areTexturesCurrent() const; - bool isLocalTextureDataAvailable(const LLViewerTexLayerSet* layerset) const; - bool isLocalTextureDataFinal(const LLViewerTexLayerSet* layerset) const; - // If you want to check all textures of a given type, pass gAgentWearables.getWearableCount() for index - /*virtual*/ bool isTextureDefined(LLAvatarAppearanceDefines::ETextureIndex type, U32 index) const; - /*virtual*/ bool isTextureVisible(LLAvatarAppearanceDefines::ETextureIndex type, U32 index = 0) const; - /*virtual*/ bool isTextureVisible(LLAvatarAppearanceDefines::ETextureIndex type, LLViewerWearable *wearable) const; - - - //-------------------------------------------------------------------- - // Local Textures - //-------------------------------------------------------------------- -public: - bool getLocalTextureGL(LLAvatarAppearanceDefines::ETextureIndex type, LLViewerTexture** image_gl_pp, U32 index) const; - LLViewerFetchedTexture* getLocalTextureGL(LLAvatarAppearanceDefines::ETextureIndex type, U32 index) const; - const LLUUID& getLocalTextureID(LLAvatarAppearanceDefines::ETextureIndex type, U32 index) const; - void setLocalTextureTE(U8 te, LLViewerTexture* image, U32 index); - /*virtual*/ void setLocalTexture(LLAvatarAppearanceDefines::ETextureIndex type, LLViewerTexture* tex, bool baked_version_exits, U32 index); -protected: - /*virtual*/ void setBakedReady(LLAvatarAppearanceDefines::ETextureIndex type, bool baked_version_exists, U32 index); - void localTextureLoaded(bool succcess, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata); - void getLocalTextureByteCount(S32* gl_byte_count) const; - /*virtual*/ void addLocalTextureStats(LLAvatarAppearanceDefines::ETextureIndex i, LLViewerFetchedTexture* imagep, F32 texel_area_ratio, bool rendered, bool covered_by_baked); - LLLocalTextureObject* getLocalTextureObject(LLAvatarAppearanceDefines::ETextureIndex i, U32 index) const; - -private: - static void onLocalTextureLoaded(bool succcess, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata); - - /*virtual*/ void setImage(const U8 te, LLViewerTexture *imagep, const U32 index); - /*virtual*/ LLViewerTexture* getImage(const U8 te, const U32 index) const; - - - //-------------------------------------------------------------------- - // Baked textures - //-------------------------------------------------------------------- -public: - LLAvatarAppearanceDefines::ETextureIndex getBakedTE(const LLViewerTexLayerSet* layerset ) const; - // SUNSHINE CLEANUP - dead? or update to just call request appearance update? - void forceBakeAllTextures(bool slam_for_debug = false); -protected: - /*virtual*/ void removeMissingBakedTextures(); - - //-------------------------------------------------------------------- - // Layers - //-------------------------------------------------------------------- -public: - void requestLayerSetUpdate(LLAvatarAppearanceDefines::ETextureIndex i); - LLViewerTexLayerSet* getLayerSet(LLAvatarAppearanceDefines::EBakedTextureIndex baked_index) const; - LLViewerTexLayerSet* getLayerSet(LLAvatarAppearanceDefines::ETextureIndex index) const; - - - //-------------------------------------------------------------------- - // Composites - //-------------------------------------------------------------------- -public: - /* virtual */ void invalidateComposite(LLTexLayerSet* layerset); - /* virtual */ void invalidateAll(); - /* virtual */ void setCompositeUpdatesEnabled(bool b); // only works for self - /* virtual */ void setCompositeUpdatesEnabled(U32 index, bool b); - /* virtual */ bool isCompositeUpdateEnabled(U32 index); - void setupComposites(); - void updateComposites(); - - const LLUUID& grabBakedTexture(LLAvatarAppearanceDefines::EBakedTextureIndex baked_index) const; - bool canGrabBakedTexture(LLAvatarAppearanceDefines::EBakedTextureIndex baked_index) const; - - - //-------------------------------------------------------------------- - // Scratch textures (used for compositing) - //-------------------------------------------------------------------- -public: - static void deleteScratchTextures(); -private: - static S32Bytes sScratchTexBytes; - static std::map< LLGLenum, LLGLuint*> sScratchTexNames; - -/** Textures - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** MESHES - **/ -protected: - /*virtual*/ void restoreMeshData(); - -/** Meshes - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** WEARABLES - **/ - -public: - void wearableUpdated(LLWearableType::EType type); -protected: - U32 getNumWearables(LLAvatarAppearanceDefines::ETextureIndex i) const; - - //-------------------------------------------------------------------- - // Attachments - //-------------------------------------------------------------------- -public: - void updateAttachmentVisibility(U32 camera_mode); - bool isWearingAttachment(const LLUUID& inv_item_id) const; - LLViewerObject* getWornAttachment(const LLUUID& inv_item_id); - bool getAttachedPointName(const LLUUID& inv_item_id, std::string& name) const; - /*virtual*/ const LLViewerJointAttachment *attachObject(LLViewerObject *viewer_object); - /*virtual*/ bool detachObject(LLViewerObject *viewer_object); - static bool detachAttachmentIntoInventory(const LLUUID& item_id); - - bool hasAttachmentsInTrash(); - - //-------------------------------------------------------------------- - // HUDs - //-------------------------------------------------------------------- -private: - LLViewerJoint* mScreenp; // special purpose joint for HUD attachments - -/** Attachments - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** APPEARANCE - **/ - -public: - static void onCustomizeStart(bool disable_camera_switch = false); - static void onCustomizeEnd(bool disable_camera_switch = false); - LLPointer mEndCustomizeCallback; - - //-------------------------------------------------------------------- - // Visibility - //-------------------------------------------------------------------- - - /* virtual */ bool shouldRenderRigged() const; - -public: - bool sendAppearanceMessage(LLMessageSystem *mesgsys) const; - - // -- care and feeding of hover height. - void setHoverIfRegionEnabled(); - void sendHoverHeight() const; - /*virtual*/ void setHoverOffset(const LLVector3& hover_offset, bool send_update=true); - -private: - mutable LLVector3 mLastHoverOffsetSent; - -/** Appearance - ** ** - *******************************************************************************/ - -/******************************************************************************** - ** ** - ** DIAGNOSTICS - **/ - - //-------------------------------------------------------------------- - // General - //-------------------------------------------------------------------- -public: - static void dumpTotalLocalTextureByteCount(); - void dumpLocalTextures() const; - static void dumpScratchTextureByteCount(); - void dumpWearableInfo(LLAPRFile& outfile); - - //-------------------------------------------------------------------- - // Avatar Rez Metrics - //-------------------------------------------------------------------- -public: - struct LLAvatarTexData - { - LLAvatarTexData(const LLUUID& id, LLAvatarAppearanceDefines::ETextureIndex index) : - mAvatarID(id), - mIndex(index) - {} - LLUUID mAvatarID; - LLAvatarAppearanceDefines::ETextureIndex mIndex; - }; - - LLTimer mTimeSinceLastRezMessage; - bool updateAvatarRezMetrics(bool force_send); - - std::vector mPendingTimerRecords; - void addMetricsTimerRecord(const LLSD& record); - - void debugWearablesLoaded() { mDebugTimeWearablesLoaded = mDebugSelfLoadTimer.getElapsedTimeF32(); } - void debugAvatarVisible() { mDebugTimeAvatarVisible = mDebugSelfLoadTimer.getElapsedTimeF32(); } - void outputRezDiagnostics() const; - void outputRezTiming(const std::string& msg) const; - void reportAvatarRezTime() const; - void debugBakedTextureUpload(LLAvatarAppearanceDefines::EBakedTextureIndex index, bool finished); - static void debugOnTimingLocalTexLoaded(bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata); - - bool isAllLocalTextureDataFinal() const; - - const LLViewerTexLayerSet* debugGetLayerSet(LLAvatarAppearanceDefines::EBakedTextureIndex index) const { return (LLViewerTexLayerSet*)(mBakedTextureDatas[index].mTexLayerSet); } - const std::string verboseDebugDumpLocalTextureDataInfo(const LLViewerTexLayerSet* layerset) const; // Lists out state of this particular baked texture layer - void dumpAllTextures() const; - const std::string debugDumpLocalTextureDataInfo(const LLViewerTexLayerSet* layerset) const; // Lists out state of this particular baked texture layer - const std::string debugDumpAllLocalTextureDataInfo() const; // Lists out which baked textures are at highest LOD - void sendViewerAppearanceChangeMetrics(); // send data associated with completing a change. -private: - LLFrameTimer mDebugSelfLoadTimer; - F32 mDebugTimeWearablesLoaded; - F32 mDebugTimeAvatarVisible; - F32 mDebugTextureLoadTimes[LLAvatarAppearanceDefines::TEX_NUM_INDICES][MAX_DISCARD_LEVEL+1]; // load time for each texture at each discard level - F32 mDebugBakedTextureTimes[LLAvatarAppearanceDefines::BAKED_NUM_INDICES][2]; // time to start upload and finish upload of each baked texture - void debugTimingLocalTexLoaded(bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata); - - void appearanceChangeMetricsCoro(std::string url); - bool mInitialMetric; - S32 mMetricSequence; -/** Diagnostics - ** ** - *******************************************************************************/ - -}; - -extern LLPointer gAgentAvatarp; - -bool isAgentAvatarValid(); - -void selfStartPhase(const std::string& phase_name); -void selfStopPhase(const std::string& phase_name, bool err_check = true); -void selfClearPhases(); - -#endif // LL_VO_AVATARSELF_H +/** + * @file llvoavatarself.h + * @brief Declaration of LLVOAvatar class which is a derivation fo + * LLViewerObject + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVOAVATARSELF_H +#define LL_LLVOAVATARSELF_H + +#include "llviewertexture.h" +#include "llvoavatar.h" +#include +#include "lleventcoro.h" +#include "llcoros.h" + +struct LocalTextureData; +class LLInventoryCallback; + + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// LLVOAvatarSelf +// +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLVOAvatarSelf : + public LLVOAvatar +{ + LOG_CLASS(LLVOAvatarSelf); + +/******************************************************************************** + ** ** + ** INITIALIZATION + **/ + +public: + LLVOAvatarSelf(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); + virtual ~LLVOAvatarSelf(); + virtual void markDead(); + virtual void initInstance(); // Called after construction to initialize the class. + void cleanup(); +protected: + /*virtual*/ bool loadAvatar(); + bool loadAvatarSelf(); + bool buildSkeletonSelf(const LLAvatarSkeletonInfo *info); + bool buildMenus(); + +/** Initialization + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** INHERITED + **/ + + //-------------------------------------------------------------------- + // LLViewerObject interface and related + //-------------------------------------------------------------------- +public: + boost::signals2::connection mRegionChangedSlot; + + void onSimulatorFeaturesReceived(const LLUUID& region_id); + /*virtual*/ void updateRegion(LLViewerRegion *regionp); + /*virtual*/ void idleUpdate(LLAgent &agent, const F64 &time); + + //-------------------------------------------------------------------- + // LLCharacter interface and related + //-------------------------------------------------------------------- +public: + /*virtual*/ bool hasMotionFromSource(const LLUUID& source_id); + /*virtual*/ void stopMotionFromSource(const LLUUID& source_id); + /*virtual*/ void requestStopMotion(LLMotion* motion); + /*virtual*/ LLJoint* getJoint(const std::string &name); + + /*virtual*/ bool setVisualParamWeight(const LLVisualParam *which_param, F32 weight); + /*virtual*/ bool setVisualParamWeight(const char* param_name, F32 weight); + /*virtual*/ bool setVisualParamWeight(S32 index, F32 weight); + /*virtual*/ void updateVisualParams(); + void writeWearablesToAvatar(); + /*virtual*/ void idleUpdateAppearanceAnimation(); + +private: + // helper function. Passed in param is assumed to be in avatar's parameter list. + bool setParamWeight(const LLViewerVisualParam *param, F32 weight); + +/******************************************************************************** + ** ** + ** STATE + **/ + +public: + /*virtual*/ bool isSelf() const { return true; } + virtual bool isBuddy() const { return false; } + /*virtual*/ bool isValid() const; + + //-------------------------------------------------------------------- + // Updates + //-------------------------------------------------------------------- +public: + /*virtual*/ bool updateCharacter(LLAgent &agent); + /*virtual*/ void idleUpdateTractorBeam(); + bool checkStuckAppearance(); + + //-------------------------------------------------------------------- + // Loading state + //-------------------------------------------------------------------- +public: + /*virtual*/ bool getIsCloud() const; + + //-------------------------------------------------------------------- + // Region state + //-------------------------------------------------------------------- + void resetRegionCrossingTimer() { mRegionCrossingTimer.reset(); } + +private: + U64 mLastRegionHandle; + LLFrameTimer mRegionCrossingTimer; + S32 mRegionCrossingCount; + +/** State + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** RENDERING + **/ + + //-------------------------------------------------------------------- + // Render beam + //-------------------------------------------------------------------- +protected: + bool needsRenderBeam(); +private: + LLPointer mBeam; + LLFrameTimer mBeamTimer; + + //-------------------------------------------------------------------- + // LLVOAvatar Constants + //-------------------------------------------------------------------- +public: + /*virtual*/ LLViewerTexture::EBoostLevel getAvatarBoostLevel() const { return LLGLTexture::BOOST_AVATAR_SELF; } + /*virtual*/ LLViewerTexture::EBoostLevel getAvatarBakedBoostLevel() const { return LLGLTexture::BOOST_AVATAR_BAKED_SELF; } + /*virtual*/ S32 getTexImageSize() const { return LLVOAvatar::getTexImageSize()*4; } + +/** Rendering + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** TEXTURES + **/ + + //-------------------------------------------------------------------- + // Loading status + //-------------------------------------------------------------------- +public: + S32 getLocalDiscardLevel(LLAvatarAppearanceDefines::ETextureIndex type, U32 index) const; + bool areTexturesCurrent() const; + bool isLocalTextureDataAvailable(const LLViewerTexLayerSet* layerset) const; + bool isLocalTextureDataFinal(const LLViewerTexLayerSet* layerset) const; + // If you want to check all textures of a given type, pass gAgentWearables.getWearableCount() for index + /*virtual*/ bool isTextureDefined(LLAvatarAppearanceDefines::ETextureIndex type, U32 index) const; + /*virtual*/ bool isTextureVisible(LLAvatarAppearanceDefines::ETextureIndex type, U32 index = 0) const; + /*virtual*/ bool isTextureVisible(LLAvatarAppearanceDefines::ETextureIndex type, LLViewerWearable *wearable) const; + + + //-------------------------------------------------------------------- + // Local Textures + //-------------------------------------------------------------------- +public: + bool getLocalTextureGL(LLAvatarAppearanceDefines::ETextureIndex type, LLViewerTexture** image_gl_pp, U32 index) const; + LLViewerFetchedTexture* getLocalTextureGL(LLAvatarAppearanceDefines::ETextureIndex type, U32 index) const; + const LLUUID& getLocalTextureID(LLAvatarAppearanceDefines::ETextureIndex type, U32 index) const; + void setLocalTextureTE(U8 te, LLViewerTexture* image, U32 index); + /*virtual*/ void setLocalTexture(LLAvatarAppearanceDefines::ETextureIndex type, LLViewerTexture* tex, bool baked_version_exits, U32 index); +protected: + /*virtual*/ void setBakedReady(LLAvatarAppearanceDefines::ETextureIndex type, bool baked_version_exists, U32 index); + void localTextureLoaded(bool succcess, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata); + void getLocalTextureByteCount(S32* gl_byte_count) const; + /*virtual*/ void addLocalTextureStats(LLAvatarAppearanceDefines::ETextureIndex i, LLViewerFetchedTexture* imagep, F32 texel_area_ratio, bool rendered, bool covered_by_baked); + LLLocalTextureObject* getLocalTextureObject(LLAvatarAppearanceDefines::ETextureIndex i, U32 index) const; + +private: + static void onLocalTextureLoaded(bool succcess, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata); + + /*virtual*/ void setImage(const U8 te, LLViewerTexture *imagep, const U32 index); + /*virtual*/ LLViewerTexture* getImage(const U8 te, const U32 index) const; + + + //-------------------------------------------------------------------- + // Baked textures + //-------------------------------------------------------------------- +public: + LLAvatarAppearanceDefines::ETextureIndex getBakedTE(const LLViewerTexLayerSet* layerset ) const; + // SUNSHINE CLEANUP - dead? or update to just call request appearance update? + void forceBakeAllTextures(bool slam_for_debug = false); +protected: + /*virtual*/ void removeMissingBakedTextures(); + + //-------------------------------------------------------------------- + // Layers + //-------------------------------------------------------------------- +public: + void requestLayerSetUpdate(LLAvatarAppearanceDefines::ETextureIndex i); + LLViewerTexLayerSet* getLayerSet(LLAvatarAppearanceDefines::EBakedTextureIndex baked_index) const; + LLViewerTexLayerSet* getLayerSet(LLAvatarAppearanceDefines::ETextureIndex index) const; + + + //-------------------------------------------------------------------- + // Composites + //-------------------------------------------------------------------- +public: + /* virtual */ void invalidateComposite(LLTexLayerSet* layerset); + /* virtual */ void invalidateAll(); + /* virtual */ void setCompositeUpdatesEnabled(bool b); // only works for self + /* virtual */ void setCompositeUpdatesEnabled(U32 index, bool b); + /* virtual */ bool isCompositeUpdateEnabled(U32 index); + void setupComposites(); + void updateComposites(); + + const LLUUID& grabBakedTexture(LLAvatarAppearanceDefines::EBakedTextureIndex baked_index) const; + bool canGrabBakedTexture(LLAvatarAppearanceDefines::EBakedTextureIndex baked_index) const; + + + //-------------------------------------------------------------------- + // Scratch textures (used for compositing) + //-------------------------------------------------------------------- +public: + static void deleteScratchTextures(); +private: + static S32Bytes sScratchTexBytes; + static std::map< LLGLenum, LLGLuint*> sScratchTexNames; + +/** Textures + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** MESHES + **/ +protected: + /*virtual*/ void restoreMeshData(); + +/** Meshes + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** WEARABLES + **/ + +public: + void wearableUpdated(LLWearableType::EType type); +protected: + U32 getNumWearables(LLAvatarAppearanceDefines::ETextureIndex i) const; + + //-------------------------------------------------------------------- + // Attachments + //-------------------------------------------------------------------- +public: + void updateAttachmentVisibility(U32 camera_mode); + bool isWearingAttachment(const LLUUID& inv_item_id) const; + LLViewerObject* getWornAttachment(const LLUUID& inv_item_id); + bool getAttachedPointName(const LLUUID& inv_item_id, std::string& name) const; + /*virtual*/ const LLViewerJointAttachment *attachObject(LLViewerObject *viewer_object); + /*virtual*/ bool detachObject(LLViewerObject *viewer_object); + static bool detachAttachmentIntoInventory(const LLUUID& item_id); + + bool hasAttachmentsInTrash(); + + //-------------------------------------------------------------------- + // HUDs + //-------------------------------------------------------------------- +private: + LLViewerJoint* mScreenp; // special purpose joint for HUD attachments + +/** Attachments + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** APPEARANCE + **/ + +public: + static void onCustomizeStart(bool disable_camera_switch = false); + static void onCustomizeEnd(bool disable_camera_switch = false); + LLPointer mEndCustomizeCallback; + + //-------------------------------------------------------------------- + // Visibility + //-------------------------------------------------------------------- + + /* virtual */ bool shouldRenderRigged() const; + +public: + bool sendAppearanceMessage(LLMessageSystem *mesgsys) const; + + // -- care and feeding of hover height. + void setHoverIfRegionEnabled(); + void sendHoverHeight() const; + /*virtual*/ void setHoverOffset(const LLVector3& hover_offset, bool send_update=true); + +private: + mutable LLVector3 mLastHoverOffsetSent; + +/** Appearance + ** ** + *******************************************************************************/ + +/******************************************************************************** + ** ** + ** DIAGNOSTICS + **/ + + //-------------------------------------------------------------------- + // General + //-------------------------------------------------------------------- +public: + static void dumpTotalLocalTextureByteCount(); + void dumpLocalTextures() const; + static void dumpScratchTextureByteCount(); + void dumpWearableInfo(LLAPRFile& outfile); + + //-------------------------------------------------------------------- + // Avatar Rez Metrics + //-------------------------------------------------------------------- +public: + struct LLAvatarTexData + { + LLAvatarTexData(const LLUUID& id, LLAvatarAppearanceDefines::ETextureIndex index) : + mAvatarID(id), + mIndex(index) + {} + LLUUID mAvatarID; + LLAvatarAppearanceDefines::ETextureIndex mIndex; + }; + + LLTimer mTimeSinceLastRezMessage; + bool updateAvatarRezMetrics(bool force_send); + + std::vector mPendingTimerRecords; + void addMetricsTimerRecord(const LLSD& record); + + void debugWearablesLoaded() { mDebugTimeWearablesLoaded = mDebugSelfLoadTimer.getElapsedTimeF32(); } + void debugAvatarVisible() { mDebugTimeAvatarVisible = mDebugSelfLoadTimer.getElapsedTimeF32(); } + void outputRezDiagnostics() const; + void outputRezTiming(const std::string& msg) const; + void reportAvatarRezTime() const; + void debugBakedTextureUpload(LLAvatarAppearanceDefines::EBakedTextureIndex index, bool finished); + static void debugOnTimingLocalTexLoaded(bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata); + + bool isAllLocalTextureDataFinal() const; + + const LLViewerTexLayerSet* debugGetLayerSet(LLAvatarAppearanceDefines::EBakedTextureIndex index) const { return (LLViewerTexLayerSet*)(mBakedTextureDatas[index].mTexLayerSet); } + const std::string verboseDebugDumpLocalTextureDataInfo(const LLViewerTexLayerSet* layerset) const; // Lists out state of this particular baked texture layer + void dumpAllTextures() const; + const std::string debugDumpLocalTextureDataInfo(const LLViewerTexLayerSet* layerset) const; // Lists out state of this particular baked texture layer + const std::string debugDumpAllLocalTextureDataInfo() const; // Lists out which baked textures are at highest LOD + void sendViewerAppearanceChangeMetrics(); // send data associated with completing a change. +private: + LLFrameTimer mDebugSelfLoadTimer; + F32 mDebugTimeWearablesLoaded; + F32 mDebugTimeAvatarVisible; + F32 mDebugTextureLoadTimes[LLAvatarAppearanceDefines::TEX_NUM_INDICES][MAX_DISCARD_LEVEL+1]; // load time for each texture at each discard level + F32 mDebugBakedTextureTimes[LLAvatarAppearanceDefines::BAKED_NUM_INDICES][2]; // time to start upload and finish upload of each baked texture + void debugTimingLocalTexLoaded(bool success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, bool final, void* userdata); + + void appearanceChangeMetricsCoro(std::string url); + bool mInitialMetric; + S32 mMetricSequence; +/** Diagnostics + ** ** + *******************************************************************************/ + +}; + +extern LLPointer gAgentAvatarp; + +bool isAgentAvatarValid(); + +void selfStartPhase(const std::string& phase_name); +void selfStopPhase(const std::string& phase_name, bool err_check = true); +void selfClearPhases(); + +#endif // LL_VO_AVATARSELF_H diff --git a/indra/newview/llvocache.cpp b/indra/newview/llvocache.cpp index 4d9dbcab14..dd0501340d 100644 --- a/indra/newview/llvocache.cpp +++ b/indra/newview/llvocache.cpp @@ -1,1819 +1,1819 @@ -/** - * @file llvocache.cpp - * @brief Cache of objects on the viewer. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llvocache.h" -#include "llregionhandle.h" -#include "llviewercontrol.h" -#include "llviewerobjectlist.h" -#include "lldrawable.h" -#include "llviewerregion.h" -#include "llagentcamera.h" -#include "llsdserialize.h" - -//static variables -U32 LLVOCacheEntry::sMinFrameRange = 0; -F32 LLVOCacheEntry::sNearRadius = 1.0f; -F32 LLVOCacheEntry::sRearFarRadius = 1.0f; -F32 LLVOCacheEntry::sFrontPixelThreshold = 1.0f; -F32 LLVOCacheEntry::sRearPixelThreshold = 1.0f; -bool LLVOCachePartition::sNeedsOcclusionCheck = false; - -const S32 ENTRY_HEADER_SIZE = 6 * sizeof(S32); -const S32 MAX_ENTRY_BODY_SIZE = 10000; - -bool check_read(LLAPRFile* apr_file, void* src, S32 n_bytes) -{ - return apr_file->read(src, n_bytes) == n_bytes ; -} - -bool check_write(LLAPRFile* apr_file, void* src, S32 n_bytes) -{ - return apr_file->write(src, n_bytes) == n_bytes ; -} - -bool LLGLTFOverrideCacheEntry::fromLLSD(const LLSD& data) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; - - llassert(data.has("local_id")); - llassert(data.has("object_id")); - llassert(data.has("region_handle_x") && data.has("region_handle_y")); - - if (!data.has("local_id")) - { - return false; - } - - if (data.has("region_handle_x") && data.has("region_handle_y")) - { - // TODO start requiring this once server sends this for all messages - U32 region_handle_y = data["region_handle_y"].asInteger(); - U32 region_handle_x = data["region_handle_x"].asInteger(); - mRegionHandle = to_region_handle(region_handle_x, region_handle_y); - } - else - { - return false; - } - - mLocalId = data["local_id"].asInteger(); - mObjectId = data["object_id"]; - - // message should be interpreted thusly: - /// sides is a list of face indices - // gltf_llsd is a list of corresponding GLTF override LLSD - // any side not represented in "sides" has no override - if (data.has("sides") && data.has("gltf_llsd")) - { - LLSD const& sides = data.get("sides"); - LLSD const& gltf_llsd = data.get("gltf_llsd"); - - if (sides.isArray() && gltf_llsd.isArray() && - sides.size() != 0 && - sides.size() == gltf_llsd.size()) - { - for (int i = 0; i < sides.size(); ++i) - { - S32 side_idx = sides[i].asInteger(); - mSides[side_idx] = gltf_llsd[i]; - LLGLTFMaterial* override_mat = new LLGLTFMaterial(); - override_mat->applyOverrideLLSD(gltf_llsd[i]); - mGLTFMaterial[side_idx] = override_mat; - } - } - else - { - LL_WARNS_IF(sides.size() != 0, "GLTF") << "broken override cache entry" << LL_ENDL; - } - } - - llassert(mSides.size() == mGLTFMaterial.size()); -#ifdef SHOW_ASSERT - for (auto const & side : mSides) - { - // check that mSides and mGLTFMaterial have exactly the same keys present - llassert(mGLTFMaterial.count(side.first) == 1); - } -#endif - - return true; -} - -LLSD LLGLTFOverrideCacheEntry::toLLSD() const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; - LLSD data; - U32 region_handle_x, region_handle_y; - from_region_handle(mRegionHandle, ®ion_handle_x, ®ion_handle_y); - data["region_handle_y"] = LLSD::Integer(region_handle_y); - data["region_handle_x"] = LLSD::Integer(region_handle_x); - - data["object_id"] = mObjectId; - data["local_id"] = (LLSD::Integer) mLocalId; - - llassert(mSides.size() == mGLTFMaterial.size()); - for (auto const & side : mSides) - { - // check that mSides and mGLTFMaterial have exactly the same keys present - llassert(mGLTFMaterial.count(side.first) == 1); - data["sides"].append(LLSD::Integer(side.first)); - data["gltf_llsd"].append(side.second); - } - - return data; -} - -//--------------------------------------------------------------------------- -// LLVOCacheEntry -//--------------------------------------------------------------------------- - -LLVOCacheEntry::LLVOCacheEntry(U32 local_id, U32 crc, LLDataPackerBinaryBuffer &dp) -: LLViewerOctreeEntryData(LLViewerOctreeEntry::LLVOCACHEENTRY), - mLocalID(local_id), - mCRC(crc), - mUpdateFlags(-1), - mHitCount(0), - mDupeCount(0), - mCRCChangeCount(0), - mState(INACTIVE), - mSceneContrib(0.f), - mValid(true), - mParentID(0), - mBSphereRadius(-1.0f) -{ - mBuffer = new U8[dp.getBufferSize()]; - mDP.assignBuffer(mBuffer, dp.getBufferSize()); - mDP = dp; -} - -LLVOCacheEntry::LLVOCacheEntry() -: LLViewerOctreeEntryData(LLViewerOctreeEntry::LLVOCACHEENTRY), - mLocalID(0), - mCRC(0), - mUpdateFlags(-1), - mHitCount(0), - mDupeCount(0), - mCRCChangeCount(0), - mBuffer(NULL), - mState(INACTIVE), - mSceneContrib(0.f), - mValid(true), - mParentID(0), - mBSphereRadius(-1.0f) -{ - mDP.assignBuffer(mBuffer, 0); -} - -LLVOCacheEntry::LLVOCacheEntry(LLAPRFile* apr_file) -: LLViewerOctreeEntryData(LLViewerOctreeEntry::LLVOCACHEENTRY), - mBuffer(NULL), - mUpdateFlags(-1), - mState(INACTIVE), - mSceneContrib(0.f), - mValid(false), - mParentID(0), - mBSphereRadius(-1.0f) -{ - S32 size = -1; - bool success; - static U8 data_buffer[ENTRY_HEADER_SIZE]; - - mDP.assignBuffer(mBuffer, 0); - - success = check_read(apr_file, (void *)data_buffer, ENTRY_HEADER_SIZE); - if (success) - { - memcpy(&mLocalID, data_buffer, sizeof(U32)); - memcpy(&mCRC, data_buffer + sizeof(U32), sizeof(U32)); - memcpy(&mHitCount, data_buffer + (2 * sizeof(U32)), sizeof(S32)); - memcpy(&mDupeCount, data_buffer + (3 * sizeof(U32)), sizeof(S32)); - memcpy(&mCRCChangeCount, data_buffer + (4 * sizeof(U32)), sizeof(S32)); - memcpy(&size, data_buffer + (5 * sizeof(U32)), sizeof(S32)); - - // Corruption in the cache entries - if ((size > MAX_ENTRY_BODY_SIZE) || (size < 1)) - { - // We've got a bogus size, skip reading it. - // We won't bother seeking, because the rest of this file - // is likely bogus, and will be tossed anyway. - LL_WARNS() << "Bogus cache entry, size " << size << ", aborting!" << LL_ENDL; - success = false; - } - } - if(success && size > 0) - { - mBuffer = new U8[size]; - success = check_read(apr_file, mBuffer, size); - - if(success) - { - mDP.assignBuffer(mBuffer, size); - } - else - { - delete[] mBuffer ; - mBuffer = NULL ; - } - } - - if(!success) - { - mLocalID = 0; - mCRC = 0; - mHitCount = 0; - mDupeCount = 0; - mCRCChangeCount = 0; - mBuffer = NULL; - mEntry = NULL; - mState = INACTIVE; - } -} - -LLVOCacheEntry::~LLVOCacheEntry() -{ - mDP.freeBuffer(); -} - -void LLVOCacheEntry::updateEntry(U32 crc, LLDataPackerBinaryBuffer &dp) -{ - if(mCRC != crc) - { - mCRC = crc; - mCRCChangeCount++; - } - - mDP.freeBuffer(); - - llassert_always(dp.getBufferSize() > 0); - mBuffer = new U8[dp.getBufferSize()]; - mDP.assignBuffer(mBuffer, dp.getBufferSize()); - mDP = dp; -} - -void LLVOCacheEntry::setParentID(U32 id) -{ - if(mParentID != id) - { - removeAllChildren(); - mParentID = id; - } -} - -void LLVOCacheEntry::removeAllChildren() -{ - if(mChildrenList.empty()) - { - return; - } - - for(vocache_entry_set_t::iterator iter = mChildrenList.begin(); iter != mChildrenList.end(); ++iter) - { - (*iter)->setParentID(0); - } - mChildrenList.clear(); - - return; -} - -//virtual -void LLVOCacheEntry::setOctreeEntry(LLViewerOctreeEntry* entry) -{ - if(!entry && mDP.getBufferSize() > 0) - { - LLUUID fullid; - LLViewerObject::unpackUUID(&mDP, fullid, "ID"); - - LLViewerObject* obj = gObjectList.findObject(fullid); - if(obj && obj->mDrawable) - { - entry = obj->mDrawable->getEntry(); - } - } - - LLViewerOctreeEntryData::setOctreeEntry(entry); -} - -void LLVOCacheEntry::setState(U32 state) -{ - if(state > LOW_BITS) //special states - { - mState |= (HIGH_BITS & state); - return; - } - - // - //otherwise LOW_BITS states - // - clearState(LOW_BITS); - mState |= (LOW_BITS & state); - - if(getState() == ACTIVE) - { - const S32 MIN_INTERVAL = 64 + sMinFrameRange; - U32 last_visible = getVisible(); - - setVisible(); - - U32 cur_visible = getVisible(); - if(cur_visible - last_visible > MIN_INTERVAL || - cur_visible < MIN_INTERVAL) - { - mLastCameraUpdated = 0; //reset - } - else - { - mLastCameraUpdated = LLViewerRegion::sLastCameraUpdated; - } - } -} - -void LLVOCacheEntry::addChild(LLVOCacheEntry* entry) -{ - llassert(entry != NULL); - llassert(entry->getParentID() == mLocalID); - llassert(entry->getEntry() != NULL); - - if(!entry || !entry->getEntry() || entry->getParentID() != mLocalID) - { - return; - } - - mChildrenList.insert(entry); - - //update parent bbox - if(getEntry() != NULL && isState(INACTIVE)) - { - updateParentBoundingInfo(entry); - resetVisible(); - } -} - -void LLVOCacheEntry::removeChild(LLVOCacheEntry* entry) -{ - entry->setParentID(0); - - vocache_entry_set_t::iterator iter = mChildrenList.find(entry); - if(iter != mChildrenList.end()) - { - mChildrenList.erase(iter); - } -} - -//remove the first child, and return it. -LLVOCacheEntry* LLVOCacheEntry::getChild() -{ - LLVOCacheEntry* child = NULL; - vocache_entry_set_t::iterator iter = mChildrenList.begin(); - if(iter != mChildrenList.end()) - { - child = *iter; - mChildrenList.erase(iter); - } - - return child; -} - -LLDataPackerBinaryBuffer *LLVOCacheEntry::getDP() -{ - if (mDP.getBufferSize() == 0) - { - //LL_INFOS() << "Not getting cache entry, invalid!" << LL_ENDL; - return NULL; - } - - return &mDP; -} - -void LLVOCacheEntry::recordHit() -{ - mHitCount++; -} - - -void LLVOCacheEntry::dump() const -{ - LL_INFOS() << "local " << mLocalID - << " crc " << mCRC - << " hits " << mHitCount - << " dupes " << mDupeCount - << " change " << mCRCChangeCount - << LL_ENDL; -} - -S32 LLVOCacheEntry::writeToBuffer(U8 *data_buffer) const -{ - S32 size = mDP.getBufferSize(); - - if (size > MAX_ENTRY_BODY_SIZE) - { - LL_WARNS() << "Failed to write entry with size above allowed limit: " << size << LL_ENDL; - return 0; - } - - memcpy(data_buffer, &mLocalID, sizeof(U32)); - memcpy(data_buffer + sizeof(U32), &mCRC, sizeof(U32)); - memcpy(data_buffer + (2 * sizeof(U32)), &mHitCount, sizeof(S32)); - memcpy(data_buffer + (3 * sizeof(U32)), &mDupeCount, sizeof(S32)); - memcpy(data_buffer + (4 * sizeof(U32)), &mCRCChangeCount, sizeof(S32)); - memcpy(data_buffer + (5 * sizeof(U32)), &size, sizeof(S32)); - memcpy(data_buffer + ENTRY_HEADER_SIZE, (void*)mBuffer, size); - - return ENTRY_HEADER_SIZE + size; -} - -#ifndef LL_TEST -//static -void LLVOCacheEntry::updateDebugSettings() -{ - static LLFrameTimer timer; - if(timer.getElapsedTimeF32() < 1.0f) //update frequency once per second. - { - return; - } - timer.reset(); - - //objects within the view frustum whose visible area is greater than this threshold will be loaded - static LLCachedControl front_pixel_threshold(gSavedSettings,"SceneLoadFrontPixelThreshold"); - sFrontPixelThreshold = front_pixel_threshold; - - //objects out of the view frustum whose visible area is greater than this threshold will remain loaded - static LLCachedControl rear_pixel_threshold(gSavedSettings,"SceneLoadRearPixelThreshold"); - sRearPixelThreshold = rear_pixel_threshold; - sRearPixelThreshold = llmax(sRearPixelThreshold, sFrontPixelThreshold); //can not be smaller than sFrontPixelThreshold. - - //make parameters adaptive to memory usage - //starts to put restrictions from low_mem_bound_MB, apply tightest restrictions when hits high_mem_bound_MB - static LLCachedControl low_mem_bound_MB(gSavedSettings,"SceneLoadLowMemoryBound"); - static LLCachedControl high_mem_bound_MB(gSavedSettings,"SceneLoadHighMemoryBound"); - - LLMemory::updateMemoryInfo() ; - U32 allocated_mem = LLMemory::getAllocatedMemKB().value(); - static const F32 KB_to_MB = 1.f / 1024.f; - U32 clamped_memory = llclamp(allocated_mem * KB_to_MB, (F32) low_mem_bound_MB, (F32) high_mem_bound_MB); - const F32 adjust_range = high_mem_bound_MB - low_mem_bound_MB; - const F32 adjust_factor = (high_mem_bound_MB - clamped_memory) / adjust_range; // [0, 1] - - //min radius: all objects within this radius remain loaded in memory - static LLCachedControl min_radius(gSavedSettings,"SceneLoadMinRadius"); - static const F32 MIN_RADIUS = 1.0f; - const F32 draw_radius = gAgentCamera.mDrawDistance; - const F32 clamped_min_radius = llclamp((F32) min_radius, MIN_RADIUS, draw_radius); // [1, mDrawDistance] - sNearRadius = MIN_RADIUS + ((clamped_min_radius - MIN_RADIUS) * adjust_factor); - - // a percentage of draw distance beyond which all objects outside of view frustum will be unloaded, regardless of pixel threshold - static LLCachedControl rear_max_radius_frac(gSavedSettings,"SceneLoadRearMaxRadiusFraction"); - const F32 min_radius_plus_one = sNearRadius + 1.f; - const F32 max_radius = rear_max_radius_frac * gAgentCamera.mDrawDistance; - const F32 clamped_max_radius = llclamp(max_radius, min_radius_plus_one, draw_radius); // [sNearRadius, mDrawDistance] - sRearFarRadius = min_radius_plus_one + ((clamped_max_radius - min_radius_plus_one) * adjust_factor); - - //the number of frames invisible objects stay in memory - static LLCachedControl inv_obj_time(gSavedSettings,"NonvisibleObjectsInMemoryTime"); - static const U32 MIN_FRAMES = 10; - static const U32 MAX_FRAMES = 64; - const U32 clamped_frames = inv_obj_time ? llclamp((U32) inv_obj_time, MIN_FRAMES, MAX_FRAMES) : MAX_FRAMES; // [10, 64], with zero => 64 - sMinFrameRange = MIN_FRAMES + ((clamped_frames - MIN_FRAMES) * adjust_factor); -} -#endif // LL_TEST - -//static -F32 LLVOCacheEntry::getSquaredPixelThreshold(bool is_front) -{ - F32 threshold; - if(is_front) - { - threshold = sFrontPixelThreshold; - } - else - { - threshold = sRearPixelThreshold; - } - - //object projected area threshold - F32 pixel_meter_ratio = LLViewerCamera::getInstance()->getPixelMeterRatio(); - F32 projection_threshold = pixel_meter_ratio > 0.f ? threshold / pixel_meter_ratio : 0.f; - projection_threshold *= projection_threshold; - - return projection_threshold; -} - -bool LLVOCacheEntry::isAnyVisible(const LLVector4a& camera_origin, const LLVector4a& local_camera_origin, F32 dist_threshold) -{ - LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*)getGroup(); - if(!group) - { - return false; - } - - //any visible - bool vis = group->isAnyRecentlyVisible(); - - //not ready to remove - if(!vis) - { - S32 cur_vis = llmax(group->getAnyVisible(), (S32)getVisible()); - vis = (cur_vis + sMinFrameRange > LLViewerOctreeEntryData::getCurrentFrame()); - } - - //within the back sphere - if(!vis && !mParentID && !group->isOcclusionState(LLOcclusionCullingGroup::OCCLUDED)) - { - LLVector4a lookAt; - - if(mBSphereRadius > 0.f) - { - lookAt.setSub(mBSphereCenter, local_camera_origin); - dist_threshold += mBSphereRadius; - } - else - { - lookAt.setSub(getPositionGroup(), camera_origin); - dist_threshold += getBinRadius(); - } - - vis = (lookAt.dot3(lookAt).getF32() < dist_threshold * dist_threshold); - } - - return vis; -} - -void LLVOCacheEntry::calcSceneContribution(const LLVector4a& camera_origin, bool needs_update, U32 last_update, F32 max_dist) -{ - if(!needs_update && getVisible() >= last_update) - { - return; //no need to update - } - - LLVector4a lookAt; - lookAt.setSub(getPositionGroup(), camera_origin); - F32 distance = lookAt.getLength3().getF32(); - distance -= sNearRadius; - - if(distance <= 0.f) - { - //nearby objects, set a large number - const F32 LARGE_SCENE_CONTRIBUTION = 1000.f; //a large number to force to load the object. - mSceneContrib = LARGE_SCENE_CONTRIBUTION; - } - else - { - F32 rad = getBinRadius(); - max_dist += rad; - - if(distance + sNearRadius < max_dist) - { - mSceneContrib = (rad * rad) / distance; - } - else - { - mSceneContrib = 0.f; //out of draw distance, not to load - } - } - - setVisible(); -} - -void LLVOCacheEntry::saveBoundingSphere() -{ - mBSphereCenter = getPositionGroup(); - mBSphereRadius = getBinRadius(); -} - -void LLVOCacheEntry::setBoundingInfo(const LLVector3& pos, const LLVector3& scale) -{ - LLVector4a center, newMin, newMax; - center.load3(pos.mV); - LLVector4a size; - size.load3(scale.mV); - newMin.setSub(center, size); - newMax.setAdd(center, size); - - setPositionGroup(center); - setSpatialExtents(newMin, newMax); - - if(getNumOfChildren() > 0) //has children - { - updateParentBoundingInfo(); - } - else - { - setBinRadius(llmin(size.getLength3().getF32() * 4.f, 256.f)); - } -} - -//make the parent bounding box to include all children -void LLVOCacheEntry::updateParentBoundingInfo() -{ - if(mChildrenList.empty()) - { - return; - } - - for(vocache_entry_set_t::iterator iter = mChildrenList.begin(); iter != mChildrenList.end(); ++iter) - { - updateParentBoundingInfo(*iter); - } - resetVisible(); -} - -//make the parent bounding box to include this child -void LLVOCacheEntry::updateParentBoundingInfo(const LLVOCacheEntry* child) -{ - const LLVector4a* child_exts = child->getSpatialExtents(); - LLVector4a newMin, newMax; - newMin = child_exts[0]; - newMax = child_exts[1]; - - //move to regional space. - { - const LLVector4a& parent_pos = getPositionGroup(); - newMin.add(parent_pos); - newMax.add(parent_pos); - } - - //update parent's bbox(min, max) - const LLVector4a* parent_exts = getSpatialExtents(); - update_min_max(newMin, newMax, parent_exts[0]); - update_min_max(newMin, newMax, parent_exts[1]); - for(S32 i = 0; i < 4; i++) - { - llclamp(newMin[i], 0.f, 256.f); - llclamp(newMax[i], 0.f, 256.f); - } - setSpatialExtents(newMin, newMax); - - //update parent's bbox center - LLVector4a center; - center.setAdd(newMin, newMax); - center.mul(0.5f); - setPositionGroup(center); - - //update parent's bbox size vector - LLVector4a size; - size.setSub(newMax, newMin); - size.mul(0.5f); - setBinRadius(llmin(size.getLength3().getF32() * 4.f, 256.f)); -} -//------------------------------------------------------------------- -//LLVOCachePartition -//------------------------------------------------------------------- -LLVOCacheGroup::~LLVOCacheGroup() -{ - if(mOcclusionState[LLViewerCamera::CAMERA_WORLD] & ACTIVE_OCCLUSION) - { - ((LLVOCachePartition*)mSpatialPartition)->removeOccluder(this); - } -} - -//virtual -void LLVOCacheGroup::handleChildAddition(const OctreeNode* parent, OctreeNode* child) -{ - if (child->getListenerCount() == 0) - { - new LLVOCacheGroup(child, mSpatialPartition); - } - else - { - OCT_ERRS << "LLVOCacheGroup redundancy detected." << LL_ENDL; - } - - unbound(); - - ((LLViewerOctreeGroup*)child->getListener(0))->unbound(); -} - -LLVOCachePartition::LLVOCachePartition(LLViewerRegion* regionp) -{ - mLODPeriod = 16; - mRegionp = regionp; - mPartitionType = LLViewerRegion::PARTITION_VO_CACHE; - mBackSlectionEnabled = -1; - mIdleHash = 0; - - for(S32 i = 0; i < LLViewerCamera::NUM_CAMERAS; i++) - { - mCulledTime[i] = 0; - } - mCullHistory = -1; - - new LLVOCacheGroup(mOctree, this); -} - -LLVOCachePartition::~LLVOCachePartition() -{ - // SL-17276 make sure to do base class cleanup while this instance - // can still be treated as an LLVOCachePartition - cleanup(); -} - -bool LLVOCachePartition::addEntry(LLViewerOctreeEntry* entry) -{ - llassert(entry->hasVOCacheEntry()); - - if(!llfinite(entry->getBinRadius()) || !entry->getPositionGroup().isFinite3()) - { - return false; //data corrupted - } - - mOctree->insert(entry); - - return true; -} - -void LLVOCachePartition::removeEntry(LLViewerOctreeEntry* entry) -{ - entry->getVOCacheEntry()->setGroup(NULL); - - llassert(!entry->getGroup()); -} - -class LLVOCacheOctreeCull : public LLViewerOctreeCull -{ -public: - LLVOCacheOctreeCull(LLCamera* camera, LLViewerRegion* regionp, - const LLVector3& shift, bool use_object_cache_occlusion, F32 pixel_threshold, LLVOCachePartition* part) - : LLViewerOctreeCull(camera), - mRegionp(regionp), - mPartition(part), - mPixelThreshold(pixel_threshold) - { - mLocalShift = shift; - mUseObjectCacheOcclusion = use_object_cache_occlusion; - mNearRadius = LLVOCacheEntry::sNearRadius; - } - - virtual bool earlyFail(LLViewerOctreeGroup* base_group) - { - if( mUseObjectCacheOcclusion && - base_group->getOctreeNode()->getParent()) //never occlusion cull the root node - { - LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*)base_group; - if(group->needsUpdate()) - { - //needs to issue new occlusion culling check, perform view culling check first. - return false; - } - - group->checkOcclusion(); - - if (group->isOcclusionState(LLOcclusionCullingGroup::OCCLUDED)) - { - return true; - } - } - - return false; - } - - virtual S32 frustumCheck(const LLViewerOctreeGroup* group) - { -#if 0 - S32 res = AABBInRegionFrustumGroupBounds(group); -#else - S32 res = AABBInRegionFrustumNoFarClipGroupBounds(group); - if (res != 0) - { - res = llmin(res, AABBRegionSphereIntersectGroupExtents(group, mLocalShift)); - } -#endif - - return res; - } - - virtual S32 frustumCheckObjects(const LLViewerOctreeGroup* group) - { -#if 0 - S32 res = AABBInRegionFrustumObjectBounds(group); -#else - S32 res = AABBInRegionFrustumNoFarClipObjectBounds(group); - if (res != 0) - { - res = llmin(res, AABBRegionSphereIntersectObjectExtents(group, mLocalShift)); - } -#endif - - if(res != 0) - { - //check if the objects projection large enough - const LLVector4a* exts = group->getObjectExtents(); - res = checkProjectionArea(exts[0], exts[1], mLocalShift, mPixelThreshold, mNearRadius); - } - - return res; - } - - virtual void processGroup(LLViewerOctreeGroup* base_group) - { - if( !mUseObjectCacheOcclusion || - !base_group->getOctreeNode()->getParent()) - { - //no occlusion check - if(mRegionp->addVisibleGroup(base_group)) - { - base_group->setVisible(); - } - return; - } - - LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*)base_group; - if(group->needsUpdate() || !group->isRecentlyVisible())//needs to issue new occlusion culling check. - { - mPartition->addOccluders(group); - group->setVisible(); - return ; //wait for occlusion culling result - } - - if(group->isOcclusionState(LLOcclusionCullingGroup::QUERY_PENDING) || - group->isOcclusionState(LLOcclusionCullingGroup::ACTIVE_OCCLUSION)) - { - //keep waiting - group->setVisible(); - } - else - { - if(mRegionp->addVisibleGroup(base_group)) - { - base_group->setVisible(); - } - } - } - -private: - LLVOCachePartition* mPartition; - LLViewerRegion* mRegionp; - LLVector3 mLocalShift; //shift vector from agent space to local region space. - F32 mPixelThreshold; - F32 mNearRadius; - bool mUseObjectCacheOcclusion; -}; - -//select objects behind camera -class LLVOCacheOctreeBackCull : public LLViewerOctreeCull -{ -public: - LLVOCacheOctreeBackCull(LLCamera* camera, const LLVector3& shift, LLViewerRegion* regionp, F32 pixel_threshold, bool use_occlusion) - : LLViewerOctreeCull(camera), mRegionp(regionp), mPixelThreshold(pixel_threshold), mUseObjectCacheOcclusion(use_occlusion) - { - mLocalShift = shift; - mSphereRadius = LLVOCacheEntry::sRearFarRadius; - } - - virtual bool earlyFail(LLViewerOctreeGroup* base_group) - { - if( mUseObjectCacheOcclusion && - base_group->getOctreeNode()->getParent()) //never occlusion cull the root node - { - LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*)base_group; - - if (group->getOcclusionState() > 0) //occlusion state is not clear. - { - return true; - } - } - - return false; - } - - virtual S32 frustumCheck(const LLViewerOctreeGroup* group) - { - const LLVector4a* exts = group->getExtents(); - return backSphereCheck(exts[0], exts[1]); - } - - virtual S32 frustumCheckObjects(const LLViewerOctreeGroup* group) - { - const LLVector4a* exts = group->getObjectExtents(); - if(backSphereCheck(exts[0], exts[1])) - { - //check if the objects projection large enough - const LLVector4a* exts = group->getObjectExtents(); - return checkProjectionArea(exts[0], exts[1], mLocalShift, mPixelThreshold, mSphereRadius); - } - return false; - } - - virtual void processGroup(LLViewerOctreeGroup* base_group) - { - mRegionp->addVisibleGroup(base_group); - return; - } - -private: - //a sphere around the camera origin, including objects behind camera. - S32 backSphereCheck(const LLVector4a& min, const LLVector4a& max) - { - return AABBSphereIntersect(min, max, mCamera->getOrigin() - mLocalShift, mSphereRadius); - } - -private: - F32 mSphereRadius; - LLViewerRegion* mRegionp; - LLVector3 mLocalShift; //shift vector from agent space to local region space. - F32 mPixelThreshold; - bool mUseObjectCacheOcclusion; -}; - -void LLVOCachePartition::selectBackObjects(LLCamera &camera, F32 pixel_threshold, bool use_occlusion) -{ - if(LLViewerCamera::sCurCameraID != LLViewerCamera::CAMERA_WORLD) - { - return; - } - - if(mBackSlectionEnabled < 0) - { - mBackSlectionEnabled = LLVOCacheEntry::sMinFrameRange - 1; - mBackSlectionEnabled = llmax(mBackSlectionEnabled, (S32)1); - } - - if(!mBackSlectionEnabled) - { - return; - } - - //localize the camera - LLVector3 region_agent = mRegionp->getOriginAgent(); - - LLVOCacheOctreeBackCull culler(&camera, region_agent, mRegionp, pixel_threshold, use_occlusion); - culler.traverse(mOctree); - - mBackSlectionEnabled--; - if(!mRegionp->getNumOfVisibleGroups()) - { - mBackSlectionEnabled = 0; - } - - return; -} - -#ifndef LL_TEST -S32 LLVOCachePartition::cull(LLCamera &camera, bool do_occlusion) -{ - static LLCachedControl use_object_cache_occlusion(gSavedSettings,"UseObjectCacheOcclusion"); - - if(!LLViewerRegion::sVOCacheCullingEnabled) - { - return 0; - } - if(mRegionp->isPaused()) - { - return 0; - } - - ((LLViewerOctreeGroup*)mOctree->getListener(0))->rebound(); - - if(LLViewerCamera::sCurCameraID != LLViewerCamera::CAMERA_WORLD) - { - return 0; //no need for those cameras. - } - - if(mCulledTime[LLViewerCamera::sCurCameraID] == LLViewerOctreeEntryData::getCurrentFrame()) - { - return 0; //already culled - } - mCulledTime[LLViewerCamera::sCurCameraID] = LLViewerOctreeEntryData::getCurrentFrame(); - - if(!mCullHistory && LLViewerRegion::isViewerCameraStatic()) - { - U32 seed = llmax(mLODPeriod >> 1, (U32)4); - if(LLViewerCamera::sCurCameraID == LLViewerCamera::CAMERA_WORLD) - { - if(!(LLViewerOctreeEntryData::getCurrentFrame() % seed)) - { - mIdleHash = (mIdleHash + 1) % seed; - } - } - if(LLViewerOctreeEntryData::getCurrentFrame() % seed != mIdleHash) - { - mFrontCull = false; - - //process back objects selection - selectBackObjects(camera, LLVOCacheEntry::getSquaredPixelThreshold(mFrontCull), - do_occlusion && use_object_cache_occlusion); - return 0; //nothing changed, reduce frequency of culling - } - } - else - { - mBackSlectionEnabled = -1; //reset it. - } - - //localize the camera - LLVector3 region_agent = mRegionp->getOriginAgent(); - camera.calcRegionFrustumPlanes(region_agent, gAgentCamera.mDrawDistance); - - mFrontCull = true; - LLVOCacheOctreeCull culler(&camera, mRegionp, region_agent, do_occlusion && use_object_cache_occlusion, - LLVOCacheEntry::getSquaredPixelThreshold(mFrontCull), this); - culler.traverse(mOctree); - - if(!sNeedsOcclusionCheck) - { - sNeedsOcclusionCheck = !mOccludedGroups.empty(); - } - return 1; -} -#endif // LL_TEST - -void LLVOCachePartition::setCullHistory(bool has_new_object) -{ - mCullHistory <<= 1; - mCullHistory |= static_cast(has_new_object); -} - -void LLVOCachePartition::addOccluders(LLViewerOctreeGroup* gp) -{ - LLVOCacheGroup* group = (LLVOCacheGroup*)gp; - - if(!group->isOcclusionState(LLOcclusionCullingGroup::ACTIVE_OCCLUSION)) - { - group->setOcclusionState(LLOcclusionCullingGroup::ACTIVE_OCCLUSION); - mOccludedGroups.insert(group); - } -} - -void LLVOCachePartition::processOccluders(LLCamera* camera) -{ - if(mOccludedGroups.empty()) - { - return; - } - if(LLViewerCamera::sCurCameraID != LLViewerCamera::CAMERA_WORLD) - { - return; //no need for those cameras. - } - - LLVector3 region_agent = mRegionp->getOriginAgent(); - LLVector4a shift(region_agent[0], region_agent[1], region_agent[2]); - for(std::set::iterator iter = mOccludedGroups.begin(); iter != mOccludedGroups.end(); ++iter) - { - LLVOCacheGroup* group = *iter; - if(group->isOcclusionState(LLOcclusionCullingGroup::ACTIVE_OCCLUSION)) - { - group->doOcclusion(camera, &shift); - group->clearOcclusionState(LLOcclusionCullingGroup::ACTIVE_OCCLUSION); - } - } - - //safe to clear mOccludedGroups here because only the world camera accesses it. - mOccludedGroups.clear(); - sNeedsOcclusionCheck = false; -} - -void LLVOCachePartition::resetOccluders() -{ - if(mOccludedGroups.empty()) - { - return; - } - - for(std::set::iterator iter = mOccludedGroups.begin(); iter != mOccludedGroups.end(); ++iter) - { - LLVOCacheGroup* group = *iter; - group->clearOcclusionState(LLOcclusionCullingGroup::ACTIVE_OCCLUSION); - } - mOccludedGroups.clear(); - sNeedsOcclusionCheck = false; -} - -void LLVOCachePartition::removeOccluder(LLVOCacheGroup* group) -{ - if(mOccludedGroups.empty()) - { - return; - } - mOccludedGroups.erase(group); -} -//------------------------------------------------------------------- -//LLVOCache -//------------------------------------------------------------------- -// Format strings used to construct filename for the object cache -static const char OBJECT_CACHE_FILENAME[] = "objects_%d_%d.slc"; -static const char OBJECT_CACHE_EXTRAS_FILENAME[] = "objects_%d_%d_extras.slec"; - -const U32 MAX_NUM_OBJECT_ENTRIES = 128 ; -const U32 MIN_ENTRIES_TO_PURGE = 16 ; -const U32 INVALID_TIME = 0 ; -const char* object_cache_dirname = "objectcache"; -const char* header_filename = "object.cache"; - - -LLVOCache::LLVOCache(bool read_only) : - mInitialized(false), - mReadOnly(read_only), - mNumEntries(0), - mCacheSize(1), - mEnabled(true) -{ -#ifndef LL_TEST - mEnabled = gSavedSettings.getBOOL("ObjectCacheEnabled"); -#endif - mLocalAPRFilePoolp = new LLVolatileAPRPool() ; -} - -LLVOCache::~LLVOCache() -{ - if(mEnabled) - { - writeCacheHeader(); - clearCacheInMemory(); - } - delete mLocalAPRFilePoolp; -} - -void LLVOCache::setDirNames(ELLPath location) -{ - mHeaderFileName = gDirUtilp->getExpandedFilename(location, object_cache_dirname, header_filename); - mObjectCacheDirName = gDirUtilp->getExpandedFilename(location, object_cache_dirname); -} - -void LLVOCache::initCache(ELLPath location, U32 size, U32 cache_version) -{ - if(!mEnabled) - { - LL_WARNS() << "Not initializing cache: Cache is currently disabled." << LL_ENDL; - return ; - } - - if(mInitialized) - { - LL_WARNS() << "Cache already initialized." << LL_ENDL; - return ; - } - mInitialized = true; - - setDirNames(location); - if (!mReadOnly) - { - LLFile::mkdir(mObjectCacheDirName); - } - mCacheSize = llclamp(size, MIN_ENTRIES_TO_PURGE, MAX_NUM_OBJECT_ENTRIES); - mMetaInfo.mVersion = cache_version; - -#if defined(ADDRESS_SIZE) - U32 expected_address = ADDRESS_SIZE; -#else - U32 expected_address = 32; -#endif - mMetaInfo.mAddressSize = expected_address; - - readCacheHeader(); - - LL_INFOS() << "Viewer Object Cache Versions - expected: " << cache_version << " found: " << mMetaInfo.mVersion << LL_ENDL; - - if( mMetaInfo.mVersion != cache_version - || mMetaInfo.mAddressSize != expected_address) - { - mMetaInfo.mVersion = cache_version ; - mMetaInfo.mAddressSize = expected_address; - if(mReadOnly) //disable cache - { - clearCacheInMemory(); - } - else //delete the current cache if the format does not match. - { - LL_INFOS() << "Viewer Object Cache Versions unmatched. clearing cache." << LL_ENDL; - removeCache(); - } - } -} - -void LLVOCache::removeCache(ELLPath location, bool started) -{ - if(started) - { - removeCache(); - return; - } - - if(mReadOnly) - { - LL_WARNS() << "Not removing cache at " << location << ": Cache is currently in read-only mode." << LL_ENDL; - return ; - } - - LL_INFOS() << "about to remove the object cache due to settings." << LL_ENDL ; - - std::string mask = "*"; - std::string cache_dir = gDirUtilp->getExpandedFilename(location, object_cache_dirname); - LL_INFOS() << "Removing cache at " << cache_dir << LL_ENDL; - gDirUtilp->deleteFilesInDir(cache_dir, mask); //delete all files - LLFile::rmdir(cache_dir); - - clearCacheInMemory(); - mInitialized = false; -} - -void LLVOCache::removeCache() -{ - if(!mInitialized) - { - //OK to remove cache even it is not initialized. - LL_WARNS() << "Object cache is not initialized yet." << LL_ENDL; - } - - if(mReadOnly) - { - LL_WARNS() << "Not clearing object cache: Cache is currently in read-only mode." << LL_ENDL; - return ; - } - - std::string mask = "*"; - LL_INFOS() << "Removing object cache at " << mObjectCacheDirName << LL_ENDL; - gDirUtilp->deleteFilesInDir(mObjectCacheDirName, mask); - - clearCacheInMemory() ; - writeCacheHeader(); -} - -void LLVOCache::removeEntry(HeaderEntryInfo* entry) -{ - llassert_always(mInitialized); - if(mReadOnly) - { - return; - } - if(!entry) - { - return; - } - - header_entry_queue_t::iterator iter = mHeaderEntryQueue.find(entry); - if(iter != mHeaderEntryQueue.end()) - { - mHandleEntryMap.erase(entry->mHandle); - mHeaderEntryQueue.erase(iter); - removeFromCache(entry); - delete entry; - - mNumEntries = mHandleEntryMap.size() ; - } -} - -void LLVOCache::removeEntry(U64 handle) -{ - handle_entry_map_t::iterator iter = mHandleEntryMap.find(handle) ; - if(iter == mHandleEntryMap.end()) //no cache - { - return ; - } - HeaderEntryInfo* entry = iter->second ; - removeEntry(entry) ; -} - -void LLVOCache::clearCacheInMemory() -{ - if(!mHeaderEntryQueue.empty()) - { - for(header_entry_queue_t::iterator iter = mHeaderEntryQueue.begin(); iter != mHeaderEntryQueue.end(); ++iter) - { - delete *iter ; - } - mHeaderEntryQueue.clear(); - mHandleEntryMap.clear(); - mNumEntries = 0 ; - } - -} - -void LLVOCache::getObjectCacheFilename(U64 handle, std::string& filename) -{ - U32 region_x, region_y; - - grid_from_region_handle(handle, ®ion_x, ®ion_y); - filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, object_cache_dirname, - llformat(OBJECT_CACHE_FILENAME, region_x, region_y)); - - return ; -} - -std::string LLVOCache::getObjectCacheExtrasFilename(U64 handle) -{ - U32 region_x, region_y; - - grid_from_region_handle(handle, ®ion_x, ®ion_y); - return gDirUtilp->getExpandedFilename(LL_PATH_CACHE, object_cache_dirname, - llformat(OBJECT_CACHE_EXTRAS_FILENAME, region_x, region_y)); -} - -void LLVOCache::removeFromCache(HeaderEntryInfo* entry) -{ - if(mReadOnly) - { - LL_WARNS() << "Not removing cache for handle " << entry->mHandle << ": Cache is currently in read-only mode." << LL_ENDL; - return ; - } - - std::string filename; - getObjectCacheFilename(entry->mHandle, filename); - LLAPRFile::remove(filename, mLocalAPRFilePoolp); - entry->mTime = INVALID_TIME ; - updateEntry(entry) ; //update the head file. -} - -void LLVOCache::readCacheHeader() -{ - if(!mEnabled) - { - LL_WARNS() << "Not reading cache header: Cache is currently disabled." << LL_ENDL; - return; - } - - //clear stale info. - clearCacheInMemory(); - - bool success = true ; - if (LLAPRFile::isExist(mHeaderFileName, mLocalAPRFilePoolp)) - { - LLAPRFile apr_file(mHeaderFileName, APR_READ|APR_BINARY, mLocalAPRFilePoolp); - - //read the meta element - success = check_read(&apr_file, &mMetaInfo, sizeof(HeaderMetaInfo)) ; - - if(success) - { - HeaderEntryInfo* entry = NULL ; - mNumEntries = 0 ; - U32 num_read = 0 ; - while(num_read++ < MAX_NUM_OBJECT_ENTRIES) - { - if(!entry) - { - entry = new HeaderEntryInfo() ; - } - success = check_read(&apr_file, entry, sizeof(HeaderEntryInfo)); - - if(!success) //failed - { - LL_WARNS() << "Error reading cache header entry. (entry_index=" << mNumEntries << ")" << LL_ENDL; - delete entry ; - entry = NULL ; - break ; - } - else if(entry->mTime == INVALID_TIME) - { - continue ; //an empty entry - } - - entry->mIndex = mNumEntries++ ; - mHeaderEntryQueue.insert(entry) ; - mHandleEntryMap[entry->mHandle] = entry ; - entry = NULL ; - } - if(entry) - { - delete entry ; - } - } - - //--------- - //debug code - //---------- - //std::string name ; - //for(header_entry_queue_t::iterator iter = mHeaderEntryQueue.begin() ; success && iter != mHeaderEntryQueue.end(); ++iter) - //{ - // getObjectCacheFilename((*iter)->mHandle, name) ; - // LL_INFOS() << name << LL_ENDL ; - //} - //----------- - } - else - { - writeCacheHeader() ; - } - - if(!success) - { - removeCache() ; //failed to read header, clear the cache - } - else if(mNumEntries >= mCacheSize) - { - purgeEntries(mCacheSize) ; - } - - return ; -} - -void LLVOCache::writeCacheHeader() -{ - if (!mEnabled) - { - LL_WARNS() << "Not writing cache header: Cache is currently disabled." << LL_ENDL; - return; - } - - if(mReadOnly) - { - LL_WARNS() << "Not writing cache header: Cache is currently in read-only mode." << LL_ENDL; - return; - } - - bool success = true ; - { - LLAPRFile apr_file(mHeaderFileName, APR_CREATE|APR_WRITE|APR_BINARY, mLocalAPRFilePoolp); - - //write the meta element - success = check_write(&apr_file, &mMetaInfo, sizeof(HeaderMetaInfo)) ; - - - mNumEntries = 0 ; - for(header_entry_queue_t::iterator iter = mHeaderEntryQueue.begin() ; success && iter != mHeaderEntryQueue.end(); ++iter) - { - (*iter)->mIndex = mNumEntries++ ; - success = check_write(&apr_file, (void*)*iter, sizeof(HeaderEntryInfo)); - } - - mNumEntries = mHeaderEntryQueue.size() ; - if(success && mNumEntries < MAX_NUM_OBJECT_ENTRIES) - { - HeaderEntryInfo* entry = new HeaderEntryInfo() ; - entry->mTime = INVALID_TIME ; - for(S32 i = mNumEntries ; success && i < MAX_NUM_OBJECT_ENTRIES ; i++) - { - //fill the cache with the default entry. - success = check_write(&apr_file, entry, sizeof(HeaderEntryInfo)) ; - - } - delete entry ; - } - } - - if(!success) - { - clearCacheInMemory() ; - mReadOnly = true ; //disable the cache. - } - return ; -} - -bool LLVOCache::updateEntry(const HeaderEntryInfo* entry) -{ - LLAPRFile apr_file(mHeaderFileName, APR_WRITE|APR_BINARY, mLocalAPRFilePoolp); - apr_file.seek(APR_SET, entry->mIndex * sizeof(HeaderEntryInfo) + sizeof(HeaderMetaInfo)) ; - - return check_write(&apr_file, (void*)entry, sizeof(HeaderEntryInfo)) ; -} - -void LLVOCache::readFromCache(U64 handle, const LLUUID& id, LLVOCacheEntry::vocache_entry_map_t& cache_entry_map) -{ - if(!mEnabled) - { - LL_WARNS() << "Not reading cache for handle " << handle << "): Cache is currently disabled." << LL_ENDL; - return ; - } - llassert_always(mInitialized); - - handle_entry_map_t::iterator iter = mHandleEntryMap.find(handle) ; - if(iter == mHandleEntryMap.end()) //no cache - { - LL_WARNS() << "No handle map entry for " << handle << LL_ENDL; - return ; - } - - bool success = true ; - { - std::string filename; - LLUUID cache_id; - getObjectCacheFilename(handle, filename); - LLAPRFile apr_file(filename, APR_READ|APR_BINARY, mLocalAPRFilePoolp); - - success = check_read(&apr_file, cache_id.mData, UUID_BYTES); - - if(success) - { - if(cache_id != id) - { - LL_INFOS() << "Cache ID doesn't match for this region, discarding"<< LL_ENDL; - success = false ; - } - - if(success) - { - S32 num_entries; // if removal was enabled during write num_entries might be wrong - success = check_read(&apr_file, &num_entries, sizeof(S32)) ; - - if(success) - { - for (S32 i = 0; i < num_entries && apr_file.eof() != APR_EOF; i++) - { - LLPointer entry = new LLVOCacheEntry(&apr_file); - if (!entry->getLocalID()) - { - LL_WARNS() << "Aborting cache file load for " << filename << ", cache file corruption!" << LL_ENDL; - success = false ; - break ; - } - cache_entry_map[entry->getLocalID()] = entry; - } - } - } - } - } - - if(!success) - { - if(cache_entry_map.empty()) - { - removeEntry(iter->second) ; - } - } - - return ; -} - -void LLVOCache::readGenericExtrasFromCache(U64 handle, const LLUUID& id, LLVOCacheEntry::vocache_gltf_overrides_map_t& cache_extras_entry_map) -{ - if(!mEnabled) - { - LL_WARNS() << "Not reading cache for handle " << handle << "): Cache is currently disabled." << LL_ENDL; - return ; - } - llassert_always(mInitialized); - - handle_entry_map_t::iterator iter = mHandleEntryMap.find(handle) ; - if(iter == mHandleEntryMap.end()) //no cache - { - LL_WARNS() << "No handle map entry for " << handle << LL_ENDL; - return; - } - - std::string filename(getObjectCacheExtrasFilename(handle)); - llifstream in(filename, std::ios::in | std::ios::binary); - - std::string line; - std::getline(in, line); - if(!in.good()) { - LL_WARNS() << "Failed reading extras cache for handle " << handle << LL_ENDL; - return; - } - - if(!LLUUID::validate(line)) - { - LL_WARNS() << "Failed reading extras cache for handle" << handle << ". invalid uuid line: '" << line << "'" << LL_ENDL; - return; - } - - LLUUID cache_id(line); - if(cache_id != id) - { - LL_INFOS() << "Cache ID doesn't match for this region, discarding" << LL_ENDL; - return; - } - - U32 num_entries; // if removal was enabled during write num_entries might be wrong - std::getline(in, line); - if(!in.good()) { - LL_WARNS() << "Failed reading extras cache for handle " << handle << LL_ENDL; - return; - } - try { - num_entries = std::stol(line); - } - catch(std::logic_error&) // either invalid_argument or out_of_range - { - LL_WARNS() << "Failed reading extras cache for handle " << handle << ". unreadable num_entries" << LL_ENDL; - return; - } - - LL_DEBUGS("GLTF") << "Beginning reading extras cache for handle " << handle << ", " << num_entries << " entries" << LL_ENDL; - - LLSD entry_llsd; - for (U32 i = 0; i < num_entries && !in.eof(); i++) - { - static const U32 max_size = 4096; - bool success = LLSDSerialize::deserialize(entry_llsd, in, max_size); - // check bool(in) this time since eof is not a failure condition here - if(!success || !in) { - LL_WARNS() << "Failed reading extras cache for handle " << handle << ", entry number " << i << LL_ENDL; - return; - } - - LLGLTFOverrideCacheEntry entry; - entry.fromLLSD(entry_llsd); - U32 local_id = entry_llsd["local_id"].asInteger(); - cache_extras_entry_map[local_id] = entry; - } - - LL_DEBUGS("GLTF") << "Completed reading extras cache for handle " << handle << ", " << num_entries << " entries" << LL_ENDL; -} - -void LLVOCache::purgeEntries(U32 size) -{ - while(mHeaderEntryQueue.size() > size) - { - header_entry_queue_t::iterator iter = mHeaderEntryQueue.begin() ; - HeaderEntryInfo* entry = *iter ; - mHandleEntryMap.erase(entry->mHandle); - mHeaderEntryQueue.erase(iter) ; - removeFromCache(entry) ; - delete entry; - // TODO also delete extras - } - mNumEntries = mHandleEntryMap.size() ; -} - -void LLVOCache::writeToCache(U64 handle, const LLUUID& id, const LLVOCacheEntry::vocache_entry_map_t& cache_entry_map, bool dirty_cache, bool removal_enabled) -{ - if(!mEnabled) - { - LL_WARNS() << "Not writing cache for handle " << handle << "): Cache is currently disabled." << LL_ENDL; - return ; - } - llassert_always(mInitialized); - - if(mReadOnly) - { - LL_WARNS() << "Not writing cache for handle " << handle << "): Cache is currently in read-only mode." << LL_ENDL; - return ; - } - - HeaderEntryInfo* entry; - handle_entry_map_t::iterator iter = mHandleEntryMap.find(handle) ; - if(iter == mHandleEntryMap.end()) //new entry - { - if(mNumEntries >= mCacheSize - 1) - { - purgeEntries(mCacheSize - 1) ; - } - - entry = new HeaderEntryInfo(); - entry->mHandle = handle ; - entry->mTime = time(NULL) ; - entry->mIndex = mNumEntries++; - mHeaderEntryQueue.insert(entry) ; - mHandleEntryMap[handle] = entry ; - } - else - { - // Update access time. - entry = iter->second ; - - //resort - mHeaderEntryQueue.erase(entry) ; - - entry->mTime = time(NULL) ; - mHeaderEntryQueue.insert(entry) ; - } - - //update cache header - if(!updateEntry(entry)) - { - LL_WARNS() << "Failed to update cache header index " << entry->mIndex << ". handle = " << handle << LL_ENDL; - return ; //update failed. - } - - if(!dirty_cache) - { - LL_WARNS() << "Skipping write to cache for handle " << handle << ": cache not dirty" << LL_ENDL; - return ; //nothing changed, no need to update. - } - - //write to cache file - bool success = true ; - { - std::string filename; - getObjectCacheFilename(handle, filename); - LLAPRFile apr_file(filename, APR_CREATE|APR_WRITE|APR_BINARY|APR_TRUNCATE, mLocalAPRFilePoolp); - - success = check_write(&apr_file, (void*)id.mData, UUID_BYTES); - - if(success) - { - S32 num_entries = cache_entry_map.size(); // if removal is enabled num_entries might be wrong - success = check_write(&apr_file, &num_entries, sizeof(S32)); - if (success) - { - const S32 buffer_size = 32768; //should be large enough for couple MAX_ENTRY_BODY_SIZE - U8 data_buffer[buffer_size]; // generaly entries are fairly small, so collect them and drop onto disk in one go - S32 size_in_buffer = 0; - - // This can have a lot of entries, so might be better to dump them into buffer first and write in one go. - for (LLVOCacheEntry::vocache_entry_map_t::const_iterator iter = cache_entry_map.begin(); success && iter != cache_entry_map.end(); ++iter) - { - if (!removal_enabled || iter->second->isValid()) - { - S32 size = iter->second->writeToBuffer(data_buffer + size_in_buffer); - - if (size > ENTRY_HEADER_SIZE) // body is minimum of 1 - { - size_in_buffer += size; - } - else - { - success = false; - break; - } - - // Make sure we have space in buffer for next element - if (buffer_size - size_in_buffer < MAX_ENTRY_BODY_SIZE + ENTRY_HEADER_SIZE) - { - success = check_write(&apr_file, (void*)data_buffer, size_in_buffer); - size_in_buffer = 0; - if (!success) - { - break; - } - } - } - } - - if (success && size_in_buffer > 0) - { - // final write - success = check_write(&apr_file, (void*)data_buffer, size_in_buffer); - size_in_buffer = 0; - } - } - } - } - - if(!success) - { - removeEntry(entry) ; - } - - return ; -} - -void LLVOCache::writeGenericExtrasToCache(U64 handle, const LLUUID& id, const LLVOCacheEntry::vocache_gltf_overrides_map_t& cache_extras_entry_map, bool dirty_cache, bool removal_enabled) -{ - if(!mEnabled) - { - LL_WARNS() << "Not writing extras cache for handle " << handle << "): Cache is currently disabled." << LL_ENDL; - return; - } - llassert_always(mInitialized); - - if(mReadOnly) - { - LL_WARNS() << "Not writing extras cache for handle " << handle << "): Cache is currently in read-only mode." << LL_ENDL; - return; - } - - std::string filename(getObjectCacheExtrasFilename(handle)); - llofstream out(filename, std::ios::out | std::ios::binary); - if(!out.good()) - { - LL_WARNS() << "Failed writing extras cache for handle " << handle << LL_ENDL; - return; - // TODO - clean up broken cache file - } - - out << id << '\n'; - if(!out.good()) - { - LL_WARNS() << "Failed writing extras cache for handle " << handle << LL_ENDL; - return; - // TODO - clean up broken cache file - } - - U32 num_entries = cache_extras_entry_map.size(); - out << num_entries << '\n'; - if(!out.good()) - { - LL_WARNS() << "Failed writing extras cache for handle " << handle << LL_ENDL; - return; - // TODO - clean up broken cache file - } - - for (auto const & entry : cache_extras_entry_map) - { - S32 local_id = entry.first; - LLSD entry_llsd = entry.second.toLLSD(); - entry_llsd["local_id"] = local_id; - LLSDSerialize::serialize(entry_llsd, out, LLSDSerialize::LLSD_XML); - out << '\n'; - if(!out.good()) - { - LL_WARNS() << "Failed writing extras cache for handle " << handle << LL_ENDL; - return; - // TODO - clean up broken cache file - } - } - - LL_DEBUGS("GLTF") << "Completed writing extras cache for handle " << handle << ", " << num_entries << " entries" << LL_ENDL; -} +/** + * @file llvocache.cpp + * @brief Cache of objects on the viewer. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llvocache.h" +#include "llregionhandle.h" +#include "llviewercontrol.h" +#include "llviewerobjectlist.h" +#include "lldrawable.h" +#include "llviewerregion.h" +#include "llagentcamera.h" +#include "llsdserialize.h" + +//static variables +U32 LLVOCacheEntry::sMinFrameRange = 0; +F32 LLVOCacheEntry::sNearRadius = 1.0f; +F32 LLVOCacheEntry::sRearFarRadius = 1.0f; +F32 LLVOCacheEntry::sFrontPixelThreshold = 1.0f; +F32 LLVOCacheEntry::sRearPixelThreshold = 1.0f; +bool LLVOCachePartition::sNeedsOcclusionCheck = false; + +const S32 ENTRY_HEADER_SIZE = 6 * sizeof(S32); +const S32 MAX_ENTRY_BODY_SIZE = 10000; + +bool check_read(LLAPRFile* apr_file, void* src, S32 n_bytes) +{ + return apr_file->read(src, n_bytes) == n_bytes ; +} + +bool check_write(LLAPRFile* apr_file, void* src, S32 n_bytes) +{ + return apr_file->write(src, n_bytes) == n_bytes ; +} + +bool LLGLTFOverrideCacheEntry::fromLLSD(const LLSD& data) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; + + llassert(data.has("local_id")); + llassert(data.has("object_id")); + llassert(data.has("region_handle_x") && data.has("region_handle_y")); + + if (!data.has("local_id")) + { + return false; + } + + if (data.has("region_handle_x") && data.has("region_handle_y")) + { + // TODO start requiring this once server sends this for all messages + U32 region_handle_y = data["region_handle_y"].asInteger(); + U32 region_handle_x = data["region_handle_x"].asInteger(); + mRegionHandle = to_region_handle(region_handle_x, region_handle_y); + } + else + { + return false; + } + + mLocalId = data["local_id"].asInteger(); + mObjectId = data["object_id"]; + + // message should be interpreted thusly: + /// sides is a list of face indices + // gltf_llsd is a list of corresponding GLTF override LLSD + // any side not represented in "sides" has no override + if (data.has("sides") && data.has("gltf_llsd")) + { + LLSD const& sides = data.get("sides"); + LLSD const& gltf_llsd = data.get("gltf_llsd"); + + if (sides.isArray() && gltf_llsd.isArray() && + sides.size() != 0 && + sides.size() == gltf_llsd.size()) + { + for (int i = 0; i < sides.size(); ++i) + { + S32 side_idx = sides[i].asInteger(); + mSides[side_idx] = gltf_llsd[i]; + LLGLTFMaterial* override_mat = new LLGLTFMaterial(); + override_mat->applyOverrideLLSD(gltf_llsd[i]); + mGLTFMaterial[side_idx] = override_mat; + } + } + else + { + LL_WARNS_IF(sides.size() != 0, "GLTF") << "broken override cache entry" << LL_ENDL; + } + } + + llassert(mSides.size() == mGLTFMaterial.size()); +#ifdef SHOW_ASSERT + for (auto const & side : mSides) + { + // check that mSides and mGLTFMaterial have exactly the same keys present + llassert(mGLTFMaterial.count(side.first) == 1); + } +#endif + + return true; +} + +LLSD LLGLTFOverrideCacheEntry::toLLSD() const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; + LLSD data; + U32 region_handle_x, region_handle_y; + from_region_handle(mRegionHandle, ®ion_handle_x, ®ion_handle_y); + data["region_handle_y"] = LLSD::Integer(region_handle_y); + data["region_handle_x"] = LLSD::Integer(region_handle_x); + + data["object_id"] = mObjectId; + data["local_id"] = (LLSD::Integer) mLocalId; + + llassert(mSides.size() == mGLTFMaterial.size()); + for (auto const & side : mSides) + { + // check that mSides and mGLTFMaterial have exactly the same keys present + llassert(mGLTFMaterial.count(side.first) == 1); + data["sides"].append(LLSD::Integer(side.first)); + data["gltf_llsd"].append(side.second); + } + + return data; +} + +//--------------------------------------------------------------------------- +// LLVOCacheEntry +//--------------------------------------------------------------------------- + +LLVOCacheEntry::LLVOCacheEntry(U32 local_id, U32 crc, LLDataPackerBinaryBuffer &dp) +: LLViewerOctreeEntryData(LLViewerOctreeEntry::LLVOCACHEENTRY), + mLocalID(local_id), + mCRC(crc), + mUpdateFlags(-1), + mHitCount(0), + mDupeCount(0), + mCRCChangeCount(0), + mState(INACTIVE), + mSceneContrib(0.f), + mValid(true), + mParentID(0), + mBSphereRadius(-1.0f) +{ + mBuffer = new U8[dp.getBufferSize()]; + mDP.assignBuffer(mBuffer, dp.getBufferSize()); + mDP = dp; +} + +LLVOCacheEntry::LLVOCacheEntry() +: LLViewerOctreeEntryData(LLViewerOctreeEntry::LLVOCACHEENTRY), + mLocalID(0), + mCRC(0), + mUpdateFlags(-1), + mHitCount(0), + mDupeCount(0), + mCRCChangeCount(0), + mBuffer(NULL), + mState(INACTIVE), + mSceneContrib(0.f), + mValid(true), + mParentID(0), + mBSphereRadius(-1.0f) +{ + mDP.assignBuffer(mBuffer, 0); +} + +LLVOCacheEntry::LLVOCacheEntry(LLAPRFile* apr_file) +: LLViewerOctreeEntryData(LLViewerOctreeEntry::LLVOCACHEENTRY), + mBuffer(NULL), + mUpdateFlags(-1), + mState(INACTIVE), + mSceneContrib(0.f), + mValid(false), + mParentID(0), + mBSphereRadius(-1.0f) +{ + S32 size = -1; + bool success; + static U8 data_buffer[ENTRY_HEADER_SIZE]; + + mDP.assignBuffer(mBuffer, 0); + + success = check_read(apr_file, (void *)data_buffer, ENTRY_HEADER_SIZE); + if (success) + { + memcpy(&mLocalID, data_buffer, sizeof(U32)); + memcpy(&mCRC, data_buffer + sizeof(U32), sizeof(U32)); + memcpy(&mHitCount, data_buffer + (2 * sizeof(U32)), sizeof(S32)); + memcpy(&mDupeCount, data_buffer + (3 * sizeof(U32)), sizeof(S32)); + memcpy(&mCRCChangeCount, data_buffer + (4 * sizeof(U32)), sizeof(S32)); + memcpy(&size, data_buffer + (5 * sizeof(U32)), sizeof(S32)); + + // Corruption in the cache entries + if ((size > MAX_ENTRY_BODY_SIZE) || (size < 1)) + { + // We've got a bogus size, skip reading it. + // We won't bother seeking, because the rest of this file + // is likely bogus, and will be tossed anyway. + LL_WARNS() << "Bogus cache entry, size " << size << ", aborting!" << LL_ENDL; + success = false; + } + } + if(success && size > 0) + { + mBuffer = new U8[size]; + success = check_read(apr_file, mBuffer, size); + + if(success) + { + mDP.assignBuffer(mBuffer, size); + } + else + { + delete[] mBuffer ; + mBuffer = NULL ; + } + } + + if(!success) + { + mLocalID = 0; + mCRC = 0; + mHitCount = 0; + mDupeCount = 0; + mCRCChangeCount = 0; + mBuffer = NULL; + mEntry = NULL; + mState = INACTIVE; + } +} + +LLVOCacheEntry::~LLVOCacheEntry() +{ + mDP.freeBuffer(); +} + +void LLVOCacheEntry::updateEntry(U32 crc, LLDataPackerBinaryBuffer &dp) +{ + if(mCRC != crc) + { + mCRC = crc; + mCRCChangeCount++; + } + + mDP.freeBuffer(); + + llassert_always(dp.getBufferSize() > 0); + mBuffer = new U8[dp.getBufferSize()]; + mDP.assignBuffer(mBuffer, dp.getBufferSize()); + mDP = dp; +} + +void LLVOCacheEntry::setParentID(U32 id) +{ + if(mParentID != id) + { + removeAllChildren(); + mParentID = id; + } +} + +void LLVOCacheEntry::removeAllChildren() +{ + if(mChildrenList.empty()) + { + return; + } + + for(vocache_entry_set_t::iterator iter = mChildrenList.begin(); iter != mChildrenList.end(); ++iter) + { + (*iter)->setParentID(0); + } + mChildrenList.clear(); + + return; +} + +//virtual +void LLVOCacheEntry::setOctreeEntry(LLViewerOctreeEntry* entry) +{ + if(!entry && mDP.getBufferSize() > 0) + { + LLUUID fullid; + LLViewerObject::unpackUUID(&mDP, fullid, "ID"); + + LLViewerObject* obj = gObjectList.findObject(fullid); + if(obj && obj->mDrawable) + { + entry = obj->mDrawable->getEntry(); + } + } + + LLViewerOctreeEntryData::setOctreeEntry(entry); +} + +void LLVOCacheEntry::setState(U32 state) +{ + if(state > LOW_BITS) //special states + { + mState |= (HIGH_BITS & state); + return; + } + + // + //otherwise LOW_BITS states + // + clearState(LOW_BITS); + mState |= (LOW_BITS & state); + + if(getState() == ACTIVE) + { + const S32 MIN_INTERVAL = 64 + sMinFrameRange; + U32 last_visible = getVisible(); + + setVisible(); + + U32 cur_visible = getVisible(); + if(cur_visible - last_visible > MIN_INTERVAL || + cur_visible < MIN_INTERVAL) + { + mLastCameraUpdated = 0; //reset + } + else + { + mLastCameraUpdated = LLViewerRegion::sLastCameraUpdated; + } + } +} + +void LLVOCacheEntry::addChild(LLVOCacheEntry* entry) +{ + llassert(entry != NULL); + llassert(entry->getParentID() == mLocalID); + llassert(entry->getEntry() != NULL); + + if(!entry || !entry->getEntry() || entry->getParentID() != mLocalID) + { + return; + } + + mChildrenList.insert(entry); + + //update parent bbox + if(getEntry() != NULL && isState(INACTIVE)) + { + updateParentBoundingInfo(entry); + resetVisible(); + } +} + +void LLVOCacheEntry::removeChild(LLVOCacheEntry* entry) +{ + entry->setParentID(0); + + vocache_entry_set_t::iterator iter = mChildrenList.find(entry); + if(iter != mChildrenList.end()) + { + mChildrenList.erase(iter); + } +} + +//remove the first child, and return it. +LLVOCacheEntry* LLVOCacheEntry::getChild() +{ + LLVOCacheEntry* child = NULL; + vocache_entry_set_t::iterator iter = mChildrenList.begin(); + if(iter != mChildrenList.end()) + { + child = *iter; + mChildrenList.erase(iter); + } + + return child; +} + +LLDataPackerBinaryBuffer *LLVOCacheEntry::getDP() +{ + if (mDP.getBufferSize() == 0) + { + //LL_INFOS() << "Not getting cache entry, invalid!" << LL_ENDL; + return NULL; + } + + return &mDP; +} + +void LLVOCacheEntry::recordHit() +{ + mHitCount++; +} + + +void LLVOCacheEntry::dump() const +{ + LL_INFOS() << "local " << mLocalID + << " crc " << mCRC + << " hits " << mHitCount + << " dupes " << mDupeCount + << " change " << mCRCChangeCount + << LL_ENDL; +} + +S32 LLVOCacheEntry::writeToBuffer(U8 *data_buffer) const +{ + S32 size = mDP.getBufferSize(); + + if (size > MAX_ENTRY_BODY_SIZE) + { + LL_WARNS() << "Failed to write entry with size above allowed limit: " << size << LL_ENDL; + return 0; + } + + memcpy(data_buffer, &mLocalID, sizeof(U32)); + memcpy(data_buffer + sizeof(U32), &mCRC, sizeof(U32)); + memcpy(data_buffer + (2 * sizeof(U32)), &mHitCount, sizeof(S32)); + memcpy(data_buffer + (3 * sizeof(U32)), &mDupeCount, sizeof(S32)); + memcpy(data_buffer + (4 * sizeof(U32)), &mCRCChangeCount, sizeof(S32)); + memcpy(data_buffer + (5 * sizeof(U32)), &size, sizeof(S32)); + memcpy(data_buffer + ENTRY_HEADER_SIZE, (void*)mBuffer, size); + + return ENTRY_HEADER_SIZE + size; +} + +#ifndef LL_TEST +//static +void LLVOCacheEntry::updateDebugSettings() +{ + static LLFrameTimer timer; + if(timer.getElapsedTimeF32() < 1.0f) //update frequency once per second. + { + return; + } + timer.reset(); + + //objects within the view frustum whose visible area is greater than this threshold will be loaded + static LLCachedControl front_pixel_threshold(gSavedSettings,"SceneLoadFrontPixelThreshold"); + sFrontPixelThreshold = front_pixel_threshold; + + //objects out of the view frustum whose visible area is greater than this threshold will remain loaded + static LLCachedControl rear_pixel_threshold(gSavedSettings,"SceneLoadRearPixelThreshold"); + sRearPixelThreshold = rear_pixel_threshold; + sRearPixelThreshold = llmax(sRearPixelThreshold, sFrontPixelThreshold); //can not be smaller than sFrontPixelThreshold. + + //make parameters adaptive to memory usage + //starts to put restrictions from low_mem_bound_MB, apply tightest restrictions when hits high_mem_bound_MB + static LLCachedControl low_mem_bound_MB(gSavedSettings,"SceneLoadLowMemoryBound"); + static LLCachedControl high_mem_bound_MB(gSavedSettings,"SceneLoadHighMemoryBound"); + + LLMemory::updateMemoryInfo() ; + U32 allocated_mem = LLMemory::getAllocatedMemKB().value(); + static const F32 KB_to_MB = 1.f / 1024.f; + U32 clamped_memory = llclamp(allocated_mem * KB_to_MB, (F32) low_mem_bound_MB, (F32) high_mem_bound_MB); + const F32 adjust_range = high_mem_bound_MB - low_mem_bound_MB; + const F32 adjust_factor = (high_mem_bound_MB - clamped_memory) / adjust_range; // [0, 1] + + //min radius: all objects within this radius remain loaded in memory + static LLCachedControl min_radius(gSavedSettings,"SceneLoadMinRadius"); + static const F32 MIN_RADIUS = 1.0f; + const F32 draw_radius = gAgentCamera.mDrawDistance; + const F32 clamped_min_radius = llclamp((F32) min_radius, MIN_RADIUS, draw_radius); // [1, mDrawDistance] + sNearRadius = MIN_RADIUS + ((clamped_min_radius - MIN_RADIUS) * adjust_factor); + + // a percentage of draw distance beyond which all objects outside of view frustum will be unloaded, regardless of pixel threshold + static LLCachedControl rear_max_radius_frac(gSavedSettings,"SceneLoadRearMaxRadiusFraction"); + const F32 min_radius_plus_one = sNearRadius + 1.f; + const F32 max_radius = rear_max_radius_frac * gAgentCamera.mDrawDistance; + const F32 clamped_max_radius = llclamp(max_radius, min_radius_plus_one, draw_radius); // [sNearRadius, mDrawDistance] + sRearFarRadius = min_radius_plus_one + ((clamped_max_radius - min_radius_plus_one) * adjust_factor); + + //the number of frames invisible objects stay in memory + static LLCachedControl inv_obj_time(gSavedSettings,"NonvisibleObjectsInMemoryTime"); + static const U32 MIN_FRAMES = 10; + static const U32 MAX_FRAMES = 64; + const U32 clamped_frames = inv_obj_time ? llclamp((U32) inv_obj_time, MIN_FRAMES, MAX_FRAMES) : MAX_FRAMES; // [10, 64], with zero => 64 + sMinFrameRange = MIN_FRAMES + ((clamped_frames - MIN_FRAMES) * adjust_factor); +} +#endif // LL_TEST + +//static +F32 LLVOCacheEntry::getSquaredPixelThreshold(bool is_front) +{ + F32 threshold; + if(is_front) + { + threshold = sFrontPixelThreshold; + } + else + { + threshold = sRearPixelThreshold; + } + + //object projected area threshold + F32 pixel_meter_ratio = LLViewerCamera::getInstance()->getPixelMeterRatio(); + F32 projection_threshold = pixel_meter_ratio > 0.f ? threshold / pixel_meter_ratio : 0.f; + projection_threshold *= projection_threshold; + + return projection_threshold; +} + +bool LLVOCacheEntry::isAnyVisible(const LLVector4a& camera_origin, const LLVector4a& local_camera_origin, F32 dist_threshold) +{ + LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*)getGroup(); + if(!group) + { + return false; + } + + //any visible + bool vis = group->isAnyRecentlyVisible(); + + //not ready to remove + if(!vis) + { + S32 cur_vis = llmax(group->getAnyVisible(), (S32)getVisible()); + vis = (cur_vis + sMinFrameRange > LLViewerOctreeEntryData::getCurrentFrame()); + } + + //within the back sphere + if(!vis && !mParentID && !group->isOcclusionState(LLOcclusionCullingGroup::OCCLUDED)) + { + LLVector4a lookAt; + + if(mBSphereRadius > 0.f) + { + lookAt.setSub(mBSphereCenter, local_camera_origin); + dist_threshold += mBSphereRadius; + } + else + { + lookAt.setSub(getPositionGroup(), camera_origin); + dist_threshold += getBinRadius(); + } + + vis = (lookAt.dot3(lookAt).getF32() < dist_threshold * dist_threshold); + } + + return vis; +} + +void LLVOCacheEntry::calcSceneContribution(const LLVector4a& camera_origin, bool needs_update, U32 last_update, F32 max_dist) +{ + if(!needs_update && getVisible() >= last_update) + { + return; //no need to update + } + + LLVector4a lookAt; + lookAt.setSub(getPositionGroup(), camera_origin); + F32 distance = lookAt.getLength3().getF32(); + distance -= sNearRadius; + + if(distance <= 0.f) + { + //nearby objects, set a large number + const F32 LARGE_SCENE_CONTRIBUTION = 1000.f; //a large number to force to load the object. + mSceneContrib = LARGE_SCENE_CONTRIBUTION; + } + else + { + F32 rad = getBinRadius(); + max_dist += rad; + + if(distance + sNearRadius < max_dist) + { + mSceneContrib = (rad * rad) / distance; + } + else + { + mSceneContrib = 0.f; //out of draw distance, not to load + } + } + + setVisible(); +} + +void LLVOCacheEntry::saveBoundingSphere() +{ + mBSphereCenter = getPositionGroup(); + mBSphereRadius = getBinRadius(); +} + +void LLVOCacheEntry::setBoundingInfo(const LLVector3& pos, const LLVector3& scale) +{ + LLVector4a center, newMin, newMax; + center.load3(pos.mV); + LLVector4a size; + size.load3(scale.mV); + newMin.setSub(center, size); + newMax.setAdd(center, size); + + setPositionGroup(center); + setSpatialExtents(newMin, newMax); + + if(getNumOfChildren() > 0) //has children + { + updateParentBoundingInfo(); + } + else + { + setBinRadius(llmin(size.getLength3().getF32() * 4.f, 256.f)); + } +} + +//make the parent bounding box to include all children +void LLVOCacheEntry::updateParentBoundingInfo() +{ + if(mChildrenList.empty()) + { + return; + } + + for(vocache_entry_set_t::iterator iter = mChildrenList.begin(); iter != mChildrenList.end(); ++iter) + { + updateParentBoundingInfo(*iter); + } + resetVisible(); +} + +//make the parent bounding box to include this child +void LLVOCacheEntry::updateParentBoundingInfo(const LLVOCacheEntry* child) +{ + const LLVector4a* child_exts = child->getSpatialExtents(); + LLVector4a newMin, newMax; + newMin = child_exts[0]; + newMax = child_exts[1]; + + //move to regional space. + { + const LLVector4a& parent_pos = getPositionGroup(); + newMin.add(parent_pos); + newMax.add(parent_pos); + } + + //update parent's bbox(min, max) + const LLVector4a* parent_exts = getSpatialExtents(); + update_min_max(newMin, newMax, parent_exts[0]); + update_min_max(newMin, newMax, parent_exts[1]); + for(S32 i = 0; i < 4; i++) + { + llclamp(newMin[i], 0.f, 256.f); + llclamp(newMax[i], 0.f, 256.f); + } + setSpatialExtents(newMin, newMax); + + //update parent's bbox center + LLVector4a center; + center.setAdd(newMin, newMax); + center.mul(0.5f); + setPositionGroup(center); + + //update parent's bbox size vector + LLVector4a size; + size.setSub(newMax, newMin); + size.mul(0.5f); + setBinRadius(llmin(size.getLength3().getF32() * 4.f, 256.f)); +} +//------------------------------------------------------------------- +//LLVOCachePartition +//------------------------------------------------------------------- +LLVOCacheGroup::~LLVOCacheGroup() +{ + if(mOcclusionState[LLViewerCamera::CAMERA_WORLD] & ACTIVE_OCCLUSION) + { + ((LLVOCachePartition*)mSpatialPartition)->removeOccluder(this); + } +} + +//virtual +void LLVOCacheGroup::handleChildAddition(const OctreeNode* parent, OctreeNode* child) +{ + if (child->getListenerCount() == 0) + { + new LLVOCacheGroup(child, mSpatialPartition); + } + else + { + OCT_ERRS << "LLVOCacheGroup redundancy detected." << LL_ENDL; + } + + unbound(); + + ((LLViewerOctreeGroup*)child->getListener(0))->unbound(); +} + +LLVOCachePartition::LLVOCachePartition(LLViewerRegion* regionp) +{ + mLODPeriod = 16; + mRegionp = regionp; + mPartitionType = LLViewerRegion::PARTITION_VO_CACHE; + mBackSlectionEnabled = -1; + mIdleHash = 0; + + for(S32 i = 0; i < LLViewerCamera::NUM_CAMERAS; i++) + { + mCulledTime[i] = 0; + } + mCullHistory = -1; + + new LLVOCacheGroup(mOctree, this); +} + +LLVOCachePartition::~LLVOCachePartition() +{ + // SL-17276 make sure to do base class cleanup while this instance + // can still be treated as an LLVOCachePartition + cleanup(); +} + +bool LLVOCachePartition::addEntry(LLViewerOctreeEntry* entry) +{ + llassert(entry->hasVOCacheEntry()); + + if(!llfinite(entry->getBinRadius()) || !entry->getPositionGroup().isFinite3()) + { + return false; //data corrupted + } + + mOctree->insert(entry); + + return true; +} + +void LLVOCachePartition::removeEntry(LLViewerOctreeEntry* entry) +{ + entry->getVOCacheEntry()->setGroup(NULL); + + llassert(!entry->getGroup()); +} + +class LLVOCacheOctreeCull : public LLViewerOctreeCull +{ +public: + LLVOCacheOctreeCull(LLCamera* camera, LLViewerRegion* regionp, + const LLVector3& shift, bool use_object_cache_occlusion, F32 pixel_threshold, LLVOCachePartition* part) + : LLViewerOctreeCull(camera), + mRegionp(regionp), + mPartition(part), + mPixelThreshold(pixel_threshold) + { + mLocalShift = shift; + mUseObjectCacheOcclusion = use_object_cache_occlusion; + mNearRadius = LLVOCacheEntry::sNearRadius; + } + + virtual bool earlyFail(LLViewerOctreeGroup* base_group) + { + if( mUseObjectCacheOcclusion && + base_group->getOctreeNode()->getParent()) //never occlusion cull the root node + { + LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*)base_group; + if(group->needsUpdate()) + { + //needs to issue new occlusion culling check, perform view culling check first. + return false; + } + + group->checkOcclusion(); + + if (group->isOcclusionState(LLOcclusionCullingGroup::OCCLUDED)) + { + return true; + } + } + + return false; + } + + virtual S32 frustumCheck(const LLViewerOctreeGroup* group) + { +#if 0 + S32 res = AABBInRegionFrustumGroupBounds(group); +#else + S32 res = AABBInRegionFrustumNoFarClipGroupBounds(group); + if (res != 0) + { + res = llmin(res, AABBRegionSphereIntersectGroupExtents(group, mLocalShift)); + } +#endif + + return res; + } + + virtual S32 frustumCheckObjects(const LLViewerOctreeGroup* group) + { +#if 0 + S32 res = AABBInRegionFrustumObjectBounds(group); +#else + S32 res = AABBInRegionFrustumNoFarClipObjectBounds(group); + if (res != 0) + { + res = llmin(res, AABBRegionSphereIntersectObjectExtents(group, mLocalShift)); + } +#endif + + if(res != 0) + { + //check if the objects projection large enough + const LLVector4a* exts = group->getObjectExtents(); + res = checkProjectionArea(exts[0], exts[1], mLocalShift, mPixelThreshold, mNearRadius); + } + + return res; + } + + virtual void processGroup(LLViewerOctreeGroup* base_group) + { + if( !mUseObjectCacheOcclusion || + !base_group->getOctreeNode()->getParent()) + { + //no occlusion check + if(mRegionp->addVisibleGroup(base_group)) + { + base_group->setVisible(); + } + return; + } + + LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*)base_group; + if(group->needsUpdate() || !group->isRecentlyVisible())//needs to issue new occlusion culling check. + { + mPartition->addOccluders(group); + group->setVisible(); + return ; //wait for occlusion culling result + } + + if(group->isOcclusionState(LLOcclusionCullingGroup::QUERY_PENDING) || + group->isOcclusionState(LLOcclusionCullingGroup::ACTIVE_OCCLUSION)) + { + //keep waiting + group->setVisible(); + } + else + { + if(mRegionp->addVisibleGroup(base_group)) + { + base_group->setVisible(); + } + } + } + +private: + LLVOCachePartition* mPartition; + LLViewerRegion* mRegionp; + LLVector3 mLocalShift; //shift vector from agent space to local region space. + F32 mPixelThreshold; + F32 mNearRadius; + bool mUseObjectCacheOcclusion; +}; + +//select objects behind camera +class LLVOCacheOctreeBackCull : public LLViewerOctreeCull +{ +public: + LLVOCacheOctreeBackCull(LLCamera* camera, const LLVector3& shift, LLViewerRegion* regionp, F32 pixel_threshold, bool use_occlusion) + : LLViewerOctreeCull(camera), mRegionp(regionp), mPixelThreshold(pixel_threshold), mUseObjectCacheOcclusion(use_occlusion) + { + mLocalShift = shift; + mSphereRadius = LLVOCacheEntry::sRearFarRadius; + } + + virtual bool earlyFail(LLViewerOctreeGroup* base_group) + { + if( mUseObjectCacheOcclusion && + base_group->getOctreeNode()->getParent()) //never occlusion cull the root node + { + LLOcclusionCullingGroup* group = (LLOcclusionCullingGroup*)base_group; + + if (group->getOcclusionState() > 0) //occlusion state is not clear. + { + return true; + } + } + + return false; + } + + virtual S32 frustumCheck(const LLViewerOctreeGroup* group) + { + const LLVector4a* exts = group->getExtents(); + return backSphereCheck(exts[0], exts[1]); + } + + virtual S32 frustumCheckObjects(const LLViewerOctreeGroup* group) + { + const LLVector4a* exts = group->getObjectExtents(); + if(backSphereCheck(exts[0], exts[1])) + { + //check if the objects projection large enough + const LLVector4a* exts = group->getObjectExtents(); + return checkProjectionArea(exts[0], exts[1], mLocalShift, mPixelThreshold, mSphereRadius); + } + return false; + } + + virtual void processGroup(LLViewerOctreeGroup* base_group) + { + mRegionp->addVisibleGroup(base_group); + return; + } + +private: + //a sphere around the camera origin, including objects behind camera. + S32 backSphereCheck(const LLVector4a& min, const LLVector4a& max) + { + return AABBSphereIntersect(min, max, mCamera->getOrigin() - mLocalShift, mSphereRadius); + } + +private: + F32 mSphereRadius; + LLViewerRegion* mRegionp; + LLVector3 mLocalShift; //shift vector from agent space to local region space. + F32 mPixelThreshold; + bool mUseObjectCacheOcclusion; +}; + +void LLVOCachePartition::selectBackObjects(LLCamera &camera, F32 pixel_threshold, bool use_occlusion) +{ + if(LLViewerCamera::sCurCameraID != LLViewerCamera::CAMERA_WORLD) + { + return; + } + + if(mBackSlectionEnabled < 0) + { + mBackSlectionEnabled = LLVOCacheEntry::sMinFrameRange - 1; + mBackSlectionEnabled = llmax(mBackSlectionEnabled, (S32)1); + } + + if(!mBackSlectionEnabled) + { + return; + } + + //localize the camera + LLVector3 region_agent = mRegionp->getOriginAgent(); + + LLVOCacheOctreeBackCull culler(&camera, region_agent, mRegionp, pixel_threshold, use_occlusion); + culler.traverse(mOctree); + + mBackSlectionEnabled--; + if(!mRegionp->getNumOfVisibleGroups()) + { + mBackSlectionEnabled = 0; + } + + return; +} + +#ifndef LL_TEST +S32 LLVOCachePartition::cull(LLCamera &camera, bool do_occlusion) +{ + static LLCachedControl use_object_cache_occlusion(gSavedSettings,"UseObjectCacheOcclusion"); + + if(!LLViewerRegion::sVOCacheCullingEnabled) + { + return 0; + } + if(mRegionp->isPaused()) + { + return 0; + } + + ((LLViewerOctreeGroup*)mOctree->getListener(0))->rebound(); + + if(LLViewerCamera::sCurCameraID != LLViewerCamera::CAMERA_WORLD) + { + return 0; //no need for those cameras. + } + + if(mCulledTime[LLViewerCamera::sCurCameraID] == LLViewerOctreeEntryData::getCurrentFrame()) + { + return 0; //already culled + } + mCulledTime[LLViewerCamera::sCurCameraID] = LLViewerOctreeEntryData::getCurrentFrame(); + + if(!mCullHistory && LLViewerRegion::isViewerCameraStatic()) + { + U32 seed = llmax(mLODPeriod >> 1, (U32)4); + if(LLViewerCamera::sCurCameraID == LLViewerCamera::CAMERA_WORLD) + { + if(!(LLViewerOctreeEntryData::getCurrentFrame() % seed)) + { + mIdleHash = (mIdleHash + 1) % seed; + } + } + if(LLViewerOctreeEntryData::getCurrentFrame() % seed != mIdleHash) + { + mFrontCull = false; + + //process back objects selection + selectBackObjects(camera, LLVOCacheEntry::getSquaredPixelThreshold(mFrontCull), + do_occlusion && use_object_cache_occlusion); + return 0; //nothing changed, reduce frequency of culling + } + } + else + { + mBackSlectionEnabled = -1; //reset it. + } + + //localize the camera + LLVector3 region_agent = mRegionp->getOriginAgent(); + camera.calcRegionFrustumPlanes(region_agent, gAgentCamera.mDrawDistance); + + mFrontCull = true; + LLVOCacheOctreeCull culler(&camera, mRegionp, region_agent, do_occlusion && use_object_cache_occlusion, + LLVOCacheEntry::getSquaredPixelThreshold(mFrontCull), this); + culler.traverse(mOctree); + + if(!sNeedsOcclusionCheck) + { + sNeedsOcclusionCheck = !mOccludedGroups.empty(); + } + return 1; +} +#endif // LL_TEST + +void LLVOCachePartition::setCullHistory(bool has_new_object) +{ + mCullHistory <<= 1; + mCullHistory |= static_cast(has_new_object); +} + +void LLVOCachePartition::addOccluders(LLViewerOctreeGroup* gp) +{ + LLVOCacheGroup* group = (LLVOCacheGroup*)gp; + + if(!group->isOcclusionState(LLOcclusionCullingGroup::ACTIVE_OCCLUSION)) + { + group->setOcclusionState(LLOcclusionCullingGroup::ACTIVE_OCCLUSION); + mOccludedGroups.insert(group); + } +} + +void LLVOCachePartition::processOccluders(LLCamera* camera) +{ + if(mOccludedGroups.empty()) + { + return; + } + if(LLViewerCamera::sCurCameraID != LLViewerCamera::CAMERA_WORLD) + { + return; //no need for those cameras. + } + + LLVector3 region_agent = mRegionp->getOriginAgent(); + LLVector4a shift(region_agent[0], region_agent[1], region_agent[2]); + for(std::set::iterator iter = mOccludedGroups.begin(); iter != mOccludedGroups.end(); ++iter) + { + LLVOCacheGroup* group = *iter; + if(group->isOcclusionState(LLOcclusionCullingGroup::ACTIVE_OCCLUSION)) + { + group->doOcclusion(camera, &shift); + group->clearOcclusionState(LLOcclusionCullingGroup::ACTIVE_OCCLUSION); + } + } + + //safe to clear mOccludedGroups here because only the world camera accesses it. + mOccludedGroups.clear(); + sNeedsOcclusionCheck = false; +} + +void LLVOCachePartition::resetOccluders() +{ + if(mOccludedGroups.empty()) + { + return; + } + + for(std::set::iterator iter = mOccludedGroups.begin(); iter != mOccludedGroups.end(); ++iter) + { + LLVOCacheGroup* group = *iter; + group->clearOcclusionState(LLOcclusionCullingGroup::ACTIVE_OCCLUSION); + } + mOccludedGroups.clear(); + sNeedsOcclusionCheck = false; +} + +void LLVOCachePartition::removeOccluder(LLVOCacheGroup* group) +{ + if(mOccludedGroups.empty()) + { + return; + } + mOccludedGroups.erase(group); +} +//------------------------------------------------------------------- +//LLVOCache +//------------------------------------------------------------------- +// Format strings used to construct filename for the object cache +static const char OBJECT_CACHE_FILENAME[] = "objects_%d_%d.slc"; +static const char OBJECT_CACHE_EXTRAS_FILENAME[] = "objects_%d_%d_extras.slec"; + +const U32 MAX_NUM_OBJECT_ENTRIES = 128 ; +const U32 MIN_ENTRIES_TO_PURGE = 16 ; +const U32 INVALID_TIME = 0 ; +const char* object_cache_dirname = "objectcache"; +const char* header_filename = "object.cache"; + + +LLVOCache::LLVOCache(bool read_only) : + mInitialized(false), + mReadOnly(read_only), + mNumEntries(0), + mCacheSize(1), + mEnabled(true) +{ +#ifndef LL_TEST + mEnabled = gSavedSettings.getBOOL("ObjectCacheEnabled"); +#endif + mLocalAPRFilePoolp = new LLVolatileAPRPool() ; +} + +LLVOCache::~LLVOCache() +{ + if(mEnabled) + { + writeCacheHeader(); + clearCacheInMemory(); + } + delete mLocalAPRFilePoolp; +} + +void LLVOCache::setDirNames(ELLPath location) +{ + mHeaderFileName = gDirUtilp->getExpandedFilename(location, object_cache_dirname, header_filename); + mObjectCacheDirName = gDirUtilp->getExpandedFilename(location, object_cache_dirname); +} + +void LLVOCache::initCache(ELLPath location, U32 size, U32 cache_version) +{ + if(!mEnabled) + { + LL_WARNS() << "Not initializing cache: Cache is currently disabled." << LL_ENDL; + return ; + } + + if(mInitialized) + { + LL_WARNS() << "Cache already initialized." << LL_ENDL; + return ; + } + mInitialized = true; + + setDirNames(location); + if (!mReadOnly) + { + LLFile::mkdir(mObjectCacheDirName); + } + mCacheSize = llclamp(size, MIN_ENTRIES_TO_PURGE, MAX_NUM_OBJECT_ENTRIES); + mMetaInfo.mVersion = cache_version; + +#if defined(ADDRESS_SIZE) + U32 expected_address = ADDRESS_SIZE; +#else + U32 expected_address = 32; +#endif + mMetaInfo.mAddressSize = expected_address; + + readCacheHeader(); + + LL_INFOS() << "Viewer Object Cache Versions - expected: " << cache_version << " found: " << mMetaInfo.mVersion << LL_ENDL; + + if( mMetaInfo.mVersion != cache_version + || mMetaInfo.mAddressSize != expected_address) + { + mMetaInfo.mVersion = cache_version ; + mMetaInfo.mAddressSize = expected_address; + if(mReadOnly) //disable cache + { + clearCacheInMemory(); + } + else //delete the current cache if the format does not match. + { + LL_INFOS() << "Viewer Object Cache Versions unmatched. clearing cache." << LL_ENDL; + removeCache(); + } + } +} + +void LLVOCache::removeCache(ELLPath location, bool started) +{ + if(started) + { + removeCache(); + return; + } + + if(mReadOnly) + { + LL_WARNS() << "Not removing cache at " << location << ": Cache is currently in read-only mode." << LL_ENDL; + return ; + } + + LL_INFOS() << "about to remove the object cache due to settings." << LL_ENDL ; + + std::string mask = "*"; + std::string cache_dir = gDirUtilp->getExpandedFilename(location, object_cache_dirname); + LL_INFOS() << "Removing cache at " << cache_dir << LL_ENDL; + gDirUtilp->deleteFilesInDir(cache_dir, mask); //delete all files + LLFile::rmdir(cache_dir); + + clearCacheInMemory(); + mInitialized = false; +} + +void LLVOCache::removeCache() +{ + if(!mInitialized) + { + //OK to remove cache even it is not initialized. + LL_WARNS() << "Object cache is not initialized yet." << LL_ENDL; + } + + if(mReadOnly) + { + LL_WARNS() << "Not clearing object cache: Cache is currently in read-only mode." << LL_ENDL; + return ; + } + + std::string mask = "*"; + LL_INFOS() << "Removing object cache at " << mObjectCacheDirName << LL_ENDL; + gDirUtilp->deleteFilesInDir(mObjectCacheDirName, mask); + + clearCacheInMemory() ; + writeCacheHeader(); +} + +void LLVOCache::removeEntry(HeaderEntryInfo* entry) +{ + llassert_always(mInitialized); + if(mReadOnly) + { + return; + } + if(!entry) + { + return; + } + + header_entry_queue_t::iterator iter = mHeaderEntryQueue.find(entry); + if(iter != mHeaderEntryQueue.end()) + { + mHandleEntryMap.erase(entry->mHandle); + mHeaderEntryQueue.erase(iter); + removeFromCache(entry); + delete entry; + + mNumEntries = mHandleEntryMap.size() ; + } +} + +void LLVOCache::removeEntry(U64 handle) +{ + handle_entry_map_t::iterator iter = mHandleEntryMap.find(handle) ; + if(iter == mHandleEntryMap.end()) //no cache + { + return ; + } + HeaderEntryInfo* entry = iter->second ; + removeEntry(entry) ; +} + +void LLVOCache::clearCacheInMemory() +{ + if(!mHeaderEntryQueue.empty()) + { + for(header_entry_queue_t::iterator iter = mHeaderEntryQueue.begin(); iter != mHeaderEntryQueue.end(); ++iter) + { + delete *iter ; + } + mHeaderEntryQueue.clear(); + mHandleEntryMap.clear(); + mNumEntries = 0 ; + } + +} + +void LLVOCache::getObjectCacheFilename(U64 handle, std::string& filename) +{ + U32 region_x, region_y; + + grid_from_region_handle(handle, ®ion_x, ®ion_y); + filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, object_cache_dirname, + llformat(OBJECT_CACHE_FILENAME, region_x, region_y)); + + return ; +} + +std::string LLVOCache::getObjectCacheExtrasFilename(U64 handle) +{ + U32 region_x, region_y; + + grid_from_region_handle(handle, ®ion_x, ®ion_y); + return gDirUtilp->getExpandedFilename(LL_PATH_CACHE, object_cache_dirname, + llformat(OBJECT_CACHE_EXTRAS_FILENAME, region_x, region_y)); +} + +void LLVOCache::removeFromCache(HeaderEntryInfo* entry) +{ + if(mReadOnly) + { + LL_WARNS() << "Not removing cache for handle " << entry->mHandle << ": Cache is currently in read-only mode." << LL_ENDL; + return ; + } + + std::string filename; + getObjectCacheFilename(entry->mHandle, filename); + LLAPRFile::remove(filename, mLocalAPRFilePoolp); + entry->mTime = INVALID_TIME ; + updateEntry(entry) ; //update the head file. +} + +void LLVOCache::readCacheHeader() +{ + if(!mEnabled) + { + LL_WARNS() << "Not reading cache header: Cache is currently disabled." << LL_ENDL; + return; + } + + //clear stale info. + clearCacheInMemory(); + + bool success = true ; + if (LLAPRFile::isExist(mHeaderFileName, mLocalAPRFilePoolp)) + { + LLAPRFile apr_file(mHeaderFileName, APR_READ|APR_BINARY, mLocalAPRFilePoolp); + + //read the meta element + success = check_read(&apr_file, &mMetaInfo, sizeof(HeaderMetaInfo)) ; + + if(success) + { + HeaderEntryInfo* entry = NULL ; + mNumEntries = 0 ; + U32 num_read = 0 ; + while(num_read++ < MAX_NUM_OBJECT_ENTRIES) + { + if(!entry) + { + entry = new HeaderEntryInfo() ; + } + success = check_read(&apr_file, entry, sizeof(HeaderEntryInfo)); + + if(!success) //failed + { + LL_WARNS() << "Error reading cache header entry. (entry_index=" << mNumEntries << ")" << LL_ENDL; + delete entry ; + entry = NULL ; + break ; + } + else if(entry->mTime == INVALID_TIME) + { + continue ; //an empty entry + } + + entry->mIndex = mNumEntries++ ; + mHeaderEntryQueue.insert(entry) ; + mHandleEntryMap[entry->mHandle] = entry ; + entry = NULL ; + } + if(entry) + { + delete entry ; + } + } + + //--------- + //debug code + //---------- + //std::string name ; + //for(header_entry_queue_t::iterator iter = mHeaderEntryQueue.begin() ; success && iter != mHeaderEntryQueue.end(); ++iter) + //{ + // getObjectCacheFilename((*iter)->mHandle, name) ; + // LL_INFOS() << name << LL_ENDL ; + //} + //----------- + } + else + { + writeCacheHeader() ; + } + + if(!success) + { + removeCache() ; //failed to read header, clear the cache + } + else if(mNumEntries >= mCacheSize) + { + purgeEntries(mCacheSize) ; + } + + return ; +} + +void LLVOCache::writeCacheHeader() +{ + if (!mEnabled) + { + LL_WARNS() << "Not writing cache header: Cache is currently disabled." << LL_ENDL; + return; + } + + if(mReadOnly) + { + LL_WARNS() << "Not writing cache header: Cache is currently in read-only mode." << LL_ENDL; + return; + } + + bool success = true ; + { + LLAPRFile apr_file(mHeaderFileName, APR_CREATE|APR_WRITE|APR_BINARY, mLocalAPRFilePoolp); + + //write the meta element + success = check_write(&apr_file, &mMetaInfo, sizeof(HeaderMetaInfo)) ; + + + mNumEntries = 0 ; + for(header_entry_queue_t::iterator iter = mHeaderEntryQueue.begin() ; success && iter != mHeaderEntryQueue.end(); ++iter) + { + (*iter)->mIndex = mNumEntries++ ; + success = check_write(&apr_file, (void*)*iter, sizeof(HeaderEntryInfo)); + } + + mNumEntries = mHeaderEntryQueue.size() ; + if(success && mNumEntries < MAX_NUM_OBJECT_ENTRIES) + { + HeaderEntryInfo* entry = new HeaderEntryInfo() ; + entry->mTime = INVALID_TIME ; + for(S32 i = mNumEntries ; success && i < MAX_NUM_OBJECT_ENTRIES ; i++) + { + //fill the cache with the default entry. + success = check_write(&apr_file, entry, sizeof(HeaderEntryInfo)) ; + + } + delete entry ; + } + } + + if(!success) + { + clearCacheInMemory() ; + mReadOnly = true ; //disable the cache. + } + return ; +} + +bool LLVOCache::updateEntry(const HeaderEntryInfo* entry) +{ + LLAPRFile apr_file(mHeaderFileName, APR_WRITE|APR_BINARY, mLocalAPRFilePoolp); + apr_file.seek(APR_SET, entry->mIndex * sizeof(HeaderEntryInfo) + sizeof(HeaderMetaInfo)) ; + + return check_write(&apr_file, (void*)entry, sizeof(HeaderEntryInfo)) ; +} + +void LLVOCache::readFromCache(U64 handle, const LLUUID& id, LLVOCacheEntry::vocache_entry_map_t& cache_entry_map) +{ + if(!mEnabled) + { + LL_WARNS() << "Not reading cache for handle " << handle << "): Cache is currently disabled." << LL_ENDL; + return ; + } + llassert_always(mInitialized); + + handle_entry_map_t::iterator iter = mHandleEntryMap.find(handle) ; + if(iter == mHandleEntryMap.end()) //no cache + { + LL_WARNS() << "No handle map entry for " << handle << LL_ENDL; + return ; + } + + bool success = true ; + { + std::string filename; + LLUUID cache_id; + getObjectCacheFilename(handle, filename); + LLAPRFile apr_file(filename, APR_READ|APR_BINARY, mLocalAPRFilePoolp); + + success = check_read(&apr_file, cache_id.mData, UUID_BYTES); + + if(success) + { + if(cache_id != id) + { + LL_INFOS() << "Cache ID doesn't match for this region, discarding"<< LL_ENDL; + success = false ; + } + + if(success) + { + S32 num_entries; // if removal was enabled during write num_entries might be wrong + success = check_read(&apr_file, &num_entries, sizeof(S32)) ; + + if(success) + { + for (S32 i = 0; i < num_entries && apr_file.eof() != APR_EOF; i++) + { + LLPointer entry = new LLVOCacheEntry(&apr_file); + if (!entry->getLocalID()) + { + LL_WARNS() << "Aborting cache file load for " << filename << ", cache file corruption!" << LL_ENDL; + success = false ; + break ; + } + cache_entry_map[entry->getLocalID()] = entry; + } + } + } + } + } + + if(!success) + { + if(cache_entry_map.empty()) + { + removeEntry(iter->second) ; + } + } + + return ; +} + +void LLVOCache::readGenericExtrasFromCache(U64 handle, const LLUUID& id, LLVOCacheEntry::vocache_gltf_overrides_map_t& cache_extras_entry_map) +{ + if(!mEnabled) + { + LL_WARNS() << "Not reading cache for handle " << handle << "): Cache is currently disabled." << LL_ENDL; + return ; + } + llassert_always(mInitialized); + + handle_entry_map_t::iterator iter = mHandleEntryMap.find(handle) ; + if(iter == mHandleEntryMap.end()) //no cache + { + LL_WARNS() << "No handle map entry for " << handle << LL_ENDL; + return; + } + + std::string filename(getObjectCacheExtrasFilename(handle)); + llifstream in(filename, std::ios::in | std::ios::binary); + + std::string line; + std::getline(in, line); + if(!in.good()) { + LL_WARNS() << "Failed reading extras cache for handle " << handle << LL_ENDL; + return; + } + + if(!LLUUID::validate(line)) + { + LL_WARNS() << "Failed reading extras cache for handle" << handle << ". invalid uuid line: '" << line << "'" << LL_ENDL; + return; + } + + LLUUID cache_id(line); + if(cache_id != id) + { + LL_INFOS() << "Cache ID doesn't match for this region, discarding" << LL_ENDL; + return; + } + + U32 num_entries; // if removal was enabled during write num_entries might be wrong + std::getline(in, line); + if(!in.good()) { + LL_WARNS() << "Failed reading extras cache for handle " << handle << LL_ENDL; + return; + } + try { + num_entries = std::stol(line); + } + catch(std::logic_error&) // either invalid_argument or out_of_range + { + LL_WARNS() << "Failed reading extras cache for handle " << handle << ". unreadable num_entries" << LL_ENDL; + return; + } + + LL_DEBUGS("GLTF") << "Beginning reading extras cache for handle " << handle << ", " << num_entries << " entries" << LL_ENDL; + + LLSD entry_llsd; + for (U32 i = 0; i < num_entries && !in.eof(); i++) + { + static const U32 max_size = 4096; + bool success = LLSDSerialize::deserialize(entry_llsd, in, max_size); + // check bool(in) this time since eof is not a failure condition here + if(!success || !in) { + LL_WARNS() << "Failed reading extras cache for handle " << handle << ", entry number " << i << LL_ENDL; + return; + } + + LLGLTFOverrideCacheEntry entry; + entry.fromLLSD(entry_llsd); + U32 local_id = entry_llsd["local_id"].asInteger(); + cache_extras_entry_map[local_id] = entry; + } + + LL_DEBUGS("GLTF") << "Completed reading extras cache for handle " << handle << ", " << num_entries << " entries" << LL_ENDL; +} + +void LLVOCache::purgeEntries(U32 size) +{ + while(mHeaderEntryQueue.size() > size) + { + header_entry_queue_t::iterator iter = mHeaderEntryQueue.begin() ; + HeaderEntryInfo* entry = *iter ; + mHandleEntryMap.erase(entry->mHandle); + mHeaderEntryQueue.erase(iter) ; + removeFromCache(entry) ; + delete entry; + // TODO also delete extras + } + mNumEntries = mHandleEntryMap.size() ; +} + +void LLVOCache::writeToCache(U64 handle, const LLUUID& id, const LLVOCacheEntry::vocache_entry_map_t& cache_entry_map, bool dirty_cache, bool removal_enabled) +{ + if(!mEnabled) + { + LL_WARNS() << "Not writing cache for handle " << handle << "): Cache is currently disabled." << LL_ENDL; + return ; + } + llassert_always(mInitialized); + + if(mReadOnly) + { + LL_WARNS() << "Not writing cache for handle " << handle << "): Cache is currently in read-only mode." << LL_ENDL; + return ; + } + + HeaderEntryInfo* entry; + handle_entry_map_t::iterator iter = mHandleEntryMap.find(handle) ; + if(iter == mHandleEntryMap.end()) //new entry + { + if(mNumEntries >= mCacheSize - 1) + { + purgeEntries(mCacheSize - 1) ; + } + + entry = new HeaderEntryInfo(); + entry->mHandle = handle ; + entry->mTime = time(NULL) ; + entry->mIndex = mNumEntries++; + mHeaderEntryQueue.insert(entry) ; + mHandleEntryMap[handle] = entry ; + } + else + { + // Update access time. + entry = iter->second ; + + //resort + mHeaderEntryQueue.erase(entry) ; + + entry->mTime = time(NULL) ; + mHeaderEntryQueue.insert(entry) ; + } + + //update cache header + if(!updateEntry(entry)) + { + LL_WARNS() << "Failed to update cache header index " << entry->mIndex << ". handle = " << handle << LL_ENDL; + return ; //update failed. + } + + if(!dirty_cache) + { + LL_WARNS() << "Skipping write to cache for handle " << handle << ": cache not dirty" << LL_ENDL; + return ; //nothing changed, no need to update. + } + + //write to cache file + bool success = true ; + { + std::string filename; + getObjectCacheFilename(handle, filename); + LLAPRFile apr_file(filename, APR_CREATE|APR_WRITE|APR_BINARY|APR_TRUNCATE, mLocalAPRFilePoolp); + + success = check_write(&apr_file, (void*)id.mData, UUID_BYTES); + + if(success) + { + S32 num_entries = cache_entry_map.size(); // if removal is enabled num_entries might be wrong + success = check_write(&apr_file, &num_entries, sizeof(S32)); + if (success) + { + const S32 buffer_size = 32768; //should be large enough for couple MAX_ENTRY_BODY_SIZE + U8 data_buffer[buffer_size]; // generaly entries are fairly small, so collect them and drop onto disk in one go + S32 size_in_buffer = 0; + + // This can have a lot of entries, so might be better to dump them into buffer first and write in one go. + for (LLVOCacheEntry::vocache_entry_map_t::const_iterator iter = cache_entry_map.begin(); success && iter != cache_entry_map.end(); ++iter) + { + if (!removal_enabled || iter->second->isValid()) + { + S32 size = iter->second->writeToBuffer(data_buffer + size_in_buffer); + + if (size > ENTRY_HEADER_SIZE) // body is minimum of 1 + { + size_in_buffer += size; + } + else + { + success = false; + break; + } + + // Make sure we have space in buffer for next element + if (buffer_size - size_in_buffer < MAX_ENTRY_BODY_SIZE + ENTRY_HEADER_SIZE) + { + success = check_write(&apr_file, (void*)data_buffer, size_in_buffer); + size_in_buffer = 0; + if (!success) + { + break; + } + } + } + } + + if (success && size_in_buffer > 0) + { + // final write + success = check_write(&apr_file, (void*)data_buffer, size_in_buffer); + size_in_buffer = 0; + } + } + } + } + + if(!success) + { + removeEntry(entry) ; + } + + return ; +} + +void LLVOCache::writeGenericExtrasToCache(U64 handle, const LLUUID& id, const LLVOCacheEntry::vocache_gltf_overrides_map_t& cache_extras_entry_map, bool dirty_cache, bool removal_enabled) +{ + if(!mEnabled) + { + LL_WARNS() << "Not writing extras cache for handle " << handle << "): Cache is currently disabled." << LL_ENDL; + return; + } + llassert_always(mInitialized); + + if(mReadOnly) + { + LL_WARNS() << "Not writing extras cache for handle " << handle << "): Cache is currently in read-only mode." << LL_ENDL; + return; + } + + std::string filename(getObjectCacheExtrasFilename(handle)); + llofstream out(filename, std::ios::out | std::ios::binary); + if(!out.good()) + { + LL_WARNS() << "Failed writing extras cache for handle " << handle << LL_ENDL; + return; + // TODO - clean up broken cache file + } + + out << id << '\n'; + if(!out.good()) + { + LL_WARNS() << "Failed writing extras cache for handle " << handle << LL_ENDL; + return; + // TODO - clean up broken cache file + } + + U32 num_entries = cache_extras_entry_map.size(); + out << num_entries << '\n'; + if(!out.good()) + { + LL_WARNS() << "Failed writing extras cache for handle " << handle << LL_ENDL; + return; + // TODO - clean up broken cache file + } + + for (auto const & entry : cache_extras_entry_map) + { + S32 local_id = entry.first; + LLSD entry_llsd = entry.second.toLLSD(); + entry_llsd["local_id"] = local_id; + LLSDSerialize::serialize(entry_llsd, out, LLSDSerialize::LLSD_XML); + out << '\n'; + if(!out.good()) + { + LL_WARNS() << "Failed writing extras cache for handle " << handle << LL_ENDL; + return; + // TODO - clean up broken cache file + } + } + + LL_DEBUGS("GLTF") << "Completed writing extras cache for handle " << handle << ", " << num_entries << " entries" << LL_ENDL; +} diff --git a/indra/newview/llvocache.h b/indra/newview/llvocache.h index 5dc4879f2a..8efdeb50f0 100644 --- a/indra/newview/llvocache.h +++ b/indra/newview/llvocache.h @@ -1,330 +1,330 @@ -/** - * @file llvocache.h - * @brief Cache of objects on the viewer. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVOCACHE_H -#define LL_LLVOCACHE_H - -#include "lluuid.h" -#include "lldatapacker.h" -#include "lldir.h" -#include "llvieweroctree.h" -#include "llapr.h" -#include "llgltfmaterial.h" - -#include - -//--------------------------------------------------------------------------- -// Cache entries -class LLCamera; - -class LLGLTFOverrideCacheEntry -{ -public: - bool fromLLSD(const LLSD& data); - LLSD toLLSD() const; - - LLUUID mObjectId; - U32 mLocalId = 0; - std::unordered_map mSides; //override LLSD per side - std::unordered_map > mGLTFMaterial; //GLTF material per side - U64 mRegionHandle = 0; -}; - -class LLVOCacheEntry -: public LLViewerOctreeEntryData -{ - LL_ALIGN_NEW -public: - enum - { - //low 16-bit state - INACTIVE = 0x00000000, //not visible - IN_QUEUE = 0x00000001, //in visible queue, object to be created - WAITING = 0x00000002, //object creation request sent - ACTIVE = 0x00000004, //object created, and in rendering pipeline. - - //high 16-bit state - IN_VO_TREE = 0x00010000, //the entry is in the object cache tree. - - LOW_BITS = 0x0000ffff, - HIGH_BITS = 0xffff0000 - }; - - struct CompareVOCacheEntry - { - bool operator()(const LLVOCacheEntry* const& lhs, const LLVOCacheEntry* const& rhs) const - { - F32 lpa = lhs->getSceneContribution(); - F32 rpa = rhs->getSceneContribution(); - - //larger pixel area first - if(lpa > rpa) - { - return true; - } - else if(lpa < rpa) - { - return false; - } - else - { - return lhs < rhs; - } - } - }; - - struct ExtrasEntry - { - LLSD extras; - std::string extras_raw; - }; - -protected: - ~LLVOCacheEntry(); -public: - LLVOCacheEntry(U32 local_id, U32 crc, LLDataPackerBinaryBuffer &dp); - LLVOCacheEntry(LLAPRFile* apr_file); - LLVOCacheEntry(); - - void updateEntry(U32 crc, LLDataPackerBinaryBuffer &dp); - - void clearState(U32 state) {mState &= ~state;} - bool hasState(U32 state) {return mState & state;} - void setState(U32 state); - bool isState(U32 state) {return (mState & LOW_BITS) == state;} - U32 getState() const {return mState & LOW_BITS;} - - bool isAnyVisible(const LLVector4a& camera_origin, const LLVector4a& local_camera_origin, F32 dist_threshold); - - U32 getLocalID() const { return mLocalID; } - U32 getCRC() const { return mCRC; } - S32 getHitCount() const { return mHitCount; } - S32 getCRCChangeCount() const { return mCRCChangeCount; } - - void calcSceneContribution(const LLVector4a& camera_origin, bool needs_update, U32 last_update, F32 dist_threshold); - void setSceneContribution(F32 scene_contrib) {mSceneContrib = scene_contrib;} - F32 getSceneContribution() const { return mSceneContrib;} - - void dump() const; - S32 writeToBuffer(U8 *data_buffer) const; - LLDataPackerBinaryBuffer *getDP(); - void recordHit(); - void recordDupe() { mDupeCount++; } - - /*virtual*/ void setOctreeEntry(LLViewerOctreeEntry* entry); - - void setParentID(U32 id); - U32 getParentID() const {return mParentID;} - bool isChild() const {return mParentID > 0;} - - void addChild(LLVOCacheEntry* entry); - void removeChild(LLVOCacheEntry* entry); - void removeAllChildren(); - LLVOCacheEntry* getChild(); //remove the first child, and return it. - S32 getNumOfChildren() const {return mChildrenList.size();} - - void setBoundingInfo(const LLVector3& pos, const LLVector3& scale); //called from processing object update message - void updateParentBoundingInfo(); - void saveBoundingSphere(); - - void setValid(bool valid = true) {mValid = valid;} - bool isValid() const {return mValid;} - - void setUpdateFlags(U32 flags) {mUpdateFlags = flags;} - U32 getUpdateFlags() const {return mUpdateFlags;} - - static void updateDebugSettings(); - static F32 getSquaredPixelThreshold(bool is_front); - -private: - void updateParentBoundingInfo(const LLVOCacheEntry* child); - -public: - typedef std::map > vocache_entry_map_t; - typedef std::set vocache_entry_set_t; - typedef std::set vocache_entry_priority_list_t; - - typedef std::unordered_map vocache_gltf_overrides_map_t; - - S32 mLastCameraUpdated; -protected: - U32 mLocalID; - U32 mParentID; - U32 mCRC; - U32 mUpdateFlags; //receive from sim - S32 mHitCount; - S32 mDupeCount; - S32 mCRCChangeCount; - LLDataPackerBinaryBuffer mDP; - U8 *mBuffer; - - F32 mSceneContrib; //projected scene contributuion of this object. - U32 mState; //high 16 bits reserved for special use. - vocache_entry_set_t mChildrenList; //children entries in a linked set. - - bool mValid; //if set, this entry is valid, otherwise it is invalid and will be removed. - - LLVector4a mBSphereCenter; //bounding sphere center - F32 mBSphereRadius; //bounding sphere radius - -public: - static U32 sMinFrameRange; - static F32 sNearRadius; - static F32 sRearFarRadius; - static F32 sFrontPixelThreshold; - static F32 sRearPixelThreshold; -}; - -class LLVOCacheGroup : public LLOcclusionCullingGroup -{ -public: - LLVOCacheGroup(OctreeNode* node, LLViewerOctreePartition* part) : LLOcclusionCullingGroup(node, part){} - - //virtual - void handleChildAddition(const OctreeNode* parent, OctreeNode* child); - -protected: - virtual ~LLVOCacheGroup(); -}; - -class LLVOCachePartition : public LLViewerOctreePartition -{ -public: - LLVOCachePartition(LLViewerRegion* regionp); - virtual ~LLVOCachePartition(); - - bool addEntry(LLViewerOctreeEntry* entry); - void removeEntry(LLViewerOctreeEntry* entry); - /*virtual*/ S32 cull(LLCamera &camera, bool do_occlusion); - void addOccluders(LLViewerOctreeGroup* gp); - void resetOccluders(); - void processOccluders(LLCamera* camera); - void removeOccluder(LLVOCacheGroup* group); - - void setCullHistory(bool has_new_object); - - bool isFrontCull() const {return mFrontCull;} - -private: - void selectBackObjects(LLCamera &camera, F32 projection_area_cutoff, bool use_occlusion); //select objects behind camera. - -public: - static bool sNeedsOcclusionCheck; - -private: - bool mFrontCull; //the view frustum cull if set, otherwise is back sphere cull. - U32 mCullHistory; - U32 mCulledTime[LLViewerCamera::NUM_CAMERAS]; - std::set mOccludedGroups; - - S32 mBackSlectionEnabled; //enable to select back objects if > 0. - U32 mIdleHash; -}; - -// -//Note: LLVOCache is not thread-safe -// -class LLVOCache : public LLParamSingleton -{ - LLSINGLETON(LLVOCache, bool read_only); - ~LLVOCache() ; - -private: - struct HeaderEntryInfo - { - HeaderEntryInfo() : mIndex(0), mHandle(0), mTime(0) {} - S32 mIndex; - U64 mHandle ; - U32 mTime ; - }; - - struct HeaderMetaInfo - { - HeaderMetaInfo() : mVersion(0), mAddressSize(0) {} - - U32 mVersion; - U32 mAddressSize; - }; - - struct header_entry_less - { - bool operator()(const HeaderEntryInfo* lhs, const HeaderEntryInfo* rhs) const - { - if(lhs->mTime == rhs->mTime) - { - return lhs < rhs ; - } - - return lhs->mTime < rhs->mTime ; // older entry in front of queue (set) - } - }; - typedef std::set header_entry_queue_t; - typedef std::map handle_entry_map_t; - -public: - // We need this init to be separate from constructor, since we might construct cache, purge it, then init. - void initCache(ELLPath location, U32 size, U32 cache_version); - void removeCache(ELLPath location, bool started = false) ; - - void readFromCache(U64 handle, const LLUUID& id, LLVOCacheEntry::vocache_entry_map_t& cache_entry_map) ; - void readGenericExtrasFromCache(U64 handle, const LLUUID& id, LLVOCacheEntry::vocache_gltf_overrides_map_t& cache_extras_entry_map); - - void writeToCache(U64 handle, const LLUUID& id, const LLVOCacheEntry::vocache_entry_map_t& cache_entry_map, bool dirty_cache, bool removal_enabled); - void writeGenericExtrasToCache(U64 handle, const LLUUID& id, const LLVOCacheEntry::vocache_gltf_overrides_map_t& cache_extras_entry_map, bool dirty_cache, bool removal_enabled); - void removeEntry(U64 handle) ; - - U32 getCacheEntries() { return mNumEntries; } - U32 getCacheEntriesMax() { return mCacheSize; } - -private: - void setDirNames(ELLPath location); - // determine the cache filename for the region from the region handle - void getObjectCacheFilename(U64 handle, std::string& filename); - std::string getObjectCacheExtrasFilename(U64 handle); - void removeFromCache(HeaderEntryInfo* entry); - void readCacheHeader(); - void writeCacheHeader(); - void clearCacheInMemory(); - void removeCache() ; - void removeEntry(HeaderEntryInfo* entry) ; - void purgeEntries(U32 size); - bool updateEntry(const HeaderEntryInfo* entry); - -private: - bool mEnabled; - bool mInitialized ; - bool mReadOnly ; - HeaderMetaInfo mMetaInfo; - U32 mCacheSize; - U32 mNumEntries; - std::string mHeaderFileName ; - std::string mObjectCacheDirName; - LLVolatileAPRPool* mLocalAPRFilePoolp ; - header_entry_queue_t mHeaderEntryQueue; - handle_entry_map_t mHandleEntryMap; -}; - -#endif +/** + * @file llvocache.h + * @brief Cache of objects on the viewer. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVOCACHE_H +#define LL_LLVOCACHE_H + +#include "lluuid.h" +#include "lldatapacker.h" +#include "lldir.h" +#include "llvieweroctree.h" +#include "llapr.h" +#include "llgltfmaterial.h" + +#include + +//--------------------------------------------------------------------------- +// Cache entries +class LLCamera; + +class LLGLTFOverrideCacheEntry +{ +public: + bool fromLLSD(const LLSD& data); + LLSD toLLSD() const; + + LLUUID mObjectId; + U32 mLocalId = 0; + std::unordered_map mSides; //override LLSD per side + std::unordered_map > mGLTFMaterial; //GLTF material per side + U64 mRegionHandle = 0; +}; + +class LLVOCacheEntry +: public LLViewerOctreeEntryData +{ + LL_ALIGN_NEW +public: + enum + { + //low 16-bit state + INACTIVE = 0x00000000, //not visible + IN_QUEUE = 0x00000001, //in visible queue, object to be created + WAITING = 0x00000002, //object creation request sent + ACTIVE = 0x00000004, //object created, and in rendering pipeline. + + //high 16-bit state + IN_VO_TREE = 0x00010000, //the entry is in the object cache tree. + + LOW_BITS = 0x0000ffff, + HIGH_BITS = 0xffff0000 + }; + + struct CompareVOCacheEntry + { + bool operator()(const LLVOCacheEntry* const& lhs, const LLVOCacheEntry* const& rhs) const + { + F32 lpa = lhs->getSceneContribution(); + F32 rpa = rhs->getSceneContribution(); + + //larger pixel area first + if(lpa > rpa) + { + return true; + } + else if(lpa < rpa) + { + return false; + } + else + { + return lhs < rhs; + } + } + }; + + struct ExtrasEntry + { + LLSD extras; + std::string extras_raw; + }; + +protected: + ~LLVOCacheEntry(); +public: + LLVOCacheEntry(U32 local_id, U32 crc, LLDataPackerBinaryBuffer &dp); + LLVOCacheEntry(LLAPRFile* apr_file); + LLVOCacheEntry(); + + void updateEntry(U32 crc, LLDataPackerBinaryBuffer &dp); + + void clearState(U32 state) {mState &= ~state;} + bool hasState(U32 state) {return mState & state;} + void setState(U32 state); + bool isState(U32 state) {return (mState & LOW_BITS) == state;} + U32 getState() const {return mState & LOW_BITS;} + + bool isAnyVisible(const LLVector4a& camera_origin, const LLVector4a& local_camera_origin, F32 dist_threshold); + + U32 getLocalID() const { return mLocalID; } + U32 getCRC() const { return mCRC; } + S32 getHitCount() const { return mHitCount; } + S32 getCRCChangeCount() const { return mCRCChangeCount; } + + void calcSceneContribution(const LLVector4a& camera_origin, bool needs_update, U32 last_update, F32 dist_threshold); + void setSceneContribution(F32 scene_contrib) {mSceneContrib = scene_contrib;} + F32 getSceneContribution() const { return mSceneContrib;} + + void dump() const; + S32 writeToBuffer(U8 *data_buffer) const; + LLDataPackerBinaryBuffer *getDP(); + void recordHit(); + void recordDupe() { mDupeCount++; } + + /*virtual*/ void setOctreeEntry(LLViewerOctreeEntry* entry); + + void setParentID(U32 id); + U32 getParentID() const {return mParentID;} + bool isChild() const {return mParentID > 0;} + + void addChild(LLVOCacheEntry* entry); + void removeChild(LLVOCacheEntry* entry); + void removeAllChildren(); + LLVOCacheEntry* getChild(); //remove the first child, and return it. + S32 getNumOfChildren() const {return mChildrenList.size();} + + void setBoundingInfo(const LLVector3& pos, const LLVector3& scale); //called from processing object update message + void updateParentBoundingInfo(); + void saveBoundingSphere(); + + void setValid(bool valid = true) {mValid = valid;} + bool isValid() const {return mValid;} + + void setUpdateFlags(U32 flags) {mUpdateFlags = flags;} + U32 getUpdateFlags() const {return mUpdateFlags;} + + static void updateDebugSettings(); + static F32 getSquaredPixelThreshold(bool is_front); + +private: + void updateParentBoundingInfo(const LLVOCacheEntry* child); + +public: + typedef std::map > vocache_entry_map_t; + typedef std::set vocache_entry_set_t; + typedef std::set vocache_entry_priority_list_t; + + typedef std::unordered_map vocache_gltf_overrides_map_t; + + S32 mLastCameraUpdated; +protected: + U32 mLocalID; + U32 mParentID; + U32 mCRC; + U32 mUpdateFlags; //receive from sim + S32 mHitCount; + S32 mDupeCount; + S32 mCRCChangeCount; + LLDataPackerBinaryBuffer mDP; + U8 *mBuffer; + + F32 mSceneContrib; //projected scene contributuion of this object. + U32 mState; //high 16 bits reserved for special use. + vocache_entry_set_t mChildrenList; //children entries in a linked set. + + bool mValid; //if set, this entry is valid, otherwise it is invalid and will be removed. + + LLVector4a mBSphereCenter; //bounding sphere center + F32 mBSphereRadius; //bounding sphere radius + +public: + static U32 sMinFrameRange; + static F32 sNearRadius; + static F32 sRearFarRadius; + static F32 sFrontPixelThreshold; + static F32 sRearPixelThreshold; +}; + +class LLVOCacheGroup : public LLOcclusionCullingGroup +{ +public: + LLVOCacheGroup(OctreeNode* node, LLViewerOctreePartition* part) : LLOcclusionCullingGroup(node, part){} + + //virtual + void handleChildAddition(const OctreeNode* parent, OctreeNode* child); + +protected: + virtual ~LLVOCacheGroup(); +}; + +class LLVOCachePartition : public LLViewerOctreePartition +{ +public: + LLVOCachePartition(LLViewerRegion* regionp); + virtual ~LLVOCachePartition(); + + bool addEntry(LLViewerOctreeEntry* entry); + void removeEntry(LLViewerOctreeEntry* entry); + /*virtual*/ S32 cull(LLCamera &camera, bool do_occlusion); + void addOccluders(LLViewerOctreeGroup* gp); + void resetOccluders(); + void processOccluders(LLCamera* camera); + void removeOccluder(LLVOCacheGroup* group); + + void setCullHistory(bool has_new_object); + + bool isFrontCull() const {return mFrontCull;} + +private: + void selectBackObjects(LLCamera &camera, F32 projection_area_cutoff, bool use_occlusion); //select objects behind camera. + +public: + static bool sNeedsOcclusionCheck; + +private: + bool mFrontCull; //the view frustum cull if set, otherwise is back sphere cull. + U32 mCullHistory; + U32 mCulledTime[LLViewerCamera::NUM_CAMERAS]; + std::set mOccludedGroups; + + S32 mBackSlectionEnabled; //enable to select back objects if > 0. + U32 mIdleHash; +}; + +// +//Note: LLVOCache is not thread-safe +// +class LLVOCache : public LLParamSingleton +{ + LLSINGLETON(LLVOCache, bool read_only); + ~LLVOCache() ; + +private: + struct HeaderEntryInfo + { + HeaderEntryInfo() : mIndex(0), mHandle(0), mTime(0) {} + S32 mIndex; + U64 mHandle ; + U32 mTime ; + }; + + struct HeaderMetaInfo + { + HeaderMetaInfo() : mVersion(0), mAddressSize(0) {} + + U32 mVersion; + U32 mAddressSize; + }; + + struct header_entry_less + { + bool operator()(const HeaderEntryInfo* lhs, const HeaderEntryInfo* rhs) const + { + if(lhs->mTime == rhs->mTime) + { + return lhs < rhs ; + } + + return lhs->mTime < rhs->mTime ; // older entry in front of queue (set) + } + }; + typedef std::set header_entry_queue_t; + typedef std::map handle_entry_map_t; + +public: + // We need this init to be separate from constructor, since we might construct cache, purge it, then init. + void initCache(ELLPath location, U32 size, U32 cache_version); + void removeCache(ELLPath location, bool started = false) ; + + void readFromCache(U64 handle, const LLUUID& id, LLVOCacheEntry::vocache_entry_map_t& cache_entry_map) ; + void readGenericExtrasFromCache(U64 handle, const LLUUID& id, LLVOCacheEntry::vocache_gltf_overrides_map_t& cache_extras_entry_map); + + void writeToCache(U64 handle, const LLUUID& id, const LLVOCacheEntry::vocache_entry_map_t& cache_entry_map, bool dirty_cache, bool removal_enabled); + void writeGenericExtrasToCache(U64 handle, const LLUUID& id, const LLVOCacheEntry::vocache_gltf_overrides_map_t& cache_extras_entry_map, bool dirty_cache, bool removal_enabled); + void removeEntry(U64 handle) ; + + U32 getCacheEntries() { return mNumEntries; } + U32 getCacheEntriesMax() { return mCacheSize; } + +private: + void setDirNames(ELLPath location); + // determine the cache filename for the region from the region handle + void getObjectCacheFilename(U64 handle, std::string& filename); + std::string getObjectCacheExtrasFilename(U64 handle); + void removeFromCache(HeaderEntryInfo* entry); + void readCacheHeader(); + void writeCacheHeader(); + void clearCacheInMemory(); + void removeCache() ; + void removeEntry(HeaderEntryInfo* entry) ; + void purgeEntries(U32 size); + bool updateEntry(const HeaderEntryInfo* entry); + +private: + bool mEnabled; + bool mInitialized ; + bool mReadOnly ; + HeaderMetaInfo mMetaInfo; + U32 mCacheSize; + U32 mNumEntries; + std::string mHeaderFileName ; + std::string mObjectCacheDirName; + LLVolatileAPRPool* mLocalAPRFilePoolp ; + header_entry_queue_t mHeaderEntryQueue; + handle_entry_map_t mHandleEntryMap; +}; + +#endif diff --git a/indra/newview/llvograss.cpp b/indra/newview/llvograss.cpp index 568168c7bc..6af9593df8 100644 --- a/indra/newview/llvograss.cpp +++ b/indra/newview/llvograss.cpp @@ -1,895 +1,895 @@ -/** - * @file llvograss.cpp - * @brief Not a blade, but a clump of grass - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llvograss.h" - -#include "llviewercontrol.h" - -#include "llagentcamera.h" -#include "llnotificationsutil.h" -#include "lldrawable.h" -#include "lldrawpoolalpha.h" -#include "llface.h" -#include "llsky.h" -#include "llsurface.h" -#include "llsurfacepatch.h" -#include "llviewercamera.h" -#include "llviewertexturelist.h" -#include "llviewerregion.h" -#include "pipeline.h" -#include "llspatialpartition.h" -#include "llworld.h" -#include "lldir.h" -#include "llxmltree.h" -#include "llvotree.h" - -const S32 GRASS_MAX_BLADES = 32; -const F32 GRASS_BLADE_BASE = 0.25f; // Width of grass at base -const F32 GRASS_BLADE_HEIGHT = 0.5f; // meters -const F32 GRASS_DISTRIBUTION_SD = 0.15f; // empirically defined - -F32 exp_x[GRASS_MAX_BLADES]; -F32 exp_y[GRASS_MAX_BLADES]; -F32 rot_x[GRASS_MAX_BLADES]; -F32 rot_y[GRASS_MAX_BLADES]; -F32 dz_x [GRASS_MAX_BLADES]; -F32 dz_y [GRASS_MAX_BLADES]; - -F32 w_mod[GRASS_MAX_BLADES]; // Factor to modulate wind movement by to randomize appearance - -LLVOGrass::SpeciesMap LLVOGrass::sSpeciesTable; -S32 LLVOGrass::sMaxGrassSpecies = 0; - - -LLVOGrass::LLVOGrass(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) -: LLAlphaObject(id, pcode, regionp) -{ - mPatch = NULL; - mLastPatchUpdateTime = 0; - mGrassVel.clearVec(); - mGrassBend.clearVec(); - mbCanSelect = true; - - mBladeWindAngle = 35.f; - mBWAOverlap = 2.f; - - setNumTEs(1); - - setTEColor(0, LLColor4(1.0f, 1.0f, 1.0f, 1.f)); - mNumBlades = GRASS_MAX_BLADES; -} - -LLVOGrass::~LLVOGrass() -{ -} - - -void LLVOGrass::updateSpecies() -{ - mSpecies = getAttachmentState(); - - if (!sSpeciesTable.count(mSpecies)) - { - LL_INFOS() << "Unknown grass type, substituting grass type." << LL_ENDL; - SpeciesMap::const_iterator it = sSpeciesTable.begin(); - mSpecies = (*it).first; - } - setTEImage(0, LLViewerTextureManager::getFetchedTexture(sSpeciesTable[mSpecies]->mTextureID, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE)); -} - - -void LLVOGrass::initClass() -{ - LLVector3 pos(0.0f, 0.0f, 0.0f); - // Create nifty list of exponential distribution 0-1 - F32 x = 0.f; - F32 y = 0.f; - F32 rot; - - std::string xml_filename = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS,"grass.xml"); - - LLXmlTree grass_def_grass; - - if (!grass_def_grass.parseFile(xml_filename)) - { - LL_ERRS() << "Failed to parse grass file." << LL_ENDL; - return; - } - - LLXmlTreeNode* rootp = grass_def_grass.getRoot(); - - for (LLXmlTreeNode* grass_def = rootp->getFirstChild(); - grass_def; - grass_def = rootp->getNextChild()) - { - if (!grass_def->hasName("grass")) - { - LL_WARNS() << "Invalid grass definition node " << grass_def->getName() << LL_ENDL; - continue; - } - F32 F32_val; - LLUUID id; - - bool success{ true }; - - S32 species; - static LLStdStringHandle species_id_string = LLXmlTree::addAttributeString("species_id"); - if (!grass_def->getFastAttributeS32(species_id_string, species)) - { - LL_WARNS() << "No species id defined" << LL_ENDL; - continue; - } - - if (species < 0) - { - LL_WARNS() << "Invalid species id " << species << LL_ENDL; - continue; - } - - GrassSpeciesData* newGrass = new GrassSpeciesData(); - - - static LLStdStringHandle texture_id_string = LLXmlTree::addAttributeString("texture_id"); - grass_def->getFastAttributeUUID(texture_id_string, id); - newGrass->mTextureID = id; - - static LLStdStringHandle blade_sizex_string = LLXmlTree::addAttributeString("blade_size_x"); - success &= grass_def->getFastAttributeF32(blade_sizex_string, F32_val); - newGrass->mBladeSizeX = F32_val; - - static LLStdStringHandle blade_sizey_string = LLXmlTree::addAttributeString("blade_size_y"); - success &= grass_def->getFastAttributeF32(blade_sizey_string, F32_val); - newGrass->mBladeSizeY = F32_val; - - if (sSpeciesTable.count(species)) - { - LL_INFOS() << "Grass species " << species << " already defined! Duplicate discarded." << LL_ENDL; - delete newGrass; - continue; - } - else - { - sSpeciesTable[species] = newGrass; - } - - if (species >= sMaxGrassSpecies) sMaxGrassSpecies = species + 1; - - if (!success) - { - std::string name; - static LLStdStringHandle name_string = LLXmlTree::addAttributeString("name"); - grass_def->getFastAttributeString(name_string, name); - LL_WARNS() << "Incomplete definition of grass " << name << LL_ENDL; - } - } - - bool have_all_grass{ true }; - std::string err; - - for (S32 i=0;i 0.f) - ||(getAcceleration().lengthSquared() > 0.f) - ||(getAngularVelocity().lengthSquared() > 0.f)) - { - LL_INFOS() << "ACK! Moving grass!" << LL_ENDL; - setVelocity(LLVector3::zero); - setAcceleration(LLVector3::zero); - setAngularVelocity(LLVector3::zero); - } - - if (mDrawable) - { - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); - } - - return retval; -} - -bool LLVOGrass::isActive() const -{ - return true; -} - -void LLVOGrass::idleUpdate(LLAgent &agent, const F64 &time) -{ - if (mDead || !(gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_GRASS))) - { - return; - } - - if (!mDrawable) - { - // So drones work. - return; - } - if (!LLVOTree::isTreeRenderingStopped() && !mNumBlades)//restart grass rendering - { - mNumBlades = GRASS_MAX_BLADES; - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); - return; - } - if (mPatch && (mLastPatchUpdateTime != mPatch->getLastUpdateTime())) - { - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); - } - - return; -} - - -void LLVOGrass::setPixelAreaAndAngle(LLAgent &agent) -{ - // This should be the camera's center, as soon as we move to all region-local. - LLVector3 relative_position = getPositionAgent() - gAgentCamera.getCameraPositionAgent(); - F32 range = relative_position.length(); - - F32 max_scale = getMaxScale(); - - mAppAngle = (F32) atan2( max_scale, range) * RAD_TO_DEG; - - // Compute pixels per meter at the given range - F32 pixels_per_meter = LLViewerCamera::getInstance()->getViewHeightInPixels() / (tan(LLViewerCamera::getInstance()->getView()) * range); - - // Assume grass texture is a 5 meter by 5 meter sprite at the grass object's center - mPixelArea = (pixels_per_meter) * (pixels_per_meter) * 25.f; -} - - -// BUG could speed this up by caching the relative_position and range calculations -void LLVOGrass::updateTextures() -{ - if (getTEImage(0)) - { - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_AREA)) - { - setDebugText(llformat("%4.0f", (F32) sqrt(mPixelArea))); - } - getTEImage(0)->addTextureStats(mPixelArea); - } -} - -bool LLVOGrass::updateLOD() -{ - if (mDrawable->getNumFaces() <= 0) - { - return false; - } - - LLFace* face = mDrawable->getFace(0); - - if(LLVOTree::isTreeRenderingStopped()) - { - if(mNumBlades) - { - mNumBlades = 0 ; - face->setSize(0, 0); - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); - } - return true ; - } - if(!mNumBlades) - { - mNumBlades = GRASS_MAX_BLADES; - } - - F32 tan_angle = 0.f; - S32 num_blades = 0; - - tan_angle = (mScale.mV[0]*mScale.mV[1])/mDrawable->mDistanceWRTCamera; - num_blades = llmin(GRASS_MAX_BLADES, lltrunc(tan_angle * 5)); - num_blades = llmax(1, num_blades); - if (num_blades >= (mNumBlades << 1)) - { - while (mNumBlades < num_blades) - { - mNumBlades <<= 1; - } - if (face) - { - face->setSize(mNumBlades*8, mNumBlades*12); - } - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); - } - else if (num_blades <= (mNumBlades >> 1)) - { - while (mNumBlades > num_blades) - { - mNumBlades >>=1; - } - - if (face) - { - face->setSize(mNumBlades*8, mNumBlades*12); - } - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); - return true; - } - - return false; -} - -LLDrawable* LLVOGrass::createDrawable(LLPipeline *pipeline) -{ - pipeline->allocDrawable(this); - mDrawable->setRenderType(LLPipeline::RENDER_TYPE_GRASS); - - return mDrawable; -} - -static LLTrace::BlockTimerStatHandle FTM_UPDATE_GRASS("Update Grass"); - -bool LLVOGrass::updateGeometry(LLDrawable *drawable) -{ - LL_RECORD_BLOCK_TIME(FTM_UPDATE_GRASS); - - dirtySpatialGroup(); - - if(!mNumBlades)//stop rendering grass - { - if (mDrawable->getNumFaces() > 0) - { - LLFace* facep = mDrawable->getFace(0); - if(facep) - { - facep->setSize(0, 0); - } - } - } - else - { - plantBlades(); - } - return true; -} - -void LLVOGrass::plantBlades() -{ - // It is possible that the species of a grass is not defined - // This is bad, but not the end of the world. - if (!sSpeciesTable.count(mSpecies)) - { - LL_INFOS() << "Unknown grass species " << mSpecies << LL_ENDL; - return; - } - - if (mDrawable->getNumFaces() < 1) - { - mDrawable->setNumFaces(1, NULL, getTEImage(0)); - } - - LLFace *face = mDrawable->getFace(0); - if (face) - { - face->setTexture(getTEImage(0)); - face->setState(LLFace::GLOBAL); - face->setSize(mNumBlades * 8, mNumBlades * 12); - face->setVertexBuffer(NULL); - face->setTEOffset(0); - face->mCenterLocal = mPosition + mRegionp->getOriginAgent(); - } - - mDepth = (face->mCenterLocal - LLViewerCamera::getInstance()->getOrigin())*LLViewerCamera::getInstance()->getAtAxis(); - mDrawable->setPosition(face->mCenterLocal); - mDrawable->movePartition(); - LLPipeline::sCompiles++; -} - -void LLVOGrass::getGeometry(S32 idx, - LLStrider& verticesp, - LLStrider& normalsp, - LLStrider& texcoordsp, - LLStrider& colorsp, - LLStrider& emissivep, - LLStrider& indicesp) -{ - if(!mNumBlades)//stop rendering grass - { - return ; - } - - mPatch = mRegionp->getLand().resolvePatchRegion(getPositionRegion()); - if (mPatch) - mLastPatchUpdateTime = mPatch->getLastUpdateTime(); - - LLVector3 position; - // Create random blades of grass with gaussian distribution - F32 x,y,xf,yf,dzx,dzy; - - LLColor4U color(255,255,255,255); - - LLFace *face = mDrawable->getFace(idx); - if (!face) - return; - - F32 width = sSpeciesTable[mSpecies]->mBladeSizeX; - F32 height = sSpeciesTable[mSpecies]->mBladeSizeY; - - U32 index_offset = face->getGeomIndex(); - - for (S32 i = 0; i < mNumBlades; i++) - { - x = exp_x[i] * mScale.mV[VX]; - y = exp_y[i] * mScale.mV[VY]; - xf = rot_x[i] * GRASS_BLADE_BASE * width * w_mod[i]; - yf = rot_y[i] * GRASS_BLADE_BASE * width * w_mod[i]; - dzx = dz_x [i]; - dzy = dz_y [i]; - - LLVector3 v1,v2,v3; - F32 blade_height= GRASS_BLADE_HEIGHT * height * w_mod[i]; - - *texcoordsp++ = LLVector2(0, 0); - *texcoordsp++ = LLVector2(0, 0); - *texcoordsp++ = LLVector2(0, 0.98f); - *texcoordsp++ = LLVector2(0, 0.98f); - *texcoordsp++ = LLVector2(1, 0); - *texcoordsp++ = LLVector2(1, 0); - *texcoordsp++ = LLVector2(1, 0.98f); - *texcoordsp++ = LLVector2(1, 0.98f); - - position.mV[0] = mPosition.mV[VX] + x + xf; - position.mV[1] = mPosition.mV[VY] + y + yf; - position.mV[2] = mRegionp->getLand().resolveHeightRegion(position); - v1 = position + mRegionp->getOriginAgent(); - (*verticesp++).load3(v1.mV); - (*verticesp++).load3(v1.mV); - - - position.mV[0] += dzx; - position.mV[1] += dzy; - position.mV[2] += blade_height; - v2 = position + mRegionp->getOriginAgent(); - (*verticesp++).load3(v2.mV); - (*verticesp++).load3(v2.mV); - - position.mV[0] = mPosition.mV[VX] + x - xf; - position.mV[1] = mPosition.mV[VY] + y - xf; - position.mV[2] = mRegionp->getLand().resolveHeightRegion(position); - v3 = position + mRegionp->getOriginAgent(); - (*verticesp++).load3(v3.mV); - (*verticesp++).load3(v3.mV); - - LLVector3 normal1 = (v1-v2) % (v2-v3); - normal1.mV[VZ] = 0.75f; - normal1.normalize(); - LLVector3 normal2 = -normal1; - normal2.mV[VZ] = -normal2.mV[VZ]; - - position.mV[0] += dzx; - position.mV[1] += dzy; - position.mV[2] += blade_height; - v1 = position + mRegionp->getOriginAgent(); - (*verticesp++).load3(v1.mV); - (*verticesp++).load3(v1.mV); - - *(normalsp++) = normal1; - *(normalsp++) = normal2; - *(normalsp++) = normal1; - *(normalsp++) = normal2; - - *(normalsp++) = normal1; - *(normalsp++) = normal2; - *(normalsp++) = normal1; - *(normalsp++) = normal2; - - *(colorsp++) = color; - *(colorsp++) = color; - *(colorsp++) = color; - *(colorsp++) = color; - *(colorsp++) = color; - *(colorsp++) = color; - *(colorsp++) = color; - *(colorsp++) = color; - - *indicesp++ = index_offset + 0; - *indicesp++ = index_offset + 2; - *indicesp++ = index_offset + 4; - - *indicesp++ = index_offset + 2; - *indicesp++ = index_offset + 6; - *indicesp++ = index_offset + 4; - - *indicesp++ = index_offset + 1; - *indicesp++ = index_offset + 5; - *indicesp++ = index_offset + 3; - - *indicesp++ = index_offset + 3; - *indicesp++ = index_offset + 5; - *indicesp++ = index_offset + 7; - index_offset += 8; - } - - LLPipeline::sCompiles++; -} - -U32 LLVOGrass::getPartitionType() const -{ - return LLViewerRegion::PARTITION_GRASS; -} - -LLGrassPartition::LLGrassPartition(LLViewerRegion* regionp) -: LLSpatialPartition(LLDrawPoolAlpha::VERTEX_DATA_MASK | LLVertexBuffer::MAP_TEXTURE_INDEX, true, regionp) -{ - mDrawableType = LLPipeline::RENDER_TYPE_GRASS; - mPartitionType = LLViewerRegion::PARTITION_GRASS; - mLODPeriod = 16; - mDepthMask = true; - mSlopRatio = 0.1f; - mRenderPass = LLRenderPass::PASS_GRASS; -} - -void LLGrassPartition::addGeometryCount(LLSpatialGroup* group, U32& vertex_count, U32& index_count) -{ - mFaceList.clear(); - - LLViewerCamera* camera = LLViewerCamera::getInstance(); - for (LLSpatialGroup::element_iter i = group->getDataBegin(); i != group->getDataEnd(); ++i) - { - LLDrawable* drawablep = (LLDrawable*)(*i)->getDrawable(); - - if (!drawablep || drawablep->isDead()) - { - continue; - } - - LLAlphaObject* obj = (LLAlphaObject*) drawablep->getVObj().get(); - obj->mDepth = 0.f; - - U32 count = 0; - for (S32 j = 0; j < drawablep->getNumFaces(); ++j) - { - drawablep->updateFaceSize(j); - - LLFace* facep = drawablep->getFace(j); - if ( !facep || !facep->hasGeometry()) - { - continue; - } - - if ((facep->getGeomCount() + vertex_count) <= 65536) - { - count++; - facep->mDistance = (facep->mCenterLocal - camera->getOrigin()) * camera->getAtAxis(); - obj->mDepth += facep->mDistance; - - mFaceList.push_back(facep); - vertex_count += facep->getGeomCount(); - index_count += facep->getIndicesCount(); - llassert(facep->getIndicesCount() < 65536); - } - else - { - facep->clearVertexBuffer(); - } - } - - obj->mDepth /= count; - } -} - -void LLGrassPartition::getGeometry(LLSpatialGroup* group) -{ - LL_PROFILE_ZONE_SCOPED; - - std::sort(mFaceList.begin(), mFaceList.end(), LLFace::CompareDistanceGreater()); - - U32 index_count = 0; - U32 vertex_count = 0; - - group->clearDrawMap(); - - LLVertexBuffer* buffer = group->mVertexBuffer; - - LLStrider indicesp; - LLStrider verticesp; - LLStrider normalsp; - LLStrider texcoordsp; - LLStrider colorsp; - - buffer->getVertexStrider(verticesp); - buffer->getNormalStrider(normalsp); - buffer->getColorStrider(colorsp); - buffer->getTexCoord0Strider(texcoordsp); - buffer->getIndexStrider(indicesp); - - LLSpatialGroup::drawmap_elem_t& draw_vec = group->mDrawMap[mRenderPass]; - - for (std::vector::iterator i = mFaceList.begin(); i != mFaceList.end(); ++i) - { - LLFace* facep = *i; - LLAlphaObject* object = (LLAlphaObject*) facep->getViewerObject(); - facep->setGeomIndex(vertex_count); - facep->setIndicesIndex(index_count); - facep->setVertexBuffer(buffer); - facep->setPoolType(LLDrawPool::POOL_ALPHA); - - //dummy parameter (unused by this implementation) - LLStrider emissivep; - - object->getGeometry(facep->getTEOffset(), verticesp, normalsp, texcoordsp, colorsp, emissivep, indicesp); - - vertex_count += facep->getGeomCount(); - index_count += facep->getIndicesCount(); - - S32 idx = draw_vec.size()-1; - - bool fullbright = facep->isState(LLFace::FULLBRIGHT); - - if (idx >= 0 && draw_vec[idx]->mEnd == facep->getGeomIndex()-1 && - draw_vec[idx]->mTexture == facep->getTexture() && - (U16) (draw_vec[idx]->mEnd - draw_vec[idx]->mStart + facep->getGeomCount()) <= (U32) gGLManager.mGLMaxVertexRange && - //draw_vec[idx]->mCount + facep->getIndicesCount() <= (U32) gGLManager.mGLMaxIndexRange && - draw_vec[idx]->mEnd - draw_vec[idx]->mStart + facep->getGeomCount() < 4096 && - draw_vec[idx]->mFullbright == fullbright) - { - draw_vec[idx]->mCount += facep->getIndicesCount(); - draw_vec[idx]->mEnd += facep->getGeomCount(); - } - else - { - U32 start = facep->getGeomIndex(); - U32 end = start + facep->getGeomCount()-1; - U32 offset = facep->getIndicesStart(); - U32 count = facep->getIndicesCount(); - LLDrawInfo* info = new LLDrawInfo(start,end,count,offset,facep->getTexture(), - //facep->getTexture(), - buffer, object->isSelected(), fullbright); - - draw_vec.push_back(info); - //for alpha sorting - facep->setDrawInfo(info); - } - } - - buffer->unmapBuffer(); - mFaceList.clear(); -} - -// virtual -void LLVOGrass::updateDrawable(bool force_damped) -{ - // Force an immediate rebuild on any update - if (mDrawable.notNull()) - { - mDrawable->updateXform(true); - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); - } - clearChanged(SHIFTED); -} - -// virtual -bool LLVOGrass::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, S32 face, bool pick_transparent, bool pick_rigged, bool pick_unselectable, S32 *face_hitp, - LLVector4a* intersection,LLVector2* tex_coord, LLVector4a* normal, LLVector4a* tangent) - -{ - bool ret = false; - if (!mbCanSelect || - mDrawable->isDead() || - !gPipeline.hasRenderType(mDrawable->getRenderType())) - { - return false; - } - - LLVector4a dir; - dir.setSub(end, start); - - mPatch = mRegionp->getLand().resolvePatchRegion(getPositionRegion()); - - LLVector3 position; - // Create random blades of grass with gaussian distribution - F32 x,y,xf,yf,dzx,dzy; - - LLColor4U color(255,255,255,255); - - F32 width = sSpeciesTable[mSpecies]->mBladeSizeX; - F32 height = sSpeciesTable[mSpecies]->mBladeSizeY; - - LLVector2 tc[4]; - LLVector3 v[4]; - //LLVector3 n[4]; - - F32 closest_t = 1.f; - - for (S32 i = 0; i < mNumBlades; i++) - { - x = exp_x[i] * mScale.mV[VX]; - y = exp_y[i] * mScale.mV[VY]; - xf = rot_x[i] * GRASS_BLADE_BASE * width * w_mod[i]; - yf = rot_y[i] * GRASS_BLADE_BASE * width * w_mod[i]; - dzx = dz_x [i]; - dzy = dz_y [i]; - - LLVector3 v1,v2,v3; - F32 blade_height= GRASS_BLADE_HEIGHT * height * w_mod[i]; - - tc[0] = LLVector2(0, 0); - tc[1] = LLVector2(0, 0.98f); - tc[2] = LLVector2(1, 0); - tc[3] = LLVector2(1, 0.98f); - - position.mV[0] = mPosition.mV[VX] + x + xf; - position.mV[1] = mPosition.mV[VY] + y + yf; - position.mV[2] = mRegionp->getLand().resolveHeightRegion(position); - v[0] = v1 = position + mRegionp->getOriginAgent(); - - - - position.mV[0] += dzx; - position.mV[1] += dzy; - position.mV[2] += blade_height; - v[1] = v2 = position + mRegionp->getOriginAgent(); - - position.mV[0] = mPosition.mV[VX] + x - xf; - position.mV[1] = mPosition.mV[VY] + y - xf; - position.mV[2] = mRegionp->getLand().resolveHeightRegion(position); - v[2] = v3 = position + mRegionp->getOriginAgent(); - - LLVector3 normal1 = (v1-v2) % (v2-v3); - normal1.normalize(); - - position.mV[0] += dzx; - position.mV[1] += dzy; - position.mV[2] += blade_height; - v[3] = v1 = position + mRegionp->getOriginAgent(); - - F32 a,b,t; - - bool hit = false; - - - U32 idx0 = 0,idx1 = 0,idx2 = 0; - - LLVector4a v0a,v1a,v2a,v3a; - - v0a.load3(v[0].mV); - v1a.load3(v[1].mV); - v2a.load3(v[2].mV); - v3a.load3(v[3].mV); - - - if (LLTriangleRayIntersect(v0a, v1a, v2a, start, dir, a, b, t)) - { - hit = true; - idx0 = 0; idx1 = 1; idx2 = 2; - } - else if (LLTriangleRayIntersect(v1a, v3a, v2a, start, dir, a, b, t)) - { - hit = true; - idx0 = 1; idx1 = 3; idx2 = 2; - } - else if (LLTriangleRayIntersect(v2a, v1a, v0a, start, dir, a, b, t)) - { - normal1 = -normal1; - hit = true; - idx0 = 2; idx1 = 1; idx2 = 0; - } - else if (LLTriangleRayIntersect(v2a, v3a, v1a, start, dir, a, b, t)) - { - normal1 = -normal1; - hit = true; - idx0 = 2; idx1 = 3; idx2 = 1; - } - - if (hit) - { - if (t >= 0.f && - t <= 1.f && - t < closest_t) - { - - LLVector2 hit_tc = ((1.f - a - b) * tc[idx0] + - a * tc[idx1] + - b * tc[idx2]); - if (pick_transparent || - getTEImage(0)->getMask(hit_tc)) - { - closest_t = t; - if (intersection != NULL) - { - dir.mul(closest_t); - intersection->setAdd(start, dir); - } - - if (tex_coord != NULL) - { - *tex_coord = hit_tc; - } - - if (normal != NULL) - { - normal->load3(normal1.mV); - } - ret = true; - } - } - } - } - - return ret; -} - +/** + * @file llvograss.cpp + * @brief Not a blade, but a clump of grass + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llvograss.h" + +#include "llviewercontrol.h" + +#include "llagentcamera.h" +#include "llnotificationsutil.h" +#include "lldrawable.h" +#include "lldrawpoolalpha.h" +#include "llface.h" +#include "llsky.h" +#include "llsurface.h" +#include "llsurfacepatch.h" +#include "llviewercamera.h" +#include "llviewertexturelist.h" +#include "llviewerregion.h" +#include "pipeline.h" +#include "llspatialpartition.h" +#include "llworld.h" +#include "lldir.h" +#include "llxmltree.h" +#include "llvotree.h" + +const S32 GRASS_MAX_BLADES = 32; +const F32 GRASS_BLADE_BASE = 0.25f; // Width of grass at base +const F32 GRASS_BLADE_HEIGHT = 0.5f; // meters +const F32 GRASS_DISTRIBUTION_SD = 0.15f; // empirically defined + +F32 exp_x[GRASS_MAX_BLADES]; +F32 exp_y[GRASS_MAX_BLADES]; +F32 rot_x[GRASS_MAX_BLADES]; +F32 rot_y[GRASS_MAX_BLADES]; +F32 dz_x [GRASS_MAX_BLADES]; +F32 dz_y [GRASS_MAX_BLADES]; + +F32 w_mod[GRASS_MAX_BLADES]; // Factor to modulate wind movement by to randomize appearance + +LLVOGrass::SpeciesMap LLVOGrass::sSpeciesTable; +S32 LLVOGrass::sMaxGrassSpecies = 0; + + +LLVOGrass::LLVOGrass(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) +: LLAlphaObject(id, pcode, regionp) +{ + mPatch = NULL; + mLastPatchUpdateTime = 0; + mGrassVel.clearVec(); + mGrassBend.clearVec(); + mbCanSelect = true; + + mBladeWindAngle = 35.f; + mBWAOverlap = 2.f; + + setNumTEs(1); + + setTEColor(0, LLColor4(1.0f, 1.0f, 1.0f, 1.f)); + mNumBlades = GRASS_MAX_BLADES; +} + +LLVOGrass::~LLVOGrass() +{ +} + + +void LLVOGrass::updateSpecies() +{ + mSpecies = getAttachmentState(); + + if (!sSpeciesTable.count(mSpecies)) + { + LL_INFOS() << "Unknown grass type, substituting grass type." << LL_ENDL; + SpeciesMap::const_iterator it = sSpeciesTable.begin(); + mSpecies = (*it).first; + } + setTEImage(0, LLViewerTextureManager::getFetchedTexture(sSpeciesTable[mSpecies]->mTextureID, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE)); +} + + +void LLVOGrass::initClass() +{ + LLVector3 pos(0.0f, 0.0f, 0.0f); + // Create nifty list of exponential distribution 0-1 + F32 x = 0.f; + F32 y = 0.f; + F32 rot; + + std::string xml_filename = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS,"grass.xml"); + + LLXmlTree grass_def_grass; + + if (!grass_def_grass.parseFile(xml_filename)) + { + LL_ERRS() << "Failed to parse grass file." << LL_ENDL; + return; + } + + LLXmlTreeNode* rootp = grass_def_grass.getRoot(); + + for (LLXmlTreeNode* grass_def = rootp->getFirstChild(); + grass_def; + grass_def = rootp->getNextChild()) + { + if (!grass_def->hasName("grass")) + { + LL_WARNS() << "Invalid grass definition node " << grass_def->getName() << LL_ENDL; + continue; + } + F32 F32_val; + LLUUID id; + + bool success{ true }; + + S32 species; + static LLStdStringHandle species_id_string = LLXmlTree::addAttributeString("species_id"); + if (!grass_def->getFastAttributeS32(species_id_string, species)) + { + LL_WARNS() << "No species id defined" << LL_ENDL; + continue; + } + + if (species < 0) + { + LL_WARNS() << "Invalid species id " << species << LL_ENDL; + continue; + } + + GrassSpeciesData* newGrass = new GrassSpeciesData(); + + + static LLStdStringHandle texture_id_string = LLXmlTree::addAttributeString("texture_id"); + grass_def->getFastAttributeUUID(texture_id_string, id); + newGrass->mTextureID = id; + + static LLStdStringHandle blade_sizex_string = LLXmlTree::addAttributeString("blade_size_x"); + success &= grass_def->getFastAttributeF32(blade_sizex_string, F32_val); + newGrass->mBladeSizeX = F32_val; + + static LLStdStringHandle blade_sizey_string = LLXmlTree::addAttributeString("blade_size_y"); + success &= grass_def->getFastAttributeF32(blade_sizey_string, F32_val); + newGrass->mBladeSizeY = F32_val; + + if (sSpeciesTable.count(species)) + { + LL_INFOS() << "Grass species " << species << " already defined! Duplicate discarded." << LL_ENDL; + delete newGrass; + continue; + } + else + { + sSpeciesTable[species] = newGrass; + } + + if (species >= sMaxGrassSpecies) sMaxGrassSpecies = species + 1; + + if (!success) + { + std::string name; + static LLStdStringHandle name_string = LLXmlTree::addAttributeString("name"); + grass_def->getFastAttributeString(name_string, name); + LL_WARNS() << "Incomplete definition of grass " << name << LL_ENDL; + } + } + + bool have_all_grass{ true }; + std::string err; + + for (S32 i=0;i 0.f) + ||(getAcceleration().lengthSquared() > 0.f) + ||(getAngularVelocity().lengthSquared() > 0.f)) + { + LL_INFOS() << "ACK! Moving grass!" << LL_ENDL; + setVelocity(LLVector3::zero); + setAcceleration(LLVector3::zero); + setAngularVelocity(LLVector3::zero); + } + + if (mDrawable) + { + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); + } + + return retval; +} + +bool LLVOGrass::isActive() const +{ + return true; +} + +void LLVOGrass::idleUpdate(LLAgent &agent, const F64 &time) +{ + if (mDead || !(gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_GRASS))) + { + return; + } + + if (!mDrawable) + { + // So drones work. + return; + } + if (!LLVOTree::isTreeRenderingStopped() && !mNumBlades)//restart grass rendering + { + mNumBlades = GRASS_MAX_BLADES; + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); + return; + } + if (mPatch && (mLastPatchUpdateTime != mPatch->getLastUpdateTime())) + { + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); + } + + return; +} + + +void LLVOGrass::setPixelAreaAndAngle(LLAgent &agent) +{ + // This should be the camera's center, as soon as we move to all region-local. + LLVector3 relative_position = getPositionAgent() - gAgentCamera.getCameraPositionAgent(); + F32 range = relative_position.length(); + + F32 max_scale = getMaxScale(); + + mAppAngle = (F32) atan2( max_scale, range) * RAD_TO_DEG; + + // Compute pixels per meter at the given range + F32 pixels_per_meter = LLViewerCamera::getInstance()->getViewHeightInPixels() / (tan(LLViewerCamera::getInstance()->getView()) * range); + + // Assume grass texture is a 5 meter by 5 meter sprite at the grass object's center + mPixelArea = (pixels_per_meter) * (pixels_per_meter) * 25.f; +} + + +// BUG could speed this up by caching the relative_position and range calculations +void LLVOGrass::updateTextures() +{ + if (getTEImage(0)) + { + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_AREA)) + { + setDebugText(llformat("%4.0f", (F32) sqrt(mPixelArea))); + } + getTEImage(0)->addTextureStats(mPixelArea); + } +} + +bool LLVOGrass::updateLOD() +{ + if (mDrawable->getNumFaces() <= 0) + { + return false; + } + + LLFace* face = mDrawable->getFace(0); + + if(LLVOTree::isTreeRenderingStopped()) + { + if(mNumBlades) + { + mNumBlades = 0 ; + face->setSize(0, 0); + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); + } + return true ; + } + if(!mNumBlades) + { + mNumBlades = GRASS_MAX_BLADES; + } + + F32 tan_angle = 0.f; + S32 num_blades = 0; + + tan_angle = (mScale.mV[0]*mScale.mV[1])/mDrawable->mDistanceWRTCamera; + num_blades = llmin(GRASS_MAX_BLADES, lltrunc(tan_angle * 5)); + num_blades = llmax(1, num_blades); + if (num_blades >= (mNumBlades << 1)) + { + while (mNumBlades < num_blades) + { + mNumBlades <<= 1; + } + if (face) + { + face->setSize(mNumBlades*8, mNumBlades*12); + } + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); + } + else if (num_blades <= (mNumBlades >> 1)) + { + while (mNumBlades > num_blades) + { + mNumBlades >>=1; + } + + if (face) + { + face->setSize(mNumBlades*8, mNumBlades*12); + } + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); + return true; + } + + return false; +} + +LLDrawable* LLVOGrass::createDrawable(LLPipeline *pipeline) +{ + pipeline->allocDrawable(this); + mDrawable->setRenderType(LLPipeline::RENDER_TYPE_GRASS); + + return mDrawable; +} + +static LLTrace::BlockTimerStatHandle FTM_UPDATE_GRASS("Update Grass"); + +bool LLVOGrass::updateGeometry(LLDrawable *drawable) +{ + LL_RECORD_BLOCK_TIME(FTM_UPDATE_GRASS); + + dirtySpatialGroup(); + + if(!mNumBlades)//stop rendering grass + { + if (mDrawable->getNumFaces() > 0) + { + LLFace* facep = mDrawable->getFace(0); + if(facep) + { + facep->setSize(0, 0); + } + } + } + else + { + plantBlades(); + } + return true; +} + +void LLVOGrass::plantBlades() +{ + // It is possible that the species of a grass is not defined + // This is bad, but not the end of the world. + if (!sSpeciesTable.count(mSpecies)) + { + LL_INFOS() << "Unknown grass species " << mSpecies << LL_ENDL; + return; + } + + if (mDrawable->getNumFaces() < 1) + { + mDrawable->setNumFaces(1, NULL, getTEImage(0)); + } + + LLFace *face = mDrawable->getFace(0); + if (face) + { + face->setTexture(getTEImage(0)); + face->setState(LLFace::GLOBAL); + face->setSize(mNumBlades * 8, mNumBlades * 12); + face->setVertexBuffer(NULL); + face->setTEOffset(0); + face->mCenterLocal = mPosition + mRegionp->getOriginAgent(); + } + + mDepth = (face->mCenterLocal - LLViewerCamera::getInstance()->getOrigin())*LLViewerCamera::getInstance()->getAtAxis(); + mDrawable->setPosition(face->mCenterLocal); + mDrawable->movePartition(); + LLPipeline::sCompiles++; +} + +void LLVOGrass::getGeometry(S32 idx, + LLStrider& verticesp, + LLStrider& normalsp, + LLStrider& texcoordsp, + LLStrider& colorsp, + LLStrider& emissivep, + LLStrider& indicesp) +{ + if(!mNumBlades)//stop rendering grass + { + return ; + } + + mPatch = mRegionp->getLand().resolvePatchRegion(getPositionRegion()); + if (mPatch) + mLastPatchUpdateTime = mPatch->getLastUpdateTime(); + + LLVector3 position; + // Create random blades of grass with gaussian distribution + F32 x,y,xf,yf,dzx,dzy; + + LLColor4U color(255,255,255,255); + + LLFace *face = mDrawable->getFace(idx); + if (!face) + return; + + F32 width = sSpeciesTable[mSpecies]->mBladeSizeX; + F32 height = sSpeciesTable[mSpecies]->mBladeSizeY; + + U32 index_offset = face->getGeomIndex(); + + for (S32 i = 0; i < mNumBlades; i++) + { + x = exp_x[i] * mScale.mV[VX]; + y = exp_y[i] * mScale.mV[VY]; + xf = rot_x[i] * GRASS_BLADE_BASE * width * w_mod[i]; + yf = rot_y[i] * GRASS_BLADE_BASE * width * w_mod[i]; + dzx = dz_x [i]; + dzy = dz_y [i]; + + LLVector3 v1,v2,v3; + F32 blade_height= GRASS_BLADE_HEIGHT * height * w_mod[i]; + + *texcoordsp++ = LLVector2(0, 0); + *texcoordsp++ = LLVector2(0, 0); + *texcoordsp++ = LLVector2(0, 0.98f); + *texcoordsp++ = LLVector2(0, 0.98f); + *texcoordsp++ = LLVector2(1, 0); + *texcoordsp++ = LLVector2(1, 0); + *texcoordsp++ = LLVector2(1, 0.98f); + *texcoordsp++ = LLVector2(1, 0.98f); + + position.mV[0] = mPosition.mV[VX] + x + xf; + position.mV[1] = mPosition.mV[VY] + y + yf; + position.mV[2] = mRegionp->getLand().resolveHeightRegion(position); + v1 = position + mRegionp->getOriginAgent(); + (*verticesp++).load3(v1.mV); + (*verticesp++).load3(v1.mV); + + + position.mV[0] += dzx; + position.mV[1] += dzy; + position.mV[2] += blade_height; + v2 = position + mRegionp->getOriginAgent(); + (*verticesp++).load3(v2.mV); + (*verticesp++).load3(v2.mV); + + position.mV[0] = mPosition.mV[VX] + x - xf; + position.mV[1] = mPosition.mV[VY] + y - xf; + position.mV[2] = mRegionp->getLand().resolveHeightRegion(position); + v3 = position + mRegionp->getOriginAgent(); + (*verticesp++).load3(v3.mV); + (*verticesp++).load3(v3.mV); + + LLVector3 normal1 = (v1-v2) % (v2-v3); + normal1.mV[VZ] = 0.75f; + normal1.normalize(); + LLVector3 normal2 = -normal1; + normal2.mV[VZ] = -normal2.mV[VZ]; + + position.mV[0] += dzx; + position.mV[1] += dzy; + position.mV[2] += blade_height; + v1 = position + mRegionp->getOriginAgent(); + (*verticesp++).load3(v1.mV); + (*verticesp++).load3(v1.mV); + + *(normalsp++) = normal1; + *(normalsp++) = normal2; + *(normalsp++) = normal1; + *(normalsp++) = normal2; + + *(normalsp++) = normal1; + *(normalsp++) = normal2; + *(normalsp++) = normal1; + *(normalsp++) = normal2; + + *(colorsp++) = color; + *(colorsp++) = color; + *(colorsp++) = color; + *(colorsp++) = color; + *(colorsp++) = color; + *(colorsp++) = color; + *(colorsp++) = color; + *(colorsp++) = color; + + *indicesp++ = index_offset + 0; + *indicesp++ = index_offset + 2; + *indicesp++ = index_offset + 4; + + *indicesp++ = index_offset + 2; + *indicesp++ = index_offset + 6; + *indicesp++ = index_offset + 4; + + *indicesp++ = index_offset + 1; + *indicesp++ = index_offset + 5; + *indicesp++ = index_offset + 3; + + *indicesp++ = index_offset + 3; + *indicesp++ = index_offset + 5; + *indicesp++ = index_offset + 7; + index_offset += 8; + } + + LLPipeline::sCompiles++; +} + +U32 LLVOGrass::getPartitionType() const +{ + return LLViewerRegion::PARTITION_GRASS; +} + +LLGrassPartition::LLGrassPartition(LLViewerRegion* regionp) +: LLSpatialPartition(LLDrawPoolAlpha::VERTEX_DATA_MASK | LLVertexBuffer::MAP_TEXTURE_INDEX, true, regionp) +{ + mDrawableType = LLPipeline::RENDER_TYPE_GRASS; + mPartitionType = LLViewerRegion::PARTITION_GRASS; + mLODPeriod = 16; + mDepthMask = true; + mSlopRatio = 0.1f; + mRenderPass = LLRenderPass::PASS_GRASS; +} + +void LLGrassPartition::addGeometryCount(LLSpatialGroup* group, U32& vertex_count, U32& index_count) +{ + mFaceList.clear(); + + LLViewerCamera* camera = LLViewerCamera::getInstance(); + for (LLSpatialGroup::element_iter i = group->getDataBegin(); i != group->getDataEnd(); ++i) + { + LLDrawable* drawablep = (LLDrawable*)(*i)->getDrawable(); + + if (!drawablep || drawablep->isDead()) + { + continue; + } + + LLAlphaObject* obj = (LLAlphaObject*) drawablep->getVObj().get(); + obj->mDepth = 0.f; + + U32 count = 0; + for (S32 j = 0; j < drawablep->getNumFaces(); ++j) + { + drawablep->updateFaceSize(j); + + LLFace* facep = drawablep->getFace(j); + if ( !facep || !facep->hasGeometry()) + { + continue; + } + + if ((facep->getGeomCount() + vertex_count) <= 65536) + { + count++; + facep->mDistance = (facep->mCenterLocal - camera->getOrigin()) * camera->getAtAxis(); + obj->mDepth += facep->mDistance; + + mFaceList.push_back(facep); + vertex_count += facep->getGeomCount(); + index_count += facep->getIndicesCount(); + llassert(facep->getIndicesCount() < 65536); + } + else + { + facep->clearVertexBuffer(); + } + } + + obj->mDepth /= count; + } +} + +void LLGrassPartition::getGeometry(LLSpatialGroup* group) +{ + LL_PROFILE_ZONE_SCOPED; + + std::sort(mFaceList.begin(), mFaceList.end(), LLFace::CompareDistanceGreater()); + + U32 index_count = 0; + U32 vertex_count = 0; + + group->clearDrawMap(); + + LLVertexBuffer* buffer = group->mVertexBuffer; + + LLStrider indicesp; + LLStrider verticesp; + LLStrider normalsp; + LLStrider texcoordsp; + LLStrider colorsp; + + buffer->getVertexStrider(verticesp); + buffer->getNormalStrider(normalsp); + buffer->getColorStrider(colorsp); + buffer->getTexCoord0Strider(texcoordsp); + buffer->getIndexStrider(indicesp); + + LLSpatialGroup::drawmap_elem_t& draw_vec = group->mDrawMap[mRenderPass]; + + for (std::vector::iterator i = mFaceList.begin(); i != mFaceList.end(); ++i) + { + LLFace* facep = *i; + LLAlphaObject* object = (LLAlphaObject*) facep->getViewerObject(); + facep->setGeomIndex(vertex_count); + facep->setIndicesIndex(index_count); + facep->setVertexBuffer(buffer); + facep->setPoolType(LLDrawPool::POOL_ALPHA); + + //dummy parameter (unused by this implementation) + LLStrider emissivep; + + object->getGeometry(facep->getTEOffset(), verticesp, normalsp, texcoordsp, colorsp, emissivep, indicesp); + + vertex_count += facep->getGeomCount(); + index_count += facep->getIndicesCount(); + + S32 idx = draw_vec.size()-1; + + bool fullbright = facep->isState(LLFace::FULLBRIGHT); + + if (idx >= 0 && draw_vec[idx]->mEnd == facep->getGeomIndex()-1 && + draw_vec[idx]->mTexture == facep->getTexture() && + (U16) (draw_vec[idx]->mEnd - draw_vec[idx]->mStart + facep->getGeomCount()) <= (U32) gGLManager.mGLMaxVertexRange && + //draw_vec[idx]->mCount + facep->getIndicesCount() <= (U32) gGLManager.mGLMaxIndexRange && + draw_vec[idx]->mEnd - draw_vec[idx]->mStart + facep->getGeomCount() < 4096 && + draw_vec[idx]->mFullbright == fullbright) + { + draw_vec[idx]->mCount += facep->getIndicesCount(); + draw_vec[idx]->mEnd += facep->getGeomCount(); + } + else + { + U32 start = facep->getGeomIndex(); + U32 end = start + facep->getGeomCount()-1; + U32 offset = facep->getIndicesStart(); + U32 count = facep->getIndicesCount(); + LLDrawInfo* info = new LLDrawInfo(start,end,count,offset,facep->getTexture(), + //facep->getTexture(), + buffer, object->isSelected(), fullbright); + + draw_vec.push_back(info); + //for alpha sorting + facep->setDrawInfo(info); + } + } + + buffer->unmapBuffer(); + mFaceList.clear(); +} + +// virtual +void LLVOGrass::updateDrawable(bool force_damped) +{ + // Force an immediate rebuild on any update + if (mDrawable.notNull()) + { + mDrawable->updateXform(true); + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); + } + clearChanged(SHIFTED); +} + +// virtual +bool LLVOGrass::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, S32 face, bool pick_transparent, bool pick_rigged, bool pick_unselectable, S32 *face_hitp, + LLVector4a* intersection,LLVector2* tex_coord, LLVector4a* normal, LLVector4a* tangent) + +{ + bool ret = false; + if (!mbCanSelect || + mDrawable->isDead() || + !gPipeline.hasRenderType(mDrawable->getRenderType())) + { + return false; + } + + LLVector4a dir; + dir.setSub(end, start); + + mPatch = mRegionp->getLand().resolvePatchRegion(getPositionRegion()); + + LLVector3 position; + // Create random blades of grass with gaussian distribution + F32 x,y,xf,yf,dzx,dzy; + + LLColor4U color(255,255,255,255); + + F32 width = sSpeciesTable[mSpecies]->mBladeSizeX; + F32 height = sSpeciesTable[mSpecies]->mBladeSizeY; + + LLVector2 tc[4]; + LLVector3 v[4]; + //LLVector3 n[4]; + + F32 closest_t = 1.f; + + for (S32 i = 0; i < mNumBlades; i++) + { + x = exp_x[i] * mScale.mV[VX]; + y = exp_y[i] * mScale.mV[VY]; + xf = rot_x[i] * GRASS_BLADE_BASE * width * w_mod[i]; + yf = rot_y[i] * GRASS_BLADE_BASE * width * w_mod[i]; + dzx = dz_x [i]; + dzy = dz_y [i]; + + LLVector3 v1,v2,v3; + F32 blade_height= GRASS_BLADE_HEIGHT * height * w_mod[i]; + + tc[0] = LLVector2(0, 0); + tc[1] = LLVector2(0, 0.98f); + tc[2] = LLVector2(1, 0); + tc[3] = LLVector2(1, 0.98f); + + position.mV[0] = mPosition.mV[VX] + x + xf; + position.mV[1] = mPosition.mV[VY] + y + yf; + position.mV[2] = mRegionp->getLand().resolveHeightRegion(position); + v[0] = v1 = position + mRegionp->getOriginAgent(); + + + + position.mV[0] += dzx; + position.mV[1] += dzy; + position.mV[2] += blade_height; + v[1] = v2 = position + mRegionp->getOriginAgent(); + + position.mV[0] = mPosition.mV[VX] + x - xf; + position.mV[1] = mPosition.mV[VY] + y - xf; + position.mV[2] = mRegionp->getLand().resolveHeightRegion(position); + v[2] = v3 = position + mRegionp->getOriginAgent(); + + LLVector3 normal1 = (v1-v2) % (v2-v3); + normal1.normalize(); + + position.mV[0] += dzx; + position.mV[1] += dzy; + position.mV[2] += blade_height; + v[3] = v1 = position + mRegionp->getOriginAgent(); + + F32 a,b,t; + + bool hit = false; + + + U32 idx0 = 0,idx1 = 0,idx2 = 0; + + LLVector4a v0a,v1a,v2a,v3a; + + v0a.load3(v[0].mV); + v1a.load3(v[1].mV); + v2a.load3(v[2].mV); + v3a.load3(v[3].mV); + + + if (LLTriangleRayIntersect(v0a, v1a, v2a, start, dir, a, b, t)) + { + hit = true; + idx0 = 0; idx1 = 1; idx2 = 2; + } + else if (LLTriangleRayIntersect(v1a, v3a, v2a, start, dir, a, b, t)) + { + hit = true; + idx0 = 1; idx1 = 3; idx2 = 2; + } + else if (LLTriangleRayIntersect(v2a, v1a, v0a, start, dir, a, b, t)) + { + normal1 = -normal1; + hit = true; + idx0 = 2; idx1 = 1; idx2 = 0; + } + else if (LLTriangleRayIntersect(v2a, v3a, v1a, start, dir, a, b, t)) + { + normal1 = -normal1; + hit = true; + idx0 = 2; idx1 = 3; idx2 = 1; + } + + if (hit) + { + if (t >= 0.f && + t <= 1.f && + t < closest_t) + { + + LLVector2 hit_tc = ((1.f - a - b) * tc[idx0] + + a * tc[idx1] + + b * tc[idx2]); + if (pick_transparent || + getTEImage(0)->getMask(hit_tc)) + { + closest_t = t; + if (intersection != NULL) + { + dir.mul(closest_t); + intersection->setAdd(start, dir); + } + + if (tex_coord != NULL) + { + *tex_coord = hit_tc; + } + + if (normal != NULL) + { + normal->load3(normal1.mV); + } + ret = true; + } + } + } + } + + return ret; +} + diff --git a/indra/newview/llvograss.h b/indra/newview/llvograss.h index 9cf375733b..f34883fa59 100644 --- a/indra/newview/llvograss.h +++ b/indra/newview/llvograss.h @@ -1,126 +1,126 @@ -/** - * @file llvograss.h - * @brief Description of LLVOGrass class - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVOGRASS_H -#define LL_LLVOGRASS_H - -#include "llviewerobject.h" -#include - -class LLSurfacePatch; -class LLViewerTexture; - - -class LLVOGrass : public LLAlphaObject -{ -public: - LLVOGrass(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); - - // Initialize data that's only inited once per class. - static void initClass(); - static void cleanupClass(); - - virtual U32 getPartitionType() const; - - /*virtual*/ U32 processUpdateMessage(LLMessageSystem *mesgsys, - void **user_data, - U32 block_num, - const EObjectUpdateType update_type, - LLDataPacker *dp); - static void import(LLFILE *file, LLMessageSystem *mesgsys, const LLVector3 &pos); - /*virtual*/ void exportFile(LLFILE *file, const LLVector3 &position); - - void updateDrawable(bool force_damped); - - /*virtual*/ LLDrawable* createDrawable(LLPipeline *pipeline); - /*virtual*/ bool updateGeometry(LLDrawable *drawable); - /*virtual*/ void getGeometry(S32 idx, - LLStrider& verticesp, - LLStrider& normalsp, - LLStrider& texcoordsp, - LLStrider& colorsp, - LLStrider& emissivep, - LLStrider& indicesp); - - void updateFaceSize(S32 idx) { } - /*virtual*/ void updateTextures(); - /*virtual*/ bool updateLOD(); - /*virtual*/ void setPixelAreaAndAngle(LLAgent &agent); // generate accurate apparent angle and area - - void plantBlades(); - - /*virtual*/ bool isActive() const; // Whether this object needs to do an idleUpdate. - /*virtual*/ void idleUpdate(LLAgent &agent, const F64 &time); - - /*virtual*/ bool lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, - S32 face = -1, // which face to check, -1 = ALL_SIDES - bool pick_transparent = false, - bool pick_rigged = false, - bool pick_unselectable = true, - S32* face_hit = NULL, // which face was hit - LLVector4a* intersection = NULL, // return the intersection point - LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point - LLVector4a* normal = NULL, // return the surface normal at the intersection point - LLVector4a* tangent = NULL // return the surface tangent at the intersection point - ); - - static S32 sMaxGrassSpecies; - - struct GrassSpeciesData - { - LLUUID mTextureID; - - F32 mBladeSizeX; - F32 mBladeSizeY; - }; - - typedef std::map SpeciesMap; - - U8 mSpecies; // Species of grass - F32 mBladeSizeX; - F32 mBladeSizeY; - - LLSurfacePatch *mPatch; // Stores the land patch where the grass is centered - - U64 mLastPatchUpdateTime; - - LLVector3 mGrassBend; // Accumulated wind (used for blowing trees) - LLVector3 mGrassVel; - LLVector3 mWind; - F32 mBladeWindAngle; - F32 mBWAOverlap; - -protected: - ~LLVOGrass(); - -private: - void updateSpecies(); - F32 mLastHeight; // For cheap update hack - S32 mNumBlades; - - static SpeciesMap sSpeciesTable; -}; -#endif // LL_VO_GRASS_ +/** + * @file llvograss.h + * @brief Description of LLVOGrass class + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVOGRASS_H +#define LL_LLVOGRASS_H + +#include "llviewerobject.h" +#include + +class LLSurfacePatch; +class LLViewerTexture; + + +class LLVOGrass : public LLAlphaObject +{ +public: + LLVOGrass(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); + + // Initialize data that's only inited once per class. + static void initClass(); + static void cleanupClass(); + + virtual U32 getPartitionType() const; + + /*virtual*/ U32 processUpdateMessage(LLMessageSystem *mesgsys, + void **user_data, + U32 block_num, + const EObjectUpdateType update_type, + LLDataPacker *dp); + static void import(LLFILE *file, LLMessageSystem *mesgsys, const LLVector3 &pos); + /*virtual*/ void exportFile(LLFILE *file, const LLVector3 &position); + + void updateDrawable(bool force_damped); + + /*virtual*/ LLDrawable* createDrawable(LLPipeline *pipeline); + /*virtual*/ bool updateGeometry(LLDrawable *drawable); + /*virtual*/ void getGeometry(S32 idx, + LLStrider& verticesp, + LLStrider& normalsp, + LLStrider& texcoordsp, + LLStrider& colorsp, + LLStrider& emissivep, + LLStrider& indicesp); + + void updateFaceSize(S32 idx) { } + /*virtual*/ void updateTextures(); + /*virtual*/ bool updateLOD(); + /*virtual*/ void setPixelAreaAndAngle(LLAgent &agent); // generate accurate apparent angle and area + + void plantBlades(); + + /*virtual*/ bool isActive() const; // Whether this object needs to do an idleUpdate. + /*virtual*/ void idleUpdate(LLAgent &agent, const F64 &time); + + /*virtual*/ bool lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, + S32 face = -1, // which face to check, -1 = ALL_SIDES + bool pick_transparent = false, + bool pick_rigged = false, + bool pick_unselectable = true, + S32* face_hit = NULL, // which face was hit + LLVector4a* intersection = NULL, // return the intersection point + LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point + LLVector4a* normal = NULL, // return the surface normal at the intersection point + LLVector4a* tangent = NULL // return the surface tangent at the intersection point + ); + + static S32 sMaxGrassSpecies; + + struct GrassSpeciesData + { + LLUUID mTextureID; + + F32 mBladeSizeX; + F32 mBladeSizeY; + }; + + typedef std::map SpeciesMap; + + U8 mSpecies; // Species of grass + F32 mBladeSizeX; + F32 mBladeSizeY; + + LLSurfacePatch *mPatch; // Stores the land patch where the grass is centered + + U64 mLastPatchUpdateTime; + + LLVector3 mGrassBend; // Accumulated wind (used for blowing trees) + LLVector3 mGrassVel; + LLVector3 mWind; + F32 mBladeWindAngle; + F32 mBWAOverlap; + +protected: + ~LLVOGrass(); + +private: + void updateSpecies(); + F32 mLastHeight; // For cheap update hack + S32 mNumBlades; + + static SpeciesMap sSpeciesTable; +}; +#endif // LL_VO_GRASS_ diff --git a/indra/newview/llvoicecallhandler.cpp b/indra/newview/llvoicecallhandler.cpp index e8d8b659b1..25b0e69436 100644 --- a/indra/newview/llvoicecallhandler.cpp +++ b/indra/newview/llvoicecallhandler.cpp @@ -1,63 +1,63 @@ - /** - * @file llvoicecallhandler.cpp - * @brief slapp to handle avatar to avatar voice call. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llcommandhandler.h" -#include "llavataractions.h" -#include "llnotificationsutil.h" -#include "llui.h" - -class LLVoiceCallAvatarHandler : public LLCommandHandler -{ -public: - // requires trusted browser to trigger - LLVoiceCallAvatarHandler() : LLCommandHandler("voicecallavatar", UNTRUSTED_THROTTLE) - { - } - - bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) - { - //Make sure we have some parameters - if (params.size() == 0) - { - return false; - } - - //Get the ID - LLUUID id; - if (!id.set( params[0], false )) - { - return false; - } - - //instigate call with this avatar - LLAvatarActions::startCall( id ); - return true; - } -}; - -LLVoiceCallAvatarHandler gVoiceCallAvatarHandler; - + /** + * @file llvoicecallhandler.cpp + * @brief slapp to handle avatar to avatar voice call. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llcommandhandler.h" +#include "llavataractions.h" +#include "llnotificationsutil.h" +#include "llui.h" + +class LLVoiceCallAvatarHandler : public LLCommandHandler +{ +public: + // requires trusted browser to trigger + LLVoiceCallAvatarHandler() : LLCommandHandler("voicecallavatar", UNTRUSTED_THROTTLE) + { + } + + bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) + { + //Make sure we have some parameters + if (params.size() == 0) + { + return false; + } + + //Get the ID + LLUUID id; + if (!id.set( params[0], false )) + { + return false; + } + + //instigate call with this avatar + LLAvatarActions::startCall( id ); + return true; + } +}; + +LLVoiceCallAvatarHandler gVoiceCallAvatarHandler; + diff --git a/indra/newview/llvoicechannel.cpp b/indra/newview/llvoicechannel.cpp index a457f838d2..234520a544 100644 --- a/indra/newview/llvoicechannel.cpp +++ b/indra/newview/llvoicechannel.cpp @@ -1,947 +1,947 @@ -/** - * @file llvoicechannel.cpp - * @brief Voice Channel related classes - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llagent.h" -#include "llfloaterreg.h" -#include "llimview.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "llpanel.h" -#include "llrecentpeople.h" -#include "llviewercontrol.h" -#include "llviewerregion.h" -#include "llvoicechannel.h" -#include "llcorehttputil.h" - -LLVoiceChannel::voice_channel_map_t LLVoiceChannel::sVoiceChannelMap; -LLVoiceChannel::voice_channel_map_uri_t LLVoiceChannel::sVoiceChannelURIMap; -LLVoiceChannel* LLVoiceChannel::sCurrentVoiceChannel = NULL; -LLVoiceChannel* LLVoiceChannel::sSuspendedVoiceChannel = NULL; -LLVoiceChannel::channel_changed_signal_t LLVoiceChannel::sCurrentVoiceChannelChangedSignal; - -bool LLVoiceChannel::sSuspended = false; - -// -// Constants -// -const U32 DEFAULT_RETRIES_COUNT = 3; - -// -// LLVoiceChannel -// -LLVoiceChannel::LLVoiceChannel(const LLUUID& session_id, const std::string& session_name) : - mSessionID(session_id), - mState(STATE_NO_CHANNEL_INFO), - mSessionName(session_name), - mCallDirection(OUTGOING_CALL), - mIgnoreNextSessionLeave(false), - mCallEndedByAgent(false) -{ - mNotifyArgs["VOICE_CHANNEL_NAME"] = mSessionName; - - if (!sVoiceChannelMap.insert(std::make_pair(session_id, this)).second) - { - // a voice channel already exists for this session id, so this instance will be orphaned - // the end result should simply be the failure to make voice calls - LL_WARNS("Voice") << "Duplicate voice channels registered for session_id " << session_id << LL_ENDL; - } -} - -LLVoiceChannel::~LLVoiceChannel() -{ - if (sSuspendedVoiceChannel == this) - { - sSuspendedVoiceChannel = NULL; - } - if (sCurrentVoiceChannel == this) - { - sCurrentVoiceChannel = NULL; - // Must check instance exists here, the singleton MAY have already been destroyed. - if(LLVoiceClient::instanceExists()) - { - LLVoiceClient::getInstance()->removeObserver(this); - } - } - - sVoiceChannelMap.erase(mSessionID); - sVoiceChannelURIMap.erase(mURI); -} - -void LLVoiceChannel::setChannelInfo( - const std::string& uri, - const std::string& credentials) -{ - setURI(uri); - - mCredentials = credentials; - - if (mState == STATE_NO_CHANNEL_INFO) - { - if (mURI.empty()) - { - LLNotificationsUtil::add("VoiceChannelJoinFailed", mNotifyArgs); - LL_WARNS("Voice") << "Received empty URI for channel " << mSessionName << LL_ENDL; - deactivate(); - } - else if (mCredentials.empty()) - { - LLNotificationsUtil::add("VoiceChannelJoinFailed", mNotifyArgs); - LL_WARNS("Voice") << "Received empty credentials for channel " << mSessionName << LL_ENDL; - deactivate(); - } - else - { - setState(STATE_READY); - - // if we are supposed to be active, reconnect - // this will happen on initial connect, as we request credentials on first use - if (sCurrentVoiceChannel == this) - { - // just in case we got new channel info while active - // should move over to new channel - activate(); - } - } - } -} - -void LLVoiceChannel::onChange(EStatusType type, const std::string &channelURI, bool proximal) -{ - if (channelURI != mURI) - { - return; - } - - if (type < BEGIN_ERROR_STATUS) - { - handleStatusChange(type); - } - else - { - handleError(type); - } -} - -void LLVoiceChannel::handleStatusChange(EStatusType type) -{ - // status updates - switch(type) - { - case STATUS_LOGIN_RETRY: - // no user notice - break; - case STATUS_LOGGED_IN: - break; - case STATUS_LEFT_CHANNEL: - if (callStarted() && !mIgnoreNextSessionLeave && !sSuspended) - { - // if forceably removed from channel - // update the UI and revert to default channel - deactivate(); - } - mIgnoreNextSessionLeave = false; - break; - case STATUS_JOINING: - if (callStarted()) - { - setState(STATE_RINGING); - } - break; - case STATUS_JOINED: - if (callStarted()) - { - setState(STATE_CONNECTED); - } - default: - break; - } -} - -// default behavior is to just deactivate channel -// derived classes provide specific error messages -void LLVoiceChannel::handleError(EStatusType type) -{ - deactivate(); - setState(STATE_ERROR); -} - -bool LLVoiceChannel::isActive() -{ - // only considered active when currently bound channel matches what our channel - return callStarted() && LLVoiceClient::getInstance()->getCurrentChannel() == mURI; -} - -bool LLVoiceChannel::callStarted() -{ - return mState >= STATE_CALL_STARTED; -} - -void LLVoiceChannel::deactivate() -{ - if (mState >= STATE_RINGING) - { - // ignore session leave event - mIgnoreNextSessionLeave = true; - } - - if (callStarted()) - { - setState(STATE_HUNG_UP); - - //Default mic is OFF when leaving voice calls - if (gSavedSettings.getBOOL("AutoDisengageMic") && - sCurrentVoiceChannel == this && - LLVoiceClient::getInstance()->getUserPTTState()) - { - gSavedSettings.setBOOL("PTTCurrentlyEnabled", true); - LLVoiceClient::getInstance()->setUserPTTState(false); - } - } - LLVoiceClient::getInstance()->removeObserver(this); - - if (sCurrentVoiceChannel == this) - { - // default channel is proximal channel - sCurrentVoiceChannel = LLVoiceChannelProximal::getInstance(); - sCurrentVoiceChannel->activate(); - } -} - -void LLVoiceChannel::activate() -{ - if (callStarted()) - { - return; - } - - // deactivate old channel and mark ourselves as the active one - if (sCurrentVoiceChannel != this) - { - // mark as current before deactivating the old channel to prevent - // activating the proximal channel between IM calls - LLVoiceChannel* old_channel = sCurrentVoiceChannel; - sCurrentVoiceChannel = this; - mCallDialogPayload["old_channel_name"] = ""; - if (old_channel) - { - mCallDialogPayload["old_channel_name"] = old_channel->getSessionName(); - old_channel->deactivate(); - } - } - - if (mState == STATE_NO_CHANNEL_INFO) - { - // responsible for setting status to active - getChannelInfo(); - } - else - { - setState(STATE_CALL_STARTED); - } - - LLVoiceClient::getInstance()->addObserver(this); - - //do not send earlier, channel should be initialized, should not be in STATE_NO_CHANNEL_INFO state - sCurrentVoiceChannelChangedSignal(this->mSessionID); -} - -void LLVoiceChannel::getChannelInfo() -{ - // pretend we have everything we need - if (sCurrentVoiceChannel == this) - { - setState(STATE_CALL_STARTED); - } -} - -//static -LLVoiceChannel* LLVoiceChannel::getChannelByID(const LLUUID& session_id) -{ - voice_channel_map_t::iterator found_it = sVoiceChannelMap.find(session_id); - if (found_it == sVoiceChannelMap.end()) - { - return NULL; - } - else - { - return found_it->second; - } -} - -//static -LLVoiceChannel* LLVoiceChannel::getChannelByURI(std::string uri) -{ - voice_channel_map_uri_t::iterator found_it = sVoiceChannelURIMap.find(uri); - if (found_it == sVoiceChannelURIMap.end()) - { - return NULL; - } - else - { - return found_it->second; - } -} - -LLVoiceChannel* LLVoiceChannel::getCurrentVoiceChannel() -{ - return sCurrentVoiceChannel; -} - -void LLVoiceChannel::updateSessionID(const LLUUID& new_session_id) -{ - sVoiceChannelMap.erase(sVoiceChannelMap.find(mSessionID)); - mSessionID = new_session_id; - sVoiceChannelMap.insert(std::make_pair(mSessionID, this)); -} - -void LLVoiceChannel::setURI(std::string uri) -{ - sVoiceChannelURIMap.erase(mURI); - mURI = uri; - sVoiceChannelURIMap.insert(std::make_pair(mURI, this)); -} - -void LLVoiceChannel::setState(EState state) -{ - switch(state) - { - case STATE_RINGING: - //TODO: remove or redirect this call status notification -// LLCallInfoDialog::show("ringing", mNotifyArgs); - break; - case STATE_CONNECTED: - //TODO: remove or redirect this call status notification -// LLCallInfoDialog::show("connected", mNotifyArgs); - break; - case STATE_HUNG_UP: - //TODO: remove or redirect this call status notification -// LLCallInfoDialog::show("hang_up", mNotifyArgs); - break; - default: - break; - } - - doSetState(state); -} - -void LLVoiceChannel::doSetState(const EState& new_state) -{ - EState old_state = mState; - mState = new_state; - - if (!mStateChangedCallback.empty()) - mStateChangedCallback(old_state, mState, mCallDirection, mCallEndedByAgent, mSessionID); -} - -//static -void LLVoiceChannel::initClass() -{ - sCurrentVoiceChannel = LLVoiceChannelProximal::getInstance(); -} - -//static -void LLVoiceChannel::suspend() -{ - if (!sSuspended) - { - sSuspendedVoiceChannel = sCurrentVoiceChannel; - sSuspended = true; - } -} - -//static -void LLVoiceChannel::resume() -{ - if (sSuspended) - { - if (LLVoiceClient::getInstance()->voiceEnabled()) - { - if (sSuspendedVoiceChannel) - { - sSuspendedVoiceChannel->activate(); - } - else - { - LLVoiceChannelProximal::getInstance()->activate(); - } - } - sSuspended = false; - } -} - -boost::signals2::connection LLVoiceChannel::setCurrentVoiceChannelChangedCallback(channel_changed_callback_t cb, bool at_front) -{ - if (at_front) - { - return sCurrentVoiceChannelChangedSignal.connect(cb, boost::signals2::at_front); - } - else - { - return sCurrentVoiceChannelChangedSignal.connect(cb); - } -} - -// -// LLVoiceChannelGroup -// - -LLVoiceChannelGroup::LLVoiceChannelGroup(const LLUUID& session_id, const std::string& session_name) : - LLVoiceChannel(session_id, session_name) -{ - mRetries = DEFAULT_RETRIES_COUNT; - mIsRetrying = false; -} - -void LLVoiceChannelGroup::deactivate() -{ - if (callStarted()) - { - LLVoiceClient::getInstance()->leaveNonSpatialChannel(); - } - LLVoiceChannel::deactivate(); -} - -void LLVoiceChannelGroup::activate() -{ - if (callStarted()) return; - - LLVoiceChannel::activate(); - - if (callStarted()) - { - // we have the channel info, just need to use it now - LLVoiceClient::getInstance()->setNonSpatialChannel( - mURI, - mCredentials); - - if (!gAgent.isInGroup(mSessionID)) // ad-hoc channel - { - LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(mSessionID); - // Adding ad-hoc call participants to Recent People List. - // If it's an outgoing ad-hoc, we can use mInitialTargetIDs that holds IDs of people we - // called(both online and offline) as source to get people for recent (STORM-210). - if (session->isOutgoingAdHoc()) - { - for (uuid_vec_t::iterator it = session->mInitialTargetIDs.begin(); - it!=session->mInitialTargetIDs.end();++it) - { - const LLUUID id = *it; - LLRecentPeople::instance().add(id); - } - } - // If this ad-hoc is incoming then trying to get ids of people from mInitialTargetIDs - // would lead to EXT-8246. So in this case we get them from speakers list. - else - { - LLIMModel::addSpeakersToRecent(mSessionID); - } - } - - //Mic default state is OFF on initiating/joining Ad-Hoc/Group calls - if (LLVoiceClient::getInstance()->getUserPTTState() && LLVoiceClient::getInstance()->getPTTIsToggle()) - { - LLVoiceClient::getInstance()->inputUserControlState(true); - } - - } -} - -void LLVoiceChannelGroup::getChannelInfo() -{ - LLViewerRegion* region = gAgent.getRegion(); - if (region) - { - std::string url = region->getCapability("ChatSessionRequest"); - - LLCoros::instance().launch("LLVoiceChannelGroup::voiceCallCapCoro", - boost::bind(&LLVoiceChannelGroup::voiceCallCapCoro, this, url)); - } -} - -void LLVoiceChannelGroup::setChannelInfo( - const std::string& uri, - const std::string& credentials) -{ - setURI(uri); - - mCredentials = credentials; - - if (mState == STATE_NO_CHANNEL_INFO) - { - if(!mURI.empty() && !mCredentials.empty()) - { - setState(STATE_READY); - - // if we are supposed to be active, reconnect - // this will happen on initial connect, as we request credentials on first use - if (sCurrentVoiceChannel == this) - { - // just in case we got new channel info while active - // should move over to new channel - activate(); - } - } - else - { - //*TODO: notify user - LL_WARNS("Voice") << "Received invalid credentials for channel " << mSessionName << LL_ENDL; - deactivate(); - } - } - else if ( mIsRetrying ) - { - // we have the channel info, just need to use it now - LLVoiceClient::getInstance()->setNonSpatialChannel( - mURI, - mCredentials); - } -} - -void LLVoiceChannelGroup::handleStatusChange(EStatusType type) -{ - // status updates - switch(type) - { - case STATUS_JOINED: - mRetries = 3; - mIsRetrying = false; - default: - break; - } - - LLVoiceChannel::handleStatusChange(type); -} - -void LLVoiceChannelGroup::handleError(EStatusType status) -{ - std::string notify; - switch(status) - { - case ERROR_CHANNEL_LOCKED: - case ERROR_CHANNEL_FULL: - notify = "VoiceChannelFull"; - break; - case ERROR_NOT_AVAILABLE: - //clear URI and credentials - //set the state to be no info - //and activate - if ( mRetries > 0 ) - { - mRetries--; - mIsRetrying = true; - mIgnoreNextSessionLeave = true; - - getChannelInfo(); - return; - } - else - { - notify = "VoiceChannelJoinFailed"; - mRetries = DEFAULT_RETRIES_COUNT; - mIsRetrying = false; - } - - break; - - case ERROR_UNKNOWN: - default: - break; - } - - // notification - if (!notify.empty()) - { - LLNotificationPtr notification = LLNotificationsUtil::add(notify, mNotifyArgs); - // echo to im window - gIMMgr->addMessage(mSessionID, LLUUID::null, SYSTEM_FROM, notification->getMessage()); - } - - LLVoiceChannel::handleError(status); -} - -void LLVoiceChannelGroup::setState(EState state) -{ - switch(state) - { - case STATE_RINGING: - if ( !mIsRetrying ) - { - //TODO: remove or redirect this call status notification -// LLCallInfoDialog::show("ringing", mNotifyArgs); - } - - doSetState(state); - break; - default: - LLVoiceChannel::setState(state); - } -} - -void LLVoiceChannelGroup::voiceCallCapCoro(std::string url) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("voiceCallCapCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD postData; - postData["method"] = "call"; - postData["session-id"] = mSessionID; - - LL_INFOS("Voice", "voiceCallCapCoro") << "Generic POST for " << url << LL_ENDL; - - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(mSessionID); - if (!channelp) - { - LL_WARNS("Voice") << "Unable to retrieve channel with Id = " << mSessionID << LL_ENDL; - return; - } - - if (!status) - { - if (status == LLCore::HttpStatus(HTTP_FORBIDDEN)) - { - //403 == no ability - LLNotificationsUtil::add( - "VoiceNotAllowed", - channelp->getNotifyArgs()); - } - else - { - LLNotificationsUtil::add( - "VoiceCallGenericError", - channelp->getNotifyArgs()); - } - channelp->deactivate(); - return; - } - - result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); - - LLSD::map_const_iterator iter; - for (iter = result.beginMap(); iter != result.endMap(); ++iter) - { - LL_DEBUGS("Voice") << "LLVoiceCallCapResponder::result got " - << iter->first << LL_ENDL; - } - - channelp->setChannelInfo( - result["voice_credentials"]["channel_uri"].asString(), - result["voice_credentials"]["channel_credentials"].asString()); - -} - - -// -// LLVoiceChannelProximal -// -LLVoiceChannelProximal::LLVoiceChannelProximal() : - LLVoiceChannel(LLUUID::null, LLStringUtil::null) -{ -} - -bool LLVoiceChannelProximal::isActive() -{ - return callStarted() && LLVoiceClient::getInstance()->inProximalChannel(); -} - -void LLVoiceChannelProximal::activate() -{ - if (callStarted()) return; - - if((LLVoiceChannel::sCurrentVoiceChannel != this) && (LLVoiceChannel::getState() == STATE_CONNECTED)) - { - // we're connected to a non-spatial channel, so disconnect. - LLVoiceClient::getInstance()->leaveNonSpatialChannel(); - } - LLVoiceChannel::activate(); - -} - -void LLVoiceChannelProximal::onChange(EStatusType type, const std::string &channelURI, bool proximal) -{ - if (!proximal) - { - return; - } - - if (type < BEGIN_ERROR_STATUS) - { - handleStatusChange(type); - } - else - { - handleError(type); - } -} - -void LLVoiceChannelProximal::handleStatusChange(EStatusType status) -{ - // status updates - switch(status) - { - case STATUS_LEFT_CHANNEL: - // do not notify user when leaving proximal channel - return; - case STATUS_VOICE_DISABLED: - LLVoiceClient::getInstance()->setUserPTTState(false); - gAgent.setVoiceConnected(false); - //skip showing "Voice not available at your current location" when agent voice is disabled (EXT-4749) - if(LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking()) - { - //TODO: remove or redirect this call status notification -// LLCallInfoDialog::show("unavailable", mNotifyArgs); - } - return; - default: - break; - } - LLVoiceChannel::handleStatusChange(status); -} - - -void LLVoiceChannelProximal::handleError(EStatusType status) -{ - std::string notify; - switch(status) - { - case ERROR_CHANNEL_LOCKED: - case ERROR_CHANNEL_FULL: - notify = "ProximalVoiceChannelFull"; - break; - default: - break; - } - - // notification - if (!notify.empty()) - { - LLNotificationsUtil::add(notify, mNotifyArgs); - } - - LLVoiceChannel::handleError(status); -} - -void LLVoiceChannelProximal::deactivate() -{ - if (callStarted()) - { - setState(STATE_HUNG_UP); - } -} - - -// -// LLVoiceChannelP2P -// -LLVoiceChannelP2P::LLVoiceChannelP2P(const LLUUID& session_id, const std::string& session_name, const LLUUID& other_user_id) : - LLVoiceChannelGroup(session_id, session_name), - mOtherUserID(other_user_id), - mReceivedCall(false) -{ - // make sure URI reflects encoded version of other user's agent id - setURI(LLVoiceClient::getInstance()->sipURIFromID(other_user_id)); -} - -void LLVoiceChannelP2P::handleStatusChange(EStatusType type) -{ - LL_INFOS("Voice") << "P2P CALL CHANNEL STATUS CHANGE: incoming=" << int(mReceivedCall) << " newstatus=" << LLVoiceClientStatusObserver::status2string(type) << " (mState=" << mState << ")" << LL_ENDL; - - // status updates - switch(type) - { - case STATUS_LEFT_CHANNEL: - if (callStarted() && !mIgnoreNextSessionLeave && !sSuspended) - { - // *TODO: use it to show DECLINE voice notification - if (mState == STATE_RINGING) - { - // other user declined call - LLNotificationsUtil::add("P2PCallDeclined", mNotifyArgs); - } - else - { - // other user hung up, so we didn't end the call - mCallEndedByAgent = false; - } - deactivate(); - } - mIgnoreNextSessionLeave = false; - return; - case STATUS_JOINING: - // because we join session we expect to process session leave event in the future. EXT-7371 - // may be this should be done in the LLVoiceChannel::handleStatusChange. - mIgnoreNextSessionLeave = false; - break; - - default: - break; - } - - LLVoiceChannel::handleStatusChange(type); -} - -void LLVoiceChannelP2P::handleError(EStatusType type) -{ - switch(type) - { - case ERROR_NOT_AVAILABLE: - LLNotificationsUtil::add("P2PCallNoAnswer", mNotifyArgs); - break; - default: - break; - } - - LLVoiceChannel::handleError(type); -} - -void LLVoiceChannelP2P::activate() -{ - if (callStarted()) return; - - //call will be counted as ended by user unless this variable is changed in handleStatusChange() - mCallEndedByAgent = true; - - LLVoiceChannel::activate(); - - if (callStarted()) - { - // no session handle yet, we're starting the call - if (mSessionHandle.empty()) - { - mReceivedCall = false; - LLVoiceClient::getInstance()->callUser(mOtherUserID); - } - // otherwise answering the call - else - { - if (!LLVoiceClient::getInstance()->answerInvite(mSessionHandle)) - { - mCallEndedByAgent = false; - mSessionHandle.clear(); - handleError(ERROR_UNKNOWN); - return; - } - // using the session handle invalidates it. Clear it out here so we can't reuse it by accident. - mSessionHandle.clear(); - } - - // Add the party to the list of people with which we've recently interacted. - addToTheRecentPeopleList(); - - //Default mic is ON on initiating/joining P2P calls - if (!LLVoiceClient::getInstance()->getUserPTTState() && LLVoiceClient::getInstance()->getPTTIsToggle()) - { - LLVoiceClient::getInstance()->inputUserControlState(true); - } - } -} - -void LLVoiceChannelP2P::getChannelInfo() -{ - // pretend we have everything we need, since P2P doesn't use channel info - if (sCurrentVoiceChannel == this) - { - setState(STATE_CALL_STARTED); - } -} - -// receiving session from other user who initiated call -void LLVoiceChannelP2P::setSessionHandle(const std::string& handle, const std::string &inURI) -{ - bool needs_activate = false; - if (callStarted()) - { - // defer to lower agent id when already active - if (mOtherUserID < gAgent.getID()) - { - // pretend we haven't started the call yet, so we can connect to this session instead - deactivate(); - needs_activate = true; - } - else - { - // we are active and have priority, invite the other user again - // under the assumption they will join this new session - mSessionHandle.clear(); - LLVoiceClient::getInstance()->callUser(mOtherUserID); - return; - } - } - - mSessionHandle = handle; - - // The URI of a p2p session should always be the other end's SIP URI. - if(!inURI.empty()) - { - setURI(inURI); - } - else - { - LL_WARNS("Voice") << "incoming SIP URL is not provided. Channel may not work properly." << LL_ENDL; - // See LLVoiceClient::sessionAddedEvent() - setURI(LLVoiceClient::getInstance()->sipURIFromID(mOtherUserID)); - } - - mReceivedCall = true; - - if (needs_activate) - { - activate(); - } -} - -void LLVoiceChannelP2P::setState(EState state) -{ - LL_INFOS("Voice") << "P2P CALL STATE CHANGE: incoming=" << int(mReceivedCall) << " oldstate=" << mState << " newstate=" << state << LL_ENDL; - - if (mReceivedCall) // incoming call - { - // you only "answer" voice invites in p2p mode - // so provide a special purpose message here - if (mReceivedCall && state == STATE_RINGING) - { - //TODO: remove or redirect this call status notification -// LLCallInfoDialog::show("answering", mNotifyArgs); - doSetState(state); - return; - } - } - - LLVoiceChannel::setState(state); -} - -void LLVoiceChannelP2P::addToTheRecentPeopleList() -{ - LLRecentPeople::instance().add(mOtherUserID); -} +/** + * @file llvoicechannel.cpp + * @brief Voice Channel related classes + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llagent.h" +#include "llfloaterreg.h" +#include "llimview.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "llpanel.h" +#include "llrecentpeople.h" +#include "llviewercontrol.h" +#include "llviewerregion.h" +#include "llvoicechannel.h" +#include "llcorehttputil.h" + +LLVoiceChannel::voice_channel_map_t LLVoiceChannel::sVoiceChannelMap; +LLVoiceChannel::voice_channel_map_uri_t LLVoiceChannel::sVoiceChannelURIMap; +LLVoiceChannel* LLVoiceChannel::sCurrentVoiceChannel = NULL; +LLVoiceChannel* LLVoiceChannel::sSuspendedVoiceChannel = NULL; +LLVoiceChannel::channel_changed_signal_t LLVoiceChannel::sCurrentVoiceChannelChangedSignal; + +bool LLVoiceChannel::sSuspended = false; + +// +// Constants +// +const U32 DEFAULT_RETRIES_COUNT = 3; + +// +// LLVoiceChannel +// +LLVoiceChannel::LLVoiceChannel(const LLUUID& session_id, const std::string& session_name) : + mSessionID(session_id), + mState(STATE_NO_CHANNEL_INFO), + mSessionName(session_name), + mCallDirection(OUTGOING_CALL), + mIgnoreNextSessionLeave(false), + mCallEndedByAgent(false) +{ + mNotifyArgs["VOICE_CHANNEL_NAME"] = mSessionName; + + if (!sVoiceChannelMap.insert(std::make_pair(session_id, this)).second) + { + // a voice channel already exists for this session id, so this instance will be orphaned + // the end result should simply be the failure to make voice calls + LL_WARNS("Voice") << "Duplicate voice channels registered for session_id " << session_id << LL_ENDL; + } +} + +LLVoiceChannel::~LLVoiceChannel() +{ + if (sSuspendedVoiceChannel == this) + { + sSuspendedVoiceChannel = NULL; + } + if (sCurrentVoiceChannel == this) + { + sCurrentVoiceChannel = NULL; + // Must check instance exists here, the singleton MAY have already been destroyed. + if(LLVoiceClient::instanceExists()) + { + LLVoiceClient::getInstance()->removeObserver(this); + } + } + + sVoiceChannelMap.erase(mSessionID); + sVoiceChannelURIMap.erase(mURI); +} + +void LLVoiceChannel::setChannelInfo( + const std::string& uri, + const std::string& credentials) +{ + setURI(uri); + + mCredentials = credentials; + + if (mState == STATE_NO_CHANNEL_INFO) + { + if (mURI.empty()) + { + LLNotificationsUtil::add("VoiceChannelJoinFailed", mNotifyArgs); + LL_WARNS("Voice") << "Received empty URI for channel " << mSessionName << LL_ENDL; + deactivate(); + } + else if (mCredentials.empty()) + { + LLNotificationsUtil::add("VoiceChannelJoinFailed", mNotifyArgs); + LL_WARNS("Voice") << "Received empty credentials for channel " << mSessionName << LL_ENDL; + deactivate(); + } + else + { + setState(STATE_READY); + + // if we are supposed to be active, reconnect + // this will happen on initial connect, as we request credentials on first use + if (sCurrentVoiceChannel == this) + { + // just in case we got new channel info while active + // should move over to new channel + activate(); + } + } + } +} + +void LLVoiceChannel::onChange(EStatusType type, const std::string &channelURI, bool proximal) +{ + if (channelURI != mURI) + { + return; + } + + if (type < BEGIN_ERROR_STATUS) + { + handleStatusChange(type); + } + else + { + handleError(type); + } +} + +void LLVoiceChannel::handleStatusChange(EStatusType type) +{ + // status updates + switch(type) + { + case STATUS_LOGIN_RETRY: + // no user notice + break; + case STATUS_LOGGED_IN: + break; + case STATUS_LEFT_CHANNEL: + if (callStarted() && !mIgnoreNextSessionLeave && !sSuspended) + { + // if forceably removed from channel + // update the UI and revert to default channel + deactivate(); + } + mIgnoreNextSessionLeave = false; + break; + case STATUS_JOINING: + if (callStarted()) + { + setState(STATE_RINGING); + } + break; + case STATUS_JOINED: + if (callStarted()) + { + setState(STATE_CONNECTED); + } + default: + break; + } +} + +// default behavior is to just deactivate channel +// derived classes provide specific error messages +void LLVoiceChannel::handleError(EStatusType type) +{ + deactivate(); + setState(STATE_ERROR); +} + +bool LLVoiceChannel::isActive() +{ + // only considered active when currently bound channel matches what our channel + return callStarted() && LLVoiceClient::getInstance()->getCurrentChannel() == mURI; +} + +bool LLVoiceChannel::callStarted() +{ + return mState >= STATE_CALL_STARTED; +} + +void LLVoiceChannel::deactivate() +{ + if (mState >= STATE_RINGING) + { + // ignore session leave event + mIgnoreNextSessionLeave = true; + } + + if (callStarted()) + { + setState(STATE_HUNG_UP); + + //Default mic is OFF when leaving voice calls + if (gSavedSettings.getBOOL("AutoDisengageMic") && + sCurrentVoiceChannel == this && + LLVoiceClient::getInstance()->getUserPTTState()) + { + gSavedSettings.setBOOL("PTTCurrentlyEnabled", true); + LLVoiceClient::getInstance()->setUserPTTState(false); + } + } + LLVoiceClient::getInstance()->removeObserver(this); + + if (sCurrentVoiceChannel == this) + { + // default channel is proximal channel + sCurrentVoiceChannel = LLVoiceChannelProximal::getInstance(); + sCurrentVoiceChannel->activate(); + } +} + +void LLVoiceChannel::activate() +{ + if (callStarted()) + { + return; + } + + // deactivate old channel and mark ourselves as the active one + if (sCurrentVoiceChannel != this) + { + // mark as current before deactivating the old channel to prevent + // activating the proximal channel between IM calls + LLVoiceChannel* old_channel = sCurrentVoiceChannel; + sCurrentVoiceChannel = this; + mCallDialogPayload["old_channel_name"] = ""; + if (old_channel) + { + mCallDialogPayload["old_channel_name"] = old_channel->getSessionName(); + old_channel->deactivate(); + } + } + + if (mState == STATE_NO_CHANNEL_INFO) + { + // responsible for setting status to active + getChannelInfo(); + } + else + { + setState(STATE_CALL_STARTED); + } + + LLVoiceClient::getInstance()->addObserver(this); + + //do not send earlier, channel should be initialized, should not be in STATE_NO_CHANNEL_INFO state + sCurrentVoiceChannelChangedSignal(this->mSessionID); +} + +void LLVoiceChannel::getChannelInfo() +{ + // pretend we have everything we need + if (sCurrentVoiceChannel == this) + { + setState(STATE_CALL_STARTED); + } +} + +//static +LLVoiceChannel* LLVoiceChannel::getChannelByID(const LLUUID& session_id) +{ + voice_channel_map_t::iterator found_it = sVoiceChannelMap.find(session_id); + if (found_it == sVoiceChannelMap.end()) + { + return NULL; + } + else + { + return found_it->second; + } +} + +//static +LLVoiceChannel* LLVoiceChannel::getChannelByURI(std::string uri) +{ + voice_channel_map_uri_t::iterator found_it = sVoiceChannelURIMap.find(uri); + if (found_it == sVoiceChannelURIMap.end()) + { + return NULL; + } + else + { + return found_it->second; + } +} + +LLVoiceChannel* LLVoiceChannel::getCurrentVoiceChannel() +{ + return sCurrentVoiceChannel; +} + +void LLVoiceChannel::updateSessionID(const LLUUID& new_session_id) +{ + sVoiceChannelMap.erase(sVoiceChannelMap.find(mSessionID)); + mSessionID = new_session_id; + sVoiceChannelMap.insert(std::make_pair(mSessionID, this)); +} + +void LLVoiceChannel::setURI(std::string uri) +{ + sVoiceChannelURIMap.erase(mURI); + mURI = uri; + sVoiceChannelURIMap.insert(std::make_pair(mURI, this)); +} + +void LLVoiceChannel::setState(EState state) +{ + switch(state) + { + case STATE_RINGING: + //TODO: remove or redirect this call status notification +// LLCallInfoDialog::show("ringing", mNotifyArgs); + break; + case STATE_CONNECTED: + //TODO: remove or redirect this call status notification +// LLCallInfoDialog::show("connected", mNotifyArgs); + break; + case STATE_HUNG_UP: + //TODO: remove or redirect this call status notification +// LLCallInfoDialog::show("hang_up", mNotifyArgs); + break; + default: + break; + } + + doSetState(state); +} + +void LLVoiceChannel::doSetState(const EState& new_state) +{ + EState old_state = mState; + mState = new_state; + + if (!mStateChangedCallback.empty()) + mStateChangedCallback(old_state, mState, mCallDirection, mCallEndedByAgent, mSessionID); +} + +//static +void LLVoiceChannel::initClass() +{ + sCurrentVoiceChannel = LLVoiceChannelProximal::getInstance(); +} + +//static +void LLVoiceChannel::suspend() +{ + if (!sSuspended) + { + sSuspendedVoiceChannel = sCurrentVoiceChannel; + sSuspended = true; + } +} + +//static +void LLVoiceChannel::resume() +{ + if (sSuspended) + { + if (LLVoiceClient::getInstance()->voiceEnabled()) + { + if (sSuspendedVoiceChannel) + { + sSuspendedVoiceChannel->activate(); + } + else + { + LLVoiceChannelProximal::getInstance()->activate(); + } + } + sSuspended = false; + } +} + +boost::signals2::connection LLVoiceChannel::setCurrentVoiceChannelChangedCallback(channel_changed_callback_t cb, bool at_front) +{ + if (at_front) + { + return sCurrentVoiceChannelChangedSignal.connect(cb, boost::signals2::at_front); + } + else + { + return sCurrentVoiceChannelChangedSignal.connect(cb); + } +} + +// +// LLVoiceChannelGroup +// + +LLVoiceChannelGroup::LLVoiceChannelGroup(const LLUUID& session_id, const std::string& session_name) : + LLVoiceChannel(session_id, session_name) +{ + mRetries = DEFAULT_RETRIES_COUNT; + mIsRetrying = false; +} + +void LLVoiceChannelGroup::deactivate() +{ + if (callStarted()) + { + LLVoiceClient::getInstance()->leaveNonSpatialChannel(); + } + LLVoiceChannel::deactivate(); +} + +void LLVoiceChannelGroup::activate() +{ + if (callStarted()) return; + + LLVoiceChannel::activate(); + + if (callStarted()) + { + // we have the channel info, just need to use it now + LLVoiceClient::getInstance()->setNonSpatialChannel( + mURI, + mCredentials); + + if (!gAgent.isInGroup(mSessionID)) // ad-hoc channel + { + LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(mSessionID); + // Adding ad-hoc call participants to Recent People List. + // If it's an outgoing ad-hoc, we can use mInitialTargetIDs that holds IDs of people we + // called(both online and offline) as source to get people for recent (STORM-210). + if (session->isOutgoingAdHoc()) + { + for (uuid_vec_t::iterator it = session->mInitialTargetIDs.begin(); + it!=session->mInitialTargetIDs.end();++it) + { + const LLUUID id = *it; + LLRecentPeople::instance().add(id); + } + } + // If this ad-hoc is incoming then trying to get ids of people from mInitialTargetIDs + // would lead to EXT-8246. So in this case we get them from speakers list. + else + { + LLIMModel::addSpeakersToRecent(mSessionID); + } + } + + //Mic default state is OFF on initiating/joining Ad-Hoc/Group calls + if (LLVoiceClient::getInstance()->getUserPTTState() && LLVoiceClient::getInstance()->getPTTIsToggle()) + { + LLVoiceClient::getInstance()->inputUserControlState(true); + } + + } +} + +void LLVoiceChannelGroup::getChannelInfo() +{ + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + std::string url = region->getCapability("ChatSessionRequest"); + + LLCoros::instance().launch("LLVoiceChannelGroup::voiceCallCapCoro", + boost::bind(&LLVoiceChannelGroup::voiceCallCapCoro, this, url)); + } +} + +void LLVoiceChannelGroup::setChannelInfo( + const std::string& uri, + const std::string& credentials) +{ + setURI(uri); + + mCredentials = credentials; + + if (mState == STATE_NO_CHANNEL_INFO) + { + if(!mURI.empty() && !mCredentials.empty()) + { + setState(STATE_READY); + + // if we are supposed to be active, reconnect + // this will happen on initial connect, as we request credentials on first use + if (sCurrentVoiceChannel == this) + { + // just in case we got new channel info while active + // should move over to new channel + activate(); + } + } + else + { + //*TODO: notify user + LL_WARNS("Voice") << "Received invalid credentials for channel " << mSessionName << LL_ENDL; + deactivate(); + } + } + else if ( mIsRetrying ) + { + // we have the channel info, just need to use it now + LLVoiceClient::getInstance()->setNonSpatialChannel( + mURI, + mCredentials); + } +} + +void LLVoiceChannelGroup::handleStatusChange(EStatusType type) +{ + // status updates + switch(type) + { + case STATUS_JOINED: + mRetries = 3; + mIsRetrying = false; + default: + break; + } + + LLVoiceChannel::handleStatusChange(type); +} + +void LLVoiceChannelGroup::handleError(EStatusType status) +{ + std::string notify; + switch(status) + { + case ERROR_CHANNEL_LOCKED: + case ERROR_CHANNEL_FULL: + notify = "VoiceChannelFull"; + break; + case ERROR_NOT_AVAILABLE: + //clear URI and credentials + //set the state to be no info + //and activate + if ( mRetries > 0 ) + { + mRetries--; + mIsRetrying = true; + mIgnoreNextSessionLeave = true; + + getChannelInfo(); + return; + } + else + { + notify = "VoiceChannelJoinFailed"; + mRetries = DEFAULT_RETRIES_COUNT; + mIsRetrying = false; + } + + break; + + case ERROR_UNKNOWN: + default: + break; + } + + // notification + if (!notify.empty()) + { + LLNotificationPtr notification = LLNotificationsUtil::add(notify, mNotifyArgs); + // echo to im window + gIMMgr->addMessage(mSessionID, LLUUID::null, SYSTEM_FROM, notification->getMessage()); + } + + LLVoiceChannel::handleError(status); +} + +void LLVoiceChannelGroup::setState(EState state) +{ + switch(state) + { + case STATE_RINGING: + if ( !mIsRetrying ) + { + //TODO: remove or redirect this call status notification +// LLCallInfoDialog::show("ringing", mNotifyArgs); + } + + doSetState(state); + break; + default: + LLVoiceChannel::setState(state); + } +} + +void LLVoiceChannelGroup::voiceCallCapCoro(std::string url) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("voiceCallCapCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD postData; + postData["method"] = "call"; + postData["session-id"] = mSessionID; + + LL_INFOS("Voice", "voiceCallCapCoro") << "Generic POST for " << url << LL_ENDL; + + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, postData); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(mSessionID); + if (!channelp) + { + LL_WARNS("Voice") << "Unable to retrieve channel with Id = " << mSessionID << LL_ENDL; + return; + } + + if (!status) + { + if (status == LLCore::HttpStatus(HTTP_FORBIDDEN)) + { + //403 == no ability + LLNotificationsUtil::add( + "VoiceNotAllowed", + channelp->getNotifyArgs()); + } + else + { + LLNotificationsUtil::add( + "VoiceCallGenericError", + channelp->getNotifyArgs()); + } + channelp->deactivate(); + return; + } + + result.erase(LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS); + + LLSD::map_const_iterator iter; + for (iter = result.beginMap(); iter != result.endMap(); ++iter) + { + LL_DEBUGS("Voice") << "LLVoiceCallCapResponder::result got " + << iter->first << LL_ENDL; + } + + channelp->setChannelInfo( + result["voice_credentials"]["channel_uri"].asString(), + result["voice_credentials"]["channel_credentials"].asString()); + +} + + +// +// LLVoiceChannelProximal +// +LLVoiceChannelProximal::LLVoiceChannelProximal() : + LLVoiceChannel(LLUUID::null, LLStringUtil::null) +{ +} + +bool LLVoiceChannelProximal::isActive() +{ + return callStarted() && LLVoiceClient::getInstance()->inProximalChannel(); +} + +void LLVoiceChannelProximal::activate() +{ + if (callStarted()) return; + + if((LLVoiceChannel::sCurrentVoiceChannel != this) && (LLVoiceChannel::getState() == STATE_CONNECTED)) + { + // we're connected to a non-spatial channel, so disconnect. + LLVoiceClient::getInstance()->leaveNonSpatialChannel(); + } + LLVoiceChannel::activate(); + +} + +void LLVoiceChannelProximal::onChange(EStatusType type, const std::string &channelURI, bool proximal) +{ + if (!proximal) + { + return; + } + + if (type < BEGIN_ERROR_STATUS) + { + handleStatusChange(type); + } + else + { + handleError(type); + } +} + +void LLVoiceChannelProximal::handleStatusChange(EStatusType status) +{ + // status updates + switch(status) + { + case STATUS_LEFT_CHANNEL: + // do not notify user when leaving proximal channel + return; + case STATUS_VOICE_DISABLED: + LLVoiceClient::getInstance()->setUserPTTState(false); + gAgent.setVoiceConnected(false); + //skip showing "Voice not available at your current location" when agent voice is disabled (EXT-4749) + if(LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking()) + { + //TODO: remove or redirect this call status notification +// LLCallInfoDialog::show("unavailable", mNotifyArgs); + } + return; + default: + break; + } + LLVoiceChannel::handleStatusChange(status); +} + + +void LLVoiceChannelProximal::handleError(EStatusType status) +{ + std::string notify; + switch(status) + { + case ERROR_CHANNEL_LOCKED: + case ERROR_CHANNEL_FULL: + notify = "ProximalVoiceChannelFull"; + break; + default: + break; + } + + // notification + if (!notify.empty()) + { + LLNotificationsUtil::add(notify, mNotifyArgs); + } + + LLVoiceChannel::handleError(status); +} + +void LLVoiceChannelProximal::deactivate() +{ + if (callStarted()) + { + setState(STATE_HUNG_UP); + } +} + + +// +// LLVoiceChannelP2P +// +LLVoiceChannelP2P::LLVoiceChannelP2P(const LLUUID& session_id, const std::string& session_name, const LLUUID& other_user_id) : + LLVoiceChannelGroup(session_id, session_name), + mOtherUserID(other_user_id), + mReceivedCall(false) +{ + // make sure URI reflects encoded version of other user's agent id + setURI(LLVoiceClient::getInstance()->sipURIFromID(other_user_id)); +} + +void LLVoiceChannelP2P::handleStatusChange(EStatusType type) +{ + LL_INFOS("Voice") << "P2P CALL CHANNEL STATUS CHANGE: incoming=" << int(mReceivedCall) << " newstatus=" << LLVoiceClientStatusObserver::status2string(type) << " (mState=" << mState << ")" << LL_ENDL; + + // status updates + switch(type) + { + case STATUS_LEFT_CHANNEL: + if (callStarted() && !mIgnoreNextSessionLeave && !sSuspended) + { + // *TODO: use it to show DECLINE voice notification + if (mState == STATE_RINGING) + { + // other user declined call + LLNotificationsUtil::add("P2PCallDeclined", mNotifyArgs); + } + else + { + // other user hung up, so we didn't end the call + mCallEndedByAgent = false; + } + deactivate(); + } + mIgnoreNextSessionLeave = false; + return; + case STATUS_JOINING: + // because we join session we expect to process session leave event in the future. EXT-7371 + // may be this should be done in the LLVoiceChannel::handleStatusChange. + mIgnoreNextSessionLeave = false; + break; + + default: + break; + } + + LLVoiceChannel::handleStatusChange(type); +} + +void LLVoiceChannelP2P::handleError(EStatusType type) +{ + switch(type) + { + case ERROR_NOT_AVAILABLE: + LLNotificationsUtil::add("P2PCallNoAnswer", mNotifyArgs); + break; + default: + break; + } + + LLVoiceChannel::handleError(type); +} + +void LLVoiceChannelP2P::activate() +{ + if (callStarted()) return; + + //call will be counted as ended by user unless this variable is changed in handleStatusChange() + mCallEndedByAgent = true; + + LLVoiceChannel::activate(); + + if (callStarted()) + { + // no session handle yet, we're starting the call + if (mSessionHandle.empty()) + { + mReceivedCall = false; + LLVoiceClient::getInstance()->callUser(mOtherUserID); + } + // otherwise answering the call + else + { + if (!LLVoiceClient::getInstance()->answerInvite(mSessionHandle)) + { + mCallEndedByAgent = false; + mSessionHandle.clear(); + handleError(ERROR_UNKNOWN); + return; + } + // using the session handle invalidates it. Clear it out here so we can't reuse it by accident. + mSessionHandle.clear(); + } + + // Add the party to the list of people with which we've recently interacted. + addToTheRecentPeopleList(); + + //Default mic is ON on initiating/joining P2P calls + if (!LLVoiceClient::getInstance()->getUserPTTState() && LLVoiceClient::getInstance()->getPTTIsToggle()) + { + LLVoiceClient::getInstance()->inputUserControlState(true); + } + } +} + +void LLVoiceChannelP2P::getChannelInfo() +{ + // pretend we have everything we need, since P2P doesn't use channel info + if (sCurrentVoiceChannel == this) + { + setState(STATE_CALL_STARTED); + } +} + +// receiving session from other user who initiated call +void LLVoiceChannelP2P::setSessionHandle(const std::string& handle, const std::string &inURI) +{ + bool needs_activate = false; + if (callStarted()) + { + // defer to lower agent id when already active + if (mOtherUserID < gAgent.getID()) + { + // pretend we haven't started the call yet, so we can connect to this session instead + deactivate(); + needs_activate = true; + } + else + { + // we are active and have priority, invite the other user again + // under the assumption they will join this new session + mSessionHandle.clear(); + LLVoiceClient::getInstance()->callUser(mOtherUserID); + return; + } + } + + mSessionHandle = handle; + + // The URI of a p2p session should always be the other end's SIP URI. + if(!inURI.empty()) + { + setURI(inURI); + } + else + { + LL_WARNS("Voice") << "incoming SIP URL is not provided. Channel may not work properly." << LL_ENDL; + // See LLVoiceClient::sessionAddedEvent() + setURI(LLVoiceClient::getInstance()->sipURIFromID(mOtherUserID)); + } + + mReceivedCall = true; + + if (needs_activate) + { + activate(); + } +} + +void LLVoiceChannelP2P::setState(EState state) +{ + LL_INFOS("Voice") << "P2P CALL STATE CHANGE: incoming=" << int(mReceivedCall) << " oldstate=" << mState << " newstate=" << state << LL_ENDL; + + if (mReceivedCall) // incoming call + { + // you only "answer" voice invites in p2p mode + // so provide a special purpose message here + if (mReceivedCall && state == STATE_RINGING) + { + //TODO: remove or redirect this call status notification +// LLCallInfoDialog::show("answering", mNotifyArgs); + doSetState(state); + return; + } + } + + LLVoiceChannel::setState(state); +} + +void LLVoiceChannelP2P::addToTheRecentPeopleList() +{ + LLRecentPeople::instance().add(mOtherUserID); +} diff --git a/indra/newview/llvoicechannel.h b/indra/newview/llvoicechannel.h index 81818c9599..db176f4e50 100644 --- a/indra/newview/llvoicechannel.h +++ b/indra/newview/llvoicechannel.h @@ -1,210 +1,210 @@ -/** - * @file llvoicechannel.h - * @brief Voice channel related classes - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_VOICECHANNEL_H -#define LL_VOICECHANNEL_H - -#include "llhandle.h" -#include "llvoiceclient.h" -#include "lleventcoro.h" -#include "llcoros.h" - -class LLPanel; - -class LLVoiceChannel : public LLVoiceClientStatusObserver -{ -public: - typedef enum e_voice_channel_state - { - STATE_NO_CHANNEL_INFO, - STATE_ERROR, - STATE_HUNG_UP, - STATE_READY, - STATE_CALL_STARTED, - STATE_RINGING, - STATE_CONNECTED - } EState; - - typedef enum e_voice_channel_direction - { - INCOMING_CALL, - OUTGOING_CALL - } EDirection; - - typedef boost::signals2::signal state_changed_signal_t; - - // on current channel changed signal - typedef boost::function channel_changed_callback_t; - typedef boost::signals2::signal channel_changed_signal_t; - static channel_changed_signal_t sCurrentVoiceChannelChangedSignal; - static boost::signals2::connection setCurrentVoiceChannelChangedCallback(channel_changed_callback_t cb, bool at_front = false); - - - LLVoiceChannel(const LLUUID& session_id, const std::string& session_name); - virtual ~LLVoiceChannel(); - - /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal); - - virtual void handleStatusChange(EStatusType status); - virtual void handleError(EStatusType status); - virtual void deactivate(); - virtual void activate(); - virtual void setChannelInfo( - const std::string& uri, - const std::string& credentials); - virtual void getChannelInfo(); - virtual bool isActive(); - virtual bool callStarted(); - - // Session name is a UI label used for feedback about which person, - // group, or phone number you are talking to - const std::string& getSessionName() const { return mSessionName; } - - boost::signals2::connection setStateChangedCallback(const state_changed_signal_t::slot_type& callback) - { return mStateChangedCallback.connect(callback); } - - const LLUUID getSessionID() { return mSessionID; } - EState getState() { return mState; } - - void updateSessionID(const LLUUID& new_session_id); - const LLSD& getNotifyArgs() { return mNotifyArgs; } - - void setCallDirection(EDirection direction) {mCallDirection = direction;} - EDirection getCallDirection() {return mCallDirection;} - - static LLVoiceChannel* getChannelByID(const LLUUID& session_id); - static LLVoiceChannel* getChannelByURI(std::string uri); - static LLVoiceChannel* getCurrentVoiceChannel(); - - static void initClass(); - - static void suspend(); - static void resume(); - -protected: - virtual void setState(EState state); - /** - * Use this method if you want mStateChangedCallback to be executed while state is changed - */ - void doSetState(const EState& state); - void setURI(std::string uri); - - // there can be two directions INCOMING and OUTGOING - EDirection mCallDirection; - - std::string mURI; - std::string mCredentials; - LLUUID mSessionID; - EState mState; - std::string mSessionName; - LLSD mNotifyArgs; - LLSD mCallDialogPayload; - // true if call was ended by agent - bool mCallEndedByAgent; - bool mIgnoreNextSessionLeave; - LLHandle mLoginNotificationHandle; - - typedef std::map voice_channel_map_t; - static voice_channel_map_t sVoiceChannelMap; - - typedef std::map voice_channel_map_uri_t; - static voice_channel_map_uri_t sVoiceChannelURIMap; - - static LLVoiceChannel* sCurrentVoiceChannel; - static LLVoiceChannel* sSuspendedVoiceChannel; - static bool sSuspended; - -private: - state_changed_signal_t mStateChangedCallback; -}; - -class LLVoiceChannelGroup : public LLVoiceChannel -{ -public: - LLVoiceChannelGroup(const LLUUID& session_id, const std::string& session_name); - - /*virtual*/ void handleStatusChange(EStatusType status); - /*virtual*/ void handleError(EStatusType status); - /*virtual*/ void activate(); - /*virtual*/ void deactivate(); - /*vritual*/ void setChannelInfo( - const std::string& uri, - const std::string& credentials); - /*virtual*/ void getChannelInfo(); - -protected: - virtual void setState(EState state); - -private: - void voiceCallCapCoro(std::string url); - - U32 mRetries; - bool mIsRetrying; -}; - -class LLVoiceChannelProximal : public LLVoiceChannel, public LLSingleton -{ - LLSINGLETON(LLVoiceChannelProximal); -public: - - /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal) override; - /*virtual*/ void handleStatusChange(EStatusType status) override; - /*virtual*/ void handleError(EStatusType status) override; - /*virtual*/ bool isActive() override; - /*virtual*/ void activate() override; - /*virtual*/ void deactivate() override; - -}; - -class LLVoiceChannelP2P : public LLVoiceChannelGroup -{ -public: - LLVoiceChannelP2P(const LLUUID& session_id, const std::string& session_name, const LLUUID& other_user_id); - - /*virtual*/ void handleStatusChange(EStatusType status) override; - /*virtual*/ void handleError(EStatusType status) override; - /*virtual*/ void activate() override; - /*virtual*/ void getChannelInfo() override; - - void setSessionHandle(const std::string& handle, const std::string &inURI); - -protected: - virtual void setState(EState state) override; - -private: - - /** - * Add the caller to the list of people with which we've recently interacted - * - **/ - void addToTheRecentPeopleList(); - - std::string mSessionHandle; - LLUUID mOtherUserID; - bool mReceivedCall; -}; - -#endif // LL_VOICECHANNEL_H +/** + * @file llvoicechannel.h + * @brief Voice channel related classes + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_VOICECHANNEL_H +#define LL_VOICECHANNEL_H + +#include "llhandle.h" +#include "llvoiceclient.h" +#include "lleventcoro.h" +#include "llcoros.h" + +class LLPanel; + +class LLVoiceChannel : public LLVoiceClientStatusObserver +{ +public: + typedef enum e_voice_channel_state + { + STATE_NO_CHANNEL_INFO, + STATE_ERROR, + STATE_HUNG_UP, + STATE_READY, + STATE_CALL_STARTED, + STATE_RINGING, + STATE_CONNECTED + } EState; + + typedef enum e_voice_channel_direction + { + INCOMING_CALL, + OUTGOING_CALL + } EDirection; + + typedef boost::signals2::signal state_changed_signal_t; + + // on current channel changed signal + typedef boost::function channel_changed_callback_t; + typedef boost::signals2::signal channel_changed_signal_t; + static channel_changed_signal_t sCurrentVoiceChannelChangedSignal; + static boost::signals2::connection setCurrentVoiceChannelChangedCallback(channel_changed_callback_t cb, bool at_front = false); + + + LLVoiceChannel(const LLUUID& session_id, const std::string& session_name); + virtual ~LLVoiceChannel(); + + /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal); + + virtual void handleStatusChange(EStatusType status); + virtual void handleError(EStatusType status); + virtual void deactivate(); + virtual void activate(); + virtual void setChannelInfo( + const std::string& uri, + const std::string& credentials); + virtual void getChannelInfo(); + virtual bool isActive(); + virtual bool callStarted(); + + // Session name is a UI label used for feedback about which person, + // group, or phone number you are talking to + const std::string& getSessionName() const { return mSessionName; } + + boost::signals2::connection setStateChangedCallback(const state_changed_signal_t::slot_type& callback) + { return mStateChangedCallback.connect(callback); } + + const LLUUID getSessionID() { return mSessionID; } + EState getState() { return mState; } + + void updateSessionID(const LLUUID& new_session_id); + const LLSD& getNotifyArgs() { return mNotifyArgs; } + + void setCallDirection(EDirection direction) {mCallDirection = direction;} + EDirection getCallDirection() {return mCallDirection;} + + static LLVoiceChannel* getChannelByID(const LLUUID& session_id); + static LLVoiceChannel* getChannelByURI(std::string uri); + static LLVoiceChannel* getCurrentVoiceChannel(); + + static void initClass(); + + static void suspend(); + static void resume(); + +protected: + virtual void setState(EState state); + /** + * Use this method if you want mStateChangedCallback to be executed while state is changed + */ + void doSetState(const EState& state); + void setURI(std::string uri); + + // there can be two directions INCOMING and OUTGOING + EDirection mCallDirection; + + std::string mURI; + std::string mCredentials; + LLUUID mSessionID; + EState mState; + std::string mSessionName; + LLSD mNotifyArgs; + LLSD mCallDialogPayload; + // true if call was ended by agent + bool mCallEndedByAgent; + bool mIgnoreNextSessionLeave; + LLHandle mLoginNotificationHandle; + + typedef std::map voice_channel_map_t; + static voice_channel_map_t sVoiceChannelMap; + + typedef std::map voice_channel_map_uri_t; + static voice_channel_map_uri_t sVoiceChannelURIMap; + + static LLVoiceChannel* sCurrentVoiceChannel; + static LLVoiceChannel* sSuspendedVoiceChannel; + static bool sSuspended; + +private: + state_changed_signal_t mStateChangedCallback; +}; + +class LLVoiceChannelGroup : public LLVoiceChannel +{ +public: + LLVoiceChannelGroup(const LLUUID& session_id, const std::string& session_name); + + /*virtual*/ void handleStatusChange(EStatusType status); + /*virtual*/ void handleError(EStatusType status); + /*virtual*/ void activate(); + /*virtual*/ void deactivate(); + /*vritual*/ void setChannelInfo( + const std::string& uri, + const std::string& credentials); + /*virtual*/ void getChannelInfo(); + +protected: + virtual void setState(EState state); + +private: + void voiceCallCapCoro(std::string url); + + U32 mRetries; + bool mIsRetrying; +}; + +class LLVoiceChannelProximal : public LLVoiceChannel, public LLSingleton +{ + LLSINGLETON(LLVoiceChannelProximal); +public: + + /*virtual*/ void onChange(EStatusType status, const std::string &channelURI, bool proximal) override; + /*virtual*/ void handleStatusChange(EStatusType status) override; + /*virtual*/ void handleError(EStatusType status) override; + /*virtual*/ bool isActive() override; + /*virtual*/ void activate() override; + /*virtual*/ void deactivate() override; + +}; + +class LLVoiceChannelP2P : public LLVoiceChannelGroup +{ +public: + LLVoiceChannelP2P(const LLUUID& session_id, const std::string& session_name, const LLUUID& other_user_id); + + /*virtual*/ void handleStatusChange(EStatusType status) override; + /*virtual*/ void handleError(EStatusType status) override; + /*virtual*/ void activate() override; + /*virtual*/ void getChannelInfo() override; + + void setSessionHandle(const std::string& handle, const std::string &inURI); + +protected: + virtual void setState(EState state) override; + +private: + + /** + * Add the caller to the list of people with which we've recently interacted + * + **/ + void addToTheRecentPeopleList(); + + std::string mSessionHandle; + LLUUID mOtherUserID; + bool mReceivedCall; +}; + +#endif // LL_VOICECHANNEL_H diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp index ad5d9cdfcd..48a34efdeb 100644 --- a/indra/newview/llvoiceclient.cpp +++ b/indra/newview/llvoiceclient.cpp @@ -1,1098 +1,1098 @@ - /** - * @file llvoiceclient.cpp - * @brief Voice client delegation class implementation. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "llvoiceclient.h" -#include "llviewercontrol.h" -#include "llviewerwindow.h" -#include "llvoicevivox.h" -#include "llviewernetwork.h" -#include "llcommandhandler.h" -#include "llhttpnode.h" -#include "llnotificationsutil.h" -#include "llsdserialize.h" -#include "llui.h" -#include "llkeyboard.h" -#include "llagent.h" -#include "lluiusage.h" - -const F32 LLVoiceClient::OVERDRIVEN_POWER_LEVEL = 0.7f; - -const F32 LLVoiceClient::VOLUME_MIN = 0.f; -const F32 LLVoiceClient::VOLUME_DEFAULT = 0.5f; -const F32 LLVoiceClient::VOLUME_MAX = 1.0f; - - -// Support for secondlife:///app/voice SLapps -class LLVoiceHandler : public LLCommandHandler -{ -public: - // requests will be throttled from a non-trusted browser - LLVoiceHandler() : LLCommandHandler("voice", UNTRUSTED_THROTTLE) {} - - bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) - { - if (params[0].asString() == "effects") - { - LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); - // If the voice client doesn't support voice effects, we can't handle effects SLapps - if (!effect_interface) - { - return false; - } - - // Support secondlife:///app/voice/effects/refresh to update the voice effect list with new effects - if (params[1].asString() == "refresh") - { - effect_interface->refreshVoiceEffectLists(false); - return true; - } - } - return false; - } -}; -LLVoiceHandler gVoiceHandler; - - - -std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserver::EStatusType inStatus) -{ - std::string result = "UNTRANSLATED"; - - // Prevent copy-paste errors when updating this list... -#define CASE(x) case x: result = #x; break - - switch(inStatus) - { - CASE(STATUS_LOGIN_RETRY); - CASE(STATUS_LOGGED_IN); - CASE(STATUS_JOINING); - CASE(STATUS_JOINED); - CASE(STATUS_LEFT_CHANNEL); - CASE(STATUS_VOICE_DISABLED); - CASE(STATUS_VOICE_ENABLED); - CASE(BEGIN_ERROR_STATUS); - CASE(ERROR_CHANNEL_FULL); - CASE(ERROR_CHANNEL_LOCKED); - CASE(ERROR_NOT_AVAILABLE); - CASE(ERROR_UNKNOWN); - default: - { - std::ostringstream stream; - stream << "UNKNOWN(" << (int)inStatus << ")"; - result = stream.str(); - } - break; - } - -#undef CASE - - return result; -} - - - -/////////////////////////////////////////////////////////////////////////////////////////////// - -LLVoiceClient::LLVoiceClient(LLPumpIO *pump) - : - mVoiceModule(NULL), - m_servicePump(NULL), - mVoiceEffectEnabled(LLCachedControl(gSavedSettings, "VoiceMorphingEnabled", true)), - mVoiceEffectDefault(LLCachedControl(gSavedPerAccountSettings, "VoiceEffectDefault", "00000000-0000-0000-0000-000000000000")), - mPTTDirty(true), - mPTT(true), - mUsePTT(true), - mPTTMouseButton(0), - mPTTKey(0), - mPTTIsToggle(false), - mUserPTTState(false), - mMuteMic(false), - mDisableMic(false) -{ - updateSettings(); - init(pump); -} - -//--------------------------------------------------- -// Basic setup/shutdown - -LLVoiceClient::~LLVoiceClient() -{ - llassert(!mVoiceModule); -} - -void LLVoiceClient::init(LLPumpIO *pump) -{ - // Initialize all of the voice modules - m_servicePump = pump; -} - -void LLVoiceClient::userAuthorized(const std::string& user_id, const LLUUID &agentID) -{ - // In the future, we should change this to allow voice module registration - // with a table lookup of sorts. - std::string voice_server = gSavedSettings.getString("VoiceServerType"); - LL_DEBUGS("Voice") << "voice server type " << voice_server << LL_ENDL; - if(voice_server == "vivox") - { - mVoiceModule = (LLVoiceModuleInterface *)LLVivoxVoiceClient::getInstance(); - } - else - { - mVoiceModule = NULL; - return; - } - mVoiceModule->init(m_servicePump); - mVoiceModule->userAuthorized(user_id, agentID); -} - -void LLVoiceClient::setHidden(bool hidden) -{ - if (mVoiceModule) - { - mVoiceModule->setHidden(hidden); - } -} - -void LLVoiceClient::terminate() -{ - if (mVoiceModule) mVoiceModule->terminate(); - mVoiceModule = NULL; - m_servicePump = NULL; - - // Shutdown speaker volume storage before LLSingletonBase::deleteAll() does it - if (LLSpeakerVolumeStorage::instanceExists()) - { - LLSpeakerVolumeStorage::deleteSingleton(); - } -} - -const LLVoiceVersionInfo LLVoiceClient::getVersion() -{ - if (mVoiceModule) - { - return mVoiceModule->getVersion(); - } - else - { - LLVoiceVersionInfo result; - result.serverVersion = std::string(); - result.serverType = std::string(); - result.mBuildVersion = std::string(); - return result; - } -} - -void LLVoiceClient::updateSettings() -{ - setUsePTT(gSavedSettings.getBOOL("PTTCurrentlyEnabled")); - setPTTIsToggle(gSavedSettings.getBOOL("PushToTalkToggle")); - mDisableMic = gSavedSettings.getBOOL("VoiceDisableMic"); - - updateMicMuteLogic(); - - if (mVoiceModule) - { - mVoiceModule->updateSettings(); - } -} - -//-------------------------------------------------- -// tuning - -void LLVoiceClient::tuningStart() -{ - if (mVoiceModule) mVoiceModule->tuningStart(); -} - -void LLVoiceClient::tuningStop() -{ - if (mVoiceModule) mVoiceModule->tuningStop(); -} - -bool LLVoiceClient::inTuningMode() -{ - if (mVoiceModule) - { - return mVoiceModule->inTuningMode(); - } - else - { - return false; - } -} - -void LLVoiceClient::tuningSetMicVolume(float volume) -{ - if (mVoiceModule) mVoiceModule->tuningSetMicVolume(volume); -} - -void LLVoiceClient::tuningSetSpeakerVolume(float volume) -{ - if (mVoiceModule) mVoiceModule->tuningSetSpeakerVolume(volume); -} - -float LLVoiceClient::tuningGetEnergy(void) -{ - if (mVoiceModule) - { - return mVoiceModule->tuningGetEnergy(); - } - else - { - return 0.0; - } -} - - -//------------------------------------------------ -// devices - -bool LLVoiceClient::deviceSettingsAvailable() -{ - if (mVoiceModule) - { - return mVoiceModule->deviceSettingsAvailable(); - } - else - { - return false; - } -} - -bool LLVoiceClient::deviceSettingsUpdated() -{ - if (mVoiceModule) - { - return mVoiceModule->deviceSettingsUpdated(); - } - else - { - return false; - } -} - -void LLVoiceClient::refreshDeviceLists(bool clearCurrentList) -{ - if (mVoiceModule) mVoiceModule->refreshDeviceLists(clearCurrentList); -} - -void LLVoiceClient::setCaptureDevice(const std::string& name) -{ - if (mVoiceModule) mVoiceModule->setCaptureDevice(name); - -} - -void LLVoiceClient::setRenderDevice(const std::string& name) -{ - if (mVoiceModule) mVoiceModule->setRenderDevice(name); -} - -const LLVoiceDeviceList& LLVoiceClient::getCaptureDevices() -{ - static LLVoiceDeviceList nullCaptureDevices; - if (mVoiceModule) - { - return mVoiceModule->getCaptureDevices(); - } - else - { - return nullCaptureDevices; - } -} - - -const LLVoiceDeviceList& LLVoiceClient::getRenderDevices() -{ - static LLVoiceDeviceList nullRenderDevices; - if (mVoiceModule) - { - return mVoiceModule->getRenderDevices(); - } - else - { - return nullRenderDevices; - } -} - - -//-------------------------------------------------- -// participants - -void LLVoiceClient::getParticipantList(std::set &participants) -{ - if (mVoiceModule) - { - mVoiceModule->getParticipantList(participants); - } - else - { - participants = std::set(); - } -} - -bool LLVoiceClient::isParticipant(const LLUUID &speaker_id) -{ - if(mVoiceModule) - { - return mVoiceModule->isParticipant(speaker_id); - } - return false; -} - - -//-------------------------------------------------- -// text chat - - -bool LLVoiceClient::isSessionTextIMPossible(const LLUUID& id) -{ - if (mVoiceModule) - { - return mVoiceModule->isSessionTextIMPossible(id); - } - else - { - return false; - } -} - -bool LLVoiceClient::isSessionCallBackPossible(const LLUUID& id) -{ - if (mVoiceModule) - { - return mVoiceModule->isSessionCallBackPossible(id); - } - else - { - return false; - } -} - -/* obsolete -bool LLVoiceClient::sendTextMessage(const LLUUID& participant_id, const std::string& message) -{ - if (mVoiceModule) - { - return mVoiceModule->sendTextMessage(participant_id, message); - } - else - { - return false; - } -} -*/ - -void LLVoiceClient::endUserIMSession(const LLUUID& participant_id) -{ - if (mVoiceModule) - { - // mVoiceModule->endUserIMSession(participant_id); // A SLim leftover - } -} - -//---------------------------------------------- -// channels - -bool LLVoiceClient::inProximalChannel() -{ - if (mVoiceModule) - { - return mVoiceModule->inProximalChannel(); - } - else - { - return false; - } -} - -void LLVoiceClient::setNonSpatialChannel( - const std::string &uri, - const std::string &credentials) -{ - if (mVoiceModule) - { - mVoiceModule->setNonSpatialChannel(uri, credentials); - } -} - -void LLVoiceClient::setSpatialChannel( - const std::string &uri, - const std::string &credentials) -{ - if (mVoiceModule) - { - mVoiceModule->setSpatialChannel(uri, credentials); - } -} - -void LLVoiceClient::leaveNonSpatialChannel() -{ - if (mVoiceModule) - { - mVoiceModule->leaveNonSpatialChannel(); - } -} - -void LLVoiceClient::leaveChannel(void) -{ - if (mVoiceModule) - { - mVoiceModule->leaveChannel(); - } -} - -std::string LLVoiceClient::getCurrentChannel() -{ - if (mVoiceModule) - { - return mVoiceModule->getCurrentChannel(); - } - else - { - return std::string(); - } -} - - -//--------------------------------------- -// invitations - -void LLVoiceClient::callUser(const LLUUID &uuid) -{ - if (mVoiceModule) mVoiceModule->callUser(uuid); -} - -bool LLVoiceClient::isValidChannel(std::string &session_handle) -{ - if (mVoiceModule) - { - return mVoiceModule->isValidChannel(session_handle); - } - else - { - return false; - } -} - -bool LLVoiceClient::answerInvite(std::string &channelHandle) -{ - if (mVoiceModule) - { - return mVoiceModule->answerInvite(channelHandle); - } - else - { - return false; - } -} - -void LLVoiceClient::declineInvite(std::string &channelHandle) -{ - if (mVoiceModule) mVoiceModule->declineInvite(channelHandle); -} - - -//------------------------------------------ -// Volume/gain - - -void LLVoiceClient::setVoiceVolume(F32 volume) -{ - if (mVoiceModule) mVoiceModule->setVoiceVolume(volume); -} - -void LLVoiceClient::setMicGain(F32 volume) -{ - if (mVoiceModule) mVoiceModule->setMicGain(volume); -} - - -//------------------------------------------ -// enable/disable voice features - -bool LLVoiceClient::voiceEnabled() -{ - if (mVoiceModule) - { - return mVoiceModule->voiceEnabled(); - } - else - { - return false; - } -} - -void LLVoiceClient::setVoiceEnabled(bool enabled) -{ - if (mVoiceModule) - { - mVoiceModule->setVoiceEnabled(enabled); - } -} - -void LLVoiceClient::updateMicMuteLogic() -{ - // If not configured to use PTT, the mic should be open (otherwise the user will be unable to speak). - bool new_mic_mute = false; - - if(mUsePTT) - { - // If configured to use PTT, track the user state. - new_mic_mute = !mUserPTTState; - } - - if(mMuteMic || mDisableMic) - { - // Either of these always overrides any other PTT setting. - new_mic_mute = true; - } - - if (mVoiceModule) mVoiceModule->setMuteMic(new_mic_mute); -} - -void LLVoiceClient::setLipSyncEnabled(bool enabled) -{ - if (mVoiceModule) mVoiceModule->setLipSyncEnabled(enabled); -} - -bool LLVoiceClient::lipSyncEnabled() -{ - if (mVoiceModule) - { - return mVoiceModule->lipSyncEnabled(); - } - else - { - return false; - } -} - -void LLVoiceClient::setMuteMic(bool muted) -{ - mMuteMic = muted; - updateMicMuteLogic(); - mMicroChangedSignal(); -} - - -// ---------------------------------------------- -// PTT - -void LLVoiceClient::setUserPTTState(bool ptt) -{ - if (ptt) - { - LLUIUsage::instance().logCommand("Agent.EnableMicrophone"); - } - mUserPTTState = ptt; - updateMicMuteLogic(); - mMicroChangedSignal(); -} - -bool LLVoiceClient::getUserPTTState() -{ - return mUserPTTState; -} - -void LLVoiceClient::setUsePTT(bool usePTT) -{ - if(usePTT && !mUsePTT) - { - // When the user turns on PTT, reset the current state. - mUserPTTState = false; - } - mUsePTT = usePTT; - - updateMicMuteLogic(); -} - -void LLVoiceClient::setPTTIsToggle(bool PTTIsToggle) -{ - if(!PTTIsToggle && mPTTIsToggle) - { - // When the user turns off toggle, reset the current state. - mUserPTTState = false; - } - - mPTTIsToggle = PTTIsToggle; - - updateMicMuteLogic(); -} - -bool LLVoiceClient::getPTTIsToggle() -{ - return mPTTIsToggle; -} - -void LLVoiceClient::inputUserControlState(bool down) -{ - if(mPTTIsToggle) - { - if(down) // toggle open-mic state on 'down' - { - toggleUserPTTState(); - } - } - else // set open-mic state as an absolute - { - setUserPTTState(down); - } -} - -void LLVoiceClient::toggleUserPTTState(void) -{ - setUserPTTState(!getUserPTTState()); -} - - -//------------------------------------------- -// nearby speaker accessors - -bool LLVoiceClient::getVoiceEnabled(const LLUUID& id) -{ - if (mVoiceModule) - { - return mVoiceModule->getVoiceEnabled(id); - } - else - { - return false; - } -} - -std::string LLVoiceClient::getDisplayName(const LLUUID& id) -{ - if (mVoiceModule) - { - return mVoiceModule->getDisplayName(id); - } - else - { - return std::string(); - } -} - -bool LLVoiceClient::isVoiceWorking() const -{ - if (mVoiceModule) - { - return mVoiceModule->isVoiceWorking(); - } - return false; -} - -bool LLVoiceClient::isParticipantAvatar(const LLUUID& id) -{ - if (mVoiceModule) - { - return mVoiceModule->isParticipantAvatar(id); - } - else - { - return false; - } -} - -bool LLVoiceClient::isOnlineSIP(const LLUUID& id) -{ - return false; -} - -bool LLVoiceClient::getIsSpeaking(const LLUUID& id) -{ - if (mVoiceModule) - { - return mVoiceModule->getIsSpeaking(id); - } - else - { - return false; - } -} - -bool LLVoiceClient::getIsModeratorMuted(const LLUUID& id) -{ - if (mVoiceModule) - { - return mVoiceModule->getIsModeratorMuted(id); - } - else - { - return false; - } -} - -F32 LLVoiceClient::getCurrentPower(const LLUUID& id) -{ - if (mVoiceModule) - { - return mVoiceModule->getCurrentPower(id); - } - else - { - return 0.0; - } -} - -bool LLVoiceClient::getOnMuteList(const LLUUID& id) -{ - if (mVoiceModule) - { - return mVoiceModule->getOnMuteList(id); - } - else - { - return false; - } -} - -F32 LLVoiceClient::getUserVolume(const LLUUID& id) -{ - if (mVoiceModule) - { - return mVoiceModule->getUserVolume(id); - } - else - { - return 0.0; - } -} - -void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume) -{ - if (mVoiceModule) mVoiceModule->setUserVolume(id, volume); -} - -//-------------------------------------------------- -// status observers - -void LLVoiceClient::addObserver(LLVoiceClientStatusObserver* observer) -{ - if (mVoiceModule) mVoiceModule->addObserver(observer); -} - -void LLVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer) -{ - if (mVoiceModule) - { - mVoiceModule->removeObserver(observer); - } -} - -void LLVoiceClient::addObserver(LLFriendObserver* observer) -{ - if (mVoiceModule) mVoiceModule->addObserver(observer); -} - -void LLVoiceClient::removeObserver(LLFriendObserver* observer) -{ - if (mVoiceModule) - { - mVoiceModule->removeObserver(observer); - } -} - -void LLVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer) -{ - if (mVoiceModule) mVoiceModule->addObserver(observer); -} - -void LLVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer) -{ - if (mVoiceModule) - { - mVoiceModule->removeObserver(observer); - } -} - -std::string LLVoiceClient::sipURIFromID(const LLUUID &id) -{ - if (mVoiceModule) - { - return mVoiceModule->sipURIFromID(id); - } - else - { - return std::string(); - } -} - -LLVoiceEffectInterface* LLVoiceClient::getVoiceEffectInterface() const -{ - return getVoiceEffectEnabled() ? dynamic_cast(mVoiceModule) : NULL; -} - -/////////////////// -// version checking - -class LLViewerRequiredVoiceVersion : public LLHTTPNode -{ - static bool sAlertedUser; - virtual void post( - LLHTTPNode::ResponsePtr response, - const LLSD& context, - const LLSD& input) const - { - //You received this messsage (most likely on region cross or - //teleport) - if ( input.has("body") && input["body"].has("major_version") ) - { - int major_voice_version = - input["body"]["major_version"].asInteger(); - // int minor_voice_version = - // input["body"]["minor_version"].asInteger(); - LLVoiceVersionInfo versionInfo = LLVoiceClient::getInstance()->getVersion(); - - if (major_voice_version > 1) - { - if (!sAlertedUser) - { - //sAlertedUser = true; - LLNotificationsUtil::add("VoiceVersionMismatch"); - gSavedSettings.setBOOL("EnableVoiceChat", false); // toggles listener - } - } - } - } -}; - -class LLViewerParcelVoiceInfo : public LLHTTPNode -{ - virtual void post( - LLHTTPNode::ResponsePtr response, - const LLSD& context, - const LLSD& input) const - { - //the parcel you are in has changed something about its - //voice information - - //this is a misnomer, as it can also be when you are not in - //a parcel at all. Should really be something like - //LLViewerVoiceInfoChanged..... - if ( input.has("body") ) - { - LLSD body = input["body"]; - - //body has "region_name" (str), "parcel_local_id"(int), - //"voice_credentials" (map). - - //body["voice_credentials"] has "channel_uri" (str), - //body["voice_credentials"] has "channel_credentials" (str) - - //if we really wanted to be extra careful, - //we'd check the supplied - //local parcel id to make sure it's for the same parcel - //we believe we're in - if ( body.has("voice_credentials") ) - { - LLSD voice_credentials = body["voice_credentials"]; - std::string uri; - std::string credentials; - - if ( voice_credentials.has("channel_uri") ) - { - uri = voice_credentials["channel_uri"].asString(); - } - if ( voice_credentials.has("channel_credentials") ) - { - credentials = - voice_credentials["channel_credentials"].asString(); - } - - LLVoiceClient::getInstance()->setSpatialChannel(uri, credentials); - } - } - } -}; - -const std::string LLSpeakerVolumeStorage::SETTINGS_FILE_NAME = "volume_settings.xml"; - -LLSpeakerVolumeStorage::LLSpeakerVolumeStorage() -{ - load(); -} - -LLSpeakerVolumeStorage::~LLSpeakerVolumeStorage() -{ -} - -//virtual -void LLSpeakerVolumeStorage::cleanupSingleton() -{ - save(); -} - -void LLSpeakerVolumeStorage::storeSpeakerVolume(const LLUUID& speaker_id, F32 volume) -{ - if ((volume >= LLVoiceClient::VOLUME_MIN) && (volume <= LLVoiceClient::VOLUME_MAX)) - { - mSpeakersData[speaker_id] = volume; - - // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging. - // LL_DEBUGS("Voice") << "Stored volume = " << volume << " for " << id << LL_ENDL; - } - else - { - LL_WARNS("Voice") << "Attempted to store out of range volume " << volume << " for " << speaker_id << LL_ENDL; - llassert(0); - } -} - -bool LLSpeakerVolumeStorage::getSpeakerVolume(const LLUUID& speaker_id, F32& volume) -{ - speaker_data_map_t::const_iterator it = mSpeakersData.find(speaker_id); - - if (it != mSpeakersData.end()) - { - volume = it->second; - - // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging. - // LL_DEBUGS("Voice") << "Retrieved stored volume = " << volume << " for " << id << LL_ENDL; - - return true; - } - - return false; -} - -void LLSpeakerVolumeStorage::removeSpeakerVolume(const LLUUID& speaker_id) -{ - mSpeakersData.erase(speaker_id); - - // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging. - // LL_DEBUGS("Voice") << "Removing stored volume for " << id << LL_ENDL; -} - -/* static */ F32 LLSpeakerVolumeStorage::transformFromLegacyVolume(F32 volume_in) -{ - // Convert to linear-logarithmic [0.0..1.0] with 0.5 = 0dB - // from legacy characteristic composed of two square-curves - // that intersect at volume_in = 0.5, volume_out = 0.56 - - F32 volume_out = 0.f; - volume_in = llclamp(volume_in, 0.f, 1.0f); - - if (volume_in <= 0.5f) - { - volume_out = volume_in * volume_in * 4.f * 0.56f; - } - else - { - volume_out = (1.f - 0.56f) * (4.f * volume_in * volume_in - 1.f) / 3.f + 0.56f; - } - - return volume_out; -} - -/* static */ F32 LLSpeakerVolumeStorage::transformToLegacyVolume(F32 volume_in) -{ - // Convert from linear-logarithmic [0.0..1.0] with 0.5 = 0dB - // to legacy characteristic composed of two square-curves - // that intersect at volume_in = 0.56, volume_out = 0.5 - - F32 volume_out = 0.f; - volume_in = llclamp(volume_in, 0.f, 1.0f); - - if (volume_in <= 0.56f) - { - volume_out = sqrt(volume_in / (4.f * 0.56f)); - } - else - { - volume_out = sqrt((3.f * (volume_in - 0.56f) / (1.f - 0.56f) + 1.f) / 4.f); - } - - return volume_out; -} - -void LLSpeakerVolumeStorage::load() -{ - // load per-resident voice volume information - std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, SETTINGS_FILE_NAME); - - LL_INFOS("Voice") << "Loading stored speaker volumes from: " << filename << LL_ENDL; - - LLSD settings_llsd; - llifstream file; - file.open(filename.c_str()); - if (file.is_open()) - { - if (LLSDParser::PARSE_FAILURE == LLSDSerialize::fromXML(settings_llsd, file)) - { - LL_WARNS("Voice") << "failed to parse " << filename << LL_ENDL; - - } - - } - - for (LLSD::map_const_iterator iter = settings_llsd.beginMap(); - iter != settings_llsd.endMap(); ++iter) - { - // Maintain compatibility with 1.23 non-linear saved volume levels - F32 volume = transformFromLegacyVolume((F32)iter->second.asReal()); - - storeSpeakerVolume(LLUUID(iter->first), volume); - } -} - -void LLSpeakerVolumeStorage::save() -{ - // If we quit from the login screen we will not have an SL account - // name. Don't try to save, otherwise we'll dump a file in - // C:\Program Files\SecondLife\ or similar. JC - std::string user_dir = gDirUtilp->getLindenUserDir(); - if (!user_dir.empty()) - { - std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, SETTINGS_FILE_NAME); - LLSD settings_llsd; - - LL_INFOS("Voice") << "Saving stored speaker volumes to: " << filename << LL_ENDL; - - for(speaker_data_map_t::const_iterator iter = mSpeakersData.begin(); iter != mSpeakersData.end(); ++iter) - { - // Maintain compatibility with 1.23 non-linear saved volume levels - F32 volume = transformToLegacyVolume(iter->second); - - settings_llsd[iter->first.asString()] = volume; - } - - llofstream file; - file.open(filename.c_str()); - LLSDSerialize::toPrettyXML(settings_llsd, file); - } -} - -bool LLViewerRequiredVoiceVersion::sAlertedUser = false; - -LLHTTPRegistration -gHTTPRegistrationMessageParcelVoiceInfo( - "/message/ParcelVoiceInfo"); - -LLHTTPRegistration -gHTTPRegistrationMessageRequiredVoiceVersion( - "/message/RequiredVoiceVersion"); + /** + * @file llvoiceclient.cpp + * @brief Voice client delegation class implementation. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llvoiceclient.h" +#include "llviewercontrol.h" +#include "llviewerwindow.h" +#include "llvoicevivox.h" +#include "llviewernetwork.h" +#include "llcommandhandler.h" +#include "llhttpnode.h" +#include "llnotificationsutil.h" +#include "llsdserialize.h" +#include "llui.h" +#include "llkeyboard.h" +#include "llagent.h" +#include "lluiusage.h" + +const F32 LLVoiceClient::OVERDRIVEN_POWER_LEVEL = 0.7f; + +const F32 LLVoiceClient::VOLUME_MIN = 0.f; +const F32 LLVoiceClient::VOLUME_DEFAULT = 0.5f; +const F32 LLVoiceClient::VOLUME_MAX = 1.0f; + + +// Support for secondlife:///app/voice SLapps +class LLVoiceHandler : public LLCommandHandler +{ +public: + // requests will be throttled from a non-trusted browser + LLVoiceHandler() : LLCommandHandler("voice", UNTRUSTED_THROTTLE) {} + + bool handle(const LLSD& params, const LLSD& query_map, const std::string& grid, LLMediaCtrl* web) + { + if (params[0].asString() == "effects") + { + LLVoiceEffectInterface* effect_interface = LLVoiceClient::instance().getVoiceEffectInterface(); + // If the voice client doesn't support voice effects, we can't handle effects SLapps + if (!effect_interface) + { + return false; + } + + // Support secondlife:///app/voice/effects/refresh to update the voice effect list with new effects + if (params[1].asString() == "refresh") + { + effect_interface->refreshVoiceEffectLists(false); + return true; + } + } + return false; + } +}; +LLVoiceHandler gVoiceHandler; + + + +std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserver::EStatusType inStatus) +{ + std::string result = "UNTRANSLATED"; + + // Prevent copy-paste errors when updating this list... +#define CASE(x) case x: result = #x; break + + switch(inStatus) + { + CASE(STATUS_LOGIN_RETRY); + CASE(STATUS_LOGGED_IN); + CASE(STATUS_JOINING); + CASE(STATUS_JOINED); + CASE(STATUS_LEFT_CHANNEL); + CASE(STATUS_VOICE_DISABLED); + CASE(STATUS_VOICE_ENABLED); + CASE(BEGIN_ERROR_STATUS); + CASE(ERROR_CHANNEL_FULL); + CASE(ERROR_CHANNEL_LOCKED); + CASE(ERROR_NOT_AVAILABLE); + CASE(ERROR_UNKNOWN); + default: + { + std::ostringstream stream; + stream << "UNKNOWN(" << (int)inStatus << ")"; + result = stream.str(); + } + break; + } + +#undef CASE + + return result; +} + + + +/////////////////////////////////////////////////////////////////////////////////////////////// + +LLVoiceClient::LLVoiceClient(LLPumpIO *pump) + : + mVoiceModule(NULL), + m_servicePump(NULL), + mVoiceEffectEnabled(LLCachedControl(gSavedSettings, "VoiceMorphingEnabled", true)), + mVoiceEffectDefault(LLCachedControl(gSavedPerAccountSettings, "VoiceEffectDefault", "00000000-0000-0000-0000-000000000000")), + mPTTDirty(true), + mPTT(true), + mUsePTT(true), + mPTTMouseButton(0), + mPTTKey(0), + mPTTIsToggle(false), + mUserPTTState(false), + mMuteMic(false), + mDisableMic(false) +{ + updateSettings(); + init(pump); +} + +//--------------------------------------------------- +// Basic setup/shutdown + +LLVoiceClient::~LLVoiceClient() +{ + llassert(!mVoiceModule); +} + +void LLVoiceClient::init(LLPumpIO *pump) +{ + // Initialize all of the voice modules + m_servicePump = pump; +} + +void LLVoiceClient::userAuthorized(const std::string& user_id, const LLUUID &agentID) +{ + // In the future, we should change this to allow voice module registration + // with a table lookup of sorts. + std::string voice_server = gSavedSettings.getString("VoiceServerType"); + LL_DEBUGS("Voice") << "voice server type " << voice_server << LL_ENDL; + if(voice_server == "vivox") + { + mVoiceModule = (LLVoiceModuleInterface *)LLVivoxVoiceClient::getInstance(); + } + else + { + mVoiceModule = NULL; + return; + } + mVoiceModule->init(m_servicePump); + mVoiceModule->userAuthorized(user_id, agentID); +} + +void LLVoiceClient::setHidden(bool hidden) +{ + if (mVoiceModule) + { + mVoiceModule->setHidden(hidden); + } +} + +void LLVoiceClient::terminate() +{ + if (mVoiceModule) mVoiceModule->terminate(); + mVoiceModule = NULL; + m_servicePump = NULL; + + // Shutdown speaker volume storage before LLSingletonBase::deleteAll() does it + if (LLSpeakerVolumeStorage::instanceExists()) + { + LLSpeakerVolumeStorage::deleteSingleton(); + } +} + +const LLVoiceVersionInfo LLVoiceClient::getVersion() +{ + if (mVoiceModule) + { + return mVoiceModule->getVersion(); + } + else + { + LLVoiceVersionInfo result; + result.serverVersion = std::string(); + result.serverType = std::string(); + result.mBuildVersion = std::string(); + return result; + } +} + +void LLVoiceClient::updateSettings() +{ + setUsePTT(gSavedSettings.getBOOL("PTTCurrentlyEnabled")); + setPTTIsToggle(gSavedSettings.getBOOL("PushToTalkToggle")); + mDisableMic = gSavedSettings.getBOOL("VoiceDisableMic"); + + updateMicMuteLogic(); + + if (mVoiceModule) + { + mVoiceModule->updateSettings(); + } +} + +//-------------------------------------------------- +// tuning + +void LLVoiceClient::tuningStart() +{ + if (mVoiceModule) mVoiceModule->tuningStart(); +} + +void LLVoiceClient::tuningStop() +{ + if (mVoiceModule) mVoiceModule->tuningStop(); +} + +bool LLVoiceClient::inTuningMode() +{ + if (mVoiceModule) + { + return mVoiceModule->inTuningMode(); + } + else + { + return false; + } +} + +void LLVoiceClient::tuningSetMicVolume(float volume) +{ + if (mVoiceModule) mVoiceModule->tuningSetMicVolume(volume); +} + +void LLVoiceClient::tuningSetSpeakerVolume(float volume) +{ + if (mVoiceModule) mVoiceModule->tuningSetSpeakerVolume(volume); +} + +float LLVoiceClient::tuningGetEnergy(void) +{ + if (mVoiceModule) + { + return mVoiceModule->tuningGetEnergy(); + } + else + { + return 0.0; + } +} + + +//------------------------------------------------ +// devices + +bool LLVoiceClient::deviceSettingsAvailable() +{ + if (mVoiceModule) + { + return mVoiceModule->deviceSettingsAvailable(); + } + else + { + return false; + } +} + +bool LLVoiceClient::deviceSettingsUpdated() +{ + if (mVoiceModule) + { + return mVoiceModule->deviceSettingsUpdated(); + } + else + { + return false; + } +} + +void LLVoiceClient::refreshDeviceLists(bool clearCurrentList) +{ + if (mVoiceModule) mVoiceModule->refreshDeviceLists(clearCurrentList); +} + +void LLVoiceClient::setCaptureDevice(const std::string& name) +{ + if (mVoiceModule) mVoiceModule->setCaptureDevice(name); + +} + +void LLVoiceClient::setRenderDevice(const std::string& name) +{ + if (mVoiceModule) mVoiceModule->setRenderDevice(name); +} + +const LLVoiceDeviceList& LLVoiceClient::getCaptureDevices() +{ + static LLVoiceDeviceList nullCaptureDevices; + if (mVoiceModule) + { + return mVoiceModule->getCaptureDevices(); + } + else + { + return nullCaptureDevices; + } +} + + +const LLVoiceDeviceList& LLVoiceClient::getRenderDevices() +{ + static LLVoiceDeviceList nullRenderDevices; + if (mVoiceModule) + { + return mVoiceModule->getRenderDevices(); + } + else + { + return nullRenderDevices; + } +} + + +//-------------------------------------------------- +// participants + +void LLVoiceClient::getParticipantList(std::set &participants) +{ + if (mVoiceModule) + { + mVoiceModule->getParticipantList(participants); + } + else + { + participants = std::set(); + } +} + +bool LLVoiceClient::isParticipant(const LLUUID &speaker_id) +{ + if(mVoiceModule) + { + return mVoiceModule->isParticipant(speaker_id); + } + return false; +} + + +//-------------------------------------------------- +// text chat + + +bool LLVoiceClient::isSessionTextIMPossible(const LLUUID& id) +{ + if (mVoiceModule) + { + return mVoiceModule->isSessionTextIMPossible(id); + } + else + { + return false; + } +} + +bool LLVoiceClient::isSessionCallBackPossible(const LLUUID& id) +{ + if (mVoiceModule) + { + return mVoiceModule->isSessionCallBackPossible(id); + } + else + { + return false; + } +} + +/* obsolete +bool LLVoiceClient::sendTextMessage(const LLUUID& participant_id, const std::string& message) +{ + if (mVoiceModule) + { + return mVoiceModule->sendTextMessage(participant_id, message); + } + else + { + return false; + } +} +*/ + +void LLVoiceClient::endUserIMSession(const LLUUID& participant_id) +{ + if (mVoiceModule) + { + // mVoiceModule->endUserIMSession(participant_id); // A SLim leftover + } +} + +//---------------------------------------------- +// channels + +bool LLVoiceClient::inProximalChannel() +{ + if (mVoiceModule) + { + return mVoiceModule->inProximalChannel(); + } + else + { + return false; + } +} + +void LLVoiceClient::setNonSpatialChannel( + const std::string &uri, + const std::string &credentials) +{ + if (mVoiceModule) + { + mVoiceModule->setNonSpatialChannel(uri, credentials); + } +} + +void LLVoiceClient::setSpatialChannel( + const std::string &uri, + const std::string &credentials) +{ + if (mVoiceModule) + { + mVoiceModule->setSpatialChannel(uri, credentials); + } +} + +void LLVoiceClient::leaveNonSpatialChannel() +{ + if (mVoiceModule) + { + mVoiceModule->leaveNonSpatialChannel(); + } +} + +void LLVoiceClient::leaveChannel(void) +{ + if (mVoiceModule) + { + mVoiceModule->leaveChannel(); + } +} + +std::string LLVoiceClient::getCurrentChannel() +{ + if (mVoiceModule) + { + return mVoiceModule->getCurrentChannel(); + } + else + { + return std::string(); + } +} + + +//--------------------------------------- +// invitations + +void LLVoiceClient::callUser(const LLUUID &uuid) +{ + if (mVoiceModule) mVoiceModule->callUser(uuid); +} + +bool LLVoiceClient::isValidChannel(std::string &session_handle) +{ + if (mVoiceModule) + { + return mVoiceModule->isValidChannel(session_handle); + } + else + { + return false; + } +} + +bool LLVoiceClient::answerInvite(std::string &channelHandle) +{ + if (mVoiceModule) + { + return mVoiceModule->answerInvite(channelHandle); + } + else + { + return false; + } +} + +void LLVoiceClient::declineInvite(std::string &channelHandle) +{ + if (mVoiceModule) mVoiceModule->declineInvite(channelHandle); +} + + +//------------------------------------------ +// Volume/gain + + +void LLVoiceClient::setVoiceVolume(F32 volume) +{ + if (mVoiceModule) mVoiceModule->setVoiceVolume(volume); +} + +void LLVoiceClient::setMicGain(F32 volume) +{ + if (mVoiceModule) mVoiceModule->setMicGain(volume); +} + + +//------------------------------------------ +// enable/disable voice features + +bool LLVoiceClient::voiceEnabled() +{ + if (mVoiceModule) + { + return mVoiceModule->voiceEnabled(); + } + else + { + return false; + } +} + +void LLVoiceClient::setVoiceEnabled(bool enabled) +{ + if (mVoiceModule) + { + mVoiceModule->setVoiceEnabled(enabled); + } +} + +void LLVoiceClient::updateMicMuteLogic() +{ + // If not configured to use PTT, the mic should be open (otherwise the user will be unable to speak). + bool new_mic_mute = false; + + if(mUsePTT) + { + // If configured to use PTT, track the user state. + new_mic_mute = !mUserPTTState; + } + + if(mMuteMic || mDisableMic) + { + // Either of these always overrides any other PTT setting. + new_mic_mute = true; + } + + if (mVoiceModule) mVoiceModule->setMuteMic(new_mic_mute); +} + +void LLVoiceClient::setLipSyncEnabled(bool enabled) +{ + if (mVoiceModule) mVoiceModule->setLipSyncEnabled(enabled); +} + +bool LLVoiceClient::lipSyncEnabled() +{ + if (mVoiceModule) + { + return mVoiceModule->lipSyncEnabled(); + } + else + { + return false; + } +} + +void LLVoiceClient::setMuteMic(bool muted) +{ + mMuteMic = muted; + updateMicMuteLogic(); + mMicroChangedSignal(); +} + + +// ---------------------------------------------- +// PTT + +void LLVoiceClient::setUserPTTState(bool ptt) +{ + if (ptt) + { + LLUIUsage::instance().logCommand("Agent.EnableMicrophone"); + } + mUserPTTState = ptt; + updateMicMuteLogic(); + mMicroChangedSignal(); +} + +bool LLVoiceClient::getUserPTTState() +{ + return mUserPTTState; +} + +void LLVoiceClient::setUsePTT(bool usePTT) +{ + if(usePTT && !mUsePTT) + { + // When the user turns on PTT, reset the current state. + mUserPTTState = false; + } + mUsePTT = usePTT; + + updateMicMuteLogic(); +} + +void LLVoiceClient::setPTTIsToggle(bool PTTIsToggle) +{ + if(!PTTIsToggle && mPTTIsToggle) + { + // When the user turns off toggle, reset the current state. + mUserPTTState = false; + } + + mPTTIsToggle = PTTIsToggle; + + updateMicMuteLogic(); +} + +bool LLVoiceClient::getPTTIsToggle() +{ + return mPTTIsToggle; +} + +void LLVoiceClient::inputUserControlState(bool down) +{ + if(mPTTIsToggle) + { + if(down) // toggle open-mic state on 'down' + { + toggleUserPTTState(); + } + } + else // set open-mic state as an absolute + { + setUserPTTState(down); + } +} + +void LLVoiceClient::toggleUserPTTState(void) +{ + setUserPTTState(!getUserPTTState()); +} + + +//------------------------------------------- +// nearby speaker accessors + +bool LLVoiceClient::getVoiceEnabled(const LLUUID& id) +{ + if (mVoiceModule) + { + return mVoiceModule->getVoiceEnabled(id); + } + else + { + return false; + } +} + +std::string LLVoiceClient::getDisplayName(const LLUUID& id) +{ + if (mVoiceModule) + { + return mVoiceModule->getDisplayName(id); + } + else + { + return std::string(); + } +} + +bool LLVoiceClient::isVoiceWorking() const +{ + if (mVoiceModule) + { + return mVoiceModule->isVoiceWorking(); + } + return false; +} + +bool LLVoiceClient::isParticipantAvatar(const LLUUID& id) +{ + if (mVoiceModule) + { + return mVoiceModule->isParticipantAvatar(id); + } + else + { + return false; + } +} + +bool LLVoiceClient::isOnlineSIP(const LLUUID& id) +{ + return false; +} + +bool LLVoiceClient::getIsSpeaking(const LLUUID& id) +{ + if (mVoiceModule) + { + return mVoiceModule->getIsSpeaking(id); + } + else + { + return false; + } +} + +bool LLVoiceClient::getIsModeratorMuted(const LLUUID& id) +{ + if (mVoiceModule) + { + return mVoiceModule->getIsModeratorMuted(id); + } + else + { + return false; + } +} + +F32 LLVoiceClient::getCurrentPower(const LLUUID& id) +{ + if (mVoiceModule) + { + return mVoiceModule->getCurrentPower(id); + } + else + { + return 0.0; + } +} + +bool LLVoiceClient::getOnMuteList(const LLUUID& id) +{ + if (mVoiceModule) + { + return mVoiceModule->getOnMuteList(id); + } + else + { + return false; + } +} + +F32 LLVoiceClient::getUserVolume(const LLUUID& id) +{ + if (mVoiceModule) + { + return mVoiceModule->getUserVolume(id); + } + else + { + return 0.0; + } +} + +void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume) +{ + if (mVoiceModule) mVoiceModule->setUserVolume(id, volume); +} + +//-------------------------------------------------- +// status observers + +void LLVoiceClient::addObserver(LLVoiceClientStatusObserver* observer) +{ + if (mVoiceModule) mVoiceModule->addObserver(observer); +} + +void LLVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer) +{ + if (mVoiceModule) + { + mVoiceModule->removeObserver(observer); + } +} + +void LLVoiceClient::addObserver(LLFriendObserver* observer) +{ + if (mVoiceModule) mVoiceModule->addObserver(observer); +} + +void LLVoiceClient::removeObserver(LLFriendObserver* observer) +{ + if (mVoiceModule) + { + mVoiceModule->removeObserver(observer); + } +} + +void LLVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer) +{ + if (mVoiceModule) mVoiceModule->addObserver(observer); +} + +void LLVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer) +{ + if (mVoiceModule) + { + mVoiceModule->removeObserver(observer); + } +} + +std::string LLVoiceClient::sipURIFromID(const LLUUID &id) +{ + if (mVoiceModule) + { + return mVoiceModule->sipURIFromID(id); + } + else + { + return std::string(); + } +} + +LLVoiceEffectInterface* LLVoiceClient::getVoiceEffectInterface() const +{ + return getVoiceEffectEnabled() ? dynamic_cast(mVoiceModule) : NULL; +} + +/////////////////// +// version checking + +class LLViewerRequiredVoiceVersion : public LLHTTPNode +{ + static bool sAlertedUser; + virtual void post( + LLHTTPNode::ResponsePtr response, + const LLSD& context, + const LLSD& input) const + { + //You received this messsage (most likely on region cross or + //teleport) + if ( input.has("body") && input["body"].has("major_version") ) + { + int major_voice_version = + input["body"]["major_version"].asInteger(); + // int minor_voice_version = + // input["body"]["minor_version"].asInteger(); + LLVoiceVersionInfo versionInfo = LLVoiceClient::getInstance()->getVersion(); + + if (major_voice_version > 1) + { + if (!sAlertedUser) + { + //sAlertedUser = true; + LLNotificationsUtil::add("VoiceVersionMismatch"); + gSavedSettings.setBOOL("EnableVoiceChat", false); // toggles listener + } + } + } + } +}; + +class LLViewerParcelVoiceInfo : public LLHTTPNode +{ + virtual void post( + LLHTTPNode::ResponsePtr response, + const LLSD& context, + const LLSD& input) const + { + //the parcel you are in has changed something about its + //voice information + + //this is a misnomer, as it can also be when you are not in + //a parcel at all. Should really be something like + //LLViewerVoiceInfoChanged..... + if ( input.has("body") ) + { + LLSD body = input["body"]; + + //body has "region_name" (str), "parcel_local_id"(int), + //"voice_credentials" (map). + + //body["voice_credentials"] has "channel_uri" (str), + //body["voice_credentials"] has "channel_credentials" (str) + + //if we really wanted to be extra careful, + //we'd check the supplied + //local parcel id to make sure it's for the same parcel + //we believe we're in + if ( body.has("voice_credentials") ) + { + LLSD voice_credentials = body["voice_credentials"]; + std::string uri; + std::string credentials; + + if ( voice_credentials.has("channel_uri") ) + { + uri = voice_credentials["channel_uri"].asString(); + } + if ( voice_credentials.has("channel_credentials") ) + { + credentials = + voice_credentials["channel_credentials"].asString(); + } + + LLVoiceClient::getInstance()->setSpatialChannel(uri, credentials); + } + } + } +}; + +const std::string LLSpeakerVolumeStorage::SETTINGS_FILE_NAME = "volume_settings.xml"; + +LLSpeakerVolumeStorage::LLSpeakerVolumeStorage() +{ + load(); +} + +LLSpeakerVolumeStorage::~LLSpeakerVolumeStorage() +{ +} + +//virtual +void LLSpeakerVolumeStorage::cleanupSingleton() +{ + save(); +} + +void LLSpeakerVolumeStorage::storeSpeakerVolume(const LLUUID& speaker_id, F32 volume) +{ + if ((volume >= LLVoiceClient::VOLUME_MIN) && (volume <= LLVoiceClient::VOLUME_MAX)) + { + mSpeakersData[speaker_id] = volume; + + // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging. + // LL_DEBUGS("Voice") << "Stored volume = " << volume << " for " << id << LL_ENDL; + } + else + { + LL_WARNS("Voice") << "Attempted to store out of range volume " << volume << " for " << speaker_id << LL_ENDL; + llassert(0); + } +} + +bool LLSpeakerVolumeStorage::getSpeakerVolume(const LLUUID& speaker_id, F32& volume) +{ + speaker_data_map_t::const_iterator it = mSpeakersData.find(speaker_id); + + if (it != mSpeakersData.end()) + { + volume = it->second; + + // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging. + // LL_DEBUGS("Voice") << "Retrieved stored volume = " << volume << " for " << id << LL_ENDL; + + return true; + } + + return false; +} + +void LLSpeakerVolumeStorage::removeSpeakerVolume(const LLUUID& speaker_id) +{ + mSpeakersData.erase(speaker_id); + + // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging. + // LL_DEBUGS("Voice") << "Removing stored volume for " << id << LL_ENDL; +} + +/* static */ F32 LLSpeakerVolumeStorage::transformFromLegacyVolume(F32 volume_in) +{ + // Convert to linear-logarithmic [0.0..1.0] with 0.5 = 0dB + // from legacy characteristic composed of two square-curves + // that intersect at volume_in = 0.5, volume_out = 0.56 + + F32 volume_out = 0.f; + volume_in = llclamp(volume_in, 0.f, 1.0f); + + if (volume_in <= 0.5f) + { + volume_out = volume_in * volume_in * 4.f * 0.56f; + } + else + { + volume_out = (1.f - 0.56f) * (4.f * volume_in * volume_in - 1.f) / 3.f + 0.56f; + } + + return volume_out; +} + +/* static */ F32 LLSpeakerVolumeStorage::transformToLegacyVolume(F32 volume_in) +{ + // Convert from linear-logarithmic [0.0..1.0] with 0.5 = 0dB + // to legacy characteristic composed of two square-curves + // that intersect at volume_in = 0.56, volume_out = 0.5 + + F32 volume_out = 0.f; + volume_in = llclamp(volume_in, 0.f, 1.0f); + + if (volume_in <= 0.56f) + { + volume_out = sqrt(volume_in / (4.f * 0.56f)); + } + else + { + volume_out = sqrt((3.f * (volume_in - 0.56f) / (1.f - 0.56f) + 1.f) / 4.f); + } + + return volume_out; +} + +void LLSpeakerVolumeStorage::load() +{ + // load per-resident voice volume information + std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, SETTINGS_FILE_NAME); + + LL_INFOS("Voice") << "Loading stored speaker volumes from: " << filename << LL_ENDL; + + LLSD settings_llsd; + llifstream file; + file.open(filename.c_str()); + if (file.is_open()) + { + if (LLSDParser::PARSE_FAILURE == LLSDSerialize::fromXML(settings_llsd, file)) + { + LL_WARNS("Voice") << "failed to parse " << filename << LL_ENDL; + + } + + } + + for (LLSD::map_const_iterator iter = settings_llsd.beginMap(); + iter != settings_llsd.endMap(); ++iter) + { + // Maintain compatibility with 1.23 non-linear saved volume levels + F32 volume = transformFromLegacyVolume((F32)iter->second.asReal()); + + storeSpeakerVolume(LLUUID(iter->first), volume); + } +} + +void LLSpeakerVolumeStorage::save() +{ + // If we quit from the login screen we will not have an SL account + // name. Don't try to save, otherwise we'll dump a file in + // C:\Program Files\SecondLife\ or similar. JC + std::string user_dir = gDirUtilp->getLindenUserDir(); + if (!user_dir.empty()) + { + std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, SETTINGS_FILE_NAME); + LLSD settings_llsd; + + LL_INFOS("Voice") << "Saving stored speaker volumes to: " << filename << LL_ENDL; + + for(speaker_data_map_t::const_iterator iter = mSpeakersData.begin(); iter != mSpeakersData.end(); ++iter) + { + // Maintain compatibility with 1.23 non-linear saved volume levels + F32 volume = transformToLegacyVolume(iter->second); + + settings_llsd[iter->first.asString()] = volume; + } + + llofstream file; + file.open(filename.c_str()); + LLSDSerialize::toPrettyXML(settings_llsd, file); + } +} + +bool LLViewerRequiredVoiceVersion::sAlertedUser = false; + +LLHTTPRegistration +gHTTPRegistrationMessageParcelVoiceInfo( + "/message/ParcelVoiceInfo"); + +LLHTTPRegistration +gHTTPRegistrationMessageRequiredVoiceVersion( + "/message/RequiredVoiceVersion"); diff --git a/indra/newview/llvoiceclient.h b/indra/newview/llvoiceclient.h index 2e4dedf8a2..9e035ef908 100644 --- a/indra/newview/llvoiceclient.h +++ b/indra/newview/llvoiceclient.h @@ -1,544 +1,544 @@ -/** - * @file llvoiceclient.h - * @brief Declaration of LLVoiceClient class which is the interface to the voice client process. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ -#ifndef LL_VOICE_CLIENT_H -#define LL_VOICE_CLIENT_H - -class LLVOAvatar; - -#include "lliopipe.h" -#include "llpumpio.h" -#include "llchainio.h" -#include "lliosocket.h" -#include "v3math.h" -#include "llframetimer.h" -#include "llcallingcard.h" // for LLFriendObserver -#include "llsecapi.h" -#include "llcontrol.h" - -// devices - -class LLVoiceDevice -{ - public: - std::string display_name; // friendly value for the user - std::string full_name; // internal value for selection - - LLVoiceDevice(const std::string& display_name, const std::string& full_name) - :display_name(display_name) - ,full_name(full_name) - { - }; -}; -typedef std::vector LLVoiceDeviceList; - -class LLVoiceClientParticipantObserver -{ -public: - virtual ~LLVoiceClientParticipantObserver() { } - virtual void onParticipantsChanged() = 0; -}; - - -/////////////////////////////////// -/// @class LLVoiceClientStatusObserver -class LLVoiceClientStatusObserver -{ -public: - typedef enum e_voice_status_type - { - // NOTE: when updating this enum, please also update the switch in - // LLVoiceClientStatusObserver::status2string(). - STATUS_LOGIN_RETRY, - STATUS_LOGGED_IN, - STATUS_JOINING, - STATUS_JOINED, - STATUS_LEFT_CHANNEL, - STATUS_VOICE_DISABLED, - STATUS_VOICE_ENABLED, - BEGIN_ERROR_STATUS, - ERROR_CHANNEL_FULL, - ERROR_CHANNEL_LOCKED, - ERROR_NOT_AVAILABLE, - ERROR_UNKNOWN - } EStatusType; - - virtual ~LLVoiceClientStatusObserver() { } - virtual void onChange(EStatusType status, const std::string &channelURI, bool proximal) = 0; - - static std::string status2string(EStatusType inStatus); -}; - -struct LLVoiceVersionInfo -{ - std::string serverType; - std::string serverVersion; - std::string mBuildVersion; -}; - -////////////////////////////////// -/// @class LLVoiceModuleInterface -/// @brief Voice module interface -/// -/// Voice modules should provide an implementation for this interface. -///////////////////////////////// - -class LLVoiceModuleInterface -{ -public: - LLVoiceModuleInterface() {} - virtual ~LLVoiceModuleInterface() {} - - virtual void init(LLPumpIO *pump)=0; // Call this once at application startup (creates connector) - virtual void terminate()=0; // Call this to clean up during shutdown - - virtual void updateSettings()=0; // call after loading settings and whenever they change - - virtual bool isVoiceWorking() const = 0; // connected to a voice server and voice channel - - virtual void setHidden(bool hidden)=0; // Hides the user from voice. - - virtual const LLVoiceVersionInfo& getVersion()=0; - - ///////////////////// - /// @name Tuning - //@{ - virtual void tuningStart()=0; - virtual void tuningStop()=0; - virtual bool inTuningMode()=0; - - virtual void tuningSetMicVolume(float volume)=0; - virtual void tuningSetSpeakerVolume(float volume)=0; - virtual float tuningGetEnergy(void)=0; - //@} - - ///////////////////// - /// @name Devices - //@{ - // This returns true when it's safe to bring up the "device settings" dialog in the prefs. - // i.e. when the daemon is running and connected, and the device lists are populated. - virtual bool deviceSettingsAvailable()=0; - virtual bool deviceSettingsUpdated() = 0; - - // Requery the vivox daemon for the current list of input/output devices. - // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed - // (use this if you want to know when it's done). - // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim. - virtual void refreshDeviceLists(bool clearCurrentList = true)=0; - - virtual void setCaptureDevice(const std::string& name)=0; - virtual void setRenderDevice(const std::string& name)=0; - - virtual LLVoiceDeviceList& getCaptureDevices()=0; - virtual LLVoiceDeviceList& getRenderDevices()=0; - - virtual void getParticipantList(std::set &participants)=0; - virtual bool isParticipant(const LLUUID& speaker_id)=0; - //@} - - //////////////////////////// - /// @ name Channel stuff - //@{ - // returns true iff the user is currently in a proximal (local spatial) channel. - // Note that gestures should only fire if this returns true. - virtual bool inProximalChannel()=0; - - virtual void setNonSpatialChannel(const std::string &uri, - const std::string &credentials)=0; - - virtual bool setSpatialChannel(const std::string &uri, - const std::string &credentials)=0; - - virtual void leaveNonSpatialChannel()=0; - - virtual void leaveChannel(void)=0; - - // Returns the URI of the current channel, or an empty string if not currently in a channel. - // NOTE that it will return an empty string if it's in the process of joining a channel. - virtual std::string getCurrentChannel()=0; - //@} - - - ////////////////////////// - /// @name invitations - //@{ - // start a voice channel with the specified user - virtual void callUser(const LLUUID &uuid)=0; - virtual bool isValidChannel(std::string& channelHandle)=0; - virtual bool answerInvite(std::string &channelHandle)=0; - virtual void declineInvite(std::string &channelHandle)=0; - //@} - - ///////////////////////// - /// @name Volume/gain - //@{ - virtual void setVoiceVolume(F32 volume)=0; - virtual void setMicGain(F32 volume)=0; - //@} - - ///////////////////////// - /// @name enable disable voice and features - //@{ - virtual bool voiceEnabled()=0; - virtual void setVoiceEnabled(bool enabled)=0; - virtual void setLipSyncEnabled(bool enabled)=0; - virtual bool lipSyncEnabled()=0; - virtual void setMuteMic(bool muted)=0; // Set the mute state of the local mic. - //@} - - ////////////////////////// - /// @name nearby speaker accessors - //@{ - virtual bool getVoiceEnabled(const LLUUID& id)=0; // true if we've received data for this avatar - virtual std::string getDisplayName(const LLUUID& id)=0; - virtual bool isParticipantAvatar(const LLUUID &id)=0; - virtual bool getIsSpeaking(const LLUUID& id)=0; - virtual bool getIsModeratorMuted(const LLUUID& id)=0; - virtual F32 getCurrentPower(const LLUUID& id)=0; // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is... - virtual bool getOnMuteList(const LLUUID& id)=0; - virtual F32 getUserVolume(const LLUUID& id)=0; - virtual void setUserVolume(const LLUUID& id, F32 volume)=0; // set's volume for specified agent, from 0-1 (where .5 is nominal) - //@} - - ////////////////////////// - /// @name text chat - //@{ - virtual bool isSessionTextIMPossible(const LLUUID& id)=0; - virtual bool isSessionCallBackPossible(const LLUUID& id)=0; - //virtual bool sendTextMessage(const LLUUID& participant_id, const std::string& message)=0; - virtual void endUserIMSession(const LLUUID &uuid)=0; - //@} - - // authorize the user - virtual void userAuthorized(const std::string& user_id, - const LLUUID &agentID)=0; - - ////////////////////////////// - /// @name Status notification - //@{ - virtual void addObserver(LLVoiceClientStatusObserver* observer)=0; - virtual void removeObserver(LLVoiceClientStatusObserver* observer)=0; - virtual void addObserver(LLFriendObserver* observer)=0; - virtual void removeObserver(LLFriendObserver* observer)=0; - virtual void addObserver(LLVoiceClientParticipantObserver* observer)=0; - virtual void removeObserver(LLVoiceClientParticipantObserver* observer)=0; - //@} - - virtual std::string sipURIFromID(const LLUUID &id)=0; - //@} - -}; - - -////////////////////////////////// -/// @class LLVoiceEffectObserver -class LLVoiceEffectObserver -{ -public: - virtual ~LLVoiceEffectObserver() { } - virtual void onVoiceEffectChanged(bool effect_list_updated) = 0; -}; - -typedef std::multimap voice_effect_list_t; - -////////////////////////////////// -/// @class LLVoiceEffectInterface -/// @brief Voice effect module interface -/// -/// Voice effect modules should provide an implementation for this interface. -///////////////////////////////// - -class LLVoiceEffectInterface -{ -public: - LLVoiceEffectInterface() {} - virtual ~LLVoiceEffectInterface() {} - - ////////////////////////// - /// @name Accessors - //@{ - virtual bool setVoiceEffect(const LLUUID& id) = 0; - virtual const LLUUID getVoiceEffect() = 0; - virtual LLSD getVoiceEffectProperties(const LLUUID& id) = 0; - - virtual void refreshVoiceEffectLists(bool clear_lists) = 0; - virtual const voice_effect_list_t &getVoiceEffectList() const = 0; - virtual const voice_effect_list_t &getVoiceEffectTemplateList() const = 0; - //@} - - ////////////////////////////// - /// @name Status notification - //@{ - virtual void addObserver(LLVoiceEffectObserver* observer) = 0; - virtual void removeObserver(LLVoiceEffectObserver* observer) = 0; - //@} - - ////////////////////////////// - /// @name Preview buffer - //@{ - virtual void enablePreviewBuffer(bool enable) = 0; - virtual void recordPreviewBuffer() = 0; - virtual void playPreviewBuffer(const LLUUID& effect_id = LLUUID::null) = 0; - virtual void stopPreviewBuffer() = 0; - - virtual bool isPreviewRecording() = 0; - virtual bool isPreviewPlaying() = 0; - //@} -}; - - -class LLVoiceClient: public LLParamSingleton -{ - LLSINGLETON(LLVoiceClient, LLPumpIO *pump); - LOG_CLASS(LLVoiceClient); - ~LLVoiceClient(); - -public: - typedef boost::signals2::signal micro_changed_signal_t; - micro_changed_signal_t mMicroChangedSignal; - - void terminate(); // Call this to clean up during shutdown - - const LLVoiceVersionInfo getVersion(); - - static const F32 OVERDRIVEN_POWER_LEVEL; - - static const F32 VOLUME_MIN; - static const F32 VOLUME_DEFAULT; - static const F32 VOLUME_MAX; - - void updateSettings(); // call after loading settings and whenever they change - - bool isVoiceWorking() const; // connected to a voice server and voice channel - - // tuning - void tuningStart(); - void tuningStop(); - bool inTuningMode(); - - void tuningSetMicVolume(float volume); - void tuningSetSpeakerVolume(float volume); - float tuningGetEnergy(void); - - // devices - - // This returns true when it's safe to bring up the "device settings" dialog in the prefs. - // i.e. when the daemon is running and connected, and the device lists are populated. - bool deviceSettingsAvailable(); - bool deviceSettingsUpdated(); // returns true when the device list has been updated recently. - - // Requery the vivox daemon for the current list of input/output devices. - // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed - // (use this if you want to know when it's done). - // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim. - void refreshDeviceLists(bool clearCurrentList = true); - - void setCaptureDevice(const std::string& name); - void setRenderDevice(const std::string& name); - void setHidden(bool hidden); - - const LLVoiceDeviceList& getCaptureDevices(); - const LLVoiceDeviceList& getRenderDevices(); - - //////////////////////////// - // Channel stuff - // - - // returns true iff the user is currently in a proximal (local spatial) channel. - // Note that gestures should only fire if this returns true. - bool inProximalChannel(); - void setNonSpatialChannel( - const std::string &uri, - const std::string &credentials); - void setSpatialChannel( - const std::string &uri, - const std::string &credentials); - void leaveNonSpatialChannel(); - - // Returns the URI of the current channel, or an empty string if not currently in a channel. - // NOTE that it will return an empty string if it's in the process of joining a channel. - std::string getCurrentChannel(); - // start a voice channel with the specified user - void callUser(const LLUUID &uuid); - bool isValidChannel(std::string& channelHandle); - bool answerInvite(std::string &channelHandle); - void declineInvite(std::string &channelHandle); - void leaveChannel(void); // call this on logout or teleport begin - - - ///////////////////////////// - // Sending updates of current state - - - void setVoiceVolume(F32 volume); - void setMicGain(F32 volume); - void setUserVolume(const LLUUID& id, F32 volume); // set's volume for specified agent, from 0-1 (where .5 is nominal) - bool voiceEnabled(); - void setLipSyncEnabled(bool enabled); - void setMuteMic(bool muted); // Use this to mute the local mic (for when the client is minimized, etc), ignoring user PTT state. - void setUserPTTState(bool ptt); - bool getUserPTTState(); - void toggleUserPTTState(void); - void inputUserControlState(bool down); // interpret any sort of up-down mic-open control input according to ptt-toggle prefs - void setVoiceEnabled(bool enabled); - - void setUsePTT(bool usePTT); - void setPTTIsToggle(bool PTTIsToggle); - bool getPTTIsToggle(); - - void updateMicMuteLogic(); - - bool lipSyncEnabled(); - - boost::signals2::connection MicroChangedCallback(const micro_changed_signal_t::slot_type& cb ) { return mMicroChangedSignal.connect(cb); } - - - ///////////////////////////// - // Accessors for data related to nearby speakers - bool getVoiceEnabled(const LLUUID& id); // true if we've received data for this avatar - std::string getDisplayName(const LLUUID& id); - bool isOnlineSIP(const LLUUID &id); - bool isParticipantAvatar(const LLUUID &id); - bool getIsSpeaking(const LLUUID& id); - bool getIsModeratorMuted(const LLUUID& id); - F32 getCurrentPower(const LLUUID& id); // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is... - bool getOnMuteList(const LLUUID& id); - F32 getUserVolume(const LLUUID& id); - - ///////////////////////////// - bool getAreaVoiceDisabled(); // returns true if the area the avatar is in is speech-disabled. - // Use this to determine whether to show a "no speech" icon in the menu bar. - void getParticipantList(std::set &participants); - bool isParticipant(const LLUUID& speaker_id); - - ////////////////////////// - /// @name text chat - //@{ - bool isSessionTextIMPossible(const LLUUID& id); - bool isSessionCallBackPossible(const LLUUID& id); - //bool sendTextMessage(const LLUUID& participant_id, const std::string& message) const {return true;} ; - void endUserIMSession(const LLUUID &uuid); - //@} - - - void userAuthorized(const std::string& user_id, - const LLUUID &agentID); - - void addObserver(LLVoiceClientStatusObserver* observer); - void removeObserver(LLVoiceClientStatusObserver* observer); - void addObserver(LLFriendObserver* observer); - void removeObserver(LLFriendObserver* observer); - void addObserver(LLVoiceClientParticipantObserver* observer); - void removeObserver(LLVoiceClientParticipantObserver* observer); - - std::string sipURIFromID(const LLUUID &id); - - ////////////////////////// - /// @name Voice effects - //@{ - bool getVoiceEffectEnabled() const { return mVoiceEffectEnabled; }; - LLUUID getVoiceEffectDefault() const { return LLUUID(mVoiceEffectDefault); }; - - // Returns NULL if voice effects are not supported, or not enabled. - LLVoiceEffectInterface* getVoiceEffectInterface() const; - //@} -private: - void init(LLPumpIO *pump); - -protected: - LLVoiceModuleInterface* mVoiceModule; - LLPumpIO *m_servicePump; - - - LLCachedControl mVoiceEffectEnabled; - LLCachedControl mVoiceEffectDefault; - - bool mPTTDirty; - bool mPTT; - - bool mUsePTT; - S32 mPTTMouseButton; - KEY mPTTKey; - bool mPTTIsToggle; - bool mUserPTTState; - bool mMuteMic; - bool mDisableMic; -}; - -/** - * Speaker volume storage helper class - **/ -class LLSpeakerVolumeStorage : public LLSingleton -{ - LLSINGLETON(LLSpeakerVolumeStorage); - ~LLSpeakerVolumeStorage(); - LOG_CLASS(LLSpeakerVolumeStorage); - -protected: - virtual void cleanupSingleton() override; - -public: - - /** - * Stores volume level for specified user. - * - * @param[in] speaker_id - LLUUID of user to store volume level for. - * @param[in] volume - volume level to be stored for user. - */ - void storeSpeakerVolume(const LLUUID& speaker_id, F32 volume); - - /** - * Gets stored volume level for specified speaker - * - * @param[in] speaker_id - LLUUID of user to retrieve volume level for. - * @param[out] volume - set to stored volume if found, otherwise unmodified. - * @return - true if a stored volume is found. - */ - bool getSpeakerVolume(const LLUUID& speaker_id, F32& volume); - - /** - * Removes stored volume level for specified user. - * - * @param[in] speaker_id - LLUUID of user to remove. - */ - void removeSpeakerVolume(const LLUUID& speaker_id); - -private: - const static std::string SETTINGS_FILE_NAME; - - void load(); - void save(); - - static F32 transformFromLegacyVolume(F32 volume_in); - static F32 transformToLegacyVolume(F32 volume_in); - - typedef std::map speaker_data_map_t; - speaker_data_map_t mSpeakersData; -}; - -#endif //LL_VOICE_CLIENT_H - - - +/** + * @file llvoiceclient.h + * @brief Declaration of LLVoiceClient class which is the interface to the voice client process. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#ifndef LL_VOICE_CLIENT_H +#define LL_VOICE_CLIENT_H + +class LLVOAvatar; + +#include "lliopipe.h" +#include "llpumpio.h" +#include "llchainio.h" +#include "lliosocket.h" +#include "v3math.h" +#include "llframetimer.h" +#include "llcallingcard.h" // for LLFriendObserver +#include "llsecapi.h" +#include "llcontrol.h" + +// devices + +class LLVoiceDevice +{ + public: + std::string display_name; // friendly value for the user + std::string full_name; // internal value for selection + + LLVoiceDevice(const std::string& display_name, const std::string& full_name) + :display_name(display_name) + ,full_name(full_name) + { + }; +}; +typedef std::vector LLVoiceDeviceList; + +class LLVoiceClientParticipantObserver +{ +public: + virtual ~LLVoiceClientParticipantObserver() { } + virtual void onParticipantsChanged() = 0; +}; + + +/////////////////////////////////// +/// @class LLVoiceClientStatusObserver +class LLVoiceClientStatusObserver +{ +public: + typedef enum e_voice_status_type + { + // NOTE: when updating this enum, please also update the switch in + // LLVoiceClientStatusObserver::status2string(). + STATUS_LOGIN_RETRY, + STATUS_LOGGED_IN, + STATUS_JOINING, + STATUS_JOINED, + STATUS_LEFT_CHANNEL, + STATUS_VOICE_DISABLED, + STATUS_VOICE_ENABLED, + BEGIN_ERROR_STATUS, + ERROR_CHANNEL_FULL, + ERROR_CHANNEL_LOCKED, + ERROR_NOT_AVAILABLE, + ERROR_UNKNOWN + } EStatusType; + + virtual ~LLVoiceClientStatusObserver() { } + virtual void onChange(EStatusType status, const std::string &channelURI, bool proximal) = 0; + + static std::string status2string(EStatusType inStatus); +}; + +struct LLVoiceVersionInfo +{ + std::string serverType; + std::string serverVersion; + std::string mBuildVersion; +}; + +////////////////////////////////// +/// @class LLVoiceModuleInterface +/// @brief Voice module interface +/// +/// Voice modules should provide an implementation for this interface. +///////////////////////////////// + +class LLVoiceModuleInterface +{ +public: + LLVoiceModuleInterface() {} + virtual ~LLVoiceModuleInterface() {} + + virtual void init(LLPumpIO *pump)=0; // Call this once at application startup (creates connector) + virtual void terminate()=0; // Call this to clean up during shutdown + + virtual void updateSettings()=0; // call after loading settings and whenever they change + + virtual bool isVoiceWorking() const = 0; // connected to a voice server and voice channel + + virtual void setHidden(bool hidden)=0; // Hides the user from voice. + + virtual const LLVoiceVersionInfo& getVersion()=0; + + ///////////////////// + /// @name Tuning + //@{ + virtual void tuningStart()=0; + virtual void tuningStop()=0; + virtual bool inTuningMode()=0; + + virtual void tuningSetMicVolume(float volume)=0; + virtual void tuningSetSpeakerVolume(float volume)=0; + virtual float tuningGetEnergy(void)=0; + //@} + + ///////////////////// + /// @name Devices + //@{ + // This returns true when it's safe to bring up the "device settings" dialog in the prefs. + // i.e. when the daemon is running and connected, and the device lists are populated. + virtual bool deviceSettingsAvailable()=0; + virtual bool deviceSettingsUpdated() = 0; + + // Requery the vivox daemon for the current list of input/output devices. + // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed + // (use this if you want to know when it's done). + // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim. + virtual void refreshDeviceLists(bool clearCurrentList = true)=0; + + virtual void setCaptureDevice(const std::string& name)=0; + virtual void setRenderDevice(const std::string& name)=0; + + virtual LLVoiceDeviceList& getCaptureDevices()=0; + virtual LLVoiceDeviceList& getRenderDevices()=0; + + virtual void getParticipantList(std::set &participants)=0; + virtual bool isParticipant(const LLUUID& speaker_id)=0; + //@} + + //////////////////////////// + /// @ name Channel stuff + //@{ + // returns true iff the user is currently in a proximal (local spatial) channel. + // Note that gestures should only fire if this returns true. + virtual bool inProximalChannel()=0; + + virtual void setNonSpatialChannel(const std::string &uri, + const std::string &credentials)=0; + + virtual bool setSpatialChannel(const std::string &uri, + const std::string &credentials)=0; + + virtual void leaveNonSpatialChannel()=0; + + virtual void leaveChannel(void)=0; + + // Returns the URI of the current channel, or an empty string if not currently in a channel. + // NOTE that it will return an empty string if it's in the process of joining a channel. + virtual std::string getCurrentChannel()=0; + //@} + + + ////////////////////////// + /// @name invitations + //@{ + // start a voice channel with the specified user + virtual void callUser(const LLUUID &uuid)=0; + virtual bool isValidChannel(std::string& channelHandle)=0; + virtual bool answerInvite(std::string &channelHandle)=0; + virtual void declineInvite(std::string &channelHandle)=0; + //@} + + ///////////////////////// + /// @name Volume/gain + //@{ + virtual void setVoiceVolume(F32 volume)=0; + virtual void setMicGain(F32 volume)=0; + //@} + + ///////////////////////// + /// @name enable disable voice and features + //@{ + virtual bool voiceEnabled()=0; + virtual void setVoiceEnabled(bool enabled)=0; + virtual void setLipSyncEnabled(bool enabled)=0; + virtual bool lipSyncEnabled()=0; + virtual void setMuteMic(bool muted)=0; // Set the mute state of the local mic. + //@} + + ////////////////////////// + /// @name nearby speaker accessors + //@{ + virtual bool getVoiceEnabled(const LLUUID& id)=0; // true if we've received data for this avatar + virtual std::string getDisplayName(const LLUUID& id)=0; + virtual bool isParticipantAvatar(const LLUUID &id)=0; + virtual bool getIsSpeaking(const LLUUID& id)=0; + virtual bool getIsModeratorMuted(const LLUUID& id)=0; + virtual F32 getCurrentPower(const LLUUID& id)=0; // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is... + virtual bool getOnMuteList(const LLUUID& id)=0; + virtual F32 getUserVolume(const LLUUID& id)=0; + virtual void setUserVolume(const LLUUID& id, F32 volume)=0; // set's volume for specified agent, from 0-1 (where .5 is nominal) + //@} + + ////////////////////////// + /// @name text chat + //@{ + virtual bool isSessionTextIMPossible(const LLUUID& id)=0; + virtual bool isSessionCallBackPossible(const LLUUID& id)=0; + //virtual bool sendTextMessage(const LLUUID& participant_id, const std::string& message)=0; + virtual void endUserIMSession(const LLUUID &uuid)=0; + //@} + + // authorize the user + virtual void userAuthorized(const std::string& user_id, + const LLUUID &agentID)=0; + + ////////////////////////////// + /// @name Status notification + //@{ + virtual void addObserver(LLVoiceClientStatusObserver* observer)=0; + virtual void removeObserver(LLVoiceClientStatusObserver* observer)=0; + virtual void addObserver(LLFriendObserver* observer)=0; + virtual void removeObserver(LLFriendObserver* observer)=0; + virtual void addObserver(LLVoiceClientParticipantObserver* observer)=0; + virtual void removeObserver(LLVoiceClientParticipantObserver* observer)=0; + //@} + + virtual std::string sipURIFromID(const LLUUID &id)=0; + //@} + +}; + + +////////////////////////////////// +/// @class LLVoiceEffectObserver +class LLVoiceEffectObserver +{ +public: + virtual ~LLVoiceEffectObserver() { } + virtual void onVoiceEffectChanged(bool effect_list_updated) = 0; +}; + +typedef std::multimap voice_effect_list_t; + +////////////////////////////////// +/// @class LLVoiceEffectInterface +/// @brief Voice effect module interface +/// +/// Voice effect modules should provide an implementation for this interface. +///////////////////////////////// + +class LLVoiceEffectInterface +{ +public: + LLVoiceEffectInterface() {} + virtual ~LLVoiceEffectInterface() {} + + ////////////////////////// + /// @name Accessors + //@{ + virtual bool setVoiceEffect(const LLUUID& id) = 0; + virtual const LLUUID getVoiceEffect() = 0; + virtual LLSD getVoiceEffectProperties(const LLUUID& id) = 0; + + virtual void refreshVoiceEffectLists(bool clear_lists) = 0; + virtual const voice_effect_list_t &getVoiceEffectList() const = 0; + virtual const voice_effect_list_t &getVoiceEffectTemplateList() const = 0; + //@} + + ////////////////////////////// + /// @name Status notification + //@{ + virtual void addObserver(LLVoiceEffectObserver* observer) = 0; + virtual void removeObserver(LLVoiceEffectObserver* observer) = 0; + //@} + + ////////////////////////////// + /// @name Preview buffer + //@{ + virtual void enablePreviewBuffer(bool enable) = 0; + virtual void recordPreviewBuffer() = 0; + virtual void playPreviewBuffer(const LLUUID& effect_id = LLUUID::null) = 0; + virtual void stopPreviewBuffer() = 0; + + virtual bool isPreviewRecording() = 0; + virtual bool isPreviewPlaying() = 0; + //@} +}; + + +class LLVoiceClient: public LLParamSingleton +{ + LLSINGLETON(LLVoiceClient, LLPumpIO *pump); + LOG_CLASS(LLVoiceClient); + ~LLVoiceClient(); + +public: + typedef boost::signals2::signal micro_changed_signal_t; + micro_changed_signal_t mMicroChangedSignal; + + void terminate(); // Call this to clean up during shutdown + + const LLVoiceVersionInfo getVersion(); + + static const F32 OVERDRIVEN_POWER_LEVEL; + + static const F32 VOLUME_MIN; + static const F32 VOLUME_DEFAULT; + static const F32 VOLUME_MAX; + + void updateSettings(); // call after loading settings and whenever they change + + bool isVoiceWorking() const; // connected to a voice server and voice channel + + // tuning + void tuningStart(); + void tuningStop(); + bool inTuningMode(); + + void tuningSetMicVolume(float volume); + void tuningSetSpeakerVolume(float volume); + float tuningGetEnergy(void); + + // devices + + // This returns true when it's safe to bring up the "device settings" dialog in the prefs. + // i.e. when the daemon is running and connected, and the device lists are populated. + bool deviceSettingsAvailable(); + bool deviceSettingsUpdated(); // returns true when the device list has been updated recently. + + // Requery the vivox daemon for the current list of input/output devices. + // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed + // (use this if you want to know when it's done). + // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim. + void refreshDeviceLists(bool clearCurrentList = true); + + void setCaptureDevice(const std::string& name); + void setRenderDevice(const std::string& name); + void setHidden(bool hidden); + + const LLVoiceDeviceList& getCaptureDevices(); + const LLVoiceDeviceList& getRenderDevices(); + + //////////////////////////// + // Channel stuff + // + + // returns true iff the user is currently in a proximal (local spatial) channel. + // Note that gestures should only fire if this returns true. + bool inProximalChannel(); + void setNonSpatialChannel( + const std::string &uri, + const std::string &credentials); + void setSpatialChannel( + const std::string &uri, + const std::string &credentials); + void leaveNonSpatialChannel(); + + // Returns the URI of the current channel, or an empty string if not currently in a channel. + // NOTE that it will return an empty string if it's in the process of joining a channel. + std::string getCurrentChannel(); + // start a voice channel with the specified user + void callUser(const LLUUID &uuid); + bool isValidChannel(std::string& channelHandle); + bool answerInvite(std::string &channelHandle); + void declineInvite(std::string &channelHandle); + void leaveChannel(void); // call this on logout or teleport begin + + + ///////////////////////////// + // Sending updates of current state + + + void setVoiceVolume(F32 volume); + void setMicGain(F32 volume); + void setUserVolume(const LLUUID& id, F32 volume); // set's volume for specified agent, from 0-1 (where .5 is nominal) + bool voiceEnabled(); + void setLipSyncEnabled(bool enabled); + void setMuteMic(bool muted); // Use this to mute the local mic (for when the client is minimized, etc), ignoring user PTT state. + void setUserPTTState(bool ptt); + bool getUserPTTState(); + void toggleUserPTTState(void); + void inputUserControlState(bool down); // interpret any sort of up-down mic-open control input according to ptt-toggle prefs + void setVoiceEnabled(bool enabled); + + void setUsePTT(bool usePTT); + void setPTTIsToggle(bool PTTIsToggle); + bool getPTTIsToggle(); + + void updateMicMuteLogic(); + + bool lipSyncEnabled(); + + boost::signals2::connection MicroChangedCallback(const micro_changed_signal_t::slot_type& cb ) { return mMicroChangedSignal.connect(cb); } + + + ///////////////////////////// + // Accessors for data related to nearby speakers + bool getVoiceEnabled(const LLUUID& id); // true if we've received data for this avatar + std::string getDisplayName(const LLUUID& id); + bool isOnlineSIP(const LLUUID &id); + bool isParticipantAvatar(const LLUUID &id); + bool getIsSpeaking(const LLUUID& id); + bool getIsModeratorMuted(const LLUUID& id); + F32 getCurrentPower(const LLUUID& id); // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is... + bool getOnMuteList(const LLUUID& id); + F32 getUserVolume(const LLUUID& id); + + ///////////////////////////// + bool getAreaVoiceDisabled(); // returns true if the area the avatar is in is speech-disabled. + // Use this to determine whether to show a "no speech" icon in the menu bar. + void getParticipantList(std::set &participants); + bool isParticipant(const LLUUID& speaker_id); + + ////////////////////////// + /// @name text chat + //@{ + bool isSessionTextIMPossible(const LLUUID& id); + bool isSessionCallBackPossible(const LLUUID& id); + //bool sendTextMessage(const LLUUID& participant_id, const std::string& message) const {return true;} ; + void endUserIMSession(const LLUUID &uuid); + //@} + + + void userAuthorized(const std::string& user_id, + const LLUUID &agentID); + + void addObserver(LLVoiceClientStatusObserver* observer); + void removeObserver(LLVoiceClientStatusObserver* observer); + void addObserver(LLFriendObserver* observer); + void removeObserver(LLFriendObserver* observer); + void addObserver(LLVoiceClientParticipantObserver* observer); + void removeObserver(LLVoiceClientParticipantObserver* observer); + + std::string sipURIFromID(const LLUUID &id); + + ////////////////////////// + /// @name Voice effects + //@{ + bool getVoiceEffectEnabled() const { return mVoiceEffectEnabled; }; + LLUUID getVoiceEffectDefault() const { return LLUUID(mVoiceEffectDefault); }; + + // Returns NULL if voice effects are not supported, or not enabled. + LLVoiceEffectInterface* getVoiceEffectInterface() const; + //@} +private: + void init(LLPumpIO *pump); + +protected: + LLVoiceModuleInterface* mVoiceModule; + LLPumpIO *m_servicePump; + + + LLCachedControl mVoiceEffectEnabled; + LLCachedControl mVoiceEffectDefault; + + bool mPTTDirty; + bool mPTT; + + bool mUsePTT; + S32 mPTTMouseButton; + KEY mPTTKey; + bool mPTTIsToggle; + bool mUserPTTState; + bool mMuteMic; + bool mDisableMic; +}; + +/** + * Speaker volume storage helper class + **/ +class LLSpeakerVolumeStorage : public LLSingleton +{ + LLSINGLETON(LLSpeakerVolumeStorage); + ~LLSpeakerVolumeStorage(); + LOG_CLASS(LLSpeakerVolumeStorage); + +protected: + virtual void cleanupSingleton() override; + +public: + + /** + * Stores volume level for specified user. + * + * @param[in] speaker_id - LLUUID of user to store volume level for. + * @param[in] volume - volume level to be stored for user. + */ + void storeSpeakerVolume(const LLUUID& speaker_id, F32 volume); + + /** + * Gets stored volume level for specified speaker + * + * @param[in] speaker_id - LLUUID of user to retrieve volume level for. + * @param[out] volume - set to stored volume if found, otherwise unmodified. + * @return - true if a stored volume is found. + */ + bool getSpeakerVolume(const LLUUID& speaker_id, F32& volume); + + /** + * Removes stored volume level for specified user. + * + * @param[in] speaker_id - LLUUID of user to remove. + */ + void removeSpeakerVolume(const LLUUID& speaker_id); + +private: + const static std::string SETTINGS_FILE_NAME; + + void load(); + void save(); + + static F32 transformFromLegacyVolume(F32 volume_in); + static F32 transformToLegacyVolume(F32 volume_in); + + typedef std::map speaker_data_map_t; + speaker_data_map_t mSpeakersData; +}; + +#endif //LL_VOICE_CLIENT_H + + + diff --git a/indra/newview/llvoicevisualizer.cpp b/indra/newview/llvoicevisualizer.cpp index 6e177fb1fb..b1ff6eb346 100644 --- a/indra/newview/llvoicevisualizer.cpp +++ b/indra/newview/llvoicevisualizer.cpp @@ -1,612 +1,612 @@ -/** - * @file llvoicevisualizer.cpp - * @brief Draws in-world speaking indicators. - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -//---------------------------------------------------------------------- -// Voice Visualizer -// author: JJ Ventrella -// (information about this stuff can be found in "llvoicevisualizer.h") -//---------------------------------------------------------------------- -#include "llviewerprecompiledheaders.h" -#include "llviewercontrol.h" -#include "llglheaders.h" -#include "llsphere.h" -#include "llvoicevisualizer.h" -#include "llviewercamera.h" -#include "llviewerobject.h" -#include "llviewertexture.h" -#include "llviewertexturelist.h" -#include "llvoiceclient.h" -#include "llrender.h" -#include "llagent.h" - -//brent's wave image -//29de489d-0491-fb00-7dab-f9e686d31e83 - - -//-------------------------------------------------------------------------------------- -// sound symbol constants -//-------------------------------------------------------------------------------------- -const F32 HEIGHT_ABOVE_HEAD = 0.3f; // how many meters vertically above the av's head the voice symbol will appear -const F32 RED_THRESHOLD = LLVoiceClient::OVERDRIVEN_POWER_LEVEL; // value above which speaking amplitude causes the voice symbol to turn red -const F32 GREEN_THRESHOLD = 0.2f; // value above which speaking amplitude causes the voice symbol to turn green -const F32 FADE_OUT_DURATION = 0.4f; // how many seconds it takes for a pair of waves to fade away -const F32 EXPANSION_RATE = 1.0f; // how many seconds it takes for the waves to expand to twice their original size -const F32 EXPANSION_MAX = 1.5f; // maximum size scale to which the waves can expand before popping back to 1.0 -const F32 WAVE_WIDTH_SCALE = 0.03f; // base width of the waves -const F32 WAVE_HEIGHT_SCALE = 0.02f; // base height of the waves -const F32 BASE_BRIGHTNESS = 0.7f; // gray level of the voice indicator when quiet (below green threshold) -const F32 DOT_SIZE = 0.05f; // size of the dot billboard texture -const F32 DOT_OPACITY = 0.7f; // how opaque the dot is -const F32 WAVE_MOTION_RATE = 1.5f; // scalar applied to consecutive waves as a function of speaking amplitude - -//-------------------------------------------------------------------------------------- -// gesticulation constants -//-------------------------------------------------------------------------------------- -const F32 DEFAULT_MINIMUM_GESTICULATION_AMPLITUDE = 0.2f; -const F32 DEFAULT_MAXIMUM_GESTICULATION_AMPLITUDE = 1.0f; - -//-------------------------------------------------------------------------------------- -// other constants -//-------------------------------------------------------------------------------------- -const LLVector3 WORLD_UPWARD_DIRECTION = LLVector3( 0.0f, 0.0f, 1.0f ); // Z is up in SL - -//------------------------------------------------------------------ -// Initialize the statics -//------------------------------------------------------------------ -bool LLVoiceVisualizer::sPrefsInitialized = false; -bool LLVoiceVisualizer::sLipSyncEnabled = false; -F32* LLVoiceVisualizer::sOoh = NULL; -F32* LLVoiceVisualizer::sAah = NULL; -U32 LLVoiceVisualizer::sOohs = 0; -U32 LLVoiceVisualizer::sAahs = 0; -F32 LLVoiceVisualizer::sOohAahRate = 0.0f; -F32* LLVoiceVisualizer::sOohPowerTransfer = NULL; -U32 LLVoiceVisualizer::sOohPowerTransfers = 0; -F32 LLVoiceVisualizer::sOohPowerTransfersf = 0.0f; -F32* LLVoiceVisualizer::sAahPowerTransfer = NULL; -U32 LLVoiceVisualizer::sAahPowerTransfers = 0; -F32 LLVoiceVisualizer::sAahPowerTransfersf = 0.0f; - - -//----------------------------------------------- -// constructor -//----------------------------------------------- -LLVoiceVisualizer::LLVoiceVisualizer( const U8 type ) - : LLHUDEffect(type) -{ - mCurrentTime = mTimer.getTotalSeconds(); - mPreviousTime = mCurrentTime; - mStartTime = mCurrentTime; - mVoiceSourceWorldPosition = LLVector3( 0.0f, 0.0f, 0.0f ); - mSpeakingAmplitude = 0.0f; - mCurrentlySpeaking = false; - mVoiceEnabled = false; - mMinGesticulationAmplitude = DEFAULT_MINIMUM_GESTICULATION_AMPLITUDE; - mMaxGesticulationAmplitude = DEFAULT_MAXIMUM_GESTICULATION_AMPLITUDE; - mSoundSymbol.mActive = true; - mSoundSymbol.mPosition = LLVector3( 0.0f, 0.0f, 0.0f ); - - mTimer.reset(); - - const char* sound_level_img[] = - { - "voice_meter_dot.j2c", - "voice_meter_rings.j2c", - "voice_meter_rings.j2c", - "voice_meter_rings.j2c", - "voice_meter_rings.j2c", - "voice_meter_rings.j2c", - "voice_meter_rings.j2c" - }; - - for (int i=0; isetFilteringOption(LLTexUnit::TFO_ANISOTROPIC); - - // The first instance loads the initial state from prefs. - if (!sPrefsInitialized) - { - setPreferences(); - - // Set up our listener to get updates on all prefs values we care about. - gSavedSettings.getControl("LipSyncEnabled")->getSignal()->connect(boost::bind(&LLVoiceVisualizer::handleVoiceVisualizerPrefsChanged, _2)); - gSavedSettings.getControl("LipSyncOohAahRate")->getSignal()->connect(boost::bind(&LLVoiceVisualizer::handleVoiceVisualizerPrefsChanged, _2)); - gSavedSettings.getControl("LipSyncOoh")->getSignal()->connect(boost::bind(&LLVoiceVisualizer::handleVoiceVisualizerPrefsChanged, _2)); - gSavedSettings.getControl("LipSyncAah")->getSignal()->connect(boost::bind(&LLVoiceVisualizer::handleVoiceVisualizerPrefsChanged, _2)); - gSavedSettings.getControl("LipSyncOohPowerTransfer")->getSignal()->connect(boost::bind(&LLVoiceVisualizer::handleVoiceVisualizerPrefsChanged, _2)); - gSavedSettings.getControl("LipSyncAahPowerTransfer")->getSignal()->connect(boost::bind(&LLVoiceVisualizer::handleVoiceVisualizerPrefsChanged, _2)); - - sPrefsInitialized = true; - } - -}//--------------------------------------------------- - -//--------------------------------------------------- -void LLVoiceVisualizer::setMinGesticulationAmplitude( F32 m ) -{ - mMinGesticulationAmplitude = m; - -}//--------------------------------------------------- - -//--------------------------------------------------- -void LLVoiceVisualizer::setMaxGesticulationAmplitude( F32 m ) -{ - mMaxGesticulationAmplitude = m; - -}//--------------------------------------------------- - -//--------------------------------------------------- -void LLVoiceVisualizer::setVoiceEnabled( bool v ) -{ - mVoiceEnabled = v; - -}//--------------------------------------------------- - -//--------------------------------------------------- -void LLVoiceVisualizer::setStartSpeaking() -{ - mStartTime = mTimer.getTotalSeconds(); - mCurrentlySpeaking = true; - mSoundSymbol.mActive = true; - -}//--------------------------------------------------- - - -//--------------------------------------------------- -bool LLVoiceVisualizer::getCurrentlySpeaking() -{ - return mCurrentlySpeaking; - -}//--------------------------------------------------- - - -//--------------------------------------------------- -void LLVoiceVisualizer::setStopSpeaking() -{ - mCurrentlySpeaking = false; - mSpeakingAmplitude = 0.0f; - -}//--------------------------------------------------- - - -//--------------------------------------------------- -void LLVoiceVisualizer::setSpeakingAmplitude( F32 a ) -{ - mSpeakingAmplitude = a; - -}//--------------------------------------------------- - -//------------------------------------------------------------------ -// handles parameter updates -//------------------------------------------------------------------ -bool LLVoiceVisualizer::handleVoiceVisualizerPrefsChanged(const LLSD& newvalue) -{ - // Note: Ignore the specific event value, we look up the ones we want - LLVoiceVisualizer::setPreferences(); - return true; -} - -//--------------------------------------------------- -void LLVoiceVisualizer::setPreferences( ) -{ - sLipSyncEnabled = gSavedSettings.getBOOL("LipSyncEnabled"); - sOohAahRate = gSavedSettings.getF32("LipSyncOohAahRate"); - - std::string oohString = gSavedSettings.getString("LipSyncOoh"); - lipStringToF32s (oohString, sOoh, sOohs); - - std::string aahString = gSavedSettings.getString("LipSyncAah"); - lipStringToF32s (aahString, sAah, sAahs); - - std::string oohPowerString = gSavedSettings.getString("LipSyncOohPowerTransfer"); - lipStringToF32s (oohPowerString, sOohPowerTransfer, sOohPowerTransfers); - sOohPowerTransfersf = (F32) sOohPowerTransfers; - - std::string aahPowerString = gSavedSettings.getString("LipSyncAahPowerTransfer"); - lipStringToF32s (aahPowerString, sAahPowerTransfer, sAahPowerTransfers); - sAahPowerTransfersf = (F32) sAahPowerTransfers; - -}//--------------------------------------------------- - - -//--------------------------------------------------- -// convert a string of digits to an array of floats. -// the result for each digit is the value of the -// digit multiplied by 0.11 -//--------------------------------------------------- -void LLVoiceVisualizer::lipStringToF32s ( std::string& in_string, F32*& out_F32s, U32& count_F32s ) -{ - delete[] out_F32s; // get rid of the current array - - count_F32s = in_string.length(); - if (count_F32s == 0) - { - // we don't like zero length arrays - - count_F32s = 1; - out_F32s = new F32[1]; - out_F32s[0] = 0.0f; - } - else - { - out_F32s = new F32[count_F32s]; - - for (U32 i=0; i 9) - { - four_bits = 9; - } - out_F32s[i] = 0.11f * (F32) four_bits; - } - } - -}//--------------------------------------------------- - - -//-------------------------------------------------------------------------- -// find the amount to blend the ooh and aah mouth morphs -//-------------------------------------------------------------------------- -void LLVoiceVisualizer::lipSyncOohAah( F32& ooh, F32& aah ) -{ - if (sLipSyncEnabled && mCurrentlySpeaking) - { - U32 transfer_index = (U32) (sOohPowerTransfersf * mSpeakingAmplitude); - if (transfer_index >= sOohPowerTransfers) - { - transfer_index = sOohPowerTransfers - 1; - } - F32 transfer_ooh = sOohPowerTransfer[transfer_index]; - - transfer_index = (U32) (sAahPowerTransfersf * mSpeakingAmplitude); - if (transfer_index >= sAahPowerTransfers) - { - transfer_index = sAahPowerTransfers - 1; - } - F32 transfer_aah = sAahPowerTransfer[transfer_index]; - - F64 current_time = mTimer.getTotalSeconds(); - F64 elapsed_time = current_time - mStartTime; - U32 elapsed_frames = (U32) (elapsed_time * sOohAahRate); - U32 elapsed_oohs = elapsed_frames % sOohs; - U32 elapsed_aahs = elapsed_frames % sAahs; - - ooh = transfer_ooh * sOoh[elapsed_oohs]; - aah = transfer_aah * sAah[elapsed_aahs]; - - /* - LL_INFOS() << " elapsed frames " << elapsed_frames - << " ooh " << ooh - << " aah " << aah - << " transfer ooh" << transfer_ooh - << " transfer aah" << transfer_aah - << " start time " << mStartTime - << " current time " << current_time - << " elapsed time " << elapsed_time - << " elapsed oohs " << elapsed_oohs - << " elapsed aahs " << elapsed_aahs - << LL_ENDL; - */ - } - else - { - ooh = 0.0f; - aah = 0.0f; - } - -}//--------------------------------------------------- - - -//--------------------------------------------------- -// this method is inherited from HUD Effect -//--------------------------------------------------- -void LLVoiceVisualizer::render() -{ - if ( ! mVoiceEnabled ) - { - return; - } - - if ( mSoundSymbol.mActive ) - { - mPreviousTime = mCurrentTime; - mCurrentTime = mTimer.getTotalSeconds(); - - //--------------------------------------------------------------- - // set the sound symbol position over the source (avatar's head) - //--------------------------------------------------------------- - mSoundSymbol.mPosition = gAgent.getPosAgentFromGlobal(mPositionGlobal) + WORLD_UPWARD_DIRECTION * HEIGHT_ABOVE_HEAD; - - //--------------------------------------------------------------- - // some gl state - //--------------------------------------------------------------- - LLGLSPipelineAlpha alpha_blend; - LLGLDepthTest depth(GL_TRUE, GL_FALSE); - //LLGLDisable gls_stencil(GL_STENCIL_TEST); - - //------------------------------------------------------------- - // create coordinates of the geometry for the dot - //------------------------------------------------------------- - LLViewerCamera* camera = LLViewerCamera::getInstance(); - LLVector3 l = camera->getLeftAxis() * DOT_SIZE; - LLVector3 u = camera->getUpAxis() * DOT_SIZE; - - LLVector3 bottomLeft = mSoundSymbol.mPosition + l - u; - LLVector3 bottomRight = mSoundSymbol.mPosition - l - u; - LLVector3 topLeft = mSoundSymbol.mPosition + l + u; - LLVector3 topRight = mSoundSymbol.mPosition - l + u; - - //----------------------------- - // bind texture 0 (the dot) - //----------------------------- - gGL.getTexUnit(0)->bind(mSoundSymbol.mTexture[0]); - - //------------------------------------------------------------- - // now render the dot - //------------------------------------------------------------- - gGL.color4fv( LLColor4( 1.0f, 1.0f, 1.0f, DOT_OPACITY ).mV ); - - gGL.begin( LLRender::TRIANGLE_STRIP ); - gGL.texCoord2i( 0, 0 ); gGL.vertex3fv( bottomLeft.mV ); - gGL.texCoord2i( 1, 0 ); gGL.vertex3fv( bottomRight.mV ); - gGL.texCoord2i( 0, 1 ); gGL.vertex3fv( topLeft.mV ); - gGL.end(); - - gGL.begin( LLRender::TRIANGLE_STRIP ); - gGL.texCoord2i( 1, 0 ); gGL.vertex3fv( bottomRight.mV ); - gGL.texCoord2i( 1, 1 ); gGL.vertex3fv( topRight.mV ); - gGL.texCoord2i( 0, 1 ); gGL.vertex3fv( topLeft.mV ); - gGL.end(); - - - - //-------------------------------------------------------------------------------------- - // if currently speaking, trigger waves (1 through 6) based on speaking amplitude - //-------------------------------------------------------------------------------------- - if ( mCurrentlySpeaking ) - { - F32 min = 0.2f; - F32 max = 0.7f; - F32 fraction = ( mSpeakingAmplitude - min ) / ( max - min ); - - // in case mSpeakingAmplitude > max.... - if ( fraction > 1.0f ) - { - fraction = 1.0f; - } - - S32 level = 1 + (int)( fraction * ( NUM_VOICE_SYMBOL_WAVES - 2 ) ); - - for (int i=0; i EXPANSION_MAX ) - { - mSoundSymbol.mWaveExpansion[i] = 1.0f; - } - - //---------------------------------------------------------------------------------- - // create geometry for the wave billboard textures - //---------------------------------------------------------------------------------- - F32 width = i * WAVE_WIDTH_SCALE * mSoundSymbol.mWaveExpansion[i]; - F32 height = i * WAVE_HEIGHT_SCALE * mSoundSymbol.mWaveExpansion[i]; - - LLVector3 l = camera->getLeftAxis() * width; - LLVector3 u = camera->getUpAxis() * height; - - LLVector3 bottomLeft = mSoundSymbol.mPosition + l - u; - LLVector3 bottomRight = mSoundSymbol.mPosition - l - u; - LLVector3 topLeft = mSoundSymbol.mPosition + l + u; - LLVector3 topRight = mSoundSymbol.mPosition - l + u; - - gGL.color4fv( LLColor4( red, green, blue, mSoundSymbol.mWaveOpacity[i] ).mV ); - gGL.getTexUnit(0)->bind(mSoundSymbol.mTexture[i]); - - - //--------------------------------------------------- - // now, render the mofo - //--------------------------------------------------- - gGL.begin( LLRender::TRIANGLE_STRIP ); - gGL.texCoord2i( 0, 0 ); gGL.vertex3fv( bottomLeft.mV ); - gGL.texCoord2i( 1, 0 ); gGL.vertex3fv( bottomRight.mV ); - gGL.texCoord2i( 0, 1 ); gGL.vertex3fv( topLeft.mV ); - gGL.end(); - - gGL.begin( LLRender::TRIANGLE_STRIP ); - gGL.texCoord2i( 1, 0 ); gGL.vertex3fv( bottomRight.mV ); - gGL.texCoord2i( 1, 1 ); gGL.vertex3fv( topRight.mV ); - gGL.texCoord2i( 0, 1 ); gGL.vertex3fv( topLeft.mV ); - gGL.end(); - - } //if ( mSoundSymbol.mWaveActive[i] ) - - }// for loop - - }//if ( mSoundSymbol.mActive ) - -}//--------------------------------------------------- - -//--------------------------------------------------- -void LLVoiceVisualizer::setVoiceSourceWorldPosition( const LLVector3 &p ) -{ - mVoiceSourceWorldPosition = p; - -}//--------------------------------------------------- - -//--------------------------------------------------- -VoiceGesticulationLevel LLVoiceVisualizer::getCurrentGesticulationLevel() -{ - VoiceGesticulationLevel gesticulationLevel = VOICE_GESTICULATION_LEVEL_OFF; //default - - //----------------------------------------------------------------------------------------- - // Within the range of gesticulation amplitudes, the sound signal is split into - // three equal amplitude regimes, each specifying one of three gesticulation levels. - //----------------------------------------------------------------------------------------- - F32 range = mMaxGesticulationAmplitude - mMinGesticulationAmplitude; - - if ( mSpeakingAmplitude > mMinGesticulationAmplitude + range * 0.5f ) { gesticulationLevel = VOICE_GESTICULATION_LEVEL_HIGH; } - else if ( mSpeakingAmplitude > mMinGesticulationAmplitude + range * 0.25f ) { gesticulationLevel = VOICE_GESTICULATION_LEVEL_MEDIUM; } - else if ( mSpeakingAmplitude > mMinGesticulationAmplitude + range * 0.00000f ) { gesticulationLevel = VOICE_GESTICULATION_LEVEL_LOW; } - - return gesticulationLevel; - -}//--------------------------------------------------- - - - -//------------------------------------ -// Destructor -//------------------------------------ -LLVoiceVisualizer::~LLVoiceVisualizer() -{ -}//---------------------------------------------- - - -//--------------------------------------------------- -// "packData" is inherited from HUDEffect -//--------------------------------------------------- -void LLVoiceVisualizer::packData(LLMessageSystem *mesgsys) -{ - // Pack the default data - LLHUDEffect::packData(mesgsys); - - // TODO -- pack the relevant data for voice effects - // we'll come up with some cool configurations....TBD - //U8 packed_data[41]; - //mesgsys->addBinaryDataFast(_PREHASH_TypeData, packed_data, 41); - U8 packed_data = 0; - mesgsys->addBinaryDataFast(_PREHASH_TypeData, &packed_data, 1); -} - - -//--------------------------------------------------- -// "unpackData" is inherited from HUDEffect -//--------------------------------------------------- -void LLVoiceVisualizer::unpackData(LLMessageSystem *mesgsys, S32 blocknum) -{ - // TODO -- find the speaker, unpack binary data, set the properties of this effect - /* - LLHUDEffect::unpackData(mesgsys, blocknum); - LLUUID source_id; - LLUUID target_id; - S32 size = mesgsys->getSizeFast(_PREHASH_Effect, blocknum, _PREHASH_TypeData); - if (size != 1) - { - LL_WARNS() << "Voice effect with bad size " << size << LL_ENDL; - return; - } - mesgsys->getBinaryDataFast(_PREHASH_Effect, _PREHASH_TypeData, packed_data, 1, blocknum); - */ -} - - -//------------------------------------------------------------------ -// this method is inherited from HUD Effect -//------------------------------------------------------------------ -void LLVoiceVisualizer::markDead() -{ - mCurrentlySpeaking = false; - mVoiceEnabled = false; - mSoundSymbol.mActive = false; - - LLHUDEffect::markDead(); -}//------------------------------------------------------------------ +/** + * @file llvoicevisualizer.cpp + * @brief Draws in-world speaking indicators. + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +//---------------------------------------------------------------------- +// Voice Visualizer +// author: JJ Ventrella +// (information about this stuff can be found in "llvoicevisualizer.h") +//---------------------------------------------------------------------- +#include "llviewerprecompiledheaders.h" +#include "llviewercontrol.h" +#include "llglheaders.h" +#include "llsphere.h" +#include "llvoicevisualizer.h" +#include "llviewercamera.h" +#include "llviewerobject.h" +#include "llviewertexture.h" +#include "llviewertexturelist.h" +#include "llvoiceclient.h" +#include "llrender.h" +#include "llagent.h" + +//brent's wave image +//29de489d-0491-fb00-7dab-f9e686d31e83 + + +//-------------------------------------------------------------------------------------- +// sound symbol constants +//-------------------------------------------------------------------------------------- +const F32 HEIGHT_ABOVE_HEAD = 0.3f; // how many meters vertically above the av's head the voice symbol will appear +const F32 RED_THRESHOLD = LLVoiceClient::OVERDRIVEN_POWER_LEVEL; // value above which speaking amplitude causes the voice symbol to turn red +const F32 GREEN_THRESHOLD = 0.2f; // value above which speaking amplitude causes the voice symbol to turn green +const F32 FADE_OUT_DURATION = 0.4f; // how many seconds it takes for a pair of waves to fade away +const F32 EXPANSION_RATE = 1.0f; // how many seconds it takes for the waves to expand to twice their original size +const F32 EXPANSION_MAX = 1.5f; // maximum size scale to which the waves can expand before popping back to 1.0 +const F32 WAVE_WIDTH_SCALE = 0.03f; // base width of the waves +const F32 WAVE_HEIGHT_SCALE = 0.02f; // base height of the waves +const F32 BASE_BRIGHTNESS = 0.7f; // gray level of the voice indicator when quiet (below green threshold) +const F32 DOT_SIZE = 0.05f; // size of the dot billboard texture +const F32 DOT_OPACITY = 0.7f; // how opaque the dot is +const F32 WAVE_MOTION_RATE = 1.5f; // scalar applied to consecutive waves as a function of speaking amplitude + +//-------------------------------------------------------------------------------------- +// gesticulation constants +//-------------------------------------------------------------------------------------- +const F32 DEFAULT_MINIMUM_GESTICULATION_AMPLITUDE = 0.2f; +const F32 DEFAULT_MAXIMUM_GESTICULATION_AMPLITUDE = 1.0f; + +//-------------------------------------------------------------------------------------- +// other constants +//-------------------------------------------------------------------------------------- +const LLVector3 WORLD_UPWARD_DIRECTION = LLVector3( 0.0f, 0.0f, 1.0f ); // Z is up in SL + +//------------------------------------------------------------------ +// Initialize the statics +//------------------------------------------------------------------ +bool LLVoiceVisualizer::sPrefsInitialized = false; +bool LLVoiceVisualizer::sLipSyncEnabled = false; +F32* LLVoiceVisualizer::sOoh = NULL; +F32* LLVoiceVisualizer::sAah = NULL; +U32 LLVoiceVisualizer::sOohs = 0; +U32 LLVoiceVisualizer::sAahs = 0; +F32 LLVoiceVisualizer::sOohAahRate = 0.0f; +F32* LLVoiceVisualizer::sOohPowerTransfer = NULL; +U32 LLVoiceVisualizer::sOohPowerTransfers = 0; +F32 LLVoiceVisualizer::sOohPowerTransfersf = 0.0f; +F32* LLVoiceVisualizer::sAahPowerTransfer = NULL; +U32 LLVoiceVisualizer::sAahPowerTransfers = 0; +F32 LLVoiceVisualizer::sAahPowerTransfersf = 0.0f; + + +//----------------------------------------------- +// constructor +//----------------------------------------------- +LLVoiceVisualizer::LLVoiceVisualizer( const U8 type ) + : LLHUDEffect(type) +{ + mCurrentTime = mTimer.getTotalSeconds(); + mPreviousTime = mCurrentTime; + mStartTime = mCurrentTime; + mVoiceSourceWorldPosition = LLVector3( 0.0f, 0.0f, 0.0f ); + mSpeakingAmplitude = 0.0f; + mCurrentlySpeaking = false; + mVoiceEnabled = false; + mMinGesticulationAmplitude = DEFAULT_MINIMUM_GESTICULATION_AMPLITUDE; + mMaxGesticulationAmplitude = DEFAULT_MAXIMUM_GESTICULATION_AMPLITUDE; + mSoundSymbol.mActive = true; + mSoundSymbol.mPosition = LLVector3( 0.0f, 0.0f, 0.0f ); + + mTimer.reset(); + + const char* sound_level_img[] = + { + "voice_meter_dot.j2c", + "voice_meter_rings.j2c", + "voice_meter_rings.j2c", + "voice_meter_rings.j2c", + "voice_meter_rings.j2c", + "voice_meter_rings.j2c", + "voice_meter_rings.j2c" + }; + + for (int i=0; isetFilteringOption(LLTexUnit::TFO_ANISOTROPIC); + + // The first instance loads the initial state from prefs. + if (!sPrefsInitialized) + { + setPreferences(); + + // Set up our listener to get updates on all prefs values we care about. + gSavedSettings.getControl("LipSyncEnabled")->getSignal()->connect(boost::bind(&LLVoiceVisualizer::handleVoiceVisualizerPrefsChanged, _2)); + gSavedSettings.getControl("LipSyncOohAahRate")->getSignal()->connect(boost::bind(&LLVoiceVisualizer::handleVoiceVisualizerPrefsChanged, _2)); + gSavedSettings.getControl("LipSyncOoh")->getSignal()->connect(boost::bind(&LLVoiceVisualizer::handleVoiceVisualizerPrefsChanged, _2)); + gSavedSettings.getControl("LipSyncAah")->getSignal()->connect(boost::bind(&LLVoiceVisualizer::handleVoiceVisualizerPrefsChanged, _2)); + gSavedSettings.getControl("LipSyncOohPowerTransfer")->getSignal()->connect(boost::bind(&LLVoiceVisualizer::handleVoiceVisualizerPrefsChanged, _2)); + gSavedSettings.getControl("LipSyncAahPowerTransfer")->getSignal()->connect(boost::bind(&LLVoiceVisualizer::handleVoiceVisualizerPrefsChanged, _2)); + + sPrefsInitialized = true; + } + +}//--------------------------------------------------- + +//--------------------------------------------------- +void LLVoiceVisualizer::setMinGesticulationAmplitude( F32 m ) +{ + mMinGesticulationAmplitude = m; + +}//--------------------------------------------------- + +//--------------------------------------------------- +void LLVoiceVisualizer::setMaxGesticulationAmplitude( F32 m ) +{ + mMaxGesticulationAmplitude = m; + +}//--------------------------------------------------- + +//--------------------------------------------------- +void LLVoiceVisualizer::setVoiceEnabled( bool v ) +{ + mVoiceEnabled = v; + +}//--------------------------------------------------- + +//--------------------------------------------------- +void LLVoiceVisualizer::setStartSpeaking() +{ + mStartTime = mTimer.getTotalSeconds(); + mCurrentlySpeaking = true; + mSoundSymbol.mActive = true; + +}//--------------------------------------------------- + + +//--------------------------------------------------- +bool LLVoiceVisualizer::getCurrentlySpeaking() +{ + return mCurrentlySpeaking; + +}//--------------------------------------------------- + + +//--------------------------------------------------- +void LLVoiceVisualizer::setStopSpeaking() +{ + mCurrentlySpeaking = false; + mSpeakingAmplitude = 0.0f; + +}//--------------------------------------------------- + + +//--------------------------------------------------- +void LLVoiceVisualizer::setSpeakingAmplitude( F32 a ) +{ + mSpeakingAmplitude = a; + +}//--------------------------------------------------- + +//------------------------------------------------------------------ +// handles parameter updates +//------------------------------------------------------------------ +bool LLVoiceVisualizer::handleVoiceVisualizerPrefsChanged(const LLSD& newvalue) +{ + // Note: Ignore the specific event value, we look up the ones we want + LLVoiceVisualizer::setPreferences(); + return true; +} + +//--------------------------------------------------- +void LLVoiceVisualizer::setPreferences( ) +{ + sLipSyncEnabled = gSavedSettings.getBOOL("LipSyncEnabled"); + sOohAahRate = gSavedSettings.getF32("LipSyncOohAahRate"); + + std::string oohString = gSavedSettings.getString("LipSyncOoh"); + lipStringToF32s (oohString, sOoh, sOohs); + + std::string aahString = gSavedSettings.getString("LipSyncAah"); + lipStringToF32s (aahString, sAah, sAahs); + + std::string oohPowerString = gSavedSettings.getString("LipSyncOohPowerTransfer"); + lipStringToF32s (oohPowerString, sOohPowerTransfer, sOohPowerTransfers); + sOohPowerTransfersf = (F32) sOohPowerTransfers; + + std::string aahPowerString = gSavedSettings.getString("LipSyncAahPowerTransfer"); + lipStringToF32s (aahPowerString, sAahPowerTransfer, sAahPowerTransfers); + sAahPowerTransfersf = (F32) sAahPowerTransfers; + +}//--------------------------------------------------- + + +//--------------------------------------------------- +// convert a string of digits to an array of floats. +// the result for each digit is the value of the +// digit multiplied by 0.11 +//--------------------------------------------------- +void LLVoiceVisualizer::lipStringToF32s ( std::string& in_string, F32*& out_F32s, U32& count_F32s ) +{ + delete[] out_F32s; // get rid of the current array + + count_F32s = in_string.length(); + if (count_F32s == 0) + { + // we don't like zero length arrays + + count_F32s = 1; + out_F32s = new F32[1]; + out_F32s[0] = 0.0f; + } + else + { + out_F32s = new F32[count_F32s]; + + for (U32 i=0; i 9) + { + four_bits = 9; + } + out_F32s[i] = 0.11f * (F32) four_bits; + } + } + +}//--------------------------------------------------- + + +//-------------------------------------------------------------------------- +// find the amount to blend the ooh and aah mouth morphs +//-------------------------------------------------------------------------- +void LLVoiceVisualizer::lipSyncOohAah( F32& ooh, F32& aah ) +{ + if (sLipSyncEnabled && mCurrentlySpeaking) + { + U32 transfer_index = (U32) (sOohPowerTransfersf * mSpeakingAmplitude); + if (transfer_index >= sOohPowerTransfers) + { + transfer_index = sOohPowerTransfers - 1; + } + F32 transfer_ooh = sOohPowerTransfer[transfer_index]; + + transfer_index = (U32) (sAahPowerTransfersf * mSpeakingAmplitude); + if (transfer_index >= sAahPowerTransfers) + { + transfer_index = sAahPowerTransfers - 1; + } + F32 transfer_aah = sAahPowerTransfer[transfer_index]; + + F64 current_time = mTimer.getTotalSeconds(); + F64 elapsed_time = current_time - mStartTime; + U32 elapsed_frames = (U32) (elapsed_time * sOohAahRate); + U32 elapsed_oohs = elapsed_frames % sOohs; + U32 elapsed_aahs = elapsed_frames % sAahs; + + ooh = transfer_ooh * sOoh[elapsed_oohs]; + aah = transfer_aah * sAah[elapsed_aahs]; + + /* + LL_INFOS() << " elapsed frames " << elapsed_frames + << " ooh " << ooh + << " aah " << aah + << " transfer ooh" << transfer_ooh + << " transfer aah" << transfer_aah + << " start time " << mStartTime + << " current time " << current_time + << " elapsed time " << elapsed_time + << " elapsed oohs " << elapsed_oohs + << " elapsed aahs " << elapsed_aahs + << LL_ENDL; + */ + } + else + { + ooh = 0.0f; + aah = 0.0f; + } + +}//--------------------------------------------------- + + +//--------------------------------------------------- +// this method is inherited from HUD Effect +//--------------------------------------------------- +void LLVoiceVisualizer::render() +{ + if ( ! mVoiceEnabled ) + { + return; + } + + if ( mSoundSymbol.mActive ) + { + mPreviousTime = mCurrentTime; + mCurrentTime = mTimer.getTotalSeconds(); + + //--------------------------------------------------------------- + // set the sound symbol position over the source (avatar's head) + //--------------------------------------------------------------- + mSoundSymbol.mPosition = gAgent.getPosAgentFromGlobal(mPositionGlobal) + WORLD_UPWARD_DIRECTION * HEIGHT_ABOVE_HEAD; + + //--------------------------------------------------------------- + // some gl state + //--------------------------------------------------------------- + LLGLSPipelineAlpha alpha_blend; + LLGLDepthTest depth(GL_TRUE, GL_FALSE); + //LLGLDisable gls_stencil(GL_STENCIL_TEST); + + //------------------------------------------------------------- + // create coordinates of the geometry for the dot + //------------------------------------------------------------- + LLViewerCamera* camera = LLViewerCamera::getInstance(); + LLVector3 l = camera->getLeftAxis() * DOT_SIZE; + LLVector3 u = camera->getUpAxis() * DOT_SIZE; + + LLVector3 bottomLeft = mSoundSymbol.mPosition + l - u; + LLVector3 bottomRight = mSoundSymbol.mPosition - l - u; + LLVector3 topLeft = mSoundSymbol.mPosition + l + u; + LLVector3 topRight = mSoundSymbol.mPosition - l + u; + + //----------------------------- + // bind texture 0 (the dot) + //----------------------------- + gGL.getTexUnit(0)->bind(mSoundSymbol.mTexture[0]); + + //------------------------------------------------------------- + // now render the dot + //------------------------------------------------------------- + gGL.color4fv( LLColor4( 1.0f, 1.0f, 1.0f, DOT_OPACITY ).mV ); + + gGL.begin( LLRender::TRIANGLE_STRIP ); + gGL.texCoord2i( 0, 0 ); gGL.vertex3fv( bottomLeft.mV ); + gGL.texCoord2i( 1, 0 ); gGL.vertex3fv( bottomRight.mV ); + gGL.texCoord2i( 0, 1 ); gGL.vertex3fv( topLeft.mV ); + gGL.end(); + + gGL.begin( LLRender::TRIANGLE_STRIP ); + gGL.texCoord2i( 1, 0 ); gGL.vertex3fv( bottomRight.mV ); + gGL.texCoord2i( 1, 1 ); gGL.vertex3fv( topRight.mV ); + gGL.texCoord2i( 0, 1 ); gGL.vertex3fv( topLeft.mV ); + gGL.end(); + + + + //-------------------------------------------------------------------------------------- + // if currently speaking, trigger waves (1 through 6) based on speaking amplitude + //-------------------------------------------------------------------------------------- + if ( mCurrentlySpeaking ) + { + F32 min = 0.2f; + F32 max = 0.7f; + F32 fraction = ( mSpeakingAmplitude - min ) / ( max - min ); + + // in case mSpeakingAmplitude > max.... + if ( fraction > 1.0f ) + { + fraction = 1.0f; + } + + S32 level = 1 + (int)( fraction * ( NUM_VOICE_SYMBOL_WAVES - 2 ) ); + + for (int i=0; i EXPANSION_MAX ) + { + mSoundSymbol.mWaveExpansion[i] = 1.0f; + } + + //---------------------------------------------------------------------------------- + // create geometry for the wave billboard textures + //---------------------------------------------------------------------------------- + F32 width = i * WAVE_WIDTH_SCALE * mSoundSymbol.mWaveExpansion[i]; + F32 height = i * WAVE_HEIGHT_SCALE * mSoundSymbol.mWaveExpansion[i]; + + LLVector3 l = camera->getLeftAxis() * width; + LLVector3 u = camera->getUpAxis() * height; + + LLVector3 bottomLeft = mSoundSymbol.mPosition + l - u; + LLVector3 bottomRight = mSoundSymbol.mPosition - l - u; + LLVector3 topLeft = mSoundSymbol.mPosition + l + u; + LLVector3 topRight = mSoundSymbol.mPosition - l + u; + + gGL.color4fv( LLColor4( red, green, blue, mSoundSymbol.mWaveOpacity[i] ).mV ); + gGL.getTexUnit(0)->bind(mSoundSymbol.mTexture[i]); + + + //--------------------------------------------------- + // now, render the mofo + //--------------------------------------------------- + gGL.begin( LLRender::TRIANGLE_STRIP ); + gGL.texCoord2i( 0, 0 ); gGL.vertex3fv( bottomLeft.mV ); + gGL.texCoord2i( 1, 0 ); gGL.vertex3fv( bottomRight.mV ); + gGL.texCoord2i( 0, 1 ); gGL.vertex3fv( topLeft.mV ); + gGL.end(); + + gGL.begin( LLRender::TRIANGLE_STRIP ); + gGL.texCoord2i( 1, 0 ); gGL.vertex3fv( bottomRight.mV ); + gGL.texCoord2i( 1, 1 ); gGL.vertex3fv( topRight.mV ); + gGL.texCoord2i( 0, 1 ); gGL.vertex3fv( topLeft.mV ); + gGL.end(); + + } //if ( mSoundSymbol.mWaveActive[i] ) + + }// for loop + + }//if ( mSoundSymbol.mActive ) + +}//--------------------------------------------------- + +//--------------------------------------------------- +void LLVoiceVisualizer::setVoiceSourceWorldPosition( const LLVector3 &p ) +{ + mVoiceSourceWorldPosition = p; + +}//--------------------------------------------------- + +//--------------------------------------------------- +VoiceGesticulationLevel LLVoiceVisualizer::getCurrentGesticulationLevel() +{ + VoiceGesticulationLevel gesticulationLevel = VOICE_GESTICULATION_LEVEL_OFF; //default + + //----------------------------------------------------------------------------------------- + // Within the range of gesticulation amplitudes, the sound signal is split into + // three equal amplitude regimes, each specifying one of three gesticulation levels. + //----------------------------------------------------------------------------------------- + F32 range = mMaxGesticulationAmplitude - mMinGesticulationAmplitude; + + if ( mSpeakingAmplitude > mMinGesticulationAmplitude + range * 0.5f ) { gesticulationLevel = VOICE_GESTICULATION_LEVEL_HIGH; } + else if ( mSpeakingAmplitude > mMinGesticulationAmplitude + range * 0.25f ) { gesticulationLevel = VOICE_GESTICULATION_LEVEL_MEDIUM; } + else if ( mSpeakingAmplitude > mMinGesticulationAmplitude + range * 0.00000f ) { gesticulationLevel = VOICE_GESTICULATION_LEVEL_LOW; } + + return gesticulationLevel; + +}//--------------------------------------------------- + + + +//------------------------------------ +// Destructor +//------------------------------------ +LLVoiceVisualizer::~LLVoiceVisualizer() +{ +}//---------------------------------------------- + + +//--------------------------------------------------- +// "packData" is inherited from HUDEffect +//--------------------------------------------------- +void LLVoiceVisualizer::packData(LLMessageSystem *mesgsys) +{ + // Pack the default data + LLHUDEffect::packData(mesgsys); + + // TODO -- pack the relevant data for voice effects + // we'll come up with some cool configurations....TBD + //U8 packed_data[41]; + //mesgsys->addBinaryDataFast(_PREHASH_TypeData, packed_data, 41); + U8 packed_data = 0; + mesgsys->addBinaryDataFast(_PREHASH_TypeData, &packed_data, 1); +} + + +//--------------------------------------------------- +// "unpackData" is inherited from HUDEffect +//--------------------------------------------------- +void LLVoiceVisualizer::unpackData(LLMessageSystem *mesgsys, S32 blocknum) +{ + // TODO -- find the speaker, unpack binary data, set the properties of this effect + /* + LLHUDEffect::unpackData(mesgsys, blocknum); + LLUUID source_id; + LLUUID target_id; + S32 size = mesgsys->getSizeFast(_PREHASH_Effect, blocknum, _PREHASH_TypeData); + if (size != 1) + { + LL_WARNS() << "Voice effect with bad size " << size << LL_ENDL; + return; + } + mesgsys->getBinaryDataFast(_PREHASH_Effect, _PREHASH_TypeData, packed_data, 1, blocknum); + */ +} + + +//------------------------------------------------------------------ +// this method is inherited from HUD Effect +//------------------------------------------------------------------ +void LLVoiceVisualizer::markDead() +{ + mCurrentlySpeaking = false; + mVoiceEnabled = false; + mSoundSymbol.mActive = false; + + LLHUDEffect::markDead(); +}//------------------------------------------------------------------ diff --git a/indra/newview/llvoicevisualizer.h b/indra/newview/llvoicevisualizer.h index 3a357f4241..a44f60bd16 100644 --- a/indra/newview/llvoicevisualizer.h +++ b/indra/newview/llvoicevisualizer.h @@ -1,157 +1,157 @@ -/** - * @file llvoicevisualizer.h - * @brief Draws in-world speaking indicators. - * - * $LicenseInfo:firstyear=2000&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -//-------------------------------------------------------------------- -// -// VOICE VISUALIZER -// author: JJ Ventrella, Linden Lab -// (latest update to this info: Jan 18, 2007) -// -// The Voice Visualizer is responsible for taking realtime signals from actual users speaking and -// visualizing this speech in two forms: -// -// (1) as a dynamic sound symbol (also referred to as the "voice indicator" that appears over the avatar's head -// (2) as gesticulation events that are used to trigger avatr gestures -// -// The input for the voice visualizer is a continual stream of voice amplitudes. - -//----------------------------------------------------------------------------- -#ifndef LL_VOICE_VISUALIZER_H -#define LL_VOICE_VISUALIZER_H - -#include "llhudeffect.h" - -//----------------------------------------------------------------------------------------------- -// The values of voice gesticulation represent energy levels for avatar animation, based on -// amplitude surge events parsed from the voice signal. These are made available so that -// the appropriate kind of avatar animation can be triggered, and thereby simulate the physical -// motion effects of speech. It is recommended that multiple body parts be animated as well as -// lips, such as head, shoulders, and hands, with large gestures used when the energy level is high. -//----------------------------------------------------------------------------------------------- -enum VoiceGesticulationLevel -{ - VOICE_GESTICULATION_LEVEL_OFF = -1, - VOICE_GESTICULATION_LEVEL_LOW = 0, - VOICE_GESTICULATION_LEVEL_MEDIUM, - VOICE_GESTICULATION_LEVEL_HIGH, - NUM_VOICE_GESTICULATION_LEVELS -}; - -const static int NUM_VOICE_SYMBOL_WAVES = 7; - -//---------------------------------------------------- -// LLVoiceVisualizer class -//---------------------------------------------------- -class LLVoiceVisualizer : public LLHUDEffect -{ - //--------------------------------------------------- - // public methods - //--------------------------------------------------- - public: - LLVoiceVisualizer( const U8 type ); //constructor - ~LLVoiceVisualizer(); //destructor - - void setVoiceSourceWorldPosition( const LLVector3 &p ); // this should be the position of the speaking avatar's head - void setMinGesticulationAmplitude( F32 ); // the lower range of meaningful amplitude for setting gesticulation level - void setMaxGesticulationAmplitude( F32 ); // the upper range of meaningful amplitude for setting gesticulation level - void setStartSpeaking(); // tell me when the av starts speaking - void setVoiceEnabled( bool ); // tell me whether or not the user is voice enabled - void setSpeakingAmplitude( F32 ); // tell me how loud the av is speaking (ranges from 0 to 1) - void setStopSpeaking(); // tell me when the av stops speaking - bool getCurrentlySpeaking(); // the get for the above set - VoiceGesticulationLevel getCurrentGesticulationLevel(); // based on voice amplitude, I'll give you the current "energy level" of avatar speech - void lipSyncOohAah( F32& ooh, F32& aah ); - void render(); // inherited from HUD Effect - void packData(LLMessageSystem *mesgsys); // inherited from HUD Effect - void unpackData(LLMessageSystem *mesgsys, S32 blocknum); // inherited from HUD Effect - void markDead(); // inherited from HUD Effect - - //---------------------------------------------------------------------------------------------- - // "setMaxGesticulationAmplitude" and "setMinGesticulationAmplitude" allow for the tuning of the - // gesticulation level detector to be responsive to different kinds of signals. For instance, we - // may find that the average voice amplitude rarely exceeds 0.7 (in a range from 0 to 1), and - // therefore we may want to set 0.7 as the max, so we can more easily catch all the variance - // within that range. Also, we may find that there is often noise below a certain range like 0.1, - // and so we would want to set 0.1 as the min so as not to accidentally use this as signal. - //---------------------------------------------------------------------------------------------- - void setMaxGesticulationAmplitude(); - void setMinGesticulationAmplitude(); - - //--------------------------------------------------- - // private members - //--------------------------------------------------- - private: - static bool handleVoiceVisualizerPrefsChanged(const LLSD& newvalue); - static void setPreferences( ); - static void lipStringToF32s ( std::string& in_string, F32*& out_F32s, U32& count_F32s ); // convert a string of digits to an array of floats - - struct SoundSymbol - { - F32 mWaveExpansion [ NUM_VOICE_SYMBOL_WAVES ]; - bool mWaveActive [ NUM_VOICE_SYMBOL_WAVES ]; - F64 mWaveFadeOutStartTime [ NUM_VOICE_SYMBOL_WAVES ]; - F32 mWaveOpacity [ NUM_VOICE_SYMBOL_WAVES ]; - LLPointer mTexture [ NUM_VOICE_SYMBOL_WAVES ]; - bool mActive; - LLVector3 mPosition; - }; - - LLFrameTimer mTimer; // so I can ask the current time in seconds - F64 mStartTime; // time in seconds when speaking started - F64 mCurrentTime; // current time in seconds, captured every step - F64 mPreviousTime; // copy of "current time" from last frame - SoundSymbol mSoundSymbol; // the sound symbol that appears over the avatar's head - bool mVoiceEnabled; // if off, no rendering should happen - bool mCurrentlySpeaking; // is the user currently speaking? - LLVector3 mVoiceSourceWorldPosition; // give this to me every step - I need it to update the sound symbol - F32 mSpeakingAmplitude; // this should be set as often as possible when the user is speaking - F32 mMaxGesticulationAmplitude; // this is the upper-limit of the envelope of detectable gesticulation leves - F32 mMinGesticulationAmplitude; // this is the lower-limit of the envelope of detectable gesticulation leves - - //--------------------------------------------------- - // private static members - //--------------------------------------------------- - - static bool sLipSyncEnabled; // 0 disabled, 1 babble loop - static bool sPrefsInitialized; // the first instance will initialize the static members - static F32* sOoh; // the babble loop of amplitudes for the ooh morph - static F32* sAah; // the babble loop of amplitudes for the ooh morph - static U32 sOohs; // the number of entries in the ooh loop - static U32 sAahs; // the number of entries in the aah loop - static F32 sOohAahRate; // frames per second for the babble loop - static F32* sOohPowerTransfer; // the power transfer characteristics for the ooh amplitude - static U32 sOohPowerTransfers; // the number of entries in the ooh transfer characteristics - static F32 sOohPowerTransfersf; // the number of entries in the ooh transfer characteristics as a float - static F32* sAahPowerTransfer; // the power transfer characteristics for the aah amplitude - static U32 sAahPowerTransfers; // the number of entries in the aah transfer characteristics - static F32 sAahPowerTransfersf; // the number of entries in the aah transfer characteristics as a float - -};//----------------------------------------------------------------- - // end of LLVoiceVisualizer class -//------------------------------------------------------------------ - -#endif //LL_VOICE_VISUALIZER_H - +/** + * @file llvoicevisualizer.h + * @brief Draws in-world speaking indicators. + * + * $LicenseInfo:firstyear=2000&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +//-------------------------------------------------------------------- +// +// VOICE VISUALIZER +// author: JJ Ventrella, Linden Lab +// (latest update to this info: Jan 18, 2007) +// +// The Voice Visualizer is responsible for taking realtime signals from actual users speaking and +// visualizing this speech in two forms: +// +// (1) as a dynamic sound symbol (also referred to as the "voice indicator" that appears over the avatar's head +// (2) as gesticulation events that are used to trigger avatr gestures +// +// The input for the voice visualizer is a continual stream of voice amplitudes. + +//----------------------------------------------------------------------------- +#ifndef LL_VOICE_VISUALIZER_H +#define LL_VOICE_VISUALIZER_H + +#include "llhudeffect.h" + +//----------------------------------------------------------------------------------------------- +// The values of voice gesticulation represent energy levels for avatar animation, based on +// amplitude surge events parsed from the voice signal. These are made available so that +// the appropriate kind of avatar animation can be triggered, and thereby simulate the physical +// motion effects of speech. It is recommended that multiple body parts be animated as well as +// lips, such as head, shoulders, and hands, with large gestures used when the energy level is high. +//----------------------------------------------------------------------------------------------- +enum VoiceGesticulationLevel +{ + VOICE_GESTICULATION_LEVEL_OFF = -1, + VOICE_GESTICULATION_LEVEL_LOW = 0, + VOICE_GESTICULATION_LEVEL_MEDIUM, + VOICE_GESTICULATION_LEVEL_HIGH, + NUM_VOICE_GESTICULATION_LEVELS +}; + +const static int NUM_VOICE_SYMBOL_WAVES = 7; + +//---------------------------------------------------- +// LLVoiceVisualizer class +//---------------------------------------------------- +class LLVoiceVisualizer : public LLHUDEffect +{ + //--------------------------------------------------- + // public methods + //--------------------------------------------------- + public: + LLVoiceVisualizer( const U8 type ); //constructor + ~LLVoiceVisualizer(); //destructor + + void setVoiceSourceWorldPosition( const LLVector3 &p ); // this should be the position of the speaking avatar's head + void setMinGesticulationAmplitude( F32 ); // the lower range of meaningful amplitude for setting gesticulation level + void setMaxGesticulationAmplitude( F32 ); // the upper range of meaningful amplitude for setting gesticulation level + void setStartSpeaking(); // tell me when the av starts speaking + void setVoiceEnabled( bool ); // tell me whether or not the user is voice enabled + void setSpeakingAmplitude( F32 ); // tell me how loud the av is speaking (ranges from 0 to 1) + void setStopSpeaking(); // tell me when the av stops speaking + bool getCurrentlySpeaking(); // the get for the above set + VoiceGesticulationLevel getCurrentGesticulationLevel(); // based on voice amplitude, I'll give you the current "energy level" of avatar speech + void lipSyncOohAah( F32& ooh, F32& aah ); + void render(); // inherited from HUD Effect + void packData(LLMessageSystem *mesgsys); // inherited from HUD Effect + void unpackData(LLMessageSystem *mesgsys, S32 blocknum); // inherited from HUD Effect + void markDead(); // inherited from HUD Effect + + //---------------------------------------------------------------------------------------------- + // "setMaxGesticulationAmplitude" and "setMinGesticulationAmplitude" allow for the tuning of the + // gesticulation level detector to be responsive to different kinds of signals. For instance, we + // may find that the average voice amplitude rarely exceeds 0.7 (in a range from 0 to 1), and + // therefore we may want to set 0.7 as the max, so we can more easily catch all the variance + // within that range. Also, we may find that there is often noise below a certain range like 0.1, + // and so we would want to set 0.1 as the min so as not to accidentally use this as signal. + //---------------------------------------------------------------------------------------------- + void setMaxGesticulationAmplitude(); + void setMinGesticulationAmplitude(); + + //--------------------------------------------------- + // private members + //--------------------------------------------------- + private: + static bool handleVoiceVisualizerPrefsChanged(const LLSD& newvalue); + static void setPreferences( ); + static void lipStringToF32s ( std::string& in_string, F32*& out_F32s, U32& count_F32s ); // convert a string of digits to an array of floats + + struct SoundSymbol + { + F32 mWaveExpansion [ NUM_VOICE_SYMBOL_WAVES ]; + bool mWaveActive [ NUM_VOICE_SYMBOL_WAVES ]; + F64 mWaveFadeOutStartTime [ NUM_VOICE_SYMBOL_WAVES ]; + F32 mWaveOpacity [ NUM_VOICE_SYMBOL_WAVES ]; + LLPointer mTexture [ NUM_VOICE_SYMBOL_WAVES ]; + bool mActive; + LLVector3 mPosition; + }; + + LLFrameTimer mTimer; // so I can ask the current time in seconds + F64 mStartTime; // time in seconds when speaking started + F64 mCurrentTime; // current time in seconds, captured every step + F64 mPreviousTime; // copy of "current time" from last frame + SoundSymbol mSoundSymbol; // the sound symbol that appears over the avatar's head + bool mVoiceEnabled; // if off, no rendering should happen + bool mCurrentlySpeaking; // is the user currently speaking? + LLVector3 mVoiceSourceWorldPosition; // give this to me every step - I need it to update the sound symbol + F32 mSpeakingAmplitude; // this should be set as often as possible when the user is speaking + F32 mMaxGesticulationAmplitude; // this is the upper-limit of the envelope of detectable gesticulation leves + F32 mMinGesticulationAmplitude; // this is the lower-limit of the envelope of detectable gesticulation leves + + //--------------------------------------------------- + // private static members + //--------------------------------------------------- + + static bool sLipSyncEnabled; // 0 disabled, 1 babble loop + static bool sPrefsInitialized; // the first instance will initialize the static members + static F32* sOoh; // the babble loop of amplitudes for the ooh morph + static F32* sAah; // the babble loop of amplitudes for the ooh morph + static U32 sOohs; // the number of entries in the ooh loop + static U32 sAahs; // the number of entries in the aah loop + static F32 sOohAahRate; // frames per second for the babble loop + static F32* sOohPowerTransfer; // the power transfer characteristics for the ooh amplitude + static U32 sOohPowerTransfers; // the number of entries in the ooh transfer characteristics + static F32 sOohPowerTransfersf; // the number of entries in the ooh transfer characteristics as a float + static F32* sAahPowerTransfer; // the power transfer characteristics for the aah amplitude + static U32 sAahPowerTransfers; // the number of entries in the aah transfer characteristics + static F32 sAahPowerTransfersf; // the number of entries in the aah transfer characteristics as a float + +};//----------------------------------------------------------------- + // end of LLVoiceVisualizer class +//------------------------------------------------------------------ + +#endif //LL_VOICE_VISUALIZER_H + diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp index 42ea34b706..de2c0dfd6a 100644 --- a/indra/newview/llvoicevivox.cpp +++ b/indra/newview/llvoicevivox.cpp @@ -1,8102 +1,8102 @@ - /** - * @file LLVivoxVoiceClient.cpp - * @brief Implementation of LLVivoxVoiceClient class which is the interface to the voice client process. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include -#include "llvoicevivox.h" - -#include "llsdutil.h" - -// Linden library includes -#include "llavatarnamecache.h" -#include "llvoavatarself.h" -#include "llbufferstream.h" -#include "llfile.h" -#include "llmenugl.h" -#ifdef LL_USESYSTEMLIBS -# include "expat.h" -#else -# include "expat/expat.h" -#endif -#include "llcallbacklist.h" -#include "llviewerregion.h" -#include "llviewernetwork.h" // for gGridChoice -#include "llbase64.h" -#include "llviewercontrol.h" -#include "llappviewer.h" // for gDisconnected, gDisableVoice -#include "llprocess.h" - -// Viewer includes -#include "llmutelist.h" // to check for muted avatars -#include "llagent.h" -#include "llcachename.h" -#include "llimview.h" // for LLIMMgr -#include "llparcel.h" -#include "llviewerparcelmgr.h" -#include "llfirstuse.h" -#include "llspeakers.h" -#include "lltrans.h" -#include "llviewerwindow.h" -#include "llviewercamera.h" -#include "llversioninfo.h" - -#include "llviewernetwork.h" -#include "llnotificationsutil.h" - -#include "llcorehttputil.h" -#include "lleventfilter.h" - -#include "stringize.h" - -// for base64 decoding -#include "apr_base64.h" - -#define USE_SESSION_GROUPS 0 -#define VX_NULL_POSITION -2147483648.0 /*The Silence*/ - -extern LLMenuBarGL* gMenuBarView; -extern void handle_voice_morphing_subscribe(); - -namespace { - const F32 VOLUME_SCALE_VIVOX = 0.01f; - - const F32 SPEAKING_TIMEOUT = 1.f; - - static const std::string VOICE_SERVER_TYPE = "Vivox"; - - // Don't retry connecting to the daemon more frequently than this: - const F32 DAEMON_CONNECT_THROTTLE_SECONDS = 1.0f; - const int DAEMON_CONNECT_RETRY_MAX = 3; - - // Don't send positional updates more frequently than this: - const F32 UPDATE_THROTTLE_SECONDS = 0.5f; - - // Timeout for connection to Vivox - const F32 CONNECT_ATTEMPT_TIMEOUT = 300.0f; - const F32 CONNECT_DNS_TIMEOUT = 5.0f; - const int CONNECT_RETRY_MAX = 3; - - const F32 LOGIN_ATTEMPT_TIMEOUT = 30.0f; - const F32 LOGOUT_ATTEMPT_TIMEOUT = 5.0f; - const int LOGIN_RETRY_MAX = 3; - - const F32 PROVISION_RETRY_TIMEOUT = 2.0; - const int PROVISION_RETRY_MAX = 5; - - // Cosine of a "trivially" small angle - const F32 FOUR_DEGREES = 4.0f * (F_PI / 180.0f); - const F32 MINUSCULE_ANGLE_COS = (F32) cos(0.5f * FOUR_DEGREES); - - const F32 SESSION_JOIN_TIMEOUT = 30.0f; - - // Defines the maximum number of times(in a row) "stateJoiningSession" case for spatial channel is reached in stateMachine() - // which is treated as normal. The is the number of frames to wait for a channel join before giving up. This was changed - // from the original count of 50 for two reason. Modern PCs have higher frame rates and sometimes the SLVoice process - // backs up processing join requests. There is a log statement that records when channel joins take longer than 100 frames. - const int MAX_NORMAL_JOINING_SPATIAL_NUM = 1500; - - // How often to check for expired voice fonts in seconds - const F32 VOICE_FONT_EXPIRY_INTERVAL = 10.f; - // Time of day at which Vivox expires voice font subscriptions. - // Used to replace the time portion of received expiry timestamps. - static const std::string VOICE_FONT_EXPIRY_TIME = "T05:00:00Z"; - - // Maximum length of capture buffer recordings in seconds. - const F32 CAPTURE_BUFFER_MAX_TIME = 10.f; - - const int ERROR_VIVOX_OBJECT_NOT_FOUND = 1001; - const int ERROR_VIVOX_NOT_LOGGED_IN = 1007; -} - -static int scale_mic_volume(float volume) -{ - // incoming volume has the range [0.0 ... 2.0], with 1.0 as the default. - // Map it to Vivox levels as follows: 0.0 -> 30, 1.0 -> 50, 2.0 -> 70 - return 30 + (int)(volume * 20.0f); -} - -static int scale_speaker_volume(float volume) -{ - // incoming volume has the range [0.0 ... 1.0], with 0.5 as the default. - // Map it to Vivox levels as follows: 0.0 -> 30, 0.5 -> 50, 1.0 -> 70 - return 30 + (int)(volume * 40.0f); - -} - - -/////////////////////////////////////////////////////////////////////////////////////////////// - -class LLVivoxVoiceClientMuteListObserver : public LLMuteListObserver -{ - /* virtual */ void onChange() { LLVivoxVoiceClient::getInstance()->muteListChanged();} -}; - - -void LLVoiceVivoxStats::reset() -{ - mStartTime = -1.0f; - mConnectCycles = 0; - mConnectTime = -1.0f; - mConnectAttempts = 0; - mProvisionTime = -1.0f; - mProvisionAttempts = 0; - mEstablishTime = -1.0f; - mEstablishAttempts = 0; -} - -LLVoiceVivoxStats::LLVoiceVivoxStats() -{ - reset(); -} - -LLVoiceVivoxStats::~LLVoiceVivoxStats() -{ -} - -void LLVoiceVivoxStats::connectionAttemptStart() -{ - if (!mConnectAttempts) - { - mStartTime = LLTimer::getTotalTime(); - mConnectCycles++; - } - mConnectAttempts++; -} - -void LLVoiceVivoxStats::connectionAttemptEnd(bool success) -{ - if ( success ) - { - mConnectTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC; - } -} - -void LLVoiceVivoxStats::provisionAttemptStart() -{ - if (!mProvisionAttempts) - { - mStartTime = LLTimer::getTotalTime(); - } - mProvisionAttempts++; -} - -void LLVoiceVivoxStats::provisionAttemptEnd(bool success) -{ - if ( success ) - { - mProvisionTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC; - } -} - -void LLVoiceVivoxStats::establishAttemptStart() -{ - if (!mEstablishAttempts) - { - mStartTime = LLTimer::getTotalTime(); - } - mEstablishAttempts++; -} - -void LLVoiceVivoxStats::establishAttemptEnd(bool success) -{ - if ( success ) - { - mEstablishTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC; - } -} - -LLSD LLVoiceVivoxStats::read() -{ - LLSD stats(LLSD::emptyMap()); - - stats["connect_cycles"] = LLSD::Integer(mConnectCycles); - stats["connect_attempts"] = LLSD::Integer(mConnectAttempts); - stats["connect_time"] = LLSD::Real(mConnectTime); - - stats["provision_attempts"] = LLSD::Integer(mProvisionAttempts); - stats["provision_time"] = LLSD::Real(mProvisionTime); - - stats["establish_attempts"] = LLSD::Integer(mEstablishAttempts); - stats["establish_time"] = LLSD::Real(mEstablishTime); - - return stats; -} - -static LLVivoxVoiceClientMuteListObserver mutelist_listener; -static bool sMuteListListener_listening = false; - -/////////////////////////////////////////////////////////////////////////////////////////////// -static LLProcessPtr sGatewayPtr; -static LLEventStream sGatewayPump("VivoxDaemonPump", true); - -static bool isGatewayRunning() -{ - return sGatewayPtr && sGatewayPtr->isRunning(); -} - -static void killGateway() -{ - if (sGatewayPtr) - { - LL_DEBUGS("Voice") << "SLVoice " << sGatewayPtr->getStatusString() << LL_ENDL; - - sGatewayPump.stopListening("VivoxDaemonPump"); - sGatewayPtr->kill(__FUNCTION__); - sGatewayPtr=NULL; - } - else - { - LL_DEBUGS("Voice") << "no gateway" << LL_ENDL; - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////// - -bool LLVivoxVoiceClient::sShuttingDown = false; -bool LLVivoxVoiceClient::sConnected = false; -LLPumpIO *LLVivoxVoiceClient::sPump = nullptr; - -LLVivoxVoiceClient::LLVivoxVoiceClient() : - mSessionTerminateRequested(false), - mRelogRequested(false), - mTerminateDaemon(false), - mSpatialJoiningNum(0), - - mTuningMode(false), - mTuningEnergy(0.0f), - mTuningMicVolume(0), - mTuningMicVolumeDirty(true), - mTuningSpeakerVolume(50), // Set to 50 so the user can hear himself when he sets his mic volume - mTuningSpeakerVolumeDirty(true), - mDevicesListUpdated(false), - - mAreaVoiceDisabled(false), - mAudioSession(), // TBD - should be NULL - mAudioSessionChanged(false), - mNextAudioSession(), - - mCurrentParcelLocalID(0), - mConnectorEstablished(false), - mAccountLoggedIn(false), - mNumberOfAliases(0), - mCommandCookie(0), - mLoginRetryCount(0), - - mBuddyListMapPopulated(false), - mBlockRulesListReceived(false), - mAutoAcceptRulesListReceived(false), - - mCaptureDeviceDirty(false), - mRenderDeviceDirty(false), - mSpatialCoordsDirty(false), - mIsInitialized(false), - - mMuteMic(false), - mMuteMicDirty(false), - mFriendsListDirty(true), - - mEarLocation(0), - mSpeakerVolumeDirty(true), - mSpeakerMuteDirty(true), - mMicVolume(0), - mMicVolumeDirty(true), - - mVoiceEnabled(false), - mWriteInProgress(false), - - mLipSyncEnabled(false), - - mVoiceFontsReceived(false), - mVoiceFontsNew(false), - mVoiceFontListDirty(false), - - mCaptureBufferMode(false), - mCaptureBufferRecording(false), - mCaptureBufferRecorded(false), - mCaptureBufferPlaying(false), - mShutdownComplete(true), - mPlayRequestCount(0), - - mAvatarNameCacheConnection(), - mIsInTuningMode(false), - mIsInChannel(false), - mIsJoiningSession(false), - mIsWaitingForFonts(false), - mIsLoggingIn(false), - mIsLoggedIn(false), - mIsProcessingChannels(false), - mIsCoroutineActive(false), - mVivoxPump("vivoxClientPump") -{ - sShuttingDown = false; - sConnected = false; - sPump = nullptr; - - mSpeakerVolume = scale_speaker_volume(0); - - mVoiceVersion.serverVersion = ""; - mVoiceVersion.serverType = VOICE_SERVER_TYPE; - - // gMuteListp isn't set up at this point, so we defer this until later. -// gMuteListp->addObserver(&mutelist_listener); - - -#if LL_DARWIN || LL_LINUX - // HACK: THIS DOES NOT BELONG HERE - // When the vivox daemon dies, the next write attempt on our socket generates a SIGPIPE, which kills us. - // This should cause us to ignore SIGPIPE and handle the error through proper channels. - // This should really be set up elsewhere. Where should it go? - signal(SIGPIPE, SIG_IGN); - - // Since we're now launching the gateway with fork/exec instead of system(), we need to deal with zombie processes. - // Ignoring SIGCHLD should prevent zombies from being created. Alternately, we could use wait(), but I'd rather not do that. - signal(SIGCHLD, SIG_IGN); -#endif - - - gIdleCallbacks.addFunction(idle, this); -} - -//--------------------------------------------------- - -LLVivoxVoiceClient::~LLVivoxVoiceClient() -{ - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - sShuttingDown = true; -} - -//--------------------------------------------------- - -void LLVivoxVoiceClient::init(LLPumpIO *pump) -{ - // constructor will set up LLVoiceClient::getInstance() - sPump = pump; - -// LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro", -// boost::bind(&LLVivoxVoiceClient::voiceControlCoro, LLVivoxVoiceClient::getInstance())); - -} - -void LLVivoxVoiceClient::terminate() -{ - if (sShuttingDown) - { - return; - } - - // needs to be done manually here since we will not get another pass in - // coroutines... that mechanism is long since gone. - if (mIsLoggedIn) - { - logoutOfVivox(false); - } - - if(sConnected) - { - breakVoiceConnection(false); - sConnected = false; - } - else - { - mRelogRequested = false; - killGateway(); - } - - sShuttingDown = true; - sPump = NULL; -} - -//--------------------------------------------------- - -void LLVivoxVoiceClient::cleanUp() -{ - LL_DEBUGS("Voice") << LL_ENDL; - - deleteAllSessions(); - deleteAllVoiceFonts(); - deleteVoiceFontTemplates(); - LL_DEBUGS("Voice") << "exiting" << LL_ENDL; -} - -//--------------------------------------------------- - -const LLVoiceVersionInfo& LLVivoxVoiceClient::getVersion() -{ - return mVoiceVersion; -} - -//--------------------------------------------------- - -void LLVivoxVoiceClient::updateSettings() -{ - setVoiceEnabled(voiceEnabled()); - setEarLocation(gSavedSettings.getS32("VoiceEarLocation")); - - std::string inputDevice = gSavedSettings.getString("VoiceInputAudioDevice"); - setCaptureDevice(inputDevice); - std::string outputDevice = gSavedSettings.getString("VoiceOutputAudioDevice"); - setRenderDevice(outputDevice); - F32 mic_level = gSavedSettings.getF32("AudioLevelMic"); - setMicGain(mic_level); - setLipSyncEnabled(gSavedSettings.getBOOL("LipSyncEnabled")); -} - -///////////////////////////// -// utility functions - -bool LLVivoxVoiceClient::writeString(const std::string &str) -{ - bool result = false; - LL_DEBUGS("LowVoice") << "sending:\n" << str << LL_ENDL; - - if(sConnected) - { - apr_status_t err; - apr_size_t size = (apr_size_t)str.size(); - apr_size_t written = size; - - //MARK: Turn this on to log outgoing XML - // LL_DEBUGS("Voice") << "sending: " << str << LL_ENDL; - - // check return code - sockets will fail (broken, etc.) - err = apr_socket_send( - mSocket->getSocket(), - (const char*)str.data(), - &written); - - if(err == 0 && written == size) - { - // Success. - result = true; - } - else if (err == 0 && written != size) { - // Did a short write, log it for now - LL_WARNS("Voice") << ") short write on socket sending data to vivox daemon." << "Sent " << written << "bytes instead of " << size <getExpandedFilename(LL_PATH_LOGS, ""); - - // Transition to stateConnectorStarted when the connector handle comes back. - std::string vivoxLogLevel = gSavedSettings.getString("VivoxDebugLevel"); - if ( vivoxLogLevel.empty() ) - { - vivoxLogLevel = "0"; - } - LL_DEBUGS("Voice") << "creating connector with log level " << vivoxLogLevel << LL_ENDL; - - stream - << "" - << "V2 SDK" - << "" << mVoiceAccountServerURI << "" - << "Normal" - << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" - << "" - << "" << logdir << "" - << "Connector" - << ".log" - << "" << vivoxLogLevel << "" - << "" - << "" << LLVersionInfo::instance().getChannel() << " " << LLVersionInfo::instance().getVersion() << "" - //<< "" //Name can cause problems per vivox. - << "12" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::connectorShutdown() -{ - if(mConnectorEstablished) - { - std::ostringstream stream; - stream - << "" - << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" - << "" - << "\n\n\n"; - - mShutdownComplete = false; - mConnectorEstablished = false; - - writeString(stream.str()); - } - else - { - mShutdownComplete = true; - } -} - -void LLVivoxVoiceClient::userAuthorized(const std::string& user_id, const LLUUID &agentID) -{ - - mAccountDisplayName = user_id; - - LL_INFOS("Voice") << "name \"" << mAccountDisplayName << "\" , ID " << agentID << LL_ENDL; - - mAccountName = nameFromID(agentID); -} - -void LLVivoxVoiceClient::setLoginInfo( - const std::string& account_name, - const std::string& password, - const std::string& voice_sip_uri_hostname, - const std::string& voice_account_server_uri) -{ - mVoiceSIPURIHostName = voice_sip_uri_hostname; - mVoiceAccountServerURI = voice_account_server_uri; - - if(mAccountLoggedIn) - { - // Already logged in. - LL_WARNS("Voice") << "Called while already logged in." << LL_ENDL; - - // Don't process another login. - return; - } - else if ( account_name != mAccountName ) - { - LL_WARNS("Voice") << "Mismatched account name! " << account_name - << " instead of " << mAccountName << LL_ENDL; - } - else - { - mAccountPassword = password; - } - - std::string debugSIPURIHostName = gSavedSettings.getString("VivoxDebugSIPURIHostName"); - - if( !debugSIPURIHostName.empty() ) - { - LL_INFOS("Voice") << "Overriding account server based on VivoxDebugSIPURIHostName: " - << debugSIPURIHostName << LL_ENDL; - mVoiceSIPURIHostName = debugSIPURIHostName; - } - - if( mVoiceSIPURIHostName.empty() ) - { - // we have an empty account server name - // so we fall back to hardcoded defaults - - if(LLGridManager::getInstance()->isInProductionGrid()) - { - // Use the release account server - mVoiceSIPURIHostName = "bhr.vivox.com"; - } - else - { - // Use the development account server - mVoiceSIPURIHostName = "bhd.vivox.com"; - } - LL_INFOS("Voice") << "Defaulting SIP URI host: " - << mVoiceSIPURIHostName << LL_ENDL; - - } - - std::string debugAccountServerURI = gSavedSettings.getString("VivoxDebugVoiceAccountServerURI"); - - if( !debugAccountServerURI.empty() ) - { - LL_INFOS("Voice") << "Overriding account server based on VivoxDebugVoiceAccountServerURI: " - << debugAccountServerURI << LL_ENDL; - mVoiceAccountServerURI = debugAccountServerURI; - } - - if( mVoiceAccountServerURI.empty() ) - { - // If the account server URI isn't specified, construct it from the SIP URI hostname - mVoiceAccountServerURI = "https://www." + mVoiceSIPURIHostName + "/api2/"; - LL_INFOS("Voice") << "Inferring account server based on SIP URI Host name: " - << mVoiceAccountServerURI << LL_ENDL; - } -} - -void LLVivoxVoiceClient::idle(void* user_data) -{ -} - -//========================================================================= -// the following are methods to support the coroutine implementation of the -// voice connection and processing. They should only be called in the context -// of a coroutine. -// -// - -typedef enum e_voice_control_coro_state -{ - VOICE_STATE_ERROR = -1, - VOICE_STATE_DONE = 0, - VOICE_STATE_TP_WAIT, // entry point - VOICE_STATE_START_DAEMON, - VOICE_STATE_PROVISION_ACCOUNT, - VOICE_STATE_START_SESSION, - VOICE_STATE_SESSION_RETRY, - VOICE_STATE_SESSION_ESTABLISHED, - VOICE_STATE_WAIT_FOR_CHANNEL, - VOICE_STATE_DISCONNECT, - VOICE_STATE_WAIT_FOR_EXIT, -} EVoiceControlCoroState; - -void LLVivoxVoiceClient::voiceControlCoro() -{ - int state = 0; - try - { - // state is passed as a reference instead of being - // a member due to unresolved issues with coroutine - // surviving longer than LLVivoxVoiceClient - voiceControlStateMachine(state); - } - catch (const LLCoros::Stop&) - { - LL_DEBUGS("LLVivoxVoiceClient") << "Received a shutdown exception" << LL_ENDL; - } - catch (const LLContinueError&) - { - LOG_UNHANDLED_EXCEPTION("LLVivoxVoiceClient"); - } - catch (...) - { - // Ideally for Windows need to log SEH exception instead or to set SEH - // handlers but bugsplat shows local variables for windows, which should - // be enough - LL_WARNS("Voice") << "voiceControlStateMachine crashed in state " << state << LL_ENDL; - throw; - } -} - -void LLVivoxVoiceClient::voiceControlStateMachine(S32 &coro_state) -{ - if (sShuttingDown) - { - return; - } - - LL_DEBUGS("Voice") << "starting" << LL_ENDL; - mIsCoroutineActive = true; - LLCoros::set_consuming(true); - - U32 retry = 0; - - coro_state = VOICE_STATE_TP_WAIT; - - do - { - if (sShuttingDown) - { - // Vivox singleton performed the exit, logged out, - // cleaned sockets, gateway and no longer cares - // about state of coroutine, so just stop - return; - } - - switch (coro_state) - { - case VOICE_STATE_TP_WAIT: - // starting point for voice - if (gAgent.getTeleportState() != LLAgent::TELEPORT_NONE) - { - LL_DEBUGS("Voice") << "Suspending voiceControlCoro() momentarily for teleport. Tuning: " << mTuningMode << ". Relog: " << mRelogRequested << LL_ENDL; - llcoro::suspendUntilTimeout(1.0); - } - else - { - coro_state = VOICE_STATE_START_DAEMON; - } - break; - - case VOICE_STATE_START_DAEMON: - LL_DEBUGS("Voice") << "Launching daemon" << LL_ENDL; - LLVoiceVivoxStats::getInstance()->reset(); - if (startAndLaunchDaemon()) - { - coro_state = VOICE_STATE_PROVISION_ACCOUNT; - } - else - { - coro_state = VOICE_STATE_SESSION_RETRY; - } - break; - - case VOICE_STATE_PROVISION_ACCOUNT: - if (provisionVoiceAccount()) - { - coro_state = VOICE_STATE_START_SESSION; - } - else - { - coro_state = VOICE_STATE_SESSION_RETRY; - } - break; - - case VOICE_STATE_START_SESSION: - if (establishVoiceConnection()) - { - coro_state = VOICE_STATE_SESSION_ESTABLISHED; - } - else - { - coro_state = VOICE_STATE_SESSION_RETRY; - } - break; - - case VOICE_STATE_SESSION_RETRY: - giveUp(); // cleans sockets and session - if (mRelogRequested) - { - // We failed to connect, give it a bit time before retrying. - retry++; - F32 full_delay = llmin(5.f * (F32)retry, 60.f); - F32 current_delay = 0.f; - LL_INFOS("Voice") << "Voice failed to establish session after " << retry - << " tries. Will attempt to reconnect in " << full_delay - << " seconds" << LL_ENDL; - while (current_delay < full_delay && !sShuttingDown) - { - // Assuming that a second has passed is not accurate, - // but we don't need accurancy here, just to make sure - // that some time passed and not to outlive voice itself - current_delay++; - llcoro::suspendUntilTimeout(1.f); - } - coro_state = VOICE_STATE_WAIT_FOR_EXIT; - } - else - { - coro_state = VOICE_STATE_DONE; - } - break; - - case VOICE_STATE_SESSION_ESTABLISHED: - { - // enable/disable the automatic VAD and explicitly set the initial values of - // the VAD variables ourselves when it is off - see SL-15072 for more details - // note: we set the other parameters too even if the auto VAD is on which is ok - unsigned int vad_auto = gSavedSettings.getU32("VivoxVadAuto"); - unsigned int vad_hangover = gSavedSettings.getU32("VivoxVadHangover"); - unsigned int vad_noise_floor = gSavedSettings.getU32("VivoxVadNoiseFloor"); - unsigned int vad_sensitivity = gSavedSettings.getU32("VivoxVadSensitivity"); - setupVADParams(vad_auto, vad_hangover, vad_noise_floor, vad_sensitivity); - - // watch for changes to the VAD settings via Debug Settings UI and act on them accordingly - gSavedSettings.getControl("VivoxVadAuto")->getSignal()->connect(boost::bind(&LLVivoxVoiceClient::onVADSettingsChange, this)); - gSavedSettings.getControl("VivoxVadHangover")->getSignal()->connect(boost::bind(&LLVivoxVoiceClient::onVADSettingsChange, this)); - gSavedSettings.getControl("VivoxVadNoiseFloor")->getSignal()->connect(boost::bind(&LLVivoxVoiceClient::onVADSettingsChange, this)); - gSavedSettings.getControl("VivoxVadSensitivity")->getSignal()->connect(boost::bind(&LLVivoxVoiceClient::onVADSettingsChange, this)); - - if (mTuningMode) - { - performMicTuning(); - } - - coro_state = VOICE_STATE_WAIT_FOR_CHANNEL; - } - break; - - case VOICE_STATE_WAIT_FOR_CHANNEL: - waitForChannel(); // todo: split into more states like login/fonts - coro_state = VOICE_STATE_DISCONNECT; - break; - - case VOICE_STATE_DISCONNECT: - LL_DEBUGS("Voice") << "lost channel RelogRequested=" << mRelogRequested << LL_ENDL; - endAndDisconnectSession(); - retry = 0; // Connected without issues - coro_state = VOICE_STATE_WAIT_FOR_EXIT; - break; - - case VOICE_STATE_WAIT_FOR_EXIT: - if (isGatewayRunning()) - { - LL_INFOS("Voice") << "waiting for SLVoice to exit" << LL_ENDL; - llcoro::suspendUntilTimeout(1.0); - } - else if (mRelogRequested && mVoiceEnabled) - { - LL_INFOS("Voice") << "will attempt to reconnect to voice" << LL_ENDL; - coro_state = VOICE_STATE_TP_WAIT; - } - else - { - coro_state = VOICE_STATE_DONE; - } - break; - - case VOICE_STATE_DONE: - break; - } - } while (coro_state > 0); - - if (sShuttingDown) - { - // LLVivoxVoiceClient might be already dead - return; - } - - mIsCoroutineActive = false; - LL_INFOS("Voice") << "exiting" << LL_ENDL; -} - -bool LLVivoxVoiceClient::endAndDisconnectSession() -{ - LL_DEBUGS("Voice") << LL_ENDL; - - breakVoiceConnection(true); - - killGateway(); - - return true; -} - -bool LLVivoxVoiceClient::callbackEndDaemon(const LLSD& data) -{ - if (!sShuttingDown && mVoiceEnabled) - { - LL_WARNS("Voice") << "SLVoice terminated " << ll_stream_notation_sd(data) << LL_ENDL; - terminateAudioSession(false); - closeSocket(); - cleanUp(); - LLVoiceClient::getInstance()->setUserPTTState(false); - gAgent.setVoiceConnected(false); - mRelogRequested = true; - } - sGatewayPump.stopListening("VivoxDaemonPump"); - return false; -} - -bool LLVivoxVoiceClient::startAndLaunchDaemon() -{ - //--------------------------------------------------------------------- - if (!voiceEnabled()) - { - // Voice is locked out, we must not launch the vivox daemon. - LL_WARNS("Voice") << "voice disabled; not starting daemon" << LL_ENDL; - return false; - } - - if (!isGatewayRunning()) - { -#ifndef VIVOXDAEMON_REMOTEHOST - // Launch the voice daemon -#if LL_WINDOWS - // On windows use exe (not work or RO) directory - std::string exe_path = gDirUtilp->getExecutableDir(); - gDirUtilp->append(exe_path, "SLVoice.exe"); -#elif LL_DARWIN - // On MAC use resource directory - std::string exe_path = gDirUtilp->getAppRODataDir(); - gDirUtilp->append(exe_path, "SLVoice"); -#else - std::string exe_path = gDirUtilp->getExecutableDir(); - gDirUtilp->append(exe_path, "SLVoice"); -#endif - // See if the vivox executable exists - llstat s; - if (!LLFile::stat(exe_path, &s)) - { - // vivox executable exists. Build the command line and launch the daemon. - LLProcess::Params params; - params.executable = exe_path; - - // VOICE-88: Cycle through [portbase..portbase+portrange) on - // successive tries because attempting to relaunch (after manually - // disabling and then re-enabling voice) with the same port can - // cause SLVoice's bind() call to fail with EADDRINUSE. We expect - // that eventually the OS will time out previous ports, which is - // why we cycle instead of incrementing indefinitely. - - static LLCachedControl portbase(gSavedSettings, "VivoxVoicePort"); - static LLCachedControl host(gSavedSettings, "VivoxVoiceHost"); - static LLCachedControl loglevel(gSavedSettings, "VivoxDebugLevel"); - static LLCachedControl log_folder(gSavedSettings, "VivoxLogDirectory"); - static LLCachedControl shutdown_timeout(gSavedSettings, "VivoxShutdownTimeout"); - static const U32 portrange = 100; - static U32 portoffset = 0; - U32 port = 0; - - if (LLAppViewer::instance()->isSecondInstance()) - { - // Ideally need to know amount of instances and - // to increment instance_offset on EADDRINUSE. - // But for now just use rand - static U32 instance_offset = portrange * ll_rand(20); - port = portbase + portoffset + instance_offset; - } - else - { - // leave main thread with exclusive port set - port = portbase + portoffset; - } - portoffset = (portoffset + 1) % portrange; - params.args.add("-i"); - params.args.add(STRINGIZE(host() << ':' << port)); - - params.args.add("-ll"); - if (loglevel().empty()) - { - params.args.add("0"); - } - else - { - params.args.add(loglevel); - } - - params.args.add("-lf"); - if (log_folder().empty()) - { - params.args.add(gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "")); - } - else - { - params.args.add(log_folder); - } - - // set log file basename and .log - params.args.add("-lp"); - params.args.add("SLVoice"); - params.args.add("-ls"); - params.args.add(".log"); - - // rotate any existing log - std::string new_log = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SLVoice.log"); - std::string old_log = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SLVoice.old"); - if (gDirUtilp->fileExists(new_log)) - { - LLFile::rename(new_log, old_log); - } - - if (!shutdown_timeout().empty()) - { - params.args.add("-st"); - params.args.add(shutdown_timeout); - } - params.cwd = gDirUtilp->getAppRODataDir(); - -# ifdef VIVOX_HANDLE_ARGS - params.args.add("-ah"); - params.args.add(LLVivoxSecurity::getInstance()->accountHandle()); - - params.args.add("-ch"); - params.args.add(LLVivoxSecurity::getInstance()->connectorHandle()); -# endif // VIVOX_HANDLE_ARGS - - params.postend = sGatewayPump.getName(); - sGatewayPump.listen("VivoxDaemonPump", boost::bind(&LLVivoxVoiceClient::callbackEndDaemon, this, _1)); - - LL_INFOS("Voice") << "Launching SLVoice" << LL_ENDL; - LL_DEBUGS("Voice") << "SLVoice params " << params << LL_ENDL; - - sGatewayPtr = LLProcess::create(params); - - mDaemonHost = LLHost(host().c_str(), port); - } - else - { - LL_WARNS("Voice") << exe_path << " not found." << LL_ENDL; - return false; - } -#else - // SLIM SDK: port changed from 44124 to 44125. - // We can connect to a client gateway running on another host. This is useful for testing. - // To do this, launch the gateway on a nearby host like this: - // vivox-gw.exe -p tcp -i 0.0.0.0:44125 - // and put that host's IP address here. - mDaemonHost = LLHost(gSavedSettings.getString("VivoxVoiceHost"), gSavedSettings.getU32("VivoxVoicePort")); -#endif - - // Dirty the states we'll need to sync with the daemon when it comes up. - mMuteMicDirty = true; - mMicVolumeDirty = true; - mSpeakerVolumeDirty = true; - mSpeakerMuteDirty = true; - // These only need to be set if they're not default (i.e. empty string). - mCaptureDeviceDirty = !mCaptureDevice.empty(); - mRenderDeviceDirty = !mRenderDevice.empty(); - - mMainSessionGroupHandle.clear(); - } - else - { - LL_DEBUGS("Voice") << " gateway running; not attempting to start" << LL_ENDL; - } - - //--------------------------------------------------------------------- - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - - LL_DEBUGS("Voice") << "Connecting to vivox daemon:" << mDaemonHost << LL_ENDL; - - int retryCount(0); - LLVoiceVivoxStats::getInstance()->reset(); - while (!sConnected && !sShuttingDown && retryCount++ <= DAEMON_CONNECT_RETRY_MAX) - { - LLVoiceVivoxStats::getInstance()->connectionAttemptStart(); - LL_DEBUGS("Voice") << "Attempting to connect to vivox daemon: " << mDaemonHost << LL_ENDL; - closeSocket(); - if (!mSocket) - { - mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP); - } - - sConnected = mSocket->blockingConnect(mDaemonHost); - LLVoiceVivoxStats::getInstance()->connectionAttemptEnd(sConnected); - if (!sConnected) - { - llcoro::suspendUntilTimeout(DAEMON_CONNECT_THROTTLE_SECONDS); - } - } - - //--------------------------------------------------------------------- - if (sShuttingDown && !sConnected) - { - return false; - } - - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - - while (!sPump && !sShuttingDown) - { // Can't use the pump until we have it available. - llcoro::suspend(); - } - - if (sShuttingDown) - { - return false; - } - - // MBW -- Note to self: pumps and pipes examples in - // indra/test/io.cpp - // indra/test/llpipeutil.{cpp|h} - - // Attach the pumps and pipes - - LLPumpIO::chain_t readChain; - - readChain.push_back(LLIOPipe::ptr_t(new LLIOSocketReader(mSocket))); - readChain.push_back(LLIOPipe::ptr_t(new LLVivoxProtocolParser())); - - - sPump->addChain(readChain, NEVER_CHAIN_EXPIRY_SECS); - - - //--------------------------------------------------------------------- - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - - // Initial devices query - getCaptureDevicesSendMessage(); - getRenderDevicesSendMessage(); - - mLoginRetryCount = 0; - - return true; -} - -bool LLVivoxVoiceClient::provisionVoiceAccount() -{ - LL_INFOS("Voice") << "Provisioning voice account." << LL_ENDL; - - while ((!gAgent.getRegion() || !gAgent.getRegion()->capabilitiesReceived()) && !sShuttingDown) - { - LL_DEBUGS("Voice") << "no capabilities for voice provisioning; waiting " << LL_ENDL; - // *TODO* Pump a message for wake up. - llcoro::suspend(); - } - - if (sShuttingDown) - { - return false; - } - - std::string url = gAgent.getRegionCapability("ProvisionVoiceAccountRequest"); - - LL_DEBUGS("Voice") << "region ready for voice provisioning; url=" << url << LL_ENDL; - - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("voiceAccountProvision", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); - int retryCount(0); - - LLSD result; - bool provisioned = false; - do - { - LLVoiceVivoxStats::getInstance()->provisionAttemptStart(); - result = httpAdapter->postAndSuspend(httpRequest, url, LLSD(), httpOpts); - - if (sShuttingDown) - { - return false; - } - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (status == LLCore::HttpStatus(404)) - { - F32 timeout = pow(PROVISION_RETRY_TIMEOUT, static_cast(retryCount)); - LL_WARNS("Voice") << "Provision CAP 404. Retrying in " << timeout << " seconds. Retries: " << (S32)retryCount << LL_ENDL; - llcoro::suspendUntilTimeout(timeout); - - if (sShuttingDown) - { - return false; - } - } - else if (!status) - { - LL_WARNS("Voice") << "Unable to provision voice account." << LL_ENDL; - LLVoiceVivoxStats::getInstance()->provisionAttemptEnd(false); - return false; - } - else - { - provisioned = true; - } - } while (!provisioned && ++retryCount <= PROVISION_RETRY_MAX && !sShuttingDown); - - if (sShuttingDown && !provisioned) - { - return false; - } - - LLVoiceVivoxStats::getInstance()->provisionAttemptEnd(provisioned); - if (!provisioned) - { - LL_WARNS("Voice") << "Could not access voice provision cap after " << retryCount << " attempts." << LL_ENDL; - return false; - } - - std::string voiceSipUriHostname; - std::string voiceAccountServerUri; - std::string voiceUserName = result["username"].asString(); - std::string voicePassword = result["password"].asString(); - - if (result.has("voice_sip_uri_hostname")) - { - voiceSipUriHostname = result["voice_sip_uri_hostname"].asString(); - } - - // this key is actually misnamed -- it will be an entire URI, not just a hostname. - if (result.has("voice_account_server_name")) - { - voiceAccountServerUri = result["voice_account_server_name"].asString(); - } - - LL_DEBUGS("Voice") << "ProvisionVoiceAccountRequest response" - << " user " << (voiceUserName.empty() ? "not set" : "set") - << " password " << (voicePassword.empty() ? "not set" : "set") - << " sip uri " << voiceSipUriHostname - << " account uri " << voiceAccountServerUri - << LL_ENDL; - - setLoginInfo(voiceUserName, voicePassword, voiceSipUriHostname, voiceAccountServerUri); - - return true; -} - -bool LLVivoxVoiceClient::establishVoiceConnection() -{ - if (!mVoiceEnabled && mIsInitialized) - { - LL_WARNS("Voice") << "cannot establish connection; enabled "<establishAttemptStart(); - connectorCreate(); - do - { - result = llcoro::suspendUntilEventOn(mVivoxPump); - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - - if (result.has("connector")) - { - connected = LLSD::Boolean(result["connector"]); - LLVoiceVivoxStats::getInstance()->establishAttemptEnd(connected); - if (!connected) - { - if (result.has("retry") && ++retries <= CONNECT_RETRY_MAX && !sShuttingDown) - { - F32 timeout = LLSD::Real(result["retry"]); - timeout *= retries; - LL_INFOS("Voice") << "Retry connection to voice service in " << timeout << " seconds" << LL_ENDL; - llcoro::suspendUntilTimeout(timeout); - - if (mVoiceEnabled) // user may have switched it off - { - // try again - LLVoiceVivoxStats::getInstance()->establishAttemptStart(); - connectorCreate(); - } - else - { - // stop if they've turned off voice - giving_up = true; - } - } - else - { - giving_up=true; - } - } - } - LL_DEBUGS("Voice") << (connected ? "" : "not ") << "connected, " - << (giving_up ? "" : "not ") << "giving up" - << LL_ENDL; - } while (!connected && !giving_up && !sShuttingDown); - - if (giving_up) - { - LLSD args; - args["HOSTID"] = LLURI(mVoiceAccountServerURI).authority(); - LLNotificationsUtil::add("NoVoiceConnect", args); - } - - return connected; -} - -bool LLVivoxVoiceClient::breakVoiceConnection(bool corowait) -{ - LL_DEBUGS("Voice") << "( wait=" << corowait << ")" << LL_ENDL; - bool retval(true); - - mShutdownComplete = false; - connectorShutdown(); - - if (corowait) - { - LLSD timeoutResult(LLSDMap("connector", "timeout")); - - LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult); - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - - retval = result.has("connector"); - } - else - { - mRelogRequested = false; //stop the control coro - // If we are not doing a corowait then we must sleep until the connector has responded - // otherwise we may very well close the socket too early. -#if LL_WINDOWS - if (!mShutdownComplete) - { - // The situation that brings us here is a call from ::terminate() - // At this point message system is already down so we can't wait for - // the message, yet we need to receive "connector shutdown response". - // Either wait a bit and emulate it or check gMessageSystem for specific message - _sleep(1000); - if (sConnected) - { - sConnected = false; - LLSD vivoxevent(LLSDMap("connector", LLSD::Boolean(false))); - mVivoxPump.post(vivoxevent); - } - mShutdownComplete = true; - } -#endif - } - - LL_DEBUGS("Voice") << "closing SLVoice socket" << LL_ENDL; - closeSocket(); // Need to do this now -- bad things happen if the destructor does it later. - cleanUp(); - sConnected = false; - - return retval; -} - -bool LLVivoxVoiceClient::loginToVivox() -{ - LLSD timeoutResult(LLSDMap("login", "timeout")); - - int loginRetryCount(0); - - bool response_ok(false); - bool account_login(false); - bool send_login(true); - - do - { - mIsLoggingIn = true; - if (send_login) - { - loginSendMessage(); - send_login = false; - } - - LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGIN_ATTEMPT_TIMEOUT, timeoutResult); - - if (sShuttingDown) - { - return false; - } - - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - - if (result.has("login")) - { - std::string loginresp = result["login"]; - - if (((loginresp == "retry") || (loginresp == "timeout")) && !sShuttingDown) - { - LL_WARNS("Voice") << "login failed with status '" << loginresp << "' " - << " count " << loginRetryCount << "/" << LOGIN_RETRY_MAX - << LL_ENDL; - if (++loginRetryCount > LOGIN_RETRY_MAX) - { - // We've run out of retries - tell the user - LL_WARNS("Voice") << "too many login retries (" << loginRetryCount << "); giving up." << LL_ENDL; - LLSD args; - args["HOSTID"] = LLURI(mVoiceAccountServerURI).authority(); - mTerminateDaemon = true; - LLNotificationsUtil::add("NoVoiceConnect", args); - - mIsLoggingIn = false; - return false; - } - response_ok = false; - account_login = false; - send_login = true; - - // an exponential backoff gets too long too quickly; stretch it out, but not too much - F32 timeout = loginRetryCount * LOGIN_ATTEMPT_TIMEOUT; - - // tell the user there is a problem - LL_WARNS("Voice") << "login " << loginresp << " will retry login in " << timeout << " seconds." << LL_ENDL; - - if (!sShuttingDown) - { - // Todo: this is way to long, viewer can get stuck waiting during shutdown - // either make it listen to pump or split in smaller waits with checks for shutdown - llcoro::suspendUntilTimeout(timeout); - } - } - else if (loginresp == "failed") - { - mIsLoggingIn = false; - return false; - } - else if (loginresp == "response_ok") - { - response_ok = true; - } - else if (loginresp == "account_login") - { - account_login = true; - } - else if (sShuttingDown) - { - mIsLoggingIn = false; - return false; - } - } - - } while ((!response_ok || !account_login) && !sShuttingDown); - - if (sShuttingDown) - { - return false; - } - - mRelogRequested = false; - mIsLoggedIn = true; - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN); - - // Set up the mute list observer if it hasn't been set up already. - if ((!sMuteListListener_listening)) - { - LLMuteList::getInstance()->addObserver(&mutelist_listener); - sMuteListListener_listening = true; - } - - // Set the initial state of mic mute, local speaker volume, etc. - sendLocalAudioUpdates(); - mIsLoggingIn = false; - - return true; -} - -void LLVivoxVoiceClient::logoutOfVivox(bool wait) -{ - if (mIsLoggedIn) - { - // Ensure that we'll re-request provisioning before logging in again - mAccountPassword.clear(); - mVoiceAccountServerURI.clear(); - - logoutSendMessage(); - - if (wait) - { - LLSD timeoutResult(LLSDMap("logout", "timeout")); - LLSD result; - - do - { - LL_DEBUGS("Voice") - << "waiting for logout response on " - << mVivoxPump.getName() - << LL_ENDL; - - result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult); - - if (sShuttingDown) - { - break; - } - - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - // Don't get confused by prior queued events -- note that it's - // very important that mVivoxPump is an LLEventMailDrop, which - // does queue events. - } while (! result["logout"]); - } - else - { - LL_DEBUGS("Voice") << "not waiting for logout" << LL_ENDL; - } - - mIsLoggedIn = false; - } -} - - -bool LLVivoxVoiceClient::retrieveVoiceFonts() -{ - // Request the set of available voice fonts. - refreshVoiceEffectLists(true); - - mIsWaitingForFonts = true; - LLSD result; - do - { - result = llcoro::suspendUntilEventOn(mVivoxPump); - - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - if (result.has("voice_fonts")) - break; - } while (true); - mIsWaitingForFonts = false; - - mVoiceFontExpiryTimer.start(); - mVoiceFontExpiryTimer.setTimerExpirySec(VOICE_FONT_EXPIRY_INTERVAL); - - return result["voice_fonts"].asBoolean(); -} - -bool LLVivoxVoiceClient::requestParcelVoiceInfo() -{ - //_INFOS("Voice") << "Requesting voice info for Parcel" << LL_ENDL; - - LLViewerRegion * region = gAgent.getRegion(); - if (region == NULL || !region->capabilitiesReceived()) - { - LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest capability not yet available, deferring" << LL_ENDL; - return false; - } - - // grab the cap. - std::string url = gAgent.getRegion()->getCapability("ParcelVoiceInfoRequest"); - if (url.empty()) - { - // Region dosn't have the cap. Stop probing. - LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest capability not available in this region" << LL_ENDL; - return false; - } - - // update the parcel - checkParcelChanged(true); - - LL_DEBUGS("Voice") << "sending ParcelVoiceInfoRequest (" << mCurrentRegionName << ", " << mCurrentParcelLocalID << ")" << LL_ENDL; - - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("parcelVoiceInfoRequest", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, LLSD()); - - if (sShuttingDown) - { - return false; - } - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized)) - { - // if a terminate request has been received, - // bail and go to the stateSessionTerminated - // state. If the cap request is still pending, - // the responder will check to see if we've moved - // to a new session and won't change any state. - LL_DEBUGS("Voice") << "terminate requested " << mSessionTerminateRequested - << " enabled " << mVoiceEnabled - << " initialized " << mIsInitialized - << LL_ENDL; - terminateAudioSession(true); - return false; - } - - if ((!status) || (mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized))) - { - if (mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized)) - { - LL_WARNS("Voice") << "Session terminated." << LL_ENDL; - } - - LL_WARNS("Voice") << "No voice on parcel" << LL_ENDL; - sessionTerminate(); - return false; - } - - std::string uri; - std::string credentials; - - if (result.has("voice_credentials")) - { - LLSD voice_credentials = result["voice_credentials"]; - if (voice_credentials.has("channel_uri")) - { - LL_DEBUGS("Voice") << "got voice channel uri" << LL_ENDL; - uri = voice_credentials["channel_uri"].asString(); - } - else - { - LL_WARNS("Voice") << "No voice channel uri" << LL_ENDL; - } - - if (voice_credentials.has("channel_credentials")) - { - LL_DEBUGS("Voice") << "got voice channel credentials" << LL_ENDL; - credentials = - voice_credentials["channel_credentials"].asString(); - } - else - { - LLVoiceChannel* channel = LLVoiceChannel::getCurrentVoiceChannel(); - if (channel != NULL) - { - if (channel->getSessionName().empty() && channel->getSessionID().isNull()) - { - if (LLViewerParcelMgr::getInstance()->allowAgentVoice()) - { - LL_WARNS("Voice") << "No channel credentials for default channel" << LL_ENDL; - } - } - else - { - LL_WARNS("Voice") << "No voice channel credentials" << LL_ENDL; - } - } - } - } - else - { - if (LLViewerParcelMgr::getInstance()->allowAgentVoice()) - { - LL_WARNS("Voice") << "No voice credentials" << LL_ENDL; - } - else - { - LL_DEBUGS("Voice") << "No voice credentials" << LL_ENDL; - } - } - - // set the spatial channel. If no voice credentials or uri are - // available, then we simply drop out of voice spatially. - return !setSpatialChannel(uri, credentials); -} - -bool LLVivoxVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession) -{ - mIsJoiningSession = true; - - sessionStatePtr_t oldSession = mAudioSession; - - LL_INFOS("Voice") << "Adding or joining voice session " << nextSession->mHandle << LL_ENDL; - - mAudioSession = nextSession; - mAudioSessionChanged = true; - if (!mAudioSession || !mAudioSession->mReconnect) - { - mNextAudioSession.reset(); - } - - // The old session may now need to be deleted. - reapSession(oldSession); - - if (mAudioSession) - { - if (!mAudioSession->mHandle.empty()) - { - // Connect to a session by session handle - - sessionMediaConnectSendMessage(mAudioSession); - } - else - { - // Connect to a session by URI - sessionCreateSendMessage(mAudioSession, true, false); - } - } - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING); - - llcoro::suspend(); - - if (sShuttingDown) - { - return false; - } - - LLSD result; - - if (mSpatialJoiningNum == MAX_NORMAL_JOINING_SPATIAL_NUM) - { - // Notify observers to let them know there is problem with voice - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); - LL_WARNS() << "There seems to be problem with connection to voice server. Disabling voice chat abilities." << LL_ENDL; - } - - // Increase mSpatialJoiningNum only for spatial sessions- it's normal to reach this case for - // example for p2p many times while waiting for response, so it can't be used to detect errors - if (mAudioSession && mAudioSession->mIsSpatial) - { - mSpatialJoiningNum++; - } - - if (!mVoiceEnabled && mIsInitialized) - { - LL_DEBUGS("Voice") << "Voice no longer enabled. Exiting" - << " enabled " << mVoiceEnabled - << " initialized " << mIsInitialized - << LL_ENDL; - mIsJoiningSession = false; - // User bailed out during connect -- jump straight to teardown. - terminateAudioSession(true); - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); - return false; - } - else if (mSessionTerminateRequested) - { - LL_DEBUGS("Voice") << "Terminate requested" << LL_ENDL; - if (mAudioSession && !mAudioSession->mHandle.empty()) - { - // Only allow direct exits from this state in p2p calls (for cancelling an invite). - // Terminating a half-connected session on other types of calls seems to break something in the vivox gateway. - if (mAudioSession->mIsP2P) - { - terminateAudioSession(true); - mIsJoiningSession = false; - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); - return false; - } - } - } - - bool added(true); - bool joined(false); - - LLSD timeoutResult(LLSDMap("session", "timeout")); - - // We are about to start a whole new session. Anything that MIGHT still be in our - // maildrop is going to be stale and cause us much wailing and gnashing of teeth. - // Just flush it all out and start new. - mVivoxPump.discard(); - - // It appears that I need to wait for BOTH the SessionGroup.AddSession response and the SessionStateChangeEvent with state 4 - // before continuing from this state. They can happen in either order, and if I don't wait for both, things can get stuck. - // For now, the SessionGroup.AddSession response handler sets mSessionHandle and the SessionStateChangeEvent handler transitions to stateSessionJoined. - // This is a cheap way to make sure both have happened before proceeding. - do - { - result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, SESSION_JOIN_TIMEOUT, timeoutResult); - - if (sShuttingDown) - { - return false; - } - - LL_INFOS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - if (result.has("session")) - { - if (!mAudioSession) - { - LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while session is not initialized." << LL_ENDL; - continue; - } - if (result.has("handle") && result["handle"] != mAudioSession->mHandle) - { - LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while waiting for \"" << mAudioSession->mHandle << "\"." << LL_ENDL; - continue; - } - - std::string message = result["session"].asString(); - - if ((message == "added") || (message == "created")) - { - added = true; - } - else if (message == "joined") - { - joined = true; - } - else if ((message == "failed") || (message == "removed") || (message == "timeout")) - { // we will get a removed message if a voice call is declined. - - if (message == "failed") - { - int reason = result["reason"].asInteger(); - LL_WARNS("Voice") << "Add and join failed for reason " << reason << LL_ENDL; - - if ( (reason == ERROR_VIVOX_NOT_LOGGED_IN) - || (reason == ERROR_VIVOX_OBJECT_NOT_FOUND)) - { - LL_DEBUGS("Voice") << "Requesting reprovision and login." << LL_ENDL; - requestRelog(); - } - } - else - { - LL_WARNS("Voice") << "session '" << message << "' " - << LL_ENDL; - } - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); - mIsJoiningSession = false; - return false; - } - } - } while (!added || !joined); - - mIsJoiningSession = false; - - if (mSpatialJoiningNum > 100) - { - LL_WARNS("Voice") << "There seems to be problem with connecting to a voice channel. Frames to join were " << mSpatialJoiningNum << LL_ENDL; - } - - mSpatialJoiningNum = 0; - - // Events that need to happen when a session is joined could go here. - // send an initial positional information immediately upon joining. - // - // do an initial update for position and the camera position, then send a - // positional update. - updatePosition(); - enforceTether(); - - // Dirty state that may need to be sync'ed with the daemon. - mMuteMicDirty = true; - mSpeakerVolumeDirty = true; - mSpatialCoordsDirty = true; - - sendPositionAndVolumeUpdate(); - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED); - - return true; -} - -bool LLVivoxVoiceClient::terminateAudioSession(bool wait) -{ - - if (mAudioSession) - { - LL_INFOS("Voice") << "terminateAudioSession(" << wait << ") Terminating current voice session " << mAudioSession->mHandle << LL_ENDL; - - if (mIsLoggedIn) - { - if (!mAudioSession->mHandle.empty()) - { - -#if RECORD_EVERYTHING - // HACK: for testing only - // Save looped recording - std::string savepath("/tmp/vivoxrecording"); - { - time_t now = time(NULL); - const size_t BUF_SIZE = 64; - char time_str[BUF_SIZE]; /* Flawfinder: ignore */ - - strftime(time_str, BUF_SIZE, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now)); - savepath += time_str; - } - recordingLoopSave(savepath); -#endif - - sessionMediaDisconnectSendMessage(mAudioSession); - - if (wait) - { - LLSD result; - do - { - LLSD timeoutResult(LLSDMap("session", "timeout")); - - result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult); - - if (sShuttingDown) - { - return false; - } - - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - if (result.has("session")) - { - if (result.has("handle")) - { - if (result["handle"] != mAudioSession->mHandle) - { - LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while waiting for \"" << mAudioSession->mHandle << "\"." << LL_ENDL; - continue; - } - } - - std::string message = result["session"].asString(); - if (message == "removed" || message == "timeout") - break; - } - } while (true); - - } - } - else - { - LL_WARNS("Voice") << "called with no session handle" << LL_ENDL; - } - } - else - { - LL_WARNS("Voice") << "Session " << mAudioSession->mHandle << " already terminated by logout." << LL_ENDL; - } - - sessionStatePtr_t oldSession = mAudioSession; - - mAudioSession.reset(); - // We just notified status observers about this change. Don't do it again. - mAudioSessionChanged = false; - - // The old session may now need to be deleted. - reapSession(oldSession); - } - else - { - LL_WARNS("Voice") << "terminateAudioSession(" << wait << ") with NULL mAudioSession" << LL_ENDL; - } - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); - - // Always reset the terminate request flag when we get here. - // Some slower PCs have a race condition where they can switch to an incoming P2P call faster than the state machine leaves - // the region chat. - mSessionTerminateRequested = false; - - bool status=((mVoiceEnabled || !mIsInitialized) && !mRelogRequested && !sShuttingDown); - LL_DEBUGS("Voice") << "exiting" - << " VoiceEnabled " << mVoiceEnabled - << " IsInitialized " << mIsInitialized - << " RelogRequested " << mRelogRequested - << " ShuttingDown " << (sShuttingDown ? "true" : "false") - << " returning " << status - << LL_ENDL; - return status; -} - - -typedef enum e_voice_wait_for_channel_state -{ - VOICE_CHANNEL_STATE_LOGIN = 0, // entry point - VOICE_CHANNEL_STATE_CHECK_EFFECTS, - VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING, - VOICE_CHANNEL_STATE_PROCESS_CHANNEL, - VOICE_CHANNEL_STATE_NEXT_CHANNEL_DELAY, - VOICE_CHANNEL_STATE_NEXT_CHANNEL_CHECK, - VOICE_CHANNEL_STATE_LOGOUT, - VOICE_CHANNEL_STATE_RELOG, - VOICE_CHANNEL_STATE_DONE, -} EVoiceWaitForChannelState; - -bool LLVivoxVoiceClient::waitForChannel() -{ - LL_INFOS("Voice") << "Waiting for channel" << LL_ENDL; - - EVoiceWaitForChannelState state = VOICE_CHANNEL_STATE_LOGIN; - - do - { - if (sShuttingDown) - { - // terminate() forcefully disconects voice, no need for cleanup - return false; - } - - switch (state) - { - case VOICE_CHANNEL_STATE_LOGIN: - if (!loginToVivox()) - { - return false; - } - state = VOICE_CHANNEL_STATE_CHECK_EFFECTS; - break; - - case VOICE_CHANNEL_STATE_CHECK_EFFECTS: - if (LLVoiceClient::instance().getVoiceEffectEnabled()) - { - retrieveVoiceFonts(); - - if (sShuttingDown) - { - return false; - } - - // Request the set of available voice fonts. - refreshVoiceEffectLists(false); - } - -#if USE_SESSION_GROUPS - // Rider: This code is completely unchanged from the original state machine - // It does not seem to be in active use... but I'd rather not rip it out. - // create the main session group - setState(stateCreatingSessionGroup); - sessionGroupCreateSendMessage(); -#endif - - state = VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING; - break; - - case VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING: - mIsProcessingChannels = true; - llcoro::suspend(); - state = VOICE_CHANNEL_STATE_PROCESS_CHANNEL; - break; - - case VOICE_CHANNEL_STATE_PROCESS_CHANNEL: - if (mTuningMode) - { - performMicTuning(); - } - else if (mCaptureBufferMode) - { - recordingAndPlaybackMode(); - } - else if (checkParcelChanged() || (mNextAudioSession == NULL)) - { - // the parcel is changed, or we have no pending audio sessions, - // so try to request the parcel voice info - // if we have the cap, we move to the appropriate state - requestParcelVoiceInfo(); //suspends for http reply - } - else if (sessionNeedsRelog(mNextAudioSession)) - { - LL_INFOS("Voice") << "Session requesting reprovision and login." << LL_ENDL; - requestRelog(); - break; - } - else if (mNextAudioSession) - { - sessionStatePtr_t joinSession = mNextAudioSession; - mNextAudioSession.reset(); - if (!runSession(joinSession)) //suspends - { - LL_DEBUGS("Voice") << "runSession returned false; leaving inner loop" << LL_ENDL; - break; - } - else - { - LL_DEBUGS("Voice") - << "runSession returned true to inner loop" - << " RelogRequested=" << mRelogRequested - << " VoiceEnabled=" << mVoiceEnabled - << LL_ENDL; - } - } - - state = VOICE_CHANNEL_STATE_NEXT_CHANNEL_DELAY; - break; - - case VOICE_CHANNEL_STATE_NEXT_CHANNEL_DELAY: - if (!mNextAudioSession) - { - llcoro::suspendUntilTimeout(1.0); - } - state = VOICE_CHANNEL_STATE_NEXT_CHANNEL_CHECK; - break; - - case VOICE_CHANNEL_STATE_NEXT_CHANNEL_CHECK: - if (mVoiceEnabled && !mRelogRequested) - { - state = VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING; - break; - } - else - { - mIsProcessingChannels = false; - LL_DEBUGS("Voice") - << "leaving inner waitForChannel loop" - << " RelogRequested=" << mRelogRequested - << " VoiceEnabled=" << mVoiceEnabled - << LL_ENDL; - state = VOICE_CHANNEL_STATE_LOGOUT; - break; - } - - case VOICE_CHANNEL_STATE_LOGOUT: - logoutOfVivox(true /*bool wait*/); - if (mRelogRequested) - { - state = VOICE_CHANNEL_STATE_RELOG; - } - else - { - state = VOICE_CHANNEL_STATE_DONE; - } - break; - - case VOICE_CHANNEL_STATE_RELOG: - LL_DEBUGS("Voice") << "Relog Requested, restarting provisioning" << LL_ENDL; - if (!provisionVoiceAccount()) - { - if (sShuttingDown) - { - return false; - } - LL_WARNS("Voice") << "provisioning voice failed; giving up" << LL_ENDL; - giveUp(); - return false; - } - if (mVoiceEnabled && mRelogRequested && isGatewayRunning()) - { - state = VOICE_CHANNEL_STATE_LOGIN; - } - else - { - state = VOICE_CHANNEL_STATE_DONE; - } - break; - case VOICE_CHANNEL_STATE_DONE: - LL_DEBUGS("Voice") - << "exiting" - << " RelogRequested=" << mRelogRequested - << " VoiceEnabled=" << mVoiceEnabled - << LL_ENDL; - return !sShuttingDown; - } - } while (true); -} - -bool LLVivoxVoiceClient::runSession(const sessionStatePtr_t &session) -{ - LL_INFOS("Voice") << "running new voice session " << session->mHandle << LL_ENDL; - - bool joined_session = addAndJoinSession(session); - - if (sShuttingDown) - { - return false; - } - - if (!joined_session) - { - notifyStatusObservers(LLVoiceClientStatusObserver::ERROR_UNKNOWN); - - if (mSessionTerminateRequested) - { - LL_DEBUGS("Voice") << "runSession terminate requested " << LL_ENDL; - terminateAudioSession(true); - } - // if a relog has been requested then addAndJoineSession - // failed in a spectacular way and we need to back out. - // If this is not the case then we were simply trying to - // make a call and the other party rejected it. - return !mRelogRequested; - } - - notifyParticipantObservers(); - notifyVoiceFontObservers(); - - LLSD timeoutEvent(LLSDMap("timeout", LLSD::Boolean(true))); - - mIsInChannel = true; - mMuteMicDirty = true; - - while (!sShuttingDown - && mVoiceEnabled - && isGatewayRunning() - && !mSessionTerminateRequested - && !mTuningMode) - { - sendCaptureAndRenderDevices(); // suspends - - if (sShuttingDown) - { - return false; - } - - if (mSessionTerminateRequested) - { - break; - } - - if (mAudioSession && mAudioSession->mParticipantsChanged) - { - mAudioSession->mParticipantsChanged = false; - notifyParticipantObservers(); - } - - if (!inSpatialChannel()) - { - // When in a non-spatial channel, never send positional updates. - mSpatialCoordsDirty = false; - } - else - { - updatePosition(); - - if (checkParcelChanged()) - { - // *RIDER: I think I can just return here if the parcel has changed - // and grab the new voice channel from the outside loop. - // - // if the parcel has changed, attempted to request the - // cap for the parcel voice info. If we can't request it - // then we don't have the cap URL so we do nothing and will - // recheck next time around - if (requestParcelVoiceInfo()) // suspends - { // The parcel voice URI has changed.. break out and reconnect. - break; - } - - if (sShuttingDown) - { - return false; - } - } - // Do the calculation that enforces the listener<->speaker tether (and also updates the real camera position) - enforceTether(); - } - sendPositionAndVolumeUpdate(); - - // Do notifications for expiring Voice Fonts. - if (mVoiceFontExpiryTimer.hasExpired()) - { - expireVoiceFonts(); - mVoiceFontExpiryTimer.setTimerExpirySec(VOICE_FONT_EXPIRY_INTERVAL); - } - - // send any requests to adjust mic and speaker settings if they have changed - sendLocalAudioUpdates(); - - mIsInitialized = true; - LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, UPDATE_THROTTLE_SECONDS, timeoutEvent); - - if (sShuttingDown) - { - return false; - } - - if (!result.has("timeout")) // logging the timeout event spams the log - { - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - } - if (result.has("session")) - { - if (result.has("handle")) - { - if (!mAudioSession) - { - LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while session is not initiated." << LL_ENDL; - continue; - } - if (result["handle"] != mAudioSession->mHandle) - { - LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while waiting for \"" << mAudioSession->mHandle << "\"." << LL_ENDL; - continue; - } - } - - std::string message = result["session"]; - - if (message == "removed") - { - LL_DEBUGS("Voice") << "session removed" << LL_ENDL; - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); - break; - } - } - else if (result.has("login")) - { - std::string message = result["login"]; - if (message == "account_logout") - { - LL_DEBUGS("Voice") << "logged out" << LL_ENDL; - mIsLoggedIn = false; - mRelogRequested = true; - break; - } - } - } - - if (sShuttingDown) - { - return false; - } - - mIsInChannel = false; - LL_DEBUGS("Voice") << "terminating at end of runSession" << LL_ENDL; - terminateAudioSession(true); - - return true; -} - -void LLVivoxVoiceClient::sendCaptureAndRenderDevices() -{ - if (mCaptureDeviceDirty || mRenderDeviceDirty) - { - std::ostringstream stream; - - buildSetCaptureDevice(stream); - buildSetRenderDevice(stream); - - if (!stream.str().empty()) - { - writeString(stream.str()); - } - - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - } -} - -void LLVivoxVoiceClient::recordingAndPlaybackMode() -{ - LL_INFOS("Voice") << "In voice capture/playback mode." << LL_ENDL; - - while (true) - { - LLSD command; - do - { - command = llcoro::suspendUntilEventOn(mVivoxPump); - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(command) << LL_ENDL; - } while (!command.has("recplay")); - - if (command["recplay"].asString() == "quit") - { - mCaptureBufferMode = false; - break; - } - else if (command["recplay"].asString() == "record") - { - voiceRecordBuffer(); - } - else if (command["recplay"].asString() == "playback") - { - voicePlaybackBuffer(); - } - } - - LL_INFOS("Voice") << "Leaving capture/playback mode." << LL_ENDL; - mCaptureBufferRecording = false; - mCaptureBufferRecorded = false; - mCaptureBufferPlaying = false; - - return; -} - -int LLVivoxVoiceClient::voiceRecordBuffer() -{ - LLSD timeoutResult(LLSDMap("recplay", "stop")); - - LL_INFOS("Voice") << "Recording voice buffer" << LL_ENDL; - - LLSD result; - - captureBufferRecordStartSendMessage(); - notifyVoiceFontObservers(); - - do - { - result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, CAPTURE_BUFFER_MAX_TIME, timeoutResult); - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - } while (!result.has("recplay")); - - mCaptureBufferRecorded = true; - - captureBufferRecordStopSendMessage(); - mCaptureBufferRecording = false; - - // Update UI, should really use a separate callback. - notifyVoiceFontObservers(); - - return true; - /*TODO expand return to move directly into play*/ -} - -int LLVivoxVoiceClient::voicePlaybackBuffer() -{ - LLSD timeoutResult(LLSDMap("recplay", "stop")); - - LL_INFOS("Voice") << "Playing voice buffer" << LL_ENDL; - - LLSD result; - - do - { - captureBufferPlayStartSendMessage(mPreviewVoiceFont); - - // Store the voice font being previewed, so that we know to restart if it changes. - mPreviewVoiceFontLast = mPreviewVoiceFont; - - do - { - // Update UI, should really use a separate callback. - notifyVoiceFontObservers(); - - result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, CAPTURE_BUFFER_MAX_TIME, timeoutResult); - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - } while (!result.has("recplay")); - - if (result["recplay"] == "playback") - continue; // restart playback... May be a font change. - - break; - } while (true); - - // Stop playing. - captureBufferPlayStopSendMessage(); - mCaptureBufferPlaying = false; - - // Update UI, should really use a separate callback. - notifyVoiceFontObservers(); - - return true; -} - - -bool LLVivoxVoiceClient::performMicTuning() -{ - LL_INFOS("Voice") << "Entering voice tuning mode." << LL_ENDL; - - mIsInTuningMode = true; - llcoro::suspend(); - - while (mTuningMode && !sShuttingDown) - { - - if (mCaptureDeviceDirty || mRenderDeviceDirty) - { - // These can't be changed while in tuning mode. Set them before starting. - std::ostringstream stream; - - buildSetCaptureDevice(stream); - buildSetRenderDevice(stream); - - if (!stream.str().empty()) - { - writeString(stream.str()); - } - - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - } - - // loop mic back to render device. - //setMuteMic(0); // make sure the mic is not muted - std::ostringstream stream; - - stream << "" - << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" - << "false" - << "\n\n\n"; - - // Dirty the mute mic state so that it will get reset when we finishing previewing - mMuteMicDirty = true; - mTuningSpeakerVolumeDirty = true; - - writeString(stream.str()); - tuningCaptureStartSendMessage(1); // 1-loop, zero, don't loop - - //--------------------------------------------------------------------- - if (!sShuttingDown) - { - llcoro::suspend(); - } - - while (mTuningMode && !mCaptureDeviceDirty && !mRenderDeviceDirty && !sShuttingDown) - { - // process mic/speaker volume changes - if (mTuningMicVolumeDirty || mTuningSpeakerVolumeDirty) - { - std::ostringstream stream; - - if (mTuningMicVolumeDirty) - { - LL_INFOS("Voice") << "setting tuning mic level to " << mTuningMicVolume << LL_ENDL; - stream - << "" - << "" << mTuningMicVolume << "" - << "\n\n\n"; - } - - if (mTuningSpeakerVolumeDirty) - { - LL_INFOS("Voice") << "setting tuning speaker level to " << mTuningSpeakerVolume << LL_ENDL; - stream - << "" - << "" << mTuningSpeakerVolume << "" - << "\n\n\n"; - } - - mTuningMicVolumeDirty = false; - mTuningSpeakerVolumeDirty = false; - - if (!stream.str().empty()) - { - writeString(stream.str()); - } - } - llcoro::suspend(); - } - - //--------------------------------------------------------------------- - - // transition out of mic tuning - tuningCaptureStopSendMessage(); - if ((mCaptureDeviceDirty || mRenderDeviceDirty) && !sShuttingDown) - { - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - } - } - - mIsInTuningMode = false; - - //--------------------------------------------------------------------- - return true; -} - -//========================================================================= - -void LLVivoxVoiceClient::closeSocket(void) -{ - mSocket.reset(); - sConnected = false; - mConnectorEstablished = false; - mAccountLoggedIn = false; -} - -void LLVivoxVoiceClient::loginSendMessage() -{ - std::ostringstream stream; - - bool autoPostCrashDumps = gSavedSettings.getBOOL("VivoxAutoPostCrashDumps"); - - stream - << "" - << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" - << "" << mAccountName << "" - << "" << mAccountPassword << "" - << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" - << "VerifyAnswer" - << "false" - << "0" - << "Application" - << "5" - << (autoPostCrashDumps?"true":"") - << "\n\n\n"; - - LL_INFOS("Voice") << "Attempting voice login" << LL_ENDL; - writeString(stream.str()); -} - -void LLVivoxVoiceClient::logout() -{ - // Ensure that we'll re-request provisioning before logging in again - mAccountPassword.clear(); - mVoiceAccountServerURI.clear(); - - logoutSendMessage(); -} - -void LLVivoxVoiceClient::logoutSendMessage() -{ - if(mAccountLoggedIn) - { - LL_INFOS("Voice") << "Attempting voice logout" << LL_ENDL; - std::ostringstream stream; - stream - << "" - << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" - << "" - << "\n\n\n"; - - mAccountLoggedIn = false; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::sessionGroupCreateSendMessage() -{ - if(mAccountLoggedIn) - { - std::ostringstream stream; - - LL_DEBUGS("Voice") << "creating session group" << LL_ENDL; - - stream - << "" - << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" - << "Normal" - << "" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::sessionCreateSendMessage(const sessionStatePtr_t &session, bool startAudio, bool startText) -{ - S32 font_index = getVoiceFontIndex(session->mVoiceFontID); - LL_DEBUGS("Voice") << "Requesting create: " << session->mSIPURI - << " with voice font: " << session->mVoiceFontID << " (" << font_index << ")" - << LL_ENDL; - - session->mCreateInProgress = true; - if(startAudio) - { - session->mMediaConnectInProgress = true; - } - - std::ostringstream stream; - stream - << "mSIPURI << "\" action=\"Session.Create.1\">" - << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" - << "" << session->mSIPURI << ""; - - static const std::string allowed_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - "0123456789" - "-._~"; - - if(!session->mHash.empty()) - { - stream - << "" << LLURI::escape(session->mHash, allowed_chars) << "" - << "SHA1UserName"; - } - - stream - << "" << (startAudio?"true":"false") << "" - << "" << (startText?"true":"false") << "" - << "" << font_index << "" - << "" << mChannelName << "" - << "\n\n\n"; - writeString(stream.str()); -} - -void LLVivoxVoiceClient::sessionGroupAddSessionSendMessage(const sessionStatePtr_t &session, bool startAudio, bool startText) -{ - LL_DEBUGS("Voice") << "Requesting create: " << session->mSIPURI << LL_ENDL; - - S32 font_index = getVoiceFontIndex(session->mVoiceFontID); - LL_DEBUGS("Voice") << "With voice font: " << session->mVoiceFontID << " (" << font_index << ")" << LL_ENDL; - - session->mCreateInProgress = true; - if(startAudio) - { - session->mMediaConnectInProgress = true; - } - - std::string password; - if(!session->mHash.empty()) - { - static const std::string allowed_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - "0123456789" - "-._~" - ; - password = LLURI::escape(session->mHash, allowed_chars); - } - - std::ostringstream stream; - stream - << "mSIPURI << "\" action=\"SessionGroup.AddSession.1\">" - << "" << session->mGroupHandle << "" - << "" << session->mSIPURI << "" - << "" << mChannelName << "" - << "" << (startAudio?"true":"false") << "" - << "" << (startText?"true":"false") << "" - << "" << font_index << "" - << "" << password << "" - << "SHA1UserName" - << "\n\n\n" - ; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::sessionMediaConnectSendMessage(const sessionStatePtr_t &session) -{ - S32 font_index = getVoiceFontIndex(session->mVoiceFontID); - LL_DEBUGS("Voice") << "Connecting audio to session handle: " << session->mHandle - << " with voice font: " << session->mVoiceFontID << " (" << font_index << ")" - << LL_ENDL; - - session->mMediaConnectInProgress = true; - - std::ostringstream stream; - - stream - << "mHandle << "\" action=\"Session.MediaConnect.1\">" - << "" << session->mGroupHandle << "" - << "" << session->mHandle << "" - << "" << font_index << "" - << "Audio" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::sessionTextConnectSendMessage(const sessionStatePtr_t &session) -{ - LL_DEBUGS("Voice") << "connecting text to session handle: " << session->mHandle << LL_ENDL; - - std::ostringstream stream; - - stream - << "mHandle << "\" action=\"Session.TextConnect.1\">" - << "" << session->mGroupHandle << "" - << "" << session->mHandle << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::sessionTerminate() -{ - mSessionTerminateRequested = true; -} - -void LLVivoxVoiceClient::requestRelog() -{ - mSessionTerminateRequested = true; - mRelogRequested = true; -} - - -void LLVivoxVoiceClient::leaveAudioSession() -{ - if(mAudioSession) - { - LL_DEBUGS("Voice") << "leaving session: " << mAudioSession->mSIPURI << LL_ENDL; - - if(!mAudioSession->mHandle.empty()) - { - -#if RECORD_EVERYTHING - // HACK: for testing only - // Save looped recording - std::string savepath("/tmp/vivoxrecording"); - { - time_t now = time(NULL); - const size_t BUF_SIZE = 64; - char time_str[BUF_SIZE]; /* Flawfinder: ignore */ - - strftime(time_str, BUF_SIZE, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now)); - savepath += time_str; - } - recordingLoopSave(savepath); -#endif - - sessionMediaDisconnectSendMessage(mAudioSession); - } - else - { - LL_WARNS("Voice") << "called with no session handle" << LL_ENDL; - } - } - else - { - LL_WARNS("Voice") << "called with no active session" << LL_ENDL; - } - sessionTerminate(); -} - -void LLVivoxVoiceClient::sessionTerminateSendMessage(const sessionStatePtr_t &session) -{ - std::ostringstream stream; - - sessionGroupTerminateSendMessage(session); - return; - /* - LL_DEBUGS("Voice") << "Sending Session.Terminate with handle " << session->mHandle << LL_ENDL; - stream - << "" - << "" << session->mHandle << "" - << "\n\n\n"; - - writeString(stream.str()); - */ -} - -void LLVivoxVoiceClient::sessionGroupTerminateSendMessage(const sessionStatePtr_t &session) -{ - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Sending SessionGroup.Terminate with handle " << session->mGroupHandle << LL_ENDL; - stream - << "" - << "" << session->mGroupHandle << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::sessionMediaDisconnectSendMessage(const sessionStatePtr_t &session) -{ - std::ostringstream stream; - sessionGroupTerminateSendMessage(session); - return; - /* - LL_DEBUGS("Voice") << "Sending Session.MediaDisconnect with handle " << session->mHandle << LL_ENDL; - stream - << "" - << "" << session->mGroupHandle << "" - << "" << session->mHandle << "" - << "Audio" - << "\n\n\n"; - - writeString(stream.str()); - */ - -} - - -void LLVivoxVoiceClient::getCaptureDevicesSendMessage() -{ - std::ostringstream stream; - stream - << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::getRenderDevicesSendMessage() -{ - std::ostringstream stream; - stream - << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::clearCaptureDevices() -{ - LL_DEBUGS("Voice") << "called" << LL_ENDL; - mCaptureDevices.clear(); -} - -void LLVivoxVoiceClient::addCaptureDevice(const LLVoiceDevice& device) -{ - LL_DEBUGS("Voice") << "display: '" << device.display_name << "' device: '" << device.full_name << "'" << LL_ENDL; - mCaptureDevices.push_back(device); -} - -LLVoiceDeviceList& LLVivoxVoiceClient::getCaptureDevices() -{ - return mCaptureDevices; -} - -void LLVivoxVoiceClient::setCaptureDevice(const std::string& name) -{ - if(name == "Default") - { - if(!mCaptureDevice.empty()) - { - mCaptureDevice.clear(); - mCaptureDeviceDirty = true; - } - } - else - { - if(mCaptureDevice != name) - { - mCaptureDevice = name; - mCaptureDeviceDirty = true; - } - } -} -void LLVivoxVoiceClient::setDevicesListUpdated(bool state) -{ - mDevicesListUpdated = state; -} - -void LLVivoxVoiceClient::clearRenderDevices() -{ - LL_DEBUGS("Voice") << "called" << LL_ENDL; - mRenderDevices.clear(); -} - -void LLVivoxVoiceClient::addRenderDevice(const LLVoiceDevice& device) -{ - LL_DEBUGS("Voice") << "display: '" << device.display_name << "' device: '" << device.full_name << "'" << LL_ENDL; - mRenderDevices.push_back(device); -} - -LLVoiceDeviceList& LLVivoxVoiceClient::getRenderDevices() -{ - return mRenderDevices; -} - -void LLVivoxVoiceClient::setRenderDevice(const std::string& name) -{ - if(name == "Default") - { - if(!mRenderDevice.empty()) - { - mRenderDevice.clear(); - mRenderDeviceDirty = true; - } - } - else - { - if(mRenderDevice != name) - { - mRenderDevice = name; - mRenderDeviceDirty = true; - } - } - -} - -void LLVivoxVoiceClient::tuningStart() -{ - LL_DEBUGS("Voice") << "Starting tuning" << LL_ENDL; - mTuningMode = true; - if (!mIsCoroutineActive) - { - LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro", - boost::bind(&LLVivoxVoiceClient::voiceControlCoro, LLVivoxVoiceClient::getInstance())); - } - else if (mIsInChannel) - { - LL_DEBUGS("Voice") << "no channel" << LL_ENDL; - sessionTerminate(); - } -} - -void LLVivoxVoiceClient::tuningStop() -{ - mTuningMode = false; -} - -bool LLVivoxVoiceClient::inTuningMode() -{ - return mIsInTuningMode; -} - -void LLVivoxVoiceClient::tuningRenderStartSendMessage(const std::string& name, bool loop) -{ - mTuningAudioFile = name; - std::ostringstream stream; - stream - << "" - << "" << mTuningAudioFile << "" - << "" << (loop?"1":"0") << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::tuningRenderStopSendMessage() -{ - std::ostringstream stream; - stream - << "" - << "" << mTuningAudioFile << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::tuningCaptureStartSendMessage(int loop) -{ - LL_DEBUGS("Voice") << "sending CaptureAudioStart" << LL_ENDL; - - std::ostringstream stream; - stream - << "" - << "-1" - << "" << loop << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::tuningCaptureStopSendMessage() -{ - LL_DEBUGS("Voice") << "sending CaptureAudioStop" << LL_ENDL; - - std::ostringstream stream; - stream - << "" - << "\n\n\n"; - - writeString(stream.str()); - - mTuningEnergy = 0.0f; -} - -void LLVivoxVoiceClient::tuningSetMicVolume(float volume) -{ - int scaled_volume = scale_mic_volume(volume); - - if(scaled_volume != mTuningMicVolume) - { - mTuningMicVolume = scaled_volume; - mTuningMicVolumeDirty = true; - } -} - -void LLVivoxVoiceClient::tuningSetSpeakerVolume(float volume) -{ - int scaled_volume = scale_speaker_volume(volume); - - if(scaled_volume != mTuningSpeakerVolume) - { - mTuningSpeakerVolume = scaled_volume; - mTuningSpeakerVolumeDirty = true; - } -} - -float LLVivoxVoiceClient::tuningGetEnergy(void) -{ - return mTuningEnergy; -} - -bool LLVivoxVoiceClient::deviceSettingsAvailable() -{ - bool result = true; - - if(!sConnected) - result = false; - - if(mRenderDevices.empty()) - result = false; - - return result; -} -bool LLVivoxVoiceClient::deviceSettingsUpdated() -{ - bool updated = mDevicesListUpdated; - if (mDevicesListUpdated) - { - // a hot swap event or a polling of the audio devices has been parsed since the last redraw of the input and output device panel. - mDevicesListUpdated = false; // toggle the setting - } - return updated; -} - -void LLVivoxVoiceClient::refreshDeviceLists(bool clearCurrentList) -{ - if(clearCurrentList) - { - clearCaptureDevices(); - clearRenderDevices(); - } - getCaptureDevicesSendMessage(); - getRenderDevicesSendMessage(); -} - -void LLVivoxVoiceClient::daemonDied() -{ - // The daemon died, so the connection is gone. Reset everything and start over. - LL_WARNS("Voice") << "Connection to vivox daemon lost. Resetting state."<< LL_ENDL; - - //TODO: Try to relaunch the daemon -} - -void LLVivoxVoiceClient::giveUp() -{ - // All has failed. Clean up and stop trying. - LL_WARNS("Voice") << "Terminating Voice Service" << LL_ENDL; - closeSocket(); - cleanUp(); -} - -static void oldSDKTransform (LLVector3 &left, LLVector3 &up, LLVector3 &at, LLVector3d &pos, LLVector3 &vel) -{ - F32 nat[3], nup[3], nl[3]; // the new at, up, left vectors and the new position and velocity -// F32 nvel[3]; - F64 npos[3]; - - // The original XML command was sent like this: - /* - << "" - << "" << pos[VX] << "" - << "" << pos[VZ] << "" - << "" << pos[VY] << "" - << "" - << "" - << "" << mAvatarVelocity[VX] << "" - << "" << mAvatarVelocity[VZ] << "" - << "" << mAvatarVelocity[VY] << "" - << "" - << "" - << "" << l.mV[VX] << "" - << "" << u.mV[VX] << "" - << "" << a.mV[VX] << "" - << "" - << "" - << "" << l.mV[VZ] << "" - << "" << u.mV[VY] << "" - << "" << a.mV[VZ] << "" - << "" - << "" - << "" << l.mV [VY] << "" - << "" << u.mV [VZ] << "" - << "" << a.mV [VY] << "" - << ""; - */ - -#if 1 - // This was the original transform done when building the XML command - nat[0] = left.mV[VX]; - nat[1] = up.mV[VX]; - nat[2] = at.mV[VX]; - - nup[0] = left.mV[VZ]; - nup[1] = up.mV[VY]; - nup[2] = at.mV[VZ]; - - nl[0] = left.mV[VY]; - nl[1] = up.mV[VZ]; - nl[2] = at.mV[VY]; - - npos[0] = pos.mdV[VX]; - npos[1] = pos.mdV[VZ]; - npos[2] = pos.mdV[VY]; - -// nvel[0] = vel.mV[VX]; -// nvel[1] = vel.mV[VZ]; -// nvel[2] = vel.mV[VY]; - - for(int i=0;i<3;++i) { - at.mV[i] = nat[i]; - up.mV[i] = nup[i]; - left.mV[i] = nl[i]; - pos.mdV[i] = npos[i]; - } - - // This was the original transform done in the SDK - nat[0] = at.mV[2]; - nat[1] = 0; // y component of at vector is always 0, this was up[2] - nat[2] = -1 * left.mV[2]; - - // We override whatever the application gives us - nup[0] = 0; // x component of up vector is always 0 - nup[1] = 1; // y component of up vector is always 1 - nup[2] = 0; // z component of up vector is always 0 - - nl[0] = at.mV[0]; - nl[1] = 0; // y component of left vector is always zero, this was up[0] - nl[2] = -1 * left.mV[0]; - - npos[2] = pos.mdV[2] * -1.0; - npos[1] = pos.mdV[1]; - npos[0] = pos.mdV[0]; - - for(int i=0;i<3;++i) { - at.mV[i] = nat[i]; - up.mV[i] = nup[i]; - left.mV[i] = nl[i]; - pos.mdV[i] = npos[i]; - } -#else - // This is the compose of the two transforms (at least, that's what I'm trying for) - nat[0] = at.mV[VX]; - nat[1] = 0; // y component of at vector is always 0, this was up[2] - nat[2] = -1 * up.mV[VZ]; - - // We override whatever the application gives us - nup[0] = 0; // x component of up vector is always 0 - nup[1] = 1; // y component of up vector is always 1 - nup[2] = 0; // z component of up vector is always 0 - - nl[0] = left.mV[VX]; - nl[1] = 0; // y component of left vector is always zero, this was up[0] - nl[2] = -1 * left.mV[VY]; - - npos[0] = pos.mdV[VX]; - npos[1] = pos.mdV[VZ]; - npos[2] = pos.mdV[VY] * -1.0; - - nvel[0] = vel.mV[VX]; - nvel[1] = vel.mV[VZ]; - nvel[2] = vel.mV[VY]; - - for(int i=0;i<3;++i) { - at.mV[i] = nat[i]; - up.mV[i] = nup[i]; - left.mV[i] = nl[i]; - pos.mdV[i] = npos[i]; - } - -#endif -} - -void LLVivoxVoiceClient::setHidden(bool hidden) -{ - mHidden = hidden; - - if (mHidden && inSpatialChannel()) - { - // get out of the channel entirely - leaveAudioSession(); - } - else - { - sendPositionAndVolumeUpdate(); - } -} - -void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) -{ - std::ostringstream stream; - - if (mSpatialCoordsDirty && inSpatialChannel()) - { - LLVector3 l, u, a, vel; - LLVector3d pos; - - mSpatialCoordsDirty = false; - - // Always send both speaker and listener positions together. - stream << "" - << "" << getAudioSessionHandle() << ""; - - stream << ""; - - LLMatrix3 avatarRot = mAvatarRot.getMatrix3(); - -// LL_DEBUGS("Voice") << "Sending speaker position " << mAvatarPosition << LL_ENDL; - l = avatarRot.getLeftRow(); - u = avatarRot.getUpRow(); - a = avatarRot.getFwdRow(); - - pos = mAvatarPosition; - vel = mAvatarVelocity; - - // SLIM SDK: the old SDK was doing a transform on the passed coordinates that the new one doesn't do anymore. - // The old transform is replicated by this function. - oldSDKTransform(l, u, a, pos, vel); - - if (mHidden) - { - for (int i=0;i<3;++i) - { - pos.mdV[i] = VX_NULL_POSITION; - } - } - - stream - << "" - << "" << pos.mdV[VX] << "" - << "" << pos.mdV[VY] << "" - << "" << pos.mdV[VZ] << "" - << "" - << "" - << "" << vel.mV[VX] << "" - << "" << vel.mV[VY] << "" - << "" << vel.mV[VZ] << "" - << "" - << "" - << "" << a.mV[VX] << "" - << "" << a.mV[VY] << "" - << "" << a.mV[VZ] << "" - << "" - << "" - << "" << u.mV[VX] << "" - << "" << u.mV[VY] << "" - << "" << u.mV[VZ] << "" - << "" - << "" - << "" << l.mV [VX] << "" - << "" << l.mV [VY] << "" - << "" << l.mV [VZ] << "" - << "" - ; - - stream << ""; - - stream << ""; - - LLVector3d earPosition; - LLVector3 earVelocity; - LLMatrix3 earRot; - - switch(mEarLocation) - { - case earLocCamera: - default: - earPosition = mCameraPosition; - earVelocity = mCameraVelocity; - earRot = mCameraRot; - break; - - case earLocAvatar: - earPosition = mAvatarPosition; - earVelocity = mAvatarVelocity; - earRot = avatarRot; - break; - - case earLocMixed: - earPosition = mAvatarPosition; - earVelocity = mAvatarVelocity; - earRot = mCameraRot; - break; - } - - l = earRot.getLeftRow(); - u = earRot.getUpRow(); - a = earRot.getFwdRow(); - - pos = earPosition; - vel = earVelocity; - - - oldSDKTransform(l, u, a, pos, vel); - - if (mHidden) - { - for (int i=0;i<3;++i) - { - pos.mdV[i] = VX_NULL_POSITION; - } - } - - stream - << "" - << "" << pos.mdV[VX] << "" - << "" << pos.mdV[VY] << "" - << "" << pos.mdV[VZ] << "" - << "" - << "" - << "" << vel.mV[VX] << "" - << "" << vel.mV[VY] << "" - << "" << vel.mV[VZ] << "" - << "" - << "" - << "" << a.mV[VX] << "" - << "" << a.mV[VY] << "" - << "" << a.mV[VZ] << "" - << "" - << "" - << "" << u.mV[VX] << "" - << "" << u.mV[VY] << "" - << "" << u.mV[VZ] << "" - << "" - << "" - << "" << l.mV [VX] << "" - << "" << l.mV [VY] << "" - << "" << l.mV [VZ] << "" - << "" - ; - - stream << ""; - - stream << "1"; //do not generate responses for update requests - stream << "\n\n\n"; - } - - if(mAudioSession && (mAudioSession->mVolumeDirty || mAudioSession->mMuteDirty)) - { - participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin(); - - mAudioSession->mVolumeDirty = false; - mAudioSession->mMuteDirty = false; - - for(; iter != mAudioSession->mParticipantsByURI.end(); iter++) - { - participantStatePtr_t p(iter->second); - - if(p->mVolumeDirty) - { - // Can't set volume/mute for yourself - if(!p->mIsSelf) - { - // scale from the range 0.0-1.0 to vivox volume in the range 0-100 - S32 volume = ll_round(p->mVolume / VOLUME_SCALE_VIVOX); - bool mute = p->mOnMuteList; - - if(mute) - { - // SetParticipantMuteForMe doesn't work in p2p sessions. - // If we want the user to be muted, set their volume to 0 as well. - // This isn't perfect, but it will at least reduce their volume to a minimum. - volume = 0; - // Mark the current volume level as set to prevent incoming events - // changing it to 0, so that we can return to it when unmuting. - p->mVolumeSet = true; - } - - if(volume == 0) - { - mute = true; - } - - LL_DEBUGS("Voice") << "Setting volume/mute for avatar " << p->mAvatarID << " to " << volume << (mute?"/true":"/false") << LL_ENDL; - - // SLIM SDK: Send both volume and mute commands. - - // Send a "volume for me" command for the user. - stream << "" - << "" << getAudioSessionHandle() << "" - << "" << p->mURI << "" - << "" << volume << "" - << "\n\n\n"; - - if(!mAudioSession->mIsP2P) - { - // Send a "mute for me" command for the user - // Doesn't work in P2P sessions - stream << "" - << "" << getAudioSessionHandle() << "" - << "" << p->mURI << "" - << "" << (mute?"1":"0") << "" - << "Audio" - << "\n\n\n"; - } - } - - p->mVolumeDirty = false; - } - } - } - - std::string update(stream.str()); - if(!update.empty()) - { - LL_DEBUGS("VoiceUpdate") << "sending update " << update << LL_ENDL; - writeString(update); - } - -} - -void LLVivoxVoiceClient::buildSetCaptureDevice(std::ostringstream &stream) -{ - if(mCaptureDeviceDirty) - { - LL_DEBUGS("Voice") << "Setting input device = \"" << mCaptureDevice << "\"" << LL_ENDL; - - stream - << "" - << "" << mCaptureDevice << "" - << "" - << "\n\n\n"; - - mCaptureDeviceDirty = false; - } -} - -void LLVivoxVoiceClient::buildSetRenderDevice(std::ostringstream &stream) -{ - if(mRenderDeviceDirty) - { - LL_DEBUGS("Voice") << "Setting output device = \"" << mRenderDevice << "\"" << LL_ENDL; - - stream - << "" - << "" << mRenderDevice << "" - << "" - << "\n\n\n"; - mRenderDeviceDirty = false; - } -} - -void LLVivoxVoiceClient::sendLocalAudioUpdates() -{ - // Check all of the dirty states and then send messages to those needing to be changed. - // Tuningmode hands its own mute settings. - std::ostringstream stream; - - if (mMuteMicDirty && !mTuningMode) - { - mMuteMicDirty = false; - - // Send a local mute command. - - LL_INFOS("Voice") << "Sending MuteLocalMic command with parameter " << (mMuteMic ? "true" : "false") << LL_ENDL; - - stream << "" - << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" - << "" << (mMuteMic ? "true" : "false") << "" - << "\n\n\n"; - - } - - if (mSpeakerMuteDirty && !mTuningMode) - { - const char *muteval = ((mSpeakerVolume <= scale_speaker_volume(0)) ? "true" : "false"); - - mSpeakerMuteDirty = false; - - LL_INFOS("Voice") << "Setting speaker mute to " << muteval << LL_ENDL; - - stream << "" - << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" - << "" << muteval << "" - << "\n\n\n"; - - } - - if (mSpeakerVolumeDirty) - { - mSpeakerVolumeDirty = false; - - LL_INFOS("Voice") << "Setting speaker volume to " << mSpeakerVolume << LL_ENDL; - - stream << "" - << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" - << "" << mSpeakerVolume << "" - << "\n\n\n"; - - } - - if (mMicVolumeDirty) - { - mMicVolumeDirty = false; - - LL_INFOS("Voice") << "Setting mic volume to " << mMicVolume << LL_ENDL; - - stream << "" - << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" - << "" << mMicVolume << "" - << "\n\n\n"; - } - - - if (!stream.str().empty()) - { - writeString(stream.str()); - } -} - -/** - * Because of the recurring voice cutout issues (SL-15072) we are going to try - * to disable the automatic VAD (Voice Activity Detection) and set the associated - * parameters directly. We will expose them via Debug Settings and that should - * let us iterate on a collection of values that work for us. Hopefully! - * - * From the VIVOX Docs: - * - * VadAuto: A flag indicating if the automatic VAD is enabled (1) or disabled (0) - * - * VadHangover: The time (in milliseconds) that it takes - * for the VAD to switch back to silence from speech mode after the last speech - * frame has been detected. - * - * VadNoiseFloor: A dimensionless value between 0 and - * 20000 (default 576) that controls the maximum level at which the noise floor - * may be set at by the VAD's noise tracking. Too low of a value will make noise - * tracking ineffective (A value of 0 disables noise tracking and the VAD then - * relies purely on the sensitivity property). Too high of a value will make - * long speech classifiable as noise. - * - * VadSensitivity: A dimensionless value between 0 and - * 100, indicating the 'sensitivity of the VAD'. Increasing this value corresponds - * to decreasing the sensitivity of the VAD (i.e. '0' is most sensitive, - * while 100 is 'least sensitive') - */ -void LLVivoxVoiceClient::setupVADParams(unsigned int vad_auto, - unsigned int vad_hangover, - unsigned int vad_noise_floor, - unsigned int vad_sensitivity) -{ - std::ostringstream stream; - - LL_INFOS("Voice") << "Setting the automatic VAD to " - << (vad_auto ? "True" : "False") - << " and discrete values to" - << " VadHangover = " << vad_hangover - << ", VadSensitivity = " << vad_sensitivity - << ", VadNoiseFloor = " << vad_noise_floor - << LL_ENDL; - - // Create a request to set the VAD parameters: - stream << "" - << "" << vad_auto << "" - << "" << vad_hangover << "" - << "" << vad_sensitivity << "" - << "" << vad_noise_floor << "" - << "\n\n\n"; - - if (!stream.str().empty()) - { - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::onVADSettingsChange() -{ - // pick up the VAD variables (one of which was changed) - unsigned int vad_auto = gSavedSettings.getU32("VivoxVadAuto"); - unsigned int vad_hangover = gSavedSettings.getU32("VivoxVadHangover"); - unsigned int vad_noise_floor = gSavedSettings.getU32("VivoxVadNoiseFloor"); - unsigned int vad_sensitivity = gSavedSettings.getU32("VivoxVadSensitivity"); - - // build a VAD params change request and send it to SLVoice - setupVADParams(vad_auto, vad_hangover, vad_noise_floor, vad_sensitivity); -} - -///////////////////////////// -// Response/Event handlers - -void LLVivoxVoiceClient::connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID) -{ - LLSD result = LLSD::emptyMap(); - - if(statusCode == 0) - { - // Connector created, move forward. - if (connectorHandle == LLVivoxSecurity::getInstance()->connectorHandle()) - { - LL_INFOS("Voice") << "Voice connector succeeded, Vivox SDK version is " << versionID << " connector handle " << connectorHandle << LL_ENDL; - mVoiceVersion.serverVersion = versionID; - mConnectorEstablished = true; - mTerminateDaemon = false; - - result["connector"] = LLSD::Boolean(true); - } - else - { - // This shouldn't happen - we are somehow out of sync with SLVoice - // or possibly there are two things trying to run SLVoice at once - // or someone is trying to hack into it. - LL_WARNS("Voice") << "Connector returned wrong handle " - << "(" << connectorHandle << ")" - << " expected (" << LLVivoxSecurity::getInstance()->connectorHandle() << ")" - << LL_ENDL; - result["connector"] = LLSD::Boolean(false); - // Give up. - mTerminateDaemon = true; - } - } - else if (statusCode == 10028) // web request timeout prior to login - { - // this is usually fatal, but a long timeout might work - result["connector"] = LLSD::Boolean(false); - result["retry"] = LLSD::Real(CONNECT_ATTEMPT_TIMEOUT); - - LL_WARNS("Voice") << "Voice connection failed" << LL_ENDL; - } - else if (statusCode == 10006) // name resolution failure - a shorter retry may work - { - // some networks have slower DNS, but a short timeout might let it catch up - result["connector"] = LLSD::Boolean(false); - result["retry"] = LLSD::Real(CONNECT_DNS_TIMEOUT); - - LL_WARNS("Voice") << "Voice connection DNS lookup failed" << LL_ENDL; - } - else // unknown failure - give up - { - LL_WARNS("Voice") << "Voice connection failure ("<< statusCode << "): " << statusString << LL_ENDL; - mTerminateDaemon = true; - result["connector"] = LLSD::Boolean(false); - } - - mVivoxPump.post(result); -} - -void LLVivoxVoiceClient::loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases) -{ - LLSD result = LLSD::emptyMap(); - - LL_DEBUGS("Voice") << "Account.Login response (" << statusCode << "): " << statusString << LL_ENDL; - - // Status code of 20200 means "bad password". We may want to special-case that at some point. - - if ( statusCode == HTTP_UNAUTHORIZED ) - { - // Login failure which is probably caused by the delay after a user's password being updated. - LL_INFOS("Voice") << "Account.Login response failure (" << statusCode << "): " << statusString << LL_ENDL; - result["login"] = LLSD::String("retry"); - } - else if(statusCode != 0) - { - LL_WARNS("Voice") << "Account.Login response failure (" << statusCode << "): " << statusString << LL_ENDL; - result["login"] = LLSD::String("failed"); - } - else - { - // Login succeeded, move forward. - mAccountLoggedIn = true; - mNumberOfAliases = numberOfAliases; - result["login"] = LLSD::String("response_ok"); - } - - mVivoxPump.post(result); - -} - -void LLVivoxVoiceClient::sessionCreateResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle) -{ - sessionStatePtr_t session(findSessionBeingCreatedByURI(requestId)); - - if(session) - { - session->mCreateInProgress = false; - } - - if(statusCode != 0) - { - LL_WARNS("Voice") << "Session.Create response failure (" << statusCode << "): " << statusString << LL_ENDL; - if(session) - { - session->mErrorStatusCode = statusCode; - session->mErrorStatusString = statusString; - if(session == mAudioSession) - { - LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle)) - ("session", "failed") - ("reason", LLSD::Integer(statusCode))); - - mVivoxPump.post(vivoxevent); - } - else - { - reapSession(session); - } - } - } - else - { - LL_INFOS("Voice") << "Session.Create response received (success), session handle is " << sessionHandle << LL_ENDL; - if(session) - { - setSessionHandle(session, sessionHandle); - } - LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle)) - ("session", "created")); - - mVivoxPump.post(vivoxevent); - } -} - -void LLVivoxVoiceClient::sessionGroupAddSessionResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle) -{ - sessionStatePtr_t session(findSessionBeingCreatedByURI(requestId)); - - if(session) - { - session->mCreateInProgress = false; - } - - if(statusCode != 0) - { - LL_WARNS("Voice") << "SessionGroup.AddSession response failure (" << statusCode << "): " << statusString << LL_ENDL; - if(session) - { - session->mErrorStatusCode = statusCode; - session->mErrorStatusString = statusString; - if(session == mAudioSession) - { - LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle)) - ("session", "failed")); - - mVivoxPump.post(vivoxevent); - } - else - { - reapSession(session); - } - } - } - else - { - LL_DEBUGS("Voice") << "SessionGroup.AddSession response received (success), session handle is " << sessionHandle << LL_ENDL; - if(session) - { - setSessionHandle(session, sessionHandle); - } - - LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle)) - ("session", "added")); - - mVivoxPump.post(vivoxevent); - - } -} - -void LLVivoxVoiceClient::sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString) -{ - sessionStatePtr_t session(findSession(requestId)); - // 1026 is session already has media, somehow mediaconnect was called twice on the same session. - // set the session info to reflect that the user is already connected. - if (statusCode == 1026) - { - session->mVoiceActive = true; - session->mMediaConnectInProgress = false; - session->mMediaStreamState = streamStateConnected; - //session->mTextStreamState = streamStateConnected; - session->mErrorStatusCode = 0; - } - else if (statusCode != 0) - { - LL_WARNS("Voice") << "Session.Connect response failure (" << statusCode << "): " << statusString << LL_ENDL; - if (session) - { - session->mMediaConnectInProgress = false; - session->mErrorStatusCode = statusCode; - session->mErrorStatusString = statusString; - } - } - else - { - LL_DEBUGS("Voice") << "Session.Connect response received (success)" << LL_ENDL; - } -} - -void LLVivoxVoiceClient::logoutResponse(int statusCode, std::string &statusString) -{ - if(statusCode != 0) - { - LL_WARNS("Voice") << "Account.Logout response failure: " << statusString << LL_ENDL; - // Should this ever fail? do we care if it does? - } - LLSD vivoxevent(LLSDMap("logout", LLSD::Boolean(true))); - - mVivoxPump.post(vivoxevent); -} - -void LLVivoxVoiceClient::connectorShutdownResponse(int statusCode, std::string &statusString) -{ - if(statusCode != 0) - { - LL_WARNS("Voice") << "Connector.InitiateShutdown response failure: " << statusString << LL_ENDL; - // Should this ever fail? do we care if it does? - } - - sConnected = false; - mShutdownComplete = true; - - LLSD vivoxevent(LLSDMap("connector", LLSD::Boolean(false))); - - mVivoxPump.post(vivoxevent); -} - -void LLVivoxVoiceClient::sessionAddedEvent( - std::string &uriString, - std::string &alias, - std::string &sessionHandle, - std::string &sessionGroupHandle, - bool isChannel, - bool incoming, - std::string &nameString, - std::string &applicationString) -{ - sessionStatePtr_t session; - - LL_INFOS("Voice") << "session " << uriString << ", alias " << alias << ", name " << nameString << " handle " << sessionHandle << LL_ENDL; - - session = addSession(uriString, sessionHandle); - if(session) - { - session->mGroupHandle = sessionGroupHandle; - session->mIsChannel = isChannel; - session->mIncoming = incoming; - session->mAlias = alias; - - // Generate a caller UUID -- don't need to do this for channels - if(!session->mIsChannel) - { - if(IDFromName(session->mSIPURI, session->mCallerID)) - { - // Normal URI(base64-encoded UUID) - } - else if(!session->mAlias.empty() && IDFromName(session->mAlias, session->mCallerID)) - { - // Wrong URI, but an alias is available. Stash the incoming URI as an alternate - session->mAlternateSIPURI = session->mSIPURI; - - // and generate a proper URI from the ID. - setSessionURI(session, sipURIFromID(session->mCallerID)); - } - else - { - LL_INFOS("Voice") << "Could not generate caller id from uri, using hash of uri " << session->mSIPURI << LL_ENDL; - session->mCallerID.generate(session->mSIPURI); - session->mSynthesizedCallerID = true; - - // Can't look up the name in this case -- we have to extract it from the URI. - std::string namePortion = nameFromsipURI(session->mSIPURI); - if(namePortion.empty()) - { - // Didn't seem to be a SIP URI, just use the whole provided name. - namePortion = nameString; - } - - // Some incoming names may be separated with an underscore instead of a space. Fix this. - LLStringUtil::replaceChar(namePortion, '_', ' '); - - // Act like we just finished resolving the name (this stores it in all the right places) - avatarNameResolved(session->mCallerID, namePortion); - } - - LL_INFOS("Voice") << "caller ID: " << session->mCallerID << LL_ENDL; - - if(!session->mSynthesizedCallerID) - { - // If we got here, we don't have a proper name. Initiate a lookup. - lookupName(session->mCallerID); - } - } - } -} - -void LLVivoxVoiceClient::sessionGroupAddedEvent(std::string &sessionGroupHandle) -{ - LL_DEBUGS("Voice") << "handle " << sessionGroupHandle << LL_ENDL; - -#if USE_SESSION_GROUPS - if(mMainSessionGroupHandle.empty()) - { - // This is the first (i.e. "main") session group. Save its handle. - mMainSessionGroupHandle = sessionGroupHandle; - } - else - { - LL_DEBUGS("Voice") << "Already had a session group handle " << mMainSessionGroupHandle << LL_ENDL; - } -#endif -} - -void LLVivoxVoiceClient::joinedAudioSession(const sessionStatePtr_t &session) -{ - LL_DEBUGS("Voice") << "Joined Audio Session" << LL_ENDL; - if(mAudioSession != session) - { - sessionStatePtr_t oldSession = mAudioSession; - - mAudioSession = session; - mAudioSessionChanged = true; - - // The old session may now need to be deleted. - reapSession(oldSession); - } - - // This is the session we're joining. - if(mIsJoiningSession) - { - LLSD vivoxevent(LLSDMap("handle", LLSD::String(session->mHandle)) - ("session", "joined")); - - mVivoxPump.post(vivoxevent); - - // Add the current user as a participant here. - participantStatePtr_t participant(session->addParticipant(sipURIFromName(mAccountName))); - if(participant) - { - participant->mIsSelf = true; - lookupName(participant->mAvatarID); - - LL_INFOS("Voice") << "added self as participant \"" << participant->mAccountName - << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; - } - - if(!session->mIsChannel) - { - // this is a p2p session. Make sure the other end is added as a participant. - participantStatePtr_t participant(session->addParticipant(session->mSIPURI)); - if(participant) - { - if(participant->mAvatarIDValid) - { - lookupName(participant->mAvatarID); - } - else if(!session->mName.empty()) - { - participant->mDisplayName = session->mName; - avatarNameResolved(participant->mAvatarID, session->mName); - } - - // TODO: Question: Do we need to set up mAvatarID/mAvatarIDValid here? - LL_INFOS("Voice") << "added caller as participant \"" << participant->mAccountName - << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; - } - } - } -} - -void LLVivoxVoiceClient::sessionRemovedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle) -{ - LL_INFOS("Voice") << "handle " << sessionHandle << LL_ENDL; - - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - leftAudioSession(session); - - // This message invalidates the session's handle. Set it to empty. - clearSessionHandle(session); - - // This also means that the session's session group is now empty. - // Terminate the session group so it doesn't leak. - sessionGroupTerminateSendMessage(session); - - // Reset the media state (we now have no info) - session->mMediaStreamState = streamStateUnknown; - //session->mTextStreamState = streamStateUnknown; - - // Conditionally delete the session - reapSession(session); - } - else - { - // Already reaped this session. - LL_DEBUGS("Voice") << "unknown session " << sessionHandle << " removed" << LL_ENDL; - } - -} - -void LLVivoxVoiceClient::reapSession(const sessionStatePtr_t &session) -{ - if(session) - { - - if(session->mCreateInProgress) - { - LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (create in progress)" << LL_ENDL; - } - else if(session->mMediaConnectInProgress) - { - LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (connect in progress)" << LL_ENDL; - } - else if(session == mAudioSession) - { - LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the current session)" << LL_ENDL; - } - else if(session == mNextAudioSession) - { - LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the next session)" << LL_ENDL; - } - else - { - // We don't have a reason to keep tracking this session, so just delete it. - LL_DEBUGS("Voice") << "deleting session " << session->mSIPURI << LL_ENDL; - deleteSession(session); - } - } - else - { -// LL_DEBUGS("Voice") << "session is NULL" << LL_ENDL; - } -} - -// Returns true if the session seems to indicate we've moved to a region on a different voice server -bool LLVivoxVoiceClient::sessionNeedsRelog(const sessionStatePtr_t &session) -{ - bool result = false; - - if(session) - { - // Only make this check for spatial channels (so it won't happen for group or p2p calls) - if(session->mIsSpatial) - { - std::string::size_type atsign; - - atsign = session->mSIPURI.find("@"); - - if(atsign != std::string::npos) - { - std::string urihost = session->mSIPURI.substr(atsign + 1); - if(stricmp(urihost.c_str(), mVoiceSIPURIHostName.c_str())) - { - // The hostname in this URI is different from what we expect. This probably means we need to relog. - - // We could make a ProvisionVoiceAccountRequest and compare the result with the current values of - // mVoiceSIPURIHostName and mVoiceAccountServerURI to be really sure, but this is a pretty good indicator. - - result = true; - } - } - } - } - - return result; -} - -void LLVivoxVoiceClient::leftAudioSession(const sessionStatePtr_t &session) -{ - if (mAudioSession == session) - { - LLSD vivoxevent(LLSDMap("handle", LLSD::String(session->mHandle)) - ("session", "removed")); - - mVivoxPump.post(vivoxevent); - } -} - -void LLVivoxVoiceClient::accountLoginStateChangeEvent( - std::string &accountHandle, - int statusCode, - std::string &statusString, - int state) -{ - LLSD levent = LLSD::emptyMap(); - - /* - According to Mike S., status codes for this event are: - login_state_logged_out=0, - login_state_logged_in = 1, - login_state_logging_in = 2, - login_state_logging_out = 3, - login_state_resetting = 4, - login_state_error=100 - */ - - LL_DEBUGS("Voice") << "state change event: " << state << LL_ENDL; - switch(state) - { - case 1: - levent["login"] = LLSD::String("account_login"); - - mVivoxPump.post(levent); - break; - case 2: - break; - - case 3: - levent["login"] = LLSD::String("account_loggingOut"); - - mVivoxPump.post(levent); - break; - - case 4: - break; - - case 100: - LL_WARNS("Voice") << "account state event error" << LL_ENDL; - break; - - case 0: - levent["login"] = LLSD::String("account_logout"); - - mVivoxPump.post(levent); - break; - - default: - //Used to be a commented out warning - LL_WARNS("Voice") << "unknown account state event: " << state << LL_ENDL; - break; - } -} - -void LLVivoxVoiceClient::mediaCompletionEvent(std::string &sessionGroupHandle, std::string &mediaCompletionType) -{ - LLSD result; - - if (mediaCompletionType == "AuxBufferAudioCapture") - { - mCaptureBufferRecording = false; - result["recplay"] = "end"; - } - else if (mediaCompletionType == "AuxBufferAudioRender") - { - // Ignore all but the last stop event - if (--mPlayRequestCount <= 0) - { - mCaptureBufferPlaying = false; - result["recplay"] = "end"; -// result["recplay"] = "done"; - } - } - else - { - LL_WARNS("Voice") << "Unknown MediaCompletionType: " << mediaCompletionType << LL_ENDL; - } - - if (!result.isUndefined()) - mVivoxPump.post(result); -} - -void LLVivoxVoiceClient::mediaStreamUpdatedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - int statusCode, - std::string &statusString, - int state, - bool incoming) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - - LL_DEBUGS("Voice") << "session " << sessionHandle << ", status code " << statusCode << ", string \"" << statusString << "\"" << LL_ENDL; - - if(session) - { - // We know about this session - - // Save the state for later use - session->mMediaStreamState = state; - - switch(statusCode) - { - case 0: - case HTTP_OK: - // generic success - // Don't change the saved error code (it may have been set elsewhere) - break; - default: - // save the status code for later - session->mErrorStatusCode = statusCode; - break; - } - - switch(state) - { - case streamStateDisconnecting: - case streamStateIdle: - // Standard "left audio session", Vivox state 'disconnected' - session->mVoiceActive = false; - session->mMediaConnectInProgress = false; - leftAudioSession(session); - break; - - case streamStateConnected: - session->mVoiceActive = true; - session->mMediaConnectInProgress = false; - joinedAudioSession(session); - case streamStateConnecting: // do nothing, but prevents a warning getting into the logs. - break; - - case streamStateRinging: - if(incoming) - { - // Send the voice chat invite to the GUI layer - // TODO: Question: Should we correlate with the mute list here? - session->mIMSessionID = LLIMMgr::computeSessionID(IM_SESSION_P2P_INVITE, session->mCallerID); - session->mVoiceInvitePending = true; - if(session->mName.empty()) - { - lookupName(session->mCallerID); - } - else - { - // Act like we just finished resolving the name - avatarNameResolved(session->mCallerID, session->mName); - } - } - break; - - default: - LL_WARNS("Voice") << "unknown state " << state << LL_ENDL; - break; - - } - - } - else - { - // session disconnectintg and disconnected events arriving after we have already left the session. - LL_DEBUGS("Voice") << "session " << sessionHandle << " not found"<< LL_ENDL; - } -} - -void LLVivoxVoiceClient::participantAddedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - std::string &uriString, - std::string &alias, - std::string &nameString, - std::string &displayNameString, - int participantType) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - participantStatePtr_t participant(session->addParticipant(uriString)); - if(participant) - { - participant->mAccountName = nameString; - - LL_DEBUGS("Voice") << "added participant \"" << participant->mAccountName - << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; - - if(participant->mAvatarIDValid) - { - // Initiate a lookup - lookupName(participant->mAvatarID); - } - else - { - // If we don't have a valid avatar UUID, we need to fill in the display name to make the active speakers floater work. - std::string namePortion = nameFromsipURI(uriString); - if(namePortion.empty()) - { - // Problem with the SIP URI, fall back to the display name - namePortion = displayNameString; - } - if(namePortion.empty()) - { - // Problems with both of the above, fall back to the account name - namePortion = nameString; - } - - // Set the display name (which is a hint to the active speakers window not to do its own lookup) - participant->mDisplayName = namePortion; - avatarNameResolved(participant->mAvatarID, namePortion); - } - } - } -} - -void LLVivoxVoiceClient::participantRemovedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - std::string &uriString, - std::string &alias, - std::string &nameString) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - participantStatePtr_t participant(session->findParticipant(uriString)); - if(participant) - { - session->removeParticipant(participant); - } - else - { - LL_DEBUGS("Voice") << "unknown participant " << uriString << LL_ENDL; - } - } - else - { - // a late arriving event on a session we have already left. - LL_DEBUGS("Voice") << "unknown session " << sessionHandle << LL_ENDL; - } -} - - -void LLVivoxVoiceClient::participantUpdatedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - std::string &uriString, - std::string &alias, - bool isModeratorMuted, - bool isSpeaking, - int volume, - F32 energy) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - participantStatePtr_t participant(session->findParticipant(uriString)); - - if(participant) - { - //LL_INFOS("Voice") << "Participant Update for " << participant->mDisplayName << LL_ENDL; - - participant->mIsSpeaking = isSpeaking; - participant->mIsModeratorMuted = isModeratorMuted; - - // SLIM SDK: convert range: ensure that energy is set to zero if is_speaking is false - if (isSpeaking) - { - participant->mSpeakingTimeout.reset(); - participant->mPower = energy; - } - else - { - participant->mPower = 0.0f; - } - - // Ignore incoming volume level if it has been explicitly set, or there - // is a volume or mute change pending. - if ( !participant->mVolumeSet && !participant->mVolumeDirty) - { - participant->mVolume = (F32)volume * VOLUME_SCALE_VIVOX; - } - - // *HACK: mantipov: added while working on EXT-3544 - /* - Sometimes LLVoiceClient::participantUpdatedEvent callback is called BEFORE - LLViewerChatterBoxSessionAgentListUpdates::post() sometimes AFTER. - - participantUpdatedEvent updates voice participant state in particular participantState::mIsModeratorMuted - Originally we wanted to update session Speaker Manager to fire LLSpeakerVoiceModerationEvent to fix the EXT-3544 bug. - Calling of the LLSpeakerMgr::update() method was added into LLIMMgr::processAgentListUpdates. - - But in case participantUpdatedEvent() is called after LLViewerChatterBoxSessionAgentListUpdates::post() - voice participant mIsModeratorMuted is changed after speakers are updated in Speaker Manager - and event is not fired. - - So, we have to call LLSpeakerMgr::update() here. - */ - LLVoiceChannel* voice_cnl = LLVoiceChannel::getCurrentVoiceChannel(); - - // ignore session ID of local chat - if (voice_cnl && voice_cnl->getSessionID().notNull()) - { - LLSpeakerMgr* speaker_manager = LLIMModel::getInstance()->getSpeakerManager(voice_cnl->getSessionID()); - if (speaker_manager) - { - speaker_manager->update(true); - - // also initialize voice moderate_mode depend on Agent's participant. See EXT-6937. - // *TODO: remove once a way to request the current voice channel moderation mode is implemented. - if (gAgent.getID() == participant->mAvatarID) - { - speaker_manager->initVoiceModerateMode(); - } - } - } - } - else - { - LL_WARNS("Voice") << "unknown participant: " << uriString << LL_ENDL; - } - } - else - { - LL_DEBUGS("Voice") << "unknown session " << sessionHandle << LL_ENDL; - } -} - -void LLVivoxVoiceClient::messageEvent( - std::string &sessionHandle, - std::string &uriString, - std::string &alias, - std::string &messageHeader, - std::string &messageBody, - std::string &applicationString) -{ - LL_DEBUGS("Voice") << "Message event, session " << sessionHandle << " from " << uriString << LL_ENDL; -// LL_DEBUGS("Voice") << " header " << messageHeader << ", body: \n" << messageBody << LL_ENDL; - - LL_INFOS("Voice") << "Vivox raw message:" << std::endl << messageBody << LL_ENDL; - - if(messageHeader.find(HTTP_CONTENT_TEXT_HTML) != std::string::npos) - { - std::string message; - - { - const std::string startMarker = ", try looking for a instead. - start = messageBody.find(startSpan); - start = messageBody.find(startMarker2, start); - end = messageBody.find(endSpan); - - if(start != std::string::npos) - { - start += startMarker2.size(); - - if(end != std::string::npos) - end -= start; - - message.assign(messageBody, start, end); - } - } - } - -// LL_DEBUGS("Voice") << " raw message = \n" << message << LL_ENDL; - - // strip formatting tags - { - std::string::size_type start; - std::string::size_type end; - - while((start = message.find('<')) != std::string::npos) - { - if((end = message.find('>', start + 1)) != std::string::npos) - { - // Strip out the tag - message.erase(start, (end + 1) - start); - } - else - { - // Avoid an infinite loop - break; - } - } - } - - // Decode ampersand-escaped chars - { - std::string::size_type mark = 0; - - // The text may contain text encoded with <, >, and & - mark = 0; - while((mark = message.find("<", mark)) != std::string::npos) - { - message.replace(mark, 4, "<"); - mark += 1; - } - - mark = 0; - while((mark = message.find(">", mark)) != std::string::npos) - { - message.replace(mark, 4, ">"); - mark += 1; - } - - mark = 0; - while((mark = message.find("&", mark)) != std::string::npos) - { - message.replace(mark, 5, "&"); - mark += 1; - } - } - - // strip leading/trailing whitespace (since we always seem to get a couple newlines) - LLStringUtil::trim(message); - -// LL_DEBUGS("Voice") << " stripped message = \n" << message << LL_ENDL; - - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - bool is_do_not_disturb = gAgent.isDoNotDisturb(); - bool is_muted = LLMuteList::getInstance()->isMuted(session->mCallerID, session->mName, LLMute::flagTextChat); - bool is_linden = LLMuteList::isLinden(session->mName); - LLChat chat; - - chat.mMuted = is_muted && !is_linden; - - if(!chat.mMuted) - { - chat.mFromID = session->mCallerID; - chat.mFromName = session->mName; - chat.mSourceType = CHAT_SOURCE_AGENT; - - if(is_do_not_disturb && !is_linden) - { - // TODO: Question: Return do not disturb mode response here? Or maybe when session is started instead? - } - - LL_DEBUGS("Voice") << "adding message, name " << session->mName << " session " << session->mIMSessionID << ", target " << session->mCallerID << LL_ENDL; - LLIMMgr::getInstance()->addMessage(session->mIMSessionID, - session->mCallerID, - session->mName.c_str(), - message.c_str(), - false, - LLStringUtil::null, // default arg - IM_NOTHING_SPECIAL, // default arg - 0, // default arg - LLUUID::null, // default arg - LLVector3::zero); // default arg - } - } - } -} - -void LLVivoxVoiceClient::sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string ¬ificationType) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - - if(session) - { - participantStatePtr_t participant(session->findParticipant(uriString)); - if(participant) - { - if (!stricmp(notificationType.c_str(), "Typing")) - { - // Other end started typing - // TODO: The proper way to add a typing notification seems to be LLIMMgr::processIMTypingStart(). - // It requires some info for the message, which we don't have here. - } - else if (!stricmp(notificationType.c_str(), "NotTyping")) - { - // Other end stopped typing - // TODO: The proper way to remove a typing notification seems to be LLIMMgr::processIMTypingStop(). - // It requires some info for the message, which we don't have here. - } - else - { - LL_DEBUGS("Voice") << "Unknown notification type " << notificationType << "for participant " << uriString << " in session " << session->mSIPURI << LL_ENDL; - } - } - else - { - LL_DEBUGS("Voice") << "Unknown participant " << uriString << " in session " << session->mSIPURI << LL_ENDL; - } - } - else - { - LL_DEBUGS("Voice") << "Unknown session handle " << sessionHandle << LL_ENDL; - } -} - -void LLVivoxVoiceClient::voiceServiceConnectionStateChangedEvent(int statusCode, std::string &statusString, std::string &build_id) -{ - // We don't generally need to process this. However, one occurence is when we first connect, and so it is the - // earliest opportunity to learn what we're connected to. - if (statusCode) - { - LL_WARNS("Voice") << "VoiceServiceConnectionStateChangedEvent statusCode: " << statusCode << - "statusString: " << statusString << LL_ENDL; - return; - } - if (build_id.empty()) - { - return; - } - mVoiceVersion.mBuildVersion = build_id; -} - -void LLVivoxVoiceClient::auxAudioPropertiesEvent(F32 energy) -{ - LL_DEBUGS("VoiceEnergy") << "got energy " << energy << LL_ENDL; - mTuningEnergy = energy; -} - -void LLVivoxVoiceClient::muteListChanged() -{ - // The user's mute list has been updated. Go through the current participant list and sync it with the mute list. - if(mAudioSession) - { - participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin(); - - for(; iter != mAudioSession->mParticipantsByURI.end(); iter++) - { - participantStatePtr_t p(iter->second); - - // Check to see if this participant is on the mute list already - if(p->updateMuteState()) - mAudioSession->mVolumeDirty = true; - } - } -} - -///////////////////////////// -// Managing list of participants -LLVivoxVoiceClient::participantState::participantState(const std::string &uri) : - mURI(uri), - mPTT(false), - mIsSpeaking(false), - mIsModeratorMuted(false), - mLastSpokeTimestamp(0.f), - mPower(0.f), - mVolume(LLVoiceClient::VOLUME_DEFAULT), - mUserVolume(0), - mOnMuteList(false), - mVolumeSet(false), - mVolumeDirty(false), - mAvatarIDValid(false), - mIsSelf(false) -{ -} - -LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::addParticipant(const std::string &uri) -{ - participantStatePtr_t result; - bool useAlternateURI = false; - - // Note: this is mostly the body of LLVivoxVoiceClient::sessionState::findParticipant(), but since we need to know if it - // matched the alternate SIP URI (so we can add it properly), we need to reproduce it here. - { - participantMap::iterator iter = mParticipantsByURI.find(uri); - - if(iter == mParticipantsByURI.end()) - { - if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI)) - { - // This is a p2p session (probably with the SLIM client) with an alternate URI for the other participant. - // Use mSIPURI instead, since it will be properly encoded. - iter = mParticipantsByURI.find(mSIPURI); - useAlternateURI = true; - } - } - - if(iter != mParticipantsByURI.end()) - { - result = iter->second; - } - } - - if(!result) - { - // participant isn't already in one list or the other. - result.reset(new participantState(useAlternateURI?mSIPURI:uri)); - mParticipantsByURI.insert(participantMap::value_type(result->mURI, result)); - mParticipantsChanged = true; - - // Try to do a reverse transform on the URI to get the GUID back. - { - LLUUID id; - if(LLVivoxVoiceClient::getInstance()->IDFromName(result->mURI, id)) - { - result->mAvatarIDValid = true; - result->mAvatarID = id; - } - else - { - // Create a UUID by hashing the URI, but do NOT set mAvatarIDValid. - // This indicates that the ID will not be in the name cache. - result->mAvatarID.generate(uri); - } - } - - if(result->updateMuteState()) - { - mMuteDirty = true; - } - - mParticipantsByUUID.insert(participantUUIDMap::value_type(result->mAvatarID, result)); - - if (LLSpeakerVolumeStorage::getInstance()->getSpeakerVolume(result->mAvatarID, result->mVolume)) - { - result->mVolumeDirty = true; - mVolumeDirty = true; - } - - LL_DEBUGS("Voice") << "participant \"" << result->mURI << "\" added." << LL_ENDL; - } - - return result; -} - -bool LLVivoxVoiceClient::participantState::updateMuteState() -{ - bool result = false; - - bool isMuted = LLMuteList::getInstance()->isMuted(mAvatarID, LLMute::flagVoiceChat); - if(mOnMuteList != isMuted) - { - mOnMuteList = isMuted; - mVolumeDirty = true; - result = true; - } - return result; -} - -bool LLVivoxVoiceClient::participantState::isAvatar() -{ - return mAvatarIDValid; -} - -void LLVivoxVoiceClient::sessionState::removeParticipant(const LLVivoxVoiceClient::participantStatePtr_t &participant) -{ - if(participant) - { - participantMap::iterator iter = mParticipantsByURI.find(participant->mURI); - participantUUIDMap::iterator iter2 = mParticipantsByUUID.find(participant->mAvatarID); - - LL_DEBUGS("Voice") << "participant \"" << participant->mURI << "\" (" << participant->mAvatarID << ") removed." << LL_ENDL; - - if(iter == mParticipantsByURI.end()) - { - LL_WARNS("Voice") << "Internal error: participant " << participant->mURI << " not in URI map" << LL_ENDL; - } - else if(iter2 == mParticipantsByUUID.end()) - { - LL_WARNS("Voice") << "Internal error: participant ID " << participant->mAvatarID << " not in UUID map" << LL_ENDL; - } - else if(iter->second != iter2->second) - { - LL_WARNS("Voice") << "Internal error: participant mismatch!" << LL_ENDL; - } - else - { - mParticipantsByURI.erase(iter); - mParticipantsByUUID.erase(iter2); - - mParticipantsChanged = true; - } - } -} - -void LLVivoxVoiceClient::sessionState::removeAllParticipants() -{ - LL_DEBUGS("Voice") << "called" << LL_ENDL; - - while(!mParticipantsByURI.empty()) - { - removeParticipant(mParticipantsByURI.begin()->second); - } - - if(!mParticipantsByUUID.empty()) - { - LL_WARNS("Voice") << "Internal error: empty URI map, non-empty UUID map" << LL_ENDL; - } -} - -/*static*/ -void LLVivoxVoiceClient::sessionState::VerifySessions() -{ - std::set::iterator it = mSession.begin(); - while (it != mSession.end()) - { - if ((*it).expired()) - { - LL_WARNS("Voice") << "Expired session found! removing" << LL_ENDL; - it = mSession.erase(it); - } - else - ++it; - } -} - - -void LLVivoxVoiceClient::getParticipantList(std::set &participants) -{ - if(mAudioSession) - { - for(participantUUIDMap::iterator iter = mAudioSession->mParticipantsByUUID.begin(); - iter != mAudioSession->mParticipantsByUUID.end(); - iter++) - { - participants.insert(iter->first); - } - } -} - -bool LLVivoxVoiceClient::isParticipant(const LLUUID &speaker_id) -{ - if(mAudioSession) - { - return (mAudioSession->mParticipantsByUUID.find(speaker_id) != mAudioSession->mParticipantsByUUID.end()); - } - return false; -} - - -LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::findParticipant(const std::string &uri) -{ - participantStatePtr_t result; - - participantMap::iterator iter = mParticipantsByURI.find(uri); - - if(iter == mParticipantsByURI.end()) - { - if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI)) - { - // This is a p2p session (probably with the SLIM client) with an alternate URI for the other participant. - // Look up the other URI - iter = mParticipantsByURI.find(mSIPURI); - } - } - - if(iter != mParticipantsByURI.end()) - { - result = iter->second; - } - - return result; -} - -LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::findParticipantByID(const LLUUID& id) -{ - participantStatePtr_t result; - participantUUIDMap::iterator iter = mParticipantsByUUID.find(id); - - if(iter != mParticipantsByUUID.end()) - { - result = iter->second; - } - - return result; -} - -LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::findParticipantByID(const LLUUID& id) -{ - participantStatePtr_t result; - - if(mAudioSession) - { - result = mAudioSession->findParticipantByID(id); - } - - return result; -} - - - -// Check for parcel boundary crossing -bool LLVivoxVoiceClient::checkParcelChanged(bool update) -{ - LLViewerRegion *region = gAgent.getRegion(); - LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - - if(region && parcel) - { - S32 parcelLocalID = parcel->getLocalID(); - std::string regionName = region->getName(); - - // LL_DEBUGS("Voice") << "Region name = \"" << regionName << "\", parcel local ID = " << parcelLocalID << ", cap URI = \"" << capURI << "\"" << LL_ENDL; - - // The region name starts out empty and gets filled in later. - // Also, the cap gets filled in a short time after the region cross, but a little too late for our purposes. - // If either is empty, wait for the next time around. - if(!regionName.empty()) - { - if((parcelLocalID != mCurrentParcelLocalID) || (regionName != mCurrentRegionName)) - { - // We have changed parcels. Initiate a parcel channel lookup. - if (update) - { - mCurrentParcelLocalID = parcelLocalID; - mCurrentRegionName = regionName; - } - return true; - } - } - } - return false; -} - -bool LLVivoxVoiceClient::switchChannel( - std::string uri, - bool spatial, - bool no_reconnect, - bool is_p2p, - std::string hash) -{ - bool needsSwitch = !mIsInChannel; - - if (mIsInChannel) - { - if (mSessionTerminateRequested) - { - // If a terminate has been requested, we need to compare against where the URI we're already headed to. - if(mNextAudioSession) - { - if(mNextAudioSession->mSIPURI != uri) - needsSwitch = true; - } - else - { - // mNextAudioSession is null -- this probably means we're on our way back to spatial. - if(!uri.empty()) - { - // We do want to process a switch in this case. - needsSwitch = true; - } - } - } - else - { - // Otherwise, compare against the URI we're in now. - if(mAudioSession) - { - if(mAudioSession->mSIPURI != uri) - { - needsSwitch = true; - } - } - else - { - if(!uri.empty()) - { - // mAudioSession is null -- it's not clear what case would cause this. - // For now, log it as a warning and see if it ever crops up. - LL_WARNS("Voice") << "No current audio session... Forcing switch" << LL_ENDL; - needsSwitch = true; - } - } - } - } - - if(needsSwitch) - { - if(uri.empty()) - { - // Leave any channel we may be in - LL_DEBUGS("Voice") << "leaving channel" << LL_ENDL; - - sessionStatePtr_t oldSession = mNextAudioSession; - mNextAudioSession.reset(); - - // The old session may now need to be deleted. - reapSession(oldSession); - - // If voice was on, turn it off - if (LLVoiceClient::getInstance()->getUserPTTState()) - { - LLVoiceClient::getInstance()->setUserPTTState(false); - } - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); - } - else - { - LL_DEBUGS("Voice") << "switching to channel " << uri << LL_ENDL; - - mNextAudioSession = addSession(uri); - mNextAudioSession->mHash = hash; - mNextAudioSession->mIsSpatial = spatial; - mNextAudioSession->mReconnect = !no_reconnect; - mNextAudioSession->mIsP2P = is_p2p; - } - - if (mIsInChannel) - { - // If we're already in a channel, or if we're joining one, terminate - // so we can rejoin with the new session data. - sessionTerminate(); - } - } - - return needsSwitch; -} - -void LLVivoxVoiceClient::joinSession(const sessionStatePtr_t &session) -{ - mNextAudioSession = session; - - if (mIsInChannel) - { - // If we're already in a channel, or if we're joining one, terminate - // so we can rejoin with the new session data. - sessionTerminate(); - } -} - -void LLVivoxVoiceClient::setNonSpatialChannel( - const std::string &uri, - const std::string &credentials) -{ - switchChannel(uri, false, false, false, credentials); -} - -bool LLVivoxVoiceClient::setSpatialChannel( - const std::string &uri, - const std::string &credentials) -{ - mSpatialSessionURI = uri; - mSpatialSessionCredentials = credentials; - mAreaVoiceDisabled = mSpatialSessionURI.empty(); - - LL_DEBUGS("Voice") << "got spatial channel uri: \"" << uri << "\"" << LL_ENDL; - - if((mIsInChannel && mAudioSession && !(mAudioSession->mIsSpatial)) || (mNextAudioSession && !(mNextAudioSession->mIsSpatial))) - { - // User is in a non-spatial chat or joining a non-spatial chat. Don't switch channels. - LL_INFOS("Voice") << "in non-spatial chat, not switching channels" << LL_ENDL; - return false; - } - else - { - return switchChannel(mSpatialSessionURI, true, false, false, mSpatialSessionCredentials); - } -} - -void LLVivoxVoiceClient::callUser(const LLUUID &uuid) -{ - std::string userURI = sipURIFromID(uuid); - - switchChannel(userURI, false, true, true); -} - -#if 0 -// Vivox text IMs are not in use. -LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::startUserIMSession(const LLUUID &uuid) -{ - // Figure out if a session with the user already exists - sessionStatePtr_t session(findSession(uuid)); - if(!session) - { - // No session with user, need to start one. - std::string uri = sipURIFromID(uuid); - session = addSession(uri); - - llassert(session); - if (!session) - return session; - - session->mIsSpatial = false; - session->mReconnect = false; - session->mIsP2P = true; - session->mCallerID = uuid; - } - - if(session->mHandle.empty()) - { - // Session isn't active -- start it up. - sessionCreateSendMessage(session, false, false); - } - else - { - // Session is already active -- start up text. - sessionTextConnectSendMessage(session); - } - - return session; -} -#endif - -void LLVivoxVoiceClient::endUserIMSession(const LLUUID &uuid) -{ -#if 0 - // Vivox text IMs are not in use. - - // Figure out if a session with the user exists - sessionStatePtr_t session(findSession(uuid)); - if(session) - { - // found the session - if(!session->mHandle.empty()) - { - // sessionTextDisconnectSendMessage(session); // a SLim leftover, not used any more. - } - } - else - { - LL_DEBUGS("Voice") << "Session not found for participant ID " << uuid << LL_ENDL; - } -#endif -} -bool LLVivoxVoiceClient::isValidChannel(std::string &sessionHandle) -{ - return(findSession(sessionHandle) != NULL); - -} -bool LLVivoxVoiceClient::answerInvite(std::string &sessionHandle) -{ - // this is only ever used to answer incoming p2p call invites. - - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - session->mIsSpatial = false; - session->mReconnect = false; - session->mIsP2P = true; - - joinSession(session); - return true; - } - - return false; -} - -bool LLVivoxVoiceClient::isVoiceWorking() const -{ - - //Added stateSessionTerminated state to avoid problems with call in parcels with disabled voice (EXT-4758) - // Condition with joining spatial num was added to take into account possible problems with connection to voice - // server(EXT-4313). See bug descriptions and comments for MAX_NORMAL_JOINING_SPATIAL_NUM for more info. - return (mSpatialJoiningNum < MAX_NORMAL_JOINING_SPATIAL_NUM) && mIsProcessingChannels; -// return (mSpatialJoiningNum < MAX_NORMAL_JOINING_SPATIAL_NUM) && (stateLoggedIn <= mState) && (mState <= stateSessionTerminated); -} - -// Returns true if the indicated participant in the current audio session is really an SL avatar. -// Currently this will be false only for PSTN callers into group chats, and PSTN p2p calls. -bool LLVivoxVoiceClient::isParticipantAvatar(const LLUUID &id) -{ - bool result = true; - sessionStatePtr_t session(findSession(id)); - - if(session) - { - // this is a p2p session with the indicated caller, or the session with the specified UUID. - if(session->mSynthesizedCallerID) - result = false; - } - else - { - // Didn't find a matching session -- check the current audio session for a matching participant - if(mAudioSession) - { - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->isAvatar(); - } - } - } - - return result; -} - -// Returns true if calling back the session URI after the session has closed is possible. -// Currently this will be false only for PSTN P2P calls. -bool LLVivoxVoiceClient::isSessionCallBackPossible(const LLUUID &session_id) -{ - bool result = true; - sessionStatePtr_t session(findSession(session_id)); - - if(session != NULL) - { - result = session->isCallBackPossible(); - } - - return result; -} - -// Returns true if the session can accept text IM's. -// Currently this will be false only for PSTN P2P calls. -bool LLVivoxVoiceClient::isSessionTextIMPossible(const LLUUID &session_id) -{ - bool result = true; - sessionStatePtr_t session(findSession(session_id)); - - if(session != NULL) - { - result = session->isTextIMPossible(); - } - - return result; -} - - -void LLVivoxVoiceClient::declineInvite(std::string &sessionHandle) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - sessionMediaDisconnectSendMessage(session); - } -} - -void LLVivoxVoiceClient::leaveNonSpatialChannel() -{ - LL_DEBUGS("Voice") << "Request to leave spacial channel." << LL_ENDL; - - // Make sure we don't rejoin the current session. - sessionStatePtr_t oldNextSession(mNextAudioSession); - mNextAudioSession.reset(); - - // Most likely this will still be the current session at this point, but check it anyway. - reapSession(oldNextSession); - - verifySessionState(); - - sessionTerminate(); -} - -std::string LLVivoxVoiceClient::getCurrentChannel() -{ - std::string result; - - if (mIsInChannel && !mSessionTerminateRequested) - { - result = getAudioSessionURI(); - } - - return result; -} - -bool LLVivoxVoiceClient::inProximalChannel() -{ - bool result = false; - - if (mIsInChannel && !mSessionTerminateRequested) - { - result = inSpatialChannel(); - } - - return result; -} - -std::string LLVivoxVoiceClient::sipURIFromID(const LLUUID &id) -{ - std::string result; - result = "sip:"; - result += nameFromID(id); - result += "@"; - result += mVoiceSIPURIHostName; - - return result; -} - -std::string LLVivoxVoiceClient::sipURIFromAvatar(LLVOAvatar *avatar) -{ - std::string result; - if(avatar) - { - result = "sip:"; - result += nameFromID(avatar->getID()); - result += "@"; - result += mVoiceSIPURIHostName; - } - - return result; -} - -std::string LLVivoxVoiceClient::nameFromAvatar(LLVOAvatar *avatar) -{ - std::string result; - if(avatar) - { - result = nameFromID(avatar->getID()); - } - return result; -} - -std::string LLVivoxVoiceClient::nameFromID(const LLUUID &uuid) -{ - std::string result; - - if (uuid.isNull()) { - //VIVOX, the uuid emtpy look for the mURIString and return that instead. - //result.assign(uuid.mURIStringName); - LLStringUtil::replaceChar(result, '_', ' '); - return result; - } - // Prepending this apparently prevents conflicts with reserved names inside the vivox code. - result = "x"; - - // Base64 encode and replace the pieces of base64 that are less compatible - // with e-mail local-parts. - // See RFC-4648 "Base 64 Encoding with URL and Filename Safe Alphabet" - result += LLBase64::encode(uuid.mData, UUID_BYTES); - LLStringUtil::replaceChar(result, '+', '-'); - LLStringUtil::replaceChar(result, '/', '_'); - - // If you need to transform a GUID to this form on the macOS command line, this will do so: - // echo -n x && (echo e669132a-6c43-4ee1-a78d-6c82fff59f32 |xxd -r -p |openssl base64|tr '/+' '_-') - - // The reverse transform can be done with: - // echo 'x5mkTKmxDTuGnjWyC__WfMg==' |cut -b 2- -|tr '_-' '/+' |openssl base64 -d|xxd -p - - return result; -} - -bool LLVivoxVoiceClient::IDFromName(const std::string inName, LLUUID &uuid) -{ - bool result = false; - - // SLIM SDK: The "name" may actually be a SIP URI such as: "sip:xFnPP04IpREWNkuw1cOXlhw==@bhr.vivox.com" - // If it is, convert to a bare name before doing the transform. - std::string name = nameFromsipURI(inName); - - // Doesn't look like a SIP URI, assume it's an actual name. - if(name.empty()) - name = inName; - - // This will only work if the name is of the proper form. - // As an example, the account name for Monroe Linden (UUID 1673cfd3-8229-4445-8d92-ec3570e5e587) is: - // "xFnPP04IpREWNkuw1cOXlhw==" - - if((name.size() == 25) && (name[0] == 'x') && (name[23] == '=') && (name[24] == '=')) - { - // The name appears to have the right form. - - // Reverse the transforms done by nameFromID - std::string temp = name; - LLStringUtil::replaceChar(temp, '-', '+'); - LLStringUtil::replaceChar(temp, '_', '/'); - - U8 rawuuid[UUID_BYTES + 1]; - int len = apr_base64_decode_binary(rawuuid, temp.c_str() + 1); - if(len == UUID_BYTES) - { - // The decode succeeded. Stuff the bits into the result's UUID - memcpy(uuid.mData, rawuuid, UUID_BYTES); - result = true; - } - } - - if(!result) - { - // VIVOX: not a standard account name, just copy the URI name mURIString field - // and hope for the best. bpj - uuid.setNull(); // VIVOX, set the uuid field to nulls - } - - return result; -} - -std::string LLVivoxVoiceClient::displayNameFromAvatar(LLVOAvatar *avatar) -{ - return avatar->getFullname(); -} - -std::string LLVivoxVoiceClient::sipURIFromName(std::string &name) -{ - std::string result; - result = "sip:"; - result += name; - result += "@"; - result += mVoiceSIPURIHostName; - -// LLStringUtil::toLower(result); - - return result; -} - -std::string LLVivoxVoiceClient::nameFromsipURI(const std::string &uri) -{ - std::string result; - - std::string::size_type sipOffset, atOffset; - sipOffset = uri.find("sip:"); - atOffset = uri.find("@"); - if((sipOffset != std::string::npos) && (atOffset != std::string::npos)) - { - result = uri.substr(sipOffset + 4, atOffset - (sipOffset + 4)); - } - - return result; -} - -bool LLVivoxVoiceClient::inSpatialChannel(void) -{ - bool result = false; - - if(mAudioSession) - { - result = mAudioSession->mIsSpatial; - } - - return result; -} - -std::string LLVivoxVoiceClient::getAudioSessionURI() -{ - std::string result; - - if(mAudioSession) - result = mAudioSession->mSIPURI; - - return result; -} - -std::string LLVivoxVoiceClient::getAudioSessionHandle() -{ - std::string result; - - if(mAudioSession) - result = mAudioSession->mHandle; - - return result; -} - - -///////////////////////////// -// Sending updates of current state - -void LLVivoxVoiceClient::enforceTether(void) -{ - LLVector3d tethered = mCameraRequestedPosition; - - // constrain 'tethered' to within 50m of mAvatarPosition. - { - F32 max_dist = 50.0f; - LLVector3d camera_offset = mCameraRequestedPosition - mAvatarPosition; - F32 camera_distance = (F32)camera_offset.magVec(); - if(camera_distance > max_dist) - { - tethered = mAvatarPosition + - (max_dist / camera_distance) * camera_offset; - } - } - - if(dist_vec_squared(mCameraPosition, tethered) > 0.01) - { - mCameraPosition = tethered; - mSpatialCoordsDirty = true; - } -} - -void LLVivoxVoiceClient::updatePosition(void) -{ - - LLViewerRegion *region = gAgent.getRegion(); - if(region && isAgentAvatarValid()) - { - LLMatrix3 rot; - LLVector3d pos; - LLQuaternion qrot; - - // TODO: If camera and avatar velocity are actually used by the voice system, we could compute them here... - // They're currently always set to zero. - - // Send the current camera position to the voice code - rot.setRows(LLViewerCamera::getInstance()->getAtAxis(), LLViewerCamera::getInstance()->getLeftAxis (), LLViewerCamera::getInstance()->getUpAxis()); - pos = gAgent.getRegion()->getPosGlobalFromRegion(LLViewerCamera::getInstance()->getOrigin()); - - LLVivoxVoiceClient::getInstance()->setCameraPosition( - pos, // position - LLVector3::zero, // velocity - rot); // rotation matrix - - // Send the current avatar position to the voice code - qrot = gAgentAvatarp->getRootJoint()->getWorldRotation(); - pos = gAgentAvatarp->getPositionGlobal(); - - // TODO: Can we get the head offset from outside the LLVOAvatar? - // pos += LLVector3d(mHeadOffset); - pos += LLVector3d(0.f, 0.f, 1.f); - - LLVivoxVoiceClient::getInstance()->setAvatarPosition( - pos, // position - LLVector3::zero, // velocity - qrot); // rotation matrix - } -} - -void LLVivoxVoiceClient::setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot) -{ - mCameraRequestedPosition = position; - - if(mCameraVelocity != velocity) - { - mCameraVelocity = velocity; - mSpatialCoordsDirty = true; - } - - if(mCameraRot != rot) - { - mCameraRot = rot; - mSpatialCoordsDirty = true; - } -} - -void LLVivoxVoiceClient::setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLQuaternion &rot) -{ - if(dist_vec_squared(mAvatarPosition, position) > 0.01) - { - mAvatarPosition = position; - mSpatialCoordsDirty = true; - } - - if(mAvatarVelocity != velocity) - { - mAvatarVelocity = velocity; - mSpatialCoordsDirty = true; - } - - // If the two rotations are not exactly equal test their dot product - // to get the cos of the angle between them. - // If it is too small, don't update. - F32 rot_cos_diff = llabs(dot(mAvatarRot, rot)); - if ((mAvatarRot != rot) && (rot_cos_diff < MINUSCULE_ANGLE_COS)) - { - mAvatarRot = rot; - mSpatialCoordsDirty = true; - } -} - -bool LLVivoxVoiceClient::channelFromRegion(LLViewerRegion *region, std::string &name) -{ - bool result = false; - - if(region) - { - name = region->getName(); - } - - if(!name.empty()) - result = true; - - return result; -} - -void LLVivoxVoiceClient::leaveChannel(void) -{ - if (mIsInChannel) - { - LL_DEBUGS("Voice") << "leaving channel for teleport/logout" << LL_ENDL; - mChannelName.clear(); - sessionTerminate(); - } -} - -void LLVivoxVoiceClient::setMuteMic(bool muted) -{ - if(mMuteMic != muted) - { - mMuteMic = muted; - mMuteMicDirty = true; - } -} - -void LLVivoxVoiceClient::setVoiceEnabled(bool enabled) -{ - LL_DEBUGS("Voice") - << "( " << (enabled ? "enabled" : "disabled") << " )" - << " was "<< (mVoiceEnabled ? "enabled" : "disabled") - << " coro "<< (mIsCoroutineActive ? "active" : "inactive") - << LL_ENDL; - - if (enabled != mVoiceEnabled) - { - // TODO: Refactor this so we don't call into LLVoiceChannel, but simply - // use the status observer - mVoiceEnabled = enabled; - LLVoiceClientStatusObserver::EStatusType status; - - if (enabled) - { - LL_DEBUGS("Voice") << "enabling" << LL_ENDL; - LLVoiceChannel::getCurrentVoiceChannel()->activate(); - status = LLVoiceClientStatusObserver::STATUS_VOICE_ENABLED; - - if (!mIsCoroutineActive) - { - LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro", - boost::bind(&LLVivoxVoiceClient::voiceControlCoro, LLVivoxVoiceClient::getInstance())); - } - else - { - LL_DEBUGS("Voice") << "coro should be active.. not launching" << LL_ENDL; - } - } - else - { - // Turning voice off looses your current channel -- this makes sure the UI isn't out of sync when you re-enable it. - LLVoiceChannel::getCurrentVoiceChannel()->deactivate(); - gAgent.setVoiceConnected(false); - status = LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED; - } - - notifyStatusObservers(status); - } - else - { - LL_DEBUGS("Voice") << " no-op" << LL_ENDL; - } -} - -bool LLVivoxVoiceClient::voiceEnabled() -{ - return gSavedSettings.getBOOL("EnableVoiceChat") && - !gSavedSettings.getBOOL("CmdLineDisableVoice") && - !gNonInteractive; -} - -void LLVivoxVoiceClient::setLipSyncEnabled(bool enabled) -{ - mLipSyncEnabled = enabled; -} - -bool LLVivoxVoiceClient::lipSyncEnabled() -{ - - if ( mVoiceEnabled ) - { - return mLipSyncEnabled; - } - else - { - return false; - } -} - - -void LLVivoxVoiceClient::setEarLocation(S32 loc) -{ - if(mEarLocation != loc) - { - LL_DEBUGS("Voice") << "Setting mEarLocation to " << loc << LL_ENDL; - - mEarLocation = loc; - mSpatialCoordsDirty = true; - } -} - -void LLVivoxVoiceClient::setVoiceVolume(F32 volume) -{ - int scaled_volume = scale_speaker_volume(volume); - - if(scaled_volume != mSpeakerVolume) - { - int min_volume = scale_speaker_volume(0); - if((scaled_volume == min_volume) || (mSpeakerVolume == min_volume)) - { - mSpeakerMuteDirty = true; - } - - mSpeakerVolume = scaled_volume; - mSpeakerVolumeDirty = true; - } -} - -void LLVivoxVoiceClient::setMicGain(F32 volume) -{ - int scaled_volume = scale_mic_volume(volume); - - if(scaled_volume != mMicVolume) - { - mMicVolume = scaled_volume; - mMicVolumeDirty = true; - } -} - -///////////////////////////// -// Accessors for data related to nearby speakers -bool LLVivoxVoiceClient::getVoiceEnabled(const LLUUID& id) -{ - bool result = false; - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - // I'm not sure what the semantics of this should be. - // For now, if we have any data about the user that came through the chat channel, assume they're voice-enabled. - result = true; - } - - return result; -} - -std::string LLVivoxVoiceClient::getDisplayName(const LLUUID& id) -{ - std::string result; - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->mDisplayName; - } - - return result; -} - - - -bool LLVivoxVoiceClient::getIsSpeaking(const LLUUID& id) -{ - bool result = false; - - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT) - { - participant->mIsSpeaking = false; - } - result = participant->mIsSpeaking; - } - - return result; -} - -bool LLVivoxVoiceClient::getIsModeratorMuted(const LLUUID& id) -{ - bool result = false; - - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->mIsModeratorMuted; - } - - return result; -} - -F32 LLVivoxVoiceClient::getCurrentPower(const LLUUID& id) -{ - F32 result = 0; - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->mPower; - } - - return result; -} - - - -bool LLVivoxVoiceClient::getUsingPTT(const LLUUID& id) -{ - bool result = false; - - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - // I'm not sure what the semantics of this should be. - // Does "using PTT" mean they're configured with a push-to-talk button? - // For now, we know there's no PTT mechanism in place, so nobody is using it. - } - - return result; -} - -bool LLVivoxVoiceClient::getOnMuteList(const LLUUID& id) -{ - bool result = false; - - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->mOnMuteList; - } - - return result; -} - -// External accessors. -F32 LLVivoxVoiceClient::getUserVolume(const LLUUID& id) -{ - // Minimum volume will be returned for users with voice disabled - F32 result = LLVoiceClient::VOLUME_MIN; - - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->mVolume; - - // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging. - // LL_DEBUGS("Voice") << "mVolume = " << result << " for " << id << LL_ENDL; - } - - return result; -} - -void LLVivoxVoiceClient::setUserVolume(const LLUUID& id, F32 volume) -{ - if(mAudioSession) - { - participantStatePtr_t participant(findParticipantByID(id)); - if (participant && !participant->mIsSelf) - { - if (!is_approx_equal(volume, LLVoiceClient::VOLUME_DEFAULT)) - { - // Store this volume setting for future sessions if it has been - // changed from the default - LLSpeakerVolumeStorage::getInstance()->storeSpeakerVolume(id, volume); - } - else - { - // Remove stored volume setting if it is returned to the default - LLSpeakerVolumeStorage::getInstance()->removeSpeakerVolume(id); - } - - participant->mVolume = llclamp(volume, LLVoiceClient::VOLUME_MIN, LLVoiceClient::VOLUME_MAX); - participant->mVolumeDirty = true; - mAudioSession->mVolumeDirty = true; - } - } -} - -std::string LLVivoxVoiceClient::getGroupID(const LLUUID& id) -{ - std::string result; - - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->mGroupID; - } - - return result; -} - -bool LLVivoxVoiceClient::getAreaVoiceDisabled() -{ - return mAreaVoiceDisabled; -} - -void LLVivoxVoiceClient::recordingLoopStart(int seconds, int deltaFramesPerControlFrame) -{ -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Start)" << LL_ENDL; - - if(!mMainSessionGroupHandle.empty()) - { - std::ostringstream stream; - stream - << "" - << "" << mMainSessionGroupHandle << "" - << "Start" - << "" << deltaFramesPerControlFrame << "" - << "" << "" << "" - << "false" - << "" << seconds << "" - << "\n\n\n"; - - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::recordingLoopSave(const std::string& filename) -{ -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Flush)" << LL_ENDL; - - if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) - { - std::ostringstream stream; - stream - << "" - << "" << mMainSessionGroupHandle << "" - << "Flush" - << "" << filename << "" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::recordingStop() -{ -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Stop)" << LL_ENDL; - - if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) - { - std::ostringstream stream; - stream - << "" - << "" << mMainSessionGroupHandle << "" - << "Stop" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::filePlaybackStart(const std::string& filename) -{ -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Start)" << LL_ENDL; - - if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) - { - std::ostringstream stream; - stream - << "" - << "" << mMainSessionGroupHandle << "" - << "Start" - << "" << filename << "" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::filePlaybackStop() -{ -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Stop)" << LL_ENDL; - - if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) - { - std::ostringstream stream; - stream - << "" - << "" << mMainSessionGroupHandle << "" - << "Stop" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::filePlaybackSetPaused(bool paused) -{ - // TODO: Implement once Vivox gives me a sample -} - -void LLVivoxVoiceClient::filePlaybackSetMode(bool vox, float speed) -{ - // TODO: Implement once Vivox gives me a sample -} - -//------------------------------------------------------------------------ -std::set> LLVivoxVoiceClient::sessionState::mSession; - - -LLVivoxVoiceClient::sessionState::sessionState() : - mErrorStatusCode(0), - mMediaStreamState(streamStateUnknown), - mCreateInProgress(false), - mMediaConnectInProgress(false), - mVoiceInvitePending(false), - mTextInvitePending(false), - mSynthesizedCallerID(false), - mIsChannel(false), - mIsSpatial(false), - mIsP2P(false), - mIncoming(false), - mVoiceActive(false), - mReconnect(false), - mVolumeDirty(false), - mMuteDirty(false), - mParticipantsChanged(false) -{ -} - -/*static*/ -LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::createSession() -{ - sessionState::ptr_t ptr(new sessionState()); - - std::pair::iterator, bool> result = mSession.insert(ptr); - - if (result.second) - ptr->mMyIterator = result.first; - - return ptr; -} - -LLVivoxVoiceClient::sessionState::~sessionState() -{ - LL_INFOS("Voice") << "Destroying session handle=" << mHandle << " SIP=" << mSIPURI << LL_ENDL; - if (mMyIterator != mSession.end()) - mSession.erase(mMyIterator); - - removeAllParticipants(); -} - -bool LLVivoxVoiceClient::sessionState::isCallBackPossible() -{ - // This may change to be explicitly specified by vivox in the future... - // Currently, only PSTN P2P calls cannot be returned. - // Conveniently, this is also the only case where we synthesize a caller UUID. - return !mSynthesizedCallerID; -} - -bool LLVivoxVoiceClient::sessionState::isTextIMPossible() -{ - // This may change to be explicitly specified by vivox in the future... - return !mSynthesizedCallerID; -} - - -/*static*/ -LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchSessionByHandle(const std::string &handle) -{ - sessionStatePtr_t result; - - // *TODO: My kingdom for a lambda! - std::set::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testByHandle, _1, handle)); - - if (it != mSession.end()) - result = (*it).lock(); - - return result; -} - -/*static*/ -LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchCreatingSessionByURI(const std::string &uri) -{ - sessionStatePtr_t result; - - // *TODO: My kingdom for a lambda! - std::set::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testByCreatingURI, _1, uri)); - - if (it != mSession.end()) - result = (*it).lock(); - - return result; -} - -/*static*/ -LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchSessionByURI(const std::string &uri) -{ - sessionStatePtr_t result; - - // *TODO: My kingdom for a lambda! - std::set::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testBySIPOrAlterateURI, _1, uri)); - - if (it != mSession.end()) - result = (*it).lock(); - - return result; -} - -/*static*/ -LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchSessionByParticipant(const LLUUID &participant_id) -{ - sessionStatePtr_t result; - - // *TODO: My kingdom for a lambda! - std::set::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testByCallerId, _1, participant_id)); - - if (it != mSession.end()) - result = (*it).lock(); - - return result; -} - -void LLVivoxVoiceClient::sessionState::for_each(sessionFunc_t func) -{ - std::for_each(mSession.begin(), mSession.end(), boost::bind(for_eachPredicate, _1, func)); -} - -// simple test predicates. -// *TODO: These should be made into lambdas when we can pull the trigger on newer C++ features. -bool LLVivoxVoiceClient::sessionState::testByHandle(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string handle) -{ - ptr_t aLock(a.lock()); - - return aLock ? aLock->mHandle == handle : false; -} - -bool LLVivoxVoiceClient::sessionState::testByCreatingURI(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string uri) -{ - ptr_t aLock(a.lock()); - - return aLock ? (aLock->mCreateInProgress && (aLock->mSIPURI == uri)) : false; -} - -bool LLVivoxVoiceClient::sessionState::testBySIPOrAlterateURI(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string uri) -{ - ptr_t aLock(a.lock()); - - return aLock ? ((aLock->mSIPURI == uri) || (aLock->mAlternateSIPURI == uri)) : false; -} - - -bool LLVivoxVoiceClient::sessionState::testByCallerId(const LLVivoxVoiceClient::sessionState::wptr_t &a, LLUUID participantId) -{ - ptr_t aLock(a.lock()); - - return aLock ? ((aLock->mCallerID == participantId) || (aLock->mIMSessionID == participantId)) : false; -} - -/*static*/ -void LLVivoxVoiceClient::sessionState::for_eachPredicate(const LLVivoxVoiceClient::sessionState::wptr_t &a, sessionFunc_t func) -{ - ptr_t aLock(a.lock()); - - if (aLock) - func(aLock); - else - { - LL_WARNS("Voice") << "Stale handle in session map!" << LL_ENDL; - } -} - - - -LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::findSession(const std::string &handle) -{ - sessionStatePtr_t result; - sessionMap::iterator iter = mSessionsByHandle.find(handle); - if(iter != mSessionsByHandle.end()) - { - result = iter->second; - } - - return result; -} - -LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::findSessionBeingCreatedByURI(const std::string &uri) -{ - sessionStatePtr_t result = sessionState::matchCreatingSessionByURI(uri); - - return result; -} - -LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::findSession(const LLUUID &participant_id) -{ - sessionStatePtr_t result = sessionState::matchSessionByParticipant(participant_id); - - return result; -} - -LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::addSession(const std::string &uri, const std::string &handle) -{ - sessionStatePtr_t result; - - if(handle.empty()) - { - // No handle supplied. - // Check whether there's already a session with this URI - result = sessionState::matchSessionByURI(uri); - } - else // (!handle.empty()) - { - // Check for an existing session with this handle - sessionMap::iterator iter = mSessionsByHandle.find(handle); - - if(iter != mSessionsByHandle.end()) - { - result = iter->second; - } - } - - if(!result) - { - // No existing session found. - - LL_DEBUGS("Voice") << "adding new session: handle \"" << handle << "\" URI " << uri << LL_ENDL; - result = sessionState::createSession(); - result->mSIPURI = uri; - result->mHandle = handle; - - if (LLVoiceClient::instance().getVoiceEffectEnabled()) - { - result->mVoiceFontID = LLVoiceClient::instance().getVoiceEffectDefault(); - } - - if(!result->mHandle.empty()) - { - // *TODO: Rider: This concerns me. There is a path (via switchChannel) where - // we do not track the session. In theory this means that we could end up with - // a mAuidoSession that does not match the session tracked in mSessionsByHandle - mSessionsByHandle.insert(sessionMap::value_type(result->mHandle, result)); - } - } - else - { - // Found an existing session - - if(uri != result->mSIPURI) - { - // TODO: Should this be an internal error? - LL_DEBUGS("Voice") << "changing uri from " << result->mSIPURI << " to " << uri << LL_ENDL; - setSessionURI(result, uri); - } - - if(handle != result->mHandle) - { - if(handle.empty()) - { - // There's at least one race condition where where addSession was clearing an existing session handle, which caused things to break. - LL_DEBUGS("Voice") << "NOT clearing handle " << result->mHandle << LL_ENDL; - } - else - { - // TODO: Should this be an internal error? - LL_DEBUGS("Voice") << "changing handle from " << result->mHandle << " to " << handle << LL_ENDL; - setSessionHandle(result, handle); - } - } - - LL_DEBUGS("Voice") << "returning existing session: handle " << handle << " URI " << uri << LL_ENDL; - } - - verifySessionState(); - - return result; -} - -void LLVivoxVoiceClient::clearSessionHandle(const sessionStatePtr_t &session) -{ - if (session) - { - if (!session->mHandle.empty()) - { - sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle); - if (iter != mSessionsByHandle.end()) - { - mSessionsByHandle.erase(iter); - } - } - else - { - LL_WARNS("Voice") << "Session has empty handle!" << LL_ENDL; - } - } - else - { - LL_WARNS("Voice") << "Attempt to clear NULL session!" << LL_ENDL; - } - -} - -void LLVivoxVoiceClient::setSessionHandle(const sessionStatePtr_t &session, const std::string &handle) -{ - // Have to remove the session from the handle-indexed map before changing the handle, or things will break badly. - - if(!session->mHandle.empty()) - { - // Remove session from the map if it should have been there. - sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle); - if(iter != mSessionsByHandle.end()) - { - if(iter->second != session) - { - LL_WARNS("Voice") << "Internal error: session mismatch! Session may have been duplicated. Removing version in map." << LL_ENDL; - } - - mSessionsByHandle.erase(iter); - } - else - { - LL_WARNS("Voice") << "Attempt to remove session with handle " << session->mHandle << " not found in map!" << LL_ENDL; - } - } - - session->mHandle = handle; - - if(!handle.empty()) - { - mSessionsByHandle.insert(sessionMap::value_type(session->mHandle, session)); - } - - verifySessionState(); -} - -void LLVivoxVoiceClient::setSessionURI(const sessionStatePtr_t &session, const std::string &uri) -{ - // There used to be a map of session URIs to sessions, which made this complex.... - session->mSIPURI = uri; - - verifySessionState(); -} - -void LLVivoxVoiceClient::deleteSession(const sessionStatePtr_t &session) -{ - // Remove the session from the handle map - if(!session->mHandle.empty()) - { - sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle); - if(iter != mSessionsByHandle.end()) - { - if(iter->second != session) - { - LL_WARNS("Voice") << "Internal error: session mismatch, removing session in map." << LL_ENDL; - } - mSessionsByHandle.erase(iter); - } - } - - // At this point, the session should be unhooked from all lists and all state should be consistent. - verifySessionState(); - - // If this is the current audio session, clean up the pointer which will soon be dangling. - if(mAudioSession == session) - { - mAudioSession.reset(); - mAudioSessionChanged = true; - } - - // ditto for the next audio session - if(mNextAudioSession == session) - { - mNextAudioSession.reset(); - } - -} - -void LLVivoxVoiceClient::deleteAllSessions() -{ - LL_DEBUGS("Voice") << LL_ENDL; - - while (!mSessionsByHandle.empty()) - { - const sessionStatePtr_t session = mSessionsByHandle.begin()->second; - deleteSession(session); - } - -} - -void LLVivoxVoiceClient::verifySessionState(void) -{ - LL_DEBUGS("Voice") << "Sessions in handle map=" << mSessionsByHandle.size() << LL_ENDL; - sessionState::VerifySessions(); -} - -void LLVivoxVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer) -{ - mParticipantObservers.insert(observer); -} - -void LLVivoxVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer) -{ - mParticipantObservers.erase(observer); -} - -void LLVivoxVoiceClient::notifyParticipantObservers() -{ - for (observer_set_t::iterator it = mParticipantObservers.begin(); - it != mParticipantObservers.end(); - ) - { - LLVoiceClientParticipantObserver* observer = *it; - observer->onParticipantsChanged(); - // In case onParticipantsChanged() deleted an entry. - it = mParticipantObservers.upper_bound(observer); - } -} - -void LLVivoxVoiceClient::addObserver(LLVoiceClientStatusObserver* observer) -{ - mStatusObservers.insert(observer); -} - -void LLVivoxVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer) -{ - mStatusObservers.erase(observer); -} - -void LLVivoxVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status) -{ - LL_DEBUGS("Voice") << "( " << LLVoiceClientStatusObserver::status2string(status) << " )" - << " mAudioSession=" << mAudioSession - << LL_ENDL; - - if(mAudioSession) - { - if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN) - { - switch(mAudioSession->mErrorStatusCode) - { - case 20713: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_FULL; break; - case 20714: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_LOCKED; break; - case 20715: - //invalid channel, we may be using a set of poorly cached - //info - status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; - break; - case 1009: - //invalid username and password - status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; - break; - } - - // Reset the error code to make sure it won't be reused later by accident. - mAudioSession->mErrorStatusCode = 0; - } - else if(status == LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL) - { - switch(mAudioSession->mErrorStatusCode) - { - case HTTP_NOT_FOUND: // NOT_FOUND - // *TODO: Should this be 503? - case 480: // TEMPORARILY_UNAVAILABLE - case HTTP_REQUEST_TIME_OUT: // REQUEST_TIMEOUT - // call failed because other user was not available - // treat this as an error case - status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; - - // Reset the error code to make sure it won't be reused later by accident. - mAudioSession->mErrorStatusCode = 0; - break; - } - } - } - - LL_DEBUGS("Voice") - << " " << LLVoiceClientStatusObserver::status2string(status) - << ", session URI " << getAudioSessionURI() - << ", proximal is " << inSpatialChannel() - << LL_ENDL; - - for (status_observer_set_t::iterator it = mStatusObservers.begin(); - it != mStatusObservers.end(); - ) - { - LLVoiceClientStatusObserver* observer = *it; - observer->onChange(status, getAudioSessionURI(), inSpatialChannel()); - // In case onError() deleted an entry. - it = mStatusObservers.upper_bound(observer); - } - - // skipped to avoid speak button blinking - if ( status != LLVoiceClientStatusObserver::STATUS_JOINING - && status != LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL - && status != LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED) - { - bool voice_status = LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking(); - - gAgent.setVoiceConnected(voice_status); - - if (voice_status) - { - LLFirstUse::speak(true); - } - } -} - -void LLVivoxVoiceClient::addObserver(LLFriendObserver* observer) -{ - mFriendObservers.insert(observer); -} - -void LLVivoxVoiceClient::removeObserver(LLFriendObserver* observer) -{ - mFriendObservers.erase(observer); -} - -void LLVivoxVoiceClient::notifyFriendObservers() -{ - for (friend_observer_set_t::iterator it = mFriendObservers.begin(); - it != mFriendObservers.end(); - ) - { - LLFriendObserver* observer = *it; - it++; - // The only friend-related thing we notify on is online/offline transitions. - observer->changed(LLFriendObserver::ONLINE); - } -} - -void LLVivoxVoiceClient::lookupName(const LLUUID &id) -{ - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - mAvatarNameCacheConnection = LLAvatarNameCache::get(id, boost::bind(&LLVivoxVoiceClient::onAvatarNameCache, this, _1, _2)); -} - -void LLVivoxVoiceClient::onAvatarNameCache(const LLUUID& agent_id, - const LLAvatarName& av_name) -{ - mAvatarNameCacheConnection.disconnect(); - std::string display_name = av_name.getDisplayName(); - avatarNameResolved(agent_id, display_name); -} - -void LLVivoxVoiceClient::predAvatarNameResolution(const LLVivoxVoiceClient::sessionStatePtr_t &session, LLUUID id, std::string name) -{ - participantStatePtr_t participant(session->findParticipantByID(id)); - if (participant) - { - // Found -- fill in the name - participant->mAccountName = name; - // and post a "participants updated" message to listeners later. - session->mParticipantsChanged = true; - } - - // Check whether this is a p2p session whose caller name just resolved - if (session->mCallerID == id) - { - // this session's "caller ID" just resolved. Fill in the name. - session->mName = name; - if (session->mTextInvitePending) - { - session->mTextInvitePending = false; - - // We don't need to call LLIMMgr::getInstance()->addP2PSession() here. The first incoming message will create the panel. - } - if (session->mVoiceInvitePending) - { - session->mVoiceInvitePending = false; - - LLIMMgr::getInstance()->inviteToSession( - session->mIMSessionID, - session->mName, - session->mCallerID, - session->mName, - IM_SESSION_P2P_INVITE, - LLIMMgr::INVITATION_TYPE_VOICE, - session->mHandle, - session->mSIPURI); - } - - } -} - -void LLVivoxVoiceClient::avatarNameResolved(const LLUUID &id, const std::string &name) -{ - sessionState::for_each(boost::bind(predAvatarNameResolution, _1, id, name)); -} - -bool LLVivoxVoiceClient::setVoiceEffect(const LLUUID& id) -{ - if (!mAudioSession) - { - return false; - } - - if (!id.isNull()) - { - if (mVoiceFontMap.empty()) - { - LL_DEBUGS("Voice") << "Voice fonts not available." << LL_ENDL; - return false; - } - else if (mVoiceFontMap.find(id) == mVoiceFontMap.end()) - { - LL_DEBUGS("Voice") << "Invalid voice font " << id << LL_ENDL; - return false; - } - } - - // *TODO: Check for expired fonts? - mAudioSession->mVoiceFontID = id; - - // *TODO: Separate voice font defaults for spatial chat and IM? - gSavedPerAccountSettings.setString("VoiceEffectDefault", id.asString()); - - sessionSetVoiceFontSendMessage(mAudioSession); - notifyVoiceFontObservers(); - - return true; -} - -const LLUUID LLVivoxVoiceClient::getVoiceEffect() -{ - return mAudioSession ? mAudioSession->mVoiceFontID : LLUUID::null; -} - -LLSD LLVivoxVoiceClient::getVoiceEffectProperties(const LLUUID& id) -{ - LLSD sd; - - voice_font_map_t::iterator iter = mVoiceFontMap.find(id); - if (iter != mVoiceFontMap.end()) - { - sd["template_only"] = false; - } - else - { - // Voice effect is not in the voice font map, see if there is a template - iter = mVoiceFontTemplateMap.find(id); - if (iter == mVoiceFontTemplateMap.end()) - { - LL_WARNS("Voice") << "Voice effect " << id << "not found." << LL_ENDL; - return sd; - } - sd["template_only"] = true; - } - - voiceFontEntry *font = iter->second; - sd["name"] = font->mName; - sd["expiry_date"] = font->mExpirationDate; - sd["is_new"] = font->mIsNew; - - return sd; -} - -LLVivoxVoiceClient::voiceFontEntry::voiceFontEntry(LLUUID& id) : - mID(id), - mFontIndex(0), - mFontType(VOICE_FONT_TYPE_NONE), - mFontStatus(VOICE_FONT_STATUS_NONE), - mIsNew(false) -{ - mExpiryTimer.stop(); - mExpiryWarningTimer.stop(); -} - -LLVivoxVoiceClient::voiceFontEntry::~voiceFontEntry() -{ -} - -void LLVivoxVoiceClient::refreshVoiceEffectLists(bool clear_lists) -{ - if (clear_lists) - { - mVoiceFontsReceived = false; - deleteAllVoiceFonts(); - deleteVoiceFontTemplates(); - } - - accountGetSessionFontsSendMessage(); - accountGetTemplateFontsSendMessage(); -} - -const voice_effect_list_t& LLVivoxVoiceClient::getVoiceEffectList() const -{ - return mVoiceFontList; -} - -const voice_effect_list_t& LLVivoxVoiceClient::getVoiceEffectTemplateList() const -{ - return mVoiceFontTemplateList; -} - -void LLVivoxVoiceClient::addVoiceFont(const S32 font_index, - const std::string &name, - const std::string &description, - const LLDate &expiration_date, - bool has_expired, - const S32 font_type, - const S32 font_status, - const bool template_font) -{ - // Vivox SessionFontIDs are not guaranteed to remain the same between - // sessions or grids so use a UUID for the name. - - // If received name is not a UUID, fudge one by hashing the name and type. - LLUUID font_id; - if (LLUUID::validate(name)) - { - font_id = LLUUID(name); - } - else - { - font_id.generate(STRINGIZE(font_type << ":" << name)); - } - - voiceFontEntry *font = NULL; - - voice_font_map_t& font_map = template_font ? mVoiceFontTemplateMap : mVoiceFontMap; - voice_effect_list_t& font_list = template_font ? mVoiceFontTemplateList : mVoiceFontList; - - // Check whether we've seen this font before. - voice_font_map_t::iterator iter = font_map.find(font_id); - bool new_font = (iter == font_map.end()); - - // Override the has_expired flag if we have passed the expiration_date as a double check. - if (expiration_date.secondsSinceEpoch() < (LLDate::now().secondsSinceEpoch() + VOICE_FONT_EXPIRY_INTERVAL)) - { - has_expired = true; - } - - if (has_expired) - { - LL_DEBUGS("VoiceFont") << "Expired " << (template_font ? "Template " : "") - << expiration_date.asString() << " " << font_id - << " (" << font_index << ") " << name << LL_ENDL; - - // Remove existing session fonts that have expired since we last saw them. - if (!new_font && !template_font) - { - deleteVoiceFont(font_id); - } - return; - } - - if (new_font) - { - // If it is a new font create a new entry. - font = new voiceFontEntry(font_id); - } - else - { - // Not a new font, update the existing entry - font = iter->second; - } - - if (font) - { - font->mFontIndex = font_index; - // Use the description for the human readable name if available, as the - // "name" may be a UUID. - font->mName = description.empty() ? name : description; - font->mFontType = font_type; - font->mFontStatus = font_status; - - // If the font is new or the expiration date has changed the expiry timers need updating. - if (!template_font && (new_font || font->mExpirationDate != expiration_date)) - { - font->mExpirationDate = expiration_date; - - // Set the expiry timer to trigger a notification when the voice font can no longer be used. - font->mExpiryTimer.start(); - font->mExpiryTimer.setExpiryAt(expiration_date.secondsSinceEpoch() - VOICE_FONT_EXPIRY_INTERVAL); - - // Set the warning timer to some interval before actual expiry. - S32 warning_time = gSavedSettings.getS32("VoiceEffectExpiryWarningTime"); - if (warning_time != 0) - { - font->mExpiryWarningTimer.start(); - F64 expiry_time = (expiration_date.secondsSinceEpoch() - (F64)warning_time); - font->mExpiryWarningTimer.setExpiryAt(expiry_time - VOICE_FONT_EXPIRY_INTERVAL); - } - else - { - // Disable the warning timer. - font->mExpiryWarningTimer.stop(); - } - - // Only flag new session fonts after the first time we have fetched the list. - if (mVoiceFontsReceived) - { - font->mIsNew = true; - mVoiceFontsNew = true; - } - } - - LL_DEBUGS("VoiceFont") << (template_font ? "Template " : "") - << font->mExpirationDate.asString() << " " << font->mID - << " (" << font->mFontIndex << ") " << name << LL_ENDL; - - if (new_font) - { - font_map.insert(voice_font_map_t::value_type(font->mID, font)); - font_list.insert(voice_effect_list_t::value_type(font->mName, font->mID)); - } - - mVoiceFontListDirty = true; - - // Debugging stuff - - if (font_type < VOICE_FONT_TYPE_NONE || font_type >= VOICE_FONT_TYPE_UNKNOWN) - { - LL_WARNS("VoiceFont") << "Unknown voice font type: " << font_type << LL_ENDL; - } - if (font_status < VOICE_FONT_STATUS_NONE || font_status >= VOICE_FONT_STATUS_UNKNOWN) - { - LL_WARNS("VoiceFont") << "Unknown voice font status: " << font_status << LL_ENDL; - } - } -} - -void LLVivoxVoiceClient::expireVoiceFonts() -{ - // *TODO: If we are selling voice fonts in packs, there are probably - // going to be a number of fonts with the same expiration time, so would - // be more efficient to just keep a list of expiration times rather - // than checking each font individually. - - bool have_expired = false; - bool will_expire = false; - bool expired_in_use = false; - - LLUUID current_effect = LLVoiceClient::instance().getVoiceEffectDefault(); - - voice_font_map_t::iterator iter; - for (iter = mVoiceFontMap.begin(); iter != mVoiceFontMap.end(); ++iter) - { - voiceFontEntry* voice_font = iter->second; - LLFrameTimer& expiry_timer = voice_font->mExpiryTimer; - LLFrameTimer& warning_timer = voice_font->mExpiryWarningTimer; - - // Check for expired voice fonts - if (expiry_timer.getStarted() && expiry_timer.hasExpired()) - { - // Check whether it is the active voice font - if (voice_font->mID == current_effect) - { - // Reset to no voice effect. - setVoiceEffect(LLUUID::null); - expired_in_use = true; - } - - LL_DEBUGS("Voice") << "Voice Font " << voice_font->mName << " has expired." << LL_ENDL; - deleteVoiceFont(voice_font->mID); - have_expired = true; - } - - // Check for voice fonts that will expire in less that the warning time - if (warning_timer.getStarted() && warning_timer.hasExpired()) - { - LL_DEBUGS("VoiceFont") << "Voice Font " << voice_font->mName << " will expire soon." << LL_ENDL; - will_expire = true; - warning_timer.stop(); - } - } - - LLSD args; - args["URL"] = LLTrans::getString("voice_morphing_url"); - args["PREMIUM_URL"] = LLTrans::getString("premium_voice_morphing_url"); - - // Give a notification if any voice fonts have expired. - if (have_expired) - { - if (expired_in_use) - { - LLNotificationsUtil::add("VoiceEffectsExpiredInUse", args); - } - else - { - LLNotificationsUtil::add("VoiceEffectsExpired", args); - } - - // Refresh voice font lists in the UI. - notifyVoiceFontObservers(); - } - - // Give a warning notification if any voice fonts are due to expire. - if (will_expire) - { - S32Seconds seconds(gSavedSettings.getS32("VoiceEffectExpiryWarningTime")); - args["INTERVAL"] = llformat("%d", LLUnit(seconds).value()); - - LLNotificationsUtil::add("VoiceEffectsWillExpire", args); - } -} - -void LLVivoxVoiceClient::deleteVoiceFont(const LLUUID& id) -{ - // Remove the entry from the voice font list. - voice_effect_list_t::iterator list_iter = mVoiceFontList.begin(); - while (list_iter != mVoiceFontList.end()) - { - if (list_iter->second == id) - { - LL_DEBUGS("VoiceFont") << "Removing " << id << " from the voice font list." << LL_ENDL; - list_iter = mVoiceFontList.erase(list_iter); - mVoiceFontListDirty = true; - } - else - { - ++list_iter; - } - } - - // Find the entry in the voice font map and erase its data. - voice_font_map_t::iterator map_iter = mVoiceFontMap.find(id); - if (map_iter != mVoiceFontMap.end()) - { - delete map_iter->second; - } - - // Remove the entry from the voice font map. - mVoiceFontMap.erase(map_iter); -} - -void LLVivoxVoiceClient::deleteAllVoiceFonts() -{ - mVoiceFontList.clear(); - - voice_font_map_t::iterator iter; - for (iter = mVoiceFontMap.begin(); iter != mVoiceFontMap.end(); ++iter) - { - delete iter->second; - } - mVoiceFontMap.clear(); -} - -void LLVivoxVoiceClient::deleteVoiceFontTemplates() -{ - mVoiceFontTemplateList.clear(); - - voice_font_map_t::iterator iter; - for (iter = mVoiceFontTemplateMap.begin(); iter != mVoiceFontTemplateMap.end(); ++iter) - { - delete iter->second; - } - mVoiceFontTemplateMap.clear(); -} - -S32 LLVivoxVoiceClient::getVoiceFontIndex(const LLUUID& id) const -{ - S32 result = 0; - if (!id.isNull()) - { - voice_font_map_t::const_iterator it = mVoiceFontMap.find(id); - if (it != mVoiceFontMap.end()) - { - result = it->second->mFontIndex; - } - else - { - LL_WARNS("VoiceFont") << "Selected voice font " << id << " is not available." << LL_ENDL; - } - } - return result; -} - -S32 LLVivoxVoiceClient::getVoiceFontTemplateIndex(const LLUUID& id) const -{ - S32 result = 0; - if (!id.isNull()) - { - voice_font_map_t::const_iterator it = mVoiceFontTemplateMap.find(id); - if (it != mVoiceFontTemplateMap.end()) - { - result = it->second->mFontIndex; - } - else - { - LL_WARNS("VoiceFont") << "Selected voice font template " << id << " is not available." << LL_ENDL; - } - } - return result; -} - -void LLVivoxVoiceClient::accountGetSessionFontsSendMessage() -{ - if(mAccountLoggedIn) - { - std::ostringstream stream; - - LL_DEBUGS("VoiceFont") << "Requesting voice font list." << LL_ENDL; - - stream - << "" - << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" - << "" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::accountGetTemplateFontsSendMessage() -{ - if(mAccountLoggedIn) - { - std::ostringstream stream; - - LL_DEBUGS("VoiceFont") << "Requesting voice font template list." << LL_ENDL; - - stream - << "" - << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" - << "" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::sessionSetVoiceFontSendMessage(const sessionStatePtr_t &session) -{ - S32 font_index = getVoiceFontIndex(session->mVoiceFontID); - LL_DEBUGS("VoiceFont") << "Requesting voice font: " << session->mVoiceFontID << " (" << font_index << "), session handle: " << session->mHandle << LL_ENDL; - - std::ostringstream stream; - - stream - << "" - << "" << session->mHandle << "" - << "" << font_index << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::accountGetSessionFontsResponse(int statusCode, const std::string &statusString) -{ - if (mIsWaitingForFonts) - { - // *TODO: We seem to get multiple events of this type. Should figure a way to advance only after - // receiving the last one. - LLSD result(LLSDMap("voice_fonts", LLSD::Boolean(true))); - - mVivoxPump.post(result); - } - notifyVoiceFontObservers(); - mVoiceFontsReceived = true; -} - -void LLVivoxVoiceClient::accountGetTemplateFontsResponse(int statusCode, const std::string &statusString) -{ - // Voice font list entries were updated via addVoiceFont() during parsing. - notifyVoiceFontObservers(); -} -void LLVivoxVoiceClient::addObserver(LLVoiceEffectObserver* observer) -{ - mVoiceFontObservers.insert(observer); -} - -void LLVivoxVoiceClient::removeObserver(LLVoiceEffectObserver* observer) -{ - mVoiceFontObservers.erase(observer); -} - -// method checks the item in VoiceMorphing menu for appropriate current voice font -bool LLVivoxVoiceClient::onCheckVoiceEffect(const std::string& voice_effect_name) -{ - LLVoiceEffectInterface * effect_interfacep = LLVoiceClient::instance().getVoiceEffectInterface(); - if (NULL != effect_interfacep) - { - const LLUUID& currect_voice_effect_id = effect_interfacep->getVoiceEffect(); - - if (currect_voice_effect_id.isNull()) - { - if (voice_effect_name == "NoVoiceMorphing") - { - return true; - } - } - else - { - const LLSD& voice_effect_props = effect_interfacep->getVoiceEffectProperties(currect_voice_effect_id); - if (voice_effect_props["name"].asString() == voice_effect_name) - { - return true; - } - } - } - - return false; -} - -// method changes voice font for selected VoiceMorphing menu item -void LLVivoxVoiceClient::onClickVoiceEffect(const std::string& voice_effect_name) -{ - LLVoiceEffectInterface * effect_interfacep = LLVoiceClient::instance().getVoiceEffectInterface(); - if (NULL != effect_interfacep) - { - if (voice_effect_name == "NoVoiceMorphing") - { - effect_interfacep->setVoiceEffect(LLUUID()); - return; - } - const voice_effect_list_t& effect_list = effect_interfacep->getVoiceEffectList(); - if (!effect_list.empty()) - { - for (voice_effect_list_t::const_iterator it = effect_list.begin(); it != effect_list.end(); ++it) - { - if (voice_effect_name == it->first) - { - effect_interfacep->setVoiceEffect(it->second); - return; - } - } - } - } -} - -// it updates VoiceMorphing menu items in accordance with purchased properties -void LLVivoxVoiceClient::updateVoiceMorphingMenu() -{ - if (mVoiceFontListDirty) - { - LLVoiceEffectInterface * effect_interfacep = LLVoiceClient::instance().getVoiceEffectInterface(); - if (effect_interfacep) - { - const voice_effect_list_t& effect_list = effect_interfacep->getVoiceEffectList(); - if (!effect_list.empty()) - { - LLMenuGL * voice_morphing_menup = gMenuBarView->findChildMenuByName("VoiceMorphing", true); - - if (NULL != voice_morphing_menup) - { - S32 items = voice_morphing_menup->getItemCount(); - if (items > 0) - { - voice_morphing_menup->erase(1, items - 3, false); - - S32 pos = 1; - for (voice_effect_list_t::const_iterator it = effect_list.begin(); it != effect_list.end(); ++it) - { - LLMenuItemCheckGL::Params p; - p.name = it->first; - p.label = it->first; - p.on_check.function(boost::bind(&LLVivoxVoiceClient::onCheckVoiceEffect, this, it->first)); - p.on_click.function(boost::bind(&LLVivoxVoiceClient::onClickVoiceEffect, this, it->first)); - LLMenuItemCheckGL * voice_effect_itemp = LLUICtrlFactory::create(p); - voice_morphing_menup->insert(pos++, voice_effect_itemp, false); - } - - voice_morphing_menup->needsArrange(); - } - } - } - } - } -} -void LLVivoxVoiceClient::notifyVoiceFontObservers() -{ - LL_DEBUGS("VoiceFont") << "Notifying voice effect observers. Lists changed: " << mVoiceFontListDirty << LL_ENDL; - - updateVoiceMorphingMenu(); - - for (voice_font_observer_set_t::iterator it = mVoiceFontObservers.begin(); - it != mVoiceFontObservers.end();) - { - LLVoiceEffectObserver* observer = *it; - observer->onVoiceEffectChanged(mVoiceFontListDirty); - // In case onVoiceEffectChanged() deleted an entry. - it = mVoiceFontObservers.upper_bound(observer); - } - mVoiceFontListDirty = false; - - // If new Voice Fonts have been added notify the user. - if (mVoiceFontsNew) - { - if (mVoiceFontsReceived) - { - LLNotificationsUtil::add("VoiceEffectsNew"); - } - mVoiceFontsNew = false; - } -} - -void LLVivoxVoiceClient::enablePreviewBuffer(bool enable) -{ - LLSD result; - mCaptureBufferMode = enable; - - if (enable) - result["recplay"] = "start"; - else - result["recplay"] = "quit"; - - mVivoxPump.post(result); - - if(mCaptureBufferMode && mIsInChannel) - { - LL_DEBUGS("Voice") << "no channel" << LL_ENDL; - sessionTerminate(); - } -} - -void LLVivoxVoiceClient::recordPreviewBuffer() -{ - if (!mCaptureBufferMode) - { - LL_DEBUGS("Voice") << "Not in voice effect preview mode, cannot start recording." << LL_ENDL; - mCaptureBufferRecording = false; - return; - } - - mCaptureBufferRecording = true; - - LLSD result(LLSDMap("recplay", "record")); - mVivoxPump.post(result); -} - -void LLVivoxVoiceClient::playPreviewBuffer(const LLUUID& effect_id) -{ - if (!mCaptureBufferMode) - { - LL_DEBUGS("Voice") << "Not in voice effect preview mode, no buffer to play." << LL_ENDL; - mCaptureBufferRecording = false; - return; - } - - if (!mCaptureBufferRecorded) - { - // Can't play until we have something recorded! - mCaptureBufferPlaying = false; - return; - } - - mPreviewVoiceFont = effect_id; - mCaptureBufferPlaying = true; - - LLSD result(LLSDMap("recplay", "playback")); - mVivoxPump.post(result); -} - -void LLVivoxVoiceClient::stopPreviewBuffer() -{ - mCaptureBufferRecording = false; - mCaptureBufferPlaying = false; - - LLSD result(LLSDMap("recplay", "quit")); - mVivoxPump.post(result); -} - -bool LLVivoxVoiceClient::isPreviewRecording() -{ - return (mCaptureBufferMode && mCaptureBufferRecording); -} - -bool LLVivoxVoiceClient::isPreviewPlaying() -{ - return (mCaptureBufferMode && mCaptureBufferPlaying); -} - -void LLVivoxVoiceClient::captureBufferRecordStartSendMessage() -{ if(mAccountLoggedIn) - { - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Starting audio capture to buffer." << LL_ENDL; - - // Start capture - stream - << "" - << "" - << "\n\n\n"; - - // Unmute the mic - stream << "" - << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" - << "false" - << "\n\n\n"; - - // Dirty the mute mic state so that it will get reset when we finishing previewing - mMuteMicDirty = true; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::captureBufferRecordStopSendMessage() -{ - if(mAccountLoggedIn) - { - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Stopping audio capture to buffer." << LL_ENDL; - - // Mute the mic. Mic mute state was dirtied at recording start, so will be reset when finished previewing. - stream << "" - << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" - << "true" - << "\n\n\n"; - - // Stop capture - stream - << "" - << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" - << "" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::captureBufferPlayStartSendMessage(const LLUUID& voice_font_id) -{ - if(mAccountLoggedIn) - { - // Track how may play requests are sent, so we know how many stop events to - // expect before play actually stops. - ++mPlayRequestCount; - - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Starting audio buffer playback." << LL_ENDL; - - S32 font_index = getVoiceFontTemplateIndex(voice_font_id); - LL_DEBUGS("Voice") << "With voice font: " << voice_font_id << " (" << font_index << ")" << LL_ENDL; - - stream - << "" - << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" - << "" << font_index << "" - << "" - << "" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::captureBufferPlayStopSendMessage() -{ - if(mAccountLoggedIn) - { - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Stopping audio buffer playback." << LL_ENDL; - - stream - << "" - << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" - << "" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -LLVivoxProtocolParser::LLVivoxProtocolParser() -{ - parser = XML_ParserCreate(NULL); - - reset(); -} - -void LLVivoxProtocolParser::reset() -{ - responseDepth = 0; - ignoringTags = false; - accumulateText = false; - energy = 0.f; - hasText = false; - hasAudio = false; - hasVideo = false; - terminated = false; - ignoreDepth = 0; - isChannel = false; - incoming = false; - enabled = false; - isEvent = false; - isLocallyMuted = false; - isModeratorMuted = false; - isSpeaking = false; - participantType = 0; - returnCode = -1; - state = 0; - statusCode = 0; - volume = 0; - textBuffer.clear(); - alias.clear(); - numberOfAliases = 0; - applicationString.clear(); -} - -//virtual -LLVivoxProtocolParser::~LLVivoxProtocolParser() -{ - if (parser) - XML_ParserFree(parser); -} - -static LLTrace::BlockTimerStatHandle FTM_VIVOX_PROCESS("Vivox Process"); - -// virtual -LLIOPipe::EStatus LLVivoxProtocolParser::process_impl( - const LLChannelDescriptors& channels, - buffer_ptr_t& buffer, - bool& eos, - LLSD& context, - LLPumpIO* pump) -{ - LL_RECORD_BLOCK_TIME(FTM_VIVOX_PROCESS); - LLBufferStream istr(channels, buffer.get()); - std::ostringstream ostr; - while (istr.good()) - { - char buf[1024]; - istr.read(buf, sizeof(buf)); - mInput.append(buf, istr.gcount()); - } - - // Look for input delimiter(s) in the input buffer. If one is found, send the message to the xml parser. - int start = 0; - int delim; - while((delim = mInput.find("\n\n\n", start)) != std::string::npos) - { - - // Reset internal state of the LLVivoxProtocolParser (no effect on the expat parser) - reset(); - - XML_ParserReset(parser, NULL); - XML_SetElementHandler(parser, ExpatStartTag, ExpatEndTag); - XML_SetCharacterDataHandler(parser, ExpatCharHandler); - XML_SetUserData(parser, this); - XML_Parse(parser, mInput.data() + start, delim - start, false); - - LL_DEBUGS("VivoxProtocolParser") << "parsing: " << mInput.substr(start, delim - start) << LL_ENDL; - start = delim + 3; - } - - if(start != 0) - mInput = mInput.substr(start); - - LL_DEBUGS("VivoxProtocolParser") << "at end, mInput is: " << mInput << LL_ENDL; - - if(!LLVivoxVoiceClient::sConnected) - { - // If voice has been disabled, we just want to close the socket. This does so. - LL_INFOS("Voice") << "returning STATUS_STOP" << LL_ENDL; - return STATUS_STOP; - } - - return STATUS_OK; -} - -void XMLCALL LLVivoxProtocolParser::ExpatStartTag(void *data, const char *el, const char **attr) -{ - if (data) - { - LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data; - object->StartTag(el, attr); - } -} - -// -------------------------------------------------------------------------------- - -void XMLCALL LLVivoxProtocolParser::ExpatEndTag(void *data, const char *el) -{ - if (data) - { - LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data; - object->EndTag(el); - } -} - -// -------------------------------------------------------------------------------- - -void XMLCALL LLVivoxProtocolParser::ExpatCharHandler(void *data, const XML_Char *s, int len) -{ - if (data) - { - LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data; - object->CharData(s, len); - } -} - -// -------------------------------------------------------------------------------- - - -void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr) -{ - // Reset the text accumulator. We shouldn't have strings that are inturrupted by new tags - textBuffer.clear(); - // only accumulate text if we're not ignoring tags. - accumulateText = !ignoringTags; - - if (responseDepth == 0) - { - isEvent = !stricmp("Event", tag); - - if (!stricmp("Response", tag) || isEvent) - { - // Grab the attributes - while (*attr) - { - const char *key = *attr++; - const char *value = *attr++; - - if (!stricmp("requestId", key)) - { - requestId = value; - } - else if (!stricmp("action", key)) - { - actionString = value; - } - else if (!stricmp("type", key)) - { - eventTypeString = value; - } - } - } - LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL; - } - else - { - if (ignoringTags) - { - LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; - } - else - { - LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL; - - // Ignore the InputXml stuff so we don't get confused - if (!stricmp("InputXml", tag)) - { - ignoringTags = true; - ignoreDepth = responseDepth; - accumulateText = false; - - LL_DEBUGS("VivoxProtocolParser") << "starting ignore, ignoreDepth is " << ignoreDepth << LL_ENDL; - } - else if (!stricmp("CaptureDevices", tag)) - { - LLVivoxVoiceClient::getInstance()->clearCaptureDevices(); - } - else if (!stricmp("RenderDevices", tag)) - { - LLVivoxVoiceClient::getInstance()->clearRenderDevices(); - } - else if (!stricmp("CaptureDevice", tag)) - { - deviceString.clear(); - } - else if (!stricmp("RenderDevice", tag)) - { - deviceString.clear(); - } - else if (!stricmp("SessionFont", tag)) - { - id = 0; - nameString.clear(); - descriptionString.clear(); - expirationDate = LLDate(); - hasExpired = false; - fontType = 0; - fontStatus = 0; - } - else if (!stricmp("TemplateFont", tag)) - { - id = 0; - nameString.clear(); - descriptionString.clear(); - expirationDate = LLDate(); - hasExpired = false; - fontType = 0; - fontStatus = 0; - } - else if (!stricmp("MediaCompletionType", tag)) - { - mediaCompletionType.clear(); - } - } - } - responseDepth++; -} - -// -------------------------------------------------------------------------------- - -void LLVivoxProtocolParser::EndTag(const char *tag) -{ - const std::string& string = textBuffer; - - responseDepth--; - - if (ignoringTags) - { - if (ignoreDepth == responseDepth) - { - LL_DEBUGS("VivoxProtocolParser") << "end of ignore" << LL_ENDL; - ignoringTags = false; - } - else - { - LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; - } - } - - if (!ignoringTags) - { - LL_DEBUGS("VivoxProtocolParser") << "processing tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; - - // Closing a tag. Finalize the text we've accumulated and reset - if (!stricmp("ReturnCode", tag)) - returnCode = strtol(string.c_str(), NULL, 10); - else if (!stricmp("SessionHandle", tag)) - sessionHandle = string; - else if (!stricmp("SessionGroupHandle", tag)) - sessionGroupHandle = string; - else if (!stricmp("StatusCode", tag)) - statusCode = strtol(string.c_str(), NULL, 10); - else if (!stricmp("StatusString", tag)) - statusString = string; - else if (!stricmp("ParticipantURI", tag)) - uriString = string; - else if (!stricmp("Volume", tag)) - volume = strtol(string.c_str(), NULL, 10); - else if (!stricmp("Energy", tag)) - energy = (F32)strtod(string.c_str(), NULL); - else if (!stricmp("IsModeratorMuted", tag)) - isModeratorMuted = !stricmp(string.c_str(), "true"); - else if (!stricmp("IsSpeaking", tag)) - isSpeaking = !stricmp(string.c_str(), "true"); - else if (!stricmp("Alias", tag)) - alias = string; - else if (!stricmp("NumberOfAliases", tag)) - numberOfAliases = strtol(string.c_str(), NULL, 10); - else if (!stricmp("Application", tag)) - applicationString = string; - else if (!stricmp("ConnectorHandle", tag)) - connectorHandle = string; - else if (!stricmp("VersionID", tag)) - versionID = string; - else if (!stricmp("Version", tag)) - mBuildID = string; - else if (!stricmp("AccountHandle", tag)) - accountHandle = string; - else if (!stricmp("State", tag)) - state = strtol(string.c_str(), NULL, 10); - else if (!stricmp("URI", tag)) - uriString = string; - else if (!stricmp("IsChannel", tag)) - isChannel = !stricmp(string.c_str(), "true"); - else if (!stricmp("Incoming", tag)) - incoming = !stricmp(string.c_str(), "true"); - else if (!stricmp("Enabled", tag)) - enabled = !stricmp(string.c_str(), "true"); - else if (!stricmp("Name", tag)) - nameString = string; - else if (!stricmp("AudioMedia", tag)) - audioMediaString = string; - else if (!stricmp("ChannelName", tag)) - nameString = string; - else if (!stricmp("DisplayName", tag)) - displayNameString = string; - else if (!stricmp("Device", tag)) - deviceString = string; - else if (!stricmp("AccountName", tag)) - nameString = string; - else if (!stricmp("ParticipantType", tag)) - participantType = strtol(string.c_str(), NULL, 10); - else if (!stricmp("IsLocallyMuted", tag)) - isLocallyMuted = !stricmp(string.c_str(), "true"); - else if (!stricmp("MicEnergy", tag)) - energy = (F32)strtod(string.c_str(), NULL); - else if (!stricmp("ChannelName", tag)) - nameString = string; - else if (!stricmp("ChannelURI", tag)) - uriString = string; - else if (!stricmp("BuddyURI", tag)) - uriString = string; - else if (!stricmp("Presence", tag)) - statusString = string; - else if (!stricmp("CaptureDevices", tag)) - { - LLVivoxVoiceClient::getInstance()->setDevicesListUpdated(true); - } - else if (!stricmp("RenderDevices", tag)) - { - LLVivoxVoiceClient::getInstance()->setDevicesListUpdated(true); - } - else if (!stricmp("CaptureDevice", tag)) - { - LLVivoxVoiceClient::getInstance()->addCaptureDevice(LLVoiceDevice(displayNameString, deviceString)); - } - else if (!stricmp("RenderDevice", tag)) - { - LLVivoxVoiceClient::getInstance()->addRenderDevice(LLVoiceDevice(displayNameString, deviceString)); - } - else if (!stricmp("BlockMask", tag)) - blockMask = string; - else if (!stricmp("PresenceOnly", tag)) - presenceOnly = string; - else if (!stricmp("AutoAcceptMask", tag)) - autoAcceptMask = string; - else if (!stricmp("AutoAddAsBuddy", tag)) - autoAddAsBuddy = string; - else if (!stricmp("MessageHeader", tag)) - messageHeader = string; - else if (!stricmp("MessageBody", tag)) - messageBody = string; - else if (!stricmp("NotificationType", tag)) - notificationType = string; - else if (!stricmp("HasText", tag)) - hasText = !stricmp(string.c_str(), "true"); - else if (!stricmp("HasAudio", tag)) - hasAudio = !stricmp(string.c_str(), "true"); - else if (!stricmp("HasVideo", tag)) - hasVideo = !stricmp(string.c_str(), "true"); - else if (!stricmp("Terminated", tag)) - terminated = !stricmp(string.c_str(), "true"); - else if (!stricmp("SubscriptionHandle", tag)) - subscriptionHandle = string; - else if (!stricmp("SubscriptionType", tag)) - subscriptionType = string; - else if (!stricmp("SessionFont", tag)) - { - LLVivoxVoiceClient::getInstance()->addVoiceFont(id, nameString, descriptionString, expirationDate, hasExpired, fontType, fontStatus, false); - } - else if (!stricmp("TemplateFont", tag)) - { - LLVivoxVoiceClient::getInstance()->addVoiceFont(id, nameString, descriptionString, expirationDate, hasExpired, fontType, fontStatus, true); - } - else if (!stricmp("ID", tag)) - { - id = strtol(string.c_str(), NULL, 10); - } - else if (!stricmp("Description", tag)) - { - descriptionString = string; - } - else if (!stricmp("ExpirationDate", tag)) - { - expirationDate = expiryTimeStampToLLDate(string); - } - else if (!stricmp("Expired", tag)) - { - hasExpired = !stricmp(string.c_str(), "1"); - } - else if (!stricmp("Type", tag)) - { - fontType = strtol(string.c_str(), NULL, 10); - } - else if (!stricmp("Status", tag)) - { - fontStatus = strtol(string.c_str(), NULL, 10); - } - else if (!stricmp("MediaCompletionType", tag)) - { - mediaCompletionType = string;; - } - - textBuffer.clear(); - accumulateText= false; - - if (responseDepth == 0) - { - // We finished all of the XML, process the data - processResponse(tag); - } - } -} - -// -------------------------------------------------------------------------------- - -void LLVivoxProtocolParser::CharData(const char *buffer, int length) -{ - /* - This method is called for anything that isn't a tag, which can be text you - want that lies between tags, and a lot of stuff you don't want like file formatting - (tabs, spaces, CR/LF, etc). - - Only copy text if we are in accumulate mode... - */ - if (accumulateText) - textBuffer.append(buffer, length); -} - -// -------------------------------------------------------------------------------- - -LLDate LLVivoxProtocolParser::expiryTimeStampToLLDate(const std::string& vivox_ts) -{ - // *HACK: Vivox reports the time incorrectly. LLDate also only parses a - // subset of valid ISO 8601 dates (only handles Z, not offsets). - // So just use the date portion and fix the time here. - std::string time_stamp = vivox_ts.substr(0, 10); - time_stamp += VOICE_FONT_EXPIRY_TIME; - - LL_DEBUGS("VivoxProtocolParser") << "Vivox timestamp " << vivox_ts << " modified to: " << time_stamp << LL_ENDL; - - return LLDate(time_stamp); -} - -// -------------------------------------------------------------------------------- - -void LLVivoxProtocolParser::processResponse(std::string tag) -{ - LL_DEBUGS("VivoxProtocolParser") << tag << LL_ENDL; - - // SLIM SDK: the SDK now returns a statusCode of "200" (OK) for success. This is a change vs. previous SDKs. - // According to Mike S., "The actual API convention is that responses with return codes of 0 are successful, regardless of the status code returned", - // so I believe this will give correct behavior. - - if(returnCode == 0) - statusCode = 0; - - if (isEvent) - { - const char *eventTypeCstr = eventTypeString.c_str(); - LL_DEBUGS("LowVoice") << eventTypeCstr << LL_ENDL; - - if (!stricmp(eventTypeCstr, "ParticipantUpdatedEvent")) - { - // These happen so often that logging them is pretty useless. - LL_DEBUGS("LowVoice") << "Updated Params: " << sessionHandle << ", " << sessionGroupHandle << ", " << uriString << ", " << alias << ", " << isModeratorMuted << ", " << isSpeaking << ", " << volume << ", " << energy << LL_ENDL; - LLVivoxVoiceClient::getInstance()->participantUpdatedEvent(sessionHandle, sessionGroupHandle, uriString, alias, isModeratorMuted, isSpeaking, volume, energy); - } - else if (!stricmp(eventTypeCstr, "AccountLoginStateChangeEvent")) - { - LLVivoxVoiceClient::getInstance()->accountLoginStateChangeEvent(accountHandle, statusCode, statusString, state); - } - else if (!stricmp(eventTypeCstr, "SessionAddedEvent")) - { - /* - - c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0 - c1_m1000xFnPP04IpREWNkuw1cOXlhw==0 - sip:confctl-1408789@bhr.vivox.com - true - false - - - */ - LLVivoxVoiceClient::getInstance()->sessionAddedEvent(uriString, alias, sessionHandle, sessionGroupHandle, isChannel, incoming, nameString, applicationString); - } - else if (!stricmp(eventTypeCstr, "SessionRemovedEvent")) - { - LLVivoxVoiceClient::getInstance()->sessionRemovedEvent(sessionHandle, sessionGroupHandle); - } - else if (!stricmp(eventTypeCstr, "SessionGroupUpdatedEvent")) - { - //nothng useful to process for this event, but we should not WARN that we have received it. - } - else if (!stricmp(eventTypeCstr, "SessionGroupAddedEvent")) - { - LLVivoxVoiceClient::getInstance()->sessionGroupAddedEvent(sessionGroupHandle); - } - else if (!stricmp(eventTypeCstr, "MediaStreamUpdatedEvent")) - { - /* - - c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0 - c1_m1000xFnPP04IpREWNkuw1cOXlhw==0 - 200 - OK - 2 - false - - */ - LLVivoxVoiceClient::getInstance()->mediaStreamUpdatedEvent(sessionHandle, sessionGroupHandle, statusCode, statusString, state, incoming); - } - else if (!stricmp(eventTypeCstr, "MediaCompletionEvent")) - { - /* - - - AuxBufferAudioCapture - - */ - LLVivoxVoiceClient::getInstance()->mediaCompletionEvent(sessionGroupHandle, mediaCompletionType); - } - else if (!stricmp(eventTypeCstr, "ParticipantAddedEvent")) - { - /* - - c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4 - c1_m1000xFnPP04IpREWNkuw1cOXlhw==4 - sip:xI5auBZ60SJWIk606-1JGRQ==@bhr.vivox.com - xI5auBZ60SJWIk606-1JGRQ== - - 0 - - */ - LL_DEBUGS("LowVoice") << "Added Params: " << sessionHandle << ", " << sessionGroupHandle << ", " << uriString << ", " << alias << ", " << nameString << ", " << displayNameString << ", " << participantType << LL_ENDL; - LLVivoxVoiceClient::getInstance()->participantAddedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString, displayNameString, participantType); - } - else if (!stricmp(eventTypeCstr, "ParticipantRemovedEvent")) - { - /* - - c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4 - c1_m1000xFnPP04IpREWNkuw1cOXlhw==4 - sip:xtx7YNV-3SGiG7rA1fo5Ndw==@bhr.vivox.com - xtx7YNV-3SGiG7rA1fo5Ndw== - - */ - LL_DEBUGS("LowVoice") << "Removed params:" << sessionHandle << ", " << sessionGroupHandle << ", " << uriString << ", " << alias << ", " << nameString << LL_ENDL; - - LLVivoxVoiceClient::getInstance()->participantRemovedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString); - } - else if (!stricmp(eventTypeCstr, "AuxAudioPropertiesEvent")) - { - // These are really spammy in tuning mode - LLVivoxVoiceClient::getInstance()->auxAudioPropertiesEvent(energy); - } - else if (!stricmp(eventTypeCstr, "MessageEvent")) - { - //TODO: This probably is not received any more, it was used to support SLim clients - LLVivoxVoiceClient::getInstance()->messageEvent(sessionHandle, uriString, alias, messageHeader, messageBody, applicationString); - } - else if (!stricmp(eventTypeCstr, "SessionNotificationEvent")) - { - //TODO: This probably is not received any more, it was used to support SLim clients - LLVivoxVoiceClient::getInstance()->sessionNotificationEvent(sessionHandle, uriString, notificationType); - } - else if (!stricmp(eventTypeCstr, "SessionUpdatedEvent")) - { - /* - - c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0 - c1_m1000xFnPP04IpREWNkuw1cOXlhw==0 - sip:confctl-9@bhd.vivox.com - 0 - 50 - 1 - 0 - 000 - 0 - - */ - // We don't need to process this, but we also shouldn't warn on it, since that confuses people. - } - else if (!stricmp(eventTypeCstr, "SessionGroupRemovedEvent")) - { - // We don't need to process this, but we also shouldn't warn on it, since that confuses people. - } - else if (!stricmp(eventTypeCstr, "VoiceServiceConnectionStateChangedEvent")) - { - LLVivoxVoiceClient::getInstance()->voiceServiceConnectionStateChangedEvent(statusCode, statusString, mBuildID); - } - else if (!stricmp(eventTypeCstr, "AudioDeviceHotSwapEvent")) - { - /* - - RenderDeviceChanged< / EventType> - - Speakers(Turtle Beach P11 Headset)< / Device> - Speakers(Turtle Beach P11 Headset)< / DisplayName> - SpecificDevice< / Type> - < / RelevantDevice> - < / Event> - */ - // an audio device was removed or added, fetch and update the local list of audio devices. - LLVivoxVoiceClient::getInstance()->getCaptureDevicesSendMessage(); - LLVivoxVoiceClient::getInstance()->getRenderDevicesSendMessage(); - } - else - { - LL_WARNS("VivoxProtocolParser") << "Unknown event type " << eventTypeString << LL_ENDL; - } - } - else - { - const char *actionCstr = actionString.c_str(); - LL_DEBUGS("LowVoice") << actionCstr << LL_ENDL; - - if (!stricmp(actionCstr, "Session.Set3DPosition.1")) - { - // We don't need to process these - } - else if (!stricmp(actionCstr, "Connector.Create.1")) - { - LLVivoxVoiceClient::getInstance()->connectorCreateResponse(statusCode, statusString, connectorHandle, versionID); - } - else if (!stricmp(actionCstr, "Account.Login.1")) - { - LLVivoxVoiceClient::getInstance()->loginResponse(statusCode, statusString, accountHandle, numberOfAliases); - } - else if (!stricmp(actionCstr, "Session.Create.1")) - { - LLVivoxVoiceClient::getInstance()->sessionCreateResponse(requestId, statusCode, statusString, sessionHandle); - } - else if (!stricmp(actionCstr, "SessionGroup.AddSession.1")) - { - LLVivoxVoiceClient::getInstance()->sessionGroupAddSessionResponse(requestId, statusCode, statusString, sessionHandle); - } - else if (!stricmp(actionCstr, "Session.Connect.1")) - { - LLVivoxVoiceClient::getInstance()->sessionConnectResponse(requestId, statusCode, statusString); - } - else if (!stricmp(actionCstr, "Account.Logout.1")) - { - LLVivoxVoiceClient::getInstance()->logoutResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Connector.InitiateShutdown.1")) - { - LLVivoxVoiceClient::getInstance()->connectorShutdownResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Account.GetSessionFonts.1")) - { - LLVivoxVoiceClient::getInstance()->accountGetSessionFontsResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Account.GetTemplateFonts.1")) - { - LLVivoxVoiceClient::getInstance()->accountGetTemplateFontsResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Aux.SetVadProperties.1")) - { - // both values of statusCode (old and more recent) indicate valid requests - if (statusCode != 0 && statusCode != 200) - { - LL_WARNS("Voice") << "Aux.SetVadProperties.1 request failed: " - << "statusCode: " << statusCode - << " and " - << "statusString: " << statusString - << LL_ENDL; - } - } - /* - else if (!stricmp(actionCstr, "Account.ChannelGetList.1")) - { - LLVoiceClient::getInstance()->channelGetListResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Connector.AccountCreate.1")) - { - - } - else if (!stricmp(actionCstr, "Connector.MuteLocalMic.1")) - { - - } - else if (!stricmp(actionCstr, "Connector.MuteLocalSpeaker.1")) - { - - } - else if (!stricmp(actionCstr, "Connector.SetLocalMicVolume.1")) - { - - } - else if (!stricmp(actionCstr, "Connector.SetLocalSpeakerVolume.1")) - { - - } - else if (!stricmp(actionCstr, "Session.ListenerSetPosition.1")) - { - - } - else if (!stricmp(actionCstr, "Session.SpeakerSetPosition.1")) - { - - } - else if (!stricmp(actionCstr, "Session.AudioSourceSetPosition.1")) - { - - } - else if (!stricmp(actionCstr, "Session.GetChannelParticipants.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelCreate.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelUpdate.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelDelete.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelCreateAndInvite.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelFolderCreate.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelFolderUpdate.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelFolderDelete.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelAddModerator.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelDeleteModerator.1")) - { - - } - */ - } -} - -LLVivoxSecurity::LLVivoxSecurity() -{ - // This size is an arbitrary choice; Vivox does not care - // Use a multiple of three so that there is no '=' padding in the base64 (purely an esthetic choice) - #define VIVOX_TOKEN_BYTES 9 - U8 random_value[VIVOX_TOKEN_BYTES]; - - for (int b = 0; b < VIVOX_TOKEN_BYTES; b++) - { - random_value[b] = ll_rand() & 0xff; - } - mConnectorHandle = LLBase64::encode(random_value, VIVOX_TOKEN_BYTES); - - for (int b = 0; b < VIVOX_TOKEN_BYTES; b++) - { - random_value[b] = ll_rand() & 0xff; - } - mAccountHandle = LLBase64::encode(random_value, VIVOX_TOKEN_BYTES); -} - -LLVivoxSecurity::~LLVivoxSecurity() -{ -} + /** + * @file LLVivoxVoiceClient.cpp + * @brief Implementation of LLVivoxVoiceClient class which is the interface to the voice client process. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include +#include "llvoicevivox.h" + +#include "llsdutil.h" + +// Linden library includes +#include "llavatarnamecache.h" +#include "llvoavatarself.h" +#include "llbufferstream.h" +#include "llfile.h" +#include "llmenugl.h" +#ifdef LL_USESYSTEMLIBS +# include "expat.h" +#else +# include "expat/expat.h" +#endif +#include "llcallbacklist.h" +#include "llviewerregion.h" +#include "llviewernetwork.h" // for gGridChoice +#include "llbase64.h" +#include "llviewercontrol.h" +#include "llappviewer.h" // for gDisconnected, gDisableVoice +#include "llprocess.h" + +// Viewer includes +#include "llmutelist.h" // to check for muted avatars +#include "llagent.h" +#include "llcachename.h" +#include "llimview.h" // for LLIMMgr +#include "llparcel.h" +#include "llviewerparcelmgr.h" +#include "llfirstuse.h" +#include "llspeakers.h" +#include "lltrans.h" +#include "llviewerwindow.h" +#include "llviewercamera.h" +#include "llversioninfo.h" + +#include "llviewernetwork.h" +#include "llnotificationsutil.h" + +#include "llcorehttputil.h" +#include "lleventfilter.h" + +#include "stringize.h" + +// for base64 decoding +#include "apr_base64.h" + +#define USE_SESSION_GROUPS 0 +#define VX_NULL_POSITION -2147483648.0 /*The Silence*/ + +extern LLMenuBarGL* gMenuBarView; +extern void handle_voice_morphing_subscribe(); + +namespace { + const F32 VOLUME_SCALE_VIVOX = 0.01f; + + const F32 SPEAKING_TIMEOUT = 1.f; + + static const std::string VOICE_SERVER_TYPE = "Vivox"; + + // Don't retry connecting to the daemon more frequently than this: + const F32 DAEMON_CONNECT_THROTTLE_SECONDS = 1.0f; + const int DAEMON_CONNECT_RETRY_MAX = 3; + + // Don't send positional updates more frequently than this: + const F32 UPDATE_THROTTLE_SECONDS = 0.5f; + + // Timeout for connection to Vivox + const F32 CONNECT_ATTEMPT_TIMEOUT = 300.0f; + const F32 CONNECT_DNS_TIMEOUT = 5.0f; + const int CONNECT_RETRY_MAX = 3; + + const F32 LOGIN_ATTEMPT_TIMEOUT = 30.0f; + const F32 LOGOUT_ATTEMPT_TIMEOUT = 5.0f; + const int LOGIN_RETRY_MAX = 3; + + const F32 PROVISION_RETRY_TIMEOUT = 2.0; + const int PROVISION_RETRY_MAX = 5; + + // Cosine of a "trivially" small angle + const F32 FOUR_DEGREES = 4.0f * (F_PI / 180.0f); + const F32 MINUSCULE_ANGLE_COS = (F32) cos(0.5f * FOUR_DEGREES); + + const F32 SESSION_JOIN_TIMEOUT = 30.0f; + + // Defines the maximum number of times(in a row) "stateJoiningSession" case for spatial channel is reached in stateMachine() + // which is treated as normal. The is the number of frames to wait for a channel join before giving up. This was changed + // from the original count of 50 for two reason. Modern PCs have higher frame rates and sometimes the SLVoice process + // backs up processing join requests. There is a log statement that records when channel joins take longer than 100 frames. + const int MAX_NORMAL_JOINING_SPATIAL_NUM = 1500; + + // How often to check for expired voice fonts in seconds + const F32 VOICE_FONT_EXPIRY_INTERVAL = 10.f; + // Time of day at which Vivox expires voice font subscriptions. + // Used to replace the time portion of received expiry timestamps. + static const std::string VOICE_FONT_EXPIRY_TIME = "T05:00:00Z"; + + // Maximum length of capture buffer recordings in seconds. + const F32 CAPTURE_BUFFER_MAX_TIME = 10.f; + + const int ERROR_VIVOX_OBJECT_NOT_FOUND = 1001; + const int ERROR_VIVOX_NOT_LOGGED_IN = 1007; +} + +static int scale_mic_volume(float volume) +{ + // incoming volume has the range [0.0 ... 2.0], with 1.0 as the default. + // Map it to Vivox levels as follows: 0.0 -> 30, 1.0 -> 50, 2.0 -> 70 + return 30 + (int)(volume * 20.0f); +} + +static int scale_speaker_volume(float volume) +{ + // incoming volume has the range [0.0 ... 1.0], with 0.5 as the default. + // Map it to Vivox levels as follows: 0.0 -> 30, 0.5 -> 50, 1.0 -> 70 + return 30 + (int)(volume * 40.0f); + +} + + +/////////////////////////////////////////////////////////////////////////////////////////////// + +class LLVivoxVoiceClientMuteListObserver : public LLMuteListObserver +{ + /* virtual */ void onChange() { LLVivoxVoiceClient::getInstance()->muteListChanged();} +}; + + +void LLVoiceVivoxStats::reset() +{ + mStartTime = -1.0f; + mConnectCycles = 0; + mConnectTime = -1.0f; + mConnectAttempts = 0; + mProvisionTime = -1.0f; + mProvisionAttempts = 0; + mEstablishTime = -1.0f; + mEstablishAttempts = 0; +} + +LLVoiceVivoxStats::LLVoiceVivoxStats() +{ + reset(); +} + +LLVoiceVivoxStats::~LLVoiceVivoxStats() +{ +} + +void LLVoiceVivoxStats::connectionAttemptStart() +{ + if (!mConnectAttempts) + { + mStartTime = LLTimer::getTotalTime(); + mConnectCycles++; + } + mConnectAttempts++; +} + +void LLVoiceVivoxStats::connectionAttemptEnd(bool success) +{ + if ( success ) + { + mConnectTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC; + } +} + +void LLVoiceVivoxStats::provisionAttemptStart() +{ + if (!mProvisionAttempts) + { + mStartTime = LLTimer::getTotalTime(); + } + mProvisionAttempts++; +} + +void LLVoiceVivoxStats::provisionAttemptEnd(bool success) +{ + if ( success ) + { + mProvisionTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC; + } +} + +void LLVoiceVivoxStats::establishAttemptStart() +{ + if (!mEstablishAttempts) + { + mStartTime = LLTimer::getTotalTime(); + } + mEstablishAttempts++; +} + +void LLVoiceVivoxStats::establishAttemptEnd(bool success) +{ + if ( success ) + { + mEstablishTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC; + } +} + +LLSD LLVoiceVivoxStats::read() +{ + LLSD stats(LLSD::emptyMap()); + + stats["connect_cycles"] = LLSD::Integer(mConnectCycles); + stats["connect_attempts"] = LLSD::Integer(mConnectAttempts); + stats["connect_time"] = LLSD::Real(mConnectTime); + + stats["provision_attempts"] = LLSD::Integer(mProvisionAttempts); + stats["provision_time"] = LLSD::Real(mProvisionTime); + + stats["establish_attempts"] = LLSD::Integer(mEstablishAttempts); + stats["establish_time"] = LLSD::Real(mEstablishTime); + + return stats; +} + +static LLVivoxVoiceClientMuteListObserver mutelist_listener; +static bool sMuteListListener_listening = false; + +/////////////////////////////////////////////////////////////////////////////////////////////// +static LLProcessPtr sGatewayPtr; +static LLEventStream sGatewayPump("VivoxDaemonPump", true); + +static bool isGatewayRunning() +{ + return sGatewayPtr && sGatewayPtr->isRunning(); +} + +static void killGateway() +{ + if (sGatewayPtr) + { + LL_DEBUGS("Voice") << "SLVoice " << sGatewayPtr->getStatusString() << LL_ENDL; + + sGatewayPump.stopListening("VivoxDaemonPump"); + sGatewayPtr->kill(__FUNCTION__); + sGatewayPtr=NULL; + } + else + { + LL_DEBUGS("Voice") << "no gateway" << LL_ENDL; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////// + +bool LLVivoxVoiceClient::sShuttingDown = false; +bool LLVivoxVoiceClient::sConnected = false; +LLPumpIO *LLVivoxVoiceClient::sPump = nullptr; + +LLVivoxVoiceClient::LLVivoxVoiceClient() : + mSessionTerminateRequested(false), + mRelogRequested(false), + mTerminateDaemon(false), + mSpatialJoiningNum(0), + + mTuningMode(false), + mTuningEnergy(0.0f), + mTuningMicVolume(0), + mTuningMicVolumeDirty(true), + mTuningSpeakerVolume(50), // Set to 50 so the user can hear himself when he sets his mic volume + mTuningSpeakerVolumeDirty(true), + mDevicesListUpdated(false), + + mAreaVoiceDisabled(false), + mAudioSession(), // TBD - should be NULL + mAudioSessionChanged(false), + mNextAudioSession(), + + mCurrentParcelLocalID(0), + mConnectorEstablished(false), + mAccountLoggedIn(false), + mNumberOfAliases(0), + mCommandCookie(0), + mLoginRetryCount(0), + + mBuddyListMapPopulated(false), + mBlockRulesListReceived(false), + mAutoAcceptRulesListReceived(false), + + mCaptureDeviceDirty(false), + mRenderDeviceDirty(false), + mSpatialCoordsDirty(false), + mIsInitialized(false), + + mMuteMic(false), + mMuteMicDirty(false), + mFriendsListDirty(true), + + mEarLocation(0), + mSpeakerVolumeDirty(true), + mSpeakerMuteDirty(true), + mMicVolume(0), + mMicVolumeDirty(true), + + mVoiceEnabled(false), + mWriteInProgress(false), + + mLipSyncEnabled(false), + + mVoiceFontsReceived(false), + mVoiceFontsNew(false), + mVoiceFontListDirty(false), + + mCaptureBufferMode(false), + mCaptureBufferRecording(false), + mCaptureBufferRecorded(false), + mCaptureBufferPlaying(false), + mShutdownComplete(true), + mPlayRequestCount(0), + + mAvatarNameCacheConnection(), + mIsInTuningMode(false), + mIsInChannel(false), + mIsJoiningSession(false), + mIsWaitingForFonts(false), + mIsLoggingIn(false), + mIsLoggedIn(false), + mIsProcessingChannels(false), + mIsCoroutineActive(false), + mVivoxPump("vivoxClientPump") +{ + sShuttingDown = false; + sConnected = false; + sPump = nullptr; + + mSpeakerVolume = scale_speaker_volume(0); + + mVoiceVersion.serverVersion = ""; + mVoiceVersion.serverType = VOICE_SERVER_TYPE; + + // gMuteListp isn't set up at this point, so we defer this until later. +// gMuteListp->addObserver(&mutelist_listener); + + +#if LL_DARWIN || LL_LINUX + // HACK: THIS DOES NOT BELONG HERE + // When the vivox daemon dies, the next write attempt on our socket generates a SIGPIPE, which kills us. + // This should cause us to ignore SIGPIPE and handle the error through proper channels. + // This should really be set up elsewhere. Where should it go? + signal(SIGPIPE, SIG_IGN); + + // Since we're now launching the gateway with fork/exec instead of system(), we need to deal with zombie processes. + // Ignoring SIGCHLD should prevent zombies from being created. Alternately, we could use wait(), but I'd rather not do that. + signal(SIGCHLD, SIG_IGN); +#endif + + + gIdleCallbacks.addFunction(idle, this); +} + +//--------------------------------------------------- + +LLVivoxVoiceClient::~LLVivoxVoiceClient() +{ + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + sShuttingDown = true; +} + +//--------------------------------------------------- + +void LLVivoxVoiceClient::init(LLPumpIO *pump) +{ + // constructor will set up LLVoiceClient::getInstance() + sPump = pump; + +// LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro", +// boost::bind(&LLVivoxVoiceClient::voiceControlCoro, LLVivoxVoiceClient::getInstance())); + +} + +void LLVivoxVoiceClient::terminate() +{ + if (sShuttingDown) + { + return; + } + + // needs to be done manually here since we will not get another pass in + // coroutines... that mechanism is long since gone. + if (mIsLoggedIn) + { + logoutOfVivox(false); + } + + if(sConnected) + { + breakVoiceConnection(false); + sConnected = false; + } + else + { + mRelogRequested = false; + killGateway(); + } + + sShuttingDown = true; + sPump = NULL; +} + +//--------------------------------------------------- + +void LLVivoxVoiceClient::cleanUp() +{ + LL_DEBUGS("Voice") << LL_ENDL; + + deleteAllSessions(); + deleteAllVoiceFonts(); + deleteVoiceFontTemplates(); + LL_DEBUGS("Voice") << "exiting" << LL_ENDL; +} + +//--------------------------------------------------- + +const LLVoiceVersionInfo& LLVivoxVoiceClient::getVersion() +{ + return mVoiceVersion; +} + +//--------------------------------------------------- + +void LLVivoxVoiceClient::updateSettings() +{ + setVoiceEnabled(voiceEnabled()); + setEarLocation(gSavedSettings.getS32("VoiceEarLocation")); + + std::string inputDevice = gSavedSettings.getString("VoiceInputAudioDevice"); + setCaptureDevice(inputDevice); + std::string outputDevice = gSavedSettings.getString("VoiceOutputAudioDevice"); + setRenderDevice(outputDevice); + F32 mic_level = gSavedSettings.getF32("AudioLevelMic"); + setMicGain(mic_level); + setLipSyncEnabled(gSavedSettings.getBOOL("LipSyncEnabled")); +} + +///////////////////////////// +// utility functions + +bool LLVivoxVoiceClient::writeString(const std::string &str) +{ + bool result = false; + LL_DEBUGS("LowVoice") << "sending:\n" << str << LL_ENDL; + + if(sConnected) + { + apr_status_t err; + apr_size_t size = (apr_size_t)str.size(); + apr_size_t written = size; + + //MARK: Turn this on to log outgoing XML + // LL_DEBUGS("Voice") << "sending: " << str << LL_ENDL; + + // check return code - sockets will fail (broken, etc.) + err = apr_socket_send( + mSocket->getSocket(), + (const char*)str.data(), + &written); + + if(err == 0 && written == size) + { + // Success. + result = true; + } + else if (err == 0 && written != size) { + // Did a short write, log it for now + LL_WARNS("Voice") << ") short write on socket sending data to vivox daemon." << "Sent " << written << "bytes instead of " << size <getExpandedFilename(LL_PATH_LOGS, ""); + + // Transition to stateConnectorStarted when the connector handle comes back. + std::string vivoxLogLevel = gSavedSettings.getString("VivoxDebugLevel"); + if ( vivoxLogLevel.empty() ) + { + vivoxLogLevel = "0"; + } + LL_DEBUGS("Voice") << "creating connector with log level " << vivoxLogLevel << LL_ENDL; + + stream + << "" + << "V2 SDK" + << "" << mVoiceAccountServerURI << "" + << "Normal" + << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" + << "" + << "" << logdir << "" + << "Connector" + << ".log" + << "" << vivoxLogLevel << "" + << "" + << "" << LLVersionInfo::instance().getChannel() << " " << LLVersionInfo::instance().getVersion() << "" + //<< "" //Name can cause problems per vivox. + << "12" + << "\n\n\n"; + + writeString(stream.str()); +} + +void LLVivoxVoiceClient::connectorShutdown() +{ + if(mConnectorEstablished) + { + std::ostringstream stream; + stream + << "" + << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" + << "" + << "\n\n\n"; + + mShutdownComplete = false; + mConnectorEstablished = false; + + writeString(stream.str()); + } + else + { + mShutdownComplete = true; + } +} + +void LLVivoxVoiceClient::userAuthorized(const std::string& user_id, const LLUUID &agentID) +{ + + mAccountDisplayName = user_id; + + LL_INFOS("Voice") << "name \"" << mAccountDisplayName << "\" , ID " << agentID << LL_ENDL; + + mAccountName = nameFromID(agentID); +} + +void LLVivoxVoiceClient::setLoginInfo( + const std::string& account_name, + const std::string& password, + const std::string& voice_sip_uri_hostname, + const std::string& voice_account_server_uri) +{ + mVoiceSIPURIHostName = voice_sip_uri_hostname; + mVoiceAccountServerURI = voice_account_server_uri; + + if(mAccountLoggedIn) + { + // Already logged in. + LL_WARNS("Voice") << "Called while already logged in." << LL_ENDL; + + // Don't process another login. + return; + } + else if ( account_name != mAccountName ) + { + LL_WARNS("Voice") << "Mismatched account name! " << account_name + << " instead of " << mAccountName << LL_ENDL; + } + else + { + mAccountPassword = password; + } + + std::string debugSIPURIHostName = gSavedSettings.getString("VivoxDebugSIPURIHostName"); + + if( !debugSIPURIHostName.empty() ) + { + LL_INFOS("Voice") << "Overriding account server based on VivoxDebugSIPURIHostName: " + << debugSIPURIHostName << LL_ENDL; + mVoiceSIPURIHostName = debugSIPURIHostName; + } + + if( mVoiceSIPURIHostName.empty() ) + { + // we have an empty account server name + // so we fall back to hardcoded defaults + + if(LLGridManager::getInstance()->isInProductionGrid()) + { + // Use the release account server + mVoiceSIPURIHostName = "bhr.vivox.com"; + } + else + { + // Use the development account server + mVoiceSIPURIHostName = "bhd.vivox.com"; + } + LL_INFOS("Voice") << "Defaulting SIP URI host: " + << mVoiceSIPURIHostName << LL_ENDL; + + } + + std::string debugAccountServerURI = gSavedSettings.getString("VivoxDebugVoiceAccountServerURI"); + + if( !debugAccountServerURI.empty() ) + { + LL_INFOS("Voice") << "Overriding account server based on VivoxDebugVoiceAccountServerURI: " + << debugAccountServerURI << LL_ENDL; + mVoiceAccountServerURI = debugAccountServerURI; + } + + if( mVoiceAccountServerURI.empty() ) + { + // If the account server URI isn't specified, construct it from the SIP URI hostname + mVoiceAccountServerURI = "https://www." + mVoiceSIPURIHostName + "/api2/"; + LL_INFOS("Voice") << "Inferring account server based on SIP URI Host name: " + << mVoiceAccountServerURI << LL_ENDL; + } +} + +void LLVivoxVoiceClient::idle(void* user_data) +{ +} + +//========================================================================= +// the following are methods to support the coroutine implementation of the +// voice connection and processing. They should only be called in the context +// of a coroutine. +// +// + +typedef enum e_voice_control_coro_state +{ + VOICE_STATE_ERROR = -1, + VOICE_STATE_DONE = 0, + VOICE_STATE_TP_WAIT, // entry point + VOICE_STATE_START_DAEMON, + VOICE_STATE_PROVISION_ACCOUNT, + VOICE_STATE_START_SESSION, + VOICE_STATE_SESSION_RETRY, + VOICE_STATE_SESSION_ESTABLISHED, + VOICE_STATE_WAIT_FOR_CHANNEL, + VOICE_STATE_DISCONNECT, + VOICE_STATE_WAIT_FOR_EXIT, +} EVoiceControlCoroState; + +void LLVivoxVoiceClient::voiceControlCoro() +{ + int state = 0; + try + { + // state is passed as a reference instead of being + // a member due to unresolved issues with coroutine + // surviving longer than LLVivoxVoiceClient + voiceControlStateMachine(state); + } + catch (const LLCoros::Stop&) + { + LL_DEBUGS("LLVivoxVoiceClient") << "Received a shutdown exception" << LL_ENDL; + } + catch (const LLContinueError&) + { + LOG_UNHANDLED_EXCEPTION("LLVivoxVoiceClient"); + } + catch (...) + { + // Ideally for Windows need to log SEH exception instead or to set SEH + // handlers but bugsplat shows local variables for windows, which should + // be enough + LL_WARNS("Voice") << "voiceControlStateMachine crashed in state " << state << LL_ENDL; + throw; + } +} + +void LLVivoxVoiceClient::voiceControlStateMachine(S32 &coro_state) +{ + if (sShuttingDown) + { + return; + } + + LL_DEBUGS("Voice") << "starting" << LL_ENDL; + mIsCoroutineActive = true; + LLCoros::set_consuming(true); + + U32 retry = 0; + + coro_state = VOICE_STATE_TP_WAIT; + + do + { + if (sShuttingDown) + { + // Vivox singleton performed the exit, logged out, + // cleaned sockets, gateway and no longer cares + // about state of coroutine, so just stop + return; + } + + switch (coro_state) + { + case VOICE_STATE_TP_WAIT: + // starting point for voice + if (gAgent.getTeleportState() != LLAgent::TELEPORT_NONE) + { + LL_DEBUGS("Voice") << "Suspending voiceControlCoro() momentarily for teleport. Tuning: " << mTuningMode << ". Relog: " << mRelogRequested << LL_ENDL; + llcoro::suspendUntilTimeout(1.0); + } + else + { + coro_state = VOICE_STATE_START_DAEMON; + } + break; + + case VOICE_STATE_START_DAEMON: + LL_DEBUGS("Voice") << "Launching daemon" << LL_ENDL; + LLVoiceVivoxStats::getInstance()->reset(); + if (startAndLaunchDaemon()) + { + coro_state = VOICE_STATE_PROVISION_ACCOUNT; + } + else + { + coro_state = VOICE_STATE_SESSION_RETRY; + } + break; + + case VOICE_STATE_PROVISION_ACCOUNT: + if (provisionVoiceAccount()) + { + coro_state = VOICE_STATE_START_SESSION; + } + else + { + coro_state = VOICE_STATE_SESSION_RETRY; + } + break; + + case VOICE_STATE_START_SESSION: + if (establishVoiceConnection()) + { + coro_state = VOICE_STATE_SESSION_ESTABLISHED; + } + else + { + coro_state = VOICE_STATE_SESSION_RETRY; + } + break; + + case VOICE_STATE_SESSION_RETRY: + giveUp(); // cleans sockets and session + if (mRelogRequested) + { + // We failed to connect, give it a bit time before retrying. + retry++; + F32 full_delay = llmin(5.f * (F32)retry, 60.f); + F32 current_delay = 0.f; + LL_INFOS("Voice") << "Voice failed to establish session after " << retry + << " tries. Will attempt to reconnect in " << full_delay + << " seconds" << LL_ENDL; + while (current_delay < full_delay && !sShuttingDown) + { + // Assuming that a second has passed is not accurate, + // but we don't need accurancy here, just to make sure + // that some time passed and not to outlive voice itself + current_delay++; + llcoro::suspendUntilTimeout(1.f); + } + coro_state = VOICE_STATE_WAIT_FOR_EXIT; + } + else + { + coro_state = VOICE_STATE_DONE; + } + break; + + case VOICE_STATE_SESSION_ESTABLISHED: + { + // enable/disable the automatic VAD and explicitly set the initial values of + // the VAD variables ourselves when it is off - see SL-15072 for more details + // note: we set the other parameters too even if the auto VAD is on which is ok + unsigned int vad_auto = gSavedSettings.getU32("VivoxVadAuto"); + unsigned int vad_hangover = gSavedSettings.getU32("VivoxVadHangover"); + unsigned int vad_noise_floor = gSavedSettings.getU32("VivoxVadNoiseFloor"); + unsigned int vad_sensitivity = gSavedSettings.getU32("VivoxVadSensitivity"); + setupVADParams(vad_auto, vad_hangover, vad_noise_floor, vad_sensitivity); + + // watch for changes to the VAD settings via Debug Settings UI and act on them accordingly + gSavedSettings.getControl("VivoxVadAuto")->getSignal()->connect(boost::bind(&LLVivoxVoiceClient::onVADSettingsChange, this)); + gSavedSettings.getControl("VivoxVadHangover")->getSignal()->connect(boost::bind(&LLVivoxVoiceClient::onVADSettingsChange, this)); + gSavedSettings.getControl("VivoxVadNoiseFloor")->getSignal()->connect(boost::bind(&LLVivoxVoiceClient::onVADSettingsChange, this)); + gSavedSettings.getControl("VivoxVadSensitivity")->getSignal()->connect(boost::bind(&LLVivoxVoiceClient::onVADSettingsChange, this)); + + if (mTuningMode) + { + performMicTuning(); + } + + coro_state = VOICE_STATE_WAIT_FOR_CHANNEL; + } + break; + + case VOICE_STATE_WAIT_FOR_CHANNEL: + waitForChannel(); // todo: split into more states like login/fonts + coro_state = VOICE_STATE_DISCONNECT; + break; + + case VOICE_STATE_DISCONNECT: + LL_DEBUGS("Voice") << "lost channel RelogRequested=" << mRelogRequested << LL_ENDL; + endAndDisconnectSession(); + retry = 0; // Connected without issues + coro_state = VOICE_STATE_WAIT_FOR_EXIT; + break; + + case VOICE_STATE_WAIT_FOR_EXIT: + if (isGatewayRunning()) + { + LL_INFOS("Voice") << "waiting for SLVoice to exit" << LL_ENDL; + llcoro::suspendUntilTimeout(1.0); + } + else if (mRelogRequested && mVoiceEnabled) + { + LL_INFOS("Voice") << "will attempt to reconnect to voice" << LL_ENDL; + coro_state = VOICE_STATE_TP_WAIT; + } + else + { + coro_state = VOICE_STATE_DONE; + } + break; + + case VOICE_STATE_DONE: + break; + } + } while (coro_state > 0); + + if (sShuttingDown) + { + // LLVivoxVoiceClient might be already dead + return; + } + + mIsCoroutineActive = false; + LL_INFOS("Voice") << "exiting" << LL_ENDL; +} + +bool LLVivoxVoiceClient::endAndDisconnectSession() +{ + LL_DEBUGS("Voice") << LL_ENDL; + + breakVoiceConnection(true); + + killGateway(); + + return true; +} + +bool LLVivoxVoiceClient::callbackEndDaemon(const LLSD& data) +{ + if (!sShuttingDown && mVoiceEnabled) + { + LL_WARNS("Voice") << "SLVoice terminated " << ll_stream_notation_sd(data) << LL_ENDL; + terminateAudioSession(false); + closeSocket(); + cleanUp(); + LLVoiceClient::getInstance()->setUserPTTState(false); + gAgent.setVoiceConnected(false); + mRelogRequested = true; + } + sGatewayPump.stopListening("VivoxDaemonPump"); + return false; +} + +bool LLVivoxVoiceClient::startAndLaunchDaemon() +{ + //--------------------------------------------------------------------- + if (!voiceEnabled()) + { + // Voice is locked out, we must not launch the vivox daemon. + LL_WARNS("Voice") << "voice disabled; not starting daemon" << LL_ENDL; + return false; + } + + if (!isGatewayRunning()) + { +#ifndef VIVOXDAEMON_REMOTEHOST + // Launch the voice daemon +#if LL_WINDOWS + // On windows use exe (not work or RO) directory + std::string exe_path = gDirUtilp->getExecutableDir(); + gDirUtilp->append(exe_path, "SLVoice.exe"); +#elif LL_DARWIN + // On MAC use resource directory + std::string exe_path = gDirUtilp->getAppRODataDir(); + gDirUtilp->append(exe_path, "SLVoice"); +#else + std::string exe_path = gDirUtilp->getExecutableDir(); + gDirUtilp->append(exe_path, "SLVoice"); +#endif + // See if the vivox executable exists + llstat s; + if (!LLFile::stat(exe_path, &s)) + { + // vivox executable exists. Build the command line and launch the daemon. + LLProcess::Params params; + params.executable = exe_path; + + // VOICE-88: Cycle through [portbase..portbase+portrange) on + // successive tries because attempting to relaunch (after manually + // disabling and then re-enabling voice) with the same port can + // cause SLVoice's bind() call to fail with EADDRINUSE. We expect + // that eventually the OS will time out previous ports, which is + // why we cycle instead of incrementing indefinitely. + + static LLCachedControl portbase(gSavedSettings, "VivoxVoicePort"); + static LLCachedControl host(gSavedSettings, "VivoxVoiceHost"); + static LLCachedControl loglevel(gSavedSettings, "VivoxDebugLevel"); + static LLCachedControl log_folder(gSavedSettings, "VivoxLogDirectory"); + static LLCachedControl shutdown_timeout(gSavedSettings, "VivoxShutdownTimeout"); + static const U32 portrange = 100; + static U32 portoffset = 0; + U32 port = 0; + + if (LLAppViewer::instance()->isSecondInstance()) + { + // Ideally need to know amount of instances and + // to increment instance_offset on EADDRINUSE. + // But for now just use rand + static U32 instance_offset = portrange * ll_rand(20); + port = portbase + portoffset + instance_offset; + } + else + { + // leave main thread with exclusive port set + port = portbase + portoffset; + } + portoffset = (portoffset + 1) % portrange; + params.args.add("-i"); + params.args.add(STRINGIZE(host() << ':' << port)); + + params.args.add("-ll"); + if (loglevel().empty()) + { + params.args.add("0"); + } + else + { + params.args.add(loglevel); + } + + params.args.add("-lf"); + if (log_folder().empty()) + { + params.args.add(gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "")); + } + else + { + params.args.add(log_folder); + } + + // set log file basename and .log + params.args.add("-lp"); + params.args.add("SLVoice"); + params.args.add("-ls"); + params.args.add(".log"); + + // rotate any existing log + std::string new_log = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SLVoice.log"); + std::string old_log = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SLVoice.old"); + if (gDirUtilp->fileExists(new_log)) + { + LLFile::rename(new_log, old_log); + } + + if (!shutdown_timeout().empty()) + { + params.args.add("-st"); + params.args.add(shutdown_timeout); + } + params.cwd = gDirUtilp->getAppRODataDir(); + +# ifdef VIVOX_HANDLE_ARGS + params.args.add("-ah"); + params.args.add(LLVivoxSecurity::getInstance()->accountHandle()); + + params.args.add("-ch"); + params.args.add(LLVivoxSecurity::getInstance()->connectorHandle()); +# endif // VIVOX_HANDLE_ARGS + + params.postend = sGatewayPump.getName(); + sGatewayPump.listen("VivoxDaemonPump", boost::bind(&LLVivoxVoiceClient::callbackEndDaemon, this, _1)); + + LL_INFOS("Voice") << "Launching SLVoice" << LL_ENDL; + LL_DEBUGS("Voice") << "SLVoice params " << params << LL_ENDL; + + sGatewayPtr = LLProcess::create(params); + + mDaemonHost = LLHost(host().c_str(), port); + } + else + { + LL_WARNS("Voice") << exe_path << " not found." << LL_ENDL; + return false; + } +#else + // SLIM SDK: port changed from 44124 to 44125. + // We can connect to a client gateway running on another host. This is useful for testing. + // To do this, launch the gateway on a nearby host like this: + // vivox-gw.exe -p tcp -i 0.0.0.0:44125 + // and put that host's IP address here. + mDaemonHost = LLHost(gSavedSettings.getString("VivoxVoiceHost"), gSavedSettings.getU32("VivoxVoicePort")); +#endif + + // Dirty the states we'll need to sync with the daemon when it comes up. + mMuteMicDirty = true; + mMicVolumeDirty = true; + mSpeakerVolumeDirty = true; + mSpeakerMuteDirty = true; + // These only need to be set if they're not default (i.e. empty string). + mCaptureDeviceDirty = !mCaptureDevice.empty(); + mRenderDeviceDirty = !mRenderDevice.empty(); + + mMainSessionGroupHandle.clear(); + } + else + { + LL_DEBUGS("Voice") << " gateway running; not attempting to start" << LL_ENDL; + } + + //--------------------------------------------------------------------- + llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); + + LL_DEBUGS("Voice") << "Connecting to vivox daemon:" << mDaemonHost << LL_ENDL; + + int retryCount(0); + LLVoiceVivoxStats::getInstance()->reset(); + while (!sConnected && !sShuttingDown && retryCount++ <= DAEMON_CONNECT_RETRY_MAX) + { + LLVoiceVivoxStats::getInstance()->connectionAttemptStart(); + LL_DEBUGS("Voice") << "Attempting to connect to vivox daemon: " << mDaemonHost << LL_ENDL; + closeSocket(); + if (!mSocket) + { + mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP); + } + + sConnected = mSocket->blockingConnect(mDaemonHost); + LLVoiceVivoxStats::getInstance()->connectionAttemptEnd(sConnected); + if (!sConnected) + { + llcoro::suspendUntilTimeout(DAEMON_CONNECT_THROTTLE_SECONDS); + } + } + + //--------------------------------------------------------------------- + if (sShuttingDown && !sConnected) + { + return false; + } + + llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); + + while (!sPump && !sShuttingDown) + { // Can't use the pump until we have it available. + llcoro::suspend(); + } + + if (sShuttingDown) + { + return false; + } + + // MBW -- Note to self: pumps and pipes examples in + // indra/test/io.cpp + // indra/test/llpipeutil.{cpp|h} + + // Attach the pumps and pipes + + LLPumpIO::chain_t readChain; + + readChain.push_back(LLIOPipe::ptr_t(new LLIOSocketReader(mSocket))); + readChain.push_back(LLIOPipe::ptr_t(new LLVivoxProtocolParser())); + + + sPump->addChain(readChain, NEVER_CHAIN_EXPIRY_SECS); + + + //--------------------------------------------------------------------- + llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); + + // Initial devices query + getCaptureDevicesSendMessage(); + getRenderDevicesSendMessage(); + + mLoginRetryCount = 0; + + return true; +} + +bool LLVivoxVoiceClient::provisionVoiceAccount() +{ + LL_INFOS("Voice") << "Provisioning voice account." << LL_ENDL; + + while ((!gAgent.getRegion() || !gAgent.getRegion()->capabilitiesReceived()) && !sShuttingDown) + { + LL_DEBUGS("Voice") << "no capabilities for voice provisioning; waiting " << LL_ENDL; + // *TODO* Pump a message for wake up. + llcoro::suspend(); + } + + if (sShuttingDown) + { + return false; + } + + std::string url = gAgent.getRegionCapability("ProvisionVoiceAccountRequest"); + + LL_DEBUGS("Voice") << "region ready for voice provisioning; url=" << url << LL_ENDL; + + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("voiceAccountProvision", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts = LLCore::HttpOptions::ptr_t(new LLCore::HttpOptions); + int retryCount(0); + + LLSD result; + bool provisioned = false; + do + { + LLVoiceVivoxStats::getInstance()->provisionAttemptStart(); + result = httpAdapter->postAndSuspend(httpRequest, url, LLSD(), httpOpts); + + if (sShuttingDown) + { + return false; + } + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (status == LLCore::HttpStatus(404)) + { + F32 timeout = pow(PROVISION_RETRY_TIMEOUT, static_cast(retryCount)); + LL_WARNS("Voice") << "Provision CAP 404. Retrying in " << timeout << " seconds. Retries: " << (S32)retryCount << LL_ENDL; + llcoro::suspendUntilTimeout(timeout); + + if (sShuttingDown) + { + return false; + } + } + else if (!status) + { + LL_WARNS("Voice") << "Unable to provision voice account." << LL_ENDL; + LLVoiceVivoxStats::getInstance()->provisionAttemptEnd(false); + return false; + } + else + { + provisioned = true; + } + } while (!provisioned && ++retryCount <= PROVISION_RETRY_MAX && !sShuttingDown); + + if (sShuttingDown && !provisioned) + { + return false; + } + + LLVoiceVivoxStats::getInstance()->provisionAttemptEnd(provisioned); + if (!provisioned) + { + LL_WARNS("Voice") << "Could not access voice provision cap after " << retryCount << " attempts." << LL_ENDL; + return false; + } + + std::string voiceSipUriHostname; + std::string voiceAccountServerUri; + std::string voiceUserName = result["username"].asString(); + std::string voicePassword = result["password"].asString(); + + if (result.has("voice_sip_uri_hostname")) + { + voiceSipUriHostname = result["voice_sip_uri_hostname"].asString(); + } + + // this key is actually misnamed -- it will be an entire URI, not just a hostname. + if (result.has("voice_account_server_name")) + { + voiceAccountServerUri = result["voice_account_server_name"].asString(); + } + + LL_DEBUGS("Voice") << "ProvisionVoiceAccountRequest response" + << " user " << (voiceUserName.empty() ? "not set" : "set") + << " password " << (voicePassword.empty() ? "not set" : "set") + << " sip uri " << voiceSipUriHostname + << " account uri " << voiceAccountServerUri + << LL_ENDL; + + setLoginInfo(voiceUserName, voicePassword, voiceSipUriHostname, voiceAccountServerUri); + + return true; +} + +bool LLVivoxVoiceClient::establishVoiceConnection() +{ + if (!mVoiceEnabled && mIsInitialized) + { + LL_WARNS("Voice") << "cannot establish connection; enabled "<establishAttemptStart(); + connectorCreate(); + do + { + result = llcoro::suspendUntilEventOn(mVivoxPump); + LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; + + if (result.has("connector")) + { + connected = LLSD::Boolean(result["connector"]); + LLVoiceVivoxStats::getInstance()->establishAttemptEnd(connected); + if (!connected) + { + if (result.has("retry") && ++retries <= CONNECT_RETRY_MAX && !sShuttingDown) + { + F32 timeout = LLSD::Real(result["retry"]); + timeout *= retries; + LL_INFOS("Voice") << "Retry connection to voice service in " << timeout << " seconds" << LL_ENDL; + llcoro::suspendUntilTimeout(timeout); + + if (mVoiceEnabled) // user may have switched it off + { + // try again + LLVoiceVivoxStats::getInstance()->establishAttemptStart(); + connectorCreate(); + } + else + { + // stop if they've turned off voice + giving_up = true; + } + } + else + { + giving_up=true; + } + } + } + LL_DEBUGS("Voice") << (connected ? "" : "not ") << "connected, " + << (giving_up ? "" : "not ") << "giving up" + << LL_ENDL; + } while (!connected && !giving_up && !sShuttingDown); + + if (giving_up) + { + LLSD args; + args["HOSTID"] = LLURI(mVoiceAccountServerURI).authority(); + LLNotificationsUtil::add("NoVoiceConnect", args); + } + + return connected; +} + +bool LLVivoxVoiceClient::breakVoiceConnection(bool corowait) +{ + LL_DEBUGS("Voice") << "( wait=" << corowait << ")" << LL_ENDL; + bool retval(true); + + mShutdownComplete = false; + connectorShutdown(); + + if (corowait) + { + LLSD timeoutResult(LLSDMap("connector", "timeout")); + + LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult); + LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; + + retval = result.has("connector"); + } + else + { + mRelogRequested = false; //stop the control coro + // If we are not doing a corowait then we must sleep until the connector has responded + // otherwise we may very well close the socket too early. +#if LL_WINDOWS + if (!mShutdownComplete) + { + // The situation that brings us here is a call from ::terminate() + // At this point message system is already down so we can't wait for + // the message, yet we need to receive "connector shutdown response". + // Either wait a bit and emulate it or check gMessageSystem for specific message + _sleep(1000); + if (sConnected) + { + sConnected = false; + LLSD vivoxevent(LLSDMap("connector", LLSD::Boolean(false))); + mVivoxPump.post(vivoxevent); + } + mShutdownComplete = true; + } +#endif + } + + LL_DEBUGS("Voice") << "closing SLVoice socket" << LL_ENDL; + closeSocket(); // Need to do this now -- bad things happen if the destructor does it later. + cleanUp(); + sConnected = false; + + return retval; +} + +bool LLVivoxVoiceClient::loginToVivox() +{ + LLSD timeoutResult(LLSDMap("login", "timeout")); + + int loginRetryCount(0); + + bool response_ok(false); + bool account_login(false); + bool send_login(true); + + do + { + mIsLoggingIn = true; + if (send_login) + { + loginSendMessage(); + send_login = false; + } + + LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGIN_ATTEMPT_TIMEOUT, timeoutResult); + + if (sShuttingDown) + { + return false; + } + + LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; + + if (result.has("login")) + { + std::string loginresp = result["login"]; + + if (((loginresp == "retry") || (loginresp == "timeout")) && !sShuttingDown) + { + LL_WARNS("Voice") << "login failed with status '" << loginresp << "' " + << " count " << loginRetryCount << "/" << LOGIN_RETRY_MAX + << LL_ENDL; + if (++loginRetryCount > LOGIN_RETRY_MAX) + { + // We've run out of retries - tell the user + LL_WARNS("Voice") << "too many login retries (" << loginRetryCount << "); giving up." << LL_ENDL; + LLSD args; + args["HOSTID"] = LLURI(mVoiceAccountServerURI).authority(); + mTerminateDaemon = true; + LLNotificationsUtil::add("NoVoiceConnect", args); + + mIsLoggingIn = false; + return false; + } + response_ok = false; + account_login = false; + send_login = true; + + // an exponential backoff gets too long too quickly; stretch it out, but not too much + F32 timeout = loginRetryCount * LOGIN_ATTEMPT_TIMEOUT; + + // tell the user there is a problem + LL_WARNS("Voice") << "login " << loginresp << " will retry login in " << timeout << " seconds." << LL_ENDL; + + if (!sShuttingDown) + { + // Todo: this is way to long, viewer can get stuck waiting during shutdown + // either make it listen to pump or split in smaller waits with checks for shutdown + llcoro::suspendUntilTimeout(timeout); + } + } + else if (loginresp == "failed") + { + mIsLoggingIn = false; + return false; + } + else if (loginresp == "response_ok") + { + response_ok = true; + } + else if (loginresp == "account_login") + { + account_login = true; + } + else if (sShuttingDown) + { + mIsLoggingIn = false; + return false; + } + } + + } while ((!response_ok || !account_login) && !sShuttingDown); + + if (sShuttingDown) + { + return false; + } + + mRelogRequested = false; + mIsLoggedIn = true; + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN); + + // Set up the mute list observer if it hasn't been set up already. + if ((!sMuteListListener_listening)) + { + LLMuteList::getInstance()->addObserver(&mutelist_listener); + sMuteListListener_listening = true; + } + + // Set the initial state of mic mute, local speaker volume, etc. + sendLocalAudioUpdates(); + mIsLoggingIn = false; + + return true; +} + +void LLVivoxVoiceClient::logoutOfVivox(bool wait) +{ + if (mIsLoggedIn) + { + // Ensure that we'll re-request provisioning before logging in again + mAccountPassword.clear(); + mVoiceAccountServerURI.clear(); + + logoutSendMessage(); + + if (wait) + { + LLSD timeoutResult(LLSDMap("logout", "timeout")); + LLSD result; + + do + { + LL_DEBUGS("Voice") + << "waiting for logout response on " + << mVivoxPump.getName() + << LL_ENDL; + + result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult); + + if (sShuttingDown) + { + break; + } + + LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; + // Don't get confused by prior queued events -- note that it's + // very important that mVivoxPump is an LLEventMailDrop, which + // does queue events. + } while (! result["logout"]); + } + else + { + LL_DEBUGS("Voice") << "not waiting for logout" << LL_ENDL; + } + + mIsLoggedIn = false; + } +} + + +bool LLVivoxVoiceClient::retrieveVoiceFonts() +{ + // Request the set of available voice fonts. + refreshVoiceEffectLists(true); + + mIsWaitingForFonts = true; + LLSD result; + do + { + result = llcoro::suspendUntilEventOn(mVivoxPump); + + LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; + if (result.has("voice_fonts")) + break; + } while (true); + mIsWaitingForFonts = false; + + mVoiceFontExpiryTimer.start(); + mVoiceFontExpiryTimer.setTimerExpirySec(VOICE_FONT_EXPIRY_INTERVAL); + + return result["voice_fonts"].asBoolean(); +} + +bool LLVivoxVoiceClient::requestParcelVoiceInfo() +{ + //_INFOS("Voice") << "Requesting voice info for Parcel" << LL_ENDL; + + LLViewerRegion * region = gAgent.getRegion(); + if (region == NULL || !region->capabilitiesReceived()) + { + LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest capability not yet available, deferring" << LL_ENDL; + return false; + } + + // grab the cap. + std::string url = gAgent.getRegion()->getCapability("ParcelVoiceInfoRequest"); + if (url.empty()) + { + // Region dosn't have the cap. Stop probing. + LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest capability not available in this region" << LL_ENDL; + return false; + } + + // update the parcel + checkParcelChanged(true); + + LL_DEBUGS("Voice") << "sending ParcelVoiceInfoRequest (" << mCurrentRegionName << ", " << mCurrentParcelLocalID << ")" << LL_ENDL; + + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("parcelVoiceInfoRequest", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + + LLSD result = httpAdapter->postAndSuspend(httpRequest, url, LLSD()); + + if (sShuttingDown) + { + return false; + } + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized)) + { + // if a terminate request has been received, + // bail and go to the stateSessionTerminated + // state. If the cap request is still pending, + // the responder will check to see if we've moved + // to a new session and won't change any state. + LL_DEBUGS("Voice") << "terminate requested " << mSessionTerminateRequested + << " enabled " << mVoiceEnabled + << " initialized " << mIsInitialized + << LL_ENDL; + terminateAudioSession(true); + return false; + } + + if ((!status) || (mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized))) + { + if (mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized)) + { + LL_WARNS("Voice") << "Session terminated." << LL_ENDL; + } + + LL_WARNS("Voice") << "No voice on parcel" << LL_ENDL; + sessionTerminate(); + return false; + } + + std::string uri; + std::string credentials; + + if (result.has("voice_credentials")) + { + LLSD voice_credentials = result["voice_credentials"]; + if (voice_credentials.has("channel_uri")) + { + LL_DEBUGS("Voice") << "got voice channel uri" << LL_ENDL; + uri = voice_credentials["channel_uri"].asString(); + } + else + { + LL_WARNS("Voice") << "No voice channel uri" << LL_ENDL; + } + + if (voice_credentials.has("channel_credentials")) + { + LL_DEBUGS("Voice") << "got voice channel credentials" << LL_ENDL; + credentials = + voice_credentials["channel_credentials"].asString(); + } + else + { + LLVoiceChannel* channel = LLVoiceChannel::getCurrentVoiceChannel(); + if (channel != NULL) + { + if (channel->getSessionName().empty() && channel->getSessionID().isNull()) + { + if (LLViewerParcelMgr::getInstance()->allowAgentVoice()) + { + LL_WARNS("Voice") << "No channel credentials for default channel" << LL_ENDL; + } + } + else + { + LL_WARNS("Voice") << "No voice channel credentials" << LL_ENDL; + } + } + } + } + else + { + if (LLViewerParcelMgr::getInstance()->allowAgentVoice()) + { + LL_WARNS("Voice") << "No voice credentials" << LL_ENDL; + } + else + { + LL_DEBUGS("Voice") << "No voice credentials" << LL_ENDL; + } + } + + // set the spatial channel. If no voice credentials or uri are + // available, then we simply drop out of voice spatially. + return !setSpatialChannel(uri, credentials); +} + +bool LLVivoxVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession) +{ + mIsJoiningSession = true; + + sessionStatePtr_t oldSession = mAudioSession; + + LL_INFOS("Voice") << "Adding or joining voice session " << nextSession->mHandle << LL_ENDL; + + mAudioSession = nextSession; + mAudioSessionChanged = true; + if (!mAudioSession || !mAudioSession->mReconnect) + { + mNextAudioSession.reset(); + } + + // The old session may now need to be deleted. + reapSession(oldSession); + + if (mAudioSession) + { + if (!mAudioSession->mHandle.empty()) + { + // Connect to a session by session handle + + sessionMediaConnectSendMessage(mAudioSession); + } + else + { + // Connect to a session by URI + sessionCreateSendMessage(mAudioSession, true, false); + } + } + + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING); + + llcoro::suspend(); + + if (sShuttingDown) + { + return false; + } + + LLSD result; + + if (mSpatialJoiningNum == MAX_NORMAL_JOINING_SPATIAL_NUM) + { + // Notify observers to let them know there is problem with voice + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); + LL_WARNS() << "There seems to be problem with connection to voice server. Disabling voice chat abilities." << LL_ENDL; + } + + // Increase mSpatialJoiningNum only for spatial sessions- it's normal to reach this case for + // example for p2p many times while waiting for response, so it can't be used to detect errors + if (mAudioSession && mAudioSession->mIsSpatial) + { + mSpatialJoiningNum++; + } + + if (!mVoiceEnabled && mIsInitialized) + { + LL_DEBUGS("Voice") << "Voice no longer enabled. Exiting" + << " enabled " << mVoiceEnabled + << " initialized " << mIsInitialized + << LL_ENDL; + mIsJoiningSession = false; + // User bailed out during connect -- jump straight to teardown. + terminateAudioSession(true); + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); + return false; + } + else if (mSessionTerminateRequested) + { + LL_DEBUGS("Voice") << "Terminate requested" << LL_ENDL; + if (mAudioSession && !mAudioSession->mHandle.empty()) + { + // Only allow direct exits from this state in p2p calls (for cancelling an invite). + // Terminating a half-connected session on other types of calls seems to break something in the vivox gateway. + if (mAudioSession->mIsP2P) + { + terminateAudioSession(true); + mIsJoiningSession = false; + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); + return false; + } + } + } + + bool added(true); + bool joined(false); + + LLSD timeoutResult(LLSDMap("session", "timeout")); + + // We are about to start a whole new session. Anything that MIGHT still be in our + // maildrop is going to be stale and cause us much wailing and gnashing of teeth. + // Just flush it all out and start new. + mVivoxPump.discard(); + + // It appears that I need to wait for BOTH the SessionGroup.AddSession response and the SessionStateChangeEvent with state 4 + // before continuing from this state. They can happen in either order, and if I don't wait for both, things can get stuck. + // For now, the SessionGroup.AddSession response handler sets mSessionHandle and the SessionStateChangeEvent handler transitions to stateSessionJoined. + // This is a cheap way to make sure both have happened before proceeding. + do + { + result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, SESSION_JOIN_TIMEOUT, timeoutResult); + + if (sShuttingDown) + { + return false; + } + + LL_INFOS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; + if (result.has("session")) + { + if (!mAudioSession) + { + LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while session is not initialized." << LL_ENDL; + continue; + } + if (result.has("handle") && result["handle"] != mAudioSession->mHandle) + { + LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while waiting for \"" << mAudioSession->mHandle << "\"." << LL_ENDL; + continue; + } + + std::string message = result["session"].asString(); + + if ((message == "added") || (message == "created")) + { + added = true; + } + else if (message == "joined") + { + joined = true; + } + else if ((message == "failed") || (message == "removed") || (message == "timeout")) + { // we will get a removed message if a voice call is declined. + + if (message == "failed") + { + int reason = result["reason"].asInteger(); + LL_WARNS("Voice") << "Add and join failed for reason " << reason << LL_ENDL; + + if ( (reason == ERROR_VIVOX_NOT_LOGGED_IN) + || (reason == ERROR_VIVOX_OBJECT_NOT_FOUND)) + { + LL_DEBUGS("Voice") << "Requesting reprovision and login." << LL_ENDL; + requestRelog(); + } + } + else + { + LL_WARNS("Voice") << "session '" << message << "' " + << LL_ENDL; + } + + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); + mIsJoiningSession = false; + return false; + } + } + } while (!added || !joined); + + mIsJoiningSession = false; + + if (mSpatialJoiningNum > 100) + { + LL_WARNS("Voice") << "There seems to be problem with connecting to a voice channel. Frames to join were " << mSpatialJoiningNum << LL_ENDL; + } + + mSpatialJoiningNum = 0; + + // Events that need to happen when a session is joined could go here. + // send an initial positional information immediately upon joining. + // + // do an initial update for position and the camera position, then send a + // positional update. + updatePosition(); + enforceTether(); + + // Dirty state that may need to be sync'ed with the daemon. + mMuteMicDirty = true; + mSpeakerVolumeDirty = true; + mSpatialCoordsDirty = true; + + sendPositionAndVolumeUpdate(); + + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED); + + return true; +} + +bool LLVivoxVoiceClient::terminateAudioSession(bool wait) +{ + + if (mAudioSession) + { + LL_INFOS("Voice") << "terminateAudioSession(" << wait << ") Terminating current voice session " << mAudioSession->mHandle << LL_ENDL; + + if (mIsLoggedIn) + { + if (!mAudioSession->mHandle.empty()) + { + +#if RECORD_EVERYTHING + // HACK: for testing only + // Save looped recording + std::string savepath("/tmp/vivoxrecording"); + { + time_t now = time(NULL); + const size_t BUF_SIZE = 64; + char time_str[BUF_SIZE]; /* Flawfinder: ignore */ + + strftime(time_str, BUF_SIZE, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now)); + savepath += time_str; + } + recordingLoopSave(savepath); +#endif + + sessionMediaDisconnectSendMessage(mAudioSession); + + if (wait) + { + LLSD result; + do + { + LLSD timeoutResult(LLSDMap("session", "timeout")); + + result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult); + + if (sShuttingDown) + { + return false; + } + + LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; + if (result.has("session")) + { + if (result.has("handle")) + { + if (result["handle"] != mAudioSession->mHandle) + { + LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while waiting for \"" << mAudioSession->mHandle << "\"." << LL_ENDL; + continue; + } + } + + std::string message = result["session"].asString(); + if (message == "removed" || message == "timeout") + break; + } + } while (true); + + } + } + else + { + LL_WARNS("Voice") << "called with no session handle" << LL_ENDL; + } + } + else + { + LL_WARNS("Voice") << "Session " << mAudioSession->mHandle << " already terminated by logout." << LL_ENDL; + } + + sessionStatePtr_t oldSession = mAudioSession; + + mAudioSession.reset(); + // We just notified status observers about this change. Don't do it again. + mAudioSessionChanged = false; + + // The old session may now need to be deleted. + reapSession(oldSession); + } + else + { + LL_WARNS("Voice") << "terminateAudioSession(" << wait << ") with NULL mAudioSession" << LL_ENDL; + } + + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); + + // Always reset the terminate request flag when we get here. + // Some slower PCs have a race condition where they can switch to an incoming P2P call faster than the state machine leaves + // the region chat. + mSessionTerminateRequested = false; + + bool status=((mVoiceEnabled || !mIsInitialized) && !mRelogRequested && !sShuttingDown); + LL_DEBUGS("Voice") << "exiting" + << " VoiceEnabled " << mVoiceEnabled + << " IsInitialized " << mIsInitialized + << " RelogRequested " << mRelogRequested + << " ShuttingDown " << (sShuttingDown ? "true" : "false") + << " returning " << status + << LL_ENDL; + return status; +} + + +typedef enum e_voice_wait_for_channel_state +{ + VOICE_CHANNEL_STATE_LOGIN = 0, // entry point + VOICE_CHANNEL_STATE_CHECK_EFFECTS, + VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING, + VOICE_CHANNEL_STATE_PROCESS_CHANNEL, + VOICE_CHANNEL_STATE_NEXT_CHANNEL_DELAY, + VOICE_CHANNEL_STATE_NEXT_CHANNEL_CHECK, + VOICE_CHANNEL_STATE_LOGOUT, + VOICE_CHANNEL_STATE_RELOG, + VOICE_CHANNEL_STATE_DONE, +} EVoiceWaitForChannelState; + +bool LLVivoxVoiceClient::waitForChannel() +{ + LL_INFOS("Voice") << "Waiting for channel" << LL_ENDL; + + EVoiceWaitForChannelState state = VOICE_CHANNEL_STATE_LOGIN; + + do + { + if (sShuttingDown) + { + // terminate() forcefully disconects voice, no need for cleanup + return false; + } + + switch (state) + { + case VOICE_CHANNEL_STATE_LOGIN: + if (!loginToVivox()) + { + return false; + } + state = VOICE_CHANNEL_STATE_CHECK_EFFECTS; + break; + + case VOICE_CHANNEL_STATE_CHECK_EFFECTS: + if (LLVoiceClient::instance().getVoiceEffectEnabled()) + { + retrieveVoiceFonts(); + + if (sShuttingDown) + { + return false; + } + + // Request the set of available voice fonts. + refreshVoiceEffectLists(false); + } + +#if USE_SESSION_GROUPS + // Rider: This code is completely unchanged from the original state machine + // It does not seem to be in active use... but I'd rather not rip it out. + // create the main session group + setState(stateCreatingSessionGroup); + sessionGroupCreateSendMessage(); +#endif + + state = VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING; + break; + + case VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING: + mIsProcessingChannels = true; + llcoro::suspend(); + state = VOICE_CHANNEL_STATE_PROCESS_CHANNEL; + break; + + case VOICE_CHANNEL_STATE_PROCESS_CHANNEL: + if (mTuningMode) + { + performMicTuning(); + } + else if (mCaptureBufferMode) + { + recordingAndPlaybackMode(); + } + else if (checkParcelChanged() || (mNextAudioSession == NULL)) + { + // the parcel is changed, or we have no pending audio sessions, + // so try to request the parcel voice info + // if we have the cap, we move to the appropriate state + requestParcelVoiceInfo(); //suspends for http reply + } + else if (sessionNeedsRelog(mNextAudioSession)) + { + LL_INFOS("Voice") << "Session requesting reprovision and login." << LL_ENDL; + requestRelog(); + break; + } + else if (mNextAudioSession) + { + sessionStatePtr_t joinSession = mNextAudioSession; + mNextAudioSession.reset(); + if (!runSession(joinSession)) //suspends + { + LL_DEBUGS("Voice") << "runSession returned false; leaving inner loop" << LL_ENDL; + break; + } + else + { + LL_DEBUGS("Voice") + << "runSession returned true to inner loop" + << " RelogRequested=" << mRelogRequested + << " VoiceEnabled=" << mVoiceEnabled + << LL_ENDL; + } + } + + state = VOICE_CHANNEL_STATE_NEXT_CHANNEL_DELAY; + break; + + case VOICE_CHANNEL_STATE_NEXT_CHANNEL_DELAY: + if (!mNextAudioSession) + { + llcoro::suspendUntilTimeout(1.0); + } + state = VOICE_CHANNEL_STATE_NEXT_CHANNEL_CHECK; + break; + + case VOICE_CHANNEL_STATE_NEXT_CHANNEL_CHECK: + if (mVoiceEnabled && !mRelogRequested) + { + state = VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING; + break; + } + else + { + mIsProcessingChannels = false; + LL_DEBUGS("Voice") + << "leaving inner waitForChannel loop" + << " RelogRequested=" << mRelogRequested + << " VoiceEnabled=" << mVoiceEnabled + << LL_ENDL; + state = VOICE_CHANNEL_STATE_LOGOUT; + break; + } + + case VOICE_CHANNEL_STATE_LOGOUT: + logoutOfVivox(true /*bool wait*/); + if (mRelogRequested) + { + state = VOICE_CHANNEL_STATE_RELOG; + } + else + { + state = VOICE_CHANNEL_STATE_DONE; + } + break; + + case VOICE_CHANNEL_STATE_RELOG: + LL_DEBUGS("Voice") << "Relog Requested, restarting provisioning" << LL_ENDL; + if (!provisionVoiceAccount()) + { + if (sShuttingDown) + { + return false; + } + LL_WARNS("Voice") << "provisioning voice failed; giving up" << LL_ENDL; + giveUp(); + return false; + } + if (mVoiceEnabled && mRelogRequested && isGatewayRunning()) + { + state = VOICE_CHANNEL_STATE_LOGIN; + } + else + { + state = VOICE_CHANNEL_STATE_DONE; + } + break; + case VOICE_CHANNEL_STATE_DONE: + LL_DEBUGS("Voice") + << "exiting" + << " RelogRequested=" << mRelogRequested + << " VoiceEnabled=" << mVoiceEnabled + << LL_ENDL; + return !sShuttingDown; + } + } while (true); +} + +bool LLVivoxVoiceClient::runSession(const sessionStatePtr_t &session) +{ + LL_INFOS("Voice") << "running new voice session " << session->mHandle << LL_ENDL; + + bool joined_session = addAndJoinSession(session); + + if (sShuttingDown) + { + return false; + } + + if (!joined_session) + { + notifyStatusObservers(LLVoiceClientStatusObserver::ERROR_UNKNOWN); + + if (mSessionTerminateRequested) + { + LL_DEBUGS("Voice") << "runSession terminate requested " << LL_ENDL; + terminateAudioSession(true); + } + // if a relog has been requested then addAndJoineSession + // failed in a spectacular way and we need to back out. + // If this is not the case then we were simply trying to + // make a call and the other party rejected it. + return !mRelogRequested; + } + + notifyParticipantObservers(); + notifyVoiceFontObservers(); + + LLSD timeoutEvent(LLSDMap("timeout", LLSD::Boolean(true))); + + mIsInChannel = true; + mMuteMicDirty = true; + + while (!sShuttingDown + && mVoiceEnabled + && isGatewayRunning() + && !mSessionTerminateRequested + && !mTuningMode) + { + sendCaptureAndRenderDevices(); // suspends + + if (sShuttingDown) + { + return false; + } + + if (mSessionTerminateRequested) + { + break; + } + + if (mAudioSession && mAudioSession->mParticipantsChanged) + { + mAudioSession->mParticipantsChanged = false; + notifyParticipantObservers(); + } + + if (!inSpatialChannel()) + { + // When in a non-spatial channel, never send positional updates. + mSpatialCoordsDirty = false; + } + else + { + updatePosition(); + + if (checkParcelChanged()) + { + // *RIDER: I think I can just return here if the parcel has changed + // and grab the new voice channel from the outside loop. + // + // if the parcel has changed, attempted to request the + // cap for the parcel voice info. If we can't request it + // then we don't have the cap URL so we do nothing and will + // recheck next time around + if (requestParcelVoiceInfo()) // suspends + { // The parcel voice URI has changed.. break out and reconnect. + break; + } + + if (sShuttingDown) + { + return false; + } + } + // Do the calculation that enforces the listener<->speaker tether (and also updates the real camera position) + enforceTether(); + } + sendPositionAndVolumeUpdate(); + + // Do notifications for expiring Voice Fonts. + if (mVoiceFontExpiryTimer.hasExpired()) + { + expireVoiceFonts(); + mVoiceFontExpiryTimer.setTimerExpirySec(VOICE_FONT_EXPIRY_INTERVAL); + } + + // send any requests to adjust mic and speaker settings if they have changed + sendLocalAudioUpdates(); + + mIsInitialized = true; + LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, UPDATE_THROTTLE_SECONDS, timeoutEvent); + + if (sShuttingDown) + { + return false; + } + + if (!result.has("timeout")) // logging the timeout event spams the log + { + LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; + } + if (result.has("session")) + { + if (result.has("handle")) + { + if (!mAudioSession) + { + LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while session is not initiated." << LL_ENDL; + continue; + } + if (result["handle"] != mAudioSession->mHandle) + { + LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while waiting for \"" << mAudioSession->mHandle << "\"." << LL_ENDL; + continue; + } + } + + std::string message = result["session"]; + + if (message == "removed") + { + LL_DEBUGS("Voice") << "session removed" << LL_ENDL; + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); + break; + } + } + else if (result.has("login")) + { + std::string message = result["login"]; + if (message == "account_logout") + { + LL_DEBUGS("Voice") << "logged out" << LL_ENDL; + mIsLoggedIn = false; + mRelogRequested = true; + break; + } + } + } + + if (sShuttingDown) + { + return false; + } + + mIsInChannel = false; + LL_DEBUGS("Voice") << "terminating at end of runSession" << LL_ENDL; + terminateAudioSession(true); + + return true; +} + +void LLVivoxVoiceClient::sendCaptureAndRenderDevices() +{ + if (mCaptureDeviceDirty || mRenderDeviceDirty) + { + std::ostringstream stream; + + buildSetCaptureDevice(stream); + buildSetRenderDevice(stream); + + if (!stream.str().empty()) + { + writeString(stream.str()); + } + + llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); + } +} + +void LLVivoxVoiceClient::recordingAndPlaybackMode() +{ + LL_INFOS("Voice") << "In voice capture/playback mode." << LL_ENDL; + + while (true) + { + LLSD command; + do + { + command = llcoro::suspendUntilEventOn(mVivoxPump); + LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(command) << LL_ENDL; + } while (!command.has("recplay")); + + if (command["recplay"].asString() == "quit") + { + mCaptureBufferMode = false; + break; + } + else if (command["recplay"].asString() == "record") + { + voiceRecordBuffer(); + } + else if (command["recplay"].asString() == "playback") + { + voicePlaybackBuffer(); + } + } + + LL_INFOS("Voice") << "Leaving capture/playback mode." << LL_ENDL; + mCaptureBufferRecording = false; + mCaptureBufferRecorded = false; + mCaptureBufferPlaying = false; + + return; +} + +int LLVivoxVoiceClient::voiceRecordBuffer() +{ + LLSD timeoutResult(LLSDMap("recplay", "stop")); + + LL_INFOS("Voice") << "Recording voice buffer" << LL_ENDL; + + LLSD result; + + captureBufferRecordStartSendMessage(); + notifyVoiceFontObservers(); + + do + { + result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, CAPTURE_BUFFER_MAX_TIME, timeoutResult); + LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; + } while (!result.has("recplay")); + + mCaptureBufferRecorded = true; + + captureBufferRecordStopSendMessage(); + mCaptureBufferRecording = false; + + // Update UI, should really use a separate callback. + notifyVoiceFontObservers(); + + return true; + /*TODO expand return to move directly into play*/ +} + +int LLVivoxVoiceClient::voicePlaybackBuffer() +{ + LLSD timeoutResult(LLSDMap("recplay", "stop")); + + LL_INFOS("Voice") << "Playing voice buffer" << LL_ENDL; + + LLSD result; + + do + { + captureBufferPlayStartSendMessage(mPreviewVoiceFont); + + // Store the voice font being previewed, so that we know to restart if it changes. + mPreviewVoiceFontLast = mPreviewVoiceFont; + + do + { + // Update UI, should really use a separate callback. + notifyVoiceFontObservers(); + + result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, CAPTURE_BUFFER_MAX_TIME, timeoutResult); + LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; + } while (!result.has("recplay")); + + if (result["recplay"] == "playback") + continue; // restart playback... May be a font change. + + break; + } while (true); + + // Stop playing. + captureBufferPlayStopSendMessage(); + mCaptureBufferPlaying = false; + + // Update UI, should really use a separate callback. + notifyVoiceFontObservers(); + + return true; +} + + +bool LLVivoxVoiceClient::performMicTuning() +{ + LL_INFOS("Voice") << "Entering voice tuning mode." << LL_ENDL; + + mIsInTuningMode = true; + llcoro::suspend(); + + while (mTuningMode && !sShuttingDown) + { + + if (mCaptureDeviceDirty || mRenderDeviceDirty) + { + // These can't be changed while in tuning mode. Set them before starting. + std::ostringstream stream; + + buildSetCaptureDevice(stream); + buildSetRenderDevice(stream); + + if (!stream.str().empty()) + { + writeString(stream.str()); + } + + llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); + } + + // loop mic back to render device. + //setMuteMic(0); // make sure the mic is not muted + std::ostringstream stream; + + stream << "" + << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" + << "false" + << "\n\n\n"; + + // Dirty the mute mic state so that it will get reset when we finishing previewing + mMuteMicDirty = true; + mTuningSpeakerVolumeDirty = true; + + writeString(stream.str()); + tuningCaptureStartSendMessage(1); // 1-loop, zero, don't loop + + //--------------------------------------------------------------------- + if (!sShuttingDown) + { + llcoro::suspend(); + } + + while (mTuningMode && !mCaptureDeviceDirty && !mRenderDeviceDirty && !sShuttingDown) + { + // process mic/speaker volume changes + if (mTuningMicVolumeDirty || mTuningSpeakerVolumeDirty) + { + std::ostringstream stream; + + if (mTuningMicVolumeDirty) + { + LL_INFOS("Voice") << "setting tuning mic level to " << mTuningMicVolume << LL_ENDL; + stream + << "" + << "" << mTuningMicVolume << "" + << "\n\n\n"; + } + + if (mTuningSpeakerVolumeDirty) + { + LL_INFOS("Voice") << "setting tuning speaker level to " << mTuningSpeakerVolume << LL_ENDL; + stream + << "" + << "" << mTuningSpeakerVolume << "" + << "\n\n\n"; + } + + mTuningMicVolumeDirty = false; + mTuningSpeakerVolumeDirty = false; + + if (!stream.str().empty()) + { + writeString(stream.str()); + } + } + llcoro::suspend(); + } + + //--------------------------------------------------------------------- + + // transition out of mic tuning + tuningCaptureStopSendMessage(); + if ((mCaptureDeviceDirty || mRenderDeviceDirty) && !sShuttingDown) + { + llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); + } + } + + mIsInTuningMode = false; + + //--------------------------------------------------------------------- + return true; +} + +//========================================================================= + +void LLVivoxVoiceClient::closeSocket(void) +{ + mSocket.reset(); + sConnected = false; + mConnectorEstablished = false; + mAccountLoggedIn = false; +} + +void LLVivoxVoiceClient::loginSendMessage() +{ + std::ostringstream stream; + + bool autoPostCrashDumps = gSavedSettings.getBOOL("VivoxAutoPostCrashDumps"); + + stream + << "" + << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" + << "" << mAccountName << "" + << "" << mAccountPassword << "" + << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" + << "VerifyAnswer" + << "false" + << "0" + << "Application" + << "5" + << (autoPostCrashDumps?"true":"") + << "\n\n\n"; + + LL_INFOS("Voice") << "Attempting voice login" << LL_ENDL; + writeString(stream.str()); +} + +void LLVivoxVoiceClient::logout() +{ + // Ensure that we'll re-request provisioning before logging in again + mAccountPassword.clear(); + mVoiceAccountServerURI.clear(); + + logoutSendMessage(); +} + +void LLVivoxVoiceClient::logoutSendMessage() +{ + if(mAccountLoggedIn) + { + LL_INFOS("Voice") << "Attempting voice logout" << LL_ENDL; + std::ostringstream stream; + stream + << "" + << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" + << "" + << "\n\n\n"; + + mAccountLoggedIn = false; + + writeString(stream.str()); + } +} + +void LLVivoxVoiceClient::sessionGroupCreateSendMessage() +{ + if(mAccountLoggedIn) + { + std::ostringstream stream; + + LL_DEBUGS("Voice") << "creating session group" << LL_ENDL; + + stream + << "" + << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" + << "Normal" + << "" + << "\n\n\n"; + + writeString(stream.str()); + } +} + +void LLVivoxVoiceClient::sessionCreateSendMessage(const sessionStatePtr_t &session, bool startAudio, bool startText) +{ + S32 font_index = getVoiceFontIndex(session->mVoiceFontID); + LL_DEBUGS("Voice") << "Requesting create: " << session->mSIPURI + << " with voice font: " << session->mVoiceFontID << " (" << font_index << ")" + << LL_ENDL; + + session->mCreateInProgress = true; + if(startAudio) + { + session->mMediaConnectInProgress = true; + } + + std::ostringstream stream; + stream + << "mSIPURI << "\" action=\"Session.Create.1\">" + << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" + << "" << session->mSIPURI << ""; + + static const std::string allowed_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789" + "-._~"; + + if(!session->mHash.empty()) + { + stream + << "" << LLURI::escape(session->mHash, allowed_chars) << "" + << "SHA1UserName"; + } + + stream + << "" << (startAudio?"true":"false") << "" + << "" << (startText?"true":"false") << "" + << "" << font_index << "" + << "" << mChannelName << "" + << "\n\n\n"; + writeString(stream.str()); +} + +void LLVivoxVoiceClient::sessionGroupAddSessionSendMessage(const sessionStatePtr_t &session, bool startAudio, bool startText) +{ + LL_DEBUGS("Voice") << "Requesting create: " << session->mSIPURI << LL_ENDL; + + S32 font_index = getVoiceFontIndex(session->mVoiceFontID); + LL_DEBUGS("Voice") << "With voice font: " << session->mVoiceFontID << " (" << font_index << ")" << LL_ENDL; + + session->mCreateInProgress = true; + if(startAudio) + { + session->mMediaConnectInProgress = true; + } + + std::string password; + if(!session->mHash.empty()) + { + static const std::string allowed_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789" + "-._~" + ; + password = LLURI::escape(session->mHash, allowed_chars); + } + + std::ostringstream stream; + stream + << "mSIPURI << "\" action=\"SessionGroup.AddSession.1\">" + << "" << session->mGroupHandle << "" + << "" << session->mSIPURI << "" + << "" << mChannelName << "" + << "" << (startAudio?"true":"false") << "" + << "" << (startText?"true":"false") << "" + << "" << font_index << "" + << "" << password << "" + << "SHA1UserName" + << "\n\n\n" + ; + + writeString(stream.str()); +} + +void LLVivoxVoiceClient::sessionMediaConnectSendMessage(const sessionStatePtr_t &session) +{ + S32 font_index = getVoiceFontIndex(session->mVoiceFontID); + LL_DEBUGS("Voice") << "Connecting audio to session handle: " << session->mHandle + << " with voice font: " << session->mVoiceFontID << " (" << font_index << ")" + << LL_ENDL; + + session->mMediaConnectInProgress = true; + + std::ostringstream stream; + + stream + << "mHandle << "\" action=\"Session.MediaConnect.1\">" + << "" << session->mGroupHandle << "" + << "" << session->mHandle << "" + << "" << font_index << "" + << "Audio" + << "\n\n\n"; + + writeString(stream.str()); +} + +void LLVivoxVoiceClient::sessionTextConnectSendMessage(const sessionStatePtr_t &session) +{ + LL_DEBUGS("Voice") << "connecting text to session handle: " << session->mHandle << LL_ENDL; + + std::ostringstream stream; + + stream + << "mHandle << "\" action=\"Session.TextConnect.1\">" + << "" << session->mGroupHandle << "" + << "" << session->mHandle << "" + << "\n\n\n"; + + writeString(stream.str()); +} + +void LLVivoxVoiceClient::sessionTerminate() +{ + mSessionTerminateRequested = true; +} + +void LLVivoxVoiceClient::requestRelog() +{ + mSessionTerminateRequested = true; + mRelogRequested = true; +} + + +void LLVivoxVoiceClient::leaveAudioSession() +{ + if(mAudioSession) + { + LL_DEBUGS("Voice") << "leaving session: " << mAudioSession->mSIPURI << LL_ENDL; + + if(!mAudioSession->mHandle.empty()) + { + +#if RECORD_EVERYTHING + // HACK: for testing only + // Save looped recording + std::string savepath("/tmp/vivoxrecording"); + { + time_t now = time(NULL); + const size_t BUF_SIZE = 64; + char time_str[BUF_SIZE]; /* Flawfinder: ignore */ + + strftime(time_str, BUF_SIZE, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now)); + savepath += time_str; + } + recordingLoopSave(savepath); +#endif + + sessionMediaDisconnectSendMessage(mAudioSession); + } + else + { + LL_WARNS("Voice") << "called with no session handle" << LL_ENDL; + } + } + else + { + LL_WARNS("Voice") << "called with no active session" << LL_ENDL; + } + sessionTerminate(); +} + +void LLVivoxVoiceClient::sessionTerminateSendMessage(const sessionStatePtr_t &session) +{ + std::ostringstream stream; + + sessionGroupTerminateSendMessage(session); + return; + /* + LL_DEBUGS("Voice") << "Sending Session.Terminate with handle " << session->mHandle << LL_ENDL; + stream + << "" + << "" << session->mHandle << "" + << "\n\n\n"; + + writeString(stream.str()); + */ +} + +void LLVivoxVoiceClient::sessionGroupTerminateSendMessage(const sessionStatePtr_t &session) +{ + std::ostringstream stream; + + LL_DEBUGS("Voice") << "Sending SessionGroup.Terminate with handle " << session->mGroupHandle << LL_ENDL; + stream + << "" + << "" << session->mGroupHandle << "" + << "\n\n\n"; + + writeString(stream.str()); +} + +void LLVivoxVoiceClient::sessionMediaDisconnectSendMessage(const sessionStatePtr_t &session) +{ + std::ostringstream stream; + sessionGroupTerminateSendMessage(session); + return; + /* + LL_DEBUGS("Voice") << "Sending Session.MediaDisconnect with handle " << session->mHandle << LL_ENDL; + stream + << "" + << "" << session->mGroupHandle << "" + << "" << session->mHandle << "" + << "Audio" + << "\n\n\n"; + + writeString(stream.str()); + */ + +} + + +void LLVivoxVoiceClient::getCaptureDevicesSendMessage() +{ + std::ostringstream stream; + stream + << "" + << "\n\n\n"; + + writeString(stream.str()); +} + +void LLVivoxVoiceClient::getRenderDevicesSendMessage() +{ + std::ostringstream stream; + stream + << "" + << "\n\n\n"; + + writeString(stream.str()); +} + +void LLVivoxVoiceClient::clearCaptureDevices() +{ + LL_DEBUGS("Voice") << "called" << LL_ENDL; + mCaptureDevices.clear(); +} + +void LLVivoxVoiceClient::addCaptureDevice(const LLVoiceDevice& device) +{ + LL_DEBUGS("Voice") << "display: '" << device.display_name << "' device: '" << device.full_name << "'" << LL_ENDL; + mCaptureDevices.push_back(device); +} + +LLVoiceDeviceList& LLVivoxVoiceClient::getCaptureDevices() +{ + return mCaptureDevices; +} + +void LLVivoxVoiceClient::setCaptureDevice(const std::string& name) +{ + if(name == "Default") + { + if(!mCaptureDevice.empty()) + { + mCaptureDevice.clear(); + mCaptureDeviceDirty = true; + } + } + else + { + if(mCaptureDevice != name) + { + mCaptureDevice = name; + mCaptureDeviceDirty = true; + } + } +} +void LLVivoxVoiceClient::setDevicesListUpdated(bool state) +{ + mDevicesListUpdated = state; +} + +void LLVivoxVoiceClient::clearRenderDevices() +{ + LL_DEBUGS("Voice") << "called" << LL_ENDL; + mRenderDevices.clear(); +} + +void LLVivoxVoiceClient::addRenderDevice(const LLVoiceDevice& device) +{ + LL_DEBUGS("Voice") << "display: '" << device.display_name << "' device: '" << device.full_name << "'" << LL_ENDL; + mRenderDevices.push_back(device); +} + +LLVoiceDeviceList& LLVivoxVoiceClient::getRenderDevices() +{ + return mRenderDevices; +} + +void LLVivoxVoiceClient::setRenderDevice(const std::string& name) +{ + if(name == "Default") + { + if(!mRenderDevice.empty()) + { + mRenderDevice.clear(); + mRenderDeviceDirty = true; + } + } + else + { + if(mRenderDevice != name) + { + mRenderDevice = name; + mRenderDeviceDirty = true; + } + } + +} + +void LLVivoxVoiceClient::tuningStart() +{ + LL_DEBUGS("Voice") << "Starting tuning" << LL_ENDL; + mTuningMode = true; + if (!mIsCoroutineActive) + { + LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro", + boost::bind(&LLVivoxVoiceClient::voiceControlCoro, LLVivoxVoiceClient::getInstance())); + } + else if (mIsInChannel) + { + LL_DEBUGS("Voice") << "no channel" << LL_ENDL; + sessionTerminate(); + } +} + +void LLVivoxVoiceClient::tuningStop() +{ + mTuningMode = false; +} + +bool LLVivoxVoiceClient::inTuningMode() +{ + return mIsInTuningMode; +} + +void LLVivoxVoiceClient::tuningRenderStartSendMessage(const std::string& name, bool loop) +{ + mTuningAudioFile = name; + std::ostringstream stream; + stream + << "" + << "" << mTuningAudioFile << "" + << "" << (loop?"1":"0") << "" + << "\n\n\n"; + + writeString(stream.str()); +} + +void LLVivoxVoiceClient::tuningRenderStopSendMessage() +{ + std::ostringstream stream; + stream + << "" + << "" << mTuningAudioFile << "" + << "\n\n\n"; + + writeString(stream.str()); +} + +void LLVivoxVoiceClient::tuningCaptureStartSendMessage(int loop) +{ + LL_DEBUGS("Voice") << "sending CaptureAudioStart" << LL_ENDL; + + std::ostringstream stream; + stream + << "" + << "-1" + << "" << loop << "" + << "\n\n\n"; + + writeString(stream.str()); +} + +void LLVivoxVoiceClient::tuningCaptureStopSendMessage() +{ + LL_DEBUGS("Voice") << "sending CaptureAudioStop" << LL_ENDL; + + std::ostringstream stream; + stream + << "" + << "\n\n\n"; + + writeString(stream.str()); + + mTuningEnergy = 0.0f; +} + +void LLVivoxVoiceClient::tuningSetMicVolume(float volume) +{ + int scaled_volume = scale_mic_volume(volume); + + if(scaled_volume != mTuningMicVolume) + { + mTuningMicVolume = scaled_volume; + mTuningMicVolumeDirty = true; + } +} + +void LLVivoxVoiceClient::tuningSetSpeakerVolume(float volume) +{ + int scaled_volume = scale_speaker_volume(volume); + + if(scaled_volume != mTuningSpeakerVolume) + { + mTuningSpeakerVolume = scaled_volume; + mTuningSpeakerVolumeDirty = true; + } +} + +float LLVivoxVoiceClient::tuningGetEnergy(void) +{ + return mTuningEnergy; +} + +bool LLVivoxVoiceClient::deviceSettingsAvailable() +{ + bool result = true; + + if(!sConnected) + result = false; + + if(mRenderDevices.empty()) + result = false; + + return result; +} +bool LLVivoxVoiceClient::deviceSettingsUpdated() +{ + bool updated = mDevicesListUpdated; + if (mDevicesListUpdated) + { + // a hot swap event or a polling of the audio devices has been parsed since the last redraw of the input and output device panel. + mDevicesListUpdated = false; // toggle the setting + } + return updated; +} + +void LLVivoxVoiceClient::refreshDeviceLists(bool clearCurrentList) +{ + if(clearCurrentList) + { + clearCaptureDevices(); + clearRenderDevices(); + } + getCaptureDevicesSendMessage(); + getRenderDevicesSendMessage(); +} + +void LLVivoxVoiceClient::daemonDied() +{ + // The daemon died, so the connection is gone. Reset everything and start over. + LL_WARNS("Voice") << "Connection to vivox daemon lost. Resetting state."<< LL_ENDL; + + //TODO: Try to relaunch the daemon +} + +void LLVivoxVoiceClient::giveUp() +{ + // All has failed. Clean up and stop trying. + LL_WARNS("Voice") << "Terminating Voice Service" << LL_ENDL; + closeSocket(); + cleanUp(); +} + +static void oldSDKTransform (LLVector3 &left, LLVector3 &up, LLVector3 &at, LLVector3d &pos, LLVector3 &vel) +{ + F32 nat[3], nup[3], nl[3]; // the new at, up, left vectors and the new position and velocity +// F32 nvel[3]; + F64 npos[3]; + + // The original XML command was sent like this: + /* + << "" + << "" << pos[VX] << "" + << "" << pos[VZ] << "" + << "" << pos[VY] << "" + << "" + << "" + << "" << mAvatarVelocity[VX] << "" + << "" << mAvatarVelocity[VZ] << "" + << "" << mAvatarVelocity[VY] << "" + << "" + << "" + << "" << l.mV[VX] << "" + << "" << u.mV[VX] << "" + << "" << a.mV[VX] << "" + << "" + << "" + << "" << l.mV[VZ] << "" + << "" << u.mV[VY] << "" + << "" << a.mV[VZ] << "" + << "" + << "" + << "" << l.mV [VY] << "" + << "" << u.mV [VZ] << "" + << "" << a.mV [VY] << "" + << ""; + */ + +#if 1 + // This was the original transform done when building the XML command + nat[0] = left.mV[VX]; + nat[1] = up.mV[VX]; + nat[2] = at.mV[VX]; + + nup[0] = left.mV[VZ]; + nup[1] = up.mV[VY]; + nup[2] = at.mV[VZ]; + + nl[0] = left.mV[VY]; + nl[1] = up.mV[VZ]; + nl[2] = at.mV[VY]; + + npos[0] = pos.mdV[VX]; + npos[1] = pos.mdV[VZ]; + npos[2] = pos.mdV[VY]; + +// nvel[0] = vel.mV[VX]; +// nvel[1] = vel.mV[VZ]; +// nvel[2] = vel.mV[VY]; + + for(int i=0;i<3;++i) { + at.mV[i] = nat[i]; + up.mV[i] = nup[i]; + left.mV[i] = nl[i]; + pos.mdV[i] = npos[i]; + } + + // This was the original transform done in the SDK + nat[0] = at.mV[2]; + nat[1] = 0; // y component of at vector is always 0, this was up[2] + nat[2] = -1 * left.mV[2]; + + // We override whatever the application gives us + nup[0] = 0; // x component of up vector is always 0 + nup[1] = 1; // y component of up vector is always 1 + nup[2] = 0; // z component of up vector is always 0 + + nl[0] = at.mV[0]; + nl[1] = 0; // y component of left vector is always zero, this was up[0] + nl[2] = -1 * left.mV[0]; + + npos[2] = pos.mdV[2] * -1.0; + npos[1] = pos.mdV[1]; + npos[0] = pos.mdV[0]; + + for(int i=0;i<3;++i) { + at.mV[i] = nat[i]; + up.mV[i] = nup[i]; + left.mV[i] = nl[i]; + pos.mdV[i] = npos[i]; + } +#else + // This is the compose of the two transforms (at least, that's what I'm trying for) + nat[0] = at.mV[VX]; + nat[1] = 0; // y component of at vector is always 0, this was up[2] + nat[2] = -1 * up.mV[VZ]; + + // We override whatever the application gives us + nup[0] = 0; // x component of up vector is always 0 + nup[1] = 1; // y component of up vector is always 1 + nup[2] = 0; // z component of up vector is always 0 + + nl[0] = left.mV[VX]; + nl[1] = 0; // y component of left vector is always zero, this was up[0] + nl[2] = -1 * left.mV[VY]; + + npos[0] = pos.mdV[VX]; + npos[1] = pos.mdV[VZ]; + npos[2] = pos.mdV[VY] * -1.0; + + nvel[0] = vel.mV[VX]; + nvel[1] = vel.mV[VZ]; + nvel[2] = vel.mV[VY]; + + for(int i=0;i<3;++i) { + at.mV[i] = nat[i]; + up.mV[i] = nup[i]; + left.mV[i] = nl[i]; + pos.mdV[i] = npos[i]; + } + +#endif +} + +void LLVivoxVoiceClient::setHidden(bool hidden) +{ + mHidden = hidden; + + if (mHidden && inSpatialChannel()) + { + // get out of the channel entirely + leaveAudioSession(); + } + else + { + sendPositionAndVolumeUpdate(); + } +} + +void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) +{ + std::ostringstream stream; + + if (mSpatialCoordsDirty && inSpatialChannel()) + { + LLVector3 l, u, a, vel; + LLVector3d pos; + + mSpatialCoordsDirty = false; + + // Always send both speaker and listener positions together. + stream << "" + << "" << getAudioSessionHandle() << ""; + + stream << ""; + + LLMatrix3 avatarRot = mAvatarRot.getMatrix3(); + +// LL_DEBUGS("Voice") << "Sending speaker position " << mAvatarPosition << LL_ENDL; + l = avatarRot.getLeftRow(); + u = avatarRot.getUpRow(); + a = avatarRot.getFwdRow(); + + pos = mAvatarPosition; + vel = mAvatarVelocity; + + // SLIM SDK: the old SDK was doing a transform on the passed coordinates that the new one doesn't do anymore. + // The old transform is replicated by this function. + oldSDKTransform(l, u, a, pos, vel); + + if (mHidden) + { + for (int i=0;i<3;++i) + { + pos.mdV[i] = VX_NULL_POSITION; + } + } + + stream + << "" + << "" << pos.mdV[VX] << "" + << "" << pos.mdV[VY] << "" + << "" << pos.mdV[VZ] << "" + << "" + << "" + << "" << vel.mV[VX] << "" + << "" << vel.mV[VY] << "" + << "" << vel.mV[VZ] << "" + << "" + << "" + << "" << a.mV[VX] << "" + << "" << a.mV[VY] << "" + << "" << a.mV[VZ] << "" + << "" + << "" + << "" << u.mV[VX] << "" + << "" << u.mV[VY] << "" + << "" << u.mV[VZ] << "" + << "" + << "" + << "" << l.mV [VX] << "" + << "" << l.mV [VY] << "" + << "" << l.mV [VZ] << "" + << "" + ; + + stream << ""; + + stream << ""; + + LLVector3d earPosition; + LLVector3 earVelocity; + LLMatrix3 earRot; + + switch(mEarLocation) + { + case earLocCamera: + default: + earPosition = mCameraPosition; + earVelocity = mCameraVelocity; + earRot = mCameraRot; + break; + + case earLocAvatar: + earPosition = mAvatarPosition; + earVelocity = mAvatarVelocity; + earRot = avatarRot; + break; + + case earLocMixed: + earPosition = mAvatarPosition; + earVelocity = mAvatarVelocity; + earRot = mCameraRot; + break; + } + + l = earRot.getLeftRow(); + u = earRot.getUpRow(); + a = earRot.getFwdRow(); + + pos = earPosition; + vel = earVelocity; + + + oldSDKTransform(l, u, a, pos, vel); + + if (mHidden) + { + for (int i=0;i<3;++i) + { + pos.mdV[i] = VX_NULL_POSITION; + } + } + + stream + << "" + << "" << pos.mdV[VX] << "" + << "" << pos.mdV[VY] << "" + << "" << pos.mdV[VZ] << "" + << "" + << "" + << "" << vel.mV[VX] << "" + << "" << vel.mV[VY] << "" + << "" << vel.mV[VZ] << "" + << "" + << "" + << "" << a.mV[VX] << "" + << "" << a.mV[VY] << "" + << "" << a.mV[VZ] << "" + << "" + << "" + << "" << u.mV[VX] << "" + << "" << u.mV[VY] << "" + << "" << u.mV[VZ] << "" + << "" + << "" + << "" << l.mV [VX] << "" + << "" << l.mV [VY] << "" + << "" << l.mV [VZ] << "" + << "" + ; + + stream << ""; + + stream << "1"; //do not generate responses for update requests + stream << "\n\n\n"; + } + + if(mAudioSession && (mAudioSession->mVolumeDirty || mAudioSession->mMuteDirty)) + { + participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin(); + + mAudioSession->mVolumeDirty = false; + mAudioSession->mMuteDirty = false; + + for(; iter != mAudioSession->mParticipantsByURI.end(); iter++) + { + participantStatePtr_t p(iter->second); + + if(p->mVolumeDirty) + { + // Can't set volume/mute for yourself + if(!p->mIsSelf) + { + // scale from the range 0.0-1.0 to vivox volume in the range 0-100 + S32 volume = ll_round(p->mVolume / VOLUME_SCALE_VIVOX); + bool mute = p->mOnMuteList; + + if(mute) + { + // SetParticipantMuteForMe doesn't work in p2p sessions. + // If we want the user to be muted, set their volume to 0 as well. + // This isn't perfect, but it will at least reduce their volume to a minimum. + volume = 0; + // Mark the current volume level as set to prevent incoming events + // changing it to 0, so that we can return to it when unmuting. + p->mVolumeSet = true; + } + + if(volume == 0) + { + mute = true; + } + + LL_DEBUGS("Voice") << "Setting volume/mute for avatar " << p->mAvatarID << " to " << volume << (mute?"/true":"/false") << LL_ENDL; + + // SLIM SDK: Send both volume and mute commands. + + // Send a "volume for me" command for the user. + stream << "" + << "" << getAudioSessionHandle() << "" + << "" << p->mURI << "" + << "" << volume << "" + << "\n\n\n"; + + if(!mAudioSession->mIsP2P) + { + // Send a "mute for me" command for the user + // Doesn't work in P2P sessions + stream << "" + << "" << getAudioSessionHandle() << "" + << "" << p->mURI << "" + << "" << (mute?"1":"0") << "" + << "Audio" + << "\n\n\n"; + } + } + + p->mVolumeDirty = false; + } + } + } + + std::string update(stream.str()); + if(!update.empty()) + { + LL_DEBUGS("VoiceUpdate") << "sending update " << update << LL_ENDL; + writeString(update); + } + +} + +void LLVivoxVoiceClient::buildSetCaptureDevice(std::ostringstream &stream) +{ + if(mCaptureDeviceDirty) + { + LL_DEBUGS("Voice") << "Setting input device = \"" << mCaptureDevice << "\"" << LL_ENDL; + + stream + << "" + << "" << mCaptureDevice << "" + << "" + << "\n\n\n"; + + mCaptureDeviceDirty = false; + } +} + +void LLVivoxVoiceClient::buildSetRenderDevice(std::ostringstream &stream) +{ + if(mRenderDeviceDirty) + { + LL_DEBUGS("Voice") << "Setting output device = \"" << mRenderDevice << "\"" << LL_ENDL; + + stream + << "" + << "" << mRenderDevice << "" + << "" + << "\n\n\n"; + mRenderDeviceDirty = false; + } +} + +void LLVivoxVoiceClient::sendLocalAudioUpdates() +{ + // Check all of the dirty states and then send messages to those needing to be changed. + // Tuningmode hands its own mute settings. + std::ostringstream stream; + + if (mMuteMicDirty && !mTuningMode) + { + mMuteMicDirty = false; + + // Send a local mute command. + + LL_INFOS("Voice") << "Sending MuteLocalMic command with parameter " << (mMuteMic ? "true" : "false") << LL_ENDL; + + stream << "" + << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" + << "" << (mMuteMic ? "true" : "false") << "" + << "\n\n\n"; + + } + + if (mSpeakerMuteDirty && !mTuningMode) + { + const char *muteval = ((mSpeakerVolume <= scale_speaker_volume(0)) ? "true" : "false"); + + mSpeakerMuteDirty = false; + + LL_INFOS("Voice") << "Setting speaker mute to " << muteval << LL_ENDL; + + stream << "" + << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" + << "" << muteval << "" + << "\n\n\n"; + + } + + if (mSpeakerVolumeDirty) + { + mSpeakerVolumeDirty = false; + + LL_INFOS("Voice") << "Setting speaker volume to " << mSpeakerVolume << LL_ENDL; + + stream << "" + << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" + << "" << mSpeakerVolume << "" + << "\n\n\n"; + + } + + if (mMicVolumeDirty) + { + mMicVolumeDirty = false; + + LL_INFOS("Voice") << "Setting mic volume to " << mMicVolume << LL_ENDL; + + stream << "" + << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" + << "" << mMicVolume << "" + << "\n\n\n"; + } + + + if (!stream.str().empty()) + { + writeString(stream.str()); + } +} + +/** + * Because of the recurring voice cutout issues (SL-15072) we are going to try + * to disable the automatic VAD (Voice Activity Detection) and set the associated + * parameters directly. We will expose them via Debug Settings and that should + * let us iterate on a collection of values that work for us. Hopefully! + * + * From the VIVOX Docs: + * + * VadAuto: A flag indicating if the automatic VAD is enabled (1) or disabled (0) + * + * VadHangover: The time (in milliseconds) that it takes + * for the VAD to switch back to silence from speech mode after the last speech + * frame has been detected. + * + * VadNoiseFloor: A dimensionless value between 0 and + * 20000 (default 576) that controls the maximum level at which the noise floor + * may be set at by the VAD's noise tracking. Too low of a value will make noise + * tracking ineffective (A value of 0 disables noise tracking and the VAD then + * relies purely on the sensitivity property). Too high of a value will make + * long speech classifiable as noise. + * + * VadSensitivity: A dimensionless value between 0 and + * 100, indicating the 'sensitivity of the VAD'. Increasing this value corresponds + * to decreasing the sensitivity of the VAD (i.e. '0' is most sensitive, + * while 100 is 'least sensitive') + */ +void LLVivoxVoiceClient::setupVADParams(unsigned int vad_auto, + unsigned int vad_hangover, + unsigned int vad_noise_floor, + unsigned int vad_sensitivity) +{ + std::ostringstream stream; + + LL_INFOS("Voice") << "Setting the automatic VAD to " + << (vad_auto ? "True" : "False") + << " and discrete values to" + << " VadHangover = " << vad_hangover + << ", VadSensitivity = " << vad_sensitivity + << ", VadNoiseFloor = " << vad_noise_floor + << LL_ENDL; + + // Create a request to set the VAD parameters: + stream << "" + << "" << vad_auto << "" + << "" << vad_hangover << "" + << "" << vad_sensitivity << "" + << "" << vad_noise_floor << "" + << "\n\n\n"; + + if (!stream.str().empty()) + { + writeString(stream.str()); + } +} + +void LLVivoxVoiceClient::onVADSettingsChange() +{ + // pick up the VAD variables (one of which was changed) + unsigned int vad_auto = gSavedSettings.getU32("VivoxVadAuto"); + unsigned int vad_hangover = gSavedSettings.getU32("VivoxVadHangover"); + unsigned int vad_noise_floor = gSavedSettings.getU32("VivoxVadNoiseFloor"); + unsigned int vad_sensitivity = gSavedSettings.getU32("VivoxVadSensitivity"); + + // build a VAD params change request and send it to SLVoice + setupVADParams(vad_auto, vad_hangover, vad_noise_floor, vad_sensitivity); +} + +///////////////////////////// +// Response/Event handlers + +void LLVivoxVoiceClient::connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID) +{ + LLSD result = LLSD::emptyMap(); + + if(statusCode == 0) + { + // Connector created, move forward. + if (connectorHandle == LLVivoxSecurity::getInstance()->connectorHandle()) + { + LL_INFOS("Voice") << "Voice connector succeeded, Vivox SDK version is " << versionID << " connector handle " << connectorHandle << LL_ENDL; + mVoiceVersion.serverVersion = versionID; + mConnectorEstablished = true; + mTerminateDaemon = false; + + result["connector"] = LLSD::Boolean(true); + } + else + { + // This shouldn't happen - we are somehow out of sync with SLVoice + // or possibly there are two things trying to run SLVoice at once + // or someone is trying to hack into it. + LL_WARNS("Voice") << "Connector returned wrong handle " + << "(" << connectorHandle << ")" + << " expected (" << LLVivoxSecurity::getInstance()->connectorHandle() << ")" + << LL_ENDL; + result["connector"] = LLSD::Boolean(false); + // Give up. + mTerminateDaemon = true; + } + } + else if (statusCode == 10028) // web request timeout prior to login + { + // this is usually fatal, but a long timeout might work + result["connector"] = LLSD::Boolean(false); + result["retry"] = LLSD::Real(CONNECT_ATTEMPT_TIMEOUT); + + LL_WARNS("Voice") << "Voice connection failed" << LL_ENDL; + } + else if (statusCode == 10006) // name resolution failure - a shorter retry may work + { + // some networks have slower DNS, but a short timeout might let it catch up + result["connector"] = LLSD::Boolean(false); + result["retry"] = LLSD::Real(CONNECT_DNS_TIMEOUT); + + LL_WARNS("Voice") << "Voice connection DNS lookup failed" << LL_ENDL; + } + else // unknown failure - give up + { + LL_WARNS("Voice") << "Voice connection failure ("<< statusCode << "): " << statusString << LL_ENDL; + mTerminateDaemon = true; + result["connector"] = LLSD::Boolean(false); + } + + mVivoxPump.post(result); +} + +void LLVivoxVoiceClient::loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases) +{ + LLSD result = LLSD::emptyMap(); + + LL_DEBUGS("Voice") << "Account.Login response (" << statusCode << "): " << statusString << LL_ENDL; + + // Status code of 20200 means "bad password". We may want to special-case that at some point. + + if ( statusCode == HTTP_UNAUTHORIZED ) + { + // Login failure which is probably caused by the delay after a user's password being updated. + LL_INFOS("Voice") << "Account.Login response failure (" << statusCode << "): " << statusString << LL_ENDL; + result["login"] = LLSD::String("retry"); + } + else if(statusCode != 0) + { + LL_WARNS("Voice") << "Account.Login response failure (" << statusCode << "): " << statusString << LL_ENDL; + result["login"] = LLSD::String("failed"); + } + else + { + // Login succeeded, move forward. + mAccountLoggedIn = true; + mNumberOfAliases = numberOfAliases; + result["login"] = LLSD::String("response_ok"); + } + + mVivoxPump.post(result); + +} + +void LLVivoxVoiceClient::sessionCreateResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle) +{ + sessionStatePtr_t session(findSessionBeingCreatedByURI(requestId)); + + if(session) + { + session->mCreateInProgress = false; + } + + if(statusCode != 0) + { + LL_WARNS("Voice") << "Session.Create response failure (" << statusCode << "): " << statusString << LL_ENDL; + if(session) + { + session->mErrorStatusCode = statusCode; + session->mErrorStatusString = statusString; + if(session == mAudioSession) + { + LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle)) + ("session", "failed") + ("reason", LLSD::Integer(statusCode))); + + mVivoxPump.post(vivoxevent); + } + else + { + reapSession(session); + } + } + } + else + { + LL_INFOS("Voice") << "Session.Create response received (success), session handle is " << sessionHandle << LL_ENDL; + if(session) + { + setSessionHandle(session, sessionHandle); + } + LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle)) + ("session", "created")); + + mVivoxPump.post(vivoxevent); + } +} + +void LLVivoxVoiceClient::sessionGroupAddSessionResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle) +{ + sessionStatePtr_t session(findSessionBeingCreatedByURI(requestId)); + + if(session) + { + session->mCreateInProgress = false; + } + + if(statusCode != 0) + { + LL_WARNS("Voice") << "SessionGroup.AddSession response failure (" << statusCode << "): " << statusString << LL_ENDL; + if(session) + { + session->mErrorStatusCode = statusCode; + session->mErrorStatusString = statusString; + if(session == mAudioSession) + { + LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle)) + ("session", "failed")); + + mVivoxPump.post(vivoxevent); + } + else + { + reapSession(session); + } + } + } + else + { + LL_DEBUGS("Voice") << "SessionGroup.AddSession response received (success), session handle is " << sessionHandle << LL_ENDL; + if(session) + { + setSessionHandle(session, sessionHandle); + } + + LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle)) + ("session", "added")); + + mVivoxPump.post(vivoxevent); + + } +} + +void LLVivoxVoiceClient::sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString) +{ + sessionStatePtr_t session(findSession(requestId)); + // 1026 is session already has media, somehow mediaconnect was called twice on the same session. + // set the session info to reflect that the user is already connected. + if (statusCode == 1026) + { + session->mVoiceActive = true; + session->mMediaConnectInProgress = false; + session->mMediaStreamState = streamStateConnected; + //session->mTextStreamState = streamStateConnected; + session->mErrorStatusCode = 0; + } + else if (statusCode != 0) + { + LL_WARNS("Voice") << "Session.Connect response failure (" << statusCode << "): " << statusString << LL_ENDL; + if (session) + { + session->mMediaConnectInProgress = false; + session->mErrorStatusCode = statusCode; + session->mErrorStatusString = statusString; + } + } + else + { + LL_DEBUGS("Voice") << "Session.Connect response received (success)" << LL_ENDL; + } +} + +void LLVivoxVoiceClient::logoutResponse(int statusCode, std::string &statusString) +{ + if(statusCode != 0) + { + LL_WARNS("Voice") << "Account.Logout response failure: " << statusString << LL_ENDL; + // Should this ever fail? do we care if it does? + } + LLSD vivoxevent(LLSDMap("logout", LLSD::Boolean(true))); + + mVivoxPump.post(vivoxevent); +} + +void LLVivoxVoiceClient::connectorShutdownResponse(int statusCode, std::string &statusString) +{ + if(statusCode != 0) + { + LL_WARNS("Voice") << "Connector.InitiateShutdown response failure: " << statusString << LL_ENDL; + // Should this ever fail? do we care if it does? + } + + sConnected = false; + mShutdownComplete = true; + + LLSD vivoxevent(LLSDMap("connector", LLSD::Boolean(false))); + + mVivoxPump.post(vivoxevent); +} + +void LLVivoxVoiceClient::sessionAddedEvent( + std::string &uriString, + std::string &alias, + std::string &sessionHandle, + std::string &sessionGroupHandle, + bool isChannel, + bool incoming, + std::string &nameString, + std::string &applicationString) +{ + sessionStatePtr_t session; + + LL_INFOS("Voice") << "session " << uriString << ", alias " << alias << ", name " << nameString << " handle " << sessionHandle << LL_ENDL; + + session = addSession(uriString, sessionHandle); + if(session) + { + session->mGroupHandle = sessionGroupHandle; + session->mIsChannel = isChannel; + session->mIncoming = incoming; + session->mAlias = alias; + + // Generate a caller UUID -- don't need to do this for channels + if(!session->mIsChannel) + { + if(IDFromName(session->mSIPURI, session->mCallerID)) + { + // Normal URI(base64-encoded UUID) + } + else if(!session->mAlias.empty() && IDFromName(session->mAlias, session->mCallerID)) + { + // Wrong URI, but an alias is available. Stash the incoming URI as an alternate + session->mAlternateSIPURI = session->mSIPURI; + + // and generate a proper URI from the ID. + setSessionURI(session, sipURIFromID(session->mCallerID)); + } + else + { + LL_INFOS("Voice") << "Could not generate caller id from uri, using hash of uri " << session->mSIPURI << LL_ENDL; + session->mCallerID.generate(session->mSIPURI); + session->mSynthesizedCallerID = true; + + // Can't look up the name in this case -- we have to extract it from the URI. + std::string namePortion = nameFromsipURI(session->mSIPURI); + if(namePortion.empty()) + { + // Didn't seem to be a SIP URI, just use the whole provided name. + namePortion = nameString; + } + + // Some incoming names may be separated with an underscore instead of a space. Fix this. + LLStringUtil::replaceChar(namePortion, '_', ' '); + + // Act like we just finished resolving the name (this stores it in all the right places) + avatarNameResolved(session->mCallerID, namePortion); + } + + LL_INFOS("Voice") << "caller ID: " << session->mCallerID << LL_ENDL; + + if(!session->mSynthesizedCallerID) + { + // If we got here, we don't have a proper name. Initiate a lookup. + lookupName(session->mCallerID); + } + } + } +} + +void LLVivoxVoiceClient::sessionGroupAddedEvent(std::string &sessionGroupHandle) +{ + LL_DEBUGS("Voice") << "handle " << sessionGroupHandle << LL_ENDL; + +#if USE_SESSION_GROUPS + if(mMainSessionGroupHandle.empty()) + { + // This is the first (i.e. "main") session group. Save its handle. + mMainSessionGroupHandle = sessionGroupHandle; + } + else + { + LL_DEBUGS("Voice") << "Already had a session group handle " << mMainSessionGroupHandle << LL_ENDL; + } +#endif +} + +void LLVivoxVoiceClient::joinedAudioSession(const sessionStatePtr_t &session) +{ + LL_DEBUGS("Voice") << "Joined Audio Session" << LL_ENDL; + if(mAudioSession != session) + { + sessionStatePtr_t oldSession = mAudioSession; + + mAudioSession = session; + mAudioSessionChanged = true; + + // The old session may now need to be deleted. + reapSession(oldSession); + } + + // This is the session we're joining. + if(mIsJoiningSession) + { + LLSD vivoxevent(LLSDMap("handle", LLSD::String(session->mHandle)) + ("session", "joined")); + + mVivoxPump.post(vivoxevent); + + // Add the current user as a participant here. + participantStatePtr_t participant(session->addParticipant(sipURIFromName(mAccountName))); + if(participant) + { + participant->mIsSelf = true; + lookupName(participant->mAvatarID); + + LL_INFOS("Voice") << "added self as participant \"" << participant->mAccountName + << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; + } + + if(!session->mIsChannel) + { + // this is a p2p session. Make sure the other end is added as a participant. + participantStatePtr_t participant(session->addParticipant(session->mSIPURI)); + if(participant) + { + if(participant->mAvatarIDValid) + { + lookupName(participant->mAvatarID); + } + else if(!session->mName.empty()) + { + participant->mDisplayName = session->mName; + avatarNameResolved(participant->mAvatarID, session->mName); + } + + // TODO: Question: Do we need to set up mAvatarID/mAvatarIDValid here? + LL_INFOS("Voice") << "added caller as participant \"" << participant->mAccountName + << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; + } + } + } +} + +void LLVivoxVoiceClient::sessionRemovedEvent( + std::string &sessionHandle, + std::string &sessionGroupHandle) +{ + LL_INFOS("Voice") << "handle " << sessionHandle << LL_ENDL; + + sessionStatePtr_t session(findSession(sessionHandle)); + if(session) + { + leftAudioSession(session); + + // This message invalidates the session's handle. Set it to empty. + clearSessionHandle(session); + + // This also means that the session's session group is now empty. + // Terminate the session group so it doesn't leak. + sessionGroupTerminateSendMessage(session); + + // Reset the media state (we now have no info) + session->mMediaStreamState = streamStateUnknown; + //session->mTextStreamState = streamStateUnknown; + + // Conditionally delete the session + reapSession(session); + } + else + { + // Already reaped this session. + LL_DEBUGS("Voice") << "unknown session " << sessionHandle << " removed" << LL_ENDL; + } + +} + +void LLVivoxVoiceClient::reapSession(const sessionStatePtr_t &session) +{ + if(session) + { + + if(session->mCreateInProgress) + { + LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (create in progress)" << LL_ENDL; + } + else if(session->mMediaConnectInProgress) + { + LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (connect in progress)" << LL_ENDL; + } + else if(session == mAudioSession) + { + LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the current session)" << LL_ENDL; + } + else if(session == mNextAudioSession) + { + LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the next session)" << LL_ENDL; + } + else + { + // We don't have a reason to keep tracking this session, so just delete it. + LL_DEBUGS("Voice") << "deleting session " << session->mSIPURI << LL_ENDL; + deleteSession(session); + } + } + else + { +// LL_DEBUGS("Voice") << "session is NULL" << LL_ENDL; + } +} + +// Returns true if the session seems to indicate we've moved to a region on a different voice server +bool LLVivoxVoiceClient::sessionNeedsRelog(const sessionStatePtr_t &session) +{ + bool result = false; + + if(session) + { + // Only make this check for spatial channels (so it won't happen for group or p2p calls) + if(session->mIsSpatial) + { + std::string::size_type atsign; + + atsign = session->mSIPURI.find("@"); + + if(atsign != std::string::npos) + { + std::string urihost = session->mSIPURI.substr(atsign + 1); + if(stricmp(urihost.c_str(), mVoiceSIPURIHostName.c_str())) + { + // The hostname in this URI is different from what we expect. This probably means we need to relog. + + // We could make a ProvisionVoiceAccountRequest and compare the result with the current values of + // mVoiceSIPURIHostName and mVoiceAccountServerURI to be really sure, but this is a pretty good indicator. + + result = true; + } + } + } + } + + return result; +} + +void LLVivoxVoiceClient::leftAudioSession(const sessionStatePtr_t &session) +{ + if (mAudioSession == session) + { + LLSD vivoxevent(LLSDMap("handle", LLSD::String(session->mHandle)) + ("session", "removed")); + + mVivoxPump.post(vivoxevent); + } +} + +void LLVivoxVoiceClient::accountLoginStateChangeEvent( + std::string &accountHandle, + int statusCode, + std::string &statusString, + int state) +{ + LLSD levent = LLSD::emptyMap(); + + /* + According to Mike S., status codes for this event are: + login_state_logged_out=0, + login_state_logged_in = 1, + login_state_logging_in = 2, + login_state_logging_out = 3, + login_state_resetting = 4, + login_state_error=100 + */ + + LL_DEBUGS("Voice") << "state change event: " << state << LL_ENDL; + switch(state) + { + case 1: + levent["login"] = LLSD::String("account_login"); + + mVivoxPump.post(levent); + break; + case 2: + break; + + case 3: + levent["login"] = LLSD::String("account_loggingOut"); + + mVivoxPump.post(levent); + break; + + case 4: + break; + + case 100: + LL_WARNS("Voice") << "account state event error" << LL_ENDL; + break; + + case 0: + levent["login"] = LLSD::String("account_logout"); + + mVivoxPump.post(levent); + break; + + default: + //Used to be a commented out warning + LL_WARNS("Voice") << "unknown account state event: " << state << LL_ENDL; + break; + } +} + +void LLVivoxVoiceClient::mediaCompletionEvent(std::string &sessionGroupHandle, std::string &mediaCompletionType) +{ + LLSD result; + + if (mediaCompletionType == "AuxBufferAudioCapture") + { + mCaptureBufferRecording = false; + result["recplay"] = "end"; + } + else if (mediaCompletionType == "AuxBufferAudioRender") + { + // Ignore all but the last stop event + if (--mPlayRequestCount <= 0) + { + mCaptureBufferPlaying = false; + result["recplay"] = "end"; +// result["recplay"] = "done"; + } + } + else + { + LL_WARNS("Voice") << "Unknown MediaCompletionType: " << mediaCompletionType << LL_ENDL; + } + + if (!result.isUndefined()) + mVivoxPump.post(result); +} + +void LLVivoxVoiceClient::mediaStreamUpdatedEvent( + std::string &sessionHandle, + std::string &sessionGroupHandle, + int statusCode, + std::string &statusString, + int state, + bool incoming) +{ + sessionStatePtr_t session(findSession(sessionHandle)); + + LL_DEBUGS("Voice") << "session " << sessionHandle << ", status code " << statusCode << ", string \"" << statusString << "\"" << LL_ENDL; + + if(session) + { + // We know about this session + + // Save the state for later use + session->mMediaStreamState = state; + + switch(statusCode) + { + case 0: + case HTTP_OK: + // generic success + // Don't change the saved error code (it may have been set elsewhere) + break; + default: + // save the status code for later + session->mErrorStatusCode = statusCode; + break; + } + + switch(state) + { + case streamStateDisconnecting: + case streamStateIdle: + // Standard "left audio session", Vivox state 'disconnected' + session->mVoiceActive = false; + session->mMediaConnectInProgress = false; + leftAudioSession(session); + break; + + case streamStateConnected: + session->mVoiceActive = true; + session->mMediaConnectInProgress = false; + joinedAudioSession(session); + case streamStateConnecting: // do nothing, but prevents a warning getting into the logs. + break; + + case streamStateRinging: + if(incoming) + { + // Send the voice chat invite to the GUI layer + // TODO: Question: Should we correlate with the mute list here? + session->mIMSessionID = LLIMMgr::computeSessionID(IM_SESSION_P2P_INVITE, session->mCallerID); + session->mVoiceInvitePending = true; + if(session->mName.empty()) + { + lookupName(session->mCallerID); + } + else + { + // Act like we just finished resolving the name + avatarNameResolved(session->mCallerID, session->mName); + } + } + break; + + default: + LL_WARNS("Voice") << "unknown state " << state << LL_ENDL; + break; + + } + + } + else + { + // session disconnectintg and disconnected events arriving after we have already left the session. + LL_DEBUGS("Voice") << "session " << sessionHandle << " not found"<< LL_ENDL; + } +} + +void LLVivoxVoiceClient::participantAddedEvent( + std::string &sessionHandle, + std::string &sessionGroupHandle, + std::string &uriString, + std::string &alias, + std::string &nameString, + std::string &displayNameString, + int participantType) +{ + sessionStatePtr_t session(findSession(sessionHandle)); + if(session) + { + participantStatePtr_t participant(session->addParticipant(uriString)); + if(participant) + { + participant->mAccountName = nameString; + + LL_DEBUGS("Voice") << "added participant \"" << participant->mAccountName + << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; + + if(participant->mAvatarIDValid) + { + // Initiate a lookup + lookupName(participant->mAvatarID); + } + else + { + // If we don't have a valid avatar UUID, we need to fill in the display name to make the active speakers floater work. + std::string namePortion = nameFromsipURI(uriString); + if(namePortion.empty()) + { + // Problem with the SIP URI, fall back to the display name + namePortion = displayNameString; + } + if(namePortion.empty()) + { + // Problems with both of the above, fall back to the account name + namePortion = nameString; + } + + // Set the display name (which is a hint to the active speakers window not to do its own lookup) + participant->mDisplayName = namePortion; + avatarNameResolved(participant->mAvatarID, namePortion); + } + } + } +} + +void LLVivoxVoiceClient::participantRemovedEvent( + std::string &sessionHandle, + std::string &sessionGroupHandle, + std::string &uriString, + std::string &alias, + std::string &nameString) +{ + sessionStatePtr_t session(findSession(sessionHandle)); + if(session) + { + participantStatePtr_t participant(session->findParticipant(uriString)); + if(participant) + { + session->removeParticipant(participant); + } + else + { + LL_DEBUGS("Voice") << "unknown participant " << uriString << LL_ENDL; + } + } + else + { + // a late arriving event on a session we have already left. + LL_DEBUGS("Voice") << "unknown session " << sessionHandle << LL_ENDL; + } +} + + +void LLVivoxVoiceClient::participantUpdatedEvent( + std::string &sessionHandle, + std::string &sessionGroupHandle, + std::string &uriString, + std::string &alias, + bool isModeratorMuted, + bool isSpeaking, + int volume, + F32 energy) +{ + sessionStatePtr_t session(findSession(sessionHandle)); + if(session) + { + participantStatePtr_t participant(session->findParticipant(uriString)); + + if(participant) + { + //LL_INFOS("Voice") << "Participant Update for " << participant->mDisplayName << LL_ENDL; + + participant->mIsSpeaking = isSpeaking; + participant->mIsModeratorMuted = isModeratorMuted; + + // SLIM SDK: convert range: ensure that energy is set to zero if is_speaking is false + if (isSpeaking) + { + participant->mSpeakingTimeout.reset(); + participant->mPower = energy; + } + else + { + participant->mPower = 0.0f; + } + + // Ignore incoming volume level if it has been explicitly set, or there + // is a volume or mute change pending. + if ( !participant->mVolumeSet && !participant->mVolumeDirty) + { + participant->mVolume = (F32)volume * VOLUME_SCALE_VIVOX; + } + + // *HACK: mantipov: added while working on EXT-3544 + /* + Sometimes LLVoiceClient::participantUpdatedEvent callback is called BEFORE + LLViewerChatterBoxSessionAgentListUpdates::post() sometimes AFTER. + + participantUpdatedEvent updates voice participant state in particular participantState::mIsModeratorMuted + Originally we wanted to update session Speaker Manager to fire LLSpeakerVoiceModerationEvent to fix the EXT-3544 bug. + Calling of the LLSpeakerMgr::update() method was added into LLIMMgr::processAgentListUpdates. + + But in case participantUpdatedEvent() is called after LLViewerChatterBoxSessionAgentListUpdates::post() + voice participant mIsModeratorMuted is changed after speakers are updated in Speaker Manager + and event is not fired. + + So, we have to call LLSpeakerMgr::update() here. + */ + LLVoiceChannel* voice_cnl = LLVoiceChannel::getCurrentVoiceChannel(); + + // ignore session ID of local chat + if (voice_cnl && voice_cnl->getSessionID().notNull()) + { + LLSpeakerMgr* speaker_manager = LLIMModel::getInstance()->getSpeakerManager(voice_cnl->getSessionID()); + if (speaker_manager) + { + speaker_manager->update(true); + + // also initialize voice moderate_mode depend on Agent's participant. See EXT-6937. + // *TODO: remove once a way to request the current voice channel moderation mode is implemented. + if (gAgent.getID() == participant->mAvatarID) + { + speaker_manager->initVoiceModerateMode(); + } + } + } + } + else + { + LL_WARNS("Voice") << "unknown participant: " << uriString << LL_ENDL; + } + } + else + { + LL_DEBUGS("Voice") << "unknown session " << sessionHandle << LL_ENDL; + } +} + +void LLVivoxVoiceClient::messageEvent( + std::string &sessionHandle, + std::string &uriString, + std::string &alias, + std::string &messageHeader, + std::string &messageBody, + std::string &applicationString) +{ + LL_DEBUGS("Voice") << "Message event, session " << sessionHandle << " from " << uriString << LL_ENDL; +// LL_DEBUGS("Voice") << " header " << messageHeader << ", body: \n" << messageBody << LL_ENDL; + + LL_INFOS("Voice") << "Vivox raw message:" << std::endl << messageBody << LL_ENDL; + + if(messageHeader.find(HTTP_CONTENT_TEXT_HTML) != std::string::npos) + { + std::string message; + + { + const std::string startMarker = ", try looking for a instead. + start = messageBody.find(startSpan); + start = messageBody.find(startMarker2, start); + end = messageBody.find(endSpan); + + if(start != std::string::npos) + { + start += startMarker2.size(); + + if(end != std::string::npos) + end -= start; + + message.assign(messageBody, start, end); + } + } + } + +// LL_DEBUGS("Voice") << " raw message = \n" << message << LL_ENDL; + + // strip formatting tags + { + std::string::size_type start; + std::string::size_type end; + + while((start = message.find('<')) != std::string::npos) + { + if((end = message.find('>', start + 1)) != std::string::npos) + { + // Strip out the tag + message.erase(start, (end + 1) - start); + } + else + { + // Avoid an infinite loop + break; + } + } + } + + // Decode ampersand-escaped chars + { + std::string::size_type mark = 0; + + // The text may contain text encoded with <, >, and & + mark = 0; + while((mark = message.find("<", mark)) != std::string::npos) + { + message.replace(mark, 4, "<"); + mark += 1; + } + + mark = 0; + while((mark = message.find(">", mark)) != std::string::npos) + { + message.replace(mark, 4, ">"); + mark += 1; + } + + mark = 0; + while((mark = message.find("&", mark)) != std::string::npos) + { + message.replace(mark, 5, "&"); + mark += 1; + } + } + + // strip leading/trailing whitespace (since we always seem to get a couple newlines) + LLStringUtil::trim(message); + +// LL_DEBUGS("Voice") << " stripped message = \n" << message << LL_ENDL; + + sessionStatePtr_t session(findSession(sessionHandle)); + if(session) + { + bool is_do_not_disturb = gAgent.isDoNotDisturb(); + bool is_muted = LLMuteList::getInstance()->isMuted(session->mCallerID, session->mName, LLMute::flagTextChat); + bool is_linden = LLMuteList::isLinden(session->mName); + LLChat chat; + + chat.mMuted = is_muted && !is_linden; + + if(!chat.mMuted) + { + chat.mFromID = session->mCallerID; + chat.mFromName = session->mName; + chat.mSourceType = CHAT_SOURCE_AGENT; + + if(is_do_not_disturb && !is_linden) + { + // TODO: Question: Return do not disturb mode response here? Or maybe when session is started instead? + } + + LL_DEBUGS("Voice") << "adding message, name " << session->mName << " session " << session->mIMSessionID << ", target " << session->mCallerID << LL_ENDL; + LLIMMgr::getInstance()->addMessage(session->mIMSessionID, + session->mCallerID, + session->mName.c_str(), + message.c_str(), + false, + LLStringUtil::null, // default arg + IM_NOTHING_SPECIAL, // default arg + 0, // default arg + LLUUID::null, // default arg + LLVector3::zero); // default arg + } + } + } +} + +void LLVivoxVoiceClient::sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string ¬ificationType) +{ + sessionStatePtr_t session(findSession(sessionHandle)); + + if(session) + { + participantStatePtr_t participant(session->findParticipant(uriString)); + if(participant) + { + if (!stricmp(notificationType.c_str(), "Typing")) + { + // Other end started typing + // TODO: The proper way to add a typing notification seems to be LLIMMgr::processIMTypingStart(). + // It requires some info for the message, which we don't have here. + } + else if (!stricmp(notificationType.c_str(), "NotTyping")) + { + // Other end stopped typing + // TODO: The proper way to remove a typing notification seems to be LLIMMgr::processIMTypingStop(). + // It requires some info for the message, which we don't have here. + } + else + { + LL_DEBUGS("Voice") << "Unknown notification type " << notificationType << "for participant " << uriString << " in session " << session->mSIPURI << LL_ENDL; + } + } + else + { + LL_DEBUGS("Voice") << "Unknown participant " << uriString << " in session " << session->mSIPURI << LL_ENDL; + } + } + else + { + LL_DEBUGS("Voice") << "Unknown session handle " << sessionHandle << LL_ENDL; + } +} + +void LLVivoxVoiceClient::voiceServiceConnectionStateChangedEvent(int statusCode, std::string &statusString, std::string &build_id) +{ + // We don't generally need to process this. However, one occurence is when we first connect, and so it is the + // earliest opportunity to learn what we're connected to. + if (statusCode) + { + LL_WARNS("Voice") << "VoiceServiceConnectionStateChangedEvent statusCode: " << statusCode << + "statusString: " << statusString << LL_ENDL; + return; + } + if (build_id.empty()) + { + return; + } + mVoiceVersion.mBuildVersion = build_id; +} + +void LLVivoxVoiceClient::auxAudioPropertiesEvent(F32 energy) +{ + LL_DEBUGS("VoiceEnergy") << "got energy " << energy << LL_ENDL; + mTuningEnergy = energy; +} + +void LLVivoxVoiceClient::muteListChanged() +{ + // The user's mute list has been updated. Go through the current participant list and sync it with the mute list. + if(mAudioSession) + { + participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin(); + + for(; iter != mAudioSession->mParticipantsByURI.end(); iter++) + { + participantStatePtr_t p(iter->second); + + // Check to see if this participant is on the mute list already + if(p->updateMuteState()) + mAudioSession->mVolumeDirty = true; + } + } +} + +///////////////////////////// +// Managing list of participants +LLVivoxVoiceClient::participantState::participantState(const std::string &uri) : + mURI(uri), + mPTT(false), + mIsSpeaking(false), + mIsModeratorMuted(false), + mLastSpokeTimestamp(0.f), + mPower(0.f), + mVolume(LLVoiceClient::VOLUME_DEFAULT), + mUserVolume(0), + mOnMuteList(false), + mVolumeSet(false), + mVolumeDirty(false), + mAvatarIDValid(false), + mIsSelf(false) +{ +} + +LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::addParticipant(const std::string &uri) +{ + participantStatePtr_t result; + bool useAlternateURI = false; + + // Note: this is mostly the body of LLVivoxVoiceClient::sessionState::findParticipant(), but since we need to know if it + // matched the alternate SIP URI (so we can add it properly), we need to reproduce it here. + { + participantMap::iterator iter = mParticipantsByURI.find(uri); + + if(iter == mParticipantsByURI.end()) + { + if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI)) + { + // This is a p2p session (probably with the SLIM client) with an alternate URI for the other participant. + // Use mSIPURI instead, since it will be properly encoded. + iter = mParticipantsByURI.find(mSIPURI); + useAlternateURI = true; + } + } + + if(iter != mParticipantsByURI.end()) + { + result = iter->second; + } + } + + if(!result) + { + // participant isn't already in one list or the other. + result.reset(new participantState(useAlternateURI?mSIPURI:uri)); + mParticipantsByURI.insert(participantMap::value_type(result->mURI, result)); + mParticipantsChanged = true; + + // Try to do a reverse transform on the URI to get the GUID back. + { + LLUUID id; + if(LLVivoxVoiceClient::getInstance()->IDFromName(result->mURI, id)) + { + result->mAvatarIDValid = true; + result->mAvatarID = id; + } + else + { + // Create a UUID by hashing the URI, but do NOT set mAvatarIDValid. + // This indicates that the ID will not be in the name cache. + result->mAvatarID.generate(uri); + } + } + + if(result->updateMuteState()) + { + mMuteDirty = true; + } + + mParticipantsByUUID.insert(participantUUIDMap::value_type(result->mAvatarID, result)); + + if (LLSpeakerVolumeStorage::getInstance()->getSpeakerVolume(result->mAvatarID, result->mVolume)) + { + result->mVolumeDirty = true; + mVolumeDirty = true; + } + + LL_DEBUGS("Voice") << "participant \"" << result->mURI << "\" added." << LL_ENDL; + } + + return result; +} + +bool LLVivoxVoiceClient::participantState::updateMuteState() +{ + bool result = false; + + bool isMuted = LLMuteList::getInstance()->isMuted(mAvatarID, LLMute::flagVoiceChat); + if(mOnMuteList != isMuted) + { + mOnMuteList = isMuted; + mVolumeDirty = true; + result = true; + } + return result; +} + +bool LLVivoxVoiceClient::participantState::isAvatar() +{ + return mAvatarIDValid; +} + +void LLVivoxVoiceClient::sessionState::removeParticipant(const LLVivoxVoiceClient::participantStatePtr_t &participant) +{ + if(participant) + { + participantMap::iterator iter = mParticipantsByURI.find(participant->mURI); + participantUUIDMap::iterator iter2 = mParticipantsByUUID.find(participant->mAvatarID); + + LL_DEBUGS("Voice") << "participant \"" << participant->mURI << "\" (" << participant->mAvatarID << ") removed." << LL_ENDL; + + if(iter == mParticipantsByURI.end()) + { + LL_WARNS("Voice") << "Internal error: participant " << participant->mURI << " not in URI map" << LL_ENDL; + } + else if(iter2 == mParticipantsByUUID.end()) + { + LL_WARNS("Voice") << "Internal error: participant ID " << participant->mAvatarID << " not in UUID map" << LL_ENDL; + } + else if(iter->second != iter2->second) + { + LL_WARNS("Voice") << "Internal error: participant mismatch!" << LL_ENDL; + } + else + { + mParticipantsByURI.erase(iter); + mParticipantsByUUID.erase(iter2); + + mParticipantsChanged = true; + } + } +} + +void LLVivoxVoiceClient::sessionState::removeAllParticipants() +{ + LL_DEBUGS("Voice") << "called" << LL_ENDL; + + while(!mParticipantsByURI.empty()) + { + removeParticipant(mParticipantsByURI.begin()->second); + } + + if(!mParticipantsByUUID.empty()) + { + LL_WARNS("Voice") << "Internal error: empty URI map, non-empty UUID map" << LL_ENDL; + } +} + +/*static*/ +void LLVivoxVoiceClient::sessionState::VerifySessions() +{ + std::set::iterator it = mSession.begin(); + while (it != mSession.end()) + { + if ((*it).expired()) + { + LL_WARNS("Voice") << "Expired session found! removing" << LL_ENDL; + it = mSession.erase(it); + } + else + ++it; + } +} + + +void LLVivoxVoiceClient::getParticipantList(std::set &participants) +{ + if(mAudioSession) + { + for(participantUUIDMap::iterator iter = mAudioSession->mParticipantsByUUID.begin(); + iter != mAudioSession->mParticipantsByUUID.end(); + iter++) + { + participants.insert(iter->first); + } + } +} + +bool LLVivoxVoiceClient::isParticipant(const LLUUID &speaker_id) +{ + if(mAudioSession) + { + return (mAudioSession->mParticipantsByUUID.find(speaker_id) != mAudioSession->mParticipantsByUUID.end()); + } + return false; +} + + +LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::findParticipant(const std::string &uri) +{ + participantStatePtr_t result; + + participantMap::iterator iter = mParticipantsByURI.find(uri); + + if(iter == mParticipantsByURI.end()) + { + if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI)) + { + // This is a p2p session (probably with the SLIM client) with an alternate URI for the other participant. + // Look up the other URI + iter = mParticipantsByURI.find(mSIPURI); + } + } + + if(iter != mParticipantsByURI.end()) + { + result = iter->second; + } + + return result; +} + +LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::findParticipantByID(const LLUUID& id) +{ + participantStatePtr_t result; + participantUUIDMap::iterator iter = mParticipantsByUUID.find(id); + + if(iter != mParticipantsByUUID.end()) + { + result = iter->second; + } + + return result; +} + +LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::findParticipantByID(const LLUUID& id) +{ + participantStatePtr_t result; + + if(mAudioSession) + { + result = mAudioSession->findParticipantByID(id); + } + + return result; +} + + + +// Check for parcel boundary crossing +bool LLVivoxVoiceClient::checkParcelChanged(bool update) +{ + LLViewerRegion *region = gAgent.getRegion(); + LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + + if(region && parcel) + { + S32 parcelLocalID = parcel->getLocalID(); + std::string regionName = region->getName(); + + // LL_DEBUGS("Voice") << "Region name = \"" << regionName << "\", parcel local ID = " << parcelLocalID << ", cap URI = \"" << capURI << "\"" << LL_ENDL; + + // The region name starts out empty and gets filled in later. + // Also, the cap gets filled in a short time after the region cross, but a little too late for our purposes. + // If either is empty, wait for the next time around. + if(!regionName.empty()) + { + if((parcelLocalID != mCurrentParcelLocalID) || (regionName != mCurrentRegionName)) + { + // We have changed parcels. Initiate a parcel channel lookup. + if (update) + { + mCurrentParcelLocalID = parcelLocalID; + mCurrentRegionName = regionName; + } + return true; + } + } + } + return false; +} + +bool LLVivoxVoiceClient::switchChannel( + std::string uri, + bool spatial, + bool no_reconnect, + bool is_p2p, + std::string hash) +{ + bool needsSwitch = !mIsInChannel; + + if (mIsInChannel) + { + if (mSessionTerminateRequested) + { + // If a terminate has been requested, we need to compare against where the URI we're already headed to. + if(mNextAudioSession) + { + if(mNextAudioSession->mSIPURI != uri) + needsSwitch = true; + } + else + { + // mNextAudioSession is null -- this probably means we're on our way back to spatial. + if(!uri.empty()) + { + // We do want to process a switch in this case. + needsSwitch = true; + } + } + } + else + { + // Otherwise, compare against the URI we're in now. + if(mAudioSession) + { + if(mAudioSession->mSIPURI != uri) + { + needsSwitch = true; + } + } + else + { + if(!uri.empty()) + { + // mAudioSession is null -- it's not clear what case would cause this. + // For now, log it as a warning and see if it ever crops up. + LL_WARNS("Voice") << "No current audio session... Forcing switch" << LL_ENDL; + needsSwitch = true; + } + } + } + } + + if(needsSwitch) + { + if(uri.empty()) + { + // Leave any channel we may be in + LL_DEBUGS("Voice") << "leaving channel" << LL_ENDL; + + sessionStatePtr_t oldSession = mNextAudioSession; + mNextAudioSession.reset(); + + // The old session may now need to be deleted. + reapSession(oldSession); + + // If voice was on, turn it off + if (LLVoiceClient::getInstance()->getUserPTTState()) + { + LLVoiceClient::getInstance()->setUserPTTState(false); + } + + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); + } + else + { + LL_DEBUGS("Voice") << "switching to channel " << uri << LL_ENDL; + + mNextAudioSession = addSession(uri); + mNextAudioSession->mHash = hash; + mNextAudioSession->mIsSpatial = spatial; + mNextAudioSession->mReconnect = !no_reconnect; + mNextAudioSession->mIsP2P = is_p2p; + } + + if (mIsInChannel) + { + // If we're already in a channel, or if we're joining one, terminate + // so we can rejoin with the new session data. + sessionTerminate(); + } + } + + return needsSwitch; +} + +void LLVivoxVoiceClient::joinSession(const sessionStatePtr_t &session) +{ + mNextAudioSession = session; + + if (mIsInChannel) + { + // If we're already in a channel, or if we're joining one, terminate + // so we can rejoin with the new session data. + sessionTerminate(); + } +} + +void LLVivoxVoiceClient::setNonSpatialChannel( + const std::string &uri, + const std::string &credentials) +{ + switchChannel(uri, false, false, false, credentials); +} + +bool LLVivoxVoiceClient::setSpatialChannel( + const std::string &uri, + const std::string &credentials) +{ + mSpatialSessionURI = uri; + mSpatialSessionCredentials = credentials; + mAreaVoiceDisabled = mSpatialSessionURI.empty(); + + LL_DEBUGS("Voice") << "got spatial channel uri: \"" << uri << "\"" << LL_ENDL; + + if((mIsInChannel && mAudioSession && !(mAudioSession->mIsSpatial)) || (mNextAudioSession && !(mNextAudioSession->mIsSpatial))) + { + // User is in a non-spatial chat or joining a non-spatial chat. Don't switch channels. + LL_INFOS("Voice") << "in non-spatial chat, not switching channels" << LL_ENDL; + return false; + } + else + { + return switchChannel(mSpatialSessionURI, true, false, false, mSpatialSessionCredentials); + } +} + +void LLVivoxVoiceClient::callUser(const LLUUID &uuid) +{ + std::string userURI = sipURIFromID(uuid); + + switchChannel(userURI, false, true, true); +} + +#if 0 +// Vivox text IMs are not in use. +LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::startUserIMSession(const LLUUID &uuid) +{ + // Figure out if a session with the user already exists + sessionStatePtr_t session(findSession(uuid)); + if(!session) + { + // No session with user, need to start one. + std::string uri = sipURIFromID(uuid); + session = addSession(uri); + + llassert(session); + if (!session) + return session; + + session->mIsSpatial = false; + session->mReconnect = false; + session->mIsP2P = true; + session->mCallerID = uuid; + } + + if(session->mHandle.empty()) + { + // Session isn't active -- start it up. + sessionCreateSendMessage(session, false, false); + } + else + { + // Session is already active -- start up text. + sessionTextConnectSendMessage(session); + } + + return session; +} +#endif + +void LLVivoxVoiceClient::endUserIMSession(const LLUUID &uuid) +{ +#if 0 + // Vivox text IMs are not in use. + + // Figure out if a session with the user exists + sessionStatePtr_t session(findSession(uuid)); + if(session) + { + // found the session + if(!session->mHandle.empty()) + { + // sessionTextDisconnectSendMessage(session); // a SLim leftover, not used any more. + } + } + else + { + LL_DEBUGS("Voice") << "Session not found for participant ID " << uuid << LL_ENDL; + } +#endif +} +bool LLVivoxVoiceClient::isValidChannel(std::string &sessionHandle) +{ + return(findSession(sessionHandle) != NULL); + +} +bool LLVivoxVoiceClient::answerInvite(std::string &sessionHandle) +{ + // this is only ever used to answer incoming p2p call invites. + + sessionStatePtr_t session(findSession(sessionHandle)); + if(session) + { + session->mIsSpatial = false; + session->mReconnect = false; + session->mIsP2P = true; + + joinSession(session); + return true; + } + + return false; +} + +bool LLVivoxVoiceClient::isVoiceWorking() const +{ + + //Added stateSessionTerminated state to avoid problems with call in parcels with disabled voice (EXT-4758) + // Condition with joining spatial num was added to take into account possible problems with connection to voice + // server(EXT-4313). See bug descriptions and comments for MAX_NORMAL_JOINING_SPATIAL_NUM for more info. + return (mSpatialJoiningNum < MAX_NORMAL_JOINING_SPATIAL_NUM) && mIsProcessingChannels; +// return (mSpatialJoiningNum < MAX_NORMAL_JOINING_SPATIAL_NUM) && (stateLoggedIn <= mState) && (mState <= stateSessionTerminated); +} + +// Returns true if the indicated participant in the current audio session is really an SL avatar. +// Currently this will be false only for PSTN callers into group chats, and PSTN p2p calls. +bool LLVivoxVoiceClient::isParticipantAvatar(const LLUUID &id) +{ + bool result = true; + sessionStatePtr_t session(findSession(id)); + + if(session) + { + // this is a p2p session with the indicated caller, or the session with the specified UUID. + if(session->mSynthesizedCallerID) + result = false; + } + else + { + // Didn't find a matching session -- check the current audio session for a matching participant + if(mAudioSession) + { + participantStatePtr_t participant(findParticipantByID(id)); + if(participant) + { + result = participant->isAvatar(); + } + } + } + + return result; +} + +// Returns true if calling back the session URI after the session has closed is possible. +// Currently this will be false only for PSTN P2P calls. +bool LLVivoxVoiceClient::isSessionCallBackPossible(const LLUUID &session_id) +{ + bool result = true; + sessionStatePtr_t session(findSession(session_id)); + + if(session != NULL) + { + result = session->isCallBackPossible(); + } + + return result; +} + +// Returns true if the session can accept text IM's. +// Currently this will be false only for PSTN P2P calls. +bool LLVivoxVoiceClient::isSessionTextIMPossible(const LLUUID &session_id) +{ + bool result = true; + sessionStatePtr_t session(findSession(session_id)); + + if(session != NULL) + { + result = session->isTextIMPossible(); + } + + return result; +} + + +void LLVivoxVoiceClient::declineInvite(std::string &sessionHandle) +{ + sessionStatePtr_t session(findSession(sessionHandle)); + if(session) + { + sessionMediaDisconnectSendMessage(session); + } +} + +void LLVivoxVoiceClient::leaveNonSpatialChannel() +{ + LL_DEBUGS("Voice") << "Request to leave spacial channel." << LL_ENDL; + + // Make sure we don't rejoin the current session. + sessionStatePtr_t oldNextSession(mNextAudioSession); + mNextAudioSession.reset(); + + // Most likely this will still be the current session at this point, but check it anyway. + reapSession(oldNextSession); + + verifySessionState(); + + sessionTerminate(); +} + +std::string LLVivoxVoiceClient::getCurrentChannel() +{ + std::string result; + + if (mIsInChannel && !mSessionTerminateRequested) + { + result = getAudioSessionURI(); + } + + return result; +} + +bool LLVivoxVoiceClient::inProximalChannel() +{ + bool result = false; + + if (mIsInChannel && !mSessionTerminateRequested) + { + result = inSpatialChannel(); + } + + return result; +} + +std::string LLVivoxVoiceClient::sipURIFromID(const LLUUID &id) +{ + std::string result; + result = "sip:"; + result += nameFromID(id); + result += "@"; + result += mVoiceSIPURIHostName; + + return result; +} + +std::string LLVivoxVoiceClient::sipURIFromAvatar(LLVOAvatar *avatar) +{ + std::string result; + if(avatar) + { + result = "sip:"; + result += nameFromID(avatar->getID()); + result += "@"; + result += mVoiceSIPURIHostName; + } + + return result; +} + +std::string LLVivoxVoiceClient::nameFromAvatar(LLVOAvatar *avatar) +{ + std::string result; + if(avatar) + { + result = nameFromID(avatar->getID()); + } + return result; +} + +std::string LLVivoxVoiceClient::nameFromID(const LLUUID &uuid) +{ + std::string result; + + if (uuid.isNull()) { + //VIVOX, the uuid emtpy look for the mURIString and return that instead. + //result.assign(uuid.mURIStringName); + LLStringUtil::replaceChar(result, '_', ' '); + return result; + } + // Prepending this apparently prevents conflicts with reserved names inside the vivox code. + result = "x"; + + // Base64 encode and replace the pieces of base64 that are less compatible + // with e-mail local-parts. + // See RFC-4648 "Base 64 Encoding with URL and Filename Safe Alphabet" + result += LLBase64::encode(uuid.mData, UUID_BYTES); + LLStringUtil::replaceChar(result, '+', '-'); + LLStringUtil::replaceChar(result, '/', '_'); + + // If you need to transform a GUID to this form on the macOS command line, this will do so: + // echo -n x && (echo e669132a-6c43-4ee1-a78d-6c82fff59f32 |xxd -r -p |openssl base64|tr '/+' '_-') + + // The reverse transform can be done with: + // echo 'x5mkTKmxDTuGnjWyC__WfMg==' |cut -b 2- -|tr '_-' '/+' |openssl base64 -d|xxd -p + + return result; +} + +bool LLVivoxVoiceClient::IDFromName(const std::string inName, LLUUID &uuid) +{ + bool result = false; + + // SLIM SDK: The "name" may actually be a SIP URI such as: "sip:xFnPP04IpREWNkuw1cOXlhw==@bhr.vivox.com" + // If it is, convert to a bare name before doing the transform. + std::string name = nameFromsipURI(inName); + + // Doesn't look like a SIP URI, assume it's an actual name. + if(name.empty()) + name = inName; + + // This will only work if the name is of the proper form. + // As an example, the account name for Monroe Linden (UUID 1673cfd3-8229-4445-8d92-ec3570e5e587) is: + // "xFnPP04IpREWNkuw1cOXlhw==" + + if((name.size() == 25) && (name[0] == 'x') && (name[23] == '=') && (name[24] == '=')) + { + // The name appears to have the right form. + + // Reverse the transforms done by nameFromID + std::string temp = name; + LLStringUtil::replaceChar(temp, '-', '+'); + LLStringUtil::replaceChar(temp, '_', '/'); + + U8 rawuuid[UUID_BYTES + 1]; + int len = apr_base64_decode_binary(rawuuid, temp.c_str() + 1); + if(len == UUID_BYTES) + { + // The decode succeeded. Stuff the bits into the result's UUID + memcpy(uuid.mData, rawuuid, UUID_BYTES); + result = true; + } + } + + if(!result) + { + // VIVOX: not a standard account name, just copy the URI name mURIString field + // and hope for the best. bpj + uuid.setNull(); // VIVOX, set the uuid field to nulls + } + + return result; +} + +std::string LLVivoxVoiceClient::displayNameFromAvatar(LLVOAvatar *avatar) +{ + return avatar->getFullname(); +} + +std::string LLVivoxVoiceClient::sipURIFromName(std::string &name) +{ + std::string result; + result = "sip:"; + result += name; + result += "@"; + result += mVoiceSIPURIHostName; + +// LLStringUtil::toLower(result); + + return result; +} + +std::string LLVivoxVoiceClient::nameFromsipURI(const std::string &uri) +{ + std::string result; + + std::string::size_type sipOffset, atOffset; + sipOffset = uri.find("sip:"); + atOffset = uri.find("@"); + if((sipOffset != std::string::npos) && (atOffset != std::string::npos)) + { + result = uri.substr(sipOffset + 4, atOffset - (sipOffset + 4)); + } + + return result; +} + +bool LLVivoxVoiceClient::inSpatialChannel(void) +{ + bool result = false; + + if(mAudioSession) + { + result = mAudioSession->mIsSpatial; + } + + return result; +} + +std::string LLVivoxVoiceClient::getAudioSessionURI() +{ + std::string result; + + if(mAudioSession) + result = mAudioSession->mSIPURI; + + return result; +} + +std::string LLVivoxVoiceClient::getAudioSessionHandle() +{ + std::string result; + + if(mAudioSession) + result = mAudioSession->mHandle; + + return result; +} + + +///////////////////////////// +// Sending updates of current state + +void LLVivoxVoiceClient::enforceTether(void) +{ + LLVector3d tethered = mCameraRequestedPosition; + + // constrain 'tethered' to within 50m of mAvatarPosition. + { + F32 max_dist = 50.0f; + LLVector3d camera_offset = mCameraRequestedPosition - mAvatarPosition; + F32 camera_distance = (F32)camera_offset.magVec(); + if(camera_distance > max_dist) + { + tethered = mAvatarPosition + + (max_dist / camera_distance) * camera_offset; + } + } + + if(dist_vec_squared(mCameraPosition, tethered) > 0.01) + { + mCameraPosition = tethered; + mSpatialCoordsDirty = true; + } +} + +void LLVivoxVoiceClient::updatePosition(void) +{ + + LLViewerRegion *region = gAgent.getRegion(); + if(region && isAgentAvatarValid()) + { + LLMatrix3 rot; + LLVector3d pos; + LLQuaternion qrot; + + // TODO: If camera and avatar velocity are actually used by the voice system, we could compute them here... + // They're currently always set to zero. + + // Send the current camera position to the voice code + rot.setRows(LLViewerCamera::getInstance()->getAtAxis(), LLViewerCamera::getInstance()->getLeftAxis (), LLViewerCamera::getInstance()->getUpAxis()); + pos = gAgent.getRegion()->getPosGlobalFromRegion(LLViewerCamera::getInstance()->getOrigin()); + + LLVivoxVoiceClient::getInstance()->setCameraPosition( + pos, // position + LLVector3::zero, // velocity + rot); // rotation matrix + + // Send the current avatar position to the voice code + qrot = gAgentAvatarp->getRootJoint()->getWorldRotation(); + pos = gAgentAvatarp->getPositionGlobal(); + + // TODO: Can we get the head offset from outside the LLVOAvatar? + // pos += LLVector3d(mHeadOffset); + pos += LLVector3d(0.f, 0.f, 1.f); + + LLVivoxVoiceClient::getInstance()->setAvatarPosition( + pos, // position + LLVector3::zero, // velocity + qrot); // rotation matrix + } +} + +void LLVivoxVoiceClient::setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot) +{ + mCameraRequestedPosition = position; + + if(mCameraVelocity != velocity) + { + mCameraVelocity = velocity; + mSpatialCoordsDirty = true; + } + + if(mCameraRot != rot) + { + mCameraRot = rot; + mSpatialCoordsDirty = true; + } +} + +void LLVivoxVoiceClient::setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLQuaternion &rot) +{ + if(dist_vec_squared(mAvatarPosition, position) > 0.01) + { + mAvatarPosition = position; + mSpatialCoordsDirty = true; + } + + if(mAvatarVelocity != velocity) + { + mAvatarVelocity = velocity; + mSpatialCoordsDirty = true; + } + + // If the two rotations are not exactly equal test their dot product + // to get the cos of the angle between them. + // If it is too small, don't update. + F32 rot_cos_diff = llabs(dot(mAvatarRot, rot)); + if ((mAvatarRot != rot) && (rot_cos_diff < MINUSCULE_ANGLE_COS)) + { + mAvatarRot = rot; + mSpatialCoordsDirty = true; + } +} + +bool LLVivoxVoiceClient::channelFromRegion(LLViewerRegion *region, std::string &name) +{ + bool result = false; + + if(region) + { + name = region->getName(); + } + + if(!name.empty()) + result = true; + + return result; +} + +void LLVivoxVoiceClient::leaveChannel(void) +{ + if (mIsInChannel) + { + LL_DEBUGS("Voice") << "leaving channel for teleport/logout" << LL_ENDL; + mChannelName.clear(); + sessionTerminate(); + } +} + +void LLVivoxVoiceClient::setMuteMic(bool muted) +{ + if(mMuteMic != muted) + { + mMuteMic = muted; + mMuteMicDirty = true; + } +} + +void LLVivoxVoiceClient::setVoiceEnabled(bool enabled) +{ + LL_DEBUGS("Voice") + << "( " << (enabled ? "enabled" : "disabled") << " )" + << " was "<< (mVoiceEnabled ? "enabled" : "disabled") + << " coro "<< (mIsCoroutineActive ? "active" : "inactive") + << LL_ENDL; + + if (enabled != mVoiceEnabled) + { + // TODO: Refactor this so we don't call into LLVoiceChannel, but simply + // use the status observer + mVoiceEnabled = enabled; + LLVoiceClientStatusObserver::EStatusType status; + + if (enabled) + { + LL_DEBUGS("Voice") << "enabling" << LL_ENDL; + LLVoiceChannel::getCurrentVoiceChannel()->activate(); + status = LLVoiceClientStatusObserver::STATUS_VOICE_ENABLED; + + if (!mIsCoroutineActive) + { + LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro", + boost::bind(&LLVivoxVoiceClient::voiceControlCoro, LLVivoxVoiceClient::getInstance())); + } + else + { + LL_DEBUGS("Voice") << "coro should be active.. not launching" << LL_ENDL; + } + } + else + { + // Turning voice off looses your current channel -- this makes sure the UI isn't out of sync when you re-enable it. + LLVoiceChannel::getCurrentVoiceChannel()->deactivate(); + gAgent.setVoiceConnected(false); + status = LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED; + } + + notifyStatusObservers(status); + } + else + { + LL_DEBUGS("Voice") << " no-op" << LL_ENDL; + } +} + +bool LLVivoxVoiceClient::voiceEnabled() +{ + return gSavedSettings.getBOOL("EnableVoiceChat") && + !gSavedSettings.getBOOL("CmdLineDisableVoice") && + !gNonInteractive; +} + +void LLVivoxVoiceClient::setLipSyncEnabled(bool enabled) +{ + mLipSyncEnabled = enabled; +} + +bool LLVivoxVoiceClient::lipSyncEnabled() +{ + + if ( mVoiceEnabled ) + { + return mLipSyncEnabled; + } + else + { + return false; + } +} + + +void LLVivoxVoiceClient::setEarLocation(S32 loc) +{ + if(mEarLocation != loc) + { + LL_DEBUGS("Voice") << "Setting mEarLocation to " << loc << LL_ENDL; + + mEarLocation = loc; + mSpatialCoordsDirty = true; + } +} + +void LLVivoxVoiceClient::setVoiceVolume(F32 volume) +{ + int scaled_volume = scale_speaker_volume(volume); + + if(scaled_volume != mSpeakerVolume) + { + int min_volume = scale_speaker_volume(0); + if((scaled_volume == min_volume) || (mSpeakerVolume == min_volume)) + { + mSpeakerMuteDirty = true; + } + + mSpeakerVolume = scaled_volume; + mSpeakerVolumeDirty = true; + } +} + +void LLVivoxVoiceClient::setMicGain(F32 volume) +{ + int scaled_volume = scale_mic_volume(volume); + + if(scaled_volume != mMicVolume) + { + mMicVolume = scaled_volume; + mMicVolumeDirty = true; + } +} + +///////////////////////////// +// Accessors for data related to nearby speakers +bool LLVivoxVoiceClient::getVoiceEnabled(const LLUUID& id) +{ + bool result = false; + participantStatePtr_t participant(findParticipantByID(id)); + if(participant) + { + // I'm not sure what the semantics of this should be. + // For now, if we have any data about the user that came through the chat channel, assume they're voice-enabled. + result = true; + } + + return result; +} + +std::string LLVivoxVoiceClient::getDisplayName(const LLUUID& id) +{ + std::string result; + participantStatePtr_t participant(findParticipantByID(id)); + if(participant) + { + result = participant->mDisplayName; + } + + return result; +} + + + +bool LLVivoxVoiceClient::getIsSpeaking(const LLUUID& id) +{ + bool result = false; + + participantStatePtr_t participant(findParticipantByID(id)); + if(participant) + { + if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT) + { + participant->mIsSpeaking = false; + } + result = participant->mIsSpeaking; + } + + return result; +} + +bool LLVivoxVoiceClient::getIsModeratorMuted(const LLUUID& id) +{ + bool result = false; + + participantStatePtr_t participant(findParticipantByID(id)); + if(participant) + { + result = participant->mIsModeratorMuted; + } + + return result; +} + +F32 LLVivoxVoiceClient::getCurrentPower(const LLUUID& id) +{ + F32 result = 0; + participantStatePtr_t participant(findParticipantByID(id)); + if(participant) + { + result = participant->mPower; + } + + return result; +} + + + +bool LLVivoxVoiceClient::getUsingPTT(const LLUUID& id) +{ + bool result = false; + + participantStatePtr_t participant(findParticipantByID(id)); + if(participant) + { + // I'm not sure what the semantics of this should be. + // Does "using PTT" mean they're configured with a push-to-talk button? + // For now, we know there's no PTT mechanism in place, so nobody is using it. + } + + return result; +} + +bool LLVivoxVoiceClient::getOnMuteList(const LLUUID& id) +{ + bool result = false; + + participantStatePtr_t participant(findParticipantByID(id)); + if(participant) + { + result = participant->mOnMuteList; + } + + return result; +} + +// External accessors. +F32 LLVivoxVoiceClient::getUserVolume(const LLUUID& id) +{ + // Minimum volume will be returned for users with voice disabled + F32 result = LLVoiceClient::VOLUME_MIN; + + participantStatePtr_t participant(findParticipantByID(id)); + if(participant) + { + result = participant->mVolume; + + // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging. + // LL_DEBUGS("Voice") << "mVolume = " << result << " for " << id << LL_ENDL; + } + + return result; +} + +void LLVivoxVoiceClient::setUserVolume(const LLUUID& id, F32 volume) +{ + if(mAudioSession) + { + participantStatePtr_t participant(findParticipantByID(id)); + if (participant && !participant->mIsSelf) + { + if (!is_approx_equal(volume, LLVoiceClient::VOLUME_DEFAULT)) + { + // Store this volume setting for future sessions if it has been + // changed from the default + LLSpeakerVolumeStorage::getInstance()->storeSpeakerVolume(id, volume); + } + else + { + // Remove stored volume setting if it is returned to the default + LLSpeakerVolumeStorage::getInstance()->removeSpeakerVolume(id); + } + + participant->mVolume = llclamp(volume, LLVoiceClient::VOLUME_MIN, LLVoiceClient::VOLUME_MAX); + participant->mVolumeDirty = true; + mAudioSession->mVolumeDirty = true; + } + } +} + +std::string LLVivoxVoiceClient::getGroupID(const LLUUID& id) +{ + std::string result; + + participantStatePtr_t participant(findParticipantByID(id)); + if(participant) + { + result = participant->mGroupID; + } + + return result; +} + +bool LLVivoxVoiceClient::getAreaVoiceDisabled() +{ + return mAreaVoiceDisabled; +} + +void LLVivoxVoiceClient::recordingLoopStart(int seconds, int deltaFramesPerControlFrame) +{ +// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Start)" << LL_ENDL; + + if(!mMainSessionGroupHandle.empty()) + { + std::ostringstream stream; + stream + << "" + << "" << mMainSessionGroupHandle << "" + << "Start" + << "" << deltaFramesPerControlFrame << "" + << "" << "" << "" + << "false" + << "" << seconds << "" + << "\n\n\n"; + + + writeString(stream.str()); + } +} + +void LLVivoxVoiceClient::recordingLoopSave(const std::string& filename) +{ +// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Flush)" << LL_ENDL; + + if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) + { + std::ostringstream stream; + stream + << "" + << "" << mMainSessionGroupHandle << "" + << "Flush" + << "" << filename << "" + << "\n\n\n"; + + writeString(stream.str()); + } +} + +void LLVivoxVoiceClient::recordingStop() +{ +// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Stop)" << LL_ENDL; + + if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) + { + std::ostringstream stream; + stream + << "" + << "" << mMainSessionGroupHandle << "" + << "Stop" + << "\n\n\n"; + + writeString(stream.str()); + } +} + +void LLVivoxVoiceClient::filePlaybackStart(const std::string& filename) +{ +// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Start)" << LL_ENDL; + + if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) + { + std::ostringstream stream; + stream + << "" + << "" << mMainSessionGroupHandle << "" + << "Start" + << "" << filename << "" + << "\n\n\n"; + + writeString(stream.str()); + } +} + +void LLVivoxVoiceClient::filePlaybackStop() +{ +// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Stop)" << LL_ENDL; + + if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) + { + std::ostringstream stream; + stream + << "" + << "" << mMainSessionGroupHandle << "" + << "Stop" + << "\n\n\n"; + + writeString(stream.str()); + } +} + +void LLVivoxVoiceClient::filePlaybackSetPaused(bool paused) +{ + // TODO: Implement once Vivox gives me a sample +} + +void LLVivoxVoiceClient::filePlaybackSetMode(bool vox, float speed) +{ + // TODO: Implement once Vivox gives me a sample +} + +//------------------------------------------------------------------------ +std::set> LLVivoxVoiceClient::sessionState::mSession; + + +LLVivoxVoiceClient::sessionState::sessionState() : + mErrorStatusCode(0), + mMediaStreamState(streamStateUnknown), + mCreateInProgress(false), + mMediaConnectInProgress(false), + mVoiceInvitePending(false), + mTextInvitePending(false), + mSynthesizedCallerID(false), + mIsChannel(false), + mIsSpatial(false), + mIsP2P(false), + mIncoming(false), + mVoiceActive(false), + mReconnect(false), + mVolumeDirty(false), + mMuteDirty(false), + mParticipantsChanged(false) +{ +} + +/*static*/ +LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::createSession() +{ + sessionState::ptr_t ptr(new sessionState()); + + std::pair::iterator, bool> result = mSession.insert(ptr); + + if (result.second) + ptr->mMyIterator = result.first; + + return ptr; +} + +LLVivoxVoiceClient::sessionState::~sessionState() +{ + LL_INFOS("Voice") << "Destroying session handle=" << mHandle << " SIP=" << mSIPURI << LL_ENDL; + if (mMyIterator != mSession.end()) + mSession.erase(mMyIterator); + + removeAllParticipants(); +} + +bool LLVivoxVoiceClient::sessionState::isCallBackPossible() +{ + // This may change to be explicitly specified by vivox in the future... + // Currently, only PSTN P2P calls cannot be returned. + // Conveniently, this is also the only case where we synthesize a caller UUID. + return !mSynthesizedCallerID; +} + +bool LLVivoxVoiceClient::sessionState::isTextIMPossible() +{ + // This may change to be explicitly specified by vivox in the future... + return !mSynthesizedCallerID; +} + + +/*static*/ +LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchSessionByHandle(const std::string &handle) +{ + sessionStatePtr_t result; + + // *TODO: My kingdom for a lambda! + std::set::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testByHandle, _1, handle)); + + if (it != mSession.end()) + result = (*it).lock(); + + return result; +} + +/*static*/ +LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchCreatingSessionByURI(const std::string &uri) +{ + sessionStatePtr_t result; + + // *TODO: My kingdom for a lambda! + std::set::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testByCreatingURI, _1, uri)); + + if (it != mSession.end()) + result = (*it).lock(); + + return result; +} + +/*static*/ +LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchSessionByURI(const std::string &uri) +{ + sessionStatePtr_t result; + + // *TODO: My kingdom for a lambda! + std::set::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testBySIPOrAlterateURI, _1, uri)); + + if (it != mSession.end()) + result = (*it).lock(); + + return result; +} + +/*static*/ +LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchSessionByParticipant(const LLUUID &participant_id) +{ + sessionStatePtr_t result; + + // *TODO: My kingdom for a lambda! + std::set::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testByCallerId, _1, participant_id)); + + if (it != mSession.end()) + result = (*it).lock(); + + return result; +} + +void LLVivoxVoiceClient::sessionState::for_each(sessionFunc_t func) +{ + std::for_each(mSession.begin(), mSession.end(), boost::bind(for_eachPredicate, _1, func)); +} + +// simple test predicates. +// *TODO: These should be made into lambdas when we can pull the trigger on newer C++ features. +bool LLVivoxVoiceClient::sessionState::testByHandle(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string handle) +{ + ptr_t aLock(a.lock()); + + return aLock ? aLock->mHandle == handle : false; +} + +bool LLVivoxVoiceClient::sessionState::testByCreatingURI(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string uri) +{ + ptr_t aLock(a.lock()); + + return aLock ? (aLock->mCreateInProgress && (aLock->mSIPURI == uri)) : false; +} + +bool LLVivoxVoiceClient::sessionState::testBySIPOrAlterateURI(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string uri) +{ + ptr_t aLock(a.lock()); + + return aLock ? ((aLock->mSIPURI == uri) || (aLock->mAlternateSIPURI == uri)) : false; +} + + +bool LLVivoxVoiceClient::sessionState::testByCallerId(const LLVivoxVoiceClient::sessionState::wptr_t &a, LLUUID participantId) +{ + ptr_t aLock(a.lock()); + + return aLock ? ((aLock->mCallerID == participantId) || (aLock->mIMSessionID == participantId)) : false; +} + +/*static*/ +void LLVivoxVoiceClient::sessionState::for_eachPredicate(const LLVivoxVoiceClient::sessionState::wptr_t &a, sessionFunc_t func) +{ + ptr_t aLock(a.lock()); + + if (aLock) + func(aLock); + else + { + LL_WARNS("Voice") << "Stale handle in session map!" << LL_ENDL; + } +} + + + +LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::findSession(const std::string &handle) +{ + sessionStatePtr_t result; + sessionMap::iterator iter = mSessionsByHandle.find(handle); + if(iter != mSessionsByHandle.end()) + { + result = iter->second; + } + + return result; +} + +LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::findSessionBeingCreatedByURI(const std::string &uri) +{ + sessionStatePtr_t result = sessionState::matchCreatingSessionByURI(uri); + + return result; +} + +LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::findSession(const LLUUID &participant_id) +{ + sessionStatePtr_t result = sessionState::matchSessionByParticipant(participant_id); + + return result; +} + +LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::addSession(const std::string &uri, const std::string &handle) +{ + sessionStatePtr_t result; + + if(handle.empty()) + { + // No handle supplied. + // Check whether there's already a session with this URI + result = sessionState::matchSessionByURI(uri); + } + else // (!handle.empty()) + { + // Check for an existing session with this handle + sessionMap::iterator iter = mSessionsByHandle.find(handle); + + if(iter != mSessionsByHandle.end()) + { + result = iter->second; + } + } + + if(!result) + { + // No existing session found. + + LL_DEBUGS("Voice") << "adding new session: handle \"" << handle << "\" URI " << uri << LL_ENDL; + result = sessionState::createSession(); + result->mSIPURI = uri; + result->mHandle = handle; + + if (LLVoiceClient::instance().getVoiceEffectEnabled()) + { + result->mVoiceFontID = LLVoiceClient::instance().getVoiceEffectDefault(); + } + + if(!result->mHandle.empty()) + { + // *TODO: Rider: This concerns me. There is a path (via switchChannel) where + // we do not track the session. In theory this means that we could end up with + // a mAuidoSession that does not match the session tracked in mSessionsByHandle + mSessionsByHandle.insert(sessionMap::value_type(result->mHandle, result)); + } + } + else + { + // Found an existing session + + if(uri != result->mSIPURI) + { + // TODO: Should this be an internal error? + LL_DEBUGS("Voice") << "changing uri from " << result->mSIPURI << " to " << uri << LL_ENDL; + setSessionURI(result, uri); + } + + if(handle != result->mHandle) + { + if(handle.empty()) + { + // There's at least one race condition where where addSession was clearing an existing session handle, which caused things to break. + LL_DEBUGS("Voice") << "NOT clearing handle " << result->mHandle << LL_ENDL; + } + else + { + // TODO: Should this be an internal error? + LL_DEBUGS("Voice") << "changing handle from " << result->mHandle << " to " << handle << LL_ENDL; + setSessionHandle(result, handle); + } + } + + LL_DEBUGS("Voice") << "returning existing session: handle " << handle << " URI " << uri << LL_ENDL; + } + + verifySessionState(); + + return result; +} + +void LLVivoxVoiceClient::clearSessionHandle(const sessionStatePtr_t &session) +{ + if (session) + { + if (!session->mHandle.empty()) + { + sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle); + if (iter != mSessionsByHandle.end()) + { + mSessionsByHandle.erase(iter); + } + } + else + { + LL_WARNS("Voice") << "Session has empty handle!" << LL_ENDL; + } + } + else + { + LL_WARNS("Voice") << "Attempt to clear NULL session!" << LL_ENDL; + } + +} + +void LLVivoxVoiceClient::setSessionHandle(const sessionStatePtr_t &session, const std::string &handle) +{ + // Have to remove the session from the handle-indexed map before changing the handle, or things will break badly. + + if(!session->mHandle.empty()) + { + // Remove session from the map if it should have been there. + sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle); + if(iter != mSessionsByHandle.end()) + { + if(iter->second != session) + { + LL_WARNS("Voice") << "Internal error: session mismatch! Session may have been duplicated. Removing version in map." << LL_ENDL; + } + + mSessionsByHandle.erase(iter); + } + else + { + LL_WARNS("Voice") << "Attempt to remove session with handle " << session->mHandle << " not found in map!" << LL_ENDL; + } + } + + session->mHandle = handle; + + if(!handle.empty()) + { + mSessionsByHandle.insert(sessionMap::value_type(session->mHandle, session)); + } + + verifySessionState(); +} + +void LLVivoxVoiceClient::setSessionURI(const sessionStatePtr_t &session, const std::string &uri) +{ + // There used to be a map of session URIs to sessions, which made this complex.... + session->mSIPURI = uri; + + verifySessionState(); +} + +void LLVivoxVoiceClient::deleteSession(const sessionStatePtr_t &session) +{ + // Remove the session from the handle map + if(!session->mHandle.empty()) + { + sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle); + if(iter != mSessionsByHandle.end()) + { + if(iter->second != session) + { + LL_WARNS("Voice") << "Internal error: session mismatch, removing session in map." << LL_ENDL; + } + mSessionsByHandle.erase(iter); + } + } + + // At this point, the session should be unhooked from all lists and all state should be consistent. + verifySessionState(); + + // If this is the current audio session, clean up the pointer which will soon be dangling. + if(mAudioSession == session) + { + mAudioSession.reset(); + mAudioSessionChanged = true; + } + + // ditto for the next audio session + if(mNextAudioSession == session) + { + mNextAudioSession.reset(); + } + +} + +void LLVivoxVoiceClient::deleteAllSessions() +{ + LL_DEBUGS("Voice") << LL_ENDL; + + while (!mSessionsByHandle.empty()) + { + const sessionStatePtr_t session = mSessionsByHandle.begin()->second; + deleteSession(session); + } + +} + +void LLVivoxVoiceClient::verifySessionState(void) +{ + LL_DEBUGS("Voice") << "Sessions in handle map=" << mSessionsByHandle.size() << LL_ENDL; + sessionState::VerifySessions(); +} + +void LLVivoxVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer) +{ + mParticipantObservers.insert(observer); +} + +void LLVivoxVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer) +{ + mParticipantObservers.erase(observer); +} + +void LLVivoxVoiceClient::notifyParticipantObservers() +{ + for (observer_set_t::iterator it = mParticipantObservers.begin(); + it != mParticipantObservers.end(); + ) + { + LLVoiceClientParticipantObserver* observer = *it; + observer->onParticipantsChanged(); + // In case onParticipantsChanged() deleted an entry. + it = mParticipantObservers.upper_bound(observer); + } +} + +void LLVivoxVoiceClient::addObserver(LLVoiceClientStatusObserver* observer) +{ + mStatusObservers.insert(observer); +} + +void LLVivoxVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer) +{ + mStatusObservers.erase(observer); +} + +void LLVivoxVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status) +{ + LL_DEBUGS("Voice") << "( " << LLVoiceClientStatusObserver::status2string(status) << " )" + << " mAudioSession=" << mAudioSession + << LL_ENDL; + + if(mAudioSession) + { + if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN) + { + switch(mAudioSession->mErrorStatusCode) + { + case 20713: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_FULL; break; + case 20714: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_LOCKED; break; + case 20715: + //invalid channel, we may be using a set of poorly cached + //info + status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; + break; + case 1009: + //invalid username and password + status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; + break; + } + + // Reset the error code to make sure it won't be reused later by accident. + mAudioSession->mErrorStatusCode = 0; + } + else if(status == LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL) + { + switch(mAudioSession->mErrorStatusCode) + { + case HTTP_NOT_FOUND: // NOT_FOUND + // *TODO: Should this be 503? + case 480: // TEMPORARILY_UNAVAILABLE + case HTTP_REQUEST_TIME_OUT: // REQUEST_TIMEOUT + // call failed because other user was not available + // treat this as an error case + status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; + + // Reset the error code to make sure it won't be reused later by accident. + mAudioSession->mErrorStatusCode = 0; + break; + } + } + } + + LL_DEBUGS("Voice") + << " " << LLVoiceClientStatusObserver::status2string(status) + << ", session URI " << getAudioSessionURI() + << ", proximal is " << inSpatialChannel() + << LL_ENDL; + + for (status_observer_set_t::iterator it = mStatusObservers.begin(); + it != mStatusObservers.end(); + ) + { + LLVoiceClientStatusObserver* observer = *it; + observer->onChange(status, getAudioSessionURI(), inSpatialChannel()); + // In case onError() deleted an entry. + it = mStatusObservers.upper_bound(observer); + } + + // skipped to avoid speak button blinking + if ( status != LLVoiceClientStatusObserver::STATUS_JOINING + && status != LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL + && status != LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED) + { + bool voice_status = LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking(); + + gAgent.setVoiceConnected(voice_status); + + if (voice_status) + { + LLFirstUse::speak(true); + } + } +} + +void LLVivoxVoiceClient::addObserver(LLFriendObserver* observer) +{ + mFriendObservers.insert(observer); +} + +void LLVivoxVoiceClient::removeObserver(LLFriendObserver* observer) +{ + mFriendObservers.erase(observer); +} + +void LLVivoxVoiceClient::notifyFriendObservers() +{ + for (friend_observer_set_t::iterator it = mFriendObservers.begin(); + it != mFriendObservers.end(); + ) + { + LLFriendObserver* observer = *it; + it++; + // The only friend-related thing we notify on is online/offline transitions. + observer->changed(LLFriendObserver::ONLINE); + } +} + +void LLVivoxVoiceClient::lookupName(const LLUUID &id) +{ + if (mAvatarNameCacheConnection.connected()) + { + mAvatarNameCacheConnection.disconnect(); + } + mAvatarNameCacheConnection = LLAvatarNameCache::get(id, boost::bind(&LLVivoxVoiceClient::onAvatarNameCache, this, _1, _2)); +} + +void LLVivoxVoiceClient::onAvatarNameCache(const LLUUID& agent_id, + const LLAvatarName& av_name) +{ + mAvatarNameCacheConnection.disconnect(); + std::string display_name = av_name.getDisplayName(); + avatarNameResolved(agent_id, display_name); +} + +void LLVivoxVoiceClient::predAvatarNameResolution(const LLVivoxVoiceClient::sessionStatePtr_t &session, LLUUID id, std::string name) +{ + participantStatePtr_t participant(session->findParticipantByID(id)); + if (participant) + { + // Found -- fill in the name + participant->mAccountName = name; + // and post a "participants updated" message to listeners later. + session->mParticipantsChanged = true; + } + + // Check whether this is a p2p session whose caller name just resolved + if (session->mCallerID == id) + { + // this session's "caller ID" just resolved. Fill in the name. + session->mName = name; + if (session->mTextInvitePending) + { + session->mTextInvitePending = false; + + // We don't need to call LLIMMgr::getInstance()->addP2PSession() here. The first incoming message will create the panel. + } + if (session->mVoiceInvitePending) + { + session->mVoiceInvitePending = false; + + LLIMMgr::getInstance()->inviteToSession( + session->mIMSessionID, + session->mName, + session->mCallerID, + session->mName, + IM_SESSION_P2P_INVITE, + LLIMMgr::INVITATION_TYPE_VOICE, + session->mHandle, + session->mSIPURI); + } + + } +} + +void LLVivoxVoiceClient::avatarNameResolved(const LLUUID &id, const std::string &name) +{ + sessionState::for_each(boost::bind(predAvatarNameResolution, _1, id, name)); +} + +bool LLVivoxVoiceClient::setVoiceEffect(const LLUUID& id) +{ + if (!mAudioSession) + { + return false; + } + + if (!id.isNull()) + { + if (mVoiceFontMap.empty()) + { + LL_DEBUGS("Voice") << "Voice fonts not available." << LL_ENDL; + return false; + } + else if (mVoiceFontMap.find(id) == mVoiceFontMap.end()) + { + LL_DEBUGS("Voice") << "Invalid voice font " << id << LL_ENDL; + return false; + } + } + + // *TODO: Check for expired fonts? + mAudioSession->mVoiceFontID = id; + + // *TODO: Separate voice font defaults for spatial chat and IM? + gSavedPerAccountSettings.setString("VoiceEffectDefault", id.asString()); + + sessionSetVoiceFontSendMessage(mAudioSession); + notifyVoiceFontObservers(); + + return true; +} + +const LLUUID LLVivoxVoiceClient::getVoiceEffect() +{ + return mAudioSession ? mAudioSession->mVoiceFontID : LLUUID::null; +} + +LLSD LLVivoxVoiceClient::getVoiceEffectProperties(const LLUUID& id) +{ + LLSD sd; + + voice_font_map_t::iterator iter = mVoiceFontMap.find(id); + if (iter != mVoiceFontMap.end()) + { + sd["template_only"] = false; + } + else + { + // Voice effect is not in the voice font map, see if there is a template + iter = mVoiceFontTemplateMap.find(id); + if (iter == mVoiceFontTemplateMap.end()) + { + LL_WARNS("Voice") << "Voice effect " << id << "not found." << LL_ENDL; + return sd; + } + sd["template_only"] = true; + } + + voiceFontEntry *font = iter->second; + sd["name"] = font->mName; + sd["expiry_date"] = font->mExpirationDate; + sd["is_new"] = font->mIsNew; + + return sd; +} + +LLVivoxVoiceClient::voiceFontEntry::voiceFontEntry(LLUUID& id) : + mID(id), + mFontIndex(0), + mFontType(VOICE_FONT_TYPE_NONE), + mFontStatus(VOICE_FONT_STATUS_NONE), + mIsNew(false) +{ + mExpiryTimer.stop(); + mExpiryWarningTimer.stop(); +} + +LLVivoxVoiceClient::voiceFontEntry::~voiceFontEntry() +{ +} + +void LLVivoxVoiceClient::refreshVoiceEffectLists(bool clear_lists) +{ + if (clear_lists) + { + mVoiceFontsReceived = false; + deleteAllVoiceFonts(); + deleteVoiceFontTemplates(); + } + + accountGetSessionFontsSendMessage(); + accountGetTemplateFontsSendMessage(); +} + +const voice_effect_list_t& LLVivoxVoiceClient::getVoiceEffectList() const +{ + return mVoiceFontList; +} + +const voice_effect_list_t& LLVivoxVoiceClient::getVoiceEffectTemplateList() const +{ + return mVoiceFontTemplateList; +} + +void LLVivoxVoiceClient::addVoiceFont(const S32 font_index, + const std::string &name, + const std::string &description, + const LLDate &expiration_date, + bool has_expired, + const S32 font_type, + const S32 font_status, + const bool template_font) +{ + // Vivox SessionFontIDs are not guaranteed to remain the same between + // sessions or grids so use a UUID for the name. + + // If received name is not a UUID, fudge one by hashing the name and type. + LLUUID font_id; + if (LLUUID::validate(name)) + { + font_id = LLUUID(name); + } + else + { + font_id.generate(STRINGIZE(font_type << ":" << name)); + } + + voiceFontEntry *font = NULL; + + voice_font_map_t& font_map = template_font ? mVoiceFontTemplateMap : mVoiceFontMap; + voice_effect_list_t& font_list = template_font ? mVoiceFontTemplateList : mVoiceFontList; + + // Check whether we've seen this font before. + voice_font_map_t::iterator iter = font_map.find(font_id); + bool new_font = (iter == font_map.end()); + + // Override the has_expired flag if we have passed the expiration_date as a double check. + if (expiration_date.secondsSinceEpoch() < (LLDate::now().secondsSinceEpoch() + VOICE_FONT_EXPIRY_INTERVAL)) + { + has_expired = true; + } + + if (has_expired) + { + LL_DEBUGS("VoiceFont") << "Expired " << (template_font ? "Template " : "") + << expiration_date.asString() << " " << font_id + << " (" << font_index << ") " << name << LL_ENDL; + + // Remove existing session fonts that have expired since we last saw them. + if (!new_font && !template_font) + { + deleteVoiceFont(font_id); + } + return; + } + + if (new_font) + { + // If it is a new font create a new entry. + font = new voiceFontEntry(font_id); + } + else + { + // Not a new font, update the existing entry + font = iter->second; + } + + if (font) + { + font->mFontIndex = font_index; + // Use the description for the human readable name if available, as the + // "name" may be a UUID. + font->mName = description.empty() ? name : description; + font->mFontType = font_type; + font->mFontStatus = font_status; + + // If the font is new or the expiration date has changed the expiry timers need updating. + if (!template_font && (new_font || font->mExpirationDate != expiration_date)) + { + font->mExpirationDate = expiration_date; + + // Set the expiry timer to trigger a notification when the voice font can no longer be used. + font->mExpiryTimer.start(); + font->mExpiryTimer.setExpiryAt(expiration_date.secondsSinceEpoch() - VOICE_FONT_EXPIRY_INTERVAL); + + // Set the warning timer to some interval before actual expiry. + S32 warning_time = gSavedSettings.getS32("VoiceEffectExpiryWarningTime"); + if (warning_time != 0) + { + font->mExpiryWarningTimer.start(); + F64 expiry_time = (expiration_date.secondsSinceEpoch() - (F64)warning_time); + font->mExpiryWarningTimer.setExpiryAt(expiry_time - VOICE_FONT_EXPIRY_INTERVAL); + } + else + { + // Disable the warning timer. + font->mExpiryWarningTimer.stop(); + } + + // Only flag new session fonts after the first time we have fetched the list. + if (mVoiceFontsReceived) + { + font->mIsNew = true; + mVoiceFontsNew = true; + } + } + + LL_DEBUGS("VoiceFont") << (template_font ? "Template " : "") + << font->mExpirationDate.asString() << " " << font->mID + << " (" << font->mFontIndex << ") " << name << LL_ENDL; + + if (new_font) + { + font_map.insert(voice_font_map_t::value_type(font->mID, font)); + font_list.insert(voice_effect_list_t::value_type(font->mName, font->mID)); + } + + mVoiceFontListDirty = true; + + // Debugging stuff + + if (font_type < VOICE_FONT_TYPE_NONE || font_type >= VOICE_FONT_TYPE_UNKNOWN) + { + LL_WARNS("VoiceFont") << "Unknown voice font type: " << font_type << LL_ENDL; + } + if (font_status < VOICE_FONT_STATUS_NONE || font_status >= VOICE_FONT_STATUS_UNKNOWN) + { + LL_WARNS("VoiceFont") << "Unknown voice font status: " << font_status << LL_ENDL; + } + } +} + +void LLVivoxVoiceClient::expireVoiceFonts() +{ + // *TODO: If we are selling voice fonts in packs, there are probably + // going to be a number of fonts with the same expiration time, so would + // be more efficient to just keep a list of expiration times rather + // than checking each font individually. + + bool have_expired = false; + bool will_expire = false; + bool expired_in_use = false; + + LLUUID current_effect = LLVoiceClient::instance().getVoiceEffectDefault(); + + voice_font_map_t::iterator iter; + for (iter = mVoiceFontMap.begin(); iter != mVoiceFontMap.end(); ++iter) + { + voiceFontEntry* voice_font = iter->second; + LLFrameTimer& expiry_timer = voice_font->mExpiryTimer; + LLFrameTimer& warning_timer = voice_font->mExpiryWarningTimer; + + // Check for expired voice fonts + if (expiry_timer.getStarted() && expiry_timer.hasExpired()) + { + // Check whether it is the active voice font + if (voice_font->mID == current_effect) + { + // Reset to no voice effect. + setVoiceEffect(LLUUID::null); + expired_in_use = true; + } + + LL_DEBUGS("Voice") << "Voice Font " << voice_font->mName << " has expired." << LL_ENDL; + deleteVoiceFont(voice_font->mID); + have_expired = true; + } + + // Check for voice fonts that will expire in less that the warning time + if (warning_timer.getStarted() && warning_timer.hasExpired()) + { + LL_DEBUGS("VoiceFont") << "Voice Font " << voice_font->mName << " will expire soon." << LL_ENDL; + will_expire = true; + warning_timer.stop(); + } + } + + LLSD args; + args["URL"] = LLTrans::getString("voice_morphing_url"); + args["PREMIUM_URL"] = LLTrans::getString("premium_voice_morphing_url"); + + // Give a notification if any voice fonts have expired. + if (have_expired) + { + if (expired_in_use) + { + LLNotificationsUtil::add("VoiceEffectsExpiredInUse", args); + } + else + { + LLNotificationsUtil::add("VoiceEffectsExpired", args); + } + + // Refresh voice font lists in the UI. + notifyVoiceFontObservers(); + } + + // Give a warning notification if any voice fonts are due to expire. + if (will_expire) + { + S32Seconds seconds(gSavedSettings.getS32("VoiceEffectExpiryWarningTime")); + args["INTERVAL"] = llformat("%d", LLUnit(seconds).value()); + + LLNotificationsUtil::add("VoiceEffectsWillExpire", args); + } +} + +void LLVivoxVoiceClient::deleteVoiceFont(const LLUUID& id) +{ + // Remove the entry from the voice font list. + voice_effect_list_t::iterator list_iter = mVoiceFontList.begin(); + while (list_iter != mVoiceFontList.end()) + { + if (list_iter->second == id) + { + LL_DEBUGS("VoiceFont") << "Removing " << id << " from the voice font list." << LL_ENDL; + list_iter = mVoiceFontList.erase(list_iter); + mVoiceFontListDirty = true; + } + else + { + ++list_iter; + } + } + + // Find the entry in the voice font map and erase its data. + voice_font_map_t::iterator map_iter = mVoiceFontMap.find(id); + if (map_iter != mVoiceFontMap.end()) + { + delete map_iter->second; + } + + // Remove the entry from the voice font map. + mVoiceFontMap.erase(map_iter); +} + +void LLVivoxVoiceClient::deleteAllVoiceFonts() +{ + mVoiceFontList.clear(); + + voice_font_map_t::iterator iter; + for (iter = mVoiceFontMap.begin(); iter != mVoiceFontMap.end(); ++iter) + { + delete iter->second; + } + mVoiceFontMap.clear(); +} + +void LLVivoxVoiceClient::deleteVoiceFontTemplates() +{ + mVoiceFontTemplateList.clear(); + + voice_font_map_t::iterator iter; + for (iter = mVoiceFontTemplateMap.begin(); iter != mVoiceFontTemplateMap.end(); ++iter) + { + delete iter->second; + } + mVoiceFontTemplateMap.clear(); +} + +S32 LLVivoxVoiceClient::getVoiceFontIndex(const LLUUID& id) const +{ + S32 result = 0; + if (!id.isNull()) + { + voice_font_map_t::const_iterator it = mVoiceFontMap.find(id); + if (it != mVoiceFontMap.end()) + { + result = it->second->mFontIndex; + } + else + { + LL_WARNS("VoiceFont") << "Selected voice font " << id << " is not available." << LL_ENDL; + } + } + return result; +} + +S32 LLVivoxVoiceClient::getVoiceFontTemplateIndex(const LLUUID& id) const +{ + S32 result = 0; + if (!id.isNull()) + { + voice_font_map_t::const_iterator it = mVoiceFontTemplateMap.find(id); + if (it != mVoiceFontTemplateMap.end()) + { + result = it->second->mFontIndex; + } + else + { + LL_WARNS("VoiceFont") << "Selected voice font template " << id << " is not available." << LL_ENDL; + } + } + return result; +} + +void LLVivoxVoiceClient::accountGetSessionFontsSendMessage() +{ + if(mAccountLoggedIn) + { + std::ostringstream stream; + + LL_DEBUGS("VoiceFont") << "Requesting voice font list." << LL_ENDL; + + stream + << "" + << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" + << "" + << "\n\n\n"; + + writeString(stream.str()); + } +} + +void LLVivoxVoiceClient::accountGetTemplateFontsSendMessage() +{ + if(mAccountLoggedIn) + { + std::ostringstream stream; + + LL_DEBUGS("VoiceFont") << "Requesting voice font template list." << LL_ENDL; + + stream + << "" + << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" + << "" + << "\n\n\n"; + + writeString(stream.str()); + } +} + +void LLVivoxVoiceClient::sessionSetVoiceFontSendMessage(const sessionStatePtr_t &session) +{ + S32 font_index = getVoiceFontIndex(session->mVoiceFontID); + LL_DEBUGS("VoiceFont") << "Requesting voice font: " << session->mVoiceFontID << " (" << font_index << "), session handle: " << session->mHandle << LL_ENDL; + + std::ostringstream stream; + + stream + << "" + << "" << session->mHandle << "" + << "" << font_index << "" + << "\n\n\n"; + + writeString(stream.str()); +} + +void LLVivoxVoiceClient::accountGetSessionFontsResponse(int statusCode, const std::string &statusString) +{ + if (mIsWaitingForFonts) + { + // *TODO: We seem to get multiple events of this type. Should figure a way to advance only after + // receiving the last one. + LLSD result(LLSDMap("voice_fonts", LLSD::Boolean(true))); + + mVivoxPump.post(result); + } + notifyVoiceFontObservers(); + mVoiceFontsReceived = true; +} + +void LLVivoxVoiceClient::accountGetTemplateFontsResponse(int statusCode, const std::string &statusString) +{ + // Voice font list entries were updated via addVoiceFont() during parsing. + notifyVoiceFontObservers(); +} +void LLVivoxVoiceClient::addObserver(LLVoiceEffectObserver* observer) +{ + mVoiceFontObservers.insert(observer); +} + +void LLVivoxVoiceClient::removeObserver(LLVoiceEffectObserver* observer) +{ + mVoiceFontObservers.erase(observer); +} + +// method checks the item in VoiceMorphing menu for appropriate current voice font +bool LLVivoxVoiceClient::onCheckVoiceEffect(const std::string& voice_effect_name) +{ + LLVoiceEffectInterface * effect_interfacep = LLVoiceClient::instance().getVoiceEffectInterface(); + if (NULL != effect_interfacep) + { + const LLUUID& currect_voice_effect_id = effect_interfacep->getVoiceEffect(); + + if (currect_voice_effect_id.isNull()) + { + if (voice_effect_name == "NoVoiceMorphing") + { + return true; + } + } + else + { + const LLSD& voice_effect_props = effect_interfacep->getVoiceEffectProperties(currect_voice_effect_id); + if (voice_effect_props["name"].asString() == voice_effect_name) + { + return true; + } + } + } + + return false; +} + +// method changes voice font for selected VoiceMorphing menu item +void LLVivoxVoiceClient::onClickVoiceEffect(const std::string& voice_effect_name) +{ + LLVoiceEffectInterface * effect_interfacep = LLVoiceClient::instance().getVoiceEffectInterface(); + if (NULL != effect_interfacep) + { + if (voice_effect_name == "NoVoiceMorphing") + { + effect_interfacep->setVoiceEffect(LLUUID()); + return; + } + const voice_effect_list_t& effect_list = effect_interfacep->getVoiceEffectList(); + if (!effect_list.empty()) + { + for (voice_effect_list_t::const_iterator it = effect_list.begin(); it != effect_list.end(); ++it) + { + if (voice_effect_name == it->first) + { + effect_interfacep->setVoiceEffect(it->second); + return; + } + } + } + } +} + +// it updates VoiceMorphing menu items in accordance with purchased properties +void LLVivoxVoiceClient::updateVoiceMorphingMenu() +{ + if (mVoiceFontListDirty) + { + LLVoiceEffectInterface * effect_interfacep = LLVoiceClient::instance().getVoiceEffectInterface(); + if (effect_interfacep) + { + const voice_effect_list_t& effect_list = effect_interfacep->getVoiceEffectList(); + if (!effect_list.empty()) + { + LLMenuGL * voice_morphing_menup = gMenuBarView->findChildMenuByName("VoiceMorphing", true); + + if (NULL != voice_morphing_menup) + { + S32 items = voice_morphing_menup->getItemCount(); + if (items > 0) + { + voice_morphing_menup->erase(1, items - 3, false); + + S32 pos = 1; + for (voice_effect_list_t::const_iterator it = effect_list.begin(); it != effect_list.end(); ++it) + { + LLMenuItemCheckGL::Params p; + p.name = it->first; + p.label = it->first; + p.on_check.function(boost::bind(&LLVivoxVoiceClient::onCheckVoiceEffect, this, it->first)); + p.on_click.function(boost::bind(&LLVivoxVoiceClient::onClickVoiceEffect, this, it->first)); + LLMenuItemCheckGL * voice_effect_itemp = LLUICtrlFactory::create(p); + voice_morphing_menup->insert(pos++, voice_effect_itemp, false); + } + + voice_morphing_menup->needsArrange(); + } + } + } + } + } +} +void LLVivoxVoiceClient::notifyVoiceFontObservers() +{ + LL_DEBUGS("VoiceFont") << "Notifying voice effect observers. Lists changed: " << mVoiceFontListDirty << LL_ENDL; + + updateVoiceMorphingMenu(); + + for (voice_font_observer_set_t::iterator it = mVoiceFontObservers.begin(); + it != mVoiceFontObservers.end();) + { + LLVoiceEffectObserver* observer = *it; + observer->onVoiceEffectChanged(mVoiceFontListDirty); + // In case onVoiceEffectChanged() deleted an entry. + it = mVoiceFontObservers.upper_bound(observer); + } + mVoiceFontListDirty = false; + + // If new Voice Fonts have been added notify the user. + if (mVoiceFontsNew) + { + if (mVoiceFontsReceived) + { + LLNotificationsUtil::add("VoiceEffectsNew"); + } + mVoiceFontsNew = false; + } +} + +void LLVivoxVoiceClient::enablePreviewBuffer(bool enable) +{ + LLSD result; + mCaptureBufferMode = enable; + + if (enable) + result["recplay"] = "start"; + else + result["recplay"] = "quit"; + + mVivoxPump.post(result); + + if(mCaptureBufferMode && mIsInChannel) + { + LL_DEBUGS("Voice") << "no channel" << LL_ENDL; + sessionTerminate(); + } +} + +void LLVivoxVoiceClient::recordPreviewBuffer() +{ + if (!mCaptureBufferMode) + { + LL_DEBUGS("Voice") << "Not in voice effect preview mode, cannot start recording." << LL_ENDL; + mCaptureBufferRecording = false; + return; + } + + mCaptureBufferRecording = true; + + LLSD result(LLSDMap("recplay", "record")); + mVivoxPump.post(result); +} + +void LLVivoxVoiceClient::playPreviewBuffer(const LLUUID& effect_id) +{ + if (!mCaptureBufferMode) + { + LL_DEBUGS("Voice") << "Not in voice effect preview mode, no buffer to play." << LL_ENDL; + mCaptureBufferRecording = false; + return; + } + + if (!mCaptureBufferRecorded) + { + // Can't play until we have something recorded! + mCaptureBufferPlaying = false; + return; + } + + mPreviewVoiceFont = effect_id; + mCaptureBufferPlaying = true; + + LLSD result(LLSDMap("recplay", "playback")); + mVivoxPump.post(result); +} + +void LLVivoxVoiceClient::stopPreviewBuffer() +{ + mCaptureBufferRecording = false; + mCaptureBufferPlaying = false; + + LLSD result(LLSDMap("recplay", "quit")); + mVivoxPump.post(result); +} + +bool LLVivoxVoiceClient::isPreviewRecording() +{ + return (mCaptureBufferMode && mCaptureBufferRecording); +} + +bool LLVivoxVoiceClient::isPreviewPlaying() +{ + return (mCaptureBufferMode && mCaptureBufferPlaying); +} + +void LLVivoxVoiceClient::captureBufferRecordStartSendMessage() +{ if(mAccountLoggedIn) + { + std::ostringstream stream; + + LL_DEBUGS("Voice") << "Starting audio capture to buffer." << LL_ENDL; + + // Start capture + stream + << "" + << "" + << "\n\n\n"; + + // Unmute the mic + stream << "" + << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" + << "false" + << "\n\n\n"; + + // Dirty the mute mic state so that it will get reset when we finishing previewing + mMuteMicDirty = true; + + writeString(stream.str()); + } +} + +void LLVivoxVoiceClient::captureBufferRecordStopSendMessage() +{ + if(mAccountLoggedIn) + { + std::ostringstream stream; + + LL_DEBUGS("Voice") << "Stopping audio capture to buffer." << LL_ENDL; + + // Mute the mic. Mic mute state was dirtied at recording start, so will be reset when finished previewing. + stream << "" + << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" + << "true" + << "\n\n\n"; + + // Stop capture + stream + << "" + << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" + << "" + << "\n\n\n"; + + writeString(stream.str()); + } +} + +void LLVivoxVoiceClient::captureBufferPlayStartSendMessage(const LLUUID& voice_font_id) +{ + if(mAccountLoggedIn) + { + // Track how may play requests are sent, so we know how many stop events to + // expect before play actually stops. + ++mPlayRequestCount; + + std::ostringstream stream; + + LL_DEBUGS("Voice") << "Starting audio buffer playback." << LL_ENDL; + + S32 font_index = getVoiceFontTemplateIndex(voice_font_id); + LL_DEBUGS("Voice") << "With voice font: " << voice_font_id << " (" << font_index << ")" << LL_ENDL; + + stream + << "" + << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" + << "" << font_index << "" + << "" + << "" + << "\n\n\n"; + + writeString(stream.str()); + } +} + +void LLVivoxVoiceClient::captureBufferPlayStopSendMessage() +{ + if(mAccountLoggedIn) + { + std::ostringstream stream; + + LL_DEBUGS("Voice") << "Stopping audio buffer playback." << LL_ENDL; + + stream + << "" + << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" + << "" + << "\n\n\n"; + + writeString(stream.str()); + } +} + +LLVivoxProtocolParser::LLVivoxProtocolParser() +{ + parser = XML_ParserCreate(NULL); + + reset(); +} + +void LLVivoxProtocolParser::reset() +{ + responseDepth = 0; + ignoringTags = false; + accumulateText = false; + energy = 0.f; + hasText = false; + hasAudio = false; + hasVideo = false; + terminated = false; + ignoreDepth = 0; + isChannel = false; + incoming = false; + enabled = false; + isEvent = false; + isLocallyMuted = false; + isModeratorMuted = false; + isSpeaking = false; + participantType = 0; + returnCode = -1; + state = 0; + statusCode = 0; + volume = 0; + textBuffer.clear(); + alias.clear(); + numberOfAliases = 0; + applicationString.clear(); +} + +//virtual +LLVivoxProtocolParser::~LLVivoxProtocolParser() +{ + if (parser) + XML_ParserFree(parser); +} + +static LLTrace::BlockTimerStatHandle FTM_VIVOX_PROCESS("Vivox Process"); + +// virtual +LLIOPipe::EStatus LLVivoxProtocolParser::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + LL_RECORD_BLOCK_TIME(FTM_VIVOX_PROCESS); + LLBufferStream istr(channels, buffer.get()); + std::ostringstream ostr; + while (istr.good()) + { + char buf[1024]; + istr.read(buf, sizeof(buf)); + mInput.append(buf, istr.gcount()); + } + + // Look for input delimiter(s) in the input buffer. If one is found, send the message to the xml parser. + int start = 0; + int delim; + while((delim = mInput.find("\n\n\n", start)) != std::string::npos) + { + + // Reset internal state of the LLVivoxProtocolParser (no effect on the expat parser) + reset(); + + XML_ParserReset(parser, NULL); + XML_SetElementHandler(parser, ExpatStartTag, ExpatEndTag); + XML_SetCharacterDataHandler(parser, ExpatCharHandler); + XML_SetUserData(parser, this); + XML_Parse(parser, mInput.data() + start, delim - start, false); + + LL_DEBUGS("VivoxProtocolParser") << "parsing: " << mInput.substr(start, delim - start) << LL_ENDL; + start = delim + 3; + } + + if(start != 0) + mInput = mInput.substr(start); + + LL_DEBUGS("VivoxProtocolParser") << "at end, mInput is: " << mInput << LL_ENDL; + + if(!LLVivoxVoiceClient::sConnected) + { + // If voice has been disabled, we just want to close the socket. This does so. + LL_INFOS("Voice") << "returning STATUS_STOP" << LL_ENDL; + return STATUS_STOP; + } + + return STATUS_OK; +} + +void XMLCALL LLVivoxProtocolParser::ExpatStartTag(void *data, const char *el, const char **attr) +{ + if (data) + { + LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data; + object->StartTag(el, attr); + } +} + +// -------------------------------------------------------------------------------- + +void XMLCALL LLVivoxProtocolParser::ExpatEndTag(void *data, const char *el) +{ + if (data) + { + LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data; + object->EndTag(el); + } +} + +// -------------------------------------------------------------------------------- + +void XMLCALL LLVivoxProtocolParser::ExpatCharHandler(void *data, const XML_Char *s, int len) +{ + if (data) + { + LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data; + object->CharData(s, len); + } +} + +// -------------------------------------------------------------------------------- + + +void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr) +{ + // Reset the text accumulator. We shouldn't have strings that are inturrupted by new tags + textBuffer.clear(); + // only accumulate text if we're not ignoring tags. + accumulateText = !ignoringTags; + + if (responseDepth == 0) + { + isEvent = !stricmp("Event", tag); + + if (!stricmp("Response", tag) || isEvent) + { + // Grab the attributes + while (*attr) + { + const char *key = *attr++; + const char *value = *attr++; + + if (!stricmp("requestId", key)) + { + requestId = value; + } + else if (!stricmp("action", key)) + { + actionString = value; + } + else if (!stricmp("type", key)) + { + eventTypeString = value; + } + } + } + LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL; + } + else + { + if (ignoringTags) + { + LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; + } + else + { + LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL; + + // Ignore the InputXml stuff so we don't get confused + if (!stricmp("InputXml", tag)) + { + ignoringTags = true; + ignoreDepth = responseDepth; + accumulateText = false; + + LL_DEBUGS("VivoxProtocolParser") << "starting ignore, ignoreDepth is " << ignoreDepth << LL_ENDL; + } + else if (!stricmp("CaptureDevices", tag)) + { + LLVivoxVoiceClient::getInstance()->clearCaptureDevices(); + } + else if (!stricmp("RenderDevices", tag)) + { + LLVivoxVoiceClient::getInstance()->clearRenderDevices(); + } + else if (!stricmp("CaptureDevice", tag)) + { + deviceString.clear(); + } + else if (!stricmp("RenderDevice", tag)) + { + deviceString.clear(); + } + else if (!stricmp("SessionFont", tag)) + { + id = 0; + nameString.clear(); + descriptionString.clear(); + expirationDate = LLDate(); + hasExpired = false; + fontType = 0; + fontStatus = 0; + } + else if (!stricmp("TemplateFont", tag)) + { + id = 0; + nameString.clear(); + descriptionString.clear(); + expirationDate = LLDate(); + hasExpired = false; + fontType = 0; + fontStatus = 0; + } + else if (!stricmp("MediaCompletionType", tag)) + { + mediaCompletionType.clear(); + } + } + } + responseDepth++; +} + +// -------------------------------------------------------------------------------- + +void LLVivoxProtocolParser::EndTag(const char *tag) +{ + const std::string& string = textBuffer; + + responseDepth--; + + if (ignoringTags) + { + if (ignoreDepth == responseDepth) + { + LL_DEBUGS("VivoxProtocolParser") << "end of ignore" << LL_ENDL; + ignoringTags = false; + } + else + { + LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; + } + } + + if (!ignoringTags) + { + LL_DEBUGS("VivoxProtocolParser") << "processing tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; + + // Closing a tag. Finalize the text we've accumulated and reset + if (!stricmp("ReturnCode", tag)) + returnCode = strtol(string.c_str(), NULL, 10); + else if (!stricmp("SessionHandle", tag)) + sessionHandle = string; + else if (!stricmp("SessionGroupHandle", tag)) + sessionGroupHandle = string; + else if (!stricmp("StatusCode", tag)) + statusCode = strtol(string.c_str(), NULL, 10); + else if (!stricmp("StatusString", tag)) + statusString = string; + else if (!stricmp("ParticipantURI", tag)) + uriString = string; + else if (!stricmp("Volume", tag)) + volume = strtol(string.c_str(), NULL, 10); + else if (!stricmp("Energy", tag)) + energy = (F32)strtod(string.c_str(), NULL); + else if (!stricmp("IsModeratorMuted", tag)) + isModeratorMuted = !stricmp(string.c_str(), "true"); + else if (!stricmp("IsSpeaking", tag)) + isSpeaking = !stricmp(string.c_str(), "true"); + else if (!stricmp("Alias", tag)) + alias = string; + else if (!stricmp("NumberOfAliases", tag)) + numberOfAliases = strtol(string.c_str(), NULL, 10); + else if (!stricmp("Application", tag)) + applicationString = string; + else if (!stricmp("ConnectorHandle", tag)) + connectorHandle = string; + else if (!stricmp("VersionID", tag)) + versionID = string; + else if (!stricmp("Version", tag)) + mBuildID = string; + else if (!stricmp("AccountHandle", tag)) + accountHandle = string; + else if (!stricmp("State", tag)) + state = strtol(string.c_str(), NULL, 10); + else if (!stricmp("URI", tag)) + uriString = string; + else if (!stricmp("IsChannel", tag)) + isChannel = !stricmp(string.c_str(), "true"); + else if (!stricmp("Incoming", tag)) + incoming = !stricmp(string.c_str(), "true"); + else if (!stricmp("Enabled", tag)) + enabled = !stricmp(string.c_str(), "true"); + else if (!stricmp("Name", tag)) + nameString = string; + else if (!stricmp("AudioMedia", tag)) + audioMediaString = string; + else if (!stricmp("ChannelName", tag)) + nameString = string; + else if (!stricmp("DisplayName", tag)) + displayNameString = string; + else if (!stricmp("Device", tag)) + deviceString = string; + else if (!stricmp("AccountName", tag)) + nameString = string; + else if (!stricmp("ParticipantType", tag)) + participantType = strtol(string.c_str(), NULL, 10); + else if (!stricmp("IsLocallyMuted", tag)) + isLocallyMuted = !stricmp(string.c_str(), "true"); + else if (!stricmp("MicEnergy", tag)) + energy = (F32)strtod(string.c_str(), NULL); + else if (!stricmp("ChannelName", tag)) + nameString = string; + else if (!stricmp("ChannelURI", tag)) + uriString = string; + else if (!stricmp("BuddyURI", tag)) + uriString = string; + else if (!stricmp("Presence", tag)) + statusString = string; + else if (!stricmp("CaptureDevices", tag)) + { + LLVivoxVoiceClient::getInstance()->setDevicesListUpdated(true); + } + else if (!stricmp("RenderDevices", tag)) + { + LLVivoxVoiceClient::getInstance()->setDevicesListUpdated(true); + } + else if (!stricmp("CaptureDevice", tag)) + { + LLVivoxVoiceClient::getInstance()->addCaptureDevice(LLVoiceDevice(displayNameString, deviceString)); + } + else if (!stricmp("RenderDevice", tag)) + { + LLVivoxVoiceClient::getInstance()->addRenderDevice(LLVoiceDevice(displayNameString, deviceString)); + } + else if (!stricmp("BlockMask", tag)) + blockMask = string; + else if (!stricmp("PresenceOnly", tag)) + presenceOnly = string; + else if (!stricmp("AutoAcceptMask", tag)) + autoAcceptMask = string; + else if (!stricmp("AutoAddAsBuddy", tag)) + autoAddAsBuddy = string; + else if (!stricmp("MessageHeader", tag)) + messageHeader = string; + else if (!stricmp("MessageBody", tag)) + messageBody = string; + else if (!stricmp("NotificationType", tag)) + notificationType = string; + else if (!stricmp("HasText", tag)) + hasText = !stricmp(string.c_str(), "true"); + else if (!stricmp("HasAudio", tag)) + hasAudio = !stricmp(string.c_str(), "true"); + else if (!stricmp("HasVideo", tag)) + hasVideo = !stricmp(string.c_str(), "true"); + else if (!stricmp("Terminated", tag)) + terminated = !stricmp(string.c_str(), "true"); + else if (!stricmp("SubscriptionHandle", tag)) + subscriptionHandle = string; + else if (!stricmp("SubscriptionType", tag)) + subscriptionType = string; + else if (!stricmp("SessionFont", tag)) + { + LLVivoxVoiceClient::getInstance()->addVoiceFont(id, nameString, descriptionString, expirationDate, hasExpired, fontType, fontStatus, false); + } + else if (!stricmp("TemplateFont", tag)) + { + LLVivoxVoiceClient::getInstance()->addVoiceFont(id, nameString, descriptionString, expirationDate, hasExpired, fontType, fontStatus, true); + } + else if (!stricmp("ID", tag)) + { + id = strtol(string.c_str(), NULL, 10); + } + else if (!stricmp("Description", tag)) + { + descriptionString = string; + } + else if (!stricmp("ExpirationDate", tag)) + { + expirationDate = expiryTimeStampToLLDate(string); + } + else if (!stricmp("Expired", tag)) + { + hasExpired = !stricmp(string.c_str(), "1"); + } + else if (!stricmp("Type", tag)) + { + fontType = strtol(string.c_str(), NULL, 10); + } + else if (!stricmp("Status", tag)) + { + fontStatus = strtol(string.c_str(), NULL, 10); + } + else if (!stricmp("MediaCompletionType", tag)) + { + mediaCompletionType = string;; + } + + textBuffer.clear(); + accumulateText= false; + + if (responseDepth == 0) + { + // We finished all of the XML, process the data + processResponse(tag); + } + } +} + +// -------------------------------------------------------------------------------- + +void LLVivoxProtocolParser::CharData(const char *buffer, int length) +{ + /* + This method is called for anything that isn't a tag, which can be text you + want that lies between tags, and a lot of stuff you don't want like file formatting + (tabs, spaces, CR/LF, etc). + + Only copy text if we are in accumulate mode... + */ + if (accumulateText) + textBuffer.append(buffer, length); +} + +// -------------------------------------------------------------------------------- + +LLDate LLVivoxProtocolParser::expiryTimeStampToLLDate(const std::string& vivox_ts) +{ + // *HACK: Vivox reports the time incorrectly. LLDate also only parses a + // subset of valid ISO 8601 dates (only handles Z, not offsets). + // So just use the date portion and fix the time here. + std::string time_stamp = vivox_ts.substr(0, 10); + time_stamp += VOICE_FONT_EXPIRY_TIME; + + LL_DEBUGS("VivoxProtocolParser") << "Vivox timestamp " << vivox_ts << " modified to: " << time_stamp << LL_ENDL; + + return LLDate(time_stamp); +} + +// -------------------------------------------------------------------------------- + +void LLVivoxProtocolParser::processResponse(std::string tag) +{ + LL_DEBUGS("VivoxProtocolParser") << tag << LL_ENDL; + + // SLIM SDK: the SDK now returns a statusCode of "200" (OK) for success. This is a change vs. previous SDKs. + // According to Mike S., "The actual API convention is that responses with return codes of 0 are successful, regardless of the status code returned", + // so I believe this will give correct behavior. + + if(returnCode == 0) + statusCode = 0; + + if (isEvent) + { + const char *eventTypeCstr = eventTypeString.c_str(); + LL_DEBUGS("LowVoice") << eventTypeCstr << LL_ENDL; + + if (!stricmp(eventTypeCstr, "ParticipantUpdatedEvent")) + { + // These happen so often that logging them is pretty useless. + LL_DEBUGS("LowVoice") << "Updated Params: " << sessionHandle << ", " << sessionGroupHandle << ", " << uriString << ", " << alias << ", " << isModeratorMuted << ", " << isSpeaking << ", " << volume << ", " << energy << LL_ENDL; + LLVivoxVoiceClient::getInstance()->participantUpdatedEvent(sessionHandle, sessionGroupHandle, uriString, alias, isModeratorMuted, isSpeaking, volume, energy); + } + else if (!stricmp(eventTypeCstr, "AccountLoginStateChangeEvent")) + { + LLVivoxVoiceClient::getInstance()->accountLoginStateChangeEvent(accountHandle, statusCode, statusString, state); + } + else if (!stricmp(eventTypeCstr, "SessionAddedEvent")) + { + /* + + c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0 + c1_m1000xFnPP04IpREWNkuw1cOXlhw==0 + sip:confctl-1408789@bhr.vivox.com + true + false + + + */ + LLVivoxVoiceClient::getInstance()->sessionAddedEvent(uriString, alias, sessionHandle, sessionGroupHandle, isChannel, incoming, nameString, applicationString); + } + else if (!stricmp(eventTypeCstr, "SessionRemovedEvent")) + { + LLVivoxVoiceClient::getInstance()->sessionRemovedEvent(sessionHandle, sessionGroupHandle); + } + else if (!stricmp(eventTypeCstr, "SessionGroupUpdatedEvent")) + { + //nothng useful to process for this event, but we should not WARN that we have received it. + } + else if (!stricmp(eventTypeCstr, "SessionGroupAddedEvent")) + { + LLVivoxVoiceClient::getInstance()->sessionGroupAddedEvent(sessionGroupHandle); + } + else if (!stricmp(eventTypeCstr, "MediaStreamUpdatedEvent")) + { + /* + + c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0 + c1_m1000xFnPP04IpREWNkuw1cOXlhw==0 + 200 + OK + 2 + false + + */ + LLVivoxVoiceClient::getInstance()->mediaStreamUpdatedEvent(sessionHandle, sessionGroupHandle, statusCode, statusString, state, incoming); + } + else if (!stricmp(eventTypeCstr, "MediaCompletionEvent")) + { + /* + + + AuxBufferAudioCapture + + */ + LLVivoxVoiceClient::getInstance()->mediaCompletionEvent(sessionGroupHandle, mediaCompletionType); + } + else if (!stricmp(eventTypeCstr, "ParticipantAddedEvent")) + { + /* + + c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4 + c1_m1000xFnPP04IpREWNkuw1cOXlhw==4 + sip:xI5auBZ60SJWIk606-1JGRQ==@bhr.vivox.com + xI5auBZ60SJWIk606-1JGRQ== + + 0 + + */ + LL_DEBUGS("LowVoice") << "Added Params: " << sessionHandle << ", " << sessionGroupHandle << ", " << uriString << ", " << alias << ", " << nameString << ", " << displayNameString << ", " << participantType << LL_ENDL; + LLVivoxVoiceClient::getInstance()->participantAddedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString, displayNameString, participantType); + } + else if (!stricmp(eventTypeCstr, "ParticipantRemovedEvent")) + { + /* + + c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4 + c1_m1000xFnPP04IpREWNkuw1cOXlhw==4 + sip:xtx7YNV-3SGiG7rA1fo5Ndw==@bhr.vivox.com + xtx7YNV-3SGiG7rA1fo5Ndw== + + */ + LL_DEBUGS("LowVoice") << "Removed params:" << sessionHandle << ", " << sessionGroupHandle << ", " << uriString << ", " << alias << ", " << nameString << LL_ENDL; + + LLVivoxVoiceClient::getInstance()->participantRemovedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString); + } + else if (!stricmp(eventTypeCstr, "AuxAudioPropertiesEvent")) + { + // These are really spammy in tuning mode + LLVivoxVoiceClient::getInstance()->auxAudioPropertiesEvent(energy); + } + else if (!stricmp(eventTypeCstr, "MessageEvent")) + { + //TODO: This probably is not received any more, it was used to support SLim clients + LLVivoxVoiceClient::getInstance()->messageEvent(sessionHandle, uriString, alias, messageHeader, messageBody, applicationString); + } + else if (!stricmp(eventTypeCstr, "SessionNotificationEvent")) + { + //TODO: This probably is not received any more, it was used to support SLim clients + LLVivoxVoiceClient::getInstance()->sessionNotificationEvent(sessionHandle, uriString, notificationType); + } + else if (!stricmp(eventTypeCstr, "SessionUpdatedEvent")) + { + /* + + c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0 + c1_m1000xFnPP04IpREWNkuw1cOXlhw==0 + sip:confctl-9@bhd.vivox.com + 0 + 50 + 1 + 0 + 000 + 0 + + */ + // We don't need to process this, but we also shouldn't warn on it, since that confuses people. + } + else if (!stricmp(eventTypeCstr, "SessionGroupRemovedEvent")) + { + // We don't need to process this, but we also shouldn't warn on it, since that confuses people. + } + else if (!stricmp(eventTypeCstr, "VoiceServiceConnectionStateChangedEvent")) + { + LLVivoxVoiceClient::getInstance()->voiceServiceConnectionStateChangedEvent(statusCode, statusString, mBuildID); + } + else if (!stricmp(eventTypeCstr, "AudioDeviceHotSwapEvent")) + { + /* + + RenderDeviceChanged< / EventType> + + Speakers(Turtle Beach P11 Headset)< / Device> + Speakers(Turtle Beach P11 Headset)< / DisplayName> + SpecificDevice< / Type> + < / RelevantDevice> + < / Event> + */ + // an audio device was removed or added, fetch and update the local list of audio devices. + LLVivoxVoiceClient::getInstance()->getCaptureDevicesSendMessage(); + LLVivoxVoiceClient::getInstance()->getRenderDevicesSendMessage(); + } + else + { + LL_WARNS("VivoxProtocolParser") << "Unknown event type " << eventTypeString << LL_ENDL; + } + } + else + { + const char *actionCstr = actionString.c_str(); + LL_DEBUGS("LowVoice") << actionCstr << LL_ENDL; + + if (!stricmp(actionCstr, "Session.Set3DPosition.1")) + { + // We don't need to process these + } + else if (!stricmp(actionCstr, "Connector.Create.1")) + { + LLVivoxVoiceClient::getInstance()->connectorCreateResponse(statusCode, statusString, connectorHandle, versionID); + } + else if (!stricmp(actionCstr, "Account.Login.1")) + { + LLVivoxVoiceClient::getInstance()->loginResponse(statusCode, statusString, accountHandle, numberOfAliases); + } + else if (!stricmp(actionCstr, "Session.Create.1")) + { + LLVivoxVoiceClient::getInstance()->sessionCreateResponse(requestId, statusCode, statusString, sessionHandle); + } + else if (!stricmp(actionCstr, "SessionGroup.AddSession.1")) + { + LLVivoxVoiceClient::getInstance()->sessionGroupAddSessionResponse(requestId, statusCode, statusString, sessionHandle); + } + else if (!stricmp(actionCstr, "Session.Connect.1")) + { + LLVivoxVoiceClient::getInstance()->sessionConnectResponse(requestId, statusCode, statusString); + } + else if (!stricmp(actionCstr, "Account.Logout.1")) + { + LLVivoxVoiceClient::getInstance()->logoutResponse(statusCode, statusString); + } + else if (!stricmp(actionCstr, "Connector.InitiateShutdown.1")) + { + LLVivoxVoiceClient::getInstance()->connectorShutdownResponse(statusCode, statusString); + } + else if (!stricmp(actionCstr, "Account.GetSessionFonts.1")) + { + LLVivoxVoiceClient::getInstance()->accountGetSessionFontsResponse(statusCode, statusString); + } + else if (!stricmp(actionCstr, "Account.GetTemplateFonts.1")) + { + LLVivoxVoiceClient::getInstance()->accountGetTemplateFontsResponse(statusCode, statusString); + } + else if (!stricmp(actionCstr, "Aux.SetVadProperties.1")) + { + // both values of statusCode (old and more recent) indicate valid requests + if (statusCode != 0 && statusCode != 200) + { + LL_WARNS("Voice") << "Aux.SetVadProperties.1 request failed: " + << "statusCode: " << statusCode + << " and " + << "statusString: " << statusString + << LL_ENDL; + } + } + /* + else if (!stricmp(actionCstr, "Account.ChannelGetList.1")) + { + LLVoiceClient::getInstance()->channelGetListResponse(statusCode, statusString); + } + else if (!stricmp(actionCstr, "Connector.AccountCreate.1")) + { + + } + else if (!stricmp(actionCstr, "Connector.MuteLocalMic.1")) + { + + } + else if (!stricmp(actionCstr, "Connector.MuteLocalSpeaker.1")) + { + + } + else if (!stricmp(actionCstr, "Connector.SetLocalMicVolume.1")) + { + + } + else if (!stricmp(actionCstr, "Connector.SetLocalSpeakerVolume.1")) + { + + } + else if (!stricmp(actionCstr, "Session.ListenerSetPosition.1")) + { + + } + else if (!stricmp(actionCstr, "Session.SpeakerSetPosition.1")) + { + + } + else if (!stricmp(actionCstr, "Session.AudioSourceSetPosition.1")) + { + + } + else if (!stricmp(actionCstr, "Session.GetChannelParticipants.1")) + { + + } + else if (!stricmp(actionCstr, "Account.ChannelCreate.1")) + { + + } + else if (!stricmp(actionCstr, "Account.ChannelUpdate.1")) + { + + } + else if (!stricmp(actionCstr, "Account.ChannelDelete.1")) + { + + } + else if (!stricmp(actionCstr, "Account.ChannelCreateAndInvite.1")) + { + + } + else if (!stricmp(actionCstr, "Account.ChannelFolderCreate.1")) + { + + } + else if (!stricmp(actionCstr, "Account.ChannelFolderUpdate.1")) + { + + } + else if (!stricmp(actionCstr, "Account.ChannelFolderDelete.1")) + { + + } + else if (!stricmp(actionCstr, "Account.ChannelAddModerator.1")) + { + + } + else if (!stricmp(actionCstr, "Account.ChannelDeleteModerator.1")) + { + + } + */ + } +} + +LLVivoxSecurity::LLVivoxSecurity() +{ + // This size is an arbitrary choice; Vivox does not care + // Use a multiple of three so that there is no '=' padding in the base64 (purely an esthetic choice) + #define VIVOX_TOKEN_BYTES 9 + U8 random_value[VIVOX_TOKEN_BYTES]; + + for (int b = 0; b < VIVOX_TOKEN_BYTES; b++) + { + random_value[b] = ll_rand() & 0xff; + } + mConnectorHandle = LLBase64::encode(random_value, VIVOX_TOKEN_BYTES); + + for (int b = 0; b < VIVOX_TOKEN_BYTES; b++) + { + random_value[b] = ll_rand() & 0xff; + } + mAccountHandle = LLBase64::encode(random_value, VIVOX_TOKEN_BYTES); +} + +LLVivoxSecurity::~LLVivoxSecurity() +{ +} diff --git a/indra/newview/llvoicevivox.h b/indra/newview/llvoicevivox.h index 2581c011eb..dde3a46514 100644 --- a/indra/newview/llvoicevivox.h +++ b/indra/newview/llvoicevivox.h @@ -1,1084 +1,1084 @@ -/** - * @file llvoicevivox.h - * @brief Declaration of LLDiamondwareVoiceClient class which is the interface to the voice client process. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ -#ifndef LL_VOICE_VIVOX_H -#define LL_VOICE_VIVOX_H - -class LLVOAvatar; -class LLVivoxProtocolParser; - -#include "lliopipe.h" -#include "llpumpio.h" -#include "llchainio.h" -#include "lliosocket.h" -#include "v3math.h" -#include "llframetimer.h" -#include "llviewerregion.h" -#include "llcallingcard.h" // for LLFriendObserver -#include "lleventcoro.h" -#include "llcoros.h" -#include - -#ifdef LL_USESYSTEMLIBS -# include "expat.h" -#else -# include "expat/expat.h" -#endif -#include "llvoiceclient.h" - -class LLAvatarName; -class LLVivoxVoiceClientMuteListObserver; - - -class LLVivoxVoiceClient : public LLSingleton, - virtual public LLVoiceModuleInterface, - virtual public LLVoiceEffectInterface -{ - LLSINGLETON(LLVivoxVoiceClient); - LOG_CLASS(LLVivoxVoiceClient); - virtual ~LLVivoxVoiceClient(); - -public: - /// @name LLVoiceModuleInterface virtual implementations - /// @see LLVoiceModuleInterface - //@{ - virtual void init(LLPumpIO *pump) override; // Call this once at application startup (creates connector) - virtual void terminate() override; // Call this to clean up during shutdown - - virtual const LLVoiceVersionInfo& getVersion() override; - - virtual void updateSettings() override; // call after loading settings and whenever they change - - // Returns true if vivox has successfully logged in and is not in error state - virtual bool isVoiceWorking() const override; - - ///////////////////// - /// @name Tuning - //@{ - virtual void tuningStart() override; - virtual void tuningStop() override; - virtual bool inTuningMode() override; - - virtual void tuningSetMicVolume(float volume) override; - virtual void tuningSetSpeakerVolume(float volume) override; - virtual float tuningGetEnergy(void) override; - //@} - - ///////////////////// - /// @name Devices - //@{ - // This returns true when it's safe to bring up the "device settings" dialog in the prefs. - // i.e. when the daemon is running and connected, and the device lists are populated. - virtual bool deviceSettingsAvailable() override; - virtual bool deviceSettingsUpdated() override; //return if the list has been updated and never fetched, only to be called from the voicepanel. - - // Requery the vivox daemon for the current list of input/output devices. - // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed - // (use this if you want to know when it's done). - // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim. - virtual void refreshDeviceLists(bool clearCurrentList = true) override; - - virtual void setCaptureDevice(const std::string& name) override; - virtual void setRenderDevice(const std::string& name) override; - - virtual LLVoiceDeviceList& getCaptureDevices() override; - virtual LLVoiceDeviceList& getRenderDevices() override; - //@} - - virtual void getParticipantList(std::set &participants) override; - virtual bool isParticipant(const LLUUID& speaker_id) override; - - // Send a text message to the specified user, initiating the session if necessary. - // virtual bool sendTextMessage(const LLUUID& participant_id, const std::string& message) const {return false;}; - - // close any existing text IM session with the specified user - virtual void endUserIMSession(const LLUUID &uuid) override; - - // Returns true if calling back the session URI after the session has closed is possible. - // Currently this will be false only for PSTN P2P calls. - // NOTE: this will return true if the session can't be found. - virtual bool isSessionCallBackPossible(const LLUUID &session_id) override; - - // Returns true if the session can accepte text IM's. - // Currently this will be false only for PSTN P2P calls. - // NOTE: this will return true if the session can't be found. - virtual bool isSessionTextIMPossible(const LLUUID &session_id) override; - - - //////////////////////////// - /// @name Channel stuff - //@{ - // returns true iff the user is currently in a proximal (local spatial) channel. - // Note that gestures should only fire if this returns true. - virtual bool inProximalChannel() override; - - virtual void setNonSpatialChannel(const std::string &uri, - const std::string &credentials) override; - - virtual bool setSpatialChannel(const std::string &uri, - const std::string &credentials) override; - - virtual void leaveNonSpatialChannel() override; - - virtual void leaveChannel(void) override; - - // Returns the URI of the current channel, or an empty string if not currently in a channel. - // NOTE that it will return an empty string if it's in the process of joining a channel. - virtual std::string getCurrentChannel() override; - //@} - - - ////////////////////////// - /// @name invitations - //@{ - // start a voice channel with the specified user - virtual void callUser(const LLUUID &uuid) override; - virtual bool isValidChannel(std::string &channelHandle) override; - virtual bool answerInvite(std::string &channelHandle) override; - virtual void declineInvite(std::string &channelHandle) override; - //@} - - ///////////////////////// - /// @name Volume/gain - //@{ - virtual void setVoiceVolume(F32 volume) override; - virtual void setMicGain(F32 volume) override; - //@} - - ///////////////////////// - /// @name enable disable voice and features - //@{ - virtual bool voiceEnabled() override; - virtual void setVoiceEnabled(bool enabled) override; - virtual bool lipSyncEnabled() override; - virtual void setLipSyncEnabled(bool enabled) override; - virtual void setMuteMic(bool muted) override; // Set the mute state of the local mic. - //@} - - ////////////////////////// - /// @name nearby speaker accessors - //@{ - virtual bool getVoiceEnabled(const LLUUID& id) override; // true if we've received data for this avatar - virtual std::string getDisplayName(const LLUUID& id) override; - virtual bool isParticipantAvatar(const LLUUID &id) override; - virtual bool getIsSpeaking(const LLUUID& id) override; - virtual bool getIsModeratorMuted(const LLUUID& id) override; - virtual F32 getCurrentPower(const LLUUID& id) override; // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is... - virtual bool getOnMuteList(const LLUUID& id) override; - virtual F32 getUserVolume(const LLUUID& id) override; - virtual void setUserVolume(const LLUUID& id, F32 volume) override; // set's volume for specified agent, from 0-1 (where .5 is nominal) - //@} - - // authorize the user - virtual void userAuthorized(const std::string& user_id, - const LLUUID &agentID) override; - - ////////////////////////////// - /// @name Status notification - //@{ - virtual void addObserver(LLVoiceClientStatusObserver* observer) override; - virtual void removeObserver(LLVoiceClientStatusObserver* observer) override; - virtual void addObserver(LLFriendObserver* observer) override; - virtual void removeObserver(LLFriendObserver* observer) override; - virtual void addObserver(LLVoiceClientParticipantObserver* observer) override; - virtual void removeObserver(LLVoiceClientParticipantObserver* observer) override; - //@} - - virtual std::string sipURIFromID(const LLUUID &id) override; - //@} - - /// @name LLVoiceEffectInterface virtual implementations - /// @see LLVoiceEffectInterface - //@{ - - ////////////////////////// - /// @name Accessors - //@{ - virtual bool setVoiceEffect(const LLUUID& id) override; - virtual const LLUUID getVoiceEffect() override; - virtual LLSD getVoiceEffectProperties(const LLUUID& id) override; - - virtual void refreshVoiceEffectLists(bool clear_lists) override; - virtual const voice_effect_list_t& getVoiceEffectList() const override; - virtual const voice_effect_list_t& getVoiceEffectTemplateList() const override; - //@} - - ////////////////////////////// - /// @name Status notification - //@{ - virtual void addObserver(LLVoiceEffectObserver* observer) override; - virtual void removeObserver(LLVoiceEffectObserver* observer) override; - //@} - - ////////////////////////////// - /// @name Effect preview buffer - //@{ - virtual void enablePreviewBuffer(bool enable) override; - virtual void recordPreviewBuffer() override; - virtual void playPreviewBuffer(const LLUUID& effect_id = LLUUID::null) override; - virtual void stopPreviewBuffer() override; - - virtual bool isPreviewRecording() override; - virtual bool isPreviewPlaying() override; - //@} - - //@} - - bool onCheckVoiceEffect(const std::string& voice_effect_name); - void onClickVoiceEffect(const std::string& voice_effect_name); - -protected: - ////////////////////// - // Vivox Specific definitions - - friend class LLVivoxVoiceClientMuteListObserver; - friend class LLVivoxVoiceClientFriendsObserver; - - - enum streamState - { - streamStateUnknown = 0, - streamStateIdle = 1, - streamStateConnected = 2, - streamStateRinging = 3, - streamStateConnecting = 6, // same as Vivox session_media_connecting enum - streamStateDisconnecting = 7, //Same as Vivox session_media_disconnecting enum - }; - - struct participantState - { - public: - participantState(const std::string &uri); - - bool updateMuteState(); // true if mute state has changed - bool isAvatar(); - - std::string mURI; - LLUUID mAvatarID; - std::string mAccountName; - std::string mDisplayName; - LLFrameTimer mSpeakingTimeout; - F32 mLastSpokeTimestamp; - F32 mPower; - F32 mVolume; - std::string mGroupID; - int mUserVolume; - bool mPTT; - bool mIsSpeaking; - bool mIsModeratorMuted; - bool mOnMuteList; // true if this avatar is on the user's mute list (and should be muted) - bool mVolumeSet; // true if incoming volume messages should not change the volume - bool mVolumeDirty; // true if this participant needs a volume command sent (either mOnMuteList or mUserVolume has changed) - bool mAvatarIDValid; - bool mIsSelf; - }; - typedef std::shared_ptr participantStatePtr_t; - typedef std::weak_ptr participantStateWptr_t; - - typedef std::map participantMap; - typedef std::map participantUUIDMap; - - struct sessionState - { - public: - typedef std::shared_ptr ptr_t; - typedef std::weak_ptr wptr_t; - - typedef std::function sessionFunc_t; - - static ptr_t createSession(); - ~sessionState(); - - participantStatePtr_t addParticipant(const std::string &uri); - void removeParticipant(const participantStatePtr_t &participant); - void removeAllParticipants(); - - participantStatePtr_t findParticipant(const std::string &uri); - participantStatePtr_t findParticipantByID(const LLUUID& id); - - static ptr_t matchSessionByHandle(const std::string &handle); - static ptr_t matchCreatingSessionByURI(const std::string &uri); - static ptr_t matchSessionByURI(const std::string &uri); - static ptr_t matchSessionByParticipant(const LLUUID &participant_id); - - bool isCallBackPossible(); - bool isTextIMPossible(); - - static void for_each(sessionFunc_t func); - - std::string mHandle; - std::string mGroupHandle; - std::string mSIPURI; - std::string mAlias; - std::string mName; - std::string mAlternateSIPURI; - std::string mHash; // Channel password - std::string mErrorStatusString; - std::queue mTextMsgQueue; - - LLUUID mIMSessionID; - LLUUID mCallerID; - int mErrorStatusCode; - int mMediaStreamState; - bool mCreateInProgress; // True if a Session.Create has been sent for this session and no response has been received yet. - bool mMediaConnectInProgress; // True if a Session.MediaConnect has been sent for this session and no response has been received yet. - bool mVoiceInvitePending; // True if a voice invite is pending for this session (usually waiting on a name lookup) - bool mTextInvitePending; // True if a text invite is pending for this session (usually waiting on a name lookup) - bool mSynthesizedCallerID; // True if the caller ID is a hash of the SIP URI -- this means we shouldn't do a name lookup. - bool mIsChannel; // True for both group and spatial channels (false for p2p, PSTN) - bool mIsSpatial; // True for spatial channels - bool mIsP2P; - bool mIncoming; - bool mVoiceActive; - bool mReconnect; // Whether we should try to reconnect to this session if it's dropped - - // Set to true when the volume/mute state of someone in the participant list changes. - // The code will have to walk the list to find the changed participant(s). - bool mVolumeDirty; - bool mMuteDirty; - - bool mParticipantsChanged; - participantMap mParticipantsByURI; - participantUUIDMap mParticipantsByUUID; - - LLUUID mVoiceFontID; - - static void VerifySessions(); - - private: - sessionState(); - - static std::set> mSession; // canonical list of outstanding sessions. - std::set::iterator mMyIterator; // used for delete - - static void for_eachPredicate(const wptr_t &a, sessionFunc_t func); - - static bool testByHandle(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string handle); - static bool testByCreatingURI(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string uri); - static bool testBySIPOrAlterateURI(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string uri); - static bool testByCallerId(const LLVivoxVoiceClient::sessionState::wptr_t &a, LLUUID participantId); - - }; - typedef std::shared_ptr sessionStatePtr_t; - - typedef std::map sessionMap; - - /////////////////////////////////////////////////////// - // Private Member Functions - ////////////////////////////////////////////////////// - - - - ////////////////////////////// - /// @name TVC/Server management and communication - //@{ - // Call this if the connection to the daemon terminates unexpectedly. It will attempt to reset everything and relaunch. - void daemonDied(); - - // Call this if we're just giving up on voice (can't provision an account, etc.). It will clean up and go away. - void giveUp(); - - // write to the tvc - bool writeString(const std::string &str); - - void connectorCreate(); - void connectorShutdown(); - void closeSocket(void); - -// void requestVoiceAccountProvision(S32 retries = 3); - void setLoginInfo( - const std::string& account_name, - const std::string& password, - const std::string& voice_sip_uri_hostname, - const std::string& voice_account_server_uri); - void loginSendMessage(); - void logout(); - void logoutSendMessage(); - - - //@} - - //------------------------------------ - // tuning - - void tuningRenderStartSendMessage(const std::string& name, bool loop); - void tuningRenderStopSendMessage(); - - void tuningCaptureStartSendMessage(int duration); - void tuningCaptureStopSendMessage(); - - //---------------------------------- - // devices - void clearCaptureDevices(); - void addCaptureDevice(const LLVoiceDevice& device); - void clearRenderDevices(); - void setDevicesListUpdated(bool state); - void addRenderDevice(const LLVoiceDevice& device); - void buildSetAudioDevices(std::ostringstream &stream); - - void getCaptureDevicesSendMessage(); - void getRenderDevicesSendMessage(); - - // local audio updates, mic mute, speaker mute, mic volume and speaker volumes - void sendLocalAudioUpdates(); - - ///////////////////////////// - // Response/Event handlers - void connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID); - void loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases); - void sessionCreateResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle); - void sessionGroupAddSessionResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle); - void sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString); - void logoutResponse(int statusCode, std::string &statusString); - void connectorShutdownResponse(int statusCode, std::string &statusString); - - void accountLoginStateChangeEvent(std::string &accountHandle, int statusCode, std::string &statusString, int state); - void mediaCompletionEvent(std::string &sessionGroupHandle, std::string &mediaCompletionType); - void mediaStreamUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, int statusCode, std::string &statusString, int state, bool incoming); - void sessionAddedEvent(std::string &uriString, std::string &alias, std::string &sessionHandle, std::string &sessionGroupHandle, bool isChannel, bool incoming, std::string &nameString, std::string &applicationString); - void sessionGroupAddedEvent(std::string &sessionGroupHandle); - void sessionRemovedEvent(std::string &sessionHandle, std::string &sessionGroupHandle); - void participantAddedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, std::string &nameString, std::string &displayNameString, int participantType); - void participantRemovedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, std::string &nameString); - void participantUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, bool isModeratorMuted, bool isSpeaking, int volume, F32 energy); - void voiceServiceConnectionStateChangedEvent(int statusCode, std::string &statusString, std::string &build_id); - void auxAudioPropertiesEvent(F32 energy); - void messageEvent(std::string &sessionHandle, std::string &uriString, std::string &alias, std::string &messageHeader, std::string &messageBody, std::string &applicationString); - void sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string ¬ificationType); - - void muteListChanged(); - - ///////////////////////////// - // VAD changes - // disable auto-VAD and configure VAD parameters explicitly - void setupVADParams(unsigned int vad_auto, unsigned int vad_hangover, unsigned int vad_noise_floor, unsigned int vad_sensitivity); - void onVADSettingsChange(); - - ///////////////////////////// - // Sending updates of current state - void updatePosition(void); - void setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot); - void setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLQuaternion &rot); - bool channelFromRegion(LLViewerRegion *region, std::string &name); - - void setEarLocation(S32 loc); - - - ///////////////////////////// - // Accessors for data related to nearby speakers - - // MBW -- XXX -- Not sure how to get this data out of the TVC - bool getUsingPTT(const LLUUID& id); - std::string getGroupID(const LLUUID& id); // group ID if the user is in group chat (empty string if not applicable) - - ///////////////////////////// - bool getAreaVoiceDisabled(); // returns true if the area the avatar is in is speech-disabled. - // Use this to determine whether to show a "no speech" icon in the menu bar. - - - ///////////////////////////// - // Recording controls - void recordingLoopStart(int seconds = 3600, int deltaFramesPerControlFrame = 200); - void recordingLoopSave(const std::string& filename); - void recordingStop(); - - // Playback controls - void filePlaybackStart(const std::string& filename); - void filePlaybackStop(); - void filePlaybackSetPaused(bool paused); - void filePlaybackSetMode(bool vox = false, float speed = 1.0f); - - participantStatePtr_t findParticipantByID(const LLUUID& id); - - -#if 0 - //////////////////////////////////////// - // voice sessions. - typedef std::set sessionSet; - - typedef sessionSet::iterator sessionIterator; - sessionIterator sessionsBegin(void); - sessionIterator sessionsEnd(void); -#endif - - sessionStatePtr_t findSession(const std::string &handle); - sessionStatePtr_t findSessionBeingCreatedByURI(const std::string &uri); - sessionStatePtr_t findSession(const LLUUID &participant_id); - - sessionStatePtr_t addSession(const std::string &uri, const std::string &handle = std::string()); - void clearSessionHandle(const sessionStatePtr_t &session); - void setSessionHandle(const sessionStatePtr_t &session, const std::string &handle); - void setSessionURI(const sessionStatePtr_t &session, const std::string &uri); - void deleteSession(const sessionStatePtr_t &session); - void deleteAllSessions(void); - - void verifySessionState(void); - - void joinedAudioSession(const sessionStatePtr_t &session); - void leftAudioSession(const sessionStatePtr_t &session); - - // This is called in several places where the session _may_ need to be deleted. - // It contains logic for whether to delete the session or keep it around. - void reapSession(const sessionStatePtr_t &session); - - // Returns true if the session seems to indicate we've moved to a region on a different voice server - bool sessionNeedsRelog(const sessionStatePtr_t &session); - - - ////////////////////////////////////// - // buddy list stuff, needed for SLIM later - struct buddyListEntry - { - buddyListEntry(const std::string &uri); - std::string mURI; - std::string mDisplayName; - LLUUID mUUID; - bool mOnlineSL; - bool mOnlineSLim; - bool mCanSeeMeOnline; - bool mHasBlockListEntry; - bool mHasAutoAcceptListEntry; - bool mNameResolved; - bool mInSLFriends; - bool mInVivoxBuddies; - }; - - typedef std::map buddyListMap; - - ///////////////////////////// - // session control messages - - void accountListBlockRulesSendMessage(); - void accountListAutoAcceptRulesSendMessage(); - - void sessionGroupCreateSendMessage(); - void sessionCreateSendMessage(const sessionStatePtr_t &session, bool startAudio = true, bool startText = false); - void sessionGroupAddSessionSendMessage(const sessionStatePtr_t &session, bool startAudio = true, bool startText = false); - void sessionMediaConnectSendMessage(const sessionStatePtr_t &session); // just joins the audio session - void sessionTextConnectSendMessage(const sessionStatePtr_t &session); // just joins the text session - void sessionTerminateSendMessage(const sessionStatePtr_t &session); - void sessionGroupTerminateSendMessage(const sessionStatePtr_t &session); - void sessionMediaDisconnectSendMessage(const sessionStatePtr_t &session); - // void sessionTextDisconnectSendMessage(sessionState *session); - - - - // Pokes the state machine to leave the audio session next time around. - void sessionTerminate(); - - // Pokes the state machine to shut down the connector and restart it. - void requestRelog(); - - // Does the actual work to get out of the audio session - void leaveAudioSession(); - - friend class LLVivoxVoiceClientCapResponder; - - - void lookupName(const LLUUID &id); - void onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name); - void avatarNameResolved(const LLUUID &id, const std::string &name); - static void predAvatarNameResolution(const LLVivoxVoiceClient::sessionStatePtr_t &session, LLUUID id, std::string name); - - boost::signals2::connection mAvatarNameCacheConnection; - - ///////////////////////////// - // Voice fonts - - void addVoiceFont(const S32 id, - const std::string &name, - const std::string &description, - const LLDate &expiration_date, - bool has_expired, - const S32 font_type, - const S32 font_status, - const bool template_font = false); - void accountGetSessionFontsResponse(int statusCode, const std::string &statusString); - void accountGetTemplateFontsResponse(int statusCode, const std::string &statusString); - -private: - - LLVoiceVersionInfo mVoiceVersion; - - // Coroutine support methods - //--- - void voiceControlCoro(); - void voiceControlStateMachine(S32 &coro_state); - - bool endAndDisconnectSession(); - - bool callbackEndDaemon(const LLSD& data); - bool startAndLaunchDaemon(); - bool provisionVoiceAccount(); - bool establishVoiceConnection(); - bool breakVoiceConnection(bool wait); - bool loginToVivox(); - void logoutOfVivox(bool wait); - bool retrieveVoiceFonts(); - - bool requestParcelVoiceInfo(); - - bool addAndJoinSession(const sessionStatePtr_t &nextSession); - bool terminateAudioSession(bool wait); - - bool waitForChannel(); - bool runSession(const sessionStatePtr_t &session); - - void recordingAndPlaybackMode(); - int voiceRecordBuffer(); - int voicePlaybackBuffer(); - - bool performMicTuning(); - //--- - /// Clean up objects created during a voice session. - void cleanUp(); - - bool mSessionTerminateRequested; - bool mRelogRequested; - // Number of times (in a row) "stateJoiningSession" case for spatial channel is reached in stateMachine(). - // The larger it is the greater is possibility there is a problem with connection to voice server. - // Introduced while fixing EXT-4313. - int mSpatialJoiningNum; - - static void idle(void *user_data); - - LLHost mDaemonHost; - LLSocket::ptr_t mSocket; - - // We should kill the voice daemon in case of connection alert - bool mTerminateDaemon; - - friend class LLVivoxProtocolParser; - - std::string mAccountName; - std::string mAccountPassword; - std::string mAccountDisplayName; - - bool mTuningMode; - float mTuningEnergy; - std::string mTuningAudioFile; - int mTuningMicVolume; - bool mTuningMicVolumeDirty; - int mTuningSpeakerVolume; - bool mTuningSpeakerVolumeDirty; - bool mDevicesListUpdated; // set to true when the device list has been updated - // and false when the panelvoicedevicesettings has queried for an update status. - - std::string mSpatialSessionURI; - std::string mSpatialSessionCredentials; - - std::string mMainSessionGroupHandle; // handle of the "main" session group. - - std::string mChannelName; // Name of the channel to be looked up - bool mAreaVoiceDisabled; - sessionStatePtr_t mAudioSession; // Session state for the current audio session - bool mAudioSessionChanged; // set to true when the above pointer gets changed, so observers can be notified. - - sessionStatePtr_t mNextAudioSession; // Session state for the audio session we're trying to join - - S32 mCurrentParcelLocalID; // Used to detect parcel boundary crossings - std::string mCurrentRegionName; // Used to detect parcel boundary crossings - - bool mConnectorEstablished; // set by "Create Connector" response - bool mAccountLoggedIn; // set by login message - int mNumberOfAliases; - U32 mCommandCookie; - - std::string mVoiceAccountServerURI; - std::string mVoiceSIPURIHostName; - - int mLoginRetryCount; - - sessionMap mSessionsByHandle; // Active sessions, indexed by session handle. Sessions which are being initiated may not be in this map. -#if 0 - sessionSet mSessions; // All sessions, not indexed. This is the canonical session list. -#endif - - bool mBuddyListMapPopulated; - bool mBlockRulesListReceived; - bool mAutoAcceptRulesListReceived; - buddyListMap mBuddyListMap; - - LLVoiceDeviceList mCaptureDevices; - LLVoiceDeviceList mRenderDevices; - - std::string mCaptureDevice; - std::string mRenderDevice; - bool mCaptureDeviceDirty; - bool mRenderDeviceDirty; - - bool mIsInitialized; - bool mShutdownComplete; - - bool checkParcelChanged(bool update = false); - bool switchChannel(std::string uri = std::string(), bool spatial = true, bool no_reconnect = false, bool is_p2p = false, std::string hash = ""); - void joinSession(const sessionStatePtr_t &session); - - std::string nameFromAvatar(LLVOAvatar *avatar); - std::string nameFromID(const LLUUID &id); - bool IDFromName(const std::string name, LLUUID &uuid); - std::string displayNameFromAvatar(LLVOAvatar *avatar); - std::string sipURIFromAvatar(LLVOAvatar *avatar); - std::string sipURIFromName(std::string &name); - - // Returns the name portion of the SIP URI if the string looks vaguely like a SIP URI, or an empty string if not. - std::string nameFromsipURI(const std::string &uri); - - bool inSpatialChannel(void); - std::string getAudioSessionURI(); - std::string getAudioSessionHandle(); - - void setHidden(bool hidden) override; //virtual - void sendPositionAndVolumeUpdate(void); - - void sendCaptureAndRenderDevices(); - void buildSetCaptureDevice(std::ostringstream &stream); - void buildSetRenderDevice(std::ostringstream &stream); - - - void sendFriendsListUpdates(); - -#if 0 - // start a text IM session with the specified user - // This will be asynchronous, the session may be established at a future time. - sessionStatePtr_t startUserIMSession(const LLUUID& uuid); -#endif - - void enforceTether(void); - - bool mSpatialCoordsDirty; - - LLVector3d mCameraPosition; - LLVector3d mCameraRequestedPosition; - LLVector3 mCameraVelocity; - LLMatrix3 mCameraRot; - - LLVector3d mAvatarPosition; - LLVector3 mAvatarVelocity; - LLQuaternion mAvatarRot; - - bool mMuteMic; - bool mMuteMicDirty; - bool mHidden; //Set to true during teleport to hide the agent's position. - - // Set to true when the friends list is known to have changed. - bool mFriendsListDirty; - - enum - { - earLocCamera = 0, // ear at camera - earLocAvatar, // ear at avatar - earLocMixed // ear at avatar location/camera direction - }; - - S32 mEarLocation; - - bool mSpeakerVolumeDirty; - bool mSpeakerMuteDirty; - int mSpeakerVolume; - - int mMicVolume; - bool mMicVolumeDirty; - - bool mVoiceEnabled; - bool mWriteInProgress; - std::string mWriteString; - size_t mWriteOffset; - - bool mLipSyncEnabled; - - typedef std::set observer_set_t; - observer_set_t mParticipantObservers; - - void notifyParticipantObservers(); - - typedef std::set status_observer_set_t; - status_observer_set_t mStatusObservers; - - void notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status); - - typedef std::set friend_observer_set_t; - friend_observer_set_t mFriendObservers; - void notifyFriendObservers(); - - // Voice Fonts - - void expireVoiceFonts(); - void deleteVoiceFont(const LLUUID& id); - void deleteAllVoiceFonts(); - void deleteVoiceFontTemplates(); - - S32 getVoiceFontIndex(const LLUUID& id) const; - S32 getVoiceFontTemplateIndex(const LLUUID& id) const; - - void accountGetSessionFontsSendMessage(); - void accountGetTemplateFontsSendMessage(); - void sessionSetVoiceFontSendMessage(const sessionStatePtr_t &session); - - void updateVoiceMorphingMenu(); - void notifyVoiceFontObservers(); - - typedef enum e_voice_font_type - { - VOICE_FONT_TYPE_NONE = 0, - VOICE_FONT_TYPE_ROOT = 1, - VOICE_FONT_TYPE_USER = 2, - VOICE_FONT_TYPE_UNKNOWN - } EVoiceFontType; - - typedef enum e_voice_font_status - { - VOICE_FONT_STATUS_NONE = 0, - VOICE_FONT_STATUS_FREE = 1, - VOICE_FONT_STATUS_NOT_FREE = 2, - VOICE_FONT_STATUS_UNKNOWN - } EVoiceFontStatus; - - struct voiceFontEntry - { - voiceFontEntry(LLUUID& id); - ~voiceFontEntry(); - - LLUUID mID; - S32 mFontIndex; - std::string mName; - LLDate mExpirationDate; - S32 mFontType; - S32 mFontStatus; - bool mIsNew; - - LLFrameTimer mExpiryTimer; - LLFrameTimer mExpiryWarningTimer; - }; - - bool mVoiceFontsReceived; - bool mVoiceFontsNew; - bool mVoiceFontListDirty; - voice_effect_list_t mVoiceFontList; - voice_effect_list_t mVoiceFontTemplateList; - - typedef std::map voice_font_map_t; - voice_font_map_t mVoiceFontMap; - voice_font_map_t mVoiceFontTemplateMap; - - typedef std::set voice_font_observer_set_t; - voice_font_observer_set_t mVoiceFontObservers; - - LLFrameTimer mVoiceFontExpiryTimer; - - - // Audio capture buffer - - void captureBufferRecordStartSendMessage(); - void captureBufferRecordStopSendMessage(); - void captureBufferPlayStartSendMessage(const LLUUID& voice_font_id = LLUUID::null); - void captureBufferPlayStopSendMessage(); - - bool mCaptureBufferMode; // Disconnected from voice channels while using the capture buffer. - bool mCaptureBufferRecording; // A voice sample is being captured. - bool mCaptureBufferRecorded; // A voice sample is captured in the buffer ready to play. - bool mCaptureBufferPlaying; // A voice sample is being played. - - LLTimer mCaptureTimer; - LLUUID mPreviewVoiceFont; - LLUUID mPreviewVoiceFontLast; - S32 mPlayRequestCount; - bool mIsInTuningMode; - bool mIsInChannel; - bool mIsJoiningSession; - bool mIsWaitingForFonts; - bool mIsLoggingIn; - bool mIsLoggedIn; - bool mIsProcessingChannels; - bool mIsCoroutineActive; - - // This variables can last longer than vivox in coroutines so we need them as static - static bool sShuttingDown; - static bool sConnected; - static LLPumpIO* sPump; - - LLEventMailDrop mVivoxPump; -}; - - -/** - * @class LLVivoxProtocolParser - * @brief This class helps construct new LLIOPipe specializations - * @see LLIOPipe - * - * THOROUGH_DESCRIPTION - */ -class LLVivoxProtocolParser : public LLIOPipe -{ - LOG_CLASS(LLVivoxProtocolParser); -public: - LLVivoxProtocolParser(); - virtual ~LLVivoxProtocolParser(); - -protected: - /* @name LLIOPipe virtual implementations - */ - //@{ - /** - * @brief Process the data in buffer - */ - virtual EStatus process_impl( - const LLChannelDescriptors& channels, - buffer_ptr_t& buffer, - bool& eos, - LLSD& context, - LLPumpIO* pump); - //@} - - std::string mInput; - - // Expat control members - XML_Parser parser; - int responseDepth; - bool ignoringTags; - bool isEvent; - int ignoreDepth; - - // Members for processing responses. The values are transient and only valid within a call to processResponse(). - int returnCode; - int statusCode; - std::string statusString; - std::string requestId; - std::string actionString; - std::string connectorHandle; - std::string versionID; - std::string mBuildID; - std::string accountHandle; - std::string sessionHandle; - std::string sessionGroupHandle; - std::string alias; - std::string applicationString; - - // Members for processing events. The values are transient and only valid within a call to processResponse(). - std::string eventTypeString; - int state; - std::string uriString; - bool isChannel; - bool incoming; - bool enabled; - std::string nameString; - std::string audioMediaString; - std::string deviceString; - std::string displayNameString; - int participantType; - bool isLocallyMuted; - bool isModeratorMuted; - bool isSpeaking; - int volume; - F32 energy; - std::string messageHeader; - std::string messageBody; - std::string notificationType; - bool hasText; - bool hasAudio; - bool hasVideo; - bool terminated; - std::string blockMask; - std::string presenceOnly; - std::string autoAcceptMask; - std::string autoAddAsBuddy; - int numberOfAliases; - std::string subscriptionHandle; - std::string subscriptionType; - S32 id; - std::string descriptionString; - LLDate expirationDate; - bool hasExpired; - S32 fontType; - S32 fontStatus; - std::string mediaCompletionType; - - // Members for processing text between tags - std::string textBuffer; - bool accumulateText; - - void reset(); - - void processResponse(std::string tag); - - static void XMLCALL ExpatStartTag(void *data, const char *el, const char **attr); - static void XMLCALL ExpatEndTag(void *data, const char *el); - static void XMLCALL ExpatCharHandler(void *data, const XML_Char *s, int len); - - void StartTag(const char *tag, const char **attr); - void EndTag(const char *tag); - void CharData(const char *buffer, int length); - LLDate expiryTimeStampToLLDate(const std::string& vivox_ts); - -}; - -class LLVivoxSecurity : public LLSingleton -{ - LLSINGLETON(LLVivoxSecurity); - virtual ~LLVivoxSecurity(); - - public: - std::string connectorHandle() { return mConnectorHandle; }; - std::string accountHandle() { return mAccountHandle; }; - - private: - std::string mConnectorHandle; - std::string mAccountHandle; -}; - -class LLVoiceVivoxStats : public LLSingleton -{ - LLSINGLETON(LLVoiceVivoxStats); - LOG_CLASS(LLVoiceVivoxStats); - virtual ~LLVoiceVivoxStats(); - - private: - F64SecondsImplicit mStartTime; - - U32 mConnectCycles; - - F64 mConnectTime; - U32 mConnectAttempts; - - F64 mProvisionTime; - U32 mProvisionAttempts; - - F64 mEstablishTime; - U32 mEstablishAttempts; - - public: - - void reset(); - void connectionAttemptStart(); - void connectionAttemptEnd(bool success); - void provisionAttemptStart(); - void provisionAttemptEnd(bool success); - void establishAttemptStart(); - void establishAttemptEnd(bool success); - LLSD read(); -}; - -#endif //LL_VIVOX_VOICE_CLIENT_H - +/** + * @file llvoicevivox.h + * @brief Declaration of LLDiamondwareVoiceClient class which is the interface to the voice client process. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#ifndef LL_VOICE_VIVOX_H +#define LL_VOICE_VIVOX_H + +class LLVOAvatar; +class LLVivoxProtocolParser; + +#include "lliopipe.h" +#include "llpumpio.h" +#include "llchainio.h" +#include "lliosocket.h" +#include "v3math.h" +#include "llframetimer.h" +#include "llviewerregion.h" +#include "llcallingcard.h" // for LLFriendObserver +#include "lleventcoro.h" +#include "llcoros.h" +#include + +#ifdef LL_USESYSTEMLIBS +# include "expat.h" +#else +# include "expat/expat.h" +#endif +#include "llvoiceclient.h" + +class LLAvatarName; +class LLVivoxVoiceClientMuteListObserver; + + +class LLVivoxVoiceClient : public LLSingleton, + virtual public LLVoiceModuleInterface, + virtual public LLVoiceEffectInterface +{ + LLSINGLETON(LLVivoxVoiceClient); + LOG_CLASS(LLVivoxVoiceClient); + virtual ~LLVivoxVoiceClient(); + +public: + /// @name LLVoiceModuleInterface virtual implementations + /// @see LLVoiceModuleInterface + //@{ + virtual void init(LLPumpIO *pump) override; // Call this once at application startup (creates connector) + virtual void terminate() override; // Call this to clean up during shutdown + + virtual const LLVoiceVersionInfo& getVersion() override; + + virtual void updateSettings() override; // call after loading settings and whenever they change + + // Returns true if vivox has successfully logged in and is not in error state + virtual bool isVoiceWorking() const override; + + ///////////////////// + /// @name Tuning + //@{ + virtual void tuningStart() override; + virtual void tuningStop() override; + virtual bool inTuningMode() override; + + virtual void tuningSetMicVolume(float volume) override; + virtual void tuningSetSpeakerVolume(float volume) override; + virtual float tuningGetEnergy(void) override; + //@} + + ///////////////////// + /// @name Devices + //@{ + // This returns true when it's safe to bring up the "device settings" dialog in the prefs. + // i.e. when the daemon is running and connected, and the device lists are populated. + virtual bool deviceSettingsAvailable() override; + virtual bool deviceSettingsUpdated() override; //return if the list has been updated and never fetched, only to be called from the voicepanel. + + // Requery the vivox daemon for the current list of input/output devices. + // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed + // (use this if you want to know when it's done). + // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim. + virtual void refreshDeviceLists(bool clearCurrentList = true) override; + + virtual void setCaptureDevice(const std::string& name) override; + virtual void setRenderDevice(const std::string& name) override; + + virtual LLVoiceDeviceList& getCaptureDevices() override; + virtual LLVoiceDeviceList& getRenderDevices() override; + //@} + + virtual void getParticipantList(std::set &participants) override; + virtual bool isParticipant(const LLUUID& speaker_id) override; + + // Send a text message to the specified user, initiating the session if necessary. + // virtual bool sendTextMessage(const LLUUID& participant_id, const std::string& message) const {return false;}; + + // close any existing text IM session with the specified user + virtual void endUserIMSession(const LLUUID &uuid) override; + + // Returns true if calling back the session URI after the session has closed is possible. + // Currently this will be false only for PSTN P2P calls. + // NOTE: this will return true if the session can't be found. + virtual bool isSessionCallBackPossible(const LLUUID &session_id) override; + + // Returns true if the session can accepte text IM's. + // Currently this will be false only for PSTN P2P calls. + // NOTE: this will return true if the session can't be found. + virtual bool isSessionTextIMPossible(const LLUUID &session_id) override; + + + //////////////////////////// + /// @name Channel stuff + //@{ + // returns true iff the user is currently in a proximal (local spatial) channel. + // Note that gestures should only fire if this returns true. + virtual bool inProximalChannel() override; + + virtual void setNonSpatialChannel(const std::string &uri, + const std::string &credentials) override; + + virtual bool setSpatialChannel(const std::string &uri, + const std::string &credentials) override; + + virtual void leaveNonSpatialChannel() override; + + virtual void leaveChannel(void) override; + + // Returns the URI of the current channel, or an empty string if not currently in a channel. + // NOTE that it will return an empty string if it's in the process of joining a channel. + virtual std::string getCurrentChannel() override; + //@} + + + ////////////////////////// + /// @name invitations + //@{ + // start a voice channel with the specified user + virtual void callUser(const LLUUID &uuid) override; + virtual bool isValidChannel(std::string &channelHandle) override; + virtual bool answerInvite(std::string &channelHandle) override; + virtual void declineInvite(std::string &channelHandle) override; + //@} + + ///////////////////////// + /// @name Volume/gain + //@{ + virtual void setVoiceVolume(F32 volume) override; + virtual void setMicGain(F32 volume) override; + //@} + + ///////////////////////// + /// @name enable disable voice and features + //@{ + virtual bool voiceEnabled() override; + virtual void setVoiceEnabled(bool enabled) override; + virtual bool lipSyncEnabled() override; + virtual void setLipSyncEnabled(bool enabled) override; + virtual void setMuteMic(bool muted) override; // Set the mute state of the local mic. + //@} + + ////////////////////////// + /// @name nearby speaker accessors + //@{ + virtual bool getVoiceEnabled(const LLUUID& id) override; // true if we've received data for this avatar + virtual std::string getDisplayName(const LLUUID& id) override; + virtual bool isParticipantAvatar(const LLUUID &id) override; + virtual bool getIsSpeaking(const LLUUID& id) override; + virtual bool getIsModeratorMuted(const LLUUID& id) override; + virtual F32 getCurrentPower(const LLUUID& id) override; // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is... + virtual bool getOnMuteList(const LLUUID& id) override; + virtual F32 getUserVolume(const LLUUID& id) override; + virtual void setUserVolume(const LLUUID& id, F32 volume) override; // set's volume for specified agent, from 0-1 (where .5 is nominal) + //@} + + // authorize the user + virtual void userAuthorized(const std::string& user_id, + const LLUUID &agentID) override; + + ////////////////////////////// + /// @name Status notification + //@{ + virtual void addObserver(LLVoiceClientStatusObserver* observer) override; + virtual void removeObserver(LLVoiceClientStatusObserver* observer) override; + virtual void addObserver(LLFriendObserver* observer) override; + virtual void removeObserver(LLFriendObserver* observer) override; + virtual void addObserver(LLVoiceClientParticipantObserver* observer) override; + virtual void removeObserver(LLVoiceClientParticipantObserver* observer) override; + //@} + + virtual std::string sipURIFromID(const LLUUID &id) override; + //@} + + /// @name LLVoiceEffectInterface virtual implementations + /// @see LLVoiceEffectInterface + //@{ + + ////////////////////////// + /// @name Accessors + //@{ + virtual bool setVoiceEffect(const LLUUID& id) override; + virtual const LLUUID getVoiceEffect() override; + virtual LLSD getVoiceEffectProperties(const LLUUID& id) override; + + virtual void refreshVoiceEffectLists(bool clear_lists) override; + virtual const voice_effect_list_t& getVoiceEffectList() const override; + virtual const voice_effect_list_t& getVoiceEffectTemplateList() const override; + //@} + + ////////////////////////////// + /// @name Status notification + //@{ + virtual void addObserver(LLVoiceEffectObserver* observer) override; + virtual void removeObserver(LLVoiceEffectObserver* observer) override; + //@} + + ////////////////////////////// + /// @name Effect preview buffer + //@{ + virtual void enablePreviewBuffer(bool enable) override; + virtual void recordPreviewBuffer() override; + virtual void playPreviewBuffer(const LLUUID& effect_id = LLUUID::null) override; + virtual void stopPreviewBuffer() override; + + virtual bool isPreviewRecording() override; + virtual bool isPreviewPlaying() override; + //@} + + //@} + + bool onCheckVoiceEffect(const std::string& voice_effect_name); + void onClickVoiceEffect(const std::string& voice_effect_name); + +protected: + ////////////////////// + // Vivox Specific definitions + + friend class LLVivoxVoiceClientMuteListObserver; + friend class LLVivoxVoiceClientFriendsObserver; + + + enum streamState + { + streamStateUnknown = 0, + streamStateIdle = 1, + streamStateConnected = 2, + streamStateRinging = 3, + streamStateConnecting = 6, // same as Vivox session_media_connecting enum + streamStateDisconnecting = 7, //Same as Vivox session_media_disconnecting enum + }; + + struct participantState + { + public: + participantState(const std::string &uri); + + bool updateMuteState(); // true if mute state has changed + bool isAvatar(); + + std::string mURI; + LLUUID mAvatarID; + std::string mAccountName; + std::string mDisplayName; + LLFrameTimer mSpeakingTimeout; + F32 mLastSpokeTimestamp; + F32 mPower; + F32 mVolume; + std::string mGroupID; + int mUserVolume; + bool mPTT; + bool mIsSpeaking; + bool mIsModeratorMuted; + bool mOnMuteList; // true if this avatar is on the user's mute list (and should be muted) + bool mVolumeSet; // true if incoming volume messages should not change the volume + bool mVolumeDirty; // true if this participant needs a volume command sent (either mOnMuteList or mUserVolume has changed) + bool mAvatarIDValid; + bool mIsSelf; + }; + typedef std::shared_ptr participantStatePtr_t; + typedef std::weak_ptr participantStateWptr_t; + + typedef std::map participantMap; + typedef std::map participantUUIDMap; + + struct sessionState + { + public: + typedef std::shared_ptr ptr_t; + typedef std::weak_ptr wptr_t; + + typedef std::function sessionFunc_t; + + static ptr_t createSession(); + ~sessionState(); + + participantStatePtr_t addParticipant(const std::string &uri); + void removeParticipant(const participantStatePtr_t &participant); + void removeAllParticipants(); + + participantStatePtr_t findParticipant(const std::string &uri); + participantStatePtr_t findParticipantByID(const LLUUID& id); + + static ptr_t matchSessionByHandle(const std::string &handle); + static ptr_t matchCreatingSessionByURI(const std::string &uri); + static ptr_t matchSessionByURI(const std::string &uri); + static ptr_t matchSessionByParticipant(const LLUUID &participant_id); + + bool isCallBackPossible(); + bool isTextIMPossible(); + + static void for_each(sessionFunc_t func); + + std::string mHandle; + std::string mGroupHandle; + std::string mSIPURI; + std::string mAlias; + std::string mName; + std::string mAlternateSIPURI; + std::string mHash; // Channel password + std::string mErrorStatusString; + std::queue mTextMsgQueue; + + LLUUID mIMSessionID; + LLUUID mCallerID; + int mErrorStatusCode; + int mMediaStreamState; + bool mCreateInProgress; // True if a Session.Create has been sent for this session and no response has been received yet. + bool mMediaConnectInProgress; // True if a Session.MediaConnect has been sent for this session and no response has been received yet. + bool mVoiceInvitePending; // True if a voice invite is pending for this session (usually waiting on a name lookup) + bool mTextInvitePending; // True if a text invite is pending for this session (usually waiting on a name lookup) + bool mSynthesizedCallerID; // True if the caller ID is a hash of the SIP URI -- this means we shouldn't do a name lookup. + bool mIsChannel; // True for both group and spatial channels (false for p2p, PSTN) + bool mIsSpatial; // True for spatial channels + bool mIsP2P; + bool mIncoming; + bool mVoiceActive; + bool mReconnect; // Whether we should try to reconnect to this session if it's dropped + + // Set to true when the volume/mute state of someone in the participant list changes. + // The code will have to walk the list to find the changed participant(s). + bool mVolumeDirty; + bool mMuteDirty; + + bool mParticipantsChanged; + participantMap mParticipantsByURI; + participantUUIDMap mParticipantsByUUID; + + LLUUID mVoiceFontID; + + static void VerifySessions(); + + private: + sessionState(); + + static std::set> mSession; // canonical list of outstanding sessions. + std::set::iterator mMyIterator; // used for delete + + static void for_eachPredicate(const wptr_t &a, sessionFunc_t func); + + static bool testByHandle(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string handle); + static bool testByCreatingURI(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string uri); + static bool testBySIPOrAlterateURI(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string uri); + static bool testByCallerId(const LLVivoxVoiceClient::sessionState::wptr_t &a, LLUUID participantId); + + }; + typedef std::shared_ptr sessionStatePtr_t; + + typedef std::map sessionMap; + + /////////////////////////////////////////////////////// + // Private Member Functions + ////////////////////////////////////////////////////// + + + + ////////////////////////////// + /// @name TVC/Server management and communication + //@{ + // Call this if the connection to the daemon terminates unexpectedly. It will attempt to reset everything and relaunch. + void daemonDied(); + + // Call this if we're just giving up on voice (can't provision an account, etc.). It will clean up and go away. + void giveUp(); + + // write to the tvc + bool writeString(const std::string &str); + + void connectorCreate(); + void connectorShutdown(); + void closeSocket(void); + +// void requestVoiceAccountProvision(S32 retries = 3); + void setLoginInfo( + const std::string& account_name, + const std::string& password, + const std::string& voice_sip_uri_hostname, + const std::string& voice_account_server_uri); + void loginSendMessage(); + void logout(); + void logoutSendMessage(); + + + //@} + + //------------------------------------ + // tuning + + void tuningRenderStartSendMessage(const std::string& name, bool loop); + void tuningRenderStopSendMessage(); + + void tuningCaptureStartSendMessage(int duration); + void tuningCaptureStopSendMessage(); + + //---------------------------------- + // devices + void clearCaptureDevices(); + void addCaptureDevice(const LLVoiceDevice& device); + void clearRenderDevices(); + void setDevicesListUpdated(bool state); + void addRenderDevice(const LLVoiceDevice& device); + void buildSetAudioDevices(std::ostringstream &stream); + + void getCaptureDevicesSendMessage(); + void getRenderDevicesSendMessage(); + + // local audio updates, mic mute, speaker mute, mic volume and speaker volumes + void sendLocalAudioUpdates(); + + ///////////////////////////// + // Response/Event handlers + void connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID); + void loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases); + void sessionCreateResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle); + void sessionGroupAddSessionResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle); + void sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString); + void logoutResponse(int statusCode, std::string &statusString); + void connectorShutdownResponse(int statusCode, std::string &statusString); + + void accountLoginStateChangeEvent(std::string &accountHandle, int statusCode, std::string &statusString, int state); + void mediaCompletionEvent(std::string &sessionGroupHandle, std::string &mediaCompletionType); + void mediaStreamUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, int statusCode, std::string &statusString, int state, bool incoming); + void sessionAddedEvent(std::string &uriString, std::string &alias, std::string &sessionHandle, std::string &sessionGroupHandle, bool isChannel, bool incoming, std::string &nameString, std::string &applicationString); + void sessionGroupAddedEvent(std::string &sessionGroupHandle); + void sessionRemovedEvent(std::string &sessionHandle, std::string &sessionGroupHandle); + void participantAddedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, std::string &nameString, std::string &displayNameString, int participantType); + void participantRemovedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, std::string &nameString); + void participantUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, bool isModeratorMuted, bool isSpeaking, int volume, F32 energy); + void voiceServiceConnectionStateChangedEvent(int statusCode, std::string &statusString, std::string &build_id); + void auxAudioPropertiesEvent(F32 energy); + void messageEvent(std::string &sessionHandle, std::string &uriString, std::string &alias, std::string &messageHeader, std::string &messageBody, std::string &applicationString); + void sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string ¬ificationType); + + void muteListChanged(); + + ///////////////////////////// + // VAD changes + // disable auto-VAD and configure VAD parameters explicitly + void setupVADParams(unsigned int vad_auto, unsigned int vad_hangover, unsigned int vad_noise_floor, unsigned int vad_sensitivity); + void onVADSettingsChange(); + + ///////////////////////////// + // Sending updates of current state + void updatePosition(void); + void setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot); + void setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLQuaternion &rot); + bool channelFromRegion(LLViewerRegion *region, std::string &name); + + void setEarLocation(S32 loc); + + + ///////////////////////////// + // Accessors for data related to nearby speakers + + // MBW -- XXX -- Not sure how to get this data out of the TVC + bool getUsingPTT(const LLUUID& id); + std::string getGroupID(const LLUUID& id); // group ID if the user is in group chat (empty string if not applicable) + + ///////////////////////////// + bool getAreaVoiceDisabled(); // returns true if the area the avatar is in is speech-disabled. + // Use this to determine whether to show a "no speech" icon in the menu bar. + + + ///////////////////////////// + // Recording controls + void recordingLoopStart(int seconds = 3600, int deltaFramesPerControlFrame = 200); + void recordingLoopSave(const std::string& filename); + void recordingStop(); + + // Playback controls + void filePlaybackStart(const std::string& filename); + void filePlaybackStop(); + void filePlaybackSetPaused(bool paused); + void filePlaybackSetMode(bool vox = false, float speed = 1.0f); + + participantStatePtr_t findParticipantByID(const LLUUID& id); + + +#if 0 + //////////////////////////////////////// + // voice sessions. + typedef std::set sessionSet; + + typedef sessionSet::iterator sessionIterator; + sessionIterator sessionsBegin(void); + sessionIterator sessionsEnd(void); +#endif + + sessionStatePtr_t findSession(const std::string &handle); + sessionStatePtr_t findSessionBeingCreatedByURI(const std::string &uri); + sessionStatePtr_t findSession(const LLUUID &participant_id); + + sessionStatePtr_t addSession(const std::string &uri, const std::string &handle = std::string()); + void clearSessionHandle(const sessionStatePtr_t &session); + void setSessionHandle(const sessionStatePtr_t &session, const std::string &handle); + void setSessionURI(const sessionStatePtr_t &session, const std::string &uri); + void deleteSession(const sessionStatePtr_t &session); + void deleteAllSessions(void); + + void verifySessionState(void); + + void joinedAudioSession(const sessionStatePtr_t &session); + void leftAudioSession(const sessionStatePtr_t &session); + + // This is called in several places where the session _may_ need to be deleted. + // It contains logic for whether to delete the session or keep it around. + void reapSession(const sessionStatePtr_t &session); + + // Returns true if the session seems to indicate we've moved to a region on a different voice server + bool sessionNeedsRelog(const sessionStatePtr_t &session); + + + ////////////////////////////////////// + // buddy list stuff, needed for SLIM later + struct buddyListEntry + { + buddyListEntry(const std::string &uri); + std::string mURI; + std::string mDisplayName; + LLUUID mUUID; + bool mOnlineSL; + bool mOnlineSLim; + bool mCanSeeMeOnline; + bool mHasBlockListEntry; + bool mHasAutoAcceptListEntry; + bool mNameResolved; + bool mInSLFriends; + bool mInVivoxBuddies; + }; + + typedef std::map buddyListMap; + + ///////////////////////////// + // session control messages + + void accountListBlockRulesSendMessage(); + void accountListAutoAcceptRulesSendMessage(); + + void sessionGroupCreateSendMessage(); + void sessionCreateSendMessage(const sessionStatePtr_t &session, bool startAudio = true, bool startText = false); + void sessionGroupAddSessionSendMessage(const sessionStatePtr_t &session, bool startAudio = true, bool startText = false); + void sessionMediaConnectSendMessage(const sessionStatePtr_t &session); // just joins the audio session + void sessionTextConnectSendMessage(const sessionStatePtr_t &session); // just joins the text session + void sessionTerminateSendMessage(const sessionStatePtr_t &session); + void sessionGroupTerminateSendMessage(const sessionStatePtr_t &session); + void sessionMediaDisconnectSendMessage(const sessionStatePtr_t &session); + // void sessionTextDisconnectSendMessage(sessionState *session); + + + + // Pokes the state machine to leave the audio session next time around. + void sessionTerminate(); + + // Pokes the state machine to shut down the connector and restart it. + void requestRelog(); + + // Does the actual work to get out of the audio session + void leaveAudioSession(); + + friend class LLVivoxVoiceClientCapResponder; + + + void lookupName(const LLUUID &id); + void onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name); + void avatarNameResolved(const LLUUID &id, const std::string &name); + static void predAvatarNameResolution(const LLVivoxVoiceClient::sessionStatePtr_t &session, LLUUID id, std::string name); + + boost::signals2::connection mAvatarNameCacheConnection; + + ///////////////////////////// + // Voice fonts + + void addVoiceFont(const S32 id, + const std::string &name, + const std::string &description, + const LLDate &expiration_date, + bool has_expired, + const S32 font_type, + const S32 font_status, + const bool template_font = false); + void accountGetSessionFontsResponse(int statusCode, const std::string &statusString); + void accountGetTemplateFontsResponse(int statusCode, const std::string &statusString); + +private: + + LLVoiceVersionInfo mVoiceVersion; + + // Coroutine support methods + //--- + void voiceControlCoro(); + void voiceControlStateMachine(S32 &coro_state); + + bool endAndDisconnectSession(); + + bool callbackEndDaemon(const LLSD& data); + bool startAndLaunchDaemon(); + bool provisionVoiceAccount(); + bool establishVoiceConnection(); + bool breakVoiceConnection(bool wait); + bool loginToVivox(); + void logoutOfVivox(bool wait); + bool retrieveVoiceFonts(); + + bool requestParcelVoiceInfo(); + + bool addAndJoinSession(const sessionStatePtr_t &nextSession); + bool terminateAudioSession(bool wait); + + bool waitForChannel(); + bool runSession(const sessionStatePtr_t &session); + + void recordingAndPlaybackMode(); + int voiceRecordBuffer(); + int voicePlaybackBuffer(); + + bool performMicTuning(); + //--- + /// Clean up objects created during a voice session. + void cleanUp(); + + bool mSessionTerminateRequested; + bool mRelogRequested; + // Number of times (in a row) "stateJoiningSession" case for spatial channel is reached in stateMachine(). + // The larger it is the greater is possibility there is a problem with connection to voice server. + // Introduced while fixing EXT-4313. + int mSpatialJoiningNum; + + static void idle(void *user_data); + + LLHost mDaemonHost; + LLSocket::ptr_t mSocket; + + // We should kill the voice daemon in case of connection alert + bool mTerminateDaemon; + + friend class LLVivoxProtocolParser; + + std::string mAccountName; + std::string mAccountPassword; + std::string mAccountDisplayName; + + bool mTuningMode; + float mTuningEnergy; + std::string mTuningAudioFile; + int mTuningMicVolume; + bool mTuningMicVolumeDirty; + int mTuningSpeakerVolume; + bool mTuningSpeakerVolumeDirty; + bool mDevicesListUpdated; // set to true when the device list has been updated + // and false when the panelvoicedevicesettings has queried for an update status. + + std::string mSpatialSessionURI; + std::string mSpatialSessionCredentials; + + std::string mMainSessionGroupHandle; // handle of the "main" session group. + + std::string mChannelName; // Name of the channel to be looked up + bool mAreaVoiceDisabled; + sessionStatePtr_t mAudioSession; // Session state for the current audio session + bool mAudioSessionChanged; // set to true when the above pointer gets changed, so observers can be notified. + + sessionStatePtr_t mNextAudioSession; // Session state for the audio session we're trying to join + + S32 mCurrentParcelLocalID; // Used to detect parcel boundary crossings + std::string mCurrentRegionName; // Used to detect parcel boundary crossings + + bool mConnectorEstablished; // set by "Create Connector" response + bool mAccountLoggedIn; // set by login message + int mNumberOfAliases; + U32 mCommandCookie; + + std::string mVoiceAccountServerURI; + std::string mVoiceSIPURIHostName; + + int mLoginRetryCount; + + sessionMap mSessionsByHandle; // Active sessions, indexed by session handle. Sessions which are being initiated may not be in this map. +#if 0 + sessionSet mSessions; // All sessions, not indexed. This is the canonical session list. +#endif + + bool mBuddyListMapPopulated; + bool mBlockRulesListReceived; + bool mAutoAcceptRulesListReceived; + buddyListMap mBuddyListMap; + + LLVoiceDeviceList mCaptureDevices; + LLVoiceDeviceList mRenderDevices; + + std::string mCaptureDevice; + std::string mRenderDevice; + bool mCaptureDeviceDirty; + bool mRenderDeviceDirty; + + bool mIsInitialized; + bool mShutdownComplete; + + bool checkParcelChanged(bool update = false); + bool switchChannel(std::string uri = std::string(), bool spatial = true, bool no_reconnect = false, bool is_p2p = false, std::string hash = ""); + void joinSession(const sessionStatePtr_t &session); + + std::string nameFromAvatar(LLVOAvatar *avatar); + std::string nameFromID(const LLUUID &id); + bool IDFromName(const std::string name, LLUUID &uuid); + std::string displayNameFromAvatar(LLVOAvatar *avatar); + std::string sipURIFromAvatar(LLVOAvatar *avatar); + std::string sipURIFromName(std::string &name); + + // Returns the name portion of the SIP URI if the string looks vaguely like a SIP URI, or an empty string if not. + std::string nameFromsipURI(const std::string &uri); + + bool inSpatialChannel(void); + std::string getAudioSessionURI(); + std::string getAudioSessionHandle(); + + void setHidden(bool hidden) override; //virtual + void sendPositionAndVolumeUpdate(void); + + void sendCaptureAndRenderDevices(); + void buildSetCaptureDevice(std::ostringstream &stream); + void buildSetRenderDevice(std::ostringstream &stream); + + + void sendFriendsListUpdates(); + +#if 0 + // start a text IM session with the specified user + // This will be asynchronous, the session may be established at a future time. + sessionStatePtr_t startUserIMSession(const LLUUID& uuid); +#endif + + void enforceTether(void); + + bool mSpatialCoordsDirty; + + LLVector3d mCameraPosition; + LLVector3d mCameraRequestedPosition; + LLVector3 mCameraVelocity; + LLMatrix3 mCameraRot; + + LLVector3d mAvatarPosition; + LLVector3 mAvatarVelocity; + LLQuaternion mAvatarRot; + + bool mMuteMic; + bool mMuteMicDirty; + bool mHidden; //Set to true during teleport to hide the agent's position. + + // Set to true when the friends list is known to have changed. + bool mFriendsListDirty; + + enum + { + earLocCamera = 0, // ear at camera + earLocAvatar, // ear at avatar + earLocMixed // ear at avatar location/camera direction + }; + + S32 mEarLocation; + + bool mSpeakerVolumeDirty; + bool mSpeakerMuteDirty; + int mSpeakerVolume; + + int mMicVolume; + bool mMicVolumeDirty; + + bool mVoiceEnabled; + bool mWriteInProgress; + std::string mWriteString; + size_t mWriteOffset; + + bool mLipSyncEnabled; + + typedef std::set observer_set_t; + observer_set_t mParticipantObservers; + + void notifyParticipantObservers(); + + typedef std::set status_observer_set_t; + status_observer_set_t mStatusObservers; + + void notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status); + + typedef std::set friend_observer_set_t; + friend_observer_set_t mFriendObservers; + void notifyFriendObservers(); + + // Voice Fonts + + void expireVoiceFonts(); + void deleteVoiceFont(const LLUUID& id); + void deleteAllVoiceFonts(); + void deleteVoiceFontTemplates(); + + S32 getVoiceFontIndex(const LLUUID& id) const; + S32 getVoiceFontTemplateIndex(const LLUUID& id) const; + + void accountGetSessionFontsSendMessage(); + void accountGetTemplateFontsSendMessage(); + void sessionSetVoiceFontSendMessage(const sessionStatePtr_t &session); + + void updateVoiceMorphingMenu(); + void notifyVoiceFontObservers(); + + typedef enum e_voice_font_type + { + VOICE_FONT_TYPE_NONE = 0, + VOICE_FONT_TYPE_ROOT = 1, + VOICE_FONT_TYPE_USER = 2, + VOICE_FONT_TYPE_UNKNOWN + } EVoiceFontType; + + typedef enum e_voice_font_status + { + VOICE_FONT_STATUS_NONE = 0, + VOICE_FONT_STATUS_FREE = 1, + VOICE_FONT_STATUS_NOT_FREE = 2, + VOICE_FONT_STATUS_UNKNOWN + } EVoiceFontStatus; + + struct voiceFontEntry + { + voiceFontEntry(LLUUID& id); + ~voiceFontEntry(); + + LLUUID mID; + S32 mFontIndex; + std::string mName; + LLDate mExpirationDate; + S32 mFontType; + S32 mFontStatus; + bool mIsNew; + + LLFrameTimer mExpiryTimer; + LLFrameTimer mExpiryWarningTimer; + }; + + bool mVoiceFontsReceived; + bool mVoiceFontsNew; + bool mVoiceFontListDirty; + voice_effect_list_t mVoiceFontList; + voice_effect_list_t mVoiceFontTemplateList; + + typedef std::map voice_font_map_t; + voice_font_map_t mVoiceFontMap; + voice_font_map_t mVoiceFontTemplateMap; + + typedef std::set voice_font_observer_set_t; + voice_font_observer_set_t mVoiceFontObservers; + + LLFrameTimer mVoiceFontExpiryTimer; + + + // Audio capture buffer + + void captureBufferRecordStartSendMessage(); + void captureBufferRecordStopSendMessage(); + void captureBufferPlayStartSendMessage(const LLUUID& voice_font_id = LLUUID::null); + void captureBufferPlayStopSendMessage(); + + bool mCaptureBufferMode; // Disconnected from voice channels while using the capture buffer. + bool mCaptureBufferRecording; // A voice sample is being captured. + bool mCaptureBufferRecorded; // A voice sample is captured in the buffer ready to play. + bool mCaptureBufferPlaying; // A voice sample is being played. + + LLTimer mCaptureTimer; + LLUUID mPreviewVoiceFont; + LLUUID mPreviewVoiceFontLast; + S32 mPlayRequestCount; + bool mIsInTuningMode; + bool mIsInChannel; + bool mIsJoiningSession; + bool mIsWaitingForFonts; + bool mIsLoggingIn; + bool mIsLoggedIn; + bool mIsProcessingChannels; + bool mIsCoroutineActive; + + // This variables can last longer than vivox in coroutines so we need them as static + static bool sShuttingDown; + static bool sConnected; + static LLPumpIO* sPump; + + LLEventMailDrop mVivoxPump; +}; + + +/** + * @class LLVivoxProtocolParser + * @brief This class helps construct new LLIOPipe specializations + * @see LLIOPipe + * + * THOROUGH_DESCRIPTION + */ +class LLVivoxProtocolParser : public LLIOPipe +{ + LOG_CLASS(LLVivoxProtocolParser); +public: + LLVivoxProtocolParser(); + virtual ~LLVivoxProtocolParser(); + +protected: + /* @name LLIOPipe virtual implementations + */ + //@{ + /** + * @brief Process the data in buffer + */ + virtual EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} + + std::string mInput; + + // Expat control members + XML_Parser parser; + int responseDepth; + bool ignoringTags; + bool isEvent; + int ignoreDepth; + + // Members for processing responses. The values are transient and only valid within a call to processResponse(). + int returnCode; + int statusCode; + std::string statusString; + std::string requestId; + std::string actionString; + std::string connectorHandle; + std::string versionID; + std::string mBuildID; + std::string accountHandle; + std::string sessionHandle; + std::string sessionGroupHandle; + std::string alias; + std::string applicationString; + + // Members for processing events. The values are transient and only valid within a call to processResponse(). + std::string eventTypeString; + int state; + std::string uriString; + bool isChannel; + bool incoming; + bool enabled; + std::string nameString; + std::string audioMediaString; + std::string deviceString; + std::string displayNameString; + int participantType; + bool isLocallyMuted; + bool isModeratorMuted; + bool isSpeaking; + int volume; + F32 energy; + std::string messageHeader; + std::string messageBody; + std::string notificationType; + bool hasText; + bool hasAudio; + bool hasVideo; + bool terminated; + std::string blockMask; + std::string presenceOnly; + std::string autoAcceptMask; + std::string autoAddAsBuddy; + int numberOfAliases; + std::string subscriptionHandle; + std::string subscriptionType; + S32 id; + std::string descriptionString; + LLDate expirationDate; + bool hasExpired; + S32 fontType; + S32 fontStatus; + std::string mediaCompletionType; + + // Members for processing text between tags + std::string textBuffer; + bool accumulateText; + + void reset(); + + void processResponse(std::string tag); + + static void XMLCALL ExpatStartTag(void *data, const char *el, const char **attr); + static void XMLCALL ExpatEndTag(void *data, const char *el); + static void XMLCALL ExpatCharHandler(void *data, const XML_Char *s, int len); + + void StartTag(const char *tag, const char **attr); + void EndTag(const char *tag); + void CharData(const char *buffer, int length); + LLDate expiryTimeStampToLLDate(const std::string& vivox_ts); + +}; + +class LLVivoxSecurity : public LLSingleton +{ + LLSINGLETON(LLVivoxSecurity); + virtual ~LLVivoxSecurity(); + + public: + std::string connectorHandle() { return mConnectorHandle; }; + std::string accountHandle() { return mAccountHandle; }; + + private: + std::string mConnectorHandle; + std::string mAccountHandle; +}; + +class LLVoiceVivoxStats : public LLSingleton +{ + LLSINGLETON(LLVoiceVivoxStats); + LOG_CLASS(LLVoiceVivoxStats); + virtual ~LLVoiceVivoxStats(); + + private: + F64SecondsImplicit mStartTime; + + U32 mConnectCycles; + + F64 mConnectTime; + U32 mConnectAttempts; + + F64 mProvisionTime; + U32 mProvisionAttempts; + + F64 mEstablishTime; + U32 mEstablishAttempts; + + public: + + void reset(); + void connectionAttemptStart(); + void connectionAttemptEnd(bool success); + void provisionAttemptStart(); + void provisionAttemptEnd(bool success); + void establishAttemptStart(); + void establishAttemptEnd(bool success); + LLSD read(); +}; + +#endif //LL_VIVOX_VOICE_CLIENT_H + diff --git a/indra/newview/llvopartgroup.cpp b/indra/newview/llvopartgroup.cpp index 68281f2276..fa4dbd586e 100644 --- a/indra/newview/llvopartgroup.cpp +++ b/indra/newview/llvopartgroup.cpp @@ -1,984 +1,984 @@ -/** - * @file llvopartgroup.cpp - * @brief Group of particle systems - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llvopartgroup.h" - -#include "lldrawpoolalpha.h" - -#include "llfasttimer.h" -#include "message.h" -#include "v2math.h" - -#include "llagentcamera.h" -#include "lldrawable.h" -#include "llface.h" -#include "llsky.h" -#include "llviewercamera.h" -#include "llviewerpartsim.h" -#include "llviewerregion.h" -#include "pipeline.h" -#include "llspatialpartition.h" - -extern U64MicrosecondsImplicit gFrameTime; - -void LLVOPartGroup::initClass() -{ -} - -//static -void LLVOPartGroup::restoreGL() -{ - - //TODO: optimize out binormal mask here. Specular and normal coords as well. -#if 0 - sVB = new LLVertexBuffer(VERTEX_DATA_MASK | LLVertexBuffer::MAP_TANGENT | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TEXCOORD2); - U32 count = LL_MAX_PARTICLE_COUNT; - if (!sVB->allocateBuffer(count*4, count*6)) - { - LL_WARNS() << "Failed to allocate Vertex Buffer to " - << count*4 << " vertices and " - << count * 6 << " indices" << LL_ENDL; - // we are likelly to crash at following getTexCoord0Strider(), so unref and return - sVB = NULL; - return; - } - - //indices and texcoords are always the same, set once - LLStrider indicesp; - - LLStrider verticesp; - - sVB->getIndexStrider(indicesp); - sVB->getVertexStrider(verticesp); - - LLVector4a v; - v.set(0,0,0,0); - - - U16 vert_offset = 0; - - for (U32 i = 0; i < LL_MAX_PARTICLE_COUNT; i++) - { - *indicesp++ = vert_offset + 0; - *indicesp++ = vert_offset + 1; - *indicesp++ = vert_offset + 2; - - *indicesp++ = vert_offset + 1; - *indicesp++ = vert_offset + 3; - *indicesp++ = vert_offset + 2; - - *verticesp++ = v; - - vert_offset += 4; - } - - LLStrider texcoordsp; - sVB->getTexCoord0Strider(texcoordsp); - - for (U32 i = 0; i < LL_MAX_PARTICLE_COUNT; i++) - { - *texcoordsp++ = LLVector2(0.f, 1.f); - *texcoordsp++ = LLVector2(0.f, 0.f); - *texcoordsp++ = LLVector2(1.f, 1.f); - *texcoordsp++ = LLVector2(1.f, 0.f); - } - - sVB->unmapBuffer(); -#endif - -} - -//static -void LLVOPartGroup::destroyGL() -{ -} - -bool ll_is_part_idx_allocated(S32 idx, S32* start, S32* end) -{ - /*while (start < end) - { - if (*start == idx) - { //not allocated (in free list) - return false; - } - ++start; - }*/ - - //allocated (not in free list) - return false; -} - -LLVOPartGroup::LLVOPartGroup(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) - : LLAlphaObject(id, pcode, regionp), - mViewerPartGroupp(NULL) -{ - setNumTEs(1); - setTETexture(0, LLUUID::null); - mbCanSelect = false; // users can't select particle systems -} - - -LLVOPartGroup::~LLVOPartGroup() -{ -} - -bool LLVOPartGroup::isActive() const -{ - return false; -} - -F32 LLVOPartGroup::getBinRadius() -{ - return mViewerPartGroupp->getBoxSide(); -} - -void LLVOPartGroup::updateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax) -{ - const LLVector3& pos_agent = getPositionAgent(); - - LLVector4a scale; - LLVector4a p; - - p.load3(pos_agent.mV); - - scale.splat(mScale.mV[0]+mViewerPartGroupp->getBoxSide()*0.5f); - - newMin.setSub(p, scale); - newMax.setAdd(p,scale); - - llassert(newMin.isFinite3()); - llassert(newMax.isFinite3()); - - llassert(p.isFinite3()); - mDrawable->setPositionGroup(p); -} - -void LLVOPartGroup::idleUpdate(LLAgent &agent, const F64 &time) -{ -} - -void LLVOPartGroup::setPixelAreaAndAngle(LLAgent &agent) -{ - // mPixelArea is calculated during render - F32 mid_scale = getMidScale(); - F32 range = (getRenderPosition()-LLViewerCamera::getInstance()->getOrigin()).length(); - - if (range < 0.001f || isHUDAttachment()) // range == zero - { - mAppAngle = 180.f; - } - else - { - mAppAngle = (F32) atan2( mid_scale, range) * RAD_TO_DEG; - } -} - -void LLVOPartGroup::updateTextures() -{ - // Texture stats for particles need to be updated in a different way... -} - - -LLDrawable* LLVOPartGroup::createDrawable(LLPipeline *pipeline) -{ - pipeline->allocDrawable(this); - mDrawable->setLit(false); - mDrawable->setRenderType(LLPipeline::RENDER_TYPE_PARTICLES); - return mDrawable; -} - - const F32 MAX_PARTICLE_AREA_SCALE = 0.02f; // some tuned constant, limits on how much particle area to draw - - LLUUID LLVOPartGroup::getPartOwner(S32 idx) - { - LLUUID ret = LLUUID::null; - - if (idx < (S32) mViewerPartGroupp->mParticles.size()) - { - ret = mViewerPartGroupp->mParticles[idx]->mPartSourcep->getOwnerUUID(); - } - - return ret; - } - - LLUUID LLVOPartGroup::getPartSource(S32 idx) - { - LLUUID ret = LLUUID::null; - - if (idx < (S32) mViewerPartGroupp->mParticles.size()) - { - LLViewerPart* part = mViewerPartGroupp->mParticles[idx]; - if (part && part->mPartSourcep.notNull() && - part->mPartSourcep->mSourceObjectp.notNull()) - { - LLViewerObject* source = part->mPartSourcep->mSourceObjectp; - ret = source->getID(); - } - } - - return ret; - } - - -F32 LLVOPartGroup::getPartSize(S32 idx) -{ - if (idx < (S32) mViewerPartGroupp->mParticles.size()) - { - return mViewerPartGroupp->mParticles[idx]->mScale.mV[0]; - } - - return 0.f; -} - -void LLVOPartGroup::getBlendFunc(S32 idx, LLRender::eBlendFactor& src, LLRender::eBlendFactor& dst) -{ - if (idx < (S32) mViewerPartGroupp->mParticles.size()) - { - LLViewerPart* part = mViewerPartGroupp->mParticles[idx]; - src = (LLRender::eBlendFactor) part->mBlendFuncSource; - dst = (LLRender::eBlendFactor) part->mBlendFuncDest; - } -} - -LLVector3 LLVOPartGroup::getCameraPosition() const -{ - return gAgentCamera.getCameraPositionAgent(); -} - -bool LLVOPartGroup::updateGeometry(LLDrawable *drawable) -{ - LL_PROFILE_ZONE_SCOPED; - - dirtySpatialGroup(); - - S32 num_parts = mViewerPartGroupp->getCount(); - LLFace *facep; - LLSpatialGroup* group = drawable->getSpatialGroup(); - if (!group && num_parts) - { - drawable->movePartition(); - group = drawable->getSpatialGroup(); - } - - if (group && group->isVisible()) - { - dirtySpatialGroup(); - } - - if (!num_parts) - { - if (group && drawable->getNumFaces()) - { - group->setState(LLSpatialGroup::GEOM_DIRTY); - } - drawable->setNumFaces(0, NULL, getTEImage(0)); - LLPipeline::sCompiles++; - return true; - } - - if (!(gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_PARTICLES))) - { - return true; - } - - if (num_parts > drawable->getNumFaces()) - { - drawable->setNumFacesFast(num_parts+num_parts/4, NULL, getTEImage(0)); - } - - F32 tot_area = 0; - - F32 max_area = LLViewerPartSim::getMaxPartCount() * MAX_PARTICLE_AREA_SCALE; - F32 pixel_meter_ratio = LLViewerCamera::getInstance()->getPixelMeterRatio(); - pixel_meter_ratio *= pixel_meter_ratio; - - LLViewerPartSim::checkParticleCount(mViewerPartGroupp->mParticles.size()) ; - - S32 count=0; - mDepth = 0.f; - S32 i = 0 ; - LLVector3 camera_agent = getCameraPosition(); - - F32 max_scale = 0.f; - - - for (i = 0 ; i < (S32)mViewerPartGroupp->mParticles.size(); i++) - { - const LLViewerPart *part = mViewerPartGroupp->mParticles[i]; - - - //remember the largest particle - max_scale = llmax(max_scale, part->mScale.mV[0], part->mScale.mV[1]); - - if (part->mFlags & LLPartData::LL_PART_RIBBON_MASK) - { //include ribbon segment length in scale - const LLVector3* pos_agent = NULL; - if (part->mParent) - { - pos_agent = &(part->mParent->mPosAgent); - } - else if (part->mPartSourcep.notNull()) - { - pos_agent = &(part->mPartSourcep->mPosAgent); - } - - if (pos_agent) - { - F32 dist = (*pos_agent-part->mPosAgent).length(); - - max_scale = llmax(max_scale, dist); - } - } - - LLVector3 part_pos_agent(part->mPosAgent); - LLVector3 at(part_pos_agent - camera_agent); - - - F32 camera_dist_squared = at.lengthSquared(); - F32 inv_camera_dist_squared; - if (camera_dist_squared > 1.f) - inv_camera_dist_squared = 1.f / camera_dist_squared; - else - inv_camera_dist_squared = 1.f; - - llassert(llfinite(inv_camera_dist_squared)); - llassert(!llisnan(inv_camera_dist_squared)); - - F32 area = part->mScale.mV[0] * part->mScale.mV[1] * inv_camera_dist_squared; - tot_area = llmax(tot_area, area); - - if (tot_area > max_area) - { - break; - } - - count++; - - facep = drawable->getFace(i); - if (!facep) - { - LL_WARNS() << "No face found for index " << i << "!" << LL_ENDL; - continue; - } - - facep->setTEOffset(i); - const F32 NEAR_PART_DIST_SQ = 5.f*5.f; // Only discard particles > 5 m from the camera - const F32 MIN_PART_AREA = .005f*.005f; // only less than 5 mm x 5 mm at 1 m from camera - - if (camera_dist_squared > NEAR_PART_DIST_SQ && area < MIN_PART_AREA) - { - facep->setSize(0, 0); - continue; - } - - facep->setSize(4, 6); - - facep->setViewerObject(this); - - if (part->mFlags & LLPartData::LL_PART_EMISSIVE_MASK) - { - facep->setState(LLFace::FULLBRIGHT); - } - else - { - facep->clearState(LLFace::FULLBRIGHT); - } - - facep->mCenterLocal = part->mPosAgent; - facep->setFaceColor(part->mColor); - facep->setTexture(part->mImagep); - - //check if this particle texture is replaced by a parcel media texture. - if(part->mImagep.notNull() && part->mImagep->hasParcelMedia()) - { - part->mImagep->getParcelMedia()->addMediaToFace(facep) ; - } - - mPixelArea = tot_area * pixel_meter_ratio; - const F32 area_scale = 10.f; // scale area to increase priority a bit - facep->setVirtualSize(mPixelArea*area_scale); - } - for (i = count; i < drawable->getNumFaces(); i++) - { - LLFace* facep = drawable->getFace(i); - if (!facep) - { - LL_WARNS() << "No face found for index " << i << "!" << LL_ENDL; - continue; - } - facep->setTEOffset(i); - facep->setSize(0, 0); - } - - //record max scale (used to stretch bounding box for visibility culling) - - mScale.set(max_scale, max_scale, max_scale); - - mDrawable->movePartition(); - LLPipeline::sCompiles++; - return true; -} - - -bool LLVOPartGroup::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, - S32 face, - bool pick_transparent, - bool pick_rigged, - bool pick_unselectable, - S32* face_hit, - LLVector4a* intersection, - LLVector2* tex_coord, - LLVector4a* normal, - LLVector4a* bi_normal) -{ - LLVector4a dir; - dir.setSub(end, start); - - F32 closest_t = 2.f; - bool ret = false; - - for (U32 idx = 0; idx < mViewerPartGroupp->mParticles.size(); ++idx) - { - const LLViewerPart &part = *((LLViewerPart*) (mViewerPartGroupp->mParticles[idx])); - - LLVector4a v[4]; - LLStrider verticesp; - verticesp = v; - - getGeometry(part, verticesp); - - F32 a,b,t; - if (LLTriangleRayIntersect(v[0], v[1], v[2], start, dir, a,b,t) || - LLTriangleRayIntersect(v[1], v[3], v[2], start, dir, a,b,t)) - { - if (t >= 0.f && - t <= 1.f && - t < closest_t) - { - ret = true; - closest_t = t; - if (face_hit) - { - *face_hit = idx; - } - - if (intersection) - { - LLVector4a intersect = dir; - intersect.mul(closest_t); - intersection->setAdd(intersect, start); - } - } - } - } - - return ret; -} - -void LLVOPartGroup::getGeometry(const LLViewerPart& part, - LLStrider& verticesp) -{ - if (part.mFlags & LLPartData::LL_PART_RIBBON_MASK) - { - LLVector4a axis, pos, paxis, ppos; - F32 scale, pscale; - - pos.load3(part.mPosAgent.mV); - axis.load3(part.mAxis.mV); - scale = part.mScale.mV[0]; - - if (part.mParent) - { - ppos.load3(part.mParent->mPosAgent.mV); - paxis.load3(part.mParent->mAxis.mV); - pscale = part.mParent->mScale.mV[0]; - } - else - { //use source object as position - - if (part.mPartSourcep->mSourceObjectp.notNull()) - { - LLVector3 v = LLVector3(0,0,1); - v *= part.mPartSourcep->mSourceObjectp->getRenderRotation(); - paxis.load3(v.mV); - ppos.load3(part.mPartSourcep->mPosAgent.mV); - pscale = part.mStartScale.mV[0]; - } - else - { //no source object, no parent, nothing to draw - ppos = pos; - pscale = scale; - paxis = axis; - } - } - - LLVector4a p0, p1, p2, p3; - - scale *= 0.5f; - pscale *= 0.5f; - - axis.mul(scale); - paxis.mul(pscale); - - p0.setAdd(pos, axis); - p1.setSub(pos,axis); - p2.setAdd(ppos, paxis); - p3.setSub(ppos, paxis); - - (*verticesp++) = p2; - (*verticesp++) = p3; - (*verticesp++) = p0; - (*verticesp++) = p1; - } - else - { - LLVector4a part_pos_agent; - part_pos_agent.load3(part.mPosAgent.mV); - LLVector4a camera_agent; - camera_agent.load3(getCameraPosition().mV); - LLVector4a at; - at.setSub(part_pos_agent, camera_agent); - LLVector4a up(0, 0, 1); - LLVector4a right; - - right.setCross3(at, up); - right.normalize3fast(); - - up.setCross3(right, at); - up.normalize3fast(); - - if (part.mFlags & LLPartData::LL_PART_FOLLOW_VELOCITY_MASK && !part.mVelocity.isExactlyZero()) - { - LLVector4a normvel; - normvel.load3(part.mVelocity.mV); - normvel.normalize3fast(); - LLVector2 up_fracs; - up_fracs.mV[0] = normvel.dot3(right).getF32(); - up_fracs.mV[1] = normvel.dot3(up).getF32(); - up_fracs.normalize(); - LLVector4a new_up; - LLVector4a new_right; - - //new_up = up_fracs.mV[0] * right + up_fracs.mV[1]*up; - LLVector4a t = right; - t.mul(up_fracs.mV[0]); - new_up = up; - new_up.mul(up_fracs.mV[1]); - new_up.add(t); - - //new_right = up_fracs.mV[1] * right - up_fracs.mV[0]*up; - t = right; - t.mul(up_fracs.mV[1]); - new_right = up; - new_right.mul(up_fracs.mV[0]); - t.sub(new_right); - - up = new_up; - right = t; - up.normalize3fast(); - right.normalize3fast(); - } - - right.mul(0.5f*part.mScale.mV[0]); - up.mul(0.5f*part.mScale.mV[1]); - - - //HACK -- the verticesp->mV[3] = 0.f here are to set the texture index to 0 (particles don't use texture batching, maybe they should) - // this works because there is actually a 4th float stored after the vertex position which is used as a texture index - // also, somebody please VECTORIZE THIS - - LLVector4a ppapu; - LLVector4a ppamu; - - ppapu.setAdd(part_pos_agent, up); - ppamu.setSub(part_pos_agent, up); - - verticesp->setSub(ppapu, right); - (*verticesp++).getF32ptr()[3] = 0.f; - verticesp->setSub(ppamu, right); - (*verticesp++).getF32ptr()[3] = 0.f; - verticesp->setAdd(ppapu, right); - (*verticesp++).getF32ptr()[3] = 0.f; - verticesp->setAdd(ppamu, right); - (*verticesp++).getF32ptr()[3] = 0.f; - } -} - - - -void LLVOPartGroup::getGeometry(S32 idx, - LLStrider& verticesp, - LLStrider& normalsp, - LLStrider& texcoordsp, - LLStrider& colorsp, - LLStrider& emissivep, - LLStrider& indicesp) -{ - if (idx >= (S32) mViewerPartGroupp->mParticles.size()) - { - return; - } - - const LLViewerPart &part = *((LLViewerPart*) (mViewerPartGroupp->mParticles[idx])); - - getGeometry(part, verticesp); - - LLColor4U pcolor; - LLColor4U color = part.mColor; - - LLColor4U pglow; - - if (part.mFlags & LLPartData::LL_PART_RIBBON_MASK) - { //make sure color blends properly - if (part.mParent) - { - pglow = part.mParent->mGlow; - pcolor = part.mParent->mColor; - } - else - { - pglow = LLColor4U(0, 0, 0, (U8) ll_round(255.f*part.mStartGlow)); - pcolor = part.mStartColor; - } - } - else - { - pglow = part.mGlow; - pcolor = color; - } - - *colorsp++ = pcolor; - *colorsp++ = pcolor; - *colorsp++ = color; - *colorsp++ = color; - - //if (pglow.mV[3] || part.mGlow.mV[3]) - { //only write glow if it is not zero - *emissivep++ = pglow; - *emissivep++ = pglow; - *emissivep++ = part.mGlow; - *emissivep++ = part.mGlow; - } - - - if (!(part.mFlags & LLPartData::LL_PART_EMISSIVE_MASK)) - { //not fullbright, needs normal - LLVector3 normal = -LLViewerCamera::getInstance()->getXAxis(); - *normalsp++ = normal; - *normalsp++ = normal; - *normalsp++ = normal; - *normalsp++ = normal; - } -} - -U32 LLVOPartGroup::getPartitionType() const -{ - return LLViewerRegion::PARTITION_PARTICLE; -} - -LLParticlePartition::LLParticlePartition(LLViewerRegion* regionp) -: LLSpatialPartition(LLDrawPoolAlpha::VERTEX_DATA_MASK | LLVertexBuffer::MAP_TEXTURE_INDEX, true, regionp) -{ - mRenderPass = LLRenderPass::PASS_ALPHA; - mDrawableType = LLPipeline::RENDER_TYPE_PARTICLES; - mPartitionType = LLViewerRegion::PARTITION_PARTICLE; - mSlopRatio = 0.f; - mLODPeriod = 1; -} - -LLHUDParticlePartition::LLHUDParticlePartition(LLViewerRegion* regionp) : - LLParticlePartition(regionp) -{ - mDrawableType = LLPipeline::RENDER_TYPE_HUD_PARTICLES; - mPartitionType = LLViewerRegion::PARTITION_HUD_PARTICLE; -} - -void LLParticlePartition::rebuildGeom(LLSpatialGroup* group) -{ - LL_PROFILE_ZONE_SCOPED; - LL_PROFILE_GPU_ZONE("particle vbo"); - if (group->isDead() || !group->hasState(LLSpatialGroup::GEOM_DIRTY)) - { - return; - } - - if (group->changeLOD()) - { - group->mLastUpdateDistance = group->mDistance; - group->mLastUpdateViewAngle = group->mViewAngle; - } - - group->clearDrawMap(); - - //get geometry count - U32 index_count = 0; - U32 vertex_count = 0; - - addGeometryCount(group, vertex_count, index_count); - - - if (vertex_count > 0 && index_count > 0) - { - group->mBuilt = 1.f; - if (group->mVertexBuffer.isNull() || - group->mVertexBuffer->getNumVerts() < vertex_count || group->mVertexBuffer->getNumIndices() < index_count) - { - group->mVertexBuffer = new LLVertexBuffer(LLVOPartGroup::VERTEX_DATA_MASK); - group->mVertexBuffer->allocateBuffer(vertex_count, index_count); - - // initialize index and texture coordinates only when buffer is reallocated - U16* indicesp = (U16*)group->mVertexBuffer->mapIndexBuffer(0, index_count); - - U16 geom_idx = 0; - for (U32 i = 0; i < index_count; i += 6) - { - *indicesp++ = geom_idx + 0; - *indicesp++ = geom_idx + 1; - *indicesp++ = geom_idx + 2; - - *indicesp++ = geom_idx + 1; - *indicesp++ = geom_idx + 3; - *indicesp++ = geom_idx + 2; - - geom_idx += 4; - } - - LLStrider texcoordsp; - - group->mVertexBuffer->getTexCoord0Strider(texcoordsp); - - for (U32 i = 0; i < vertex_count; i += 4) - { - *texcoordsp++ = LLVector2(0.f, 1.f); - *texcoordsp++ = LLVector2(0.f, 0.f); - *texcoordsp++ = LLVector2(1.f, 1.f); - *texcoordsp++ = LLVector2(1.f, 0.f); - } - - } - - - getGeometry(group); - } - else - { - group->mVertexBuffer = NULL; - group->mBufferMap.clear(); - } - - group->mLastUpdateTime = gFrameTimeSeconds; - group->clearState(LLSpatialGroup::GEOM_DIRTY); -} - -void LLParticlePartition::addGeometryCount(LLSpatialGroup* group, U32& vertex_count, U32& index_count) -{ - mFaceList.clear(); - - LLViewerCamera* camera = LLViewerCamera::getInstance(); - for (LLSpatialGroup::element_iter i = group->getDataBegin(); i != group->getDataEnd(); ++i) - { - LLDrawable* drawablep = (LLDrawable*)(*i)->getDrawable(); - - if (!drawablep || drawablep->isDead()) - { - continue; - } - - LLAlphaObject* obj = (LLAlphaObject*) drawablep->getVObj().get(); - obj->mDepth = 0.f; - - U32 count = 0; - for (S32 j = 0; j < drawablep->getNumFaces(); ++j) - { - drawablep->updateFaceSize(j); - - LLFace* facep = drawablep->getFace(j); - if ( !facep || !facep->hasGeometry()) - { - continue; - } - - vertex_count += facep->getGeomCount(); - index_count += facep->getIndicesCount(); - - count++; - facep->mDistance = (facep->mCenterLocal - camera->getOrigin()) * camera->getAtAxis(); - obj->mDepth += facep->mDistance; - - mFaceList.push_back(facep); - llassert(facep->getIndicesCount() < 65536); - } - - obj->mDepth /= count; - } -} - - -void LLParticlePartition::getGeometry(LLSpatialGroup* group) -{ - LL_PROFILE_ZONE_SCOPED; - - std::sort(mFaceList.begin(), mFaceList.end(), LLFace::CompareDistanceGreater()); - - group->clearDrawMap(); - - LLVertexBuffer* buffer = group->mVertexBuffer; - - LLStrider verticesp; - LLStrider normalsp; - LLStrider colorsp; - LLStrider emissivep; - - buffer->getVertexStrider(verticesp); - buffer->getNormalStrider(normalsp); - buffer->getColorStrider(colorsp); - buffer->getEmissiveStrider(emissivep); - - S32 geom_idx = 0; - S32 indices_idx = 0; - - LLSpatialGroup::drawmap_elem_t& draw_vec = group->mDrawMap[mRenderPass]; - - for (std::vector::iterator i = mFaceList.begin(); i != mFaceList.end(); ++i) - { - LLFace* facep = *i; - LLAlphaObject* object = (LLAlphaObject*) facep->getViewerObject(); - - facep->setGeomIndex(geom_idx); - facep->setIndicesIndex(indices_idx); - - LLStrider cur_vert = verticesp + geom_idx; - LLStrider cur_norm = normalsp + geom_idx; - LLStrider cur_col = colorsp + geom_idx; - LLStrider cur_glow = emissivep + geom_idx; - - // not actually used - LLStrider cur_tc; - LLStrider cur_idx; - - - geom_idx += 4; - indices_idx += 6; - - LLColor4U* start_glow = cur_glow.get(); - - object->getGeometry(facep->getTEOffset(), cur_vert, cur_norm, cur_tc, cur_col, cur_glow, cur_idx); - - bool has_glow = false; - - if (cur_glow.get() != start_glow) - { - has_glow = true; - } - - llassert(facep->getGeomCount() == 4); - llassert(facep->getIndicesCount() == 6); - - S32 idx = draw_vec.size()-1; - - bool fullbright = facep->isState(LLFace::FULLBRIGHT); - - bool batched = false; - - LLRender::eBlendFactor bf_src = LLRender::BF_SOURCE_ALPHA; - LLRender::eBlendFactor bf_dst = LLRender::BF_ONE_MINUS_SOURCE_ALPHA; - - object->getBlendFunc(facep->getTEOffset(), bf_src, bf_dst); - - - if (idx >= 0) - { - LLDrawInfo* info = draw_vec[idx]; - - if (info->mTexture == facep->getTexture() && - info->mHasGlow == has_glow && - info->mFullbright == fullbright && - info->mBlendFuncDst == bf_dst && - info->mBlendFuncSrc == bf_src) - { - if (draw_vec[idx]->mEnd == facep->getGeomIndex()-1) - { - batched = true; - info->mCount += facep->getIndicesCount(); - info->mEnd += facep->getGeomCount(); - } - else if (draw_vec[idx]->mStart == facep->getGeomIndex()+facep->getGeomCount()+1) - { - batched = true; - info->mCount += facep->getIndicesCount(); - info->mStart -= facep->getGeomCount(); - info->mOffset = facep->getIndicesStart(); - } - } - } - - if (!batched) - { - U32 start = facep->getGeomIndex(); - U32 end = start + facep->getGeomCount()-1; - U32 offset = facep->getIndicesStart(); - U32 count = facep->getIndicesCount(); - LLDrawInfo* info = new LLDrawInfo(start,end,count,offset,facep->getTexture(), - buffer, fullbright); - - info->mBlendFuncDst = bf_dst; - info->mBlendFuncSrc = bf_src; - info->mHasGlow = has_glow; - draw_vec.push_back(info); - //for alpha sorting - facep->setDrawInfo(info); - } - } - - buffer->unmapBuffer(); - mFaceList.clear(); -} - -F32 LLParticlePartition::calcPixelArea(LLSpatialGroup* group, LLCamera& camera) -{ - return 1024.f; -} - -U32 LLVOHUDPartGroup::getPartitionType() const -{ - return LLViewerRegion::PARTITION_HUD_PARTICLE; -} - -LLDrawable* LLVOHUDPartGroup::createDrawable(LLPipeline *pipeline) -{ - pipeline->allocDrawable(this); - mDrawable->setLit(false); - mDrawable->setRenderType(LLPipeline::RENDER_TYPE_HUD_PARTICLES); - return mDrawable; -} - -LLVector3 LLVOHUDPartGroup::getCameraPosition() const -{ - return LLVector3(-1,0,0); -} - +/** + * @file llvopartgroup.cpp + * @brief Group of particle systems + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llvopartgroup.h" + +#include "lldrawpoolalpha.h" + +#include "llfasttimer.h" +#include "message.h" +#include "v2math.h" + +#include "llagentcamera.h" +#include "lldrawable.h" +#include "llface.h" +#include "llsky.h" +#include "llviewercamera.h" +#include "llviewerpartsim.h" +#include "llviewerregion.h" +#include "pipeline.h" +#include "llspatialpartition.h" + +extern U64MicrosecondsImplicit gFrameTime; + +void LLVOPartGroup::initClass() +{ +} + +//static +void LLVOPartGroup::restoreGL() +{ + + //TODO: optimize out binormal mask here. Specular and normal coords as well. +#if 0 + sVB = new LLVertexBuffer(VERTEX_DATA_MASK | LLVertexBuffer::MAP_TANGENT | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TEXCOORD2); + U32 count = LL_MAX_PARTICLE_COUNT; + if (!sVB->allocateBuffer(count*4, count*6)) + { + LL_WARNS() << "Failed to allocate Vertex Buffer to " + << count*4 << " vertices and " + << count * 6 << " indices" << LL_ENDL; + // we are likelly to crash at following getTexCoord0Strider(), so unref and return + sVB = NULL; + return; + } + + //indices and texcoords are always the same, set once + LLStrider indicesp; + + LLStrider verticesp; + + sVB->getIndexStrider(indicesp); + sVB->getVertexStrider(verticesp); + + LLVector4a v; + v.set(0,0,0,0); + + + U16 vert_offset = 0; + + for (U32 i = 0; i < LL_MAX_PARTICLE_COUNT; i++) + { + *indicesp++ = vert_offset + 0; + *indicesp++ = vert_offset + 1; + *indicesp++ = vert_offset + 2; + + *indicesp++ = vert_offset + 1; + *indicesp++ = vert_offset + 3; + *indicesp++ = vert_offset + 2; + + *verticesp++ = v; + + vert_offset += 4; + } + + LLStrider texcoordsp; + sVB->getTexCoord0Strider(texcoordsp); + + for (U32 i = 0; i < LL_MAX_PARTICLE_COUNT; i++) + { + *texcoordsp++ = LLVector2(0.f, 1.f); + *texcoordsp++ = LLVector2(0.f, 0.f); + *texcoordsp++ = LLVector2(1.f, 1.f); + *texcoordsp++ = LLVector2(1.f, 0.f); + } + + sVB->unmapBuffer(); +#endif + +} + +//static +void LLVOPartGroup::destroyGL() +{ +} + +bool ll_is_part_idx_allocated(S32 idx, S32* start, S32* end) +{ + /*while (start < end) + { + if (*start == idx) + { //not allocated (in free list) + return false; + } + ++start; + }*/ + + //allocated (not in free list) + return false; +} + +LLVOPartGroup::LLVOPartGroup(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) + : LLAlphaObject(id, pcode, regionp), + mViewerPartGroupp(NULL) +{ + setNumTEs(1); + setTETexture(0, LLUUID::null); + mbCanSelect = false; // users can't select particle systems +} + + +LLVOPartGroup::~LLVOPartGroup() +{ +} + +bool LLVOPartGroup::isActive() const +{ + return false; +} + +F32 LLVOPartGroup::getBinRadius() +{ + return mViewerPartGroupp->getBoxSide(); +} + +void LLVOPartGroup::updateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax) +{ + const LLVector3& pos_agent = getPositionAgent(); + + LLVector4a scale; + LLVector4a p; + + p.load3(pos_agent.mV); + + scale.splat(mScale.mV[0]+mViewerPartGroupp->getBoxSide()*0.5f); + + newMin.setSub(p, scale); + newMax.setAdd(p,scale); + + llassert(newMin.isFinite3()); + llassert(newMax.isFinite3()); + + llassert(p.isFinite3()); + mDrawable->setPositionGroup(p); +} + +void LLVOPartGroup::idleUpdate(LLAgent &agent, const F64 &time) +{ +} + +void LLVOPartGroup::setPixelAreaAndAngle(LLAgent &agent) +{ + // mPixelArea is calculated during render + F32 mid_scale = getMidScale(); + F32 range = (getRenderPosition()-LLViewerCamera::getInstance()->getOrigin()).length(); + + if (range < 0.001f || isHUDAttachment()) // range == zero + { + mAppAngle = 180.f; + } + else + { + mAppAngle = (F32) atan2( mid_scale, range) * RAD_TO_DEG; + } +} + +void LLVOPartGroup::updateTextures() +{ + // Texture stats for particles need to be updated in a different way... +} + + +LLDrawable* LLVOPartGroup::createDrawable(LLPipeline *pipeline) +{ + pipeline->allocDrawable(this); + mDrawable->setLit(false); + mDrawable->setRenderType(LLPipeline::RENDER_TYPE_PARTICLES); + return mDrawable; +} + + const F32 MAX_PARTICLE_AREA_SCALE = 0.02f; // some tuned constant, limits on how much particle area to draw + + LLUUID LLVOPartGroup::getPartOwner(S32 idx) + { + LLUUID ret = LLUUID::null; + + if (idx < (S32) mViewerPartGroupp->mParticles.size()) + { + ret = mViewerPartGroupp->mParticles[idx]->mPartSourcep->getOwnerUUID(); + } + + return ret; + } + + LLUUID LLVOPartGroup::getPartSource(S32 idx) + { + LLUUID ret = LLUUID::null; + + if (idx < (S32) mViewerPartGroupp->mParticles.size()) + { + LLViewerPart* part = mViewerPartGroupp->mParticles[idx]; + if (part && part->mPartSourcep.notNull() && + part->mPartSourcep->mSourceObjectp.notNull()) + { + LLViewerObject* source = part->mPartSourcep->mSourceObjectp; + ret = source->getID(); + } + } + + return ret; + } + + +F32 LLVOPartGroup::getPartSize(S32 idx) +{ + if (idx < (S32) mViewerPartGroupp->mParticles.size()) + { + return mViewerPartGroupp->mParticles[idx]->mScale.mV[0]; + } + + return 0.f; +} + +void LLVOPartGroup::getBlendFunc(S32 idx, LLRender::eBlendFactor& src, LLRender::eBlendFactor& dst) +{ + if (idx < (S32) mViewerPartGroupp->mParticles.size()) + { + LLViewerPart* part = mViewerPartGroupp->mParticles[idx]; + src = (LLRender::eBlendFactor) part->mBlendFuncSource; + dst = (LLRender::eBlendFactor) part->mBlendFuncDest; + } +} + +LLVector3 LLVOPartGroup::getCameraPosition() const +{ + return gAgentCamera.getCameraPositionAgent(); +} + +bool LLVOPartGroup::updateGeometry(LLDrawable *drawable) +{ + LL_PROFILE_ZONE_SCOPED; + + dirtySpatialGroup(); + + S32 num_parts = mViewerPartGroupp->getCount(); + LLFace *facep; + LLSpatialGroup* group = drawable->getSpatialGroup(); + if (!group && num_parts) + { + drawable->movePartition(); + group = drawable->getSpatialGroup(); + } + + if (group && group->isVisible()) + { + dirtySpatialGroup(); + } + + if (!num_parts) + { + if (group && drawable->getNumFaces()) + { + group->setState(LLSpatialGroup::GEOM_DIRTY); + } + drawable->setNumFaces(0, NULL, getTEImage(0)); + LLPipeline::sCompiles++; + return true; + } + + if (!(gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_PARTICLES))) + { + return true; + } + + if (num_parts > drawable->getNumFaces()) + { + drawable->setNumFacesFast(num_parts+num_parts/4, NULL, getTEImage(0)); + } + + F32 tot_area = 0; + + F32 max_area = LLViewerPartSim::getMaxPartCount() * MAX_PARTICLE_AREA_SCALE; + F32 pixel_meter_ratio = LLViewerCamera::getInstance()->getPixelMeterRatio(); + pixel_meter_ratio *= pixel_meter_ratio; + + LLViewerPartSim::checkParticleCount(mViewerPartGroupp->mParticles.size()) ; + + S32 count=0; + mDepth = 0.f; + S32 i = 0 ; + LLVector3 camera_agent = getCameraPosition(); + + F32 max_scale = 0.f; + + + for (i = 0 ; i < (S32)mViewerPartGroupp->mParticles.size(); i++) + { + const LLViewerPart *part = mViewerPartGroupp->mParticles[i]; + + + //remember the largest particle + max_scale = llmax(max_scale, part->mScale.mV[0], part->mScale.mV[1]); + + if (part->mFlags & LLPartData::LL_PART_RIBBON_MASK) + { //include ribbon segment length in scale + const LLVector3* pos_agent = NULL; + if (part->mParent) + { + pos_agent = &(part->mParent->mPosAgent); + } + else if (part->mPartSourcep.notNull()) + { + pos_agent = &(part->mPartSourcep->mPosAgent); + } + + if (pos_agent) + { + F32 dist = (*pos_agent-part->mPosAgent).length(); + + max_scale = llmax(max_scale, dist); + } + } + + LLVector3 part_pos_agent(part->mPosAgent); + LLVector3 at(part_pos_agent - camera_agent); + + + F32 camera_dist_squared = at.lengthSquared(); + F32 inv_camera_dist_squared; + if (camera_dist_squared > 1.f) + inv_camera_dist_squared = 1.f / camera_dist_squared; + else + inv_camera_dist_squared = 1.f; + + llassert(llfinite(inv_camera_dist_squared)); + llassert(!llisnan(inv_camera_dist_squared)); + + F32 area = part->mScale.mV[0] * part->mScale.mV[1] * inv_camera_dist_squared; + tot_area = llmax(tot_area, area); + + if (tot_area > max_area) + { + break; + } + + count++; + + facep = drawable->getFace(i); + if (!facep) + { + LL_WARNS() << "No face found for index " << i << "!" << LL_ENDL; + continue; + } + + facep->setTEOffset(i); + const F32 NEAR_PART_DIST_SQ = 5.f*5.f; // Only discard particles > 5 m from the camera + const F32 MIN_PART_AREA = .005f*.005f; // only less than 5 mm x 5 mm at 1 m from camera + + if (camera_dist_squared > NEAR_PART_DIST_SQ && area < MIN_PART_AREA) + { + facep->setSize(0, 0); + continue; + } + + facep->setSize(4, 6); + + facep->setViewerObject(this); + + if (part->mFlags & LLPartData::LL_PART_EMISSIVE_MASK) + { + facep->setState(LLFace::FULLBRIGHT); + } + else + { + facep->clearState(LLFace::FULLBRIGHT); + } + + facep->mCenterLocal = part->mPosAgent; + facep->setFaceColor(part->mColor); + facep->setTexture(part->mImagep); + + //check if this particle texture is replaced by a parcel media texture. + if(part->mImagep.notNull() && part->mImagep->hasParcelMedia()) + { + part->mImagep->getParcelMedia()->addMediaToFace(facep) ; + } + + mPixelArea = tot_area * pixel_meter_ratio; + const F32 area_scale = 10.f; // scale area to increase priority a bit + facep->setVirtualSize(mPixelArea*area_scale); + } + for (i = count; i < drawable->getNumFaces(); i++) + { + LLFace* facep = drawable->getFace(i); + if (!facep) + { + LL_WARNS() << "No face found for index " << i << "!" << LL_ENDL; + continue; + } + facep->setTEOffset(i); + facep->setSize(0, 0); + } + + //record max scale (used to stretch bounding box for visibility culling) + + mScale.set(max_scale, max_scale, max_scale); + + mDrawable->movePartition(); + LLPipeline::sCompiles++; + return true; +} + + +bool LLVOPartGroup::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, + S32 face, + bool pick_transparent, + bool pick_rigged, + bool pick_unselectable, + S32* face_hit, + LLVector4a* intersection, + LLVector2* tex_coord, + LLVector4a* normal, + LLVector4a* bi_normal) +{ + LLVector4a dir; + dir.setSub(end, start); + + F32 closest_t = 2.f; + bool ret = false; + + for (U32 idx = 0; idx < mViewerPartGroupp->mParticles.size(); ++idx) + { + const LLViewerPart &part = *((LLViewerPart*) (mViewerPartGroupp->mParticles[idx])); + + LLVector4a v[4]; + LLStrider verticesp; + verticesp = v; + + getGeometry(part, verticesp); + + F32 a,b,t; + if (LLTriangleRayIntersect(v[0], v[1], v[2], start, dir, a,b,t) || + LLTriangleRayIntersect(v[1], v[3], v[2], start, dir, a,b,t)) + { + if (t >= 0.f && + t <= 1.f && + t < closest_t) + { + ret = true; + closest_t = t; + if (face_hit) + { + *face_hit = idx; + } + + if (intersection) + { + LLVector4a intersect = dir; + intersect.mul(closest_t); + intersection->setAdd(intersect, start); + } + } + } + } + + return ret; +} + +void LLVOPartGroup::getGeometry(const LLViewerPart& part, + LLStrider& verticesp) +{ + if (part.mFlags & LLPartData::LL_PART_RIBBON_MASK) + { + LLVector4a axis, pos, paxis, ppos; + F32 scale, pscale; + + pos.load3(part.mPosAgent.mV); + axis.load3(part.mAxis.mV); + scale = part.mScale.mV[0]; + + if (part.mParent) + { + ppos.load3(part.mParent->mPosAgent.mV); + paxis.load3(part.mParent->mAxis.mV); + pscale = part.mParent->mScale.mV[0]; + } + else + { //use source object as position + + if (part.mPartSourcep->mSourceObjectp.notNull()) + { + LLVector3 v = LLVector3(0,0,1); + v *= part.mPartSourcep->mSourceObjectp->getRenderRotation(); + paxis.load3(v.mV); + ppos.load3(part.mPartSourcep->mPosAgent.mV); + pscale = part.mStartScale.mV[0]; + } + else + { //no source object, no parent, nothing to draw + ppos = pos; + pscale = scale; + paxis = axis; + } + } + + LLVector4a p0, p1, p2, p3; + + scale *= 0.5f; + pscale *= 0.5f; + + axis.mul(scale); + paxis.mul(pscale); + + p0.setAdd(pos, axis); + p1.setSub(pos,axis); + p2.setAdd(ppos, paxis); + p3.setSub(ppos, paxis); + + (*verticesp++) = p2; + (*verticesp++) = p3; + (*verticesp++) = p0; + (*verticesp++) = p1; + } + else + { + LLVector4a part_pos_agent; + part_pos_agent.load3(part.mPosAgent.mV); + LLVector4a camera_agent; + camera_agent.load3(getCameraPosition().mV); + LLVector4a at; + at.setSub(part_pos_agent, camera_agent); + LLVector4a up(0, 0, 1); + LLVector4a right; + + right.setCross3(at, up); + right.normalize3fast(); + + up.setCross3(right, at); + up.normalize3fast(); + + if (part.mFlags & LLPartData::LL_PART_FOLLOW_VELOCITY_MASK && !part.mVelocity.isExactlyZero()) + { + LLVector4a normvel; + normvel.load3(part.mVelocity.mV); + normvel.normalize3fast(); + LLVector2 up_fracs; + up_fracs.mV[0] = normvel.dot3(right).getF32(); + up_fracs.mV[1] = normvel.dot3(up).getF32(); + up_fracs.normalize(); + LLVector4a new_up; + LLVector4a new_right; + + //new_up = up_fracs.mV[0] * right + up_fracs.mV[1]*up; + LLVector4a t = right; + t.mul(up_fracs.mV[0]); + new_up = up; + new_up.mul(up_fracs.mV[1]); + new_up.add(t); + + //new_right = up_fracs.mV[1] * right - up_fracs.mV[0]*up; + t = right; + t.mul(up_fracs.mV[1]); + new_right = up; + new_right.mul(up_fracs.mV[0]); + t.sub(new_right); + + up = new_up; + right = t; + up.normalize3fast(); + right.normalize3fast(); + } + + right.mul(0.5f*part.mScale.mV[0]); + up.mul(0.5f*part.mScale.mV[1]); + + + //HACK -- the verticesp->mV[3] = 0.f here are to set the texture index to 0 (particles don't use texture batching, maybe they should) + // this works because there is actually a 4th float stored after the vertex position which is used as a texture index + // also, somebody please VECTORIZE THIS + + LLVector4a ppapu; + LLVector4a ppamu; + + ppapu.setAdd(part_pos_agent, up); + ppamu.setSub(part_pos_agent, up); + + verticesp->setSub(ppapu, right); + (*verticesp++).getF32ptr()[3] = 0.f; + verticesp->setSub(ppamu, right); + (*verticesp++).getF32ptr()[3] = 0.f; + verticesp->setAdd(ppapu, right); + (*verticesp++).getF32ptr()[3] = 0.f; + verticesp->setAdd(ppamu, right); + (*verticesp++).getF32ptr()[3] = 0.f; + } +} + + + +void LLVOPartGroup::getGeometry(S32 idx, + LLStrider& verticesp, + LLStrider& normalsp, + LLStrider& texcoordsp, + LLStrider& colorsp, + LLStrider& emissivep, + LLStrider& indicesp) +{ + if (idx >= (S32) mViewerPartGroupp->mParticles.size()) + { + return; + } + + const LLViewerPart &part = *((LLViewerPart*) (mViewerPartGroupp->mParticles[idx])); + + getGeometry(part, verticesp); + + LLColor4U pcolor; + LLColor4U color = part.mColor; + + LLColor4U pglow; + + if (part.mFlags & LLPartData::LL_PART_RIBBON_MASK) + { //make sure color blends properly + if (part.mParent) + { + pglow = part.mParent->mGlow; + pcolor = part.mParent->mColor; + } + else + { + pglow = LLColor4U(0, 0, 0, (U8) ll_round(255.f*part.mStartGlow)); + pcolor = part.mStartColor; + } + } + else + { + pglow = part.mGlow; + pcolor = color; + } + + *colorsp++ = pcolor; + *colorsp++ = pcolor; + *colorsp++ = color; + *colorsp++ = color; + + //if (pglow.mV[3] || part.mGlow.mV[3]) + { //only write glow if it is not zero + *emissivep++ = pglow; + *emissivep++ = pglow; + *emissivep++ = part.mGlow; + *emissivep++ = part.mGlow; + } + + + if (!(part.mFlags & LLPartData::LL_PART_EMISSIVE_MASK)) + { //not fullbright, needs normal + LLVector3 normal = -LLViewerCamera::getInstance()->getXAxis(); + *normalsp++ = normal; + *normalsp++ = normal; + *normalsp++ = normal; + *normalsp++ = normal; + } +} + +U32 LLVOPartGroup::getPartitionType() const +{ + return LLViewerRegion::PARTITION_PARTICLE; +} + +LLParticlePartition::LLParticlePartition(LLViewerRegion* regionp) +: LLSpatialPartition(LLDrawPoolAlpha::VERTEX_DATA_MASK | LLVertexBuffer::MAP_TEXTURE_INDEX, true, regionp) +{ + mRenderPass = LLRenderPass::PASS_ALPHA; + mDrawableType = LLPipeline::RENDER_TYPE_PARTICLES; + mPartitionType = LLViewerRegion::PARTITION_PARTICLE; + mSlopRatio = 0.f; + mLODPeriod = 1; +} + +LLHUDParticlePartition::LLHUDParticlePartition(LLViewerRegion* regionp) : + LLParticlePartition(regionp) +{ + mDrawableType = LLPipeline::RENDER_TYPE_HUD_PARTICLES; + mPartitionType = LLViewerRegion::PARTITION_HUD_PARTICLE; +} + +void LLParticlePartition::rebuildGeom(LLSpatialGroup* group) +{ + LL_PROFILE_ZONE_SCOPED; + LL_PROFILE_GPU_ZONE("particle vbo"); + if (group->isDead() || !group->hasState(LLSpatialGroup::GEOM_DIRTY)) + { + return; + } + + if (group->changeLOD()) + { + group->mLastUpdateDistance = group->mDistance; + group->mLastUpdateViewAngle = group->mViewAngle; + } + + group->clearDrawMap(); + + //get geometry count + U32 index_count = 0; + U32 vertex_count = 0; + + addGeometryCount(group, vertex_count, index_count); + + + if (vertex_count > 0 && index_count > 0) + { + group->mBuilt = 1.f; + if (group->mVertexBuffer.isNull() || + group->mVertexBuffer->getNumVerts() < vertex_count || group->mVertexBuffer->getNumIndices() < index_count) + { + group->mVertexBuffer = new LLVertexBuffer(LLVOPartGroup::VERTEX_DATA_MASK); + group->mVertexBuffer->allocateBuffer(vertex_count, index_count); + + // initialize index and texture coordinates only when buffer is reallocated + U16* indicesp = (U16*)group->mVertexBuffer->mapIndexBuffer(0, index_count); + + U16 geom_idx = 0; + for (U32 i = 0; i < index_count; i += 6) + { + *indicesp++ = geom_idx + 0; + *indicesp++ = geom_idx + 1; + *indicesp++ = geom_idx + 2; + + *indicesp++ = geom_idx + 1; + *indicesp++ = geom_idx + 3; + *indicesp++ = geom_idx + 2; + + geom_idx += 4; + } + + LLStrider texcoordsp; + + group->mVertexBuffer->getTexCoord0Strider(texcoordsp); + + for (U32 i = 0; i < vertex_count; i += 4) + { + *texcoordsp++ = LLVector2(0.f, 1.f); + *texcoordsp++ = LLVector2(0.f, 0.f); + *texcoordsp++ = LLVector2(1.f, 1.f); + *texcoordsp++ = LLVector2(1.f, 0.f); + } + + } + + + getGeometry(group); + } + else + { + group->mVertexBuffer = NULL; + group->mBufferMap.clear(); + } + + group->mLastUpdateTime = gFrameTimeSeconds; + group->clearState(LLSpatialGroup::GEOM_DIRTY); +} + +void LLParticlePartition::addGeometryCount(LLSpatialGroup* group, U32& vertex_count, U32& index_count) +{ + mFaceList.clear(); + + LLViewerCamera* camera = LLViewerCamera::getInstance(); + for (LLSpatialGroup::element_iter i = group->getDataBegin(); i != group->getDataEnd(); ++i) + { + LLDrawable* drawablep = (LLDrawable*)(*i)->getDrawable(); + + if (!drawablep || drawablep->isDead()) + { + continue; + } + + LLAlphaObject* obj = (LLAlphaObject*) drawablep->getVObj().get(); + obj->mDepth = 0.f; + + U32 count = 0; + for (S32 j = 0; j < drawablep->getNumFaces(); ++j) + { + drawablep->updateFaceSize(j); + + LLFace* facep = drawablep->getFace(j); + if ( !facep || !facep->hasGeometry()) + { + continue; + } + + vertex_count += facep->getGeomCount(); + index_count += facep->getIndicesCount(); + + count++; + facep->mDistance = (facep->mCenterLocal - camera->getOrigin()) * camera->getAtAxis(); + obj->mDepth += facep->mDistance; + + mFaceList.push_back(facep); + llassert(facep->getIndicesCount() < 65536); + } + + obj->mDepth /= count; + } +} + + +void LLParticlePartition::getGeometry(LLSpatialGroup* group) +{ + LL_PROFILE_ZONE_SCOPED; + + std::sort(mFaceList.begin(), mFaceList.end(), LLFace::CompareDistanceGreater()); + + group->clearDrawMap(); + + LLVertexBuffer* buffer = group->mVertexBuffer; + + LLStrider verticesp; + LLStrider normalsp; + LLStrider colorsp; + LLStrider emissivep; + + buffer->getVertexStrider(verticesp); + buffer->getNormalStrider(normalsp); + buffer->getColorStrider(colorsp); + buffer->getEmissiveStrider(emissivep); + + S32 geom_idx = 0; + S32 indices_idx = 0; + + LLSpatialGroup::drawmap_elem_t& draw_vec = group->mDrawMap[mRenderPass]; + + for (std::vector::iterator i = mFaceList.begin(); i != mFaceList.end(); ++i) + { + LLFace* facep = *i; + LLAlphaObject* object = (LLAlphaObject*) facep->getViewerObject(); + + facep->setGeomIndex(geom_idx); + facep->setIndicesIndex(indices_idx); + + LLStrider cur_vert = verticesp + geom_idx; + LLStrider cur_norm = normalsp + geom_idx; + LLStrider cur_col = colorsp + geom_idx; + LLStrider cur_glow = emissivep + geom_idx; + + // not actually used + LLStrider cur_tc; + LLStrider cur_idx; + + + geom_idx += 4; + indices_idx += 6; + + LLColor4U* start_glow = cur_glow.get(); + + object->getGeometry(facep->getTEOffset(), cur_vert, cur_norm, cur_tc, cur_col, cur_glow, cur_idx); + + bool has_glow = false; + + if (cur_glow.get() != start_glow) + { + has_glow = true; + } + + llassert(facep->getGeomCount() == 4); + llassert(facep->getIndicesCount() == 6); + + S32 idx = draw_vec.size()-1; + + bool fullbright = facep->isState(LLFace::FULLBRIGHT); + + bool batched = false; + + LLRender::eBlendFactor bf_src = LLRender::BF_SOURCE_ALPHA; + LLRender::eBlendFactor bf_dst = LLRender::BF_ONE_MINUS_SOURCE_ALPHA; + + object->getBlendFunc(facep->getTEOffset(), bf_src, bf_dst); + + + if (idx >= 0) + { + LLDrawInfo* info = draw_vec[idx]; + + if (info->mTexture == facep->getTexture() && + info->mHasGlow == has_glow && + info->mFullbright == fullbright && + info->mBlendFuncDst == bf_dst && + info->mBlendFuncSrc == bf_src) + { + if (draw_vec[idx]->mEnd == facep->getGeomIndex()-1) + { + batched = true; + info->mCount += facep->getIndicesCount(); + info->mEnd += facep->getGeomCount(); + } + else if (draw_vec[idx]->mStart == facep->getGeomIndex()+facep->getGeomCount()+1) + { + batched = true; + info->mCount += facep->getIndicesCount(); + info->mStart -= facep->getGeomCount(); + info->mOffset = facep->getIndicesStart(); + } + } + } + + if (!batched) + { + U32 start = facep->getGeomIndex(); + U32 end = start + facep->getGeomCount()-1; + U32 offset = facep->getIndicesStart(); + U32 count = facep->getIndicesCount(); + LLDrawInfo* info = new LLDrawInfo(start,end,count,offset,facep->getTexture(), + buffer, fullbright); + + info->mBlendFuncDst = bf_dst; + info->mBlendFuncSrc = bf_src; + info->mHasGlow = has_glow; + draw_vec.push_back(info); + //for alpha sorting + facep->setDrawInfo(info); + } + } + + buffer->unmapBuffer(); + mFaceList.clear(); +} + +F32 LLParticlePartition::calcPixelArea(LLSpatialGroup* group, LLCamera& camera) +{ + return 1024.f; +} + +U32 LLVOHUDPartGroup::getPartitionType() const +{ + return LLViewerRegion::PARTITION_HUD_PARTICLE; +} + +LLDrawable* LLVOHUDPartGroup::createDrawable(LLPipeline *pipeline) +{ + pipeline->allocDrawable(this); + mDrawable->setLit(false); + mDrawable->setRenderType(LLPipeline::RENDER_TYPE_HUD_PARTICLES); + return mDrawable; +} + +LLVector3 LLVOHUDPartGroup::getCameraPosition() const +{ + return LLVector3(-1,0,0); +} + diff --git a/indra/newview/llvopartgroup.h b/indra/newview/llvopartgroup.h index 1b3b33ab46..20b4561374 100644 --- a/indra/newview/llvopartgroup.h +++ b/indra/newview/llvopartgroup.h @@ -1,125 +1,125 @@ -/** - * @file llvopartgroup.h - * @brief Group of particle systems - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVOPARTGROUP_H -#define LL_LLVOPARTGROUP_H - -#include "llviewerobject.h" -#include "v3math.h" -#include "v3color.h" -#include "llframetimer.h" -#include "llviewerpartsim.h" -#include "llvertexbuffer.h" - -class LLViewerPartGroup; - -class LLVOPartGroup : public LLAlphaObject -{ -public: - - static void initClass(); - static void restoreGL(); - static void destroyGL(); - - enum - { - VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX | - LLVertexBuffer::MAP_NORMAL | - LLVertexBuffer::MAP_TEXCOORD0 | - LLVertexBuffer::MAP_COLOR | - LLVertexBuffer::MAP_EMISSIVE | - LLVertexBuffer::MAP_TEXTURE_INDEX - }; - - LLVOPartGroup(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); - - /*virtual*/ bool isActive() const; // Whether this object needs to do an idleUpdate. - void idleUpdate(LLAgent &agent, const F64 &time); - - virtual F32 getBinRadius(); - virtual void updateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax); - virtual U32 getPartitionType() const; - - /*virtual*/ bool lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, - S32 face, - bool pick_transparent, - bool pick_rigged, - bool pick_unselectable, - S32* face_hit, - LLVector4a* intersection, - LLVector2* tex_coord, - LLVector4a* normal, - LLVector4a* tangent); - - /*virtual*/ void setPixelAreaAndAngle(LLAgent &agent); - /*virtual*/ void updateTextures(); - - /*virtual*/ LLDrawable* createDrawable(LLPipeline *pipeline); - /*virtual*/ bool updateGeometry(LLDrawable *drawable); - void getGeometry(const LLViewerPart& part, - LLStrider& verticesp); - - void getGeometry(S32 idx, - LLStrider& verticesp, - LLStrider& normalsp, - LLStrider& texcoordsp, - LLStrider& colorsp, - LLStrider& emissivep, - LLStrider& indicesp); - - void updateFaceSize(S32 idx) { } - F32 getPartSize(S32 idx); - void getBlendFunc(S32 idx, LLRender::eBlendFactor& src, LLRender::eBlendFactor& dst); - LLUUID getPartOwner(S32 idx); - LLUUID getPartSource(S32 idx); - - void setViewerPartGroup(LLViewerPartGroup *part_groupp) { mViewerPartGroupp = part_groupp; } - LLViewerPartGroup* getViewerPartGroup() { return mViewerPartGroupp; } - -protected: - ~LLVOPartGroup(); - - LLViewerPartGroup *mViewerPartGroupp; - - virtual LLVector3 getCameraPosition() const; - -}; - - -class LLVOHUDPartGroup : public LLVOPartGroup -{ -public: - LLVOHUDPartGroup(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) : - LLVOPartGroup(id, pcode, regionp) - { - } -protected: - LLDrawable* createDrawable(LLPipeline *pipeline); - U32 getPartitionType() const; - virtual LLVector3 getCameraPosition() const; -}; - -#endif // LL_LLVOPARTGROUP_H +/** + * @file llvopartgroup.h + * @brief Group of particle systems + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVOPARTGROUP_H +#define LL_LLVOPARTGROUP_H + +#include "llviewerobject.h" +#include "v3math.h" +#include "v3color.h" +#include "llframetimer.h" +#include "llviewerpartsim.h" +#include "llvertexbuffer.h" + +class LLViewerPartGroup; + +class LLVOPartGroup : public LLAlphaObject +{ +public: + + static void initClass(); + static void restoreGL(); + static void destroyGL(); + + enum + { + VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX | + LLVertexBuffer::MAP_NORMAL | + LLVertexBuffer::MAP_TEXCOORD0 | + LLVertexBuffer::MAP_COLOR | + LLVertexBuffer::MAP_EMISSIVE | + LLVertexBuffer::MAP_TEXTURE_INDEX + }; + + LLVOPartGroup(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); + + /*virtual*/ bool isActive() const; // Whether this object needs to do an idleUpdate. + void idleUpdate(LLAgent &agent, const F64 &time); + + virtual F32 getBinRadius(); + virtual void updateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax); + virtual U32 getPartitionType() const; + + /*virtual*/ bool lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, + S32 face, + bool pick_transparent, + bool pick_rigged, + bool pick_unselectable, + S32* face_hit, + LLVector4a* intersection, + LLVector2* tex_coord, + LLVector4a* normal, + LLVector4a* tangent); + + /*virtual*/ void setPixelAreaAndAngle(LLAgent &agent); + /*virtual*/ void updateTextures(); + + /*virtual*/ LLDrawable* createDrawable(LLPipeline *pipeline); + /*virtual*/ bool updateGeometry(LLDrawable *drawable); + void getGeometry(const LLViewerPart& part, + LLStrider& verticesp); + + void getGeometry(S32 idx, + LLStrider& verticesp, + LLStrider& normalsp, + LLStrider& texcoordsp, + LLStrider& colorsp, + LLStrider& emissivep, + LLStrider& indicesp); + + void updateFaceSize(S32 idx) { } + F32 getPartSize(S32 idx); + void getBlendFunc(S32 idx, LLRender::eBlendFactor& src, LLRender::eBlendFactor& dst); + LLUUID getPartOwner(S32 idx); + LLUUID getPartSource(S32 idx); + + void setViewerPartGroup(LLViewerPartGroup *part_groupp) { mViewerPartGroupp = part_groupp; } + LLViewerPartGroup* getViewerPartGroup() { return mViewerPartGroupp; } + +protected: + ~LLVOPartGroup(); + + LLViewerPartGroup *mViewerPartGroupp; + + virtual LLVector3 getCameraPosition() const; + +}; + + +class LLVOHUDPartGroup : public LLVOPartGroup +{ +public: + LLVOHUDPartGroup(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) : + LLVOPartGroup(id, pcode, regionp) + { + } +protected: + LLDrawable* createDrawable(LLPipeline *pipeline); + U32 getPartitionType() const; + virtual LLVector3 getCameraPosition() const; +}; + +#endif // LL_LLVOPARTGROUP_H diff --git a/indra/newview/llvosky.cpp b/indra/newview/llvosky.cpp index 0dff51c2f2..2b8ed74b0f 100644 --- a/indra/newview/llvosky.cpp +++ b/indra/newview/llvosky.cpp @@ -1,1591 +1,1591 @@ -/** - * @file llvosky.cpp - * @brief LLVOSky class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llvosky.h" - -#include "llfeaturemanager.h" -#include "llviewercontrol.h" -#include "llframetimer.h" - -#include "llagent.h" -#include "llagentcamera.h" -#include "lldrawable.h" -#include "llface.h" -#include "llcubemap.h" -#include "lldrawpoolsky.h" -#include "lldrawpoolwater.h" -#include "llglheaders.h" -#include "llsky.h" -#include "llviewercamera.h" -#include "llviewertexturelist.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llworld.h" -#include "pipeline.h" -#include "lldrawpoolwlsky.h" -#include "v3colorutil.h" - -#include "llsettingssky.h" -#include "llenvironment.h" - -#include "lltrace.h" -#include "llfasttimer.h" - -#undef min -#undef max - -namespace -{ - const S32 NUM_TILES_X = 8; - const S32 NUM_TILES_Y = 4; - const S32 NUM_TILES = NUM_TILES_X * NUM_TILES_Y; - const S32 NUM_CUBEMAP_FACES = 6; // See SKYTEX_RESOLUTION for face dimensions - const S32 TOTAL_TILES = NUM_CUBEMAP_FACES * NUM_TILES; - const S32 MAX_TILES = TOTAL_TILES + 1; - -// Heavenly body constants - const F32 SUN_DISK_RADIUS = 0.5f; - const F32 MOON_DISK_RADIUS = SUN_DISK_RADIUS * 0.9f; - const F32 SUN_INTENSITY = 1e5; - -// Texture coordinates: - const LLVector2 TEX00 = LLVector2(0.f, 0.f); - const LLVector2 TEX01 = LLVector2(0.f, 1.f); - const LLVector2 TEX10 = LLVector2(1.f, 0.f); - const LLVector2 TEX11 = LLVector2(1.f, 1.f); - - F32Seconds UPDATE_EXPRY(0.25f); - - const F32 UPDATE_MIN_DELTA_THRESHOLD = 0.0005f; -} -/*************************************** - SkyTex -***************************************/ - -S32 LLSkyTex::sCurrent = 0; - - -LLSkyTex::LLSkyTex() : - mSkyData(NULL), - mSkyDirs(NULL), - mIsShiny(false) -{ -} - -void LLSkyTex::init(bool isShiny) -{ - mIsShiny = isShiny; - mSkyData = new LLColor4[(U32)(SKYTEX_RESOLUTION * SKYTEX_RESOLUTION)]; - mSkyDirs = new LLVector3[(U32)(SKYTEX_RESOLUTION * SKYTEX_RESOLUTION)]; - - for (S32 i = 0; i < 2; ++i) - { - mTexture[i] = LLViewerTextureManager::getLocalTexture(false); - mTexture[i]->setAddressMode(LLTexUnit::TAM_CLAMP); - mImageRaw[i] = new LLImageRaw(SKYTEX_RESOLUTION, SKYTEX_RESOLUTION, SKYTEX_COMPONENTS); - - initEmpty(i); - } -} - -void LLSkyTex::cleanupGL() -{ - mTexture[0] = NULL; - mTexture[1] = NULL; -} - -void LLSkyTex::restoreGL() -{ - for (S32 i = 0; i < 2; i++) - { - mTexture[i] = LLViewerTextureManager::getLocalTexture(false); - mTexture[i]->setAddressMode(LLTexUnit::TAM_CLAMP); - } -} - -LLSkyTex::~LLSkyTex() -{ - delete[] mSkyData; - mSkyData = NULL; - - delete[] mSkyDirs; - mSkyDirs = NULL; -} - -S32 LLSkyTex::getResolution() -{ - return SKYTEX_RESOLUTION; -} - -S32 LLSkyTex::getCurrent() -{ - return sCurrent; -} - -S32 LLSkyTex::stepCurrent() { - sCurrent++; - sCurrent &= 1; - return sCurrent; -} - -S32 LLSkyTex::getNext() -{ - return ((sCurrent+1) & 1); -} - -S32 LLSkyTex::getWhich(const bool curr) -{ - int tex = curr ? sCurrent : getNext(); - return tex; -} - -void LLSkyTex::initEmpty(const S32 tex) -{ - LLImageDataLock lock(mImageRaw[tex]); - U8* data = mImageRaw[tex]->getData(); - for (S32 i = 0; i < SKYTEX_RESOLUTION; ++i) - { - for (S32 j = 0; j < SKYTEX_RESOLUTION; ++j) - { - const S32 basic_offset = (i * SKYTEX_RESOLUTION + j); - S32 offset = basic_offset * SKYTEX_COMPONENTS; - data[offset] = 0; - data[offset+1] = 0; - data[offset+2] = 0; - data[offset+3] = 255; - - mSkyData[basic_offset].setToBlack(); - } - } - - createGLImage(tex); -} - -void LLSkyTex::create() -{ - LLImageDataSharedLock lock(mImageRaw[sCurrent]); - const U8* data = mImageRaw[sCurrent]->getData(); - for (S32 i = 0; i < SKYTEX_RESOLUTION; ++i) - { - for (S32 j = 0; j < SKYTEX_RESOLUTION; ++j) - { - const S32 basic_offset = (i * SKYTEX_RESOLUTION + j); - S32 offset = basic_offset * SKYTEX_COMPONENTS; - U32* pix = (U32*)(data + offset); - LLColor4U temp = LLColor4U(mSkyData[basic_offset]); - *pix = temp.asRGBA(); - } - } - createGLImage(sCurrent); -} - -void LLSkyTex::createGLImage(S32 which) -{ - mTexture[which]->setExplicitFormat(GL_RGBA8, GL_RGBA); - mTexture[which]->createGLTexture(0, mImageRaw[which], 0, true, LLGLTexture::LOCAL); - mTexture[which]->setAddressMode(LLTexUnit::TAM_CLAMP); -} - -void LLSkyTex::bindTexture(bool curr) -{ - int tex = getWhich(curr); - gGL.getTexUnit(0)->bind(mTexture[tex], true); -} - -LLImageRaw* LLSkyTex::getImageRaw(bool curr) -{ - int tex = getWhich(curr); - return mImageRaw[tex]; -} - -/*************************************** - LLHeavenBody -***************************************/ - -F32 LLHeavenBody::sInterpVal = 0; - -LLHeavenBody::LLHeavenBody(const F32 rad) -: mDirectionCached(LLVector3(0,0,0)), - mDirection(LLVector3(0,0,0)), - mIntensity(0.f), - mDiskRadius(rad), - mDraw(false), - mHorizonVisibility(1.f), - mVisibility(1.f), - mVisible(false) -{ - mColor.setToBlack(); - mColorCached.setToBlack(); -} - -const LLQuaternion& LLHeavenBody::getRotation() const -{ - return mRotation; -} - -void LLHeavenBody::setRotation(const LLQuaternion& rot) -{ - mRotation = rot; -} - -const LLVector3& LLHeavenBody::getDirection() const -{ - return mDirection; -} - -void LLHeavenBody::setDirection(const LLVector3 &direction) -{ - mDirection = direction; -} - -void LLHeavenBody::setAngularVelocity(const LLVector3 &ang_vel) -{ - mAngularVelocity = ang_vel; -} - -const LLVector3& LLHeavenBody::getAngularVelocity() const -{ - return mAngularVelocity; -} - -const LLVector3& LLHeavenBody::getDirectionCached() const -{ - return mDirectionCached; -} - -void LLHeavenBody::renewDirection() -{ - mDirectionCached = mDirection; -} - -const LLColor3& LLHeavenBody::getColorCached() const -{ - return mColorCached; -} - -void LLHeavenBody::setColorCached(const LLColor3& c) -{ - mColorCached = c; -} - -const LLColor3& LLHeavenBody::getColor() const -{ - return mColor; -} - -void LLHeavenBody::setColor(const LLColor3& c) -{ - mColor = c; -} - -void LLHeavenBody::renewColor() -{ - mColorCached = mColor; -} - -F32 LLHeavenBody::interpVal() -{ - return sInterpVal; -} - -void LLHeavenBody::setInterpVal(const F32 v) -{ - sInterpVal = v; -} - -LLColor3 LLHeavenBody::getInterpColor() const -{ - return sInterpVal * mColor + (1 - sInterpVal) * mColorCached; -} - -const F32& LLHeavenBody::getVisibility() const -{ - return mVisibility; -} - -void LLHeavenBody::setVisibility(const F32 c) -{ - mVisibility = c; -} - -bool LLHeavenBody::isVisible() const -{ - return mVisible; -} - -void LLHeavenBody::setVisible(const bool v) -{ - mVisible = v; -} - -const F32& LLHeavenBody::getIntensity() const -{ - return mIntensity; -} - -void LLHeavenBody::setIntensity(const F32 c) -{ - mIntensity = c; -} - -void LLHeavenBody::setDiskRadius(const F32 radius) -{ - mDiskRadius = radius; -} - -F32 LLHeavenBody::getDiskRadius() const -{ - return mDiskRadius; -} - -void LLHeavenBody::setDraw(const bool draw) -{ - mDraw = draw; -} - -bool LLHeavenBody::getDraw() const -{ - return mDraw; -} - -const LLVector3& LLHeavenBody::corner(const S32 n) const -{ - return mQuadCorner[n]; -} - -LLVector3& LLHeavenBody::corner(const S32 n) -{ - return mQuadCorner[n]; -} - -const LLVector3* LLHeavenBody::corners() const -{ - return mQuadCorner; -} - -/*************************************** - Sky -***************************************/ - -const S32 SKYTEX_TILE_RES_X = SKYTEX_RESOLUTION / NUM_TILES_X; -const S32 SKYTEX_TILE_RES_Y = SKYTEX_RESOLUTION / NUM_TILES_Y; - -LLVOSky::LLVOSky(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) -: LLStaticViewerObject(id, pcode, regionp, true), - mSun(SUN_DISK_RADIUS), mMoon(MOON_DISK_RADIUS), - mBrightnessScale(1.f), - mBrightnessScaleNew(0.f), - mBrightnessScaleGuess(1.f), - mWeatherChange(false), - mCloudDensity(0.2f), - mWind(0.f), - mForceUpdate(false), - mNeedUpdate(true), - mCubeMapUpdateStage(-1), - mWorldScale(1.f), - mBumpSunDir(0.f, 0.f, 1.f) -{ - /// WL PARAMS - - mInitialized = false; - mbCanSelect = false; - mUpdateTimer.reset(); - - mForceUpdateThrottle.setTimerExpirySec(UPDATE_EXPRY); - mForceUpdateThrottle.reset(); - - for (S32 i = 0; i < NUM_CUBEMAP_FACES; i++) - { - mSkyTex[i].init(false); - mShinyTex[i].init(true); - } - for (S32 i=0; iupdate(); - - updateDirections(psky); - - cacheEnvironment(psky,m_atmosphericsVars); - - // Initialize the cached normalized direction vectors - for (S32 side = 0; side < NUM_CUBEMAP_FACES; ++side) - { - for (S32 tile = 0; tile < NUM_TILES; ++tile) - { - initSkyTextureDirs(side, tile); - createSkyTexture(psky, m_atmosphericsVars, side, tile); - } - mSkyTex[side].create(); - mShinyTex[side].create(); - } - - initCubeMap(); - - mInitialized = true; - - mHeavenlyBodyUpdated = false ; - - mRainbowMap = LLViewerTextureManager::getFetchedTexture(psky->getRainbowTextureId(), FTT_DEFAULT, true, LLGLTexture::BOOST_UI); - mHaloMap = LLViewerTextureManager::getFetchedTexture(psky->getHaloTextureId(), FTT_DEFAULT, true, LLGLTexture::BOOST_UI); -} - - -void LLVOSky::cacheEnvironment(LLSettingsSky::ptr_t psky,AtmosphericsVars& atmosphericsVars) -{ - // NOTE: Also see: LLAtmospherics::updateFog() - // invariants across whole sky tex process... - atmosphericsVars.blue_density = psky->getBlueDensity(); - atmosphericsVars.blue_horizon = psky->getBlueHorizon(); - atmosphericsVars.haze_density = psky->getHazeDensity(); - atmosphericsVars.haze_horizon = psky->getHazeHorizon(); - atmosphericsVars.density_multiplier = psky->getDensityMultiplier(); - atmosphericsVars.distance_multiplier = psky->getDistanceMultiplier(); - atmosphericsVars.max_y = psky->getMaxY(); - atmosphericsVars.sun_norm = LLEnvironment::instance().getClampedSunNorm(); - atmosphericsVars.sunlight = psky->getIsSunUp() ? psky->getSunlightColor() : psky->getMoonlightColor(); - atmosphericsVars.ambient = psky->getAmbientColor(); - atmosphericsVars.glow = psky->getGlow(); - atmosphericsVars.cloud_shadow = psky->getCloudShadow(); - atmosphericsVars.dome_radius = psky->getDomeRadius(); - atmosphericsVars.dome_offset = psky->getDomeOffset(); - atmosphericsVars.light_atten = psky->getLightAttenuation(atmosphericsVars.max_y); - atmosphericsVars.light_transmittance = psky->getLightTransmittance(atmosphericsVars.max_y); - atmosphericsVars.total_density = psky->getTotalDensity(); - atmosphericsVars.gamma = psky->getGamma(); -} - -void LLVOSky::calc() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT; - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - cacheEnvironment(psky,m_atmosphericsVars); - - mSun.setColor(psky->getSunDiffuse()); - mMoon.setColor(LLColor3(1.0f, 1.0f, 1.0f)); - - mSun.renewDirection(); - mSun.renewColor(); - mMoon.renewDirection(); - mMoon.renewColor(); -} - -void LLVOSky::initCubeMap() -{ - std::vector > images; - for (S32 side = 0; side < NUM_CUBEMAP_FACES; side++) - { - images.push_back(mShinyTex[side].getImageRaw()); - } - - if (!mCubeMap && gSavedSettings.getBOOL("RenderWater") && LLCubeMap::sUseCubeMaps) - { - mCubeMap = new LLCubeMap(false); - } - - if (mCubeMap) - { - mCubeMap->init(images); - } - - gGL.getTexUnit(0)->disable(); -} - - -void LLVOSky::cleanupGL() -{ - S32 i; - for (i = 0; i < NUM_CUBEMAP_FACES; i++) - { - mSkyTex[i].cleanupGL(); - } - if (getCubeMap()) - { - getCubeMap()->destroyGL(); - } -} - -void LLVOSky::restoreGL() -{ - S32 i; - for (i = 0; i < NUM_CUBEMAP_FACES; i++) - { - mSkyTex[i].restoreGL(); - } - - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - - if (psky) - { - setSunTextures(psky->getSunTextureId(), psky->getNextSunTextureId()); - setMoonTextures(psky->getMoonTextureId(), psky->getNextMoonTextureId()); - } - - updateDirections(psky); - - if (gSavedSettings.getBOOL("RenderWater") && LLCubeMap::sUseCubeMaps) - { - initCubeMap(); - } - - forceSkyUpdate(); - - if (mDrawable) - { - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); - } - -} - -void LLVOSky::initSkyTextureDirs(const S32 side, const S32 tile) -{ - S32 tile_x = tile % NUM_TILES_X; - S32 tile_y = tile / NUM_TILES_X; - - S32 tile_x_pos = tile_x * SKYTEX_TILE_RES_X; - S32 tile_y_pos = tile_y * SKYTEX_TILE_RES_Y; - - F32 coeff[3] = {0, 0, 0}; - const S32 curr_coef = side >> 1; // 0/1 = Z axis, 2/3 = Y, 4/5 = X - const S32 side_dir = (((side & 1) << 1) - 1); // even = -1, odd = 1 - const S32 x_coef = (curr_coef + 1) % 3; - const S32 y_coef = (x_coef + 1) % 3; - - coeff[curr_coef] = (F32)side_dir; - - F32 inv_res = 1.f/SKYTEX_RESOLUTION; - S32 x, y; - for (y = tile_y_pos; y < (tile_y_pos + SKYTEX_TILE_RES_Y); ++y) - { - for (x = tile_x_pos; x < (tile_x_pos + SKYTEX_TILE_RES_X); ++x) - { - coeff[x_coef] = F32((x<<1) + 1) * inv_res - 1.f; - coeff[y_coef] = F32((y<<1) + 1) * inv_res - 1.f; - LLVector3 dir(coeff[0], coeff[1], coeff[2]); - dir.normalize(); - mSkyTex[side].setDir(dir, x, y); - mShinyTex[side].setDir(dir, x, y); - } - } -} - -void LLVOSky::createSkyTexture(const LLSettingsSky::ptr_t &psky, AtmosphericsVars& vars, const S32 side, const S32 tile) -{ - const bool low_end = !gPipeline.canUseWindLightShaders(); - - S32 tile_x = tile % NUM_TILES_X; - S32 tile_y = tile / NUM_TILES_X; - - S32 tile_x_pos = tile_x * SKYTEX_TILE_RES_X; - S32 tile_y_pos = tile_y * SKYTEX_TILE_RES_Y; - - S32 x, y; - for (y = tile_y_pos; y < (tile_y_pos + SKYTEX_TILE_RES_Y); ++y) - { - for (x = tile_x_pos; x < (tile_x_pos + SKYTEX_TILE_RES_X); ++x) - { - mSkyTex [side].setPixel(m_legacyAtmospherics.calcSkyColorInDir(psky, vars, mSkyTex [side].getDir(x, y), false, low_end), x, y); - mShinyTex[side].setPixel(m_legacyAtmospherics.calcSkyColorInDir(psky, vars, mShinyTex[side].getDir(x, y), true , low_end), x, y); - } - } -} - -void LLVOSky::updateDirections(LLSettingsSky::ptr_t psky) -{ - mSun.setDirection(psky->getSunDirection()); - mMoon.setDirection(psky->getMoonDirection()); - mSun.setRotation(psky->getSunRotation()); - mMoon.setRotation(psky->getMoonRotation()); - mSun.renewDirection(); - mMoon.renewDirection(); -} - -void LLVOSky::idleUpdate(LLAgent &agent, const F64 &time) -{ -} - -void LLVOSky::forceSkyUpdate() -{ - mForceUpdate = true; - m_lastAtmosphericsVars = {}; - mCubeMapUpdateStage = -1; -} - -bool LLVOSky::updateSky() -{ - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - - if (mDead || !(gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_SKY))) - { - // It's dead. Don't update it. - return true; - } - - if (gGLManager.mIsDisabled) - { - return true; - } - - LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT; - - static S32 next_frame = 0; - - mNeedUpdate = mForceUpdate; - - ++next_frame; - next_frame = next_frame % MAX_TILES; - - mInterpVal = (!mInitialized) ? 1 : (F32)next_frame / MAX_TILES; - LLHeavenBody::setInterpVal( mInterpVal ); - updateDirections(psky); - - if (!mCubeMap || LLPipeline::sReflectionProbesEnabled) - { - mCubeMapUpdateStage = NUM_CUBEMAP_FACES; - mForceUpdate = false; - return true; - } - - if (mCubeMapUpdateStage < 0) - { - calc(); - - bool same_atmospherics = approximatelyEqual(m_lastAtmosphericsVars, m_atmosphericsVars, UPDATE_MIN_DELTA_THRESHOLD); - - mNeedUpdate = mNeedUpdate || !same_atmospherics; - - if (mNeedUpdate && (mForceUpdateThrottle.hasExpired() || mForceUpdate)) - { - // start updating cube map sides - updateFog(LLViewerCamera::getInstance()->getFar()); - mCubeMapUpdateStage = 0; - mForceUpdate = false; - } - } - else if (mCubeMapUpdateStage == NUM_CUBEMAP_FACES && !LLPipeline::sReflectionProbesEnabled) - { - LL_PROFILE_ZONE_NAMED("updateSky - forced"); - LLSkyTex::stepCurrent(); - - bool is_alm_wl_sky = gPipeline.canUseWindLightShaders(); - - int tex = mSkyTex[0].getWhich(true); - - for (int side = 0; side < NUM_CUBEMAP_FACES; side++) - { - LLImageRaw* raw1 = nullptr; - LLImageRaw* raw2 = nullptr; - - if (!is_alm_wl_sky) - { - raw1 = mSkyTex[side].getImageRaw(true); - raw2 = mSkyTex[side].getImageRaw(false); - raw2->copy(raw1); - mSkyTex[side].createGLImage(tex); - } - - raw1 = mShinyTex[side].getImageRaw(true); - raw2 = mShinyTex[side].getImageRaw(false); - raw2->copy(raw1); - mShinyTex[side].createGLImage(tex); - } - next_frame = 0; - - // update the sky texture - if (!is_alm_wl_sky) - { - for (S32 i = 0; i < NUM_CUBEMAP_FACES; ++i) - { - mSkyTex[i].create(); - } - } - - for (S32 i = 0; i < NUM_CUBEMAP_FACES; ++i) - { - mShinyTex[i].create(); - } - - // update the environment map - initCubeMap(); - - m_lastAtmosphericsVars = m_atmosphericsVars; - - mNeedUpdate = false; - mForceUpdate = false; - - mForceUpdateThrottle.setTimerExpirySec(UPDATE_EXPRY); - - if (mDrawable.notNull() && mDrawable->getFace(0) && !mDrawable->getFace(0)->getVertexBuffer()) - { - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); - } - mCubeMapUpdateStage = -1; - } - // run 0 to 5 faces, each face in own frame - else if (mCubeMapUpdateStage >= 0 && mCubeMapUpdateStage < NUM_CUBEMAP_FACES && !LLPipeline::sReflectionProbesEnabled) - { - LL_PROFILE_ZONE_NAMED("updateSky - create"); - S32 side = mCubeMapUpdateStage; - // CPU hungry part, createSkyTexture() is math heavy - // Prior to EEP it was mostly per tile, but since EPP it is per face. - // This still can be optimized further - // (i.e. potentially can be made per tile again, can be moved to thread - // instead of executing per face, or may be can be moved to shaders) - for (S32 tile = 0; tile < NUM_TILES; tile++) - { - createSkyTexture(psky, m_atmosphericsVars, side, tile); - } - mCubeMapUpdateStage++; - } - - return true; -} - -void LLVOSky::updateTextures() -{ - if (mSunTexturep[0]) - { - mSunTexturep[0]->addTextureStats( (F32)MAX_IMAGE_AREA ); - } - - if (mSunTexturep[1]) - { - mSunTexturep[1]->addTextureStats( (F32)MAX_IMAGE_AREA ); -} - - if (mMoonTexturep[0]) -{ - mMoonTexturep[0]->addTextureStats( (F32)MAX_IMAGE_AREA ); -} - - if (mMoonTexturep[1]) -{ - mMoonTexturep[1]->addTextureStats( (F32)MAX_IMAGE_AREA ); -} - - if (mBloomTexturep[0]) - { - mBloomTexturep[0]->addTextureStats( (F32)MAX_IMAGE_AREA ); - } - - if (mBloomTexturep[1]) - { - mBloomTexturep[1]->addTextureStats( (F32)MAX_IMAGE_AREA ); - } - } - -LLDrawable *LLVOSky::createDrawable(LLPipeline *pipeline) -{ - pipeline->allocDrawable(this); - mDrawable->setLit(false); - - LLDrawPoolSky *poolp = (LLDrawPoolSky*) gPipeline.getPool(LLDrawPool::POOL_SKY); - poolp->setSkyTex(mSkyTex); - mDrawable->setRenderType(LLPipeline::RENDER_TYPE_SKY); - - for (S32 i = 0; i < NUM_CUBEMAP_FACES; ++i) - { - mFace[FACE_SIDE0 + i] = mDrawable->addFace(poolp, NULL); - } - - mFace[FACE_SUN] = mDrawable->addFace(poolp, nullptr); - mFace[FACE_MOON] = mDrawable->addFace(poolp, nullptr); - mFace[FACE_BLOOM] = mDrawable->addFace(poolp, nullptr); - - mFace[FACE_SUN]->setMediaAllowed(false); - mFace[FACE_MOON]->setMediaAllowed(false); - mFace[FACE_BLOOM]->setMediaAllowed(false); - - return mDrawable; -} - -void LLVOSky::setSunScale(F32 sun_scale) -{ - mSunScale = sun_scale; -} - -void LLVOSky::setMoonScale(F32 moon_scale) -{ - mMoonScale = moon_scale; -} - -void LLVOSky::setSunTextures(const LLUUID& sun_texture, const LLUUID& sun_texture_next) -{ - // We test the UUIDs here because we explicitly do not want the default image returned by getFetchedTexture in that case... - mSunTexturep[0] = sun_texture.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(sun_texture, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); - mSunTexturep[1] = sun_texture_next.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(sun_texture_next, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); - - bool can_use_wl = gPipeline.canUseWindLightShaders(); - - if (mFace[FACE_SUN]) - { - if (mSunTexturep[0]) - { - mSunTexturep[0]->setAddressMode(LLTexUnit::TAM_CLAMP); - } - - LLViewerTexture* current_tex0 = mFace[FACE_SUN]->getTexture(LLRender::DIFFUSE_MAP); - LLViewerTexture* current_tex1 = mFace[FACE_SUN]->getTexture(LLRender::ALTERNATE_DIFFUSE_MAP); - - if (current_tex0 && (mSunTexturep[0] != current_tex0) && current_tex0->isViewerMediaTexture()) - { - static_cast(current_tex0)->removeMediaFromFace(mFace[FACE_SUN]); - } - - if (current_tex1 && (mSunTexturep[1] != current_tex1) && current_tex1->isViewerMediaTexture()) - { - static_cast(current_tex1)->removeMediaFromFace(mFace[FACE_SUN]); - } - - mFace[FACE_SUN]->setTexture(LLRender::DIFFUSE_MAP, mSunTexturep[0]); - - if (can_use_wl) - { - if (mSunTexturep[1]) - { - mSunTexturep[1]->setAddressMode(LLTexUnit::TAM_CLAMP); - } - mFace[FACE_SUN]->setTexture(LLRender::ALTERNATE_DIFFUSE_MAP, mSunTexturep[1]); - } - } -} - -void LLVOSky::setMoonTextures(const LLUUID& moon_texture, const LLUUID& moon_texture_next) -{ - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - - bool can_use_wl = gPipeline.canUseWindLightShaders(); - - mMoonTexturep[0] = moon_texture.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(moon_texture, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); - mMoonTexturep[1] = moon_texture_next.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(moon_texture_next, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); - - if (mFace[FACE_MOON]) - { - if (mMoonTexturep[0]) - { - mMoonTexturep[0]->setAddressMode(LLTexUnit::TAM_CLAMP); - } - mFace[FACE_MOON]->setTexture(LLRender::DIFFUSE_MAP, mMoonTexturep[0]); - - if (mMoonTexturep[1] && can_use_wl) - { - mMoonTexturep[1]->setAddressMode(LLTexUnit::TAM_CLAMP); - mFace[FACE_MOON]->setTexture(LLRender::ALTERNATE_DIFFUSE_MAP, mMoonTexturep[1]); - } - } -} - -void LLVOSky::setCloudNoiseTextures(const LLUUID& cloud_noise_texture, const LLUUID& cloud_noise_texture_next) -{ - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - - mCloudNoiseTexturep[0] = cloud_noise_texture.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(cloud_noise_texture, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); - mCloudNoiseTexturep[1] = cloud_noise_texture_next.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(cloud_noise_texture_next, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); - - if (mCloudNoiseTexturep[0]) - { - mCloudNoiseTexturep[0]->setAddressMode(LLTexUnit::TAM_WRAP); - } - - if (mCloudNoiseTexturep[1]) - { - mCloudNoiseTexturep[1]->setAddressMode(LLTexUnit::TAM_WRAP); - } -} - -void LLVOSky::setBloomTextures(const LLUUID& bloom_texture, const LLUUID& bloom_texture_next) -{ - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - - LLUUID bloom_tex = bloom_texture.isNull() ? psky->GetDefaultBloomTextureId() : bloom_texture; - LLUUID bloom_tex_next = bloom_texture_next.isNull() ? (bloom_texture.isNull() ? psky->GetDefaultBloomTextureId() : bloom_texture) : bloom_texture_next; - - mBloomTexturep[0] = bloom_tex.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(bloom_tex, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); - mBloomTexturep[1] = bloom_tex_next.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(bloom_tex_next, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); - - if (mBloomTexturep[0]) - { - mBloomTexturep[0]->setAddressMode(LLTexUnit::TAM_CLAMP); - } - - if (mBloomTexturep[1]) - { - mBloomTexturep[1]->setAddressMode(LLTexUnit::TAM_CLAMP); - } -} - -bool LLVOSky::updateGeometry(LLDrawable *drawable) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE; - if (mFace[FACE_REFLECTION] == NULL) - { - LLDrawPoolWater *poolp = (LLDrawPoolWater*) gPipeline.getPool(LLDrawPool::POOL_WATER); - if (gPipeline.getPool(LLDrawPool::POOL_WATER)->getShaderLevel() != 0) - { - mFace[FACE_REFLECTION] = drawable->addFace(poolp, NULL); - } - } - - mCameraPosAgent = drawable->getPositionAgent(); - - mEarthCenter.mV[0] = mCameraPosAgent.mV[0]; - mEarthCenter.mV[1] = mCameraPosAgent.mV[1]; - - LLVector3 v_agent[8]; - for (S32 i = 0; i < 8; ++i) - { - F32 x_sgn = (i&1) ? 1.f : -1.f; - F32 y_sgn = (i&2) ? 1.f : -1.f; - F32 z_sgn = (i&4) ? 1.f : -1.f; - v_agent[i] = HORIZON_DIST * SKY_BOX_MULT * LLVector3(x_sgn, y_sgn, z_sgn); - } - - LLStrider verticesp; - LLStrider normalsp; - LLStrider texCoordsp; - LLStrider indicesp; - U16 index_offset; - LLFace *face; - - for (S32 side = 0; side < NUM_CUBEMAP_FACES; ++side) - { - face = mFace[FACE_SIDE0 + side]; - - if (!face->getVertexBuffer()) - { - face->setSize(4, 6); - face->setGeomIndex(0); - face->setIndicesIndex(0); - LLVertexBuffer* buff = new LLVertexBuffer(LLDrawPoolSky::VERTEX_DATA_MASK); - buff->allocateBuffer(4, 6); - face->setVertexBuffer(buff); - - index_offset = face->getGeometry(verticesp,normalsp,texCoordsp, indicesp); - - S32 vtx = 0; - S32 curr_bit = side >> 1; // 0/1 = Z axis, 2/3 = Y, 4/5 = X - S32 side_dir = side & 1; // even - 0, odd - 1 - S32 i_bit = (curr_bit + 2) % 3; - S32 j_bit = (i_bit + 2) % 3; - - LLVector3 axis; - axis.mV[curr_bit] = 1; - face->mCenterAgent = (F32)((side_dir << 1) - 1) * axis * HORIZON_DIST; - - vtx = side_dir << curr_bit; - *(verticesp++) = v_agent[vtx]; - *(verticesp++) = v_agent[vtx | 1 << j_bit]; - *(verticesp++) = v_agent[vtx | 1 << i_bit]; - *(verticesp++) = v_agent[vtx | 1 << i_bit | 1 << j_bit]; - - *(texCoordsp++) = TEX00; - *(texCoordsp++) = TEX01; - *(texCoordsp++) = TEX10; - *(texCoordsp++) = TEX11; - - // Triangles for each side - *indicesp++ = index_offset + 0; - *indicesp++ = index_offset + 1; - *indicesp++ = index_offset + 3; - - *indicesp++ = index_offset + 0; - *indicesp++ = index_offset + 3; - *indicesp++ = index_offset + 2; - - buff->unmapBuffer(); - } - } - - const LLVector3 &look_at = LLViewerCamera::getInstance()->getAtAxis(); - LLVector3 right = look_at % LLVector3::z_axis; - LLVector3 up = right % look_at; - right.normalize(); - up.normalize(); - - bool draw_sun = updateHeavenlyBodyGeometry(drawable, mSunScale, FACE_SUN, mSun, up, right); - bool draw_moon = updateHeavenlyBodyGeometry(drawable, mMoonScale, FACE_MOON, mMoon, up, right); - - draw_sun &= LLEnvironment::getInstance()->getIsSunUp(); - draw_moon &= LLEnvironment::getInstance()->getIsMoonUp(); - - mSun.setDraw(draw_sun); - mMoon.setDraw(draw_moon); - - const F32 water_height = gAgent.getRegion()->getWaterHeight() + 0.01f; - // LLWorld::getInstance()->getWaterHeight() + 0.01f; - const F32 camera_height = mCameraPosAgent.mV[2]; - const F32 height_above_water = camera_height - water_height; - - bool sun_flag = false; - if (mSun.isVisible()) - { - sun_flag = !mMoon.isVisible() || ((look_at * mSun.getDirection()) > 0); - } - - bool above_water = (height_above_water > 0); - bool render_ref = above_water && gPipeline.getPool(LLDrawPool::POOL_WATER)->getShaderLevel() == 0; - setDrawRefl(above_water ? (sun_flag ? 0 : 1) : -1); - if (render_ref) - { - updateReflectionGeometry(drawable, height_above_water, mSun); - } - - LLPipeline::sCompiles++; - return true; -} - -bool LLVOSky::updateHeavenlyBodyGeometry(LLDrawable *drawable, F32 scale, const S32 f, LLHeavenBody& hb, const LLVector3 &up, const LLVector3 &right) -{ - mHeavenlyBodyUpdated = true ; - - LLStrider verticesp; - LLStrider normalsp; - LLStrider texCoordsp; - LLStrider indicesp; - S32 index_offset; - LLFace *facep; - - - LLQuaternion rot = hb.getRotation(); - LLVector3 to_dir = LLVector3::x_axis * rot; - - LLVector3 hb_right = to_dir % LLVector3::z_axis; - LLVector3 hb_up = hb_right % to_dir; - - // at zenith so math below fails spectacularly - if ((to_dir * LLVector3::z_axis) > 0.99f) - { - hb_right = LLVector3::y_axis_neg * rot; - hb_up = LLVector3::z_axis * rot; - } - - LLVector3 draw_pos = to_dir * HEAVENLY_BODY_DIST; - - hb_right.normalize(); - hb_up.normalize(); - - const F32 enlargm_factor = ( 1 - to_dir.mV[2] ); - F32 horiz_enlargement = 1 + enlargm_factor * 0.3f; - F32 vert_enlargement = 1 + enlargm_factor * 0.2f; - - const LLVector3 scaled_right = horiz_enlargement * scale * HEAVENLY_BODY_DIST * HEAVENLY_BODY_FACTOR * hb.getDiskRadius() * hb_right; - const LLVector3 scaled_up = vert_enlargement * scale * HEAVENLY_BODY_DIST * HEAVENLY_BODY_FACTOR * hb.getDiskRadius() * hb_up; - - LLVector3 v_clipped[4]; - - v_clipped[0] = draw_pos - scaled_right + scaled_up; - v_clipped[1] = draw_pos - scaled_right - scaled_up; - v_clipped[2] = draw_pos + scaled_right + scaled_up; - v_clipped[3] = draw_pos + scaled_right - scaled_up; - - hb.setVisible(true); - - facep = mFace[f]; - - if (!facep->getVertexBuffer()) - { - facep->setSize(4, 6); - LLVertexBuffer* buff = new LLVertexBuffer(LLDrawPoolSky::VERTEX_DATA_MASK); - if (!buff->allocateBuffer(facep->getGeomCount(), facep->getIndicesCount())) - { - LL_WARNS() << "Failed to allocate Vertex Buffer for vosky to " - << facep->getGeomCount() << " vertices and " - << facep->getIndicesCount() << " indices" << LL_ENDL; - } - facep->setGeomIndex(0); - facep->setIndicesIndex(0); - facep->setVertexBuffer(buff); - } - - llassert(facep->getVertexBuffer()->getNumIndices() == 6); - - index_offset = facep->getGeometry(verticesp,normalsp,texCoordsp, indicesp); - - if (-1 == index_offset) - { - return true; - } - - for (S32 vtx = 0; vtx < 4; ++vtx) - { - hb.corner(vtx) = v_clipped[vtx]; - *(verticesp++) = hb.corner(vtx) + mCameraPosAgent; - } - - *(texCoordsp++) = TEX01; - *(texCoordsp++) = TEX00; - *(texCoordsp++) = TEX11; - *(texCoordsp++) = TEX10; - - *indicesp++ = index_offset + 0; - *indicesp++ = index_offset + 2; - *indicesp++ = index_offset + 1; - - *indicesp++ = index_offset + 1; - *indicesp++ = index_offset + 2; - *indicesp++ = index_offset + 3; - - facep->getVertexBuffer()->unmapBuffer(); - - return true; -} - -F32 dtReflection(const LLVector3& p, F32 cos_dir_from_top, F32 sin_dir_from_top, F32 diff_angl_dir) -{ - LLVector3 P = p; - P.normalize(); - - const F32 cos_dir_angle = -P.mV[VZ]; - const F32 sin_dir_angle = sqrt(1 - cos_dir_angle * cos_dir_angle); - - F32 cos_diff_angles = cos_dir_angle * cos_dir_from_top - + sin_dir_angle * sin_dir_from_top; - - F32 diff_angles; - if (cos_diff_angles > (1 - 1e-7)) - diff_angles = 0; - else - diff_angles = acos(cos_diff_angles); - - const F32 rel_diff_angles = diff_angles / diff_angl_dir; - const F32 dt = 1 - rel_diff_angles; - - return (dt < 0) ? 0 : dt; -} - - -F32 dtClip(const LLVector3& v0, const LLVector3& v1, F32 far_clip2) -{ - F32 dt_clip; - const LLVector3 otrezok = v1 - v0; - const F32 A = otrezok.lengthSquared(); - const F32 B = v0 * otrezok; - const F32 C = v0.lengthSquared() - far_clip2; - const F32 det = sqrt(B*B - A*C); - dt_clip = (-B - det) / A; - if ((dt_clip < 0) || (dt_clip > 1)) - dt_clip = (-B + det) / A; - return dt_clip; -} - - -void LLVOSky::updateReflectionGeometry(LLDrawable *drawable, F32 H, - const LLHeavenBody& HB) -{ - const LLVector3 &look_at = LLViewerCamera::getInstance()->getAtAxis(); - // const F32 water_height = gAgent.getRegion()->getWaterHeight() + 0.001f; - // LLWorld::getInstance()->getWaterHeight() + 0.001f; - - LLVector3 to_dir = HB.getDirection(); - LLVector3 hb_pos = to_dir * (HORIZON_DIST - 10); - LLVector3 to_dir_proj = to_dir; - to_dir_proj.mV[VZ] = 0; - to_dir_proj.normalize(); - - LLVector3 Right = to_dir % LLVector3::z_axis; - LLVector3 Up = Right % to_dir; - Right.normalize(); - Up.normalize(); - - // finding angle between look direction and sprite. - LLVector3 look_at_right = look_at % LLVector3::z_axis; - look_at_right.normalize(); - - const F32 enlargm_factor = ( 1 - to_dir.mV[2] ); - F32 horiz_enlargement = 1 + enlargm_factor * 0.3f; - F32 vert_enlargement = 1 + enlargm_factor * 0.2f; - - F32 vert_size = vert_enlargement * HEAVENLY_BODY_SCALE * HB.getDiskRadius(); - Right *= /*cos_lookAt_toDir */ horiz_enlargement * HEAVENLY_BODY_SCALE * HB.getDiskRadius(); - Up *= vert_size; - - LLVector3 v_corner[2]; - LLVector3 stretch_corner[2]; - - LLVector3 top_hb = v_corner[0] = stretch_corner[0] = hb_pos - Right + Up; - v_corner[1] = stretch_corner[1] = hb_pos - Right - Up; - - LLVector2 TEX0t = TEX00; - LLVector2 TEX1t = TEX10; - LLVector3 lower_corner = v_corner[1]; - - top_hb.normalize(); - const F32 cos_angle_of_view = fabs(top_hb.mV[VZ]); - const F32 extension = llmin (5.0f, 1.0f / cos_angle_of_view); - - const S32 cols = 1; - const S32 raws = lltrunc(16 * extension); - S32 quads = cols * raws; - - stretch_corner[0] = lower_corner + extension * (stretch_corner[0] - lower_corner); - stretch_corner[1] = lower_corner + extension * (stretch_corner[1] - lower_corner); - - F32 cos_dir_from_top[2]; - - LLVector3 dir = stretch_corner[0]; - dir.normalize(); - cos_dir_from_top[0] = dir.mV[VZ]; - - dir = stretch_corner[1]; - dir.normalize(); - cos_dir_from_top[1] = dir.mV[VZ]; - - const F32 sin_dir_from_top = sqrt(1 - cos_dir_from_top[0] * cos_dir_from_top[0]); - const F32 sin_dir_from_top2 = sqrt(1 - cos_dir_from_top[1] * cos_dir_from_top[1]); - const F32 cos_diff_dir = cos_dir_from_top[0] * cos_dir_from_top[1] - + sin_dir_from_top * sin_dir_from_top2; - const F32 diff_angl_dir = acos(cos_diff_dir); - - v_corner[0] = stretch_corner[0]; - v_corner[1] = lower_corner; - - - LLVector2 TEX0tt = TEX01; - LLVector2 TEX1tt = TEX11; - - LLVector3 v_refl_corner[4]; - LLVector3 v_sprite_corner[4]; - - S32 vtx; - for (vtx = 0; vtx < 2; ++vtx) - { - LLVector3 light_proj = v_corner[vtx]; - light_proj.normalize(); - - const F32 z = light_proj.mV[VZ]; - const F32 sin_angle = sqrt(1 - z * z); - light_proj *= 1.f / sin_angle; - light_proj.mV[VZ] = 0; - const F32 to_refl_point = H * sin_angle / fabs(z); - - v_refl_corner[vtx] = to_refl_point * light_proj; - } - - - for (vtx = 2; vtx < 4; ++vtx) - { - const LLVector3 to_dir_vec = (to_dir_proj * v_refl_corner[vtx-2]) * to_dir_proj; - v_refl_corner[vtx] = v_refl_corner[vtx-2] + 2 * (to_dir_vec - v_refl_corner[vtx-2]); - } - - for (vtx = 0; vtx < 4; ++vtx) - v_refl_corner[vtx].mV[VZ] -= H; - - S32 side = 0; - LLVector3 refl_corn_norm[2]; - refl_corn_norm[0] = v_refl_corner[1]; - refl_corn_norm[0].normalize(); - refl_corn_norm[1] = v_refl_corner[3]; - refl_corn_norm[1].normalize(); - - F32 cos_refl_look_at[2]; - cos_refl_look_at[0] = refl_corn_norm[0] * look_at; - cos_refl_look_at[1] = refl_corn_norm[1] * look_at; - - if (cos_refl_look_at[1] > cos_refl_look_at[0]) - { - side = 2; - } - - //const F32 far_clip = (LLViewerCamera::getInstance()->getFar() - 0.01) / far_clip_factor; - const F32 far_clip = 512; - const F32 far_clip2 = far_clip*far_clip; - - F32 dt_clip; - F32 vtx_near2, vtx_far2; - - if ((vtx_far2 = v_refl_corner[side].lengthSquared()) > far_clip2) - { - // whole thing is sprite: reflection is beyond far clip plane. - dt_clip = 1.1f; - quads = 1; - } - else if ((vtx_near2 = v_refl_corner[side+1].lengthSquared()) > far_clip2) - { - // part is reflection, the rest is sprite. - dt_clip = dtClip(v_refl_corner[side + 1], v_refl_corner[side], far_clip2); - const LLVector3 P = (1 - dt_clip) * v_refl_corner[side + 1] + dt_clip * v_refl_corner[side]; - - F32 dt_tex = dtReflection(P, cos_dir_from_top[0], sin_dir_from_top, diff_angl_dir); - - TEX0tt = LLVector2(0, dt_tex); - TEX1tt = LLVector2(1, dt_tex); - quads++; - } - else - { - // whole thing is correct reflection. - dt_clip = -0.1f; - } - - LLFace *face = mFace[FACE_REFLECTION]; - - if (face) - { - if (!face->getVertexBuffer() || quads*4 != face->getGeomCount()) - { - face->setSize(quads * 4, quads * 6); - LLVertexBuffer* buff = new LLVertexBuffer(LLDrawPoolWater::VERTEX_DATA_MASK); - if (!buff->allocateBuffer(face->getGeomCount(), face->getIndicesCount())) - { - LL_WARNS() << "Failed to allocate Vertex Buffer for vosky to " - << face->getGeomCount() << " vertices and " - << face->getIndicesCount() << " indices" << LL_ENDL; - } - face->setIndicesIndex(0); - face->setGeomIndex(0); - face->setVertexBuffer(buff); - } - - LLStrider verticesp; - LLStrider normalsp; - LLStrider texCoordsp; - LLStrider indicesp; - S32 index_offset; - - index_offset = face->getGeometry(verticesp,normalsp,texCoordsp, indicesp); - if (-1 == index_offset) - { - return; - } - - LLColor3 hb_col3 = HB.getInterpColor(); - hb_col3.clamp(); - const LLColor4 hb_col = LLColor4(hb_col3); - - const F32 min_attenuation = 0.4f; - const F32 max_attenuation = 0.7f; - const F32 attenuation = min_attenuation - + cos_angle_of_view * (max_attenuation - min_attenuation); - - LLColor4 hb_refl_col = (1 - attenuation) * hb_col + attenuation * getSkyFogColor(); - face->setFaceColor(hb_refl_col); - - LLVector3 v_far[2]; - v_far[0] = v_refl_corner[1]; - v_far[1] = v_refl_corner[3]; - - if(dt_clip > 0) - { - if (dt_clip >= 1) - { - for (S32 vtx = 0; vtx < 4; ++vtx) - { - F32 ratio = far_clip / v_refl_corner[vtx].length(); - *(verticesp++) = v_refl_corner[vtx] = ratio * v_refl_corner[vtx] + mCameraPosAgent; - } - const LLVector3 draw_pos = 0.25 * - (v_refl_corner[0] + v_refl_corner[1] + v_refl_corner[2] + v_refl_corner[3]); - face->mCenterAgent = draw_pos; - } - else - { - F32 ratio = far_clip / v_refl_corner[1].length(); - v_sprite_corner[1] = v_refl_corner[1] * ratio; - - ratio = far_clip / v_refl_corner[3].length(); - v_sprite_corner[3] = v_refl_corner[3] * ratio; - - v_refl_corner[1] = (1 - dt_clip) * v_refl_corner[1] + dt_clip * v_refl_corner[0]; - v_refl_corner[3] = (1 - dt_clip) * v_refl_corner[3] + dt_clip * v_refl_corner[2]; - v_sprite_corner[0] = v_refl_corner[1]; - v_sprite_corner[2] = v_refl_corner[3]; - - for (S32 vtx = 0; vtx < 4; ++vtx) - { - *(verticesp++) = v_sprite_corner[vtx] + mCameraPosAgent; - } - - const LLVector3 draw_pos = 0.25 * - (v_refl_corner[0] + v_sprite_corner[1] + v_refl_corner[2] + v_sprite_corner[3]); - face->mCenterAgent = draw_pos; - } - - *(texCoordsp++) = TEX0tt; - *(texCoordsp++) = TEX0t; - *(texCoordsp++) = TEX1tt; - *(texCoordsp++) = TEX1t; - - *indicesp++ = index_offset + 0; - *indicesp++ = index_offset + 2; - *indicesp++ = index_offset + 1; - - *indicesp++ = index_offset + 1; - *indicesp++ = index_offset + 2; - *indicesp++ = index_offset + 3; - - index_offset += 4; - } - - if (dt_clip < 1) - { - if (dt_clip <= 0) - { - const LLVector3 draw_pos = 0.25 * - (v_refl_corner[0] + v_refl_corner[1] + v_refl_corner[2] + v_refl_corner[3]); - face->mCenterAgent = draw_pos; - } - - const F32 raws_inv = 1.f/raws; - const F32 cols_inv = 1.f/cols; - LLVector3 left = v_refl_corner[0] - v_refl_corner[1]; - LLVector3 right = v_refl_corner[2] - v_refl_corner[3]; - left *= raws_inv; - right *= raws_inv; - - for (S32 raw = 0; raw < raws; ++raw) - { - F32 dt_v0 = raw * raws_inv; - F32 dt_v1 = (raw + 1) * raws_inv; - const LLVector3 BL = v_refl_corner[1] + (F32)raw * left; - const LLVector3 BR = v_refl_corner[3] + (F32)raw * right; - const LLVector3 EL = BL + left; - const LLVector3 ER = BR + right; - dt_v0 = dt_v1 = dtReflection(EL, cos_dir_from_top[0], sin_dir_from_top, diff_angl_dir); - for (S32 col = 0; col < cols; ++col) - { - F32 dt_h0 = col * cols_inv; - *(verticesp++) = (1 - dt_h0) * EL + dt_h0 * ER + mCameraPosAgent; - *(verticesp++) = (1 - dt_h0) * BL + dt_h0 * BR + mCameraPosAgent; - F32 dt_h1 = (col + 1) * cols_inv; - *(verticesp++) = (1 - dt_h1) * EL + dt_h1 * ER + mCameraPosAgent; - *(verticesp++) = (1 - dt_h1) * BL + dt_h1 * BR + mCameraPosAgent; - - *(texCoordsp++) = LLVector2(dt_h0, dt_v1); - *(texCoordsp++) = LLVector2(dt_h0, dt_v0); - *(texCoordsp++) = LLVector2(dt_h1, dt_v1); - *(texCoordsp++) = LLVector2(dt_h1, dt_v0); - - *indicesp++ = index_offset + 0; - *indicesp++ = index_offset + 2; - *indicesp++ = index_offset + 1; - - *indicesp++ = index_offset + 1; - *indicesp++ = index_offset + 2; - *indicesp++ = index_offset + 3; - - index_offset += 4; - } - } - } - - face->getVertexBuffer()->unmapBuffer(); -} -} - -void LLVOSky::updateFog(const F32 distance) -{ - LLEnvironment& environment = LLEnvironment::instance(); - if (environment.getCurrentSky() != nullptr) - { - LLVector3 light_dir = LLVector3(environment.getClampedLightNorm()); - m_legacyAtmospherics.updateFog(distance, light_dir); - } - } - -void LLVOSky::setSunAndMoonDirectionsCFR(const LLVector3 &sun_dir_cfr, const LLVector3 &moon_dir_cfr) -{ - mSun.setDirection(sun_dir_cfr); - mMoon.setDirection(moon_dir_cfr); - - // Push the sun "South" as it approaches directly overhead so that we can always see bump mapping - // on the upward facing faces of cubes. - // Same as dot product with the up direction + clamp. - F32 sunDot = llmax(0.f, sun_dir_cfr.mV[2]); - sunDot *= sunDot; - - // Create normalized vector that has the sunDir pushed south about an hour and change. - LLVector3 adjustedDir = (sun_dir_cfr + LLVector3(0.f, -0.70711f, 0.70711f)) * 0.5f; - - // Blend between normal sun dir and adjusted sun dir based on how close we are - // to having the sun overhead. - mBumpSunDir = adjustedDir * sunDot + sun_dir_cfr * (1.0f - sunDot); - mBumpSunDir.normalize(); - - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - updateDirections(psky); -} - -void LLVOSky::setSunDirectionCFR(const LLVector3 &sun_dir_cfr) -{ - mSun.setDirection(sun_dir_cfr); - - // Push the sun "South" as it approaches directly overhead so that we can always see bump mapping - // on the upward facing faces of cubes. - // Same as dot product with the up direction + clamp. - F32 sunDot = llmax(0.f, sun_dir_cfr.mV[2]); - sunDot *= sunDot; - - // Create normalized vector that has the sunDir pushed south about an hour and change. - LLVector3 adjustedDir = (sun_dir_cfr + LLVector3(0.f, -0.70711f, 0.70711f)) * 0.5f; - - // Blend between normal sun dir and adjusted sun dir based on how close we are - // to having the sun overhead. - mBumpSunDir = adjustedDir * sunDot + sun_dir_cfr * (1.0f - sunDot); - mBumpSunDir.normalize(); - - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - updateDirections(psky); -} - -void LLVOSky::setMoonDirectionCFR(const LLVector3 &moon_dir_cfr) -{ - mMoon.setDirection(moon_dir_cfr); - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - updateDirections(psky); -} +/** + * @file llvosky.cpp + * @brief LLVOSky class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llvosky.h" + +#include "llfeaturemanager.h" +#include "llviewercontrol.h" +#include "llframetimer.h" + +#include "llagent.h" +#include "llagentcamera.h" +#include "lldrawable.h" +#include "llface.h" +#include "llcubemap.h" +#include "lldrawpoolsky.h" +#include "lldrawpoolwater.h" +#include "llglheaders.h" +#include "llsky.h" +#include "llviewercamera.h" +#include "llviewertexturelist.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llworld.h" +#include "pipeline.h" +#include "lldrawpoolwlsky.h" +#include "v3colorutil.h" + +#include "llsettingssky.h" +#include "llenvironment.h" + +#include "lltrace.h" +#include "llfasttimer.h" + +#undef min +#undef max + +namespace +{ + const S32 NUM_TILES_X = 8; + const S32 NUM_TILES_Y = 4; + const S32 NUM_TILES = NUM_TILES_X * NUM_TILES_Y; + const S32 NUM_CUBEMAP_FACES = 6; // See SKYTEX_RESOLUTION for face dimensions + const S32 TOTAL_TILES = NUM_CUBEMAP_FACES * NUM_TILES; + const S32 MAX_TILES = TOTAL_TILES + 1; + +// Heavenly body constants + const F32 SUN_DISK_RADIUS = 0.5f; + const F32 MOON_DISK_RADIUS = SUN_DISK_RADIUS * 0.9f; + const F32 SUN_INTENSITY = 1e5; + +// Texture coordinates: + const LLVector2 TEX00 = LLVector2(0.f, 0.f); + const LLVector2 TEX01 = LLVector2(0.f, 1.f); + const LLVector2 TEX10 = LLVector2(1.f, 0.f); + const LLVector2 TEX11 = LLVector2(1.f, 1.f); + + F32Seconds UPDATE_EXPRY(0.25f); + + const F32 UPDATE_MIN_DELTA_THRESHOLD = 0.0005f; +} +/*************************************** + SkyTex +***************************************/ + +S32 LLSkyTex::sCurrent = 0; + + +LLSkyTex::LLSkyTex() : + mSkyData(NULL), + mSkyDirs(NULL), + mIsShiny(false) +{ +} + +void LLSkyTex::init(bool isShiny) +{ + mIsShiny = isShiny; + mSkyData = new LLColor4[(U32)(SKYTEX_RESOLUTION * SKYTEX_RESOLUTION)]; + mSkyDirs = new LLVector3[(U32)(SKYTEX_RESOLUTION * SKYTEX_RESOLUTION)]; + + for (S32 i = 0; i < 2; ++i) + { + mTexture[i] = LLViewerTextureManager::getLocalTexture(false); + mTexture[i]->setAddressMode(LLTexUnit::TAM_CLAMP); + mImageRaw[i] = new LLImageRaw(SKYTEX_RESOLUTION, SKYTEX_RESOLUTION, SKYTEX_COMPONENTS); + + initEmpty(i); + } +} + +void LLSkyTex::cleanupGL() +{ + mTexture[0] = NULL; + mTexture[1] = NULL; +} + +void LLSkyTex::restoreGL() +{ + for (S32 i = 0; i < 2; i++) + { + mTexture[i] = LLViewerTextureManager::getLocalTexture(false); + mTexture[i]->setAddressMode(LLTexUnit::TAM_CLAMP); + } +} + +LLSkyTex::~LLSkyTex() +{ + delete[] mSkyData; + mSkyData = NULL; + + delete[] mSkyDirs; + mSkyDirs = NULL; +} + +S32 LLSkyTex::getResolution() +{ + return SKYTEX_RESOLUTION; +} + +S32 LLSkyTex::getCurrent() +{ + return sCurrent; +} + +S32 LLSkyTex::stepCurrent() { + sCurrent++; + sCurrent &= 1; + return sCurrent; +} + +S32 LLSkyTex::getNext() +{ + return ((sCurrent+1) & 1); +} + +S32 LLSkyTex::getWhich(const bool curr) +{ + int tex = curr ? sCurrent : getNext(); + return tex; +} + +void LLSkyTex::initEmpty(const S32 tex) +{ + LLImageDataLock lock(mImageRaw[tex]); + U8* data = mImageRaw[tex]->getData(); + for (S32 i = 0; i < SKYTEX_RESOLUTION; ++i) + { + for (S32 j = 0; j < SKYTEX_RESOLUTION; ++j) + { + const S32 basic_offset = (i * SKYTEX_RESOLUTION + j); + S32 offset = basic_offset * SKYTEX_COMPONENTS; + data[offset] = 0; + data[offset+1] = 0; + data[offset+2] = 0; + data[offset+3] = 255; + + mSkyData[basic_offset].setToBlack(); + } + } + + createGLImage(tex); +} + +void LLSkyTex::create() +{ + LLImageDataSharedLock lock(mImageRaw[sCurrent]); + const U8* data = mImageRaw[sCurrent]->getData(); + for (S32 i = 0; i < SKYTEX_RESOLUTION; ++i) + { + for (S32 j = 0; j < SKYTEX_RESOLUTION; ++j) + { + const S32 basic_offset = (i * SKYTEX_RESOLUTION + j); + S32 offset = basic_offset * SKYTEX_COMPONENTS; + U32* pix = (U32*)(data + offset); + LLColor4U temp = LLColor4U(mSkyData[basic_offset]); + *pix = temp.asRGBA(); + } + } + createGLImage(sCurrent); +} + +void LLSkyTex::createGLImage(S32 which) +{ + mTexture[which]->setExplicitFormat(GL_RGBA8, GL_RGBA); + mTexture[which]->createGLTexture(0, mImageRaw[which], 0, true, LLGLTexture::LOCAL); + mTexture[which]->setAddressMode(LLTexUnit::TAM_CLAMP); +} + +void LLSkyTex::bindTexture(bool curr) +{ + int tex = getWhich(curr); + gGL.getTexUnit(0)->bind(mTexture[tex], true); +} + +LLImageRaw* LLSkyTex::getImageRaw(bool curr) +{ + int tex = getWhich(curr); + return mImageRaw[tex]; +} + +/*************************************** + LLHeavenBody +***************************************/ + +F32 LLHeavenBody::sInterpVal = 0; + +LLHeavenBody::LLHeavenBody(const F32 rad) +: mDirectionCached(LLVector3(0,0,0)), + mDirection(LLVector3(0,0,0)), + mIntensity(0.f), + mDiskRadius(rad), + mDraw(false), + mHorizonVisibility(1.f), + mVisibility(1.f), + mVisible(false) +{ + mColor.setToBlack(); + mColorCached.setToBlack(); +} + +const LLQuaternion& LLHeavenBody::getRotation() const +{ + return mRotation; +} + +void LLHeavenBody::setRotation(const LLQuaternion& rot) +{ + mRotation = rot; +} + +const LLVector3& LLHeavenBody::getDirection() const +{ + return mDirection; +} + +void LLHeavenBody::setDirection(const LLVector3 &direction) +{ + mDirection = direction; +} + +void LLHeavenBody::setAngularVelocity(const LLVector3 &ang_vel) +{ + mAngularVelocity = ang_vel; +} + +const LLVector3& LLHeavenBody::getAngularVelocity() const +{ + return mAngularVelocity; +} + +const LLVector3& LLHeavenBody::getDirectionCached() const +{ + return mDirectionCached; +} + +void LLHeavenBody::renewDirection() +{ + mDirectionCached = mDirection; +} + +const LLColor3& LLHeavenBody::getColorCached() const +{ + return mColorCached; +} + +void LLHeavenBody::setColorCached(const LLColor3& c) +{ + mColorCached = c; +} + +const LLColor3& LLHeavenBody::getColor() const +{ + return mColor; +} + +void LLHeavenBody::setColor(const LLColor3& c) +{ + mColor = c; +} + +void LLHeavenBody::renewColor() +{ + mColorCached = mColor; +} + +F32 LLHeavenBody::interpVal() +{ + return sInterpVal; +} + +void LLHeavenBody::setInterpVal(const F32 v) +{ + sInterpVal = v; +} + +LLColor3 LLHeavenBody::getInterpColor() const +{ + return sInterpVal * mColor + (1 - sInterpVal) * mColorCached; +} + +const F32& LLHeavenBody::getVisibility() const +{ + return mVisibility; +} + +void LLHeavenBody::setVisibility(const F32 c) +{ + mVisibility = c; +} + +bool LLHeavenBody::isVisible() const +{ + return mVisible; +} + +void LLHeavenBody::setVisible(const bool v) +{ + mVisible = v; +} + +const F32& LLHeavenBody::getIntensity() const +{ + return mIntensity; +} + +void LLHeavenBody::setIntensity(const F32 c) +{ + mIntensity = c; +} + +void LLHeavenBody::setDiskRadius(const F32 radius) +{ + mDiskRadius = radius; +} + +F32 LLHeavenBody::getDiskRadius() const +{ + return mDiskRadius; +} + +void LLHeavenBody::setDraw(const bool draw) +{ + mDraw = draw; +} + +bool LLHeavenBody::getDraw() const +{ + return mDraw; +} + +const LLVector3& LLHeavenBody::corner(const S32 n) const +{ + return mQuadCorner[n]; +} + +LLVector3& LLHeavenBody::corner(const S32 n) +{ + return mQuadCorner[n]; +} + +const LLVector3* LLHeavenBody::corners() const +{ + return mQuadCorner; +} + +/*************************************** + Sky +***************************************/ + +const S32 SKYTEX_TILE_RES_X = SKYTEX_RESOLUTION / NUM_TILES_X; +const S32 SKYTEX_TILE_RES_Y = SKYTEX_RESOLUTION / NUM_TILES_Y; + +LLVOSky::LLVOSky(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) +: LLStaticViewerObject(id, pcode, regionp, true), + mSun(SUN_DISK_RADIUS), mMoon(MOON_DISK_RADIUS), + mBrightnessScale(1.f), + mBrightnessScaleNew(0.f), + mBrightnessScaleGuess(1.f), + mWeatherChange(false), + mCloudDensity(0.2f), + mWind(0.f), + mForceUpdate(false), + mNeedUpdate(true), + mCubeMapUpdateStage(-1), + mWorldScale(1.f), + mBumpSunDir(0.f, 0.f, 1.f) +{ + /// WL PARAMS + + mInitialized = false; + mbCanSelect = false; + mUpdateTimer.reset(); + + mForceUpdateThrottle.setTimerExpirySec(UPDATE_EXPRY); + mForceUpdateThrottle.reset(); + + for (S32 i = 0; i < NUM_CUBEMAP_FACES; i++) + { + mSkyTex[i].init(false); + mShinyTex[i].init(true); + } + for (S32 i=0; iupdate(); + + updateDirections(psky); + + cacheEnvironment(psky,m_atmosphericsVars); + + // Initialize the cached normalized direction vectors + for (S32 side = 0; side < NUM_CUBEMAP_FACES; ++side) + { + for (S32 tile = 0; tile < NUM_TILES; ++tile) + { + initSkyTextureDirs(side, tile); + createSkyTexture(psky, m_atmosphericsVars, side, tile); + } + mSkyTex[side].create(); + mShinyTex[side].create(); + } + + initCubeMap(); + + mInitialized = true; + + mHeavenlyBodyUpdated = false ; + + mRainbowMap = LLViewerTextureManager::getFetchedTexture(psky->getRainbowTextureId(), FTT_DEFAULT, true, LLGLTexture::BOOST_UI); + mHaloMap = LLViewerTextureManager::getFetchedTexture(psky->getHaloTextureId(), FTT_DEFAULT, true, LLGLTexture::BOOST_UI); +} + + +void LLVOSky::cacheEnvironment(LLSettingsSky::ptr_t psky,AtmosphericsVars& atmosphericsVars) +{ + // NOTE: Also see: LLAtmospherics::updateFog() + // invariants across whole sky tex process... + atmosphericsVars.blue_density = psky->getBlueDensity(); + atmosphericsVars.blue_horizon = psky->getBlueHorizon(); + atmosphericsVars.haze_density = psky->getHazeDensity(); + atmosphericsVars.haze_horizon = psky->getHazeHorizon(); + atmosphericsVars.density_multiplier = psky->getDensityMultiplier(); + atmosphericsVars.distance_multiplier = psky->getDistanceMultiplier(); + atmosphericsVars.max_y = psky->getMaxY(); + atmosphericsVars.sun_norm = LLEnvironment::instance().getClampedSunNorm(); + atmosphericsVars.sunlight = psky->getIsSunUp() ? psky->getSunlightColor() : psky->getMoonlightColor(); + atmosphericsVars.ambient = psky->getAmbientColor(); + atmosphericsVars.glow = psky->getGlow(); + atmosphericsVars.cloud_shadow = psky->getCloudShadow(); + atmosphericsVars.dome_radius = psky->getDomeRadius(); + atmosphericsVars.dome_offset = psky->getDomeOffset(); + atmosphericsVars.light_atten = psky->getLightAttenuation(atmosphericsVars.max_y); + atmosphericsVars.light_transmittance = psky->getLightTransmittance(atmosphericsVars.max_y); + atmosphericsVars.total_density = psky->getTotalDensity(); + atmosphericsVars.gamma = psky->getGamma(); +} + +void LLVOSky::calc() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT; + LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); + cacheEnvironment(psky,m_atmosphericsVars); + + mSun.setColor(psky->getSunDiffuse()); + mMoon.setColor(LLColor3(1.0f, 1.0f, 1.0f)); + + mSun.renewDirection(); + mSun.renewColor(); + mMoon.renewDirection(); + mMoon.renewColor(); +} + +void LLVOSky::initCubeMap() +{ + std::vector > images; + for (S32 side = 0; side < NUM_CUBEMAP_FACES; side++) + { + images.push_back(mShinyTex[side].getImageRaw()); + } + + if (!mCubeMap && gSavedSettings.getBOOL("RenderWater") && LLCubeMap::sUseCubeMaps) + { + mCubeMap = new LLCubeMap(false); + } + + if (mCubeMap) + { + mCubeMap->init(images); + } + + gGL.getTexUnit(0)->disable(); +} + + +void LLVOSky::cleanupGL() +{ + S32 i; + for (i = 0; i < NUM_CUBEMAP_FACES; i++) + { + mSkyTex[i].cleanupGL(); + } + if (getCubeMap()) + { + getCubeMap()->destroyGL(); + } +} + +void LLVOSky::restoreGL() +{ + S32 i; + for (i = 0; i < NUM_CUBEMAP_FACES; i++) + { + mSkyTex[i].restoreGL(); + } + + LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); + + if (psky) + { + setSunTextures(psky->getSunTextureId(), psky->getNextSunTextureId()); + setMoonTextures(psky->getMoonTextureId(), psky->getNextMoonTextureId()); + } + + updateDirections(psky); + + if (gSavedSettings.getBOOL("RenderWater") && LLCubeMap::sUseCubeMaps) + { + initCubeMap(); + } + + forceSkyUpdate(); + + if (mDrawable) + { + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); + } + +} + +void LLVOSky::initSkyTextureDirs(const S32 side, const S32 tile) +{ + S32 tile_x = tile % NUM_TILES_X; + S32 tile_y = tile / NUM_TILES_X; + + S32 tile_x_pos = tile_x * SKYTEX_TILE_RES_X; + S32 tile_y_pos = tile_y * SKYTEX_TILE_RES_Y; + + F32 coeff[3] = {0, 0, 0}; + const S32 curr_coef = side >> 1; // 0/1 = Z axis, 2/3 = Y, 4/5 = X + const S32 side_dir = (((side & 1) << 1) - 1); // even = -1, odd = 1 + const S32 x_coef = (curr_coef + 1) % 3; + const S32 y_coef = (x_coef + 1) % 3; + + coeff[curr_coef] = (F32)side_dir; + + F32 inv_res = 1.f/SKYTEX_RESOLUTION; + S32 x, y; + for (y = tile_y_pos; y < (tile_y_pos + SKYTEX_TILE_RES_Y); ++y) + { + for (x = tile_x_pos; x < (tile_x_pos + SKYTEX_TILE_RES_X); ++x) + { + coeff[x_coef] = F32((x<<1) + 1) * inv_res - 1.f; + coeff[y_coef] = F32((y<<1) + 1) * inv_res - 1.f; + LLVector3 dir(coeff[0], coeff[1], coeff[2]); + dir.normalize(); + mSkyTex[side].setDir(dir, x, y); + mShinyTex[side].setDir(dir, x, y); + } + } +} + +void LLVOSky::createSkyTexture(const LLSettingsSky::ptr_t &psky, AtmosphericsVars& vars, const S32 side, const S32 tile) +{ + const bool low_end = !gPipeline.canUseWindLightShaders(); + + S32 tile_x = tile % NUM_TILES_X; + S32 tile_y = tile / NUM_TILES_X; + + S32 tile_x_pos = tile_x * SKYTEX_TILE_RES_X; + S32 tile_y_pos = tile_y * SKYTEX_TILE_RES_Y; + + S32 x, y; + for (y = tile_y_pos; y < (tile_y_pos + SKYTEX_TILE_RES_Y); ++y) + { + for (x = tile_x_pos; x < (tile_x_pos + SKYTEX_TILE_RES_X); ++x) + { + mSkyTex [side].setPixel(m_legacyAtmospherics.calcSkyColorInDir(psky, vars, mSkyTex [side].getDir(x, y), false, low_end), x, y); + mShinyTex[side].setPixel(m_legacyAtmospherics.calcSkyColorInDir(psky, vars, mShinyTex[side].getDir(x, y), true , low_end), x, y); + } + } +} + +void LLVOSky::updateDirections(LLSettingsSky::ptr_t psky) +{ + mSun.setDirection(psky->getSunDirection()); + mMoon.setDirection(psky->getMoonDirection()); + mSun.setRotation(psky->getSunRotation()); + mMoon.setRotation(psky->getMoonRotation()); + mSun.renewDirection(); + mMoon.renewDirection(); +} + +void LLVOSky::idleUpdate(LLAgent &agent, const F64 &time) +{ +} + +void LLVOSky::forceSkyUpdate() +{ + mForceUpdate = true; + m_lastAtmosphericsVars = {}; + mCubeMapUpdateStage = -1; +} + +bool LLVOSky::updateSky() +{ + LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); + + if (mDead || !(gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_SKY))) + { + // It's dead. Don't update it. + return true; + } + + if (gGLManager.mIsDisabled) + { + return true; + } + + LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT; + + static S32 next_frame = 0; + + mNeedUpdate = mForceUpdate; + + ++next_frame; + next_frame = next_frame % MAX_TILES; + + mInterpVal = (!mInitialized) ? 1 : (F32)next_frame / MAX_TILES; + LLHeavenBody::setInterpVal( mInterpVal ); + updateDirections(psky); + + if (!mCubeMap || LLPipeline::sReflectionProbesEnabled) + { + mCubeMapUpdateStage = NUM_CUBEMAP_FACES; + mForceUpdate = false; + return true; + } + + if (mCubeMapUpdateStage < 0) + { + calc(); + + bool same_atmospherics = approximatelyEqual(m_lastAtmosphericsVars, m_atmosphericsVars, UPDATE_MIN_DELTA_THRESHOLD); + + mNeedUpdate = mNeedUpdate || !same_atmospherics; + + if (mNeedUpdate && (mForceUpdateThrottle.hasExpired() || mForceUpdate)) + { + // start updating cube map sides + updateFog(LLViewerCamera::getInstance()->getFar()); + mCubeMapUpdateStage = 0; + mForceUpdate = false; + } + } + else if (mCubeMapUpdateStage == NUM_CUBEMAP_FACES && !LLPipeline::sReflectionProbesEnabled) + { + LL_PROFILE_ZONE_NAMED("updateSky - forced"); + LLSkyTex::stepCurrent(); + + bool is_alm_wl_sky = gPipeline.canUseWindLightShaders(); + + int tex = mSkyTex[0].getWhich(true); + + for (int side = 0; side < NUM_CUBEMAP_FACES; side++) + { + LLImageRaw* raw1 = nullptr; + LLImageRaw* raw2 = nullptr; + + if (!is_alm_wl_sky) + { + raw1 = mSkyTex[side].getImageRaw(true); + raw2 = mSkyTex[side].getImageRaw(false); + raw2->copy(raw1); + mSkyTex[side].createGLImage(tex); + } + + raw1 = mShinyTex[side].getImageRaw(true); + raw2 = mShinyTex[side].getImageRaw(false); + raw2->copy(raw1); + mShinyTex[side].createGLImage(tex); + } + next_frame = 0; + + // update the sky texture + if (!is_alm_wl_sky) + { + for (S32 i = 0; i < NUM_CUBEMAP_FACES; ++i) + { + mSkyTex[i].create(); + } + } + + for (S32 i = 0; i < NUM_CUBEMAP_FACES; ++i) + { + mShinyTex[i].create(); + } + + // update the environment map + initCubeMap(); + + m_lastAtmosphericsVars = m_atmosphericsVars; + + mNeedUpdate = false; + mForceUpdate = false; + + mForceUpdateThrottle.setTimerExpirySec(UPDATE_EXPRY); + + if (mDrawable.notNull() && mDrawable->getFace(0) && !mDrawable->getFace(0)->getVertexBuffer()) + { + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); + } + mCubeMapUpdateStage = -1; + } + // run 0 to 5 faces, each face in own frame + else if (mCubeMapUpdateStage >= 0 && mCubeMapUpdateStage < NUM_CUBEMAP_FACES && !LLPipeline::sReflectionProbesEnabled) + { + LL_PROFILE_ZONE_NAMED("updateSky - create"); + S32 side = mCubeMapUpdateStage; + // CPU hungry part, createSkyTexture() is math heavy + // Prior to EEP it was mostly per tile, but since EPP it is per face. + // This still can be optimized further + // (i.e. potentially can be made per tile again, can be moved to thread + // instead of executing per face, or may be can be moved to shaders) + for (S32 tile = 0; tile < NUM_TILES; tile++) + { + createSkyTexture(psky, m_atmosphericsVars, side, tile); + } + mCubeMapUpdateStage++; + } + + return true; +} + +void LLVOSky::updateTextures() +{ + if (mSunTexturep[0]) + { + mSunTexturep[0]->addTextureStats( (F32)MAX_IMAGE_AREA ); + } + + if (mSunTexturep[1]) + { + mSunTexturep[1]->addTextureStats( (F32)MAX_IMAGE_AREA ); +} + + if (mMoonTexturep[0]) +{ + mMoonTexturep[0]->addTextureStats( (F32)MAX_IMAGE_AREA ); +} + + if (mMoonTexturep[1]) +{ + mMoonTexturep[1]->addTextureStats( (F32)MAX_IMAGE_AREA ); +} + + if (mBloomTexturep[0]) + { + mBloomTexturep[0]->addTextureStats( (F32)MAX_IMAGE_AREA ); + } + + if (mBloomTexturep[1]) + { + mBloomTexturep[1]->addTextureStats( (F32)MAX_IMAGE_AREA ); + } + } + +LLDrawable *LLVOSky::createDrawable(LLPipeline *pipeline) +{ + pipeline->allocDrawable(this); + mDrawable->setLit(false); + + LLDrawPoolSky *poolp = (LLDrawPoolSky*) gPipeline.getPool(LLDrawPool::POOL_SKY); + poolp->setSkyTex(mSkyTex); + mDrawable->setRenderType(LLPipeline::RENDER_TYPE_SKY); + + for (S32 i = 0; i < NUM_CUBEMAP_FACES; ++i) + { + mFace[FACE_SIDE0 + i] = mDrawable->addFace(poolp, NULL); + } + + mFace[FACE_SUN] = mDrawable->addFace(poolp, nullptr); + mFace[FACE_MOON] = mDrawable->addFace(poolp, nullptr); + mFace[FACE_BLOOM] = mDrawable->addFace(poolp, nullptr); + + mFace[FACE_SUN]->setMediaAllowed(false); + mFace[FACE_MOON]->setMediaAllowed(false); + mFace[FACE_BLOOM]->setMediaAllowed(false); + + return mDrawable; +} + +void LLVOSky::setSunScale(F32 sun_scale) +{ + mSunScale = sun_scale; +} + +void LLVOSky::setMoonScale(F32 moon_scale) +{ + mMoonScale = moon_scale; +} + +void LLVOSky::setSunTextures(const LLUUID& sun_texture, const LLUUID& sun_texture_next) +{ + // We test the UUIDs here because we explicitly do not want the default image returned by getFetchedTexture in that case... + mSunTexturep[0] = sun_texture.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(sun_texture, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); + mSunTexturep[1] = sun_texture_next.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(sun_texture_next, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); + + bool can_use_wl = gPipeline.canUseWindLightShaders(); + + if (mFace[FACE_SUN]) + { + if (mSunTexturep[0]) + { + mSunTexturep[0]->setAddressMode(LLTexUnit::TAM_CLAMP); + } + + LLViewerTexture* current_tex0 = mFace[FACE_SUN]->getTexture(LLRender::DIFFUSE_MAP); + LLViewerTexture* current_tex1 = mFace[FACE_SUN]->getTexture(LLRender::ALTERNATE_DIFFUSE_MAP); + + if (current_tex0 && (mSunTexturep[0] != current_tex0) && current_tex0->isViewerMediaTexture()) + { + static_cast(current_tex0)->removeMediaFromFace(mFace[FACE_SUN]); + } + + if (current_tex1 && (mSunTexturep[1] != current_tex1) && current_tex1->isViewerMediaTexture()) + { + static_cast(current_tex1)->removeMediaFromFace(mFace[FACE_SUN]); + } + + mFace[FACE_SUN]->setTexture(LLRender::DIFFUSE_MAP, mSunTexturep[0]); + + if (can_use_wl) + { + if (mSunTexturep[1]) + { + mSunTexturep[1]->setAddressMode(LLTexUnit::TAM_CLAMP); + } + mFace[FACE_SUN]->setTexture(LLRender::ALTERNATE_DIFFUSE_MAP, mSunTexturep[1]); + } + } +} + +void LLVOSky::setMoonTextures(const LLUUID& moon_texture, const LLUUID& moon_texture_next) +{ + LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); + + bool can_use_wl = gPipeline.canUseWindLightShaders(); + + mMoonTexturep[0] = moon_texture.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(moon_texture, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); + mMoonTexturep[1] = moon_texture_next.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(moon_texture_next, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); + + if (mFace[FACE_MOON]) + { + if (mMoonTexturep[0]) + { + mMoonTexturep[0]->setAddressMode(LLTexUnit::TAM_CLAMP); + } + mFace[FACE_MOON]->setTexture(LLRender::DIFFUSE_MAP, mMoonTexturep[0]); + + if (mMoonTexturep[1] && can_use_wl) + { + mMoonTexturep[1]->setAddressMode(LLTexUnit::TAM_CLAMP); + mFace[FACE_MOON]->setTexture(LLRender::ALTERNATE_DIFFUSE_MAP, mMoonTexturep[1]); + } + } +} + +void LLVOSky::setCloudNoiseTextures(const LLUUID& cloud_noise_texture, const LLUUID& cloud_noise_texture_next) +{ + LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); + + mCloudNoiseTexturep[0] = cloud_noise_texture.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(cloud_noise_texture, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); + mCloudNoiseTexturep[1] = cloud_noise_texture_next.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(cloud_noise_texture_next, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); + + if (mCloudNoiseTexturep[0]) + { + mCloudNoiseTexturep[0]->setAddressMode(LLTexUnit::TAM_WRAP); + } + + if (mCloudNoiseTexturep[1]) + { + mCloudNoiseTexturep[1]->setAddressMode(LLTexUnit::TAM_WRAP); + } +} + +void LLVOSky::setBloomTextures(const LLUUID& bloom_texture, const LLUUID& bloom_texture_next) +{ + LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); + + LLUUID bloom_tex = bloom_texture.isNull() ? psky->GetDefaultBloomTextureId() : bloom_texture; + LLUUID bloom_tex_next = bloom_texture_next.isNull() ? (bloom_texture.isNull() ? psky->GetDefaultBloomTextureId() : bloom_texture) : bloom_texture_next; + + mBloomTexturep[0] = bloom_tex.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(bloom_tex, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); + mBloomTexturep[1] = bloom_tex_next.isNull() ? nullptr : LLViewerTextureManager::getFetchedTexture(bloom_tex_next, FTT_DEFAULT, true, LLGLTexture::BOOST_UI); + + if (mBloomTexturep[0]) + { + mBloomTexturep[0]->setAddressMode(LLTexUnit::TAM_CLAMP); + } + + if (mBloomTexturep[1]) + { + mBloomTexturep[1]->setAddressMode(LLTexUnit::TAM_CLAMP); + } +} + +bool LLVOSky::updateGeometry(LLDrawable *drawable) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWABLE; + if (mFace[FACE_REFLECTION] == NULL) + { + LLDrawPoolWater *poolp = (LLDrawPoolWater*) gPipeline.getPool(LLDrawPool::POOL_WATER); + if (gPipeline.getPool(LLDrawPool::POOL_WATER)->getShaderLevel() != 0) + { + mFace[FACE_REFLECTION] = drawable->addFace(poolp, NULL); + } + } + + mCameraPosAgent = drawable->getPositionAgent(); + + mEarthCenter.mV[0] = mCameraPosAgent.mV[0]; + mEarthCenter.mV[1] = mCameraPosAgent.mV[1]; + + LLVector3 v_agent[8]; + for (S32 i = 0; i < 8; ++i) + { + F32 x_sgn = (i&1) ? 1.f : -1.f; + F32 y_sgn = (i&2) ? 1.f : -1.f; + F32 z_sgn = (i&4) ? 1.f : -1.f; + v_agent[i] = HORIZON_DIST * SKY_BOX_MULT * LLVector3(x_sgn, y_sgn, z_sgn); + } + + LLStrider verticesp; + LLStrider normalsp; + LLStrider texCoordsp; + LLStrider indicesp; + U16 index_offset; + LLFace *face; + + for (S32 side = 0; side < NUM_CUBEMAP_FACES; ++side) + { + face = mFace[FACE_SIDE0 + side]; + + if (!face->getVertexBuffer()) + { + face->setSize(4, 6); + face->setGeomIndex(0); + face->setIndicesIndex(0); + LLVertexBuffer* buff = new LLVertexBuffer(LLDrawPoolSky::VERTEX_DATA_MASK); + buff->allocateBuffer(4, 6); + face->setVertexBuffer(buff); + + index_offset = face->getGeometry(verticesp,normalsp,texCoordsp, indicesp); + + S32 vtx = 0; + S32 curr_bit = side >> 1; // 0/1 = Z axis, 2/3 = Y, 4/5 = X + S32 side_dir = side & 1; // even - 0, odd - 1 + S32 i_bit = (curr_bit + 2) % 3; + S32 j_bit = (i_bit + 2) % 3; + + LLVector3 axis; + axis.mV[curr_bit] = 1; + face->mCenterAgent = (F32)((side_dir << 1) - 1) * axis * HORIZON_DIST; + + vtx = side_dir << curr_bit; + *(verticesp++) = v_agent[vtx]; + *(verticesp++) = v_agent[vtx | 1 << j_bit]; + *(verticesp++) = v_agent[vtx | 1 << i_bit]; + *(verticesp++) = v_agent[vtx | 1 << i_bit | 1 << j_bit]; + + *(texCoordsp++) = TEX00; + *(texCoordsp++) = TEX01; + *(texCoordsp++) = TEX10; + *(texCoordsp++) = TEX11; + + // Triangles for each side + *indicesp++ = index_offset + 0; + *indicesp++ = index_offset + 1; + *indicesp++ = index_offset + 3; + + *indicesp++ = index_offset + 0; + *indicesp++ = index_offset + 3; + *indicesp++ = index_offset + 2; + + buff->unmapBuffer(); + } + } + + const LLVector3 &look_at = LLViewerCamera::getInstance()->getAtAxis(); + LLVector3 right = look_at % LLVector3::z_axis; + LLVector3 up = right % look_at; + right.normalize(); + up.normalize(); + + bool draw_sun = updateHeavenlyBodyGeometry(drawable, mSunScale, FACE_SUN, mSun, up, right); + bool draw_moon = updateHeavenlyBodyGeometry(drawable, mMoonScale, FACE_MOON, mMoon, up, right); + + draw_sun &= LLEnvironment::getInstance()->getIsSunUp(); + draw_moon &= LLEnvironment::getInstance()->getIsMoonUp(); + + mSun.setDraw(draw_sun); + mMoon.setDraw(draw_moon); + + const F32 water_height = gAgent.getRegion()->getWaterHeight() + 0.01f; + // LLWorld::getInstance()->getWaterHeight() + 0.01f; + const F32 camera_height = mCameraPosAgent.mV[2]; + const F32 height_above_water = camera_height - water_height; + + bool sun_flag = false; + if (mSun.isVisible()) + { + sun_flag = !mMoon.isVisible() || ((look_at * mSun.getDirection()) > 0); + } + + bool above_water = (height_above_water > 0); + bool render_ref = above_water && gPipeline.getPool(LLDrawPool::POOL_WATER)->getShaderLevel() == 0; + setDrawRefl(above_water ? (sun_flag ? 0 : 1) : -1); + if (render_ref) + { + updateReflectionGeometry(drawable, height_above_water, mSun); + } + + LLPipeline::sCompiles++; + return true; +} + +bool LLVOSky::updateHeavenlyBodyGeometry(LLDrawable *drawable, F32 scale, const S32 f, LLHeavenBody& hb, const LLVector3 &up, const LLVector3 &right) +{ + mHeavenlyBodyUpdated = true ; + + LLStrider verticesp; + LLStrider normalsp; + LLStrider texCoordsp; + LLStrider indicesp; + S32 index_offset; + LLFace *facep; + + + LLQuaternion rot = hb.getRotation(); + LLVector3 to_dir = LLVector3::x_axis * rot; + + LLVector3 hb_right = to_dir % LLVector3::z_axis; + LLVector3 hb_up = hb_right % to_dir; + + // at zenith so math below fails spectacularly + if ((to_dir * LLVector3::z_axis) > 0.99f) + { + hb_right = LLVector3::y_axis_neg * rot; + hb_up = LLVector3::z_axis * rot; + } + + LLVector3 draw_pos = to_dir * HEAVENLY_BODY_DIST; + + hb_right.normalize(); + hb_up.normalize(); + + const F32 enlargm_factor = ( 1 - to_dir.mV[2] ); + F32 horiz_enlargement = 1 + enlargm_factor * 0.3f; + F32 vert_enlargement = 1 + enlargm_factor * 0.2f; + + const LLVector3 scaled_right = horiz_enlargement * scale * HEAVENLY_BODY_DIST * HEAVENLY_BODY_FACTOR * hb.getDiskRadius() * hb_right; + const LLVector3 scaled_up = vert_enlargement * scale * HEAVENLY_BODY_DIST * HEAVENLY_BODY_FACTOR * hb.getDiskRadius() * hb_up; + + LLVector3 v_clipped[4]; + + v_clipped[0] = draw_pos - scaled_right + scaled_up; + v_clipped[1] = draw_pos - scaled_right - scaled_up; + v_clipped[2] = draw_pos + scaled_right + scaled_up; + v_clipped[3] = draw_pos + scaled_right - scaled_up; + + hb.setVisible(true); + + facep = mFace[f]; + + if (!facep->getVertexBuffer()) + { + facep->setSize(4, 6); + LLVertexBuffer* buff = new LLVertexBuffer(LLDrawPoolSky::VERTEX_DATA_MASK); + if (!buff->allocateBuffer(facep->getGeomCount(), facep->getIndicesCount())) + { + LL_WARNS() << "Failed to allocate Vertex Buffer for vosky to " + << facep->getGeomCount() << " vertices and " + << facep->getIndicesCount() << " indices" << LL_ENDL; + } + facep->setGeomIndex(0); + facep->setIndicesIndex(0); + facep->setVertexBuffer(buff); + } + + llassert(facep->getVertexBuffer()->getNumIndices() == 6); + + index_offset = facep->getGeometry(verticesp,normalsp,texCoordsp, indicesp); + + if (-1 == index_offset) + { + return true; + } + + for (S32 vtx = 0; vtx < 4; ++vtx) + { + hb.corner(vtx) = v_clipped[vtx]; + *(verticesp++) = hb.corner(vtx) + mCameraPosAgent; + } + + *(texCoordsp++) = TEX01; + *(texCoordsp++) = TEX00; + *(texCoordsp++) = TEX11; + *(texCoordsp++) = TEX10; + + *indicesp++ = index_offset + 0; + *indicesp++ = index_offset + 2; + *indicesp++ = index_offset + 1; + + *indicesp++ = index_offset + 1; + *indicesp++ = index_offset + 2; + *indicesp++ = index_offset + 3; + + facep->getVertexBuffer()->unmapBuffer(); + + return true; +} + +F32 dtReflection(const LLVector3& p, F32 cos_dir_from_top, F32 sin_dir_from_top, F32 diff_angl_dir) +{ + LLVector3 P = p; + P.normalize(); + + const F32 cos_dir_angle = -P.mV[VZ]; + const F32 sin_dir_angle = sqrt(1 - cos_dir_angle * cos_dir_angle); + + F32 cos_diff_angles = cos_dir_angle * cos_dir_from_top + + sin_dir_angle * sin_dir_from_top; + + F32 diff_angles; + if (cos_diff_angles > (1 - 1e-7)) + diff_angles = 0; + else + diff_angles = acos(cos_diff_angles); + + const F32 rel_diff_angles = diff_angles / diff_angl_dir; + const F32 dt = 1 - rel_diff_angles; + + return (dt < 0) ? 0 : dt; +} + + +F32 dtClip(const LLVector3& v0, const LLVector3& v1, F32 far_clip2) +{ + F32 dt_clip; + const LLVector3 otrezok = v1 - v0; + const F32 A = otrezok.lengthSquared(); + const F32 B = v0 * otrezok; + const F32 C = v0.lengthSquared() - far_clip2; + const F32 det = sqrt(B*B - A*C); + dt_clip = (-B - det) / A; + if ((dt_clip < 0) || (dt_clip > 1)) + dt_clip = (-B + det) / A; + return dt_clip; +} + + +void LLVOSky::updateReflectionGeometry(LLDrawable *drawable, F32 H, + const LLHeavenBody& HB) +{ + const LLVector3 &look_at = LLViewerCamera::getInstance()->getAtAxis(); + // const F32 water_height = gAgent.getRegion()->getWaterHeight() + 0.001f; + // LLWorld::getInstance()->getWaterHeight() + 0.001f; + + LLVector3 to_dir = HB.getDirection(); + LLVector3 hb_pos = to_dir * (HORIZON_DIST - 10); + LLVector3 to_dir_proj = to_dir; + to_dir_proj.mV[VZ] = 0; + to_dir_proj.normalize(); + + LLVector3 Right = to_dir % LLVector3::z_axis; + LLVector3 Up = Right % to_dir; + Right.normalize(); + Up.normalize(); + + // finding angle between look direction and sprite. + LLVector3 look_at_right = look_at % LLVector3::z_axis; + look_at_right.normalize(); + + const F32 enlargm_factor = ( 1 - to_dir.mV[2] ); + F32 horiz_enlargement = 1 + enlargm_factor * 0.3f; + F32 vert_enlargement = 1 + enlargm_factor * 0.2f; + + F32 vert_size = vert_enlargement * HEAVENLY_BODY_SCALE * HB.getDiskRadius(); + Right *= /*cos_lookAt_toDir */ horiz_enlargement * HEAVENLY_BODY_SCALE * HB.getDiskRadius(); + Up *= vert_size; + + LLVector3 v_corner[2]; + LLVector3 stretch_corner[2]; + + LLVector3 top_hb = v_corner[0] = stretch_corner[0] = hb_pos - Right + Up; + v_corner[1] = stretch_corner[1] = hb_pos - Right - Up; + + LLVector2 TEX0t = TEX00; + LLVector2 TEX1t = TEX10; + LLVector3 lower_corner = v_corner[1]; + + top_hb.normalize(); + const F32 cos_angle_of_view = fabs(top_hb.mV[VZ]); + const F32 extension = llmin (5.0f, 1.0f / cos_angle_of_view); + + const S32 cols = 1; + const S32 raws = lltrunc(16 * extension); + S32 quads = cols * raws; + + stretch_corner[0] = lower_corner + extension * (stretch_corner[0] - lower_corner); + stretch_corner[1] = lower_corner + extension * (stretch_corner[1] - lower_corner); + + F32 cos_dir_from_top[2]; + + LLVector3 dir = stretch_corner[0]; + dir.normalize(); + cos_dir_from_top[0] = dir.mV[VZ]; + + dir = stretch_corner[1]; + dir.normalize(); + cos_dir_from_top[1] = dir.mV[VZ]; + + const F32 sin_dir_from_top = sqrt(1 - cos_dir_from_top[0] * cos_dir_from_top[0]); + const F32 sin_dir_from_top2 = sqrt(1 - cos_dir_from_top[1] * cos_dir_from_top[1]); + const F32 cos_diff_dir = cos_dir_from_top[0] * cos_dir_from_top[1] + + sin_dir_from_top * sin_dir_from_top2; + const F32 diff_angl_dir = acos(cos_diff_dir); + + v_corner[0] = stretch_corner[0]; + v_corner[1] = lower_corner; + + + LLVector2 TEX0tt = TEX01; + LLVector2 TEX1tt = TEX11; + + LLVector3 v_refl_corner[4]; + LLVector3 v_sprite_corner[4]; + + S32 vtx; + for (vtx = 0; vtx < 2; ++vtx) + { + LLVector3 light_proj = v_corner[vtx]; + light_proj.normalize(); + + const F32 z = light_proj.mV[VZ]; + const F32 sin_angle = sqrt(1 - z * z); + light_proj *= 1.f / sin_angle; + light_proj.mV[VZ] = 0; + const F32 to_refl_point = H * sin_angle / fabs(z); + + v_refl_corner[vtx] = to_refl_point * light_proj; + } + + + for (vtx = 2; vtx < 4; ++vtx) + { + const LLVector3 to_dir_vec = (to_dir_proj * v_refl_corner[vtx-2]) * to_dir_proj; + v_refl_corner[vtx] = v_refl_corner[vtx-2] + 2 * (to_dir_vec - v_refl_corner[vtx-2]); + } + + for (vtx = 0; vtx < 4; ++vtx) + v_refl_corner[vtx].mV[VZ] -= H; + + S32 side = 0; + LLVector3 refl_corn_norm[2]; + refl_corn_norm[0] = v_refl_corner[1]; + refl_corn_norm[0].normalize(); + refl_corn_norm[1] = v_refl_corner[3]; + refl_corn_norm[1].normalize(); + + F32 cos_refl_look_at[2]; + cos_refl_look_at[0] = refl_corn_norm[0] * look_at; + cos_refl_look_at[1] = refl_corn_norm[1] * look_at; + + if (cos_refl_look_at[1] > cos_refl_look_at[0]) + { + side = 2; + } + + //const F32 far_clip = (LLViewerCamera::getInstance()->getFar() - 0.01) / far_clip_factor; + const F32 far_clip = 512; + const F32 far_clip2 = far_clip*far_clip; + + F32 dt_clip; + F32 vtx_near2, vtx_far2; + + if ((vtx_far2 = v_refl_corner[side].lengthSquared()) > far_clip2) + { + // whole thing is sprite: reflection is beyond far clip plane. + dt_clip = 1.1f; + quads = 1; + } + else if ((vtx_near2 = v_refl_corner[side+1].lengthSquared()) > far_clip2) + { + // part is reflection, the rest is sprite. + dt_clip = dtClip(v_refl_corner[side + 1], v_refl_corner[side], far_clip2); + const LLVector3 P = (1 - dt_clip) * v_refl_corner[side + 1] + dt_clip * v_refl_corner[side]; + + F32 dt_tex = dtReflection(P, cos_dir_from_top[0], sin_dir_from_top, diff_angl_dir); + + TEX0tt = LLVector2(0, dt_tex); + TEX1tt = LLVector2(1, dt_tex); + quads++; + } + else + { + // whole thing is correct reflection. + dt_clip = -0.1f; + } + + LLFace *face = mFace[FACE_REFLECTION]; + + if (face) + { + if (!face->getVertexBuffer() || quads*4 != face->getGeomCount()) + { + face->setSize(quads * 4, quads * 6); + LLVertexBuffer* buff = new LLVertexBuffer(LLDrawPoolWater::VERTEX_DATA_MASK); + if (!buff->allocateBuffer(face->getGeomCount(), face->getIndicesCount())) + { + LL_WARNS() << "Failed to allocate Vertex Buffer for vosky to " + << face->getGeomCount() << " vertices and " + << face->getIndicesCount() << " indices" << LL_ENDL; + } + face->setIndicesIndex(0); + face->setGeomIndex(0); + face->setVertexBuffer(buff); + } + + LLStrider verticesp; + LLStrider normalsp; + LLStrider texCoordsp; + LLStrider indicesp; + S32 index_offset; + + index_offset = face->getGeometry(verticesp,normalsp,texCoordsp, indicesp); + if (-1 == index_offset) + { + return; + } + + LLColor3 hb_col3 = HB.getInterpColor(); + hb_col3.clamp(); + const LLColor4 hb_col = LLColor4(hb_col3); + + const F32 min_attenuation = 0.4f; + const F32 max_attenuation = 0.7f; + const F32 attenuation = min_attenuation + + cos_angle_of_view * (max_attenuation - min_attenuation); + + LLColor4 hb_refl_col = (1 - attenuation) * hb_col + attenuation * getSkyFogColor(); + face->setFaceColor(hb_refl_col); + + LLVector3 v_far[2]; + v_far[0] = v_refl_corner[1]; + v_far[1] = v_refl_corner[3]; + + if(dt_clip > 0) + { + if (dt_clip >= 1) + { + for (S32 vtx = 0; vtx < 4; ++vtx) + { + F32 ratio = far_clip / v_refl_corner[vtx].length(); + *(verticesp++) = v_refl_corner[vtx] = ratio * v_refl_corner[vtx] + mCameraPosAgent; + } + const LLVector3 draw_pos = 0.25 * + (v_refl_corner[0] + v_refl_corner[1] + v_refl_corner[2] + v_refl_corner[3]); + face->mCenterAgent = draw_pos; + } + else + { + F32 ratio = far_clip / v_refl_corner[1].length(); + v_sprite_corner[1] = v_refl_corner[1] * ratio; + + ratio = far_clip / v_refl_corner[3].length(); + v_sprite_corner[3] = v_refl_corner[3] * ratio; + + v_refl_corner[1] = (1 - dt_clip) * v_refl_corner[1] + dt_clip * v_refl_corner[0]; + v_refl_corner[3] = (1 - dt_clip) * v_refl_corner[3] + dt_clip * v_refl_corner[2]; + v_sprite_corner[0] = v_refl_corner[1]; + v_sprite_corner[2] = v_refl_corner[3]; + + for (S32 vtx = 0; vtx < 4; ++vtx) + { + *(verticesp++) = v_sprite_corner[vtx] + mCameraPosAgent; + } + + const LLVector3 draw_pos = 0.25 * + (v_refl_corner[0] + v_sprite_corner[1] + v_refl_corner[2] + v_sprite_corner[3]); + face->mCenterAgent = draw_pos; + } + + *(texCoordsp++) = TEX0tt; + *(texCoordsp++) = TEX0t; + *(texCoordsp++) = TEX1tt; + *(texCoordsp++) = TEX1t; + + *indicesp++ = index_offset + 0; + *indicesp++ = index_offset + 2; + *indicesp++ = index_offset + 1; + + *indicesp++ = index_offset + 1; + *indicesp++ = index_offset + 2; + *indicesp++ = index_offset + 3; + + index_offset += 4; + } + + if (dt_clip < 1) + { + if (dt_clip <= 0) + { + const LLVector3 draw_pos = 0.25 * + (v_refl_corner[0] + v_refl_corner[1] + v_refl_corner[2] + v_refl_corner[3]); + face->mCenterAgent = draw_pos; + } + + const F32 raws_inv = 1.f/raws; + const F32 cols_inv = 1.f/cols; + LLVector3 left = v_refl_corner[0] - v_refl_corner[1]; + LLVector3 right = v_refl_corner[2] - v_refl_corner[3]; + left *= raws_inv; + right *= raws_inv; + + for (S32 raw = 0; raw < raws; ++raw) + { + F32 dt_v0 = raw * raws_inv; + F32 dt_v1 = (raw + 1) * raws_inv; + const LLVector3 BL = v_refl_corner[1] + (F32)raw * left; + const LLVector3 BR = v_refl_corner[3] + (F32)raw * right; + const LLVector3 EL = BL + left; + const LLVector3 ER = BR + right; + dt_v0 = dt_v1 = dtReflection(EL, cos_dir_from_top[0], sin_dir_from_top, diff_angl_dir); + for (S32 col = 0; col < cols; ++col) + { + F32 dt_h0 = col * cols_inv; + *(verticesp++) = (1 - dt_h0) * EL + dt_h0 * ER + mCameraPosAgent; + *(verticesp++) = (1 - dt_h0) * BL + dt_h0 * BR + mCameraPosAgent; + F32 dt_h1 = (col + 1) * cols_inv; + *(verticesp++) = (1 - dt_h1) * EL + dt_h1 * ER + mCameraPosAgent; + *(verticesp++) = (1 - dt_h1) * BL + dt_h1 * BR + mCameraPosAgent; + + *(texCoordsp++) = LLVector2(dt_h0, dt_v1); + *(texCoordsp++) = LLVector2(dt_h0, dt_v0); + *(texCoordsp++) = LLVector2(dt_h1, dt_v1); + *(texCoordsp++) = LLVector2(dt_h1, dt_v0); + + *indicesp++ = index_offset + 0; + *indicesp++ = index_offset + 2; + *indicesp++ = index_offset + 1; + + *indicesp++ = index_offset + 1; + *indicesp++ = index_offset + 2; + *indicesp++ = index_offset + 3; + + index_offset += 4; + } + } + } + + face->getVertexBuffer()->unmapBuffer(); +} +} + +void LLVOSky::updateFog(const F32 distance) +{ + LLEnvironment& environment = LLEnvironment::instance(); + if (environment.getCurrentSky() != nullptr) + { + LLVector3 light_dir = LLVector3(environment.getClampedLightNorm()); + m_legacyAtmospherics.updateFog(distance, light_dir); + } + } + +void LLVOSky::setSunAndMoonDirectionsCFR(const LLVector3 &sun_dir_cfr, const LLVector3 &moon_dir_cfr) +{ + mSun.setDirection(sun_dir_cfr); + mMoon.setDirection(moon_dir_cfr); + + // Push the sun "South" as it approaches directly overhead so that we can always see bump mapping + // on the upward facing faces of cubes. + // Same as dot product with the up direction + clamp. + F32 sunDot = llmax(0.f, sun_dir_cfr.mV[2]); + sunDot *= sunDot; + + // Create normalized vector that has the sunDir pushed south about an hour and change. + LLVector3 adjustedDir = (sun_dir_cfr + LLVector3(0.f, -0.70711f, 0.70711f)) * 0.5f; + + // Blend between normal sun dir and adjusted sun dir based on how close we are + // to having the sun overhead. + mBumpSunDir = adjustedDir * sunDot + sun_dir_cfr * (1.0f - sunDot); + mBumpSunDir.normalize(); + + LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); + updateDirections(psky); +} + +void LLVOSky::setSunDirectionCFR(const LLVector3 &sun_dir_cfr) +{ + mSun.setDirection(sun_dir_cfr); + + // Push the sun "South" as it approaches directly overhead so that we can always see bump mapping + // on the upward facing faces of cubes. + // Same as dot product with the up direction + clamp. + F32 sunDot = llmax(0.f, sun_dir_cfr.mV[2]); + sunDot *= sunDot; + + // Create normalized vector that has the sunDir pushed south about an hour and change. + LLVector3 adjustedDir = (sun_dir_cfr + LLVector3(0.f, -0.70711f, 0.70711f)) * 0.5f; + + // Blend between normal sun dir and adjusted sun dir based on how close we are + // to having the sun overhead. + mBumpSunDir = adjustedDir * sunDot + sun_dir_cfr * (1.0f - sunDot); + mBumpSunDir.normalize(); + + LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); + updateDirections(psky); +} + +void LLVOSky::setMoonDirectionCFR(const LLVector3 &moon_dir_cfr) +{ + mMoon.setDirection(moon_dir_cfr); + LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); + updateDirections(psky); +} diff --git a/indra/newview/llvosky.h b/indra/newview/llvosky.h index fb056b5e81..ad7570105e 100644 --- a/indra/newview/llvosky.h +++ b/indra/newview/llvosky.h @@ -1,362 +1,362 @@ -/** - * @file llvosky.h - * @brief LLVOSky class header file - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVOSKY_H -#define LL_LLVOSKY_H - -#include "stdtypes.h" -#include "v3color.h" -#include "v4coloru.h" -#include "llquaternion.h" -#include "llviewertexture.h" -#include "llviewerobject.h" -#include "llframetimer.h" -#include "v3colorutil.h" -#include "llsettingssky.h" -#include "lllegacyatmospherics.h" - -const F32 SKY_BOX_MULT = 16.0f; -const F32 HEAVENLY_BODY_DIST = HORIZON_DIST - 20.f; -const F32 HEAVENLY_BODY_FACTOR = 0.1f; -const F32 HEAVENLY_BODY_SCALE = HEAVENLY_BODY_DIST * HEAVENLY_BODY_FACTOR; - -const F32 SKYTEX_COMPONENTS = 4; -const F32 SKYTEX_RESOLUTION = 64; - -class LLFace; -class LLHaze; - -class LLSkyTex -{ - friend class LLVOSky; -private: - LLPointer mTexture[2]; - LLPointer mImageRaw[2]; - LLColor4 *mSkyData; - LLVector3 *mSkyDirs; // Cache of sky direction vectors - static S32 sCurrent; - -public: - void bindTexture(bool curr = true); - -protected: - LLSkyTex(); - void init(bool isShiny); - void cleanupGL(); - void restoreGL(); - - ~LLSkyTex(); - - - static S32 getResolution(); - static S32 getCurrent(); - static S32 stepCurrent(); - static S32 getNext(); - static S32 getWhich(const bool curr); - - void initEmpty(const S32 tex); - - void create(); - - void setDir(const LLVector3 &dir, const S32 i, const S32 j) - { - S32 offset = i * SKYTEX_RESOLUTION + j; - mSkyDirs[offset] = dir; - } - - const LLVector3 &getDir(const S32 i, const S32 j) const - { - S32 offset = i * SKYTEX_RESOLUTION + j; - return mSkyDirs[offset]; - } - - void setPixel(const LLColor4 &col, const S32 i, const S32 j) - { - S32 offset = i * SKYTEX_RESOLUTION + j; - mSkyData[offset] = col; - } - - void setPixel(const LLColor4U &col, const S32 i, const S32 j) - { - LLImageDataSharedLock lock(mImageRaw[sCurrent]); - S32 offset = (i * SKYTEX_RESOLUTION + j) * SKYTEX_COMPONENTS; - U32* pix = (U32*) &(mImageRaw[sCurrent]->getData()[offset]); - *pix = col.asRGBA(); - } - - LLColor4U getPixel(const S32 i, const S32 j) - { - LLColor4U col; - LLImageDataSharedLock lock(mImageRaw[sCurrent]); - S32 offset = (i * SKYTEX_RESOLUTION + j) * SKYTEX_COMPONENTS; - U32* pix = (U32*) &(mImageRaw[sCurrent]->getData()[offset]); - col.fromRGBA( *pix ); - return col; - } - - LLImageRaw* getImageRaw(bool curr=true); - void createGLImage(S32 which); - - bool mIsShiny; -}; - -/// TODO Move into the stars draw pool (and rename them appropriately). -class LLHeavenBody -{ -protected: - LLVector3 mDirectionCached; // hack for events that shouldn't happen every frame - - LLColor3 mColor; - LLColor3 mColorCached; - F32 mIntensity; - LLVector3 mDirection; // direction of the local heavenly body - LLQuaternion mRotation; - LLVector3 mAngularVelocity; // velocity of the local heavenly body - - F32 mDiskRadius; - bool mDraw; // false - do not draw. - F32 mHorizonVisibility; // number [0, 1] due to how horizon - F32 mVisibility; // same but due to other objects being in throng. - bool mVisible; - static F32 sInterpVal; - LLVector3 mQuadCorner[4]; - LLVector3 mO; - -public: - LLHeavenBody(const F32 rad); - ~LLHeavenBody() {} - - const LLQuaternion& getRotation() const; - void setRotation(const LLQuaternion& rot); - - const LLVector3& getDirection() const; - void setDirection(const LLVector3 &direction); - void setAngularVelocity(const LLVector3 &ang_vel); - const LLVector3& getAngularVelocity() const; - - const LLVector3& getDirectionCached() const; - void renewDirection(); - - const LLColor3& getColorCached() const; - void setColorCached(const LLColor3& c); - const LLColor3& getColor() const; - void setColor(const LLColor3& c); - - void renewColor(); - - static F32 interpVal(); - static void setInterpVal(const F32 v); - - LLColor3 getInterpColor() const; - - const F32& getVisibility() const; - void setVisibility(const F32 c = 1); - - bool isVisible() const; - void setVisible(const bool v); - - const F32& getIntensity() const; - void setIntensity(const F32 c); - - void setDiskRadius(const F32 radius); - F32 getDiskRadius() const; - - void setDraw(const bool draw); - bool getDraw() const; - - const LLVector3& corner(const S32 n) const; - LLVector3& corner(const S32 n); - const LLVector3* corners() const; -}; - -class LLCubeMap; - -class LLVOSky : public LLStaticViewerObject -{ -public: - enum - { - FACE_SIDE0, - FACE_SIDE1, - FACE_SIDE2, - FACE_SIDE3, - FACE_SIDE4, - FACE_SIDE5, - FACE_SUN, // was 6 - FACE_MOON, // was 7 - FACE_BLOOM, // was 8 - FACE_REFLECTION, // was 10 - FACE_COUNT - }; - - LLVOSky(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); - - // Initialize/delete data that's only inited once per class. - void init(); - void initCubeMap(); - - void cleanupGL(); - void restoreGL(); - - void calc(); - void cacheEnvironment(LLSettingsSky::ptr_t psky, AtmosphericsVars& atmosphericsVars); - - /*virtual*/ void idleUpdate(LLAgent &agent, const F64 &time); - bool updateSky(); - - // Graphical stuff for objects - maybe broken out into render class - // later? - /*virtual*/ void updateTextures(); - /*virtual*/ LLDrawable* createDrawable(LLPipeline *pipeline); - /*virtual*/ bool updateGeometry(LLDrawable *drawable); - - const LLHeavenBody& getSun() const { return mSun; } - const LLHeavenBody& getMoon() const { return mMoon; } - - bool isSameFace(S32 idx, const LLFace* face) const { return mFace[idx] == face; } - - // directions provided should already be in CFR coord sys (+x at, +z up, +y right) - void setSunAndMoonDirectionsCFR(const LLVector3 &sun_dir, const LLVector3 &moon_dir); - void setSunDirectionCFR(const LLVector3 &sun_direction); - void setMoonDirectionCFR(const LLVector3 &moon_direction); - - bool updateHeavenlyBodyGeometry(LLDrawable *drawable, F32 scale, const S32 side, LLHeavenBody& hb, const LLVector3 &up, const LLVector3 &right); - void updateReflectionGeometry(LLDrawable *drawable, F32 H, const LLHeavenBody& HB); - - F32 getWorldScale() const { return mWorldScale; } - void setWorldScale(const F32 s) { mWorldScale = s; } - void updateFog(const F32 distance); - - void setFogRatio(const F32 fog_ratio) { m_legacyAtmospherics.setFogRatio(fog_ratio); } - F32 getFogRatio() const { return m_legacyAtmospherics.getFogRatio(); } - - LLColor4 getSkyFogColor() const { return m_legacyAtmospherics.getFogColor(); } - LLColor4 getGLFogColor() const { return m_legacyAtmospherics.getGLFogColor(); } - - void setCloudDensity(F32 cloud_density) { mCloudDensity = cloud_density; } - void setWind ( const LLVector3& wind ) { mWind = wind.length(); } - - const LLVector3 &getCameraPosAgent() const { return mCameraPosAgent; } - LLVector3 getEarthCenter() const { return mEarthCenter; } - - LLCubeMap *getCubeMap() const { return mCubeMap; } - S32 getDrawRefl() const { return mDrawRefl; } - void setDrawRefl(const S32 r) { mDrawRefl = r; } - bool isReflFace(const LLFace* face) const { return face == mFace[FACE_REFLECTION]; } - LLFace* getReflFace() const { return mFace[FACE_REFLECTION]; } - - LLViewerTexture* getSunTex() const { return mSunTexturep[0]; } - LLViewerTexture* getMoonTex() const { return mMoonTexturep[0]; } - LLViewerTexture* getBloomTex() const { return mBloomTexturep[0]; } - LLViewerTexture* getCloudNoiseTex() const { return mCloudNoiseTexturep[0]; } - - LLViewerTexture* getRainbowTex() const { return mRainbowMap; } - LLViewerTexture* getHaloTex() const { return mHaloMap; } - - LLViewerTexture* getSunTexNext() const { return mSunTexturep[1]; } - LLViewerTexture* getMoonTexNext() const { return mMoonTexturep[1]; } - LLViewerTexture* getBloomTexNext() const { return mBloomTexturep[1]; } - LLViewerTexture* getCloudNoiseTexNext() const { return mCloudNoiseTexturep[1]; } - - void setSunTextures(const LLUUID& sun_texture, const LLUUID& sun_texture_next); - void setMoonTextures(const LLUUID& moon_texture, const LLUUID& moon_texture_next); - void setCloudNoiseTextures(const LLUUID& cloud_noise_texture, const LLUUID& cloud_noise_texture_next); - void setBloomTextures(const LLUUID& bloom_texture, const LLUUID& bloom_texture_next); - - void setSunScale(F32 sun_scale); - void setMoonScale(F32 sun_scale); - - void forceSkyUpdate(void); - -public: - LLFace *mFace[FACE_COUNT]; - LLVector3 mBumpSunDir; - - F32 getInterpVal() const { return mInterpVal; } - -protected: - ~LLVOSky(); - - void updateDirections(LLSettingsSky::ptr_t psky); - - void initSkyTextureDirs(const S32 side, const S32 tile); - void createSkyTexture(const LLSettingsSky::ptr_t &psky, AtmosphericsVars& vars, const S32 side, const S32 tile); - - LLPointer mSunTexturep[2]; - LLPointer mMoonTexturep[2]; - LLPointer mCloudNoiseTexturep[2]; - LLPointer mBloomTexturep[2]; - LLPointer mRainbowMap; - LLPointer mHaloMap; - - F32 mSunScale = 1.0f; - F32 mMoonScale = 1.0f; - - static S32 sResolution; - static S32 sTileResX; - static S32 sTileResY; - LLSkyTex mSkyTex[6]; - LLSkyTex mShinyTex[6]; - LLHeavenBody mSun; - LLHeavenBody mMoon; - LLVector3 mSunDefaultPosition; - LLVector3 mSunAngVel; - F32 mAtmHeight; - LLVector3 mEarthCenter; - LLVector3 mCameraPosAgent; - F32 mBrightnessScale; - LLColor3 mBrightestPoint; - F32 mBrightnessScaleNew; - LLColor3 mBrightestPointNew; - F32 mBrightnessScaleGuess; - LLColor3 mBrightestPointGuess; - bool mWeatherChange; - F32 mCloudDensity; - F32 mWind; - - bool mInitialized; - bool mForceUpdate; - bool mNeedUpdate; // flag to force update of cubemap - S32 mCubeMapUpdateStage; // state of cubemap uodate: -1 idle; 0-5 per-face updates; 6 finalizing - - F32 mAmbientScale; - LLColor3 mNightColorShift; - F32 mInterpVal; - F32 mWorldScale; - - LLPointer mCubeMap; // Cube map for the environment - S32 mDrawRefl; - - LLFrameTimer mUpdateTimer; - LLTimer mForceUpdateThrottle; - bool mHeavenlyBodyUpdated ; - - AtmosphericsVars m_atmosphericsVars; - AtmosphericsVars m_lastAtmosphericsVars; - LLAtmospherics m_legacyAtmospherics; -}; - -#endif +/** + * @file llvosky.h + * @brief LLVOSky class header file + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVOSKY_H +#define LL_LLVOSKY_H + +#include "stdtypes.h" +#include "v3color.h" +#include "v4coloru.h" +#include "llquaternion.h" +#include "llviewertexture.h" +#include "llviewerobject.h" +#include "llframetimer.h" +#include "v3colorutil.h" +#include "llsettingssky.h" +#include "lllegacyatmospherics.h" + +const F32 SKY_BOX_MULT = 16.0f; +const F32 HEAVENLY_BODY_DIST = HORIZON_DIST - 20.f; +const F32 HEAVENLY_BODY_FACTOR = 0.1f; +const F32 HEAVENLY_BODY_SCALE = HEAVENLY_BODY_DIST * HEAVENLY_BODY_FACTOR; + +const F32 SKYTEX_COMPONENTS = 4; +const F32 SKYTEX_RESOLUTION = 64; + +class LLFace; +class LLHaze; + +class LLSkyTex +{ + friend class LLVOSky; +private: + LLPointer mTexture[2]; + LLPointer mImageRaw[2]; + LLColor4 *mSkyData; + LLVector3 *mSkyDirs; // Cache of sky direction vectors + static S32 sCurrent; + +public: + void bindTexture(bool curr = true); + +protected: + LLSkyTex(); + void init(bool isShiny); + void cleanupGL(); + void restoreGL(); + + ~LLSkyTex(); + + + static S32 getResolution(); + static S32 getCurrent(); + static S32 stepCurrent(); + static S32 getNext(); + static S32 getWhich(const bool curr); + + void initEmpty(const S32 tex); + + void create(); + + void setDir(const LLVector3 &dir, const S32 i, const S32 j) + { + S32 offset = i * SKYTEX_RESOLUTION + j; + mSkyDirs[offset] = dir; + } + + const LLVector3 &getDir(const S32 i, const S32 j) const + { + S32 offset = i * SKYTEX_RESOLUTION + j; + return mSkyDirs[offset]; + } + + void setPixel(const LLColor4 &col, const S32 i, const S32 j) + { + S32 offset = i * SKYTEX_RESOLUTION + j; + mSkyData[offset] = col; + } + + void setPixel(const LLColor4U &col, const S32 i, const S32 j) + { + LLImageDataSharedLock lock(mImageRaw[sCurrent]); + S32 offset = (i * SKYTEX_RESOLUTION + j) * SKYTEX_COMPONENTS; + U32* pix = (U32*) &(mImageRaw[sCurrent]->getData()[offset]); + *pix = col.asRGBA(); + } + + LLColor4U getPixel(const S32 i, const S32 j) + { + LLColor4U col; + LLImageDataSharedLock lock(mImageRaw[sCurrent]); + S32 offset = (i * SKYTEX_RESOLUTION + j) * SKYTEX_COMPONENTS; + U32* pix = (U32*) &(mImageRaw[sCurrent]->getData()[offset]); + col.fromRGBA( *pix ); + return col; + } + + LLImageRaw* getImageRaw(bool curr=true); + void createGLImage(S32 which); + + bool mIsShiny; +}; + +/// TODO Move into the stars draw pool (and rename them appropriately). +class LLHeavenBody +{ +protected: + LLVector3 mDirectionCached; // hack for events that shouldn't happen every frame + + LLColor3 mColor; + LLColor3 mColorCached; + F32 mIntensity; + LLVector3 mDirection; // direction of the local heavenly body + LLQuaternion mRotation; + LLVector3 mAngularVelocity; // velocity of the local heavenly body + + F32 mDiskRadius; + bool mDraw; // false - do not draw. + F32 mHorizonVisibility; // number [0, 1] due to how horizon + F32 mVisibility; // same but due to other objects being in throng. + bool mVisible; + static F32 sInterpVal; + LLVector3 mQuadCorner[4]; + LLVector3 mO; + +public: + LLHeavenBody(const F32 rad); + ~LLHeavenBody() {} + + const LLQuaternion& getRotation() const; + void setRotation(const LLQuaternion& rot); + + const LLVector3& getDirection() const; + void setDirection(const LLVector3 &direction); + void setAngularVelocity(const LLVector3 &ang_vel); + const LLVector3& getAngularVelocity() const; + + const LLVector3& getDirectionCached() const; + void renewDirection(); + + const LLColor3& getColorCached() const; + void setColorCached(const LLColor3& c); + const LLColor3& getColor() const; + void setColor(const LLColor3& c); + + void renewColor(); + + static F32 interpVal(); + static void setInterpVal(const F32 v); + + LLColor3 getInterpColor() const; + + const F32& getVisibility() const; + void setVisibility(const F32 c = 1); + + bool isVisible() const; + void setVisible(const bool v); + + const F32& getIntensity() const; + void setIntensity(const F32 c); + + void setDiskRadius(const F32 radius); + F32 getDiskRadius() const; + + void setDraw(const bool draw); + bool getDraw() const; + + const LLVector3& corner(const S32 n) const; + LLVector3& corner(const S32 n); + const LLVector3* corners() const; +}; + +class LLCubeMap; + +class LLVOSky : public LLStaticViewerObject +{ +public: + enum + { + FACE_SIDE0, + FACE_SIDE1, + FACE_SIDE2, + FACE_SIDE3, + FACE_SIDE4, + FACE_SIDE5, + FACE_SUN, // was 6 + FACE_MOON, // was 7 + FACE_BLOOM, // was 8 + FACE_REFLECTION, // was 10 + FACE_COUNT + }; + + LLVOSky(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); + + // Initialize/delete data that's only inited once per class. + void init(); + void initCubeMap(); + + void cleanupGL(); + void restoreGL(); + + void calc(); + void cacheEnvironment(LLSettingsSky::ptr_t psky, AtmosphericsVars& atmosphericsVars); + + /*virtual*/ void idleUpdate(LLAgent &agent, const F64 &time); + bool updateSky(); + + // Graphical stuff for objects - maybe broken out into render class + // later? + /*virtual*/ void updateTextures(); + /*virtual*/ LLDrawable* createDrawable(LLPipeline *pipeline); + /*virtual*/ bool updateGeometry(LLDrawable *drawable); + + const LLHeavenBody& getSun() const { return mSun; } + const LLHeavenBody& getMoon() const { return mMoon; } + + bool isSameFace(S32 idx, const LLFace* face) const { return mFace[idx] == face; } + + // directions provided should already be in CFR coord sys (+x at, +z up, +y right) + void setSunAndMoonDirectionsCFR(const LLVector3 &sun_dir, const LLVector3 &moon_dir); + void setSunDirectionCFR(const LLVector3 &sun_direction); + void setMoonDirectionCFR(const LLVector3 &moon_direction); + + bool updateHeavenlyBodyGeometry(LLDrawable *drawable, F32 scale, const S32 side, LLHeavenBody& hb, const LLVector3 &up, const LLVector3 &right); + void updateReflectionGeometry(LLDrawable *drawable, F32 H, const LLHeavenBody& HB); + + F32 getWorldScale() const { return mWorldScale; } + void setWorldScale(const F32 s) { mWorldScale = s; } + void updateFog(const F32 distance); + + void setFogRatio(const F32 fog_ratio) { m_legacyAtmospherics.setFogRatio(fog_ratio); } + F32 getFogRatio() const { return m_legacyAtmospherics.getFogRatio(); } + + LLColor4 getSkyFogColor() const { return m_legacyAtmospherics.getFogColor(); } + LLColor4 getGLFogColor() const { return m_legacyAtmospherics.getGLFogColor(); } + + void setCloudDensity(F32 cloud_density) { mCloudDensity = cloud_density; } + void setWind ( const LLVector3& wind ) { mWind = wind.length(); } + + const LLVector3 &getCameraPosAgent() const { return mCameraPosAgent; } + LLVector3 getEarthCenter() const { return mEarthCenter; } + + LLCubeMap *getCubeMap() const { return mCubeMap; } + S32 getDrawRefl() const { return mDrawRefl; } + void setDrawRefl(const S32 r) { mDrawRefl = r; } + bool isReflFace(const LLFace* face) const { return face == mFace[FACE_REFLECTION]; } + LLFace* getReflFace() const { return mFace[FACE_REFLECTION]; } + + LLViewerTexture* getSunTex() const { return mSunTexturep[0]; } + LLViewerTexture* getMoonTex() const { return mMoonTexturep[0]; } + LLViewerTexture* getBloomTex() const { return mBloomTexturep[0]; } + LLViewerTexture* getCloudNoiseTex() const { return mCloudNoiseTexturep[0]; } + + LLViewerTexture* getRainbowTex() const { return mRainbowMap; } + LLViewerTexture* getHaloTex() const { return mHaloMap; } + + LLViewerTexture* getSunTexNext() const { return mSunTexturep[1]; } + LLViewerTexture* getMoonTexNext() const { return mMoonTexturep[1]; } + LLViewerTexture* getBloomTexNext() const { return mBloomTexturep[1]; } + LLViewerTexture* getCloudNoiseTexNext() const { return mCloudNoiseTexturep[1]; } + + void setSunTextures(const LLUUID& sun_texture, const LLUUID& sun_texture_next); + void setMoonTextures(const LLUUID& moon_texture, const LLUUID& moon_texture_next); + void setCloudNoiseTextures(const LLUUID& cloud_noise_texture, const LLUUID& cloud_noise_texture_next); + void setBloomTextures(const LLUUID& bloom_texture, const LLUUID& bloom_texture_next); + + void setSunScale(F32 sun_scale); + void setMoonScale(F32 sun_scale); + + void forceSkyUpdate(void); + +public: + LLFace *mFace[FACE_COUNT]; + LLVector3 mBumpSunDir; + + F32 getInterpVal() const { return mInterpVal; } + +protected: + ~LLVOSky(); + + void updateDirections(LLSettingsSky::ptr_t psky); + + void initSkyTextureDirs(const S32 side, const S32 tile); + void createSkyTexture(const LLSettingsSky::ptr_t &psky, AtmosphericsVars& vars, const S32 side, const S32 tile); + + LLPointer mSunTexturep[2]; + LLPointer mMoonTexturep[2]; + LLPointer mCloudNoiseTexturep[2]; + LLPointer mBloomTexturep[2]; + LLPointer mRainbowMap; + LLPointer mHaloMap; + + F32 mSunScale = 1.0f; + F32 mMoonScale = 1.0f; + + static S32 sResolution; + static S32 sTileResX; + static S32 sTileResY; + LLSkyTex mSkyTex[6]; + LLSkyTex mShinyTex[6]; + LLHeavenBody mSun; + LLHeavenBody mMoon; + LLVector3 mSunDefaultPosition; + LLVector3 mSunAngVel; + F32 mAtmHeight; + LLVector3 mEarthCenter; + LLVector3 mCameraPosAgent; + F32 mBrightnessScale; + LLColor3 mBrightestPoint; + F32 mBrightnessScaleNew; + LLColor3 mBrightestPointNew; + F32 mBrightnessScaleGuess; + LLColor3 mBrightestPointGuess; + bool mWeatherChange; + F32 mCloudDensity; + F32 mWind; + + bool mInitialized; + bool mForceUpdate; + bool mNeedUpdate; // flag to force update of cubemap + S32 mCubeMapUpdateStage; // state of cubemap uodate: -1 idle; 0-5 per-face updates; 6 finalizing + + F32 mAmbientScale; + LLColor3 mNightColorShift; + F32 mInterpVal; + F32 mWorldScale; + + LLPointer mCubeMap; // Cube map for the environment + S32 mDrawRefl; + + LLFrameTimer mUpdateTimer; + LLTimer mForceUpdateThrottle; + bool mHeavenlyBodyUpdated ; + + AtmosphericsVars m_atmosphericsVars; + AtmosphericsVars m_lastAtmosphericsVars; + LLAtmospherics m_legacyAtmospherics; +}; + +#endif diff --git a/indra/newview/llvosurfacepatch.cpp b/indra/newview/llvosurfacepatch.cpp index 312a8a8b0b..f143e9c759 100644 --- a/indra/newview/llvosurfacepatch.cpp +++ b/indra/newview/llvosurfacepatch.cpp @@ -1,1025 +1,1025 @@ -/** - * @file llvosurfacepatch.cpp - * @brief Viewer-object derived "surface patch", which is a piece of terrain - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llvosurfacepatch.h" - -#include "lldrawpoolterrain.h" - -#include "lldrawable.h" -#include "llface.h" -#include "llprimitive.h" -#include "llsky.h" -#include "llsurfacepatch.h" -#include "llsurface.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llvlcomposition.h" -#include "llvovolume.h" -#include "pipeline.h" -#include "llspatialpartition.h" - -F32 LLVOSurfacePatch::sLODFactor = 1.f; - -LLVOSurfacePatch::LLVOSurfacePatch(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) - : LLStaticViewerObject(id, pcode, regionp), - mDirtiedPatch(false), - mPool(NULL), - mBaseComp(0), - mPatchp(NULL), - mDirtyTexture(false), - mDirtyTerrain(false), - mLastNorthStride(0), - mLastEastStride(0), - mLastStride(0), - mLastLength(0) -{ - // Terrain must draw during selection passes so it can block objects behind it. - mbCanSelect = true; - setScale(LLVector3(16.f, 16.f, 16.f)); // Hack for setting scale for bounding boxes/visibility. -} - - -LLVOSurfacePatch::~LLVOSurfacePatch() -{ - mPatchp = NULL; -} - - -void LLVOSurfacePatch::markDead() -{ - if (mPatchp) - { - mPatchp->clearVObj(); - mPatchp = NULL; - } - LLViewerObject::markDead(); -} - - -bool LLVOSurfacePatch::isActive() const -{ - return false; -} - - -void LLVOSurfacePatch::setPixelAreaAndAngle(LLAgent &agent) -{ - mAppAngle = 50; - mPixelArea = 500*500; -} - - -void LLVOSurfacePatch::updateTextures() -{ -} - - -LLFacePool *LLVOSurfacePatch::getPool() -{ - mPool = (LLDrawPoolTerrain*) gPipeline.getPool(LLDrawPool::POOL_TERRAIN, mPatchp->getSurface()->getSTexture()); - - return mPool; -} - - -LLDrawable *LLVOSurfacePatch::createDrawable(LLPipeline *pipeline) -{ - pipeline->allocDrawable(this); - - mDrawable->setRenderType(LLPipeline::RENDER_TYPE_TERRAIN); - - mBaseComp = llfloor(mPatchp->getMinComposition()); - S32 min_comp, max_comp, range; - min_comp = llfloor(mPatchp->getMinComposition()); - max_comp = llceil(mPatchp->getMaxComposition()); - range = (max_comp - min_comp); - range++; - if (range > 3) - { - if ((mPatchp->getMinComposition() - min_comp) > (max_comp - mPatchp->getMaxComposition())) - { - // The top side runs over more - mBaseComp++; - } - range = 3; - } - - LLFacePool *poolp = getPool(); - - mDrawable->addFace(poolp, NULL); - - return mDrawable; -} - - -void LLVOSurfacePatch::updateGL() -{ - if (mPatchp) - { - LL_PROFILE_ZONE_SCOPED - mPatchp->updateGL(); - } -} - -bool LLVOSurfacePatch::updateGeometry(LLDrawable *drawable) -{ - LL_PROFILE_ZONE_SCOPED; - - dirtySpatialGroup(); - - S32 min_comp, max_comp, range; - min_comp = lltrunc(mPatchp->getMinComposition()); - max_comp = lltrunc(ceil(mPatchp->getMaxComposition())); - range = (max_comp - min_comp); - range++; - S32 new_base_comp = lltrunc(mPatchp->getMinComposition()); - if (range > 3) - { - if ((mPatchp->getMinComposition() - min_comp) > (max_comp - mPatchp->getMaxComposition())) - { - // The top side runs over more - new_base_comp++; - } - range = 3; - } - - // Pick the two closest detail textures for this patch... - // Then create the draw pool for it. - // Actually, should get the average composition instead of the center. - mBaseComp = new_base_comp; - - ////////////////////////// - // - // Figure out the strides - // - // - - U32 patch_width, render_stride, north_stride, east_stride, length; - render_stride = mPatchp->getRenderStride(); - patch_width = mPatchp->getSurface()->getGridsPerPatchEdge(); - - length = patch_width / render_stride; - - if (mPatchp->getNeighborPatch(NORTH)) - { - north_stride = mPatchp->getNeighborPatch(NORTH)->getRenderStride(); - } - else - { - north_stride = render_stride; - } - - if (mPatchp->getNeighborPatch(EAST)) - { - east_stride = mPatchp->getNeighborPatch(EAST)->getRenderStride(); - } - else - { - east_stride = render_stride; - } - - mLastLength = length; - mLastStride = render_stride; - mLastNorthStride = north_stride; - mLastEastStride = east_stride; - - return true; -} - -void LLVOSurfacePatch::updateFaceSize(S32 idx) -{ - if (idx != 0) - { - LL_WARNS() << "Terrain partition requested invalid face!!!" << LL_ENDL; - return; - } - - LLFace* facep = mDrawable->getFace(idx); - if (facep) - { - S32 num_vertices = 0; - S32 num_indices = 0; - - if (mLastStride) - { - getGeomSizesMain(mLastStride, num_vertices, num_indices); - getGeomSizesNorth(mLastStride, mLastNorthStride, num_vertices, num_indices); - getGeomSizesEast(mLastStride, mLastEastStride, num_vertices, num_indices); - } - - facep->setSize(num_vertices, num_indices); - } -} - -bool LLVOSurfacePatch::updateLOD() -{ - return true; -} - -void LLVOSurfacePatch::getGeometry(LLStrider &verticesp, - LLStrider &normalsp, - LLStrider &texCoords0p, - LLStrider &texCoords1p, - LLStrider &indicesp) -{ - LLFace* facep = mDrawable->getFace(0); - if (facep) - { - U32 index_offset = facep->getGeomIndex(); - - updateMainGeometry(facep, - verticesp, - normalsp, - texCoords0p, - texCoords1p, - indicesp, - index_offset); - updateNorthGeometry(facep, - verticesp, - normalsp, - texCoords0p, - texCoords1p, - indicesp, - index_offset); - updateEastGeometry(facep, - verticesp, - normalsp, - texCoords0p, - texCoords1p, - indicesp, - index_offset); - } -} - -void LLVOSurfacePatch::updateMainGeometry(LLFace *facep, - LLStrider &verticesp, - LLStrider &normalsp, - LLStrider &texCoords0p, - LLStrider &texCoords1p, - LLStrider &indicesp, - U32 &index_offset) -{ - S32 i, j, x, y; - - U32 patch_size, render_stride; - S32 num_vertices, num_indices; - U32 index; - - llassert(mLastStride > 0); - - render_stride = mLastStride; - patch_size = mPatchp->getSurface()->getGridsPerPatchEdge(); - S32 vert_size = patch_size / render_stride; - - /////////////////////////// - // - // Render the main patch - // - // - - num_vertices = 0; - num_indices = 0; - // First, figure out how many vertices we need... - getGeomSizesMain(render_stride, num_vertices, num_indices); - - if (num_vertices > 0) - { - facep->mCenterAgent = mPatchp->getPointAgent(8, 8); - - // Generate patch points first - for (j = 0; j < vert_size; j++) - { - for (i = 0; i < vert_size; i++) - { - x = i * render_stride; - y = j * render_stride; - mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); - verticesp++; - normalsp++; - texCoords0p++; - texCoords1p++; - } - } - - for (j = 0; j < (vert_size - 1); j++) - { - if (j % 2) - { - for (i = (vert_size - 1); i > 0; i--) - { - index = (i - 1)+ j*vert_size; - *(indicesp++) = index_offset + index; - - index = i + (j+1)*vert_size; - *(indicesp++) = index_offset + index; - - index = (i - 1) + (j+1)*vert_size; - *(indicesp++) = index_offset + index; - - index = (i - 1) + j*vert_size; - *(indicesp++) = index_offset + index; - - index = i + j*vert_size; - *(indicesp++) = index_offset + index; - - index = i + (j+1)*vert_size; - *(indicesp++) = index_offset + index; - } - } - else - { - for (i = 0; i < (vert_size - 1); i++) - { - index = i + j*vert_size; - *(indicesp++) = index_offset + index; - - index = (i + 1) + (j+1)*vert_size; - *(indicesp++) = index_offset + index; - - index = i + (j+1)*vert_size; - *(indicesp++) = index_offset + index; - - index = i + j*vert_size; - *(indicesp++) = index_offset + index; - - index = (i + 1) + j*vert_size; - *(indicesp++) = index_offset + index; - - index = (i + 1) + (j + 1)*vert_size; - *(indicesp++) = index_offset + index; - } - } - } - } - index_offset += num_vertices; -} - - -void LLVOSurfacePatch::updateNorthGeometry(LLFace *facep, - LLStrider &verticesp, - LLStrider &normalsp, - LLStrider &texCoords0p, - LLStrider &texCoords1p, - LLStrider &indicesp, - U32 &index_offset) -{ - S32 i, x, y; - - S32 num_vertices; - - U32 render_stride = mLastStride; - S32 patch_size = mPatchp->getSurface()->getGridsPerPatchEdge(); - S32 length = patch_size / render_stride; - S32 half_length = length / 2; - U32 north_stride = mLastNorthStride; - - /////////////////////////// - // - // Render the north strip - // - // - - // Stride lengths are the same - if (north_stride == render_stride) - { - num_vertices = 2 * length + 1; - - facep->mCenterAgent = (mPatchp->getPointAgent(8, 15) + mPatchp->getPointAgent(8, 16))*0.5f; - - // Main patch - for (i = 0; i < length; i++) - { - x = i * render_stride; - y = 16 - render_stride; - - mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); - verticesp++; - normalsp++; - texCoords0p++; - texCoords1p++; - } - - // North patch - for (i = 0; i <= length; i++) - { - x = i * render_stride; - y = 16; - mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); - verticesp++; - normalsp++; - texCoords0p++; - texCoords1p++; - } - - - for (i = 0; i < length; i++) - { - // Generate indices - *(indicesp++) = index_offset + i; - *(indicesp++) = index_offset + length + i + 1; - *(indicesp++) = index_offset + length + i; - - if (i != length - 1) - { - *(indicesp++) = index_offset + i; - *(indicesp++) = index_offset + i + 1; - *(indicesp++) = index_offset + length + i + 1; - } - } - } - else if (north_stride > render_stride) - { - // North stride is longer (has less vertices) - num_vertices = length + length/2 + 1; - - facep->mCenterAgent = (mPatchp->getPointAgent(7, 15) + mPatchp->getPointAgent(8, 16))*0.5f; - - // Iterate through this patch's points - for (i = 0; i < length; i++) - { - x = i * render_stride; - y = 16 - render_stride; - - mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); - verticesp++; - normalsp++; - texCoords0p++; - texCoords1p++; - } - - // Iterate through the north patch's points - for (i = 0; i <= length; i+=2) - { - x = i * render_stride; - y = 16; - - mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); - verticesp++; - normalsp++; - texCoords0p++; - texCoords1p++; - } - - - for (i = 0; i < length; i++) - { - if (!(i % 2)) - { - *(indicesp++) = index_offset + i; - *(indicesp++) = index_offset + i + 1; - *(indicesp++) = index_offset + length + (i/2); - - *(indicesp++) = index_offset + i + 1; - *(indicesp++) = index_offset + length + (i/2) + 1; - *(indicesp++) = index_offset + length + (i/2); - } - else if (i < (length - 1)) - { - *(indicesp++) = index_offset + i; - *(indicesp++) = index_offset + i + 1; - *(indicesp++) = index_offset + length + (i/2) + 1; - } - } - } - else - { - // North stride is shorter (more vertices) - length = patch_size / north_stride; - half_length = length / 2; - num_vertices = length + half_length + 1; - - facep->mCenterAgent = (mPatchp->getPointAgent(15, 7) + mPatchp->getPointAgent(16, 8))*0.5f; - - // Iterate through this patch's points - for (i = 0; i < length; i+=2) - { - x = i * north_stride; - y = 16 - render_stride; - - mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); - verticesp++; - normalsp++; - texCoords0p++; - texCoords1p++; - } - - // Iterate through the north patch's points - for (i = 0; i <= length; i++) - { - x = i * north_stride; - y = 16; - - mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); - verticesp++; - normalsp++; - texCoords0p++; - texCoords1p++; - } - - for (i = 0; i < length; i++) - { - if (!(i%2)) - { - *(indicesp++) = index_offset + half_length + i; - *(indicesp++) = index_offset + i/2; - *(indicesp++) = index_offset + half_length + i + 1; - } - else if (i < (length - 2)) - { - *(indicesp++) = index_offset + half_length + i; - *(indicesp++) = index_offset + i/2; - *(indicesp++) = index_offset + i/2 + 1; - - *(indicesp++) = index_offset + half_length + i; - *(indicesp++) = index_offset + i/2 + 1; - *(indicesp++) = index_offset + half_length + i + 1; - } - else - { - *(indicesp++) = index_offset + half_length + i; - *(indicesp++) = index_offset + i/2; - *(indicesp++) = index_offset + half_length + i + 1; - } - } - } - index_offset += num_vertices; -} - -void LLVOSurfacePatch::updateEastGeometry(LLFace *facep, - LLStrider &verticesp, - LLStrider &normalsp, - LLStrider &texCoords0p, - LLStrider &texCoords1p, - LLStrider &indicesp, - U32 &index_offset) -{ - S32 i, x, y; - - S32 num_vertices; - - U32 render_stride = mLastStride; - S32 patch_size = mPatchp->getSurface()->getGridsPerPatchEdge(); - S32 length = patch_size / render_stride; - S32 half_length = length / 2; - - U32 east_stride = mLastEastStride; - - // Stride lengths are the same - if (east_stride == render_stride) - { - num_vertices = 2 * length + 1; - - facep->mCenterAgent = (mPatchp->getPointAgent(8, 15) + mPatchp->getPointAgent(8, 16))*0.5f; - - // Main patch - for (i = 0; i < length; i++) - { - x = 16 - render_stride; - y = i * render_stride; - - mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); - verticesp++; - normalsp++; - texCoords0p++; - texCoords1p++; - } - - // East patch - for (i = 0; i <= length; i++) - { - x = 16; - y = i * render_stride; - mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); - verticesp++; - normalsp++; - texCoords0p++; - texCoords1p++; - } - - - for (i = 0; i < length; i++) - { - // Generate indices - *(indicesp++) = index_offset + i; - *(indicesp++) = index_offset + length + i; - *(indicesp++) = index_offset + length + i + 1; - - if (i != length - 1) - { - *(indicesp++) = index_offset + i; - *(indicesp++) = index_offset + length + i + 1; - *(indicesp++) = index_offset + i + 1; - } - } - } - else if (east_stride > render_stride) - { - // East stride is longer (has less vertices) - num_vertices = length + half_length + 1; - - facep->mCenterAgent = (mPatchp->getPointAgent(7, 15) + mPatchp->getPointAgent(8, 16))*0.5f; - - // Iterate through this patch's points - for (i = 0; i < length; i++) - { - x = 16 - render_stride; - y = i * render_stride; - - mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); - verticesp++; - normalsp++; - texCoords0p++; - texCoords1p++; - } - // Iterate through the east patch's points - for (i = 0; i <= length; i+=2) - { - x = 16; - y = i * render_stride; - - mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); - verticesp++; - normalsp++; - texCoords0p++; - texCoords1p++; - } - - for (i = 0; i < length; i++) - { - if (!(i % 2)) - { - *(indicesp++) = index_offset + i; - *(indicesp++) = index_offset + length + (i/2); - *(indicesp++) = index_offset + i + 1; - - *(indicesp++) = index_offset + i + 1; - *(indicesp++) = index_offset + length + (i/2); - *(indicesp++) = index_offset + length + (i/2) + 1; - } - else if (i < (length - 1)) - { - *(indicesp++) = index_offset + i; - *(indicesp++) = index_offset + length + (i/2) + 1; - *(indicesp++) = index_offset + i + 1; - } - } - } - else - { - // East stride is shorter (more vertices) - length = patch_size / east_stride; - half_length = length / 2; - num_vertices = length + length/2 + 1; - - facep->mCenterAgent = (mPatchp->getPointAgent(15, 7) + mPatchp->getPointAgent(16, 8))*0.5f; - - // Iterate through this patch's points - for (i = 0; i < length; i+=2) - { - x = 16 - render_stride; - y = i * east_stride; - - mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); - verticesp++; - normalsp++; - texCoords0p++; - texCoords1p++; - } - // Iterate through the east patch's points - for (i = 0; i <= length; i++) - { - x = 16; - y = i * east_stride; - - mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); - verticesp++; - normalsp++; - texCoords0p++; - texCoords1p++; - } - - for (i = 0; i < length; i++) - { - if (!(i%2)) - { - *(indicesp++) = index_offset + half_length + i; - *(indicesp++) = index_offset + half_length + i + 1; - *(indicesp++) = index_offset + i/2; - } - else if (i < (length - 2)) - { - *(indicesp++) = index_offset + half_length + i; - *(indicesp++) = index_offset + i/2 + 1; - *(indicesp++) = index_offset + i/2; - - *(indicesp++) = index_offset + half_length + i; - *(indicesp++) = index_offset + half_length + i + 1; - *(indicesp++) = index_offset + i/2 + 1; - } - else - { - *(indicesp++) = index_offset + half_length + i; - *(indicesp++) = index_offset + half_length + i + 1; - *(indicesp++) = index_offset + i/2; - } - } - } - index_offset += num_vertices; -} - -void LLVOSurfacePatch::setPatch(LLSurfacePatch *patchp) -{ - mPatchp = patchp; - - dirtyPatch(); -}; - - -void LLVOSurfacePatch::dirtyPatch() -{ - mDirtiedPatch = true; - dirtyGeom(); - mDirtyTerrain = true; - LLVector3 center = mPatchp->getCenterRegion(); - LLSurface *surfacep = mPatchp->getSurface(); - - setPositionRegion(center); - - F32 scale_factor = surfacep->getGridsPerPatchEdge() * surfacep->getMetersPerGrid(); - setScale(LLVector3(scale_factor, scale_factor, mPatchp->getMaxZ() - mPatchp->getMinZ())); -} - -void LLVOSurfacePatch::dirtyGeom() -{ - if (mDrawable) - { - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); - LLFace* facep = mDrawable->getFace(0); - if (facep) - { - facep->setVertexBuffer(NULL); - } - mDrawable->movePartition(); - } -} - -void LLVOSurfacePatch::getGeomSizesMain(const S32 stride, S32 &num_vertices, S32 &num_indices) -{ - S32 patch_size = mPatchp->getSurface()->getGridsPerPatchEdge(); - - // First, figure out how many vertices we need... - S32 vert_size = patch_size / stride; - if (vert_size >= 2) - { - num_vertices += vert_size * vert_size; - num_indices += 6 * (vert_size - 1)*(vert_size - 1); - } -} - -void LLVOSurfacePatch::getGeomSizesNorth(const S32 stride, const S32 north_stride, - S32 &num_vertices, S32 &num_indices) -{ - S32 patch_size = mPatchp->getSurface()->getGridsPerPatchEdge(); - S32 length = patch_size / stride; - // Stride lengths are the same - if (north_stride == stride) - { - num_vertices += 2 * length + 1; - num_indices += length * 6 - 3; - } - else if (north_stride > stride) - { - // North stride is longer (has less vertices) - num_vertices += length + (length/2) + 1; - num_indices += (length/2)*9 - 3; - } - else - { - // North stride is shorter (more vertices) - length = patch_size / north_stride; - num_vertices += length + (length/2) + 1; - num_indices += 9*(length/2) - 3; - } -} - -void LLVOSurfacePatch::getGeomSizesEast(const S32 stride, const S32 east_stride, - S32 &num_vertices, S32 &num_indices) -{ - S32 patch_size = mPatchp->getSurface()->getGridsPerPatchEdge(); - S32 length = patch_size / stride; - // Stride lengths are the same - if (east_stride == stride) - { - num_vertices += 2 * length + 1; - num_indices += length * 6 - 3; - } - else if (east_stride > stride) - { - // East stride is longer (has less vertices) - num_vertices += length + (length/2) + 1; - num_indices += (length/2)*9 - 3; - } - else - { - // East stride is shorter (more vertices) - length = patch_size / east_stride; - num_vertices += length + (length/2) + 1; - num_indices += 9*(length/2) - 3; - } -} - -bool LLVOSurfacePatch::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, S32 face, bool pick_transparent, bool pick_rigged, bool pick_unselectable, S32 *face_hitp, - LLVector4a* intersection,LLVector2* tex_coord, LLVector4a* normal, LLVector4a* tangent) - -{ - - if (!lineSegmentBoundingBox(start, end)) - { - return false; - } - - LLVector4a da; - da.setSub(end, start); - LLVector3 delta(da.getF32ptr()); - - LLVector3 pdelta = delta; - pdelta.mV[2] = 0; - - F32 plength = pdelta.length(); - - F32 tdelta = 1.f/plength; - - LLVector3 v_start(start.getF32ptr()); - - LLVector3 origin = v_start - mRegionp->getOriginAgent(); - - if (mRegionp->getLandHeightRegion(origin) > origin.mV[2]) - { - //origin is under ground, treat as no intersection - return false; - } - - //step one meter at a time until intersection point found - - //VECTORIZE THIS - const LLVector4a* exta = mDrawable->getSpatialExtents(); - - LLVector3 ext[2]; - ext[0].set(exta[0].getF32ptr()); - ext[1].set(exta[1].getF32ptr()); - - F32 rad = (delta*tdelta).magVecSquared(); - - F32 t = 0.f; - while ( t <= 1.f) - { - LLVector3 sample = origin + delta*t; - - if (AABBSphereIntersectR2(ext[0], ext[1], sample+mRegionp->getOriginAgent(), rad)) - { - F32 height = mRegionp->getLandHeightRegion(sample); - if (height > sample.mV[2]) - { //ray went below ground, positive intersection - //quick and dirty binary search to get impact point - tdelta = -tdelta*0.5f; - F32 err_dist = 0.001f; - F32 dist = fabsf(sample.mV[2] - height); - - while (dist > err_dist && tdelta*tdelta > 0.0f) - { - t += tdelta; - sample = origin+delta*t; - height = mRegionp->getLandHeightRegion(sample); - if ((tdelta < 0 && height < sample.mV[2]) || - (height > sample.mV[2] && tdelta > 0)) - { //jumped over intersection point, go back - tdelta = -tdelta; - } - tdelta *= 0.5f; - dist = fabsf(sample.mV[2] - height); - } - - if (intersection) - { - F32 height = mRegionp->getLandHeightRegion(sample); - if (fabsf(sample.mV[2]-height) < delta.length()*tdelta) - { - sample.mV[2] = mRegionp->getLandHeightRegion(sample); - } - intersection->load3((sample + mRegionp->getOriginAgent()).mV); - } - - if (normal) - { - normal->load3((mRegionp->getLand().resolveNormalGlobal(mRegionp->getPosGlobalFromRegion(sample))).mV); - } - - return true; - } - } - - t += tdelta; - if (t > 1 && t < 1.f+tdelta*0.99f) - { //make sure end point is checked (saves vertical lines coming up negative) - t = 1.f; - } - } - - - return false; -} - -void LLVOSurfacePatch::updateSpatialExtents(LLVector4a& newMin, LLVector4a &newMax) -{ - LLVector3 posAgent = getPositionAgent(); - LLVector3 scale = getScale(); - //make z-axis scale at least 1 to avoid shadow artifacts on totally flat land - scale.mV[VZ] = llmax(scale.mV[VZ], 1.f); - newMin.load3( (posAgent-scale*0.5f).mV); // Changing to 2.f makes the culling a -little- better, but still wrong - newMax.load3( (posAgent+scale*0.5f).mV); - LLVector4a pos; - pos.setAdd(newMin,newMax); - pos.mul(0.5f); - mDrawable->setPositionGroup(pos); -} - -U32 LLVOSurfacePatch::getPartitionType() const -{ - return LLViewerRegion::PARTITION_TERRAIN; -} - -LLTerrainPartition::LLTerrainPartition(LLViewerRegion* regionp) -: LLSpatialPartition(LLDrawPoolTerrain::VERTEX_DATA_MASK, false, regionp) -{ - mOcclusionEnabled = false; - mInfiniteFarClip = true; - mDrawableType = LLPipeline::RENDER_TYPE_TERRAIN; - mPartitionType = LLViewerRegion::PARTITION_TERRAIN; -} - -void LLTerrainPartition::getGeometry(LLSpatialGroup* group) -{ - LL_PROFILE_ZONE_SCOPED; - - LLVertexBuffer* buffer = group->mVertexBuffer; - - //get vertex buffer striders - LLStrider vertices; - LLStrider normals; - LLStrider texcoords2; - LLStrider texcoords; - LLStrider indices; - - llassert_always(buffer->getVertexStrider(vertices)); - llassert_always(buffer->getNormalStrider(normals)); - llassert_always(buffer->getTexCoord0Strider(texcoords)); - llassert_always(buffer->getTexCoord1Strider(texcoords2)); - llassert_always(buffer->getIndexStrider(indices)); - - U32 indices_index = 0; - U32 index_offset = 0; - - for (std::vector::iterator i = mFaceList.begin(); i != mFaceList.end(); ++i) - { - LLFace* facep = *i; - - facep->setIndicesIndex(indices_index); - facep->setGeomIndex(index_offset); - facep->setVertexBuffer(buffer); - - LLVOSurfacePatch* patchp = (LLVOSurfacePatch*) facep->getViewerObject(); - patchp->getGeometry(vertices, normals, texcoords, texcoords2, indices); - - indices_index += facep->getIndicesCount(); - index_offset += facep->getGeomCount(); - } - - buffer->unmapBuffer(); - mFaceList.clear(); -} - +/** + * @file llvosurfacepatch.cpp + * @brief Viewer-object derived "surface patch", which is a piece of terrain + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llvosurfacepatch.h" + +#include "lldrawpoolterrain.h" + +#include "lldrawable.h" +#include "llface.h" +#include "llprimitive.h" +#include "llsky.h" +#include "llsurfacepatch.h" +#include "llsurface.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llvlcomposition.h" +#include "llvovolume.h" +#include "pipeline.h" +#include "llspatialpartition.h" + +F32 LLVOSurfacePatch::sLODFactor = 1.f; + +LLVOSurfacePatch::LLVOSurfacePatch(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) + : LLStaticViewerObject(id, pcode, regionp), + mDirtiedPatch(false), + mPool(NULL), + mBaseComp(0), + mPatchp(NULL), + mDirtyTexture(false), + mDirtyTerrain(false), + mLastNorthStride(0), + mLastEastStride(0), + mLastStride(0), + mLastLength(0) +{ + // Terrain must draw during selection passes so it can block objects behind it. + mbCanSelect = true; + setScale(LLVector3(16.f, 16.f, 16.f)); // Hack for setting scale for bounding boxes/visibility. +} + + +LLVOSurfacePatch::~LLVOSurfacePatch() +{ + mPatchp = NULL; +} + + +void LLVOSurfacePatch::markDead() +{ + if (mPatchp) + { + mPatchp->clearVObj(); + mPatchp = NULL; + } + LLViewerObject::markDead(); +} + + +bool LLVOSurfacePatch::isActive() const +{ + return false; +} + + +void LLVOSurfacePatch::setPixelAreaAndAngle(LLAgent &agent) +{ + mAppAngle = 50; + mPixelArea = 500*500; +} + + +void LLVOSurfacePatch::updateTextures() +{ +} + + +LLFacePool *LLVOSurfacePatch::getPool() +{ + mPool = (LLDrawPoolTerrain*) gPipeline.getPool(LLDrawPool::POOL_TERRAIN, mPatchp->getSurface()->getSTexture()); + + return mPool; +} + + +LLDrawable *LLVOSurfacePatch::createDrawable(LLPipeline *pipeline) +{ + pipeline->allocDrawable(this); + + mDrawable->setRenderType(LLPipeline::RENDER_TYPE_TERRAIN); + + mBaseComp = llfloor(mPatchp->getMinComposition()); + S32 min_comp, max_comp, range; + min_comp = llfloor(mPatchp->getMinComposition()); + max_comp = llceil(mPatchp->getMaxComposition()); + range = (max_comp - min_comp); + range++; + if (range > 3) + { + if ((mPatchp->getMinComposition() - min_comp) > (max_comp - mPatchp->getMaxComposition())) + { + // The top side runs over more + mBaseComp++; + } + range = 3; + } + + LLFacePool *poolp = getPool(); + + mDrawable->addFace(poolp, NULL); + + return mDrawable; +} + + +void LLVOSurfacePatch::updateGL() +{ + if (mPatchp) + { + LL_PROFILE_ZONE_SCOPED + mPatchp->updateGL(); + } +} + +bool LLVOSurfacePatch::updateGeometry(LLDrawable *drawable) +{ + LL_PROFILE_ZONE_SCOPED; + + dirtySpatialGroup(); + + S32 min_comp, max_comp, range; + min_comp = lltrunc(mPatchp->getMinComposition()); + max_comp = lltrunc(ceil(mPatchp->getMaxComposition())); + range = (max_comp - min_comp); + range++; + S32 new_base_comp = lltrunc(mPatchp->getMinComposition()); + if (range > 3) + { + if ((mPatchp->getMinComposition() - min_comp) > (max_comp - mPatchp->getMaxComposition())) + { + // The top side runs over more + new_base_comp++; + } + range = 3; + } + + // Pick the two closest detail textures for this patch... + // Then create the draw pool for it. + // Actually, should get the average composition instead of the center. + mBaseComp = new_base_comp; + + ////////////////////////// + // + // Figure out the strides + // + // + + U32 patch_width, render_stride, north_stride, east_stride, length; + render_stride = mPatchp->getRenderStride(); + patch_width = mPatchp->getSurface()->getGridsPerPatchEdge(); + + length = patch_width / render_stride; + + if (mPatchp->getNeighborPatch(NORTH)) + { + north_stride = mPatchp->getNeighborPatch(NORTH)->getRenderStride(); + } + else + { + north_stride = render_stride; + } + + if (mPatchp->getNeighborPatch(EAST)) + { + east_stride = mPatchp->getNeighborPatch(EAST)->getRenderStride(); + } + else + { + east_stride = render_stride; + } + + mLastLength = length; + mLastStride = render_stride; + mLastNorthStride = north_stride; + mLastEastStride = east_stride; + + return true; +} + +void LLVOSurfacePatch::updateFaceSize(S32 idx) +{ + if (idx != 0) + { + LL_WARNS() << "Terrain partition requested invalid face!!!" << LL_ENDL; + return; + } + + LLFace* facep = mDrawable->getFace(idx); + if (facep) + { + S32 num_vertices = 0; + S32 num_indices = 0; + + if (mLastStride) + { + getGeomSizesMain(mLastStride, num_vertices, num_indices); + getGeomSizesNorth(mLastStride, mLastNorthStride, num_vertices, num_indices); + getGeomSizesEast(mLastStride, mLastEastStride, num_vertices, num_indices); + } + + facep->setSize(num_vertices, num_indices); + } +} + +bool LLVOSurfacePatch::updateLOD() +{ + return true; +} + +void LLVOSurfacePatch::getGeometry(LLStrider &verticesp, + LLStrider &normalsp, + LLStrider &texCoords0p, + LLStrider &texCoords1p, + LLStrider &indicesp) +{ + LLFace* facep = mDrawable->getFace(0); + if (facep) + { + U32 index_offset = facep->getGeomIndex(); + + updateMainGeometry(facep, + verticesp, + normalsp, + texCoords0p, + texCoords1p, + indicesp, + index_offset); + updateNorthGeometry(facep, + verticesp, + normalsp, + texCoords0p, + texCoords1p, + indicesp, + index_offset); + updateEastGeometry(facep, + verticesp, + normalsp, + texCoords0p, + texCoords1p, + indicesp, + index_offset); + } +} + +void LLVOSurfacePatch::updateMainGeometry(LLFace *facep, + LLStrider &verticesp, + LLStrider &normalsp, + LLStrider &texCoords0p, + LLStrider &texCoords1p, + LLStrider &indicesp, + U32 &index_offset) +{ + S32 i, j, x, y; + + U32 patch_size, render_stride; + S32 num_vertices, num_indices; + U32 index; + + llassert(mLastStride > 0); + + render_stride = mLastStride; + patch_size = mPatchp->getSurface()->getGridsPerPatchEdge(); + S32 vert_size = patch_size / render_stride; + + /////////////////////////// + // + // Render the main patch + // + // + + num_vertices = 0; + num_indices = 0; + // First, figure out how many vertices we need... + getGeomSizesMain(render_stride, num_vertices, num_indices); + + if (num_vertices > 0) + { + facep->mCenterAgent = mPatchp->getPointAgent(8, 8); + + // Generate patch points first + for (j = 0; j < vert_size; j++) + { + for (i = 0; i < vert_size; i++) + { + x = i * render_stride; + y = j * render_stride; + mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); + verticesp++; + normalsp++; + texCoords0p++; + texCoords1p++; + } + } + + for (j = 0; j < (vert_size - 1); j++) + { + if (j % 2) + { + for (i = (vert_size - 1); i > 0; i--) + { + index = (i - 1)+ j*vert_size; + *(indicesp++) = index_offset + index; + + index = i + (j+1)*vert_size; + *(indicesp++) = index_offset + index; + + index = (i - 1) + (j+1)*vert_size; + *(indicesp++) = index_offset + index; + + index = (i - 1) + j*vert_size; + *(indicesp++) = index_offset + index; + + index = i + j*vert_size; + *(indicesp++) = index_offset + index; + + index = i + (j+1)*vert_size; + *(indicesp++) = index_offset + index; + } + } + else + { + for (i = 0; i < (vert_size - 1); i++) + { + index = i + j*vert_size; + *(indicesp++) = index_offset + index; + + index = (i + 1) + (j+1)*vert_size; + *(indicesp++) = index_offset + index; + + index = i + (j+1)*vert_size; + *(indicesp++) = index_offset + index; + + index = i + j*vert_size; + *(indicesp++) = index_offset + index; + + index = (i + 1) + j*vert_size; + *(indicesp++) = index_offset + index; + + index = (i + 1) + (j + 1)*vert_size; + *(indicesp++) = index_offset + index; + } + } + } + } + index_offset += num_vertices; +} + + +void LLVOSurfacePatch::updateNorthGeometry(LLFace *facep, + LLStrider &verticesp, + LLStrider &normalsp, + LLStrider &texCoords0p, + LLStrider &texCoords1p, + LLStrider &indicesp, + U32 &index_offset) +{ + S32 i, x, y; + + S32 num_vertices; + + U32 render_stride = mLastStride; + S32 patch_size = mPatchp->getSurface()->getGridsPerPatchEdge(); + S32 length = patch_size / render_stride; + S32 half_length = length / 2; + U32 north_stride = mLastNorthStride; + + /////////////////////////// + // + // Render the north strip + // + // + + // Stride lengths are the same + if (north_stride == render_stride) + { + num_vertices = 2 * length + 1; + + facep->mCenterAgent = (mPatchp->getPointAgent(8, 15) + mPatchp->getPointAgent(8, 16))*0.5f; + + // Main patch + for (i = 0; i < length; i++) + { + x = i * render_stride; + y = 16 - render_stride; + + mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); + verticesp++; + normalsp++; + texCoords0p++; + texCoords1p++; + } + + // North patch + for (i = 0; i <= length; i++) + { + x = i * render_stride; + y = 16; + mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); + verticesp++; + normalsp++; + texCoords0p++; + texCoords1p++; + } + + + for (i = 0; i < length; i++) + { + // Generate indices + *(indicesp++) = index_offset + i; + *(indicesp++) = index_offset + length + i + 1; + *(indicesp++) = index_offset + length + i; + + if (i != length - 1) + { + *(indicesp++) = index_offset + i; + *(indicesp++) = index_offset + i + 1; + *(indicesp++) = index_offset + length + i + 1; + } + } + } + else if (north_stride > render_stride) + { + // North stride is longer (has less vertices) + num_vertices = length + length/2 + 1; + + facep->mCenterAgent = (mPatchp->getPointAgent(7, 15) + mPatchp->getPointAgent(8, 16))*0.5f; + + // Iterate through this patch's points + for (i = 0; i < length; i++) + { + x = i * render_stride; + y = 16 - render_stride; + + mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); + verticesp++; + normalsp++; + texCoords0p++; + texCoords1p++; + } + + // Iterate through the north patch's points + for (i = 0; i <= length; i+=2) + { + x = i * render_stride; + y = 16; + + mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); + verticesp++; + normalsp++; + texCoords0p++; + texCoords1p++; + } + + + for (i = 0; i < length; i++) + { + if (!(i % 2)) + { + *(indicesp++) = index_offset + i; + *(indicesp++) = index_offset + i + 1; + *(indicesp++) = index_offset + length + (i/2); + + *(indicesp++) = index_offset + i + 1; + *(indicesp++) = index_offset + length + (i/2) + 1; + *(indicesp++) = index_offset + length + (i/2); + } + else if (i < (length - 1)) + { + *(indicesp++) = index_offset + i; + *(indicesp++) = index_offset + i + 1; + *(indicesp++) = index_offset + length + (i/2) + 1; + } + } + } + else + { + // North stride is shorter (more vertices) + length = patch_size / north_stride; + half_length = length / 2; + num_vertices = length + half_length + 1; + + facep->mCenterAgent = (mPatchp->getPointAgent(15, 7) + mPatchp->getPointAgent(16, 8))*0.5f; + + // Iterate through this patch's points + for (i = 0; i < length; i+=2) + { + x = i * north_stride; + y = 16 - render_stride; + + mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); + verticesp++; + normalsp++; + texCoords0p++; + texCoords1p++; + } + + // Iterate through the north patch's points + for (i = 0; i <= length; i++) + { + x = i * north_stride; + y = 16; + + mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); + verticesp++; + normalsp++; + texCoords0p++; + texCoords1p++; + } + + for (i = 0; i < length; i++) + { + if (!(i%2)) + { + *(indicesp++) = index_offset + half_length + i; + *(indicesp++) = index_offset + i/2; + *(indicesp++) = index_offset + half_length + i + 1; + } + else if (i < (length - 2)) + { + *(indicesp++) = index_offset + half_length + i; + *(indicesp++) = index_offset + i/2; + *(indicesp++) = index_offset + i/2 + 1; + + *(indicesp++) = index_offset + half_length + i; + *(indicesp++) = index_offset + i/2 + 1; + *(indicesp++) = index_offset + half_length + i + 1; + } + else + { + *(indicesp++) = index_offset + half_length + i; + *(indicesp++) = index_offset + i/2; + *(indicesp++) = index_offset + half_length + i + 1; + } + } + } + index_offset += num_vertices; +} + +void LLVOSurfacePatch::updateEastGeometry(LLFace *facep, + LLStrider &verticesp, + LLStrider &normalsp, + LLStrider &texCoords0p, + LLStrider &texCoords1p, + LLStrider &indicesp, + U32 &index_offset) +{ + S32 i, x, y; + + S32 num_vertices; + + U32 render_stride = mLastStride; + S32 patch_size = mPatchp->getSurface()->getGridsPerPatchEdge(); + S32 length = patch_size / render_stride; + S32 half_length = length / 2; + + U32 east_stride = mLastEastStride; + + // Stride lengths are the same + if (east_stride == render_stride) + { + num_vertices = 2 * length + 1; + + facep->mCenterAgent = (mPatchp->getPointAgent(8, 15) + mPatchp->getPointAgent(8, 16))*0.5f; + + // Main patch + for (i = 0; i < length; i++) + { + x = 16 - render_stride; + y = i * render_stride; + + mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); + verticesp++; + normalsp++; + texCoords0p++; + texCoords1p++; + } + + // East patch + for (i = 0; i <= length; i++) + { + x = 16; + y = i * render_stride; + mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); + verticesp++; + normalsp++; + texCoords0p++; + texCoords1p++; + } + + + for (i = 0; i < length; i++) + { + // Generate indices + *(indicesp++) = index_offset + i; + *(indicesp++) = index_offset + length + i; + *(indicesp++) = index_offset + length + i + 1; + + if (i != length - 1) + { + *(indicesp++) = index_offset + i; + *(indicesp++) = index_offset + length + i + 1; + *(indicesp++) = index_offset + i + 1; + } + } + } + else if (east_stride > render_stride) + { + // East stride is longer (has less vertices) + num_vertices = length + half_length + 1; + + facep->mCenterAgent = (mPatchp->getPointAgent(7, 15) + mPatchp->getPointAgent(8, 16))*0.5f; + + // Iterate through this patch's points + for (i = 0; i < length; i++) + { + x = 16 - render_stride; + y = i * render_stride; + + mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); + verticesp++; + normalsp++; + texCoords0p++; + texCoords1p++; + } + // Iterate through the east patch's points + for (i = 0; i <= length; i+=2) + { + x = 16; + y = i * render_stride; + + mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); + verticesp++; + normalsp++; + texCoords0p++; + texCoords1p++; + } + + for (i = 0; i < length; i++) + { + if (!(i % 2)) + { + *(indicesp++) = index_offset + i; + *(indicesp++) = index_offset + length + (i/2); + *(indicesp++) = index_offset + i + 1; + + *(indicesp++) = index_offset + i + 1; + *(indicesp++) = index_offset + length + (i/2); + *(indicesp++) = index_offset + length + (i/2) + 1; + } + else if (i < (length - 1)) + { + *(indicesp++) = index_offset + i; + *(indicesp++) = index_offset + length + (i/2) + 1; + *(indicesp++) = index_offset + i + 1; + } + } + } + else + { + // East stride is shorter (more vertices) + length = patch_size / east_stride; + half_length = length / 2; + num_vertices = length + length/2 + 1; + + facep->mCenterAgent = (mPatchp->getPointAgent(15, 7) + mPatchp->getPointAgent(16, 8))*0.5f; + + // Iterate through this patch's points + for (i = 0; i < length; i+=2) + { + x = 16 - render_stride; + y = i * east_stride; + + mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); + verticesp++; + normalsp++; + texCoords0p++; + texCoords1p++; + } + // Iterate through the east patch's points + for (i = 0; i <= length; i++) + { + x = 16; + y = i * east_stride; + + mPatchp->eval(x, y, render_stride, verticesp.get(), normalsp.get(), texCoords0p.get(), texCoords1p.get()); + verticesp++; + normalsp++; + texCoords0p++; + texCoords1p++; + } + + for (i = 0; i < length; i++) + { + if (!(i%2)) + { + *(indicesp++) = index_offset + half_length + i; + *(indicesp++) = index_offset + half_length + i + 1; + *(indicesp++) = index_offset + i/2; + } + else if (i < (length - 2)) + { + *(indicesp++) = index_offset + half_length + i; + *(indicesp++) = index_offset + i/2 + 1; + *(indicesp++) = index_offset + i/2; + + *(indicesp++) = index_offset + half_length + i; + *(indicesp++) = index_offset + half_length + i + 1; + *(indicesp++) = index_offset + i/2 + 1; + } + else + { + *(indicesp++) = index_offset + half_length + i; + *(indicesp++) = index_offset + half_length + i + 1; + *(indicesp++) = index_offset + i/2; + } + } + } + index_offset += num_vertices; +} + +void LLVOSurfacePatch::setPatch(LLSurfacePatch *patchp) +{ + mPatchp = patchp; + + dirtyPatch(); +}; + + +void LLVOSurfacePatch::dirtyPatch() +{ + mDirtiedPatch = true; + dirtyGeom(); + mDirtyTerrain = true; + LLVector3 center = mPatchp->getCenterRegion(); + LLSurface *surfacep = mPatchp->getSurface(); + + setPositionRegion(center); + + F32 scale_factor = surfacep->getGridsPerPatchEdge() * surfacep->getMetersPerGrid(); + setScale(LLVector3(scale_factor, scale_factor, mPatchp->getMaxZ() - mPatchp->getMinZ())); +} + +void LLVOSurfacePatch::dirtyGeom() +{ + if (mDrawable) + { + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); + LLFace* facep = mDrawable->getFace(0); + if (facep) + { + facep->setVertexBuffer(NULL); + } + mDrawable->movePartition(); + } +} + +void LLVOSurfacePatch::getGeomSizesMain(const S32 stride, S32 &num_vertices, S32 &num_indices) +{ + S32 patch_size = mPatchp->getSurface()->getGridsPerPatchEdge(); + + // First, figure out how many vertices we need... + S32 vert_size = patch_size / stride; + if (vert_size >= 2) + { + num_vertices += vert_size * vert_size; + num_indices += 6 * (vert_size - 1)*(vert_size - 1); + } +} + +void LLVOSurfacePatch::getGeomSizesNorth(const S32 stride, const S32 north_stride, + S32 &num_vertices, S32 &num_indices) +{ + S32 patch_size = mPatchp->getSurface()->getGridsPerPatchEdge(); + S32 length = patch_size / stride; + // Stride lengths are the same + if (north_stride == stride) + { + num_vertices += 2 * length + 1; + num_indices += length * 6 - 3; + } + else if (north_stride > stride) + { + // North stride is longer (has less vertices) + num_vertices += length + (length/2) + 1; + num_indices += (length/2)*9 - 3; + } + else + { + // North stride is shorter (more vertices) + length = patch_size / north_stride; + num_vertices += length + (length/2) + 1; + num_indices += 9*(length/2) - 3; + } +} + +void LLVOSurfacePatch::getGeomSizesEast(const S32 stride, const S32 east_stride, + S32 &num_vertices, S32 &num_indices) +{ + S32 patch_size = mPatchp->getSurface()->getGridsPerPatchEdge(); + S32 length = patch_size / stride; + // Stride lengths are the same + if (east_stride == stride) + { + num_vertices += 2 * length + 1; + num_indices += length * 6 - 3; + } + else if (east_stride > stride) + { + // East stride is longer (has less vertices) + num_vertices += length + (length/2) + 1; + num_indices += (length/2)*9 - 3; + } + else + { + // East stride is shorter (more vertices) + length = patch_size / east_stride; + num_vertices += length + (length/2) + 1; + num_indices += 9*(length/2) - 3; + } +} + +bool LLVOSurfacePatch::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, S32 face, bool pick_transparent, bool pick_rigged, bool pick_unselectable, S32 *face_hitp, + LLVector4a* intersection,LLVector2* tex_coord, LLVector4a* normal, LLVector4a* tangent) + +{ + + if (!lineSegmentBoundingBox(start, end)) + { + return false; + } + + LLVector4a da; + da.setSub(end, start); + LLVector3 delta(da.getF32ptr()); + + LLVector3 pdelta = delta; + pdelta.mV[2] = 0; + + F32 plength = pdelta.length(); + + F32 tdelta = 1.f/plength; + + LLVector3 v_start(start.getF32ptr()); + + LLVector3 origin = v_start - mRegionp->getOriginAgent(); + + if (mRegionp->getLandHeightRegion(origin) > origin.mV[2]) + { + //origin is under ground, treat as no intersection + return false; + } + + //step one meter at a time until intersection point found + + //VECTORIZE THIS + const LLVector4a* exta = mDrawable->getSpatialExtents(); + + LLVector3 ext[2]; + ext[0].set(exta[0].getF32ptr()); + ext[1].set(exta[1].getF32ptr()); + + F32 rad = (delta*tdelta).magVecSquared(); + + F32 t = 0.f; + while ( t <= 1.f) + { + LLVector3 sample = origin + delta*t; + + if (AABBSphereIntersectR2(ext[0], ext[1], sample+mRegionp->getOriginAgent(), rad)) + { + F32 height = mRegionp->getLandHeightRegion(sample); + if (height > sample.mV[2]) + { //ray went below ground, positive intersection + //quick and dirty binary search to get impact point + tdelta = -tdelta*0.5f; + F32 err_dist = 0.001f; + F32 dist = fabsf(sample.mV[2] - height); + + while (dist > err_dist && tdelta*tdelta > 0.0f) + { + t += tdelta; + sample = origin+delta*t; + height = mRegionp->getLandHeightRegion(sample); + if ((tdelta < 0 && height < sample.mV[2]) || + (height > sample.mV[2] && tdelta > 0)) + { //jumped over intersection point, go back + tdelta = -tdelta; + } + tdelta *= 0.5f; + dist = fabsf(sample.mV[2] - height); + } + + if (intersection) + { + F32 height = mRegionp->getLandHeightRegion(sample); + if (fabsf(sample.mV[2]-height) < delta.length()*tdelta) + { + sample.mV[2] = mRegionp->getLandHeightRegion(sample); + } + intersection->load3((sample + mRegionp->getOriginAgent()).mV); + } + + if (normal) + { + normal->load3((mRegionp->getLand().resolveNormalGlobal(mRegionp->getPosGlobalFromRegion(sample))).mV); + } + + return true; + } + } + + t += tdelta; + if (t > 1 && t < 1.f+tdelta*0.99f) + { //make sure end point is checked (saves vertical lines coming up negative) + t = 1.f; + } + } + + + return false; +} + +void LLVOSurfacePatch::updateSpatialExtents(LLVector4a& newMin, LLVector4a &newMax) +{ + LLVector3 posAgent = getPositionAgent(); + LLVector3 scale = getScale(); + //make z-axis scale at least 1 to avoid shadow artifacts on totally flat land + scale.mV[VZ] = llmax(scale.mV[VZ], 1.f); + newMin.load3( (posAgent-scale*0.5f).mV); // Changing to 2.f makes the culling a -little- better, but still wrong + newMax.load3( (posAgent+scale*0.5f).mV); + LLVector4a pos; + pos.setAdd(newMin,newMax); + pos.mul(0.5f); + mDrawable->setPositionGroup(pos); +} + +U32 LLVOSurfacePatch::getPartitionType() const +{ + return LLViewerRegion::PARTITION_TERRAIN; +} + +LLTerrainPartition::LLTerrainPartition(LLViewerRegion* regionp) +: LLSpatialPartition(LLDrawPoolTerrain::VERTEX_DATA_MASK, false, regionp) +{ + mOcclusionEnabled = false; + mInfiniteFarClip = true; + mDrawableType = LLPipeline::RENDER_TYPE_TERRAIN; + mPartitionType = LLViewerRegion::PARTITION_TERRAIN; +} + +void LLTerrainPartition::getGeometry(LLSpatialGroup* group) +{ + LL_PROFILE_ZONE_SCOPED; + + LLVertexBuffer* buffer = group->mVertexBuffer; + + //get vertex buffer striders + LLStrider vertices; + LLStrider normals; + LLStrider texcoords2; + LLStrider texcoords; + LLStrider indices; + + llassert_always(buffer->getVertexStrider(vertices)); + llassert_always(buffer->getNormalStrider(normals)); + llassert_always(buffer->getTexCoord0Strider(texcoords)); + llassert_always(buffer->getTexCoord1Strider(texcoords2)); + llassert_always(buffer->getIndexStrider(indices)); + + U32 indices_index = 0; + U32 index_offset = 0; + + for (std::vector::iterator i = mFaceList.begin(); i != mFaceList.end(); ++i) + { + LLFace* facep = *i; + + facep->setIndicesIndex(indices_index); + facep->setGeomIndex(index_offset); + facep->setVertexBuffer(buffer); + + LLVOSurfacePatch* patchp = (LLVOSurfacePatch*) facep->getViewerObject(); + patchp->getGeometry(vertices, normals, texcoords, texcoords2, indices); + + indices_index += facep->getIndicesCount(); + index_offset += facep->getGeomCount(); + } + + buffer->unmapBuffer(); + mFaceList.clear(); +} + diff --git a/indra/newview/llvosurfacepatch.h b/indra/newview/llvosurfacepatch.h index 70f403fc66..9e24c33774 100644 --- a/indra/newview/llvosurfacepatch.h +++ b/indra/newview/llvosurfacepatch.h @@ -1,141 +1,141 @@ -/** - * @file llvosurfacepatch.h - * @brief Description of LLVOSurfacePatch class - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_VOSURFACEPATCH_H -#define LL_VOSURFACEPATCH_H - -#include "llviewerobject.h" -#include "llstrider.h" - -class LLSurfacePatch; -class LLDrawPool; -class LLVector2; -class LLFacePool; -class LLFace; - -class LLVOSurfacePatch : public LLStaticViewerObject -{ -public: - static F32 sLODFactor; - - enum - { - VERTEX_DATA_MASK = (1 << LLVertexBuffer::TYPE_VERTEX) | - (1 << LLVertexBuffer::TYPE_NORMAL) | - (1 << LLVertexBuffer::TYPE_TEXCOORD0) | - (1 << LLVertexBuffer::TYPE_TEXCOORD1) - }; - - LLVOSurfacePatch(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); - - /*virtual*/ void markDead(); - - // Initialize data that's only inited once per class. - static void initClass(); - - virtual U32 getPartitionType() const; - - /*virtual*/ LLDrawable* createDrawable(LLPipeline *pipeline); - /*virtual*/ void updateGL(); - /*virtual*/ bool updateGeometry(LLDrawable *drawable); - /*virtual*/ bool updateLOD(); - /*virtual*/ void updateFaceSize(S32 idx); - void getGeometry(LLStrider &verticesp, - LLStrider &normalsp, - LLStrider &texCoords0p, - LLStrider &texCoords1p, - LLStrider &indicesp); - - /*virtual*/ void updateTextures(); - /*virtual*/ void setPixelAreaAndAngle(LLAgent &agent); // generate accurate apparent angle and area - - /*virtual*/ void updateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax); - /*virtual*/ bool isActive() const; // Whether this object needs to do an idleUpdate. - - void setPatch(LLSurfacePatch *patchp); - LLSurfacePatch *getPatch() const { return mPatchp; } - - void dirtyPatch(); - void dirtyGeom(); - - /*virtual*/ bool lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, - S32 face = -1, // which face to check, -1 = ALL_SIDES - bool pick_transparent = false, - bool pick_rigged = false, - bool pick_unselectable = true, - S32* face_hit = NULL, // which face was hit - LLVector4a* intersection = NULL, // return the intersection point - LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point - LLVector4a* normal = NULL, // return the surface normal at the intersection point - LLVector4a* tangent = NULL // return the surface tangent at the intersection point - ); - - bool mDirtiedPatch; -protected: - ~LLVOSurfacePatch(); - - LLFacePool *mPool; - LLFacePool *getPool(); - S32 mBaseComp; - LLSurfacePatch *mPatchp; - bool mDirtyTexture; - bool mDirtyTerrain; - - S32 mLastNorthStride; - S32 mLastEastStride; - S32 mLastStride; - S32 mLastLength; - - void getGeomSizesMain(const S32 stride, S32 &num_vertices, S32 &num_indices); - void getGeomSizesNorth(const S32 stride, const S32 north_stride, - S32 &num_vertices, S32 &num_indices); - void getGeomSizesEast(const S32 stride, const S32 east_stride, - S32 &num_vertices, S32 &num_indices); - - void updateMainGeometry(LLFace *facep, - LLStrider &verticesp, - LLStrider &normalsp, - LLStrider &texCoords0p, - LLStrider &texCoords1p, - LLStrider &indicesp, - U32 &index_offset); - void updateNorthGeometry(LLFace *facep, - LLStrider &verticesp, - LLStrider &normalsp, - LLStrider &texCoords0p, - LLStrider &texCoords1p, - LLStrider &indicesp, - U32 &index_offset); - void updateEastGeometry(LLFace *facep, - LLStrider &verticesp, - LLStrider &normalsp, - LLStrider &texCoords0p, - LLStrider &texCoords1p, - LLStrider &indicesp, - U32 &index_offset); -}; - -#endif // LL_VOSURFACEPATCH_H +/** + * @file llvosurfacepatch.h + * @brief Description of LLVOSurfacePatch class + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_VOSURFACEPATCH_H +#define LL_VOSURFACEPATCH_H + +#include "llviewerobject.h" +#include "llstrider.h" + +class LLSurfacePatch; +class LLDrawPool; +class LLVector2; +class LLFacePool; +class LLFace; + +class LLVOSurfacePatch : public LLStaticViewerObject +{ +public: + static F32 sLODFactor; + + enum + { + VERTEX_DATA_MASK = (1 << LLVertexBuffer::TYPE_VERTEX) | + (1 << LLVertexBuffer::TYPE_NORMAL) | + (1 << LLVertexBuffer::TYPE_TEXCOORD0) | + (1 << LLVertexBuffer::TYPE_TEXCOORD1) + }; + + LLVOSurfacePatch(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); + + /*virtual*/ void markDead(); + + // Initialize data that's only inited once per class. + static void initClass(); + + virtual U32 getPartitionType() const; + + /*virtual*/ LLDrawable* createDrawable(LLPipeline *pipeline); + /*virtual*/ void updateGL(); + /*virtual*/ bool updateGeometry(LLDrawable *drawable); + /*virtual*/ bool updateLOD(); + /*virtual*/ void updateFaceSize(S32 idx); + void getGeometry(LLStrider &verticesp, + LLStrider &normalsp, + LLStrider &texCoords0p, + LLStrider &texCoords1p, + LLStrider &indicesp); + + /*virtual*/ void updateTextures(); + /*virtual*/ void setPixelAreaAndAngle(LLAgent &agent); // generate accurate apparent angle and area + + /*virtual*/ void updateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax); + /*virtual*/ bool isActive() const; // Whether this object needs to do an idleUpdate. + + void setPatch(LLSurfacePatch *patchp); + LLSurfacePatch *getPatch() const { return mPatchp; } + + void dirtyPatch(); + void dirtyGeom(); + + /*virtual*/ bool lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, + S32 face = -1, // which face to check, -1 = ALL_SIDES + bool pick_transparent = false, + bool pick_rigged = false, + bool pick_unselectable = true, + S32* face_hit = NULL, // which face was hit + LLVector4a* intersection = NULL, // return the intersection point + LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point + LLVector4a* normal = NULL, // return the surface normal at the intersection point + LLVector4a* tangent = NULL // return the surface tangent at the intersection point + ); + + bool mDirtiedPatch; +protected: + ~LLVOSurfacePatch(); + + LLFacePool *mPool; + LLFacePool *getPool(); + S32 mBaseComp; + LLSurfacePatch *mPatchp; + bool mDirtyTexture; + bool mDirtyTerrain; + + S32 mLastNorthStride; + S32 mLastEastStride; + S32 mLastStride; + S32 mLastLength; + + void getGeomSizesMain(const S32 stride, S32 &num_vertices, S32 &num_indices); + void getGeomSizesNorth(const S32 stride, const S32 north_stride, + S32 &num_vertices, S32 &num_indices); + void getGeomSizesEast(const S32 stride, const S32 east_stride, + S32 &num_vertices, S32 &num_indices); + + void updateMainGeometry(LLFace *facep, + LLStrider &verticesp, + LLStrider &normalsp, + LLStrider &texCoords0p, + LLStrider &texCoords1p, + LLStrider &indicesp, + U32 &index_offset); + void updateNorthGeometry(LLFace *facep, + LLStrider &verticesp, + LLStrider &normalsp, + LLStrider &texCoords0p, + LLStrider &texCoords1p, + LLStrider &indicesp, + U32 &index_offset); + void updateEastGeometry(LLFace *facep, + LLStrider &verticesp, + LLStrider &normalsp, + LLStrider &texCoords0p, + LLStrider &texCoords1p, + LLStrider &indicesp, + U32 &index_offset); +}; + +#endif // LL_VOSURFACEPATCH_H diff --git a/indra/newview/llvotree.cpp b/indra/newview/llvotree.cpp index 4822b0b7ee..a981c60ef2 100644 --- a/indra/newview/llvotree.cpp +++ b/indra/newview/llvotree.cpp @@ -1,1237 +1,1237 @@ -/** - * @file llvotree.cpp - * @brief LLVOTree class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llvotree.h" - -#include "lldrawpooltree.h" - -#include "llviewercontrol.h" -#include "lldir.h" -#include "llprimitive.h" -#include "lltree_common.h" -#include "llxmltree.h" -#include "material_codes.h" -#include "object_flags.h" - -#include "llagentcamera.h" -#include "lldrawable.h" -#include "llface.h" -#include "llviewercamera.h" -#include "llviewertexturelist.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llvolumemgr.h" -#include "llvovolume.h" -#include "llworld.h" -#include "noise.h" -#include "pipeline.h" -#include "llspatialpartition.h" -#include "llnotificationsutil.h" -#include "raytrace.h" -#include "llglslshader.h" - -extern LLPipeline gPipeline; - -const S32 MAX_SLICES = 32; -const F32 LEAF_LEFT = 0.52f; -const F32 LEAF_RIGHT = 0.98f; -const F32 LEAF_TOP = 1.0f; -const F32 LEAF_BOTTOM = 0.52f; -const F32 LEAF_WIDTH = 1.f; - -const S32 LLVOTree::sMAX_NUM_TREE_LOD_LEVELS = 4 ; - -S32 LLVOTree::sLODVertexOffset[sMAX_NUM_TREE_LOD_LEVELS]; -S32 LLVOTree::sLODVertexCount[sMAX_NUM_TREE_LOD_LEVELS]; -S32 LLVOTree::sLODIndexOffset[sMAX_NUM_TREE_LOD_LEVELS]; -S32 LLVOTree::sLODIndexCount[sMAX_NUM_TREE_LOD_LEVELS]; -S32 LLVOTree::sLODSlices[sMAX_NUM_TREE_LOD_LEVELS] = {10, 5, 4, 3}; -F32 LLVOTree::sLODAngles[sMAX_NUM_TREE_LOD_LEVELS] = {30.f, 20.f, 15.f, F_ALMOST_ZERO}; - -F32 LLVOTree::sTreeFactor = 1.f; - -LLVOTree::SpeciesMap LLVOTree::sSpeciesTable; -S32 LLVOTree::sMaxTreeSpecies = 0; - -// Tree variables and functions - -LLVOTree::LLVOTree(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp): - LLViewerObject(id, pcode, regionp) -{ - mSpecies = 0; - mFrameCount = 0; - mWind = mRegionp->mWind.getVelocity(getPositionRegion()); - mTrunkLOD = 0; - - // if assert triggers, idleUpdate() needs to be revised and adjusted to new LOD levels - llassert(sMAX_NUM_TREE_LOD_LEVELS == LLVolumeLODGroup::NUM_LODS); -} - - -LLVOTree::~LLVOTree() -{ - if (mData) - { - delete[] mData; - mData = NULL; - } -} - -//static -bool LLVOTree::isTreeRenderingStopped() -{ - return LLVOTree::sTreeFactor < LLVOTree::sLODAngles[sMAX_NUM_TREE_LOD_LEVELS - 1] ; -} - -// static -void LLVOTree::initClass() -{ - std::string xml_filename = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS,"trees.xml"); - - LLXmlTree tree_def_tree; - - if (!tree_def_tree.parseFile(xml_filename)) - { - LL_ERRS() << "Failed to parse tree file." << LL_ENDL; - } - - LLXmlTreeNode* rootp = tree_def_tree.getRoot(); - - for (LLXmlTreeNode* tree_def = rootp->getFirstChild(); - tree_def; - tree_def = rootp->getNextChild()) - { - if (!tree_def->hasName("tree")) - { - LL_WARNS() << "Invalid tree definition node " << tree_def->getName() << LL_ENDL; - continue; - } - F32 F32_val; - LLUUID id; - S32 S32_val; - - bool success{ true }; - S32 species; - static LLStdStringHandle species_id_string = LLXmlTree::addAttributeString("species_id"); - if (!tree_def->getFastAttributeS32(species_id_string, species)) - { - LL_WARNS() << "No species id defined" << LL_ENDL; - continue; - } - - if (species < 0) - { - LL_WARNS() << "Invalid species id " << species << LL_ENDL; - continue; - } - - if (sSpeciesTable.count(species)) - { - LL_WARNS() << "Tree species " << species << " already defined! Duplicate discarded." << LL_ENDL; - continue; - } - - TreeSpeciesData* newTree = new TreeSpeciesData(); - - static LLStdStringHandle texture_id_string = LLXmlTree::addAttributeString("texture_id"); - success &= tree_def->getFastAttributeUUID(texture_id_string, id); - newTree->mTextureID = id; - - static LLStdStringHandle droop_string = LLXmlTree::addAttributeString("droop"); - success &= tree_def->getFastAttributeF32(droop_string, F32_val); - newTree->mDroop = F32_val; - - static LLStdStringHandle twist_string = LLXmlTree::addAttributeString("twist"); - success &= tree_def->getFastAttributeF32(twist_string, F32_val); - newTree->mTwist = F32_val; - - static LLStdStringHandle branches_string = LLXmlTree::addAttributeString("branches"); - success &= tree_def->getFastAttributeF32(branches_string, F32_val); - newTree->mBranches = F32_val; - - static LLStdStringHandle depth_string = LLXmlTree::addAttributeString("depth"); - success &= tree_def->getFastAttributeS32(depth_string, S32_val); - newTree->mDepth = S32_val; - - static LLStdStringHandle scale_step_string = LLXmlTree::addAttributeString("scale_step"); - success &= tree_def->getFastAttributeF32(scale_step_string, F32_val); - newTree->mScaleStep = F32_val; - - static LLStdStringHandle trunk_depth_string = LLXmlTree::addAttributeString("trunk_depth"); - success &= tree_def->getFastAttributeS32(trunk_depth_string, S32_val); - newTree->mTrunkDepth = S32_val; - - static LLStdStringHandle branch_length_string = LLXmlTree::addAttributeString("branch_length"); - success &= tree_def->getFastAttributeF32(branch_length_string, F32_val); - newTree->mBranchLength = F32_val; - - static LLStdStringHandle trunk_length_string = LLXmlTree::addAttributeString("trunk_length"); - success &= tree_def->getFastAttributeF32(trunk_length_string, F32_val); - newTree->mTrunkLength = F32_val; - - static LLStdStringHandle leaf_scale_string = LLXmlTree::addAttributeString("leaf_scale"); - success &= tree_def->getFastAttributeF32(leaf_scale_string, F32_val); - newTree->mLeafScale = F32_val; - - static LLStdStringHandle billboard_scale_string = LLXmlTree::addAttributeString("billboard_scale"); - success &= tree_def->getFastAttributeF32(billboard_scale_string, F32_val); - newTree->mBillboardScale = F32_val; - - static LLStdStringHandle billboard_ratio_string = LLXmlTree::addAttributeString("billboard_ratio"); - success &= tree_def->getFastAttributeF32(billboard_ratio_string, F32_val); - newTree->mBillboardRatio = F32_val; - - static LLStdStringHandle trunk_aspect_string = LLXmlTree::addAttributeString("trunk_aspect"); - success &= tree_def->getFastAttributeF32(trunk_aspect_string, F32_val); - newTree->mTrunkAspect = F32_val; - - static LLStdStringHandle branch_aspect_string = LLXmlTree::addAttributeString("branch_aspect"); - success &= tree_def->getFastAttributeF32(branch_aspect_string, F32_val); - newTree->mBranchAspect = F32_val; - - static LLStdStringHandle leaf_rotate_string = LLXmlTree::addAttributeString("leaf_rotate"); - success &= tree_def->getFastAttributeF32(leaf_rotate_string, F32_val); - newTree->mRandomLeafRotate = F32_val; - - static LLStdStringHandle noise_mag_string = LLXmlTree::addAttributeString("noise_mag"); - success &= tree_def->getFastAttributeF32(noise_mag_string, F32_val); - newTree->mNoiseMag = F32_val; - - static LLStdStringHandle noise_scale_string = LLXmlTree::addAttributeString("noise_scale"); - success &= tree_def->getFastAttributeF32(noise_scale_string, F32_val); - newTree->mNoiseScale = F32_val; - - static LLStdStringHandle taper_string = LLXmlTree::addAttributeString("taper"); - success &= tree_def->getFastAttributeF32(taper_string, F32_val); - newTree->mTaper = F32_val; - - static LLStdStringHandle repeat_z_string = LLXmlTree::addAttributeString("repeat_z"); - success &= tree_def->getFastAttributeF32(repeat_z_string, F32_val); - newTree->mRepeatTrunkZ = F32_val; - - sSpeciesTable[species] = newTree; - - if (species >= sMaxTreeSpecies) sMaxTreeSpecies = species + 1; - - if (!success) - { - std::string name; - static LLStdStringHandle name_string = LLXmlTree::addAttributeString("name"); - tree_def->getFastAttributeString(name_string, name); - LL_WARNS() << "Incomplete definition of tree " << name << LL_ENDL; - } - } - - bool have_all_trees {true}; - std::string err; - - for (S32 i=0;i 0.f) - ||(getAcceleration().lengthSquared() > 0.f) - ||(getAngularVelocity().lengthSquared() > 0.f)) - { - LL_INFOS() << "ACK! Moving tree!" << LL_ENDL; - setVelocity(LLVector3::zero); - setAcceleration(LLVector3::zero); - setAngularVelocity(LLVector3::zero); - } - - if (update_type == OUT_TERSE_IMPROVED) - { - // Nothing else needs to be done for the terse message. - return retval; - } - - // - // Load Instance-Specific data - // - if (mData) - { - mSpecies = ((U8 *)mData)[0]; - } - - if (!sSpeciesTable.count(mSpecies)) - { - if (sSpeciesTable.size()) - { - SpeciesMap::const_iterator it = sSpeciesTable.begin(); - mSpecies = (*it).first; - } - } - - // - // Load Species-Specific data - // - static const S32 MAX_TREE_TEXTURE_VIRTURE_SIZE_RESET_INTERVAL = 32 ; //frames. - mTreeImagep = LLViewerTextureManager::getFetchedTexture(sSpeciesTable[mSpecies]->mTextureID, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); - mTreeImagep->setMaxVirtualSizeResetInterval(MAX_TREE_TEXTURE_VIRTURE_SIZE_RESET_INTERVAL); //allow to wait for at most 16 frames to reset virtual size. - - mBranchLength = sSpeciesTable[mSpecies]->mBranchLength; - mTrunkLength = sSpeciesTable[mSpecies]->mTrunkLength; - mLeafScale = sSpeciesTable[mSpecies]->mLeafScale; - mDroop = sSpeciesTable[mSpecies]->mDroop; - mTwist = sSpeciesTable[mSpecies]->mTwist; - mBranches = sSpeciesTable[mSpecies]->mBranches; - mDepth = sSpeciesTable[mSpecies]->mDepth; - mScaleStep = sSpeciesTable[mSpecies]->mScaleStep; - mTrunkDepth = sSpeciesTable[mSpecies]->mTrunkDepth; - mBillboardScale = sSpeciesTable[mSpecies]->mBillboardScale; - mBillboardRatio = sSpeciesTable[mSpecies]->mBillboardRatio; - mTrunkAspect = sSpeciesTable[mSpecies]->mTrunkAspect; - mBranchAspect = sSpeciesTable[mSpecies]->mBranchAspect; - - // position change not caused by us, etc. make sure to rebuild. - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); - - return retval; -} - -void LLVOTree::idleUpdate(LLAgent &agent, const F64 &time) -{ - if (mDead || !(gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_TREE))) - { - return; - } - - S32 trunk_LOD = sMAX_NUM_TREE_LOD_LEVELS ; // disabled - F32 app_angle = getAppAngle()*LLVOTree::sTreeFactor; - F32 distance = mDrawable->mDistanceWRTCamera * LLVOVolume::sDistanceFactor * (F_PI / 3.f); - F32 diameter = getScale().length(); // trees have very broken scale, but length rougtly outlines proper diameter - F32 sz = mBillboardScale * mBillboardRatio * diameter; - - for (S32 j = 0; j < sMAX_NUM_TREE_LOD_LEVELS; j++) - { - if (app_angle > LLVOTree::sLODAngles[j]) - { - trunk_LOD = j; - break; - } - } - - F32 tan_angle = (LLVOTree::sTreeFactor * 64 * sz) / distance; - S32 cur_detail = LLVolumeLODGroup::getDetailFromTan(ll_round(tan_angle, 0.01f)); // larger value, better quality - - // for trunk_LOD lower value means better quality, but both trunk_LOD and cur_detail have 4 levels - trunk_LOD = llmax(trunk_LOD, LLVolumeLODGroup::NUM_LODS - cur_detail - 1); - trunk_LOD = llmin(trunk_LOD, sMAX_NUM_TREE_LOD_LEVELS); - - if (mReferenceBuffer.isNull()) - { - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); - } - else if (trunk_LOD != mTrunkLOD) - { - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); - } - else - { - // we're not animating but we may *still* need to - // regenerate the mesh if we moved, since position - // and rotation are baked into the mesh. - // *TODO: I don't know what's so special about trees - // that they don't get REBUILD_POSITION automatically - // at a higher level. - const LLVector3 &this_position = getPositionRegion(); - if (this_position != mLastPosition) - { - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_POSITION); - mLastPosition = this_position; - } - else - { - const LLQuaternion &this_rotation = getRotation(); - - if (this_rotation != mLastRotation) - { - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_POSITION); - mLastRotation = this_rotation; - } - } - } - - mTrunkLOD = trunk_LOD; -} - -void LLVOTree::render(LLAgent &agent) -{ -} - - -void LLVOTree::setPixelAreaAndAngle(LLAgent &agent) -{ - LLVector3 center = getPositionAgent();//center of tree. - LLVector3 viewer_pos_agent = gAgentCamera.getCameraPositionAgent(); - LLVector3 lookAt = center - viewer_pos_agent; - F32 dist = lookAt.normVec() ; - F32 cos_angle_to_view_dir = lookAt * LLViewerCamera::getInstance()->getXAxis() ; - F32 radius = getScale().length()*0.5f; - F32 range = dist - radius; - - if (range < F_ALMOST_ZERO || isHUDAttachment()) // range == zero - { - mAppAngle = 180.f; - } - else - { - mAppAngle = (F32) atan2( getMaxScale(), range) * RAD_TO_DEG; - } - - F32 max_scale = mBillboardScale * getMaxScale(); - F32 area = max_scale * (max_scale*mBillboardRatio); - // Compute pixels per meter at the given range - F32 pixels_per_meter = LLViewerCamera::getInstance()->getViewHeightInPixels() / (tan(LLViewerCamera::getInstance()->getView()) * dist); - mPixelArea = pixels_per_meter * pixels_per_meter * area ; - - F32 importance = LLFace::calcImportanceToCamera(cos_angle_to_view_dir, dist) ; - mPixelArea = LLFace::adjustPixelArea(importance, mPixelArea) ; - if (mPixelArea > LLViewerCamera::getInstance()->getScreenPixelArea()) - { - mAppAngle = 180.f; - } - -#if 0 - // mAppAngle is a bit of voodoo; - // use the one calculated LLViewerObject::setPixelAreaAndAngle above - // to avoid LOD miscalculations - mAppAngle = (F32) atan2( max_scale, range) * RAD_TO_DEG; -#endif -} - -void LLVOTree::updateTextures() -{ - if (mTreeImagep) - { - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_AREA)) - { - setDebugText(llformat("%4.0f", (F32) sqrt(mPixelArea))); - } - mTreeImagep->addTextureStats(mPixelArea); - } - -} - - -LLDrawable* LLVOTree::createDrawable(LLPipeline *pipeline) -{ - pipeline->allocDrawable(this); - mDrawable->setLit(false); - - mDrawable->setRenderType(LLPipeline::RENDER_TYPE_TREE); - - LLDrawPoolTree *poolp = (LLDrawPoolTree*) gPipeline.getPool(LLDrawPool::POOL_TREE, mTreeImagep); - - // Just a placeholder for an actual object... - LLFace *facep = mDrawable->addFace(poolp, mTreeImagep); - facep->setSize(1, 3); - - updateRadius(); - - return mDrawable; -} - - -// Yes, I know this is bad. I'll clean this up soon. - djs 04/02/02 -const S32 LEAF_INDICES = 24; -const S32 LEAF_VERTICES = 16; - -bool LLVOTree::updateGeometry(LLDrawable *drawable) -{ - LL_PROFILE_ZONE_SCOPED; - - if(mTrunkLOD >= sMAX_NUM_TREE_LOD_LEVELS) //do not display the tree. - { - mReferenceBuffer = NULL ; - LLFace * facep = drawable->getFace(0); - if (facep) - { - facep->setVertexBuffer(NULL); - } - return true ; - } - - if (mDrawable->getFace(0) && - (mReferenceBuffer.isNull() || !mDrawable->getFace(0)->getVertexBuffer())) - { - const F32 SRR3 = 0.577350269f; // sqrt(1/3) - const F32 SRR2 = 0.707106781f; // sqrt(1/2) - U32 i, j; - - U32 slices = MAX_SLICES; - - S32 max_indices = LEAF_INDICES; - S32 max_vertices = LEAF_VERTICES; - S32 lod; - - LLFace *face = drawable->getFace(0); - if (!face) return true; - - face->mCenterAgent = getPositionAgent(); - face->mCenterLocal = face->mCenterAgent; - - for (lod = 0; lod < sMAX_NUM_TREE_LOD_LEVELS; lod++) - { - slices = sLODSlices[lod]; - sLODVertexOffset[lod] = max_vertices; - sLODVertexCount[lod] = slices*slices; - sLODIndexOffset[lod] = max_indices; - sLODIndexCount[lod] = (slices-1)*(slices-1)*6; - max_indices += sLODIndexCount[lod]; - max_vertices += sLODVertexCount[lod]; - } - - mReferenceBuffer = new LLVertexBuffer(LLDrawPoolTree::VERTEX_DATA_MASK); - if (!mReferenceBuffer->allocateBuffer(max_vertices, max_indices)) - { - LL_WARNS() << "Failed to allocate Vertex Buffer on update to " - << max_vertices << " vertices and " - << max_indices << " indices" << LL_ENDL; - mReferenceBuffer = NULL; //unref - return true; - } - - LLStrider vertices; - LLStrider normals; - LLStrider colors; - LLStrider tex_coords; - LLStrider indicesp; - - mReferenceBuffer->getVertexStrider(vertices); - mReferenceBuffer->getNormalStrider(normals); - mReferenceBuffer->getTexCoord0Strider(tex_coords); - mReferenceBuffer->getColorStrider(colors); - mReferenceBuffer->getIndexStrider(indicesp); - - S32 vertex_count = 0; - S32 index_count = 0; - - // First leaf - *(normals++) = LLVector3(-SRR2, -SRR2, 0.f); - *(tex_coords++) = LLVector2(LEAF_LEFT, LEAF_BOTTOM); - *(vertices++) = LLVector3(-0.5f*LEAF_WIDTH, 0.f, 0.f); - *(colors++) = LLColor4U::white; - vertex_count++; - - *(normals++) = LLVector3(SRR3, -SRR3, SRR3); - *(tex_coords++) = LLVector2(LEAF_RIGHT, LEAF_TOP); - *(vertices++) = LLVector3(0.5f*LEAF_WIDTH, 0.f, 1.f); - *(colors++) = LLColor4U::white; - vertex_count++; - - *(normals++) = LLVector3(-SRR3, -SRR3, SRR3); - *(tex_coords++) = LLVector2(LEAF_LEFT, LEAF_TOP); - *(vertices++) = LLVector3(-0.5f*LEAF_WIDTH, 0.f, 1.f); - *(colors++) = LLColor4U::white; - vertex_count++; - - *(normals++) = LLVector3(SRR2, -SRR2, 0.f); - *(tex_coords++) = LLVector2(LEAF_RIGHT, LEAF_BOTTOM); - *(vertices++) = LLVector3(0.5f*LEAF_WIDTH, 0.f, 0.f); - *(colors++) = LLColor4U::white; - vertex_count++; - - *(indicesp++) = 0; - index_count++; - *(indicesp++) = 1; - index_count++; - *(indicesp++) = 2; - index_count++; - - *(indicesp++) = 0; - index_count++; - *(indicesp++) = 3; - index_count++; - *(indicesp++) = 1; - index_count++; - - // Same leaf, inverse winding/normals - *(normals++) = LLVector3(-SRR2, SRR2, 0.f); - *(tex_coords++) = LLVector2(LEAF_LEFT, LEAF_BOTTOM); - *(vertices++) = LLVector3(-0.5f*LEAF_WIDTH, 0.f, 0.f); - *(colors++) = LLColor4U::white; - vertex_count++; - - *(normals++) = LLVector3(SRR3, SRR3, SRR3); - *(tex_coords++) = LLVector2(LEAF_RIGHT, LEAF_TOP); - *(vertices++) = LLVector3(0.5f*LEAF_WIDTH, 0.f, 1.f); - *(colors++) = LLColor4U::white; - vertex_count++; - - *(normals++) = LLVector3(-SRR3, SRR3, SRR3); - *(tex_coords++) = LLVector2(LEAF_LEFT, LEAF_TOP); - *(vertices++) = LLVector3(-0.5f*LEAF_WIDTH, 0.f, 1.f); - *(colors++) = LLColor4U::white; - vertex_count++; - - *(normals++) = LLVector3(SRR2, SRR2, 0.f); - *(tex_coords++) = LLVector2(LEAF_RIGHT, LEAF_BOTTOM); - *(vertices++) = LLVector3(0.5f*LEAF_WIDTH, 0.f, 0.f); - *(colors++) = LLColor4U::white; - vertex_count++; - - *(indicesp++) = 4; - index_count++; - *(indicesp++) = 6; - index_count++; - *(indicesp++) = 5; - index_count++; - - *(indicesp++) = 4; - index_count++; - *(indicesp++) = 5; - index_count++; - *(indicesp++) = 7; - index_count++; - - - // next leaf - *(normals++) = LLVector3(SRR2, -SRR2, 0.f); - *(tex_coords++) = LLVector2(LEAF_LEFT, LEAF_BOTTOM); - *(vertices++) = LLVector3(0.f, -0.5f*LEAF_WIDTH, 0.f); - *(colors++) = LLColor4U::white; - vertex_count++; - - *(normals++) = LLVector3(SRR3, SRR3, SRR3); - *(tex_coords++) = LLVector2(LEAF_RIGHT, LEAF_TOP); - *(vertices++) = LLVector3(0.f, 0.5f*LEAF_WIDTH, 1.f); - *(colors++) = LLColor4U::white; - vertex_count++; - - *(normals++) = LLVector3(SRR3, -SRR3, SRR3); - *(tex_coords++) = LLVector2(LEAF_LEFT, LEAF_TOP); - *(vertices++) = LLVector3(0.f, -0.5f*LEAF_WIDTH, 1.f); - *(colors++) = LLColor4U::white; - vertex_count++; - - *(normals++) = LLVector3(SRR2, SRR2, 0.f); - *(tex_coords++) = LLVector2(LEAF_RIGHT, LEAF_BOTTOM); - *(vertices++) = LLVector3(0.f, 0.5f*LEAF_WIDTH, 0.f); - *(colors++) = LLColor4U::white; - vertex_count++; - - *(indicesp++) = 8; - index_count++; - *(indicesp++) = 9; - index_count++; - *(indicesp++) = 10; - index_count++; - - *(indicesp++) = 8; - index_count++; - *(indicesp++) = 11; - index_count++; - *(indicesp++) = 9; - index_count++; - - - // other side of same leaf - *(normals++) = LLVector3(-SRR2, -SRR2, 0.f); - *(tex_coords++) = LLVector2(LEAF_LEFT, LEAF_BOTTOM); - *(vertices++) = LLVector3(0.f, -0.5f*LEAF_WIDTH, 0.f); - *(colors++) = LLColor4U::white; - vertex_count++; - - *(normals++) = LLVector3(-SRR3, SRR3, SRR3); - *(tex_coords++) = LLVector2(LEAF_RIGHT, LEAF_TOP); - *(vertices++) = LLVector3(0.f, 0.5f*LEAF_WIDTH, 1.f); - *(colors++) = LLColor4U::white; - vertex_count++; - - *(normals++) = LLVector3(-SRR3, -SRR3, SRR3); - *(tex_coords++) = LLVector2(LEAF_LEFT, LEAF_TOP); - *(vertices++) = LLVector3(0.f, -0.5f*LEAF_WIDTH, 1.f); - *(colors++) = LLColor4U::white; - vertex_count++; - - *(normals++) = LLVector3(-SRR2, SRR2, 0.f); - *(tex_coords++) = LLVector2(LEAF_RIGHT, LEAF_BOTTOM); - *(vertices++) = LLVector3(0.f, 0.5f*LEAF_WIDTH, 0.f); - *(colors++) = LLColor4U::white; - vertex_count++; - - *(indicesp++) = 12; - index_count++; - *(indicesp++) = 14; - index_count++; - *(indicesp++) = 13; - index_count++; - - *(indicesp++) = 12; - index_count++; - *(indicesp++) = 13; - index_count++; - *(indicesp++) = 15; - index_count++; - - // Generate geometry for the cylinders - - // Different LOD's - - // Generate the vertices - // Generate the indices - - for (lod = 0; lod < sMAX_NUM_TREE_LOD_LEVELS; lod++) - { - slices = sLODSlices[lod]; - F32 base_radius = 0.65f; - F32 top_radius = base_radius * sSpeciesTable[mSpecies]->mTaper; - //LL_INFOS() << "Species " << ((U32) mSpecies) << ", taper = " << sSpeciesTable[mSpecies].mTaper << LL_ENDL; - //LL_INFOS() << "Droop " << mDroop << ", branchlength: " << mBranchLength << LL_ENDL; - F32 angle = 0; - F32 angle_inc = 360.f/(slices-1); - F32 z = 0.f; - F32 z_inc = 1.f; - if (slices > 3) - { - z_inc = 1.f/(slices - 3); - } - F32 radius = base_radius; - - F32 x1,y1; - F32 noise_scale = sSpeciesTable[mSpecies]->mNoiseMag; - LLVector3 nvec; - - const F32 cap_nudge = 0.1f; // Height to 'peak' the caps on top/bottom of branch - - const S32 fractal_depth = 5; - F32 nvec_scale = 1.f * sSpeciesTable[mSpecies]->mNoiseScale; - F32 nvec_scalez = 4.f * sSpeciesTable[mSpecies]->mNoiseScale; - - F32 tex_z_repeat = sSpeciesTable[mSpecies]->mRepeatTrunkZ; - - F32 start_radius; - F32 nangle = 0; - F32 height = 1.f; - F32 r0; - - for (i = 0; i < slices; i++) - { - if (i == 0) - { - z = - cap_nudge; - r0 = 0.0; - } - else if (i == (slices - 1)) - { - z = 1.f + cap_nudge;//((i - 2) * z_inc) + cap_nudge; - r0 = 0.0; - } - else - { - z = (i - 1) * z_inc; - r0 = base_radius + (top_radius - base_radius)*z; - } - - for (j = 0; j < slices; j++) - { - if (slices - 1 == j) - { - angle = 0.f; - } - else - { - angle = j*angle_inc; - } - - nangle = angle; - - x1 = cos(angle * DEG_TO_RAD); - y1 = sin(angle * DEG_TO_RAD); - LLVector2 tc; - // This isn't totally accurate. Should compute based on slope as well. - start_radius = r0 * (1.f + 1.2f*fabs(z - 0.66f*height)/height); - nvec.set( cos(nangle * DEG_TO_RAD)*start_radius*nvec_scale, - sin(nangle * DEG_TO_RAD)*start_radius*nvec_scale, - z*nvec_scalez); - // First and last slice at 0 radius (to bring in top/bottom of structure) - radius = start_radius + turbulence3((F32*)&nvec.mV, (F32)fractal_depth)*noise_scale; - - if (slices - 1 == j) - { - // Not 0.5 for slight slop factor to avoid edges on leaves - tc = LLVector2(0.490f, (1.f - z/2.f)*tex_z_repeat); - } - else - { - tc = LLVector2((angle/360.f)*0.5f, (1.f - z/2.f)*tex_z_repeat); - } - - *(vertices++) = LLVector3(x1*radius, y1*radius, z); - *(normals++) = LLVector3(x1, y1, 0.f); - *(tex_coords++) = tc; - *(colors++) = LLColor4U::white; - vertex_count++; - } - } - - for (i = 0; i < (slices - 1); i++) - { - for (j = 0; j < (slices - 1); j++) - { - S32 x1_offset = j+1; - if ((j+1) == slices) - { - x1_offset = 0; - } - // Generate the matching quads - *(indicesp) = j + (i*slices) + sLODVertexOffset[lod]; - llassert(*(indicesp) < (U32)max_vertices); - indicesp++; - index_count++; - *(indicesp) = x1_offset + ((i+1)*slices) + sLODVertexOffset[lod]; - llassert(*(indicesp) < (U32)max_vertices); - indicesp++; - index_count++; - *(indicesp) = j + ((i+1)*slices) + sLODVertexOffset[lod]; - llassert(*(indicesp) < (U32)max_vertices); - indicesp++; - index_count++; - - *(indicesp) = j + (i*slices) + sLODVertexOffset[lod]; - llassert(*(indicesp) < (U32)max_vertices); - indicesp++; - index_count++; - *(indicesp) = x1_offset + (i*slices) + sLODVertexOffset[lod]; - llassert(*(indicesp) < (U32)max_vertices); - indicesp++; - index_count++; - *(indicesp) = x1_offset + ((i+1)*slices) + sLODVertexOffset[lod]; - llassert(*(indicesp) < (U32)max_vertices); - indicesp++; - index_count++; - } - } - slices /= 2; - } - - mReferenceBuffer->unmapBuffer(); - llassert(vertex_count == max_vertices); - llassert(index_count == max_indices); -#ifndef SHOW_ASSERT - (void)vertex_count; - (void)index_count; -#endif - } - - //generate tree mesh - updateMesh(); - - return true; -} - -void LLVOTree::updateMesh() -{ - LLMatrix4 matrix; - - // Translate to tree base HACK - adjustment in Z plants tree underground - const LLVector3 &pos_region = getPositionRegion(); - //gGL.translatef(pos_agent.mV[VX], pos_agent.mV[VY], pos_agent.mV[VZ] - 0.1f); - LLMatrix4 trans_mat; - trans_mat.setTranslation(pos_region.mV[VX], pos_region.mV[VY], pos_region.mV[VZ] - 0.1f); - trans_mat *= matrix; - - // Rotate to tree position and bend for current trunk/wind - // Note that trunk stiffness controls the amount of bend at the trunk as - // opposed to the crown of the tree - // - const F32 TRUNK_STIFF = 22.f; - - LLQuaternion rot = - LLQuaternion(mTrunkBend.magVec()*TRUNK_STIFF*DEG_TO_RAD, LLVector4(mTrunkBend.mV[VX], mTrunkBend.mV[VY], 0)) * - LLQuaternion(90.f*DEG_TO_RAD, LLVector4(0,0,1)) * - getRotation(); - - LLMatrix4 rot_mat(rot); - rot_mat *= trans_mat; - - F32 radius = getScale().magVec()*0.05f; - LLMatrix4 scale_mat; - scale_mat.mMatrix[0][0] = - scale_mat.mMatrix[1][1] = - scale_mat.mMatrix[2][2] = radius; - - scale_mat *= rot_mat; - -// const F32 THRESH_ANGLE_FOR_BILLBOARD = 15.f; -// const F32 BLEND_RANGE_FOR_BILLBOARD = 3.f; - - F32 droop = mDroop + 25.f*(1.f - mTrunkBend.magVec()); - - S32 stop_depth = 0; - F32 alpha = 1.0; - - U32 vert_count = 0; - U32 index_count = 0; - - calcNumVerts(vert_count, index_count, mTrunkLOD, stop_depth, mDepth, mTrunkDepth, mBranches); - - LLFace* facep = mDrawable->getFace(0); - if (!facep) return; - LLVertexBuffer* buff = new LLVertexBuffer(LLDrawPoolTree::VERTEX_DATA_MASK); - if (!buff->allocateBuffer(vert_count, index_count)) - { - LL_WARNS() << "Failed to allocate Vertex Buffer on mesh update to " - << vert_count << " vertices and " - << index_count << " indices" << LL_ENDL; - buff->allocateBuffer(1, 3); - memset((U8*)buff->getMappedData(), 0, buff->getSize()); - memset((U8*)buff->getMappedIndices(), 0, buff->getIndicesSize()); - facep->setSize(1, 3); - facep->setVertexBuffer(buff); - mReferenceBuffer->unmapBuffer(); - buff->unmapBuffer(); - return; - } - - facep->setVertexBuffer(buff); - - LLStrider vertices; - LLStrider normals; - LLStrider tex_coords; - LLStrider colors; - LLStrider indices; - U16 idx_offset = 0; - - buff->getVertexStrider(vertices); - buff->getNormalStrider(normals); - buff->getTexCoord0Strider(tex_coords); - buff->getColorStrider(colors); - buff->getIndexStrider(indices); - - genBranchPipeline(vertices, normals, tex_coords, colors, indices, idx_offset, scale_mat, mTrunkLOD, stop_depth, mDepth, mTrunkDepth, 1.0, mTwist, droop, mBranches, alpha); - - mReferenceBuffer->unmapBuffer(); - buff->unmapBuffer(); -} - -void LLVOTree::appendMesh(LLStrider& vertices, - LLStrider& normals, - LLStrider& tex_coords, - LLStrider& colors, - LLStrider& indices, - U16& cur_idx, - LLMatrix4& matrix, - LLMatrix4& norm_mat, - S32 vert_start, - S32 vert_count, - S32 index_count, - S32 index_offset) -{ - LLStrider v; - LLStrider n; - LLStrider t; - LLStrider c; - LLStrider idx; - - mReferenceBuffer->getVertexStrider(v); - mReferenceBuffer->getNormalStrider(n); - mReferenceBuffer->getTexCoord0Strider(t); - mReferenceBuffer->getColorStrider(c); - mReferenceBuffer->getIndexStrider(idx); - - //copy/transform vertices into mesh - check - for (S32 i = 0; i < vert_count; i++) - { - U16 index = vert_start + i; - *vertices++ = v[index] * matrix; - LLVector3 norm = n[index] * norm_mat; - norm.normalize(); - *normals++ = norm; - *tex_coords++ = t[index]; - *colors++ = c[index]; - } - - //copy offset indices into mesh - check - for (S32 i = 0; i < index_count; i++) - { - U16 index = index_offset + i; - *indices++ = idx[index]-vert_start+cur_idx; - } - - //increment index offset - check - cur_idx += vert_count; -} - - -void LLVOTree::genBranchPipeline(LLStrider& vertices, - LLStrider& normals, - LLStrider& tex_coords, - LLStrider& colors, - LLStrider& indices, - U16& index_offset, - LLMatrix4& matrix, - S32 trunk_LOD, - S32 stop_level, - U16 depth, - U16 trunk_depth, - F32 scale, - F32 twist, - F32 droop, - F32 branches, - F32 alpha) -{ - // - // Generates a tree mesh by recursing, generating branches and then a 'leaf' texture. - - static F32 constant_twist; - static F32 width = 0; - - F32 length = ((trunk_depth || (scale == 1.f))? mTrunkLength:mBranchLength); - F32 aspect = ((trunk_depth || (scale == 1.f))? mTrunkAspect:mBranchAspect); - - constant_twist = 360.f/branches; - - if (stop_level >= 0) - { - if (depth > stop_level) - { - { - llassert(sLODIndexCount[trunk_LOD] > 0); - width = scale * length * aspect; - LLMatrix4 scale_mat; - scale_mat.mMatrix[0][0] = width; - scale_mat.mMatrix[1][1] = width; - scale_mat.mMatrix[2][2] = scale*length; - scale_mat *= matrix; - - glh::matrix4f norm((F32*) scale_mat.mMatrix); - LLMatrix4 norm_mat = LLMatrix4(norm.inverse().transpose().m); - - norm_mat.invert(); - appendMesh(vertices, normals, tex_coords, colors, indices, index_offset, scale_mat, norm_mat, - sLODVertexOffset[trunk_LOD], sLODVertexCount[trunk_LOD], sLODIndexCount[trunk_LOD], sLODIndexOffset[trunk_LOD]); - } - - // Recurse to create more branches - for (S32 i=0; i < (S32)branches; i++) - { - LLMatrix4 trans_mat; - trans_mat.setTranslation(0,0,scale*length); - trans_mat *= matrix; - - LLQuaternion rot = - LLQuaternion(20.f*DEG_TO_RAD, LLVector4(0.f, 0.f, 1.f)) * - LLQuaternion(droop*DEG_TO_RAD, LLVector4(0.f, 1.f, 0.f)) * - LLQuaternion(((constant_twist + ((i%2==0)?twist:-twist))*i)*DEG_TO_RAD, LLVector4(0.f, 0.f, 1.f)); - - LLMatrix4 rot_mat(rot); - rot_mat *= trans_mat; - - genBranchPipeline(vertices, normals, tex_coords, colors, indices, index_offset, rot_mat, trunk_LOD, stop_level, depth - 1, 0, scale*mScaleStep, twist, droop, branches, alpha); - } - // Recurse to continue trunk - if (trunk_depth) - { - LLMatrix4 trans_mat; - trans_mat.setTranslation(0,0,scale*length); - trans_mat *= matrix; - - LLMatrix4 rot_mat(70.5f*DEG_TO_RAD, LLVector4(0,0,1)); - rot_mat *= trans_mat; // rotate a bit around Z when ascending - genBranchPipeline(vertices, normals, tex_coords, colors, indices, index_offset, rot_mat, trunk_LOD, stop_level, depth, trunk_depth-1, scale*mScaleStep, twist, droop, branches, alpha); - } - } - else - { - // - // Append leaves as two 90 deg crossed quads with leaf textures - // - { - LLMatrix4 scale_mat; - scale_mat.mMatrix[0][0] = - scale_mat.mMatrix[1][1] = - scale_mat.mMatrix[2][2] = scale*mLeafScale; - - scale_mat *= matrix; - - glh::matrix4f norm((F32*) scale_mat.mMatrix); - LLMatrix4 norm_mat = LLMatrix4(norm.inverse().transpose().m); - - appendMesh(vertices, normals, tex_coords, colors, indices, index_offset, scale_mat, norm_mat, 0, LEAF_VERTICES, LEAF_INDICES, 0); - } - } - } -} - - - -void LLVOTree::calcNumVerts(U32& vert_count, U32& index_count, S32 trunk_LOD, S32 stop_level, U16 depth, U16 trunk_depth, F32 branches) -{ - if (stop_level >= 0) - { - if (depth > stop_level) - { - index_count += sLODIndexCount[trunk_LOD]; - vert_count += sLODVertexCount[trunk_LOD]; - - // Recurse to create more branches - for (S32 i=0; i < (S32)branches; i++) - { - calcNumVerts(vert_count, index_count, trunk_LOD, stop_level, depth - 1, 0, branches); - } - - // Recurse to continue trunk - if (trunk_depth) - { - calcNumVerts(vert_count, index_count, trunk_LOD, stop_level, depth, trunk_depth-1, branches); - } - } - else - { - index_count += LEAF_INDICES; - vert_count += LEAF_VERTICES; - } - } - else - { - index_count += LEAF_INDICES; - vert_count += LEAF_VERTICES; - } -} - -void LLVOTree::updateRadius() -{ - if (mDrawable.isNull()) - { - return; - } - - mDrawable->setRadius(32.0f); -} - -void LLVOTree::updateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax) -{ - F32 radius = getScale().length()*0.05f; - LLVector3 center = getRenderPosition(); - - F32 sz = mBillboardScale*mBillboardRatio*radius*0.5f; - LLVector3 size(sz,sz,sz); - - center += LLVector3(0, 0, size.mV[2]) * getRotation(); - - newMin.load3((center-size).mV); - newMax.load3((center+size).mV); - LLVector4a pos; - pos.load3(center.mV); - mDrawable->setPositionGroup(pos); -} - -bool LLVOTree::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, S32 face, bool pick_transparent, bool pick_rigged, bool pick_unselectable, S32 *face_hitp, - LLVector4a* intersection,LLVector2* tex_coord, LLVector4a* normal, LLVector4a* tangent) - -{ - - if (!lineSegmentBoundingBox(start, end)) - { - return false; - } - - const LLVector4a* exta = mDrawable->getSpatialExtents(); - - //VECTORIZE THIS - LLVector3 ext[2]; - ext[0].set(exta[0].getF32ptr()); - ext[1].set(exta[1].getF32ptr()); - - LLVector3 center = (ext[1]+ext[0])*0.5f; - LLVector3 size = (ext[1]-ext[0]); - - LLQuaternion quat = getRotation(); - - center -= LLVector3(0,0,size.magVec() * 0.25f)*quat; - - size.scaleVec(LLVector3(0.25f, 0.25f, 1.f)); - size.mV[0] = llmin(size.mV[0], 1.f); - size.mV[1] = llmin(size.mV[1], 1.f); - - LLVector3 pos, norm; - - LLVector3 start3(start.getF32ptr()); - LLVector3 end3(end.getF32ptr()); - - if (linesegment_tetrahedron(start3, end3, center, size, quat, pos, norm)) - { - if (intersection) - { - intersection->load3(pos.mV); - } - - if (normal) - { - normal->load3(norm.mV); - } - return true; - } - - return false; -} - -U32 LLVOTree::getPartitionType() const -{ - return LLViewerRegion::PARTITION_TREE; -} - -LLTreePartition::LLTreePartition(LLViewerRegion* regionp) -: LLSpatialPartition(0, false, regionp) -{ - mDrawableType = LLPipeline::RENDER_TYPE_TREE; - mPartitionType = LLViewerRegion::PARTITION_TREE; - mSlopRatio = 0.f; - mLODPeriod = 1; -} - +/** + * @file llvotree.cpp + * @brief LLVOTree class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llvotree.h" + +#include "lldrawpooltree.h" + +#include "llviewercontrol.h" +#include "lldir.h" +#include "llprimitive.h" +#include "lltree_common.h" +#include "llxmltree.h" +#include "material_codes.h" +#include "object_flags.h" + +#include "llagentcamera.h" +#include "lldrawable.h" +#include "llface.h" +#include "llviewercamera.h" +#include "llviewertexturelist.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llvolumemgr.h" +#include "llvovolume.h" +#include "llworld.h" +#include "noise.h" +#include "pipeline.h" +#include "llspatialpartition.h" +#include "llnotificationsutil.h" +#include "raytrace.h" +#include "llglslshader.h" + +extern LLPipeline gPipeline; + +const S32 MAX_SLICES = 32; +const F32 LEAF_LEFT = 0.52f; +const F32 LEAF_RIGHT = 0.98f; +const F32 LEAF_TOP = 1.0f; +const F32 LEAF_BOTTOM = 0.52f; +const F32 LEAF_WIDTH = 1.f; + +const S32 LLVOTree::sMAX_NUM_TREE_LOD_LEVELS = 4 ; + +S32 LLVOTree::sLODVertexOffset[sMAX_NUM_TREE_LOD_LEVELS]; +S32 LLVOTree::sLODVertexCount[sMAX_NUM_TREE_LOD_LEVELS]; +S32 LLVOTree::sLODIndexOffset[sMAX_NUM_TREE_LOD_LEVELS]; +S32 LLVOTree::sLODIndexCount[sMAX_NUM_TREE_LOD_LEVELS]; +S32 LLVOTree::sLODSlices[sMAX_NUM_TREE_LOD_LEVELS] = {10, 5, 4, 3}; +F32 LLVOTree::sLODAngles[sMAX_NUM_TREE_LOD_LEVELS] = {30.f, 20.f, 15.f, F_ALMOST_ZERO}; + +F32 LLVOTree::sTreeFactor = 1.f; + +LLVOTree::SpeciesMap LLVOTree::sSpeciesTable; +S32 LLVOTree::sMaxTreeSpecies = 0; + +// Tree variables and functions + +LLVOTree::LLVOTree(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp): + LLViewerObject(id, pcode, regionp) +{ + mSpecies = 0; + mFrameCount = 0; + mWind = mRegionp->mWind.getVelocity(getPositionRegion()); + mTrunkLOD = 0; + + // if assert triggers, idleUpdate() needs to be revised and adjusted to new LOD levels + llassert(sMAX_NUM_TREE_LOD_LEVELS == LLVolumeLODGroup::NUM_LODS); +} + + +LLVOTree::~LLVOTree() +{ + if (mData) + { + delete[] mData; + mData = NULL; + } +} + +//static +bool LLVOTree::isTreeRenderingStopped() +{ + return LLVOTree::sTreeFactor < LLVOTree::sLODAngles[sMAX_NUM_TREE_LOD_LEVELS - 1] ; +} + +// static +void LLVOTree::initClass() +{ + std::string xml_filename = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS,"trees.xml"); + + LLXmlTree tree_def_tree; + + if (!tree_def_tree.parseFile(xml_filename)) + { + LL_ERRS() << "Failed to parse tree file." << LL_ENDL; + } + + LLXmlTreeNode* rootp = tree_def_tree.getRoot(); + + for (LLXmlTreeNode* tree_def = rootp->getFirstChild(); + tree_def; + tree_def = rootp->getNextChild()) + { + if (!tree_def->hasName("tree")) + { + LL_WARNS() << "Invalid tree definition node " << tree_def->getName() << LL_ENDL; + continue; + } + F32 F32_val; + LLUUID id; + S32 S32_val; + + bool success{ true }; + S32 species; + static LLStdStringHandle species_id_string = LLXmlTree::addAttributeString("species_id"); + if (!tree_def->getFastAttributeS32(species_id_string, species)) + { + LL_WARNS() << "No species id defined" << LL_ENDL; + continue; + } + + if (species < 0) + { + LL_WARNS() << "Invalid species id " << species << LL_ENDL; + continue; + } + + if (sSpeciesTable.count(species)) + { + LL_WARNS() << "Tree species " << species << " already defined! Duplicate discarded." << LL_ENDL; + continue; + } + + TreeSpeciesData* newTree = new TreeSpeciesData(); + + static LLStdStringHandle texture_id_string = LLXmlTree::addAttributeString("texture_id"); + success &= tree_def->getFastAttributeUUID(texture_id_string, id); + newTree->mTextureID = id; + + static LLStdStringHandle droop_string = LLXmlTree::addAttributeString("droop"); + success &= tree_def->getFastAttributeF32(droop_string, F32_val); + newTree->mDroop = F32_val; + + static LLStdStringHandle twist_string = LLXmlTree::addAttributeString("twist"); + success &= tree_def->getFastAttributeF32(twist_string, F32_val); + newTree->mTwist = F32_val; + + static LLStdStringHandle branches_string = LLXmlTree::addAttributeString("branches"); + success &= tree_def->getFastAttributeF32(branches_string, F32_val); + newTree->mBranches = F32_val; + + static LLStdStringHandle depth_string = LLXmlTree::addAttributeString("depth"); + success &= tree_def->getFastAttributeS32(depth_string, S32_val); + newTree->mDepth = S32_val; + + static LLStdStringHandle scale_step_string = LLXmlTree::addAttributeString("scale_step"); + success &= tree_def->getFastAttributeF32(scale_step_string, F32_val); + newTree->mScaleStep = F32_val; + + static LLStdStringHandle trunk_depth_string = LLXmlTree::addAttributeString("trunk_depth"); + success &= tree_def->getFastAttributeS32(trunk_depth_string, S32_val); + newTree->mTrunkDepth = S32_val; + + static LLStdStringHandle branch_length_string = LLXmlTree::addAttributeString("branch_length"); + success &= tree_def->getFastAttributeF32(branch_length_string, F32_val); + newTree->mBranchLength = F32_val; + + static LLStdStringHandle trunk_length_string = LLXmlTree::addAttributeString("trunk_length"); + success &= tree_def->getFastAttributeF32(trunk_length_string, F32_val); + newTree->mTrunkLength = F32_val; + + static LLStdStringHandle leaf_scale_string = LLXmlTree::addAttributeString("leaf_scale"); + success &= tree_def->getFastAttributeF32(leaf_scale_string, F32_val); + newTree->mLeafScale = F32_val; + + static LLStdStringHandle billboard_scale_string = LLXmlTree::addAttributeString("billboard_scale"); + success &= tree_def->getFastAttributeF32(billboard_scale_string, F32_val); + newTree->mBillboardScale = F32_val; + + static LLStdStringHandle billboard_ratio_string = LLXmlTree::addAttributeString("billboard_ratio"); + success &= tree_def->getFastAttributeF32(billboard_ratio_string, F32_val); + newTree->mBillboardRatio = F32_val; + + static LLStdStringHandle trunk_aspect_string = LLXmlTree::addAttributeString("trunk_aspect"); + success &= tree_def->getFastAttributeF32(trunk_aspect_string, F32_val); + newTree->mTrunkAspect = F32_val; + + static LLStdStringHandle branch_aspect_string = LLXmlTree::addAttributeString("branch_aspect"); + success &= tree_def->getFastAttributeF32(branch_aspect_string, F32_val); + newTree->mBranchAspect = F32_val; + + static LLStdStringHandle leaf_rotate_string = LLXmlTree::addAttributeString("leaf_rotate"); + success &= tree_def->getFastAttributeF32(leaf_rotate_string, F32_val); + newTree->mRandomLeafRotate = F32_val; + + static LLStdStringHandle noise_mag_string = LLXmlTree::addAttributeString("noise_mag"); + success &= tree_def->getFastAttributeF32(noise_mag_string, F32_val); + newTree->mNoiseMag = F32_val; + + static LLStdStringHandle noise_scale_string = LLXmlTree::addAttributeString("noise_scale"); + success &= tree_def->getFastAttributeF32(noise_scale_string, F32_val); + newTree->mNoiseScale = F32_val; + + static LLStdStringHandle taper_string = LLXmlTree::addAttributeString("taper"); + success &= tree_def->getFastAttributeF32(taper_string, F32_val); + newTree->mTaper = F32_val; + + static LLStdStringHandle repeat_z_string = LLXmlTree::addAttributeString("repeat_z"); + success &= tree_def->getFastAttributeF32(repeat_z_string, F32_val); + newTree->mRepeatTrunkZ = F32_val; + + sSpeciesTable[species] = newTree; + + if (species >= sMaxTreeSpecies) sMaxTreeSpecies = species + 1; + + if (!success) + { + std::string name; + static LLStdStringHandle name_string = LLXmlTree::addAttributeString("name"); + tree_def->getFastAttributeString(name_string, name); + LL_WARNS() << "Incomplete definition of tree " << name << LL_ENDL; + } + } + + bool have_all_trees {true}; + std::string err; + + for (S32 i=0;i 0.f) + ||(getAcceleration().lengthSquared() > 0.f) + ||(getAngularVelocity().lengthSquared() > 0.f)) + { + LL_INFOS() << "ACK! Moving tree!" << LL_ENDL; + setVelocity(LLVector3::zero); + setAcceleration(LLVector3::zero); + setAngularVelocity(LLVector3::zero); + } + + if (update_type == OUT_TERSE_IMPROVED) + { + // Nothing else needs to be done for the terse message. + return retval; + } + + // + // Load Instance-Specific data + // + if (mData) + { + mSpecies = ((U8 *)mData)[0]; + } + + if (!sSpeciesTable.count(mSpecies)) + { + if (sSpeciesTable.size()) + { + SpeciesMap::const_iterator it = sSpeciesTable.begin(); + mSpecies = (*it).first; + } + } + + // + // Load Species-Specific data + // + static const S32 MAX_TREE_TEXTURE_VIRTURE_SIZE_RESET_INTERVAL = 32 ; //frames. + mTreeImagep = LLViewerTextureManager::getFetchedTexture(sSpeciesTable[mSpecies]->mTextureID, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + mTreeImagep->setMaxVirtualSizeResetInterval(MAX_TREE_TEXTURE_VIRTURE_SIZE_RESET_INTERVAL); //allow to wait for at most 16 frames to reset virtual size. + + mBranchLength = sSpeciesTable[mSpecies]->mBranchLength; + mTrunkLength = sSpeciesTable[mSpecies]->mTrunkLength; + mLeafScale = sSpeciesTable[mSpecies]->mLeafScale; + mDroop = sSpeciesTable[mSpecies]->mDroop; + mTwist = sSpeciesTable[mSpecies]->mTwist; + mBranches = sSpeciesTable[mSpecies]->mBranches; + mDepth = sSpeciesTable[mSpecies]->mDepth; + mScaleStep = sSpeciesTable[mSpecies]->mScaleStep; + mTrunkDepth = sSpeciesTable[mSpecies]->mTrunkDepth; + mBillboardScale = sSpeciesTable[mSpecies]->mBillboardScale; + mBillboardRatio = sSpeciesTable[mSpecies]->mBillboardRatio; + mTrunkAspect = sSpeciesTable[mSpecies]->mTrunkAspect; + mBranchAspect = sSpeciesTable[mSpecies]->mBranchAspect; + + // position change not caused by us, etc. make sure to rebuild. + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); + + return retval; +} + +void LLVOTree::idleUpdate(LLAgent &agent, const F64 &time) +{ + if (mDead || !(gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_TREE))) + { + return; + } + + S32 trunk_LOD = sMAX_NUM_TREE_LOD_LEVELS ; // disabled + F32 app_angle = getAppAngle()*LLVOTree::sTreeFactor; + F32 distance = mDrawable->mDistanceWRTCamera * LLVOVolume::sDistanceFactor * (F_PI / 3.f); + F32 diameter = getScale().length(); // trees have very broken scale, but length rougtly outlines proper diameter + F32 sz = mBillboardScale * mBillboardRatio * diameter; + + for (S32 j = 0; j < sMAX_NUM_TREE_LOD_LEVELS; j++) + { + if (app_angle > LLVOTree::sLODAngles[j]) + { + trunk_LOD = j; + break; + } + } + + F32 tan_angle = (LLVOTree::sTreeFactor * 64 * sz) / distance; + S32 cur_detail = LLVolumeLODGroup::getDetailFromTan(ll_round(tan_angle, 0.01f)); // larger value, better quality + + // for trunk_LOD lower value means better quality, but both trunk_LOD and cur_detail have 4 levels + trunk_LOD = llmax(trunk_LOD, LLVolumeLODGroup::NUM_LODS - cur_detail - 1); + trunk_LOD = llmin(trunk_LOD, sMAX_NUM_TREE_LOD_LEVELS); + + if (mReferenceBuffer.isNull()) + { + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); + } + else if (trunk_LOD != mTrunkLOD) + { + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); + } + else + { + // we're not animating but we may *still* need to + // regenerate the mesh if we moved, since position + // and rotation are baked into the mesh. + // *TODO: I don't know what's so special about trees + // that they don't get REBUILD_POSITION automatically + // at a higher level. + const LLVector3 &this_position = getPositionRegion(); + if (this_position != mLastPosition) + { + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_POSITION); + mLastPosition = this_position; + } + else + { + const LLQuaternion &this_rotation = getRotation(); + + if (this_rotation != mLastRotation) + { + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_POSITION); + mLastRotation = this_rotation; + } + } + } + + mTrunkLOD = trunk_LOD; +} + +void LLVOTree::render(LLAgent &agent) +{ +} + + +void LLVOTree::setPixelAreaAndAngle(LLAgent &agent) +{ + LLVector3 center = getPositionAgent();//center of tree. + LLVector3 viewer_pos_agent = gAgentCamera.getCameraPositionAgent(); + LLVector3 lookAt = center - viewer_pos_agent; + F32 dist = lookAt.normVec() ; + F32 cos_angle_to_view_dir = lookAt * LLViewerCamera::getInstance()->getXAxis() ; + F32 radius = getScale().length()*0.5f; + F32 range = dist - radius; + + if (range < F_ALMOST_ZERO || isHUDAttachment()) // range == zero + { + mAppAngle = 180.f; + } + else + { + mAppAngle = (F32) atan2( getMaxScale(), range) * RAD_TO_DEG; + } + + F32 max_scale = mBillboardScale * getMaxScale(); + F32 area = max_scale * (max_scale*mBillboardRatio); + // Compute pixels per meter at the given range + F32 pixels_per_meter = LLViewerCamera::getInstance()->getViewHeightInPixels() / (tan(LLViewerCamera::getInstance()->getView()) * dist); + mPixelArea = pixels_per_meter * pixels_per_meter * area ; + + F32 importance = LLFace::calcImportanceToCamera(cos_angle_to_view_dir, dist) ; + mPixelArea = LLFace::adjustPixelArea(importance, mPixelArea) ; + if (mPixelArea > LLViewerCamera::getInstance()->getScreenPixelArea()) + { + mAppAngle = 180.f; + } + +#if 0 + // mAppAngle is a bit of voodoo; + // use the one calculated LLViewerObject::setPixelAreaAndAngle above + // to avoid LOD miscalculations + mAppAngle = (F32) atan2( max_scale, range) * RAD_TO_DEG; +#endif +} + +void LLVOTree::updateTextures() +{ + if (mTreeImagep) + { + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_AREA)) + { + setDebugText(llformat("%4.0f", (F32) sqrt(mPixelArea))); + } + mTreeImagep->addTextureStats(mPixelArea); + } + +} + + +LLDrawable* LLVOTree::createDrawable(LLPipeline *pipeline) +{ + pipeline->allocDrawable(this); + mDrawable->setLit(false); + + mDrawable->setRenderType(LLPipeline::RENDER_TYPE_TREE); + + LLDrawPoolTree *poolp = (LLDrawPoolTree*) gPipeline.getPool(LLDrawPool::POOL_TREE, mTreeImagep); + + // Just a placeholder for an actual object... + LLFace *facep = mDrawable->addFace(poolp, mTreeImagep); + facep->setSize(1, 3); + + updateRadius(); + + return mDrawable; +} + + +// Yes, I know this is bad. I'll clean this up soon. - djs 04/02/02 +const S32 LEAF_INDICES = 24; +const S32 LEAF_VERTICES = 16; + +bool LLVOTree::updateGeometry(LLDrawable *drawable) +{ + LL_PROFILE_ZONE_SCOPED; + + if(mTrunkLOD >= sMAX_NUM_TREE_LOD_LEVELS) //do not display the tree. + { + mReferenceBuffer = NULL ; + LLFace * facep = drawable->getFace(0); + if (facep) + { + facep->setVertexBuffer(NULL); + } + return true ; + } + + if (mDrawable->getFace(0) && + (mReferenceBuffer.isNull() || !mDrawable->getFace(0)->getVertexBuffer())) + { + const F32 SRR3 = 0.577350269f; // sqrt(1/3) + const F32 SRR2 = 0.707106781f; // sqrt(1/2) + U32 i, j; + + U32 slices = MAX_SLICES; + + S32 max_indices = LEAF_INDICES; + S32 max_vertices = LEAF_VERTICES; + S32 lod; + + LLFace *face = drawable->getFace(0); + if (!face) return true; + + face->mCenterAgent = getPositionAgent(); + face->mCenterLocal = face->mCenterAgent; + + for (lod = 0; lod < sMAX_NUM_TREE_LOD_LEVELS; lod++) + { + slices = sLODSlices[lod]; + sLODVertexOffset[lod] = max_vertices; + sLODVertexCount[lod] = slices*slices; + sLODIndexOffset[lod] = max_indices; + sLODIndexCount[lod] = (slices-1)*(slices-1)*6; + max_indices += sLODIndexCount[lod]; + max_vertices += sLODVertexCount[lod]; + } + + mReferenceBuffer = new LLVertexBuffer(LLDrawPoolTree::VERTEX_DATA_MASK); + if (!mReferenceBuffer->allocateBuffer(max_vertices, max_indices)) + { + LL_WARNS() << "Failed to allocate Vertex Buffer on update to " + << max_vertices << " vertices and " + << max_indices << " indices" << LL_ENDL; + mReferenceBuffer = NULL; //unref + return true; + } + + LLStrider vertices; + LLStrider normals; + LLStrider colors; + LLStrider tex_coords; + LLStrider indicesp; + + mReferenceBuffer->getVertexStrider(vertices); + mReferenceBuffer->getNormalStrider(normals); + mReferenceBuffer->getTexCoord0Strider(tex_coords); + mReferenceBuffer->getColorStrider(colors); + mReferenceBuffer->getIndexStrider(indicesp); + + S32 vertex_count = 0; + S32 index_count = 0; + + // First leaf + *(normals++) = LLVector3(-SRR2, -SRR2, 0.f); + *(tex_coords++) = LLVector2(LEAF_LEFT, LEAF_BOTTOM); + *(vertices++) = LLVector3(-0.5f*LEAF_WIDTH, 0.f, 0.f); + *(colors++) = LLColor4U::white; + vertex_count++; + + *(normals++) = LLVector3(SRR3, -SRR3, SRR3); + *(tex_coords++) = LLVector2(LEAF_RIGHT, LEAF_TOP); + *(vertices++) = LLVector3(0.5f*LEAF_WIDTH, 0.f, 1.f); + *(colors++) = LLColor4U::white; + vertex_count++; + + *(normals++) = LLVector3(-SRR3, -SRR3, SRR3); + *(tex_coords++) = LLVector2(LEAF_LEFT, LEAF_TOP); + *(vertices++) = LLVector3(-0.5f*LEAF_WIDTH, 0.f, 1.f); + *(colors++) = LLColor4U::white; + vertex_count++; + + *(normals++) = LLVector3(SRR2, -SRR2, 0.f); + *(tex_coords++) = LLVector2(LEAF_RIGHT, LEAF_BOTTOM); + *(vertices++) = LLVector3(0.5f*LEAF_WIDTH, 0.f, 0.f); + *(colors++) = LLColor4U::white; + vertex_count++; + + *(indicesp++) = 0; + index_count++; + *(indicesp++) = 1; + index_count++; + *(indicesp++) = 2; + index_count++; + + *(indicesp++) = 0; + index_count++; + *(indicesp++) = 3; + index_count++; + *(indicesp++) = 1; + index_count++; + + // Same leaf, inverse winding/normals + *(normals++) = LLVector3(-SRR2, SRR2, 0.f); + *(tex_coords++) = LLVector2(LEAF_LEFT, LEAF_BOTTOM); + *(vertices++) = LLVector3(-0.5f*LEAF_WIDTH, 0.f, 0.f); + *(colors++) = LLColor4U::white; + vertex_count++; + + *(normals++) = LLVector3(SRR3, SRR3, SRR3); + *(tex_coords++) = LLVector2(LEAF_RIGHT, LEAF_TOP); + *(vertices++) = LLVector3(0.5f*LEAF_WIDTH, 0.f, 1.f); + *(colors++) = LLColor4U::white; + vertex_count++; + + *(normals++) = LLVector3(-SRR3, SRR3, SRR3); + *(tex_coords++) = LLVector2(LEAF_LEFT, LEAF_TOP); + *(vertices++) = LLVector3(-0.5f*LEAF_WIDTH, 0.f, 1.f); + *(colors++) = LLColor4U::white; + vertex_count++; + + *(normals++) = LLVector3(SRR2, SRR2, 0.f); + *(tex_coords++) = LLVector2(LEAF_RIGHT, LEAF_BOTTOM); + *(vertices++) = LLVector3(0.5f*LEAF_WIDTH, 0.f, 0.f); + *(colors++) = LLColor4U::white; + vertex_count++; + + *(indicesp++) = 4; + index_count++; + *(indicesp++) = 6; + index_count++; + *(indicesp++) = 5; + index_count++; + + *(indicesp++) = 4; + index_count++; + *(indicesp++) = 5; + index_count++; + *(indicesp++) = 7; + index_count++; + + + // next leaf + *(normals++) = LLVector3(SRR2, -SRR2, 0.f); + *(tex_coords++) = LLVector2(LEAF_LEFT, LEAF_BOTTOM); + *(vertices++) = LLVector3(0.f, -0.5f*LEAF_WIDTH, 0.f); + *(colors++) = LLColor4U::white; + vertex_count++; + + *(normals++) = LLVector3(SRR3, SRR3, SRR3); + *(tex_coords++) = LLVector2(LEAF_RIGHT, LEAF_TOP); + *(vertices++) = LLVector3(0.f, 0.5f*LEAF_WIDTH, 1.f); + *(colors++) = LLColor4U::white; + vertex_count++; + + *(normals++) = LLVector3(SRR3, -SRR3, SRR3); + *(tex_coords++) = LLVector2(LEAF_LEFT, LEAF_TOP); + *(vertices++) = LLVector3(0.f, -0.5f*LEAF_WIDTH, 1.f); + *(colors++) = LLColor4U::white; + vertex_count++; + + *(normals++) = LLVector3(SRR2, SRR2, 0.f); + *(tex_coords++) = LLVector2(LEAF_RIGHT, LEAF_BOTTOM); + *(vertices++) = LLVector3(0.f, 0.5f*LEAF_WIDTH, 0.f); + *(colors++) = LLColor4U::white; + vertex_count++; + + *(indicesp++) = 8; + index_count++; + *(indicesp++) = 9; + index_count++; + *(indicesp++) = 10; + index_count++; + + *(indicesp++) = 8; + index_count++; + *(indicesp++) = 11; + index_count++; + *(indicesp++) = 9; + index_count++; + + + // other side of same leaf + *(normals++) = LLVector3(-SRR2, -SRR2, 0.f); + *(tex_coords++) = LLVector2(LEAF_LEFT, LEAF_BOTTOM); + *(vertices++) = LLVector3(0.f, -0.5f*LEAF_WIDTH, 0.f); + *(colors++) = LLColor4U::white; + vertex_count++; + + *(normals++) = LLVector3(-SRR3, SRR3, SRR3); + *(tex_coords++) = LLVector2(LEAF_RIGHT, LEAF_TOP); + *(vertices++) = LLVector3(0.f, 0.5f*LEAF_WIDTH, 1.f); + *(colors++) = LLColor4U::white; + vertex_count++; + + *(normals++) = LLVector3(-SRR3, -SRR3, SRR3); + *(tex_coords++) = LLVector2(LEAF_LEFT, LEAF_TOP); + *(vertices++) = LLVector3(0.f, -0.5f*LEAF_WIDTH, 1.f); + *(colors++) = LLColor4U::white; + vertex_count++; + + *(normals++) = LLVector3(-SRR2, SRR2, 0.f); + *(tex_coords++) = LLVector2(LEAF_RIGHT, LEAF_BOTTOM); + *(vertices++) = LLVector3(0.f, 0.5f*LEAF_WIDTH, 0.f); + *(colors++) = LLColor4U::white; + vertex_count++; + + *(indicesp++) = 12; + index_count++; + *(indicesp++) = 14; + index_count++; + *(indicesp++) = 13; + index_count++; + + *(indicesp++) = 12; + index_count++; + *(indicesp++) = 13; + index_count++; + *(indicesp++) = 15; + index_count++; + + // Generate geometry for the cylinders + + // Different LOD's + + // Generate the vertices + // Generate the indices + + for (lod = 0; lod < sMAX_NUM_TREE_LOD_LEVELS; lod++) + { + slices = sLODSlices[lod]; + F32 base_radius = 0.65f; + F32 top_radius = base_radius * sSpeciesTable[mSpecies]->mTaper; + //LL_INFOS() << "Species " << ((U32) mSpecies) << ", taper = " << sSpeciesTable[mSpecies].mTaper << LL_ENDL; + //LL_INFOS() << "Droop " << mDroop << ", branchlength: " << mBranchLength << LL_ENDL; + F32 angle = 0; + F32 angle_inc = 360.f/(slices-1); + F32 z = 0.f; + F32 z_inc = 1.f; + if (slices > 3) + { + z_inc = 1.f/(slices - 3); + } + F32 radius = base_radius; + + F32 x1,y1; + F32 noise_scale = sSpeciesTable[mSpecies]->mNoiseMag; + LLVector3 nvec; + + const F32 cap_nudge = 0.1f; // Height to 'peak' the caps on top/bottom of branch + + const S32 fractal_depth = 5; + F32 nvec_scale = 1.f * sSpeciesTable[mSpecies]->mNoiseScale; + F32 nvec_scalez = 4.f * sSpeciesTable[mSpecies]->mNoiseScale; + + F32 tex_z_repeat = sSpeciesTable[mSpecies]->mRepeatTrunkZ; + + F32 start_radius; + F32 nangle = 0; + F32 height = 1.f; + F32 r0; + + for (i = 0; i < slices; i++) + { + if (i == 0) + { + z = - cap_nudge; + r0 = 0.0; + } + else if (i == (slices - 1)) + { + z = 1.f + cap_nudge;//((i - 2) * z_inc) + cap_nudge; + r0 = 0.0; + } + else + { + z = (i - 1) * z_inc; + r0 = base_radius + (top_radius - base_radius)*z; + } + + for (j = 0; j < slices; j++) + { + if (slices - 1 == j) + { + angle = 0.f; + } + else + { + angle = j*angle_inc; + } + + nangle = angle; + + x1 = cos(angle * DEG_TO_RAD); + y1 = sin(angle * DEG_TO_RAD); + LLVector2 tc; + // This isn't totally accurate. Should compute based on slope as well. + start_radius = r0 * (1.f + 1.2f*fabs(z - 0.66f*height)/height); + nvec.set( cos(nangle * DEG_TO_RAD)*start_radius*nvec_scale, + sin(nangle * DEG_TO_RAD)*start_radius*nvec_scale, + z*nvec_scalez); + // First and last slice at 0 radius (to bring in top/bottom of structure) + radius = start_radius + turbulence3((F32*)&nvec.mV, (F32)fractal_depth)*noise_scale; + + if (slices - 1 == j) + { + // Not 0.5 for slight slop factor to avoid edges on leaves + tc = LLVector2(0.490f, (1.f - z/2.f)*tex_z_repeat); + } + else + { + tc = LLVector2((angle/360.f)*0.5f, (1.f - z/2.f)*tex_z_repeat); + } + + *(vertices++) = LLVector3(x1*radius, y1*radius, z); + *(normals++) = LLVector3(x1, y1, 0.f); + *(tex_coords++) = tc; + *(colors++) = LLColor4U::white; + vertex_count++; + } + } + + for (i = 0; i < (slices - 1); i++) + { + for (j = 0; j < (slices - 1); j++) + { + S32 x1_offset = j+1; + if ((j+1) == slices) + { + x1_offset = 0; + } + // Generate the matching quads + *(indicesp) = j + (i*slices) + sLODVertexOffset[lod]; + llassert(*(indicesp) < (U32)max_vertices); + indicesp++; + index_count++; + *(indicesp) = x1_offset + ((i+1)*slices) + sLODVertexOffset[lod]; + llassert(*(indicesp) < (U32)max_vertices); + indicesp++; + index_count++; + *(indicesp) = j + ((i+1)*slices) + sLODVertexOffset[lod]; + llassert(*(indicesp) < (U32)max_vertices); + indicesp++; + index_count++; + + *(indicesp) = j + (i*slices) + sLODVertexOffset[lod]; + llassert(*(indicesp) < (U32)max_vertices); + indicesp++; + index_count++; + *(indicesp) = x1_offset + (i*slices) + sLODVertexOffset[lod]; + llassert(*(indicesp) < (U32)max_vertices); + indicesp++; + index_count++; + *(indicesp) = x1_offset + ((i+1)*slices) + sLODVertexOffset[lod]; + llassert(*(indicesp) < (U32)max_vertices); + indicesp++; + index_count++; + } + } + slices /= 2; + } + + mReferenceBuffer->unmapBuffer(); + llassert(vertex_count == max_vertices); + llassert(index_count == max_indices); +#ifndef SHOW_ASSERT + (void)vertex_count; + (void)index_count; +#endif + } + + //generate tree mesh + updateMesh(); + + return true; +} + +void LLVOTree::updateMesh() +{ + LLMatrix4 matrix; + + // Translate to tree base HACK - adjustment in Z plants tree underground + const LLVector3 &pos_region = getPositionRegion(); + //gGL.translatef(pos_agent.mV[VX], pos_agent.mV[VY], pos_agent.mV[VZ] - 0.1f); + LLMatrix4 trans_mat; + trans_mat.setTranslation(pos_region.mV[VX], pos_region.mV[VY], pos_region.mV[VZ] - 0.1f); + trans_mat *= matrix; + + // Rotate to tree position and bend for current trunk/wind + // Note that trunk stiffness controls the amount of bend at the trunk as + // opposed to the crown of the tree + // + const F32 TRUNK_STIFF = 22.f; + + LLQuaternion rot = + LLQuaternion(mTrunkBend.magVec()*TRUNK_STIFF*DEG_TO_RAD, LLVector4(mTrunkBend.mV[VX], mTrunkBend.mV[VY], 0)) * + LLQuaternion(90.f*DEG_TO_RAD, LLVector4(0,0,1)) * + getRotation(); + + LLMatrix4 rot_mat(rot); + rot_mat *= trans_mat; + + F32 radius = getScale().magVec()*0.05f; + LLMatrix4 scale_mat; + scale_mat.mMatrix[0][0] = + scale_mat.mMatrix[1][1] = + scale_mat.mMatrix[2][2] = radius; + + scale_mat *= rot_mat; + +// const F32 THRESH_ANGLE_FOR_BILLBOARD = 15.f; +// const F32 BLEND_RANGE_FOR_BILLBOARD = 3.f; + + F32 droop = mDroop + 25.f*(1.f - mTrunkBend.magVec()); + + S32 stop_depth = 0; + F32 alpha = 1.0; + + U32 vert_count = 0; + U32 index_count = 0; + + calcNumVerts(vert_count, index_count, mTrunkLOD, stop_depth, mDepth, mTrunkDepth, mBranches); + + LLFace* facep = mDrawable->getFace(0); + if (!facep) return; + LLVertexBuffer* buff = new LLVertexBuffer(LLDrawPoolTree::VERTEX_DATA_MASK); + if (!buff->allocateBuffer(vert_count, index_count)) + { + LL_WARNS() << "Failed to allocate Vertex Buffer on mesh update to " + << vert_count << " vertices and " + << index_count << " indices" << LL_ENDL; + buff->allocateBuffer(1, 3); + memset((U8*)buff->getMappedData(), 0, buff->getSize()); + memset((U8*)buff->getMappedIndices(), 0, buff->getIndicesSize()); + facep->setSize(1, 3); + facep->setVertexBuffer(buff); + mReferenceBuffer->unmapBuffer(); + buff->unmapBuffer(); + return; + } + + facep->setVertexBuffer(buff); + + LLStrider vertices; + LLStrider normals; + LLStrider tex_coords; + LLStrider colors; + LLStrider indices; + U16 idx_offset = 0; + + buff->getVertexStrider(vertices); + buff->getNormalStrider(normals); + buff->getTexCoord0Strider(tex_coords); + buff->getColorStrider(colors); + buff->getIndexStrider(indices); + + genBranchPipeline(vertices, normals, tex_coords, colors, indices, idx_offset, scale_mat, mTrunkLOD, stop_depth, mDepth, mTrunkDepth, 1.0, mTwist, droop, mBranches, alpha); + + mReferenceBuffer->unmapBuffer(); + buff->unmapBuffer(); +} + +void LLVOTree::appendMesh(LLStrider& vertices, + LLStrider& normals, + LLStrider& tex_coords, + LLStrider& colors, + LLStrider& indices, + U16& cur_idx, + LLMatrix4& matrix, + LLMatrix4& norm_mat, + S32 vert_start, + S32 vert_count, + S32 index_count, + S32 index_offset) +{ + LLStrider v; + LLStrider n; + LLStrider t; + LLStrider c; + LLStrider idx; + + mReferenceBuffer->getVertexStrider(v); + mReferenceBuffer->getNormalStrider(n); + mReferenceBuffer->getTexCoord0Strider(t); + mReferenceBuffer->getColorStrider(c); + mReferenceBuffer->getIndexStrider(idx); + + //copy/transform vertices into mesh - check + for (S32 i = 0; i < vert_count; i++) + { + U16 index = vert_start + i; + *vertices++ = v[index] * matrix; + LLVector3 norm = n[index] * norm_mat; + norm.normalize(); + *normals++ = norm; + *tex_coords++ = t[index]; + *colors++ = c[index]; + } + + //copy offset indices into mesh - check + for (S32 i = 0; i < index_count; i++) + { + U16 index = index_offset + i; + *indices++ = idx[index]-vert_start+cur_idx; + } + + //increment index offset - check + cur_idx += vert_count; +} + + +void LLVOTree::genBranchPipeline(LLStrider& vertices, + LLStrider& normals, + LLStrider& tex_coords, + LLStrider& colors, + LLStrider& indices, + U16& index_offset, + LLMatrix4& matrix, + S32 trunk_LOD, + S32 stop_level, + U16 depth, + U16 trunk_depth, + F32 scale, + F32 twist, + F32 droop, + F32 branches, + F32 alpha) +{ + // + // Generates a tree mesh by recursing, generating branches and then a 'leaf' texture. + + static F32 constant_twist; + static F32 width = 0; + + F32 length = ((trunk_depth || (scale == 1.f))? mTrunkLength:mBranchLength); + F32 aspect = ((trunk_depth || (scale == 1.f))? mTrunkAspect:mBranchAspect); + + constant_twist = 360.f/branches; + + if (stop_level >= 0) + { + if (depth > stop_level) + { + { + llassert(sLODIndexCount[trunk_LOD] > 0); + width = scale * length * aspect; + LLMatrix4 scale_mat; + scale_mat.mMatrix[0][0] = width; + scale_mat.mMatrix[1][1] = width; + scale_mat.mMatrix[2][2] = scale*length; + scale_mat *= matrix; + + glh::matrix4f norm((F32*) scale_mat.mMatrix); + LLMatrix4 norm_mat = LLMatrix4(norm.inverse().transpose().m); + + norm_mat.invert(); + appendMesh(vertices, normals, tex_coords, colors, indices, index_offset, scale_mat, norm_mat, + sLODVertexOffset[trunk_LOD], sLODVertexCount[trunk_LOD], sLODIndexCount[trunk_LOD], sLODIndexOffset[trunk_LOD]); + } + + // Recurse to create more branches + for (S32 i=0; i < (S32)branches; i++) + { + LLMatrix4 trans_mat; + trans_mat.setTranslation(0,0,scale*length); + trans_mat *= matrix; + + LLQuaternion rot = + LLQuaternion(20.f*DEG_TO_RAD, LLVector4(0.f, 0.f, 1.f)) * + LLQuaternion(droop*DEG_TO_RAD, LLVector4(0.f, 1.f, 0.f)) * + LLQuaternion(((constant_twist + ((i%2==0)?twist:-twist))*i)*DEG_TO_RAD, LLVector4(0.f, 0.f, 1.f)); + + LLMatrix4 rot_mat(rot); + rot_mat *= trans_mat; + + genBranchPipeline(vertices, normals, tex_coords, colors, indices, index_offset, rot_mat, trunk_LOD, stop_level, depth - 1, 0, scale*mScaleStep, twist, droop, branches, alpha); + } + // Recurse to continue trunk + if (trunk_depth) + { + LLMatrix4 trans_mat; + trans_mat.setTranslation(0,0,scale*length); + trans_mat *= matrix; + + LLMatrix4 rot_mat(70.5f*DEG_TO_RAD, LLVector4(0,0,1)); + rot_mat *= trans_mat; // rotate a bit around Z when ascending + genBranchPipeline(vertices, normals, tex_coords, colors, indices, index_offset, rot_mat, trunk_LOD, stop_level, depth, trunk_depth-1, scale*mScaleStep, twist, droop, branches, alpha); + } + } + else + { + // + // Append leaves as two 90 deg crossed quads with leaf textures + // + { + LLMatrix4 scale_mat; + scale_mat.mMatrix[0][0] = + scale_mat.mMatrix[1][1] = + scale_mat.mMatrix[2][2] = scale*mLeafScale; + + scale_mat *= matrix; + + glh::matrix4f norm((F32*) scale_mat.mMatrix); + LLMatrix4 norm_mat = LLMatrix4(norm.inverse().transpose().m); + + appendMesh(vertices, normals, tex_coords, colors, indices, index_offset, scale_mat, norm_mat, 0, LEAF_VERTICES, LEAF_INDICES, 0); + } + } + } +} + + + +void LLVOTree::calcNumVerts(U32& vert_count, U32& index_count, S32 trunk_LOD, S32 stop_level, U16 depth, U16 trunk_depth, F32 branches) +{ + if (stop_level >= 0) + { + if (depth > stop_level) + { + index_count += sLODIndexCount[trunk_LOD]; + vert_count += sLODVertexCount[trunk_LOD]; + + // Recurse to create more branches + for (S32 i=0; i < (S32)branches; i++) + { + calcNumVerts(vert_count, index_count, trunk_LOD, stop_level, depth - 1, 0, branches); + } + + // Recurse to continue trunk + if (trunk_depth) + { + calcNumVerts(vert_count, index_count, trunk_LOD, stop_level, depth, trunk_depth-1, branches); + } + } + else + { + index_count += LEAF_INDICES; + vert_count += LEAF_VERTICES; + } + } + else + { + index_count += LEAF_INDICES; + vert_count += LEAF_VERTICES; + } +} + +void LLVOTree::updateRadius() +{ + if (mDrawable.isNull()) + { + return; + } + + mDrawable->setRadius(32.0f); +} + +void LLVOTree::updateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax) +{ + F32 radius = getScale().length()*0.05f; + LLVector3 center = getRenderPosition(); + + F32 sz = mBillboardScale*mBillboardRatio*radius*0.5f; + LLVector3 size(sz,sz,sz); + + center += LLVector3(0, 0, size.mV[2]) * getRotation(); + + newMin.load3((center-size).mV); + newMax.load3((center+size).mV); + LLVector4a pos; + pos.load3(center.mV); + mDrawable->setPositionGroup(pos); +} + +bool LLVOTree::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, S32 face, bool pick_transparent, bool pick_rigged, bool pick_unselectable, S32 *face_hitp, + LLVector4a* intersection,LLVector2* tex_coord, LLVector4a* normal, LLVector4a* tangent) + +{ + + if (!lineSegmentBoundingBox(start, end)) + { + return false; + } + + const LLVector4a* exta = mDrawable->getSpatialExtents(); + + //VECTORIZE THIS + LLVector3 ext[2]; + ext[0].set(exta[0].getF32ptr()); + ext[1].set(exta[1].getF32ptr()); + + LLVector3 center = (ext[1]+ext[0])*0.5f; + LLVector3 size = (ext[1]-ext[0]); + + LLQuaternion quat = getRotation(); + + center -= LLVector3(0,0,size.magVec() * 0.25f)*quat; + + size.scaleVec(LLVector3(0.25f, 0.25f, 1.f)); + size.mV[0] = llmin(size.mV[0], 1.f); + size.mV[1] = llmin(size.mV[1], 1.f); + + LLVector3 pos, norm; + + LLVector3 start3(start.getF32ptr()); + LLVector3 end3(end.getF32ptr()); + + if (linesegment_tetrahedron(start3, end3, center, size, quat, pos, norm)) + { + if (intersection) + { + intersection->load3(pos.mV); + } + + if (normal) + { + normal->load3(norm.mV); + } + return true; + } + + return false; +} + +U32 LLVOTree::getPartitionType() const +{ + return LLViewerRegion::PARTITION_TREE; +} + +LLTreePartition::LLTreePartition(LLViewerRegion* regionp) +: LLSpatialPartition(0, false, regionp) +{ + mDrawableType = LLPipeline::RENDER_TYPE_TREE; + mPartitionType = LLViewerRegion::PARTITION_TREE; + mSlopRatio = 0.f; + mLODPeriod = 1; +} + diff --git a/indra/newview/llvotree.h b/indra/newview/llvotree.h index d71a0b3705..b2fc16bd17 100644 --- a/indra/newview/llvotree.h +++ b/indra/newview/llvotree.h @@ -1,196 +1,196 @@ -/** - * @file llvotree.h - * @brief LLVOTree class header file - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVOTREE_H -#define LL_LLVOTREE_H - -#include "llviewerobject.h" -#include "xform.h" - -class LLFace; -class LLDrawPool; -class LLViewerFetchedTexture; - -class LLVOTree : public LLViewerObject -{ -protected: - ~LLVOTree(); - -public: - enum - { - VERTEX_DATA_MASK = (1 << LLVertexBuffer::TYPE_VERTEX) | - (1 << LLVertexBuffer::TYPE_NORMAL) | - (1 << LLVertexBuffer::TYPE_TEXCOORD0) - }; - - LLVOTree(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); - - // Initialize data that's only inited once per class. - static void initClass(); - static void cleanupClass(); - static bool isTreeRenderingStopped(); - - /*virtual*/ U32 processUpdateMessage(LLMessageSystem *mesgsys, - void **user_data, - U32 block_num, const EObjectUpdateType update_type, - LLDataPacker *dp); - /*virtual*/ void idleUpdate(LLAgent &agent, const F64 &time); - - // Graphical stuff for objects - maybe broken out into render class later? - /*virtual*/ void render(LLAgent &agent); - /*virtual*/ void setPixelAreaAndAngle(LLAgent &agent); - /*virtual*/ void updateTextures(); - - /*virtual*/ LLDrawable* createDrawable(LLPipeline *pipeline); - /*virtual*/ bool updateGeometry(LLDrawable *drawable); - /*virtual*/ void updateSpatialExtents(LLVector4a &min, LLVector4a &max); - - virtual U32 getPartitionType() const; - - void updateRadius(); - - void calcNumVerts(U32& vert_count, U32& index_count, S32 trunk_LOD, S32 stop_level, U16 depth, U16 trunk_depth, F32 branches); - - void updateMesh(); - - void appendMesh(LLStrider& vertices, - LLStrider& normals, - LLStrider& tex_coords, - LLStrider& colors, - LLStrider& indices, - U16& idx_offset, - LLMatrix4& matrix, - LLMatrix4& norm_mat, - S32 vertex_offset, - S32 vertex_count, - S32 index_count, - S32 index_offset); - - void genBranchPipeline(LLStrider& vertices, - LLStrider& normals, - LLStrider& tex_coords, - LLStrider& colors, - LLStrider& indices, - U16& index_offset, - LLMatrix4& matrix, - S32 trunk_LOD, - S32 stop_level, - U16 depth, - U16 trunk_depth, - F32 scale, - F32 twist, - F32 droop, - F32 branches, - F32 alpha); - - /*virtual*/ bool lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, - S32 face = -1, // which face to check, -1 = ALL_SIDES - bool pick_transparent = false, - bool pick_rigged = false, - bool pick_unselectable = true, - S32* face_hit = NULL, // which face was hit - LLVector4a* intersection = NULL, // return the intersection point - LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point - LLVector4a* normal = NULL, // return the surface normal at the intersection point - LLVector4a* tangent = NULL // return the surface tangent at the intersection point - ); - - static S32 sMaxTreeSpecies; - - struct TreeSpeciesData - { - LLUUID mTextureID; - - F32 mBranchLength; // Scale (length) of tree branches - F32 mDroop; // Droop from vertical (degrees) at each branch recursion - F32 mTwist; // Twist - F32 mBranches; // Number of branches emitted at each recursion level - U8 mDepth; // Number of recursions to tips of branches - F32 mScaleStep; // Multiplier for scale at each recursion level - U8 mTrunkDepth; - - F32 mLeafScale; // Scales leaf texture when rendering - F32 mTrunkLength; // Scales branch diameters when rendering - F32 mBillboardScale; // Scales the billboard representation - F32 mBillboardRatio; // Height to width aspect ratio - F32 mTrunkAspect; - F32 mBranchAspect; - F32 mRandomLeafRotate; - F32 mNoiseScale; // Scaling of noise function in perlin space (norm = 1.0) - F32 mNoiseMag; // amount of perlin noise to deform by (0 = none) - F32 mTaper; // amount of perlin noise to deform by (0 = none) - F32 mRepeatTrunkZ; // Times to repeat the trunk texture vertically along trunk - }; - - static F32 sTreeFactor; // Tree level of detail factor - static const S32 sMAX_NUM_TREE_LOD_LEVELS ; - - friend class LLDrawPoolTree; -protected: - LLVector3 mTrunkBend; // Accumulated wind (used for blowing trees) - LLVector3 mWind; - - LLPointer mReferenceBuffer; //reference geometry for generating tree mesh - LLPointer mTreeImagep; // Pointer to proper tree image - - U8 mSpecies; // Species of tree - F32 mBranchLength; // Scale (length) of tree branches - F32 mTrunkLength; // Trunk length (first recursion) - F32 mDroop; // Droop from vertical (degrees) at each branch recursion - F32 mTwist; // Twist - F32 mBranches; // Number of branches emitted at each recursion level - U8 mDepth; // Number of recursions to tips of branches - F32 mScaleStep; // Multiplier for scale at each recursion level - U8 mTrunkDepth; - U32 mTrunkLOD; - F32 mLeafScale; // Scales leaf texture when rendering - - F32 mBillboardScale; // How big to draw the billboard? - F32 mBillboardRatio; // Height to width ratio of billboard - F32 mTrunkAspect; // Ratio between width/length of trunk - F32 mBranchAspect; // Ratio between width/length of branch - F32 mRandomLeafRotate; // How much to randomly rotate leaves about arbitrary axis - - // cache last position+rotation so we can detect the need for a - // complete rebuild when not animating - LLVector3 mLastPosition; - LLQuaternion mLastRotation; - - U32 mFrameCount; - - typedef std::map SpeciesMap; - static SpeciesMap sSpeciesTable; - - static S32 sLODIndexOffset[4]; - static S32 sLODIndexCount[4]; - static S32 sLODVertexOffset[4]; - static S32 sLODVertexCount[4]; - static S32 sLODSlices[4]; - static F32 sLODAngles[4]; -}; - -#endif +/** + * @file llvotree.h + * @brief LLVOTree class header file + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVOTREE_H +#define LL_LLVOTREE_H + +#include "llviewerobject.h" +#include "xform.h" + +class LLFace; +class LLDrawPool; +class LLViewerFetchedTexture; + +class LLVOTree : public LLViewerObject +{ +protected: + ~LLVOTree(); + +public: + enum + { + VERTEX_DATA_MASK = (1 << LLVertexBuffer::TYPE_VERTEX) | + (1 << LLVertexBuffer::TYPE_NORMAL) | + (1 << LLVertexBuffer::TYPE_TEXCOORD0) + }; + + LLVOTree(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); + + // Initialize data that's only inited once per class. + static void initClass(); + static void cleanupClass(); + static bool isTreeRenderingStopped(); + + /*virtual*/ U32 processUpdateMessage(LLMessageSystem *mesgsys, + void **user_data, + U32 block_num, const EObjectUpdateType update_type, + LLDataPacker *dp); + /*virtual*/ void idleUpdate(LLAgent &agent, const F64 &time); + + // Graphical stuff for objects - maybe broken out into render class later? + /*virtual*/ void render(LLAgent &agent); + /*virtual*/ void setPixelAreaAndAngle(LLAgent &agent); + /*virtual*/ void updateTextures(); + + /*virtual*/ LLDrawable* createDrawable(LLPipeline *pipeline); + /*virtual*/ bool updateGeometry(LLDrawable *drawable); + /*virtual*/ void updateSpatialExtents(LLVector4a &min, LLVector4a &max); + + virtual U32 getPartitionType() const; + + void updateRadius(); + + void calcNumVerts(U32& vert_count, U32& index_count, S32 trunk_LOD, S32 stop_level, U16 depth, U16 trunk_depth, F32 branches); + + void updateMesh(); + + void appendMesh(LLStrider& vertices, + LLStrider& normals, + LLStrider& tex_coords, + LLStrider& colors, + LLStrider& indices, + U16& idx_offset, + LLMatrix4& matrix, + LLMatrix4& norm_mat, + S32 vertex_offset, + S32 vertex_count, + S32 index_count, + S32 index_offset); + + void genBranchPipeline(LLStrider& vertices, + LLStrider& normals, + LLStrider& tex_coords, + LLStrider& colors, + LLStrider& indices, + U16& index_offset, + LLMatrix4& matrix, + S32 trunk_LOD, + S32 stop_level, + U16 depth, + U16 trunk_depth, + F32 scale, + F32 twist, + F32 droop, + F32 branches, + F32 alpha); + + /*virtual*/ bool lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, + S32 face = -1, // which face to check, -1 = ALL_SIDES + bool pick_transparent = false, + bool pick_rigged = false, + bool pick_unselectable = true, + S32* face_hit = NULL, // which face was hit + LLVector4a* intersection = NULL, // return the intersection point + LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point + LLVector4a* normal = NULL, // return the surface normal at the intersection point + LLVector4a* tangent = NULL // return the surface tangent at the intersection point + ); + + static S32 sMaxTreeSpecies; + + struct TreeSpeciesData + { + LLUUID mTextureID; + + F32 mBranchLength; // Scale (length) of tree branches + F32 mDroop; // Droop from vertical (degrees) at each branch recursion + F32 mTwist; // Twist + F32 mBranches; // Number of branches emitted at each recursion level + U8 mDepth; // Number of recursions to tips of branches + F32 mScaleStep; // Multiplier for scale at each recursion level + U8 mTrunkDepth; + + F32 mLeafScale; // Scales leaf texture when rendering + F32 mTrunkLength; // Scales branch diameters when rendering + F32 mBillboardScale; // Scales the billboard representation + F32 mBillboardRatio; // Height to width aspect ratio + F32 mTrunkAspect; + F32 mBranchAspect; + F32 mRandomLeafRotate; + F32 mNoiseScale; // Scaling of noise function in perlin space (norm = 1.0) + F32 mNoiseMag; // amount of perlin noise to deform by (0 = none) + F32 mTaper; // amount of perlin noise to deform by (0 = none) + F32 mRepeatTrunkZ; // Times to repeat the trunk texture vertically along trunk + }; + + static F32 sTreeFactor; // Tree level of detail factor + static const S32 sMAX_NUM_TREE_LOD_LEVELS ; + + friend class LLDrawPoolTree; +protected: + LLVector3 mTrunkBend; // Accumulated wind (used for blowing trees) + LLVector3 mWind; + + LLPointer mReferenceBuffer; //reference geometry for generating tree mesh + LLPointer mTreeImagep; // Pointer to proper tree image + + U8 mSpecies; // Species of tree + F32 mBranchLength; // Scale (length) of tree branches + F32 mTrunkLength; // Trunk length (first recursion) + F32 mDroop; // Droop from vertical (degrees) at each branch recursion + F32 mTwist; // Twist + F32 mBranches; // Number of branches emitted at each recursion level + U8 mDepth; // Number of recursions to tips of branches + F32 mScaleStep; // Multiplier for scale at each recursion level + U8 mTrunkDepth; + U32 mTrunkLOD; + F32 mLeafScale; // Scales leaf texture when rendering + + F32 mBillboardScale; // How big to draw the billboard? + F32 mBillboardRatio; // Height to width ratio of billboard + F32 mTrunkAspect; // Ratio between width/length of trunk + F32 mBranchAspect; // Ratio between width/length of branch + F32 mRandomLeafRotate; // How much to randomly rotate leaves about arbitrary axis + + // cache last position+rotation so we can detect the need for a + // complete rebuild when not animating + LLVector3 mLastPosition; + LLQuaternion mLastRotation; + + U32 mFrameCount; + + typedef std::map SpeciesMap; + static SpeciesMap sSpeciesTable; + + static S32 sLODIndexOffset[4]; + static S32 sLODIndexCount[4]; + static S32 sLODVertexOffset[4]; + static S32 sLODVertexCount[4]; + static S32 sLODSlices[4]; + static F32 sLODAngles[4]; +}; + +#endif diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index 387ebf0c67..86aaa244ec 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -1,6853 +1,6853 @@ -/** - * @file llvovolume.cpp - * @brief LLVOVolume class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -// A "volume" is a box, cylinder, sphere, or other primitive shape. - -#include "llviewerprecompiledheaders.h" - -#include "llvovolume.h" - -#include - -#include "llviewercontrol.h" -#include "lldir.h" -#include "llflexibleobject.h" -#include "llfloatertools.h" -#include "llmaterialid.h" -#include "llmaterialtable.h" -#include "llprimitive.h" -#include "llvolume.h" -#include "llvolumeoctree.h" -#include "llvolumemgr.h" -#include "llvolumemessage.h" -#include "material_codes.h" -#include "message.h" -#include "llpluginclassmedia.h" // for code in the mediaEvent handler -#include "object_flags.h" -#include "lldrawable.h" -#include "lldrawpoolavatar.h" -#include "lldrawpoolbump.h" -#include "llface.h" -#include "llspatialpartition.h" -#include "llhudmanager.h" -#include "llflexibleobject.h" -#include "llskinningutil.h" -#include "llsky.h" -#include "lltexturefetch.h" -#include "llvector4a.h" -#include "llviewercamera.h" -#include "llviewertexturelist.h" -#include "llviewerobjectlist.h" -#include "llviewerregion.h" -#include "llviewertextureanim.h" -#include "llworld.h" -#include "llselectmgr.h" -#include "pipeline.h" -#include "llsdutil.h" -#include "llmatrix4a.h" -#include "llmediaentry.h" -#include "llmediadataclient.h" -#include "llmeshrepository.h" -#include "llnotifications.h" -#include "llnotificationsutil.h" -#include "llagent.h" -#include "llviewermediafocus.h" -#include "lldatapacker.h" -#include "llviewershadermgr.h" -#include "llvoavatar.h" -#include "llcontrolavatar.h" -#include "llvoavatarself.h" -#include "llvocache.h" -#include "llmaterialmgr.h" -#include "llanimationstates.h" -#include "llinventorytype.h" -#include "llviewerinventory.h" -#include "llcallstack.h" -#include "llsculptidsize.h" -#include "llavatarappearancedefines.h" -#include "llgltfmateriallist.h" - -const F32 FORCE_SIMPLE_RENDER_AREA = 512.f; -const F32 FORCE_CULL_AREA = 8.f; -U32 JOINT_COUNT_REQUIRED_FOR_FULLRIG = 1; - -bool gAnimateTextures = true; - -F32 LLVOVolume::sLODFactor = 1.f; -F32 LLVOVolume::sLODSlopDistanceFactor = 0.5f; //Changing this to zero, effectively disables the LOD transition slop -F32 LLVOVolume::sDistanceFactor = 1.0f; -S32 LLVOVolume::sNumLODChanges = 0; -S32 LLVOVolume::mRenderComplexity_last = 0; -S32 LLVOVolume::mRenderComplexity_current = 0; -LLPointer LLVOVolume::sObjectMediaClient = NULL; -LLPointer LLVOVolume::sObjectMediaNavigateClient = NULL; - -extern bool gCubeSnapshot; - -// Implementation class of LLMediaDataClientObject. See llmediadataclient.h -class LLMediaDataClientObjectImpl : public LLMediaDataClientObject -{ -public: - LLMediaDataClientObjectImpl(LLVOVolume *obj, bool isNew) : mObject(obj), mNew(isNew) - { - mObject->addMDCImpl(); - } - ~LLMediaDataClientObjectImpl() - { - mObject->removeMDCImpl(); - } - - virtual U8 getMediaDataCount() const - { return mObject->getNumTEs(); } - - virtual LLSD getMediaDataLLSD(U8 index) const - { - LLSD result; - LLTextureEntry *te = mObject->getTE(index); - if (NULL != te) - { - llassert((te->getMediaData() != NULL) == te->hasMedia()); - if (te->getMediaData() != NULL) - { - result = te->getMediaData()->asLLSD(); - // XXX HACK: workaround bug in asLLSD() where whitelist is not set properly - // See DEV-41949 - if (!result.has(LLMediaEntry::WHITELIST_KEY)) - { - result[LLMediaEntry::WHITELIST_KEY] = LLSD::emptyArray(); - } - } - } - return result; - } - virtual bool isCurrentMediaUrl(U8 index, const std::string &url) const - { - LLTextureEntry *te = mObject->getTE(index); - if (te) - { - if (te->getMediaData()) - { - return (te->getMediaData()->getCurrentURL() == url); - } - } - return url.empty(); - } - - virtual LLUUID getID() const - { return mObject->getID(); } - - virtual void mediaNavigateBounceBack(U8 index) - { mObject->mediaNavigateBounceBack(index); } - - virtual bool hasMedia() const - { return mObject->hasMedia(); } - - virtual void updateObjectMediaData(LLSD const &data, const std::string &version_string) - { mObject->updateObjectMediaData(data, version_string); } - - virtual F64 getMediaInterest() const - { - F64 interest = mObject->getTotalMediaInterest(); - if (interest < (F64)0.0) - { - // media interest not valid yet, try pixel area - interest = mObject->getPixelArea(); - // HACK: force recalculation of pixel area if interest is the "magic default" of 1024. - if (interest == 1024.f) - { - const_cast(static_cast(mObject))->setPixelAreaAndAngle(gAgent); - interest = mObject->getPixelArea(); - } - } - return interest; - } - - virtual bool isInterestingEnough() const - { - return LLViewerMedia::getInstance()->isInterestingEnough(mObject, getMediaInterest()); - } - - virtual std::string getCapabilityUrl(const std::string &name) const - { return mObject->getRegion()->getCapability(name); } - - virtual bool isDead() const - { return mObject->isDead(); } - - virtual U32 getMediaVersion() const - { return LLTextureEntry::getVersionFromMediaVersionString(mObject->getMediaURL()); } - - virtual bool isNew() const - { return mNew; } - -private: - LLPointer mObject; - bool mNew; -}; - - -LLVOVolume::LLVOVolume(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) - : LLViewerObject(id, pcode, regionp), - mVolumeImpl(NULL) -{ - mTexAnimMode = 0; - mRelativeXform.setIdentity(); - mRelativeXformInvTrans.setIdentity(); - - mFaceMappingChanged = false; - mLOD = MIN_LOD; - mLODDistance = 0.0f; - mLODAdjustedDistance = 0.0f; - mLODRadius = 0.0f; - mTextureAnimp = NULL; - mVolumeChanged = false; - mVObjRadius = LLVector3(1,1,0.5f).length(); - mNumFaces = 0; - mLODChanged = false; - mSculptChanged = false; - mColorChanged = false; - mSpotLightPriority = 0.f; - - mSkinInfoUnavaliable = false; - mSkinInfo = NULL; - - mMediaImplList.resize(getNumTEs()); - mLastFetchedMediaVersion = -1; - mServerDrawableUpdateCount = 0; - memset(&mIndexInTex, 0, sizeof(S32) * LLRender::NUM_VOLUME_TEXTURE_CHANNELS); - mMDCImplCount = 0; - mLastRiggingInfoLOD = -1; - mResetDebugText = false; -} - -LLVOVolume::~LLVOVolume() -{ - LL_PROFILE_ZONE_SCOPED; - delete mTextureAnimp; - mTextureAnimp = NULL; - delete mVolumeImpl; - mVolumeImpl = NULL; - - gMeshRepo.unregisterMesh(this); - - if(!mMediaImplList.empty()) - { - for(U32 i = 0 ; i < mMediaImplList.size() ; i++) - { - if(mMediaImplList[i].notNull()) - { - mMediaImplList[i]->removeObject(this) ; - } - } - } -} - -void LLVOVolume::markDead() -{ - if (!mDead) - { - LL_PROFILE_ZONE_SCOPED; - if (getVolume()) - { - LLSculptIDSize::instance().rem(getVolume()->getParams().getSculptID()); - } - - if(getMDCImplCount() > 0) - { - LLMediaDataClientObject::ptr_t obj = new LLMediaDataClientObjectImpl(const_cast(this), false); - if (sObjectMediaClient) sObjectMediaClient->removeFromQueue(obj); - if (sObjectMediaNavigateClient) sObjectMediaNavigateClient->removeFromQueue(obj); - } - - // Detach all media impls from this object - for(U32 i = 0 ; i < mMediaImplList.size() ; i++) - { - removeMediaImpl(i); - } - - if (mSculptTexture.notNull()) - { - mSculptTexture->removeVolume(LLRender::SCULPT_TEX, this); - } - - if (mLightTexture.notNull()) - { - mLightTexture->removeVolume(LLRender::LIGHT_TEX, this); - } - } - - LLViewerObject::markDead(); -} - - -// static -void LLVOVolume::initClass() -{ - // gSavedSettings better be around - if (gSavedSettings.getBOOL("PrimMediaMasterEnabled")) - { - const F32 queue_timer_delay = gSavedSettings.getF32("PrimMediaRequestQueueDelay"); - const F32 retry_timer_delay = gSavedSettings.getF32("PrimMediaRetryTimerDelay"); - const U32 max_retries = gSavedSettings.getU32("PrimMediaMaxRetries"); - const U32 max_sorted_queue_size = gSavedSettings.getU32("PrimMediaMaxSortedQueueSize"); - const U32 max_round_robin_queue_size = gSavedSettings.getU32("PrimMediaMaxRoundRobinQueueSize"); - sObjectMediaClient = new LLObjectMediaDataClient(queue_timer_delay, retry_timer_delay, max_retries, - max_sorted_queue_size, max_round_robin_queue_size); - sObjectMediaNavigateClient = new LLObjectMediaNavigateClient(queue_timer_delay, retry_timer_delay, - max_retries, max_sorted_queue_size, max_round_robin_queue_size); - } -} - -// static -void LLVOVolume::cleanupClass() -{ - sObjectMediaClient = NULL; - sObjectMediaNavigateClient = NULL; -} - -U32 LLVOVolume::processUpdateMessage(LLMessageSystem *mesgsys, - void **user_data, - U32 block_num, EObjectUpdateType update_type, - LLDataPacker *dp) -{ - - LLColor4U color; - const S32 teDirtyBits = (TEM_CHANGE_TEXTURE|TEM_CHANGE_COLOR|TEM_CHANGE_MEDIA); - const bool previously_volume_changed = mVolumeChanged; - const bool previously_face_mapping_changed = mFaceMappingChanged; - const bool previously_color_changed = mColorChanged; - - // Do base class updates... - U32 retval = LLViewerObject::processUpdateMessage(mesgsys, user_data, block_num, update_type, dp); - - LLUUID sculpt_id; - U8 sculpt_type = 0; - if (isSculpted()) - { - LLSculptParams *sculpt_params = (LLSculptParams *)getParameterEntry(LLNetworkData::PARAMS_SCULPT); - sculpt_id = sculpt_params->getSculptTexture(); - sculpt_type = sculpt_params->getSculptType(); - - LL_DEBUGS("ObjectUpdate") << "uuid " << mID << " set sculpt_id " << sculpt_id << LL_ENDL; - dumpStack("ObjectUpdateStack"); - } - - if (!dp) - { - if (update_type == OUT_FULL) - { - //////////////////////////////// - // - // Unpack texture animation data - // - // - - if (mesgsys->getSizeFast(_PREHASH_ObjectData, block_num, _PREHASH_TextureAnim)) - { - if (!mTextureAnimp) - { - mTextureAnimp = new LLViewerTextureAnim(this); - } - else - { - if (!(mTextureAnimp->mMode & LLTextureAnim::SMOOTH)) - { - mTextureAnimp->reset(); - } - } - mTexAnimMode = 0; - - mTextureAnimp->unpackTAMessage(mesgsys, block_num); - } - else - { - if (mTextureAnimp) - { - delete mTextureAnimp; - mTextureAnimp = NULL; - - for (S32 i = 0; i < getNumTEs(); i++) - { - LLFace* facep = mDrawable->getFace(i); - if (facep && facep->mTextureMatrix) - { - // delete or reset - delete facep->mTextureMatrix; - facep->mTextureMatrix = NULL; - } - } - - gPipeline.markTextured(mDrawable); - mFaceMappingChanged = true; - mTexAnimMode = 0; - } - } - - // Unpack volume data - LLVolumeParams volume_params; - LLVolumeMessage::unpackVolumeParams(&volume_params, mesgsys, _PREHASH_ObjectData, block_num); - volume_params.setSculptID(sculpt_id, sculpt_type); - - if (setVolume(volume_params, 0)) - { - markForUpdate(); - } - } - - // Sigh, this needs to be done AFTER the volume is set as well, otherwise bad stuff happens... - //////////////////////////// - // - // Unpack texture entry data - // - - S32 result = unpackTEMessage(mesgsys, _PREHASH_ObjectData, (S32) block_num); - - if (result & TEM_CHANGE_MEDIA) - { - retval |= MEDIA_FLAGS_CHANGED; - } - } - else - { - if (update_type != OUT_TERSE_IMPROVED) - { - LLVolumeParams volume_params; - bool res = LLVolumeMessage::unpackVolumeParams(&volume_params, *dp); - if (!res) - { - LL_WARNS() << "Bogus volume parameters in object " << getID() << LL_ENDL; - LL_WARNS() << getRegion()->getOriginGlobal() << LL_ENDL; - } - - volume_params.setSculptID(sculpt_id, sculpt_type); - - if (setVolume(volume_params, 0)) - { - markForUpdate(); - } - S32 res2 = unpackTEMessage(*dp); - if (TEM_INVALID == res2) - { - // There's something bogus in the data that we're unpacking. - dp->dumpBufferToLog(); - LL_WARNS() << "Flushing cache files" << LL_ENDL; - - if(LLVOCache::instanceExists() && getRegion()) - { - LLVOCache::getInstance()->removeEntry(getRegion()->getHandle()) ; - } - - LL_WARNS() << "Bogus TE data in " << getID() << LL_ENDL; - } - else - { - if (res2 & TEM_CHANGE_MEDIA) - { - retval |= MEDIA_FLAGS_CHANGED; - } - } - - U32 value = dp->getPassFlags(); - - if (value & 0x40) - { - if (!mTextureAnimp) - { - mTextureAnimp = new LLViewerTextureAnim(this); - } - else - { - if (!(mTextureAnimp->mMode & LLTextureAnim::SMOOTH)) - { - mTextureAnimp->reset(); - } - } - mTexAnimMode = 0; - mTextureAnimp->unpackTAMessage(*dp); - } - else if (mTextureAnimp) - { - delete mTextureAnimp; - mTextureAnimp = NULL; - - for (S32 i = 0; i < getNumTEs(); i++) - { - LLFace* facep = mDrawable->getFace(i); - if (facep && facep->mTextureMatrix) - { - // delete or reset - delete facep->mTextureMatrix; - facep->mTextureMatrix = NULL; - } - } - - gPipeline.markTextured(mDrawable); - mFaceMappingChanged = true; - mTexAnimMode = 0; - } - - if (value & 0x400) - { //particle system (new) - unpackParticleSource(*dp, mOwnerID, false); - } - } - else - { - S32 texture_length = mesgsys->getSizeFast(_PREHASH_ObjectData, block_num, _PREHASH_TextureEntry); - if (texture_length) - { - U8 tdpbuffer[1024]; - LLDataPackerBinaryBuffer tdp(tdpbuffer, 1024); - mesgsys->getBinaryDataFast(_PREHASH_ObjectData, _PREHASH_TextureEntry, tdpbuffer, 0, block_num, 1024); - S32 result = unpackTEMessage(tdp); - if (result & teDirtyBits) - { - if (mDrawable) - { //on the fly TE updates break batches, isolate in octree - shrinkWrap(); - } - } - if (result & TEM_CHANGE_MEDIA) - { - retval |= MEDIA_FLAGS_CHANGED; - } - } - } - } - if (retval & (MEDIA_URL_REMOVED | MEDIA_URL_ADDED | MEDIA_URL_UPDATED | MEDIA_FLAGS_CHANGED)) - { - // If only the media URL changed, and it isn't a media version URL, - // ignore it - if ( ! ( retval & (MEDIA_URL_ADDED | MEDIA_URL_UPDATED) && - mMedia && ! mMedia->mMediaURL.empty() && - ! LLTextureEntry::isMediaVersionString(mMedia->mMediaURL) ) ) - { - // If the media changed at all, request new media data - LL_DEBUGS("MediaOnAPrim") << "Media update: " << getID() << ": retval=" << retval << " Media URL: " << - ((mMedia) ? mMedia->mMediaURL : std::string("")) << LL_ENDL; - requestMediaDataUpdate(retval & MEDIA_FLAGS_CHANGED); - } - else { - LL_INFOS("MediaOnAPrim") << "Ignoring media update for: " << getID() << " Media URL: " << - ((mMedia) ? mMedia->mMediaURL : std::string("")) << LL_ENDL; - } - } - // ...and clean up any media impls - cleanUpMediaImpls(); - - if (( - (mVolumeChanged && !previously_volume_changed) || - (mFaceMappingChanged && !previously_face_mapping_changed) || - (mColorChanged && !previously_color_changed) - ) - && !mLODChanged) { - onDrawableUpdateFromServer(); - } - - return retval; -} - -// Called when a volume, material, etc is updated by the server, possibly by a -// script. If this occurs too often for this object, mark it as active so that -// it doesn't disrupt the octree/render batches, thereby potentially causing a -// big performance penalty. -void LLVOVolume::onDrawableUpdateFromServer() -{ - constexpr U32 UPDATES_UNTIL_ACTIVE = 8; - ++mServerDrawableUpdateCount; - if (mDrawable && !mDrawable->isActive() && mServerDrawableUpdateCount > UPDATES_UNTIL_ACTIVE) - { - mDrawable->makeActive(); - } -} - -void LLVOVolume::animateTextures() -{ - if (!mDead) - { - shrinkWrap(); - F32 off_s = 0.f, off_t = 0.f, scale_s = 1.f, scale_t = 1.f, rot = 0.f; - S32 result = mTextureAnimp->animateTextures(off_s, off_t, scale_s, scale_t, rot); - - if (result) - { - if (!mTexAnimMode) - { - mFaceMappingChanged = true; - gPipeline.markTextured(mDrawable); - } - mTexAnimMode = result | mTextureAnimp->mMode; - - S32 start=0, end=mDrawable->getNumFaces()-1; - if (mTextureAnimp->mFace >= 0 && mTextureAnimp->mFace <= end) - { - start = end = mTextureAnimp->mFace; - } - - for (S32 i = start; i <= end; i++) - { - LLFace* facep = mDrawable->getFace(i); - if (!facep) continue; - if(facep->getVirtualSize() <= MIN_TEX_ANIM_SIZE && facep->mTextureMatrix) continue; - - const LLTextureEntry* te = facep->getTextureEntry(); - - if (!te) - { - continue; - } - - if (!(result & LLViewerTextureAnim::ROTATE)) - { - te->getRotation(&rot); - } - if (!(result & LLViewerTextureAnim::TRANSLATE)) - { - te->getOffset(&off_s,&off_t); - } - if (!(result & LLViewerTextureAnim::SCALE)) - { - te->getScale(&scale_s, &scale_t); - } - - if (!facep->mTextureMatrix) - { - facep->mTextureMatrix = new LLMatrix4(); - } - - LLMatrix4& tex_mat = *facep->mTextureMatrix; - tex_mat.setIdentity(); - LLVector3 trans ; - - trans.set(LLVector3(off_s+0.5f, off_t+0.5f, 0.f)); - tex_mat.translate(LLVector3(-0.5f, -0.5f, 0.f)); - - LLVector3 scale(scale_s, scale_t, 1.f); - LLQuaternion quat; - quat.setQuat(rot, 0, 0, -1.f); - - tex_mat.rotate(quat); - - LLMatrix4 mat; - mat.initAll(scale, LLQuaternion(), LLVector3()); - tex_mat *= mat; - - tex_mat.translate(trans); - } - } - else - { - if (mTexAnimMode && mTextureAnimp->mRate == 0) - { - U8 start, count; - - if (mTextureAnimp->mFace == -1) - { - start = 0; - count = getNumTEs(); - } - else - { - start = (U8) mTextureAnimp->mFace; - count = 1; - } - - for (S32 i = start; i < start + count; i++) - { - if (mTexAnimMode & LLViewerTextureAnim::TRANSLATE) - { - setTEOffset(i, mTextureAnimp->mOffS, mTextureAnimp->mOffT); - } - if (mTexAnimMode & LLViewerTextureAnim::SCALE) - { - setTEScale(i, mTextureAnimp->mScaleS, mTextureAnimp->mScaleT); - } - if (mTexAnimMode & LLViewerTextureAnim::ROTATE) - { - setTERotation(i, mTextureAnimp->mRot); - } - } - - gPipeline.markTextured(mDrawable); - mFaceMappingChanged = true; - mTexAnimMode = 0; - } - } - } -} - -void LLVOVolume::updateTextures() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; - updateTextureVirtualSize(); -} - -bool LLVOVolume::isVisible() const -{ - if(mDrawable.notNull() && mDrawable->isVisible()) - { - return true ; - } - - if(isAttachment()) - { - LLViewerObject* objp = (LLViewerObject*)getParent() ; - while(objp && !objp->isAvatar()) - { - objp = (LLViewerObject*)objp->getParent() ; - } - - return objp && objp->mDrawable.notNull() && objp->mDrawable->isVisible() ; - } - - return false ; -} - -void LLVOVolume::updateTextureVirtualSize(bool forced) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - // Update the pixel area of all faces - - if (mDrawable.isNull() || gCubeSnapshot) - { - return; - } - - if(!forced) - { - if(!isVisible()) - { //don't load textures for non-visible faces - const S32 num_faces = mDrawable->getNumFaces(); - for (S32 i = 0; i < num_faces; i++) - { - LLFace* face = mDrawable->getFace(i); - if (face) - { - face->setPixelArea(0.f); - face->setVirtualSize(0.f); - } - } - - return ; - } - - if (!gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_SIMPLE)) - { - return; - } - } - - static LLCachedControl dont_load_textures(gSavedSettings,"TextureDisable", false); - - if (dont_load_textures || LLAppViewer::getTextureFetch()->mDebugPause) // || !mDrawable->isVisible()) - { - return; - } - - mTextureUpdateTimer.reset(); - - F32 old_area = mPixelArea; - mPixelArea = 0.f; - - const S32 num_faces = mDrawable->getNumFaces(); - F32 min_vsize=999999999.f, max_vsize=0.f; - LLViewerCamera* camera = LLViewerCamera::getInstance(); - std::stringstream debug_text; - for (S32 i = 0; i < num_faces; i++) - { - LLFace* face = mDrawable->getFace(i); - if (!face) continue; - const LLTextureEntry *te = face->getTextureEntry(); - LLViewerTexture *imagep = face->getTexture(); - if (!imagep || !te || - face->mExtents[0].equals3(face->mExtents[1])) - { - continue; - } - - F32 vsize; - F32 old_size = face->getVirtualSize(); - - if (isHUDAttachment()) - { - F32 area = (F32) camera->getScreenPixelArea(); - vsize = area; - imagep->setBoostLevel(LLGLTexture::BOOST_HUD); - face->setPixelArea(area); // treat as full screen - face->setVirtualSize(vsize); - } - else - { - vsize = face->getTextureVirtualSize(); - } - - mPixelArea = llmax(mPixelArea, face->getPixelArea()); - - // if the face has gotten small enough to turn off texture animation and texture - // animation is running, rebuild the render batch for this face to turn off - // texture animation - if (face->mTextureMatrix != NULL) - { - if ((vsize < MIN_TEX_ANIM_SIZE && old_size > MIN_TEX_ANIM_SIZE) || - (vsize > MIN_TEX_ANIM_SIZE && old_size < MIN_TEX_ANIM_SIZE)) - { - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_TCOORD); - } - } - - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_PRIORITY)) - { - LLViewerFetchedTexture* img = LLViewerTextureManager::staticCastToFetchedTexture(imagep) ; - if(img) - { - debug_text << img->getDiscardLevel() << ":" << img->getDesiredDiscardLevel() << ":" << img->getWidth() << ":" << (S32) sqrtf(vsize) << ":" << (S32) sqrtf(img->getMaxVirtualSize()) << "\n"; - /*F32 pri = img->getDecodePriority(); - pri = llmax(pri, 0.0f); - if (pri < min_vsize) min_vsize = pri; - if (pri > max_vsize) max_vsize = pri;*/ - } - } - else if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_FACE_AREA)) - { - F32 pri = mPixelArea; - if (pri < min_vsize) min_vsize = pri; - if (pri > max_vsize) max_vsize = pri; - } - } - - if (isSculpted()) - { - updateSculptTexture(); - - - - if (mSculptTexture.notNull()) - { - mSculptTexture->setBoostLevel(llmax((S32)mSculptTexture->getBoostLevel(), - (S32)LLGLTexture::BOOST_SCULPTED)); - mSculptTexture->setForSculpt() ; - - if(!mSculptTexture->isCachedRawImageReady()) - { - S32 lod = llmin(mLOD, 3); - F32 lodf = ((F32)(lod + 1.0f)/4.f); - F32 tex_size = lodf * LLViewerTexture::sMaxSculptRez ; - mSculptTexture->addTextureStats(2.f * tex_size * tex_size, false); - } - - S32 texture_discard = mSculptTexture->getCachedRawImageLevel(); //try to match the texture - S32 current_discard = getVolume() ? getVolume()->getSculptLevel() : -2 ; - - if (texture_discard >= 0 && //texture has some data available - (texture_discard < current_discard || //texture has more data than last rebuild - current_discard < 0)) //no previous rebuild - { - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); - mSculptChanged = true; - } - - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_SCULPTED)) - { - setDebugText(llformat("T%d C%d V%d\n%dx%d", - texture_discard, current_discard, getVolume()->getSculptLevel(), - mSculptTexture->getHeight(), mSculptTexture->getWidth())); - } - } - - } - - if (getLightTextureID().notNull()) - { - LLLightImageParams* params = (LLLightImageParams*) getParameterEntry(LLNetworkData::PARAMS_LIGHT_IMAGE); - LLUUID id = params->getLightTexture(); - mLightTexture = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE); - if (mLightTexture.notNull()) - { - F32 rad = getLightRadius(); - mLightTexture->addTextureStats(gPipeline.calcPixelArea(getPositionAgent(), - LLVector3(rad,rad,rad), - *camera)); - } - } - - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_AREA)) - { - setDebugText(llformat("%.0f:%.0f", (F32) sqrt(min_vsize),(F32) sqrt(max_vsize))); - } - else if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_PRIORITY)) - { - //setDebugText(llformat("%.0f:%.0f", (F32) sqrt(min_vsize),(F32) sqrt(max_vsize))); - setDebugText(debug_text.str()); - } - else if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_FACE_AREA)) - { - setDebugText(llformat("%.0f:%.0f", (F32) sqrt(min_vsize),(F32) sqrt(max_vsize))); - } - - if (mPixelArea == 0) - { //flexi phasing issues make this happen - mPixelArea = old_area; - } -} - -bool LLVOVolume::isActive() const -{ - return !mStatic; -} - -bool LLVOVolume::setMaterial(const U8 material) -{ - bool res = LLViewerObject::setMaterial(material); - - return res; -} - -void LLVOVolume::setTexture(const S32 face) -{ - llassert(face < getNumTEs()); - gGL.getTexUnit(0)->bind(getTEImage(face)); -} - -void LLVOVolume::setScale(const LLVector3 &scale, bool damped) -{ - if (scale != getScale()) - { - // store local radius - LLViewerObject::setScale(scale); - - if (mVolumeImpl) - { - mVolumeImpl->onSetScale(scale, damped); - } - - updateRadius(); - - //since drawable transforms do not include scale, changing volume scale - //requires an immediate rebuild of volume verts. - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_POSITION); - - if (mDrawable) - { - shrinkWrap(); - } - } -} - -LLFace* LLVOVolume::addFace(S32 f) -{ - const LLTextureEntry* te = getTE(f); - LLViewerTexture* imagep = getTEImage(f); - if (te->getMaterialParams().notNull()) - { - LLViewerTexture* normalp = getTENormalMap(f); - LLViewerTexture* specularp = getTESpecularMap(f); - return mDrawable->addFace(te, imagep, normalp, specularp); - } - return mDrawable->addFace(te, imagep); -} - -LLDrawable *LLVOVolume::createDrawable(LLPipeline *pipeline) -{ - pipeline->allocDrawable(this); - - mDrawable->setRenderType(LLPipeline::RENDER_TYPE_VOLUME); - - S32 max_tes_to_set = getNumTEs(); - for (S32 i = 0; i < max_tes_to_set; i++) - { - addFace(i); - } - mNumFaces = max_tes_to_set; - - if (isAttachment()) - { - mDrawable->makeActive(); - } - - if (getIsLight()) - { - // Add it to the pipeline mLightSet - gPipeline.setLight(mDrawable, true); - } - - if (isReflectionProbe()) - { - updateReflectionProbePtr(); - } - - updateRadius(); - bool force_update = true; // avoid non-alpha mDistance update being optimized away - mDrawable->updateDistance(*LLViewerCamera::getInstance(), force_update); - - return mDrawable; -} - -bool LLVOVolume::setVolume(const LLVolumeParams ¶ms_in, const S32 detail, bool unique_volume) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - LLVolumeParams volume_params = params_in; - - S32 last_lod = mVolumep.notNull() ? LLVolumeLODGroup::getVolumeDetailFromScale(mVolumep->getDetail()) : -1; - S32 lod = mLOD; - - bool is404 = false; - - if (isSculpted()) - { - // if it's a mesh - if ((volume_params.getSculptType() & LL_SCULPT_TYPE_MASK) == LL_SCULPT_TYPE_MESH) - { //meshes might not have all LODs, get the force detail to best existing LOD - if (NO_LOD != lod) - { - lod = gMeshRepo.getActualMeshLOD(volume_params, lod); - if (lod == -1) - { - is404 = true; - lod = 0; - } - } - } - } - - // Check if we need to change implementations - bool is_flexible = (volume_params.getPathParams().getCurveType() == LL_PCODE_PATH_FLEXIBLE); - if (is_flexible) - { - setParameterEntryInUse(LLNetworkData::PARAMS_FLEXIBLE, true, false); - if (!mVolumeImpl) - { - LLFlexibleObjectData* data = (LLFlexibleObjectData*)getParameterEntry(LLNetworkData::PARAMS_FLEXIBLE); - mVolumeImpl = new LLVolumeImplFlexible(this, data); - } - } - else - { - // Mark the parameter not in use - setParameterEntryInUse(LLNetworkData::PARAMS_FLEXIBLE, false, false); - if (mVolumeImpl) - { - delete mVolumeImpl; - mVolumeImpl = NULL; - if (mDrawable.notNull()) - { - // Undo the damage we did to this matrix - mDrawable->updateXform(false); - } - } - } - - if (is404) - { - setIcon(LLViewerTextureManager::getFetchedTextureFromFile("icons/Inv_Mesh.png", FTT_LOCAL_FILE, true, LLGLTexture::BOOST_UI)); - //render prim proxy when mesh loading attempts give up - volume_params.setSculptID(LLUUID::null, LL_SCULPT_TYPE_NONE); - - } - - if ((LLPrimitive::setVolume(volume_params, lod, (mVolumeImpl && mVolumeImpl->isVolumeUnique()))) || mSculptChanged) - { - mFaceMappingChanged = true; - - if (mVolumeImpl) - { - mVolumeImpl->onSetVolume(volume_params, mLOD); - } - - updateSculptTexture(); - - if (isSculpted()) - { - updateSculptTexture(); - // if it's a mesh - if ((volume_params.getSculptType() & LL_SCULPT_TYPE_MASK) == LL_SCULPT_TYPE_MESH) - { - if (mSkinInfo && mSkinInfo->mMeshID != volume_params.getSculptID()) - { - mSkinInfo = NULL; - mSkinInfoUnavaliable = false; - } - - if (!getVolume()->isMeshAssetLoaded()) - { - //load request not yet issued, request pipeline load this mesh - S32 available_lod = gMeshRepo.loadMesh(this, volume_params, lod, last_lod); - if (available_lod != lod) - { - LLPrimitive::setVolume(volume_params, available_lod); - } - } - - if (!mSkinInfo && !mSkinInfoUnavaliable) - { - LLUUID mesh_id = volume_params.getSculptID(); - if (gMeshRepo.hasHeader(mesh_id) && !gMeshRepo.hasSkinInfo(mesh_id)) - { - // If header is present but has no data about skin, - // no point fetching - mSkinInfoUnavaliable = true; - } - - if (!mSkinInfoUnavaliable) - { - const LLMeshSkinInfo* skin_info = gMeshRepo.getSkinInfo(mesh_id, this); - if (skin_info) - { - notifySkinInfoLoaded(skin_info); - } - } - } - } - else // otherwise is sculptie - { - if (mSculptTexture.notNull()) - { - sculpt(); - } - } - } - - return true; - } - else if (NO_LOD == lod) - { - LLSculptIDSize::instance().resetSizeSum(volume_params.getSculptID()); - } - - return false; -} - -void LLVOVolume::updateSculptTexture() -{ - LLPointer old_sculpt = mSculptTexture; - - if (isSculpted() && !isMesh()) - { - LLSculptParams *sculpt_params = (LLSculptParams *)getParameterEntry(LLNetworkData::PARAMS_SCULPT); - LLUUID id = sculpt_params->getSculptTexture(); - if (id.notNull()) - { - mSculptTexture = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); - } - - mSkinInfoUnavaliable = false; - mSkinInfo = NULL; - } - else - { - mSculptTexture = NULL; - } - - if (mSculptTexture != old_sculpt) - { - if (old_sculpt.notNull()) - { - old_sculpt->removeVolume(LLRender::SCULPT_TEX, this); - } - if (mSculptTexture.notNull()) - { - mSculptTexture->addVolume(LLRender::SCULPT_TEX, this); - } - } - -} - -void LLVOVolume::updateVisualComplexity() -{ - LLVOAvatar* avatar = getAvatarAncestor(); - if (avatar) - { - avatar->updateVisualComplexity(); - } - LLVOAvatar* rigged_avatar = getAvatar(); - if(rigged_avatar && (rigged_avatar != avatar)) - { - rigged_avatar->updateVisualComplexity(); - } -} - -void LLVOVolume::notifyMeshLoaded() -{ - mSculptChanged = true; - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_GEOMETRY); - - if (!mSkinInfo && !mSkinInfoUnavaliable) - { - // Header was loaded, update skin info state from header - LLUUID mesh_id = getVolume()->getParams().getSculptID(); - if (!gMeshRepo.hasSkinInfo(mesh_id)) - { - mSkinInfoUnavaliable = true; - } - } - - LLVOAvatar *av = getAvatar(); - if (av && !isAnimatedObject()) - { - av->addAttachmentOverridesForObject(this); - av->notifyAttachmentMeshLoaded(); - } - LLControlAvatar *cav = getControlAvatar(); - if (cav && isAnimatedObject()) - { - cav->addAttachmentOverridesForObject(this); - cav->notifyAttachmentMeshLoaded(); - } - updateVisualComplexity(); -} - -void LLVOVolume::notifySkinInfoLoaded(const LLMeshSkinInfo* skin) -{ - mSkinInfoUnavaliable = false; - mSkinInfo = skin; - - notifyMeshLoaded(); -} - -void LLVOVolume::notifySkinInfoUnavailable() -{ - mSkinInfoUnavaliable = true; - mSkinInfo = nullptr; -} - -// sculpt replaces generate() for sculpted surfaces -void LLVOVolume::sculpt() -{ - if (mSculptTexture.notNull()) - { - U16 sculpt_height = 0; - U16 sculpt_width = 0; - S8 sculpt_components = 0; - const U8* sculpt_data = NULL; - - S32 discard_level = mSculptTexture->getCachedRawImageLevel() ; - LLImageRaw* raw_image = mSculptTexture->getCachedRawImage() ; - - S32 max_discard = mSculptTexture->getMaxDiscardLevel(); - if (discard_level > max_discard) - { - discard_level = max_discard; // clamp to the best we can do - } - if(discard_level > MAX_DISCARD_LEVEL) - { - return; //we think data is not ready yet. - } - - S32 current_discard = getVolume()->getSculptLevel() ; - if(current_discard < -2) - { - static S32 low_sculpty_discard_warning_count = 1; - S32 exponent = llmax(1, llfloor( log10((F64) low_sculpty_discard_warning_count) )); - S32 interval = pow(10.0, exponent); - if ( low_sculpty_discard_warning_count < 10 || - (low_sculpty_discard_warning_count % interval) == 0) - { // Log first 10 time, then decreasing intervals afterwards otherwise this can flood the logs - LL_WARNS() << "WARNING!!: Current discard for sculpty " << mSculptTexture->getID() - << " at " << current_discard - << " is less than -2." - << " Hit this " << low_sculpty_discard_warning_count << " times" - << LL_ENDL; - } - low_sculpty_discard_warning_count++; - - // corrupted volume... don't update the sculpty - return; - } - else if (current_discard > MAX_DISCARD_LEVEL) - { - static S32 high_sculpty_discard_warning_count = 1; - S32 exponent = llmax(1, llfloor( log10((F64) high_sculpty_discard_warning_count) )); - S32 interval = pow(10.0, exponent); - if ( high_sculpty_discard_warning_count < 10 || - (high_sculpty_discard_warning_count % interval) == 0) - { // Log first 10 time, then decreasing intervals afterwards otherwise this can flood the logs - LL_WARNS() << "WARNING!!: Current discard for sculpty " << mSculptTexture->getID() - << " at " << current_discard - << " is more than than allowed max of " << MAX_DISCARD_LEVEL - << ". Hit this " << high_sculpty_discard_warning_count << " times" - << LL_ENDL; - } - high_sculpty_discard_warning_count++; - - // corrupted volume... don't update the sculpty - return; - } - - if (current_discard == discard_level) // no work to do here - return; - - if(!raw_image) - { - llassert(discard_level < 0) ; - - sculpt_width = 0; - sculpt_height = 0; - sculpt_data = NULL ; - - if(LLViewerTextureManager::sTesterp) - { - LLViewerTextureManager::sTesterp->updateGrayTextureBinding(); - } - } - else - { - LLImageDataSharedLock lock(raw_image); - - sculpt_height = raw_image->getHeight(); - sculpt_width = raw_image->getWidth(); - sculpt_components = raw_image->getComponents(); - - sculpt_data = raw_image->getData(); - - if(LLViewerTextureManager::sTesterp) - { - mSculptTexture->updateBindStatsForTester() ; - } - } - getVolume()->sculpt(sculpt_width, sculpt_height, sculpt_components, sculpt_data, discard_level, mSculptTexture->isMissingAsset()); - - //notify rebuild any other VOVolumes that reference this sculpty volume - for (S32 i = 0; i < mSculptTexture->getNumVolumes(LLRender::SCULPT_TEX); ++i) - { - LLVOVolume* volume = (*(mSculptTexture->getVolumeList(LLRender::SCULPT_TEX)))[i]; - if (volume != this && volume->getVolume() == getVolume()) - { - gPipeline.markRebuild(volume->mDrawable, LLDrawable::REBUILD_GEOMETRY); - } - } - } -} - -S32 LLVOVolume::computeLODDetail(F32 distance, F32 radius, F32 lod_factor) -{ - S32 cur_detail; - if (LLPipeline::sDynamicLOD) - { - // We've got LOD in the profile, and in the twist. Use radius. - F32 tan_angle = (lod_factor*radius)/distance; - cur_detail = LLVolumeLODGroup::getDetailFromTan(ll_round(tan_angle, 0.01f)); - } - else - { - cur_detail = llclamp((S32) (sqrtf(radius)*lod_factor*4.f), 0, 3); - } - return cur_detail; -} - -std::string get_debug_object_lod_text(LLVOVolume *rootp) -{ - std::string cam_dist_string = ""; - cam_dist_string += LLStringOps::getReadableNumber(rootp->mLODDistance) + " "; - std::string lod_string = llformat("%d",rootp->getLOD()); - F32 lod_radius = rootp->mLODRadius; - S32 cam_dist_count = 0; - LLViewerObject::const_child_list_t& child_list = rootp->getChildren(); - for (LLViewerObject::const_child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); ++iter) - { - LLViewerObject *childp = *iter; - LLVOVolume *volp = dynamic_cast(childp); - if (volp) - { - lod_string += llformat("%d",volp->getLOD()); - if (volp->isRiggedMesh()) - { - // Rigged/animatable mesh. This is computed from the - // avatar dynamic box, so value from any vol will be - // the same. - lod_radius = volp->mLODRadius; - } - if (volp->mDrawable) - { - if (cam_dist_count < 4) - { - cam_dist_string += LLStringOps::getReadableNumber(volp->mLODDistance) + " "; - cam_dist_count++; - } - } - } - } - std::string result = llformat("lod_radius %s dists %s lods %s", - LLStringOps::getReadableNumber(lod_radius).c_str(), - cam_dist_string.c_str(), - lod_string.c_str()); - return result; -} - -bool LLVOVolume::calcLOD() -{ - if (mDrawable.isNull()) - { - return false; - } - - S32 cur_detail = 0; - - F32 radius; - F32 distance; - F32 lod_factor = LLVOVolume::sLODFactor; - - if (mDrawable->isState(LLDrawable::RIGGED)) - { - LLVOAvatar* avatar = getAvatar(); - - // Not sure how this can really happen, but alas it does. Better exit here than crashing. - if( !avatar || !avatar->mDrawable ) - { - return false; - } - - distance = avatar->mDrawable->mDistanceWRTCamera; - - - if (avatar->isControlAvatar()) - { - // MAINT-7926 Handle volumes in an animated object as a special case - const LLVector3* box = avatar->getLastAnimExtents(); - LLVector3 diag = box[1] - box[0]; - radius = diag.magVec() * 0.5f; - LL_DEBUGS("DynamicBox") << avatar->getFullname() << " diag " << diag << " radius " << radius << LL_ENDL; - } - else - { - // Volume in a rigged mesh attached to a regular avatar. - // Note this isn't really a radius, so distance calcs are off by factor of 2 - //radius = avatar->getBinRadius(); - // SL-937: add dynamic box handling for rigged mesh on regular avatars. - const LLVector3* box = avatar->getLastAnimExtents(); - LLVector3 diag = box[1] - box[0]; - radius = diag.magVec(); // preserve old BinRadius behavior - 2x off - LL_DEBUGS("DynamicBox") << avatar->getFullname() << " diag " << diag << " radius " << radius << LL_ENDL; - } - if (distance <= 0.f || radius <= 0.f) - { - LL_DEBUGS("DynamicBox","CalcLOD") << "avatar distance/radius uninitialized, skipping" << LL_ENDL; - return false; - } - } - else - { - distance = mDrawable->mDistanceWRTCamera; - radius = getVolume() ? getVolume()->mLODScaleBias.scaledVec(getScale()).length() : getScale().length(); - if (distance <= 0.f || radius <= 0.f) - { - LL_DEBUGS("DynamicBox","CalcLOD") << "non-avatar distance/radius uninitialized, skipping" << LL_ENDL; - return false; - } - } - - //hold onto unmodified distance for debugging - //F32 debug_distance = distance; - - mLODDistance = distance; - mLODRadius = radius; - - static LLCachedControl debug_lods(gSavedSettings, "DebugObjectLODs", false); - if (debug_lods) - { - if (getAvatar() && isRootEdit()) - { - std::string debug_object_text = get_debug_object_lod_text(this); - setDebugText(debug_object_text); - mResetDebugText = true; - } - } - else - { - if (mResetDebugText) - { - restoreHudText(); - mResetDebugText = false; - } - } - - distance *= sDistanceFactor; - - F32 rampDist = LLVOVolume::sLODFactor * 2; - - if (distance < rampDist) - { - // Boost LOD when you're REALLY close - distance *= 1.0f/rampDist; - distance *= distance; - distance *= rampDist; - } - - - distance *= F_PI/3.f; - - static LLCachedControl ignore_fov_zoom(gSavedSettings,"IgnoreFOVZoomForLODs"); - if(!ignore_fov_zoom) - { - lod_factor *= DEFAULT_FIELD_OF_VIEW / LLViewerCamera::getInstance()->getDefaultFOV(); - } - - mLODAdjustedDistance = distance; - - if (isHUDAttachment()) - { - // HUDs always show at highest detail - cur_detail = 3; - } - else - { - cur_detail = computeLODDetail(ll_round(distance, 0.01f), ll_round(radius, 0.01f), lod_factor); - } - - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TRIANGLE_COUNT) && mDrawable->getFace(0)) - { - if (isRootEdit()) - { - S32 total_tris = recursiveGetTriangleCount(); - S32 est_max_tris = recursiveGetEstTrianglesMax(); - setDebugText(llformat("TRIS SHOWN %d EST %d", total_tris, est_max_tris)); - } - } - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_LOD_INFO) && - mDrawable->getFace(0)) - { - // This is a debug display for LODs. Please don't put the texture index here. - setDebugText(llformat("%d", cur_detail)); - } - - if (cur_detail != mLOD) - { - LL_DEBUGS("DynamicBox","CalcLOD") << "new LOD " << cur_detail << " change from " << mLOD - << " distance " << distance << " radius " << radius << " rampDist " << rampDist - << " drawable rigged? " << (mDrawable ? (S32) mDrawable->isState(LLDrawable::RIGGED) : (S32) -1) - << " mRiggedVolume " << (void*)getRiggedVolume() - << " distanceWRTCamera " << (mDrawable ? mDrawable->mDistanceWRTCamera : -1.f) - << LL_ENDL; - - mAppAngle = ll_round((F32) atan2( mDrawable->getRadius(), mDrawable->mDistanceWRTCamera) * RAD_TO_DEG, 0.01f); - mLOD = cur_detail; - - return true; - } - - return false; -} - -bool LLVOVolume::updateLOD() -{ - if (mDrawable.isNull()) - { - return false; - } - - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - - bool lod_changed = false; - - if (!LLSculptIDSize::instance().isUnloaded(getVolume()->getParams().getSculptID())) - { - lod_changed = calcLOD(); - } - else - { - return false; - } - - if (lod_changed) - { - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); - mLODChanged = true; - } - else - { - F32 new_radius = getBinRadius(); - F32 old_radius = mDrawable->getBinRadius(); - if (new_radius < old_radius * 0.9f || new_radius > old_radius*1.1f) - { - gPipeline.markPartitionMove(mDrawable); - } - } - - lod_changed = lod_changed || LLViewerObject::updateLOD(); - - return lod_changed; -} - -bool LLVOVolume::setDrawableParent(LLDrawable* parentp) -{ - if (!LLViewerObject::setDrawableParent(parentp)) - { - // no change in drawable parent - return false; - } - - if (!mDrawable->isRoot()) - { - // rebuild vertices in parent relative space - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); - - if (mDrawable->isActive() && !parentp->isActive()) - { - parentp->makeActive(); - } - else if (mDrawable->isStatic() && parentp->isActive()) - { - mDrawable->makeActive(); - } - } - - return true; -} - -void LLVOVolume::updateFaceFlags() -{ - // There's no guarantee that getVolume()->getNumFaces() == mDrawable->getNumFaces() - for (S32 i = 0; i < getVolume()->getNumFaces() && i < mDrawable->getNumFaces(); i++) - { - LLFace *face = mDrawable->getFace(i); - if (face) - { - bool fullbright = getTE(i)->getFullbright(); - face->clearState(LLFace::FULLBRIGHT | LLFace::HUD_RENDER | LLFace::LIGHT); - - if (fullbright || (mMaterial == LL_MCODE_LIGHT)) - { - face->setState(LLFace::FULLBRIGHT); - } - if (mDrawable->isLight()) - { - face->setState(LLFace::LIGHT); - } - if (isHUDAttachment()) - { - face->setState(LLFace::HUD_RENDER); - } - } - } -} - -bool LLVOVolume::setParent(LLViewerObject* parent) -{ - bool ret = false ; - LLViewerObject *old_parent = (LLViewerObject*) getParent(); - if (parent != old_parent) - { - ret = LLViewerObject::setParent(parent); - if (ret && mDrawable) - { - gPipeline.markMoved(mDrawable); - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); - } - onReparent(old_parent, parent); - } - - return ret ; -} - -// NOTE: regenFaces() MUST be followed by genTriangles()! -void LLVOVolume::regenFaces() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - // remove existing faces - bool count_changed = mNumFaces != getNumTEs(); - - if (count_changed) - { - deleteFaces(); - // add new faces - mNumFaces = getNumTEs(); - } - - for (S32 i = 0; i < mNumFaces; i++) - { - LLFace* facep = count_changed ? addFace(i) : mDrawable->getFace(i); - if (!facep) continue; - - facep->setTEOffset(i); - facep->setTexture(getTEImage(i)); - if (facep->getTextureEntry()->getMaterialParams().notNull()) - { - facep->setNormalMap(getTENormalMap(i)); - facep->setSpecularMap(getTESpecularMap(i)); - } - facep->setViewerObject(this); - - // If the face had media on it, this will have broken the link between the LLViewerMediaTexture and the face. - // Re-establish the link. - if((int)mMediaImplList.size() > i) - { - if(mMediaImplList[i]) - { - LLViewerMediaTexture* media_tex = LLViewerTextureManager::findMediaTexture(mMediaImplList[i]->getMediaTextureID()) ; - if(media_tex) - { - media_tex->addMediaToFace(facep) ; - } - } - } - } - - if (!count_changed) - { - updateFaceFlags(); - } -} - -bool LLVOVolume::genBBoxes(bool force_global, bool should_update_octree_bounds) -{ - LL_PROFILE_ZONE_SCOPED; - bool res = true; - - LLVector4a min, max; - - min.clear(); - max.clear(); - - bool rebuild = mDrawable->isState(LLDrawable::REBUILD_VOLUME | LLDrawable::REBUILD_POSITION | LLDrawable::REBUILD_RIGGED); - - if (getRiggedVolume()) - { - // MAINT-8264 - better to use the existing call in calling - // func LLVOVolume::updateGeometry() if we can detect when - // updates needed, set REBUILD_RIGGED accordingly. - - // Without the flag, this will remove unused rigged volumes, which we are not currently very aggressive about. - updateRiggedVolume(false); - } - - LLVolume* volume = mRiggedVolume; - if (!volume) - { - volume = getVolume(); - } - - bool any_valid_boxes = false; - - if (getRiggedVolume()) - { - LL_DEBUGS("RiggedBox") << "rebuilding box, volume face count " << getVolume()->getNumVolumeFaces() << " drawable face count " << mDrawable->getNumFaces() << LL_ENDL; - } - - // There's no guarantee that getVolume()->getNumFaces() == mDrawable->getNumFaces() - for (S32 i = 0; - i < getVolume()->getNumVolumeFaces() && i < mDrawable->getNumFaces() && i < getNumTEs(); - i++) - { - LLFace* face = mDrawable->getFace(i); - if (!face) - { - continue; - } - - bool face_res = face->genVolumeBBoxes(*volume, i, - mRelativeXform, - (mVolumeImpl && mVolumeImpl->isVolumeGlobal()) || force_global); - res &= face_res; // note that this result is never used - - // MAINT-8264 - ignore bboxes of ill-formed faces. - if (!face_res) - { - continue; - } - if (rebuild) - { - if (getRiggedVolume()) - { - LL_DEBUGS("RiggedBox") << "rebuilding box, face " << i << " extents " << face->mExtents[0] << ", " << face->mExtents[1] << LL_ENDL; - } - if (!any_valid_boxes) - { - min = face->mExtents[0]; - max = face->mExtents[1]; - any_valid_boxes = true; - } - else - { - min.setMin(min, face->mExtents[0]); - max.setMax(max, face->mExtents[1]); - } - } - } - - if (any_valid_boxes) - { - if (rebuild && should_update_octree_bounds) - { - //get the Avatar associated with this object if it's rigged - LLVOAvatar* avatar = nullptr; - if (isRiggedMesh()) - { - if (!isAnimatedObject()) - { - if (isAttachment()) - { - avatar = getAvatar(); - } - } - else - { - LLControlAvatar* controlAvatar = getControlAvatar(); - if (controlAvatar && controlAvatar->mPlaying) - { - avatar = controlAvatar; - } - } - } - - mDrawable->setSpatialExtents(min, max); - - if (avatar) - { - // put all rigged drawables in the same octree node for better batching - mDrawable->setPositionGroup(LLVector4a(0, 0, 0)); - } - else - { - min.add(max); - min.mul(0.5f); - mDrawable->setPositionGroup(min); - } - } - - updateRadius(); - mDrawable->movePartition(); - } - else - { - LL_DEBUGS("RiggedBox") << "genBBoxes failed to find any valid face boxes" << LL_ENDL; - } - - return res; -} - -void LLVOVolume::preRebuild() -{ - if (mVolumeImpl != NULL) - { - mVolumeImpl->preRebuild(); - } -} - -void LLVOVolume::updateRelativeXform(bool force_identity) -{ - if (mVolumeImpl) - { - mVolumeImpl->updateRelativeXform(force_identity); - return; - } - - LLDrawable* drawable = mDrawable; - - if (drawable->isState(LLDrawable::RIGGED) && mRiggedVolume.notNull()) - { //rigged volume (which is in agent space) is used for generating bounding boxes etc - //inverse of render matrix should go to partition space - mRelativeXform = getRenderMatrix(); - - F32* dst = (F32*) mRelativeXformInvTrans.mMatrix; - F32* src = (F32*) mRelativeXform.mMatrix; - dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; - dst[3] = src[4]; dst[4] = src[5]; dst[5] = src[6]; - dst[6] = src[8]; dst[7] = src[9]; dst[8] = src[10]; - - mRelativeXform.invert(); - mRelativeXformInvTrans.transpose(); - } - else if (drawable->isActive() || force_identity) - { - // setup relative transforms - LLQuaternion delta_rot; - LLVector3 delta_pos, delta_scale; - - //matrix from local space to parent relative/global space - bool use_identity = force_identity || drawable->isSpatialRoot(); - delta_rot = use_identity ? LLQuaternion() : mDrawable->getRotation(); - delta_pos = use_identity ? LLVector3(0,0,0) : mDrawable->getPosition(); - delta_scale = mDrawable->getScale(); - - // Vertex transform (4x4) - LLVector3 x_axis = LLVector3(delta_scale.mV[VX], 0.f, 0.f) * delta_rot; - LLVector3 y_axis = LLVector3(0.f, delta_scale.mV[VY], 0.f) * delta_rot; - LLVector3 z_axis = LLVector3(0.f, 0.f, delta_scale.mV[VZ]) * delta_rot; - - mRelativeXform.initRows(LLVector4(x_axis, 0.f), - LLVector4(y_axis, 0.f), - LLVector4(z_axis, 0.f), - LLVector4(delta_pos, 1.f)); - - - // compute inverse transpose for normals - // mRelativeXformInvTrans.setRows(x_axis, y_axis, z_axis); - // mRelativeXformInvTrans.invert(); - // mRelativeXformInvTrans.setRows(x_axis, y_axis, z_axis); - // grumble - invert is NOT a matrix invert, so we do it by hand: - - LLMatrix3 rot_inverse = LLMatrix3(~delta_rot); - - LLMatrix3 scale_inverse; - scale_inverse.setRows(LLVector3(1.0, 0.0, 0.0) / delta_scale.mV[VX], - LLVector3(0.0, 1.0, 0.0) / delta_scale.mV[VY], - LLVector3(0.0, 0.0, 1.0) / delta_scale.mV[VZ]); - - - mRelativeXformInvTrans = rot_inverse * scale_inverse; - - mRelativeXformInvTrans.transpose(); - } - else - { - LLVector3 pos = getPosition(); - LLVector3 scale = getScale(); - LLQuaternion rot = getRotation(); - - if (mParent) - { - pos *= mParent->getRotation(); - pos += mParent->getPosition(); - rot *= mParent->getRotation(); - } - - //LLViewerRegion* region = getRegion(); - //pos += region->getOriginAgent(); - - LLVector3 x_axis = LLVector3(scale.mV[VX], 0.f, 0.f) * rot; - LLVector3 y_axis = LLVector3(0.f, scale.mV[VY], 0.f) * rot; - LLVector3 z_axis = LLVector3(0.f, 0.f, scale.mV[VZ]) * rot; - - mRelativeXform.initRows(LLVector4(x_axis, 0.f), - LLVector4(y_axis, 0.f), - LLVector4(z_axis, 0.f), - LLVector4(pos, 1.f)); - - // compute inverse transpose for normals - LLMatrix3 rot_inverse = LLMatrix3(~rot); - - LLMatrix3 scale_inverse; - scale_inverse.setRows(LLVector3(1.0, 0.0, 0.0) / scale.mV[VX], - LLVector3(0.0, 1.0, 0.0) / scale.mV[VY], - LLVector3(0.0, 0.0, 1.0) / scale.mV[VZ]); - - - mRelativeXformInvTrans = rot_inverse * scale_inverse; - - mRelativeXformInvTrans.transpose(); - } -} - -bool LLVOVolume::lodOrSculptChanged(LLDrawable *drawable, bool &compiled, bool &should_update_octree_bounds) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - bool regen_faces = false; - - LLVolume *old_volumep, *new_volumep; - F32 old_lod, new_lod; - S32 old_num_faces, new_num_faces; - - old_volumep = getVolume(); - old_lod = old_volumep->getDetail(); - old_num_faces = old_volumep->getNumFaces(); - old_volumep = NULL; - - { - const LLVolumeParams &volume_params = getVolume()->getParams(); - setVolume(volume_params, 0); - } - - new_volumep = getVolume(); - new_lod = new_volumep->getDetail(); - new_num_faces = new_volumep->getNumFaces(); - new_volumep = NULL; - - if ((new_lod != old_lod) || mSculptChanged) - { - if (mDrawable->isState(LLDrawable::RIGGED)) - { - updateVisualComplexity(); - } - - compiled = true; - // new_lod > old_lod breaks a feedback loop between LOD updates and - // bounding box updates. - should_update_octree_bounds = should_update_octree_bounds || mSculptChanged || new_lod > old_lod; - sNumLODChanges += new_num_faces; - - if ((S32)getNumTEs() != getVolume()->getNumFaces()) - { - setNumTEs(getVolume()->getNumFaces()); //mesh loading may change number of faces. - } - - drawable->setState(LLDrawable::REBUILD_VOLUME); // for face->genVolumeTriangles() - - { - regen_faces = new_num_faces != old_num_faces || mNumFaces != (S32)getNumTEs(); - if (regen_faces) - { - regenFaces(); - } - - if (mSculptChanged) - { //changes in sculpt maps can thrash an object bounding box without - //triggering a spatial group bounding box update -- force spatial group - //to update bounding boxes - LLSpatialGroup* group = mDrawable->getSpatialGroup(); - if (group) - { - group->unbound(); - } - } - } - } - - return regen_faces; -} - -bool LLVOVolume::updateGeometry(LLDrawable *drawable) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - - if (mDrawable->isState(LLDrawable::REBUILD_RIGGED)) - { - updateRiggedVolume(false); - genBBoxes(false); - mDrawable->clearState(LLDrawable::REBUILD_RIGGED); - } - - if (mVolumeImpl != NULL) - { - bool res; - { - res = mVolumeImpl->doUpdateGeometry(drawable); - } - updateFaceFlags(); - return res; - } - - LLSpatialGroup* group = drawable->getSpatialGroup(); - if (group) - { - group->dirtyMesh(); - } - - updateRelativeXform(); - - if (mDrawable.isNull()) // Not sure why this is happening, but it is... - { - return true; // No update to complete - } - - bool compiled = false; - // This should be true in most cases, unless we're sure no octree update is - // needed. - bool should_update_octree_bounds = bool(getRiggedVolume()) || mDrawable->isState(LLDrawable::REBUILD_POSITION) || !mDrawable->getSpatialExtents()->isFinite3(); - - if (mVolumeChanged || mFaceMappingChanged) - { - dirtySpatialGroup(); - - bool was_regen_faces = false; - should_update_octree_bounds = true; - - if (mVolumeChanged) - { - was_regen_faces = lodOrSculptChanged(drawable, compiled, should_update_octree_bounds); - drawable->setState(LLDrawable::REBUILD_VOLUME); - } - else if (mSculptChanged || mLODChanged || mColorChanged) - { - compiled = true; - was_regen_faces = lodOrSculptChanged(drawable, compiled, should_update_octree_bounds); - } - - if (!was_regen_faces) { - regenFaces(); - } - } - else if (mLODChanged || mSculptChanged || mColorChanged) - { - dirtySpatialGroup(); - compiled = true; - lodOrSculptChanged(drawable, compiled, should_update_octree_bounds); - - if(drawable->isState(LLDrawable::REBUILD_RIGGED | LLDrawable::RIGGED)) - { - updateRiggedVolume(false); - } - } - // it has its own drawable (it's moved) or it has changed UVs or it has changed xforms from global<->local - else - { - compiled = true; - // All it did was move or we changed the texture coordinate offset - } - - // Generate bounding boxes if needed, and update the object's size in the - // octree - genBBoxes(false, should_update_octree_bounds); - - // Update face flags - updateFaceFlags(); - - if(compiled) - { - LLPipeline::sCompiles++; - } - - mVolumeChanged = false; - mLODChanged = false; - mSculptChanged = false; - mFaceMappingChanged = false; - mColorChanged = false; - - return LLViewerObject::updateGeometry(drawable); -} - -void LLVOVolume::updateFaceSize(S32 idx) -{ - if( mDrawable->getNumFaces() <= idx ) - { - return; - } - - LLFace* facep = mDrawable->getFace(idx); - if (facep) - { - if (idx >= getVolume()->getNumVolumeFaces()) - { - facep->setSize(0,0, true); - } - else - { - const LLVolumeFace& vol_face = getVolume()->getVolumeFace(idx); - facep->setSize(vol_face.mNumVertices, vol_face.mNumIndices, - true); // <--- volume faces should be padded for 16-byte alignment - - } - } -} - -bool LLVOVolume::isRootEdit() const -{ - if (mParent && !((LLViewerObject*)mParent)->isAvatar()) - { - return false; - } - return true; -} - -//virtual -void LLVOVolume::setNumTEs(const U8 num_tes) -{ - const U8 old_num_tes = getNumTEs() ; - - if(old_num_tes && old_num_tes < num_tes) //new faces added - { - LLViewerObject::setNumTEs(num_tes) ; - - if(mMediaImplList.size() >= old_num_tes && mMediaImplList[old_num_tes -1].notNull())//duplicate the last media textures if exists. - { - mMediaImplList.resize(num_tes) ; - const LLTextureEntry* te = getTE(old_num_tes - 1) ; - for(U8 i = old_num_tes; i < num_tes ; i++) - { - setTE(i, *te) ; - mMediaImplList[i] = mMediaImplList[old_num_tes -1] ; - } - mMediaImplList[old_num_tes -1]->setUpdated(true) ; - } - } - else if(old_num_tes > num_tes && mMediaImplList.size() > num_tes) //old faces removed - { - U8 end = (U8)(mMediaImplList.size()) ; - for(U8 i = num_tes; i < end ; i++) - { - removeMediaImpl(i) ; - } - mMediaImplList.resize(num_tes) ; - - LLViewerObject::setNumTEs(num_tes) ; - } - else - { - LLViewerObject::setNumTEs(num_tes) ; - } - - return ; -} - - -//virtual -void LLVOVolume::changeTEImage(S32 index, LLViewerTexture* imagep) -{ - bool changed = (mTEImages[index] != imagep); - LLViewerObject::changeTEImage(index, imagep); - if (changed) - { - gPipeline.markTextured(mDrawable); - mFaceMappingChanged = true; - } -} - -void LLVOVolume::setTEImage(const U8 te, LLViewerTexture *imagep) -{ - bool changed = (mTEImages[te] != imagep); - LLViewerObject::setTEImage(te, imagep); - if (changed) - { - gPipeline.markTextured(mDrawable); - mFaceMappingChanged = true; - } -} - -S32 LLVOVolume::setTETexture(const U8 te, const LLUUID &uuid) -{ - S32 res = LLViewerObject::setTETexture(te, uuid); - if (res) - { - if (mDrawable) - { - // dynamic texture changes break batches, isolate in octree - shrinkWrap(); - gPipeline.markTextured(mDrawable); - } - mFaceMappingChanged = true; - } - return res; -} - -S32 LLVOVolume::setTEColor(const U8 te, const LLColor3& color) -{ - return setTEColor(te, LLColor4(color)); -} - -S32 LLVOVolume::setTEColor(const U8 te, const LLColor4& color) -{ - S32 retval = 0; - const LLTextureEntry *tep = getTE(te); - if (!tep) - { - LL_WARNS("MaterialTEs") << "No texture entry for te " << (S32)te << ", object " << mID << LL_ENDL; - } - else if (color != tep->getColor()) - { - F32 old_alpha = tep->getColor().mV[3]; - if (color.mV[3] != old_alpha) - { - gPipeline.markTextured(mDrawable); - //treat this alpha change as an LoD update since render batches may need to get rebuilt - mLODChanged = true; - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); - } - retval = LLPrimitive::setTEColor(te, color); - if (mDrawable.notNull() && retval) - { - // These should only happen on updates which are not the initial update. - mColorChanged = true; - mDrawable->setState(LLDrawable::REBUILD_COLOR); - shrinkWrap(); - dirtyMesh(); - } - } - - return retval; -} - -S32 LLVOVolume::setTEBumpmap(const U8 te, const U8 bumpmap) -{ - S32 res = LLViewerObject::setTEBumpmap(te, bumpmap); - if (res) - { - gPipeline.markTextured(mDrawable); - mFaceMappingChanged = true; - } - return res; -} - -S32 LLVOVolume::setTETexGen(const U8 te, const U8 texgen) -{ - S32 res = LLViewerObject::setTETexGen(te, texgen); - if (res) - { - gPipeline.markTextured(mDrawable); - mFaceMappingChanged = true; - } - return res; -} - -S32 LLVOVolume::setTEMediaTexGen(const U8 te, const U8 media) -{ - S32 res = LLViewerObject::setTEMediaTexGen(te, media); - if (res) - { - gPipeline.markTextured(mDrawable); - mFaceMappingChanged = true; - } - return res; -} - -S32 LLVOVolume::setTEShiny(const U8 te, const U8 shiny) -{ - S32 res = LLViewerObject::setTEShiny(te, shiny); - if (res) - { - gPipeline.markTextured(mDrawable); - mFaceMappingChanged = true; - } - return res; -} - -S32 LLVOVolume::setTEFullbright(const U8 te, const U8 fullbright) -{ - S32 res = LLViewerObject::setTEFullbright(te, fullbright); - if (res) - { - gPipeline.markTextured(mDrawable); - mFaceMappingChanged = true; - } - return res; -} - -S32 LLVOVolume::setTEBumpShinyFullbright(const U8 te, const U8 bump) -{ - S32 res = LLViewerObject::setTEBumpShinyFullbright(te, bump); - if (res) - { - gPipeline.markTextured(mDrawable); - mFaceMappingChanged = true; - } - return res; -} - -S32 LLVOVolume::setTEMediaFlags(const U8 te, const U8 media_flags) -{ - S32 res = LLViewerObject::setTEMediaFlags(te, media_flags); - if (res) - { - gPipeline.markTextured(mDrawable); - mFaceMappingChanged = true; - } - return res; -} - -S32 LLVOVolume::setTEGlow(const U8 te, const F32 glow) -{ - S32 res = LLViewerObject::setTEGlow(te, glow); - if (res) - { - if (mDrawable) - { - gPipeline.markTextured(mDrawable); - shrinkWrap(); - } - mFaceMappingChanged = true; - } - return res; -} - -void LLVOVolume::setTEMaterialParamsCallbackTE(const LLUUID& objectID, const LLMaterialID &pMaterialID, const LLMaterialPtr pMaterialParams, U32 te) -{ - LLVOVolume* pVol = (LLVOVolume*)gObjectList.findObject(objectID); - if (pVol) - { - LL_DEBUGS("MaterialTEs") << "materialid " << pMaterialID.asString() << " to TE " << te << LL_ENDL; - if (te >= pVol->getNumTEs()) - return; - - LLTextureEntry* texture_entry = pVol->getTE(te); - if (texture_entry && (texture_entry->getMaterialID() == pMaterialID)) - { - pVol->setTEMaterialParams(te, pMaterialParams); - } - } -} - -S32 LLVOVolume::setTEMaterialID(const U8 te, const LLMaterialID& pMaterialID) -{ - S32 res = LLViewerObject::setTEMaterialID(te, pMaterialID); - LL_DEBUGS("MaterialTEs") << "te "<< (S32)te << " materialid " << pMaterialID.asString() << " res " << res - << ( LLSelectMgr::getInstance()->getSelection()->contains(const_cast(this), te) ? " selected" : " not selected" ) - << LL_ENDL; - - LL_DEBUGS("MaterialTEs") << " " << pMaterialID.asString() << LL_ENDL; - if (res) - { - LLMaterialMgr::instance().getTE(getRegion()->getRegionID(), pMaterialID, te, boost::bind(&LLVOVolume::setTEMaterialParamsCallbackTE, getID(), _1, _2, _3)); - - setChanged(ALL_CHANGED); - if (!mDrawable.isNull()) - { - gPipeline.markTextured(mDrawable); - gPipeline.markRebuild(mDrawable,LLDrawable::REBUILD_ALL); - } - mFaceMappingChanged = true; - } - return res; -} - -S32 LLVOVolume::setTEMaterialParams(const U8 te, const LLMaterialPtr pMaterialParams) -{ - S32 res = LLViewerObject::setTEMaterialParams(te, pMaterialParams); - - LL_DEBUGS("MaterialTEs") << "te " << (S32)te << " material " << ((pMaterialParams) ? pMaterialParams->asLLSD() : LLSD("null")) << " res " << res - << ( LLSelectMgr::getInstance()->getSelection()->contains(const_cast(this), te) ? " selected" : " not selected" ) - << LL_ENDL; - setChanged(ALL_CHANGED); - if (!mDrawable.isNull()) - { - gPipeline.markTextured(mDrawable); - gPipeline.markRebuild(mDrawable,LLDrawable::REBUILD_ALL); - } - mFaceMappingChanged = true; - return TEM_CHANGE_TEXTURE; -} - -S32 LLVOVolume::setTEGLTFMaterialOverride(U8 te, LLGLTFMaterial* mat) -{ - S32 retval = LLViewerObject::setTEGLTFMaterialOverride(te, mat); - - if (retval == TEM_CHANGE_TEXTURE) - { - if (!mDrawable.isNull()) - { - gPipeline.markTextured(mDrawable); - gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); - } - mFaceMappingChanged = true; - } - - return retval; -} - - -S32 LLVOVolume::setTEScale(const U8 te, const F32 s, const F32 t) -{ - S32 res = LLViewerObject::setTEScale(te, s, t); - if (res) - { - gPipeline.markTextured(mDrawable); - mFaceMappingChanged = true; - } - return res; -} - -S32 LLVOVolume::setTEScaleS(const U8 te, const F32 s) -{ - S32 res = LLViewerObject::setTEScaleS(te, s); - if (res) - { - gPipeline.markTextured(mDrawable); - mFaceMappingChanged = true; - } - return res; -} - -S32 LLVOVolume::setTEScaleT(const U8 te, const F32 t) -{ - S32 res = LLViewerObject::setTEScaleT(te, t); - if (res) - { - gPipeline.markTextured(mDrawable); - mFaceMappingChanged = true; - } - return res; -} - -bool LLVOVolume::hasMedia() const -{ - bool result = false; - const U8 numTEs = getNumTEs(); - for (U8 i = 0; i < numTEs; i++) - { - const LLTextureEntry* te = getTE(i); - if(te->hasMedia()) - { - result = true; - break; - } - } - return result; -} - -LLVector3 LLVOVolume::getApproximateFaceNormal(U8 face_id) -{ - LLVolume* volume = getVolume(); - LLVector4a result; - result.clear(); - - LLVector3 ret; - - if (volume && face_id < volume->getNumVolumeFaces()) - { - const LLVolumeFace& face = volume->getVolumeFace(face_id); - for (S32 i = 0; i < (S32)face.mNumVertices; ++i) - { - result.add(face.mNormals[i]); - } - - LLVector3 ret(result.getF32ptr()); - ret = volumeDirectionToAgent(ret); - ret.normVec(); - } - - return ret; -} - -void LLVOVolume::requestMediaDataUpdate(bool isNew) -{ - if (sObjectMediaClient) - sObjectMediaClient->fetchMedia(new LLMediaDataClientObjectImpl(this, isNew)); -} - -bool LLVOVolume::isMediaDataBeingFetched() const -{ - // I know what I'm doing by const_casting this away: this is just - // a wrapper class that is only going to do a lookup. - return (sObjectMediaClient) ? sObjectMediaClient->isInQueue(new LLMediaDataClientObjectImpl(const_cast(this), false)) : false; -} - -void LLVOVolume::cleanUpMediaImpls() -{ - // Iterate through our TEs and remove any Impls that are no longer used - const U8 numTEs = getNumTEs(); - for (U8 i = 0; i < numTEs; i++) - { - const LLTextureEntry* te = getTE(i); - if( ! te->hasMedia()) - { - // Delete the media IMPL! - removeMediaImpl(i) ; - } - } -} - -void LLVOVolume::updateObjectMediaData(const LLSD &media_data_array, const std::string &media_version) -{ - // media_data_array is an array of media entry maps - // media_version is the version string in the response. - U32 fetched_version = LLTextureEntry::getVersionFromMediaVersionString(media_version); - - // Only update it if it is newer! - if ( (S32)fetched_version > mLastFetchedMediaVersion) - { - mLastFetchedMediaVersion = fetched_version; - //LL_INFOS() << "updating:" << this->getID() << " " << ll_pretty_print_sd(media_data_array) << LL_ENDL; - - LLSD::array_const_iterator iter = media_data_array.beginArray(); - LLSD::array_const_iterator end = media_data_array.endArray(); - U8 texture_index = 0; - for (; iter != end; ++iter, ++texture_index) - { - syncMediaData(texture_index, *iter, false/*merge*/, false/*ignore_agent*/); - } - } -} - -void LLVOVolume::syncMediaData(S32 texture_index, const LLSD &media_data, bool merge, bool ignore_agent) -{ - if(mDead) - { - // If the object has been marked dead, don't process media updates. - return; - } - - LLTextureEntry *te = getTE(texture_index); - if(!te) - { - return ; - } - - LL_DEBUGS("MediaOnAPrim") << "BEFORE: texture_index = " << texture_index - << " hasMedia = " << te->hasMedia() << " : " - << ((NULL == te->getMediaData()) ? "NULL MEDIA DATA" : ll_pretty_print_sd(te->getMediaData()->asLLSD())) << LL_ENDL; - - std::string previous_url; - LLMediaEntry* mep = te->getMediaData(); - if(mep) - { - // Save the "current url" from before the update so we can tell if - // it changes. - previous_url = mep->getCurrentURL(); - } - - if (merge) - { - te->mergeIntoMediaData(media_data); - } - else { - // XXX Question: what if the media data is undefined LLSD, but the - // update we got above said that we have media flags?? Here we clobber - // that, assuming the data from the service is more up-to-date. - te->updateMediaData(media_data); - } - - mep = te->getMediaData(); - if(mep) - { - bool update_from_self = false; - if (!ignore_agent) - { - LLUUID updating_agent = LLTextureEntry::getAgentIDFromMediaVersionString(getMediaURL()); - update_from_self = (updating_agent == gAgent.getID()); - } - viewer_media_t media_impl = LLViewerMedia::getInstance()->updateMediaImpl(mep, previous_url, update_from_self); - - addMediaImpl(media_impl, texture_index) ; - } - else - { - removeMediaImpl(texture_index); - } - - LL_DEBUGS("MediaOnAPrim") << "AFTER: texture_index = " << texture_index - << " hasMedia = " << te->hasMedia() << " : " - << ((NULL == te->getMediaData()) ? "NULL MEDIA DATA" : ll_pretty_print_sd(te->getMediaData()->asLLSD())) << LL_ENDL; -} - -void LLVOVolume::mediaNavigateBounceBack(U8 texture_index) -{ - // Find the media entry for this navigate - const LLMediaEntry* mep = NULL; - viewer_media_t impl = getMediaImpl(texture_index); - LLTextureEntry *te = getTE(texture_index); - if(te) - { - mep = te->getMediaData(); - } - - if (mep && impl) - { - std::string url = mep->getCurrentURL(); - // Look for a ":", if not there, assume "http://" - if (!url.empty() && std::string::npos == url.find(':')) - { - url = "http://" + url; - } - // If the url we're trying to "bounce back" to is either empty or not - // allowed by the whitelist, try the home url. If *that* doesn't work, - // set the media as failed and unload it - if (url.empty() || !mep->checkCandidateUrl(url)) - { - url = mep->getHomeURL(); - // Look for a ":", if not there, assume "http://" - if (!url.empty() && std::string::npos == url.find(':')) - { - url = "http://" + url; - } - } - if (url.empty() || !mep->checkCandidateUrl(url)) - { - // The url to navigate back to is not good, and we have nowhere else - // to go. - LL_WARNS("MediaOnAPrim") << "FAILED to bounce back URL \"" << url << "\" -- unloading impl" << LL_ENDL; - impl->setMediaFailed(true); - } - // Make sure we are not bouncing to url we came from - else if (impl->getCurrentMediaURL() != url) - { - // Okay, navigate now - LL_INFOS("MediaOnAPrim") << "bouncing back to URL: " << url << LL_ENDL; - impl->navigateTo(url, "", false, true); - } - } -} - -bool LLVOVolume::hasMediaPermission(const LLMediaEntry* media_entry, MediaPermType perm_type) -{ - // NOTE: This logic ALMOST duplicates the logic in the server (in particular, in llmediaservice.cpp). - if (NULL == media_entry ) return false; // XXX should we assert here? - - // The agent has permissions if: - // - world permissions are on, or - // - group permissions are on, and agent_id is in the group, or - // - agent permissions are on, and agent_id is the owner - - // *NOTE: We *used* to check for modify permissions here (i.e. permissions were - // granted if permModify() was true). However, this doesn't make sense in the - // viewer: we don't want to show controls or allow interaction if the author - // has deemed it so. See DEV-42115. - - U8 media_perms = (perm_type == MEDIA_PERM_INTERACT) ? media_entry->getPermsInteract() : media_entry->getPermsControl(); - - // World permissions - if (0 != (media_perms & LLMediaEntry::PERM_ANYONE)) - { - return true; - } - - // Group permissions - else if (0 != (media_perms & LLMediaEntry::PERM_GROUP)) - { - LLPermissions* obj_perm = LLSelectMgr::getInstance()->findObjectPermissions(this); - if (obj_perm && gAgent.isInGroup(obj_perm->getGroup())) - { - return true; - } - } - - // Owner permissions - else if (0 != (media_perms & LLMediaEntry::PERM_OWNER) && permYouOwner()) - { - return true; - } - - return false; - -} - -void LLVOVolume::mediaNavigated(LLViewerMediaImpl *impl, LLPluginClassMedia* plugin, std::string new_location) -{ - bool block_navigation = false; - // FIXME: if/when we allow the same media impl to be used by multiple faces, the logic here will need to be fixed - // to deal with multiple face indices. - int face_index = getFaceIndexWithMediaImpl(impl, -1); - - // Find the media entry for this navigate - LLMediaEntry* mep = NULL; - LLTextureEntry *te = getTE(face_index); - if(te) - { - mep = te->getMediaData(); - } - - if(mep) - { - if(!mep->checkCandidateUrl(new_location)) - { - block_navigation = true; - } - if (!block_navigation && !hasMediaPermission(mep, MEDIA_PERM_INTERACT)) - { - block_navigation = true; - } - } - else - { - LL_WARNS("MediaOnAPrim") << "Couldn't find media entry!" << LL_ENDL; - } - - if(block_navigation) - { - LL_INFOS("MediaOnAPrim") << "blocking navigate to URI " << new_location << LL_ENDL; - - // "bounce back" to the current URL from the media entry - mediaNavigateBounceBack(face_index); - } - else if (sObjectMediaNavigateClient) - { - - LL_DEBUGS("MediaOnAPrim") << "broadcasting navigate with URI " << new_location << LL_ENDL; - - sObjectMediaNavigateClient->navigate(new LLMediaDataClientObjectImpl(this, false), face_index, new_location); - } -} - -void LLVOVolume::mediaEvent(LLViewerMediaImpl *impl, LLPluginClassMedia* plugin, LLViewerMediaObserver::EMediaEvent event) -{ - switch(event) - { - - case LLViewerMediaObserver::MEDIA_EVENT_LOCATION_CHANGED: - { - switch(impl->getNavState()) - { - case LLViewerMediaImpl::MEDIANAVSTATE_FIRST_LOCATION_CHANGED: - { - // This is the first location changed event after the start of a non-server-directed nav. It may need to be broadcast or bounced back. - mediaNavigated(impl, plugin, plugin->getLocation()); - } - break; - - case LLViewerMediaImpl::MEDIANAVSTATE_FIRST_LOCATION_CHANGED_SPURIOUS: - // This navigate didn't change the current URL. - LL_DEBUGS("MediaOnAPrim") << " NOT broadcasting navigate (spurious)" << LL_ENDL; - break; - - case LLViewerMediaImpl::MEDIANAVSTATE_SERVER_FIRST_LOCATION_CHANGED: - // This is the first location changed event after the start of a server-directed nav. Don't broadcast it. - LL_INFOS("MediaOnAPrim") << " NOT broadcasting navigate (server-directed)" << LL_ENDL; - break; - - default: - // This is a subsequent location-changed due to a redirect. Don't broadcast. - LL_INFOS("MediaOnAPrim") << " NOT broadcasting navigate (redirect)" << LL_ENDL; - break; - } - } - break; - - case LLViewerMediaObserver::MEDIA_EVENT_NAVIGATE_COMPLETE: - { - switch(impl->getNavState()) - { - case LLViewerMediaImpl::MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED: - { - // This is the first location changed event after the start of a non-server-directed nav. It may need to be broadcast or bounced back. - mediaNavigated(impl, plugin, plugin->getNavigateURI()); - } - break; - - case LLViewerMediaImpl::MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED_SPURIOUS: - // This navigate didn't change the current URL. - LL_DEBUGS("MediaOnAPrim") << " NOT broadcasting navigate (spurious)" << LL_ENDL; - break; - - case LLViewerMediaImpl::MEDIANAVSTATE_SERVER_COMPLETE_BEFORE_LOCATION_CHANGED: - // This is the the navigate complete event from a server-directed nav. Don't broadcast it. - LL_INFOS("MediaOnAPrim") << " NOT broadcasting navigate (server-directed)" << LL_ENDL; - break; - - default: - // For all other states, the navigate should have been handled by LOCATION_CHANGED events already. - break; - } - } - break; - - case LLViewerMediaObserver::MEDIA_EVENT_FILE_DOWNLOAD: - { - // Media might be blocked, waiting for a file, - // send an empty response to unblock it - const std::vector empty_response; - plugin->sendPickFileResponse(empty_response); - - LLNotificationsUtil::add("MediaFileDownloadUnsupported"); - } - break; - - default: - break; - } - -} - -void LLVOVolume::sendMediaDataUpdate() -{ - if (sObjectMediaClient) - sObjectMediaClient->updateMedia(new LLMediaDataClientObjectImpl(this, false)); -} - -void LLVOVolume::removeMediaImpl(S32 texture_index) -{ - if(mMediaImplList.size() <= (U32)texture_index || mMediaImplList[texture_index].isNull()) - { - return ; - } - - //make the face referencing to mMediaImplList[texture_index] to point back to the old texture. - if(mDrawable && texture_index < mDrawable->getNumFaces()) - { - LLFace* facep = mDrawable->getFace(texture_index) ; - if(facep) - { - LLViewerMediaTexture* media_tex = LLViewerTextureManager::findMediaTexture(mMediaImplList[texture_index]->getMediaTextureID()) ; - if(media_tex) - { - media_tex->removeMediaFromFace(facep) ; - } - } - } - - //check if some other face(s) of this object reference(s)to this media impl. - S32 i ; - S32 end = (S32)mMediaImplList.size() ; - for(i = 0; i < end ; i++) - { - if( i != texture_index && mMediaImplList[i] == mMediaImplList[texture_index]) - { - break ; - } - } - - if(i == end) //this object does not need this media impl. - { - mMediaImplList[texture_index]->removeObject(this) ; - } - - mMediaImplList[texture_index] = NULL ; - return ; -} - -void LLVOVolume::addMediaImpl(LLViewerMediaImpl* media_impl, S32 texture_index) -{ - if((S32)mMediaImplList.size() < texture_index + 1) - { - mMediaImplList.resize(texture_index + 1) ; - } - - if(mMediaImplList[texture_index].notNull()) - { - if(mMediaImplList[texture_index] == media_impl) - { - return ; - } - - removeMediaImpl(texture_index) ; - } - - mMediaImplList[texture_index] = media_impl; - media_impl->addObject(this) ; - - //add the face to show the media if it is in playing - if(mDrawable) - { - LLFace* facep(NULL); - if( texture_index < mDrawable->getNumFaces() ) - { - facep = mDrawable->getFace(texture_index) ; - } - - if(facep) - { - LLViewerMediaTexture* media_tex = LLViewerTextureManager::findMediaTexture(mMediaImplList[texture_index]->getMediaTextureID()) ; - if(media_tex) - { - media_tex->addMediaToFace(facep) ; - } - } - else //the face is not available now, start media on this face later. - { - media_impl->setUpdated(true) ; - } - } - return ; -} - -viewer_media_t LLVOVolume::getMediaImpl(U8 face_id) const -{ - if(mMediaImplList.size() > face_id) - { - return mMediaImplList[face_id]; - } - return NULL; -} - -F64 LLVOVolume::getTotalMediaInterest() const -{ - // If this object is currently focused, this object has "high" interest - if (LLViewerMediaFocus::getInstance()->getFocusedObjectID() == getID()) - return F64_MAX; - - F64 interest = (F64)-1.0; // means not interested; - - // If this object is selected, this object has "high" interest, but since - // there can be more than one, we still add in calculated impl interest - // XXX Sadly, 'contains()' doesn't take a const :( - if (LLSelectMgr::getInstance()->getSelection()->contains(const_cast(this))) - interest = F64_MAX / 2.0; - - int i = 0; - const int end = getNumTEs(); - for ( ; i < end; ++i) - { - const viewer_media_t &impl = getMediaImpl(i); - if (!impl.isNull()) - { - if (interest == (F64)-1.0) interest = (F64)0.0; - interest += impl->getInterest(); - } - } - return interest; -} - -S32 LLVOVolume::getFaceIndexWithMediaImpl(const LLViewerMediaImpl* media_impl, S32 start_face_id) -{ - S32 end = (S32)mMediaImplList.size() ; - for(S32 face_id = start_face_id + 1; face_id < end; face_id++) - { - if(mMediaImplList[face_id] == media_impl) - { - return face_id ; - } - } - return -1 ; -} - -//---------------------------------------------------------------------------- - -void LLVOVolume::setLightTextureID(LLUUID id) -{ - LLViewerTexture* old_texturep = getLightTexture(); // same as mLightTexture, but inits if nessesary - if (id.notNull()) - { - if (!hasLightTexture()) - { - setParameterEntryInUse(LLNetworkData::PARAMS_LIGHT_IMAGE, true, true); - } - else if (old_texturep) - { - old_texturep->removeVolume(LLRender::LIGHT_TEX, this); - } - LLLightImageParams* param_block = (LLLightImageParams*) getParameterEntry(LLNetworkData::PARAMS_LIGHT_IMAGE); - if (param_block && param_block->getLightTexture() != id) - { - param_block->setLightTexture(id); - parameterChanged(LLNetworkData::PARAMS_LIGHT_IMAGE, true); - } - LLViewerTexture* tex = getLightTexture(); - if (tex) - { - tex->addVolume(LLRender::LIGHT_TEX, this); // new texture - } - else - { - LL_WARNS() << "Can't get light texture for ID " << id.asString() << LL_ENDL; - } - } - else if (hasLightTexture()) - { - if (old_texturep) - { - old_texturep->removeVolume(LLRender::LIGHT_TEX, this); - } - setParameterEntryInUse(LLNetworkData::PARAMS_LIGHT_IMAGE, false, true); - parameterChanged(LLNetworkData::PARAMS_LIGHT_IMAGE, true); - mLightTexture = NULL; - } -} - -void LLVOVolume::setSpotLightParams(LLVector3 params) -{ - LLLightImageParams* param_block = (LLLightImageParams*) getParameterEntry(LLNetworkData::PARAMS_LIGHT_IMAGE); - if (param_block && param_block->getParams() != params) - { - param_block->setParams(params); - parameterChanged(LLNetworkData::PARAMS_LIGHT_IMAGE, true); - } -} - -void LLVOVolume::setIsLight(bool is_light) -{ - bool was_light = getIsLight(); - if (is_light != was_light) - { - if (is_light) - { - setParameterEntryInUse(LLNetworkData::PARAMS_LIGHT, true, true); - } - else - { - setParameterEntryInUse(LLNetworkData::PARAMS_LIGHT, false, true); - } - - if (is_light) - { - // Add it to the pipeline mLightSet - gPipeline.setLight(mDrawable, true); - } - else - { - // Not a light. Remove it from the pipeline's light set. - gPipeline.setLight(mDrawable, false); - } - } -} - -void LLVOVolume::setLightSRGBColor(const LLColor3& color) -{ - setLightLinearColor(linearColor3(color)); -} - -void LLVOVolume::setLightLinearColor(const LLColor3& color) -{ - LLLightParams *param_block = (LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); - if (param_block) - { - if (param_block->getLinearColor() != color) - { - param_block->setLinearColor(LLColor4(color, param_block->getLinearColor().mV[3])); - parameterChanged(LLNetworkData::PARAMS_LIGHT, true); - gPipeline.markTextured(mDrawable); - mFaceMappingChanged = true; - } - } -} - -void LLVOVolume::setLightIntensity(F32 intensity) -{ - LLLightParams *param_block = (LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); - if (param_block) - { - if (param_block->getLinearColor().mV[3] != intensity) - { - param_block->setLinearColor(LLColor4(LLColor3(param_block->getLinearColor()), intensity)); - parameterChanged(LLNetworkData::PARAMS_LIGHT, true); - } - } -} - -void LLVOVolume::setLightRadius(F32 radius) -{ - LLLightParams *param_block = (LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); - if (param_block) - { - if (param_block->getRadius() != radius) - { - param_block->setRadius(radius); - parameterChanged(LLNetworkData::PARAMS_LIGHT, true); - } - } -} - -void LLVOVolume::setLightFalloff(F32 falloff) -{ - LLLightParams *param_block = (LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); - if (param_block) - { - if (param_block->getFalloff() != falloff) - { - param_block->setFalloff(falloff); - parameterChanged(LLNetworkData::PARAMS_LIGHT, true); - } - } -} - -void LLVOVolume::setLightCutoff(F32 cutoff) -{ - LLLightParams *param_block = (LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); - if (param_block) - { - if (param_block->getCutoff() != cutoff) - { - param_block->setCutoff(cutoff); - parameterChanged(LLNetworkData::PARAMS_LIGHT, true); - } - } -} - -//---------------------------------------------------------------------------- - -bool LLVOVolume::getIsLight() const -{ - mIsLight = getParameterEntryInUse(LLNetworkData::PARAMS_LIGHT); - return mIsLight; -} - -bool LLVOVolume::getIsLightFast() const -{ - return mIsLight; -} - -LLColor3 LLVOVolume::getLightSRGBBaseColor() const -{ - return srgbColor3(getLightLinearBaseColor()); -} - -LLColor3 LLVOVolume::getLightLinearBaseColor() const -{ - const LLLightParams *param_block = (const LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); - if (param_block) - { - return LLColor3(param_block->getLinearColor()); - } - else - { - return LLColor3(1,1,1); - } -} - -LLColor3 LLVOVolume::getLightLinearColor() const -{ - const LLLightParams *param_block = (const LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); - if (param_block) - { - return LLColor3(param_block->getLinearColor()) * param_block->getLinearColor().mV[3]; - } - else - { - return LLColor3(1, 1, 1); - } -} - -LLColor3 LLVOVolume::getLightSRGBColor() const -{ - LLColor3 ret = getLightLinearColor(); - ret = srgbColor3(ret); - return ret; -} - -LLUUID LLVOVolume::getLightTextureID() const -{ - if (getParameterEntryInUse(LLNetworkData::PARAMS_LIGHT_IMAGE)) - { - const LLLightImageParams *param_block = (const LLLightImageParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT_IMAGE); - if (param_block) - { - return param_block->getLightTexture(); - } - } - - return LLUUID::null; -} - - -LLVector3 LLVOVolume::getSpotLightParams() const -{ - if (getParameterEntryInUse(LLNetworkData::PARAMS_LIGHT_IMAGE)) - { - const LLLightImageParams *param_block = (const LLLightImageParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT_IMAGE); - if (param_block) - { - return param_block->getParams(); - } - } - - return LLVector3(); -} - -F32 LLVOVolume::getSpotLightPriority() const -{ - return mSpotLightPriority; -} - -void LLVOVolume::updateSpotLightPriority() -{ - if (gCubeSnapshot) - { - return; - } - - F32 r = getLightRadius(); - LLVector3 pos = mDrawable->getPositionAgent(); - - LLVector3 at(0,0,-1); - at *= getRenderRotation(); - pos += at * r; - - at = LLViewerCamera::getInstance()->getAtAxis(); - pos -= at * r; - - mSpotLightPriority = gPipeline.calcPixelArea(pos, LLVector3(r,r,r), *LLViewerCamera::getInstance()); - - if (mLightTexture.notNull()) - { - mLightTexture->addTextureStats(mSpotLightPriority); - } -} - - -bool LLVOVolume::isLightSpotlight() const -{ - LLLightImageParams* params = (LLLightImageParams*) getParameterEntry(LLNetworkData::PARAMS_LIGHT_IMAGE); - if (params && getParameterEntryInUse(LLNetworkData::PARAMS_LIGHT_IMAGE)) - { - return params->isLightSpotlight(); - } - return false; -} - - -LLViewerTexture* LLVOVolume::getLightTexture() -{ - LLUUID id = getLightTextureID(); - - if (id.notNull()) - { - if (mLightTexture.isNull() || id != mLightTexture->getID()) - { - mLightTexture = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE); - } - } - else - { - mLightTexture = NULL; - } - - return mLightTexture; -} - -F32 LLVOVolume::getLightIntensity() const -{ - const LLLightParams *param_block = (const LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); - if (param_block) - { - return param_block->getLinearColor().mV[3]; - } - else - { - return 1.f; - } -} - -F32 LLVOVolume::getLightRadius() const -{ - const LLLightParams *param_block = (const LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); - if (param_block) - { - return param_block->getRadius(); - } - else - { - return 0.f; - } -} - -F32 LLVOVolume::getLightFalloff(const F32 fudge_factor) const -{ - const LLLightParams *param_block = (const LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); - if (param_block) - { - return param_block->getFalloff() * fudge_factor; - } - else - { - return 0.f; - } -} - -F32 LLVOVolume::getLightCutoff() const -{ - const LLLightParams *param_block = (const LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); - if (param_block) - { - return param_block->getCutoff(); - } - else - { - return 0.f; - } -} - -bool LLVOVolume::isReflectionProbe() const -{ - return getParameterEntryInUse(LLNetworkData::PARAMS_REFLECTION_PROBE); -} - -bool LLVOVolume::setIsReflectionProbe(bool is_probe) -{ - bool was_probe = isReflectionProbe(); - if (is_probe != was_probe) - { - if (is_probe) - { - setParameterEntryInUse(LLNetworkData::PARAMS_REFLECTION_PROBE, true, true); - } - else - { - setParameterEntryInUse(LLNetworkData::PARAMS_REFLECTION_PROBE, false, true); - } - } - - updateReflectionProbePtr(); - - return was_probe != is_probe; -} - -bool LLVOVolume::setReflectionProbeAmbiance(F32 ambiance) -{ - LLReflectionProbeParams* param_block = (LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE); - if (param_block) - { - if (param_block->getAmbiance() != ambiance) - { - param_block->setAmbiance(ambiance); - parameterChanged(LLNetworkData::PARAMS_REFLECTION_PROBE, true); - return true; - } - } - - return false; -} - -bool LLVOVolume::setReflectionProbeNearClip(F32 near_clip) -{ - LLReflectionProbeParams* param_block = (LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE); - if (param_block) - { - if (param_block->getClipDistance() != near_clip) - { - param_block->setClipDistance(near_clip); - parameterChanged(LLNetworkData::PARAMS_REFLECTION_PROBE, true); - return true; - } - } - - return false; -} - -bool LLVOVolume::setReflectionProbeIsBox(bool is_box) -{ - LLReflectionProbeParams* param_block = (LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE); - if (param_block) - { - if (param_block->getIsBox() != is_box) - { - param_block->setIsBox(is_box); - parameterChanged(LLNetworkData::PARAMS_REFLECTION_PROBE, true); - return true; - } - } - - return false; -} - -bool LLVOVolume::setReflectionProbeIsDynamic(bool is_dynamic) -{ - LLReflectionProbeParams* param_block = (LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE); - if (param_block) - { - if (param_block->getIsDynamic() != is_dynamic) - { - param_block->setIsDynamic(is_dynamic); - parameterChanged(LLNetworkData::PARAMS_REFLECTION_PROBE, true); - return true; - } - } - - return false; -} - -F32 LLVOVolume::getReflectionProbeAmbiance() const -{ - const LLReflectionProbeParams* param_block = (const LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE); - if (param_block) - { - return param_block->getAmbiance(); - } - else - { - return 0.f; - } -} - -F32 LLVOVolume::getReflectionProbeNearClip() const -{ - const LLReflectionProbeParams* param_block = (const LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE); - if (param_block) - { - return param_block->getClipDistance(); - } - else - { - return 0.f; - } -} - -bool LLVOVolume::getReflectionProbeIsBox() const -{ - const LLReflectionProbeParams* param_block = (const LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE); - if (param_block) - { - return param_block->getIsBox(); - } - - return false; -} - -bool LLVOVolume::getReflectionProbeIsDynamic() const -{ - const LLReflectionProbeParams* param_block = (const LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE); - if (param_block) - { - return param_block->getIsDynamic(); - } - - return false; -} - -U32 LLVOVolume::getVolumeInterfaceID() const -{ - if (mVolumeImpl) - { - return mVolumeImpl->getID(); - } - - return 0; -} - -bool LLVOVolume::isFlexible() const -{ - if (getParameterEntryInUse(LLNetworkData::PARAMS_FLEXIBLE)) - { - LLVolume* volume = getVolume(); - if (volume && volume->getParams().getPathParams().getCurveType() != LL_PCODE_PATH_FLEXIBLE) - { - LLVolumeParams volume_params = getVolume()->getParams(); - U8 profile_and_hole = volume_params.getProfileParams().getCurveType(); - volume_params.setType(profile_and_hole, LL_PCODE_PATH_FLEXIBLE); - } - return true; - } - else - { - return false; - } -} - -bool LLVOVolume::isSculpted() const -{ - if (getParameterEntryInUse(LLNetworkData::PARAMS_SCULPT)) - { - return true; - } - - return false; -} - -bool LLVOVolume::isMesh() const -{ - if (isSculpted()) - { - LLSculptParams *sculpt_params = (LLSculptParams *)getParameterEntry(LLNetworkData::PARAMS_SCULPT); - U8 sculpt_type = sculpt_params->getSculptType(); - - if ((sculpt_type & LL_SCULPT_TYPE_MASK) == LL_SCULPT_TYPE_MESH) - // mesh is a mesh - { - return true; - } - } - - return false; -} - -bool LLVOVolume::hasLightTexture() const -{ - if (getParameterEntryInUse(LLNetworkData::PARAMS_LIGHT_IMAGE)) - { - return true; - } - - return false; -} - -bool LLVOVolume::isFlexibleFast() const -{ - return mVolumep && mVolumep->getParams().getPathParams().getCurveType() == LL_PCODE_PATH_FLEXIBLE; -} - -bool LLVOVolume::isSculptedFast() const -{ - return mVolumep && mVolumep->getParams().isSculpt(); -} - -bool LLVOVolume::isMeshFast() const -{ - return mVolumep && mVolumep->getParams().isMeshSculpt(); -} - -bool LLVOVolume::isRiggedMeshFast() const -{ - return mSkinInfo.notNull(); -} - -bool LLVOVolume::isAnimatedObjectFast() const -{ - return mIsAnimatedObject; -} - -bool LLVOVolume::isVolumeGlobal() const -{ - if (mVolumeImpl) - { - return mVolumeImpl->isVolumeGlobal(); - } - - if (mRiggedVolume.notNull()) - { - return true; - } - - return false; -} - -bool LLVOVolume::canBeFlexible() const -{ - U8 path = getVolume()->getParams().getPathParams().getCurveType(); - return (path == LL_PCODE_PATH_FLEXIBLE || path == LL_PCODE_PATH_LINE); -} - -bool LLVOVolume::setIsFlexible(bool is_flexible) -{ - bool res = false; - bool was_flexible = isFlexible(); - LLVolumeParams volume_params; - if (is_flexible) - { - if (!was_flexible) - { - volume_params = getVolume()->getParams(); - U8 profile_and_hole = volume_params.getProfileParams().getCurveType(); - volume_params.setType(profile_and_hole, LL_PCODE_PATH_FLEXIBLE); - res = true; - setFlags(FLAGS_USE_PHYSICS, false); - setFlags(FLAGS_PHANTOM, true); - setParameterEntryInUse(LLNetworkData::PARAMS_FLEXIBLE, true, true); - if (mDrawable) - { - mDrawable->makeActive(); - } - } - } - else - { - if (was_flexible) - { - volume_params = getVolume()->getParams(); - U8 profile_and_hole = volume_params.getProfileParams().getCurveType(); - volume_params.setType(profile_and_hole, LL_PCODE_PATH_LINE); - res = true; - setFlags(FLAGS_PHANTOM, false); - setParameterEntryInUse(LLNetworkData::PARAMS_FLEXIBLE, false, true); - } - } - if (res) - { - res = setVolume(volume_params, 1); - if (res) - { - markForUpdate(); - } - } - return res; -} - -const LLMeshSkinInfo* LLVOVolume::getSkinInfo() const -{ - if (getVolume()) - { - return mSkinInfo; - } - else - { - return NULL; - } -} - -// virtual -bool LLVOVolume::isRiggedMesh() const -{ - return isMesh() && getSkinInfo(); -} - -//---------------------------------------------------------------------------- -U32 LLVOVolume::getExtendedMeshFlags() const -{ - const LLExtendedMeshParams *param_block = - (const LLExtendedMeshParams *)getParameterEntry(LLNetworkData::PARAMS_EXTENDED_MESH); - if (param_block) - { - return param_block->getFlags(); - } - else - { - return 0; - } -} - -void LLVOVolume::onSetExtendedMeshFlags(U32 flags) -{ - - // The isAnySelected() check was needed at one point to prevent - // graphics problems. These are now believed to be fixed so the - // check has been disabled. - if (/*!getRootEdit()->isAnySelected() &&*/ mDrawable.notNull()) - { - // Need to trigger rebuildGeom(), which is where control avatars get created/removed - getRootEdit()->recursiveMarkForUpdate(); - } - if (isAttachment() && getAvatarAncestor()) - { - updateVisualComplexity(); - if (flags & LLExtendedMeshParams::ANIMATED_MESH_ENABLED_FLAG) - { - // Making a rigged mesh into an animated object - getAvatarAncestor()->updateAttachmentOverrides(); - } - else - { - // Making an animated object into a rigged mesh - getAvatarAncestor()->updateAttachmentOverrides(); - } - } -} - -void LLVOVolume::setExtendedMeshFlags(U32 flags) -{ - U32 curr_flags = getExtendedMeshFlags(); - if (curr_flags != flags) - { - bool in_use = true; - setParameterEntryInUse(LLNetworkData::PARAMS_EXTENDED_MESH, in_use, true); - LLExtendedMeshParams *param_block = - (LLExtendedMeshParams *)getParameterEntry(LLNetworkData::PARAMS_EXTENDED_MESH); - if (param_block) - { - param_block->setFlags(flags); - } - parameterChanged(LLNetworkData::PARAMS_EXTENDED_MESH, true); - LL_DEBUGS("AnimatedObjects") << this - << " new flags " << flags << " curr_flags " << curr_flags - << ", calling onSetExtendedMeshFlags()" - << LL_ENDL; - onSetExtendedMeshFlags(flags); - } -} - -bool LLVOVolume::canBeAnimatedObject() const -{ - F32 est_tris = recursiveGetEstTrianglesMax(); - if (est_tris < 0 || est_tris > getAnimatedObjectMaxTris()) - { - return false; - } - return true; -} - -bool LLVOVolume::isAnimatedObject() const -{ - LLVOVolume *root_vol = (LLVOVolume*)getRootEdit(); - mIsAnimatedObject = root_vol->getExtendedMeshFlags() & LLExtendedMeshParams::ANIMATED_MESH_ENABLED_FLAG; - return mIsAnimatedObject; -} - -// Called any time parenting changes for a volume. Update flags and -// control av accordingly. This is called after parent has been -// changed to new_parent, but before new_parent's mChildList has changed. - -// virtual -void LLVOVolume::onReparent(LLViewerObject *old_parent, LLViewerObject *new_parent) -{ - LLVOVolume *old_volp = dynamic_cast(old_parent); - - if (new_parent && !new_parent->isAvatar()) - { - if (mControlAvatar.notNull()) - { - // Here an animated object is being made the child of some - // other prim. Should remove the control av from the child. - LLControlAvatar *av = mControlAvatar; - mControlAvatar = NULL; - av->markForDeath(); - } - } - if (old_volp && old_volp->isAnimatedObject()) - { - if (old_volp->getControlAvatar()) - { - // We have been removed from an animated object, need to do cleanup. - old_volp->getControlAvatar()->updateAttachmentOverrides(); - old_volp->getControlAvatar()->updateAnimations(); - } - } -} - -// This needs to be called after onReparent(), because mChildList is -// not updated until the end of LLViewerObject::addChild() - -// virtual -void LLVOVolume::afterReparent() -{ - { - LL_DEBUGS("AnimatedObjects") << "new child added for parent " - << ((LLViewerObject*)getParent())->getID() << LL_ENDL; - } - - if (isAnimatedObject() && getControlAvatar()) - { - LL_DEBUGS("AnimatedObjects") << "adding attachment overrides, parent is animated object " - << ((LLViewerObject*)getParent())->getID() << LL_ENDL; - - // MAINT-8239 - doing a full rebuild whenever parent is set - // makes the joint overrides load more robustly. In theory, - // addAttachmentOverrides should be sufficient, but in - // practice doing a full rebuild helps compensate for - // notifyMeshLoaded() not being called reliably enough. - - // was: getControlAvatar()->addAttachmentOverridesForObject(this); - //getControlAvatar()->rebuildAttachmentOverrides(); - getControlAvatar()->updateAnimations(); - } - else - { - LL_DEBUGS("AnimatedObjects") << "not adding overrides, parent: " - << ((LLViewerObject*)getParent())->getID() - << " isAnimated: " << isAnimatedObject() << " cav " - << getControlAvatar() << LL_ENDL; - } -} - -//---------------------------------------------------------------------------- -void LLVOVolume::updateRiggingInfo() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - if (isRiggedMesh()) - { - const LLMeshSkinInfo* skin = getSkinInfo(); - LLVOAvatar *avatar = getAvatar(); - LLVolume *volume = getVolume(); - if (skin && avatar && volume) - { - LL_DEBUGS("RigSpammish") << "starting, vovol " << this << " lod " << getLOD() << " last " << mLastRiggingInfoLOD << LL_ENDL; - if (getLOD()>mLastRiggingInfoLOD || getLOD()==3) - { - // Rigging info may need update - mJointRiggingInfoTab.clear(); - for (S32 f = 0; f < volume->getNumVolumeFaces(); ++f) - { - LLVolumeFace& vol_face = volume->getVolumeFace(f); - LLSkinningUtil::updateRiggingInfo(skin, avatar, vol_face); - if (vol_face.mJointRiggingInfoTab.size()>0) - { - mJointRiggingInfoTab.merge(vol_face.mJointRiggingInfoTab); - } - } - // Keep the highest LOD info available. - mLastRiggingInfoLOD = getLOD(); - LL_DEBUGS("RigSpammish") << "updated rigging info for LLVOVolume " - << this << " lod " << mLastRiggingInfoLOD - << LL_ENDL; - } - } - } -} - -//---------------------------------------------------------------------------- - -void LLVOVolume::generateSilhouette(LLSelectNode* nodep, const LLVector3& view_point) -{ - LLVolume *volume = getVolume(); - - if (volume) - { - LLVector3 view_vector; - view_vector = view_point; - - //transform view vector into volume space - view_vector -= getRenderPosition(); - //mDrawable->mDistanceWRTCamera = view_vector.length(); - LLQuaternion worldRot = getRenderRotation(); - view_vector = view_vector * ~worldRot; - if (!isVolumeGlobal()) - { - LLVector3 objScale = getScale(); - LLVector3 invObjScale(1.f / objScale.mV[VX], 1.f / objScale.mV[VY], 1.f / objScale.mV[VZ]); - view_vector.scaleVec(invObjScale); - } - - updateRelativeXform(); - LLMatrix4 trans_mat = mRelativeXform; - if (mDrawable->isStatic()) - { - trans_mat.translate(getRegion()->getOriginAgent()); - } - - volume->generateSilhouetteVertices(nodep->mSilhouetteVertices, nodep->mSilhouetteNormals, view_vector, trans_mat, mRelativeXformInvTrans, nodep->getTESelectMask()); - - nodep->mSilhouetteExists = true; - } -} - -void LLVOVolume::deleteFaces() -{ - S32 face_count = mNumFaces; - if (mDrawable.notNull()) - { - mDrawable->deleteFaces(0, face_count); - } - - mNumFaces = 0; -} - -void LLVOVolume::updateRadius() -{ - if (mDrawable.isNull()) - { - return; - } - - mVObjRadius = getScale().length(); - mDrawable->setRadius(mVObjRadius); -} - - -bool LLVOVolume::isAttachment() const -{ - return mAttachmentState != 0 ; -} - -bool LLVOVolume::isHUDAttachment() const -{ - // *NOTE: we assume hud attachment points are in defined range - // since this range is constant for backwards compatibility - // reasons this is probably a reasonable assumption to make - S32 attachment_id = ATTACHMENT_ID_FROM_STATE(mAttachmentState); - return ( attachment_id >= 31 && attachment_id <= 38 ); -} - - -const LLMatrix4 LLVOVolume::getRenderMatrix() const -{ - if (mDrawable->isActive() && !mDrawable->isRoot()) - { - return mDrawable->getParent()->getWorldMatrix(); - } - return mDrawable->getWorldMatrix(); -} - -//static -S32 LLVOVolume::getTextureCost(const LLViewerTexture* img) -{ - static const U32 ARC_TEXTURE_COST = 16; // multiplier for texture resolution - performance tested - - S32 texture_cost = 0; - S8 type = img->getType(); - if (type == LLViewerTexture::FETCHED_TEXTURE || type == LLViewerTexture::LOD_TEXTURE) - { - const LLViewerFetchedTexture* fetched_texturep = static_cast(img); - if (fetched_texturep - && fetched_texturep->getFTType() == FTT_LOCAL_FILE - && (img->getID() == IMG_ALPHA_GRAD_2D || img->getID() == IMG_ALPHA_GRAD) - ) - { - // These two textures appear to switch between each other, but are of different sizes (4x256 and 256x256). - // Hardcode cost from larger one to not cause random complexity changes - texture_cost = 320; - } - } - if (texture_cost == 0) - { - texture_cost = 256 + (S32)(ARC_TEXTURE_COST * (img->getFullHeight() / 128.f + img->getFullWidth() / 128.f)); - } - - return texture_cost; -} - -// Returns a base cost and adds textures to passed in set. -// total cost is returned value + 5 * size of the resulting set. -// Cannot include cost of textures, as they may be re-used in linked -// children, and cost should only be increased for unique textures -Nyx -U32 LLVOVolume::getRenderCost(texture_cost_t &textures) const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - /***************************************************************** - * This calculation should not be modified by third party viewers, - * since it is used to limit rendering and should be uniform for - * everyone. If you have suggested improvements, submit them to - * the official viewer for consideration. - *****************************************************************/ - - // Get access to params we'll need at various points. - // Skip if this is object doesn't have a volume (e.g. is an avatar). - if (getVolume() == NULL) - { - return 0; - } - - U32 num_triangles = 0; - - // per-prim costs - static const U32 ARC_PARTICLE_COST = 1; // determined experimentally - static const U32 ARC_PARTICLE_MAX = 2048; // default values - static const U32 ARC_LIGHT_COST = 500; // static cost for light-producing prims - static const U32 ARC_MEDIA_FACE_COST = 1500; // static cost per media-enabled face - - - // per-prim multipliers - static const F32 ARC_GLOW_MULT = 1.5f; // tested based on performance - static const F32 ARC_BUMP_MULT = 1.25f; // tested based on performance - static const F32 ARC_FLEXI_MULT = 5; // tested based on performance - static const F32 ARC_SHINY_MULT = 1.6f; // tested based on performance - static const F32 ARC_INVISI_COST = 1.2f; // tested based on performance - static const F32 ARC_WEIGHTED_MESH = 1.2f; // tested based on performance - - static const F32 ARC_PLANAR_COST = 1.0f; // tested based on performance to have negligible impact - static const F32 ARC_ANIM_TEX_COST = 4.f; // tested based on performance - static const F32 ARC_ALPHA_COST = 4.f; // 4x max - based on performance - - F32 shame = 0; - - U32 invisi = 0; - U32 shiny = 0; - U32 glow = 0; - U32 alpha = 0; - U32 flexi = 0; - U32 animtex = 0; - U32 particles = 0; - U32 bump = 0; - U32 planar = 0; - U32 weighted_mesh = 0; - U32 produces_light = 0; - U32 media_faces = 0; - - const LLDrawable* drawablep = mDrawable; - U32 num_faces = drawablep->getNumFaces(); - - const LLVolumeParams& volume_params = getVolume()->getParams(); - - LLMeshCostData costs; - if (getCostData(costs)) - { - if (isAnimatedObjectFast() && isRiggedMeshFast()) - { - // Scaling here is to make animated object vs - // non-animated object ARC proportional to the - // corresponding calculations for streaming cost. - num_triangles = (ANIMATED_OBJECT_COST_PER_KTRI * 0.001 * costs.getEstTrisForStreamingCost())/0.06; - } - else - { - F32 radius = getScale().length()*0.5f; - num_triangles = costs.getRadiusWeightedTris(radius); - } - } - - - if (num_triangles <= 0) - { - num_triangles = 4; - } - - if (isSculptedFast()) - { - if (isMeshFast()) - { - // base cost is dependent on mesh complexity - // note that 3 is the highest LOD as of the time of this coding. - S32 size = gMeshRepo.getMeshSize(volume_params.getSculptID(), getLOD()); - if ( size > 0) - { - if (isRiggedMeshFast()) - { - // weighted attachment - 1 point for every 3 bytes - weighted_mesh = 1; - } - } - else - { - // something went wrong - user should know their content isn't render-free - return 0; - } - } - else - { - LLViewerFetchedTexture* texture = mSculptTexture; - if (texture && textures.find(texture) == textures.end()) - { - textures.insert(texture); - } - } - } - - if (isFlexibleFast()) - { - flexi = 1; - } - if (isParticleSource()) - { - particles = 1; - } - - if (getIsLightFast()) - { - produces_light = 1; - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_VOLUME("ARC - face list"); - for (S32 i = 0; i < num_faces; ++i) - { - const LLFace* face = drawablep->getFace(i); - if (!face) continue; - const LLTextureEntry* te = face->getTextureEntry(); - const LLViewerTexture* img = face->getTexture(); - - if (img) - { - textures.insert(img); - } - - if (face->isInAlphaPool()) - { - alpha = 1; - } - else if (img && img->getPrimaryFormat() == GL_ALPHA) - { - invisi = 1; - } - if (face->hasMedia()) - { - media_faces++; - } - - if (te) - { - if (te->getBumpmap()) - { - // bump is a multiplier, don't add per-face - bump = 1; - } - if (te->getShiny()) - { - // shiny is a multiplier, don't add per-face - shiny = 1; - } - if (te->getGlow() > 0.f) - { - // glow is a multiplier, don't add per-face - glow = 1; - } - if (face->mTextureMatrix != NULL) - { - animtex = 1; - } - if (te->getTexGen()) - { - planar = 1; - } - } - } - } - - // shame currently has the "base" cost of 1 point per 15 triangles, min 2. - shame = num_triangles * 5.f; - shame = shame < 2.f ? 2.f : shame; - - // multiply by per-face modifiers - if (planar) - { - shame *= planar * ARC_PLANAR_COST; - } - - if (animtex) - { - shame *= animtex * ARC_ANIM_TEX_COST; - } - - if (alpha) - { - shame *= alpha * ARC_ALPHA_COST; - } - - if(invisi) - { - shame *= invisi * ARC_INVISI_COST; - } - - if (glow) - { - shame *= glow * ARC_GLOW_MULT; - } - - if (bump) - { - shame *= bump * ARC_BUMP_MULT; - } - - if (shiny) - { - shame *= shiny * ARC_SHINY_MULT; - } - - - // multiply shame by multipliers - if (weighted_mesh) - { - shame *= weighted_mesh * ARC_WEIGHTED_MESH; - } - - if (flexi) - { - shame *= flexi * ARC_FLEXI_MULT; - } - - - // add additional costs - if (particles) - { - const LLPartSysData *part_sys_data = &(mPartSourcep->mPartSysData); - const LLPartData *part_data = &(part_sys_data->mPartData); - U32 num_particles = (U32)(part_sys_data->mBurstPartCount * llceil( part_data->mMaxAge / part_sys_data->mBurstRate)); - num_particles = num_particles > ARC_PARTICLE_MAX ? ARC_PARTICLE_MAX : num_particles; - F32 part_size = (llmax(part_data->mStartScale[0], part_data->mEndScale[0]) + llmax(part_data->mStartScale[1], part_data->mEndScale[1])) / 2.f; - shame += num_particles * part_size * ARC_PARTICLE_COST; - } - - if (produces_light) - { - shame += ARC_LIGHT_COST; - } - - if (media_faces) - { - shame += media_faces * ARC_MEDIA_FACE_COST; - } - - // Streaming cost for animated objects includes a fixed cost - // per linkset. Add a corresponding charge here translated into - // triangles, but not weighted by any graphics properties. - if (isAnimatedObjectFast() && isRootEdit()) - { - shame += (ANIMATED_OBJECT_BASE_COST/0.06) * 5.0f; - } - - if (shame > mRenderComplexity_current) - { - mRenderComplexity_current = (S32)shame; - } - - return (U32)shame; -} - -F32 LLVOVolume::getEstTrianglesMax() const -{ - if (isMeshFast() && getVolume()) - { - return gMeshRepo.getEstTrianglesMax(getVolume()->getParams().getSculptID()); - } - return 0.f; -} - -F32 LLVOVolume::getEstTrianglesStreamingCost() const -{ - if (isMeshFast() && getVolume()) - { - return gMeshRepo.getEstTrianglesStreamingCost(getVolume()->getParams().getSculptID()); - } - return 0.f; -} - -F32 LLVOVolume::getStreamingCost() const -{ - F32 radius = getScale().length()*0.5f; - F32 linkset_base_cost = 0.f; - - LLMeshCostData costs; - if (getCostData(costs)) - { - if (isRootEdit() && isAnimatedObject()) - { - // Root object of an animated object has this to account for skeleton overhead. - linkset_base_cost = ANIMATED_OBJECT_BASE_COST; - } - if (isMesh()) - { - if (isAnimatedObject() && isRiggedMesh()) - { - return linkset_base_cost + costs.getTriangleBasedStreamingCost(); - } - else - { - return linkset_base_cost + costs.getRadiusBasedStreamingCost(radius); - } - } - else - { - return linkset_base_cost + costs.getRadiusBasedStreamingCost(radius); - } - } - else - { - return 0.f; - } -} - -// virtual -bool LLVOVolume::getCostData(LLMeshCostData& costs) const -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - - if (isMeshFast()) - { - return gMeshRepo.getCostData(getVolume()->getParams().getSculptID(), costs); - } - else - { - LLVolume* volume = getVolume(); - S32 counts[4]; - LLVolume::getLoDTriangleCounts(volume->getParams(), counts); - - LLMeshHeader header; - header.mLodSize[0] = counts[0] * 10; - header.mLodSize[1] = counts[1] * 10; - header.mLodSize[2] = counts[2] * 10; - header.mLodSize[3] = counts[3] * 10; - - return gMeshRepo.getCostData(header, costs); - } -} - -//static -void LLVOVolume::updateRenderComplexity() -{ - mRenderComplexity_last = mRenderComplexity_current; - mRenderComplexity_current = 0; -} - -U32 LLVOVolume::getTriangleCount(S32* vcount) const -{ - U32 count = 0; - LLVolume* volume = getVolume(); - if (volume) - { - count = volume->getNumTriangles(vcount); - } - - return count; -} - -U32 LLVOVolume::getHighLODTriangleCount() -{ - U32 ret = 0; - - LLVolume* volume = getVolume(); - - if (!isSculpted()) - { - LLVolume* ref = LLPrimitive::getVolumeManager()->refVolume(volume->getParams(), 3); - ret = ref->getNumTriangles(); - LLPrimitive::getVolumeManager()->unrefVolume(ref); - } - else if (isMesh()) - { - LLVolume* ref = LLPrimitive::getVolumeManager()->refVolume(volume->getParams(), 3); - if (!ref->isMeshAssetLoaded() || ref->getNumVolumeFaces() == 0) - { - gMeshRepo.loadMesh(this, volume->getParams(), LLModel::LOD_HIGH); - } - ret = ref->getNumTriangles(); - LLPrimitive::getVolumeManager()->unrefVolume(ref); - } - else - { //default sculpts have a constant number of triangles - ret = 31*2*31; //31 rows of 31 columns of quads for a 32x32 vertex patch - } - - return ret; -} - -//static -void LLVOVolume::preUpdateGeom() -{ - sNumLODChanges = 0; -} - -void LLVOVolume::parameterChanged(U16 param_type, bool local_origin) -{ - LLViewerObject::parameterChanged(param_type, local_origin); -} - -void LLVOVolume::parameterChanged(U16 param_type, LLNetworkData* data, bool in_use, bool local_origin) -{ - LLViewerObject::parameterChanged(param_type, data, in_use, local_origin); - if (mVolumeImpl) - { - mVolumeImpl->onParameterChanged(param_type, data, in_use, local_origin); - } - if (!local_origin && param_type == LLNetworkData::PARAMS_EXTENDED_MESH) - { - U32 extended_mesh_flags = getExtendedMeshFlags(); - bool enabled = (extended_mesh_flags & LLExtendedMeshParams::ANIMATED_MESH_ENABLED_FLAG); - bool was_enabled = (getControlAvatar() != NULL); - if (enabled != was_enabled) - { - LL_DEBUGS("AnimatedObjects") << this - << " calling onSetExtendedMeshFlags, enabled " << (U32) enabled - << " was_enabled " << (U32) was_enabled - << " local_origin " << (U32) local_origin - << LL_ENDL; - onSetExtendedMeshFlags(extended_mesh_flags); - } - } - if (mDrawable.notNull()) - { - bool is_light = getIsLight(); - if (is_light != mDrawable->isState(LLDrawable::LIGHT)) - { - gPipeline.setLight(mDrawable, is_light); - } - } - - updateReflectionProbePtr(); -} - -void LLVOVolume::updateReflectionProbePtr() -{ - if (isReflectionProbe()) - { - if (mReflectionProbe.isNull()) - { - mReflectionProbe = gPipeline.mReflectionMapManager.registerViewerObject(this); - } - } - else if (mReflectionProbe.notNull()) - { - mReflectionProbe = nullptr; - } -} - -void LLVOVolume::setSelected(bool sel) -{ - LLViewerObject::setSelected(sel); - if (isAnimatedObject()) - { - getRootEdit()->recursiveMarkForUpdate(); - } - else - { - if (mDrawable.notNull()) - { - markForUpdate(); - } - } -} - -void LLVOVolume::updateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax) -{ -} - -F32 LLVOVolume::getBinRadius() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - F32 radius; - - static LLCachedControl octree_size_factor(gSavedSettings, "OctreeStaticObjectSizeFactor", 3); - static LLCachedControl octree_attachment_size_factor(gSavedSettings, "OctreeAttachmentSizeFactor", 4); - static LLCachedControl octree_distance_factor(gSavedSettings, "OctreeDistanceFactor", LLVector3(0.01f, 0.f, 0.f)); - static LLCachedControl octree_alpha_distance_factor(gSavedSettings, "OctreeAlphaDistanceFactor", LLVector3(0.1f, 0.f, 0.f)); - - S32 size_factor = llmax((S32)octree_size_factor, 1); - LLVector3 alpha_distance_factor = octree_alpha_distance_factor; - - //const LLVector4a* ext = mDrawable->getSpatialExtents(); - - bool shrink_wrap = mShouldShrinkWrap || mDrawable->isAnimating(); - bool alpha_wrap = false; - - if (!isHUDAttachment() && mDrawable->mDistanceWRTCamera < alpha_distance_factor[2]) - { - for (S32 i = 0; i < mDrawable->getNumFaces(); i++) - { - LLFace* face = mDrawable->getFace(i); - if (!face) continue; - if (face->isInAlphaPool() && - !face->canRenderAsMask()) - { - alpha_wrap = true; - break; - } - } - } - else - { - shrink_wrap = false; - } - - if (alpha_wrap) - { - LLVector3 bounds = getScale(); - radius = llmin(bounds.mV[1], bounds.mV[2]); - radius = llmin(radius, bounds.mV[0]); - radius *= 0.5f; - //radius *= 1.f+mDrawable->mDistanceWRTCamera*alpha_distance_factor[1]; - //radius += mDrawable->mDistanceWRTCamera*alpha_distance_factor[0]; - } - else if (shrink_wrap) - { - radius = mDrawable->getRadius() * 0.25f; - } - else - { - F32 szf = size_factor; - radius = llmax(mDrawable->getRadius(), szf); - //radius = llmax(radius, mDrawable->mDistanceWRTCamera * distance_factor[0]); - } - - return llclamp(radius, 0.5f, 256.f); -} - -const LLVector3 LLVOVolume::getPivotPositionAgent() const -{ - if (mVolumeImpl) - { - return mVolumeImpl->getPivotPosition(); - } - return LLViewerObject::getPivotPositionAgent(); -} - -void LLVOVolume::onShift(const LLVector4a &shift_vector) -{ - if (mVolumeImpl) - { - mVolumeImpl->onShift(shift_vector); - } - - updateRelativeXform(); -} - -const LLMatrix4& LLVOVolume::getWorldMatrix(LLXformMatrix* xform) const -{ - if (mVolumeImpl) - { - return mVolumeImpl->getWorldMatrix(xform); - } - return xform->getWorldMatrix(); -} - -void LLVOVolume::markForUpdate() -{ - if (mDrawable) - { - shrinkWrap(); - } - - LLViewerObject::markForUpdate(); - mVolumeChanged = true; -} - -LLVector3 LLVOVolume::agentPositionToVolume(const LLVector3& pos) const -{ - LLVector3 ret = pos - getRenderPosition(); - ret = ret * ~getRenderRotation(); - if (!isVolumeGlobal()) - { - LLVector3 objScale = getScale(); - LLVector3 invObjScale(1.f / objScale.mV[VX], 1.f / objScale.mV[VY], 1.f / objScale.mV[VZ]); - ret.scaleVec(invObjScale); - } - - return ret; -} - -LLVector3 LLVOVolume::agentDirectionToVolume(const LLVector3& dir) const -{ - LLVector3 ret = dir * ~getRenderRotation(); - - LLVector3 objScale = isVolumeGlobal() ? LLVector3(1,1,1) : getScale(); - ret.scaleVec(objScale); - - return ret; -} - -LLVector3 LLVOVolume::volumePositionToAgent(const LLVector3& dir) const -{ - LLVector3 ret = dir; - if (!isVolumeGlobal()) - { - LLVector3 objScale = getScale(); - ret.scaleVec(objScale); - } - - ret = ret * getRenderRotation(); - ret += getRenderPosition(); - - return ret; -} - -LLVector3 LLVOVolume::volumeDirectionToAgent(const LLVector3& dir) const -{ - LLVector3 ret = dir; - LLVector3 objScale = isVolumeGlobal() ? LLVector3(1,1,1) : getScale(); - LLVector3 invObjScale(1.f / objScale.mV[VX], 1.f / objScale.mV[VY], 1.f / objScale.mV[VZ]); - ret.scaleVec(invObjScale); - ret = ret * getRenderRotation(); - - return ret; -} - - -bool LLVOVolume::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, S32 face, bool pick_transparent, bool pick_rigged, bool pick_unselectable, S32 *face_hitp, - LLVector4a* intersection,LLVector2* tex_coord, LLVector4a* normal, LLVector4a* tangent) - -{ - if (!mbCanSelect - || mDrawable->isDead() - || !gPipeline.hasRenderType(mDrawable->getRenderType())) - { - return false; - } - - if (!pick_unselectable) - { - if (!LLSelectMgr::instance().canSelectObject(this, true)) - { - return false; - } - } - - if (getClickAction() == CLICK_ACTION_IGNORE && !LLFloater::isVisible(gFloaterTools)) - { - return false; - } - - bool ret = false; - - LLVolume* volume = getVolume(); - - bool transform = true; - - if (mDrawable->isState(LLDrawable::RIGGED)) - { - if ((pick_rigged) || (getAvatar() && (getAvatar()->isSelf()) && (LLFloater::isVisible(gFloaterTools)))) - { - updateRiggedVolume(true, LLRiggedVolume::DO_NOT_UPDATE_FACES); - volume = mRiggedVolume; - transform = false; - } - else - { //cannot pick rigged attachments on other avatars or when not in build mode - return false; - } - } - - if (volume) - { - LLVector4a local_start = start; - LLVector4a local_end = end; - - if (transform) - { - LLVector3 v_start(start.getF32ptr()); - LLVector3 v_end(end.getF32ptr()); - - v_start = agentPositionToVolume(v_start); - v_end = agentPositionToVolume(v_end); - - local_start.load3(v_start.mV); - local_end.load3(v_end.mV); - } - - LLVector4a p; - LLVector4a n; - LLVector2 tc; - LLVector4a tn; - - if (intersection != NULL) - { - p = *intersection; - } - - if (tex_coord != NULL) - { - tc = *tex_coord; - } - - if (normal != NULL) - { - n = *normal; - } - - if (tangent != NULL) - { - tn = *tangent; - } - - S32 face_hit = -1; - - S32 start_face, end_face; - if (face == -1) - { - start_face = 0; - end_face = volume->getNumVolumeFaces(); - } - else - { - start_face = face; - end_face = face+1; - } - pick_transparent |= isHiglightedOrBeacon(); - - // we *probably* shouldn't care about special cursor at all, but we *definitely* - // don't care about special cursor for reflection probes -- makes alt-zoom - // go through reflection probes on vehicles - bool special_cursor = mReflectionProbe.isNull() && specialHoverCursor(); - - for (S32 i = start_face; i < end_face; ++i) - { - if (!special_cursor && !pick_transparent && getTE(i) && getTE(i)->getColor().mV[3] == 0.f) - { //don't attempt to pick completely transparent faces unless - //pick_transparent is true - continue; - } - - // This calculates the bounding box of the skinned mesh from scratch. It's actually quite expensive, but not nearly as expensive as building a full octree. - // rebuild_face_octrees = false because an octree for this face will be built later only if needed for narrow phase picking. - updateRiggedVolume(true, i, false); - face_hit = volume->lineSegmentIntersect(local_start, local_end, i, - &p, &tc, &n, &tn); - - if (face_hit >= 0 && mDrawable->getNumFaces() > face_hit) - { - LLFace* face = mDrawable->getFace(face_hit); - - bool ignore_alpha = false; - - const LLTextureEntry* te = face->getTextureEntry(); - if (te) - { - LLMaterial* mat = te->getMaterialParams(); - if (mat) - { - U8 mode = mat->getDiffuseAlphaMode(); - - if (mode == LLMaterial::DIFFUSE_ALPHA_MODE_EMISSIVE - || mode == LLMaterial::DIFFUSE_ALPHA_MODE_NONE - || (mode == LLMaterial::DIFFUSE_ALPHA_MODE_MASK && mat->getAlphaMaskCutoff() == 0)) - { - ignore_alpha = true; - } - } - } - - bool no_texture = !face->getTexture() || !face->getTexture()->hasGLTexture(); - bool mask = no_texture ? false : face->getTexture()->getMask(face->surfaceToTexture(tc, p, n)); - if (face && - (ignore_alpha || pick_transparent || no_texture || mask)) - { - local_end = p; - if (face_hitp != NULL) - { - *face_hitp = face_hit; - } - - if (intersection != NULL) - { - if (transform) - { - LLVector3 v_p(p.getF32ptr()); - - intersection->load3(volumePositionToAgent(v_p).mV); // must map back to agent space - } - else - { - *intersection = p; - } - } - - if (normal != NULL) - { - if (transform) - { - LLVector3 v_n(n.getF32ptr()); - normal->load3(volumeDirectionToAgent(v_n).mV); - } - else - { - *normal = n; - } - (*normal).normalize3fast(); - } - - if (tangent != NULL) - { - if (transform) - { - LLVector3 v_tn(tn.getF32ptr()); - - LLVector4a trans_tangent; - trans_tangent.load3(volumeDirectionToAgent(v_tn).mV); - - LLVector4Logical mask; - mask.clear(); - mask.setElement<3>(); - - tangent->setSelectWithMask(mask, tn, trans_tangent); - } - else - { - *tangent = tn; - } - (*tangent).normalize3fast(); - } - - if (tex_coord != NULL) - { - *tex_coord = tc; - } - - ret = true; - } - } - } - } - - return ret; -} - -bool LLVOVolume::treatAsRigged() -{ - return isSelected() && - (isAttachment() || isAnimatedObject()) && - mDrawable.notNull() && - mDrawable->isState(LLDrawable::RIGGED); -} - -LLRiggedVolume* LLVOVolume::getRiggedVolume() -{ - return mRiggedVolume; -} - -void LLVOVolume::clearRiggedVolume() -{ - if (mRiggedVolume.notNull()) - { - mRiggedVolume = NULL; - updateRelativeXform(); - } -} - -void LLVOVolume::updateRiggedVolume(bool force_treat_as_rigged, LLRiggedVolume::FaceIndex face_index, bool rebuild_face_octrees) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - //Update mRiggedVolume to match current animation frame of avatar. - //Also update position/size in octree. - - if ((!force_treat_as_rigged) && (!treatAsRigged())) - { - clearRiggedVolume(); - - return; - } - - LLVolume* volume = getVolume(); - const LLMeshSkinInfo* skin = getSkinInfo(); - if (!skin) - { - clearRiggedVolume(); - return; - } - - LLVOAvatar* avatar = getAvatar(); - if (!avatar) - { - clearRiggedVolume(); - return; - } - - if (!mRiggedVolume) - { - LLVolumeParams p; - mRiggedVolume = new LLRiggedVolume(p); - updateRelativeXform(); - } - - mRiggedVolume->update(skin, avatar, volume, face_index, rebuild_face_octrees); -} - -void LLRiggedVolume::update( - const LLMeshSkinInfo* skin, - LLVOAvatar* avatar, - const LLVolume* volume, - FaceIndex face_index, - bool rebuild_face_octrees) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - bool copy = false; - if (volume->getNumVolumeFaces() != getNumVolumeFaces()) - { - copy = true; - } - - for (S32 i = 0; i < volume->getNumVolumeFaces() && !copy; ++i) - { - const LLVolumeFace& src_face = volume->getVolumeFace(i); - const LLVolumeFace& dst_face = getVolumeFace(i); - - if (src_face.mNumIndices != dst_face.mNumIndices || - src_face.mNumVertices != dst_face.mNumVertices) - { - copy = true; - } - } - - if (copy) - { - copyVolumeFaces(volume); - } - else - { - bool is_paused = avatar && avatar->areAnimationsPaused(); - if (is_paused) - { - S32 frames_paused = LLFrameTimer::getFrameCount() - avatar->getMotionController().getPausedFrame(); - if (frames_paused > 1) - { - return; - } - } - } - - - //build matrix palette - static const size_t kMaxJoints = LL_MAX_JOINTS_PER_MESH_OBJECT; - - LLMatrix4a mat[kMaxJoints]; - U32 maxJoints = LLSkinningUtil::getMeshJointCount(skin); - LLSkinningUtil::initSkinningMatrixPalette(mat, maxJoints, skin, avatar); - const LLMatrix4a bind_shape_matrix = skin->mBindShapeMatrix; - - S32 rigged_vert_count = 0; - S32 rigged_face_count = 0; - LLVector4a box_min, box_max; - S32 face_begin; - S32 face_end; - if (face_index == DO_NOT_UPDATE_FACES) - { - face_begin = 0; - face_end = 0; - } - else if (face_index == UPDATE_ALL_FACES) - { - face_begin = 0; - face_end = volume->getNumVolumeFaces(); - } - else - { - face_begin = face_index; - face_end = face_begin + 1; - } - for (S32 i = face_begin; i < face_end; ++i) - { - const LLVolumeFace& vol_face = volume->getVolumeFace(i); - - LLVolumeFace& dst_face = mVolumeFaces[i]; - - LLVector4a* weight = vol_face.mWeights; - - if ( weight ) - { - LLSkinningUtil::checkSkinWeights(weight, dst_face.mNumVertices, skin); - - LLVector4a* pos = dst_face.mPositions; - - if (pos && dst_face.mExtents) - { - U32 max_joints = LLSkinningUtil::getMaxJointCount(); - rigged_vert_count += dst_face.mNumVertices; - rigged_face_count++; - - #if USE_SEPARATE_JOINT_INDICES_AND_WEIGHTS - if (vol_face.mJointIndices) // fast path with preconditioned joint indices - { - LLMatrix4a src[4]; - U8* joint_indices_cursor = vol_face.mJointIndices; - LLVector4a* just_weights = vol_face.mJustWeights; - for (U32 j = 0; j < dst_face.mNumVertices; ++j) - { - LLMatrix4a final_mat; - F32* w = just_weights[j].getF32ptr(); - LLSkinningUtil::getPerVertexSkinMatrixWithIndices(w, joint_indices_cursor, mat, final_mat, src); - joint_indices_cursor += 4; - - LLVector4a& v = vol_face.mPositions[j]; - LLVector4a t; - LLVector4a dst; - bind_shape_matrix.affineTransform(v, t); - final_mat.affineTransform(t, dst); - pos[j] = dst; - } - } - else - #endif - { - for (U32 j = 0; j < dst_face.mNumVertices; ++j) - { - LLMatrix4a final_mat; - LLSkinningUtil::getPerVertexSkinMatrix(weight[j].getF32ptr(), mat, false, final_mat, max_joints); - - LLVector4a& v = vol_face.mPositions[j]; - LLVector4a t; - LLVector4a dst; - bind_shape_matrix.affineTransform(v, t); - final_mat.affineTransform(t, dst); - pos[j] = dst; - } - } - - //update bounding box - // VFExtents change - LLVector4a& min = dst_face.mExtents[0]; - LLVector4a& max = dst_face.mExtents[1]; - - min = pos[0]; - max = pos[1]; - if (i==0) - { - box_min = min; - box_max = max; - } - - for (U32 j = 1; j < dst_face.mNumVertices; ++j) - { - min.setMin(min, pos[j]); - max.setMax(max, pos[j]); - } - - box_min.setMin(min,box_min); - box_max.setMax(max,box_max); - - dst_face.mCenter->setAdd(dst_face.mExtents[0], dst_face.mExtents[1]); - dst_face.mCenter->mul(0.5f); - - } - - if (rebuild_face_octrees) - { - dst_face.destroyOctree(); - dst_face.createOctree(); - } - } - } - mExtraDebugText = llformat("rigged %d/%d - box (%f %f %f) (%f %f %f)", - rigged_face_count, rigged_vert_count, - box_min[0], box_min[1], box_min[2], - box_max[0], box_max[1], box_max[2]); -} - -U32 LLVOVolume::getPartitionType() const -{ - if (isHUDAttachment()) - { - return LLViewerRegion::PARTITION_HUD; - } - if (isAnimatedObject() && getControlAvatar()) - { - return LLViewerRegion::PARTITION_CONTROL_AV; - } - if (isAttachment()) - { - return LLViewerRegion::PARTITION_AVATAR; - } - - return LLViewerRegion::PARTITION_VOLUME; -} - -LLVolumePartition::LLVolumePartition(LLViewerRegion* regionp) -: LLSpatialPartition(LLVOVolume::VERTEX_DATA_MASK, true, regionp), -LLVolumeGeometryManager() -{ - mLODPeriod = 32; - mDepthMask = false; - mDrawableType = LLPipeline::RENDER_TYPE_VOLUME; - mPartitionType = LLViewerRegion::PARTITION_VOLUME; - mSlopRatio = 0.25f; -} - -LLVolumeBridge::LLVolumeBridge(LLDrawable* drawablep, LLViewerRegion* regionp) -: LLSpatialBridge(drawablep, true, LLVOVolume::VERTEX_DATA_MASK, regionp), -LLVolumeGeometryManager() -{ - mDepthMask = false; - mLODPeriod = 32; - mDrawableType = LLPipeline::RENDER_TYPE_VOLUME; - mPartitionType = LLViewerRegion::PARTITION_BRIDGE; - - mSlopRatio = 0.25f; -} - -LLAvatarBridge::LLAvatarBridge(LLDrawable* drawablep, LLViewerRegion* regionp) - : LLVolumeBridge(drawablep, regionp) -{ - mDrawableType = LLPipeline::RENDER_TYPE_AVATAR; - mPartitionType = LLViewerRegion::PARTITION_AVATAR; -} - -LLControlAVBridge::LLControlAVBridge(LLDrawable* drawablep, LLViewerRegion* regionp) - : LLVolumeBridge(drawablep, regionp) -{ - mDrawableType = LLPipeline::RENDER_TYPE_CONTROL_AV; - mPartitionType = LLViewerRegion::PARTITION_CONTROL_AV; -} - -bool can_batch_texture(LLFace* facep) -{ - if (facep->getTextureEntry()->getBumpmap()) - { //bump maps aren't worked into texture batching yet - return false; - } - - if (facep->getTextureEntry()->getMaterialParams().notNull()) - { //materials don't work with texture batching yet - return false; - } - - if (facep->getTexture() && facep->getTexture()->getPrimaryFormat() == GL_ALPHA) - { //can't batch invisiprims - return false; - } - - if (facep->isState(LLFace::TEXTURE_ANIM) && facep->getVirtualSize() > MIN_TEX_ANIM_SIZE) - { //texture animation breaks batches - return false; - } - - if (facep->getTextureEntry()->getGLTFRenderMaterial() != nullptr) - { // PBR materials break indexed texture batching - return false; - } - - return true; -} - -const static U32 MAX_FACE_COUNT = 4096U; -int32_t LLVolumeGeometryManager::sInstanceCount = 0; -LLFace** LLVolumeGeometryManager::sFullbrightFaces[2] = { NULL }; -LLFace** LLVolumeGeometryManager::sBumpFaces[2] = { NULL }; -LLFace** LLVolumeGeometryManager::sSimpleFaces[2] = { NULL }; -LLFace** LLVolumeGeometryManager::sNormFaces[2] = { NULL }; -LLFace** LLVolumeGeometryManager::sSpecFaces[2] = { NULL }; -LLFace** LLVolumeGeometryManager::sNormSpecFaces[2] = { NULL }; -LLFace** LLVolumeGeometryManager::sPbrFaces[2] = { NULL }; -LLFace** LLVolumeGeometryManager::sAlphaFaces[2] = { NULL }; - -LLVolumeGeometryManager::LLVolumeGeometryManager() - : LLGeometryManager() -{ - llassert(sInstanceCount >= 0); - if (sInstanceCount == 0) - { - allocateFaces(MAX_FACE_COUNT); - } - - ++sInstanceCount; -} - -LLVolumeGeometryManager::~LLVolumeGeometryManager() -{ - llassert(sInstanceCount > 0); - --sInstanceCount; - - if (sInstanceCount <= 0) - { - freeFaces(); - sInstanceCount = 0; - } -} - -void LLVolumeGeometryManager::allocateFaces(U32 pMaxFaceCount) -{ - for (int i = 0; i < 2; ++i) - { - sFullbrightFaces[i] = static_cast(ll_aligned_malloc<64>(pMaxFaceCount * sizeof(LLFace*))); - sBumpFaces[i] = static_cast(ll_aligned_malloc<64>(pMaxFaceCount * sizeof(LLFace*))); - sSimpleFaces[i] = static_cast(ll_aligned_malloc<64>(pMaxFaceCount * sizeof(LLFace*))); - sNormFaces[i] = static_cast(ll_aligned_malloc<64>(pMaxFaceCount * sizeof(LLFace*))); - sSpecFaces[i] = static_cast(ll_aligned_malloc<64>(pMaxFaceCount * sizeof(LLFace*))); - sNormSpecFaces[i] = static_cast(ll_aligned_malloc<64>(pMaxFaceCount * sizeof(LLFace*))); - sPbrFaces[i] = static_cast(ll_aligned_malloc<64>(pMaxFaceCount * sizeof(LLFace*))); - sAlphaFaces[i] = static_cast(ll_aligned_malloc<64>(pMaxFaceCount * sizeof(LLFace*))); - } -} - -void LLVolumeGeometryManager::freeFaces() -{ - for (int i = 0; i < 2; ++i) - { - ll_aligned_free<64>(sFullbrightFaces[i]); - ll_aligned_free<64>(sBumpFaces[i]); - ll_aligned_free<64>(sSimpleFaces[i]); - ll_aligned_free<64>(sNormFaces[i]); - ll_aligned_free<64>(sSpecFaces[i]); - ll_aligned_free<64>(sNormSpecFaces[i]); - ll_aligned_free<64>(sPbrFaces[i]); - ll_aligned_free<64>(sAlphaFaces[i]); - - sFullbrightFaces[i] = NULL; - sBumpFaces[i] = NULL; - sSimpleFaces[i] = NULL; - sNormFaces[i] = NULL; - sSpecFaces[i] = NULL; - sNormSpecFaces[i] = NULL; - sPbrFaces[i] = NULL; - sAlphaFaces[i] = NULL; - } -} - -void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep, U32 type) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - if ( type == LLRenderPass::PASS_ALPHA - && facep->getTextureEntry()->getMaterialParams().notNull() - && !facep->getVertexBuffer()->hasDataType(LLVertexBuffer::TYPE_TANGENT) - && LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_OBJECT) > 1) - { - LL_WARNS_ONCE("RenderMaterials") << "Oh no! No binormals for this alpha blended face!" << LL_ENDL; - } - - bool selected = facep->getViewerObject()->isSelected(); - - if (selected && LLSelectMgr::getInstance()->mHideSelectedObjects) - { - return; - } - - LL_LABEL_VERTEX_BUFFER(facep->getVertexBuffer(), LLRenderPass::lookupPassName(type)); - - U32 passType = type; - - bool rigged = facep->isState(LLFace::RIGGED); - - if (rigged) - { - // hacky, should probably clean up -- if this face is rigged, put it in "type + 1" - // See LLRenderPass PASS_foo enum - passType += 1; - } - //add face to drawmap - LLSpatialGroup::drawmap_elem_t& draw_vec = group->mDrawMap[passType]; - - S32 idx = draw_vec.size()-1; - - bool fullbright = (type == LLRenderPass::PASS_FULLBRIGHT) || - (type == LLRenderPass::PASS_INVISIBLE) || - (type == LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK) || - (type == LLRenderPass::PASS_ALPHA && facep->isState(LLFace::FULLBRIGHT)) || - (facep->getTextureEntry()->getFullbright()); - - if (!fullbright && - type != LLRenderPass::PASS_GLOW && - !facep->getVertexBuffer()->hasDataType(LLVertexBuffer::TYPE_NORMAL)) - { - llassert(false); - LL_WARNS() << "Non fullbright face has no normals!" << LL_ENDL; - return; - } - - const LLMatrix4* tex_mat = NULL; - if (facep->isState(LLFace::TEXTURE_ANIM) && facep->getVirtualSize() > MIN_TEX_ANIM_SIZE) - { - tex_mat = facep->mTextureMatrix; - } - - const LLMatrix4* model_mat = NULL; - - LLDrawable* drawable = facep->getDrawable(); - - if (rigged) - { - // rigged meshes ignore their model matrix - model_mat = nullptr; - } - else if (drawable->isState(LLDrawable::ANIMATED_CHILD)) - { - model_mat = &drawable->getWorldMatrix(); - } - else if (drawable->isActive()) - { - model_mat = &drawable->getRenderMatrix(); - } - else - { - model_mat = &(drawable->getRegion()->mRenderMatrix); - } - - //drawable->getVObj()->setDebugText(llformat("%d", drawable->isState(LLDrawable::ANIMATED_CHILD))); - - const LLTextureEntry* te = facep->getTextureEntry(); - U8 bump = (type == LLRenderPass::PASS_BUMP || type == LLRenderPass::PASS_POST_BUMP) ? te->getBumpmap() : 0; - U8 shiny = te->getShiny(); - - LLViewerTexture* tex = facep->getTexture(); - - U8 index = facep->getTextureIndex(); - - LLMaterial* mat = nullptr; - - LLUUID mat_id; - - auto* gltf_mat = (LLFetchedGLTFMaterial*)te->getGLTFRenderMaterial(); - llassert(gltf_mat == nullptr || dynamic_cast(te->getGLTFRenderMaterial()) != nullptr); - if (gltf_mat != nullptr) - { - mat_id = gltf_mat->getHash(); // TODO: cache this hash - if (!facep->hasMedia() || (tex && tex->getType() != LLViewerTexture::MEDIA_TEXTURE)) - { // no media texture, face texture will be unused - tex = nullptr; - } - } - else - { - mat = te->getMaterialParams().get(); - if (mat) - { - mat_id = te->getMaterialParams()->getHash(); - } - } - - bool batchable = false; - - U32 shader_mask = 0xFFFFFFFF; //no shader - - if (mat) - { - bool is_alpha = (facep->getPoolType() == LLDrawPool::POOL_ALPHA) || (te->getColor().mV[3] < 0.999f); - if (type == LLRenderPass::PASS_ALPHA) - { - shader_mask = mat->getShaderMask(LLMaterial::DIFFUSE_ALPHA_MODE_BLEND, is_alpha); - } - else - { - shader_mask = mat->getShaderMask(LLMaterial::DIFFUSE_ALPHA_MODE_DEFAULT, is_alpha); - } - } - - if (index < FACE_DO_NOT_BATCH_TEXTURES && idx >= 0) - { - if (mat || gltf_mat || draw_vec[idx]->mMaterial) - { //can't batch textures when materials are present (yet) - batchable = false; - } - else if (index < draw_vec[idx]->mTextureList.size()) - { - if (draw_vec[idx]->mTextureList[index].isNull()) - { - batchable = true; - draw_vec[idx]->mTextureList[index] = tex; - } - else if (draw_vec[idx]->mTextureList[index] == tex) - { //this face's texture index can be used with this batch - batchable = true; - } - } - else - { //texture list can be expanded to fit this texture index - batchable = true; - } - } - - LLDrawInfo* info = idx >= 0 ? draw_vec[idx] : nullptr; - - if (info && - info->mVertexBuffer == facep->getVertexBuffer() && - info->mEnd == facep->getGeomIndex()-1 && - (LLPipeline::sTextureBindTest || draw_vec[idx]->mTexture == tex || batchable) && -#if LL_DARWIN - info->mEnd - draw_vec[idx]->mStart + facep->getGeomCount() <= (U32) gGLManager.mGLMaxVertexRange && - info->mCount + facep->getIndicesCount() <= (U32) gGLManager.mGLMaxIndexRange && -#endif - info->mMaterialID == mat_id && - info->mFullbright == fullbright && - info->mBump == bump && - (!mat || (info->mShiny == shiny)) && // need to break batches when a material is shared, but legacy settings are different - info->mTextureMatrix == tex_mat && - info->mModelMatrix == model_mat && - info->mShaderMask == shader_mask && - info->mAvatar == facep->mAvatar && - info->getSkinHash() == facep->getSkinHash()) - { - info->mCount += facep->getIndicesCount(); - info->mEnd += facep->getGeomCount(); - - if (index < FACE_DO_NOT_BATCH_TEXTURES && index >= info->mTextureList.size()) - { - info->mTextureList.resize(index+1); - info->mTextureList[index] = tex; - } - info->validate(); - } - else - { - U32 start = facep->getGeomIndex(); - U32 end = start + facep->getGeomCount()-1; - U32 offset = facep->getIndicesStart(); - U32 count = facep->getIndicesCount(); - LLPointer draw_info = new LLDrawInfo(start,end,count,offset, tex, - facep->getVertexBuffer(), fullbright, bump); - - info = draw_info; - - draw_vec.push_back(draw_info); - draw_info->mTextureMatrix = tex_mat; - draw_info->mModelMatrix = model_mat; - - draw_info->mBump = bump; - draw_info->mShiny = shiny; - - static const float alpha[4] = - { - 0.00f, - 0.25f, - 0.5f, - 0.75f - }; - float spec = alpha[shiny & TEM_SHINY_MASK]; - LLVector4 specColor(spec, spec, spec, spec); - draw_info->mSpecColor = specColor; - draw_info->mEnvIntensity = spec; - draw_info->mSpecularMap = NULL; - draw_info->mMaterial = mat; - draw_info->mGLTFMaterial = gltf_mat; - draw_info->mShaderMask = shader_mask; - draw_info->mAvatar = facep->mAvatar; - draw_info->mSkinInfo = facep->mSkinInfo; - - if (gltf_mat) - { - // just remember the material ID, render pools will reference the GLTF material - draw_info->mMaterialID = mat_id; - } - else if (mat) - { - draw_info->mMaterialID = mat_id; - - // We have a material. Update our draw info accordingly. - - if (!mat->getSpecularID().isNull()) - { - LLVector4 specColor; - specColor.mV[0] = mat->getSpecularLightColor().mV[0] * (1.f / 255.f); - specColor.mV[1] = mat->getSpecularLightColor().mV[1] * (1.f / 255.f); - specColor.mV[2] = mat->getSpecularLightColor().mV[2] * (1.f / 255.f); - specColor.mV[3] = mat->getSpecularLightExponent() * (1.f / 255.f); - draw_info->mSpecColor = specColor; - draw_info->mEnvIntensity = mat->getEnvironmentIntensity() * (1.f / 255.f); - draw_info->mSpecularMap = facep->getViewerObject()->getTESpecularMap(facep->getTEOffset()); - } - - draw_info->mAlphaMaskCutoff = mat->getAlphaMaskCutoff() * (1.f / 255.f); - draw_info->mDiffuseAlphaMode = mat->getDiffuseAlphaMode(); - draw_info->mNormalMap = facep->getViewerObject()->getTENormalMap(facep->getTEOffset()); - } - else - { - if (type == LLRenderPass::PASS_GRASS) - { - draw_info->mAlphaMaskCutoff = 0.5f; - } - else - { - draw_info->mAlphaMaskCutoff = 0.33f; - } - } - - // if (type == LLRenderPass::PASS_ALPHA) // always populate the draw_info ptr - { //for alpha sorting - facep->setDrawInfo(draw_info); - } - - if (index < FACE_DO_NOT_BATCH_TEXTURES) - { //initialize texture list for texture batching - draw_info->mTextureList.resize(index+1); - draw_info->mTextureList[index] = tex; - } - draw_info->validate(); - } - - llassert(info->mGLTFMaterial == nullptr || (info->mVertexBuffer->getTypeMask() & LLVertexBuffer::MAP_TANGENT) != 0); - llassert(type != LLPipeline::RENDER_TYPE_PASS_GLTF_PBR || info->mGLTFMaterial != nullptr); - llassert(type != LLPipeline::RENDER_TYPE_PASS_GLTF_PBR_RIGGED || info->mGLTFMaterial != nullptr); - llassert(type != LLPipeline::RENDER_TYPE_PASS_GLTF_PBR_ALPHA_MASK || info->mGLTFMaterial != nullptr); - llassert(type != LLPipeline::RENDER_TYPE_PASS_GLTF_PBR_ALPHA_MASK_RIGGED || info->mGLTFMaterial != nullptr); - - llassert(type != LLRenderPass::PASS_BUMP || (info->mVertexBuffer->getTypeMask() & LLVertexBuffer::MAP_TANGENT) != 0); - llassert(type != LLRenderPass::PASS_NORMSPEC || info->mNormalMap.notNull()); - llassert(type != LLRenderPass::PASS_SPECMAP || (info->mVertexBuffer->getTypeMask() & LLVertexBuffer::MAP_TEXCOORD2) != 0); -} - -void LLVolumeGeometryManager::getGeometry(LLSpatialGroup* group) -{ - -} - -// add a face pointer to a list of face pointers without going over MAX_COUNT faces -template -static inline void add_face(T*** list, U32* count, T* face) -{ - if (face->isState(LLFace::RIGGED)) - { - if (count[1] < MAX_FACE_COUNT) - { - face->setDrawOrderIndex(count[1]); - list[1][count[1]++] = face; - } - } - else - { - if (count[0] < MAX_FACE_COUNT) - { - face->setDrawOrderIndex(count[0]); - list[0][count[0]++] = face; - } - } -} - -void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - llassert(!gCubeSnapshot); - - if (group->isDead()) - { - return; - } - - if (group->changeLOD()) - { - group->mLastUpdateDistance = group->mDistance; - } - - group->mLastUpdateViewAngle = group->mViewAngle; - - if (!group->hasState(LLSpatialGroup::GEOM_DIRTY | LLSpatialGroup::ALPHA_DIRTY)) - { - if (group->hasState(LLSpatialGroup::MESH_DIRTY)) - { - rebuildMesh(group); - } - return; - } - - group->mBuilt = 1.f; - - LLSpatialBridge* bridge = group->getSpatialPartition()->asBridge(); - LLViewerObject *vobj = NULL; - LLVOVolume *vol_obj = NULL; - - if (bridge) - { - vobj = bridge->mDrawable->getVObj(); - vol_obj = dynamic_cast(vobj); - } - if (vol_obj) - { - vol_obj->updateVisualComplexity(); - } - - group->mGeometryBytes = 0; - group->mSurfaceArea = 0; - - //cache object box size since it might be used for determining visibility - const LLVector4a* bounds = group->getObjectBounds(); - group->mObjectBoxSize = bounds[1].getLength3().getF32(); - - group->clearDrawMap(); - - U32 fullbright_count[2] = { 0 }; - U32 bump_count[2] = { 0 }; - U32 simple_count[2] = { 0 }; - U32 alpha_count[2] = { 0 }; - U32 norm_count[2] = { 0 }; - U32 spec_count[2] = { 0 }; - U32 normspec_count[2] = { 0 }; - U32 pbr_count[2] = { 0 }; - - static LLCachedControl max_vbo_size(gSavedSettings, "RenderMaxVBOSize", 512); - static LLCachedControl max_node_size(gSavedSettings, "RenderMaxNodeSize", 65536); - U32 max_vertices = (max_vbo_size * 1024)/LLVertexBuffer::calcVertexSize(group->getSpatialPartition()->mVertexDataMask); - U32 max_total = (max_node_size * 1024) / LLVertexBuffer::calcVertexSize(group->getSpatialPartition()->mVertexDataMask); - max_vertices = llmin(max_vertices, (U32) 65535); - - U32 cur_total = 0; - - bool emissive = false; - - //Determine if we've received skininfo that contains an - //alternate bind matrix - if it does then apply the translational component - //to the joints of the avatar. -#if 0 - bool pelvisGotSet = false; -#endif - - { - LL_PROFILE_ZONE_NAMED("rebuildGeom - face list"); - - //get all the faces into a list - for (LLSpatialGroup::element_iter drawable_iter = group->getDataBegin(); - drawable_iter != group->getDataEnd(); ++drawable_iter) - { - LLDrawable* drawablep = (LLDrawable*)(*drawable_iter)->getDrawable(); - - if (!drawablep || drawablep->isDead() || drawablep->isState(LLDrawable::FORCE_INVISIBLE) ) - { - continue; - } - - LLVOVolume* vobj = drawablep->getVOVolume(); - - if (!vobj || vobj->isDead()) - { - continue; - } - - // HACK -- brute force this check every time a drawable gets rebuilt - for (S32 i = 0; i < drawablep->getNumFaces(); ++i) - { - vobj->updateTEMaterialTextures(i); - } - - // apply any pending material overrides - gGLTFMaterialList.applyQueuedOverrides(vobj); - - std::string vobj_name = llformat("Vol%p", vobj); - - bool is_mesh = vobj->isMesh(); - if (is_mesh) - { - if ((vobj->getVolume() && !vobj->getVolume()->isMeshAssetLoaded()) - || !gMeshRepo.meshRezEnabled()) - { - // Waiting for asset to fetch - continue; - } - - if (!vobj->getSkinInfo() && !vobj->isSkinInfoUnavaliable()) - { - // Waiting for skin info to fetch - continue; - } - } - - LLVolume* volume = vobj->getVolume(); - if (volume) - { - const LLVector3& scale = vobj->getScale(); - group->mSurfaceArea += volume->getSurfaceArea() * llmax(llmax(scale.mV[0], scale.mV[1]), scale.mV[2]); - } - - - F32 est_tris = vobj->getEstTrianglesMax(); - - vobj->updateControlAvatar(); - - LL_DEBUGS("AnimatedObjectsLinkset") << vobj_name << " rebuilding, isAttachment: " << (U32) vobj->isAttachment() - << " is_mesh " << is_mesh - << " est_tris " << est_tris - << " is_animated " << vobj->isAnimatedObject() - << " can_animate " << vobj->canBeAnimatedObject() - << " cav " << vobj->getControlAvatar() - << " lod " << vobj->getLOD() - << " drawable rigged " << (drawablep->isState(LLDrawable::RIGGED)) - << " drawable state " << drawablep->getState() - << " playing " << (U32) (vobj->getControlAvatar() ? vobj->getControlAvatar()->mPlaying : false) - << " frame " << LLFrameTimer::getFrameCount() - << LL_ENDL; - - llassert_always(vobj); - vobj->updateTextureVirtualSize(true); - vobj->preRebuild(); - - drawablep->clearState(LLDrawable::HAS_ALPHA); - - LLVOAvatar* avatar = nullptr; - const LLMeshSkinInfo* skinInfo = nullptr; - if (is_mesh) - { - skinInfo = vobj->getSkinInfo(); - } - - if (skinInfo) - { - if (vobj->isAnimatedObject()) - { - avatar = vobj->getControlAvatar(); - } - else - { - avatar = vobj->getAvatar(); - } - } - - if (avatar != nullptr) - { - avatar->addAttachmentOverridesForObject(vobj, NULL, false); - } - - // Standard rigged mesh attachments: - bool rigged = !vobj->isAnimatedObject() && skinInfo && vobj->isAttachment(); - // Animated objects. Have to check for isRiggedMesh() to - // exclude static objects in animated object linksets. - rigged = rigged || (vobj->isAnimatedObject() && vobj->isRiggedMesh() && - vobj->getControlAvatar() && vobj->getControlAvatar()->mPlaying); - - bool any_rigged_face = false; - - //for each face - for (S32 i = 0; i < drawablep->getNumFaces(); i++) - { - LLFace* facep = drawablep->getFace(i); - if (!facep) - { - continue; - } -#if 0 -#if LL_RELEASE_WITH_DEBUG_INFO - const LLUUID pbr_id( "49c88210-7238-2a6b-70ac-92d4f35963cf" ); - const LLUUID obj_id( vobj->getID() ); - bool is_pbr = (obj_id == pbr_id); -#else - bool is_pbr = false; -#endif -#else - LLGLTFMaterial *gltf_mat = facep->getTextureEntry()->getGLTFRenderMaterial(); - bool is_pbr = gltf_mat != nullptr; -#endif - - //ALWAYS null out vertex buffer on rebuild -- if the face lands in a render - // batch, it will recover its vertex buffer reference from the spatial group - facep->setVertexBuffer(NULL); - - //sum up face verts and indices - drawablep->updateFaceSize(i); - - if (rigged) - { - if (!facep->isState(LLFace::RIGGED)) - { //completely reset vertex buffer - facep->clearVertexBuffer(); - } - - facep->setState(LLFace::RIGGED); - facep->mSkinInfo = (LLMeshSkinInfo*) skinInfo; // TODO -- fix ugly de-consting here - facep->mAvatar = avatar; - any_rigged_face = true; - } - else - { - if (facep->isState(LLFace::RIGGED)) - { - //face is not rigged but used to be, remove from rigged face pool - LLDrawPoolAvatar* pool = (LLDrawPoolAvatar*)facep->getPool(); - if (pool) - { - pool->removeFace(facep); - } - facep->clearState(LLFace::RIGGED); - facep->mAvatar = NULL; - facep->mSkinInfo = NULL; - } - } - - if (cur_total > max_total || facep->getIndicesCount() <= 0 || facep->getGeomCount() <= 0) - { - facep->clearVertexBuffer(); - continue; - } - - if (facep->hasGeometry() && - (rigged || // <-- HACK FIXME -- getPixelArea might be incorrect for rigged objects - facep->getPixelArea() > FORCE_CULL_AREA)) // <-- don't render tiny faces - { - cur_total += facep->getGeomCount(); - - const LLTextureEntry* te = facep->getTextureEntry(); - LLViewerTexture* tex = facep->getTexture(); - - if (te->getGlow() > 0.f) - { - emissive = true; - } - - if (facep->isState(LLFace::TEXTURE_ANIM)) - { - if (!vobj->mTexAnimMode) - { - facep->clearState(LLFace::TEXTURE_ANIM); - } - } - - bool force_simple = (facep->getPixelArea() < FORCE_SIMPLE_RENDER_AREA); - U32 type = gPipeline.getPoolTypeFromTE(te, tex); - if (is_pbr && gltf_mat && gltf_mat->mAlphaMode != LLGLTFMaterial::ALPHA_MODE_BLEND) - { - type = LLDrawPool::POOL_GLTF_PBR; - } - else - if (type != LLDrawPool::POOL_ALPHA && force_simple) - { - type = LLDrawPool::POOL_SIMPLE; - } - facep->setPoolType(type); - - if (vobj->isHUDAttachment() && !is_pbr) - { - facep->setState(LLFace::FULLBRIGHT); - } - - if (vobj->mTextureAnimp && vobj->mTexAnimMode) - { - if (vobj->mTextureAnimp->mFace <= -1) - { - S32 face; - for (face = 0; face < vobj->getNumTEs(); face++) - { - LLFace * facep = drawablep->getFace(face); - if (facep) - { - facep->setState(LLFace::TEXTURE_ANIM); - } - } - } - else if (vobj->mTextureAnimp->mFace < vobj->getNumTEs()) - { - LLFace * facep = drawablep->getFace(vobj->mTextureAnimp->mFace); - if (facep) - { - facep->setState(LLFace::TEXTURE_ANIM); - } - } - } - - if (type == LLDrawPool::POOL_ALPHA) - { - if (facep->canRenderAsMask()) - { //can be treated as alpha mask - add_face(sSimpleFaces, simple_count, facep); - } - else - { - F32 alpha; - if (is_pbr) - { - alpha = gltf_mat ? gltf_mat->mBaseColor.mV[3] : 1.0; - } - else - { - alpha = te->getColor().mV[3]; - } - if (alpha > 0.f || te->getGlow() > 0.f) - { //only treat as alpha in the pipeline if < 100% transparent - drawablep->setState(LLDrawable::HAS_ALPHA); - add_face(sAlphaFaces, alpha_count, facep); - } - else if (LLDrawPoolAlpha::sShowDebugAlpha || - (gPipeline.sRenderHighlight && !drawablep->getParent() && - //only root objects are highlighted with red color in this case - drawablep->getVObj() && drawablep->getVObj()->flagScripted() && - (LLPipeline::getRenderScriptedBeacons() || - (LLPipeline::getRenderScriptedTouchBeacons() && drawablep->getVObj()->flagHandleTouch())))) - { //draw the transparent face for debugging purposes using a custom texture - add_face(sAlphaFaces, alpha_count, facep); - } - } - } - else - { - if (drawablep->isState(LLDrawable::REBUILD_VOLUME)) - { - facep->mLastUpdateTime = gFrameTimeSeconds; - } - - { - LLGLTFMaterial* gltf_mat = te->getGLTFRenderMaterial(); - - if (gltf_mat != nullptr || (te->getMaterialParams().notNull())) - { - if (gltf_mat != nullptr) - { - add_face(sPbrFaces, pbr_count, facep); - } - else - { - LLMaterial* mat = te->getMaterialParams().get(); - if (mat->getNormalID().notNull() || // <-- has a normal map, needs tangents - (te->getBumpmap() && (te->getBumpmap() < 18))) // <-- has an emboss bump map, needs tangents - { - if (mat->getSpecularID().notNull()) - { //has normal and specular maps (needs texcoord1, texcoord2, and tangent) - add_face(sNormSpecFaces, normspec_count, facep); - } - else - { //has normal map (needs texcoord1 and tangent) - add_face(sNormFaces, norm_count, facep); - } - } - else if (mat->getSpecularID().notNull()) - { //has specular map but no normal map, needs texcoord2 - add_face(sSpecFaces, spec_count, facep); - } - else - { //has neither specular map nor normal map, only needs texcoord0 - add_face(sSimpleFaces, simple_count, facep); - } - } - } - else if (te->getBumpmap()) - { //needs normal + tangent - add_face(sBumpFaces, bump_count, facep); - } - else if (te->getShiny() || !te->getFullbright()) - { //needs normal - add_face(sSimpleFaces, simple_count, facep); - } - else - { //doesn't need normal - facep->setState(LLFace::FULLBRIGHT); - add_face(sFullbrightFaces, fullbright_count, facep); - } - } - } - } - else - { //face has no renderable geometry - facep->clearVertexBuffer(); - } - } - - if (any_rigged_face) - { - if (!drawablep->isState(LLDrawable::RIGGED)) - { - drawablep->setState(LLDrawable::RIGGED); - LLDrawable* root = drawablep->getRoot(); - if (root != drawablep) - { - root->setState(LLDrawable::RIGGED_CHILD); - } - - //first time this is drawable is being marked as rigged, - // do another LoD update to use avatar bounding box - vobj->updateLOD(); - } - } - else - { - drawablep->clearState(LLDrawable::RIGGED); - vobj->updateRiggedVolume(false); - } - } - } - - //PROCESS NON-ALPHA FACES - U32 simple_mask = LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_COLOR; - U32 alpha_mask = simple_mask | 0x80000000; //hack to give alpha verts their own VBO - U32 bump_mask = LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_COLOR; - U32 fullbright_mask = LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_COLOR; - - U32 norm_mask = simple_mask | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TANGENT; - U32 normspec_mask = norm_mask | LLVertexBuffer::MAP_TEXCOORD2; - U32 spec_mask = simple_mask | LLVertexBuffer::MAP_TEXCOORD2; - - U32 pbr_mask = LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_COLOR | LLVertexBuffer::MAP_TANGENT; - - if (emissive) - { //emissive faces are present, include emissive byte to preserve batching - simple_mask = simple_mask | LLVertexBuffer::MAP_EMISSIVE; - alpha_mask = alpha_mask | LLVertexBuffer::MAP_EMISSIVE; - bump_mask = bump_mask | LLVertexBuffer::MAP_EMISSIVE; - fullbright_mask = fullbright_mask | LLVertexBuffer::MAP_EMISSIVE; - norm_mask = norm_mask | LLVertexBuffer::MAP_EMISSIVE; - normspec_mask = normspec_mask | LLVertexBuffer::MAP_EMISSIVE; - spec_mask = spec_mask | LLVertexBuffer::MAP_EMISSIVE; - pbr_mask = pbr_mask | LLVertexBuffer::MAP_EMISSIVE; - } - - bool batch_textures = LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_OBJECT) > 1; - - // add extra vertex data for deferred rendering (not necessarily for batching textures) - if (batch_textures) - { - bump_mask = bump_mask | LLVertexBuffer::MAP_TANGENT; - simple_mask = simple_mask | LLVertexBuffer::MAP_TEXTURE_INDEX; - alpha_mask = alpha_mask | LLVertexBuffer::MAP_TEXTURE_INDEX | LLVertexBuffer::MAP_TANGENT | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TEXCOORD2; - fullbright_mask = fullbright_mask | LLVertexBuffer::MAP_TEXTURE_INDEX; - } - - group->mGeometryBytes = 0; - - U32 geometryBytes = 0; - - // generate render batches for static geometry - U32 extra_mask = LLVertexBuffer::MAP_TEXTURE_INDEX; - bool alpha_sort = true; - bool rigged = false; - for (int i = 0; i < 2; ++i) //two sets, static and rigged) - { - geometryBytes += genDrawInfo(group, simple_mask | extra_mask, sSimpleFaces[i], simple_count[i], false, batch_textures, rigged); - geometryBytes += genDrawInfo(group, fullbright_mask | extra_mask, sFullbrightFaces[i], fullbright_count[i], false, batch_textures, rigged); - geometryBytes += genDrawInfo(group, alpha_mask | extra_mask, sAlphaFaces[i], alpha_count[i], alpha_sort, batch_textures, rigged); - geometryBytes += genDrawInfo(group, bump_mask | extra_mask, sBumpFaces[i], bump_count[i], false, false, rigged); - geometryBytes += genDrawInfo(group, norm_mask | extra_mask, sNormFaces[i], norm_count[i], false, false, rigged); - geometryBytes += genDrawInfo(group, spec_mask | extra_mask, sSpecFaces[i], spec_count[i], false, false, rigged); - geometryBytes += genDrawInfo(group, normspec_mask | extra_mask, sNormSpecFaces[i], normspec_count[i], false, false, rigged); - geometryBytes += genDrawInfo(group, pbr_mask | extra_mask, sPbrFaces[i], pbr_count[i], false, false, rigged); - - // for rigged set, add weights and disable alpha sorting (rigged items use depth buffer) - extra_mask |= LLVertexBuffer::MAP_WEIGHT4; - rigged = true; - } - - group->mGeometryBytes = geometryBytes; - - { - //drawables have been rebuilt, clear rebuild status - for (LLSpatialGroup::element_iter drawable_iter = group->getDataBegin(); drawable_iter != group->getDataEnd(); ++drawable_iter) - { - LLDrawable* drawablep = (LLDrawable*)(*drawable_iter)->getDrawable(); - if(drawablep) - { - drawablep->clearState(LLDrawable::REBUILD_ALL); - } - } - } - - group->mLastUpdateTime = gFrameTimeSeconds; - group->mBuilt = 1.f; - group->clearState(LLSpatialGroup::GEOM_DIRTY | LLSpatialGroup::ALPHA_DIRTY); -} - -void LLVolumeGeometryManager::rebuildMesh(LLSpatialGroup* group) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - llassert(group); - if (group && group->hasState(LLSpatialGroup::MESH_DIRTY) && !group->hasState(LLSpatialGroup::GEOM_DIRTY)) - { - { - LL_PROFILE_ZONE_NAMED("rebuildMesh - gen draw info"); - - group->mBuilt = 1.f; - - const U32 MAX_BUFFER_COUNT = 4096; - LLVertexBuffer* locked_buffer[MAX_BUFFER_COUNT]; - - U32 buffer_count = 0; - - for (LLSpatialGroup::element_iter drawable_iter = group->getDataBegin(); drawable_iter != group->getDataEnd(); ++drawable_iter) - { - LLDrawable* drawablep = (LLDrawable*)(*drawable_iter)->getDrawable(); - - if (drawablep && !drawablep->isDead() && drawablep->isState(LLDrawable::REBUILD_ALL)) - { - LLVOVolume* vobj = drawablep->getVOVolume(); - - if (!vobj) continue; - - if (vobj->isNoLOD()) continue; - - vobj->preRebuild(); - - if (drawablep->isState(LLDrawable::ANIMATED_CHILD)) - { - vobj->updateRelativeXform(true); - } - - LLVolume* volume = vobj->getVolume(); - if (!volume) continue; - for (S32 i = 0; i < drawablep->getNumFaces(); ++i) - { - LLFace* face = drawablep->getFace(i); - if (face) - { - LLVertexBuffer* buff = face->getVertexBuffer(); - if (buff) - { - if (!face->getGeometryVolume(*volume, // volume - face->getTEOffset(), // face_index - vobj->getRelativeXform(), // mat_vert_in - vobj->getRelativeXformInvTrans(), // mat_norm_in - face->getGeomIndex(), // index_offset - false, // force_rebuild - true)) // no_debug_assert - { // Something's gone wrong with the vertex buffer accounting, - // rebuild this group with no debug assert because MESH_DIRTY - group->dirtyGeom(); - gPipeline.markRebuild(group); - } - - buff->unmapBuffer(); - } - } - } - - if (drawablep->isState(LLDrawable::ANIMATED_CHILD)) - { - vobj->updateRelativeXform(); - } - - drawablep->clearState(LLDrawable::REBUILD_ALL); - } - } - - { - LL_PROFILE_ZONE_NAMED("rebuildMesh - flush"); - for (LLVertexBuffer** iter = locked_buffer, ** end_iter = locked_buffer+buffer_count; iter != end_iter; ++iter) - { - (*iter)->unmapBuffer(); - } - - // don't forget alpha - if(group != NULL && - !group->mVertexBuffer.isNull()) - { - group->mVertexBuffer->unmapBuffer(); - } - } - - group->clearState(LLSpatialGroup::MESH_DIRTY | LLSpatialGroup::NEW_DRAWINFO); - } - } -} - -struct CompareBatchBreaker -{ - bool operator()(const LLFace* const& lhs, const LLFace* const& rhs) - { - const LLTextureEntry* lte = lhs->getTextureEntry(); - const LLTextureEntry* rte = rhs->getTextureEntry(); - - if (lte->getBumpmap() != rte->getBumpmap()) - { - return lte->getBumpmap() < rte->getBumpmap(); - } - else if (lte->getFullbright() != rte->getFullbright()) - { - return lte->getFullbright() < rte->getFullbright(); - } - else if (lte->getMaterialID() != rte->getMaterialID()) - { - return lte->getMaterialID() < rte->getMaterialID(); - } - else if (lte->getShiny() != rte->getShiny()) - { - return lte->getShiny() < rte->getShiny(); - } - else if (lhs->getTexture() != rhs->getTexture()) - { - return lhs->getTexture() < rhs->getTexture(); - } - else - { - // all else being equal, maintain consistent draw order - return lhs->getDrawOrderIndex() < rhs->getDrawOrderIndex(); - } - } -}; - -struct CompareBatchBreakerRigged -{ - bool operator()(const LLFace* const& lhs, const LLFace* const& rhs) - { - if (lhs->mAvatar != rhs->mAvatar) - { - return lhs->mAvatar < rhs->mAvatar; - } - else if (lhs->mSkinInfo->mHash != rhs->mSkinInfo->mHash) - { - return lhs->mSkinInfo->mHash < rhs->mSkinInfo->mHash; - } - else - { - // "inherit" non-rigged behavior - CompareBatchBreaker comp; - return comp(lhs, rhs); - } - } -}; - -U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace** faces, U32 face_count, bool distance_sort, bool batch_textures, bool rigged) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - - U32 geometryBytes = 0; - - //calculate maximum number of vertices to store in a single buffer - static LLCachedControl max_vbo_size(gSavedSettings, "RenderMaxVBOSize", 512); - U32 max_vertices = (max_vbo_size * 1024)/LLVertexBuffer::calcVertexSize(group->getSpatialPartition()->mVertexDataMask); - max_vertices = llmin(max_vertices, (U32) 65535); - - { - LL_PROFILE_ZONE_NAMED("genDrawInfo - sort"); - - if (rigged) - { - if (!distance_sort) // <--- alpha "sort" rigged faces by maintaining original draw order - { - //sort faces by things that break batches, including avatar and mesh id - std::sort(faces, faces + face_count, CompareBatchBreakerRigged()); - } - } - else if (!distance_sort) - { - //sort faces by things that break batches, not including avatar and mesh id - std::sort(faces, faces + face_count, CompareBatchBreaker()); - } - else - { - //sort faces by distance - std::sort(faces, faces+face_count, LLFace::CompareDistanceGreater()); - } - } - - bool hud_group = group->isHUDGroup() ; - LLFace** face_iter = faces; - LLFace** end_faces = faces+face_count; - - LLSpatialGroup::buffer_map_t buffer_map; - - LLViewerTexture* last_tex = NULL; - - S32 texture_index_channels = 1; - - if (gGLManager.mGLSLVersionMajor > 1 || gGLManager.mGLSLVersionMinor >= 30) - { - texture_index_channels = LLGLSLShader::sIndexedTextureChannels-1; //always reserve one for shiny for now just for simplicity; - } - - if (distance_sort) - { - texture_index_channels = gDeferredAlphaProgram.mFeatures.mIndexedTextureChannels; - } - - texture_index_channels = LLGLSLShader::sIndexedTextureChannels; - - bool flexi = false; - - while (face_iter != end_faces) - { - //pull off next face - LLFace* facep = *face_iter; - LLViewerTexture* tex = facep->getTexture(); - const LLTextureEntry* te = facep->getTextureEntry(); - LLMaterialPtr mat = te->getMaterialParams(); - LLMaterialID matId = te->getMaterialID(); - - if (distance_sort) - { - tex = NULL; - } - - if (last_tex != tex) - { - last_tex = tex; - } - - bool bake_sunlight = LLPipeline::sBakeSunlight && facep->getDrawable()->isStatic(); - - U32 index_count = facep->getIndicesCount(); - U32 geom_count = facep->getGeomCount(); - - flexi = flexi || facep->getViewerObject()->getVolume()->isUnique(); - - //sum up vertices needed for this render batch - LLFace** i = face_iter; - ++i; - - const U32 MAX_TEXTURE_COUNT = 32; - LLViewerTexture* texture_list[MAX_TEXTURE_COUNT]; - - U32 texture_count = 0; - - { - LL_PROFILE_ZONE_NAMED("genDrawInfo - face size"); - if (batch_textures) - { - U8 cur_tex = 0; - facep->setTextureIndex(cur_tex); - if (texture_count < MAX_TEXTURE_COUNT) - { - texture_list[texture_count++] = tex; - } - - if (can_batch_texture(facep)) - { //populate texture_list with any textures that can be batched - //move i to the next unbatchable face - while (i != end_faces) - { - facep = *i; - - if (!can_batch_texture(facep)) - { //face is bump mapped or has an animated texture matrix -- can't - //batch more than 1 texture at a time - facep->setTextureIndex(0); - break; - } - - if (facep->getTexture() != tex) - { - if (distance_sort) - { //textures might be out of order, see if texture exists in current batch - bool found = false; - for (U32 tex_idx = 0; tex_idx < texture_count; ++tex_idx) - { - if (facep->getTexture() == texture_list[tex_idx]) - { - cur_tex = tex_idx; - found = true; - break; - } - } - - if (!found) - { - cur_tex = texture_count; - } - } - else - { - cur_tex++; - } - - if (cur_tex >= texture_index_channels) - { //cut batches when index channels are depleted - break; - } - - tex = facep->getTexture(); - - if (texture_count < MAX_TEXTURE_COUNT) - { - texture_list[texture_count++] = tex; - } - } - - if (geom_count + facep->getGeomCount() > max_vertices) - { //cut batches on geom count too big - break; - } - - ++i; - - flexi = flexi || facep->getViewerObject()->getVolume()->isUnique(); - - index_count += facep->getIndicesCount(); - geom_count += facep->getGeomCount(); - - facep->setTextureIndex(cur_tex); - } - } - else - { - facep->setTextureIndex(0); - } - - tex = texture_list[0]; - } - else - { - while (i != end_faces && - (LLPipeline::sTextureBindTest || - (distance_sort || - ((*i)->getTexture() == tex)))) - { - facep = *i; - const LLTextureEntry* nextTe = facep->getTextureEntry(); - if (nextTe->getMaterialID() != matId) - { - break; - } - - //face has no texture index - facep->mDrawInfo = NULL; - facep->setTextureIndex(FACE_DO_NOT_BATCH_TEXTURES); - - if (geom_count + facep->getGeomCount() > max_vertices) - { //cut batches on geom count too big - break; - } - - ++i; - index_count += facep->getIndicesCount(); - geom_count += facep->getGeomCount(); - - flexi = flexi || facep->getViewerObject()->getVolume()->isUnique(); - } - } - } - - //create vertex buffer - LLPointer buffer; - - { - LL_PROFILE_ZONE_NAMED("genDrawInfo - allocate"); - buffer = new LLVertexBuffer(mask); - if(!buffer->allocateBuffer(geom_count, index_count)) - { - LL_WARNS() << "Failed to allocate group Vertex Buffer to " - << geom_count << " vertices and " - << index_count << " indices" << LL_ENDL; - buffer = NULL; - } - } - - if (buffer) - { - geometryBytes += buffer->getSize() + buffer->getIndicesSize(); - buffer_map[mask][*face_iter].push_back(buffer); - } - - //add face geometry - - U32 indices_index = 0; - U16 index_offset = 0; - - while (face_iter < i) - { - //update face indices for new buffer - facep = *face_iter; - - if (buffer.isNull()) - { - // Bulk allocation failed - facep->setVertexBuffer(buffer); - facep->setSize(0, 0); // mark as no geometry - ++face_iter; - continue; - } - facep->setIndicesIndex(indices_index); - facep->setGeomIndex(index_offset); - facep->setVertexBuffer(buffer); - - if (batch_textures && facep->getTextureIndex() == FACE_DO_NOT_BATCH_TEXTURES) - { - LL_ERRS() << "Invalid texture index." << LL_ENDL; - } - - { - //for debugging, set last time face was updated vs moved - facep->updateRebuildFlags(); - - { //copy face geometry into vertex buffer - LLDrawable* drawablep = facep->getDrawable(); - LLVOVolume* vobj = drawablep->getVOVolume(); - LLVolume* volume = vobj->getVolume(); - - if (drawablep->isState(LLDrawable::ANIMATED_CHILD)) - { - vobj->updateRelativeXform(true); - } - - U32 te_idx = facep->getTEOffset(); - - if (!facep->getGeometryVolume(*volume, te_idx, - vobj->getRelativeXform(), vobj->getRelativeXformInvTrans(), index_offset,true)) - { - LL_WARNS() << "Failed to get geometry for face!" << LL_ENDL; - } - - if (drawablep->isState(LLDrawable::ANIMATED_CHILD)) - { - vobj->updateRelativeXform(false); - } - } - } - - index_offset += facep->getGeomCount(); - indices_index += facep->getIndicesCount(); - - //append face to appropriate render batch - - bool force_simple = facep->getPixelArea() < FORCE_SIMPLE_RENDER_AREA; - bool fullbright = facep->isState(LLFace::FULLBRIGHT); - if ((mask & LLVertexBuffer::MAP_NORMAL) == 0) - { //paranoia check to make sure GL doesn't try to read non-existant normals - fullbright = true; - } - - const LLTextureEntry* te = facep->getTextureEntry(); - LLGLTFMaterial* gltf_mat = te->getGLTFRenderMaterial(); - - if (hud_group && gltf_mat == nullptr) - { //all hud attachments are fullbright - fullbright = true; - } - - tex = facep->getTexture(); - - bool is_alpha = facep->getPoolType() == LLDrawPool::POOL_ALPHA; - - LLMaterial* mat = nullptr; - bool can_be_shiny = false; - - // ignore traditional material if GLTF material is present - if (gltf_mat == nullptr) - { - mat = te->getMaterialParams().get(); - - can_be_shiny = true; - if (mat) - { - U8 mode = mat->getDiffuseAlphaMode(); - can_be_shiny = mode == LLMaterial::DIFFUSE_ALPHA_MODE_NONE || - mode == LLMaterial::DIFFUSE_ALPHA_MODE_EMISSIVE; - } - } - - F32 blinn_phong_alpha = te->getColor().mV[3]; - bool use_legacy_bump = te->getBumpmap() && (te->getBumpmap() < 18) && (!mat || mat->getNormalID().isNull()); - bool blinn_phong_opaque = blinn_phong_alpha >= 0.999f; - bool blinn_phong_transparent = blinn_phong_alpha < 0.999f; - - if (!gltf_mat) - { - is_alpha |= blinn_phong_transparent; - } - - if (gltf_mat || (mat && !hud_group)) - { - bool material_pass = false; - - if (gltf_mat) - { // all other parameters ignored if gltf material is present - if (gltf_mat->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_BLEND) - { - registerFace(group, facep, LLRenderPass::PASS_ALPHA); - } - else if (gltf_mat->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_MASK) - { - registerFace(group, facep, LLRenderPass::PASS_GLTF_PBR_ALPHA_MASK); - } - else - { - registerFace(group, facep, LLRenderPass::PASS_GLTF_PBR); - } - } - else - // do NOT use 'fullbright' for this logic or you risk sending - // things without normals down the materials pipeline and will - // render poorly if not crash NORSPEC-240,314 - // - if (te->getFullbright()) - { - if (mat->getDiffuseAlphaMode() == LLMaterial::DIFFUSE_ALPHA_MODE_MASK) - { - if (blinn_phong_opaque) - { - registerFace(group, facep, LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK); - } - else - { - registerFace(group, facep, LLRenderPass::PASS_ALPHA); - } - } - else if (is_alpha) - { - registerFace(group, facep, LLRenderPass::PASS_ALPHA); - } - else - { - if (mat->getEnvironmentIntensity() > 0 || te->getShiny() > 0) - { - material_pass = true; - } - else - { - if (blinn_phong_opaque) - { - registerFace(group, facep, LLRenderPass::PASS_FULLBRIGHT); - } - else - { - registerFace(group, facep, LLRenderPass::PASS_ALPHA); - } - } - } - } - else if (blinn_phong_transparent) - { - registerFace(group, facep, LLRenderPass::PASS_ALPHA); - } - else if (use_legacy_bump) - { - llassert(mask & LLVertexBuffer::MAP_TANGENT); - // we have a material AND legacy bump settings, but no normal map - registerFace(group, facep, LLRenderPass::PASS_BUMP); - } - else - { - material_pass = true; - } - - if (material_pass) - { - static const U32 pass[] = - { - LLRenderPass::PASS_MATERIAL, - LLRenderPass::PASS_ALPHA, //LLRenderPass::PASS_MATERIAL_ALPHA, - LLRenderPass::PASS_MATERIAL_ALPHA_MASK, - LLRenderPass::PASS_MATERIAL_ALPHA_EMISSIVE, - LLRenderPass::PASS_SPECMAP, - LLRenderPass::PASS_ALPHA, //LLRenderPass::PASS_SPECMAP_BLEND, - LLRenderPass::PASS_SPECMAP_MASK, - LLRenderPass::PASS_SPECMAP_EMISSIVE, - LLRenderPass::PASS_NORMMAP, - LLRenderPass::PASS_ALPHA, //LLRenderPass::PASS_NORMMAP_BLEND, - LLRenderPass::PASS_NORMMAP_MASK, - LLRenderPass::PASS_NORMMAP_EMISSIVE, - LLRenderPass::PASS_NORMSPEC, - LLRenderPass::PASS_ALPHA, //LLRenderPass::PASS_NORMSPEC_BLEND, - LLRenderPass::PASS_NORMSPEC_MASK, - LLRenderPass::PASS_NORMSPEC_EMISSIVE, - }; - - U32 alpha_mode = mat->getDiffuseAlphaMode(); - if (!distance_sort && alpha_mode == LLMaterial::DIFFUSE_ALPHA_MODE_BLEND) - { // HACK - this should never happen, but sometimes we get a material that thinks it has alpha blending when it ought not - alpha_mode = LLMaterial::DIFFUSE_ALPHA_MODE_NONE; - } - U32 mask = mat->getShaderMask(alpha_mode, is_alpha); - - U32 vb_mask = facep->getVertexBuffer()->getTypeMask(); - - // HACK - this should also never happen, but sometimes we get here and the material thinks it has a specmap now - // even though it didn't appear to have a specmap when the face was added to the list of faces - if ((mask & 0x4) && !(vb_mask & LLVertexBuffer::MAP_TEXCOORD2)) - { - mask &= ~0x4; - } - - llassert(mask < sizeof(pass)/sizeof(U32)); - - mask = llmin(mask, (U32)(sizeof(pass)/sizeof(U32)-1)); - - // if this is going into alpha pool, distance sort MUST be true - llassert(pass[mask] == LLRenderPass::PASS_ALPHA ? distance_sort : true); - registerFace(group, facep, pass[mask]); - } - } - else if (mat) - { - U8 mode = mat->getDiffuseAlphaMode(); - - is_alpha = (is_alpha || (mode == LLMaterial::DIFFUSE_ALPHA_MODE_BLEND)); - - if (is_alpha) - { - mode = LLMaterial::DIFFUSE_ALPHA_MODE_BLEND; - } - - if (mode == LLMaterial::DIFFUSE_ALPHA_MODE_MASK) - { - registerFace(group, facep, fullbright ? LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK : LLRenderPass::PASS_ALPHA_MASK); - } - else if (is_alpha ) - { - registerFace(group, facep, LLRenderPass::PASS_ALPHA); - } - else if (gPipeline.shadersLoaded() - && te->getShiny() - && can_be_shiny) - { - registerFace(group, facep, fullbright ? LLRenderPass::PASS_FULLBRIGHT_SHINY : LLRenderPass::PASS_SHINY); - } - else - { - registerFace(group, facep, fullbright ? LLRenderPass::PASS_FULLBRIGHT : LLRenderPass::PASS_SIMPLE); - } - } - else if (is_alpha) - { - // can we safely treat this as an alpha mask? - if (facep->getFaceColor().mV[3] <= 0.f) - { //100% transparent, don't render unless we're highlighting transparent - registerFace(group, facep, LLRenderPass::PASS_ALPHA_INVISIBLE); - } - else if (facep->canRenderAsMask() && !hud_group) - { - if (te->getFullbright() || LLPipeline::sNoAlpha) - { - registerFace(group, facep, LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK); - } - else - { - registerFace(group, facep, LLRenderPass::PASS_ALPHA_MASK); - } - } - else - { - registerFace(group, facep, LLRenderPass::PASS_ALPHA); - } - } - else if (gPipeline.shadersLoaded() - && te->getShiny() - && can_be_shiny) - { //shiny - if (tex->getPrimaryFormat() == GL_ALPHA) - { //invisiprim+shiny - registerFace(group, facep, LLRenderPass::PASS_INVISI_SHINY); - registerFace(group, facep, LLRenderPass::PASS_INVISIBLE); - } - else if (!hud_group) - { //deferred rendering - if (te->getFullbright()) - { //register in post deferred fullbright shiny pass - registerFace(group, facep, LLRenderPass::PASS_FULLBRIGHT_SHINY); - if (te->getBumpmap()) - { //register in post deferred bump pass - registerFace(group, facep, LLRenderPass::PASS_POST_BUMP); - } - } - else if (use_legacy_bump) - { //register in deferred bump pass - llassert(mask& LLVertexBuffer::MAP_TANGENT); - registerFace(group, facep, LLRenderPass::PASS_BUMP); - } - else - { //register in deferred simple pass (deferred simple includes shiny) - llassert(mask & LLVertexBuffer::MAP_NORMAL); - registerFace(group, facep, LLRenderPass::PASS_SIMPLE); - } - } - else if (fullbright) - { //not deferred, register in standard fullbright shiny pass - registerFace(group, facep, LLRenderPass::PASS_FULLBRIGHT_SHINY); - } - else - { //not deferred or fullbright, register in standard shiny pass - registerFace(group, facep, LLRenderPass::PASS_SHINY); - } - } - else - { //not alpha and not shiny - if (!is_alpha && tex->getPrimaryFormat() == GL_ALPHA) - { //invisiprim - registerFace(group, facep, LLRenderPass::PASS_INVISIBLE); - } - else if (fullbright || bake_sunlight) - { //fullbright - if (mat && mat->getDiffuseAlphaMode() == LLMaterial::DIFFUSE_ALPHA_MODE_MASK) - { - registerFace(group, facep, LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK); - } - else - { - registerFace(group, facep, LLRenderPass::PASS_FULLBRIGHT); - } - if (!hud_group && use_legacy_bump) - { //if this is the deferred render and a bump map is present, register in post deferred bump - registerFace(group, facep, LLRenderPass::PASS_POST_BUMP); - } - } - else - { - if (use_legacy_bump) - { //non-shiny or fullbright deferred bump - llassert(mask& LLVertexBuffer::MAP_TANGENT); - registerFace(group, facep, LLRenderPass::PASS_BUMP); - } - else - { //all around simple - llassert(mask & LLVertexBuffer::MAP_NORMAL); - if (mat && mat->getDiffuseAlphaMode() == LLMaterial::DIFFUSE_ALPHA_MODE_MASK) - { //material alpha mask can be respected in non-deferred - registerFace(group, facep, LLRenderPass::PASS_ALPHA_MASK); - } - else - { - registerFace(group, facep, LLRenderPass::PASS_SIMPLE); - } - } - } - - - if (!gPipeline.shadersLoaded() && - !is_alpha && - te->getShiny()) - { //shiny as an extra pass when shaders are disabled - registerFace(group, facep, LLRenderPass::PASS_SHINY); - } - } - - //not sure why this is here, and looks like it might cause bump mapped objects to get rendered redundantly -- davep 5/11/2010 - if (!is_alpha && hud_group) - { - llassert((mask & LLVertexBuffer::MAP_NORMAL) || fullbright); - facep->setPoolType((fullbright) ? LLDrawPool::POOL_FULLBRIGHT : LLDrawPool::POOL_SIMPLE); - - if (!force_simple && use_legacy_bump) - { - llassert(mask & LLVertexBuffer::MAP_TANGENT); - registerFace(group, facep, LLRenderPass::PASS_BUMP); - } - } - - if (!is_alpha && LLPipeline::sRenderGlow && te->getGlow() > 0.f) - { - if (gltf_mat) - { - registerFace(group, facep, LLRenderPass::PASS_GLTF_GLOW); - } - else - { - registerFace(group, facep, LLRenderPass::PASS_GLOW); - } - } - - ++face_iter; - } - - if (buffer) - { - buffer->unmapBuffer(); - } - } - - group->mBufferMap[mask].clear(); - for (LLSpatialGroup::buffer_texture_map_t::iterator i = buffer_map[mask].begin(); i != buffer_map[mask].end(); ++i) - { - group->mBufferMap[mask][i->first] = i->second; - } - - return geometryBytes; -} - -void LLVolumeGeometryManager::addGeometryCount(LLSpatialGroup* group, U32& vertex_count, U32& index_count) -{ - //for each drawable - for (LLSpatialGroup::element_iter drawable_iter = group->getDataBegin(); drawable_iter != group->getDataEnd(); ++drawable_iter) - { - LLDrawable* drawablep = (LLDrawable*)(*drawable_iter)->getDrawable(); - - if (!drawablep || drawablep->isDead()) - { - continue; - } - } -} - -void LLGeometryManager::addGeometryCount(LLSpatialGroup* group, U32 &vertex_count, U32 &index_count) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; - - //clear off any old faces - mFaceList.clear(); - - //for each drawable - for (LLSpatialGroup::element_iter drawable_iter = group->getDataBegin(); drawable_iter != group->getDataEnd(); ++drawable_iter) - { - LLDrawable* drawablep = (LLDrawable*)(*drawable_iter)->getDrawable(); - - if (!drawablep || drawablep->isDead()) - { - continue; - } - - //for each face - for (S32 i = 0; i < drawablep->getNumFaces(); i++) - { - //sum up face verts and indices - drawablep->updateFaceSize(i); - LLFace* facep = drawablep->getFace(i); - if (facep) - { - if (facep->hasGeometry() && facep->getPixelArea() > FORCE_CULL_AREA && - facep->getGeomCount() + vertex_count <= 65536) - { - vertex_count += facep->getGeomCount(); - index_count += facep->getIndicesCount(); - - //remember face (for sorting) - mFaceList.push_back(facep); - } - else - { - facep->clearVertexBuffer(); - } - } - } - } -} - -LLHUDPartition::LLHUDPartition(LLViewerRegion* regionp) : LLBridgePartition(regionp) -{ - mPartitionType = LLViewerRegion::PARTITION_HUD; - mDrawableType = LLPipeline::RENDER_TYPE_HUD; - mSlopRatio = 0.f; - mLODPeriod = 1; -} - -void LLHUDPartition::shift(const LLVector4a &offset) -{ - //HUD objects don't shift with region crossing. That would be silly. -} +/** + * @file llvovolume.cpp + * @brief LLVOVolume class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// A "volume" is a box, cylinder, sphere, or other primitive shape. + +#include "llviewerprecompiledheaders.h" + +#include "llvovolume.h" + +#include + +#include "llviewercontrol.h" +#include "lldir.h" +#include "llflexibleobject.h" +#include "llfloatertools.h" +#include "llmaterialid.h" +#include "llmaterialtable.h" +#include "llprimitive.h" +#include "llvolume.h" +#include "llvolumeoctree.h" +#include "llvolumemgr.h" +#include "llvolumemessage.h" +#include "material_codes.h" +#include "message.h" +#include "llpluginclassmedia.h" // for code in the mediaEvent handler +#include "object_flags.h" +#include "lldrawable.h" +#include "lldrawpoolavatar.h" +#include "lldrawpoolbump.h" +#include "llface.h" +#include "llspatialpartition.h" +#include "llhudmanager.h" +#include "llflexibleobject.h" +#include "llskinningutil.h" +#include "llsky.h" +#include "lltexturefetch.h" +#include "llvector4a.h" +#include "llviewercamera.h" +#include "llviewertexturelist.h" +#include "llviewerobjectlist.h" +#include "llviewerregion.h" +#include "llviewertextureanim.h" +#include "llworld.h" +#include "llselectmgr.h" +#include "pipeline.h" +#include "llsdutil.h" +#include "llmatrix4a.h" +#include "llmediaentry.h" +#include "llmediadataclient.h" +#include "llmeshrepository.h" +#include "llnotifications.h" +#include "llnotificationsutil.h" +#include "llagent.h" +#include "llviewermediafocus.h" +#include "lldatapacker.h" +#include "llviewershadermgr.h" +#include "llvoavatar.h" +#include "llcontrolavatar.h" +#include "llvoavatarself.h" +#include "llvocache.h" +#include "llmaterialmgr.h" +#include "llanimationstates.h" +#include "llinventorytype.h" +#include "llviewerinventory.h" +#include "llcallstack.h" +#include "llsculptidsize.h" +#include "llavatarappearancedefines.h" +#include "llgltfmateriallist.h" + +const F32 FORCE_SIMPLE_RENDER_AREA = 512.f; +const F32 FORCE_CULL_AREA = 8.f; +U32 JOINT_COUNT_REQUIRED_FOR_FULLRIG = 1; + +bool gAnimateTextures = true; + +F32 LLVOVolume::sLODFactor = 1.f; +F32 LLVOVolume::sLODSlopDistanceFactor = 0.5f; //Changing this to zero, effectively disables the LOD transition slop +F32 LLVOVolume::sDistanceFactor = 1.0f; +S32 LLVOVolume::sNumLODChanges = 0; +S32 LLVOVolume::mRenderComplexity_last = 0; +S32 LLVOVolume::mRenderComplexity_current = 0; +LLPointer LLVOVolume::sObjectMediaClient = NULL; +LLPointer LLVOVolume::sObjectMediaNavigateClient = NULL; + +extern bool gCubeSnapshot; + +// Implementation class of LLMediaDataClientObject. See llmediadataclient.h +class LLMediaDataClientObjectImpl : public LLMediaDataClientObject +{ +public: + LLMediaDataClientObjectImpl(LLVOVolume *obj, bool isNew) : mObject(obj), mNew(isNew) + { + mObject->addMDCImpl(); + } + ~LLMediaDataClientObjectImpl() + { + mObject->removeMDCImpl(); + } + + virtual U8 getMediaDataCount() const + { return mObject->getNumTEs(); } + + virtual LLSD getMediaDataLLSD(U8 index) const + { + LLSD result; + LLTextureEntry *te = mObject->getTE(index); + if (NULL != te) + { + llassert((te->getMediaData() != NULL) == te->hasMedia()); + if (te->getMediaData() != NULL) + { + result = te->getMediaData()->asLLSD(); + // XXX HACK: workaround bug in asLLSD() where whitelist is not set properly + // See DEV-41949 + if (!result.has(LLMediaEntry::WHITELIST_KEY)) + { + result[LLMediaEntry::WHITELIST_KEY] = LLSD::emptyArray(); + } + } + } + return result; + } + virtual bool isCurrentMediaUrl(U8 index, const std::string &url) const + { + LLTextureEntry *te = mObject->getTE(index); + if (te) + { + if (te->getMediaData()) + { + return (te->getMediaData()->getCurrentURL() == url); + } + } + return url.empty(); + } + + virtual LLUUID getID() const + { return mObject->getID(); } + + virtual void mediaNavigateBounceBack(U8 index) + { mObject->mediaNavigateBounceBack(index); } + + virtual bool hasMedia() const + { return mObject->hasMedia(); } + + virtual void updateObjectMediaData(LLSD const &data, const std::string &version_string) + { mObject->updateObjectMediaData(data, version_string); } + + virtual F64 getMediaInterest() const + { + F64 interest = mObject->getTotalMediaInterest(); + if (interest < (F64)0.0) + { + // media interest not valid yet, try pixel area + interest = mObject->getPixelArea(); + // HACK: force recalculation of pixel area if interest is the "magic default" of 1024. + if (interest == 1024.f) + { + const_cast(static_cast(mObject))->setPixelAreaAndAngle(gAgent); + interest = mObject->getPixelArea(); + } + } + return interest; + } + + virtual bool isInterestingEnough() const + { + return LLViewerMedia::getInstance()->isInterestingEnough(mObject, getMediaInterest()); + } + + virtual std::string getCapabilityUrl(const std::string &name) const + { return mObject->getRegion()->getCapability(name); } + + virtual bool isDead() const + { return mObject->isDead(); } + + virtual U32 getMediaVersion() const + { return LLTextureEntry::getVersionFromMediaVersionString(mObject->getMediaURL()); } + + virtual bool isNew() const + { return mNew; } + +private: + LLPointer mObject; + bool mNew; +}; + + +LLVOVolume::LLVOVolume(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) + : LLViewerObject(id, pcode, regionp), + mVolumeImpl(NULL) +{ + mTexAnimMode = 0; + mRelativeXform.setIdentity(); + mRelativeXformInvTrans.setIdentity(); + + mFaceMappingChanged = false; + mLOD = MIN_LOD; + mLODDistance = 0.0f; + mLODAdjustedDistance = 0.0f; + mLODRadius = 0.0f; + mTextureAnimp = NULL; + mVolumeChanged = false; + mVObjRadius = LLVector3(1,1,0.5f).length(); + mNumFaces = 0; + mLODChanged = false; + mSculptChanged = false; + mColorChanged = false; + mSpotLightPriority = 0.f; + + mSkinInfoUnavaliable = false; + mSkinInfo = NULL; + + mMediaImplList.resize(getNumTEs()); + mLastFetchedMediaVersion = -1; + mServerDrawableUpdateCount = 0; + memset(&mIndexInTex, 0, sizeof(S32) * LLRender::NUM_VOLUME_TEXTURE_CHANNELS); + mMDCImplCount = 0; + mLastRiggingInfoLOD = -1; + mResetDebugText = false; +} + +LLVOVolume::~LLVOVolume() +{ + LL_PROFILE_ZONE_SCOPED; + delete mTextureAnimp; + mTextureAnimp = NULL; + delete mVolumeImpl; + mVolumeImpl = NULL; + + gMeshRepo.unregisterMesh(this); + + if(!mMediaImplList.empty()) + { + for(U32 i = 0 ; i < mMediaImplList.size() ; i++) + { + if(mMediaImplList[i].notNull()) + { + mMediaImplList[i]->removeObject(this) ; + } + } + } +} + +void LLVOVolume::markDead() +{ + if (!mDead) + { + LL_PROFILE_ZONE_SCOPED; + if (getVolume()) + { + LLSculptIDSize::instance().rem(getVolume()->getParams().getSculptID()); + } + + if(getMDCImplCount() > 0) + { + LLMediaDataClientObject::ptr_t obj = new LLMediaDataClientObjectImpl(const_cast(this), false); + if (sObjectMediaClient) sObjectMediaClient->removeFromQueue(obj); + if (sObjectMediaNavigateClient) sObjectMediaNavigateClient->removeFromQueue(obj); + } + + // Detach all media impls from this object + for(U32 i = 0 ; i < mMediaImplList.size() ; i++) + { + removeMediaImpl(i); + } + + if (mSculptTexture.notNull()) + { + mSculptTexture->removeVolume(LLRender::SCULPT_TEX, this); + } + + if (mLightTexture.notNull()) + { + mLightTexture->removeVolume(LLRender::LIGHT_TEX, this); + } + } + + LLViewerObject::markDead(); +} + + +// static +void LLVOVolume::initClass() +{ + // gSavedSettings better be around + if (gSavedSettings.getBOOL("PrimMediaMasterEnabled")) + { + const F32 queue_timer_delay = gSavedSettings.getF32("PrimMediaRequestQueueDelay"); + const F32 retry_timer_delay = gSavedSettings.getF32("PrimMediaRetryTimerDelay"); + const U32 max_retries = gSavedSettings.getU32("PrimMediaMaxRetries"); + const U32 max_sorted_queue_size = gSavedSettings.getU32("PrimMediaMaxSortedQueueSize"); + const U32 max_round_robin_queue_size = gSavedSettings.getU32("PrimMediaMaxRoundRobinQueueSize"); + sObjectMediaClient = new LLObjectMediaDataClient(queue_timer_delay, retry_timer_delay, max_retries, + max_sorted_queue_size, max_round_robin_queue_size); + sObjectMediaNavigateClient = new LLObjectMediaNavigateClient(queue_timer_delay, retry_timer_delay, + max_retries, max_sorted_queue_size, max_round_robin_queue_size); + } +} + +// static +void LLVOVolume::cleanupClass() +{ + sObjectMediaClient = NULL; + sObjectMediaNavigateClient = NULL; +} + +U32 LLVOVolume::processUpdateMessage(LLMessageSystem *mesgsys, + void **user_data, + U32 block_num, EObjectUpdateType update_type, + LLDataPacker *dp) +{ + + LLColor4U color; + const S32 teDirtyBits = (TEM_CHANGE_TEXTURE|TEM_CHANGE_COLOR|TEM_CHANGE_MEDIA); + const bool previously_volume_changed = mVolumeChanged; + const bool previously_face_mapping_changed = mFaceMappingChanged; + const bool previously_color_changed = mColorChanged; + + // Do base class updates... + U32 retval = LLViewerObject::processUpdateMessage(mesgsys, user_data, block_num, update_type, dp); + + LLUUID sculpt_id; + U8 sculpt_type = 0; + if (isSculpted()) + { + LLSculptParams *sculpt_params = (LLSculptParams *)getParameterEntry(LLNetworkData::PARAMS_SCULPT); + sculpt_id = sculpt_params->getSculptTexture(); + sculpt_type = sculpt_params->getSculptType(); + + LL_DEBUGS("ObjectUpdate") << "uuid " << mID << " set sculpt_id " << sculpt_id << LL_ENDL; + dumpStack("ObjectUpdateStack"); + } + + if (!dp) + { + if (update_type == OUT_FULL) + { + //////////////////////////////// + // + // Unpack texture animation data + // + // + + if (mesgsys->getSizeFast(_PREHASH_ObjectData, block_num, _PREHASH_TextureAnim)) + { + if (!mTextureAnimp) + { + mTextureAnimp = new LLViewerTextureAnim(this); + } + else + { + if (!(mTextureAnimp->mMode & LLTextureAnim::SMOOTH)) + { + mTextureAnimp->reset(); + } + } + mTexAnimMode = 0; + + mTextureAnimp->unpackTAMessage(mesgsys, block_num); + } + else + { + if (mTextureAnimp) + { + delete mTextureAnimp; + mTextureAnimp = NULL; + + for (S32 i = 0; i < getNumTEs(); i++) + { + LLFace* facep = mDrawable->getFace(i); + if (facep && facep->mTextureMatrix) + { + // delete or reset + delete facep->mTextureMatrix; + facep->mTextureMatrix = NULL; + } + } + + gPipeline.markTextured(mDrawable); + mFaceMappingChanged = true; + mTexAnimMode = 0; + } + } + + // Unpack volume data + LLVolumeParams volume_params; + LLVolumeMessage::unpackVolumeParams(&volume_params, mesgsys, _PREHASH_ObjectData, block_num); + volume_params.setSculptID(sculpt_id, sculpt_type); + + if (setVolume(volume_params, 0)) + { + markForUpdate(); + } + } + + // Sigh, this needs to be done AFTER the volume is set as well, otherwise bad stuff happens... + //////////////////////////// + // + // Unpack texture entry data + // + + S32 result = unpackTEMessage(mesgsys, _PREHASH_ObjectData, (S32) block_num); + + if (result & TEM_CHANGE_MEDIA) + { + retval |= MEDIA_FLAGS_CHANGED; + } + } + else + { + if (update_type != OUT_TERSE_IMPROVED) + { + LLVolumeParams volume_params; + bool res = LLVolumeMessage::unpackVolumeParams(&volume_params, *dp); + if (!res) + { + LL_WARNS() << "Bogus volume parameters in object " << getID() << LL_ENDL; + LL_WARNS() << getRegion()->getOriginGlobal() << LL_ENDL; + } + + volume_params.setSculptID(sculpt_id, sculpt_type); + + if (setVolume(volume_params, 0)) + { + markForUpdate(); + } + S32 res2 = unpackTEMessage(*dp); + if (TEM_INVALID == res2) + { + // There's something bogus in the data that we're unpacking. + dp->dumpBufferToLog(); + LL_WARNS() << "Flushing cache files" << LL_ENDL; + + if(LLVOCache::instanceExists() && getRegion()) + { + LLVOCache::getInstance()->removeEntry(getRegion()->getHandle()) ; + } + + LL_WARNS() << "Bogus TE data in " << getID() << LL_ENDL; + } + else + { + if (res2 & TEM_CHANGE_MEDIA) + { + retval |= MEDIA_FLAGS_CHANGED; + } + } + + U32 value = dp->getPassFlags(); + + if (value & 0x40) + { + if (!mTextureAnimp) + { + mTextureAnimp = new LLViewerTextureAnim(this); + } + else + { + if (!(mTextureAnimp->mMode & LLTextureAnim::SMOOTH)) + { + mTextureAnimp->reset(); + } + } + mTexAnimMode = 0; + mTextureAnimp->unpackTAMessage(*dp); + } + else if (mTextureAnimp) + { + delete mTextureAnimp; + mTextureAnimp = NULL; + + for (S32 i = 0; i < getNumTEs(); i++) + { + LLFace* facep = mDrawable->getFace(i); + if (facep && facep->mTextureMatrix) + { + // delete or reset + delete facep->mTextureMatrix; + facep->mTextureMatrix = NULL; + } + } + + gPipeline.markTextured(mDrawable); + mFaceMappingChanged = true; + mTexAnimMode = 0; + } + + if (value & 0x400) + { //particle system (new) + unpackParticleSource(*dp, mOwnerID, false); + } + } + else + { + S32 texture_length = mesgsys->getSizeFast(_PREHASH_ObjectData, block_num, _PREHASH_TextureEntry); + if (texture_length) + { + U8 tdpbuffer[1024]; + LLDataPackerBinaryBuffer tdp(tdpbuffer, 1024); + mesgsys->getBinaryDataFast(_PREHASH_ObjectData, _PREHASH_TextureEntry, tdpbuffer, 0, block_num, 1024); + S32 result = unpackTEMessage(tdp); + if (result & teDirtyBits) + { + if (mDrawable) + { //on the fly TE updates break batches, isolate in octree + shrinkWrap(); + } + } + if (result & TEM_CHANGE_MEDIA) + { + retval |= MEDIA_FLAGS_CHANGED; + } + } + } + } + if (retval & (MEDIA_URL_REMOVED | MEDIA_URL_ADDED | MEDIA_URL_UPDATED | MEDIA_FLAGS_CHANGED)) + { + // If only the media URL changed, and it isn't a media version URL, + // ignore it + if ( ! ( retval & (MEDIA_URL_ADDED | MEDIA_URL_UPDATED) && + mMedia && ! mMedia->mMediaURL.empty() && + ! LLTextureEntry::isMediaVersionString(mMedia->mMediaURL) ) ) + { + // If the media changed at all, request new media data + LL_DEBUGS("MediaOnAPrim") << "Media update: " << getID() << ": retval=" << retval << " Media URL: " << + ((mMedia) ? mMedia->mMediaURL : std::string("")) << LL_ENDL; + requestMediaDataUpdate(retval & MEDIA_FLAGS_CHANGED); + } + else { + LL_INFOS("MediaOnAPrim") << "Ignoring media update for: " << getID() << " Media URL: " << + ((mMedia) ? mMedia->mMediaURL : std::string("")) << LL_ENDL; + } + } + // ...and clean up any media impls + cleanUpMediaImpls(); + + if (( + (mVolumeChanged && !previously_volume_changed) || + (mFaceMappingChanged && !previously_face_mapping_changed) || + (mColorChanged && !previously_color_changed) + ) + && !mLODChanged) { + onDrawableUpdateFromServer(); + } + + return retval; +} + +// Called when a volume, material, etc is updated by the server, possibly by a +// script. If this occurs too often for this object, mark it as active so that +// it doesn't disrupt the octree/render batches, thereby potentially causing a +// big performance penalty. +void LLVOVolume::onDrawableUpdateFromServer() +{ + constexpr U32 UPDATES_UNTIL_ACTIVE = 8; + ++mServerDrawableUpdateCount; + if (mDrawable && !mDrawable->isActive() && mServerDrawableUpdateCount > UPDATES_UNTIL_ACTIVE) + { + mDrawable->makeActive(); + } +} + +void LLVOVolume::animateTextures() +{ + if (!mDead) + { + shrinkWrap(); + F32 off_s = 0.f, off_t = 0.f, scale_s = 1.f, scale_t = 1.f, rot = 0.f; + S32 result = mTextureAnimp->animateTextures(off_s, off_t, scale_s, scale_t, rot); + + if (result) + { + if (!mTexAnimMode) + { + mFaceMappingChanged = true; + gPipeline.markTextured(mDrawable); + } + mTexAnimMode = result | mTextureAnimp->mMode; + + S32 start=0, end=mDrawable->getNumFaces()-1; + if (mTextureAnimp->mFace >= 0 && mTextureAnimp->mFace <= end) + { + start = end = mTextureAnimp->mFace; + } + + for (S32 i = start; i <= end; i++) + { + LLFace* facep = mDrawable->getFace(i); + if (!facep) continue; + if(facep->getVirtualSize() <= MIN_TEX_ANIM_SIZE && facep->mTextureMatrix) continue; + + const LLTextureEntry* te = facep->getTextureEntry(); + + if (!te) + { + continue; + } + + if (!(result & LLViewerTextureAnim::ROTATE)) + { + te->getRotation(&rot); + } + if (!(result & LLViewerTextureAnim::TRANSLATE)) + { + te->getOffset(&off_s,&off_t); + } + if (!(result & LLViewerTextureAnim::SCALE)) + { + te->getScale(&scale_s, &scale_t); + } + + if (!facep->mTextureMatrix) + { + facep->mTextureMatrix = new LLMatrix4(); + } + + LLMatrix4& tex_mat = *facep->mTextureMatrix; + tex_mat.setIdentity(); + LLVector3 trans ; + + trans.set(LLVector3(off_s+0.5f, off_t+0.5f, 0.f)); + tex_mat.translate(LLVector3(-0.5f, -0.5f, 0.f)); + + LLVector3 scale(scale_s, scale_t, 1.f); + LLQuaternion quat; + quat.setQuat(rot, 0, 0, -1.f); + + tex_mat.rotate(quat); + + LLMatrix4 mat; + mat.initAll(scale, LLQuaternion(), LLVector3()); + tex_mat *= mat; + + tex_mat.translate(trans); + } + } + else + { + if (mTexAnimMode && mTextureAnimp->mRate == 0) + { + U8 start, count; + + if (mTextureAnimp->mFace == -1) + { + start = 0; + count = getNumTEs(); + } + else + { + start = (U8) mTextureAnimp->mFace; + count = 1; + } + + for (S32 i = start; i < start + count; i++) + { + if (mTexAnimMode & LLViewerTextureAnim::TRANSLATE) + { + setTEOffset(i, mTextureAnimp->mOffS, mTextureAnimp->mOffT); + } + if (mTexAnimMode & LLViewerTextureAnim::SCALE) + { + setTEScale(i, mTextureAnimp->mScaleS, mTextureAnimp->mScaleT); + } + if (mTexAnimMode & LLViewerTextureAnim::ROTATE) + { + setTERotation(i, mTextureAnimp->mRot); + } + } + + gPipeline.markTextured(mDrawable); + mFaceMappingChanged = true; + mTexAnimMode = 0; + } + } + } +} + +void LLVOVolume::updateTextures() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_TEXTURE; + updateTextureVirtualSize(); +} + +bool LLVOVolume::isVisible() const +{ + if(mDrawable.notNull() && mDrawable->isVisible()) + { + return true ; + } + + if(isAttachment()) + { + LLViewerObject* objp = (LLViewerObject*)getParent() ; + while(objp && !objp->isAvatar()) + { + objp = (LLViewerObject*)objp->getParent() ; + } + + return objp && objp->mDrawable.notNull() && objp->mDrawable->isVisible() ; + } + + return false ; +} + +void LLVOVolume::updateTextureVirtualSize(bool forced) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + // Update the pixel area of all faces + + if (mDrawable.isNull() || gCubeSnapshot) + { + return; + } + + if(!forced) + { + if(!isVisible()) + { //don't load textures for non-visible faces + const S32 num_faces = mDrawable->getNumFaces(); + for (S32 i = 0; i < num_faces; i++) + { + LLFace* face = mDrawable->getFace(i); + if (face) + { + face->setPixelArea(0.f); + face->setVirtualSize(0.f); + } + } + + return ; + } + + if (!gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_SIMPLE)) + { + return; + } + } + + static LLCachedControl dont_load_textures(gSavedSettings,"TextureDisable", false); + + if (dont_load_textures || LLAppViewer::getTextureFetch()->mDebugPause) // || !mDrawable->isVisible()) + { + return; + } + + mTextureUpdateTimer.reset(); + + F32 old_area = mPixelArea; + mPixelArea = 0.f; + + const S32 num_faces = mDrawable->getNumFaces(); + F32 min_vsize=999999999.f, max_vsize=0.f; + LLViewerCamera* camera = LLViewerCamera::getInstance(); + std::stringstream debug_text; + for (S32 i = 0; i < num_faces; i++) + { + LLFace* face = mDrawable->getFace(i); + if (!face) continue; + const LLTextureEntry *te = face->getTextureEntry(); + LLViewerTexture *imagep = face->getTexture(); + if (!imagep || !te || + face->mExtents[0].equals3(face->mExtents[1])) + { + continue; + } + + F32 vsize; + F32 old_size = face->getVirtualSize(); + + if (isHUDAttachment()) + { + F32 area = (F32) camera->getScreenPixelArea(); + vsize = area; + imagep->setBoostLevel(LLGLTexture::BOOST_HUD); + face->setPixelArea(area); // treat as full screen + face->setVirtualSize(vsize); + } + else + { + vsize = face->getTextureVirtualSize(); + } + + mPixelArea = llmax(mPixelArea, face->getPixelArea()); + + // if the face has gotten small enough to turn off texture animation and texture + // animation is running, rebuild the render batch for this face to turn off + // texture animation + if (face->mTextureMatrix != NULL) + { + if ((vsize < MIN_TEX_ANIM_SIZE && old_size > MIN_TEX_ANIM_SIZE) || + (vsize > MIN_TEX_ANIM_SIZE && old_size < MIN_TEX_ANIM_SIZE)) + { + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_TCOORD); + } + } + + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_PRIORITY)) + { + LLViewerFetchedTexture* img = LLViewerTextureManager::staticCastToFetchedTexture(imagep) ; + if(img) + { + debug_text << img->getDiscardLevel() << ":" << img->getDesiredDiscardLevel() << ":" << img->getWidth() << ":" << (S32) sqrtf(vsize) << ":" << (S32) sqrtf(img->getMaxVirtualSize()) << "\n"; + /*F32 pri = img->getDecodePriority(); + pri = llmax(pri, 0.0f); + if (pri < min_vsize) min_vsize = pri; + if (pri > max_vsize) max_vsize = pri;*/ + } + } + else if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_FACE_AREA)) + { + F32 pri = mPixelArea; + if (pri < min_vsize) min_vsize = pri; + if (pri > max_vsize) max_vsize = pri; + } + } + + if (isSculpted()) + { + updateSculptTexture(); + + + + if (mSculptTexture.notNull()) + { + mSculptTexture->setBoostLevel(llmax((S32)mSculptTexture->getBoostLevel(), + (S32)LLGLTexture::BOOST_SCULPTED)); + mSculptTexture->setForSculpt() ; + + if(!mSculptTexture->isCachedRawImageReady()) + { + S32 lod = llmin(mLOD, 3); + F32 lodf = ((F32)(lod + 1.0f)/4.f); + F32 tex_size = lodf * LLViewerTexture::sMaxSculptRez ; + mSculptTexture->addTextureStats(2.f * tex_size * tex_size, false); + } + + S32 texture_discard = mSculptTexture->getCachedRawImageLevel(); //try to match the texture + S32 current_discard = getVolume() ? getVolume()->getSculptLevel() : -2 ; + + if (texture_discard >= 0 && //texture has some data available + (texture_discard < current_discard || //texture has more data than last rebuild + current_discard < 0)) //no previous rebuild + { + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); + mSculptChanged = true; + } + + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_SCULPTED)) + { + setDebugText(llformat("T%d C%d V%d\n%dx%d", + texture_discard, current_discard, getVolume()->getSculptLevel(), + mSculptTexture->getHeight(), mSculptTexture->getWidth())); + } + } + + } + + if (getLightTextureID().notNull()) + { + LLLightImageParams* params = (LLLightImageParams*) getParameterEntry(LLNetworkData::PARAMS_LIGHT_IMAGE); + LLUUID id = params->getLightTexture(); + mLightTexture = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE); + if (mLightTexture.notNull()) + { + F32 rad = getLightRadius(); + mLightTexture->addTextureStats(gPipeline.calcPixelArea(getPositionAgent(), + LLVector3(rad,rad,rad), + *camera)); + } + } + + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_AREA)) + { + setDebugText(llformat("%.0f:%.0f", (F32) sqrt(min_vsize),(F32) sqrt(max_vsize))); + } + else if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TEXTURE_PRIORITY)) + { + //setDebugText(llformat("%.0f:%.0f", (F32) sqrt(min_vsize),(F32) sqrt(max_vsize))); + setDebugText(debug_text.str()); + } + else if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_FACE_AREA)) + { + setDebugText(llformat("%.0f:%.0f", (F32) sqrt(min_vsize),(F32) sqrt(max_vsize))); + } + + if (mPixelArea == 0) + { //flexi phasing issues make this happen + mPixelArea = old_area; + } +} + +bool LLVOVolume::isActive() const +{ + return !mStatic; +} + +bool LLVOVolume::setMaterial(const U8 material) +{ + bool res = LLViewerObject::setMaterial(material); + + return res; +} + +void LLVOVolume::setTexture(const S32 face) +{ + llassert(face < getNumTEs()); + gGL.getTexUnit(0)->bind(getTEImage(face)); +} + +void LLVOVolume::setScale(const LLVector3 &scale, bool damped) +{ + if (scale != getScale()) + { + // store local radius + LLViewerObject::setScale(scale); + + if (mVolumeImpl) + { + mVolumeImpl->onSetScale(scale, damped); + } + + updateRadius(); + + //since drawable transforms do not include scale, changing volume scale + //requires an immediate rebuild of volume verts. + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_POSITION); + + if (mDrawable) + { + shrinkWrap(); + } + } +} + +LLFace* LLVOVolume::addFace(S32 f) +{ + const LLTextureEntry* te = getTE(f); + LLViewerTexture* imagep = getTEImage(f); + if (te->getMaterialParams().notNull()) + { + LLViewerTexture* normalp = getTENormalMap(f); + LLViewerTexture* specularp = getTESpecularMap(f); + return mDrawable->addFace(te, imagep, normalp, specularp); + } + return mDrawable->addFace(te, imagep); +} + +LLDrawable *LLVOVolume::createDrawable(LLPipeline *pipeline) +{ + pipeline->allocDrawable(this); + + mDrawable->setRenderType(LLPipeline::RENDER_TYPE_VOLUME); + + S32 max_tes_to_set = getNumTEs(); + for (S32 i = 0; i < max_tes_to_set; i++) + { + addFace(i); + } + mNumFaces = max_tes_to_set; + + if (isAttachment()) + { + mDrawable->makeActive(); + } + + if (getIsLight()) + { + // Add it to the pipeline mLightSet + gPipeline.setLight(mDrawable, true); + } + + if (isReflectionProbe()) + { + updateReflectionProbePtr(); + } + + updateRadius(); + bool force_update = true; // avoid non-alpha mDistance update being optimized away + mDrawable->updateDistance(*LLViewerCamera::getInstance(), force_update); + + return mDrawable; +} + +bool LLVOVolume::setVolume(const LLVolumeParams ¶ms_in, const S32 detail, bool unique_volume) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + LLVolumeParams volume_params = params_in; + + S32 last_lod = mVolumep.notNull() ? LLVolumeLODGroup::getVolumeDetailFromScale(mVolumep->getDetail()) : -1; + S32 lod = mLOD; + + bool is404 = false; + + if (isSculpted()) + { + // if it's a mesh + if ((volume_params.getSculptType() & LL_SCULPT_TYPE_MASK) == LL_SCULPT_TYPE_MESH) + { //meshes might not have all LODs, get the force detail to best existing LOD + if (NO_LOD != lod) + { + lod = gMeshRepo.getActualMeshLOD(volume_params, lod); + if (lod == -1) + { + is404 = true; + lod = 0; + } + } + } + } + + // Check if we need to change implementations + bool is_flexible = (volume_params.getPathParams().getCurveType() == LL_PCODE_PATH_FLEXIBLE); + if (is_flexible) + { + setParameterEntryInUse(LLNetworkData::PARAMS_FLEXIBLE, true, false); + if (!mVolumeImpl) + { + LLFlexibleObjectData* data = (LLFlexibleObjectData*)getParameterEntry(LLNetworkData::PARAMS_FLEXIBLE); + mVolumeImpl = new LLVolumeImplFlexible(this, data); + } + } + else + { + // Mark the parameter not in use + setParameterEntryInUse(LLNetworkData::PARAMS_FLEXIBLE, false, false); + if (mVolumeImpl) + { + delete mVolumeImpl; + mVolumeImpl = NULL; + if (mDrawable.notNull()) + { + // Undo the damage we did to this matrix + mDrawable->updateXform(false); + } + } + } + + if (is404) + { + setIcon(LLViewerTextureManager::getFetchedTextureFromFile("icons/Inv_Mesh.png", FTT_LOCAL_FILE, true, LLGLTexture::BOOST_UI)); + //render prim proxy when mesh loading attempts give up + volume_params.setSculptID(LLUUID::null, LL_SCULPT_TYPE_NONE); + + } + + if ((LLPrimitive::setVolume(volume_params, lod, (mVolumeImpl && mVolumeImpl->isVolumeUnique()))) || mSculptChanged) + { + mFaceMappingChanged = true; + + if (mVolumeImpl) + { + mVolumeImpl->onSetVolume(volume_params, mLOD); + } + + updateSculptTexture(); + + if (isSculpted()) + { + updateSculptTexture(); + // if it's a mesh + if ((volume_params.getSculptType() & LL_SCULPT_TYPE_MASK) == LL_SCULPT_TYPE_MESH) + { + if (mSkinInfo && mSkinInfo->mMeshID != volume_params.getSculptID()) + { + mSkinInfo = NULL; + mSkinInfoUnavaliable = false; + } + + if (!getVolume()->isMeshAssetLoaded()) + { + //load request not yet issued, request pipeline load this mesh + S32 available_lod = gMeshRepo.loadMesh(this, volume_params, lod, last_lod); + if (available_lod != lod) + { + LLPrimitive::setVolume(volume_params, available_lod); + } + } + + if (!mSkinInfo && !mSkinInfoUnavaliable) + { + LLUUID mesh_id = volume_params.getSculptID(); + if (gMeshRepo.hasHeader(mesh_id) && !gMeshRepo.hasSkinInfo(mesh_id)) + { + // If header is present but has no data about skin, + // no point fetching + mSkinInfoUnavaliable = true; + } + + if (!mSkinInfoUnavaliable) + { + const LLMeshSkinInfo* skin_info = gMeshRepo.getSkinInfo(mesh_id, this); + if (skin_info) + { + notifySkinInfoLoaded(skin_info); + } + } + } + } + else // otherwise is sculptie + { + if (mSculptTexture.notNull()) + { + sculpt(); + } + } + } + + return true; + } + else if (NO_LOD == lod) + { + LLSculptIDSize::instance().resetSizeSum(volume_params.getSculptID()); + } + + return false; +} + +void LLVOVolume::updateSculptTexture() +{ + LLPointer old_sculpt = mSculptTexture; + + if (isSculpted() && !isMesh()) + { + LLSculptParams *sculpt_params = (LLSculptParams *)getParameterEntry(LLNetworkData::PARAMS_SCULPT); + LLUUID id = sculpt_params->getSculptTexture(); + if (id.notNull()) + { + mSculptTexture = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + } + + mSkinInfoUnavaliable = false; + mSkinInfo = NULL; + } + else + { + mSculptTexture = NULL; + } + + if (mSculptTexture != old_sculpt) + { + if (old_sculpt.notNull()) + { + old_sculpt->removeVolume(LLRender::SCULPT_TEX, this); + } + if (mSculptTexture.notNull()) + { + mSculptTexture->addVolume(LLRender::SCULPT_TEX, this); + } + } + +} + +void LLVOVolume::updateVisualComplexity() +{ + LLVOAvatar* avatar = getAvatarAncestor(); + if (avatar) + { + avatar->updateVisualComplexity(); + } + LLVOAvatar* rigged_avatar = getAvatar(); + if(rigged_avatar && (rigged_avatar != avatar)) + { + rigged_avatar->updateVisualComplexity(); + } +} + +void LLVOVolume::notifyMeshLoaded() +{ + mSculptChanged = true; + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_GEOMETRY); + + if (!mSkinInfo && !mSkinInfoUnavaliable) + { + // Header was loaded, update skin info state from header + LLUUID mesh_id = getVolume()->getParams().getSculptID(); + if (!gMeshRepo.hasSkinInfo(mesh_id)) + { + mSkinInfoUnavaliable = true; + } + } + + LLVOAvatar *av = getAvatar(); + if (av && !isAnimatedObject()) + { + av->addAttachmentOverridesForObject(this); + av->notifyAttachmentMeshLoaded(); + } + LLControlAvatar *cav = getControlAvatar(); + if (cav && isAnimatedObject()) + { + cav->addAttachmentOverridesForObject(this); + cav->notifyAttachmentMeshLoaded(); + } + updateVisualComplexity(); +} + +void LLVOVolume::notifySkinInfoLoaded(const LLMeshSkinInfo* skin) +{ + mSkinInfoUnavaliable = false; + mSkinInfo = skin; + + notifyMeshLoaded(); +} + +void LLVOVolume::notifySkinInfoUnavailable() +{ + mSkinInfoUnavaliable = true; + mSkinInfo = nullptr; +} + +// sculpt replaces generate() for sculpted surfaces +void LLVOVolume::sculpt() +{ + if (mSculptTexture.notNull()) + { + U16 sculpt_height = 0; + U16 sculpt_width = 0; + S8 sculpt_components = 0; + const U8* sculpt_data = NULL; + + S32 discard_level = mSculptTexture->getCachedRawImageLevel() ; + LLImageRaw* raw_image = mSculptTexture->getCachedRawImage() ; + + S32 max_discard = mSculptTexture->getMaxDiscardLevel(); + if (discard_level > max_discard) + { + discard_level = max_discard; // clamp to the best we can do + } + if(discard_level > MAX_DISCARD_LEVEL) + { + return; //we think data is not ready yet. + } + + S32 current_discard = getVolume()->getSculptLevel() ; + if(current_discard < -2) + { + static S32 low_sculpty_discard_warning_count = 1; + S32 exponent = llmax(1, llfloor( log10((F64) low_sculpty_discard_warning_count) )); + S32 interval = pow(10.0, exponent); + if ( low_sculpty_discard_warning_count < 10 || + (low_sculpty_discard_warning_count % interval) == 0) + { // Log first 10 time, then decreasing intervals afterwards otherwise this can flood the logs + LL_WARNS() << "WARNING!!: Current discard for sculpty " << mSculptTexture->getID() + << " at " << current_discard + << " is less than -2." + << " Hit this " << low_sculpty_discard_warning_count << " times" + << LL_ENDL; + } + low_sculpty_discard_warning_count++; + + // corrupted volume... don't update the sculpty + return; + } + else if (current_discard > MAX_DISCARD_LEVEL) + { + static S32 high_sculpty_discard_warning_count = 1; + S32 exponent = llmax(1, llfloor( log10((F64) high_sculpty_discard_warning_count) )); + S32 interval = pow(10.0, exponent); + if ( high_sculpty_discard_warning_count < 10 || + (high_sculpty_discard_warning_count % interval) == 0) + { // Log first 10 time, then decreasing intervals afterwards otherwise this can flood the logs + LL_WARNS() << "WARNING!!: Current discard for sculpty " << mSculptTexture->getID() + << " at " << current_discard + << " is more than than allowed max of " << MAX_DISCARD_LEVEL + << ". Hit this " << high_sculpty_discard_warning_count << " times" + << LL_ENDL; + } + high_sculpty_discard_warning_count++; + + // corrupted volume... don't update the sculpty + return; + } + + if (current_discard == discard_level) // no work to do here + return; + + if(!raw_image) + { + llassert(discard_level < 0) ; + + sculpt_width = 0; + sculpt_height = 0; + sculpt_data = NULL ; + + if(LLViewerTextureManager::sTesterp) + { + LLViewerTextureManager::sTesterp->updateGrayTextureBinding(); + } + } + else + { + LLImageDataSharedLock lock(raw_image); + + sculpt_height = raw_image->getHeight(); + sculpt_width = raw_image->getWidth(); + sculpt_components = raw_image->getComponents(); + + sculpt_data = raw_image->getData(); + + if(LLViewerTextureManager::sTesterp) + { + mSculptTexture->updateBindStatsForTester() ; + } + } + getVolume()->sculpt(sculpt_width, sculpt_height, sculpt_components, sculpt_data, discard_level, mSculptTexture->isMissingAsset()); + + //notify rebuild any other VOVolumes that reference this sculpty volume + for (S32 i = 0; i < mSculptTexture->getNumVolumes(LLRender::SCULPT_TEX); ++i) + { + LLVOVolume* volume = (*(mSculptTexture->getVolumeList(LLRender::SCULPT_TEX)))[i]; + if (volume != this && volume->getVolume() == getVolume()) + { + gPipeline.markRebuild(volume->mDrawable, LLDrawable::REBUILD_GEOMETRY); + } + } + } +} + +S32 LLVOVolume::computeLODDetail(F32 distance, F32 radius, F32 lod_factor) +{ + S32 cur_detail; + if (LLPipeline::sDynamicLOD) + { + // We've got LOD in the profile, and in the twist. Use radius. + F32 tan_angle = (lod_factor*radius)/distance; + cur_detail = LLVolumeLODGroup::getDetailFromTan(ll_round(tan_angle, 0.01f)); + } + else + { + cur_detail = llclamp((S32) (sqrtf(radius)*lod_factor*4.f), 0, 3); + } + return cur_detail; +} + +std::string get_debug_object_lod_text(LLVOVolume *rootp) +{ + std::string cam_dist_string = ""; + cam_dist_string += LLStringOps::getReadableNumber(rootp->mLODDistance) + " "; + std::string lod_string = llformat("%d",rootp->getLOD()); + F32 lod_radius = rootp->mLODRadius; + S32 cam_dist_count = 0; + LLViewerObject::const_child_list_t& child_list = rootp->getChildren(); + for (LLViewerObject::const_child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); ++iter) + { + LLViewerObject *childp = *iter; + LLVOVolume *volp = dynamic_cast(childp); + if (volp) + { + lod_string += llformat("%d",volp->getLOD()); + if (volp->isRiggedMesh()) + { + // Rigged/animatable mesh. This is computed from the + // avatar dynamic box, so value from any vol will be + // the same. + lod_radius = volp->mLODRadius; + } + if (volp->mDrawable) + { + if (cam_dist_count < 4) + { + cam_dist_string += LLStringOps::getReadableNumber(volp->mLODDistance) + " "; + cam_dist_count++; + } + } + } + } + std::string result = llformat("lod_radius %s dists %s lods %s", + LLStringOps::getReadableNumber(lod_radius).c_str(), + cam_dist_string.c_str(), + lod_string.c_str()); + return result; +} + +bool LLVOVolume::calcLOD() +{ + if (mDrawable.isNull()) + { + return false; + } + + S32 cur_detail = 0; + + F32 radius; + F32 distance; + F32 lod_factor = LLVOVolume::sLODFactor; + + if (mDrawable->isState(LLDrawable::RIGGED)) + { + LLVOAvatar* avatar = getAvatar(); + + // Not sure how this can really happen, but alas it does. Better exit here than crashing. + if( !avatar || !avatar->mDrawable ) + { + return false; + } + + distance = avatar->mDrawable->mDistanceWRTCamera; + + + if (avatar->isControlAvatar()) + { + // MAINT-7926 Handle volumes in an animated object as a special case + const LLVector3* box = avatar->getLastAnimExtents(); + LLVector3 diag = box[1] - box[0]; + radius = diag.magVec() * 0.5f; + LL_DEBUGS("DynamicBox") << avatar->getFullname() << " diag " << diag << " radius " << radius << LL_ENDL; + } + else + { + // Volume in a rigged mesh attached to a regular avatar. + // Note this isn't really a radius, so distance calcs are off by factor of 2 + //radius = avatar->getBinRadius(); + // SL-937: add dynamic box handling for rigged mesh on regular avatars. + const LLVector3* box = avatar->getLastAnimExtents(); + LLVector3 diag = box[1] - box[0]; + radius = diag.magVec(); // preserve old BinRadius behavior - 2x off + LL_DEBUGS("DynamicBox") << avatar->getFullname() << " diag " << diag << " radius " << radius << LL_ENDL; + } + if (distance <= 0.f || radius <= 0.f) + { + LL_DEBUGS("DynamicBox","CalcLOD") << "avatar distance/radius uninitialized, skipping" << LL_ENDL; + return false; + } + } + else + { + distance = mDrawable->mDistanceWRTCamera; + radius = getVolume() ? getVolume()->mLODScaleBias.scaledVec(getScale()).length() : getScale().length(); + if (distance <= 0.f || radius <= 0.f) + { + LL_DEBUGS("DynamicBox","CalcLOD") << "non-avatar distance/radius uninitialized, skipping" << LL_ENDL; + return false; + } + } + + //hold onto unmodified distance for debugging + //F32 debug_distance = distance; + + mLODDistance = distance; + mLODRadius = radius; + + static LLCachedControl debug_lods(gSavedSettings, "DebugObjectLODs", false); + if (debug_lods) + { + if (getAvatar() && isRootEdit()) + { + std::string debug_object_text = get_debug_object_lod_text(this); + setDebugText(debug_object_text); + mResetDebugText = true; + } + } + else + { + if (mResetDebugText) + { + restoreHudText(); + mResetDebugText = false; + } + } + + distance *= sDistanceFactor; + + F32 rampDist = LLVOVolume::sLODFactor * 2; + + if (distance < rampDist) + { + // Boost LOD when you're REALLY close + distance *= 1.0f/rampDist; + distance *= distance; + distance *= rampDist; + } + + + distance *= F_PI/3.f; + + static LLCachedControl ignore_fov_zoom(gSavedSettings,"IgnoreFOVZoomForLODs"); + if(!ignore_fov_zoom) + { + lod_factor *= DEFAULT_FIELD_OF_VIEW / LLViewerCamera::getInstance()->getDefaultFOV(); + } + + mLODAdjustedDistance = distance; + + if (isHUDAttachment()) + { + // HUDs always show at highest detail + cur_detail = 3; + } + else + { + cur_detail = computeLODDetail(ll_round(distance, 0.01f), ll_round(radius, 0.01f), lod_factor); + } + + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_TRIANGLE_COUNT) && mDrawable->getFace(0)) + { + if (isRootEdit()) + { + S32 total_tris = recursiveGetTriangleCount(); + S32 est_max_tris = recursiveGetEstTrianglesMax(); + setDebugText(llformat("TRIS SHOWN %d EST %d", total_tris, est_max_tris)); + } + } + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_LOD_INFO) && + mDrawable->getFace(0)) + { + // This is a debug display for LODs. Please don't put the texture index here. + setDebugText(llformat("%d", cur_detail)); + } + + if (cur_detail != mLOD) + { + LL_DEBUGS("DynamicBox","CalcLOD") << "new LOD " << cur_detail << " change from " << mLOD + << " distance " << distance << " radius " << radius << " rampDist " << rampDist + << " drawable rigged? " << (mDrawable ? (S32) mDrawable->isState(LLDrawable::RIGGED) : (S32) -1) + << " mRiggedVolume " << (void*)getRiggedVolume() + << " distanceWRTCamera " << (mDrawable ? mDrawable->mDistanceWRTCamera : -1.f) + << LL_ENDL; + + mAppAngle = ll_round((F32) atan2( mDrawable->getRadius(), mDrawable->mDistanceWRTCamera) * RAD_TO_DEG, 0.01f); + mLOD = cur_detail; + + return true; + } + + return false; +} + +bool LLVOVolume::updateLOD() +{ + if (mDrawable.isNull()) + { + return false; + } + + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + + bool lod_changed = false; + + if (!LLSculptIDSize::instance().isUnloaded(getVolume()->getParams().getSculptID())) + { + lod_changed = calcLOD(); + } + else + { + return false; + } + + if (lod_changed) + { + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); + mLODChanged = true; + } + else + { + F32 new_radius = getBinRadius(); + F32 old_radius = mDrawable->getBinRadius(); + if (new_radius < old_radius * 0.9f || new_radius > old_radius*1.1f) + { + gPipeline.markPartitionMove(mDrawable); + } + } + + lod_changed = lod_changed || LLViewerObject::updateLOD(); + + return lod_changed; +} + +bool LLVOVolume::setDrawableParent(LLDrawable* parentp) +{ + if (!LLViewerObject::setDrawableParent(parentp)) + { + // no change in drawable parent + return false; + } + + if (!mDrawable->isRoot()) + { + // rebuild vertices in parent relative space + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); + + if (mDrawable->isActive() && !parentp->isActive()) + { + parentp->makeActive(); + } + else if (mDrawable->isStatic() && parentp->isActive()) + { + mDrawable->makeActive(); + } + } + + return true; +} + +void LLVOVolume::updateFaceFlags() +{ + // There's no guarantee that getVolume()->getNumFaces() == mDrawable->getNumFaces() + for (S32 i = 0; i < getVolume()->getNumFaces() && i < mDrawable->getNumFaces(); i++) + { + LLFace *face = mDrawable->getFace(i); + if (face) + { + bool fullbright = getTE(i)->getFullbright(); + face->clearState(LLFace::FULLBRIGHT | LLFace::HUD_RENDER | LLFace::LIGHT); + + if (fullbright || (mMaterial == LL_MCODE_LIGHT)) + { + face->setState(LLFace::FULLBRIGHT); + } + if (mDrawable->isLight()) + { + face->setState(LLFace::LIGHT); + } + if (isHUDAttachment()) + { + face->setState(LLFace::HUD_RENDER); + } + } + } +} + +bool LLVOVolume::setParent(LLViewerObject* parent) +{ + bool ret = false ; + LLViewerObject *old_parent = (LLViewerObject*) getParent(); + if (parent != old_parent) + { + ret = LLViewerObject::setParent(parent); + if (ret && mDrawable) + { + gPipeline.markMoved(mDrawable); + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); + } + onReparent(old_parent, parent); + } + + return ret ; +} + +// NOTE: regenFaces() MUST be followed by genTriangles()! +void LLVOVolume::regenFaces() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + // remove existing faces + bool count_changed = mNumFaces != getNumTEs(); + + if (count_changed) + { + deleteFaces(); + // add new faces + mNumFaces = getNumTEs(); + } + + for (S32 i = 0; i < mNumFaces; i++) + { + LLFace* facep = count_changed ? addFace(i) : mDrawable->getFace(i); + if (!facep) continue; + + facep->setTEOffset(i); + facep->setTexture(getTEImage(i)); + if (facep->getTextureEntry()->getMaterialParams().notNull()) + { + facep->setNormalMap(getTENormalMap(i)); + facep->setSpecularMap(getTESpecularMap(i)); + } + facep->setViewerObject(this); + + // If the face had media on it, this will have broken the link between the LLViewerMediaTexture and the face. + // Re-establish the link. + if((int)mMediaImplList.size() > i) + { + if(mMediaImplList[i]) + { + LLViewerMediaTexture* media_tex = LLViewerTextureManager::findMediaTexture(mMediaImplList[i]->getMediaTextureID()) ; + if(media_tex) + { + media_tex->addMediaToFace(facep) ; + } + } + } + } + + if (!count_changed) + { + updateFaceFlags(); + } +} + +bool LLVOVolume::genBBoxes(bool force_global, bool should_update_octree_bounds) +{ + LL_PROFILE_ZONE_SCOPED; + bool res = true; + + LLVector4a min, max; + + min.clear(); + max.clear(); + + bool rebuild = mDrawable->isState(LLDrawable::REBUILD_VOLUME | LLDrawable::REBUILD_POSITION | LLDrawable::REBUILD_RIGGED); + + if (getRiggedVolume()) + { + // MAINT-8264 - better to use the existing call in calling + // func LLVOVolume::updateGeometry() if we can detect when + // updates needed, set REBUILD_RIGGED accordingly. + + // Without the flag, this will remove unused rigged volumes, which we are not currently very aggressive about. + updateRiggedVolume(false); + } + + LLVolume* volume = mRiggedVolume; + if (!volume) + { + volume = getVolume(); + } + + bool any_valid_boxes = false; + + if (getRiggedVolume()) + { + LL_DEBUGS("RiggedBox") << "rebuilding box, volume face count " << getVolume()->getNumVolumeFaces() << " drawable face count " << mDrawable->getNumFaces() << LL_ENDL; + } + + // There's no guarantee that getVolume()->getNumFaces() == mDrawable->getNumFaces() + for (S32 i = 0; + i < getVolume()->getNumVolumeFaces() && i < mDrawable->getNumFaces() && i < getNumTEs(); + i++) + { + LLFace* face = mDrawable->getFace(i); + if (!face) + { + continue; + } + + bool face_res = face->genVolumeBBoxes(*volume, i, + mRelativeXform, + (mVolumeImpl && mVolumeImpl->isVolumeGlobal()) || force_global); + res &= face_res; // note that this result is never used + + // MAINT-8264 - ignore bboxes of ill-formed faces. + if (!face_res) + { + continue; + } + if (rebuild) + { + if (getRiggedVolume()) + { + LL_DEBUGS("RiggedBox") << "rebuilding box, face " << i << " extents " << face->mExtents[0] << ", " << face->mExtents[1] << LL_ENDL; + } + if (!any_valid_boxes) + { + min = face->mExtents[0]; + max = face->mExtents[1]; + any_valid_boxes = true; + } + else + { + min.setMin(min, face->mExtents[0]); + max.setMax(max, face->mExtents[1]); + } + } + } + + if (any_valid_boxes) + { + if (rebuild && should_update_octree_bounds) + { + //get the Avatar associated with this object if it's rigged + LLVOAvatar* avatar = nullptr; + if (isRiggedMesh()) + { + if (!isAnimatedObject()) + { + if (isAttachment()) + { + avatar = getAvatar(); + } + } + else + { + LLControlAvatar* controlAvatar = getControlAvatar(); + if (controlAvatar && controlAvatar->mPlaying) + { + avatar = controlAvatar; + } + } + } + + mDrawable->setSpatialExtents(min, max); + + if (avatar) + { + // put all rigged drawables in the same octree node for better batching + mDrawable->setPositionGroup(LLVector4a(0, 0, 0)); + } + else + { + min.add(max); + min.mul(0.5f); + mDrawable->setPositionGroup(min); + } + } + + updateRadius(); + mDrawable->movePartition(); + } + else + { + LL_DEBUGS("RiggedBox") << "genBBoxes failed to find any valid face boxes" << LL_ENDL; + } + + return res; +} + +void LLVOVolume::preRebuild() +{ + if (mVolumeImpl != NULL) + { + mVolumeImpl->preRebuild(); + } +} + +void LLVOVolume::updateRelativeXform(bool force_identity) +{ + if (mVolumeImpl) + { + mVolumeImpl->updateRelativeXform(force_identity); + return; + } + + LLDrawable* drawable = mDrawable; + + if (drawable->isState(LLDrawable::RIGGED) && mRiggedVolume.notNull()) + { //rigged volume (which is in agent space) is used for generating bounding boxes etc + //inverse of render matrix should go to partition space + mRelativeXform = getRenderMatrix(); + + F32* dst = (F32*) mRelativeXformInvTrans.mMatrix; + F32* src = (F32*) mRelativeXform.mMatrix; + dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; + dst[3] = src[4]; dst[4] = src[5]; dst[5] = src[6]; + dst[6] = src[8]; dst[7] = src[9]; dst[8] = src[10]; + + mRelativeXform.invert(); + mRelativeXformInvTrans.transpose(); + } + else if (drawable->isActive() || force_identity) + { + // setup relative transforms + LLQuaternion delta_rot; + LLVector3 delta_pos, delta_scale; + + //matrix from local space to parent relative/global space + bool use_identity = force_identity || drawable->isSpatialRoot(); + delta_rot = use_identity ? LLQuaternion() : mDrawable->getRotation(); + delta_pos = use_identity ? LLVector3(0,0,0) : mDrawable->getPosition(); + delta_scale = mDrawable->getScale(); + + // Vertex transform (4x4) + LLVector3 x_axis = LLVector3(delta_scale.mV[VX], 0.f, 0.f) * delta_rot; + LLVector3 y_axis = LLVector3(0.f, delta_scale.mV[VY], 0.f) * delta_rot; + LLVector3 z_axis = LLVector3(0.f, 0.f, delta_scale.mV[VZ]) * delta_rot; + + mRelativeXform.initRows(LLVector4(x_axis, 0.f), + LLVector4(y_axis, 0.f), + LLVector4(z_axis, 0.f), + LLVector4(delta_pos, 1.f)); + + + // compute inverse transpose for normals + // mRelativeXformInvTrans.setRows(x_axis, y_axis, z_axis); + // mRelativeXformInvTrans.invert(); + // mRelativeXformInvTrans.setRows(x_axis, y_axis, z_axis); + // grumble - invert is NOT a matrix invert, so we do it by hand: + + LLMatrix3 rot_inverse = LLMatrix3(~delta_rot); + + LLMatrix3 scale_inverse; + scale_inverse.setRows(LLVector3(1.0, 0.0, 0.0) / delta_scale.mV[VX], + LLVector3(0.0, 1.0, 0.0) / delta_scale.mV[VY], + LLVector3(0.0, 0.0, 1.0) / delta_scale.mV[VZ]); + + + mRelativeXformInvTrans = rot_inverse * scale_inverse; + + mRelativeXformInvTrans.transpose(); + } + else + { + LLVector3 pos = getPosition(); + LLVector3 scale = getScale(); + LLQuaternion rot = getRotation(); + + if (mParent) + { + pos *= mParent->getRotation(); + pos += mParent->getPosition(); + rot *= mParent->getRotation(); + } + + //LLViewerRegion* region = getRegion(); + //pos += region->getOriginAgent(); + + LLVector3 x_axis = LLVector3(scale.mV[VX], 0.f, 0.f) * rot; + LLVector3 y_axis = LLVector3(0.f, scale.mV[VY], 0.f) * rot; + LLVector3 z_axis = LLVector3(0.f, 0.f, scale.mV[VZ]) * rot; + + mRelativeXform.initRows(LLVector4(x_axis, 0.f), + LLVector4(y_axis, 0.f), + LLVector4(z_axis, 0.f), + LLVector4(pos, 1.f)); + + // compute inverse transpose for normals + LLMatrix3 rot_inverse = LLMatrix3(~rot); + + LLMatrix3 scale_inverse; + scale_inverse.setRows(LLVector3(1.0, 0.0, 0.0) / scale.mV[VX], + LLVector3(0.0, 1.0, 0.0) / scale.mV[VY], + LLVector3(0.0, 0.0, 1.0) / scale.mV[VZ]); + + + mRelativeXformInvTrans = rot_inverse * scale_inverse; + + mRelativeXformInvTrans.transpose(); + } +} + +bool LLVOVolume::lodOrSculptChanged(LLDrawable *drawable, bool &compiled, bool &should_update_octree_bounds) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + bool regen_faces = false; + + LLVolume *old_volumep, *new_volumep; + F32 old_lod, new_lod; + S32 old_num_faces, new_num_faces; + + old_volumep = getVolume(); + old_lod = old_volumep->getDetail(); + old_num_faces = old_volumep->getNumFaces(); + old_volumep = NULL; + + { + const LLVolumeParams &volume_params = getVolume()->getParams(); + setVolume(volume_params, 0); + } + + new_volumep = getVolume(); + new_lod = new_volumep->getDetail(); + new_num_faces = new_volumep->getNumFaces(); + new_volumep = NULL; + + if ((new_lod != old_lod) || mSculptChanged) + { + if (mDrawable->isState(LLDrawable::RIGGED)) + { + updateVisualComplexity(); + } + + compiled = true; + // new_lod > old_lod breaks a feedback loop between LOD updates and + // bounding box updates. + should_update_octree_bounds = should_update_octree_bounds || mSculptChanged || new_lod > old_lod; + sNumLODChanges += new_num_faces; + + if ((S32)getNumTEs() != getVolume()->getNumFaces()) + { + setNumTEs(getVolume()->getNumFaces()); //mesh loading may change number of faces. + } + + drawable->setState(LLDrawable::REBUILD_VOLUME); // for face->genVolumeTriangles() + + { + regen_faces = new_num_faces != old_num_faces || mNumFaces != (S32)getNumTEs(); + if (regen_faces) + { + regenFaces(); + } + + if (mSculptChanged) + { //changes in sculpt maps can thrash an object bounding box without + //triggering a spatial group bounding box update -- force spatial group + //to update bounding boxes + LLSpatialGroup* group = mDrawable->getSpatialGroup(); + if (group) + { + group->unbound(); + } + } + } + } + + return regen_faces; +} + +bool LLVOVolume::updateGeometry(LLDrawable *drawable) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + + if (mDrawable->isState(LLDrawable::REBUILD_RIGGED)) + { + updateRiggedVolume(false); + genBBoxes(false); + mDrawable->clearState(LLDrawable::REBUILD_RIGGED); + } + + if (mVolumeImpl != NULL) + { + bool res; + { + res = mVolumeImpl->doUpdateGeometry(drawable); + } + updateFaceFlags(); + return res; + } + + LLSpatialGroup* group = drawable->getSpatialGroup(); + if (group) + { + group->dirtyMesh(); + } + + updateRelativeXform(); + + if (mDrawable.isNull()) // Not sure why this is happening, but it is... + { + return true; // No update to complete + } + + bool compiled = false; + // This should be true in most cases, unless we're sure no octree update is + // needed. + bool should_update_octree_bounds = bool(getRiggedVolume()) || mDrawable->isState(LLDrawable::REBUILD_POSITION) || !mDrawable->getSpatialExtents()->isFinite3(); + + if (mVolumeChanged || mFaceMappingChanged) + { + dirtySpatialGroup(); + + bool was_regen_faces = false; + should_update_octree_bounds = true; + + if (mVolumeChanged) + { + was_regen_faces = lodOrSculptChanged(drawable, compiled, should_update_octree_bounds); + drawable->setState(LLDrawable::REBUILD_VOLUME); + } + else if (mSculptChanged || mLODChanged || mColorChanged) + { + compiled = true; + was_regen_faces = lodOrSculptChanged(drawable, compiled, should_update_octree_bounds); + } + + if (!was_regen_faces) { + regenFaces(); + } + } + else if (mLODChanged || mSculptChanged || mColorChanged) + { + dirtySpatialGroup(); + compiled = true; + lodOrSculptChanged(drawable, compiled, should_update_octree_bounds); + + if(drawable->isState(LLDrawable::REBUILD_RIGGED | LLDrawable::RIGGED)) + { + updateRiggedVolume(false); + } + } + // it has its own drawable (it's moved) or it has changed UVs or it has changed xforms from global<->local + else + { + compiled = true; + // All it did was move or we changed the texture coordinate offset + } + + // Generate bounding boxes if needed, and update the object's size in the + // octree + genBBoxes(false, should_update_octree_bounds); + + // Update face flags + updateFaceFlags(); + + if(compiled) + { + LLPipeline::sCompiles++; + } + + mVolumeChanged = false; + mLODChanged = false; + mSculptChanged = false; + mFaceMappingChanged = false; + mColorChanged = false; + + return LLViewerObject::updateGeometry(drawable); +} + +void LLVOVolume::updateFaceSize(S32 idx) +{ + if( mDrawable->getNumFaces() <= idx ) + { + return; + } + + LLFace* facep = mDrawable->getFace(idx); + if (facep) + { + if (idx >= getVolume()->getNumVolumeFaces()) + { + facep->setSize(0,0, true); + } + else + { + const LLVolumeFace& vol_face = getVolume()->getVolumeFace(idx); + facep->setSize(vol_face.mNumVertices, vol_face.mNumIndices, + true); // <--- volume faces should be padded for 16-byte alignment + + } + } +} + +bool LLVOVolume::isRootEdit() const +{ + if (mParent && !((LLViewerObject*)mParent)->isAvatar()) + { + return false; + } + return true; +} + +//virtual +void LLVOVolume::setNumTEs(const U8 num_tes) +{ + const U8 old_num_tes = getNumTEs() ; + + if(old_num_tes && old_num_tes < num_tes) //new faces added + { + LLViewerObject::setNumTEs(num_tes) ; + + if(mMediaImplList.size() >= old_num_tes && mMediaImplList[old_num_tes -1].notNull())//duplicate the last media textures if exists. + { + mMediaImplList.resize(num_tes) ; + const LLTextureEntry* te = getTE(old_num_tes - 1) ; + for(U8 i = old_num_tes; i < num_tes ; i++) + { + setTE(i, *te) ; + mMediaImplList[i] = mMediaImplList[old_num_tes -1] ; + } + mMediaImplList[old_num_tes -1]->setUpdated(true) ; + } + } + else if(old_num_tes > num_tes && mMediaImplList.size() > num_tes) //old faces removed + { + U8 end = (U8)(mMediaImplList.size()) ; + for(U8 i = num_tes; i < end ; i++) + { + removeMediaImpl(i) ; + } + mMediaImplList.resize(num_tes) ; + + LLViewerObject::setNumTEs(num_tes) ; + } + else + { + LLViewerObject::setNumTEs(num_tes) ; + } + + return ; +} + + +//virtual +void LLVOVolume::changeTEImage(S32 index, LLViewerTexture* imagep) +{ + bool changed = (mTEImages[index] != imagep); + LLViewerObject::changeTEImage(index, imagep); + if (changed) + { + gPipeline.markTextured(mDrawable); + mFaceMappingChanged = true; + } +} + +void LLVOVolume::setTEImage(const U8 te, LLViewerTexture *imagep) +{ + bool changed = (mTEImages[te] != imagep); + LLViewerObject::setTEImage(te, imagep); + if (changed) + { + gPipeline.markTextured(mDrawable); + mFaceMappingChanged = true; + } +} + +S32 LLVOVolume::setTETexture(const U8 te, const LLUUID &uuid) +{ + S32 res = LLViewerObject::setTETexture(te, uuid); + if (res) + { + if (mDrawable) + { + // dynamic texture changes break batches, isolate in octree + shrinkWrap(); + gPipeline.markTextured(mDrawable); + } + mFaceMappingChanged = true; + } + return res; +} + +S32 LLVOVolume::setTEColor(const U8 te, const LLColor3& color) +{ + return setTEColor(te, LLColor4(color)); +} + +S32 LLVOVolume::setTEColor(const U8 te, const LLColor4& color) +{ + S32 retval = 0; + const LLTextureEntry *tep = getTE(te); + if (!tep) + { + LL_WARNS("MaterialTEs") << "No texture entry for te " << (S32)te << ", object " << mID << LL_ENDL; + } + else if (color != tep->getColor()) + { + F32 old_alpha = tep->getColor().mV[3]; + if (color.mV[3] != old_alpha) + { + gPipeline.markTextured(mDrawable); + //treat this alpha change as an LoD update since render batches may need to get rebuilt + mLODChanged = true; + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_VOLUME); + } + retval = LLPrimitive::setTEColor(te, color); + if (mDrawable.notNull() && retval) + { + // These should only happen on updates which are not the initial update. + mColorChanged = true; + mDrawable->setState(LLDrawable::REBUILD_COLOR); + shrinkWrap(); + dirtyMesh(); + } + } + + return retval; +} + +S32 LLVOVolume::setTEBumpmap(const U8 te, const U8 bumpmap) +{ + S32 res = LLViewerObject::setTEBumpmap(te, bumpmap); + if (res) + { + gPipeline.markTextured(mDrawable); + mFaceMappingChanged = true; + } + return res; +} + +S32 LLVOVolume::setTETexGen(const U8 te, const U8 texgen) +{ + S32 res = LLViewerObject::setTETexGen(te, texgen); + if (res) + { + gPipeline.markTextured(mDrawable); + mFaceMappingChanged = true; + } + return res; +} + +S32 LLVOVolume::setTEMediaTexGen(const U8 te, const U8 media) +{ + S32 res = LLViewerObject::setTEMediaTexGen(te, media); + if (res) + { + gPipeline.markTextured(mDrawable); + mFaceMappingChanged = true; + } + return res; +} + +S32 LLVOVolume::setTEShiny(const U8 te, const U8 shiny) +{ + S32 res = LLViewerObject::setTEShiny(te, shiny); + if (res) + { + gPipeline.markTextured(mDrawable); + mFaceMappingChanged = true; + } + return res; +} + +S32 LLVOVolume::setTEFullbright(const U8 te, const U8 fullbright) +{ + S32 res = LLViewerObject::setTEFullbright(te, fullbright); + if (res) + { + gPipeline.markTextured(mDrawable); + mFaceMappingChanged = true; + } + return res; +} + +S32 LLVOVolume::setTEBumpShinyFullbright(const U8 te, const U8 bump) +{ + S32 res = LLViewerObject::setTEBumpShinyFullbright(te, bump); + if (res) + { + gPipeline.markTextured(mDrawable); + mFaceMappingChanged = true; + } + return res; +} + +S32 LLVOVolume::setTEMediaFlags(const U8 te, const U8 media_flags) +{ + S32 res = LLViewerObject::setTEMediaFlags(te, media_flags); + if (res) + { + gPipeline.markTextured(mDrawable); + mFaceMappingChanged = true; + } + return res; +} + +S32 LLVOVolume::setTEGlow(const U8 te, const F32 glow) +{ + S32 res = LLViewerObject::setTEGlow(te, glow); + if (res) + { + if (mDrawable) + { + gPipeline.markTextured(mDrawable); + shrinkWrap(); + } + mFaceMappingChanged = true; + } + return res; +} + +void LLVOVolume::setTEMaterialParamsCallbackTE(const LLUUID& objectID, const LLMaterialID &pMaterialID, const LLMaterialPtr pMaterialParams, U32 te) +{ + LLVOVolume* pVol = (LLVOVolume*)gObjectList.findObject(objectID); + if (pVol) + { + LL_DEBUGS("MaterialTEs") << "materialid " << pMaterialID.asString() << " to TE " << te << LL_ENDL; + if (te >= pVol->getNumTEs()) + return; + + LLTextureEntry* texture_entry = pVol->getTE(te); + if (texture_entry && (texture_entry->getMaterialID() == pMaterialID)) + { + pVol->setTEMaterialParams(te, pMaterialParams); + } + } +} + +S32 LLVOVolume::setTEMaterialID(const U8 te, const LLMaterialID& pMaterialID) +{ + S32 res = LLViewerObject::setTEMaterialID(te, pMaterialID); + LL_DEBUGS("MaterialTEs") << "te "<< (S32)te << " materialid " << pMaterialID.asString() << " res " << res + << ( LLSelectMgr::getInstance()->getSelection()->contains(const_cast(this), te) ? " selected" : " not selected" ) + << LL_ENDL; + + LL_DEBUGS("MaterialTEs") << " " << pMaterialID.asString() << LL_ENDL; + if (res) + { + LLMaterialMgr::instance().getTE(getRegion()->getRegionID(), pMaterialID, te, boost::bind(&LLVOVolume::setTEMaterialParamsCallbackTE, getID(), _1, _2, _3)); + + setChanged(ALL_CHANGED); + if (!mDrawable.isNull()) + { + gPipeline.markTextured(mDrawable); + gPipeline.markRebuild(mDrawable,LLDrawable::REBUILD_ALL); + } + mFaceMappingChanged = true; + } + return res; +} + +S32 LLVOVolume::setTEMaterialParams(const U8 te, const LLMaterialPtr pMaterialParams) +{ + S32 res = LLViewerObject::setTEMaterialParams(te, pMaterialParams); + + LL_DEBUGS("MaterialTEs") << "te " << (S32)te << " material " << ((pMaterialParams) ? pMaterialParams->asLLSD() : LLSD("null")) << " res " << res + << ( LLSelectMgr::getInstance()->getSelection()->contains(const_cast(this), te) ? " selected" : " not selected" ) + << LL_ENDL; + setChanged(ALL_CHANGED); + if (!mDrawable.isNull()) + { + gPipeline.markTextured(mDrawable); + gPipeline.markRebuild(mDrawable,LLDrawable::REBUILD_ALL); + } + mFaceMappingChanged = true; + return TEM_CHANGE_TEXTURE; +} + +S32 LLVOVolume::setTEGLTFMaterialOverride(U8 te, LLGLTFMaterial* mat) +{ + S32 retval = LLViewerObject::setTEGLTFMaterialOverride(te, mat); + + if (retval == TEM_CHANGE_TEXTURE) + { + if (!mDrawable.isNull()) + { + gPipeline.markTextured(mDrawable); + gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); + } + mFaceMappingChanged = true; + } + + return retval; +} + + +S32 LLVOVolume::setTEScale(const U8 te, const F32 s, const F32 t) +{ + S32 res = LLViewerObject::setTEScale(te, s, t); + if (res) + { + gPipeline.markTextured(mDrawable); + mFaceMappingChanged = true; + } + return res; +} + +S32 LLVOVolume::setTEScaleS(const U8 te, const F32 s) +{ + S32 res = LLViewerObject::setTEScaleS(te, s); + if (res) + { + gPipeline.markTextured(mDrawable); + mFaceMappingChanged = true; + } + return res; +} + +S32 LLVOVolume::setTEScaleT(const U8 te, const F32 t) +{ + S32 res = LLViewerObject::setTEScaleT(te, t); + if (res) + { + gPipeline.markTextured(mDrawable); + mFaceMappingChanged = true; + } + return res; +} + +bool LLVOVolume::hasMedia() const +{ + bool result = false; + const U8 numTEs = getNumTEs(); + for (U8 i = 0; i < numTEs; i++) + { + const LLTextureEntry* te = getTE(i); + if(te->hasMedia()) + { + result = true; + break; + } + } + return result; +} + +LLVector3 LLVOVolume::getApproximateFaceNormal(U8 face_id) +{ + LLVolume* volume = getVolume(); + LLVector4a result; + result.clear(); + + LLVector3 ret; + + if (volume && face_id < volume->getNumVolumeFaces()) + { + const LLVolumeFace& face = volume->getVolumeFace(face_id); + for (S32 i = 0; i < (S32)face.mNumVertices; ++i) + { + result.add(face.mNormals[i]); + } + + LLVector3 ret(result.getF32ptr()); + ret = volumeDirectionToAgent(ret); + ret.normVec(); + } + + return ret; +} + +void LLVOVolume::requestMediaDataUpdate(bool isNew) +{ + if (sObjectMediaClient) + sObjectMediaClient->fetchMedia(new LLMediaDataClientObjectImpl(this, isNew)); +} + +bool LLVOVolume::isMediaDataBeingFetched() const +{ + // I know what I'm doing by const_casting this away: this is just + // a wrapper class that is only going to do a lookup. + return (sObjectMediaClient) ? sObjectMediaClient->isInQueue(new LLMediaDataClientObjectImpl(const_cast(this), false)) : false; +} + +void LLVOVolume::cleanUpMediaImpls() +{ + // Iterate through our TEs and remove any Impls that are no longer used + const U8 numTEs = getNumTEs(); + for (U8 i = 0; i < numTEs; i++) + { + const LLTextureEntry* te = getTE(i); + if( ! te->hasMedia()) + { + // Delete the media IMPL! + removeMediaImpl(i) ; + } + } +} + +void LLVOVolume::updateObjectMediaData(const LLSD &media_data_array, const std::string &media_version) +{ + // media_data_array is an array of media entry maps + // media_version is the version string in the response. + U32 fetched_version = LLTextureEntry::getVersionFromMediaVersionString(media_version); + + // Only update it if it is newer! + if ( (S32)fetched_version > mLastFetchedMediaVersion) + { + mLastFetchedMediaVersion = fetched_version; + //LL_INFOS() << "updating:" << this->getID() << " " << ll_pretty_print_sd(media_data_array) << LL_ENDL; + + LLSD::array_const_iterator iter = media_data_array.beginArray(); + LLSD::array_const_iterator end = media_data_array.endArray(); + U8 texture_index = 0; + for (; iter != end; ++iter, ++texture_index) + { + syncMediaData(texture_index, *iter, false/*merge*/, false/*ignore_agent*/); + } + } +} + +void LLVOVolume::syncMediaData(S32 texture_index, const LLSD &media_data, bool merge, bool ignore_agent) +{ + if(mDead) + { + // If the object has been marked dead, don't process media updates. + return; + } + + LLTextureEntry *te = getTE(texture_index); + if(!te) + { + return ; + } + + LL_DEBUGS("MediaOnAPrim") << "BEFORE: texture_index = " << texture_index + << " hasMedia = " << te->hasMedia() << " : " + << ((NULL == te->getMediaData()) ? "NULL MEDIA DATA" : ll_pretty_print_sd(te->getMediaData()->asLLSD())) << LL_ENDL; + + std::string previous_url; + LLMediaEntry* mep = te->getMediaData(); + if(mep) + { + // Save the "current url" from before the update so we can tell if + // it changes. + previous_url = mep->getCurrentURL(); + } + + if (merge) + { + te->mergeIntoMediaData(media_data); + } + else { + // XXX Question: what if the media data is undefined LLSD, but the + // update we got above said that we have media flags?? Here we clobber + // that, assuming the data from the service is more up-to-date. + te->updateMediaData(media_data); + } + + mep = te->getMediaData(); + if(mep) + { + bool update_from_self = false; + if (!ignore_agent) + { + LLUUID updating_agent = LLTextureEntry::getAgentIDFromMediaVersionString(getMediaURL()); + update_from_self = (updating_agent == gAgent.getID()); + } + viewer_media_t media_impl = LLViewerMedia::getInstance()->updateMediaImpl(mep, previous_url, update_from_self); + + addMediaImpl(media_impl, texture_index) ; + } + else + { + removeMediaImpl(texture_index); + } + + LL_DEBUGS("MediaOnAPrim") << "AFTER: texture_index = " << texture_index + << " hasMedia = " << te->hasMedia() << " : " + << ((NULL == te->getMediaData()) ? "NULL MEDIA DATA" : ll_pretty_print_sd(te->getMediaData()->asLLSD())) << LL_ENDL; +} + +void LLVOVolume::mediaNavigateBounceBack(U8 texture_index) +{ + // Find the media entry for this navigate + const LLMediaEntry* mep = NULL; + viewer_media_t impl = getMediaImpl(texture_index); + LLTextureEntry *te = getTE(texture_index); + if(te) + { + mep = te->getMediaData(); + } + + if (mep && impl) + { + std::string url = mep->getCurrentURL(); + // Look for a ":", if not there, assume "http://" + if (!url.empty() && std::string::npos == url.find(':')) + { + url = "http://" + url; + } + // If the url we're trying to "bounce back" to is either empty or not + // allowed by the whitelist, try the home url. If *that* doesn't work, + // set the media as failed and unload it + if (url.empty() || !mep->checkCandidateUrl(url)) + { + url = mep->getHomeURL(); + // Look for a ":", if not there, assume "http://" + if (!url.empty() && std::string::npos == url.find(':')) + { + url = "http://" + url; + } + } + if (url.empty() || !mep->checkCandidateUrl(url)) + { + // The url to navigate back to is not good, and we have nowhere else + // to go. + LL_WARNS("MediaOnAPrim") << "FAILED to bounce back URL \"" << url << "\" -- unloading impl" << LL_ENDL; + impl->setMediaFailed(true); + } + // Make sure we are not bouncing to url we came from + else if (impl->getCurrentMediaURL() != url) + { + // Okay, navigate now + LL_INFOS("MediaOnAPrim") << "bouncing back to URL: " << url << LL_ENDL; + impl->navigateTo(url, "", false, true); + } + } +} + +bool LLVOVolume::hasMediaPermission(const LLMediaEntry* media_entry, MediaPermType perm_type) +{ + // NOTE: This logic ALMOST duplicates the logic in the server (in particular, in llmediaservice.cpp). + if (NULL == media_entry ) return false; // XXX should we assert here? + + // The agent has permissions if: + // - world permissions are on, or + // - group permissions are on, and agent_id is in the group, or + // - agent permissions are on, and agent_id is the owner + + // *NOTE: We *used* to check for modify permissions here (i.e. permissions were + // granted if permModify() was true). However, this doesn't make sense in the + // viewer: we don't want to show controls or allow interaction if the author + // has deemed it so. See DEV-42115. + + U8 media_perms = (perm_type == MEDIA_PERM_INTERACT) ? media_entry->getPermsInteract() : media_entry->getPermsControl(); + + // World permissions + if (0 != (media_perms & LLMediaEntry::PERM_ANYONE)) + { + return true; + } + + // Group permissions + else if (0 != (media_perms & LLMediaEntry::PERM_GROUP)) + { + LLPermissions* obj_perm = LLSelectMgr::getInstance()->findObjectPermissions(this); + if (obj_perm && gAgent.isInGroup(obj_perm->getGroup())) + { + return true; + } + } + + // Owner permissions + else if (0 != (media_perms & LLMediaEntry::PERM_OWNER) && permYouOwner()) + { + return true; + } + + return false; + +} + +void LLVOVolume::mediaNavigated(LLViewerMediaImpl *impl, LLPluginClassMedia* plugin, std::string new_location) +{ + bool block_navigation = false; + // FIXME: if/when we allow the same media impl to be used by multiple faces, the logic here will need to be fixed + // to deal with multiple face indices. + int face_index = getFaceIndexWithMediaImpl(impl, -1); + + // Find the media entry for this navigate + LLMediaEntry* mep = NULL; + LLTextureEntry *te = getTE(face_index); + if(te) + { + mep = te->getMediaData(); + } + + if(mep) + { + if(!mep->checkCandidateUrl(new_location)) + { + block_navigation = true; + } + if (!block_navigation && !hasMediaPermission(mep, MEDIA_PERM_INTERACT)) + { + block_navigation = true; + } + } + else + { + LL_WARNS("MediaOnAPrim") << "Couldn't find media entry!" << LL_ENDL; + } + + if(block_navigation) + { + LL_INFOS("MediaOnAPrim") << "blocking navigate to URI " << new_location << LL_ENDL; + + // "bounce back" to the current URL from the media entry + mediaNavigateBounceBack(face_index); + } + else if (sObjectMediaNavigateClient) + { + + LL_DEBUGS("MediaOnAPrim") << "broadcasting navigate with URI " << new_location << LL_ENDL; + + sObjectMediaNavigateClient->navigate(new LLMediaDataClientObjectImpl(this, false), face_index, new_location); + } +} + +void LLVOVolume::mediaEvent(LLViewerMediaImpl *impl, LLPluginClassMedia* plugin, LLViewerMediaObserver::EMediaEvent event) +{ + switch(event) + { + + case LLViewerMediaObserver::MEDIA_EVENT_LOCATION_CHANGED: + { + switch(impl->getNavState()) + { + case LLViewerMediaImpl::MEDIANAVSTATE_FIRST_LOCATION_CHANGED: + { + // This is the first location changed event after the start of a non-server-directed nav. It may need to be broadcast or bounced back. + mediaNavigated(impl, plugin, plugin->getLocation()); + } + break; + + case LLViewerMediaImpl::MEDIANAVSTATE_FIRST_LOCATION_CHANGED_SPURIOUS: + // This navigate didn't change the current URL. + LL_DEBUGS("MediaOnAPrim") << " NOT broadcasting navigate (spurious)" << LL_ENDL; + break; + + case LLViewerMediaImpl::MEDIANAVSTATE_SERVER_FIRST_LOCATION_CHANGED: + // This is the first location changed event after the start of a server-directed nav. Don't broadcast it. + LL_INFOS("MediaOnAPrim") << " NOT broadcasting navigate (server-directed)" << LL_ENDL; + break; + + default: + // This is a subsequent location-changed due to a redirect. Don't broadcast. + LL_INFOS("MediaOnAPrim") << " NOT broadcasting navigate (redirect)" << LL_ENDL; + break; + } + } + break; + + case LLViewerMediaObserver::MEDIA_EVENT_NAVIGATE_COMPLETE: + { + switch(impl->getNavState()) + { + case LLViewerMediaImpl::MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED: + { + // This is the first location changed event after the start of a non-server-directed nav. It may need to be broadcast or bounced back. + mediaNavigated(impl, plugin, plugin->getNavigateURI()); + } + break; + + case LLViewerMediaImpl::MEDIANAVSTATE_COMPLETE_BEFORE_LOCATION_CHANGED_SPURIOUS: + // This navigate didn't change the current URL. + LL_DEBUGS("MediaOnAPrim") << " NOT broadcasting navigate (spurious)" << LL_ENDL; + break; + + case LLViewerMediaImpl::MEDIANAVSTATE_SERVER_COMPLETE_BEFORE_LOCATION_CHANGED: + // This is the the navigate complete event from a server-directed nav. Don't broadcast it. + LL_INFOS("MediaOnAPrim") << " NOT broadcasting navigate (server-directed)" << LL_ENDL; + break; + + default: + // For all other states, the navigate should have been handled by LOCATION_CHANGED events already. + break; + } + } + break; + + case LLViewerMediaObserver::MEDIA_EVENT_FILE_DOWNLOAD: + { + // Media might be blocked, waiting for a file, + // send an empty response to unblock it + const std::vector empty_response; + plugin->sendPickFileResponse(empty_response); + + LLNotificationsUtil::add("MediaFileDownloadUnsupported"); + } + break; + + default: + break; + } + +} + +void LLVOVolume::sendMediaDataUpdate() +{ + if (sObjectMediaClient) + sObjectMediaClient->updateMedia(new LLMediaDataClientObjectImpl(this, false)); +} + +void LLVOVolume::removeMediaImpl(S32 texture_index) +{ + if(mMediaImplList.size() <= (U32)texture_index || mMediaImplList[texture_index].isNull()) + { + return ; + } + + //make the face referencing to mMediaImplList[texture_index] to point back to the old texture. + if(mDrawable && texture_index < mDrawable->getNumFaces()) + { + LLFace* facep = mDrawable->getFace(texture_index) ; + if(facep) + { + LLViewerMediaTexture* media_tex = LLViewerTextureManager::findMediaTexture(mMediaImplList[texture_index]->getMediaTextureID()) ; + if(media_tex) + { + media_tex->removeMediaFromFace(facep) ; + } + } + } + + //check if some other face(s) of this object reference(s)to this media impl. + S32 i ; + S32 end = (S32)mMediaImplList.size() ; + for(i = 0; i < end ; i++) + { + if( i != texture_index && mMediaImplList[i] == mMediaImplList[texture_index]) + { + break ; + } + } + + if(i == end) //this object does not need this media impl. + { + mMediaImplList[texture_index]->removeObject(this) ; + } + + mMediaImplList[texture_index] = NULL ; + return ; +} + +void LLVOVolume::addMediaImpl(LLViewerMediaImpl* media_impl, S32 texture_index) +{ + if((S32)mMediaImplList.size() < texture_index + 1) + { + mMediaImplList.resize(texture_index + 1) ; + } + + if(mMediaImplList[texture_index].notNull()) + { + if(mMediaImplList[texture_index] == media_impl) + { + return ; + } + + removeMediaImpl(texture_index) ; + } + + mMediaImplList[texture_index] = media_impl; + media_impl->addObject(this) ; + + //add the face to show the media if it is in playing + if(mDrawable) + { + LLFace* facep(NULL); + if( texture_index < mDrawable->getNumFaces() ) + { + facep = mDrawable->getFace(texture_index) ; + } + + if(facep) + { + LLViewerMediaTexture* media_tex = LLViewerTextureManager::findMediaTexture(mMediaImplList[texture_index]->getMediaTextureID()) ; + if(media_tex) + { + media_tex->addMediaToFace(facep) ; + } + } + else //the face is not available now, start media on this face later. + { + media_impl->setUpdated(true) ; + } + } + return ; +} + +viewer_media_t LLVOVolume::getMediaImpl(U8 face_id) const +{ + if(mMediaImplList.size() > face_id) + { + return mMediaImplList[face_id]; + } + return NULL; +} + +F64 LLVOVolume::getTotalMediaInterest() const +{ + // If this object is currently focused, this object has "high" interest + if (LLViewerMediaFocus::getInstance()->getFocusedObjectID() == getID()) + return F64_MAX; + + F64 interest = (F64)-1.0; // means not interested; + + // If this object is selected, this object has "high" interest, but since + // there can be more than one, we still add in calculated impl interest + // XXX Sadly, 'contains()' doesn't take a const :( + if (LLSelectMgr::getInstance()->getSelection()->contains(const_cast(this))) + interest = F64_MAX / 2.0; + + int i = 0; + const int end = getNumTEs(); + for ( ; i < end; ++i) + { + const viewer_media_t &impl = getMediaImpl(i); + if (!impl.isNull()) + { + if (interest == (F64)-1.0) interest = (F64)0.0; + interest += impl->getInterest(); + } + } + return interest; +} + +S32 LLVOVolume::getFaceIndexWithMediaImpl(const LLViewerMediaImpl* media_impl, S32 start_face_id) +{ + S32 end = (S32)mMediaImplList.size() ; + for(S32 face_id = start_face_id + 1; face_id < end; face_id++) + { + if(mMediaImplList[face_id] == media_impl) + { + return face_id ; + } + } + return -1 ; +} + +//---------------------------------------------------------------------------- + +void LLVOVolume::setLightTextureID(LLUUID id) +{ + LLViewerTexture* old_texturep = getLightTexture(); // same as mLightTexture, but inits if nessesary + if (id.notNull()) + { + if (!hasLightTexture()) + { + setParameterEntryInUse(LLNetworkData::PARAMS_LIGHT_IMAGE, true, true); + } + else if (old_texturep) + { + old_texturep->removeVolume(LLRender::LIGHT_TEX, this); + } + LLLightImageParams* param_block = (LLLightImageParams*) getParameterEntry(LLNetworkData::PARAMS_LIGHT_IMAGE); + if (param_block && param_block->getLightTexture() != id) + { + param_block->setLightTexture(id); + parameterChanged(LLNetworkData::PARAMS_LIGHT_IMAGE, true); + } + LLViewerTexture* tex = getLightTexture(); + if (tex) + { + tex->addVolume(LLRender::LIGHT_TEX, this); // new texture + } + else + { + LL_WARNS() << "Can't get light texture for ID " << id.asString() << LL_ENDL; + } + } + else if (hasLightTexture()) + { + if (old_texturep) + { + old_texturep->removeVolume(LLRender::LIGHT_TEX, this); + } + setParameterEntryInUse(LLNetworkData::PARAMS_LIGHT_IMAGE, false, true); + parameterChanged(LLNetworkData::PARAMS_LIGHT_IMAGE, true); + mLightTexture = NULL; + } +} + +void LLVOVolume::setSpotLightParams(LLVector3 params) +{ + LLLightImageParams* param_block = (LLLightImageParams*) getParameterEntry(LLNetworkData::PARAMS_LIGHT_IMAGE); + if (param_block && param_block->getParams() != params) + { + param_block->setParams(params); + parameterChanged(LLNetworkData::PARAMS_LIGHT_IMAGE, true); + } +} + +void LLVOVolume::setIsLight(bool is_light) +{ + bool was_light = getIsLight(); + if (is_light != was_light) + { + if (is_light) + { + setParameterEntryInUse(LLNetworkData::PARAMS_LIGHT, true, true); + } + else + { + setParameterEntryInUse(LLNetworkData::PARAMS_LIGHT, false, true); + } + + if (is_light) + { + // Add it to the pipeline mLightSet + gPipeline.setLight(mDrawable, true); + } + else + { + // Not a light. Remove it from the pipeline's light set. + gPipeline.setLight(mDrawable, false); + } + } +} + +void LLVOVolume::setLightSRGBColor(const LLColor3& color) +{ + setLightLinearColor(linearColor3(color)); +} + +void LLVOVolume::setLightLinearColor(const LLColor3& color) +{ + LLLightParams *param_block = (LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); + if (param_block) + { + if (param_block->getLinearColor() != color) + { + param_block->setLinearColor(LLColor4(color, param_block->getLinearColor().mV[3])); + parameterChanged(LLNetworkData::PARAMS_LIGHT, true); + gPipeline.markTextured(mDrawable); + mFaceMappingChanged = true; + } + } +} + +void LLVOVolume::setLightIntensity(F32 intensity) +{ + LLLightParams *param_block = (LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); + if (param_block) + { + if (param_block->getLinearColor().mV[3] != intensity) + { + param_block->setLinearColor(LLColor4(LLColor3(param_block->getLinearColor()), intensity)); + parameterChanged(LLNetworkData::PARAMS_LIGHT, true); + } + } +} + +void LLVOVolume::setLightRadius(F32 radius) +{ + LLLightParams *param_block = (LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); + if (param_block) + { + if (param_block->getRadius() != radius) + { + param_block->setRadius(radius); + parameterChanged(LLNetworkData::PARAMS_LIGHT, true); + } + } +} + +void LLVOVolume::setLightFalloff(F32 falloff) +{ + LLLightParams *param_block = (LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); + if (param_block) + { + if (param_block->getFalloff() != falloff) + { + param_block->setFalloff(falloff); + parameterChanged(LLNetworkData::PARAMS_LIGHT, true); + } + } +} + +void LLVOVolume::setLightCutoff(F32 cutoff) +{ + LLLightParams *param_block = (LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); + if (param_block) + { + if (param_block->getCutoff() != cutoff) + { + param_block->setCutoff(cutoff); + parameterChanged(LLNetworkData::PARAMS_LIGHT, true); + } + } +} + +//---------------------------------------------------------------------------- + +bool LLVOVolume::getIsLight() const +{ + mIsLight = getParameterEntryInUse(LLNetworkData::PARAMS_LIGHT); + return mIsLight; +} + +bool LLVOVolume::getIsLightFast() const +{ + return mIsLight; +} + +LLColor3 LLVOVolume::getLightSRGBBaseColor() const +{ + return srgbColor3(getLightLinearBaseColor()); +} + +LLColor3 LLVOVolume::getLightLinearBaseColor() const +{ + const LLLightParams *param_block = (const LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); + if (param_block) + { + return LLColor3(param_block->getLinearColor()); + } + else + { + return LLColor3(1,1,1); + } +} + +LLColor3 LLVOVolume::getLightLinearColor() const +{ + const LLLightParams *param_block = (const LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); + if (param_block) + { + return LLColor3(param_block->getLinearColor()) * param_block->getLinearColor().mV[3]; + } + else + { + return LLColor3(1, 1, 1); + } +} + +LLColor3 LLVOVolume::getLightSRGBColor() const +{ + LLColor3 ret = getLightLinearColor(); + ret = srgbColor3(ret); + return ret; +} + +LLUUID LLVOVolume::getLightTextureID() const +{ + if (getParameterEntryInUse(LLNetworkData::PARAMS_LIGHT_IMAGE)) + { + const LLLightImageParams *param_block = (const LLLightImageParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT_IMAGE); + if (param_block) + { + return param_block->getLightTexture(); + } + } + + return LLUUID::null; +} + + +LLVector3 LLVOVolume::getSpotLightParams() const +{ + if (getParameterEntryInUse(LLNetworkData::PARAMS_LIGHT_IMAGE)) + { + const LLLightImageParams *param_block = (const LLLightImageParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT_IMAGE); + if (param_block) + { + return param_block->getParams(); + } + } + + return LLVector3(); +} + +F32 LLVOVolume::getSpotLightPriority() const +{ + return mSpotLightPriority; +} + +void LLVOVolume::updateSpotLightPriority() +{ + if (gCubeSnapshot) + { + return; + } + + F32 r = getLightRadius(); + LLVector3 pos = mDrawable->getPositionAgent(); + + LLVector3 at(0,0,-1); + at *= getRenderRotation(); + pos += at * r; + + at = LLViewerCamera::getInstance()->getAtAxis(); + pos -= at * r; + + mSpotLightPriority = gPipeline.calcPixelArea(pos, LLVector3(r,r,r), *LLViewerCamera::getInstance()); + + if (mLightTexture.notNull()) + { + mLightTexture->addTextureStats(mSpotLightPriority); + } +} + + +bool LLVOVolume::isLightSpotlight() const +{ + LLLightImageParams* params = (LLLightImageParams*) getParameterEntry(LLNetworkData::PARAMS_LIGHT_IMAGE); + if (params && getParameterEntryInUse(LLNetworkData::PARAMS_LIGHT_IMAGE)) + { + return params->isLightSpotlight(); + } + return false; +} + + +LLViewerTexture* LLVOVolume::getLightTexture() +{ + LLUUID id = getLightTextureID(); + + if (id.notNull()) + { + if (mLightTexture.isNull() || id != mLightTexture->getID()) + { + mLightTexture = LLViewerTextureManager::getFetchedTexture(id, FTT_DEFAULT, true, LLGLTexture::BOOST_NONE); + } + } + else + { + mLightTexture = NULL; + } + + return mLightTexture; +} + +F32 LLVOVolume::getLightIntensity() const +{ + const LLLightParams *param_block = (const LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); + if (param_block) + { + return param_block->getLinearColor().mV[3]; + } + else + { + return 1.f; + } +} + +F32 LLVOVolume::getLightRadius() const +{ + const LLLightParams *param_block = (const LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); + if (param_block) + { + return param_block->getRadius(); + } + else + { + return 0.f; + } +} + +F32 LLVOVolume::getLightFalloff(const F32 fudge_factor) const +{ + const LLLightParams *param_block = (const LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); + if (param_block) + { + return param_block->getFalloff() * fudge_factor; + } + else + { + return 0.f; + } +} + +F32 LLVOVolume::getLightCutoff() const +{ + const LLLightParams *param_block = (const LLLightParams *)getParameterEntry(LLNetworkData::PARAMS_LIGHT); + if (param_block) + { + return param_block->getCutoff(); + } + else + { + return 0.f; + } +} + +bool LLVOVolume::isReflectionProbe() const +{ + return getParameterEntryInUse(LLNetworkData::PARAMS_REFLECTION_PROBE); +} + +bool LLVOVolume::setIsReflectionProbe(bool is_probe) +{ + bool was_probe = isReflectionProbe(); + if (is_probe != was_probe) + { + if (is_probe) + { + setParameterEntryInUse(LLNetworkData::PARAMS_REFLECTION_PROBE, true, true); + } + else + { + setParameterEntryInUse(LLNetworkData::PARAMS_REFLECTION_PROBE, false, true); + } + } + + updateReflectionProbePtr(); + + return was_probe != is_probe; +} + +bool LLVOVolume::setReflectionProbeAmbiance(F32 ambiance) +{ + LLReflectionProbeParams* param_block = (LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE); + if (param_block) + { + if (param_block->getAmbiance() != ambiance) + { + param_block->setAmbiance(ambiance); + parameterChanged(LLNetworkData::PARAMS_REFLECTION_PROBE, true); + return true; + } + } + + return false; +} + +bool LLVOVolume::setReflectionProbeNearClip(F32 near_clip) +{ + LLReflectionProbeParams* param_block = (LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE); + if (param_block) + { + if (param_block->getClipDistance() != near_clip) + { + param_block->setClipDistance(near_clip); + parameterChanged(LLNetworkData::PARAMS_REFLECTION_PROBE, true); + return true; + } + } + + return false; +} + +bool LLVOVolume::setReflectionProbeIsBox(bool is_box) +{ + LLReflectionProbeParams* param_block = (LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE); + if (param_block) + { + if (param_block->getIsBox() != is_box) + { + param_block->setIsBox(is_box); + parameterChanged(LLNetworkData::PARAMS_REFLECTION_PROBE, true); + return true; + } + } + + return false; +} + +bool LLVOVolume::setReflectionProbeIsDynamic(bool is_dynamic) +{ + LLReflectionProbeParams* param_block = (LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE); + if (param_block) + { + if (param_block->getIsDynamic() != is_dynamic) + { + param_block->setIsDynamic(is_dynamic); + parameterChanged(LLNetworkData::PARAMS_REFLECTION_PROBE, true); + return true; + } + } + + return false; +} + +F32 LLVOVolume::getReflectionProbeAmbiance() const +{ + const LLReflectionProbeParams* param_block = (const LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE); + if (param_block) + { + return param_block->getAmbiance(); + } + else + { + return 0.f; + } +} + +F32 LLVOVolume::getReflectionProbeNearClip() const +{ + const LLReflectionProbeParams* param_block = (const LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE); + if (param_block) + { + return param_block->getClipDistance(); + } + else + { + return 0.f; + } +} + +bool LLVOVolume::getReflectionProbeIsBox() const +{ + const LLReflectionProbeParams* param_block = (const LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE); + if (param_block) + { + return param_block->getIsBox(); + } + + return false; +} + +bool LLVOVolume::getReflectionProbeIsDynamic() const +{ + const LLReflectionProbeParams* param_block = (const LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE); + if (param_block) + { + return param_block->getIsDynamic(); + } + + return false; +} + +U32 LLVOVolume::getVolumeInterfaceID() const +{ + if (mVolumeImpl) + { + return mVolumeImpl->getID(); + } + + return 0; +} + +bool LLVOVolume::isFlexible() const +{ + if (getParameterEntryInUse(LLNetworkData::PARAMS_FLEXIBLE)) + { + LLVolume* volume = getVolume(); + if (volume && volume->getParams().getPathParams().getCurveType() != LL_PCODE_PATH_FLEXIBLE) + { + LLVolumeParams volume_params = getVolume()->getParams(); + U8 profile_and_hole = volume_params.getProfileParams().getCurveType(); + volume_params.setType(profile_and_hole, LL_PCODE_PATH_FLEXIBLE); + } + return true; + } + else + { + return false; + } +} + +bool LLVOVolume::isSculpted() const +{ + if (getParameterEntryInUse(LLNetworkData::PARAMS_SCULPT)) + { + return true; + } + + return false; +} + +bool LLVOVolume::isMesh() const +{ + if (isSculpted()) + { + LLSculptParams *sculpt_params = (LLSculptParams *)getParameterEntry(LLNetworkData::PARAMS_SCULPT); + U8 sculpt_type = sculpt_params->getSculptType(); + + if ((sculpt_type & LL_SCULPT_TYPE_MASK) == LL_SCULPT_TYPE_MESH) + // mesh is a mesh + { + return true; + } + } + + return false; +} + +bool LLVOVolume::hasLightTexture() const +{ + if (getParameterEntryInUse(LLNetworkData::PARAMS_LIGHT_IMAGE)) + { + return true; + } + + return false; +} + +bool LLVOVolume::isFlexibleFast() const +{ + return mVolumep && mVolumep->getParams().getPathParams().getCurveType() == LL_PCODE_PATH_FLEXIBLE; +} + +bool LLVOVolume::isSculptedFast() const +{ + return mVolumep && mVolumep->getParams().isSculpt(); +} + +bool LLVOVolume::isMeshFast() const +{ + return mVolumep && mVolumep->getParams().isMeshSculpt(); +} + +bool LLVOVolume::isRiggedMeshFast() const +{ + return mSkinInfo.notNull(); +} + +bool LLVOVolume::isAnimatedObjectFast() const +{ + return mIsAnimatedObject; +} + +bool LLVOVolume::isVolumeGlobal() const +{ + if (mVolumeImpl) + { + return mVolumeImpl->isVolumeGlobal(); + } + + if (mRiggedVolume.notNull()) + { + return true; + } + + return false; +} + +bool LLVOVolume::canBeFlexible() const +{ + U8 path = getVolume()->getParams().getPathParams().getCurveType(); + return (path == LL_PCODE_PATH_FLEXIBLE || path == LL_PCODE_PATH_LINE); +} + +bool LLVOVolume::setIsFlexible(bool is_flexible) +{ + bool res = false; + bool was_flexible = isFlexible(); + LLVolumeParams volume_params; + if (is_flexible) + { + if (!was_flexible) + { + volume_params = getVolume()->getParams(); + U8 profile_and_hole = volume_params.getProfileParams().getCurveType(); + volume_params.setType(profile_and_hole, LL_PCODE_PATH_FLEXIBLE); + res = true; + setFlags(FLAGS_USE_PHYSICS, false); + setFlags(FLAGS_PHANTOM, true); + setParameterEntryInUse(LLNetworkData::PARAMS_FLEXIBLE, true, true); + if (mDrawable) + { + mDrawable->makeActive(); + } + } + } + else + { + if (was_flexible) + { + volume_params = getVolume()->getParams(); + U8 profile_and_hole = volume_params.getProfileParams().getCurveType(); + volume_params.setType(profile_and_hole, LL_PCODE_PATH_LINE); + res = true; + setFlags(FLAGS_PHANTOM, false); + setParameterEntryInUse(LLNetworkData::PARAMS_FLEXIBLE, false, true); + } + } + if (res) + { + res = setVolume(volume_params, 1); + if (res) + { + markForUpdate(); + } + } + return res; +} + +const LLMeshSkinInfo* LLVOVolume::getSkinInfo() const +{ + if (getVolume()) + { + return mSkinInfo; + } + else + { + return NULL; + } +} + +// virtual +bool LLVOVolume::isRiggedMesh() const +{ + return isMesh() && getSkinInfo(); +} + +//---------------------------------------------------------------------------- +U32 LLVOVolume::getExtendedMeshFlags() const +{ + const LLExtendedMeshParams *param_block = + (const LLExtendedMeshParams *)getParameterEntry(LLNetworkData::PARAMS_EXTENDED_MESH); + if (param_block) + { + return param_block->getFlags(); + } + else + { + return 0; + } +} + +void LLVOVolume::onSetExtendedMeshFlags(U32 flags) +{ + + // The isAnySelected() check was needed at one point to prevent + // graphics problems. These are now believed to be fixed so the + // check has been disabled. + if (/*!getRootEdit()->isAnySelected() &&*/ mDrawable.notNull()) + { + // Need to trigger rebuildGeom(), which is where control avatars get created/removed + getRootEdit()->recursiveMarkForUpdate(); + } + if (isAttachment() && getAvatarAncestor()) + { + updateVisualComplexity(); + if (flags & LLExtendedMeshParams::ANIMATED_MESH_ENABLED_FLAG) + { + // Making a rigged mesh into an animated object + getAvatarAncestor()->updateAttachmentOverrides(); + } + else + { + // Making an animated object into a rigged mesh + getAvatarAncestor()->updateAttachmentOverrides(); + } + } +} + +void LLVOVolume::setExtendedMeshFlags(U32 flags) +{ + U32 curr_flags = getExtendedMeshFlags(); + if (curr_flags != flags) + { + bool in_use = true; + setParameterEntryInUse(LLNetworkData::PARAMS_EXTENDED_MESH, in_use, true); + LLExtendedMeshParams *param_block = + (LLExtendedMeshParams *)getParameterEntry(LLNetworkData::PARAMS_EXTENDED_MESH); + if (param_block) + { + param_block->setFlags(flags); + } + parameterChanged(LLNetworkData::PARAMS_EXTENDED_MESH, true); + LL_DEBUGS("AnimatedObjects") << this + << " new flags " << flags << " curr_flags " << curr_flags + << ", calling onSetExtendedMeshFlags()" + << LL_ENDL; + onSetExtendedMeshFlags(flags); + } +} + +bool LLVOVolume::canBeAnimatedObject() const +{ + F32 est_tris = recursiveGetEstTrianglesMax(); + if (est_tris < 0 || est_tris > getAnimatedObjectMaxTris()) + { + return false; + } + return true; +} + +bool LLVOVolume::isAnimatedObject() const +{ + LLVOVolume *root_vol = (LLVOVolume*)getRootEdit(); + mIsAnimatedObject = root_vol->getExtendedMeshFlags() & LLExtendedMeshParams::ANIMATED_MESH_ENABLED_FLAG; + return mIsAnimatedObject; +} + +// Called any time parenting changes for a volume. Update flags and +// control av accordingly. This is called after parent has been +// changed to new_parent, but before new_parent's mChildList has changed. + +// virtual +void LLVOVolume::onReparent(LLViewerObject *old_parent, LLViewerObject *new_parent) +{ + LLVOVolume *old_volp = dynamic_cast(old_parent); + + if (new_parent && !new_parent->isAvatar()) + { + if (mControlAvatar.notNull()) + { + // Here an animated object is being made the child of some + // other prim. Should remove the control av from the child. + LLControlAvatar *av = mControlAvatar; + mControlAvatar = NULL; + av->markForDeath(); + } + } + if (old_volp && old_volp->isAnimatedObject()) + { + if (old_volp->getControlAvatar()) + { + // We have been removed from an animated object, need to do cleanup. + old_volp->getControlAvatar()->updateAttachmentOverrides(); + old_volp->getControlAvatar()->updateAnimations(); + } + } +} + +// This needs to be called after onReparent(), because mChildList is +// not updated until the end of LLViewerObject::addChild() + +// virtual +void LLVOVolume::afterReparent() +{ + { + LL_DEBUGS("AnimatedObjects") << "new child added for parent " + << ((LLViewerObject*)getParent())->getID() << LL_ENDL; + } + + if (isAnimatedObject() && getControlAvatar()) + { + LL_DEBUGS("AnimatedObjects") << "adding attachment overrides, parent is animated object " + << ((LLViewerObject*)getParent())->getID() << LL_ENDL; + + // MAINT-8239 - doing a full rebuild whenever parent is set + // makes the joint overrides load more robustly. In theory, + // addAttachmentOverrides should be sufficient, but in + // practice doing a full rebuild helps compensate for + // notifyMeshLoaded() not being called reliably enough. + + // was: getControlAvatar()->addAttachmentOverridesForObject(this); + //getControlAvatar()->rebuildAttachmentOverrides(); + getControlAvatar()->updateAnimations(); + } + else + { + LL_DEBUGS("AnimatedObjects") << "not adding overrides, parent: " + << ((LLViewerObject*)getParent())->getID() + << " isAnimated: " << isAnimatedObject() << " cav " + << getControlAvatar() << LL_ENDL; + } +} + +//---------------------------------------------------------------------------- +void LLVOVolume::updateRiggingInfo() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + if (isRiggedMesh()) + { + const LLMeshSkinInfo* skin = getSkinInfo(); + LLVOAvatar *avatar = getAvatar(); + LLVolume *volume = getVolume(); + if (skin && avatar && volume) + { + LL_DEBUGS("RigSpammish") << "starting, vovol " << this << " lod " << getLOD() << " last " << mLastRiggingInfoLOD << LL_ENDL; + if (getLOD()>mLastRiggingInfoLOD || getLOD()==3) + { + // Rigging info may need update + mJointRiggingInfoTab.clear(); + for (S32 f = 0; f < volume->getNumVolumeFaces(); ++f) + { + LLVolumeFace& vol_face = volume->getVolumeFace(f); + LLSkinningUtil::updateRiggingInfo(skin, avatar, vol_face); + if (vol_face.mJointRiggingInfoTab.size()>0) + { + mJointRiggingInfoTab.merge(vol_face.mJointRiggingInfoTab); + } + } + // Keep the highest LOD info available. + mLastRiggingInfoLOD = getLOD(); + LL_DEBUGS("RigSpammish") << "updated rigging info for LLVOVolume " + << this << " lod " << mLastRiggingInfoLOD + << LL_ENDL; + } + } + } +} + +//---------------------------------------------------------------------------- + +void LLVOVolume::generateSilhouette(LLSelectNode* nodep, const LLVector3& view_point) +{ + LLVolume *volume = getVolume(); + + if (volume) + { + LLVector3 view_vector; + view_vector = view_point; + + //transform view vector into volume space + view_vector -= getRenderPosition(); + //mDrawable->mDistanceWRTCamera = view_vector.length(); + LLQuaternion worldRot = getRenderRotation(); + view_vector = view_vector * ~worldRot; + if (!isVolumeGlobal()) + { + LLVector3 objScale = getScale(); + LLVector3 invObjScale(1.f / objScale.mV[VX], 1.f / objScale.mV[VY], 1.f / objScale.mV[VZ]); + view_vector.scaleVec(invObjScale); + } + + updateRelativeXform(); + LLMatrix4 trans_mat = mRelativeXform; + if (mDrawable->isStatic()) + { + trans_mat.translate(getRegion()->getOriginAgent()); + } + + volume->generateSilhouetteVertices(nodep->mSilhouetteVertices, nodep->mSilhouetteNormals, view_vector, trans_mat, mRelativeXformInvTrans, nodep->getTESelectMask()); + + nodep->mSilhouetteExists = true; + } +} + +void LLVOVolume::deleteFaces() +{ + S32 face_count = mNumFaces; + if (mDrawable.notNull()) + { + mDrawable->deleteFaces(0, face_count); + } + + mNumFaces = 0; +} + +void LLVOVolume::updateRadius() +{ + if (mDrawable.isNull()) + { + return; + } + + mVObjRadius = getScale().length(); + mDrawable->setRadius(mVObjRadius); +} + + +bool LLVOVolume::isAttachment() const +{ + return mAttachmentState != 0 ; +} + +bool LLVOVolume::isHUDAttachment() const +{ + // *NOTE: we assume hud attachment points are in defined range + // since this range is constant for backwards compatibility + // reasons this is probably a reasonable assumption to make + S32 attachment_id = ATTACHMENT_ID_FROM_STATE(mAttachmentState); + return ( attachment_id >= 31 && attachment_id <= 38 ); +} + + +const LLMatrix4 LLVOVolume::getRenderMatrix() const +{ + if (mDrawable->isActive() && !mDrawable->isRoot()) + { + return mDrawable->getParent()->getWorldMatrix(); + } + return mDrawable->getWorldMatrix(); +} + +//static +S32 LLVOVolume::getTextureCost(const LLViewerTexture* img) +{ + static const U32 ARC_TEXTURE_COST = 16; // multiplier for texture resolution - performance tested + + S32 texture_cost = 0; + S8 type = img->getType(); + if (type == LLViewerTexture::FETCHED_TEXTURE || type == LLViewerTexture::LOD_TEXTURE) + { + const LLViewerFetchedTexture* fetched_texturep = static_cast(img); + if (fetched_texturep + && fetched_texturep->getFTType() == FTT_LOCAL_FILE + && (img->getID() == IMG_ALPHA_GRAD_2D || img->getID() == IMG_ALPHA_GRAD) + ) + { + // These two textures appear to switch between each other, but are of different sizes (4x256 and 256x256). + // Hardcode cost from larger one to not cause random complexity changes + texture_cost = 320; + } + } + if (texture_cost == 0) + { + texture_cost = 256 + (S32)(ARC_TEXTURE_COST * (img->getFullHeight() / 128.f + img->getFullWidth() / 128.f)); + } + + return texture_cost; +} + +// Returns a base cost and adds textures to passed in set. +// total cost is returned value + 5 * size of the resulting set. +// Cannot include cost of textures, as they may be re-used in linked +// children, and cost should only be increased for unique textures -Nyx +U32 LLVOVolume::getRenderCost(texture_cost_t &textures) const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + /***************************************************************** + * This calculation should not be modified by third party viewers, + * since it is used to limit rendering and should be uniform for + * everyone. If you have suggested improvements, submit them to + * the official viewer for consideration. + *****************************************************************/ + + // Get access to params we'll need at various points. + // Skip if this is object doesn't have a volume (e.g. is an avatar). + if (getVolume() == NULL) + { + return 0; + } + + U32 num_triangles = 0; + + // per-prim costs + static const U32 ARC_PARTICLE_COST = 1; // determined experimentally + static const U32 ARC_PARTICLE_MAX = 2048; // default values + static const U32 ARC_LIGHT_COST = 500; // static cost for light-producing prims + static const U32 ARC_MEDIA_FACE_COST = 1500; // static cost per media-enabled face + + + // per-prim multipliers + static const F32 ARC_GLOW_MULT = 1.5f; // tested based on performance + static const F32 ARC_BUMP_MULT = 1.25f; // tested based on performance + static const F32 ARC_FLEXI_MULT = 5; // tested based on performance + static const F32 ARC_SHINY_MULT = 1.6f; // tested based on performance + static const F32 ARC_INVISI_COST = 1.2f; // tested based on performance + static const F32 ARC_WEIGHTED_MESH = 1.2f; // tested based on performance + + static const F32 ARC_PLANAR_COST = 1.0f; // tested based on performance to have negligible impact + static const F32 ARC_ANIM_TEX_COST = 4.f; // tested based on performance + static const F32 ARC_ALPHA_COST = 4.f; // 4x max - based on performance + + F32 shame = 0; + + U32 invisi = 0; + U32 shiny = 0; + U32 glow = 0; + U32 alpha = 0; + U32 flexi = 0; + U32 animtex = 0; + U32 particles = 0; + U32 bump = 0; + U32 planar = 0; + U32 weighted_mesh = 0; + U32 produces_light = 0; + U32 media_faces = 0; + + const LLDrawable* drawablep = mDrawable; + U32 num_faces = drawablep->getNumFaces(); + + const LLVolumeParams& volume_params = getVolume()->getParams(); + + LLMeshCostData costs; + if (getCostData(costs)) + { + if (isAnimatedObjectFast() && isRiggedMeshFast()) + { + // Scaling here is to make animated object vs + // non-animated object ARC proportional to the + // corresponding calculations for streaming cost. + num_triangles = (ANIMATED_OBJECT_COST_PER_KTRI * 0.001 * costs.getEstTrisForStreamingCost())/0.06; + } + else + { + F32 radius = getScale().length()*0.5f; + num_triangles = costs.getRadiusWeightedTris(radius); + } + } + + + if (num_triangles <= 0) + { + num_triangles = 4; + } + + if (isSculptedFast()) + { + if (isMeshFast()) + { + // base cost is dependent on mesh complexity + // note that 3 is the highest LOD as of the time of this coding. + S32 size = gMeshRepo.getMeshSize(volume_params.getSculptID(), getLOD()); + if ( size > 0) + { + if (isRiggedMeshFast()) + { + // weighted attachment - 1 point for every 3 bytes + weighted_mesh = 1; + } + } + else + { + // something went wrong - user should know their content isn't render-free + return 0; + } + } + else + { + LLViewerFetchedTexture* texture = mSculptTexture; + if (texture && textures.find(texture) == textures.end()) + { + textures.insert(texture); + } + } + } + + if (isFlexibleFast()) + { + flexi = 1; + } + if (isParticleSource()) + { + particles = 1; + } + + if (getIsLightFast()) + { + produces_light = 1; + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_VOLUME("ARC - face list"); + for (S32 i = 0; i < num_faces; ++i) + { + const LLFace* face = drawablep->getFace(i); + if (!face) continue; + const LLTextureEntry* te = face->getTextureEntry(); + const LLViewerTexture* img = face->getTexture(); + + if (img) + { + textures.insert(img); + } + + if (face->isInAlphaPool()) + { + alpha = 1; + } + else if (img && img->getPrimaryFormat() == GL_ALPHA) + { + invisi = 1; + } + if (face->hasMedia()) + { + media_faces++; + } + + if (te) + { + if (te->getBumpmap()) + { + // bump is a multiplier, don't add per-face + bump = 1; + } + if (te->getShiny()) + { + // shiny is a multiplier, don't add per-face + shiny = 1; + } + if (te->getGlow() > 0.f) + { + // glow is a multiplier, don't add per-face + glow = 1; + } + if (face->mTextureMatrix != NULL) + { + animtex = 1; + } + if (te->getTexGen()) + { + planar = 1; + } + } + } + } + + // shame currently has the "base" cost of 1 point per 15 triangles, min 2. + shame = num_triangles * 5.f; + shame = shame < 2.f ? 2.f : shame; + + // multiply by per-face modifiers + if (planar) + { + shame *= planar * ARC_PLANAR_COST; + } + + if (animtex) + { + shame *= animtex * ARC_ANIM_TEX_COST; + } + + if (alpha) + { + shame *= alpha * ARC_ALPHA_COST; + } + + if(invisi) + { + shame *= invisi * ARC_INVISI_COST; + } + + if (glow) + { + shame *= glow * ARC_GLOW_MULT; + } + + if (bump) + { + shame *= bump * ARC_BUMP_MULT; + } + + if (shiny) + { + shame *= shiny * ARC_SHINY_MULT; + } + + + // multiply shame by multipliers + if (weighted_mesh) + { + shame *= weighted_mesh * ARC_WEIGHTED_MESH; + } + + if (flexi) + { + shame *= flexi * ARC_FLEXI_MULT; + } + + + // add additional costs + if (particles) + { + const LLPartSysData *part_sys_data = &(mPartSourcep->mPartSysData); + const LLPartData *part_data = &(part_sys_data->mPartData); + U32 num_particles = (U32)(part_sys_data->mBurstPartCount * llceil( part_data->mMaxAge / part_sys_data->mBurstRate)); + num_particles = num_particles > ARC_PARTICLE_MAX ? ARC_PARTICLE_MAX : num_particles; + F32 part_size = (llmax(part_data->mStartScale[0], part_data->mEndScale[0]) + llmax(part_data->mStartScale[1], part_data->mEndScale[1])) / 2.f; + shame += num_particles * part_size * ARC_PARTICLE_COST; + } + + if (produces_light) + { + shame += ARC_LIGHT_COST; + } + + if (media_faces) + { + shame += media_faces * ARC_MEDIA_FACE_COST; + } + + // Streaming cost for animated objects includes a fixed cost + // per linkset. Add a corresponding charge here translated into + // triangles, but not weighted by any graphics properties. + if (isAnimatedObjectFast() && isRootEdit()) + { + shame += (ANIMATED_OBJECT_BASE_COST/0.06) * 5.0f; + } + + if (shame > mRenderComplexity_current) + { + mRenderComplexity_current = (S32)shame; + } + + return (U32)shame; +} + +F32 LLVOVolume::getEstTrianglesMax() const +{ + if (isMeshFast() && getVolume()) + { + return gMeshRepo.getEstTrianglesMax(getVolume()->getParams().getSculptID()); + } + return 0.f; +} + +F32 LLVOVolume::getEstTrianglesStreamingCost() const +{ + if (isMeshFast() && getVolume()) + { + return gMeshRepo.getEstTrianglesStreamingCost(getVolume()->getParams().getSculptID()); + } + return 0.f; +} + +F32 LLVOVolume::getStreamingCost() const +{ + F32 radius = getScale().length()*0.5f; + F32 linkset_base_cost = 0.f; + + LLMeshCostData costs; + if (getCostData(costs)) + { + if (isRootEdit() && isAnimatedObject()) + { + // Root object of an animated object has this to account for skeleton overhead. + linkset_base_cost = ANIMATED_OBJECT_BASE_COST; + } + if (isMesh()) + { + if (isAnimatedObject() && isRiggedMesh()) + { + return linkset_base_cost + costs.getTriangleBasedStreamingCost(); + } + else + { + return linkset_base_cost + costs.getRadiusBasedStreamingCost(radius); + } + } + else + { + return linkset_base_cost + costs.getRadiusBasedStreamingCost(radius); + } + } + else + { + return 0.f; + } +} + +// virtual +bool LLVOVolume::getCostData(LLMeshCostData& costs) const +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + + if (isMeshFast()) + { + return gMeshRepo.getCostData(getVolume()->getParams().getSculptID(), costs); + } + else + { + LLVolume* volume = getVolume(); + S32 counts[4]; + LLVolume::getLoDTriangleCounts(volume->getParams(), counts); + + LLMeshHeader header; + header.mLodSize[0] = counts[0] * 10; + header.mLodSize[1] = counts[1] * 10; + header.mLodSize[2] = counts[2] * 10; + header.mLodSize[3] = counts[3] * 10; + + return gMeshRepo.getCostData(header, costs); + } +} + +//static +void LLVOVolume::updateRenderComplexity() +{ + mRenderComplexity_last = mRenderComplexity_current; + mRenderComplexity_current = 0; +} + +U32 LLVOVolume::getTriangleCount(S32* vcount) const +{ + U32 count = 0; + LLVolume* volume = getVolume(); + if (volume) + { + count = volume->getNumTriangles(vcount); + } + + return count; +} + +U32 LLVOVolume::getHighLODTriangleCount() +{ + U32 ret = 0; + + LLVolume* volume = getVolume(); + + if (!isSculpted()) + { + LLVolume* ref = LLPrimitive::getVolumeManager()->refVolume(volume->getParams(), 3); + ret = ref->getNumTriangles(); + LLPrimitive::getVolumeManager()->unrefVolume(ref); + } + else if (isMesh()) + { + LLVolume* ref = LLPrimitive::getVolumeManager()->refVolume(volume->getParams(), 3); + if (!ref->isMeshAssetLoaded() || ref->getNumVolumeFaces() == 0) + { + gMeshRepo.loadMesh(this, volume->getParams(), LLModel::LOD_HIGH); + } + ret = ref->getNumTriangles(); + LLPrimitive::getVolumeManager()->unrefVolume(ref); + } + else + { //default sculpts have a constant number of triangles + ret = 31*2*31; //31 rows of 31 columns of quads for a 32x32 vertex patch + } + + return ret; +} + +//static +void LLVOVolume::preUpdateGeom() +{ + sNumLODChanges = 0; +} + +void LLVOVolume::parameterChanged(U16 param_type, bool local_origin) +{ + LLViewerObject::parameterChanged(param_type, local_origin); +} + +void LLVOVolume::parameterChanged(U16 param_type, LLNetworkData* data, bool in_use, bool local_origin) +{ + LLViewerObject::parameterChanged(param_type, data, in_use, local_origin); + if (mVolumeImpl) + { + mVolumeImpl->onParameterChanged(param_type, data, in_use, local_origin); + } + if (!local_origin && param_type == LLNetworkData::PARAMS_EXTENDED_MESH) + { + U32 extended_mesh_flags = getExtendedMeshFlags(); + bool enabled = (extended_mesh_flags & LLExtendedMeshParams::ANIMATED_MESH_ENABLED_FLAG); + bool was_enabled = (getControlAvatar() != NULL); + if (enabled != was_enabled) + { + LL_DEBUGS("AnimatedObjects") << this + << " calling onSetExtendedMeshFlags, enabled " << (U32) enabled + << " was_enabled " << (U32) was_enabled + << " local_origin " << (U32) local_origin + << LL_ENDL; + onSetExtendedMeshFlags(extended_mesh_flags); + } + } + if (mDrawable.notNull()) + { + bool is_light = getIsLight(); + if (is_light != mDrawable->isState(LLDrawable::LIGHT)) + { + gPipeline.setLight(mDrawable, is_light); + } + } + + updateReflectionProbePtr(); +} + +void LLVOVolume::updateReflectionProbePtr() +{ + if (isReflectionProbe()) + { + if (mReflectionProbe.isNull()) + { + mReflectionProbe = gPipeline.mReflectionMapManager.registerViewerObject(this); + } + } + else if (mReflectionProbe.notNull()) + { + mReflectionProbe = nullptr; + } +} + +void LLVOVolume::setSelected(bool sel) +{ + LLViewerObject::setSelected(sel); + if (isAnimatedObject()) + { + getRootEdit()->recursiveMarkForUpdate(); + } + else + { + if (mDrawable.notNull()) + { + markForUpdate(); + } + } +} + +void LLVOVolume::updateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax) +{ +} + +F32 LLVOVolume::getBinRadius() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + F32 radius; + + static LLCachedControl octree_size_factor(gSavedSettings, "OctreeStaticObjectSizeFactor", 3); + static LLCachedControl octree_attachment_size_factor(gSavedSettings, "OctreeAttachmentSizeFactor", 4); + static LLCachedControl octree_distance_factor(gSavedSettings, "OctreeDistanceFactor", LLVector3(0.01f, 0.f, 0.f)); + static LLCachedControl octree_alpha_distance_factor(gSavedSettings, "OctreeAlphaDistanceFactor", LLVector3(0.1f, 0.f, 0.f)); + + S32 size_factor = llmax((S32)octree_size_factor, 1); + LLVector3 alpha_distance_factor = octree_alpha_distance_factor; + + //const LLVector4a* ext = mDrawable->getSpatialExtents(); + + bool shrink_wrap = mShouldShrinkWrap || mDrawable->isAnimating(); + bool alpha_wrap = false; + + if (!isHUDAttachment() && mDrawable->mDistanceWRTCamera < alpha_distance_factor[2]) + { + for (S32 i = 0; i < mDrawable->getNumFaces(); i++) + { + LLFace* face = mDrawable->getFace(i); + if (!face) continue; + if (face->isInAlphaPool() && + !face->canRenderAsMask()) + { + alpha_wrap = true; + break; + } + } + } + else + { + shrink_wrap = false; + } + + if (alpha_wrap) + { + LLVector3 bounds = getScale(); + radius = llmin(bounds.mV[1], bounds.mV[2]); + radius = llmin(radius, bounds.mV[0]); + radius *= 0.5f; + //radius *= 1.f+mDrawable->mDistanceWRTCamera*alpha_distance_factor[1]; + //radius += mDrawable->mDistanceWRTCamera*alpha_distance_factor[0]; + } + else if (shrink_wrap) + { + radius = mDrawable->getRadius() * 0.25f; + } + else + { + F32 szf = size_factor; + radius = llmax(mDrawable->getRadius(), szf); + //radius = llmax(radius, mDrawable->mDistanceWRTCamera * distance_factor[0]); + } + + return llclamp(radius, 0.5f, 256.f); +} + +const LLVector3 LLVOVolume::getPivotPositionAgent() const +{ + if (mVolumeImpl) + { + return mVolumeImpl->getPivotPosition(); + } + return LLViewerObject::getPivotPositionAgent(); +} + +void LLVOVolume::onShift(const LLVector4a &shift_vector) +{ + if (mVolumeImpl) + { + mVolumeImpl->onShift(shift_vector); + } + + updateRelativeXform(); +} + +const LLMatrix4& LLVOVolume::getWorldMatrix(LLXformMatrix* xform) const +{ + if (mVolumeImpl) + { + return mVolumeImpl->getWorldMatrix(xform); + } + return xform->getWorldMatrix(); +} + +void LLVOVolume::markForUpdate() +{ + if (mDrawable) + { + shrinkWrap(); + } + + LLViewerObject::markForUpdate(); + mVolumeChanged = true; +} + +LLVector3 LLVOVolume::agentPositionToVolume(const LLVector3& pos) const +{ + LLVector3 ret = pos - getRenderPosition(); + ret = ret * ~getRenderRotation(); + if (!isVolumeGlobal()) + { + LLVector3 objScale = getScale(); + LLVector3 invObjScale(1.f / objScale.mV[VX], 1.f / objScale.mV[VY], 1.f / objScale.mV[VZ]); + ret.scaleVec(invObjScale); + } + + return ret; +} + +LLVector3 LLVOVolume::agentDirectionToVolume(const LLVector3& dir) const +{ + LLVector3 ret = dir * ~getRenderRotation(); + + LLVector3 objScale = isVolumeGlobal() ? LLVector3(1,1,1) : getScale(); + ret.scaleVec(objScale); + + return ret; +} + +LLVector3 LLVOVolume::volumePositionToAgent(const LLVector3& dir) const +{ + LLVector3 ret = dir; + if (!isVolumeGlobal()) + { + LLVector3 objScale = getScale(); + ret.scaleVec(objScale); + } + + ret = ret * getRenderRotation(); + ret += getRenderPosition(); + + return ret; +} + +LLVector3 LLVOVolume::volumeDirectionToAgent(const LLVector3& dir) const +{ + LLVector3 ret = dir; + LLVector3 objScale = isVolumeGlobal() ? LLVector3(1,1,1) : getScale(); + LLVector3 invObjScale(1.f / objScale.mV[VX], 1.f / objScale.mV[VY], 1.f / objScale.mV[VZ]); + ret.scaleVec(invObjScale); + ret = ret * getRenderRotation(); + + return ret; +} + + +bool LLVOVolume::lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, S32 face, bool pick_transparent, bool pick_rigged, bool pick_unselectable, S32 *face_hitp, + LLVector4a* intersection,LLVector2* tex_coord, LLVector4a* normal, LLVector4a* tangent) + +{ + if (!mbCanSelect + || mDrawable->isDead() + || !gPipeline.hasRenderType(mDrawable->getRenderType())) + { + return false; + } + + if (!pick_unselectable) + { + if (!LLSelectMgr::instance().canSelectObject(this, true)) + { + return false; + } + } + + if (getClickAction() == CLICK_ACTION_IGNORE && !LLFloater::isVisible(gFloaterTools)) + { + return false; + } + + bool ret = false; + + LLVolume* volume = getVolume(); + + bool transform = true; + + if (mDrawable->isState(LLDrawable::RIGGED)) + { + if ((pick_rigged) || (getAvatar() && (getAvatar()->isSelf()) && (LLFloater::isVisible(gFloaterTools)))) + { + updateRiggedVolume(true, LLRiggedVolume::DO_NOT_UPDATE_FACES); + volume = mRiggedVolume; + transform = false; + } + else + { //cannot pick rigged attachments on other avatars or when not in build mode + return false; + } + } + + if (volume) + { + LLVector4a local_start = start; + LLVector4a local_end = end; + + if (transform) + { + LLVector3 v_start(start.getF32ptr()); + LLVector3 v_end(end.getF32ptr()); + + v_start = agentPositionToVolume(v_start); + v_end = agentPositionToVolume(v_end); + + local_start.load3(v_start.mV); + local_end.load3(v_end.mV); + } + + LLVector4a p; + LLVector4a n; + LLVector2 tc; + LLVector4a tn; + + if (intersection != NULL) + { + p = *intersection; + } + + if (tex_coord != NULL) + { + tc = *tex_coord; + } + + if (normal != NULL) + { + n = *normal; + } + + if (tangent != NULL) + { + tn = *tangent; + } + + S32 face_hit = -1; + + S32 start_face, end_face; + if (face == -1) + { + start_face = 0; + end_face = volume->getNumVolumeFaces(); + } + else + { + start_face = face; + end_face = face+1; + } + pick_transparent |= isHiglightedOrBeacon(); + + // we *probably* shouldn't care about special cursor at all, but we *definitely* + // don't care about special cursor for reflection probes -- makes alt-zoom + // go through reflection probes on vehicles + bool special_cursor = mReflectionProbe.isNull() && specialHoverCursor(); + + for (S32 i = start_face; i < end_face; ++i) + { + if (!special_cursor && !pick_transparent && getTE(i) && getTE(i)->getColor().mV[3] == 0.f) + { //don't attempt to pick completely transparent faces unless + //pick_transparent is true + continue; + } + + // This calculates the bounding box of the skinned mesh from scratch. It's actually quite expensive, but not nearly as expensive as building a full octree. + // rebuild_face_octrees = false because an octree for this face will be built later only if needed for narrow phase picking. + updateRiggedVolume(true, i, false); + face_hit = volume->lineSegmentIntersect(local_start, local_end, i, + &p, &tc, &n, &tn); + + if (face_hit >= 0 && mDrawable->getNumFaces() > face_hit) + { + LLFace* face = mDrawable->getFace(face_hit); + + bool ignore_alpha = false; + + const LLTextureEntry* te = face->getTextureEntry(); + if (te) + { + LLMaterial* mat = te->getMaterialParams(); + if (mat) + { + U8 mode = mat->getDiffuseAlphaMode(); + + if (mode == LLMaterial::DIFFUSE_ALPHA_MODE_EMISSIVE + || mode == LLMaterial::DIFFUSE_ALPHA_MODE_NONE + || (mode == LLMaterial::DIFFUSE_ALPHA_MODE_MASK && mat->getAlphaMaskCutoff() == 0)) + { + ignore_alpha = true; + } + } + } + + bool no_texture = !face->getTexture() || !face->getTexture()->hasGLTexture(); + bool mask = no_texture ? false : face->getTexture()->getMask(face->surfaceToTexture(tc, p, n)); + if (face && + (ignore_alpha || pick_transparent || no_texture || mask)) + { + local_end = p; + if (face_hitp != NULL) + { + *face_hitp = face_hit; + } + + if (intersection != NULL) + { + if (transform) + { + LLVector3 v_p(p.getF32ptr()); + + intersection->load3(volumePositionToAgent(v_p).mV); // must map back to agent space + } + else + { + *intersection = p; + } + } + + if (normal != NULL) + { + if (transform) + { + LLVector3 v_n(n.getF32ptr()); + normal->load3(volumeDirectionToAgent(v_n).mV); + } + else + { + *normal = n; + } + (*normal).normalize3fast(); + } + + if (tangent != NULL) + { + if (transform) + { + LLVector3 v_tn(tn.getF32ptr()); + + LLVector4a trans_tangent; + trans_tangent.load3(volumeDirectionToAgent(v_tn).mV); + + LLVector4Logical mask; + mask.clear(); + mask.setElement<3>(); + + tangent->setSelectWithMask(mask, tn, trans_tangent); + } + else + { + *tangent = tn; + } + (*tangent).normalize3fast(); + } + + if (tex_coord != NULL) + { + *tex_coord = tc; + } + + ret = true; + } + } + } + } + + return ret; +} + +bool LLVOVolume::treatAsRigged() +{ + return isSelected() && + (isAttachment() || isAnimatedObject()) && + mDrawable.notNull() && + mDrawable->isState(LLDrawable::RIGGED); +} + +LLRiggedVolume* LLVOVolume::getRiggedVolume() +{ + return mRiggedVolume; +} + +void LLVOVolume::clearRiggedVolume() +{ + if (mRiggedVolume.notNull()) + { + mRiggedVolume = NULL; + updateRelativeXform(); + } +} + +void LLVOVolume::updateRiggedVolume(bool force_treat_as_rigged, LLRiggedVolume::FaceIndex face_index, bool rebuild_face_octrees) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + //Update mRiggedVolume to match current animation frame of avatar. + //Also update position/size in octree. + + if ((!force_treat_as_rigged) && (!treatAsRigged())) + { + clearRiggedVolume(); + + return; + } + + LLVolume* volume = getVolume(); + const LLMeshSkinInfo* skin = getSkinInfo(); + if (!skin) + { + clearRiggedVolume(); + return; + } + + LLVOAvatar* avatar = getAvatar(); + if (!avatar) + { + clearRiggedVolume(); + return; + } + + if (!mRiggedVolume) + { + LLVolumeParams p; + mRiggedVolume = new LLRiggedVolume(p); + updateRelativeXform(); + } + + mRiggedVolume->update(skin, avatar, volume, face_index, rebuild_face_octrees); +} + +void LLRiggedVolume::update( + const LLMeshSkinInfo* skin, + LLVOAvatar* avatar, + const LLVolume* volume, + FaceIndex face_index, + bool rebuild_face_octrees) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + bool copy = false; + if (volume->getNumVolumeFaces() != getNumVolumeFaces()) + { + copy = true; + } + + for (S32 i = 0; i < volume->getNumVolumeFaces() && !copy; ++i) + { + const LLVolumeFace& src_face = volume->getVolumeFace(i); + const LLVolumeFace& dst_face = getVolumeFace(i); + + if (src_face.mNumIndices != dst_face.mNumIndices || + src_face.mNumVertices != dst_face.mNumVertices) + { + copy = true; + } + } + + if (copy) + { + copyVolumeFaces(volume); + } + else + { + bool is_paused = avatar && avatar->areAnimationsPaused(); + if (is_paused) + { + S32 frames_paused = LLFrameTimer::getFrameCount() - avatar->getMotionController().getPausedFrame(); + if (frames_paused > 1) + { + return; + } + } + } + + + //build matrix palette + static const size_t kMaxJoints = LL_MAX_JOINTS_PER_MESH_OBJECT; + + LLMatrix4a mat[kMaxJoints]; + U32 maxJoints = LLSkinningUtil::getMeshJointCount(skin); + LLSkinningUtil::initSkinningMatrixPalette(mat, maxJoints, skin, avatar); + const LLMatrix4a bind_shape_matrix = skin->mBindShapeMatrix; + + S32 rigged_vert_count = 0; + S32 rigged_face_count = 0; + LLVector4a box_min, box_max; + S32 face_begin; + S32 face_end; + if (face_index == DO_NOT_UPDATE_FACES) + { + face_begin = 0; + face_end = 0; + } + else if (face_index == UPDATE_ALL_FACES) + { + face_begin = 0; + face_end = volume->getNumVolumeFaces(); + } + else + { + face_begin = face_index; + face_end = face_begin + 1; + } + for (S32 i = face_begin; i < face_end; ++i) + { + const LLVolumeFace& vol_face = volume->getVolumeFace(i); + + LLVolumeFace& dst_face = mVolumeFaces[i]; + + LLVector4a* weight = vol_face.mWeights; + + if ( weight ) + { + LLSkinningUtil::checkSkinWeights(weight, dst_face.mNumVertices, skin); + + LLVector4a* pos = dst_face.mPositions; + + if (pos && dst_face.mExtents) + { + U32 max_joints = LLSkinningUtil::getMaxJointCount(); + rigged_vert_count += dst_face.mNumVertices; + rigged_face_count++; + + #if USE_SEPARATE_JOINT_INDICES_AND_WEIGHTS + if (vol_face.mJointIndices) // fast path with preconditioned joint indices + { + LLMatrix4a src[4]; + U8* joint_indices_cursor = vol_face.mJointIndices; + LLVector4a* just_weights = vol_face.mJustWeights; + for (U32 j = 0; j < dst_face.mNumVertices; ++j) + { + LLMatrix4a final_mat; + F32* w = just_weights[j].getF32ptr(); + LLSkinningUtil::getPerVertexSkinMatrixWithIndices(w, joint_indices_cursor, mat, final_mat, src); + joint_indices_cursor += 4; + + LLVector4a& v = vol_face.mPositions[j]; + LLVector4a t; + LLVector4a dst; + bind_shape_matrix.affineTransform(v, t); + final_mat.affineTransform(t, dst); + pos[j] = dst; + } + } + else + #endif + { + for (U32 j = 0; j < dst_face.mNumVertices; ++j) + { + LLMatrix4a final_mat; + LLSkinningUtil::getPerVertexSkinMatrix(weight[j].getF32ptr(), mat, false, final_mat, max_joints); + + LLVector4a& v = vol_face.mPositions[j]; + LLVector4a t; + LLVector4a dst; + bind_shape_matrix.affineTransform(v, t); + final_mat.affineTransform(t, dst); + pos[j] = dst; + } + } + + //update bounding box + // VFExtents change + LLVector4a& min = dst_face.mExtents[0]; + LLVector4a& max = dst_face.mExtents[1]; + + min = pos[0]; + max = pos[1]; + if (i==0) + { + box_min = min; + box_max = max; + } + + for (U32 j = 1; j < dst_face.mNumVertices; ++j) + { + min.setMin(min, pos[j]); + max.setMax(max, pos[j]); + } + + box_min.setMin(min,box_min); + box_max.setMax(max,box_max); + + dst_face.mCenter->setAdd(dst_face.mExtents[0], dst_face.mExtents[1]); + dst_face.mCenter->mul(0.5f); + + } + + if (rebuild_face_octrees) + { + dst_face.destroyOctree(); + dst_face.createOctree(); + } + } + } + mExtraDebugText = llformat("rigged %d/%d - box (%f %f %f) (%f %f %f)", + rigged_face_count, rigged_vert_count, + box_min[0], box_min[1], box_min[2], + box_max[0], box_max[1], box_max[2]); +} + +U32 LLVOVolume::getPartitionType() const +{ + if (isHUDAttachment()) + { + return LLViewerRegion::PARTITION_HUD; + } + if (isAnimatedObject() && getControlAvatar()) + { + return LLViewerRegion::PARTITION_CONTROL_AV; + } + if (isAttachment()) + { + return LLViewerRegion::PARTITION_AVATAR; + } + + return LLViewerRegion::PARTITION_VOLUME; +} + +LLVolumePartition::LLVolumePartition(LLViewerRegion* regionp) +: LLSpatialPartition(LLVOVolume::VERTEX_DATA_MASK, true, regionp), +LLVolumeGeometryManager() +{ + mLODPeriod = 32; + mDepthMask = false; + mDrawableType = LLPipeline::RENDER_TYPE_VOLUME; + mPartitionType = LLViewerRegion::PARTITION_VOLUME; + mSlopRatio = 0.25f; +} + +LLVolumeBridge::LLVolumeBridge(LLDrawable* drawablep, LLViewerRegion* regionp) +: LLSpatialBridge(drawablep, true, LLVOVolume::VERTEX_DATA_MASK, regionp), +LLVolumeGeometryManager() +{ + mDepthMask = false; + mLODPeriod = 32; + mDrawableType = LLPipeline::RENDER_TYPE_VOLUME; + mPartitionType = LLViewerRegion::PARTITION_BRIDGE; + + mSlopRatio = 0.25f; +} + +LLAvatarBridge::LLAvatarBridge(LLDrawable* drawablep, LLViewerRegion* regionp) + : LLVolumeBridge(drawablep, regionp) +{ + mDrawableType = LLPipeline::RENDER_TYPE_AVATAR; + mPartitionType = LLViewerRegion::PARTITION_AVATAR; +} + +LLControlAVBridge::LLControlAVBridge(LLDrawable* drawablep, LLViewerRegion* regionp) + : LLVolumeBridge(drawablep, regionp) +{ + mDrawableType = LLPipeline::RENDER_TYPE_CONTROL_AV; + mPartitionType = LLViewerRegion::PARTITION_CONTROL_AV; +} + +bool can_batch_texture(LLFace* facep) +{ + if (facep->getTextureEntry()->getBumpmap()) + { //bump maps aren't worked into texture batching yet + return false; + } + + if (facep->getTextureEntry()->getMaterialParams().notNull()) + { //materials don't work with texture batching yet + return false; + } + + if (facep->getTexture() && facep->getTexture()->getPrimaryFormat() == GL_ALPHA) + { //can't batch invisiprims + return false; + } + + if (facep->isState(LLFace::TEXTURE_ANIM) && facep->getVirtualSize() > MIN_TEX_ANIM_SIZE) + { //texture animation breaks batches + return false; + } + + if (facep->getTextureEntry()->getGLTFRenderMaterial() != nullptr) + { // PBR materials break indexed texture batching + return false; + } + + return true; +} + +const static U32 MAX_FACE_COUNT = 4096U; +int32_t LLVolumeGeometryManager::sInstanceCount = 0; +LLFace** LLVolumeGeometryManager::sFullbrightFaces[2] = { NULL }; +LLFace** LLVolumeGeometryManager::sBumpFaces[2] = { NULL }; +LLFace** LLVolumeGeometryManager::sSimpleFaces[2] = { NULL }; +LLFace** LLVolumeGeometryManager::sNormFaces[2] = { NULL }; +LLFace** LLVolumeGeometryManager::sSpecFaces[2] = { NULL }; +LLFace** LLVolumeGeometryManager::sNormSpecFaces[2] = { NULL }; +LLFace** LLVolumeGeometryManager::sPbrFaces[2] = { NULL }; +LLFace** LLVolumeGeometryManager::sAlphaFaces[2] = { NULL }; + +LLVolumeGeometryManager::LLVolumeGeometryManager() + : LLGeometryManager() +{ + llassert(sInstanceCount >= 0); + if (sInstanceCount == 0) + { + allocateFaces(MAX_FACE_COUNT); + } + + ++sInstanceCount; +} + +LLVolumeGeometryManager::~LLVolumeGeometryManager() +{ + llassert(sInstanceCount > 0); + --sInstanceCount; + + if (sInstanceCount <= 0) + { + freeFaces(); + sInstanceCount = 0; + } +} + +void LLVolumeGeometryManager::allocateFaces(U32 pMaxFaceCount) +{ + for (int i = 0; i < 2; ++i) + { + sFullbrightFaces[i] = static_cast(ll_aligned_malloc<64>(pMaxFaceCount * sizeof(LLFace*))); + sBumpFaces[i] = static_cast(ll_aligned_malloc<64>(pMaxFaceCount * sizeof(LLFace*))); + sSimpleFaces[i] = static_cast(ll_aligned_malloc<64>(pMaxFaceCount * sizeof(LLFace*))); + sNormFaces[i] = static_cast(ll_aligned_malloc<64>(pMaxFaceCount * sizeof(LLFace*))); + sSpecFaces[i] = static_cast(ll_aligned_malloc<64>(pMaxFaceCount * sizeof(LLFace*))); + sNormSpecFaces[i] = static_cast(ll_aligned_malloc<64>(pMaxFaceCount * sizeof(LLFace*))); + sPbrFaces[i] = static_cast(ll_aligned_malloc<64>(pMaxFaceCount * sizeof(LLFace*))); + sAlphaFaces[i] = static_cast(ll_aligned_malloc<64>(pMaxFaceCount * sizeof(LLFace*))); + } +} + +void LLVolumeGeometryManager::freeFaces() +{ + for (int i = 0; i < 2; ++i) + { + ll_aligned_free<64>(sFullbrightFaces[i]); + ll_aligned_free<64>(sBumpFaces[i]); + ll_aligned_free<64>(sSimpleFaces[i]); + ll_aligned_free<64>(sNormFaces[i]); + ll_aligned_free<64>(sSpecFaces[i]); + ll_aligned_free<64>(sNormSpecFaces[i]); + ll_aligned_free<64>(sPbrFaces[i]); + ll_aligned_free<64>(sAlphaFaces[i]); + + sFullbrightFaces[i] = NULL; + sBumpFaces[i] = NULL; + sSimpleFaces[i] = NULL; + sNormFaces[i] = NULL; + sSpecFaces[i] = NULL; + sNormSpecFaces[i] = NULL; + sPbrFaces[i] = NULL; + sAlphaFaces[i] = NULL; + } +} + +void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep, U32 type) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + if ( type == LLRenderPass::PASS_ALPHA + && facep->getTextureEntry()->getMaterialParams().notNull() + && !facep->getVertexBuffer()->hasDataType(LLVertexBuffer::TYPE_TANGENT) + && LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_OBJECT) > 1) + { + LL_WARNS_ONCE("RenderMaterials") << "Oh no! No binormals for this alpha blended face!" << LL_ENDL; + } + + bool selected = facep->getViewerObject()->isSelected(); + + if (selected && LLSelectMgr::getInstance()->mHideSelectedObjects) + { + return; + } + + LL_LABEL_VERTEX_BUFFER(facep->getVertexBuffer(), LLRenderPass::lookupPassName(type)); + + U32 passType = type; + + bool rigged = facep->isState(LLFace::RIGGED); + + if (rigged) + { + // hacky, should probably clean up -- if this face is rigged, put it in "type + 1" + // See LLRenderPass PASS_foo enum + passType += 1; + } + //add face to drawmap + LLSpatialGroup::drawmap_elem_t& draw_vec = group->mDrawMap[passType]; + + S32 idx = draw_vec.size()-1; + + bool fullbright = (type == LLRenderPass::PASS_FULLBRIGHT) || + (type == LLRenderPass::PASS_INVISIBLE) || + (type == LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK) || + (type == LLRenderPass::PASS_ALPHA && facep->isState(LLFace::FULLBRIGHT)) || + (facep->getTextureEntry()->getFullbright()); + + if (!fullbright && + type != LLRenderPass::PASS_GLOW && + !facep->getVertexBuffer()->hasDataType(LLVertexBuffer::TYPE_NORMAL)) + { + llassert(false); + LL_WARNS() << "Non fullbright face has no normals!" << LL_ENDL; + return; + } + + const LLMatrix4* tex_mat = NULL; + if (facep->isState(LLFace::TEXTURE_ANIM) && facep->getVirtualSize() > MIN_TEX_ANIM_SIZE) + { + tex_mat = facep->mTextureMatrix; + } + + const LLMatrix4* model_mat = NULL; + + LLDrawable* drawable = facep->getDrawable(); + + if (rigged) + { + // rigged meshes ignore their model matrix + model_mat = nullptr; + } + else if (drawable->isState(LLDrawable::ANIMATED_CHILD)) + { + model_mat = &drawable->getWorldMatrix(); + } + else if (drawable->isActive()) + { + model_mat = &drawable->getRenderMatrix(); + } + else + { + model_mat = &(drawable->getRegion()->mRenderMatrix); + } + + //drawable->getVObj()->setDebugText(llformat("%d", drawable->isState(LLDrawable::ANIMATED_CHILD))); + + const LLTextureEntry* te = facep->getTextureEntry(); + U8 bump = (type == LLRenderPass::PASS_BUMP || type == LLRenderPass::PASS_POST_BUMP) ? te->getBumpmap() : 0; + U8 shiny = te->getShiny(); + + LLViewerTexture* tex = facep->getTexture(); + + U8 index = facep->getTextureIndex(); + + LLMaterial* mat = nullptr; + + LLUUID mat_id; + + auto* gltf_mat = (LLFetchedGLTFMaterial*)te->getGLTFRenderMaterial(); + llassert(gltf_mat == nullptr || dynamic_cast(te->getGLTFRenderMaterial()) != nullptr); + if (gltf_mat != nullptr) + { + mat_id = gltf_mat->getHash(); // TODO: cache this hash + if (!facep->hasMedia() || (tex && tex->getType() != LLViewerTexture::MEDIA_TEXTURE)) + { // no media texture, face texture will be unused + tex = nullptr; + } + } + else + { + mat = te->getMaterialParams().get(); + if (mat) + { + mat_id = te->getMaterialParams()->getHash(); + } + } + + bool batchable = false; + + U32 shader_mask = 0xFFFFFFFF; //no shader + + if (mat) + { + bool is_alpha = (facep->getPoolType() == LLDrawPool::POOL_ALPHA) || (te->getColor().mV[3] < 0.999f); + if (type == LLRenderPass::PASS_ALPHA) + { + shader_mask = mat->getShaderMask(LLMaterial::DIFFUSE_ALPHA_MODE_BLEND, is_alpha); + } + else + { + shader_mask = mat->getShaderMask(LLMaterial::DIFFUSE_ALPHA_MODE_DEFAULT, is_alpha); + } + } + + if (index < FACE_DO_NOT_BATCH_TEXTURES && idx >= 0) + { + if (mat || gltf_mat || draw_vec[idx]->mMaterial) + { //can't batch textures when materials are present (yet) + batchable = false; + } + else if (index < draw_vec[idx]->mTextureList.size()) + { + if (draw_vec[idx]->mTextureList[index].isNull()) + { + batchable = true; + draw_vec[idx]->mTextureList[index] = tex; + } + else if (draw_vec[idx]->mTextureList[index] == tex) + { //this face's texture index can be used with this batch + batchable = true; + } + } + else + { //texture list can be expanded to fit this texture index + batchable = true; + } + } + + LLDrawInfo* info = idx >= 0 ? draw_vec[idx] : nullptr; + + if (info && + info->mVertexBuffer == facep->getVertexBuffer() && + info->mEnd == facep->getGeomIndex()-1 && + (LLPipeline::sTextureBindTest || draw_vec[idx]->mTexture == tex || batchable) && +#if LL_DARWIN + info->mEnd - draw_vec[idx]->mStart + facep->getGeomCount() <= (U32) gGLManager.mGLMaxVertexRange && + info->mCount + facep->getIndicesCount() <= (U32) gGLManager.mGLMaxIndexRange && +#endif + info->mMaterialID == mat_id && + info->mFullbright == fullbright && + info->mBump == bump && + (!mat || (info->mShiny == shiny)) && // need to break batches when a material is shared, but legacy settings are different + info->mTextureMatrix == tex_mat && + info->mModelMatrix == model_mat && + info->mShaderMask == shader_mask && + info->mAvatar == facep->mAvatar && + info->getSkinHash() == facep->getSkinHash()) + { + info->mCount += facep->getIndicesCount(); + info->mEnd += facep->getGeomCount(); + + if (index < FACE_DO_NOT_BATCH_TEXTURES && index >= info->mTextureList.size()) + { + info->mTextureList.resize(index+1); + info->mTextureList[index] = tex; + } + info->validate(); + } + else + { + U32 start = facep->getGeomIndex(); + U32 end = start + facep->getGeomCount()-1; + U32 offset = facep->getIndicesStart(); + U32 count = facep->getIndicesCount(); + LLPointer draw_info = new LLDrawInfo(start,end,count,offset, tex, + facep->getVertexBuffer(), fullbright, bump); + + info = draw_info; + + draw_vec.push_back(draw_info); + draw_info->mTextureMatrix = tex_mat; + draw_info->mModelMatrix = model_mat; + + draw_info->mBump = bump; + draw_info->mShiny = shiny; + + static const float alpha[4] = + { + 0.00f, + 0.25f, + 0.5f, + 0.75f + }; + float spec = alpha[shiny & TEM_SHINY_MASK]; + LLVector4 specColor(spec, spec, spec, spec); + draw_info->mSpecColor = specColor; + draw_info->mEnvIntensity = spec; + draw_info->mSpecularMap = NULL; + draw_info->mMaterial = mat; + draw_info->mGLTFMaterial = gltf_mat; + draw_info->mShaderMask = shader_mask; + draw_info->mAvatar = facep->mAvatar; + draw_info->mSkinInfo = facep->mSkinInfo; + + if (gltf_mat) + { + // just remember the material ID, render pools will reference the GLTF material + draw_info->mMaterialID = mat_id; + } + else if (mat) + { + draw_info->mMaterialID = mat_id; + + // We have a material. Update our draw info accordingly. + + if (!mat->getSpecularID().isNull()) + { + LLVector4 specColor; + specColor.mV[0] = mat->getSpecularLightColor().mV[0] * (1.f / 255.f); + specColor.mV[1] = mat->getSpecularLightColor().mV[1] * (1.f / 255.f); + specColor.mV[2] = mat->getSpecularLightColor().mV[2] * (1.f / 255.f); + specColor.mV[3] = mat->getSpecularLightExponent() * (1.f / 255.f); + draw_info->mSpecColor = specColor; + draw_info->mEnvIntensity = mat->getEnvironmentIntensity() * (1.f / 255.f); + draw_info->mSpecularMap = facep->getViewerObject()->getTESpecularMap(facep->getTEOffset()); + } + + draw_info->mAlphaMaskCutoff = mat->getAlphaMaskCutoff() * (1.f / 255.f); + draw_info->mDiffuseAlphaMode = mat->getDiffuseAlphaMode(); + draw_info->mNormalMap = facep->getViewerObject()->getTENormalMap(facep->getTEOffset()); + } + else + { + if (type == LLRenderPass::PASS_GRASS) + { + draw_info->mAlphaMaskCutoff = 0.5f; + } + else + { + draw_info->mAlphaMaskCutoff = 0.33f; + } + } + + // if (type == LLRenderPass::PASS_ALPHA) // always populate the draw_info ptr + { //for alpha sorting + facep->setDrawInfo(draw_info); + } + + if (index < FACE_DO_NOT_BATCH_TEXTURES) + { //initialize texture list for texture batching + draw_info->mTextureList.resize(index+1); + draw_info->mTextureList[index] = tex; + } + draw_info->validate(); + } + + llassert(info->mGLTFMaterial == nullptr || (info->mVertexBuffer->getTypeMask() & LLVertexBuffer::MAP_TANGENT) != 0); + llassert(type != LLPipeline::RENDER_TYPE_PASS_GLTF_PBR || info->mGLTFMaterial != nullptr); + llassert(type != LLPipeline::RENDER_TYPE_PASS_GLTF_PBR_RIGGED || info->mGLTFMaterial != nullptr); + llassert(type != LLPipeline::RENDER_TYPE_PASS_GLTF_PBR_ALPHA_MASK || info->mGLTFMaterial != nullptr); + llassert(type != LLPipeline::RENDER_TYPE_PASS_GLTF_PBR_ALPHA_MASK_RIGGED || info->mGLTFMaterial != nullptr); + + llassert(type != LLRenderPass::PASS_BUMP || (info->mVertexBuffer->getTypeMask() & LLVertexBuffer::MAP_TANGENT) != 0); + llassert(type != LLRenderPass::PASS_NORMSPEC || info->mNormalMap.notNull()); + llassert(type != LLRenderPass::PASS_SPECMAP || (info->mVertexBuffer->getTypeMask() & LLVertexBuffer::MAP_TEXCOORD2) != 0); +} + +void LLVolumeGeometryManager::getGeometry(LLSpatialGroup* group) +{ + +} + +// add a face pointer to a list of face pointers without going over MAX_COUNT faces +template +static inline void add_face(T*** list, U32* count, T* face) +{ + if (face->isState(LLFace::RIGGED)) + { + if (count[1] < MAX_FACE_COUNT) + { + face->setDrawOrderIndex(count[1]); + list[1][count[1]++] = face; + } + } + else + { + if (count[0] < MAX_FACE_COUNT) + { + face->setDrawOrderIndex(count[0]); + list[0][count[0]++] = face; + } + } +} + +void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + llassert(!gCubeSnapshot); + + if (group->isDead()) + { + return; + } + + if (group->changeLOD()) + { + group->mLastUpdateDistance = group->mDistance; + } + + group->mLastUpdateViewAngle = group->mViewAngle; + + if (!group->hasState(LLSpatialGroup::GEOM_DIRTY | LLSpatialGroup::ALPHA_DIRTY)) + { + if (group->hasState(LLSpatialGroup::MESH_DIRTY)) + { + rebuildMesh(group); + } + return; + } + + group->mBuilt = 1.f; + + LLSpatialBridge* bridge = group->getSpatialPartition()->asBridge(); + LLViewerObject *vobj = NULL; + LLVOVolume *vol_obj = NULL; + + if (bridge) + { + vobj = bridge->mDrawable->getVObj(); + vol_obj = dynamic_cast(vobj); + } + if (vol_obj) + { + vol_obj->updateVisualComplexity(); + } + + group->mGeometryBytes = 0; + group->mSurfaceArea = 0; + + //cache object box size since it might be used for determining visibility + const LLVector4a* bounds = group->getObjectBounds(); + group->mObjectBoxSize = bounds[1].getLength3().getF32(); + + group->clearDrawMap(); + + U32 fullbright_count[2] = { 0 }; + U32 bump_count[2] = { 0 }; + U32 simple_count[2] = { 0 }; + U32 alpha_count[2] = { 0 }; + U32 norm_count[2] = { 0 }; + U32 spec_count[2] = { 0 }; + U32 normspec_count[2] = { 0 }; + U32 pbr_count[2] = { 0 }; + + static LLCachedControl max_vbo_size(gSavedSettings, "RenderMaxVBOSize", 512); + static LLCachedControl max_node_size(gSavedSettings, "RenderMaxNodeSize", 65536); + U32 max_vertices = (max_vbo_size * 1024)/LLVertexBuffer::calcVertexSize(group->getSpatialPartition()->mVertexDataMask); + U32 max_total = (max_node_size * 1024) / LLVertexBuffer::calcVertexSize(group->getSpatialPartition()->mVertexDataMask); + max_vertices = llmin(max_vertices, (U32) 65535); + + U32 cur_total = 0; + + bool emissive = false; + + //Determine if we've received skininfo that contains an + //alternate bind matrix - if it does then apply the translational component + //to the joints of the avatar. +#if 0 + bool pelvisGotSet = false; +#endif + + { + LL_PROFILE_ZONE_NAMED("rebuildGeom - face list"); + + //get all the faces into a list + for (LLSpatialGroup::element_iter drawable_iter = group->getDataBegin(); + drawable_iter != group->getDataEnd(); ++drawable_iter) + { + LLDrawable* drawablep = (LLDrawable*)(*drawable_iter)->getDrawable(); + + if (!drawablep || drawablep->isDead() || drawablep->isState(LLDrawable::FORCE_INVISIBLE) ) + { + continue; + } + + LLVOVolume* vobj = drawablep->getVOVolume(); + + if (!vobj || vobj->isDead()) + { + continue; + } + + // HACK -- brute force this check every time a drawable gets rebuilt + for (S32 i = 0; i < drawablep->getNumFaces(); ++i) + { + vobj->updateTEMaterialTextures(i); + } + + // apply any pending material overrides + gGLTFMaterialList.applyQueuedOverrides(vobj); + + std::string vobj_name = llformat("Vol%p", vobj); + + bool is_mesh = vobj->isMesh(); + if (is_mesh) + { + if ((vobj->getVolume() && !vobj->getVolume()->isMeshAssetLoaded()) + || !gMeshRepo.meshRezEnabled()) + { + // Waiting for asset to fetch + continue; + } + + if (!vobj->getSkinInfo() && !vobj->isSkinInfoUnavaliable()) + { + // Waiting for skin info to fetch + continue; + } + } + + LLVolume* volume = vobj->getVolume(); + if (volume) + { + const LLVector3& scale = vobj->getScale(); + group->mSurfaceArea += volume->getSurfaceArea() * llmax(llmax(scale.mV[0], scale.mV[1]), scale.mV[2]); + } + + + F32 est_tris = vobj->getEstTrianglesMax(); + + vobj->updateControlAvatar(); + + LL_DEBUGS("AnimatedObjectsLinkset") << vobj_name << " rebuilding, isAttachment: " << (U32) vobj->isAttachment() + << " is_mesh " << is_mesh + << " est_tris " << est_tris + << " is_animated " << vobj->isAnimatedObject() + << " can_animate " << vobj->canBeAnimatedObject() + << " cav " << vobj->getControlAvatar() + << " lod " << vobj->getLOD() + << " drawable rigged " << (drawablep->isState(LLDrawable::RIGGED)) + << " drawable state " << drawablep->getState() + << " playing " << (U32) (vobj->getControlAvatar() ? vobj->getControlAvatar()->mPlaying : false) + << " frame " << LLFrameTimer::getFrameCount() + << LL_ENDL; + + llassert_always(vobj); + vobj->updateTextureVirtualSize(true); + vobj->preRebuild(); + + drawablep->clearState(LLDrawable::HAS_ALPHA); + + LLVOAvatar* avatar = nullptr; + const LLMeshSkinInfo* skinInfo = nullptr; + if (is_mesh) + { + skinInfo = vobj->getSkinInfo(); + } + + if (skinInfo) + { + if (vobj->isAnimatedObject()) + { + avatar = vobj->getControlAvatar(); + } + else + { + avatar = vobj->getAvatar(); + } + } + + if (avatar != nullptr) + { + avatar->addAttachmentOverridesForObject(vobj, NULL, false); + } + + // Standard rigged mesh attachments: + bool rigged = !vobj->isAnimatedObject() && skinInfo && vobj->isAttachment(); + // Animated objects. Have to check for isRiggedMesh() to + // exclude static objects in animated object linksets. + rigged = rigged || (vobj->isAnimatedObject() && vobj->isRiggedMesh() && + vobj->getControlAvatar() && vobj->getControlAvatar()->mPlaying); + + bool any_rigged_face = false; + + //for each face + for (S32 i = 0; i < drawablep->getNumFaces(); i++) + { + LLFace* facep = drawablep->getFace(i); + if (!facep) + { + continue; + } +#if 0 +#if LL_RELEASE_WITH_DEBUG_INFO + const LLUUID pbr_id( "49c88210-7238-2a6b-70ac-92d4f35963cf" ); + const LLUUID obj_id( vobj->getID() ); + bool is_pbr = (obj_id == pbr_id); +#else + bool is_pbr = false; +#endif +#else + LLGLTFMaterial *gltf_mat = facep->getTextureEntry()->getGLTFRenderMaterial(); + bool is_pbr = gltf_mat != nullptr; +#endif + + //ALWAYS null out vertex buffer on rebuild -- if the face lands in a render + // batch, it will recover its vertex buffer reference from the spatial group + facep->setVertexBuffer(NULL); + + //sum up face verts and indices + drawablep->updateFaceSize(i); + + if (rigged) + { + if (!facep->isState(LLFace::RIGGED)) + { //completely reset vertex buffer + facep->clearVertexBuffer(); + } + + facep->setState(LLFace::RIGGED); + facep->mSkinInfo = (LLMeshSkinInfo*) skinInfo; // TODO -- fix ugly de-consting here + facep->mAvatar = avatar; + any_rigged_face = true; + } + else + { + if (facep->isState(LLFace::RIGGED)) + { + //face is not rigged but used to be, remove from rigged face pool + LLDrawPoolAvatar* pool = (LLDrawPoolAvatar*)facep->getPool(); + if (pool) + { + pool->removeFace(facep); + } + facep->clearState(LLFace::RIGGED); + facep->mAvatar = NULL; + facep->mSkinInfo = NULL; + } + } + + if (cur_total > max_total || facep->getIndicesCount() <= 0 || facep->getGeomCount() <= 0) + { + facep->clearVertexBuffer(); + continue; + } + + if (facep->hasGeometry() && + (rigged || // <-- HACK FIXME -- getPixelArea might be incorrect for rigged objects + facep->getPixelArea() > FORCE_CULL_AREA)) // <-- don't render tiny faces + { + cur_total += facep->getGeomCount(); + + const LLTextureEntry* te = facep->getTextureEntry(); + LLViewerTexture* tex = facep->getTexture(); + + if (te->getGlow() > 0.f) + { + emissive = true; + } + + if (facep->isState(LLFace::TEXTURE_ANIM)) + { + if (!vobj->mTexAnimMode) + { + facep->clearState(LLFace::TEXTURE_ANIM); + } + } + + bool force_simple = (facep->getPixelArea() < FORCE_SIMPLE_RENDER_AREA); + U32 type = gPipeline.getPoolTypeFromTE(te, tex); + if (is_pbr && gltf_mat && gltf_mat->mAlphaMode != LLGLTFMaterial::ALPHA_MODE_BLEND) + { + type = LLDrawPool::POOL_GLTF_PBR; + } + else + if (type != LLDrawPool::POOL_ALPHA && force_simple) + { + type = LLDrawPool::POOL_SIMPLE; + } + facep->setPoolType(type); + + if (vobj->isHUDAttachment() && !is_pbr) + { + facep->setState(LLFace::FULLBRIGHT); + } + + if (vobj->mTextureAnimp && vobj->mTexAnimMode) + { + if (vobj->mTextureAnimp->mFace <= -1) + { + S32 face; + for (face = 0; face < vobj->getNumTEs(); face++) + { + LLFace * facep = drawablep->getFace(face); + if (facep) + { + facep->setState(LLFace::TEXTURE_ANIM); + } + } + } + else if (vobj->mTextureAnimp->mFace < vobj->getNumTEs()) + { + LLFace * facep = drawablep->getFace(vobj->mTextureAnimp->mFace); + if (facep) + { + facep->setState(LLFace::TEXTURE_ANIM); + } + } + } + + if (type == LLDrawPool::POOL_ALPHA) + { + if (facep->canRenderAsMask()) + { //can be treated as alpha mask + add_face(sSimpleFaces, simple_count, facep); + } + else + { + F32 alpha; + if (is_pbr) + { + alpha = gltf_mat ? gltf_mat->mBaseColor.mV[3] : 1.0; + } + else + { + alpha = te->getColor().mV[3]; + } + if (alpha > 0.f || te->getGlow() > 0.f) + { //only treat as alpha in the pipeline if < 100% transparent + drawablep->setState(LLDrawable::HAS_ALPHA); + add_face(sAlphaFaces, alpha_count, facep); + } + else if (LLDrawPoolAlpha::sShowDebugAlpha || + (gPipeline.sRenderHighlight && !drawablep->getParent() && + //only root objects are highlighted with red color in this case + drawablep->getVObj() && drawablep->getVObj()->flagScripted() && + (LLPipeline::getRenderScriptedBeacons() || + (LLPipeline::getRenderScriptedTouchBeacons() && drawablep->getVObj()->flagHandleTouch())))) + { //draw the transparent face for debugging purposes using a custom texture + add_face(sAlphaFaces, alpha_count, facep); + } + } + } + else + { + if (drawablep->isState(LLDrawable::REBUILD_VOLUME)) + { + facep->mLastUpdateTime = gFrameTimeSeconds; + } + + { + LLGLTFMaterial* gltf_mat = te->getGLTFRenderMaterial(); + + if (gltf_mat != nullptr || (te->getMaterialParams().notNull())) + { + if (gltf_mat != nullptr) + { + add_face(sPbrFaces, pbr_count, facep); + } + else + { + LLMaterial* mat = te->getMaterialParams().get(); + if (mat->getNormalID().notNull() || // <-- has a normal map, needs tangents + (te->getBumpmap() && (te->getBumpmap() < 18))) // <-- has an emboss bump map, needs tangents + { + if (mat->getSpecularID().notNull()) + { //has normal and specular maps (needs texcoord1, texcoord2, and tangent) + add_face(sNormSpecFaces, normspec_count, facep); + } + else + { //has normal map (needs texcoord1 and tangent) + add_face(sNormFaces, norm_count, facep); + } + } + else if (mat->getSpecularID().notNull()) + { //has specular map but no normal map, needs texcoord2 + add_face(sSpecFaces, spec_count, facep); + } + else + { //has neither specular map nor normal map, only needs texcoord0 + add_face(sSimpleFaces, simple_count, facep); + } + } + } + else if (te->getBumpmap()) + { //needs normal + tangent + add_face(sBumpFaces, bump_count, facep); + } + else if (te->getShiny() || !te->getFullbright()) + { //needs normal + add_face(sSimpleFaces, simple_count, facep); + } + else + { //doesn't need normal + facep->setState(LLFace::FULLBRIGHT); + add_face(sFullbrightFaces, fullbright_count, facep); + } + } + } + } + else + { //face has no renderable geometry + facep->clearVertexBuffer(); + } + } + + if (any_rigged_face) + { + if (!drawablep->isState(LLDrawable::RIGGED)) + { + drawablep->setState(LLDrawable::RIGGED); + LLDrawable* root = drawablep->getRoot(); + if (root != drawablep) + { + root->setState(LLDrawable::RIGGED_CHILD); + } + + //first time this is drawable is being marked as rigged, + // do another LoD update to use avatar bounding box + vobj->updateLOD(); + } + } + else + { + drawablep->clearState(LLDrawable::RIGGED); + vobj->updateRiggedVolume(false); + } + } + } + + //PROCESS NON-ALPHA FACES + U32 simple_mask = LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_COLOR; + U32 alpha_mask = simple_mask | 0x80000000; //hack to give alpha verts their own VBO + U32 bump_mask = LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_COLOR; + U32 fullbright_mask = LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_COLOR; + + U32 norm_mask = simple_mask | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TANGENT; + U32 normspec_mask = norm_mask | LLVertexBuffer::MAP_TEXCOORD2; + U32 spec_mask = simple_mask | LLVertexBuffer::MAP_TEXCOORD2; + + U32 pbr_mask = LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_NORMAL | LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_COLOR | LLVertexBuffer::MAP_TANGENT; + + if (emissive) + { //emissive faces are present, include emissive byte to preserve batching + simple_mask = simple_mask | LLVertexBuffer::MAP_EMISSIVE; + alpha_mask = alpha_mask | LLVertexBuffer::MAP_EMISSIVE; + bump_mask = bump_mask | LLVertexBuffer::MAP_EMISSIVE; + fullbright_mask = fullbright_mask | LLVertexBuffer::MAP_EMISSIVE; + norm_mask = norm_mask | LLVertexBuffer::MAP_EMISSIVE; + normspec_mask = normspec_mask | LLVertexBuffer::MAP_EMISSIVE; + spec_mask = spec_mask | LLVertexBuffer::MAP_EMISSIVE; + pbr_mask = pbr_mask | LLVertexBuffer::MAP_EMISSIVE; + } + + bool batch_textures = LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_OBJECT) > 1; + + // add extra vertex data for deferred rendering (not necessarily for batching textures) + if (batch_textures) + { + bump_mask = bump_mask | LLVertexBuffer::MAP_TANGENT; + simple_mask = simple_mask | LLVertexBuffer::MAP_TEXTURE_INDEX; + alpha_mask = alpha_mask | LLVertexBuffer::MAP_TEXTURE_INDEX | LLVertexBuffer::MAP_TANGENT | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TEXCOORD2; + fullbright_mask = fullbright_mask | LLVertexBuffer::MAP_TEXTURE_INDEX; + } + + group->mGeometryBytes = 0; + + U32 geometryBytes = 0; + + // generate render batches for static geometry + U32 extra_mask = LLVertexBuffer::MAP_TEXTURE_INDEX; + bool alpha_sort = true; + bool rigged = false; + for (int i = 0; i < 2; ++i) //two sets, static and rigged) + { + geometryBytes += genDrawInfo(group, simple_mask | extra_mask, sSimpleFaces[i], simple_count[i], false, batch_textures, rigged); + geometryBytes += genDrawInfo(group, fullbright_mask | extra_mask, sFullbrightFaces[i], fullbright_count[i], false, batch_textures, rigged); + geometryBytes += genDrawInfo(group, alpha_mask | extra_mask, sAlphaFaces[i], alpha_count[i], alpha_sort, batch_textures, rigged); + geometryBytes += genDrawInfo(group, bump_mask | extra_mask, sBumpFaces[i], bump_count[i], false, false, rigged); + geometryBytes += genDrawInfo(group, norm_mask | extra_mask, sNormFaces[i], norm_count[i], false, false, rigged); + geometryBytes += genDrawInfo(group, spec_mask | extra_mask, sSpecFaces[i], spec_count[i], false, false, rigged); + geometryBytes += genDrawInfo(group, normspec_mask | extra_mask, sNormSpecFaces[i], normspec_count[i], false, false, rigged); + geometryBytes += genDrawInfo(group, pbr_mask | extra_mask, sPbrFaces[i], pbr_count[i], false, false, rigged); + + // for rigged set, add weights and disable alpha sorting (rigged items use depth buffer) + extra_mask |= LLVertexBuffer::MAP_WEIGHT4; + rigged = true; + } + + group->mGeometryBytes = geometryBytes; + + { + //drawables have been rebuilt, clear rebuild status + for (LLSpatialGroup::element_iter drawable_iter = group->getDataBegin(); drawable_iter != group->getDataEnd(); ++drawable_iter) + { + LLDrawable* drawablep = (LLDrawable*)(*drawable_iter)->getDrawable(); + if(drawablep) + { + drawablep->clearState(LLDrawable::REBUILD_ALL); + } + } + } + + group->mLastUpdateTime = gFrameTimeSeconds; + group->mBuilt = 1.f; + group->clearState(LLSpatialGroup::GEOM_DIRTY | LLSpatialGroup::ALPHA_DIRTY); +} + +void LLVolumeGeometryManager::rebuildMesh(LLSpatialGroup* group) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + llassert(group); + if (group && group->hasState(LLSpatialGroup::MESH_DIRTY) && !group->hasState(LLSpatialGroup::GEOM_DIRTY)) + { + { + LL_PROFILE_ZONE_NAMED("rebuildMesh - gen draw info"); + + group->mBuilt = 1.f; + + const U32 MAX_BUFFER_COUNT = 4096; + LLVertexBuffer* locked_buffer[MAX_BUFFER_COUNT]; + + U32 buffer_count = 0; + + for (LLSpatialGroup::element_iter drawable_iter = group->getDataBegin(); drawable_iter != group->getDataEnd(); ++drawable_iter) + { + LLDrawable* drawablep = (LLDrawable*)(*drawable_iter)->getDrawable(); + + if (drawablep && !drawablep->isDead() && drawablep->isState(LLDrawable::REBUILD_ALL)) + { + LLVOVolume* vobj = drawablep->getVOVolume(); + + if (!vobj) continue; + + if (vobj->isNoLOD()) continue; + + vobj->preRebuild(); + + if (drawablep->isState(LLDrawable::ANIMATED_CHILD)) + { + vobj->updateRelativeXform(true); + } + + LLVolume* volume = vobj->getVolume(); + if (!volume) continue; + for (S32 i = 0; i < drawablep->getNumFaces(); ++i) + { + LLFace* face = drawablep->getFace(i); + if (face) + { + LLVertexBuffer* buff = face->getVertexBuffer(); + if (buff) + { + if (!face->getGeometryVolume(*volume, // volume + face->getTEOffset(), // face_index + vobj->getRelativeXform(), // mat_vert_in + vobj->getRelativeXformInvTrans(), // mat_norm_in + face->getGeomIndex(), // index_offset + false, // force_rebuild + true)) // no_debug_assert + { // Something's gone wrong with the vertex buffer accounting, + // rebuild this group with no debug assert because MESH_DIRTY + group->dirtyGeom(); + gPipeline.markRebuild(group); + } + + buff->unmapBuffer(); + } + } + } + + if (drawablep->isState(LLDrawable::ANIMATED_CHILD)) + { + vobj->updateRelativeXform(); + } + + drawablep->clearState(LLDrawable::REBUILD_ALL); + } + } + + { + LL_PROFILE_ZONE_NAMED("rebuildMesh - flush"); + for (LLVertexBuffer** iter = locked_buffer, ** end_iter = locked_buffer+buffer_count; iter != end_iter; ++iter) + { + (*iter)->unmapBuffer(); + } + + // don't forget alpha + if(group != NULL && + !group->mVertexBuffer.isNull()) + { + group->mVertexBuffer->unmapBuffer(); + } + } + + group->clearState(LLSpatialGroup::MESH_DIRTY | LLSpatialGroup::NEW_DRAWINFO); + } + } +} + +struct CompareBatchBreaker +{ + bool operator()(const LLFace* const& lhs, const LLFace* const& rhs) + { + const LLTextureEntry* lte = lhs->getTextureEntry(); + const LLTextureEntry* rte = rhs->getTextureEntry(); + + if (lte->getBumpmap() != rte->getBumpmap()) + { + return lte->getBumpmap() < rte->getBumpmap(); + } + else if (lte->getFullbright() != rte->getFullbright()) + { + return lte->getFullbright() < rte->getFullbright(); + } + else if (lte->getMaterialID() != rte->getMaterialID()) + { + return lte->getMaterialID() < rte->getMaterialID(); + } + else if (lte->getShiny() != rte->getShiny()) + { + return lte->getShiny() < rte->getShiny(); + } + else if (lhs->getTexture() != rhs->getTexture()) + { + return lhs->getTexture() < rhs->getTexture(); + } + else + { + // all else being equal, maintain consistent draw order + return lhs->getDrawOrderIndex() < rhs->getDrawOrderIndex(); + } + } +}; + +struct CompareBatchBreakerRigged +{ + bool operator()(const LLFace* const& lhs, const LLFace* const& rhs) + { + if (lhs->mAvatar != rhs->mAvatar) + { + return lhs->mAvatar < rhs->mAvatar; + } + else if (lhs->mSkinInfo->mHash != rhs->mSkinInfo->mHash) + { + return lhs->mSkinInfo->mHash < rhs->mSkinInfo->mHash; + } + else + { + // "inherit" non-rigged behavior + CompareBatchBreaker comp; + return comp(lhs, rhs); + } + } +}; + +U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace** faces, U32 face_count, bool distance_sort, bool batch_textures, bool rigged) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + + U32 geometryBytes = 0; + + //calculate maximum number of vertices to store in a single buffer + static LLCachedControl max_vbo_size(gSavedSettings, "RenderMaxVBOSize", 512); + U32 max_vertices = (max_vbo_size * 1024)/LLVertexBuffer::calcVertexSize(group->getSpatialPartition()->mVertexDataMask); + max_vertices = llmin(max_vertices, (U32) 65535); + + { + LL_PROFILE_ZONE_NAMED("genDrawInfo - sort"); + + if (rigged) + { + if (!distance_sort) // <--- alpha "sort" rigged faces by maintaining original draw order + { + //sort faces by things that break batches, including avatar and mesh id + std::sort(faces, faces + face_count, CompareBatchBreakerRigged()); + } + } + else if (!distance_sort) + { + //sort faces by things that break batches, not including avatar and mesh id + std::sort(faces, faces + face_count, CompareBatchBreaker()); + } + else + { + //sort faces by distance + std::sort(faces, faces+face_count, LLFace::CompareDistanceGreater()); + } + } + + bool hud_group = group->isHUDGroup() ; + LLFace** face_iter = faces; + LLFace** end_faces = faces+face_count; + + LLSpatialGroup::buffer_map_t buffer_map; + + LLViewerTexture* last_tex = NULL; + + S32 texture_index_channels = 1; + + if (gGLManager.mGLSLVersionMajor > 1 || gGLManager.mGLSLVersionMinor >= 30) + { + texture_index_channels = LLGLSLShader::sIndexedTextureChannels-1; //always reserve one for shiny for now just for simplicity; + } + + if (distance_sort) + { + texture_index_channels = gDeferredAlphaProgram.mFeatures.mIndexedTextureChannels; + } + + texture_index_channels = LLGLSLShader::sIndexedTextureChannels; + + bool flexi = false; + + while (face_iter != end_faces) + { + //pull off next face + LLFace* facep = *face_iter; + LLViewerTexture* tex = facep->getTexture(); + const LLTextureEntry* te = facep->getTextureEntry(); + LLMaterialPtr mat = te->getMaterialParams(); + LLMaterialID matId = te->getMaterialID(); + + if (distance_sort) + { + tex = NULL; + } + + if (last_tex != tex) + { + last_tex = tex; + } + + bool bake_sunlight = LLPipeline::sBakeSunlight && facep->getDrawable()->isStatic(); + + U32 index_count = facep->getIndicesCount(); + U32 geom_count = facep->getGeomCount(); + + flexi = flexi || facep->getViewerObject()->getVolume()->isUnique(); + + //sum up vertices needed for this render batch + LLFace** i = face_iter; + ++i; + + const U32 MAX_TEXTURE_COUNT = 32; + LLViewerTexture* texture_list[MAX_TEXTURE_COUNT]; + + U32 texture_count = 0; + + { + LL_PROFILE_ZONE_NAMED("genDrawInfo - face size"); + if (batch_textures) + { + U8 cur_tex = 0; + facep->setTextureIndex(cur_tex); + if (texture_count < MAX_TEXTURE_COUNT) + { + texture_list[texture_count++] = tex; + } + + if (can_batch_texture(facep)) + { //populate texture_list with any textures that can be batched + //move i to the next unbatchable face + while (i != end_faces) + { + facep = *i; + + if (!can_batch_texture(facep)) + { //face is bump mapped or has an animated texture matrix -- can't + //batch more than 1 texture at a time + facep->setTextureIndex(0); + break; + } + + if (facep->getTexture() != tex) + { + if (distance_sort) + { //textures might be out of order, see if texture exists in current batch + bool found = false; + for (U32 tex_idx = 0; tex_idx < texture_count; ++tex_idx) + { + if (facep->getTexture() == texture_list[tex_idx]) + { + cur_tex = tex_idx; + found = true; + break; + } + } + + if (!found) + { + cur_tex = texture_count; + } + } + else + { + cur_tex++; + } + + if (cur_tex >= texture_index_channels) + { //cut batches when index channels are depleted + break; + } + + tex = facep->getTexture(); + + if (texture_count < MAX_TEXTURE_COUNT) + { + texture_list[texture_count++] = tex; + } + } + + if (geom_count + facep->getGeomCount() > max_vertices) + { //cut batches on geom count too big + break; + } + + ++i; + + flexi = flexi || facep->getViewerObject()->getVolume()->isUnique(); + + index_count += facep->getIndicesCount(); + geom_count += facep->getGeomCount(); + + facep->setTextureIndex(cur_tex); + } + } + else + { + facep->setTextureIndex(0); + } + + tex = texture_list[0]; + } + else + { + while (i != end_faces && + (LLPipeline::sTextureBindTest || + (distance_sort || + ((*i)->getTexture() == tex)))) + { + facep = *i; + const LLTextureEntry* nextTe = facep->getTextureEntry(); + if (nextTe->getMaterialID() != matId) + { + break; + } + + //face has no texture index + facep->mDrawInfo = NULL; + facep->setTextureIndex(FACE_DO_NOT_BATCH_TEXTURES); + + if (geom_count + facep->getGeomCount() > max_vertices) + { //cut batches on geom count too big + break; + } + + ++i; + index_count += facep->getIndicesCount(); + geom_count += facep->getGeomCount(); + + flexi = flexi || facep->getViewerObject()->getVolume()->isUnique(); + } + } + } + + //create vertex buffer + LLPointer buffer; + + { + LL_PROFILE_ZONE_NAMED("genDrawInfo - allocate"); + buffer = new LLVertexBuffer(mask); + if(!buffer->allocateBuffer(geom_count, index_count)) + { + LL_WARNS() << "Failed to allocate group Vertex Buffer to " + << geom_count << " vertices and " + << index_count << " indices" << LL_ENDL; + buffer = NULL; + } + } + + if (buffer) + { + geometryBytes += buffer->getSize() + buffer->getIndicesSize(); + buffer_map[mask][*face_iter].push_back(buffer); + } + + //add face geometry + + U32 indices_index = 0; + U16 index_offset = 0; + + while (face_iter < i) + { + //update face indices for new buffer + facep = *face_iter; + + if (buffer.isNull()) + { + // Bulk allocation failed + facep->setVertexBuffer(buffer); + facep->setSize(0, 0); // mark as no geometry + ++face_iter; + continue; + } + facep->setIndicesIndex(indices_index); + facep->setGeomIndex(index_offset); + facep->setVertexBuffer(buffer); + + if (batch_textures && facep->getTextureIndex() == FACE_DO_NOT_BATCH_TEXTURES) + { + LL_ERRS() << "Invalid texture index." << LL_ENDL; + } + + { + //for debugging, set last time face was updated vs moved + facep->updateRebuildFlags(); + + { //copy face geometry into vertex buffer + LLDrawable* drawablep = facep->getDrawable(); + LLVOVolume* vobj = drawablep->getVOVolume(); + LLVolume* volume = vobj->getVolume(); + + if (drawablep->isState(LLDrawable::ANIMATED_CHILD)) + { + vobj->updateRelativeXform(true); + } + + U32 te_idx = facep->getTEOffset(); + + if (!facep->getGeometryVolume(*volume, te_idx, + vobj->getRelativeXform(), vobj->getRelativeXformInvTrans(), index_offset,true)) + { + LL_WARNS() << "Failed to get geometry for face!" << LL_ENDL; + } + + if (drawablep->isState(LLDrawable::ANIMATED_CHILD)) + { + vobj->updateRelativeXform(false); + } + } + } + + index_offset += facep->getGeomCount(); + indices_index += facep->getIndicesCount(); + + //append face to appropriate render batch + + bool force_simple = facep->getPixelArea() < FORCE_SIMPLE_RENDER_AREA; + bool fullbright = facep->isState(LLFace::FULLBRIGHT); + if ((mask & LLVertexBuffer::MAP_NORMAL) == 0) + { //paranoia check to make sure GL doesn't try to read non-existant normals + fullbright = true; + } + + const LLTextureEntry* te = facep->getTextureEntry(); + LLGLTFMaterial* gltf_mat = te->getGLTFRenderMaterial(); + + if (hud_group && gltf_mat == nullptr) + { //all hud attachments are fullbright + fullbright = true; + } + + tex = facep->getTexture(); + + bool is_alpha = facep->getPoolType() == LLDrawPool::POOL_ALPHA; + + LLMaterial* mat = nullptr; + bool can_be_shiny = false; + + // ignore traditional material if GLTF material is present + if (gltf_mat == nullptr) + { + mat = te->getMaterialParams().get(); + + can_be_shiny = true; + if (mat) + { + U8 mode = mat->getDiffuseAlphaMode(); + can_be_shiny = mode == LLMaterial::DIFFUSE_ALPHA_MODE_NONE || + mode == LLMaterial::DIFFUSE_ALPHA_MODE_EMISSIVE; + } + } + + F32 blinn_phong_alpha = te->getColor().mV[3]; + bool use_legacy_bump = te->getBumpmap() && (te->getBumpmap() < 18) && (!mat || mat->getNormalID().isNull()); + bool blinn_phong_opaque = blinn_phong_alpha >= 0.999f; + bool blinn_phong_transparent = blinn_phong_alpha < 0.999f; + + if (!gltf_mat) + { + is_alpha |= blinn_phong_transparent; + } + + if (gltf_mat || (mat && !hud_group)) + { + bool material_pass = false; + + if (gltf_mat) + { // all other parameters ignored if gltf material is present + if (gltf_mat->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_BLEND) + { + registerFace(group, facep, LLRenderPass::PASS_ALPHA); + } + else if (gltf_mat->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_MASK) + { + registerFace(group, facep, LLRenderPass::PASS_GLTF_PBR_ALPHA_MASK); + } + else + { + registerFace(group, facep, LLRenderPass::PASS_GLTF_PBR); + } + } + else + // do NOT use 'fullbright' for this logic or you risk sending + // things without normals down the materials pipeline and will + // render poorly if not crash NORSPEC-240,314 + // + if (te->getFullbright()) + { + if (mat->getDiffuseAlphaMode() == LLMaterial::DIFFUSE_ALPHA_MODE_MASK) + { + if (blinn_phong_opaque) + { + registerFace(group, facep, LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK); + } + else + { + registerFace(group, facep, LLRenderPass::PASS_ALPHA); + } + } + else if (is_alpha) + { + registerFace(group, facep, LLRenderPass::PASS_ALPHA); + } + else + { + if (mat->getEnvironmentIntensity() > 0 || te->getShiny() > 0) + { + material_pass = true; + } + else + { + if (blinn_phong_opaque) + { + registerFace(group, facep, LLRenderPass::PASS_FULLBRIGHT); + } + else + { + registerFace(group, facep, LLRenderPass::PASS_ALPHA); + } + } + } + } + else if (blinn_phong_transparent) + { + registerFace(group, facep, LLRenderPass::PASS_ALPHA); + } + else if (use_legacy_bump) + { + llassert(mask & LLVertexBuffer::MAP_TANGENT); + // we have a material AND legacy bump settings, but no normal map + registerFace(group, facep, LLRenderPass::PASS_BUMP); + } + else + { + material_pass = true; + } + + if (material_pass) + { + static const U32 pass[] = + { + LLRenderPass::PASS_MATERIAL, + LLRenderPass::PASS_ALPHA, //LLRenderPass::PASS_MATERIAL_ALPHA, + LLRenderPass::PASS_MATERIAL_ALPHA_MASK, + LLRenderPass::PASS_MATERIAL_ALPHA_EMISSIVE, + LLRenderPass::PASS_SPECMAP, + LLRenderPass::PASS_ALPHA, //LLRenderPass::PASS_SPECMAP_BLEND, + LLRenderPass::PASS_SPECMAP_MASK, + LLRenderPass::PASS_SPECMAP_EMISSIVE, + LLRenderPass::PASS_NORMMAP, + LLRenderPass::PASS_ALPHA, //LLRenderPass::PASS_NORMMAP_BLEND, + LLRenderPass::PASS_NORMMAP_MASK, + LLRenderPass::PASS_NORMMAP_EMISSIVE, + LLRenderPass::PASS_NORMSPEC, + LLRenderPass::PASS_ALPHA, //LLRenderPass::PASS_NORMSPEC_BLEND, + LLRenderPass::PASS_NORMSPEC_MASK, + LLRenderPass::PASS_NORMSPEC_EMISSIVE, + }; + + U32 alpha_mode = mat->getDiffuseAlphaMode(); + if (!distance_sort && alpha_mode == LLMaterial::DIFFUSE_ALPHA_MODE_BLEND) + { // HACK - this should never happen, but sometimes we get a material that thinks it has alpha blending when it ought not + alpha_mode = LLMaterial::DIFFUSE_ALPHA_MODE_NONE; + } + U32 mask = mat->getShaderMask(alpha_mode, is_alpha); + + U32 vb_mask = facep->getVertexBuffer()->getTypeMask(); + + // HACK - this should also never happen, but sometimes we get here and the material thinks it has a specmap now + // even though it didn't appear to have a specmap when the face was added to the list of faces + if ((mask & 0x4) && !(vb_mask & LLVertexBuffer::MAP_TEXCOORD2)) + { + mask &= ~0x4; + } + + llassert(mask < sizeof(pass)/sizeof(U32)); + + mask = llmin(mask, (U32)(sizeof(pass)/sizeof(U32)-1)); + + // if this is going into alpha pool, distance sort MUST be true + llassert(pass[mask] == LLRenderPass::PASS_ALPHA ? distance_sort : true); + registerFace(group, facep, pass[mask]); + } + } + else if (mat) + { + U8 mode = mat->getDiffuseAlphaMode(); + + is_alpha = (is_alpha || (mode == LLMaterial::DIFFUSE_ALPHA_MODE_BLEND)); + + if (is_alpha) + { + mode = LLMaterial::DIFFUSE_ALPHA_MODE_BLEND; + } + + if (mode == LLMaterial::DIFFUSE_ALPHA_MODE_MASK) + { + registerFace(group, facep, fullbright ? LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK : LLRenderPass::PASS_ALPHA_MASK); + } + else if (is_alpha ) + { + registerFace(group, facep, LLRenderPass::PASS_ALPHA); + } + else if (gPipeline.shadersLoaded() + && te->getShiny() + && can_be_shiny) + { + registerFace(group, facep, fullbright ? LLRenderPass::PASS_FULLBRIGHT_SHINY : LLRenderPass::PASS_SHINY); + } + else + { + registerFace(group, facep, fullbright ? LLRenderPass::PASS_FULLBRIGHT : LLRenderPass::PASS_SIMPLE); + } + } + else if (is_alpha) + { + // can we safely treat this as an alpha mask? + if (facep->getFaceColor().mV[3] <= 0.f) + { //100% transparent, don't render unless we're highlighting transparent + registerFace(group, facep, LLRenderPass::PASS_ALPHA_INVISIBLE); + } + else if (facep->canRenderAsMask() && !hud_group) + { + if (te->getFullbright() || LLPipeline::sNoAlpha) + { + registerFace(group, facep, LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK); + } + else + { + registerFace(group, facep, LLRenderPass::PASS_ALPHA_MASK); + } + } + else + { + registerFace(group, facep, LLRenderPass::PASS_ALPHA); + } + } + else if (gPipeline.shadersLoaded() + && te->getShiny() + && can_be_shiny) + { //shiny + if (tex->getPrimaryFormat() == GL_ALPHA) + { //invisiprim+shiny + registerFace(group, facep, LLRenderPass::PASS_INVISI_SHINY); + registerFace(group, facep, LLRenderPass::PASS_INVISIBLE); + } + else if (!hud_group) + { //deferred rendering + if (te->getFullbright()) + { //register in post deferred fullbright shiny pass + registerFace(group, facep, LLRenderPass::PASS_FULLBRIGHT_SHINY); + if (te->getBumpmap()) + { //register in post deferred bump pass + registerFace(group, facep, LLRenderPass::PASS_POST_BUMP); + } + } + else if (use_legacy_bump) + { //register in deferred bump pass + llassert(mask& LLVertexBuffer::MAP_TANGENT); + registerFace(group, facep, LLRenderPass::PASS_BUMP); + } + else + { //register in deferred simple pass (deferred simple includes shiny) + llassert(mask & LLVertexBuffer::MAP_NORMAL); + registerFace(group, facep, LLRenderPass::PASS_SIMPLE); + } + } + else if (fullbright) + { //not deferred, register in standard fullbright shiny pass + registerFace(group, facep, LLRenderPass::PASS_FULLBRIGHT_SHINY); + } + else + { //not deferred or fullbright, register in standard shiny pass + registerFace(group, facep, LLRenderPass::PASS_SHINY); + } + } + else + { //not alpha and not shiny + if (!is_alpha && tex->getPrimaryFormat() == GL_ALPHA) + { //invisiprim + registerFace(group, facep, LLRenderPass::PASS_INVISIBLE); + } + else if (fullbright || bake_sunlight) + { //fullbright + if (mat && mat->getDiffuseAlphaMode() == LLMaterial::DIFFUSE_ALPHA_MODE_MASK) + { + registerFace(group, facep, LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK); + } + else + { + registerFace(group, facep, LLRenderPass::PASS_FULLBRIGHT); + } + if (!hud_group && use_legacy_bump) + { //if this is the deferred render and a bump map is present, register in post deferred bump + registerFace(group, facep, LLRenderPass::PASS_POST_BUMP); + } + } + else + { + if (use_legacy_bump) + { //non-shiny or fullbright deferred bump + llassert(mask& LLVertexBuffer::MAP_TANGENT); + registerFace(group, facep, LLRenderPass::PASS_BUMP); + } + else + { //all around simple + llassert(mask & LLVertexBuffer::MAP_NORMAL); + if (mat && mat->getDiffuseAlphaMode() == LLMaterial::DIFFUSE_ALPHA_MODE_MASK) + { //material alpha mask can be respected in non-deferred + registerFace(group, facep, LLRenderPass::PASS_ALPHA_MASK); + } + else + { + registerFace(group, facep, LLRenderPass::PASS_SIMPLE); + } + } + } + + + if (!gPipeline.shadersLoaded() && + !is_alpha && + te->getShiny()) + { //shiny as an extra pass when shaders are disabled + registerFace(group, facep, LLRenderPass::PASS_SHINY); + } + } + + //not sure why this is here, and looks like it might cause bump mapped objects to get rendered redundantly -- davep 5/11/2010 + if (!is_alpha && hud_group) + { + llassert((mask & LLVertexBuffer::MAP_NORMAL) || fullbright); + facep->setPoolType((fullbright) ? LLDrawPool::POOL_FULLBRIGHT : LLDrawPool::POOL_SIMPLE); + + if (!force_simple && use_legacy_bump) + { + llassert(mask & LLVertexBuffer::MAP_TANGENT); + registerFace(group, facep, LLRenderPass::PASS_BUMP); + } + } + + if (!is_alpha && LLPipeline::sRenderGlow && te->getGlow() > 0.f) + { + if (gltf_mat) + { + registerFace(group, facep, LLRenderPass::PASS_GLTF_GLOW); + } + else + { + registerFace(group, facep, LLRenderPass::PASS_GLOW); + } + } + + ++face_iter; + } + + if (buffer) + { + buffer->unmapBuffer(); + } + } + + group->mBufferMap[mask].clear(); + for (LLSpatialGroup::buffer_texture_map_t::iterator i = buffer_map[mask].begin(); i != buffer_map[mask].end(); ++i) + { + group->mBufferMap[mask][i->first] = i->second; + } + + return geometryBytes; +} + +void LLVolumeGeometryManager::addGeometryCount(LLSpatialGroup* group, U32& vertex_count, U32& index_count) +{ + //for each drawable + for (LLSpatialGroup::element_iter drawable_iter = group->getDataBegin(); drawable_iter != group->getDataEnd(); ++drawable_iter) + { + LLDrawable* drawablep = (LLDrawable*)(*drawable_iter)->getDrawable(); + + if (!drawablep || drawablep->isDead()) + { + continue; + } + } +} + +void LLGeometryManager::addGeometryCount(LLSpatialGroup* group, U32 &vertex_count, U32 &index_count) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + + //clear off any old faces + mFaceList.clear(); + + //for each drawable + for (LLSpatialGroup::element_iter drawable_iter = group->getDataBegin(); drawable_iter != group->getDataEnd(); ++drawable_iter) + { + LLDrawable* drawablep = (LLDrawable*)(*drawable_iter)->getDrawable(); + + if (!drawablep || drawablep->isDead()) + { + continue; + } + + //for each face + for (S32 i = 0; i < drawablep->getNumFaces(); i++) + { + //sum up face verts and indices + drawablep->updateFaceSize(i); + LLFace* facep = drawablep->getFace(i); + if (facep) + { + if (facep->hasGeometry() && facep->getPixelArea() > FORCE_CULL_AREA && + facep->getGeomCount() + vertex_count <= 65536) + { + vertex_count += facep->getGeomCount(); + index_count += facep->getIndicesCount(); + + //remember face (for sorting) + mFaceList.push_back(facep); + } + else + { + facep->clearVertexBuffer(); + } + } + } + } +} + +LLHUDPartition::LLHUDPartition(LLViewerRegion* regionp) : LLBridgePartition(regionp) +{ + mPartitionType = LLViewerRegion::PARTITION_HUD; + mDrawableType = LLPipeline::RENDER_TYPE_HUD; + mSlopRatio = 0.f; + mLODPeriod = 1; +} + +void LLHUDPartition::shift(const LLVector4a &offset) +{ + //HUD objects don't shift with region crossing. That would be silly. +} diff --git a/indra/newview/llvovolume.h b/indra/newview/llvovolume.h index 7bc71627b8..83057573eb 100644 --- a/indra/newview/llvovolume.h +++ b/indra/newview/llvovolume.h @@ -1,501 +1,501 @@ -/** - * @file llvovolume.h - * @brief LLVOVolume class header file - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLVOVOLUME_H -#define LL_LLVOVOLUME_H - -#include "llviewerobject.h" -#include "llviewertexture.h" -#include "llviewermedia.h" -#include "llframetimer.h" -#include "lllocalbitmaps.h" -#include "m3math.h" // LLMatrix3 -#include "m4math.h" // LLMatrix4 -#include -#include - - -class LLViewerTextureAnim; -class LLDrawPool; -class LLMaterialID; -class LLSelectNode; -class LLObjectMediaDataClient; -class LLObjectMediaNavigateClient; -class LLVOAvatar; -class LLMeshSkinInfo; - -typedef std::vector media_list_t; - -enum LLVolumeInterfaceType -{ - INTERFACE_FLEXIBLE = 1, -}; - -const F32 MAX_LOD_FACTOR = 4.0f; - - -class LLRiggedVolume : public LLVolume -{ -public: - LLRiggedVolume(const LLVolumeParams& params) - : LLVolume(params, 0.f) - { - } - - using FaceIndex = S32; - static const FaceIndex UPDATE_ALL_FACES = -1; - static const FaceIndex DO_NOT_UPDATE_FACES = -2; - void update( - const LLMeshSkinInfo* skin, - LLVOAvatar* avatar, - const LLVolume* src_volume, - FaceIndex face_index = UPDATE_ALL_FACES, - bool rebuild_face_octrees = true); - - std::string mExtraDebugText; -}; - -// Base class for implementations of the volume - Primitive, Flexible Object, etc. -class LLVolumeInterface -{ -public: - virtual ~LLVolumeInterface() { } - virtual LLVolumeInterfaceType getInterfaceType() const = 0; - virtual void doIdleUpdate() = 0; - virtual bool doUpdateGeometry(LLDrawable *drawable) = 0; - virtual LLVector3 getPivotPosition() const = 0; - virtual void onSetVolume(const LLVolumeParams &volume_params, const S32 detail) = 0; - virtual void onSetScale(const LLVector3 &scale, bool damped) = 0; - virtual void onParameterChanged(U16 param_type, LLNetworkData *data, bool in_use, bool local_origin) = 0; - virtual void onShift(const LLVector4a &shift_vector) = 0; - virtual bool isVolumeUnique() const = 0; // Do we need a unique LLVolume instance? - virtual bool isVolumeGlobal() const = 0; // Are we in global space? - virtual bool isActive() const = 0; // Is this object currently active? - virtual const LLMatrix4& getWorldMatrix(LLXformMatrix* xform) const = 0; - virtual void updateRelativeXform(bool force_identity = false) = 0; - virtual U32 getID() const = 0; - virtual void preRebuild() = 0; -}; - -// Class which embodies all Volume objects (with pcode LL_PCODE_VOLUME) -class LLVOVolume : public LLViewerObject -{ - LOG_CLASS(LLVOVolume); -protected: - virtual ~LLVOVolume(); - -public: - static void initClass(); - static void cleanupClass(); - static void preUpdateGeom(); - - enum - { - VERTEX_DATA_MASK = (1 << LLVertexBuffer::TYPE_VERTEX) | - (1 << LLVertexBuffer::TYPE_NORMAL) | - (1 << LLVertexBuffer::TYPE_TEXCOORD0) | - (1 << LLVertexBuffer::TYPE_TEXCOORD1) | - (1 << LLVertexBuffer::TYPE_COLOR) - }; - -public: - LLVOVolume(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); - void markDead() override; // Override (and call through to parent) to clean up media references - - LLDrawable* createDrawable(LLPipeline *pipeline) override; - - void deleteFaces(); - - void animateTextures(); - - bool isVisible() const ; - bool isActive() const override; - bool isAttachment() const override; - bool isRootEdit() const override; // overridden for sake of attachments treating themselves as a root object - bool isHUDAttachment() const override; - - void generateSilhouette(LLSelectNode* nodep, const LLVector3& view_point); - /*virtual*/ bool setParent(LLViewerObject* parent) override; - S32 getLOD() const override { return mLOD; } - void setNoLOD() { mLOD = NO_LOD; mLODChanged = true; } - bool isNoLOD() const { return NO_LOD == mLOD; } - const LLVector3 getPivotPositionAgent() const override; - const LLMatrix4& getRelativeXform() const { return mRelativeXform; } - const LLMatrix3& getRelativeXformInvTrans() const { return mRelativeXformInvTrans; } - /*virtual*/ const LLMatrix4 getRenderMatrix() const override; - typedef std::unordered_set texture_cost_t; - static S32 getTextureCost(const LLViewerTexture* img); - U32 getRenderCost(texture_cost_t &textures) const; - /*virtual*/ F32 getEstTrianglesMax() const override; - /*virtual*/ F32 getEstTrianglesStreamingCost() const override; - /* virtual*/ F32 getStreamingCost() const override; - /*virtual*/ bool getCostData(LLMeshCostData& costs) const override; - - /*virtual*/ U32 getTriangleCount(S32* vcount = NULL) const override; - /*virtual*/ U32 getHighLODTriangleCount() override; - /*virtual*/ bool lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, - S32 face = -1, // which face to check, -1 = ALL_SIDES - bool pick_transparent = false, - bool pick_rigged = false, - bool pick_unselectable = true, - S32* face_hit = NULL, // which face was hit - LLVector4a* intersection = NULL, // return the intersection point - LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point - LLVector4a* normal = NULL, // return the surface normal at the intersection point - LLVector4a* tangent = NULL // return the surface tangent at the intersection point - ) override; - - LLVector3 agentPositionToVolume(const LLVector3& pos) const; - LLVector3 agentDirectionToVolume(const LLVector3& dir) const; - LLVector3 volumePositionToAgent(const LLVector3& dir) const; - LLVector3 volumeDirectionToAgent(const LLVector3& dir) const; - - - bool getVolumeChanged() const { return mVolumeChanged; } - - F32 getVObjRadius() const override { return mVObjRadius; }; - const LLMatrix4& getWorldMatrix(LLXformMatrix* xform) const override; - - void markForUpdate() override; - void faceMappingChanged() override { mFaceMappingChanged=true; } - - /*virtual*/ void onShift(const LLVector4a &shift_vector) override; // Called when the drawable shifts - - /*virtual*/ void parameterChanged(U16 param_type, bool local_origin) override; - /*virtual*/ void parameterChanged(U16 param_type, LLNetworkData* data, bool in_use, bool local_origin) override; - - // update mReflectionProbe based on isReflectionProbe() - void updateReflectionProbePtr(); - - /*virtual*/ U32 processUpdateMessage(LLMessageSystem *mesgsys, - void **user_data, - U32 block_num, const EObjectUpdateType update_type, - LLDataPacker *dp) override; - - /*virtual*/ void setSelected(bool sel) override; - /*virtual*/ bool setDrawableParent(LLDrawable* parentp) override; - - /*virtual*/ void setScale(const LLVector3 &scale, bool damped) override; - - /*virtual*/ void changeTEImage(S32 index, LLViewerTexture* new_image) override; - /*virtual*/ void setNumTEs(const U8 num_tes) override; - /*virtual*/ void setTEImage(const U8 te, LLViewerTexture *imagep) override; - /*virtual*/ S32 setTETexture(const U8 te, const LLUUID &uuid) override; - /*virtual*/ S32 setTEColor(const U8 te, const LLColor3 &color) override; - /*virtual*/ S32 setTEColor(const U8 te, const LLColor4 &color) override; - /*virtual*/ S32 setTEBumpmap(const U8 te, const U8 bump) override; - /*virtual*/ S32 setTEShiny(const U8 te, const U8 shiny) override; - /*virtual*/ S32 setTEFullbright(const U8 te, const U8 fullbright) override; - /*virtual*/ S32 setTEBumpShinyFullbright(const U8 te, const U8 bump) override; - /*virtual*/ S32 setTEMediaFlags(const U8 te, const U8 media_flags) override; - /*virtual*/ S32 setTEGlow(const U8 te, const F32 glow) override; - /*virtual*/ S32 setTEMaterialID(const U8 te, const LLMaterialID& pMaterialID) override; - - static void setTEMaterialParamsCallbackTE(const LLUUID& objectID, const LLMaterialID& pMaterialID, const LLMaterialPtr pMaterialParams, U32 te); - - /*virtual*/ S32 setTEMaterialParams(const U8 te, const LLMaterialPtr pMaterialParams) override; - S32 setTEGLTFMaterialOverride(U8 te, LLGLTFMaterial* mat) override; - /*virtual*/ S32 setTEScale(const U8 te, const F32 s, const F32 t) override; - /*virtual*/ S32 setTEScaleS(const U8 te, const F32 s) override; - /*virtual*/ S32 setTEScaleT(const U8 te, const F32 t) override; - /*virtual*/ S32 setTETexGen(const U8 te, const U8 texgen) override; - /*virtual*/ S32 setTEMediaTexGen(const U8 te, const U8 media) override; - /*virtual*/ bool setMaterial(const U8 material) override; - - void setTexture(const S32 face); - S32 getIndexInTex(U32 ch) const {return mIndexInTex[ch];} - /*virtual*/ bool setVolume(const LLVolumeParams &volume_params, const S32 detail, bool unique_volume = false) override; - void updateSculptTexture(); - void setIndexInTex(U32 ch, S32 index) { mIndexInTex[ch] = index ;} - void sculpt(); - static void rebuildMeshAssetCallback(const LLUUID& asset_uuid, - LLAssetType::EType type, - void* user_data, S32 status, LLExtStat ext_status); - - void updateRelativeXform(bool force_identity = false); - /*virtual*/ bool updateGeometry(LLDrawable *drawable) override; - /*virtual*/ void updateFaceSize(S32 idx) override; - /*virtual*/ bool updateLOD() override; - void updateRadius() override; - /*virtual*/ void updateTextures() override; - void updateTextureVirtualSize(bool forced = false); - - void updateFaceFlags(); - void regenFaces(); - bool genBBoxes(bool force_global, bool should_update_octree_bounds = true); - void preRebuild(); - virtual void updateSpatialExtents(LLVector4a& min, LLVector4a& max) override; - virtual F32 getBinRadius() override; - - virtual U32 getPartitionType() const override; - - // For Lights - void setIsLight(bool is_light); - //set the gamma-corrected (sRGB) color of this light - void setLightSRGBColor(const LLColor3& color); - //set the linear color of this light - void setLightLinearColor(const LLColor3& color); - - void setLightIntensity(F32 intensity); - void setLightRadius(F32 radius); - void setLightFalloff(F32 falloff); - void setLightCutoff(F32 cutoff); - void setLightTextureID(LLUUID id); - void setSpotLightParams(LLVector3 params); - - bool getIsLight() const; - bool getIsLightFast() const; - - - // Get the light color in sRGB color space NOT scaled by intensity. - LLColor3 getLightSRGBBaseColor() const; - - // Get the light color in linear color space NOT scaled by intensity. - LLColor3 getLightLinearBaseColor() const; - - // Get the light color in linear color space scaled by intensity - // this is the value that should be fed into shaders - LLColor3 getLightLinearColor() const; - - // Get the light color in sRGB color space scaled by intensity. - LLColor3 getLightSRGBColor() const; - - LLUUID getLightTextureID() const; - bool isLightSpotlight() const; - LLVector3 getSpotLightParams() const; - void updateSpotLightPriority(); - F32 getSpotLightPriority() const; - - LLViewerTexture* getLightTexture(); - F32 getLightIntensity() const; - F32 getLightRadius() const; - F32 getLightFalloff(const F32 fudge_factor = 1.f) const; - F32 getLightCutoff() const; - - // Reflection Probes - bool setIsReflectionProbe(bool is_probe); - bool setReflectionProbeAmbiance(F32 ambiance); - bool setReflectionProbeNearClip(F32 near_clip); - bool setReflectionProbeIsBox(bool is_box); - bool setReflectionProbeIsDynamic(bool is_dynamic); - - bool isReflectionProbe() const override; - F32 getReflectionProbeAmbiance() const; - F32 getReflectionProbeNearClip() const; - bool getReflectionProbeIsBox() const; - bool getReflectionProbeIsDynamic() const; - - // Flexible Objects - U32 getVolumeInterfaceID() const; - virtual bool isFlexible() const override; - virtual bool isSculpted() const override; - virtual bool isMesh() const override; - virtual bool isRiggedMesh() const override; - virtual bool hasLightTexture() const override; - - // fast variants above that use state that is filled in later - // not reliable early in the life of an object, but should be used after - // object is loaded - bool isFlexibleFast() const; - bool isSculptedFast() const; - bool isMeshFast() const; - bool isRiggedMeshFast() const; - bool isAnimatedObjectFast() const; - - bool isVolumeGlobal() const; - bool canBeFlexible() const; - bool setIsFlexible(bool is_flexible); - - const LLMeshSkinInfo* getSkinInfo() const; - const bool isSkinInfoUnavaliable() const { return mSkinInfoUnavaliable; } - - //convenience accessor for mesh ID (which is stored in sculpt id for legacy reasons) - const LLUUID& getMeshID() const { return getVolume()->getParams().getSculptID(); } - - // Extended Mesh Properties - U32 getExtendedMeshFlags() const; - void onSetExtendedMeshFlags(U32 flags); - void setExtendedMeshFlags(U32 flags); - bool canBeAnimatedObject() const; - bool isAnimatedObject() const override; - virtual void onReparent(LLViewerObject *old_parent, LLViewerObject *new_parent) override; - virtual void afterReparent() override; - - //virtual - void updateRiggingInfo() override; - S32 mLastRiggingInfoLOD; - - // Functions that deal with media, or media navigation - - // Update this object's media data with the given media data array - // (typically this is only called upon a response from a server request) - void updateObjectMediaData(const LLSD &media_data_array, const std::string &media_version); - - // Bounce back media at the given index to its current URL (or home URL, if current URL is empty) - void mediaNavigateBounceBack(U8 texture_index); - - // Returns whether or not this object has permission to navigate or control - // the given media entry - enum MediaPermType { - MEDIA_PERM_INTERACT, MEDIA_PERM_CONTROL - }; - bool hasMediaPermission(const LLMediaEntry* media_entry, MediaPermType perm_type); - - void mediaNavigated(LLViewerMediaImpl *impl, LLPluginClassMedia* plugin, std::string new_location); - void mediaEvent(LLViewerMediaImpl *impl, LLPluginClassMedia* plugin, LLViewerMediaObserver::EMediaEvent event); - - - // Sync the given media data with the impl and the given te - void syncMediaData(S32 te, const LLSD &media_data, bool merge, bool ignore_agent); - - // Send media data update to the simulator. - void sendMediaDataUpdate(); - - viewer_media_t getMediaImpl(U8 face_id) const; - S32 getFaceIndexWithMediaImpl(const LLViewerMediaImpl* media_impl, S32 start_face_id); - F64 getTotalMediaInterest() const; - - bool hasMedia() const; - - LLVector3 getApproximateFaceNormal(U8 face_id); - - // Flag any corresponding avatars as needing update. - void updateVisualComplexity(); - - void notifyMeshLoaded(); - void notifySkinInfoLoaded(const LLMeshSkinInfo* skin); - void notifySkinInfoUnavailable(); - - // Returns 'true' iff the media data for this object is in flight - bool isMediaDataBeingFetched() const; - - // Returns the "last fetched" media version, or -1 if not fetched yet - S32 getLastFetchedMediaVersion() const { return mLastFetchedMediaVersion; } - - void addMDCImpl() { ++mMDCImplCount; } - void removeMDCImpl() { --mMDCImplCount; } - S32 getMDCImplCount() { return mMDCImplCount; } - - - // Rigged volume update (for raycasting) - // By default, this updates the bounding boxes of all the faces and builds an octree for precise per-triangle raycasting - void updateRiggedVolume( - bool force_treat_as_rigged, - LLRiggedVolume::FaceIndex face_index = LLRiggedVolume::UPDATE_ALL_FACES, - bool rebuild_face_octrees = true); - LLRiggedVolume* getRiggedVolume(); - - //returns true if volume should be treated as a rigged volume - // - Build tools are open - // - object is an attachment - // - object is attached to self - // - object is rendered as rigged - bool treatAsRigged(); - - //clear out rigged volume and revert back to non-rigged state for picking/LOD/distance updates - void clearRiggedVolume(); - -protected: - S32 computeLODDetail(F32 distance, F32 radius, F32 lod_factor); - bool calcLOD(); - LLFace* addFace(S32 face_index); - - // stats tracking for render complexity - static S32 mRenderComplexity_last; - static S32 mRenderComplexity_current; - - void onDrawableUpdateFromServer(); - void requestMediaDataUpdate(bool isNew); - void cleanUpMediaImpls(); - void addMediaImpl(LLViewerMediaImpl* media_impl, S32 texture_index) ; - void removeMediaImpl(S32 texture_index) ; - -private: - bool lodOrSculptChanged(LLDrawable *drawable, bool &compiled, bool &shouldUpdateOctreeBounds); - -public: - - static S32 getRenderComplexityMax() {return mRenderComplexity_last;} - static void updateRenderComplexity(); - - LLViewerTextureAnim *mTextureAnimp; - U8 mTexAnimMode; - F32 mLODDistance; - F32 mLODAdjustedDistance; - F32 mLODRadius; -private: - friend class LLDrawable; - friend class LLFace; - - bool mFaceMappingChanged; - LLFrameTimer mTextureUpdateTimer; - S32 mLOD; - bool mLODChanged; - bool mSculptChanged; - bool mColorChanged; - F32 mSpotLightPriority; - LLMatrix4 mRelativeXform; - LLMatrix3 mRelativeXformInvTrans; - bool mVolumeChanged; - F32 mVObjRadius; - LLVolumeInterface *mVolumeImpl; - LLPointer mSculptTexture; - LLPointer mLightTexture; - media_list_t mMediaImplList; - S32 mLastFetchedMediaVersion; // as fetched from the server, starts as -1 - U32 mServerDrawableUpdateCount; - S32 mIndexInTex[LLRender::NUM_VOLUME_TEXTURE_CHANNELS]; - S32 mMDCImplCount; - - // cached value of getIsLight to avoid redundant map lookups - // accessed by getIsLightFast - mutable bool mIsLight = false; - - // cached value of getIsAnimatedObject to avoid redundant map lookups - // accessed by getIsAnimatedObjectFast - mutable bool mIsAnimatedObject = false; - bool mResetDebugText; - - LLPointer mRiggedVolume; - - bool mSkinInfoUnavaliable; - LLConstPointer mSkinInfo; - // statics -public: - static F32 sLODSlopDistanceFactor;// Changing this to zero, effectively disables the LOD transition slop - static F32 sLODFactor; // LOD scale factor - static F32 sDistanceFactor; // LOD distance factor - - static LLPointer sObjectMediaClient; - static LLPointer sObjectMediaNavigateClient; -protected: - static S32 sNumLODChanges; - - friend class LLVolumeImplFlexible; -}; - -#endif // LL_LLVOVOLUME_H - +/** + * @file llvovolume.h + * @brief LLVOVolume class header file + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVOVOLUME_H +#define LL_LLVOVOLUME_H + +#include "llviewerobject.h" +#include "llviewertexture.h" +#include "llviewermedia.h" +#include "llframetimer.h" +#include "lllocalbitmaps.h" +#include "m3math.h" // LLMatrix3 +#include "m4math.h" // LLMatrix4 +#include +#include + + +class LLViewerTextureAnim; +class LLDrawPool; +class LLMaterialID; +class LLSelectNode; +class LLObjectMediaDataClient; +class LLObjectMediaNavigateClient; +class LLVOAvatar; +class LLMeshSkinInfo; + +typedef std::vector media_list_t; + +enum LLVolumeInterfaceType +{ + INTERFACE_FLEXIBLE = 1, +}; + +const F32 MAX_LOD_FACTOR = 4.0f; + + +class LLRiggedVolume : public LLVolume +{ +public: + LLRiggedVolume(const LLVolumeParams& params) + : LLVolume(params, 0.f) + { + } + + using FaceIndex = S32; + static const FaceIndex UPDATE_ALL_FACES = -1; + static const FaceIndex DO_NOT_UPDATE_FACES = -2; + void update( + const LLMeshSkinInfo* skin, + LLVOAvatar* avatar, + const LLVolume* src_volume, + FaceIndex face_index = UPDATE_ALL_FACES, + bool rebuild_face_octrees = true); + + std::string mExtraDebugText; +}; + +// Base class for implementations of the volume - Primitive, Flexible Object, etc. +class LLVolumeInterface +{ +public: + virtual ~LLVolumeInterface() { } + virtual LLVolumeInterfaceType getInterfaceType() const = 0; + virtual void doIdleUpdate() = 0; + virtual bool doUpdateGeometry(LLDrawable *drawable) = 0; + virtual LLVector3 getPivotPosition() const = 0; + virtual void onSetVolume(const LLVolumeParams &volume_params, const S32 detail) = 0; + virtual void onSetScale(const LLVector3 &scale, bool damped) = 0; + virtual void onParameterChanged(U16 param_type, LLNetworkData *data, bool in_use, bool local_origin) = 0; + virtual void onShift(const LLVector4a &shift_vector) = 0; + virtual bool isVolumeUnique() const = 0; // Do we need a unique LLVolume instance? + virtual bool isVolumeGlobal() const = 0; // Are we in global space? + virtual bool isActive() const = 0; // Is this object currently active? + virtual const LLMatrix4& getWorldMatrix(LLXformMatrix* xform) const = 0; + virtual void updateRelativeXform(bool force_identity = false) = 0; + virtual U32 getID() const = 0; + virtual void preRebuild() = 0; +}; + +// Class which embodies all Volume objects (with pcode LL_PCODE_VOLUME) +class LLVOVolume : public LLViewerObject +{ + LOG_CLASS(LLVOVolume); +protected: + virtual ~LLVOVolume(); + +public: + static void initClass(); + static void cleanupClass(); + static void preUpdateGeom(); + + enum + { + VERTEX_DATA_MASK = (1 << LLVertexBuffer::TYPE_VERTEX) | + (1 << LLVertexBuffer::TYPE_NORMAL) | + (1 << LLVertexBuffer::TYPE_TEXCOORD0) | + (1 << LLVertexBuffer::TYPE_TEXCOORD1) | + (1 << LLVertexBuffer::TYPE_COLOR) + }; + +public: + LLVOVolume(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); + void markDead() override; // Override (and call through to parent) to clean up media references + + LLDrawable* createDrawable(LLPipeline *pipeline) override; + + void deleteFaces(); + + void animateTextures(); + + bool isVisible() const ; + bool isActive() const override; + bool isAttachment() const override; + bool isRootEdit() const override; // overridden for sake of attachments treating themselves as a root object + bool isHUDAttachment() const override; + + void generateSilhouette(LLSelectNode* nodep, const LLVector3& view_point); + /*virtual*/ bool setParent(LLViewerObject* parent) override; + S32 getLOD() const override { return mLOD; } + void setNoLOD() { mLOD = NO_LOD; mLODChanged = true; } + bool isNoLOD() const { return NO_LOD == mLOD; } + const LLVector3 getPivotPositionAgent() const override; + const LLMatrix4& getRelativeXform() const { return mRelativeXform; } + const LLMatrix3& getRelativeXformInvTrans() const { return mRelativeXformInvTrans; } + /*virtual*/ const LLMatrix4 getRenderMatrix() const override; + typedef std::unordered_set texture_cost_t; + static S32 getTextureCost(const LLViewerTexture* img); + U32 getRenderCost(texture_cost_t &textures) const; + /*virtual*/ F32 getEstTrianglesMax() const override; + /*virtual*/ F32 getEstTrianglesStreamingCost() const override; + /* virtual*/ F32 getStreamingCost() const override; + /*virtual*/ bool getCostData(LLMeshCostData& costs) const override; + + /*virtual*/ U32 getTriangleCount(S32* vcount = NULL) const override; + /*virtual*/ U32 getHighLODTriangleCount() override; + /*virtual*/ bool lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, + S32 face = -1, // which face to check, -1 = ALL_SIDES + bool pick_transparent = false, + bool pick_rigged = false, + bool pick_unselectable = true, + S32* face_hit = NULL, // which face was hit + LLVector4a* intersection = NULL, // return the intersection point + LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point + LLVector4a* normal = NULL, // return the surface normal at the intersection point + LLVector4a* tangent = NULL // return the surface tangent at the intersection point + ) override; + + LLVector3 agentPositionToVolume(const LLVector3& pos) const; + LLVector3 agentDirectionToVolume(const LLVector3& dir) const; + LLVector3 volumePositionToAgent(const LLVector3& dir) const; + LLVector3 volumeDirectionToAgent(const LLVector3& dir) const; + + + bool getVolumeChanged() const { return mVolumeChanged; } + + F32 getVObjRadius() const override { return mVObjRadius; }; + const LLMatrix4& getWorldMatrix(LLXformMatrix* xform) const override; + + void markForUpdate() override; + void faceMappingChanged() override { mFaceMappingChanged=true; } + + /*virtual*/ void onShift(const LLVector4a &shift_vector) override; // Called when the drawable shifts + + /*virtual*/ void parameterChanged(U16 param_type, bool local_origin) override; + /*virtual*/ void parameterChanged(U16 param_type, LLNetworkData* data, bool in_use, bool local_origin) override; + + // update mReflectionProbe based on isReflectionProbe() + void updateReflectionProbePtr(); + + /*virtual*/ U32 processUpdateMessage(LLMessageSystem *mesgsys, + void **user_data, + U32 block_num, const EObjectUpdateType update_type, + LLDataPacker *dp) override; + + /*virtual*/ void setSelected(bool sel) override; + /*virtual*/ bool setDrawableParent(LLDrawable* parentp) override; + + /*virtual*/ void setScale(const LLVector3 &scale, bool damped) override; + + /*virtual*/ void changeTEImage(S32 index, LLViewerTexture* new_image) override; + /*virtual*/ void setNumTEs(const U8 num_tes) override; + /*virtual*/ void setTEImage(const U8 te, LLViewerTexture *imagep) override; + /*virtual*/ S32 setTETexture(const U8 te, const LLUUID &uuid) override; + /*virtual*/ S32 setTEColor(const U8 te, const LLColor3 &color) override; + /*virtual*/ S32 setTEColor(const U8 te, const LLColor4 &color) override; + /*virtual*/ S32 setTEBumpmap(const U8 te, const U8 bump) override; + /*virtual*/ S32 setTEShiny(const U8 te, const U8 shiny) override; + /*virtual*/ S32 setTEFullbright(const U8 te, const U8 fullbright) override; + /*virtual*/ S32 setTEBumpShinyFullbright(const U8 te, const U8 bump) override; + /*virtual*/ S32 setTEMediaFlags(const U8 te, const U8 media_flags) override; + /*virtual*/ S32 setTEGlow(const U8 te, const F32 glow) override; + /*virtual*/ S32 setTEMaterialID(const U8 te, const LLMaterialID& pMaterialID) override; + + static void setTEMaterialParamsCallbackTE(const LLUUID& objectID, const LLMaterialID& pMaterialID, const LLMaterialPtr pMaterialParams, U32 te); + + /*virtual*/ S32 setTEMaterialParams(const U8 te, const LLMaterialPtr pMaterialParams) override; + S32 setTEGLTFMaterialOverride(U8 te, LLGLTFMaterial* mat) override; + /*virtual*/ S32 setTEScale(const U8 te, const F32 s, const F32 t) override; + /*virtual*/ S32 setTEScaleS(const U8 te, const F32 s) override; + /*virtual*/ S32 setTEScaleT(const U8 te, const F32 t) override; + /*virtual*/ S32 setTETexGen(const U8 te, const U8 texgen) override; + /*virtual*/ S32 setTEMediaTexGen(const U8 te, const U8 media) override; + /*virtual*/ bool setMaterial(const U8 material) override; + + void setTexture(const S32 face); + S32 getIndexInTex(U32 ch) const {return mIndexInTex[ch];} + /*virtual*/ bool setVolume(const LLVolumeParams &volume_params, const S32 detail, bool unique_volume = false) override; + void updateSculptTexture(); + void setIndexInTex(U32 ch, S32 index) { mIndexInTex[ch] = index ;} + void sculpt(); + static void rebuildMeshAssetCallback(const LLUUID& asset_uuid, + LLAssetType::EType type, + void* user_data, S32 status, LLExtStat ext_status); + + void updateRelativeXform(bool force_identity = false); + /*virtual*/ bool updateGeometry(LLDrawable *drawable) override; + /*virtual*/ void updateFaceSize(S32 idx) override; + /*virtual*/ bool updateLOD() override; + void updateRadius() override; + /*virtual*/ void updateTextures() override; + void updateTextureVirtualSize(bool forced = false); + + void updateFaceFlags(); + void regenFaces(); + bool genBBoxes(bool force_global, bool should_update_octree_bounds = true); + void preRebuild(); + virtual void updateSpatialExtents(LLVector4a& min, LLVector4a& max) override; + virtual F32 getBinRadius() override; + + virtual U32 getPartitionType() const override; + + // For Lights + void setIsLight(bool is_light); + //set the gamma-corrected (sRGB) color of this light + void setLightSRGBColor(const LLColor3& color); + //set the linear color of this light + void setLightLinearColor(const LLColor3& color); + + void setLightIntensity(F32 intensity); + void setLightRadius(F32 radius); + void setLightFalloff(F32 falloff); + void setLightCutoff(F32 cutoff); + void setLightTextureID(LLUUID id); + void setSpotLightParams(LLVector3 params); + + bool getIsLight() const; + bool getIsLightFast() const; + + + // Get the light color in sRGB color space NOT scaled by intensity. + LLColor3 getLightSRGBBaseColor() const; + + // Get the light color in linear color space NOT scaled by intensity. + LLColor3 getLightLinearBaseColor() const; + + // Get the light color in linear color space scaled by intensity + // this is the value that should be fed into shaders + LLColor3 getLightLinearColor() const; + + // Get the light color in sRGB color space scaled by intensity. + LLColor3 getLightSRGBColor() const; + + LLUUID getLightTextureID() const; + bool isLightSpotlight() const; + LLVector3 getSpotLightParams() const; + void updateSpotLightPriority(); + F32 getSpotLightPriority() const; + + LLViewerTexture* getLightTexture(); + F32 getLightIntensity() const; + F32 getLightRadius() const; + F32 getLightFalloff(const F32 fudge_factor = 1.f) const; + F32 getLightCutoff() const; + + // Reflection Probes + bool setIsReflectionProbe(bool is_probe); + bool setReflectionProbeAmbiance(F32 ambiance); + bool setReflectionProbeNearClip(F32 near_clip); + bool setReflectionProbeIsBox(bool is_box); + bool setReflectionProbeIsDynamic(bool is_dynamic); + + bool isReflectionProbe() const override; + F32 getReflectionProbeAmbiance() const; + F32 getReflectionProbeNearClip() const; + bool getReflectionProbeIsBox() const; + bool getReflectionProbeIsDynamic() const; + + // Flexible Objects + U32 getVolumeInterfaceID() const; + virtual bool isFlexible() const override; + virtual bool isSculpted() const override; + virtual bool isMesh() const override; + virtual bool isRiggedMesh() const override; + virtual bool hasLightTexture() const override; + + // fast variants above that use state that is filled in later + // not reliable early in the life of an object, but should be used after + // object is loaded + bool isFlexibleFast() const; + bool isSculptedFast() const; + bool isMeshFast() const; + bool isRiggedMeshFast() const; + bool isAnimatedObjectFast() const; + + bool isVolumeGlobal() const; + bool canBeFlexible() const; + bool setIsFlexible(bool is_flexible); + + const LLMeshSkinInfo* getSkinInfo() const; + const bool isSkinInfoUnavaliable() const { return mSkinInfoUnavaliable; } + + //convenience accessor for mesh ID (which is stored in sculpt id for legacy reasons) + const LLUUID& getMeshID() const { return getVolume()->getParams().getSculptID(); } + + // Extended Mesh Properties + U32 getExtendedMeshFlags() const; + void onSetExtendedMeshFlags(U32 flags); + void setExtendedMeshFlags(U32 flags); + bool canBeAnimatedObject() const; + bool isAnimatedObject() const override; + virtual void onReparent(LLViewerObject *old_parent, LLViewerObject *new_parent) override; + virtual void afterReparent() override; + + //virtual + void updateRiggingInfo() override; + S32 mLastRiggingInfoLOD; + + // Functions that deal with media, or media navigation + + // Update this object's media data with the given media data array + // (typically this is only called upon a response from a server request) + void updateObjectMediaData(const LLSD &media_data_array, const std::string &media_version); + + // Bounce back media at the given index to its current URL (or home URL, if current URL is empty) + void mediaNavigateBounceBack(U8 texture_index); + + // Returns whether or not this object has permission to navigate or control + // the given media entry + enum MediaPermType { + MEDIA_PERM_INTERACT, MEDIA_PERM_CONTROL + }; + bool hasMediaPermission(const LLMediaEntry* media_entry, MediaPermType perm_type); + + void mediaNavigated(LLViewerMediaImpl *impl, LLPluginClassMedia* plugin, std::string new_location); + void mediaEvent(LLViewerMediaImpl *impl, LLPluginClassMedia* plugin, LLViewerMediaObserver::EMediaEvent event); + + + // Sync the given media data with the impl and the given te + void syncMediaData(S32 te, const LLSD &media_data, bool merge, bool ignore_agent); + + // Send media data update to the simulator. + void sendMediaDataUpdate(); + + viewer_media_t getMediaImpl(U8 face_id) const; + S32 getFaceIndexWithMediaImpl(const LLViewerMediaImpl* media_impl, S32 start_face_id); + F64 getTotalMediaInterest() const; + + bool hasMedia() const; + + LLVector3 getApproximateFaceNormal(U8 face_id); + + // Flag any corresponding avatars as needing update. + void updateVisualComplexity(); + + void notifyMeshLoaded(); + void notifySkinInfoLoaded(const LLMeshSkinInfo* skin); + void notifySkinInfoUnavailable(); + + // Returns 'true' iff the media data for this object is in flight + bool isMediaDataBeingFetched() const; + + // Returns the "last fetched" media version, or -1 if not fetched yet + S32 getLastFetchedMediaVersion() const { return mLastFetchedMediaVersion; } + + void addMDCImpl() { ++mMDCImplCount; } + void removeMDCImpl() { --mMDCImplCount; } + S32 getMDCImplCount() { return mMDCImplCount; } + + + // Rigged volume update (for raycasting) + // By default, this updates the bounding boxes of all the faces and builds an octree for precise per-triangle raycasting + void updateRiggedVolume( + bool force_treat_as_rigged, + LLRiggedVolume::FaceIndex face_index = LLRiggedVolume::UPDATE_ALL_FACES, + bool rebuild_face_octrees = true); + LLRiggedVolume* getRiggedVolume(); + + //returns true if volume should be treated as a rigged volume + // - Build tools are open + // - object is an attachment + // - object is attached to self + // - object is rendered as rigged + bool treatAsRigged(); + + //clear out rigged volume and revert back to non-rigged state for picking/LOD/distance updates + void clearRiggedVolume(); + +protected: + S32 computeLODDetail(F32 distance, F32 radius, F32 lod_factor); + bool calcLOD(); + LLFace* addFace(S32 face_index); + + // stats tracking for render complexity + static S32 mRenderComplexity_last; + static S32 mRenderComplexity_current; + + void onDrawableUpdateFromServer(); + void requestMediaDataUpdate(bool isNew); + void cleanUpMediaImpls(); + void addMediaImpl(LLViewerMediaImpl* media_impl, S32 texture_index) ; + void removeMediaImpl(S32 texture_index) ; + +private: + bool lodOrSculptChanged(LLDrawable *drawable, bool &compiled, bool &shouldUpdateOctreeBounds); + +public: + + static S32 getRenderComplexityMax() {return mRenderComplexity_last;} + static void updateRenderComplexity(); + + LLViewerTextureAnim *mTextureAnimp; + U8 mTexAnimMode; + F32 mLODDistance; + F32 mLODAdjustedDistance; + F32 mLODRadius; +private: + friend class LLDrawable; + friend class LLFace; + + bool mFaceMappingChanged; + LLFrameTimer mTextureUpdateTimer; + S32 mLOD; + bool mLODChanged; + bool mSculptChanged; + bool mColorChanged; + F32 mSpotLightPriority; + LLMatrix4 mRelativeXform; + LLMatrix3 mRelativeXformInvTrans; + bool mVolumeChanged; + F32 mVObjRadius; + LLVolumeInterface *mVolumeImpl; + LLPointer mSculptTexture; + LLPointer mLightTexture; + media_list_t mMediaImplList; + S32 mLastFetchedMediaVersion; // as fetched from the server, starts as -1 + U32 mServerDrawableUpdateCount; + S32 mIndexInTex[LLRender::NUM_VOLUME_TEXTURE_CHANNELS]; + S32 mMDCImplCount; + + // cached value of getIsLight to avoid redundant map lookups + // accessed by getIsLightFast + mutable bool mIsLight = false; + + // cached value of getIsAnimatedObject to avoid redundant map lookups + // accessed by getIsAnimatedObjectFast + mutable bool mIsAnimatedObject = false; + bool mResetDebugText; + + LLPointer mRiggedVolume; + + bool mSkinInfoUnavaliable; + LLConstPointer mSkinInfo; + // statics +public: + static F32 sLODSlopDistanceFactor;// Changing this to zero, effectively disables the LOD transition slop + static F32 sLODFactor; // LOD scale factor + static F32 sDistanceFactor; // LOD distance factor + + static LLPointer sObjectMediaClient; + static LLPointer sObjectMediaNavigateClient; +protected: + static S32 sNumLODChanges; + + friend class LLVolumeImplFlexible; +}; + +#endif // LL_LLVOVOLUME_H + diff --git a/indra/newview/llvowater.cpp b/indra/newview/llvowater.cpp index 2ec91679bf..34040e1aca 100644 --- a/indra/newview/llvowater.cpp +++ b/indra/newview/llvowater.cpp @@ -1,307 +1,307 @@ -/** - * @file llvowater.cpp - * @brief LLVOWater class implementation - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llvowater.h" - -#include "llviewercontrol.h" - -#include "lldrawable.h" -#include "lldrawpoolwater.h" -#include "llface.h" -#include "llsky.h" -#include "llsurface.h" -#include "llviewercamera.h" -#include "llviewertexturelist.h" -#include "llviewerregion.h" -#include "llworld.h" -#include "pipeline.h" -#include "llspatialpartition.h" - -/////////////////////////////////// - -template inline T LERP(T a, T b, F32 factor) -{ - return a + (b - a) * factor; -} - -LLVOWater::LLVOWater(const LLUUID &id, - const LLPCode pcode, - LLViewerRegion *regionp) : - LLStaticViewerObject(id, pcode, regionp), - mRenderType(LLPipeline::RENDER_TYPE_WATER) -{ - // Terrain must draw during selection passes so it can block objects behind it. - mbCanSelect = false; - setScale(LLVector3(256.f, 256.f, 0.f)); // Hack for setting scale for bounding boxes/visibility. - - mUseTexture = true; - mIsEdgePatch = false; -} - - -void LLVOWater::markDead() -{ - LLViewerObject::markDead(); -} - - -bool LLVOWater::isActive() const -{ - return false; -} - - -void LLVOWater::setPixelAreaAndAngle(LLAgent &agent) -{ - mAppAngle = 50; - mPixelArea = 500*500; -} - - -// virtual -void LLVOWater::updateTextures() -{ -} - -// Never gets called -void LLVOWater::idleUpdate(LLAgent &agent, const F64 &time) -{ -} - -LLDrawable *LLVOWater::createDrawable(LLPipeline *pipeline) -{ - pipeline->allocDrawable(this); - mDrawable->setLit(false); - mDrawable->setRenderType(mRenderType); - - LLDrawPoolWater *pool = (LLDrawPoolWater*) gPipeline.getPool(LLDrawPool::POOL_WATER); - - if (mUseTexture) - { - mDrawable->setNumFaces(1, pool, mRegionp->getLand().getWaterTexture()); - } - else - { - mDrawable->setNumFaces(1, pool, LLWorld::getInstance()->getDefaultWaterTexture()); - } - - return mDrawable; -} - -bool LLVOWater::updateGeometry(LLDrawable *drawable) -{ - LL_PROFILE_ZONE_SCOPED; - LLFace *face; - - if (drawable->getNumFaces() < 1) - { - LLDrawPoolWater *poolp = (LLDrawPoolWater*) gPipeline.getPool(LLDrawPool::POOL_WATER); - drawable->addFace(poolp, NULL); - } - face = drawable->getFace(0); - if (!face) - { - return true; - } - -// LLVector2 uvs[4]; -// LLVector3 vtx[4]; - - LLStrider verticesp, normalsp; - LLStrider texCoordsp; - LLStrider indicesp; - U16 index_offset; - - - // A quad is 4 vertices and 6 indices (making 2 triangles) - static const unsigned int vertices_per_quad = 4; - static const unsigned int indices_per_quad = 6; - - S32 size_x = LLPipeline::sRenderTransparentWater ? 8 : 1; - S32 size_y = LLPipeline::sRenderTransparentWater ? 8 : 1; - - const LLVector3& scale = getScale(); - size_x *= llmin(llround(scale.mV[0] / 256.f), 8); - size_y *= llmin(llround(scale.mV[1] / 256.f), 8); - - const S32 num_quads = size_x * size_y; - face->setSize(vertices_per_quad * num_quads, - indices_per_quad * num_quads); - - LLVertexBuffer* buff = face->getVertexBuffer(); - if (!buff || - buff->getNumIndices() != face->getIndicesCount() || - buff->getNumVerts() != face->getGeomCount() || - face->getIndicesStart() != 0 || - face->getGeomIndex() != 0) - { - buff = new LLVertexBuffer(LLDrawPoolWater::VERTEX_DATA_MASK); - if (!buff->allocateBuffer(face->getGeomCount(), face->getIndicesCount())) - { - LL_WARNS() << "Failed to allocate Vertex Buffer on water update to " - << face->getGeomCount() << " vertices and " - << face->getIndicesCount() << " indices" << LL_ENDL; - } - face->setIndicesIndex(0); - face->setGeomIndex(0); - face->setVertexBuffer(buff); - } - - index_offset = face->getGeometry(verticesp,normalsp,texCoordsp, indicesp); - - LLVector3 position_agent; - position_agent = getPositionAgent(); - face->mCenterAgent = position_agent; - face->mCenterLocal = position_agent; - - S32 x, y; - F32 step_x = getScale().mV[0] / size_x; - F32 step_y = getScale().mV[1] / size_y; - - const LLVector3 up(0.f, step_y * 0.5f, 0.f); - const LLVector3 right(step_x * 0.5f, 0.f, 0.f); - const LLVector3 normal(0.f, 0.f, 1.f); - - F32 size_inv_x = 1.f / size_x; - F32 size_inv_y = 1.f / size_y; - - for (y = 0; y < size_y; y++) - { - for (x = 0; x < size_x; x++) - { - S32 toffset = index_offset + 4*(y*size_x + x); - position_agent = getPositionAgent() - getScale() * 0.5f; - position_agent.mV[VX] += (x + 0.5f) * step_x; - position_agent.mV[VY] += (y + 0.5f) * step_y; - - position_agent.mV[VX] = llround(position_agent.mV[VX]); - position_agent.mV[VY] = llround(position_agent.mV[VY]); - - *verticesp++ = position_agent - right + up; - *verticesp++ = position_agent - right - up; - *verticesp++ = position_agent + right + up; - *verticesp++ = position_agent + right - up; - - *texCoordsp++ = LLVector2(x*size_inv_x, (y+1)*size_inv_y); - *texCoordsp++ = LLVector2(x*size_inv_x, y*size_inv_y); - *texCoordsp++ = LLVector2((x+1)*size_inv_x, (y+1)*size_inv_y); - *texCoordsp++ = LLVector2((x+1)*size_inv_x, y*size_inv_y); - - *normalsp++ = normal; - *normalsp++ = normal; - *normalsp++ = normal; - *normalsp++ = normal; - - *indicesp++ = toffset + 0; - *indicesp++ = toffset + 1; - *indicesp++ = toffset + 2; - - *indicesp++ = toffset + 1; - *indicesp++ = toffset + 3; - *indicesp++ = toffset + 2; - } - } - - buff->unmapBuffer(); - - mDrawable->movePartition(); - LLPipeline::sCompiles++; - return true; -} - -void LLVOWater::initClass() -{ -} - -void LLVOWater::cleanupClass() -{ -} - -void setVecZ(LLVector3& v) -{ - v.mV[VX] = 0; - v.mV[VY] = 0; - v.mV[VZ] = 1; -} - -void LLVOWater::setUseTexture(const bool use_texture) -{ - mUseTexture = use_texture; -} - -void LLVOWater::setIsEdgePatch(const bool edge_patch) -{ - mIsEdgePatch = edge_patch; -} - -void LLVOWater::updateSpatialExtents(LLVector4a &newMin, LLVector4a& newMax) -{ - LLVector4a pos; - pos.load3(getPositionAgent().mV); - LLVector4a scale; - scale.load3(getScale().mV); - scale.mul(0.5f); - - newMin.setSub(pos, scale); - newMax.setAdd(pos, scale); - - pos.setAdd(newMin,newMax); - pos.mul(0.5f); - - mDrawable->setPositionGroup(pos); -} - -U32 LLVOWater::getPartitionType() const -{ - if (mIsEdgePatch) - { - return LLViewerRegion::PARTITION_VOIDWATER; - } - - return LLViewerRegion::PARTITION_WATER; -} - -U32 LLVOVoidWater::getPartitionType() const -{ - return LLViewerRegion::PARTITION_VOIDWATER; -} - -LLWaterPartition::LLWaterPartition(LLViewerRegion* regionp) -: LLSpatialPartition(0, false, regionp) -{ - mInfiniteFarClip = true; - mDrawableType = LLPipeline::RENDER_TYPE_WATER; - mPartitionType = LLViewerRegion::PARTITION_WATER; -} - -LLVoidWaterPartition::LLVoidWaterPartition(LLViewerRegion* regionp) : LLWaterPartition(regionp) -{ - mOcclusionEnabled = false; - mDrawableType = LLPipeline::RENDER_TYPE_VOIDWATER; - mPartitionType = LLViewerRegion::PARTITION_VOIDWATER; -} +/** + * @file llvowater.cpp + * @brief LLVOWater class implementation + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llvowater.h" + +#include "llviewercontrol.h" + +#include "lldrawable.h" +#include "lldrawpoolwater.h" +#include "llface.h" +#include "llsky.h" +#include "llsurface.h" +#include "llviewercamera.h" +#include "llviewertexturelist.h" +#include "llviewerregion.h" +#include "llworld.h" +#include "pipeline.h" +#include "llspatialpartition.h" + +/////////////////////////////////// + +template inline T LERP(T a, T b, F32 factor) +{ + return a + (b - a) * factor; +} + +LLVOWater::LLVOWater(const LLUUID &id, + const LLPCode pcode, + LLViewerRegion *regionp) : + LLStaticViewerObject(id, pcode, regionp), + mRenderType(LLPipeline::RENDER_TYPE_WATER) +{ + // Terrain must draw during selection passes so it can block objects behind it. + mbCanSelect = false; + setScale(LLVector3(256.f, 256.f, 0.f)); // Hack for setting scale for bounding boxes/visibility. + + mUseTexture = true; + mIsEdgePatch = false; +} + + +void LLVOWater::markDead() +{ + LLViewerObject::markDead(); +} + + +bool LLVOWater::isActive() const +{ + return false; +} + + +void LLVOWater::setPixelAreaAndAngle(LLAgent &agent) +{ + mAppAngle = 50; + mPixelArea = 500*500; +} + + +// virtual +void LLVOWater::updateTextures() +{ +} + +// Never gets called +void LLVOWater::idleUpdate(LLAgent &agent, const F64 &time) +{ +} + +LLDrawable *LLVOWater::createDrawable(LLPipeline *pipeline) +{ + pipeline->allocDrawable(this); + mDrawable->setLit(false); + mDrawable->setRenderType(mRenderType); + + LLDrawPoolWater *pool = (LLDrawPoolWater*) gPipeline.getPool(LLDrawPool::POOL_WATER); + + if (mUseTexture) + { + mDrawable->setNumFaces(1, pool, mRegionp->getLand().getWaterTexture()); + } + else + { + mDrawable->setNumFaces(1, pool, LLWorld::getInstance()->getDefaultWaterTexture()); + } + + return mDrawable; +} + +bool LLVOWater::updateGeometry(LLDrawable *drawable) +{ + LL_PROFILE_ZONE_SCOPED; + LLFace *face; + + if (drawable->getNumFaces() < 1) + { + LLDrawPoolWater *poolp = (LLDrawPoolWater*) gPipeline.getPool(LLDrawPool::POOL_WATER); + drawable->addFace(poolp, NULL); + } + face = drawable->getFace(0); + if (!face) + { + return true; + } + +// LLVector2 uvs[4]; +// LLVector3 vtx[4]; + + LLStrider verticesp, normalsp; + LLStrider texCoordsp; + LLStrider indicesp; + U16 index_offset; + + + // A quad is 4 vertices and 6 indices (making 2 triangles) + static const unsigned int vertices_per_quad = 4; + static const unsigned int indices_per_quad = 6; + + S32 size_x = LLPipeline::sRenderTransparentWater ? 8 : 1; + S32 size_y = LLPipeline::sRenderTransparentWater ? 8 : 1; + + const LLVector3& scale = getScale(); + size_x *= llmin(llround(scale.mV[0] / 256.f), 8); + size_y *= llmin(llround(scale.mV[1] / 256.f), 8); + + const S32 num_quads = size_x * size_y; + face->setSize(vertices_per_quad * num_quads, + indices_per_quad * num_quads); + + LLVertexBuffer* buff = face->getVertexBuffer(); + if (!buff || + buff->getNumIndices() != face->getIndicesCount() || + buff->getNumVerts() != face->getGeomCount() || + face->getIndicesStart() != 0 || + face->getGeomIndex() != 0) + { + buff = new LLVertexBuffer(LLDrawPoolWater::VERTEX_DATA_MASK); + if (!buff->allocateBuffer(face->getGeomCount(), face->getIndicesCount())) + { + LL_WARNS() << "Failed to allocate Vertex Buffer on water update to " + << face->getGeomCount() << " vertices and " + << face->getIndicesCount() << " indices" << LL_ENDL; + } + face->setIndicesIndex(0); + face->setGeomIndex(0); + face->setVertexBuffer(buff); + } + + index_offset = face->getGeometry(verticesp,normalsp,texCoordsp, indicesp); + + LLVector3 position_agent; + position_agent = getPositionAgent(); + face->mCenterAgent = position_agent; + face->mCenterLocal = position_agent; + + S32 x, y; + F32 step_x = getScale().mV[0] / size_x; + F32 step_y = getScale().mV[1] / size_y; + + const LLVector3 up(0.f, step_y * 0.5f, 0.f); + const LLVector3 right(step_x * 0.5f, 0.f, 0.f); + const LLVector3 normal(0.f, 0.f, 1.f); + + F32 size_inv_x = 1.f / size_x; + F32 size_inv_y = 1.f / size_y; + + for (y = 0; y < size_y; y++) + { + for (x = 0; x < size_x; x++) + { + S32 toffset = index_offset + 4*(y*size_x + x); + position_agent = getPositionAgent() - getScale() * 0.5f; + position_agent.mV[VX] += (x + 0.5f) * step_x; + position_agent.mV[VY] += (y + 0.5f) * step_y; + + position_agent.mV[VX] = llround(position_agent.mV[VX]); + position_agent.mV[VY] = llround(position_agent.mV[VY]); + + *verticesp++ = position_agent - right + up; + *verticesp++ = position_agent - right - up; + *verticesp++ = position_agent + right + up; + *verticesp++ = position_agent + right - up; + + *texCoordsp++ = LLVector2(x*size_inv_x, (y+1)*size_inv_y); + *texCoordsp++ = LLVector2(x*size_inv_x, y*size_inv_y); + *texCoordsp++ = LLVector2((x+1)*size_inv_x, (y+1)*size_inv_y); + *texCoordsp++ = LLVector2((x+1)*size_inv_x, y*size_inv_y); + + *normalsp++ = normal; + *normalsp++ = normal; + *normalsp++ = normal; + *normalsp++ = normal; + + *indicesp++ = toffset + 0; + *indicesp++ = toffset + 1; + *indicesp++ = toffset + 2; + + *indicesp++ = toffset + 1; + *indicesp++ = toffset + 3; + *indicesp++ = toffset + 2; + } + } + + buff->unmapBuffer(); + + mDrawable->movePartition(); + LLPipeline::sCompiles++; + return true; +} + +void LLVOWater::initClass() +{ +} + +void LLVOWater::cleanupClass() +{ +} + +void setVecZ(LLVector3& v) +{ + v.mV[VX] = 0; + v.mV[VY] = 0; + v.mV[VZ] = 1; +} + +void LLVOWater::setUseTexture(const bool use_texture) +{ + mUseTexture = use_texture; +} + +void LLVOWater::setIsEdgePatch(const bool edge_patch) +{ + mIsEdgePatch = edge_patch; +} + +void LLVOWater::updateSpatialExtents(LLVector4a &newMin, LLVector4a& newMax) +{ + LLVector4a pos; + pos.load3(getPositionAgent().mV); + LLVector4a scale; + scale.load3(getScale().mV); + scale.mul(0.5f); + + newMin.setSub(pos, scale); + newMax.setAdd(pos, scale); + + pos.setAdd(newMin,newMax); + pos.mul(0.5f); + + mDrawable->setPositionGroup(pos); +} + +U32 LLVOWater::getPartitionType() const +{ + if (mIsEdgePatch) + { + return LLViewerRegion::PARTITION_VOIDWATER; + } + + return LLViewerRegion::PARTITION_WATER; +} + +U32 LLVOVoidWater::getPartitionType() const +{ + return LLViewerRegion::PARTITION_VOIDWATER; +} + +LLWaterPartition::LLWaterPartition(LLViewerRegion* regionp) +: LLSpatialPartition(0, false, regionp) +{ + mInfiniteFarClip = true; + mDrawableType = LLPipeline::RENDER_TYPE_WATER; + mPartitionType = LLViewerRegion::PARTITION_WATER; +} + +LLVoidWaterPartition::LLVoidWaterPartition(LLViewerRegion* regionp) : LLWaterPartition(regionp) +{ + mOcclusionEnabled = false; + mDrawableType = LLPipeline::RENDER_TYPE_VOIDWATER; + mPartitionType = LLViewerRegion::PARTITION_VOIDWATER; +} diff --git a/indra/newview/llvowater.h b/indra/newview/llvowater.h index 9fbdce3563..adae86691a 100644 --- a/indra/newview/llvowater.h +++ b/indra/newview/llvowater.h @@ -1,96 +1,96 @@ -/** - * @file llvowater.h - * @brief Description of LLVOWater class - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_VOWATER_H -#define LL_VOWATER_H - -#include "llviewerobject.h" -#include "llviewertexture.h" -#include "pipeline.h" -#include "v2math.h" - -const U32 N_RES = 16; //32 // number of subdivisions of wave tile -const U8 WAVE_STEP = 8; - -class LLSurface; -class LLHeavenBody; -class LLVOSky; -class LLFace; - -class LLVOWater : public LLStaticViewerObject -{ -public: - enum - { - VERTEX_DATA_MASK = (1 << LLVertexBuffer::TYPE_VERTEX) | - (1 << LLVertexBuffer::TYPE_NORMAL) | - (1 << LLVertexBuffer::TYPE_TEXCOORD0) - }; - - LLVOWater(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); - - /*virtual*/ void markDead(); - - // Initialize data that's only inited once per class. - static void initClass(); - static void cleanupClass(); - - /*virtual*/ void idleUpdate(LLAgent &agent, const F64 &time); - /*virtual*/ LLDrawable* createDrawable(LLPipeline *pipeline); - /*virtual*/ bool updateGeometry(LLDrawable *drawable); - /*virtual*/ void updateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax); - - /*virtual*/ void updateTextures(); - /*virtual*/ void setPixelAreaAndAngle(LLAgent &agent); // generate accurate apparent angle and area - - virtual U32 getPartitionType() const; - - /*virtual*/ bool isActive() const; // Whether this object needs to do an idleUpdate. - - void setUseTexture(const bool use_texture); - void setIsEdgePatch(const bool edge_patch); - bool getUseTexture() const { return mUseTexture; } - bool getIsEdgePatch() const { return mIsEdgePatch; } - -protected: - bool mUseTexture; - bool mIsEdgePatch; - S32 mRenderType; -}; - -class LLVOVoidWater : public LLVOWater -{ -public: - LLVOVoidWater(LLUUID const& id, LLPCode pcode, LLViewerRegion* regionp) : LLVOWater(id, pcode, regionp) - { - mRenderType = LLPipeline::RENDER_TYPE_VOIDWATER; - } - - /*virtual*/ U32 getPartitionType() const; -}; - - -#endif // LL_VOSURFACEPATCH_H +/** + * @file llvowater.h + * @brief Description of LLVOWater class + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_VOWATER_H +#define LL_VOWATER_H + +#include "llviewerobject.h" +#include "llviewertexture.h" +#include "pipeline.h" +#include "v2math.h" + +const U32 N_RES = 16; //32 // number of subdivisions of wave tile +const U8 WAVE_STEP = 8; + +class LLSurface; +class LLHeavenBody; +class LLVOSky; +class LLFace; + +class LLVOWater : public LLStaticViewerObject +{ +public: + enum + { + VERTEX_DATA_MASK = (1 << LLVertexBuffer::TYPE_VERTEX) | + (1 << LLVertexBuffer::TYPE_NORMAL) | + (1 << LLVertexBuffer::TYPE_TEXCOORD0) + }; + + LLVOWater(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); + + /*virtual*/ void markDead(); + + // Initialize data that's only inited once per class. + static void initClass(); + static void cleanupClass(); + + /*virtual*/ void idleUpdate(LLAgent &agent, const F64 &time); + /*virtual*/ LLDrawable* createDrawable(LLPipeline *pipeline); + /*virtual*/ bool updateGeometry(LLDrawable *drawable); + /*virtual*/ void updateSpatialExtents(LLVector4a& newMin, LLVector4a& newMax); + + /*virtual*/ void updateTextures(); + /*virtual*/ void setPixelAreaAndAngle(LLAgent &agent); // generate accurate apparent angle and area + + virtual U32 getPartitionType() const; + + /*virtual*/ bool isActive() const; // Whether this object needs to do an idleUpdate. + + void setUseTexture(const bool use_texture); + void setIsEdgePatch(const bool edge_patch); + bool getUseTexture() const { return mUseTexture; } + bool getIsEdgePatch() const { return mIsEdgePatch; } + +protected: + bool mUseTexture; + bool mIsEdgePatch; + S32 mRenderType; +}; + +class LLVOVoidWater : public LLVOWater +{ +public: + LLVOVoidWater(LLUUID const& id, LLPCode pcode, LLViewerRegion* regionp) : LLVOWater(id, pcode, regionp) + { + mRenderType = LLPipeline::RENDER_TYPE_VOIDWATER; + } + + /*virtual*/ U32 getPartitionType() const; +}; + + +#endif // LL_VOSURFACEPATCH_H diff --git a/indra/newview/llvowlsky.cpp b/indra/newview/llvowlsky.cpp index 4c36055038..b5ee42f36c 100644 --- a/indra/newview/llvowlsky.cpp +++ b/indra/newview/llvowlsky.cpp @@ -1,581 +1,581 @@ -/** - * @file llvowlsky.cpp - * @brief LLVOWLSky class implementation - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "pipeline.h" - -#include "llvowlsky.h" -#include "llsky.h" -#include "lldrawpoolwlsky.h" -#include "llface.h" -#include "llviewercontrol.h" -#include "llenvironment.h" -#include "llsettingssky.h" - -constexpr U32 MIN_SKY_DETAIL = 8; -constexpr U32 MAX_SKY_DETAIL = 180; - -inline U32 LLVOWLSky::getNumStacks(void) -{ - return llmin(MAX_SKY_DETAIL, llmax(MIN_SKY_DETAIL, gSavedSettings.getU32("WLSkyDetail"))); -} - -inline U32 LLVOWLSky::getNumSlices(void) -{ - return 2 * llmin(MAX_SKY_DETAIL, llmax(MIN_SKY_DETAIL, gSavedSettings.getU32("WLSkyDetail"))); -} - -inline U32 LLVOWLSky::getStripsNumVerts(void) -{ - return (getNumStacks() - 1) * getNumSlices(); -} - -inline U32 LLVOWLSky::getStripsNumIndices(void) -{ - return 2 * ((getNumStacks() - 2) * (getNumSlices() + 1)) + 1 ; -} - -inline U32 LLVOWLSky::getStarsNumVerts(void) -{ - return 1000; -} - -inline U32 LLVOWLSky::getStarsNumIndices(void) -{ - return 1000; -} - -LLVOWLSky::LLVOWLSky(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) - : LLStaticViewerObject(id, pcode, regionp, true) -{ - initStars(); -} - -void LLVOWLSky::idleUpdate(LLAgent &agent, const F64 &time) -{ - -} - -bool LLVOWLSky::isActive(void) const -{ - return false; -} - -LLDrawable * LLVOWLSky::createDrawable(LLPipeline * pipeline) -{ - pipeline->allocDrawable(this); - - //LLDrawPoolWLSky *poolp = static_cast( - gPipeline.getPool(LLDrawPool::POOL_WL_SKY); - - mDrawable->setRenderType(LLPipeline::RENDER_TYPE_WL_SKY); - - return mDrawable; -} - -// a tiny helper function for controlling the sky dome tesselation. -inline F32 calcPhi(const U32 &i, const F32 &reciprocal_num_stacks) -{ - // Calc: PI/8 * 1-((1-t^4)*(1-t^4)) { 0 vertices; - LLStrider texCoords; - LLStrider indices; - - if (mFsSkyVerts.isNull()) - { - mFsSkyVerts = new LLVertexBuffer(LLDrawPoolWLSky::ADV_ATMO_SKY_VERTEX_DATA_MASK); - - if (!mFsSkyVerts->allocateBuffer(4, 6)) - { - LL_WARNS() << "Failed to allocate Vertex Buffer on full screen sky update" << LL_ENDL; - } - - bool success = mFsSkyVerts->getVertexStrider(vertices) - && mFsSkyVerts->getTexCoord0Strider(texCoords) - && mFsSkyVerts->getIndexStrider(indices); - - if(!success) - { - LL_ERRS() << "Failed updating WindLight fullscreen sky geometry." << LL_ENDL; - } - - *vertices++ = LLVector3(-1.0f, -1.0f, 0.0f); - *vertices++ = LLVector3( 1.0f, -1.0f, 0.0f); - *vertices++ = LLVector3(-1.0f, 1.0f, 0.0f); - *vertices++ = LLVector3( 1.0f, 1.0f, 0.0f); - - *texCoords++ = LLVector2(0.0f, 0.0f); - *texCoords++ = LLVector2(1.0f, 0.0f); - *texCoords++ = LLVector2(0.0f, 1.0f); - *texCoords++ = LLVector2(1.0f, 1.0f); - - *indices++ = 0; - *indices++ = 1; - *indices++ = 2; - *indices++ = 1; - *indices++ = 3; - *indices++ = 2; - - mFsSkyVerts->unmapBuffer(); - } - - { - const F32 dome_radius = LLEnvironment::instance().getCurrentSky()->getDomeRadius(); - - const U32 max_buffer_bytes = gSavedSettings.getS32("RenderMaxVBOSize")*1024; - const U32 data_mask = LLDrawPoolWLSky::SKY_VERTEX_DATA_MASK; - const U32 max_verts = max_buffer_bytes / LLVertexBuffer::calcVertexSize(data_mask); - - const U32 total_stacks = getNumStacks(); - - const U32 verts_per_stack = getNumSlices(); - - // each seg has to have one more row of verts than it has stacks - // then round down - const U32 stacks_per_seg = (max_verts - verts_per_stack) / verts_per_stack; - - // round up to a whole number of segments - const U32 strips_segments = (total_stacks+stacks_per_seg-1) / stacks_per_seg; - - mStripsVerts.resize(strips_segments, NULL); - -#if RELEASE_SHOW_DEBUG - LL_INFOS() << "WL Skydome strips in " << strips_segments << " batches." << LL_ENDL; - - LLTimer timer; - timer.start(); -#endif - - for (U32 i = 0; i < strips_segments ;++i) - { - LLVertexBuffer * segment = new LLVertexBuffer(LLDrawPoolWLSky::SKY_VERTEX_DATA_MASK); - mStripsVerts[i] = segment; - - U32 num_stacks_this_seg = stacks_per_seg; - if ((i == strips_segments - 1) && (total_stacks % stacks_per_seg) != 0) - { - // for the last buffer only allocate what we'll use - num_stacks_this_seg = total_stacks % stacks_per_seg; - } - - // figure out what range of the sky we're filling - const U32 begin_stack = i * stacks_per_seg; - const U32 end_stack = begin_stack + num_stacks_this_seg; - llassert(end_stack <= total_stacks); - - const U32 num_verts_this_seg = verts_per_stack * (num_stacks_this_seg+1); - llassert(num_verts_this_seg <= max_verts); - - const U32 num_indices_this_seg = 1+num_stacks_this_seg*(2+2*verts_per_stack); - llassert(num_indices_this_seg * sizeof(U16) <= max_buffer_bytes); - - bool allocated = segment->allocateBuffer(num_verts_this_seg, num_indices_this_seg); -#if RELEASE_SHOW_WARNS - if( !allocated ) - { - LL_WARNS() << "Failed to allocate Vertex Buffer on update to " - << num_verts_this_seg << " vertices and " - << num_indices_this_seg << " indices" << LL_ENDL; - } -#else - (void) allocated; -#endif - - // lock the buffer - bool success = segment->getVertexStrider(vertices) - && segment->getTexCoord0Strider(texCoords) - && segment->getIndexStrider(indices); - -#if RELEASE_SHOW_DEBUG - if(!success) - { - LL_ERRS() << "Failed updating WindLight sky geometry." << LL_ENDL; - } -#else - (void) success; -#endif - - // fill it - buildStripsBuffer(begin_stack, end_stack, vertices, texCoords, indices, dome_radius, verts_per_stack, total_stacks); - - // and unlock the buffer - segment->unmapBuffer(); - } - -#if RELEASE_SHOW_DEBUG - LL_INFOS() << "completed in " << llformat("%.2f", timer.getElapsedTimeF32().value()) << "seconds" << LL_ENDL; -#endif - } - - updateStarColors(); - updateStarGeometry(drawable); - - LLPipeline::sCompiles++; - - return true; -} - -void LLVOWLSky::drawStars(void) -{ - // render the stars as a sphere centered at viewer camera - if (mStarsVerts.notNull()) - { - mStarsVerts->setBuffer(); - mStarsVerts->drawArrays(LLRender::TRIANGLES, 0, getStarsNumVerts()*4); - } -} - -void LLVOWLSky::drawFsSky(void) -{ - if (mFsSkyVerts.isNull()) - { - updateGeometry(mDrawable); - } - - LLGLDisable disable_blend(GL_BLEND); - - mFsSkyVerts->setBuffer(); - mFsSkyVerts->drawRange(LLRender::TRIANGLES, 0, mFsSkyVerts->getNumVerts() - 1, mFsSkyVerts->getNumIndices(), 0); - gPipeline.addTrianglesDrawn(mFsSkyVerts->getNumIndices()); - LLVertexBuffer::unbind(); -} - -void LLVOWLSky::drawDome(void) -{ - if (mStripsVerts.empty()) - { - updateGeometry(mDrawable); - } - - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); - - std::vector< LLPointer >::const_iterator strips_vbo_iter, end_strips; - end_strips = mStripsVerts.end(); - for(strips_vbo_iter = mStripsVerts.begin(); strips_vbo_iter != end_strips; ++strips_vbo_iter) - { - LLVertexBuffer * strips_segment = strips_vbo_iter->get(); - - strips_segment->setBuffer(); - - strips_segment->drawRange( - LLRender::TRIANGLE_STRIP, - 0, strips_segment->getNumVerts()-1, strips_segment->getNumIndices(), - 0); - gPipeline.addTrianglesDrawn(strips_segment->getNumIndices()); - } - - LLVertexBuffer::unbind(); -} - -void LLVOWLSky::initStars() -{ - const F32 DISTANCE_TO_STARS = LLEnvironment::instance().getCurrentSky()->getDomeRadius(); - - // Initialize star map - mStarVertices.resize(getStarsNumVerts()); - mStarColors.resize(getStarsNumVerts()); - mStarIntensities.resize(getStarsNumVerts()); - - std::vector::iterator v_p = mStarVertices.begin(); - std::vector::iterator v_c = mStarColors.begin(); - std::vector::iterator v_i = mStarIntensities.begin(); - - U32 i; - - for (i = 0; i < getStarsNumVerts(); ++i) - { - v_p->mV[VX] = ll_frand() - 0.5f; - v_p->mV[VY] = ll_frand() - 0.5f; - - // we only want stars on the top half of the dome! - - v_p->mV[VZ] = ll_frand()/2.f; - - v_p->normVec(); - *v_p *= DISTANCE_TO_STARS; - *v_i = llmin((F32)pow(ll_frand(),2.f) + 0.1f, 1.f); - v_c->mV[VRED] = 0.75f + ll_frand() * 0.25f ; - v_c->mV[VGREEN] = 1.f ; - v_c->mV[VBLUE] = 0.75f + ll_frand() * 0.25f ; - v_c->mV[VALPHA] = 1.f; - v_c->clamp(); - v_p++; - v_c++; - v_i++; - } -} - -void LLVOWLSky::buildStripsBuffer(U32 begin_stack, - U32 end_stack, - LLStrider & vertices, - LLStrider & texCoords, - LLStrider & indices, - const F32 dome_radius, - const U32& num_slices, - const U32& num_stacks) -{ - U32 i, j; - F32 phi0, theta, x0, y0, z0; - const F32 reciprocal_num_stacks = 1.f / num_stacks; - - llassert(end_stack <= num_stacks); - - // stacks are iterated one-indexed since phi(0) was handled by the fan above -#if NEW_TESS - for(i = begin_stack; i <= end_stack; ++i) -#else - for(i = begin_stack + 1; i <= end_stack+1; ++i) -#endif - { - phi0 = calcPhi(i, reciprocal_num_stacks); - - for(j = 0; j < num_slices; ++j) - { - theta = F_TWO_PI * (float(j) / float(num_slices)); - - // standard transformation from spherical to - // rectangular coordinates - x0 = sin(phi0) * cos(theta); - y0 = cos(phi0); - z0 = sin(phi0) * sin(theta); - -#if NEW_TESS - *vertices++ = LLVector3(x0 * dome_radius, y0 * dome_radius, z0 * dome_radius); -#else - if (i == num_stacks-2) - { - *vertices++ = LLVector3(x0*dome_radius, y0*dome_radius-1024.f*2.f, z0*dome_radius); - } - else if (i == num_stacks-1) - { - *vertices++ = LLVector3(0, y0*dome_radius-1024.f*2.f, 0); - } - else - { - *vertices++ = LLVector3(x0 * dome_radius, y0 * dome_radius, z0 * dome_radius); - } -#endif - - // generate planar uv coordinates - // note: x and z are transposed in order for things to animate - // correctly in the global coordinate system where +x is east and - // +y is north - *texCoords++ = LLVector2((-z0 + 1.f) / 2.f, (-x0 + 1.f) / 2.f); - } - } - - //build triangle strip... - *indices++ = 0 ; - - S32 k = 0 ; - for(i = 1; i <= end_stack - begin_stack; ++i) - { - *indices++ = i * num_slices + k ; - - k = (k+1) % num_slices ; - for(j = 0; j < num_slices ; ++j) - { - *indices++ = (i-1) * num_slices + k ; - *indices++ = i * num_slices + k ; - - k = (k+1) % num_slices ; - } - - if((--k) < 0) - { - k = num_slices - 1 ; - } - - *indices++ = i * num_slices + k ; - } -} - -void LLVOWLSky::updateStarColors() -{ - std::vector::iterator v_c = mStarColors.begin(); - std::vector::iterator v_i = mStarIntensities.begin(); - std::vector::iterator v_p = mStarVertices.begin(); - - const F32 var = 0.15f; - const F32 min = 0.5f; //0.75f; - //const F32 sunclose_max = 0.6f; - //const F32 sunclose_range = 1 - sunclose_max; - - //F32 below_horizon = - llmin(0.0f, gSky.mVOSkyp->getToSunLast().mV[2]); - //F32 brightness_factor = llmin(1.0f, below_horizon * 20); - - static S32 swap = 0; - swap++; - - if ((swap % 2) == 1) - { - F32 intensity; // max intensity of each star - U32 x; - for (x = 0; x < getStarsNumVerts(); ++x) - { - //F32 sundir_factor = 1; - LLVector3 tostar = *v_p; - tostar.normVec(); - //const F32 how_close_to_sun = tostar * gSky.mVOSkyp->getToSunLast(); - //if (how_close_to_sun > sunclose_max) - //{ - // sundir_factor = (1 - how_close_to_sun) / sunclose_range; - //} - intensity = *(v_i); - F32 alpha = v_c->mV[VALPHA] + (ll_frand() - 0.5f) * var * intensity; - if (alpha < min * intensity) - { - alpha = min * intensity; - } - if (alpha > intensity) - { - alpha = intensity; - } - //alpha *= brightness_factor * sundir_factor; - - alpha = llclamp(alpha, 0.f, 1.f); - v_c->mV[VALPHA] = alpha; - v_c++; - v_i++; - v_p++; - } - } -} - -bool LLVOWLSky::updateStarGeometry(LLDrawable *drawable) -{ - LLStrider verticesp; - LLStrider colorsp; - LLStrider texcoordsp; - - if (mStarsVerts.isNull()) - { - mStarsVerts = new LLVertexBuffer(LLDrawPoolWLSky::STAR_VERTEX_DATA_MASK); - if (!mStarsVerts->allocateBuffer(getStarsNumVerts()*6, 0)) - { - LL_WARNS() << "Failed to allocate Vertex Buffer for Sky to " << getStarsNumVerts() * 6 << " vertices" << LL_ENDL; - } - } - - bool success = mStarsVerts->getVertexStrider(verticesp) - && mStarsVerts->getColorStrider(colorsp) - && mStarsVerts->getTexCoord0Strider(texcoordsp); - - if(!success) - { - LL_ERRS() << "Failed updating star geometry." << LL_ENDL; - } - - // *TODO: fix LLStrider with a real prefix increment operator so it can be - // used as a model of OutputIterator. -Brad - // std::copy(mStarVertices.begin(), mStarVertices.end(), verticesp); - - if (mStarVertices.size() < getStarsNumVerts()) - { - LL_ERRS() << "Star reference geometry insufficient." << LL_ENDL; - } - - for (U32 vtx = 0; vtx < getStarsNumVerts(); ++vtx) - { - LLVector3 at = mStarVertices[vtx]; - at.normVec(); - LLVector3 left = at%LLVector3(0,0,1); - LLVector3 up = at%left; - - F32 sc = 16.0f + (ll_frand() * 20.0f); - left *= sc; - up *= sc; - - *(verticesp++) = mStarVertices[vtx]; - *(verticesp++) = mStarVertices[vtx]+up; - *(verticesp++) = mStarVertices[vtx]+left+up; - *(verticesp++) = mStarVertices[vtx]; - *(verticesp++) = mStarVertices[vtx]+left+up; - *(verticesp++) = mStarVertices[vtx]+left; - - *(texcoordsp++) = LLVector2(1,0); - *(texcoordsp++) = LLVector2(1,1); - *(texcoordsp++) = LLVector2(0,1); - *(texcoordsp++) = LLVector2(1,0); - *(texcoordsp++) = LLVector2(0,1); - *(texcoordsp++) = LLVector2(0,0); - - *(colorsp++) = LLColor4U(mStarColors[vtx]); - *(colorsp++) = LLColor4U(mStarColors[vtx]); - *(colorsp++) = LLColor4U(mStarColors[vtx]); - *(colorsp++) = LLColor4U(mStarColors[vtx]); - *(colorsp++) = LLColor4U(mStarColors[vtx]); - *(colorsp++) = LLColor4U(mStarColors[vtx]); - } - - mStarsVerts->unmapBuffer(); - return true; -} +/** + * @file llvowlsky.cpp + * @brief LLVOWLSky class implementation + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "pipeline.h" + +#include "llvowlsky.h" +#include "llsky.h" +#include "lldrawpoolwlsky.h" +#include "llface.h" +#include "llviewercontrol.h" +#include "llenvironment.h" +#include "llsettingssky.h" + +constexpr U32 MIN_SKY_DETAIL = 8; +constexpr U32 MAX_SKY_DETAIL = 180; + +inline U32 LLVOWLSky::getNumStacks(void) +{ + return llmin(MAX_SKY_DETAIL, llmax(MIN_SKY_DETAIL, gSavedSettings.getU32("WLSkyDetail"))); +} + +inline U32 LLVOWLSky::getNumSlices(void) +{ + return 2 * llmin(MAX_SKY_DETAIL, llmax(MIN_SKY_DETAIL, gSavedSettings.getU32("WLSkyDetail"))); +} + +inline U32 LLVOWLSky::getStripsNumVerts(void) +{ + return (getNumStacks() - 1) * getNumSlices(); +} + +inline U32 LLVOWLSky::getStripsNumIndices(void) +{ + return 2 * ((getNumStacks() - 2) * (getNumSlices() + 1)) + 1 ; +} + +inline U32 LLVOWLSky::getStarsNumVerts(void) +{ + return 1000; +} + +inline U32 LLVOWLSky::getStarsNumIndices(void) +{ + return 1000; +} + +LLVOWLSky::LLVOWLSky(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp) + : LLStaticViewerObject(id, pcode, regionp, true) +{ + initStars(); +} + +void LLVOWLSky::idleUpdate(LLAgent &agent, const F64 &time) +{ + +} + +bool LLVOWLSky::isActive(void) const +{ + return false; +} + +LLDrawable * LLVOWLSky::createDrawable(LLPipeline * pipeline) +{ + pipeline->allocDrawable(this); + + //LLDrawPoolWLSky *poolp = static_cast( + gPipeline.getPool(LLDrawPool::POOL_WL_SKY); + + mDrawable->setRenderType(LLPipeline::RENDER_TYPE_WL_SKY); + + return mDrawable; +} + +// a tiny helper function for controlling the sky dome tesselation. +inline F32 calcPhi(const U32 &i, const F32 &reciprocal_num_stacks) +{ + // Calc: PI/8 * 1-((1-t^4)*(1-t^4)) { 0 vertices; + LLStrider texCoords; + LLStrider indices; + + if (mFsSkyVerts.isNull()) + { + mFsSkyVerts = new LLVertexBuffer(LLDrawPoolWLSky::ADV_ATMO_SKY_VERTEX_DATA_MASK); + + if (!mFsSkyVerts->allocateBuffer(4, 6)) + { + LL_WARNS() << "Failed to allocate Vertex Buffer on full screen sky update" << LL_ENDL; + } + + bool success = mFsSkyVerts->getVertexStrider(vertices) + && mFsSkyVerts->getTexCoord0Strider(texCoords) + && mFsSkyVerts->getIndexStrider(indices); + + if(!success) + { + LL_ERRS() << "Failed updating WindLight fullscreen sky geometry." << LL_ENDL; + } + + *vertices++ = LLVector3(-1.0f, -1.0f, 0.0f); + *vertices++ = LLVector3( 1.0f, -1.0f, 0.0f); + *vertices++ = LLVector3(-1.0f, 1.0f, 0.0f); + *vertices++ = LLVector3( 1.0f, 1.0f, 0.0f); + + *texCoords++ = LLVector2(0.0f, 0.0f); + *texCoords++ = LLVector2(1.0f, 0.0f); + *texCoords++ = LLVector2(0.0f, 1.0f); + *texCoords++ = LLVector2(1.0f, 1.0f); + + *indices++ = 0; + *indices++ = 1; + *indices++ = 2; + *indices++ = 1; + *indices++ = 3; + *indices++ = 2; + + mFsSkyVerts->unmapBuffer(); + } + + { + const F32 dome_radius = LLEnvironment::instance().getCurrentSky()->getDomeRadius(); + + const U32 max_buffer_bytes = gSavedSettings.getS32("RenderMaxVBOSize")*1024; + const U32 data_mask = LLDrawPoolWLSky::SKY_VERTEX_DATA_MASK; + const U32 max_verts = max_buffer_bytes / LLVertexBuffer::calcVertexSize(data_mask); + + const U32 total_stacks = getNumStacks(); + + const U32 verts_per_stack = getNumSlices(); + + // each seg has to have one more row of verts than it has stacks + // then round down + const U32 stacks_per_seg = (max_verts - verts_per_stack) / verts_per_stack; + + // round up to a whole number of segments + const U32 strips_segments = (total_stacks+stacks_per_seg-1) / stacks_per_seg; + + mStripsVerts.resize(strips_segments, NULL); + +#if RELEASE_SHOW_DEBUG + LL_INFOS() << "WL Skydome strips in " << strips_segments << " batches." << LL_ENDL; + + LLTimer timer; + timer.start(); +#endif + + for (U32 i = 0; i < strips_segments ;++i) + { + LLVertexBuffer * segment = new LLVertexBuffer(LLDrawPoolWLSky::SKY_VERTEX_DATA_MASK); + mStripsVerts[i] = segment; + + U32 num_stacks_this_seg = stacks_per_seg; + if ((i == strips_segments - 1) && (total_stacks % stacks_per_seg) != 0) + { + // for the last buffer only allocate what we'll use + num_stacks_this_seg = total_stacks % stacks_per_seg; + } + + // figure out what range of the sky we're filling + const U32 begin_stack = i * stacks_per_seg; + const U32 end_stack = begin_stack + num_stacks_this_seg; + llassert(end_stack <= total_stacks); + + const U32 num_verts_this_seg = verts_per_stack * (num_stacks_this_seg+1); + llassert(num_verts_this_seg <= max_verts); + + const U32 num_indices_this_seg = 1+num_stacks_this_seg*(2+2*verts_per_stack); + llassert(num_indices_this_seg * sizeof(U16) <= max_buffer_bytes); + + bool allocated = segment->allocateBuffer(num_verts_this_seg, num_indices_this_seg); +#if RELEASE_SHOW_WARNS + if( !allocated ) + { + LL_WARNS() << "Failed to allocate Vertex Buffer on update to " + << num_verts_this_seg << " vertices and " + << num_indices_this_seg << " indices" << LL_ENDL; + } +#else + (void) allocated; +#endif + + // lock the buffer + bool success = segment->getVertexStrider(vertices) + && segment->getTexCoord0Strider(texCoords) + && segment->getIndexStrider(indices); + +#if RELEASE_SHOW_DEBUG + if(!success) + { + LL_ERRS() << "Failed updating WindLight sky geometry." << LL_ENDL; + } +#else + (void) success; +#endif + + // fill it + buildStripsBuffer(begin_stack, end_stack, vertices, texCoords, indices, dome_radius, verts_per_stack, total_stacks); + + // and unlock the buffer + segment->unmapBuffer(); + } + +#if RELEASE_SHOW_DEBUG + LL_INFOS() << "completed in " << llformat("%.2f", timer.getElapsedTimeF32().value()) << "seconds" << LL_ENDL; +#endif + } + + updateStarColors(); + updateStarGeometry(drawable); + + LLPipeline::sCompiles++; + + return true; +} + +void LLVOWLSky::drawStars(void) +{ + // render the stars as a sphere centered at viewer camera + if (mStarsVerts.notNull()) + { + mStarsVerts->setBuffer(); + mStarsVerts->drawArrays(LLRender::TRIANGLES, 0, getStarsNumVerts()*4); + } +} + +void LLVOWLSky::drawFsSky(void) +{ + if (mFsSkyVerts.isNull()) + { + updateGeometry(mDrawable); + } + + LLGLDisable disable_blend(GL_BLEND); + + mFsSkyVerts->setBuffer(); + mFsSkyVerts->drawRange(LLRender::TRIANGLES, 0, mFsSkyVerts->getNumVerts() - 1, mFsSkyVerts->getNumIndices(), 0); + gPipeline.addTrianglesDrawn(mFsSkyVerts->getNumIndices()); + LLVertexBuffer::unbind(); +} + +void LLVOWLSky::drawDome(void) +{ + if (mStripsVerts.empty()) + { + updateGeometry(mDrawable); + } + + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); + + std::vector< LLPointer >::const_iterator strips_vbo_iter, end_strips; + end_strips = mStripsVerts.end(); + for(strips_vbo_iter = mStripsVerts.begin(); strips_vbo_iter != end_strips; ++strips_vbo_iter) + { + LLVertexBuffer * strips_segment = strips_vbo_iter->get(); + + strips_segment->setBuffer(); + + strips_segment->drawRange( + LLRender::TRIANGLE_STRIP, + 0, strips_segment->getNumVerts()-1, strips_segment->getNumIndices(), + 0); + gPipeline.addTrianglesDrawn(strips_segment->getNumIndices()); + } + + LLVertexBuffer::unbind(); +} + +void LLVOWLSky::initStars() +{ + const F32 DISTANCE_TO_STARS = LLEnvironment::instance().getCurrentSky()->getDomeRadius(); + + // Initialize star map + mStarVertices.resize(getStarsNumVerts()); + mStarColors.resize(getStarsNumVerts()); + mStarIntensities.resize(getStarsNumVerts()); + + std::vector::iterator v_p = mStarVertices.begin(); + std::vector::iterator v_c = mStarColors.begin(); + std::vector::iterator v_i = mStarIntensities.begin(); + + U32 i; + + for (i = 0; i < getStarsNumVerts(); ++i) + { + v_p->mV[VX] = ll_frand() - 0.5f; + v_p->mV[VY] = ll_frand() - 0.5f; + + // we only want stars on the top half of the dome! + + v_p->mV[VZ] = ll_frand()/2.f; + + v_p->normVec(); + *v_p *= DISTANCE_TO_STARS; + *v_i = llmin((F32)pow(ll_frand(),2.f) + 0.1f, 1.f); + v_c->mV[VRED] = 0.75f + ll_frand() * 0.25f ; + v_c->mV[VGREEN] = 1.f ; + v_c->mV[VBLUE] = 0.75f + ll_frand() * 0.25f ; + v_c->mV[VALPHA] = 1.f; + v_c->clamp(); + v_p++; + v_c++; + v_i++; + } +} + +void LLVOWLSky::buildStripsBuffer(U32 begin_stack, + U32 end_stack, + LLStrider & vertices, + LLStrider & texCoords, + LLStrider & indices, + const F32 dome_radius, + const U32& num_slices, + const U32& num_stacks) +{ + U32 i, j; + F32 phi0, theta, x0, y0, z0; + const F32 reciprocal_num_stacks = 1.f / num_stacks; + + llassert(end_stack <= num_stacks); + + // stacks are iterated one-indexed since phi(0) was handled by the fan above +#if NEW_TESS + for(i = begin_stack; i <= end_stack; ++i) +#else + for(i = begin_stack + 1; i <= end_stack+1; ++i) +#endif + { + phi0 = calcPhi(i, reciprocal_num_stacks); + + for(j = 0; j < num_slices; ++j) + { + theta = F_TWO_PI * (float(j) / float(num_slices)); + + // standard transformation from spherical to + // rectangular coordinates + x0 = sin(phi0) * cos(theta); + y0 = cos(phi0); + z0 = sin(phi0) * sin(theta); + +#if NEW_TESS + *vertices++ = LLVector3(x0 * dome_radius, y0 * dome_radius, z0 * dome_radius); +#else + if (i == num_stacks-2) + { + *vertices++ = LLVector3(x0*dome_radius, y0*dome_radius-1024.f*2.f, z0*dome_radius); + } + else if (i == num_stacks-1) + { + *vertices++ = LLVector3(0, y0*dome_radius-1024.f*2.f, 0); + } + else + { + *vertices++ = LLVector3(x0 * dome_radius, y0 * dome_radius, z0 * dome_radius); + } +#endif + + // generate planar uv coordinates + // note: x and z are transposed in order for things to animate + // correctly in the global coordinate system where +x is east and + // +y is north + *texCoords++ = LLVector2((-z0 + 1.f) / 2.f, (-x0 + 1.f) / 2.f); + } + } + + //build triangle strip... + *indices++ = 0 ; + + S32 k = 0 ; + for(i = 1; i <= end_stack - begin_stack; ++i) + { + *indices++ = i * num_slices + k ; + + k = (k+1) % num_slices ; + for(j = 0; j < num_slices ; ++j) + { + *indices++ = (i-1) * num_slices + k ; + *indices++ = i * num_slices + k ; + + k = (k+1) % num_slices ; + } + + if((--k) < 0) + { + k = num_slices - 1 ; + } + + *indices++ = i * num_slices + k ; + } +} + +void LLVOWLSky::updateStarColors() +{ + std::vector::iterator v_c = mStarColors.begin(); + std::vector::iterator v_i = mStarIntensities.begin(); + std::vector::iterator v_p = mStarVertices.begin(); + + const F32 var = 0.15f; + const F32 min = 0.5f; //0.75f; + //const F32 sunclose_max = 0.6f; + //const F32 sunclose_range = 1 - sunclose_max; + + //F32 below_horizon = - llmin(0.0f, gSky.mVOSkyp->getToSunLast().mV[2]); + //F32 brightness_factor = llmin(1.0f, below_horizon * 20); + + static S32 swap = 0; + swap++; + + if ((swap % 2) == 1) + { + F32 intensity; // max intensity of each star + U32 x; + for (x = 0; x < getStarsNumVerts(); ++x) + { + //F32 sundir_factor = 1; + LLVector3 tostar = *v_p; + tostar.normVec(); + //const F32 how_close_to_sun = tostar * gSky.mVOSkyp->getToSunLast(); + //if (how_close_to_sun > sunclose_max) + //{ + // sundir_factor = (1 - how_close_to_sun) / sunclose_range; + //} + intensity = *(v_i); + F32 alpha = v_c->mV[VALPHA] + (ll_frand() - 0.5f) * var * intensity; + if (alpha < min * intensity) + { + alpha = min * intensity; + } + if (alpha > intensity) + { + alpha = intensity; + } + //alpha *= brightness_factor * sundir_factor; + + alpha = llclamp(alpha, 0.f, 1.f); + v_c->mV[VALPHA] = alpha; + v_c++; + v_i++; + v_p++; + } + } +} + +bool LLVOWLSky::updateStarGeometry(LLDrawable *drawable) +{ + LLStrider verticesp; + LLStrider colorsp; + LLStrider texcoordsp; + + if (mStarsVerts.isNull()) + { + mStarsVerts = new LLVertexBuffer(LLDrawPoolWLSky::STAR_VERTEX_DATA_MASK); + if (!mStarsVerts->allocateBuffer(getStarsNumVerts()*6, 0)) + { + LL_WARNS() << "Failed to allocate Vertex Buffer for Sky to " << getStarsNumVerts() * 6 << " vertices" << LL_ENDL; + } + } + + bool success = mStarsVerts->getVertexStrider(verticesp) + && mStarsVerts->getColorStrider(colorsp) + && mStarsVerts->getTexCoord0Strider(texcoordsp); + + if(!success) + { + LL_ERRS() << "Failed updating star geometry." << LL_ENDL; + } + + // *TODO: fix LLStrider with a real prefix increment operator so it can be + // used as a model of OutputIterator. -Brad + // std::copy(mStarVertices.begin(), mStarVertices.end(), verticesp); + + if (mStarVertices.size() < getStarsNumVerts()) + { + LL_ERRS() << "Star reference geometry insufficient." << LL_ENDL; + } + + for (U32 vtx = 0; vtx < getStarsNumVerts(); ++vtx) + { + LLVector3 at = mStarVertices[vtx]; + at.normVec(); + LLVector3 left = at%LLVector3(0,0,1); + LLVector3 up = at%left; + + F32 sc = 16.0f + (ll_frand() * 20.0f); + left *= sc; + up *= sc; + + *(verticesp++) = mStarVertices[vtx]; + *(verticesp++) = mStarVertices[vtx]+up; + *(verticesp++) = mStarVertices[vtx]+left+up; + *(verticesp++) = mStarVertices[vtx]; + *(verticesp++) = mStarVertices[vtx]+left+up; + *(verticesp++) = mStarVertices[vtx]+left; + + *(texcoordsp++) = LLVector2(1,0); + *(texcoordsp++) = LLVector2(1,1); + *(texcoordsp++) = LLVector2(0,1); + *(texcoordsp++) = LLVector2(1,0); + *(texcoordsp++) = LLVector2(0,1); + *(texcoordsp++) = LLVector2(0,0); + + *(colorsp++) = LLColor4U(mStarColors[vtx]); + *(colorsp++) = LLColor4U(mStarColors[vtx]); + *(colorsp++) = LLColor4U(mStarColors[vtx]); + *(colorsp++) = LLColor4U(mStarColors[vtx]); + *(colorsp++) = LLColor4U(mStarColors[vtx]); + *(colorsp++) = LLColor4U(mStarColors[vtx]); + } + + mStarsVerts->unmapBuffer(); + return true; +} diff --git a/indra/newview/llvowlsky.h b/indra/newview/llvowlsky.h index 8205f07f6a..13105c4b0d 100644 --- a/indra/newview/llvowlsky.h +++ b/indra/newview/llvowlsky.h @@ -1,90 +1,90 @@ -/** - * @file llvowlsky.h - * @brief LLVOWLSky class definition - * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_VOWLSKY_H -#define LL_VOWLSKY_H - -#include "llviewerobject.h" - -class LLVOWLSky : public LLStaticViewerObject { -private: - inline static U32 getNumStacks(void); - inline static U32 getNumSlices(void); - inline static U32 getStripsNumVerts(void); - inline static U32 getStripsNumIndices(void); - inline static U32 getStarsNumVerts(void); - inline static U32 getStarsNumIndices(void); - -public: - LLVOWLSky(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); - - /*virtual*/ void idleUpdate(LLAgent &agent, const F64 &time); - /*virtual*/ bool isActive(void) const; - /*virtual*/ LLDrawable * createDrawable(LLPipeline *pipeline); - /*virtual*/ bool updateGeometry(LLDrawable *drawable); - - void drawStars(void); - void drawDome(void); - void drawFsSky(void); // fullscreen sky for advanced atmo - void resetVertexBuffers(void); - - void cleanupGL(); - void restoreGL(); - -private: - - // helper function for initializing the stars. - void initStars(); - - // helper function for building the strips vertex buffer. - // note begin_stack and end_stack follow stl iterator conventions, - // begin_stack is the first stack to be included, end_stack is the first - // stack not to be included. - static void buildStripsBuffer(U32 begin_stack, U32 end_stack, - LLStrider & vertices, - LLStrider & texCoords, - LLStrider & indices, - const F32 RADIUS, - const U32& num_slices, - const U32& num_stacks); - - // helper function for updating the stars colors. - void updateStarColors(); - - // helper function for updating the stars geometry. - bool updateStarGeometry(LLDrawable *drawable); - -private: - LLPointer mFsSkyVerts; - std::vector< LLPointer > mStripsVerts; - LLPointer mStarsVerts; - - std::vector mStarVertices; // Star verticies - std::vector mStarColors; // Star colors - std::vector mStarIntensities; // Star intensities -}; - -#endif // LL_VOWLSKY_H +/** + * @file llvowlsky.h + * @brief LLVOWLSky class definition + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_VOWLSKY_H +#define LL_VOWLSKY_H + +#include "llviewerobject.h" + +class LLVOWLSky : public LLStaticViewerObject { +private: + inline static U32 getNumStacks(void); + inline static U32 getNumSlices(void); + inline static U32 getStripsNumVerts(void); + inline static U32 getStripsNumIndices(void); + inline static U32 getStarsNumVerts(void); + inline static U32 getStarsNumIndices(void); + +public: + LLVOWLSky(const LLUUID &id, const LLPCode pcode, LLViewerRegion *regionp); + + /*virtual*/ void idleUpdate(LLAgent &agent, const F64 &time); + /*virtual*/ bool isActive(void) const; + /*virtual*/ LLDrawable * createDrawable(LLPipeline *pipeline); + /*virtual*/ bool updateGeometry(LLDrawable *drawable); + + void drawStars(void); + void drawDome(void); + void drawFsSky(void); // fullscreen sky for advanced atmo + void resetVertexBuffers(void); + + void cleanupGL(); + void restoreGL(); + +private: + + // helper function for initializing the stars. + void initStars(); + + // helper function for building the strips vertex buffer. + // note begin_stack and end_stack follow stl iterator conventions, + // begin_stack is the first stack to be included, end_stack is the first + // stack not to be included. + static void buildStripsBuffer(U32 begin_stack, U32 end_stack, + LLStrider & vertices, + LLStrider & texCoords, + LLStrider & indices, + const F32 RADIUS, + const U32& num_slices, + const U32& num_stacks); + + // helper function for updating the stars colors. + void updateStarColors(); + + // helper function for updating the stars geometry. + bool updateStarGeometry(LLDrawable *drawable); + +private: + LLPointer mFsSkyVerts; + std::vector< LLPointer > mStripsVerts; + LLPointer mStarsVerts; + + std::vector mStarVertices; // Star verticies + std::vector mStarColors; // Star colors + std::vector mStarIntensities; // Star intensities +}; + +#endif // LL_VOWLSKY_H diff --git a/indra/newview/llwearableitemslist.cpp b/indra/newview/llwearableitemslist.cpp index 0c02076766..a68ad0029f 100644 --- a/indra/newview/llwearableitemslist.cpp +++ b/indra/newview/llwearableitemslist.cpp @@ -1,1149 +1,1149 @@ -/** - * @file llwearableitemslist.cpp - * @brief A flat list of wearable items. - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llwearableitemslist.h" - -#include "lliconctrl.h" -#include "llmenugl.h" // for LLContextMenu - -#include "llagentwearables.h" -#include "llappearancemgr.h" -#include "llinventoryfunctions.h" -#include "llinventoryicon.h" -#include "llgesturemgr.h" -#include "lltransutil.h" -#include "llviewerattachmenu.h" -#include "llviewermenu.h" -#include "llvoavatarself.h" - -class LLFindOutfitItems : public LLInventoryCollectFunctor -{ -public: - LLFindOutfitItems() {} - virtual ~LLFindOutfitItems() {} - virtual bool operator()(LLInventoryCategory* cat, - LLInventoryItem* item); -}; - -bool LLFindOutfitItems::operator()(LLInventoryCategory* cat, - LLInventoryItem* item) -{ - if(item) - { - if((item->getType() == LLAssetType::AT_CLOTHING) - || (item->getType() == LLAssetType::AT_BODYPART) - || (item->getType() == LLAssetType::AT_OBJECT) - || (item->getType() == LLAssetType::AT_GESTURE)) - { - return true; - } - } - return false; -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -void LLPanelWearableListItem::onMouseEnter(S32 x, S32 y, MASK mask) -{ - LLPanelInventoryListItemBase::onMouseEnter(x, y, mask); - setWidgetsVisible(true); - reshapeWidgets(); -} - -void LLPanelWearableListItem::onMouseLeave(S32 x, S32 y, MASK mask) -{ - LLPanelInventoryListItemBase::onMouseLeave(x, y, mask); - setWidgetsVisible(false); - reshapeWidgets(); -} - -LLPanelWearableListItem::LLPanelWearableListItem(LLViewerInventoryItem* item, const LLPanelWearableListItem::Params& params) -: LLPanelInventoryListItemBase(item, params) -{ -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -static LLWidgetNameRegistry::StaticRegistrar sRegisterPanelWearableOutfitItem(&typeid(LLPanelWearableOutfitItem::Params), "wearable_outfit_list_item"); - -LLPanelWearableOutfitItem::Params::Params() -: add_btn("add_btn"), - remove_btn("remove_btn") -{ -} - -bool LLPanelWearableOutfitItem::postBuild() -{ - LLPanelWearableListItem::postBuild(); - - if(mShowWidgets) - { - addWidgetToRightSide("add_wearable"); - addWidgetToRightSide("remove_wearable"); - - childSetAction("add_wearable", boost::bind(&LLPanelWearableOutfitItem::onAddWearable, this)); - childSetAction("remove_wearable", boost::bind(&LLPanelWearableOutfitItem::onRemoveWearable, this)); - - setWidgetsVisible(false); - reshapeWidgets(); - } - return true; -} - -bool LLPanelWearableOutfitItem::handleDoubleClick(S32 x, S32 y, MASK mask) -{ - if(!mShowWidgets) - { - return LLPanelWearableListItem::handleDoubleClick(x, y, mask); - } - - if(LLAppearanceMgr::instance().isLinkedInCOF(mInventoryItemUUID)) - { - onRemoveWearable(); - } - else - { - onAddWearable(); - } - return true; -} - -void LLPanelWearableOutfitItem::onAddWearable() -{ - setWidgetsVisible(false); - reshapeWidgets(); - LLAppearanceMgr::instance().wearItemOnAvatar(mInventoryItemUUID, true, false); -} - -void LLPanelWearableOutfitItem::onRemoveWearable() -{ - setWidgetsVisible(false); - reshapeWidgets(); - LLAppearanceMgr::instance().removeItemFromAvatar(mInventoryItemUUID); -} - -// static -LLPanelWearableOutfitItem* LLPanelWearableOutfitItem::create(LLViewerInventoryItem* item, - bool worn_indication_enabled, - bool show_widgets) -{ - LLPanelWearableOutfitItem* list_item = NULL; - if (item) - { - const LLPanelWearableOutfitItem::Params& params = LLUICtrlFactory::getDefaultParams(); - - list_item = new LLPanelWearableOutfitItem(item, worn_indication_enabled, params, show_widgets); - list_item->initFromParams(params); - list_item->postBuild(); - } - return list_item; -} - -LLPanelWearableOutfitItem::LLPanelWearableOutfitItem(LLViewerInventoryItem* item, - bool worn_indication_enabled, - const LLPanelWearableOutfitItem::Params& params, - bool show_widgets) -: LLPanelWearableListItem(item, params) -, mWornIndicationEnabled(worn_indication_enabled) -, mShowWidgets(show_widgets) -{ - if(mShowWidgets) - { - LLButton::Params button_params = params.add_btn; - applyXUILayout(button_params, this); - addChild(LLUICtrlFactory::create(button_params)); - - button_params = params.remove_btn; - applyXUILayout(button_params, this); - addChild(LLUICtrlFactory::create(button_params)); - } -} - -// virtual -void LLPanelWearableOutfitItem::updateItem(const std::string& name, - EItemState item_state) -{ - std::string search_label = name; - - // Updating item's worn status depending on whether it is linked in COF or not. - // We don't use get_is_item_worn() here because this update is triggered by - // an inventory observer upon link in COF beind added or removed so actual - // worn status of a linked item may still remain unchanged. - bool is_worn = LLAppearanceMgr::instance().isLinkedInCOF(mInventoryItemUUID); - if (mWornIndicationEnabled && is_worn) - { - search_label += LLTrans::getString("worn"); - item_state = IS_WORN; - } - if(mShowWidgets) - { - setShowWidget("add_wearable", !is_worn); - - // Body parts can't be removed, only replaced - LLViewerInventoryItem* inv_item = getItem(); - bool show_remove = is_worn && inv_item && (inv_item->getType() != LLAssetType::AT_BODYPART); - setShowWidget("remove_wearable", show_remove); - - if(mHovered) - { - setWidgetsVisible(true); - reshapeWidgets(); - } - } - - LLPanelInventoryListItemBase::updateItem(search_label, item_state); -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -static LLWidgetNameRegistry::StaticRegistrar sRegisterPanelClothingListItem(&typeid(LLPanelClothingListItem::Params), "clothing_list_item"); - - -LLPanelClothingListItem::Params::Params() -: up_btn("up_btn"), - down_btn("down_btn"), - edit_btn("edit_btn"), - lock_panel("lock_panel"), - edit_panel("edit_panel"), - lock_icon("lock_icon") -{} - -// static -LLPanelClothingListItem* LLPanelClothingListItem::create(LLViewerInventoryItem* item) -{ - LLPanelClothingListItem* list_item = NULL; - if(item) - { - const LLPanelClothingListItem::Params& params = LLUICtrlFactory::getDefaultParams(); - list_item = new LLPanelClothingListItem(item, params); - list_item->initFromParams(params); - list_item->postBuild(); - } - return list_item; -} - -LLPanelClothingListItem::LLPanelClothingListItem(LLViewerInventoryItem* item, const LLPanelClothingListItem::Params& params) - : LLPanelDeletableWearableListItem(item, params) -{ - LLButton::Params button_params = params.up_btn; - applyXUILayout(button_params, this); - addChild(LLUICtrlFactory::create(button_params)); - - button_params = params.down_btn; - applyXUILayout(button_params, this); - addChild(LLUICtrlFactory::create(button_params)); - - LLPanel::Params panel_params = params.lock_panel; - applyXUILayout(panel_params, this); - LLPanel* lock_panelp = LLUICtrlFactory::create(panel_params); - addChild(lock_panelp); - - panel_params = params.edit_panel; - applyXUILayout(panel_params, this); - LLPanel* edit_panelp = LLUICtrlFactory::create(panel_params); - addChild(edit_panelp); - - if (lock_panelp) -{ - LLIconCtrl::Params icon_params = params.lock_icon; - applyXUILayout(icon_params, this); - lock_panelp->addChild(LLUICtrlFactory::create(icon_params)); -} - - if (edit_panelp) -{ - button_params = params.edit_btn; - applyXUILayout(button_params, this); - edit_panelp->addChild(LLUICtrlFactory::create(button_params)); - } - - setSeparatorVisible(false); -} - -LLPanelClothingListItem::~LLPanelClothingListItem() -{ -} - -bool LLPanelClothingListItem::postBuild() -{ - LLPanelDeletableWearableListItem::postBuild(); - - addWidgetToRightSide("btn_move_up"); - addWidgetToRightSide("btn_move_down"); - addWidgetToRightSide("btn_lock"); - addWidgetToRightSide("btn_edit_panel"); - - setWidgetsVisible(false); - reshapeWidgets(); - - return true; -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -static LLWidgetNameRegistry::StaticRegistrar sRegisterPanelBodyPartsListItem(&typeid(LLPanelBodyPartsListItem::Params), "bodyparts_list_item"); - - -LLPanelBodyPartsListItem::Params::Params() -: edit_btn("edit_btn"), - edit_panel("edit_panel"), - lock_panel("lock_panel"), - lock_icon("lock_icon") -{} - -// static -LLPanelBodyPartsListItem* LLPanelBodyPartsListItem::create(LLViewerInventoryItem* item) -{ - LLPanelBodyPartsListItem* list_item = NULL; - if(item) - { - const Params& params = LLUICtrlFactory::getDefaultParams(); - list_item = new LLPanelBodyPartsListItem(item, params); - list_item->initFromParams(params); - list_item->postBuild(); - } - return list_item; -} - -LLPanelBodyPartsListItem::LLPanelBodyPartsListItem(LLViewerInventoryItem* item, const LLPanelBodyPartsListItem::Params& params) -: LLPanelWearableListItem(item, params) -{ - LLPanel::Params panel_params = params.edit_panel; - applyXUILayout(panel_params, this); - LLPanel* edit_panelp = LLUICtrlFactory::create(panel_params); - addChild(edit_panelp); - - panel_params = params.lock_panel; - applyXUILayout(panel_params, this); - LLPanel* lock_panelp = LLUICtrlFactory::create(panel_params); - addChild(lock_panelp); - - if (edit_panelp) - { - LLButton::Params btn_params = params.edit_btn; - applyXUILayout(btn_params, this); - edit_panelp->addChild(LLUICtrlFactory::create(btn_params)); -} - - if (lock_panelp) -{ - LLIconCtrl::Params icon_params = params.lock_icon; - applyXUILayout(icon_params, this); - lock_panelp->addChild(LLUICtrlFactory::create(icon_params)); - } - - setSeparatorVisible(true); -} - -LLPanelBodyPartsListItem::~LLPanelBodyPartsListItem() -{ -} - -bool LLPanelBodyPartsListItem::postBuild() -{ - LLPanelInventoryListItemBase::postBuild(); - - addWidgetToRightSide("btn_lock"); - addWidgetToRightSide("btn_edit_panel"); - - setWidgetsVisible(false); - reshapeWidgets(); - - return true; -} - -static LLWidgetNameRegistry::StaticRegistrar sRegisterPanelDeletableWearableListItem(&typeid(LLPanelDeletableWearableListItem::Params), "deletable_wearable_list_item"); - -LLPanelDeletableWearableListItem::Params::Params() -: delete_btn("delete_btn") -{} - -// static -LLPanelDeletableWearableListItem* LLPanelDeletableWearableListItem::create(LLViewerInventoryItem* item) -{ - LLPanelDeletableWearableListItem* list_item = NULL; - if(item) - { - const Params& params = LLUICtrlFactory::getDefaultParams(); - list_item = new LLPanelDeletableWearableListItem(item, params); - list_item->initFromParams(params); - list_item->postBuild(); - } - return list_item; -} - -LLPanelDeletableWearableListItem::LLPanelDeletableWearableListItem(LLViewerInventoryItem* item, const LLPanelDeletableWearableListItem::Params& params) -: LLPanelWearableListItem(item, params) -{ - LLButton::Params button_params = params.delete_btn; - applyXUILayout(button_params, this); - addChild(LLUICtrlFactory::create(button_params)); - - setSeparatorVisible(true); -} - -bool LLPanelDeletableWearableListItem::postBuild() -{ - LLPanelWearableListItem::postBuild(); - - addWidgetToLeftSide("btn_delete"); - - LLButton* delete_btn = getChild("btn_delete"); - // Reserve space for 'delete' button event if it is invisible. - setLeftWidgetsWidth(delete_btn->getRect().mRight); - - setWidgetsVisible(false); - reshapeWidgets(); - - return true; -} - - -// static -LLPanelAttachmentListItem* LLPanelAttachmentListItem::create(LLViewerInventoryItem* item) -{ - LLPanelAttachmentListItem* list_item = NULL; - if(item) - { - const Params& params = LLUICtrlFactory::getDefaultParams(); - - list_item = new LLPanelAttachmentListItem(item, params); - list_item->initFromParams(params); - list_item->postBuild(); - } - return list_item; -} - -void LLPanelAttachmentListItem::updateItem(const std::string& name, - EItemState item_state) -{ - std::string title_joint = name; - - LLViewerInventoryItem* inv_item = getItem(); - if (inv_item && isAgentAvatarValid() && gAgentAvatarp->isWearingAttachment(inv_item->getLinkedUUID())) - { - std::string found_name; - bool found = gAgentAvatarp->getAttachedPointName(inv_item->getLinkedUUID(),found_name); - std::string trans_name = LLTrans::getString(found_name); - if (!found) - { - LL_WARNS() << "invalid attachment joint, err " << found_name << LL_ENDL; - } - title_joint = title_joint + " (" + trans_name + ")"; - } - - LLPanelInventoryListItemBase::updateItem(title_joint, item_state); -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -static LLWidgetNameRegistry::StaticRegistrar sRegisterPanelDummyClothingListItem(&typeid(LLPanelDummyClothingListItem::Params), "dummy_clothing_list_item"); - -LLPanelDummyClothingListItem::Params::Params() -: add_panel("add_panel"), - add_btn("add_btn") -{} - -LLPanelDummyClothingListItem* LLPanelDummyClothingListItem::create(LLWearableType::EType w_type) -{ - const Params& params = LLUICtrlFactory::getDefaultParams(); - - LLPanelDummyClothingListItem* list_item = new LLPanelDummyClothingListItem(w_type, params); - list_item->initFromParams(params); - list_item->postBuild(); - return list_item; -} - -bool LLPanelDummyClothingListItem::postBuild() -{ - addWidgetToRightSide("btn_add_panel"); - - setIconImage(LLInventoryIcon::getIcon(LLAssetType::AT_CLOTHING, LLInventoryType::IT_NONE, mWearableType, false)); - updateItem(wearableTypeToString(mWearableType)); - - // Make it look loke clothing item - reserve space for 'delete' button - setLeftWidgetsWidth(getChildView("item_icon")->getRect().mLeft); - - setWidgetsVisible(false); - reshapeWidgets(); - - return true; -} - -LLWearableType::EType LLPanelDummyClothingListItem::getWearableType() const -{ - return mWearableType; -} - -LLPanelDummyClothingListItem::LLPanelDummyClothingListItem(LLWearableType::EType w_type, const LLPanelDummyClothingListItem::Params& params) -: LLPanelWearableListItem(NULL, params), - mWearableType(w_type) -{ - LLPanel::Params panel_params(params.add_panel); - applyXUILayout(panel_params, this); - LLPanel* add_panelp = LLUICtrlFactory::create(panel_params); - addChild(add_panelp); - - if (add_panelp) -{ - LLButton::Params button_params(params.add_btn); - applyXUILayout(button_params, this); - add_panelp->addChild(LLUICtrlFactory::create(button_params)); -} - - setSeparatorVisible(true); -} - -typedef std::map clothing_to_string_map_t; - -clothing_to_string_map_t init_clothing_string_map() -{ - clothing_to_string_map_t w_map; - w_map.insert(std::make_pair(LLWearableType::WT_SHIRT, "shirt_not_worn")); - w_map.insert(std::make_pair(LLWearableType::WT_PANTS, "pants_not_worn")); - w_map.insert(std::make_pair(LLWearableType::WT_SHOES, "shoes_not_worn")); - w_map.insert(std::make_pair(LLWearableType::WT_SOCKS, "socks_not_worn")); - w_map.insert(std::make_pair(LLWearableType::WT_JACKET, "jacket_not_worn")); - w_map.insert(std::make_pair(LLWearableType::WT_GLOVES, "gloves_not_worn")); - w_map.insert(std::make_pair(LLWearableType::WT_UNDERSHIRT, "undershirt_not_worn")); - w_map.insert(std::make_pair(LLWearableType::WT_UNDERPANTS, "underpants_not_worn")); - w_map.insert(std::make_pair(LLWearableType::WT_SKIRT, "skirt_not_worn")); - w_map.insert(std::make_pair(LLWearableType::WT_ALPHA, "alpha_not_worn")); - w_map.insert(std::make_pair(LLWearableType::WT_TATTOO, "tattoo_not_worn")); - w_map.insert(std::make_pair(LLWearableType::WT_UNIVERSAL, "universal_not_worn")); - w_map.insert(std::make_pair(LLWearableType::WT_PHYSICS, "physics_not_worn")); - return w_map; -} - -std::string LLPanelDummyClothingListItem::wearableTypeToString(LLWearableType::EType w_type) -{ - static const clothing_to_string_map_t w_map = init_clothing_string_map(); - static const std::string invalid_str = LLTrans::getString("invalid_not_worn"); - - std::string type_str = invalid_str; - clothing_to_string_map_t::const_iterator it = w_map.find(w_type); - if(w_map.end() != it) - { - type_str = LLTrans::getString(it->second); - } - return type_str; -} - -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -LLWearableItemTypeNameComparator::LLWearableTypeOrder::LLWearableTypeOrder(LLWearableItemTypeNameComparator::ETypeListOrder order_priority, bool sort_asset_by_name, bool sort_wearable_by_name): - mOrderPriority(order_priority), - mSortAssetTypeByName(sort_asset_by_name), - mSortWearableTypeByName(sort_wearable_by_name) -{ -} - -LLWearableItemTypeNameComparator::LLWearableItemTypeNameComparator() -{ - // By default the sort order conforms the order by spec of MY OUTFITS items list: - // 1. CLOTHING - sorted by name - // 2. OBJECT - sorted by type - // 3. BODYPART - sorted by name - mWearableOrder[LLAssetType::AT_CLOTHING] = LLWearableTypeOrder(ORDER_RANK_1, false, false); - mWearableOrder[LLAssetType::AT_OBJECT] = LLWearableTypeOrder(ORDER_RANK_2, true, true); - mWearableOrder[LLAssetType::AT_BODYPART] = LLWearableTypeOrder(ORDER_RANK_3, false, true); - mWearableOrder[LLAssetType::AT_GESTURE] = LLWearableTypeOrder(ORDER_RANK_4, true, false); -} - -void LLWearableItemTypeNameComparator::setOrder(LLAssetType::EType items_of_type, LLWearableItemTypeNameComparator::ETypeListOrder order_priority, bool sort_asset_items_by_name, bool sort_wearable_items_by_name) -{ - mWearableOrder[items_of_type] = LLWearableTypeOrder(order_priority, sort_asset_items_by_name, sort_wearable_items_by_name); -} - -/*virtual*/ -bool LLWearableItemNameComparator::doCompare(const LLPanelInventoryListItemBase* wearable_item1, const LLPanelInventoryListItemBase* wearable_item2) const -{ - std::string name1 = wearable_item1->getItemName(); - std::string name2 = wearable_item2->getItemName(); - - LLStringUtil::toUpper(name1); - LLStringUtil::toUpper(name2); - - return name1 < name2; -} - -/*virtual*/ -bool LLWearableItemTypeNameComparator::doCompare(const LLPanelInventoryListItemBase* wearable_item1, const LLPanelInventoryListItemBase* wearable_item2) const -{ - const LLAssetType::EType item_type1 = wearable_item1->getType(); - const LLAssetType::EType item_type2 = wearable_item2->getType(); - - LLWearableItemTypeNameComparator::ETypeListOrder item_type_order1 = getTypeListOrder(item_type1); - LLWearableItemTypeNameComparator::ETypeListOrder item_type_order2 = getTypeListOrder(item_type2); - - if (item_type_order1 != item_type_order2) - { - // If items are of different asset types we can compare them - // by types order in the list. - return item_type_order1 < item_type_order2; - } - - if (sortAssetTypeByName(item_type1)) - { - // If both items are of the same asset type except AT_CLOTHING and AT_BODYPART - // we can compare them by name. - return LLWearableItemNameComparator::doCompare(wearable_item1, wearable_item2); - } - - const LLWearableType::EType item_wearable_type1 = wearable_item1->getWearableType(); - const LLWearableType::EType item_wearable_type2 = wearable_item2->getWearableType(); - - if (item_wearable_type1 != item_wearable_type2) - // If items are of different LLWearableType::EType types they are compared - // by LLWearableType::EType. types order determined in LLWearableType::EType. - { - // If items are of different LLWearableType::EType types they are compared - // by LLWearableType::EType. types order determined in LLWearableType::EType. - return item_wearable_type1 < item_wearable_type2; - } - else - { - // If both items are of the same clothing type they are compared - // by description and place in reverse order (i.e. outer layer item - // on top) OR by name - if(sortWearableTypeByName(item_type1)) - { - return LLWearableItemNameComparator::doCompare(wearable_item1, wearable_item2); - } - return wearable_item1->getDescription() > wearable_item2->getDescription(); - } -} - -LLWearableItemTypeNameComparator::ETypeListOrder LLWearableItemTypeNameComparator::getTypeListOrder(LLAssetType::EType item_type) const -{ - wearable_type_order_map_t::const_iterator const_it = mWearableOrder.find(item_type); - - - if(const_it == mWearableOrder.end()) - { - LL_WARNS()<<"Absent information about order rang of items of "<second.mOrderPriority; -} - -bool LLWearableItemTypeNameComparator::sortAssetTypeByName(LLAssetType::EType item_type) const -{ - wearable_type_order_map_t::const_iterator const_it = mWearableOrder.find(item_type); - - - if(const_it == mWearableOrder.end()) - { - LL_WARNS()<<"Absent information about sorting items of "<second.mSortAssetTypeByName; - } - - -bool LLWearableItemTypeNameComparator::sortWearableTypeByName(LLAssetType::EType item_type) const -{ - wearable_type_order_map_t::const_iterator const_it = mWearableOrder.find(item_type); - - - if(const_it == mWearableOrder.end()) - { - LL_WARNS()<<"Absent information about sorting items of "<second.mSortWearableTypeByName; -} - -/*virtual*/ -bool LLWearableItemCreationDateComparator::doCompare(const LLPanelInventoryListItemBase* item1, const LLPanelInventoryListItemBase* item2) const -{ - time_t date1 = item1->getCreationDate(); - time_t date2 = item2->getCreationDate(); - - if (date1 == date2) - { - return LLWearableItemNameComparator::doCompare(item1, item2); - } - - return date1 > date2; -} -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////// - -static LLWearableItemTypeNameComparator WEARABLE_TYPE_NAME_COMPARATOR; -static const LLWearableItemTypeNameComparator WEARABLE_TYPE_LAYER_COMPARATOR; -static const LLWearableItemNameComparator WEARABLE_NAME_COMPARATOR; -static const LLWearableItemCreationDateComparator WEARABLE_CREATION_DATE_COMPARATOR; - -static const LLDefaultChildRegistry::Register r("wearable_items_list"); - -LLWearableItemsList::Params::Params() -: standalone("standalone", true) -, worn_indication_enabled("worn_indication_enabled", true) -, show_item_widgets("show_item_widgets", false) -{} - -LLWearableItemsList::LLWearableItemsList(const LLWearableItemsList::Params& p) -: LLInventoryItemsList(p) -{ - setSortOrder(E_SORT_BY_TYPE_LAYER, false); - mMenuWearableType = LLWearableType::WT_NONE; - mIsStandalone = p.standalone; - if (mIsStandalone) - { - // Use built-in context menu. - setRightMouseDownCallback(boost::bind(&LLWearableItemsList::onRightClick, this, _2, _3)); - } - mWornIndicationEnabled = p.worn_indication_enabled; - setNoItemsCommentText(LLTrans::getString("LoadingData")); - mShowItemWidgets = p.show_item_widgets; -} - -// virtual -LLWearableItemsList::~LLWearableItemsList() -{} - -// virtual -LLPanel* LLWearableItemsList::createNewItem(LLViewerInventoryItem* item) -{ - if (!item) - { - LL_WARNS() << "No inventory item. Couldn't create flat list item." << LL_ENDL; - llassert(item != NULL); - return NULL; - } - - return LLPanelWearableOutfitItem::create(item, mWornIndicationEnabled, mShowItemWidgets); -} - -void LLWearableItemsList::updateList(const LLUUID& category_id) -{ - LLInventoryModel::cat_array_t cat_array; - LLInventoryModel::item_array_t item_array; - - LLFindOutfitItems collector = LLFindOutfitItems(); - // collectDescendentsIf takes non-const reference: - gInventory.collectDescendentsIf( - category_id, - cat_array, - item_array, - LLInventoryModel::EXCLUDE_TRASH, - collector); - - if(item_array.empty() && gInventory.isCategoryComplete(category_id)) - { - setNoItemsCommentText(LLTrans::getString("EmptyOutfitText")); - } - - refreshList(item_array); -} - -void LLWearableItemsList::updateChangedItems(const uuid_vec_t& changed_items_uuids) -{ - // nothing to update - if (changed_items_uuids.empty()) - return; - - uuid_vec_t::const_iterator uuids_begin = changed_items_uuids.begin(), uuids_end = changed_items_uuids.end(); - pairs_const_iterator_t pairs_iter = getItemPairs().begin(), pairs_end = getItemPairs().end(); - while (pairs_iter != pairs_end) - { - LLPanel* panel = (*(pairs_iter++))->first; - LLPanelInventoryListItemBase* item = dynamic_cast(panel); - if (!item) - continue; - - LLViewerInventoryItem* inv_item = item->getItem(); - if (!inv_item) - continue; - - const LLUUID& linked_uuid = inv_item->getLinkedUUID(); - if (std::find(uuids_begin, uuids_end, linked_uuid) != uuids_end) - { - item->setNeedsRefresh(true); - } - } -} - -void LLWearableItemsList::onRightClick(S32 x, S32 y) -{ - uuid_vec_t selected_uuids; - - getSelectedUUIDs(selected_uuids); - if (selected_uuids.empty()) - { - if ((mMenuWearableType != LLWearableType::WT_NONE) && (size() == 0)) - { - ContextMenu::instance().show(this, mMenuWearableType, x, y); - } - } - else - { - ContextMenu::instance().show(this, selected_uuids, x, y); - } -} - -void LLWearableItemsList::setSortOrder(ESortOrder sort_order, bool sort_now) -{ - switch (sort_order) - { - case E_SORT_BY_MOST_RECENT: - setComparator(&WEARABLE_CREATION_DATE_COMPARATOR); - break; - case E_SORT_BY_NAME: - setComparator(&WEARABLE_NAME_COMPARATOR); - break; - case E_SORT_BY_TYPE_LAYER: - setComparator(&WEARABLE_TYPE_LAYER_COMPARATOR); - break; - case E_SORT_BY_TYPE_NAME: - { - WEARABLE_TYPE_NAME_COMPARATOR.setOrder(LLAssetType::AT_CLOTHING, LLWearableItemTypeNameComparator::ORDER_RANK_1, false, true); - setComparator(&WEARABLE_TYPE_NAME_COMPARATOR); - break; - } - - // No "default:" to raise compiler warning - // if we're not handling something - } - - mSortOrder = sort_order; - - if (sort_now) - { - sort(); - } -} - -////////////////////////////////////////////////////////////////////////// -/// ContextMenu -////////////////////////////////////////////////////////////////////////// - -LLWearableItemsList::ContextMenu::ContextMenu() -: mParent(NULL) -{ -} - -void LLWearableItemsList::ContextMenu::show(LLView* spawning_view, const uuid_vec_t& uuids, S32 x, S32 y) -{ - mParent = dynamic_cast(spawning_view); - LLListContextMenu::show(spawning_view, uuids, x, y); - mParent = NULL; // to avoid dereferencing an invalid pointer -} - -void LLWearableItemsList::ContextMenu::show(LLView* spawning_view, LLWearableType::EType w_type, S32 x, S32 y) -{ - mParent = dynamic_cast(spawning_view); - LLContextMenu* menup = mMenuHandle.get(); - if (menup) - { - //preventing parent (menu holder) from deleting already "dead" context menus on exit - LLView* parent = menup->getParent(); - if (parent) - { - parent->removeChild(menup); - } - delete menup; - mUUIDs.clear(); - } - - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - registrar.add("Wearable.CreateNew", boost::bind(createNewWearableByType, w_type)); - menup = createFromFile("menu_wearable_list_item.xml"); - if (!menup) - { - LL_WARNS() << "Context menu creation failed" << LL_ENDL; - return; - } - setMenuItemVisible(menup, "create_new", true); - setMenuItemEnabled(menup, "create_new", true); - setMenuItemVisible(menup, "wearable_attach_to", false); - setMenuItemVisible(menup, "wearable_attach_to_hud", false); - - std::string new_label = LLTrans::getString("create_new_" + LLWearableType::getInstance()->getTypeName(w_type)); - LLMenuItemGL* menu_item = menup->getChild("create_new"); - menu_item->setLabel(new_label); - - mMenuHandle = menup->getHandle(); - menup->show(x, y); - LLMenuGL::showPopup(spawning_view, menup, x, y); - - mParent = NULL; // to avoid dereferencing an invalid pointer -} - -// virtual -LLContextMenu* LLWearableItemsList::ContextMenu::createMenu() -{ - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - const uuid_vec_t& ids = mUUIDs; // selected items IDs - LLUUID selected_id = ids.front(); // ID of the first selected item - - // Register handlers common for all wearable types. - registrar.add("Wearable.Wear", boost::bind(wear_multiple, ids, true)); - registrar.add("Wearable.Add", boost::bind(wear_multiple, ids, false)); - registrar.add("Wearable.Edit", boost::bind(handle_item_edit, selected_id)); - registrar.add("Wearable.CreateNew", boost::bind(createNewWearable, selected_id)); - registrar.add("Wearable.ShowOriginal", boost::bind(show_item_original, selected_id)); - registrar.add("Wearable.TakeOffDetach", - boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), ids, no_op)); - - // Register handlers for clothing. - registrar.add("Clothing.TakeOff", - boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), ids, no_op)); - - // Register handlers for body parts. - - // Register handlers for attachments. - registrar.add("Attachment.Detach", - boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), ids, no_op)); - registrar.add("Attachment.Touch", boost::bind(handle_attachment_touch, selected_id)); - registrar.add("Attachment.Profile", boost::bind(show_item_profile, selected_id)); - registrar.add("Object.Attach", boost::bind(LLViewerAttachMenu::attachObjects, ids, _2)); - - // Create the menu. - LLContextMenu* menu = createFromFile("menu_wearable_list_item.xml"); - - // Determine which items should be visible/enabled. - updateItemsVisibility(menu); - - // Update labels for the items requiring that. - updateItemsLabels(menu); - return menu; -} - -void LLWearableItemsList::ContextMenu::updateItemsVisibility(LLContextMenu* menu) -{ - if (!menu) - { - LL_WARNS() << "Invalid menu" << LL_ENDL; - return; - } - - const uuid_vec_t& ids = mUUIDs; // selected items IDs - U32 mask = 0; // mask of selected items' types - U32 n_items = ids.size(); // number of selected items - U32 n_worn = 0; // number of worn items among the selected ones - U32 n_already_worn = 0; // number of items worn of same type as selected items - U32 n_links = 0; // number of links among the selected items - U32 n_editable = 0; // number of editable items among the selected ones - U32 n_touchable = 0; // number of touchable items among the selected ones - - bool can_be_worn = true; - - for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it) - { - LLUUID id = *it; - LLViewerInventoryItem* item = gInventory.getItem(id); - - if (!item) - { - LL_WARNS() << "Invalid item" << LL_ENDL; - // *NOTE: the logic below may not work in this case - continue; - } - - updateMask(mask, item->getType()); - - const LLWearableType::EType wearable_type = item->getWearableType(); - const bool is_link = item->getIsLinkType(); - const bool is_worn = get_is_item_worn(id); - const bool is_editable = get_is_item_editable(id); - const bool is_touchable = enable_attachment_touch(id); - const bool is_already_worn = gAgentWearables.selfHasWearable(wearable_type); - if (is_worn) - { - ++n_worn; - } - if (is_touchable) - { - ++n_touchable; - } - if (is_editable) - { - ++n_editable; - } - if (is_link) - { - ++n_links; - } - if (is_already_worn) - { - ++n_already_worn; - } - - if (can_be_worn) - { - can_be_worn = get_can_item_be_worn(item->getLinkedUUID()); - } - } // for - - bool standalone = mParent ? mParent->isStandalone() : false; - bool wear_add_visible = mask & (MASK_CLOTHING|MASK_ATTACHMENT) && n_worn == 0 && can_be_worn && (n_already_worn != 0 || mask & MASK_ATTACHMENT); - - // *TODO: eliminate multiple traversals over the menu items - setMenuItemVisible(menu, "wear_wear", n_already_worn == 0 && n_worn == 0 && can_be_worn); - setMenuItemEnabled(menu, "wear_wear", n_already_worn == 0 && n_worn == 0); - setMenuItemVisible(menu, "wear_add", wear_add_visible); - setMenuItemEnabled(menu, "wear_add", LLAppearanceMgr::instance().canAddWearables(ids)); - setMenuItemVisible(menu, "wear_replace", n_worn == 0 && n_already_worn != 0 && can_be_worn); - //visible only when one item selected and this item is worn - setMenuItemVisible(menu, "touch", !standalone && mask == MASK_ATTACHMENT && n_worn == n_items); - setMenuItemEnabled(menu, "touch", n_touchable && n_worn == 1 && n_items == 1); - setMenuItemVisible(menu, "edit", !standalone && mask & (MASK_CLOTHING|MASK_BODYPART|MASK_ATTACHMENT) && n_worn == n_items); - setMenuItemEnabled(menu, "edit", n_editable && n_worn == 1 && n_items == 1); - setMenuItemVisible(menu, "create_new", mask & (MASK_CLOTHING|MASK_BODYPART) && n_items == 1); - setMenuItemEnabled(menu, "create_new", LLAppearanceMgr::instance().canAddWearables(ids)); - setMenuItemVisible(menu, "show_original", !standalone); - setMenuItemEnabled(menu, "show_original", n_items == 1 && n_links == n_items); - setMenuItemVisible(menu, "take_off", mask == MASK_CLOTHING && n_worn == n_items); - setMenuItemVisible(menu, "detach", mask == MASK_ATTACHMENT && n_worn == n_items); - setMenuItemVisible(menu, "take_off_or_detach", mask == (MASK_ATTACHMENT|MASK_CLOTHING)); - setMenuItemEnabled(menu, "take_off_or_detach", n_worn == n_items); - setMenuItemVisible(menu, "object_profile", !standalone); - setMenuItemEnabled(menu, "object_profile", n_items == 1); - setMenuItemVisible(menu, "--no options--", false); - setMenuItemEnabled(menu, "--no options--", false); - - // Populate or hide the "Attach to..." / "Attach to HUD..." submenus. - if (mask == MASK_ATTACHMENT && n_worn == 0) - { - LLViewerAttachMenu::populateMenus("wearable_attach_to", "wearable_attach_to_hud"); - } - else - { - setMenuItemVisible(menu, "wearable_attach_to", false); - setMenuItemVisible(menu, "wearable_attach_to_hud", false); - } - - if (mask & MASK_UNKNOWN) - { - LL_WARNS() << "Non-wearable items passed." << LL_ENDL; - } - - U32 num_visible_items = 0; - for (U32 menu_item_index = 0; menu_item_index < menu->getItemCount(); ++menu_item_index) - { - const LLMenuItemGL* menu_item = menu->getItem(menu_item_index); - if (menu_item && menu_item->getVisible()) - { - num_visible_items++; - } - } - if (num_visible_items == 0) - { - setMenuItemVisible(menu, "--no options--", true); - } -} - -void LLWearableItemsList::ContextMenu::updateItemsLabels(LLContextMenu* menu) -{ - llassert(menu); - if (!menu) return; - - // Set proper label for the "Create new " menu item. - LLViewerInventoryItem* item = gInventory.getLinkedItem(mUUIDs.back()); - if (!item || !item->isWearableType()) return; - - LLWearableType::EType w_type = item->getWearableType(); - std::string new_label = LLTrans::getString("create_new_" + LLWearableType::getInstance()->getTypeName(w_type)); - - LLMenuItemGL* menu_item = menu->getChild("create_new"); - menu_item->setLabel(new_label); -} - -// We need this method to convert non-zero bool values to exactly 1 (true). -// Otherwise code relying on a bool value being true may fail -// (I experienced a weird assert in LLView::drawChildren() because of that. -// static -void LLWearableItemsList::ContextMenu::setMenuItemVisible(LLContextMenu* menu, const std::string& name, bool val) -{ - menu->setItemVisible(name, val); -} - -// static -void LLWearableItemsList::ContextMenu::setMenuItemEnabled(LLContextMenu* menu, const std::string& name, bool val) -{ - menu->setItemEnabled(name, val); -} - -// static -void LLWearableItemsList::ContextMenu::updateMask(U32& mask, LLAssetType::EType at) -{ - if (at == LLAssetType::AT_CLOTHING) - { - mask |= MASK_CLOTHING; - } - else if (at == LLAssetType::AT_BODYPART) - { - mask |= MASK_BODYPART; - } - else if (at == LLAssetType::AT_OBJECT) - { - mask |= MASK_ATTACHMENT; - } - else if (at == LLAssetType::AT_GESTURE) - { - mask |= MASK_GESTURE; - } - else - { - mask |= MASK_UNKNOWN; - } -} - -// static -void LLWearableItemsList::ContextMenu::createNewWearable(const LLUUID& item_id) -{ - LLViewerInventoryItem* item = gInventory.getLinkedItem(item_id); - if (!item || !item->isWearableType()) return; - - LLAgentWearables::createWearable(item->getWearableType(), true); -} - -// static -void LLWearableItemsList::ContextMenu::createNewWearableByType(LLWearableType::EType type) -{ - LLAgentWearables::createWearable(type, true); -} - -// EOF +/** + * @file llwearableitemslist.cpp + * @brief A flat list of wearable items. + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llwearableitemslist.h" + +#include "lliconctrl.h" +#include "llmenugl.h" // for LLContextMenu + +#include "llagentwearables.h" +#include "llappearancemgr.h" +#include "llinventoryfunctions.h" +#include "llinventoryicon.h" +#include "llgesturemgr.h" +#include "lltransutil.h" +#include "llviewerattachmenu.h" +#include "llviewermenu.h" +#include "llvoavatarself.h" + +class LLFindOutfitItems : public LLInventoryCollectFunctor +{ +public: + LLFindOutfitItems() {} + virtual ~LLFindOutfitItems() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); +}; + +bool LLFindOutfitItems::operator()(LLInventoryCategory* cat, + LLInventoryItem* item) +{ + if(item) + { + if((item->getType() == LLAssetType::AT_CLOTHING) + || (item->getType() == LLAssetType::AT_BODYPART) + || (item->getType() == LLAssetType::AT_OBJECT) + || (item->getType() == LLAssetType::AT_GESTURE)) + { + return true; + } + } + return false; +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +void LLPanelWearableListItem::onMouseEnter(S32 x, S32 y, MASK mask) +{ + LLPanelInventoryListItemBase::onMouseEnter(x, y, mask); + setWidgetsVisible(true); + reshapeWidgets(); +} + +void LLPanelWearableListItem::onMouseLeave(S32 x, S32 y, MASK mask) +{ + LLPanelInventoryListItemBase::onMouseLeave(x, y, mask); + setWidgetsVisible(false); + reshapeWidgets(); +} + +LLPanelWearableListItem::LLPanelWearableListItem(LLViewerInventoryItem* item, const LLPanelWearableListItem::Params& params) +: LLPanelInventoryListItemBase(item, params) +{ +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +static LLWidgetNameRegistry::StaticRegistrar sRegisterPanelWearableOutfitItem(&typeid(LLPanelWearableOutfitItem::Params), "wearable_outfit_list_item"); + +LLPanelWearableOutfitItem::Params::Params() +: add_btn("add_btn"), + remove_btn("remove_btn") +{ +} + +bool LLPanelWearableOutfitItem::postBuild() +{ + LLPanelWearableListItem::postBuild(); + + if(mShowWidgets) + { + addWidgetToRightSide("add_wearable"); + addWidgetToRightSide("remove_wearable"); + + childSetAction("add_wearable", boost::bind(&LLPanelWearableOutfitItem::onAddWearable, this)); + childSetAction("remove_wearable", boost::bind(&LLPanelWearableOutfitItem::onRemoveWearable, this)); + + setWidgetsVisible(false); + reshapeWidgets(); + } + return true; +} + +bool LLPanelWearableOutfitItem::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + if(!mShowWidgets) + { + return LLPanelWearableListItem::handleDoubleClick(x, y, mask); + } + + if(LLAppearanceMgr::instance().isLinkedInCOF(mInventoryItemUUID)) + { + onRemoveWearable(); + } + else + { + onAddWearable(); + } + return true; +} + +void LLPanelWearableOutfitItem::onAddWearable() +{ + setWidgetsVisible(false); + reshapeWidgets(); + LLAppearanceMgr::instance().wearItemOnAvatar(mInventoryItemUUID, true, false); +} + +void LLPanelWearableOutfitItem::onRemoveWearable() +{ + setWidgetsVisible(false); + reshapeWidgets(); + LLAppearanceMgr::instance().removeItemFromAvatar(mInventoryItemUUID); +} + +// static +LLPanelWearableOutfitItem* LLPanelWearableOutfitItem::create(LLViewerInventoryItem* item, + bool worn_indication_enabled, + bool show_widgets) +{ + LLPanelWearableOutfitItem* list_item = NULL; + if (item) + { + const LLPanelWearableOutfitItem::Params& params = LLUICtrlFactory::getDefaultParams(); + + list_item = new LLPanelWearableOutfitItem(item, worn_indication_enabled, params, show_widgets); + list_item->initFromParams(params); + list_item->postBuild(); + } + return list_item; +} + +LLPanelWearableOutfitItem::LLPanelWearableOutfitItem(LLViewerInventoryItem* item, + bool worn_indication_enabled, + const LLPanelWearableOutfitItem::Params& params, + bool show_widgets) +: LLPanelWearableListItem(item, params) +, mWornIndicationEnabled(worn_indication_enabled) +, mShowWidgets(show_widgets) +{ + if(mShowWidgets) + { + LLButton::Params button_params = params.add_btn; + applyXUILayout(button_params, this); + addChild(LLUICtrlFactory::create(button_params)); + + button_params = params.remove_btn; + applyXUILayout(button_params, this); + addChild(LLUICtrlFactory::create(button_params)); + } +} + +// virtual +void LLPanelWearableOutfitItem::updateItem(const std::string& name, + EItemState item_state) +{ + std::string search_label = name; + + // Updating item's worn status depending on whether it is linked in COF or not. + // We don't use get_is_item_worn() here because this update is triggered by + // an inventory observer upon link in COF beind added or removed so actual + // worn status of a linked item may still remain unchanged. + bool is_worn = LLAppearanceMgr::instance().isLinkedInCOF(mInventoryItemUUID); + if (mWornIndicationEnabled && is_worn) + { + search_label += LLTrans::getString("worn"); + item_state = IS_WORN; + } + if(mShowWidgets) + { + setShowWidget("add_wearable", !is_worn); + + // Body parts can't be removed, only replaced + LLViewerInventoryItem* inv_item = getItem(); + bool show_remove = is_worn && inv_item && (inv_item->getType() != LLAssetType::AT_BODYPART); + setShowWidget("remove_wearable", show_remove); + + if(mHovered) + { + setWidgetsVisible(true); + reshapeWidgets(); + } + } + + LLPanelInventoryListItemBase::updateItem(search_label, item_state); +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +static LLWidgetNameRegistry::StaticRegistrar sRegisterPanelClothingListItem(&typeid(LLPanelClothingListItem::Params), "clothing_list_item"); + + +LLPanelClothingListItem::Params::Params() +: up_btn("up_btn"), + down_btn("down_btn"), + edit_btn("edit_btn"), + lock_panel("lock_panel"), + edit_panel("edit_panel"), + lock_icon("lock_icon") +{} + +// static +LLPanelClothingListItem* LLPanelClothingListItem::create(LLViewerInventoryItem* item) +{ + LLPanelClothingListItem* list_item = NULL; + if(item) + { + const LLPanelClothingListItem::Params& params = LLUICtrlFactory::getDefaultParams(); + list_item = new LLPanelClothingListItem(item, params); + list_item->initFromParams(params); + list_item->postBuild(); + } + return list_item; +} + +LLPanelClothingListItem::LLPanelClothingListItem(LLViewerInventoryItem* item, const LLPanelClothingListItem::Params& params) + : LLPanelDeletableWearableListItem(item, params) +{ + LLButton::Params button_params = params.up_btn; + applyXUILayout(button_params, this); + addChild(LLUICtrlFactory::create(button_params)); + + button_params = params.down_btn; + applyXUILayout(button_params, this); + addChild(LLUICtrlFactory::create(button_params)); + + LLPanel::Params panel_params = params.lock_panel; + applyXUILayout(panel_params, this); + LLPanel* lock_panelp = LLUICtrlFactory::create(panel_params); + addChild(lock_panelp); + + panel_params = params.edit_panel; + applyXUILayout(panel_params, this); + LLPanel* edit_panelp = LLUICtrlFactory::create(panel_params); + addChild(edit_panelp); + + if (lock_panelp) +{ + LLIconCtrl::Params icon_params = params.lock_icon; + applyXUILayout(icon_params, this); + lock_panelp->addChild(LLUICtrlFactory::create(icon_params)); +} + + if (edit_panelp) +{ + button_params = params.edit_btn; + applyXUILayout(button_params, this); + edit_panelp->addChild(LLUICtrlFactory::create(button_params)); + } + + setSeparatorVisible(false); +} + +LLPanelClothingListItem::~LLPanelClothingListItem() +{ +} + +bool LLPanelClothingListItem::postBuild() +{ + LLPanelDeletableWearableListItem::postBuild(); + + addWidgetToRightSide("btn_move_up"); + addWidgetToRightSide("btn_move_down"); + addWidgetToRightSide("btn_lock"); + addWidgetToRightSide("btn_edit_panel"); + + setWidgetsVisible(false); + reshapeWidgets(); + + return true; +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +static LLWidgetNameRegistry::StaticRegistrar sRegisterPanelBodyPartsListItem(&typeid(LLPanelBodyPartsListItem::Params), "bodyparts_list_item"); + + +LLPanelBodyPartsListItem::Params::Params() +: edit_btn("edit_btn"), + edit_panel("edit_panel"), + lock_panel("lock_panel"), + lock_icon("lock_icon") +{} + +// static +LLPanelBodyPartsListItem* LLPanelBodyPartsListItem::create(LLViewerInventoryItem* item) +{ + LLPanelBodyPartsListItem* list_item = NULL; + if(item) + { + const Params& params = LLUICtrlFactory::getDefaultParams(); + list_item = new LLPanelBodyPartsListItem(item, params); + list_item->initFromParams(params); + list_item->postBuild(); + } + return list_item; +} + +LLPanelBodyPartsListItem::LLPanelBodyPartsListItem(LLViewerInventoryItem* item, const LLPanelBodyPartsListItem::Params& params) +: LLPanelWearableListItem(item, params) +{ + LLPanel::Params panel_params = params.edit_panel; + applyXUILayout(panel_params, this); + LLPanel* edit_panelp = LLUICtrlFactory::create(panel_params); + addChild(edit_panelp); + + panel_params = params.lock_panel; + applyXUILayout(panel_params, this); + LLPanel* lock_panelp = LLUICtrlFactory::create(panel_params); + addChild(lock_panelp); + + if (edit_panelp) + { + LLButton::Params btn_params = params.edit_btn; + applyXUILayout(btn_params, this); + edit_panelp->addChild(LLUICtrlFactory::create(btn_params)); +} + + if (lock_panelp) +{ + LLIconCtrl::Params icon_params = params.lock_icon; + applyXUILayout(icon_params, this); + lock_panelp->addChild(LLUICtrlFactory::create(icon_params)); + } + + setSeparatorVisible(true); +} + +LLPanelBodyPartsListItem::~LLPanelBodyPartsListItem() +{ +} + +bool LLPanelBodyPartsListItem::postBuild() +{ + LLPanelInventoryListItemBase::postBuild(); + + addWidgetToRightSide("btn_lock"); + addWidgetToRightSide("btn_edit_panel"); + + setWidgetsVisible(false); + reshapeWidgets(); + + return true; +} + +static LLWidgetNameRegistry::StaticRegistrar sRegisterPanelDeletableWearableListItem(&typeid(LLPanelDeletableWearableListItem::Params), "deletable_wearable_list_item"); + +LLPanelDeletableWearableListItem::Params::Params() +: delete_btn("delete_btn") +{} + +// static +LLPanelDeletableWearableListItem* LLPanelDeletableWearableListItem::create(LLViewerInventoryItem* item) +{ + LLPanelDeletableWearableListItem* list_item = NULL; + if(item) + { + const Params& params = LLUICtrlFactory::getDefaultParams(); + list_item = new LLPanelDeletableWearableListItem(item, params); + list_item->initFromParams(params); + list_item->postBuild(); + } + return list_item; +} + +LLPanelDeletableWearableListItem::LLPanelDeletableWearableListItem(LLViewerInventoryItem* item, const LLPanelDeletableWearableListItem::Params& params) +: LLPanelWearableListItem(item, params) +{ + LLButton::Params button_params = params.delete_btn; + applyXUILayout(button_params, this); + addChild(LLUICtrlFactory::create(button_params)); + + setSeparatorVisible(true); +} + +bool LLPanelDeletableWearableListItem::postBuild() +{ + LLPanelWearableListItem::postBuild(); + + addWidgetToLeftSide("btn_delete"); + + LLButton* delete_btn = getChild("btn_delete"); + // Reserve space for 'delete' button event if it is invisible. + setLeftWidgetsWidth(delete_btn->getRect().mRight); + + setWidgetsVisible(false); + reshapeWidgets(); + + return true; +} + + +// static +LLPanelAttachmentListItem* LLPanelAttachmentListItem::create(LLViewerInventoryItem* item) +{ + LLPanelAttachmentListItem* list_item = NULL; + if(item) + { + const Params& params = LLUICtrlFactory::getDefaultParams(); + + list_item = new LLPanelAttachmentListItem(item, params); + list_item->initFromParams(params); + list_item->postBuild(); + } + return list_item; +} + +void LLPanelAttachmentListItem::updateItem(const std::string& name, + EItemState item_state) +{ + std::string title_joint = name; + + LLViewerInventoryItem* inv_item = getItem(); + if (inv_item && isAgentAvatarValid() && gAgentAvatarp->isWearingAttachment(inv_item->getLinkedUUID())) + { + std::string found_name; + bool found = gAgentAvatarp->getAttachedPointName(inv_item->getLinkedUUID(),found_name); + std::string trans_name = LLTrans::getString(found_name); + if (!found) + { + LL_WARNS() << "invalid attachment joint, err " << found_name << LL_ENDL; + } + title_joint = title_joint + " (" + trans_name + ")"; + } + + LLPanelInventoryListItemBase::updateItem(title_joint, item_state); +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +static LLWidgetNameRegistry::StaticRegistrar sRegisterPanelDummyClothingListItem(&typeid(LLPanelDummyClothingListItem::Params), "dummy_clothing_list_item"); + +LLPanelDummyClothingListItem::Params::Params() +: add_panel("add_panel"), + add_btn("add_btn") +{} + +LLPanelDummyClothingListItem* LLPanelDummyClothingListItem::create(LLWearableType::EType w_type) +{ + const Params& params = LLUICtrlFactory::getDefaultParams(); + + LLPanelDummyClothingListItem* list_item = new LLPanelDummyClothingListItem(w_type, params); + list_item->initFromParams(params); + list_item->postBuild(); + return list_item; +} + +bool LLPanelDummyClothingListItem::postBuild() +{ + addWidgetToRightSide("btn_add_panel"); + + setIconImage(LLInventoryIcon::getIcon(LLAssetType::AT_CLOTHING, LLInventoryType::IT_NONE, mWearableType, false)); + updateItem(wearableTypeToString(mWearableType)); + + // Make it look loke clothing item - reserve space for 'delete' button + setLeftWidgetsWidth(getChildView("item_icon")->getRect().mLeft); + + setWidgetsVisible(false); + reshapeWidgets(); + + return true; +} + +LLWearableType::EType LLPanelDummyClothingListItem::getWearableType() const +{ + return mWearableType; +} + +LLPanelDummyClothingListItem::LLPanelDummyClothingListItem(LLWearableType::EType w_type, const LLPanelDummyClothingListItem::Params& params) +: LLPanelWearableListItem(NULL, params), + mWearableType(w_type) +{ + LLPanel::Params panel_params(params.add_panel); + applyXUILayout(panel_params, this); + LLPanel* add_panelp = LLUICtrlFactory::create(panel_params); + addChild(add_panelp); + + if (add_panelp) +{ + LLButton::Params button_params(params.add_btn); + applyXUILayout(button_params, this); + add_panelp->addChild(LLUICtrlFactory::create(button_params)); +} + + setSeparatorVisible(true); +} + +typedef std::map clothing_to_string_map_t; + +clothing_to_string_map_t init_clothing_string_map() +{ + clothing_to_string_map_t w_map; + w_map.insert(std::make_pair(LLWearableType::WT_SHIRT, "shirt_not_worn")); + w_map.insert(std::make_pair(LLWearableType::WT_PANTS, "pants_not_worn")); + w_map.insert(std::make_pair(LLWearableType::WT_SHOES, "shoes_not_worn")); + w_map.insert(std::make_pair(LLWearableType::WT_SOCKS, "socks_not_worn")); + w_map.insert(std::make_pair(LLWearableType::WT_JACKET, "jacket_not_worn")); + w_map.insert(std::make_pair(LLWearableType::WT_GLOVES, "gloves_not_worn")); + w_map.insert(std::make_pair(LLWearableType::WT_UNDERSHIRT, "undershirt_not_worn")); + w_map.insert(std::make_pair(LLWearableType::WT_UNDERPANTS, "underpants_not_worn")); + w_map.insert(std::make_pair(LLWearableType::WT_SKIRT, "skirt_not_worn")); + w_map.insert(std::make_pair(LLWearableType::WT_ALPHA, "alpha_not_worn")); + w_map.insert(std::make_pair(LLWearableType::WT_TATTOO, "tattoo_not_worn")); + w_map.insert(std::make_pair(LLWearableType::WT_UNIVERSAL, "universal_not_worn")); + w_map.insert(std::make_pair(LLWearableType::WT_PHYSICS, "physics_not_worn")); + return w_map; +} + +std::string LLPanelDummyClothingListItem::wearableTypeToString(LLWearableType::EType w_type) +{ + static const clothing_to_string_map_t w_map = init_clothing_string_map(); + static const std::string invalid_str = LLTrans::getString("invalid_not_worn"); + + std::string type_str = invalid_str; + clothing_to_string_map_t::const_iterator it = w_map.find(w_type); + if(w_map.end() != it) + { + type_str = LLTrans::getString(it->second); + } + return type_str; +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +LLWearableItemTypeNameComparator::LLWearableTypeOrder::LLWearableTypeOrder(LLWearableItemTypeNameComparator::ETypeListOrder order_priority, bool sort_asset_by_name, bool sort_wearable_by_name): + mOrderPriority(order_priority), + mSortAssetTypeByName(sort_asset_by_name), + mSortWearableTypeByName(sort_wearable_by_name) +{ +} + +LLWearableItemTypeNameComparator::LLWearableItemTypeNameComparator() +{ + // By default the sort order conforms the order by spec of MY OUTFITS items list: + // 1. CLOTHING - sorted by name + // 2. OBJECT - sorted by type + // 3. BODYPART - sorted by name + mWearableOrder[LLAssetType::AT_CLOTHING] = LLWearableTypeOrder(ORDER_RANK_1, false, false); + mWearableOrder[LLAssetType::AT_OBJECT] = LLWearableTypeOrder(ORDER_RANK_2, true, true); + mWearableOrder[LLAssetType::AT_BODYPART] = LLWearableTypeOrder(ORDER_RANK_3, false, true); + mWearableOrder[LLAssetType::AT_GESTURE] = LLWearableTypeOrder(ORDER_RANK_4, true, false); +} + +void LLWearableItemTypeNameComparator::setOrder(LLAssetType::EType items_of_type, LLWearableItemTypeNameComparator::ETypeListOrder order_priority, bool sort_asset_items_by_name, bool sort_wearable_items_by_name) +{ + mWearableOrder[items_of_type] = LLWearableTypeOrder(order_priority, sort_asset_items_by_name, sort_wearable_items_by_name); +} + +/*virtual*/ +bool LLWearableItemNameComparator::doCompare(const LLPanelInventoryListItemBase* wearable_item1, const LLPanelInventoryListItemBase* wearable_item2) const +{ + std::string name1 = wearable_item1->getItemName(); + std::string name2 = wearable_item2->getItemName(); + + LLStringUtil::toUpper(name1); + LLStringUtil::toUpper(name2); + + return name1 < name2; +} + +/*virtual*/ +bool LLWearableItemTypeNameComparator::doCompare(const LLPanelInventoryListItemBase* wearable_item1, const LLPanelInventoryListItemBase* wearable_item2) const +{ + const LLAssetType::EType item_type1 = wearable_item1->getType(); + const LLAssetType::EType item_type2 = wearable_item2->getType(); + + LLWearableItemTypeNameComparator::ETypeListOrder item_type_order1 = getTypeListOrder(item_type1); + LLWearableItemTypeNameComparator::ETypeListOrder item_type_order2 = getTypeListOrder(item_type2); + + if (item_type_order1 != item_type_order2) + { + // If items are of different asset types we can compare them + // by types order in the list. + return item_type_order1 < item_type_order2; + } + + if (sortAssetTypeByName(item_type1)) + { + // If both items are of the same asset type except AT_CLOTHING and AT_BODYPART + // we can compare them by name. + return LLWearableItemNameComparator::doCompare(wearable_item1, wearable_item2); + } + + const LLWearableType::EType item_wearable_type1 = wearable_item1->getWearableType(); + const LLWearableType::EType item_wearable_type2 = wearable_item2->getWearableType(); + + if (item_wearable_type1 != item_wearable_type2) + // If items are of different LLWearableType::EType types they are compared + // by LLWearableType::EType. types order determined in LLWearableType::EType. + { + // If items are of different LLWearableType::EType types they are compared + // by LLWearableType::EType. types order determined in LLWearableType::EType. + return item_wearable_type1 < item_wearable_type2; + } + else + { + // If both items are of the same clothing type they are compared + // by description and place in reverse order (i.e. outer layer item + // on top) OR by name + if(sortWearableTypeByName(item_type1)) + { + return LLWearableItemNameComparator::doCompare(wearable_item1, wearable_item2); + } + return wearable_item1->getDescription() > wearable_item2->getDescription(); + } +} + +LLWearableItemTypeNameComparator::ETypeListOrder LLWearableItemTypeNameComparator::getTypeListOrder(LLAssetType::EType item_type) const +{ + wearable_type_order_map_t::const_iterator const_it = mWearableOrder.find(item_type); + + + if(const_it == mWearableOrder.end()) + { + LL_WARNS()<<"Absent information about order rang of items of "<second.mOrderPriority; +} + +bool LLWearableItemTypeNameComparator::sortAssetTypeByName(LLAssetType::EType item_type) const +{ + wearable_type_order_map_t::const_iterator const_it = mWearableOrder.find(item_type); + + + if(const_it == mWearableOrder.end()) + { + LL_WARNS()<<"Absent information about sorting items of "<second.mSortAssetTypeByName; + } + + +bool LLWearableItemTypeNameComparator::sortWearableTypeByName(LLAssetType::EType item_type) const +{ + wearable_type_order_map_t::const_iterator const_it = mWearableOrder.find(item_type); + + + if(const_it == mWearableOrder.end()) + { + LL_WARNS()<<"Absent information about sorting items of "<second.mSortWearableTypeByName; +} + +/*virtual*/ +bool LLWearableItemCreationDateComparator::doCompare(const LLPanelInventoryListItemBase* item1, const LLPanelInventoryListItemBase* item2) const +{ + time_t date1 = item1->getCreationDate(); + time_t date2 = item2->getCreationDate(); + + if (date1 == date2) + { + return LLWearableItemNameComparator::doCompare(item1, item2); + } + + return date1 > date2; +} +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +static LLWearableItemTypeNameComparator WEARABLE_TYPE_NAME_COMPARATOR; +static const LLWearableItemTypeNameComparator WEARABLE_TYPE_LAYER_COMPARATOR; +static const LLWearableItemNameComparator WEARABLE_NAME_COMPARATOR; +static const LLWearableItemCreationDateComparator WEARABLE_CREATION_DATE_COMPARATOR; + +static const LLDefaultChildRegistry::Register r("wearable_items_list"); + +LLWearableItemsList::Params::Params() +: standalone("standalone", true) +, worn_indication_enabled("worn_indication_enabled", true) +, show_item_widgets("show_item_widgets", false) +{} + +LLWearableItemsList::LLWearableItemsList(const LLWearableItemsList::Params& p) +: LLInventoryItemsList(p) +{ + setSortOrder(E_SORT_BY_TYPE_LAYER, false); + mMenuWearableType = LLWearableType::WT_NONE; + mIsStandalone = p.standalone; + if (mIsStandalone) + { + // Use built-in context menu. + setRightMouseDownCallback(boost::bind(&LLWearableItemsList::onRightClick, this, _2, _3)); + } + mWornIndicationEnabled = p.worn_indication_enabled; + setNoItemsCommentText(LLTrans::getString("LoadingData")); + mShowItemWidgets = p.show_item_widgets; +} + +// virtual +LLWearableItemsList::~LLWearableItemsList() +{} + +// virtual +LLPanel* LLWearableItemsList::createNewItem(LLViewerInventoryItem* item) +{ + if (!item) + { + LL_WARNS() << "No inventory item. Couldn't create flat list item." << LL_ENDL; + llassert(item != NULL); + return NULL; + } + + return LLPanelWearableOutfitItem::create(item, mWornIndicationEnabled, mShowItemWidgets); +} + +void LLWearableItemsList::updateList(const LLUUID& category_id) +{ + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + + LLFindOutfitItems collector = LLFindOutfitItems(); + // collectDescendentsIf takes non-const reference: + gInventory.collectDescendentsIf( + category_id, + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH, + collector); + + if(item_array.empty() && gInventory.isCategoryComplete(category_id)) + { + setNoItemsCommentText(LLTrans::getString("EmptyOutfitText")); + } + + refreshList(item_array); +} + +void LLWearableItemsList::updateChangedItems(const uuid_vec_t& changed_items_uuids) +{ + // nothing to update + if (changed_items_uuids.empty()) + return; + + uuid_vec_t::const_iterator uuids_begin = changed_items_uuids.begin(), uuids_end = changed_items_uuids.end(); + pairs_const_iterator_t pairs_iter = getItemPairs().begin(), pairs_end = getItemPairs().end(); + while (pairs_iter != pairs_end) + { + LLPanel* panel = (*(pairs_iter++))->first; + LLPanelInventoryListItemBase* item = dynamic_cast(panel); + if (!item) + continue; + + LLViewerInventoryItem* inv_item = item->getItem(); + if (!inv_item) + continue; + + const LLUUID& linked_uuid = inv_item->getLinkedUUID(); + if (std::find(uuids_begin, uuids_end, linked_uuid) != uuids_end) + { + item->setNeedsRefresh(true); + } + } +} + +void LLWearableItemsList::onRightClick(S32 x, S32 y) +{ + uuid_vec_t selected_uuids; + + getSelectedUUIDs(selected_uuids); + if (selected_uuids.empty()) + { + if ((mMenuWearableType != LLWearableType::WT_NONE) && (size() == 0)) + { + ContextMenu::instance().show(this, mMenuWearableType, x, y); + } + } + else + { + ContextMenu::instance().show(this, selected_uuids, x, y); + } +} + +void LLWearableItemsList::setSortOrder(ESortOrder sort_order, bool sort_now) +{ + switch (sort_order) + { + case E_SORT_BY_MOST_RECENT: + setComparator(&WEARABLE_CREATION_DATE_COMPARATOR); + break; + case E_SORT_BY_NAME: + setComparator(&WEARABLE_NAME_COMPARATOR); + break; + case E_SORT_BY_TYPE_LAYER: + setComparator(&WEARABLE_TYPE_LAYER_COMPARATOR); + break; + case E_SORT_BY_TYPE_NAME: + { + WEARABLE_TYPE_NAME_COMPARATOR.setOrder(LLAssetType::AT_CLOTHING, LLWearableItemTypeNameComparator::ORDER_RANK_1, false, true); + setComparator(&WEARABLE_TYPE_NAME_COMPARATOR); + break; + } + + // No "default:" to raise compiler warning + // if we're not handling something + } + + mSortOrder = sort_order; + + if (sort_now) + { + sort(); + } +} + +////////////////////////////////////////////////////////////////////////// +/// ContextMenu +////////////////////////////////////////////////////////////////////////// + +LLWearableItemsList::ContextMenu::ContextMenu() +: mParent(NULL) +{ +} + +void LLWearableItemsList::ContextMenu::show(LLView* spawning_view, const uuid_vec_t& uuids, S32 x, S32 y) +{ + mParent = dynamic_cast(spawning_view); + LLListContextMenu::show(spawning_view, uuids, x, y); + mParent = NULL; // to avoid dereferencing an invalid pointer +} + +void LLWearableItemsList::ContextMenu::show(LLView* spawning_view, LLWearableType::EType w_type, S32 x, S32 y) +{ + mParent = dynamic_cast(spawning_view); + LLContextMenu* menup = mMenuHandle.get(); + if (menup) + { + //preventing parent (menu holder) from deleting already "dead" context menus on exit + LLView* parent = menup->getParent(); + if (parent) + { + parent->removeChild(menup); + } + delete menup; + mUUIDs.clear(); + } + + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + registrar.add("Wearable.CreateNew", boost::bind(createNewWearableByType, w_type)); + menup = createFromFile("menu_wearable_list_item.xml"); + if (!menup) + { + LL_WARNS() << "Context menu creation failed" << LL_ENDL; + return; + } + setMenuItemVisible(menup, "create_new", true); + setMenuItemEnabled(menup, "create_new", true); + setMenuItemVisible(menup, "wearable_attach_to", false); + setMenuItemVisible(menup, "wearable_attach_to_hud", false); + + std::string new_label = LLTrans::getString("create_new_" + LLWearableType::getInstance()->getTypeName(w_type)); + LLMenuItemGL* menu_item = menup->getChild("create_new"); + menu_item->setLabel(new_label); + + mMenuHandle = menup->getHandle(); + menup->show(x, y); + LLMenuGL::showPopup(spawning_view, menup, x, y); + + mParent = NULL; // to avoid dereferencing an invalid pointer +} + +// virtual +LLContextMenu* LLWearableItemsList::ContextMenu::createMenu() +{ + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + const uuid_vec_t& ids = mUUIDs; // selected items IDs + LLUUID selected_id = ids.front(); // ID of the first selected item + + // Register handlers common for all wearable types. + registrar.add("Wearable.Wear", boost::bind(wear_multiple, ids, true)); + registrar.add("Wearable.Add", boost::bind(wear_multiple, ids, false)); + registrar.add("Wearable.Edit", boost::bind(handle_item_edit, selected_id)); + registrar.add("Wearable.CreateNew", boost::bind(createNewWearable, selected_id)); + registrar.add("Wearable.ShowOriginal", boost::bind(show_item_original, selected_id)); + registrar.add("Wearable.TakeOffDetach", + boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), ids, no_op)); + + // Register handlers for clothing. + registrar.add("Clothing.TakeOff", + boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), ids, no_op)); + + // Register handlers for body parts. + + // Register handlers for attachments. + registrar.add("Attachment.Detach", + boost::bind(&LLAppearanceMgr::removeItemsFromAvatar, LLAppearanceMgr::getInstance(), ids, no_op)); + registrar.add("Attachment.Touch", boost::bind(handle_attachment_touch, selected_id)); + registrar.add("Attachment.Profile", boost::bind(show_item_profile, selected_id)); + registrar.add("Object.Attach", boost::bind(LLViewerAttachMenu::attachObjects, ids, _2)); + + // Create the menu. + LLContextMenu* menu = createFromFile("menu_wearable_list_item.xml"); + + // Determine which items should be visible/enabled. + updateItemsVisibility(menu); + + // Update labels for the items requiring that. + updateItemsLabels(menu); + return menu; +} + +void LLWearableItemsList::ContextMenu::updateItemsVisibility(LLContextMenu* menu) +{ + if (!menu) + { + LL_WARNS() << "Invalid menu" << LL_ENDL; + return; + } + + const uuid_vec_t& ids = mUUIDs; // selected items IDs + U32 mask = 0; // mask of selected items' types + U32 n_items = ids.size(); // number of selected items + U32 n_worn = 0; // number of worn items among the selected ones + U32 n_already_worn = 0; // number of items worn of same type as selected items + U32 n_links = 0; // number of links among the selected items + U32 n_editable = 0; // number of editable items among the selected ones + U32 n_touchable = 0; // number of touchable items among the selected ones + + bool can_be_worn = true; + + for (uuid_vec_t::const_iterator it = ids.begin(); it != ids.end(); ++it) + { + LLUUID id = *it; + LLViewerInventoryItem* item = gInventory.getItem(id); + + if (!item) + { + LL_WARNS() << "Invalid item" << LL_ENDL; + // *NOTE: the logic below may not work in this case + continue; + } + + updateMask(mask, item->getType()); + + const LLWearableType::EType wearable_type = item->getWearableType(); + const bool is_link = item->getIsLinkType(); + const bool is_worn = get_is_item_worn(id); + const bool is_editable = get_is_item_editable(id); + const bool is_touchable = enable_attachment_touch(id); + const bool is_already_worn = gAgentWearables.selfHasWearable(wearable_type); + if (is_worn) + { + ++n_worn; + } + if (is_touchable) + { + ++n_touchable; + } + if (is_editable) + { + ++n_editable; + } + if (is_link) + { + ++n_links; + } + if (is_already_worn) + { + ++n_already_worn; + } + + if (can_be_worn) + { + can_be_worn = get_can_item_be_worn(item->getLinkedUUID()); + } + } // for + + bool standalone = mParent ? mParent->isStandalone() : false; + bool wear_add_visible = mask & (MASK_CLOTHING|MASK_ATTACHMENT) && n_worn == 0 && can_be_worn && (n_already_worn != 0 || mask & MASK_ATTACHMENT); + + // *TODO: eliminate multiple traversals over the menu items + setMenuItemVisible(menu, "wear_wear", n_already_worn == 0 && n_worn == 0 && can_be_worn); + setMenuItemEnabled(menu, "wear_wear", n_already_worn == 0 && n_worn == 0); + setMenuItemVisible(menu, "wear_add", wear_add_visible); + setMenuItemEnabled(menu, "wear_add", LLAppearanceMgr::instance().canAddWearables(ids)); + setMenuItemVisible(menu, "wear_replace", n_worn == 0 && n_already_worn != 0 && can_be_worn); + //visible only when one item selected and this item is worn + setMenuItemVisible(menu, "touch", !standalone && mask == MASK_ATTACHMENT && n_worn == n_items); + setMenuItemEnabled(menu, "touch", n_touchable && n_worn == 1 && n_items == 1); + setMenuItemVisible(menu, "edit", !standalone && mask & (MASK_CLOTHING|MASK_BODYPART|MASK_ATTACHMENT) && n_worn == n_items); + setMenuItemEnabled(menu, "edit", n_editable && n_worn == 1 && n_items == 1); + setMenuItemVisible(menu, "create_new", mask & (MASK_CLOTHING|MASK_BODYPART) && n_items == 1); + setMenuItemEnabled(menu, "create_new", LLAppearanceMgr::instance().canAddWearables(ids)); + setMenuItemVisible(menu, "show_original", !standalone); + setMenuItemEnabled(menu, "show_original", n_items == 1 && n_links == n_items); + setMenuItemVisible(menu, "take_off", mask == MASK_CLOTHING && n_worn == n_items); + setMenuItemVisible(menu, "detach", mask == MASK_ATTACHMENT && n_worn == n_items); + setMenuItemVisible(menu, "take_off_or_detach", mask == (MASK_ATTACHMENT|MASK_CLOTHING)); + setMenuItemEnabled(menu, "take_off_or_detach", n_worn == n_items); + setMenuItemVisible(menu, "object_profile", !standalone); + setMenuItemEnabled(menu, "object_profile", n_items == 1); + setMenuItemVisible(menu, "--no options--", false); + setMenuItemEnabled(menu, "--no options--", false); + + // Populate or hide the "Attach to..." / "Attach to HUD..." submenus. + if (mask == MASK_ATTACHMENT && n_worn == 0) + { + LLViewerAttachMenu::populateMenus("wearable_attach_to", "wearable_attach_to_hud"); + } + else + { + setMenuItemVisible(menu, "wearable_attach_to", false); + setMenuItemVisible(menu, "wearable_attach_to_hud", false); + } + + if (mask & MASK_UNKNOWN) + { + LL_WARNS() << "Non-wearable items passed." << LL_ENDL; + } + + U32 num_visible_items = 0; + for (U32 menu_item_index = 0; menu_item_index < menu->getItemCount(); ++menu_item_index) + { + const LLMenuItemGL* menu_item = menu->getItem(menu_item_index); + if (menu_item && menu_item->getVisible()) + { + num_visible_items++; + } + } + if (num_visible_items == 0) + { + setMenuItemVisible(menu, "--no options--", true); + } +} + +void LLWearableItemsList::ContextMenu::updateItemsLabels(LLContextMenu* menu) +{ + llassert(menu); + if (!menu) return; + + // Set proper label for the "Create new " menu item. + LLViewerInventoryItem* item = gInventory.getLinkedItem(mUUIDs.back()); + if (!item || !item->isWearableType()) return; + + LLWearableType::EType w_type = item->getWearableType(); + std::string new_label = LLTrans::getString("create_new_" + LLWearableType::getInstance()->getTypeName(w_type)); + + LLMenuItemGL* menu_item = menu->getChild("create_new"); + menu_item->setLabel(new_label); +} + +// We need this method to convert non-zero bool values to exactly 1 (true). +// Otherwise code relying on a bool value being true may fail +// (I experienced a weird assert in LLView::drawChildren() because of that. +// static +void LLWearableItemsList::ContextMenu::setMenuItemVisible(LLContextMenu* menu, const std::string& name, bool val) +{ + menu->setItemVisible(name, val); +} + +// static +void LLWearableItemsList::ContextMenu::setMenuItemEnabled(LLContextMenu* menu, const std::string& name, bool val) +{ + menu->setItemEnabled(name, val); +} + +// static +void LLWearableItemsList::ContextMenu::updateMask(U32& mask, LLAssetType::EType at) +{ + if (at == LLAssetType::AT_CLOTHING) + { + mask |= MASK_CLOTHING; + } + else if (at == LLAssetType::AT_BODYPART) + { + mask |= MASK_BODYPART; + } + else if (at == LLAssetType::AT_OBJECT) + { + mask |= MASK_ATTACHMENT; + } + else if (at == LLAssetType::AT_GESTURE) + { + mask |= MASK_GESTURE; + } + else + { + mask |= MASK_UNKNOWN; + } +} + +// static +void LLWearableItemsList::ContextMenu::createNewWearable(const LLUUID& item_id) +{ + LLViewerInventoryItem* item = gInventory.getLinkedItem(item_id); + if (!item || !item->isWearableType()) return; + + LLAgentWearables::createWearable(item->getWearableType(), true); +} + +// static +void LLWearableItemsList::ContextMenu::createNewWearableByType(LLWearableType::EType type) +{ + LLAgentWearables::createWearable(type, true); +} + +// EOF diff --git a/indra/newview/llwearableitemslist.h b/indra/newview/llwearableitemslist.h index 7af76ee40d..7b69711154 100644 --- a/indra/newview/llwearableitemslist.h +++ b/indra/newview/llwearableitemslist.h @@ -1,508 +1,508 @@ -/** - * @file llwearableitemslist.h - * @brief A flat list of wearable items. - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLWEARABLEITEMSLIST_H -#define LL_LLWEARABLEITEMSLIST_H - -// libs -#include "llpanel.h" -#include "llsingleton.h" - -// newview -#include "llinventoryitemslist.h" -#include "llinventorylistitem.h" -#include "lllistcontextmenu.h" -#include "llwearabletype.h" - -/** - * @class LLPanelWearableListItem - * - * Extends LLPanelInventoryListItemBase: - * - makes side widgets show on mouse_enter and hide on - * mouse_leave events. - * - provides callback for button clicks - */ -class LLPanelWearableListItem : public LLPanelInventoryListItemBase -{ - LOG_CLASS(LLPanelWearableListItem); -public: - - /** - * Shows buttons when mouse is over - */ - /*virtual*/ void onMouseEnter(S32 x, S32 y, MASK mask); - - /** - * Hides buttons when mouse is out - */ - /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask); - -protected: - - LLPanelWearableListItem(LLViewerInventoryItem* item, const Params& params); -}; - -/** - * @class LLPanelWearableOutfitItem - * - * Outfit item for "My Outfits" list. - * Extends LLPanelInventoryListItemBase with handling - * double click to wear the item. - */ -class LLPanelWearableOutfitItem : public LLPanelWearableListItem -{ - LOG_CLASS(LLPanelWearableOutfitItem); -public: - struct Params : public LLInitParam::Block - { - Optional add_btn, remove_btn; - - Params(); - }; - - bool postBuild(); - bool handleDoubleClick(S32 x, S32 y, MASK mask); - - static LLPanelWearableOutfitItem* create(LLViewerInventoryItem* item, - bool worn_indication_enabled, - bool show_widgets); - - /** - * Updates item name and (worn) suffix. - */ - /*virtual*/ void updateItem(const std::string& name, - EItemState item_state = IS_DEFAULT); - - void onAddWearable(); - void onRemoveWearable(); - -protected: - LLPanelWearableOutfitItem(LLViewerInventoryItem* item, - bool worn_indication_enabled, const Params& params, bool show_widgets = false); - -private: - bool mWornIndicationEnabled; - bool mShowWidgets; -}; - -class LLPanelDeletableWearableListItem : public LLPanelWearableListItem -{ - LOG_CLASS(LLPanelDeletableWearableListItem); -public: - struct Params : public LLInitParam::Block - { - Optional delete_btn; - - Params(); - }; - - - static LLPanelDeletableWearableListItem* create(LLViewerInventoryItem* item); - - virtual ~LLPanelDeletableWearableListItem() {}; - - /*virtual*/ bool postBuild(); - - /** - * Make button visible during mouse over event. - */ - inline void setShowDeleteButton(bool show) { setShowWidget("btn_delete", show); } - -protected: - LLPanelDeletableWearableListItem(LLViewerInventoryItem* item, const Params& params); -}; - -/** Outfit list item for an attachment */ -class LLPanelAttachmentListItem : public LLPanelDeletableWearableListItem -{ - LOG_CLASS(LLPanelAttachmentListItem); -public: - static LLPanelAttachmentListItem* create(LLViewerInventoryItem* item); - virtual ~LLPanelAttachmentListItem() {}; - - /** Set item title. Joint name is added to the title in parenthesis */ - /*virtual*/ void updateItem(const std::string& name, - EItemState item_state = IS_DEFAULT); - -protected: - LLPanelAttachmentListItem(LLViewerInventoryItem* item, const Params& params) : LLPanelDeletableWearableListItem(item, params) {}; -}; - -/** - * @class LLPanelClothingListItem - * - * Provides buttons for editing, moving, deleting a wearable. - */ -class LLPanelClothingListItem : public LLPanelDeletableWearableListItem -{ - LOG_CLASS(LLPanelClothingListItem); -public: - - struct Params : public LLInitParam::Block - { - Optional up_btn, - down_btn, - edit_btn; - Optional lock_panel, - edit_panel; - Optional lock_icon; - - Params(); - }; - - static LLPanelClothingListItem* create(LLViewerInventoryItem* item); - - virtual ~LLPanelClothingListItem(); - - /*virtual*/ bool postBuild(); - - /** - * Make button visible during mouse over event. - */ - inline void setShowMoveUpButton(bool show) { setShowWidget("btn_move_up", show); } - - inline void setShowMoveDownButton(bool show) { setShowWidget("btn_move_down", show); } - inline void setShowLockButton(bool show) { setShowWidget("btn_lock", show); } - inline void setShowEditButton(bool show) { setShowWidget("btn_edit_panel", show); } - -protected: - - LLPanelClothingListItem(LLViewerInventoryItem* item, const Params& params); - -}; - -class LLPanelBodyPartsListItem : public LLPanelWearableListItem -{ - LOG_CLASS(LLPanelBodyPartsListItem); -public: - struct Params : public LLInitParam::Block - { - Optional edit_btn; - Optional lock_panel, - edit_panel; - Optional lock_icon; - - Params(); - }; - - static LLPanelBodyPartsListItem* create(LLViewerInventoryItem* item); - - virtual ~LLPanelBodyPartsListItem(); - - /*virtual*/ bool postBuild(); - - /** - * Make button visible during mouse over event. - */ - inline void setShowLockButton(bool show) { setShowWidget("btn_lock", show); } - inline void setShowEditButton(bool show) { setShowWidget("btn_edit_panel", show); } - -protected: - LLPanelBodyPartsListItem(LLViewerInventoryItem* item, const Params& params); -}; - - -/** - * @class LLPanelDummyClothingListItem - * - * A dummy item panel - displays grayed clothing icon, grayed title ' not worn' and 'add' button - */ -class LLPanelDummyClothingListItem : public LLPanelWearableListItem -{ -public: - struct Params : public LLInitParam::Block - { - Optional add_panel; - Optional add_btn; - Params(); - }; - static LLPanelDummyClothingListItem* create(LLWearableType::EType w_type); - - /*virtual*/ bool postBuild(); - LLWearableType::EType getWearableType() const; - -protected: - LLPanelDummyClothingListItem(LLWearableType::EType w_type, const Params& params); - - static std::string wearableTypeToString(LLWearableType::EType w_type); - -private: - LLWearableType::EType mWearableType; -}; - -/** - * @class LLWearableListItemComparator - * - * Abstract comparator of wearable list items. - */ -class LLWearableListItemComparator : public LLFlatListView::ItemComparator -{ - LOG_CLASS(LLWearableListItemComparator); - -public: - LLWearableListItemComparator() {}; - virtual ~LLWearableListItemComparator() {}; - - virtual bool compare(const LLPanel* item1, const LLPanel* item2) const - { - const LLPanelInventoryListItemBase* wearable_item1 = dynamic_cast(item1); - const LLPanelInventoryListItemBase* wearable_item2 = dynamic_cast(item2); - - if (!wearable_item1 || !wearable_item2) - { - LL_WARNS() << "item1 and item2 cannot be null" << LL_ENDL; - return true; - } - - return doCompare(wearable_item1, wearable_item2); - } - -protected: - - /** - * Returns true if wearable_item1 < wearable_item2, false otherwise - * Implement this method in your particular comparator. - */ - virtual bool doCompare(const LLPanelInventoryListItemBase* wearable_item1, const LLPanelInventoryListItemBase* wearable_item2) const = 0; -}; - -/** - * @class LLWearableItemNameComparator - * - * Comparator for sorting wearable list items by name. - */ -class LLWearableItemNameComparator : public LLWearableListItemComparator -{ - LOG_CLASS(LLWearableItemNameComparator); - -public: - LLWearableItemNameComparator() {}; - virtual ~LLWearableItemNameComparator() {}; - -protected: - /*virtual*/ bool doCompare(const LLPanelInventoryListItemBase* wearable_item1, const LLPanelInventoryListItemBase* wearable_item2) const; -}; - -/** - * @class LLWearableItemTypeNameComparator - * - * Comparator for sorting wearable list items by type and name. - */ -class LLWearableItemTypeNameComparator : public LLWearableItemNameComparator -{ - LOG_CLASS(LLWearableItemTypeNameComparator); - -public: - - LLWearableItemTypeNameComparator(); - virtual ~LLWearableItemTypeNameComparator() {}; - - enum ETypeListOrder - { - ORDER_RANK_1 = 1, - ORDER_RANK_2, - ORDER_RANK_3, - ORDER_RANK_4, - ORDER_RANK_UNKNOWN - }; - - void setOrder(LLAssetType::EType items_of_type, ETypeListOrder order_priority, bool sort_items_by_name, bool sort_wearable_items_by_name); - -protected: - /** - * All information about sort order is stored in mWearableOrder map - * - * mWearableOrder : KEYS VALUES - * [LLAssetType] [struct LLWearableTypeOrder] - * - *--------------------------------------------------------------------------------------------- - * I. Determines order (ORDER_RANK) in which items of LLAssetType should be displayed in list. - * For example by spec in MY OUTFITS the order is: - * 1. AT_CLOTHING (ORDER_RANK_1) - * 2. AT_OBJECT (ORDER_RANK_2) - * 3. AT_BODYPART (ORDER_RANK_3) - * - * II.Items of each type(LLAssetType) are sorted by name or type(LLWearableType) - * For example by spec in MY OUTFITS the order within each items type(LLAssetType) is: - * 1. AT_OBJECTS (abc order) - * 2. AT_CLOTHINGS - * - by type (types order determined in LLWearableType::EType) - * - outer layer on top - * 3. AT_BODYPARTS (abc order) - *--------------------------------------------------------------------------------------------- - * - * For each LLAssetType (KEYS in mWearableOrder) the information about: - * - * I. ORDER_RANK (the flag is LLWearableTypeOrder::mOrderPriority) - * - * II. whether items of this LLAssetType type should be ordered - * by name or by LLWearableType::EType (the flag is LLWearableTypeOrder::mSortAssetTypeByName) - * - * III.whether items of LLWearableType type within this LLAssetType - * should be ordered by name (the flag is LLWearableTypeOrder::mSortWearableTypeByName) - * - * holds in mWearableOrder map as VALUES (struct LLWearableTypeOrder). - */ - /*virtual*/ bool doCompare(const LLPanelInventoryListItemBase* wearable_item1, const LLPanelInventoryListItemBase* wearable_item2) const; - -private: - - struct LLWearableTypeOrder - { - ETypeListOrder mOrderPriority; - bool mSortAssetTypeByName; - bool mSortWearableTypeByName; - - LLWearableTypeOrder(ETypeListOrder order_priority, bool sort_asset_by_name, bool sort_wearable_by_name); - LLWearableTypeOrder() : mOrderPriority(ORDER_RANK_UNKNOWN), mSortAssetTypeByName(false), mSortWearableTypeByName(false) {}; - }; - - ETypeListOrder getTypeListOrder(LLAssetType::EType item_type) const; - - bool sortAssetTypeByName(LLAssetType::EType item_type) const; - bool sortWearableTypeByName(LLAssetType::EType item_type) const; - - typedef std::map wearable_type_order_map_t; - wearable_type_order_map_t mWearableOrder; -}; - -/** - * @class LLWearableItemCreationDateComparator - * - * Comparator for sorting wearable list items by creation date (newest go first). - */ -class LLWearableItemCreationDateComparator : public LLWearableItemNameComparator -{ - LOG_CLASS(LLWearableItemCreationDateComparator); - -public: - // clang demands a default ctor here - LLWearableItemCreationDateComparator() {} - -protected: - /*virtual*/ bool doCompare(const LLPanelInventoryListItemBase* item1, const LLPanelInventoryListItemBase* item2) const; -}; - -/** - * @class LLWearableItemsList - * - * A flat list of wearable inventory items. - * Collects all items that can be a part of an outfit from - * an inventory category specified by UUID and displays them - * as a flat list. - */ -class LLWearableItemsList : public LLInventoryItemsList -{ -public: - /** - * Context menu. - * - * This menu is likely to be used from outside - * (e.g. for items selected across multiple wearable lists), - * so making it a singleton. - */ - class ContextMenu : public LLListContextMenu, public LLSingleton - { - LLSINGLETON(ContextMenu); - public: - /*virtual*/ void show(LLView* spawning_view, const uuid_vec_t& uuids, S32 x, S32 y) override; - - void show(LLView* spawning_view, LLWearableType::EType w_type, S32 x, S32 y); - - protected: - enum { - MASK_CLOTHING = 0x01, - MASK_BODYPART = 0x02, - MASK_ATTACHMENT = 0x04, - MASK_GESTURE = 0x08, - MASK_UNKNOWN = 0x10, - }; - - /* virtual */ LLContextMenu* createMenu() override; - void updateItemsVisibility(LLContextMenu* menu); - void updateItemsLabels(LLContextMenu* menu); - static void setMenuItemVisible(LLContextMenu* menu, const std::string& name, bool val); - static void setMenuItemEnabled(LLContextMenu* menu, const std::string& name, bool val); - static void updateMask(U32& mask, LLAssetType::EType at); - static void createNewWearable(const LLUUID& item_id); - static void createNewWearableByType(LLWearableType::EType type); - - LLWearableItemsList* mParent; - }; - - struct Params : public LLInitParam::Block - { - Optional standalone; - Optional worn_indication_enabled; - Optional show_item_widgets; - - Params(); - }; - - typedef enum e_sort_order { - // Values should be compatible with InventorySortOrder setting. - E_SORT_BY_NAME = 0, - E_SORT_BY_MOST_RECENT = 1, - E_SORT_BY_TYPE_LAYER = 2, - E_SORT_BY_TYPE_NAME = 3, - } ESortOrder; - - virtual ~LLWearableItemsList(); - - /*virtual*/ LLPanel* createNewItem(LLViewerInventoryItem* item) override; - - void updateList(const LLUUID& category_id); - - /** - * Update items that match UUIDs from changed_items_uuids - * or links that point at such items. - */ - void updateChangedItems(const uuid_vec_t& changed_items_uuids); - - bool isStandalone() const { return mIsStandalone; } - - ESortOrder getSortOrder() const { return mSortOrder; } - - void setSortOrder(ESortOrder sort_order, bool sort_now = true); - - void setMenuWearableType(LLWearableType::EType type) { mMenuWearableType = type; } - -protected: - friend class LLUICtrlFactory; - LLWearableItemsList(const LLWearableItemsList::Params& p); - - void onRightClick(S32 x, S32 y); - - bool mIsStandalone; - bool mWornIndicationEnabled; - bool mShowItemWidgets; - - ESortOrder mSortOrder; - - LLWearableType::EType mMenuWearableType; -}; - -#endif //LL_LLWEARABLEITEMSLIST_H +/** + * @file llwearableitemslist.h + * @brief A flat list of wearable items. + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLWEARABLEITEMSLIST_H +#define LL_LLWEARABLEITEMSLIST_H + +// libs +#include "llpanel.h" +#include "llsingleton.h" + +// newview +#include "llinventoryitemslist.h" +#include "llinventorylistitem.h" +#include "lllistcontextmenu.h" +#include "llwearabletype.h" + +/** + * @class LLPanelWearableListItem + * + * Extends LLPanelInventoryListItemBase: + * - makes side widgets show on mouse_enter and hide on + * mouse_leave events. + * - provides callback for button clicks + */ +class LLPanelWearableListItem : public LLPanelInventoryListItemBase +{ + LOG_CLASS(LLPanelWearableListItem); +public: + + /** + * Shows buttons when mouse is over + */ + /*virtual*/ void onMouseEnter(S32 x, S32 y, MASK mask); + + /** + * Hides buttons when mouse is out + */ + /*virtual*/ void onMouseLeave(S32 x, S32 y, MASK mask); + +protected: + + LLPanelWearableListItem(LLViewerInventoryItem* item, const Params& params); +}; + +/** + * @class LLPanelWearableOutfitItem + * + * Outfit item for "My Outfits" list. + * Extends LLPanelInventoryListItemBase with handling + * double click to wear the item. + */ +class LLPanelWearableOutfitItem : public LLPanelWearableListItem +{ + LOG_CLASS(LLPanelWearableOutfitItem); +public: + struct Params : public LLInitParam::Block + { + Optional add_btn, remove_btn; + + Params(); + }; + + bool postBuild(); + bool handleDoubleClick(S32 x, S32 y, MASK mask); + + static LLPanelWearableOutfitItem* create(LLViewerInventoryItem* item, + bool worn_indication_enabled, + bool show_widgets); + + /** + * Updates item name and (worn) suffix. + */ + /*virtual*/ void updateItem(const std::string& name, + EItemState item_state = IS_DEFAULT); + + void onAddWearable(); + void onRemoveWearable(); + +protected: + LLPanelWearableOutfitItem(LLViewerInventoryItem* item, + bool worn_indication_enabled, const Params& params, bool show_widgets = false); + +private: + bool mWornIndicationEnabled; + bool mShowWidgets; +}; + +class LLPanelDeletableWearableListItem : public LLPanelWearableListItem +{ + LOG_CLASS(LLPanelDeletableWearableListItem); +public: + struct Params : public LLInitParam::Block + { + Optional delete_btn; + + Params(); + }; + + + static LLPanelDeletableWearableListItem* create(LLViewerInventoryItem* item); + + virtual ~LLPanelDeletableWearableListItem() {}; + + /*virtual*/ bool postBuild(); + + /** + * Make button visible during mouse over event. + */ + inline void setShowDeleteButton(bool show) { setShowWidget("btn_delete", show); } + +protected: + LLPanelDeletableWearableListItem(LLViewerInventoryItem* item, const Params& params); +}; + +/** Outfit list item for an attachment */ +class LLPanelAttachmentListItem : public LLPanelDeletableWearableListItem +{ + LOG_CLASS(LLPanelAttachmentListItem); +public: + static LLPanelAttachmentListItem* create(LLViewerInventoryItem* item); + virtual ~LLPanelAttachmentListItem() {}; + + /** Set item title. Joint name is added to the title in parenthesis */ + /*virtual*/ void updateItem(const std::string& name, + EItemState item_state = IS_DEFAULT); + +protected: + LLPanelAttachmentListItem(LLViewerInventoryItem* item, const Params& params) : LLPanelDeletableWearableListItem(item, params) {}; +}; + +/** + * @class LLPanelClothingListItem + * + * Provides buttons for editing, moving, deleting a wearable. + */ +class LLPanelClothingListItem : public LLPanelDeletableWearableListItem +{ + LOG_CLASS(LLPanelClothingListItem); +public: + + struct Params : public LLInitParam::Block + { + Optional up_btn, + down_btn, + edit_btn; + Optional lock_panel, + edit_panel; + Optional lock_icon; + + Params(); + }; + + static LLPanelClothingListItem* create(LLViewerInventoryItem* item); + + virtual ~LLPanelClothingListItem(); + + /*virtual*/ bool postBuild(); + + /** + * Make button visible during mouse over event. + */ + inline void setShowMoveUpButton(bool show) { setShowWidget("btn_move_up", show); } + + inline void setShowMoveDownButton(bool show) { setShowWidget("btn_move_down", show); } + inline void setShowLockButton(bool show) { setShowWidget("btn_lock", show); } + inline void setShowEditButton(bool show) { setShowWidget("btn_edit_panel", show); } + +protected: + + LLPanelClothingListItem(LLViewerInventoryItem* item, const Params& params); + +}; + +class LLPanelBodyPartsListItem : public LLPanelWearableListItem +{ + LOG_CLASS(LLPanelBodyPartsListItem); +public: + struct Params : public LLInitParam::Block + { + Optional edit_btn; + Optional lock_panel, + edit_panel; + Optional lock_icon; + + Params(); + }; + + static LLPanelBodyPartsListItem* create(LLViewerInventoryItem* item); + + virtual ~LLPanelBodyPartsListItem(); + + /*virtual*/ bool postBuild(); + + /** + * Make button visible during mouse over event. + */ + inline void setShowLockButton(bool show) { setShowWidget("btn_lock", show); } + inline void setShowEditButton(bool show) { setShowWidget("btn_edit_panel", show); } + +protected: + LLPanelBodyPartsListItem(LLViewerInventoryItem* item, const Params& params); +}; + + +/** + * @class LLPanelDummyClothingListItem + * + * A dummy item panel - displays grayed clothing icon, grayed title ' not worn' and 'add' button + */ +class LLPanelDummyClothingListItem : public LLPanelWearableListItem +{ +public: + struct Params : public LLInitParam::Block + { + Optional add_panel; + Optional add_btn; + Params(); + }; + static LLPanelDummyClothingListItem* create(LLWearableType::EType w_type); + + /*virtual*/ bool postBuild(); + LLWearableType::EType getWearableType() const; + +protected: + LLPanelDummyClothingListItem(LLWearableType::EType w_type, const Params& params); + + static std::string wearableTypeToString(LLWearableType::EType w_type); + +private: + LLWearableType::EType mWearableType; +}; + +/** + * @class LLWearableListItemComparator + * + * Abstract comparator of wearable list items. + */ +class LLWearableListItemComparator : public LLFlatListView::ItemComparator +{ + LOG_CLASS(LLWearableListItemComparator); + +public: + LLWearableListItemComparator() {}; + virtual ~LLWearableListItemComparator() {}; + + virtual bool compare(const LLPanel* item1, const LLPanel* item2) const + { + const LLPanelInventoryListItemBase* wearable_item1 = dynamic_cast(item1); + const LLPanelInventoryListItemBase* wearable_item2 = dynamic_cast(item2); + + if (!wearable_item1 || !wearable_item2) + { + LL_WARNS() << "item1 and item2 cannot be null" << LL_ENDL; + return true; + } + + return doCompare(wearable_item1, wearable_item2); + } + +protected: + + /** + * Returns true if wearable_item1 < wearable_item2, false otherwise + * Implement this method in your particular comparator. + */ + virtual bool doCompare(const LLPanelInventoryListItemBase* wearable_item1, const LLPanelInventoryListItemBase* wearable_item2) const = 0; +}; + +/** + * @class LLWearableItemNameComparator + * + * Comparator for sorting wearable list items by name. + */ +class LLWearableItemNameComparator : public LLWearableListItemComparator +{ + LOG_CLASS(LLWearableItemNameComparator); + +public: + LLWearableItemNameComparator() {}; + virtual ~LLWearableItemNameComparator() {}; + +protected: + /*virtual*/ bool doCompare(const LLPanelInventoryListItemBase* wearable_item1, const LLPanelInventoryListItemBase* wearable_item2) const; +}; + +/** + * @class LLWearableItemTypeNameComparator + * + * Comparator for sorting wearable list items by type and name. + */ +class LLWearableItemTypeNameComparator : public LLWearableItemNameComparator +{ + LOG_CLASS(LLWearableItemTypeNameComparator); + +public: + + LLWearableItemTypeNameComparator(); + virtual ~LLWearableItemTypeNameComparator() {}; + + enum ETypeListOrder + { + ORDER_RANK_1 = 1, + ORDER_RANK_2, + ORDER_RANK_3, + ORDER_RANK_4, + ORDER_RANK_UNKNOWN + }; + + void setOrder(LLAssetType::EType items_of_type, ETypeListOrder order_priority, bool sort_items_by_name, bool sort_wearable_items_by_name); + +protected: + /** + * All information about sort order is stored in mWearableOrder map + * + * mWearableOrder : KEYS VALUES + * [LLAssetType] [struct LLWearableTypeOrder] + * + *--------------------------------------------------------------------------------------------- + * I. Determines order (ORDER_RANK) in which items of LLAssetType should be displayed in list. + * For example by spec in MY OUTFITS the order is: + * 1. AT_CLOTHING (ORDER_RANK_1) + * 2. AT_OBJECT (ORDER_RANK_2) + * 3. AT_BODYPART (ORDER_RANK_3) + * + * II.Items of each type(LLAssetType) are sorted by name or type(LLWearableType) + * For example by spec in MY OUTFITS the order within each items type(LLAssetType) is: + * 1. AT_OBJECTS (abc order) + * 2. AT_CLOTHINGS + * - by type (types order determined in LLWearableType::EType) + * - outer layer on top + * 3. AT_BODYPARTS (abc order) + *--------------------------------------------------------------------------------------------- + * + * For each LLAssetType (KEYS in mWearableOrder) the information about: + * + * I. ORDER_RANK (the flag is LLWearableTypeOrder::mOrderPriority) + * + * II. whether items of this LLAssetType type should be ordered + * by name or by LLWearableType::EType (the flag is LLWearableTypeOrder::mSortAssetTypeByName) + * + * III.whether items of LLWearableType type within this LLAssetType + * should be ordered by name (the flag is LLWearableTypeOrder::mSortWearableTypeByName) + * + * holds in mWearableOrder map as VALUES (struct LLWearableTypeOrder). + */ + /*virtual*/ bool doCompare(const LLPanelInventoryListItemBase* wearable_item1, const LLPanelInventoryListItemBase* wearable_item2) const; + +private: + + struct LLWearableTypeOrder + { + ETypeListOrder mOrderPriority; + bool mSortAssetTypeByName; + bool mSortWearableTypeByName; + + LLWearableTypeOrder(ETypeListOrder order_priority, bool sort_asset_by_name, bool sort_wearable_by_name); + LLWearableTypeOrder() : mOrderPriority(ORDER_RANK_UNKNOWN), mSortAssetTypeByName(false), mSortWearableTypeByName(false) {}; + }; + + ETypeListOrder getTypeListOrder(LLAssetType::EType item_type) const; + + bool sortAssetTypeByName(LLAssetType::EType item_type) const; + bool sortWearableTypeByName(LLAssetType::EType item_type) const; + + typedef std::map wearable_type_order_map_t; + wearable_type_order_map_t mWearableOrder; +}; + +/** + * @class LLWearableItemCreationDateComparator + * + * Comparator for sorting wearable list items by creation date (newest go first). + */ +class LLWearableItemCreationDateComparator : public LLWearableItemNameComparator +{ + LOG_CLASS(LLWearableItemCreationDateComparator); + +public: + // clang demands a default ctor here + LLWearableItemCreationDateComparator() {} + +protected: + /*virtual*/ bool doCompare(const LLPanelInventoryListItemBase* item1, const LLPanelInventoryListItemBase* item2) const; +}; + +/** + * @class LLWearableItemsList + * + * A flat list of wearable inventory items. + * Collects all items that can be a part of an outfit from + * an inventory category specified by UUID and displays them + * as a flat list. + */ +class LLWearableItemsList : public LLInventoryItemsList +{ +public: + /** + * Context menu. + * + * This menu is likely to be used from outside + * (e.g. for items selected across multiple wearable lists), + * so making it a singleton. + */ + class ContextMenu : public LLListContextMenu, public LLSingleton + { + LLSINGLETON(ContextMenu); + public: + /*virtual*/ void show(LLView* spawning_view, const uuid_vec_t& uuids, S32 x, S32 y) override; + + void show(LLView* spawning_view, LLWearableType::EType w_type, S32 x, S32 y); + + protected: + enum { + MASK_CLOTHING = 0x01, + MASK_BODYPART = 0x02, + MASK_ATTACHMENT = 0x04, + MASK_GESTURE = 0x08, + MASK_UNKNOWN = 0x10, + }; + + /* virtual */ LLContextMenu* createMenu() override; + void updateItemsVisibility(LLContextMenu* menu); + void updateItemsLabels(LLContextMenu* menu); + static void setMenuItemVisible(LLContextMenu* menu, const std::string& name, bool val); + static void setMenuItemEnabled(LLContextMenu* menu, const std::string& name, bool val); + static void updateMask(U32& mask, LLAssetType::EType at); + static void createNewWearable(const LLUUID& item_id); + static void createNewWearableByType(LLWearableType::EType type); + + LLWearableItemsList* mParent; + }; + + struct Params : public LLInitParam::Block + { + Optional standalone; + Optional worn_indication_enabled; + Optional show_item_widgets; + + Params(); + }; + + typedef enum e_sort_order { + // Values should be compatible with InventorySortOrder setting. + E_SORT_BY_NAME = 0, + E_SORT_BY_MOST_RECENT = 1, + E_SORT_BY_TYPE_LAYER = 2, + E_SORT_BY_TYPE_NAME = 3, + } ESortOrder; + + virtual ~LLWearableItemsList(); + + /*virtual*/ LLPanel* createNewItem(LLViewerInventoryItem* item) override; + + void updateList(const LLUUID& category_id); + + /** + * Update items that match UUIDs from changed_items_uuids + * or links that point at such items. + */ + void updateChangedItems(const uuid_vec_t& changed_items_uuids); + + bool isStandalone() const { return mIsStandalone; } + + ESortOrder getSortOrder() const { return mSortOrder; } + + void setSortOrder(ESortOrder sort_order, bool sort_now = true); + + void setMenuWearableType(LLWearableType::EType type) { mMenuWearableType = type; } + +protected: + friend class LLUICtrlFactory; + LLWearableItemsList(const LLWearableItemsList::Params& p); + + void onRightClick(S32 x, S32 y); + + bool mIsStandalone; + bool mWornIndicationEnabled; + bool mShowItemWidgets; + + ESortOrder mSortOrder; + + LLWearableType::EType mMenuWearableType; +}; + +#endif //LL_LLWEARABLEITEMSLIST_H diff --git a/indra/newview/llwearablelist.cpp b/indra/newview/llwearablelist.cpp index 0922768a23..76348d4ea1 100644 --- a/indra/newview/llwearablelist.cpp +++ b/indra/newview/llwearablelist.cpp @@ -1,278 +1,278 @@ -/** - * @file llwearablelist.cpp - * @brief LLWearableList class implementation - * - * $LicenseInfo:firstyear=2002&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llwearablelist.h" - -#include "message.h" -#include "llassetstorage.h" -#include "llagent.h" -#include "llvoavatar.h" -#include "llviewerstats.h" -#include "llnotificationsutil.h" -#include "llinventorymodel.h" -#include "lltrans.h" - -// Callback struct -struct LLWearableArrivedData -{ - LLWearableArrivedData(LLAssetType::EType asset_type, - const std::string& wearable_name, - LLAvatarAppearance* avatarp, - void(*asset_arrived_callback)(LLViewerWearable*, void* userdata), - void* userdata) : - mAssetType( asset_type ), - mCallback( asset_arrived_callback ), - mUserdata( userdata ), - mName( wearable_name ), - mRetries(0), - mAvatarp(avatarp) - {} - - LLAssetType::EType mAssetType; - void (*mCallback)(LLViewerWearable*, void* userdata); - void* mUserdata; - std::string mName; - S32 mRetries; - LLAvatarAppearance *mAvatarp; -}; - -//////////////////////////////////////////////////////////////////////////// -// LLWearableList - -LLWearableList::~LLWearableList() -{ - cleanup(); -} - -void LLWearableList::cleanup() -{ - for_each(mList.begin(), mList.end(), DeletePairedPointer()); - mList.clear(); -} - -void LLWearableList::getAsset(const LLAssetID& assetID, const std::string& wearable_name, LLAvatarAppearance* avatarp, LLAssetType::EType asset_type, void(*asset_arrived_callback)(LLViewerWearable*, void* userdata), void* userdata) -{ - llassert( (asset_type == LLAssetType::AT_CLOTHING) || (asset_type == LLAssetType::AT_BODYPART) ); - LLViewerWearable* instance = get_if_there(mList, assetID, (LLViewerWearable*)NULL ); - if( instance ) - { - LL_DEBUGS("Avatar") << "wearable " << assetID << " found in LLWearableList" << LL_ENDL; - asset_arrived_callback( instance, userdata ); - } - else - { - gAssetStorage->getAssetData(assetID, - asset_type, - LLWearableList::processGetAssetReply, - (void*)new LLWearableArrivedData( asset_type, wearable_name, avatarp, asset_arrived_callback, userdata ), - true); - } -} - -// static -void LLWearableList::processGetAssetReply( const char* filename, const LLAssetID& uuid, void* userdata, S32 status, LLExtStat ext_status ) -{ - bool isNewWearable = false; - LLWearableArrivedData* data = (LLWearableArrivedData*) userdata; - LLViewerWearable* wearable = NULL; // NULL indicates failure - LLAvatarAppearance *avatarp = data->mAvatarp; - - if( !filename ) - { - LL_WARNS("Wearable") << "Bad Wearable Asset: missing file." << LL_ENDL; - } - else if(!avatarp) - { - LL_WARNS("Wearable") << "Bad asset request: missing avatar pointer." << LL_ENDL; - } - else if (status >= 0) - { - // read the file - llifstream ifs(filename, llifstream::binary); - if( !ifs.is_open() ) - { - LL_WARNS("Wearable") << "Bad Wearable Asset: unable to open file: '" << filename << "'" << LL_ENDL; - } - else - { - wearable = new LLViewerWearable(uuid); - LLWearable::EImportResult result = wearable->importStream( - ifs, avatarp ); - if (LLWearable::SUCCESS != result) - { - if (wearable->getType() == LLWearableType::WT_COUNT) - { - isNewWearable = true; - } - delete wearable; - wearable = NULL; - } - - if(filename) - { - if (ifs.is_open()) - { - ifs.close(); - } - LLFile::remove(std::string(filename)); - } - } - } - else - { - if(filename) - { - LLFile::remove(std::string(filename)); - } - - LL_WARNS("Wearable") << "Wearable download failed: " << LLAssetStorage::getErrorString( status ) << " " << uuid << LL_ENDL; - switch( status ) - { - case LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE: - { - // Fail - break; - } - default: - { - static const S32 MAX_RETRIES = 3; - if (data->mRetries < MAX_RETRIES) - { - // Try again - data->mRetries++; - gAssetStorage->getAssetData(uuid, - data->mAssetType, - LLWearableList::processGetAssetReply, - userdata); // re-use instead of deleting. - return; - } - else - { - // Fail - break; - } - } - } - } - - if (wearable) // success - { - LLWearableList::instance().mList[ uuid ] = wearable; - LL_DEBUGS("Wearable") << "processGetAssetReply()" << LL_ENDL; - LL_DEBUGS("Wearable") << wearable << LL_ENDL; - } - else - { - LLSD args; - args["TYPE"] =LLTrans::getString(LLAssetType::lookupHumanReadable(data->mAssetType)); - if (isNewWearable) - { - LLNotificationsUtil::add("InvalidWearable"); - } - else if (data->mName.empty()) - { - LLNotificationsUtil::add("FailedToFindWearableUnnamed", args); - } - else - { - args["DESC"] = data->mName; - LLNotificationsUtil::add("FailedToFindWearable", args); - } - } - // Always call callback; wearable will be NULL if we failed - { - if( data->mCallback ) - { - data->mCallback( wearable, data->mUserdata ); - } - } - delete data; -} - - -LLViewerWearable* LLWearableList::createCopy(const LLViewerWearable* old_wearable, const std::string& new_name) -{ - LL_DEBUGS() << "LLWearableList::createCopy()" << LL_ENDL; - - LLViewerWearable *wearable = generateNewWearable(); - wearable->copyDataFrom(old_wearable); - - LLPermissions perm(old_wearable->getPermissions()); - perm.setOwnerAndGroup(LLUUID::null, gAgent.getID(), LLUUID::null, true); - wearable->setPermissions(perm); - - if (!new_name.empty()) wearable->setName(new_name); - - // Send to the dataserver - wearable->saveNewAsset(); - - return wearable; -} - -LLViewerWearable* LLWearableList::createNewWearable( LLWearableType::EType type, LLAvatarAppearance *avatarp ) -{ - LL_DEBUGS() << "LLWearableList::createNewWearable()" << LL_ENDL; - - LLViewerWearable *wearable = generateNewWearable(); - wearable->setType( type, avatarp ); - - // LLWearableType has pre-translated getTypeLabel(), but it returns 'name', not 'New Name'. - std::string name = LLTrans::getString( LLWearableType::getInstance()->getTypeDefaultNewName(wearable->getType()) ); - wearable->setName( name ); - - LLPermissions perm; - perm.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null); - perm.initMasks(PERM_ALL, PERM_ALL, PERM_NONE, PERM_NONE, PERM_MOVE | PERM_TRANSFER); - wearable->setPermissions(perm); - - wearable->setDefinitionVersion(LLWearable::getCurrentDefinitionVersion()); - - // Description and sale info have default values. - wearable->setParamsToDefaults(); - wearable->setTexturesToDefaults(); - - //mark all values (params & images) as saved - wearable->saveValues(); - - // Send to the dataserver - wearable->saveNewAsset(); - - - return wearable; -} - -LLViewerWearable *LLWearableList::generateNewWearable() -{ - LLTransactionID tid; - tid.generate(); - LLAssetID new_asset_id = tid.makeAssetID(gAgent.getSecureSessionID()); - - LLViewerWearable* wearable = new LLViewerWearable(tid); - mList[new_asset_id] = wearable; - return wearable; -} +/** + * @file llwearablelist.cpp + * @brief LLWearableList class implementation + * + * $LicenseInfo:firstyear=2002&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llwearablelist.h" + +#include "message.h" +#include "llassetstorage.h" +#include "llagent.h" +#include "llvoavatar.h" +#include "llviewerstats.h" +#include "llnotificationsutil.h" +#include "llinventorymodel.h" +#include "lltrans.h" + +// Callback struct +struct LLWearableArrivedData +{ + LLWearableArrivedData(LLAssetType::EType asset_type, + const std::string& wearable_name, + LLAvatarAppearance* avatarp, + void(*asset_arrived_callback)(LLViewerWearable*, void* userdata), + void* userdata) : + mAssetType( asset_type ), + mCallback( asset_arrived_callback ), + mUserdata( userdata ), + mName( wearable_name ), + mRetries(0), + mAvatarp(avatarp) + {} + + LLAssetType::EType mAssetType; + void (*mCallback)(LLViewerWearable*, void* userdata); + void* mUserdata; + std::string mName; + S32 mRetries; + LLAvatarAppearance *mAvatarp; +}; + +//////////////////////////////////////////////////////////////////////////// +// LLWearableList + +LLWearableList::~LLWearableList() +{ + cleanup(); +} + +void LLWearableList::cleanup() +{ + for_each(mList.begin(), mList.end(), DeletePairedPointer()); + mList.clear(); +} + +void LLWearableList::getAsset(const LLAssetID& assetID, const std::string& wearable_name, LLAvatarAppearance* avatarp, LLAssetType::EType asset_type, void(*asset_arrived_callback)(LLViewerWearable*, void* userdata), void* userdata) +{ + llassert( (asset_type == LLAssetType::AT_CLOTHING) || (asset_type == LLAssetType::AT_BODYPART) ); + LLViewerWearable* instance = get_if_there(mList, assetID, (LLViewerWearable*)NULL ); + if( instance ) + { + LL_DEBUGS("Avatar") << "wearable " << assetID << " found in LLWearableList" << LL_ENDL; + asset_arrived_callback( instance, userdata ); + } + else + { + gAssetStorage->getAssetData(assetID, + asset_type, + LLWearableList::processGetAssetReply, + (void*)new LLWearableArrivedData( asset_type, wearable_name, avatarp, asset_arrived_callback, userdata ), + true); + } +} + +// static +void LLWearableList::processGetAssetReply( const char* filename, const LLAssetID& uuid, void* userdata, S32 status, LLExtStat ext_status ) +{ + bool isNewWearable = false; + LLWearableArrivedData* data = (LLWearableArrivedData*) userdata; + LLViewerWearable* wearable = NULL; // NULL indicates failure + LLAvatarAppearance *avatarp = data->mAvatarp; + + if( !filename ) + { + LL_WARNS("Wearable") << "Bad Wearable Asset: missing file." << LL_ENDL; + } + else if(!avatarp) + { + LL_WARNS("Wearable") << "Bad asset request: missing avatar pointer." << LL_ENDL; + } + else if (status >= 0) + { + // read the file + llifstream ifs(filename, llifstream::binary); + if( !ifs.is_open() ) + { + LL_WARNS("Wearable") << "Bad Wearable Asset: unable to open file: '" << filename << "'" << LL_ENDL; + } + else + { + wearable = new LLViewerWearable(uuid); + LLWearable::EImportResult result = wearable->importStream( + ifs, avatarp ); + if (LLWearable::SUCCESS != result) + { + if (wearable->getType() == LLWearableType::WT_COUNT) + { + isNewWearable = true; + } + delete wearable; + wearable = NULL; + } + + if(filename) + { + if (ifs.is_open()) + { + ifs.close(); + } + LLFile::remove(std::string(filename)); + } + } + } + else + { + if(filename) + { + LLFile::remove(std::string(filename)); + } + + LL_WARNS("Wearable") << "Wearable download failed: " << LLAssetStorage::getErrorString( status ) << " " << uuid << LL_ENDL; + switch( status ) + { + case LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE: + { + // Fail + break; + } + default: + { + static const S32 MAX_RETRIES = 3; + if (data->mRetries < MAX_RETRIES) + { + // Try again + data->mRetries++; + gAssetStorage->getAssetData(uuid, + data->mAssetType, + LLWearableList::processGetAssetReply, + userdata); // re-use instead of deleting. + return; + } + else + { + // Fail + break; + } + } + } + } + + if (wearable) // success + { + LLWearableList::instance().mList[ uuid ] = wearable; + LL_DEBUGS("Wearable") << "processGetAssetReply()" << LL_ENDL; + LL_DEBUGS("Wearable") << wearable << LL_ENDL; + } + else + { + LLSD args; + args["TYPE"] =LLTrans::getString(LLAssetType::lookupHumanReadable(data->mAssetType)); + if (isNewWearable) + { + LLNotificationsUtil::add("InvalidWearable"); + } + else if (data->mName.empty()) + { + LLNotificationsUtil::add("FailedToFindWearableUnnamed", args); + } + else + { + args["DESC"] = data->mName; + LLNotificationsUtil::add("FailedToFindWearable", args); + } + } + // Always call callback; wearable will be NULL if we failed + { + if( data->mCallback ) + { + data->mCallback( wearable, data->mUserdata ); + } + } + delete data; +} + + +LLViewerWearable* LLWearableList::createCopy(const LLViewerWearable* old_wearable, const std::string& new_name) +{ + LL_DEBUGS() << "LLWearableList::createCopy()" << LL_ENDL; + + LLViewerWearable *wearable = generateNewWearable(); + wearable->copyDataFrom(old_wearable); + + LLPermissions perm(old_wearable->getPermissions()); + perm.setOwnerAndGroup(LLUUID::null, gAgent.getID(), LLUUID::null, true); + wearable->setPermissions(perm); + + if (!new_name.empty()) wearable->setName(new_name); + + // Send to the dataserver + wearable->saveNewAsset(); + + return wearable; +} + +LLViewerWearable* LLWearableList::createNewWearable( LLWearableType::EType type, LLAvatarAppearance *avatarp ) +{ + LL_DEBUGS() << "LLWearableList::createNewWearable()" << LL_ENDL; + + LLViewerWearable *wearable = generateNewWearable(); + wearable->setType( type, avatarp ); + + // LLWearableType has pre-translated getTypeLabel(), but it returns 'name', not 'New Name'. + std::string name = LLTrans::getString( LLWearableType::getInstance()->getTypeDefaultNewName(wearable->getType()) ); + wearable->setName( name ); + + LLPermissions perm; + perm.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null); + perm.initMasks(PERM_ALL, PERM_ALL, PERM_NONE, PERM_NONE, PERM_MOVE | PERM_TRANSFER); + wearable->setPermissions(perm); + + wearable->setDefinitionVersion(LLWearable::getCurrentDefinitionVersion()); + + // Description and sale info have default values. + wearable->setParamsToDefaults(); + wearable->setTexturesToDefaults(); + + //mark all values (params & images) as saved + wearable->saveValues(); + + // Send to the dataserver + wearable->saveNewAsset(); + + + return wearable; +} + +LLViewerWearable *LLWearableList::generateNewWearable() +{ + LLTransactionID tid; + tid.generate(); + LLAssetID new_asset_id = tid.makeAssetID(gAgent.getSecureSessionID()); + + LLViewerWearable* wearable = new LLViewerWearable(tid); + mList[new_asset_id] = wearable; + return wearable; +} diff --git a/indra/newview/llwebprofile.cpp b/indra/newview/llwebprofile.cpp index 8559f6275b..83e417633a 100644 --- a/indra/newview/llwebprofile.cpp +++ b/indra/newview/llwebprofile.cpp @@ -1,272 +1,272 @@ -/** - * @file llwebprofile.cpp - * @brief Web profile access. - * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llwebprofile.h" - -// libs -#include "llbufferstream.h" -#include "llimagepng.h" - -#include "llsdserialize.h" -#include "llstring.h" - -// newview -#include "llavataractions.h" // for getProfileURL() -#include "llviewermedia.h" // FIXME: don't use LLViewerMedia internals - -#include "llcorehttputil.h" - -// third-party - - -/* - * Workflow: - * 1. LLViewerMedia::setOpenIDCookie() - * -> GET https://my-demo.secondlife.com/ via LLViewerMediaWebProfileResponder - * -> LLWebProfile::setAuthCookie() - * 2. LLWebProfile::uploadImage() - * -> GET "https://my-demo.secondlife.com/snapshots/s3_upload_config" via ConfigResponder - * 3. LLWebProfile::post() - * -> POST via PostImageResponder - * -> redirect - * -> GET via PostImageRedirectResponder - */ - -/////////////////////////////////////////////////////////////////////////////// -// LLWebProfile - -std::string LLWebProfile::sAuthCookie; -LLWebProfile::status_callback_t LLWebProfile::mStatusCallback; - -// static -void LLWebProfile::uploadImage(LLPointer image, const std::string& caption, bool add_location) -{ - LLCoros::instance().launch("LLWebProfile::uploadImageCoro", - boost::bind(&LLWebProfile::uploadImageCoro, image, caption, add_location)); - -} - -// static -void LLWebProfile::setAuthCookie(const std::string& cookie) -{ - LL_DEBUGS("Snapshots") << "Setting auth cookie: " << cookie << LL_ENDL; - sAuthCookie = cookie; -} - - -/*static*/ -LLCore::HttpHeaders::ptr_t LLWebProfile::buildDefaultHeaders() -{ - LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); - LLSD headers = LLViewerMedia::getInstance()->getHeaders(); - - for (LLSD::map_iterator it = headers.beginMap(); it != headers.endMap(); ++it) - { - httpHeaders->append((*it).first, (*it).second.asStringRef()); - } - - return httpHeaders; -} - - -/*static*/ -void LLWebProfile::uploadImageCoro(LLPointer image, std::string caption, bool addLocation) -{ - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("genericPostCoro", httpPolicy)); - LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); - LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); - LLCore::HttpHeaders::ptr_t httpHeaders; - - if (dynamic_cast(image.get()) == 0) - { - LL_WARNS() << "Image to upload is not a PNG" << LL_ENDL; - llassert(dynamic_cast(image.get()) != 0); - return; - } - - httpOpts->setWantHeaders(true); - httpOpts->setFollowRedirects(false); - httpOpts->setSSLVerifyPeer(false); ; // viewer's cert bundle doesn't appear to agree with web certs from "https://my.secondlife.com/" - - // Get upload configuration data. - std::string configUrl(getProfileURL(std::string()) + "snapshots/s3_upload_config"); - configUrl += "?caption=" + LLURI::escape(caption); - configUrl += "&add_loc=" + std::string(addLocation ? "1" : "0"); - - LL_DEBUGS("Snapshots") << "Requesting " << configUrl << LL_ENDL; - - httpHeaders = buildDefaultHeaders(); - httpHeaders->append(HTTP_OUT_HEADER_COOKIE, getAuthCookie()); - - LLSD result = httpAdapter->getJsonAndSuspend(httpRequest, configUrl, httpOpts, httpHeaders); - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - LL_WARNS("Snapshots") << "Failed to get image upload config" << LL_ENDL; - LLWebProfile::reportImageUploadStatus(false); - return; - } - - // Ready to build our image post body. - - const LLSD &data = result["data"]; - const std::string &uploadUrl = result["url"].asStringRef(); - const std::string boundary = "----------------------------0123abcdefab"; - - // a new set of headers. - httpHeaders = LLWebProfile::buildDefaultHeaders(); - httpHeaders->append(HTTP_OUT_HEADER_COOKIE, getAuthCookie()); - httpHeaders->remove(HTTP_OUT_HEADER_CONTENT_TYPE); - httpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, "multipart/form-data; boundary=" + boundary); - - LLCore::BufferArray::ptr_t body = LLWebProfile::buildPostData(data, image, boundary); - - result = httpAdapter->postAndSuspend(httpRequest, uploadUrl, body, httpOpts, httpHeaders); - - body.reset(); - httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status && (status != LLCore::HttpStatus(HTTP_SEE_OTHER))) - { - LL_WARNS("Snapshots") << "Failed to upload image data." << LL_ENDL; - LLWebProfile::reportImageUploadStatus(false); - return; - } - - LLSD resultHeaders = httpResults[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS]; - - httpHeaders = LLWebProfile::buildDefaultHeaders(); - httpHeaders->append(HTTP_OUT_HEADER_COOKIE, getAuthCookie()); - - const std::string& redirUrl = resultHeaders[HTTP_IN_HEADER_LOCATION].asStringRef(); - - if (redirUrl.empty()) - { - LL_WARNS("Snapshots") << "Received empty redirection URL in post image." << LL_ENDL; - LLWebProfile::reportImageUploadStatus(false); - } - - LL_DEBUGS("Snapshots") << "Got redirection URL: " << redirUrl << LL_ENDL; - - result = httpAdapter->getRawAndSuspend(httpRequest, redirUrl, httpOpts, httpHeaders); - - httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (status != LLCore::HttpStatus(HTTP_OK)) - { - LL_WARNS("Snapshots") << "Failed to upload image." << LL_ENDL; - LLWebProfile::reportImageUploadStatus(false); - return; - } - - //LLSD raw = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_RAW]; - - LL_INFOS("Snapshots") << "Image uploaded." << LL_ENDL; - //LL_DEBUGS("Snapshots") << "Uploading image succeeded. Response: [" << raw.asString() << "]" << LL_ENDL; - LLWebProfile::reportImageUploadStatus(true); - - -} - -/*static*/ -LLCore::BufferArray::ptr_t LLWebProfile::buildPostData(const LLSD &data, LLPointer &image, const std::string &boundary) -{ - LLCore::BufferArray::ptr_t body(new LLCore::BufferArray); - LLCore::BufferArrayStream bas(body.get()); - - // *NOTE: The order seems to matter. - bas << "--" << boundary << "\r\n" - << "Content-Disposition: form-data; name=\"key\"\r\n\r\n" - << data["key"].asString() << "\r\n"; - - bas << "--" << boundary << "\r\n" - << "Content-Disposition: form-data; name=\"AWSAccessKeyId\"\r\n\r\n" - << data["AWSAccessKeyId"].asString() << "\r\n"; - - bas << "--" << boundary << "\r\n" - << "Content-Disposition: form-data; name=\"acl\"\r\n\r\n" - << data["acl"].asString() << "\r\n"; - - bas << "--" << boundary << "\r\n" - << "Content-Disposition: form-data; name=\"Content-Type\"\r\n\r\n" - << data["Content-Type"].asString() << "\r\n"; - - bas << "--" << boundary << "\r\n" - << "Content-Disposition: form-data; name=\"policy\"\r\n\r\n" - << data["policy"].asString() << "\r\n"; - - bas << "--" << boundary << "\r\n" - << "Content-Disposition: form-data; name=\"signature\"\r\n\r\n" - << data["signature"].asString() << "\r\n"; - - bas << "--" << boundary << "\r\n" - << "Content-Disposition: form-data; name=\"success_action_redirect\"\r\n\r\n" - << data["success_action_redirect"].asString() << "\r\n"; - - bas << "--" << boundary << "\r\n" - << "Content-Disposition: form-data; name=\"file\"; filename=\"snapshot.png\"\r\n" - << "Content-Type: image/png\r\n\r\n"; - - LLImageDataSharedLock lock(image); - - // Insert the image data. - //char *datap = (char *)(image->getData()); - //bas.write(datap, image->getDataSize()); - const U8* image_data = image->getData(); - for (S32 i = 0; i < image->getDataSize(); ++i) - { - bas << image_data[i]; - } - - bas << "\r\n--" << boundary << "--\r\n"; - - return body; -} - -// static -void LLWebProfile::reportImageUploadStatus(bool ok) -{ - if (mStatusCallback) - { - mStatusCallback(ok); - } -} - -// static -std::string LLWebProfile::getAuthCookie() -{ - // This is needed to test image uploads on Linux viewer built with OpenSSL 1.0.0 (0.9.8 works fine). - return LLStringUtil::getenv("LL_SNAPSHOT_COOKIE", sAuthCookie); -} +/** + * @file llwebprofile.cpp + * @brief Web profile access. + * + * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2011, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llwebprofile.h" + +// libs +#include "llbufferstream.h" +#include "llimagepng.h" + +#include "llsdserialize.h" +#include "llstring.h" + +// newview +#include "llavataractions.h" // for getProfileURL() +#include "llviewermedia.h" // FIXME: don't use LLViewerMedia internals + +#include "llcorehttputil.h" + +// third-party + + +/* + * Workflow: + * 1. LLViewerMedia::setOpenIDCookie() + * -> GET https://my-demo.secondlife.com/ via LLViewerMediaWebProfileResponder + * -> LLWebProfile::setAuthCookie() + * 2. LLWebProfile::uploadImage() + * -> GET "https://my-demo.secondlife.com/snapshots/s3_upload_config" via ConfigResponder + * 3. LLWebProfile::post() + * -> POST via PostImageResponder + * -> redirect + * -> GET via PostImageRedirectResponder + */ + +/////////////////////////////////////////////////////////////////////////////// +// LLWebProfile + +std::string LLWebProfile::sAuthCookie; +LLWebProfile::status_callback_t LLWebProfile::mStatusCallback; + +// static +void LLWebProfile::uploadImage(LLPointer image, const std::string& caption, bool add_location) +{ + LLCoros::instance().launch("LLWebProfile::uploadImageCoro", + boost::bind(&LLWebProfile::uploadImageCoro, image, caption, add_location)); + +} + +// static +void LLWebProfile::setAuthCookie(const std::string& cookie) +{ + LL_DEBUGS("Snapshots") << "Setting auth cookie: " << cookie << LL_ENDL; + sAuthCookie = cookie; +} + + +/*static*/ +LLCore::HttpHeaders::ptr_t LLWebProfile::buildDefaultHeaders() +{ + LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders); + LLSD headers = LLViewerMedia::getInstance()->getHeaders(); + + for (LLSD::map_iterator it = headers.beginMap(); it != headers.endMap(); ++it) + { + httpHeaders->append((*it).first, (*it).second.asStringRef()); + } + + return httpHeaders; +} + + +/*static*/ +void LLWebProfile::uploadImageCoro(LLPointer image, std::string caption, bool addLocation) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("genericPostCoro", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + LLCore::HttpHeaders::ptr_t httpHeaders; + + if (dynamic_cast(image.get()) == 0) + { + LL_WARNS() << "Image to upload is not a PNG" << LL_ENDL; + llassert(dynamic_cast(image.get()) != 0); + return; + } + + httpOpts->setWantHeaders(true); + httpOpts->setFollowRedirects(false); + httpOpts->setSSLVerifyPeer(false); ; // viewer's cert bundle doesn't appear to agree with web certs from "https://my.secondlife.com/" + + // Get upload configuration data. + std::string configUrl(getProfileURL(std::string()) + "snapshots/s3_upload_config"); + configUrl += "?caption=" + LLURI::escape(caption); + configUrl += "&add_loc=" + std::string(addLocation ? "1" : "0"); + + LL_DEBUGS("Snapshots") << "Requesting " << configUrl << LL_ENDL; + + httpHeaders = buildDefaultHeaders(); + httpHeaders->append(HTTP_OUT_HEADER_COOKIE, getAuthCookie()); + + LLSD result = httpAdapter->getJsonAndSuspend(httpRequest, configUrl, httpOpts, httpHeaders); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + LL_WARNS("Snapshots") << "Failed to get image upload config" << LL_ENDL; + LLWebProfile::reportImageUploadStatus(false); + return; + } + + // Ready to build our image post body. + + const LLSD &data = result["data"]; + const std::string &uploadUrl = result["url"].asStringRef(); + const std::string boundary = "----------------------------0123abcdefab"; + + // a new set of headers. + httpHeaders = LLWebProfile::buildDefaultHeaders(); + httpHeaders->append(HTTP_OUT_HEADER_COOKIE, getAuthCookie()); + httpHeaders->remove(HTTP_OUT_HEADER_CONTENT_TYPE); + httpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, "multipart/form-data; boundary=" + boundary); + + LLCore::BufferArray::ptr_t body = LLWebProfile::buildPostData(data, image, boundary); + + result = httpAdapter->postAndSuspend(httpRequest, uploadUrl, body, httpOpts, httpHeaders); + + body.reset(); + httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status && (status != LLCore::HttpStatus(HTTP_SEE_OTHER))) + { + LL_WARNS("Snapshots") << "Failed to upload image data." << LL_ENDL; + LLWebProfile::reportImageUploadStatus(false); + return; + } + + LLSD resultHeaders = httpResults[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_HEADERS]; + + httpHeaders = LLWebProfile::buildDefaultHeaders(); + httpHeaders->append(HTTP_OUT_HEADER_COOKIE, getAuthCookie()); + + const std::string& redirUrl = resultHeaders[HTTP_IN_HEADER_LOCATION].asStringRef(); + + if (redirUrl.empty()) + { + LL_WARNS("Snapshots") << "Received empty redirection URL in post image." << LL_ENDL; + LLWebProfile::reportImageUploadStatus(false); + } + + LL_DEBUGS("Snapshots") << "Got redirection URL: " << redirUrl << LL_ENDL; + + result = httpAdapter->getRawAndSuspend(httpRequest, redirUrl, httpOpts, httpHeaders); + + httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (status != LLCore::HttpStatus(HTTP_OK)) + { + LL_WARNS("Snapshots") << "Failed to upload image." << LL_ENDL; + LLWebProfile::reportImageUploadStatus(false); + return; + } + + //LLSD raw = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_RAW]; + + LL_INFOS("Snapshots") << "Image uploaded." << LL_ENDL; + //LL_DEBUGS("Snapshots") << "Uploading image succeeded. Response: [" << raw.asString() << "]" << LL_ENDL; + LLWebProfile::reportImageUploadStatus(true); + + +} + +/*static*/ +LLCore::BufferArray::ptr_t LLWebProfile::buildPostData(const LLSD &data, LLPointer &image, const std::string &boundary) +{ + LLCore::BufferArray::ptr_t body(new LLCore::BufferArray); + LLCore::BufferArrayStream bas(body.get()); + + // *NOTE: The order seems to matter. + bas << "--" << boundary << "\r\n" + << "Content-Disposition: form-data; name=\"key\"\r\n\r\n" + << data["key"].asString() << "\r\n"; + + bas << "--" << boundary << "\r\n" + << "Content-Disposition: form-data; name=\"AWSAccessKeyId\"\r\n\r\n" + << data["AWSAccessKeyId"].asString() << "\r\n"; + + bas << "--" << boundary << "\r\n" + << "Content-Disposition: form-data; name=\"acl\"\r\n\r\n" + << data["acl"].asString() << "\r\n"; + + bas << "--" << boundary << "\r\n" + << "Content-Disposition: form-data; name=\"Content-Type\"\r\n\r\n" + << data["Content-Type"].asString() << "\r\n"; + + bas << "--" << boundary << "\r\n" + << "Content-Disposition: form-data; name=\"policy\"\r\n\r\n" + << data["policy"].asString() << "\r\n"; + + bas << "--" << boundary << "\r\n" + << "Content-Disposition: form-data; name=\"signature\"\r\n\r\n" + << data["signature"].asString() << "\r\n"; + + bas << "--" << boundary << "\r\n" + << "Content-Disposition: form-data; name=\"success_action_redirect\"\r\n\r\n" + << data["success_action_redirect"].asString() << "\r\n"; + + bas << "--" << boundary << "\r\n" + << "Content-Disposition: form-data; name=\"file\"; filename=\"snapshot.png\"\r\n" + << "Content-Type: image/png\r\n\r\n"; + + LLImageDataSharedLock lock(image); + + // Insert the image data. + //char *datap = (char *)(image->getData()); + //bas.write(datap, image->getDataSize()); + const U8* image_data = image->getData(); + for (S32 i = 0; i < image->getDataSize(); ++i) + { + bas << image_data[i]; + } + + bas << "\r\n--" << boundary << "--\r\n"; + + return body; +} + +// static +void LLWebProfile::reportImageUploadStatus(bool ok) +{ + if (mStatusCallback) + { + mStatusCallback(ok); + } +} + +// static +std::string LLWebProfile::getAuthCookie() +{ + // This is needed to test image uploads on Linux viewer built with OpenSSL 1.0.0 (0.9.8 works fine). + return LLStringUtil::getenv("LL_SNAPSHOT_COOKIE", sAuthCookie); +} diff --git a/indra/newview/llwindebug.cpp b/indra/newview/llwindebug.cpp index 1ef5446db1..5cd75f7a02 100644 --- a/indra/newview/llwindebug.cpp +++ b/indra/newview/llwindebug.cpp @@ -1,201 +1,201 @@ -/** - * @file llwindebug.cpp - * @brief Windows debugging functions - * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ -#include "llviewerprecompiledheaders.h" - -#include "llwindebug.h" -#include "lldir.h" - - -// based on dbghelp.h -typedef bool (WINAPI *MINIDUMPWRITEDUMP)(HANDLE hProcess, DWORD dwPid, HANDLE hFile, MINIDUMP_TYPE DumpType, - CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, - CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, - CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam - ); - -MINIDUMPWRITEDUMP f_mdwp = NULL; - - -class LLMemoryReserve { -public: - LLMemoryReserve(); - ~LLMemoryReserve(); - void reserve(); - void release(); -private: - unsigned char *mReserve; - static const size_t MEMORY_RESERVATION_SIZE; -}; - -LLMemoryReserve::LLMemoryReserve() : - mReserve(NULL) -{ -} - -LLMemoryReserve::~LLMemoryReserve() -{ - release(); -} - -// I dunno - this just seemed like a pretty good value. -const size_t LLMemoryReserve::MEMORY_RESERVATION_SIZE = 5 * 1024 * 1024; - -void LLMemoryReserve::reserve() -{ - if(NULL == mReserve) - { - mReserve = new unsigned char[MEMORY_RESERVATION_SIZE]; - } -} - -void LLMemoryReserve::release() -{ - if (NULL != mReserve) - { - delete [] mReserve; - } - mReserve = NULL; -} - -static LLMemoryReserve gEmergencyMemoryReserve; - - -LONG NTAPI vectoredHandler(PEXCEPTION_POINTERS exception_infop) -{ - LLWinDebug::instance().generateMinidump(exception_infop); - return EXCEPTION_CONTINUE_SEARCH; -} - -// static -void LLWinDebug::initSingleton() -{ - static bool s_first_run = true; - // Load the dbghelp dll now, instead of waiting for the crash. - // Less potential for stack mangling - - // Don't install vectored exception handler if being debugged. - if(IsDebuggerPresent()) return; - - if (s_first_run) - { - // First, try loading from the directory that the app resides in. - std::string local_dll_name = gDirUtilp->findFile("dbghelp.dll", gDirUtilp->getWorkingDir(), gDirUtilp->getExecutableDir()); - - HMODULE hDll = NULL; - hDll = LoadLibraryA(local_dll_name.c_str()); - if (!hDll) - { - hDll = LoadLibrary(L"dbghelp.dll"); - } - - if (!hDll) - { - LL_WARNS("AppInit") << "Couldn't find dbghelp.dll!" << LL_ENDL; - } - else - { - f_mdwp = (MINIDUMPWRITEDUMP) GetProcAddress(hDll, "MiniDumpWriteDump"); - - if (!f_mdwp) - { - FreeLibrary(hDll); - hDll = NULL; - } - } - - gEmergencyMemoryReserve.reserve(); - - s_first_run = false; - - // Add this exeption hanlder to save windows style minidump. - AddVectoredExceptionHandler(0, &vectoredHandler); - } -} - -void LLWinDebug::cleanupSingleton() -{ - gEmergencyMemoryReserve.release(); -} - -void LLWinDebug::writeDumpToFile(MINIDUMP_TYPE type, MINIDUMP_EXCEPTION_INFORMATION *ExInfop, const std::string& filename) -{ - // Temporary fix to switch out the code that writes the DMP file. - // Fix coming that doesn't write a mini dump file for regular C++ exceptions. - const bool enable_write_dump_file = false; - if ( enable_write_dump_file ) - { - if(f_mdwp == NULL || gDirUtilp == NULL) - { - return; - } - else - { - std::string dump_path = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, filename); - - HANDLE hFile = CreateFileA(dump_path.c_str(), - GENERIC_WRITE, - FILE_SHARE_WRITE, - NULL, - CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, - NULL); - - if (hFile != INVALID_HANDLE_VALUE) - { - // Write the dump, ignoring the return value - f_mdwp(GetCurrentProcess(), - GetCurrentProcessId(), - hFile, - type, - ExInfop, - NULL, - NULL); - - CloseHandle(hFile); - } - - } - } -} - -// static -void LLWinDebug::generateMinidump(struct _EXCEPTION_POINTERS *exception_infop) -{ - std::string dump_path = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, - "SecondLifeException"); - if (exception_infop) - { - // Since there is exception info... Release the hounds. - gEmergencyMemoryReserve.release(); - - _MINIDUMP_EXCEPTION_INFORMATION ExInfo; - - ExInfo.ThreadId = ::GetCurrentThreadId(); - ExInfo.ExceptionPointers = exception_infop; - ExInfo.ClientPointers = NULL; - writeDumpToFile((MINIDUMP_TYPE)(MiniDumpWithDataSegs | MiniDumpWithIndirectlyReferencedMemory), &ExInfo, "SecondLife.dmp"); - } -} +/** + * @file llwindebug.cpp + * @brief Windows debugging functions + * + * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#include "llviewerprecompiledheaders.h" + +#include "llwindebug.h" +#include "lldir.h" + + +// based on dbghelp.h +typedef bool (WINAPI *MINIDUMPWRITEDUMP)(HANDLE hProcess, DWORD dwPid, HANDLE hFile, MINIDUMP_TYPE DumpType, + CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + CONST PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam + ); + +MINIDUMPWRITEDUMP f_mdwp = NULL; + + +class LLMemoryReserve { +public: + LLMemoryReserve(); + ~LLMemoryReserve(); + void reserve(); + void release(); +private: + unsigned char *mReserve; + static const size_t MEMORY_RESERVATION_SIZE; +}; + +LLMemoryReserve::LLMemoryReserve() : + mReserve(NULL) +{ +} + +LLMemoryReserve::~LLMemoryReserve() +{ + release(); +} + +// I dunno - this just seemed like a pretty good value. +const size_t LLMemoryReserve::MEMORY_RESERVATION_SIZE = 5 * 1024 * 1024; + +void LLMemoryReserve::reserve() +{ + if(NULL == mReserve) + { + mReserve = new unsigned char[MEMORY_RESERVATION_SIZE]; + } +} + +void LLMemoryReserve::release() +{ + if (NULL != mReserve) + { + delete [] mReserve; + } + mReserve = NULL; +} + +static LLMemoryReserve gEmergencyMemoryReserve; + + +LONG NTAPI vectoredHandler(PEXCEPTION_POINTERS exception_infop) +{ + LLWinDebug::instance().generateMinidump(exception_infop); + return EXCEPTION_CONTINUE_SEARCH; +} + +// static +void LLWinDebug::initSingleton() +{ + static bool s_first_run = true; + // Load the dbghelp dll now, instead of waiting for the crash. + // Less potential for stack mangling + + // Don't install vectored exception handler if being debugged. + if(IsDebuggerPresent()) return; + + if (s_first_run) + { + // First, try loading from the directory that the app resides in. + std::string local_dll_name = gDirUtilp->findFile("dbghelp.dll", gDirUtilp->getWorkingDir(), gDirUtilp->getExecutableDir()); + + HMODULE hDll = NULL; + hDll = LoadLibraryA(local_dll_name.c_str()); + if (!hDll) + { + hDll = LoadLibrary(L"dbghelp.dll"); + } + + if (!hDll) + { + LL_WARNS("AppInit") << "Couldn't find dbghelp.dll!" << LL_ENDL; + } + else + { + f_mdwp = (MINIDUMPWRITEDUMP) GetProcAddress(hDll, "MiniDumpWriteDump"); + + if (!f_mdwp) + { + FreeLibrary(hDll); + hDll = NULL; + } + } + + gEmergencyMemoryReserve.reserve(); + + s_first_run = false; + + // Add this exeption hanlder to save windows style minidump. + AddVectoredExceptionHandler(0, &vectoredHandler); + } +} + +void LLWinDebug::cleanupSingleton() +{ + gEmergencyMemoryReserve.release(); +} + +void LLWinDebug::writeDumpToFile(MINIDUMP_TYPE type, MINIDUMP_EXCEPTION_INFORMATION *ExInfop, const std::string& filename) +{ + // Temporary fix to switch out the code that writes the DMP file. + // Fix coming that doesn't write a mini dump file for regular C++ exceptions. + const bool enable_write_dump_file = false; + if ( enable_write_dump_file ) + { + if(f_mdwp == NULL || gDirUtilp == NULL) + { + return; + } + else + { + std::string dump_path = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, filename); + + HANDLE hFile = CreateFileA(dump_path.c_str(), + GENERIC_WRITE, + FILE_SHARE_WRITE, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (hFile != INVALID_HANDLE_VALUE) + { + // Write the dump, ignoring the return value + f_mdwp(GetCurrentProcess(), + GetCurrentProcessId(), + hFile, + type, + ExInfop, + NULL, + NULL); + + CloseHandle(hFile); + } + + } + } +} + +// static +void LLWinDebug::generateMinidump(struct _EXCEPTION_POINTERS *exception_infop) +{ + std::string dump_path = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, + "SecondLifeException"); + if (exception_infop) + { + // Since there is exception info... Release the hounds. + gEmergencyMemoryReserve.release(); + + _MINIDUMP_EXCEPTION_INFORMATION ExInfo; + + ExInfo.ThreadId = ::GetCurrentThreadId(); + ExInfo.ExceptionPointers = exception_infop; + ExInfo.ClientPointers = NULL; + writeDumpToFile((MINIDUMP_TYPE)(MiniDumpWithDataSegs | MiniDumpWithIndirectlyReferencedMemory), &ExInfo, "SecondLife.dmp"); + } +} diff --git a/indra/newview/llwindowlistener.cpp b/indra/newview/llwindowlistener.cpp index 19abf8837c..6221d67b26 100644 --- a/indra/newview/llwindowlistener.cpp +++ b/indra/newview/llwindowlistener.cpp @@ -1,525 +1,525 @@ -/** - * @file llwindowlistener.cpp - * @brief EventAPI interface for injecting input into LLWindow - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include "linden_common.h" - -#include "llwindowlistener.h" - -#include "llcoord.h" -#include "llfocusmgr.h" -#include "llkeyboard.h" -#include "llwindowcallbacks.h" -#include "llui.h" -#include "llview.h" -#include "llviewinject.h" -#include "llviewerwindow.h" -#include "llviewerinput.h" -#include "llrootview.h" -#include "llsdutil.h" -#include "stringize.h" -#include -#include -#include -#include - -LLWindowListener::LLWindowListener(LLViewerWindow *window, const KeyboardGetter& kbgetter) - : LLEventAPI("LLWindow", "Inject input events into the LLWindow instance"), - mWindow(window), - mKbGetter(kbgetter) -{ - std::string keySomething = - "Given [\"keysym\"], [\"keycode\"] or [\"char\"], inject the specified "; - std::string keyExplain = - "(integer keycode values, or keysym string from any addKeyName() call in\n" - "http://bitbucket.org/lindenlab/viewer-release/src/tip/indra/llwindow/llkeyboard.cpp )\n"; - std::string mask = - "Specify optional [\"mask\"] as an array containing any of \"CTL\", \"ALT\",\n" - "\"SHIFT\" or \"MAC_CONTROL\"; the corresponding modifier bits will be combined\n" - "to form the mask used with the event."; - - std::string given = "Given "; - std::string mouseParams = - "optional [\"path\"], optional [\"x\"] and [\"y\"], inject the requested mouse "; - std::string buttonParams = - std::string("[\"button\"], ") + mouseParams; - std::string buttonExplain = - "(button values \"LEFT\", \"MIDDLE\", \"RIGHT\")\n"; - std::string paramsExplain = - "[\"path\"] is as for LLUI::getInstance()->resolvePath(), described in\n" - "http://bitbucket.org/lindenlab/viewer-release/src/tip/indra/llui/llui.h\n" - "If you omit [\"path\"], you must specify both [\"x\"] and [\"y\"].\n" - "If you specify [\"path\"] without both [\"x\"] and [\"y\"], will synthesize (x, y)\n" - "in the center of the LLView selected by [\"path\"].\n" - "You may specify [\"path\"] with both [\"x\"] and [\"y\"], will use your (x, y).\n" - "This may cause the LLView selected by [\"path\"] to reject the event.\n" - "Optional [\"reply\"] requests a reply event on the named LLEventPump.\n" - "reply[\"error\"] isUndefined (None) on success, else an explanatory message.\n"; - - add("getInfo", - "Get information about the ui element specified by [\"path\"]", - &LLWindowListener::getInfo, - LLSDMap("reply", LLSD())); - add("getPaths", - "Send on [\"reply\"] an event in which [\"paths\"] is an array of valid LLView\n" - "pathnames. Optional [\"under\"] pathname specifies the base node under which\n" - "to list; all nodes from root if no [\"under\"].", - &LLWindowListener::getPaths, - LLSDMap("reply", LLSD())); - add("keyDown", - keySomething + "keypress event.\n" + keyExplain + mask, - &LLWindowListener::keyDown); - add("keyUp", - keySomething + "key release event.\n" + keyExplain + mask, - &LLWindowListener::keyUp); - add("mouseDown", - given + buttonParams + "click event.\n" + buttonExplain + paramsExplain + mask, - &LLWindowListener::mouseDown); - add("mouseUp", - given + buttonParams + "release event.\n" + buttonExplain + paramsExplain + mask, - &LLWindowListener::mouseUp); - add("mouseMove", - given + mouseParams + "movement event.\n" + paramsExplain + mask, - &LLWindowListener::mouseMove); - add("mouseScroll", - "Given an integer number of [\"clicks\"], inject the requested mouse scroll event.\n" - "(positive clicks moves downward through typical content)", - &LLWindowListener::mouseScroll); -} - -template -class StringLookup -{ -private: - std::string mDesc; - typedef std::map Map; - Map mMap; - -public: - StringLookup(const std::string& desc): mDesc(desc) {} - - MAPPED lookup(const typename Map::key_type& key) const - { - typename Map::const_iterator found = mMap.find(key); - if (found == mMap.end()) - { - LL_WARNS("LLWindowListener") << "Unknown " << mDesc << " '" << key << "'" << LL_ENDL; - return MAPPED(); - } - return found->second; - } - -protected: - void add(const typename Map::key_type& key, const typename Map::mapped_type& value) - { - mMap.insert(typename Map::value_type(key, value)); - } -}; - -namespace { - -// helper for getMask() -MASK lookupMask_(const std::string& maskname) -{ - // It's unclear to me whether MASK_MAC_CONTROL is important, but it's not - // supported by maskFromString(). Handle that specially. - if (maskname == "MAC_CONTROL") - { - return MASK_MAC_CONTROL; - } - else - { - // In case of lookup failure, return MASK_NONE, which won't affect our - // caller's OR. - MASK mask(MASK_NONE); - LLKeyboard::maskFromString(maskname, &mask); - return mask; - } -} - -MASK getMask(const LLSD& event) -{ - LLSD masknames(event["mask"]); - if (! masknames.isArray()) - { - // If event["mask"] is a single string, perform normal lookup on it. - return lookupMask_(masknames); - } - - // Here event["mask"] is an array of mask-name strings. OR together their - // corresponding bits. - MASK mask(MASK_NONE); - for (LLSD::array_const_iterator ai(masknames.beginArray()), aend(masknames.endArray()); - ai != aend; ++ai) - { - mask |= lookupMask_(*ai); - } - return mask; -} - -KEY getKEY(const LLSD& event) -{ - if (event.has("keysym")) - { - // Initialize to KEY_NONE; that way we can ignore the bool return from - // keyFromString() and, in the lookup-fail case, simply return KEY_NONE. - KEY key(KEY_NONE); - LLKeyboard::keyFromString(event["keysym"], &key); - return key; - } - else if (event.has("keycode")) - { - return KEY(event["keycode"].asInteger()); - } - else - { - return KEY(event["char"].asString()[0]); - } -} - -} // namespace - -void LLWindowListener::getInfo(LLSD const & evt) -{ - Response response(LLSD(), evt); - - if (evt.has("path")) - { - std::string path(evt["path"]); - LLView * target_view = LLUI::getInstance()->resolvePath(LLUI::getInstance()->getRootView(), path); - if (target_view != 0) - { - response.setResponse(target_view->getInfo()); - } - else - { - response.error(STRINGIZE(evt["op"].asString() << " request " - "specified invalid \"path\": '" << path << "'")); - } - } - else - { - response.error( - STRINGIZE(evt["op"].asString() << "request did not provide a path" )); - } -} - -void LLWindowListener::getPaths(LLSD const & request) -{ - Response response(LLSD(), request); - LLView *root(LLUI::getInstance()->getRootView()), *base(NULL); - // Capturing request["under"] as string means we conflate the case in - // which there is no ["under"] key with the case in which its value is the - // empty string. That seems to make sense to me. - std::string under(request["under"]); - - // Deal with optional "under" parameter - if (under.empty()) - { - base = root; - } - else - { - base = LLUI::getInstance()->resolvePath(root, under); - if (! base) - { - return response.error(STRINGIZE(request["op"].asString() << " request " - "specified invalid \"under\" path: '" << under << "'")); - } - } - - // Traverse the entire subtree under 'base', collecting pathnames - for (LLView::tree_iterator_t ti(base->beginTreeDFS()), tend(base->endTreeDFS()); - ti != tend; ++ti) - { - response["paths"].append((*ti)->getPathname()); - } -} - -void LLWindowListener::keyDown(LLSD const & evt) -{ - Response response(LLSD(), evt); - KEY key = getKEY(evt); - MASK mask = getMask(evt); - - if (evt.has("path")) - { - std::string path(evt["path"]); - LLView * target_view = LLUI::getInstance()->resolvePath(LLUI::getInstance()->getRootView(), path); - if (target_view == 0) - { - response.error(STRINGIZE(evt["op"].asString() << " request " - "specified invalid \"path\": '" << path << "'")); - } - else if(target_view->isAvailable()) - { - response.setResponse(target_view->getInfo()); - - gFocusMgr.setKeyboardFocus(target_view); - gViewerInput.handleKey(key, mask, false); - if(key < 0x80) mWindow->handleUnicodeChar(key, mask); - } - else - { - response.error(STRINGIZE(evt["op"].asString() << " request " - "element specified by \"path\": '" << path << "'" - << " is not visible")); - } - } - else - { - gViewerInput.handleKey(key, mask, false); - if(key < 0x80) mWindow->handleUnicodeChar(key, mask); - } -} - -void LLWindowListener::keyUp(LLSD const & evt) -{ - Response response(LLSD(), evt); - - if (evt.has("path")) - { - std::string path(evt["path"]); - LLView * target_view = LLUI::getInstance()->resolvePath(LLUI::getInstance()->getRootView(), path); - if (target_view == 0 ) - { - response.error(STRINGIZE(evt["op"].asString() << " request " - "specified invalid \"path\": '" << path << "'")); - } - else if (target_view->isAvailable()) - { - response.setResponse(target_view->getInfo()); - - gFocusMgr.setKeyboardFocus(target_view); - mKbGetter()->handleTranslatedKeyUp(getKEY(evt), getMask(evt)); - } - else - { - response.error(STRINGIZE(evt["op"].asString() << " request " - "element specified byt \"path\": '" << path << "'" - << " is not visible")); - } - } - else - { - mKbGetter()->handleTranslatedKeyUp(getKEY(evt), getMask(evt)); - } -} - -// for WhichButton -typedef bool (LLWindowCallbacks::*MouseMethod)(LLWindow *, LLCoordGL, MASK); -struct Actions -{ - Actions(const MouseMethod& d, const MouseMethod& u): down(d), up(u), valid(true) {} - Actions(): valid(false) {} - MouseMethod down, up; - bool valid; -}; - -struct WhichButton: public StringLookup -{ - WhichButton(): StringLookup("mouse button") - { - add("LEFT", Actions(&LLWindowCallbacks::handleMouseDown, - &LLWindowCallbacks::handleMouseUp)); - add("RIGHT", Actions(&LLWindowCallbacks::handleRightMouseDown, - &LLWindowCallbacks::handleRightMouseUp)); - add("MIDDLE", Actions(&LLWindowCallbacks::handleMiddleMouseDown, - &LLWindowCallbacks::handleMiddleMouseUp)); - } -}; -static WhichButton buttons; - -typedef boost::function MouseFunc; - -// Wrap a function returning 'void' to return 'true' instead. I'm sure there's -// a more generic way to accomplish this, but generically handling the -// arguments seems to require variadic templates and perfect forwarding. (We -// used to be able to write (boost::lambda::bind(...), true), counting on -// boost::lambda's comma operator overload, until -// https://svn.boost.org/trac/boost/ticket/10864. And boost::phoenix doesn't -// seem to overload comma the same way; or at least not with bind().) -class MouseFuncTrue -{ - typedef boost::function MouseFuncVoid; - MouseFuncVoid mFunc; - -public: - MouseFuncTrue(const MouseFuncVoid& func): - mFunc(func) - {} - - bool operator()(LLCoordGL coords, MASK mask) const - { - mFunc(coords, mask); - return true; - } -}; - -static void mouseEvent(const MouseFunc& func, const LLSD& request) -{ - // Ensure we send response - LLEventAPI::Response response(LLSD(), request); - // We haven't yet established whether the incoming request has "x" and "y", - // but capture this anyway, with 0 for omitted values. - LLCoordGL pos(request["x"].asInteger(), request["y"].asInteger()); - bool has_pos(request.has("x") && request.has("y")); - - std::unique_ptr tempfunc; - - // Documentation for mouseDown(), mouseUp() and mouseMove() claims you - // must either specify ["path"], or both of ["x"] and ["y"]. You MAY - // specify all. Let's say that passing "path" as an empty string is - // equivalent to not passing it at all. - std::string path(request["path"]); - if (path.empty()) - { - // Without "path", you must specify both "x" and "y". - if (! has_pos) - { - return response.error(STRINGIZE(request["op"].asString() << " request " - "without \"path\" must specify both \"x\" and \"y\": " - << request)); - } - } - else // ! path.empty() - { - LLView* root = LLUI::getInstance()->getRootView(); - LLView* target = LLUI::getInstance()->resolvePath(root, path); - if (! target) - { - return response.error(STRINGIZE(request["op"].asString() << " request " - "specified invalid \"path\": '" << path << "'")); - } - - response.setResponse(target->getInfo()); - - // The intent of this test is to prevent trying to drill down to a - // widget in a hidden floater, or on a tab that's not current, etc. - if (! target->isInVisibleChain()) - { - return response.error(STRINGIZE(request["op"].asString() << " request " - "specified \"path\" not currently visible: '" - << path << "'")); - } - - // This test isn't folded in with the above error case since you can - // (e.g.) pop up a tooltip even for a disabled widget. - if (! target->isInEnabledChain()) - { - response.warn(STRINGIZE(request["op"].asString() << " request " - "specified \"path\" not currently enabled: '" - << path << "'")); - } - - if (! has_pos) - { - LLRect rect(target->calcScreenRect()); - pos.set(rect.getCenterX(), rect.getCenterY()); - // nonstandard warning tactic: probably usual case; we want event - // sender to know synthesized (x, y), but maybe don't need to log? - response["warnings"].append(STRINGIZE("using center point (" - << pos.mX << ", " << pos.mY << ")")); - } - -/*==========================================================================*| - // NEVER MIND: the LLView tree defines priority handler layers in - // front of the normal widget set, so this has never yet produced - // anything but spam warnings. (sigh) - - // recursive childFromPoint() should give us the frontmost, leafmost - // widget at the specified (x, y). - LLView* frontmost = root->childFromPoint(pos.mX, pos.mY, true); - if (frontmost != target) - { - response.warn(STRINGIZE(request["op"].asString() << " request " - "specified \"path\" = '" << path - << "', but frontmost LLView at (" << pos.mX << ", " << pos.mY - << ") is '" << LLView::getPathname(frontmost) << "'")); - } -|*==========================================================================*/ - - // Instantiate a TemporaryDrilldownFunc to route incoming mouse events - // to the target LLView*. But put it on the heap since "path" is - // optional. Nonetheless, manage it with a boost::scoped_ptr so it - // will be destroyed when we leave. - tempfunc.reset(new LLView::TemporaryDrilldownFunc(llview::TargetEvent(target))); - } - - // The question of whether the requested LLView actually handled the - // specified event is important enough, and its handling unclear enough, - // to warrant a separate response attribute. Instead of deciding here to - // make it a warning, or an error, let caller decide. - response["handled"] = func(pos, getMask(request)); - - // On exiting this scope, response will send, tempfunc will restore the - // normal pointInView(x, y) containment logic, etc. -} - -void LLWindowListener::mouseDown(LLSD const & request) -{ - Actions actions(buttons.lookup(request["button"])); - if (actions.valid) - { - // Normally you can pass NULL to an LLWindow* without compiler - // complaint, but going through boost::bind() evidently - // bypasses that special case: it only knows you're trying to pass an - // int to a pointer. Explicitly cast NULL to the desired pointer type. - mouseEvent(boost::bind(actions.down, mWindow, - static_cast(NULL), _1, _2), - request); - } -} - -void LLWindowListener::mouseUp(LLSD const & request) -{ - Actions actions(buttons.lookup(request["button"])); - if (actions.valid) - { - mouseEvent(boost::bind(actions.up, mWindow, - static_cast(NULL), _1, _2), - request); - } -} - -void LLWindowListener::mouseMove(LLSD const & request) -{ - // We want to call the same central mouseEvent() routine for - // handleMouseMove() as for button clicks. But handleMouseMove() returns - // void, whereas mouseEvent() accepts a function returning bool -- and - // uses that bool return. Use MouseFuncTrue to construct a callable that - // returns bool anyway. - mouseEvent(MouseFuncTrue(boost::bind(&LLWindowCallbacks::handleMouseMove, mWindow, - static_cast(NULL), _1, _2)), - request); -} - -void LLWindowListener::mouseScroll(LLSD const & request) -{ - S32 clicks = request["clicks"].asInteger(); - - mWindow->handleScrollWheel(NULL, clicks); -} +/** + * @file llwindowlistener.cpp + * @brief EventAPI interface for injecting input into LLWindow + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "linden_common.h" + +#include "llwindowlistener.h" + +#include "llcoord.h" +#include "llfocusmgr.h" +#include "llkeyboard.h" +#include "llwindowcallbacks.h" +#include "llui.h" +#include "llview.h" +#include "llviewinject.h" +#include "llviewerwindow.h" +#include "llviewerinput.h" +#include "llrootview.h" +#include "llsdutil.h" +#include "stringize.h" +#include +#include +#include +#include + +LLWindowListener::LLWindowListener(LLViewerWindow *window, const KeyboardGetter& kbgetter) + : LLEventAPI("LLWindow", "Inject input events into the LLWindow instance"), + mWindow(window), + mKbGetter(kbgetter) +{ + std::string keySomething = + "Given [\"keysym\"], [\"keycode\"] or [\"char\"], inject the specified "; + std::string keyExplain = + "(integer keycode values, or keysym string from any addKeyName() call in\n" + "http://bitbucket.org/lindenlab/viewer-release/src/tip/indra/llwindow/llkeyboard.cpp )\n"; + std::string mask = + "Specify optional [\"mask\"] as an array containing any of \"CTL\", \"ALT\",\n" + "\"SHIFT\" or \"MAC_CONTROL\"; the corresponding modifier bits will be combined\n" + "to form the mask used with the event."; + + std::string given = "Given "; + std::string mouseParams = + "optional [\"path\"], optional [\"x\"] and [\"y\"], inject the requested mouse "; + std::string buttonParams = + std::string("[\"button\"], ") + mouseParams; + std::string buttonExplain = + "(button values \"LEFT\", \"MIDDLE\", \"RIGHT\")\n"; + std::string paramsExplain = + "[\"path\"] is as for LLUI::getInstance()->resolvePath(), described in\n" + "http://bitbucket.org/lindenlab/viewer-release/src/tip/indra/llui/llui.h\n" + "If you omit [\"path\"], you must specify both [\"x\"] and [\"y\"].\n" + "If you specify [\"path\"] without both [\"x\"] and [\"y\"], will synthesize (x, y)\n" + "in the center of the LLView selected by [\"path\"].\n" + "You may specify [\"path\"] with both [\"x\"] and [\"y\"], will use your (x, y).\n" + "This may cause the LLView selected by [\"path\"] to reject the event.\n" + "Optional [\"reply\"] requests a reply event on the named LLEventPump.\n" + "reply[\"error\"] isUndefined (None) on success, else an explanatory message.\n"; + + add("getInfo", + "Get information about the ui element specified by [\"path\"]", + &LLWindowListener::getInfo, + LLSDMap("reply", LLSD())); + add("getPaths", + "Send on [\"reply\"] an event in which [\"paths\"] is an array of valid LLView\n" + "pathnames. Optional [\"under\"] pathname specifies the base node under which\n" + "to list; all nodes from root if no [\"under\"].", + &LLWindowListener::getPaths, + LLSDMap("reply", LLSD())); + add("keyDown", + keySomething + "keypress event.\n" + keyExplain + mask, + &LLWindowListener::keyDown); + add("keyUp", + keySomething + "key release event.\n" + keyExplain + mask, + &LLWindowListener::keyUp); + add("mouseDown", + given + buttonParams + "click event.\n" + buttonExplain + paramsExplain + mask, + &LLWindowListener::mouseDown); + add("mouseUp", + given + buttonParams + "release event.\n" + buttonExplain + paramsExplain + mask, + &LLWindowListener::mouseUp); + add("mouseMove", + given + mouseParams + "movement event.\n" + paramsExplain + mask, + &LLWindowListener::mouseMove); + add("mouseScroll", + "Given an integer number of [\"clicks\"], inject the requested mouse scroll event.\n" + "(positive clicks moves downward through typical content)", + &LLWindowListener::mouseScroll); +} + +template +class StringLookup +{ +private: + std::string mDesc; + typedef std::map Map; + Map mMap; + +public: + StringLookup(const std::string& desc): mDesc(desc) {} + + MAPPED lookup(const typename Map::key_type& key) const + { + typename Map::const_iterator found = mMap.find(key); + if (found == mMap.end()) + { + LL_WARNS("LLWindowListener") << "Unknown " << mDesc << " '" << key << "'" << LL_ENDL; + return MAPPED(); + } + return found->second; + } + +protected: + void add(const typename Map::key_type& key, const typename Map::mapped_type& value) + { + mMap.insert(typename Map::value_type(key, value)); + } +}; + +namespace { + +// helper for getMask() +MASK lookupMask_(const std::string& maskname) +{ + // It's unclear to me whether MASK_MAC_CONTROL is important, but it's not + // supported by maskFromString(). Handle that specially. + if (maskname == "MAC_CONTROL") + { + return MASK_MAC_CONTROL; + } + else + { + // In case of lookup failure, return MASK_NONE, which won't affect our + // caller's OR. + MASK mask(MASK_NONE); + LLKeyboard::maskFromString(maskname, &mask); + return mask; + } +} + +MASK getMask(const LLSD& event) +{ + LLSD masknames(event["mask"]); + if (! masknames.isArray()) + { + // If event["mask"] is a single string, perform normal lookup on it. + return lookupMask_(masknames); + } + + // Here event["mask"] is an array of mask-name strings. OR together their + // corresponding bits. + MASK mask(MASK_NONE); + for (LLSD::array_const_iterator ai(masknames.beginArray()), aend(masknames.endArray()); + ai != aend; ++ai) + { + mask |= lookupMask_(*ai); + } + return mask; +} + +KEY getKEY(const LLSD& event) +{ + if (event.has("keysym")) + { + // Initialize to KEY_NONE; that way we can ignore the bool return from + // keyFromString() and, in the lookup-fail case, simply return KEY_NONE. + KEY key(KEY_NONE); + LLKeyboard::keyFromString(event["keysym"], &key); + return key; + } + else if (event.has("keycode")) + { + return KEY(event["keycode"].asInteger()); + } + else + { + return KEY(event["char"].asString()[0]); + } +} + +} // namespace + +void LLWindowListener::getInfo(LLSD const & evt) +{ + Response response(LLSD(), evt); + + if (evt.has("path")) + { + std::string path(evt["path"]); + LLView * target_view = LLUI::getInstance()->resolvePath(LLUI::getInstance()->getRootView(), path); + if (target_view != 0) + { + response.setResponse(target_view->getInfo()); + } + else + { + response.error(STRINGIZE(evt["op"].asString() << " request " + "specified invalid \"path\": '" << path << "'")); + } + } + else + { + response.error( + STRINGIZE(evt["op"].asString() << "request did not provide a path" )); + } +} + +void LLWindowListener::getPaths(LLSD const & request) +{ + Response response(LLSD(), request); + LLView *root(LLUI::getInstance()->getRootView()), *base(NULL); + // Capturing request["under"] as string means we conflate the case in + // which there is no ["under"] key with the case in which its value is the + // empty string. That seems to make sense to me. + std::string under(request["under"]); + + // Deal with optional "under" parameter + if (under.empty()) + { + base = root; + } + else + { + base = LLUI::getInstance()->resolvePath(root, under); + if (! base) + { + return response.error(STRINGIZE(request["op"].asString() << " request " + "specified invalid \"under\" path: '" << under << "'")); + } + } + + // Traverse the entire subtree under 'base', collecting pathnames + for (LLView::tree_iterator_t ti(base->beginTreeDFS()), tend(base->endTreeDFS()); + ti != tend; ++ti) + { + response["paths"].append((*ti)->getPathname()); + } +} + +void LLWindowListener::keyDown(LLSD const & evt) +{ + Response response(LLSD(), evt); + KEY key = getKEY(evt); + MASK mask = getMask(evt); + + if (evt.has("path")) + { + std::string path(evt["path"]); + LLView * target_view = LLUI::getInstance()->resolvePath(LLUI::getInstance()->getRootView(), path); + if (target_view == 0) + { + response.error(STRINGIZE(evt["op"].asString() << " request " + "specified invalid \"path\": '" << path << "'")); + } + else if(target_view->isAvailable()) + { + response.setResponse(target_view->getInfo()); + + gFocusMgr.setKeyboardFocus(target_view); + gViewerInput.handleKey(key, mask, false); + if(key < 0x80) mWindow->handleUnicodeChar(key, mask); + } + else + { + response.error(STRINGIZE(evt["op"].asString() << " request " + "element specified by \"path\": '" << path << "'" + << " is not visible")); + } + } + else + { + gViewerInput.handleKey(key, mask, false); + if(key < 0x80) mWindow->handleUnicodeChar(key, mask); + } +} + +void LLWindowListener::keyUp(LLSD const & evt) +{ + Response response(LLSD(), evt); + + if (evt.has("path")) + { + std::string path(evt["path"]); + LLView * target_view = LLUI::getInstance()->resolvePath(LLUI::getInstance()->getRootView(), path); + if (target_view == 0 ) + { + response.error(STRINGIZE(evt["op"].asString() << " request " + "specified invalid \"path\": '" << path << "'")); + } + else if (target_view->isAvailable()) + { + response.setResponse(target_view->getInfo()); + + gFocusMgr.setKeyboardFocus(target_view); + mKbGetter()->handleTranslatedKeyUp(getKEY(evt), getMask(evt)); + } + else + { + response.error(STRINGIZE(evt["op"].asString() << " request " + "element specified byt \"path\": '" << path << "'" + << " is not visible")); + } + } + else + { + mKbGetter()->handleTranslatedKeyUp(getKEY(evt), getMask(evt)); + } +} + +// for WhichButton +typedef bool (LLWindowCallbacks::*MouseMethod)(LLWindow *, LLCoordGL, MASK); +struct Actions +{ + Actions(const MouseMethod& d, const MouseMethod& u): down(d), up(u), valid(true) {} + Actions(): valid(false) {} + MouseMethod down, up; + bool valid; +}; + +struct WhichButton: public StringLookup +{ + WhichButton(): StringLookup("mouse button") + { + add("LEFT", Actions(&LLWindowCallbacks::handleMouseDown, + &LLWindowCallbacks::handleMouseUp)); + add("RIGHT", Actions(&LLWindowCallbacks::handleRightMouseDown, + &LLWindowCallbacks::handleRightMouseUp)); + add("MIDDLE", Actions(&LLWindowCallbacks::handleMiddleMouseDown, + &LLWindowCallbacks::handleMiddleMouseUp)); + } +}; +static WhichButton buttons; + +typedef boost::function MouseFunc; + +// Wrap a function returning 'void' to return 'true' instead. I'm sure there's +// a more generic way to accomplish this, but generically handling the +// arguments seems to require variadic templates and perfect forwarding. (We +// used to be able to write (boost::lambda::bind(...), true), counting on +// boost::lambda's comma operator overload, until +// https://svn.boost.org/trac/boost/ticket/10864. And boost::phoenix doesn't +// seem to overload comma the same way; or at least not with bind().) +class MouseFuncTrue +{ + typedef boost::function MouseFuncVoid; + MouseFuncVoid mFunc; + +public: + MouseFuncTrue(const MouseFuncVoid& func): + mFunc(func) + {} + + bool operator()(LLCoordGL coords, MASK mask) const + { + mFunc(coords, mask); + return true; + } +}; + +static void mouseEvent(const MouseFunc& func, const LLSD& request) +{ + // Ensure we send response + LLEventAPI::Response response(LLSD(), request); + // We haven't yet established whether the incoming request has "x" and "y", + // but capture this anyway, with 0 for omitted values. + LLCoordGL pos(request["x"].asInteger(), request["y"].asInteger()); + bool has_pos(request.has("x") && request.has("y")); + + std::unique_ptr tempfunc; + + // Documentation for mouseDown(), mouseUp() and mouseMove() claims you + // must either specify ["path"], or both of ["x"] and ["y"]. You MAY + // specify all. Let's say that passing "path" as an empty string is + // equivalent to not passing it at all. + std::string path(request["path"]); + if (path.empty()) + { + // Without "path", you must specify both "x" and "y". + if (! has_pos) + { + return response.error(STRINGIZE(request["op"].asString() << " request " + "without \"path\" must specify both \"x\" and \"y\": " + << request)); + } + } + else // ! path.empty() + { + LLView* root = LLUI::getInstance()->getRootView(); + LLView* target = LLUI::getInstance()->resolvePath(root, path); + if (! target) + { + return response.error(STRINGIZE(request["op"].asString() << " request " + "specified invalid \"path\": '" << path << "'")); + } + + response.setResponse(target->getInfo()); + + // The intent of this test is to prevent trying to drill down to a + // widget in a hidden floater, or on a tab that's not current, etc. + if (! target->isInVisibleChain()) + { + return response.error(STRINGIZE(request["op"].asString() << " request " + "specified \"path\" not currently visible: '" + << path << "'")); + } + + // This test isn't folded in with the above error case since you can + // (e.g.) pop up a tooltip even for a disabled widget. + if (! target->isInEnabledChain()) + { + response.warn(STRINGIZE(request["op"].asString() << " request " + "specified \"path\" not currently enabled: '" + << path << "'")); + } + + if (! has_pos) + { + LLRect rect(target->calcScreenRect()); + pos.set(rect.getCenterX(), rect.getCenterY()); + // nonstandard warning tactic: probably usual case; we want event + // sender to know synthesized (x, y), but maybe don't need to log? + response["warnings"].append(STRINGIZE("using center point (" + << pos.mX << ", " << pos.mY << ")")); + } + +/*==========================================================================*| + // NEVER MIND: the LLView tree defines priority handler layers in + // front of the normal widget set, so this has never yet produced + // anything but spam warnings. (sigh) + + // recursive childFromPoint() should give us the frontmost, leafmost + // widget at the specified (x, y). + LLView* frontmost = root->childFromPoint(pos.mX, pos.mY, true); + if (frontmost != target) + { + response.warn(STRINGIZE(request["op"].asString() << " request " + "specified \"path\" = '" << path + << "', but frontmost LLView at (" << pos.mX << ", " << pos.mY + << ") is '" << LLView::getPathname(frontmost) << "'")); + } +|*==========================================================================*/ + + // Instantiate a TemporaryDrilldownFunc to route incoming mouse events + // to the target LLView*. But put it on the heap since "path" is + // optional. Nonetheless, manage it with a boost::scoped_ptr so it + // will be destroyed when we leave. + tempfunc.reset(new LLView::TemporaryDrilldownFunc(llview::TargetEvent(target))); + } + + // The question of whether the requested LLView actually handled the + // specified event is important enough, and its handling unclear enough, + // to warrant a separate response attribute. Instead of deciding here to + // make it a warning, or an error, let caller decide. + response["handled"] = func(pos, getMask(request)); + + // On exiting this scope, response will send, tempfunc will restore the + // normal pointInView(x, y) containment logic, etc. +} + +void LLWindowListener::mouseDown(LLSD const & request) +{ + Actions actions(buttons.lookup(request["button"])); + if (actions.valid) + { + // Normally you can pass NULL to an LLWindow* without compiler + // complaint, but going through boost::bind() evidently + // bypasses that special case: it only knows you're trying to pass an + // int to a pointer. Explicitly cast NULL to the desired pointer type. + mouseEvent(boost::bind(actions.down, mWindow, + static_cast(NULL), _1, _2), + request); + } +} + +void LLWindowListener::mouseUp(LLSD const & request) +{ + Actions actions(buttons.lookup(request["button"])); + if (actions.valid) + { + mouseEvent(boost::bind(actions.up, mWindow, + static_cast(NULL), _1, _2), + request); + } +} + +void LLWindowListener::mouseMove(LLSD const & request) +{ + // We want to call the same central mouseEvent() routine for + // handleMouseMove() as for button clicks. But handleMouseMove() returns + // void, whereas mouseEvent() accepts a function returning bool -- and + // uses that bool return. Use MouseFuncTrue to construct a callable that + // returns bool anyway. + mouseEvent(MouseFuncTrue(boost::bind(&LLWindowCallbacks::handleMouseMove, mWindow, + static_cast(NULL), _1, _2)), + request); +} + +void LLWindowListener::mouseScroll(LLSD const & request) +{ + S32 clicks = request["clicks"].asInteger(); + + mWindow->handleScrollWheel(NULL, clicks); +} diff --git a/indra/newview/llworld.cpp b/indra/newview/llworld.cpp index 8b163e0cab..e0c8186578 100644 --- a/indra/newview/llworld.cpp +++ b/indra/newview/llworld.cpp @@ -1,1419 +1,1419 @@ -/** - * @file llworld.cpp - * @brief Initial test structure to organize viewer regions - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llworld.h" -#include "llrender.h" - -#include "indra_constants.h" -#include "llstl.h" - -#include "llagent.h" -#include "llagentcamera.h" -#include "llviewercontrol.h" -#include "lldrawpool.h" -#include "llglheaders.h" -#include "llhttpnode.h" -#include "llregionhandle.h" -#include "llsky.h" -#include "llsurface.h" -#include "lltrans.h" -#include "llviewercamera.h" -#include "llviewertexture.h" -#include "llviewertexturelist.h" -#include "llviewernetwork.h" -#include "llviewerobjectlist.h" -#include "llviewerparceloverlay.h" -#include "llviewerregion.h" -#include "llviewerstats.h" -#include "llvlcomposition.h" -#include "llvoavatar.h" -#include "llvocache.h" -#include "llvowater.h" -#include "message.h" -#include "pipeline.h" -#include "llappviewer.h" // for do_disconnect() -#include "llscenemonitor.h" -#include -#include -#include -#include - - -// -// Globals -// -U32 gAgentPauseSerialNum = 0; - -// -// Constants -// -const S32 WORLD_PATCH_SIZE = 16; - -extern LLColor4U MAX_WATER_COLOR; - -const U32 LLWorld::mWidth = 256; - -// meters/point, therefore mWidth * mScale = meters per edge -const F32 LLWorld::mScale = 1.f; - -const F32 LLWorld::mWidthInMeters = mWidth * mScale; - -// -// Functions -// - -// allocate the stack -LLWorld::LLWorld() : - mLandFarClip(DEFAULT_FAR_PLANE), - mLastPacketsIn(0), - mLastPacketsOut(0), - mLastPacketsLost(0), - mSpaceTimeUSec(0) -{ - for (S32 i = 0; i < EDGE_WATER_OBJECTS_COUNT; i++) - { - mEdgeWaterObjects[i] = NULL; - } - - LLPointer raw = new LLImageRaw(1,1,4); - U8 *default_texture = raw->getData(); - *(default_texture++) = MAX_WATER_COLOR.mV[0]; - *(default_texture++) = MAX_WATER_COLOR.mV[1]; - *(default_texture++) = MAX_WATER_COLOR.mV[2]; - *(default_texture++) = MAX_WATER_COLOR.mV[3]; - - mDefaultWaterTexturep = LLViewerTextureManager::getLocalTexture(raw.get(), false); - gGL.getTexUnit(0)->bind(mDefaultWaterTexturep); - mDefaultWaterTexturep->setAddressMode(LLTexUnit::TAM_CLAMP); - - LLViewerRegion::sVOCacheCullingEnabled = gSavedSettings.getBOOL("RequestFullRegionCache") && gSavedSettings.getBOOL("ObjectCacheEnabled"); -} - - -void LLWorld::resetClass() -{ - mHoleWaterObjects.clear(); - gObjectList.destroy(); - gSky.cleanup(); // references an object - for(region_list_t::iterator region_it = mRegionList.begin(); region_it != mRegionList.end(); ) - { - LLViewerRegion* region_to_delete = *region_it++; - removeRegion(region_to_delete->getHost()); - } - - LLViewerPartSim::getInstance()->destroyClass(); - - mDefaultWaterTexturep = NULL ; - for (S32 i = 0; i < EDGE_WATER_OBJECTS_COUNT; i++) - { - mEdgeWaterObjects[i] = NULL; - } - - //make all visible drawbles invisible. - LLDrawable::incrementVisible(); - - LLSceneMonitor::deleteSingleton(); -} - - -LLViewerRegion* LLWorld::addRegion(const U64 ®ion_handle, const LLHost &host) -{ - LL_INFOS() << "Add region with handle: " << region_handle << " on host " << host << LL_ENDL; - LLViewerRegion *regionp = getRegionFromHandle(region_handle); - std::string seedUrl; - if (regionp) - { - LLHost old_host = regionp->getHost(); - // region already exists! - if (host == old_host && regionp->isAlive()) - { - // This is a duplicate for the same host and it's alive, don't bother. - LL_INFOS() << "Region already exists and is alive, using existing region" << LL_ENDL; - return regionp; - } - - if (host != old_host) - { - LL_WARNS() << "LLWorld::addRegion exists, but old host " << old_host - << " does not match new host " << host - << ", removing old region and creating new" << LL_ENDL; - } - if (!regionp->isAlive()) - { - LL_WARNS() << "LLWorld::addRegion exists, but isn't alive. Removing old region and creating new" << LL_ENDL; - } - - // Save capabilities seed URL - seedUrl = regionp->getCapability("Seed"); - - // Kill the old host, and then we can continue on and add the new host. We have to kill even if the host - // matches, because all the agent state for the new camera is completely different. - removeRegion(old_host); - } - else - { - LL_INFOS() << "Region does not exist, creating new one" << LL_ENDL; - } - - U32 iindex = 0; - U32 jindex = 0; - from_region_handle(region_handle, &iindex, &jindex); - S32 x = (S32)(iindex/mWidth); - S32 y = (S32)(jindex/mWidth); - LL_INFOS() << "Adding new region (" << x << ":" << y << ")" - << " on host: " << host << LL_ENDL; - - LLVector3d origin_global; - - origin_global = from_region_handle(region_handle); - - regionp = new LLViewerRegion(region_handle, - host, - mWidth, - WORLD_PATCH_SIZE, - getRegionWidthInMeters() ); - if (!regionp) - { - LL_ERRS() << "Unable to create new region!" << LL_ENDL; - } - - if ( !seedUrl.empty() ) - { - regionp->setCapability("Seed", seedUrl); - } - - mRegionList.push_back(regionp); - mActiveRegionList.push_back(regionp); - mCulledRegionList.push_back(regionp); - - - // Find all the adjacent regions, and attach them. - // Generate handles for all of the adjacent regions, and attach them in the correct way. - // connect the edges - F32 adj_x = 0.f; - F32 adj_y = 0.f; - F32 region_x = 0.f; - F32 region_y = 0.f; - U64 adj_handle = 0; - - F32 width = getRegionWidthInMeters(); - - LLViewerRegion *neighborp; - from_region_handle(region_handle, ®ion_x, ®ion_y); - - // Iterate through all directions, and connect neighbors if there. - S32 dir; - for (dir = 0; dir < 8; dir++) - { - adj_x = region_x + width * gDirAxes[dir][0]; - adj_y = region_y + width * gDirAxes[dir][1]; - to_region_handle(adj_x, adj_y, &adj_handle); - - neighborp = getRegionFromHandle(adj_handle); - if (neighborp) - { - //LL_INFOS() << "Connecting " << region_x << ":" << region_y << " -> " << adj_x << ":" << adj_y << LL_ENDL; - regionp->connectNeighbor(neighborp, dir); - } - } - - updateWaterObjects(); - - return regionp; -} - - -void LLWorld::removeRegion(const LLHost &host) -{ - F32 x, y; - - LLViewerRegion *regionp = getRegion(host); - if (!regionp) - { - LL_WARNS() << "Trying to remove region that doesn't exist!" << LL_ENDL; - return; - } - - if (regionp == gAgent.getRegion()) - { - for (region_list_t::iterator iter = mRegionList.begin(); - iter != mRegionList.end(); ++iter) - { - LLViewerRegion* reg = *iter; - LL_WARNS() << "RegionDump: " << reg->getName() - << " " << reg->getHost() - << " " << reg->getOriginGlobal() - << LL_ENDL; - } - - LL_WARNS() << "Agent position global " << gAgent.getPositionGlobal() - << " agent " << gAgent.getPositionAgent() - << LL_ENDL; - - LL_WARNS() << "Regions visited " << gAgent.getRegionsVisited() << LL_ENDL; - - LL_WARNS() << "gFrameTimeSeconds " << gFrameTimeSeconds << LL_ENDL; - - LL_WARNS() << "Disabling region " << regionp->getName() << " that agent is in!" << LL_ENDL; - LLAppViewer::instance()->forceDisconnect(LLTrans::getString("YouHaveBeenDisconnected")); - - regionp->saveObjectCache() ; //force to save objects here in case that the object cache is about to be destroyed. - return; - } - - from_region_handle(regionp->getHandle(), &x, &y); - LL_INFOS() << "Removing region " << x << ":" << y << LL_ENDL; - - mRegionList.remove(regionp); - mActiveRegionList.remove(regionp); - mCulledRegionList.remove(regionp); - mVisibleRegionList.remove(regionp); - - mRegionRemovedSignal(regionp); - - updateWaterObjects(); - - //double check all objects of this region are removed. - gObjectList.clearAllMapObjectsInRegion(regionp) ; - //llassert_always(!gObjectList.hasMapObjectInRegion(regionp)) ; - - delete regionp; // Delete last to prevent use after free -} - - -LLViewerRegion* LLWorld::getRegion(const LLHost &host) -{ - for (region_list_t::iterator iter = mRegionList.begin(); - iter != mRegionList.end(); ++iter) - { - LLViewerRegion* regionp = *iter; - if (regionp->getHost() == host) - { - return regionp; - } - } - return NULL; -} - -LLViewerRegion* LLWorld::getRegionFromPosAgent(const LLVector3 &pos) -{ - return getRegionFromPosGlobal(gAgent.getPosGlobalFromAgent(pos)); -} - -LLViewerRegion* LLWorld::getRegionFromPosGlobal(const LLVector3d &pos) -{ - for (region_list_t::iterator iter = mRegionList.begin(); - iter != mRegionList.end(); ++iter) - { - LLViewerRegion* regionp = *iter; - if (regionp->pointInRegionGlobal(pos)) - { - return regionp; - } - } - return NULL; -} - - -LLVector3d LLWorld::clipToVisibleRegions(const LLVector3d &start_pos, const LLVector3d &end_pos) -{ - if (positionRegionValidGlobal(end_pos)) - { - return end_pos; - } - - LLViewerRegion* regionp = getRegionFromPosGlobal(start_pos); - if (!regionp) - { - return start_pos; - } - - LLVector3d delta_pos = end_pos - start_pos; - LLVector3d delta_pos_abs; - delta_pos_abs.setVec(delta_pos); - delta_pos_abs.abs(); - - LLVector3 region_coord = regionp->getPosRegionFromGlobal(end_pos); - F64 clip_factor = 1.0; - F32 region_width = regionp->getWidth(); - if (region_coord.mV[VX] < 0.f) - { - if (region_coord.mV[VY] < region_coord.mV[VX]) - { - // clip along y - - clip_factor = -(region_coord.mV[VY] / delta_pos_abs.mdV[VY]); - } - else - { - // clip along x - - clip_factor = -(region_coord.mV[VX] / delta_pos_abs.mdV[VX]); - } - } - else if (region_coord.mV[VX] > region_width) - { - if (region_coord.mV[VY] > region_coord.mV[VX]) - { - // clip along y + - clip_factor = (region_coord.mV[VY] - region_width) / delta_pos_abs.mdV[VY]; - } - else - { - //clip along x + - clip_factor = (region_coord.mV[VX] - region_width) / delta_pos_abs.mdV[VX]; - } - } - else if (region_coord.mV[VY] < 0.f) - { - // clip along y - - clip_factor = -(region_coord.mV[VY] / delta_pos_abs.mdV[VY]); - } - else if (region_coord.mV[VY] > region_width) - { - // clip along y + - clip_factor = (region_coord.mV[VY] - region_width) / delta_pos_abs.mdV[VY]; - } - - // clamp to within region dimensions - LLVector3d final_region_pos = LLVector3d(region_coord) - (delta_pos * clip_factor); - final_region_pos.mdV[VX] = llclamp(final_region_pos.mdV[VX], 0.0, - (F64)(region_width - F_ALMOST_ZERO)); - final_region_pos.mdV[VY] = llclamp(final_region_pos.mdV[VY], 0.0, - (F64)(region_width - F_ALMOST_ZERO)); - final_region_pos.mdV[VZ] = llclamp(final_region_pos.mdV[VZ], 0.0, - (F64)(LLWorld::getInstance()->getRegionMaxHeight() - F_ALMOST_ZERO)); - return regionp->getPosGlobalFromRegion(LLVector3(final_region_pos)); -} - -LLViewerRegion* LLWorld::getRegionFromHandle(const U64 &handle) -{ - for (region_list_t::iterator iter = mRegionList.begin(); - iter != mRegionList.end(); ++iter) - { - LLViewerRegion* regionp = *iter; - if (regionp->getHandle() == handle) - { - return regionp; - } - } - return NULL; -} - -LLViewerRegion* LLWorld::getRegionFromID(const LLUUID& region_id) -{ - for (region_list_t::iterator iter = mRegionList.begin(); - iter != mRegionList.end(); ++iter) - { - LLViewerRegion* regionp = *iter; - if (regionp->getRegionID() == region_id) - { - return regionp; - } - } - return NULL; -} - -void LLWorld::updateAgentOffset(const LLVector3d &offset_global) -{ -#if 0 - for (region_list_t::iterator iter = mRegionList.begin(); - iter != mRegionList.end(); ++iter) - { - LLViewerRegion* regionp = *iter; - regionp->setAgentOffset(offset_global); - } -#endif -} - - -bool LLWorld::positionRegionValidGlobal(const LLVector3d &pos_global) -{ - for (region_list_t::iterator iter = mRegionList.begin(); - iter != mRegionList.end(); ++iter) - { - LLViewerRegion* regionp = *iter; - if (regionp->pointInRegionGlobal(pos_global)) - { - return true; - } - } - return false; -} - - -// Allow objects to go up to their radius underground. -F32 LLWorld::getMinAllowedZ(LLViewerObject* object, const LLVector3d &global_pos) -{ - F32 land_height = resolveLandHeightGlobal(global_pos); - F32 radius = 0.5f * object->getScale().length(); - return land_height - radius; -} - - - -LLViewerRegion* LLWorld::resolveRegionGlobal(LLVector3 &pos_region, const LLVector3d &pos_global) -{ - LLViewerRegion *regionp = getRegionFromPosGlobal(pos_global); - - if (regionp) - { - pos_region = regionp->getPosRegionFromGlobal(pos_global); - return regionp; - } - - return NULL; -} - - -LLViewerRegion* LLWorld::resolveRegionAgent(LLVector3 &pos_region, const LLVector3 &pos_agent) -{ - LLVector3d pos_global = gAgent.getPosGlobalFromAgent(pos_agent); - LLViewerRegion *regionp = getRegionFromPosGlobal(pos_global); - - if (regionp) - { - pos_region = regionp->getPosRegionFromGlobal(pos_global); - return regionp; - } - - return NULL; -} - - -F32 LLWorld::resolveLandHeightAgent(const LLVector3 &pos_agent) -{ - LLVector3d pos_global = gAgent.getPosGlobalFromAgent(pos_agent); - return resolveLandHeightGlobal(pos_global); -} - - -F32 LLWorld::resolveLandHeightGlobal(const LLVector3d &pos_global) -{ - LLViewerRegion *regionp = getRegionFromPosGlobal(pos_global); - if (regionp) - { - return regionp->getLand().resolveHeightGlobal(pos_global); - } - return 0.0f; -} - - -// Takes a line defined by "point_a" and "point_b" and determines the closest (to point_a) -// point where the the line intersects an object or the land surface. Stores the results -// in "intersection" and "intersection_normal" and returns a scalar value that represents -// the normalized distance along the line from "point_a" to "intersection". -// -// Currently assumes point_a and point_b only differ in z-direction, -// but it may eventually become more general. -F32 LLWorld::resolveStepHeightGlobal(const LLVOAvatar* avatarp, const LLVector3d &point_a, const LLVector3d &point_b, - LLVector3d &intersection, LLVector3 &intersection_normal, - LLViewerObject **viewerObjectPtr) -{ - // initialize return value to null - if (viewerObjectPtr) - { - *viewerObjectPtr = NULL; - } - - LLViewerRegion *regionp = getRegionFromPosGlobal(point_a); - if (!regionp) - { - // We're outside the world - intersection = 0.5f * (point_a + point_b); - intersection_normal.setVec(0.0f, 0.0f, 1.0f); - return 0.5f; - } - - // calculate the length of the segment - F32 segment_length = (F32)((point_a - point_b).length()); - if (0.0f == segment_length) - { - intersection = point_a; - intersection_normal.setVec(0.0f, 0.0f, 1.0f); - return segment_length; - } - - // get land height - // Note: we assume that the line is parallel to z-axis here - LLVector3d land_intersection = point_a; - F32 normalized_land_distance; - - land_intersection.mdV[VZ] = regionp->getLand().resolveHeightGlobal(point_a); - normalized_land_distance = (F32)(point_a.mdV[VZ] - land_intersection.mdV[VZ]) / segment_length; - intersection = land_intersection; - intersection_normal = resolveLandNormalGlobal(land_intersection); - - if (avatarp && !avatarp->mFootPlane.isExactlyClear()) - { - LLVector3 foot_plane_normal(avatarp->mFootPlane.mV); - LLVector3 start_pt = avatarp->getRegion()->getPosRegionFromGlobal(point_a); - // added 0.05 meters to compensate for error in foot plane reported by Havok - F32 norm_dist_from_plane = ((start_pt * foot_plane_normal) - avatarp->mFootPlane.mV[VW]) + 0.05f; - norm_dist_from_plane = llclamp(norm_dist_from_plane / segment_length, 0.f, 1.f); - if (norm_dist_from_plane < normalized_land_distance) - { - // collided with object before land - normalized_land_distance = norm_dist_from_plane; - intersection = point_a; - intersection.mdV[VZ] -= norm_dist_from_plane * segment_length; - intersection_normal = foot_plane_normal; - } - else - { - intersection = land_intersection; - intersection_normal = resolveLandNormalGlobal(land_intersection); - } - } - - return normalized_land_distance; -} - - -LLSurfacePatch * LLWorld::resolveLandPatchGlobal(const LLVector3d &pos_global) -{ - // returns a pointer to the patch at this location - LLViewerRegion *regionp = getRegionFromPosGlobal(pos_global); - if (!regionp) - { - return NULL; - } - - return regionp->getLand().resolvePatchGlobal(pos_global); -} - - -LLVector3 LLWorld::resolveLandNormalGlobal(const LLVector3d &pos_global) -{ - LLViewerRegion *regionp = getRegionFromPosGlobal(pos_global); - if (!regionp) - { - return LLVector3::z_axis; - } - - return regionp->getLand().resolveNormalGlobal(pos_global); -} - - -void LLWorld::updateVisibilities() -{ - F32 cur_far_clip = LLViewerCamera::getInstance()->getFar(); - - // Go through the culled list and check for visible regions (region is visible if land is visible) - for (region_list_t::iterator iter = mCulledRegionList.begin(); - iter != mCulledRegionList.end(); ) - { - region_list_t::iterator curiter = iter++; - LLViewerRegion* regionp = *curiter; - - LLSpatialPartition* part = regionp->getSpatialPartition(LLViewerRegion::PARTITION_TERRAIN); - if (part) - { - LLSpatialGroup* group = (LLSpatialGroup*) part->mOctree->getListener(0); - const LLVector4a* bounds = group->getBounds(); - if (LLViewerCamera::getInstance()->AABBInFrustum(bounds[0], bounds[1])) - { - mCulledRegionList.erase(curiter); - mVisibleRegionList.push_back(regionp); - } - } - } - - // Update all of the visible regions - for (region_list_t::iterator iter = mVisibleRegionList.begin(); - iter != mVisibleRegionList.end(); ) - { - region_list_t::iterator curiter = iter++; - LLViewerRegion* regionp = *curiter; - if (!regionp->getLand().hasZData()) - { - continue; - } - - LLSpatialPartition* part = regionp->getSpatialPartition(LLViewerRegion::PARTITION_TERRAIN); - if (part) - { - LLSpatialGroup* group = (LLSpatialGroup*) part->mOctree->getListener(0); - const LLVector4a* bounds = group->getBounds(); - if (LLViewerCamera::getInstance()->AABBInFrustum(bounds[0], bounds[1])) - { - regionp->calculateCameraDistance(); - regionp->getLand().updatePatchVisibilities(gAgent); - } - else - { - mVisibleRegionList.erase(curiter); - mCulledRegionList.push_back(regionp); - } - } - } - - // Sort visible regions - mVisibleRegionList.sort(LLViewerRegion::CompareDistance()); - - LLViewerCamera::getInstance()->setFar(cur_far_clip); -} - -static LLTrace::SampleStatHandle<> sNumActiveCachedObjects("numactivecachedobjects", "Number of objects loaded from cache"); - -void LLWorld::updateRegions(F32 max_update_time) -{ - LL_PROFILE_ZONE_SCOPED; - LLTimer update_timer; - mNumOfActiveCachedObjects = 0; - - if(LLViewerCamera::getInstance()->isChanged()) - { - LLViewerRegion::sLastCameraUpdated = LLViewerOctreeEntryData::getCurrentFrame() + 1; - } - LLViewerRegion::calcNewObjectCreationThrottle(); - if(LLViewerRegion::isNewObjectCreationThrottleDisabled()) - { - max_update_time = llmax(max_update_time, 1.0f); //seconds, loosen the time throttle. - } - - F32 max_time = llmin((F32)(max_update_time - update_timer.getElapsedTimeF32()), max_update_time * 0.25f); - //update the self avatar region - LLViewerRegion* self_regionp = gAgent.getRegion(); - if(self_regionp) - { - self_regionp->idleUpdate(max_time); - } - - //sort regions by its mLastUpdate - //smaller mLastUpdate first to make sure every region has chance to get updated. - LLViewerRegion::region_priority_list_t region_list; - for (region_list_t::iterator iter = mRegionList.begin(); - iter != mRegionList.end(); ++iter) - { - LLViewerRegion* regionp = *iter; - if(regionp != self_regionp) - { - region_list.insert(regionp); - } - mNumOfActiveCachedObjects += regionp->getNumOfActiveCachedObjects(); - } - - // Perform idle time updates for the regions (and associated surfaces) - for (LLViewerRegion::region_priority_list_t::iterator iter = region_list.begin(); - iter != region_list.end(); ++iter) - { - if(max_time > 0.f) - { - max_time = llmin((F32)(max_update_time - update_timer.getElapsedTimeF32()), max_update_time * 0.25f); - } - - if(max_time > 0.f) - { - (*iter)->idleUpdate(max_time); - } - else - { - //perform some necessary but very light updates. - (*iter)->lightIdleUpdate(); - } - } - - if(max_time > 0.f) - { - max_time = llmin((F32)(max_update_time - update_timer.getElapsedTimeF32()), max_update_time * 0.25f); - } - if(max_time > 0.f) - { - LLViewerRegion::idleCleanup(max_time); - } - - sample(sNumActiveCachedObjects, mNumOfActiveCachedObjects); -} - -void LLWorld::clearAllVisibleObjects() -{ - for (region_list_t::iterator iter = mRegionList.begin(); - iter != mRegionList.end(); ++iter) - { - //clear all cached visible objects. - (*iter)->clearCachedVisibleObjects(); - } - clearHoleWaterObjects(); - clearEdgeWaterObjects(); -} - -void LLWorld::updateParticles() -{ - LLViewerPartSim::getInstance()->updateSimulation(); -} - -void LLWorld::renderPropertyLines() -{ - for (region_list_t::iterator iter = mVisibleRegionList.begin(); - iter != mVisibleRegionList.end(); ++iter) - { - LLViewerRegion* regionp = *iter; - regionp->renderPropertyLines(); - } -} - - -void LLWorld::updateNetStats() -{ - F64Bits bits; - - for (region_list_t::iterator iter = mActiveRegionList.begin(); - iter != mActiveRegionList.end(); ++iter) - { - LLViewerRegion* regionp = *iter; - regionp->updateNetStats(); - bits += regionp->mBitsReceived; - regionp->mBitsReceived = (F32Bits)0.f; - regionp->mPacketsReceived = 0.f; - } - - S32 packets_in = gMessageSystem->mPacketsIn - mLastPacketsIn; - S32 packets_out = gMessageSystem->mPacketsOut - mLastPacketsOut; - S32 packets_lost = gMessageSystem->mDroppedPackets - mLastPacketsLost; - - F64Bits actual_in_bits(gMessageSystem->mPacketRing.getAndResetActualInBits()); - F64Bits actual_out_bits(gMessageSystem->mPacketRing.getAndResetActualOutBits()); - - add(LLStatViewer::MESSAGE_SYSTEM_DATA_IN, actual_in_bits); - add(LLStatViewer::MESSAGE_SYSTEM_DATA_OUT, actual_out_bits); - add(LLStatViewer::ACTIVE_MESSAGE_DATA_RECEIVED, bits); - add(LLStatViewer::PACKETS_IN, packets_in); - add(LLStatViewer::PACKETS_OUT, packets_out); - add(LLStatViewer::PACKETS_LOST, packets_lost); - - F32 total_packets_in = LLViewerStats::instance().getRecording().getSum(LLStatViewer::PACKETS_IN); - if (total_packets_in > 0) - { - F32 total_packets_lost = LLViewerStats::instance().getRecording().getSum(LLStatViewer::PACKETS_LOST); - sample(LLStatViewer::PACKETS_LOST_PERCENT, LLUnits::Ratio::fromValue((F32)total_packets_lost/(F32)total_packets_in)); - } - - mLastPacketsIn = gMessageSystem->mPacketsIn; - mLastPacketsOut = gMessageSystem->mPacketsOut; - mLastPacketsLost = gMessageSystem->mDroppedPackets; -} - - -void LLWorld::printPacketsLost() -{ - LL_INFOS() << "Simulators:" << LL_ENDL; - LL_INFOS() << "----------" << LL_ENDL; - - LLCircuitData *cdp = NULL; - for (region_list_t::iterator iter = mActiveRegionList.begin(); - iter != mActiveRegionList.end(); ++iter) - { - LLViewerRegion* regionp = *iter; - cdp = gMessageSystem->mCircuitInfo.findCircuit(regionp->getHost()); - if (cdp) - { - LLVector3d range = regionp->getCenterGlobal() - gAgent.getPositionGlobal(); - - LL_INFOS() << regionp->getHost() << ", range: " << range.length() - << " packets lost: " << cdp->getPacketsLost() << LL_ENDL; - } - } -} - -void LLWorld::processCoarseUpdate(LLMessageSystem* msg, void** user_data) -{ - LLViewerRegion* region = LLWorld::getInstance()->getRegion(msg->getSender()); - if( region ) - { - region->updateCoarseLocations(msg); - } -} - -F32 LLWorld::getLandFarClip() const -{ - return mLandFarClip; -} - -void LLWorld::setLandFarClip(const F32 far_clip) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT; - static S32 const rwidth = (S32)REGION_WIDTH_U32; - S32 const n1 = (llceil(mLandFarClip) - 1) / rwidth; - S32 const n2 = (llceil(far_clip) - 1) / rwidth; - bool need_water_objects_update = n1 != n2; - - mLandFarClip = far_clip; - - if (need_water_objects_update) - { - updateWaterObjects(); - } -} - -// Some region that we're connected to, but not the one we're in, gave us -// a (possibly) new water height. Update it in our local copy. -void LLWorld::waterHeightRegionInfo(std::string const& sim_name, F32 water_height) -{ - for (region_list_t::iterator iter = mRegionList.begin(); iter != mRegionList.end(); ++iter) - { - if ((*iter)->getName() == sim_name) - { - (*iter)->setWaterHeight(water_height); - break; - } - } -} - -void LLWorld::clearHoleWaterObjects() -{ - for (std::list >::iterator iter = mHoleWaterObjects.begin(); - iter != mHoleWaterObjects.end(); ++iter) - { - LLVOWater* waterp = (*iter).get(); - gObjectList.killObject(waterp); - } - mHoleWaterObjects.clear(); -} - -void LLWorld::clearEdgeWaterObjects() -{ - for (S32 i = 0; i < EDGE_WATER_OBJECTS_COUNT; i++) - { - gObjectList.killObject(mEdgeWaterObjects[i]); - mEdgeWaterObjects[i] = NULL; - } -} - -void LLWorld::updateWaterObjects() -{ - if (!gAgent.getRegion()) - { - return; - } - if (mRegionList.empty()) - { - LL_WARNS() << "No regions!" << LL_ENDL; - return; - } - - // First, determine the min and max "box" of water objects - S32 min_x = 0; - S32 min_y = 0; - S32 max_x = 0; - S32 max_y = 0; - U32 region_x, region_y; - - S32 rwidth = 256; - - // We only want to fill in water for stuff that's near us, say, within 256 or 512m - S32 range = LLViewerCamera::getInstance()->getFar() > 256.f ? 512 : 256; - - LLViewerRegion* regionp = gAgent.getRegion(); - from_region_handle(regionp->getHandle(), ®ion_x, ®ion_y); - - min_x = (S32)region_x - range; - min_y = (S32)region_y - range; - max_x = (S32)region_x + range; - max_y = (S32)region_y + range; - - for (region_list_t::iterator iter = mRegionList.begin(); - iter != mRegionList.end(); ++iter) - { - LLViewerRegion* regionp = *iter; - LLVOWater* waterp = regionp->getLand().getWaterObj(); - if (waterp) - { - gObjectList.updateActive(waterp); - } - } - - clearHoleWaterObjects(); - - // Use the water height of the region we're on for areas where there is no region - F32 water_height = gAgent.getRegion()->getWaterHeight(); - - // Now, get a list of the holes - S32 x, y; - for (x = min_x; x <= max_x; x += rwidth) - { - for (y = min_y; y <= max_y; y += rwidth) - { - U64 region_handle = to_region_handle(x, y); - if (!getRegionFromHandle(region_handle)) - { // No region at that area, so make water - LLVOWater* waterp = (LLVOWater *)gObjectList.createObjectViewer(LLViewerObject::LL_VO_WATER, gAgent.getRegion()); - waterp->setUseTexture(false); - waterp->setPositionGlobal(LLVector3d(x + rwidth/2, - y + rwidth/2, - 256.f + water_height)); - waterp->setScale(LLVector3((F32)rwidth, (F32)rwidth, 512.f)); - gPipeline.createObject(waterp); - mHoleWaterObjects.push_back(waterp); - } - } - } - - // Update edge water objects - S32 wx, wy; - S32 center_x, center_y; - wx = (max_x - min_x) + rwidth; - wy = (max_y - min_y) + rwidth; - center_x = min_x + (wx >> 1); - center_y = min_y + (wy >> 1); - - S32 add_boundary[4] = { - (S32)(512 - (max_x - region_x)), - (S32)(512 - (max_y - region_y)), - (S32)(512 - (region_x - min_x)), - (S32)(512 - (region_y - min_y)) }; - - S32 dir; - for (dir = 0; dir < EDGE_WATER_OBJECTS_COUNT; dir++) - { - S32 dim[2] = { 0 }; - switch (gDirAxes[dir][0]) - { - case -1: dim[0] = add_boundary[2]; break; - case 0: dim[0] = wx; break; - default: dim[0] = add_boundary[0]; break; - } - switch (gDirAxes[dir][1]) - { - case -1: dim[1] = add_boundary[3]; break; - case 0: dim[1] = wy; break; - default: dim[1] = add_boundary[1]; break; - } - - // Resize and reshape the water objects - const S32 water_center_x = center_x + ll_round((wx + dim[0]) * 0.5f * gDirAxes[dir][0]); - const S32 water_center_y = center_y + ll_round((wy + dim[1]) * 0.5f * gDirAxes[dir][1]); - - LLVOWater* waterp = mEdgeWaterObjects[dir]; - if (!waterp || waterp->isDead()) - { - // The edge water objects can be dead because they're attached to the region that the - // agent was in when they were originally created. - mEdgeWaterObjects[dir] = (LLVOWater *)gObjectList.createObjectViewer(LLViewerObject::LL_VO_VOID_WATER, - gAgent.getRegion()); - waterp = mEdgeWaterObjects[dir]; - waterp->setUseTexture(false); - waterp->setIsEdgePatch(true); - gPipeline.createObject(waterp); - } - - waterp->setRegion(gAgent.getRegion()); - LLVector3d water_pos(water_center_x, water_center_y, 256.f + water_height) ; - LLVector3 water_scale((F32) dim[0], (F32) dim[1], 512.f); - - //stretch out to horizon - water_scale.mV[0] += fabsf(2048.f * gDirAxes[dir][0]); - water_scale.mV[1] += fabsf(2048.f * gDirAxes[dir][1]); - - water_pos.mdV[0] += 1024.f * gDirAxes[dir][0]; - water_pos.mdV[1] += 1024.f * gDirAxes[dir][1]; - - waterp->setPositionGlobal(water_pos); - waterp->setScale(water_scale); - - gObjectList.updateActive(waterp); - } -} - - -void LLWorld::shiftRegions(const LLVector3& offset) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - for (region_list_t::const_iterator i = getRegionList().begin(); i != getRegionList().end(); ++i) - { - LLViewerRegion* region = *i; - region->updateRenderMatrix(); - } - - LLViewerPartSim::getInstance()->shift(offset); -} - -LLViewerTexture* LLWorld::getDefaultWaterTexture() -{ - return mDefaultWaterTexturep; -} - -void LLWorld::setSpaceTimeUSec(const U64MicrosecondsImplicit space_time_usec) -{ - mSpaceTimeUSec = space_time_usec; -} - -U64MicrosecondsImplicit LLWorld::getSpaceTimeUSec() const -{ - return mSpaceTimeUSec; -} - -void LLWorld::requestCacheMisses() -{ - for (region_list_t::iterator iter = mRegionList.begin(); - iter != mRegionList.end(); ++iter) - { - LLViewerRegion* regionp = *iter; - regionp->requestCacheMisses(); - } -} - -void LLWorld::getInfo(LLSD& info) -{ - LLSD region_info; - for (region_list_t::iterator iter = mRegionList.begin(); - iter != mRegionList.end(); ++iter) - { - LLViewerRegion* regionp = *iter; - regionp->getInfo(region_info); - info["World"].append(region_info); - } -} - -void LLWorld::disconnectRegions() -{ - LLMessageSystem* msg = gMessageSystem; - for (region_list_t::iterator iter = mRegionList.begin(); - iter != mRegionList.end(); ++iter) - { - LLViewerRegion* regionp = *iter; - if (regionp == gAgent.getRegion()) - { - // Skip the main agent - continue; - } - - LL_INFOS() << "Sending AgentQuitCopy to: " << regionp->getHost() << LL_ENDL; - msg->newMessageFast(_PREHASH_AgentQuitCopy); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_FuseBlock); - msg->addU32Fast(_PREHASH_ViewerCircuitCode, gMessageSystem->mOurCircuitCode); - msg->sendMessage(regionp->getHost()); - } -} - -void process_enable_simulator(LLMessageSystem *msg, void **user_data) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; - // enable the appropriate circuit for this simulator and - // add its values into the gSimulator structure - U64 handle; - U32 ip_u32; - U16 port; - - msg->getU64Fast(_PREHASH_SimulatorInfo, _PREHASH_Handle, handle); - msg->getIPAddrFast(_PREHASH_SimulatorInfo, _PREHASH_IP, ip_u32); - msg->getIPPortFast(_PREHASH_SimulatorInfo, _PREHASH_Port, port); - - // which simulator should we modify? - LLHost sim(ip_u32, port); - - // Viewer trusts the simulator. - msg->enableCircuit(sim, true); - LLWorld::getInstance()->addRegion(handle, sim); - - // give the simulator a message it can use to get ip and port - LL_INFOS() << "simulator_enable() Enabling " << sim << " with code " << msg->getOurCircuitCode() << LL_ENDL; - msg->newMessageFast(_PREHASH_UseCircuitCode); - msg->nextBlockFast(_PREHASH_CircuitCode); - msg->addU32Fast(_PREHASH_Code, msg->getOurCircuitCode()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addUUIDFast(_PREHASH_ID, gAgent.getID()); - msg->sendReliable(sim); -} - -class LLEstablishAgentCommunication : public LLHTTPNode -{ - LOG_CLASS(LLEstablishAgentCommunication); -public: - virtual void describe(Description& desc) const - { - desc.shortInfo("seed capability info for a region"); - desc.postAPI(); - desc.input( - "{ seed-capability: ..., sim-ip: ..., sim-port }"); - desc.source(__FILE__, __LINE__); - } - - virtual void post(ResponsePtr response, const LLSD& context, const LLSD& input) const - { - if (LLApp::isExiting()) - { - return; - } - - if (gDisconnected) - { - return; - } - - if (!LLWorld::instanceExists()) - { - return; - } - - if (!input["body"].has("agent-id") || - !input["body"].has("sim-ip-and-port") || - !input["body"].has("seed-capability")) - { - LL_WARNS() << "invalid parameters" << LL_ENDL; - return; - } - - LLHost sim(input["body"]["sim-ip-and-port"].asString()); - if (sim.isInvalid()) - { - LL_WARNS() << "Got EstablishAgentCommunication with invalid host" << LL_ENDL; - return; - } - - LLViewerRegion* regionp = LLWorld::getInstance()->getRegion(sim); - if (!regionp) - { - LL_WARNS() << "Got EstablishAgentCommunication for unknown region " - << sim << LL_ENDL; - return; - } - LL_DEBUGS("CrossingCaps") << "Calling setSeedCapability from LLEstablishAgentCommunication::post. Seed cap == " - << input["body"]["seed-capability"] << " for region " << regionp->getRegionID() << LL_ENDL; - regionp->setSeedCapability(input["body"]["seed-capability"]); - } -}; - -// disable the circuit to this simulator -// Called in response to "DisableSimulator" message. -void process_disable_simulator(LLMessageSystem *mesgsys, void **user_data) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; - - LLHost host = mesgsys->getSender(); - - //LL_INFOS() << "Disabling simulator with message from " << host << LL_ENDL; - LLWorld::getInstance()->removeRegion(host); - - mesgsys->disableCircuit(host); -} - - -void process_region_handshake(LLMessageSystem* msg, void** user_data) -{ - LLHost host = msg->getSender(); - LLViewerRegion* regionp = LLWorld::getInstance()->getRegion(host); - if (!regionp) - { - LL_WARNS() << "Got region handshake for unknown region " - << host << LL_ENDL; - return; - } - - regionp->unpackRegionHandshake(); -} - - -void send_agent_pause() -{ - // *NOTE:Mani Pausing the mainloop timeout. Otherwise a long modal event may cause - // the thread monitor to timeout. - LLAppViewer::instance()->pauseMainloopTimeout(); - - // Note: used to check for LLWorld initialization before it became a singleton. - // Rather than just remove this check I'm changing it to assure that the message - // system has been initialized. -MG - if (!gMessageSystem) - { - return; - } - - gMessageSystem->newMessageFast(_PREHASH_AgentPause); - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgentID); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgentSessionID); - - gAgentPauseSerialNum++; - gMessageSystem->addU32Fast(_PREHASH_SerialNum, gAgentPauseSerialNum); - - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) - { - LLViewerRegion* regionp = *iter; - gMessageSystem->sendReliable(regionp->getHost()); - } - - gObjectList.mWasPaused = true; - LLViewerStats::instance().getRecording().stop(); -} - - -void send_agent_resume() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK - // Note: used to check for LLWorld initialization before it became a singleton. - // Rather than just remove this check I'm changing it to assure that the message - // system has been initialized. -MG - if (!gMessageSystem) - { - return; - } - - gMessageSystem->newMessageFast(_PREHASH_AgentResume); - gMessageSystem->nextBlockFast(_PREHASH_AgentData); - gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgentID); - gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgentSessionID); - - gAgentPauseSerialNum++; - gMessageSystem->addU32Fast(_PREHASH_SerialNum, gAgentPauseSerialNum); - - - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) - { - LLViewerRegion* regionp = *iter; - gMessageSystem->sendReliable(regionp->getHost()); - } - - // Resume data collection to ignore invalid rates - LLViewerStats::instance().getRecording().resume(); - - LLAppViewer::instance()->resumeMainloopTimeout(); -} - -static LLVector3d unpackLocalToGlobalPosition(U32 compact_local, const LLVector3d& region_origin) -{ - LLVector3d pos_local; - - pos_local.mdV[VZ] = (compact_local & 0xFFU) * 4; - pos_local.mdV[VY] = (compact_local >> 8) & 0xFFU; - pos_local.mdV[VX] = (compact_local >> 16) & 0xFFU; - - return region_origin + pos_local; -} - -void LLWorld::getAvatars(uuid_vec_t* avatar_ids, std::vector* positions, const LLVector3d& relative_to, F32 radius) const -{ - F32 radius_squared = radius * radius; - - if(avatar_ids != NULL) - { - avatar_ids->clear(); - } - if(positions != NULL) - { - positions->clear(); - } - // get the list of avatars from the character list first, so distances are correct - // when agent is above 1020m and other avatars are nearby - for (std::vector::iterator iter = LLCharacter::sInstances.begin(); - iter != LLCharacter::sInstances.end(); ++iter) - { - LLVOAvatar* pVOAvatar = (LLVOAvatar*) *iter; - - if (!pVOAvatar->isDead() && !pVOAvatar->mIsDummy && !pVOAvatar->isOrphaned()) - { - LLVector3d pos_global = pVOAvatar->getPositionGlobal(); - LLUUID uuid = pVOAvatar->getID(); - - if (!uuid.isNull() - && dist_vec_squared(pos_global, relative_to) <= radius_squared) - { - if(positions != NULL) - { - positions->push_back(pos_global); - } - if(avatar_ids !=NULL) - { - avatar_ids->push_back(uuid); - } - } - } - } - // region avatars added for situations where radius is greater than RenderFarClip - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) - { - LLViewerRegion* regionp = *iter; - const LLVector3d& origin_global = regionp->getOriginGlobal(); - S32 count = regionp->mMapAvatars.size(); - for (S32 i = 0; i < count; i++) - { - LLVector3d pos_global = unpackLocalToGlobalPosition(regionp->mMapAvatars.at(i), origin_global); - if(dist_vec_squared(pos_global, relative_to) <= radius_squared) - { - LLUUID uuid = regionp->mMapAvatarIDs.at(i); - // if this avatar doesn't already exist in the list, add it - if(uuid.notNull() && avatar_ids != NULL && std::find(avatar_ids->begin(), avatar_ids->end(), uuid) == avatar_ids->end()) - { - if (positions != NULL) - { - positions->push_back(pos_global); - } - avatar_ids->push_back(uuid); - } - } - } - } -} - -F32 LLWorld::getNearbyAvatarsAndMaxGPUTime(std::vector &valid_nearby_avs) -{ - static LLCachedControl render_far_clip(gSavedSettings, "RenderFarClip", 64); - F32 nearby_max_complexity = 0; - F32 radius = render_far_clip * render_far_clip; - std::vector::iterator char_iter = LLCharacter::sInstances.begin(); - while (char_iter != LLCharacter::sInstances.end()) - { - LLVOAvatar* avatar = dynamic_cast(*char_iter); - if (avatar && !avatar->isDead() && !avatar->isControlAvatar()) - { - if ((dist_vec_squared(avatar->getPositionGlobal(), gAgent.getPositionGlobal()) > radius) && - (dist_vec_squared(avatar->getPositionGlobal(), gAgentCamera.getCameraPositionGlobal()) > radius)) - { - char_iter++; - continue; - } - - if (!avatar->isTooSlow()) - { - gPipeline.profileAvatar(avatar); - } - nearby_max_complexity = llmax(nearby_max_complexity, avatar->getGPURenderTime()); - valid_nearby_avs.push_back(*char_iter); - } - char_iter++; - } - return nearby_max_complexity; -} - -bool LLWorld::isRegionListed(const LLViewerRegion* region) const -{ - region_list_t::const_iterator it = find(mRegionList.begin(), mRegionList.end(), region); - return it != mRegionList.end(); -} - -boost::signals2::connection LLWorld::setRegionRemovedCallback(const region_remove_signal_t::slot_type& cb) -{ - return mRegionRemovedSignal.connect(cb); -} - -LLHTTPRegistration - gHTTPRegistrationEstablishAgentCommunication( - "/message/EstablishAgentCommunication"); +/** + * @file llworld.cpp + * @brief Initial test structure to organize viewer regions + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llworld.h" +#include "llrender.h" + +#include "indra_constants.h" +#include "llstl.h" + +#include "llagent.h" +#include "llagentcamera.h" +#include "llviewercontrol.h" +#include "lldrawpool.h" +#include "llglheaders.h" +#include "llhttpnode.h" +#include "llregionhandle.h" +#include "llsky.h" +#include "llsurface.h" +#include "lltrans.h" +#include "llviewercamera.h" +#include "llviewertexture.h" +#include "llviewertexturelist.h" +#include "llviewernetwork.h" +#include "llviewerobjectlist.h" +#include "llviewerparceloverlay.h" +#include "llviewerregion.h" +#include "llviewerstats.h" +#include "llvlcomposition.h" +#include "llvoavatar.h" +#include "llvocache.h" +#include "llvowater.h" +#include "message.h" +#include "pipeline.h" +#include "llappviewer.h" // for do_disconnect() +#include "llscenemonitor.h" +#include +#include +#include +#include + + +// +// Globals +// +U32 gAgentPauseSerialNum = 0; + +// +// Constants +// +const S32 WORLD_PATCH_SIZE = 16; + +extern LLColor4U MAX_WATER_COLOR; + +const U32 LLWorld::mWidth = 256; + +// meters/point, therefore mWidth * mScale = meters per edge +const F32 LLWorld::mScale = 1.f; + +const F32 LLWorld::mWidthInMeters = mWidth * mScale; + +// +// Functions +// + +// allocate the stack +LLWorld::LLWorld() : + mLandFarClip(DEFAULT_FAR_PLANE), + mLastPacketsIn(0), + mLastPacketsOut(0), + mLastPacketsLost(0), + mSpaceTimeUSec(0) +{ + for (S32 i = 0; i < EDGE_WATER_OBJECTS_COUNT; i++) + { + mEdgeWaterObjects[i] = NULL; + } + + LLPointer raw = new LLImageRaw(1,1,4); + U8 *default_texture = raw->getData(); + *(default_texture++) = MAX_WATER_COLOR.mV[0]; + *(default_texture++) = MAX_WATER_COLOR.mV[1]; + *(default_texture++) = MAX_WATER_COLOR.mV[2]; + *(default_texture++) = MAX_WATER_COLOR.mV[3]; + + mDefaultWaterTexturep = LLViewerTextureManager::getLocalTexture(raw.get(), false); + gGL.getTexUnit(0)->bind(mDefaultWaterTexturep); + mDefaultWaterTexturep->setAddressMode(LLTexUnit::TAM_CLAMP); + + LLViewerRegion::sVOCacheCullingEnabled = gSavedSettings.getBOOL("RequestFullRegionCache") && gSavedSettings.getBOOL("ObjectCacheEnabled"); +} + + +void LLWorld::resetClass() +{ + mHoleWaterObjects.clear(); + gObjectList.destroy(); + gSky.cleanup(); // references an object + for(region_list_t::iterator region_it = mRegionList.begin(); region_it != mRegionList.end(); ) + { + LLViewerRegion* region_to_delete = *region_it++; + removeRegion(region_to_delete->getHost()); + } + + LLViewerPartSim::getInstance()->destroyClass(); + + mDefaultWaterTexturep = NULL ; + for (S32 i = 0; i < EDGE_WATER_OBJECTS_COUNT; i++) + { + mEdgeWaterObjects[i] = NULL; + } + + //make all visible drawbles invisible. + LLDrawable::incrementVisible(); + + LLSceneMonitor::deleteSingleton(); +} + + +LLViewerRegion* LLWorld::addRegion(const U64 ®ion_handle, const LLHost &host) +{ + LL_INFOS() << "Add region with handle: " << region_handle << " on host " << host << LL_ENDL; + LLViewerRegion *regionp = getRegionFromHandle(region_handle); + std::string seedUrl; + if (regionp) + { + LLHost old_host = regionp->getHost(); + // region already exists! + if (host == old_host && regionp->isAlive()) + { + // This is a duplicate for the same host and it's alive, don't bother. + LL_INFOS() << "Region already exists and is alive, using existing region" << LL_ENDL; + return regionp; + } + + if (host != old_host) + { + LL_WARNS() << "LLWorld::addRegion exists, but old host " << old_host + << " does not match new host " << host + << ", removing old region and creating new" << LL_ENDL; + } + if (!regionp->isAlive()) + { + LL_WARNS() << "LLWorld::addRegion exists, but isn't alive. Removing old region and creating new" << LL_ENDL; + } + + // Save capabilities seed URL + seedUrl = regionp->getCapability("Seed"); + + // Kill the old host, and then we can continue on and add the new host. We have to kill even if the host + // matches, because all the agent state for the new camera is completely different. + removeRegion(old_host); + } + else + { + LL_INFOS() << "Region does not exist, creating new one" << LL_ENDL; + } + + U32 iindex = 0; + U32 jindex = 0; + from_region_handle(region_handle, &iindex, &jindex); + S32 x = (S32)(iindex/mWidth); + S32 y = (S32)(jindex/mWidth); + LL_INFOS() << "Adding new region (" << x << ":" << y << ")" + << " on host: " << host << LL_ENDL; + + LLVector3d origin_global; + + origin_global = from_region_handle(region_handle); + + regionp = new LLViewerRegion(region_handle, + host, + mWidth, + WORLD_PATCH_SIZE, + getRegionWidthInMeters() ); + if (!regionp) + { + LL_ERRS() << "Unable to create new region!" << LL_ENDL; + } + + if ( !seedUrl.empty() ) + { + regionp->setCapability("Seed", seedUrl); + } + + mRegionList.push_back(regionp); + mActiveRegionList.push_back(regionp); + mCulledRegionList.push_back(regionp); + + + // Find all the adjacent regions, and attach them. + // Generate handles for all of the adjacent regions, and attach them in the correct way. + // connect the edges + F32 adj_x = 0.f; + F32 adj_y = 0.f; + F32 region_x = 0.f; + F32 region_y = 0.f; + U64 adj_handle = 0; + + F32 width = getRegionWidthInMeters(); + + LLViewerRegion *neighborp; + from_region_handle(region_handle, ®ion_x, ®ion_y); + + // Iterate through all directions, and connect neighbors if there. + S32 dir; + for (dir = 0; dir < 8; dir++) + { + adj_x = region_x + width * gDirAxes[dir][0]; + adj_y = region_y + width * gDirAxes[dir][1]; + to_region_handle(adj_x, adj_y, &adj_handle); + + neighborp = getRegionFromHandle(adj_handle); + if (neighborp) + { + //LL_INFOS() << "Connecting " << region_x << ":" << region_y << " -> " << adj_x << ":" << adj_y << LL_ENDL; + regionp->connectNeighbor(neighborp, dir); + } + } + + updateWaterObjects(); + + return regionp; +} + + +void LLWorld::removeRegion(const LLHost &host) +{ + F32 x, y; + + LLViewerRegion *regionp = getRegion(host); + if (!regionp) + { + LL_WARNS() << "Trying to remove region that doesn't exist!" << LL_ENDL; + return; + } + + if (regionp == gAgent.getRegion()) + { + for (region_list_t::iterator iter = mRegionList.begin(); + iter != mRegionList.end(); ++iter) + { + LLViewerRegion* reg = *iter; + LL_WARNS() << "RegionDump: " << reg->getName() + << " " << reg->getHost() + << " " << reg->getOriginGlobal() + << LL_ENDL; + } + + LL_WARNS() << "Agent position global " << gAgent.getPositionGlobal() + << " agent " << gAgent.getPositionAgent() + << LL_ENDL; + + LL_WARNS() << "Regions visited " << gAgent.getRegionsVisited() << LL_ENDL; + + LL_WARNS() << "gFrameTimeSeconds " << gFrameTimeSeconds << LL_ENDL; + + LL_WARNS() << "Disabling region " << regionp->getName() << " that agent is in!" << LL_ENDL; + LLAppViewer::instance()->forceDisconnect(LLTrans::getString("YouHaveBeenDisconnected")); + + regionp->saveObjectCache() ; //force to save objects here in case that the object cache is about to be destroyed. + return; + } + + from_region_handle(regionp->getHandle(), &x, &y); + LL_INFOS() << "Removing region " << x << ":" << y << LL_ENDL; + + mRegionList.remove(regionp); + mActiveRegionList.remove(regionp); + mCulledRegionList.remove(regionp); + mVisibleRegionList.remove(regionp); + + mRegionRemovedSignal(regionp); + + updateWaterObjects(); + + //double check all objects of this region are removed. + gObjectList.clearAllMapObjectsInRegion(regionp) ; + //llassert_always(!gObjectList.hasMapObjectInRegion(regionp)) ; + + delete regionp; // Delete last to prevent use after free +} + + +LLViewerRegion* LLWorld::getRegion(const LLHost &host) +{ + for (region_list_t::iterator iter = mRegionList.begin(); + iter != mRegionList.end(); ++iter) + { + LLViewerRegion* regionp = *iter; + if (regionp->getHost() == host) + { + return regionp; + } + } + return NULL; +} + +LLViewerRegion* LLWorld::getRegionFromPosAgent(const LLVector3 &pos) +{ + return getRegionFromPosGlobal(gAgent.getPosGlobalFromAgent(pos)); +} + +LLViewerRegion* LLWorld::getRegionFromPosGlobal(const LLVector3d &pos) +{ + for (region_list_t::iterator iter = mRegionList.begin(); + iter != mRegionList.end(); ++iter) + { + LLViewerRegion* regionp = *iter; + if (regionp->pointInRegionGlobal(pos)) + { + return regionp; + } + } + return NULL; +} + + +LLVector3d LLWorld::clipToVisibleRegions(const LLVector3d &start_pos, const LLVector3d &end_pos) +{ + if (positionRegionValidGlobal(end_pos)) + { + return end_pos; + } + + LLViewerRegion* regionp = getRegionFromPosGlobal(start_pos); + if (!regionp) + { + return start_pos; + } + + LLVector3d delta_pos = end_pos - start_pos; + LLVector3d delta_pos_abs; + delta_pos_abs.setVec(delta_pos); + delta_pos_abs.abs(); + + LLVector3 region_coord = regionp->getPosRegionFromGlobal(end_pos); + F64 clip_factor = 1.0; + F32 region_width = regionp->getWidth(); + if (region_coord.mV[VX] < 0.f) + { + if (region_coord.mV[VY] < region_coord.mV[VX]) + { + // clip along y - + clip_factor = -(region_coord.mV[VY] / delta_pos_abs.mdV[VY]); + } + else + { + // clip along x - + clip_factor = -(region_coord.mV[VX] / delta_pos_abs.mdV[VX]); + } + } + else if (region_coord.mV[VX] > region_width) + { + if (region_coord.mV[VY] > region_coord.mV[VX]) + { + // clip along y + + clip_factor = (region_coord.mV[VY] - region_width) / delta_pos_abs.mdV[VY]; + } + else + { + //clip along x + + clip_factor = (region_coord.mV[VX] - region_width) / delta_pos_abs.mdV[VX]; + } + } + else if (region_coord.mV[VY] < 0.f) + { + // clip along y - + clip_factor = -(region_coord.mV[VY] / delta_pos_abs.mdV[VY]); + } + else if (region_coord.mV[VY] > region_width) + { + // clip along y + + clip_factor = (region_coord.mV[VY] - region_width) / delta_pos_abs.mdV[VY]; + } + + // clamp to within region dimensions + LLVector3d final_region_pos = LLVector3d(region_coord) - (delta_pos * clip_factor); + final_region_pos.mdV[VX] = llclamp(final_region_pos.mdV[VX], 0.0, + (F64)(region_width - F_ALMOST_ZERO)); + final_region_pos.mdV[VY] = llclamp(final_region_pos.mdV[VY], 0.0, + (F64)(region_width - F_ALMOST_ZERO)); + final_region_pos.mdV[VZ] = llclamp(final_region_pos.mdV[VZ], 0.0, + (F64)(LLWorld::getInstance()->getRegionMaxHeight() - F_ALMOST_ZERO)); + return regionp->getPosGlobalFromRegion(LLVector3(final_region_pos)); +} + +LLViewerRegion* LLWorld::getRegionFromHandle(const U64 &handle) +{ + for (region_list_t::iterator iter = mRegionList.begin(); + iter != mRegionList.end(); ++iter) + { + LLViewerRegion* regionp = *iter; + if (regionp->getHandle() == handle) + { + return regionp; + } + } + return NULL; +} + +LLViewerRegion* LLWorld::getRegionFromID(const LLUUID& region_id) +{ + for (region_list_t::iterator iter = mRegionList.begin(); + iter != mRegionList.end(); ++iter) + { + LLViewerRegion* regionp = *iter; + if (regionp->getRegionID() == region_id) + { + return regionp; + } + } + return NULL; +} + +void LLWorld::updateAgentOffset(const LLVector3d &offset_global) +{ +#if 0 + for (region_list_t::iterator iter = mRegionList.begin(); + iter != mRegionList.end(); ++iter) + { + LLViewerRegion* regionp = *iter; + regionp->setAgentOffset(offset_global); + } +#endif +} + + +bool LLWorld::positionRegionValidGlobal(const LLVector3d &pos_global) +{ + for (region_list_t::iterator iter = mRegionList.begin(); + iter != mRegionList.end(); ++iter) + { + LLViewerRegion* regionp = *iter; + if (regionp->pointInRegionGlobal(pos_global)) + { + return true; + } + } + return false; +} + + +// Allow objects to go up to their radius underground. +F32 LLWorld::getMinAllowedZ(LLViewerObject* object, const LLVector3d &global_pos) +{ + F32 land_height = resolveLandHeightGlobal(global_pos); + F32 radius = 0.5f * object->getScale().length(); + return land_height - radius; +} + + + +LLViewerRegion* LLWorld::resolveRegionGlobal(LLVector3 &pos_region, const LLVector3d &pos_global) +{ + LLViewerRegion *regionp = getRegionFromPosGlobal(pos_global); + + if (regionp) + { + pos_region = regionp->getPosRegionFromGlobal(pos_global); + return regionp; + } + + return NULL; +} + + +LLViewerRegion* LLWorld::resolveRegionAgent(LLVector3 &pos_region, const LLVector3 &pos_agent) +{ + LLVector3d pos_global = gAgent.getPosGlobalFromAgent(pos_agent); + LLViewerRegion *regionp = getRegionFromPosGlobal(pos_global); + + if (regionp) + { + pos_region = regionp->getPosRegionFromGlobal(pos_global); + return regionp; + } + + return NULL; +} + + +F32 LLWorld::resolveLandHeightAgent(const LLVector3 &pos_agent) +{ + LLVector3d pos_global = gAgent.getPosGlobalFromAgent(pos_agent); + return resolveLandHeightGlobal(pos_global); +} + + +F32 LLWorld::resolveLandHeightGlobal(const LLVector3d &pos_global) +{ + LLViewerRegion *regionp = getRegionFromPosGlobal(pos_global); + if (regionp) + { + return regionp->getLand().resolveHeightGlobal(pos_global); + } + return 0.0f; +} + + +// Takes a line defined by "point_a" and "point_b" and determines the closest (to point_a) +// point where the the line intersects an object or the land surface. Stores the results +// in "intersection" and "intersection_normal" and returns a scalar value that represents +// the normalized distance along the line from "point_a" to "intersection". +// +// Currently assumes point_a and point_b only differ in z-direction, +// but it may eventually become more general. +F32 LLWorld::resolveStepHeightGlobal(const LLVOAvatar* avatarp, const LLVector3d &point_a, const LLVector3d &point_b, + LLVector3d &intersection, LLVector3 &intersection_normal, + LLViewerObject **viewerObjectPtr) +{ + // initialize return value to null + if (viewerObjectPtr) + { + *viewerObjectPtr = NULL; + } + + LLViewerRegion *regionp = getRegionFromPosGlobal(point_a); + if (!regionp) + { + // We're outside the world + intersection = 0.5f * (point_a + point_b); + intersection_normal.setVec(0.0f, 0.0f, 1.0f); + return 0.5f; + } + + // calculate the length of the segment + F32 segment_length = (F32)((point_a - point_b).length()); + if (0.0f == segment_length) + { + intersection = point_a; + intersection_normal.setVec(0.0f, 0.0f, 1.0f); + return segment_length; + } + + // get land height + // Note: we assume that the line is parallel to z-axis here + LLVector3d land_intersection = point_a; + F32 normalized_land_distance; + + land_intersection.mdV[VZ] = regionp->getLand().resolveHeightGlobal(point_a); + normalized_land_distance = (F32)(point_a.mdV[VZ] - land_intersection.mdV[VZ]) / segment_length; + intersection = land_intersection; + intersection_normal = resolveLandNormalGlobal(land_intersection); + + if (avatarp && !avatarp->mFootPlane.isExactlyClear()) + { + LLVector3 foot_plane_normal(avatarp->mFootPlane.mV); + LLVector3 start_pt = avatarp->getRegion()->getPosRegionFromGlobal(point_a); + // added 0.05 meters to compensate for error in foot plane reported by Havok + F32 norm_dist_from_plane = ((start_pt * foot_plane_normal) - avatarp->mFootPlane.mV[VW]) + 0.05f; + norm_dist_from_plane = llclamp(norm_dist_from_plane / segment_length, 0.f, 1.f); + if (norm_dist_from_plane < normalized_land_distance) + { + // collided with object before land + normalized_land_distance = norm_dist_from_plane; + intersection = point_a; + intersection.mdV[VZ] -= norm_dist_from_plane * segment_length; + intersection_normal = foot_plane_normal; + } + else + { + intersection = land_intersection; + intersection_normal = resolveLandNormalGlobal(land_intersection); + } + } + + return normalized_land_distance; +} + + +LLSurfacePatch * LLWorld::resolveLandPatchGlobal(const LLVector3d &pos_global) +{ + // returns a pointer to the patch at this location + LLViewerRegion *regionp = getRegionFromPosGlobal(pos_global); + if (!regionp) + { + return NULL; + } + + return regionp->getLand().resolvePatchGlobal(pos_global); +} + + +LLVector3 LLWorld::resolveLandNormalGlobal(const LLVector3d &pos_global) +{ + LLViewerRegion *regionp = getRegionFromPosGlobal(pos_global); + if (!regionp) + { + return LLVector3::z_axis; + } + + return regionp->getLand().resolveNormalGlobal(pos_global); +} + + +void LLWorld::updateVisibilities() +{ + F32 cur_far_clip = LLViewerCamera::getInstance()->getFar(); + + // Go through the culled list and check for visible regions (region is visible if land is visible) + for (region_list_t::iterator iter = mCulledRegionList.begin(); + iter != mCulledRegionList.end(); ) + { + region_list_t::iterator curiter = iter++; + LLViewerRegion* regionp = *curiter; + + LLSpatialPartition* part = regionp->getSpatialPartition(LLViewerRegion::PARTITION_TERRAIN); + if (part) + { + LLSpatialGroup* group = (LLSpatialGroup*) part->mOctree->getListener(0); + const LLVector4a* bounds = group->getBounds(); + if (LLViewerCamera::getInstance()->AABBInFrustum(bounds[0], bounds[1])) + { + mCulledRegionList.erase(curiter); + mVisibleRegionList.push_back(regionp); + } + } + } + + // Update all of the visible regions + for (region_list_t::iterator iter = mVisibleRegionList.begin(); + iter != mVisibleRegionList.end(); ) + { + region_list_t::iterator curiter = iter++; + LLViewerRegion* regionp = *curiter; + if (!regionp->getLand().hasZData()) + { + continue; + } + + LLSpatialPartition* part = regionp->getSpatialPartition(LLViewerRegion::PARTITION_TERRAIN); + if (part) + { + LLSpatialGroup* group = (LLSpatialGroup*) part->mOctree->getListener(0); + const LLVector4a* bounds = group->getBounds(); + if (LLViewerCamera::getInstance()->AABBInFrustum(bounds[0], bounds[1])) + { + regionp->calculateCameraDistance(); + regionp->getLand().updatePatchVisibilities(gAgent); + } + else + { + mVisibleRegionList.erase(curiter); + mCulledRegionList.push_back(regionp); + } + } + } + + // Sort visible regions + mVisibleRegionList.sort(LLViewerRegion::CompareDistance()); + + LLViewerCamera::getInstance()->setFar(cur_far_clip); +} + +static LLTrace::SampleStatHandle<> sNumActiveCachedObjects("numactivecachedobjects", "Number of objects loaded from cache"); + +void LLWorld::updateRegions(F32 max_update_time) +{ + LL_PROFILE_ZONE_SCOPED; + LLTimer update_timer; + mNumOfActiveCachedObjects = 0; + + if(LLViewerCamera::getInstance()->isChanged()) + { + LLViewerRegion::sLastCameraUpdated = LLViewerOctreeEntryData::getCurrentFrame() + 1; + } + LLViewerRegion::calcNewObjectCreationThrottle(); + if(LLViewerRegion::isNewObjectCreationThrottleDisabled()) + { + max_update_time = llmax(max_update_time, 1.0f); //seconds, loosen the time throttle. + } + + F32 max_time = llmin((F32)(max_update_time - update_timer.getElapsedTimeF32()), max_update_time * 0.25f); + //update the self avatar region + LLViewerRegion* self_regionp = gAgent.getRegion(); + if(self_regionp) + { + self_regionp->idleUpdate(max_time); + } + + //sort regions by its mLastUpdate + //smaller mLastUpdate first to make sure every region has chance to get updated. + LLViewerRegion::region_priority_list_t region_list; + for (region_list_t::iterator iter = mRegionList.begin(); + iter != mRegionList.end(); ++iter) + { + LLViewerRegion* regionp = *iter; + if(regionp != self_regionp) + { + region_list.insert(regionp); + } + mNumOfActiveCachedObjects += regionp->getNumOfActiveCachedObjects(); + } + + // Perform idle time updates for the regions (and associated surfaces) + for (LLViewerRegion::region_priority_list_t::iterator iter = region_list.begin(); + iter != region_list.end(); ++iter) + { + if(max_time > 0.f) + { + max_time = llmin((F32)(max_update_time - update_timer.getElapsedTimeF32()), max_update_time * 0.25f); + } + + if(max_time > 0.f) + { + (*iter)->idleUpdate(max_time); + } + else + { + //perform some necessary but very light updates. + (*iter)->lightIdleUpdate(); + } + } + + if(max_time > 0.f) + { + max_time = llmin((F32)(max_update_time - update_timer.getElapsedTimeF32()), max_update_time * 0.25f); + } + if(max_time > 0.f) + { + LLViewerRegion::idleCleanup(max_time); + } + + sample(sNumActiveCachedObjects, mNumOfActiveCachedObjects); +} + +void LLWorld::clearAllVisibleObjects() +{ + for (region_list_t::iterator iter = mRegionList.begin(); + iter != mRegionList.end(); ++iter) + { + //clear all cached visible objects. + (*iter)->clearCachedVisibleObjects(); + } + clearHoleWaterObjects(); + clearEdgeWaterObjects(); +} + +void LLWorld::updateParticles() +{ + LLViewerPartSim::getInstance()->updateSimulation(); +} + +void LLWorld::renderPropertyLines() +{ + for (region_list_t::iterator iter = mVisibleRegionList.begin(); + iter != mVisibleRegionList.end(); ++iter) + { + LLViewerRegion* regionp = *iter; + regionp->renderPropertyLines(); + } +} + + +void LLWorld::updateNetStats() +{ + F64Bits bits; + + for (region_list_t::iterator iter = mActiveRegionList.begin(); + iter != mActiveRegionList.end(); ++iter) + { + LLViewerRegion* regionp = *iter; + regionp->updateNetStats(); + bits += regionp->mBitsReceived; + regionp->mBitsReceived = (F32Bits)0.f; + regionp->mPacketsReceived = 0.f; + } + + S32 packets_in = gMessageSystem->mPacketsIn - mLastPacketsIn; + S32 packets_out = gMessageSystem->mPacketsOut - mLastPacketsOut; + S32 packets_lost = gMessageSystem->mDroppedPackets - mLastPacketsLost; + + F64Bits actual_in_bits(gMessageSystem->mPacketRing.getAndResetActualInBits()); + F64Bits actual_out_bits(gMessageSystem->mPacketRing.getAndResetActualOutBits()); + + add(LLStatViewer::MESSAGE_SYSTEM_DATA_IN, actual_in_bits); + add(LLStatViewer::MESSAGE_SYSTEM_DATA_OUT, actual_out_bits); + add(LLStatViewer::ACTIVE_MESSAGE_DATA_RECEIVED, bits); + add(LLStatViewer::PACKETS_IN, packets_in); + add(LLStatViewer::PACKETS_OUT, packets_out); + add(LLStatViewer::PACKETS_LOST, packets_lost); + + F32 total_packets_in = LLViewerStats::instance().getRecording().getSum(LLStatViewer::PACKETS_IN); + if (total_packets_in > 0) + { + F32 total_packets_lost = LLViewerStats::instance().getRecording().getSum(LLStatViewer::PACKETS_LOST); + sample(LLStatViewer::PACKETS_LOST_PERCENT, LLUnits::Ratio::fromValue((F32)total_packets_lost/(F32)total_packets_in)); + } + + mLastPacketsIn = gMessageSystem->mPacketsIn; + mLastPacketsOut = gMessageSystem->mPacketsOut; + mLastPacketsLost = gMessageSystem->mDroppedPackets; +} + + +void LLWorld::printPacketsLost() +{ + LL_INFOS() << "Simulators:" << LL_ENDL; + LL_INFOS() << "----------" << LL_ENDL; + + LLCircuitData *cdp = NULL; + for (region_list_t::iterator iter = mActiveRegionList.begin(); + iter != mActiveRegionList.end(); ++iter) + { + LLViewerRegion* regionp = *iter; + cdp = gMessageSystem->mCircuitInfo.findCircuit(regionp->getHost()); + if (cdp) + { + LLVector3d range = regionp->getCenterGlobal() - gAgent.getPositionGlobal(); + + LL_INFOS() << regionp->getHost() << ", range: " << range.length() + << " packets lost: " << cdp->getPacketsLost() << LL_ENDL; + } + } +} + +void LLWorld::processCoarseUpdate(LLMessageSystem* msg, void** user_data) +{ + LLViewerRegion* region = LLWorld::getInstance()->getRegion(msg->getSender()); + if( region ) + { + region->updateCoarseLocations(msg); + } +} + +F32 LLWorld::getLandFarClip() const +{ + return mLandFarClip; +} + +void LLWorld::setLandFarClip(const F32 far_clip) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_ENVIRONMENT; + static S32 const rwidth = (S32)REGION_WIDTH_U32; + S32 const n1 = (llceil(mLandFarClip) - 1) / rwidth; + S32 const n2 = (llceil(far_clip) - 1) / rwidth; + bool need_water_objects_update = n1 != n2; + + mLandFarClip = far_clip; + + if (need_water_objects_update) + { + updateWaterObjects(); + } +} + +// Some region that we're connected to, but not the one we're in, gave us +// a (possibly) new water height. Update it in our local copy. +void LLWorld::waterHeightRegionInfo(std::string const& sim_name, F32 water_height) +{ + for (region_list_t::iterator iter = mRegionList.begin(); iter != mRegionList.end(); ++iter) + { + if ((*iter)->getName() == sim_name) + { + (*iter)->setWaterHeight(water_height); + break; + } + } +} + +void LLWorld::clearHoleWaterObjects() +{ + for (std::list >::iterator iter = mHoleWaterObjects.begin(); + iter != mHoleWaterObjects.end(); ++iter) + { + LLVOWater* waterp = (*iter).get(); + gObjectList.killObject(waterp); + } + mHoleWaterObjects.clear(); +} + +void LLWorld::clearEdgeWaterObjects() +{ + for (S32 i = 0; i < EDGE_WATER_OBJECTS_COUNT; i++) + { + gObjectList.killObject(mEdgeWaterObjects[i]); + mEdgeWaterObjects[i] = NULL; + } +} + +void LLWorld::updateWaterObjects() +{ + if (!gAgent.getRegion()) + { + return; + } + if (mRegionList.empty()) + { + LL_WARNS() << "No regions!" << LL_ENDL; + return; + } + + // First, determine the min and max "box" of water objects + S32 min_x = 0; + S32 min_y = 0; + S32 max_x = 0; + S32 max_y = 0; + U32 region_x, region_y; + + S32 rwidth = 256; + + // We only want to fill in water for stuff that's near us, say, within 256 or 512m + S32 range = LLViewerCamera::getInstance()->getFar() > 256.f ? 512 : 256; + + LLViewerRegion* regionp = gAgent.getRegion(); + from_region_handle(regionp->getHandle(), ®ion_x, ®ion_y); + + min_x = (S32)region_x - range; + min_y = (S32)region_y - range; + max_x = (S32)region_x + range; + max_y = (S32)region_y + range; + + for (region_list_t::iterator iter = mRegionList.begin(); + iter != mRegionList.end(); ++iter) + { + LLViewerRegion* regionp = *iter; + LLVOWater* waterp = regionp->getLand().getWaterObj(); + if (waterp) + { + gObjectList.updateActive(waterp); + } + } + + clearHoleWaterObjects(); + + // Use the water height of the region we're on for areas where there is no region + F32 water_height = gAgent.getRegion()->getWaterHeight(); + + // Now, get a list of the holes + S32 x, y; + for (x = min_x; x <= max_x; x += rwidth) + { + for (y = min_y; y <= max_y; y += rwidth) + { + U64 region_handle = to_region_handle(x, y); + if (!getRegionFromHandle(region_handle)) + { // No region at that area, so make water + LLVOWater* waterp = (LLVOWater *)gObjectList.createObjectViewer(LLViewerObject::LL_VO_WATER, gAgent.getRegion()); + waterp->setUseTexture(false); + waterp->setPositionGlobal(LLVector3d(x + rwidth/2, + y + rwidth/2, + 256.f + water_height)); + waterp->setScale(LLVector3((F32)rwidth, (F32)rwidth, 512.f)); + gPipeline.createObject(waterp); + mHoleWaterObjects.push_back(waterp); + } + } + } + + // Update edge water objects + S32 wx, wy; + S32 center_x, center_y; + wx = (max_x - min_x) + rwidth; + wy = (max_y - min_y) + rwidth; + center_x = min_x + (wx >> 1); + center_y = min_y + (wy >> 1); + + S32 add_boundary[4] = { + (S32)(512 - (max_x - region_x)), + (S32)(512 - (max_y - region_y)), + (S32)(512 - (region_x - min_x)), + (S32)(512 - (region_y - min_y)) }; + + S32 dir; + for (dir = 0; dir < EDGE_WATER_OBJECTS_COUNT; dir++) + { + S32 dim[2] = { 0 }; + switch (gDirAxes[dir][0]) + { + case -1: dim[0] = add_boundary[2]; break; + case 0: dim[0] = wx; break; + default: dim[0] = add_boundary[0]; break; + } + switch (gDirAxes[dir][1]) + { + case -1: dim[1] = add_boundary[3]; break; + case 0: dim[1] = wy; break; + default: dim[1] = add_boundary[1]; break; + } + + // Resize and reshape the water objects + const S32 water_center_x = center_x + ll_round((wx + dim[0]) * 0.5f * gDirAxes[dir][0]); + const S32 water_center_y = center_y + ll_round((wy + dim[1]) * 0.5f * gDirAxes[dir][1]); + + LLVOWater* waterp = mEdgeWaterObjects[dir]; + if (!waterp || waterp->isDead()) + { + // The edge water objects can be dead because they're attached to the region that the + // agent was in when they were originally created. + mEdgeWaterObjects[dir] = (LLVOWater *)gObjectList.createObjectViewer(LLViewerObject::LL_VO_VOID_WATER, + gAgent.getRegion()); + waterp = mEdgeWaterObjects[dir]; + waterp->setUseTexture(false); + waterp->setIsEdgePatch(true); + gPipeline.createObject(waterp); + } + + waterp->setRegion(gAgent.getRegion()); + LLVector3d water_pos(water_center_x, water_center_y, 256.f + water_height) ; + LLVector3 water_scale((F32) dim[0], (F32) dim[1], 512.f); + + //stretch out to horizon + water_scale.mV[0] += fabsf(2048.f * gDirAxes[dir][0]); + water_scale.mV[1] += fabsf(2048.f * gDirAxes[dir][1]); + + water_pos.mdV[0] += 1024.f * gDirAxes[dir][0]; + water_pos.mdV[1] += 1024.f * gDirAxes[dir][1]; + + waterp->setPositionGlobal(water_pos); + waterp->setScale(water_scale); + + gObjectList.updateActive(waterp); + } +} + + +void LLWorld::shiftRegions(const LLVector3& offset) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + for (region_list_t::const_iterator i = getRegionList().begin(); i != getRegionList().end(); ++i) + { + LLViewerRegion* region = *i; + region->updateRenderMatrix(); + } + + LLViewerPartSim::getInstance()->shift(offset); +} + +LLViewerTexture* LLWorld::getDefaultWaterTexture() +{ + return mDefaultWaterTexturep; +} + +void LLWorld::setSpaceTimeUSec(const U64MicrosecondsImplicit space_time_usec) +{ + mSpaceTimeUSec = space_time_usec; +} + +U64MicrosecondsImplicit LLWorld::getSpaceTimeUSec() const +{ + return mSpaceTimeUSec; +} + +void LLWorld::requestCacheMisses() +{ + for (region_list_t::iterator iter = mRegionList.begin(); + iter != mRegionList.end(); ++iter) + { + LLViewerRegion* regionp = *iter; + regionp->requestCacheMisses(); + } +} + +void LLWorld::getInfo(LLSD& info) +{ + LLSD region_info; + for (region_list_t::iterator iter = mRegionList.begin(); + iter != mRegionList.end(); ++iter) + { + LLViewerRegion* regionp = *iter; + regionp->getInfo(region_info); + info["World"].append(region_info); + } +} + +void LLWorld::disconnectRegions() +{ + LLMessageSystem* msg = gMessageSystem; + for (region_list_t::iterator iter = mRegionList.begin(); + iter != mRegionList.end(); ++iter) + { + LLViewerRegion* regionp = *iter; + if (regionp == gAgent.getRegion()) + { + // Skip the main agent + continue; + } + + LL_INFOS() << "Sending AgentQuitCopy to: " << regionp->getHost() << LL_ENDL; + msg->newMessageFast(_PREHASH_AgentQuitCopy); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_FuseBlock); + msg->addU32Fast(_PREHASH_ViewerCircuitCode, gMessageSystem->mOurCircuitCode); + msg->sendMessage(regionp->getHost()); + } +} + +void process_enable_simulator(LLMessageSystem *msg, void **user_data) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; + // enable the appropriate circuit for this simulator and + // add its values into the gSimulator structure + U64 handle; + U32 ip_u32; + U16 port; + + msg->getU64Fast(_PREHASH_SimulatorInfo, _PREHASH_Handle, handle); + msg->getIPAddrFast(_PREHASH_SimulatorInfo, _PREHASH_IP, ip_u32); + msg->getIPPortFast(_PREHASH_SimulatorInfo, _PREHASH_Port, port); + + // which simulator should we modify? + LLHost sim(ip_u32, port); + + // Viewer trusts the simulator. + msg->enableCircuit(sim, true); + LLWorld::getInstance()->addRegion(handle, sim); + + // give the simulator a message it can use to get ip and port + LL_INFOS() << "simulator_enable() Enabling " << sim << " with code " << msg->getOurCircuitCode() << LL_ENDL; + msg->newMessageFast(_PREHASH_UseCircuitCode); + msg->nextBlockFast(_PREHASH_CircuitCode); + msg->addU32Fast(_PREHASH_Code, msg->getOurCircuitCode()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addUUIDFast(_PREHASH_ID, gAgent.getID()); + msg->sendReliable(sim); +} + +class LLEstablishAgentCommunication : public LLHTTPNode +{ + LOG_CLASS(LLEstablishAgentCommunication); +public: + virtual void describe(Description& desc) const + { + desc.shortInfo("seed capability info for a region"); + desc.postAPI(); + desc.input( + "{ seed-capability: ..., sim-ip: ..., sim-port }"); + desc.source(__FILE__, __LINE__); + } + + virtual void post(ResponsePtr response, const LLSD& context, const LLSD& input) const + { + if (LLApp::isExiting()) + { + return; + } + + if (gDisconnected) + { + return; + } + + if (!LLWorld::instanceExists()) + { + return; + } + + if (!input["body"].has("agent-id") || + !input["body"].has("sim-ip-and-port") || + !input["body"].has("seed-capability")) + { + LL_WARNS() << "invalid parameters" << LL_ENDL; + return; + } + + LLHost sim(input["body"]["sim-ip-and-port"].asString()); + if (sim.isInvalid()) + { + LL_WARNS() << "Got EstablishAgentCommunication with invalid host" << LL_ENDL; + return; + } + + LLViewerRegion* regionp = LLWorld::getInstance()->getRegion(sim); + if (!regionp) + { + LL_WARNS() << "Got EstablishAgentCommunication for unknown region " + << sim << LL_ENDL; + return; + } + LL_DEBUGS("CrossingCaps") << "Calling setSeedCapability from LLEstablishAgentCommunication::post. Seed cap == " + << input["body"]["seed-capability"] << " for region " << regionp->getRegionID() << LL_ENDL; + regionp->setSeedCapability(input["body"]["seed-capability"]); + } +}; + +// disable the circuit to this simulator +// Called in response to "DisableSimulator" message. +void process_disable_simulator(LLMessageSystem *mesgsys, void **user_data) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK; + + LLHost host = mesgsys->getSender(); + + //LL_INFOS() << "Disabling simulator with message from " << host << LL_ENDL; + LLWorld::getInstance()->removeRegion(host); + + mesgsys->disableCircuit(host); +} + + +void process_region_handshake(LLMessageSystem* msg, void** user_data) +{ + LLHost host = msg->getSender(); + LLViewerRegion* regionp = LLWorld::getInstance()->getRegion(host); + if (!regionp) + { + LL_WARNS() << "Got region handshake for unknown region " + << host << LL_ENDL; + return; + } + + regionp->unpackRegionHandshake(); +} + + +void send_agent_pause() +{ + // *NOTE:Mani Pausing the mainloop timeout. Otherwise a long modal event may cause + // the thread monitor to timeout. + LLAppViewer::instance()->pauseMainloopTimeout(); + + // Note: used to check for LLWorld initialization before it became a singleton. + // Rather than just remove this check I'm changing it to assure that the message + // system has been initialized. -MG + if (!gMessageSystem) + { + return; + } + + gMessageSystem->newMessageFast(_PREHASH_AgentPause); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgentID); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgentSessionID); + + gAgentPauseSerialNum++; + gMessageSystem->addU32Fast(_PREHASH_SerialNum, gAgentPauseSerialNum); + + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* regionp = *iter; + gMessageSystem->sendReliable(regionp->getHost()); + } + + gObjectList.mWasPaused = true; + LLViewerStats::instance().getRecording().stop(); +} + + +void send_agent_resume() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_NETWORK + // Note: used to check for LLWorld initialization before it became a singleton. + // Rather than just remove this check I'm changing it to assure that the message + // system has been initialized. -MG + if (!gMessageSystem) + { + return; + } + + gMessageSystem->newMessageFast(_PREHASH_AgentResume); + gMessageSystem->nextBlockFast(_PREHASH_AgentData); + gMessageSystem->addUUIDFast(_PREHASH_AgentID, gAgentID); + gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgentSessionID); + + gAgentPauseSerialNum++; + gMessageSystem->addU32Fast(_PREHASH_SerialNum, gAgentPauseSerialNum); + + + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* regionp = *iter; + gMessageSystem->sendReliable(regionp->getHost()); + } + + // Resume data collection to ignore invalid rates + LLViewerStats::instance().getRecording().resume(); + + LLAppViewer::instance()->resumeMainloopTimeout(); +} + +static LLVector3d unpackLocalToGlobalPosition(U32 compact_local, const LLVector3d& region_origin) +{ + LLVector3d pos_local; + + pos_local.mdV[VZ] = (compact_local & 0xFFU) * 4; + pos_local.mdV[VY] = (compact_local >> 8) & 0xFFU; + pos_local.mdV[VX] = (compact_local >> 16) & 0xFFU; + + return region_origin + pos_local; +} + +void LLWorld::getAvatars(uuid_vec_t* avatar_ids, std::vector* positions, const LLVector3d& relative_to, F32 radius) const +{ + F32 radius_squared = radius * radius; + + if(avatar_ids != NULL) + { + avatar_ids->clear(); + } + if(positions != NULL) + { + positions->clear(); + } + // get the list of avatars from the character list first, so distances are correct + // when agent is above 1020m and other avatars are nearby + for (std::vector::iterator iter = LLCharacter::sInstances.begin(); + iter != LLCharacter::sInstances.end(); ++iter) + { + LLVOAvatar* pVOAvatar = (LLVOAvatar*) *iter; + + if (!pVOAvatar->isDead() && !pVOAvatar->mIsDummy && !pVOAvatar->isOrphaned()) + { + LLVector3d pos_global = pVOAvatar->getPositionGlobal(); + LLUUID uuid = pVOAvatar->getID(); + + if (!uuid.isNull() + && dist_vec_squared(pos_global, relative_to) <= radius_squared) + { + if(positions != NULL) + { + positions->push_back(pos_global); + } + if(avatar_ids !=NULL) + { + avatar_ids->push_back(uuid); + } + } + } + } + // region avatars added for situations where radius is greater than RenderFarClip + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* regionp = *iter; + const LLVector3d& origin_global = regionp->getOriginGlobal(); + S32 count = regionp->mMapAvatars.size(); + for (S32 i = 0; i < count; i++) + { + LLVector3d pos_global = unpackLocalToGlobalPosition(regionp->mMapAvatars.at(i), origin_global); + if(dist_vec_squared(pos_global, relative_to) <= radius_squared) + { + LLUUID uuid = regionp->mMapAvatarIDs.at(i); + // if this avatar doesn't already exist in the list, add it + if(uuid.notNull() && avatar_ids != NULL && std::find(avatar_ids->begin(), avatar_ids->end(), uuid) == avatar_ids->end()) + { + if (positions != NULL) + { + positions->push_back(pos_global); + } + avatar_ids->push_back(uuid); + } + } + } + } +} + +F32 LLWorld::getNearbyAvatarsAndMaxGPUTime(std::vector &valid_nearby_avs) +{ + static LLCachedControl render_far_clip(gSavedSettings, "RenderFarClip", 64); + F32 nearby_max_complexity = 0; + F32 radius = render_far_clip * render_far_clip; + std::vector::iterator char_iter = LLCharacter::sInstances.begin(); + while (char_iter != LLCharacter::sInstances.end()) + { + LLVOAvatar* avatar = dynamic_cast(*char_iter); + if (avatar && !avatar->isDead() && !avatar->isControlAvatar()) + { + if ((dist_vec_squared(avatar->getPositionGlobal(), gAgent.getPositionGlobal()) > radius) && + (dist_vec_squared(avatar->getPositionGlobal(), gAgentCamera.getCameraPositionGlobal()) > radius)) + { + char_iter++; + continue; + } + + if (!avatar->isTooSlow()) + { + gPipeline.profileAvatar(avatar); + } + nearby_max_complexity = llmax(nearby_max_complexity, avatar->getGPURenderTime()); + valid_nearby_avs.push_back(*char_iter); + } + char_iter++; + } + return nearby_max_complexity; +} + +bool LLWorld::isRegionListed(const LLViewerRegion* region) const +{ + region_list_t::const_iterator it = find(mRegionList.begin(), mRegionList.end(), region); + return it != mRegionList.end(); +} + +boost::signals2::connection LLWorld::setRegionRemovedCallback(const region_remove_signal_t::slot_type& cb) +{ + return mRegionRemovedSignal.connect(cb); +} + +LLHTTPRegistration + gHTTPRegistrationEstablishAgentCommunication( + "/message/EstablishAgentCommunication"); diff --git a/indra/newview/llworld.h b/indra/newview/llworld.h index bb21ce126b..01f666d19a 100644 --- a/indra/newview/llworld.h +++ b/indra/newview/llworld.h @@ -1,225 +1,225 @@ -/** - * @file llworld.h - * @brief Collection of viewer regions in the vacinity of the user. - * - * Represents the whole world, so far as 3D functionality is conserned. - * Always contains the region that the user's avatar is in along with - * neighboring regions. As the user crosses region boundaries, new - * regions are added to the world and distant ones are rolled up. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_LLWORLD_H -#define LL_LLWORLD_H - -#include "llpatchvertexarray.h" - -#include "llmath.h" -#include "v3math.h" -#include "llsingleton.h" -#include "llstring.h" -#include "llviewerpartsim.h" -#include "llviewertexture.h" -#include "llvowater.h" - -class LLViewerRegion; -class LLVector3d; -class LLMessageSystem; -class LLNetMap; -class LLHost; - -class LLViewerObject; -class LLSurfacePatch; - -class LLCharacter; -class LLCloudPuff; -class LLCloudGroup; -class LLVOAvatar; - -// LLWorld maintains a stack of unused viewer_regions and an array of pointers to viewer regions -// as simulators are connected to, viewer_regions are popped off the stack and connected as required -// as simulators are removed, they are pushed back onto the stack - -class LLWorld : public LLSimpleton -{ -public: - LLWorld(); - - // Clear any objects, regions - // Prepares class to be reused or destroyed - void resetClass(); - - LLViewerRegion* addRegion(const U64 ®ion_handle, const LLHost &host); - // safe to call if already present, does the "right thing" if - // hosts are same, or if hosts are different, etc... - void removeRegion(const LLHost &host); - - void disconnectRegions(); // Send quit messages to all child regions - - LLViewerRegion* getRegion(const LLHost &host); - LLViewerRegion* getRegionFromPosGlobal(const LLVector3d &pos); - LLViewerRegion* getRegionFromPosAgent(const LLVector3 &pos); - LLViewerRegion* getRegionFromHandle(const U64 &handle); - LLViewerRegion* getRegionFromID(const LLUUID& region_id); - bool positionRegionValidGlobal(const LLVector3d& pos); // true if position is in valid region - LLVector3d clipToVisibleRegions(const LLVector3d &start_pos, const LLVector3d &end_pos); - - void updateAgentOffset(const LLVector3d &offset); - - // All of these should be in the agent coordinate frame - LLViewerRegion* resolveRegionGlobal(LLVector3 &localpos, const LLVector3d &position); - LLViewerRegion* resolveRegionAgent(LLVector3 &localpos, const LLVector3 &position); - F32 resolveLandHeightGlobal(const LLVector3d &position); - F32 resolveLandHeightAgent(const LLVector3 &position); - - // Return the lowest allowed Z point to prevent objects from being moved - // underground. - F32 getMinAllowedZ(LLViewerObject* object, const LLVector3d &global_pos); - - // takes a line segment defined by point_a and point_b, then - // determines the closest (to point_a) point of intersection that is - // on the land surface or on an object of the world. - // Stores results in "intersection" and "intersection_normal" and - // returns a scalar value that is the normalized (by length of line segment) - // distance along the line from "point_a" to "intersection". - // - // Currently assumes point_a and point_b only differ in z-direction, - // but it may eventually become more general. - F32 resolveStepHeightGlobal(const LLVOAvatar* avatarp, const LLVector3d &point_a, const LLVector3d &point_b, - LLVector3d &intersection, LLVector3 &intersection_normal, - LLViewerObject** viewerObjectPtr=NULL); - - LLSurfacePatch * resolveLandPatchGlobal(const LLVector3d &position); - LLVector3 resolveLandNormalGlobal(const LLVector3d &position); // absolute frame - - U32 getRegionWidthInPoints() const { return mWidth; } - F32 getRegionScale() const { return mScale; } - - // region X and Y size in meters - F32 getRegionWidthInMeters() const { return mWidthInMeters; } - F32 getRegionMinHeight() const { return -mWidthInMeters; } - F32 getRegionMaxHeight() const { return MAX_OBJECT_Z; } - - void updateRegions(F32 max_update_time); - void updateVisibilities(); - void updateParticles(); - - void renderPropertyLines(); - - void updateNetStats(); // Update network statistics for all the regions... - - void printPacketsLost(); - void requestCacheMisses(); - - // deal with map object updates in the world. - static void processCoarseUpdate(LLMessageSystem* msg, void** user_data); - - F32 getLandFarClip() const; - void setLandFarClip(const F32 far_clip); - - LLViewerTexture *getDefaultWaterTexture(); - void updateWaterObjects(); - - void waterHeightRegionInfo(std::string const& sim_name, F32 water_height); - void shiftRegions(const LLVector3& offset); - - void setSpaceTimeUSec(const U64MicrosecondsImplicit space_time_usec); - U64MicrosecondsImplicit getSpaceTimeUSec() const; - - void getInfo(LLSD& info); - U32 getNumOfActiveCachedObjects() const {return mNumOfActiveCachedObjects;} - - void clearAllVisibleObjects(); -public: - typedef std::list region_list_t; - const region_list_t& getRegionList() const { return mActiveRegionList; } - - typedef boost::signals2::signal region_remove_signal_t; - boost::signals2::connection setRegionRemovedCallback(const region_remove_signal_t::slot_type& cb); - - // Returns lists of avatar IDs and their world-space positions within a given distance of a point. - // All arguments are optional. Given containers will be emptied and then filled. - // Not supplying origin or radius input returns data on all avatars in the known regions. - void getAvatars( - uuid_vec_t* avatar_ids = NULL, - std::vector* positions = NULL, - const LLVector3d& relative_to = LLVector3d(), F32 radius = FLT_MAX) const; - - // Returns 'true' if the region is in mRegionList, - // 'false' if the region has been removed due to region change - // or if the circuit to this simulator had been lost. - bool isRegionListed(const LLViewerRegion* region) const; - - // profile nearby avatars using gPipeline.profileAvatar and update their render times - // return max GPU time - F32 getNearbyAvatarsAndMaxGPUTime(std::vector &valid_nearby_avs); - -private: - void clearHoleWaterObjects(); - void clearEdgeWaterObjects(); - - region_list_t mActiveRegionList; - region_list_t mRegionList; - region_list_t mVisibleRegionList; - region_list_t mCulledRegionList; - - region_remove_signal_t mRegionRemovedSignal; - - // Number of points on edge - static const U32 mWidth; - - // meters/point, therefore mWidth * mScale = meters per edge - static const F32 mScale; - - static const F32 mWidthInMeters; - - F32 mLandFarClip; // Far clip distance for land. - LLPatchVertexArray mLandPatch; - S32 mLastPacketsIn; - S32 mLastPacketsOut; - S32 mLastPacketsLost; - U32 mNumOfActiveCachedObjects; - U64MicrosecondsImplicit mSpaceTimeUSec; - - //////////////////////////// - // - // Data for "Fake" objects - // - - std::list > mHoleWaterObjects; - static const S32 EDGE_WATER_OBJECTS_COUNT = 8; - LLPointer mEdgeWaterObjects[EDGE_WATER_OBJECTS_COUNT]; - - LLPointer mDefaultWaterTexturep; -}; - - -void process_enable_simulator(LLMessageSystem *mesgsys, void **user_data); -void process_disable_simulator(LLMessageSystem *mesgsys, void **user_data); - -void process_region_handshake(LLMessageSystem* msg, void** user_data); - -void send_agent_pause(); -void send_agent_resume(); - -#endif +/** + * @file llworld.h + * @brief Collection of viewer regions in the vacinity of the user. + * + * Represents the whole world, so far as 3D functionality is conserned. + * Always contains the region that the user's avatar is in along with + * neighboring regions. As the user crosses region boundaries, new + * regions are added to the world and distant ones are rolled up. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLWORLD_H +#define LL_LLWORLD_H + +#include "llpatchvertexarray.h" + +#include "llmath.h" +#include "v3math.h" +#include "llsingleton.h" +#include "llstring.h" +#include "llviewerpartsim.h" +#include "llviewertexture.h" +#include "llvowater.h" + +class LLViewerRegion; +class LLVector3d; +class LLMessageSystem; +class LLNetMap; +class LLHost; + +class LLViewerObject; +class LLSurfacePatch; + +class LLCharacter; +class LLCloudPuff; +class LLCloudGroup; +class LLVOAvatar; + +// LLWorld maintains a stack of unused viewer_regions and an array of pointers to viewer regions +// as simulators are connected to, viewer_regions are popped off the stack and connected as required +// as simulators are removed, they are pushed back onto the stack + +class LLWorld : public LLSimpleton +{ +public: + LLWorld(); + + // Clear any objects, regions + // Prepares class to be reused or destroyed + void resetClass(); + + LLViewerRegion* addRegion(const U64 ®ion_handle, const LLHost &host); + // safe to call if already present, does the "right thing" if + // hosts are same, or if hosts are different, etc... + void removeRegion(const LLHost &host); + + void disconnectRegions(); // Send quit messages to all child regions + + LLViewerRegion* getRegion(const LLHost &host); + LLViewerRegion* getRegionFromPosGlobal(const LLVector3d &pos); + LLViewerRegion* getRegionFromPosAgent(const LLVector3 &pos); + LLViewerRegion* getRegionFromHandle(const U64 &handle); + LLViewerRegion* getRegionFromID(const LLUUID& region_id); + bool positionRegionValidGlobal(const LLVector3d& pos); // true if position is in valid region + LLVector3d clipToVisibleRegions(const LLVector3d &start_pos, const LLVector3d &end_pos); + + void updateAgentOffset(const LLVector3d &offset); + + // All of these should be in the agent coordinate frame + LLViewerRegion* resolveRegionGlobal(LLVector3 &localpos, const LLVector3d &position); + LLViewerRegion* resolveRegionAgent(LLVector3 &localpos, const LLVector3 &position); + F32 resolveLandHeightGlobal(const LLVector3d &position); + F32 resolveLandHeightAgent(const LLVector3 &position); + + // Return the lowest allowed Z point to prevent objects from being moved + // underground. + F32 getMinAllowedZ(LLViewerObject* object, const LLVector3d &global_pos); + + // takes a line segment defined by point_a and point_b, then + // determines the closest (to point_a) point of intersection that is + // on the land surface or on an object of the world. + // Stores results in "intersection" and "intersection_normal" and + // returns a scalar value that is the normalized (by length of line segment) + // distance along the line from "point_a" to "intersection". + // + // Currently assumes point_a and point_b only differ in z-direction, + // but it may eventually become more general. + F32 resolveStepHeightGlobal(const LLVOAvatar* avatarp, const LLVector3d &point_a, const LLVector3d &point_b, + LLVector3d &intersection, LLVector3 &intersection_normal, + LLViewerObject** viewerObjectPtr=NULL); + + LLSurfacePatch * resolveLandPatchGlobal(const LLVector3d &position); + LLVector3 resolveLandNormalGlobal(const LLVector3d &position); // absolute frame + + U32 getRegionWidthInPoints() const { return mWidth; } + F32 getRegionScale() const { return mScale; } + + // region X and Y size in meters + F32 getRegionWidthInMeters() const { return mWidthInMeters; } + F32 getRegionMinHeight() const { return -mWidthInMeters; } + F32 getRegionMaxHeight() const { return MAX_OBJECT_Z; } + + void updateRegions(F32 max_update_time); + void updateVisibilities(); + void updateParticles(); + + void renderPropertyLines(); + + void updateNetStats(); // Update network statistics for all the regions... + + void printPacketsLost(); + void requestCacheMisses(); + + // deal with map object updates in the world. + static void processCoarseUpdate(LLMessageSystem* msg, void** user_data); + + F32 getLandFarClip() const; + void setLandFarClip(const F32 far_clip); + + LLViewerTexture *getDefaultWaterTexture(); + void updateWaterObjects(); + + void waterHeightRegionInfo(std::string const& sim_name, F32 water_height); + void shiftRegions(const LLVector3& offset); + + void setSpaceTimeUSec(const U64MicrosecondsImplicit space_time_usec); + U64MicrosecondsImplicit getSpaceTimeUSec() const; + + void getInfo(LLSD& info); + U32 getNumOfActiveCachedObjects() const {return mNumOfActiveCachedObjects;} + + void clearAllVisibleObjects(); +public: + typedef std::list region_list_t; + const region_list_t& getRegionList() const { return mActiveRegionList; } + + typedef boost::signals2::signal region_remove_signal_t; + boost::signals2::connection setRegionRemovedCallback(const region_remove_signal_t::slot_type& cb); + + // Returns lists of avatar IDs and their world-space positions within a given distance of a point. + // All arguments are optional. Given containers will be emptied and then filled. + // Not supplying origin or radius input returns data on all avatars in the known regions. + void getAvatars( + uuid_vec_t* avatar_ids = NULL, + std::vector* positions = NULL, + const LLVector3d& relative_to = LLVector3d(), F32 radius = FLT_MAX) const; + + // Returns 'true' if the region is in mRegionList, + // 'false' if the region has been removed due to region change + // or if the circuit to this simulator had been lost. + bool isRegionListed(const LLViewerRegion* region) const; + + // profile nearby avatars using gPipeline.profileAvatar and update their render times + // return max GPU time + F32 getNearbyAvatarsAndMaxGPUTime(std::vector &valid_nearby_avs); + +private: + void clearHoleWaterObjects(); + void clearEdgeWaterObjects(); + + region_list_t mActiveRegionList; + region_list_t mRegionList; + region_list_t mVisibleRegionList; + region_list_t mCulledRegionList; + + region_remove_signal_t mRegionRemovedSignal; + + // Number of points on edge + static const U32 mWidth; + + // meters/point, therefore mWidth * mScale = meters per edge + static const F32 mScale; + + static const F32 mWidthInMeters; + + F32 mLandFarClip; // Far clip distance for land. + LLPatchVertexArray mLandPatch; + S32 mLastPacketsIn; + S32 mLastPacketsOut; + S32 mLastPacketsLost; + U32 mNumOfActiveCachedObjects; + U64MicrosecondsImplicit mSpaceTimeUSec; + + //////////////////////////// + // + // Data for "Fake" objects + // + + std::list > mHoleWaterObjects; + static const S32 EDGE_WATER_OBJECTS_COUNT = 8; + LLPointer mEdgeWaterObjects[EDGE_WATER_OBJECTS_COUNT]; + + LLPointer mDefaultWaterTexturep; +}; + + +void process_enable_simulator(LLMessageSystem *mesgsys, void **user_data); +void process_disable_simulator(LLMessageSystem *mesgsys, void **user_data); + +void process_region_handshake(LLMessageSystem* msg, void** user_data); + +void send_agent_pause(); +void send_agent_resume(); + +#endif diff --git a/indra/newview/llworldmapmessage.cpp b/indra/newview/llworldmapmessage.cpp index 672f277abd..c60d075e0c 100644 --- a/indra/newview/llworldmapmessage.cpp +++ b/indra/newview/llworldmapmessage.cpp @@ -1,262 +1,262 @@ -/** - * @file llworldmapmessage.cpp - * @brief Handling of the messages to the DB made by and for the world map. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llworldmapmessage.h" -#include "message.h" - -#include "llworldmap.h" -#include "llagent.h" -#include "llfloaterworldmap.h" - -const U32 LAYER_FLAG = 2; - -//--------------------------------------------------------------------------- -// World Map Message Handling -//--------------------------------------------------------------------------- - -LLWorldMapMessage::LLWorldMapMessage() : - mSLURLRegionName(), - mSLURLRegionHandle(0), - mSLURL(), - mSLURLCallback(0), - mSLURLTeleport(false) -{ -} - -LLWorldMapMessage::~LLWorldMapMessage() -{ -} - -void LLWorldMapMessage::sendItemRequest(U32 type, U64 handle) -{ - //LL_INFOS("WorldMap") << "Send item request : type = " << type << LL_ENDL; - LLMessageSystem* msg = gMessageSystem; - - msg->newMessageFast(_PREHASH_MapItemRequest); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addU32Fast(_PREHASH_Flags, LAYER_FLAG); - msg->addU32Fast(_PREHASH_EstateID, 0); // Filled in on sim - msg->addBOOLFast(_PREHASH_Godlike, false); // Filled in on sim - - msg->nextBlockFast(_PREHASH_RequestData); - msg->addU32Fast(_PREHASH_ItemType, type); - msg->addU64Fast(_PREHASH_RegionHandle, handle); // If zero, filled in on sim - - gAgent.sendReliableMessage(); -} - -void LLWorldMapMessage::sendNamedRegionRequest(std::string region_name) -{ - //LL_INFOS("WorldMap") << LL_ENDL; - LLMessageSystem* msg = gMessageSystem; - - // Request for region data - msg->newMessageFast(_PREHASH_MapNameRequest); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->addU32Fast(_PREHASH_Flags, LAYER_FLAG); - msg->addU32Fast(_PREHASH_EstateID, 0); // Filled in on sim - msg->addBOOLFast(_PREHASH_Godlike, false); // Filled in on sim - msg->nextBlockFast(_PREHASH_NameData); - msg->addStringFast(_PREHASH_Name, region_name); - gAgent.sendReliableMessage(); -} - -void LLWorldMapMessage::sendNamedRegionRequest(std::string region_name, - url_callback_t callback, - const std::string& callback_url, - bool teleport) // immediately teleport when result returned -{ - //LL_INFOS("WorldMap") << LL_ENDL; - mSLURLRegionName = region_name; - mSLURLRegionHandle = 0; - mSLURL = callback_url; - mSLURLCallback = callback; - mSLURLTeleport = teleport; - - sendNamedRegionRequest(region_name); -} - -void LLWorldMapMessage::sendHandleRegionRequest(U64 region_handle, - url_callback_t callback, - const std::string& callback_url, - bool teleport) // immediately teleport when result returned -{ - //LL_INFOS("WorldMap") << LL_ENDL; - mSLURLRegionName.clear(); - mSLURLRegionHandle = region_handle; - mSLURL = callback_url; - mSLURLCallback = callback; - mSLURLTeleport = teleport; - - U32 global_x; - U32 global_y; - from_region_handle(region_handle, &global_x, &global_y); - U16 grid_x = (U16)(global_x / REGION_WIDTH_UNITS); - U16 grid_y = (U16)(global_y / REGION_WIDTH_UNITS); - - sendMapBlockRequest(grid_x, grid_y, grid_x, grid_y, true); -} - -void LLWorldMapMessage::sendMapBlockRequest(U16 min_x, U16 min_y, U16 max_x, U16 max_y, bool return_nonexistent) -{ - //LL_INFOS("WorldMap" << " min = (" << min_x << ", " << min_y << "), max = (" << max_x << ", " << max_y << ", nonexistent = " << return_nonexistent << LL_ENDL; - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_MapBlockRequest); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - U32 flags = LAYER_FLAG; - flags |= (return_nonexistent ? 0x10000 : 0); - msg->addU32Fast(_PREHASH_Flags, flags); - msg->addU32Fast(_PREHASH_EstateID, 0); // Filled in on sim - msg->addBOOLFast(_PREHASH_Godlike, false); // Filled in on sim - msg->nextBlockFast(_PREHASH_PositionData); - msg->addU16Fast(_PREHASH_MinX, min_x); - msg->addU16Fast(_PREHASH_MinY, min_y); - msg->addU16Fast(_PREHASH_MaxX, max_x); - msg->addU16Fast(_PREHASH_MaxY, max_y); - gAgent.sendReliableMessage(); -} - -// public static -void LLWorldMapMessage::processMapBlockReply(LLMessageSystem* msg, void**) -{ - if (gNonInteractive) - { - return; - } - U32 agent_flags; - msg->getU32Fast(_PREHASH_AgentData, _PREHASH_Flags, agent_flags); - - // There's only one flag that we ever use here - if (agent_flags != LAYER_FLAG) - { - LL_WARNS() << "Invalid map image type returned! layer = " << agent_flags << LL_ENDL; - return; - } - - S32 num_blocks = msg->getNumberOfBlocksFast(_PREHASH_Data); - //LL_INFOS("WorldMap") << "num_blocks = " << num_blocks << LL_ENDL; - - bool found_null_sim = false; - - for (S32 block=0; blockgetU16Fast(_PREHASH_Data, _PREHASH_X, x_regions, block); - msg->getU16Fast(_PREHASH_Data, _PREHASH_Y, y_regions, block); - msg->getStringFast(_PREHASH_Data, _PREHASH_Name, name, block); - msg->getU8Fast(_PREHASH_Data, _PREHASH_Access, accesscode, block); - msg->getU32Fast(_PREHASH_Data, _PREHASH_RegionFlags, region_flags, block); -// msg->getU8Fast(_PREHASH_Data, _PREHASH_WaterHeight, water_height, block); -// msg->getU8Fast(_PREHASH_Data, _PREHASH_Agents, agents, block); - msg->getUUIDFast(_PREHASH_Data, _PREHASH_MapImageID, image_id, block); - - U32 x_world = (U32)(x_regions) * REGION_WIDTH_UNITS; - U32 y_world = (U32)(y_regions) * REGION_WIDTH_UNITS; - - // name shouldn't be empty, see EXT-4568 - llassert(!name.empty()); - - // Insert that region in the world map, if failure, flag it as a "null_sim" - if (!(LLWorldMap::getInstance()->insertRegion(x_world, y_world, name, image_id, (U32)accesscode, region_flags))) - { - found_null_sim = true; - } - - // If we hit a valid tracking location, do what needs to be done app level wise - if (LLWorldMap::getInstance()->isTrackingValidLocation()) - { - LLVector3d pos_global = LLWorldMap::getInstance()->getTrackedPositionGlobal(); - if (LLWorldMap::getInstance()->isTrackingDoubleClick()) - { - // Teleport if the user double clicked - gAgent.teleportViaLocation(pos_global); - } - // Update the "real" tracker information - gFloaterWorldMap->trackLocation(pos_global); - } - - // Handle the SLURL callback if any - url_callback_t callback = LLWorldMapMessage::getInstance()->mSLURLCallback; - if(callback != NULL) - { - U64 handle = to_region_handle(x_world, y_world); - // Check if we reached the requested region - if ((LLStringUtil::compareInsensitive(LLWorldMapMessage::getInstance()->mSLURLRegionName, name)==0) - || (LLWorldMapMessage::getInstance()->mSLURLRegionHandle == handle)) - { - LLWorldMapMessage::getInstance()->mSLURLCallback = NULL; - LLWorldMapMessage::getInstance()->mSLURLRegionName.clear(); - LLWorldMapMessage::getInstance()->mSLURLRegionHandle = 0; - - callback(handle, LLWorldMapMessage::getInstance()->mSLURL, image_id, LLWorldMapMessage::getInstance()->mSLURLTeleport); - } - } - } - // Tell the UI to update itself - gFloaterWorldMap->updateSims(found_null_sim); -} - -// public static -void LLWorldMapMessage::processMapItemReply(LLMessageSystem* msg, void**) -{ - //LL_INFOS("WorldMap") << LL_ENDL; - U32 type; - msg->getU32Fast(_PREHASH_RequestData, _PREHASH_ItemType, type); - - S32 num_blocks = msg->getNumberOfBlocks("Data"); - - for (S32 block=0; blockgetU32Fast(_PREHASH_Data, _PREHASH_X, X, block); - msg->getU32Fast(_PREHASH_Data, _PREHASH_Y, Y, block); - msg->getStringFast(_PREHASH_Data, _PREHASH_Name, name, block); - msg->getUUIDFast(_PREHASH_Data, _PREHASH_ID, uuid, block); - msg->getS32Fast(_PREHASH_Data, _PREHASH_Extra, extra, block); - msg->getS32Fast(_PREHASH_Data, _PREHASH_Extra2, extra2, block); - - LLWorldMap::getInstance()->insertItem(X, Y, name, uuid, type, extra, extra2); - } -} - +/** + * @file llworldmapmessage.cpp + * @brief Handling of the messages to the DB made by and for the world map. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llworldmapmessage.h" +#include "message.h" + +#include "llworldmap.h" +#include "llagent.h" +#include "llfloaterworldmap.h" + +const U32 LAYER_FLAG = 2; + +//--------------------------------------------------------------------------- +// World Map Message Handling +//--------------------------------------------------------------------------- + +LLWorldMapMessage::LLWorldMapMessage() : + mSLURLRegionName(), + mSLURLRegionHandle(0), + mSLURL(), + mSLURLCallback(0), + mSLURLTeleport(false) +{ +} + +LLWorldMapMessage::~LLWorldMapMessage() +{ +} + +void LLWorldMapMessage::sendItemRequest(U32 type, U64 handle) +{ + //LL_INFOS("WorldMap") << "Send item request : type = " << type << LL_ENDL; + LLMessageSystem* msg = gMessageSystem; + + msg->newMessageFast(_PREHASH_MapItemRequest); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addU32Fast(_PREHASH_Flags, LAYER_FLAG); + msg->addU32Fast(_PREHASH_EstateID, 0); // Filled in on sim + msg->addBOOLFast(_PREHASH_Godlike, false); // Filled in on sim + + msg->nextBlockFast(_PREHASH_RequestData); + msg->addU32Fast(_PREHASH_ItemType, type); + msg->addU64Fast(_PREHASH_RegionHandle, handle); // If zero, filled in on sim + + gAgent.sendReliableMessage(); +} + +void LLWorldMapMessage::sendNamedRegionRequest(std::string region_name) +{ + //LL_INFOS("WorldMap") << LL_ENDL; + LLMessageSystem* msg = gMessageSystem; + + // Request for region data + msg->newMessageFast(_PREHASH_MapNameRequest); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->addU32Fast(_PREHASH_Flags, LAYER_FLAG); + msg->addU32Fast(_PREHASH_EstateID, 0); // Filled in on sim + msg->addBOOLFast(_PREHASH_Godlike, false); // Filled in on sim + msg->nextBlockFast(_PREHASH_NameData); + msg->addStringFast(_PREHASH_Name, region_name); + gAgent.sendReliableMessage(); +} + +void LLWorldMapMessage::sendNamedRegionRequest(std::string region_name, + url_callback_t callback, + const std::string& callback_url, + bool teleport) // immediately teleport when result returned +{ + //LL_INFOS("WorldMap") << LL_ENDL; + mSLURLRegionName = region_name; + mSLURLRegionHandle = 0; + mSLURL = callback_url; + mSLURLCallback = callback; + mSLURLTeleport = teleport; + + sendNamedRegionRequest(region_name); +} + +void LLWorldMapMessage::sendHandleRegionRequest(U64 region_handle, + url_callback_t callback, + const std::string& callback_url, + bool teleport) // immediately teleport when result returned +{ + //LL_INFOS("WorldMap") << LL_ENDL; + mSLURLRegionName.clear(); + mSLURLRegionHandle = region_handle; + mSLURL = callback_url; + mSLURLCallback = callback; + mSLURLTeleport = teleport; + + U32 global_x; + U32 global_y; + from_region_handle(region_handle, &global_x, &global_y); + U16 grid_x = (U16)(global_x / REGION_WIDTH_UNITS); + U16 grid_y = (U16)(global_y / REGION_WIDTH_UNITS); + + sendMapBlockRequest(grid_x, grid_y, grid_x, grid_y, true); +} + +void LLWorldMapMessage::sendMapBlockRequest(U16 min_x, U16 min_y, U16 max_x, U16 max_y, bool return_nonexistent) +{ + //LL_INFOS("WorldMap" << " min = (" << min_x << ", " << min_y << "), max = (" << max_x << ", " << max_y << ", nonexistent = " << return_nonexistent << LL_ENDL; + LLMessageSystem* msg = gMessageSystem; + msg->newMessageFast(_PREHASH_MapBlockRequest); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + U32 flags = LAYER_FLAG; + flags |= (return_nonexistent ? 0x10000 : 0); + msg->addU32Fast(_PREHASH_Flags, flags); + msg->addU32Fast(_PREHASH_EstateID, 0); // Filled in on sim + msg->addBOOLFast(_PREHASH_Godlike, false); // Filled in on sim + msg->nextBlockFast(_PREHASH_PositionData); + msg->addU16Fast(_PREHASH_MinX, min_x); + msg->addU16Fast(_PREHASH_MinY, min_y); + msg->addU16Fast(_PREHASH_MaxX, max_x); + msg->addU16Fast(_PREHASH_MaxY, max_y); + gAgent.sendReliableMessage(); +} + +// public static +void LLWorldMapMessage::processMapBlockReply(LLMessageSystem* msg, void**) +{ + if (gNonInteractive) + { + return; + } + U32 agent_flags; + msg->getU32Fast(_PREHASH_AgentData, _PREHASH_Flags, agent_flags); + + // There's only one flag that we ever use here + if (agent_flags != LAYER_FLAG) + { + LL_WARNS() << "Invalid map image type returned! layer = " << agent_flags << LL_ENDL; + return; + } + + S32 num_blocks = msg->getNumberOfBlocksFast(_PREHASH_Data); + //LL_INFOS("WorldMap") << "num_blocks = " << num_blocks << LL_ENDL; + + bool found_null_sim = false; + + for (S32 block=0; blockgetU16Fast(_PREHASH_Data, _PREHASH_X, x_regions, block); + msg->getU16Fast(_PREHASH_Data, _PREHASH_Y, y_regions, block); + msg->getStringFast(_PREHASH_Data, _PREHASH_Name, name, block); + msg->getU8Fast(_PREHASH_Data, _PREHASH_Access, accesscode, block); + msg->getU32Fast(_PREHASH_Data, _PREHASH_RegionFlags, region_flags, block); +// msg->getU8Fast(_PREHASH_Data, _PREHASH_WaterHeight, water_height, block); +// msg->getU8Fast(_PREHASH_Data, _PREHASH_Agents, agents, block); + msg->getUUIDFast(_PREHASH_Data, _PREHASH_MapImageID, image_id, block); + + U32 x_world = (U32)(x_regions) * REGION_WIDTH_UNITS; + U32 y_world = (U32)(y_regions) * REGION_WIDTH_UNITS; + + // name shouldn't be empty, see EXT-4568 + llassert(!name.empty()); + + // Insert that region in the world map, if failure, flag it as a "null_sim" + if (!(LLWorldMap::getInstance()->insertRegion(x_world, y_world, name, image_id, (U32)accesscode, region_flags))) + { + found_null_sim = true; + } + + // If we hit a valid tracking location, do what needs to be done app level wise + if (LLWorldMap::getInstance()->isTrackingValidLocation()) + { + LLVector3d pos_global = LLWorldMap::getInstance()->getTrackedPositionGlobal(); + if (LLWorldMap::getInstance()->isTrackingDoubleClick()) + { + // Teleport if the user double clicked + gAgent.teleportViaLocation(pos_global); + } + // Update the "real" tracker information + gFloaterWorldMap->trackLocation(pos_global); + } + + // Handle the SLURL callback if any + url_callback_t callback = LLWorldMapMessage::getInstance()->mSLURLCallback; + if(callback != NULL) + { + U64 handle = to_region_handle(x_world, y_world); + // Check if we reached the requested region + if ((LLStringUtil::compareInsensitive(LLWorldMapMessage::getInstance()->mSLURLRegionName, name)==0) + || (LLWorldMapMessage::getInstance()->mSLURLRegionHandle == handle)) + { + LLWorldMapMessage::getInstance()->mSLURLCallback = NULL; + LLWorldMapMessage::getInstance()->mSLURLRegionName.clear(); + LLWorldMapMessage::getInstance()->mSLURLRegionHandle = 0; + + callback(handle, LLWorldMapMessage::getInstance()->mSLURL, image_id, LLWorldMapMessage::getInstance()->mSLURLTeleport); + } + } + } + // Tell the UI to update itself + gFloaterWorldMap->updateSims(found_null_sim); +} + +// public static +void LLWorldMapMessage::processMapItemReply(LLMessageSystem* msg, void**) +{ + //LL_INFOS("WorldMap") << LL_ENDL; + U32 type; + msg->getU32Fast(_PREHASH_RequestData, _PREHASH_ItemType, type); + + S32 num_blocks = msg->getNumberOfBlocks("Data"); + + for (S32 block=0; blockgetU32Fast(_PREHASH_Data, _PREHASH_X, X, block); + msg->getU32Fast(_PREHASH_Data, _PREHASH_Y, Y, block); + msg->getStringFast(_PREHASH_Data, _PREHASH_Name, name, block); + msg->getUUIDFast(_PREHASH_Data, _PREHASH_ID, uuid, block); + msg->getS32Fast(_PREHASH_Data, _PREHASH_Extra, extra, block); + msg->getS32Fast(_PREHASH_Data, _PREHASH_Extra2, extra2, block); + + LLWorldMap::getInstance()->insertItem(X, Y, name, uuid, type, extra, extra2); + } +} + diff --git a/indra/newview/llworldmapview.cpp b/indra/newview/llworldmapview.cpp index e84c34ae21..09a18a9825 100755 --- a/indra/newview/llworldmapview.cpp +++ b/indra/newview/llworldmapview.cpp @@ -1,1832 +1,1832 @@ -/** - * @file llworldmapview.cpp - * @brief LLWorldMapView class implementation - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llworldmapview.h" - -#include "indra_constants.h" -#include "llui.h" -#include "llmath.h" // clampf() -#include "llregionhandle.h" -#include "lleventflags.h" -#include "llfloaterreg.h" -#include "llrender.h" -#include "lltooltip.h" - -#include "llagent.h" -#include "llagentcamera.h" -#include "llcallingcard.h" -#include "llcommandhandler.h" -#include "llviewercontrol.h" -#include "llfloatermap.h" -#include "llfloaterworldmap.h" -#include "llfocusmgr.h" -#include "lllocalcliprect.h" -#include "lltextbox.h" -#include "lltextureview.h" -#include "lltracker.h" -#include "llviewercamera.h" -#include "llviewernetwork.h" -#include "llviewertexture.h" -#include "llviewertexturelist.h" -#include "llviewerregion.h" -#include "llviewerwindow.h" -#include "lltrans.h" - -#include "llglheaders.h" - -// # Constants -static constexpr F32 MAP_DEFAULT_SCALE = 128.f; -static constexpr F32 MAP_ITERP_TIME_CONSTANT = 0.75f; -static constexpr F32 MAP_ZOOM_ACCELERATION_TIME = 0.3f; -static constexpr F32 MAP_ZOOM_MAX_INTERP = 0.5f; -static constexpr F32 MAP_SCALE_SNAP_THRESHOLD = 0.005f; - -// Basically a C++ implementation of the OCEAN_COLOR defined in mapstitcher.py -// Please ensure consistency between those 2 files (TODO: would be better to get that color from an asset source...) -// OCEAN_COLOR = "#1D475F" -constexpr F32 OCEAN_RED = (F32)(0x1D)/255.f; -constexpr F32 OCEAN_GREEN = (F32)(0x47)/255.f; -constexpr F32 OCEAN_BLUE = (F32)(0x5F)/255.f; - -constexpr F32 GODLY_TELEPORT_HEIGHT = 200.f; -constexpr F32 BIG_DOT_RADIUS = 5.f; -bool LLWorldMapView::sHandledLastClick = false; - -LLUIImagePtr LLWorldMapView::sAvatarSmallImage = NULL; -LLUIImagePtr LLWorldMapView::sAvatarYouImage = NULL; -LLUIImagePtr LLWorldMapView::sAvatarYouLargeImage = NULL; -LLUIImagePtr LLWorldMapView::sAvatarLevelImage = NULL; -LLUIImagePtr LLWorldMapView::sAvatarAboveImage = NULL; -LLUIImagePtr LLWorldMapView::sAvatarBelowImage = NULL; -LLUIImagePtr LLWorldMapView::sAvatarUnknownImage = NULL; - -LLUIImagePtr LLWorldMapView::sTelehubImage = NULL; -LLUIImagePtr LLWorldMapView::sInfohubImage = NULL; -LLUIImagePtr LLWorldMapView::sHomeImage = NULL; -LLUIImagePtr LLWorldMapView::sEventImage = NULL; -LLUIImagePtr LLWorldMapView::sEventMatureImage = NULL; -LLUIImagePtr LLWorldMapView::sEventAdultImage = NULL; - -LLUIImagePtr LLWorldMapView::sTrackCircleImage = NULL; -LLUIImagePtr LLWorldMapView::sTrackArrowImage = NULL; - -LLUIImagePtr LLWorldMapView::sClassifiedsImage = NULL; -LLUIImagePtr LLWorldMapView::sForSaleImage = NULL; -LLUIImagePtr LLWorldMapView::sForSaleAdultImage = NULL; - -S32 LLWorldMapView::sTrackingArrowX = 0; -S32 LLWorldMapView::sTrackingArrowY = 0; -bool LLWorldMapView::sVisibleTilesLoaded = false; -F32 LLWorldMapView::sMapScaleSetting = MAP_DEFAULT_SCALE; -LLVector2 LLWorldMapView::sZoomPivot = LLVector2(0.0f, 0.0f); -LLFrameTimer LLWorldMapView::sZoomTimer = LLFrameTimer(); - -std::map LLWorldMapView::sStringsMap; - -// Fetch and draw info thresholds -const F32 DRAW_TEXT_THRESHOLD = 96.f; // Don't draw text under that resolution value (res = width region in meters) -const S32 DRAW_SIMINFO_THRESHOLD = 3; // Max level for which we load or display sim level information (level in LLWorldMipmap sense) -const S32 DRAW_LANDFORSALE_THRESHOLD = 2; // Max level for which we load or display land for sale picture data (level in LLWorldMipmap sense) - -// When on, draw an outline for each mipmap tile gotten from S3 -#define DEBUG_DRAW_TILE 0 - - -void LLWorldMapView::initClass() -{ - sAvatarSmallImage = LLUI::getUIImage("map_avatar_8.tga"); - sAvatarYouImage = LLUI::getUIImage("map_avatar_16.tga"); - sAvatarYouLargeImage = LLUI::getUIImage("map_avatar_you_32.tga"); - sAvatarLevelImage = LLUI::getUIImage("map_avatar_32.tga"); - sAvatarAboveImage = LLUI::getUIImage("map_avatar_above_32.tga"); - sAvatarBelowImage = LLUI::getUIImage("map_avatar_below_32.tga"); - sAvatarUnknownImage = LLUI::getUIImage("map_avatar_unknown_32.tga"); - - sHomeImage = LLUI::getUIImage("map_home.tga"); - sTelehubImage = LLUI::getUIImage("map_telehub.tga"); - sInfohubImage = LLUI::getUIImage("map_infohub.tga"); - sEventImage = LLUI::getUIImage("Parcel_PG_Dark"); - sEventMatureImage = LLUI::getUIImage("Parcel_M_Dark"); - // To Do: update the image resource for adult events. - sEventAdultImage = LLUI::getUIImage("Parcel_R_Dark"); - - sTrackCircleImage = LLUI::getUIImage("map_track_16.tga"); - sTrackArrowImage = LLUI::getUIImage("direction_arrow.tga"); - sClassifiedsImage = LLUI::getUIImage("icon_top_pick.tga"); - sForSaleImage = LLUI::getUIImage("icon_for_sale.tga"); - // To Do: update the image resource for adult lands on sale. - sForSaleAdultImage = LLUI::getUIImage("icon_for_sale_adult.tga"); - - sStringsMap["loading"] = LLTrans::getString("texture_loading"); - sStringsMap["offline"] = LLTrans::getString("worldmap_offline"); -} - -// static -void LLWorldMapView::cleanupClass() -{ - sAvatarSmallImage = NULL; - sAvatarYouImage = NULL; - sAvatarYouLargeImage = NULL; - sAvatarLevelImage = NULL; - sAvatarAboveImage = NULL; - sAvatarBelowImage = NULL; - sAvatarUnknownImage = NULL; - - sTelehubImage = NULL; - sInfohubImage = NULL; - sHomeImage = NULL; - sEventImage = NULL; - sEventMatureImage = NULL; - sEventAdultImage = NULL; - - sTrackCircleImage = NULL; - sTrackArrowImage = NULL; - sClassifiedsImage = NULL; - sForSaleImage = NULL; - sForSaleAdultImage = NULL; -} - -LLWorldMapView::LLWorldMapView() : - LLPanel(), - mBackgroundColor(LLColor4(OCEAN_RED, OCEAN_GREEN, OCEAN_BLUE, 1.f)), - mItemPicked(false), - mPanX(0.f), - mPanY(0.f), - mTargetPanX(0.f), - mTargetPanY(0.f), - mPanning(false), - mMouseDownPanX(0), - mMouseDownPanY(0), - mMouseDownX(0), - mMouseDownY(0), - mSelectIDStart(0), - mMapScale(0.f), - mTargetMapScale(0.f), - mMapIterpTime(MAP_ITERP_TIME_CONSTANT) -{ - // LL_INFOS("WorldMap") << "Creating the Map -> LLWorldMapView::LLWorldMapView()" << LL_ENDL; - - clearLastClick(); -} - -bool LLWorldMapView::postBuild() -{ - mTextBoxNorth = getChild ("floater_map_north"); - mTextBoxEast = getChild ("floater_map_east"); - mTextBoxWest = getChild ("floater_map_west"); - mTextBoxSouth = getChild ("floater_map_south"); - mTextBoxSouthEast = getChild ("floater_map_southeast"); - mTextBoxNorthEast = getChild ("floater_map_northeast"); - mTextBoxSouthWest = getChild ("floater_map_southwest"); - mTextBoxNorthWest = getChild ("floater_map_northwest"); - - mTextBoxNorth->setText(getString("world_map_north")); - mTextBoxEast->setText(getString ("world_map_east")); - mTextBoxWest->setText(getString("world_map_west")); - mTextBoxSouth->setText(getString ("world_map_south")); - mTextBoxSouthEast ->setText(getString ("world_map_southeast")); - mTextBoxNorthEast ->setText(getString ("world_map_northeast")); - mTextBoxSouthWest->setText(getString ("world_map_southwest")); - mTextBoxNorthWest ->setText(getString("world_map_northwest")); - - mTextBoxNorth->reshapeToFitText(); - mTextBoxEast->reshapeToFitText(); - mTextBoxWest->reshapeToFitText(); - mTextBoxSouth->reshapeToFitText(); - mTextBoxSouthEast ->reshapeToFitText(); - mTextBoxNorthEast ->reshapeToFitText(); - mTextBoxSouthWest->reshapeToFitText(); - mTextBoxNorthWest ->reshapeToFitText(); - - sZoomTimer.stop(); - setScale(sMapScaleSetting, true); - - return true; -} - - -LLWorldMapView::~LLWorldMapView() -{ - //LL_INFOS("WorldMap") << "Destroying the map -> LLWorldMapView::~LLWorldMapView()" << LL_ENDL; - cleanupTextures(); -} - - -// static -void LLWorldMapView::cleanupTextures() -{ -} - -void LLWorldMapView::zoom(F32 zoom) -{ - mTargetMapScale = scaleFromZoom(zoom); - if (!sZoomTimer.getStarted() && mMapScale != mTargetMapScale) - { - sZoomPivot = LLVector2(0, 0); - sZoomTimer.start(); - } -} - -void LLWorldMapView::zoomWithPivot(F32 zoom, S32 x, S32 y) -{ - mTargetMapScale = scaleFromZoom(zoom); - sZoomPivot = LLVector2(x, y); - if (!sZoomTimer.getStarted() && mMapScale != mTargetMapScale) - { - sZoomTimer.start(); - } -} - -F32 LLWorldMapView::getZoom() { return LLWorldMapView::zoomFromScale(mMapScale); } - -F32 LLWorldMapView::getScale() { return mMapScale; } - -// static -void LLWorldMapView::setScaleSetting(F32 scaleSetting) { sMapScaleSetting = scaleSetting; } - -// static -F32 LLWorldMapView::getScaleSetting() { return sMapScaleSetting; } - -void LLWorldMapView::setScale(F32 scale, bool snap) -{ - if (scale != mMapScale) - { - F32 old_scale = mMapScale; - - mMapScale = scale; - // Set the scale used when saving the setting - sMapScaleSetting = scale; - if (mMapScale <= 0.f) - { - mMapScale = 0.1f; - } - mMapIterpTime = MAP_ITERP_TIME_CONSTANT; - F32 ratio = (scale / old_scale); - mPanX *= ratio; - mPanY *= ratio; - mTargetPanX = mPanX; - mTargetPanY = mPanY; - sVisibleTilesLoaded = false; - - // If we are zooming relative to somewhere else rather than the center of the map, compensate for the difference in panning here - if (!sZoomPivot.isExactlyZero()) - { - LLVector2 relative_pivot; - relative_pivot.mV[VX] = sZoomPivot.mV[VX] - (getRect().getWidth() / 2.0); - relative_pivot.mV[VY] = sZoomPivot.mV[VY] - (getRect().getHeight() / 2.0); - LLVector2 zoom_pan_offset = relative_pivot - (relative_pivot * scale / old_scale); - mPanX += zoom_pan_offset.mV[VX]; - mPanY += zoom_pan_offset.mV[VY]; - mTargetPanX += zoom_pan_offset.mV[VX]; - mTargetPanY += zoom_pan_offset.mV[VY]; - } - } - - if (snap) - { - mTargetMapScale = scale; - } -} - -// static -void LLWorldMapView::translatePan(S32 delta_x, S32 delta_y) -{ - mPanX += delta_x; - mPanY += delta_y; - mTargetPanX = mPanX; - mTargetPanY = mPanY; - sVisibleTilesLoaded = false; -} - - -// static -void LLWorldMapView::setPan(S32 x, S32 y, bool snap) -{ - mMapIterpTime = MAP_ITERP_TIME_CONSTANT; - mTargetPanX = (F32) x; - mTargetPanY = (F32) y; - if (snap) - { - mPanX = mTargetPanX; - mPanY = mTargetPanY; - } - sVisibleTilesLoaded = false; -} - -// static -void LLWorldMapView::setPanWithInterpTime(S32 x, S32 y, bool snap, F32 interp_time) -{ - setPan(x, y, snap); - mMapIterpTime = interp_time; -} - -bool LLWorldMapView::showRegionInfo() { return LLWorldMipmap::scaleToLevel(mMapScale) <= DRAW_SIMINFO_THRESHOLD; } - -/////////////////////////////////////////////////////////////////////////////////// -// HELPERS - -bool is_agent_in_region(LLViewerRegion* region, LLSimInfo* info) -{ - return (region && info && info->isName(region->getName())); -} - -/////////////////////////////////////////////////////////////////////////////////// - -void LLWorldMapView::draw() -{ - static LLUIColor map_track_color = LLUIColorTable::instance().getColor("MapTrackColor", LLColor4::white); - - LLTextureView::clearDebugImages(); - - F64 current_time = LLTimer::getElapsedSeconds(); - - mVisibleRegions.clear(); - - // animate pan if necessary - mPanX = lerp(mPanX, mTargetPanX, LLSmoothInterpolation::getInterpolant(mMapIterpTime)); - mPanY = lerp(mPanY, mTargetPanY, LLSmoothInterpolation::getInterpolant(mMapIterpTime)); - - //RN: snaps to zoom value because interpolation caused jitter in the text rendering - if (!sZoomTimer.getStarted() && mMapScale != mTargetMapScale) - { - sZoomTimer.start(); - } - bool snap_scale = false; - F32 interp = llmin(MAP_ZOOM_MAX_INTERP, sZoomTimer.getElapsedTimeF32() / MAP_ZOOM_ACCELERATION_TIME); - F32 current_zoom_val = zoomFromScale(mMapScale); - F32 target_zoom_val = zoomFromScale(mTargetMapScale); - F32 new_zoom_val = lerp(current_zoom_val, target_zoom_val, interp); - if (abs(new_zoom_val - current_zoom_val) < MAP_SCALE_SNAP_THRESHOLD) - { - sZoomTimer.stop(); - snap_scale = true; - new_zoom_val = target_zoom_val; - } - F32 map_scale = scaleFromZoom(new_zoom_val); - setScale(map_scale, snap_scale); - - const S32 width = getRect().getWidth(); - const S32 height = getRect().getHeight(); - const F32 half_width = F32(width) / 2.0f; - const F32 half_height = F32(height) / 2.0f; - LLVector3d camera_global = gAgentCamera.getCameraPositionGlobal(); - - S32 level = LLWorldMipmap::scaleToLevel(mMapScale); - - LLLocalClipRect clip(getLocalRect()); - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - gGL.matrixMode(LLRender::MM_MODELVIEW); - - // Draw background rectangle - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.color4fv(mBackgroundColor.mV); - gl_rect_2d(0, height, width, 0); - - // Draw the image tiles - drawMipmap(width, height); - - // Draw per sim overlayed information (names, mature, offline...) - for (LLWorldMap::sim_info_map_t::const_iterator it = LLWorldMap::getInstance()->getRegionMap().begin(); - it != LLWorldMap::getInstance()->getRegionMap().end(); ++it) - { - U64 handle = it->first; - LLSimInfo* info = it->second; - - LLVector3d origin_global = from_region_handle(handle); - - // Find x and y position relative to camera's center. - LLVector3d rel_region_pos = origin_global - camera_global; - F32 relative_x = (rel_region_pos.mdV[0] / REGION_WIDTH_METERS) * mMapScale; - F32 relative_y = (rel_region_pos.mdV[1] / REGION_WIDTH_METERS) * mMapScale; - - // Coordinates of the sim in pixels in the UI panel - // When the view isn't panned, 0,0 = center of rectangle - F32 bottom = mPanY + half_height + relative_y; - F32 left = mPanX + half_width + relative_x; - F32 top = bottom + mMapScale ; - F32 right = left + mMapScale ; - - // Discard if region is outside the screen rectangle (not visible on screen) - if ((top < 0.f) || (bottom > height) || - (right < 0.f) || (left > width) ) - { - // Drop the "land for sale" fetching priority since it's outside the view rectangle - info->dropImagePriority(); - continue; - } - - // This list is used by other methods to know which regions are indeed displayed on screen - - mVisibleRegions.push_back(handle); - - // Update the agent count for that region if we're not too zoomed out already - if (level <= DRAW_SIMINFO_THRESHOLD) - { - info->updateAgentCount(current_time); - } - - if (info->isDown()) - { - // Draw a transparent red square over down sims - gGL.color4f(0.2f, 0.0f, 0.0f, 0.4f); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.begin(LLRender::QUADS); - gGL.vertex2f(left, top); - gGL.vertex2f(left, bottom); - gGL.vertex2f(right, bottom); - gGL.vertex2f(right, top); - gGL.end(); - } - else if (gSavedSettings.getBOOL("MapShowLandForSale") && (level <= DRAW_LANDFORSALE_THRESHOLD)) - { - // Draw the overlay image "Land for Sale / Land for Auction" - LLViewerFetchedTexture* overlayimage = info->getLandForSaleImage(); - if (overlayimage) - { - // Inform the fetch mechanism of the size we need - S32 draw_size = ll_round(mMapScale); - overlayimage->setKnownDrawSize(ll_round(draw_size * LLUI::getScaleFactor().mV[VX]), ll_round(draw_size * LLUI::getScaleFactor().mV[VY])); - // Draw something whenever we have enough info - if (overlayimage->hasGLTexture()) - { - gGL.getTexUnit(0)->bind(overlayimage); - gGL.color4f(1.f, 1.f, 1.f, 1.f); - gGL.begin(LLRender::QUADS); - gGL.texCoord2f(0.f, 1.f); - gGL.vertex3f(left, top, -0.5f); - gGL.texCoord2f(0.f, 0.f); - gGL.vertex3f(left, bottom, -0.5f); - gGL.texCoord2f(1.f, 0.f); - gGL.vertex3f(right, bottom, -0.5f); - gGL.texCoord2f(1.f, 1.f); - gGL.vertex3f(right, top, -0.5f); - gGL.end(); - } - } - } - else - { - // If we're not displaying the "land for sale", drop its fetching priority - info->dropImagePriority(); - } - - // Draw the region name in the lower left corner - if (mMapScale >= DRAW_TEXT_THRESHOLD) - { - LLFontGL* font = LLFontGL::getFont(LLFontDescriptor("SansSerif", "Small", LLFontGL::BOLD)); - std::string mesg; - if (info->isDown()) - { - mesg = llformat( "%s (%s)", info->getName().c_str(), sStringsMap["offline"].c_str()); - } - else - { - mesg = info->getName(); - } - if (!mesg.empty()) - { - font->renderUTF8( - mesg, 0, - llfloor(left + 3), llfloor(bottom + 2), - LLColor4::white, - LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::DROP_SHADOW, - S32_MAX, //max_chars - mMapScale, //max_pixels - NULL, - /*use_ellipses*/true); - } - } - } - - // Draw item infos if we're not zoomed out too much and there's something to draw - if ((level <= DRAW_SIMINFO_THRESHOLD) && (gSavedSettings.getBOOL("MapShowInfohubs") || - gSavedSettings.getBOOL("MapShowTelehubs") || - gSavedSettings.getBOOL("MapShowLandForSale") || - gSavedSettings.getBOOL("MapShowEvents") || - gSavedSettings.getBOOL("ShowMatureEvents") || - gSavedSettings.getBOOL("ShowAdultEvents"))) - { - drawItems(); - } - - // Draw the Home location (always) - LLVector3d home; - if (gAgent.getHomePosGlobal(&home)) - { - drawImage(home, sHomeImage); - } - - // Draw the current agent after all that other stuff. - LLVector3d pos_global = gAgent.getPositionGlobal(); - drawImage(pos_global, sAvatarYouImage); - - LLVector3 pos_map = globalPosToView(pos_global); - if (!pointInView(ll_round(pos_map.mV[VX]), ll_round(pos_map.mV[VY]))) - { - drawTracking(pos_global, - lerp(LLColor4::yellow, LLColor4::orange, 0.4f), - true, - "You are here", - "", - LLFontGL::getFontSansSerifSmall()->getLineHeight()); // offset vertically by one line, to avoid overlap with target tracking - } - - // Draw the current agent viewing angle - drawFrustum(); - - // Draw icons for the avatars in each region. - // Drawn this after the current agent avatar so one can see nearby people - if (gSavedSettings.getBOOL("MapShowPeople") && (level <= DRAW_SIMINFO_THRESHOLD)) - { - drawAgents(); - } - - // Always draw tracking information - LLTracker::ETrackingStatus tracking_status = LLTracker::getTrackingStatus(); - if ( LLTracker::TRACKING_AVATAR == tracking_status ) - { - drawTracking( LLAvatarTracker::instance().getGlobalPos(), map_track_color, true, LLTracker::getLabel(), "" ); - } - else if ( LLTracker::TRACKING_LANDMARK == tracking_status - || LLTracker::TRACKING_LOCATION == tracking_status ) - { - // While fetching landmarks, will have 0,0,0 location for a while, - // so don't draw. JC - LLVector3d pos_global = LLTracker::getTrackedPositionGlobal(); - if (!pos_global.isExactlyZero()) - { - drawTracking( pos_global, map_track_color, true, LLTracker::getLabel(), LLTracker::getToolTip() ); - } - } - else if (LLWorldMap::getInstance()->isTracking()) - { - if (LLWorldMap::getInstance()->isTrackingInvalidLocation()) - { - // We know this location to be invalid, draw a blue circle - LLColor4 loading_color(0.0, 0.5, 1.0, 1.0); - drawTracking( LLWorldMap::getInstance()->getTrackedPositionGlobal(), loading_color, true, getString("InvalidLocation"), ""); - } - else - { - // We don't know yet what that location is, draw a throbing blue circle - double value = fmod(current_time, 2); - value = 0.5 + 0.5*cos(value * F_PI); - LLColor4 loading_color(0.0, F32(value/2), F32(value), 1.0); - drawTracking( LLWorldMap::getInstance()->getTrackedPositionGlobal(), loading_color, true, getString("Loading"), ""); - } - } - - - // turn off the scissor - LLGLDisable no_scissor(GL_SCISSOR_TEST); - - updateDirections(); - - LLView::draw(); - - // Get sim info for all sims in view - updateVisibleBlocks(); -} // end draw() - - -//virtual -void LLWorldMapView::setVisible(bool visible) -{ - LLPanel::setVisible(visible); - if (!visible) - { - // Drop the download of tiles and images priority to nil if we hide the map - LLWorldMap::getInstance()->dropImagePriorities(); - } -} - -void LLWorldMapView::drawMipmap(S32 width, S32 height) -{ - // Compute the level of the mipmap to use for the current scale level - S32 level = LLWorldMipmap::scaleToLevel(mMapScale); - // Set the tile boost level so that unused tiles get to 0 - LLWorldMap::getInstance()->equalizeBoostLevels(); - - // Render whatever we already have loaded if we haven't the current level - // complete and use it as a background (scaled up or scaled down) - if (!sVisibleTilesLoaded) - { - // Note: the (load = false) parameter avoids missing tiles to be fetched (i.e. we render what we have, no more) - // Check all the lower res levels and render them in reverse order (worse to best) - // We need to traverse all the levels as the user can zoom in very fast - for (S32 l = LLWorldMipmap::MAP_LEVELS; l > level; l--) - { - drawMipmapLevel(width, height, l, false); - } - // Skip the current level, as we'll do it anyway here under... - - // Just go one level down in res as it can really get too much stuff - // when zooming out and too small to see anyway... - if (level > 1) - { - drawMipmapLevel(width, height, level - 1, false); - } - } - else - { - //LL_INFOS("WorldMap") << "Render complete, don't draw background..." << LL_ENDL; - } - - // Render the current level - sVisibleTilesLoaded = drawMipmapLevel(width, height, level); - - return; -} - -// Return true if all the tiles required to render that level have been fetched or are truly missing -bool LLWorldMapView::drawMipmapLevel(S32 width, S32 height, S32 level, bool load) -{ - // Check input level - llassert (level > 0); - if (level <= 0) - return false; - - // Count tiles hit and completed - S32 completed_tiles = 0; - S32 total_tiles = 0; - - // Size in meters (global) of each tile of that level - S32 tile_width = LLWorldMipmap::MAP_TILE_SIZE * (1 << (level - 1)); - // Dimension of the screen in meter at that scale - LLVector3d pos_SW = viewPosToGlobal(0, 0); - LLVector3d pos_NE = viewPosToGlobal(width, height); - // Add external band of tiles on the outskirt so to hit the partially displayed tiles right and top - pos_NE[VX] += tile_width; - pos_NE[VY] += tile_width; - - // Iterate through the tiles on screen: we just need to ask for one tile every tile_width meters - U32 grid_x, grid_y; - for (F64 index_y = pos_SW[VY]; index_y < pos_NE[VY]; index_y += tile_width) - { - for (F64 index_x = pos_SW[VX]; index_x < pos_NE[VX]; index_x += tile_width) - { - // Compute the world coordinates of the current point - LLVector3d pos_global(index_x, index_y, pos_SW[VZ]); - // Convert to the mipmap level coordinates for that point (i.e. which tile to we hit) - LLWorldMipmap::globalToMipmap(pos_global[VX], pos_global[VY], level, &grid_x, &grid_y); - // Get the tile. Note: NULL means that the image does not exist (so it's considered "complete" as far as fetching is concerned) - LLPointer simimage = LLWorldMap::getInstance()->getObjectsTile(grid_x, grid_y, level, load); - if (simimage) - { - // Checks that the image has a valid texture - if (simimage->hasGLTexture()) - { - // Increment the number of completly fetched tiles - completed_tiles++; - - // Convert those coordinates (SW corner of the mipmap tile) into world (meters) coordinates - pos_global[VX] = grid_x * REGION_WIDTH_METERS; - pos_global[VY] = grid_y * REGION_WIDTH_METERS; - // Now to screen coordinates for SW corner of that tile - LLVector3 pos_screen = globalPosToView (pos_global); - F32 left = pos_screen[VX]; - F32 bottom = pos_screen[VY]; - // Compute the NE corner coordinates of the tile now - pos_global[VX] += tile_width; - pos_global[VY] += tile_width; - pos_screen = globalPosToView (pos_global); - F32 right = pos_screen[VX]; - F32 top = pos_screen[VY]; - - // Draw the tile - LLGLSUIDefault gls_ui; - gGL.getTexUnit(0)->bind(simimage.get()); - simimage->setAddressMode(LLTexUnit::TAM_CLAMP); - - gGL.color4f(1.f, 1.0f, 1.0f, 1.0f); - - gGL.begin(LLRender::QUADS); - gGL.texCoord2f(0.f, 1.f); - gGL.vertex3f(left, top, 0.f); - gGL.texCoord2f(0.f, 0.f); - gGL.vertex3f(left, bottom, 0.f); - gGL.texCoord2f(1.f, 0.f); - gGL.vertex3f(right, bottom, 0.f); - gGL.texCoord2f(1.f, 1.f); - gGL.vertex3f(right, top, 0.f); - gGL.end(); -#if DEBUG_DRAW_TILE - drawTileOutline(level, top, left, bottom, right); -#endif // DEBUG_DRAW_TILE - } - //else - //{ - // Waiting for a tile -> the level is not complete - // LL_INFOS("WorldMap") << "Unfetched tile. level = " << level << LL_ENDL; - //} - } - else - { - // Unexistent tiles are counted as "completed" - completed_tiles++; - } - // Increment the number of tiles in that level / screen - total_tiles++; - } - } - return (completed_tiles == total_tiles); -} - -// Draw lines (rectangle outline and cross) to visualize the position of the tile -// Used for debug only -void LLWorldMapView::drawTileOutline(S32 level, F32 top, F32 left, F32 bottom, F32 right) -{ - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - if (level == 1) - gGL.color3f(1.f, 0.f, 0.f); // red - else if (level == 2) - gGL.color3f(0.f, 1.f, 0.f); // green - else if (level == 3) - gGL.color3f(0.f, 0.f, 1.f); // blue - else if (level == 4) - gGL.color3f(1.f, 1.f, 0.f); // yellow - else if (level == 5) - gGL.color3f(1.f, 0.f, 1.f); // magenta - else if (level == 6) - gGL.color3f(0.f, 1.f, 1.f); // cyan - else if (level == 7) - gGL.color3f(1.f, 1.f, 1.f); // white - else - gGL.color3f(0.f, 0.f, 0.f); // black - gGL.begin(LLRender::LINE_STRIP); - gGL.vertex2f(left, top); - gGL.vertex2f(right, bottom); - gGL.vertex2f(left, bottom); - gGL.vertex2f(right, top); - gGL.vertex2f(left, top); - gGL.vertex2f(left, bottom); - gGL.vertex2f(right, bottom); - gGL.vertex2f(right, top); - gGL.end(); -} - -void LLWorldMapView::drawGenericItems(const LLSimInfo::item_info_list_t& items, LLUIImagePtr image) -{ - LLSimInfo::item_info_list_t::const_iterator e; - for (e = items.begin(); e != items.end(); ++e) - { - drawGenericItem(*e, image); - } -} - -void LLWorldMapView::drawGenericItem(const LLItemInfo& item, LLUIImagePtr image) -{ - drawImage(item.getGlobalPosition(), image); -} - - -void LLWorldMapView::drawImage(const LLVector3d& global_pos, LLUIImagePtr image, const LLColor4& color) -{ - LLVector3 pos_map = globalPosToView( global_pos ); - image->draw(ll_round(pos_map.mV[VX] - image->getWidth() /2.f), - ll_round(pos_map.mV[VY] - image->getHeight()/2.f), - color); -} - -void LLWorldMapView::drawImageStack(const LLVector3d& global_pos, LLUIImagePtr image, U32 count, F32 offset, const LLColor4& color) -{ - LLVector3 pos_map = globalPosToView( global_pos ); - for(U32 i=0; idraw(ll_round(pos_map.mV[VX] - image->getWidth() /2.f), - ll_round(pos_map.mV[VY] - image->getHeight()/2.f + i*offset), - color); - } -} - -void LLWorldMapView::drawItems() -{ - bool mature_enabled = gAgent.canAccessMature(); - bool adult_enabled = gAgent.canAccessAdult(); - - bool show_mature = mature_enabled && gSavedSettings.getBOOL("ShowMatureEvents"); - bool show_adult = adult_enabled && gSavedSettings.getBOOL("ShowAdultEvents"); - - for (handle_list_t::iterator iter = mVisibleRegions.begin(); iter != mVisibleRegions.end(); ++iter) - { - U64 handle = *iter; - LLSimInfo* info = LLWorldMap::getInstance()->simInfoFromHandle(handle); - if ((info == NULL) || (info->isDown())) - { - continue; - } - // Infohubs - if (gSavedSettings.getBOOL("MapShowInfohubs")) - { - drawGenericItems(info->getInfoHub(), sInfohubImage); - } - // Telehubs - if (gSavedSettings.getBOOL("MapShowTelehubs")) - { - drawGenericItems(info->getTeleHub(), sTelehubImage); - } - // Land for sale - if (gSavedSettings.getBOOL("MapShowLandForSale")) - { - drawGenericItems(info->getLandForSale(), sForSaleImage); - // for 1.23, we're showing normal land and adult land in the same UI; you don't - // get a choice about which ones you want. If you're currently asking for adult - // content and land you'll get the adult land. - if (gAgent.canAccessAdult()) - { - drawGenericItems(info->getLandForSaleAdult(), sForSaleAdultImage); - } - } - // PG Events - if (gSavedSettings.getBOOL("MapShowEvents")) - { - drawGenericItems(info->getPGEvent(), sEventImage); - } - // Mature Events - if (show_mature) - { - drawGenericItems(info->getMatureEvent(), sEventMatureImage); - } - // Adult Events - if (show_adult) - { - drawGenericItems(info->getAdultEvent(), sEventAdultImage); - } - } -} - -void LLWorldMapView::drawAgents() -{ - static LLUIColor map_avatar_color = LLUIColorTable::instance().getColor("MapAvatarColor", LLColor4::white); - - for (handle_list_t::iterator iter = mVisibleRegions.begin(); iter != mVisibleRegions.end(); ++iter) - { - U64 handle = *iter; - LLSimInfo* siminfo = LLWorldMap::getInstance()->simInfoFromHandle(handle); - if ((siminfo == NULL) || (siminfo->isDown())) - { - continue; - } - LLSimInfo::item_info_list_t::const_iterator it = siminfo->getAgentLocation().begin(); - while (it != siminfo->getAgentLocation().end()) - { - // Show Individual agents (or little stacks where real agents are) - - // Here's how we'd choose the color if info.mID were available but it's not being sent: - // LLColor4 color = (agent_count == 1 && is_agent_friend(info.mID)) ? friend_color : avatar_color; - drawImageStack(it->getGlobalPosition(), sAvatarSmallImage, it->getCount(), 3.f, map_avatar_color); - ++it; - } - } -} - -void LLWorldMapView::drawFrustum() -{ - // Draw frustum - F32 meters_to_pixels = mMapScale/ REGION_WIDTH_METERS; - - F32 horiz_fov = LLViewerCamera::getInstance()->getView() * LLViewerCamera::getInstance()->getAspect(); - F32 far_clip_meters = LLViewerCamera::getInstance()->getFar(); - F32 far_clip_pixels = far_clip_meters * meters_to_pixels; - - F32 half_width_meters = far_clip_meters * tan( horiz_fov / 2 ); - F32 half_width_pixels = half_width_meters * meters_to_pixels; - - // Compute the frustum coordinates. Take the UI scale into account. - F32 ctr_x = ((getLocalRect().getWidth() * 0.5f + mPanX) * LLUI::getScaleFactor().mV[VX]); - F32 ctr_y = ((getLocalRect().getHeight() * 0.5f + mPanY) * LLUI::getScaleFactor().mV[VY]); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - // Since we don't rotate the map, we have to rotate the frustum. - gGL.pushMatrix(); - { - gGL.translatef( ctr_x, ctr_y, 0 ); - - // Draw triangle with more alpha in far pixels to make it - // fade out in distance. - gGL.begin( LLRender::TRIANGLES ); - { - // get camera look at and left axes - LLVector3 at_axis = LLViewerCamera::instance().getAtAxis(); - LLVector3 left_axis = LLViewerCamera::instance().getLeftAxis(); - - // grab components along XY plane - LLVector2 cam_lookat(at_axis.mV[VX], at_axis.mV[VY]); - LLVector2 cam_left(left_axis.mV[VX], left_axis.mV[VY]); - - // but, when looking near straight up or down... - if (is_approx_zero(cam_lookat.magVecSquared())) - { - //...just fall back to looking down the x axis - cam_lookat = LLVector2(1.f, 0.f); // x axis - cam_left = LLVector2(0.f, 1.f); // y axis - } - - // normalize to unit length - cam_lookat.normVec(); - cam_left.normVec(); - - gGL.color4f(1.f, 1.f, 1.f, 0.25f); - gGL.vertex2f( 0, 0 ); - - gGL.color4f(1.f, 1.f, 1.f, 0.02f); - - // use 2d camera vectors to render frustum triangle - LLVector2 vert = cam_lookat * far_clip_pixels + cam_left * half_width_pixels; - gGL.vertex2f(vert.mV[VX], vert.mV[VY]); - - vert = cam_lookat * far_clip_pixels - cam_left * half_width_pixels; - gGL.vertex2f(vert.mV[VX], vert.mV[VY]); - } - gGL.end(); - } - gGL.popMatrix(); -} - - -LLVector3 LLWorldMapView::globalPosToView( const LLVector3d& global_pos ) -{ - LLVector3d relative_pos_global = global_pos - gAgentCamera.getCameraPositionGlobal(); - LLVector3 pos_local; - pos_local.setVec(relative_pos_global); // convert to floats from doubles - - pos_local.mV[VX] *= mMapScale / REGION_WIDTH_METERS; - pos_local.mV[VY] *= mMapScale / REGION_WIDTH_METERS; - // leave Z component in meters - - - pos_local.mV[VX] += getRect().getWidth() / 2 + mPanX; - pos_local.mV[VY] += getRect().getHeight() / 2 + mPanY; - - return pos_local; -} - - -void LLWorldMapView::drawTracking(const LLVector3d& pos_global, const LLColor4& color, bool draw_arrow, - const std::string& label, const std::string& tooltip, S32 vert_offset ) -{ - LLVector3 pos_local = globalPosToView( pos_global ); - S32 x = ll_round( pos_local.mV[VX] ); - S32 y = ll_round( pos_local.mV[VY] ); - LLFontGL* font = LLFontGL::getFontSansSerifSmall(); - S32 text_x = x; - S32 text_y = (S32)(y - sTrackCircleImage->getHeight()/2 - font->getLineHeight()); - - if( x < 0 - || y < 0 - || x >= getRect().getWidth() - || y >= getRect().getHeight() ) - { - if (draw_arrow) - { - drawTrackingCircle( getRect(), x, y, color, 3, 15 ); - drawTrackingArrow( getRect(), x, y, color ); - text_x = sTrackingArrowX; - text_y = sTrackingArrowY; - } - } - else if (LLTracker::getTrackingStatus() == LLTracker::TRACKING_LOCATION && - LLTracker::getTrackedLocationType() != LLTracker::LOCATION_NOTHING) - { - drawTrackingCircle( getRect(), x, y, color, 3, 15 ); - } - else - { - drawImage(pos_global, sTrackCircleImage, color); - } - - // clamp text position to on-screen - const S32 TEXT_PADDING = DEFAULT_TRACKING_ARROW_SIZE + 2; - S32 half_text_width = llfloor(font->getWidthF32(label) * 0.5f); - text_x = llclamp(text_x, half_text_width + TEXT_PADDING, getRect().getWidth() - half_text_width - TEXT_PADDING); - text_y = llclamp(text_y + vert_offset, TEXT_PADDING + vert_offset, getRect().getHeight() - font->getLineHeight() - TEXT_PADDING - vert_offset); - - if (label != "") - { - font->renderUTF8( - label, 0, - text_x, - text_y, - LLColor4::white, LLFontGL::HCENTER, - LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::DROP_SHADOW); - - if (tooltip != "") - { - text_y -= font->getLineHeight(); - - font->renderUTF8( - tooltip, 0, - text_x, - text_y, - LLColor4::white, LLFontGL::HCENTER, - LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::DROP_SHADOW); - } - } -} - -// If you change this, then you need to change LLTracker::getTrackedPositionGlobal() as well -LLVector3d LLWorldMapView::viewPosToGlobal( S32 x, S32 y ) -{ - x -= llfloor((getRect().getWidth() / 2 + mPanX)); - y -= llfloor((getRect().getHeight() / 2 + mPanY)); - - LLVector3 pos_local( (F32)x, (F32)y, 0.f ); - - pos_local *= ( REGION_WIDTH_METERS / mMapScale ); - - LLVector3d pos_global; - pos_global.setVec( pos_local ); - pos_global += gAgentCamera.getCameraPositionGlobal(); - if(gAgent.isGodlike()) - { - pos_global.mdV[VZ] = GODLY_TELEPORT_HEIGHT; // Godly height should always be 200. - } - else - { - pos_global.mdV[VZ] = gAgent.getPositionAgent().mV[VZ]; // Want agent's height, not camera's - } - - return pos_global; -} - - -bool LLWorldMapView::handleToolTip( S32 x, S32 y, MASK mask ) -{ - LLVector3d pos_global = viewPosToGlobal(x, y); - U64 handle = to_region_handle(pos_global); - std::string tooltip_msg; - - LLSimInfo* info = LLWorldMap::getInstance()->simInfoFromHandle(handle); - if (info) - { - LLViewerRegion *region = gAgent.getRegion(); - - std::string message = llformat("%s (%s)", info->getName().c_str(), info->getAccessString().c_str()); - - if (!info->isDown()) - { - S32 agent_count = info->getAgentCount(); - if (region && (region->getHandle() == handle)) - { - ++agent_count; // Bump by 1 if we're here - } - - // We may not have an agent count when the map is really - // zoomed out, so don't display anything about the count. JC - if (agent_count > 0) - { - LLStringUtil::format_map_t string_args; - string_args["[NUMBER]"] = llformat("%d", agent_count); - message += '\n'; - message += getString((agent_count == 1 ? "world_map_person" : "world_map_people") , string_args); - } - } - tooltip_msg.assign( message ); - - // Optionally show region flags - std::string region_flags = info->getFlagsString(); - - if (!region_flags.empty()) - { - tooltip_msg += '\n'; - tooltip_msg += region_flags; - } - - const S32 SLOP = 9; - S32 screen_x, screen_y; - - localPointToScreen(x, y, &screen_x, &screen_y); - LLRect sticky_rect_screen; - sticky_rect_screen.setCenterAndSize(screen_x, screen_y, SLOP, SLOP); - - LLToolTipMgr::instance().show(LLToolTip::Params() - .message(tooltip_msg) - .sticky_rect(sticky_rect_screen)); - } - return true; -} - -// Pass relative Z of 0 to draw at same level. -// static -static void drawDot(F32 x_pixels, F32 y_pixels, - const LLColor4& color, - F32 relative_z, - F32 dot_radius, - LLUIImagePtr dot_image) -{ - const F32 HEIGHT_THRESHOLD = 7.f; - - if(-HEIGHT_THRESHOLD <= relative_z && relative_z <= HEIGHT_THRESHOLD) - { - dot_image->draw(ll_round(x_pixels) - dot_image->getWidth()/2, - ll_round(y_pixels) - dot_image->getHeight()/2, - color); - } - else - { - // Draw V indicator for above or below - // *TODO: Replace this vector drawing with icons - - F32 left = x_pixels - dot_radius; - F32 right = x_pixels + dot_radius; - F32 center = (left + right) * 0.5f; - F32 top = y_pixels + dot_radius; - F32 bottom = y_pixels - dot_radius; - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.color4fv( color.mV ); - LLUI::setLineWidth(3.0f); - F32 point = relative_z > HEIGHT_THRESHOLD ? top : bottom; // Y pos of the point of the V - F32 back = relative_z > HEIGHT_THRESHOLD ? bottom : top; // Y pos of the ends of the V - gGL.begin( LLRender::LINES ); - gGL.vertex2f(left, back); - gGL.vertex2f(center, point); - gGL.vertex2f(center, point); - gGL.vertex2f(right, back); - gGL.end(); - LLUI::setLineWidth(1.0f); - } -} - -// Pass relative Z of 0 to draw at same level. -// static -void LLWorldMapView::drawAvatar(F32 x_pixels, - F32 y_pixels, - const LLColor4& color, - F32 relative_z, - F32 dot_radius, - bool unknown_relative_z) -{ - const F32 HEIGHT_THRESHOLD = 7.f; - LLUIImagePtr dot_image = sAvatarLevelImage; - if (unknown_relative_z && llabs(relative_z) > HEIGHT_THRESHOLD) - { - dot_image = sAvatarUnknownImage; - } - else - { - if(relative_z < -HEIGHT_THRESHOLD) - { - dot_image = sAvatarBelowImage; - } - else if(relative_z > HEIGHT_THRESHOLD) - { - dot_image = sAvatarAboveImage; - } - } - S32 dot_width = ll_round(dot_radius * 2.f); - dot_image->draw(ll_round(x_pixels - dot_radius), - ll_round(y_pixels - dot_radius), - dot_width, - dot_width, - color); -} - -// Pass relative Z of 0 to draw at same level. -// static -void LLWorldMapView::drawTrackingDot( F32 x_pixels, - F32 y_pixels, - const LLColor4& color, - F32 relative_z, - F32 dot_radius) -{ - drawDot(x_pixels, y_pixels, color, relative_z, dot_radius, sTrackCircleImage); -} - - -// Pass relative Z of 0 to draw at same level. -// static -void LLWorldMapView::drawIconName(F32 x_pixels, - F32 y_pixels, - const LLColor4& color, - const std::string& first_line, - const std::string& second_line) -{ - const S32 VERT_PAD = 8; - S32 text_x = ll_round(x_pixels); - S32 text_y = ll_round(y_pixels - - BIG_DOT_RADIUS - - VERT_PAD); - - // render text - LLFontGL::getFontSansSerif()->renderUTF8(first_line, 0, - text_x, - text_y, - color, - LLFontGL::HCENTER, - LLFontGL::TOP, - LLFontGL::NORMAL, - LLFontGL::DROP_SHADOW); - - text_y -= LLFontGL::getFontSansSerif()->getLineHeight(); - - // render text - LLFontGL::getFontSansSerif()->renderUTF8(second_line, 0, - text_x, - text_y, - color, - LLFontGL::HCENTER, - LLFontGL::TOP, - LLFontGL::NORMAL, - LLFontGL::DROP_SHADOW); -} - - -//static -void LLWorldMapView::drawTrackingCircle( const LLRect& rect, S32 x, S32 y, const LLColor4& color, S32 min_thickness, S32 overlap ) -{ - F32 start_theta = 0.f; - F32 end_theta = F_TWO_PI; - F32 x_delta = 0.f; - F32 y_delta = 0.f; - - if (x < 0) - { - x_delta = 0.f - (F32)x; - start_theta = F_PI + F_PI_BY_TWO; - end_theta = F_TWO_PI + F_PI_BY_TWO; - } - else if (x > rect.getWidth()) - { - x_delta = (F32)(x - rect.getWidth()); - start_theta = F_PI_BY_TWO; - end_theta = F_PI + F_PI_BY_TWO; - } - - if (y < 0) - { - y_delta = 0.f - (F32)y; - if (x < 0) - { - start_theta = 0.f; - end_theta = F_PI_BY_TWO; - } - else if (x > rect.getWidth()) - { - start_theta = F_PI_BY_TWO; - end_theta = F_PI; - } - else - { - start_theta = 0.f; - end_theta = F_PI; - } - } - else if (y > rect.getHeight()) - { - y_delta = (F32)(y - rect.getHeight()); - if (x < 0) - { - start_theta = F_PI + F_PI_BY_TWO; - end_theta = F_TWO_PI; - } - else if (x > rect.getWidth()) - { - start_theta = F_PI; - end_theta = F_PI + F_PI_BY_TWO; - } - else - { - start_theta = F_PI; - end_theta = F_TWO_PI; - } - } - - F32 distance = sqrtf(x_delta * x_delta + y_delta * y_delta); - - distance = llmax(0.1f, distance); - - F32 outer_radius = distance + (1.f + (9.f * sqrtf(x_delta * y_delta) / distance)) * (F32)overlap; - F32 inner_radius = outer_radius - (F32)min_thickness; - - F32 angle_adjust_x = asin(x_delta / outer_radius); - F32 angle_adjust_y = asin(y_delta / outer_radius); - - if (angle_adjust_x) - { - if (angle_adjust_y) - { - F32 angle_adjust = llmin(angle_adjust_x, angle_adjust_y); - start_theta += angle_adjust; - end_theta -= angle_adjust; - } - else - { - start_theta += angle_adjust_x; - end_theta -= angle_adjust_x; - } - } - else if (angle_adjust_y) - { - start_theta += angle_adjust_y; - end_theta -= angle_adjust_y; - } - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - gGL.translatef((F32)x * LLUI::getScaleFactor().mV[VX], (F32)y * LLUI::getScaleFactor().mV[VY], 0.f); - gl_washer_segment_2d(inner_radius, outer_radius, start_theta, end_theta, 40, color, color); - gGL.popMatrix(); - -} - -// static -void LLWorldMapView::drawTrackingArrow(const LLRect& rect, S32 x, S32 y, - const LLColor4& color, - S32 arrow_size) -{ - F32 x_center = (F32)rect.getWidth() / 2.f; - F32 y_center = (F32)rect.getHeight() / 2.f; - - F32 x_clamped = (F32)llclamp( x, 0, rect.getWidth() - arrow_size ); - F32 y_clamped = (F32)llclamp( y, 0, rect.getHeight() - arrow_size ); - - F32 slope = (F32)(y - y_center) / (F32)(x - x_center); - F32 window_ratio = (F32)rect.getHeight() / (F32)rect.getWidth(); - - if (llabs(slope) > window_ratio && y_clamped != (F32)y) - { - // clamp by y - x_clamped = (y_clamped - y_center) / slope + x_center; - // adjust for arrow size - x_clamped = llclamp(x_clamped , 0.f, (F32)(rect.getWidth() - arrow_size) ); - } - else if (x_clamped != (F32)x) - { - // clamp by x - y_clamped = (x_clamped - x_center) * slope + y_center; - // adjust for arrow size - y_clamped = llclamp( y_clamped, 0.f, (F32)(rect.getHeight() - arrow_size) ); - } - - // *FIX: deal with non-square window properly. - // I do not understand what this comment means -- is it actually - // broken or is it correctly dealing with non-square - // windows. Phoenix 2007-01-03. - S32 half_arrow_size = (S32) (0.5f * arrow_size); - - F32 angle = atan2( y + half_arrow_size - y_center, x + half_arrow_size - x_center); - - sTrackingArrowX = llfloor(x_clamped); - sTrackingArrowY = llfloor(y_clamped); - - gl_draw_scaled_rotated_image( - sTrackingArrowX, - sTrackingArrowY, - arrow_size, arrow_size, - RAD_TO_DEG * angle, - sTrackArrowImage->getImage(), - color); -} - -void LLWorldMapView::setDirectionPos( LLTextBox* text_box, F32 rotation ) -{ - // Rotation is in radians. - // Rotation of 0 means x = 1, y = 0 on the unit circle. - - - F32 map_half_height = getRect().getHeight() * 0.5f; - F32 map_half_width = getRect().getWidth() * 0.5f; - F32 text_half_height = text_box->getRect().getHeight() * 0.5f; - F32 text_half_width = text_box->getRect().getWidth() * 0.5f; - F32 radius = llmin( map_half_height - text_half_height, map_half_width - text_half_width ); - - text_box->setOrigin( - ll_round(map_half_width - text_half_width + radius * cos( rotation )), - ll_round(map_half_height - text_half_height + radius * sin( rotation )) ); -} - - -void LLWorldMapView::updateDirections() -{ - S32 width = getRect().getWidth(); - S32 height = getRect().getHeight(); - - S32 text_height = mTextBoxNorth->getRect().getHeight(); - S32 text_width = mTextBoxNorth->getRect().getWidth(); - - const S32 PAD = 2; - S32 top = height - text_height - PAD; - S32 left = PAD*2; - S32 bottom = PAD; - S32 right = width - text_width - PAD; - S32 center_x = width/2 - text_width/2; - S32 center_y = height/2 - text_height/2; - - mTextBoxNorth->setOrigin( center_x, top ); - mTextBoxEast->setOrigin( right, center_y ); - mTextBoxSouth->setOrigin( center_x, bottom ); - mTextBoxWest->setOrigin( left, center_y ); - - // These have wider text boxes - text_width = mTextBoxNorthWest->getRect().getWidth(); - right = width - text_width - PAD; - - mTextBoxNorthWest->setOrigin(left, top); - mTextBoxNorthEast->setOrigin(right, top); - mTextBoxSouthWest->setOrigin(left, bottom); - mTextBoxSouthEast->setOrigin(right, bottom); - -// S32 hint_width = mTextBoxScrollHint->getRect().getWidth(); -// mTextBoxScrollHint->setOrigin( width - hint_width - text_width - 2 * PAD, -// PAD * 2 + text_height ); -} - - -void LLWorldMapView::reshape( S32 width, S32 height, bool called_from_parent ) -{ - LLView::reshape( width, height, called_from_parent ); -} - -bool LLWorldMapView::checkItemHit(S32 x, S32 y, LLItemInfo& item, LLUUID* id, bool track) -{ - LLVector3 pos_view = globalPosToView(item.getGlobalPosition()); - S32 item_x = ll_round(pos_view.mV[VX]); - S32 item_y = ll_round(pos_view.mV[VY]); - - if (x < item_x - BIG_DOT_RADIUS) return false; - if (x > item_x + BIG_DOT_RADIUS) return false; - if (y < item_y - BIG_DOT_RADIUS) return false; - if (y > item_y + BIG_DOT_RADIUS) return false; - - LLSimInfo* sim_info = LLWorldMap::getInstance()->simInfoFromHandle(item.getRegionHandle()); - if (sim_info) - { - if (track) - { - gFloaterWorldMap->trackLocation(item.getGlobalPosition()); - } - } - - if (track) - { - gFloaterWorldMap->trackGenericItem(item); - } - -// item.setSelected(true); - *id = item.getUUID(); - - return true; -} - -// Handle a click, which might be on a dot -void LLWorldMapView::handleClick(S32 x, S32 y, MASK mask, - S32* hit_type, - LLUUID* id) -{ - LLVector3d pos_global = viewPosToGlobal(x, y); - - // *HACK: Adjust Z values automatically for liaisons & gods so - // we swoop down when they click on the map. Sadly, the P2P - // branch does not pay attention to this value; however, the - // Distributed Messaging branch honors it. - if(gAgent.isGodlike()) - { - pos_global.mdV[VZ] = 200.0; - } - - *hit_type = 0; // hit nothing - - LLWorldMap::getInstance()->cancelTracking(); - - S32 level = LLWorldMipmap::scaleToLevel(mMapScale); - // If the zoom level is not too far out already, test hits - if (level <= DRAW_SIMINFO_THRESHOLD) - { - bool show_mature = gAgent.canAccessMature() && gSavedSettings.getBOOL("ShowMatureEvents"); - bool show_adult = gAgent.canAccessAdult() && gSavedSettings.getBOOL("ShowAdultEvents"); - - // Test hits if trackable data are displayed, otherwise, we don't even bother - if (gSavedSettings.getBOOL("MapShowEvents") || show_mature || show_adult || gSavedSettings.getBOOL("MapShowLandForSale")) - { - // Iterate through the visible regions - for (handle_list_t::iterator iter = mVisibleRegions.begin(); iter != mVisibleRegions.end(); ++iter) - { - U64 handle = *iter; - LLSimInfo* siminfo = LLWorldMap::getInstance()->simInfoFromHandle(handle); - if ((siminfo == NULL) || (siminfo->isDown())) - { - continue; - } - // If on screen check hits with the visible item lists - if (gSavedSettings.getBOOL("MapShowEvents")) - { - LLSimInfo::item_info_list_t::const_iterator it = siminfo->getPGEvent().begin(); - while (it != siminfo->getPGEvent().end()) - { - LLItemInfo event = *it; - if (checkItemHit(x, y, event, id, false)) - { - *hit_type = MAP_ITEM_PG_EVENT; - mItemPicked = true; - gFloaterWorldMap->trackEvent(event); - return; - } - ++it; - } - } - if (show_mature) - { - LLSimInfo::item_info_list_t::const_iterator it = siminfo->getMatureEvent().begin(); - while (it != siminfo->getMatureEvent().end()) - { - LLItemInfo event = *it; - if (checkItemHit(x, y, event, id, false)) - { - *hit_type = MAP_ITEM_MATURE_EVENT; - mItemPicked = true; - gFloaterWorldMap->trackEvent(event); - return; - } - ++it; - } - } - if (show_adult) - { - LLSimInfo::item_info_list_t::const_iterator it = siminfo->getAdultEvent().begin(); - while (it != siminfo->getAdultEvent().end()) - { - LLItemInfo event = *it; - if (checkItemHit(x, y, event, id, false)) - { - *hit_type = MAP_ITEM_ADULT_EVENT; - mItemPicked = true; - gFloaterWorldMap->trackEvent(event); - return; - } - ++it; - } - } - if (gSavedSettings.getBOOL("MapShowLandForSale")) - { - LLSimInfo::item_info_list_t::const_iterator it = siminfo->getLandForSale().begin(); - while (it != siminfo->getLandForSale().end()) - { - LLItemInfo event = *it; - if (checkItemHit(x, y, event, id, true)) - { - *hit_type = MAP_ITEM_LAND_FOR_SALE; - mItemPicked = true; - return; - } - ++it; - } - // for 1.23, we're showing normal land and adult land in the same UI; you don't - // get a choice about which ones you want. If you're currently asking for adult - // content and land you'll get the adult land. - if (gAgent.canAccessAdult()) - { - LLSimInfo::item_info_list_t::const_iterator it = siminfo->getLandForSaleAdult().begin(); - while (it != siminfo->getLandForSaleAdult().end()) - { - LLItemInfo event = *it; - if (checkItemHit(x, y, event, id, true)) - { - *hit_type = MAP_ITEM_LAND_FOR_SALE_ADULT; - mItemPicked = true; - return; - } - ++it; - } - } - } - } - } - } - - // If we get here, we haven't clicked on anything - gFloaterWorldMap->trackLocation(pos_global); - mItemPicked = false; - *id = LLUUID::null; - return; -} - - -bool LLWorldMapView::handleMouseDown( S32 x, S32 y, MASK mask ) -{ - gFocusMgr.setMouseCapture( this ); - - mMouseDownPanX = ll_round(mPanX); - mMouseDownPanY = ll_round(mPanY); - mMouseDownX = x; - mMouseDownY = y; - sHandledLastClick = true; - return true; -} - -bool LLWorldMapView::handleMouseUp( S32 x, S32 y, MASK mask ) -{ - if (hasMouseCapture()) - { - if (mPanning) - { - // restore mouse cursor - S32 local_x, local_y; - local_x = mMouseDownX + llfloor(mPanX - mMouseDownPanX); - local_y = mMouseDownY + llfloor(mPanY - mMouseDownPanY); - LLRect clip_rect = getRect(); - clip_rect.stretch(-8); - clip_rect.clipPointToRect(mMouseDownX, mMouseDownY, local_x, local_y); - LLUI::getInstance()->setMousePositionLocal(this, local_x, local_y); - - // finish the pan - mPanning = false; - - mMouseDownX = 0; - mMouseDownY = 0; - } - else - { - // ignore whether we hit an event or not - S32 hit_type; - LLUUID id; - handleClick(x, y, mask, &hit_type, &id); - } - gViewerWindow->showCursor(); - gFocusMgr.setMouseCapture( NULL ); - return true; - } - return false; -} - -void LLWorldMapView::updateVisibleBlocks() -{ - if (LLWorldMipmap::scaleToLevel(mMapScale) > DRAW_SIMINFO_THRESHOLD) - { - // If we're zoomed out too much, we just don't load all those sim info: too much! - return; - } - - // Load the blocks visible in the current World Map view - - // Get the World Map view coordinates and boundaries - LLVector3d camera_global = gAgentCamera.getCameraPositionGlobal(); - const S32 width = getRect().getWidth(); - const S32 height = getRect().getHeight(); - const F32 half_width = F32(width) / 2.0f; - const F32 half_height = F32(height) / 2.0f; - - // Compute center into sim grid coordinates - S32 world_center_x = S32((-mPanX / mMapScale) + (camera_global.mdV[0] / REGION_WIDTH_METERS)); - S32 world_center_y = S32((-mPanY / mMapScale) + (camera_global.mdV[1] / REGION_WIDTH_METERS)); - - // Compute the boundaries into sim grid coordinates - S32 world_left = world_center_x - S32(half_width / mMapScale) - 1; - S32 world_right = world_center_x + S32(half_width / mMapScale) + 1; - S32 world_bottom = world_center_y - S32(half_height / mMapScale) - 1; - S32 world_top = world_center_y + S32(half_height / mMapScale) + 1; - - //LL_INFOS("WorldMap") << "LLWorldMapView::updateVisibleBlocks() : mMapScale = " << mMapScale << ", left = " << world_left << ", right = " << world_right << ", bottom = " << world_bottom << ", top = " << world_top << LL_ENDL; - LLWorldMap::getInstance()->updateRegions(world_left, world_bottom, world_right, world_top); -} - -bool LLWorldMapView::handleHover( S32 x, S32 y, MASK mask ) -{ - if (hasMouseCapture()) - { - if (mPanning || llabs(x - mMouseDownX) > 1 || llabs(y - mMouseDownY) > 1) - { - // just started panning, so hide cursor - if (!mPanning) - { - mPanning = true; - gViewerWindow->hideCursor(); - } - - F32 delta_x = (F32)(gViewerWindow->getCurrentMouseDX()); - F32 delta_y = (F32)(gViewerWindow->getCurrentMouseDY()); - - // Set pan to value at start of drag + offset - mPanX += delta_x; - mPanY += delta_y; - mTargetPanX = mPanX; - mTargetPanY = mPanY; - - gViewerWindow->moveCursorToCenter(); - } - - // doesn't matter, cursor should be hidden - gViewerWindow->setCursor(UI_CURSOR_CROSS ); - return true; - } - else - { - // While we're waiting for data from the tracker, we're busy. JC - LLVector3d pos_global = LLTracker::getTrackedPositionGlobal(); - if (LLTracker::isTracking(NULL) - && pos_global.isExactlyZero()) - { - gViewerWindow->setCursor( UI_CURSOR_WAIT ); - } - else - { - gViewerWindow->setCursor( UI_CURSOR_CROSS ); - } - LL_DEBUGS("UserInput") << "hover handled by LLWorldMapView" << LL_ENDL; - return true; - } -} - - -bool LLWorldMapView::handleDoubleClick( S32 x, S32 y, MASK mask ) -{ - if( sHandledLastClick ) - { - S32 hit_type; - LLUUID id; - handleClick(x, y, mask, &hit_type, &id); - - switch (hit_type) - { - case MAP_ITEM_PG_EVENT: - case MAP_ITEM_MATURE_EVENT: - case MAP_ITEM_ADULT_EVENT: - { - LLFloaterReg::hideInstance("world_map"); - // This is an ungainly hack - std::string uuid_str; - S32 event_id; - id.toString(uuid_str); - uuid_str = uuid_str.substr(28); - sscanf(uuid_str.c_str(), "%X", &event_id); - // Invoke the event details floater if someone is clicking on an event. - LLSD params(LLSD::emptyArray()); - params.append(event_id); - LLCommandDispatcher::dispatch("event", params, LLSD(), LLGridManager::getInstance()->getGrid(), NULL, LLCommandHandler::NAV_TYPE_CLICKED, true); - break; - } - case MAP_ITEM_LAND_FOR_SALE: - case MAP_ITEM_LAND_FOR_SALE_ADULT: - { - LLVector3d pos_global = viewPosToGlobal(x, y); - std::string sim_name; - if (LLWorldMap::getInstance()->simNameFromPosGlobal(pos_global, sim_name)) - { - LLFloaterReg::hideInstance("world_map"); - LLFloaterReg::showInstance("search", LLSD().with("category", "land").with("query", sim_name)); - } - break; - } - case MAP_ITEM_CLASSIFIED: - { - LLFloaterReg::hideInstance("world_map"); - LLFloaterReg::showInstance("search", LLSD().with("category", "classifieds").with("query", id)); - break; - } - default: - { - if (LLWorldMap::getInstance()->isTracking()) - { - LLWorldMap::getInstance()->setTrackingDoubleClick(); - } - else - { - // Teleport if we got a valid location - LLVector3d pos_global = viewPosToGlobal(x,y); - LLSimInfo* sim_info = LLWorldMap::getInstance()->simInfoFromPosGlobal(pos_global); - if (sim_info && !sim_info->isDown()) - { - gAgent.teleportViaLocation( pos_global ); - } - } - } - }; - - return true; - } - return false; -} - -// static -F32 LLWorldMapView::scaleFromZoom(F32 zoom) { return exp2(zoom) * 256.0f; } - -// static -F32 LLWorldMapView::zoomFromScale(F32 scale) { return log2(scale / 256.f); } +/** + * @file llworldmapview.cpp + * @brief LLWorldMapView class implementation + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llworldmapview.h" + +#include "indra_constants.h" +#include "llui.h" +#include "llmath.h" // clampf() +#include "llregionhandle.h" +#include "lleventflags.h" +#include "llfloaterreg.h" +#include "llrender.h" +#include "lltooltip.h" + +#include "llagent.h" +#include "llagentcamera.h" +#include "llcallingcard.h" +#include "llcommandhandler.h" +#include "llviewercontrol.h" +#include "llfloatermap.h" +#include "llfloaterworldmap.h" +#include "llfocusmgr.h" +#include "lllocalcliprect.h" +#include "lltextbox.h" +#include "lltextureview.h" +#include "lltracker.h" +#include "llviewercamera.h" +#include "llviewernetwork.h" +#include "llviewertexture.h" +#include "llviewertexturelist.h" +#include "llviewerregion.h" +#include "llviewerwindow.h" +#include "lltrans.h" + +#include "llglheaders.h" + +// # Constants +static constexpr F32 MAP_DEFAULT_SCALE = 128.f; +static constexpr F32 MAP_ITERP_TIME_CONSTANT = 0.75f; +static constexpr F32 MAP_ZOOM_ACCELERATION_TIME = 0.3f; +static constexpr F32 MAP_ZOOM_MAX_INTERP = 0.5f; +static constexpr F32 MAP_SCALE_SNAP_THRESHOLD = 0.005f; + +// Basically a C++ implementation of the OCEAN_COLOR defined in mapstitcher.py +// Please ensure consistency between those 2 files (TODO: would be better to get that color from an asset source...) +// OCEAN_COLOR = "#1D475F" +constexpr F32 OCEAN_RED = (F32)(0x1D)/255.f; +constexpr F32 OCEAN_GREEN = (F32)(0x47)/255.f; +constexpr F32 OCEAN_BLUE = (F32)(0x5F)/255.f; + +constexpr F32 GODLY_TELEPORT_HEIGHT = 200.f; +constexpr F32 BIG_DOT_RADIUS = 5.f; +bool LLWorldMapView::sHandledLastClick = false; + +LLUIImagePtr LLWorldMapView::sAvatarSmallImage = NULL; +LLUIImagePtr LLWorldMapView::sAvatarYouImage = NULL; +LLUIImagePtr LLWorldMapView::sAvatarYouLargeImage = NULL; +LLUIImagePtr LLWorldMapView::sAvatarLevelImage = NULL; +LLUIImagePtr LLWorldMapView::sAvatarAboveImage = NULL; +LLUIImagePtr LLWorldMapView::sAvatarBelowImage = NULL; +LLUIImagePtr LLWorldMapView::sAvatarUnknownImage = NULL; + +LLUIImagePtr LLWorldMapView::sTelehubImage = NULL; +LLUIImagePtr LLWorldMapView::sInfohubImage = NULL; +LLUIImagePtr LLWorldMapView::sHomeImage = NULL; +LLUIImagePtr LLWorldMapView::sEventImage = NULL; +LLUIImagePtr LLWorldMapView::sEventMatureImage = NULL; +LLUIImagePtr LLWorldMapView::sEventAdultImage = NULL; + +LLUIImagePtr LLWorldMapView::sTrackCircleImage = NULL; +LLUIImagePtr LLWorldMapView::sTrackArrowImage = NULL; + +LLUIImagePtr LLWorldMapView::sClassifiedsImage = NULL; +LLUIImagePtr LLWorldMapView::sForSaleImage = NULL; +LLUIImagePtr LLWorldMapView::sForSaleAdultImage = NULL; + +S32 LLWorldMapView::sTrackingArrowX = 0; +S32 LLWorldMapView::sTrackingArrowY = 0; +bool LLWorldMapView::sVisibleTilesLoaded = false; +F32 LLWorldMapView::sMapScaleSetting = MAP_DEFAULT_SCALE; +LLVector2 LLWorldMapView::sZoomPivot = LLVector2(0.0f, 0.0f); +LLFrameTimer LLWorldMapView::sZoomTimer = LLFrameTimer(); + +std::map LLWorldMapView::sStringsMap; + +// Fetch and draw info thresholds +const F32 DRAW_TEXT_THRESHOLD = 96.f; // Don't draw text under that resolution value (res = width region in meters) +const S32 DRAW_SIMINFO_THRESHOLD = 3; // Max level for which we load or display sim level information (level in LLWorldMipmap sense) +const S32 DRAW_LANDFORSALE_THRESHOLD = 2; // Max level for which we load or display land for sale picture data (level in LLWorldMipmap sense) + +// When on, draw an outline for each mipmap tile gotten from S3 +#define DEBUG_DRAW_TILE 0 + + +void LLWorldMapView::initClass() +{ + sAvatarSmallImage = LLUI::getUIImage("map_avatar_8.tga"); + sAvatarYouImage = LLUI::getUIImage("map_avatar_16.tga"); + sAvatarYouLargeImage = LLUI::getUIImage("map_avatar_you_32.tga"); + sAvatarLevelImage = LLUI::getUIImage("map_avatar_32.tga"); + sAvatarAboveImage = LLUI::getUIImage("map_avatar_above_32.tga"); + sAvatarBelowImage = LLUI::getUIImage("map_avatar_below_32.tga"); + sAvatarUnknownImage = LLUI::getUIImage("map_avatar_unknown_32.tga"); + + sHomeImage = LLUI::getUIImage("map_home.tga"); + sTelehubImage = LLUI::getUIImage("map_telehub.tga"); + sInfohubImage = LLUI::getUIImage("map_infohub.tga"); + sEventImage = LLUI::getUIImage("Parcel_PG_Dark"); + sEventMatureImage = LLUI::getUIImage("Parcel_M_Dark"); + // To Do: update the image resource for adult events. + sEventAdultImage = LLUI::getUIImage("Parcel_R_Dark"); + + sTrackCircleImage = LLUI::getUIImage("map_track_16.tga"); + sTrackArrowImage = LLUI::getUIImage("direction_arrow.tga"); + sClassifiedsImage = LLUI::getUIImage("icon_top_pick.tga"); + sForSaleImage = LLUI::getUIImage("icon_for_sale.tga"); + // To Do: update the image resource for adult lands on sale. + sForSaleAdultImage = LLUI::getUIImage("icon_for_sale_adult.tga"); + + sStringsMap["loading"] = LLTrans::getString("texture_loading"); + sStringsMap["offline"] = LLTrans::getString("worldmap_offline"); +} + +// static +void LLWorldMapView::cleanupClass() +{ + sAvatarSmallImage = NULL; + sAvatarYouImage = NULL; + sAvatarYouLargeImage = NULL; + sAvatarLevelImage = NULL; + sAvatarAboveImage = NULL; + sAvatarBelowImage = NULL; + sAvatarUnknownImage = NULL; + + sTelehubImage = NULL; + sInfohubImage = NULL; + sHomeImage = NULL; + sEventImage = NULL; + sEventMatureImage = NULL; + sEventAdultImage = NULL; + + sTrackCircleImage = NULL; + sTrackArrowImage = NULL; + sClassifiedsImage = NULL; + sForSaleImage = NULL; + sForSaleAdultImage = NULL; +} + +LLWorldMapView::LLWorldMapView() : + LLPanel(), + mBackgroundColor(LLColor4(OCEAN_RED, OCEAN_GREEN, OCEAN_BLUE, 1.f)), + mItemPicked(false), + mPanX(0.f), + mPanY(0.f), + mTargetPanX(0.f), + mTargetPanY(0.f), + mPanning(false), + mMouseDownPanX(0), + mMouseDownPanY(0), + mMouseDownX(0), + mMouseDownY(0), + mSelectIDStart(0), + mMapScale(0.f), + mTargetMapScale(0.f), + mMapIterpTime(MAP_ITERP_TIME_CONSTANT) +{ + // LL_INFOS("WorldMap") << "Creating the Map -> LLWorldMapView::LLWorldMapView()" << LL_ENDL; + + clearLastClick(); +} + +bool LLWorldMapView::postBuild() +{ + mTextBoxNorth = getChild ("floater_map_north"); + mTextBoxEast = getChild ("floater_map_east"); + mTextBoxWest = getChild ("floater_map_west"); + mTextBoxSouth = getChild ("floater_map_south"); + mTextBoxSouthEast = getChild ("floater_map_southeast"); + mTextBoxNorthEast = getChild ("floater_map_northeast"); + mTextBoxSouthWest = getChild ("floater_map_southwest"); + mTextBoxNorthWest = getChild ("floater_map_northwest"); + + mTextBoxNorth->setText(getString("world_map_north")); + mTextBoxEast->setText(getString ("world_map_east")); + mTextBoxWest->setText(getString("world_map_west")); + mTextBoxSouth->setText(getString ("world_map_south")); + mTextBoxSouthEast ->setText(getString ("world_map_southeast")); + mTextBoxNorthEast ->setText(getString ("world_map_northeast")); + mTextBoxSouthWest->setText(getString ("world_map_southwest")); + mTextBoxNorthWest ->setText(getString("world_map_northwest")); + + mTextBoxNorth->reshapeToFitText(); + mTextBoxEast->reshapeToFitText(); + mTextBoxWest->reshapeToFitText(); + mTextBoxSouth->reshapeToFitText(); + mTextBoxSouthEast ->reshapeToFitText(); + mTextBoxNorthEast ->reshapeToFitText(); + mTextBoxSouthWest->reshapeToFitText(); + mTextBoxNorthWest ->reshapeToFitText(); + + sZoomTimer.stop(); + setScale(sMapScaleSetting, true); + + return true; +} + + +LLWorldMapView::~LLWorldMapView() +{ + //LL_INFOS("WorldMap") << "Destroying the map -> LLWorldMapView::~LLWorldMapView()" << LL_ENDL; + cleanupTextures(); +} + + +// static +void LLWorldMapView::cleanupTextures() +{ +} + +void LLWorldMapView::zoom(F32 zoom) +{ + mTargetMapScale = scaleFromZoom(zoom); + if (!sZoomTimer.getStarted() && mMapScale != mTargetMapScale) + { + sZoomPivot = LLVector2(0, 0); + sZoomTimer.start(); + } +} + +void LLWorldMapView::zoomWithPivot(F32 zoom, S32 x, S32 y) +{ + mTargetMapScale = scaleFromZoom(zoom); + sZoomPivot = LLVector2(x, y); + if (!sZoomTimer.getStarted() && mMapScale != mTargetMapScale) + { + sZoomTimer.start(); + } +} + +F32 LLWorldMapView::getZoom() { return LLWorldMapView::zoomFromScale(mMapScale); } + +F32 LLWorldMapView::getScale() { return mMapScale; } + +// static +void LLWorldMapView::setScaleSetting(F32 scaleSetting) { sMapScaleSetting = scaleSetting; } + +// static +F32 LLWorldMapView::getScaleSetting() { return sMapScaleSetting; } + +void LLWorldMapView::setScale(F32 scale, bool snap) +{ + if (scale != mMapScale) + { + F32 old_scale = mMapScale; + + mMapScale = scale; + // Set the scale used when saving the setting + sMapScaleSetting = scale; + if (mMapScale <= 0.f) + { + mMapScale = 0.1f; + } + mMapIterpTime = MAP_ITERP_TIME_CONSTANT; + F32 ratio = (scale / old_scale); + mPanX *= ratio; + mPanY *= ratio; + mTargetPanX = mPanX; + mTargetPanY = mPanY; + sVisibleTilesLoaded = false; + + // If we are zooming relative to somewhere else rather than the center of the map, compensate for the difference in panning here + if (!sZoomPivot.isExactlyZero()) + { + LLVector2 relative_pivot; + relative_pivot.mV[VX] = sZoomPivot.mV[VX] - (getRect().getWidth() / 2.0); + relative_pivot.mV[VY] = sZoomPivot.mV[VY] - (getRect().getHeight() / 2.0); + LLVector2 zoom_pan_offset = relative_pivot - (relative_pivot * scale / old_scale); + mPanX += zoom_pan_offset.mV[VX]; + mPanY += zoom_pan_offset.mV[VY]; + mTargetPanX += zoom_pan_offset.mV[VX]; + mTargetPanY += zoom_pan_offset.mV[VY]; + } + } + + if (snap) + { + mTargetMapScale = scale; + } +} + +// static +void LLWorldMapView::translatePan(S32 delta_x, S32 delta_y) +{ + mPanX += delta_x; + mPanY += delta_y; + mTargetPanX = mPanX; + mTargetPanY = mPanY; + sVisibleTilesLoaded = false; +} + + +// static +void LLWorldMapView::setPan(S32 x, S32 y, bool snap) +{ + mMapIterpTime = MAP_ITERP_TIME_CONSTANT; + mTargetPanX = (F32) x; + mTargetPanY = (F32) y; + if (snap) + { + mPanX = mTargetPanX; + mPanY = mTargetPanY; + } + sVisibleTilesLoaded = false; +} + +// static +void LLWorldMapView::setPanWithInterpTime(S32 x, S32 y, bool snap, F32 interp_time) +{ + setPan(x, y, snap); + mMapIterpTime = interp_time; +} + +bool LLWorldMapView::showRegionInfo() { return LLWorldMipmap::scaleToLevel(mMapScale) <= DRAW_SIMINFO_THRESHOLD; } + +/////////////////////////////////////////////////////////////////////////////////// +// HELPERS + +bool is_agent_in_region(LLViewerRegion* region, LLSimInfo* info) +{ + return (region && info && info->isName(region->getName())); +} + +/////////////////////////////////////////////////////////////////////////////////// + +void LLWorldMapView::draw() +{ + static LLUIColor map_track_color = LLUIColorTable::instance().getColor("MapTrackColor", LLColor4::white); + + LLTextureView::clearDebugImages(); + + F64 current_time = LLTimer::getElapsedSeconds(); + + mVisibleRegions.clear(); + + // animate pan if necessary + mPanX = lerp(mPanX, mTargetPanX, LLSmoothInterpolation::getInterpolant(mMapIterpTime)); + mPanY = lerp(mPanY, mTargetPanY, LLSmoothInterpolation::getInterpolant(mMapIterpTime)); + + //RN: snaps to zoom value because interpolation caused jitter in the text rendering + if (!sZoomTimer.getStarted() && mMapScale != mTargetMapScale) + { + sZoomTimer.start(); + } + bool snap_scale = false; + F32 interp = llmin(MAP_ZOOM_MAX_INTERP, sZoomTimer.getElapsedTimeF32() / MAP_ZOOM_ACCELERATION_TIME); + F32 current_zoom_val = zoomFromScale(mMapScale); + F32 target_zoom_val = zoomFromScale(mTargetMapScale); + F32 new_zoom_val = lerp(current_zoom_val, target_zoom_val, interp); + if (abs(new_zoom_val - current_zoom_val) < MAP_SCALE_SNAP_THRESHOLD) + { + sZoomTimer.stop(); + snap_scale = true; + new_zoom_val = target_zoom_val; + } + F32 map_scale = scaleFromZoom(new_zoom_val); + setScale(map_scale, snap_scale); + + const S32 width = getRect().getWidth(); + const S32 height = getRect().getHeight(); + const F32 half_width = F32(width) / 2.0f; + const F32 half_height = F32(height) / 2.0f; + LLVector3d camera_global = gAgentCamera.getCameraPositionGlobal(); + + S32 level = LLWorldMipmap::scaleToLevel(mMapScale); + + LLLocalClipRect clip(getLocalRect()); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + + // Draw background rectangle + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gGL.color4fv(mBackgroundColor.mV); + gl_rect_2d(0, height, width, 0); + + // Draw the image tiles + drawMipmap(width, height); + + // Draw per sim overlayed information (names, mature, offline...) + for (LLWorldMap::sim_info_map_t::const_iterator it = LLWorldMap::getInstance()->getRegionMap().begin(); + it != LLWorldMap::getInstance()->getRegionMap().end(); ++it) + { + U64 handle = it->first; + LLSimInfo* info = it->second; + + LLVector3d origin_global = from_region_handle(handle); + + // Find x and y position relative to camera's center. + LLVector3d rel_region_pos = origin_global - camera_global; + F32 relative_x = (rel_region_pos.mdV[0] / REGION_WIDTH_METERS) * mMapScale; + F32 relative_y = (rel_region_pos.mdV[1] / REGION_WIDTH_METERS) * mMapScale; + + // Coordinates of the sim in pixels in the UI panel + // When the view isn't panned, 0,0 = center of rectangle + F32 bottom = mPanY + half_height + relative_y; + F32 left = mPanX + half_width + relative_x; + F32 top = bottom + mMapScale ; + F32 right = left + mMapScale ; + + // Discard if region is outside the screen rectangle (not visible on screen) + if ((top < 0.f) || (bottom > height) || + (right < 0.f) || (left > width) ) + { + // Drop the "land for sale" fetching priority since it's outside the view rectangle + info->dropImagePriority(); + continue; + } + + // This list is used by other methods to know which regions are indeed displayed on screen + + mVisibleRegions.push_back(handle); + + // Update the agent count for that region if we're not too zoomed out already + if (level <= DRAW_SIMINFO_THRESHOLD) + { + info->updateAgentCount(current_time); + } + + if (info->isDown()) + { + // Draw a transparent red square over down sims + gGL.color4f(0.2f, 0.0f, 0.0f, 0.4f); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gGL.begin(LLRender::QUADS); + gGL.vertex2f(left, top); + gGL.vertex2f(left, bottom); + gGL.vertex2f(right, bottom); + gGL.vertex2f(right, top); + gGL.end(); + } + else if (gSavedSettings.getBOOL("MapShowLandForSale") && (level <= DRAW_LANDFORSALE_THRESHOLD)) + { + // Draw the overlay image "Land for Sale / Land for Auction" + LLViewerFetchedTexture* overlayimage = info->getLandForSaleImage(); + if (overlayimage) + { + // Inform the fetch mechanism of the size we need + S32 draw_size = ll_round(mMapScale); + overlayimage->setKnownDrawSize(ll_round(draw_size * LLUI::getScaleFactor().mV[VX]), ll_round(draw_size * LLUI::getScaleFactor().mV[VY])); + // Draw something whenever we have enough info + if (overlayimage->hasGLTexture()) + { + gGL.getTexUnit(0)->bind(overlayimage); + gGL.color4f(1.f, 1.f, 1.f, 1.f); + gGL.begin(LLRender::QUADS); + gGL.texCoord2f(0.f, 1.f); + gGL.vertex3f(left, top, -0.5f); + gGL.texCoord2f(0.f, 0.f); + gGL.vertex3f(left, bottom, -0.5f); + gGL.texCoord2f(1.f, 0.f); + gGL.vertex3f(right, bottom, -0.5f); + gGL.texCoord2f(1.f, 1.f); + gGL.vertex3f(right, top, -0.5f); + gGL.end(); + } + } + } + else + { + // If we're not displaying the "land for sale", drop its fetching priority + info->dropImagePriority(); + } + + // Draw the region name in the lower left corner + if (mMapScale >= DRAW_TEXT_THRESHOLD) + { + LLFontGL* font = LLFontGL::getFont(LLFontDescriptor("SansSerif", "Small", LLFontGL::BOLD)); + std::string mesg; + if (info->isDown()) + { + mesg = llformat( "%s (%s)", info->getName().c_str(), sStringsMap["offline"].c_str()); + } + else + { + mesg = info->getName(); + } + if (!mesg.empty()) + { + font->renderUTF8( + mesg, 0, + llfloor(left + 3), llfloor(bottom + 2), + LLColor4::white, + LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::DROP_SHADOW, + S32_MAX, //max_chars + mMapScale, //max_pixels + NULL, + /*use_ellipses*/true); + } + } + } + + // Draw item infos if we're not zoomed out too much and there's something to draw + if ((level <= DRAW_SIMINFO_THRESHOLD) && (gSavedSettings.getBOOL("MapShowInfohubs") || + gSavedSettings.getBOOL("MapShowTelehubs") || + gSavedSettings.getBOOL("MapShowLandForSale") || + gSavedSettings.getBOOL("MapShowEvents") || + gSavedSettings.getBOOL("ShowMatureEvents") || + gSavedSettings.getBOOL("ShowAdultEvents"))) + { + drawItems(); + } + + // Draw the Home location (always) + LLVector3d home; + if (gAgent.getHomePosGlobal(&home)) + { + drawImage(home, sHomeImage); + } + + // Draw the current agent after all that other stuff. + LLVector3d pos_global = gAgent.getPositionGlobal(); + drawImage(pos_global, sAvatarYouImage); + + LLVector3 pos_map = globalPosToView(pos_global); + if (!pointInView(ll_round(pos_map.mV[VX]), ll_round(pos_map.mV[VY]))) + { + drawTracking(pos_global, + lerp(LLColor4::yellow, LLColor4::orange, 0.4f), + true, + "You are here", + "", + LLFontGL::getFontSansSerifSmall()->getLineHeight()); // offset vertically by one line, to avoid overlap with target tracking + } + + // Draw the current agent viewing angle + drawFrustum(); + + // Draw icons for the avatars in each region. + // Drawn this after the current agent avatar so one can see nearby people + if (gSavedSettings.getBOOL("MapShowPeople") && (level <= DRAW_SIMINFO_THRESHOLD)) + { + drawAgents(); + } + + // Always draw tracking information + LLTracker::ETrackingStatus tracking_status = LLTracker::getTrackingStatus(); + if ( LLTracker::TRACKING_AVATAR == tracking_status ) + { + drawTracking( LLAvatarTracker::instance().getGlobalPos(), map_track_color, true, LLTracker::getLabel(), "" ); + } + else if ( LLTracker::TRACKING_LANDMARK == tracking_status + || LLTracker::TRACKING_LOCATION == tracking_status ) + { + // While fetching landmarks, will have 0,0,0 location for a while, + // so don't draw. JC + LLVector3d pos_global = LLTracker::getTrackedPositionGlobal(); + if (!pos_global.isExactlyZero()) + { + drawTracking( pos_global, map_track_color, true, LLTracker::getLabel(), LLTracker::getToolTip() ); + } + } + else if (LLWorldMap::getInstance()->isTracking()) + { + if (LLWorldMap::getInstance()->isTrackingInvalidLocation()) + { + // We know this location to be invalid, draw a blue circle + LLColor4 loading_color(0.0, 0.5, 1.0, 1.0); + drawTracking( LLWorldMap::getInstance()->getTrackedPositionGlobal(), loading_color, true, getString("InvalidLocation"), ""); + } + else + { + // We don't know yet what that location is, draw a throbing blue circle + double value = fmod(current_time, 2); + value = 0.5 + 0.5*cos(value * F_PI); + LLColor4 loading_color(0.0, F32(value/2), F32(value), 1.0); + drawTracking( LLWorldMap::getInstance()->getTrackedPositionGlobal(), loading_color, true, getString("Loading"), ""); + } + } + + + // turn off the scissor + LLGLDisable no_scissor(GL_SCISSOR_TEST); + + updateDirections(); + + LLView::draw(); + + // Get sim info for all sims in view + updateVisibleBlocks(); +} // end draw() + + +//virtual +void LLWorldMapView::setVisible(bool visible) +{ + LLPanel::setVisible(visible); + if (!visible) + { + // Drop the download of tiles and images priority to nil if we hide the map + LLWorldMap::getInstance()->dropImagePriorities(); + } +} + +void LLWorldMapView::drawMipmap(S32 width, S32 height) +{ + // Compute the level of the mipmap to use for the current scale level + S32 level = LLWorldMipmap::scaleToLevel(mMapScale); + // Set the tile boost level so that unused tiles get to 0 + LLWorldMap::getInstance()->equalizeBoostLevels(); + + // Render whatever we already have loaded if we haven't the current level + // complete and use it as a background (scaled up or scaled down) + if (!sVisibleTilesLoaded) + { + // Note: the (load = false) parameter avoids missing tiles to be fetched (i.e. we render what we have, no more) + // Check all the lower res levels and render them in reverse order (worse to best) + // We need to traverse all the levels as the user can zoom in very fast + for (S32 l = LLWorldMipmap::MAP_LEVELS; l > level; l--) + { + drawMipmapLevel(width, height, l, false); + } + // Skip the current level, as we'll do it anyway here under... + + // Just go one level down in res as it can really get too much stuff + // when zooming out and too small to see anyway... + if (level > 1) + { + drawMipmapLevel(width, height, level - 1, false); + } + } + else + { + //LL_INFOS("WorldMap") << "Render complete, don't draw background..." << LL_ENDL; + } + + // Render the current level + sVisibleTilesLoaded = drawMipmapLevel(width, height, level); + + return; +} + +// Return true if all the tiles required to render that level have been fetched or are truly missing +bool LLWorldMapView::drawMipmapLevel(S32 width, S32 height, S32 level, bool load) +{ + // Check input level + llassert (level > 0); + if (level <= 0) + return false; + + // Count tiles hit and completed + S32 completed_tiles = 0; + S32 total_tiles = 0; + + // Size in meters (global) of each tile of that level + S32 tile_width = LLWorldMipmap::MAP_TILE_SIZE * (1 << (level - 1)); + // Dimension of the screen in meter at that scale + LLVector3d pos_SW = viewPosToGlobal(0, 0); + LLVector3d pos_NE = viewPosToGlobal(width, height); + // Add external band of tiles on the outskirt so to hit the partially displayed tiles right and top + pos_NE[VX] += tile_width; + pos_NE[VY] += tile_width; + + // Iterate through the tiles on screen: we just need to ask for one tile every tile_width meters + U32 grid_x, grid_y; + for (F64 index_y = pos_SW[VY]; index_y < pos_NE[VY]; index_y += tile_width) + { + for (F64 index_x = pos_SW[VX]; index_x < pos_NE[VX]; index_x += tile_width) + { + // Compute the world coordinates of the current point + LLVector3d pos_global(index_x, index_y, pos_SW[VZ]); + // Convert to the mipmap level coordinates for that point (i.e. which tile to we hit) + LLWorldMipmap::globalToMipmap(pos_global[VX], pos_global[VY], level, &grid_x, &grid_y); + // Get the tile. Note: NULL means that the image does not exist (so it's considered "complete" as far as fetching is concerned) + LLPointer simimage = LLWorldMap::getInstance()->getObjectsTile(grid_x, grid_y, level, load); + if (simimage) + { + // Checks that the image has a valid texture + if (simimage->hasGLTexture()) + { + // Increment the number of completly fetched tiles + completed_tiles++; + + // Convert those coordinates (SW corner of the mipmap tile) into world (meters) coordinates + pos_global[VX] = grid_x * REGION_WIDTH_METERS; + pos_global[VY] = grid_y * REGION_WIDTH_METERS; + // Now to screen coordinates for SW corner of that tile + LLVector3 pos_screen = globalPosToView (pos_global); + F32 left = pos_screen[VX]; + F32 bottom = pos_screen[VY]; + // Compute the NE corner coordinates of the tile now + pos_global[VX] += tile_width; + pos_global[VY] += tile_width; + pos_screen = globalPosToView (pos_global); + F32 right = pos_screen[VX]; + F32 top = pos_screen[VY]; + + // Draw the tile + LLGLSUIDefault gls_ui; + gGL.getTexUnit(0)->bind(simimage.get()); + simimage->setAddressMode(LLTexUnit::TAM_CLAMP); + + gGL.color4f(1.f, 1.0f, 1.0f, 1.0f); + + gGL.begin(LLRender::QUADS); + gGL.texCoord2f(0.f, 1.f); + gGL.vertex3f(left, top, 0.f); + gGL.texCoord2f(0.f, 0.f); + gGL.vertex3f(left, bottom, 0.f); + gGL.texCoord2f(1.f, 0.f); + gGL.vertex3f(right, bottom, 0.f); + gGL.texCoord2f(1.f, 1.f); + gGL.vertex3f(right, top, 0.f); + gGL.end(); +#if DEBUG_DRAW_TILE + drawTileOutline(level, top, left, bottom, right); +#endif // DEBUG_DRAW_TILE + } + //else + //{ + // Waiting for a tile -> the level is not complete + // LL_INFOS("WorldMap") << "Unfetched tile. level = " << level << LL_ENDL; + //} + } + else + { + // Unexistent tiles are counted as "completed" + completed_tiles++; + } + // Increment the number of tiles in that level / screen + total_tiles++; + } + } + return (completed_tiles == total_tiles); +} + +// Draw lines (rectangle outline and cross) to visualize the position of the tile +// Used for debug only +void LLWorldMapView::drawTileOutline(S32 level, F32 top, F32 left, F32 bottom, F32 right) +{ + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + if (level == 1) + gGL.color3f(1.f, 0.f, 0.f); // red + else if (level == 2) + gGL.color3f(0.f, 1.f, 0.f); // green + else if (level == 3) + gGL.color3f(0.f, 0.f, 1.f); // blue + else if (level == 4) + gGL.color3f(1.f, 1.f, 0.f); // yellow + else if (level == 5) + gGL.color3f(1.f, 0.f, 1.f); // magenta + else if (level == 6) + gGL.color3f(0.f, 1.f, 1.f); // cyan + else if (level == 7) + gGL.color3f(1.f, 1.f, 1.f); // white + else + gGL.color3f(0.f, 0.f, 0.f); // black + gGL.begin(LLRender::LINE_STRIP); + gGL.vertex2f(left, top); + gGL.vertex2f(right, bottom); + gGL.vertex2f(left, bottom); + gGL.vertex2f(right, top); + gGL.vertex2f(left, top); + gGL.vertex2f(left, bottom); + gGL.vertex2f(right, bottom); + gGL.vertex2f(right, top); + gGL.end(); +} + +void LLWorldMapView::drawGenericItems(const LLSimInfo::item_info_list_t& items, LLUIImagePtr image) +{ + LLSimInfo::item_info_list_t::const_iterator e; + for (e = items.begin(); e != items.end(); ++e) + { + drawGenericItem(*e, image); + } +} + +void LLWorldMapView::drawGenericItem(const LLItemInfo& item, LLUIImagePtr image) +{ + drawImage(item.getGlobalPosition(), image); +} + + +void LLWorldMapView::drawImage(const LLVector3d& global_pos, LLUIImagePtr image, const LLColor4& color) +{ + LLVector3 pos_map = globalPosToView( global_pos ); + image->draw(ll_round(pos_map.mV[VX] - image->getWidth() /2.f), + ll_round(pos_map.mV[VY] - image->getHeight()/2.f), + color); +} + +void LLWorldMapView::drawImageStack(const LLVector3d& global_pos, LLUIImagePtr image, U32 count, F32 offset, const LLColor4& color) +{ + LLVector3 pos_map = globalPosToView( global_pos ); + for(U32 i=0; idraw(ll_round(pos_map.mV[VX] - image->getWidth() /2.f), + ll_round(pos_map.mV[VY] - image->getHeight()/2.f + i*offset), + color); + } +} + +void LLWorldMapView::drawItems() +{ + bool mature_enabled = gAgent.canAccessMature(); + bool adult_enabled = gAgent.canAccessAdult(); + + bool show_mature = mature_enabled && gSavedSettings.getBOOL("ShowMatureEvents"); + bool show_adult = adult_enabled && gSavedSettings.getBOOL("ShowAdultEvents"); + + for (handle_list_t::iterator iter = mVisibleRegions.begin(); iter != mVisibleRegions.end(); ++iter) + { + U64 handle = *iter; + LLSimInfo* info = LLWorldMap::getInstance()->simInfoFromHandle(handle); + if ((info == NULL) || (info->isDown())) + { + continue; + } + // Infohubs + if (gSavedSettings.getBOOL("MapShowInfohubs")) + { + drawGenericItems(info->getInfoHub(), sInfohubImage); + } + // Telehubs + if (gSavedSettings.getBOOL("MapShowTelehubs")) + { + drawGenericItems(info->getTeleHub(), sTelehubImage); + } + // Land for sale + if (gSavedSettings.getBOOL("MapShowLandForSale")) + { + drawGenericItems(info->getLandForSale(), sForSaleImage); + // for 1.23, we're showing normal land and adult land in the same UI; you don't + // get a choice about which ones you want. If you're currently asking for adult + // content and land you'll get the adult land. + if (gAgent.canAccessAdult()) + { + drawGenericItems(info->getLandForSaleAdult(), sForSaleAdultImage); + } + } + // PG Events + if (gSavedSettings.getBOOL("MapShowEvents")) + { + drawGenericItems(info->getPGEvent(), sEventImage); + } + // Mature Events + if (show_mature) + { + drawGenericItems(info->getMatureEvent(), sEventMatureImage); + } + // Adult Events + if (show_adult) + { + drawGenericItems(info->getAdultEvent(), sEventAdultImage); + } + } +} + +void LLWorldMapView::drawAgents() +{ + static LLUIColor map_avatar_color = LLUIColorTable::instance().getColor("MapAvatarColor", LLColor4::white); + + for (handle_list_t::iterator iter = mVisibleRegions.begin(); iter != mVisibleRegions.end(); ++iter) + { + U64 handle = *iter; + LLSimInfo* siminfo = LLWorldMap::getInstance()->simInfoFromHandle(handle); + if ((siminfo == NULL) || (siminfo->isDown())) + { + continue; + } + LLSimInfo::item_info_list_t::const_iterator it = siminfo->getAgentLocation().begin(); + while (it != siminfo->getAgentLocation().end()) + { + // Show Individual agents (or little stacks where real agents are) + + // Here's how we'd choose the color if info.mID were available but it's not being sent: + // LLColor4 color = (agent_count == 1 && is_agent_friend(info.mID)) ? friend_color : avatar_color; + drawImageStack(it->getGlobalPosition(), sAvatarSmallImage, it->getCount(), 3.f, map_avatar_color); + ++it; + } + } +} + +void LLWorldMapView::drawFrustum() +{ + // Draw frustum + F32 meters_to_pixels = mMapScale/ REGION_WIDTH_METERS; + + F32 horiz_fov = LLViewerCamera::getInstance()->getView() * LLViewerCamera::getInstance()->getAspect(); + F32 far_clip_meters = LLViewerCamera::getInstance()->getFar(); + F32 far_clip_pixels = far_clip_meters * meters_to_pixels; + + F32 half_width_meters = far_clip_meters * tan( horiz_fov / 2 ); + F32 half_width_pixels = half_width_meters * meters_to_pixels; + + // Compute the frustum coordinates. Take the UI scale into account. + F32 ctr_x = ((getLocalRect().getWidth() * 0.5f + mPanX) * LLUI::getScaleFactor().mV[VX]); + F32 ctr_y = ((getLocalRect().getHeight() * 0.5f + mPanY) * LLUI::getScaleFactor().mV[VY]); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + // Since we don't rotate the map, we have to rotate the frustum. + gGL.pushMatrix(); + { + gGL.translatef( ctr_x, ctr_y, 0 ); + + // Draw triangle with more alpha in far pixels to make it + // fade out in distance. + gGL.begin( LLRender::TRIANGLES ); + { + // get camera look at and left axes + LLVector3 at_axis = LLViewerCamera::instance().getAtAxis(); + LLVector3 left_axis = LLViewerCamera::instance().getLeftAxis(); + + // grab components along XY plane + LLVector2 cam_lookat(at_axis.mV[VX], at_axis.mV[VY]); + LLVector2 cam_left(left_axis.mV[VX], left_axis.mV[VY]); + + // but, when looking near straight up or down... + if (is_approx_zero(cam_lookat.magVecSquared())) + { + //...just fall back to looking down the x axis + cam_lookat = LLVector2(1.f, 0.f); // x axis + cam_left = LLVector2(0.f, 1.f); // y axis + } + + // normalize to unit length + cam_lookat.normVec(); + cam_left.normVec(); + + gGL.color4f(1.f, 1.f, 1.f, 0.25f); + gGL.vertex2f( 0, 0 ); + + gGL.color4f(1.f, 1.f, 1.f, 0.02f); + + // use 2d camera vectors to render frustum triangle + LLVector2 vert = cam_lookat * far_clip_pixels + cam_left * half_width_pixels; + gGL.vertex2f(vert.mV[VX], vert.mV[VY]); + + vert = cam_lookat * far_clip_pixels - cam_left * half_width_pixels; + gGL.vertex2f(vert.mV[VX], vert.mV[VY]); + } + gGL.end(); + } + gGL.popMatrix(); +} + + +LLVector3 LLWorldMapView::globalPosToView( const LLVector3d& global_pos ) +{ + LLVector3d relative_pos_global = global_pos - gAgentCamera.getCameraPositionGlobal(); + LLVector3 pos_local; + pos_local.setVec(relative_pos_global); // convert to floats from doubles + + pos_local.mV[VX] *= mMapScale / REGION_WIDTH_METERS; + pos_local.mV[VY] *= mMapScale / REGION_WIDTH_METERS; + // leave Z component in meters + + + pos_local.mV[VX] += getRect().getWidth() / 2 + mPanX; + pos_local.mV[VY] += getRect().getHeight() / 2 + mPanY; + + return pos_local; +} + + +void LLWorldMapView::drawTracking(const LLVector3d& pos_global, const LLColor4& color, bool draw_arrow, + const std::string& label, const std::string& tooltip, S32 vert_offset ) +{ + LLVector3 pos_local = globalPosToView( pos_global ); + S32 x = ll_round( pos_local.mV[VX] ); + S32 y = ll_round( pos_local.mV[VY] ); + LLFontGL* font = LLFontGL::getFontSansSerifSmall(); + S32 text_x = x; + S32 text_y = (S32)(y - sTrackCircleImage->getHeight()/2 - font->getLineHeight()); + + if( x < 0 + || y < 0 + || x >= getRect().getWidth() + || y >= getRect().getHeight() ) + { + if (draw_arrow) + { + drawTrackingCircle( getRect(), x, y, color, 3, 15 ); + drawTrackingArrow( getRect(), x, y, color ); + text_x = sTrackingArrowX; + text_y = sTrackingArrowY; + } + } + else if (LLTracker::getTrackingStatus() == LLTracker::TRACKING_LOCATION && + LLTracker::getTrackedLocationType() != LLTracker::LOCATION_NOTHING) + { + drawTrackingCircle( getRect(), x, y, color, 3, 15 ); + } + else + { + drawImage(pos_global, sTrackCircleImage, color); + } + + // clamp text position to on-screen + const S32 TEXT_PADDING = DEFAULT_TRACKING_ARROW_SIZE + 2; + S32 half_text_width = llfloor(font->getWidthF32(label) * 0.5f); + text_x = llclamp(text_x, half_text_width + TEXT_PADDING, getRect().getWidth() - half_text_width - TEXT_PADDING); + text_y = llclamp(text_y + vert_offset, TEXT_PADDING + vert_offset, getRect().getHeight() - font->getLineHeight() - TEXT_PADDING - vert_offset); + + if (label != "") + { + font->renderUTF8( + label, 0, + text_x, + text_y, + LLColor4::white, LLFontGL::HCENTER, + LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::DROP_SHADOW); + + if (tooltip != "") + { + text_y -= font->getLineHeight(); + + font->renderUTF8( + tooltip, 0, + text_x, + text_y, + LLColor4::white, LLFontGL::HCENTER, + LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::DROP_SHADOW); + } + } +} + +// If you change this, then you need to change LLTracker::getTrackedPositionGlobal() as well +LLVector3d LLWorldMapView::viewPosToGlobal( S32 x, S32 y ) +{ + x -= llfloor((getRect().getWidth() / 2 + mPanX)); + y -= llfloor((getRect().getHeight() / 2 + mPanY)); + + LLVector3 pos_local( (F32)x, (F32)y, 0.f ); + + pos_local *= ( REGION_WIDTH_METERS / mMapScale ); + + LLVector3d pos_global; + pos_global.setVec( pos_local ); + pos_global += gAgentCamera.getCameraPositionGlobal(); + if(gAgent.isGodlike()) + { + pos_global.mdV[VZ] = GODLY_TELEPORT_HEIGHT; // Godly height should always be 200. + } + else + { + pos_global.mdV[VZ] = gAgent.getPositionAgent().mV[VZ]; // Want agent's height, not camera's + } + + return pos_global; +} + + +bool LLWorldMapView::handleToolTip( S32 x, S32 y, MASK mask ) +{ + LLVector3d pos_global = viewPosToGlobal(x, y); + U64 handle = to_region_handle(pos_global); + std::string tooltip_msg; + + LLSimInfo* info = LLWorldMap::getInstance()->simInfoFromHandle(handle); + if (info) + { + LLViewerRegion *region = gAgent.getRegion(); + + std::string message = llformat("%s (%s)", info->getName().c_str(), info->getAccessString().c_str()); + + if (!info->isDown()) + { + S32 agent_count = info->getAgentCount(); + if (region && (region->getHandle() == handle)) + { + ++agent_count; // Bump by 1 if we're here + } + + // We may not have an agent count when the map is really + // zoomed out, so don't display anything about the count. JC + if (agent_count > 0) + { + LLStringUtil::format_map_t string_args; + string_args["[NUMBER]"] = llformat("%d", agent_count); + message += '\n'; + message += getString((agent_count == 1 ? "world_map_person" : "world_map_people") , string_args); + } + } + tooltip_msg.assign( message ); + + // Optionally show region flags + std::string region_flags = info->getFlagsString(); + + if (!region_flags.empty()) + { + tooltip_msg += '\n'; + tooltip_msg += region_flags; + } + + const S32 SLOP = 9; + S32 screen_x, screen_y; + + localPointToScreen(x, y, &screen_x, &screen_y); + LLRect sticky_rect_screen; + sticky_rect_screen.setCenterAndSize(screen_x, screen_y, SLOP, SLOP); + + LLToolTipMgr::instance().show(LLToolTip::Params() + .message(tooltip_msg) + .sticky_rect(sticky_rect_screen)); + } + return true; +} + +// Pass relative Z of 0 to draw at same level. +// static +static void drawDot(F32 x_pixels, F32 y_pixels, + const LLColor4& color, + F32 relative_z, + F32 dot_radius, + LLUIImagePtr dot_image) +{ + const F32 HEIGHT_THRESHOLD = 7.f; + + if(-HEIGHT_THRESHOLD <= relative_z && relative_z <= HEIGHT_THRESHOLD) + { + dot_image->draw(ll_round(x_pixels) - dot_image->getWidth()/2, + ll_round(y_pixels) - dot_image->getHeight()/2, + color); + } + else + { + // Draw V indicator for above or below + // *TODO: Replace this vector drawing with icons + + F32 left = x_pixels - dot_radius; + F32 right = x_pixels + dot_radius; + F32 center = (left + right) * 0.5f; + F32 top = y_pixels + dot_radius; + F32 bottom = y_pixels - dot_radius; + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gGL.color4fv( color.mV ); + LLUI::setLineWidth(3.0f); + F32 point = relative_z > HEIGHT_THRESHOLD ? top : bottom; // Y pos of the point of the V + F32 back = relative_z > HEIGHT_THRESHOLD ? bottom : top; // Y pos of the ends of the V + gGL.begin( LLRender::LINES ); + gGL.vertex2f(left, back); + gGL.vertex2f(center, point); + gGL.vertex2f(center, point); + gGL.vertex2f(right, back); + gGL.end(); + LLUI::setLineWidth(1.0f); + } +} + +// Pass relative Z of 0 to draw at same level. +// static +void LLWorldMapView::drawAvatar(F32 x_pixels, + F32 y_pixels, + const LLColor4& color, + F32 relative_z, + F32 dot_radius, + bool unknown_relative_z) +{ + const F32 HEIGHT_THRESHOLD = 7.f; + LLUIImagePtr dot_image = sAvatarLevelImage; + if (unknown_relative_z && llabs(relative_z) > HEIGHT_THRESHOLD) + { + dot_image = sAvatarUnknownImage; + } + else + { + if(relative_z < -HEIGHT_THRESHOLD) + { + dot_image = sAvatarBelowImage; + } + else if(relative_z > HEIGHT_THRESHOLD) + { + dot_image = sAvatarAboveImage; + } + } + S32 dot_width = ll_round(dot_radius * 2.f); + dot_image->draw(ll_round(x_pixels - dot_radius), + ll_round(y_pixels - dot_radius), + dot_width, + dot_width, + color); +} + +// Pass relative Z of 0 to draw at same level. +// static +void LLWorldMapView::drawTrackingDot( F32 x_pixels, + F32 y_pixels, + const LLColor4& color, + F32 relative_z, + F32 dot_radius) +{ + drawDot(x_pixels, y_pixels, color, relative_z, dot_radius, sTrackCircleImage); +} + + +// Pass relative Z of 0 to draw at same level. +// static +void LLWorldMapView::drawIconName(F32 x_pixels, + F32 y_pixels, + const LLColor4& color, + const std::string& first_line, + const std::string& second_line) +{ + const S32 VERT_PAD = 8; + S32 text_x = ll_round(x_pixels); + S32 text_y = ll_round(y_pixels + - BIG_DOT_RADIUS + - VERT_PAD); + + // render text + LLFontGL::getFontSansSerif()->renderUTF8(first_line, 0, + text_x, + text_y, + color, + LLFontGL::HCENTER, + LLFontGL::TOP, + LLFontGL::NORMAL, + LLFontGL::DROP_SHADOW); + + text_y -= LLFontGL::getFontSansSerif()->getLineHeight(); + + // render text + LLFontGL::getFontSansSerif()->renderUTF8(second_line, 0, + text_x, + text_y, + color, + LLFontGL::HCENTER, + LLFontGL::TOP, + LLFontGL::NORMAL, + LLFontGL::DROP_SHADOW); +} + + +//static +void LLWorldMapView::drawTrackingCircle( const LLRect& rect, S32 x, S32 y, const LLColor4& color, S32 min_thickness, S32 overlap ) +{ + F32 start_theta = 0.f; + F32 end_theta = F_TWO_PI; + F32 x_delta = 0.f; + F32 y_delta = 0.f; + + if (x < 0) + { + x_delta = 0.f - (F32)x; + start_theta = F_PI + F_PI_BY_TWO; + end_theta = F_TWO_PI + F_PI_BY_TWO; + } + else if (x > rect.getWidth()) + { + x_delta = (F32)(x - rect.getWidth()); + start_theta = F_PI_BY_TWO; + end_theta = F_PI + F_PI_BY_TWO; + } + + if (y < 0) + { + y_delta = 0.f - (F32)y; + if (x < 0) + { + start_theta = 0.f; + end_theta = F_PI_BY_TWO; + } + else if (x > rect.getWidth()) + { + start_theta = F_PI_BY_TWO; + end_theta = F_PI; + } + else + { + start_theta = 0.f; + end_theta = F_PI; + } + } + else if (y > rect.getHeight()) + { + y_delta = (F32)(y - rect.getHeight()); + if (x < 0) + { + start_theta = F_PI + F_PI_BY_TWO; + end_theta = F_TWO_PI; + } + else if (x > rect.getWidth()) + { + start_theta = F_PI; + end_theta = F_PI + F_PI_BY_TWO; + } + else + { + start_theta = F_PI; + end_theta = F_TWO_PI; + } + } + + F32 distance = sqrtf(x_delta * x_delta + y_delta * y_delta); + + distance = llmax(0.1f, distance); + + F32 outer_radius = distance + (1.f + (9.f * sqrtf(x_delta * y_delta) / distance)) * (F32)overlap; + F32 inner_radius = outer_radius - (F32)min_thickness; + + F32 angle_adjust_x = asin(x_delta / outer_radius); + F32 angle_adjust_y = asin(y_delta / outer_radius); + + if (angle_adjust_x) + { + if (angle_adjust_y) + { + F32 angle_adjust = llmin(angle_adjust_x, angle_adjust_y); + start_theta += angle_adjust; + end_theta -= angle_adjust; + } + else + { + start_theta += angle_adjust_x; + end_theta -= angle_adjust_x; + } + } + else if (angle_adjust_y) + { + start_theta += angle_adjust_y; + end_theta -= angle_adjust_y; + } + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + gGL.translatef((F32)x * LLUI::getScaleFactor().mV[VX], (F32)y * LLUI::getScaleFactor().mV[VY], 0.f); + gl_washer_segment_2d(inner_radius, outer_radius, start_theta, end_theta, 40, color, color); + gGL.popMatrix(); + +} + +// static +void LLWorldMapView::drawTrackingArrow(const LLRect& rect, S32 x, S32 y, + const LLColor4& color, + S32 arrow_size) +{ + F32 x_center = (F32)rect.getWidth() / 2.f; + F32 y_center = (F32)rect.getHeight() / 2.f; + + F32 x_clamped = (F32)llclamp( x, 0, rect.getWidth() - arrow_size ); + F32 y_clamped = (F32)llclamp( y, 0, rect.getHeight() - arrow_size ); + + F32 slope = (F32)(y - y_center) / (F32)(x - x_center); + F32 window_ratio = (F32)rect.getHeight() / (F32)rect.getWidth(); + + if (llabs(slope) > window_ratio && y_clamped != (F32)y) + { + // clamp by y + x_clamped = (y_clamped - y_center) / slope + x_center; + // adjust for arrow size + x_clamped = llclamp(x_clamped , 0.f, (F32)(rect.getWidth() - arrow_size) ); + } + else if (x_clamped != (F32)x) + { + // clamp by x + y_clamped = (x_clamped - x_center) * slope + y_center; + // adjust for arrow size + y_clamped = llclamp( y_clamped, 0.f, (F32)(rect.getHeight() - arrow_size) ); + } + + // *FIX: deal with non-square window properly. + // I do not understand what this comment means -- is it actually + // broken or is it correctly dealing with non-square + // windows. Phoenix 2007-01-03. + S32 half_arrow_size = (S32) (0.5f * arrow_size); + + F32 angle = atan2( y + half_arrow_size - y_center, x + half_arrow_size - x_center); + + sTrackingArrowX = llfloor(x_clamped); + sTrackingArrowY = llfloor(y_clamped); + + gl_draw_scaled_rotated_image( + sTrackingArrowX, + sTrackingArrowY, + arrow_size, arrow_size, + RAD_TO_DEG * angle, + sTrackArrowImage->getImage(), + color); +} + +void LLWorldMapView::setDirectionPos( LLTextBox* text_box, F32 rotation ) +{ + // Rotation is in radians. + // Rotation of 0 means x = 1, y = 0 on the unit circle. + + + F32 map_half_height = getRect().getHeight() * 0.5f; + F32 map_half_width = getRect().getWidth() * 0.5f; + F32 text_half_height = text_box->getRect().getHeight() * 0.5f; + F32 text_half_width = text_box->getRect().getWidth() * 0.5f; + F32 radius = llmin( map_half_height - text_half_height, map_half_width - text_half_width ); + + text_box->setOrigin( + ll_round(map_half_width - text_half_width + radius * cos( rotation )), + ll_round(map_half_height - text_half_height + radius * sin( rotation )) ); +} + + +void LLWorldMapView::updateDirections() +{ + S32 width = getRect().getWidth(); + S32 height = getRect().getHeight(); + + S32 text_height = mTextBoxNorth->getRect().getHeight(); + S32 text_width = mTextBoxNorth->getRect().getWidth(); + + const S32 PAD = 2; + S32 top = height - text_height - PAD; + S32 left = PAD*2; + S32 bottom = PAD; + S32 right = width - text_width - PAD; + S32 center_x = width/2 - text_width/2; + S32 center_y = height/2 - text_height/2; + + mTextBoxNorth->setOrigin( center_x, top ); + mTextBoxEast->setOrigin( right, center_y ); + mTextBoxSouth->setOrigin( center_x, bottom ); + mTextBoxWest->setOrigin( left, center_y ); + + // These have wider text boxes + text_width = mTextBoxNorthWest->getRect().getWidth(); + right = width - text_width - PAD; + + mTextBoxNorthWest->setOrigin(left, top); + mTextBoxNorthEast->setOrigin(right, top); + mTextBoxSouthWest->setOrigin(left, bottom); + mTextBoxSouthEast->setOrigin(right, bottom); + +// S32 hint_width = mTextBoxScrollHint->getRect().getWidth(); +// mTextBoxScrollHint->setOrigin( width - hint_width - text_width - 2 * PAD, +// PAD * 2 + text_height ); +} + + +void LLWorldMapView::reshape( S32 width, S32 height, bool called_from_parent ) +{ + LLView::reshape( width, height, called_from_parent ); +} + +bool LLWorldMapView::checkItemHit(S32 x, S32 y, LLItemInfo& item, LLUUID* id, bool track) +{ + LLVector3 pos_view = globalPosToView(item.getGlobalPosition()); + S32 item_x = ll_round(pos_view.mV[VX]); + S32 item_y = ll_round(pos_view.mV[VY]); + + if (x < item_x - BIG_DOT_RADIUS) return false; + if (x > item_x + BIG_DOT_RADIUS) return false; + if (y < item_y - BIG_DOT_RADIUS) return false; + if (y > item_y + BIG_DOT_RADIUS) return false; + + LLSimInfo* sim_info = LLWorldMap::getInstance()->simInfoFromHandle(item.getRegionHandle()); + if (sim_info) + { + if (track) + { + gFloaterWorldMap->trackLocation(item.getGlobalPosition()); + } + } + + if (track) + { + gFloaterWorldMap->trackGenericItem(item); + } + +// item.setSelected(true); + *id = item.getUUID(); + + return true; +} + +// Handle a click, which might be on a dot +void LLWorldMapView::handleClick(S32 x, S32 y, MASK mask, + S32* hit_type, + LLUUID* id) +{ + LLVector3d pos_global = viewPosToGlobal(x, y); + + // *HACK: Adjust Z values automatically for liaisons & gods so + // we swoop down when they click on the map. Sadly, the P2P + // branch does not pay attention to this value; however, the + // Distributed Messaging branch honors it. + if(gAgent.isGodlike()) + { + pos_global.mdV[VZ] = 200.0; + } + + *hit_type = 0; // hit nothing + + LLWorldMap::getInstance()->cancelTracking(); + + S32 level = LLWorldMipmap::scaleToLevel(mMapScale); + // If the zoom level is not too far out already, test hits + if (level <= DRAW_SIMINFO_THRESHOLD) + { + bool show_mature = gAgent.canAccessMature() && gSavedSettings.getBOOL("ShowMatureEvents"); + bool show_adult = gAgent.canAccessAdult() && gSavedSettings.getBOOL("ShowAdultEvents"); + + // Test hits if trackable data are displayed, otherwise, we don't even bother + if (gSavedSettings.getBOOL("MapShowEvents") || show_mature || show_adult || gSavedSettings.getBOOL("MapShowLandForSale")) + { + // Iterate through the visible regions + for (handle_list_t::iterator iter = mVisibleRegions.begin(); iter != mVisibleRegions.end(); ++iter) + { + U64 handle = *iter; + LLSimInfo* siminfo = LLWorldMap::getInstance()->simInfoFromHandle(handle); + if ((siminfo == NULL) || (siminfo->isDown())) + { + continue; + } + // If on screen check hits with the visible item lists + if (gSavedSettings.getBOOL("MapShowEvents")) + { + LLSimInfo::item_info_list_t::const_iterator it = siminfo->getPGEvent().begin(); + while (it != siminfo->getPGEvent().end()) + { + LLItemInfo event = *it; + if (checkItemHit(x, y, event, id, false)) + { + *hit_type = MAP_ITEM_PG_EVENT; + mItemPicked = true; + gFloaterWorldMap->trackEvent(event); + return; + } + ++it; + } + } + if (show_mature) + { + LLSimInfo::item_info_list_t::const_iterator it = siminfo->getMatureEvent().begin(); + while (it != siminfo->getMatureEvent().end()) + { + LLItemInfo event = *it; + if (checkItemHit(x, y, event, id, false)) + { + *hit_type = MAP_ITEM_MATURE_EVENT; + mItemPicked = true; + gFloaterWorldMap->trackEvent(event); + return; + } + ++it; + } + } + if (show_adult) + { + LLSimInfo::item_info_list_t::const_iterator it = siminfo->getAdultEvent().begin(); + while (it != siminfo->getAdultEvent().end()) + { + LLItemInfo event = *it; + if (checkItemHit(x, y, event, id, false)) + { + *hit_type = MAP_ITEM_ADULT_EVENT; + mItemPicked = true; + gFloaterWorldMap->trackEvent(event); + return; + } + ++it; + } + } + if (gSavedSettings.getBOOL("MapShowLandForSale")) + { + LLSimInfo::item_info_list_t::const_iterator it = siminfo->getLandForSale().begin(); + while (it != siminfo->getLandForSale().end()) + { + LLItemInfo event = *it; + if (checkItemHit(x, y, event, id, true)) + { + *hit_type = MAP_ITEM_LAND_FOR_SALE; + mItemPicked = true; + return; + } + ++it; + } + // for 1.23, we're showing normal land and adult land in the same UI; you don't + // get a choice about which ones you want. If you're currently asking for adult + // content and land you'll get the adult land. + if (gAgent.canAccessAdult()) + { + LLSimInfo::item_info_list_t::const_iterator it = siminfo->getLandForSaleAdult().begin(); + while (it != siminfo->getLandForSaleAdult().end()) + { + LLItemInfo event = *it; + if (checkItemHit(x, y, event, id, true)) + { + *hit_type = MAP_ITEM_LAND_FOR_SALE_ADULT; + mItemPicked = true; + return; + } + ++it; + } + } + } + } + } + } + + // If we get here, we haven't clicked on anything + gFloaterWorldMap->trackLocation(pos_global); + mItemPicked = false; + *id = LLUUID::null; + return; +} + + +bool LLWorldMapView::handleMouseDown( S32 x, S32 y, MASK mask ) +{ + gFocusMgr.setMouseCapture( this ); + + mMouseDownPanX = ll_round(mPanX); + mMouseDownPanY = ll_round(mPanY); + mMouseDownX = x; + mMouseDownY = y; + sHandledLastClick = true; + return true; +} + +bool LLWorldMapView::handleMouseUp( S32 x, S32 y, MASK mask ) +{ + if (hasMouseCapture()) + { + if (mPanning) + { + // restore mouse cursor + S32 local_x, local_y; + local_x = mMouseDownX + llfloor(mPanX - mMouseDownPanX); + local_y = mMouseDownY + llfloor(mPanY - mMouseDownPanY); + LLRect clip_rect = getRect(); + clip_rect.stretch(-8); + clip_rect.clipPointToRect(mMouseDownX, mMouseDownY, local_x, local_y); + LLUI::getInstance()->setMousePositionLocal(this, local_x, local_y); + + // finish the pan + mPanning = false; + + mMouseDownX = 0; + mMouseDownY = 0; + } + else + { + // ignore whether we hit an event or not + S32 hit_type; + LLUUID id; + handleClick(x, y, mask, &hit_type, &id); + } + gViewerWindow->showCursor(); + gFocusMgr.setMouseCapture( NULL ); + return true; + } + return false; +} + +void LLWorldMapView::updateVisibleBlocks() +{ + if (LLWorldMipmap::scaleToLevel(mMapScale) > DRAW_SIMINFO_THRESHOLD) + { + // If we're zoomed out too much, we just don't load all those sim info: too much! + return; + } + + // Load the blocks visible in the current World Map view + + // Get the World Map view coordinates and boundaries + LLVector3d camera_global = gAgentCamera.getCameraPositionGlobal(); + const S32 width = getRect().getWidth(); + const S32 height = getRect().getHeight(); + const F32 half_width = F32(width) / 2.0f; + const F32 half_height = F32(height) / 2.0f; + + // Compute center into sim grid coordinates + S32 world_center_x = S32((-mPanX / mMapScale) + (camera_global.mdV[0] / REGION_WIDTH_METERS)); + S32 world_center_y = S32((-mPanY / mMapScale) + (camera_global.mdV[1] / REGION_WIDTH_METERS)); + + // Compute the boundaries into sim grid coordinates + S32 world_left = world_center_x - S32(half_width / mMapScale) - 1; + S32 world_right = world_center_x + S32(half_width / mMapScale) + 1; + S32 world_bottom = world_center_y - S32(half_height / mMapScale) - 1; + S32 world_top = world_center_y + S32(half_height / mMapScale) + 1; + + //LL_INFOS("WorldMap") << "LLWorldMapView::updateVisibleBlocks() : mMapScale = " << mMapScale << ", left = " << world_left << ", right = " << world_right << ", bottom = " << world_bottom << ", top = " << world_top << LL_ENDL; + LLWorldMap::getInstance()->updateRegions(world_left, world_bottom, world_right, world_top); +} + +bool LLWorldMapView::handleHover( S32 x, S32 y, MASK mask ) +{ + if (hasMouseCapture()) + { + if (mPanning || llabs(x - mMouseDownX) > 1 || llabs(y - mMouseDownY) > 1) + { + // just started panning, so hide cursor + if (!mPanning) + { + mPanning = true; + gViewerWindow->hideCursor(); + } + + F32 delta_x = (F32)(gViewerWindow->getCurrentMouseDX()); + F32 delta_y = (F32)(gViewerWindow->getCurrentMouseDY()); + + // Set pan to value at start of drag + offset + mPanX += delta_x; + mPanY += delta_y; + mTargetPanX = mPanX; + mTargetPanY = mPanY; + + gViewerWindow->moveCursorToCenter(); + } + + // doesn't matter, cursor should be hidden + gViewerWindow->setCursor(UI_CURSOR_CROSS ); + return true; + } + else + { + // While we're waiting for data from the tracker, we're busy. JC + LLVector3d pos_global = LLTracker::getTrackedPositionGlobal(); + if (LLTracker::isTracking(NULL) + && pos_global.isExactlyZero()) + { + gViewerWindow->setCursor( UI_CURSOR_WAIT ); + } + else + { + gViewerWindow->setCursor( UI_CURSOR_CROSS ); + } + LL_DEBUGS("UserInput") << "hover handled by LLWorldMapView" << LL_ENDL; + return true; + } +} + + +bool LLWorldMapView::handleDoubleClick( S32 x, S32 y, MASK mask ) +{ + if( sHandledLastClick ) + { + S32 hit_type; + LLUUID id; + handleClick(x, y, mask, &hit_type, &id); + + switch (hit_type) + { + case MAP_ITEM_PG_EVENT: + case MAP_ITEM_MATURE_EVENT: + case MAP_ITEM_ADULT_EVENT: + { + LLFloaterReg::hideInstance("world_map"); + // This is an ungainly hack + std::string uuid_str; + S32 event_id; + id.toString(uuid_str); + uuid_str = uuid_str.substr(28); + sscanf(uuid_str.c_str(), "%X", &event_id); + // Invoke the event details floater if someone is clicking on an event. + LLSD params(LLSD::emptyArray()); + params.append(event_id); + LLCommandDispatcher::dispatch("event", params, LLSD(), LLGridManager::getInstance()->getGrid(), NULL, LLCommandHandler::NAV_TYPE_CLICKED, true); + break; + } + case MAP_ITEM_LAND_FOR_SALE: + case MAP_ITEM_LAND_FOR_SALE_ADULT: + { + LLVector3d pos_global = viewPosToGlobal(x, y); + std::string sim_name; + if (LLWorldMap::getInstance()->simNameFromPosGlobal(pos_global, sim_name)) + { + LLFloaterReg::hideInstance("world_map"); + LLFloaterReg::showInstance("search", LLSD().with("category", "land").with("query", sim_name)); + } + break; + } + case MAP_ITEM_CLASSIFIED: + { + LLFloaterReg::hideInstance("world_map"); + LLFloaterReg::showInstance("search", LLSD().with("category", "classifieds").with("query", id)); + break; + } + default: + { + if (LLWorldMap::getInstance()->isTracking()) + { + LLWorldMap::getInstance()->setTrackingDoubleClick(); + } + else + { + // Teleport if we got a valid location + LLVector3d pos_global = viewPosToGlobal(x,y); + LLSimInfo* sim_info = LLWorldMap::getInstance()->simInfoFromPosGlobal(pos_global); + if (sim_info && !sim_info->isDown()) + { + gAgent.teleportViaLocation( pos_global ); + } + } + } + }; + + return true; + } + return false; +} + +// static +F32 LLWorldMapView::scaleFromZoom(F32 zoom) { return exp2(zoom) * 256.0f; } + +// static +F32 LLWorldMapView::zoomFromScale(F32 scale) { return log2(scale / 256.f); } diff --git a/indra/newview/llworldmapview.h b/indra/newview/llworldmapview.h index 7206e135c4..ebc9c6d738 100644 --- a/indra/newview/llworldmapview.h +++ b/indra/newview/llworldmapview.h @@ -1,220 +1,220 @@ -/** - * @file llworldmapview.h - * @brief LLWorldMapView class header file - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -// View of the global map of the world - -// The data (model) for the global map (a singleton, unique to the application instance) is -// in LLWorldMap and is typically accessed using LLWorldMap::getInstance() - -#ifndef LL_LLWORLDMAPVIEW_H -#define LL_LLWORLDMAPVIEW_H - -#include "llpanel.h" -#include "llworldmap.h" -#include "v4color.h" - -const S32 DEFAULT_TRACKING_ARROW_SIZE = 16; - -class LLUUID; -class LLVector3d; -class LLVector3; -class LLTextBox; - - -class LLWorldMapView : public LLPanel -{ -public: - static void initClass(); - static void cleanupClass(); - - LLWorldMapView(); - virtual ~LLWorldMapView(); - - virtual bool postBuild(); - - virtual void reshape(S32 width, S32 height, bool called_from_parent = true ); - virtual void setVisible(bool visible); - - virtual bool handleMouseDown(S32 x, S32 y, MASK mask); - virtual bool handleMouseUp(S32 x, S32 y, MASK mask); - virtual bool handleDoubleClick( S32 x, S32 y, MASK mask ); - virtual bool handleHover( S32 x, S32 y, MASK mask ); - virtual bool handleToolTip( S32 x, S32 y, MASK mask); - - bool checkItemHit(S32 x, S32 y, LLItemInfo& item, LLUUID* id, bool track); - void handleClick(S32 x, S32 y, MASK mask, S32* hit_type, LLUUID* id); - - // Scale, aka zoom, is shared across all instances! (i.e. Terrain and Objects maps are always registered) - // Zoom is used for UI and will interpolate the map scale over multiple frames. - void zoom(F32 zoom); - void zoomWithPivot(F32 zoom, S32 x, S32 y); - F32 getZoom(); - // Scale is a linear scaling factor of in-world coordinates - F32 getScale(); - // setScaleSetting/getScaleSetting are for the default map setting on login - static void setScaleSetting(F32 scaleSetting); - static F32 getScaleSetting(); - // Pan is in pixels relative to the center of the map. - void translatePan( S32 delta_x, S32 delta_y ); - void setPan( S32 x, S32 y, bool snap = true ); - void setPanWithInterpTime(S32 x, S32 y, bool snap, F32 interp_time); - // Return true if the current scale level is above the threshold for accessing region info - bool showRegionInfo(); - - LLVector3 globalPosToView(const LLVector3d& global_pos); - LLVector3d viewPosToGlobal(S32 x,S32 y); - - virtual void draw(); - void drawGenericItems(const LLSimInfo::item_info_list_t& items, LLUIImagePtr image); - void drawGenericItem(const LLItemInfo& item, LLUIImagePtr image); - void drawImage(const LLVector3d& global_pos, LLUIImagePtr image, const LLColor4& color = LLColor4::white); - void drawImageStack(const LLVector3d& global_pos, LLUIImagePtr image, U32 count, F32 offset, const LLColor4& color); - void drawAgents(); - void drawItems(); - void drawFrustum(); - void drawMipmap(S32 width, S32 height); - bool drawMipmapLevel(S32 width, S32 height, S32 level, bool load = true); - - static void cleanupTextures(); - - // Draw the tracking indicator, doing the right thing if it's outside - // the view area. - void drawTracking( const LLVector3d& pos_global, const LLColor4& color, bool draw_arrow = true, - const std::string& label = std::string(), const std::string& tooltip = std::string(), - S32 vert_offset = 0); - static void drawTrackingArrow(const LLRect& view_rect, S32 x, S32 y, - const LLColor4& color, - S32 arrow_size = DEFAULT_TRACKING_ARROW_SIZE); - static void drawTrackingDot(F32 x_pixels, - F32 y_pixels, - const LLColor4& color, - F32 relative_z = 0.f, - F32 dot_radius = 5.f); - - static void drawTrackingCircle( const LLRect& rect, S32 x, S32 y, - const LLColor4& color, - S32 min_thickness, - S32 overlap ); - static void drawAvatar( F32 x_pixels, - F32 y_pixels, - const LLColor4& color, - F32 relative_z = 0.f, - F32 dot_radius = 3.f, - bool reached_max_z = false); - static void drawIconName(F32 x_pixels, - F32 y_pixels, - const LLColor4& color, - const std::string& first_line, - const std::string& second_line); - - // Prevents accidental double clicks - static void clearLastClick() { sHandledLastClick = false; } - - // if the view changes, download additional sim info as needed - void updateVisibleBlocks(); - -protected: - void setDirectionPos( LLTextBox* text_box, F32 rotation ); - void updateDirections(); - -public: - LLColor4 mBackgroundColor; - - static LLUIImagePtr sAvatarSmallImage; - static LLUIImagePtr sAvatarYouImage; - static LLUIImagePtr sAvatarYouLargeImage; - static LLUIImagePtr sAvatarLevelImage; - static LLUIImagePtr sAvatarAboveImage; - static LLUIImagePtr sAvatarBelowImage; - static LLUIImagePtr sAvatarUnknownImage; - - static LLUIImagePtr sTelehubImage; - static LLUIImagePtr sInfohubImage; - static LLUIImagePtr sHomeImage; - static LLUIImagePtr sEventImage; - static LLUIImagePtr sEventMatureImage; - static LLUIImagePtr sEventAdultImage; - static LLUIImagePtr sTrackCircleImage; - static LLUIImagePtr sTrackArrowImage; - static LLUIImagePtr sClassifiedsImage; - static LLUIImagePtr sForSaleImage; - static LLUIImagePtr sForSaleAdultImage; - - bool mItemPicked; - - F32 mPanX; // in pixels - F32 mPanY; // in pixels - F32 mTargetPanX; // in pixels - F32 mTargetPanY; // in pixels - static S32 sTrackingArrowX; - static S32 sTrackingArrowY; - static bool sVisibleTilesLoaded; - - // Are we mid-pan from a user drag? - bool mPanning; - S32 mMouseDownPanX; // value at start of drag - S32 mMouseDownPanY; // value at start of drag - S32 mMouseDownX; - S32 mMouseDownY; - - LLTextBox* mTextBoxEast; - LLTextBox* mTextBoxNorth; - LLTextBox* mTextBoxWest; - LLTextBox* mTextBoxSouth; - - LLTextBox* mTextBoxSouthEast; - LLTextBox* mTextBoxNorthEast; - LLTextBox* mTextBoxNorthWest; - LLTextBox* mTextBoxSouthWest; - LLTextBox* mTextBoxScrollHint; - - static bool sHandledLastClick; - S32 mSelectIDStart; - - // Keep the list of regions that are displayed on screen. Avoids iterating through the whole region map after draw(). - typedef std::vector handle_list_t; - handle_list_t mVisibleRegions; // set every frame - - static std::map sStringsMap; - -private: - void drawTileOutline(S32 level, F32 top, F32 left, F32 bottom, F32 right); - - void setScale(F32 scale, bool snap = true); - - static F32 scaleFromZoom(F32 zoom); - static F32 zoomFromScale(F32 scale); - - F32 mMapScale; - F32 mTargetMapScale; - static F32 sMapScaleSetting; - static LLVector2 sZoomPivot; - static LLFrameTimer sZoomTimer; - - F32 mMapIterpTime; -}; - -#endif +/** + * @file llworldmapview.h + * @brief LLWorldMapView class header file + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// View of the global map of the world + +// The data (model) for the global map (a singleton, unique to the application instance) is +// in LLWorldMap and is typically accessed using LLWorldMap::getInstance() + +#ifndef LL_LLWORLDMAPVIEW_H +#define LL_LLWORLDMAPVIEW_H + +#include "llpanel.h" +#include "llworldmap.h" +#include "v4color.h" + +const S32 DEFAULT_TRACKING_ARROW_SIZE = 16; + +class LLUUID; +class LLVector3d; +class LLVector3; +class LLTextBox; + + +class LLWorldMapView : public LLPanel +{ +public: + static void initClass(); + static void cleanupClass(); + + LLWorldMapView(); + virtual ~LLWorldMapView(); + + virtual bool postBuild(); + + virtual void reshape(S32 width, S32 height, bool called_from_parent = true ); + virtual void setVisible(bool visible); + + virtual bool handleMouseDown(S32 x, S32 y, MASK mask); + virtual bool handleMouseUp(S32 x, S32 y, MASK mask); + virtual bool handleDoubleClick( S32 x, S32 y, MASK mask ); + virtual bool handleHover( S32 x, S32 y, MASK mask ); + virtual bool handleToolTip( S32 x, S32 y, MASK mask); + + bool checkItemHit(S32 x, S32 y, LLItemInfo& item, LLUUID* id, bool track); + void handleClick(S32 x, S32 y, MASK mask, S32* hit_type, LLUUID* id); + + // Scale, aka zoom, is shared across all instances! (i.e. Terrain and Objects maps are always registered) + // Zoom is used for UI and will interpolate the map scale over multiple frames. + void zoom(F32 zoom); + void zoomWithPivot(F32 zoom, S32 x, S32 y); + F32 getZoom(); + // Scale is a linear scaling factor of in-world coordinates + F32 getScale(); + // setScaleSetting/getScaleSetting are for the default map setting on login + static void setScaleSetting(F32 scaleSetting); + static F32 getScaleSetting(); + // Pan is in pixels relative to the center of the map. + void translatePan( S32 delta_x, S32 delta_y ); + void setPan( S32 x, S32 y, bool snap = true ); + void setPanWithInterpTime(S32 x, S32 y, bool snap, F32 interp_time); + // Return true if the current scale level is above the threshold for accessing region info + bool showRegionInfo(); + + LLVector3 globalPosToView(const LLVector3d& global_pos); + LLVector3d viewPosToGlobal(S32 x,S32 y); + + virtual void draw(); + void drawGenericItems(const LLSimInfo::item_info_list_t& items, LLUIImagePtr image); + void drawGenericItem(const LLItemInfo& item, LLUIImagePtr image); + void drawImage(const LLVector3d& global_pos, LLUIImagePtr image, const LLColor4& color = LLColor4::white); + void drawImageStack(const LLVector3d& global_pos, LLUIImagePtr image, U32 count, F32 offset, const LLColor4& color); + void drawAgents(); + void drawItems(); + void drawFrustum(); + void drawMipmap(S32 width, S32 height); + bool drawMipmapLevel(S32 width, S32 height, S32 level, bool load = true); + + static void cleanupTextures(); + + // Draw the tracking indicator, doing the right thing if it's outside + // the view area. + void drawTracking( const LLVector3d& pos_global, const LLColor4& color, bool draw_arrow = true, + const std::string& label = std::string(), const std::string& tooltip = std::string(), + S32 vert_offset = 0); + static void drawTrackingArrow(const LLRect& view_rect, S32 x, S32 y, + const LLColor4& color, + S32 arrow_size = DEFAULT_TRACKING_ARROW_SIZE); + static void drawTrackingDot(F32 x_pixels, + F32 y_pixels, + const LLColor4& color, + F32 relative_z = 0.f, + F32 dot_radius = 5.f); + + static void drawTrackingCircle( const LLRect& rect, S32 x, S32 y, + const LLColor4& color, + S32 min_thickness, + S32 overlap ); + static void drawAvatar( F32 x_pixels, + F32 y_pixels, + const LLColor4& color, + F32 relative_z = 0.f, + F32 dot_radius = 3.f, + bool reached_max_z = false); + static void drawIconName(F32 x_pixels, + F32 y_pixels, + const LLColor4& color, + const std::string& first_line, + const std::string& second_line); + + // Prevents accidental double clicks + static void clearLastClick() { sHandledLastClick = false; } + + // if the view changes, download additional sim info as needed + void updateVisibleBlocks(); + +protected: + void setDirectionPos( LLTextBox* text_box, F32 rotation ); + void updateDirections(); + +public: + LLColor4 mBackgroundColor; + + static LLUIImagePtr sAvatarSmallImage; + static LLUIImagePtr sAvatarYouImage; + static LLUIImagePtr sAvatarYouLargeImage; + static LLUIImagePtr sAvatarLevelImage; + static LLUIImagePtr sAvatarAboveImage; + static LLUIImagePtr sAvatarBelowImage; + static LLUIImagePtr sAvatarUnknownImage; + + static LLUIImagePtr sTelehubImage; + static LLUIImagePtr sInfohubImage; + static LLUIImagePtr sHomeImage; + static LLUIImagePtr sEventImage; + static LLUIImagePtr sEventMatureImage; + static LLUIImagePtr sEventAdultImage; + static LLUIImagePtr sTrackCircleImage; + static LLUIImagePtr sTrackArrowImage; + static LLUIImagePtr sClassifiedsImage; + static LLUIImagePtr sForSaleImage; + static LLUIImagePtr sForSaleAdultImage; + + bool mItemPicked; + + F32 mPanX; // in pixels + F32 mPanY; // in pixels + F32 mTargetPanX; // in pixels + F32 mTargetPanY; // in pixels + static S32 sTrackingArrowX; + static S32 sTrackingArrowY; + static bool sVisibleTilesLoaded; + + // Are we mid-pan from a user drag? + bool mPanning; + S32 mMouseDownPanX; // value at start of drag + S32 mMouseDownPanY; // value at start of drag + S32 mMouseDownX; + S32 mMouseDownY; + + LLTextBox* mTextBoxEast; + LLTextBox* mTextBoxNorth; + LLTextBox* mTextBoxWest; + LLTextBox* mTextBoxSouth; + + LLTextBox* mTextBoxSouthEast; + LLTextBox* mTextBoxNorthEast; + LLTextBox* mTextBoxNorthWest; + LLTextBox* mTextBoxSouthWest; + LLTextBox* mTextBoxScrollHint; + + static bool sHandledLastClick; + S32 mSelectIDStart; + + // Keep the list of regions that are displayed on screen. Avoids iterating through the whole region map after draw(). + typedef std::vector handle_list_t; + handle_list_t mVisibleRegions; // set every frame + + static std::map sStringsMap; + +private: + void drawTileOutline(S32 level, F32 top, F32 left, F32 bottom, F32 right); + + void setScale(F32 scale, bool snap = true); + + static F32 scaleFromZoom(F32 zoom); + static F32 zoomFromScale(F32 scale); + + F32 mMapScale; + F32 mTargetMapScale; + static F32 sMapScaleSetting; + static LLVector2 sZoomPivot; + static LLFrameTimer sZoomTimer; + + F32 mMapIterpTime; +}; + +#endif diff --git a/indra/newview/llworldmipmap.cpp b/indra/newview/llworldmipmap.cpp index 7deeb07403..e226fd4748 100644 --- a/indra/newview/llworldmipmap.cpp +++ b/indra/newview/llworldmipmap.cpp @@ -1,270 +1,270 @@ -/** - * @file llworldmipmap.cpp - * @brief Data storage for the S3 mipmap of the entire world. - * - * $LicenseInfo:firstyear=2003&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "llworldmipmap.h" -#include "llviewercontrol.h" // LLControlGroup - -#include "llviewertexturelist.h" -#include "math.h" // log() - -// Turn this on to output tile stats in the standard output -#define DEBUG_TILES_STAT 0 - -LLWorldMipmap::LLWorldMipmap() : - mCurrentLevel(0) -{ -} - -LLWorldMipmap::~LLWorldMipmap() -{ - reset(); -} - -// Delete all sublevel maps and clean them -void LLWorldMipmap::reset() -{ - for (int level = 0; level < MAP_LEVELS; level++) - { - mWorldObjectsMipMap[level].clear(); - } -} - -// This method should be called before each use of the mipmap (typically, before each draw), so that to let -// the boost level of unused tiles to drop to 0 (BOOST_NONE). -// Tiles that are accessed have had their boost level pushed to BOOST_MAP_VISIBLE so we can identify them. -// The result of this strategy is that if a tile is not used during 2 consecutive loops, its boost level drops to 0. -void LLWorldMipmap::equalizeBoostLevels() -{ -#if DEBUG_TILES_STAT - S32 nb_missing = 0; - S32 nb_tiles = 0; - S32 nb_visible = 0; -#endif // DEBUG_TILES_STAT - // For each level - for (S32 level = 0; level < MAP_LEVELS; level++) - { - sublevel_tiles_t& level_mipmap = mWorldObjectsMipMap[level]; - // For each tile - for (sublevel_tiles_t::iterator iter = level_mipmap.begin(); iter != level_mipmap.end(); iter++) - { - LLPointer img = iter->second; - S32 current_boost_level = img->getBoostLevel(); - if (current_boost_level == LLGLTexture::BOOST_MAP_VISIBLE) - { - // If level was BOOST_MAP_VISIBLE, the tile has been used in the last draw so keep it high - img->setBoostLevel(LLGLTexture::BOOST_MAP); - } - else - { - // If level was BOOST_MAP only (or anything else...), the tile wasn't used in the last draw - // so we drop its boost level to BOOST_NONE. - img->setBoostLevel(LLGLTexture::BOOST_NONE); - } -#if DEBUG_TILES_STAT - // Increment some stats if compile option on - nb_tiles++; - if (current_boost_level == LLGLTexture::BOOST_MAP_VISIBLE) - { - nb_visible++; - } - if (img->isMissingAsset()) - { - nb_missing++; - } -#endif // DEBUG_TILES_STAT - } - } -#if DEBUG_TILES_STAT - LL_INFOS("WorldMap") << "LLWorldMipmap tile stats : total requested = " << nb_tiles << ", visible = " << nb_visible << ", missing = " << nb_missing << LL_ENDL; -#endif // DEBUG_TILES_STAT -} - -// This method should be used when the mipmap is not actively used for a while, e.g., the map UI is hidden -void LLWorldMipmap::dropBoostLevels() -{ - // For each level - for (S32 level = 0; level < MAP_LEVELS; level++) - { - sublevel_tiles_t& level_mipmap = mWorldObjectsMipMap[level]; - // For each tile - for (sublevel_tiles_t::iterator iter = level_mipmap.begin(); iter != level_mipmap.end(); iter++) - { - LLPointer img = iter->second; - img->setBoostLevel(LLGLTexture::BOOST_NONE); - } - } -} - -LLPointer LLWorldMipmap::getObjectsTile(U32 grid_x, U32 grid_y, S32 level, bool load) -{ - // Check the input data - llassert(level <= MAP_LEVELS); - llassert(level >= 1); - - // If the *loading* level changed, cleared the new level from "missed" tiles - // so that we get a chance to reload them - if (load && (level != mCurrentLevel)) - { - cleanMissedTilesFromLevel(level); - mCurrentLevel = level; - } - - // Build the region handle - U64 handle = convertGridToHandle(grid_x, grid_y); - - // Check if the image is around already - sublevel_tiles_t& level_mipmap = mWorldObjectsMipMap[level-1]; - sublevel_tiles_t::iterator found = level_mipmap.find(handle); - - // If not there and load on, go load it - if (found == level_mipmap.end()) - { - if (load) - { - // Load it - LLPointer img = loadObjectsTile(grid_x, grid_y, level); - // Insert the image in the map - level_mipmap.insert(sublevel_tiles_t::value_type( handle, img )); - // Find the element again in the map (it's there now...) - found = level_mipmap.find(handle); - } - else - { - // Return with NULL if not found and we're not trying to load - return NULL; - } - } - - // Get the image pointer and check if this asset is missing - LLPointer img = found->second; - if (img->isMissingAsset()) - { - // Return NULL if asset missing - return NULL; - } - else - { - // Boost the tile level so to mark it's in use *if* load on - if (load) - { - img->setBoostLevel(LLGLTexture::BOOST_MAP_VISIBLE); - } - return img; - } -} - -LLPointer LLWorldMipmap::loadObjectsTile(U32 grid_x, U32 grid_y, S32 level) -{ - // Get the grid coordinates - std::string imageurl = gSavedSettings.getString("CurrentMapServerURL") + llformat("map-%d-%d-%d-objects.jpg", level, grid_x, grid_y); - - // DO NOT COMMIT!! DEBUG ONLY!!! - // Use a local jpeg for every tile to test map speed without S3 access - //imageurl = "file://C:\\Develop\\mapserver-distribute-3\\indra\\build-vc80\\mapserver\\relwithdebinfo\\regions\\00995\\01001\\region-995-1001-prims.jpg"; - // END DEBUG - //LL_INFOS("WorldMap") << "LLWorldMipmap::loadObjectsTile(), URL = " << imageurl << LL_ENDL; - - LLPointer img = LLViewerTextureManager::getFetchedTextureFromUrl(imageurl, FTT_MAP_TILE, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); - LL_INFOS("MAPURL") << "fetching map tile from " << imageurl << LL_ENDL; - - img->setBoostLevel(LLGLTexture::BOOST_MAP); - - // Return the smart pointer - return img; -} - -// This method is used to clean up a level from tiles marked as "missing". -// The idea is to allow tiles that have been improperly marked missing to be reloaded when retraversing the level again. -// When zooming in and out rapidly, some tiles are never properly loaded and, eventually marked missing. -// This creates "blue" areas in a subresolution that never got a chance to reload if we don't clean up the level. -void LLWorldMipmap::cleanMissedTilesFromLevel(S32 level) -{ - // Check the input data - llassert(level <= MAP_LEVELS); - llassert(level >= 0); - - // This happens when the object is first initialized - if (level == 0) - { - return; - } - - // Iterate through the subresolution level and suppress the tiles that are marked as missing - // Note: erasing in a map while iterating through it is bug prone. Using a postfix increment is mandatory here. - sublevel_tiles_t& level_mipmap = mWorldObjectsMipMap[level-1]; - sublevel_tiles_t::iterator it = level_mipmap.begin(); - while (it != level_mipmap.end()) - { - LLPointer img = it->second; - if (img->isMissingAsset()) - { - level_mipmap.erase(it++); - } - else - { - ++it; - } - } - return; -} - -// static methods -// Compute the level in the world mipmap (between 1 and MAP_LEVELS, as in the URL) given the scale (size of a sim in screen pixels) -S32 LLWorldMipmap::scaleToLevel(F32 scale) -{ - // If scale really small, picks up the higest level there is (lowest resolution) - if (scale <= F32_MIN) - return MAP_LEVELS; - // Compute the power of two resolution level knowing the base level - S32 level = llfloor((log(REGION_WIDTH_METERS/scale)/log(2.0f)) + 1.0f); - // Check bounds and return the value - if (level > MAP_LEVELS) - return MAP_LEVELS; - else if (level < 1) - return 1; - else - return level; -} - -// Convert world coordinates to mipmap grid coordinates at a given level (between 1 and MAP_LEVELS) -void LLWorldMipmap::globalToMipmap(F64 global_x, F64 global_y, S32 level, U32* grid_x, U32* grid_y) -{ - // Check the input data - llassert(level <= MAP_LEVELS); - llassert(level >= 1); - - // Convert world coordinates into grid coordinates - *grid_x = lltrunc(global_x/REGION_WIDTH_METERS); - *grid_y = lltrunc(global_y/REGION_WIDTH_METERS); - // Compute the valid grid coordinates at that level of the mipmap - S32 regions_in_tile = 1 << (level - 1); - *grid_x = *grid_x - (*grid_x % regions_in_tile); - *grid_y = *grid_y - (*grid_y % regions_in_tile); -} - - +/** + * @file llworldmipmap.cpp + * @brief Data storage for the S3 mipmap of the entire world. + * + * $LicenseInfo:firstyear=2003&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llworldmipmap.h" +#include "llviewercontrol.h" // LLControlGroup + +#include "llviewertexturelist.h" +#include "math.h" // log() + +// Turn this on to output tile stats in the standard output +#define DEBUG_TILES_STAT 0 + +LLWorldMipmap::LLWorldMipmap() : + mCurrentLevel(0) +{ +} + +LLWorldMipmap::~LLWorldMipmap() +{ + reset(); +} + +// Delete all sublevel maps and clean them +void LLWorldMipmap::reset() +{ + for (int level = 0; level < MAP_LEVELS; level++) + { + mWorldObjectsMipMap[level].clear(); + } +} + +// This method should be called before each use of the mipmap (typically, before each draw), so that to let +// the boost level of unused tiles to drop to 0 (BOOST_NONE). +// Tiles that are accessed have had their boost level pushed to BOOST_MAP_VISIBLE so we can identify them. +// The result of this strategy is that if a tile is not used during 2 consecutive loops, its boost level drops to 0. +void LLWorldMipmap::equalizeBoostLevels() +{ +#if DEBUG_TILES_STAT + S32 nb_missing = 0; + S32 nb_tiles = 0; + S32 nb_visible = 0; +#endif // DEBUG_TILES_STAT + // For each level + for (S32 level = 0; level < MAP_LEVELS; level++) + { + sublevel_tiles_t& level_mipmap = mWorldObjectsMipMap[level]; + // For each tile + for (sublevel_tiles_t::iterator iter = level_mipmap.begin(); iter != level_mipmap.end(); iter++) + { + LLPointer img = iter->second; + S32 current_boost_level = img->getBoostLevel(); + if (current_boost_level == LLGLTexture::BOOST_MAP_VISIBLE) + { + // If level was BOOST_MAP_VISIBLE, the tile has been used in the last draw so keep it high + img->setBoostLevel(LLGLTexture::BOOST_MAP); + } + else + { + // If level was BOOST_MAP only (or anything else...), the tile wasn't used in the last draw + // so we drop its boost level to BOOST_NONE. + img->setBoostLevel(LLGLTexture::BOOST_NONE); + } +#if DEBUG_TILES_STAT + // Increment some stats if compile option on + nb_tiles++; + if (current_boost_level == LLGLTexture::BOOST_MAP_VISIBLE) + { + nb_visible++; + } + if (img->isMissingAsset()) + { + nb_missing++; + } +#endif // DEBUG_TILES_STAT + } + } +#if DEBUG_TILES_STAT + LL_INFOS("WorldMap") << "LLWorldMipmap tile stats : total requested = " << nb_tiles << ", visible = " << nb_visible << ", missing = " << nb_missing << LL_ENDL; +#endif // DEBUG_TILES_STAT +} + +// This method should be used when the mipmap is not actively used for a while, e.g., the map UI is hidden +void LLWorldMipmap::dropBoostLevels() +{ + // For each level + for (S32 level = 0; level < MAP_LEVELS; level++) + { + sublevel_tiles_t& level_mipmap = mWorldObjectsMipMap[level]; + // For each tile + for (sublevel_tiles_t::iterator iter = level_mipmap.begin(); iter != level_mipmap.end(); iter++) + { + LLPointer img = iter->second; + img->setBoostLevel(LLGLTexture::BOOST_NONE); + } + } +} + +LLPointer LLWorldMipmap::getObjectsTile(U32 grid_x, U32 grid_y, S32 level, bool load) +{ + // Check the input data + llassert(level <= MAP_LEVELS); + llassert(level >= 1); + + // If the *loading* level changed, cleared the new level from "missed" tiles + // so that we get a chance to reload them + if (load && (level != mCurrentLevel)) + { + cleanMissedTilesFromLevel(level); + mCurrentLevel = level; + } + + // Build the region handle + U64 handle = convertGridToHandle(grid_x, grid_y); + + // Check if the image is around already + sublevel_tiles_t& level_mipmap = mWorldObjectsMipMap[level-1]; + sublevel_tiles_t::iterator found = level_mipmap.find(handle); + + // If not there and load on, go load it + if (found == level_mipmap.end()) + { + if (load) + { + // Load it + LLPointer img = loadObjectsTile(grid_x, grid_y, level); + // Insert the image in the map + level_mipmap.insert(sublevel_tiles_t::value_type( handle, img )); + // Find the element again in the map (it's there now...) + found = level_mipmap.find(handle); + } + else + { + // Return with NULL if not found and we're not trying to load + return NULL; + } + } + + // Get the image pointer and check if this asset is missing + LLPointer img = found->second; + if (img->isMissingAsset()) + { + // Return NULL if asset missing + return NULL; + } + else + { + // Boost the tile level so to mark it's in use *if* load on + if (load) + { + img->setBoostLevel(LLGLTexture::BOOST_MAP_VISIBLE); + } + return img; + } +} + +LLPointer LLWorldMipmap::loadObjectsTile(U32 grid_x, U32 grid_y, S32 level) +{ + // Get the grid coordinates + std::string imageurl = gSavedSettings.getString("CurrentMapServerURL") + llformat("map-%d-%d-%d-objects.jpg", level, grid_x, grid_y); + + // DO NOT COMMIT!! DEBUG ONLY!!! + // Use a local jpeg for every tile to test map speed without S3 access + //imageurl = "file://C:\\Develop\\mapserver-distribute-3\\indra\\build-vc80\\mapserver\\relwithdebinfo\\regions\\00995\\01001\\region-995-1001-prims.jpg"; + // END DEBUG + //LL_INFOS("WorldMap") << "LLWorldMipmap::loadObjectsTile(), URL = " << imageurl << LL_ENDL; + + LLPointer img = LLViewerTextureManager::getFetchedTextureFromUrl(imageurl, FTT_MAP_TILE, true, LLGLTexture::BOOST_NONE, LLViewerTexture::LOD_TEXTURE); + LL_INFOS("MAPURL") << "fetching map tile from " << imageurl << LL_ENDL; + + img->setBoostLevel(LLGLTexture::BOOST_MAP); + + // Return the smart pointer + return img; +} + +// This method is used to clean up a level from tiles marked as "missing". +// The idea is to allow tiles that have been improperly marked missing to be reloaded when retraversing the level again. +// When zooming in and out rapidly, some tiles are never properly loaded and, eventually marked missing. +// This creates "blue" areas in a subresolution that never got a chance to reload if we don't clean up the level. +void LLWorldMipmap::cleanMissedTilesFromLevel(S32 level) +{ + // Check the input data + llassert(level <= MAP_LEVELS); + llassert(level >= 0); + + // This happens when the object is first initialized + if (level == 0) + { + return; + } + + // Iterate through the subresolution level and suppress the tiles that are marked as missing + // Note: erasing in a map while iterating through it is bug prone. Using a postfix increment is mandatory here. + sublevel_tiles_t& level_mipmap = mWorldObjectsMipMap[level-1]; + sublevel_tiles_t::iterator it = level_mipmap.begin(); + while (it != level_mipmap.end()) + { + LLPointer img = it->second; + if (img->isMissingAsset()) + { + level_mipmap.erase(it++); + } + else + { + ++it; + } + } + return; +} + +// static methods +// Compute the level in the world mipmap (between 1 and MAP_LEVELS, as in the URL) given the scale (size of a sim in screen pixels) +S32 LLWorldMipmap::scaleToLevel(F32 scale) +{ + // If scale really small, picks up the higest level there is (lowest resolution) + if (scale <= F32_MIN) + return MAP_LEVELS; + // Compute the power of two resolution level knowing the base level + S32 level = llfloor((log(REGION_WIDTH_METERS/scale)/log(2.0f)) + 1.0f); + // Check bounds and return the value + if (level > MAP_LEVELS) + return MAP_LEVELS; + else if (level < 1) + return 1; + else + return level; +} + +// Convert world coordinates to mipmap grid coordinates at a given level (between 1 and MAP_LEVELS) +void LLWorldMipmap::globalToMipmap(F64 global_x, F64 global_y, S32 level, U32* grid_x, U32* grid_y) +{ + // Check the input data + llassert(level <= MAP_LEVELS); + llassert(level >= 1); + + // Convert world coordinates into grid coordinates + *grid_x = lltrunc(global_x/REGION_WIDTH_METERS); + *grid_y = lltrunc(global_y/REGION_WIDTH_METERS); + // Compute the valid grid coordinates at that level of the mipmap + S32 regions_in_tile = 1 << (level - 1); + *grid_x = *grid_x - (*grid_x % regions_in_tile); + *grid_y = *grid_y - (*grid_y % regions_in_tile); +} + + diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index 039eea7bc5..472ccd220d 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -1,10834 +1,10834 @@ -/** - * @file pipeline.cpp - * @brief Rendering pipeline. - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" - -#include "pipeline.h" - -// library includes -#include "llaudioengine.h" // For debugging. -#include "llerror.h" -#include "llviewercontrol.h" -#include "llfasttimer.h" -#include "llfontgl.h" -#include "llnamevalue.h" -#include "llpointer.h" -#include "llprimitive.h" -#include "llvolume.h" -#include "material_codes.h" -#include "v3color.h" -#include "llui.h" -#include "llglheaders.h" -#include "llrender.h" -#include "llstartup.h" -#include "llwindow.h" // swapBuffers() - -// newview includes -#include "llagent.h" -#include "llagentcamera.h" -#include "llappviewer.h" -#include "lltexturecache.h" -#include "lltexturefetch.h" -#include "llimageworker.h" -#include "lldrawable.h" -#include "lldrawpoolalpha.h" -#include "lldrawpoolavatar.h" -#include "lldrawpoolbump.h" -#include "lldrawpooltree.h" -#include "lldrawpoolwater.h" -#include "llface.h" -#include "llfeaturemanager.h" -#include "llfloatertelehub.h" -#include "llfloaterreg.h" -#include "llhudmanager.h" -#include "llhudnametag.h" -#include "llhudtext.h" -#include "lllightconstants.h" -#include "llmeshrepository.h" -#include "llpipelinelistener.h" -#include "llresmgr.h" -#include "llselectmgr.h" -#include "llsky.h" -#include "lltracker.h" -#include "lltool.h" -#include "lltoolmgr.h" -#include "llviewercamera.h" -#include "llviewermediafocus.h" -#include "llviewertexturelist.h" -#include "llviewerobject.h" -#include "llviewerobjectlist.h" -#include "llviewerparcelmgr.h" -#include "llviewerregion.h" // for audio debugging. -#include "llviewerwindow.h" // For getSpinAxis -#include "llvoavatarself.h" -#include "llvocache.h" -#include "llvosky.h" -#include "llvowlsky.h" -#include "llvotree.h" -#include "llvovolume.h" -#include "llvosurfacepatch.h" -#include "llvowater.h" -#include "llvotree.h" -#include "llvopartgroup.h" -#include "llworld.h" -#include "llcubemap.h" -#include "llviewershadermgr.h" -#include "llviewerstats.h" -#include "llviewerjoystick.h" -#include "llviewerdisplay.h" -#include "llspatialpartition.h" -#include "llmutelist.h" -#include "lltoolpie.h" -#include "llnotifications.h" -#include "llpathinglib.h" -#include "llfloaterpathfindingconsole.h" -#include "llfloaterpathfindingcharacters.h" -#include "llfloatertools.h" -#include "llpanelface.h" -#include "llpathfindingpathtool.h" -#include "llscenemonitor.h" -#include "llprogressview.h" -#include "llcleanup.h" - -#include "llenvironment.h" -#include "llsettingsvo.h" - -extern bool gSnapshot; -bool gShiftFrame = false; - -//cached settings -bool LLPipeline::WindLightUseAtmosShaders; -bool LLPipeline::RenderDeferred; -F32 LLPipeline::RenderDeferredSunWash; -U32 LLPipeline::RenderFSAASamples; -U32 LLPipeline::RenderResolutionDivisor; -bool LLPipeline::RenderUIBuffer; -S32 LLPipeline::RenderShadowDetail; -S32 LLPipeline::RenderShadowSplits; -bool LLPipeline::RenderDeferredSSAO; -F32 LLPipeline::RenderShadowResolutionScale; -bool LLPipeline::RenderDelayCreation; -bool LLPipeline::RenderAnimateRes; -bool LLPipeline::FreezeTime; -S32 LLPipeline::DebugBeaconLineWidth; -F32 LLPipeline::RenderHighlightBrightness; -LLColor4 LLPipeline::RenderHighlightColor; -F32 LLPipeline::RenderHighlightThickness; -bool LLPipeline::RenderSpotLightsInNondeferred; -LLColor4 LLPipeline::PreviewAmbientColor; -LLColor4 LLPipeline::PreviewDiffuse0; -LLColor4 LLPipeline::PreviewSpecular0; -LLColor4 LLPipeline::PreviewDiffuse1; -LLColor4 LLPipeline::PreviewSpecular1; -LLColor4 LLPipeline::PreviewDiffuse2; -LLColor4 LLPipeline::PreviewSpecular2; -LLVector3 LLPipeline::PreviewDirection0; -LLVector3 LLPipeline::PreviewDirection1; -LLVector3 LLPipeline::PreviewDirection2; -F32 LLPipeline::RenderGlowMaxExtractAlpha; -F32 LLPipeline::RenderGlowWarmthAmount; -LLVector3 LLPipeline::RenderGlowLumWeights; -LLVector3 LLPipeline::RenderGlowWarmthWeights; -S32 LLPipeline::RenderGlowResolutionPow; -S32 LLPipeline::RenderGlowIterations; -F32 LLPipeline::RenderGlowWidth; -F32 LLPipeline::RenderGlowStrength; -bool LLPipeline::RenderGlowNoise; -bool LLPipeline::RenderDepthOfField; -bool LLPipeline::RenderDepthOfFieldInEditMode; -F32 LLPipeline::CameraFocusTransitionTime; -F32 LLPipeline::CameraFNumber; -F32 LLPipeline::CameraFocalLength; -F32 LLPipeline::CameraFieldOfView; -F32 LLPipeline::RenderShadowNoise; -F32 LLPipeline::RenderShadowBlurSize; -F32 LLPipeline::RenderSSAOScale; -U32 LLPipeline::RenderSSAOMaxScale; -F32 LLPipeline::RenderSSAOFactor; -LLVector3 LLPipeline::RenderSSAOEffect; -F32 LLPipeline::RenderShadowOffsetError; -F32 LLPipeline::RenderShadowBiasError; -F32 LLPipeline::RenderShadowOffset; -F32 LLPipeline::RenderShadowBias; -F32 LLPipeline::RenderSpotShadowOffset; -F32 LLPipeline::RenderSpotShadowBias; -LLDrawable* LLPipeline::RenderSpotLight = nullptr; -F32 LLPipeline::RenderEdgeDepthCutoff; -F32 LLPipeline::RenderEdgeNormCutoff; -LLVector3 LLPipeline::RenderShadowGaussian; -F32 LLPipeline::RenderShadowBlurDistFactor; -bool LLPipeline::RenderDeferredAtmospheric; -F32 LLPipeline::RenderHighlightFadeTime; -F32 LLPipeline::RenderFarClip; -LLVector3 LLPipeline::RenderShadowSplitExponent; -F32 LLPipeline::RenderShadowErrorCutoff; -F32 LLPipeline::RenderShadowFOVCutoff; -bool LLPipeline::CameraOffset; -F32 LLPipeline::CameraMaxCoF; -F32 LLPipeline::CameraDoFResScale; -F32 LLPipeline::RenderAutoHideSurfaceAreaLimit; -bool LLPipeline::RenderScreenSpaceReflections; -S32 LLPipeline::RenderScreenSpaceReflectionIterations; -F32 LLPipeline::RenderScreenSpaceReflectionRayStep; -F32 LLPipeline::RenderScreenSpaceReflectionDistanceBias; -F32 LLPipeline::RenderScreenSpaceReflectionDepthRejectBias; -F32 LLPipeline::RenderScreenSpaceReflectionAdaptiveStepMultiplier; -S32 LLPipeline::RenderScreenSpaceReflectionGlossySamples; -S32 LLPipeline::RenderBufferVisualization; -LLTrace::EventStatHandle LLPipeline::sStatBatchSize("renderbatchsize"); - -const F32 BACKLIGHT_DAY_MAGNITUDE_OBJECT = 0.1f; -const F32 BACKLIGHT_NIGHT_MAGNITUDE_OBJECT = 0.08f; -const F32 ALPHA_BLEND_CUTOFF = 0.598f; -const F32 DEFERRED_LIGHT_FALLOFF = 0.5f; -const U32 DEFERRED_VB_MASK = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_TEXCOORD1; - -extern S32 gBoxFrame; -//extern bool gHideSelectedObjects; -extern bool gDisplaySwapBuffers; -extern bool gDebugGL; -extern bool gCubeSnapshot; -extern bool gSnapshotNoPost; - -bool gAvatarBacklight = false; - -bool gDebugPipeline = false; -LLPipeline gPipeline; -const LLMatrix4* gGLLastMatrix = NULL; - -LLTrace::BlockTimerStatHandle FTM_RENDER_GEOMETRY("Render Geometry"); -LLTrace::BlockTimerStatHandle FTM_RENDER_GRASS("Grass"); -LLTrace::BlockTimerStatHandle FTM_RENDER_INVISIBLE("Invisible"); -LLTrace::BlockTimerStatHandle FTM_RENDER_SHINY("Shiny"); -LLTrace::BlockTimerStatHandle FTM_RENDER_SIMPLE("Simple"); -LLTrace::BlockTimerStatHandle FTM_RENDER_TERRAIN("Terrain"); -LLTrace::BlockTimerStatHandle FTM_RENDER_TREES("Trees"); -LLTrace::BlockTimerStatHandle FTM_RENDER_UI("UI"); -LLTrace::BlockTimerStatHandle FTM_RENDER_WATER("Water"); -LLTrace::BlockTimerStatHandle FTM_RENDER_WL_SKY("Windlight Sky"); -LLTrace::BlockTimerStatHandle FTM_RENDER_ALPHA("Alpha Objects"); -LLTrace::BlockTimerStatHandle FTM_RENDER_CHARACTERS("Avatars"); -LLTrace::BlockTimerStatHandle FTM_RENDER_BUMP("Bump"); -LLTrace::BlockTimerStatHandle FTM_RENDER_MATERIALS("Render Materials"); -LLTrace::BlockTimerStatHandle FTM_RENDER_FULLBRIGHT("Fullbright"); -LLTrace::BlockTimerStatHandle FTM_RENDER_GLOW("Glow"); -LLTrace::BlockTimerStatHandle FTM_GEO_UPDATE("Geo Update"); -LLTrace::BlockTimerStatHandle FTM_POOLRENDER("RenderPool"); -LLTrace::BlockTimerStatHandle FTM_POOLS("Pools"); -LLTrace::BlockTimerStatHandle FTM_DEFERRED_POOLRENDER("RenderPool (Deferred)"); -LLTrace::BlockTimerStatHandle FTM_DEFERRED_POOLS("Pools (Deferred)"); -LLTrace::BlockTimerStatHandle FTM_POST_DEFERRED_POOLRENDER("RenderPool (Post)"); -LLTrace::BlockTimerStatHandle FTM_POST_DEFERRED_POOLS("Pools (Post)"); -LLTrace::BlockTimerStatHandle FTM_STATESORT("Sort Draw State"); -LLTrace::BlockTimerStatHandle FTM_PIPELINE("Pipeline"); -LLTrace::BlockTimerStatHandle FTM_CLIENT_COPY("Client Copy"); -LLTrace::BlockTimerStatHandle FTM_RENDER_DEFERRED("Deferred Shading"); - -LLTrace::BlockTimerStatHandle FTM_RENDER_UI_HUD("HUD"); -LLTrace::BlockTimerStatHandle FTM_RENDER_UI_3D("3D"); -LLTrace::BlockTimerStatHandle FTM_RENDER_UI_2D("2D"); - -static LLTrace::BlockTimerStatHandle FTM_STATESORT_DRAWABLE("Sort Drawables"); - -static LLStaticHashedString sTint("tint"); -static LLStaticHashedString sAmbiance("ambiance"); -static LLStaticHashedString sAlphaScale("alpha_scale"); -static LLStaticHashedString sNormMat("norm_mat"); -static LLStaticHashedString sOffset("offset"); -static LLStaticHashedString sScreenRes("screenRes"); -static LLStaticHashedString sDelta("delta"); -static LLStaticHashedString sDistFactor("dist_factor"); -static LLStaticHashedString sKern("kern"); -static LLStaticHashedString sKernScale("kern_scale"); - -//---------------------------------------- - -void drawBox(const LLVector4a& c, const LLVector4a& r); -void drawBoxOutline(const LLVector3& pos, const LLVector3& size); -U32 nhpo2(U32 v); -LLVertexBuffer* ll_create_cube_vb(U32 type_mask); - -void display_update_camera(); -//---------------------------------------- - -S32 LLPipeline::sCompiles = 0; - -bool LLPipeline::sPickAvatar = true; -bool LLPipeline::sDynamicLOD = true; -bool LLPipeline::sShowHUDAttachments = true; -bool LLPipeline::sRenderMOAPBeacons = false; -bool LLPipeline::sRenderPhysicalBeacons = true; -bool LLPipeline::sRenderScriptedBeacons = false; -bool LLPipeline::sRenderScriptedTouchBeacons = true; -bool LLPipeline::sRenderParticleBeacons = false; -bool LLPipeline::sRenderSoundBeacons = false; -bool LLPipeline::sRenderBeacons = false; -bool LLPipeline::sRenderHighlight = true; -LLRender::eTexIndex LLPipeline::sRenderHighlightTextureChannel = LLRender::DIFFUSE_MAP; -bool LLPipeline::sForceOldBakedUpload = false; -S32 LLPipeline::sUseOcclusion = 0; -bool LLPipeline::sAutoMaskAlphaDeferred = true; -bool LLPipeline::sAutoMaskAlphaNonDeferred = false; -bool LLPipeline::sRenderTransparentWater = true; -bool LLPipeline::sBakeSunlight = false; -bool LLPipeline::sNoAlpha = false; -bool LLPipeline::sUseFarClip = true; -bool LLPipeline::sShadowRender = false; -bool LLPipeline::sRenderGlow = false; -bool LLPipeline::sReflectionRender = false; -bool LLPipeline::sDistortionRender = false; -bool LLPipeline::sImpostorRender = false; -bool LLPipeline::sImpostorRenderAlphaDepthPass = false; -bool LLPipeline::sUnderWaterRender = false; -bool LLPipeline::sTextureBindTest = false; -bool LLPipeline::sRenderAttachedLights = true; -bool LLPipeline::sRenderAttachedParticles = true; -bool LLPipeline::sRenderDeferred = false; -bool LLPipeline::sReflectionProbesEnabled = false; -S32 LLPipeline::sVisibleLightCount = 0; -bool LLPipeline::sRenderingHUDs; -F32 LLPipeline::sDistortionWaterClipPlaneMargin = 1.0125f; - -// EventHost API LLPipeline listener. -static LLPipelineListener sPipelineListener; - -static LLCullResult* sCull = NULL; - -void validate_framebuffer_object(); - -// Add color attachments for deferred rendering -// target -- RenderTarget to add attachments to -bool addDeferredAttachments(LLRenderTarget& target, bool for_impostor = false) -{ - bool valid = true - && target.addColorAttachment(GL_RGBA) // frag-data[1] specular OR PBR ORM - && target.addColorAttachment(GL_RGBA16F) // frag_data[2] normal+z+fogmask, See: class1\deferred\materialF.glsl & softenlight - && target.addColorAttachment(GL_RGB16F); // frag_data[3] PBR emissive - return valid; -} - -LLPipeline::LLPipeline() : - mBackfaceCull(false), - mMatrixOpCount(0), - mTextureMatrixOps(0), - mNumVisibleNodes(0), - mNumVisibleFaces(0), - mPoissonOffset(0), - - mInitialized(false), - mShadersLoaded(false), - mTransformFeedbackPrimitives(0), - mRenderDebugFeatureMask(0), - mRenderDebugMask(0), - mOldRenderDebugMask(0), - mMeshDirtyQueryObject(0), - mGroupQ1Locked(false), - mResetVertexBuffers(false), - mLastRebuildPool(NULL), - mLightMask(0), - mLightMovingMask(0) -{ - mNoiseMap = 0; - mTrueNoiseMap = 0; - mLightFunc = 0; - - for(U32 i = 0; i < 8; i++) - { - mHWLightColors[i] = LLColor4::black; - } -} - -void LLPipeline::connectRefreshCachedSettingsSafe(const std::string name) -{ - LLPointer cntrl_ptr = gSavedSettings.getControl(name); - if ( cntrl_ptr.isNull() ) - { - LL_WARNS() << "Global setting name not found:" << name << LL_ENDL; - } - else - { - cntrl_ptr->getCommitSignal()->connect(boost::bind(&LLPipeline::refreshCachedSettings)); - } -} - -void LLPipeline::init() -{ - refreshCachedSettings(); - - mRT = &mMainRT; - - gOctreeMaxCapacity = gSavedSettings.getU32("OctreeMaxNodeCapacity"); - gOctreeMinSize = gSavedSettings.getF32("OctreeMinimumNodeSize"); - sDynamicLOD = gSavedSettings.getBOOL("RenderDynamicLOD"); - sRenderAttachedLights = gSavedSettings.getBOOL("RenderAttachedLights"); - sRenderAttachedParticles = gSavedSettings.getBOOL("RenderAttachedParticles"); - - mInitialized = true; - - stop_glerror(); - - //create render pass pools - getPool(LLDrawPool::POOL_ALPHA_PRE_WATER); - getPool(LLDrawPool::POOL_ALPHA_POST_WATER); - getPool(LLDrawPool::POOL_SIMPLE); - getPool(LLDrawPool::POOL_ALPHA_MASK); - getPool(LLDrawPool::POOL_FULLBRIGHT_ALPHA_MASK); - getPool(LLDrawPool::POOL_GRASS); - getPool(LLDrawPool::POOL_FULLBRIGHT); - getPool(LLDrawPool::POOL_BUMP); - getPool(LLDrawPool::POOL_MATERIALS); - getPool(LLDrawPool::POOL_GLOW); - getPool(LLDrawPool::POOL_GLTF_PBR); - getPool(LLDrawPool::POOL_GLTF_PBR_ALPHA_MASK); - - resetFrameStats(); - - if (gSavedSettings.getBOOL("DisableAllRenderFeatures")) - { - clearAllRenderDebugFeatures(); - } - else - { - setAllRenderDebugFeatures(); // By default, all debugging features on - } - clearAllRenderDebugDisplays(); // All debug displays off - - if (gSavedSettings.getBOOL("DisableAllRenderTypes")) - { - clearAllRenderTypes(); - } - else if (gNonInteractive) - { - clearAllRenderTypes(); - } - else - { - setAllRenderTypes(); // By default, all rendering types start enabled - } - - // make sure RenderPerformanceTest persists (hackity hack hack) - // disables non-object rendering (UI, sky, water, etc) - if (gSavedSettings.getBOOL("RenderPerformanceTest")) - { - gSavedSettings.setBOOL("RenderPerformanceTest", false); - gSavedSettings.setBOOL("RenderPerformanceTest", true); - } - - mOldRenderDebugMask = mRenderDebugMask; - - mBackfaceCull = true; - - // Enable features - LLViewerShaderMgr::instance()->setShaders(); - - for (U32 i = 0; i < 2; ++i) - { - mSpotLightFade[i] = 1.f; - } - - if (mCubeVB.isNull()) - { - mCubeVB = ll_create_cube_vb(LLVertexBuffer::MAP_VERTEX); - } - - mDeferredVB = new LLVertexBuffer(DEFERRED_VB_MASK); - mDeferredVB->allocateBuffer(8, 0); - - { - mScreenTriangleVB = new LLVertexBuffer(LLVertexBuffer::MAP_VERTEX); - mScreenTriangleVB->allocateBuffer(3, 0); - LLStrider vert; - mScreenTriangleVB->getVertexStrider(vert); - - vert[0].set(-1, 1, 0); - vert[1].set(-1, -3, 0); - vert[2].set(3, 1, 0); - - mScreenTriangleVB->unmapBuffer(); - } - - // - // Update all settings to trigger a cached settings refresh - // - connectRefreshCachedSettingsSafe("RenderAutoMaskAlphaDeferred"); - connectRefreshCachedSettingsSafe("RenderAutoMaskAlphaNonDeferred"); - connectRefreshCachedSettingsSafe("RenderUseFarClip"); - connectRefreshCachedSettingsSafe("RenderAvatarMaxNonImpostors"); - connectRefreshCachedSettingsSafe("UseOcclusion"); - // DEPRECATED -- connectRefreshCachedSettingsSafe("WindLightUseAtmosShaders"); - // DEPRECATED -- connectRefreshCachedSettingsSafe("RenderDeferred"); - connectRefreshCachedSettingsSafe("RenderDeferredSunWash"); - connectRefreshCachedSettingsSafe("RenderFSAASamples"); - connectRefreshCachedSettingsSafe("RenderResolutionDivisor"); - connectRefreshCachedSettingsSafe("RenderUIBuffer"); - connectRefreshCachedSettingsSafe("RenderShadowDetail"); - connectRefreshCachedSettingsSafe("RenderShadowSplits"); - connectRefreshCachedSettingsSafe("RenderDeferredSSAO"); - connectRefreshCachedSettingsSafe("RenderShadowResolutionScale"); - connectRefreshCachedSettingsSafe("RenderDelayCreation"); - connectRefreshCachedSettingsSafe("RenderAnimateRes"); - connectRefreshCachedSettingsSafe("FreezeTime"); - connectRefreshCachedSettingsSafe("DebugBeaconLineWidth"); - connectRefreshCachedSettingsSafe("RenderHighlightBrightness"); - connectRefreshCachedSettingsSafe("RenderHighlightColor"); - connectRefreshCachedSettingsSafe("RenderHighlightThickness"); - connectRefreshCachedSettingsSafe("RenderSpotLightsInNondeferred"); - connectRefreshCachedSettingsSafe("PreviewAmbientColor"); - connectRefreshCachedSettingsSafe("PreviewDiffuse0"); - connectRefreshCachedSettingsSafe("PreviewSpecular0"); - connectRefreshCachedSettingsSafe("PreviewDiffuse1"); - connectRefreshCachedSettingsSafe("PreviewSpecular1"); - connectRefreshCachedSettingsSafe("PreviewDiffuse2"); - connectRefreshCachedSettingsSafe("PreviewSpecular2"); - connectRefreshCachedSettingsSafe("PreviewDirection0"); - connectRefreshCachedSettingsSafe("PreviewDirection1"); - connectRefreshCachedSettingsSafe("PreviewDirection2"); - connectRefreshCachedSettingsSafe("RenderGlowMaxExtractAlpha"); - connectRefreshCachedSettingsSafe("RenderGlowWarmthAmount"); - connectRefreshCachedSettingsSafe("RenderGlowLumWeights"); - connectRefreshCachedSettingsSafe("RenderGlowWarmthWeights"); - connectRefreshCachedSettingsSafe("RenderGlowResolutionPow"); - connectRefreshCachedSettingsSafe("RenderGlowIterations"); - connectRefreshCachedSettingsSafe("RenderGlowWidth"); - connectRefreshCachedSettingsSafe("RenderGlowStrength"); - connectRefreshCachedSettingsSafe("RenderGlowNoise"); - connectRefreshCachedSettingsSafe("RenderDepthOfField"); - connectRefreshCachedSettingsSafe("RenderDepthOfFieldInEditMode"); - connectRefreshCachedSettingsSafe("CameraFocusTransitionTime"); - connectRefreshCachedSettingsSafe("CameraFNumber"); - connectRefreshCachedSettingsSafe("CameraFocalLength"); - connectRefreshCachedSettingsSafe("CameraFieldOfView"); - connectRefreshCachedSettingsSafe("RenderShadowNoise"); - connectRefreshCachedSettingsSafe("RenderShadowBlurSize"); - connectRefreshCachedSettingsSafe("RenderSSAOScale"); - connectRefreshCachedSettingsSafe("RenderSSAOMaxScale"); - connectRefreshCachedSettingsSafe("RenderSSAOFactor"); - connectRefreshCachedSettingsSafe("RenderSSAOEffect"); - connectRefreshCachedSettingsSafe("RenderShadowOffsetError"); - connectRefreshCachedSettingsSafe("RenderShadowBiasError"); - connectRefreshCachedSettingsSafe("RenderShadowOffset"); - connectRefreshCachedSettingsSafe("RenderShadowBias"); - connectRefreshCachedSettingsSafe("RenderSpotShadowOffset"); - connectRefreshCachedSettingsSafe("RenderSpotShadowBias"); - connectRefreshCachedSettingsSafe("RenderEdgeDepthCutoff"); - connectRefreshCachedSettingsSafe("RenderEdgeNormCutoff"); - connectRefreshCachedSettingsSafe("RenderShadowGaussian"); - connectRefreshCachedSettingsSafe("RenderShadowBlurDistFactor"); - connectRefreshCachedSettingsSafe("RenderDeferredAtmospheric"); - connectRefreshCachedSettingsSafe("RenderHighlightFadeTime"); - connectRefreshCachedSettingsSafe("RenderFarClip"); - connectRefreshCachedSettingsSafe("RenderShadowSplitExponent"); - connectRefreshCachedSettingsSafe("RenderShadowErrorCutoff"); - connectRefreshCachedSettingsSafe("RenderShadowFOVCutoff"); - connectRefreshCachedSettingsSafe("CameraOffset"); - connectRefreshCachedSettingsSafe("CameraMaxCoF"); - connectRefreshCachedSettingsSafe("CameraDoFResScale"); - connectRefreshCachedSettingsSafe("RenderAutoHideSurfaceAreaLimit"); - connectRefreshCachedSettingsSafe("RenderScreenSpaceReflections"); - connectRefreshCachedSettingsSafe("RenderScreenSpaceReflectionIterations"); - connectRefreshCachedSettingsSafe("RenderScreenSpaceReflectionRayStep"); - connectRefreshCachedSettingsSafe("RenderScreenSpaceReflectionDistanceBias"); - connectRefreshCachedSettingsSafe("RenderScreenSpaceReflectionDepthRejectBias"); - connectRefreshCachedSettingsSafe("RenderScreenSpaceReflectionAdaptiveStepMultiplier"); - connectRefreshCachedSettingsSafe("RenderScreenSpaceReflectionGlossySamples"); - connectRefreshCachedSettingsSafe("RenderBufferVisualization"); - gSavedSettings.getControl("RenderAutoHideSurfaceAreaLimit")->getCommitSignal()->connect(boost::bind(&LLPipeline::refreshCachedSettings)); -} - -LLPipeline::~LLPipeline() -{ -} - -void LLPipeline::cleanup() -{ - assertInitialized(); - - mGroupQ1.clear() ; - - for(pool_set_t::iterator iter = mPools.begin(); - iter != mPools.end(); ) - { - pool_set_t::iterator curiter = iter++; - LLDrawPool* poolp = *curiter; - if (poolp->isFacePool()) - { - LLFacePool* face_pool = (LLFacePool*) poolp; - if (face_pool->mReferences.empty()) - { - mPools.erase(curiter); - removeFromQuickLookup( poolp ); - delete poolp; - } - } - else - { - mPools.erase(curiter); - removeFromQuickLookup( poolp ); - delete poolp; - } - } - - if (!mTerrainPools.empty()) - { - LL_WARNS() << "Terrain Pools not cleaned up" << LL_ENDL; - } - if (!mTreePools.empty()) - { - LL_WARNS() << "Tree Pools not cleaned up" << LL_ENDL; - } - - delete mAlphaPoolPreWater; - mAlphaPoolPreWater = nullptr; - delete mAlphaPoolPostWater; - mAlphaPoolPostWater = nullptr; - delete mSkyPool; - mSkyPool = NULL; - delete mTerrainPool; - mTerrainPool = NULL; - delete mWaterPool; - mWaterPool = NULL; - delete mSimplePool; - mSimplePool = NULL; - delete mFullbrightPool; - mFullbrightPool = NULL; - delete mGlowPool; - mGlowPool = NULL; - delete mBumpPool; - mBumpPool = NULL; - // don't delete wl sky pool it was handled above in the for loop - //delete mWLSkyPool; - mWLSkyPool = NULL; - - releaseGLBuffers(); - - mFaceSelectImagep = NULL; - - mMovedList.clear(); - mMovedBridge.clear(); - mShiftList.clear(); - - mInitialized = false; - - mDeferredVB = NULL; - mScreenTriangleVB = nullptr; - - mCubeVB = NULL; - - mReflectionMapManager.cleanup(); -} - -//============================================================================ - -void LLPipeline::destroyGL() -{ - stop_glerror(); - unloadShaders(); - mHighlightFaces.clear(); - - resetDrawOrders(); - - releaseGLBuffers(); - - if (mMeshDirtyQueryObject) - { - glDeleteQueries(1, &mMeshDirtyQueryObject); - mMeshDirtyQueryObject = 0; - } -} - -void LLPipeline::requestResizeScreenTexture() -{ - gResizeScreenTexture = true; -} - -void LLPipeline::requestResizeShadowTexture() -{ - gResizeShadowTexture = true; -} - -void LLPipeline::resizeShadowTexture() -{ - releaseSunShadowTargets(); - releaseSpotShadowTargets(); - allocateShadowBuffer(mRT->width, mRT->height); - gResizeShadowTexture = false; -} - -void LLPipeline::resizeScreenTexture() -{ - if (gPipeline.shadersLoaded()) - { - GLuint resX = gViewerWindow->getWorldViewWidthRaw(); - GLuint resY = gViewerWindow->getWorldViewHeightRaw(); - - if (gResizeScreenTexture || (resX != mRT->screen.getWidth()) || (resY != mRT->screen.getHeight())) - { - releaseScreenBuffers(); - releaseSunShadowTargets(); - releaseSpotShadowTargets(); - allocateScreenBuffer(resX,resY); - gResizeScreenTexture = false; - } - } -} - -bool LLPipeline::allocateScreenBuffer(U32 resX, U32 resY) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY; - eFBOStatus ret = doAllocateScreenBuffer(resX, resY); - - return ret == FBO_SUCCESS_FULLRES; -} - - -LLPipeline::eFBOStatus LLPipeline::doAllocateScreenBuffer(U32 resX, U32 resY) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY; - // try to allocate screen buffers at requested resolution and samples - // - on failure, shrink number of samples and try again - // - if not multisampled, shrink resolution and try again (favor X resolution over Y) - // Make sure to call "releaseScreenBuffers" after each failure to cleanup the partially loaded state - - // refresh cached settings here to protect against inconsistent event handling order - refreshCachedSettings(); - - U32 samples = RenderFSAASamples; - - eFBOStatus ret = FBO_SUCCESS_FULLRES; - if (!allocateScreenBuffer(resX, resY, samples)) - { - //failed to allocate at requested specification, return false - ret = FBO_FAILURE; - - releaseScreenBuffers(); - //reduce number of samples - while (samples > 0) - { - samples /= 2; - if (allocateScreenBuffer(resX, resY, samples)) - { //success - return FBO_SUCCESS_LOWRES; - } - releaseScreenBuffers(); - } - - samples = 0; - - //reduce resolution - while (resY > 0 && resX > 0) - { - resY /= 2; - if (allocateScreenBuffer(resX, resY, samples)) - { - return FBO_SUCCESS_LOWRES; - } - releaseScreenBuffers(); - - resX /= 2; - if (allocateScreenBuffer(resX, resY, samples)) - { - return FBO_SUCCESS_LOWRES; - } - releaseScreenBuffers(); - } - - LL_WARNS() << "Unable to allocate screen buffer at any resolution!" << LL_ENDL; - } - - return ret; -} - -bool LLPipeline::allocateScreenBuffer(U32 resX, U32 resY, U32 samples) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY; - if (mRT == &mMainRT && sReflectionProbesEnabled) - { // hacky -- allocate auxillary buffer - gCubeSnapshot = true; - mReflectionMapManager.initReflectionMaps(); - mRT = &mAuxillaryRT; - U32 res = mReflectionMapManager.mProbeResolution * 4; //multiply by 4 because probes will be 16x super sampled - allocateScreenBuffer(res, res, samples); - mRT = &mMainRT; - gCubeSnapshot = false; - } - - // remember these dimensions - mRT->width = resX; - mRT->height = resY; - - U32 res_mod = RenderResolutionDivisor; - - if (res_mod > 1 && res_mod < resX && res_mod < resY) - { - resX /= res_mod; - resY /= res_mod; - } - - //water reflection texture (always needed as scratch space whether or not transparent water is enabled) - mWaterDis.allocate(resX, resY, GL_RGBA16F, true); - - if (RenderUIBuffer) - { - if (!mRT->uiScreen.allocate(resX,resY, GL_RGBA)) - { - return false; - } - } - - S32 shadow_detail = RenderShadowDetail; - bool ssao = RenderDeferredSSAO; - - //allocate deferred rendering color buffers - if (!mRT->deferredScreen.allocate(resX, resY, GL_RGBA, true)) return false; - if (!addDeferredAttachments(mRT->deferredScreen)) return false; - - GLuint screenFormat = GL_RGBA16F; - - if (!mRT->screen.allocate(resX, resY, screenFormat)) return false; - - mRT->deferredScreen.shareDepthBuffer(mRT->screen); - - if (samples > 0) - { - if (!mRT->fxaaBuffer.allocate(resX, resY, GL_RGBA)) return false; - } - else - { - mRT->fxaaBuffer.release(); - } - - if (shadow_detail > 0 || ssao || RenderDepthOfField || samples > 0) - { //only need mRT->deferredLight for shadows OR ssao OR dof OR fxaa - if (!mRT->deferredLight.allocate(resX, resY, GL_RGBA16F)) return false; - } - else - { - mRT->deferredLight.release(); - } - - allocateShadowBuffer(resX, resY); - - if (!gCubeSnapshot && RenderScreenSpaceReflections) // hack to not allocate mSceneMap for cube snapshots - { - mSceneMap.allocate(resX, resY, GL_RGB, true); - } - - const bool post_hdr = gSavedSettings.getBOOL("RenderPostProcessingHDR"); - const U32 post_color_fmt = post_hdr ? GL_RGBA16F : GL_RGBA; - mPostMap.allocate(resX, resY, post_color_fmt); - - //HACK make screenbuffer allocations start failing after 30 seconds - if (gSavedSettings.getBOOL("SimulateFBOFailure")) - { - return false; - } - - gGL.getTexUnit(0)->disable(); - - stop_glerror(); - - return true; -} - -// must be even to avoid a stripe in the horizontal shadow blur -inline U32 BlurHappySize(U32 x, F32 scale) { return U32( x * scale + 16.0f) & ~0xF; } - -bool LLPipeline::allocateShadowBuffer(U32 resX, U32 resY) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY; - S32 shadow_detail = RenderShadowDetail; - - F32 scale = llmax(0.f, RenderShadowResolutionScale); - U32 sun_shadow_map_width = BlurHappySize(resX, scale); - U32 sun_shadow_map_height = BlurHappySize(resY, scale); - - if (shadow_detail > 0) - { //allocate 4 sun shadow maps - for (U32 i = 0; i < 4; i++) - { - if (!mRT->shadow[i].allocate(sun_shadow_map_width, sun_shadow_map_height, 0, true)) - { - return false; - } - } - } - else - { - for (U32 i = 0; i < 4; i++) - { - releaseSunShadowTarget(i); - } - } - - if (!gCubeSnapshot) // hack to not allocate spot shadow maps during ReflectionMapManager init - { - U32 width = (U32)(resX * scale); - U32 height = width; - - if (shadow_detail > 1) - { //allocate two spot shadow maps - U32 spot_shadow_map_width = width; - U32 spot_shadow_map_height = height; - for (U32 i = 0; i < 2; i++) - { - if (!mSpotShadow[i].allocate(spot_shadow_map_width, spot_shadow_map_height, 0, true)) - { - return false; - } - } - } - else - { - releaseSpotShadowTargets(); - } - } - - - // set up shadow map filtering and compare modes - if (shadow_detail > 0) - { - for (U32 i = 0; i < 4; i++) - { - LLRenderTarget* shadow_target = getSunShadowTarget(i); - if (shadow_target) - { - gGL.getTexUnit(0)->bind(getSunShadowTarget(i), true); - gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_ANISOTROPIC); - gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_CLAMP); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); - } - } - } - - if (shadow_detail > 1 && !gCubeSnapshot) - { - for (U32 i = 0; i < 2; i++) - { - LLRenderTarget* shadow_target = getSpotShadowTarget(i); - if (shadow_target) - { - gGL.getTexUnit(0)->bind(shadow_target, true); - gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_ANISOTROPIC); - gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_CLAMP); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); - } - } - } - - return true; -} - -//static -void LLPipeline::updateRenderTransparentWater() -{ - sRenderTransparentWater = gSavedSettings.getBOOL("RenderTransparentWater"); -} - -// static -void LLPipeline::refreshCachedSettings() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY; - LLPipeline::sAutoMaskAlphaDeferred = gSavedSettings.getBOOL("RenderAutoMaskAlphaDeferred"); - LLPipeline::sAutoMaskAlphaNonDeferred = gSavedSettings.getBOOL("RenderAutoMaskAlphaNonDeferred"); - LLPipeline::sUseFarClip = gSavedSettings.getBOOL("RenderUseFarClip"); - LLVOAvatar::sMaxNonImpostors = gSavedSettings.getU32("RenderAvatarMaxNonImpostors"); - LLVOAvatar::updateImpostorRendering(LLVOAvatar::sMaxNonImpostors); - - LLPipeline::sUseOcclusion = - (!gUseWireframe - && LLFeatureManager::getInstance()->isFeatureAvailable("UseOcclusion") - && gSavedSettings.getBOOL("UseOcclusion")) ? 2 : 0; - - WindLightUseAtmosShaders = true; // DEPRECATED -- gSavedSettings.getBOOL("WindLightUseAtmosShaders"); - RenderDeferred = true; // DEPRECATED -- gSavedSettings.getBOOL("RenderDeferred"); - RenderDeferredSunWash = gSavedSettings.getF32("RenderDeferredSunWash"); - RenderFSAASamples = LLFeatureManager::getInstance()->isFeatureAvailable("RenderFSAASamples") ? gSavedSettings.getU32("RenderFSAASamples") : 0; - RenderResolutionDivisor = gSavedSettings.getU32("RenderResolutionDivisor"); - RenderUIBuffer = gSavedSettings.getBOOL("RenderUIBuffer"); - RenderShadowDetail = gSavedSettings.getS32("RenderShadowDetail"); - RenderShadowSplits = gSavedSettings.getS32("RenderShadowSplits"); - RenderDeferredSSAO = gSavedSettings.getBOOL("RenderDeferredSSAO"); - RenderShadowResolutionScale = gSavedSettings.getF32("RenderShadowResolutionScale"); - RenderDelayCreation = gSavedSettings.getBOOL("RenderDelayCreation"); - RenderAnimateRes = gSavedSettings.getBOOL("RenderAnimateRes"); - FreezeTime = gSavedSettings.getBOOL("FreezeTime"); - DebugBeaconLineWidth = gSavedSettings.getS32("DebugBeaconLineWidth"); - RenderHighlightBrightness = gSavedSettings.getF32("RenderHighlightBrightness"); - RenderHighlightColor = gSavedSettings.getColor4("RenderHighlightColor"); - RenderHighlightThickness = gSavedSettings.getF32("RenderHighlightThickness"); - RenderSpotLightsInNondeferred = gSavedSettings.getBOOL("RenderSpotLightsInNondeferred"); - PreviewAmbientColor = gSavedSettings.getColor4("PreviewAmbientColor"); - PreviewDiffuse0 = gSavedSettings.getColor4("PreviewDiffuse0"); - PreviewSpecular0 = gSavedSettings.getColor4("PreviewSpecular0"); - PreviewDiffuse1 = gSavedSettings.getColor4("PreviewDiffuse1"); - PreviewSpecular1 = gSavedSettings.getColor4("PreviewSpecular1"); - PreviewDiffuse2 = gSavedSettings.getColor4("PreviewDiffuse2"); - PreviewSpecular2 = gSavedSettings.getColor4("PreviewSpecular2"); - PreviewDirection0 = gSavedSettings.getVector3("PreviewDirection0"); - PreviewDirection1 = gSavedSettings.getVector3("PreviewDirection1"); - PreviewDirection2 = gSavedSettings.getVector3("PreviewDirection2"); - RenderGlowMaxExtractAlpha = gSavedSettings.getF32("RenderGlowMaxExtractAlpha"); - RenderGlowWarmthAmount = gSavedSettings.getF32("RenderGlowWarmthAmount"); - RenderGlowLumWeights = gSavedSettings.getVector3("RenderGlowLumWeights"); - RenderGlowWarmthWeights = gSavedSettings.getVector3("RenderGlowWarmthWeights"); - RenderGlowResolutionPow = gSavedSettings.getS32("RenderGlowResolutionPow"); - RenderGlowIterations = gSavedSettings.getS32("RenderGlowIterations"); - RenderGlowWidth = gSavedSettings.getF32("RenderGlowWidth"); - RenderGlowStrength = gSavedSettings.getF32("RenderGlowStrength"); - RenderGlowNoise = gSavedSettings.getBOOL("RenderGlowNoise"); - RenderDepthOfField = gSavedSettings.getBOOL("RenderDepthOfField"); - RenderDepthOfFieldInEditMode = gSavedSettings.getBOOL("RenderDepthOfFieldInEditMode"); - CameraFocusTransitionTime = gSavedSettings.getF32("CameraFocusTransitionTime"); - CameraFNumber = gSavedSettings.getF32("CameraFNumber"); - CameraFocalLength = gSavedSettings.getF32("CameraFocalLength"); - CameraFieldOfView = gSavedSettings.getF32("CameraFieldOfView"); - RenderShadowNoise = gSavedSettings.getF32("RenderShadowNoise"); - RenderShadowBlurSize = gSavedSettings.getF32("RenderShadowBlurSize"); - RenderSSAOScale = gSavedSettings.getF32("RenderSSAOScale"); - RenderSSAOMaxScale = gSavedSettings.getU32("RenderSSAOMaxScale"); - RenderSSAOFactor = gSavedSettings.getF32("RenderSSAOFactor"); - RenderSSAOEffect = gSavedSettings.getVector3("RenderSSAOEffect"); - RenderShadowOffsetError = gSavedSettings.getF32("RenderShadowOffsetError"); - RenderShadowBiasError = gSavedSettings.getF32("RenderShadowBiasError"); - RenderShadowOffset = gSavedSettings.getF32("RenderShadowOffset"); - RenderShadowBias = gSavedSettings.getF32("RenderShadowBias"); - RenderSpotShadowOffset = gSavedSettings.getF32("RenderSpotShadowOffset"); - RenderSpotShadowBias = gSavedSettings.getF32("RenderSpotShadowBias"); - RenderEdgeDepthCutoff = gSavedSettings.getF32("RenderEdgeDepthCutoff"); - RenderEdgeNormCutoff = gSavedSettings.getF32("RenderEdgeNormCutoff"); - RenderShadowGaussian = gSavedSettings.getVector3("RenderShadowGaussian"); - RenderShadowBlurDistFactor = gSavedSettings.getF32("RenderShadowBlurDistFactor"); - RenderDeferredAtmospheric = gSavedSettings.getBOOL("RenderDeferredAtmospheric"); - RenderHighlightFadeTime = gSavedSettings.getF32("RenderHighlightFadeTime"); - RenderFarClip = gSavedSettings.getF32("RenderFarClip"); - RenderShadowSplitExponent = gSavedSettings.getVector3("RenderShadowSplitExponent"); - RenderShadowErrorCutoff = gSavedSettings.getF32("RenderShadowErrorCutoff"); - RenderShadowFOVCutoff = gSavedSettings.getF32("RenderShadowFOVCutoff"); - CameraOffset = gSavedSettings.getBOOL("CameraOffset"); - CameraMaxCoF = gSavedSettings.getF32("CameraMaxCoF"); - CameraDoFResScale = gSavedSettings.getF32("CameraDoFResScale"); - RenderAutoHideSurfaceAreaLimit = gSavedSettings.getF32("RenderAutoHideSurfaceAreaLimit"); - RenderScreenSpaceReflections = gSavedSettings.getBOOL("RenderScreenSpaceReflections"); - RenderScreenSpaceReflectionIterations = gSavedSettings.getS32("RenderScreenSpaceReflectionIterations"); - RenderScreenSpaceReflectionRayStep = gSavedSettings.getF32("RenderScreenSpaceReflectionRayStep"); - RenderScreenSpaceReflectionDistanceBias = gSavedSettings.getF32("RenderScreenSpaceReflectionDistanceBias"); - RenderScreenSpaceReflectionDepthRejectBias = gSavedSettings.getF32("RenderScreenSpaceReflectionDepthRejectBias"); - RenderScreenSpaceReflectionAdaptiveStepMultiplier = gSavedSettings.getF32("RenderScreenSpaceReflectionAdaptiveStepMultiplier"); - RenderScreenSpaceReflectionGlossySamples = gSavedSettings.getS32("RenderScreenSpaceReflectionGlossySamples"); - RenderBufferVisualization = gSavedSettings.getS32("RenderBufferVisualization"); - sReflectionProbesEnabled = LLFeatureManager::getInstance()->isFeatureAvailable("RenderReflectionsEnabled") && gSavedSettings.getBOOL("RenderReflectionsEnabled"); - RenderSpotLight = nullptr; - - if (gNonInteractive) - { - LLVOAvatar::sMaxNonImpostors = 1; - LLVOAvatar::updateImpostorRendering(LLVOAvatar::sMaxNonImpostors); - } -} - -void LLPipeline::releaseGLBuffers() -{ - assertInitialized(); - - if (mNoiseMap) - { - LLImageGL::deleteTextures(1, &mNoiseMap); - mNoiseMap = 0; - } - - if (mTrueNoiseMap) - { - LLImageGL::deleteTextures(1, &mTrueNoiseMap); - mTrueNoiseMap = 0; - } - - releaseLUTBuffers(); - - mWaterDis.release(); - mBake.release(); - - mSceneMap.release(); - - mPostMap.release(); - - for (U32 i = 0; i < 3; i++) - { - mGlow[i].release(); - } - - releaseScreenBuffers(); - - gBumpImageList.destroyGL(); - LLVOAvatar::resetImpostors(); -} - -void LLPipeline::releaseLUTBuffers() -{ - if (mLightFunc) - { - LLImageGL::deleteTextures(1, &mLightFunc); - mLightFunc = 0; - } - - mPbrBrdfLut.release(); - - mExposureMap.release(); - mLuminanceMap.release(); - mLastExposure.release(); - -} - -void LLPipeline::releaseShadowBuffers() -{ - releaseSunShadowTargets(); - releaseSpotShadowTargets(); -} - -void LLPipeline::releaseScreenBuffers() -{ - mRT->uiScreen.release(); - mRT->screen.release(); - mRT->fxaaBuffer.release(); - mRT->deferredScreen.release(); - mRT->deferredLight.release(); -} - -void LLPipeline::releaseSunShadowTarget(U32 index) -{ - llassert(index < 4); - mRT->shadow[index].release(); -} - -void LLPipeline::releaseSunShadowTargets() -{ - for (U32 i = 0; i < 4; i++) - { - releaseSunShadowTarget(i); - } -} - -void LLPipeline::releaseSpotShadowTargets() -{ - if (!gCubeSnapshot) // hack to avoid freeing spot shadows during ReflectionMapManager init - { - for (U32 i = 0; i < 2; i++) - { - mSpotShadow[i].release(); - } - } -} - -void LLPipeline::createGLBuffers() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - stop_glerror(); - assertInitialized(); - - // Use FBO for bake tex - mBake.allocate(512, 512, GL_RGBA, true); // SL-12781 Build > Upload > Model; 3D Preview - - stop_glerror(); - - GLuint resX = gViewerWindow->getWorldViewWidthRaw(); - GLuint resY = gViewerWindow->getWorldViewHeightRaw(); - - // allocate screen space glow buffers - const U32 glow_res = llmax(1, llmin(512, 1 << gSavedSettings.getS32("RenderGlowResolutionPow"))); - const bool glow_hdr = gSavedSettings.getBOOL("RenderGlowHDR"); - const U32 glow_color_fmt = glow_hdr ? GL_RGBA16F : GL_RGBA; - for (U32 i = 0; i < 3; i++) - { - mGlow[i].allocate(512, glow_res, glow_color_fmt); - } - - allocateScreenBuffer(resX, resY); - mRT->width = 0; - mRT->height = 0; - - - if (!mNoiseMap) - { - const U32 noiseRes = 128; - LLVector3 noise[noiseRes*noiseRes]; - - F32 scaler = gSavedSettings.getF32("RenderDeferredNoise")/100.f; - for (U32 i = 0; i < noiseRes*noiseRes; ++i) - { - noise[i] = LLVector3(ll_frand()-0.5f, ll_frand()-0.5f, 0.f); - noise[i].normVec(); - noise[i].mV[2] = ll_frand()*scaler+1.f-scaler/2.f; - } - - LLImageGL::generateTextures(1, &mNoiseMap); - - gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, mNoiseMap); - LLImageGL::setManualImage(LLTexUnit::getInternalType(LLTexUnit::TT_TEXTURE), 0, GL_RGB16F, noiseRes, noiseRes, GL_RGB, GL_FLOAT, noise, false); - gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT); - } - - if (!mTrueNoiseMap) - { - const U32 noiseRes = 128; - F32 noise[noiseRes*noiseRes*3]; - for (U32 i = 0; i < noiseRes*noiseRes*3; i++) - { - noise[i] = ll_frand()*2.0-1.0; - } - - LLImageGL::generateTextures(1, &mTrueNoiseMap); - gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, mTrueNoiseMap); - LLImageGL::setManualImage(LLTexUnit::getInternalType(LLTexUnit::TT_TEXTURE), 0, GL_RGB16F, noiseRes, noiseRes, GL_RGB,GL_FLOAT, noise, false); - gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT); - } - - createLUTBuffers(); - - gBumpImageList.restoreGL(); -} - -F32 lerpf(F32 a, F32 b, F32 w) -{ - return a + w * (b - a); -} - -void LLPipeline::createLUTBuffers() -{ - if (!mLightFunc) - { - U32 lightResX = gSavedSettings.getU32("RenderSpecularResX"); - U32 lightResY = gSavedSettings.getU32("RenderSpecularResY"); - F32* ls = new F32[lightResX*lightResY]; - F32 specExp = gSavedSettings.getF32("RenderSpecularExponent"); - // Calculate the (normalized) blinn-phong specular lookup texture. (with a few tweaks) - for (U32 y = 0; y < lightResY; ++y) - { - for (U32 x = 0; x < lightResX; ++x) - { - ls[y*lightResX+x] = 0; - F32 sa = (F32) x/(lightResX-1); - F32 spec = (F32) y/(lightResY-1); - F32 n = spec * spec * specExp; - - // Nothing special here. Just your typical blinn-phong term. - spec = powf(sa, n); - - // Apply our normalization function. - // Note: This is the full equation that applies the full normalization curve, not an approximation. - // This is fine, given we only need to create our LUT once per buffer initialization. - spec *= (((n + 2) * (n + 4)) / (8 * F_PI * (powf(2, -n/2) + n))); - - // Since we use R16F, we no longer have a dynamic range issue we need to work around here. - // Though some older drivers may not like this, newer drivers shouldn't have this problem. - ls[y*lightResX+x] = spec; - } - } - - U32 pix_format = GL_R16F; -#if LL_DARWIN - // Need to work around limited precision with 10.6.8 and older drivers - // - pix_format = GL_R32F; -#endif - LLImageGL::generateTextures(1, &mLightFunc); - gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, mLightFunc); - LLImageGL::setManualImage(LLTexUnit::getInternalType(LLTexUnit::TT_TEXTURE), 0, pix_format, lightResX, lightResY, GL_RED, GL_FLOAT, ls, false); - gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_CLAMP); - gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_TRILINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); - - delete [] ls; - } - - mPbrBrdfLut.allocate(512, 512, GL_RG16F); - mPbrBrdfLut.bindTarget(); - gDeferredGenBrdfLutProgram.bind(); - - gGL.begin(LLRender::TRIANGLE_STRIP); - gGL.vertex2f(-1, -1); - gGL.vertex2f(-1, 1); - gGL.vertex2f(1, -1); - gGL.vertex2f(1, 1); - gGL.end(); - gGL.flush(); - - gDeferredGenBrdfLutProgram.unbind(); - mPbrBrdfLut.flush(); - - mExposureMap.allocate(1, 1, GL_R16F); - mExposureMap.bindTarget(); - glClearColor(1, 1, 1, 0); - mExposureMap.clear(); - glClearColor(0, 0, 0, 0); - mExposureMap.flush(); - - mLuminanceMap.allocate(256, 256, GL_R16F, false, LLTexUnit::TT_TEXTURE, LLTexUnit::TMG_AUTO); - - mLastExposure.allocate(1, 1, GL_R16F); -} - - -void LLPipeline::restoreGL() -{ - assertInitialized(); - - LLViewerShaderMgr::instance()->setShaders(); - - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) - { - LLViewerRegion* region = *iter; - for (U32 i = 0; i < LLViewerRegion::NUM_PARTITIONS; i++) - { - LLSpatialPartition* part = region->getSpatialPartition(i); - if (part) - { - part->restoreGL(); - } - } - } -} - -bool LLPipeline::shadersLoaded() -{ - return (assertInitialized() && mShadersLoaded); -} - -bool LLPipeline::canUseWindLightShaders() const -{ - return true; -} - -bool LLPipeline::canUseAntiAliasing() const -{ - return true; -} - -void LLPipeline::unloadShaders() -{ - LLViewerShaderMgr::instance()->unloadShaders(); - mShadersLoaded = false; -} - -void LLPipeline::assertInitializedDoError() -{ - LL_ERRS() << "LLPipeline used when uninitialized." << LL_ENDL; -} - -//============================================================================ - -void LLPipeline::enableShadows(const bool enable_shadows) -{ - //should probably do something here to wrangle shadows.... -} - -class LLOctreeDirtyTexture : public OctreeTraveler -{ -public: - const std::set& mTextures; - - LLOctreeDirtyTexture(const std::set& textures) : mTextures(textures) { } - - virtual void visit(const OctreeNode* node) - { - LLSpatialGroup* group = (LLSpatialGroup*) node->getListener(0); - - if (!group->hasState(LLSpatialGroup::GEOM_DIRTY) && !group->isEmpty()) - { - for (LLSpatialGroup::draw_map_t::iterator i = group->mDrawMap.begin(); i != group->mDrawMap.end(); ++i) - { - for (LLSpatialGroup::drawmap_elem_t::iterator j = i->second.begin(); j != i->second.end(); ++j) - { - LLDrawInfo* params = *j; - LLViewerFetchedTexture* tex = LLViewerTextureManager::staticCastToFetchedTexture(params->mTexture); - if (tex && mTextures.find(tex) != mTextures.end()) - { - group->setState(LLSpatialGroup::GEOM_DIRTY); - } - } - } - } - - for (LLSpatialGroup::bridge_list_t::iterator i = group->mBridgeList.begin(); i != group->mBridgeList.end(); ++i) - { - LLSpatialBridge* bridge = *i; - traverse(bridge->mOctree); - } - } -}; - -// Called when a texture changes # of channels (causes faces to move to alpha pool) -void LLPipeline::dirtyPoolObjectTextures(const std::set& textures) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - assertInitialized(); - - // *TODO: This is inefficient and causes frame spikes; need a better way to do this - // Most of the time is spent in dirty.traverse. - - for (pool_set_t::iterator iter = mPools.begin(); iter != mPools.end(); ++iter) - { - LLDrawPool *poolp = *iter; - if (poolp->isFacePool()) - { - ((LLFacePool*) poolp)->dirtyTextures(textures); - } - } - - LLOctreeDirtyTexture dirty(textures); - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) - { - LLViewerRegion* region = *iter; - for (U32 i = 0; i < LLViewerRegion::NUM_PARTITIONS; i++) - { - LLSpatialPartition* part = region->getSpatialPartition(i); - if (part) - { - dirty.traverse(part->mOctree); - } - } - } -} - -LLDrawPool *LLPipeline::findPool(const U32 type, LLViewerTexture *tex0) -{ - assertInitialized(); - - LLDrawPool *poolp = NULL; - switch( type ) - { - case LLDrawPool::POOL_SIMPLE: - poolp = mSimplePool; - break; - - case LLDrawPool::POOL_GRASS: - poolp = mGrassPool; - break; - - case LLDrawPool::POOL_ALPHA_MASK: - poolp = mAlphaMaskPool; - break; - - case LLDrawPool::POOL_FULLBRIGHT_ALPHA_MASK: - poolp = mFullbrightAlphaMaskPool; - break; - - case LLDrawPool::POOL_FULLBRIGHT: - poolp = mFullbrightPool; - break; - - case LLDrawPool::POOL_GLOW: - poolp = mGlowPool; - break; - - case LLDrawPool::POOL_TREE: - poolp = get_if_there(mTreePools, (uintptr_t)tex0, (LLDrawPool*)0 ); - break; - - case LLDrawPool::POOL_TERRAIN: - poolp = get_if_there(mTerrainPools, (uintptr_t)tex0, (LLDrawPool*)0 ); - break; - - case LLDrawPool::POOL_BUMP: - poolp = mBumpPool; - break; - case LLDrawPool::POOL_MATERIALS: - poolp = mMaterialsPool; - break; - case LLDrawPool::POOL_ALPHA_PRE_WATER: - poolp = mAlphaPoolPreWater; - break; - case LLDrawPool::POOL_ALPHA_POST_WATER: - poolp = mAlphaPoolPostWater; - break; - - case LLDrawPool::POOL_AVATAR: - case LLDrawPool::POOL_CONTROL_AV: - break; // Do nothing - - case LLDrawPool::POOL_SKY: - poolp = mSkyPool; - break; - - case LLDrawPool::POOL_WATER: - poolp = mWaterPool; - break; - - case LLDrawPool::POOL_WL_SKY: - poolp = mWLSkyPool; - break; - - case LLDrawPool::POOL_GLTF_PBR: - poolp = mPBROpaquePool; - break; - case LLDrawPool::POOL_GLTF_PBR_ALPHA_MASK: - poolp = mPBRAlphaMaskPool; - break; - - default: - llassert(0); - LL_ERRS() << "Invalid Pool Type in LLPipeline::findPool() type=" << type << LL_ENDL; - break; - } - - return poolp; -} - - -LLDrawPool *LLPipeline::getPool(const U32 type, LLViewerTexture *tex0) -{ - LLDrawPool *poolp = findPool(type, tex0); - if (poolp) - { - return poolp; - } - - LLDrawPool *new_poolp = LLDrawPool::createPool(type, tex0); - addPool( new_poolp ); - - return new_poolp; -} - - -// static -LLDrawPool* LLPipeline::getPoolFromTE(const LLTextureEntry* te, LLViewerTexture* imagep) -{ - U32 type = getPoolTypeFromTE(te, imagep); - return gPipeline.getPool(type, imagep); -} - -//static -U32 LLPipeline::getPoolTypeFromTE(const LLTextureEntry* te, LLViewerTexture* imagep) -{ - if (!te || !imagep) - { - return 0; - } - - LLMaterial* mat = te->getMaterialParams().get(); - LLGLTFMaterial* gltf_mat = te->getGLTFRenderMaterial(); - - bool color_alpha = te->getColor().mV[3] < 0.999f; - bool alpha = color_alpha; - if (imagep) - { - alpha = alpha || (imagep->getComponents() == 4 && imagep->getType() != LLViewerTexture::MEDIA_TEXTURE) || (imagep->getComponents() == 2); - } - - if (alpha && mat) - { - switch (mat->getDiffuseAlphaMode()) - { - case 1: - alpha = true; // Material's alpha mode is set to blend. Toss it into the alpha draw pool. - break; - case 0: //alpha mode set to none, never go to alpha pool - case 3: //alpha mode set to emissive, never go to alpha pool - alpha = color_alpha; - break; - default: //alpha mode set to "mask", go to alpha pool if fullbright - alpha = color_alpha; // Material's alpha mode is set to none, mask, or emissive. Toss it into the opaque material draw pool. - break; - } - } - - if (alpha || (gltf_mat && gltf_mat->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_BLEND)) - { - return LLDrawPool::POOL_ALPHA; - } - else if ((te->getBumpmap() || te->getShiny()) && (!mat || mat->getNormalID().isNull())) - { - return LLDrawPool::POOL_BUMP; - } - else if (gltf_mat) - { - return LLDrawPool::POOL_GLTF_PBR; - } - else if (mat && !alpha) - { - return LLDrawPool::POOL_MATERIALS; - } - else - { - return LLDrawPool::POOL_SIMPLE; - } -} - - -void LLPipeline::addPool(LLDrawPool *new_poolp) -{ - assertInitialized(); - mPools.insert(new_poolp); - addToQuickLookup( new_poolp ); -} - -void LLPipeline::allocDrawable(LLViewerObject *vobj) -{ - LLDrawable *drawable = new LLDrawable(vobj); - vobj->mDrawable = drawable; - - //encompass completely sheared objects by taking - //the most extreme point possible (<1,1,0.5>) - drawable->setRadius(LLVector3(1,1,0.5f).scaleVec(vobj->getScale()).length()); - if (vobj->isOrphaned()) - { - drawable->setState(LLDrawable::FORCE_INVISIBLE); - } - drawable->updateXform(true); -} - - -void LLPipeline::unlinkDrawable(LLDrawable *drawable) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - - assertInitialized(); - - LLPointer drawablep = drawable; // make sure this doesn't get deleted before we are done - - // Based on flags, remove the drawable from the queues that it's on. - if (drawablep->isState(LLDrawable::ON_MOVE_LIST)) - { - LLDrawable::drawable_vector_t::iterator iter = std::find(mMovedList.begin(), mMovedList.end(), drawablep); - if (iter != mMovedList.end()) - { - mMovedList.erase(iter); - } - } - - if (drawablep->getSpatialGroup()) - { - if (!drawablep->getSpatialGroup()->getSpatialPartition()->remove(drawablep, drawablep->getSpatialGroup())) - { -#ifdef LL_RELEASE_FOR_DOWNLOAD - LL_WARNS() << "Couldn't remove object from spatial group!" << LL_ENDL; -#else - LL_ERRS() << "Couldn't remove object from spatial group!" << LL_ENDL; -#endif - } - } - - mLights.erase(drawablep); - - for (light_set_t::iterator iter = mNearbyLights.begin(); - iter != mNearbyLights.end(); iter++) - { - if (iter->drawable == drawablep) - { - mNearbyLights.erase(iter); - break; - } - } - - for (U32 i = 0; i < 2; ++i) - { - if (mShadowSpotLight[i] == drawablep) - { - mShadowSpotLight[i] = NULL; - } - - if (mTargetShadowSpotLight[i] == drawablep) - { - mTargetShadowSpotLight[i] = NULL; - } - } -} - -//static -void LLPipeline::removeMutedAVsLights(LLVOAvatar* muted_avatar) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - light_set_t::iterator iter = gPipeline.mNearbyLights.begin(); - while (iter != gPipeline.mNearbyLights.end()) - { - const LLViewerObject* vobj = iter->drawable->getVObj(); - if (vobj - && vobj->getAvatar() - && vobj->isAttachment() - && vobj->getAvatar() == muted_avatar) - { - gPipeline.mLights.erase(iter->drawable); - iter = gPipeline.mNearbyLights.erase(iter); - } - else - { - iter++; - } - } -} - -U32 LLPipeline::addObject(LLViewerObject *vobj) -{ - if (RenderDelayCreation) - { - mCreateQ.push_back(vobj); - } - else - { - createObject(vobj); - } - - return 1; -} - -void LLPipeline::createObjects(F32 max_dtime) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - - LLTimer update_timer; - - while (!mCreateQ.empty() && update_timer.getElapsedTimeF32() < max_dtime) - { - LLViewerObject* vobj = mCreateQ.front(); - if (!vobj->isDead()) - { - createObject(vobj); - } - mCreateQ.pop_front(); - } - - //for (LLViewerObject::vobj_list_t::iterator iter = mCreateQ.begin(); iter != mCreateQ.end(); ++iter) - //{ - // createObject(*iter); - //} - - //mCreateQ.clear(); -} - -void LLPipeline::createObject(LLViewerObject* vobj) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - LLDrawable* drawablep = vobj->mDrawable; - - if (!drawablep) - { - drawablep = vobj->createDrawable(this); - } - else - { - LL_ERRS() << "Redundant drawable creation!" << LL_ENDL; - } - - llassert(drawablep); - - if (vobj->getParent()) - { - vobj->setDrawableParent(((LLViewerObject*)vobj->getParent())->mDrawable); // LLPipeline::addObject 1 - } - else - { - vobj->setDrawableParent(NULL); // LLPipeline::addObject 2 - } - - markRebuild(drawablep, LLDrawable::REBUILD_ALL); - - if (drawablep->getVOVolume() && RenderAnimateRes) - { - // fun animated res - drawablep->updateXform(true); - drawablep->clearState(LLDrawable::MOVE_UNDAMPED); - drawablep->setScale(LLVector3(0,0,0)); - drawablep->makeActive(); - } -} - - -void LLPipeline::resetFrameStats() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - assertInitialized(); - - sCompiles = 0; - mNumVisibleFaces = 0; - - if (mOldRenderDebugMask != mRenderDebugMask) - { - gObjectList.clearDebugText(); - mOldRenderDebugMask = mRenderDebugMask; - } -} - -//external functions for asynchronous updating -void LLPipeline::updateMoveDampedAsync(LLDrawable* drawablep) -{ - LL_PROFILE_ZONE_SCOPED; - if (FreezeTime) - { - return; - } - if (!drawablep) - { - LL_ERRS() << "updateMove called with NULL drawablep" << LL_ENDL; - return; - } - if (drawablep->isState(LLDrawable::EARLY_MOVE)) - { - return; - } - - assertInitialized(); - - // update drawable now - drawablep->clearState(LLDrawable::MOVE_UNDAMPED); // force to DAMPED - drawablep->updateMove(); // returns done - drawablep->setState(LLDrawable::EARLY_MOVE); // flag says we already did an undamped move this frame - // Put on move list so that EARLY_MOVE gets cleared - if (!drawablep->isState(LLDrawable::ON_MOVE_LIST)) - { - mMovedList.push_back(drawablep); - drawablep->setState(LLDrawable::ON_MOVE_LIST); - } -} - -void LLPipeline::updateMoveNormalAsync(LLDrawable* drawablep) -{ - LL_PROFILE_ZONE_SCOPED; - if (FreezeTime) - { - return; - } - if (!drawablep) - { - LL_ERRS() << "updateMove called with NULL drawablep" << LL_ENDL; - return; - } - if (drawablep->isState(LLDrawable::EARLY_MOVE)) - { - return; - } - - assertInitialized(); - - // update drawable now - drawablep->setState(LLDrawable::MOVE_UNDAMPED); // force to UNDAMPED - drawablep->updateMove(); - drawablep->setState(LLDrawable::EARLY_MOVE); // flag says we already did an undamped move this frame - // Put on move list so that EARLY_MOVE gets cleared - if (!drawablep->isState(LLDrawable::ON_MOVE_LIST)) - { - mMovedList.push_back(drawablep); - drawablep->setState(LLDrawable::ON_MOVE_LIST); - } -} - -void LLPipeline::updateMovedList(LLDrawable::drawable_vector_t& moved_list) -{ - LL_PROFILE_ZONE_SCOPED; - for (LLDrawable::drawable_vector_t::iterator iter = moved_list.begin(); - iter != moved_list.end(); ) - { - LLDrawable::drawable_vector_t::iterator curiter = iter++; - LLDrawable *drawablep = *curiter; - bool done = true; - if (!drawablep->isDead() && (!drawablep->isState(LLDrawable::EARLY_MOVE))) - { - done = drawablep->updateMove(); - } - drawablep->clearState(LLDrawable::EARLY_MOVE | LLDrawable::MOVE_UNDAMPED); - if (done) - { - if (drawablep->isRoot() && !drawablep->isState(LLDrawable::ACTIVE)) - { - drawablep->makeStatic(); - } - drawablep->clearState(LLDrawable::ON_MOVE_LIST); - if (drawablep->isState(LLDrawable::ANIMATED_CHILD)) - { //will likely not receive any future world matrix updates - // -- this keeps attachments from getting stuck in space and falling off your avatar - drawablep->clearState(LLDrawable::ANIMATED_CHILD); - markRebuild(drawablep, LLDrawable::REBUILD_VOLUME); - if (drawablep->getVObj()) - { - drawablep->getVObj()->dirtySpatialGroup(); - } - } - iter = moved_list.erase(curiter); - } - } -} - -void LLPipeline::updateMove() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - - if (FreezeTime) - { - return; - } - - assertInitialized(); - - for (LLDrawable::drawable_set_t::iterator iter = mRetexturedList.begin(); - iter != mRetexturedList.end(); ++iter) - { - LLDrawable* drawablep = *iter; - if (drawablep && !drawablep->isDead()) - { - drawablep->updateTexture(); - } - } - mRetexturedList.clear(); - - updateMovedList(mMovedList); - - //balance octrees - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) - { - LLViewerRegion* region = *iter; - for (U32 i = 0; i < LLViewerRegion::NUM_PARTITIONS; i++) - { - LLSpatialPartition* part = region->getSpatialPartition(i); - if (part) - { - part->mOctree->balance(); - } - } - - //balance the VO Cache tree - LLVOCachePartition* vo_part = region->getVOCachePartition(); - if(vo_part) - { - vo_part->mOctree->balance(); - } - } -} - -///////////////////////////////////////////////////////////////////////////// -// Culling and occlusion testing -///////////////////////////////////////////////////////////////////////////// - -//static -F32 LLPipeline::calcPixelArea(LLVector3 center, LLVector3 size, LLCamera &camera) -{ - llassert(!gCubeSnapshot); // shouldn't be doing ANY of this during cube snap shots - LLVector3 lookAt = center - camera.getOrigin(); - F32 dist = lookAt.length(); - - //ramp down distance for nearby objects - //shrink dist by dist/16. - if (dist < 16.f) - { - dist /= 16.f; - dist *= dist; - dist *= 16.f; - } - - //get area of circle around node - F32 app_angle = atanf(size.length()/dist); - F32 radius = app_angle*LLDrawable::sCurPixelAngle; - return radius*radius * F_PI; -} - -//static -F32 LLPipeline::calcPixelArea(const LLVector4a& center, const LLVector4a& size, LLCamera &camera) -{ - LLVector4a origin; - origin.load3(camera.getOrigin().mV); - - LLVector4a lookAt; - lookAt.setSub(center, origin); - F32 dist = lookAt.getLength3().getF32(); - - //ramp down distance for nearby objects - //shrink dist by dist/16. - if (dist < 16.f) - { - dist /= 16.f; - dist *= dist; - dist *= 16.f; - } - - //get area of circle around node - F32 app_angle = atanf(size.getLength3().getF32()/dist); - F32 radius = app_angle*LLDrawable::sCurPixelAngle; - return radius*radius * F_PI; -} - -void LLPipeline::grabReferences(LLCullResult& result) -{ - sCull = &result; -} - -void LLPipeline::clearReferences() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - sCull = NULL; - mGroupSaveQ1.clear(); -} - -void check_references(LLSpatialGroup* group, LLDrawable* drawable) -{ - for (LLSpatialGroup::element_iter i = group->getDataBegin(); i != group->getDataEnd(); ++i) - { - LLDrawable* drawablep = (LLDrawable*)(*i)->getDrawable(); - if (drawable == drawablep) - { - LL_ERRS() << "LLDrawable deleted while actively reference by LLPipeline." << LL_ENDL; - } - } -} - -void check_references(LLDrawable* drawable, LLFace* face) -{ - for (S32 i = 0; i < drawable->getNumFaces(); ++i) - { - if (drawable->getFace(i) == face) - { - LL_ERRS() << "LLFace deleted while actively referenced by LLPipeline." << LL_ENDL; - } - } -} - -void check_references(LLSpatialGroup* group, LLFace* face) -{ - for (LLSpatialGroup::element_iter i = group->getDataBegin(); i != group->getDataEnd(); ++i) - { - LLDrawable* drawable = (LLDrawable*)(*i)->getDrawable(); - if(drawable) - { - check_references(drawable, face); - } -} -} - -void LLPipeline::checkReferences(LLFace* face) -{ -#if 0 - if (sCull) - { - for (LLCullResult::sg_iterator iter = sCull->beginVisibleGroups(); iter != sCull->endVisibleGroups(); ++iter) - { - LLSpatialGroup* group = *iter; - check_references(group, face); - } - - for (LLCullResult::sg_iterator iter = sCull->beginAlphaGroups(); iter != sCull->endAlphaGroups(); ++iter) - { - LLSpatialGroup* group = *iter; - check_references(group, face); - } - - for (LLCullResult::sg_iterator iter = sCull->beginDrawableGroups(); iter != sCull->endDrawableGroups(); ++iter) - { - LLSpatialGroup* group = *iter; - check_references(group, face); - } - - for (LLCullResult::drawable_iterator iter = sCull->beginVisibleList(); iter != sCull->endVisibleList(); ++iter) - { - LLDrawable* drawable = *iter; - check_references(drawable, face); - } - } -#endif -} - -void LLPipeline::checkReferences(LLDrawable* drawable) -{ -#if 0 - if (sCull) - { - for (LLCullResult::sg_iterator iter = sCull->beginVisibleGroups(); iter != sCull->endVisibleGroups(); ++iter) - { - LLSpatialGroup* group = *iter; - check_references(group, drawable); - } - - for (LLCullResult::sg_iterator iter = sCull->beginAlphaGroups(); iter != sCull->endAlphaGroups(); ++iter) - { - LLSpatialGroup* group = *iter; - check_references(group, drawable); - } - - for (LLCullResult::sg_iterator iter = sCull->beginDrawableGroups(); iter != sCull->endDrawableGroups(); ++iter) - { - LLSpatialGroup* group = *iter; - check_references(group, drawable); - } - - for (LLCullResult::drawable_iterator iter = sCull->beginVisibleList(); iter != sCull->endVisibleList(); ++iter) - { - if (drawable == *iter) - { - LL_ERRS() << "LLDrawable deleted while actively referenced by LLPipeline." << LL_ENDL; - } - } - } -#endif -} - -void check_references(LLSpatialGroup* group, LLDrawInfo* draw_info) -{ - for (LLSpatialGroup::draw_map_t::iterator i = group->mDrawMap.begin(); i != group->mDrawMap.end(); ++i) - { - LLSpatialGroup::drawmap_elem_t& draw_vec = i->second; - for (LLSpatialGroup::drawmap_elem_t::iterator j = draw_vec.begin(); j != draw_vec.end(); ++j) - { - LLDrawInfo* params = *j; - if (params == draw_info) - { - LL_ERRS() << "LLDrawInfo deleted while actively referenced by LLPipeline." << LL_ENDL; - } - } - } -} - - -void LLPipeline::checkReferences(LLDrawInfo* draw_info) -{ -#if 0 - if (sCull) - { - for (LLCullResult::sg_iterator iter = sCull->beginVisibleGroups(); iter != sCull->endVisibleGroups(); ++iter) - { - LLSpatialGroup* group = *iter; - check_references(group, draw_info); - } - - for (LLCullResult::sg_iterator iter = sCull->beginAlphaGroups(); iter != sCull->endAlphaGroups(); ++iter) - { - LLSpatialGroup* group = *iter; - check_references(group, draw_info); - } - - for (LLCullResult::sg_iterator iter = sCull->beginDrawableGroups(); iter != sCull->endDrawableGroups(); ++iter) - { - LLSpatialGroup* group = *iter; - check_references(group, draw_info); - } - } -#endif -} - -void LLPipeline::checkReferences(LLSpatialGroup* group) -{ -#if CHECK_PIPELINE_REFERENCES - if (sCull) - { - for (LLCullResult::sg_iterator iter = sCull->beginVisibleGroups(); iter != sCull->endVisibleGroups(); ++iter) - { - if (group == *iter) - { - LL_ERRS() << "LLSpatialGroup deleted while actively referenced by LLPipeline." << LL_ENDL; - } - } - - for (LLCullResult::sg_iterator iter = sCull->beginAlphaGroups(); iter != sCull->endAlphaGroups(); ++iter) - { - if (group == *iter) - { - LL_ERRS() << "LLSpatialGroup deleted while actively referenced by LLPipeline." << LL_ENDL; - } - } - - for (LLCullResult::sg_iterator iter = sCull->beginDrawableGroups(); iter != sCull->endDrawableGroups(); ++iter) - { - if (group == *iter) - { - LL_ERRS() << "LLSpatialGroup deleted while actively referenced by LLPipeline." << LL_ENDL; - } - } - } -#endif -} - - -bool LLPipeline::visibleObjectsInFrustum(LLCamera& camera) -{ - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) - { - LLViewerRegion* region = *iter; - - for (U32 i = 0; i < LLViewerRegion::NUM_PARTITIONS; i++) - { - LLSpatialPartition* part = region->getSpatialPartition(i); - if (part) - { - if (hasRenderType(part->mDrawableType)) - { - if (part->visibleObjectsInFrustum(camera)) - { - return true; - } - } - } - } - } - - return false; -} - -bool LLPipeline::getVisibleExtents(LLCamera& camera, LLVector3& min, LLVector3& max) -{ - const F32 X = 65536.f; - - min = LLVector3(X,X,X); - max = LLVector3(-X,-X,-X); - - LLViewerCamera::eCameraID saved_camera_id = LLViewerCamera::sCurCameraID; - LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD; - - bool res = true; - - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) - { - LLViewerRegion* region = *iter; - - for (U32 i = 0; i < LLViewerRegion::NUM_PARTITIONS; i++) - { - LLSpatialPartition* part = region->getSpatialPartition(i); - if (part) - { - if (hasRenderType(part->mDrawableType)) - { - if (!part->getVisibleExtents(camera, min, max)) - { - res = false; - } - } - } - } - } - - LLViewerCamera::sCurCameraID = saved_camera_id; - return res; -} - -static LLTrace::BlockTimerStatHandle FTM_CULL("Object Culling"); - -// static -bool LLPipeline::isWaterClip() -{ - return (!sRenderTransparentWater || gCubeSnapshot) && !sRenderingHUDs; -} - -void LLPipeline::updateCull(LLCamera& camera, LLCullResult& result) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; //LL_RECORD_BLOCK_TIME(FTM_CULL); - LL_PROFILE_GPU_ZONE("updateCull"); // should always be zero GPU time, but drop a timer to flush stuff out - - bool water_clip = isWaterClip(); - - if (water_clip) - { - - LLVector3 pnorm; - - F32 water_height = LLEnvironment::instance().getWaterHeight(); - - if (sUnderWaterRender) - { - //camera is below water, cull above water - pnorm.setVec(0, 0, 1); - } - else - { - //camera is above water, cull below water - pnorm = LLVector3(0, 0, -1); - } - - LLPlane plane; - plane.setVec(LLVector3(0, 0, water_height), pnorm); - - camera.setUserClipPlane(plane); - } - else - { - camera.disableUserClipPlane(); - } - - grabReferences(result); - - sCull->clear(); - - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) - { - LLViewerRegion* region = *iter; - - for (U32 i = 0; i < LLViewerRegion::NUM_PARTITIONS; i++) - { - LLSpatialPartition* part = region->getSpatialPartition(i); - if (part) - { - if (hasRenderType(part->mDrawableType)) - { - part->cull(camera); - } - } - } - - //scan the VO Cache tree - LLVOCachePartition* vo_part = region->getVOCachePartition(); - if(vo_part) - { - vo_part->cull(camera, sUseOcclusion > 0); - } - } - - if (hasRenderType(LLPipeline::RENDER_TYPE_SKY) && - gSky.mVOSkyp.notNull() && - gSky.mVOSkyp->mDrawable.notNull()) - { - gSky.mVOSkyp->mDrawable->setVisible(camera); - sCull->pushDrawable(gSky.mVOSkyp->mDrawable); - gSky.updateCull(); - stop_glerror(); - } - - if (hasRenderType(LLPipeline::RENDER_TYPE_WL_SKY) && - gPipeline.canUseWindLightShaders() && - gSky.mVOWLSkyp.notNull() && - gSky.mVOWLSkyp->mDrawable.notNull()) - { - gSky.mVOWLSkyp->mDrawable->setVisible(camera); - sCull->pushDrawable(gSky.mVOWLSkyp->mDrawable); - } -} - -void LLPipeline::markNotCulled(LLSpatialGroup* group, LLCamera& camera) -{ - if (group->isEmpty()) - { - return; - } - - group->setVisible(); - - if (LLViewerCamera::sCurCameraID == LLViewerCamera::CAMERA_WORLD && !gCubeSnapshot) - { - group->updateDistance(camera); - } - - assertInitialized(); - - if (!group->getSpatialPartition()->mRenderByGroup) - { //render by drawable - sCull->pushDrawableGroup(group); - } - else - { //render by group - sCull->pushVisibleGroup(group); - } - - if (group->needsUpdate() || - group->getVisible(LLViewerCamera::sCurCameraID) < LLDrawable::getCurrentFrame() - 1) - { - // include this group in occlusion groups, not because it is an occluder, but because we want to run - // an occlusion query to find out if it's an occluder - markOccluder(group); - } - mNumVisibleNodes++; -} - -void LLPipeline::markOccluder(LLSpatialGroup* group) -{ - if (sUseOcclusion > 1 && group && !group->isOcclusionState(LLSpatialGroup::ACTIVE_OCCLUSION)) - { - LLSpatialGroup* parent = group->getParent(); - - if (!parent || !parent->isOcclusionState(LLSpatialGroup::OCCLUDED)) - { //only mark top most occluders as active occlusion - sCull->pushOcclusionGroup(group); - group->setOcclusionState(LLSpatialGroup::ACTIVE_OCCLUSION); - - if (parent && - !parent->isOcclusionState(LLSpatialGroup::ACTIVE_OCCLUSION) && - parent->getElementCount() == 0 && - parent->needsUpdate()) - { - sCull->pushOcclusionGroup(group); - parent->setOcclusionState(LLSpatialGroup::ACTIVE_OCCLUSION); - } - } - } -} - -void LLPipeline::doOcclusion(LLCamera& camera) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - LL_PROFILE_GPU_ZONE("doOcclusion"); - llassert(!gCubeSnapshot); - - if (sReflectionProbesEnabled && sUseOcclusion > 1 && !LLPipeline::sShadowRender && !gCubeSnapshot) - { - gGL.setColorMask(false, false); - LLGLDepthTest depth(GL_TRUE, GL_FALSE); - LLGLDisable cull(GL_CULL_FACE); - - gOcclusionCubeProgram.bind(); - - if (mCubeVB.isNull()) - { //cube VB will be used for issuing occlusion queries - mCubeVB = ll_create_cube_vb(LLVertexBuffer::MAP_VERTEX); - } - mCubeVB->setBuffer(); - - mReflectionMapManager.doOcclusion(); - gOcclusionCubeProgram.unbind(); - - gGL.setColorMask(true, true); - } - - if (LLPipeline::sUseOcclusion > 1 && - (sCull->hasOcclusionGroups() || LLVOCachePartition::sNeedsOcclusionCheck)) - { - LLVertexBuffer::unbind(); - - gGL.setColorMask(false, false); - - LLGLDisable blend(GL_BLEND); - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - LLGLDepthTest depth(GL_TRUE, GL_FALSE); - - LLGLDisable cull(GL_CULL_FACE); - - gOcclusionCubeProgram.bind(); - - if (mCubeVB.isNull()) - { //cube VB will be used for issuing occlusion queries - mCubeVB = ll_create_cube_vb(LLVertexBuffer::MAP_VERTEX); - } - mCubeVB->setBuffer(); - - for (LLCullResult::sg_iterator iter = sCull->beginOcclusionGroups(); iter != sCull->endOcclusionGroups(); ++iter) - { - LLSpatialGroup* group = *iter; - if (!group->isDead()) - { - group->doOcclusion(&camera); - group->clearOcclusionState(LLSpatialGroup::ACTIVE_OCCLUSION); - } - } - - //apply occlusion culling to object cache tree - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) - { - LLVOCachePartition* vo_part = (*iter)->getVOCachePartition(); - if(vo_part) - { - vo_part->processOccluders(&camera); - } - } - - gGL.setColorMask(true, true); - } -} - -bool LLPipeline::updateDrawableGeom(LLDrawable* drawablep) -{ - bool update_complete = drawablep->updateGeometry(); - if (update_complete && assertInitialized()) - { - drawablep->setState(LLDrawable::BUILT); - } - return update_complete; -} - -void LLPipeline::updateGL() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - { - while (!LLGLUpdate::sGLQ.empty()) - { - LLGLUpdate* glu = LLGLUpdate::sGLQ.front(); - glu->updateGL(); - glu->mInQ = false; - LLGLUpdate::sGLQ.pop_front(); - } - } -} - -void LLPipeline::clearRebuildGroups() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - LLSpatialGroup::sg_vector_t hudGroups; - - mGroupQ1Locked = true; - // Iterate through all drawables on the priority build queue, - for (LLSpatialGroup::sg_vector_t::iterator iter = mGroupQ1.begin(); - iter != mGroupQ1.end(); ++iter) - { - LLSpatialGroup* group = *iter; - - // If the group contains HUD objects, save the group - if (group->isHUDGroup()) - { - hudGroups.push_back(group); - } - // Else, no HUD objects so clear the build state - else - { - group->clearState(LLSpatialGroup::IN_BUILD_Q1); - } - } - - // Clear the group - mGroupQ1.clear(); - - // Copy the saved HUD groups back in - mGroupQ1.assign(hudGroups.begin(), hudGroups.end()); - mGroupQ1Locked = false; -} - -void LLPipeline::clearRebuildDrawables() -{ - // Clear all drawables on the priority build queue, - for (LLDrawable::drawable_list_t::iterator iter = mBuildQ1.begin(); - iter != mBuildQ1.end(); ++iter) - { - LLDrawable* drawablep = *iter; - if (drawablep && !drawablep->isDead()) - { - drawablep->clearState(LLDrawable::IN_REBUILD_Q); - } - } - mBuildQ1.clear(); - - //clear all moving bridges - for (LLDrawable::drawable_vector_t::iterator iter = mMovedBridge.begin(); - iter != mMovedBridge.end(); ++iter) - { - LLDrawable *drawablep = *iter; - drawablep->clearState(LLDrawable::EARLY_MOVE | LLDrawable::MOVE_UNDAMPED | LLDrawable::ON_MOVE_LIST | LLDrawable::ANIMATED_CHILD); - } - mMovedBridge.clear(); - - //clear all moving drawables - for (LLDrawable::drawable_vector_t::iterator iter = mMovedList.begin(); - iter != mMovedList.end(); ++iter) - { - LLDrawable *drawablep = *iter; - drawablep->clearState(LLDrawable::EARLY_MOVE | LLDrawable::MOVE_UNDAMPED | LLDrawable::ON_MOVE_LIST | LLDrawable::ANIMATED_CHILD); - } - mMovedList.clear(); - - for (LLDrawable::drawable_vector_t::iterator iter = mShiftList.begin(); - iter != mShiftList.end(); ++iter) - { - LLDrawable *drawablep = *iter; - drawablep->clearState(LLDrawable::EARLY_MOVE | LLDrawable::MOVE_UNDAMPED | LLDrawable::ON_MOVE_LIST | LLDrawable::ANIMATED_CHILD | LLDrawable::ON_SHIFT_LIST); - } - mShiftList.clear(); -} - -void LLPipeline::rebuildPriorityGroups() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - LL_PROFILE_GPU_ZONE("rebuildPriorityGroups"); - - LLTimer update_timer; - assertInitialized(); - - gMeshRepo.notifyLoadedMeshes(); - - mGroupQ1Locked = true; - // Iterate through all drawables on the priority build queue, - for (LLSpatialGroup::sg_vector_t::iterator iter = mGroupQ1.begin(); - iter != mGroupQ1.end(); ++iter) - { - LLSpatialGroup* group = *iter; - group->rebuildGeom(); - group->clearState(LLSpatialGroup::IN_BUILD_Q1); - } - - mGroupSaveQ1 = mGroupQ1; - mGroupQ1.clear(); - mGroupQ1Locked = false; - -} - -void LLPipeline::updateGeom(F32 max_dtime) -{ - LLTimer update_timer; - LLPointer drawablep; - - LL_RECORD_BLOCK_TIME(FTM_GEO_UPDATE); - if (gCubeSnapshot) - { - return; - } - - assertInitialized(); - - // notify various object types to reset internal cost metrics, etc. - // for now, only LLVOVolume does this to throttle LOD changes - LLVOVolume::preUpdateGeom(); - - // Iterate through all drawables on the priority build queue, - for (LLDrawable::drawable_list_t::iterator iter = mBuildQ1.begin(); - iter != mBuildQ1.end();) - { - LLDrawable::drawable_list_t::iterator curiter = iter++; - LLDrawable* drawablep = *curiter; - if (drawablep && !drawablep->isDead()) - { - if (drawablep->isUnload()) - { - drawablep->unload(); - drawablep->clearState(LLDrawable::FOR_UNLOAD); - } - - if (updateDrawableGeom(drawablep)) - { - drawablep->clearState(LLDrawable::IN_REBUILD_Q); - mBuildQ1.erase(curiter); - } - } - else - { - mBuildQ1.erase(curiter); - } - } - - updateMovedList(mMovedBridge); -} - -void LLPipeline::markVisible(LLDrawable *drawablep, LLCamera& camera) -{ - if(drawablep && !drawablep->isDead()) - { - if (drawablep->isSpatialBridge()) - { - const LLDrawable* root = ((LLSpatialBridge*) drawablep)->mDrawable; - llassert(root); // trying to catch a bad assumption - - if (root && // // this test may not be needed, see above - root->getVObj()->isAttachment()) - { - LLDrawable* rootparent = root->getParent(); - if (rootparent) // this IS sometimes NULL - { - LLViewerObject *vobj = rootparent->getVObj(); - llassert(vobj); // trying to catch a bad assumption - if (vobj) // this test may not be needed, see above - { - LLVOAvatar* av = vobj->asAvatar(); - if (av && - ((!sImpostorRender && av->isImpostor()) //ignore impostor flag during impostor pass - || av->isInMuteList() - || (LLVOAvatar::AOA_JELLYDOLL == av->getOverallAppearance() && !av->needsImpostorUpdate()) )) - { - return; - } - } - } - } - sCull->pushBridge((LLSpatialBridge*) drawablep); - } - else - { - - sCull->pushDrawable(drawablep); - } - - drawablep->setVisible(camera); - } -} - -void LLPipeline::markMoved(LLDrawable *drawablep, bool damped_motion) -{ - if (!drawablep) - { - //LL_ERRS() << "Sending null drawable to moved list!" << LL_ENDL; - return; - } - - if (drawablep->isDead()) - { - LL_WARNS() << "Marking NULL or dead drawable moved!" << LL_ENDL; - return; - } - - if (drawablep->getParent()) - { - //ensure that parent drawables are moved first - markMoved(drawablep->getParent(), damped_motion); - } - - assertInitialized(); - - if (!drawablep->isState(LLDrawable::ON_MOVE_LIST)) - { - if (drawablep->isSpatialBridge()) - { - mMovedBridge.push_back(drawablep); - } - else - { - mMovedList.push_back(drawablep); - } - drawablep->setState(LLDrawable::ON_MOVE_LIST); - } - if (! damped_motion) - { - drawablep->setState(LLDrawable::MOVE_UNDAMPED); // UNDAMPED trumps DAMPED - } - else if (drawablep->isState(LLDrawable::MOVE_UNDAMPED)) - { - drawablep->clearState(LLDrawable::MOVE_UNDAMPED); - } -} - -void LLPipeline::markShift(LLDrawable *drawablep) -{ - if (!drawablep || drawablep->isDead()) - { - return; - } - - assertInitialized(); - - if (!drawablep->isState(LLDrawable::ON_SHIFT_LIST)) - { - drawablep->getVObj()->setChanged(LLXform::SHIFTED | LLXform::SILHOUETTE); - if (drawablep->getParent()) - { - markShift(drawablep->getParent()); - } - mShiftList.push_back(drawablep); - drawablep->setState(LLDrawable::ON_SHIFT_LIST); - } -} - -void LLPipeline::shiftObjects(const LLVector3 &offset) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - assertInitialized(); - - glClear(GL_DEPTH_BUFFER_BIT); - gDepthDirty = true; - - LLVector4a offseta; - offseta.load3(offset.mV); - - for (LLDrawable::drawable_vector_t::iterator iter = mShiftList.begin(); - iter != mShiftList.end(); iter++) - { - LLDrawable *drawablep = *iter; - if (drawablep->isDead()) - { - continue; - } - drawablep->shiftPos(offseta); - drawablep->clearState(LLDrawable::ON_SHIFT_LIST); - } - mShiftList.resize(0); - - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) - { - LLViewerRegion* region = *iter; - for (U32 i = 0; i < LLViewerRegion::NUM_PARTITIONS; i++) - { - LLSpatialPartition* part = region->getSpatialPartition(i); - if (part) - { - part->shift(offseta); - } - } - } - - mReflectionMapManager.shift(offseta); - - LLHUDText::shiftAll(offset); - LLHUDNameTag::shiftAll(offset); - - display_update_camera(); -} - -void LLPipeline::markTextured(LLDrawable *drawablep) -{ - if (drawablep && !drawablep->isDead() && assertInitialized()) - { - mRetexturedList.insert(drawablep); - } -} - -void LLPipeline::markGLRebuild(LLGLUpdate* glu) -{ - if (glu && !glu->mInQ) - { - LLGLUpdate::sGLQ.push_back(glu); - glu->mInQ = true; - } -} - -void LLPipeline::markPartitionMove(LLDrawable* drawable) -{ - if (!drawable->isState(LLDrawable::PARTITION_MOVE) && - !drawable->getPositionGroup().equals3(LLVector4a::getZero())) - { - drawable->setState(LLDrawable::PARTITION_MOVE); - mPartitionQ.push_back(drawable); - } -} - -void LLPipeline::processPartitionQ() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - for (LLDrawable::drawable_list_t::iterator iter = mPartitionQ.begin(); iter != mPartitionQ.end(); ++iter) - { - LLDrawable* drawable = *iter; - if (!drawable->isDead()) - { - drawable->updateBinRadius(); - drawable->movePartition(); - } - drawable->clearState(LLDrawable::PARTITION_MOVE); - } - - mPartitionQ.clear(); -} - -void LLPipeline::markMeshDirty(LLSpatialGroup* group) -{ - mMeshDirtyGroup.push_back(group); -} - -void LLPipeline::markRebuild(LLSpatialGroup* group) -{ - if (group && !group->isDead() && group->getSpatialPartition()) - { - if (!group->hasState(LLSpatialGroup::IN_BUILD_Q1)) - { - llassert_always(!mGroupQ1Locked); - - mGroupQ1.push_back(group); - group->setState(LLSpatialGroup::IN_BUILD_Q1); - } - } -} - -void LLPipeline::markRebuild(LLDrawable *drawablep, LLDrawable::EDrawableFlags flag) -{ - if (drawablep && !drawablep->isDead() && assertInitialized()) - { - if (!drawablep->isState(LLDrawable::IN_REBUILD_Q)) - { - mBuildQ1.push_back(drawablep); - drawablep->setState(LLDrawable::IN_REBUILD_Q); // mark drawable as being in priority queue - } - - if (flag & (LLDrawable::REBUILD_VOLUME | LLDrawable::REBUILD_POSITION)) - { - drawablep->getVObj()->setChanged(LLXform::SILHOUETTE); - } - drawablep->setState(flag); - } -} - -void LLPipeline::stateSort(LLCamera& camera, LLCullResult &result) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - LL_PROFILE_GPU_ZONE("stateSort"); - - if (hasAnyRenderType(LLPipeline::RENDER_TYPE_AVATAR, - LLPipeline::RENDER_TYPE_CONTROL_AV, - LLPipeline::RENDER_TYPE_TERRAIN, - LLPipeline::RENDER_TYPE_TREE, - LLPipeline::RENDER_TYPE_SKY, - LLPipeline::RENDER_TYPE_VOIDWATER, - LLPipeline::RENDER_TYPE_WATER, - LLPipeline::END_RENDER_TYPES)) - { - //clear faces from face pools - gPipeline.resetDrawOrders(); - } - - //LLVertexBuffer::unbind(); - - grabReferences(result); - for (LLCullResult::sg_iterator iter = sCull->beginDrawableGroups(); iter != sCull->endDrawableGroups(); ++iter) - { - LLSpatialGroup* group = *iter; - if (group->isDead()) - { - continue; - } - group->checkOcclusion(); - if (sUseOcclusion > 1 && group->isOcclusionState(LLSpatialGroup::OCCLUDED)) - { - markOccluder(group); - } - else - { - group->setVisible(); - for (LLSpatialGroup::element_iter i = group->getDataBegin(); i != group->getDataEnd(); ++i) - { - LLDrawable* drawablep = (LLDrawable*)(*i)->getDrawable(); - markVisible(drawablep, camera); - } - - { //rebuild mesh as soon as we know it's visible - group->rebuildMesh(); - } - } - } - - if (LLViewerCamera::sCurCameraID == LLViewerCamera::CAMERA_WORLD && !gCubeSnapshot) - { - LLSpatialGroup* last_group = NULL; - bool fov_changed = LLViewerCamera::getInstance()->isDefaultFOVChanged(); - for (LLCullResult::bridge_iterator i = sCull->beginVisibleBridge(); i != sCull->endVisibleBridge(); ++i) - { - LLCullResult::bridge_iterator cur_iter = i; - LLSpatialBridge* bridge = *cur_iter; - LLSpatialGroup* group = bridge->getSpatialGroup(); - - if (last_group == NULL) - { - last_group = group; - } - - if (!bridge->isDead() && group && !group->isOcclusionState(LLSpatialGroup::OCCLUDED)) - { - stateSort(bridge, camera, fov_changed); - } - - if (LLViewerCamera::sCurCameraID == LLViewerCamera::CAMERA_WORLD && - last_group != group && last_group->changeLOD()) - { - last_group->mLastUpdateDistance = last_group->mDistance; - } - - last_group = group; - } - - if (LLViewerCamera::sCurCameraID == LLViewerCamera::CAMERA_WORLD && - last_group && last_group->changeLOD()) - { - last_group->mLastUpdateDistance = last_group->mDistance; - } - } - - for (LLCullResult::sg_iterator iter = sCull->beginVisibleGroups(); iter != sCull->endVisibleGroups(); ++iter) - { - LLSpatialGroup* group = *iter; - if (group->isDead()) - { - continue; - } - group->checkOcclusion(); - if (sUseOcclusion > 1 && group->isOcclusionState(LLSpatialGroup::OCCLUDED)) - { - markOccluder(group); - } - else - { - group->setVisible(); - stateSort(group, camera); - - { //rebuild mesh as soon as we know it's visible - group->rebuildMesh(); - } - } - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWABLE("stateSort"); // LL_RECORD_BLOCK_TIME(FTM_STATESORT_DRAWABLE); - for (LLCullResult::drawable_iterator iter = sCull->beginVisibleList(); - iter != sCull->endVisibleList(); ++iter) - { - LLDrawable *drawablep = *iter; - if (!drawablep->isDead()) - { - stateSort(drawablep, camera); - } - } - } - - postSort(camera); -} - -void LLPipeline::stateSort(LLSpatialGroup* group, LLCamera& camera) -{ - if (group->changeLOD()) - { - for (LLSpatialGroup::element_iter i = group->getDataBegin(); i != group->getDataEnd(); ++i) - { - LLDrawable* drawablep = (LLDrawable*)(*i)->getDrawable(); - stateSort(drawablep, camera); - } - - if (LLViewerCamera::sCurCameraID == LLViewerCamera::CAMERA_WORLD && !gCubeSnapshot) - { //avoid redundant stateSort calls - group->mLastUpdateDistance = group->mDistance; - } - } -} - -void LLPipeline::stateSort(LLSpatialBridge* bridge, LLCamera& camera, bool fov_changed) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - if (bridge->getSpatialGroup()->changeLOD() || fov_changed) - { - bool force_update = false; - bridge->updateDistance(camera, force_update); - } -} - -void LLPipeline::stateSort(LLDrawable* drawablep, LLCamera& camera) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - if (!drawablep - || drawablep->isDead() - || !hasRenderType(drawablep->getRenderType())) - { - return; - } - - // SL-11353 - // ignore our own geo when rendering spotlight shadowmaps... - // - if (RenderSpotLight && drawablep == RenderSpotLight) - { - return; - } - - if (LLSelectMgr::getInstance()->mHideSelectedObjects) - { - if (drawablep->getVObj().notNull() && - drawablep->getVObj()->isSelected()) - { - return; - } - } - - if (drawablep->isAvatar()) - { //don't draw avatars beyond render distance or if we don't have a spatial group. - if ((drawablep->getSpatialGroup() == NULL) || - (drawablep->getSpatialGroup()->mDistance > LLVOAvatar::sRenderDistance)) - { - return; - } - - LLVOAvatar* avatarp = (LLVOAvatar*) drawablep->getVObj().get(); - if (!avatarp->isVisible()) - { - return; - } - } - - assertInitialized(); - - if (hasRenderType(drawablep->mRenderType)) - { - if (!drawablep->isState(LLDrawable::INVISIBLE|LLDrawable::FORCE_INVISIBLE)) - { - drawablep->setVisible(camera, NULL, false); - } - } - - if (LLViewerCamera::sCurCameraID == LLViewerCamera::CAMERA_WORLD && !gCubeSnapshot) - { - //if (drawablep->isVisible()) isVisible() check here is redundant, if it wasn't visible, it wouldn't be here - { - if (!drawablep->isActive()) - { - bool force_update = false; - drawablep->updateDistance(camera, force_update); - } - else if (drawablep->isAvatar()) - { - bool force_update = false; - drawablep->updateDistance(camera, force_update); // calls vobj->updateLOD() which calls LLVOAvatar::updateVisibility() - } - } - } - - if (!drawablep->getVOVolume()) - { - for (LLDrawable::face_list_t::iterator iter = drawablep->mFaces.begin(); - iter != drawablep->mFaces.end(); iter++) - { - LLFace* facep = *iter; - - if (facep->hasGeometry()) - { - if (facep->getPool()) - { - facep->getPool()->enqueue(facep); - } - else - { - break; - } - } - } - } - - mNumVisibleFaces += drawablep->getNumFaces(); -} - - -void forAllDrawables(LLCullResult::sg_iterator begin, - LLCullResult::sg_iterator end, - void (*func)(LLDrawable*)) -{ - for (LLCullResult::sg_iterator i = begin; i != end; ++i) - { - LLSpatialGroup* group = *i; - if (group->isDead()) - { - continue; - } - for (LLSpatialGroup::element_iter j = group->getDataBegin(); j != group->getDataEnd(); ++j) - { - if((*j)->hasDrawable()) - { - func((LLDrawable*)(*j)->getDrawable()); - } - } - } -} - -void LLPipeline::forAllVisibleDrawables(void (*func)(LLDrawable*)) -{ - forAllDrawables(sCull->beginDrawableGroups(), sCull->endDrawableGroups(), func); - forAllDrawables(sCull->beginVisibleGroups(), sCull->endVisibleGroups(), func); -} - -//function for creating scripted beacons -void renderScriptedBeacons(LLDrawable* drawablep) -{ - LLViewerObject *vobj = drawablep->getVObj(); - if (vobj - && !vobj->isAvatar() - && !vobj->getParent() - && vobj->flagScripted()) - { - if (gPipeline.sRenderBeacons) - { - gObjectList.addDebugBeacon(vobj->getPositionAgent(), "", LLColor4(1.f, 0.f, 0.f, 0.5f), LLColor4(1.f, 1.f, 1.f, 0.5f), LLPipeline::DebugBeaconLineWidth); - } - - if (gPipeline.sRenderHighlight) - { - S32 face_id; - S32 count = drawablep->getNumFaces(); - for (face_id = 0; face_id < count; face_id++) - { - LLFace * facep = drawablep->getFace(face_id); - if (facep) - { - gPipeline.mHighlightFaces.push_back(facep); - } - } - } - } -} - -void renderScriptedTouchBeacons(LLDrawable *drawablep) -{ - LLViewerObject *vobj = drawablep->getVObj(); - if (vobj && !vobj->isAvatar() && !vobj->getParent() && vobj->flagScripted() && vobj->flagHandleTouch()) - { - if (gPipeline.sRenderBeacons) - { - gObjectList.addDebugBeacon(vobj->getPositionAgent(), "", LLColor4(1.f, 0.f, 0.f, 0.5f), LLColor4(1.f, 1.f, 1.f, 0.5f), - LLPipeline::DebugBeaconLineWidth); - } - - if (gPipeline.sRenderHighlight) - { - S32 face_id; - S32 count = drawablep->getNumFaces(); - for (face_id = 0; face_id < count; face_id++) - { - LLFace *facep = drawablep->getFace(face_id); - if (facep) - { - gPipeline.mHighlightFaces.push_back(facep); - } - } - } - } -} - -void renderPhysicalBeacons(LLDrawable *drawablep) -{ - LLViewerObject *vobj = drawablep->getVObj(); - if (vobj && - !vobj->isAvatar() - //&& !vobj->getParent() - && vobj->flagUsePhysics()) - { - if (gPipeline.sRenderBeacons) - { - gObjectList.addDebugBeacon(vobj->getPositionAgent(), "", LLColor4(0.f, 1.f, 0.f, 0.5f), LLColor4(1.f, 1.f, 1.f, 0.5f), - LLPipeline::DebugBeaconLineWidth); - } - - if (gPipeline.sRenderHighlight) - { - S32 face_id; - S32 count = drawablep->getNumFaces(); - for (face_id = 0; face_id < count; face_id++) - { - LLFace *facep = drawablep->getFace(face_id); - if (facep) - { - gPipeline.mHighlightFaces.push_back(facep); - } - } - } - } -} - -void renderMOAPBeacons(LLDrawable *drawablep) -{ - LLViewerObject *vobj = drawablep->getVObj(); - - if (!vobj || vobj->isAvatar()) - return; - - bool beacon = false; - U8 tecount = vobj->getNumTEs(); - for (int x = 0; x < tecount; x++) - { - if (vobj->getTE(x)->hasMedia()) - { - beacon = true; - break; - } - } - if (beacon) - { - if (gPipeline.sRenderBeacons) - { - gObjectList.addDebugBeacon(vobj->getPositionAgent(), "", LLColor4(1.f, 1.f, 1.f, 0.5f), LLColor4(1.f, 1.f, 1.f, 0.5f), - LLPipeline::DebugBeaconLineWidth); - } - - if (gPipeline.sRenderHighlight) - { - S32 face_id; - S32 count = drawablep->getNumFaces(); - for (face_id = 0; face_id < count; face_id++) - { - LLFace *facep = drawablep->getFace(face_id); - if (facep) - { - gPipeline.mHighlightFaces.push_back(facep); - } - } - } - } -} - -void renderParticleBeacons(LLDrawable *drawablep) -{ - // Look for attachments, objects, etc. - LLViewerObject *vobj = drawablep->getVObj(); - if (vobj && vobj->isParticleSource()) - { - if (gPipeline.sRenderBeacons) - { - LLColor4 light_blue(0.5f, 0.5f, 1.f, 0.5f); - gObjectList.addDebugBeacon(vobj->getPositionAgent(), "", light_blue, LLColor4(1.f, 1.f, 1.f, 0.5f), - LLPipeline::DebugBeaconLineWidth); - } - - if (gPipeline.sRenderHighlight) - { - S32 face_id; - S32 count = drawablep->getNumFaces(); - for (face_id = 0; face_id < count; face_id++) - { - LLFace *facep = drawablep->getFace(face_id); - if (facep) - { - gPipeline.mHighlightFaces.push_back(facep); - } - } - } - } -} - -void renderSoundHighlights(LLDrawable *drawablep) -{ - // Look for attachments, objects, etc. - LLViewerObject *vobj = drawablep->getVObj(); - if (vobj && vobj->isAudioSource()) - { - if (gPipeline.sRenderHighlight) - { - S32 face_id; - S32 count = drawablep->getNumFaces(); - for (face_id = 0; face_id < count; face_id++) - { - LLFace *facep = drawablep->getFace(face_id); - if (facep) - { - gPipeline.mHighlightFaces.push_back(facep); - } - } - } - } -} - -void LLPipeline::postSort(LLCamera &camera) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - - assertInitialized(); - - LL_PUSH_CALLSTACKS(); - - if (!gCubeSnapshot) - { - // rebuild drawable geometry - for (LLCullResult::sg_iterator i = sCull->beginDrawableGroups(); i != sCull->endDrawableGroups(); ++i) - { - LLSpatialGroup *group = *i; - if (group->isDead()) - { - continue; - } - if (!sUseOcclusion || !group->isOcclusionState(LLSpatialGroup::OCCLUDED)) - { - group->rebuildGeom(); - } - } - LL_PUSH_CALLSTACKS(); - // rebuild groups - sCull->assertDrawMapsEmpty(); - - rebuildPriorityGroups(); - } - - LL_PUSH_CALLSTACKS(); - - // build render map - for (LLCullResult::sg_iterator i = sCull->beginVisibleGroups(); i != sCull->endVisibleGroups(); ++i) - { - LLSpatialGroup *group = *i; - - if (group->isDead()) - { - continue; - } - - if ((sUseOcclusion && group->isOcclusionState(LLSpatialGroup::OCCLUDED)) || - (RenderAutoHideSurfaceAreaLimit > 0.f && - group->mSurfaceArea > RenderAutoHideSurfaceAreaLimit * llmax(group->mObjectBoxSize, 10.f))) - { - continue; - } - - if (group->hasState(LLSpatialGroup::NEW_DRAWINFO) && group->hasState(LLSpatialGroup::GEOM_DIRTY) && !gCubeSnapshot) - { // no way this group is going to be drawable without a rebuild - group->rebuildGeom(); - } - - for (LLSpatialGroup::draw_map_t::iterator j = group->mDrawMap.begin(); j != group->mDrawMap.end(); ++j) - { - LLSpatialGroup::drawmap_elem_t &src_vec = j->second; - if (!hasRenderType(j->first)) - { - continue; - } - - for (LLSpatialGroup::drawmap_elem_t::iterator k = src_vec.begin(); k != src_vec.end(); ++k) - { - LLDrawInfo *info = *k; - - sCull->pushDrawInfo(j->first, info); - if (!sShadowRender && !sReflectionRender && !gCubeSnapshot) - { - addTrianglesDrawn(info->mCount); - } - } - } - - if (hasRenderType(LLPipeline::RENDER_TYPE_PASS_ALPHA)) - { - LLSpatialGroup::draw_map_t::iterator alpha = group->mDrawMap.find(LLRenderPass::PASS_ALPHA); - - if (alpha != group->mDrawMap.end()) - { // store alpha groups for sorting - LLSpatialBridge *bridge = group->getSpatialPartition()->asBridge(); - if (LLViewerCamera::sCurCameraID == LLViewerCamera::CAMERA_WORLD && !gCubeSnapshot) - { - if (bridge) - { - LLCamera trans_camera = bridge->transformCamera(camera); - group->updateDistance(trans_camera); - } - else - { - group->updateDistance(camera); - } - } - - if (hasRenderType(LLDrawPool::POOL_ALPHA)) - { - sCull->pushAlphaGroup(group); - } - } - - LLSpatialGroup::draw_map_t::iterator rigged_alpha = group->mDrawMap.find(LLRenderPass::PASS_ALPHA_RIGGED); - - if (rigged_alpha != group->mDrawMap.end()) - { // store rigged alpha groups for LLDrawPoolAlpha prepass (skip distance update, rigged attachments use depth buffer) - if (hasRenderType(LLDrawPool::POOL_ALPHA)) - { - sCull->pushRiggedAlphaGroup(group); - } - } - } - } - - /*bool use_transform_feedback = gTransformPositionProgram.mProgramObject && !mMeshDirtyGroup.empty(); - - if (use_transform_feedback) - { //place a query around potential transform feedback code for synchronization - mTransformFeedbackPrimitives = 0; - - if (!mMeshDirtyQueryObject) - { - glGenQueries(1, &mMeshDirtyQueryObject); - } - - - glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, mMeshDirtyQueryObject); - }*/ - - // pack vertex buffers for groups that chose to delay their updates - { - LL_PROFILE_GPU_ZONE("rebuildMesh"); - for (LLSpatialGroup::sg_vector_t::iterator iter = mMeshDirtyGroup.begin(); iter != mMeshDirtyGroup.end(); ++iter) - { - (*iter)->rebuildMesh(); - } - } - - /*if (use_transform_feedback) - { - glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN); - }*/ - - mMeshDirtyGroup.clear(); - - if (!sShadowRender) - { - // order alpha groups by distance - std::sort(sCull->beginAlphaGroups(), sCull->endAlphaGroups(), LLSpatialGroup::CompareDepthGreater()); - - // order rigged alpha groups by avatar attachment order - std::sort(sCull->beginRiggedAlphaGroups(), sCull->endRiggedAlphaGroups(), LLSpatialGroup::CompareRenderOrder()); - } - - LL_PUSH_CALLSTACKS(); - // only render if the flag is set. The flag is only set if we are in edit mode or the toggle is set in the menus - if (LLFloaterReg::instanceVisible("beacons") && !sShadowRender && !gCubeSnapshot) - { - if (sRenderScriptedTouchBeacons) - { - // Only show the beacon on the root object. - forAllVisibleDrawables(renderScriptedTouchBeacons); - } - else if (sRenderScriptedBeacons) - { - // Only show the beacon on the root object. - forAllVisibleDrawables(renderScriptedBeacons); - } - - if (sRenderPhysicalBeacons) - { - // Only show the beacon on the root object. - forAllVisibleDrawables(renderPhysicalBeacons); - } - - if (sRenderMOAPBeacons) - { - forAllVisibleDrawables(renderMOAPBeacons); - } - - if (sRenderParticleBeacons) - { - forAllVisibleDrawables(renderParticleBeacons); - } - - // If god mode, also show audio cues - if (sRenderSoundBeacons && gAudiop) - { - // Walk all sound sources and render out beacons for them. Note, this isn't done in the ForAllVisibleDrawables function, because - // some are not visible. - LLAudioEngine::source_map::iterator iter; - for (iter = gAudiop->mAllSources.begin(); iter != gAudiop->mAllSources.end(); ++iter) - { - LLAudioSource *sourcep = iter->second; - - LLVector3d pos_global = sourcep->getPositionGlobal(); - LLVector3 pos = gAgent.getPosAgentFromGlobal(pos_global); - if (gPipeline.sRenderBeacons) - { - // pos += LLVector3(0.f, 0.f, 0.2f); - gObjectList.addDebugBeacon(pos, "", LLColor4(1.f, 1.f, 0.f, 0.5f), LLColor4(1.f, 1.f, 1.f, 0.5f), DebugBeaconLineWidth); - } - } - // now deal with highlights for all those seeable sound sources - forAllVisibleDrawables(renderSoundHighlights); - } - } - LL_PUSH_CALLSTACKS(); - // If managing your telehub, draw beacons at telehub and currently selected spawnpoint. - if (LLFloaterTelehub::renderBeacons() && !sShadowRender && !gCubeSnapshot) - { - LLFloaterTelehub::addBeacons(); - } - - if (!sShadowRender && !gCubeSnapshot) - { - mSelectedFaces.clear(); - - if (!gNonInteractive) - { - LLPipeline::setRenderHighlightTextureChannel(gFloaterTools->getPanelFace()->getTextureChannelToEdit()); - } - - // Draw face highlights for selected faces. - if (LLSelectMgr::getInstance()->getTEMode()) - { - struct f : public LLSelectedTEFunctor - { - virtual bool apply(LLViewerObject *object, S32 te) - { - if (object->mDrawable) - { - LLFace *facep = object->mDrawable->getFace(te); - if (facep) - { - gPipeline.mSelectedFaces.push_back(facep); - } - } - return true; - } - } func; - LLSelectMgr::getInstance()->getSelection()->applyToTEs(&func); - } - } - - // LLSpatialGroup::sNoDelete = false; - LL_PUSH_CALLSTACKS(); -} - - -void render_hud_elements() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; //LL_RECORD_BLOCK_TIME(FTM_RENDER_UI); - gPipeline.disableLights(); - - LLGLSUIDefault gls_ui; - - //LLGLEnable stencil(GL_STENCIL_TEST); - //glStencilFunc(GL_ALWAYS, 255, 0xFFFFFFFF); - //glStencilMask(0xFFFFFFFF); - //glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); - - gUIProgram.bind(); - gGL.color4f(1, 1, 1, 1); - LLGLDepthTest depth(GL_TRUE, GL_FALSE); - - if (!LLPipeline::sReflectionRender && gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) - { - gViewerWindow->renderSelections(false, false, false); // For HUD version in render_ui_3d() - - // Draw the tracking overlays - LLTracker::render3D(); - - if (LLWorld::instanceExists()) - { - // Show the property lines - LLWorld::getInstance()->renderPropertyLines(); - } - LLViewerParcelMgr::getInstance()->render(); - LLViewerParcelMgr::getInstance()->renderParcelCollision(); - } - else if (gForceRenderLandFence) - { - // This is only set when not rendering the UI, for parcel snapshots - LLViewerParcelMgr::getInstance()->render(); - } - else if (gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_HUD)) - { - LLHUDText::renderAllHUD(); - } - - gUIProgram.unbind(); -} - -void LLPipeline::renderHighlights() -{ - assertInitialized(); - - // Draw 3D UI elements here (before we clear the Z buffer in POOL_HUD) - // Render highlighted faces. - LLGLSPipelineAlpha gls_pipeline_alpha; - LLColor4 color(1.f, 1.f, 1.f, 0.5f); - disableLights(); - - if ((LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_INTERFACE) > 0)) - { - gHighlightProgram.bind(); - gGL.diffuseColor4f(1,1,1,0.5f); - } - - if (hasRenderDebugFeatureMask(RENDER_DEBUG_FEATURE_SELECTED) && !mFaceSelectImagep) - { - mFaceSelectImagep = LLViewerTextureManager::getFetchedTexture(IMG_FACE_SELECT); - } - - if (hasRenderDebugFeatureMask(RENDER_DEBUG_FEATURE_SELECTED) && (sRenderHighlightTextureChannel == LLRender::DIFFUSE_MAP)) - { - // Make sure the selection image gets downloaded and decoded - mFaceSelectImagep->addTextureStats((F32)MAX_IMAGE_AREA); - - U32 count = mSelectedFaces.size(); - for (U32 i = 0; i < count; i++) - { - LLFace *facep = mSelectedFaces[i]; - if (!facep || facep->getDrawable()->isDead()) - { - LL_ERRS() << "Bad face on selection" << LL_ENDL; - return; - } - - facep->renderSelected(mFaceSelectImagep, color); - } - } - - if (hasRenderDebugFeatureMask(RENDER_DEBUG_FEATURE_SELECTED)) - { - // Paint 'em red! - color.setVec(1.f, 0.f, 0.f, 0.5f); - - int count = mHighlightFaces.size(); - for (S32 i = 0; i < count; i++) - { - LLFace* facep = mHighlightFaces[i]; - facep->renderSelected(LLViewerTexture::sNullImagep, color); - } - } - - // Contains a list of the faces of objects that are physical or - // have touch-handlers. - mHighlightFaces.clear(); - - if (LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_INTERFACE) > 0) - { - gHighlightProgram.unbind(); - } - - - if (hasRenderDebugFeatureMask(RENDER_DEBUG_FEATURE_SELECTED) && (sRenderHighlightTextureChannel == LLRender::NORMAL_MAP)) - { - color.setVec(1.0f, 0.5f, 0.5f, 0.5f); - if ((LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_INTERFACE) > 0)) - { - gHighlightNormalProgram.bind(); - gGL.diffuseColor4f(1,1,1,0.5f); - } - - mFaceSelectImagep->addTextureStats((F32)MAX_IMAGE_AREA); - - U32 count = mSelectedFaces.size(); - for (U32 i = 0; i < count; i++) - { - LLFace *facep = mSelectedFaces[i]; - if (!facep || facep->getDrawable()->isDead()) - { - LL_ERRS() << "Bad face on selection" << LL_ENDL; - return; - } - - facep->renderSelected(mFaceSelectImagep, color); - } - - if ((LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_INTERFACE) > 0)) - { - gHighlightNormalProgram.unbind(); - } - } - - if (hasRenderDebugFeatureMask(RENDER_DEBUG_FEATURE_SELECTED) && (sRenderHighlightTextureChannel == LLRender::SPECULAR_MAP)) - { - color.setVec(0.0f, 0.3f, 1.0f, 0.8f); - if ((LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_INTERFACE) > 0)) - { - gHighlightSpecularProgram.bind(); - gGL.diffuseColor4f(1,1,1,0.5f); - } - - mFaceSelectImagep->addTextureStats((F32)MAX_IMAGE_AREA); - - U32 count = mSelectedFaces.size(); - for (U32 i = 0; i < count; i++) - { - LLFace *facep = mSelectedFaces[i]; - if (!facep || facep->getDrawable()->isDead()) - { - LL_ERRS() << "Bad face on selection" << LL_ENDL; - return; - } - - facep->renderSelected(mFaceSelectImagep, color); - } - - if ((LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_INTERFACE) > 0)) - { - gHighlightSpecularProgram.unbind(); - } - } -} - -//debug use -U32 LLPipeline::sCurRenderPoolType = 0 ; - -void LLPipeline::renderGeomDeferred(LLCamera& camera, bool do_occlusion) -{ - LLAppViewer::instance()->pingMainloopTimeout("Pipeline:RenderGeomDeferred"); - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; //LL_RECORD_BLOCK_TIME(FTM_RENDER_GEOMETRY); - LL_PROFILE_GPU_ZONE("renderGeomDeferred"); - - llassert(!sRenderingHUDs); - - if (gUseWireframe) - { - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - } - - if (&camera == LLViewerCamera::getInstance()) - { // a bit hacky, this is the start of the main render frame, figure out delta between last modelview matrix and - // current modelview matrix - glh::matrix4f last_modelview(gGLLastModelView); - glh::matrix4f cur_modelview(gGLModelView); - - // goal is to have a matrix here that goes from the last frame's camera space to the current frame's camera space - glh::matrix4f m = last_modelview.inverse(); // last camera space to world space - m.mult_left(cur_modelview); // world space to camera space - - glh::matrix4f n = m.inverse(); - - for (U32 i = 0; i < 16; ++i) - { - gGLDeltaModelView[i] = m.m[i]; - gGLInverseDeltaModelView[i] = n.m[i]; - } - } - - bool occlude = LLPipeline::sUseOcclusion > 1 && do_occlusion && !LLGLSLShader::sProfileEnabled; - - setupHWLights(); - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("deferred pools"); - - LLGLEnable cull(GL_CULL_FACE); - - for (pool_set_t::iterator iter = mPools.begin(); iter != mPools.end(); ++iter) - { - LLDrawPool *poolp = *iter; - if (hasRenderType(poolp->getType())) - { - poolp->prerender(); - } - } - - LLVertexBuffer::unbind(); - - LLGLState::checkStates(); - - if (LLViewerShaderMgr::instance()->mShaderLevel[LLViewerShaderMgr::SHADER_DEFERRED] > 1) - { - //update reflection probe uniform - mReflectionMapManager.updateUniforms(); - } - - U32 cur_type = 0; - - gGL.setColorMask(true, true); - - pool_set_t::iterator iter1 = mPools.begin(); - - while ( iter1 != mPools.end() ) - { - LLDrawPool *poolp = *iter1; - - cur_type = poolp->getType(); - - if (occlude && cur_type >= LLDrawPool::POOL_GRASS) - { - llassert(!gCubeSnapshot); // never do occlusion culling on cube snapshots - occlude = false; - gGLLastMatrix = NULL; - gGL.loadMatrix(gGLModelView); - doOcclusion(camera); - } - - pool_set_t::iterator iter2 = iter1; - if (hasRenderType(poolp->getType()) && poolp->getNumDeferredPasses() > 0) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("deferred pool render"); - - gGLLastMatrix = NULL; - gGL.loadMatrix(gGLModelView); - - for( S32 i = 0; i < poolp->getNumDeferredPasses(); i++ ) - { - LLVertexBuffer::unbind(); - poolp->beginDeferredPass(i); - for (iter2 = iter1; iter2 != mPools.end(); iter2++) - { - LLDrawPool *p = *iter2; - if (p->getType() != cur_type) - { - break; - } - - if ( !p->getSkipRenderFlag() ) { p->renderDeferred(i); } - } - poolp->endDeferredPass(i); - LLVertexBuffer::unbind(); - - LLGLState::checkStates(); - } - } - else - { - // Skip all pools of this type - for (iter2 = iter1; iter2 != mPools.end(); iter2++) - { - LLDrawPool *p = *iter2; - if (p->getType() != cur_type) - { - break; - } - } - } - iter1 = iter2; - stop_glerror(); - } - - gGLLastMatrix = NULL; - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.loadMatrix(gGLModelView); - - gGL.setColorMask(true, false); - - } // Tracy ZoneScoped - - if (gUseWireframe) - { - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - } -} - -void LLPipeline::renderGeomPostDeferred(LLCamera& camera) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - LL_PROFILE_GPU_ZONE("renderGeomPostDeferred"); - - if (gUseWireframe) - { - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - } - - U32 cur_type = 0; - - LLGLEnable cull(GL_CULL_FACE); - - bool done_atmospherics = LLPipeline::sRenderingHUDs; //skip atmospherics on huds - bool done_water_haze = done_atmospherics; - - // do atmospheric haze just before post water alpha - U32 atmospherics_pass = LLDrawPool::POOL_ALPHA_POST_WATER; - - if (LLPipeline::sUnderWaterRender) - { // if under water, do atmospherics just before the water pass - atmospherics_pass = LLDrawPool::POOL_WATER; - } - - // do water haze just before pre water alpha - U32 water_haze_pass = LLDrawPool::POOL_ALPHA_PRE_WATER; - - calcNearbyLights(camera); - setupHWLights(); - - gGL.setSceneBlendType(LLRender::BT_ALPHA); - gGL.setColorMask(true, false); - - pool_set_t::iterator iter1 = mPools.begin(); - - if (gDebugGL || gDebugPipeline) - { - LLGLState::checkStates(GL_FALSE); - } - - while ( iter1 != mPools.end() ) - { - LLDrawPool *poolp = *iter1; - - cur_type = poolp->getType(); - - if (cur_type >= atmospherics_pass && !done_atmospherics) - { // do atmospherics against depth buffer before rendering alpha - doAtmospherics(); - done_atmospherics = true; - } - - if (cur_type >= water_haze_pass && !done_water_haze) - { // do water haze against depth buffer before rendering alpha - doWaterHaze(); - done_water_haze = true; - } - - pool_set_t::iterator iter2 = iter1; - if (hasRenderType(poolp->getType()) && poolp->getNumPostDeferredPasses() > 0) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("deferred poolrender"); - - gGLLastMatrix = NULL; - gGL.loadMatrix(gGLModelView); - - for( S32 i = 0; i < poolp->getNumPostDeferredPasses(); i++ ) - { - LLVertexBuffer::unbind(); - poolp->beginPostDeferredPass(i); - for (iter2 = iter1; iter2 != mPools.end(); iter2++) - { - LLDrawPool *p = *iter2; - if (p->getType() != cur_type) - { - break; - } - - p->renderPostDeferred(i); - } - poolp->endPostDeferredPass(i); - LLVertexBuffer::unbind(); - - if (gDebugGL || gDebugPipeline) - { - LLGLState::checkStates(GL_FALSE); - } - } - } - else - { - // Skip all pools of this type - for (iter2 = iter1; iter2 != mPools.end(); iter2++) - { - LLDrawPool *p = *iter2; - if (p->getType() != cur_type) - { - break; - } - } - } - iter1 = iter2; - stop_glerror(); - } - - gGLLastMatrix = NULL; - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.loadMatrix(gGLModelView); - - if (!gCubeSnapshot) - { - // debug displays - renderHighlights(); - mHighlightFaces.clear(); - - renderDebug(); - } - - if (gUseWireframe) - { - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - } -} - -void LLPipeline::renderGeomShadow(LLCamera& camera) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - LL_PROFILE_GPU_ZONE("renderGeomShadow"); - U32 cur_type = 0; - - LLGLEnable cull(GL_CULL_FACE); - - LLVertexBuffer::unbind(); - - pool_set_t::iterator iter1 = mPools.begin(); - - while ( iter1 != mPools.end() ) - { - LLDrawPool *poolp = *iter1; - - cur_type = poolp->getType(); - - pool_set_t::iterator iter2 = iter1; - if (hasRenderType(poolp->getType()) && poolp->getNumShadowPasses() > 0) - { - poolp->prerender() ; - - gGLLastMatrix = NULL; - gGL.loadMatrix(gGLModelView); - - for( S32 i = 0; i < poolp->getNumShadowPasses(); i++ ) - { - LLVertexBuffer::unbind(); - poolp->beginShadowPass(i); - for (iter2 = iter1; iter2 != mPools.end(); iter2++) - { - LLDrawPool *p = *iter2; - if (p->getType() != cur_type) - { - break; - } - - p->renderShadow(i); - } - poolp->endShadowPass(i); - LLVertexBuffer::unbind(); - } - } - else - { - // Skip all pools of this type - for (iter2 = iter1; iter2 != mPools.end(); iter2++) - { - LLDrawPool *p = *iter2; - if (p->getType() != cur_type) - { - break; - } - } - } - iter1 = iter2; - stop_glerror(); - } - - gGLLastMatrix = NULL; - gGL.loadMatrix(gGLModelView); -} - - -static U32 sIndicesDrawnCount = 0; - -void LLPipeline::addTrianglesDrawn(S32 index_count) -{ - sIndicesDrawnCount += index_count; -} - -void LLPipeline::recordTrianglesDrawn() -{ - assertInitialized(); - U32 count = sIndicesDrawnCount / 3; - sIndicesDrawnCount = 0; - add(LLStatViewer::TRIANGLES_DRAWN, LLUnits::Triangles::fromValue(count)); -} - -void LLPipeline::renderPhysicsDisplay() -{ - if (!hasRenderDebugMask(LLPipeline::RENDER_DEBUG_PHYSICS_SHAPES)) - { - return; - } - - gGL.flush(); - gDebugProgram.bind(); - - LLGLEnable(GL_POLYGON_OFFSET_LINE); - glPolygonOffset(3.f, 3.f); - glLineWidth(3.f); - LLGLEnable blend(GL_BLEND); - gGL.setSceneBlendType(LLRender::BT_ALPHA); - - for (int pass = 0; pass < 3; ++pass) - { - // pass 0 - depth write enabled, color write disabled, fill - // pass 1 - depth write disabled, color write enabled, fill - // pass 2 - depth write disabled, color write enabled, wireframe - gGL.setColorMask(pass >= 1, false); - LLGLDepthTest depth(GL_TRUE, pass == 0); - - bool wireframe = (pass == 2); - - if (wireframe) - { - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - } - - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) - { - LLViewerRegion* region = *iter; - for (U32 i = 0; i < LLViewerRegion::NUM_PARTITIONS; i++) - { - LLSpatialPartition* part = region->getSpatialPartition(i); - if (part) - { - if (hasRenderType(part->mDrawableType)) - { - part->renderPhysicsShapes(wireframe); - } - } - } - } - gGL.flush(); - - if (wireframe) - { - glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - } - } - glLineWidth(1.f); - gDebugProgram.unbind(); - -} - -extern std::set visible_selected_groups; - -void LLPipeline::renderDebug() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - - assertInitialized(); - - bool hud_only = hasRenderType(LLPipeline::RENDER_TYPE_HUD); - - if (!hud_only ) - { - //Render any navmesh geometry - LLPathingLib *llPathingLibInstance = LLPathingLib::getInstance(); - if ( llPathingLibInstance != NULL ) - { - //character floater renderables - - LLHandle pathfindingCharacterHandle = LLFloaterPathfindingCharacters::getInstanceHandle(); - if ( !pathfindingCharacterHandle.isDead() ) - { - LLFloaterPathfindingCharacters *pathfindingCharacter = pathfindingCharacterHandle.get(); - - if ( pathfindingCharacter->getVisible() || gAgentCamera.cameraMouselook() ) - { - gPathfindingProgram.bind(); - gPathfindingProgram.uniform1f(sTint, 1.f); - gPathfindingProgram.uniform1f(sAmbiance, 1.f); - gPathfindingProgram.uniform1f(sAlphaScale, 1.f); - - //Requried character physics capsule render parameters - LLUUID id; - LLVector3 pos; - LLQuaternion rot; - - if ( pathfindingCharacter->isPhysicsCapsuleEnabled( id, pos, rot ) ) - { - //remove blending artifacts - gGL.setColorMask(false, false); - llPathingLibInstance->renderSimpleShapeCapsuleID( gGL, id, pos, rot ); - gGL.setColorMask(true, false); - LLGLEnable blend(GL_BLEND); - gPathfindingProgram.uniform1f(sAlphaScale, 0.90f); - llPathingLibInstance->renderSimpleShapeCapsuleID( gGL, id, pos, rot ); - gPathfindingProgram.bind(); - } - } - } - - - //pathing console renderables - LLHandle pathfindingConsoleHandle = LLFloaterPathfindingConsole::getInstanceHandle(); - if (!pathfindingConsoleHandle.isDead()) - { - LLFloaterPathfindingConsole *pathfindingConsole = pathfindingConsoleHandle.get(); - - if ( pathfindingConsole->getVisible() || gAgentCamera.cameraMouselook() ) - { - F32 ambiance = gSavedSettings.getF32("PathfindingAmbiance"); - - gPathfindingProgram.bind(); - - gPathfindingProgram.uniform1f(sTint, 1.f); - gPathfindingProgram.uniform1f(sAmbiance, ambiance); - gPathfindingProgram.uniform1f(sAlphaScale, 1.f); - - if ( !pathfindingConsole->isRenderWorld() ) - { - const LLColor4 clearColor = gSavedSettings.getColor4("PathfindingNavMeshClear"); - gGL.setColorMask(true, true); - glClearColor(clearColor.mV[0],clearColor.mV[1],clearColor.mV[2],0); - glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); // no stencil -- deprecated | GL_STENCIL_BUFFER_BIT); - gGL.setColorMask(true, false); - glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); - } - - //NavMesh - if ( pathfindingConsole->isRenderNavMesh() ) - { - gGL.flush(); - glLineWidth(2.0f); - LLGLEnable cull(GL_CULL_FACE); - LLGLDisable blend(GL_BLEND); - - if ( pathfindingConsole->isRenderWorld() ) - { - LLGLEnable blend(GL_BLEND); - gPathfindingProgram.uniform1f(sAlphaScale, 0.66f); - llPathingLibInstance->renderNavMesh(); - } - else - { - llPathingLibInstance->renderNavMesh(); - } - - //render edges - gPathfindingNoNormalsProgram.bind(); - gPathfindingNoNormalsProgram.uniform1f(sTint, 1.f); - gPathfindingNoNormalsProgram.uniform1f(sAlphaScale, 1.f); - llPathingLibInstance->renderNavMeshEdges(); - gPathfindingProgram.bind(); - - gGL.flush(); - glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); - glLineWidth(1.0f); - gGL.flush(); - } - //User designated path - if ( LLPathfindingPathTool::getInstance()->isRenderPath() ) - { - //The path - gUIProgram.bind(); - gGL.getTexUnit(0)->bind(LLViewerFetchedTexture::sWhiteImagep); - llPathingLibInstance->renderPath(); - gPathfindingProgram.bind(); - - //The bookends - //remove blending artifacts - gGL.setColorMask(false, false); - llPathingLibInstance->renderPathBookend( gGL, LLPathingLib::LLPL_START ); - llPathingLibInstance->renderPathBookend( gGL, LLPathingLib::LLPL_END ); - - gGL.setColorMask(true, false); - //render the bookends - LLGLEnable blend(GL_BLEND); - gPathfindingProgram.uniform1f(sAlphaScale, 0.90f); - llPathingLibInstance->renderPathBookend( gGL, LLPathingLib::LLPL_START ); - llPathingLibInstance->renderPathBookend( gGL, LLPathingLib::LLPL_END ); - gPathfindingProgram.bind(); - } - - if ( pathfindingConsole->isRenderWaterPlane() ) - { - LLGLEnable blend(GL_BLEND); - gPathfindingProgram.uniform1f(sAlphaScale, 0.90f); - llPathingLibInstance->renderSimpleShapes( gGL, gAgent.getRegion()->getWaterHeight() ); - } - //physics/exclusion shapes - if ( pathfindingConsole->isRenderAnyShapes() ) - { - U32 render_order[] = { - 1 << LLPathingLib::LLST_ObstacleObjects, - 1 << LLPathingLib::LLST_WalkableObjects, - 1 << LLPathingLib::LLST_ExclusionPhantoms, - 1 << LLPathingLib::LLST_MaterialPhantoms, - }; - - U32 flags = pathfindingConsole->getRenderShapeFlags(); - - for (U32 i = 0; i < 4; i++) - { - if (!(flags & render_order[i])) - { - continue; - } - - //turn off backface culling for volumes so they are visible when camera is inside volume - LLGLDisable cull(i >= 2 ? GL_CULL_FACE : 0); - - gGL.flush(); - glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); - - //get rid of some z-fighting - LLGLEnable polyOffset(GL_POLYGON_OFFSET_FILL); - glPolygonOffset(1.0f, 1.0f); - - //render to depth first to avoid blending artifacts - gGL.setColorMask(false, false); - llPathingLibInstance->renderNavMeshShapesVBO( render_order[i] ); - gGL.setColorMask(true, false); - - //get rid of some z-fighting - glPolygonOffset(0.f, 0.f); - - LLGLEnable blend(GL_BLEND); - - { - gPathfindingProgram.uniform1f(sAmbiance, ambiance); - - { //draw solid overlay - LLGLDepthTest depth(GL_TRUE, GL_FALSE, GL_LEQUAL); - llPathingLibInstance->renderNavMeshShapesVBO( render_order[i] ); - gGL.flush(); - } - - LLGLEnable lineOffset(GL_POLYGON_OFFSET_LINE); - glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); - - F32 offset = gSavedSettings.getF32("PathfindingLineOffset"); - - if (pathfindingConsole->isRenderXRay()) - { - gPathfindingProgram.uniform1f(sTint, gSavedSettings.getF32("PathfindingXRayTint")); - gPathfindingProgram.uniform1f(sAlphaScale, gSavedSettings.getF32("PathfindingXRayOpacity")); - LLGLEnable blend(GL_BLEND); - LLGLDepthTest depth(GL_TRUE, GL_FALSE, GL_GREATER); - - glPolygonOffset(offset, -offset); - - if (gSavedSettings.getBOOL("PathfindingXRayWireframe")) - { //draw hidden wireframe as darker and less opaque - gPathfindingProgram.uniform1f(sAmbiance, 1.f); - llPathingLibInstance->renderNavMeshShapesVBO( render_order[i] ); - } - else - { - glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); - gPathfindingProgram.uniform1f(sAmbiance, ambiance); - llPathingLibInstance->renderNavMeshShapesVBO( render_order[i] ); - glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - } - } - - { //draw visible wireframe as brighter, thicker and more opaque - glPolygonOffset(offset, offset); - gPathfindingProgram.uniform1f(sAmbiance, 1.f); - gPathfindingProgram.uniform1f(sTint, 1.f); - gPathfindingProgram.uniform1f(sAlphaScale, 1.f); - - glLineWidth(gSavedSettings.getF32("PathfindingLineWidth")); - LLGLDisable blendOut(GL_BLEND); - llPathingLibInstance->renderNavMeshShapesVBO( render_order[i] ); - gGL.flush(); - glLineWidth(1.f); - } - - glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); - } - } - } - - glPolygonOffset(0.f, 0.f); - - if ( pathfindingConsole->isRenderNavMesh() && pathfindingConsole->isRenderXRay() ) - { //render navmesh xray - F32 ambiance = gSavedSettings.getF32("PathfindingAmbiance"); - - LLGLEnable lineOffset(GL_POLYGON_OFFSET_LINE); - LLGLEnable polyOffset(GL_POLYGON_OFFSET_FILL); - - F32 offset = gSavedSettings.getF32("PathfindingLineOffset"); - glPolygonOffset(offset, -offset); - - LLGLEnable blend(GL_BLEND); - LLGLDepthTest depth(GL_TRUE, GL_FALSE, GL_GREATER); - gGL.flush(); - glLineWidth(2.0f); - LLGLEnable cull(GL_CULL_FACE); - - gPathfindingProgram.uniform1f(sTint, gSavedSettings.getF32("PathfindingXRayTint")); - gPathfindingProgram.uniform1f(sAlphaScale, gSavedSettings.getF32("PathfindingXRayOpacity")); - - if (gSavedSettings.getBOOL("PathfindingXRayWireframe")) - { //draw hidden wireframe as darker and less opaque - glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); - gPathfindingProgram.uniform1f(sAmbiance, 1.f); - llPathingLibInstance->renderNavMesh(); - glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); - } - else - { - gPathfindingProgram.uniform1f(sAmbiance, ambiance); - llPathingLibInstance->renderNavMesh(); - } - - //render edges - gPathfindingNoNormalsProgram.bind(); - gPathfindingNoNormalsProgram.uniform1f(sTint, gSavedSettings.getF32("PathfindingXRayTint")); - gPathfindingNoNormalsProgram.uniform1f(sAlphaScale, gSavedSettings.getF32("PathfindingXRayOpacity")); - llPathingLibInstance->renderNavMeshEdges(); - gPathfindingProgram.bind(); - - gGL.flush(); - glLineWidth(1.0f); - } - - glPolygonOffset(0.f, 0.f); - - gGL.flush(); - gPathfindingProgram.unbind(); - } - } - } - } - - gGLLastMatrix = NULL; - gGL.loadMatrix(gGLModelView); - gGL.setColorMask(true, false); - - - if (!hud_only && !mDebugBlips.empty()) - { //render debug blips - gUIProgram.bind(); - gGL.color4f(1, 1, 1, 1); - - gGL.getTexUnit(0)->bind(LLViewerFetchedTexture::sWhiteImagep, true); - - glPointSize(8.f); - LLGLDepthTest depth(GL_TRUE, GL_TRUE, GL_ALWAYS); - - gGL.begin(LLRender::POINTS); - for (std::list::iterator iter = mDebugBlips.begin(); iter != mDebugBlips.end(); ) - { - DebugBlip& blip = *iter; - - blip.mAge += gFrameIntervalSeconds.value(); - if (blip.mAge > 2.f) - { - mDebugBlips.erase(iter++); - } - else - { - iter++; - } - - blip.mPosition.mV[2] += gFrameIntervalSeconds.value()*2.f; - - gGL.color4fv(blip.mColor.mV); - gGL.vertex3fv(blip.mPosition.mV); - } - gGL.end(); - gGL.flush(); - glPointSize(1.f); - } - - // Debug stuff. - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) - { - LLViewerRegion* region = *iter; - for (U32 i = 0; i < LLViewerRegion::NUM_PARTITIONS; i++) - { - LLSpatialPartition* part = region->getSpatialPartition(i); - if (part) - { - if ( (hud_only && (part->mDrawableType == RENDER_TYPE_HUD || part->mDrawableType == RENDER_TYPE_HUD_PARTICLES)) || - (!hud_only && hasRenderType(part->mDrawableType)) ) - { - part->renderDebug(); - } - } - } - } - - for (LLCullResult::bridge_iterator i = sCull->beginVisibleBridge(); i != sCull->endVisibleBridge(); ++i) - { - LLSpatialBridge* bridge = *i; - if (!bridge->isDead() && hasRenderType(bridge->mDrawableType)) - { - gGL.pushMatrix(); - gGL.multMatrix((F32*)bridge->mDrawable->getRenderMatrix().mMatrix); - bridge->renderDebug(); - gGL.popMatrix(); - } - } - - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_OCCLUSION)) - { //render visible selected group occlusion geometry - gDebugProgram.bind(); - LLGLDepthTest depth(GL_TRUE, GL_FALSE); - gGL.diffuseColor3f(1,0,1); - for (std::set::iterator iter = visible_selected_groups.begin(); iter != visible_selected_groups.end(); ++iter) - { - LLSpatialGroup* group = *iter; - - LLVector4a fudge; - fudge.splat(0.25f); //SG_OCCLUSION_FUDGE - - LLVector4a size; - const LLVector4a* bounds = group->getBounds(); - size.setAdd(fudge, bounds[1]); - - drawBox(bounds[0], size); - } - } - - visible_selected_groups.clear(); - - //draw reflection probes and links between them - if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_REFLECTION_PROBES) && !hud_only) - { - mReflectionMapManager.renderDebug(); - } - - if (gSavedSettings.getBOOL("RenderReflectionProbeVolumes") && !hud_only) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("probe debug display"); - - bindDeferredShader(gReflectionProbeDisplayProgram, NULL); - mScreenTriangleVB->setBuffer(); - - LLGLEnable blend(GL_BLEND); - LLGLDepthTest depth(GL_FALSE); - - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - - unbindDeferredShader(gReflectionProbeDisplayProgram); - } - - gUIProgram.bind(); - - if (hasRenderDebugMask(LLPipeline::RENDER_DEBUG_RAYCAST) && !hud_only) - { //draw crosshairs on particle intersection - if (gDebugRaycastParticle) - { - gDebugProgram.bind(); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - LLVector3 center(gDebugRaycastParticleIntersection.getF32ptr()); - LLVector3 size(0.1f, 0.1f, 0.1f); - - LLVector3 p[6]; - - p[0] = center + size.scaledVec(LLVector3(1,0,0)); - p[1] = center + size.scaledVec(LLVector3(-1,0,0)); - p[2] = center + size.scaledVec(LLVector3(0,1,0)); - p[3] = center + size.scaledVec(LLVector3(0,-1,0)); - p[4] = center + size.scaledVec(LLVector3(0,0,1)); - p[5] = center + size.scaledVec(LLVector3(0,0,-1)); - - gGL.begin(LLRender::LINES); - gGL.diffuseColor3f(1.f, 1.f, 0.f); - for (U32 i = 0; i < 6; i++) - { - gGL.vertex3fv(p[i].mV); - } - gGL.end(); - gGL.flush(); - - gDebugProgram.unbind(); - } - } - - if (hasRenderDebugMask(LLPipeline::RENDER_DEBUG_SHADOW_FRUSTA) && !hud_only) - { - LLVertexBuffer::unbind(); - - LLGLEnable blend(GL_BLEND); - LLGLDepthTest depth(true, false); - LLGLDisable cull(GL_CULL_FACE); - - gGL.color4f(1,1,1,1); - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - F32 a = 0.1f; - - F32 col[] = - { - 1,0,0,a, - 0,1,0,a, - 0,0,1,a, - 1,0,1,a, - - 1,1,0,a, - 0,1,1,a, - 1,1,1,a, - 1,0,1,a, - }; - - for (U32 i = 0; i < 8; i++) - { - LLVector3* frust = mShadowCamera[i].mAgentFrustum; - - if (i > 3) - { //render shadow frusta as volumes - if (mShadowFrustPoints[i-4].empty()) - { - continue; - } - - gGL.color4fv(col+(i-4)*4); - - gGL.begin(LLRender::TRIANGLE_STRIP); - gGL.vertex3fv(frust[0].mV); gGL.vertex3fv(frust[4].mV); - gGL.vertex3fv(frust[1].mV); gGL.vertex3fv(frust[5].mV); - gGL.vertex3fv(frust[2].mV); gGL.vertex3fv(frust[6].mV); - gGL.vertex3fv(frust[3].mV); gGL.vertex3fv(frust[7].mV); - gGL.vertex3fv(frust[0].mV); gGL.vertex3fv(frust[4].mV); - gGL.end(); - - - gGL.begin(LLRender::TRIANGLE_STRIP); - gGL.vertex3fv(frust[0].mV); - gGL.vertex3fv(frust[1].mV); - gGL.vertex3fv(frust[3].mV); - gGL.vertex3fv(frust[2].mV); - gGL.end(); - - gGL.begin(LLRender::TRIANGLE_STRIP); - gGL.vertex3fv(frust[4].mV); - gGL.vertex3fv(frust[5].mV); - gGL.vertex3fv(frust[7].mV); - gGL.vertex3fv(frust[6].mV); - gGL.end(); - } - - - if (i < 4) - { - - //if (i == 0 || !mShadowFrustPoints[i].empty()) - { - //render visible point cloud - gGL.flush(); - glPointSize(8.f); - gGL.begin(LLRender::POINTS); - - F32* c = col+i*4; - gGL.color3fv(c); - - for (U32 j = 0; j < mShadowFrustPoints[i].size(); ++j) - { - gGL.vertex3fv(mShadowFrustPoints[i][j].mV); - - } - gGL.end(); - - gGL.flush(); - glPointSize(1.f); - - LLVector3* ext = mShadowExtents[i]; - LLVector3 pos = (ext[0]+ext[1])*0.5f; - LLVector3 size = (ext[1]-ext[0])*0.5f; - drawBoxOutline(pos, size); - - //render camera frustum splits as outlines - gGL.begin(LLRender::LINES); - gGL.vertex3fv(frust[0].mV); gGL.vertex3fv(frust[1].mV); - gGL.vertex3fv(frust[1].mV); gGL.vertex3fv(frust[2].mV); - gGL.vertex3fv(frust[2].mV); gGL.vertex3fv(frust[3].mV); - gGL.vertex3fv(frust[3].mV); gGL.vertex3fv(frust[0].mV); - gGL.vertex3fv(frust[4].mV); gGL.vertex3fv(frust[5].mV); - gGL.vertex3fv(frust[5].mV); gGL.vertex3fv(frust[6].mV); - gGL.vertex3fv(frust[6].mV); gGL.vertex3fv(frust[7].mV); - gGL.vertex3fv(frust[7].mV); gGL.vertex3fv(frust[4].mV); - gGL.vertex3fv(frust[0].mV); gGL.vertex3fv(frust[4].mV); - gGL.vertex3fv(frust[1].mV); gGL.vertex3fv(frust[5].mV); - gGL.vertex3fv(frust[2].mV); gGL.vertex3fv(frust[6].mV); - gGL.vertex3fv(frust[3].mV); gGL.vertex3fv(frust[7].mV); - gGL.end(); - } - } - - /*gGL.flush(); - glLineWidth(16-i*2); - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) - { - LLViewerRegion* region = *iter; - for (U32 j = 0; j < LLViewerRegion::NUM_PARTITIONS; j++) - { - LLSpatialPartition* part = region->getSpatialPartition(j); - if (part) - { - if (hasRenderType(part->mDrawableType)) - { - part->renderIntersectingBBoxes(&mShadowCamera[i]); - } - } - } - } - gGL.flush(); - glLineWidth(1.f);*/ - } - } - - if (mRenderDebugMask & RENDER_DEBUG_WIND_VECTORS) - { - gAgent.getRegion()->mWind.renderVectors(); - } - - if (mRenderDebugMask & RENDER_DEBUG_COMPOSITION) - { - // Debug composition layers - F32 x, y; - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - if (gAgent.getRegion()) - { - gGL.begin(LLRender::POINTS); - // Draw the composition layer for the region that I'm in. - for (x = 0; x <= 260; x++) - { - for (y = 0; y <= 260; y++) - { - if ((x > 255) || (y > 255)) - { - gGL.color4f(1.f, 0.f, 0.f, 1.f); - } - else - { - gGL.color4f(0.f, 0.f, 1.f, 1.f); - } - F32 z = gAgent.getRegion()->getCompositionXY((S32)x, (S32)y); - z *= 5.f; - z += 50.f; - gGL.vertex3f(x, y, z); - } - } - gGL.end(); - } - } - - gGL.flush(); - gUIProgram.unbind(); -} - -void LLPipeline::rebuildPools() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - - assertInitialized(); - - S32 max_count = mPools.size(); - pool_set_t::iterator iter1 = mPools.upper_bound(mLastRebuildPool); - while(max_count > 0 && mPools.size() > 0) // && num_rebuilds < MAX_REBUILDS) - { - if (iter1 == mPools.end()) - { - iter1 = mPools.begin(); - } - LLDrawPool* poolp = *iter1; - - if (poolp->isDead()) - { - mPools.erase(iter1++); - removeFromQuickLookup( poolp ); - if (poolp == mLastRebuildPool) - { - mLastRebuildPool = NULL; - } - delete poolp; - } - else - { - mLastRebuildPool = poolp; - iter1++; - } - max_count--; - } -} - -void LLPipeline::addToQuickLookup( LLDrawPool* new_poolp ) -{ - assertInitialized(); - - switch( new_poolp->getType() ) - { - case LLDrawPool::POOL_SIMPLE: - if (mSimplePool) - { - llassert(0); - LL_WARNS() << "Ignoring duplicate simple pool." << LL_ENDL; - } - else - { - mSimplePool = (LLRenderPass*) new_poolp; - } - break; - - case LLDrawPool::POOL_ALPHA_MASK: - if (mAlphaMaskPool) - { - llassert(0); - LL_WARNS() << "Ignoring duplicate alpha mask pool." << LL_ENDL; - break; - } - else - { - mAlphaMaskPool = (LLRenderPass*) new_poolp; - } - break; - - case LLDrawPool::POOL_FULLBRIGHT_ALPHA_MASK: - if (mFullbrightAlphaMaskPool) - { - llassert(0); - LL_WARNS() << "Ignoring duplicate alpha mask pool." << LL_ENDL; - break; - } - else - { - mFullbrightAlphaMaskPool = (LLRenderPass*) new_poolp; - } - break; - - case LLDrawPool::POOL_GRASS: - if (mGrassPool) - { - llassert(0); - LL_WARNS() << "Ignoring duplicate grass pool." << LL_ENDL; - } - else - { - mGrassPool = (LLRenderPass*) new_poolp; - } - break; - - case LLDrawPool::POOL_FULLBRIGHT: - if (mFullbrightPool) - { - llassert(0); - LL_WARNS() << "Ignoring duplicate simple pool." << LL_ENDL; - } - else - { - mFullbrightPool = (LLRenderPass*) new_poolp; - } - break; - - case LLDrawPool::POOL_GLOW: - if (mGlowPool) - { - llassert(0); - LL_WARNS() << "Ignoring duplicate glow pool." << LL_ENDL; - } - else - { - mGlowPool = (LLRenderPass*) new_poolp; - } - break; - - case LLDrawPool::POOL_TREE: - mTreePools[ uintptr_t(new_poolp->getTexture()) ] = new_poolp ; - break; - - case LLDrawPool::POOL_TERRAIN: - mTerrainPools[ uintptr_t(new_poolp->getTexture()) ] = new_poolp ; - break; - - case LLDrawPool::POOL_BUMP: - if (mBumpPool) - { - llassert(0); - LL_WARNS() << "Ignoring duplicate bump pool." << LL_ENDL; - } - else - { - mBumpPool = new_poolp; - } - break; - case LLDrawPool::POOL_MATERIALS: - if (mMaterialsPool) - { - llassert(0); - LL_WARNS() << "Ignorning duplicate materials pool." << LL_ENDL; - } - else - { - mMaterialsPool = new_poolp; - } - break; - case LLDrawPool::POOL_ALPHA_PRE_WATER: - if( mAlphaPoolPreWater ) - { - llassert(0); - LL_WARNS() << "LLPipeline::addPool(): Ignoring duplicate Alpha pre-water pool" << LL_ENDL; - } - else - { - mAlphaPoolPreWater = (LLDrawPoolAlpha*) new_poolp; - } - break; - case LLDrawPool::POOL_ALPHA_POST_WATER: - if (mAlphaPoolPostWater) - { - llassert(0); - LL_WARNS() << "LLPipeline::addPool(): Ignoring duplicate Alpha post-water pool" << LL_ENDL; - } - else - { - mAlphaPoolPostWater = (LLDrawPoolAlpha*)new_poolp; - } - break; - - case LLDrawPool::POOL_AVATAR: - case LLDrawPool::POOL_CONTROL_AV: - break; // Do nothing - - case LLDrawPool::POOL_SKY: - if( mSkyPool ) - { - llassert(0); - LL_WARNS() << "LLPipeline::addPool(): Ignoring duplicate Sky pool" << LL_ENDL; - } - else - { - mSkyPool = new_poolp; - } - break; - - case LLDrawPool::POOL_WATER: - if( mWaterPool ) - { - llassert(0); - LL_WARNS() << "LLPipeline::addPool(): Ignoring duplicate Water pool" << LL_ENDL; - } - else - { - mWaterPool = new_poolp; - } - break; - - case LLDrawPool::POOL_WL_SKY: - if( mWLSkyPool ) - { - llassert(0); - LL_WARNS() << "LLPipeline::addPool(): Ignoring duplicate WLSky Pool" << LL_ENDL; - } - else - { - mWLSkyPool = new_poolp; - } - break; - - case LLDrawPool::POOL_GLTF_PBR: - if( mPBROpaquePool ) - { - llassert(0); - LL_WARNS() << "LLPipeline::addPool(): Ignoring duplicate PBR Opaque Pool" << LL_ENDL; - } - else - { - mPBROpaquePool = new_poolp; - } - break; - - case LLDrawPool::POOL_GLTF_PBR_ALPHA_MASK: - if (mPBRAlphaMaskPool) - { - llassert(0); - LL_WARNS() << "LLPipeline::addPool(): Ignoring duplicate PBR Alpha Mask Pool" << LL_ENDL; - } - else - { - mPBRAlphaMaskPool = new_poolp; - } - break; - - - default: - llassert(0); - LL_WARNS() << "Invalid Pool Type in LLPipeline::addPool()" << LL_ENDL; - break; - } -} - -void LLPipeline::removePool( LLDrawPool* poolp ) -{ - assertInitialized(); - removeFromQuickLookup(poolp); - mPools.erase(poolp); - delete poolp; -} - -void LLPipeline::removeFromQuickLookup( LLDrawPool* poolp ) -{ - assertInitialized(); - switch( poolp->getType() ) - { - case LLDrawPool::POOL_SIMPLE: - llassert(mSimplePool == poolp); - mSimplePool = NULL; - break; - - case LLDrawPool::POOL_ALPHA_MASK: - llassert(mAlphaMaskPool == poolp); - mAlphaMaskPool = NULL; - break; - - case LLDrawPool::POOL_FULLBRIGHT_ALPHA_MASK: - llassert(mFullbrightAlphaMaskPool == poolp); - mFullbrightAlphaMaskPool = NULL; - break; - - case LLDrawPool::POOL_GRASS: - llassert(mGrassPool == poolp); - mGrassPool = NULL; - break; - - case LLDrawPool::POOL_FULLBRIGHT: - llassert(mFullbrightPool == poolp); - mFullbrightPool = NULL; - break; - - case LLDrawPool::POOL_WL_SKY: - llassert(mWLSkyPool == poolp); - mWLSkyPool = NULL; - break; - - case LLDrawPool::POOL_GLOW: - llassert(mGlowPool == poolp); - mGlowPool = NULL; - break; - - case LLDrawPool::POOL_TREE: - #ifdef _DEBUG - { - bool found = mTreePools.erase( (uintptr_t)poolp->getTexture() ); - llassert( found ); - } - #else - mTreePools.erase( (uintptr_t)poolp->getTexture() ); - #endif - break; - - case LLDrawPool::POOL_TERRAIN: - #ifdef _DEBUG - { - bool found = mTerrainPools.erase( (uintptr_t)poolp->getTexture() ); - llassert( found ); - } - #else - mTerrainPools.erase( (uintptr_t)poolp->getTexture() ); - #endif - break; - - case LLDrawPool::POOL_BUMP: - llassert( poolp == mBumpPool ); - mBumpPool = NULL; - break; - - case LLDrawPool::POOL_MATERIALS: - llassert(poolp == mMaterialsPool); - mMaterialsPool = NULL; - break; - - case LLDrawPool::POOL_ALPHA_PRE_WATER: - llassert( poolp == mAlphaPoolPreWater ); - mAlphaPoolPreWater = nullptr; - break; - - case LLDrawPool::POOL_ALPHA_POST_WATER: - llassert(poolp == mAlphaPoolPostWater); - mAlphaPoolPostWater = nullptr; - break; - - case LLDrawPool::POOL_AVATAR: - case LLDrawPool::POOL_CONTROL_AV: - break; // Do nothing - - case LLDrawPool::POOL_SKY: - llassert( poolp == mSkyPool ); - mSkyPool = NULL; - break; - - case LLDrawPool::POOL_WATER: - llassert( poolp == mWaterPool ); - mWaterPool = NULL; - break; - - case LLDrawPool::POOL_GLTF_PBR: - llassert( poolp == mPBROpaquePool ); - mPBROpaquePool = NULL; - break; - - case LLDrawPool::POOL_GLTF_PBR_ALPHA_MASK: - llassert(poolp == mPBRAlphaMaskPool); - mPBRAlphaMaskPool = NULL; - break; - - default: - llassert(0); - LL_WARNS() << "Invalid Pool Type in LLPipeline::removeFromQuickLookup() type=" << poolp->getType() << LL_ENDL; - break; - } -} - -void LLPipeline::resetDrawOrders() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - assertInitialized(); - // Iterate through all of the draw pools and rebuild them. - for (pool_set_t::iterator iter = mPools.begin(); iter != mPools.end(); ++iter) - { - LLDrawPool *poolp = *iter; - poolp->resetDrawOrders(); - } -} - -//============================================================================ -// Once-per-frame setup of hardware lights, -// including sun/moon, avatar backlight, and up to 6 local lights - -void LLPipeline::setupAvatarLights(bool for_edit) -{ - assertInitialized(); - - LLEnvironment& environment = LLEnvironment::instance(); - LLSettingsSky::ptr_t psky = environment.getCurrentSky(); - - bool sun_up = environment.getIsSunUp(); - - - if (for_edit) - { - LLColor4 diffuse(1.f, 1.f, 1.f, 0.f); - LLVector4 light_pos_cam(-8.f, 0.25f, 10.f, 0.f); // w==0 => directional light - LLMatrix4 camera_mat = LLViewerCamera::getInstance()->getModelview(); - LLMatrix4 camera_rot(camera_mat.getMat3()); - camera_rot.invert(); - LLVector4 light_pos = light_pos_cam * camera_rot; - - light_pos.normalize(); - - LLLightState* light = gGL.getLight(1); - - mHWLightColors[1] = diffuse; - - light->setDiffuse(diffuse); - light->setAmbient(LLColor4::black); - light->setSpecular(LLColor4::black); - light->setPosition(light_pos); - light->setConstantAttenuation(1.f); - light->setLinearAttenuation(0.f); - light->setQuadraticAttenuation(0.f); - light->setSpotExponent(0.f); - light->setSpotCutoff(180.f); - } - else if (gAvatarBacklight) - { - LLVector3 light_dir = sun_up ? LLVector3(mSunDir) : LLVector3(mMoonDir); - LLVector3 opposite_pos = -light_dir; - LLVector3 orthog_light_pos = light_dir % LLVector3::z_axis; - LLVector4 backlight_pos = LLVector4(lerp(opposite_pos, orthog_light_pos, 0.3f), 0.0f); - backlight_pos.normalize(); - - LLColor4 light_diffuse = sun_up ? mSunDiffuse : mMoonDiffuse; - - LLColor4 backlight_diffuse(1.f - light_diffuse.mV[VRED], 1.f - light_diffuse.mV[VGREEN], 1.f - light_diffuse.mV[VBLUE], 1.f); - F32 max_component = 0.001f; - for (S32 i = 0; i < 3; i++) - { - if (backlight_diffuse.mV[i] > max_component) - { - max_component = backlight_diffuse.mV[i]; - } - } - F32 backlight_mag; - if (LLEnvironment::instance().getIsSunUp()) - { - backlight_mag = BACKLIGHT_DAY_MAGNITUDE_OBJECT; - } - else - { - backlight_mag = BACKLIGHT_NIGHT_MAGNITUDE_OBJECT; - } - backlight_diffuse *= backlight_mag / max_component; - - mHWLightColors[1] = backlight_diffuse; - - LLLightState* light = gGL.getLight(1); - - light->setPosition(backlight_pos); - light->setDiffuse(backlight_diffuse); - light->setAmbient(LLColor4::black); - light->setSpecular(LLColor4::black); - light->setConstantAttenuation(1.f); - light->setLinearAttenuation(0.f); - light->setQuadraticAttenuation(0.f); - light->setSpotExponent(0.f); - light->setSpotCutoff(180.f); - } - else - { - LLLightState* light = gGL.getLight(1); - - mHWLightColors[1] = LLColor4::black; - - light->setDiffuse(LLColor4::black); - light->setAmbient(LLColor4::black); - light->setSpecular(LLColor4::black); - } -} - -static F32 calc_light_dist(LLVOVolume* light, const LLVector3& cam_pos, F32 max_dist) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - F32 inten = light->getLightIntensity(); - if (inten < .001f) - { - return max_dist; - } - bool selected = light->isSelected(); - if (selected) - { - return 0.f; // selected lights get highest priority - } - F32 radius = light->getLightRadius(); - F32 dist = dist_vec(light->getRenderPosition(), cam_pos); - dist = llmax(dist - radius, 0.f); - if (light->mDrawable.notNull() && light->mDrawable->isState(LLDrawable::ACTIVE)) - { - // moving lights get a little higher priority (too much causes artifacts) - dist = llmax(dist - light->getLightRadius()*0.25f, 0.f); - } - return dist; -} - -void LLPipeline::calcNearbyLights(LLCamera& camera) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - assertInitialized(); - - if (LLPipeline::sReflectionRender || gCubeSnapshot || LLPipeline::sRenderingHUDs) - { - return; - } - - static LLCachedControl local_light_count(gSavedSettings, "RenderLocalLightCount", 256); - - if (local_light_count >= 1) - { - // mNearbyLight (and all light_set_t's) are sorted such that - // begin() == the closest light and rbegin() == the farthest light - const S32 MAX_LOCAL_LIGHTS = 6; - LLVector3 cam_pos = camera.getOrigin(); - - F32 max_dist; - if (LLPipeline::sRenderDeferred) - { - max_dist = RenderFarClip; - } - else - { - max_dist = llmin(RenderFarClip, LIGHT_MAX_RADIUS * 4.f); - } - - // UPDATE THE EXISTING NEARBY LIGHTS - light_set_t cur_nearby_lights; - for (light_set_t::iterator iter = mNearbyLights.begin(); - iter != mNearbyLights.end(); iter++) - { - const Light* light = &(*iter); - LLDrawable* drawable = light->drawable; - const LLViewerObject *vobj = light->drawable->getVObj(); - if(vobj && vobj->getAvatar() - && (vobj->getAvatar()->isTooComplex() || vobj->getAvatar()->isInMuteList() || vobj->getAvatar()->isTooSlow()) - ) - { - drawable->clearState(LLDrawable::NEARBY_LIGHT); - continue; - } - - LLVOVolume* volight = drawable->getVOVolume(); - if (!volight || !drawable->isState(LLDrawable::LIGHT)) - { - drawable->clearState(LLDrawable::NEARBY_LIGHT); - continue; - } - if (light->fade <= -LIGHT_FADE_TIME) - { - drawable->clearState(LLDrawable::NEARBY_LIGHT); - continue; - } - if (!sRenderAttachedLights && volight && volight->isAttachment()) - { - drawable->clearState(LLDrawable::NEARBY_LIGHT); - continue; - } - - F32 dist = calc_light_dist(volight, cam_pos, max_dist); - F32 fade = light->fade; - // actual fade gets decreased/increased by setupHWLights - // light->fade value is 'time'. - // >=0 and light will become visible as value increases - // <0 and light will fade out - if (dist < max_dist) - { - if (fade < 0) - { - // mark light to fade in - // if fade was -LIGHT_FADE_TIME - it was fully invisible - // if fade -0 - it was fully visible - // visibility goes up from 0 to LIGHT_FADE_TIME. - fade += LIGHT_FADE_TIME; - } - } - else - { - // mark light to fade out - // visibility goes down from -0 to -LIGHT_FADE_TIME. - if (fade >= LIGHT_FADE_TIME) - { - fade = -0.0001f; // was fully visible - } - else if (fade >= 0) - { - // 0.75 visible light should stay 0.75 visible, but should reverse direction - fade -= LIGHT_FADE_TIME; - } - } - cur_nearby_lights.insert(Light(drawable, dist, fade)); - } - mNearbyLights = cur_nearby_lights; - - // FIND NEW LIGHTS THAT ARE IN RANGE - light_set_t new_nearby_lights; - for (LLDrawable::ordered_drawable_set_t::iterator iter = mLights.begin(); - iter != mLights.end(); ++iter) - { - LLDrawable* drawable = *iter; - LLVOVolume* light = drawable->getVOVolume(); - if (!light || drawable->isState(LLDrawable::NEARBY_LIGHT)) - { - continue; - } - if (light->isHUDAttachment()) - { - continue; // no lighting from HUD objects - } - if (!sRenderAttachedLights && light && light->isAttachment()) - { - continue; - } - LLVOAvatar * av = light->getAvatar(); - if (av && (av->isTooComplex() || av->isInMuteList() || av->isTooSlow())) - { - // avatars that are already in the list will be removed by removeMutedAVsLights - continue; - } - F32 dist = calc_light_dist(light, cam_pos, max_dist); - if (dist >= max_dist) - { - continue; - } - new_nearby_lights.insert(Light(drawable, dist, 0.f)); - if (!LLPipeline::sRenderDeferred && new_nearby_lights.size() > (U32)MAX_LOCAL_LIGHTS) - { - new_nearby_lights.erase(--new_nearby_lights.end()); - const Light& last = *new_nearby_lights.rbegin(); - max_dist = last.dist; - } - } - - // INSERT ANY NEW LIGHTS - for (light_set_t::iterator iter = new_nearby_lights.begin(); - iter != new_nearby_lights.end(); iter++) - { - const Light* light = &(*iter); - if (LLPipeline::sRenderDeferred || mNearbyLights.size() < (U32)MAX_LOCAL_LIGHTS) - { - mNearbyLights.insert(*light); - ((LLDrawable*) light->drawable)->setState(LLDrawable::NEARBY_LIGHT); - } - else - { - // crazy cast so that we can overwrite the fade value - // even though gcc enforces sets as const - // (fade value doesn't affect sort so this is safe) - Light* farthest_light = (const_cast(&(*(mNearbyLights.rbegin())))); - if (light->dist < farthest_light->dist) - { - // mark light to fade out - // visibility goes down from -0 to -LIGHT_FADE_TIME. - // - // This is a mess, but for now it needs to be in sync - // with fade code above. Ex: code above detects distance < max, - // sets fade time to positive, this code then detects closer - // lights and sets fade time negative, fully compensating - // for the code above - if (farthest_light->fade >= LIGHT_FADE_TIME) - { - farthest_light->fade = -0.0001f; // was fully visible - } - else if (farthest_light->fade >= 0) - { - farthest_light->fade -= LIGHT_FADE_TIME; - } - } - else - { - break; // none of the other lights are closer - } - } - } - - //mark nearby lights not-removable. - for (light_set_t::iterator iter = mNearbyLights.begin(); - iter != mNearbyLights.end(); iter++) - { - const Light* light = &(*iter); - ((LLViewerOctreeEntryData*) light->drawable)->setVisible(); - } - } -} - -void LLPipeline::setupHWLights() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - assertInitialized(); - - if (LLPipeline::sRenderingHUDs) - { - return; - } - - F32 light_scale = 1.f; - - if (gCubeSnapshot) - { //darken local lights when probe ambiance is above 1 - light_scale = mReflectionMapManager.mLightScale; - } - - - LLEnvironment& environment = LLEnvironment::instance(); - LLSettingsSky::ptr_t psky = environment.getCurrentSky(); - - // Ambient - LLColor4 ambient = psky->getTotalAmbient(); - - gGL.setAmbientLightColor(ambient); - - bool sun_up = environment.getIsSunUp(); - bool moon_up = environment.getIsMoonUp(); - - // Light 0 = Sun or Moon (All objects) - { - LLVector4 sun_dir(environment.getSunDirection(), 0.0f); - LLVector4 moon_dir(environment.getMoonDirection(), 0.0f); - - mSunDir.setVec(sun_dir); - mMoonDir.setVec(moon_dir); - - mSunDiffuse.setVec(psky->getSunlightColor()); - mMoonDiffuse.setVec(psky->getMoonlightColor()); - - F32 max_color = llmax(mSunDiffuse.mV[0], mSunDiffuse.mV[1], mSunDiffuse.mV[2]); - if (max_color > 1.f) - { - mSunDiffuse *= 1.f/max_color; - } - mSunDiffuse.clamp(); - - max_color = llmax(mMoonDiffuse.mV[0], mMoonDiffuse.mV[1], mMoonDiffuse.mV[2]); - if (max_color > 1.f) - { - mMoonDiffuse *= 1.f/max_color; - } - mMoonDiffuse.clamp(); - - // prevent underlighting from having neither lightsource facing us - if (!sun_up && !moon_up) - { - mSunDiffuse.setVec(LLColor4(0.0, 0.0, 0.0, 1.0)); - mMoonDiffuse.setVec(LLColor4(0.0, 0.0, 0.0, 1.0)); - mSunDir.setVec(LLVector4(0.0, 1.0, 0.0, 0.0)); - mMoonDir.setVec(LLVector4(0.0, 1.0, 0.0, 0.0)); - } - - LLVector4 light_dir = sun_up ? mSunDir : mMoonDir; - - mHWLightColors[0] = sun_up ? mSunDiffuse : mMoonDiffuse; - - LLLightState* light = gGL.getLight(0); - light->setPosition(light_dir); - - light->setSunPrimary(sun_up); - light->setDiffuse(mHWLightColors[0]); - light->setDiffuseB(mMoonDiffuse); - light->setAmbient(psky->getTotalAmbient()); - light->setSpecular(LLColor4::black); - light->setConstantAttenuation(1.f); - light->setLinearAttenuation(0.f); - light->setQuadraticAttenuation(0.f); - light->setSpotExponent(0.f); - light->setSpotCutoff(180.f); - } - - // Light 1 = Backlight (for avatars) - // (set by enableLightsAvatar) - - S32 cur_light = 2; - - // Nearby lights = LIGHT 2-7 - - mLightMovingMask = 0; - - static LLCachedControl local_light_count(gSavedSettings, "RenderLocalLightCount", 256); - - if (local_light_count >= 1) - { - for (light_set_t::iterator iter = mNearbyLights.begin(); - iter != mNearbyLights.end(); ++iter) - { - LLDrawable* drawable = iter->drawable; - LLVOVolume* light = drawable->getVOVolume(); - if (!light) - { - continue; - } - - if (light->isAttachment()) - { - if (!sRenderAttachedLights) - { - continue; - } - } - - if (drawable->isState(LLDrawable::ACTIVE)) - { - mLightMovingMask |= (1<getLightLinearColor() * light_scale; - light_color.mV[3] = 0.0f; - - F32 fade = iter->fade; - if (fade < LIGHT_FADE_TIME) - { - // fade in/out light - if (fade >= 0.f) - { - fade = fade / LIGHT_FADE_TIME; - ((Light*) (&(*iter)))->fade += gFrameIntervalSeconds.value(); - } - else - { - fade = 1.f + fade / LIGHT_FADE_TIME; - ((Light*) (&(*iter)))->fade -= gFrameIntervalSeconds.value(); - } - fade = llclamp(fade,0.f,1.f); - light_color *= fade; - } - - if (light_color.magVecSquared() < 0.001f) - { - continue; - } - - LLVector3 light_pos(light->getRenderPosition()); - LLVector4 light_pos_gl(light_pos, 1.0f); - - F32 adjusted_radius = light->getLightRadius() * (sRenderDeferred ? 1.5f : 1.0f); - if (adjusted_radius <= 0.001f) - { - continue; - } - - F32 x = (3.f * (1.f + (light->getLightFalloff() * 2.0f))); // why this magic? probably trying to match a historic behavior. - F32 linatten = x / adjusted_radius; // % of brightness at radius - - mHWLightColors[cur_light] = light_color; - LLLightState* light_state = gGL.getLight(cur_light); - - light_state->setPosition(light_pos_gl); - light_state->setDiffuse(light_color); - light_state->setAmbient(LLColor4::black); - light_state->setConstantAttenuation(0.f); - light_state->setSize(light->getLightRadius() * 1.5f); - light_state->setFalloff(light->getLightFalloff(DEFERRED_LIGHT_FALLOFF)); - - if (sRenderDeferred) - { - light_state->setLinearAttenuation(linatten); - light_state->setQuadraticAttenuation(light->getLightFalloff(DEFERRED_LIGHT_FALLOFF) + 1.f); // get falloff to match for forward deferred rendering lights - } - else - { - light_state->setLinearAttenuation(linatten); - light_state->setQuadraticAttenuation(0.f); - } - - - if (light->isLightSpotlight() // directional (spot-)light - && (LLPipeline::sRenderDeferred || RenderSpotLightsInNondeferred)) // these are only rendered as GL spotlights if we're in deferred rendering mode *or* the setting forces them on - { - LLQuaternion quat = light->getRenderRotation(); - LLVector3 at_axis(0,0,-1); // this matches deferred rendering's object light direction - at_axis *= quat; - - light_state->setSpotDirection(at_axis); - light_state->setSpotCutoff(90.f); - light_state->setSpotExponent(2.f); - - LLVector3 spotParams = light->getSpotLightParams(); - - const LLColor4 specular(0.f, 0.f, 0.f, spotParams[2]); - light_state->setSpecular(specular); - } - else // omnidirectional (point) light - { - light_state->setSpotExponent(0.f); - light_state->setSpotCutoff(180.f); - - // we use specular.z = 1.0 as a cheap hack for the shaders to know that this is omnidirectional rather than a spotlight - const LLColor4 specular(0.f, 0.f, 1.f, 0.f); - light_state->setSpecular(specular); - } - cur_light++; - if (cur_light >= 8) - { - break; // safety - } - } - } - for ( ; cur_light < 8 ; cur_light++) - { - mHWLightColors[cur_light] = LLColor4::black; - LLLightState* light = gGL.getLight(cur_light); - light->setSunPrimary(true); - light->setDiffuse(LLColor4::black); - light->setAmbient(LLColor4::black); - light->setSpecular(LLColor4::black); - } - - // Bookmark comment to allow searching for mSpecialRenderMode == 3 (avatar edit mode), - // prev site of forward (non-deferred) character light injection, removed by SL-13522 09/20 - - // Init GL state - for (S32 i = 0; i < 8; ++i) - { - gGL.getLight(i)->disable(); - } - mLightMask = 0; -} - -void LLPipeline::enableLights(U32 mask) -{ - assertInitialized(); - - if (mLightMask != mask) - { - stop_glerror(); - if (mask) - { - stop_glerror(); - for (S32 i=0; i<8; i++) - { - LLLightState* light = gGL.getLight(i); - if (mask & (1<enable(); - light->setDiffuse(mHWLightColors[i]); - } - else - { - light->disable(); - light->setDiffuse(LLColor4::black); - } - } - stop_glerror(); - } - mLightMask = mask; - stop_glerror(); - } -} - -void LLPipeline::enableLightsDynamic() -{ - assertInitialized(); - U32 mask = 0xff & (~2); // Local lights - enableLights(mask); - - if (isAgentAvatarValid()) - { - if (gAgentAvatarp->mSpecialRenderMode == 0) // normal - { - gPipeline.enableLightsAvatar(); - } - else if (gAgentAvatarp->mSpecialRenderMode == 2) // anim preview - { - gPipeline.enableLightsAvatarEdit(LLColor4(0.7f, 0.6f, 0.3f, 1.f)); - } - } -} - -void LLPipeline::enableLightsAvatar() -{ - U32 mask = 0xff; // All lights - setupAvatarLights(false); - enableLights(mask); -} - -void LLPipeline::enableLightsPreview() -{ - disableLights(); - - LLColor4 ambient = PreviewAmbientColor; - gGL.setAmbientLightColor(ambient); - - LLColor4 diffuse0 = PreviewDiffuse0; - LLColor4 specular0 = PreviewSpecular0; - LLColor4 diffuse1 = PreviewDiffuse1; - LLColor4 specular1 = PreviewSpecular1; - LLColor4 diffuse2 = PreviewDiffuse2; - LLColor4 specular2 = PreviewSpecular2; - - LLVector3 dir0 = PreviewDirection0; - LLVector3 dir1 = PreviewDirection1; - LLVector3 dir2 = PreviewDirection2; - - dir0.normVec(); - dir1.normVec(); - dir2.normVec(); - - LLVector4 light_pos(dir0, 0.0f); - - LLLightState* light = gGL.getLight(1); - - light->enable(); - light->setPosition(light_pos); - light->setDiffuse(diffuse0); - light->setAmbient(ambient); - light->setSpecular(specular0); - light->setSpotExponent(0.f); - light->setSpotCutoff(180.f); - - light_pos = LLVector4(dir1, 0.f); - - light = gGL.getLight(2); - light->enable(); - light->setPosition(light_pos); - light->setDiffuse(diffuse1); - light->setAmbient(ambient); - light->setSpecular(specular1); - light->setSpotExponent(0.f); - light->setSpotCutoff(180.f); - - light_pos = LLVector4(dir2, 0.f); - light = gGL.getLight(3); - light->enable(); - light->setPosition(light_pos); - light->setDiffuse(diffuse2); - light->setAmbient(ambient); - light->setSpecular(specular2); - light->setSpotExponent(0.f); - light->setSpotCutoff(180.f); -} - - -void LLPipeline::enableLightsAvatarEdit(const LLColor4& color) -{ - U32 mask = 0x2002; // Avatar backlight only, set ambient - setupAvatarLights(true); - enableLights(mask); - - gGL.setAmbientLightColor(color); -} - -void LLPipeline::enableLightsFullbright() -{ - assertInitialized(); - U32 mask = 0x1000; // Non-0 mask, set ambient - enableLights(mask); -} - -void LLPipeline::disableLights() -{ - enableLights(0); // no lighting (full bright) -} - -//============================================================================ - -class LLMenuItemGL; -class LLInvFVBridge; -struct cat_folder_pair; -class LLVOBranch; -class LLVOLeaf; - -void LLPipeline::findReferences(LLDrawable *drawablep) -{ - assertInitialized(); - if (mLights.find(drawablep) != mLights.end()) - { - LL_INFOS() << "In mLights" << LL_ENDL; - } - if (std::find(mMovedList.begin(), mMovedList.end(), drawablep) != mMovedList.end()) - { - LL_INFOS() << "In mMovedList" << LL_ENDL; - } - if (std::find(mShiftList.begin(), mShiftList.end(), drawablep) != mShiftList.end()) - { - LL_INFOS() << "In mShiftList" << LL_ENDL; - } - if (mRetexturedList.find(drawablep) != mRetexturedList.end()) - { - LL_INFOS() << "In mRetexturedList" << LL_ENDL; - } - - if (std::find(mBuildQ1.begin(), mBuildQ1.end(), drawablep) != mBuildQ1.end()) - { - LL_INFOS() << "In mBuildQ1" << LL_ENDL; - } - - S32 count; - - count = gObjectList.findReferences(drawablep); - if (count) - { - LL_INFOS() << "In other drawables: " << count << " references" << LL_ENDL; - } -} - -bool LLPipeline::verify() -{ - bool ok = assertInitialized(); - if (ok) - { - for (pool_set_t::iterator iter = mPools.begin(); iter != mPools.end(); ++iter) - { - LLDrawPool *poolp = *iter; - if (!poolp->verify()) - { - ok = false; - } - } - } - - if (!ok) - { - LL_WARNS() << "Pipeline verify failed!" << LL_ENDL; - } - return ok; -} - -////////////////////////////// -// -// Collision detection -// -// - -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/** - * A method to compute a ray-AABB intersection. - * Original code by Andrew Woo, from "Graphics Gems", Academic Press, 1990 - * Optimized code by Pierre Terdiman, 2000 (~20-30% faster on my Celeron 500) - * Epsilon value added by Klaus Hartmann. (discarding it saves a few cycles only) - * - * Hence this version is faster as well as more robust than the original one. - * - * Should work provided: - * 1) the integer representation of 0.0f is 0x00000000 - * 2) the sign bit of the float is the most significant one - * - * Report bugs: p.terdiman@codercorner.com - * - * \param aabb [in] the axis-aligned bounding box - * \param origin [in] ray origin - * \param dir [in] ray direction - * \param coord [out] impact coordinates - * \return true if ray intersects AABB - */ -/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -//#define RAYAABB_EPSILON 0.00001f -#define IR(x) ((U32&)x) - -bool LLRayAABB(const LLVector3 ¢er, const LLVector3 &size, const LLVector3& origin, const LLVector3& dir, LLVector3 &coord, F32 epsilon) -{ - bool Inside = true; - LLVector3 MinB = center - size; - LLVector3 MaxB = center + size; - LLVector3 MaxT; - MaxT.mV[VX]=MaxT.mV[VY]=MaxT.mV[VZ]=-1.0f; - - // Find candidate planes. - for(U32 i=0;i<3;i++) - { - if(origin.mV[i] < MinB.mV[i]) - { - coord.mV[i] = MinB.mV[i]; - Inside = false; - - // Calculate T distances to candidate planes - if(IR(dir.mV[i])) MaxT.mV[i] = (MinB.mV[i] - origin.mV[i]) / dir.mV[i]; - } - else if(origin.mV[i] > MaxB.mV[i]) - { - coord.mV[i] = MaxB.mV[i]; - Inside = false; - - // Calculate T distances to candidate planes - if(IR(dir.mV[i])) MaxT.mV[i] = (MaxB.mV[i] - origin.mV[i]) / dir.mV[i]; - } - } - - // Ray origin inside bounding box - if(Inside) - { - coord = origin; - return true; - } - - // Get largest of the maxT's for final choice of intersection - U32 WhichPlane = 0; - if(MaxT.mV[1] > MaxT.mV[WhichPlane]) WhichPlane = 1; - if(MaxT.mV[2] > MaxT.mV[WhichPlane]) WhichPlane = 2; - - // Check final candidate actually inside box - if(IR(MaxT.mV[WhichPlane])&0x80000000) return false; - - for(U32 i=0;i<3;i++) - { - if(i!=WhichPlane) - { - coord.mV[i] = origin.mV[i] + MaxT.mV[WhichPlane] * dir.mV[i]; - if (epsilon > 0) - { - if(coord.mV[i] < MinB.mV[i] - epsilon || coord.mV[i] > MaxB.mV[i] + epsilon) return false; - } - else - { - if(coord.mV[i] < MinB.mV[i] || coord.mV[i] > MaxB.mV[i]) return false; - } - } - } - return true; // ray hits box -} - -////////////////////////////// -// -// Macros, functions, and inline methods from other classes -// -// - -void LLPipeline::setLight(LLDrawable *drawablep, bool is_light) -{ - if (drawablep && assertInitialized()) - { - if (is_light) - { - mLights.insert(drawablep); - drawablep->setState(LLDrawable::LIGHT); - } - else - { - drawablep->clearState(LLDrawable::LIGHT); - mLights.erase(drawablep); - } - } -} - -//static -void LLPipeline::toggleRenderType(U32 type) -{ - gPipeline.mRenderTypeEnabled[type] = !gPipeline.mRenderTypeEnabled[type]; - if (type == LLPipeline::RENDER_TYPE_WATER) - { - gPipeline.mRenderTypeEnabled[LLPipeline::RENDER_TYPE_VOIDWATER] = !gPipeline.mRenderTypeEnabled[LLPipeline::RENDER_TYPE_VOIDWATER]; - } -} - -//static -void LLPipeline::toggleRenderTypeControl(U32 type) -{ - gPipeline.toggleRenderType(type); -} - -//static -bool LLPipeline::hasRenderTypeControl(U32 type) -{ - return gPipeline.hasRenderType(type); -} - -// Allows UI items labeled "Hide foo" instead of "Show foo" -//static -bool LLPipeline::toggleRenderTypeControlNegated(S32 type) -{ - return !gPipeline.hasRenderType(type); -} - -//static -void LLPipeline::toggleRenderDebug(U64 bit) -{ - if (gPipeline.hasRenderDebugMask(bit)) - { - LL_INFOS() << "Toggling render debug mask " << std::hex << bit << " off" << std::dec << LL_ENDL; - } - else - { - LL_INFOS() << "Toggling render debug mask " << std::hex << bit << " on" << std::dec << LL_ENDL; - } - gPipeline.mRenderDebugMask ^= bit; -} - - -//static -bool LLPipeline::toggleRenderDebugControl(U64 bit) -{ - return gPipeline.hasRenderDebugMask(bit); -} - -//static -void LLPipeline::toggleRenderDebugFeature(U32 bit) -{ - gPipeline.mRenderDebugFeatureMask ^= bit; -} - - -//static -bool LLPipeline::toggleRenderDebugFeatureControl(U32 bit) -{ - return gPipeline.hasRenderDebugFeatureMask(bit); -} - -void LLPipeline::setRenderDebugFeatureControl(U32 bit, bool value) -{ - if (value) - { - gPipeline.mRenderDebugFeatureMask |= bit; - } - else - { - gPipeline.mRenderDebugFeatureMask &= !bit; - } -} - -void LLPipeline::pushRenderDebugFeatureMask() -{ - mRenderDebugFeatureStack.push(mRenderDebugFeatureMask); -} - -void LLPipeline::popRenderDebugFeatureMask() -{ - if (mRenderDebugFeatureStack.empty()) - { - LL_ERRS() << "Depleted render feature stack." << LL_ENDL; - } - - mRenderDebugFeatureMask = mRenderDebugFeatureStack.top(); - mRenderDebugFeatureStack.pop(); -} - -// static -void LLPipeline::setRenderScriptedBeacons(bool val) -{ - sRenderScriptedBeacons = val; -} - -// static -void LLPipeline::toggleRenderScriptedBeacons() -{ - sRenderScriptedBeacons = !sRenderScriptedBeacons; -} - -// static -bool LLPipeline::getRenderScriptedBeacons() -{ - return sRenderScriptedBeacons; -} - -// static -void LLPipeline::setRenderScriptedTouchBeacons(bool val) -{ - sRenderScriptedTouchBeacons = val; -} - -// static -void LLPipeline::toggleRenderScriptedTouchBeacons() -{ - sRenderScriptedTouchBeacons = !sRenderScriptedTouchBeacons; -} - -// static -bool LLPipeline::getRenderScriptedTouchBeacons() -{ - return sRenderScriptedTouchBeacons; -} - -// static -void LLPipeline::setRenderMOAPBeacons(bool val) -{ - sRenderMOAPBeacons = val; -} - -// static -void LLPipeline::toggleRenderMOAPBeacons() -{ - sRenderMOAPBeacons = !sRenderMOAPBeacons; -} - -// static -bool LLPipeline::getRenderMOAPBeacons() -{ - return sRenderMOAPBeacons; -} - -// static -void LLPipeline::setRenderPhysicalBeacons(bool val) -{ - sRenderPhysicalBeacons = val; -} - -// static -void LLPipeline::toggleRenderPhysicalBeacons() -{ - sRenderPhysicalBeacons = !sRenderPhysicalBeacons; -} - -// static -bool LLPipeline::getRenderPhysicalBeacons() -{ - return sRenderPhysicalBeacons; -} - -// static -void LLPipeline::setRenderParticleBeacons(bool val) -{ - sRenderParticleBeacons = val; -} - -// static -void LLPipeline::toggleRenderParticleBeacons() -{ - sRenderParticleBeacons = !sRenderParticleBeacons; -} - -// static -bool LLPipeline::getRenderParticleBeacons() -{ - return sRenderParticleBeacons; -} - -// static -void LLPipeline::setRenderSoundBeacons(bool val) -{ - sRenderSoundBeacons = val; -} - -// static -void LLPipeline::toggleRenderSoundBeacons() -{ - sRenderSoundBeacons = !sRenderSoundBeacons; -} - -// static -bool LLPipeline::getRenderSoundBeacons() -{ - return sRenderSoundBeacons; -} - -// static -void LLPipeline::setRenderBeacons(bool val) -{ - sRenderBeacons = val; -} - -// static -void LLPipeline::toggleRenderBeacons() -{ - sRenderBeacons = !sRenderBeacons; -} - -// static -bool LLPipeline::getRenderBeacons() -{ - return sRenderBeacons; -} - -// static -void LLPipeline::setRenderHighlights(bool val) -{ - sRenderHighlight = val; -} - -// static -void LLPipeline::toggleRenderHighlights() -{ - sRenderHighlight = !sRenderHighlight; -} - -// static -bool LLPipeline::getRenderHighlights() -{ - return sRenderHighlight; -} - -// static -void LLPipeline::setRenderHighlightTextureChannel(LLRender::eTexIndex channel) -{ - sRenderHighlightTextureChannel = channel; -} - -LLVOPartGroup* LLPipeline::lineSegmentIntersectParticle(const LLVector4a& start, const LLVector4a& end, LLVector4a* intersection, - S32* face_hit) -{ - LLVector4a local_end = end; - - LLVector4a position; - - LLDrawable* drawable = NULL; - - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) - { - LLViewerRegion* region = *iter; - - LLSpatialPartition* part = region->getSpatialPartition(LLViewerRegion::PARTITION_PARTICLE); - if (part && hasRenderType(part->mDrawableType)) - { - LLDrawable* hit = part->lineSegmentIntersect(start, local_end, true, false, true, false, face_hit, &position, NULL, NULL, NULL); - if (hit) - { - drawable = hit; - local_end = position; - } - } - } - - LLVOPartGroup* ret = NULL; - if (drawable) - { - //make sure we're returning an LLVOPartGroup - llassert(drawable->getVObj()->getPCode() == LLViewerObject::LL_VO_PART_GROUP); - ret = (LLVOPartGroup*) drawable->getVObj().get(); - } - - if (intersection) - { - *intersection = position; - } - - return ret; -} - -LLViewerObject* LLPipeline::lineSegmentIntersectInWorld(const LLVector4a& start, const LLVector4a& end, - bool pick_transparent, - bool pick_rigged, - bool pick_unselectable, - bool pick_reflection_probe, - S32* face_hit, - LLVector4a* intersection, // return the intersection point - LLVector2* tex_coord, // return the texture coordinates of the intersection point - LLVector4a* normal, // return the surface normal at the intersection point - LLVector4a* tangent // return the surface tangent at the intersection point - ) -{ - LLDrawable* drawable = NULL; - - LLVector4a local_end = end; - - LLVector4a position; - - sPickAvatar = false; //! LLToolMgr::getInstance()->inBuildMode(); - - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) - { - LLViewerRegion* region = *iter; - - for (U32 j = 0; j < LLViewerRegion::NUM_PARTITIONS; j++) - { - if ((j == LLViewerRegion::PARTITION_VOLUME) || - (j == LLViewerRegion::PARTITION_BRIDGE) || - (j == LLViewerRegion::PARTITION_AVATAR) || // for attachments - (j == LLViewerRegion::PARTITION_CONTROL_AV) || - (j == LLViewerRegion::PARTITION_TERRAIN) || - (j == LLViewerRegion::PARTITION_TREE) || - (j == LLViewerRegion::PARTITION_GRASS)) // only check these partitions for now - { - LLSpatialPartition* part = region->getSpatialPartition(j); - if (part && hasRenderType(part->mDrawableType)) - { - LLDrawable* hit = part->lineSegmentIntersect(start, local_end, pick_transparent, pick_rigged, pick_unselectable, pick_reflection_probe, face_hit, &position, tex_coord, normal, tangent); - if (hit) - { - drawable = hit; - local_end = position; - } - } - } - } - } - - if (!sPickAvatar) - { - //save hit info in case we need to restore - //due to attachment override - LLVector4a local_normal; - LLVector4a local_tangent; - LLVector2 local_texcoord; - S32 local_face_hit = -1; - - if (face_hit) - { - local_face_hit = *face_hit; - } - if (tex_coord) - { - local_texcoord = *tex_coord; - } - if (tangent) - { - local_tangent = *tangent; - } - else - { - local_tangent.clear(); - } - if (normal) - { - local_normal = *normal; - } - else - { - local_normal.clear(); - } - - const F32 ATTACHMENT_OVERRIDE_DIST = 0.1f; - - //check against avatars - sPickAvatar = true; - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) - { - LLViewerRegion* region = *iter; - - LLSpatialPartition* part = region->getSpatialPartition(LLViewerRegion::PARTITION_AVATAR); - if (part && hasRenderType(part->mDrawableType)) - { - LLDrawable* hit = part->lineSegmentIntersect(start, local_end, pick_transparent, pick_rigged, pick_unselectable, pick_reflection_probe, face_hit, &position, tex_coord, normal, tangent); - if (hit) - { - LLVector4a delta; - delta.setSub(position, local_end); - - if (!drawable || - !drawable->getVObj()->isAttachment() || - delta.getLength3().getF32() > ATTACHMENT_OVERRIDE_DIST) - { //avatar overrides if previously hit drawable is not an attachment or - //attachment is far enough away from detected intersection - drawable = hit; - local_end = position; - } - else - { //prioritize attachments over avatars - position = local_end; - - if (face_hit) - { - *face_hit = local_face_hit; - } - if (tex_coord) - { - *tex_coord = local_texcoord; - } - if (tangent) - { - *tangent = local_tangent; - } - if (normal) - { - *normal = local_normal; - } - } - } - } - } - } - - //check all avatar nametags (silly, isn't it?) - for (std::vector< LLCharacter* >::iterator iter = LLCharacter::sInstances.begin(); - iter != LLCharacter::sInstances.end(); - ++iter) - { - LLVOAvatar* av = (LLVOAvatar*) *iter; - if (av->mNameText.notNull() - && av->mNameText->lineSegmentIntersect(start, local_end, position)) - { - drawable = av->mDrawable; - local_end = position; - } - } - - if (intersection) - { - *intersection = position; - } - - return drawable ? drawable->getVObj().get() : NULL; -} - -LLViewerObject* LLPipeline::lineSegmentIntersectInHUD(const LLVector4a& start, const LLVector4a& end, - bool pick_transparent, - S32* face_hit, - LLVector4a* intersection, // return the intersection point - LLVector2* tex_coord, // return the texture coordinates of the intersection point - LLVector4a* normal, // return the surface normal at the intersection point - LLVector4a* tangent // return the surface tangent at the intersection point - ) -{ - LLDrawable* drawable = NULL; - - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) - { - LLViewerRegion* region = *iter; - - bool toggle = false; - if (!hasRenderType(LLPipeline::RENDER_TYPE_HUD)) - { - toggleRenderType(LLPipeline::RENDER_TYPE_HUD); - toggle = true; - } - - LLSpatialPartition* part = region->getSpatialPartition(LLViewerRegion::PARTITION_HUD); - if (part) - { - LLDrawable* hit = part->lineSegmentIntersect(start, end, pick_transparent, false, true, false, face_hit, intersection, tex_coord, normal, tangent); - if (hit) - { - drawable = hit; - } - } - - if (toggle) - { - toggleRenderType(LLPipeline::RENDER_TYPE_HUD); - } - } - return drawable ? drawable->getVObj().get() : NULL; -} - -LLSpatialPartition* LLPipeline::getSpatialPartition(LLViewerObject* vobj) -{ - if (vobj) - { - LLViewerRegion* region = vobj->getRegion(); - if (region) - { - return region->getSpatialPartition(vobj->getPartitionType()); - } - } - return NULL; -} - -void LLPipeline::resetVertexBuffers(LLDrawable* drawable) -{ - if (!drawable) - { - return; - } - - for (S32 i = 0; i < drawable->getNumFaces(); i++) - { - LLFace* facep = drawable->getFace(i); - if (facep) - { - facep->clearVertexBuffer(); - } - } -} - -void LLPipeline::renderObjects(U32 type, bool texture, bool batch_texture, bool rigged) -{ - assertInitialized(); - gGL.loadMatrix(gGLModelView); - gGLLastMatrix = NULL; - - if (rigged) - { - mSimplePool->pushRiggedBatches(type + 1, texture, batch_texture); - } - else - { - mSimplePool->pushBatches(type, texture, batch_texture); - } - - gGL.loadMatrix(gGLModelView); - gGLLastMatrix = NULL; -} - -void LLPipeline::renderGLTFObjects(U32 type, bool texture, bool rigged) -{ - assertInitialized(); - gGL.loadMatrix(gGLModelView); - gGLLastMatrix = NULL; - - if (rigged) - { - mSimplePool->pushRiggedGLTFBatches(type + 1, texture); - } - else - { - mSimplePool->pushGLTFBatches(type, texture); - } - - gGL.loadMatrix(gGLModelView); - gGLLastMatrix = NULL; -} - -// Currently only used for shadows -Cosmic,2023-04-19 -void LLPipeline::renderAlphaObjects(bool rigged) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - assertInitialized(); - gGL.loadMatrix(gGLModelView); - gGLLastMatrix = NULL; - S32 sun_up = LLEnvironment::instance().getIsSunUp() ? 1 : 0; - U32 target_width = LLRenderTarget::sCurResX; - U32 type = LLRenderPass::PASS_ALPHA; - LLVOAvatar* lastAvatar = nullptr; - U64 lastMeshId = 0; - auto* begin = gPipeline.beginRenderMap(type); - auto* end = gPipeline.endRenderMap(type); - - for (LLCullResult::drawinfo_iterator i = begin; i != end; ) - { - LLDrawInfo* pparams = *i; - LLCullResult::increment_iterator(i, end); - - if (rigged != (pparams->mAvatar != nullptr)) - { - // Pool contains both rigged and non-rigged DrawInfos. Only draw - // the objects we're interested in in this pass. - continue; - } - - if (rigged) - { - if (pparams->mGLTFMaterial) - { - gDeferredShadowGLTFAlphaBlendProgram.bind(rigged); - LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up); - LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); - LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(ALPHA_BLEND_CUTOFF); - mSimplePool->pushRiggedGLTFBatch(*pparams, lastAvatar, lastMeshId); - } - else - { - gDeferredShadowAlphaMaskProgram.bind(rigged); - LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up); - LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); - LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(ALPHA_BLEND_CUTOFF); - if (lastAvatar != pparams->mAvatar || lastMeshId != pparams->mSkinInfo->mHash) - { - mSimplePool->uploadMatrixPalette(*pparams); - lastAvatar = pparams->mAvatar; - lastMeshId = pparams->mSkinInfo->mHash; - } - - mSimplePool->pushBatch(*pparams, true, true); - } - } - else - { - if (pparams->mGLTFMaterial) - { - gDeferredShadowGLTFAlphaBlendProgram.bind(rigged); - LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up); - LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); - LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(ALPHA_BLEND_CUTOFF); - mSimplePool->pushGLTFBatch(*pparams); - } - else - { - gDeferredShadowAlphaMaskProgram.bind(rigged); - LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up); - LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); - LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(ALPHA_BLEND_CUTOFF); - mSimplePool->pushBatch(*pparams, true, true); - } - } - } - - gGL.loadMatrix(gGLModelView); - gGLLastMatrix = NULL; -} - -// Currently only used for shadows -Cosmic,2023-04-19 -void LLPipeline::renderMaskedObjects(U32 type, bool texture, bool batch_texture, bool rigged) -{ - assertInitialized(); - gGL.loadMatrix(gGLModelView); - gGLLastMatrix = NULL; - if (rigged) - { - mAlphaMaskPool->pushRiggedMaskBatches(type+1, texture, batch_texture); - } - else - { - mAlphaMaskPool->pushMaskBatches(type, texture, batch_texture); - } - gGL.loadMatrix(gGLModelView); - gGLLastMatrix = NULL; -} - -// Currently only used for shadows -Cosmic,2023-04-19 -void LLPipeline::renderFullbrightMaskedObjects(U32 type, bool texture, bool batch_texture, bool rigged) -{ - assertInitialized(); - gGL.loadMatrix(gGLModelView); - gGLLastMatrix = NULL; - if (rigged) - { - mFullbrightAlphaMaskPool->pushRiggedMaskBatches(type+1, texture, batch_texture); - } - else - { - mFullbrightAlphaMaskPool->pushMaskBatches(type, texture, batch_texture); - } - gGL.loadMatrix(gGLModelView); - gGLLastMatrix = NULL; -} - -void apply_cube_face_rotation(U32 face) -{ - switch (face) - { - case 0: - gGL.rotatef(90.f, 0, 1, 0); - gGL.rotatef(180.f, 1, 0, 0); - break; - case 2: - gGL.rotatef(-90.f, 1, 0, 0); - break; - case 4: - gGL.rotatef(180.f, 0, 1, 0); - gGL.rotatef(180.f, 0, 0, 1); - break; - case 1: - gGL.rotatef(-90.f, 0, 1, 0); - gGL.rotatef(180.f, 1, 0, 0); - break; - case 3: - gGL.rotatef(90, 1, 0, 0); - break; - case 5: - gGL.rotatef(180, 0, 0, 1); - break; - } -} - -void validate_framebuffer_object() -{ - GLenum status; - status = glCheckFramebufferStatus(GL_FRAMEBUFFER_EXT); - switch(status) - { - case GL_FRAMEBUFFER_COMPLETE: - //framebuffer OK, no error. - break; - case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: - // frame buffer not OK: probably means unsupported depth buffer format - LL_ERRS() << "Framebuffer Incomplete Missing Attachment." << LL_ENDL; - break; - case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: - // frame buffer not OK: probably means unsupported depth buffer format - LL_ERRS() << "Framebuffer Incomplete Attachment." << LL_ENDL; - break; - case GL_FRAMEBUFFER_UNSUPPORTED: - /* choose different formats */ - LL_ERRS() << "Framebuffer unsupported." << LL_ENDL; - break; - default: - LL_ERRS() << "Unknown framebuffer status." << LL_ENDL; - break; - } -} - -void LLPipeline::bindScreenToTexture() -{ - -} - -static LLTrace::BlockTimerStatHandle FTM_RENDER_BLOOM("Bloom"); - -void LLPipeline::visualizeBuffers(LLRenderTarget* src, LLRenderTarget* dst, U32 bufferIndex) -{ - dst->bindTarget(); - gDeferredBufferVisualProgram.bind(); - gDeferredBufferVisualProgram.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, src, false, LLTexUnit::TFO_BILINEAR, bufferIndex); - - static LLStaticHashedString mipLevel("mipLevel"); - if (RenderBufferVisualization != 4) - gDeferredBufferVisualProgram.uniform1f(mipLevel, 0); - else - gDeferredBufferVisualProgram.uniform1f(mipLevel, 8); - - mScreenTriangleVB->setBuffer(); - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - gDeferredBufferVisualProgram.unbind(); - dst->flush(); -} - -void LLPipeline::generateLuminance(LLRenderTarget* src, LLRenderTarget* dst) -{ - // luminance sample and mipmap generation - { - LL_PROFILE_GPU_ZONE("luminance sample"); - - dst->bindTarget(); - - LLGLDepthTest depth(GL_FALSE, GL_FALSE); - - gLuminanceProgram.bind(); - - S32 channel = 0; - channel = gLuminanceProgram.enableTexture(LLShaderMgr::DEFERRED_DIFFUSE); - if (channel > -1) - { - src->bindTexture(0, channel, LLTexUnit::TFO_POINT); - } - - channel = gLuminanceProgram.enableTexture(LLShaderMgr::DEFERRED_EMISSIVE); - if (channel > -1) - { - mGlow[1].bindTexture(0, channel); - } - - mScreenTriangleVB->setBuffer(); - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - dst->flush(); - - // note -- unbind AFTER the glGenerateMipMap so time in generatemipmap can be profiled under "Luminance" - // also note -- keep an eye on the performance of glGenerateMipmap, might need to replace it with a mip generation shader - gLuminanceProgram.unbind(); - } -} - -void LLPipeline::generateExposure(LLRenderTarget* src, LLRenderTarget* dst) { - // exposure sample - { - LL_PROFILE_GPU_ZONE("exposure sample"); - - { - // copy last frame's exposure into mLastExposure - mLastExposure.bindTarget(); - gCopyProgram.bind(); - gGL.getTexUnit(0)->bind(dst); - - mScreenTriangleVB->setBuffer(); - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - - mLastExposure.flush(); - } - - dst->bindTarget(); - - LLGLDepthTest depth(GL_FALSE, GL_FALSE); - - gExposureProgram.bind(); - - S32 channel = gExposureProgram.enableTexture(LLShaderMgr::DEFERRED_EMISSIVE); - if (channel > -1) - { - mLuminanceMap.bindTexture(0, channel, LLTexUnit::TFO_TRILINEAR); - } - - channel = gExposureProgram.enableTexture(LLShaderMgr::EXPOSURE_MAP); - if (channel > -1) - { - mLastExposure.bindTexture(0, channel); - } - - static LLStaticHashedString dt("dt"); - static LLStaticHashedString noiseVec("noiseVec"); - static LLStaticHashedString dynamic_exposure_params("dynamic_exposure_params"); - static LLCachedControl dynamic_exposure_coefficient(gSavedSettings, "RenderDynamicExposureCoefficient", 0.175f); - static LLCachedControl should_auto_adjust(gSavedSettings, "RenderSkyAutoAdjustLegacy", true); - - LLSettingsSky::ptr_t sky = LLEnvironment::instance().getCurrentSky(); - - F32 probe_ambiance = LLEnvironment::instance().getCurrentSky()->getReflectionProbeAmbiance(should_auto_adjust); - F32 exp_min = 1.f; - F32 exp_max = 1.f; - - if (probe_ambiance > 0.f) - { - F32 hdr_scale = sqrtf(LLEnvironment::instance().getCurrentSky()->getGamma())*2.f; - - if (hdr_scale > 1.f) - { - exp_min = 1.f / hdr_scale; - exp_max = hdr_scale; - } - } - gExposureProgram.uniform1f(dt, gFrameIntervalSeconds); - gExposureProgram.uniform2f(noiseVec, ll_frand() * 2.0 - 1.0, ll_frand() * 2.0 - 1.0); - gExposureProgram.uniform3f(dynamic_exposure_params, dynamic_exposure_coefficient, exp_min, exp_max); - - mScreenTriangleVB->setBuffer(); - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - - gGL.getTexUnit(channel)->unbind(mLastExposure.getUsage()); - gExposureProgram.unbind(); - dst->flush(); - } -} - -void LLPipeline::gammaCorrect(LLRenderTarget* src, LLRenderTarget* dst) { - dst->bindTarget(); - // gamma correct lighting - { - LL_PROFILE_GPU_ZONE("gamma correct"); - - static LLCachedControl buildNoPost(gSavedSettings, "RenderDisablePostProcessing", false); - - LLGLDepthTest depth(GL_FALSE, GL_FALSE); - - // Apply gamma correction to the frame here. - - static LLCachedControl should_auto_adjust(gSavedSettings, "RenderSkyAutoAdjustLegacy", true); - - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - - bool no_post = gSnapshotNoPost || (buildNoPost && gFloaterTools->isAvailable()); - LLGLSLShader& shader = no_post ? gNoPostGammaCorrectProgram : // no post (no gamma, no exposure, no tonemapping) - psky->getReflectionProbeAmbiance(should_auto_adjust) == 0.f ? gLegacyPostGammaCorrectProgram : - gDeferredPostGammaCorrectProgram; - - shader.bind(); - - S32 channel = 0; - - shader.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, src, false, LLTexUnit::TFO_POINT); - - shader.bindTexture(LLShaderMgr::EXPOSURE_MAP, &mExposureMap); - - shader.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, src->getWidth(), src->getHeight()); - - static LLCachedControl exposure(gSavedSettings, "RenderExposure", 1.f); - - F32 e = llclamp(exposure(), 0.5f, 4.f); - - static LLStaticHashedString s_exposure("exposure"); - - shader.uniform1f(s_exposure, e); - - mScreenTriangleVB->setBuffer(); - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - - gGL.getTexUnit(channel)->unbind(src->getUsage()); - shader.unbind(); - } - dst->flush(); -} - -void LLPipeline::copyScreenSpaceReflections(LLRenderTarget* src, LLRenderTarget* dst) -{ - - if (RenderScreenSpaceReflections && !gCubeSnapshot) - { - LL_PROFILE_GPU_ZONE("ssr copy"); - LLGLDepthTest depth(GL_TRUE, GL_TRUE, GL_ALWAYS); - - LLRenderTarget& depth_src = mRT->deferredScreen; - - dst->bindTarget(); - dst->clear(); - gCopyDepthProgram.bind(); - - S32 diff_map = gCopyDepthProgram.getTextureChannel(LLShaderMgr::DIFFUSE_MAP); - S32 depth_map = gCopyDepthProgram.getTextureChannel(LLShaderMgr::DEFERRED_DEPTH); - - gGL.getTexUnit(diff_map)->bind(src); - gGL.getTexUnit(depth_map)->bind(&depth_src, true); - - mScreenTriangleVB->setBuffer(); - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - - dst->flush(); - } -} - -void LLPipeline::generateGlow(LLRenderTarget* src) -{ - if (sRenderGlow) - { - LL_PROFILE_GPU_ZONE("glow"); - mGlow[2].bindTarget(); - mGlow[2].clear(); - - gGlowExtractProgram.bind(); - F32 maxAlpha = RenderGlowMaxExtractAlpha; - F32 warmthAmount = RenderGlowWarmthAmount; - LLVector3 lumWeights = RenderGlowLumWeights; - LLVector3 warmthWeights = RenderGlowWarmthWeights; - - gGlowExtractProgram.uniform1f(LLShaderMgr::GLOW_MIN_LUMINANCE, 9999); - gGlowExtractProgram.uniform1f(LLShaderMgr::GLOW_MAX_EXTRACT_ALPHA, maxAlpha); - gGlowExtractProgram.uniform3f(LLShaderMgr::GLOW_LUM_WEIGHTS, lumWeights.mV[0], lumWeights.mV[1], - lumWeights.mV[2]); - gGlowExtractProgram.uniform3f(LLShaderMgr::GLOW_WARMTH_WEIGHTS, warmthWeights.mV[0], warmthWeights.mV[1], - warmthWeights.mV[2]); - gGlowExtractProgram.uniform1f(LLShaderMgr::GLOW_WARMTH_AMOUNT, warmthAmount); - - if (RenderGlowNoise) - { - S32 channel = gGlowExtractProgram.enableTexture(LLShaderMgr::GLOW_NOISE_MAP); - if (channel > -1) - { - gGL.getTexUnit(channel)->bindManual(LLTexUnit::TT_TEXTURE, mTrueNoiseMap); - gGL.getTexUnit(channel)->setTextureFilteringOption(LLTexUnit::TFO_POINT); - } - gGlowExtractProgram.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, - mGlow[2].getWidth(), - mGlow[2].getHeight()); - } - - { - LLGLEnable blend_on(GL_BLEND); - - gGL.setSceneBlendType(LLRender::BT_ADD_WITH_ALPHA); - - gGlowExtractProgram.bindTexture(LLShaderMgr::DIFFUSE_MAP, src); - - gGL.color4f(1, 1, 1, 1); - gPipeline.enableLightsFullbright(); - - mScreenTriangleVB->setBuffer(); - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - - mGlow[2].flush(); - } - - gGlowExtractProgram.unbind(); - - // power of two between 1 and 1024 - U32 glowResPow = RenderGlowResolutionPow; - const U32 glow_res = llmax(1, llmin(1024, 1 << glowResPow)); - - S32 kernel = RenderGlowIterations * 2; - F32 delta = RenderGlowWidth / glow_res; - // Use half the glow width if we have the res set to less than 9 so that it looks - // almost the same in either case. - if (glowResPow < 9) - { - delta *= 0.5f; - } - F32 strength = RenderGlowStrength; - - gGlowProgram.bind(); - gGlowProgram.uniform1f(LLShaderMgr::GLOW_STRENGTH, strength); - - for (S32 i = 0; i < kernel; i++) - { - mGlow[i % 2].bindTarget(); - mGlow[i % 2].clear(); - - if (i == 0) - { - gGlowProgram.bindTexture(LLShaderMgr::DIFFUSE_MAP, &mGlow[2]); - } - else - { - gGlowProgram.bindTexture(LLShaderMgr::DIFFUSE_MAP, &mGlow[(i - 1) % 2]); - } - - if (i % 2 == 0) - { - gGlowProgram.uniform2f(LLShaderMgr::GLOW_DELTA, delta, 0); - } - else - { - gGlowProgram.uniform2f(LLShaderMgr::GLOW_DELTA, 0, delta); - } - - mScreenTriangleVB->setBuffer(); - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - - mGlow[i % 2].flush(); - } - - gGlowProgram.unbind(); - - } - else // !sRenderGlow, skip the glow ping-pong and just clear the result target - { - mGlow[1].bindTarget(); - mGlow[1].clear(); - mGlow[1].flush(); - } -} - -void LLPipeline::applyFXAA(LLRenderTarget* src, LLRenderTarget* dst) -{ - { - llassert(!gCubeSnapshot); - bool multisample = RenderFSAASamples > 1 && mRT->fxaaBuffer.isComplete(); - LLGLSLShader* shader = &gGlowCombineProgram; - - S32 width = dst->getWidth(); - S32 height = dst->getHeight(); - - // Present everything. - if (multisample) - { - LL_PROFILE_GPU_ZONE("aa"); - // bake out texture2D with RGBL for FXAA shader - mRT->fxaaBuffer.bindTarget(); - - shader = &gGlowCombineFXAAProgram; - shader->bind(); - - S32 channel = shader->enableTexture(LLShaderMgr::DEFERRED_DIFFUSE, src->getUsage()); - if (channel > -1) - { - src->bindTexture(0, channel, LLTexUnit::TFO_BILINEAR); - } - - { - LLGLDepthTest depth_test(GL_TRUE, GL_TRUE, GL_ALWAYS); - mScreenTriangleVB->setBuffer(); - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - } - - shader->disableTexture(LLShaderMgr::DEFERRED_DIFFUSE, src->getUsage()); - shader->unbind(); - - mRT->fxaaBuffer.flush(); - - dst->bindTarget(); - shader = &gFXAAProgram; - shader->bind(); - - channel = shader->enableTexture(LLShaderMgr::DIFFUSE_MAP, mRT->fxaaBuffer.getUsage()); - if (channel > -1) - { - mRT->fxaaBuffer.bindTexture(0, channel, LLTexUnit::TFO_BILINEAR); - } - - gGLViewport[0] = gViewerWindow->getWorldViewRectRaw().mLeft; - gGLViewport[1] = gViewerWindow->getWorldViewRectRaw().mBottom; - gGLViewport[2] = gViewerWindow->getWorldViewRectRaw().getWidth(); - gGLViewport[3] = gViewerWindow->getWorldViewRectRaw().getHeight(); - - glViewport(gGLViewport[0], gGLViewport[1], gGLViewport[2], gGLViewport[3]); - - F32 scale_x = (F32)width / mRT->fxaaBuffer.getWidth(); - F32 scale_y = (F32)height / mRT->fxaaBuffer.getHeight(); - shader->uniform2f(LLShaderMgr::FXAA_TC_SCALE, scale_x, scale_y); - shader->uniform2f(LLShaderMgr::FXAA_RCP_SCREEN_RES, 1.f / width * scale_x, 1.f / height * scale_y); - shader->uniform4f(LLShaderMgr::FXAA_RCP_FRAME_OPT, -0.5f / width * scale_x, -0.5f / height * scale_y, - 0.5f / width * scale_x, 0.5f / height * scale_y); - shader->uniform4f(LLShaderMgr::FXAA_RCP_FRAME_OPT2, -2.f / width * scale_x, -2.f / height * scale_y, - 2.f / width * scale_x, 2.f / height * scale_y); - - { - LLGLDepthTest depth_test(GL_TRUE, GL_TRUE, GL_ALWAYS); - S32 depth_channel = shader->getTextureChannel(LLShaderMgr::DEFERRED_DEPTH); - gGL.getTexUnit(depth_channel)->bind(&mRT->deferredScreen, true); - - mScreenTriangleVB->setBuffer(); - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - } - - shader->unbind(); - dst->flush(); - } - else { - copyRenderTarget(src, dst); - } - } -} - -void LLPipeline::copyRenderTarget(LLRenderTarget* src, LLRenderTarget* dst) -{ - - LL_PROFILE_GPU_ZONE("copyRenderTarget"); - dst->bindTarget(); - - gDeferredPostNoDoFProgram.bind(); - - gDeferredPostNoDoFProgram.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, src); - gDeferredPostNoDoFProgram.bindTexture(LLShaderMgr::DEFERRED_DEPTH, &mRT->deferredScreen, true); - - { - mScreenTriangleVB->setBuffer(); - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - } - - gDeferredPostNoDoFProgram.unbind(); - - dst->flush(); -} - -void LLPipeline::combineGlow(LLRenderTarget* src, LLRenderTarget* dst) -{ - // Go ahead and do our glow combine here in our destination. We blit this later into the front buffer. - - dst->bindTarget(); - - { - - gGlowCombineProgram.bind(); - - gGlowCombineProgram.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, src); - gGlowCombineProgram.bindTexture(LLShaderMgr::DEFERRED_EMISSIVE, &mGlow[1]); - - mScreenTriangleVB->setBuffer(); - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - } - - dst->flush(); -} - -void LLPipeline::renderDoF(LLRenderTarget* src, LLRenderTarget* dst) -{ - { - bool dof_enabled = - (RenderDepthOfFieldInEditMode || !LLToolMgr::getInstance()->inBuildMode()) && - RenderDepthOfField && - !gCubeSnapshot; - - gViewerWindow->setup3DViewport(); - - if (dof_enabled) - { - LL_PROFILE_GPU_ZONE("dof"); - LLGLDisable blend(GL_BLEND); - - // depth of field focal plane calculations - static F32 current_distance = 16.f; - static F32 start_distance = 16.f; - static F32 transition_time = 1.f; - - LLVector3 focus_point; - - LLViewerObject* obj = LLViewerMediaFocus::getInstance()->getFocusedObject(); - if (obj && obj->mDrawable && obj->isSelected()) - { // focus on selected media object - S32 face_idx = LLViewerMediaFocus::getInstance()->getFocusedFace(); - if (obj && obj->mDrawable) - { - LLFace* face = obj->mDrawable->getFace(face_idx); - if (face) - { - focus_point = face->getPositionAgent(); - } - } - } - - if (focus_point.isExactlyZero()) - { - if (LLViewerJoystick::getInstance()->getOverrideCamera()) - { // focus on point under cursor - focus_point.set(gDebugRaycastIntersection.getF32ptr()); - } - else if (gAgentCamera.cameraMouselook()) - { // focus on point under mouselook crosshairs - LLVector4a result; - result.clear(); - - gViewerWindow->cursorIntersect(-1, -1, 512.f, NULL, -1, false, false, true, true, NULL, &result); - - focus_point.set(result.getF32ptr()); - } - else - { - // focus on alt-zoom target - LLViewerRegion* region = gAgent.getRegion(); - if (region) - { - focus_point = LLVector3(gAgentCamera.getFocusGlobal() - region->getOriginGlobal()); - } - } - } - - LLVector3 eye = LLViewerCamera::getInstance()->getOrigin(); - F32 target_distance = 16.f; - if (!focus_point.isExactlyZero()) - { - target_distance = LLViewerCamera::getInstance()->getAtAxis() * (focus_point - eye); - } - - if (transition_time >= 1.f && fabsf(current_distance - target_distance) / current_distance > 0.01f) - { // large shift happened, interpolate smoothly to new target distance - transition_time = 0.f; - start_distance = current_distance; - } - else if (transition_time < 1.f) - { // currently in a transition, continue interpolating - transition_time += 1.f / CameraFocusTransitionTime * gFrameIntervalSeconds.value(); - transition_time = llmin(transition_time, 1.f); - - F32 t = cosf(transition_time * F_PI + F_PI) * 0.5f + 0.5f; - current_distance = start_distance + (target_distance - start_distance) * t; - } - else - { // small or no change, just snap to target distance - current_distance = target_distance; - } - - // convert to mm - F32 subject_distance = current_distance * 1000.f; - F32 fnumber = CameraFNumber; - F32 default_focal_length = CameraFocalLength; - - F32 fov = LLViewerCamera::getInstance()->getView(); - - const F32 default_fov = CameraFieldOfView * F_PI / 180.f; - - // F32 aspect_ratio = (F32) mRT->screen.getWidth()/(F32)mRT->screen.getHeight(); - - F32 dv = 2.f * default_focal_length * tanf(default_fov / 2.f); - - F32 focal_length = dv / (2 * tanf(fov / 2.f)); - - // F32 tan_pixel_angle = tanf(LLDrawable::sCurPixelAngle); - - // from wikipedia -- c = |s2-s1|/s2 * f^2/(N(S1-f)) - // where N = fnumber - // s2 = dot distance - // s1 = subject distance - // f = focal length - // - - F32 blur_constant = focal_length * focal_length / (fnumber * (subject_distance - focal_length)); - blur_constant /= 1000.f; // convert to meters for shader - F32 magnification = focal_length / (subject_distance - focal_length); - - { // build diffuse+bloom+CoF - mRT->deferredLight.bindTarget(); - - gDeferredCoFProgram.bind(); - - gDeferredCoFProgram.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, src, LLTexUnit::TFO_POINT); - gDeferredCoFProgram.bindTexture(LLShaderMgr::DEFERRED_DEPTH, &mRT->deferredScreen, true); - - gDeferredCoFProgram.uniform1f(LLShaderMgr::DEFERRED_DEPTH_CUTOFF, RenderEdgeDepthCutoff); - gDeferredCoFProgram.uniform1f(LLShaderMgr::DEFERRED_NORM_CUTOFF, RenderEdgeNormCutoff); - gDeferredCoFProgram.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, dst->getWidth(), dst->getHeight()); - gDeferredCoFProgram.uniform1f(LLShaderMgr::DOF_FOCAL_DISTANCE, -subject_distance / 1000.f); - gDeferredCoFProgram.uniform1f(LLShaderMgr::DOF_BLUR_CONSTANT, blur_constant); - gDeferredCoFProgram.uniform1f(LLShaderMgr::DOF_TAN_PIXEL_ANGLE, tanf(1.f / LLDrawable::sCurPixelAngle)); - gDeferredCoFProgram.uniform1f(LLShaderMgr::DOF_MAGNIFICATION, magnification); - gDeferredCoFProgram.uniform1f(LLShaderMgr::DOF_MAX_COF, CameraMaxCoF); - gDeferredCoFProgram.uniform1f(LLShaderMgr::DOF_RES_SCALE, CameraDoFResScale); - - mScreenTriangleVB->setBuffer(); - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - gDeferredCoFProgram.unbind(); - mRT->deferredLight.flush(); - } - - U32 dof_width = (U32)(mRT->screen.getWidth() * CameraDoFResScale); - U32 dof_height = (U32)(mRT->screen.getHeight() * CameraDoFResScale); - - { // perform DoF sampling at half-res (preserve alpha channel) - src->bindTarget(); - glViewport(0, 0, dof_width, dof_height); - - gGL.setColorMask(true, false); - - gDeferredPostProgram.bind(); - gDeferredPostProgram.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, &mRT->deferredLight, LLTexUnit::TFO_POINT); - - gDeferredPostProgram.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, dst->getWidth(), dst->getHeight()); - gDeferredPostProgram.uniform1f(LLShaderMgr::DOF_MAX_COF, CameraMaxCoF); - gDeferredPostProgram.uniform1f(LLShaderMgr::DOF_RES_SCALE, CameraDoFResScale); - - mScreenTriangleVB->setBuffer(); - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - - gDeferredPostProgram.unbind(); - - src->flush(); - gGL.setColorMask(true, true); - } - - { // combine result based on alpha - - dst->bindTarget(); - if (RenderFSAASamples > 1 && mRT->fxaaBuffer.isComplete()) - { - glViewport(0, 0, dst->getWidth(), dst->getHeight()); - } - else - { - gGLViewport[0] = gViewerWindow->getWorldViewRectRaw().mLeft; - gGLViewport[1] = gViewerWindow->getWorldViewRectRaw().mBottom; - gGLViewport[2] = gViewerWindow->getWorldViewRectRaw().getWidth(); - gGLViewport[3] = gViewerWindow->getWorldViewRectRaw().getHeight(); - glViewport(gGLViewport[0], gGLViewport[1], gGLViewport[2], gGLViewport[3]); - } - - gDeferredDoFCombineProgram.bind(); - gDeferredDoFCombineProgram.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, src, LLTexUnit::TFO_POINT); - gDeferredDoFCombineProgram.bindTexture(LLShaderMgr::DEFERRED_LIGHT, &mRT->deferredLight, LLTexUnit::TFO_POINT); - - gDeferredDoFCombineProgram.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, dst->getWidth(), dst->getHeight()); - gDeferredDoFCombineProgram.uniform1f(LLShaderMgr::DOF_MAX_COF, CameraMaxCoF); - gDeferredDoFCombineProgram.uniform1f(LLShaderMgr::DOF_RES_SCALE, CameraDoFResScale); - gDeferredDoFCombineProgram.uniform1f(LLShaderMgr::DOF_WIDTH, (dof_width - 1) / (F32)src->getWidth()); - gDeferredDoFCombineProgram.uniform1f(LLShaderMgr::DOF_HEIGHT, (dof_height - 1) / (F32)src->getHeight()); - - mScreenTriangleVB->setBuffer(); - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - - gDeferredDoFCombineProgram.unbind(); - - dst->flush(); - } - } - else - { - copyRenderTarget(src, dst); - } - } -} - -void LLPipeline::renderFinalize() -{ - llassert(!gCubeSnapshot); - LLVertexBuffer::unbind(); - LLGLState::checkStates(); - - assertInitialized(); - - LL_RECORD_BLOCK_TIME(FTM_RENDER_BLOOM); - LL_PROFILE_GPU_ZONE("renderFinalize"); - - gGL.color4f(1, 1, 1, 1); - LLGLDepthTest depth(GL_FALSE); - LLGLDisable blend(GL_BLEND); - LLGLDisable cull(GL_CULL_FACE); - - enableLightsFullbright(); - - gGL.setColorMask(true, true); - glClearColor(0, 0, 0, 0); - - - copyScreenSpaceReflections(&mRT->screen, &mSceneMap); - - generateLuminance(&mRT->screen, &mLuminanceMap); - - generateExposure(&mLuminanceMap, &mExposureMap); - - gammaCorrect(&mRT->screen, &mPostMap); - - LLVertexBuffer::unbind(); - - generateGlow(&mPostMap); - - combineGlow(&mPostMap, &mRT->screen); - - gGLViewport[0] = gViewerWindow->getWorldViewRectRaw().mLeft; - gGLViewport[1] = gViewerWindow->getWorldViewRectRaw().mBottom; - gGLViewport[2] = gViewerWindow->getWorldViewRectRaw().getWidth(); - gGLViewport[3] = gViewerWindow->getWorldViewRectRaw().getHeight(); - glViewport(gGLViewport[0], gGLViewport[1], gGLViewport[2], gGLViewport[3]); - - renderDoF(&mRT->screen, &mPostMap); - - applyFXAA(&mPostMap, &mRT->screen); - LLRenderTarget* finalBuffer = &mRT->screen; - if (RenderBufferVisualization > -1) - { - finalBuffer = &mPostMap; - switch (RenderBufferVisualization) - { - case 0: - case 1: - case 2: - case 3: - visualizeBuffers(&mRT->deferredScreen, finalBuffer, RenderBufferVisualization); - break; - case 4: - visualizeBuffers(&mLuminanceMap, finalBuffer, 0); - default: - break; - } - } - - // Present the screen target. - - gDeferredPostNoDoFProgram.bind(); - - // Whatever is last in the above post processing chain should _always_ be rendered directly here. If not, expect problems. - gDeferredPostNoDoFProgram.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, finalBuffer); - gDeferredPostNoDoFProgram.bindTexture(LLShaderMgr::DEFERRED_DEPTH, &mRT->deferredScreen, true); - - { - LLGLDepthTest depth_test(GL_TRUE, GL_TRUE, GL_ALWAYS); - mScreenTriangleVB->setBuffer(); - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - } - - gDeferredPostNoDoFProgram.unbind(); - - gGL.setSceneBlendType(LLRender::BT_ALPHA); - - if (hasRenderDebugMask(LLPipeline::RENDER_DEBUG_PHYSICS_SHAPES)) - { - renderPhysicsDisplay(); - } - - /*if (LLRenderTarget::sUseFBO && !gCubeSnapshot) - { // copy depth buffer from mRT->screen to framebuffer - LLRenderTarget::copyContentsToFramebuffer(mRT->screen, 0, 0, mRT->screen.getWidth(), mRT->screen.getHeight(), 0, 0, - mRT->screen.getWidth(), mRT->screen.getHeight(), - GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST); - }*/ - - LLVertexBuffer::unbind(); - - LLGLState::checkStates(); - - // flush calls made to "addTrianglesDrawn" so far to stats machinery - recordTrianglesDrawn(); -} - -void LLPipeline::bindLightFunc(LLGLSLShader& shader) -{ - S32 channel = shader.enableTexture(LLShaderMgr::DEFERRED_LIGHTFUNC); - if (channel > -1) - { - gGL.getTexUnit(channel)->bindManual(LLTexUnit::TT_TEXTURE, mLightFunc); - } - - channel = shader.enableTexture(LLShaderMgr::DEFERRED_BRDF_LUT, LLTexUnit::TT_TEXTURE); - if (channel > -1) - { - mPbrBrdfLut.bindTexture(0, channel); - } -} - -void LLPipeline::bindShadowMaps(LLGLSLShader& shader) -{ - for (U32 i = 0; i < 4; i++) - { - LLRenderTarget* shadow_target = getSunShadowTarget(i); - if (shadow_target) - { - S32 channel = shader.enableTexture(LLShaderMgr::DEFERRED_SHADOW0 + i, LLTexUnit::TT_TEXTURE); - if (channel > -1) - { - gGL.getTexUnit(channel)->bind(getSunShadowTarget(i), true); - } - } - } - - for (U32 i = 4; i < 6; i++) - { - S32 channel = shader.enableTexture(LLShaderMgr::DEFERRED_SHADOW0 + i); - if (channel > -1) - { - LLRenderTarget* shadow_target = getSpotShadowTarget(i - 4); - if (shadow_target) - { - gGL.getTexUnit(channel)->bind(shadow_target, true); - } - } - } -} - -void LLPipeline::bindDeferredShaderFast(LLGLSLShader& shader) -{ - if (shader.mCanBindFast) - { // was previously fully bound, use fast path - shader.bind(); - bindLightFunc(shader); - bindShadowMaps(shader); - bindReflectionProbes(shader); - } - else - { //wasn't previously bound, use slow path - bindDeferredShader(shader); - shader.mCanBindFast = true; - } -} - -void LLPipeline::bindDeferredShader(LLGLSLShader& shader, LLRenderTarget* light_target, LLRenderTarget* depth_target) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - LLRenderTarget* deferred_target = &mRT->deferredScreen; - LLRenderTarget* deferred_light_target = &mRT->deferredLight; - - shader.bind(); - S32 channel = 0; - channel = shader.enableTexture(LLShaderMgr::DEFERRED_DIFFUSE, deferred_target->getUsage()); - if (channel > -1) - { - deferred_target->bindTexture(0,channel, LLTexUnit::TFO_POINT); // frag_data[0] - gGL.getTexUnit(channel)->setTextureAddressMode(LLTexUnit::TAM_CLAMP); - } - - channel = shader.enableTexture(LLShaderMgr::DEFERRED_SPECULAR, deferred_target->getUsage()); - if (channel > -1) - { - deferred_target->bindTexture(1, channel, LLTexUnit::TFO_POINT); // frag_data[1] - gGL.getTexUnit(channel)->setTextureAddressMode(LLTexUnit::TAM_CLAMP); - } - - channel = shader.enableTexture(LLShaderMgr::DEFERRED_NORMAL, deferred_target->getUsage()); - if (channel > -1) - { - deferred_target->bindTexture(2, channel, LLTexUnit::TFO_POINT); // frag_data[2] - gGL.getTexUnit(channel)->setTextureAddressMode(LLTexUnit::TAM_CLAMP); - } - - channel = shader.enableTexture(LLShaderMgr::DEFERRED_EMISSIVE, deferred_target->getUsage()); - if (channel > -1) - { - deferred_target->bindTexture(3, channel, LLTexUnit::TFO_POINT); // frag_data[3] - gGL.getTexUnit(channel)->setTextureAddressMode(LLTexUnit::TAM_CLAMP); - } - - channel = shader.enableTexture(LLShaderMgr::DEFERRED_DEPTH, deferred_target->getUsage()); - if (channel > -1) - { - if (depth_target) - { - gGL.getTexUnit(channel)->bind(depth_target, true); - } - else - { - gGL.getTexUnit(channel)->bind(deferred_target, true); - } - stop_glerror(); - } - - channel = shader.enableTexture(LLShaderMgr::EXPOSURE_MAP); - if (channel > -1) - { - gGL.getTexUnit(channel)->bind(&mExposureMap); - } - - if (shader.getUniformLocation(LLShaderMgr::VIEWPORT) != -1) - { - shader.uniform4f(LLShaderMgr::VIEWPORT, (F32) gGLViewport[0], - (F32) gGLViewport[1], - (F32) gGLViewport[2], - (F32) gGLViewport[3]); - } - - if (sReflectionRender && !shader.getUniformLocation(LLShaderMgr::MODELVIEW_MATRIX)) - { - shader.uniformMatrix4fv(LLShaderMgr::MODELVIEW_MATRIX, 1, false, mReflectionModelView.m); - } - - channel = shader.enableTexture(LLShaderMgr::DEFERRED_NOISE); - if (channel > -1) - { - gGL.getTexUnit(channel)->bindManual(LLTexUnit::TT_TEXTURE, mNoiseMap); - gGL.getTexUnit(channel)->setTextureFilteringOption(LLTexUnit::TFO_POINT); - } - - bindLightFunc(shader); - - stop_glerror(); - - light_target = light_target ? light_target : deferred_light_target; - channel = shader.enableTexture(LLShaderMgr::DEFERRED_LIGHT, light_target->getUsage()); - if (channel > -1) - { - if (light_target->isComplete()) - { - light_target->bindTexture(0, channel, LLTexUnit::TFO_POINT); - } - else - { - gGL.getTexUnit(channel)->bindFast(LLViewerFetchedTexture::sWhiteImagep); - } - } - - stop_glerror(); - - bindShadowMaps(shader); - - stop_glerror(); - - F32 mat[16*6]; - for (U32 i = 0; i < 16; i++) - { - mat[i] = mSunShadowMatrix[0].m[i]; - mat[i+16] = mSunShadowMatrix[1].m[i]; - mat[i+32] = mSunShadowMatrix[2].m[i]; - mat[i+48] = mSunShadowMatrix[3].m[i]; - mat[i+64] = mSunShadowMatrix[4].m[i]; - mat[i+80] = mSunShadowMatrix[5].m[i]; - } - - shader.uniformMatrix4fv(LLShaderMgr::DEFERRED_SHADOW_MATRIX, 6, false, mat); - - stop_glerror(); - - if (!LLPipeline::sReflectionProbesEnabled) - { - channel = shader.enableTexture(LLShaderMgr::ENVIRONMENT_MAP, LLTexUnit::TT_CUBE_MAP); - if (channel > -1) - { - LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL; - if (cube_map) - { - cube_map->enable(channel); - cube_map->bind(); - } - - F32* m = gGLModelView; - - F32 mat[] = { m[0], m[1], m[2], - m[4], m[5], m[6], - m[8], m[9], m[10] }; - - shader.uniformMatrix3fv(LLShaderMgr::DEFERRED_ENV_MAT, 1, true, mat); - } - } - - bindReflectionProbes(shader); - - if (gAtmosphere) - { - // bind precomputed textures necessary for calculating sun and sky luminance - channel = shader.enableTexture(LLShaderMgr::TRANSMITTANCE_TEX, LLTexUnit::TT_TEXTURE); - if (channel > -1) - { - shader.bindTexture(LLShaderMgr::TRANSMITTANCE_TEX, gAtmosphere->getTransmittance()); - } - - channel = shader.enableTexture(LLShaderMgr::SCATTER_TEX, LLTexUnit::TT_TEXTURE_3D); - if (channel > -1) - { - shader.bindTexture(LLShaderMgr::SCATTER_TEX, gAtmosphere->getScattering()); - } - - channel = shader.enableTexture(LLShaderMgr::SINGLE_MIE_SCATTER_TEX, LLTexUnit::TT_TEXTURE_3D); - if (channel > -1) - { - shader.bindTexture(LLShaderMgr::SINGLE_MIE_SCATTER_TEX, gAtmosphere->getMieScattering()); - } - - channel = shader.enableTexture(LLShaderMgr::ILLUMINANCE_TEX, LLTexUnit::TT_TEXTURE); - if (channel > -1) - { - shader.bindTexture(LLShaderMgr::ILLUMINANCE_TEX, gAtmosphere->getIlluminance()); - } - } - - /*if (gCubeSnapshot) - { // we only really care about the first two values, but the shader needs increasing separation between clip planes - shader.uniform4f(LLShaderMgr::DEFERRED_SHADOW_CLIP, 1.f, 64.f, 128.f, 256.f); - } - else*/ - { - shader.uniform4fv(LLShaderMgr::DEFERRED_SHADOW_CLIP, 1, mSunClipPlanes.mV); - } - shader.uniform1f(LLShaderMgr::DEFERRED_SUN_WASH, RenderDeferredSunWash); - shader.uniform1f(LLShaderMgr::DEFERRED_SHADOW_NOISE, RenderShadowNoise); - shader.uniform1f(LLShaderMgr::DEFERRED_BLUR_SIZE, RenderShadowBlurSize); - - shader.uniform1f(LLShaderMgr::DEFERRED_SSAO_RADIUS, RenderSSAOScale); - shader.uniform1f(LLShaderMgr::DEFERRED_SSAO_MAX_RADIUS, RenderSSAOMaxScale); - - F32 ssao_factor = RenderSSAOFactor; - shader.uniform1f(LLShaderMgr::DEFERRED_SSAO_FACTOR, ssao_factor); - shader.uniform1f(LLShaderMgr::DEFERRED_SSAO_FACTOR_INV, 1.0/ssao_factor); - - LLVector3 ssao_effect = RenderSSAOEffect; - F32 matrix_diag = (ssao_effect[0] + 2.0*ssao_effect[1])/3.0; - F32 matrix_nondiag = (ssao_effect[0] - ssao_effect[1])/3.0; - // This matrix scales (proj of color onto <1/rt(3),1/rt(3),1/rt(3)>) by - // value factor, and scales remainder by saturation factor - F32 ssao_effect_mat[] = { matrix_diag, matrix_nondiag, matrix_nondiag, - matrix_nondiag, matrix_diag, matrix_nondiag, - matrix_nondiag, matrix_nondiag, matrix_diag}; - shader.uniformMatrix3fv(LLShaderMgr::DEFERRED_SSAO_EFFECT_MAT, 1, GL_FALSE, ssao_effect_mat); - - //F32 shadow_offset_error = 1.f + RenderShadowOffsetError * fabsf(LLViewerCamera::getInstance()->getOrigin().mV[2]); - F32 shadow_bias_error = RenderShadowBiasError * fabsf(LLViewerCamera::getInstance()->getOrigin().mV[2])/3000.f; - F32 shadow_bias = RenderShadowBias + shadow_bias_error; - - shader.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, deferred_target->getWidth(), deferred_target->getHeight()); - shader.uniform1f(LLShaderMgr::DEFERRED_NEAR_CLIP, LLViewerCamera::getInstance()->getNear()*2.f); - shader.uniform1f (LLShaderMgr::DEFERRED_SHADOW_OFFSET, RenderShadowOffset); //*shadow_offset_error); - shader.uniform1f(LLShaderMgr::DEFERRED_SHADOW_BIAS, shadow_bias); - shader.uniform1f(LLShaderMgr::DEFERRED_SPOT_SHADOW_OFFSET, RenderSpotShadowOffset); - shader.uniform1f(LLShaderMgr::DEFERRED_SPOT_SHADOW_BIAS, RenderSpotShadowBias); - - shader.uniform3fv(LLShaderMgr::DEFERRED_SUN_DIR, 1, mTransformedSunDir.mV); - shader.uniform3fv(LLShaderMgr::DEFERRED_MOON_DIR, 1, mTransformedMoonDir.mV); - shader.uniform2f(LLShaderMgr::DEFERRED_SHADOW_RES, mRT->shadow[0].getWidth(), mRT->shadow[0].getHeight()); - shader.uniform2f(LLShaderMgr::DEFERRED_PROJ_SHADOW_RES, mSpotShadow[0].getWidth(), mSpotShadow[0].getHeight()); - shader.uniform1f(LLShaderMgr::DEFERRED_DEPTH_CUTOFF, RenderEdgeDepthCutoff); - shader.uniform1f(LLShaderMgr::DEFERRED_NORM_CUTOFF, RenderEdgeNormCutoff); - - shader.uniformMatrix4fv(LLShaderMgr::MODELVIEW_DELTA_MATRIX, 1, GL_FALSE, gGLDeltaModelView); - shader.uniformMatrix4fv(LLShaderMgr::INVERSE_MODELVIEW_DELTA_MATRIX, 1, GL_FALSE, gGLInverseDeltaModelView); - - shader.uniform1i(LLShaderMgr::CUBE_SNAPSHOT, gCubeSnapshot ? 1 : 0); - - if (shader.getUniformLocation(LLShaderMgr::DEFERRED_NORM_MATRIX) >= 0) - { - glh::matrix4f norm_mat = get_current_modelview().inverse().transpose(); - shader.uniformMatrix4fv(LLShaderMgr::DEFERRED_NORM_MATRIX, 1, false, norm_mat.m); - } - - // auto adjust legacy sun color if needed - static LLCachedControl should_auto_adjust(gSavedSettings, "RenderSkyAutoAdjustLegacy", true); - static LLCachedControl auto_adjust_sun_color_scale(gSavedSettings, "RenderSkyAutoAdjustSunColorScale", 1.f); - LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - LLColor3 sun_diffuse(mSunDiffuse.mV); - if (should_auto_adjust && psky->canAutoAdjust()) - { - sun_diffuse *= auto_adjust_sun_color_scale; - } - - shader.uniform3fv(LLShaderMgr::SUNLIGHT_COLOR, 1, sun_diffuse.mV); - shader.uniform3fv(LLShaderMgr::MOONLIGHT_COLOR, 1, mMoonDiffuse.mV); - - shader.uniform1f(LLShaderMgr::REFLECTION_PROBE_MAX_LOD, mReflectionMapManager.mMaxProbeLOD); -} - - -LLColor3 pow3f(LLColor3 v, F32 f) -{ - v.mV[0] = powf(v.mV[0], f); - v.mV[1] = powf(v.mV[1], f); - v.mV[2] = powf(v.mV[2], f); - return v; -} - -LLVector4 pow4fsrgb(LLVector4 v, F32 f) -{ - v.mV[0] = powf(v.mV[0], f); - v.mV[1] = powf(v.mV[1], f); - v.mV[2] = powf(v.mV[2], f); - return v; -} - -void LLPipeline::renderDeferredLighting() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - LL_PROFILE_GPU_ZONE("renderDeferredLighting"); - if (!sCull) - { - return; - } - - llassert(!sRenderingHUDs); - - F32 light_scale = 1.f; - - if (gCubeSnapshot) - { //darken local lights when probe ambiance is above 1 - light_scale = mReflectionMapManager.mLightScale; - } - - LLRenderTarget *screen_target = &mRT->screen; - LLRenderTarget* deferred_light_target = &mRT->deferredLight; - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("deferred"); - LLViewerCamera *camera = LLViewerCamera::getInstance(); - - if (gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_HUD)) - { - gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_HUD); - } - - gGL.setColorMask(true, true); - - // draw a cube around every light - LLVertexBuffer::unbind(); - - LLGLEnable cull(GL_CULL_FACE); - LLGLEnable blend(GL_BLEND); - - glh::matrix4f mat = copy_matrix(gGLModelView); - - setupHWLights(); // to set mSun/MoonDir; - - glh::vec4f tc(mSunDir.mV); - mat.mult_matrix_vec(tc); - mTransformedSunDir.set(tc.v); - - glh::vec4f tc_moon(mMoonDir.mV); - mat.mult_matrix_vec(tc_moon); - mTransformedMoonDir.set(tc_moon.v); - - if (RenderDeferredSSAO || RenderShadowDetail > 0) - { - LL_PROFILE_GPU_ZONE("sun program"); - deferred_light_target->bindTarget(); - { // paint shadow/SSAO light map (direct lighting lightmap) - LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("renderDeferredLighting - sun shadow"); - bindDeferredShader(gDeferredSunProgram, deferred_light_target); - mScreenTriangleVB->setBuffer(); - glClearColor(1, 1, 1, 1); - deferred_light_target->clear(GL_COLOR_BUFFER_BIT); - glClearColor(0, 0, 0, 0); - - glh::matrix4f inv_trans = get_current_modelview().inverse().transpose(); - - const U32 slice = 32; - F32 offset[slice * 3]; - for (U32 i = 0; i < 4; i++) - { - for (U32 j = 0; j < 8; j++) - { - glh::vec3f v; - v.set_value(sinf(6.284f / 8 * j), cosf(6.284f / 8 * j), -(F32) i); - v.normalize(); - inv_trans.mult_matrix_vec(v); - v.normalize(); - offset[(i * 8 + j) * 3 + 0] = v.v[0]; - offset[(i * 8 + j) * 3 + 1] = v.v[2]; - offset[(i * 8 + j) * 3 + 2] = v.v[1]; - } - } - - gDeferredSunProgram.uniform3fv(sOffset, slice, offset); - gDeferredSunProgram.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, - deferred_light_target->getWidth(), - deferred_light_target->getHeight()); - - { - LLGLDisable blend(GL_BLEND); - LLGLDepthTest depth(GL_TRUE, GL_FALSE, GL_ALWAYS); - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - } - - unbindDeferredShader(gDeferredSunProgram); - } - deferred_light_target->flush(); - } - - if (RenderDeferredSSAO) - { - // soften direct lighting lightmap - LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("renderDeferredLighting - soften shadow"); - LL_PROFILE_GPU_ZONE("soften shadow"); - // blur lightmap - screen_target->bindTarget(); - glClearColor(1, 1, 1, 1); - screen_target->clear(GL_COLOR_BUFFER_BIT); - glClearColor(0, 0, 0, 0); - - bindDeferredShader(gDeferredBlurLightProgram); - - LLVector3 go = RenderShadowGaussian; - const U32 kern_length = 4; - F32 blur_size = RenderShadowBlurSize; - F32 dist_factor = RenderShadowBlurDistFactor; - - // sample symmetrically with the middle sample falling exactly on 0.0 - F32 x = 0.f; - - LLVector3 gauss[32]; // xweight, yweight, offset - - for (U32 i = 0; i < kern_length; i++) - { - gauss[i].mV[0] = llgaussian(x, go.mV[0]); - gauss[i].mV[1] = llgaussian(x, go.mV[1]); - gauss[i].mV[2] = x; - x += 1.f; - } - - gDeferredBlurLightProgram.uniform2f(sDelta, 1.f, 0.f); - gDeferredBlurLightProgram.uniform1f(sDistFactor, dist_factor); - gDeferredBlurLightProgram.uniform3fv(sKern, kern_length, gauss[0].mV); - gDeferredBlurLightProgram.uniform1f(sKernScale, blur_size * (kern_length / 2.f - 0.5f)); - - { - LLGLDisable blend(GL_BLEND); - LLGLDepthTest depth(GL_TRUE, GL_FALSE, GL_ALWAYS); - mScreenTriangleVB->setBuffer(); - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - } - - screen_target->flush(); - unbindDeferredShader(gDeferredBlurLightProgram); - - bindDeferredShader(gDeferredBlurLightProgram, screen_target); - - deferred_light_target->bindTarget(); - - gDeferredBlurLightProgram.uniform2f(sDelta, 0.f, 1.f); - - { - LLGLDisable blend(GL_BLEND); - LLGLDepthTest depth(GL_TRUE, GL_FALSE, GL_ALWAYS); - mScreenTriangleVB->setBuffer(); - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - } - deferred_light_target->flush(); - unbindDeferredShader(gDeferredBlurLightProgram); - } - - screen_target->bindTarget(); - // clear color buffer here - zeroing alpha (glow) is important or it will accumulate against sky - glClearColor(0, 0, 0, 0); - screen_target->clear(GL_COLOR_BUFFER_BIT); - - if (RenderDeferredAtmospheric) - { // apply sunlight contribution - LLGLSLShader &soften_shader = gDeferredSoftenProgram; - - LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("renderDeferredLighting - atmospherics"); - LL_PROFILE_GPU_ZONE("atmospherics"); - bindDeferredShader(soften_shader); - - static LLCachedControl ssao_scale(gSavedSettings, "RenderSSAOIrradianceScale", 0.5f); - static LLCachedControl ssao_max(gSavedSettings, "RenderSSAOIrradianceMax", 0.25f); - static LLStaticHashedString ssao_scale_str("ssao_irradiance_scale"); - static LLStaticHashedString ssao_max_str("ssao_irradiance_max"); - - soften_shader.uniform1f(ssao_scale_str, ssao_scale); - soften_shader.uniform1f(ssao_max_str, ssao_max); - - LLEnvironment &environment = LLEnvironment::instance(); - soften_shader.uniform1i(LLShaderMgr::SUN_UP_FACTOR, environment.getIsSunUp() ? 1 : 0); - soften_shader.uniform3fv(LLShaderMgr::LIGHTNORM, 1, environment.getClampedLightNorm().mV); - - soften_shader.uniform4fv(LLShaderMgr::WATER_WATERPLANE, 1, LLDrawPoolAlpha::sWaterPlane.mV); - - { - LLGLDepthTest depth(GL_FALSE); - LLGLDisable blend(GL_BLEND); - - // full screen blit - mScreenTriangleVB->setBuffer(); - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - } - - unbindDeferredShader(gDeferredSoftenProgram); - } - - static LLCachedControl local_light_count(gSavedSettings, "RenderLocalLightCount", 256); - - if (local_light_count > 0) - { - gGL.setSceneBlendType(LLRender::BT_ADD); - std::list fullscreen_lights; - LLDrawable::drawable_list_t spot_lights; - LLDrawable::drawable_list_t fullscreen_spot_lights; - - if (!gCubeSnapshot) - { - for (U32 i = 0; i < 2; i++) - { - mTargetShadowSpotLight[i] = NULL; - } - } - - std::list light_colors; - - LLVertexBuffer::unbind(); - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("renderDeferredLighting - local lights"); - LL_PROFILE_GPU_ZONE("local lights"); - bindDeferredShader(gDeferredLightProgram); - - if (mCubeVB.isNull()) - { - mCubeVB = ll_create_cube_vb(LLVertexBuffer::MAP_VERTEX); - } - - mCubeVB->setBuffer(); - - LLGLDepthTest depth(GL_TRUE, GL_FALSE); - // mNearbyLights already includes distance calculation and excludes muted avatars. - // It is calculated from mLights - // mNearbyLights also provides fade value to gracefully fade-out out of range lights - S32 count = 0; - for (light_set_t::iterator iter = mNearbyLights.begin(); iter != mNearbyLights.end(); ++iter) - { - count++; - if (count > local_light_count) - { //stop collecting lights once we hit the limit - break; - } - - LLDrawable * drawablep = iter->drawable; - LLVOVolume * volume = drawablep->getVOVolume(); - if (!volume) - { - continue; - } - - if (volume->isAttachment()) - { - if (!sRenderAttachedLights) - { - continue; - } - } - - LLVector4a center; - center.load3(drawablep->getPositionAgent().mV); - const F32 *c = center.getF32ptr(); - F32 s = volume->getLightRadius() * 1.5f; - - // send light color to shader in linear space - LLColor3 col = volume->getLightLinearColor() * light_scale; - - if (col.magVecSquared() < 0.001f) - { - continue; - } - - if (s <= 0.001f) - { - continue; - } - - LLVector4a sa; - sa.splat(s); - if (camera->AABBInFrustumNoFarClip(center, sa) == 0) - { - continue; - } - - sVisibleLightCount++; - - if (camera->getOrigin().mV[0] > c[0] + s + 0.2f || camera->getOrigin().mV[0] < c[0] - s - 0.2f || - camera->getOrigin().mV[1] > c[1] + s + 0.2f || camera->getOrigin().mV[1] < c[1] - s - 0.2f || - camera->getOrigin().mV[2] > c[2] + s + 0.2f || camera->getOrigin().mV[2] < c[2] - s - 0.2f) - { // draw box if camera is outside box - if (volume->isLightSpotlight()) - { - drawablep->getVOVolume()->updateSpotLightPriority(); - spot_lights.push_back(drawablep); - continue; - } - - gDeferredLightProgram.uniform3fv(LLShaderMgr::LIGHT_CENTER, 1, c); - gDeferredLightProgram.uniform1f(LLShaderMgr::LIGHT_SIZE, s); - gDeferredLightProgram.uniform3fv(LLShaderMgr::DIFFUSE_COLOR, 1, col.mV); - gDeferredLightProgram.uniform1f(LLShaderMgr::LIGHT_FALLOFF, volume->getLightFalloff(DEFERRED_LIGHT_FALLOFF)); - gGL.syncMatrices(); - - mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, get_box_fan_indices(camera, center)); - } - else - { - if (volume->isLightSpotlight()) - { - drawablep->getVOVolume()->updateSpotLightPriority(); - fullscreen_spot_lights.push_back(drawablep); - continue; - } - - glh::vec3f tc(c); - mat.mult_matrix_vec(tc); - - fullscreen_lights.push_back(LLVector4(tc.v[0], tc.v[1], tc.v[2], s)); - light_colors.push_back(LLVector4(col.mV[0], col.mV[1], col.mV[2], volume->getLightFalloff(DEFERRED_LIGHT_FALLOFF))); - } - } - - // Bookmark comment to allow searching for mSpecialRenderMode == 3 (avatar edit mode), - // prev site of appended deferred character light, removed by SL-13522 09/20 - - unbindDeferredShader(gDeferredLightProgram); - } - - if (!spot_lights.empty()) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("renderDeferredLighting - projectors"); - LL_PROFILE_GPU_ZONE("projectors"); - LLGLDepthTest depth(GL_TRUE, GL_FALSE); - bindDeferredShader(gDeferredSpotLightProgram); - - mCubeVB->setBuffer(); - - gDeferredSpotLightProgram.enableTexture(LLShaderMgr::DEFERRED_PROJECTION); - - for (LLDrawable::drawable_list_t::iterator iter = spot_lights.begin(); iter != spot_lights.end(); ++iter) - { - LLDrawable *drawablep = *iter; - - LLVOVolume *volume = drawablep->getVOVolume(); - - LLVector4a center; - center.load3(drawablep->getPositionAgent().mV); - const F32* c = center.getF32ptr(); - F32 s = volume->getLightRadius() * 1.5f; - - sVisibleLightCount++; - - setupSpotLight(gDeferredSpotLightProgram, drawablep); - - // send light color to shader in linear space - LLColor3 col = volume->getLightLinearColor() * light_scale; - - gDeferredSpotLightProgram.uniform3fv(LLShaderMgr::LIGHT_CENTER, 1, c); - gDeferredSpotLightProgram.uniform1f(LLShaderMgr::LIGHT_SIZE, s); - gDeferredSpotLightProgram.uniform3fv(LLShaderMgr::DIFFUSE_COLOR, 1, col.mV); - gDeferredSpotLightProgram.uniform1f(LLShaderMgr::LIGHT_FALLOFF, volume->getLightFalloff(DEFERRED_LIGHT_FALLOFF)); - gGL.syncMatrices(); - - mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, get_box_fan_indices(camera, center)); - } - gDeferredSpotLightProgram.disableTexture(LLShaderMgr::DEFERRED_PROJECTION); - unbindDeferredShader(gDeferredSpotLightProgram); - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("renderDeferredLighting - fullscreen lights"); - LLGLDepthTest depth(GL_FALSE); - LL_PROFILE_GPU_ZONE("fullscreen lights"); - - U32 count = 0; - - const U32 max_count = LL_DEFERRED_MULTI_LIGHT_COUNT; - LLVector4 light[max_count]; - LLVector4 col[max_count]; - - F32 far_z = 0.f; - - while (!fullscreen_lights.empty()) - { - light[count] = fullscreen_lights.front(); - fullscreen_lights.pop_front(); - col[count] = light_colors.front(); - light_colors.pop_front(); - - far_z = llmin(light[count].mV[2] - light[count].mV[3], far_z); - count++; - if (count == max_count || fullscreen_lights.empty()) - { - U32 idx = count - 1; - bindDeferredShader(gDeferredMultiLightProgram[idx]); - gDeferredMultiLightProgram[idx].uniform1i(LLShaderMgr::MULTI_LIGHT_COUNT, count); - gDeferredMultiLightProgram[idx].uniform4fv(LLShaderMgr::MULTI_LIGHT, count, (GLfloat*)light); - gDeferredMultiLightProgram[idx].uniform4fv(LLShaderMgr::MULTI_LIGHT_COL, count, (GLfloat*)col); - gDeferredMultiLightProgram[idx].uniform1f(LLShaderMgr::MULTI_LIGHT_FAR_Z, far_z); - far_z = 0.f; - count = 0; - mScreenTriangleVB->setBuffer(); - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - unbindDeferredShader(gDeferredMultiLightProgram[idx]); - } - } - - bindDeferredShader(gDeferredMultiSpotLightProgram); - - gDeferredMultiSpotLightProgram.enableTexture(LLShaderMgr::DEFERRED_PROJECTION); - - mScreenTriangleVB->setBuffer(); - - for (LLDrawable::drawable_list_t::iterator iter = fullscreen_spot_lights.begin(); iter != fullscreen_spot_lights.end(); ++iter) - { - LLDrawable* drawablep = *iter; - LLVOVolume* volume = drawablep->getVOVolume(); - LLVector3 center = drawablep->getPositionAgent(); - F32* c = center.mV; - F32 light_size_final = volume->getLightRadius() * 1.5f; - F32 light_falloff_final = volume->getLightFalloff(DEFERRED_LIGHT_FALLOFF); - - sVisibleLightCount++; - - glh::vec3f tc(c); - mat.mult_matrix_vec(tc); - - setupSpotLight(gDeferredMultiSpotLightProgram, drawablep); - - // send light color to shader in linear space - LLColor3 col = volume->getLightLinearColor() * light_scale; - - gDeferredMultiSpotLightProgram.uniform3fv(LLShaderMgr::LIGHT_CENTER, 1, tc.v); - gDeferredMultiSpotLightProgram.uniform1f(LLShaderMgr::LIGHT_SIZE, light_size_final); - gDeferredMultiSpotLightProgram.uniform3fv(LLShaderMgr::DIFFUSE_COLOR, 1, col.mV); - gDeferredMultiSpotLightProgram.uniform1f(LLShaderMgr::LIGHT_FALLOFF, light_falloff_final); - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - } - - gDeferredMultiSpotLightProgram.disableTexture(LLShaderMgr::DEFERRED_PROJECTION); - unbindDeferredShader(gDeferredMultiSpotLightProgram); - } - } - - gGL.setColorMask(true, true); - } - - { // render non-deferred geometry (alpha, fullbright, glow) - LLGLDisable blend(GL_BLEND); - - pushRenderTypeMask(); - andRenderTypeMask(LLPipeline::RENDER_TYPE_ALPHA, - LLPipeline::RENDER_TYPE_ALPHA_PRE_WATER, - LLPipeline::RENDER_TYPE_ALPHA_POST_WATER, - LLPipeline::RENDER_TYPE_FULLBRIGHT, - LLPipeline::RENDER_TYPE_VOLUME, - LLPipeline::RENDER_TYPE_GLOW, - LLPipeline::RENDER_TYPE_BUMP, - LLPipeline::RENDER_TYPE_GLTF_PBR, - LLPipeline::RENDER_TYPE_PASS_SIMPLE, - LLPipeline::RENDER_TYPE_PASS_ALPHA, - LLPipeline::RENDER_TYPE_PASS_ALPHA_MASK, - LLPipeline::RENDER_TYPE_PASS_BUMP, - LLPipeline::RENDER_TYPE_PASS_POST_BUMP, - LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT, - LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_ALPHA_MASK, - LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_SHINY, - LLPipeline::RENDER_TYPE_PASS_GLOW, - LLPipeline::RENDER_TYPE_PASS_GLTF_GLOW, - LLPipeline::RENDER_TYPE_PASS_GRASS, - LLPipeline::RENDER_TYPE_PASS_SHINY, - LLPipeline::RENDER_TYPE_PASS_INVISIBLE, - LLPipeline::RENDER_TYPE_PASS_INVISI_SHINY, - LLPipeline::RENDER_TYPE_AVATAR, - LLPipeline::RENDER_TYPE_CONTROL_AV, - LLPipeline::RENDER_TYPE_ALPHA_MASK, - LLPipeline::RENDER_TYPE_FULLBRIGHT_ALPHA_MASK, - LLPipeline::RENDER_TYPE_WATER, - END_RENDER_TYPES); - - renderGeomPostDeferred(*LLViewerCamera::getInstance()); - popRenderTypeMask(); - } - - screen_target->flush(); - - if (!gCubeSnapshot) - { - // this is the end of the 3D scene render, grab a copy of the modelview and projection - // matrix for use in off-by-one-frame effects in the next frame - for (U32 i = 0; i < 16; i++) - { - gGLLastModelView[i] = gGLModelView[i]; - gGLLastProjection[i] = gGLProjection[i]; - } - } - gGL.setColorMask(true, true); -} - -void LLPipeline::doAtmospherics() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - - if (sImpostorRender) - { // do not attempt atmospherics on impostors - return; - } - - if (RenderDeferredAtmospheric) - { - { - // copy depth buffer for use in haze shader (use water displacement map as temp storage) - LLGLDepthTest depth(GL_TRUE, GL_TRUE, GL_ALWAYS); - - LLRenderTarget& src = gPipeline.mRT->screen; - LLRenderTarget& depth_src = gPipeline.mRT->deferredScreen; - LLRenderTarget& dst = gPipeline.mWaterDis; - - mRT->screen.flush(); - dst.bindTarget(); - gCopyDepthProgram.bind(); - - S32 diff_map = gCopyDepthProgram.getTextureChannel(LLShaderMgr::DIFFUSE_MAP); - S32 depth_map = gCopyDepthProgram.getTextureChannel(LLShaderMgr::DEFERRED_DEPTH); - - gGL.getTexUnit(diff_map)->bind(&src); - gGL.getTexUnit(depth_map)->bind(&depth_src, true); - - gGL.setColorMask(false, false); - gPipeline.mScreenTriangleVB->setBuffer(); - gPipeline.mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - - dst.flush(); - mRT->screen.bindTarget(); - } - - LLGLEnable blend(GL_BLEND); - gGL.blendFunc(LLRender::BF_ONE, LLRender::BF_SOURCE_ALPHA, LLRender::BF_ZERO, LLRender::BF_SOURCE_ALPHA); - gGL.setColorMask(true, true); - - // apply haze - LLGLSLShader& haze_shader = gHazeProgram; - - LL_PROFILE_GPU_ZONE("haze"); - bindDeferredShader(haze_shader, nullptr, &mWaterDis); - - LLEnvironment& environment = LLEnvironment::instance(); - haze_shader.uniform1i(LLShaderMgr::SUN_UP_FACTOR, environment.getIsSunUp() ? 1 : 0); - haze_shader.uniform3fv(LLShaderMgr::LIGHTNORM, 1, environment.getClampedLightNorm().mV); - - haze_shader.uniform4fv(LLShaderMgr::WATER_WATERPLANE, 1, LLDrawPoolAlpha::sWaterPlane.mV); - - LLGLDepthTest depth(GL_FALSE); - - // full screen blit - mScreenTriangleVB->setBuffer(); - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - - unbindDeferredShader(haze_shader); - - gGL.setSceneBlendType(LLRender::BT_ALPHA); - } -} - -void LLPipeline::doWaterHaze() -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - if (sImpostorRender) - { // do not attempt water haze on impostors - return; - } - - if (RenderDeferredAtmospheric) - { - // copy depth buffer for use in haze shader (use water displacement map as temp storage) - { - LLGLDepthTest depth(GL_TRUE, GL_TRUE, GL_ALWAYS); - - LLRenderTarget& src = gPipeline.mRT->screen; - LLRenderTarget& depth_src = gPipeline.mRT->deferredScreen; - LLRenderTarget& dst = gPipeline.mWaterDis; - - mRT->screen.flush(); - dst.bindTarget(); - gCopyDepthProgram.bind(); - - S32 diff_map = gCopyDepthProgram.getTextureChannel(LLShaderMgr::DIFFUSE_MAP); - S32 depth_map = gCopyDepthProgram.getTextureChannel(LLShaderMgr::DEFERRED_DEPTH); - - gGL.getTexUnit(diff_map)->bind(&src); - gGL.getTexUnit(depth_map)->bind(&depth_src, true); - - gGL.setColorMask(false, false); - gPipeline.mScreenTriangleVB->setBuffer(); - gPipeline.mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - - dst.flush(); - mRT->screen.bindTarget(); - } - - LLGLEnable blend(GL_BLEND); - gGL.blendFunc(LLRender::BF_ONE, LLRender::BF_SOURCE_ALPHA, LLRender::BF_ZERO, LLRender::BF_SOURCE_ALPHA); - - gGL.setColorMask(true, true); - - // apply haze - LLGLSLShader& haze_shader = gHazeWaterProgram; - - LL_PROFILE_GPU_ZONE("haze"); - bindDeferredShader(haze_shader, nullptr, &mWaterDis); - - haze_shader.uniform4fv(LLShaderMgr::WATER_WATERPLANE, 1, LLDrawPoolAlpha::sWaterPlane.mV); - - static LLStaticHashedString above_water_str("above_water"); - haze_shader.uniform1i(above_water_str, sUnderWaterRender ? -1 : 1); - - if (LLPipeline::sUnderWaterRender) - { - LLGLDepthTest depth(GL_FALSE); - - // full screen blit - mScreenTriangleVB->setBuffer(); - mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); - } - else - { - //render water patches like LLDrawPoolWater does - LLGLDepthTest depth(GL_TRUE, GL_FALSE); - LLGLDisable cull(GL_CULL_FACE); - - gGLLastMatrix = NULL; - gGL.loadMatrix(gGLModelView); - - if (mWaterPool) - { - mWaterPool->pushFaceGeometry(); - } - } - - unbindDeferredShader(haze_shader); - - - gGL.setSceneBlendType(LLRender::BT_ALPHA); - } -} - -void LLPipeline::setupSpotLight(LLGLSLShader& shader, LLDrawable* drawablep) -{ - //construct frustum - LLVOVolume* volume = drawablep->getVOVolume(); - LLVector3 params = volume->getSpotLightParams(); - - F32 fov = params.mV[0]; - F32 focus = params.mV[1]; - - LLVector3 pos = drawablep->getPositionAgent(); - LLQuaternion quat = volume->getRenderRotation(); - LLVector3 scale = volume->getScale(); - - //get near clip plane - LLVector3 at_axis(0,0,-scale.mV[2]*0.5f); - at_axis *= quat; - - LLVector3 np = pos+at_axis; - at_axis.normVec(); - - //get origin that has given fov for plane np, at_axis, and given scale - F32 dist = (scale.mV[1]*0.5f)/tanf(fov*0.5f); - - LLVector3 origin = np - at_axis*dist; - - //matrix from volume space to agent space - LLMatrix4 light_mat(quat, LLVector4(origin,1.f)); - - glh::matrix4f light_to_agent((F32*) light_mat.mMatrix); - glh::matrix4f light_to_screen = get_current_modelview() * light_to_agent; - - glh::matrix4f screen_to_light = light_to_screen.inverse(); - - F32 s = volume->getLightRadius()*1.5f; - F32 near_clip = dist; - F32 width = scale.mV[VX]; - F32 height = scale.mV[VY]; - F32 far_clip = s+dist-scale.mV[VZ]; - - F32 fovy = fov * RAD_TO_DEG; - F32 aspect = width/height; - - glh::matrix4f trans(0.5f, 0.f, 0.f, 0.5f, - 0.f, 0.5f, 0.f, 0.5f, - 0.f, 0.f, 0.5f, 0.5f, - 0.f, 0.f, 0.f, 1.f); - - glh::vec3f p1(0, 0, -(near_clip+0.01f)); - glh::vec3f p2(0, 0, -(near_clip+1.f)); - - glh::vec3f screen_origin(0, 0, 0); - - light_to_screen.mult_matrix_vec(p1); - light_to_screen.mult_matrix_vec(p2); - light_to_screen.mult_matrix_vec(screen_origin); - - glh::vec3f n = p2-p1; - n.normalize(); - - F32 proj_range = far_clip - near_clip; - glh::matrix4f light_proj = gl_perspective(fovy, aspect, near_clip, far_clip); - screen_to_light = trans * light_proj * screen_to_light; - shader.uniformMatrix4fv(LLShaderMgr::PROJECTOR_MATRIX, 1, false, screen_to_light.m); - shader.uniform1f(LLShaderMgr::PROJECTOR_NEAR, near_clip); - shader.uniform3fv(LLShaderMgr::PROJECTOR_P, 1, p1.v); - shader.uniform3fv(LLShaderMgr::PROJECTOR_N, 1, n.v); - shader.uniform3fv(LLShaderMgr::PROJECTOR_ORIGIN, 1, screen_origin.v); - shader.uniform1f(LLShaderMgr::PROJECTOR_RANGE, proj_range); - shader.uniform1f(LLShaderMgr::PROJECTOR_AMBIANCE, params.mV[2]); - S32 s_idx = -1; - - for (U32 i = 0; i < 2; i++) - { - if (mShadowSpotLight[i] == drawablep) - { - s_idx = i; - } - } - - shader.uniform1i(LLShaderMgr::PROJECTOR_SHADOW_INDEX, s_idx); - - if (s_idx >= 0) - { - shader.uniform1f(LLShaderMgr::PROJECTOR_SHADOW_FADE, 1.f-mSpotLightFade[s_idx]); - } - else - { - shader.uniform1f(LLShaderMgr::PROJECTOR_SHADOW_FADE, 1.f); - } - - // make sure we're not already targeting the same spot light with both shadow maps - llassert(mTargetShadowSpotLight[0] != mTargetShadowSpotLight[1] || mTargetShadowSpotLight[0].isNull()); - - if (!gCubeSnapshot) - { - LLDrawable* potential = drawablep; - //determine if this light is higher priority than one of the existing spot shadows - F32 m_pri = volume->getSpotLightPriority(); - - for (U32 i = 0; i < 2; i++) - { - F32 pri = 0.f; - - if (mTargetShadowSpotLight[i].notNull()) - { - pri = mTargetShadowSpotLight[i]->getVOVolume()->getSpotLightPriority(); - } - - if (m_pri > pri) - { - LLDrawable* temp = mTargetShadowSpotLight[i]; - mTargetShadowSpotLight[i] = potential; - potential = temp; - m_pri = pri; - } - } - } - - // make sure we didn't end up targeting the same spot light with both shadow maps - llassert(mTargetShadowSpotLight[0] != mTargetShadowSpotLight[1] || mTargetShadowSpotLight[0].isNull()); - - LLViewerTexture* img = volume->getLightTexture(); - - if (img == NULL) - { - img = LLViewerFetchedTexture::sWhiteImagep; - } - - S32 channel = shader.enableTexture(LLShaderMgr::DEFERRED_PROJECTION); - - if (channel > -1) - { - if (img) - { - gGL.getTexUnit(channel)->bind(img); - - F32 lod_range = logf(img->getWidth())/logf(2.f); - - shader.uniform1f(LLShaderMgr::PROJECTOR_FOCUS, focus); - shader.uniform1f(LLShaderMgr::PROJECTOR_LOD, lod_range); - shader.uniform1f(LLShaderMgr::PROJECTOR_AMBIENT_LOD, llclamp((proj_range-focus)/proj_range*lod_range, 0.f, 1.f)); - } - } - -} - -void LLPipeline::unbindDeferredShader(LLGLSLShader &shader) -{ - LLRenderTarget* deferred_target = &mRT->deferredScreen; - LLRenderTarget* deferred_light_target = &mRT->deferredLight; - - stop_glerror(); - shader.disableTexture(LLShaderMgr::DEFERRED_NORMAL, deferred_target->getUsage()); - shader.disableTexture(LLShaderMgr::DEFERRED_DIFFUSE, deferred_target->getUsage()); - shader.disableTexture(LLShaderMgr::DEFERRED_SPECULAR, deferred_target->getUsage()); - shader.disableTexture(LLShaderMgr::DEFERRED_EMISSIVE, deferred_target->getUsage()); - shader.disableTexture(LLShaderMgr::DEFERRED_BRDF_LUT); - //shader.disableTexture(LLShaderMgr::DEFERRED_DEPTH, deferred_depth_target->getUsage()); - shader.disableTexture(LLShaderMgr::DEFERRED_DEPTH, deferred_target->getUsage()); - shader.disableTexture(LLShaderMgr::DEFERRED_LIGHT, deferred_light_target->getUsage()); - shader.disableTexture(LLShaderMgr::DIFFUSE_MAP); - shader.disableTexture(LLShaderMgr::DEFERRED_BLOOM); - - for (U32 i = 0; i < 4; i++) - { - if (shader.disableTexture(LLShaderMgr::DEFERRED_SHADOW0+i) > -1) - { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); - } - } - - for (U32 i = 4; i < 6; i++) - { - if (shader.disableTexture(LLShaderMgr::DEFERRED_SHADOW0+i) > -1) - { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); - } - } - - shader.disableTexture(LLShaderMgr::DEFERRED_NOISE); - shader.disableTexture(LLShaderMgr::DEFERRED_LIGHTFUNC); - - if (!LLPipeline::sReflectionProbesEnabled) - { - S32 channel = shader.disableTexture(LLShaderMgr::ENVIRONMENT_MAP, LLTexUnit::TT_CUBE_MAP); - if (channel > -1) - { - LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL; - if (cube_map) - { - cube_map->disable(); - } - } - } - - unbindReflectionProbes(shader); - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.getTexUnit(0)->activate(); - shader.unbind(); -} - -void LLPipeline::setEnvMat(LLGLSLShader& shader) -{ - F32* m = gGLModelView; - - F32 mat[] = { m[0], m[1], m[2], - m[4], m[5], m[6], - m[8], m[9], m[10] }; - - shader.uniformMatrix3fv(LLShaderMgr::DEFERRED_ENV_MAT, 1, true, mat); -} - -void LLPipeline::bindReflectionProbes(LLGLSLShader& shader) -{ - if (!sReflectionProbesEnabled) - { - return; - } - - S32 channel = shader.enableTexture(LLShaderMgr::REFLECTION_PROBES, LLTexUnit::TT_CUBE_MAP_ARRAY); - bool bound = false; - if (channel > -1 && mReflectionMapManager.mTexture.notNull()) - { - mReflectionMapManager.mTexture->bind(channel); - bound = true; - } - - channel = shader.enableTexture(LLShaderMgr::IRRADIANCE_PROBES, LLTexUnit::TT_CUBE_MAP_ARRAY); - if (channel > -1 && mReflectionMapManager.mIrradianceMaps.notNull()) - { - mReflectionMapManager.mIrradianceMaps->bind(channel); - bound = true; - } - - if (bound) - { - mReflectionMapManager.setUniforms(); - - setEnvMat(shader); - } - - // reflection probe shaders generally sample the scene map as well for SSR - channel = shader.enableTexture(LLShaderMgr::SCENE_MAP); - if (channel > -1) - { - gGL.getTexUnit(channel)->bind(&mSceneMap); - } - - - shader.uniform1f(LLShaderMgr::DEFERRED_SSR_ITR_COUNT, RenderScreenSpaceReflectionIterations); - shader.uniform1f(LLShaderMgr::DEFERRED_SSR_DIST_BIAS, RenderScreenSpaceReflectionDistanceBias); - shader.uniform1f(LLShaderMgr::DEFERRED_SSR_RAY_STEP, RenderScreenSpaceReflectionRayStep); - shader.uniform1f(LLShaderMgr::DEFERRED_SSR_GLOSSY_SAMPLES, RenderScreenSpaceReflectionGlossySamples); - shader.uniform1f(LLShaderMgr::DEFERRED_SSR_REJECT_BIAS, RenderScreenSpaceReflectionDepthRejectBias); - mPoissonOffset++; - - if (mPoissonOffset > 128 - RenderScreenSpaceReflectionGlossySamples) - mPoissonOffset = 0; - - shader.uniform1f(LLShaderMgr::DEFERRED_SSR_NOISE_SINE, mPoissonOffset); - shader.uniform1f(LLShaderMgr::DEFERRED_SSR_ADAPTIVE_STEP_MULT, RenderScreenSpaceReflectionAdaptiveStepMultiplier); - - channel = shader.enableTexture(LLShaderMgr::SCENE_DEPTH); - if (channel > -1) - { - gGL.getTexUnit(channel)->bind(&mSceneMap, true); - } - - -} - -void LLPipeline::unbindReflectionProbes(LLGLSLShader& shader) -{ - S32 channel = shader.disableTexture(LLShaderMgr::REFLECTION_PROBES, LLTexUnit::TT_CUBE_MAP); - if (channel > -1 && mReflectionMapManager.mTexture.notNull()) - { - mReflectionMapManager.mTexture->unbind(); - if (channel == 0) - { - gGL.getTexUnit(channel)->enable(LLTexUnit::TT_TEXTURE); - } - } -} - - -inline float sgn(float a) -{ - if (a > 0.0F) return (1.0F); - if (a < 0.0F) return (-1.0F); - return (0.0F); -} - -glh::matrix4f look(const LLVector3 pos, const LLVector3 dir, const LLVector3 up) -{ - glh::matrix4f ret; - - LLVector3 dirN; - LLVector3 upN; - LLVector3 lftN; - - lftN = dir % up; - lftN.normVec(); - - upN = lftN % dir; - upN.normVec(); - - dirN = dir; - dirN.normVec(); - - ret.m[ 0] = lftN[0]; - ret.m[ 1] = upN[0]; - ret.m[ 2] = -dirN[0]; - ret.m[ 3] = 0.f; - - ret.m[ 4] = lftN[1]; - ret.m[ 5] = upN[1]; - ret.m[ 6] = -dirN[1]; - ret.m[ 7] = 0.f; - - ret.m[ 8] = lftN[2]; - ret.m[ 9] = upN[2]; - ret.m[10] = -dirN[2]; - ret.m[11] = 0.f; - - ret.m[12] = -(lftN*pos); - ret.m[13] = -(upN*pos); - ret.m[14] = dirN*pos; - ret.m[15] = 1.f; - - return ret; -} - -glh::matrix4f scale_translate_to_fit(const LLVector3 min, const LLVector3 max) -{ - glh::matrix4f ret; - ret.m[ 0] = 2/(max[0]-min[0]); - ret.m[ 4] = 0; - ret.m[ 8] = 0; - ret.m[12] = -(max[0]+min[0])/(max[0]-min[0]); - - ret.m[ 1] = 0; - ret.m[ 5] = 2/(max[1]-min[1]); - ret.m[ 9] = 0; - ret.m[13] = -(max[1]+min[1])/(max[1]-min[1]); - - ret.m[ 2] = 0; - ret.m[ 6] = 0; - ret.m[10] = 2/(max[2]-min[2]); - ret.m[14] = -(max[2]+min[2])/(max[2]-min[2]); - - ret.m[ 3] = 0; - ret.m[ 7] = 0; - ret.m[11] = 0; - ret.m[15] = 1; - - return ret; -} - -static LLTrace::BlockTimerStatHandle FTM_SHADOW_RENDER("Render Shadows"); -static LLTrace::BlockTimerStatHandle FTM_SHADOW_ALPHA("Alpha Shadow"); -static LLTrace::BlockTimerStatHandle FTM_SHADOW_SIMPLE("Simple Shadow"); -static LLTrace::BlockTimerStatHandle FTM_SHADOW_GEOM("Shadow Geom"); - -static LLTrace::BlockTimerStatHandle FTM_SHADOW_ALPHA_MASKED("Alpha Masked"); -static LLTrace::BlockTimerStatHandle FTM_SHADOW_ALPHA_BLEND("Alpha Blend"); -static LLTrace::BlockTimerStatHandle FTM_SHADOW_ALPHA_TREE("Alpha Tree"); -static LLTrace::BlockTimerStatHandle FTM_SHADOW_ALPHA_GRASS("Alpha Grass"); -static LLTrace::BlockTimerStatHandle FTM_SHADOW_FULLBRIGHT_ALPHA_MASKED("Fullbright Alpha Masked"); - -void LLPipeline::renderShadow(glh::matrix4f& view, glh::matrix4f& proj, LLCamera& shadow_cam, LLCullResult& result, bool depth_clamp) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; //LL_RECORD_BLOCK_TIME(FTM_SHADOW_RENDER); - LL_PROFILE_GPU_ZONE("renderShadow"); - - LLPipeline::sShadowRender = true; - - // disable occlusion culling during shadow render - U32 saved_occlusion = sUseOcclusion; - sUseOcclusion = 0; - - // List of render pass types that use the prim volume as the shadow, - // ignoring textures. - static const U32 types[] = { - LLRenderPass::PASS_SIMPLE, - LLRenderPass::PASS_FULLBRIGHT, - LLRenderPass::PASS_SHINY, - LLRenderPass::PASS_BUMP, - LLRenderPass::PASS_FULLBRIGHT_SHINY, - LLRenderPass::PASS_MATERIAL, - LLRenderPass::PASS_MATERIAL_ALPHA_EMISSIVE, - LLRenderPass::PASS_SPECMAP, - LLRenderPass::PASS_SPECMAP_EMISSIVE, - LLRenderPass::PASS_NORMMAP, - LLRenderPass::PASS_NORMMAP_EMISSIVE, - LLRenderPass::PASS_NORMSPEC, - LLRenderPass::PASS_NORMSPEC_EMISSIVE - }; - - LLGLEnable cull(GL_CULL_FACE); - - //enable depth clamping if available - LLGLEnable clamp_depth(depth_clamp ? GL_DEPTH_CLAMP : 0); - - LLGLDepthTest depth_test(GL_TRUE, GL_TRUE, GL_LESS); - - updateCull(shadow_cam, result); - - stateSort(shadow_cam, result); - - //generate shadow map - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.pushMatrix(); - gGL.loadMatrix(proj.m); - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - gGL.loadMatrix(view.m); - - stop_glerror(); - gGLLastMatrix = NULL; - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - stop_glerror(); - - struct CompareVertexBuffer - { - bool operator()(const LLDrawInfo* const& lhs, const LLDrawInfo* const& rhs) - { - return lhs->mVertexBuffer > rhs->mVertexBuffer; - } - }; - - - LLVertexBuffer::unbind(); - for (int j = 0; j < 2; ++j) // 0 -- static, 1 -- rigged - { - bool rigged = j == 1; - gDeferredShadowProgram.bind(rigged); - - gGL.diffuseColor4f(1, 1, 1, 1); - - S32 shadow_detail = gSavedSettings.getS32("RenderShadowDetail"); - - // if not using VSM, disable color writes - if (shadow_detail <= 2) - { - gGL.setColorMask(false, false); - } - - LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("shadow simple"); //LL_RECORD_BLOCK_TIME(FTM_SHADOW_SIMPLE); - LL_PROFILE_GPU_ZONE("shadow simple"); - gGL.getTexUnit(0)->disable(); - - for (U32 type : types) - { - renderObjects(type, false, false, rigged); - } - - renderGLTFObjects(LLRenderPass::PASS_GLTF_PBR, false, rigged); - - gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE); - } - - if (LLPipeline::sUseOcclusion > 1) - { // do occlusion culling against non-masked only to take advantage of hierarchical Z - doOcclusion(shadow_cam); - } - - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("shadow geom"); - renderGeomShadow(shadow_cam); - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("shadow alpha"); - LL_PROFILE_GPU_ZONE("shadow alpha"); - const S32 sun_up = LLEnvironment::instance().getIsSunUp() ? 1 : 0; - U32 target_width = LLRenderTarget::sCurResX; - - for (int i = 0; i < 2; ++i) - { - bool rigged = i == 1; - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("shadow alpha masked"); - LL_PROFILE_GPU_ZONE("shadow alpha masked"); - gDeferredShadowAlphaMaskProgram.bind(rigged); - LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up); - LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); - renderMaskedObjects(LLRenderPass::PASS_ALPHA_MASK, true, true, rigged); - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("shadow alpha blend"); - LL_PROFILE_GPU_ZONE("shadow alpha blend"); - renderAlphaObjects(rigged); - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("shadow fullbright alpha masked"); - LL_PROFILE_GPU_ZONE("shadow alpha masked"); - gDeferredShadowFullbrightAlphaMaskProgram.bind(rigged); - LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up); - LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); - renderFullbrightMaskedObjects(LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK, true, true, rigged); - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("shadow alpha grass"); - LL_PROFILE_GPU_ZONE("shadow alpha grass"); - gDeferredTreeShadowProgram.bind(rigged); - LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(ALPHA_BLEND_CUTOFF); - - if (i == 0) - { - renderObjects(LLRenderPass::PASS_GRASS, true); - } - - { - LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("shadow alpha material"); - LL_PROFILE_GPU_ZONE("shadow alpha material"); - renderMaskedObjects(LLRenderPass::PASS_NORMSPEC_MASK, true, false, rigged); - renderMaskedObjects(LLRenderPass::PASS_MATERIAL_ALPHA_MASK, true, false, rigged); - renderMaskedObjects(LLRenderPass::PASS_SPECMAP_MASK, true, false, rigged); - renderMaskedObjects(LLRenderPass::PASS_NORMMAP_MASK, true, false, rigged); - } - } - } - - for (int i = 0; i < 2; ++i) - { - bool rigged = i == 1; - gDeferredShadowGLTFAlphaMaskProgram.bind(rigged); - LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up); - LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); - - gGL.loadMatrix(gGLModelView); - gGLLastMatrix = NULL; - - U32 type = LLRenderPass::PASS_GLTF_PBR_ALPHA_MASK; - - if (rigged) - { - mAlphaMaskPool->pushRiggedGLTFBatches(type + 1); - } - else - { - mAlphaMaskPool->pushGLTFBatches(type); - } - - gGL.loadMatrix(gGLModelView); - gGLLastMatrix = NULL; - } - } - - gDeferredShadowCubeProgram.bind(); - gGLLastMatrix = NULL; - gGL.loadMatrix(gGLModelView); - - gGL.setColorMask(true, true); - - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.popMatrix(); - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.popMatrix(); - gGLLastMatrix = NULL; - - // reset occlusion culling flag - sUseOcclusion = saved_occlusion; - LLPipeline::sShadowRender = false; -} - -bool LLPipeline::getVisiblePointCloud(LLCamera& camera, LLVector3& min, LLVector3& max, std::vector& fp, LLVector3 light_dir) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - //get point cloud of intersection of frust and min, max - - if (getVisibleExtents(camera, min, max)) - { - return false; - } - - //get set of planes on bounding box - LLPlane bp[] = { - LLPlane(min, LLVector3(-1,0,0)), - LLPlane(min, LLVector3(0,-1,0)), - LLPlane(min, LLVector3(0,0,-1)), - LLPlane(max, LLVector3(1,0,0)), - LLPlane(max, LLVector3(0,1,0)), - LLPlane(max, LLVector3(0,0,1))}; - - //potential points - std::vector pp; - - //add corners of AABB - pp.push_back(LLVector3(min.mV[0], min.mV[1], min.mV[2])); - pp.push_back(LLVector3(max.mV[0], min.mV[1], min.mV[2])); - pp.push_back(LLVector3(min.mV[0], max.mV[1], min.mV[2])); - pp.push_back(LLVector3(max.mV[0], max.mV[1], min.mV[2])); - pp.push_back(LLVector3(min.mV[0], min.mV[1], max.mV[2])); - pp.push_back(LLVector3(max.mV[0], min.mV[1], max.mV[2])); - pp.push_back(LLVector3(min.mV[0], max.mV[1], max.mV[2])); - pp.push_back(LLVector3(max.mV[0], max.mV[1], max.mV[2])); - - //add corners of camera frustum - for (U32 i = 0; i < LLCamera::AGENT_FRUSTRUM_NUM; i++) - { - pp.push_back(camera.mAgentFrustum[i]); - } - - - //bounding box line segments - U32 bs[] = - { - 0,1, - 1,3, - 3,2, - 2,0, - - 4,5, - 5,7, - 7,6, - 6,4, - - 0,4, - 1,5, - 3,7, - 2,6 - }; - - for (U32 i = 0; i < 12; i++) - { //for each line segment in bounding box - for (U32 j = 0; j < LLCamera::AGENT_PLANE_NO_USER_CLIP_NUM; j++) - { //for each plane in camera frustum - const LLPlane& cp = camera.getAgentPlane(j); - const LLVector3& v1 = pp[bs[i*2+0]]; - const LLVector3& v2 = pp[bs[i*2+1]]; - LLVector3 n; - cp.getVector3(n); - - LLVector3 line = v1-v2; - - F32 d1 = line*n; - F32 d2 = -cp.dist(v2); - - F32 t = d2/d1; - - if (t > 0.f && t < 1.f) - { - LLVector3 intersect = v2+line*t; - pp.push_back(intersect); - } - } - } - - //camera frustum line segments - const U32 fs[] = - { - 0,1, - 1,2, - 2,3, - 3,0, - - 4,5, - 5,6, - 6,7, - 7,4, - - 0,4, - 1,5, - 2,6, - 3,7 - }; - - for (U32 i = 0; i < 12; i++) - { - for (U32 j = 0; j < 6; ++j) - { - const LLVector3& v1 = pp[fs[i*2+0]+8]; - const LLVector3& v2 = pp[fs[i*2+1]+8]; - const LLPlane& cp = bp[j]; - LLVector3 n; - cp.getVector3(n); - - LLVector3 line = v1-v2; - - F32 d1 = line*n; - F32 d2 = -cp.dist(v2); - - F32 t = d2/d1; - - if (t > 0.f && t < 1.f) - { - LLVector3 intersect = v2+line*t; - pp.push_back(intersect); - } - } - } - - LLVector3 ext[] = { min-LLVector3(0.05f,0.05f,0.05f), - max+LLVector3(0.05f,0.05f,0.05f) }; - - for (U32 i = 0; i < pp.size(); ++i) - { - bool found = true; - - const F32* p = pp[i].mV; - - for (U32 j = 0; j < 3; ++j) - { - if (p[j] < ext[0].mV[j] || - p[j] > ext[1].mV[j]) - { - found = false; - break; - } - } - - for (U32 j = 0; j < LLCamera::AGENT_PLANE_NO_USER_CLIP_NUM; ++j) - { - const LLPlane& cp = camera.getAgentPlane(j); - F32 dist = cp.dist(pp[i]); - if (dist > 0.05f) //point is above some plane, not contained - { - found = false; - break; - } - } - - if (found) - { - fp.push_back(pp[i]); - } - } - - if (fp.empty()) - { - return false; - } - - return true; -} - -void LLPipeline::renderHighlight(const LLViewerObject* obj, F32 fade) -{ - if (obj && obj->getVolume()) - { - for (LLViewerObject::child_list_t::const_iterator iter = obj->getChildren().begin(); iter != obj->getChildren().end(); ++iter) - { - renderHighlight(*iter, fade); - } - - LLDrawable* drawable = obj->mDrawable; - if (drawable) - { - for (S32 i = 0; i < drawable->getNumFaces(); ++i) - { - LLFace* face = drawable->getFace(i); - if (face) - { - face->renderSelected(LLViewerTexture::sNullImagep, LLColor4(1,1,1,fade)); - } - } - } - } -} - - -LLRenderTarget* LLPipeline::getSunShadowTarget(U32 i) -{ - llassert(i < 4); - return &mRT->shadow[i]; -} - -LLRenderTarget* LLPipeline::getSpotShadowTarget(U32 i) -{ - llassert(i < 2); - return &mSpotShadow[i]; -} - -static LLTrace::BlockTimerStatHandle FTM_GEN_SUN_SHADOW("Gen Sun Shadow"); -static LLTrace::BlockTimerStatHandle FTM_GEN_SUN_SHADOW_SPOT_RENDER("Spot Shadow Render"); - -// helper class for disabling occlusion culling for the current stack frame -class LLDisableOcclusionCulling -{ -public: - S32 mUseOcclusion; - - LLDisableOcclusionCulling() - { - mUseOcclusion = LLPipeline::sUseOcclusion; - LLPipeline::sUseOcclusion = 0; - } - - ~LLDisableOcclusionCulling() - { - LLPipeline::sUseOcclusion = mUseOcclusion; - } -}; - -void LLPipeline::generateSunShadow(LLCamera& camera) -{ - if (!sRenderDeferred || RenderShadowDetail <= 0) - { - return; - } - - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; //LL_RECORD_BLOCK_TIME(FTM_GEN_SUN_SHADOW); - LL_PROFILE_GPU_ZONE("generateSunShadow"); - - LLDisableOcclusionCulling no_occlusion; - - bool skip_avatar_update = false; - if (!isAgentAvatarValid() || gAgentCamera.getCameraAnimating() || gAgentCamera.getCameraMode() != CAMERA_MODE_MOUSELOOK || !LLVOAvatar::sVisibleInFirstPerson) - { - skip_avatar_update = true; - } - - if (!skip_avatar_update) - { - gAgentAvatarp->updateAttachmentVisibility(CAMERA_MODE_THIRD_PERSON); - } - - F64 last_modelview[16]; - F64 last_projection[16]; - for (U32 i = 0; i < 16; i++) - { //store last_modelview of world camera - last_modelview[i] = gGLLastModelView[i]; - last_projection[i] = gGLLastProjection[i]; - } - - pushRenderTypeMask(); - andRenderTypeMask(LLPipeline::RENDER_TYPE_SIMPLE, - LLPipeline::RENDER_TYPE_ALPHA, - LLPipeline::RENDER_TYPE_ALPHA_PRE_WATER, - LLPipeline::RENDER_TYPE_ALPHA_POST_WATER, - LLPipeline::RENDER_TYPE_GRASS, - LLPipeline::RENDER_TYPE_GLTF_PBR, - LLPipeline::RENDER_TYPE_FULLBRIGHT, - LLPipeline::RENDER_TYPE_BUMP, - LLPipeline::RENDER_TYPE_VOLUME, - LLPipeline::RENDER_TYPE_AVATAR, - LLPipeline::RENDER_TYPE_CONTROL_AV, - LLPipeline::RENDER_TYPE_TREE, - LLPipeline::RENDER_TYPE_TERRAIN, - LLPipeline::RENDER_TYPE_WATER, - LLPipeline::RENDER_TYPE_VOIDWATER, - LLPipeline::RENDER_TYPE_PASS_ALPHA, - LLPipeline::RENDER_TYPE_PASS_ALPHA_MASK, - LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_ALPHA_MASK, - LLPipeline::RENDER_TYPE_PASS_GRASS, - LLPipeline::RENDER_TYPE_PASS_SIMPLE, - LLPipeline::RENDER_TYPE_PASS_BUMP, - LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT, - LLPipeline::RENDER_TYPE_PASS_SHINY, - LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_SHINY, - LLPipeline::RENDER_TYPE_PASS_MATERIAL, - LLPipeline::RENDER_TYPE_PASS_MATERIAL_ALPHA, - LLPipeline::RENDER_TYPE_PASS_MATERIAL_ALPHA_MASK, - LLPipeline::RENDER_TYPE_PASS_MATERIAL_ALPHA_EMISSIVE, - LLPipeline::RENDER_TYPE_PASS_SPECMAP, - LLPipeline::RENDER_TYPE_PASS_SPECMAP_BLEND, - LLPipeline::RENDER_TYPE_PASS_SPECMAP_MASK, - LLPipeline::RENDER_TYPE_PASS_SPECMAP_EMISSIVE, - LLPipeline::RENDER_TYPE_PASS_NORMMAP, - LLPipeline::RENDER_TYPE_PASS_NORMMAP_BLEND, - LLPipeline::RENDER_TYPE_PASS_NORMMAP_MASK, - LLPipeline::RENDER_TYPE_PASS_NORMMAP_EMISSIVE, - LLPipeline::RENDER_TYPE_PASS_NORMSPEC, - LLPipeline::RENDER_TYPE_PASS_NORMSPEC_BLEND, - LLPipeline::RENDER_TYPE_PASS_NORMSPEC_MASK, - LLPipeline::RENDER_TYPE_PASS_NORMSPEC_EMISSIVE, - LLPipeline::RENDER_TYPE_PASS_ALPHA_MASK_RIGGED, - LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_ALPHA_MASK_RIGGED, - LLPipeline::RENDER_TYPE_PASS_SIMPLE_RIGGED, - LLPipeline::RENDER_TYPE_PASS_BUMP_RIGGED, - LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_RIGGED, - LLPipeline::RENDER_TYPE_PASS_SHINY_RIGGED, - LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_SHINY_RIGGED, - LLPipeline::RENDER_TYPE_PASS_MATERIAL_RIGGED, - LLPipeline::RENDER_TYPE_PASS_MATERIAL_ALPHA_RIGGED, - LLPipeline::RENDER_TYPE_PASS_MATERIAL_ALPHA_MASK_RIGGED, - LLPipeline::RENDER_TYPE_PASS_MATERIAL_ALPHA_EMISSIVE_RIGGED, - LLPipeline::RENDER_TYPE_PASS_SPECMAP_RIGGED, - LLPipeline::RENDER_TYPE_PASS_SPECMAP_BLEND_RIGGED, - LLPipeline::RENDER_TYPE_PASS_SPECMAP_MASK_RIGGED, - LLPipeline::RENDER_TYPE_PASS_SPECMAP_EMISSIVE_RIGGED, - LLPipeline::RENDER_TYPE_PASS_NORMMAP_RIGGED, - LLPipeline::RENDER_TYPE_PASS_NORMMAP_BLEND_RIGGED, - LLPipeline::RENDER_TYPE_PASS_NORMMAP_MASK_RIGGED, - LLPipeline::RENDER_TYPE_PASS_NORMMAP_EMISSIVE_RIGGED, - LLPipeline::RENDER_TYPE_PASS_NORMSPEC_RIGGED, - LLPipeline::RENDER_TYPE_PASS_NORMSPEC_BLEND_RIGGED, - LLPipeline::RENDER_TYPE_PASS_NORMSPEC_MASK_RIGGED, - LLPipeline::RENDER_TYPE_PASS_NORMSPEC_EMISSIVE_RIGGED, - LLPipeline::RENDER_TYPE_PASS_GLTF_PBR, - LLPipeline::RENDER_TYPE_PASS_GLTF_PBR_RIGGED, - LLPipeline::RENDER_TYPE_PASS_GLTF_PBR_ALPHA_MASK, - LLPipeline::RENDER_TYPE_PASS_GLTF_PBR_ALPHA_MASK_RIGGED, - END_RENDER_TYPES); - - gGL.setColorMask(false, false); - - LLEnvironment& environment = LLEnvironment::instance(); - - //get sun view matrix - - //store current projection/modelview matrix - glh::matrix4f saved_proj = get_current_projection(); - glh::matrix4f saved_view = get_current_modelview(); - glh::matrix4f inv_view = saved_view.inverse(); - - glh::matrix4f view[6]; - glh::matrix4f proj[6]; - - LLVector3 caster_dir(environment.getIsSunUp() ? mSunDir : mMoonDir); - - //put together a universal "near clip" plane for shadow frusta - LLPlane shadow_near_clip; - { - LLVector3 p = camera.getOrigin(); // gAgent.getPositionAgent(); - p += caster_dir * RenderFarClip*2.f; - shadow_near_clip.setVec(p, caster_dir); - } - - LLVector3 lightDir = -caster_dir; - lightDir.normVec(); - - glh::vec3f light_dir(lightDir.mV); - - //create light space camera matrix - - LLVector3 at = lightDir; - - LLVector3 up = camera.getAtAxis(); - - if (fabsf(up*lightDir) > 0.75f) - { - up = camera.getUpAxis(); - } - - up.normVec(); - at.normVec(); - - - LLCamera main_camera = camera; - - F32 near_clip = 0.f; - { - //get visible point cloud - std::vector fp; - - main_camera.calcAgentFrustumPlanes(main_camera.mAgentFrustum); - - LLVector3 min,max; - getVisiblePointCloud(main_camera,min,max,fp); - - if (fp.empty()) - { - if (!hasRenderDebugMask(RENDER_DEBUG_SHADOW_FRUSTA) && !gCubeSnapshot) - { - mShadowCamera[0] = main_camera; - mShadowExtents[0][0] = min; - mShadowExtents[0][1] = max; - - mShadowFrustPoints[0].clear(); - mShadowFrustPoints[1].clear(); - mShadowFrustPoints[2].clear(); - mShadowFrustPoints[3].clear(); - } - popRenderTypeMask(); - - if (!skip_avatar_update) - { - gAgentAvatarp->updateAttachmentVisibility(gAgentCamera.getCameraMode()); - } - - return; - } - - //get good split distances for frustum - for (U32 i = 0; i < fp.size(); ++i) - { - glh::vec3f v(fp[i].mV); - saved_view.mult_matrix_vec(v); - fp[i].setVec(v.v); - } - - min = fp[0]; - max = fp[0]; - - //get camera space bounding box - for (U32 i = 1; i < fp.size(); ++i) - { - update_min_max(min, max, fp[i]); - } - - near_clip = llclamp(-max.mV[2], 0.01f, 4.0f); - F32 far_clip = llclamp(-min.mV[2]*2.f, 16.0f, 512.0f); - - //far_clip = llmin(far_clip, 128.f); - far_clip = llmin(far_clip, camera.getFar()); - - F32 range = far_clip-near_clip; - - LLVector3 split_exp = RenderShadowSplitExponent; - - F32 da = 1.f-llmax( fabsf(lightDir*up), fabsf(lightDir*camera.getLeftAxis()) ); - - da = powf(da, split_exp.mV[2]); - - F32 sxp = split_exp.mV[1] + (split_exp.mV[0]-split_exp.mV[1])*da; - - for (U32 i = 0; i < 4; ++i) - { - F32 x = (F32)(i+1)/4.f; - x = powf(x, sxp); - mSunClipPlanes.mV[i] = near_clip+range*x; - } - - mSunClipPlanes.mV[0] *= 1.25f; //bump back first split for transition padding - } - - if (gCubeSnapshot) - { // stretch clip planes for reflection probe renders to reduce number of shadow passes - mSunClipPlanes.mV[1] = mSunClipPlanes.mV[2]; - mSunClipPlanes.mV[2] = mSunClipPlanes.mV[3]; - mSunClipPlanes.mV[3] *= 1.5f; - } - - - // convenience array of 4 near clip plane distances - F32 dist[] = { near_clip, mSunClipPlanes.mV[0], mSunClipPlanes.mV[1], mSunClipPlanes.mV[2], mSunClipPlanes.mV[3] }; - - if (mSunDiffuse == LLColor4::black) - { //sun diffuse is totally black shadows don't matter - skipRenderingShadows(); - } - else - { - for (S32 j = 0; j < (gCubeSnapshot ? 2 : 4); j++) - { - if (!hasRenderDebugMask(RENDER_DEBUG_SHADOW_FRUSTA) && !gCubeSnapshot) - { - mShadowFrustPoints[j].clear(); - } - - LLViewerCamera::sCurCameraID = (LLViewerCamera::eCameraID)(LLViewerCamera::CAMERA_SUN_SHADOW0+j); - - //restore render matrices - set_current_modelview(saved_view); - set_current_projection(saved_proj); - - LLVector3 eye = camera.getOrigin(); - llassert(eye.isFinite()); - - //camera used for shadow cull/render - LLCamera shadow_cam; - - //create world space camera frustum for this split - shadow_cam = camera; - shadow_cam.setFar(16.f); - - LLViewerCamera::updateFrustumPlanes(shadow_cam, false, false, true); - - LLVector3* frust = shadow_cam.mAgentFrustum; - - LLVector3 pn = shadow_cam.getAtAxis(); - - LLVector3 min, max; - - //construct 8 corners of split frustum section - for (U32 i = 0; i < 4; i++) - { - LLVector3 delta = frust[i+4]-eye; - delta += (frust[i+4]-frust[(i+2)%4+4])*0.05f; - delta.normVec(); - F32 dp = delta*pn; - frust[i] = eye + (delta*dist[j]*0.75f)/dp; - frust[i+4] = eye + (delta*dist[j+1]*1.25f)/dp; - } - - shadow_cam.calcAgentFrustumPlanes(frust); - shadow_cam.mFrustumCornerDist = 0.f; - - if (!gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_SHADOW_FRUSTA) && !gCubeSnapshot) - { - mShadowCamera[j] = shadow_cam; - } - - std::vector fp; - - if (!gPipeline.getVisiblePointCloud(shadow_cam, min, max, fp, lightDir) - || j > RenderShadowSplits) - { - //no possible shadow receivers - if (!gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_SHADOW_FRUSTA) && !gCubeSnapshot) - { - mShadowExtents[j][0] = LLVector3(); - mShadowExtents[j][1] = LLVector3(); - mShadowCamera[j+4] = shadow_cam; - } - - mRT->shadow[j].bindTarget(); - { - LLGLDepthTest depth(GL_TRUE); - mRT->shadow[j].clear(); - } - mRT->shadow[j].flush(); - - mShadowError.mV[j] = 0.f; - mShadowFOV.mV[j] = 0.f; - - continue; - } - - if (!gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_SHADOW_FRUSTA) && !gCubeSnapshot) - { - mShadowExtents[j][0] = min; - mShadowExtents[j][1] = max; - mShadowFrustPoints[j] = fp; - } - - - //find a good origin for shadow projection - LLVector3 origin; - - //get a temporary view projection - view[j] = look(camera.getOrigin(), lightDir, -up); - - std::vector wpf; - - for (U32 i = 0; i < fp.size(); i++) - { - glh::vec3f p = glh::vec3f(fp[i].mV); - view[j].mult_matrix_vec(p); - wpf.push_back(LLVector3(p.v)); - } - - min = wpf[0]; - max = wpf[0]; - - for (U32 i = 0; i < fp.size(); ++i) - { //get AABB in camera space - update_min_max(min, max, wpf[i]); - } - - // Construct a perspective transform with perspective along y-axis that contains - // points in wpf - //Known: - // - far clip plane - // - near clip plane - // - points in frustum - //Find: - // - origin - - //get some "interesting" points of reference - LLVector3 center = (min+max)*0.5f; - LLVector3 size = (max-min)*0.5f; - LLVector3 near_center = center; - near_center.mV[1] += size.mV[1]*2.f; - - - //put all points in wpf in quadrant 0, reletive to center of min/max - //get the best fit line using least squares - F32 bfm = 0.f; - F32 bfb = 0.f; - - for (U32 i = 0; i < wpf.size(); ++i) - { - wpf[i] -= center; - wpf[i].mV[0] = fabsf(wpf[i].mV[0]); - wpf[i].mV[2] = fabsf(wpf[i].mV[2]); - } - - if (!wpf.empty()) - { - F32 sx = 0.f; - F32 sx2 = 0.f; - F32 sy = 0.f; - F32 sxy = 0.f; - - for (U32 i = 0; i < wpf.size(); ++i) - { - sx += wpf[i].mV[0]; - sx2 += wpf[i].mV[0]*wpf[i].mV[0]; - sy += wpf[i].mV[1]; - sxy += wpf[i].mV[0]*wpf[i].mV[1]; - } - - bfm = (sy*sx-wpf.size()*sxy)/(sx*sx-wpf.size()*sx2); - bfb = (sx*sxy-sy*sx2)/(sx*sx-bfm*sx2); - } - - { - // best fit line is y=bfm*x+bfb - - //find point that is furthest to the right of line - F32 off_x = -1.f; - LLVector3 lp; - - for (U32 i = 0; i < wpf.size(); ++i) - { - //y = bfm*x+bfb - //x = (y-bfb)/bfm - F32 lx = (wpf[i].mV[1]-bfb)/bfm; - - lx = wpf[i].mV[0]-lx; - - if (off_x < lx) - { - off_x = lx; - lp = wpf[i]; - } - } - - //get line with slope bfm through lp - // bfb = y-bfm*x - bfb = lp.mV[1]-bfm*lp.mV[0]; - - //calculate error - mShadowError.mV[j] = 0.f; - - for (U32 i = 0; i < wpf.size(); ++i) - { - F32 lx = (wpf[i].mV[1]-bfb)/bfm; - mShadowError.mV[j] += fabsf(wpf[i].mV[0]-lx); - } - - mShadowError.mV[j] /= wpf.size(); - mShadowError.mV[j] /= size.mV[0]; - - if (mShadowError.mV[j] > RenderShadowErrorCutoff) - { //just use ortho projection - mShadowFOV.mV[j] = -1.f; - origin.clearVec(); - proj[j] = gl_ortho(min.mV[0], max.mV[0], - min.mV[1], max.mV[1], - -max.mV[2], -min.mV[2]); - } - else - { - //origin is where line x = 0; - origin.setVec(0,bfb,0); - - F32 fovz = 1.f; - F32 fovx = 1.f; - - LLVector3 zp; - LLVector3 xp; - - for (U32 i = 0; i < wpf.size(); ++i) - { - LLVector3 atz = wpf[i]-origin; - atz.mV[0] = 0.f; - atz.normVec(); - if (fovz > -atz.mV[1]) - { - zp = wpf[i]; - fovz = -atz.mV[1]; - } - - LLVector3 atx = wpf[i]-origin; - atx.mV[2] = 0.f; - atx.normVec(); - if (fovx > -atx.mV[1]) - { - fovx = -atx.mV[1]; - xp = wpf[i]; - } - } - - fovx = acos(fovx); - fovz = acos(fovz); - - F32 cutoff = llmin((F32) RenderShadowFOVCutoff, 1.4f); - - mShadowFOV.mV[j] = fovx; - - if (fovx < cutoff && fovz > cutoff) - { - //x is a good fit, but z is too big, move away from zp enough so that fovz matches cutoff - F32 d = zp.mV[2]/tan(cutoff); - F32 ny = zp.mV[1] + fabsf(d); - - origin.mV[1] = ny; - - fovz = 1.f; - fovx = 1.f; - - for (U32 i = 0; i < wpf.size(); ++i) - { - LLVector3 atz = wpf[i]-origin; - atz.mV[0] = 0.f; - atz.normVec(); - fovz = llmin(fovz, -atz.mV[1]); - - LLVector3 atx = wpf[i]-origin; - atx.mV[2] = 0.f; - atx.normVec(); - fovx = llmin(fovx, -atx.mV[1]); - } - - fovx = acos(fovx); - fovz = acos(fovz); - - mShadowFOV.mV[j] = cutoff; - } - - - origin += center; - - F32 ynear = -(max.mV[1]-origin.mV[1]); - F32 yfar = -(min.mV[1]-origin.mV[1]); - - if (ynear < 0.1f) //keep a sensible near clip plane - { - F32 diff = 0.1f-ynear; - origin.mV[1] += diff; - ynear += diff; - yfar += diff; - } - - if (fovx > cutoff) - { //just use ortho projection - origin.clearVec(); - mShadowError.mV[j] = -1.f; - proj[j] = gl_ortho(min.mV[0], max.mV[0], - min.mV[1], max.mV[1], - -max.mV[2], -min.mV[2]); - } - else - { - //get perspective projection - view[j] = view[j].inverse(); - //llassert(origin.isFinite()); - - glh::vec3f origin_agent(origin.mV); - - //translate view to origin - view[j].mult_matrix_vec(origin_agent); - - eye = LLVector3(origin_agent.v); - //llassert(eye.isFinite()); - if (!hasRenderDebugMask(LLPipeline::RENDER_DEBUG_SHADOW_FRUSTA) && !gCubeSnapshot) - { - mShadowFrustOrigin[j] = eye; - } - - view[j] = look(LLVector3(origin_agent.v), lightDir, -up); - - F32 fx = 1.f/tanf(fovx); - F32 fz = 1.f/tanf(fovz); - - proj[j] = glh::matrix4f(-fx, 0, 0, 0, - 0, (yfar+ynear)/(ynear-yfar), 0, (2.f*yfar*ynear)/(ynear-yfar), - 0, 0, -fz, 0, - 0, -1.f, 0, 0); - } - } - } - - //shadow_cam.setFar(128.f); - shadow_cam.setOriginAndLookAt(eye, up, center); - - shadow_cam.setOrigin(0,0,0); - - set_current_modelview(view[j]); - set_current_projection(proj[j]); - - LLViewerCamera::updateFrustumPlanes(shadow_cam, false, false, true); - - //shadow_cam.ignoreAgentFrustumPlane(LLCamera::AGENT_PLANE_NEAR); - shadow_cam.getAgentPlane(LLCamera::AGENT_PLANE_NEAR).set(shadow_near_clip); - - //translate and scale to from [-1, 1] to [0, 1] - glh::matrix4f trans(0.5f, 0.f, 0.f, 0.5f, - 0.f, 0.5f, 0.f, 0.5f, - 0.f, 0.f, 0.5f, 0.5f, - 0.f, 0.f, 0.f, 1.f); - - set_current_modelview(view[j]); - set_current_projection(proj[j]); - - for (U32 i = 0; i < 16; i++) - { - gGLLastModelView[i] = mShadowModelview[j].m[i]; - gGLLastProjection[i] = mShadowProjection[j].m[i]; - } - - mShadowModelview[j] = view[j]; - mShadowProjection[j] = proj[j]; - mSunShadowMatrix[j] = trans*proj[j]*view[j]*inv_view; - - stop_glerror(); - - mRT->shadow[j].bindTarget(); - mRT->shadow[j].getViewport(gGLViewport); - mRT->shadow[j].clear(); - - { - static LLCullResult result[4]; - renderShadow(view[j], proj[j], shadow_cam, result[j], true); - } - - mRT->shadow[j].flush(); - - if (!gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_SHADOW_FRUSTA) && !gCubeSnapshot) - { - mShadowCamera[j+4] = shadow_cam; - } - } - } - - //hack to disable projector shadows - bool gen_shadow = RenderShadowDetail > 1; - - if (gen_shadow) - { - if (!gCubeSnapshot) //skip updating spot shadow maps during cubemap updates - { - LLTrace::CountStatHandle<>* velocity_stat = LLViewerCamera::getVelocityStat(); - F32 fade_amt = gFrameIntervalSeconds.value() - * llmax(LLTrace::get_frame_recording().getLastRecording().getSum(*velocity_stat) / LLTrace::get_frame_recording().getLastRecording().getDuration().value(), 1.0); - - // should never happen - llassert(mTargetShadowSpotLight[0] != mTargetShadowSpotLight[1] || mTargetShadowSpotLight[0].isNull()); - - //update shadow targets - for (U32 i = 0; i < 2; i++) - { //for each current shadow - LLViewerCamera::sCurCameraID = (LLViewerCamera::eCameraID)(LLViewerCamera::CAMERA_SPOT_SHADOW0 + i); - - if (mShadowSpotLight[i].notNull() && - (mShadowSpotLight[i] == mTargetShadowSpotLight[0] || - mShadowSpotLight[i] == mTargetShadowSpotLight[1])) - { //keep this spotlight - mSpotLightFade[i] = llmin(mSpotLightFade[i] + fade_amt, 1.f); - } - else - { //fade out this light - mSpotLightFade[i] = llmax(mSpotLightFade[i] - fade_amt, 0.f); - - if (mSpotLightFade[i] == 0.f || mShadowSpotLight[i].isNull()) - { //faded out, grab one of the pending spots (whichever one isn't already taken) - if (mTargetShadowSpotLight[0] != mShadowSpotLight[(i + 1) % 2]) - { - mShadowSpotLight[i] = mTargetShadowSpotLight[0]; - } - else - { - mShadowSpotLight[i] = mTargetShadowSpotLight[1]; - } - } - } - } - } - - // this should never happen - llassert(mShadowSpotLight[0] != mShadowSpotLight[1] || mShadowSpotLight[0].isNull()); - - for (S32 i = 0; i < 2; i++) - { - set_current_modelview(saved_view); - set_current_projection(saved_proj); - - if (mShadowSpotLight[i].isNull()) - { - continue; - } - - LLVOVolume* volume = mShadowSpotLight[i]->getVOVolume(); - - if (!volume) - { - mShadowSpotLight[i] = NULL; - continue; - } - - LLDrawable* drawable = mShadowSpotLight[i]; - - LLVector3 params = volume->getSpotLightParams(); - F32 fov = params.mV[0]; - - //get agent->light space matrix (modelview) - LLVector3 center = drawable->getPositionAgent(); - LLQuaternion quat = volume->getRenderRotation(); - - //get near clip plane - LLVector3 scale = volume->getScale(); - LLVector3 at_axis(0, 0, -scale.mV[2] * 0.5f); - at_axis *= quat; - - LLVector3 np = center + at_axis; - at_axis.normVec(); - - //get origin that has given fov for plane np, at_axis, and given scale - F32 dist = (scale.mV[1] * 0.5f) / tanf(fov * 0.5f); - - LLVector3 origin = np - at_axis * dist; - - LLMatrix4 mat(quat, LLVector4(origin, 1.f)); - - view[i + 4] = glh::matrix4f((F32*)mat.mMatrix); - - view[i + 4] = view[i + 4].inverse(); - - //get perspective matrix - F32 near_clip = dist + 0.01f; - F32 width = scale.mV[VX]; - F32 height = scale.mV[VY]; - F32 far_clip = dist + volume->getLightRadius() * 1.5f; - - F32 fovy = fov * RAD_TO_DEG; - F32 aspect = width / height; - - proj[i + 4] = gl_perspective(fovy, aspect, near_clip, far_clip); - - //translate and scale to from [-1, 1] to [0, 1] - glh::matrix4f trans(0.5f, 0.f, 0.f, 0.5f, - 0.f, 0.5f, 0.f, 0.5f, - 0.f, 0.f, 0.5f, 0.5f, - 0.f, 0.f, 0.f, 1.f); - - set_current_modelview(view[i + 4]); - set_current_projection(proj[i + 4]); - - mSunShadowMatrix[i + 4] = trans * proj[i + 4] * view[i + 4] * inv_view; - - for (U32 j = 0; j < 16; j++) - { - gGLLastModelView[j] = mShadowModelview[i + 4].m[j]; - gGLLastProjection[j] = mShadowProjection[i + 4].m[j]; - } - - mShadowModelview[i + 4] = view[i + 4]; - mShadowProjection[i + 4] = proj[i + 4]; - - if (!gCubeSnapshot) //skip updating spot shadow maps during cubemap updates - { - LLCamera shadow_cam = camera; - shadow_cam.setFar(far_clip); - shadow_cam.setOrigin(origin); - - LLViewerCamera::updateFrustumPlanes(shadow_cam, false, false, true); - - // - - mSpotShadow[i].bindTarget(); - mSpotShadow[i].getViewport(gGLViewport); - mSpotShadow[i].clear(); - - static LLCullResult result[2]; - - LLViewerCamera::sCurCameraID = (LLViewerCamera::eCameraID)(LLViewerCamera::CAMERA_SPOT_SHADOW0 + i); - - RenderSpotLight = drawable; - - renderShadow(view[i + 4], proj[i + 4], shadow_cam, result[i], false); - - RenderSpotLight = nullptr; - - mSpotShadow[i].flush(); - } - } - } - else - { //no spotlight shadows - mShadowSpotLight[0] = mShadowSpotLight[1] = NULL; - } - - - if (!CameraOffset) - { - set_current_modelview(saved_view); - set_current_projection(saved_proj); - } - else - { - set_current_modelview(view[1]); - set_current_projection(proj[1]); - gGL.loadMatrix(view[1].m); - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.loadMatrix(proj[1].m); - gGL.matrixMode(LLRender::MM_MODELVIEW); - } - gGL.setColorMask(true, true); - - for (U32 i = 0; i < 16; i++) - { - gGLLastModelView[i] = last_modelview[i]; - gGLLastProjection[i] = last_projection[i]; - } - - popRenderTypeMask(); - - if (!skip_avatar_update) - { - gAgentAvatarp->updateAttachmentVisibility(gAgentCamera.getCameraMode()); - } -} - -void LLPipeline::renderGroups(LLRenderPass* pass, U32 type, bool texture) -{ - for (LLCullResult::sg_iterator i = sCull->beginVisibleGroups(); i != sCull->endVisibleGroups(); ++i) - { - LLSpatialGroup* group = *i; - if (!group->isDead() && - (!sUseOcclusion || !group->isOcclusionState(LLSpatialGroup::OCCLUDED)) && - gPipeline.hasRenderType(group->getSpatialPartition()->mDrawableType) && - group->mDrawMap.find(type) != group->mDrawMap.end()) - { - pass->renderGroup(group,type,texture); - } - } -} - -void LLPipeline::renderRiggedGroups(LLRenderPass* pass, U32 type, bool texture) -{ - for (LLCullResult::sg_iterator i = sCull->beginVisibleGroups(); i != sCull->endVisibleGroups(); ++i) - { - LLSpatialGroup* group = *i; - if (!group->isDead() && - (!sUseOcclusion || !group->isOcclusionState(LLSpatialGroup::OCCLUDED)) && - gPipeline.hasRenderType(group->getSpatialPartition()->mDrawableType) && - group->mDrawMap.find(type) != group->mDrawMap.end()) - { - pass->renderRiggedGroup(group, type, texture); - } - } -} - -void LLPipeline::profileAvatar(LLVOAvatar* avatar, bool profile_attachments) -{ - if (gGLManager.mGLVersion < 3.25f) - { // profiling requires GL 3.3 or later - return; - } - - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - - // don't continue to profile an avatar that is known to be too slow - llassert(!avatar->isTooSlow()); - - LLGLSLShader* cur_shader = LLGLSLShader::sCurBoundShaderPtr; - - mRT->deferredScreen.bindTarget(); - mRT->deferredScreen.clear(); - - if (!profile_attachments) - { - // profile entire avatar all at once and readback asynchronously - avatar->placeProfileQuery(); - - LLTimer cpu_timer; - - generateImpostor(avatar, false, true); - - avatar->mCPURenderTime = (F32)cpu_timer.getElapsedTimeF32() * 1000.f; - - avatar->readProfileQuery(5); // allow up to 5 frames of latency - } - else - { - // profile attachments one at a time - LLVOAvatar::attachment_map_t::iterator iter; - LLVOAvatar::attachment_map_t::iterator begin = avatar->mAttachmentPoints.begin(); - LLVOAvatar::attachment_map_t::iterator end = avatar->mAttachmentPoints.end(); - - for (iter = begin; - iter != end; - ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - LLViewerObject* attached_object = attachment_iter->get(); - if (attached_object) - { - // use gDebugProgram to do the GPU queries - gDebugProgram.clearStats(); - gDebugProgram.placeProfileQuery(true); - - generateImpostor(avatar, false, true, attached_object); - gDebugProgram.readProfileQuery(true, true); - - attached_object->mGPURenderTime = gDebugProgram.mTimeElapsed / 1000000.f; - } - } - } - } - - mRT->deferredScreen.flush(); - - if (cur_shader) - { - cur_shader->bind(); - } -} - -void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar, bool for_profile, LLViewerObject* specific_attachment) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; - LL_PROFILE_GPU_ZONE("generateImpostor"); - LLGLState::checkStates(); - - static LLCullResult result; - result.clear(); - grabReferences(result); - - if (!avatar || !avatar->mDrawable) - { - LL_WARNS_ONCE("AvatarRenderPipeline") << "Avatar is " << (avatar ? "not drawable" : "null") << LL_ENDL; - return; - } - LL_DEBUGS_ONCE("AvatarRenderPipeline") << "Avatar " << avatar->getID() << " is drawable" << LL_ENDL; - - assertInitialized(); - - // previews can't be muted or impostered - bool visually_muted = !for_profile && !preview_avatar && avatar->isVisuallyMuted(); - LL_DEBUGS_ONCE("AvatarRenderPipeline") << "Avatar " << avatar->getID() - << " is " << ( visually_muted ? "" : "not ") << "visually muted" - << LL_ENDL; - bool too_complex = !for_profile && !preview_avatar && avatar->isTooComplex(); - LL_DEBUGS_ONCE("AvatarRenderPipeline") << "Avatar " << avatar->getID() - << " is " << ( too_complex ? "" : "not ") << "too complex" - << LL_ENDL; - - pushRenderTypeMask(); - - if (visually_muted || too_complex) - { - // only show jelly doll geometry - andRenderTypeMask(LLPipeline::RENDER_TYPE_AVATAR, - LLPipeline::RENDER_TYPE_CONTROL_AV, - END_RENDER_TYPES); - } - else - { - //hide world geometry - clearRenderTypeMask( - RENDER_TYPE_SKY, - RENDER_TYPE_WL_SKY, - RENDER_TYPE_TERRAIN, - RENDER_TYPE_GRASS, - RENDER_TYPE_CONTROL_AV, // Animesh - RENDER_TYPE_TREE, - RENDER_TYPE_VOIDWATER, - RENDER_TYPE_WATER, - RENDER_TYPE_ALPHA_PRE_WATER, - RENDER_TYPE_PASS_GRASS, - RENDER_TYPE_HUD, - RENDER_TYPE_PARTICLES, - RENDER_TYPE_CLOUDS, - RENDER_TYPE_HUD_PARTICLES, - END_RENDER_TYPES - ); - } - - if (specific_attachment && specific_attachment->isHUDAttachment()) - { //enable HUD rendering - setRenderTypeMask(RENDER_TYPE_HUD, END_RENDER_TYPES); - } - - S32 occlusion = sUseOcclusion; - sUseOcclusion = 0; - - sReflectionRender = ! sRenderDeferred; - - sShadowRender = true; - sImpostorRender = true; - - LLViewerCamera* viewer_camera = LLViewerCamera::getInstance(); - - { - markVisible(avatar->mDrawable, *viewer_camera); - - if (preview_avatar) - { - // Only show rigged attachments for preview - // For the sake of performance and so that static - // objects won't obstruct previewing changes - LLVOAvatar::attachment_map_t::iterator iter; - for (iter = avatar->mAttachmentPoints.begin(); - iter != avatar->mAttachmentPoints.end(); - ++iter) - { - LLViewerJointAttachment *attachment = iter->second; - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - LLViewerObject* attached_object = attachment_iter->get(); - if (attached_object) - { - if (attached_object->isRiggedMesh()) - { - markVisible(attached_object->mDrawable->getSpatialBridge(), *viewer_camera); - } - else - { - // sometimes object is a linkset and rigged mesh is a child - LLViewerObject::const_child_list_t& child_list = attached_object->getChildren(); - for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); iter++) - { - LLViewerObject* child = *iter; - if (child->isRiggedMesh()) - { - markVisible(attached_object->mDrawable->getSpatialBridge(), *viewer_camera); - break; - } - } - } - } - } - } - } - else - { - if (specific_attachment) - { - markVisible(specific_attachment->mDrawable->getSpatialBridge(), *viewer_camera); - } - else - { - LLVOAvatar::attachment_map_t::iterator iter; - LLVOAvatar::attachment_map_t::iterator begin = avatar->mAttachmentPoints.begin(); - LLVOAvatar::attachment_map_t::iterator end = avatar->mAttachmentPoints.end(); - - for (iter = begin; - iter != end; - ++iter) - { - LLViewerJointAttachment* attachment = iter->second; - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - LLViewerObject* attached_object = attachment_iter->get(); - if (attached_object) - { - markVisible(attached_object->mDrawable->getSpatialBridge(), *viewer_camera); - } - } - } - } - } - } - - stateSort(*LLViewerCamera::getInstance(), result); - - LLCamera camera = *viewer_camera; - LLVector2 tdim; - U32 resY = 0; - U32 resX = 0; - - if (!preview_avatar) - { - const LLVector4a* ext = avatar->mDrawable->getSpatialExtents(); - LLVector3 pos(avatar->getRenderPosition()+avatar->getImpostorOffset()); - - camera.lookAt(viewer_camera->getOrigin(), pos, viewer_camera->getUpAxis()); - - LLVector4a half_height; - half_height.setSub(ext[1], ext[0]); - half_height.mul(0.5f); - - LLVector4a left; - left.load3(camera.getLeftAxis().mV); - left.mul(left); - llassert(left.dot3(left).getF32() > F_APPROXIMATELY_ZERO); - left.normalize3fast(); - - LLVector4a up; - up.load3(camera.getUpAxis().mV); - up.mul(up); - llassert(up.dot3(up).getF32() > F_APPROXIMATELY_ZERO); - up.normalize3fast(); - - tdim.mV[0] = fabsf(half_height.dot3(left).getF32()); - tdim.mV[1] = fabsf(half_height.dot3(up).getF32()); - - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.pushMatrix(); - - F32 distance = (pos-camera.getOrigin()).length(); - F32 fov = atanf(tdim.mV[1]/distance)*2.f*RAD_TO_DEG; - F32 aspect = tdim.mV[0]/tdim.mV[1]; - glh::matrix4f persp = gl_perspective(fov, aspect, 1.f, 256.f); - set_current_projection(persp); - gGL.loadMatrix(persp.m); - - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - glh::matrix4f mat; - camera.getOpenGLTransform(mat.m); - - mat = glh::matrix4f((GLfloat*) OGL_TO_CFR_ROTATION) * mat; - - gGL.loadMatrix(mat.m); - set_current_modelview(mat); - - glClearColor(0.0f,0.0f,0.0f,0.0f); - gGL.setColorMask(true, true); - - // get the number of pixels per angle - F32 pa = gViewerWindow->getWindowHeightRaw() / (RAD_TO_DEG * viewer_camera->getView()); - - //get resolution based on angle width and height of impostor (double desired resolution to prevent aliasing) - resY = llmin(nhpo2((U32) (fov*pa)), (U32) 512); - resX = llmin(nhpo2((U32) (atanf(tdim.mV[0]/distance)*2.f*RAD_TO_DEG*pa)), (U32) 512); - - if (!for_profile) - { - if (!avatar->mImpostor.isComplete()) - { - avatar->mImpostor.allocate(resX, resY, GL_RGBA, true); - - if (LLPipeline::sRenderDeferred) - { - addDeferredAttachments(avatar->mImpostor, true); - } - - gGL.getTexUnit(0)->bind(&avatar->mImpostor); - gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT); - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - } - else if (resX != avatar->mImpostor.getWidth() || resY != avatar->mImpostor.getHeight()) - { - avatar->mImpostor.resize(resX, resY); - } - - avatar->mImpostor.bindTarget(); - } - } - - F32 old_alpha = LLDrawPoolAvatar::sMinimumAlpha; - - if (visually_muted || too_complex) - { //disable alpha masking for muted avatars (get whole skin silhouette) - LLDrawPoolAvatar::sMinimumAlpha = 0.f; - } - - if (preview_avatar || for_profile) - { - // previews and profiles don't care about imposters - renderGeomDeferred(camera); - renderGeomPostDeferred(camera); - } - else - { - avatar->mImpostor.clear(); - renderGeomDeferred(camera); - - renderGeomPostDeferred(camera); - - // Shameless hack time: render it all again, - // this time writing the depth - // values we need to generate the alpha mask below - // while preserving the alpha-sorted color rendering - // from the previous pass - // - sImpostorRenderAlphaDepthPass = true; - // depth-only here... - // - gGL.setColorMask(false,false); - renderGeomPostDeferred(camera); - - sImpostorRenderAlphaDepthPass = false; - - } - - LLDrawPoolAvatar::sMinimumAlpha = old_alpha; - - if (!for_profile) - { //create alpha mask based on depth buffer (grey out if muted) - if (LLPipeline::sRenderDeferred) - { - GLuint buff = GL_COLOR_ATTACHMENT0; - glDrawBuffers(1, &buff); - } - - LLGLDisable blend(GL_BLEND); - - if (visually_muted || too_complex) - { - gGL.setColorMask(true, true); - } - else - { - gGL.setColorMask(false, true); - } - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - - LLGLDepthTest depth(GL_TRUE, GL_FALSE, GL_GREATER); - - gGL.flush(); - - gGL.pushMatrix(); - gGL.loadIdentity(); - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.pushMatrix(); - gGL.loadIdentity(); - - static const F32 clip_plane = 0.99999f; - - gDebugProgram.bind(); - - if (visually_muted) - { // Visually muted avatar - LLColor4 muted_color(avatar->getMutedAVColor()); - LL_DEBUGS_ONCE("AvatarRenderPipeline") << "Avatar " << avatar->getID() << " MUTED set solid color " << muted_color << LL_ENDL; - gGL.diffuseColor4fv( muted_color.mV ); - } - else if (!preview_avatar) - { //grey muted avatar - LL_DEBUGS_ONCE("AvatarRenderPipeline") << "Avatar " << avatar->getID() << " MUTED set grey" << LL_ENDL; - gGL.diffuseColor4fv(LLColor4::pink.mV ); - } - - gGL.begin(LLRender::QUADS); - gGL.vertex3f(-1, -1, clip_plane); - gGL.vertex3f(1, -1, clip_plane); - gGL.vertex3f(1, 1, clip_plane); - gGL.vertex3f(-1, 1, clip_plane); - gGL.end(); - gGL.flush(); - - gDebugProgram.unbind(); - - gGL.popMatrix(); - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.popMatrix(); - } - - if (!preview_avatar && !for_profile) - { - avatar->mImpostor.flush(); - avatar->setImpostorDim(tdim); - } - - sUseOcclusion = occlusion; - sReflectionRender = false; - sImpostorRender = false; - sShadowRender = false; - popRenderTypeMask(); - - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.popMatrix(); - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.popMatrix(); - - if (!preview_avatar && !for_profile) - { - avatar->mNeedsImpostorUpdate = false; - avatar->cacheImpostorValues(); - avatar->mLastImpostorUpdateFrameTime = gFrameTimeSeconds; - } - - LLVertexBuffer::unbind(); - LLGLState::checkStates(); -} - -bool LLPipeline::hasRenderBatches(const U32 type) const -{ - return sCull->getRenderMapSize(type) > 0; -} - -LLCullResult::drawinfo_iterator LLPipeline::beginRenderMap(U32 type) -{ - return sCull->beginRenderMap(type); -} - -LLCullResult::drawinfo_iterator LLPipeline::endRenderMap(U32 type) -{ - return sCull->endRenderMap(type); -} - -LLCullResult::sg_iterator LLPipeline::beginAlphaGroups() -{ - return sCull->beginAlphaGroups(); -} - -LLCullResult::sg_iterator LLPipeline::endAlphaGroups() -{ - return sCull->endAlphaGroups(); -} - -LLCullResult::sg_iterator LLPipeline::beginRiggedAlphaGroups() -{ - return sCull->beginRiggedAlphaGroups(); -} - -LLCullResult::sg_iterator LLPipeline::endRiggedAlphaGroups() -{ - return sCull->endRiggedAlphaGroups(); -} - -bool LLPipeline::hasRenderType(const U32 type) const -{ - // STORM-365 : LLViewerJointAttachment::setAttachmentVisibility() is setting type to 0 to actually mean "do not render" - // We then need to test that value here and return false to prevent attachment to render (in mouselook for instance) - // TODO: reintroduce RENDER_TYPE_NONE in LLRenderTypeMask and initialize its mRenderTypeEnabled[RENDER_TYPE_NONE] to false explicitely - return (type == 0 ? false : mRenderTypeEnabled[type]); -} - -void LLPipeline::setRenderTypeMask(U32 type, ...) -{ - va_list args; - - va_start(args, type); - while (type < END_RENDER_TYPES) - { - mRenderTypeEnabled[type] = true; - type = va_arg(args, U32); - } - va_end(args); - - if (type > END_RENDER_TYPES) - { - LL_ERRS() << "Invalid render type." << LL_ENDL; - } -} - -bool LLPipeline::hasAnyRenderType(U32 type, ...) const -{ - va_list args; - - va_start(args, type); - while (type < END_RENDER_TYPES) - { - if (mRenderTypeEnabled[type]) - { - return true; - } - type = va_arg(args, U32); - } - va_end(args); - - if (type > END_RENDER_TYPES) - { - LL_ERRS() << "Invalid render type." << LL_ENDL; - } - - return false; -} - -void LLPipeline::pushRenderTypeMask() -{ - std::string cur_mask; - cur_mask.assign((const char*) mRenderTypeEnabled, sizeof(mRenderTypeEnabled)); - mRenderTypeEnableStack.push(cur_mask); -} - -void LLPipeline::popRenderTypeMask() -{ - if (mRenderTypeEnableStack.empty()) - { - LL_ERRS() << "Depleted render type stack." << LL_ENDL; - } - - memcpy(mRenderTypeEnabled, mRenderTypeEnableStack.top().data(), sizeof(mRenderTypeEnabled)); - mRenderTypeEnableStack.pop(); -} - -void LLPipeline::andRenderTypeMask(U32 type, ...) -{ - va_list args; - - bool tmp[NUM_RENDER_TYPES]; - for (U32 i = 0; i < NUM_RENDER_TYPES; ++i) - { - tmp[i] = false; - } - - va_start(args, type); - while (type < END_RENDER_TYPES) - { - if (mRenderTypeEnabled[type]) - { - tmp[type] = true; - } - - type = va_arg(args, U32); - } - va_end(args); - - if (type > END_RENDER_TYPES) - { - LL_ERRS() << "Invalid render type." << LL_ENDL; - } - - for (U32 i = 0; i < LLPipeline::NUM_RENDER_TYPES; ++i) - { - mRenderTypeEnabled[i] = tmp[i]; - } - -} - -void LLPipeline::clearRenderTypeMask(U32 type, ...) -{ - va_list args; - - va_start(args, type); - while (type < END_RENDER_TYPES) - { - mRenderTypeEnabled[type] = false; - - type = va_arg(args, U32); - } - va_end(args); - - if (type > END_RENDER_TYPES) - { - LL_ERRS() << "Invalid render type." << LL_ENDL; - } -} - -void LLPipeline::setAllRenderTypes() -{ - for (U32 i = 0; i < NUM_RENDER_TYPES; ++i) - { - mRenderTypeEnabled[i] = true; - } -} - -void LLPipeline::clearAllRenderTypes() -{ - for (U32 i = 0; i < NUM_RENDER_TYPES; ++i) - { - mRenderTypeEnabled[i] = false; - } -} - -void LLPipeline::addDebugBlip(const LLVector3& position, const LLColor4& color) -{ - DebugBlip blip(position, color); - mDebugBlips.push_back(blip); -} - -void LLPipeline::hidePermanentObjects( std::vector& restoreList ) -{ - //This method is used to hide any vo's from the object list that may have - //the permanent flag set. - - U32 objCnt = gObjectList.getNumObjects(); - for (U32 i = 0; i < objCnt; ++i) - { - LLViewerObject* pObject = gObjectList.getObject(i); - if ( pObject && pObject->flagObjectPermanent() ) - { - LLDrawable *pDrawable = pObject->mDrawable; - - if ( pDrawable ) - { - restoreList.push_back( i ); - hideDrawable( pDrawable ); - } - } - } - - skipRenderingOfTerrain( true ); -} - -void LLPipeline::restorePermanentObjects( const std::vector& restoreList ) -{ - //This method is used to restore(unhide) any vo's from the object list that may have - //been hidden because their permanency flag was set. - - std::vector::const_iterator itCurrent = restoreList.begin(); - std::vector::const_iterator itEnd = restoreList.end(); - - U32 objCnt = gObjectList.getNumObjects(); - - while ( itCurrent != itEnd ) - { - U32 index = *itCurrent; - LLViewerObject* pObject = NULL; - if ( index < objCnt ) - { - pObject = gObjectList.getObject( index ); - } - if ( pObject ) - { - LLDrawable *pDrawable = pObject->mDrawable; - if ( pDrawable ) - { - pDrawable->clearState( LLDrawable::FORCE_INVISIBLE ); - unhideDrawable( pDrawable ); - } - } - ++itCurrent; - } - - skipRenderingOfTerrain( false ); -} - -void LLPipeline::skipRenderingOfTerrain( bool flag ) -{ - pool_set_t::iterator iter = mPools.begin(); - while ( iter != mPools.end() ) - { - LLDrawPool* pPool = *iter; - U32 poolType = pPool->getType(); - if ( hasRenderType( pPool->getType() ) && poolType == LLDrawPool::POOL_TERRAIN ) - { - pPool->setSkipRenderFlag( flag ); - } - ++iter; - } -} - -void LLPipeline::hideObject( const LLUUID& id ) -{ - LLViewerObject *pVO = gObjectList.findObject( id ); - - if ( pVO ) - { - LLDrawable *pDrawable = pVO->mDrawable; - - if ( pDrawable ) - { - hideDrawable( pDrawable ); - } - } -} - -void LLPipeline::hideDrawable( LLDrawable *pDrawable ) -{ - pDrawable->setState( LLDrawable::FORCE_INVISIBLE ); - markRebuild( pDrawable, LLDrawable::REBUILD_ALL); - //hide the children - LLViewerObject::const_child_list_t& child_list = pDrawable->getVObj()->getChildren(); - for ( LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); iter++ ) - { - LLViewerObject* child = *iter; - LLDrawable* drawable = child->mDrawable; - if ( drawable ) - { - drawable->setState( LLDrawable::FORCE_INVISIBLE ); - markRebuild( drawable, LLDrawable::REBUILD_ALL); - } - } -} -void LLPipeline::unhideDrawable( LLDrawable *pDrawable ) -{ - pDrawable->clearState( LLDrawable::FORCE_INVISIBLE ); - markRebuild( pDrawable, LLDrawable::REBUILD_ALL); - //restore children - LLViewerObject::const_child_list_t& child_list = pDrawable->getVObj()->getChildren(); - for ( LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); - iter != child_list.end(); iter++) - { - LLViewerObject* child = *iter; - LLDrawable* drawable = child->mDrawable; - if ( drawable ) - { - drawable->clearState( LLDrawable::FORCE_INVISIBLE ); - markRebuild( drawable, LLDrawable::REBUILD_ALL); - } - } -} -void LLPipeline::restoreHiddenObject( const LLUUID& id ) -{ - LLViewerObject *pVO = gObjectList.findObject( id ); - - if ( pVO ) - { - LLDrawable *pDrawable = pVO->mDrawable; - if ( pDrawable ) - { - unhideDrawable( pDrawable ); - } - } -} - -void LLPipeline::skipRenderingShadows() -{ - LLGLDepthTest depth(GL_TRUE); - - for (S32 j = 0; j < 4; j++) - { - mRT->shadow[j].bindTarget(); - mRT->shadow[j].clear(); - mRT->shadow[j].flush(); - } -} - -void LLPipeline::handleShadowDetailChanged() -{ - if (RenderShadowDetail > gSavedSettings.getS32("RenderShadowDetail")) - { - skipRenderingShadows(); - } - else - { - LLViewerShaderMgr::instance()->setShaders(); - } -} - -class LLOctreeDirty : public OctreeTraveler -{ -public: - virtual void visit(const OctreeNode* state) - { - LLSpatialGroup* group = (LLSpatialGroup*)state->getListener(0); - - if (group->getSpatialPartition()->mRenderByGroup) - { - group->setState(LLSpatialGroup::GEOM_DIRTY); - gPipeline.markRebuild(group); - } - - for (LLSpatialGroup::bridge_list_t::iterator i = group->mBridgeList.begin(); i != group->mBridgeList.end(); ++i) - { - LLSpatialBridge* bridge = *i; - traverse(bridge->mOctree); - } - } -}; - - -void LLPipeline::rebuildDrawInfo() -{ - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) - { - LLViewerRegion* region = *iter; - - LLOctreeDirty dirty; - - LLSpatialPartition* part = region->getSpatialPartition(LLViewerRegion::PARTITION_VOLUME); - dirty.traverse(part->mOctree); - - part = region->getSpatialPartition(LLViewerRegion::PARTITION_BRIDGE); - dirty.traverse(part->mOctree); - } -} - +/** + * @file pipeline.cpp + * @brief Rendering pipeline. + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "pipeline.h" + +// library includes +#include "llaudioengine.h" // For debugging. +#include "llerror.h" +#include "llviewercontrol.h" +#include "llfasttimer.h" +#include "llfontgl.h" +#include "llnamevalue.h" +#include "llpointer.h" +#include "llprimitive.h" +#include "llvolume.h" +#include "material_codes.h" +#include "v3color.h" +#include "llui.h" +#include "llglheaders.h" +#include "llrender.h" +#include "llstartup.h" +#include "llwindow.h" // swapBuffers() + +// newview includes +#include "llagent.h" +#include "llagentcamera.h" +#include "llappviewer.h" +#include "lltexturecache.h" +#include "lltexturefetch.h" +#include "llimageworker.h" +#include "lldrawable.h" +#include "lldrawpoolalpha.h" +#include "lldrawpoolavatar.h" +#include "lldrawpoolbump.h" +#include "lldrawpooltree.h" +#include "lldrawpoolwater.h" +#include "llface.h" +#include "llfeaturemanager.h" +#include "llfloatertelehub.h" +#include "llfloaterreg.h" +#include "llhudmanager.h" +#include "llhudnametag.h" +#include "llhudtext.h" +#include "lllightconstants.h" +#include "llmeshrepository.h" +#include "llpipelinelistener.h" +#include "llresmgr.h" +#include "llselectmgr.h" +#include "llsky.h" +#include "lltracker.h" +#include "lltool.h" +#include "lltoolmgr.h" +#include "llviewercamera.h" +#include "llviewermediafocus.h" +#include "llviewertexturelist.h" +#include "llviewerobject.h" +#include "llviewerobjectlist.h" +#include "llviewerparcelmgr.h" +#include "llviewerregion.h" // for audio debugging. +#include "llviewerwindow.h" // For getSpinAxis +#include "llvoavatarself.h" +#include "llvocache.h" +#include "llvosky.h" +#include "llvowlsky.h" +#include "llvotree.h" +#include "llvovolume.h" +#include "llvosurfacepatch.h" +#include "llvowater.h" +#include "llvotree.h" +#include "llvopartgroup.h" +#include "llworld.h" +#include "llcubemap.h" +#include "llviewershadermgr.h" +#include "llviewerstats.h" +#include "llviewerjoystick.h" +#include "llviewerdisplay.h" +#include "llspatialpartition.h" +#include "llmutelist.h" +#include "lltoolpie.h" +#include "llnotifications.h" +#include "llpathinglib.h" +#include "llfloaterpathfindingconsole.h" +#include "llfloaterpathfindingcharacters.h" +#include "llfloatertools.h" +#include "llpanelface.h" +#include "llpathfindingpathtool.h" +#include "llscenemonitor.h" +#include "llprogressview.h" +#include "llcleanup.h" + +#include "llenvironment.h" +#include "llsettingsvo.h" + +extern bool gSnapshot; +bool gShiftFrame = false; + +//cached settings +bool LLPipeline::WindLightUseAtmosShaders; +bool LLPipeline::RenderDeferred; +F32 LLPipeline::RenderDeferredSunWash; +U32 LLPipeline::RenderFSAASamples; +U32 LLPipeline::RenderResolutionDivisor; +bool LLPipeline::RenderUIBuffer; +S32 LLPipeline::RenderShadowDetail; +S32 LLPipeline::RenderShadowSplits; +bool LLPipeline::RenderDeferredSSAO; +F32 LLPipeline::RenderShadowResolutionScale; +bool LLPipeline::RenderDelayCreation; +bool LLPipeline::RenderAnimateRes; +bool LLPipeline::FreezeTime; +S32 LLPipeline::DebugBeaconLineWidth; +F32 LLPipeline::RenderHighlightBrightness; +LLColor4 LLPipeline::RenderHighlightColor; +F32 LLPipeline::RenderHighlightThickness; +bool LLPipeline::RenderSpotLightsInNondeferred; +LLColor4 LLPipeline::PreviewAmbientColor; +LLColor4 LLPipeline::PreviewDiffuse0; +LLColor4 LLPipeline::PreviewSpecular0; +LLColor4 LLPipeline::PreviewDiffuse1; +LLColor4 LLPipeline::PreviewSpecular1; +LLColor4 LLPipeline::PreviewDiffuse2; +LLColor4 LLPipeline::PreviewSpecular2; +LLVector3 LLPipeline::PreviewDirection0; +LLVector3 LLPipeline::PreviewDirection1; +LLVector3 LLPipeline::PreviewDirection2; +F32 LLPipeline::RenderGlowMaxExtractAlpha; +F32 LLPipeline::RenderGlowWarmthAmount; +LLVector3 LLPipeline::RenderGlowLumWeights; +LLVector3 LLPipeline::RenderGlowWarmthWeights; +S32 LLPipeline::RenderGlowResolutionPow; +S32 LLPipeline::RenderGlowIterations; +F32 LLPipeline::RenderGlowWidth; +F32 LLPipeline::RenderGlowStrength; +bool LLPipeline::RenderGlowNoise; +bool LLPipeline::RenderDepthOfField; +bool LLPipeline::RenderDepthOfFieldInEditMode; +F32 LLPipeline::CameraFocusTransitionTime; +F32 LLPipeline::CameraFNumber; +F32 LLPipeline::CameraFocalLength; +F32 LLPipeline::CameraFieldOfView; +F32 LLPipeline::RenderShadowNoise; +F32 LLPipeline::RenderShadowBlurSize; +F32 LLPipeline::RenderSSAOScale; +U32 LLPipeline::RenderSSAOMaxScale; +F32 LLPipeline::RenderSSAOFactor; +LLVector3 LLPipeline::RenderSSAOEffect; +F32 LLPipeline::RenderShadowOffsetError; +F32 LLPipeline::RenderShadowBiasError; +F32 LLPipeline::RenderShadowOffset; +F32 LLPipeline::RenderShadowBias; +F32 LLPipeline::RenderSpotShadowOffset; +F32 LLPipeline::RenderSpotShadowBias; +LLDrawable* LLPipeline::RenderSpotLight = nullptr; +F32 LLPipeline::RenderEdgeDepthCutoff; +F32 LLPipeline::RenderEdgeNormCutoff; +LLVector3 LLPipeline::RenderShadowGaussian; +F32 LLPipeline::RenderShadowBlurDistFactor; +bool LLPipeline::RenderDeferredAtmospheric; +F32 LLPipeline::RenderHighlightFadeTime; +F32 LLPipeline::RenderFarClip; +LLVector3 LLPipeline::RenderShadowSplitExponent; +F32 LLPipeline::RenderShadowErrorCutoff; +F32 LLPipeline::RenderShadowFOVCutoff; +bool LLPipeline::CameraOffset; +F32 LLPipeline::CameraMaxCoF; +F32 LLPipeline::CameraDoFResScale; +F32 LLPipeline::RenderAutoHideSurfaceAreaLimit; +bool LLPipeline::RenderScreenSpaceReflections; +S32 LLPipeline::RenderScreenSpaceReflectionIterations; +F32 LLPipeline::RenderScreenSpaceReflectionRayStep; +F32 LLPipeline::RenderScreenSpaceReflectionDistanceBias; +F32 LLPipeline::RenderScreenSpaceReflectionDepthRejectBias; +F32 LLPipeline::RenderScreenSpaceReflectionAdaptiveStepMultiplier; +S32 LLPipeline::RenderScreenSpaceReflectionGlossySamples; +S32 LLPipeline::RenderBufferVisualization; +LLTrace::EventStatHandle LLPipeline::sStatBatchSize("renderbatchsize"); + +const F32 BACKLIGHT_DAY_MAGNITUDE_OBJECT = 0.1f; +const F32 BACKLIGHT_NIGHT_MAGNITUDE_OBJECT = 0.08f; +const F32 ALPHA_BLEND_CUTOFF = 0.598f; +const F32 DEFERRED_LIGHT_FALLOFF = 0.5f; +const U32 DEFERRED_VB_MASK = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXCOORD0 | LLVertexBuffer::MAP_TEXCOORD1; + +extern S32 gBoxFrame; +//extern bool gHideSelectedObjects; +extern bool gDisplaySwapBuffers; +extern bool gDebugGL; +extern bool gCubeSnapshot; +extern bool gSnapshotNoPost; + +bool gAvatarBacklight = false; + +bool gDebugPipeline = false; +LLPipeline gPipeline; +const LLMatrix4* gGLLastMatrix = NULL; + +LLTrace::BlockTimerStatHandle FTM_RENDER_GEOMETRY("Render Geometry"); +LLTrace::BlockTimerStatHandle FTM_RENDER_GRASS("Grass"); +LLTrace::BlockTimerStatHandle FTM_RENDER_INVISIBLE("Invisible"); +LLTrace::BlockTimerStatHandle FTM_RENDER_SHINY("Shiny"); +LLTrace::BlockTimerStatHandle FTM_RENDER_SIMPLE("Simple"); +LLTrace::BlockTimerStatHandle FTM_RENDER_TERRAIN("Terrain"); +LLTrace::BlockTimerStatHandle FTM_RENDER_TREES("Trees"); +LLTrace::BlockTimerStatHandle FTM_RENDER_UI("UI"); +LLTrace::BlockTimerStatHandle FTM_RENDER_WATER("Water"); +LLTrace::BlockTimerStatHandle FTM_RENDER_WL_SKY("Windlight Sky"); +LLTrace::BlockTimerStatHandle FTM_RENDER_ALPHA("Alpha Objects"); +LLTrace::BlockTimerStatHandle FTM_RENDER_CHARACTERS("Avatars"); +LLTrace::BlockTimerStatHandle FTM_RENDER_BUMP("Bump"); +LLTrace::BlockTimerStatHandle FTM_RENDER_MATERIALS("Render Materials"); +LLTrace::BlockTimerStatHandle FTM_RENDER_FULLBRIGHT("Fullbright"); +LLTrace::BlockTimerStatHandle FTM_RENDER_GLOW("Glow"); +LLTrace::BlockTimerStatHandle FTM_GEO_UPDATE("Geo Update"); +LLTrace::BlockTimerStatHandle FTM_POOLRENDER("RenderPool"); +LLTrace::BlockTimerStatHandle FTM_POOLS("Pools"); +LLTrace::BlockTimerStatHandle FTM_DEFERRED_POOLRENDER("RenderPool (Deferred)"); +LLTrace::BlockTimerStatHandle FTM_DEFERRED_POOLS("Pools (Deferred)"); +LLTrace::BlockTimerStatHandle FTM_POST_DEFERRED_POOLRENDER("RenderPool (Post)"); +LLTrace::BlockTimerStatHandle FTM_POST_DEFERRED_POOLS("Pools (Post)"); +LLTrace::BlockTimerStatHandle FTM_STATESORT("Sort Draw State"); +LLTrace::BlockTimerStatHandle FTM_PIPELINE("Pipeline"); +LLTrace::BlockTimerStatHandle FTM_CLIENT_COPY("Client Copy"); +LLTrace::BlockTimerStatHandle FTM_RENDER_DEFERRED("Deferred Shading"); + +LLTrace::BlockTimerStatHandle FTM_RENDER_UI_HUD("HUD"); +LLTrace::BlockTimerStatHandle FTM_RENDER_UI_3D("3D"); +LLTrace::BlockTimerStatHandle FTM_RENDER_UI_2D("2D"); + +static LLTrace::BlockTimerStatHandle FTM_STATESORT_DRAWABLE("Sort Drawables"); + +static LLStaticHashedString sTint("tint"); +static LLStaticHashedString sAmbiance("ambiance"); +static LLStaticHashedString sAlphaScale("alpha_scale"); +static LLStaticHashedString sNormMat("norm_mat"); +static LLStaticHashedString sOffset("offset"); +static LLStaticHashedString sScreenRes("screenRes"); +static LLStaticHashedString sDelta("delta"); +static LLStaticHashedString sDistFactor("dist_factor"); +static LLStaticHashedString sKern("kern"); +static LLStaticHashedString sKernScale("kern_scale"); + +//---------------------------------------- + +void drawBox(const LLVector4a& c, const LLVector4a& r); +void drawBoxOutline(const LLVector3& pos, const LLVector3& size); +U32 nhpo2(U32 v); +LLVertexBuffer* ll_create_cube_vb(U32 type_mask); + +void display_update_camera(); +//---------------------------------------- + +S32 LLPipeline::sCompiles = 0; + +bool LLPipeline::sPickAvatar = true; +bool LLPipeline::sDynamicLOD = true; +bool LLPipeline::sShowHUDAttachments = true; +bool LLPipeline::sRenderMOAPBeacons = false; +bool LLPipeline::sRenderPhysicalBeacons = true; +bool LLPipeline::sRenderScriptedBeacons = false; +bool LLPipeline::sRenderScriptedTouchBeacons = true; +bool LLPipeline::sRenderParticleBeacons = false; +bool LLPipeline::sRenderSoundBeacons = false; +bool LLPipeline::sRenderBeacons = false; +bool LLPipeline::sRenderHighlight = true; +LLRender::eTexIndex LLPipeline::sRenderHighlightTextureChannel = LLRender::DIFFUSE_MAP; +bool LLPipeline::sForceOldBakedUpload = false; +S32 LLPipeline::sUseOcclusion = 0; +bool LLPipeline::sAutoMaskAlphaDeferred = true; +bool LLPipeline::sAutoMaskAlphaNonDeferred = false; +bool LLPipeline::sRenderTransparentWater = true; +bool LLPipeline::sBakeSunlight = false; +bool LLPipeline::sNoAlpha = false; +bool LLPipeline::sUseFarClip = true; +bool LLPipeline::sShadowRender = false; +bool LLPipeline::sRenderGlow = false; +bool LLPipeline::sReflectionRender = false; +bool LLPipeline::sDistortionRender = false; +bool LLPipeline::sImpostorRender = false; +bool LLPipeline::sImpostorRenderAlphaDepthPass = false; +bool LLPipeline::sUnderWaterRender = false; +bool LLPipeline::sTextureBindTest = false; +bool LLPipeline::sRenderAttachedLights = true; +bool LLPipeline::sRenderAttachedParticles = true; +bool LLPipeline::sRenderDeferred = false; +bool LLPipeline::sReflectionProbesEnabled = false; +S32 LLPipeline::sVisibleLightCount = 0; +bool LLPipeline::sRenderingHUDs; +F32 LLPipeline::sDistortionWaterClipPlaneMargin = 1.0125f; + +// EventHost API LLPipeline listener. +static LLPipelineListener sPipelineListener; + +static LLCullResult* sCull = NULL; + +void validate_framebuffer_object(); + +// Add color attachments for deferred rendering +// target -- RenderTarget to add attachments to +bool addDeferredAttachments(LLRenderTarget& target, bool for_impostor = false) +{ + bool valid = true + && target.addColorAttachment(GL_RGBA) // frag-data[1] specular OR PBR ORM + && target.addColorAttachment(GL_RGBA16F) // frag_data[2] normal+z+fogmask, See: class1\deferred\materialF.glsl & softenlight + && target.addColorAttachment(GL_RGB16F); // frag_data[3] PBR emissive + return valid; +} + +LLPipeline::LLPipeline() : + mBackfaceCull(false), + mMatrixOpCount(0), + mTextureMatrixOps(0), + mNumVisibleNodes(0), + mNumVisibleFaces(0), + mPoissonOffset(0), + + mInitialized(false), + mShadersLoaded(false), + mTransformFeedbackPrimitives(0), + mRenderDebugFeatureMask(0), + mRenderDebugMask(0), + mOldRenderDebugMask(0), + mMeshDirtyQueryObject(0), + mGroupQ1Locked(false), + mResetVertexBuffers(false), + mLastRebuildPool(NULL), + mLightMask(0), + mLightMovingMask(0) +{ + mNoiseMap = 0; + mTrueNoiseMap = 0; + mLightFunc = 0; + + for(U32 i = 0; i < 8; i++) + { + mHWLightColors[i] = LLColor4::black; + } +} + +void LLPipeline::connectRefreshCachedSettingsSafe(const std::string name) +{ + LLPointer cntrl_ptr = gSavedSettings.getControl(name); + if ( cntrl_ptr.isNull() ) + { + LL_WARNS() << "Global setting name not found:" << name << LL_ENDL; + } + else + { + cntrl_ptr->getCommitSignal()->connect(boost::bind(&LLPipeline::refreshCachedSettings)); + } +} + +void LLPipeline::init() +{ + refreshCachedSettings(); + + mRT = &mMainRT; + + gOctreeMaxCapacity = gSavedSettings.getU32("OctreeMaxNodeCapacity"); + gOctreeMinSize = gSavedSettings.getF32("OctreeMinimumNodeSize"); + sDynamicLOD = gSavedSettings.getBOOL("RenderDynamicLOD"); + sRenderAttachedLights = gSavedSettings.getBOOL("RenderAttachedLights"); + sRenderAttachedParticles = gSavedSettings.getBOOL("RenderAttachedParticles"); + + mInitialized = true; + + stop_glerror(); + + //create render pass pools + getPool(LLDrawPool::POOL_ALPHA_PRE_WATER); + getPool(LLDrawPool::POOL_ALPHA_POST_WATER); + getPool(LLDrawPool::POOL_SIMPLE); + getPool(LLDrawPool::POOL_ALPHA_MASK); + getPool(LLDrawPool::POOL_FULLBRIGHT_ALPHA_MASK); + getPool(LLDrawPool::POOL_GRASS); + getPool(LLDrawPool::POOL_FULLBRIGHT); + getPool(LLDrawPool::POOL_BUMP); + getPool(LLDrawPool::POOL_MATERIALS); + getPool(LLDrawPool::POOL_GLOW); + getPool(LLDrawPool::POOL_GLTF_PBR); + getPool(LLDrawPool::POOL_GLTF_PBR_ALPHA_MASK); + + resetFrameStats(); + + if (gSavedSettings.getBOOL("DisableAllRenderFeatures")) + { + clearAllRenderDebugFeatures(); + } + else + { + setAllRenderDebugFeatures(); // By default, all debugging features on + } + clearAllRenderDebugDisplays(); // All debug displays off + + if (gSavedSettings.getBOOL("DisableAllRenderTypes")) + { + clearAllRenderTypes(); + } + else if (gNonInteractive) + { + clearAllRenderTypes(); + } + else + { + setAllRenderTypes(); // By default, all rendering types start enabled + } + + // make sure RenderPerformanceTest persists (hackity hack hack) + // disables non-object rendering (UI, sky, water, etc) + if (gSavedSettings.getBOOL("RenderPerformanceTest")) + { + gSavedSettings.setBOOL("RenderPerformanceTest", false); + gSavedSettings.setBOOL("RenderPerformanceTest", true); + } + + mOldRenderDebugMask = mRenderDebugMask; + + mBackfaceCull = true; + + // Enable features + LLViewerShaderMgr::instance()->setShaders(); + + for (U32 i = 0; i < 2; ++i) + { + mSpotLightFade[i] = 1.f; + } + + if (mCubeVB.isNull()) + { + mCubeVB = ll_create_cube_vb(LLVertexBuffer::MAP_VERTEX); + } + + mDeferredVB = new LLVertexBuffer(DEFERRED_VB_MASK); + mDeferredVB->allocateBuffer(8, 0); + + { + mScreenTriangleVB = new LLVertexBuffer(LLVertexBuffer::MAP_VERTEX); + mScreenTriangleVB->allocateBuffer(3, 0); + LLStrider vert; + mScreenTriangleVB->getVertexStrider(vert); + + vert[0].set(-1, 1, 0); + vert[1].set(-1, -3, 0); + vert[2].set(3, 1, 0); + + mScreenTriangleVB->unmapBuffer(); + } + + // + // Update all settings to trigger a cached settings refresh + // + connectRefreshCachedSettingsSafe("RenderAutoMaskAlphaDeferred"); + connectRefreshCachedSettingsSafe("RenderAutoMaskAlphaNonDeferred"); + connectRefreshCachedSettingsSafe("RenderUseFarClip"); + connectRefreshCachedSettingsSafe("RenderAvatarMaxNonImpostors"); + connectRefreshCachedSettingsSafe("UseOcclusion"); + // DEPRECATED -- connectRefreshCachedSettingsSafe("WindLightUseAtmosShaders"); + // DEPRECATED -- connectRefreshCachedSettingsSafe("RenderDeferred"); + connectRefreshCachedSettingsSafe("RenderDeferredSunWash"); + connectRefreshCachedSettingsSafe("RenderFSAASamples"); + connectRefreshCachedSettingsSafe("RenderResolutionDivisor"); + connectRefreshCachedSettingsSafe("RenderUIBuffer"); + connectRefreshCachedSettingsSafe("RenderShadowDetail"); + connectRefreshCachedSettingsSafe("RenderShadowSplits"); + connectRefreshCachedSettingsSafe("RenderDeferredSSAO"); + connectRefreshCachedSettingsSafe("RenderShadowResolutionScale"); + connectRefreshCachedSettingsSafe("RenderDelayCreation"); + connectRefreshCachedSettingsSafe("RenderAnimateRes"); + connectRefreshCachedSettingsSafe("FreezeTime"); + connectRefreshCachedSettingsSafe("DebugBeaconLineWidth"); + connectRefreshCachedSettingsSafe("RenderHighlightBrightness"); + connectRefreshCachedSettingsSafe("RenderHighlightColor"); + connectRefreshCachedSettingsSafe("RenderHighlightThickness"); + connectRefreshCachedSettingsSafe("RenderSpotLightsInNondeferred"); + connectRefreshCachedSettingsSafe("PreviewAmbientColor"); + connectRefreshCachedSettingsSafe("PreviewDiffuse0"); + connectRefreshCachedSettingsSafe("PreviewSpecular0"); + connectRefreshCachedSettingsSafe("PreviewDiffuse1"); + connectRefreshCachedSettingsSafe("PreviewSpecular1"); + connectRefreshCachedSettingsSafe("PreviewDiffuse2"); + connectRefreshCachedSettingsSafe("PreviewSpecular2"); + connectRefreshCachedSettingsSafe("PreviewDirection0"); + connectRefreshCachedSettingsSafe("PreviewDirection1"); + connectRefreshCachedSettingsSafe("PreviewDirection2"); + connectRefreshCachedSettingsSafe("RenderGlowMaxExtractAlpha"); + connectRefreshCachedSettingsSafe("RenderGlowWarmthAmount"); + connectRefreshCachedSettingsSafe("RenderGlowLumWeights"); + connectRefreshCachedSettingsSafe("RenderGlowWarmthWeights"); + connectRefreshCachedSettingsSafe("RenderGlowResolutionPow"); + connectRefreshCachedSettingsSafe("RenderGlowIterations"); + connectRefreshCachedSettingsSafe("RenderGlowWidth"); + connectRefreshCachedSettingsSafe("RenderGlowStrength"); + connectRefreshCachedSettingsSafe("RenderGlowNoise"); + connectRefreshCachedSettingsSafe("RenderDepthOfField"); + connectRefreshCachedSettingsSafe("RenderDepthOfFieldInEditMode"); + connectRefreshCachedSettingsSafe("CameraFocusTransitionTime"); + connectRefreshCachedSettingsSafe("CameraFNumber"); + connectRefreshCachedSettingsSafe("CameraFocalLength"); + connectRefreshCachedSettingsSafe("CameraFieldOfView"); + connectRefreshCachedSettingsSafe("RenderShadowNoise"); + connectRefreshCachedSettingsSafe("RenderShadowBlurSize"); + connectRefreshCachedSettingsSafe("RenderSSAOScale"); + connectRefreshCachedSettingsSafe("RenderSSAOMaxScale"); + connectRefreshCachedSettingsSafe("RenderSSAOFactor"); + connectRefreshCachedSettingsSafe("RenderSSAOEffect"); + connectRefreshCachedSettingsSafe("RenderShadowOffsetError"); + connectRefreshCachedSettingsSafe("RenderShadowBiasError"); + connectRefreshCachedSettingsSafe("RenderShadowOffset"); + connectRefreshCachedSettingsSafe("RenderShadowBias"); + connectRefreshCachedSettingsSafe("RenderSpotShadowOffset"); + connectRefreshCachedSettingsSafe("RenderSpotShadowBias"); + connectRefreshCachedSettingsSafe("RenderEdgeDepthCutoff"); + connectRefreshCachedSettingsSafe("RenderEdgeNormCutoff"); + connectRefreshCachedSettingsSafe("RenderShadowGaussian"); + connectRefreshCachedSettingsSafe("RenderShadowBlurDistFactor"); + connectRefreshCachedSettingsSafe("RenderDeferredAtmospheric"); + connectRefreshCachedSettingsSafe("RenderHighlightFadeTime"); + connectRefreshCachedSettingsSafe("RenderFarClip"); + connectRefreshCachedSettingsSafe("RenderShadowSplitExponent"); + connectRefreshCachedSettingsSafe("RenderShadowErrorCutoff"); + connectRefreshCachedSettingsSafe("RenderShadowFOVCutoff"); + connectRefreshCachedSettingsSafe("CameraOffset"); + connectRefreshCachedSettingsSafe("CameraMaxCoF"); + connectRefreshCachedSettingsSafe("CameraDoFResScale"); + connectRefreshCachedSettingsSafe("RenderAutoHideSurfaceAreaLimit"); + connectRefreshCachedSettingsSafe("RenderScreenSpaceReflections"); + connectRefreshCachedSettingsSafe("RenderScreenSpaceReflectionIterations"); + connectRefreshCachedSettingsSafe("RenderScreenSpaceReflectionRayStep"); + connectRefreshCachedSettingsSafe("RenderScreenSpaceReflectionDistanceBias"); + connectRefreshCachedSettingsSafe("RenderScreenSpaceReflectionDepthRejectBias"); + connectRefreshCachedSettingsSafe("RenderScreenSpaceReflectionAdaptiveStepMultiplier"); + connectRefreshCachedSettingsSafe("RenderScreenSpaceReflectionGlossySamples"); + connectRefreshCachedSettingsSafe("RenderBufferVisualization"); + gSavedSettings.getControl("RenderAutoHideSurfaceAreaLimit")->getCommitSignal()->connect(boost::bind(&LLPipeline::refreshCachedSettings)); +} + +LLPipeline::~LLPipeline() +{ +} + +void LLPipeline::cleanup() +{ + assertInitialized(); + + mGroupQ1.clear() ; + + for(pool_set_t::iterator iter = mPools.begin(); + iter != mPools.end(); ) + { + pool_set_t::iterator curiter = iter++; + LLDrawPool* poolp = *curiter; + if (poolp->isFacePool()) + { + LLFacePool* face_pool = (LLFacePool*) poolp; + if (face_pool->mReferences.empty()) + { + mPools.erase(curiter); + removeFromQuickLookup( poolp ); + delete poolp; + } + } + else + { + mPools.erase(curiter); + removeFromQuickLookup( poolp ); + delete poolp; + } + } + + if (!mTerrainPools.empty()) + { + LL_WARNS() << "Terrain Pools not cleaned up" << LL_ENDL; + } + if (!mTreePools.empty()) + { + LL_WARNS() << "Tree Pools not cleaned up" << LL_ENDL; + } + + delete mAlphaPoolPreWater; + mAlphaPoolPreWater = nullptr; + delete mAlphaPoolPostWater; + mAlphaPoolPostWater = nullptr; + delete mSkyPool; + mSkyPool = NULL; + delete mTerrainPool; + mTerrainPool = NULL; + delete mWaterPool; + mWaterPool = NULL; + delete mSimplePool; + mSimplePool = NULL; + delete mFullbrightPool; + mFullbrightPool = NULL; + delete mGlowPool; + mGlowPool = NULL; + delete mBumpPool; + mBumpPool = NULL; + // don't delete wl sky pool it was handled above in the for loop + //delete mWLSkyPool; + mWLSkyPool = NULL; + + releaseGLBuffers(); + + mFaceSelectImagep = NULL; + + mMovedList.clear(); + mMovedBridge.clear(); + mShiftList.clear(); + + mInitialized = false; + + mDeferredVB = NULL; + mScreenTriangleVB = nullptr; + + mCubeVB = NULL; + + mReflectionMapManager.cleanup(); +} + +//============================================================================ + +void LLPipeline::destroyGL() +{ + stop_glerror(); + unloadShaders(); + mHighlightFaces.clear(); + + resetDrawOrders(); + + releaseGLBuffers(); + + if (mMeshDirtyQueryObject) + { + glDeleteQueries(1, &mMeshDirtyQueryObject); + mMeshDirtyQueryObject = 0; + } +} + +void LLPipeline::requestResizeScreenTexture() +{ + gResizeScreenTexture = true; +} + +void LLPipeline::requestResizeShadowTexture() +{ + gResizeShadowTexture = true; +} + +void LLPipeline::resizeShadowTexture() +{ + releaseSunShadowTargets(); + releaseSpotShadowTargets(); + allocateShadowBuffer(mRT->width, mRT->height); + gResizeShadowTexture = false; +} + +void LLPipeline::resizeScreenTexture() +{ + if (gPipeline.shadersLoaded()) + { + GLuint resX = gViewerWindow->getWorldViewWidthRaw(); + GLuint resY = gViewerWindow->getWorldViewHeightRaw(); + + if (gResizeScreenTexture || (resX != mRT->screen.getWidth()) || (resY != mRT->screen.getHeight())) + { + releaseScreenBuffers(); + releaseSunShadowTargets(); + releaseSpotShadowTargets(); + allocateScreenBuffer(resX,resY); + gResizeScreenTexture = false; + } + } +} + +bool LLPipeline::allocateScreenBuffer(U32 resX, U32 resY) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY; + eFBOStatus ret = doAllocateScreenBuffer(resX, resY); + + return ret == FBO_SUCCESS_FULLRES; +} + + +LLPipeline::eFBOStatus LLPipeline::doAllocateScreenBuffer(U32 resX, U32 resY) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY; + // try to allocate screen buffers at requested resolution and samples + // - on failure, shrink number of samples and try again + // - if not multisampled, shrink resolution and try again (favor X resolution over Y) + // Make sure to call "releaseScreenBuffers" after each failure to cleanup the partially loaded state + + // refresh cached settings here to protect against inconsistent event handling order + refreshCachedSettings(); + + U32 samples = RenderFSAASamples; + + eFBOStatus ret = FBO_SUCCESS_FULLRES; + if (!allocateScreenBuffer(resX, resY, samples)) + { + //failed to allocate at requested specification, return false + ret = FBO_FAILURE; + + releaseScreenBuffers(); + //reduce number of samples + while (samples > 0) + { + samples /= 2; + if (allocateScreenBuffer(resX, resY, samples)) + { //success + return FBO_SUCCESS_LOWRES; + } + releaseScreenBuffers(); + } + + samples = 0; + + //reduce resolution + while (resY > 0 && resX > 0) + { + resY /= 2; + if (allocateScreenBuffer(resX, resY, samples)) + { + return FBO_SUCCESS_LOWRES; + } + releaseScreenBuffers(); + + resX /= 2; + if (allocateScreenBuffer(resX, resY, samples)) + { + return FBO_SUCCESS_LOWRES; + } + releaseScreenBuffers(); + } + + LL_WARNS() << "Unable to allocate screen buffer at any resolution!" << LL_ENDL; + } + + return ret; +} + +bool LLPipeline::allocateScreenBuffer(U32 resX, U32 resY, U32 samples) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY; + if (mRT == &mMainRT && sReflectionProbesEnabled) + { // hacky -- allocate auxillary buffer + gCubeSnapshot = true; + mReflectionMapManager.initReflectionMaps(); + mRT = &mAuxillaryRT; + U32 res = mReflectionMapManager.mProbeResolution * 4; //multiply by 4 because probes will be 16x super sampled + allocateScreenBuffer(res, res, samples); + mRT = &mMainRT; + gCubeSnapshot = false; + } + + // remember these dimensions + mRT->width = resX; + mRT->height = resY; + + U32 res_mod = RenderResolutionDivisor; + + if (res_mod > 1 && res_mod < resX && res_mod < resY) + { + resX /= res_mod; + resY /= res_mod; + } + + //water reflection texture (always needed as scratch space whether or not transparent water is enabled) + mWaterDis.allocate(resX, resY, GL_RGBA16F, true); + + if (RenderUIBuffer) + { + if (!mRT->uiScreen.allocate(resX,resY, GL_RGBA)) + { + return false; + } + } + + S32 shadow_detail = RenderShadowDetail; + bool ssao = RenderDeferredSSAO; + + //allocate deferred rendering color buffers + if (!mRT->deferredScreen.allocate(resX, resY, GL_RGBA, true)) return false; + if (!addDeferredAttachments(mRT->deferredScreen)) return false; + + GLuint screenFormat = GL_RGBA16F; + + if (!mRT->screen.allocate(resX, resY, screenFormat)) return false; + + mRT->deferredScreen.shareDepthBuffer(mRT->screen); + + if (samples > 0) + { + if (!mRT->fxaaBuffer.allocate(resX, resY, GL_RGBA)) return false; + } + else + { + mRT->fxaaBuffer.release(); + } + + if (shadow_detail > 0 || ssao || RenderDepthOfField || samples > 0) + { //only need mRT->deferredLight for shadows OR ssao OR dof OR fxaa + if (!mRT->deferredLight.allocate(resX, resY, GL_RGBA16F)) return false; + } + else + { + mRT->deferredLight.release(); + } + + allocateShadowBuffer(resX, resY); + + if (!gCubeSnapshot && RenderScreenSpaceReflections) // hack to not allocate mSceneMap for cube snapshots + { + mSceneMap.allocate(resX, resY, GL_RGB, true); + } + + const bool post_hdr = gSavedSettings.getBOOL("RenderPostProcessingHDR"); + const U32 post_color_fmt = post_hdr ? GL_RGBA16F : GL_RGBA; + mPostMap.allocate(resX, resY, post_color_fmt); + + //HACK make screenbuffer allocations start failing after 30 seconds + if (gSavedSettings.getBOOL("SimulateFBOFailure")) + { + return false; + } + + gGL.getTexUnit(0)->disable(); + + stop_glerror(); + + return true; +} + +// must be even to avoid a stripe in the horizontal shadow blur +inline U32 BlurHappySize(U32 x, F32 scale) { return U32( x * scale + 16.0f) & ~0xF; } + +bool LLPipeline::allocateShadowBuffer(U32 resX, U32 resY) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY; + S32 shadow_detail = RenderShadowDetail; + + F32 scale = llmax(0.f, RenderShadowResolutionScale); + U32 sun_shadow_map_width = BlurHappySize(resX, scale); + U32 sun_shadow_map_height = BlurHappySize(resY, scale); + + if (shadow_detail > 0) + { //allocate 4 sun shadow maps + for (U32 i = 0; i < 4; i++) + { + if (!mRT->shadow[i].allocate(sun_shadow_map_width, sun_shadow_map_height, 0, true)) + { + return false; + } + } + } + else + { + for (U32 i = 0; i < 4; i++) + { + releaseSunShadowTarget(i); + } + } + + if (!gCubeSnapshot) // hack to not allocate spot shadow maps during ReflectionMapManager init + { + U32 width = (U32)(resX * scale); + U32 height = width; + + if (shadow_detail > 1) + { //allocate two spot shadow maps + U32 spot_shadow_map_width = width; + U32 spot_shadow_map_height = height; + for (U32 i = 0; i < 2; i++) + { + if (!mSpotShadow[i].allocate(spot_shadow_map_width, spot_shadow_map_height, 0, true)) + { + return false; + } + } + } + else + { + releaseSpotShadowTargets(); + } + } + + + // set up shadow map filtering and compare modes + if (shadow_detail > 0) + { + for (U32 i = 0; i < 4; i++) + { + LLRenderTarget* shadow_target = getSunShadowTarget(i); + if (shadow_target) + { + gGL.getTexUnit(0)->bind(getSunShadowTarget(i), true); + gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_ANISOTROPIC); + gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_CLAMP); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); + } + } + } + + if (shadow_detail > 1 && !gCubeSnapshot) + { + for (U32 i = 0; i < 2; i++) + { + LLRenderTarget* shadow_target = getSpotShadowTarget(i); + if (shadow_target) + { + gGL.getTexUnit(0)->bind(shadow_target, true); + gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_ANISOTROPIC); + gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_CLAMP); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); + } + } + } + + return true; +} + +//static +void LLPipeline::updateRenderTransparentWater() +{ + sRenderTransparentWater = gSavedSettings.getBOOL("RenderTransparentWater"); +} + +// static +void LLPipeline::refreshCachedSettings() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY; + LLPipeline::sAutoMaskAlphaDeferred = gSavedSettings.getBOOL("RenderAutoMaskAlphaDeferred"); + LLPipeline::sAutoMaskAlphaNonDeferred = gSavedSettings.getBOOL("RenderAutoMaskAlphaNonDeferred"); + LLPipeline::sUseFarClip = gSavedSettings.getBOOL("RenderUseFarClip"); + LLVOAvatar::sMaxNonImpostors = gSavedSettings.getU32("RenderAvatarMaxNonImpostors"); + LLVOAvatar::updateImpostorRendering(LLVOAvatar::sMaxNonImpostors); + + LLPipeline::sUseOcclusion = + (!gUseWireframe + && LLFeatureManager::getInstance()->isFeatureAvailable("UseOcclusion") + && gSavedSettings.getBOOL("UseOcclusion")) ? 2 : 0; + + WindLightUseAtmosShaders = true; // DEPRECATED -- gSavedSettings.getBOOL("WindLightUseAtmosShaders"); + RenderDeferred = true; // DEPRECATED -- gSavedSettings.getBOOL("RenderDeferred"); + RenderDeferredSunWash = gSavedSettings.getF32("RenderDeferredSunWash"); + RenderFSAASamples = LLFeatureManager::getInstance()->isFeatureAvailable("RenderFSAASamples") ? gSavedSettings.getU32("RenderFSAASamples") : 0; + RenderResolutionDivisor = gSavedSettings.getU32("RenderResolutionDivisor"); + RenderUIBuffer = gSavedSettings.getBOOL("RenderUIBuffer"); + RenderShadowDetail = gSavedSettings.getS32("RenderShadowDetail"); + RenderShadowSplits = gSavedSettings.getS32("RenderShadowSplits"); + RenderDeferredSSAO = gSavedSettings.getBOOL("RenderDeferredSSAO"); + RenderShadowResolutionScale = gSavedSettings.getF32("RenderShadowResolutionScale"); + RenderDelayCreation = gSavedSettings.getBOOL("RenderDelayCreation"); + RenderAnimateRes = gSavedSettings.getBOOL("RenderAnimateRes"); + FreezeTime = gSavedSettings.getBOOL("FreezeTime"); + DebugBeaconLineWidth = gSavedSettings.getS32("DebugBeaconLineWidth"); + RenderHighlightBrightness = gSavedSettings.getF32("RenderHighlightBrightness"); + RenderHighlightColor = gSavedSettings.getColor4("RenderHighlightColor"); + RenderHighlightThickness = gSavedSettings.getF32("RenderHighlightThickness"); + RenderSpotLightsInNondeferred = gSavedSettings.getBOOL("RenderSpotLightsInNondeferred"); + PreviewAmbientColor = gSavedSettings.getColor4("PreviewAmbientColor"); + PreviewDiffuse0 = gSavedSettings.getColor4("PreviewDiffuse0"); + PreviewSpecular0 = gSavedSettings.getColor4("PreviewSpecular0"); + PreviewDiffuse1 = gSavedSettings.getColor4("PreviewDiffuse1"); + PreviewSpecular1 = gSavedSettings.getColor4("PreviewSpecular1"); + PreviewDiffuse2 = gSavedSettings.getColor4("PreviewDiffuse2"); + PreviewSpecular2 = gSavedSettings.getColor4("PreviewSpecular2"); + PreviewDirection0 = gSavedSettings.getVector3("PreviewDirection0"); + PreviewDirection1 = gSavedSettings.getVector3("PreviewDirection1"); + PreviewDirection2 = gSavedSettings.getVector3("PreviewDirection2"); + RenderGlowMaxExtractAlpha = gSavedSettings.getF32("RenderGlowMaxExtractAlpha"); + RenderGlowWarmthAmount = gSavedSettings.getF32("RenderGlowWarmthAmount"); + RenderGlowLumWeights = gSavedSettings.getVector3("RenderGlowLumWeights"); + RenderGlowWarmthWeights = gSavedSettings.getVector3("RenderGlowWarmthWeights"); + RenderGlowResolutionPow = gSavedSettings.getS32("RenderGlowResolutionPow"); + RenderGlowIterations = gSavedSettings.getS32("RenderGlowIterations"); + RenderGlowWidth = gSavedSettings.getF32("RenderGlowWidth"); + RenderGlowStrength = gSavedSettings.getF32("RenderGlowStrength"); + RenderGlowNoise = gSavedSettings.getBOOL("RenderGlowNoise"); + RenderDepthOfField = gSavedSettings.getBOOL("RenderDepthOfField"); + RenderDepthOfFieldInEditMode = gSavedSettings.getBOOL("RenderDepthOfFieldInEditMode"); + CameraFocusTransitionTime = gSavedSettings.getF32("CameraFocusTransitionTime"); + CameraFNumber = gSavedSettings.getF32("CameraFNumber"); + CameraFocalLength = gSavedSettings.getF32("CameraFocalLength"); + CameraFieldOfView = gSavedSettings.getF32("CameraFieldOfView"); + RenderShadowNoise = gSavedSettings.getF32("RenderShadowNoise"); + RenderShadowBlurSize = gSavedSettings.getF32("RenderShadowBlurSize"); + RenderSSAOScale = gSavedSettings.getF32("RenderSSAOScale"); + RenderSSAOMaxScale = gSavedSettings.getU32("RenderSSAOMaxScale"); + RenderSSAOFactor = gSavedSettings.getF32("RenderSSAOFactor"); + RenderSSAOEffect = gSavedSettings.getVector3("RenderSSAOEffect"); + RenderShadowOffsetError = gSavedSettings.getF32("RenderShadowOffsetError"); + RenderShadowBiasError = gSavedSettings.getF32("RenderShadowBiasError"); + RenderShadowOffset = gSavedSettings.getF32("RenderShadowOffset"); + RenderShadowBias = gSavedSettings.getF32("RenderShadowBias"); + RenderSpotShadowOffset = gSavedSettings.getF32("RenderSpotShadowOffset"); + RenderSpotShadowBias = gSavedSettings.getF32("RenderSpotShadowBias"); + RenderEdgeDepthCutoff = gSavedSettings.getF32("RenderEdgeDepthCutoff"); + RenderEdgeNormCutoff = gSavedSettings.getF32("RenderEdgeNormCutoff"); + RenderShadowGaussian = gSavedSettings.getVector3("RenderShadowGaussian"); + RenderShadowBlurDistFactor = gSavedSettings.getF32("RenderShadowBlurDistFactor"); + RenderDeferredAtmospheric = gSavedSettings.getBOOL("RenderDeferredAtmospheric"); + RenderHighlightFadeTime = gSavedSettings.getF32("RenderHighlightFadeTime"); + RenderFarClip = gSavedSettings.getF32("RenderFarClip"); + RenderShadowSplitExponent = gSavedSettings.getVector3("RenderShadowSplitExponent"); + RenderShadowErrorCutoff = gSavedSettings.getF32("RenderShadowErrorCutoff"); + RenderShadowFOVCutoff = gSavedSettings.getF32("RenderShadowFOVCutoff"); + CameraOffset = gSavedSettings.getBOOL("CameraOffset"); + CameraMaxCoF = gSavedSettings.getF32("CameraMaxCoF"); + CameraDoFResScale = gSavedSettings.getF32("CameraDoFResScale"); + RenderAutoHideSurfaceAreaLimit = gSavedSettings.getF32("RenderAutoHideSurfaceAreaLimit"); + RenderScreenSpaceReflections = gSavedSettings.getBOOL("RenderScreenSpaceReflections"); + RenderScreenSpaceReflectionIterations = gSavedSettings.getS32("RenderScreenSpaceReflectionIterations"); + RenderScreenSpaceReflectionRayStep = gSavedSettings.getF32("RenderScreenSpaceReflectionRayStep"); + RenderScreenSpaceReflectionDistanceBias = gSavedSettings.getF32("RenderScreenSpaceReflectionDistanceBias"); + RenderScreenSpaceReflectionDepthRejectBias = gSavedSettings.getF32("RenderScreenSpaceReflectionDepthRejectBias"); + RenderScreenSpaceReflectionAdaptiveStepMultiplier = gSavedSettings.getF32("RenderScreenSpaceReflectionAdaptiveStepMultiplier"); + RenderScreenSpaceReflectionGlossySamples = gSavedSettings.getS32("RenderScreenSpaceReflectionGlossySamples"); + RenderBufferVisualization = gSavedSettings.getS32("RenderBufferVisualization"); + sReflectionProbesEnabled = LLFeatureManager::getInstance()->isFeatureAvailable("RenderReflectionsEnabled") && gSavedSettings.getBOOL("RenderReflectionsEnabled"); + RenderSpotLight = nullptr; + + if (gNonInteractive) + { + LLVOAvatar::sMaxNonImpostors = 1; + LLVOAvatar::updateImpostorRendering(LLVOAvatar::sMaxNonImpostors); + } +} + +void LLPipeline::releaseGLBuffers() +{ + assertInitialized(); + + if (mNoiseMap) + { + LLImageGL::deleteTextures(1, &mNoiseMap); + mNoiseMap = 0; + } + + if (mTrueNoiseMap) + { + LLImageGL::deleteTextures(1, &mTrueNoiseMap); + mTrueNoiseMap = 0; + } + + releaseLUTBuffers(); + + mWaterDis.release(); + mBake.release(); + + mSceneMap.release(); + + mPostMap.release(); + + for (U32 i = 0; i < 3; i++) + { + mGlow[i].release(); + } + + releaseScreenBuffers(); + + gBumpImageList.destroyGL(); + LLVOAvatar::resetImpostors(); +} + +void LLPipeline::releaseLUTBuffers() +{ + if (mLightFunc) + { + LLImageGL::deleteTextures(1, &mLightFunc); + mLightFunc = 0; + } + + mPbrBrdfLut.release(); + + mExposureMap.release(); + mLuminanceMap.release(); + mLastExposure.release(); + +} + +void LLPipeline::releaseShadowBuffers() +{ + releaseSunShadowTargets(); + releaseSpotShadowTargets(); +} + +void LLPipeline::releaseScreenBuffers() +{ + mRT->uiScreen.release(); + mRT->screen.release(); + mRT->fxaaBuffer.release(); + mRT->deferredScreen.release(); + mRT->deferredLight.release(); +} + +void LLPipeline::releaseSunShadowTarget(U32 index) +{ + llassert(index < 4); + mRT->shadow[index].release(); +} + +void LLPipeline::releaseSunShadowTargets() +{ + for (U32 i = 0; i < 4; i++) + { + releaseSunShadowTarget(i); + } +} + +void LLPipeline::releaseSpotShadowTargets() +{ + if (!gCubeSnapshot) // hack to avoid freeing spot shadows during ReflectionMapManager init + { + for (U32 i = 0; i < 2; i++) + { + mSpotShadow[i].release(); + } + } +} + +void LLPipeline::createGLBuffers() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + stop_glerror(); + assertInitialized(); + + // Use FBO for bake tex + mBake.allocate(512, 512, GL_RGBA, true); // SL-12781 Build > Upload > Model; 3D Preview + + stop_glerror(); + + GLuint resX = gViewerWindow->getWorldViewWidthRaw(); + GLuint resY = gViewerWindow->getWorldViewHeightRaw(); + + // allocate screen space glow buffers + const U32 glow_res = llmax(1, llmin(512, 1 << gSavedSettings.getS32("RenderGlowResolutionPow"))); + const bool glow_hdr = gSavedSettings.getBOOL("RenderGlowHDR"); + const U32 glow_color_fmt = glow_hdr ? GL_RGBA16F : GL_RGBA; + for (U32 i = 0; i < 3; i++) + { + mGlow[i].allocate(512, glow_res, glow_color_fmt); + } + + allocateScreenBuffer(resX, resY); + mRT->width = 0; + mRT->height = 0; + + + if (!mNoiseMap) + { + const U32 noiseRes = 128; + LLVector3 noise[noiseRes*noiseRes]; + + F32 scaler = gSavedSettings.getF32("RenderDeferredNoise")/100.f; + for (U32 i = 0; i < noiseRes*noiseRes; ++i) + { + noise[i] = LLVector3(ll_frand()-0.5f, ll_frand()-0.5f, 0.f); + noise[i].normVec(); + noise[i].mV[2] = ll_frand()*scaler+1.f-scaler/2.f; + } + + LLImageGL::generateTextures(1, &mNoiseMap); + + gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, mNoiseMap); + LLImageGL::setManualImage(LLTexUnit::getInternalType(LLTexUnit::TT_TEXTURE), 0, GL_RGB16F, noiseRes, noiseRes, GL_RGB, GL_FLOAT, noise, false); + gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT); + } + + if (!mTrueNoiseMap) + { + const U32 noiseRes = 128; + F32 noise[noiseRes*noiseRes*3]; + for (U32 i = 0; i < noiseRes*noiseRes*3; i++) + { + noise[i] = ll_frand()*2.0-1.0; + } + + LLImageGL::generateTextures(1, &mTrueNoiseMap); + gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, mTrueNoiseMap); + LLImageGL::setManualImage(LLTexUnit::getInternalType(LLTexUnit::TT_TEXTURE), 0, GL_RGB16F, noiseRes, noiseRes, GL_RGB,GL_FLOAT, noise, false); + gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT); + } + + createLUTBuffers(); + + gBumpImageList.restoreGL(); +} + +F32 lerpf(F32 a, F32 b, F32 w) +{ + return a + w * (b - a); +} + +void LLPipeline::createLUTBuffers() +{ + if (!mLightFunc) + { + U32 lightResX = gSavedSettings.getU32("RenderSpecularResX"); + U32 lightResY = gSavedSettings.getU32("RenderSpecularResY"); + F32* ls = new F32[lightResX*lightResY]; + F32 specExp = gSavedSettings.getF32("RenderSpecularExponent"); + // Calculate the (normalized) blinn-phong specular lookup texture. (with a few tweaks) + for (U32 y = 0; y < lightResY; ++y) + { + for (U32 x = 0; x < lightResX; ++x) + { + ls[y*lightResX+x] = 0; + F32 sa = (F32) x/(lightResX-1); + F32 spec = (F32) y/(lightResY-1); + F32 n = spec * spec * specExp; + + // Nothing special here. Just your typical blinn-phong term. + spec = powf(sa, n); + + // Apply our normalization function. + // Note: This is the full equation that applies the full normalization curve, not an approximation. + // This is fine, given we only need to create our LUT once per buffer initialization. + spec *= (((n + 2) * (n + 4)) / (8 * F_PI * (powf(2, -n/2) + n))); + + // Since we use R16F, we no longer have a dynamic range issue we need to work around here. + // Though some older drivers may not like this, newer drivers shouldn't have this problem. + ls[y*lightResX+x] = spec; + } + } + + U32 pix_format = GL_R16F; +#if LL_DARWIN + // Need to work around limited precision with 10.6.8 and older drivers + // + pix_format = GL_R32F; +#endif + LLImageGL::generateTextures(1, &mLightFunc); + gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, mLightFunc); + LLImageGL::setManualImage(LLTexUnit::getInternalType(LLTexUnit::TT_TEXTURE), 0, pix_format, lightResX, lightResY, GL_RED, GL_FLOAT, ls, false); + gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_CLAMP); + gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_TRILINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + + delete [] ls; + } + + mPbrBrdfLut.allocate(512, 512, GL_RG16F); + mPbrBrdfLut.bindTarget(); + gDeferredGenBrdfLutProgram.bind(); + + gGL.begin(LLRender::TRIANGLE_STRIP); + gGL.vertex2f(-1, -1); + gGL.vertex2f(-1, 1); + gGL.vertex2f(1, -1); + gGL.vertex2f(1, 1); + gGL.end(); + gGL.flush(); + + gDeferredGenBrdfLutProgram.unbind(); + mPbrBrdfLut.flush(); + + mExposureMap.allocate(1, 1, GL_R16F); + mExposureMap.bindTarget(); + glClearColor(1, 1, 1, 0); + mExposureMap.clear(); + glClearColor(0, 0, 0, 0); + mExposureMap.flush(); + + mLuminanceMap.allocate(256, 256, GL_R16F, false, LLTexUnit::TT_TEXTURE, LLTexUnit::TMG_AUTO); + + mLastExposure.allocate(1, 1, GL_R16F); +} + + +void LLPipeline::restoreGL() +{ + assertInitialized(); + + LLViewerShaderMgr::instance()->setShaders(); + + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* region = *iter; + for (U32 i = 0; i < LLViewerRegion::NUM_PARTITIONS; i++) + { + LLSpatialPartition* part = region->getSpatialPartition(i); + if (part) + { + part->restoreGL(); + } + } + } +} + +bool LLPipeline::shadersLoaded() +{ + return (assertInitialized() && mShadersLoaded); +} + +bool LLPipeline::canUseWindLightShaders() const +{ + return true; +} + +bool LLPipeline::canUseAntiAliasing() const +{ + return true; +} + +void LLPipeline::unloadShaders() +{ + LLViewerShaderMgr::instance()->unloadShaders(); + mShadersLoaded = false; +} + +void LLPipeline::assertInitializedDoError() +{ + LL_ERRS() << "LLPipeline used when uninitialized." << LL_ENDL; +} + +//============================================================================ + +void LLPipeline::enableShadows(const bool enable_shadows) +{ + //should probably do something here to wrangle shadows.... +} + +class LLOctreeDirtyTexture : public OctreeTraveler +{ +public: + const std::set& mTextures; + + LLOctreeDirtyTexture(const std::set& textures) : mTextures(textures) { } + + virtual void visit(const OctreeNode* node) + { + LLSpatialGroup* group = (LLSpatialGroup*) node->getListener(0); + + if (!group->hasState(LLSpatialGroup::GEOM_DIRTY) && !group->isEmpty()) + { + for (LLSpatialGroup::draw_map_t::iterator i = group->mDrawMap.begin(); i != group->mDrawMap.end(); ++i) + { + for (LLSpatialGroup::drawmap_elem_t::iterator j = i->second.begin(); j != i->second.end(); ++j) + { + LLDrawInfo* params = *j; + LLViewerFetchedTexture* tex = LLViewerTextureManager::staticCastToFetchedTexture(params->mTexture); + if (tex && mTextures.find(tex) != mTextures.end()) + { + group->setState(LLSpatialGroup::GEOM_DIRTY); + } + } + } + } + + for (LLSpatialGroup::bridge_list_t::iterator i = group->mBridgeList.begin(); i != group->mBridgeList.end(); ++i) + { + LLSpatialBridge* bridge = *i; + traverse(bridge->mOctree); + } + } +}; + +// Called when a texture changes # of channels (causes faces to move to alpha pool) +void LLPipeline::dirtyPoolObjectTextures(const std::set& textures) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + assertInitialized(); + + // *TODO: This is inefficient and causes frame spikes; need a better way to do this + // Most of the time is spent in dirty.traverse. + + for (pool_set_t::iterator iter = mPools.begin(); iter != mPools.end(); ++iter) + { + LLDrawPool *poolp = *iter; + if (poolp->isFacePool()) + { + ((LLFacePool*) poolp)->dirtyTextures(textures); + } + } + + LLOctreeDirtyTexture dirty(textures); + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* region = *iter; + for (U32 i = 0; i < LLViewerRegion::NUM_PARTITIONS; i++) + { + LLSpatialPartition* part = region->getSpatialPartition(i); + if (part) + { + dirty.traverse(part->mOctree); + } + } + } +} + +LLDrawPool *LLPipeline::findPool(const U32 type, LLViewerTexture *tex0) +{ + assertInitialized(); + + LLDrawPool *poolp = NULL; + switch( type ) + { + case LLDrawPool::POOL_SIMPLE: + poolp = mSimplePool; + break; + + case LLDrawPool::POOL_GRASS: + poolp = mGrassPool; + break; + + case LLDrawPool::POOL_ALPHA_MASK: + poolp = mAlphaMaskPool; + break; + + case LLDrawPool::POOL_FULLBRIGHT_ALPHA_MASK: + poolp = mFullbrightAlphaMaskPool; + break; + + case LLDrawPool::POOL_FULLBRIGHT: + poolp = mFullbrightPool; + break; + + case LLDrawPool::POOL_GLOW: + poolp = mGlowPool; + break; + + case LLDrawPool::POOL_TREE: + poolp = get_if_there(mTreePools, (uintptr_t)tex0, (LLDrawPool*)0 ); + break; + + case LLDrawPool::POOL_TERRAIN: + poolp = get_if_there(mTerrainPools, (uintptr_t)tex0, (LLDrawPool*)0 ); + break; + + case LLDrawPool::POOL_BUMP: + poolp = mBumpPool; + break; + case LLDrawPool::POOL_MATERIALS: + poolp = mMaterialsPool; + break; + case LLDrawPool::POOL_ALPHA_PRE_WATER: + poolp = mAlphaPoolPreWater; + break; + case LLDrawPool::POOL_ALPHA_POST_WATER: + poolp = mAlphaPoolPostWater; + break; + + case LLDrawPool::POOL_AVATAR: + case LLDrawPool::POOL_CONTROL_AV: + break; // Do nothing + + case LLDrawPool::POOL_SKY: + poolp = mSkyPool; + break; + + case LLDrawPool::POOL_WATER: + poolp = mWaterPool; + break; + + case LLDrawPool::POOL_WL_SKY: + poolp = mWLSkyPool; + break; + + case LLDrawPool::POOL_GLTF_PBR: + poolp = mPBROpaquePool; + break; + case LLDrawPool::POOL_GLTF_PBR_ALPHA_MASK: + poolp = mPBRAlphaMaskPool; + break; + + default: + llassert(0); + LL_ERRS() << "Invalid Pool Type in LLPipeline::findPool() type=" << type << LL_ENDL; + break; + } + + return poolp; +} + + +LLDrawPool *LLPipeline::getPool(const U32 type, LLViewerTexture *tex0) +{ + LLDrawPool *poolp = findPool(type, tex0); + if (poolp) + { + return poolp; + } + + LLDrawPool *new_poolp = LLDrawPool::createPool(type, tex0); + addPool( new_poolp ); + + return new_poolp; +} + + +// static +LLDrawPool* LLPipeline::getPoolFromTE(const LLTextureEntry* te, LLViewerTexture* imagep) +{ + U32 type = getPoolTypeFromTE(te, imagep); + return gPipeline.getPool(type, imagep); +} + +//static +U32 LLPipeline::getPoolTypeFromTE(const LLTextureEntry* te, LLViewerTexture* imagep) +{ + if (!te || !imagep) + { + return 0; + } + + LLMaterial* mat = te->getMaterialParams().get(); + LLGLTFMaterial* gltf_mat = te->getGLTFRenderMaterial(); + + bool color_alpha = te->getColor().mV[3] < 0.999f; + bool alpha = color_alpha; + if (imagep) + { + alpha = alpha || (imagep->getComponents() == 4 && imagep->getType() != LLViewerTexture::MEDIA_TEXTURE) || (imagep->getComponents() == 2); + } + + if (alpha && mat) + { + switch (mat->getDiffuseAlphaMode()) + { + case 1: + alpha = true; // Material's alpha mode is set to blend. Toss it into the alpha draw pool. + break; + case 0: //alpha mode set to none, never go to alpha pool + case 3: //alpha mode set to emissive, never go to alpha pool + alpha = color_alpha; + break; + default: //alpha mode set to "mask", go to alpha pool if fullbright + alpha = color_alpha; // Material's alpha mode is set to none, mask, or emissive. Toss it into the opaque material draw pool. + break; + } + } + + if (alpha || (gltf_mat && gltf_mat->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_BLEND)) + { + return LLDrawPool::POOL_ALPHA; + } + else if ((te->getBumpmap() || te->getShiny()) && (!mat || mat->getNormalID().isNull())) + { + return LLDrawPool::POOL_BUMP; + } + else if (gltf_mat) + { + return LLDrawPool::POOL_GLTF_PBR; + } + else if (mat && !alpha) + { + return LLDrawPool::POOL_MATERIALS; + } + else + { + return LLDrawPool::POOL_SIMPLE; + } +} + + +void LLPipeline::addPool(LLDrawPool *new_poolp) +{ + assertInitialized(); + mPools.insert(new_poolp); + addToQuickLookup( new_poolp ); +} + +void LLPipeline::allocDrawable(LLViewerObject *vobj) +{ + LLDrawable *drawable = new LLDrawable(vobj); + vobj->mDrawable = drawable; + + //encompass completely sheared objects by taking + //the most extreme point possible (<1,1,0.5>) + drawable->setRadius(LLVector3(1,1,0.5f).scaleVec(vobj->getScale()).length()); + if (vobj->isOrphaned()) + { + drawable->setState(LLDrawable::FORCE_INVISIBLE); + } + drawable->updateXform(true); +} + + +void LLPipeline::unlinkDrawable(LLDrawable *drawable) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + + assertInitialized(); + + LLPointer drawablep = drawable; // make sure this doesn't get deleted before we are done + + // Based on flags, remove the drawable from the queues that it's on. + if (drawablep->isState(LLDrawable::ON_MOVE_LIST)) + { + LLDrawable::drawable_vector_t::iterator iter = std::find(mMovedList.begin(), mMovedList.end(), drawablep); + if (iter != mMovedList.end()) + { + mMovedList.erase(iter); + } + } + + if (drawablep->getSpatialGroup()) + { + if (!drawablep->getSpatialGroup()->getSpatialPartition()->remove(drawablep, drawablep->getSpatialGroup())) + { +#ifdef LL_RELEASE_FOR_DOWNLOAD + LL_WARNS() << "Couldn't remove object from spatial group!" << LL_ENDL; +#else + LL_ERRS() << "Couldn't remove object from spatial group!" << LL_ENDL; +#endif + } + } + + mLights.erase(drawablep); + + for (light_set_t::iterator iter = mNearbyLights.begin(); + iter != mNearbyLights.end(); iter++) + { + if (iter->drawable == drawablep) + { + mNearbyLights.erase(iter); + break; + } + } + + for (U32 i = 0; i < 2; ++i) + { + if (mShadowSpotLight[i] == drawablep) + { + mShadowSpotLight[i] = NULL; + } + + if (mTargetShadowSpotLight[i] == drawablep) + { + mTargetShadowSpotLight[i] = NULL; + } + } +} + +//static +void LLPipeline::removeMutedAVsLights(LLVOAvatar* muted_avatar) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + light_set_t::iterator iter = gPipeline.mNearbyLights.begin(); + while (iter != gPipeline.mNearbyLights.end()) + { + const LLViewerObject* vobj = iter->drawable->getVObj(); + if (vobj + && vobj->getAvatar() + && vobj->isAttachment() + && vobj->getAvatar() == muted_avatar) + { + gPipeline.mLights.erase(iter->drawable); + iter = gPipeline.mNearbyLights.erase(iter); + } + else + { + iter++; + } + } +} + +U32 LLPipeline::addObject(LLViewerObject *vobj) +{ + if (RenderDelayCreation) + { + mCreateQ.push_back(vobj); + } + else + { + createObject(vobj); + } + + return 1; +} + +void LLPipeline::createObjects(F32 max_dtime) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + + LLTimer update_timer; + + while (!mCreateQ.empty() && update_timer.getElapsedTimeF32() < max_dtime) + { + LLViewerObject* vobj = mCreateQ.front(); + if (!vobj->isDead()) + { + createObject(vobj); + } + mCreateQ.pop_front(); + } + + //for (LLViewerObject::vobj_list_t::iterator iter = mCreateQ.begin(); iter != mCreateQ.end(); ++iter) + //{ + // createObject(*iter); + //} + + //mCreateQ.clear(); +} + +void LLPipeline::createObject(LLViewerObject* vobj) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + LLDrawable* drawablep = vobj->mDrawable; + + if (!drawablep) + { + drawablep = vobj->createDrawable(this); + } + else + { + LL_ERRS() << "Redundant drawable creation!" << LL_ENDL; + } + + llassert(drawablep); + + if (vobj->getParent()) + { + vobj->setDrawableParent(((LLViewerObject*)vobj->getParent())->mDrawable); // LLPipeline::addObject 1 + } + else + { + vobj->setDrawableParent(NULL); // LLPipeline::addObject 2 + } + + markRebuild(drawablep, LLDrawable::REBUILD_ALL); + + if (drawablep->getVOVolume() && RenderAnimateRes) + { + // fun animated res + drawablep->updateXform(true); + drawablep->clearState(LLDrawable::MOVE_UNDAMPED); + drawablep->setScale(LLVector3(0,0,0)); + drawablep->makeActive(); + } +} + + +void LLPipeline::resetFrameStats() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + assertInitialized(); + + sCompiles = 0; + mNumVisibleFaces = 0; + + if (mOldRenderDebugMask != mRenderDebugMask) + { + gObjectList.clearDebugText(); + mOldRenderDebugMask = mRenderDebugMask; + } +} + +//external functions for asynchronous updating +void LLPipeline::updateMoveDampedAsync(LLDrawable* drawablep) +{ + LL_PROFILE_ZONE_SCOPED; + if (FreezeTime) + { + return; + } + if (!drawablep) + { + LL_ERRS() << "updateMove called with NULL drawablep" << LL_ENDL; + return; + } + if (drawablep->isState(LLDrawable::EARLY_MOVE)) + { + return; + } + + assertInitialized(); + + // update drawable now + drawablep->clearState(LLDrawable::MOVE_UNDAMPED); // force to DAMPED + drawablep->updateMove(); // returns done + drawablep->setState(LLDrawable::EARLY_MOVE); // flag says we already did an undamped move this frame + // Put on move list so that EARLY_MOVE gets cleared + if (!drawablep->isState(LLDrawable::ON_MOVE_LIST)) + { + mMovedList.push_back(drawablep); + drawablep->setState(LLDrawable::ON_MOVE_LIST); + } +} + +void LLPipeline::updateMoveNormalAsync(LLDrawable* drawablep) +{ + LL_PROFILE_ZONE_SCOPED; + if (FreezeTime) + { + return; + } + if (!drawablep) + { + LL_ERRS() << "updateMove called with NULL drawablep" << LL_ENDL; + return; + } + if (drawablep->isState(LLDrawable::EARLY_MOVE)) + { + return; + } + + assertInitialized(); + + // update drawable now + drawablep->setState(LLDrawable::MOVE_UNDAMPED); // force to UNDAMPED + drawablep->updateMove(); + drawablep->setState(LLDrawable::EARLY_MOVE); // flag says we already did an undamped move this frame + // Put on move list so that EARLY_MOVE gets cleared + if (!drawablep->isState(LLDrawable::ON_MOVE_LIST)) + { + mMovedList.push_back(drawablep); + drawablep->setState(LLDrawable::ON_MOVE_LIST); + } +} + +void LLPipeline::updateMovedList(LLDrawable::drawable_vector_t& moved_list) +{ + LL_PROFILE_ZONE_SCOPED; + for (LLDrawable::drawable_vector_t::iterator iter = moved_list.begin(); + iter != moved_list.end(); ) + { + LLDrawable::drawable_vector_t::iterator curiter = iter++; + LLDrawable *drawablep = *curiter; + bool done = true; + if (!drawablep->isDead() && (!drawablep->isState(LLDrawable::EARLY_MOVE))) + { + done = drawablep->updateMove(); + } + drawablep->clearState(LLDrawable::EARLY_MOVE | LLDrawable::MOVE_UNDAMPED); + if (done) + { + if (drawablep->isRoot() && !drawablep->isState(LLDrawable::ACTIVE)) + { + drawablep->makeStatic(); + } + drawablep->clearState(LLDrawable::ON_MOVE_LIST); + if (drawablep->isState(LLDrawable::ANIMATED_CHILD)) + { //will likely not receive any future world matrix updates + // -- this keeps attachments from getting stuck in space and falling off your avatar + drawablep->clearState(LLDrawable::ANIMATED_CHILD); + markRebuild(drawablep, LLDrawable::REBUILD_VOLUME); + if (drawablep->getVObj()) + { + drawablep->getVObj()->dirtySpatialGroup(); + } + } + iter = moved_list.erase(curiter); + } + } +} + +void LLPipeline::updateMove() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + + if (FreezeTime) + { + return; + } + + assertInitialized(); + + for (LLDrawable::drawable_set_t::iterator iter = mRetexturedList.begin(); + iter != mRetexturedList.end(); ++iter) + { + LLDrawable* drawablep = *iter; + if (drawablep && !drawablep->isDead()) + { + drawablep->updateTexture(); + } + } + mRetexturedList.clear(); + + updateMovedList(mMovedList); + + //balance octrees + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* region = *iter; + for (U32 i = 0; i < LLViewerRegion::NUM_PARTITIONS; i++) + { + LLSpatialPartition* part = region->getSpatialPartition(i); + if (part) + { + part->mOctree->balance(); + } + } + + //balance the VO Cache tree + LLVOCachePartition* vo_part = region->getVOCachePartition(); + if(vo_part) + { + vo_part->mOctree->balance(); + } + } +} + +///////////////////////////////////////////////////////////////////////////// +// Culling and occlusion testing +///////////////////////////////////////////////////////////////////////////// + +//static +F32 LLPipeline::calcPixelArea(LLVector3 center, LLVector3 size, LLCamera &camera) +{ + llassert(!gCubeSnapshot); // shouldn't be doing ANY of this during cube snap shots + LLVector3 lookAt = center - camera.getOrigin(); + F32 dist = lookAt.length(); + + //ramp down distance for nearby objects + //shrink dist by dist/16. + if (dist < 16.f) + { + dist /= 16.f; + dist *= dist; + dist *= 16.f; + } + + //get area of circle around node + F32 app_angle = atanf(size.length()/dist); + F32 radius = app_angle*LLDrawable::sCurPixelAngle; + return radius*radius * F_PI; +} + +//static +F32 LLPipeline::calcPixelArea(const LLVector4a& center, const LLVector4a& size, LLCamera &camera) +{ + LLVector4a origin; + origin.load3(camera.getOrigin().mV); + + LLVector4a lookAt; + lookAt.setSub(center, origin); + F32 dist = lookAt.getLength3().getF32(); + + //ramp down distance for nearby objects + //shrink dist by dist/16. + if (dist < 16.f) + { + dist /= 16.f; + dist *= dist; + dist *= 16.f; + } + + //get area of circle around node + F32 app_angle = atanf(size.getLength3().getF32()/dist); + F32 radius = app_angle*LLDrawable::sCurPixelAngle; + return radius*radius * F_PI; +} + +void LLPipeline::grabReferences(LLCullResult& result) +{ + sCull = &result; +} + +void LLPipeline::clearReferences() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + sCull = NULL; + mGroupSaveQ1.clear(); +} + +void check_references(LLSpatialGroup* group, LLDrawable* drawable) +{ + for (LLSpatialGroup::element_iter i = group->getDataBegin(); i != group->getDataEnd(); ++i) + { + LLDrawable* drawablep = (LLDrawable*)(*i)->getDrawable(); + if (drawable == drawablep) + { + LL_ERRS() << "LLDrawable deleted while actively reference by LLPipeline." << LL_ENDL; + } + } +} + +void check_references(LLDrawable* drawable, LLFace* face) +{ + for (S32 i = 0; i < drawable->getNumFaces(); ++i) + { + if (drawable->getFace(i) == face) + { + LL_ERRS() << "LLFace deleted while actively referenced by LLPipeline." << LL_ENDL; + } + } +} + +void check_references(LLSpatialGroup* group, LLFace* face) +{ + for (LLSpatialGroup::element_iter i = group->getDataBegin(); i != group->getDataEnd(); ++i) + { + LLDrawable* drawable = (LLDrawable*)(*i)->getDrawable(); + if(drawable) + { + check_references(drawable, face); + } +} +} + +void LLPipeline::checkReferences(LLFace* face) +{ +#if 0 + if (sCull) + { + for (LLCullResult::sg_iterator iter = sCull->beginVisibleGroups(); iter != sCull->endVisibleGroups(); ++iter) + { + LLSpatialGroup* group = *iter; + check_references(group, face); + } + + for (LLCullResult::sg_iterator iter = sCull->beginAlphaGroups(); iter != sCull->endAlphaGroups(); ++iter) + { + LLSpatialGroup* group = *iter; + check_references(group, face); + } + + for (LLCullResult::sg_iterator iter = sCull->beginDrawableGroups(); iter != sCull->endDrawableGroups(); ++iter) + { + LLSpatialGroup* group = *iter; + check_references(group, face); + } + + for (LLCullResult::drawable_iterator iter = sCull->beginVisibleList(); iter != sCull->endVisibleList(); ++iter) + { + LLDrawable* drawable = *iter; + check_references(drawable, face); + } + } +#endif +} + +void LLPipeline::checkReferences(LLDrawable* drawable) +{ +#if 0 + if (sCull) + { + for (LLCullResult::sg_iterator iter = sCull->beginVisibleGroups(); iter != sCull->endVisibleGroups(); ++iter) + { + LLSpatialGroup* group = *iter; + check_references(group, drawable); + } + + for (LLCullResult::sg_iterator iter = sCull->beginAlphaGroups(); iter != sCull->endAlphaGroups(); ++iter) + { + LLSpatialGroup* group = *iter; + check_references(group, drawable); + } + + for (LLCullResult::sg_iterator iter = sCull->beginDrawableGroups(); iter != sCull->endDrawableGroups(); ++iter) + { + LLSpatialGroup* group = *iter; + check_references(group, drawable); + } + + for (LLCullResult::drawable_iterator iter = sCull->beginVisibleList(); iter != sCull->endVisibleList(); ++iter) + { + if (drawable == *iter) + { + LL_ERRS() << "LLDrawable deleted while actively referenced by LLPipeline." << LL_ENDL; + } + } + } +#endif +} + +void check_references(LLSpatialGroup* group, LLDrawInfo* draw_info) +{ + for (LLSpatialGroup::draw_map_t::iterator i = group->mDrawMap.begin(); i != group->mDrawMap.end(); ++i) + { + LLSpatialGroup::drawmap_elem_t& draw_vec = i->second; + for (LLSpatialGroup::drawmap_elem_t::iterator j = draw_vec.begin(); j != draw_vec.end(); ++j) + { + LLDrawInfo* params = *j; + if (params == draw_info) + { + LL_ERRS() << "LLDrawInfo deleted while actively referenced by LLPipeline." << LL_ENDL; + } + } + } +} + + +void LLPipeline::checkReferences(LLDrawInfo* draw_info) +{ +#if 0 + if (sCull) + { + for (LLCullResult::sg_iterator iter = sCull->beginVisibleGroups(); iter != sCull->endVisibleGroups(); ++iter) + { + LLSpatialGroup* group = *iter; + check_references(group, draw_info); + } + + for (LLCullResult::sg_iterator iter = sCull->beginAlphaGroups(); iter != sCull->endAlphaGroups(); ++iter) + { + LLSpatialGroup* group = *iter; + check_references(group, draw_info); + } + + for (LLCullResult::sg_iterator iter = sCull->beginDrawableGroups(); iter != sCull->endDrawableGroups(); ++iter) + { + LLSpatialGroup* group = *iter; + check_references(group, draw_info); + } + } +#endif +} + +void LLPipeline::checkReferences(LLSpatialGroup* group) +{ +#if CHECK_PIPELINE_REFERENCES + if (sCull) + { + for (LLCullResult::sg_iterator iter = sCull->beginVisibleGroups(); iter != sCull->endVisibleGroups(); ++iter) + { + if (group == *iter) + { + LL_ERRS() << "LLSpatialGroup deleted while actively referenced by LLPipeline." << LL_ENDL; + } + } + + for (LLCullResult::sg_iterator iter = sCull->beginAlphaGroups(); iter != sCull->endAlphaGroups(); ++iter) + { + if (group == *iter) + { + LL_ERRS() << "LLSpatialGroup deleted while actively referenced by LLPipeline." << LL_ENDL; + } + } + + for (LLCullResult::sg_iterator iter = sCull->beginDrawableGroups(); iter != sCull->endDrawableGroups(); ++iter) + { + if (group == *iter) + { + LL_ERRS() << "LLSpatialGroup deleted while actively referenced by LLPipeline." << LL_ENDL; + } + } + } +#endif +} + + +bool LLPipeline::visibleObjectsInFrustum(LLCamera& camera) +{ + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* region = *iter; + + for (U32 i = 0; i < LLViewerRegion::NUM_PARTITIONS; i++) + { + LLSpatialPartition* part = region->getSpatialPartition(i); + if (part) + { + if (hasRenderType(part->mDrawableType)) + { + if (part->visibleObjectsInFrustum(camera)) + { + return true; + } + } + } + } + } + + return false; +} + +bool LLPipeline::getVisibleExtents(LLCamera& camera, LLVector3& min, LLVector3& max) +{ + const F32 X = 65536.f; + + min = LLVector3(X,X,X); + max = LLVector3(-X,-X,-X); + + LLViewerCamera::eCameraID saved_camera_id = LLViewerCamera::sCurCameraID; + LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD; + + bool res = true; + + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* region = *iter; + + for (U32 i = 0; i < LLViewerRegion::NUM_PARTITIONS; i++) + { + LLSpatialPartition* part = region->getSpatialPartition(i); + if (part) + { + if (hasRenderType(part->mDrawableType)) + { + if (!part->getVisibleExtents(camera, min, max)) + { + res = false; + } + } + } + } + } + + LLViewerCamera::sCurCameraID = saved_camera_id; + return res; +} + +static LLTrace::BlockTimerStatHandle FTM_CULL("Object Culling"); + +// static +bool LLPipeline::isWaterClip() +{ + return (!sRenderTransparentWater || gCubeSnapshot) && !sRenderingHUDs; +} + +void LLPipeline::updateCull(LLCamera& camera, LLCullResult& result) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; //LL_RECORD_BLOCK_TIME(FTM_CULL); + LL_PROFILE_GPU_ZONE("updateCull"); // should always be zero GPU time, but drop a timer to flush stuff out + + bool water_clip = isWaterClip(); + + if (water_clip) + { + + LLVector3 pnorm; + + F32 water_height = LLEnvironment::instance().getWaterHeight(); + + if (sUnderWaterRender) + { + //camera is below water, cull above water + pnorm.setVec(0, 0, 1); + } + else + { + //camera is above water, cull below water + pnorm = LLVector3(0, 0, -1); + } + + LLPlane plane; + plane.setVec(LLVector3(0, 0, water_height), pnorm); + + camera.setUserClipPlane(plane); + } + else + { + camera.disableUserClipPlane(); + } + + grabReferences(result); + + sCull->clear(); + + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* region = *iter; + + for (U32 i = 0; i < LLViewerRegion::NUM_PARTITIONS; i++) + { + LLSpatialPartition* part = region->getSpatialPartition(i); + if (part) + { + if (hasRenderType(part->mDrawableType)) + { + part->cull(camera); + } + } + } + + //scan the VO Cache tree + LLVOCachePartition* vo_part = region->getVOCachePartition(); + if(vo_part) + { + vo_part->cull(camera, sUseOcclusion > 0); + } + } + + if (hasRenderType(LLPipeline::RENDER_TYPE_SKY) && + gSky.mVOSkyp.notNull() && + gSky.mVOSkyp->mDrawable.notNull()) + { + gSky.mVOSkyp->mDrawable->setVisible(camera); + sCull->pushDrawable(gSky.mVOSkyp->mDrawable); + gSky.updateCull(); + stop_glerror(); + } + + if (hasRenderType(LLPipeline::RENDER_TYPE_WL_SKY) && + gPipeline.canUseWindLightShaders() && + gSky.mVOWLSkyp.notNull() && + gSky.mVOWLSkyp->mDrawable.notNull()) + { + gSky.mVOWLSkyp->mDrawable->setVisible(camera); + sCull->pushDrawable(gSky.mVOWLSkyp->mDrawable); + } +} + +void LLPipeline::markNotCulled(LLSpatialGroup* group, LLCamera& camera) +{ + if (group->isEmpty()) + { + return; + } + + group->setVisible(); + + if (LLViewerCamera::sCurCameraID == LLViewerCamera::CAMERA_WORLD && !gCubeSnapshot) + { + group->updateDistance(camera); + } + + assertInitialized(); + + if (!group->getSpatialPartition()->mRenderByGroup) + { //render by drawable + sCull->pushDrawableGroup(group); + } + else + { //render by group + sCull->pushVisibleGroup(group); + } + + if (group->needsUpdate() || + group->getVisible(LLViewerCamera::sCurCameraID) < LLDrawable::getCurrentFrame() - 1) + { + // include this group in occlusion groups, not because it is an occluder, but because we want to run + // an occlusion query to find out if it's an occluder + markOccluder(group); + } + mNumVisibleNodes++; +} + +void LLPipeline::markOccluder(LLSpatialGroup* group) +{ + if (sUseOcclusion > 1 && group && !group->isOcclusionState(LLSpatialGroup::ACTIVE_OCCLUSION)) + { + LLSpatialGroup* parent = group->getParent(); + + if (!parent || !parent->isOcclusionState(LLSpatialGroup::OCCLUDED)) + { //only mark top most occluders as active occlusion + sCull->pushOcclusionGroup(group); + group->setOcclusionState(LLSpatialGroup::ACTIVE_OCCLUSION); + + if (parent && + !parent->isOcclusionState(LLSpatialGroup::ACTIVE_OCCLUSION) && + parent->getElementCount() == 0 && + parent->needsUpdate()) + { + sCull->pushOcclusionGroup(group); + parent->setOcclusionState(LLSpatialGroup::ACTIVE_OCCLUSION); + } + } + } +} + +void LLPipeline::doOcclusion(LLCamera& camera) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + LL_PROFILE_GPU_ZONE("doOcclusion"); + llassert(!gCubeSnapshot); + + if (sReflectionProbesEnabled && sUseOcclusion > 1 && !LLPipeline::sShadowRender && !gCubeSnapshot) + { + gGL.setColorMask(false, false); + LLGLDepthTest depth(GL_TRUE, GL_FALSE); + LLGLDisable cull(GL_CULL_FACE); + + gOcclusionCubeProgram.bind(); + + if (mCubeVB.isNull()) + { //cube VB will be used for issuing occlusion queries + mCubeVB = ll_create_cube_vb(LLVertexBuffer::MAP_VERTEX); + } + mCubeVB->setBuffer(); + + mReflectionMapManager.doOcclusion(); + gOcclusionCubeProgram.unbind(); + + gGL.setColorMask(true, true); + } + + if (LLPipeline::sUseOcclusion > 1 && + (sCull->hasOcclusionGroups() || LLVOCachePartition::sNeedsOcclusionCheck)) + { + LLVertexBuffer::unbind(); + + gGL.setColorMask(false, false); + + LLGLDisable blend(GL_BLEND); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + LLGLDepthTest depth(GL_TRUE, GL_FALSE); + + LLGLDisable cull(GL_CULL_FACE); + + gOcclusionCubeProgram.bind(); + + if (mCubeVB.isNull()) + { //cube VB will be used for issuing occlusion queries + mCubeVB = ll_create_cube_vb(LLVertexBuffer::MAP_VERTEX); + } + mCubeVB->setBuffer(); + + for (LLCullResult::sg_iterator iter = sCull->beginOcclusionGroups(); iter != sCull->endOcclusionGroups(); ++iter) + { + LLSpatialGroup* group = *iter; + if (!group->isDead()) + { + group->doOcclusion(&camera); + group->clearOcclusionState(LLSpatialGroup::ACTIVE_OCCLUSION); + } + } + + //apply occlusion culling to object cache tree + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLVOCachePartition* vo_part = (*iter)->getVOCachePartition(); + if(vo_part) + { + vo_part->processOccluders(&camera); + } + } + + gGL.setColorMask(true, true); + } +} + +bool LLPipeline::updateDrawableGeom(LLDrawable* drawablep) +{ + bool update_complete = drawablep->updateGeometry(); + if (update_complete && assertInitialized()) + { + drawablep->setState(LLDrawable::BUILT); + } + return update_complete; +} + +void LLPipeline::updateGL() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + { + while (!LLGLUpdate::sGLQ.empty()) + { + LLGLUpdate* glu = LLGLUpdate::sGLQ.front(); + glu->updateGL(); + glu->mInQ = false; + LLGLUpdate::sGLQ.pop_front(); + } + } +} + +void LLPipeline::clearRebuildGroups() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + LLSpatialGroup::sg_vector_t hudGroups; + + mGroupQ1Locked = true; + // Iterate through all drawables on the priority build queue, + for (LLSpatialGroup::sg_vector_t::iterator iter = mGroupQ1.begin(); + iter != mGroupQ1.end(); ++iter) + { + LLSpatialGroup* group = *iter; + + // If the group contains HUD objects, save the group + if (group->isHUDGroup()) + { + hudGroups.push_back(group); + } + // Else, no HUD objects so clear the build state + else + { + group->clearState(LLSpatialGroup::IN_BUILD_Q1); + } + } + + // Clear the group + mGroupQ1.clear(); + + // Copy the saved HUD groups back in + mGroupQ1.assign(hudGroups.begin(), hudGroups.end()); + mGroupQ1Locked = false; +} + +void LLPipeline::clearRebuildDrawables() +{ + // Clear all drawables on the priority build queue, + for (LLDrawable::drawable_list_t::iterator iter = mBuildQ1.begin(); + iter != mBuildQ1.end(); ++iter) + { + LLDrawable* drawablep = *iter; + if (drawablep && !drawablep->isDead()) + { + drawablep->clearState(LLDrawable::IN_REBUILD_Q); + } + } + mBuildQ1.clear(); + + //clear all moving bridges + for (LLDrawable::drawable_vector_t::iterator iter = mMovedBridge.begin(); + iter != mMovedBridge.end(); ++iter) + { + LLDrawable *drawablep = *iter; + drawablep->clearState(LLDrawable::EARLY_MOVE | LLDrawable::MOVE_UNDAMPED | LLDrawable::ON_MOVE_LIST | LLDrawable::ANIMATED_CHILD); + } + mMovedBridge.clear(); + + //clear all moving drawables + for (LLDrawable::drawable_vector_t::iterator iter = mMovedList.begin(); + iter != mMovedList.end(); ++iter) + { + LLDrawable *drawablep = *iter; + drawablep->clearState(LLDrawable::EARLY_MOVE | LLDrawable::MOVE_UNDAMPED | LLDrawable::ON_MOVE_LIST | LLDrawable::ANIMATED_CHILD); + } + mMovedList.clear(); + + for (LLDrawable::drawable_vector_t::iterator iter = mShiftList.begin(); + iter != mShiftList.end(); ++iter) + { + LLDrawable *drawablep = *iter; + drawablep->clearState(LLDrawable::EARLY_MOVE | LLDrawable::MOVE_UNDAMPED | LLDrawable::ON_MOVE_LIST | LLDrawable::ANIMATED_CHILD | LLDrawable::ON_SHIFT_LIST); + } + mShiftList.clear(); +} + +void LLPipeline::rebuildPriorityGroups() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + LL_PROFILE_GPU_ZONE("rebuildPriorityGroups"); + + LLTimer update_timer; + assertInitialized(); + + gMeshRepo.notifyLoadedMeshes(); + + mGroupQ1Locked = true; + // Iterate through all drawables on the priority build queue, + for (LLSpatialGroup::sg_vector_t::iterator iter = mGroupQ1.begin(); + iter != mGroupQ1.end(); ++iter) + { + LLSpatialGroup* group = *iter; + group->rebuildGeom(); + group->clearState(LLSpatialGroup::IN_BUILD_Q1); + } + + mGroupSaveQ1 = mGroupQ1; + mGroupQ1.clear(); + mGroupQ1Locked = false; + +} + +void LLPipeline::updateGeom(F32 max_dtime) +{ + LLTimer update_timer; + LLPointer drawablep; + + LL_RECORD_BLOCK_TIME(FTM_GEO_UPDATE); + if (gCubeSnapshot) + { + return; + } + + assertInitialized(); + + // notify various object types to reset internal cost metrics, etc. + // for now, only LLVOVolume does this to throttle LOD changes + LLVOVolume::preUpdateGeom(); + + // Iterate through all drawables on the priority build queue, + for (LLDrawable::drawable_list_t::iterator iter = mBuildQ1.begin(); + iter != mBuildQ1.end();) + { + LLDrawable::drawable_list_t::iterator curiter = iter++; + LLDrawable* drawablep = *curiter; + if (drawablep && !drawablep->isDead()) + { + if (drawablep->isUnload()) + { + drawablep->unload(); + drawablep->clearState(LLDrawable::FOR_UNLOAD); + } + + if (updateDrawableGeom(drawablep)) + { + drawablep->clearState(LLDrawable::IN_REBUILD_Q); + mBuildQ1.erase(curiter); + } + } + else + { + mBuildQ1.erase(curiter); + } + } + + updateMovedList(mMovedBridge); +} + +void LLPipeline::markVisible(LLDrawable *drawablep, LLCamera& camera) +{ + if(drawablep && !drawablep->isDead()) + { + if (drawablep->isSpatialBridge()) + { + const LLDrawable* root = ((LLSpatialBridge*) drawablep)->mDrawable; + llassert(root); // trying to catch a bad assumption + + if (root && // // this test may not be needed, see above + root->getVObj()->isAttachment()) + { + LLDrawable* rootparent = root->getParent(); + if (rootparent) // this IS sometimes NULL + { + LLViewerObject *vobj = rootparent->getVObj(); + llassert(vobj); // trying to catch a bad assumption + if (vobj) // this test may not be needed, see above + { + LLVOAvatar* av = vobj->asAvatar(); + if (av && + ((!sImpostorRender && av->isImpostor()) //ignore impostor flag during impostor pass + || av->isInMuteList() + || (LLVOAvatar::AOA_JELLYDOLL == av->getOverallAppearance() && !av->needsImpostorUpdate()) )) + { + return; + } + } + } + } + sCull->pushBridge((LLSpatialBridge*) drawablep); + } + else + { + + sCull->pushDrawable(drawablep); + } + + drawablep->setVisible(camera); + } +} + +void LLPipeline::markMoved(LLDrawable *drawablep, bool damped_motion) +{ + if (!drawablep) + { + //LL_ERRS() << "Sending null drawable to moved list!" << LL_ENDL; + return; + } + + if (drawablep->isDead()) + { + LL_WARNS() << "Marking NULL or dead drawable moved!" << LL_ENDL; + return; + } + + if (drawablep->getParent()) + { + //ensure that parent drawables are moved first + markMoved(drawablep->getParent(), damped_motion); + } + + assertInitialized(); + + if (!drawablep->isState(LLDrawable::ON_MOVE_LIST)) + { + if (drawablep->isSpatialBridge()) + { + mMovedBridge.push_back(drawablep); + } + else + { + mMovedList.push_back(drawablep); + } + drawablep->setState(LLDrawable::ON_MOVE_LIST); + } + if (! damped_motion) + { + drawablep->setState(LLDrawable::MOVE_UNDAMPED); // UNDAMPED trumps DAMPED + } + else if (drawablep->isState(LLDrawable::MOVE_UNDAMPED)) + { + drawablep->clearState(LLDrawable::MOVE_UNDAMPED); + } +} + +void LLPipeline::markShift(LLDrawable *drawablep) +{ + if (!drawablep || drawablep->isDead()) + { + return; + } + + assertInitialized(); + + if (!drawablep->isState(LLDrawable::ON_SHIFT_LIST)) + { + drawablep->getVObj()->setChanged(LLXform::SHIFTED | LLXform::SILHOUETTE); + if (drawablep->getParent()) + { + markShift(drawablep->getParent()); + } + mShiftList.push_back(drawablep); + drawablep->setState(LLDrawable::ON_SHIFT_LIST); + } +} + +void LLPipeline::shiftObjects(const LLVector3 &offset) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + assertInitialized(); + + glClear(GL_DEPTH_BUFFER_BIT); + gDepthDirty = true; + + LLVector4a offseta; + offseta.load3(offset.mV); + + for (LLDrawable::drawable_vector_t::iterator iter = mShiftList.begin(); + iter != mShiftList.end(); iter++) + { + LLDrawable *drawablep = *iter; + if (drawablep->isDead()) + { + continue; + } + drawablep->shiftPos(offseta); + drawablep->clearState(LLDrawable::ON_SHIFT_LIST); + } + mShiftList.resize(0); + + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* region = *iter; + for (U32 i = 0; i < LLViewerRegion::NUM_PARTITIONS; i++) + { + LLSpatialPartition* part = region->getSpatialPartition(i); + if (part) + { + part->shift(offseta); + } + } + } + + mReflectionMapManager.shift(offseta); + + LLHUDText::shiftAll(offset); + LLHUDNameTag::shiftAll(offset); + + display_update_camera(); +} + +void LLPipeline::markTextured(LLDrawable *drawablep) +{ + if (drawablep && !drawablep->isDead() && assertInitialized()) + { + mRetexturedList.insert(drawablep); + } +} + +void LLPipeline::markGLRebuild(LLGLUpdate* glu) +{ + if (glu && !glu->mInQ) + { + LLGLUpdate::sGLQ.push_back(glu); + glu->mInQ = true; + } +} + +void LLPipeline::markPartitionMove(LLDrawable* drawable) +{ + if (!drawable->isState(LLDrawable::PARTITION_MOVE) && + !drawable->getPositionGroup().equals3(LLVector4a::getZero())) + { + drawable->setState(LLDrawable::PARTITION_MOVE); + mPartitionQ.push_back(drawable); + } +} + +void LLPipeline::processPartitionQ() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + for (LLDrawable::drawable_list_t::iterator iter = mPartitionQ.begin(); iter != mPartitionQ.end(); ++iter) + { + LLDrawable* drawable = *iter; + if (!drawable->isDead()) + { + drawable->updateBinRadius(); + drawable->movePartition(); + } + drawable->clearState(LLDrawable::PARTITION_MOVE); + } + + mPartitionQ.clear(); +} + +void LLPipeline::markMeshDirty(LLSpatialGroup* group) +{ + mMeshDirtyGroup.push_back(group); +} + +void LLPipeline::markRebuild(LLSpatialGroup* group) +{ + if (group && !group->isDead() && group->getSpatialPartition()) + { + if (!group->hasState(LLSpatialGroup::IN_BUILD_Q1)) + { + llassert_always(!mGroupQ1Locked); + + mGroupQ1.push_back(group); + group->setState(LLSpatialGroup::IN_BUILD_Q1); + } + } +} + +void LLPipeline::markRebuild(LLDrawable *drawablep, LLDrawable::EDrawableFlags flag) +{ + if (drawablep && !drawablep->isDead() && assertInitialized()) + { + if (!drawablep->isState(LLDrawable::IN_REBUILD_Q)) + { + mBuildQ1.push_back(drawablep); + drawablep->setState(LLDrawable::IN_REBUILD_Q); // mark drawable as being in priority queue + } + + if (flag & (LLDrawable::REBUILD_VOLUME | LLDrawable::REBUILD_POSITION)) + { + drawablep->getVObj()->setChanged(LLXform::SILHOUETTE); + } + drawablep->setState(flag); + } +} + +void LLPipeline::stateSort(LLCamera& camera, LLCullResult &result) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + LL_PROFILE_GPU_ZONE("stateSort"); + + if (hasAnyRenderType(LLPipeline::RENDER_TYPE_AVATAR, + LLPipeline::RENDER_TYPE_CONTROL_AV, + LLPipeline::RENDER_TYPE_TERRAIN, + LLPipeline::RENDER_TYPE_TREE, + LLPipeline::RENDER_TYPE_SKY, + LLPipeline::RENDER_TYPE_VOIDWATER, + LLPipeline::RENDER_TYPE_WATER, + LLPipeline::END_RENDER_TYPES)) + { + //clear faces from face pools + gPipeline.resetDrawOrders(); + } + + //LLVertexBuffer::unbind(); + + grabReferences(result); + for (LLCullResult::sg_iterator iter = sCull->beginDrawableGroups(); iter != sCull->endDrawableGroups(); ++iter) + { + LLSpatialGroup* group = *iter; + if (group->isDead()) + { + continue; + } + group->checkOcclusion(); + if (sUseOcclusion > 1 && group->isOcclusionState(LLSpatialGroup::OCCLUDED)) + { + markOccluder(group); + } + else + { + group->setVisible(); + for (LLSpatialGroup::element_iter i = group->getDataBegin(); i != group->getDataEnd(); ++i) + { + LLDrawable* drawablep = (LLDrawable*)(*i)->getDrawable(); + markVisible(drawablep, camera); + } + + { //rebuild mesh as soon as we know it's visible + group->rebuildMesh(); + } + } + } + + if (LLViewerCamera::sCurCameraID == LLViewerCamera::CAMERA_WORLD && !gCubeSnapshot) + { + LLSpatialGroup* last_group = NULL; + bool fov_changed = LLViewerCamera::getInstance()->isDefaultFOVChanged(); + for (LLCullResult::bridge_iterator i = sCull->beginVisibleBridge(); i != sCull->endVisibleBridge(); ++i) + { + LLCullResult::bridge_iterator cur_iter = i; + LLSpatialBridge* bridge = *cur_iter; + LLSpatialGroup* group = bridge->getSpatialGroup(); + + if (last_group == NULL) + { + last_group = group; + } + + if (!bridge->isDead() && group && !group->isOcclusionState(LLSpatialGroup::OCCLUDED)) + { + stateSort(bridge, camera, fov_changed); + } + + if (LLViewerCamera::sCurCameraID == LLViewerCamera::CAMERA_WORLD && + last_group != group && last_group->changeLOD()) + { + last_group->mLastUpdateDistance = last_group->mDistance; + } + + last_group = group; + } + + if (LLViewerCamera::sCurCameraID == LLViewerCamera::CAMERA_WORLD && + last_group && last_group->changeLOD()) + { + last_group->mLastUpdateDistance = last_group->mDistance; + } + } + + for (LLCullResult::sg_iterator iter = sCull->beginVisibleGroups(); iter != sCull->endVisibleGroups(); ++iter) + { + LLSpatialGroup* group = *iter; + if (group->isDead()) + { + continue; + } + group->checkOcclusion(); + if (sUseOcclusion > 1 && group->isOcclusionState(LLSpatialGroup::OCCLUDED)) + { + markOccluder(group); + } + else + { + group->setVisible(); + stateSort(group, camera); + + { //rebuild mesh as soon as we know it's visible + group->rebuildMesh(); + } + } + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWABLE("stateSort"); // LL_RECORD_BLOCK_TIME(FTM_STATESORT_DRAWABLE); + for (LLCullResult::drawable_iterator iter = sCull->beginVisibleList(); + iter != sCull->endVisibleList(); ++iter) + { + LLDrawable *drawablep = *iter; + if (!drawablep->isDead()) + { + stateSort(drawablep, camera); + } + } + } + + postSort(camera); +} + +void LLPipeline::stateSort(LLSpatialGroup* group, LLCamera& camera) +{ + if (group->changeLOD()) + { + for (LLSpatialGroup::element_iter i = group->getDataBegin(); i != group->getDataEnd(); ++i) + { + LLDrawable* drawablep = (LLDrawable*)(*i)->getDrawable(); + stateSort(drawablep, camera); + } + + if (LLViewerCamera::sCurCameraID == LLViewerCamera::CAMERA_WORLD && !gCubeSnapshot) + { //avoid redundant stateSort calls + group->mLastUpdateDistance = group->mDistance; + } + } +} + +void LLPipeline::stateSort(LLSpatialBridge* bridge, LLCamera& camera, bool fov_changed) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + if (bridge->getSpatialGroup()->changeLOD() || fov_changed) + { + bool force_update = false; + bridge->updateDistance(camera, force_update); + } +} + +void LLPipeline::stateSort(LLDrawable* drawablep, LLCamera& camera) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + if (!drawablep + || drawablep->isDead() + || !hasRenderType(drawablep->getRenderType())) + { + return; + } + + // SL-11353 + // ignore our own geo when rendering spotlight shadowmaps... + // + if (RenderSpotLight && drawablep == RenderSpotLight) + { + return; + } + + if (LLSelectMgr::getInstance()->mHideSelectedObjects) + { + if (drawablep->getVObj().notNull() && + drawablep->getVObj()->isSelected()) + { + return; + } + } + + if (drawablep->isAvatar()) + { //don't draw avatars beyond render distance or if we don't have a spatial group. + if ((drawablep->getSpatialGroup() == NULL) || + (drawablep->getSpatialGroup()->mDistance > LLVOAvatar::sRenderDistance)) + { + return; + } + + LLVOAvatar* avatarp = (LLVOAvatar*) drawablep->getVObj().get(); + if (!avatarp->isVisible()) + { + return; + } + } + + assertInitialized(); + + if (hasRenderType(drawablep->mRenderType)) + { + if (!drawablep->isState(LLDrawable::INVISIBLE|LLDrawable::FORCE_INVISIBLE)) + { + drawablep->setVisible(camera, NULL, false); + } + } + + if (LLViewerCamera::sCurCameraID == LLViewerCamera::CAMERA_WORLD && !gCubeSnapshot) + { + //if (drawablep->isVisible()) isVisible() check here is redundant, if it wasn't visible, it wouldn't be here + { + if (!drawablep->isActive()) + { + bool force_update = false; + drawablep->updateDistance(camera, force_update); + } + else if (drawablep->isAvatar()) + { + bool force_update = false; + drawablep->updateDistance(camera, force_update); // calls vobj->updateLOD() which calls LLVOAvatar::updateVisibility() + } + } + } + + if (!drawablep->getVOVolume()) + { + for (LLDrawable::face_list_t::iterator iter = drawablep->mFaces.begin(); + iter != drawablep->mFaces.end(); iter++) + { + LLFace* facep = *iter; + + if (facep->hasGeometry()) + { + if (facep->getPool()) + { + facep->getPool()->enqueue(facep); + } + else + { + break; + } + } + } + } + + mNumVisibleFaces += drawablep->getNumFaces(); +} + + +void forAllDrawables(LLCullResult::sg_iterator begin, + LLCullResult::sg_iterator end, + void (*func)(LLDrawable*)) +{ + for (LLCullResult::sg_iterator i = begin; i != end; ++i) + { + LLSpatialGroup* group = *i; + if (group->isDead()) + { + continue; + } + for (LLSpatialGroup::element_iter j = group->getDataBegin(); j != group->getDataEnd(); ++j) + { + if((*j)->hasDrawable()) + { + func((LLDrawable*)(*j)->getDrawable()); + } + } + } +} + +void LLPipeline::forAllVisibleDrawables(void (*func)(LLDrawable*)) +{ + forAllDrawables(sCull->beginDrawableGroups(), sCull->endDrawableGroups(), func); + forAllDrawables(sCull->beginVisibleGroups(), sCull->endVisibleGroups(), func); +} + +//function for creating scripted beacons +void renderScriptedBeacons(LLDrawable* drawablep) +{ + LLViewerObject *vobj = drawablep->getVObj(); + if (vobj + && !vobj->isAvatar() + && !vobj->getParent() + && vobj->flagScripted()) + { + if (gPipeline.sRenderBeacons) + { + gObjectList.addDebugBeacon(vobj->getPositionAgent(), "", LLColor4(1.f, 0.f, 0.f, 0.5f), LLColor4(1.f, 1.f, 1.f, 0.5f), LLPipeline::DebugBeaconLineWidth); + } + + if (gPipeline.sRenderHighlight) + { + S32 face_id; + S32 count = drawablep->getNumFaces(); + for (face_id = 0; face_id < count; face_id++) + { + LLFace * facep = drawablep->getFace(face_id); + if (facep) + { + gPipeline.mHighlightFaces.push_back(facep); + } + } + } + } +} + +void renderScriptedTouchBeacons(LLDrawable *drawablep) +{ + LLViewerObject *vobj = drawablep->getVObj(); + if (vobj && !vobj->isAvatar() && !vobj->getParent() && vobj->flagScripted() && vobj->flagHandleTouch()) + { + if (gPipeline.sRenderBeacons) + { + gObjectList.addDebugBeacon(vobj->getPositionAgent(), "", LLColor4(1.f, 0.f, 0.f, 0.5f), LLColor4(1.f, 1.f, 1.f, 0.5f), + LLPipeline::DebugBeaconLineWidth); + } + + if (gPipeline.sRenderHighlight) + { + S32 face_id; + S32 count = drawablep->getNumFaces(); + for (face_id = 0; face_id < count; face_id++) + { + LLFace *facep = drawablep->getFace(face_id); + if (facep) + { + gPipeline.mHighlightFaces.push_back(facep); + } + } + } + } +} + +void renderPhysicalBeacons(LLDrawable *drawablep) +{ + LLViewerObject *vobj = drawablep->getVObj(); + if (vobj && + !vobj->isAvatar() + //&& !vobj->getParent() + && vobj->flagUsePhysics()) + { + if (gPipeline.sRenderBeacons) + { + gObjectList.addDebugBeacon(vobj->getPositionAgent(), "", LLColor4(0.f, 1.f, 0.f, 0.5f), LLColor4(1.f, 1.f, 1.f, 0.5f), + LLPipeline::DebugBeaconLineWidth); + } + + if (gPipeline.sRenderHighlight) + { + S32 face_id; + S32 count = drawablep->getNumFaces(); + for (face_id = 0; face_id < count; face_id++) + { + LLFace *facep = drawablep->getFace(face_id); + if (facep) + { + gPipeline.mHighlightFaces.push_back(facep); + } + } + } + } +} + +void renderMOAPBeacons(LLDrawable *drawablep) +{ + LLViewerObject *vobj = drawablep->getVObj(); + + if (!vobj || vobj->isAvatar()) + return; + + bool beacon = false; + U8 tecount = vobj->getNumTEs(); + for (int x = 0; x < tecount; x++) + { + if (vobj->getTE(x)->hasMedia()) + { + beacon = true; + break; + } + } + if (beacon) + { + if (gPipeline.sRenderBeacons) + { + gObjectList.addDebugBeacon(vobj->getPositionAgent(), "", LLColor4(1.f, 1.f, 1.f, 0.5f), LLColor4(1.f, 1.f, 1.f, 0.5f), + LLPipeline::DebugBeaconLineWidth); + } + + if (gPipeline.sRenderHighlight) + { + S32 face_id; + S32 count = drawablep->getNumFaces(); + for (face_id = 0; face_id < count; face_id++) + { + LLFace *facep = drawablep->getFace(face_id); + if (facep) + { + gPipeline.mHighlightFaces.push_back(facep); + } + } + } + } +} + +void renderParticleBeacons(LLDrawable *drawablep) +{ + // Look for attachments, objects, etc. + LLViewerObject *vobj = drawablep->getVObj(); + if (vobj && vobj->isParticleSource()) + { + if (gPipeline.sRenderBeacons) + { + LLColor4 light_blue(0.5f, 0.5f, 1.f, 0.5f); + gObjectList.addDebugBeacon(vobj->getPositionAgent(), "", light_blue, LLColor4(1.f, 1.f, 1.f, 0.5f), + LLPipeline::DebugBeaconLineWidth); + } + + if (gPipeline.sRenderHighlight) + { + S32 face_id; + S32 count = drawablep->getNumFaces(); + for (face_id = 0; face_id < count; face_id++) + { + LLFace *facep = drawablep->getFace(face_id); + if (facep) + { + gPipeline.mHighlightFaces.push_back(facep); + } + } + } + } +} + +void renderSoundHighlights(LLDrawable *drawablep) +{ + // Look for attachments, objects, etc. + LLViewerObject *vobj = drawablep->getVObj(); + if (vobj && vobj->isAudioSource()) + { + if (gPipeline.sRenderHighlight) + { + S32 face_id; + S32 count = drawablep->getNumFaces(); + for (face_id = 0; face_id < count; face_id++) + { + LLFace *facep = drawablep->getFace(face_id); + if (facep) + { + gPipeline.mHighlightFaces.push_back(facep); + } + } + } + } +} + +void LLPipeline::postSort(LLCamera &camera) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + + assertInitialized(); + + LL_PUSH_CALLSTACKS(); + + if (!gCubeSnapshot) + { + // rebuild drawable geometry + for (LLCullResult::sg_iterator i = sCull->beginDrawableGroups(); i != sCull->endDrawableGroups(); ++i) + { + LLSpatialGroup *group = *i; + if (group->isDead()) + { + continue; + } + if (!sUseOcclusion || !group->isOcclusionState(LLSpatialGroup::OCCLUDED)) + { + group->rebuildGeom(); + } + } + LL_PUSH_CALLSTACKS(); + // rebuild groups + sCull->assertDrawMapsEmpty(); + + rebuildPriorityGroups(); + } + + LL_PUSH_CALLSTACKS(); + + // build render map + for (LLCullResult::sg_iterator i = sCull->beginVisibleGroups(); i != sCull->endVisibleGroups(); ++i) + { + LLSpatialGroup *group = *i; + + if (group->isDead()) + { + continue; + } + + if ((sUseOcclusion && group->isOcclusionState(LLSpatialGroup::OCCLUDED)) || + (RenderAutoHideSurfaceAreaLimit > 0.f && + group->mSurfaceArea > RenderAutoHideSurfaceAreaLimit * llmax(group->mObjectBoxSize, 10.f))) + { + continue; + } + + if (group->hasState(LLSpatialGroup::NEW_DRAWINFO) && group->hasState(LLSpatialGroup::GEOM_DIRTY) && !gCubeSnapshot) + { // no way this group is going to be drawable without a rebuild + group->rebuildGeom(); + } + + for (LLSpatialGroup::draw_map_t::iterator j = group->mDrawMap.begin(); j != group->mDrawMap.end(); ++j) + { + LLSpatialGroup::drawmap_elem_t &src_vec = j->second; + if (!hasRenderType(j->first)) + { + continue; + } + + for (LLSpatialGroup::drawmap_elem_t::iterator k = src_vec.begin(); k != src_vec.end(); ++k) + { + LLDrawInfo *info = *k; + + sCull->pushDrawInfo(j->first, info); + if (!sShadowRender && !sReflectionRender && !gCubeSnapshot) + { + addTrianglesDrawn(info->mCount); + } + } + } + + if (hasRenderType(LLPipeline::RENDER_TYPE_PASS_ALPHA)) + { + LLSpatialGroup::draw_map_t::iterator alpha = group->mDrawMap.find(LLRenderPass::PASS_ALPHA); + + if (alpha != group->mDrawMap.end()) + { // store alpha groups for sorting + LLSpatialBridge *bridge = group->getSpatialPartition()->asBridge(); + if (LLViewerCamera::sCurCameraID == LLViewerCamera::CAMERA_WORLD && !gCubeSnapshot) + { + if (bridge) + { + LLCamera trans_camera = bridge->transformCamera(camera); + group->updateDistance(trans_camera); + } + else + { + group->updateDistance(camera); + } + } + + if (hasRenderType(LLDrawPool::POOL_ALPHA)) + { + sCull->pushAlphaGroup(group); + } + } + + LLSpatialGroup::draw_map_t::iterator rigged_alpha = group->mDrawMap.find(LLRenderPass::PASS_ALPHA_RIGGED); + + if (rigged_alpha != group->mDrawMap.end()) + { // store rigged alpha groups for LLDrawPoolAlpha prepass (skip distance update, rigged attachments use depth buffer) + if (hasRenderType(LLDrawPool::POOL_ALPHA)) + { + sCull->pushRiggedAlphaGroup(group); + } + } + } + } + + /*bool use_transform_feedback = gTransformPositionProgram.mProgramObject && !mMeshDirtyGroup.empty(); + + if (use_transform_feedback) + { //place a query around potential transform feedback code for synchronization + mTransformFeedbackPrimitives = 0; + + if (!mMeshDirtyQueryObject) + { + glGenQueries(1, &mMeshDirtyQueryObject); + } + + + glBeginQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, mMeshDirtyQueryObject); + }*/ + + // pack vertex buffers for groups that chose to delay their updates + { + LL_PROFILE_GPU_ZONE("rebuildMesh"); + for (LLSpatialGroup::sg_vector_t::iterator iter = mMeshDirtyGroup.begin(); iter != mMeshDirtyGroup.end(); ++iter) + { + (*iter)->rebuildMesh(); + } + } + + /*if (use_transform_feedback) + { + glEndQuery(GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN); + }*/ + + mMeshDirtyGroup.clear(); + + if (!sShadowRender) + { + // order alpha groups by distance + std::sort(sCull->beginAlphaGroups(), sCull->endAlphaGroups(), LLSpatialGroup::CompareDepthGreater()); + + // order rigged alpha groups by avatar attachment order + std::sort(sCull->beginRiggedAlphaGroups(), sCull->endRiggedAlphaGroups(), LLSpatialGroup::CompareRenderOrder()); + } + + LL_PUSH_CALLSTACKS(); + // only render if the flag is set. The flag is only set if we are in edit mode or the toggle is set in the menus + if (LLFloaterReg::instanceVisible("beacons") && !sShadowRender && !gCubeSnapshot) + { + if (sRenderScriptedTouchBeacons) + { + // Only show the beacon on the root object. + forAllVisibleDrawables(renderScriptedTouchBeacons); + } + else if (sRenderScriptedBeacons) + { + // Only show the beacon on the root object. + forAllVisibleDrawables(renderScriptedBeacons); + } + + if (sRenderPhysicalBeacons) + { + // Only show the beacon on the root object. + forAllVisibleDrawables(renderPhysicalBeacons); + } + + if (sRenderMOAPBeacons) + { + forAllVisibleDrawables(renderMOAPBeacons); + } + + if (sRenderParticleBeacons) + { + forAllVisibleDrawables(renderParticleBeacons); + } + + // If god mode, also show audio cues + if (sRenderSoundBeacons && gAudiop) + { + // Walk all sound sources and render out beacons for them. Note, this isn't done in the ForAllVisibleDrawables function, because + // some are not visible. + LLAudioEngine::source_map::iterator iter; + for (iter = gAudiop->mAllSources.begin(); iter != gAudiop->mAllSources.end(); ++iter) + { + LLAudioSource *sourcep = iter->second; + + LLVector3d pos_global = sourcep->getPositionGlobal(); + LLVector3 pos = gAgent.getPosAgentFromGlobal(pos_global); + if (gPipeline.sRenderBeacons) + { + // pos += LLVector3(0.f, 0.f, 0.2f); + gObjectList.addDebugBeacon(pos, "", LLColor4(1.f, 1.f, 0.f, 0.5f), LLColor4(1.f, 1.f, 1.f, 0.5f), DebugBeaconLineWidth); + } + } + // now deal with highlights for all those seeable sound sources + forAllVisibleDrawables(renderSoundHighlights); + } + } + LL_PUSH_CALLSTACKS(); + // If managing your telehub, draw beacons at telehub and currently selected spawnpoint. + if (LLFloaterTelehub::renderBeacons() && !sShadowRender && !gCubeSnapshot) + { + LLFloaterTelehub::addBeacons(); + } + + if (!sShadowRender && !gCubeSnapshot) + { + mSelectedFaces.clear(); + + if (!gNonInteractive) + { + LLPipeline::setRenderHighlightTextureChannel(gFloaterTools->getPanelFace()->getTextureChannelToEdit()); + } + + // Draw face highlights for selected faces. + if (LLSelectMgr::getInstance()->getTEMode()) + { + struct f : public LLSelectedTEFunctor + { + virtual bool apply(LLViewerObject *object, S32 te) + { + if (object->mDrawable) + { + LLFace *facep = object->mDrawable->getFace(te); + if (facep) + { + gPipeline.mSelectedFaces.push_back(facep); + } + } + return true; + } + } func; + LLSelectMgr::getInstance()->getSelection()->applyToTEs(&func); + } + } + + // LLSpatialGroup::sNoDelete = false; + LL_PUSH_CALLSTACKS(); +} + + +void render_hud_elements() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; //LL_RECORD_BLOCK_TIME(FTM_RENDER_UI); + gPipeline.disableLights(); + + LLGLSUIDefault gls_ui; + + //LLGLEnable stencil(GL_STENCIL_TEST); + //glStencilFunc(GL_ALWAYS, 255, 0xFFFFFFFF); + //glStencilMask(0xFFFFFFFF); + //glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); + + gUIProgram.bind(); + gGL.color4f(1, 1, 1, 1); + LLGLDepthTest depth(GL_TRUE, GL_FALSE); + + if (!LLPipeline::sReflectionRender && gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) + { + gViewerWindow->renderSelections(false, false, false); // For HUD version in render_ui_3d() + + // Draw the tracking overlays + LLTracker::render3D(); + + if (LLWorld::instanceExists()) + { + // Show the property lines + LLWorld::getInstance()->renderPropertyLines(); + } + LLViewerParcelMgr::getInstance()->render(); + LLViewerParcelMgr::getInstance()->renderParcelCollision(); + } + else if (gForceRenderLandFence) + { + // This is only set when not rendering the UI, for parcel snapshots + LLViewerParcelMgr::getInstance()->render(); + } + else if (gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_HUD)) + { + LLHUDText::renderAllHUD(); + } + + gUIProgram.unbind(); +} + +void LLPipeline::renderHighlights() +{ + assertInitialized(); + + // Draw 3D UI elements here (before we clear the Z buffer in POOL_HUD) + // Render highlighted faces. + LLGLSPipelineAlpha gls_pipeline_alpha; + LLColor4 color(1.f, 1.f, 1.f, 0.5f); + disableLights(); + + if ((LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_INTERFACE) > 0)) + { + gHighlightProgram.bind(); + gGL.diffuseColor4f(1,1,1,0.5f); + } + + if (hasRenderDebugFeatureMask(RENDER_DEBUG_FEATURE_SELECTED) && !mFaceSelectImagep) + { + mFaceSelectImagep = LLViewerTextureManager::getFetchedTexture(IMG_FACE_SELECT); + } + + if (hasRenderDebugFeatureMask(RENDER_DEBUG_FEATURE_SELECTED) && (sRenderHighlightTextureChannel == LLRender::DIFFUSE_MAP)) + { + // Make sure the selection image gets downloaded and decoded + mFaceSelectImagep->addTextureStats((F32)MAX_IMAGE_AREA); + + U32 count = mSelectedFaces.size(); + for (U32 i = 0; i < count; i++) + { + LLFace *facep = mSelectedFaces[i]; + if (!facep || facep->getDrawable()->isDead()) + { + LL_ERRS() << "Bad face on selection" << LL_ENDL; + return; + } + + facep->renderSelected(mFaceSelectImagep, color); + } + } + + if (hasRenderDebugFeatureMask(RENDER_DEBUG_FEATURE_SELECTED)) + { + // Paint 'em red! + color.setVec(1.f, 0.f, 0.f, 0.5f); + + int count = mHighlightFaces.size(); + for (S32 i = 0; i < count; i++) + { + LLFace* facep = mHighlightFaces[i]; + facep->renderSelected(LLViewerTexture::sNullImagep, color); + } + } + + // Contains a list of the faces of objects that are physical or + // have touch-handlers. + mHighlightFaces.clear(); + + if (LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_INTERFACE) > 0) + { + gHighlightProgram.unbind(); + } + + + if (hasRenderDebugFeatureMask(RENDER_DEBUG_FEATURE_SELECTED) && (sRenderHighlightTextureChannel == LLRender::NORMAL_MAP)) + { + color.setVec(1.0f, 0.5f, 0.5f, 0.5f); + if ((LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_INTERFACE) > 0)) + { + gHighlightNormalProgram.bind(); + gGL.diffuseColor4f(1,1,1,0.5f); + } + + mFaceSelectImagep->addTextureStats((F32)MAX_IMAGE_AREA); + + U32 count = mSelectedFaces.size(); + for (U32 i = 0; i < count; i++) + { + LLFace *facep = mSelectedFaces[i]; + if (!facep || facep->getDrawable()->isDead()) + { + LL_ERRS() << "Bad face on selection" << LL_ENDL; + return; + } + + facep->renderSelected(mFaceSelectImagep, color); + } + + if ((LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_INTERFACE) > 0)) + { + gHighlightNormalProgram.unbind(); + } + } + + if (hasRenderDebugFeatureMask(RENDER_DEBUG_FEATURE_SELECTED) && (sRenderHighlightTextureChannel == LLRender::SPECULAR_MAP)) + { + color.setVec(0.0f, 0.3f, 1.0f, 0.8f); + if ((LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_INTERFACE) > 0)) + { + gHighlightSpecularProgram.bind(); + gGL.diffuseColor4f(1,1,1,0.5f); + } + + mFaceSelectImagep->addTextureStats((F32)MAX_IMAGE_AREA); + + U32 count = mSelectedFaces.size(); + for (U32 i = 0; i < count; i++) + { + LLFace *facep = mSelectedFaces[i]; + if (!facep || facep->getDrawable()->isDead()) + { + LL_ERRS() << "Bad face on selection" << LL_ENDL; + return; + } + + facep->renderSelected(mFaceSelectImagep, color); + } + + if ((LLViewerShaderMgr::instance()->getShaderLevel(LLViewerShaderMgr::SHADER_INTERFACE) > 0)) + { + gHighlightSpecularProgram.unbind(); + } + } +} + +//debug use +U32 LLPipeline::sCurRenderPoolType = 0 ; + +void LLPipeline::renderGeomDeferred(LLCamera& camera, bool do_occlusion) +{ + LLAppViewer::instance()->pingMainloopTimeout("Pipeline:RenderGeomDeferred"); + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; //LL_RECORD_BLOCK_TIME(FTM_RENDER_GEOMETRY); + LL_PROFILE_GPU_ZONE("renderGeomDeferred"); + + llassert(!sRenderingHUDs); + + if (gUseWireframe) + { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + } + + if (&camera == LLViewerCamera::getInstance()) + { // a bit hacky, this is the start of the main render frame, figure out delta between last modelview matrix and + // current modelview matrix + glh::matrix4f last_modelview(gGLLastModelView); + glh::matrix4f cur_modelview(gGLModelView); + + // goal is to have a matrix here that goes from the last frame's camera space to the current frame's camera space + glh::matrix4f m = last_modelview.inverse(); // last camera space to world space + m.mult_left(cur_modelview); // world space to camera space + + glh::matrix4f n = m.inverse(); + + for (U32 i = 0; i < 16; ++i) + { + gGLDeltaModelView[i] = m.m[i]; + gGLInverseDeltaModelView[i] = n.m[i]; + } + } + + bool occlude = LLPipeline::sUseOcclusion > 1 && do_occlusion && !LLGLSLShader::sProfileEnabled; + + setupHWLights(); + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("deferred pools"); + + LLGLEnable cull(GL_CULL_FACE); + + for (pool_set_t::iterator iter = mPools.begin(); iter != mPools.end(); ++iter) + { + LLDrawPool *poolp = *iter; + if (hasRenderType(poolp->getType())) + { + poolp->prerender(); + } + } + + LLVertexBuffer::unbind(); + + LLGLState::checkStates(); + + if (LLViewerShaderMgr::instance()->mShaderLevel[LLViewerShaderMgr::SHADER_DEFERRED] > 1) + { + //update reflection probe uniform + mReflectionMapManager.updateUniforms(); + } + + U32 cur_type = 0; + + gGL.setColorMask(true, true); + + pool_set_t::iterator iter1 = mPools.begin(); + + while ( iter1 != mPools.end() ) + { + LLDrawPool *poolp = *iter1; + + cur_type = poolp->getType(); + + if (occlude && cur_type >= LLDrawPool::POOL_GRASS) + { + llassert(!gCubeSnapshot); // never do occlusion culling on cube snapshots + occlude = false; + gGLLastMatrix = NULL; + gGL.loadMatrix(gGLModelView); + doOcclusion(camera); + } + + pool_set_t::iterator iter2 = iter1; + if (hasRenderType(poolp->getType()) && poolp->getNumDeferredPasses() > 0) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("deferred pool render"); + + gGLLastMatrix = NULL; + gGL.loadMatrix(gGLModelView); + + for( S32 i = 0; i < poolp->getNumDeferredPasses(); i++ ) + { + LLVertexBuffer::unbind(); + poolp->beginDeferredPass(i); + for (iter2 = iter1; iter2 != mPools.end(); iter2++) + { + LLDrawPool *p = *iter2; + if (p->getType() != cur_type) + { + break; + } + + if ( !p->getSkipRenderFlag() ) { p->renderDeferred(i); } + } + poolp->endDeferredPass(i); + LLVertexBuffer::unbind(); + + LLGLState::checkStates(); + } + } + else + { + // Skip all pools of this type + for (iter2 = iter1; iter2 != mPools.end(); iter2++) + { + LLDrawPool *p = *iter2; + if (p->getType() != cur_type) + { + break; + } + } + } + iter1 = iter2; + stop_glerror(); + } + + gGLLastMatrix = NULL; + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.loadMatrix(gGLModelView); + + gGL.setColorMask(true, false); + + } // Tracy ZoneScoped + + if (gUseWireframe) + { + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } +} + +void LLPipeline::renderGeomPostDeferred(LLCamera& camera) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + LL_PROFILE_GPU_ZONE("renderGeomPostDeferred"); + + if (gUseWireframe) + { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + } + + U32 cur_type = 0; + + LLGLEnable cull(GL_CULL_FACE); + + bool done_atmospherics = LLPipeline::sRenderingHUDs; //skip atmospherics on huds + bool done_water_haze = done_atmospherics; + + // do atmospheric haze just before post water alpha + U32 atmospherics_pass = LLDrawPool::POOL_ALPHA_POST_WATER; + + if (LLPipeline::sUnderWaterRender) + { // if under water, do atmospherics just before the water pass + atmospherics_pass = LLDrawPool::POOL_WATER; + } + + // do water haze just before pre water alpha + U32 water_haze_pass = LLDrawPool::POOL_ALPHA_PRE_WATER; + + calcNearbyLights(camera); + setupHWLights(); + + gGL.setSceneBlendType(LLRender::BT_ALPHA); + gGL.setColorMask(true, false); + + pool_set_t::iterator iter1 = mPools.begin(); + + if (gDebugGL || gDebugPipeline) + { + LLGLState::checkStates(GL_FALSE); + } + + while ( iter1 != mPools.end() ) + { + LLDrawPool *poolp = *iter1; + + cur_type = poolp->getType(); + + if (cur_type >= atmospherics_pass && !done_atmospherics) + { // do atmospherics against depth buffer before rendering alpha + doAtmospherics(); + done_atmospherics = true; + } + + if (cur_type >= water_haze_pass && !done_water_haze) + { // do water haze against depth buffer before rendering alpha + doWaterHaze(); + done_water_haze = true; + } + + pool_set_t::iterator iter2 = iter1; + if (hasRenderType(poolp->getType()) && poolp->getNumPostDeferredPasses() > 0) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("deferred poolrender"); + + gGLLastMatrix = NULL; + gGL.loadMatrix(gGLModelView); + + for( S32 i = 0; i < poolp->getNumPostDeferredPasses(); i++ ) + { + LLVertexBuffer::unbind(); + poolp->beginPostDeferredPass(i); + for (iter2 = iter1; iter2 != mPools.end(); iter2++) + { + LLDrawPool *p = *iter2; + if (p->getType() != cur_type) + { + break; + } + + p->renderPostDeferred(i); + } + poolp->endPostDeferredPass(i); + LLVertexBuffer::unbind(); + + if (gDebugGL || gDebugPipeline) + { + LLGLState::checkStates(GL_FALSE); + } + } + } + else + { + // Skip all pools of this type + for (iter2 = iter1; iter2 != mPools.end(); iter2++) + { + LLDrawPool *p = *iter2; + if (p->getType() != cur_type) + { + break; + } + } + } + iter1 = iter2; + stop_glerror(); + } + + gGLLastMatrix = NULL; + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.loadMatrix(gGLModelView); + + if (!gCubeSnapshot) + { + // debug displays + renderHighlights(); + mHighlightFaces.clear(); + + renderDebug(); + } + + if (gUseWireframe) + { + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } +} + +void LLPipeline::renderGeomShadow(LLCamera& camera) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + LL_PROFILE_GPU_ZONE("renderGeomShadow"); + U32 cur_type = 0; + + LLGLEnable cull(GL_CULL_FACE); + + LLVertexBuffer::unbind(); + + pool_set_t::iterator iter1 = mPools.begin(); + + while ( iter1 != mPools.end() ) + { + LLDrawPool *poolp = *iter1; + + cur_type = poolp->getType(); + + pool_set_t::iterator iter2 = iter1; + if (hasRenderType(poolp->getType()) && poolp->getNumShadowPasses() > 0) + { + poolp->prerender() ; + + gGLLastMatrix = NULL; + gGL.loadMatrix(gGLModelView); + + for( S32 i = 0; i < poolp->getNumShadowPasses(); i++ ) + { + LLVertexBuffer::unbind(); + poolp->beginShadowPass(i); + for (iter2 = iter1; iter2 != mPools.end(); iter2++) + { + LLDrawPool *p = *iter2; + if (p->getType() != cur_type) + { + break; + } + + p->renderShadow(i); + } + poolp->endShadowPass(i); + LLVertexBuffer::unbind(); + } + } + else + { + // Skip all pools of this type + for (iter2 = iter1; iter2 != mPools.end(); iter2++) + { + LLDrawPool *p = *iter2; + if (p->getType() != cur_type) + { + break; + } + } + } + iter1 = iter2; + stop_glerror(); + } + + gGLLastMatrix = NULL; + gGL.loadMatrix(gGLModelView); +} + + +static U32 sIndicesDrawnCount = 0; + +void LLPipeline::addTrianglesDrawn(S32 index_count) +{ + sIndicesDrawnCount += index_count; +} + +void LLPipeline::recordTrianglesDrawn() +{ + assertInitialized(); + U32 count = sIndicesDrawnCount / 3; + sIndicesDrawnCount = 0; + add(LLStatViewer::TRIANGLES_DRAWN, LLUnits::Triangles::fromValue(count)); +} + +void LLPipeline::renderPhysicsDisplay() +{ + if (!hasRenderDebugMask(LLPipeline::RENDER_DEBUG_PHYSICS_SHAPES)) + { + return; + } + + gGL.flush(); + gDebugProgram.bind(); + + LLGLEnable(GL_POLYGON_OFFSET_LINE); + glPolygonOffset(3.f, 3.f); + glLineWidth(3.f); + LLGLEnable blend(GL_BLEND); + gGL.setSceneBlendType(LLRender::BT_ALPHA); + + for (int pass = 0; pass < 3; ++pass) + { + // pass 0 - depth write enabled, color write disabled, fill + // pass 1 - depth write disabled, color write enabled, fill + // pass 2 - depth write disabled, color write enabled, wireframe + gGL.setColorMask(pass >= 1, false); + LLGLDepthTest depth(GL_TRUE, pass == 0); + + bool wireframe = (pass == 2); + + if (wireframe) + { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + } + + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* region = *iter; + for (U32 i = 0; i < LLViewerRegion::NUM_PARTITIONS; i++) + { + LLSpatialPartition* part = region->getSpatialPartition(i); + if (part) + { + if (hasRenderType(part->mDrawableType)) + { + part->renderPhysicsShapes(wireframe); + } + } + } + } + gGL.flush(); + + if (wireframe) + { + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } + } + glLineWidth(1.f); + gDebugProgram.unbind(); + +} + +extern std::set visible_selected_groups; + +void LLPipeline::renderDebug() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + + assertInitialized(); + + bool hud_only = hasRenderType(LLPipeline::RENDER_TYPE_HUD); + + if (!hud_only ) + { + //Render any navmesh geometry + LLPathingLib *llPathingLibInstance = LLPathingLib::getInstance(); + if ( llPathingLibInstance != NULL ) + { + //character floater renderables + + LLHandle pathfindingCharacterHandle = LLFloaterPathfindingCharacters::getInstanceHandle(); + if ( !pathfindingCharacterHandle.isDead() ) + { + LLFloaterPathfindingCharacters *pathfindingCharacter = pathfindingCharacterHandle.get(); + + if ( pathfindingCharacter->getVisible() || gAgentCamera.cameraMouselook() ) + { + gPathfindingProgram.bind(); + gPathfindingProgram.uniform1f(sTint, 1.f); + gPathfindingProgram.uniform1f(sAmbiance, 1.f); + gPathfindingProgram.uniform1f(sAlphaScale, 1.f); + + //Requried character physics capsule render parameters + LLUUID id; + LLVector3 pos; + LLQuaternion rot; + + if ( pathfindingCharacter->isPhysicsCapsuleEnabled( id, pos, rot ) ) + { + //remove blending artifacts + gGL.setColorMask(false, false); + llPathingLibInstance->renderSimpleShapeCapsuleID( gGL, id, pos, rot ); + gGL.setColorMask(true, false); + LLGLEnable blend(GL_BLEND); + gPathfindingProgram.uniform1f(sAlphaScale, 0.90f); + llPathingLibInstance->renderSimpleShapeCapsuleID( gGL, id, pos, rot ); + gPathfindingProgram.bind(); + } + } + } + + + //pathing console renderables + LLHandle pathfindingConsoleHandle = LLFloaterPathfindingConsole::getInstanceHandle(); + if (!pathfindingConsoleHandle.isDead()) + { + LLFloaterPathfindingConsole *pathfindingConsole = pathfindingConsoleHandle.get(); + + if ( pathfindingConsole->getVisible() || gAgentCamera.cameraMouselook() ) + { + F32 ambiance = gSavedSettings.getF32("PathfindingAmbiance"); + + gPathfindingProgram.bind(); + + gPathfindingProgram.uniform1f(sTint, 1.f); + gPathfindingProgram.uniform1f(sAmbiance, ambiance); + gPathfindingProgram.uniform1f(sAlphaScale, 1.f); + + if ( !pathfindingConsole->isRenderWorld() ) + { + const LLColor4 clearColor = gSavedSettings.getColor4("PathfindingNavMeshClear"); + gGL.setColorMask(true, true); + glClearColor(clearColor.mV[0],clearColor.mV[1],clearColor.mV[2],0); + glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); // no stencil -- deprecated | GL_STENCIL_BUFFER_BIT); + gGL.setColorMask(true, false); + glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + } + + //NavMesh + if ( pathfindingConsole->isRenderNavMesh() ) + { + gGL.flush(); + glLineWidth(2.0f); + LLGLEnable cull(GL_CULL_FACE); + LLGLDisable blend(GL_BLEND); + + if ( pathfindingConsole->isRenderWorld() ) + { + LLGLEnable blend(GL_BLEND); + gPathfindingProgram.uniform1f(sAlphaScale, 0.66f); + llPathingLibInstance->renderNavMesh(); + } + else + { + llPathingLibInstance->renderNavMesh(); + } + + //render edges + gPathfindingNoNormalsProgram.bind(); + gPathfindingNoNormalsProgram.uniform1f(sTint, 1.f); + gPathfindingNoNormalsProgram.uniform1f(sAlphaScale, 1.f); + llPathingLibInstance->renderNavMeshEdges(); + gPathfindingProgram.bind(); + + gGL.flush(); + glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + glLineWidth(1.0f); + gGL.flush(); + } + //User designated path + if ( LLPathfindingPathTool::getInstance()->isRenderPath() ) + { + //The path + gUIProgram.bind(); + gGL.getTexUnit(0)->bind(LLViewerFetchedTexture::sWhiteImagep); + llPathingLibInstance->renderPath(); + gPathfindingProgram.bind(); + + //The bookends + //remove blending artifacts + gGL.setColorMask(false, false); + llPathingLibInstance->renderPathBookend( gGL, LLPathingLib::LLPL_START ); + llPathingLibInstance->renderPathBookend( gGL, LLPathingLib::LLPL_END ); + + gGL.setColorMask(true, false); + //render the bookends + LLGLEnable blend(GL_BLEND); + gPathfindingProgram.uniform1f(sAlphaScale, 0.90f); + llPathingLibInstance->renderPathBookend( gGL, LLPathingLib::LLPL_START ); + llPathingLibInstance->renderPathBookend( gGL, LLPathingLib::LLPL_END ); + gPathfindingProgram.bind(); + } + + if ( pathfindingConsole->isRenderWaterPlane() ) + { + LLGLEnable blend(GL_BLEND); + gPathfindingProgram.uniform1f(sAlphaScale, 0.90f); + llPathingLibInstance->renderSimpleShapes( gGL, gAgent.getRegion()->getWaterHeight() ); + } + //physics/exclusion shapes + if ( pathfindingConsole->isRenderAnyShapes() ) + { + U32 render_order[] = { + 1 << LLPathingLib::LLST_ObstacleObjects, + 1 << LLPathingLib::LLST_WalkableObjects, + 1 << LLPathingLib::LLST_ExclusionPhantoms, + 1 << LLPathingLib::LLST_MaterialPhantoms, + }; + + U32 flags = pathfindingConsole->getRenderShapeFlags(); + + for (U32 i = 0; i < 4; i++) + { + if (!(flags & render_order[i])) + { + continue; + } + + //turn off backface culling for volumes so they are visible when camera is inside volume + LLGLDisable cull(i >= 2 ? GL_CULL_FACE : 0); + + gGL.flush(); + glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + + //get rid of some z-fighting + LLGLEnable polyOffset(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(1.0f, 1.0f); + + //render to depth first to avoid blending artifacts + gGL.setColorMask(false, false); + llPathingLibInstance->renderNavMeshShapesVBO( render_order[i] ); + gGL.setColorMask(true, false); + + //get rid of some z-fighting + glPolygonOffset(0.f, 0.f); + + LLGLEnable blend(GL_BLEND); + + { + gPathfindingProgram.uniform1f(sAmbiance, ambiance); + + { //draw solid overlay + LLGLDepthTest depth(GL_TRUE, GL_FALSE, GL_LEQUAL); + llPathingLibInstance->renderNavMeshShapesVBO( render_order[i] ); + gGL.flush(); + } + + LLGLEnable lineOffset(GL_POLYGON_OFFSET_LINE); + glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + + F32 offset = gSavedSettings.getF32("PathfindingLineOffset"); + + if (pathfindingConsole->isRenderXRay()) + { + gPathfindingProgram.uniform1f(sTint, gSavedSettings.getF32("PathfindingXRayTint")); + gPathfindingProgram.uniform1f(sAlphaScale, gSavedSettings.getF32("PathfindingXRayOpacity")); + LLGLEnable blend(GL_BLEND); + LLGLDepthTest depth(GL_TRUE, GL_FALSE, GL_GREATER); + + glPolygonOffset(offset, -offset); + + if (gSavedSettings.getBOOL("PathfindingXRayWireframe")) + { //draw hidden wireframe as darker and less opaque + gPathfindingProgram.uniform1f(sAmbiance, 1.f); + llPathingLibInstance->renderNavMeshShapesVBO( render_order[i] ); + } + else + { + glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + gPathfindingProgram.uniform1f(sAmbiance, ambiance); + llPathingLibInstance->renderNavMeshShapesVBO( render_order[i] ); + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + } + } + + { //draw visible wireframe as brighter, thicker and more opaque + glPolygonOffset(offset, offset); + gPathfindingProgram.uniform1f(sAmbiance, 1.f); + gPathfindingProgram.uniform1f(sTint, 1.f); + gPathfindingProgram.uniform1f(sAlphaScale, 1.f); + + glLineWidth(gSavedSettings.getF32("PathfindingLineWidth")); + LLGLDisable blendOut(GL_BLEND); + llPathingLibInstance->renderNavMeshShapesVBO( render_order[i] ); + gGL.flush(); + glLineWidth(1.f); + } + + glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + } + } + } + + glPolygonOffset(0.f, 0.f); + + if ( pathfindingConsole->isRenderNavMesh() && pathfindingConsole->isRenderXRay() ) + { //render navmesh xray + F32 ambiance = gSavedSettings.getF32("PathfindingAmbiance"); + + LLGLEnable lineOffset(GL_POLYGON_OFFSET_LINE); + LLGLEnable polyOffset(GL_POLYGON_OFFSET_FILL); + + F32 offset = gSavedSettings.getF32("PathfindingLineOffset"); + glPolygonOffset(offset, -offset); + + LLGLEnable blend(GL_BLEND); + LLGLDepthTest depth(GL_TRUE, GL_FALSE, GL_GREATER); + gGL.flush(); + glLineWidth(2.0f); + LLGLEnable cull(GL_CULL_FACE); + + gPathfindingProgram.uniform1f(sTint, gSavedSettings.getF32("PathfindingXRayTint")); + gPathfindingProgram.uniform1f(sAlphaScale, gSavedSettings.getF32("PathfindingXRayOpacity")); + + if (gSavedSettings.getBOOL("PathfindingXRayWireframe")) + { //draw hidden wireframe as darker and less opaque + glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + gPathfindingProgram.uniform1f(sAmbiance, 1.f); + llPathingLibInstance->renderNavMesh(); + glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); + } + else + { + gPathfindingProgram.uniform1f(sAmbiance, ambiance); + llPathingLibInstance->renderNavMesh(); + } + + //render edges + gPathfindingNoNormalsProgram.bind(); + gPathfindingNoNormalsProgram.uniform1f(sTint, gSavedSettings.getF32("PathfindingXRayTint")); + gPathfindingNoNormalsProgram.uniform1f(sAlphaScale, gSavedSettings.getF32("PathfindingXRayOpacity")); + llPathingLibInstance->renderNavMeshEdges(); + gPathfindingProgram.bind(); + + gGL.flush(); + glLineWidth(1.0f); + } + + glPolygonOffset(0.f, 0.f); + + gGL.flush(); + gPathfindingProgram.unbind(); + } + } + } + } + + gGLLastMatrix = NULL; + gGL.loadMatrix(gGLModelView); + gGL.setColorMask(true, false); + + + if (!hud_only && !mDebugBlips.empty()) + { //render debug blips + gUIProgram.bind(); + gGL.color4f(1, 1, 1, 1); + + gGL.getTexUnit(0)->bind(LLViewerFetchedTexture::sWhiteImagep, true); + + glPointSize(8.f); + LLGLDepthTest depth(GL_TRUE, GL_TRUE, GL_ALWAYS); + + gGL.begin(LLRender::POINTS); + for (std::list::iterator iter = mDebugBlips.begin(); iter != mDebugBlips.end(); ) + { + DebugBlip& blip = *iter; + + blip.mAge += gFrameIntervalSeconds.value(); + if (blip.mAge > 2.f) + { + mDebugBlips.erase(iter++); + } + else + { + iter++; + } + + blip.mPosition.mV[2] += gFrameIntervalSeconds.value()*2.f; + + gGL.color4fv(blip.mColor.mV); + gGL.vertex3fv(blip.mPosition.mV); + } + gGL.end(); + gGL.flush(); + glPointSize(1.f); + } + + // Debug stuff. + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* region = *iter; + for (U32 i = 0; i < LLViewerRegion::NUM_PARTITIONS; i++) + { + LLSpatialPartition* part = region->getSpatialPartition(i); + if (part) + { + if ( (hud_only && (part->mDrawableType == RENDER_TYPE_HUD || part->mDrawableType == RENDER_TYPE_HUD_PARTICLES)) || + (!hud_only && hasRenderType(part->mDrawableType)) ) + { + part->renderDebug(); + } + } + } + } + + for (LLCullResult::bridge_iterator i = sCull->beginVisibleBridge(); i != sCull->endVisibleBridge(); ++i) + { + LLSpatialBridge* bridge = *i; + if (!bridge->isDead() && hasRenderType(bridge->mDrawableType)) + { + gGL.pushMatrix(); + gGL.multMatrix((F32*)bridge->mDrawable->getRenderMatrix().mMatrix); + bridge->renderDebug(); + gGL.popMatrix(); + } + } + + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_OCCLUSION)) + { //render visible selected group occlusion geometry + gDebugProgram.bind(); + LLGLDepthTest depth(GL_TRUE, GL_FALSE); + gGL.diffuseColor3f(1,0,1); + for (std::set::iterator iter = visible_selected_groups.begin(); iter != visible_selected_groups.end(); ++iter) + { + LLSpatialGroup* group = *iter; + + LLVector4a fudge; + fudge.splat(0.25f); //SG_OCCLUSION_FUDGE + + LLVector4a size; + const LLVector4a* bounds = group->getBounds(); + size.setAdd(fudge, bounds[1]); + + drawBox(bounds[0], size); + } + } + + visible_selected_groups.clear(); + + //draw reflection probes and links between them + if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_REFLECTION_PROBES) && !hud_only) + { + mReflectionMapManager.renderDebug(); + } + + if (gSavedSettings.getBOOL("RenderReflectionProbeVolumes") && !hud_only) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("probe debug display"); + + bindDeferredShader(gReflectionProbeDisplayProgram, NULL); + mScreenTriangleVB->setBuffer(); + + LLGLEnable blend(GL_BLEND); + LLGLDepthTest depth(GL_FALSE); + + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + + unbindDeferredShader(gReflectionProbeDisplayProgram); + } + + gUIProgram.bind(); + + if (hasRenderDebugMask(LLPipeline::RENDER_DEBUG_RAYCAST) && !hud_only) + { //draw crosshairs on particle intersection + if (gDebugRaycastParticle) + { + gDebugProgram.bind(); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + LLVector3 center(gDebugRaycastParticleIntersection.getF32ptr()); + LLVector3 size(0.1f, 0.1f, 0.1f); + + LLVector3 p[6]; + + p[0] = center + size.scaledVec(LLVector3(1,0,0)); + p[1] = center + size.scaledVec(LLVector3(-1,0,0)); + p[2] = center + size.scaledVec(LLVector3(0,1,0)); + p[3] = center + size.scaledVec(LLVector3(0,-1,0)); + p[4] = center + size.scaledVec(LLVector3(0,0,1)); + p[5] = center + size.scaledVec(LLVector3(0,0,-1)); + + gGL.begin(LLRender::LINES); + gGL.diffuseColor3f(1.f, 1.f, 0.f); + for (U32 i = 0; i < 6; i++) + { + gGL.vertex3fv(p[i].mV); + } + gGL.end(); + gGL.flush(); + + gDebugProgram.unbind(); + } + } + + if (hasRenderDebugMask(LLPipeline::RENDER_DEBUG_SHADOW_FRUSTA) && !hud_only) + { + LLVertexBuffer::unbind(); + + LLGLEnable blend(GL_BLEND); + LLGLDepthTest depth(true, false); + LLGLDisable cull(GL_CULL_FACE); + + gGL.color4f(1,1,1,1); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + F32 a = 0.1f; + + F32 col[] = + { + 1,0,0,a, + 0,1,0,a, + 0,0,1,a, + 1,0,1,a, + + 1,1,0,a, + 0,1,1,a, + 1,1,1,a, + 1,0,1,a, + }; + + for (U32 i = 0; i < 8; i++) + { + LLVector3* frust = mShadowCamera[i].mAgentFrustum; + + if (i > 3) + { //render shadow frusta as volumes + if (mShadowFrustPoints[i-4].empty()) + { + continue; + } + + gGL.color4fv(col+(i-4)*4); + + gGL.begin(LLRender::TRIANGLE_STRIP); + gGL.vertex3fv(frust[0].mV); gGL.vertex3fv(frust[4].mV); + gGL.vertex3fv(frust[1].mV); gGL.vertex3fv(frust[5].mV); + gGL.vertex3fv(frust[2].mV); gGL.vertex3fv(frust[6].mV); + gGL.vertex3fv(frust[3].mV); gGL.vertex3fv(frust[7].mV); + gGL.vertex3fv(frust[0].mV); gGL.vertex3fv(frust[4].mV); + gGL.end(); + + + gGL.begin(LLRender::TRIANGLE_STRIP); + gGL.vertex3fv(frust[0].mV); + gGL.vertex3fv(frust[1].mV); + gGL.vertex3fv(frust[3].mV); + gGL.vertex3fv(frust[2].mV); + gGL.end(); + + gGL.begin(LLRender::TRIANGLE_STRIP); + gGL.vertex3fv(frust[4].mV); + gGL.vertex3fv(frust[5].mV); + gGL.vertex3fv(frust[7].mV); + gGL.vertex3fv(frust[6].mV); + gGL.end(); + } + + + if (i < 4) + { + + //if (i == 0 || !mShadowFrustPoints[i].empty()) + { + //render visible point cloud + gGL.flush(); + glPointSize(8.f); + gGL.begin(LLRender::POINTS); + + F32* c = col+i*4; + gGL.color3fv(c); + + for (U32 j = 0; j < mShadowFrustPoints[i].size(); ++j) + { + gGL.vertex3fv(mShadowFrustPoints[i][j].mV); + + } + gGL.end(); + + gGL.flush(); + glPointSize(1.f); + + LLVector3* ext = mShadowExtents[i]; + LLVector3 pos = (ext[0]+ext[1])*0.5f; + LLVector3 size = (ext[1]-ext[0])*0.5f; + drawBoxOutline(pos, size); + + //render camera frustum splits as outlines + gGL.begin(LLRender::LINES); + gGL.vertex3fv(frust[0].mV); gGL.vertex3fv(frust[1].mV); + gGL.vertex3fv(frust[1].mV); gGL.vertex3fv(frust[2].mV); + gGL.vertex3fv(frust[2].mV); gGL.vertex3fv(frust[3].mV); + gGL.vertex3fv(frust[3].mV); gGL.vertex3fv(frust[0].mV); + gGL.vertex3fv(frust[4].mV); gGL.vertex3fv(frust[5].mV); + gGL.vertex3fv(frust[5].mV); gGL.vertex3fv(frust[6].mV); + gGL.vertex3fv(frust[6].mV); gGL.vertex3fv(frust[7].mV); + gGL.vertex3fv(frust[7].mV); gGL.vertex3fv(frust[4].mV); + gGL.vertex3fv(frust[0].mV); gGL.vertex3fv(frust[4].mV); + gGL.vertex3fv(frust[1].mV); gGL.vertex3fv(frust[5].mV); + gGL.vertex3fv(frust[2].mV); gGL.vertex3fv(frust[6].mV); + gGL.vertex3fv(frust[3].mV); gGL.vertex3fv(frust[7].mV); + gGL.end(); + } + } + + /*gGL.flush(); + glLineWidth(16-i*2); + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* region = *iter; + for (U32 j = 0; j < LLViewerRegion::NUM_PARTITIONS; j++) + { + LLSpatialPartition* part = region->getSpatialPartition(j); + if (part) + { + if (hasRenderType(part->mDrawableType)) + { + part->renderIntersectingBBoxes(&mShadowCamera[i]); + } + } + } + } + gGL.flush(); + glLineWidth(1.f);*/ + } + } + + if (mRenderDebugMask & RENDER_DEBUG_WIND_VECTORS) + { + gAgent.getRegion()->mWind.renderVectors(); + } + + if (mRenderDebugMask & RENDER_DEBUG_COMPOSITION) + { + // Debug composition layers + F32 x, y; + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + if (gAgent.getRegion()) + { + gGL.begin(LLRender::POINTS); + // Draw the composition layer for the region that I'm in. + for (x = 0; x <= 260; x++) + { + for (y = 0; y <= 260; y++) + { + if ((x > 255) || (y > 255)) + { + gGL.color4f(1.f, 0.f, 0.f, 1.f); + } + else + { + gGL.color4f(0.f, 0.f, 1.f, 1.f); + } + F32 z = gAgent.getRegion()->getCompositionXY((S32)x, (S32)y); + z *= 5.f; + z += 50.f; + gGL.vertex3f(x, y, z); + } + } + gGL.end(); + } + } + + gGL.flush(); + gUIProgram.unbind(); +} + +void LLPipeline::rebuildPools() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + + assertInitialized(); + + S32 max_count = mPools.size(); + pool_set_t::iterator iter1 = mPools.upper_bound(mLastRebuildPool); + while(max_count > 0 && mPools.size() > 0) // && num_rebuilds < MAX_REBUILDS) + { + if (iter1 == mPools.end()) + { + iter1 = mPools.begin(); + } + LLDrawPool* poolp = *iter1; + + if (poolp->isDead()) + { + mPools.erase(iter1++); + removeFromQuickLookup( poolp ); + if (poolp == mLastRebuildPool) + { + mLastRebuildPool = NULL; + } + delete poolp; + } + else + { + mLastRebuildPool = poolp; + iter1++; + } + max_count--; + } +} + +void LLPipeline::addToQuickLookup( LLDrawPool* new_poolp ) +{ + assertInitialized(); + + switch( new_poolp->getType() ) + { + case LLDrawPool::POOL_SIMPLE: + if (mSimplePool) + { + llassert(0); + LL_WARNS() << "Ignoring duplicate simple pool." << LL_ENDL; + } + else + { + mSimplePool = (LLRenderPass*) new_poolp; + } + break; + + case LLDrawPool::POOL_ALPHA_MASK: + if (mAlphaMaskPool) + { + llassert(0); + LL_WARNS() << "Ignoring duplicate alpha mask pool." << LL_ENDL; + break; + } + else + { + mAlphaMaskPool = (LLRenderPass*) new_poolp; + } + break; + + case LLDrawPool::POOL_FULLBRIGHT_ALPHA_MASK: + if (mFullbrightAlphaMaskPool) + { + llassert(0); + LL_WARNS() << "Ignoring duplicate alpha mask pool." << LL_ENDL; + break; + } + else + { + mFullbrightAlphaMaskPool = (LLRenderPass*) new_poolp; + } + break; + + case LLDrawPool::POOL_GRASS: + if (mGrassPool) + { + llassert(0); + LL_WARNS() << "Ignoring duplicate grass pool." << LL_ENDL; + } + else + { + mGrassPool = (LLRenderPass*) new_poolp; + } + break; + + case LLDrawPool::POOL_FULLBRIGHT: + if (mFullbrightPool) + { + llassert(0); + LL_WARNS() << "Ignoring duplicate simple pool." << LL_ENDL; + } + else + { + mFullbrightPool = (LLRenderPass*) new_poolp; + } + break; + + case LLDrawPool::POOL_GLOW: + if (mGlowPool) + { + llassert(0); + LL_WARNS() << "Ignoring duplicate glow pool." << LL_ENDL; + } + else + { + mGlowPool = (LLRenderPass*) new_poolp; + } + break; + + case LLDrawPool::POOL_TREE: + mTreePools[ uintptr_t(new_poolp->getTexture()) ] = new_poolp ; + break; + + case LLDrawPool::POOL_TERRAIN: + mTerrainPools[ uintptr_t(new_poolp->getTexture()) ] = new_poolp ; + break; + + case LLDrawPool::POOL_BUMP: + if (mBumpPool) + { + llassert(0); + LL_WARNS() << "Ignoring duplicate bump pool." << LL_ENDL; + } + else + { + mBumpPool = new_poolp; + } + break; + case LLDrawPool::POOL_MATERIALS: + if (mMaterialsPool) + { + llassert(0); + LL_WARNS() << "Ignorning duplicate materials pool." << LL_ENDL; + } + else + { + mMaterialsPool = new_poolp; + } + break; + case LLDrawPool::POOL_ALPHA_PRE_WATER: + if( mAlphaPoolPreWater ) + { + llassert(0); + LL_WARNS() << "LLPipeline::addPool(): Ignoring duplicate Alpha pre-water pool" << LL_ENDL; + } + else + { + mAlphaPoolPreWater = (LLDrawPoolAlpha*) new_poolp; + } + break; + case LLDrawPool::POOL_ALPHA_POST_WATER: + if (mAlphaPoolPostWater) + { + llassert(0); + LL_WARNS() << "LLPipeline::addPool(): Ignoring duplicate Alpha post-water pool" << LL_ENDL; + } + else + { + mAlphaPoolPostWater = (LLDrawPoolAlpha*)new_poolp; + } + break; + + case LLDrawPool::POOL_AVATAR: + case LLDrawPool::POOL_CONTROL_AV: + break; // Do nothing + + case LLDrawPool::POOL_SKY: + if( mSkyPool ) + { + llassert(0); + LL_WARNS() << "LLPipeline::addPool(): Ignoring duplicate Sky pool" << LL_ENDL; + } + else + { + mSkyPool = new_poolp; + } + break; + + case LLDrawPool::POOL_WATER: + if( mWaterPool ) + { + llassert(0); + LL_WARNS() << "LLPipeline::addPool(): Ignoring duplicate Water pool" << LL_ENDL; + } + else + { + mWaterPool = new_poolp; + } + break; + + case LLDrawPool::POOL_WL_SKY: + if( mWLSkyPool ) + { + llassert(0); + LL_WARNS() << "LLPipeline::addPool(): Ignoring duplicate WLSky Pool" << LL_ENDL; + } + else + { + mWLSkyPool = new_poolp; + } + break; + + case LLDrawPool::POOL_GLTF_PBR: + if( mPBROpaquePool ) + { + llassert(0); + LL_WARNS() << "LLPipeline::addPool(): Ignoring duplicate PBR Opaque Pool" << LL_ENDL; + } + else + { + mPBROpaquePool = new_poolp; + } + break; + + case LLDrawPool::POOL_GLTF_PBR_ALPHA_MASK: + if (mPBRAlphaMaskPool) + { + llassert(0); + LL_WARNS() << "LLPipeline::addPool(): Ignoring duplicate PBR Alpha Mask Pool" << LL_ENDL; + } + else + { + mPBRAlphaMaskPool = new_poolp; + } + break; + + + default: + llassert(0); + LL_WARNS() << "Invalid Pool Type in LLPipeline::addPool()" << LL_ENDL; + break; + } +} + +void LLPipeline::removePool( LLDrawPool* poolp ) +{ + assertInitialized(); + removeFromQuickLookup(poolp); + mPools.erase(poolp); + delete poolp; +} + +void LLPipeline::removeFromQuickLookup( LLDrawPool* poolp ) +{ + assertInitialized(); + switch( poolp->getType() ) + { + case LLDrawPool::POOL_SIMPLE: + llassert(mSimplePool == poolp); + mSimplePool = NULL; + break; + + case LLDrawPool::POOL_ALPHA_MASK: + llassert(mAlphaMaskPool == poolp); + mAlphaMaskPool = NULL; + break; + + case LLDrawPool::POOL_FULLBRIGHT_ALPHA_MASK: + llassert(mFullbrightAlphaMaskPool == poolp); + mFullbrightAlphaMaskPool = NULL; + break; + + case LLDrawPool::POOL_GRASS: + llassert(mGrassPool == poolp); + mGrassPool = NULL; + break; + + case LLDrawPool::POOL_FULLBRIGHT: + llassert(mFullbrightPool == poolp); + mFullbrightPool = NULL; + break; + + case LLDrawPool::POOL_WL_SKY: + llassert(mWLSkyPool == poolp); + mWLSkyPool = NULL; + break; + + case LLDrawPool::POOL_GLOW: + llassert(mGlowPool == poolp); + mGlowPool = NULL; + break; + + case LLDrawPool::POOL_TREE: + #ifdef _DEBUG + { + bool found = mTreePools.erase( (uintptr_t)poolp->getTexture() ); + llassert( found ); + } + #else + mTreePools.erase( (uintptr_t)poolp->getTexture() ); + #endif + break; + + case LLDrawPool::POOL_TERRAIN: + #ifdef _DEBUG + { + bool found = mTerrainPools.erase( (uintptr_t)poolp->getTexture() ); + llassert( found ); + } + #else + mTerrainPools.erase( (uintptr_t)poolp->getTexture() ); + #endif + break; + + case LLDrawPool::POOL_BUMP: + llassert( poolp == mBumpPool ); + mBumpPool = NULL; + break; + + case LLDrawPool::POOL_MATERIALS: + llassert(poolp == mMaterialsPool); + mMaterialsPool = NULL; + break; + + case LLDrawPool::POOL_ALPHA_PRE_WATER: + llassert( poolp == mAlphaPoolPreWater ); + mAlphaPoolPreWater = nullptr; + break; + + case LLDrawPool::POOL_ALPHA_POST_WATER: + llassert(poolp == mAlphaPoolPostWater); + mAlphaPoolPostWater = nullptr; + break; + + case LLDrawPool::POOL_AVATAR: + case LLDrawPool::POOL_CONTROL_AV: + break; // Do nothing + + case LLDrawPool::POOL_SKY: + llassert( poolp == mSkyPool ); + mSkyPool = NULL; + break; + + case LLDrawPool::POOL_WATER: + llassert( poolp == mWaterPool ); + mWaterPool = NULL; + break; + + case LLDrawPool::POOL_GLTF_PBR: + llassert( poolp == mPBROpaquePool ); + mPBROpaquePool = NULL; + break; + + case LLDrawPool::POOL_GLTF_PBR_ALPHA_MASK: + llassert(poolp == mPBRAlphaMaskPool); + mPBRAlphaMaskPool = NULL; + break; + + default: + llassert(0); + LL_WARNS() << "Invalid Pool Type in LLPipeline::removeFromQuickLookup() type=" << poolp->getType() << LL_ENDL; + break; + } +} + +void LLPipeline::resetDrawOrders() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + assertInitialized(); + // Iterate through all of the draw pools and rebuild them. + for (pool_set_t::iterator iter = mPools.begin(); iter != mPools.end(); ++iter) + { + LLDrawPool *poolp = *iter; + poolp->resetDrawOrders(); + } +} + +//============================================================================ +// Once-per-frame setup of hardware lights, +// including sun/moon, avatar backlight, and up to 6 local lights + +void LLPipeline::setupAvatarLights(bool for_edit) +{ + assertInitialized(); + + LLEnvironment& environment = LLEnvironment::instance(); + LLSettingsSky::ptr_t psky = environment.getCurrentSky(); + + bool sun_up = environment.getIsSunUp(); + + + if (for_edit) + { + LLColor4 diffuse(1.f, 1.f, 1.f, 0.f); + LLVector4 light_pos_cam(-8.f, 0.25f, 10.f, 0.f); // w==0 => directional light + LLMatrix4 camera_mat = LLViewerCamera::getInstance()->getModelview(); + LLMatrix4 camera_rot(camera_mat.getMat3()); + camera_rot.invert(); + LLVector4 light_pos = light_pos_cam * camera_rot; + + light_pos.normalize(); + + LLLightState* light = gGL.getLight(1); + + mHWLightColors[1] = diffuse; + + light->setDiffuse(diffuse); + light->setAmbient(LLColor4::black); + light->setSpecular(LLColor4::black); + light->setPosition(light_pos); + light->setConstantAttenuation(1.f); + light->setLinearAttenuation(0.f); + light->setQuadraticAttenuation(0.f); + light->setSpotExponent(0.f); + light->setSpotCutoff(180.f); + } + else if (gAvatarBacklight) + { + LLVector3 light_dir = sun_up ? LLVector3(mSunDir) : LLVector3(mMoonDir); + LLVector3 opposite_pos = -light_dir; + LLVector3 orthog_light_pos = light_dir % LLVector3::z_axis; + LLVector4 backlight_pos = LLVector4(lerp(opposite_pos, orthog_light_pos, 0.3f), 0.0f); + backlight_pos.normalize(); + + LLColor4 light_diffuse = sun_up ? mSunDiffuse : mMoonDiffuse; + + LLColor4 backlight_diffuse(1.f - light_diffuse.mV[VRED], 1.f - light_diffuse.mV[VGREEN], 1.f - light_diffuse.mV[VBLUE], 1.f); + F32 max_component = 0.001f; + for (S32 i = 0; i < 3; i++) + { + if (backlight_diffuse.mV[i] > max_component) + { + max_component = backlight_diffuse.mV[i]; + } + } + F32 backlight_mag; + if (LLEnvironment::instance().getIsSunUp()) + { + backlight_mag = BACKLIGHT_DAY_MAGNITUDE_OBJECT; + } + else + { + backlight_mag = BACKLIGHT_NIGHT_MAGNITUDE_OBJECT; + } + backlight_diffuse *= backlight_mag / max_component; + + mHWLightColors[1] = backlight_diffuse; + + LLLightState* light = gGL.getLight(1); + + light->setPosition(backlight_pos); + light->setDiffuse(backlight_diffuse); + light->setAmbient(LLColor4::black); + light->setSpecular(LLColor4::black); + light->setConstantAttenuation(1.f); + light->setLinearAttenuation(0.f); + light->setQuadraticAttenuation(0.f); + light->setSpotExponent(0.f); + light->setSpotCutoff(180.f); + } + else + { + LLLightState* light = gGL.getLight(1); + + mHWLightColors[1] = LLColor4::black; + + light->setDiffuse(LLColor4::black); + light->setAmbient(LLColor4::black); + light->setSpecular(LLColor4::black); + } +} + +static F32 calc_light_dist(LLVOVolume* light, const LLVector3& cam_pos, F32 max_dist) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + F32 inten = light->getLightIntensity(); + if (inten < .001f) + { + return max_dist; + } + bool selected = light->isSelected(); + if (selected) + { + return 0.f; // selected lights get highest priority + } + F32 radius = light->getLightRadius(); + F32 dist = dist_vec(light->getRenderPosition(), cam_pos); + dist = llmax(dist - radius, 0.f); + if (light->mDrawable.notNull() && light->mDrawable->isState(LLDrawable::ACTIVE)) + { + // moving lights get a little higher priority (too much causes artifacts) + dist = llmax(dist - light->getLightRadius()*0.25f, 0.f); + } + return dist; +} + +void LLPipeline::calcNearbyLights(LLCamera& camera) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + assertInitialized(); + + if (LLPipeline::sReflectionRender || gCubeSnapshot || LLPipeline::sRenderingHUDs) + { + return; + } + + static LLCachedControl local_light_count(gSavedSettings, "RenderLocalLightCount", 256); + + if (local_light_count >= 1) + { + // mNearbyLight (and all light_set_t's) are sorted such that + // begin() == the closest light and rbegin() == the farthest light + const S32 MAX_LOCAL_LIGHTS = 6; + LLVector3 cam_pos = camera.getOrigin(); + + F32 max_dist; + if (LLPipeline::sRenderDeferred) + { + max_dist = RenderFarClip; + } + else + { + max_dist = llmin(RenderFarClip, LIGHT_MAX_RADIUS * 4.f); + } + + // UPDATE THE EXISTING NEARBY LIGHTS + light_set_t cur_nearby_lights; + for (light_set_t::iterator iter = mNearbyLights.begin(); + iter != mNearbyLights.end(); iter++) + { + const Light* light = &(*iter); + LLDrawable* drawable = light->drawable; + const LLViewerObject *vobj = light->drawable->getVObj(); + if(vobj && vobj->getAvatar() + && (vobj->getAvatar()->isTooComplex() || vobj->getAvatar()->isInMuteList() || vobj->getAvatar()->isTooSlow()) + ) + { + drawable->clearState(LLDrawable::NEARBY_LIGHT); + continue; + } + + LLVOVolume* volight = drawable->getVOVolume(); + if (!volight || !drawable->isState(LLDrawable::LIGHT)) + { + drawable->clearState(LLDrawable::NEARBY_LIGHT); + continue; + } + if (light->fade <= -LIGHT_FADE_TIME) + { + drawable->clearState(LLDrawable::NEARBY_LIGHT); + continue; + } + if (!sRenderAttachedLights && volight && volight->isAttachment()) + { + drawable->clearState(LLDrawable::NEARBY_LIGHT); + continue; + } + + F32 dist = calc_light_dist(volight, cam_pos, max_dist); + F32 fade = light->fade; + // actual fade gets decreased/increased by setupHWLights + // light->fade value is 'time'. + // >=0 and light will become visible as value increases + // <0 and light will fade out + if (dist < max_dist) + { + if (fade < 0) + { + // mark light to fade in + // if fade was -LIGHT_FADE_TIME - it was fully invisible + // if fade -0 - it was fully visible + // visibility goes up from 0 to LIGHT_FADE_TIME. + fade += LIGHT_FADE_TIME; + } + } + else + { + // mark light to fade out + // visibility goes down from -0 to -LIGHT_FADE_TIME. + if (fade >= LIGHT_FADE_TIME) + { + fade = -0.0001f; // was fully visible + } + else if (fade >= 0) + { + // 0.75 visible light should stay 0.75 visible, but should reverse direction + fade -= LIGHT_FADE_TIME; + } + } + cur_nearby_lights.insert(Light(drawable, dist, fade)); + } + mNearbyLights = cur_nearby_lights; + + // FIND NEW LIGHTS THAT ARE IN RANGE + light_set_t new_nearby_lights; + for (LLDrawable::ordered_drawable_set_t::iterator iter = mLights.begin(); + iter != mLights.end(); ++iter) + { + LLDrawable* drawable = *iter; + LLVOVolume* light = drawable->getVOVolume(); + if (!light || drawable->isState(LLDrawable::NEARBY_LIGHT)) + { + continue; + } + if (light->isHUDAttachment()) + { + continue; // no lighting from HUD objects + } + if (!sRenderAttachedLights && light && light->isAttachment()) + { + continue; + } + LLVOAvatar * av = light->getAvatar(); + if (av && (av->isTooComplex() || av->isInMuteList() || av->isTooSlow())) + { + // avatars that are already in the list will be removed by removeMutedAVsLights + continue; + } + F32 dist = calc_light_dist(light, cam_pos, max_dist); + if (dist >= max_dist) + { + continue; + } + new_nearby_lights.insert(Light(drawable, dist, 0.f)); + if (!LLPipeline::sRenderDeferred && new_nearby_lights.size() > (U32)MAX_LOCAL_LIGHTS) + { + new_nearby_lights.erase(--new_nearby_lights.end()); + const Light& last = *new_nearby_lights.rbegin(); + max_dist = last.dist; + } + } + + // INSERT ANY NEW LIGHTS + for (light_set_t::iterator iter = new_nearby_lights.begin(); + iter != new_nearby_lights.end(); iter++) + { + const Light* light = &(*iter); + if (LLPipeline::sRenderDeferred || mNearbyLights.size() < (U32)MAX_LOCAL_LIGHTS) + { + mNearbyLights.insert(*light); + ((LLDrawable*) light->drawable)->setState(LLDrawable::NEARBY_LIGHT); + } + else + { + // crazy cast so that we can overwrite the fade value + // even though gcc enforces sets as const + // (fade value doesn't affect sort so this is safe) + Light* farthest_light = (const_cast(&(*(mNearbyLights.rbegin())))); + if (light->dist < farthest_light->dist) + { + // mark light to fade out + // visibility goes down from -0 to -LIGHT_FADE_TIME. + // + // This is a mess, but for now it needs to be in sync + // with fade code above. Ex: code above detects distance < max, + // sets fade time to positive, this code then detects closer + // lights and sets fade time negative, fully compensating + // for the code above + if (farthest_light->fade >= LIGHT_FADE_TIME) + { + farthest_light->fade = -0.0001f; // was fully visible + } + else if (farthest_light->fade >= 0) + { + farthest_light->fade -= LIGHT_FADE_TIME; + } + } + else + { + break; // none of the other lights are closer + } + } + } + + //mark nearby lights not-removable. + for (light_set_t::iterator iter = mNearbyLights.begin(); + iter != mNearbyLights.end(); iter++) + { + const Light* light = &(*iter); + ((LLViewerOctreeEntryData*) light->drawable)->setVisible(); + } + } +} + +void LLPipeline::setupHWLights() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + assertInitialized(); + + if (LLPipeline::sRenderingHUDs) + { + return; + } + + F32 light_scale = 1.f; + + if (gCubeSnapshot) + { //darken local lights when probe ambiance is above 1 + light_scale = mReflectionMapManager.mLightScale; + } + + + LLEnvironment& environment = LLEnvironment::instance(); + LLSettingsSky::ptr_t psky = environment.getCurrentSky(); + + // Ambient + LLColor4 ambient = psky->getTotalAmbient(); + + gGL.setAmbientLightColor(ambient); + + bool sun_up = environment.getIsSunUp(); + bool moon_up = environment.getIsMoonUp(); + + // Light 0 = Sun or Moon (All objects) + { + LLVector4 sun_dir(environment.getSunDirection(), 0.0f); + LLVector4 moon_dir(environment.getMoonDirection(), 0.0f); + + mSunDir.setVec(sun_dir); + mMoonDir.setVec(moon_dir); + + mSunDiffuse.setVec(psky->getSunlightColor()); + mMoonDiffuse.setVec(psky->getMoonlightColor()); + + F32 max_color = llmax(mSunDiffuse.mV[0], mSunDiffuse.mV[1], mSunDiffuse.mV[2]); + if (max_color > 1.f) + { + mSunDiffuse *= 1.f/max_color; + } + mSunDiffuse.clamp(); + + max_color = llmax(mMoonDiffuse.mV[0], mMoonDiffuse.mV[1], mMoonDiffuse.mV[2]); + if (max_color > 1.f) + { + mMoonDiffuse *= 1.f/max_color; + } + mMoonDiffuse.clamp(); + + // prevent underlighting from having neither lightsource facing us + if (!sun_up && !moon_up) + { + mSunDiffuse.setVec(LLColor4(0.0, 0.0, 0.0, 1.0)); + mMoonDiffuse.setVec(LLColor4(0.0, 0.0, 0.0, 1.0)); + mSunDir.setVec(LLVector4(0.0, 1.0, 0.0, 0.0)); + mMoonDir.setVec(LLVector4(0.0, 1.0, 0.0, 0.0)); + } + + LLVector4 light_dir = sun_up ? mSunDir : mMoonDir; + + mHWLightColors[0] = sun_up ? mSunDiffuse : mMoonDiffuse; + + LLLightState* light = gGL.getLight(0); + light->setPosition(light_dir); + + light->setSunPrimary(sun_up); + light->setDiffuse(mHWLightColors[0]); + light->setDiffuseB(mMoonDiffuse); + light->setAmbient(psky->getTotalAmbient()); + light->setSpecular(LLColor4::black); + light->setConstantAttenuation(1.f); + light->setLinearAttenuation(0.f); + light->setQuadraticAttenuation(0.f); + light->setSpotExponent(0.f); + light->setSpotCutoff(180.f); + } + + // Light 1 = Backlight (for avatars) + // (set by enableLightsAvatar) + + S32 cur_light = 2; + + // Nearby lights = LIGHT 2-7 + + mLightMovingMask = 0; + + static LLCachedControl local_light_count(gSavedSettings, "RenderLocalLightCount", 256); + + if (local_light_count >= 1) + { + for (light_set_t::iterator iter = mNearbyLights.begin(); + iter != mNearbyLights.end(); ++iter) + { + LLDrawable* drawable = iter->drawable; + LLVOVolume* light = drawable->getVOVolume(); + if (!light) + { + continue; + } + + if (light->isAttachment()) + { + if (!sRenderAttachedLights) + { + continue; + } + } + + if (drawable->isState(LLDrawable::ACTIVE)) + { + mLightMovingMask |= (1<getLightLinearColor() * light_scale; + light_color.mV[3] = 0.0f; + + F32 fade = iter->fade; + if (fade < LIGHT_FADE_TIME) + { + // fade in/out light + if (fade >= 0.f) + { + fade = fade / LIGHT_FADE_TIME; + ((Light*) (&(*iter)))->fade += gFrameIntervalSeconds.value(); + } + else + { + fade = 1.f + fade / LIGHT_FADE_TIME; + ((Light*) (&(*iter)))->fade -= gFrameIntervalSeconds.value(); + } + fade = llclamp(fade,0.f,1.f); + light_color *= fade; + } + + if (light_color.magVecSquared() < 0.001f) + { + continue; + } + + LLVector3 light_pos(light->getRenderPosition()); + LLVector4 light_pos_gl(light_pos, 1.0f); + + F32 adjusted_radius = light->getLightRadius() * (sRenderDeferred ? 1.5f : 1.0f); + if (adjusted_radius <= 0.001f) + { + continue; + } + + F32 x = (3.f * (1.f + (light->getLightFalloff() * 2.0f))); // why this magic? probably trying to match a historic behavior. + F32 linatten = x / adjusted_radius; // % of brightness at radius + + mHWLightColors[cur_light] = light_color; + LLLightState* light_state = gGL.getLight(cur_light); + + light_state->setPosition(light_pos_gl); + light_state->setDiffuse(light_color); + light_state->setAmbient(LLColor4::black); + light_state->setConstantAttenuation(0.f); + light_state->setSize(light->getLightRadius() * 1.5f); + light_state->setFalloff(light->getLightFalloff(DEFERRED_LIGHT_FALLOFF)); + + if (sRenderDeferred) + { + light_state->setLinearAttenuation(linatten); + light_state->setQuadraticAttenuation(light->getLightFalloff(DEFERRED_LIGHT_FALLOFF) + 1.f); // get falloff to match for forward deferred rendering lights + } + else + { + light_state->setLinearAttenuation(linatten); + light_state->setQuadraticAttenuation(0.f); + } + + + if (light->isLightSpotlight() // directional (spot-)light + && (LLPipeline::sRenderDeferred || RenderSpotLightsInNondeferred)) // these are only rendered as GL spotlights if we're in deferred rendering mode *or* the setting forces them on + { + LLQuaternion quat = light->getRenderRotation(); + LLVector3 at_axis(0,0,-1); // this matches deferred rendering's object light direction + at_axis *= quat; + + light_state->setSpotDirection(at_axis); + light_state->setSpotCutoff(90.f); + light_state->setSpotExponent(2.f); + + LLVector3 spotParams = light->getSpotLightParams(); + + const LLColor4 specular(0.f, 0.f, 0.f, spotParams[2]); + light_state->setSpecular(specular); + } + else // omnidirectional (point) light + { + light_state->setSpotExponent(0.f); + light_state->setSpotCutoff(180.f); + + // we use specular.z = 1.0 as a cheap hack for the shaders to know that this is omnidirectional rather than a spotlight + const LLColor4 specular(0.f, 0.f, 1.f, 0.f); + light_state->setSpecular(specular); + } + cur_light++; + if (cur_light >= 8) + { + break; // safety + } + } + } + for ( ; cur_light < 8 ; cur_light++) + { + mHWLightColors[cur_light] = LLColor4::black; + LLLightState* light = gGL.getLight(cur_light); + light->setSunPrimary(true); + light->setDiffuse(LLColor4::black); + light->setAmbient(LLColor4::black); + light->setSpecular(LLColor4::black); + } + + // Bookmark comment to allow searching for mSpecialRenderMode == 3 (avatar edit mode), + // prev site of forward (non-deferred) character light injection, removed by SL-13522 09/20 + + // Init GL state + for (S32 i = 0; i < 8; ++i) + { + gGL.getLight(i)->disable(); + } + mLightMask = 0; +} + +void LLPipeline::enableLights(U32 mask) +{ + assertInitialized(); + + if (mLightMask != mask) + { + stop_glerror(); + if (mask) + { + stop_glerror(); + for (S32 i=0; i<8; i++) + { + LLLightState* light = gGL.getLight(i); + if (mask & (1<enable(); + light->setDiffuse(mHWLightColors[i]); + } + else + { + light->disable(); + light->setDiffuse(LLColor4::black); + } + } + stop_glerror(); + } + mLightMask = mask; + stop_glerror(); + } +} + +void LLPipeline::enableLightsDynamic() +{ + assertInitialized(); + U32 mask = 0xff & (~2); // Local lights + enableLights(mask); + + if (isAgentAvatarValid()) + { + if (gAgentAvatarp->mSpecialRenderMode == 0) // normal + { + gPipeline.enableLightsAvatar(); + } + else if (gAgentAvatarp->mSpecialRenderMode == 2) // anim preview + { + gPipeline.enableLightsAvatarEdit(LLColor4(0.7f, 0.6f, 0.3f, 1.f)); + } + } +} + +void LLPipeline::enableLightsAvatar() +{ + U32 mask = 0xff; // All lights + setupAvatarLights(false); + enableLights(mask); +} + +void LLPipeline::enableLightsPreview() +{ + disableLights(); + + LLColor4 ambient = PreviewAmbientColor; + gGL.setAmbientLightColor(ambient); + + LLColor4 diffuse0 = PreviewDiffuse0; + LLColor4 specular0 = PreviewSpecular0; + LLColor4 diffuse1 = PreviewDiffuse1; + LLColor4 specular1 = PreviewSpecular1; + LLColor4 diffuse2 = PreviewDiffuse2; + LLColor4 specular2 = PreviewSpecular2; + + LLVector3 dir0 = PreviewDirection0; + LLVector3 dir1 = PreviewDirection1; + LLVector3 dir2 = PreviewDirection2; + + dir0.normVec(); + dir1.normVec(); + dir2.normVec(); + + LLVector4 light_pos(dir0, 0.0f); + + LLLightState* light = gGL.getLight(1); + + light->enable(); + light->setPosition(light_pos); + light->setDiffuse(diffuse0); + light->setAmbient(ambient); + light->setSpecular(specular0); + light->setSpotExponent(0.f); + light->setSpotCutoff(180.f); + + light_pos = LLVector4(dir1, 0.f); + + light = gGL.getLight(2); + light->enable(); + light->setPosition(light_pos); + light->setDiffuse(diffuse1); + light->setAmbient(ambient); + light->setSpecular(specular1); + light->setSpotExponent(0.f); + light->setSpotCutoff(180.f); + + light_pos = LLVector4(dir2, 0.f); + light = gGL.getLight(3); + light->enable(); + light->setPosition(light_pos); + light->setDiffuse(diffuse2); + light->setAmbient(ambient); + light->setSpecular(specular2); + light->setSpotExponent(0.f); + light->setSpotCutoff(180.f); +} + + +void LLPipeline::enableLightsAvatarEdit(const LLColor4& color) +{ + U32 mask = 0x2002; // Avatar backlight only, set ambient + setupAvatarLights(true); + enableLights(mask); + + gGL.setAmbientLightColor(color); +} + +void LLPipeline::enableLightsFullbright() +{ + assertInitialized(); + U32 mask = 0x1000; // Non-0 mask, set ambient + enableLights(mask); +} + +void LLPipeline::disableLights() +{ + enableLights(0); // no lighting (full bright) +} + +//============================================================================ + +class LLMenuItemGL; +class LLInvFVBridge; +struct cat_folder_pair; +class LLVOBranch; +class LLVOLeaf; + +void LLPipeline::findReferences(LLDrawable *drawablep) +{ + assertInitialized(); + if (mLights.find(drawablep) != mLights.end()) + { + LL_INFOS() << "In mLights" << LL_ENDL; + } + if (std::find(mMovedList.begin(), mMovedList.end(), drawablep) != mMovedList.end()) + { + LL_INFOS() << "In mMovedList" << LL_ENDL; + } + if (std::find(mShiftList.begin(), mShiftList.end(), drawablep) != mShiftList.end()) + { + LL_INFOS() << "In mShiftList" << LL_ENDL; + } + if (mRetexturedList.find(drawablep) != mRetexturedList.end()) + { + LL_INFOS() << "In mRetexturedList" << LL_ENDL; + } + + if (std::find(mBuildQ1.begin(), mBuildQ1.end(), drawablep) != mBuildQ1.end()) + { + LL_INFOS() << "In mBuildQ1" << LL_ENDL; + } + + S32 count; + + count = gObjectList.findReferences(drawablep); + if (count) + { + LL_INFOS() << "In other drawables: " << count << " references" << LL_ENDL; + } +} + +bool LLPipeline::verify() +{ + bool ok = assertInitialized(); + if (ok) + { + for (pool_set_t::iterator iter = mPools.begin(); iter != mPools.end(); ++iter) + { + LLDrawPool *poolp = *iter; + if (!poolp->verify()) + { + ok = false; + } + } + } + + if (!ok) + { + LL_WARNS() << "Pipeline verify failed!" << LL_ENDL; + } + return ok; +} + +////////////////////////////// +// +// Collision detection +// +// + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/** + * A method to compute a ray-AABB intersection. + * Original code by Andrew Woo, from "Graphics Gems", Academic Press, 1990 + * Optimized code by Pierre Terdiman, 2000 (~20-30% faster on my Celeron 500) + * Epsilon value added by Klaus Hartmann. (discarding it saves a few cycles only) + * + * Hence this version is faster as well as more robust than the original one. + * + * Should work provided: + * 1) the integer representation of 0.0f is 0x00000000 + * 2) the sign bit of the float is the most significant one + * + * Report bugs: p.terdiman@codercorner.com + * + * \param aabb [in] the axis-aligned bounding box + * \param origin [in] ray origin + * \param dir [in] ray direction + * \param coord [out] impact coordinates + * \return true if ray intersects AABB + */ +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//#define RAYAABB_EPSILON 0.00001f +#define IR(x) ((U32&)x) + +bool LLRayAABB(const LLVector3 ¢er, const LLVector3 &size, const LLVector3& origin, const LLVector3& dir, LLVector3 &coord, F32 epsilon) +{ + bool Inside = true; + LLVector3 MinB = center - size; + LLVector3 MaxB = center + size; + LLVector3 MaxT; + MaxT.mV[VX]=MaxT.mV[VY]=MaxT.mV[VZ]=-1.0f; + + // Find candidate planes. + for(U32 i=0;i<3;i++) + { + if(origin.mV[i] < MinB.mV[i]) + { + coord.mV[i] = MinB.mV[i]; + Inside = false; + + // Calculate T distances to candidate planes + if(IR(dir.mV[i])) MaxT.mV[i] = (MinB.mV[i] - origin.mV[i]) / dir.mV[i]; + } + else if(origin.mV[i] > MaxB.mV[i]) + { + coord.mV[i] = MaxB.mV[i]; + Inside = false; + + // Calculate T distances to candidate planes + if(IR(dir.mV[i])) MaxT.mV[i] = (MaxB.mV[i] - origin.mV[i]) / dir.mV[i]; + } + } + + // Ray origin inside bounding box + if(Inside) + { + coord = origin; + return true; + } + + // Get largest of the maxT's for final choice of intersection + U32 WhichPlane = 0; + if(MaxT.mV[1] > MaxT.mV[WhichPlane]) WhichPlane = 1; + if(MaxT.mV[2] > MaxT.mV[WhichPlane]) WhichPlane = 2; + + // Check final candidate actually inside box + if(IR(MaxT.mV[WhichPlane])&0x80000000) return false; + + for(U32 i=0;i<3;i++) + { + if(i!=WhichPlane) + { + coord.mV[i] = origin.mV[i] + MaxT.mV[WhichPlane] * dir.mV[i]; + if (epsilon > 0) + { + if(coord.mV[i] < MinB.mV[i] - epsilon || coord.mV[i] > MaxB.mV[i] + epsilon) return false; + } + else + { + if(coord.mV[i] < MinB.mV[i] || coord.mV[i] > MaxB.mV[i]) return false; + } + } + } + return true; // ray hits box +} + +////////////////////////////// +// +// Macros, functions, and inline methods from other classes +// +// + +void LLPipeline::setLight(LLDrawable *drawablep, bool is_light) +{ + if (drawablep && assertInitialized()) + { + if (is_light) + { + mLights.insert(drawablep); + drawablep->setState(LLDrawable::LIGHT); + } + else + { + drawablep->clearState(LLDrawable::LIGHT); + mLights.erase(drawablep); + } + } +} + +//static +void LLPipeline::toggleRenderType(U32 type) +{ + gPipeline.mRenderTypeEnabled[type] = !gPipeline.mRenderTypeEnabled[type]; + if (type == LLPipeline::RENDER_TYPE_WATER) + { + gPipeline.mRenderTypeEnabled[LLPipeline::RENDER_TYPE_VOIDWATER] = !gPipeline.mRenderTypeEnabled[LLPipeline::RENDER_TYPE_VOIDWATER]; + } +} + +//static +void LLPipeline::toggleRenderTypeControl(U32 type) +{ + gPipeline.toggleRenderType(type); +} + +//static +bool LLPipeline::hasRenderTypeControl(U32 type) +{ + return gPipeline.hasRenderType(type); +} + +// Allows UI items labeled "Hide foo" instead of "Show foo" +//static +bool LLPipeline::toggleRenderTypeControlNegated(S32 type) +{ + return !gPipeline.hasRenderType(type); +} + +//static +void LLPipeline::toggleRenderDebug(U64 bit) +{ + if (gPipeline.hasRenderDebugMask(bit)) + { + LL_INFOS() << "Toggling render debug mask " << std::hex << bit << " off" << std::dec << LL_ENDL; + } + else + { + LL_INFOS() << "Toggling render debug mask " << std::hex << bit << " on" << std::dec << LL_ENDL; + } + gPipeline.mRenderDebugMask ^= bit; +} + + +//static +bool LLPipeline::toggleRenderDebugControl(U64 bit) +{ + return gPipeline.hasRenderDebugMask(bit); +} + +//static +void LLPipeline::toggleRenderDebugFeature(U32 bit) +{ + gPipeline.mRenderDebugFeatureMask ^= bit; +} + + +//static +bool LLPipeline::toggleRenderDebugFeatureControl(U32 bit) +{ + return gPipeline.hasRenderDebugFeatureMask(bit); +} + +void LLPipeline::setRenderDebugFeatureControl(U32 bit, bool value) +{ + if (value) + { + gPipeline.mRenderDebugFeatureMask |= bit; + } + else + { + gPipeline.mRenderDebugFeatureMask &= !bit; + } +} + +void LLPipeline::pushRenderDebugFeatureMask() +{ + mRenderDebugFeatureStack.push(mRenderDebugFeatureMask); +} + +void LLPipeline::popRenderDebugFeatureMask() +{ + if (mRenderDebugFeatureStack.empty()) + { + LL_ERRS() << "Depleted render feature stack." << LL_ENDL; + } + + mRenderDebugFeatureMask = mRenderDebugFeatureStack.top(); + mRenderDebugFeatureStack.pop(); +} + +// static +void LLPipeline::setRenderScriptedBeacons(bool val) +{ + sRenderScriptedBeacons = val; +} + +// static +void LLPipeline::toggleRenderScriptedBeacons() +{ + sRenderScriptedBeacons = !sRenderScriptedBeacons; +} + +// static +bool LLPipeline::getRenderScriptedBeacons() +{ + return sRenderScriptedBeacons; +} + +// static +void LLPipeline::setRenderScriptedTouchBeacons(bool val) +{ + sRenderScriptedTouchBeacons = val; +} + +// static +void LLPipeline::toggleRenderScriptedTouchBeacons() +{ + sRenderScriptedTouchBeacons = !sRenderScriptedTouchBeacons; +} + +// static +bool LLPipeline::getRenderScriptedTouchBeacons() +{ + return sRenderScriptedTouchBeacons; +} + +// static +void LLPipeline::setRenderMOAPBeacons(bool val) +{ + sRenderMOAPBeacons = val; +} + +// static +void LLPipeline::toggleRenderMOAPBeacons() +{ + sRenderMOAPBeacons = !sRenderMOAPBeacons; +} + +// static +bool LLPipeline::getRenderMOAPBeacons() +{ + return sRenderMOAPBeacons; +} + +// static +void LLPipeline::setRenderPhysicalBeacons(bool val) +{ + sRenderPhysicalBeacons = val; +} + +// static +void LLPipeline::toggleRenderPhysicalBeacons() +{ + sRenderPhysicalBeacons = !sRenderPhysicalBeacons; +} + +// static +bool LLPipeline::getRenderPhysicalBeacons() +{ + return sRenderPhysicalBeacons; +} + +// static +void LLPipeline::setRenderParticleBeacons(bool val) +{ + sRenderParticleBeacons = val; +} + +// static +void LLPipeline::toggleRenderParticleBeacons() +{ + sRenderParticleBeacons = !sRenderParticleBeacons; +} + +// static +bool LLPipeline::getRenderParticleBeacons() +{ + return sRenderParticleBeacons; +} + +// static +void LLPipeline::setRenderSoundBeacons(bool val) +{ + sRenderSoundBeacons = val; +} + +// static +void LLPipeline::toggleRenderSoundBeacons() +{ + sRenderSoundBeacons = !sRenderSoundBeacons; +} + +// static +bool LLPipeline::getRenderSoundBeacons() +{ + return sRenderSoundBeacons; +} + +// static +void LLPipeline::setRenderBeacons(bool val) +{ + sRenderBeacons = val; +} + +// static +void LLPipeline::toggleRenderBeacons() +{ + sRenderBeacons = !sRenderBeacons; +} + +// static +bool LLPipeline::getRenderBeacons() +{ + return sRenderBeacons; +} + +// static +void LLPipeline::setRenderHighlights(bool val) +{ + sRenderHighlight = val; +} + +// static +void LLPipeline::toggleRenderHighlights() +{ + sRenderHighlight = !sRenderHighlight; +} + +// static +bool LLPipeline::getRenderHighlights() +{ + return sRenderHighlight; +} + +// static +void LLPipeline::setRenderHighlightTextureChannel(LLRender::eTexIndex channel) +{ + sRenderHighlightTextureChannel = channel; +} + +LLVOPartGroup* LLPipeline::lineSegmentIntersectParticle(const LLVector4a& start, const LLVector4a& end, LLVector4a* intersection, + S32* face_hit) +{ + LLVector4a local_end = end; + + LLVector4a position; + + LLDrawable* drawable = NULL; + + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* region = *iter; + + LLSpatialPartition* part = region->getSpatialPartition(LLViewerRegion::PARTITION_PARTICLE); + if (part && hasRenderType(part->mDrawableType)) + { + LLDrawable* hit = part->lineSegmentIntersect(start, local_end, true, false, true, false, face_hit, &position, NULL, NULL, NULL); + if (hit) + { + drawable = hit; + local_end = position; + } + } + } + + LLVOPartGroup* ret = NULL; + if (drawable) + { + //make sure we're returning an LLVOPartGroup + llassert(drawable->getVObj()->getPCode() == LLViewerObject::LL_VO_PART_GROUP); + ret = (LLVOPartGroup*) drawable->getVObj().get(); + } + + if (intersection) + { + *intersection = position; + } + + return ret; +} + +LLViewerObject* LLPipeline::lineSegmentIntersectInWorld(const LLVector4a& start, const LLVector4a& end, + bool pick_transparent, + bool pick_rigged, + bool pick_unselectable, + bool pick_reflection_probe, + S32* face_hit, + LLVector4a* intersection, // return the intersection point + LLVector2* tex_coord, // return the texture coordinates of the intersection point + LLVector4a* normal, // return the surface normal at the intersection point + LLVector4a* tangent // return the surface tangent at the intersection point + ) +{ + LLDrawable* drawable = NULL; + + LLVector4a local_end = end; + + LLVector4a position; + + sPickAvatar = false; //! LLToolMgr::getInstance()->inBuildMode(); + + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* region = *iter; + + for (U32 j = 0; j < LLViewerRegion::NUM_PARTITIONS; j++) + { + if ((j == LLViewerRegion::PARTITION_VOLUME) || + (j == LLViewerRegion::PARTITION_BRIDGE) || + (j == LLViewerRegion::PARTITION_AVATAR) || // for attachments + (j == LLViewerRegion::PARTITION_CONTROL_AV) || + (j == LLViewerRegion::PARTITION_TERRAIN) || + (j == LLViewerRegion::PARTITION_TREE) || + (j == LLViewerRegion::PARTITION_GRASS)) // only check these partitions for now + { + LLSpatialPartition* part = region->getSpatialPartition(j); + if (part && hasRenderType(part->mDrawableType)) + { + LLDrawable* hit = part->lineSegmentIntersect(start, local_end, pick_transparent, pick_rigged, pick_unselectable, pick_reflection_probe, face_hit, &position, tex_coord, normal, tangent); + if (hit) + { + drawable = hit; + local_end = position; + } + } + } + } + } + + if (!sPickAvatar) + { + //save hit info in case we need to restore + //due to attachment override + LLVector4a local_normal; + LLVector4a local_tangent; + LLVector2 local_texcoord; + S32 local_face_hit = -1; + + if (face_hit) + { + local_face_hit = *face_hit; + } + if (tex_coord) + { + local_texcoord = *tex_coord; + } + if (tangent) + { + local_tangent = *tangent; + } + else + { + local_tangent.clear(); + } + if (normal) + { + local_normal = *normal; + } + else + { + local_normal.clear(); + } + + const F32 ATTACHMENT_OVERRIDE_DIST = 0.1f; + + //check against avatars + sPickAvatar = true; + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* region = *iter; + + LLSpatialPartition* part = region->getSpatialPartition(LLViewerRegion::PARTITION_AVATAR); + if (part && hasRenderType(part->mDrawableType)) + { + LLDrawable* hit = part->lineSegmentIntersect(start, local_end, pick_transparent, pick_rigged, pick_unselectable, pick_reflection_probe, face_hit, &position, tex_coord, normal, tangent); + if (hit) + { + LLVector4a delta; + delta.setSub(position, local_end); + + if (!drawable || + !drawable->getVObj()->isAttachment() || + delta.getLength3().getF32() > ATTACHMENT_OVERRIDE_DIST) + { //avatar overrides if previously hit drawable is not an attachment or + //attachment is far enough away from detected intersection + drawable = hit; + local_end = position; + } + else + { //prioritize attachments over avatars + position = local_end; + + if (face_hit) + { + *face_hit = local_face_hit; + } + if (tex_coord) + { + *tex_coord = local_texcoord; + } + if (tangent) + { + *tangent = local_tangent; + } + if (normal) + { + *normal = local_normal; + } + } + } + } + } + } + + //check all avatar nametags (silly, isn't it?) + for (std::vector< LLCharacter* >::iterator iter = LLCharacter::sInstances.begin(); + iter != LLCharacter::sInstances.end(); + ++iter) + { + LLVOAvatar* av = (LLVOAvatar*) *iter; + if (av->mNameText.notNull() + && av->mNameText->lineSegmentIntersect(start, local_end, position)) + { + drawable = av->mDrawable; + local_end = position; + } + } + + if (intersection) + { + *intersection = position; + } + + return drawable ? drawable->getVObj().get() : NULL; +} + +LLViewerObject* LLPipeline::lineSegmentIntersectInHUD(const LLVector4a& start, const LLVector4a& end, + bool pick_transparent, + S32* face_hit, + LLVector4a* intersection, // return the intersection point + LLVector2* tex_coord, // return the texture coordinates of the intersection point + LLVector4a* normal, // return the surface normal at the intersection point + LLVector4a* tangent // return the surface tangent at the intersection point + ) +{ + LLDrawable* drawable = NULL; + + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* region = *iter; + + bool toggle = false; + if (!hasRenderType(LLPipeline::RENDER_TYPE_HUD)) + { + toggleRenderType(LLPipeline::RENDER_TYPE_HUD); + toggle = true; + } + + LLSpatialPartition* part = region->getSpatialPartition(LLViewerRegion::PARTITION_HUD); + if (part) + { + LLDrawable* hit = part->lineSegmentIntersect(start, end, pick_transparent, false, true, false, face_hit, intersection, tex_coord, normal, tangent); + if (hit) + { + drawable = hit; + } + } + + if (toggle) + { + toggleRenderType(LLPipeline::RENDER_TYPE_HUD); + } + } + return drawable ? drawable->getVObj().get() : NULL; +} + +LLSpatialPartition* LLPipeline::getSpatialPartition(LLViewerObject* vobj) +{ + if (vobj) + { + LLViewerRegion* region = vobj->getRegion(); + if (region) + { + return region->getSpatialPartition(vobj->getPartitionType()); + } + } + return NULL; +} + +void LLPipeline::resetVertexBuffers(LLDrawable* drawable) +{ + if (!drawable) + { + return; + } + + for (S32 i = 0; i < drawable->getNumFaces(); i++) + { + LLFace* facep = drawable->getFace(i); + if (facep) + { + facep->clearVertexBuffer(); + } + } +} + +void LLPipeline::renderObjects(U32 type, bool texture, bool batch_texture, bool rigged) +{ + assertInitialized(); + gGL.loadMatrix(gGLModelView); + gGLLastMatrix = NULL; + + if (rigged) + { + mSimplePool->pushRiggedBatches(type + 1, texture, batch_texture); + } + else + { + mSimplePool->pushBatches(type, texture, batch_texture); + } + + gGL.loadMatrix(gGLModelView); + gGLLastMatrix = NULL; +} + +void LLPipeline::renderGLTFObjects(U32 type, bool texture, bool rigged) +{ + assertInitialized(); + gGL.loadMatrix(gGLModelView); + gGLLastMatrix = NULL; + + if (rigged) + { + mSimplePool->pushRiggedGLTFBatches(type + 1, texture); + } + else + { + mSimplePool->pushGLTFBatches(type, texture); + } + + gGL.loadMatrix(gGLModelView); + gGLLastMatrix = NULL; +} + +// Currently only used for shadows -Cosmic,2023-04-19 +void LLPipeline::renderAlphaObjects(bool rigged) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + assertInitialized(); + gGL.loadMatrix(gGLModelView); + gGLLastMatrix = NULL; + S32 sun_up = LLEnvironment::instance().getIsSunUp() ? 1 : 0; + U32 target_width = LLRenderTarget::sCurResX; + U32 type = LLRenderPass::PASS_ALPHA; + LLVOAvatar* lastAvatar = nullptr; + U64 lastMeshId = 0; + auto* begin = gPipeline.beginRenderMap(type); + auto* end = gPipeline.endRenderMap(type); + + for (LLCullResult::drawinfo_iterator i = begin; i != end; ) + { + LLDrawInfo* pparams = *i; + LLCullResult::increment_iterator(i, end); + + if (rigged != (pparams->mAvatar != nullptr)) + { + // Pool contains both rigged and non-rigged DrawInfos. Only draw + // the objects we're interested in in this pass. + continue; + } + + if (rigged) + { + if (pparams->mGLTFMaterial) + { + gDeferredShadowGLTFAlphaBlendProgram.bind(rigged); + LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up); + LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); + LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(ALPHA_BLEND_CUTOFF); + mSimplePool->pushRiggedGLTFBatch(*pparams, lastAvatar, lastMeshId); + } + else + { + gDeferredShadowAlphaMaskProgram.bind(rigged); + LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up); + LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); + LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(ALPHA_BLEND_CUTOFF); + if (lastAvatar != pparams->mAvatar || lastMeshId != pparams->mSkinInfo->mHash) + { + mSimplePool->uploadMatrixPalette(*pparams); + lastAvatar = pparams->mAvatar; + lastMeshId = pparams->mSkinInfo->mHash; + } + + mSimplePool->pushBatch(*pparams, true, true); + } + } + else + { + if (pparams->mGLTFMaterial) + { + gDeferredShadowGLTFAlphaBlendProgram.bind(rigged); + LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up); + LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); + LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(ALPHA_BLEND_CUTOFF); + mSimplePool->pushGLTFBatch(*pparams); + } + else + { + gDeferredShadowAlphaMaskProgram.bind(rigged); + LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up); + LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); + LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(ALPHA_BLEND_CUTOFF); + mSimplePool->pushBatch(*pparams, true, true); + } + } + } + + gGL.loadMatrix(gGLModelView); + gGLLastMatrix = NULL; +} + +// Currently only used for shadows -Cosmic,2023-04-19 +void LLPipeline::renderMaskedObjects(U32 type, bool texture, bool batch_texture, bool rigged) +{ + assertInitialized(); + gGL.loadMatrix(gGLModelView); + gGLLastMatrix = NULL; + if (rigged) + { + mAlphaMaskPool->pushRiggedMaskBatches(type+1, texture, batch_texture); + } + else + { + mAlphaMaskPool->pushMaskBatches(type, texture, batch_texture); + } + gGL.loadMatrix(gGLModelView); + gGLLastMatrix = NULL; +} + +// Currently only used for shadows -Cosmic,2023-04-19 +void LLPipeline::renderFullbrightMaskedObjects(U32 type, bool texture, bool batch_texture, bool rigged) +{ + assertInitialized(); + gGL.loadMatrix(gGLModelView); + gGLLastMatrix = NULL; + if (rigged) + { + mFullbrightAlphaMaskPool->pushRiggedMaskBatches(type+1, texture, batch_texture); + } + else + { + mFullbrightAlphaMaskPool->pushMaskBatches(type, texture, batch_texture); + } + gGL.loadMatrix(gGLModelView); + gGLLastMatrix = NULL; +} + +void apply_cube_face_rotation(U32 face) +{ + switch (face) + { + case 0: + gGL.rotatef(90.f, 0, 1, 0); + gGL.rotatef(180.f, 1, 0, 0); + break; + case 2: + gGL.rotatef(-90.f, 1, 0, 0); + break; + case 4: + gGL.rotatef(180.f, 0, 1, 0); + gGL.rotatef(180.f, 0, 0, 1); + break; + case 1: + gGL.rotatef(-90.f, 0, 1, 0); + gGL.rotatef(180.f, 1, 0, 0); + break; + case 3: + gGL.rotatef(90, 1, 0, 0); + break; + case 5: + gGL.rotatef(180, 0, 0, 1); + break; + } +} + +void validate_framebuffer_object() +{ + GLenum status; + status = glCheckFramebufferStatus(GL_FRAMEBUFFER_EXT); + switch(status) + { + case GL_FRAMEBUFFER_COMPLETE: + //framebuffer OK, no error. + break; + case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: + // frame buffer not OK: probably means unsupported depth buffer format + LL_ERRS() << "Framebuffer Incomplete Missing Attachment." << LL_ENDL; + break; + case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: + // frame buffer not OK: probably means unsupported depth buffer format + LL_ERRS() << "Framebuffer Incomplete Attachment." << LL_ENDL; + break; + case GL_FRAMEBUFFER_UNSUPPORTED: + /* choose different formats */ + LL_ERRS() << "Framebuffer unsupported." << LL_ENDL; + break; + default: + LL_ERRS() << "Unknown framebuffer status." << LL_ENDL; + break; + } +} + +void LLPipeline::bindScreenToTexture() +{ + +} + +static LLTrace::BlockTimerStatHandle FTM_RENDER_BLOOM("Bloom"); + +void LLPipeline::visualizeBuffers(LLRenderTarget* src, LLRenderTarget* dst, U32 bufferIndex) +{ + dst->bindTarget(); + gDeferredBufferVisualProgram.bind(); + gDeferredBufferVisualProgram.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, src, false, LLTexUnit::TFO_BILINEAR, bufferIndex); + + static LLStaticHashedString mipLevel("mipLevel"); + if (RenderBufferVisualization != 4) + gDeferredBufferVisualProgram.uniform1f(mipLevel, 0); + else + gDeferredBufferVisualProgram.uniform1f(mipLevel, 8); + + mScreenTriangleVB->setBuffer(); + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + gDeferredBufferVisualProgram.unbind(); + dst->flush(); +} + +void LLPipeline::generateLuminance(LLRenderTarget* src, LLRenderTarget* dst) +{ + // luminance sample and mipmap generation + { + LL_PROFILE_GPU_ZONE("luminance sample"); + + dst->bindTarget(); + + LLGLDepthTest depth(GL_FALSE, GL_FALSE); + + gLuminanceProgram.bind(); + + S32 channel = 0; + channel = gLuminanceProgram.enableTexture(LLShaderMgr::DEFERRED_DIFFUSE); + if (channel > -1) + { + src->bindTexture(0, channel, LLTexUnit::TFO_POINT); + } + + channel = gLuminanceProgram.enableTexture(LLShaderMgr::DEFERRED_EMISSIVE); + if (channel > -1) + { + mGlow[1].bindTexture(0, channel); + } + + mScreenTriangleVB->setBuffer(); + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + dst->flush(); + + // note -- unbind AFTER the glGenerateMipMap so time in generatemipmap can be profiled under "Luminance" + // also note -- keep an eye on the performance of glGenerateMipmap, might need to replace it with a mip generation shader + gLuminanceProgram.unbind(); + } +} + +void LLPipeline::generateExposure(LLRenderTarget* src, LLRenderTarget* dst) { + // exposure sample + { + LL_PROFILE_GPU_ZONE("exposure sample"); + + { + // copy last frame's exposure into mLastExposure + mLastExposure.bindTarget(); + gCopyProgram.bind(); + gGL.getTexUnit(0)->bind(dst); + + mScreenTriangleVB->setBuffer(); + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + + mLastExposure.flush(); + } + + dst->bindTarget(); + + LLGLDepthTest depth(GL_FALSE, GL_FALSE); + + gExposureProgram.bind(); + + S32 channel = gExposureProgram.enableTexture(LLShaderMgr::DEFERRED_EMISSIVE); + if (channel > -1) + { + mLuminanceMap.bindTexture(0, channel, LLTexUnit::TFO_TRILINEAR); + } + + channel = gExposureProgram.enableTexture(LLShaderMgr::EXPOSURE_MAP); + if (channel > -1) + { + mLastExposure.bindTexture(0, channel); + } + + static LLStaticHashedString dt("dt"); + static LLStaticHashedString noiseVec("noiseVec"); + static LLStaticHashedString dynamic_exposure_params("dynamic_exposure_params"); + static LLCachedControl dynamic_exposure_coefficient(gSavedSettings, "RenderDynamicExposureCoefficient", 0.175f); + static LLCachedControl should_auto_adjust(gSavedSettings, "RenderSkyAutoAdjustLegacy", true); + + LLSettingsSky::ptr_t sky = LLEnvironment::instance().getCurrentSky(); + + F32 probe_ambiance = LLEnvironment::instance().getCurrentSky()->getReflectionProbeAmbiance(should_auto_adjust); + F32 exp_min = 1.f; + F32 exp_max = 1.f; + + if (probe_ambiance > 0.f) + { + F32 hdr_scale = sqrtf(LLEnvironment::instance().getCurrentSky()->getGamma())*2.f; + + if (hdr_scale > 1.f) + { + exp_min = 1.f / hdr_scale; + exp_max = hdr_scale; + } + } + gExposureProgram.uniform1f(dt, gFrameIntervalSeconds); + gExposureProgram.uniform2f(noiseVec, ll_frand() * 2.0 - 1.0, ll_frand() * 2.0 - 1.0); + gExposureProgram.uniform3f(dynamic_exposure_params, dynamic_exposure_coefficient, exp_min, exp_max); + + mScreenTriangleVB->setBuffer(); + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + + gGL.getTexUnit(channel)->unbind(mLastExposure.getUsage()); + gExposureProgram.unbind(); + dst->flush(); + } +} + +void LLPipeline::gammaCorrect(LLRenderTarget* src, LLRenderTarget* dst) { + dst->bindTarget(); + // gamma correct lighting + { + LL_PROFILE_GPU_ZONE("gamma correct"); + + static LLCachedControl buildNoPost(gSavedSettings, "RenderDisablePostProcessing", false); + + LLGLDepthTest depth(GL_FALSE, GL_FALSE); + + // Apply gamma correction to the frame here. + + static LLCachedControl should_auto_adjust(gSavedSettings, "RenderSkyAutoAdjustLegacy", true); + + LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); + + bool no_post = gSnapshotNoPost || (buildNoPost && gFloaterTools->isAvailable()); + LLGLSLShader& shader = no_post ? gNoPostGammaCorrectProgram : // no post (no gamma, no exposure, no tonemapping) + psky->getReflectionProbeAmbiance(should_auto_adjust) == 0.f ? gLegacyPostGammaCorrectProgram : + gDeferredPostGammaCorrectProgram; + + shader.bind(); + + S32 channel = 0; + + shader.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, src, false, LLTexUnit::TFO_POINT); + + shader.bindTexture(LLShaderMgr::EXPOSURE_MAP, &mExposureMap); + + shader.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, src->getWidth(), src->getHeight()); + + static LLCachedControl exposure(gSavedSettings, "RenderExposure", 1.f); + + F32 e = llclamp(exposure(), 0.5f, 4.f); + + static LLStaticHashedString s_exposure("exposure"); + + shader.uniform1f(s_exposure, e); + + mScreenTriangleVB->setBuffer(); + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + + gGL.getTexUnit(channel)->unbind(src->getUsage()); + shader.unbind(); + } + dst->flush(); +} + +void LLPipeline::copyScreenSpaceReflections(LLRenderTarget* src, LLRenderTarget* dst) +{ + + if (RenderScreenSpaceReflections && !gCubeSnapshot) + { + LL_PROFILE_GPU_ZONE("ssr copy"); + LLGLDepthTest depth(GL_TRUE, GL_TRUE, GL_ALWAYS); + + LLRenderTarget& depth_src = mRT->deferredScreen; + + dst->bindTarget(); + dst->clear(); + gCopyDepthProgram.bind(); + + S32 diff_map = gCopyDepthProgram.getTextureChannel(LLShaderMgr::DIFFUSE_MAP); + S32 depth_map = gCopyDepthProgram.getTextureChannel(LLShaderMgr::DEFERRED_DEPTH); + + gGL.getTexUnit(diff_map)->bind(src); + gGL.getTexUnit(depth_map)->bind(&depth_src, true); + + mScreenTriangleVB->setBuffer(); + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + + dst->flush(); + } +} + +void LLPipeline::generateGlow(LLRenderTarget* src) +{ + if (sRenderGlow) + { + LL_PROFILE_GPU_ZONE("glow"); + mGlow[2].bindTarget(); + mGlow[2].clear(); + + gGlowExtractProgram.bind(); + F32 maxAlpha = RenderGlowMaxExtractAlpha; + F32 warmthAmount = RenderGlowWarmthAmount; + LLVector3 lumWeights = RenderGlowLumWeights; + LLVector3 warmthWeights = RenderGlowWarmthWeights; + + gGlowExtractProgram.uniform1f(LLShaderMgr::GLOW_MIN_LUMINANCE, 9999); + gGlowExtractProgram.uniform1f(LLShaderMgr::GLOW_MAX_EXTRACT_ALPHA, maxAlpha); + gGlowExtractProgram.uniform3f(LLShaderMgr::GLOW_LUM_WEIGHTS, lumWeights.mV[0], lumWeights.mV[1], + lumWeights.mV[2]); + gGlowExtractProgram.uniform3f(LLShaderMgr::GLOW_WARMTH_WEIGHTS, warmthWeights.mV[0], warmthWeights.mV[1], + warmthWeights.mV[2]); + gGlowExtractProgram.uniform1f(LLShaderMgr::GLOW_WARMTH_AMOUNT, warmthAmount); + + if (RenderGlowNoise) + { + S32 channel = gGlowExtractProgram.enableTexture(LLShaderMgr::GLOW_NOISE_MAP); + if (channel > -1) + { + gGL.getTexUnit(channel)->bindManual(LLTexUnit::TT_TEXTURE, mTrueNoiseMap); + gGL.getTexUnit(channel)->setTextureFilteringOption(LLTexUnit::TFO_POINT); + } + gGlowExtractProgram.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, + mGlow[2].getWidth(), + mGlow[2].getHeight()); + } + + { + LLGLEnable blend_on(GL_BLEND); + + gGL.setSceneBlendType(LLRender::BT_ADD_WITH_ALPHA); + + gGlowExtractProgram.bindTexture(LLShaderMgr::DIFFUSE_MAP, src); + + gGL.color4f(1, 1, 1, 1); + gPipeline.enableLightsFullbright(); + + mScreenTriangleVB->setBuffer(); + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + + mGlow[2].flush(); + } + + gGlowExtractProgram.unbind(); + + // power of two between 1 and 1024 + U32 glowResPow = RenderGlowResolutionPow; + const U32 glow_res = llmax(1, llmin(1024, 1 << glowResPow)); + + S32 kernel = RenderGlowIterations * 2; + F32 delta = RenderGlowWidth / glow_res; + // Use half the glow width if we have the res set to less than 9 so that it looks + // almost the same in either case. + if (glowResPow < 9) + { + delta *= 0.5f; + } + F32 strength = RenderGlowStrength; + + gGlowProgram.bind(); + gGlowProgram.uniform1f(LLShaderMgr::GLOW_STRENGTH, strength); + + for (S32 i = 0; i < kernel; i++) + { + mGlow[i % 2].bindTarget(); + mGlow[i % 2].clear(); + + if (i == 0) + { + gGlowProgram.bindTexture(LLShaderMgr::DIFFUSE_MAP, &mGlow[2]); + } + else + { + gGlowProgram.bindTexture(LLShaderMgr::DIFFUSE_MAP, &mGlow[(i - 1) % 2]); + } + + if (i % 2 == 0) + { + gGlowProgram.uniform2f(LLShaderMgr::GLOW_DELTA, delta, 0); + } + else + { + gGlowProgram.uniform2f(LLShaderMgr::GLOW_DELTA, 0, delta); + } + + mScreenTriangleVB->setBuffer(); + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + + mGlow[i % 2].flush(); + } + + gGlowProgram.unbind(); + + } + else // !sRenderGlow, skip the glow ping-pong and just clear the result target + { + mGlow[1].bindTarget(); + mGlow[1].clear(); + mGlow[1].flush(); + } +} + +void LLPipeline::applyFXAA(LLRenderTarget* src, LLRenderTarget* dst) +{ + { + llassert(!gCubeSnapshot); + bool multisample = RenderFSAASamples > 1 && mRT->fxaaBuffer.isComplete(); + LLGLSLShader* shader = &gGlowCombineProgram; + + S32 width = dst->getWidth(); + S32 height = dst->getHeight(); + + // Present everything. + if (multisample) + { + LL_PROFILE_GPU_ZONE("aa"); + // bake out texture2D with RGBL for FXAA shader + mRT->fxaaBuffer.bindTarget(); + + shader = &gGlowCombineFXAAProgram; + shader->bind(); + + S32 channel = shader->enableTexture(LLShaderMgr::DEFERRED_DIFFUSE, src->getUsage()); + if (channel > -1) + { + src->bindTexture(0, channel, LLTexUnit::TFO_BILINEAR); + } + + { + LLGLDepthTest depth_test(GL_TRUE, GL_TRUE, GL_ALWAYS); + mScreenTriangleVB->setBuffer(); + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + } + + shader->disableTexture(LLShaderMgr::DEFERRED_DIFFUSE, src->getUsage()); + shader->unbind(); + + mRT->fxaaBuffer.flush(); + + dst->bindTarget(); + shader = &gFXAAProgram; + shader->bind(); + + channel = shader->enableTexture(LLShaderMgr::DIFFUSE_MAP, mRT->fxaaBuffer.getUsage()); + if (channel > -1) + { + mRT->fxaaBuffer.bindTexture(0, channel, LLTexUnit::TFO_BILINEAR); + } + + gGLViewport[0] = gViewerWindow->getWorldViewRectRaw().mLeft; + gGLViewport[1] = gViewerWindow->getWorldViewRectRaw().mBottom; + gGLViewport[2] = gViewerWindow->getWorldViewRectRaw().getWidth(); + gGLViewport[3] = gViewerWindow->getWorldViewRectRaw().getHeight(); + + glViewport(gGLViewport[0], gGLViewport[1], gGLViewport[2], gGLViewport[3]); + + F32 scale_x = (F32)width / mRT->fxaaBuffer.getWidth(); + F32 scale_y = (F32)height / mRT->fxaaBuffer.getHeight(); + shader->uniform2f(LLShaderMgr::FXAA_TC_SCALE, scale_x, scale_y); + shader->uniform2f(LLShaderMgr::FXAA_RCP_SCREEN_RES, 1.f / width * scale_x, 1.f / height * scale_y); + shader->uniform4f(LLShaderMgr::FXAA_RCP_FRAME_OPT, -0.5f / width * scale_x, -0.5f / height * scale_y, + 0.5f / width * scale_x, 0.5f / height * scale_y); + shader->uniform4f(LLShaderMgr::FXAA_RCP_FRAME_OPT2, -2.f / width * scale_x, -2.f / height * scale_y, + 2.f / width * scale_x, 2.f / height * scale_y); + + { + LLGLDepthTest depth_test(GL_TRUE, GL_TRUE, GL_ALWAYS); + S32 depth_channel = shader->getTextureChannel(LLShaderMgr::DEFERRED_DEPTH); + gGL.getTexUnit(depth_channel)->bind(&mRT->deferredScreen, true); + + mScreenTriangleVB->setBuffer(); + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + } + + shader->unbind(); + dst->flush(); + } + else { + copyRenderTarget(src, dst); + } + } +} + +void LLPipeline::copyRenderTarget(LLRenderTarget* src, LLRenderTarget* dst) +{ + + LL_PROFILE_GPU_ZONE("copyRenderTarget"); + dst->bindTarget(); + + gDeferredPostNoDoFProgram.bind(); + + gDeferredPostNoDoFProgram.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, src); + gDeferredPostNoDoFProgram.bindTexture(LLShaderMgr::DEFERRED_DEPTH, &mRT->deferredScreen, true); + + { + mScreenTriangleVB->setBuffer(); + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + } + + gDeferredPostNoDoFProgram.unbind(); + + dst->flush(); +} + +void LLPipeline::combineGlow(LLRenderTarget* src, LLRenderTarget* dst) +{ + // Go ahead and do our glow combine here in our destination. We blit this later into the front buffer. + + dst->bindTarget(); + + { + + gGlowCombineProgram.bind(); + + gGlowCombineProgram.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, src); + gGlowCombineProgram.bindTexture(LLShaderMgr::DEFERRED_EMISSIVE, &mGlow[1]); + + mScreenTriangleVB->setBuffer(); + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + } + + dst->flush(); +} + +void LLPipeline::renderDoF(LLRenderTarget* src, LLRenderTarget* dst) +{ + { + bool dof_enabled = + (RenderDepthOfFieldInEditMode || !LLToolMgr::getInstance()->inBuildMode()) && + RenderDepthOfField && + !gCubeSnapshot; + + gViewerWindow->setup3DViewport(); + + if (dof_enabled) + { + LL_PROFILE_GPU_ZONE("dof"); + LLGLDisable blend(GL_BLEND); + + // depth of field focal plane calculations + static F32 current_distance = 16.f; + static F32 start_distance = 16.f; + static F32 transition_time = 1.f; + + LLVector3 focus_point; + + LLViewerObject* obj = LLViewerMediaFocus::getInstance()->getFocusedObject(); + if (obj && obj->mDrawable && obj->isSelected()) + { // focus on selected media object + S32 face_idx = LLViewerMediaFocus::getInstance()->getFocusedFace(); + if (obj && obj->mDrawable) + { + LLFace* face = obj->mDrawable->getFace(face_idx); + if (face) + { + focus_point = face->getPositionAgent(); + } + } + } + + if (focus_point.isExactlyZero()) + { + if (LLViewerJoystick::getInstance()->getOverrideCamera()) + { // focus on point under cursor + focus_point.set(gDebugRaycastIntersection.getF32ptr()); + } + else if (gAgentCamera.cameraMouselook()) + { // focus on point under mouselook crosshairs + LLVector4a result; + result.clear(); + + gViewerWindow->cursorIntersect(-1, -1, 512.f, NULL, -1, false, false, true, true, NULL, &result); + + focus_point.set(result.getF32ptr()); + } + else + { + // focus on alt-zoom target + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + focus_point = LLVector3(gAgentCamera.getFocusGlobal() - region->getOriginGlobal()); + } + } + } + + LLVector3 eye = LLViewerCamera::getInstance()->getOrigin(); + F32 target_distance = 16.f; + if (!focus_point.isExactlyZero()) + { + target_distance = LLViewerCamera::getInstance()->getAtAxis() * (focus_point - eye); + } + + if (transition_time >= 1.f && fabsf(current_distance - target_distance) / current_distance > 0.01f) + { // large shift happened, interpolate smoothly to new target distance + transition_time = 0.f; + start_distance = current_distance; + } + else if (transition_time < 1.f) + { // currently in a transition, continue interpolating + transition_time += 1.f / CameraFocusTransitionTime * gFrameIntervalSeconds.value(); + transition_time = llmin(transition_time, 1.f); + + F32 t = cosf(transition_time * F_PI + F_PI) * 0.5f + 0.5f; + current_distance = start_distance + (target_distance - start_distance) * t; + } + else + { // small or no change, just snap to target distance + current_distance = target_distance; + } + + // convert to mm + F32 subject_distance = current_distance * 1000.f; + F32 fnumber = CameraFNumber; + F32 default_focal_length = CameraFocalLength; + + F32 fov = LLViewerCamera::getInstance()->getView(); + + const F32 default_fov = CameraFieldOfView * F_PI / 180.f; + + // F32 aspect_ratio = (F32) mRT->screen.getWidth()/(F32)mRT->screen.getHeight(); + + F32 dv = 2.f * default_focal_length * tanf(default_fov / 2.f); + + F32 focal_length = dv / (2 * tanf(fov / 2.f)); + + // F32 tan_pixel_angle = tanf(LLDrawable::sCurPixelAngle); + + // from wikipedia -- c = |s2-s1|/s2 * f^2/(N(S1-f)) + // where N = fnumber + // s2 = dot distance + // s1 = subject distance + // f = focal length + // + + F32 blur_constant = focal_length * focal_length / (fnumber * (subject_distance - focal_length)); + blur_constant /= 1000.f; // convert to meters for shader + F32 magnification = focal_length / (subject_distance - focal_length); + + { // build diffuse+bloom+CoF + mRT->deferredLight.bindTarget(); + + gDeferredCoFProgram.bind(); + + gDeferredCoFProgram.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, src, LLTexUnit::TFO_POINT); + gDeferredCoFProgram.bindTexture(LLShaderMgr::DEFERRED_DEPTH, &mRT->deferredScreen, true); + + gDeferredCoFProgram.uniform1f(LLShaderMgr::DEFERRED_DEPTH_CUTOFF, RenderEdgeDepthCutoff); + gDeferredCoFProgram.uniform1f(LLShaderMgr::DEFERRED_NORM_CUTOFF, RenderEdgeNormCutoff); + gDeferredCoFProgram.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, dst->getWidth(), dst->getHeight()); + gDeferredCoFProgram.uniform1f(LLShaderMgr::DOF_FOCAL_DISTANCE, -subject_distance / 1000.f); + gDeferredCoFProgram.uniform1f(LLShaderMgr::DOF_BLUR_CONSTANT, blur_constant); + gDeferredCoFProgram.uniform1f(LLShaderMgr::DOF_TAN_PIXEL_ANGLE, tanf(1.f / LLDrawable::sCurPixelAngle)); + gDeferredCoFProgram.uniform1f(LLShaderMgr::DOF_MAGNIFICATION, magnification); + gDeferredCoFProgram.uniform1f(LLShaderMgr::DOF_MAX_COF, CameraMaxCoF); + gDeferredCoFProgram.uniform1f(LLShaderMgr::DOF_RES_SCALE, CameraDoFResScale); + + mScreenTriangleVB->setBuffer(); + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + gDeferredCoFProgram.unbind(); + mRT->deferredLight.flush(); + } + + U32 dof_width = (U32)(mRT->screen.getWidth() * CameraDoFResScale); + U32 dof_height = (U32)(mRT->screen.getHeight() * CameraDoFResScale); + + { // perform DoF sampling at half-res (preserve alpha channel) + src->bindTarget(); + glViewport(0, 0, dof_width, dof_height); + + gGL.setColorMask(true, false); + + gDeferredPostProgram.bind(); + gDeferredPostProgram.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, &mRT->deferredLight, LLTexUnit::TFO_POINT); + + gDeferredPostProgram.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, dst->getWidth(), dst->getHeight()); + gDeferredPostProgram.uniform1f(LLShaderMgr::DOF_MAX_COF, CameraMaxCoF); + gDeferredPostProgram.uniform1f(LLShaderMgr::DOF_RES_SCALE, CameraDoFResScale); + + mScreenTriangleVB->setBuffer(); + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + + gDeferredPostProgram.unbind(); + + src->flush(); + gGL.setColorMask(true, true); + } + + { // combine result based on alpha + + dst->bindTarget(); + if (RenderFSAASamples > 1 && mRT->fxaaBuffer.isComplete()) + { + glViewport(0, 0, dst->getWidth(), dst->getHeight()); + } + else + { + gGLViewport[0] = gViewerWindow->getWorldViewRectRaw().mLeft; + gGLViewport[1] = gViewerWindow->getWorldViewRectRaw().mBottom; + gGLViewport[2] = gViewerWindow->getWorldViewRectRaw().getWidth(); + gGLViewport[3] = gViewerWindow->getWorldViewRectRaw().getHeight(); + glViewport(gGLViewport[0], gGLViewport[1], gGLViewport[2], gGLViewport[3]); + } + + gDeferredDoFCombineProgram.bind(); + gDeferredDoFCombineProgram.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, src, LLTexUnit::TFO_POINT); + gDeferredDoFCombineProgram.bindTexture(LLShaderMgr::DEFERRED_LIGHT, &mRT->deferredLight, LLTexUnit::TFO_POINT); + + gDeferredDoFCombineProgram.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, dst->getWidth(), dst->getHeight()); + gDeferredDoFCombineProgram.uniform1f(LLShaderMgr::DOF_MAX_COF, CameraMaxCoF); + gDeferredDoFCombineProgram.uniform1f(LLShaderMgr::DOF_RES_SCALE, CameraDoFResScale); + gDeferredDoFCombineProgram.uniform1f(LLShaderMgr::DOF_WIDTH, (dof_width - 1) / (F32)src->getWidth()); + gDeferredDoFCombineProgram.uniform1f(LLShaderMgr::DOF_HEIGHT, (dof_height - 1) / (F32)src->getHeight()); + + mScreenTriangleVB->setBuffer(); + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + + gDeferredDoFCombineProgram.unbind(); + + dst->flush(); + } + } + else + { + copyRenderTarget(src, dst); + } + } +} + +void LLPipeline::renderFinalize() +{ + llassert(!gCubeSnapshot); + LLVertexBuffer::unbind(); + LLGLState::checkStates(); + + assertInitialized(); + + LL_RECORD_BLOCK_TIME(FTM_RENDER_BLOOM); + LL_PROFILE_GPU_ZONE("renderFinalize"); + + gGL.color4f(1, 1, 1, 1); + LLGLDepthTest depth(GL_FALSE); + LLGLDisable blend(GL_BLEND); + LLGLDisable cull(GL_CULL_FACE); + + enableLightsFullbright(); + + gGL.setColorMask(true, true); + glClearColor(0, 0, 0, 0); + + + copyScreenSpaceReflections(&mRT->screen, &mSceneMap); + + generateLuminance(&mRT->screen, &mLuminanceMap); + + generateExposure(&mLuminanceMap, &mExposureMap); + + gammaCorrect(&mRT->screen, &mPostMap); + + LLVertexBuffer::unbind(); + + generateGlow(&mPostMap); + + combineGlow(&mPostMap, &mRT->screen); + + gGLViewport[0] = gViewerWindow->getWorldViewRectRaw().mLeft; + gGLViewport[1] = gViewerWindow->getWorldViewRectRaw().mBottom; + gGLViewport[2] = gViewerWindow->getWorldViewRectRaw().getWidth(); + gGLViewport[3] = gViewerWindow->getWorldViewRectRaw().getHeight(); + glViewport(gGLViewport[0], gGLViewport[1], gGLViewport[2], gGLViewport[3]); + + renderDoF(&mRT->screen, &mPostMap); + + applyFXAA(&mPostMap, &mRT->screen); + LLRenderTarget* finalBuffer = &mRT->screen; + if (RenderBufferVisualization > -1) + { + finalBuffer = &mPostMap; + switch (RenderBufferVisualization) + { + case 0: + case 1: + case 2: + case 3: + visualizeBuffers(&mRT->deferredScreen, finalBuffer, RenderBufferVisualization); + break; + case 4: + visualizeBuffers(&mLuminanceMap, finalBuffer, 0); + default: + break; + } + } + + // Present the screen target. + + gDeferredPostNoDoFProgram.bind(); + + // Whatever is last in the above post processing chain should _always_ be rendered directly here. If not, expect problems. + gDeferredPostNoDoFProgram.bindTexture(LLShaderMgr::DEFERRED_DIFFUSE, finalBuffer); + gDeferredPostNoDoFProgram.bindTexture(LLShaderMgr::DEFERRED_DEPTH, &mRT->deferredScreen, true); + + { + LLGLDepthTest depth_test(GL_TRUE, GL_TRUE, GL_ALWAYS); + mScreenTriangleVB->setBuffer(); + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + } + + gDeferredPostNoDoFProgram.unbind(); + + gGL.setSceneBlendType(LLRender::BT_ALPHA); + + if (hasRenderDebugMask(LLPipeline::RENDER_DEBUG_PHYSICS_SHAPES)) + { + renderPhysicsDisplay(); + } + + /*if (LLRenderTarget::sUseFBO && !gCubeSnapshot) + { // copy depth buffer from mRT->screen to framebuffer + LLRenderTarget::copyContentsToFramebuffer(mRT->screen, 0, 0, mRT->screen.getWidth(), mRT->screen.getHeight(), 0, 0, + mRT->screen.getWidth(), mRT->screen.getHeight(), + GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST); + }*/ + + LLVertexBuffer::unbind(); + + LLGLState::checkStates(); + + // flush calls made to "addTrianglesDrawn" so far to stats machinery + recordTrianglesDrawn(); +} + +void LLPipeline::bindLightFunc(LLGLSLShader& shader) +{ + S32 channel = shader.enableTexture(LLShaderMgr::DEFERRED_LIGHTFUNC); + if (channel > -1) + { + gGL.getTexUnit(channel)->bindManual(LLTexUnit::TT_TEXTURE, mLightFunc); + } + + channel = shader.enableTexture(LLShaderMgr::DEFERRED_BRDF_LUT, LLTexUnit::TT_TEXTURE); + if (channel > -1) + { + mPbrBrdfLut.bindTexture(0, channel); + } +} + +void LLPipeline::bindShadowMaps(LLGLSLShader& shader) +{ + for (U32 i = 0; i < 4; i++) + { + LLRenderTarget* shadow_target = getSunShadowTarget(i); + if (shadow_target) + { + S32 channel = shader.enableTexture(LLShaderMgr::DEFERRED_SHADOW0 + i, LLTexUnit::TT_TEXTURE); + if (channel > -1) + { + gGL.getTexUnit(channel)->bind(getSunShadowTarget(i), true); + } + } + } + + for (U32 i = 4; i < 6; i++) + { + S32 channel = shader.enableTexture(LLShaderMgr::DEFERRED_SHADOW0 + i); + if (channel > -1) + { + LLRenderTarget* shadow_target = getSpotShadowTarget(i - 4); + if (shadow_target) + { + gGL.getTexUnit(channel)->bind(shadow_target, true); + } + } + } +} + +void LLPipeline::bindDeferredShaderFast(LLGLSLShader& shader) +{ + if (shader.mCanBindFast) + { // was previously fully bound, use fast path + shader.bind(); + bindLightFunc(shader); + bindShadowMaps(shader); + bindReflectionProbes(shader); + } + else + { //wasn't previously bound, use slow path + bindDeferredShader(shader); + shader.mCanBindFast = true; + } +} + +void LLPipeline::bindDeferredShader(LLGLSLShader& shader, LLRenderTarget* light_target, LLRenderTarget* depth_target) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + LLRenderTarget* deferred_target = &mRT->deferredScreen; + LLRenderTarget* deferred_light_target = &mRT->deferredLight; + + shader.bind(); + S32 channel = 0; + channel = shader.enableTexture(LLShaderMgr::DEFERRED_DIFFUSE, deferred_target->getUsage()); + if (channel > -1) + { + deferred_target->bindTexture(0,channel, LLTexUnit::TFO_POINT); // frag_data[0] + gGL.getTexUnit(channel)->setTextureAddressMode(LLTexUnit::TAM_CLAMP); + } + + channel = shader.enableTexture(LLShaderMgr::DEFERRED_SPECULAR, deferred_target->getUsage()); + if (channel > -1) + { + deferred_target->bindTexture(1, channel, LLTexUnit::TFO_POINT); // frag_data[1] + gGL.getTexUnit(channel)->setTextureAddressMode(LLTexUnit::TAM_CLAMP); + } + + channel = shader.enableTexture(LLShaderMgr::DEFERRED_NORMAL, deferred_target->getUsage()); + if (channel > -1) + { + deferred_target->bindTexture(2, channel, LLTexUnit::TFO_POINT); // frag_data[2] + gGL.getTexUnit(channel)->setTextureAddressMode(LLTexUnit::TAM_CLAMP); + } + + channel = shader.enableTexture(LLShaderMgr::DEFERRED_EMISSIVE, deferred_target->getUsage()); + if (channel > -1) + { + deferred_target->bindTexture(3, channel, LLTexUnit::TFO_POINT); // frag_data[3] + gGL.getTexUnit(channel)->setTextureAddressMode(LLTexUnit::TAM_CLAMP); + } + + channel = shader.enableTexture(LLShaderMgr::DEFERRED_DEPTH, deferred_target->getUsage()); + if (channel > -1) + { + if (depth_target) + { + gGL.getTexUnit(channel)->bind(depth_target, true); + } + else + { + gGL.getTexUnit(channel)->bind(deferred_target, true); + } + stop_glerror(); + } + + channel = shader.enableTexture(LLShaderMgr::EXPOSURE_MAP); + if (channel > -1) + { + gGL.getTexUnit(channel)->bind(&mExposureMap); + } + + if (shader.getUniformLocation(LLShaderMgr::VIEWPORT) != -1) + { + shader.uniform4f(LLShaderMgr::VIEWPORT, (F32) gGLViewport[0], + (F32) gGLViewport[1], + (F32) gGLViewport[2], + (F32) gGLViewport[3]); + } + + if (sReflectionRender && !shader.getUniformLocation(LLShaderMgr::MODELVIEW_MATRIX)) + { + shader.uniformMatrix4fv(LLShaderMgr::MODELVIEW_MATRIX, 1, false, mReflectionModelView.m); + } + + channel = shader.enableTexture(LLShaderMgr::DEFERRED_NOISE); + if (channel > -1) + { + gGL.getTexUnit(channel)->bindManual(LLTexUnit::TT_TEXTURE, mNoiseMap); + gGL.getTexUnit(channel)->setTextureFilteringOption(LLTexUnit::TFO_POINT); + } + + bindLightFunc(shader); + + stop_glerror(); + + light_target = light_target ? light_target : deferred_light_target; + channel = shader.enableTexture(LLShaderMgr::DEFERRED_LIGHT, light_target->getUsage()); + if (channel > -1) + { + if (light_target->isComplete()) + { + light_target->bindTexture(0, channel, LLTexUnit::TFO_POINT); + } + else + { + gGL.getTexUnit(channel)->bindFast(LLViewerFetchedTexture::sWhiteImagep); + } + } + + stop_glerror(); + + bindShadowMaps(shader); + + stop_glerror(); + + F32 mat[16*6]; + for (U32 i = 0; i < 16; i++) + { + mat[i] = mSunShadowMatrix[0].m[i]; + mat[i+16] = mSunShadowMatrix[1].m[i]; + mat[i+32] = mSunShadowMatrix[2].m[i]; + mat[i+48] = mSunShadowMatrix[3].m[i]; + mat[i+64] = mSunShadowMatrix[4].m[i]; + mat[i+80] = mSunShadowMatrix[5].m[i]; + } + + shader.uniformMatrix4fv(LLShaderMgr::DEFERRED_SHADOW_MATRIX, 6, false, mat); + + stop_glerror(); + + if (!LLPipeline::sReflectionProbesEnabled) + { + channel = shader.enableTexture(LLShaderMgr::ENVIRONMENT_MAP, LLTexUnit::TT_CUBE_MAP); + if (channel > -1) + { + LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL; + if (cube_map) + { + cube_map->enable(channel); + cube_map->bind(); + } + + F32* m = gGLModelView; + + F32 mat[] = { m[0], m[1], m[2], + m[4], m[5], m[6], + m[8], m[9], m[10] }; + + shader.uniformMatrix3fv(LLShaderMgr::DEFERRED_ENV_MAT, 1, true, mat); + } + } + + bindReflectionProbes(shader); + + if (gAtmosphere) + { + // bind precomputed textures necessary for calculating sun and sky luminance + channel = shader.enableTexture(LLShaderMgr::TRANSMITTANCE_TEX, LLTexUnit::TT_TEXTURE); + if (channel > -1) + { + shader.bindTexture(LLShaderMgr::TRANSMITTANCE_TEX, gAtmosphere->getTransmittance()); + } + + channel = shader.enableTexture(LLShaderMgr::SCATTER_TEX, LLTexUnit::TT_TEXTURE_3D); + if (channel > -1) + { + shader.bindTexture(LLShaderMgr::SCATTER_TEX, gAtmosphere->getScattering()); + } + + channel = shader.enableTexture(LLShaderMgr::SINGLE_MIE_SCATTER_TEX, LLTexUnit::TT_TEXTURE_3D); + if (channel > -1) + { + shader.bindTexture(LLShaderMgr::SINGLE_MIE_SCATTER_TEX, gAtmosphere->getMieScattering()); + } + + channel = shader.enableTexture(LLShaderMgr::ILLUMINANCE_TEX, LLTexUnit::TT_TEXTURE); + if (channel > -1) + { + shader.bindTexture(LLShaderMgr::ILLUMINANCE_TEX, gAtmosphere->getIlluminance()); + } + } + + /*if (gCubeSnapshot) + { // we only really care about the first two values, but the shader needs increasing separation between clip planes + shader.uniform4f(LLShaderMgr::DEFERRED_SHADOW_CLIP, 1.f, 64.f, 128.f, 256.f); + } + else*/ + { + shader.uniform4fv(LLShaderMgr::DEFERRED_SHADOW_CLIP, 1, mSunClipPlanes.mV); + } + shader.uniform1f(LLShaderMgr::DEFERRED_SUN_WASH, RenderDeferredSunWash); + shader.uniform1f(LLShaderMgr::DEFERRED_SHADOW_NOISE, RenderShadowNoise); + shader.uniform1f(LLShaderMgr::DEFERRED_BLUR_SIZE, RenderShadowBlurSize); + + shader.uniform1f(LLShaderMgr::DEFERRED_SSAO_RADIUS, RenderSSAOScale); + shader.uniform1f(LLShaderMgr::DEFERRED_SSAO_MAX_RADIUS, RenderSSAOMaxScale); + + F32 ssao_factor = RenderSSAOFactor; + shader.uniform1f(LLShaderMgr::DEFERRED_SSAO_FACTOR, ssao_factor); + shader.uniform1f(LLShaderMgr::DEFERRED_SSAO_FACTOR_INV, 1.0/ssao_factor); + + LLVector3 ssao_effect = RenderSSAOEffect; + F32 matrix_diag = (ssao_effect[0] + 2.0*ssao_effect[1])/3.0; + F32 matrix_nondiag = (ssao_effect[0] - ssao_effect[1])/3.0; + // This matrix scales (proj of color onto <1/rt(3),1/rt(3),1/rt(3)>) by + // value factor, and scales remainder by saturation factor + F32 ssao_effect_mat[] = { matrix_diag, matrix_nondiag, matrix_nondiag, + matrix_nondiag, matrix_diag, matrix_nondiag, + matrix_nondiag, matrix_nondiag, matrix_diag}; + shader.uniformMatrix3fv(LLShaderMgr::DEFERRED_SSAO_EFFECT_MAT, 1, GL_FALSE, ssao_effect_mat); + + //F32 shadow_offset_error = 1.f + RenderShadowOffsetError * fabsf(LLViewerCamera::getInstance()->getOrigin().mV[2]); + F32 shadow_bias_error = RenderShadowBiasError * fabsf(LLViewerCamera::getInstance()->getOrigin().mV[2])/3000.f; + F32 shadow_bias = RenderShadowBias + shadow_bias_error; + + shader.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, deferred_target->getWidth(), deferred_target->getHeight()); + shader.uniform1f(LLShaderMgr::DEFERRED_NEAR_CLIP, LLViewerCamera::getInstance()->getNear()*2.f); + shader.uniform1f (LLShaderMgr::DEFERRED_SHADOW_OFFSET, RenderShadowOffset); //*shadow_offset_error); + shader.uniform1f(LLShaderMgr::DEFERRED_SHADOW_BIAS, shadow_bias); + shader.uniform1f(LLShaderMgr::DEFERRED_SPOT_SHADOW_OFFSET, RenderSpotShadowOffset); + shader.uniform1f(LLShaderMgr::DEFERRED_SPOT_SHADOW_BIAS, RenderSpotShadowBias); + + shader.uniform3fv(LLShaderMgr::DEFERRED_SUN_DIR, 1, mTransformedSunDir.mV); + shader.uniform3fv(LLShaderMgr::DEFERRED_MOON_DIR, 1, mTransformedMoonDir.mV); + shader.uniform2f(LLShaderMgr::DEFERRED_SHADOW_RES, mRT->shadow[0].getWidth(), mRT->shadow[0].getHeight()); + shader.uniform2f(LLShaderMgr::DEFERRED_PROJ_SHADOW_RES, mSpotShadow[0].getWidth(), mSpotShadow[0].getHeight()); + shader.uniform1f(LLShaderMgr::DEFERRED_DEPTH_CUTOFF, RenderEdgeDepthCutoff); + shader.uniform1f(LLShaderMgr::DEFERRED_NORM_CUTOFF, RenderEdgeNormCutoff); + + shader.uniformMatrix4fv(LLShaderMgr::MODELVIEW_DELTA_MATRIX, 1, GL_FALSE, gGLDeltaModelView); + shader.uniformMatrix4fv(LLShaderMgr::INVERSE_MODELVIEW_DELTA_MATRIX, 1, GL_FALSE, gGLInverseDeltaModelView); + + shader.uniform1i(LLShaderMgr::CUBE_SNAPSHOT, gCubeSnapshot ? 1 : 0); + + if (shader.getUniformLocation(LLShaderMgr::DEFERRED_NORM_MATRIX) >= 0) + { + glh::matrix4f norm_mat = get_current_modelview().inverse().transpose(); + shader.uniformMatrix4fv(LLShaderMgr::DEFERRED_NORM_MATRIX, 1, false, norm_mat.m); + } + + // auto adjust legacy sun color if needed + static LLCachedControl should_auto_adjust(gSavedSettings, "RenderSkyAutoAdjustLegacy", true); + static LLCachedControl auto_adjust_sun_color_scale(gSavedSettings, "RenderSkyAutoAdjustSunColorScale", 1.f); + LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); + LLColor3 sun_diffuse(mSunDiffuse.mV); + if (should_auto_adjust && psky->canAutoAdjust()) + { + sun_diffuse *= auto_adjust_sun_color_scale; + } + + shader.uniform3fv(LLShaderMgr::SUNLIGHT_COLOR, 1, sun_diffuse.mV); + shader.uniform3fv(LLShaderMgr::MOONLIGHT_COLOR, 1, mMoonDiffuse.mV); + + shader.uniform1f(LLShaderMgr::REFLECTION_PROBE_MAX_LOD, mReflectionMapManager.mMaxProbeLOD); +} + + +LLColor3 pow3f(LLColor3 v, F32 f) +{ + v.mV[0] = powf(v.mV[0], f); + v.mV[1] = powf(v.mV[1], f); + v.mV[2] = powf(v.mV[2], f); + return v; +} + +LLVector4 pow4fsrgb(LLVector4 v, F32 f) +{ + v.mV[0] = powf(v.mV[0], f); + v.mV[1] = powf(v.mV[1], f); + v.mV[2] = powf(v.mV[2], f); + return v; +} + +void LLPipeline::renderDeferredLighting() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + LL_PROFILE_GPU_ZONE("renderDeferredLighting"); + if (!sCull) + { + return; + } + + llassert(!sRenderingHUDs); + + F32 light_scale = 1.f; + + if (gCubeSnapshot) + { //darken local lights when probe ambiance is above 1 + light_scale = mReflectionMapManager.mLightScale; + } + + LLRenderTarget *screen_target = &mRT->screen; + LLRenderTarget* deferred_light_target = &mRT->deferredLight; + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("deferred"); + LLViewerCamera *camera = LLViewerCamera::getInstance(); + + if (gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_HUD)) + { + gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_HUD); + } + + gGL.setColorMask(true, true); + + // draw a cube around every light + LLVertexBuffer::unbind(); + + LLGLEnable cull(GL_CULL_FACE); + LLGLEnable blend(GL_BLEND); + + glh::matrix4f mat = copy_matrix(gGLModelView); + + setupHWLights(); // to set mSun/MoonDir; + + glh::vec4f tc(mSunDir.mV); + mat.mult_matrix_vec(tc); + mTransformedSunDir.set(tc.v); + + glh::vec4f tc_moon(mMoonDir.mV); + mat.mult_matrix_vec(tc_moon); + mTransformedMoonDir.set(tc_moon.v); + + if (RenderDeferredSSAO || RenderShadowDetail > 0) + { + LL_PROFILE_GPU_ZONE("sun program"); + deferred_light_target->bindTarget(); + { // paint shadow/SSAO light map (direct lighting lightmap) + LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("renderDeferredLighting - sun shadow"); + bindDeferredShader(gDeferredSunProgram, deferred_light_target); + mScreenTriangleVB->setBuffer(); + glClearColor(1, 1, 1, 1); + deferred_light_target->clear(GL_COLOR_BUFFER_BIT); + glClearColor(0, 0, 0, 0); + + glh::matrix4f inv_trans = get_current_modelview().inverse().transpose(); + + const U32 slice = 32; + F32 offset[slice * 3]; + for (U32 i = 0; i < 4; i++) + { + for (U32 j = 0; j < 8; j++) + { + glh::vec3f v; + v.set_value(sinf(6.284f / 8 * j), cosf(6.284f / 8 * j), -(F32) i); + v.normalize(); + inv_trans.mult_matrix_vec(v); + v.normalize(); + offset[(i * 8 + j) * 3 + 0] = v.v[0]; + offset[(i * 8 + j) * 3 + 1] = v.v[2]; + offset[(i * 8 + j) * 3 + 2] = v.v[1]; + } + } + + gDeferredSunProgram.uniform3fv(sOffset, slice, offset); + gDeferredSunProgram.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, + deferred_light_target->getWidth(), + deferred_light_target->getHeight()); + + { + LLGLDisable blend(GL_BLEND); + LLGLDepthTest depth(GL_TRUE, GL_FALSE, GL_ALWAYS); + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + } + + unbindDeferredShader(gDeferredSunProgram); + } + deferred_light_target->flush(); + } + + if (RenderDeferredSSAO) + { + // soften direct lighting lightmap + LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("renderDeferredLighting - soften shadow"); + LL_PROFILE_GPU_ZONE("soften shadow"); + // blur lightmap + screen_target->bindTarget(); + glClearColor(1, 1, 1, 1); + screen_target->clear(GL_COLOR_BUFFER_BIT); + glClearColor(0, 0, 0, 0); + + bindDeferredShader(gDeferredBlurLightProgram); + + LLVector3 go = RenderShadowGaussian; + const U32 kern_length = 4; + F32 blur_size = RenderShadowBlurSize; + F32 dist_factor = RenderShadowBlurDistFactor; + + // sample symmetrically with the middle sample falling exactly on 0.0 + F32 x = 0.f; + + LLVector3 gauss[32]; // xweight, yweight, offset + + for (U32 i = 0; i < kern_length; i++) + { + gauss[i].mV[0] = llgaussian(x, go.mV[0]); + gauss[i].mV[1] = llgaussian(x, go.mV[1]); + gauss[i].mV[2] = x; + x += 1.f; + } + + gDeferredBlurLightProgram.uniform2f(sDelta, 1.f, 0.f); + gDeferredBlurLightProgram.uniform1f(sDistFactor, dist_factor); + gDeferredBlurLightProgram.uniform3fv(sKern, kern_length, gauss[0].mV); + gDeferredBlurLightProgram.uniform1f(sKernScale, blur_size * (kern_length / 2.f - 0.5f)); + + { + LLGLDisable blend(GL_BLEND); + LLGLDepthTest depth(GL_TRUE, GL_FALSE, GL_ALWAYS); + mScreenTriangleVB->setBuffer(); + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + } + + screen_target->flush(); + unbindDeferredShader(gDeferredBlurLightProgram); + + bindDeferredShader(gDeferredBlurLightProgram, screen_target); + + deferred_light_target->bindTarget(); + + gDeferredBlurLightProgram.uniform2f(sDelta, 0.f, 1.f); + + { + LLGLDisable blend(GL_BLEND); + LLGLDepthTest depth(GL_TRUE, GL_FALSE, GL_ALWAYS); + mScreenTriangleVB->setBuffer(); + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + } + deferred_light_target->flush(); + unbindDeferredShader(gDeferredBlurLightProgram); + } + + screen_target->bindTarget(); + // clear color buffer here - zeroing alpha (glow) is important or it will accumulate against sky + glClearColor(0, 0, 0, 0); + screen_target->clear(GL_COLOR_BUFFER_BIT); + + if (RenderDeferredAtmospheric) + { // apply sunlight contribution + LLGLSLShader &soften_shader = gDeferredSoftenProgram; + + LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("renderDeferredLighting - atmospherics"); + LL_PROFILE_GPU_ZONE("atmospherics"); + bindDeferredShader(soften_shader); + + static LLCachedControl ssao_scale(gSavedSettings, "RenderSSAOIrradianceScale", 0.5f); + static LLCachedControl ssao_max(gSavedSettings, "RenderSSAOIrradianceMax", 0.25f); + static LLStaticHashedString ssao_scale_str("ssao_irradiance_scale"); + static LLStaticHashedString ssao_max_str("ssao_irradiance_max"); + + soften_shader.uniform1f(ssao_scale_str, ssao_scale); + soften_shader.uniform1f(ssao_max_str, ssao_max); + + LLEnvironment &environment = LLEnvironment::instance(); + soften_shader.uniform1i(LLShaderMgr::SUN_UP_FACTOR, environment.getIsSunUp() ? 1 : 0); + soften_shader.uniform3fv(LLShaderMgr::LIGHTNORM, 1, environment.getClampedLightNorm().mV); + + soften_shader.uniform4fv(LLShaderMgr::WATER_WATERPLANE, 1, LLDrawPoolAlpha::sWaterPlane.mV); + + { + LLGLDepthTest depth(GL_FALSE); + LLGLDisable blend(GL_BLEND); + + // full screen blit + mScreenTriangleVB->setBuffer(); + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + } + + unbindDeferredShader(gDeferredSoftenProgram); + } + + static LLCachedControl local_light_count(gSavedSettings, "RenderLocalLightCount", 256); + + if (local_light_count > 0) + { + gGL.setSceneBlendType(LLRender::BT_ADD); + std::list fullscreen_lights; + LLDrawable::drawable_list_t spot_lights; + LLDrawable::drawable_list_t fullscreen_spot_lights; + + if (!gCubeSnapshot) + { + for (U32 i = 0; i < 2; i++) + { + mTargetShadowSpotLight[i] = NULL; + } + } + + std::list light_colors; + + LLVertexBuffer::unbind(); + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("renderDeferredLighting - local lights"); + LL_PROFILE_GPU_ZONE("local lights"); + bindDeferredShader(gDeferredLightProgram); + + if (mCubeVB.isNull()) + { + mCubeVB = ll_create_cube_vb(LLVertexBuffer::MAP_VERTEX); + } + + mCubeVB->setBuffer(); + + LLGLDepthTest depth(GL_TRUE, GL_FALSE); + // mNearbyLights already includes distance calculation and excludes muted avatars. + // It is calculated from mLights + // mNearbyLights also provides fade value to gracefully fade-out out of range lights + S32 count = 0; + for (light_set_t::iterator iter = mNearbyLights.begin(); iter != mNearbyLights.end(); ++iter) + { + count++; + if (count > local_light_count) + { //stop collecting lights once we hit the limit + break; + } + + LLDrawable * drawablep = iter->drawable; + LLVOVolume * volume = drawablep->getVOVolume(); + if (!volume) + { + continue; + } + + if (volume->isAttachment()) + { + if (!sRenderAttachedLights) + { + continue; + } + } + + LLVector4a center; + center.load3(drawablep->getPositionAgent().mV); + const F32 *c = center.getF32ptr(); + F32 s = volume->getLightRadius() * 1.5f; + + // send light color to shader in linear space + LLColor3 col = volume->getLightLinearColor() * light_scale; + + if (col.magVecSquared() < 0.001f) + { + continue; + } + + if (s <= 0.001f) + { + continue; + } + + LLVector4a sa; + sa.splat(s); + if (camera->AABBInFrustumNoFarClip(center, sa) == 0) + { + continue; + } + + sVisibleLightCount++; + + if (camera->getOrigin().mV[0] > c[0] + s + 0.2f || camera->getOrigin().mV[0] < c[0] - s - 0.2f || + camera->getOrigin().mV[1] > c[1] + s + 0.2f || camera->getOrigin().mV[1] < c[1] - s - 0.2f || + camera->getOrigin().mV[2] > c[2] + s + 0.2f || camera->getOrigin().mV[2] < c[2] - s - 0.2f) + { // draw box if camera is outside box + if (volume->isLightSpotlight()) + { + drawablep->getVOVolume()->updateSpotLightPriority(); + spot_lights.push_back(drawablep); + continue; + } + + gDeferredLightProgram.uniform3fv(LLShaderMgr::LIGHT_CENTER, 1, c); + gDeferredLightProgram.uniform1f(LLShaderMgr::LIGHT_SIZE, s); + gDeferredLightProgram.uniform3fv(LLShaderMgr::DIFFUSE_COLOR, 1, col.mV); + gDeferredLightProgram.uniform1f(LLShaderMgr::LIGHT_FALLOFF, volume->getLightFalloff(DEFERRED_LIGHT_FALLOFF)); + gGL.syncMatrices(); + + mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, get_box_fan_indices(camera, center)); + } + else + { + if (volume->isLightSpotlight()) + { + drawablep->getVOVolume()->updateSpotLightPriority(); + fullscreen_spot_lights.push_back(drawablep); + continue; + } + + glh::vec3f tc(c); + mat.mult_matrix_vec(tc); + + fullscreen_lights.push_back(LLVector4(tc.v[0], tc.v[1], tc.v[2], s)); + light_colors.push_back(LLVector4(col.mV[0], col.mV[1], col.mV[2], volume->getLightFalloff(DEFERRED_LIGHT_FALLOFF))); + } + } + + // Bookmark comment to allow searching for mSpecialRenderMode == 3 (avatar edit mode), + // prev site of appended deferred character light, removed by SL-13522 09/20 + + unbindDeferredShader(gDeferredLightProgram); + } + + if (!spot_lights.empty()) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("renderDeferredLighting - projectors"); + LL_PROFILE_GPU_ZONE("projectors"); + LLGLDepthTest depth(GL_TRUE, GL_FALSE); + bindDeferredShader(gDeferredSpotLightProgram); + + mCubeVB->setBuffer(); + + gDeferredSpotLightProgram.enableTexture(LLShaderMgr::DEFERRED_PROJECTION); + + for (LLDrawable::drawable_list_t::iterator iter = spot_lights.begin(); iter != spot_lights.end(); ++iter) + { + LLDrawable *drawablep = *iter; + + LLVOVolume *volume = drawablep->getVOVolume(); + + LLVector4a center; + center.load3(drawablep->getPositionAgent().mV); + const F32* c = center.getF32ptr(); + F32 s = volume->getLightRadius() * 1.5f; + + sVisibleLightCount++; + + setupSpotLight(gDeferredSpotLightProgram, drawablep); + + // send light color to shader in linear space + LLColor3 col = volume->getLightLinearColor() * light_scale; + + gDeferredSpotLightProgram.uniform3fv(LLShaderMgr::LIGHT_CENTER, 1, c); + gDeferredSpotLightProgram.uniform1f(LLShaderMgr::LIGHT_SIZE, s); + gDeferredSpotLightProgram.uniform3fv(LLShaderMgr::DIFFUSE_COLOR, 1, col.mV); + gDeferredSpotLightProgram.uniform1f(LLShaderMgr::LIGHT_FALLOFF, volume->getLightFalloff(DEFERRED_LIGHT_FALLOFF)); + gGL.syncMatrices(); + + mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, get_box_fan_indices(camera, center)); + } + gDeferredSpotLightProgram.disableTexture(LLShaderMgr::DEFERRED_PROJECTION); + unbindDeferredShader(gDeferredSpotLightProgram); + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("renderDeferredLighting - fullscreen lights"); + LLGLDepthTest depth(GL_FALSE); + LL_PROFILE_GPU_ZONE("fullscreen lights"); + + U32 count = 0; + + const U32 max_count = LL_DEFERRED_MULTI_LIGHT_COUNT; + LLVector4 light[max_count]; + LLVector4 col[max_count]; + + F32 far_z = 0.f; + + while (!fullscreen_lights.empty()) + { + light[count] = fullscreen_lights.front(); + fullscreen_lights.pop_front(); + col[count] = light_colors.front(); + light_colors.pop_front(); + + far_z = llmin(light[count].mV[2] - light[count].mV[3], far_z); + count++; + if (count == max_count || fullscreen_lights.empty()) + { + U32 idx = count - 1; + bindDeferredShader(gDeferredMultiLightProgram[idx]); + gDeferredMultiLightProgram[idx].uniform1i(LLShaderMgr::MULTI_LIGHT_COUNT, count); + gDeferredMultiLightProgram[idx].uniform4fv(LLShaderMgr::MULTI_LIGHT, count, (GLfloat*)light); + gDeferredMultiLightProgram[idx].uniform4fv(LLShaderMgr::MULTI_LIGHT_COL, count, (GLfloat*)col); + gDeferredMultiLightProgram[idx].uniform1f(LLShaderMgr::MULTI_LIGHT_FAR_Z, far_z); + far_z = 0.f; + count = 0; + mScreenTriangleVB->setBuffer(); + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + unbindDeferredShader(gDeferredMultiLightProgram[idx]); + } + } + + bindDeferredShader(gDeferredMultiSpotLightProgram); + + gDeferredMultiSpotLightProgram.enableTexture(LLShaderMgr::DEFERRED_PROJECTION); + + mScreenTriangleVB->setBuffer(); + + for (LLDrawable::drawable_list_t::iterator iter = fullscreen_spot_lights.begin(); iter != fullscreen_spot_lights.end(); ++iter) + { + LLDrawable* drawablep = *iter; + LLVOVolume* volume = drawablep->getVOVolume(); + LLVector3 center = drawablep->getPositionAgent(); + F32* c = center.mV; + F32 light_size_final = volume->getLightRadius() * 1.5f; + F32 light_falloff_final = volume->getLightFalloff(DEFERRED_LIGHT_FALLOFF); + + sVisibleLightCount++; + + glh::vec3f tc(c); + mat.mult_matrix_vec(tc); + + setupSpotLight(gDeferredMultiSpotLightProgram, drawablep); + + // send light color to shader in linear space + LLColor3 col = volume->getLightLinearColor() * light_scale; + + gDeferredMultiSpotLightProgram.uniform3fv(LLShaderMgr::LIGHT_CENTER, 1, tc.v); + gDeferredMultiSpotLightProgram.uniform1f(LLShaderMgr::LIGHT_SIZE, light_size_final); + gDeferredMultiSpotLightProgram.uniform3fv(LLShaderMgr::DIFFUSE_COLOR, 1, col.mV); + gDeferredMultiSpotLightProgram.uniform1f(LLShaderMgr::LIGHT_FALLOFF, light_falloff_final); + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + } + + gDeferredMultiSpotLightProgram.disableTexture(LLShaderMgr::DEFERRED_PROJECTION); + unbindDeferredShader(gDeferredMultiSpotLightProgram); + } + } + + gGL.setColorMask(true, true); + } + + { // render non-deferred geometry (alpha, fullbright, glow) + LLGLDisable blend(GL_BLEND); + + pushRenderTypeMask(); + andRenderTypeMask(LLPipeline::RENDER_TYPE_ALPHA, + LLPipeline::RENDER_TYPE_ALPHA_PRE_WATER, + LLPipeline::RENDER_TYPE_ALPHA_POST_WATER, + LLPipeline::RENDER_TYPE_FULLBRIGHT, + LLPipeline::RENDER_TYPE_VOLUME, + LLPipeline::RENDER_TYPE_GLOW, + LLPipeline::RENDER_TYPE_BUMP, + LLPipeline::RENDER_TYPE_GLTF_PBR, + LLPipeline::RENDER_TYPE_PASS_SIMPLE, + LLPipeline::RENDER_TYPE_PASS_ALPHA, + LLPipeline::RENDER_TYPE_PASS_ALPHA_MASK, + LLPipeline::RENDER_TYPE_PASS_BUMP, + LLPipeline::RENDER_TYPE_PASS_POST_BUMP, + LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT, + LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_ALPHA_MASK, + LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_SHINY, + LLPipeline::RENDER_TYPE_PASS_GLOW, + LLPipeline::RENDER_TYPE_PASS_GLTF_GLOW, + LLPipeline::RENDER_TYPE_PASS_GRASS, + LLPipeline::RENDER_TYPE_PASS_SHINY, + LLPipeline::RENDER_TYPE_PASS_INVISIBLE, + LLPipeline::RENDER_TYPE_PASS_INVISI_SHINY, + LLPipeline::RENDER_TYPE_AVATAR, + LLPipeline::RENDER_TYPE_CONTROL_AV, + LLPipeline::RENDER_TYPE_ALPHA_MASK, + LLPipeline::RENDER_TYPE_FULLBRIGHT_ALPHA_MASK, + LLPipeline::RENDER_TYPE_WATER, + END_RENDER_TYPES); + + renderGeomPostDeferred(*LLViewerCamera::getInstance()); + popRenderTypeMask(); + } + + screen_target->flush(); + + if (!gCubeSnapshot) + { + // this is the end of the 3D scene render, grab a copy of the modelview and projection + // matrix for use in off-by-one-frame effects in the next frame + for (U32 i = 0; i < 16; i++) + { + gGLLastModelView[i] = gGLModelView[i]; + gGLLastProjection[i] = gGLProjection[i]; + } + } + gGL.setColorMask(true, true); +} + +void LLPipeline::doAtmospherics() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + + if (sImpostorRender) + { // do not attempt atmospherics on impostors + return; + } + + if (RenderDeferredAtmospheric) + { + { + // copy depth buffer for use in haze shader (use water displacement map as temp storage) + LLGLDepthTest depth(GL_TRUE, GL_TRUE, GL_ALWAYS); + + LLRenderTarget& src = gPipeline.mRT->screen; + LLRenderTarget& depth_src = gPipeline.mRT->deferredScreen; + LLRenderTarget& dst = gPipeline.mWaterDis; + + mRT->screen.flush(); + dst.bindTarget(); + gCopyDepthProgram.bind(); + + S32 diff_map = gCopyDepthProgram.getTextureChannel(LLShaderMgr::DIFFUSE_MAP); + S32 depth_map = gCopyDepthProgram.getTextureChannel(LLShaderMgr::DEFERRED_DEPTH); + + gGL.getTexUnit(diff_map)->bind(&src); + gGL.getTexUnit(depth_map)->bind(&depth_src, true); + + gGL.setColorMask(false, false); + gPipeline.mScreenTriangleVB->setBuffer(); + gPipeline.mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + + dst.flush(); + mRT->screen.bindTarget(); + } + + LLGLEnable blend(GL_BLEND); + gGL.blendFunc(LLRender::BF_ONE, LLRender::BF_SOURCE_ALPHA, LLRender::BF_ZERO, LLRender::BF_SOURCE_ALPHA); + gGL.setColorMask(true, true); + + // apply haze + LLGLSLShader& haze_shader = gHazeProgram; + + LL_PROFILE_GPU_ZONE("haze"); + bindDeferredShader(haze_shader, nullptr, &mWaterDis); + + LLEnvironment& environment = LLEnvironment::instance(); + haze_shader.uniform1i(LLShaderMgr::SUN_UP_FACTOR, environment.getIsSunUp() ? 1 : 0); + haze_shader.uniform3fv(LLShaderMgr::LIGHTNORM, 1, environment.getClampedLightNorm().mV); + + haze_shader.uniform4fv(LLShaderMgr::WATER_WATERPLANE, 1, LLDrawPoolAlpha::sWaterPlane.mV); + + LLGLDepthTest depth(GL_FALSE); + + // full screen blit + mScreenTriangleVB->setBuffer(); + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + + unbindDeferredShader(haze_shader); + + gGL.setSceneBlendType(LLRender::BT_ALPHA); + } +} + +void LLPipeline::doWaterHaze() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + if (sImpostorRender) + { // do not attempt water haze on impostors + return; + } + + if (RenderDeferredAtmospheric) + { + // copy depth buffer for use in haze shader (use water displacement map as temp storage) + { + LLGLDepthTest depth(GL_TRUE, GL_TRUE, GL_ALWAYS); + + LLRenderTarget& src = gPipeline.mRT->screen; + LLRenderTarget& depth_src = gPipeline.mRT->deferredScreen; + LLRenderTarget& dst = gPipeline.mWaterDis; + + mRT->screen.flush(); + dst.bindTarget(); + gCopyDepthProgram.bind(); + + S32 diff_map = gCopyDepthProgram.getTextureChannel(LLShaderMgr::DIFFUSE_MAP); + S32 depth_map = gCopyDepthProgram.getTextureChannel(LLShaderMgr::DEFERRED_DEPTH); + + gGL.getTexUnit(diff_map)->bind(&src); + gGL.getTexUnit(depth_map)->bind(&depth_src, true); + + gGL.setColorMask(false, false); + gPipeline.mScreenTriangleVB->setBuffer(); + gPipeline.mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + + dst.flush(); + mRT->screen.bindTarget(); + } + + LLGLEnable blend(GL_BLEND); + gGL.blendFunc(LLRender::BF_ONE, LLRender::BF_SOURCE_ALPHA, LLRender::BF_ZERO, LLRender::BF_SOURCE_ALPHA); + + gGL.setColorMask(true, true); + + // apply haze + LLGLSLShader& haze_shader = gHazeWaterProgram; + + LL_PROFILE_GPU_ZONE("haze"); + bindDeferredShader(haze_shader, nullptr, &mWaterDis); + + haze_shader.uniform4fv(LLShaderMgr::WATER_WATERPLANE, 1, LLDrawPoolAlpha::sWaterPlane.mV); + + static LLStaticHashedString above_water_str("above_water"); + haze_shader.uniform1i(above_water_str, sUnderWaterRender ? -1 : 1); + + if (LLPipeline::sUnderWaterRender) + { + LLGLDepthTest depth(GL_FALSE); + + // full screen blit + mScreenTriangleVB->setBuffer(); + mScreenTriangleVB->drawArrays(LLRender::TRIANGLES, 0, 3); + } + else + { + //render water patches like LLDrawPoolWater does + LLGLDepthTest depth(GL_TRUE, GL_FALSE); + LLGLDisable cull(GL_CULL_FACE); + + gGLLastMatrix = NULL; + gGL.loadMatrix(gGLModelView); + + if (mWaterPool) + { + mWaterPool->pushFaceGeometry(); + } + } + + unbindDeferredShader(haze_shader); + + + gGL.setSceneBlendType(LLRender::BT_ALPHA); + } +} + +void LLPipeline::setupSpotLight(LLGLSLShader& shader, LLDrawable* drawablep) +{ + //construct frustum + LLVOVolume* volume = drawablep->getVOVolume(); + LLVector3 params = volume->getSpotLightParams(); + + F32 fov = params.mV[0]; + F32 focus = params.mV[1]; + + LLVector3 pos = drawablep->getPositionAgent(); + LLQuaternion quat = volume->getRenderRotation(); + LLVector3 scale = volume->getScale(); + + //get near clip plane + LLVector3 at_axis(0,0,-scale.mV[2]*0.5f); + at_axis *= quat; + + LLVector3 np = pos+at_axis; + at_axis.normVec(); + + //get origin that has given fov for plane np, at_axis, and given scale + F32 dist = (scale.mV[1]*0.5f)/tanf(fov*0.5f); + + LLVector3 origin = np - at_axis*dist; + + //matrix from volume space to agent space + LLMatrix4 light_mat(quat, LLVector4(origin,1.f)); + + glh::matrix4f light_to_agent((F32*) light_mat.mMatrix); + glh::matrix4f light_to_screen = get_current_modelview() * light_to_agent; + + glh::matrix4f screen_to_light = light_to_screen.inverse(); + + F32 s = volume->getLightRadius()*1.5f; + F32 near_clip = dist; + F32 width = scale.mV[VX]; + F32 height = scale.mV[VY]; + F32 far_clip = s+dist-scale.mV[VZ]; + + F32 fovy = fov * RAD_TO_DEG; + F32 aspect = width/height; + + glh::matrix4f trans(0.5f, 0.f, 0.f, 0.5f, + 0.f, 0.5f, 0.f, 0.5f, + 0.f, 0.f, 0.5f, 0.5f, + 0.f, 0.f, 0.f, 1.f); + + glh::vec3f p1(0, 0, -(near_clip+0.01f)); + glh::vec3f p2(0, 0, -(near_clip+1.f)); + + glh::vec3f screen_origin(0, 0, 0); + + light_to_screen.mult_matrix_vec(p1); + light_to_screen.mult_matrix_vec(p2); + light_to_screen.mult_matrix_vec(screen_origin); + + glh::vec3f n = p2-p1; + n.normalize(); + + F32 proj_range = far_clip - near_clip; + glh::matrix4f light_proj = gl_perspective(fovy, aspect, near_clip, far_clip); + screen_to_light = trans * light_proj * screen_to_light; + shader.uniformMatrix4fv(LLShaderMgr::PROJECTOR_MATRIX, 1, false, screen_to_light.m); + shader.uniform1f(LLShaderMgr::PROJECTOR_NEAR, near_clip); + shader.uniform3fv(LLShaderMgr::PROJECTOR_P, 1, p1.v); + shader.uniform3fv(LLShaderMgr::PROJECTOR_N, 1, n.v); + shader.uniform3fv(LLShaderMgr::PROJECTOR_ORIGIN, 1, screen_origin.v); + shader.uniform1f(LLShaderMgr::PROJECTOR_RANGE, proj_range); + shader.uniform1f(LLShaderMgr::PROJECTOR_AMBIANCE, params.mV[2]); + S32 s_idx = -1; + + for (U32 i = 0; i < 2; i++) + { + if (mShadowSpotLight[i] == drawablep) + { + s_idx = i; + } + } + + shader.uniform1i(LLShaderMgr::PROJECTOR_SHADOW_INDEX, s_idx); + + if (s_idx >= 0) + { + shader.uniform1f(LLShaderMgr::PROJECTOR_SHADOW_FADE, 1.f-mSpotLightFade[s_idx]); + } + else + { + shader.uniform1f(LLShaderMgr::PROJECTOR_SHADOW_FADE, 1.f); + } + + // make sure we're not already targeting the same spot light with both shadow maps + llassert(mTargetShadowSpotLight[0] != mTargetShadowSpotLight[1] || mTargetShadowSpotLight[0].isNull()); + + if (!gCubeSnapshot) + { + LLDrawable* potential = drawablep; + //determine if this light is higher priority than one of the existing spot shadows + F32 m_pri = volume->getSpotLightPriority(); + + for (U32 i = 0; i < 2; i++) + { + F32 pri = 0.f; + + if (mTargetShadowSpotLight[i].notNull()) + { + pri = mTargetShadowSpotLight[i]->getVOVolume()->getSpotLightPriority(); + } + + if (m_pri > pri) + { + LLDrawable* temp = mTargetShadowSpotLight[i]; + mTargetShadowSpotLight[i] = potential; + potential = temp; + m_pri = pri; + } + } + } + + // make sure we didn't end up targeting the same spot light with both shadow maps + llassert(mTargetShadowSpotLight[0] != mTargetShadowSpotLight[1] || mTargetShadowSpotLight[0].isNull()); + + LLViewerTexture* img = volume->getLightTexture(); + + if (img == NULL) + { + img = LLViewerFetchedTexture::sWhiteImagep; + } + + S32 channel = shader.enableTexture(LLShaderMgr::DEFERRED_PROJECTION); + + if (channel > -1) + { + if (img) + { + gGL.getTexUnit(channel)->bind(img); + + F32 lod_range = logf(img->getWidth())/logf(2.f); + + shader.uniform1f(LLShaderMgr::PROJECTOR_FOCUS, focus); + shader.uniform1f(LLShaderMgr::PROJECTOR_LOD, lod_range); + shader.uniform1f(LLShaderMgr::PROJECTOR_AMBIENT_LOD, llclamp((proj_range-focus)/proj_range*lod_range, 0.f, 1.f)); + } + } + +} + +void LLPipeline::unbindDeferredShader(LLGLSLShader &shader) +{ + LLRenderTarget* deferred_target = &mRT->deferredScreen; + LLRenderTarget* deferred_light_target = &mRT->deferredLight; + + stop_glerror(); + shader.disableTexture(LLShaderMgr::DEFERRED_NORMAL, deferred_target->getUsage()); + shader.disableTexture(LLShaderMgr::DEFERRED_DIFFUSE, deferred_target->getUsage()); + shader.disableTexture(LLShaderMgr::DEFERRED_SPECULAR, deferred_target->getUsage()); + shader.disableTexture(LLShaderMgr::DEFERRED_EMISSIVE, deferred_target->getUsage()); + shader.disableTexture(LLShaderMgr::DEFERRED_BRDF_LUT); + //shader.disableTexture(LLShaderMgr::DEFERRED_DEPTH, deferred_depth_target->getUsage()); + shader.disableTexture(LLShaderMgr::DEFERRED_DEPTH, deferred_target->getUsage()); + shader.disableTexture(LLShaderMgr::DEFERRED_LIGHT, deferred_light_target->getUsage()); + shader.disableTexture(LLShaderMgr::DIFFUSE_MAP); + shader.disableTexture(LLShaderMgr::DEFERRED_BLOOM); + + for (U32 i = 0; i < 4; i++) + { + if (shader.disableTexture(LLShaderMgr::DEFERRED_SHADOW0+i) > -1) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); + } + } + + for (U32 i = 4; i < 6; i++) + { + if (shader.disableTexture(LLShaderMgr::DEFERRED_SHADOW0+i) > -1) + { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); + } + } + + shader.disableTexture(LLShaderMgr::DEFERRED_NOISE); + shader.disableTexture(LLShaderMgr::DEFERRED_LIGHTFUNC); + + if (!LLPipeline::sReflectionProbesEnabled) + { + S32 channel = shader.disableTexture(LLShaderMgr::ENVIRONMENT_MAP, LLTexUnit::TT_CUBE_MAP); + if (channel > -1) + { + LLCubeMap* cube_map = gSky.mVOSkyp ? gSky.mVOSkyp->getCubeMap() : NULL; + if (cube_map) + { + cube_map->disable(); + } + } + } + + unbindReflectionProbes(shader); + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + gGL.getTexUnit(0)->activate(); + shader.unbind(); +} + +void LLPipeline::setEnvMat(LLGLSLShader& shader) +{ + F32* m = gGLModelView; + + F32 mat[] = { m[0], m[1], m[2], + m[4], m[5], m[6], + m[8], m[9], m[10] }; + + shader.uniformMatrix3fv(LLShaderMgr::DEFERRED_ENV_MAT, 1, true, mat); +} + +void LLPipeline::bindReflectionProbes(LLGLSLShader& shader) +{ + if (!sReflectionProbesEnabled) + { + return; + } + + S32 channel = shader.enableTexture(LLShaderMgr::REFLECTION_PROBES, LLTexUnit::TT_CUBE_MAP_ARRAY); + bool bound = false; + if (channel > -1 && mReflectionMapManager.mTexture.notNull()) + { + mReflectionMapManager.mTexture->bind(channel); + bound = true; + } + + channel = shader.enableTexture(LLShaderMgr::IRRADIANCE_PROBES, LLTexUnit::TT_CUBE_MAP_ARRAY); + if (channel > -1 && mReflectionMapManager.mIrradianceMaps.notNull()) + { + mReflectionMapManager.mIrradianceMaps->bind(channel); + bound = true; + } + + if (bound) + { + mReflectionMapManager.setUniforms(); + + setEnvMat(shader); + } + + // reflection probe shaders generally sample the scene map as well for SSR + channel = shader.enableTexture(LLShaderMgr::SCENE_MAP); + if (channel > -1) + { + gGL.getTexUnit(channel)->bind(&mSceneMap); + } + + + shader.uniform1f(LLShaderMgr::DEFERRED_SSR_ITR_COUNT, RenderScreenSpaceReflectionIterations); + shader.uniform1f(LLShaderMgr::DEFERRED_SSR_DIST_BIAS, RenderScreenSpaceReflectionDistanceBias); + shader.uniform1f(LLShaderMgr::DEFERRED_SSR_RAY_STEP, RenderScreenSpaceReflectionRayStep); + shader.uniform1f(LLShaderMgr::DEFERRED_SSR_GLOSSY_SAMPLES, RenderScreenSpaceReflectionGlossySamples); + shader.uniform1f(LLShaderMgr::DEFERRED_SSR_REJECT_BIAS, RenderScreenSpaceReflectionDepthRejectBias); + mPoissonOffset++; + + if (mPoissonOffset > 128 - RenderScreenSpaceReflectionGlossySamples) + mPoissonOffset = 0; + + shader.uniform1f(LLShaderMgr::DEFERRED_SSR_NOISE_SINE, mPoissonOffset); + shader.uniform1f(LLShaderMgr::DEFERRED_SSR_ADAPTIVE_STEP_MULT, RenderScreenSpaceReflectionAdaptiveStepMultiplier); + + channel = shader.enableTexture(LLShaderMgr::SCENE_DEPTH); + if (channel > -1) + { + gGL.getTexUnit(channel)->bind(&mSceneMap, true); + } + + +} + +void LLPipeline::unbindReflectionProbes(LLGLSLShader& shader) +{ + S32 channel = shader.disableTexture(LLShaderMgr::REFLECTION_PROBES, LLTexUnit::TT_CUBE_MAP); + if (channel > -1 && mReflectionMapManager.mTexture.notNull()) + { + mReflectionMapManager.mTexture->unbind(); + if (channel == 0) + { + gGL.getTexUnit(channel)->enable(LLTexUnit::TT_TEXTURE); + } + } +} + + +inline float sgn(float a) +{ + if (a > 0.0F) return (1.0F); + if (a < 0.0F) return (-1.0F); + return (0.0F); +} + +glh::matrix4f look(const LLVector3 pos, const LLVector3 dir, const LLVector3 up) +{ + glh::matrix4f ret; + + LLVector3 dirN; + LLVector3 upN; + LLVector3 lftN; + + lftN = dir % up; + lftN.normVec(); + + upN = lftN % dir; + upN.normVec(); + + dirN = dir; + dirN.normVec(); + + ret.m[ 0] = lftN[0]; + ret.m[ 1] = upN[0]; + ret.m[ 2] = -dirN[0]; + ret.m[ 3] = 0.f; + + ret.m[ 4] = lftN[1]; + ret.m[ 5] = upN[1]; + ret.m[ 6] = -dirN[1]; + ret.m[ 7] = 0.f; + + ret.m[ 8] = lftN[2]; + ret.m[ 9] = upN[2]; + ret.m[10] = -dirN[2]; + ret.m[11] = 0.f; + + ret.m[12] = -(lftN*pos); + ret.m[13] = -(upN*pos); + ret.m[14] = dirN*pos; + ret.m[15] = 1.f; + + return ret; +} + +glh::matrix4f scale_translate_to_fit(const LLVector3 min, const LLVector3 max) +{ + glh::matrix4f ret; + ret.m[ 0] = 2/(max[0]-min[0]); + ret.m[ 4] = 0; + ret.m[ 8] = 0; + ret.m[12] = -(max[0]+min[0])/(max[0]-min[0]); + + ret.m[ 1] = 0; + ret.m[ 5] = 2/(max[1]-min[1]); + ret.m[ 9] = 0; + ret.m[13] = -(max[1]+min[1])/(max[1]-min[1]); + + ret.m[ 2] = 0; + ret.m[ 6] = 0; + ret.m[10] = 2/(max[2]-min[2]); + ret.m[14] = -(max[2]+min[2])/(max[2]-min[2]); + + ret.m[ 3] = 0; + ret.m[ 7] = 0; + ret.m[11] = 0; + ret.m[15] = 1; + + return ret; +} + +static LLTrace::BlockTimerStatHandle FTM_SHADOW_RENDER("Render Shadows"); +static LLTrace::BlockTimerStatHandle FTM_SHADOW_ALPHA("Alpha Shadow"); +static LLTrace::BlockTimerStatHandle FTM_SHADOW_SIMPLE("Simple Shadow"); +static LLTrace::BlockTimerStatHandle FTM_SHADOW_GEOM("Shadow Geom"); + +static LLTrace::BlockTimerStatHandle FTM_SHADOW_ALPHA_MASKED("Alpha Masked"); +static LLTrace::BlockTimerStatHandle FTM_SHADOW_ALPHA_BLEND("Alpha Blend"); +static LLTrace::BlockTimerStatHandle FTM_SHADOW_ALPHA_TREE("Alpha Tree"); +static LLTrace::BlockTimerStatHandle FTM_SHADOW_ALPHA_GRASS("Alpha Grass"); +static LLTrace::BlockTimerStatHandle FTM_SHADOW_FULLBRIGHT_ALPHA_MASKED("Fullbright Alpha Masked"); + +void LLPipeline::renderShadow(glh::matrix4f& view, glh::matrix4f& proj, LLCamera& shadow_cam, LLCullResult& result, bool depth_clamp) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; //LL_RECORD_BLOCK_TIME(FTM_SHADOW_RENDER); + LL_PROFILE_GPU_ZONE("renderShadow"); + + LLPipeline::sShadowRender = true; + + // disable occlusion culling during shadow render + U32 saved_occlusion = sUseOcclusion; + sUseOcclusion = 0; + + // List of render pass types that use the prim volume as the shadow, + // ignoring textures. + static const U32 types[] = { + LLRenderPass::PASS_SIMPLE, + LLRenderPass::PASS_FULLBRIGHT, + LLRenderPass::PASS_SHINY, + LLRenderPass::PASS_BUMP, + LLRenderPass::PASS_FULLBRIGHT_SHINY, + LLRenderPass::PASS_MATERIAL, + LLRenderPass::PASS_MATERIAL_ALPHA_EMISSIVE, + LLRenderPass::PASS_SPECMAP, + LLRenderPass::PASS_SPECMAP_EMISSIVE, + LLRenderPass::PASS_NORMMAP, + LLRenderPass::PASS_NORMMAP_EMISSIVE, + LLRenderPass::PASS_NORMSPEC, + LLRenderPass::PASS_NORMSPEC_EMISSIVE + }; + + LLGLEnable cull(GL_CULL_FACE); + + //enable depth clamping if available + LLGLEnable clamp_depth(depth_clamp ? GL_DEPTH_CLAMP : 0); + + LLGLDepthTest depth_test(GL_TRUE, GL_TRUE, GL_LESS); + + updateCull(shadow_cam, result); + + stateSort(shadow_cam, result); + + //generate shadow map + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.pushMatrix(); + gGL.loadMatrix(proj.m); + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + gGL.loadMatrix(view.m); + + stop_glerror(); + gGLLastMatrix = NULL; + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + stop_glerror(); + + struct CompareVertexBuffer + { + bool operator()(const LLDrawInfo* const& lhs, const LLDrawInfo* const& rhs) + { + return lhs->mVertexBuffer > rhs->mVertexBuffer; + } + }; + + + LLVertexBuffer::unbind(); + for (int j = 0; j < 2; ++j) // 0 -- static, 1 -- rigged + { + bool rigged = j == 1; + gDeferredShadowProgram.bind(rigged); + + gGL.diffuseColor4f(1, 1, 1, 1); + + S32 shadow_detail = gSavedSettings.getS32("RenderShadowDetail"); + + // if not using VSM, disable color writes + if (shadow_detail <= 2) + { + gGL.setColorMask(false, false); + } + + LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("shadow simple"); //LL_RECORD_BLOCK_TIME(FTM_SHADOW_SIMPLE); + LL_PROFILE_GPU_ZONE("shadow simple"); + gGL.getTexUnit(0)->disable(); + + for (U32 type : types) + { + renderObjects(type, false, false, rigged); + } + + renderGLTFObjects(LLRenderPass::PASS_GLTF_PBR, false, rigged); + + gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE); + } + + if (LLPipeline::sUseOcclusion > 1) + { // do occlusion culling against non-masked only to take advantage of hierarchical Z + doOcclusion(shadow_cam); + } + + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("shadow geom"); + renderGeomShadow(shadow_cam); + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("shadow alpha"); + LL_PROFILE_GPU_ZONE("shadow alpha"); + const S32 sun_up = LLEnvironment::instance().getIsSunUp() ? 1 : 0; + U32 target_width = LLRenderTarget::sCurResX; + + for (int i = 0; i < 2; ++i) + { + bool rigged = i == 1; + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("shadow alpha masked"); + LL_PROFILE_GPU_ZONE("shadow alpha masked"); + gDeferredShadowAlphaMaskProgram.bind(rigged); + LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up); + LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); + renderMaskedObjects(LLRenderPass::PASS_ALPHA_MASK, true, true, rigged); + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("shadow alpha blend"); + LL_PROFILE_GPU_ZONE("shadow alpha blend"); + renderAlphaObjects(rigged); + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("shadow fullbright alpha masked"); + LL_PROFILE_GPU_ZONE("shadow alpha masked"); + gDeferredShadowFullbrightAlphaMaskProgram.bind(rigged); + LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up); + LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); + renderFullbrightMaskedObjects(LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK, true, true, rigged); + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("shadow alpha grass"); + LL_PROFILE_GPU_ZONE("shadow alpha grass"); + gDeferredTreeShadowProgram.bind(rigged); + LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(ALPHA_BLEND_CUTOFF); + + if (i == 0) + { + renderObjects(LLRenderPass::PASS_GRASS, true); + } + + { + LL_PROFILE_ZONE_NAMED_CATEGORY_PIPELINE("shadow alpha material"); + LL_PROFILE_GPU_ZONE("shadow alpha material"); + renderMaskedObjects(LLRenderPass::PASS_NORMSPEC_MASK, true, false, rigged); + renderMaskedObjects(LLRenderPass::PASS_MATERIAL_ALPHA_MASK, true, false, rigged); + renderMaskedObjects(LLRenderPass::PASS_SPECMAP_MASK, true, false, rigged); + renderMaskedObjects(LLRenderPass::PASS_NORMMAP_MASK, true, false, rigged); + } + } + } + + for (int i = 0; i < 2; ++i) + { + bool rigged = i == 1; + gDeferredShadowGLTFAlphaMaskProgram.bind(rigged); + LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up); + LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); + + gGL.loadMatrix(gGLModelView); + gGLLastMatrix = NULL; + + U32 type = LLRenderPass::PASS_GLTF_PBR_ALPHA_MASK; + + if (rigged) + { + mAlphaMaskPool->pushRiggedGLTFBatches(type + 1); + } + else + { + mAlphaMaskPool->pushGLTFBatches(type); + } + + gGL.loadMatrix(gGLModelView); + gGLLastMatrix = NULL; + } + } + + gDeferredShadowCubeProgram.bind(); + gGLLastMatrix = NULL; + gGL.loadMatrix(gGLModelView); + + gGL.setColorMask(true, true); + + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.popMatrix(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.popMatrix(); + gGLLastMatrix = NULL; + + // reset occlusion culling flag + sUseOcclusion = saved_occlusion; + LLPipeline::sShadowRender = false; +} + +bool LLPipeline::getVisiblePointCloud(LLCamera& camera, LLVector3& min, LLVector3& max, std::vector& fp, LLVector3 light_dir) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + //get point cloud of intersection of frust and min, max + + if (getVisibleExtents(camera, min, max)) + { + return false; + } + + //get set of planes on bounding box + LLPlane bp[] = { + LLPlane(min, LLVector3(-1,0,0)), + LLPlane(min, LLVector3(0,-1,0)), + LLPlane(min, LLVector3(0,0,-1)), + LLPlane(max, LLVector3(1,0,0)), + LLPlane(max, LLVector3(0,1,0)), + LLPlane(max, LLVector3(0,0,1))}; + + //potential points + std::vector pp; + + //add corners of AABB + pp.push_back(LLVector3(min.mV[0], min.mV[1], min.mV[2])); + pp.push_back(LLVector3(max.mV[0], min.mV[1], min.mV[2])); + pp.push_back(LLVector3(min.mV[0], max.mV[1], min.mV[2])); + pp.push_back(LLVector3(max.mV[0], max.mV[1], min.mV[2])); + pp.push_back(LLVector3(min.mV[0], min.mV[1], max.mV[2])); + pp.push_back(LLVector3(max.mV[0], min.mV[1], max.mV[2])); + pp.push_back(LLVector3(min.mV[0], max.mV[1], max.mV[2])); + pp.push_back(LLVector3(max.mV[0], max.mV[1], max.mV[2])); + + //add corners of camera frustum + for (U32 i = 0; i < LLCamera::AGENT_FRUSTRUM_NUM; i++) + { + pp.push_back(camera.mAgentFrustum[i]); + } + + + //bounding box line segments + U32 bs[] = + { + 0,1, + 1,3, + 3,2, + 2,0, + + 4,5, + 5,7, + 7,6, + 6,4, + + 0,4, + 1,5, + 3,7, + 2,6 + }; + + for (U32 i = 0; i < 12; i++) + { //for each line segment in bounding box + for (U32 j = 0; j < LLCamera::AGENT_PLANE_NO_USER_CLIP_NUM; j++) + { //for each plane in camera frustum + const LLPlane& cp = camera.getAgentPlane(j); + const LLVector3& v1 = pp[bs[i*2+0]]; + const LLVector3& v2 = pp[bs[i*2+1]]; + LLVector3 n; + cp.getVector3(n); + + LLVector3 line = v1-v2; + + F32 d1 = line*n; + F32 d2 = -cp.dist(v2); + + F32 t = d2/d1; + + if (t > 0.f && t < 1.f) + { + LLVector3 intersect = v2+line*t; + pp.push_back(intersect); + } + } + } + + //camera frustum line segments + const U32 fs[] = + { + 0,1, + 1,2, + 2,3, + 3,0, + + 4,5, + 5,6, + 6,7, + 7,4, + + 0,4, + 1,5, + 2,6, + 3,7 + }; + + for (U32 i = 0; i < 12; i++) + { + for (U32 j = 0; j < 6; ++j) + { + const LLVector3& v1 = pp[fs[i*2+0]+8]; + const LLVector3& v2 = pp[fs[i*2+1]+8]; + const LLPlane& cp = bp[j]; + LLVector3 n; + cp.getVector3(n); + + LLVector3 line = v1-v2; + + F32 d1 = line*n; + F32 d2 = -cp.dist(v2); + + F32 t = d2/d1; + + if (t > 0.f && t < 1.f) + { + LLVector3 intersect = v2+line*t; + pp.push_back(intersect); + } + } + } + + LLVector3 ext[] = { min-LLVector3(0.05f,0.05f,0.05f), + max+LLVector3(0.05f,0.05f,0.05f) }; + + for (U32 i = 0; i < pp.size(); ++i) + { + bool found = true; + + const F32* p = pp[i].mV; + + for (U32 j = 0; j < 3; ++j) + { + if (p[j] < ext[0].mV[j] || + p[j] > ext[1].mV[j]) + { + found = false; + break; + } + } + + for (U32 j = 0; j < LLCamera::AGENT_PLANE_NO_USER_CLIP_NUM; ++j) + { + const LLPlane& cp = camera.getAgentPlane(j); + F32 dist = cp.dist(pp[i]); + if (dist > 0.05f) //point is above some plane, not contained + { + found = false; + break; + } + } + + if (found) + { + fp.push_back(pp[i]); + } + } + + if (fp.empty()) + { + return false; + } + + return true; +} + +void LLPipeline::renderHighlight(const LLViewerObject* obj, F32 fade) +{ + if (obj && obj->getVolume()) + { + for (LLViewerObject::child_list_t::const_iterator iter = obj->getChildren().begin(); iter != obj->getChildren().end(); ++iter) + { + renderHighlight(*iter, fade); + } + + LLDrawable* drawable = obj->mDrawable; + if (drawable) + { + for (S32 i = 0; i < drawable->getNumFaces(); ++i) + { + LLFace* face = drawable->getFace(i); + if (face) + { + face->renderSelected(LLViewerTexture::sNullImagep, LLColor4(1,1,1,fade)); + } + } + } + } +} + + +LLRenderTarget* LLPipeline::getSunShadowTarget(U32 i) +{ + llassert(i < 4); + return &mRT->shadow[i]; +} + +LLRenderTarget* LLPipeline::getSpotShadowTarget(U32 i) +{ + llassert(i < 2); + return &mSpotShadow[i]; +} + +static LLTrace::BlockTimerStatHandle FTM_GEN_SUN_SHADOW("Gen Sun Shadow"); +static LLTrace::BlockTimerStatHandle FTM_GEN_SUN_SHADOW_SPOT_RENDER("Spot Shadow Render"); + +// helper class for disabling occlusion culling for the current stack frame +class LLDisableOcclusionCulling +{ +public: + S32 mUseOcclusion; + + LLDisableOcclusionCulling() + { + mUseOcclusion = LLPipeline::sUseOcclusion; + LLPipeline::sUseOcclusion = 0; + } + + ~LLDisableOcclusionCulling() + { + LLPipeline::sUseOcclusion = mUseOcclusion; + } +}; + +void LLPipeline::generateSunShadow(LLCamera& camera) +{ + if (!sRenderDeferred || RenderShadowDetail <= 0) + { + return; + } + + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; //LL_RECORD_BLOCK_TIME(FTM_GEN_SUN_SHADOW); + LL_PROFILE_GPU_ZONE("generateSunShadow"); + + LLDisableOcclusionCulling no_occlusion; + + bool skip_avatar_update = false; + if (!isAgentAvatarValid() || gAgentCamera.getCameraAnimating() || gAgentCamera.getCameraMode() != CAMERA_MODE_MOUSELOOK || !LLVOAvatar::sVisibleInFirstPerson) + { + skip_avatar_update = true; + } + + if (!skip_avatar_update) + { + gAgentAvatarp->updateAttachmentVisibility(CAMERA_MODE_THIRD_PERSON); + } + + F64 last_modelview[16]; + F64 last_projection[16]; + for (U32 i = 0; i < 16; i++) + { //store last_modelview of world camera + last_modelview[i] = gGLLastModelView[i]; + last_projection[i] = gGLLastProjection[i]; + } + + pushRenderTypeMask(); + andRenderTypeMask(LLPipeline::RENDER_TYPE_SIMPLE, + LLPipeline::RENDER_TYPE_ALPHA, + LLPipeline::RENDER_TYPE_ALPHA_PRE_WATER, + LLPipeline::RENDER_TYPE_ALPHA_POST_WATER, + LLPipeline::RENDER_TYPE_GRASS, + LLPipeline::RENDER_TYPE_GLTF_PBR, + LLPipeline::RENDER_TYPE_FULLBRIGHT, + LLPipeline::RENDER_TYPE_BUMP, + LLPipeline::RENDER_TYPE_VOLUME, + LLPipeline::RENDER_TYPE_AVATAR, + LLPipeline::RENDER_TYPE_CONTROL_AV, + LLPipeline::RENDER_TYPE_TREE, + LLPipeline::RENDER_TYPE_TERRAIN, + LLPipeline::RENDER_TYPE_WATER, + LLPipeline::RENDER_TYPE_VOIDWATER, + LLPipeline::RENDER_TYPE_PASS_ALPHA, + LLPipeline::RENDER_TYPE_PASS_ALPHA_MASK, + LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_ALPHA_MASK, + LLPipeline::RENDER_TYPE_PASS_GRASS, + LLPipeline::RENDER_TYPE_PASS_SIMPLE, + LLPipeline::RENDER_TYPE_PASS_BUMP, + LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT, + LLPipeline::RENDER_TYPE_PASS_SHINY, + LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_SHINY, + LLPipeline::RENDER_TYPE_PASS_MATERIAL, + LLPipeline::RENDER_TYPE_PASS_MATERIAL_ALPHA, + LLPipeline::RENDER_TYPE_PASS_MATERIAL_ALPHA_MASK, + LLPipeline::RENDER_TYPE_PASS_MATERIAL_ALPHA_EMISSIVE, + LLPipeline::RENDER_TYPE_PASS_SPECMAP, + LLPipeline::RENDER_TYPE_PASS_SPECMAP_BLEND, + LLPipeline::RENDER_TYPE_PASS_SPECMAP_MASK, + LLPipeline::RENDER_TYPE_PASS_SPECMAP_EMISSIVE, + LLPipeline::RENDER_TYPE_PASS_NORMMAP, + LLPipeline::RENDER_TYPE_PASS_NORMMAP_BLEND, + LLPipeline::RENDER_TYPE_PASS_NORMMAP_MASK, + LLPipeline::RENDER_TYPE_PASS_NORMMAP_EMISSIVE, + LLPipeline::RENDER_TYPE_PASS_NORMSPEC, + LLPipeline::RENDER_TYPE_PASS_NORMSPEC_BLEND, + LLPipeline::RENDER_TYPE_PASS_NORMSPEC_MASK, + LLPipeline::RENDER_TYPE_PASS_NORMSPEC_EMISSIVE, + LLPipeline::RENDER_TYPE_PASS_ALPHA_MASK_RIGGED, + LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_ALPHA_MASK_RIGGED, + LLPipeline::RENDER_TYPE_PASS_SIMPLE_RIGGED, + LLPipeline::RENDER_TYPE_PASS_BUMP_RIGGED, + LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_RIGGED, + LLPipeline::RENDER_TYPE_PASS_SHINY_RIGGED, + LLPipeline::RENDER_TYPE_PASS_FULLBRIGHT_SHINY_RIGGED, + LLPipeline::RENDER_TYPE_PASS_MATERIAL_RIGGED, + LLPipeline::RENDER_TYPE_PASS_MATERIAL_ALPHA_RIGGED, + LLPipeline::RENDER_TYPE_PASS_MATERIAL_ALPHA_MASK_RIGGED, + LLPipeline::RENDER_TYPE_PASS_MATERIAL_ALPHA_EMISSIVE_RIGGED, + LLPipeline::RENDER_TYPE_PASS_SPECMAP_RIGGED, + LLPipeline::RENDER_TYPE_PASS_SPECMAP_BLEND_RIGGED, + LLPipeline::RENDER_TYPE_PASS_SPECMAP_MASK_RIGGED, + LLPipeline::RENDER_TYPE_PASS_SPECMAP_EMISSIVE_RIGGED, + LLPipeline::RENDER_TYPE_PASS_NORMMAP_RIGGED, + LLPipeline::RENDER_TYPE_PASS_NORMMAP_BLEND_RIGGED, + LLPipeline::RENDER_TYPE_PASS_NORMMAP_MASK_RIGGED, + LLPipeline::RENDER_TYPE_PASS_NORMMAP_EMISSIVE_RIGGED, + LLPipeline::RENDER_TYPE_PASS_NORMSPEC_RIGGED, + LLPipeline::RENDER_TYPE_PASS_NORMSPEC_BLEND_RIGGED, + LLPipeline::RENDER_TYPE_PASS_NORMSPEC_MASK_RIGGED, + LLPipeline::RENDER_TYPE_PASS_NORMSPEC_EMISSIVE_RIGGED, + LLPipeline::RENDER_TYPE_PASS_GLTF_PBR, + LLPipeline::RENDER_TYPE_PASS_GLTF_PBR_RIGGED, + LLPipeline::RENDER_TYPE_PASS_GLTF_PBR_ALPHA_MASK, + LLPipeline::RENDER_TYPE_PASS_GLTF_PBR_ALPHA_MASK_RIGGED, + END_RENDER_TYPES); + + gGL.setColorMask(false, false); + + LLEnvironment& environment = LLEnvironment::instance(); + + //get sun view matrix + + //store current projection/modelview matrix + glh::matrix4f saved_proj = get_current_projection(); + glh::matrix4f saved_view = get_current_modelview(); + glh::matrix4f inv_view = saved_view.inverse(); + + glh::matrix4f view[6]; + glh::matrix4f proj[6]; + + LLVector3 caster_dir(environment.getIsSunUp() ? mSunDir : mMoonDir); + + //put together a universal "near clip" plane for shadow frusta + LLPlane shadow_near_clip; + { + LLVector3 p = camera.getOrigin(); // gAgent.getPositionAgent(); + p += caster_dir * RenderFarClip*2.f; + shadow_near_clip.setVec(p, caster_dir); + } + + LLVector3 lightDir = -caster_dir; + lightDir.normVec(); + + glh::vec3f light_dir(lightDir.mV); + + //create light space camera matrix + + LLVector3 at = lightDir; + + LLVector3 up = camera.getAtAxis(); + + if (fabsf(up*lightDir) > 0.75f) + { + up = camera.getUpAxis(); + } + + up.normVec(); + at.normVec(); + + + LLCamera main_camera = camera; + + F32 near_clip = 0.f; + { + //get visible point cloud + std::vector fp; + + main_camera.calcAgentFrustumPlanes(main_camera.mAgentFrustum); + + LLVector3 min,max; + getVisiblePointCloud(main_camera,min,max,fp); + + if (fp.empty()) + { + if (!hasRenderDebugMask(RENDER_DEBUG_SHADOW_FRUSTA) && !gCubeSnapshot) + { + mShadowCamera[0] = main_camera; + mShadowExtents[0][0] = min; + mShadowExtents[0][1] = max; + + mShadowFrustPoints[0].clear(); + mShadowFrustPoints[1].clear(); + mShadowFrustPoints[2].clear(); + mShadowFrustPoints[3].clear(); + } + popRenderTypeMask(); + + if (!skip_avatar_update) + { + gAgentAvatarp->updateAttachmentVisibility(gAgentCamera.getCameraMode()); + } + + return; + } + + //get good split distances for frustum + for (U32 i = 0; i < fp.size(); ++i) + { + glh::vec3f v(fp[i].mV); + saved_view.mult_matrix_vec(v); + fp[i].setVec(v.v); + } + + min = fp[0]; + max = fp[0]; + + //get camera space bounding box + for (U32 i = 1; i < fp.size(); ++i) + { + update_min_max(min, max, fp[i]); + } + + near_clip = llclamp(-max.mV[2], 0.01f, 4.0f); + F32 far_clip = llclamp(-min.mV[2]*2.f, 16.0f, 512.0f); + + //far_clip = llmin(far_clip, 128.f); + far_clip = llmin(far_clip, camera.getFar()); + + F32 range = far_clip-near_clip; + + LLVector3 split_exp = RenderShadowSplitExponent; + + F32 da = 1.f-llmax( fabsf(lightDir*up), fabsf(lightDir*camera.getLeftAxis()) ); + + da = powf(da, split_exp.mV[2]); + + F32 sxp = split_exp.mV[1] + (split_exp.mV[0]-split_exp.mV[1])*da; + + for (U32 i = 0; i < 4; ++i) + { + F32 x = (F32)(i+1)/4.f; + x = powf(x, sxp); + mSunClipPlanes.mV[i] = near_clip+range*x; + } + + mSunClipPlanes.mV[0] *= 1.25f; //bump back first split for transition padding + } + + if (gCubeSnapshot) + { // stretch clip planes for reflection probe renders to reduce number of shadow passes + mSunClipPlanes.mV[1] = mSunClipPlanes.mV[2]; + mSunClipPlanes.mV[2] = mSunClipPlanes.mV[3]; + mSunClipPlanes.mV[3] *= 1.5f; + } + + + // convenience array of 4 near clip plane distances + F32 dist[] = { near_clip, mSunClipPlanes.mV[0], mSunClipPlanes.mV[1], mSunClipPlanes.mV[2], mSunClipPlanes.mV[3] }; + + if (mSunDiffuse == LLColor4::black) + { //sun diffuse is totally black shadows don't matter + skipRenderingShadows(); + } + else + { + for (S32 j = 0; j < (gCubeSnapshot ? 2 : 4); j++) + { + if (!hasRenderDebugMask(RENDER_DEBUG_SHADOW_FRUSTA) && !gCubeSnapshot) + { + mShadowFrustPoints[j].clear(); + } + + LLViewerCamera::sCurCameraID = (LLViewerCamera::eCameraID)(LLViewerCamera::CAMERA_SUN_SHADOW0+j); + + //restore render matrices + set_current_modelview(saved_view); + set_current_projection(saved_proj); + + LLVector3 eye = camera.getOrigin(); + llassert(eye.isFinite()); + + //camera used for shadow cull/render + LLCamera shadow_cam; + + //create world space camera frustum for this split + shadow_cam = camera; + shadow_cam.setFar(16.f); + + LLViewerCamera::updateFrustumPlanes(shadow_cam, false, false, true); + + LLVector3* frust = shadow_cam.mAgentFrustum; + + LLVector3 pn = shadow_cam.getAtAxis(); + + LLVector3 min, max; + + //construct 8 corners of split frustum section + for (U32 i = 0; i < 4; i++) + { + LLVector3 delta = frust[i+4]-eye; + delta += (frust[i+4]-frust[(i+2)%4+4])*0.05f; + delta.normVec(); + F32 dp = delta*pn; + frust[i] = eye + (delta*dist[j]*0.75f)/dp; + frust[i+4] = eye + (delta*dist[j+1]*1.25f)/dp; + } + + shadow_cam.calcAgentFrustumPlanes(frust); + shadow_cam.mFrustumCornerDist = 0.f; + + if (!gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_SHADOW_FRUSTA) && !gCubeSnapshot) + { + mShadowCamera[j] = shadow_cam; + } + + std::vector fp; + + if (!gPipeline.getVisiblePointCloud(shadow_cam, min, max, fp, lightDir) + || j > RenderShadowSplits) + { + //no possible shadow receivers + if (!gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_SHADOW_FRUSTA) && !gCubeSnapshot) + { + mShadowExtents[j][0] = LLVector3(); + mShadowExtents[j][1] = LLVector3(); + mShadowCamera[j+4] = shadow_cam; + } + + mRT->shadow[j].bindTarget(); + { + LLGLDepthTest depth(GL_TRUE); + mRT->shadow[j].clear(); + } + mRT->shadow[j].flush(); + + mShadowError.mV[j] = 0.f; + mShadowFOV.mV[j] = 0.f; + + continue; + } + + if (!gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_SHADOW_FRUSTA) && !gCubeSnapshot) + { + mShadowExtents[j][0] = min; + mShadowExtents[j][1] = max; + mShadowFrustPoints[j] = fp; + } + + + //find a good origin for shadow projection + LLVector3 origin; + + //get a temporary view projection + view[j] = look(camera.getOrigin(), lightDir, -up); + + std::vector wpf; + + for (U32 i = 0; i < fp.size(); i++) + { + glh::vec3f p = glh::vec3f(fp[i].mV); + view[j].mult_matrix_vec(p); + wpf.push_back(LLVector3(p.v)); + } + + min = wpf[0]; + max = wpf[0]; + + for (U32 i = 0; i < fp.size(); ++i) + { //get AABB in camera space + update_min_max(min, max, wpf[i]); + } + + // Construct a perspective transform with perspective along y-axis that contains + // points in wpf + //Known: + // - far clip plane + // - near clip plane + // - points in frustum + //Find: + // - origin + + //get some "interesting" points of reference + LLVector3 center = (min+max)*0.5f; + LLVector3 size = (max-min)*0.5f; + LLVector3 near_center = center; + near_center.mV[1] += size.mV[1]*2.f; + + + //put all points in wpf in quadrant 0, reletive to center of min/max + //get the best fit line using least squares + F32 bfm = 0.f; + F32 bfb = 0.f; + + for (U32 i = 0; i < wpf.size(); ++i) + { + wpf[i] -= center; + wpf[i].mV[0] = fabsf(wpf[i].mV[0]); + wpf[i].mV[2] = fabsf(wpf[i].mV[2]); + } + + if (!wpf.empty()) + { + F32 sx = 0.f; + F32 sx2 = 0.f; + F32 sy = 0.f; + F32 sxy = 0.f; + + for (U32 i = 0; i < wpf.size(); ++i) + { + sx += wpf[i].mV[0]; + sx2 += wpf[i].mV[0]*wpf[i].mV[0]; + sy += wpf[i].mV[1]; + sxy += wpf[i].mV[0]*wpf[i].mV[1]; + } + + bfm = (sy*sx-wpf.size()*sxy)/(sx*sx-wpf.size()*sx2); + bfb = (sx*sxy-sy*sx2)/(sx*sx-bfm*sx2); + } + + { + // best fit line is y=bfm*x+bfb + + //find point that is furthest to the right of line + F32 off_x = -1.f; + LLVector3 lp; + + for (U32 i = 0; i < wpf.size(); ++i) + { + //y = bfm*x+bfb + //x = (y-bfb)/bfm + F32 lx = (wpf[i].mV[1]-bfb)/bfm; + + lx = wpf[i].mV[0]-lx; + + if (off_x < lx) + { + off_x = lx; + lp = wpf[i]; + } + } + + //get line with slope bfm through lp + // bfb = y-bfm*x + bfb = lp.mV[1]-bfm*lp.mV[0]; + + //calculate error + mShadowError.mV[j] = 0.f; + + for (U32 i = 0; i < wpf.size(); ++i) + { + F32 lx = (wpf[i].mV[1]-bfb)/bfm; + mShadowError.mV[j] += fabsf(wpf[i].mV[0]-lx); + } + + mShadowError.mV[j] /= wpf.size(); + mShadowError.mV[j] /= size.mV[0]; + + if (mShadowError.mV[j] > RenderShadowErrorCutoff) + { //just use ortho projection + mShadowFOV.mV[j] = -1.f; + origin.clearVec(); + proj[j] = gl_ortho(min.mV[0], max.mV[0], + min.mV[1], max.mV[1], + -max.mV[2], -min.mV[2]); + } + else + { + //origin is where line x = 0; + origin.setVec(0,bfb,0); + + F32 fovz = 1.f; + F32 fovx = 1.f; + + LLVector3 zp; + LLVector3 xp; + + for (U32 i = 0; i < wpf.size(); ++i) + { + LLVector3 atz = wpf[i]-origin; + atz.mV[0] = 0.f; + atz.normVec(); + if (fovz > -atz.mV[1]) + { + zp = wpf[i]; + fovz = -atz.mV[1]; + } + + LLVector3 atx = wpf[i]-origin; + atx.mV[2] = 0.f; + atx.normVec(); + if (fovx > -atx.mV[1]) + { + fovx = -atx.mV[1]; + xp = wpf[i]; + } + } + + fovx = acos(fovx); + fovz = acos(fovz); + + F32 cutoff = llmin((F32) RenderShadowFOVCutoff, 1.4f); + + mShadowFOV.mV[j] = fovx; + + if (fovx < cutoff && fovz > cutoff) + { + //x is a good fit, but z is too big, move away from zp enough so that fovz matches cutoff + F32 d = zp.mV[2]/tan(cutoff); + F32 ny = zp.mV[1] + fabsf(d); + + origin.mV[1] = ny; + + fovz = 1.f; + fovx = 1.f; + + for (U32 i = 0; i < wpf.size(); ++i) + { + LLVector3 atz = wpf[i]-origin; + atz.mV[0] = 0.f; + atz.normVec(); + fovz = llmin(fovz, -atz.mV[1]); + + LLVector3 atx = wpf[i]-origin; + atx.mV[2] = 0.f; + atx.normVec(); + fovx = llmin(fovx, -atx.mV[1]); + } + + fovx = acos(fovx); + fovz = acos(fovz); + + mShadowFOV.mV[j] = cutoff; + } + + + origin += center; + + F32 ynear = -(max.mV[1]-origin.mV[1]); + F32 yfar = -(min.mV[1]-origin.mV[1]); + + if (ynear < 0.1f) //keep a sensible near clip plane + { + F32 diff = 0.1f-ynear; + origin.mV[1] += diff; + ynear += diff; + yfar += diff; + } + + if (fovx > cutoff) + { //just use ortho projection + origin.clearVec(); + mShadowError.mV[j] = -1.f; + proj[j] = gl_ortho(min.mV[0], max.mV[0], + min.mV[1], max.mV[1], + -max.mV[2], -min.mV[2]); + } + else + { + //get perspective projection + view[j] = view[j].inverse(); + //llassert(origin.isFinite()); + + glh::vec3f origin_agent(origin.mV); + + //translate view to origin + view[j].mult_matrix_vec(origin_agent); + + eye = LLVector3(origin_agent.v); + //llassert(eye.isFinite()); + if (!hasRenderDebugMask(LLPipeline::RENDER_DEBUG_SHADOW_FRUSTA) && !gCubeSnapshot) + { + mShadowFrustOrigin[j] = eye; + } + + view[j] = look(LLVector3(origin_agent.v), lightDir, -up); + + F32 fx = 1.f/tanf(fovx); + F32 fz = 1.f/tanf(fovz); + + proj[j] = glh::matrix4f(-fx, 0, 0, 0, + 0, (yfar+ynear)/(ynear-yfar), 0, (2.f*yfar*ynear)/(ynear-yfar), + 0, 0, -fz, 0, + 0, -1.f, 0, 0); + } + } + } + + //shadow_cam.setFar(128.f); + shadow_cam.setOriginAndLookAt(eye, up, center); + + shadow_cam.setOrigin(0,0,0); + + set_current_modelview(view[j]); + set_current_projection(proj[j]); + + LLViewerCamera::updateFrustumPlanes(shadow_cam, false, false, true); + + //shadow_cam.ignoreAgentFrustumPlane(LLCamera::AGENT_PLANE_NEAR); + shadow_cam.getAgentPlane(LLCamera::AGENT_PLANE_NEAR).set(shadow_near_clip); + + //translate and scale to from [-1, 1] to [0, 1] + glh::matrix4f trans(0.5f, 0.f, 0.f, 0.5f, + 0.f, 0.5f, 0.f, 0.5f, + 0.f, 0.f, 0.5f, 0.5f, + 0.f, 0.f, 0.f, 1.f); + + set_current_modelview(view[j]); + set_current_projection(proj[j]); + + for (U32 i = 0; i < 16; i++) + { + gGLLastModelView[i] = mShadowModelview[j].m[i]; + gGLLastProjection[i] = mShadowProjection[j].m[i]; + } + + mShadowModelview[j] = view[j]; + mShadowProjection[j] = proj[j]; + mSunShadowMatrix[j] = trans*proj[j]*view[j]*inv_view; + + stop_glerror(); + + mRT->shadow[j].bindTarget(); + mRT->shadow[j].getViewport(gGLViewport); + mRT->shadow[j].clear(); + + { + static LLCullResult result[4]; + renderShadow(view[j], proj[j], shadow_cam, result[j], true); + } + + mRT->shadow[j].flush(); + + if (!gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_SHADOW_FRUSTA) && !gCubeSnapshot) + { + mShadowCamera[j+4] = shadow_cam; + } + } + } + + //hack to disable projector shadows + bool gen_shadow = RenderShadowDetail > 1; + + if (gen_shadow) + { + if (!gCubeSnapshot) //skip updating spot shadow maps during cubemap updates + { + LLTrace::CountStatHandle<>* velocity_stat = LLViewerCamera::getVelocityStat(); + F32 fade_amt = gFrameIntervalSeconds.value() + * llmax(LLTrace::get_frame_recording().getLastRecording().getSum(*velocity_stat) / LLTrace::get_frame_recording().getLastRecording().getDuration().value(), 1.0); + + // should never happen + llassert(mTargetShadowSpotLight[0] != mTargetShadowSpotLight[1] || mTargetShadowSpotLight[0].isNull()); + + //update shadow targets + for (U32 i = 0; i < 2; i++) + { //for each current shadow + LLViewerCamera::sCurCameraID = (LLViewerCamera::eCameraID)(LLViewerCamera::CAMERA_SPOT_SHADOW0 + i); + + if (mShadowSpotLight[i].notNull() && + (mShadowSpotLight[i] == mTargetShadowSpotLight[0] || + mShadowSpotLight[i] == mTargetShadowSpotLight[1])) + { //keep this spotlight + mSpotLightFade[i] = llmin(mSpotLightFade[i] + fade_amt, 1.f); + } + else + { //fade out this light + mSpotLightFade[i] = llmax(mSpotLightFade[i] - fade_amt, 0.f); + + if (mSpotLightFade[i] == 0.f || mShadowSpotLight[i].isNull()) + { //faded out, grab one of the pending spots (whichever one isn't already taken) + if (mTargetShadowSpotLight[0] != mShadowSpotLight[(i + 1) % 2]) + { + mShadowSpotLight[i] = mTargetShadowSpotLight[0]; + } + else + { + mShadowSpotLight[i] = mTargetShadowSpotLight[1]; + } + } + } + } + } + + // this should never happen + llassert(mShadowSpotLight[0] != mShadowSpotLight[1] || mShadowSpotLight[0].isNull()); + + for (S32 i = 0; i < 2; i++) + { + set_current_modelview(saved_view); + set_current_projection(saved_proj); + + if (mShadowSpotLight[i].isNull()) + { + continue; + } + + LLVOVolume* volume = mShadowSpotLight[i]->getVOVolume(); + + if (!volume) + { + mShadowSpotLight[i] = NULL; + continue; + } + + LLDrawable* drawable = mShadowSpotLight[i]; + + LLVector3 params = volume->getSpotLightParams(); + F32 fov = params.mV[0]; + + //get agent->light space matrix (modelview) + LLVector3 center = drawable->getPositionAgent(); + LLQuaternion quat = volume->getRenderRotation(); + + //get near clip plane + LLVector3 scale = volume->getScale(); + LLVector3 at_axis(0, 0, -scale.mV[2] * 0.5f); + at_axis *= quat; + + LLVector3 np = center + at_axis; + at_axis.normVec(); + + //get origin that has given fov for plane np, at_axis, and given scale + F32 dist = (scale.mV[1] * 0.5f) / tanf(fov * 0.5f); + + LLVector3 origin = np - at_axis * dist; + + LLMatrix4 mat(quat, LLVector4(origin, 1.f)); + + view[i + 4] = glh::matrix4f((F32*)mat.mMatrix); + + view[i + 4] = view[i + 4].inverse(); + + //get perspective matrix + F32 near_clip = dist + 0.01f; + F32 width = scale.mV[VX]; + F32 height = scale.mV[VY]; + F32 far_clip = dist + volume->getLightRadius() * 1.5f; + + F32 fovy = fov * RAD_TO_DEG; + F32 aspect = width / height; + + proj[i + 4] = gl_perspective(fovy, aspect, near_clip, far_clip); + + //translate and scale to from [-1, 1] to [0, 1] + glh::matrix4f trans(0.5f, 0.f, 0.f, 0.5f, + 0.f, 0.5f, 0.f, 0.5f, + 0.f, 0.f, 0.5f, 0.5f, + 0.f, 0.f, 0.f, 1.f); + + set_current_modelview(view[i + 4]); + set_current_projection(proj[i + 4]); + + mSunShadowMatrix[i + 4] = trans * proj[i + 4] * view[i + 4] * inv_view; + + for (U32 j = 0; j < 16; j++) + { + gGLLastModelView[j] = mShadowModelview[i + 4].m[j]; + gGLLastProjection[j] = mShadowProjection[i + 4].m[j]; + } + + mShadowModelview[i + 4] = view[i + 4]; + mShadowProjection[i + 4] = proj[i + 4]; + + if (!gCubeSnapshot) //skip updating spot shadow maps during cubemap updates + { + LLCamera shadow_cam = camera; + shadow_cam.setFar(far_clip); + shadow_cam.setOrigin(origin); + + LLViewerCamera::updateFrustumPlanes(shadow_cam, false, false, true); + + // + + mSpotShadow[i].bindTarget(); + mSpotShadow[i].getViewport(gGLViewport); + mSpotShadow[i].clear(); + + static LLCullResult result[2]; + + LLViewerCamera::sCurCameraID = (LLViewerCamera::eCameraID)(LLViewerCamera::CAMERA_SPOT_SHADOW0 + i); + + RenderSpotLight = drawable; + + renderShadow(view[i + 4], proj[i + 4], shadow_cam, result[i], false); + + RenderSpotLight = nullptr; + + mSpotShadow[i].flush(); + } + } + } + else + { //no spotlight shadows + mShadowSpotLight[0] = mShadowSpotLight[1] = NULL; + } + + + if (!CameraOffset) + { + set_current_modelview(saved_view); + set_current_projection(saved_proj); + } + else + { + set_current_modelview(view[1]); + set_current_projection(proj[1]); + gGL.loadMatrix(view[1].m); + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.loadMatrix(proj[1].m); + gGL.matrixMode(LLRender::MM_MODELVIEW); + } + gGL.setColorMask(true, true); + + for (U32 i = 0; i < 16; i++) + { + gGLLastModelView[i] = last_modelview[i]; + gGLLastProjection[i] = last_projection[i]; + } + + popRenderTypeMask(); + + if (!skip_avatar_update) + { + gAgentAvatarp->updateAttachmentVisibility(gAgentCamera.getCameraMode()); + } +} + +void LLPipeline::renderGroups(LLRenderPass* pass, U32 type, bool texture) +{ + for (LLCullResult::sg_iterator i = sCull->beginVisibleGroups(); i != sCull->endVisibleGroups(); ++i) + { + LLSpatialGroup* group = *i; + if (!group->isDead() && + (!sUseOcclusion || !group->isOcclusionState(LLSpatialGroup::OCCLUDED)) && + gPipeline.hasRenderType(group->getSpatialPartition()->mDrawableType) && + group->mDrawMap.find(type) != group->mDrawMap.end()) + { + pass->renderGroup(group,type,texture); + } + } +} + +void LLPipeline::renderRiggedGroups(LLRenderPass* pass, U32 type, bool texture) +{ + for (LLCullResult::sg_iterator i = sCull->beginVisibleGroups(); i != sCull->endVisibleGroups(); ++i) + { + LLSpatialGroup* group = *i; + if (!group->isDead() && + (!sUseOcclusion || !group->isOcclusionState(LLSpatialGroup::OCCLUDED)) && + gPipeline.hasRenderType(group->getSpatialPartition()->mDrawableType) && + group->mDrawMap.find(type) != group->mDrawMap.end()) + { + pass->renderRiggedGroup(group, type, texture); + } + } +} + +void LLPipeline::profileAvatar(LLVOAvatar* avatar, bool profile_attachments) +{ + if (gGLManager.mGLVersion < 3.25f) + { // profiling requires GL 3.3 or later + return; + } + + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + + // don't continue to profile an avatar that is known to be too slow + llassert(!avatar->isTooSlow()); + + LLGLSLShader* cur_shader = LLGLSLShader::sCurBoundShaderPtr; + + mRT->deferredScreen.bindTarget(); + mRT->deferredScreen.clear(); + + if (!profile_attachments) + { + // profile entire avatar all at once and readback asynchronously + avatar->placeProfileQuery(); + + LLTimer cpu_timer; + + generateImpostor(avatar, false, true); + + avatar->mCPURenderTime = (F32)cpu_timer.getElapsedTimeF32() * 1000.f; + + avatar->readProfileQuery(5); // allow up to 5 frames of latency + } + else + { + // profile attachments one at a time + LLVOAvatar::attachment_map_t::iterator iter; + LLVOAvatar::attachment_map_t::iterator begin = avatar->mAttachmentPoints.begin(); + LLVOAvatar::attachment_map_t::iterator end = avatar->mAttachmentPoints.end(); + + for (iter = begin; + iter != end; + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + LLViewerObject* attached_object = attachment_iter->get(); + if (attached_object) + { + // use gDebugProgram to do the GPU queries + gDebugProgram.clearStats(); + gDebugProgram.placeProfileQuery(true); + + generateImpostor(avatar, false, true, attached_object); + gDebugProgram.readProfileQuery(true, true); + + attached_object->mGPURenderTime = gDebugProgram.mTimeElapsed / 1000000.f; + } + } + } + } + + mRT->deferredScreen.flush(); + + if (cur_shader) + { + cur_shader->bind(); + } +} + +void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar, bool for_profile, LLViewerObject* specific_attachment) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; + LL_PROFILE_GPU_ZONE("generateImpostor"); + LLGLState::checkStates(); + + static LLCullResult result; + result.clear(); + grabReferences(result); + + if (!avatar || !avatar->mDrawable) + { + LL_WARNS_ONCE("AvatarRenderPipeline") << "Avatar is " << (avatar ? "not drawable" : "null") << LL_ENDL; + return; + } + LL_DEBUGS_ONCE("AvatarRenderPipeline") << "Avatar " << avatar->getID() << " is drawable" << LL_ENDL; + + assertInitialized(); + + // previews can't be muted or impostered + bool visually_muted = !for_profile && !preview_avatar && avatar->isVisuallyMuted(); + LL_DEBUGS_ONCE("AvatarRenderPipeline") << "Avatar " << avatar->getID() + << " is " << ( visually_muted ? "" : "not ") << "visually muted" + << LL_ENDL; + bool too_complex = !for_profile && !preview_avatar && avatar->isTooComplex(); + LL_DEBUGS_ONCE("AvatarRenderPipeline") << "Avatar " << avatar->getID() + << " is " << ( too_complex ? "" : "not ") << "too complex" + << LL_ENDL; + + pushRenderTypeMask(); + + if (visually_muted || too_complex) + { + // only show jelly doll geometry + andRenderTypeMask(LLPipeline::RENDER_TYPE_AVATAR, + LLPipeline::RENDER_TYPE_CONTROL_AV, + END_RENDER_TYPES); + } + else + { + //hide world geometry + clearRenderTypeMask( + RENDER_TYPE_SKY, + RENDER_TYPE_WL_SKY, + RENDER_TYPE_TERRAIN, + RENDER_TYPE_GRASS, + RENDER_TYPE_CONTROL_AV, // Animesh + RENDER_TYPE_TREE, + RENDER_TYPE_VOIDWATER, + RENDER_TYPE_WATER, + RENDER_TYPE_ALPHA_PRE_WATER, + RENDER_TYPE_PASS_GRASS, + RENDER_TYPE_HUD, + RENDER_TYPE_PARTICLES, + RENDER_TYPE_CLOUDS, + RENDER_TYPE_HUD_PARTICLES, + END_RENDER_TYPES + ); + } + + if (specific_attachment && specific_attachment->isHUDAttachment()) + { //enable HUD rendering + setRenderTypeMask(RENDER_TYPE_HUD, END_RENDER_TYPES); + } + + S32 occlusion = sUseOcclusion; + sUseOcclusion = 0; + + sReflectionRender = ! sRenderDeferred; + + sShadowRender = true; + sImpostorRender = true; + + LLViewerCamera* viewer_camera = LLViewerCamera::getInstance(); + + { + markVisible(avatar->mDrawable, *viewer_camera); + + if (preview_avatar) + { + // Only show rigged attachments for preview + // For the sake of performance and so that static + // objects won't obstruct previewing changes + LLVOAvatar::attachment_map_t::iterator iter; + for (iter = avatar->mAttachmentPoints.begin(); + iter != avatar->mAttachmentPoints.end(); + ++iter) + { + LLViewerJointAttachment *attachment = iter->second; + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + LLViewerObject* attached_object = attachment_iter->get(); + if (attached_object) + { + if (attached_object->isRiggedMesh()) + { + markVisible(attached_object->mDrawable->getSpatialBridge(), *viewer_camera); + } + else + { + // sometimes object is a linkset and rigged mesh is a child + LLViewerObject::const_child_list_t& child_list = attached_object->getChildren(); + for (LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); iter++) + { + LLViewerObject* child = *iter; + if (child->isRiggedMesh()) + { + markVisible(attached_object->mDrawable->getSpatialBridge(), *viewer_camera); + break; + } + } + } + } + } + } + } + else + { + if (specific_attachment) + { + markVisible(specific_attachment->mDrawable->getSpatialBridge(), *viewer_camera); + } + else + { + LLVOAvatar::attachment_map_t::iterator iter; + LLVOAvatar::attachment_map_t::iterator begin = avatar->mAttachmentPoints.begin(); + LLVOAvatar::attachment_map_t::iterator end = avatar->mAttachmentPoints.end(); + + for (iter = begin; + iter != end; + ++iter) + { + LLViewerJointAttachment* attachment = iter->second; + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); + attachment_iter != attachment->mAttachedObjects.end(); + ++attachment_iter) + { + LLViewerObject* attached_object = attachment_iter->get(); + if (attached_object) + { + markVisible(attached_object->mDrawable->getSpatialBridge(), *viewer_camera); + } + } + } + } + } + } + + stateSort(*LLViewerCamera::getInstance(), result); + + LLCamera camera = *viewer_camera; + LLVector2 tdim; + U32 resY = 0; + U32 resX = 0; + + if (!preview_avatar) + { + const LLVector4a* ext = avatar->mDrawable->getSpatialExtents(); + LLVector3 pos(avatar->getRenderPosition()+avatar->getImpostorOffset()); + + camera.lookAt(viewer_camera->getOrigin(), pos, viewer_camera->getUpAxis()); + + LLVector4a half_height; + half_height.setSub(ext[1], ext[0]); + half_height.mul(0.5f); + + LLVector4a left; + left.load3(camera.getLeftAxis().mV); + left.mul(left); + llassert(left.dot3(left).getF32() > F_APPROXIMATELY_ZERO); + left.normalize3fast(); + + LLVector4a up; + up.load3(camera.getUpAxis().mV); + up.mul(up); + llassert(up.dot3(up).getF32() > F_APPROXIMATELY_ZERO); + up.normalize3fast(); + + tdim.mV[0] = fabsf(half_height.dot3(left).getF32()); + tdim.mV[1] = fabsf(half_height.dot3(up).getF32()); + + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.pushMatrix(); + + F32 distance = (pos-camera.getOrigin()).length(); + F32 fov = atanf(tdim.mV[1]/distance)*2.f*RAD_TO_DEG; + F32 aspect = tdim.mV[0]/tdim.mV[1]; + glh::matrix4f persp = gl_perspective(fov, aspect, 1.f, 256.f); + set_current_projection(persp); + gGL.loadMatrix(persp.m); + + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.pushMatrix(); + glh::matrix4f mat; + camera.getOpenGLTransform(mat.m); + + mat = glh::matrix4f((GLfloat*) OGL_TO_CFR_ROTATION) * mat; + + gGL.loadMatrix(mat.m); + set_current_modelview(mat); + + glClearColor(0.0f,0.0f,0.0f,0.0f); + gGL.setColorMask(true, true); + + // get the number of pixels per angle + F32 pa = gViewerWindow->getWindowHeightRaw() / (RAD_TO_DEG * viewer_camera->getView()); + + //get resolution based on angle width and height of impostor (double desired resolution to prevent aliasing) + resY = llmin(nhpo2((U32) (fov*pa)), (U32) 512); + resX = llmin(nhpo2((U32) (atanf(tdim.mV[0]/distance)*2.f*RAD_TO_DEG*pa)), (U32) 512); + + if (!for_profile) + { + if (!avatar->mImpostor.isComplete()) + { + avatar->mImpostor.allocate(resX, resY, GL_RGBA, true); + + if (LLPipeline::sRenderDeferred) + { + addDeferredAttachments(avatar->mImpostor, true); + } + + gGL.getTexUnit(0)->bind(&avatar->mImpostor); + gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_POINT); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + } + else if (resX != avatar->mImpostor.getWidth() || resY != avatar->mImpostor.getHeight()) + { + avatar->mImpostor.resize(resX, resY); + } + + avatar->mImpostor.bindTarget(); + } + } + + F32 old_alpha = LLDrawPoolAvatar::sMinimumAlpha; + + if (visually_muted || too_complex) + { //disable alpha masking for muted avatars (get whole skin silhouette) + LLDrawPoolAvatar::sMinimumAlpha = 0.f; + } + + if (preview_avatar || for_profile) + { + // previews and profiles don't care about imposters + renderGeomDeferred(camera); + renderGeomPostDeferred(camera); + } + else + { + avatar->mImpostor.clear(); + renderGeomDeferred(camera); + + renderGeomPostDeferred(camera); + + // Shameless hack time: render it all again, + // this time writing the depth + // values we need to generate the alpha mask below + // while preserving the alpha-sorted color rendering + // from the previous pass + // + sImpostorRenderAlphaDepthPass = true; + // depth-only here... + // + gGL.setColorMask(false,false); + renderGeomPostDeferred(camera); + + sImpostorRenderAlphaDepthPass = false; + + } + + LLDrawPoolAvatar::sMinimumAlpha = old_alpha; + + if (!for_profile) + { //create alpha mask based on depth buffer (grey out if muted) + if (LLPipeline::sRenderDeferred) + { + GLuint buff = GL_COLOR_ATTACHMENT0; + glDrawBuffers(1, &buff); + } + + LLGLDisable blend(GL_BLEND); + + if (visually_muted || too_complex) + { + gGL.setColorMask(true, true); + } + else + { + gGL.setColorMask(false, true); + } + + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); + + LLGLDepthTest depth(GL_TRUE, GL_FALSE, GL_GREATER); + + gGL.flush(); + + gGL.pushMatrix(); + gGL.loadIdentity(); + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.pushMatrix(); + gGL.loadIdentity(); + + static const F32 clip_plane = 0.99999f; + + gDebugProgram.bind(); + + if (visually_muted) + { // Visually muted avatar + LLColor4 muted_color(avatar->getMutedAVColor()); + LL_DEBUGS_ONCE("AvatarRenderPipeline") << "Avatar " << avatar->getID() << " MUTED set solid color " << muted_color << LL_ENDL; + gGL.diffuseColor4fv( muted_color.mV ); + } + else if (!preview_avatar) + { //grey muted avatar + LL_DEBUGS_ONCE("AvatarRenderPipeline") << "Avatar " << avatar->getID() << " MUTED set grey" << LL_ENDL; + gGL.diffuseColor4fv(LLColor4::pink.mV ); + } + + gGL.begin(LLRender::QUADS); + gGL.vertex3f(-1, -1, clip_plane); + gGL.vertex3f(1, -1, clip_plane); + gGL.vertex3f(1, 1, clip_plane); + gGL.vertex3f(-1, 1, clip_plane); + gGL.end(); + gGL.flush(); + + gDebugProgram.unbind(); + + gGL.popMatrix(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.popMatrix(); + } + + if (!preview_avatar && !for_profile) + { + avatar->mImpostor.flush(); + avatar->setImpostorDim(tdim); + } + + sUseOcclusion = occlusion; + sReflectionRender = false; + sImpostorRender = false; + sShadowRender = false; + popRenderTypeMask(); + + gGL.matrixMode(LLRender::MM_PROJECTION); + gGL.popMatrix(); + gGL.matrixMode(LLRender::MM_MODELVIEW); + gGL.popMatrix(); + + if (!preview_avatar && !for_profile) + { + avatar->mNeedsImpostorUpdate = false; + avatar->cacheImpostorValues(); + avatar->mLastImpostorUpdateFrameTime = gFrameTimeSeconds; + } + + LLVertexBuffer::unbind(); + LLGLState::checkStates(); +} + +bool LLPipeline::hasRenderBatches(const U32 type) const +{ + return sCull->getRenderMapSize(type) > 0; +} + +LLCullResult::drawinfo_iterator LLPipeline::beginRenderMap(U32 type) +{ + return sCull->beginRenderMap(type); +} + +LLCullResult::drawinfo_iterator LLPipeline::endRenderMap(U32 type) +{ + return sCull->endRenderMap(type); +} + +LLCullResult::sg_iterator LLPipeline::beginAlphaGroups() +{ + return sCull->beginAlphaGroups(); +} + +LLCullResult::sg_iterator LLPipeline::endAlphaGroups() +{ + return sCull->endAlphaGroups(); +} + +LLCullResult::sg_iterator LLPipeline::beginRiggedAlphaGroups() +{ + return sCull->beginRiggedAlphaGroups(); +} + +LLCullResult::sg_iterator LLPipeline::endRiggedAlphaGroups() +{ + return sCull->endRiggedAlphaGroups(); +} + +bool LLPipeline::hasRenderType(const U32 type) const +{ + // STORM-365 : LLViewerJointAttachment::setAttachmentVisibility() is setting type to 0 to actually mean "do not render" + // We then need to test that value here and return false to prevent attachment to render (in mouselook for instance) + // TODO: reintroduce RENDER_TYPE_NONE in LLRenderTypeMask and initialize its mRenderTypeEnabled[RENDER_TYPE_NONE] to false explicitely + return (type == 0 ? false : mRenderTypeEnabled[type]); +} + +void LLPipeline::setRenderTypeMask(U32 type, ...) +{ + va_list args; + + va_start(args, type); + while (type < END_RENDER_TYPES) + { + mRenderTypeEnabled[type] = true; + type = va_arg(args, U32); + } + va_end(args); + + if (type > END_RENDER_TYPES) + { + LL_ERRS() << "Invalid render type." << LL_ENDL; + } +} + +bool LLPipeline::hasAnyRenderType(U32 type, ...) const +{ + va_list args; + + va_start(args, type); + while (type < END_RENDER_TYPES) + { + if (mRenderTypeEnabled[type]) + { + return true; + } + type = va_arg(args, U32); + } + va_end(args); + + if (type > END_RENDER_TYPES) + { + LL_ERRS() << "Invalid render type." << LL_ENDL; + } + + return false; +} + +void LLPipeline::pushRenderTypeMask() +{ + std::string cur_mask; + cur_mask.assign((const char*) mRenderTypeEnabled, sizeof(mRenderTypeEnabled)); + mRenderTypeEnableStack.push(cur_mask); +} + +void LLPipeline::popRenderTypeMask() +{ + if (mRenderTypeEnableStack.empty()) + { + LL_ERRS() << "Depleted render type stack." << LL_ENDL; + } + + memcpy(mRenderTypeEnabled, mRenderTypeEnableStack.top().data(), sizeof(mRenderTypeEnabled)); + mRenderTypeEnableStack.pop(); +} + +void LLPipeline::andRenderTypeMask(U32 type, ...) +{ + va_list args; + + bool tmp[NUM_RENDER_TYPES]; + for (U32 i = 0; i < NUM_RENDER_TYPES; ++i) + { + tmp[i] = false; + } + + va_start(args, type); + while (type < END_RENDER_TYPES) + { + if (mRenderTypeEnabled[type]) + { + tmp[type] = true; + } + + type = va_arg(args, U32); + } + va_end(args); + + if (type > END_RENDER_TYPES) + { + LL_ERRS() << "Invalid render type." << LL_ENDL; + } + + for (U32 i = 0; i < LLPipeline::NUM_RENDER_TYPES; ++i) + { + mRenderTypeEnabled[i] = tmp[i]; + } + +} + +void LLPipeline::clearRenderTypeMask(U32 type, ...) +{ + va_list args; + + va_start(args, type); + while (type < END_RENDER_TYPES) + { + mRenderTypeEnabled[type] = false; + + type = va_arg(args, U32); + } + va_end(args); + + if (type > END_RENDER_TYPES) + { + LL_ERRS() << "Invalid render type." << LL_ENDL; + } +} + +void LLPipeline::setAllRenderTypes() +{ + for (U32 i = 0; i < NUM_RENDER_TYPES; ++i) + { + mRenderTypeEnabled[i] = true; + } +} + +void LLPipeline::clearAllRenderTypes() +{ + for (U32 i = 0; i < NUM_RENDER_TYPES; ++i) + { + mRenderTypeEnabled[i] = false; + } +} + +void LLPipeline::addDebugBlip(const LLVector3& position, const LLColor4& color) +{ + DebugBlip blip(position, color); + mDebugBlips.push_back(blip); +} + +void LLPipeline::hidePermanentObjects( std::vector& restoreList ) +{ + //This method is used to hide any vo's from the object list that may have + //the permanent flag set. + + U32 objCnt = gObjectList.getNumObjects(); + for (U32 i = 0; i < objCnt; ++i) + { + LLViewerObject* pObject = gObjectList.getObject(i); + if ( pObject && pObject->flagObjectPermanent() ) + { + LLDrawable *pDrawable = pObject->mDrawable; + + if ( pDrawable ) + { + restoreList.push_back( i ); + hideDrawable( pDrawable ); + } + } + } + + skipRenderingOfTerrain( true ); +} + +void LLPipeline::restorePermanentObjects( const std::vector& restoreList ) +{ + //This method is used to restore(unhide) any vo's from the object list that may have + //been hidden because their permanency flag was set. + + std::vector::const_iterator itCurrent = restoreList.begin(); + std::vector::const_iterator itEnd = restoreList.end(); + + U32 objCnt = gObjectList.getNumObjects(); + + while ( itCurrent != itEnd ) + { + U32 index = *itCurrent; + LLViewerObject* pObject = NULL; + if ( index < objCnt ) + { + pObject = gObjectList.getObject( index ); + } + if ( pObject ) + { + LLDrawable *pDrawable = pObject->mDrawable; + if ( pDrawable ) + { + pDrawable->clearState( LLDrawable::FORCE_INVISIBLE ); + unhideDrawable( pDrawable ); + } + } + ++itCurrent; + } + + skipRenderingOfTerrain( false ); +} + +void LLPipeline::skipRenderingOfTerrain( bool flag ) +{ + pool_set_t::iterator iter = mPools.begin(); + while ( iter != mPools.end() ) + { + LLDrawPool* pPool = *iter; + U32 poolType = pPool->getType(); + if ( hasRenderType( pPool->getType() ) && poolType == LLDrawPool::POOL_TERRAIN ) + { + pPool->setSkipRenderFlag( flag ); + } + ++iter; + } +} + +void LLPipeline::hideObject( const LLUUID& id ) +{ + LLViewerObject *pVO = gObjectList.findObject( id ); + + if ( pVO ) + { + LLDrawable *pDrawable = pVO->mDrawable; + + if ( pDrawable ) + { + hideDrawable( pDrawable ); + } + } +} + +void LLPipeline::hideDrawable( LLDrawable *pDrawable ) +{ + pDrawable->setState( LLDrawable::FORCE_INVISIBLE ); + markRebuild( pDrawable, LLDrawable::REBUILD_ALL); + //hide the children + LLViewerObject::const_child_list_t& child_list = pDrawable->getVObj()->getChildren(); + for ( LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); iter++ ) + { + LLViewerObject* child = *iter; + LLDrawable* drawable = child->mDrawable; + if ( drawable ) + { + drawable->setState( LLDrawable::FORCE_INVISIBLE ); + markRebuild( drawable, LLDrawable::REBUILD_ALL); + } + } +} +void LLPipeline::unhideDrawable( LLDrawable *pDrawable ) +{ + pDrawable->clearState( LLDrawable::FORCE_INVISIBLE ); + markRebuild( pDrawable, LLDrawable::REBUILD_ALL); + //restore children + LLViewerObject::const_child_list_t& child_list = pDrawable->getVObj()->getChildren(); + for ( LLViewerObject::child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); iter++) + { + LLViewerObject* child = *iter; + LLDrawable* drawable = child->mDrawable; + if ( drawable ) + { + drawable->clearState( LLDrawable::FORCE_INVISIBLE ); + markRebuild( drawable, LLDrawable::REBUILD_ALL); + } + } +} +void LLPipeline::restoreHiddenObject( const LLUUID& id ) +{ + LLViewerObject *pVO = gObjectList.findObject( id ); + + if ( pVO ) + { + LLDrawable *pDrawable = pVO->mDrawable; + if ( pDrawable ) + { + unhideDrawable( pDrawable ); + } + } +} + +void LLPipeline::skipRenderingShadows() +{ + LLGLDepthTest depth(GL_TRUE); + + for (S32 j = 0; j < 4; j++) + { + mRT->shadow[j].bindTarget(); + mRT->shadow[j].clear(); + mRT->shadow[j].flush(); + } +} + +void LLPipeline::handleShadowDetailChanged() +{ + if (RenderShadowDetail > gSavedSettings.getS32("RenderShadowDetail")) + { + skipRenderingShadows(); + } + else + { + LLViewerShaderMgr::instance()->setShaders(); + } +} + +class LLOctreeDirty : public OctreeTraveler +{ +public: + virtual void visit(const OctreeNode* state) + { + LLSpatialGroup* group = (LLSpatialGroup*)state->getListener(0); + + if (group->getSpatialPartition()->mRenderByGroup) + { + group->setState(LLSpatialGroup::GEOM_DIRTY); + gPipeline.markRebuild(group); + } + + for (LLSpatialGroup::bridge_list_t::iterator i = group->mBridgeList.begin(); i != group->mBridgeList.end(); ++i) + { + LLSpatialBridge* bridge = *i; + traverse(bridge->mOctree); + } + } +}; + + +void LLPipeline::rebuildDrawInfo() +{ + for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); + iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + { + LLViewerRegion* region = *iter; + + LLOctreeDirty dirty; + + LLSpatialPartition* part = region->getSpatialPartition(LLViewerRegion::PARTITION_VOLUME); + dirty.traverse(part->mOctree); + + part = region->getSpatialPartition(LLViewerRegion::PARTITION_BRIDGE); + dirty.traverse(part->mOctree); + } +} + diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h index 2eaeb8d95b..b61f865d8a 100644 --- a/indra/newview/pipeline.h +++ b/indra/newview/pipeline.h @@ -1,1059 +1,1059 @@ -/** - * @file pipeline.h - * @brief Rendering pipeline definitions - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#ifndef LL_PIPELINE_H -#define LL_PIPELINE_H - -#include "llcamera.h" -#include "llerror.h" -#include "lldrawpool.h" -#include "llspatialpartition.h" -#include "m4math.h" -#include "llpointer.h" -#include "lldrawpoolalpha.h" -#include "lldrawpoolmaterials.h" -#include "llgl.h" -#include "lldrawable.h" -#include "llrendertarget.h" -#include "llreflectionmapmanager.h" - -#include - -class LLViewerTexture; -class LLFace; -class LLViewerObject; -class LLTextureEntry; -class LLCullResult; -class LLVOAvatar; -class LLVOPartGroup; -class LLGLSLShader; -class LLDrawPoolAlpha; -class LLSettingsSky; - -typedef enum e_avatar_skinning_method -{ - SKIN_METHOD_SOFTWARE, - SKIN_METHOD_VERTEX_PROGRAM -} EAvatarSkinningMethod; - -bool compute_min_max(LLMatrix4& box, LLVector2& min, LLVector2& max); // Shouldn't be defined here! -bool LLRayAABB(const LLVector3 ¢er, const LLVector3 &size, const LLVector3& origin, const LLVector3& dir, LLVector3 &coord, F32 epsilon = 0); -bool setup_hud_matrices(); // use whole screen to render hud -bool setup_hud_matrices(const LLRect& screen_region); // specify portion of screen (in pixels) to render hud attachments from (for picking) - - -extern LLTrace::BlockTimerStatHandle FTM_RENDER_GEOMETRY; -extern LLTrace::BlockTimerStatHandle FTM_RENDER_GRASS; -extern LLTrace::BlockTimerStatHandle FTM_RENDER_INVISIBLE; -extern LLTrace::BlockTimerStatHandle FTM_RENDER_SHINY; -extern LLTrace::BlockTimerStatHandle FTM_RENDER_SIMPLE; -extern LLTrace::BlockTimerStatHandle FTM_RENDER_TERRAIN; -extern LLTrace::BlockTimerStatHandle FTM_RENDER_TREES; -extern LLTrace::BlockTimerStatHandle FTM_RENDER_UI; -extern LLTrace::BlockTimerStatHandle FTM_RENDER_WATER; -extern LLTrace::BlockTimerStatHandle FTM_RENDER_WL_SKY; -extern LLTrace::BlockTimerStatHandle FTM_RENDER_ALPHA; -extern LLTrace::BlockTimerStatHandle FTM_RENDER_CHARACTERS; -extern LLTrace::BlockTimerStatHandle FTM_RENDER_BUMP; -extern LLTrace::BlockTimerStatHandle FTM_RENDER_MATERIALS; -extern LLTrace::BlockTimerStatHandle FTM_RENDER_FULLBRIGHT; -extern LLTrace::BlockTimerStatHandle FTM_RENDER_GLOW; -extern LLTrace::BlockTimerStatHandle FTM_STATESORT; -extern LLTrace::BlockTimerStatHandle FTM_PIPELINE; -extern LLTrace::BlockTimerStatHandle FTM_CLIENT_COPY; - -extern LLTrace::BlockTimerStatHandle FTM_RENDER_UI_HUD; -extern LLTrace::BlockTimerStatHandle FTM_RENDER_UI_3D; -extern LLTrace::BlockTimerStatHandle FTM_RENDER_UI_2D; - -class LLPipeline -{ -public: - LLPipeline(); - ~LLPipeline(); - - void destroyGL(); - void restoreGL(); - void requestResizeScreenTexture(); // set flag only, no work, safer for callbacks... - void requestResizeShadowTexture(); // set flag only, no work, safer for callbacks... - - void resizeScreenTexture(); - void resizeShadowTexture(); - - void releaseGLBuffers(); - void releaseLUTBuffers(); - void releaseScreenBuffers(); - void releaseShadowBuffers(); - - void createGLBuffers(); - void createLUTBuffers(); - - //allocate the largest screen buffer possible up to resX, resY - //returns true if full size buffer allocated, false if some other size is allocated - bool allocateScreenBuffer(U32 resX, U32 resY); - - typedef enum { - FBO_SUCCESS_FULLRES = 0, - FBO_SUCCESS_LOWRES, - FBO_FAILURE - } eFBOStatus; - -private: - //implementation of above, wrapped for easy error handling - eFBOStatus doAllocateScreenBuffer(U32 resX, U32 resY); -public: - - //attempt to allocate screen buffers at resX, resY - //returns true if allocation successful, false otherwise - bool allocateScreenBuffer(U32 resX, U32 resY, U32 samples); - bool allocateShadowBuffer(U32 resX, U32 resY); - - // rebuild all LLVOVolume render batches - void rebuildDrawInfo(); - - // Clear LLFace mVertexBuffer pointers - void resetVertexBuffers(LLDrawable* drawable); - - // perform a profile of the given avatar - // if profile_attachments is true, run a profile for each attachment - void profileAvatar(LLVOAvatar* avatar, bool profile_attachments = false); - - // generate an impostor for the given avatar - // preview_avatar - if true, a preview window render is being performed - // for_profile - if true, a profile is being performed, do not update actual impostor - // specific_attachment - specific attachment to profile, or nullptr to profile entire avatar - void generateImpostor(LLVOAvatar* avatar, bool preview_avatar = false, bool for_profile = false, LLViewerObject* specific_attachment = nullptr); - - void bindScreenToTexture(); - void renderFinalize(); - void copyScreenSpaceReflections(LLRenderTarget* src, LLRenderTarget* dst); - void generateLuminance(LLRenderTarget* src, LLRenderTarget* dst); - void generateExposure(LLRenderTarget* src, LLRenderTarget* dst); - void gammaCorrect(LLRenderTarget* src, LLRenderTarget* dst); - void generateGlow(LLRenderTarget* src); - void applyFXAA(LLRenderTarget* src, LLRenderTarget* dst); - void renderDoF(LLRenderTarget* src, LLRenderTarget* dst); - void copyRenderTarget(LLRenderTarget* src, LLRenderTarget* dst); - void combineGlow(LLRenderTarget* src, LLRenderTarget* dst); - void visualizeBuffers(LLRenderTarget* src, LLRenderTarget* dst, U32 bufferIndex); - - void init(); - void cleanup(); - bool isInit() { return mInitialized; }; - - /// @brief Get a draw pool from pool type (POOL_SIMPLE, POOL_MEDIA) and texture. - /// @return Draw pool, or NULL if not found. - LLDrawPool *findPool(const U32 pool_type, LLViewerTexture *tex0 = NULL); - - /// @brief Get a draw pool for faces of the appropriate type and texture. Create if necessary. - /// @return Always returns a draw pool. - LLDrawPool *getPool(const U32 pool_type, LLViewerTexture *tex0 = NULL); - - /// @brief Figures out draw pool type from texture entry. Creates pool if necessary. - static LLDrawPool* getPoolFromTE(const LLTextureEntry* te, LLViewerTexture* te_image); - static U32 getPoolTypeFromTE(const LLTextureEntry* te, LLViewerTexture* imagep); - - void addPool(LLDrawPool *poolp); // Only to be used by LLDrawPool classes for splitting pools! - void removePool( LLDrawPool* poolp ); - - void allocDrawable(LLViewerObject *obj); - - void unlinkDrawable(LLDrawable*); - - static void removeMutedAVsLights(LLVOAvatar*); - - // Object related methods - void markVisible(LLDrawable *drawablep, LLCamera& camera); - void markOccluder(LLSpatialGroup* group); - - void doOcclusion(LLCamera& camera); - void markNotCulled(LLSpatialGroup* group, LLCamera &camera); - void markMoved(LLDrawable *drawablep, bool damped_motion = false); - void markShift(LLDrawable *drawablep); - void markTextured(LLDrawable *drawablep); - void markGLRebuild(LLGLUpdate* glu); - void markRebuild(LLSpatialGroup* group); - void markRebuild(LLDrawable *drawablep, LLDrawable::EDrawableFlags flag = LLDrawable::REBUILD_ALL); - void markPartitionMove(LLDrawable* drawablep); - void markMeshDirty(LLSpatialGroup* group); - - //get the object between start and end that's closest to start. - LLViewerObject* lineSegmentIntersectInWorld(const LLVector4a& start, const LLVector4a& end, - bool pick_transparent, - bool pick_rigged, - bool pick_unselectable, - bool pick_reflection_probe, - S32* face_hit, // return the face hit - LLVector4a* intersection = NULL, // return the intersection point - LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point - LLVector4a* normal = NULL, // return the surface normal at the intersection point - LLVector4a* tangent = NULL // return the surface tangent at the intersection point - ); - - //get the closest particle to start between start and end, returns the LLVOPartGroup and particle index - LLVOPartGroup* lineSegmentIntersectParticle(const LLVector4a& start, const LLVector4a& end, LLVector4a* intersection, - S32* face_hit); - - - LLViewerObject* lineSegmentIntersectInHUD(const LLVector4a& start, const LLVector4a& end, - bool pick_transparent, - S32* face_hit, // return the face hit - LLVector4a* intersection = NULL, // return the intersection point - LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point - LLVector4a* normal = NULL, // return the surface normal at the intersection point - LLVector4a* tangent = NULL // return the surface tangent at the intersection point - ); - - // Something about these textures has changed. Dirty them. - void dirtyPoolObjectTextures(const std::set& textures); - - void resetDrawOrders(); - - U32 addObject(LLViewerObject *obj); - - void enableShadows(const bool enable_shadows); - void releaseSpotShadowTargets(); - void releaseSunShadowTargets(); - void releaseSunShadowTarget(U32 index); - - bool shadersLoaded(); - bool canUseWindLightShaders() const; - bool canUseAntiAliasing() const; - - // phases - void resetFrameStats(); - - void updateMoveDampedAsync(LLDrawable* drawablep); - void updateMoveNormalAsync(LLDrawable* drawablep); - void updateMovedList(LLDrawable::drawable_vector_t& move_list); - void updateMove(); - bool visibleObjectsInFrustum(LLCamera& camera); - bool getVisibleExtents(LLCamera& camera, LLVector3 &min, LLVector3& max); - bool getVisiblePointCloud(LLCamera& camera, LLVector3 &min, LLVector3& max, std::vector& fp, LLVector3 light_dir = LLVector3(0,0,0)); - - // Populate given LLCullResult with results of a frustum cull of the entire scene against the given LLCamera - void updateCull(LLCamera& camera, LLCullResult& result); - void createObjects(F32 max_dtime); - void createObject(LLViewerObject* vobj); - void processPartitionQ(); - void updateGeom(F32 max_dtime); - void updateGL(); - void rebuildPriorityGroups(); - void rebuildGroups(); - void clearRebuildGroups(); - void clearRebuildDrawables(); - - //calculate pixel area of given box from vantage point of given camera - static F32 calcPixelArea(LLVector3 center, LLVector3 size, LLCamera& camera); - static F32 calcPixelArea(const LLVector4a& center, const LLVector4a& size, LLCamera &camera); - - void stateSort(LLCamera& camera, LLCullResult& result); - void stateSort(LLSpatialGroup* group, LLCamera& camera); - void stateSort(LLSpatialBridge* bridge, LLCamera& camera, bool fov_changed = false); - void stateSort(LLDrawable* drawablep, LLCamera& camera); - void postSort(LLCamera& camera); - - void forAllVisibleDrawables(void (*func)(LLDrawable*)); - - void renderObjects(U32 type, bool texture = true, bool batch_texture = false, bool rigged = false); - void renderGLTFObjects(U32 type, bool texture = true, bool rigged = false); - - void renderAlphaObjects(bool rigged = false); - void renderMaskedObjects(U32 type, bool texture = true, bool batch_texture = false, bool rigged = false); - void renderFullbrightMaskedObjects(U32 type, bool texture = true, bool batch_texture = false, bool rigged = false); - - void renderGroups(LLRenderPass* pass, U32 type, bool texture); - void renderRiggedGroups(LLRenderPass* pass, U32 type, bool texture); - - void grabReferences(LLCullResult& result); - void clearReferences(); - - //check references will assert that there are no references in sCullResult to the provided data - void checkReferences(LLFace* face); - void checkReferences(LLDrawable* drawable); - void checkReferences(LLDrawInfo* draw_info); - void checkReferences(LLSpatialGroup* group); - - void renderGeomDeferred(LLCamera& camera, bool do_occlusion = false); - void renderGeomPostDeferred(LLCamera& camera); - void renderGeomShadow(LLCamera& camera); - void bindLightFunc(LLGLSLShader& shader); - - // bind shadow maps - // if setup is true, wil lset texture compare mode function and filtering options - void bindShadowMaps(LLGLSLShader& shader); - void bindDeferredShaderFast(LLGLSLShader& shader); - void bindDeferredShader(LLGLSLShader& shader, LLRenderTarget* light_target = nullptr, LLRenderTarget* depth_target = nullptr); - void setupSpotLight(LLGLSLShader& shader, LLDrawable* drawablep); - - void unbindDeferredShader(LLGLSLShader& shader); - - // set env_mat parameter in given shader - void setEnvMat(LLGLSLShader& shader); - - void bindReflectionProbes(LLGLSLShader& shader); - void unbindReflectionProbes(LLGLSLShader& shader); - - void renderDeferredLighting(); - - // apply atmospheric haze based on contents of color and depth buffer - // should be called just before rendering water when camera is under water - // and just before rendering alpha when camera is above water - void doAtmospherics(); - - // apply water haze based on contents of color and depth buffer - // should be called just before rendering pre-water alpha objects - void doWaterHaze(); - - void postDeferredGammaCorrect(LLRenderTarget* screen_target); - - void generateSunShadow(LLCamera& camera); - LLRenderTarget* getSunShadowTarget(U32 i); - LLRenderTarget* getSpotShadowTarget(U32 i); - - void renderHighlight(const LLViewerObject* obj, F32 fade); - - void renderShadow(glh::matrix4f& view, glh::matrix4f& proj, LLCamera& camera, LLCullResult& result, bool depth_clamp); - void renderHighlights(); - void renderDebug(); - void renderPhysicsDisplay(); - - void rebuildPools(); // Rebuild pools - - void findReferences(LLDrawable *drawablep); // Find the lists which have references to this object - bool verify(); // Verify that all data in the pipeline is "correct" - - S32 getLightCount() const { return mLights.size(); } - - void calcNearbyLights(LLCamera& camera); - void setupHWLights(); - void setupAvatarLights(bool for_edit = false); - void enableLights(U32 mask); - void enableLightsDynamic(); - void enableLightsAvatar(); - void enableLightsPreview(); - void enableLightsAvatarEdit(const LLColor4& color); - void enableLightsFullbright(); - void disableLights(); - - void shiftObjects(const LLVector3 &offset); - - void setLight(LLDrawable *drawablep, bool is_light); - - bool hasRenderBatches(const U32 type) const; - LLCullResult::drawinfo_iterator beginRenderMap(U32 type); - LLCullResult::drawinfo_iterator endRenderMap(U32 type); - LLCullResult::sg_iterator beginAlphaGroups(); - LLCullResult::sg_iterator endAlphaGroups(); - LLCullResult::sg_iterator beginRiggedAlphaGroups(); - LLCullResult::sg_iterator endRiggedAlphaGroups(); - - void addTrianglesDrawn(S32 index_count); - void recordTrianglesDrawn(); - - bool hasRenderDebugFeatureMask(const U32 mask) const { return bool(mRenderDebugFeatureMask & mask); } - bool hasRenderDebugMask(const U64 mask) const { return bool(mRenderDebugMask & mask); } - void setAllRenderDebugFeatures() { mRenderDebugFeatureMask = 0xffffffff; } - void clearAllRenderDebugFeatures() { mRenderDebugFeatureMask = 0x0; } - void setAllRenderDebugDisplays() { mRenderDebugMask = 0xffffffffffffffff; } - void clearAllRenderDebugDisplays() { mRenderDebugMask = 0x0; } - - bool hasRenderType(const U32 type) const; - bool hasAnyRenderType(const U32 type, ...) const; - - static bool isWaterClip(); - - void setRenderTypeMask(U32 type, ...); - // This is equivalent to 'setRenderTypeMask' - //void orRenderTypeMask(U32 type, ...); - void andRenderTypeMask(U32 type, ...); - void clearRenderTypeMask(U32 type, ...); - void setAllRenderTypes(); - void clearAllRenderTypes(); - - void pushRenderTypeMask(); - void popRenderTypeMask(); - - void pushRenderDebugFeatureMask(); - void popRenderDebugFeatureMask(); - - static void toggleRenderType(U32 type); - - // For UI control of render features - static bool hasRenderTypeControl(U32 data); - static void toggleRenderDebug(U64 data); - static void toggleRenderDebugFeature(U32 data); - static void toggleRenderTypeControl(U32 data); - static bool toggleRenderTypeControlNegated(S32 data); - static bool toggleRenderDebugControl(U64 data); - static bool toggleRenderDebugFeatureControl(U32 data); - static void setRenderDebugFeatureControl(U32 bit, bool value); - - static void setRenderParticleBeacons(bool val); - static void toggleRenderParticleBeacons(); - static bool getRenderParticleBeacons(); - - static void setRenderSoundBeacons(bool val); - static void toggleRenderSoundBeacons(); - static bool getRenderSoundBeacons(); - - static void setRenderMOAPBeacons(bool val); - static void toggleRenderMOAPBeacons(); - static bool getRenderMOAPBeacons(); - - static void setRenderPhysicalBeacons(bool val); - static void toggleRenderPhysicalBeacons(); - static bool getRenderPhysicalBeacons(); - - static void setRenderScriptedBeacons(bool val); - static void toggleRenderScriptedBeacons(); - static bool getRenderScriptedBeacons(); - - static void setRenderScriptedTouchBeacons(bool val); - static void toggleRenderScriptedTouchBeacons(); - static bool getRenderScriptedTouchBeacons(); - - static void setRenderBeacons(bool val); - static void toggleRenderBeacons(); - static bool getRenderBeacons(); - - static void setRenderHighlights(bool val); - static void toggleRenderHighlights(); - static bool getRenderHighlights(); - static void setRenderHighlightTextureChannel(LLRender::eTexIndex channel); // sets which UV setup to display in highlight overlay - - static void updateRenderTransparentWater(); - static void refreshCachedSettings(); - - void addDebugBlip(const LLVector3& position, const LLColor4& color); - - void hidePermanentObjects( std::vector& restoreList ); - void restorePermanentObjects( const std::vector& restoreList ); - void skipRenderingOfTerrain( bool flag ); - void hideObject( const LLUUID& id ); - void restoreHiddenObject( const LLUUID& id ); - void handleShadowDetailChanged(); - - LLReflectionMapManager mReflectionMapManager; - -private: - void unloadShaders(); - void addToQuickLookup( LLDrawPool* new_poolp ); - void removeFromQuickLookup( LLDrawPool* poolp ); - bool updateDrawableGeom(LLDrawable* drawable); - void assertInitializedDoError(); - bool assertInitialized() { const bool is_init = isInit(); if (!is_init) assertInitializedDoError(); return is_init; }; - void connectRefreshCachedSettingsSafe(const std::string name); - void hideDrawable( LLDrawable *pDrawable ); - void unhideDrawable( LLDrawable *pDrawable ); - void skipRenderingShadows(); -public: - enum {GPU_CLASS_MAX = 3 }; - - enum LLRenderTypeMask - { - // Following are pool types (some are also object types) - RENDER_TYPE_SKY = LLDrawPool::POOL_SKY, - RENDER_TYPE_WL_SKY = LLDrawPool::POOL_WL_SKY, - RENDER_TYPE_TERRAIN = LLDrawPool::POOL_TERRAIN, - RENDER_TYPE_SIMPLE = LLDrawPool::POOL_SIMPLE, - RENDER_TYPE_GRASS = LLDrawPool::POOL_GRASS, - RENDER_TYPE_ALPHA_MASK = LLDrawPool::POOL_ALPHA_MASK, - RENDER_TYPE_FULLBRIGHT_ALPHA_MASK = LLDrawPool::POOL_FULLBRIGHT_ALPHA_MASK, - RENDER_TYPE_FULLBRIGHT = LLDrawPool::POOL_FULLBRIGHT, - RENDER_TYPE_BUMP = LLDrawPool::POOL_BUMP, - RENDER_TYPE_MATERIALS = LLDrawPool::POOL_MATERIALS, - RENDER_TYPE_AVATAR = LLDrawPool::POOL_AVATAR, - RENDER_TYPE_CONTROL_AV = LLDrawPool::POOL_CONTROL_AV, // Animesh - RENDER_TYPE_TREE = LLDrawPool::POOL_TREE, - RENDER_TYPE_VOIDWATER = LLDrawPool::POOL_VOIDWATER, - RENDER_TYPE_WATER = LLDrawPool::POOL_WATER, - RENDER_TYPE_GLTF_PBR = LLDrawPool::POOL_GLTF_PBR, - RENDER_TYPE_GLTF_PBR_ALPHA_MASK = LLDrawPool::POOL_GLTF_PBR_ALPHA_MASK, - RENDER_TYPE_ALPHA = LLDrawPool::POOL_ALPHA, - RENDER_TYPE_ALPHA_PRE_WATER = LLDrawPool::POOL_ALPHA_PRE_WATER, - RENDER_TYPE_ALPHA_POST_WATER = LLDrawPool::POOL_ALPHA_POST_WATER, - RENDER_TYPE_GLOW = LLDrawPool::POOL_GLOW, - RENDER_TYPE_PASS_SIMPLE = LLRenderPass::PASS_SIMPLE, - RENDER_TYPE_PASS_SIMPLE_RIGGED = LLRenderPass::PASS_SIMPLE_RIGGED, - RENDER_TYPE_PASS_GRASS = LLRenderPass::PASS_GRASS, - RENDER_TYPE_PASS_FULLBRIGHT = LLRenderPass::PASS_FULLBRIGHT, - RENDER_TYPE_PASS_FULLBRIGHT_RIGGED = LLRenderPass::PASS_FULLBRIGHT_RIGGED, - RENDER_TYPE_PASS_INVISIBLE = LLRenderPass::PASS_INVISIBLE, - RENDER_TYPE_PASS_INVISIBLE_RIGGED = LLRenderPass::PASS_INVISIBLE_RIGGED, - RENDER_TYPE_PASS_INVISI_SHINY = LLRenderPass::PASS_INVISI_SHINY, - RENDER_TYPE_PASS_INVISI_SHINY_RIGGED = LLRenderPass::PASS_INVISI_SHINY_RIGGED, - RENDER_TYPE_PASS_FULLBRIGHT_SHINY = LLRenderPass::PASS_FULLBRIGHT_SHINY, - RENDER_TYPE_PASS_FULLBRIGHT_SHINY_RIGGED = LLRenderPass::PASS_FULLBRIGHT_SHINY_RIGGED, - RENDER_TYPE_PASS_SHINY = LLRenderPass::PASS_SHINY, - RENDER_TYPE_PASS_SHINY_RIGGED = LLRenderPass::PASS_SHINY_RIGGED, - RENDER_TYPE_PASS_BUMP = LLRenderPass::PASS_BUMP, - RENDER_TYPE_PASS_BUMP_RIGGED = LLRenderPass::PASS_BUMP_RIGGED, - RENDER_TYPE_PASS_POST_BUMP = LLRenderPass::PASS_POST_BUMP, - RENDER_TYPE_PASS_POST_BUMP_RIGGED = LLRenderPass::PASS_POST_BUMP_RIGGED, - RENDER_TYPE_PASS_GLOW = LLRenderPass::PASS_GLOW, - RENDER_TYPE_PASS_GLOW_RIGGED = LLRenderPass::PASS_GLOW_RIGGED, - RENDER_TYPE_PASS_GLTF_GLOW = LLRenderPass::PASS_GLTF_GLOW, - RENDER_TYPE_PASS_GLTF_GLOW_RIGGED = LLRenderPass::PASS_GLTF_GLOW_RIGGED, - RENDER_TYPE_PASS_ALPHA = LLRenderPass::PASS_ALPHA, - RENDER_TYPE_PASS_ALPHA_MASK = LLRenderPass::PASS_ALPHA_MASK, - RENDER_TYPE_PASS_ALPHA_MASK_RIGGED = LLRenderPass::PASS_ALPHA_MASK_RIGGED, - RENDER_TYPE_PASS_FULLBRIGHT_ALPHA_MASK = LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK, - RENDER_TYPE_PASS_FULLBRIGHT_ALPHA_MASK_RIGGED = LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK_RIGGED, - RENDER_TYPE_PASS_MATERIAL = LLRenderPass::PASS_MATERIAL, - RENDER_TYPE_PASS_MATERIAL_RIGGED = LLRenderPass::PASS_MATERIAL_RIGGED, - RENDER_TYPE_PASS_MATERIAL_ALPHA = LLRenderPass::PASS_MATERIAL_ALPHA, - RENDER_TYPE_PASS_MATERIAL_ALPHA_RIGGED = LLRenderPass::PASS_MATERIAL_ALPHA_RIGGED, - RENDER_TYPE_PASS_MATERIAL_ALPHA_MASK = LLRenderPass::PASS_MATERIAL_ALPHA_MASK, - RENDER_TYPE_PASS_MATERIAL_ALPHA_MASK_RIGGED = LLRenderPass::PASS_MATERIAL_ALPHA_MASK_RIGGED, - RENDER_TYPE_PASS_MATERIAL_ALPHA_EMISSIVE= LLRenderPass::PASS_MATERIAL_ALPHA_EMISSIVE, - RENDER_TYPE_PASS_MATERIAL_ALPHA_EMISSIVE_RIGGED = LLRenderPass::PASS_MATERIAL_ALPHA_EMISSIVE_RIGGED, - RENDER_TYPE_PASS_SPECMAP = LLRenderPass::PASS_SPECMAP, - RENDER_TYPE_PASS_SPECMAP_RIGGED = LLRenderPass::PASS_SPECMAP_RIGGED, - RENDER_TYPE_PASS_SPECMAP_BLEND = LLRenderPass::PASS_SPECMAP_BLEND, - RENDER_TYPE_PASS_SPECMAP_BLEND_RIGGED = LLRenderPass::PASS_SPECMAP_BLEND_RIGGED, - RENDER_TYPE_PASS_SPECMAP_MASK = LLRenderPass::PASS_SPECMAP_MASK, - RENDER_TYPE_PASS_SPECMAP_MASK_RIGGED = LLRenderPass::PASS_SPECMAP_MASK_RIGGED, - RENDER_TYPE_PASS_SPECMAP_EMISSIVE = LLRenderPass::PASS_SPECMAP_EMISSIVE, - RENDER_TYPE_PASS_SPECMAP_EMISSIVE_RIGGED = LLRenderPass::PASS_SPECMAP_EMISSIVE_RIGGED, - RENDER_TYPE_PASS_NORMMAP = LLRenderPass::PASS_NORMMAP, - RENDER_TYPE_PASS_NORMMAP_RIGGED = LLRenderPass::PASS_NORMMAP_RIGGED, - RENDER_TYPE_PASS_NORMMAP_BLEND = LLRenderPass::PASS_NORMMAP_BLEND, - RENDER_TYPE_PASS_NORMMAP_BLEND_RIGGED = LLRenderPass::PASS_NORMMAP_BLEND_RIGGED, - RENDER_TYPE_PASS_NORMMAP_MASK = LLRenderPass::PASS_NORMMAP_MASK, - RENDER_TYPE_PASS_NORMMAP_MASK_RIGGED = LLRenderPass::PASS_NORMMAP_MASK_RIGGED, - RENDER_TYPE_PASS_NORMMAP_EMISSIVE = LLRenderPass::PASS_NORMMAP_EMISSIVE, - RENDER_TYPE_PASS_NORMMAP_EMISSIVE_RIGGED = LLRenderPass::PASS_NORMMAP_EMISSIVE_RIGGED, - RENDER_TYPE_PASS_NORMSPEC = LLRenderPass::PASS_NORMSPEC, - RENDER_TYPE_PASS_NORMSPEC_RIGGED = LLRenderPass::PASS_NORMSPEC_RIGGED, - RENDER_TYPE_PASS_NORMSPEC_BLEND = LLRenderPass::PASS_NORMSPEC_BLEND, - RENDER_TYPE_PASS_NORMSPEC_BLEND_RIGGED = LLRenderPass::PASS_NORMSPEC_BLEND_RIGGED, - RENDER_TYPE_PASS_NORMSPEC_MASK = LLRenderPass::PASS_NORMSPEC_MASK, - RENDER_TYPE_PASS_NORMSPEC_MASK_RIGGED = LLRenderPass::PASS_NORMSPEC_MASK_RIGGED, - RENDER_TYPE_PASS_NORMSPEC_EMISSIVE = LLRenderPass::PASS_NORMSPEC_EMISSIVE, - RENDER_TYPE_PASS_NORMSPEC_EMISSIVE_RIGGED = LLRenderPass::PASS_NORMSPEC_EMISSIVE_RIGGED, - RENDER_TYPE_PASS_GLTF_PBR = LLRenderPass::PASS_GLTF_PBR, - RENDER_TYPE_PASS_GLTF_PBR_RIGGED = LLRenderPass::PASS_GLTF_PBR_RIGGED, - RENDER_TYPE_PASS_GLTF_PBR_ALPHA_MASK = LLRenderPass::PASS_GLTF_PBR_ALPHA_MASK, - RENDER_TYPE_PASS_GLTF_PBR_ALPHA_MASK_RIGGED = LLRenderPass::PASS_GLTF_PBR_ALPHA_MASK_RIGGED, - // Following are object types (only used in drawable mRenderType) - RENDER_TYPE_HUD = LLRenderPass::NUM_RENDER_TYPES, - RENDER_TYPE_VOLUME, - RENDER_TYPE_PARTICLES, - RENDER_TYPE_CLOUDS, - RENDER_TYPE_HUD_PARTICLES, - NUM_RENDER_TYPES, - END_RENDER_TYPES = NUM_RENDER_TYPES - }; - - enum LLRenderDebugFeatureMask - { - RENDER_DEBUG_FEATURE_UI = 0x0001, - RENDER_DEBUG_FEATURE_SELECTED = 0x0002, - RENDER_DEBUG_FEATURE_HIGHLIGHTED = 0x0004, - RENDER_DEBUG_FEATURE_DYNAMIC_TEXTURES = 0x0008, -// RENDER_DEBUG_FEATURE_HW_LIGHTING = 0x0010, - RENDER_DEBUG_FEATURE_FLEXIBLE = 0x0010, - RENDER_DEBUG_FEATURE_FOG = 0x0020, - RENDER_DEBUG_FEATURE_FR_INFO = 0x0080, - RENDER_DEBUG_FEATURE_FOOT_SHADOWS = 0x0100, - }; - - enum LLRenderDebugMask: U64 - { - RENDER_DEBUG_COMPOSITION = 0x00000001, - RENDER_DEBUG_VERIFY = 0x00000002, - RENDER_DEBUG_BBOXES = 0x00000004, - RENDER_DEBUG_OCTREE = 0x00000008, - RENDER_DEBUG_WIND_VECTORS = 0x00000010, - RENDER_DEBUG_OCCLUSION = 0x00000020, - RENDER_DEBUG_POINTS = 0x00000040, - RENDER_DEBUG_TEXTURE_PRIORITY = 0x00000080, - RENDER_DEBUG_TEXTURE_AREA = 0x00000100, - RENDER_DEBUG_FACE_AREA = 0x00000200, - RENDER_DEBUG_PARTICLES = 0x00000400, - RENDER_DEBUG_GLOW = 0x00000800, // not used - RENDER_DEBUG_TEXTURE_ANIM = 0x00001000, - RENDER_DEBUG_LIGHTS = 0x00002000, - RENDER_DEBUG_BATCH_SIZE = 0x00004000, - RENDER_DEBUG_ALPHA_BINS = 0x00008000, // not used - RENDER_DEBUG_RAYCAST = 0x00010000, - RENDER_DEBUG_AVATAR_DRAW_INFO = 0x00020000, - RENDER_DEBUG_SHADOW_FRUSTA = 0x00040000, - RENDER_DEBUG_SCULPTED = 0x00080000, - RENDER_DEBUG_AVATAR_VOLUME = 0x00100000, - RENDER_DEBUG_AVATAR_JOINTS = 0x00200000, - RENDER_DEBUG_AGENT_TARGET = 0x00800000, - RENDER_DEBUG_UPDATE_TYPE = 0x01000000, - RENDER_DEBUG_PHYSICS_SHAPES = 0x02000000, - RENDER_DEBUG_NORMALS = 0x04000000, - RENDER_DEBUG_LOD_INFO = 0x08000000, - RENDER_DEBUG_ATTACHMENT_BYTES = 0x20000000, // not used - RENDER_DEBUG_TEXEL_DENSITY = 0x40000000, - RENDER_DEBUG_TRIANGLE_COUNT = 0x80000000, - RENDER_DEBUG_IMPOSTORS = 0x100000000, - RENDER_DEBUG_REFLECTION_PROBES = 0x200000000, - RENDER_DEBUG_PROBE_UPDATES = 0x400000000 - }; - -public: - - LLSpatialPartition* getSpatialPartition(LLViewerObject* vobj); - - void updateCamera(bool reset = false); - - LLVector3 mFlyCamPosition; - LLQuaternion mFlyCamRotation; - - bool mBackfaceCull; - S32 mMatrixOpCount; - S32 mTextureMatrixOps; - S32 mNumVisibleNodes; - - S32 mDebugTextureUploadCost; - S32 mDebugSculptUploadCost; - S32 mDebugMeshUploadCost; - - S32 mNumVisibleFaces; - - S32 mPoissonOffset; - - static S32 sCompiles; - - static bool sShowHUDAttachments; - static bool sForceOldBakedUpload; // If true will not use capabilities to upload baked textures. - static S32 sUseOcclusion; // 0 = no occlusion, 1 = read only, 2 = read/write - static bool sAutoMaskAlphaDeferred; - static bool sAutoMaskAlphaNonDeferred; - static bool sRenderTransparentWater; - static bool sBakeSunlight; - static bool sNoAlpha; - static bool sUseFarClip; - static bool sShadowRender; - static bool sDynamicLOD; - static bool sPickAvatar; - static bool sReflectionRender; - static bool sDistortionRender; - static bool sImpostorRender; - static bool sImpostorRenderAlphaDepthPass; - static bool sUnderWaterRender; - static bool sRenderGlow; - static bool sTextureBindTest; - static bool sRenderAttachedLights; - static bool sRenderAttachedParticles; - static bool sRenderDeferred; - static bool sReflectionProbesEnabled; - static S32 sVisibleLightCount; - static bool sRenderingHUDs; - static F32 sDistortionWaterClipPlaneMargin; - - static LLTrace::EventStatHandle sStatBatchSize; - - class RenderTargetPack - { - public: - U32 width = 0; - U32 height = 0; - - //screen texture - LLRenderTarget screen; - LLRenderTarget uiScreen; - LLRenderTarget deferredScreen; - LLRenderTarget fxaaBuffer; - LLRenderTarget edgeMap; - LLRenderTarget deferredLight; - - //sun shadow map - LLRenderTarget shadow[4]; - }; - - // main full resoltuion render target - RenderTargetPack mMainRT; - - // auxillary 512x512 render target pack - RenderTargetPack mAuxillaryRT; - - // currently used render target pack - RenderTargetPack* mRT; - - LLRenderTarget mSpotShadow[2]; - - LLRenderTarget mPbrBrdfLut; - - // copy of the color/depth buffer just before gamma correction - // for use by SSR - LLRenderTarget mSceneMap; - - // exposure map for getting average color in scene - LLRenderTarget mLuminanceMap; - LLRenderTarget mExposureMap; - LLRenderTarget mLastExposure; - - // tonemapped and gamma corrected render ready for post - LLRenderTarget mPostMap; - - LLCullResult mSky; - LLCullResult mReflectedObjects; - LLCullResult mRefractedObjects; - - //utility buffers for rendering post effects - LLPointer mDeferredVB; - - // a single triangle that covers the whole screen - LLPointer mScreenTriangleVB; - - //utility buffer for rendering cubes, 8 vertices are corners of a cube [-1, 1] - LLPointer mCubeVB; - - //list of currently bound reflection maps - std::vector mReflectionMaps; - - std::vector mShadowFrustPoints[4]; - LLVector4 mShadowError; - LLVector4 mShadowFOV; - LLVector3 mShadowFrustOrigin[4]; - LLCamera mShadowCamera[8]; - LLVector3 mShadowExtents[4][2]; - // TODO : separate Sun Shadow and Spot Shadow matrices - glh::matrix4f mSunShadowMatrix[6]; - glh::matrix4f mShadowModelview[6]; - glh::matrix4f mShadowProjection[6]; - glh::matrix4f mReflectionModelView; - - LLPointer mShadowSpotLight[2]; - F32 mSpotLightFade[2]; - LLPointer mTargetShadowSpotLight[2]; - - LLVector4 mSunClipPlanes; - LLVector4 mSunOrthoClipPlanes; - LLVector2 mScreenScale; - - //water distortion texture (refraction) - LLRenderTarget mWaterDis; - - LLRenderTarget mBake; - - //texture for making the glow - LLRenderTarget mGlow[3]; - - //noise map - U32 mNoiseMap; - U32 mTrueNoiseMap; - U32 mLightFunc; - - LLColor4 mSunDiffuse; - LLColor4 mMoonDiffuse; - LLVector4 mSunDir; - LLVector4 mMoonDir; - bool mNeedsShadowTargetClear; - - LLVector4 mTransformedSunDir; - LLVector4 mTransformedMoonDir; - - bool mInitialized; - bool mShadersLoaded; - - U32 mTransformFeedbackPrimitives; //number of primitives expected to be generated by transform feedback -protected: - bool mRenderTypeEnabled[NUM_RENDER_TYPES]; - std::stack mRenderTypeEnableStack; - - U32 mRenderDebugFeatureMask; - U64 mRenderDebugMask; - U64 mOldRenderDebugMask; - std::stack mRenderDebugFeatureStack; - - ///////////////////////////////////////////// - // - // - LLDrawable::drawable_vector_t mMovedList; - LLDrawable::drawable_vector_t mMovedBridge; - LLDrawable::drawable_vector_t mShiftList; - - ///////////////////////////////////////////// - // - // - struct Light - { - Light(LLDrawable* ptr, F32 d, F32 f = 0.0f) - : drawable(ptr), - dist(d), - fade(f) - {} - LLPointer drawable; - F32 dist; - F32 fade; - struct compare - { - bool operator()(const Light& a, const Light& b) const - { - if ( a.dist < b.dist ) - return true; - else if ( a.dist > b.dist ) - return false; - else - return a.drawable < b.drawable; - } - }; - }; - typedef std::set< Light, Light::compare > light_set_t; - - LLDrawable::ordered_drawable_set_t mLights; - light_set_t mNearbyLights; // lights near camera - LLColor4 mHWLightColors[8]; - - ///////////////////////////////////////////// - // - // Different queues of drawables being processed. - // - LLDrawable::drawable_list_t mBuildQ1; // priority - LLSpatialGroup::sg_vector_t mGroupQ1; //priority - - LLSpatialGroup::sg_vector_t mGroupSaveQ1; // a place to save mGroupQ1 until it is safe to unref - - LLSpatialGroup::sg_vector_t mMeshDirtyGroup; //groups that need rebuildMesh called - U32 mMeshDirtyQueryObject; - - LLDrawable::drawable_list_t mPartitionQ; //drawables that need to update their spatial partition radius - - bool mGroupQ1Locked; - - bool mResetVertexBuffers; //if true, clear vertex buffers on next update - - LLViewerObject::vobj_list_t mCreateQ; - - LLDrawable::drawable_set_t mRetexturedList; - - class HighlightItem - { - public: - const LLPointer mItem; - mutable F32 mFade; - - HighlightItem(LLDrawable* item) - : mItem(item), mFade(0) - { - } - - bool operator<(const HighlightItem& rhs) const - { - return mItem < rhs.mItem; - } - - bool operator==(const HighlightItem& rhs) const - { - return mItem == rhs.mItem; - } - - void incrFade(F32 val) const - { - mFade = llclamp(mFade+val, 0.f, 1.f); - } - }; - - ////////////////////////////////////////////////// - // - // Draw pools are responsible for storing all rendered data, - // and performing the actual rendering of objects. - // - struct compare_pools - { - bool operator()(const LLDrawPool* a, const LLDrawPool* b) const - { - if (!a) - return true; - else if (!b) - return false; - else - { - S32 atype = a->getType(); - S32 btype = b->getType(); - if (atype < btype) - return true; - else if (atype > btype) - return false; - else - return a->getId() < b->getId(); - } - } - }; - typedef std::set pool_set_t; - pool_set_t mPools; - LLDrawPool* mLastRebuildPool; - - // For quick-lookups into mPools (mapped by texture pointer) - std::map mTerrainPools; - std::map mTreePools; - LLDrawPoolAlpha* mAlphaPoolPreWater = nullptr; - LLDrawPoolAlpha* mAlphaPoolPostWater = nullptr; - LLDrawPool* mSkyPool = nullptr; - LLDrawPool* mTerrainPool = nullptr; - LLDrawPool* mWaterPool = nullptr; - LLRenderPass* mSimplePool = nullptr; - LLRenderPass* mGrassPool = nullptr; - LLRenderPass* mAlphaMaskPool = nullptr; - LLRenderPass* mFullbrightAlphaMaskPool = nullptr; - LLRenderPass* mFullbrightPool = nullptr; - LLDrawPool* mGlowPool = nullptr; - LLDrawPool* mBumpPool = nullptr; - LLDrawPool* mMaterialsPool = nullptr; - LLDrawPool* mWLSkyPool = nullptr; - LLDrawPool* mPBROpaquePool = nullptr; - LLDrawPool* mPBRAlphaMaskPool = nullptr; - - // Note: no need to keep an quick-lookup to avatar pools, since there's only one per avatar - -public: - std::vector mHighlightFaces; // highlight faces on physical objects -protected: - std::vector mSelectedFaces; - - class DebugBlip - { - public: - LLColor4 mColor; - LLVector3 mPosition; - F32 mAge; - - DebugBlip(const LLVector3& position, const LLColor4& color) - : mColor(color), mPosition(position), mAge(0.f) - { } - }; - - std::list mDebugBlips; - - LLPointer mFaceSelectImagep; - - U32 mLightMask; - U32 mLightMovingMask; - - static bool sRenderPhysicalBeacons; - static bool sRenderMOAPBeacons; - static bool sRenderScriptedTouchBeacons; - static bool sRenderScriptedBeacons; - static bool sRenderParticleBeacons; - static bool sRenderSoundBeacons; -public: - static bool sRenderBeacons; - static bool sRenderHighlight; - - // Determines which set of UVs to use in highlight display - // - static LLRender::eTexIndex sRenderHighlightTextureChannel; - - //debug use - static U32 sCurRenderPoolType ; - - //cached settings - static bool WindLightUseAtmosShaders; - static bool RenderDeferred; - static F32 RenderDeferredSunWash; - static U32 RenderFSAASamples; - static U32 RenderResolutionDivisor; - static bool RenderUIBuffer; - static S32 RenderShadowDetail; - static S32 RenderShadowSplits; - static bool RenderDeferredSSAO; - static F32 RenderShadowResolutionScale; - static bool RenderDelayCreation; - static bool RenderAnimateRes; - static bool FreezeTime; - static S32 DebugBeaconLineWidth; - static F32 RenderHighlightBrightness; - static LLColor4 RenderHighlightColor; - static F32 RenderHighlightThickness; - static bool RenderSpotLightsInNondeferred; - static LLColor4 PreviewAmbientColor; - static LLColor4 PreviewDiffuse0; - static LLColor4 PreviewSpecular0; - static LLColor4 PreviewDiffuse1; - static LLColor4 PreviewSpecular1; - static LLColor4 PreviewDiffuse2; - static LLColor4 PreviewSpecular2; - static LLVector3 PreviewDirection0; - static LLVector3 PreviewDirection1; - static LLVector3 PreviewDirection2; - static F32 RenderGlowMinLuminance; - static F32 RenderGlowMaxExtractAlpha; - static F32 RenderGlowWarmthAmount; - static LLVector3 RenderGlowLumWeights; - static LLVector3 RenderGlowWarmthWeights; - static S32 RenderGlowResolutionPow; - static S32 RenderGlowIterations; - static F32 RenderGlowWidth; - static F32 RenderGlowStrength; - static bool RenderGlowNoise; - static bool RenderDepthOfField; - static bool RenderDepthOfFieldInEditMode; - static F32 CameraFocusTransitionTime; - static F32 CameraFNumber; - static F32 CameraFocalLength; - static F32 CameraFieldOfView; - static F32 RenderShadowNoise; - static F32 RenderShadowBlurSize; - static F32 RenderSSAOScale; - static U32 RenderSSAOMaxScale; - static F32 RenderSSAOFactor; - static LLVector3 RenderSSAOEffect; - static F32 RenderShadowOffsetError; - static F32 RenderShadowBiasError; - static F32 RenderShadowOffset; - static F32 RenderShadowBias; - static F32 RenderSpotShadowOffset; - static F32 RenderSpotShadowBias; - static LLDrawable* RenderSpotLight; - static F32 RenderEdgeDepthCutoff; - static F32 RenderEdgeNormCutoff; - static LLVector3 RenderShadowGaussian; - static F32 RenderShadowBlurDistFactor; - static bool RenderDeferredAtmospheric; - static F32 RenderHighlightFadeTime; - static F32 RenderFarClip; - static LLVector3 RenderShadowSplitExponent; - static F32 RenderShadowErrorCutoff; - static F32 RenderShadowFOVCutoff; - static bool CameraOffset; - static F32 CameraMaxCoF; - static F32 CameraDoFResScale; - static F32 RenderAutoHideSurfaceAreaLimit; - static bool RenderScreenSpaceReflections; - static S32 RenderScreenSpaceReflectionIterations; - static F32 RenderScreenSpaceReflectionRayStep; - static F32 RenderScreenSpaceReflectionDistanceBias; - static F32 RenderScreenSpaceReflectionDepthRejectBias; - static F32 RenderScreenSpaceReflectionAdaptiveStepMultiplier; - static S32 RenderScreenSpaceReflectionGlossySamples; - static S32 RenderBufferVisualization; -}; - -void render_bbox(const LLVector3 &min, const LLVector3 &max); -void render_hud_elements(); - -extern LLPipeline gPipeline; -extern bool gDebugPipeline; -extern const LLMatrix4* gGLLastMatrix; - -#endif +/** + * @file pipeline.h + * @brief Rendering pipeline definitions + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_PIPELINE_H +#define LL_PIPELINE_H + +#include "llcamera.h" +#include "llerror.h" +#include "lldrawpool.h" +#include "llspatialpartition.h" +#include "m4math.h" +#include "llpointer.h" +#include "lldrawpoolalpha.h" +#include "lldrawpoolmaterials.h" +#include "llgl.h" +#include "lldrawable.h" +#include "llrendertarget.h" +#include "llreflectionmapmanager.h" + +#include + +class LLViewerTexture; +class LLFace; +class LLViewerObject; +class LLTextureEntry; +class LLCullResult; +class LLVOAvatar; +class LLVOPartGroup; +class LLGLSLShader; +class LLDrawPoolAlpha; +class LLSettingsSky; + +typedef enum e_avatar_skinning_method +{ + SKIN_METHOD_SOFTWARE, + SKIN_METHOD_VERTEX_PROGRAM +} EAvatarSkinningMethod; + +bool compute_min_max(LLMatrix4& box, LLVector2& min, LLVector2& max); // Shouldn't be defined here! +bool LLRayAABB(const LLVector3 ¢er, const LLVector3 &size, const LLVector3& origin, const LLVector3& dir, LLVector3 &coord, F32 epsilon = 0); +bool setup_hud_matrices(); // use whole screen to render hud +bool setup_hud_matrices(const LLRect& screen_region); // specify portion of screen (in pixels) to render hud attachments from (for picking) + + +extern LLTrace::BlockTimerStatHandle FTM_RENDER_GEOMETRY; +extern LLTrace::BlockTimerStatHandle FTM_RENDER_GRASS; +extern LLTrace::BlockTimerStatHandle FTM_RENDER_INVISIBLE; +extern LLTrace::BlockTimerStatHandle FTM_RENDER_SHINY; +extern LLTrace::BlockTimerStatHandle FTM_RENDER_SIMPLE; +extern LLTrace::BlockTimerStatHandle FTM_RENDER_TERRAIN; +extern LLTrace::BlockTimerStatHandle FTM_RENDER_TREES; +extern LLTrace::BlockTimerStatHandle FTM_RENDER_UI; +extern LLTrace::BlockTimerStatHandle FTM_RENDER_WATER; +extern LLTrace::BlockTimerStatHandle FTM_RENDER_WL_SKY; +extern LLTrace::BlockTimerStatHandle FTM_RENDER_ALPHA; +extern LLTrace::BlockTimerStatHandle FTM_RENDER_CHARACTERS; +extern LLTrace::BlockTimerStatHandle FTM_RENDER_BUMP; +extern LLTrace::BlockTimerStatHandle FTM_RENDER_MATERIALS; +extern LLTrace::BlockTimerStatHandle FTM_RENDER_FULLBRIGHT; +extern LLTrace::BlockTimerStatHandle FTM_RENDER_GLOW; +extern LLTrace::BlockTimerStatHandle FTM_STATESORT; +extern LLTrace::BlockTimerStatHandle FTM_PIPELINE; +extern LLTrace::BlockTimerStatHandle FTM_CLIENT_COPY; + +extern LLTrace::BlockTimerStatHandle FTM_RENDER_UI_HUD; +extern LLTrace::BlockTimerStatHandle FTM_RENDER_UI_3D; +extern LLTrace::BlockTimerStatHandle FTM_RENDER_UI_2D; + +class LLPipeline +{ +public: + LLPipeline(); + ~LLPipeline(); + + void destroyGL(); + void restoreGL(); + void requestResizeScreenTexture(); // set flag only, no work, safer for callbacks... + void requestResizeShadowTexture(); // set flag only, no work, safer for callbacks... + + void resizeScreenTexture(); + void resizeShadowTexture(); + + void releaseGLBuffers(); + void releaseLUTBuffers(); + void releaseScreenBuffers(); + void releaseShadowBuffers(); + + void createGLBuffers(); + void createLUTBuffers(); + + //allocate the largest screen buffer possible up to resX, resY + //returns true if full size buffer allocated, false if some other size is allocated + bool allocateScreenBuffer(U32 resX, U32 resY); + + typedef enum { + FBO_SUCCESS_FULLRES = 0, + FBO_SUCCESS_LOWRES, + FBO_FAILURE + } eFBOStatus; + +private: + //implementation of above, wrapped for easy error handling + eFBOStatus doAllocateScreenBuffer(U32 resX, U32 resY); +public: + + //attempt to allocate screen buffers at resX, resY + //returns true if allocation successful, false otherwise + bool allocateScreenBuffer(U32 resX, U32 resY, U32 samples); + bool allocateShadowBuffer(U32 resX, U32 resY); + + // rebuild all LLVOVolume render batches + void rebuildDrawInfo(); + + // Clear LLFace mVertexBuffer pointers + void resetVertexBuffers(LLDrawable* drawable); + + // perform a profile of the given avatar + // if profile_attachments is true, run a profile for each attachment + void profileAvatar(LLVOAvatar* avatar, bool profile_attachments = false); + + // generate an impostor for the given avatar + // preview_avatar - if true, a preview window render is being performed + // for_profile - if true, a profile is being performed, do not update actual impostor + // specific_attachment - specific attachment to profile, or nullptr to profile entire avatar + void generateImpostor(LLVOAvatar* avatar, bool preview_avatar = false, bool for_profile = false, LLViewerObject* specific_attachment = nullptr); + + void bindScreenToTexture(); + void renderFinalize(); + void copyScreenSpaceReflections(LLRenderTarget* src, LLRenderTarget* dst); + void generateLuminance(LLRenderTarget* src, LLRenderTarget* dst); + void generateExposure(LLRenderTarget* src, LLRenderTarget* dst); + void gammaCorrect(LLRenderTarget* src, LLRenderTarget* dst); + void generateGlow(LLRenderTarget* src); + void applyFXAA(LLRenderTarget* src, LLRenderTarget* dst); + void renderDoF(LLRenderTarget* src, LLRenderTarget* dst); + void copyRenderTarget(LLRenderTarget* src, LLRenderTarget* dst); + void combineGlow(LLRenderTarget* src, LLRenderTarget* dst); + void visualizeBuffers(LLRenderTarget* src, LLRenderTarget* dst, U32 bufferIndex); + + void init(); + void cleanup(); + bool isInit() { return mInitialized; }; + + /// @brief Get a draw pool from pool type (POOL_SIMPLE, POOL_MEDIA) and texture. + /// @return Draw pool, or NULL if not found. + LLDrawPool *findPool(const U32 pool_type, LLViewerTexture *tex0 = NULL); + + /// @brief Get a draw pool for faces of the appropriate type and texture. Create if necessary. + /// @return Always returns a draw pool. + LLDrawPool *getPool(const U32 pool_type, LLViewerTexture *tex0 = NULL); + + /// @brief Figures out draw pool type from texture entry. Creates pool if necessary. + static LLDrawPool* getPoolFromTE(const LLTextureEntry* te, LLViewerTexture* te_image); + static U32 getPoolTypeFromTE(const LLTextureEntry* te, LLViewerTexture* imagep); + + void addPool(LLDrawPool *poolp); // Only to be used by LLDrawPool classes for splitting pools! + void removePool( LLDrawPool* poolp ); + + void allocDrawable(LLViewerObject *obj); + + void unlinkDrawable(LLDrawable*); + + static void removeMutedAVsLights(LLVOAvatar*); + + // Object related methods + void markVisible(LLDrawable *drawablep, LLCamera& camera); + void markOccluder(LLSpatialGroup* group); + + void doOcclusion(LLCamera& camera); + void markNotCulled(LLSpatialGroup* group, LLCamera &camera); + void markMoved(LLDrawable *drawablep, bool damped_motion = false); + void markShift(LLDrawable *drawablep); + void markTextured(LLDrawable *drawablep); + void markGLRebuild(LLGLUpdate* glu); + void markRebuild(LLSpatialGroup* group); + void markRebuild(LLDrawable *drawablep, LLDrawable::EDrawableFlags flag = LLDrawable::REBUILD_ALL); + void markPartitionMove(LLDrawable* drawablep); + void markMeshDirty(LLSpatialGroup* group); + + //get the object between start and end that's closest to start. + LLViewerObject* lineSegmentIntersectInWorld(const LLVector4a& start, const LLVector4a& end, + bool pick_transparent, + bool pick_rigged, + bool pick_unselectable, + bool pick_reflection_probe, + S32* face_hit, // return the face hit + LLVector4a* intersection = NULL, // return the intersection point + LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point + LLVector4a* normal = NULL, // return the surface normal at the intersection point + LLVector4a* tangent = NULL // return the surface tangent at the intersection point + ); + + //get the closest particle to start between start and end, returns the LLVOPartGroup and particle index + LLVOPartGroup* lineSegmentIntersectParticle(const LLVector4a& start, const LLVector4a& end, LLVector4a* intersection, + S32* face_hit); + + + LLViewerObject* lineSegmentIntersectInHUD(const LLVector4a& start, const LLVector4a& end, + bool pick_transparent, + S32* face_hit, // return the face hit + LLVector4a* intersection = NULL, // return the intersection point + LLVector2* tex_coord = NULL, // return the texture coordinates of the intersection point + LLVector4a* normal = NULL, // return the surface normal at the intersection point + LLVector4a* tangent = NULL // return the surface tangent at the intersection point + ); + + // Something about these textures has changed. Dirty them. + void dirtyPoolObjectTextures(const std::set& textures); + + void resetDrawOrders(); + + U32 addObject(LLViewerObject *obj); + + void enableShadows(const bool enable_shadows); + void releaseSpotShadowTargets(); + void releaseSunShadowTargets(); + void releaseSunShadowTarget(U32 index); + + bool shadersLoaded(); + bool canUseWindLightShaders() const; + bool canUseAntiAliasing() const; + + // phases + void resetFrameStats(); + + void updateMoveDampedAsync(LLDrawable* drawablep); + void updateMoveNormalAsync(LLDrawable* drawablep); + void updateMovedList(LLDrawable::drawable_vector_t& move_list); + void updateMove(); + bool visibleObjectsInFrustum(LLCamera& camera); + bool getVisibleExtents(LLCamera& camera, LLVector3 &min, LLVector3& max); + bool getVisiblePointCloud(LLCamera& camera, LLVector3 &min, LLVector3& max, std::vector& fp, LLVector3 light_dir = LLVector3(0,0,0)); + + // Populate given LLCullResult with results of a frustum cull of the entire scene against the given LLCamera + void updateCull(LLCamera& camera, LLCullResult& result); + void createObjects(F32 max_dtime); + void createObject(LLViewerObject* vobj); + void processPartitionQ(); + void updateGeom(F32 max_dtime); + void updateGL(); + void rebuildPriorityGroups(); + void rebuildGroups(); + void clearRebuildGroups(); + void clearRebuildDrawables(); + + //calculate pixel area of given box from vantage point of given camera + static F32 calcPixelArea(LLVector3 center, LLVector3 size, LLCamera& camera); + static F32 calcPixelArea(const LLVector4a& center, const LLVector4a& size, LLCamera &camera); + + void stateSort(LLCamera& camera, LLCullResult& result); + void stateSort(LLSpatialGroup* group, LLCamera& camera); + void stateSort(LLSpatialBridge* bridge, LLCamera& camera, bool fov_changed = false); + void stateSort(LLDrawable* drawablep, LLCamera& camera); + void postSort(LLCamera& camera); + + void forAllVisibleDrawables(void (*func)(LLDrawable*)); + + void renderObjects(U32 type, bool texture = true, bool batch_texture = false, bool rigged = false); + void renderGLTFObjects(U32 type, bool texture = true, bool rigged = false); + + void renderAlphaObjects(bool rigged = false); + void renderMaskedObjects(U32 type, bool texture = true, bool batch_texture = false, bool rigged = false); + void renderFullbrightMaskedObjects(U32 type, bool texture = true, bool batch_texture = false, bool rigged = false); + + void renderGroups(LLRenderPass* pass, U32 type, bool texture); + void renderRiggedGroups(LLRenderPass* pass, U32 type, bool texture); + + void grabReferences(LLCullResult& result); + void clearReferences(); + + //check references will assert that there are no references in sCullResult to the provided data + void checkReferences(LLFace* face); + void checkReferences(LLDrawable* drawable); + void checkReferences(LLDrawInfo* draw_info); + void checkReferences(LLSpatialGroup* group); + + void renderGeomDeferred(LLCamera& camera, bool do_occlusion = false); + void renderGeomPostDeferred(LLCamera& camera); + void renderGeomShadow(LLCamera& camera); + void bindLightFunc(LLGLSLShader& shader); + + // bind shadow maps + // if setup is true, wil lset texture compare mode function and filtering options + void bindShadowMaps(LLGLSLShader& shader); + void bindDeferredShaderFast(LLGLSLShader& shader); + void bindDeferredShader(LLGLSLShader& shader, LLRenderTarget* light_target = nullptr, LLRenderTarget* depth_target = nullptr); + void setupSpotLight(LLGLSLShader& shader, LLDrawable* drawablep); + + void unbindDeferredShader(LLGLSLShader& shader); + + // set env_mat parameter in given shader + void setEnvMat(LLGLSLShader& shader); + + void bindReflectionProbes(LLGLSLShader& shader); + void unbindReflectionProbes(LLGLSLShader& shader); + + void renderDeferredLighting(); + + // apply atmospheric haze based on contents of color and depth buffer + // should be called just before rendering water when camera is under water + // and just before rendering alpha when camera is above water + void doAtmospherics(); + + // apply water haze based on contents of color and depth buffer + // should be called just before rendering pre-water alpha objects + void doWaterHaze(); + + void postDeferredGammaCorrect(LLRenderTarget* screen_target); + + void generateSunShadow(LLCamera& camera); + LLRenderTarget* getSunShadowTarget(U32 i); + LLRenderTarget* getSpotShadowTarget(U32 i); + + void renderHighlight(const LLViewerObject* obj, F32 fade); + + void renderShadow(glh::matrix4f& view, glh::matrix4f& proj, LLCamera& camera, LLCullResult& result, bool depth_clamp); + void renderHighlights(); + void renderDebug(); + void renderPhysicsDisplay(); + + void rebuildPools(); // Rebuild pools + + void findReferences(LLDrawable *drawablep); // Find the lists which have references to this object + bool verify(); // Verify that all data in the pipeline is "correct" + + S32 getLightCount() const { return mLights.size(); } + + void calcNearbyLights(LLCamera& camera); + void setupHWLights(); + void setupAvatarLights(bool for_edit = false); + void enableLights(U32 mask); + void enableLightsDynamic(); + void enableLightsAvatar(); + void enableLightsPreview(); + void enableLightsAvatarEdit(const LLColor4& color); + void enableLightsFullbright(); + void disableLights(); + + void shiftObjects(const LLVector3 &offset); + + void setLight(LLDrawable *drawablep, bool is_light); + + bool hasRenderBatches(const U32 type) const; + LLCullResult::drawinfo_iterator beginRenderMap(U32 type); + LLCullResult::drawinfo_iterator endRenderMap(U32 type); + LLCullResult::sg_iterator beginAlphaGroups(); + LLCullResult::sg_iterator endAlphaGroups(); + LLCullResult::sg_iterator beginRiggedAlphaGroups(); + LLCullResult::sg_iterator endRiggedAlphaGroups(); + + void addTrianglesDrawn(S32 index_count); + void recordTrianglesDrawn(); + + bool hasRenderDebugFeatureMask(const U32 mask) const { return bool(mRenderDebugFeatureMask & mask); } + bool hasRenderDebugMask(const U64 mask) const { return bool(mRenderDebugMask & mask); } + void setAllRenderDebugFeatures() { mRenderDebugFeatureMask = 0xffffffff; } + void clearAllRenderDebugFeatures() { mRenderDebugFeatureMask = 0x0; } + void setAllRenderDebugDisplays() { mRenderDebugMask = 0xffffffffffffffff; } + void clearAllRenderDebugDisplays() { mRenderDebugMask = 0x0; } + + bool hasRenderType(const U32 type) const; + bool hasAnyRenderType(const U32 type, ...) const; + + static bool isWaterClip(); + + void setRenderTypeMask(U32 type, ...); + // This is equivalent to 'setRenderTypeMask' + //void orRenderTypeMask(U32 type, ...); + void andRenderTypeMask(U32 type, ...); + void clearRenderTypeMask(U32 type, ...); + void setAllRenderTypes(); + void clearAllRenderTypes(); + + void pushRenderTypeMask(); + void popRenderTypeMask(); + + void pushRenderDebugFeatureMask(); + void popRenderDebugFeatureMask(); + + static void toggleRenderType(U32 type); + + // For UI control of render features + static bool hasRenderTypeControl(U32 data); + static void toggleRenderDebug(U64 data); + static void toggleRenderDebugFeature(U32 data); + static void toggleRenderTypeControl(U32 data); + static bool toggleRenderTypeControlNegated(S32 data); + static bool toggleRenderDebugControl(U64 data); + static bool toggleRenderDebugFeatureControl(U32 data); + static void setRenderDebugFeatureControl(U32 bit, bool value); + + static void setRenderParticleBeacons(bool val); + static void toggleRenderParticleBeacons(); + static bool getRenderParticleBeacons(); + + static void setRenderSoundBeacons(bool val); + static void toggleRenderSoundBeacons(); + static bool getRenderSoundBeacons(); + + static void setRenderMOAPBeacons(bool val); + static void toggleRenderMOAPBeacons(); + static bool getRenderMOAPBeacons(); + + static void setRenderPhysicalBeacons(bool val); + static void toggleRenderPhysicalBeacons(); + static bool getRenderPhysicalBeacons(); + + static void setRenderScriptedBeacons(bool val); + static void toggleRenderScriptedBeacons(); + static bool getRenderScriptedBeacons(); + + static void setRenderScriptedTouchBeacons(bool val); + static void toggleRenderScriptedTouchBeacons(); + static bool getRenderScriptedTouchBeacons(); + + static void setRenderBeacons(bool val); + static void toggleRenderBeacons(); + static bool getRenderBeacons(); + + static void setRenderHighlights(bool val); + static void toggleRenderHighlights(); + static bool getRenderHighlights(); + static void setRenderHighlightTextureChannel(LLRender::eTexIndex channel); // sets which UV setup to display in highlight overlay + + static void updateRenderTransparentWater(); + static void refreshCachedSettings(); + + void addDebugBlip(const LLVector3& position, const LLColor4& color); + + void hidePermanentObjects( std::vector& restoreList ); + void restorePermanentObjects( const std::vector& restoreList ); + void skipRenderingOfTerrain( bool flag ); + void hideObject( const LLUUID& id ); + void restoreHiddenObject( const LLUUID& id ); + void handleShadowDetailChanged(); + + LLReflectionMapManager mReflectionMapManager; + +private: + void unloadShaders(); + void addToQuickLookup( LLDrawPool* new_poolp ); + void removeFromQuickLookup( LLDrawPool* poolp ); + bool updateDrawableGeom(LLDrawable* drawable); + void assertInitializedDoError(); + bool assertInitialized() { const bool is_init = isInit(); if (!is_init) assertInitializedDoError(); return is_init; }; + void connectRefreshCachedSettingsSafe(const std::string name); + void hideDrawable( LLDrawable *pDrawable ); + void unhideDrawable( LLDrawable *pDrawable ); + void skipRenderingShadows(); +public: + enum {GPU_CLASS_MAX = 3 }; + + enum LLRenderTypeMask + { + // Following are pool types (some are also object types) + RENDER_TYPE_SKY = LLDrawPool::POOL_SKY, + RENDER_TYPE_WL_SKY = LLDrawPool::POOL_WL_SKY, + RENDER_TYPE_TERRAIN = LLDrawPool::POOL_TERRAIN, + RENDER_TYPE_SIMPLE = LLDrawPool::POOL_SIMPLE, + RENDER_TYPE_GRASS = LLDrawPool::POOL_GRASS, + RENDER_TYPE_ALPHA_MASK = LLDrawPool::POOL_ALPHA_MASK, + RENDER_TYPE_FULLBRIGHT_ALPHA_MASK = LLDrawPool::POOL_FULLBRIGHT_ALPHA_MASK, + RENDER_TYPE_FULLBRIGHT = LLDrawPool::POOL_FULLBRIGHT, + RENDER_TYPE_BUMP = LLDrawPool::POOL_BUMP, + RENDER_TYPE_MATERIALS = LLDrawPool::POOL_MATERIALS, + RENDER_TYPE_AVATAR = LLDrawPool::POOL_AVATAR, + RENDER_TYPE_CONTROL_AV = LLDrawPool::POOL_CONTROL_AV, // Animesh + RENDER_TYPE_TREE = LLDrawPool::POOL_TREE, + RENDER_TYPE_VOIDWATER = LLDrawPool::POOL_VOIDWATER, + RENDER_TYPE_WATER = LLDrawPool::POOL_WATER, + RENDER_TYPE_GLTF_PBR = LLDrawPool::POOL_GLTF_PBR, + RENDER_TYPE_GLTF_PBR_ALPHA_MASK = LLDrawPool::POOL_GLTF_PBR_ALPHA_MASK, + RENDER_TYPE_ALPHA = LLDrawPool::POOL_ALPHA, + RENDER_TYPE_ALPHA_PRE_WATER = LLDrawPool::POOL_ALPHA_PRE_WATER, + RENDER_TYPE_ALPHA_POST_WATER = LLDrawPool::POOL_ALPHA_POST_WATER, + RENDER_TYPE_GLOW = LLDrawPool::POOL_GLOW, + RENDER_TYPE_PASS_SIMPLE = LLRenderPass::PASS_SIMPLE, + RENDER_TYPE_PASS_SIMPLE_RIGGED = LLRenderPass::PASS_SIMPLE_RIGGED, + RENDER_TYPE_PASS_GRASS = LLRenderPass::PASS_GRASS, + RENDER_TYPE_PASS_FULLBRIGHT = LLRenderPass::PASS_FULLBRIGHT, + RENDER_TYPE_PASS_FULLBRIGHT_RIGGED = LLRenderPass::PASS_FULLBRIGHT_RIGGED, + RENDER_TYPE_PASS_INVISIBLE = LLRenderPass::PASS_INVISIBLE, + RENDER_TYPE_PASS_INVISIBLE_RIGGED = LLRenderPass::PASS_INVISIBLE_RIGGED, + RENDER_TYPE_PASS_INVISI_SHINY = LLRenderPass::PASS_INVISI_SHINY, + RENDER_TYPE_PASS_INVISI_SHINY_RIGGED = LLRenderPass::PASS_INVISI_SHINY_RIGGED, + RENDER_TYPE_PASS_FULLBRIGHT_SHINY = LLRenderPass::PASS_FULLBRIGHT_SHINY, + RENDER_TYPE_PASS_FULLBRIGHT_SHINY_RIGGED = LLRenderPass::PASS_FULLBRIGHT_SHINY_RIGGED, + RENDER_TYPE_PASS_SHINY = LLRenderPass::PASS_SHINY, + RENDER_TYPE_PASS_SHINY_RIGGED = LLRenderPass::PASS_SHINY_RIGGED, + RENDER_TYPE_PASS_BUMP = LLRenderPass::PASS_BUMP, + RENDER_TYPE_PASS_BUMP_RIGGED = LLRenderPass::PASS_BUMP_RIGGED, + RENDER_TYPE_PASS_POST_BUMP = LLRenderPass::PASS_POST_BUMP, + RENDER_TYPE_PASS_POST_BUMP_RIGGED = LLRenderPass::PASS_POST_BUMP_RIGGED, + RENDER_TYPE_PASS_GLOW = LLRenderPass::PASS_GLOW, + RENDER_TYPE_PASS_GLOW_RIGGED = LLRenderPass::PASS_GLOW_RIGGED, + RENDER_TYPE_PASS_GLTF_GLOW = LLRenderPass::PASS_GLTF_GLOW, + RENDER_TYPE_PASS_GLTF_GLOW_RIGGED = LLRenderPass::PASS_GLTF_GLOW_RIGGED, + RENDER_TYPE_PASS_ALPHA = LLRenderPass::PASS_ALPHA, + RENDER_TYPE_PASS_ALPHA_MASK = LLRenderPass::PASS_ALPHA_MASK, + RENDER_TYPE_PASS_ALPHA_MASK_RIGGED = LLRenderPass::PASS_ALPHA_MASK_RIGGED, + RENDER_TYPE_PASS_FULLBRIGHT_ALPHA_MASK = LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK, + RENDER_TYPE_PASS_FULLBRIGHT_ALPHA_MASK_RIGGED = LLRenderPass::PASS_FULLBRIGHT_ALPHA_MASK_RIGGED, + RENDER_TYPE_PASS_MATERIAL = LLRenderPass::PASS_MATERIAL, + RENDER_TYPE_PASS_MATERIAL_RIGGED = LLRenderPass::PASS_MATERIAL_RIGGED, + RENDER_TYPE_PASS_MATERIAL_ALPHA = LLRenderPass::PASS_MATERIAL_ALPHA, + RENDER_TYPE_PASS_MATERIAL_ALPHA_RIGGED = LLRenderPass::PASS_MATERIAL_ALPHA_RIGGED, + RENDER_TYPE_PASS_MATERIAL_ALPHA_MASK = LLRenderPass::PASS_MATERIAL_ALPHA_MASK, + RENDER_TYPE_PASS_MATERIAL_ALPHA_MASK_RIGGED = LLRenderPass::PASS_MATERIAL_ALPHA_MASK_RIGGED, + RENDER_TYPE_PASS_MATERIAL_ALPHA_EMISSIVE= LLRenderPass::PASS_MATERIAL_ALPHA_EMISSIVE, + RENDER_TYPE_PASS_MATERIAL_ALPHA_EMISSIVE_RIGGED = LLRenderPass::PASS_MATERIAL_ALPHA_EMISSIVE_RIGGED, + RENDER_TYPE_PASS_SPECMAP = LLRenderPass::PASS_SPECMAP, + RENDER_TYPE_PASS_SPECMAP_RIGGED = LLRenderPass::PASS_SPECMAP_RIGGED, + RENDER_TYPE_PASS_SPECMAP_BLEND = LLRenderPass::PASS_SPECMAP_BLEND, + RENDER_TYPE_PASS_SPECMAP_BLEND_RIGGED = LLRenderPass::PASS_SPECMAP_BLEND_RIGGED, + RENDER_TYPE_PASS_SPECMAP_MASK = LLRenderPass::PASS_SPECMAP_MASK, + RENDER_TYPE_PASS_SPECMAP_MASK_RIGGED = LLRenderPass::PASS_SPECMAP_MASK_RIGGED, + RENDER_TYPE_PASS_SPECMAP_EMISSIVE = LLRenderPass::PASS_SPECMAP_EMISSIVE, + RENDER_TYPE_PASS_SPECMAP_EMISSIVE_RIGGED = LLRenderPass::PASS_SPECMAP_EMISSIVE_RIGGED, + RENDER_TYPE_PASS_NORMMAP = LLRenderPass::PASS_NORMMAP, + RENDER_TYPE_PASS_NORMMAP_RIGGED = LLRenderPass::PASS_NORMMAP_RIGGED, + RENDER_TYPE_PASS_NORMMAP_BLEND = LLRenderPass::PASS_NORMMAP_BLEND, + RENDER_TYPE_PASS_NORMMAP_BLEND_RIGGED = LLRenderPass::PASS_NORMMAP_BLEND_RIGGED, + RENDER_TYPE_PASS_NORMMAP_MASK = LLRenderPass::PASS_NORMMAP_MASK, + RENDER_TYPE_PASS_NORMMAP_MASK_RIGGED = LLRenderPass::PASS_NORMMAP_MASK_RIGGED, + RENDER_TYPE_PASS_NORMMAP_EMISSIVE = LLRenderPass::PASS_NORMMAP_EMISSIVE, + RENDER_TYPE_PASS_NORMMAP_EMISSIVE_RIGGED = LLRenderPass::PASS_NORMMAP_EMISSIVE_RIGGED, + RENDER_TYPE_PASS_NORMSPEC = LLRenderPass::PASS_NORMSPEC, + RENDER_TYPE_PASS_NORMSPEC_RIGGED = LLRenderPass::PASS_NORMSPEC_RIGGED, + RENDER_TYPE_PASS_NORMSPEC_BLEND = LLRenderPass::PASS_NORMSPEC_BLEND, + RENDER_TYPE_PASS_NORMSPEC_BLEND_RIGGED = LLRenderPass::PASS_NORMSPEC_BLEND_RIGGED, + RENDER_TYPE_PASS_NORMSPEC_MASK = LLRenderPass::PASS_NORMSPEC_MASK, + RENDER_TYPE_PASS_NORMSPEC_MASK_RIGGED = LLRenderPass::PASS_NORMSPEC_MASK_RIGGED, + RENDER_TYPE_PASS_NORMSPEC_EMISSIVE = LLRenderPass::PASS_NORMSPEC_EMISSIVE, + RENDER_TYPE_PASS_NORMSPEC_EMISSIVE_RIGGED = LLRenderPass::PASS_NORMSPEC_EMISSIVE_RIGGED, + RENDER_TYPE_PASS_GLTF_PBR = LLRenderPass::PASS_GLTF_PBR, + RENDER_TYPE_PASS_GLTF_PBR_RIGGED = LLRenderPass::PASS_GLTF_PBR_RIGGED, + RENDER_TYPE_PASS_GLTF_PBR_ALPHA_MASK = LLRenderPass::PASS_GLTF_PBR_ALPHA_MASK, + RENDER_TYPE_PASS_GLTF_PBR_ALPHA_MASK_RIGGED = LLRenderPass::PASS_GLTF_PBR_ALPHA_MASK_RIGGED, + // Following are object types (only used in drawable mRenderType) + RENDER_TYPE_HUD = LLRenderPass::NUM_RENDER_TYPES, + RENDER_TYPE_VOLUME, + RENDER_TYPE_PARTICLES, + RENDER_TYPE_CLOUDS, + RENDER_TYPE_HUD_PARTICLES, + NUM_RENDER_TYPES, + END_RENDER_TYPES = NUM_RENDER_TYPES + }; + + enum LLRenderDebugFeatureMask + { + RENDER_DEBUG_FEATURE_UI = 0x0001, + RENDER_DEBUG_FEATURE_SELECTED = 0x0002, + RENDER_DEBUG_FEATURE_HIGHLIGHTED = 0x0004, + RENDER_DEBUG_FEATURE_DYNAMIC_TEXTURES = 0x0008, +// RENDER_DEBUG_FEATURE_HW_LIGHTING = 0x0010, + RENDER_DEBUG_FEATURE_FLEXIBLE = 0x0010, + RENDER_DEBUG_FEATURE_FOG = 0x0020, + RENDER_DEBUG_FEATURE_FR_INFO = 0x0080, + RENDER_DEBUG_FEATURE_FOOT_SHADOWS = 0x0100, + }; + + enum LLRenderDebugMask: U64 + { + RENDER_DEBUG_COMPOSITION = 0x00000001, + RENDER_DEBUG_VERIFY = 0x00000002, + RENDER_DEBUG_BBOXES = 0x00000004, + RENDER_DEBUG_OCTREE = 0x00000008, + RENDER_DEBUG_WIND_VECTORS = 0x00000010, + RENDER_DEBUG_OCCLUSION = 0x00000020, + RENDER_DEBUG_POINTS = 0x00000040, + RENDER_DEBUG_TEXTURE_PRIORITY = 0x00000080, + RENDER_DEBUG_TEXTURE_AREA = 0x00000100, + RENDER_DEBUG_FACE_AREA = 0x00000200, + RENDER_DEBUG_PARTICLES = 0x00000400, + RENDER_DEBUG_GLOW = 0x00000800, // not used + RENDER_DEBUG_TEXTURE_ANIM = 0x00001000, + RENDER_DEBUG_LIGHTS = 0x00002000, + RENDER_DEBUG_BATCH_SIZE = 0x00004000, + RENDER_DEBUG_ALPHA_BINS = 0x00008000, // not used + RENDER_DEBUG_RAYCAST = 0x00010000, + RENDER_DEBUG_AVATAR_DRAW_INFO = 0x00020000, + RENDER_DEBUG_SHADOW_FRUSTA = 0x00040000, + RENDER_DEBUG_SCULPTED = 0x00080000, + RENDER_DEBUG_AVATAR_VOLUME = 0x00100000, + RENDER_DEBUG_AVATAR_JOINTS = 0x00200000, + RENDER_DEBUG_AGENT_TARGET = 0x00800000, + RENDER_DEBUG_UPDATE_TYPE = 0x01000000, + RENDER_DEBUG_PHYSICS_SHAPES = 0x02000000, + RENDER_DEBUG_NORMALS = 0x04000000, + RENDER_DEBUG_LOD_INFO = 0x08000000, + RENDER_DEBUG_ATTACHMENT_BYTES = 0x20000000, // not used + RENDER_DEBUG_TEXEL_DENSITY = 0x40000000, + RENDER_DEBUG_TRIANGLE_COUNT = 0x80000000, + RENDER_DEBUG_IMPOSTORS = 0x100000000, + RENDER_DEBUG_REFLECTION_PROBES = 0x200000000, + RENDER_DEBUG_PROBE_UPDATES = 0x400000000 + }; + +public: + + LLSpatialPartition* getSpatialPartition(LLViewerObject* vobj); + + void updateCamera(bool reset = false); + + LLVector3 mFlyCamPosition; + LLQuaternion mFlyCamRotation; + + bool mBackfaceCull; + S32 mMatrixOpCount; + S32 mTextureMatrixOps; + S32 mNumVisibleNodes; + + S32 mDebugTextureUploadCost; + S32 mDebugSculptUploadCost; + S32 mDebugMeshUploadCost; + + S32 mNumVisibleFaces; + + S32 mPoissonOffset; + + static S32 sCompiles; + + static bool sShowHUDAttachments; + static bool sForceOldBakedUpload; // If true will not use capabilities to upload baked textures. + static S32 sUseOcclusion; // 0 = no occlusion, 1 = read only, 2 = read/write + static bool sAutoMaskAlphaDeferred; + static bool sAutoMaskAlphaNonDeferred; + static bool sRenderTransparentWater; + static bool sBakeSunlight; + static bool sNoAlpha; + static bool sUseFarClip; + static bool sShadowRender; + static bool sDynamicLOD; + static bool sPickAvatar; + static bool sReflectionRender; + static bool sDistortionRender; + static bool sImpostorRender; + static bool sImpostorRenderAlphaDepthPass; + static bool sUnderWaterRender; + static bool sRenderGlow; + static bool sTextureBindTest; + static bool sRenderAttachedLights; + static bool sRenderAttachedParticles; + static bool sRenderDeferred; + static bool sReflectionProbesEnabled; + static S32 sVisibleLightCount; + static bool sRenderingHUDs; + static F32 sDistortionWaterClipPlaneMargin; + + static LLTrace::EventStatHandle sStatBatchSize; + + class RenderTargetPack + { + public: + U32 width = 0; + U32 height = 0; + + //screen texture + LLRenderTarget screen; + LLRenderTarget uiScreen; + LLRenderTarget deferredScreen; + LLRenderTarget fxaaBuffer; + LLRenderTarget edgeMap; + LLRenderTarget deferredLight; + + //sun shadow map + LLRenderTarget shadow[4]; + }; + + // main full resoltuion render target + RenderTargetPack mMainRT; + + // auxillary 512x512 render target pack + RenderTargetPack mAuxillaryRT; + + // currently used render target pack + RenderTargetPack* mRT; + + LLRenderTarget mSpotShadow[2]; + + LLRenderTarget mPbrBrdfLut; + + // copy of the color/depth buffer just before gamma correction + // for use by SSR + LLRenderTarget mSceneMap; + + // exposure map for getting average color in scene + LLRenderTarget mLuminanceMap; + LLRenderTarget mExposureMap; + LLRenderTarget mLastExposure; + + // tonemapped and gamma corrected render ready for post + LLRenderTarget mPostMap; + + LLCullResult mSky; + LLCullResult mReflectedObjects; + LLCullResult mRefractedObjects; + + //utility buffers for rendering post effects + LLPointer mDeferredVB; + + // a single triangle that covers the whole screen + LLPointer mScreenTriangleVB; + + //utility buffer for rendering cubes, 8 vertices are corners of a cube [-1, 1] + LLPointer mCubeVB; + + //list of currently bound reflection maps + std::vector mReflectionMaps; + + std::vector mShadowFrustPoints[4]; + LLVector4 mShadowError; + LLVector4 mShadowFOV; + LLVector3 mShadowFrustOrigin[4]; + LLCamera mShadowCamera[8]; + LLVector3 mShadowExtents[4][2]; + // TODO : separate Sun Shadow and Spot Shadow matrices + glh::matrix4f mSunShadowMatrix[6]; + glh::matrix4f mShadowModelview[6]; + glh::matrix4f mShadowProjection[6]; + glh::matrix4f mReflectionModelView; + + LLPointer mShadowSpotLight[2]; + F32 mSpotLightFade[2]; + LLPointer mTargetShadowSpotLight[2]; + + LLVector4 mSunClipPlanes; + LLVector4 mSunOrthoClipPlanes; + LLVector2 mScreenScale; + + //water distortion texture (refraction) + LLRenderTarget mWaterDis; + + LLRenderTarget mBake; + + //texture for making the glow + LLRenderTarget mGlow[3]; + + //noise map + U32 mNoiseMap; + U32 mTrueNoiseMap; + U32 mLightFunc; + + LLColor4 mSunDiffuse; + LLColor4 mMoonDiffuse; + LLVector4 mSunDir; + LLVector4 mMoonDir; + bool mNeedsShadowTargetClear; + + LLVector4 mTransformedSunDir; + LLVector4 mTransformedMoonDir; + + bool mInitialized; + bool mShadersLoaded; + + U32 mTransformFeedbackPrimitives; //number of primitives expected to be generated by transform feedback +protected: + bool mRenderTypeEnabled[NUM_RENDER_TYPES]; + std::stack mRenderTypeEnableStack; + + U32 mRenderDebugFeatureMask; + U64 mRenderDebugMask; + U64 mOldRenderDebugMask; + std::stack mRenderDebugFeatureStack; + + ///////////////////////////////////////////// + // + // + LLDrawable::drawable_vector_t mMovedList; + LLDrawable::drawable_vector_t mMovedBridge; + LLDrawable::drawable_vector_t mShiftList; + + ///////////////////////////////////////////// + // + // + struct Light + { + Light(LLDrawable* ptr, F32 d, F32 f = 0.0f) + : drawable(ptr), + dist(d), + fade(f) + {} + LLPointer drawable; + F32 dist; + F32 fade; + struct compare + { + bool operator()(const Light& a, const Light& b) const + { + if ( a.dist < b.dist ) + return true; + else if ( a.dist > b.dist ) + return false; + else + return a.drawable < b.drawable; + } + }; + }; + typedef std::set< Light, Light::compare > light_set_t; + + LLDrawable::ordered_drawable_set_t mLights; + light_set_t mNearbyLights; // lights near camera + LLColor4 mHWLightColors[8]; + + ///////////////////////////////////////////// + // + // Different queues of drawables being processed. + // + LLDrawable::drawable_list_t mBuildQ1; // priority + LLSpatialGroup::sg_vector_t mGroupQ1; //priority + + LLSpatialGroup::sg_vector_t mGroupSaveQ1; // a place to save mGroupQ1 until it is safe to unref + + LLSpatialGroup::sg_vector_t mMeshDirtyGroup; //groups that need rebuildMesh called + U32 mMeshDirtyQueryObject; + + LLDrawable::drawable_list_t mPartitionQ; //drawables that need to update their spatial partition radius + + bool mGroupQ1Locked; + + bool mResetVertexBuffers; //if true, clear vertex buffers on next update + + LLViewerObject::vobj_list_t mCreateQ; + + LLDrawable::drawable_set_t mRetexturedList; + + class HighlightItem + { + public: + const LLPointer mItem; + mutable F32 mFade; + + HighlightItem(LLDrawable* item) + : mItem(item), mFade(0) + { + } + + bool operator<(const HighlightItem& rhs) const + { + return mItem < rhs.mItem; + } + + bool operator==(const HighlightItem& rhs) const + { + return mItem == rhs.mItem; + } + + void incrFade(F32 val) const + { + mFade = llclamp(mFade+val, 0.f, 1.f); + } + }; + + ////////////////////////////////////////////////// + // + // Draw pools are responsible for storing all rendered data, + // and performing the actual rendering of objects. + // + struct compare_pools + { + bool operator()(const LLDrawPool* a, const LLDrawPool* b) const + { + if (!a) + return true; + else if (!b) + return false; + else + { + S32 atype = a->getType(); + S32 btype = b->getType(); + if (atype < btype) + return true; + else if (atype > btype) + return false; + else + return a->getId() < b->getId(); + } + } + }; + typedef std::set pool_set_t; + pool_set_t mPools; + LLDrawPool* mLastRebuildPool; + + // For quick-lookups into mPools (mapped by texture pointer) + std::map mTerrainPools; + std::map mTreePools; + LLDrawPoolAlpha* mAlphaPoolPreWater = nullptr; + LLDrawPoolAlpha* mAlphaPoolPostWater = nullptr; + LLDrawPool* mSkyPool = nullptr; + LLDrawPool* mTerrainPool = nullptr; + LLDrawPool* mWaterPool = nullptr; + LLRenderPass* mSimplePool = nullptr; + LLRenderPass* mGrassPool = nullptr; + LLRenderPass* mAlphaMaskPool = nullptr; + LLRenderPass* mFullbrightAlphaMaskPool = nullptr; + LLRenderPass* mFullbrightPool = nullptr; + LLDrawPool* mGlowPool = nullptr; + LLDrawPool* mBumpPool = nullptr; + LLDrawPool* mMaterialsPool = nullptr; + LLDrawPool* mWLSkyPool = nullptr; + LLDrawPool* mPBROpaquePool = nullptr; + LLDrawPool* mPBRAlphaMaskPool = nullptr; + + // Note: no need to keep an quick-lookup to avatar pools, since there's only one per avatar + +public: + std::vector mHighlightFaces; // highlight faces on physical objects +protected: + std::vector mSelectedFaces; + + class DebugBlip + { + public: + LLColor4 mColor; + LLVector3 mPosition; + F32 mAge; + + DebugBlip(const LLVector3& position, const LLColor4& color) + : mColor(color), mPosition(position), mAge(0.f) + { } + }; + + std::list mDebugBlips; + + LLPointer mFaceSelectImagep; + + U32 mLightMask; + U32 mLightMovingMask; + + static bool sRenderPhysicalBeacons; + static bool sRenderMOAPBeacons; + static bool sRenderScriptedTouchBeacons; + static bool sRenderScriptedBeacons; + static bool sRenderParticleBeacons; + static bool sRenderSoundBeacons; +public: + static bool sRenderBeacons; + static bool sRenderHighlight; + + // Determines which set of UVs to use in highlight display + // + static LLRender::eTexIndex sRenderHighlightTextureChannel; + + //debug use + static U32 sCurRenderPoolType ; + + //cached settings + static bool WindLightUseAtmosShaders; + static bool RenderDeferred; + static F32 RenderDeferredSunWash; + static U32 RenderFSAASamples; + static U32 RenderResolutionDivisor; + static bool RenderUIBuffer; + static S32 RenderShadowDetail; + static S32 RenderShadowSplits; + static bool RenderDeferredSSAO; + static F32 RenderShadowResolutionScale; + static bool RenderDelayCreation; + static bool RenderAnimateRes; + static bool FreezeTime; + static S32 DebugBeaconLineWidth; + static F32 RenderHighlightBrightness; + static LLColor4 RenderHighlightColor; + static F32 RenderHighlightThickness; + static bool RenderSpotLightsInNondeferred; + static LLColor4 PreviewAmbientColor; + static LLColor4 PreviewDiffuse0; + static LLColor4 PreviewSpecular0; + static LLColor4 PreviewDiffuse1; + static LLColor4 PreviewSpecular1; + static LLColor4 PreviewDiffuse2; + static LLColor4 PreviewSpecular2; + static LLVector3 PreviewDirection0; + static LLVector3 PreviewDirection1; + static LLVector3 PreviewDirection2; + static F32 RenderGlowMinLuminance; + static F32 RenderGlowMaxExtractAlpha; + static F32 RenderGlowWarmthAmount; + static LLVector3 RenderGlowLumWeights; + static LLVector3 RenderGlowWarmthWeights; + static S32 RenderGlowResolutionPow; + static S32 RenderGlowIterations; + static F32 RenderGlowWidth; + static F32 RenderGlowStrength; + static bool RenderGlowNoise; + static bool RenderDepthOfField; + static bool RenderDepthOfFieldInEditMode; + static F32 CameraFocusTransitionTime; + static F32 CameraFNumber; + static F32 CameraFocalLength; + static F32 CameraFieldOfView; + static F32 RenderShadowNoise; + static F32 RenderShadowBlurSize; + static F32 RenderSSAOScale; + static U32 RenderSSAOMaxScale; + static F32 RenderSSAOFactor; + static LLVector3 RenderSSAOEffect; + static F32 RenderShadowOffsetError; + static F32 RenderShadowBiasError; + static F32 RenderShadowOffset; + static F32 RenderShadowBias; + static F32 RenderSpotShadowOffset; + static F32 RenderSpotShadowBias; + static LLDrawable* RenderSpotLight; + static F32 RenderEdgeDepthCutoff; + static F32 RenderEdgeNormCutoff; + static LLVector3 RenderShadowGaussian; + static F32 RenderShadowBlurDistFactor; + static bool RenderDeferredAtmospheric; + static F32 RenderHighlightFadeTime; + static F32 RenderFarClip; + static LLVector3 RenderShadowSplitExponent; + static F32 RenderShadowErrorCutoff; + static F32 RenderShadowFOVCutoff; + static bool CameraOffset; + static F32 CameraMaxCoF; + static F32 CameraDoFResScale; + static F32 RenderAutoHideSurfaceAreaLimit; + static bool RenderScreenSpaceReflections; + static S32 RenderScreenSpaceReflectionIterations; + static F32 RenderScreenSpaceReflectionRayStep; + static F32 RenderScreenSpaceReflectionDistanceBias; + static F32 RenderScreenSpaceReflectionDepthRejectBias; + static F32 RenderScreenSpaceReflectionAdaptiveStepMultiplier; + static S32 RenderScreenSpaceReflectionGlossySamples; + static S32 RenderBufferVisualization; +}; + +void render_bbox(const LLVector3 &min, const LLVector3 &max); +void render_hud_elements(); + +extern LLPipeline gPipeline; +extern bool gDebugPipeline; +extern const LLMatrix4* gGLLastMatrix; + +#endif diff --git a/indra/newview/tests/llagentaccess_test.cpp b/indra/newview/tests/llagentaccess_test.cpp index 2c6d8b9724..6f5b3a9721 100644 --- a/indra/newview/tests/llagentaccess_test.cpp +++ b/indra/newview/tests/llagentaccess_test.cpp @@ -1,291 +1,291 @@ -/** - * @file llagentaccess_test.cpp - * @brief LLAgentAccess tests - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" -#include "../test/lltut.h" - -#include "../llagentaccess.h" - -#include "llcontrol.h" -#include "indra_constants.h" - -#include - -//---------------------------------------------------------------------------- -// Implementation of enough of LLControlGroup to support the tests: - -static U32 test_preferred_maturity = SIM_ACCESS_PG; - -LLControlGroup::LLControlGroup(const std::string& name) -: LLInstanceTracker(name) -{ -} - -LLControlGroup::~LLControlGroup() -{ -} - -// Implementation of just the LLControlGroup methods we requre -LLControlVariable* LLControlGroup::declareU32(const std::string& name, U32 initial_val, const std::string& comment, LLControlVariable::ePersist persist) -{ - test_preferred_maturity = initial_val; - return NULL; -} - -void LLControlGroup::setU32(std::string_view name, U32 val) -{ - test_preferred_maturity = val; -} - -U32 LLControlGroup::getU32(std::string_view name) -{ - return test_preferred_maturity; -} -//---------------------------------------------------------------------------- - -namespace tut -{ - struct agentaccess - { - }; - - typedef test_group agentaccess_t; - typedef agentaccess_t::object agentaccess_object_t; - tut::agentaccess_t tut_agentaccess("LLAgentAccess"); - - template<> template<> - void agentaccess_object_t::test<1>() - { - LLControlGroup cgr("test"); - cgr.declareU32("PreferredMaturity", SIM_ACCESS_PG, "declared_for_test", LLControlVariable::PERSIST_NO); - LLAgentAccess aa(cgr); - - cgr.setU32("PreferredMaturity", SIM_ACCESS_PG); -#ifndef HACKED_GODLIKE_VIEWER - ensure("1 prefersPG", aa.prefersPG()); - ensure("1 prefersMature", !aa.prefersMature()); - ensure("1 prefersAdult", !aa.prefersAdult()); -#endif // HACKED_GODLIKE_VIEWER - - cgr.setU32("PreferredMaturity", SIM_ACCESS_MATURE); -#ifndef HACKED_GODLIKE_VIEWER - ensure("2 prefersPG", !aa.prefersPG()); - ensure("2 prefersMature", aa.prefersMature()); - ensure("2 prefersAdult", !aa.prefersAdult()); -#endif // HACKED_GODLIKE_VIEWER - - cgr.setU32("PreferredMaturity", SIM_ACCESS_ADULT); -#ifndef HACKED_GODLIKE_VIEWER - ensure("3 prefersPG", !aa.prefersPG()); - ensure("3 prefersMature", aa.prefersMature()); - ensure("3 prefersAdult", aa.prefersAdult()); -#endif // HACKED_GODLIKE_VIEWER - } - - template<> template<> - void agentaccess_object_t::test<2>() - { - LLControlGroup cgr("test"); - cgr.declareU32("PreferredMaturity", SIM_ACCESS_PG, "declared_for_test", LLControlVariable::PERSIST_NO); - LLAgentAccess aa(cgr); - - // make sure default is PG -#ifndef HACKED_GODLIKE_VIEWER - ensure("1 isTeen", aa.isTeen()); - ensure("1 isMature", !aa.isMature()); - ensure("1 isAdult", !aa.isAdult()); -#endif // HACKED_GODLIKE_VIEWER - - // check the conversion routine -#ifndef HACKED_GODLIKE_VIEWER - ensure_equals("1 conversion", SIM_ACCESS_PG, aa.convertTextToMaturity('P')); - ensure_equals("2 conversion", SIM_ACCESS_MATURE, aa.convertTextToMaturity('M')); - ensure_equals("3 conversion", SIM_ACCESS_ADULT, aa.convertTextToMaturity('A')); - ensure_equals("4 conversion", SIM_ACCESS_MIN, aa.convertTextToMaturity('Q')); -#endif // HACKED_GODLIKE_VIEWER - - // now try the other method of setting it - PG - aa.setMaturity('P'); - ensure("2 isTeen", aa.isTeen()); -#ifndef HACKED_GODLIKE_VIEWER - ensure("2 isMature", !aa.isMature()); - ensure("2 isAdult", !aa.isAdult()); -#endif // HACKED_GODLIKE_VIEWER - - // Mature - aa.setMaturity('M'); -#ifndef HACKED_GODLIKE_VIEWER - ensure("3 isTeen", !aa.isTeen()); - ensure("3 isMature", aa.isMature()); - ensure("3 isAdult", !aa.isAdult()); -#endif // HACKED_GODLIKE_VIEWER - - // Adult - aa.setMaturity('A'); -#ifndef HACKED_GODLIKE_VIEWER - ensure("4 isTeen", !aa.isTeen()); - ensure("4 isMature", aa.isMature()); - ensure("4 isAdult", aa.isAdult()); -#endif // HACKED_GODLIKE_VIEWER - - } - - template<> template<> - void agentaccess_object_t::test<3>() - { - LLControlGroup cgr("test"); - cgr.declareU32("PreferredMaturity", SIM_ACCESS_PG, "declared_for_test", LLControlVariable::PERSIST_NO); - LLAgentAccess aa(cgr); - -#ifndef HACKED_GODLIKE_VIEWER - ensure("starts normal", !aa.isGodlike()); -#endif // HACKED_GODLIKE_VIEWER - aa.setGodLevel(GOD_NOT); -#ifndef HACKED_GODLIKE_VIEWER - ensure("stays normal", !aa.isGodlike()); -#endif // HACKED_GODLIKE_VIEWER - aa.setGodLevel(GOD_FULL); -#ifndef HACKED_GODLIKE_VIEWER - ensure("sets full", aa.isGodlike()); -#endif // HACKED_GODLIKE_VIEWER - aa.setGodLevel(GOD_NOT); -#ifndef HACKED_GODLIKE_VIEWER - ensure("resets normal", !aa.isGodlike()); -#endif // HACKED_GODLIKE_VIEWER - aa.setAdminOverride(true); -#ifndef HACKED_GODLIKE_VIEWER - ensure("admin true", aa.getAdminOverride()); - ensure("overrides 1", aa.isGodlike()); -#endif // HACKED_GODLIKE_VIEWER - aa.setGodLevel(GOD_FULL); -#ifndef HACKED_GODLIKE_VIEWER - ensure("overrides 2", aa.isGodlike()); -#endif // HACKED_GODLIKE_VIEWER - aa.setAdminOverride(false); -#ifndef HACKED_GODLIKE_VIEWER - ensure("admin false", !aa.getAdminOverride()); - ensure("overrides 3", aa.isGodlike()); -#endif // HACKED_GODLIKE_VIEWER - } - - template<> template<> - void agentaccess_object_t::test<4>() - { - LLControlGroup cgr("test"); - cgr.declareU32("PreferredMaturity", SIM_ACCESS_PG, "declared_for_test", LLControlVariable::PERSIST_NO); - LLAgentAccess aa(cgr); - -#ifndef HACKED_GODLIKE_VIEWER - ensure("1 pg to start", aa.wantsPGOnly()); - ensure("2 pg to start", !aa.canAccessMature()); - ensure("3 pg to start", !aa.canAccessAdult()); -#endif // HACKED_GODLIKE_VIEWER - - aa.setGodLevel(GOD_FULL); -#ifndef HACKED_GODLIKE_VIEWER - ensure("1 full god", !aa.wantsPGOnly()); - ensure("2 full god", aa.canAccessMature()); - ensure("3 full god", aa.canAccessAdult()); -#endif // HACKED_GODLIKE_VIEWER - - aa.setGodLevel(GOD_NOT); - aa.setAdminOverride(true); -#ifndef HACKED_GODLIKE_VIEWER - ensure("1 admin mode", !aa.wantsPGOnly()); - ensure("2 admin mode", aa.canAccessMature()); - ensure("3 admin mode", aa.canAccessAdult()); -#endif // HACKED_GODLIKE_VIEWER - - aa.setAdminOverride(false); - aa.setMaturity('M'); - // preferred is still pg by default -#ifndef HACKED_GODLIKE_VIEWER - ensure("1 mature pref pg", aa.wantsPGOnly()); - ensure("2 mature pref pg", !aa.canAccessMature()); - ensure("3 mature pref pg", !aa.canAccessAdult()); -#endif // HACKED_GODLIKE_VIEWER - - cgr.setU32("PreferredMaturity", SIM_ACCESS_MATURE); -#ifndef HACKED_GODLIKE_VIEWER - ensure("1 mature", !aa.wantsPGOnly()); - ensure("2 mature", aa.canAccessMature()); - ensure("3 mature", !aa.canAccessAdult()); -#endif // HACKED_GODLIKE_VIEWER - - cgr.setU32("PreferredMaturity", SIM_ACCESS_PG); -#ifndef HACKED_GODLIKE_VIEWER - ensure("1 mature pref pg", aa.wantsPGOnly()); - ensure("2 mature pref pg", !aa.canAccessMature()); - ensure("3 mature pref pg", !aa.canAccessAdult()); -#endif // HACKED_GODLIKE_VIEWER - - aa.setMaturity('A'); -#ifndef HACKED_GODLIKE_VIEWER - ensure("1 adult pref pg", aa.wantsPGOnly()); - ensure("2 adult pref pg", !aa.canAccessMature()); - ensure("3 adult pref pg", !aa.canAccessAdult()); -#endif // HACKED_GODLIKE_VIEWER - - cgr.setU32("PreferredMaturity", SIM_ACCESS_ADULT); -#ifndef HACKED_GODLIKE_VIEWER - ensure("1 adult", !aa.wantsPGOnly()); - ensure("2 adult", aa.canAccessMature()); - ensure("3 adult", aa.canAccessAdult()); -#endif // HACKED_GODLIKE_VIEWER - - // make sure that even if pref is high, if access is low we block access - // this shouldn't occur in real life but we want to be safe - cgr.setU32("PreferredMaturity", SIM_ACCESS_ADULT); - aa.setMaturity('P'); -#ifndef HACKED_GODLIKE_VIEWER - ensure("1 pref adult, actual pg", aa.wantsPGOnly()); - ensure("2 pref adult, actual pg", !aa.canAccessMature()); - ensure("3 pref adult, actual pg", !aa.canAccessAdult()); -#endif // HACKED_GODLIKE_VIEWER - - } - - template<> template<> - void agentaccess_object_t::test<5>() - { - LLControlGroup cgr("test"); - cgr.declareU32("PreferredMaturity", SIM_ACCESS_PG, "declared_for_test", LLControlVariable::PERSIST_NO); - LLAgentAccess aa(cgr); - - cgr.setU32("PreferredMaturity", SIM_ACCESS_ADULT); - aa.setMaturity('M'); -#ifndef HACKED_GODLIKE_VIEWER - ensure("1 preferred maturity pegged to M when maturity is M", cgr.getU32("PreferredMaturity") == SIM_ACCESS_MATURE); -#endif // HACKED_GODLIKE_VIEWER - - aa.setMaturity('P'); -#ifndef HACKED_GODLIKE_VIEWER - ensure("1 preferred maturity pegged to P when maturity is P", cgr.getU32("PreferredMaturity") == SIM_ACCESS_PG); -#endif // HACKED_GODLIKE_VIEWER - } -} - - +/** + * @file llagentaccess_test.cpp + * @brief LLAgentAccess tests + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "../test/lltut.h" + +#include "../llagentaccess.h" + +#include "llcontrol.h" +#include "indra_constants.h" + +#include + +//---------------------------------------------------------------------------- +// Implementation of enough of LLControlGroup to support the tests: + +static U32 test_preferred_maturity = SIM_ACCESS_PG; + +LLControlGroup::LLControlGroup(const std::string& name) +: LLInstanceTracker(name) +{ +} + +LLControlGroup::~LLControlGroup() +{ +} + +// Implementation of just the LLControlGroup methods we requre +LLControlVariable* LLControlGroup::declareU32(const std::string& name, U32 initial_val, const std::string& comment, LLControlVariable::ePersist persist) +{ + test_preferred_maturity = initial_val; + return NULL; +} + +void LLControlGroup::setU32(std::string_view name, U32 val) +{ + test_preferred_maturity = val; +} + +U32 LLControlGroup::getU32(std::string_view name) +{ + return test_preferred_maturity; +} +//---------------------------------------------------------------------------- + +namespace tut +{ + struct agentaccess + { + }; + + typedef test_group agentaccess_t; + typedef agentaccess_t::object agentaccess_object_t; + tut::agentaccess_t tut_agentaccess("LLAgentAccess"); + + template<> template<> + void agentaccess_object_t::test<1>() + { + LLControlGroup cgr("test"); + cgr.declareU32("PreferredMaturity", SIM_ACCESS_PG, "declared_for_test", LLControlVariable::PERSIST_NO); + LLAgentAccess aa(cgr); + + cgr.setU32("PreferredMaturity", SIM_ACCESS_PG); +#ifndef HACKED_GODLIKE_VIEWER + ensure("1 prefersPG", aa.prefersPG()); + ensure("1 prefersMature", !aa.prefersMature()); + ensure("1 prefersAdult", !aa.prefersAdult()); +#endif // HACKED_GODLIKE_VIEWER + + cgr.setU32("PreferredMaturity", SIM_ACCESS_MATURE); +#ifndef HACKED_GODLIKE_VIEWER + ensure("2 prefersPG", !aa.prefersPG()); + ensure("2 prefersMature", aa.prefersMature()); + ensure("2 prefersAdult", !aa.prefersAdult()); +#endif // HACKED_GODLIKE_VIEWER + + cgr.setU32("PreferredMaturity", SIM_ACCESS_ADULT); +#ifndef HACKED_GODLIKE_VIEWER + ensure("3 prefersPG", !aa.prefersPG()); + ensure("3 prefersMature", aa.prefersMature()); + ensure("3 prefersAdult", aa.prefersAdult()); +#endif // HACKED_GODLIKE_VIEWER + } + + template<> template<> + void agentaccess_object_t::test<2>() + { + LLControlGroup cgr("test"); + cgr.declareU32("PreferredMaturity", SIM_ACCESS_PG, "declared_for_test", LLControlVariable::PERSIST_NO); + LLAgentAccess aa(cgr); + + // make sure default is PG +#ifndef HACKED_GODLIKE_VIEWER + ensure("1 isTeen", aa.isTeen()); + ensure("1 isMature", !aa.isMature()); + ensure("1 isAdult", !aa.isAdult()); +#endif // HACKED_GODLIKE_VIEWER + + // check the conversion routine +#ifndef HACKED_GODLIKE_VIEWER + ensure_equals("1 conversion", SIM_ACCESS_PG, aa.convertTextToMaturity('P')); + ensure_equals("2 conversion", SIM_ACCESS_MATURE, aa.convertTextToMaturity('M')); + ensure_equals("3 conversion", SIM_ACCESS_ADULT, aa.convertTextToMaturity('A')); + ensure_equals("4 conversion", SIM_ACCESS_MIN, aa.convertTextToMaturity('Q')); +#endif // HACKED_GODLIKE_VIEWER + + // now try the other method of setting it - PG + aa.setMaturity('P'); + ensure("2 isTeen", aa.isTeen()); +#ifndef HACKED_GODLIKE_VIEWER + ensure("2 isMature", !aa.isMature()); + ensure("2 isAdult", !aa.isAdult()); +#endif // HACKED_GODLIKE_VIEWER + + // Mature + aa.setMaturity('M'); +#ifndef HACKED_GODLIKE_VIEWER + ensure("3 isTeen", !aa.isTeen()); + ensure("3 isMature", aa.isMature()); + ensure("3 isAdult", !aa.isAdult()); +#endif // HACKED_GODLIKE_VIEWER + + // Adult + aa.setMaturity('A'); +#ifndef HACKED_GODLIKE_VIEWER + ensure("4 isTeen", !aa.isTeen()); + ensure("4 isMature", aa.isMature()); + ensure("4 isAdult", aa.isAdult()); +#endif // HACKED_GODLIKE_VIEWER + + } + + template<> template<> + void agentaccess_object_t::test<3>() + { + LLControlGroup cgr("test"); + cgr.declareU32("PreferredMaturity", SIM_ACCESS_PG, "declared_for_test", LLControlVariable::PERSIST_NO); + LLAgentAccess aa(cgr); + +#ifndef HACKED_GODLIKE_VIEWER + ensure("starts normal", !aa.isGodlike()); +#endif // HACKED_GODLIKE_VIEWER + aa.setGodLevel(GOD_NOT); +#ifndef HACKED_GODLIKE_VIEWER + ensure("stays normal", !aa.isGodlike()); +#endif // HACKED_GODLIKE_VIEWER + aa.setGodLevel(GOD_FULL); +#ifndef HACKED_GODLIKE_VIEWER + ensure("sets full", aa.isGodlike()); +#endif // HACKED_GODLIKE_VIEWER + aa.setGodLevel(GOD_NOT); +#ifndef HACKED_GODLIKE_VIEWER + ensure("resets normal", !aa.isGodlike()); +#endif // HACKED_GODLIKE_VIEWER + aa.setAdminOverride(true); +#ifndef HACKED_GODLIKE_VIEWER + ensure("admin true", aa.getAdminOverride()); + ensure("overrides 1", aa.isGodlike()); +#endif // HACKED_GODLIKE_VIEWER + aa.setGodLevel(GOD_FULL); +#ifndef HACKED_GODLIKE_VIEWER + ensure("overrides 2", aa.isGodlike()); +#endif // HACKED_GODLIKE_VIEWER + aa.setAdminOverride(false); +#ifndef HACKED_GODLIKE_VIEWER + ensure("admin false", !aa.getAdminOverride()); + ensure("overrides 3", aa.isGodlike()); +#endif // HACKED_GODLIKE_VIEWER + } + + template<> template<> + void agentaccess_object_t::test<4>() + { + LLControlGroup cgr("test"); + cgr.declareU32("PreferredMaturity", SIM_ACCESS_PG, "declared_for_test", LLControlVariable::PERSIST_NO); + LLAgentAccess aa(cgr); + +#ifndef HACKED_GODLIKE_VIEWER + ensure("1 pg to start", aa.wantsPGOnly()); + ensure("2 pg to start", !aa.canAccessMature()); + ensure("3 pg to start", !aa.canAccessAdult()); +#endif // HACKED_GODLIKE_VIEWER + + aa.setGodLevel(GOD_FULL); +#ifndef HACKED_GODLIKE_VIEWER + ensure("1 full god", !aa.wantsPGOnly()); + ensure("2 full god", aa.canAccessMature()); + ensure("3 full god", aa.canAccessAdult()); +#endif // HACKED_GODLIKE_VIEWER + + aa.setGodLevel(GOD_NOT); + aa.setAdminOverride(true); +#ifndef HACKED_GODLIKE_VIEWER + ensure("1 admin mode", !aa.wantsPGOnly()); + ensure("2 admin mode", aa.canAccessMature()); + ensure("3 admin mode", aa.canAccessAdult()); +#endif // HACKED_GODLIKE_VIEWER + + aa.setAdminOverride(false); + aa.setMaturity('M'); + // preferred is still pg by default +#ifndef HACKED_GODLIKE_VIEWER + ensure("1 mature pref pg", aa.wantsPGOnly()); + ensure("2 mature pref pg", !aa.canAccessMature()); + ensure("3 mature pref pg", !aa.canAccessAdult()); +#endif // HACKED_GODLIKE_VIEWER + + cgr.setU32("PreferredMaturity", SIM_ACCESS_MATURE); +#ifndef HACKED_GODLIKE_VIEWER + ensure("1 mature", !aa.wantsPGOnly()); + ensure("2 mature", aa.canAccessMature()); + ensure("3 mature", !aa.canAccessAdult()); +#endif // HACKED_GODLIKE_VIEWER + + cgr.setU32("PreferredMaturity", SIM_ACCESS_PG); +#ifndef HACKED_GODLIKE_VIEWER + ensure("1 mature pref pg", aa.wantsPGOnly()); + ensure("2 mature pref pg", !aa.canAccessMature()); + ensure("3 mature pref pg", !aa.canAccessAdult()); +#endif // HACKED_GODLIKE_VIEWER + + aa.setMaturity('A'); +#ifndef HACKED_GODLIKE_VIEWER + ensure("1 adult pref pg", aa.wantsPGOnly()); + ensure("2 adult pref pg", !aa.canAccessMature()); + ensure("3 adult pref pg", !aa.canAccessAdult()); +#endif // HACKED_GODLIKE_VIEWER + + cgr.setU32("PreferredMaturity", SIM_ACCESS_ADULT); +#ifndef HACKED_GODLIKE_VIEWER + ensure("1 adult", !aa.wantsPGOnly()); + ensure("2 adult", aa.canAccessMature()); + ensure("3 adult", aa.canAccessAdult()); +#endif // HACKED_GODLIKE_VIEWER + + // make sure that even if pref is high, if access is low we block access + // this shouldn't occur in real life but we want to be safe + cgr.setU32("PreferredMaturity", SIM_ACCESS_ADULT); + aa.setMaturity('P'); +#ifndef HACKED_GODLIKE_VIEWER + ensure("1 pref adult, actual pg", aa.wantsPGOnly()); + ensure("2 pref adult, actual pg", !aa.canAccessMature()); + ensure("3 pref adult, actual pg", !aa.canAccessAdult()); +#endif // HACKED_GODLIKE_VIEWER + + } + + template<> template<> + void agentaccess_object_t::test<5>() + { + LLControlGroup cgr("test"); + cgr.declareU32("PreferredMaturity", SIM_ACCESS_PG, "declared_for_test", LLControlVariable::PERSIST_NO); + LLAgentAccess aa(cgr); + + cgr.setU32("PreferredMaturity", SIM_ACCESS_ADULT); + aa.setMaturity('M'); +#ifndef HACKED_GODLIKE_VIEWER + ensure("1 preferred maturity pegged to M when maturity is M", cgr.getU32("PreferredMaturity") == SIM_ACCESS_MATURE); +#endif // HACKED_GODLIKE_VIEWER + + aa.setMaturity('P'); +#ifndef HACKED_GODLIKE_VIEWER + ensure("1 preferred maturity pegged to P when maturity is P", cgr.getU32("PreferredMaturity") == SIM_ACCESS_PG); +#endif // HACKED_GODLIKE_VIEWER + } +} + + diff --git a/indra/newview/tests/lllogininstance_test.cpp b/indra/newview/tests/lllogininstance_test.cpp index a7184534c5..df0f006d02 100644 --- a/indra/newview/tests/lllogininstance_test.cpp +++ b/indra/newview/tests/lllogininstance_test.cpp @@ -1,489 +1,489 @@ -/** - * @file lllogininstance_test.cpp - * @brief Test for lllogininstance.cpp. - * - * $LicenseInfo:firstyear=2008&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -// Precompiled header -#include "../llviewerprecompiledheaders.h" -// Own header -#include "../llsecapi.h" -#include "../llviewernetwork.h" -#include "../lllogininstance.h" - - // Needed for Auth Test - #include "../llhasheduniqueid.h" - -// STL headers -// std headers -// external library headers -// other Linden headers -#include "../test/lltut.h" -#include "llevents.h" -#include "llnotificationsutil.h" -#include "lltrans.h" - -#if defined(LL_WINDOWS) -#pragma warning(disable: 4355) // using 'this' in base-class ctor initializer expr -#pragma warning(disable: 4702) // disable 'unreachable code' so we can safely use skip(). -#endif - -// Constants -const std::string VIEWERLOGIN_URI("viewerlogin_uri"); -const std::string VIEWERLOGIN_GRIDLABEL("viewerlogin_grid"); - -const std::string APPVIEWER_SERIALNUMBER("appviewer_serialno"); - -const std::string VIEWERLOGIN_CHANNEL("invalid_channel"); -const std::string VIEWERLOGIN_VERSION("invalid_version"); - -// Link seams. - -//----------------------------------------------------------------------------- -static LLEventStream gTestPump("test_pump"); - -#include "../llslurl.h" -#include "../llstartup.h" -LLSLURL LLStartUp::sStartSLURL; -LLSLURL& LLStartUp::getStartSLURL() { return sStartSLURL; } - -#include "lllogin.h" - -static std::string gLoginURI; -static LLSD gLoginCreds; -static bool gDisconnectCalled = false; - -#include "../llviewerwindow.h" -void LLViewerWindow::setShowProgress(bool show) {} -LLProgressView * LLViewerWindow::getProgressView(void) const { return 0; } - -LLViewerWindow* gViewerWindow; - -std::string LLTrans::getString(const std::string &xml_desc, const LLStringUtil::format_map_t& args, bool def_string) -{ - return std::string("test_trans"); -} - -class LLLogin::Impl -{ -}; -LLLogin::LLLogin() {} -LLLogin::~LLLogin() {} -LLEventPump& LLLogin::getEventPump() { return gTestPump; } -void LLLogin::connect(const std::string& uri, const LLSD& credentials) -{ - gLoginURI = uri; - gLoginCreds = credentials; -} - -void LLLogin::disconnect() -{ - gDisconnectCalled = true; -} - -LLSD LLCredential::getLoginParams() -{ - LLSD result = LLSD::emptyMap(); - - // legacy credential - result["passwd"] = "$1$testpasssd"; - result["first"] = "myfirst"; - result["last"] ="mylast"; - return result; -} -void LLCredential::identifierType(std::string &idType) -{ -} - -void LLCredential::authenticatorType(std::string &idType) -{ -} - -LLNotificationPtr LLNotificationsUtil::add(const std::string& name, - const LLSD& substitutions, - const LLSD& payload, - boost::function functor) -{ - return LLNotificationPtr((LLNotification*)NULL); -} - -LLNotificationPtr LLNotificationsUtil::add(const std::string& name, const LLSD& args) -{ - return LLNotificationPtr((LLNotification*)NULL); -} - -//----------------------------------------------------------------------------- -#include "../llviewernetwork.h" -LLGridManager::~LLGridManager() -{ -} - -bool LLGridManager::addGrid(LLSD& grid_data) -{ - return true; -} -LLGridManager::LLGridManager() -: - mIsInProductionGrid(false) -{ -} - -void LLGridManager::getLoginURIs(std::vector& uris) -{ - uris.push_back(VIEWERLOGIN_URI); -} - -void LLGridManager::addSystemGrid(const std::string& label, - const std::string& name, - const std::string& login, - const std::string& helper, - const std::string& login_page, - const std::string& update_url_base, - const std::string& web_profile_url, - const std::string& login_id) -{ -} -std::map LLGridManager::getKnownGrids() -{ - std::map result; - return result; -} - -void LLGridManager::setGridChoice(const std::string& grid_name) -{ -} - -bool LLGridManager::isInProductionGrid() -{ - return false; -} - -std::string LLGridManager::getSLURLBase(const std::string& grid_name) -{ - return "myslurl"; -} -std::string LLGridManager::getAppSLURLBase(const std::string& grid_name) -{ - return "myappslurl"; -} -std::string LLGridManager::getGridId(const std::string& grid) -{ - return std::string(); -} - -//----------------------------------------------------------------------------- -#include "../llviewercontrol.h" -LLControlGroup gSavedSettings("Global"); - -LLControlGroup::LLControlGroup(const std::string& name) : - LLInstanceTracker(name){} -LLControlGroup::~LLControlGroup() {} -void LLControlGroup::setBOOL(std::string_view name, bool val) {} -bool LLControlGroup::getBOOL(std::string_view name) { return false; } -F32 LLControlGroup::getF32(std::string_view name) { return 0.0f; } -U32 LLControlGroup::saveToFile(const std::string& filename, bool nondefault_only) { return 1; } -void LLControlGroup::setString(std::string_view name, const std::string& val) {} -std::string LLControlGroup::getString(std::string_view name) { return "test_string"; } -LLControlVariable* LLControlGroup::declareBOOL(const std::string& name, bool initial_val, const std::string& comment, LLControlVariable::ePersist persist) { return NULL; } -LLControlVariable* LLControlGroup::declareString(const std::string& name, const std::string &initial_val, const std::string& comment, LLControlVariable::ePersist persist) { return NULL; } - -#include "lluicolortable.h" -void LLUIColorTable::saveUserSettings(void)const {} - -//----------------------------------------------------------------------------- -#include "../llversioninfo.h" - -bool llHashedUniqueID(unsigned char* id) -{ - memcpy( id, "66666666666666666666666666666666", MD5HEX_STR_SIZE ); - return true; -} - -//----------------------------------------------------------------------------- -#include "../llappviewer.h" -void LLAppViewer::forceQuit(void) {} -bool LLAppViewer::isUpdaterMissing() { return true; } -bool LLAppViewer::waitForUpdater() { return false; } -LLAppViewer * LLAppViewer::sInstance = 0; - -//----------------------------------------------------------------------------- -#include "llnotifications.h" -#include "llfloaterreg.h" -static std::string gTOSType; -static LLEventPump * gTOSReplyPump = NULL; - -LLPointer gSecAPIHandler; - -//static -LLFloater* LLFloaterReg::showInstance(const std::string& name, const LLSD& key, bool focus) -{ - gTOSType = name; - gTOSReplyPump = &LLEventPumps::instance().obtain(key["reply_pump"]); - return NULL; -} - -//---------------------------------------------------------------------------- -#include "../llprogressview.h" -void LLProgressView::setText(std::string const &){} -void LLProgressView::setPercent(float){} -void LLProgressView::setMessage(std::string const &){} - -//----------------------------------------------------------------------------- -// LLNotifications -class MockNotifications : public LLNotificationsInterface -{ - boost::function mResponder; - int mAddedCount; - -public: - MockNotifications() : - mResponder(0), - mAddedCount(0) - { - } - - virtual ~MockNotifications() {} - - /* virtual */ LLNotificationPtr add( - const std::string& name, - const LLSD& substitutions, - const LLSD& payload, - LLNotificationFunctorRegistry::ResponseFunctor functor) - { - mResponder = functor; - mAddedCount++; - return LLNotificationPtr((LLNotification*)NULL); - } - - void sendYesResponse() - { - LLSD notification; - LLSD response; - response = 1; - mResponder(notification, response); - } - - void sendNoResponse() - { - LLSD notification; - LLSD response; - response = 2; - mResponder(notification, response); - } - - void sendBogusResponse() - { - LLSD notification; - LLSD response; - response = 666; - mResponder(notification, response); - } - - int addedCount() { return mAddedCount; } -}; - -S32 LLNotification::getSelectedOption(const LLSD& notification, const LLSD& response) -{ - return response.asInteger(); -} - -//----------------------------------------------------------------------------- -#include "../llmachineid.h" -unsigned char gMACAddress[MAC_ADDRESS_BYTES] = {77,21,46,31,89,2}; - -S32 LLMachineID::getUniqueID(unsigned char *unique_id, size_t len) -{ - memcpy(unique_id, gMACAddress, len); - return 1; -} -S32 LLMachineID::getLegacyID(unsigned char *unique_id, size_t len) -{ - return 0; -} -//----------------------------------------------------------------------------- -// misc -std::string xml_escape_string(const std::string& in) -{ - return in; -} - -/***************************************************************************** -* TUT -*****************************************************************************/ -namespace tut -{ - struct lllogininstance_data - { - lllogininstance_data() : logininstance(LLLoginInstance::getInstance()) - { - // Global initialization - gLoginURI.clear(); - gLoginCreds.clear(); - gDisconnectCalled = false; - - gTOSType = ""; // Set to invalid value. - gTOSReplyPump = 0; // clear the callback. - - - gSavedSettings.declareBOOL("NoInventoryLibrary", false, "", LLControlVariable::PERSIST_NO); - gSavedSettings.declareBOOL("ConnectAsGod", false, "", LLControlVariable::PERSIST_NO); - gSavedSettings.declareBOOL("UseDebugMenus", false, "", LLControlVariable::PERSIST_NO); - gSavedSettings.declareString("ClientSettingsFile", "test_settings.xml", "", LLControlVariable::PERSIST_NO); - gSavedSettings.declareString("NextLoginLocation", "", "", LLControlVariable::PERSIST_NO); - gSavedSettings.declareBOOL("LoginLastLocation", false, "", LLControlVariable::PERSIST_NO); - gSavedSettings.declareBOOL("CmdLineSkipUpdater", true, "", LLControlVariable::PERSIST_NO); - - LLSD authenticator = LLSD::emptyMap(); - LLSD identifier = LLSD::emptyMap(); - identifier["type"] = "agent"; - identifier["first_name"] = "testfirst"; - identifier["last_name"] = "testlast"; - authenticator["passwd"] = "testpass"; - agentCredential = new LLCredential(); - agentCredential->setCredentialData(identifier, authenticator); - - authenticator = LLSD::emptyMap(); - identifier = LLSD::emptyMap(); - identifier["type"] = "account"; - identifier["username"] = "testuser"; - authenticator["secret"] = "testsecret"; - accountCredential = new LLCredential(); - accountCredential->setCredentialData(identifier, authenticator); - - logininstance->setNotificationsInterface(¬ifications); - logininstance->setPlatformInfo("win", "1.3.5", "Windows Bogus Version 100.6.6.6"); - } - - LLLoginInstance* logininstance; - LLPointer agentCredential; - LLPointer accountCredential; - MockNotifications notifications; - }; - - typedef test_group lllogininstance_group; - typedef lllogininstance_group::object lllogininstance_object; - lllogininstance_group llsdmgr("LLLoginInstance"); - - template<> template<> - void lllogininstance_object::test<1>() - { - set_test_name("Test Simple Success And Disconnect"); - - // Test default connect. - logininstance->connect(agentCredential); - - ensure_equals("Default connect uri", gLoginURI, VIEWERLOGIN_URI); - - // Dummy success response. - LLSD response; - response["state"] = "online"; - response["change"] = "connect"; - response["progress"] = 1.0; - response["transfer_rate"] = 7; - response["data"] = "test_data"; - - gTestPump.post(response); - - ensure("Success response", logininstance->authSuccess()); - ensure_equals("Test Response Data", logininstance->getResponse().asString(), "test_data"); - - logininstance->disconnect(); - - ensure_equals("Called Login Module Disconnect", gDisconnectCalled, true); - - response.clear(); - response["state"] = "offline"; - response["change"] = "disconnect"; - response["progress"] = 0.0; - response["transfer_rate"] = 0; - response["data"] = "test_data"; - - gTestPump.post(response); - - ensure("Disconnected", !(logininstance->authSuccess())); - } - - template<> template<> - void lllogininstance_object::test<2>() - { - set_test_name("Test User TOS/Critical message Interaction"); - - const std::string test_uri = "testing-uri"; - - // Test default connect. - logininstance->connect(test_uri, agentCredential); - - // connect should call LLLogin::connect to init gLoginURI and gLoginCreds. - ensure_equals("Default connect uri", gLoginURI, "testing-uri"); - ensure_equals("Default for agree to tos", gLoginCreds["params"]["agree_to_tos"].asBoolean(), false); - ensure_equals("Default for read critical", gLoginCreds["params"]["read_critical"].asBoolean(), false); - - // TOS failure response. - LLSD response; - response["state"] = "offline"; - response["change"] = "fail.login"; - response["progress"] = 0.0; - response["transfer_rate"] = 7; - response["data"]["reason"] = "tos"; - gTestPump.post(response); - - ensure_equals("TOS Dialog type", gTOSType, "message_tos"); - ensure("TOS callback given", gTOSReplyPump != 0); - gTOSReplyPump->post(false); // Call callback denying TOS. - ensure("No TOS, failed auth", logininstance->authFailure()); - - // Start again. - logininstance->connect(test_uri, agentCredential); - gTestPump.post(response); // Fail for tos again. - gTOSReplyPump->post(true); // Accept tos, should reconnect w/ agree_to_tos. - ensure_equals("Accepted agree to tos", gLoginCreds["params"]["agree_to_tos"].asBoolean(), true); - ensure("Incomplete login status", !logininstance->authFailure() && !logininstance->authSuccess()); - - // Fail connection, attempt connect again. - // The new request should have reset agree to tos to default. - response["data"]["reason"] = "key"; // bad creds. - gTestPump.post(response); - ensure("TOS auth failure", logininstance->authFailure()); - - logininstance->connect(test_uri, agentCredential); - ensure_equals("Reset to default for agree to tos", gLoginCreds["params"]["agree_to_tos"].asBoolean(), false); - - // Critical Message failure response. - logininstance->connect(test_uri, agentCredential); - response["data"]["reason"] = "critical"; // Change response to "critical message" - gTestPump.post(response); - - ensure_equals("TOS Dialog type", gTOSType, "message_critical"); - ensure("TOS callback given", gTOSReplyPump != 0); - gTOSReplyPump->post(true); - ensure_equals("Accepted read critical message", gLoginCreds["params"]["read_critical"].asBoolean(), true); - ensure("Incomplete login status", !logininstance->authFailure() && !logininstance->authSuccess()); - - // Fail then attempt new connection - response["data"]["reason"] = "key"; // bad creds. - gTestPump.post(response); - ensure("TOS auth failure", logininstance->authFailure()); - logininstance->connect(test_uri, agentCredential); - ensure_equals("Default for agree to tos", gLoginCreds["params"]["read_critical"].asBoolean(), false); - } -} +/** + * @file lllogininstance_test.cpp + * @brief Test for lllogininstance.cpp. + * + * $LicenseInfo:firstyear=2008&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Precompiled header +#include "../llviewerprecompiledheaders.h" +// Own header +#include "../llsecapi.h" +#include "../llviewernetwork.h" +#include "../lllogininstance.h" + + // Needed for Auth Test + #include "../llhasheduniqueid.h" + +// STL headers +// std headers +// external library headers +// other Linden headers +#include "../test/lltut.h" +#include "llevents.h" +#include "llnotificationsutil.h" +#include "lltrans.h" + +#if defined(LL_WINDOWS) +#pragma warning(disable: 4355) // using 'this' in base-class ctor initializer expr +#pragma warning(disable: 4702) // disable 'unreachable code' so we can safely use skip(). +#endif + +// Constants +const std::string VIEWERLOGIN_URI("viewerlogin_uri"); +const std::string VIEWERLOGIN_GRIDLABEL("viewerlogin_grid"); + +const std::string APPVIEWER_SERIALNUMBER("appviewer_serialno"); + +const std::string VIEWERLOGIN_CHANNEL("invalid_channel"); +const std::string VIEWERLOGIN_VERSION("invalid_version"); + +// Link seams. + +//----------------------------------------------------------------------------- +static LLEventStream gTestPump("test_pump"); + +#include "../llslurl.h" +#include "../llstartup.h" +LLSLURL LLStartUp::sStartSLURL; +LLSLURL& LLStartUp::getStartSLURL() { return sStartSLURL; } + +#include "lllogin.h" + +static std::string gLoginURI; +static LLSD gLoginCreds; +static bool gDisconnectCalled = false; + +#include "../llviewerwindow.h" +void LLViewerWindow::setShowProgress(bool show) {} +LLProgressView * LLViewerWindow::getProgressView(void) const { return 0; } + +LLViewerWindow* gViewerWindow; + +std::string LLTrans::getString(const std::string &xml_desc, const LLStringUtil::format_map_t& args, bool def_string) +{ + return std::string("test_trans"); +} + +class LLLogin::Impl +{ +}; +LLLogin::LLLogin() {} +LLLogin::~LLLogin() {} +LLEventPump& LLLogin::getEventPump() { return gTestPump; } +void LLLogin::connect(const std::string& uri, const LLSD& credentials) +{ + gLoginURI = uri; + gLoginCreds = credentials; +} + +void LLLogin::disconnect() +{ + gDisconnectCalled = true; +} + +LLSD LLCredential::getLoginParams() +{ + LLSD result = LLSD::emptyMap(); + + // legacy credential + result["passwd"] = "$1$testpasssd"; + result["first"] = "myfirst"; + result["last"] ="mylast"; + return result; +} +void LLCredential::identifierType(std::string &idType) +{ +} + +void LLCredential::authenticatorType(std::string &idType) +{ +} + +LLNotificationPtr LLNotificationsUtil::add(const std::string& name, + const LLSD& substitutions, + const LLSD& payload, + boost::function functor) +{ + return LLNotificationPtr((LLNotification*)NULL); +} + +LLNotificationPtr LLNotificationsUtil::add(const std::string& name, const LLSD& args) +{ + return LLNotificationPtr((LLNotification*)NULL); +} + +//----------------------------------------------------------------------------- +#include "../llviewernetwork.h" +LLGridManager::~LLGridManager() +{ +} + +bool LLGridManager::addGrid(LLSD& grid_data) +{ + return true; +} +LLGridManager::LLGridManager() +: + mIsInProductionGrid(false) +{ +} + +void LLGridManager::getLoginURIs(std::vector& uris) +{ + uris.push_back(VIEWERLOGIN_URI); +} + +void LLGridManager::addSystemGrid(const std::string& label, + const std::string& name, + const std::string& login, + const std::string& helper, + const std::string& login_page, + const std::string& update_url_base, + const std::string& web_profile_url, + const std::string& login_id) +{ +} +std::map LLGridManager::getKnownGrids() +{ + std::map result; + return result; +} + +void LLGridManager::setGridChoice(const std::string& grid_name) +{ +} + +bool LLGridManager::isInProductionGrid() +{ + return false; +} + +std::string LLGridManager::getSLURLBase(const std::string& grid_name) +{ + return "myslurl"; +} +std::string LLGridManager::getAppSLURLBase(const std::string& grid_name) +{ + return "myappslurl"; +} +std::string LLGridManager::getGridId(const std::string& grid) +{ + return std::string(); +} + +//----------------------------------------------------------------------------- +#include "../llviewercontrol.h" +LLControlGroup gSavedSettings("Global"); + +LLControlGroup::LLControlGroup(const std::string& name) : + LLInstanceTracker(name){} +LLControlGroup::~LLControlGroup() {} +void LLControlGroup::setBOOL(std::string_view name, bool val) {} +bool LLControlGroup::getBOOL(std::string_view name) { return false; } +F32 LLControlGroup::getF32(std::string_view name) { return 0.0f; } +U32 LLControlGroup::saveToFile(const std::string& filename, bool nondefault_only) { return 1; } +void LLControlGroup::setString(std::string_view name, const std::string& val) {} +std::string LLControlGroup::getString(std::string_view name) { return "test_string"; } +LLControlVariable* LLControlGroup::declareBOOL(const std::string& name, bool initial_val, const std::string& comment, LLControlVariable::ePersist persist) { return NULL; } +LLControlVariable* LLControlGroup::declareString(const std::string& name, const std::string &initial_val, const std::string& comment, LLControlVariable::ePersist persist) { return NULL; } + +#include "lluicolortable.h" +void LLUIColorTable::saveUserSettings(void)const {} + +//----------------------------------------------------------------------------- +#include "../llversioninfo.h" + +bool llHashedUniqueID(unsigned char* id) +{ + memcpy( id, "66666666666666666666666666666666", MD5HEX_STR_SIZE ); + return true; +} + +//----------------------------------------------------------------------------- +#include "../llappviewer.h" +void LLAppViewer::forceQuit(void) {} +bool LLAppViewer::isUpdaterMissing() { return true; } +bool LLAppViewer::waitForUpdater() { return false; } +LLAppViewer * LLAppViewer::sInstance = 0; + +//----------------------------------------------------------------------------- +#include "llnotifications.h" +#include "llfloaterreg.h" +static std::string gTOSType; +static LLEventPump * gTOSReplyPump = NULL; + +LLPointer gSecAPIHandler; + +//static +LLFloater* LLFloaterReg::showInstance(const std::string& name, const LLSD& key, bool focus) +{ + gTOSType = name; + gTOSReplyPump = &LLEventPumps::instance().obtain(key["reply_pump"]); + return NULL; +} + +//---------------------------------------------------------------------------- +#include "../llprogressview.h" +void LLProgressView::setText(std::string const &){} +void LLProgressView::setPercent(float){} +void LLProgressView::setMessage(std::string const &){} + +//----------------------------------------------------------------------------- +// LLNotifications +class MockNotifications : public LLNotificationsInterface +{ + boost::function mResponder; + int mAddedCount; + +public: + MockNotifications() : + mResponder(0), + mAddedCount(0) + { + } + + virtual ~MockNotifications() {} + + /* virtual */ LLNotificationPtr add( + const std::string& name, + const LLSD& substitutions, + const LLSD& payload, + LLNotificationFunctorRegistry::ResponseFunctor functor) + { + mResponder = functor; + mAddedCount++; + return LLNotificationPtr((LLNotification*)NULL); + } + + void sendYesResponse() + { + LLSD notification; + LLSD response; + response = 1; + mResponder(notification, response); + } + + void sendNoResponse() + { + LLSD notification; + LLSD response; + response = 2; + mResponder(notification, response); + } + + void sendBogusResponse() + { + LLSD notification; + LLSD response; + response = 666; + mResponder(notification, response); + } + + int addedCount() { return mAddedCount; } +}; + +S32 LLNotification::getSelectedOption(const LLSD& notification, const LLSD& response) +{ + return response.asInteger(); +} + +//----------------------------------------------------------------------------- +#include "../llmachineid.h" +unsigned char gMACAddress[MAC_ADDRESS_BYTES] = {77,21,46,31,89,2}; + +S32 LLMachineID::getUniqueID(unsigned char *unique_id, size_t len) +{ + memcpy(unique_id, gMACAddress, len); + return 1; +} +S32 LLMachineID::getLegacyID(unsigned char *unique_id, size_t len) +{ + return 0; +} +//----------------------------------------------------------------------------- +// misc +std::string xml_escape_string(const std::string& in) +{ + return in; +} + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct lllogininstance_data + { + lllogininstance_data() : logininstance(LLLoginInstance::getInstance()) + { + // Global initialization + gLoginURI.clear(); + gLoginCreds.clear(); + gDisconnectCalled = false; + + gTOSType = ""; // Set to invalid value. + gTOSReplyPump = 0; // clear the callback. + + + gSavedSettings.declareBOOL("NoInventoryLibrary", false, "", LLControlVariable::PERSIST_NO); + gSavedSettings.declareBOOL("ConnectAsGod", false, "", LLControlVariable::PERSIST_NO); + gSavedSettings.declareBOOL("UseDebugMenus", false, "", LLControlVariable::PERSIST_NO); + gSavedSettings.declareString("ClientSettingsFile", "test_settings.xml", "", LLControlVariable::PERSIST_NO); + gSavedSettings.declareString("NextLoginLocation", "", "", LLControlVariable::PERSIST_NO); + gSavedSettings.declareBOOL("LoginLastLocation", false, "", LLControlVariable::PERSIST_NO); + gSavedSettings.declareBOOL("CmdLineSkipUpdater", true, "", LLControlVariable::PERSIST_NO); + + LLSD authenticator = LLSD::emptyMap(); + LLSD identifier = LLSD::emptyMap(); + identifier["type"] = "agent"; + identifier["first_name"] = "testfirst"; + identifier["last_name"] = "testlast"; + authenticator["passwd"] = "testpass"; + agentCredential = new LLCredential(); + agentCredential->setCredentialData(identifier, authenticator); + + authenticator = LLSD::emptyMap(); + identifier = LLSD::emptyMap(); + identifier["type"] = "account"; + identifier["username"] = "testuser"; + authenticator["secret"] = "testsecret"; + accountCredential = new LLCredential(); + accountCredential->setCredentialData(identifier, authenticator); + + logininstance->setNotificationsInterface(¬ifications); + logininstance->setPlatformInfo("win", "1.3.5", "Windows Bogus Version 100.6.6.6"); + } + + LLLoginInstance* logininstance; + LLPointer agentCredential; + LLPointer accountCredential; + MockNotifications notifications; + }; + + typedef test_group lllogininstance_group; + typedef lllogininstance_group::object lllogininstance_object; + lllogininstance_group llsdmgr("LLLoginInstance"); + + template<> template<> + void lllogininstance_object::test<1>() + { + set_test_name("Test Simple Success And Disconnect"); + + // Test default connect. + logininstance->connect(agentCredential); + + ensure_equals("Default connect uri", gLoginURI, VIEWERLOGIN_URI); + + // Dummy success response. + LLSD response; + response["state"] = "online"; + response["change"] = "connect"; + response["progress"] = 1.0; + response["transfer_rate"] = 7; + response["data"] = "test_data"; + + gTestPump.post(response); + + ensure("Success response", logininstance->authSuccess()); + ensure_equals("Test Response Data", logininstance->getResponse().asString(), "test_data"); + + logininstance->disconnect(); + + ensure_equals("Called Login Module Disconnect", gDisconnectCalled, true); + + response.clear(); + response["state"] = "offline"; + response["change"] = "disconnect"; + response["progress"] = 0.0; + response["transfer_rate"] = 0; + response["data"] = "test_data"; + + gTestPump.post(response); + + ensure("Disconnected", !(logininstance->authSuccess())); + } + + template<> template<> + void lllogininstance_object::test<2>() + { + set_test_name("Test User TOS/Critical message Interaction"); + + const std::string test_uri = "testing-uri"; + + // Test default connect. + logininstance->connect(test_uri, agentCredential); + + // connect should call LLLogin::connect to init gLoginURI and gLoginCreds. + ensure_equals("Default connect uri", gLoginURI, "testing-uri"); + ensure_equals("Default for agree to tos", gLoginCreds["params"]["agree_to_tos"].asBoolean(), false); + ensure_equals("Default for read critical", gLoginCreds["params"]["read_critical"].asBoolean(), false); + + // TOS failure response. + LLSD response; + response["state"] = "offline"; + response["change"] = "fail.login"; + response["progress"] = 0.0; + response["transfer_rate"] = 7; + response["data"]["reason"] = "tos"; + gTestPump.post(response); + + ensure_equals("TOS Dialog type", gTOSType, "message_tos"); + ensure("TOS callback given", gTOSReplyPump != 0); + gTOSReplyPump->post(false); // Call callback denying TOS. + ensure("No TOS, failed auth", logininstance->authFailure()); + + // Start again. + logininstance->connect(test_uri, agentCredential); + gTestPump.post(response); // Fail for tos again. + gTOSReplyPump->post(true); // Accept tos, should reconnect w/ agree_to_tos. + ensure_equals("Accepted agree to tos", gLoginCreds["params"]["agree_to_tos"].asBoolean(), true); + ensure("Incomplete login status", !logininstance->authFailure() && !logininstance->authSuccess()); + + // Fail connection, attempt connect again. + // The new request should have reset agree to tos to default. + response["data"]["reason"] = "key"; // bad creds. + gTestPump.post(response); + ensure("TOS auth failure", logininstance->authFailure()); + + logininstance->connect(test_uri, agentCredential); + ensure_equals("Reset to default for agree to tos", gLoginCreds["params"]["agree_to_tos"].asBoolean(), false); + + // Critical Message failure response. + logininstance->connect(test_uri, agentCredential); + response["data"]["reason"] = "critical"; // Change response to "critical message" + gTestPump.post(response); + + ensure_equals("TOS Dialog type", gTOSType, "message_critical"); + ensure("TOS callback given", gTOSReplyPump != 0); + gTOSReplyPump->post(true); + ensure_equals("Accepted read critical message", gLoginCreds["params"]["read_critical"].asBoolean(), true); + ensure("Incomplete login status", !logininstance->authFailure() && !logininstance->authSuccess()); + + // Fail then attempt new connection + response["data"]["reason"] = "key"; // bad creds. + gTestPump.post(response); + ensure("TOS auth failure", logininstance->authFailure()); + logininstance->connect(test_uri, agentCredential); + ensure_equals("Default for agree to tos", gLoginCreds["params"]["read_critical"].asBoolean(), false); + } +} diff --git a/indra/newview/tests/llsecapi_test.cpp b/indra/newview/tests/llsecapi_test.cpp index 7a09b1198c..1a2fa7d8f6 100644 --- a/indra/newview/tests/llsecapi_test.cpp +++ b/indra/newview/tests/llsecapi_test.cpp @@ -1,133 +1,133 @@ -/** - * @file llsecapi_test.cpp - * @author Roxie - * @date 2009-02-10 - * @brief Test the sec api functionality - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ -#include "../llviewerprecompiledheaders.h" -#include "../llviewernetwork.h" -#include "../test/lltut.h" -#include "../llsecapi.h" -#include "../llsechandler_basic.h" -#include "../../llxml/llcontrol.h" - - -//---------------------------------------------------------------------------- -// Mock objects for the dependencies of the code we're testing - -LLControlGroup::LLControlGroup(const std::string& name) -: LLInstanceTracker(name) {} -LLControlGroup::~LLControlGroup() {} -LLControlVariable* LLControlGroup::declareString(const std::string& name, - const std::string& initial_val, - const std::string& comment, - LLControlVariable::ePersist persist) {return NULL;} -void LLControlGroup::setString(std::string_view name, const std::string& val){} -std::string LLControlGroup::getString(std::string_view name) -{ - return ""; -} - - -LLControlGroup gSavedSettings("test"); - -LLSecAPIBasicHandler::LLSecAPIBasicHandler() {} -void LLSecAPIBasicHandler::init() {} -LLSecAPIBasicHandler::~LLSecAPIBasicHandler() {} -LLPointer LLSecAPIBasicHandler::getCertificate(const std::string& pem_cert) { return NULL; } -LLPointer LLSecAPIBasicHandler::getCertificate(X509* openssl_cert) { return NULL; } -LLPointer LLSecAPIBasicHandler::getCertificateChain(X509_STORE_CTX* chain) { return NULL; } -LLPointer LLSecAPIBasicHandler::getCertificateStore(const std::string& store_id) { return NULL; } -void LLSecAPIBasicHandler::setProtectedData(const std::string& data_type, const std::string& data_id, const LLSD& data) {} -void LLSecAPIBasicHandler::addToProtectedMap(const std::string& data_type, const std::string& data_id, const std::string& map_elem, const LLSD& data) {} -void LLSecAPIBasicHandler::removeFromProtectedMap(const std::string& data_type, const std::string& data_id, const std::string& map_elem) {} -void LLSecAPIBasicHandler::syncProtectedMap() {} -LLSD LLSecAPIBasicHandler::getProtectedData(const std::string& data_type, const std::string& data_id) { return LLSD(); } -void LLSecAPIBasicHandler::deleteProtectedData(const std::string& data_type, const std::string& data_id) {} -LLPointer LLSecAPIBasicHandler::createCredential(const std::string& grid, const LLSD& identifier, const LLSD& authenticator) { return NULL; } -LLPointer LLSecAPIBasicHandler::loadCredential(const std::string& grid) { return NULL; } -void LLSecAPIBasicHandler::saveCredential(LLPointer cred, bool save_authenticator) {} -void LLSecAPIBasicHandler::deleteCredential(LLPointer cred) {} -bool LLSecAPIBasicHandler::hasCredentialMap(const std::string& storage, const std::string& grid) { return false; } -bool LLSecAPIBasicHandler::emptyCredentialMap(const std::string& storage, const std::string& grid) { return false; } -void LLSecAPIBasicHandler::loadCredentialMap(const std::string& storage, const std::string& grid, credential_map_t& credential_map) {} -LLPointer LLSecAPIBasicHandler::loadFromCredentialMap(const std::string& storage, const std::string& grid, const std::string& userkey) { return NULL; } -void LLSecAPIBasicHandler::addToCredentialMap(const std::string& storage, LLPointer cred, bool save_authenticator) {} -void LLSecAPIBasicHandler::removeFromCredentialMap(const std::string& storage, LLPointer cred) {} -void LLSecAPIBasicHandler::removeFromCredentialMap(const std::string& storage, const std::string& grid, const std::string& userkey) {} -void LLSecAPIBasicHandler::removeCredentialMap(const std::string& storage, const std::string& grid) {} - -// ------------------------------------------------------------------------------------------- -// TUT -// ------------------------------------------------------------------------------------------- -namespace tut -{ - // Test wrapper declaration : wrapping nothing for the moment - struct secapiTest - { - - secapiTest() - { - } - ~secapiTest() - { - } - }; - - // Tut templating thingamagic: test group, object and test instance - typedef test_group secapiTestFactory; - typedef secapiTestFactory::object secapiTestObject; - tut::secapiTestFactory tut_test("LLSecAPI"); - - // --------------------------------------------------------------------------------------- - // Test functions - // --------------------------------------------------------------------------------------- - // registration - template<> template<> - void secapiTestObject::test<1>() - { - // retrieve an unknown handler - - ensure("'Unknown' handler should be NULL", getSecHandler("unknown") == nullptr); - LLPointer test1_handler = new LLSecAPIBasicHandler(); - registerSecHandler("sectest1", test1_handler); - ensure("'Unknown' handler should be NULL", getSecHandler("unknown") == nullptr); - LLPointer retrieved_test1_handler = getSecHandler("sectest1"); - ensure("Retrieved sectest1 handler should be the same", - retrieved_test1_handler == test1_handler); - - // insert a second handler - LLPointer test2_handler = new LLSecAPIBasicHandler(); - registerSecHandler("sectest2", test2_handler); - ensure("'Unknown' handler should be NULL", getSecHandler("unknown") == nullptr); - retrieved_test1_handler = getSecHandler("sectest1"); - ensure("Retrieved sectest1 handler should be the same", - retrieved_test1_handler == test1_handler); - - LLPointer retrieved_test2_handler = getSecHandler("sectest2"); - ensure("Retrieved sectest1 handler should be the same", - retrieved_test2_handler == test2_handler); - - } -} +/** + * @file llsecapi_test.cpp + * @author Roxie + * @date 2009-02-10 + * @brief Test the sec api functionality + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#include "../llviewerprecompiledheaders.h" +#include "../llviewernetwork.h" +#include "../test/lltut.h" +#include "../llsecapi.h" +#include "../llsechandler_basic.h" +#include "../../llxml/llcontrol.h" + + +//---------------------------------------------------------------------------- +// Mock objects for the dependencies of the code we're testing + +LLControlGroup::LLControlGroup(const std::string& name) +: LLInstanceTracker(name) {} +LLControlGroup::~LLControlGroup() {} +LLControlVariable* LLControlGroup::declareString(const std::string& name, + const std::string& initial_val, + const std::string& comment, + LLControlVariable::ePersist persist) {return NULL;} +void LLControlGroup::setString(std::string_view name, const std::string& val){} +std::string LLControlGroup::getString(std::string_view name) +{ + return ""; +} + + +LLControlGroup gSavedSettings("test"); + +LLSecAPIBasicHandler::LLSecAPIBasicHandler() {} +void LLSecAPIBasicHandler::init() {} +LLSecAPIBasicHandler::~LLSecAPIBasicHandler() {} +LLPointer LLSecAPIBasicHandler::getCertificate(const std::string& pem_cert) { return NULL; } +LLPointer LLSecAPIBasicHandler::getCertificate(X509* openssl_cert) { return NULL; } +LLPointer LLSecAPIBasicHandler::getCertificateChain(X509_STORE_CTX* chain) { return NULL; } +LLPointer LLSecAPIBasicHandler::getCertificateStore(const std::string& store_id) { return NULL; } +void LLSecAPIBasicHandler::setProtectedData(const std::string& data_type, const std::string& data_id, const LLSD& data) {} +void LLSecAPIBasicHandler::addToProtectedMap(const std::string& data_type, const std::string& data_id, const std::string& map_elem, const LLSD& data) {} +void LLSecAPIBasicHandler::removeFromProtectedMap(const std::string& data_type, const std::string& data_id, const std::string& map_elem) {} +void LLSecAPIBasicHandler::syncProtectedMap() {} +LLSD LLSecAPIBasicHandler::getProtectedData(const std::string& data_type, const std::string& data_id) { return LLSD(); } +void LLSecAPIBasicHandler::deleteProtectedData(const std::string& data_type, const std::string& data_id) {} +LLPointer LLSecAPIBasicHandler::createCredential(const std::string& grid, const LLSD& identifier, const LLSD& authenticator) { return NULL; } +LLPointer LLSecAPIBasicHandler::loadCredential(const std::string& grid) { return NULL; } +void LLSecAPIBasicHandler::saveCredential(LLPointer cred, bool save_authenticator) {} +void LLSecAPIBasicHandler::deleteCredential(LLPointer cred) {} +bool LLSecAPIBasicHandler::hasCredentialMap(const std::string& storage, const std::string& grid) { return false; } +bool LLSecAPIBasicHandler::emptyCredentialMap(const std::string& storage, const std::string& grid) { return false; } +void LLSecAPIBasicHandler::loadCredentialMap(const std::string& storage, const std::string& grid, credential_map_t& credential_map) {} +LLPointer LLSecAPIBasicHandler::loadFromCredentialMap(const std::string& storage, const std::string& grid, const std::string& userkey) { return NULL; } +void LLSecAPIBasicHandler::addToCredentialMap(const std::string& storage, LLPointer cred, bool save_authenticator) {} +void LLSecAPIBasicHandler::removeFromCredentialMap(const std::string& storage, LLPointer cred) {} +void LLSecAPIBasicHandler::removeFromCredentialMap(const std::string& storage, const std::string& grid, const std::string& userkey) {} +void LLSecAPIBasicHandler::removeCredentialMap(const std::string& storage, const std::string& grid) {} + +// ------------------------------------------------------------------------------------------- +// TUT +// ------------------------------------------------------------------------------------------- +namespace tut +{ + // Test wrapper declaration : wrapping nothing for the moment + struct secapiTest + { + + secapiTest() + { + } + ~secapiTest() + { + } + }; + + // Tut templating thingamagic: test group, object and test instance + typedef test_group secapiTestFactory; + typedef secapiTestFactory::object secapiTestObject; + tut::secapiTestFactory tut_test("LLSecAPI"); + + // --------------------------------------------------------------------------------------- + // Test functions + // --------------------------------------------------------------------------------------- + // registration + template<> template<> + void secapiTestObject::test<1>() + { + // retrieve an unknown handler + + ensure("'Unknown' handler should be NULL", getSecHandler("unknown") == nullptr); + LLPointer test1_handler = new LLSecAPIBasicHandler(); + registerSecHandler("sectest1", test1_handler); + ensure("'Unknown' handler should be NULL", getSecHandler("unknown") == nullptr); + LLPointer retrieved_test1_handler = getSecHandler("sectest1"); + ensure("Retrieved sectest1 handler should be the same", + retrieved_test1_handler == test1_handler); + + // insert a second handler + LLPointer test2_handler = new LLSecAPIBasicHandler(); + registerSecHandler("sectest2", test2_handler); + ensure("'Unknown' handler should be NULL", getSecHandler("unknown") == nullptr); + retrieved_test1_handler = getSecHandler("sectest1"); + ensure("Retrieved sectest1 handler should be the same", + retrieved_test1_handler == test1_handler); + + LLPointer retrieved_test2_handler = getSecHandler("sectest2"); + ensure("Retrieved sectest1 handler should be the same", + retrieved_test2_handler == test2_handler); + + } +} diff --git a/indra/newview/tests/llsechandler_basic_test.cpp b/indra/newview/tests/llsechandler_basic_test.cpp index b1eccddff8..4f32299b0d 100644 --- a/indra/newview/tests/llsechandler_basic_test.cpp +++ b/indra/newview/tests/llsechandler_basic_test.cpp @@ -1,1418 +1,1418 @@ -/** - * @file llsechandler_basic_test.cpp - * @author Roxie - * @date 2009-02-10 - * @brief Test the 'basic' sec handler functions - * - * $LicenseInfo:firstyear=2005&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ -#include "../llviewerprecompiledheaders.h" -#include "../test/lltut.h" -#include "../llsecapi.h" -#include "../llsechandler_basic.h" -#include "../../llxml/llcontrol.h" -#include "../llviewernetwork.h" -#include "lluuid.h" -#include "lldate.h" -#include "llxorcipher.h" -#include "apr_base64.h" -#include -#include -#include -#include -#include -#include -#include "llxorcipher.h" -#include -#include -#include -#include -#include -#include -#include -#include "../llmachineid.h" - -#define ensure_throws(str, exc_type, cert, func, ...) \ -try \ -{ \ -func(__VA_ARGS__); \ -fail("throws, " str); \ -} \ -catch(exc_type& except) \ -{ \ -LLSD cert_data; \ -cert->getLLSD(cert_data); \ -ensure("Exception cert is incorrect for " str, valueCompareLLSD(except.getCertData(), cert_data)); \ -} - -extern bool _cert_hostname_wildcard_match(const std::string& hostname, const std::string& wildcard_string); - -//---------------------------------------------------------------------------- -// Mock objects for the dependencies of the code we're testing - -std::string gFirstName; -std::string gLastName; -LLControlGroup::LLControlGroup(const std::string& name) -: LLInstanceTracker(name) {} -LLControlGroup::~LLControlGroup() {} -LLControlVariable* LLControlGroup::declareString(const std::string& name, - const std::string& initial_val, - const std::string& comment, - LLControlVariable::ePersist persist) {return NULL;} -void LLControlGroup::setString(std::string_view name, const std::string& val){} -std::string LLControlGroup::getString(std::string_view name) -{ - - if (name == "FirstName") - return gFirstName; - else if (name == "LastName") - return gLastName; - return ""; -} - -// Stub for --no-verify-ssl-cert -bool LLControlGroup::getBOOL(std::string_view name) { return false; } - -LLSD LLCredential::getLoginParams() -{ - LLSD result = LLSD::emptyMap(); - - // legacy credential - result["passwd"] = "$1$testpasssd"; - result["first"] = "myfirst"; - result["last"] ="mylast"; - return result; -} - -void LLCredential::identifierType(std::string &idType) -{ -} - -void LLCredential::authenticatorType(std::string &idType) -{ -} - - -LLControlGroup gSavedSettings("test"); -unsigned char gMACAddress[MAC_ADDRESS_BYTES] = {77,21,46,31,89,2}; - - -S32 LLMachineID::getUniqueID(unsigned char *unique_id, size_t len) -{ - memcpy(unique_id, gMACAddress, len); - return 1; -} -S32 LLMachineID::getLegacyID(unsigned char *unique_id, size_t len) -{ - return 0; -} -S32 LLMachineID::init() { return 1; } - - -LLCertException::LLCertException(const LLSD& cert_data, const std::string& msg) - : LLException(msg), - mCertData(cert_data) -{ - LL_WARNS("SECAPI") << "Certificate Error: " << msg << LL_ENDL; -} - - -// ------------------------------------------------------------------------------------------- -// TUT -// ------------------------------------------------------------------------------------------- -namespace tut -{ - const std::string mPemTestCert( - "Certificate:\n" - " Data:\n" - " Version: 3 (0x2)\n" - " Serial Number:\n" - " 04:00:00:00:00:01:15:4b:5a:c3:94\n" - " Signature Algorithm: sha1WithRSAEncryption\n" - " Issuer: C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA\n" - " Validity\n" - " Not Before: Sep 1 12:00:00 1998 GMT\n" - " Not After : Jan 28 12:00:00 2028 GMT\n" - " Subject: C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA\n" - " Subject Public Key Info:\n" - " Public Key Algorithm: rsaEncryption\n" - " Public-Key: (2048 bit)\n" - " Modulus:\n" - " 00:da:0e:e6:99:8d:ce:a3:e3:4f:8a:7e:fb:f1:8b:\n" - " 83:25:6b:ea:48:1f:f1:2a:b0:b9:95:11:04:bd:f0:\n" - " 63:d1:e2:67:66:cf:1c:dd:cf:1b:48:2b:ee:8d:89:\n" - " 8e:9a:af:29:80:65:ab:e9:c7:2d:12:cb:ab:1c:4c:\n" - " 70:07:a1:3d:0a:30:cd:15:8d:4f:f8:dd:d4:8c:50:\n" - " 15:1c:ef:50:ee:c4:2e:f7:fc:e9:52:f2:91:7d:e0:\n" - " 6d:d5:35:30:8e:5e:43:73:f2:41:e9:d5:6a:e3:b2:\n" - " 89:3a:56:39:38:6f:06:3c:88:69:5b:2a:4d:c5:a7:\n" - " 54:b8:6c:89:cc:9b:f9:3c:ca:e5:fd:89:f5:12:3c:\n" - " 92:78:96:d6:dc:74:6e:93:44:61:d1:8d:c7:46:b2:\n" - " 75:0e:86:e8:19:8a:d5:6d:6c:d5:78:16:95:a2:e9:\n" - " c8:0a:38:eb:f2:24:13:4f:73:54:93:13:85:3a:1b:\n" - " bc:1e:34:b5:8b:05:8c:b9:77:8b:b1:db:1f:20:91:\n" - " ab:09:53:6e:90:ce:7b:37:74:b9:70:47:91:22:51:\n" - " 63:16:79:ae:b1:ae:41:26:08:c8:19:2b:d1:46:aa:\n" - " 48:d6:64:2a:d7:83:34:ff:2c:2a:c1:6c:19:43:4a:\n" - " 07:85:e7:d3:7c:f6:21:68:ef:ea:f2:52:9f:7f:93:\n" - " 90:cf\n" - " Exponent: 65537 (0x10001)\n" - " X509v3 extensions:\n" - " X509v3 Key Usage: critical\n" - " Certificate Sign, CRL Sign\n" - " X509v3 Basic Constraints: critical\n" - " CA:TRUE\n" - " X509v3 Subject Key Identifier: \n" - " 60:7B:66:1A:45:0D:97:CA:89:50:2F:7D:04:CD:34:A8:FF:FC:FD:4B\n" - " Signature Algorithm: sha1WithRSAEncryption\n" - " d6:73:e7:7c:4f:76:d0:8d:bf:ec:ba:a2:be:34:c5:28:32:b5:\n" - " 7c:fc:6c:9c:2c:2b:bd:09:9e:53:bf:6b:5e:aa:11:48:b6:e5:\n" - " 08:a3:b3:ca:3d:61:4d:d3:46:09:b3:3e:c3:a0:e3:63:55:1b:\n" - " f2:ba:ef:ad:39:e1:43:b9:38:a3:e6:2f:8a:26:3b:ef:a0:50:\n" - " 56:f9:c6:0a:fd:38:cd:c4:0b:70:51:94:97:98:04:df:c3:5f:\n" - " 94:d5:15:c9:14:41:9c:c4:5d:75:64:15:0d:ff:55:30:ec:86:\n" - " 8f:ff:0d:ef:2c:b9:63:46:f6:aa:fc:df:bc:69:fd:2e:12:48:\n" - " 64:9a:e0:95:f0:a6:ef:29:8f:01:b1:15:b5:0c:1d:a5:fe:69:\n" - " 2c:69:24:78:1e:b3:a7:1c:71:62:ee:ca:c8:97:ac:17:5d:8a:\n" - " c2:f8:47:86:6e:2a:c4:56:31:95:d0:67:89:85:2b:f9:6c:a6:\n" - " 5d:46:9d:0c:aa:82:e4:99:51:dd:70:b7:db:56:3d:61:e4:6a:\n" - " e1:5c:d6:f6:fe:3d:de:41:cc:07:ae:63:52:bf:53:53:f4:2b:\n" - " e9:c7:fd:b6:f7:82:5f:85:d2:41:18:db:81:b3:04:1c:c5:1f:\n" - " a4:80:6f:15:20:c9:de:0c:88:0a:1d:d6:66:55:e2:fc:48:c9:\n" - " 29:26:69:e0\n" - "-----BEGIN CERTIFICATE-----\n" - "MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG\n" - "A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv\n" - "b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw\n" - "MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i\n" - "YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT\n" - "aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ\n" - "jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp\n" - "xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp\n" - "1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG\n" - "snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ\n" - "U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8\n" - "9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E\n" - "BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B\n" - "AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz\n" - "yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE\n" - "38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP\n" - "AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad\n" - "DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME\n" - "HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==\n" - "-----END CERTIFICATE-----\n" - ); - - /* - * The following certificates were generated using the instructions at - * https://jamielinux.com/docs/openssl-certificate-authority/sign-server-and-client-certificates.html - * with the exception that the server certificate has a longer expiration time, and the full text - * expansion was included in the certificates. - */ - const std::string mPemRootCert( - "Certificate:\n" - " Data:\n" - " Version: 3 (0x2)\n" - " Serial Number:\n" - " 82:2f:8f:eb:8d:06:24:b0\n" - " Signature Algorithm: sha256WithRSAEncryption\n" - " Issuer: C=US, ST=California, L=San Francisco, O=Linden Lab, OU=Second Life Engineering, CN=Integration Test Root CA/emailAddress=noreply@lindenlab.com\n" - " Validity\n" - " Not Before: May 22 22:19:45 2018 GMT\n" - " Not After : May 17 22:19:45 2038 GMT\n" - " Subject: C=US, ST=California, L=San Francisco, O=Linden Lab, OU=Second Life Engineering, CN=Integration Test Root CA/emailAddress=noreply@lindenlab.com\n" - " Subject Public Key Info:\n" - " Public Key Algorithm: rsaEncryption\n" - " Public-Key: (4096 bit)\n" - " Modulus:\n" - " 00:bd:e0:79:dd:3b:a6:ac:87:d0:39:f0:58:c7:a4:\n" - " 42:42:f6:5f:93:b0:36:04:b5:e2:d5:f7:2a:c0:6c:\n" - " a0:13:d2:1e:02:81:57:02:50:4c:57:b7:ef:27:9e:\n" - " f6:f1:f1:30:30:72:1e:57:34:e5:3f:82:3c:21:c4:\n" - " 66:d2:73:63:6c:91:e6:dd:49:9e:9c:b1:34:6a:81:\n" - " 45:a1:6e:c4:50:28:f2:d8:e3:fe:80:2f:83:aa:28:\n" - " 91:b4:8c:57:c9:f1:16:d9:0c:87:3c:25:80:a0:81:\n" - " 8d:71:f2:96:e2:16:f1:97:c4:b0:d8:53:bb:13:6c:\n" - " 73:54:2f:29:94:85:cf:86:6e:75:71:ad:39:e3:fc:\n" - " 39:12:53:93:1c:ce:39:e0:33:da:49:b7:3d:af:b0:\n" - " 37:ce:77:09:03:27:32:70:c0:9c:7f:9c:89:ce:90:\n" - " 45:b0:7d:94:8b:ff:13:27:ba:88:7f:ae:c4:aa:73:\n" - " d5:47:b8:87:69:89:80:0c:c1:22:18:78:c2:0d:47:\n" - " d9:10:ff:80:79:0d:46:71:ec:d9:ba:c9:f3:77:fd:\n" - " 92:6d:1f:0f:d9:54:18:6d:f6:72:24:5c:5c:3d:43:\n" - " 49:35:3e:1c:28:de:7e:44:dc:29:c3:9f:62:04:46:\n" - " aa:c4:e6:69:6a:15:f8:e3:74:1c:14:e9:f4:97:7c:\n" - " 30:6c:d4:28:fc:2a:0e:1d:6d:39:2e:1d:f9:17:43:\n" - " 35:5d:23:e7:ba:e3:a8:e9:97:6b:3c:3e:23:ef:d8:\n" - " bc:fb:7a:57:37:39:93:59:03:fc:78:ca:b1:31:ef:\n" - " 26:19:ed:56:e1:63:c3:ad:99:80:5b:47:b5:03:35:\n" - " 5f:fe:6a:a6:21:63:ec:50:fb:4e:c9:f9:ae:a5:66:\n" - " d0:55:33:8d:e6:c5:50:5a:c6:8f:5c:34:45:a7:72:\n" - " da:50:f6:66:4c:19:f5:d1:e4:fb:11:8b:a1:b5:4e:\n" - " 09:43:81:3d:39:28:86:3b:fe:07:28:97:02:b5:3a:\n" - " 07:5f:4a:20:80:1a:7d:a4:8c:f7:6c:f6:c5:9b:f6:\n" - " 61:e5:c7:b0:c3:d5:58:38:7b:bb:47:1e:34:d6:16:\n" - " 55:c5:d2:6c:b0:93:77:b1:90:69:06:b1:53:cb:1b:\n" - " 84:71:cf:b8:87:1b:1e:44:35:b4:2b:bb:04:59:58:\n" - " 0b:e8:93:d8:ae:21:9b:b1:1c:89:30:ae:11:80:77:\n" - " cc:16:f3:d6:35:ed:a1:b3:70:b3:4f:cd:a1:56:99:\n" - " ee:0e:c0:00:a4:09:70:c3:5b:0b:be:a1:07:18:dd:\n" - " c6:f4:6d:8b:58:bc:f9:bb:4b:01:2c:f6:cc:2c:9b:\n" - " 87:0e:b1:4f:9c:10:be:fc:45:e2:a4:ec:7e:fc:ff:\n" - " 45:b8:53\n" - " Exponent: 65537 (0x10001)\n" - " X509v3 extensions:\n" - " X509v3 Subject Key Identifier: \n" - " 8A:22:C6:9C:2E:11:F3:40:0C:CE:82:0C:22:59:FF:F8:7F:D0:B9:13\n" - " X509v3 Authority Key Identifier: \n" - " keyid:8A:22:C6:9C:2E:11:F3:40:0C:CE:82:0C:22:59:FF:F8:7F:D0:B9:13\n" - "\n" - " X509v3 Basic Constraints: critical\n" - " CA:TRUE\n" - " X509v3 Key Usage: critical\n" - " Digital Signature, Certificate Sign, CRL Sign\n" - " Signature Algorithm: sha256WithRSAEncryption\n" - " b3:cb:33:eb:0e:02:64:f4:55:9a:3d:03:9a:cf:6a:4c:18:43:\n" - " f7:42:cb:65:dc:61:52:e5:9f:2f:42:97:3c:93:16:22:d4:af:\n" - " ae:b2:0f:c3:9b:ef:e0:cc:ee:b6:b1:69:a3:d8:da:26:c3:ad:\n" - " 3b:c5:64:dc:9f:d4:c2:53:4b:91:6d:c4:92:09:0b:ac:f0:99:\n" - " be:6f:b9:3c:03:4a:6d:9f:01:5d:ec:5a:9a:f3:a7:e5:3b:2c:\n" - " 99:57:7d:7e:25:15:68:20:12:30:96:16:86:f5:db:74:90:60:\n" - " fe:8b:df:99:f6:f7:62:49:9f:bc:8d:45:23:0a:c8:73:b8:79:\n" - " 80:3c:b9:e5:72:85:4b:b3:81:66:74:a2:72:92:4c:44:fd:7b:\n" - " 46:2e:21:a2:a9:81:a2:f3:26:4d:e3:89:7d:78:b0:c6:6f:b5:\n" - " 87:cb:ee:25:ed:27:1f:75:13:fa:6d:e9:37:73:ad:07:bb:af:\n" - " d3:6c:87:ea:02:01:70:bd:53:aa:ce:39:2c:d4:66:39:33:aa:\n" - " d1:9c:ee:67:e3:a9:45:d2:7b:2e:54:09:af:70:5f:3f:5a:67:\n" - " 2e:6c:72:ef:e0:9d:92:28:4a:df:ba:0b:b7:23:ca:5b:04:11:\n" - " 45:d1:51:e9:ea:c9:ec:54:fa:34:46:ae:fc:dc:6c:f8:1e:2c:\n" - " 9e:f4:71:51:8d:b5:a1:26:9a:13:30:be:1e:41:25:59:58:05:\n" - " 2c:64:c8:f9:5e:38:ae:dc:93:b0:8a:d6:38:74:02:cb:ce:ce:\n" - " 95:31:76:f6:7c:bf:a4:a1:8e:27:fd:ca:74:82:d1:e1:4d:b6:\n" - " 48:51:fa:c5:17:59:22:a3:84:be:82:c8:83:ec:61:a0:f4:ee:\n" - " 2c:e3:a3:ea:e5:51:c9:d3:4f:db:85:bd:ba:7a:52:14:b6:03:\n" - " ed:43:17:d8:d7:1c:22:5e:c9:56:d9:d6:81:96:11:e3:5e:01:\n" - " 40:91:30:09:da:a3:5f:d3:27:60:e5:9d:6c:da:d0:f0:39:01:\n" - " 23:4a:a6:15:7a:4a:82:eb:ec:72:4a:1d:36:dc:6f:83:c4:85:\n" - " 84:b5:8d:cd:09:e5:12:63:f3:21:56:c8:64:6b:db:b8:cf:d4:\n" - " df:ca:a8:24:8e:df:8d:63:a5:96:84:bf:ff:8b:7e:46:7a:f0:\n" - " c7:73:7c:70:8a:f5:17:d0:ac:c8:89:1e:d7:89:42:0f:4d:66:\n" - " c4:d8:bb:36:a8:ae:ca:e1:cf:e2:88:f6:cf:b0:44:4a:5f:81:\n" - " 50:4b:d6:28:81:cd:6c:f0:ec:e6:09:08:f2:59:91:a2:69:ac:\n" - " c7:81:fa:ab:61:3e:db:6f:f6:7f:db:1a:9e:b9:5d:cc:cc:33:\n" - " fa:95:c6:f7:8d:4b:30:f3\n" - "-----BEGIN CERTIFICATE-----\n" - "MIIGXDCCBESgAwIBAgIJAIIvj+uNBiSwMA0GCSqGSIb3DQEBCwUAMIG6MQswCQYD\n" - "VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5j\n" - "aXNjbzETMBEGA1UECgwKTGluZGVuIExhYjEgMB4GA1UECwwXU2Vjb25kIExpZmUg\n" - "RW5naW5lZXJpbmcxITAfBgNVBAMMGEludGVncmF0aW9uIFRlc3QgUm9vdCBDQTEk\n" - "MCIGCSqGSIb3DQEJARYVbm9yZXBseUBsaW5kZW5sYWIuY29tMB4XDTE4MDUyMjIy\n" - "MTk0NVoXDTM4MDUxNzIyMTk0NVowgboxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApD\n" - "YWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRMwEQYDVQQKDApMaW5k\n" - "ZW4gTGFiMSAwHgYDVQQLDBdTZWNvbmQgTGlmZSBFbmdpbmVlcmluZzEhMB8GA1UE\n" - "AwwYSW50ZWdyYXRpb24gVGVzdCBSb290IENBMSQwIgYJKoZIhvcNAQkBFhVub3Jl\n" - "cGx5QGxpbmRlbmxhYi5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC\n" - "AQC94HndO6ash9A58FjHpEJC9l+TsDYEteLV9yrAbKAT0h4CgVcCUExXt+8nnvbx\n" - "8TAwch5XNOU/gjwhxGbSc2NskebdSZ6csTRqgUWhbsRQKPLY4/6AL4OqKJG0jFfJ\n" - "8RbZDIc8JYCggY1x8pbiFvGXxLDYU7sTbHNULymUhc+GbnVxrTnj/DkSU5Mczjng\n" - "M9pJtz2vsDfOdwkDJzJwwJx/nInOkEWwfZSL/xMnuoh/rsSqc9VHuIdpiYAMwSIY\n" - "eMINR9kQ/4B5DUZx7Nm6yfN3/ZJtHw/ZVBht9nIkXFw9Q0k1Phwo3n5E3CnDn2IE\n" - "RqrE5mlqFfjjdBwU6fSXfDBs1Cj8Kg4dbTkuHfkXQzVdI+e646jpl2s8PiPv2Lz7\n" - "elc3OZNZA/x4yrEx7yYZ7VbhY8OtmYBbR7UDNV/+aqYhY+xQ+07J+a6lZtBVM43m\n" - "xVBaxo9cNEWnctpQ9mZMGfXR5PsRi6G1TglDgT05KIY7/gcolwK1OgdfSiCAGn2k\n" - "jPds9sWb9mHlx7DD1Vg4e7tHHjTWFlXF0mywk3exkGkGsVPLG4Rxz7iHGx5ENbQr\n" - "uwRZWAvok9iuIZuxHIkwrhGAd8wW89Y17aGzcLNPzaFWme4OwACkCXDDWwu+oQcY\n" - "3cb0bYtYvPm7SwEs9swsm4cOsU+cEL78ReKk7H78/0W4UwIDAQABo2MwYTAdBgNV\n" - "HQ4EFgQUiiLGnC4R80AMzoIMIln/+H/QuRMwHwYDVR0jBBgwFoAUiiLGnC4R80AM\n" - "zoIMIln/+H/QuRMwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJ\n" - "KoZIhvcNAQELBQADggIBALPLM+sOAmT0VZo9A5rPakwYQ/dCy2XcYVLlny9ClzyT\n" - "FiLUr66yD8Ob7+DM7raxaaPY2ibDrTvFZNyf1MJTS5FtxJIJC6zwmb5vuTwDSm2f\n" - "AV3sWprzp+U7LJlXfX4lFWggEjCWFob123SQYP6L35n292JJn7yNRSMKyHO4eYA8\n" - "ueVyhUuzgWZ0onKSTET9e0YuIaKpgaLzJk3jiX14sMZvtYfL7iXtJx91E/pt6Tdz\n" - "rQe7r9Nsh+oCAXC9U6rOOSzUZjkzqtGc7mfjqUXSey5UCa9wXz9aZy5scu/gnZIo\n" - "St+6C7cjylsEEUXRUenqyexU+jRGrvzcbPgeLJ70cVGNtaEmmhMwvh5BJVlYBSxk\n" - "yPleOK7ck7CK1jh0AsvOzpUxdvZ8v6Shjif9ynSC0eFNtkhR+sUXWSKjhL6CyIPs\n" - "YaD07izjo+rlUcnTT9uFvbp6UhS2A+1DF9jXHCJeyVbZ1oGWEeNeAUCRMAnao1/T\n" - "J2DlnWza0PA5ASNKphV6SoLr7HJKHTbcb4PEhYS1jc0J5RJj8yFWyGRr27jP1N/K\n" - "qCSO341jpZaEv/+LfkZ68MdzfHCK9RfQrMiJHteJQg9NZsTYuzaorsrhz+KI9s+w\n" - "REpfgVBL1iiBzWzw7OYJCPJZkaJprMeB+qthPttv9n/bGp65XczMM/qVxveNSzDz\n" - "-----END CERTIFICATE-----\n" - ); - - const std::string mPemIntermediateCert( - "Certificate:\n" - " Data:\n" - " Version: 3 (0x2)\n" - " Serial Number: 4096 (0x1000)\n" - " Signature Algorithm: sha256WithRSAEncryption\n" - " Issuer: C=US, ST=California, L=San Francisco, O=Linden Lab, OU=Second Life Engineering, CN=Integration Test Root CA/emailAddress=noreply@lindenlab.com\n" - " Validity\n" - " Not Before: May 22 22:39:08 2018 GMT\n" - " Not After : May 19 22:39:08 2028 GMT\n" - " Subject: C=US, ST=California, O=Linden Lab, OU=Second Life Engineering, CN=Integration Test Intermediate CA/emailAddress=noreply@lindenlab.com\n" - " Subject Public Key Info:\n" - " Public Key Algorithm: rsaEncryption\n" - " Public-Key: (4096 bit)\n" - " Modulus:\n" - " 00:ce:a3:70:e2:c4:fb:4b:97:90:a1:30:bb:c1:1b:\n" - " 13:b9:aa:7e:46:17:a3:26:8d:69:3f:5e:73:95:e8:\n" - " 6a:b1:0a:b4:8f:50:65:e3:c6:5c:39:24:34:df:0b:\n" - " b7:cc:ce:62:0c:36:5a:12:2c:fe:35:4c:e9:1c:ac:\n" - " 80:5e:24:99:d7:aa:bd:be:48:c0:62:64:77:36:88:\n" - " 66:ce:f4:a8:dd:d2:76:24:62:90:55:41:fc:1d:13:\n" - " 4e:a7:4e:57:bc:a8:a4:59:4b:2c:5a:1c:d8:cc:16:\n" - " de:e8:88:30:c9:95:df:2f:a6:14:28:0f:eb:34:46:\n" - " 12:58:ba:da:0e:e6:de:9c:15:f6:f4:e3:9f:74:aa:\n" - " 70:89:79:8b:e9:5a:7b:18:54:15:94:3a:23:0a:65:\n" - " 78:05:d9:33:90:2a:ce:15:18:0d:52:fc:5c:31:65:\n" - " 20:d0:12:37:8c:11:80:ba:d4:b0:82:73:00:4b:49:\n" - " be:cb:d6:bc:e7:cd:61:f3:00:98:99:74:5a:37:81:\n" - " 49:96:7e:14:01:1b:86:d2:d0:06:94:40:63:63:46:\n" - " 11:fc:33:5c:bd:3a:5e:d4:e5:44:47:64:50:bd:a6:\n" - " 97:55:70:64:9b:26:cc:de:20:82:90:6a:83:41:9c:\n" - " 6f:71:47:14:be:cb:68:7c:85:be:ef:2e:76:12:19:\n" - " d3:c9:87:32:b4:ac:60:20:16:28:2d:af:bc:e8:01:\n" - " c6:7f:fb:d8:11:d5:f4:b7:14:bd:27:08:5b:72:be:\n" - " 09:e0:91:c8:9c:7b:b4:b3:12:ef:32:36:be:b1:b9:\n" - " a2:b7:e3:69:47:30:76:ba:9c:9b:19:99:4d:53:dd:\n" - " 5c:e8:2c:f1:b2:64:69:cf:15:bd:f8:bb:58:95:73:\n" - " 58:38:95:b4:7a:cf:84:29:a6:c2:db:f0:bd:ef:97:\n" - " 26:d4:99:ac:d7:c7:be:b0:0d:11:f4:26:86:2d:77:\n" - " 42:52:25:d7:56:c7:e3:97:b1:36:5c:97:71:d0:9b:\n" - " f5:b5:50:8d:f9:ff:fb:10:77:3c:b5:53:6d:a1:43:\n" - " 35:a9:03:32:05:ab:d7:f5:d1:19:bd:5f:92:a3:00:\n" - " 2a:79:37:a4:76:4f:e9:32:0d:e4:86:bb:ea:c3:1a:\n" - " c5:33:e8:16:d4:a5:d8:e0:e8:bb:c2:f0:22:15:e2:\n" - " d9:8c:ae:ac:7d:2b:bf:eb:a3:4c:3b:29:1d:94:ac:\n" - " a3:bb:6d:ba:6d:03:91:03:cf:46:12:c4:66:21:c5:\n" - " c6:67:d8:11:19:79:01:0e:6e:84:1c:76:6f:11:3d:\n" - " eb:94:89:c5:6a:26:1f:cd:e0:11:8b:51:ee:99:35:\n" - " 69:e5:7f:0b:77:2a:94:e4:4b:64:b9:83:04:30:05:\n" - " e4:a2:e3\n" - " Exponent: 65537 (0x10001)\n" - " X509v3 extensions:\n" - " X509v3 Subject Key Identifier: \n" - " 83:21:DE:EC:C0:79:03:6D:1E:83:F3:E5:97:29:D5:5A:C0:96:40:FA\n" - " X509v3 Authority Key Identifier: \n" - " keyid:8A:22:C6:9C:2E:11:F3:40:0C:CE:82:0C:22:59:FF:F8:7F:D0:B9:13\n" - "\n" - " X509v3 Basic Constraints: critical\n" - " CA:TRUE, pathlen:0\n" - " X509v3 Key Usage: critical\n" - " Digital Signature, Certificate Sign, CRL Sign\n" - " Signature Algorithm: sha256WithRSAEncryption\n" - " a3:6c:85:9a:2e:4e:7e:5d:83:63:0f:f5:4f:a9:7d:ec:0e:6f:\n" - " ae:d7:ba:df:64:e0:46:0e:3d:da:18:15:2c:f3:73:ca:81:b1:\n" - " 10:d9:53:14:21:7d:72:5c:94:88:a5:9d:ad:ab:45:42:c6:64:\n" - " a9:d9:2e:4e:29:47:2c:b1:95:07:b7:62:48:68:1f:68:13:1c:\n" - " d2:a0:fb:5e:38:24:4a:82:0a:87:c9:93:20:43:7e:e9:f9:79:\n" - " ef:03:a2:bd:9e:24:6b:0a:01:5e:4a:36:c5:7d:7a:fe:d6:aa:\n" - " 2f:c2:8c:38:8a:99:3c:b0:6a:e5:60:be:56:d6:eb:60:03:55:\n" - " 24:42:a0:1a:fa:91:24:a3:53:15:75:5d:c8:eb:7c:1e:68:5a:\n" - " 7e:13:34:e3:85:37:1c:76:3f:77:67:1b:ed:1b:52:17:fc:4a:\n" - " a3:e2:74:84:80:2c:69:fc:dd:7d:26:97:c4:2a:69:7d:9c:dc:\n" - " 61:97:70:29:a7:3f:2b:5b:2b:22:51:fd:fe:6a:5d:f9:e7:14:\n" - " 48:b7:2d:c8:33:58:fc:f2:5f:27:f7:26:16:be:be:b5:aa:a2:\n" - " 64:53:3c:69:e8:b5:61:eb:ab:91:a5:b4:09:9b:f6:98:b8:5c:\n" - " 5b:24:2f:93:f5:2b:9c:8c:58:fb:26:3f:67:53:d7:42:64:e8:\n" - " 79:77:73:41:4e:e3:02:39:0b:b6:68:97:8b:84:e8:1d:83:a8:\n" - " 15:f1:06:46:47:80:42:5e:14:e2:61:8a:76:84:d5:d4:71:7f:\n" - " 4e:ff:d9:74:87:ff:32:c5:87:20:0a:d4:59:40:3e:d8:17:ef:\n" - " da:65:e9:0a:51:fe:1e:c3:46:91:d2:ee:e4:23:57:97:87:d4:\n" - " a6:a5:eb:ef:81:6a:d8:8c:d6:1f:8e:b1:18:4c:6b:89:32:55:\n" - " 53:68:26:9e:bb:03:be:2c:e9:8b:ff:97:9c:1c:ac:28:c3:9f:\n" - " 0b:b7:93:23:24:31:63:e4:19:13:f2:bb:08:71:b7:c5:c5:c4:\n" - " 10:ff:dc:fc:33:54:a4:5e:ec:a3:fe:0a:80:ca:9c:bc:95:6f:\n" - " 5f:39:91:3b:61:69:16:94:0f:57:4b:fc:4b:b1:be:72:98:5d:\n" - " 10:f9:08:a7:d6:e0:e8:3d:5d:54:7d:fa:4b:6a:dd:98:41:ed:\n" - " 84:a1:39:67:5c:6c:7f:0c:b0:e1:98:c1:14:ed:fe:1e:e8:05:\n" - " 8d:7f:6a:24:cb:1b:05:42:0d:7f:13:ba:ca:b5:91:db:a5:f0:\n" - " 40:2b:70:7a:2a:a5:5d:ed:56:0c:f0:c2:72:ee:63:dd:cb:5d:\n" - " 76:f6:08:e6:e6:30:ef:3a:b2:16:34:41:a4:e1:30:14:bc:c7:\n" - " f9:23:3a:1a:70:df:b8:cc\n" - "-----BEGIN CERTIFICATE-----\n" - "MIIGSDCCBDCgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgboxCzAJBgNVBAYTAlVT\n" - "MRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRMw\n" - "EQYDVQQKDApMaW5kZW4gTGFiMSAwHgYDVQQLDBdTZWNvbmQgTGlmZSBFbmdpbmVl\n" - "cmluZzEhMB8GA1UEAwwYSW50ZWdyYXRpb24gVGVzdCBSb290IENBMSQwIgYJKoZI\n" - "hvcNAQkBFhVub3JlcGx5QGxpbmRlbmxhYi5jb20wHhcNMTgwNTIyMjIzOTA4WhcN\n" - "MjgwNTE5MjIzOTA4WjCBqjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3Ju\n" - "aWExEzARBgNVBAoMCkxpbmRlbiBMYWIxIDAeBgNVBAsMF1NlY29uZCBMaWZlIEVu\n" - "Z2luZWVyaW5nMSkwJwYDVQQDDCBJbnRlZ3JhdGlvbiBUZXN0IEludGVybWVkaWF0\n" - "ZSBDQTEkMCIGCSqGSIb3DQEJARYVbm9yZXBseUBsaW5kZW5sYWIuY29tMIICIjAN\n" - "BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzqNw4sT7S5eQoTC7wRsTuap+Rhej\n" - "Jo1pP15zlehqsQq0j1Bl48ZcOSQ03wu3zM5iDDZaEiz+NUzpHKyAXiSZ16q9vkjA\n" - "YmR3NohmzvSo3dJ2JGKQVUH8HRNOp05XvKikWUssWhzYzBbe6IgwyZXfL6YUKA/r\n" - "NEYSWLraDubenBX29OOfdKpwiXmL6Vp7GFQVlDojCmV4BdkzkCrOFRgNUvxcMWUg\n" - "0BI3jBGAutSwgnMAS0m+y9a8581h8wCYmXRaN4FJln4UARuG0tAGlEBjY0YR/DNc\n" - "vTpe1OVER2RQvaaXVXBkmybM3iCCkGqDQZxvcUcUvstofIW+7y52EhnTyYcytKxg\n" - "IBYoLa+86AHGf/vYEdX0txS9Jwhbcr4J4JHInHu0sxLvMja+sbmit+NpRzB2upyb\n" - "GZlNU91c6CzxsmRpzxW9+LtYlXNYOJW0es+EKabC2/C975cm1Jms18e+sA0R9CaG\n" - "LXdCUiXXVsfjl7E2XJdx0Jv1tVCN+f/7EHc8tVNtoUM1qQMyBavX9dEZvV+SowAq\n" - "eTekdk/pMg3khrvqwxrFM+gW1KXY4Oi7wvAiFeLZjK6sfSu/66NMOykdlKyju226\n" - "bQORA89GEsRmIcXGZ9gRGXkBDm6EHHZvET3rlInFaiYfzeARi1HumTVp5X8LdyqU\n" - "5EtkuYMEMAXkouMCAwEAAaNmMGQwHQYDVR0OBBYEFIMh3uzAeQNtHoPz5Zcp1VrA\n" - "lkD6MB8GA1UdIwQYMBaAFIoixpwuEfNADM6CDCJZ//h/0LkTMBIGA1UdEwEB/wQI\n" - "MAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQCjbIWa\n" - "Lk5+XYNjD/VPqX3sDm+u17rfZOBGDj3aGBUs83PKgbEQ2VMUIX1yXJSIpZ2tq0VC\n" - "xmSp2S5OKUcssZUHt2JIaB9oExzSoPteOCRKggqHyZMgQ37p+XnvA6K9niRrCgFe\n" - "SjbFfXr+1qovwow4ipk8sGrlYL5W1utgA1UkQqAa+pEko1MVdV3I63weaFp+EzTj\n" - "hTccdj93ZxvtG1IX/Eqj4nSEgCxp/N19JpfEKml9nNxhl3Appz8rWysiUf3+al35\n" - "5xRIty3IM1j88l8n9yYWvr61qqJkUzxp6LVh66uRpbQJm/aYuFxbJC+T9SucjFj7\n" - "Jj9nU9dCZOh5d3NBTuMCOQu2aJeLhOgdg6gV8QZGR4BCXhTiYYp2hNXUcX9O/9l0\n" - "h/8yxYcgCtRZQD7YF+/aZekKUf4ew0aR0u7kI1eXh9SmpevvgWrYjNYfjrEYTGuJ\n" - "MlVTaCaeuwO+LOmL/5ecHKwow58Lt5MjJDFj5BkT8rsIcbfFxcQQ/9z8M1SkXuyj\n" - "/gqAypy8lW9fOZE7YWkWlA9XS/xLsb5ymF0Q+Qin1uDoPV1UffpLat2YQe2EoTln\n" - "XGx/DLDhmMEU7f4e6AWNf2okyxsFQg1/E7rKtZHbpfBAK3B6KqVd7VYM8MJy7mPd\n" - "y1129gjm5jDvOrIWNEGk4TAUvMf5IzoacN+4zA==\n" - "-----END CERTIFICATE-----\n" - ); - - const std::string mPemChildCert( - "Certificate:\n" - " Data:\n" - " Version: 3 (0x2)\n" - " Serial Number: 4096 (0x1000)\n" - " Signature Algorithm: sha256WithRSAEncryption\n" - " Issuer: C=US, ST=California, O=Linden Lab, OU=Second Life Engineering, CN=Integration Test Intermediate CA/emailAddress=noreply@lindenlab.com\n" - " Validity\n" - " Not Before: May 22 22:58:15 2018 GMT\n" - " Not After : Jul 19 22:58:15 2024 GMT\n" - " Subject: C=US, ST=California, L=San Francisco, O=Linden Lab, OU=Second Life Engineering, CN=Integration Test Server Cert/emailAddress=noreply@lindenlab.com\n" - " Subject Public Key Info:\n" - " Public Key Algorithm: rsaEncryption\n" - " Public-Key: (2048 bit)\n" - " Modulus:\n" - " 00:bf:a1:1c:76:82:4a:10:1d:25:0e:02:e2:7a:64:\n" - " 54:c7:94:c5:c0:98:d5:35:f3:cb:cb:30:ba:31:9c:\n" - " bd:4c:2f:4a:4e:24:03:4b:87:5c:c1:5c:fe:d9:89:\n" - " 3b:cb:01:bc:eb:a5:b7:78:dc:b3:58:e5:78:a7:15:\n" - " 34:50:30:aa:16:3a:b2:94:17:6d:1e:7f:b2:70:1e:\n" - " 96:41:bb:1d:e3:22:80:fa:dc:00:6a:fb:34:3e:67:\n" - " e7:c2:21:2f:1b:d3:af:04:49:91:eb:bb:60:e0:26:\n" - " 52:75:28:8a:08:5b:91:56:4e:51:50:40:51:70:af:\n" - " cb:80:66:c8:59:e9:e2:48:a8:62:d0:26:67:80:0a:\n" - " 12:16:d1:f6:15:9e:1f:f5:92:37:f3:c9:2f:03:9e:\n" - " 22:f6:60:5a:76:45:8c:01:2c:99:54:72:19:db:b7:\n" - " 72:e6:5a:69:f3:e9:31:65:5d:0f:c7:5c:9c:17:29:\n" - " 71:14:7f:db:47:c9:1e:65:a2:41:b0:2f:14:17:ec:\n" - " 4b:25:f2:43:8f:b4:a3:8d:37:1a:07:34:b3:29:bb:\n" - " 8a:44:8e:84:08:a2:1b:76:7a:cb:c2:39:2f:6e:e3:\n" - " fc:d6:91:b5:1f:ce:58:91:57:70:35:6e:25:a9:48:\n" - " 0e:07:cf:4e:dd:16:42:65:cf:8a:42:b3:27:e6:fe:\n" - " 6a:e3\n" - " Exponent: 65537 (0x10001)\n" - " X509v3 extensions:\n" - " X509v3 Basic Constraints: \n" - " CA:FALSE\n" - " Netscape Cert Type: \n" - " SSL Server\n" - " Netscape Comment: \n" - " OpenSSL Generated Server Certificate\n" - " X509v3 Subject Key Identifier: \n" - " BB:59:9F:DE:6B:51:A7:6C:B3:6D:5B:8B:42:F7:B1:65:77:17:A4:E4\n" - " X509v3 Authority Key Identifier: \n" - " keyid:83:21:DE:EC:C0:79:03:6D:1E:83:F3:E5:97:29:D5:5A:C0:96:40:FA\n" - " DirName:/C=US/ST=California/L=San Francisco/O=Linden Lab/OU=Second Life Engineering/CN=Integration Test Root CA/emailAddress=noreply@lindenlab.com\n" - " serial:10:00\n" - "\n" - " X509v3 Key Usage: critical\n" - " Digital Signature, Key Encipherment\n" - " X509v3 Extended Key Usage: \n" - " TLS Web Server Authentication\n" - " Signature Algorithm: sha256WithRSAEncryption\n" - " 18:a6:58:55:9b:d4:af:7d:8a:27:d3:28:3a:4c:4b:42:4e:f0:\n" - " 30:d6:d9:95:11:48:12:0a:96:40:d9:2b:21:39:c5:d4:8d:e5:\n" - " 10:bc:68:78:69:0b:9f:15:4a:0b:f1:ab:99:45:0c:20:5f:27:\n" - " df:e7:14:2d:4a:30:f2:c2:8d:37:73:36:1a:27:55:5a:08:5f:\n" - " 71:a1:5e:05:83:b2:59:fe:02:5e:d7:4a:30:15:23:58:04:cf:\n" - " 48:cc:b0:71:88:9c:6b:57:f0:04:0a:d3:a0:64:6b:ee:f3:5f:\n" - " ea:ac:e1:2b:b9:7f:79:b8:db:ce:72:48:72:db:c8:5c:38:72:\n" - " 31:55:d0:ff:6b:bd:73:23:a7:30:18:5d:ed:47:18:0a:67:8e:\n" - " 53:32:0e:99:9b:96:72:45:7f:c6:00:2c:5d:1a:97:53:75:3a:\n" - " 0b:49:3d:3a:00:37:14:67:0c:28:97:34:87:aa:c5:32:e4:ae:\n" - " 34:83:12:4a:10:f7:0e:74:d4:5f:73:bd:ef:0c:b7:d8:0a:7d:\n" - " 8e:8d:5a:48:bd:f4:8e:7b:f9:4a:15:3b:61:c9:5e:40:59:6e:\n" - " c7:a8:a4:02:28:72:c5:54:8c:77:f4:55:a7:86:c0:38:a0:68:\n" - " 19:da:0f:72:5a:a9:7e:69:9f:9c:3a:d6:66:aa:e1:f4:fd:f9:\n" - " b8:4b:6c:71:9e:f0:38:02:c7:6a:9e:dc:e6:fb:ef:23:59:4f:\n" - " 5c:84:0a:df:ea:86:1f:fd:0e:5c:fa:c4:e5:50:1c:10:cf:89:\n" - " 4e:08:0e:4c:4b:61:1a:49:12:f7:e9:4b:17:71:43:7b:6d:b6:\n" - " b5:9f:d4:3b:c7:88:53:48:63:b6:00:80:8f:49:0a:c5:7e:58:\n" - " ac:78:d8:b9:06:b0:bc:86:e2:2e:48:5b:c3:24:fa:aa:72:d8:\n" - " ec:f6:c7:91:9f:0f:c8:b5:fd:2b:b2:a7:bc:2f:40:20:2b:47:\n" - " e0:d1:1d:94:52:6f:6b:be:12:b6:8c:dc:11:db:71:e6:19:ef:\n" - " a8:71:8b:ad:d3:32:c0:1c:a4:3f:b3:0f:af:e5:50:e1:ff:41:\n" - " a4:b7:6f:57:71:af:fd:16:4c:e8:24:b3:99:1b:cf:12:8f:43:\n" - " 05:80:ba:18:19:0a:a5:ec:49:81:41:4c:7e:28:b2:21:f2:59:\n" - " 6e:4a:ed:de:f9:fa:99:85:60:1f:e6:c2:42:5c:08:00:3c:84:\n" - " 06:a9:24:d4:cf:7b:6e:1b:59:1d:f4:70:16:03:a1:e0:0b:00:\n" - " 95:5c:39:03:fc:9d:1c:8e:f7:59:0c:61:47:f6:7f:07:22:48:\n" - " 83:40:ac:e1:98:5f:c7:be:05:d5:29:2b:bf:0d:03:0e:e9:5e:\n" - " 2b:dd:09:18:fe:5e:30:61\n" - "-----BEGIN CERTIFICATE-----\n" - "MIIGbjCCBFagAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgaoxCzAJBgNVBAYTAlVT\n" - "MRMwEQYDVQQIDApDYWxpZm9ybmlhMRMwEQYDVQQKDApMaW5kZW4gTGFiMSAwHgYD\n" - "VQQLDBdTZWNvbmQgTGlmZSBFbmdpbmVlcmluZzEpMCcGA1UEAwwgSW50ZWdyYXRp\n" - "b24gVGVzdCBJbnRlcm1lZGlhdGUgQ0ExJDAiBgkqhkiG9w0BCQEWFW5vcmVwbHlA\n" - "bGluZGVubGFiLmNvbTAeFw0xODA1MjIyMjU4MTVaFw0yNDA3MTkyMjU4MTVaMIG+\n" - "MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2Fu\n" - "IEZyYW5jaXNjbzETMBEGA1UECgwKTGluZGVuIExhYjEgMB4GA1UECwwXU2Vjb25k\n" - "IExpZmUgRW5naW5lZXJpbmcxJTAjBgNVBAMMHEludGVncmF0aW9uIFRlc3QgU2Vy\n" - "dmVyIENlcnQxJDAiBgkqhkiG9w0BCQEWFW5vcmVwbHlAbGluZGVubGFiLmNvbTCC\n" - "ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL+hHHaCShAdJQ4C4npkVMeU\n" - "xcCY1TXzy8swujGcvUwvSk4kA0uHXMFc/tmJO8sBvOult3jcs1jleKcVNFAwqhY6\n" - "spQXbR5/snAelkG7HeMigPrcAGr7ND5n58IhLxvTrwRJkeu7YOAmUnUoighbkVZO\n" - "UVBAUXCvy4BmyFnp4kioYtAmZ4AKEhbR9hWeH/WSN/PJLwOeIvZgWnZFjAEsmVRy\n" - "Gdu3cuZaafPpMWVdD8dcnBcpcRR/20fJHmWiQbAvFBfsSyXyQ4+0o403Ggc0sym7\n" - "ikSOhAiiG3Z6y8I5L27j/NaRtR/OWJFXcDVuJalIDgfPTt0WQmXPikKzJ+b+auMC\n" - "AwEAAaOCAYYwggGCMAkGA1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgZAMDMGCWCG\n" - "SAGG+EIBDQQmFiRPcGVuU1NMIEdlbmVyYXRlZCBTZXJ2ZXIgQ2VydGlmaWNhdGUw\n" - "HQYDVR0OBBYEFLtZn95rUadss21bi0L3sWV3F6TkMIHoBgNVHSMEgeAwgd2AFIMh\n" - "3uzAeQNtHoPz5Zcp1VrAlkD6oYHApIG9MIG6MQswCQYDVQQGEwJVUzETMBEGA1UE\n" - "CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzETMBEGA1UECgwK\n" - "TGluZGVuIExhYjEgMB4GA1UECwwXU2Vjb25kIExpZmUgRW5naW5lZXJpbmcxITAf\n" - "BgNVBAMMGEludGVncmF0aW9uIFRlc3QgUm9vdCBDQTEkMCIGCSqGSIb3DQEJARYV\n" - "bm9yZXBseUBsaW5kZW5sYWIuY29tggIQADAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0l\n" - "BAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggIBABimWFWb1K99iifTKDpM\n" - "S0JO8DDW2ZURSBIKlkDZKyE5xdSN5RC8aHhpC58VSgvxq5lFDCBfJ9/nFC1KMPLC\n" - "jTdzNhonVVoIX3GhXgWDsln+Al7XSjAVI1gEz0jMsHGInGtX8AQK06Bka+7zX+qs\n" - "4Su5f3m4285ySHLbyFw4cjFV0P9rvXMjpzAYXe1HGApnjlMyDpmblnJFf8YALF0a\n" - "l1N1OgtJPToANxRnDCiXNIeqxTLkrjSDEkoQ9w501F9zve8Mt9gKfY6NWki99I57\n" - "+UoVO2HJXkBZbseopAIocsVUjHf0VaeGwDigaBnaD3JaqX5pn5w61maq4fT9+bhL\n" - "bHGe8DgCx2qe3Ob77yNZT1yECt/qhh/9Dlz6xOVQHBDPiU4IDkxLYRpJEvfpSxdx\n" - "Q3tttrWf1DvHiFNIY7YAgI9JCsV+WKx42LkGsLyG4i5IW8Mk+qpy2Oz2x5GfD8i1\n" - "/Suyp7wvQCArR+DRHZRSb2u+EraM3BHbceYZ76hxi63TMsAcpD+zD6/lUOH/QaS3\n" - "b1dxr/0WTOgks5kbzxKPQwWAuhgZCqXsSYFBTH4osiHyWW5K7d75+pmFYB/mwkJc\n" - "CAA8hAapJNTPe24bWR30cBYDoeALAJVcOQP8nRyO91kMYUf2fwciSINArOGYX8e+\n" - "BdUpK78NAw7pXivdCRj+XjBh\n" - "-----END CERTIFICATE-----\n" - ); - - // Test wrapper declaration : wrapping nothing for the moment - struct sechandler_basic_test - { - X509 *mX509TestCert, *mX509RootCert, *mX509IntermediateCert, *mX509ChildCert; - LLSD mValidationDate; - - sechandler_basic_test() - { - LLMachineID::init(); - OpenSSL_add_all_algorithms(); - OpenSSL_add_all_ciphers(); - OpenSSL_add_all_digests(); - ERR_load_crypto_strings(); - gFirstName = ""; - gLastName = ""; - mValidationDate[CERT_VALIDATION_DATE] = LLDate("2017-04-11T00:00:00.00Z"); - LLFile::remove("test_password.dat"); - LLFile::remove("sechandler_settings.tmp"); - - mX509TestCert = NULL; - mX509RootCert = NULL; - mX509IntermediateCert = NULL; - mX509ChildCert = NULL; - - // Read each of the 4 Pem certs and store in mX509*Cert pointers - BIO * validation_bio; - validation_bio = BIO_new_mem_buf((void*)mPemTestCert.c_str(), mPemTestCert.length()); - PEM_read_bio_X509(validation_bio, &mX509TestCert, 0, NULL); - BIO_free(validation_bio); - - validation_bio = BIO_new_mem_buf((void*)mPemRootCert.c_str(), mPemRootCert.length()); - PEM_read_bio_X509(validation_bio, &mX509RootCert, 0, NULL); - BIO_free(validation_bio); - - validation_bio = BIO_new_mem_buf((void*)mPemIntermediateCert.c_str(), mPemIntermediateCert.length()); - PEM_read_bio_X509(validation_bio, &mX509IntermediateCert, 0, NULL); - BIO_free(validation_bio); - - validation_bio = BIO_new_mem_buf((void*)mPemChildCert.c_str(), mPemChildCert.length()); - PEM_read_bio_X509(validation_bio, &mX509ChildCert, 0, NULL); - BIO_free(validation_bio); - } - ~sechandler_basic_test() - { - LLFile::remove("test_password.dat"); - LLFile::remove("sechandler_settings.tmp"); - LLFile::remove("mycertstore.pem"); - X509_free(mX509TestCert); - X509_free(mX509RootCert); - X509_free(mX509IntermediateCert); - X509_free(mX509ChildCert); - } - }; - - // Tut templating thingamagic: test group, object and test instance - typedef test_group sechandler_basic_test_factory; - typedef sechandler_basic_test_factory::object sechandler_basic_test_object; - tut::sechandler_basic_test_factory tut_test("LLSecHandler"); - - // --------------------------------------------------------------------------------------- - // Test functions - // --------------------------------------------------------------------------------------- - // test cert data retrieval - template<> template<> - void sechandler_basic_test_object::test<1>() - { - try - { - LLPointer test_cert(new LLBasicCertificate(mPemTestCert, &mValidationDate)); - LL_INFOS() << "ok" << LL_ENDL; - } - catch (LLCertException& cert_exception) - { - LL_INFOS() << "cert ex: " << cert_exception.getCertData() << LL_ENDL; - fail("cert exception"); - } - catch (...) - { - LOG_UNHANDLED_EXCEPTION("test 1"); - fail("other exception"); - } - } - - template<> template<> - void sechandler_basic_test_object::test<2>() - { - LLPointer test_cert(new LLBasicCertificate(mPemChildCert, &mValidationDate)); - - LLSD llsd_cert; - test_cert->getLLSD(llsd_cert); - //std::ostringstream llsd_value; - //llsd_value << LLSDOStreamer(llsd_cert) << std::endl; - LL_DEBUGS() << "test 1 cert " << llsd_cert << LL_ENDL; - - ensure_equals("Issuer Name/commonName", (std::string)llsd_cert["issuer_name"]["commonName"], "Integration Test Intermediate CA"); - ensure_equals("Issuer Name/countryName", (std::string)llsd_cert["issuer_name"]["countryName"], "US"); - ensure_equals("Issuer Name/state", (std::string)llsd_cert["issuer_name"]["stateOrProvinceName"], "California"); - ensure_equals("Issuer Name/org name", (std::string)llsd_cert["issuer_name"]["organizationName"], "Linden Lab"); - ensure_equals("Issuer Name/org unit", (std::string)llsd_cert["issuer_name"]["organizationalUnitName"], "Second Life Engineering"); - ensure_equals("Issuer name string", (std::string)llsd_cert["issuer_name_string"], - "emailAddress=noreply@lindenlab.com,CN=Integration Test Intermediate CA,OU=Second Life Engineering,O=Linden Lab,ST=California,C=US"); - ensure_equals("subject Name/commonName", (std::string)llsd_cert["subject_name"]["commonName"], - "Integration Test Server Cert"); - ensure_equals("subject Name/countryName", (std::string)llsd_cert["subject_name"]["countryName"], "US"); - ensure_equals("subject Name/state", (std::string)llsd_cert["subject_name"]["stateOrProvinceName"], "California"); - ensure_equals("subject Name/localityName", (std::string)llsd_cert["subject_name"]["localityName"], "San Francisco"); - ensure_equals("subject Name/org name", (std::string)llsd_cert["subject_name"]["organizationName"], "Linden Lab"); - ensure_equals("subjectName/org unit", - (std::string)llsd_cert["subject_name"]["organizationalUnitName"], "Second Life Engineering"); - - ensure_equals("subject name string", - (std::string)llsd_cert["subject_name_string"], - "emailAddress=noreply@lindenlab.com,CN=Integration Test Server Cert,OU=Second Life Engineering,O=Linden Lab,L=San Francisco,ST=California,C=US"); - ensure_equals("serial number", (std::string)llsd_cert["serial_number"], "1000"); - ensure_equals("valid from", (std::string)llsd_cert["valid_from"], "2018-05-22T22:58:15Z"); - ensure_equals("valid to", (std::string)llsd_cert["valid_to"], "2024-07-19T22:58:15Z"); - LLSD expectedKeyUsage = LLSD::emptyArray(); - expectedKeyUsage.append(LLSD((std::string)"digitalSignature")); - expectedKeyUsage.append(LLSD((std::string)"keyEncipherment")); - ensure("key usage", valueCompareLLSD(llsd_cert["keyUsage"], expectedKeyUsage)); - ensure_equals("basic constraints", llsd_cert["basicConstraints"]["CA"].asInteger(), 0); - - ensure("x509 is equal", !X509_cmp(mX509ChildCert, test_cert->getOpenSSLX509())); - } - - - // test protected data - template<> template<> - void sechandler_basic_test_object::test<3>() - { - std::string protected_data = "sUSh3wj77NG9oAMyt3XIhaej3KLZhLZWFZvI6rIGmwUUOmmelrRg0NI9rkOj8ZDpTPxpwToaBT5u" - "GQhakdaGLJznr9bHr4/6HIC1bouKj4n2rs4TL6j2WSjto114QdlNfLsE8cbbE+ghww58g8SeyLQO" - "nyzXoz+/PBz0HD5SMFDuObccoPW24gmqYySz8YoEWhSwO0pUtEEqOjVRsAJgF5wLAtJZDeuilGsq" - "4ZT9Y4wZ9Rh8nnF3fDUL6IGamHe1ClXM1jgBu10F6UMhZbnH4C3aJ2E9+LiOntU+l3iCb2MpkEpr" - "82r2ZAMwIrpnirL/xoYoyz7MJQYwUuMvBPToZJrxNSsjI+S2Z+I3iEJAELMAAA=="; - - std::vector binary_data(apr_base64_decode_len(protected_data.c_str())); - apr_base64_decode_binary(&binary_data[0], protected_data.c_str()); - - LLXORCipher cipher(gMACAddress, MAC_ADDRESS_BYTES); - cipher.decrypt(&binary_data[0], 16); - unsigned char unique_id[MAC_ADDRESS_BYTES]; - LLMachineID::getUniqueID(unique_id, sizeof(unique_id)); - LLXORCipher cipher2(unique_id, sizeof(unique_id)); - cipher2.encrypt(&binary_data[0], 16); - std::ofstream temp_file("sechandler_settings.tmp", std::ofstream::binary); - temp_file.write((const char *)&binary_data[0], binary_data.size()); - temp_file.close(); - - LLPointer handler = new LLSecAPIBasicHandler("sechandler_settings.tmp", - "test_password.dat"); - handler->init(); - // data retrieval for existing data - LLSD data = handler->getProtectedData("test_data_type", "test_data_id"); - - - ensure_equals("retrieve existing data1", (std::string)data["data1"], "test_data_1"); - ensure_equals("retrieve existing data2", (std::string)data["data2"], "test_data_2"); - ensure_equals("retrieve existing data3", (std::string)data["data3"]["elem1"], "test element1"); - - // data storage - LLSD store_data = LLSD::emptyMap(); - store_data["store_data1"] = "test_store_data1"; - store_data["store_data2"] = 27; - store_data["store_data3"] = LLSD::emptyMap(); - store_data["store_data3"]["subelem1"] = "test_subelem1"; - - handler->setProtectedData("test_data_type", "test_data_id1", store_data); - data = handler->getProtectedData("test_data_type", "test_data_id"); - - data = handler->getProtectedData("test_data_type", "test_data_id"); - // verify no overwrite of existing data - ensure_equals("verify no overwrite 1", (std::string)data["data1"], "test_data_1"); - ensure_equals("verify no overwrite 2", (std::string)data["data2"], "test_data_2"); - ensure_equals("verify no overwrite 3", (std::string)data["data3"]["elem1"], "test element1"); - - // verify written data is good - data = handler->getProtectedData("test_data_type", "test_data_id1"); - ensure_equals("verify stored data1", (std::string)data["store_data1"], "test_store_data1"); - ensure_equals("verify stored data2", (int)data["store_data2"], 27); - ensure_equals("verify stored data3", (std::string)data["store_data3"]["subelem1"], "test_subelem1"); - - // verify overwrite works - handler->setProtectedData("test_data_type", "test_data_id", store_data); - data = handler->getProtectedData("test_data_type", "test_data_id"); - ensure_equals("verify overwrite stored data1", (std::string)data["store_data1"], "test_store_data1"); - ensure_equals("verify overwrite stored data2", (int)data["store_data2"], 27); - ensure_equals("verify overwrite stored data3", (std::string)data["store_data3"]["subelem1"], "test_subelem1"); - - // verify other datatype doesn't conflict - store_data["store_data3"] = "test_store_data3"; - store_data["store_data4"] = 28; - store_data["store_data5"] = LLSD::emptyMap(); - store_data["store_data5"]["subelem2"] = "test_subelem2"; - - handler->setProtectedData("test_data_type1", "test_data_id", store_data); - data = handler->getProtectedData("test_data_type1", "test_data_id"); - ensure_equals("verify datatype stored data3", (std::string)data["store_data3"], "test_store_data3"); - ensure_equals("verify datatype stored data4", (int)data["store_data4"], 28); - ensure_equals("verify datatype stored data5", (std::string)data["store_data5"]["subelem2"], "test_subelem2"); - - // test data not found - - data = handler->getProtectedData("test_data_type1", "test_data_not_found"); - ensure("not found", data.isUndefined()); - - // cause a 'write' by using 'LLPointer' to delete then instantiate a handler - handler = NULL; - handler = new LLSecAPIBasicHandler("sechandler_settings.tmp", "test_password.dat"); - handler->init(); - - data = handler->getProtectedData("test_data_type1", "test_data_id"); - ensure_equals("verify datatype stored data3a", (std::string)data["store_data3"], "test_store_data3"); - ensure_equals("verify datatype stored data4a", (int)data["store_data4"], 28); - ensure_equals("verify datatype stored data5a", (std::string)data["store_data5"]["subelem2"], "test_subelem2"); - - // rewrite the initial file to verify reloads - handler = NULL; - std::ofstream temp_file2("sechandler_settings.tmp", std::ofstream::binary); - temp_file2.write((const char *)&binary_data[0], binary_data.size()); - temp_file2.close(); - - // cause a 'write' - handler = new LLSecAPIBasicHandler("sechandler_settings.tmp", "test_password.dat"); - handler->init(); - data = handler->getProtectedData("test_data_type1", "test_data_id"); - ensure("not found", data.isUndefined()); - - handler->deleteProtectedData("test_data_type", "test_data_id"); - ensure("Deleted data not found", handler->getProtectedData("test_data_type", "test_data_id").isUndefined()); - - LLFile::remove("sechandler_settings.tmp"); - handler = new LLSecAPIBasicHandler("sechandler_settings.tmp", "test_password.dat"); - handler->init(); - data = handler->getProtectedData("test_data_type1", "test_data_id"); - ensure("not found", data.isUndefined()); - handler = NULL; - - ensure(LLFile::isfile("sechandler_settings.tmp")); - } - - // test credenitals - template<> template<> - void sechandler_basic_test_object::test<4>() - { - LLPointer handler = new LLSecAPIBasicHandler("sechandler_settings.tmp", "test_password.dat"); - handler->init(); - - LLSD my_id = LLSD::emptyMap(); - LLSD my_authenticator = LLSD::emptyMap(); - my_id["type"] = "test_type"; - my_id["username"] = "testuser@lindenlab.com"; - my_authenticator["type"] = "test_auth"; - my_authenticator["creds"] = "12345"; - - // test creation of credentials - LLPointer my_cred = handler->createCredential("my_grid", my_id, my_authenticator); - - // test retrieval of credential components - ensure_equals("basic credential creation: identifier", my_id, my_cred->getIdentifier()); - ensure_equals("basic credential creation: authenticator", my_authenticator, my_cred->getAuthenticator()); - ensure_equals("basic credential creation: grid", "my_grid", my_cred->getGrid()); - - // test setting/overwriting of credential components - my_id["first_name"] = "firstname"; - my_id.erase("username"); - my_authenticator.erase("creds"); - my_authenticator["hash"] = "6563245"; - - my_cred->setCredentialData(my_id, my_authenticator); - ensure_equals("set credential data: identifier", my_id, my_cred->getIdentifier()); - ensure_equals("set credential data: authenticator", my_authenticator, my_cred->getAuthenticator()); - ensure_equals("set credential data: grid", "my_grid", my_cred->getGrid()); - - // test loading of a credential, that hasn't been saved, without - // any legacy saved credential data - LLPointer my_new_cred = handler->loadCredential("my_grid2"); - ensure("unknown credential load test", my_new_cred->getIdentifier().isMap()); - ensure("unknown credential load test", !my_new_cred->getIdentifier().has("type")); - ensure("unknown credential load test", my_new_cred->getAuthenticator().isMap()); - ensure("unknown credential load test", !my_new_cred->getAuthenticator().has("type")); - // test saving of a credential - handler->saveCredential(my_cred, true); - - // test loading of a known credential - my_new_cred = handler->loadCredential("my_grid"); - ensure_equals("load a known credential: identifier", my_id, my_new_cred->getIdentifier()); - ensure_equals("load a known credential: authenticator",my_authenticator, my_new_cred->getAuthenticator()); - ensure_equals("load a known credential: grid", "my_grid", my_cred->getGrid()); - - // test deletion of a credential - handler->deleteCredential(my_new_cred); - - ensure("delete credential: identifier", my_new_cred->getIdentifier().isUndefined()); - ensure("delete credentialt: authenticator", my_new_cred->getIdentifier().isUndefined()); - ensure_equals("delete credential: grid", "my_grid", my_cred->getGrid()); - // load unknown cred - - my_new_cred = handler->loadCredential("my_grid"); - ensure("deleted credential load test", my_new_cred->getIdentifier().isMap()); - ensure("deleted credential load test", !my_new_cred->getIdentifier().has("type")); - ensure("deleted credential load test", my_new_cred->getAuthenticator().isMap()); - ensure("deleted credential load test", !my_new_cred->getAuthenticator().has("type")); - - // test loading of an unknown credential with legacy saved username, but without - // saved password - gFirstName = "myfirstname"; - gLastName = "mylastname"; - my_new_cred = handler->loadCredential("my_legacy_grid"); - ensure_equals("legacy credential with no password: type", - (const std::string)my_new_cred->getIdentifier()["type"], "agent"); - ensure_equals("legacy credential with no password: first_name", - (const std::string)my_new_cred->getIdentifier()["first_name"], "myfirstname"); - ensure_equals("legacy credential with no password: last_name", - (const std::string)my_new_cred->getIdentifier()["last_name"], "mylastname"); - - ensure("legacy credential with no password: no authenticator", my_new_cred->getAuthenticator().isUndefined()); - - // test loading of an unknown credential with legacy saved password and username - - std::string hashed_password = "fSQcLG03eyIWJmkzfyYaKm81dSweLmsxeSAYKGE7fSQ="; - int length = apr_base64_decode_len(hashed_password.c_str()); - std::vector decoded_password(length); - apr_base64_decode(&decoded_password[0], hashed_password.c_str()); - LLXORCipher cipher(gMACAddress, MAC_ADDRESS_BYTES); - cipher.decrypt((U8*)&decoded_password[0], length); - unsigned char unique_id[MAC_ADDRESS_BYTES]; - LLMachineID::getUniqueID(unique_id, sizeof(unique_id)); - LLXORCipher cipher2(unique_id, sizeof(unique_id)); - cipher2.encrypt((U8*)&decoded_password[0], length); - llofstream password_file("test_password.dat", std::ofstream::binary); - password_file.write(&decoded_password[0], length); - password_file.close(); - - my_new_cred = handler->loadCredential("my_legacy_grid2"); - ensure_equals("legacy credential with password: type", - (const std::string)my_new_cred->getIdentifier()["type"], "agent"); - ensure_equals("legacy credential with password: first_name", - (const std::string)my_new_cred->getIdentifier()["first_name"], "myfirstname"); - ensure_equals("legacy credential with password: last_name", - (const std::string)my_new_cred->getIdentifier()["last_name"], "mylastname"); - - LLSD legacy_authenticator = my_new_cred->getAuthenticator(); - ensure_equals("legacy credential with password: type", - (std::string)legacy_authenticator["type"], - "hash"); - ensure_equals("legacy credential with password: algorithm", - (std::string)legacy_authenticator["algorithm"], - "md5"); - ensure_equals("legacy credential with password: algorithm", - (std::string)legacy_authenticator["secret"], - "01234567890123456789012345678901"); - - // test creation of credentials - my_cred = handler->createCredential("mysavedgrid", my_id, my_authenticator); - // test save without saving authenticator. - handler->saveCredential(my_cred, false); - my_new_cred = handler->loadCredential("mysavedgrid"); - ensure_equals("saved credential without auth", - (const std::string)my_new_cred->getIdentifier()["type"], "test_type"); - ensure("no authenticator values were saved", my_new_cred->getAuthenticator().isUndefined()); - } - - // test cert vector - template<> template<> - void sechandler_basic_test_object::test<5>() - { - // validate create from empty vector - LLPointer test_vector = new LLBasicCertificateVector(); - ensure_equals("when loading with nothing, we should result in no certs in vector", test_vector->size(), 0); - - test_vector->add(new LLBasicCertificate(mPemTestCert, &mValidationDate)); - ensure_equals("one element in vector", test_vector->size(), 1); - test_vector->add(new LLBasicCertificate(mPemChildCert, &mValidationDate)); - ensure_equals("two elements in vector after add", test_vector->size(), 2); - - // add duplicate; should be a no-op (and log at DEBUG level) - test_vector->add(new LLBasicCertificate(mPemChildCert, &mValidationDate)); - ensure_equals("two elements in vector after re-add", test_vector->size(), 2); - - // validate order - X509* test_cert = (*test_vector)[0]->getOpenSSLX509(); - ensure("first cert added remains first cert", !X509_cmp(test_cert, mX509TestCert)); - X509_free(test_cert); - - test_cert = (*test_vector)[1]->getOpenSSLX509(); - ensure("second cert is second cert", !X509_cmp(test_cert, mX509ChildCert)); - X509_free(test_cert); - - // - // validate iterator - // - LLBasicCertificateVector::iterator current_cert = test_vector->begin(); - LLBasicCertificateVector::iterator copy_current_cert = current_cert; - // operator++(int) - ensure("validate iterator++ element in vector is expected cert", *current_cert++ == (*test_vector)[0]); - ensure("validate 2nd iterator++ element in vector is expected cert", *current_cert++ == (*test_vector)[1]); - ensure("validate end iterator++", current_cert == test_vector->end()); - - // copy - ensure("validate copy iterator element in vector is expected cert", *copy_current_cert == (*test_vector)[0]); - - // operator--(int) - current_cert--; - ensure("validate iterator-- element in vector is expected cert", *current_cert-- == (*test_vector)[1]); - ensure("validate iterator-- element in vector is expected cert", *current_cert == (*test_vector)[0]); - - ensure("begin iterator is equal", current_cert == test_vector->begin()); - - // operator++ - ensure("validate ++iterator element in vector is expected cert", *++current_cert == (*test_vector)[1]); - ensure("end of cert vector after ++iterator", ++current_cert == test_vector->end()); - // operator-- - ensure("validate --iterator element in vector is expected cert", *--current_cert == (*test_vector)[1]); - ensure("validate 2nd --iterator element in vector is expected cert", *--current_cert == (*test_vector)[0]); - - test_vector->erase(test_vector->begin()); - ensure_equals("one element in store after remove", test_vector->size(), 1); - test_cert = (*test_vector)[0]->getOpenSSLX509(); - ensure("Child cert remains", !X509_cmp(test_cert, mX509ChildCert)); - X509_free(test_cert); - - // validate insert - test_vector->insert(test_vector->begin(), new LLBasicCertificate(mPemIntermediateCert, &mValidationDate)); - test_cert = (*test_vector)[0]->getOpenSSLX509(); - ensure_equals("two elements in store after insert", test_vector->size(), 2); - ensure("validate intermediate cert was inserted at first position", !X509_cmp(test_cert, mX509IntermediateCert)); - X509_free(test_cert); - test_cert = (*test_vector)[1]->getOpenSSLX509(); - ensure("validate child cert still there", !X509_cmp(test_cert, mX509ChildCert)); - X509_free(test_cert); - - //validate find - LLSD find_info = LLSD::emptyMap(); - find_info["subjectKeyIdentifier"] = "bb:59:9f:de:6b:51:a7:6c:b3:6d:5b:8b:42:f7:b1:65:77:17:a4:e4"; - LLBasicCertificateVector::iterator found_cert = test_vector->find(find_info); - ensure("found some cert", found_cert != test_vector->end()); - X509* found_x509 = (*found_cert).get()->getOpenSSLX509(); - ensure("child cert was found", !X509_cmp(found_x509, mX509ChildCert)); - X509_free(found_x509); - - find_info["subjectKeyIdentifier"] = "00:00:00:00"; // bogus - current_cert =test_vector->find(find_info); - ensure("didn't find cert", current_cert == test_vector->end()); - } - - // test cert store - template<> template<> - void sechandler_basic_test_object::test<6>() - { - // validate load with nothing - LLFile::remove("mycertstore.pem"); - LLPointer test_store = new LLBasicCertificateStore("mycertstore.pem"); - ensure_equals("when loading with nothing, we should result in no certs in store", test_store->size(), 0); - - // validate load with empty file - test_store->save(); - test_store = NULL; - test_store = new LLBasicCertificateStore("mycertstore.pem"); - ensure_equals("when loading with nothing, we should result in no certs in store", test_store->size(), 0); - test_store=NULL; - - // instantiate a cert store from a file - llofstream certstorefile("mycertstore.pem", std::ios::out); - certstorefile << mPemChildCert << std::endl << mPemTestCert << std::endl; - certstorefile.close(); - // validate loaded certs - test_store = new LLBasicCertificateStore("mycertstore.pem"); - ensure_equals("two elements in store", test_store->size(), 2); - - // operator[] - X509* test_cert = (*test_store)[0]->getOpenSSLX509(); - - ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); - X509_free(test_cert); - test_cert = (*test_store)[1]->getOpenSSLX509(); - ensure("validate second element in store is expected cert", !X509_cmp(test_cert, mX509TestCert)); - X509_free(test_cert); - - - // validate save - LLFile::remove("mycertstore.pem"); - test_store->save(); - test_store = NULL; - test_store = new LLBasicCertificateStore("mycertstore.pem"); - ensure_equals("two elements in store after save", test_store->size(), 2); - LLCertificateStore::iterator current_cert = test_store->begin(); - test_cert = (*current_cert)->getOpenSSLX509(); - ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); - current_cert++; - X509_free(test_cert); - test_cert = (*current_cert)->getOpenSSLX509(); - ensure("validate second element in store is expected cert", !X509_cmp(test_cert, mX509TestCert)); - X509_free(test_cert); - current_cert++; - ensure("end of cert store", current_cert == test_store->end()); - - } - - // cert name wildcard matching - template<> template<> - void sechandler_basic_test_object::test<7>() - { - ensure("simple name match", - _cert_hostname_wildcard_match("foo", "foo")); - - ensure("simple name match, with end period", - _cert_hostname_wildcard_match("foo.", "foo.")); - - ensure("simple name match, with begin period", - _cert_hostname_wildcard_match(".foo", ".foo")); - - ensure("simple name match, with mismatched period cn", - _cert_hostname_wildcard_match("foo.", "foo")); - - ensure("simple name match, with mismatched period hostname", - _cert_hostname_wildcard_match("foo", "foo.")); - - ensure("simple name match, with subdomain", - _cert_hostname_wildcard_match("foo.bar", "foo.bar")); - - ensure("stutter name match", - _cert_hostname_wildcard_match("foobbbbfoo", "foo*bbbfoo")); - - ensure("simple name match, with beginning wildcard", - _cert_hostname_wildcard_match("foobar", "*bar")); - - ensure("simple name match, with ending wildcard", - _cert_hostname_wildcard_match("foobar", "foo*")); - - ensure("simple name match, with beginning null wildcard", - _cert_hostname_wildcard_match("foobar", "*foobar")); - - ensure("simple name match, with ending null wildcard", - _cert_hostname_wildcard_match("foobar", "foobar*")); - - ensure("simple name match, with embedded wildcard", - _cert_hostname_wildcard_match("foobar", "f*r")); - - ensure("simple name match, with embedded null wildcard", - _cert_hostname_wildcard_match("foobar", "foo*bar")); - - ensure("simple name match, with dual embedded wildcard", - _cert_hostname_wildcard_match("foobar", "f*o*ar")); - - ensure("simple name mismatch", - !_cert_hostname_wildcard_match("bar", "foo")); - - ensure("simple name mismatch, with end period", - !_cert_hostname_wildcard_match("foobar.", "foo.")); - - ensure("simple name mismatch, with begin period", - !_cert_hostname_wildcard_match(".foobar", ".foo")); - - ensure("simple name mismatch, with subdomain", - !_cert_hostname_wildcard_match("foobar.bar", "foo.bar")); - - ensure("simple name mismatch, with beginning wildcard", - !_cert_hostname_wildcard_match("foobara", "*bar")); - - ensure("simple name mismatch, with ending wildcard", - !_cert_hostname_wildcard_match("oobar", "foo*")); - - ensure("simple name mismatch, with embedded wildcard", - !_cert_hostname_wildcard_match("oobar", "f*r")); - - ensure("simple name mismatch, with dual embedded wildcard", - !_cert_hostname_wildcard_match("foobar", "f*d*ar")); - - ensure("simple wildcard", - _cert_hostname_wildcard_match("foobar", "*")); - - ensure("long domain", - _cert_hostname_wildcard_match("foo.bar.com", "foo.bar.com")); - - ensure("long domain with multiple wildcards", - _cert_hostname_wildcard_match("foo.bar.com", "*.b*r.com")); - - ensure("end periods", - _cert_hostname_wildcard_match("foo.bar.com.", "*.b*r.com.")); - - ensure("match end period", - _cert_hostname_wildcard_match("foo.bar.com.", "*.b*r.com")); - - ensure("match end period2", - _cert_hostname_wildcard_match("foo.bar.com", "*.b*r.com.")); - - ensure("wildcard mismatch", - !_cert_hostname_wildcard_match("bar.com", "*.bar.com")); - - ensure("wildcard match", - _cert_hostname_wildcard_match("foo.bar.com", "*.bar.com")); - - ensure("wildcard match", - _cert_hostname_wildcard_match("foo.foo.bar.com", "*.bar.com")); - - ensure("wildcard match", - _cert_hostname_wildcard_match("foo.foo.bar.com", "*.*.com")); - - ensure("wildcard mismatch", - !_cert_hostname_wildcard_match("foo.foo.bar.com", "*.foo.com")); - } - - // test cert chain - template<> template<> - void sechandler_basic_test_object::test<8>() - { - // validate create from empty chain - LLPointer test_chain = new LLBasicCertificateChain(NULL); - ensure_equals("when loading with nothing, we should result in no certs in chain", test_chain->size(), 0); - - // Single cert in the chain. - X509_STORE_CTX *test_store = X509_STORE_CTX_new(); - X509_STORE_CTX_set_cert(test_store, mX509ChildCert); - X509_STORE_CTX_set0_untrusted(test_store, NULL); - test_chain = new LLBasicCertificateChain(test_store); - X509_STORE_CTX_free(test_store); - ensure_equals("two elements in store", test_chain->size(), 1); - X509* test_cert = (*test_chain)[0]->getOpenSSLX509(); - ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); - X509_free(test_cert); - - // cert + CA - - test_store = X509_STORE_CTX_new(); - X509_STORE_CTX_set_cert(test_store, mX509ChildCert); - X509_STORE_CTX_set0_untrusted(test_store, sk_X509_new_null()); - sk_X509_push(X509_STORE_CTX_get0_untrusted(test_store), mX509IntermediateCert); - test_chain = new LLBasicCertificateChain(test_store); - X509_STORE_CTX_free(test_store); - ensure_equals("two elements in store", test_chain->size(), 2); - test_cert = (*test_chain)[0]->getOpenSSLX509(); - ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); - X509_free(test_cert); - test_cert = (*test_chain)[1]->getOpenSSLX509(); - ensure("validate second element in store is expected cert", !X509_cmp(test_cert, mX509IntermediateCert)); - X509_free(test_cert); - - // cert + nonrelated - - test_store = X509_STORE_CTX_new(); - X509_STORE_CTX_set_cert(test_store, mX509ChildCert); - X509_STORE_CTX_set0_untrusted(test_store, sk_X509_new_null()); - sk_X509_push(X509_STORE_CTX_get0_untrusted(test_store), mX509TestCert); - test_chain = new LLBasicCertificateChain(test_store); - X509_STORE_CTX_free(test_store); - ensure_equals("two elements in store", test_chain->size(), 1); - test_cert = (*test_chain)[0]->getOpenSSLX509(); - ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); - X509_free(test_cert); - - // cert + CA + nonrelated - test_store = X509_STORE_CTX_new(); - X509_STORE_CTX_set_cert(test_store, mX509ChildCert); - X509_STORE_CTX_set0_untrusted(test_store, sk_X509_new_null()); - sk_X509_push(X509_STORE_CTX_get0_untrusted(test_store), mX509IntermediateCert); - sk_X509_push(X509_STORE_CTX_get0_untrusted(test_store), mX509TestCert); - test_chain = new LLBasicCertificateChain(test_store); - X509_STORE_CTX_free(test_store); - ensure_equals("two elements in store", test_chain->size(), 2); - test_cert = (*test_chain)[0]->getOpenSSLX509(); - ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); - X509_free(test_cert); - test_cert = (*test_chain)[1]->getOpenSSLX509(); - ensure("validate second element in store is expected cert", !X509_cmp(test_cert, mX509IntermediateCert)); - X509_free(test_cert); - - // cert + intermediate + CA - test_store = X509_STORE_CTX_new(); - X509_STORE_CTX_set_cert(test_store, mX509ChildCert); - X509_STORE_CTX_set0_untrusted(test_store, sk_X509_new_null()); - sk_X509_push(X509_STORE_CTX_get0_untrusted(test_store), mX509IntermediateCert); - sk_X509_push(X509_STORE_CTX_get0_untrusted(test_store), mX509RootCert); - test_chain = new LLBasicCertificateChain(test_store); - X509_STORE_CTX_free(test_store); - ensure_equals("three elements in store", test_chain->size(), 3); - test_cert = (*test_chain)[0]->getOpenSSLX509(); - ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); - X509_free(test_cert); - test_cert = (*test_chain)[1]->getOpenSSLX509(); - ensure("validate second element in store is expected cert", !X509_cmp(test_cert, mX509IntermediateCert)); - X509_free(test_cert); - - test_cert = (*test_chain)[2]->getOpenSSLX509(); - ensure("validate second element in store is expected cert", !X509_cmp(test_cert, mX509RootCert)); - X509_free(test_cert); - } - - // test cert validation - template<> template<> - void sechandler_basic_test_object::test<9>() - { - // start with a trusted store with our known root cert - LLFile::remove("mycertstore.pem"); - LLPointer test_store = new LLBasicCertificateStore("mycertstore.pem"); - test_store->add(new LLBasicCertificate(mX509RootCert, &mValidationDate)); - LLSD validation_params; - - // validate basic trust for a chain containing only the intermediate cert. (1 deep) - LLPointer test_chain = new LLBasicCertificateChain(NULL); - - test_chain->add(new LLBasicCertificate(mX509IntermediateCert, &mValidationDate)); - - test_store->validate(0, test_chain, validation_params); - - // add the root certificate to the chain and revalidate - test_chain->add(new LLBasicCertificate(mX509RootCert, &mValidationDate)); - test_store->validate(0, test_chain, validation_params); - - // add the child cert at the head of the chain, and revalidate (3 deep chain) - test_chain->insert(test_chain->begin(), new LLBasicCertificate(mX509ChildCert, &mValidationDate)); - test_store->validate(0, test_chain, validation_params); - - // basic failure cases - test_chain = new LLBasicCertificateChain(NULL); - //validate with only the child cert in chain, but child cert was previously - // trusted - test_chain->add(new LLBasicCertificate(mX509ChildCert, &mValidationDate)); - - // validate without the trust flag. - test_store->validate(VALIDATION_POLICY_TRUSTED, test_chain, validation_params); - - // Validate with child cert but no parent, and no parent in CA store - test_store = new LLBasicCertificateStore("mycertstore.pem"); - ensure_throws("no CA, with only a child cert", - LLCertValidationTrustException, - (*test_chain)[0], - test_store->validate, - VALIDATION_POLICY_TRUSTED, - test_chain, - validation_params); - - - // validate without the trust flag. - test_store->validate(0, test_chain, validation_params); - - // clear out the store - test_store = new LLBasicCertificateStore("mycertstore.pem"); - // append the intermediate cert - test_chain->add(new LLBasicCertificate(mX509IntermediateCert, &mValidationDate)); - ensure_throws("no CA, with child and intermediate certs", - LLCertValidationTrustException, - (*test_chain)[1], - test_store->validate, - VALIDATION_POLICY_TRUSTED | VALIDATION_POLICY_TRUSTED, - test_chain, - validation_params); - // validate without the trust flag - test_store->validate(0, test_chain, validation_params); - - // Test time validity - LLSD child_info; - ((*test_chain)[0])->getLLSD(child_info); - validation_params = LLSD::emptyMap(); - validation_params[CERT_VALIDATION_DATE] = LLDate(child_info[CERT_VALID_FROM].asDate().secondsSinceEpoch() + 1.0); - test_store->validate(VALIDATION_POLICY_TIME | VALIDATION_POLICY_TRUSTED, - test_chain, validation_params); - - validation_params = LLSD::emptyMap(); - validation_params[CERT_VALIDATION_DATE] = child_info[CERT_VALID_FROM].asDate(); - - validation_params[CERT_VALIDATION_DATE] = LLDate(child_info[CERT_VALID_FROM].asDate().secondsSinceEpoch() - 1.0); - - // test not yet valid - ensure_throws("Child cert not yet valid" , - LLCertValidationExpirationException, - (*test_chain)[0], - test_store->validate, - VALIDATION_POLICY_TIME | VALIDATION_POLICY_TRUSTED, - test_chain, - validation_params); - validation_params = LLSD::emptyMap(); - validation_params[CERT_VALIDATION_DATE] = LLDate(child_info[CERT_VALID_TO].asDate().secondsSinceEpoch() + 1.0); - - // test cert expired - ensure_throws("Child cert expired", - LLCertValidationExpirationException, - (*test_chain)[0], - test_store->validate, - VALIDATION_POLICY_TIME | VALIDATION_POLICY_TRUSTED, - test_chain, - validation_params); - - // test SSL KU - // validate basic trust for a chain containing child and intermediate. - test_chain = new LLBasicCertificateChain(NULL); - test_chain->add(new LLBasicCertificate(mX509ChildCert, &mValidationDate)); - test_chain->add(new LLBasicCertificate(mX509IntermediateCert, &mValidationDate)); - test_store->validate(VALIDATION_POLICY_SSL_KU | VALIDATION_POLICY_TRUSTED, - test_chain, validation_params); - - test_chain = new LLBasicCertificateChain(NULL); - test_chain->add(new LLBasicCertificate(mX509TestCert, &mValidationDate)); - - test_store = new LLBasicCertificateStore("mycertstore.pem"); - ensure_throws("Cert doesn't have ku", - LLCertKeyUsageValidationException, - (*test_chain)[0], - test_store->validate, - VALIDATION_POLICY_SSL_KU | VALIDATION_POLICY_TRUSTED, - test_chain, - validation_params); - - test_store->validate(0, test_chain, validation_params); - } - -}; - +/** + * @file llsechandler_basic_test.cpp + * @author Roxie + * @date 2009-02-10 + * @brief Test the 'basic' sec handler functions + * + * $LicenseInfo:firstyear=2005&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#include "../llviewerprecompiledheaders.h" +#include "../test/lltut.h" +#include "../llsecapi.h" +#include "../llsechandler_basic.h" +#include "../../llxml/llcontrol.h" +#include "../llviewernetwork.h" +#include "lluuid.h" +#include "lldate.h" +#include "llxorcipher.h" +#include "apr_base64.h" +#include +#include +#include +#include +#include +#include +#include "llxorcipher.h" +#include +#include +#include +#include +#include +#include +#include +#include "../llmachineid.h" + +#define ensure_throws(str, exc_type, cert, func, ...) \ +try \ +{ \ +func(__VA_ARGS__); \ +fail("throws, " str); \ +} \ +catch(exc_type& except) \ +{ \ +LLSD cert_data; \ +cert->getLLSD(cert_data); \ +ensure("Exception cert is incorrect for " str, valueCompareLLSD(except.getCertData(), cert_data)); \ +} + +extern bool _cert_hostname_wildcard_match(const std::string& hostname, const std::string& wildcard_string); + +//---------------------------------------------------------------------------- +// Mock objects for the dependencies of the code we're testing + +std::string gFirstName; +std::string gLastName; +LLControlGroup::LLControlGroup(const std::string& name) +: LLInstanceTracker(name) {} +LLControlGroup::~LLControlGroup() {} +LLControlVariable* LLControlGroup::declareString(const std::string& name, + const std::string& initial_val, + const std::string& comment, + LLControlVariable::ePersist persist) {return NULL;} +void LLControlGroup::setString(std::string_view name, const std::string& val){} +std::string LLControlGroup::getString(std::string_view name) +{ + + if (name == "FirstName") + return gFirstName; + else if (name == "LastName") + return gLastName; + return ""; +} + +// Stub for --no-verify-ssl-cert +bool LLControlGroup::getBOOL(std::string_view name) { return false; } + +LLSD LLCredential::getLoginParams() +{ + LLSD result = LLSD::emptyMap(); + + // legacy credential + result["passwd"] = "$1$testpasssd"; + result["first"] = "myfirst"; + result["last"] ="mylast"; + return result; +} + +void LLCredential::identifierType(std::string &idType) +{ +} + +void LLCredential::authenticatorType(std::string &idType) +{ +} + + +LLControlGroup gSavedSettings("test"); +unsigned char gMACAddress[MAC_ADDRESS_BYTES] = {77,21,46,31,89,2}; + + +S32 LLMachineID::getUniqueID(unsigned char *unique_id, size_t len) +{ + memcpy(unique_id, gMACAddress, len); + return 1; +} +S32 LLMachineID::getLegacyID(unsigned char *unique_id, size_t len) +{ + return 0; +} +S32 LLMachineID::init() { return 1; } + + +LLCertException::LLCertException(const LLSD& cert_data, const std::string& msg) + : LLException(msg), + mCertData(cert_data) +{ + LL_WARNS("SECAPI") << "Certificate Error: " << msg << LL_ENDL; +} + + +// ------------------------------------------------------------------------------------------- +// TUT +// ------------------------------------------------------------------------------------------- +namespace tut +{ + const std::string mPemTestCert( + "Certificate:\n" + " Data:\n" + " Version: 3 (0x2)\n" + " Serial Number:\n" + " 04:00:00:00:00:01:15:4b:5a:c3:94\n" + " Signature Algorithm: sha1WithRSAEncryption\n" + " Issuer: C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA\n" + " Validity\n" + " Not Before: Sep 1 12:00:00 1998 GMT\n" + " Not After : Jan 28 12:00:00 2028 GMT\n" + " Subject: C=BE, O=GlobalSign nv-sa, OU=Root CA, CN=GlobalSign Root CA\n" + " Subject Public Key Info:\n" + " Public Key Algorithm: rsaEncryption\n" + " Public-Key: (2048 bit)\n" + " Modulus:\n" + " 00:da:0e:e6:99:8d:ce:a3:e3:4f:8a:7e:fb:f1:8b:\n" + " 83:25:6b:ea:48:1f:f1:2a:b0:b9:95:11:04:bd:f0:\n" + " 63:d1:e2:67:66:cf:1c:dd:cf:1b:48:2b:ee:8d:89:\n" + " 8e:9a:af:29:80:65:ab:e9:c7:2d:12:cb:ab:1c:4c:\n" + " 70:07:a1:3d:0a:30:cd:15:8d:4f:f8:dd:d4:8c:50:\n" + " 15:1c:ef:50:ee:c4:2e:f7:fc:e9:52:f2:91:7d:e0:\n" + " 6d:d5:35:30:8e:5e:43:73:f2:41:e9:d5:6a:e3:b2:\n" + " 89:3a:56:39:38:6f:06:3c:88:69:5b:2a:4d:c5:a7:\n" + " 54:b8:6c:89:cc:9b:f9:3c:ca:e5:fd:89:f5:12:3c:\n" + " 92:78:96:d6:dc:74:6e:93:44:61:d1:8d:c7:46:b2:\n" + " 75:0e:86:e8:19:8a:d5:6d:6c:d5:78:16:95:a2:e9:\n" + " c8:0a:38:eb:f2:24:13:4f:73:54:93:13:85:3a:1b:\n" + " bc:1e:34:b5:8b:05:8c:b9:77:8b:b1:db:1f:20:91:\n" + " ab:09:53:6e:90:ce:7b:37:74:b9:70:47:91:22:51:\n" + " 63:16:79:ae:b1:ae:41:26:08:c8:19:2b:d1:46:aa:\n" + " 48:d6:64:2a:d7:83:34:ff:2c:2a:c1:6c:19:43:4a:\n" + " 07:85:e7:d3:7c:f6:21:68:ef:ea:f2:52:9f:7f:93:\n" + " 90:cf\n" + " Exponent: 65537 (0x10001)\n" + " X509v3 extensions:\n" + " X509v3 Key Usage: critical\n" + " Certificate Sign, CRL Sign\n" + " X509v3 Basic Constraints: critical\n" + " CA:TRUE\n" + " X509v3 Subject Key Identifier: \n" + " 60:7B:66:1A:45:0D:97:CA:89:50:2F:7D:04:CD:34:A8:FF:FC:FD:4B\n" + " Signature Algorithm: sha1WithRSAEncryption\n" + " d6:73:e7:7c:4f:76:d0:8d:bf:ec:ba:a2:be:34:c5:28:32:b5:\n" + " 7c:fc:6c:9c:2c:2b:bd:09:9e:53:bf:6b:5e:aa:11:48:b6:e5:\n" + " 08:a3:b3:ca:3d:61:4d:d3:46:09:b3:3e:c3:a0:e3:63:55:1b:\n" + " f2:ba:ef:ad:39:e1:43:b9:38:a3:e6:2f:8a:26:3b:ef:a0:50:\n" + " 56:f9:c6:0a:fd:38:cd:c4:0b:70:51:94:97:98:04:df:c3:5f:\n" + " 94:d5:15:c9:14:41:9c:c4:5d:75:64:15:0d:ff:55:30:ec:86:\n" + " 8f:ff:0d:ef:2c:b9:63:46:f6:aa:fc:df:bc:69:fd:2e:12:48:\n" + " 64:9a:e0:95:f0:a6:ef:29:8f:01:b1:15:b5:0c:1d:a5:fe:69:\n" + " 2c:69:24:78:1e:b3:a7:1c:71:62:ee:ca:c8:97:ac:17:5d:8a:\n" + " c2:f8:47:86:6e:2a:c4:56:31:95:d0:67:89:85:2b:f9:6c:a6:\n" + " 5d:46:9d:0c:aa:82:e4:99:51:dd:70:b7:db:56:3d:61:e4:6a:\n" + " e1:5c:d6:f6:fe:3d:de:41:cc:07:ae:63:52:bf:53:53:f4:2b:\n" + " e9:c7:fd:b6:f7:82:5f:85:d2:41:18:db:81:b3:04:1c:c5:1f:\n" + " a4:80:6f:15:20:c9:de:0c:88:0a:1d:d6:66:55:e2:fc:48:c9:\n" + " 29:26:69:e0\n" + "-----BEGIN CERTIFICATE-----\n" + "MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG\n" + "A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv\n" + "b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw\n" + "MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i\n" + "YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT\n" + "aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ\n" + "jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp\n" + "xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp\n" + "1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG\n" + "snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ\n" + "U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8\n" + "9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E\n" + "BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B\n" + "AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz\n" + "yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE\n" + "38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP\n" + "AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad\n" + "DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME\n" + "HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==\n" + "-----END CERTIFICATE-----\n" + ); + + /* + * The following certificates were generated using the instructions at + * https://jamielinux.com/docs/openssl-certificate-authority/sign-server-and-client-certificates.html + * with the exception that the server certificate has a longer expiration time, and the full text + * expansion was included in the certificates. + */ + const std::string mPemRootCert( + "Certificate:\n" + " Data:\n" + " Version: 3 (0x2)\n" + " Serial Number:\n" + " 82:2f:8f:eb:8d:06:24:b0\n" + " Signature Algorithm: sha256WithRSAEncryption\n" + " Issuer: C=US, ST=California, L=San Francisco, O=Linden Lab, OU=Second Life Engineering, CN=Integration Test Root CA/emailAddress=noreply@lindenlab.com\n" + " Validity\n" + " Not Before: May 22 22:19:45 2018 GMT\n" + " Not After : May 17 22:19:45 2038 GMT\n" + " Subject: C=US, ST=California, L=San Francisco, O=Linden Lab, OU=Second Life Engineering, CN=Integration Test Root CA/emailAddress=noreply@lindenlab.com\n" + " Subject Public Key Info:\n" + " Public Key Algorithm: rsaEncryption\n" + " Public-Key: (4096 bit)\n" + " Modulus:\n" + " 00:bd:e0:79:dd:3b:a6:ac:87:d0:39:f0:58:c7:a4:\n" + " 42:42:f6:5f:93:b0:36:04:b5:e2:d5:f7:2a:c0:6c:\n" + " a0:13:d2:1e:02:81:57:02:50:4c:57:b7:ef:27:9e:\n" + " f6:f1:f1:30:30:72:1e:57:34:e5:3f:82:3c:21:c4:\n" + " 66:d2:73:63:6c:91:e6:dd:49:9e:9c:b1:34:6a:81:\n" + " 45:a1:6e:c4:50:28:f2:d8:e3:fe:80:2f:83:aa:28:\n" + " 91:b4:8c:57:c9:f1:16:d9:0c:87:3c:25:80:a0:81:\n" + " 8d:71:f2:96:e2:16:f1:97:c4:b0:d8:53:bb:13:6c:\n" + " 73:54:2f:29:94:85:cf:86:6e:75:71:ad:39:e3:fc:\n" + " 39:12:53:93:1c:ce:39:e0:33:da:49:b7:3d:af:b0:\n" + " 37:ce:77:09:03:27:32:70:c0:9c:7f:9c:89:ce:90:\n" + " 45:b0:7d:94:8b:ff:13:27:ba:88:7f:ae:c4:aa:73:\n" + " d5:47:b8:87:69:89:80:0c:c1:22:18:78:c2:0d:47:\n" + " d9:10:ff:80:79:0d:46:71:ec:d9:ba:c9:f3:77:fd:\n" + " 92:6d:1f:0f:d9:54:18:6d:f6:72:24:5c:5c:3d:43:\n" + " 49:35:3e:1c:28:de:7e:44:dc:29:c3:9f:62:04:46:\n" + " aa:c4:e6:69:6a:15:f8:e3:74:1c:14:e9:f4:97:7c:\n" + " 30:6c:d4:28:fc:2a:0e:1d:6d:39:2e:1d:f9:17:43:\n" + " 35:5d:23:e7:ba:e3:a8:e9:97:6b:3c:3e:23:ef:d8:\n" + " bc:fb:7a:57:37:39:93:59:03:fc:78:ca:b1:31:ef:\n" + " 26:19:ed:56:e1:63:c3:ad:99:80:5b:47:b5:03:35:\n" + " 5f:fe:6a:a6:21:63:ec:50:fb:4e:c9:f9:ae:a5:66:\n" + " d0:55:33:8d:e6:c5:50:5a:c6:8f:5c:34:45:a7:72:\n" + " da:50:f6:66:4c:19:f5:d1:e4:fb:11:8b:a1:b5:4e:\n" + " 09:43:81:3d:39:28:86:3b:fe:07:28:97:02:b5:3a:\n" + " 07:5f:4a:20:80:1a:7d:a4:8c:f7:6c:f6:c5:9b:f6:\n" + " 61:e5:c7:b0:c3:d5:58:38:7b:bb:47:1e:34:d6:16:\n" + " 55:c5:d2:6c:b0:93:77:b1:90:69:06:b1:53:cb:1b:\n" + " 84:71:cf:b8:87:1b:1e:44:35:b4:2b:bb:04:59:58:\n" + " 0b:e8:93:d8:ae:21:9b:b1:1c:89:30:ae:11:80:77:\n" + " cc:16:f3:d6:35:ed:a1:b3:70:b3:4f:cd:a1:56:99:\n" + " ee:0e:c0:00:a4:09:70:c3:5b:0b:be:a1:07:18:dd:\n" + " c6:f4:6d:8b:58:bc:f9:bb:4b:01:2c:f6:cc:2c:9b:\n" + " 87:0e:b1:4f:9c:10:be:fc:45:e2:a4:ec:7e:fc:ff:\n" + " 45:b8:53\n" + " Exponent: 65537 (0x10001)\n" + " X509v3 extensions:\n" + " X509v3 Subject Key Identifier: \n" + " 8A:22:C6:9C:2E:11:F3:40:0C:CE:82:0C:22:59:FF:F8:7F:D0:B9:13\n" + " X509v3 Authority Key Identifier: \n" + " keyid:8A:22:C6:9C:2E:11:F3:40:0C:CE:82:0C:22:59:FF:F8:7F:D0:B9:13\n" + "\n" + " X509v3 Basic Constraints: critical\n" + " CA:TRUE\n" + " X509v3 Key Usage: critical\n" + " Digital Signature, Certificate Sign, CRL Sign\n" + " Signature Algorithm: sha256WithRSAEncryption\n" + " b3:cb:33:eb:0e:02:64:f4:55:9a:3d:03:9a:cf:6a:4c:18:43:\n" + " f7:42:cb:65:dc:61:52:e5:9f:2f:42:97:3c:93:16:22:d4:af:\n" + " ae:b2:0f:c3:9b:ef:e0:cc:ee:b6:b1:69:a3:d8:da:26:c3:ad:\n" + " 3b:c5:64:dc:9f:d4:c2:53:4b:91:6d:c4:92:09:0b:ac:f0:99:\n" + " be:6f:b9:3c:03:4a:6d:9f:01:5d:ec:5a:9a:f3:a7:e5:3b:2c:\n" + " 99:57:7d:7e:25:15:68:20:12:30:96:16:86:f5:db:74:90:60:\n" + " fe:8b:df:99:f6:f7:62:49:9f:bc:8d:45:23:0a:c8:73:b8:79:\n" + " 80:3c:b9:e5:72:85:4b:b3:81:66:74:a2:72:92:4c:44:fd:7b:\n" + " 46:2e:21:a2:a9:81:a2:f3:26:4d:e3:89:7d:78:b0:c6:6f:b5:\n" + " 87:cb:ee:25:ed:27:1f:75:13:fa:6d:e9:37:73:ad:07:bb:af:\n" + " d3:6c:87:ea:02:01:70:bd:53:aa:ce:39:2c:d4:66:39:33:aa:\n" + " d1:9c:ee:67:e3:a9:45:d2:7b:2e:54:09:af:70:5f:3f:5a:67:\n" + " 2e:6c:72:ef:e0:9d:92:28:4a:df:ba:0b:b7:23:ca:5b:04:11:\n" + " 45:d1:51:e9:ea:c9:ec:54:fa:34:46:ae:fc:dc:6c:f8:1e:2c:\n" + " 9e:f4:71:51:8d:b5:a1:26:9a:13:30:be:1e:41:25:59:58:05:\n" + " 2c:64:c8:f9:5e:38:ae:dc:93:b0:8a:d6:38:74:02:cb:ce:ce:\n" + " 95:31:76:f6:7c:bf:a4:a1:8e:27:fd:ca:74:82:d1:e1:4d:b6:\n" + " 48:51:fa:c5:17:59:22:a3:84:be:82:c8:83:ec:61:a0:f4:ee:\n" + " 2c:e3:a3:ea:e5:51:c9:d3:4f:db:85:bd:ba:7a:52:14:b6:03:\n" + " ed:43:17:d8:d7:1c:22:5e:c9:56:d9:d6:81:96:11:e3:5e:01:\n" + " 40:91:30:09:da:a3:5f:d3:27:60:e5:9d:6c:da:d0:f0:39:01:\n" + " 23:4a:a6:15:7a:4a:82:eb:ec:72:4a:1d:36:dc:6f:83:c4:85:\n" + " 84:b5:8d:cd:09:e5:12:63:f3:21:56:c8:64:6b:db:b8:cf:d4:\n" + " df:ca:a8:24:8e:df:8d:63:a5:96:84:bf:ff:8b:7e:46:7a:f0:\n" + " c7:73:7c:70:8a:f5:17:d0:ac:c8:89:1e:d7:89:42:0f:4d:66:\n" + " c4:d8:bb:36:a8:ae:ca:e1:cf:e2:88:f6:cf:b0:44:4a:5f:81:\n" + " 50:4b:d6:28:81:cd:6c:f0:ec:e6:09:08:f2:59:91:a2:69:ac:\n" + " c7:81:fa:ab:61:3e:db:6f:f6:7f:db:1a:9e:b9:5d:cc:cc:33:\n" + " fa:95:c6:f7:8d:4b:30:f3\n" + "-----BEGIN CERTIFICATE-----\n" + "MIIGXDCCBESgAwIBAgIJAIIvj+uNBiSwMA0GCSqGSIb3DQEBCwUAMIG6MQswCQYD\n" + "VQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5j\n" + "aXNjbzETMBEGA1UECgwKTGluZGVuIExhYjEgMB4GA1UECwwXU2Vjb25kIExpZmUg\n" + "RW5naW5lZXJpbmcxITAfBgNVBAMMGEludGVncmF0aW9uIFRlc3QgUm9vdCBDQTEk\n" + "MCIGCSqGSIb3DQEJARYVbm9yZXBseUBsaW5kZW5sYWIuY29tMB4XDTE4MDUyMjIy\n" + "MTk0NVoXDTM4MDUxNzIyMTk0NVowgboxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApD\n" + "YWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRMwEQYDVQQKDApMaW5k\n" + "ZW4gTGFiMSAwHgYDVQQLDBdTZWNvbmQgTGlmZSBFbmdpbmVlcmluZzEhMB8GA1UE\n" + "AwwYSW50ZWdyYXRpb24gVGVzdCBSb290IENBMSQwIgYJKoZIhvcNAQkBFhVub3Jl\n" + "cGx5QGxpbmRlbmxhYi5jb20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC\n" + "AQC94HndO6ash9A58FjHpEJC9l+TsDYEteLV9yrAbKAT0h4CgVcCUExXt+8nnvbx\n" + "8TAwch5XNOU/gjwhxGbSc2NskebdSZ6csTRqgUWhbsRQKPLY4/6AL4OqKJG0jFfJ\n" + "8RbZDIc8JYCggY1x8pbiFvGXxLDYU7sTbHNULymUhc+GbnVxrTnj/DkSU5Mczjng\n" + "M9pJtz2vsDfOdwkDJzJwwJx/nInOkEWwfZSL/xMnuoh/rsSqc9VHuIdpiYAMwSIY\n" + "eMINR9kQ/4B5DUZx7Nm6yfN3/ZJtHw/ZVBht9nIkXFw9Q0k1Phwo3n5E3CnDn2IE\n" + "RqrE5mlqFfjjdBwU6fSXfDBs1Cj8Kg4dbTkuHfkXQzVdI+e646jpl2s8PiPv2Lz7\n" + "elc3OZNZA/x4yrEx7yYZ7VbhY8OtmYBbR7UDNV/+aqYhY+xQ+07J+a6lZtBVM43m\n" + "xVBaxo9cNEWnctpQ9mZMGfXR5PsRi6G1TglDgT05KIY7/gcolwK1OgdfSiCAGn2k\n" + "jPds9sWb9mHlx7DD1Vg4e7tHHjTWFlXF0mywk3exkGkGsVPLG4Rxz7iHGx5ENbQr\n" + "uwRZWAvok9iuIZuxHIkwrhGAd8wW89Y17aGzcLNPzaFWme4OwACkCXDDWwu+oQcY\n" + "3cb0bYtYvPm7SwEs9swsm4cOsU+cEL78ReKk7H78/0W4UwIDAQABo2MwYTAdBgNV\n" + "HQ4EFgQUiiLGnC4R80AMzoIMIln/+H/QuRMwHwYDVR0jBBgwFoAUiiLGnC4R80AM\n" + "zoIMIln/+H/QuRMwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJ\n" + "KoZIhvcNAQELBQADggIBALPLM+sOAmT0VZo9A5rPakwYQ/dCy2XcYVLlny9ClzyT\n" + "FiLUr66yD8Ob7+DM7raxaaPY2ibDrTvFZNyf1MJTS5FtxJIJC6zwmb5vuTwDSm2f\n" + "AV3sWprzp+U7LJlXfX4lFWggEjCWFob123SQYP6L35n292JJn7yNRSMKyHO4eYA8\n" + "ueVyhUuzgWZ0onKSTET9e0YuIaKpgaLzJk3jiX14sMZvtYfL7iXtJx91E/pt6Tdz\n" + "rQe7r9Nsh+oCAXC9U6rOOSzUZjkzqtGc7mfjqUXSey5UCa9wXz9aZy5scu/gnZIo\n" + "St+6C7cjylsEEUXRUenqyexU+jRGrvzcbPgeLJ70cVGNtaEmmhMwvh5BJVlYBSxk\n" + "yPleOK7ck7CK1jh0AsvOzpUxdvZ8v6Shjif9ynSC0eFNtkhR+sUXWSKjhL6CyIPs\n" + "YaD07izjo+rlUcnTT9uFvbp6UhS2A+1DF9jXHCJeyVbZ1oGWEeNeAUCRMAnao1/T\n" + "J2DlnWza0PA5ASNKphV6SoLr7HJKHTbcb4PEhYS1jc0J5RJj8yFWyGRr27jP1N/K\n" + "qCSO341jpZaEv/+LfkZ68MdzfHCK9RfQrMiJHteJQg9NZsTYuzaorsrhz+KI9s+w\n" + "REpfgVBL1iiBzWzw7OYJCPJZkaJprMeB+qthPttv9n/bGp65XczMM/qVxveNSzDz\n" + "-----END CERTIFICATE-----\n" + ); + + const std::string mPemIntermediateCert( + "Certificate:\n" + " Data:\n" + " Version: 3 (0x2)\n" + " Serial Number: 4096 (0x1000)\n" + " Signature Algorithm: sha256WithRSAEncryption\n" + " Issuer: C=US, ST=California, L=San Francisco, O=Linden Lab, OU=Second Life Engineering, CN=Integration Test Root CA/emailAddress=noreply@lindenlab.com\n" + " Validity\n" + " Not Before: May 22 22:39:08 2018 GMT\n" + " Not After : May 19 22:39:08 2028 GMT\n" + " Subject: C=US, ST=California, O=Linden Lab, OU=Second Life Engineering, CN=Integration Test Intermediate CA/emailAddress=noreply@lindenlab.com\n" + " Subject Public Key Info:\n" + " Public Key Algorithm: rsaEncryption\n" + " Public-Key: (4096 bit)\n" + " Modulus:\n" + " 00:ce:a3:70:e2:c4:fb:4b:97:90:a1:30:bb:c1:1b:\n" + " 13:b9:aa:7e:46:17:a3:26:8d:69:3f:5e:73:95:e8:\n" + " 6a:b1:0a:b4:8f:50:65:e3:c6:5c:39:24:34:df:0b:\n" + " b7:cc:ce:62:0c:36:5a:12:2c:fe:35:4c:e9:1c:ac:\n" + " 80:5e:24:99:d7:aa:bd:be:48:c0:62:64:77:36:88:\n" + " 66:ce:f4:a8:dd:d2:76:24:62:90:55:41:fc:1d:13:\n" + " 4e:a7:4e:57:bc:a8:a4:59:4b:2c:5a:1c:d8:cc:16:\n" + " de:e8:88:30:c9:95:df:2f:a6:14:28:0f:eb:34:46:\n" + " 12:58:ba:da:0e:e6:de:9c:15:f6:f4:e3:9f:74:aa:\n" + " 70:89:79:8b:e9:5a:7b:18:54:15:94:3a:23:0a:65:\n" + " 78:05:d9:33:90:2a:ce:15:18:0d:52:fc:5c:31:65:\n" + " 20:d0:12:37:8c:11:80:ba:d4:b0:82:73:00:4b:49:\n" + " be:cb:d6:bc:e7:cd:61:f3:00:98:99:74:5a:37:81:\n" + " 49:96:7e:14:01:1b:86:d2:d0:06:94:40:63:63:46:\n" + " 11:fc:33:5c:bd:3a:5e:d4:e5:44:47:64:50:bd:a6:\n" + " 97:55:70:64:9b:26:cc:de:20:82:90:6a:83:41:9c:\n" + " 6f:71:47:14:be:cb:68:7c:85:be:ef:2e:76:12:19:\n" + " d3:c9:87:32:b4:ac:60:20:16:28:2d:af:bc:e8:01:\n" + " c6:7f:fb:d8:11:d5:f4:b7:14:bd:27:08:5b:72:be:\n" + " 09:e0:91:c8:9c:7b:b4:b3:12:ef:32:36:be:b1:b9:\n" + " a2:b7:e3:69:47:30:76:ba:9c:9b:19:99:4d:53:dd:\n" + " 5c:e8:2c:f1:b2:64:69:cf:15:bd:f8:bb:58:95:73:\n" + " 58:38:95:b4:7a:cf:84:29:a6:c2:db:f0:bd:ef:97:\n" + " 26:d4:99:ac:d7:c7:be:b0:0d:11:f4:26:86:2d:77:\n" + " 42:52:25:d7:56:c7:e3:97:b1:36:5c:97:71:d0:9b:\n" + " f5:b5:50:8d:f9:ff:fb:10:77:3c:b5:53:6d:a1:43:\n" + " 35:a9:03:32:05:ab:d7:f5:d1:19:bd:5f:92:a3:00:\n" + " 2a:79:37:a4:76:4f:e9:32:0d:e4:86:bb:ea:c3:1a:\n" + " c5:33:e8:16:d4:a5:d8:e0:e8:bb:c2:f0:22:15:e2:\n" + " d9:8c:ae:ac:7d:2b:bf:eb:a3:4c:3b:29:1d:94:ac:\n" + " a3:bb:6d:ba:6d:03:91:03:cf:46:12:c4:66:21:c5:\n" + " c6:67:d8:11:19:79:01:0e:6e:84:1c:76:6f:11:3d:\n" + " eb:94:89:c5:6a:26:1f:cd:e0:11:8b:51:ee:99:35:\n" + " 69:e5:7f:0b:77:2a:94:e4:4b:64:b9:83:04:30:05:\n" + " e4:a2:e3\n" + " Exponent: 65537 (0x10001)\n" + " X509v3 extensions:\n" + " X509v3 Subject Key Identifier: \n" + " 83:21:DE:EC:C0:79:03:6D:1E:83:F3:E5:97:29:D5:5A:C0:96:40:FA\n" + " X509v3 Authority Key Identifier: \n" + " keyid:8A:22:C6:9C:2E:11:F3:40:0C:CE:82:0C:22:59:FF:F8:7F:D0:B9:13\n" + "\n" + " X509v3 Basic Constraints: critical\n" + " CA:TRUE, pathlen:0\n" + " X509v3 Key Usage: critical\n" + " Digital Signature, Certificate Sign, CRL Sign\n" + " Signature Algorithm: sha256WithRSAEncryption\n" + " a3:6c:85:9a:2e:4e:7e:5d:83:63:0f:f5:4f:a9:7d:ec:0e:6f:\n" + " ae:d7:ba:df:64:e0:46:0e:3d:da:18:15:2c:f3:73:ca:81:b1:\n" + " 10:d9:53:14:21:7d:72:5c:94:88:a5:9d:ad:ab:45:42:c6:64:\n" + " a9:d9:2e:4e:29:47:2c:b1:95:07:b7:62:48:68:1f:68:13:1c:\n" + " d2:a0:fb:5e:38:24:4a:82:0a:87:c9:93:20:43:7e:e9:f9:79:\n" + " ef:03:a2:bd:9e:24:6b:0a:01:5e:4a:36:c5:7d:7a:fe:d6:aa:\n" + " 2f:c2:8c:38:8a:99:3c:b0:6a:e5:60:be:56:d6:eb:60:03:55:\n" + " 24:42:a0:1a:fa:91:24:a3:53:15:75:5d:c8:eb:7c:1e:68:5a:\n" + " 7e:13:34:e3:85:37:1c:76:3f:77:67:1b:ed:1b:52:17:fc:4a:\n" + " a3:e2:74:84:80:2c:69:fc:dd:7d:26:97:c4:2a:69:7d:9c:dc:\n" + " 61:97:70:29:a7:3f:2b:5b:2b:22:51:fd:fe:6a:5d:f9:e7:14:\n" + " 48:b7:2d:c8:33:58:fc:f2:5f:27:f7:26:16:be:be:b5:aa:a2:\n" + " 64:53:3c:69:e8:b5:61:eb:ab:91:a5:b4:09:9b:f6:98:b8:5c:\n" + " 5b:24:2f:93:f5:2b:9c:8c:58:fb:26:3f:67:53:d7:42:64:e8:\n" + " 79:77:73:41:4e:e3:02:39:0b:b6:68:97:8b:84:e8:1d:83:a8:\n" + " 15:f1:06:46:47:80:42:5e:14:e2:61:8a:76:84:d5:d4:71:7f:\n" + " 4e:ff:d9:74:87:ff:32:c5:87:20:0a:d4:59:40:3e:d8:17:ef:\n" + " da:65:e9:0a:51:fe:1e:c3:46:91:d2:ee:e4:23:57:97:87:d4:\n" + " a6:a5:eb:ef:81:6a:d8:8c:d6:1f:8e:b1:18:4c:6b:89:32:55:\n" + " 53:68:26:9e:bb:03:be:2c:e9:8b:ff:97:9c:1c:ac:28:c3:9f:\n" + " 0b:b7:93:23:24:31:63:e4:19:13:f2:bb:08:71:b7:c5:c5:c4:\n" + " 10:ff:dc:fc:33:54:a4:5e:ec:a3:fe:0a:80:ca:9c:bc:95:6f:\n" + " 5f:39:91:3b:61:69:16:94:0f:57:4b:fc:4b:b1:be:72:98:5d:\n" + " 10:f9:08:a7:d6:e0:e8:3d:5d:54:7d:fa:4b:6a:dd:98:41:ed:\n" + " 84:a1:39:67:5c:6c:7f:0c:b0:e1:98:c1:14:ed:fe:1e:e8:05:\n" + " 8d:7f:6a:24:cb:1b:05:42:0d:7f:13:ba:ca:b5:91:db:a5:f0:\n" + " 40:2b:70:7a:2a:a5:5d:ed:56:0c:f0:c2:72:ee:63:dd:cb:5d:\n" + " 76:f6:08:e6:e6:30:ef:3a:b2:16:34:41:a4:e1:30:14:bc:c7:\n" + " f9:23:3a:1a:70:df:b8:cc\n" + "-----BEGIN CERTIFICATE-----\n" + "MIIGSDCCBDCgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgboxCzAJBgNVBAYTAlVT\n" + "MRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMRMw\n" + "EQYDVQQKDApMaW5kZW4gTGFiMSAwHgYDVQQLDBdTZWNvbmQgTGlmZSBFbmdpbmVl\n" + "cmluZzEhMB8GA1UEAwwYSW50ZWdyYXRpb24gVGVzdCBSb290IENBMSQwIgYJKoZI\n" + "hvcNAQkBFhVub3JlcGx5QGxpbmRlbmxhYi5jb20wHhcNMTgwNTIyMjIzOTA4WhcN\n" + "MjgwNTE5MjIzOTA4WjCBqjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3Ju\n" + "aWExEzARBgNVBAoMCkxpbmRlbiBMYWIxIDAeBgNVBAsMF1NlY29uZCBMaWZlIEVu\n" + "Z2luZWVyaW5nMSkwJwYDVQQDDCBJbnRlZ3JhdGlvbiBUZXN0IEludGVybWVkaWF0\n" + "ZSBDQTEkMCIGCSqGSIb3DQEJARYVbm9yZXBseUBsaW5kZW5sYWIuY29tMIICIjAN\n" + "BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAzqNw4sT7S5eQoTC7wRsTuap+Rhej\n" + "Jo1pP15zlehqsQq0j1Bl48ZcOSQ03wu3zM5iDDZaEiz+NUzpHKyAXiSZ16q9vkjA\n" + "YmR3NohmzvSo3dJ2JGKQVUH8HRNOp05XvKikWUssWhzYzBbe6IgwyZXfL6YUKA/r\n" + "NEYSWLraDubenBX29OOfdKpwiXmL6Vp7GFQVlDojCmV4BdkzkCrOFRgNUvxcMWUg\n" + "0BI3jBGAutSwgnMAS0m+y9a8581h8wCYmXRaN4FJln4UARuG0tAGlEBjY0YR/DNc\n" + "vTpe1OVER2RQvaaXVXBkmybM3iCCkGqDQZxvcUcUvstofIW+7y52EhnTyYcytKxg\n" + "IBYoLa+86AHGf/vYEdX0txS9Jwhbcr4J4JHInHu0sxLvMja+sbmit+NpRzB2upyb\n" + "GZlNU91c6CzxsmRpzxW9+LtYlXNYOJW0es+EKabC2/C975cm1Jms18e+sA0R9CaG\n" + "LXdCUiXXVsfjl7E2XJdx0Jv1tVCN+f/7EHc8tVNtoUM1qQMyBavX9dEZvV+SowAq\n" + "eTekdk/pMg3khrvqwxrFM+gW1KXY4Oi7wvAiFeLZjK6sfSu/66NMOykdlKyju226\n" + "bQORA89GEsRmIcXGZ9gRGXkBDm6EHHZvET3rlInFaiYfzeARi1HumTVp5X8LdyqU\n" + "5EtkuYMEMAXkouMCAwEAAaNmMGQwHQYDVR0OBBYEFIMh3uzAeQNtHoPz5Zcp1VrA\n" + "lkD6MB8GA1UdIwQYMBaAFIoixpwuEfNADM6CDCJZ//h/0LkTMBIGA1UdEwEB/wQI\n" + "MAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQCjbIWa\n" + "Lk5+XYNjD/VPqX3sDm+u17rfZOBGDj3aGBUs83PKgbEQ2VMUIX1yXJSIpZ2tq0VC\n" + "xmSp2S5OKUcssZUHt2JIaB9oExzSoPteOCRKggqHyZMgQ37p+XnvA6K9niRrCgFe\n" + "SjbFfXr+1qovwow4ipk8sGrlYL5W1utgA1UkQqAa+pEko1MVdV3I63weaFp+EzTj\n" + "hTccdj93ZxvtG1IX/Eqj4nSEgCxp/N19JpfEKml9nNxhl3Appz8rWysiUf3+al35\n" + "5xRIty3IM1j88l8n9yYWvr61qqJkUzxp6LVh66uRpbQJm/aYuFxbJC+T9SucjFj7\n" + "Jj9nU9dCZOh5d3NBTuMCOQu2aJeLhOgdg6gV8QZGR4BCXhTiYYp2hNXUcX9O/9l0\n" + "h/8yxYcgCtRZQD7YF+/aZekKUf4ew0aR0u7kI1eXh9SmpevvgWrYjNYfjrEYTGuJ\n" + "MlVTaCaeuwO+LOmL/5ecHKwow58Lt5MjJDFj5BkT8rsIcbfFxcQQ/9z8M1SkXuyj\n" + "/gqAypy8lW9fOZE7YWkWlA9XS/xLsb5ymF0Q+Qin1uDoPV1UffpLat2YQe2EoTln\n" + "XGx/DLDhmMEU7f4e6AWNf2okyxsFQg1/E7rKtZHbpfBAK3B6KqVd7VYM8MJy7mPd\n" + "y1129gjm5jDvOrIWNEGk4TAUvMf5IzoacN+4zA==\n" + "-----END CERTIFICATE-----\n" + ); + + const std::string mPemChildCert( + "Certificate:\n" + " Data:\n" + " Version: 3 (0x2)\n" + " Serial Number: 4096 (0x1000)\n" + " Signature Algorithm: sha256WithRSAEncryption\n" + " Issuer: C=US, ST=California, O=Linden Lab, OU=Second Life Engineering, CN=Integration Test Intermediate CA/emailAddress=noreply@lindenlab.com\n" + " Validity\n" + " Not Before: May 22 22:58:15 2018 GMT\n" + " Not After : Jul 19 22:58:15 2024 GMT\n" + " Subject: C=US, ST=California, L=San Francisco, O=Linden Lab, OU=Second Life Engineering, CN=Integration Test Server Cert/emailAddress=noreply@lindenlab.com\n" + " Subject Public Key Info:\n" + " Public Key Algorithm: rsaEncryption\n" + " Public-Key: (2048 bit)\n" + " Modulus:\n" + " 00:bf:a1:1c:76:82:4a:10:1d:25:0e:02:e2:7a:64:\n" + " 54:c7:94:c5:c0:98:d5:35:f3:cb:cb:30:ba:31:9c:\n" + " bd:4c:2f:4a:4e:24:03:4b:87:5c:c1:5c:fe:d9:89:\n" + " 3b:cb:01:bc:eb:a5:b7:78:dc:b3:58:e5:78:a7:15:\n" + " 34:50:30:aa:16:3a:b2:94:17:6d:1e:7f:b2:70:1e:\n" + " 96:41:bb:1d:e3:22:80:fa:dc:00:6a:fb:34:3e:67:\n" + " e7:c2:21:2f:1b:d3:af:04:49:91:eb:bb:60:e0:26:\n" + " 52:75:28:8a:08:5b:91:56:4e:51:50:40:51:70:af:\n" + " cb:80:66:c8:59:e9:e2:48:a8:62:d0:26:67:80:0a:\n" + " 12:16:d1:f6:15:9e:1f:f5:92:37:f3:c9:2f:03:9e:\n" + " 22:f6:60:5a:76:45:8c:01:2c:99:54:72:19:db:b7:\n" + " 72:e6:5a:69:f3:e9:31:65:5d:0f:c7:5c:9c:17:29:\n" + " 71:14:7f:db:47:c9:1e:65:a2:41:b0:2f:14:17:ec:\n" + " 4b:25:f2:43:8f:b4:a3:8d:37:1a:07:34:b3:29:bb:\n" + " 8a:44:8e:84:08:a2:1b:76:7a:cb:c2:39:2f:6e:e3:\n" + " fc:d6:91:b5:1f:ce:58:91:57:70:35:6e:25:a9:48:\n" + " 0e:07:cf:4e:dd:16:42:65:cf:8a:42:b3:27:e6:fe:\n" + " 6a:e3\n" + " Exponent: 65537 (0x10001)\n" + " X509v3 extensions:\n" + " X509v3 Basic Constraints: \n" + " CA:FALSE\n" + " Netscape Cert Type: \n" + " SSL Server\n" + " Netscape Comment: \n" + " OpenSSL Generated Server Certificate\n" + " X509v3 Subject Key Identifier: \n" + " BB:59:9F:DE:6B:51:A7:6C:B3:6D:5B:8B:42:F7:B1:65:77:17:A4:E4\n" + " X509v3 Authority Key Identifier: \n" + " keyid:83:21:DE:EC:C0:79:03:6D:1E:83:F3:E5:97:29:D5:5A:C0:96:40:FA\n" + " DirName:/C=US/ST=California/L=San Francisco/O=Linden Lab/OU=Second Life Engineering/CN=Integration Test Root CA/emailAddress=noreply@lindenlab.com\n" + " serial:10:00\n" + "\n" + " X509v3 Key Usage: critical\n" + " Digital Signature, Key Encipherment\n" + " X509v3 Extended Key Usage: \n" + " TLS Web Server Authentication\n" + " Signature Algorithm: sha256WithRSAEncryption\n" + " 18:a6:58:55:9b:d4:af:7d:8a:27:d3:28:3a:4c:4b:42:4e:f0:\n" + " 30:d6:d9:95:11:48:12:0a:96:40:d9:2b:21:39:c5:d4:8d:e5:\n" + " 10:bc:68:78:69:0b:9f:15:4a:0b:f1:ab:99:45:0c:20:5f:27:\n" + " df:e7:14:2d:4a:30:f2:c2:8d:37:73:36:1a:27:55:5a:08:5f:\n" + " 71:a1:5e:05:83:b2:59:fe:02:5e:d7:4a:30:15:23:58:04:cf:\n" + " 48:cc:b0:71:88:9c:6b:57:f0:04:0a:d3:a0:64:6b:ee:f3:5f:\n" + " ea:ac:e1:2b:b9:7f:79:b8:db:ce:72:48:72:db:c8:5c:38:72:\n" + " 31:55:d0:ff:6b:bd:73:23:a7:30:18:5d:ed:47:18:0a:67:8e:\n" + " 53:32:0e:99:9b:96:72:45:7f:c6:00:2c:5d:1a:97:53:75:3a:\n" + " 0b:49:3d:3a:00:37:14:67:0c:28:97:34:87:aa:c5:32:e4:ae:\n" + " 34:83:12:4a:10:f7:0e:74:d4:5f:73:bd:ef:0c:b7:d8:0a:7d:\n" + " 8e:8d:5a:48:bd:f4:8e:7b:f9:4a:15:3b:61:c9:5e:40:59:6e:\n" + " c7:a8:a4:02:28:72:c5:54:8c:77:f4:55:a7:86:c0:38:a0:68:\n" + " 19:da:0f:72:5a:a9:7e:69:9f:9c:3a:d6:66:aa:e1:f4:fd:f9:\n" + " b8:4b:6c:71:9e:f0:38:02:c7:6a:9e:dc:e6:fb:ef:23:59:4f:\n" + " 5c:84:0a:df:ea:86:1f:fd:0e:5c:fa:c4:e5:50:1c:10:cf:89:\n" + " 4e:08:0e:4c:4b:61:1a:49:12:f7:e9:4b:17:71:43:7b:6d:b6:\n" + " b5:9f:d4:3b:c7:88:53:48:63:b6:00:80:8f:49:0a:c5:7e:58:\n" + " ac:78:d8:b9:06:b0:bc:86:e2:2e:48:5b:c3:24:fa:aa:72:d8:\n" + " ec:f6:c7:91:9f:0f:c8:b5:fd:2b:b2:a7:bc:2f:40:20:2b:47:\n" + " e0:d1:1d:94:52:6f:6b:be:12:b6:8c:dc:11:db:71:e6:19:ef:\n" + " a8:71:8b:ad:d3:32:c0:1c:a4:3f:b3:0f:af:e5:50:e1:ff:41:\n" + " a4:b7:6f:57:71:af:fd:16:4c:e8:24:b3:99:1b:cf:12:8f:43:\n" + " 05:80:ba:18:19:0a:a5:ec:49:81:41:4c:7e:28:b2:21:f2:59:\n" + " 6e:4a:ed:de:f9:fa:99:85:60:1f:e6:c2:42:5c:08:00:3c:84:\n" + " 06:a9:24:d4:cf:7b:6e:1b:59:1d:f4:70:16:03:a1:e0:0b:00:\n" + " 95:5c:39:03:fc:9d:1c:8e:f7:59:0c:61:47:f6:7f:07:22:48:\n" + " 83:40:ac:e1:98:5f:c7:be:05:d5:29:2b:bf:0d:03:0e:e9:5e:\n" + " 2b:dd:09:18:fe:5e:30:61\n" + "-----BEGIN CERTIFICATE-----\n" + "MIIGbjCCBFagAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgaoxCzAJBgNVBAYTAlVT\n" + "MRMwEQYDVQQIDApDYWxpZm9ybmlhMRMwEQYDVQQKDApMaW5kZW4gTGFiMSAwHgYD\n" + "VQQLDBdTZWNvbmQgTGlmZSBFbmdpbmVlcmluZzEpMCcGA1UEAwwgSW50ZWdyYXRp\n" + "b24gVGVzdCBJbnRlcm1lZGlhdGUgQ0ExJDAiBgkqhkiG9w0BCQEWFW5vcmVwbHlA\n" + "bGluZGVubGFiLmNvbTAeFw0xODA1MjIyMjU4MTVaFw0yNDA3MTkyMjU4MTVaMIG+\n" + "MQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2Fu\n" + "IEZyYW5jaXNjbzETMBEGA1UECgwKTGluZGVuIExhYjEgMB4GA1UECwwXU2Vjb25k\n" + "IExpZmUgRW5naW5lZXJpbmcxJTAjBgNVBAMMHEludGVncmF0aW9uIFRlc3QgU2Vy\n" + "dmVyIENlcnQxJDAiBgkqhkiG9w0BCQEWFW5vcmVwbHlAbGluZGVubGFiLmNvbTCC\n" + "ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL+hHHaCShAdJQ4C4npkVMeU\n" + "xcCY1TXzy8swujGcvUwvSk4kA0uHXMFc/tmJO8sBvOult3jcs1jleKcVNFAwqhY6\n" + "spQXbR5/snAelkG7HeMigPrcAGr7ND5n58IhLxvTrwRJkeu7YOAmUnUoighbkVZO\n" + "UVBAUXCvy4BmyFnp4kioYtAmZ4AKEhbR9hWeH/WSN/PJLwOeIvZgWnZFjAEsmVRy\n" + "Gdu3cuZaafPpMWVdD8dcnBcpcRR/20fJHmWiQbAvFBfsSyXyQ4+0o403Ggc0sym7\n" + "ikSOhAiiG3Z6y8I5L27j/NaRtR/OWJFXcDVuJalIDgfPTt0WQmXPikKzJ+b+auMC\n" + "AwEAAaOCAYYwggGCMAkGA1UdEwQCMAAwEQYJYIZIAYb4QgEBBAQDAgZAMDMGCWCG\n" + "SAGG+EIBDQQmFiRPcGVuU1NMIEdlbmVyYXRlZCBTZXJ2ZXIgQ2VydGlmaWNhdGUw\n" + "HQYDVR0OBBYEFLtZn95rUadss21bi0L3sWV3F6TkMIHoBgNVHSMEgeAwgd2AFIMh\n" + "3uzAeQNtHoPz5Zcp1VrAlkD6oYHApIG9MIG6MQswCQYDVQQGEwJVUzETMBEGA1UE\n" + "CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzETMBEGA1UECgwK\n" + "TGluZGVuIExhYjEgMB4GA1UECwwXU2Vjb25kIExpZmUgRW5naW5lZXJpbmcxITAf\n" + "BgNVBAMMGEludGVncmF0aW9uIFRlc3QgUm9vdCBDQTEkMCIGCSqGSIb3DQEJARYV\n" + "bm9yZXBseUBsaW5kZW5sYWIuY29tggIQADAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0l\n" + "BAwwCgYIKwYBBQUHAwEwDQYJKoZIhvcNAQELBQADggIBABimWFWb1K99iifTKDpM\n" + "S0JO8DDW2ZURSBIKlkDZKyE5xdSN5RC8aHhpC58VSgvxq5lFDCBfJ9/nFC1KMPLC\n" + "jTdzNhonVVoIX3GhXgWDsln+Al7XSjAVI1gEz0jMsHGInGtX8AQK06Bka+7zX+qs\n" + "4Su5f3m4285ySHLbyFw4cjFV0P9rvXMjpzAYXe1HGApnjlMyDpmblnJFf8YALF0a\n" + "l1N1OgtJPToANxRnDCiXNIeqxTLkrjSDEkoQ9w501F9zve8Mt9gKfY6NWki99I57\n" + "+UoVO2HJXkBZbseopAIocsVUjHf0VaeGwDigaBnaD3JaqX5pn5w61maq4fT9+bhL\n" + "bHGe8DgCx2qe3Ob77yNZT1yECt/qhh/9Dlz6xOVQHBDPiU4IDkxLYRpJEvfpSxdx\n" + "Q3tttrWf1DvHiFNIY7YAgI9JCsV+WKx42LkGsLyG4i5IW8Mk+qpy2Oz2x5GfD8i1\n" + "/Suyp7wvQCArR+DRHZRSb2u+EraM3BHbceYZ76hxi63TMsAcpD+zD6/lUOH/QaS3\n" + "b1dxr/0WTOgks5kbzxKPQwWAuhgZCqXsSYFBTH4osiHyWW5K7d75+pmFYB/mwkJc\n" + "CAA8hAapJNTPe24bWR30cBYDoeALAJVcOQP8nRyO91kMYUf2fwciSINArOGYX8e+\n" + "BdUpK78NAw7pXivdCRj+XjBh\n" + "-----END CERTIFICATE-----\n" + ); + + // Test wrapper declaration : wrapping nothing for the moment + struct sechandler_basic_test + { + X509 *mX509TestCert, *mX509RootCert, *mX509IntermediateCert, *mX509ChildCert; + LLSD mValidationDate; + + sechandler_basic_test() + { + LLMachineID::init(); + OpenSSL_add_all_algorithms(); + OpenSSL_add_all_ciphers(); + OpenSSL_add_all_digests(); + ERR_load_crypto_strings(); + gFirstName = ""; + gLastName = ""; + mValidationDate[CERT_VALIDATION_DATE] = LLDate("2017-04-11T00:00:00.00Z"); + LLFile::remove("test_password.dat"); + LLFile::remove("sechandler_settings.tmp"); + + mX509TestCert = NULL; + mX509RootCert = NULL; + mX509IntermediateCert = NULL; + mX509ChildCert = NULL; + + // Read each of the 4 Pem certs and store in mX509*Cert pointers + BIO * validation_bio; + validation_bio = BIO_new_mem_buf((void*)mPemTestCert.c_str(), mPemTestCert.length()); + PEM_read_bio_X509(validation_bio, &mX509TestCert, 0, NULL); + BIO_free(validation_bio); + + validation_bio = BIO_new_mem_buf((void*)mPemRootCert.c_str(), mPemRootCert.length()); + PEM_read_bio_X509(validation_bio, &mX509RootCert, 0, NULL); + BIO_free(validation_bio); + + validation_bio = BIO_new_mem_buf((void*)mPemIntermediateCert.c_str(), mPemIntermediateCert.length()); + PEM_read_bio_X509(validation_bio, &mX509IntermediateCert, 0, NULL); + BIO_free(validation_bio); + + validation_bio = BIO_new_mem_buf((void*)mPemChildCert.c_str(), mPemChildCert.length()); + PEM_read_bio_X509(validation_bio, &mX509ChildCert, 0, NULL); + BIO_free(validation_bio); + } + ~sechandler_basic_test() + { + LLFile::remove("test_password.dat"); + LLFile::remove("sechandler_settings.tmp"); + LLFile::remove("mycertstore.pem"); + X509_free(mX509TestCert); + X509_free(mX509RootCert); + X509_free(mX509IntermediateCert); + X509_free(mX509ChildCert); + } + }; + + // Tut templating thingamagic: test group, object and test instance + typedef test_group sechandler_basic_test_factory; + typedef sechandler_basic_test_factory::object sechandler_basic_test_object; + tut::sechandler_basic_test_factory tut_test("LLSecHandler"); + + // --------------------------------------------------------------------------------------- + // Test functions + // --------------------------------------------------------------------------------------- + // test cert data retrieval + template<> template<> + void sechandler_basic_test_object::test<1>() + { + try + { + LLPointer test_cert(new LLBasicCertificate(mPemTestCert, &mValidationDate)); + LL_INFOS() << "ok" << LL_ENDL; + } + catch (LLCertException& cert_exception) + { + LL_INFOS() << "cert ex: " << cert_exception.getCertData() << LL_ENDL; + fail("cert exception"); + } + catch (...) + { + LOG_UNHANDLED_EXCEPTION("test 1"); + fail("other exception"); + } + } + + template<> template<> + void sechandler_basic_test_object::test<2>() + { + LLPointer test_cert(new LLBasicCertificate(mPemChildCert, &mValidationDate)); + + LLSD llsd_cert; + test_cert->getLLSD(llsd_cert); + //std::ostringstream llsd_value; + //llsd_value << LLSDOStreamer(llsd_cert) << std::endl; + LL_DEBUGS() << "test 1 cert " << llsd_cert << LL_ENDL; + + ensure_equals("Issuer Name/commonName", (std::string)llsd_cert["issuer_name"]["commonName"], "Integration Test Intermediate CA"); + ensure_equals("Issuer Name/countryName", (std::string)llsd_cert["issuer_name"]["countryName"], "US"); + ensure_equals("Issuer Name/state", (std::string)llsd_cert["issuer_name"]["stateOrProvinceName"], "California"); + ensure_equals("Issuer Name/org name", (std::string)llsd_cert["issuer_name"]["organizationName"], "Linden Lab"); + ensure_equals("Issuer Name/org unit", (std::string)llsd_cert["issuer_name"]["organizationalUnitName"], "Second Life Engineering"); + ensure_equals("Issuer name string", (std::string)llsd_cert["issuer_name_string"], + "emailAddress=noreply@lindenlab.com,CN=Integration Test Intermediate CA,OU=Second Life Engineering,O=Linden Lab,ST=California,C=US"); + ensure_equals("subject Name/commonName", (std::string)llsd_cert["subject_name"]["commonName"], + "Integration Test Server Cert"); + ensure_equals("subject Name/countryName", (std::string)llsd_cert["subject_name"]["countryName"], "US"); + ensure_equals("subject Name/state", (std::string)llsd_cert["subject_name"]["stateOrProvinceName"], "California"); + ensure_equals("subject Name/localityName", (std::string)llsd_cert["subject_name"]["localityName"], "San Francisco"); + ensure_equals("subject Name/org name", (std::string)llsd_cert["subject_name"]["organizationName"], "Linden Lab"); + ensure_equals("subjectName/org unit", + (std::string)llsd_cert["subject_name"]["organizationalUnitName"], "Second Life Engineering"); + + ensure_equals("subject name string", + (std::string)llsd_cert["subject_name_string"], + "emailAddress=noreply@lindenlab.com,CN=Integration Test Server Cert,OU=Second Life Engineering,O=Linden Lab,L=San Francisco,ST=California,C=US"); + ensure_equals("serial number", (std::string)llsd_cert["serial_number"], "1000"); + ensure_equals("valid from", (std::string)llsd_cert["valid_from"], "2018-05-22T22:58:15Z"); + ensure_equals("valid to", (std::string)llsd_cert["valid_to"], "2024-07-19T22:58:15Z"); + LLSD expectedKeyUsage = LLSD::emptyArray(); + expectedKeyUsage.append(LLSD((std::string)"digitalSignature")); + expectedKeyUsage.append(LLSD((std::string)"keyEncipherment")); + ensure("key usage", valueCompareLLSD(llsd_cert["keyUsage"], expectedKeyUsage)); + ensure_equals("basic constraints", llsd_cert["basicConstraints"]["CA"].asInteger(), 0); + + ensure("x509 is equal", !X509_cmp(mX509ChildCert, test_cert->getOpenSSLX509())); + } + + + // test protected data + template<> template<> + void sechandler_basic_test_object::test<3>() + { + std::string protected_data = "sUSh3wj77NG9oAMyt3XIhaej3KLZhLZWFZvI6rIGmwUUOmmelrRg0NI9rkOj8ZDpTPxpwToaBT5u" + "GQhakdaGLJznr9bHr4/6HIC1bouKj4n2rs4TL6j2WSjto114QdlNfLsE8cbbE+ghww58g8SeyLQO" + "nyzXoz+/PBz0HD5SMFDuObccoPW24gmqYySz8YoEWhSwO0pUtEEqOjVRsAJgF5wLAtJZDeuilGsq" + "4ZT9Y4wZ9Rh8nnF3fDUL6IGamHe1ClXM1jgBu10F6UMhZbnH4C3aJ2E9+LiOntU+l3iCb2MpkEpr" + "82r2ZAMwIrpnirL/xoYoyz7MJQYwUuMvBPToZJrxNSsjI+S2Z+I3iEJAELMAAA=="; + + std::vector binary_data(apr_base64_decode_len(protected_data.c_str())); + apr_base64_decode_binary(&binary_data[0], protected_data.c_str()); + + LLXORCipher cipher(gMACAddress, MAC_ADDRESS_BYTES); + cipher.decrypt(&binary_data[0], 16); + unsigned char unique_id[MAC_ADDRESS_BYTES]; + LLMachineID::getUniqueID(unique_id, sizeof(unique_id)); + LLXORCipher cipher2(unique_id, sizeof(unique_id)); + cipher2.encrypt(&binary_data[0], 16); + std::ofstream temp_file("sechandler_settings.tmp", std::ofstream::binary); + temp_file.write((const char *)&binary_data[0], binary_data.size()); + temp_file.close(); + + LLPointer handler = new LLSecAPIBasicHandler("sechandler_settings.tmp", + "test_password.dat"); + handler->init(); + // data retrieval for existing data + LLSD data = handler->getProtectedData("test_data_type", "test_data_id"); + + + ensure_equals("retrieve existing data1", (std::string)data["data1"], "test_data_1"); + ensure_equals("retrieve existing data2", (std::string)data["data2"], "test_data_2"); + ensure_equals("retrieve existing data3", (std::string)data["data3"]["elem1"], "test element1"); + + // data storage + LLSD store_data = LLSD::emptyMap(); + store_data["store_data1"] = "test_store_data1"; + store_data["store_data2"] = 27; + store_data["store_data3"] = LLSD::emptyMap(); + store_data["store_data3"]["subelem1"] = "test_subelem1"; + + handler->setProtectedData("test_data_type", "test_data_id1", store_data); + data = handler->getProtectedData("test_data_type", "test_data_id"); + + data = handler->getProtectedData("test_data_type", "test_data_id"); + // verify no overwrite of existing data + ensure_equals("verify no overwrite 1", (std::string)data["data1"], "test_data_1"); + ensure_equals("verify no overwrite 2", (std::string)data["data2"], "test_data_2"); + ensure_equals("verify no overwrite 3", (std::string)data["data3"]["elem1"], "test element1"); + + // verify written data is good + data = handler->getProtectedData("test_data_type", "test_data_id1"); + ensure_equals("verify stored data1", (std::string)data["store_data1"], "test_store_data1"); + ensure_equals("verify stored data2", (int)data["store_data2"], 27); + ensure_equals("verify stored data3", (std::string)data["store_data3"]["subelem1"], "test_subelem1"); + + // verify overwrite works + handler->setProtectedData("test_data_type", "test_data_id", store_data); + data = handler->getProtectedData("test_data_type", "test_data_id"); + ensure_equals("verify overwrite stored data1", (std::string)data["store_data1"], "test_store_data1"); + ensure_equals("verify overwrite stored data2", (int)data["store_data2"], 27); + ensure_equals("verify overwrite stored data3", (std::string)data["store_data3"]["subelem1"], "test_subelem1"); + + // verify other datatype doesn't conflict + store_data["store_data3"] = "test_store_data3"; + store_data["store_data4"] = 28; + store_data["store_data5"] = LLSD::emptyMap(); + store_data["store_data5"]["subelem2"] = "test_subelem2"; + + handler->setProtectedData("test_data_type1", "test_data_id", store_data); + data = handler->getProtectedData("test_data_type1", "test_data_id"); + ensure_equals("verify datatype stored data3", (std::string)data["store_data3"], "test_store_data3"); + ensure_equals("verify datatype stored data4", (int)data["store_data4"], 28); + ensure_equals("verify datatype stored data5", (std::string)data["store_data5"]["subelem2"], "test_subelem2"); + + // test data not found + + data = handler->getProtectedData("test_data_type1", "test_data_not_found"); + ensure("not found", data.isUndefined()); + + // cause a 'write' by using 'LLPointer' to delete then instantiate a handler + handler = NULL; + handler = new LLSecAPIBasicHandler("sechandler_settings.tmp", "test_password.dat"); + handler->init(); + + data = handler->getProtectedData("test_data_type1", "test_data_id"); + ensure_equals("verify datatype stored data3a", (std::string)data["store_data3"], "test_store_data3"); + ensure_equals("verify datatype stored data4a", (int)data["store_data4"], 28); + ensure_equals("verify datatype stored data5a", (std::string)data["store_data5"]["subelem2"], "test_subelem2"); + + // rewrite the initial file to verify reloads + handler = NULL; + std::ofstream temp_file2("sechandler_settings.tmp", std::ofstream::binary); + temp_file2.write((const char *)&binary_data[0], binary_data.size()); + temp_file2.close(); + + // cause a 'write' + handler = new LLSecAPIBasicHandler("sechandler_settings.tmp", "test_password.dat"); + handler->init(); + data = handler->getProtectedData("test_data_type1", "test_data_id"); + ensure("not found", data.isUndefined()); + + handler->deleteProtectedData("test_data_type", "test_data_id"); + ensure("Deleted data not found", handler->getProtectedData("test_data_type", "test_data_id").isUndefined()); + + LLFile::remove("sechandler_settings.tmp"); + handler = new LLSecAPIBasicHandler("sechandler_settings.tmp", "test_password.dat"); + handler->init(); + data = handler->getProtectedData("test_data_type1", "test_data_id"); + ensure("not found", data.isUndefined()); + handler = NULL; + + ensure(LLFile::isfile("sechandler_settings.tmp")); + } + + // test credenitals + template<> template<> + void sechandler_basic_test_object::test<4>() + { + LLPointer handler = new LLSecAPIBasicHandler("sechandler_settings.tmp", "test_password.dat"); + handler->init(); + + LLSD my_id = LLSD::emptyMap(); + LLSD my_authenticator = LLSD::emptyMap(); + my_id["type"] = "test_type"; + my_id["username"] = "testuser@lindenlab.com"; + my_authenticator["type"] = "test_auth"; + my_authenticator["creds"] = "12345"; + + // test creation of credentials + LLPointer my_cred = handler->createCredential("my_grid", my_id, my_authenticator); + + // test retrieval of credential components + ensure_equals("basic credential creation: identifier", my_id, my_cred->getIdentifier()); + ensure_equals("basic credential creation: authenticator", my_authenticator, my_cred->getAuthenticator()); + ensure_equals("basic credential creation: grid", "my_grid", my_cred->getGrid()); + + // test setting/overwriting of credential components + my_id["first_name"] = "firstname"; + my_id.erase("username"); + my_authenticator.erase("creds"); + my_authenticator["hash"] = "6563245"; + + my_cred->setCredentialData(my_id, my_authenticator); + ensure_equals("set credential data: identifier", my_id, my_cred->getIdentifier()); + ensure_equals("set credential data: authenticator", my_authenticator, my_cred->getAuthenticator()); + ensure_equals("set credential data: grid", "my_grid", my_cred->getGrid()); + + // test loading of a credential, that hasn't been saved, without + // any legacy saved credential data + LLPointer my_new_cred = handler->loadCredential("my_grid2"); + ensure("unknown credential load test", my_new_cred->getIdentifier().isMap()); + ensure("unknown credential load test", !my_new_cred->getIdentifier().has("type")); + ensure("unknown credential load test", my_new_cred->getAuthenticator().isMap()); + ensure("unknown credential load test", !my_new_cred->getAuthenticator().has("type")); + // test saving of a credential + handler->saveCredential(my_cred, true); + + // test loading of a known credential + my_new_cred = handler->loadCredential("my_grid"); + ensure_equals("load a known credential: identifier", my_id, my_new_cred->getIdentifier()); + ensure_equals("load a known credential: authenticator",my_authenticator, my_new_cred->getAuthenticator()); + ensure_equals("load a known credential: grid", "my_grid", my_cred->getGrid()); + + // test deletion of a credential + handler->deleteCredential(my_new_cred); + + ensure("delete credential: identifier", my_new_cred->getIdentifier().isUndefined()); + ensure("delete credentialt: authenticator", my_new_cred->getIdentifier().isUndefined()); + ensure_equals("delete credential: grid", "my_grid", my_cred->getGrid()); + // load unknown cred + + my_new_cred = handler->loadCredential("my_grid"); + ensure("deleted credential load test", my_new_cred->getIdentifier().isMap()); + ensure("deleted credential load test", !my_new_cred->getIdentifier().has("type")); + ensure("deleted credential load test", my_new_cred->getAuthenticator().isMap()); + ensure("deleted credential load test", !my_new_cred->getAuthenticator().has("type")); + + // test loading of an unknown credential with legacy saved username, but without + // saved password + gFirstName = "myfirstname"; + gLastName = "mylastname"; + my_new_cred = handler->loadCredential("my_legacy_grid"); + ensure_equals("legacy credential with no password: type", + (const std::string)my_new_cred->getIdentifier()["type"], "agent"); + ensure_equals("legacy credential with no password: first_name", + (const std::string)my_new_cred->getIdentifier()["first_name"], "myfirstname"); + ensure_equals("legacy credential with no password: last_name", + (const std::string)my_new_cred->getIdentifier()["last_name"], "mylastname"); + + ensure("legacy credential with no password: no authenticator", my_new_cred->getAuthenticator().isUndefined()); + + // test loading of an unknown credential with legacy saved password and username + + std::string hashed_password = "fSQcLG03eyIWJmkzfyYaKm81dSweLmsxeSAYKGE7fSQ="; + int length = apr_base64_decode_len(hashed_password.c_str()); + std::vector decoded_password(length); + apr_base64_decode(&decoded_password[0], hashed_password.c_str()); + LLXORCipher cipher(gMACAddress, MAC_ADDRESS_BYTES); + cipher.decrypt((U8*)&decoded_password[0], length); + unsigned char unique_id[MAC_ADDRESS_BYTES]; + LLMachineID::getUniqueID(unique_id, sizeof(unique_id)); + LLXORCipher cipher2(unique_id, sizeof(unique_id)); + cipher2.encrypt((U8*)&decoded_password[0], length); + llofstream password_file("test_password.dat", std::ofstream::binary); + password_file.write(&decoded_password[0], length); + password_file.close(); + + my_new_cred = handler->loadCredential("my_legacy_grid2"); + ensure_equals("legacy credential with password: type", + (const std::string)my_new_cred->getIdentifier()["type"], "agent"); + ensure_equals("legacy credential with password: first_name", + (const std::string)my_new_cred->getIdentifier()["first_name"], "myfirstname"); + ensure_equals("legacy credential with password: last_name", + (const std::string)my_new_cred->getIdentifier()["last_name"], "mylastname"); + + LLSD legacy_authenticator = my_new_cred->getAuthenticator(); + ensure_equals("legacy credential with password: type", + (std::string)legacy_authenticator["type"], + "hash"); + ensure_equals("legacy credential with password: algorithm", + (std::string)legacy_authenticator["algorithm"], + "md5"); + ensure_equals("legacy credential with password: algorithm", + (std::string)legacy_authenticator["secret"], + "01234567890123456789012345678901"); + + // test creation of credentials + my_cred = handler->createCredential("mysavedgrid", my_id, my_authenticator); + // test save without saving authenticator. + handler->saveCredential(my_cred, false); + my_new_cred = handler->loadCredential("mysavedgrid"); + ensure_equals("saved credential without auth", + (const std::string)my_new_cred->getIdentifier()["type"], "test_type"); + ensure("no authenticator values were saved", my_new_cred->getAuthenticator().isUndefined()); + } + + // test cert vector + template<> template<> + void sechandler_basic_test_object::test<5>() + { + // validate create from empty vector + LLPointer test_vector = new LLBasicCertificateVector(); + ensure_equals("when loading with nothing, we should result in no certs in vector", test_vector->size(), 0); + + test_vector->add(new LLBasicCertificate(mPemTestCert, &mValidationDate)); + ensure_equals("one element in vector", test_vector->size(), 1); + test_vector->add(new LLBasicCertificate(mPemChildCert, &mValidationDate)); + ensure_equals("two elements in vector after add", test_vector->size(), 2); + + // add duplicate; should be a no-op (and log at DEBUG level) + test_vector->add(new LLBasicCertificate(mPemChildCert, &mValidationDate)); + ensure_equals("two elements in vector after re-add", test_vector->size(), 2); + + // validate order + X509* test_cert = (*test_vector)[0]->getOpenSSLX509(); + ensure("first cert added remains first cert", !X509_cmp(test_cert, mX509TestCert)); + X509_free(test_cert); + + test_cert = (*test_vector)[1]->getOpenSSLX509(); + ensure("second cert is second cert", !X509_cmp(test_cert, mX509ChildCert)); + X509_free(test_cert); + + // + // validate iterator + // + LLBasicCertificateVector::iterator current_cert = test_vector->begin(); + LLBasicCertificateVector::iterator copy_current_cert = current_cert; + // operator++(int) + ensure("validate iterator++ element in vector is expected cert", *current_cert++ == (*test_vector)[0]); + ensure("validate 2nd iterator++ element in vector is expected cert", *current_cert++ == (*test_vector)[1]); + ensure("validate end iterator++", current_cert == test_vector->end()); + + // copy + ensure("validate copy iterator element in vector is expected cert", *copy_current_cert == (*test_vector)[0]); + + // operator--(int) + current_cert--; + ensure("validate iterator-- element in vector is expected cert", *current_cert-- == (*test_vector)[1]); + ensure("validate iterator-- element in vector is expected cert", *current_cert == (*test_vector)[0]); + + ensure("begin iterator is equal", current_cert == test_vector->begin()); + + // operator++ + ensure("validate ++iterator element in vector is expected cert", *++current_cert == (*test_vector)[1]); + ensure("end of cert vector after ++iterator", ++current_cert == test_vector->end()); + // operator-- + ensure("validate --iterator element in vector is expected cert", *--current_cert == (*test_vector)[1]); + ensure("validate 2nd --iterator element in vector is expected cert", *--current_cert == (*test_vector)[0]); + + test_vector->erase(test_vector->begin()); + ensure_equals("one element in store after remove", test_vector->size(), 1); + test_cert = (*test_vector)[0]->getOpenSSLX509(); + ensure("Child cert remains", !X509_cmp(test_cert, mX509ChildCert)); + X509_free(test_cert); + + // validate insert + test_vector->insert(test_vector->begin(), new LLBasicCertificate(mPemIntermediateCert, &mValidationDate)); + test_cert = (*test_vector)[0]->getOpenSSLX509(); + ensure_equals("two elements in store after insert", test_vector->size(), 2); + ensure("validate intermediate cert was inserted at first position", !X509_cmp(test_cert, mX509IntermediateCert)); + X509_free(test_cert); + test_cert = (*test_vector)[1]->getOpenSSLX509(); + ensure("validate child cert still there", !X509_cmp(test_cert, mX509ChildCert)); + X509_free(test_cert); + + //validate find + LLSD find_info = LLSD::emptyMap(); + find_info["subjectKeyIdentifier"] = "bb:59:9f:de:6b:51:a7:6c:b3:6d:5b:8b:42:f7:b1:65:77:17:a4:e4"; + LLBasicCertificateVector::iterator found_cert = test_vector->find(find_info); + ensure("found some cert", found_cert != test_vector->end()); + X509* found_x509 = (*found_cert).get()->getOpenSSLX509(); + ensure("child cert was found", !X509_cmp(found_x509, mX509ChildCert)); + X509_free(found_x509); + + find_info["subjectKeyIdentifier"] = "00:00:00:00"; // bogus + current_cert =test_vector->find(find_info); + ensure("didn't find cert", current_cert == test_vector->end()); + } + + // test cert store + template<> template<> + void sechandler_basic_test_object::test<6>() + { + // validate load with nothing + LLFile::remove("mycertstore.pem"); + LLPointer test_store = new LLBasicCertificateStore("mycertstore.pem"); + ensure_equals("when loading with nothing, we should result in no certs in store", test_store->size(), 0); + + // validate load with empty file + test_store->save(); + test_store = NULL; + test_store = new LLBasicCertificateStore("mycertstore.pem"); + ensure_equals("when loading with nothing, we should result in no certs in store", test_store->size(), 0); + test_store=NULL; + + // instantiate a cert store from a file + llofstream certstorefile("mycertstore.pem", std::ios::out); + certstorefile << mPemChildCert << std::endl << mPemTestCert << std::endl; + certstorefile.close(); + // validate loaded certs + test_store = new LLBasicCertificateStore("mycertstore.pem"); + ensure_equals("two elements in store", test_store->size(), 2); + + // operator[] + X509* test_cert = (*test_store)[0]->getOpenSSLX509(); + + ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); + X509_free(test_cert); + test_cert = (*test_store)[1]->getOpenSSLX509(); + ensure("validate second element in store is expected cert", !X509_cmp(test_cert, mX509TestCert)); + X509_free(test_cert); + + + // validate save + LLFile::remove("mycertstore.pem"); + test_store->save(); + test_store = NULL; + test_store = new LLBasicCertificateStore("mycertstore.pem"); + ensure_equals("two elements in store after save", test_store->size(), 2); + LLCertificateStore::iterator current_cert = test_store->begin(); + test_cert = (*current_cert)->getOpenSSLX509(); + ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); + current_cert++; + X509_free(test_cert); + test_cert = (*current_cert)->getOpenSSLX509(); + ensure("validate second element in store is expected cert", !X509_cmp(test_cert, mX509TestCert)); + X509_free(test_cert); + current_cert++; + ensure("end of cert store", current_cert == test_store->end()); + + } + + // cert name wildcard matching + template<> template<> + void sechandler_basic_test_object::test<7>() + { + ensure("simple name match", + _cert_hostname_wildcard_match("foo", "foo")); + + ensure("simple name match, with end period", + _cert_hostname_wildcard_match("foo.", "foo.")); + + ensure("simple name match, with begin period", + _cert_hostname_wildcard_match(".foo", ".foo")); + + ensure("simple name match, with mismatched period cn", + _cert_hostname_wildcard_match("foo.", "foo")); + + ensure("simple name match, with mismatched period hostname", + _cert_hostname_wildcard_match("foo", "foo.")); + + ensure("simple name match, with subdomain", + _cert_hostname_wildcard_match("foo.bar", "foo.bar")); + + ensure("stutter name match", + _cert_hostname_wildcard_match("foobbbbfoo", "foo*bbbfoo")); + + ensure("simple name match, with beginning wildcard", + _cert_hostname_wildcard_match("foobar", "*bar")); + + ensure("simple name match, with ending wildcard", + _cert_hostname_wildcard_match("foobar", "foo*")); + + ensure("simple name match, with beginning null wildcard", + _cert_hostname_wildcard_match("foobar", "*foobar")); + + ensure("simple name match, with ending null wildcard", + _cert_hostname_wildcard_match("foobar", "foobar*")); + + ensure("simple name match, with embedded wildcard", + _cert_hostname_wildcard_match("foobar", "f*r")); + + ensure("simple name match, with embedded null wildcard", + _cert_hostname_wildcard_match("foobar", "foo*bar")); + + ensure("simple name match, with dual embedded wildcard", + _cert_hostname_wildcard_match("foobar", "f*o*ar")); + + ensure("simple name mismatch", + !_cert_hostname_wildcard_match("bar", "foo")); + + ensure("simple name mismatch, with end period", + !_cert_hostname_wildcard_match("foobar.", "foo.")); + + ensure("simple name mismatch, with begin period", + !_cert_hostname_wildcard_match(".foobar", ".foo")); + + ensure("simple name mismatch, with subdomain", + !_cert_hostname_wildcard_match("foobar.bar", "foo.bar")); + + ensure("simple name mismatch, with beginning wildcard", + !_cert_hostname_wildcard_match("foobara", "*bar")); + + ensure("simple name mismatch, with ending wildcard", + !_cert_hostname_wildcard_match("oobar", "foo*")); + + ensure("simple name mismatch, with embedded wildcard", + !_cert_hostname_wildcard_match("oobar", "f*r")); + + ensure("simple name mismatch, with dual embedded wildcard", + !_cert_hostname_wildcard_match("foobar", "f*d*ar")); + + ensure("simple wildcard", + _cert_hostname_wildcard_match("foobar", "*")); + + ensure("long domain", + _cert_hostname_wildcard_match("foo.bar.com", "foo.bar.com")); + + ensure("long domain with multiple wildcards", + _cert_hostname_wildcard_match("foo.bar.com", "*.b*r.com")); + + ensure("end periods", + _cert_hostname_wildcard_match("foo.bar.com.", "*.b*r.com.")); + + ensure("match end period", + _cert_hostname_wildcard_match("foo.bar.com.", "*.b*r.com")); + + ensure("match end period2", + _cert_hostname_wildcard_match("foo.bar.com", "*.b*r.com.")); + + ensure("wildcard mismatch", + !_cert_hostname_wildcard_match("bar.com", "*.bar.com")); + + ensure("wildcard match", + _cert_hostname_wildcard_match("foo.bar.com", "*.bar.com")); + + ensure("wildcard match", + _cert_hostname_wildcard_match("foo.foo.bar.com", "*.bar.com")); + + ensure("wildcard match", + _cert_hostname_wildcard_match("foo.foo.bar.com", "*.*.com")); + + ensure("wildcard mismatch", + !_cert_hostname_wildcard_match("foo.foo.bar.com", "*.foo.com")); + } + + // test cert chain + template<> template<> + void sechandler_basic_test_object::test<8>() + { + // validate create from empty chain + LLPointer test_chain = new LLBasicCertificateChain(NULL); + ensure_equals("when loading with nothing, we should result in no certs in chain", test_chain->size(), 0); + + // Single cert in the chain. + X509_STORE_CTX *test_store = X509_STORE_CTX_new(); + X509_STORE_CTX_set_cert(test_store, mX509ChildCert); + X509_STORE_CTX_set0_untrusted(test_store, NULL); + test_chain = new LLBasicCertificateChain(test_store); + X509_STORE_CTX_free(test_store); + ensure_equals("two elements in store", test_chain->size(), 1); + X509* test_cert = (*test_chain)[0]->getOpenSSLX509(); + ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); + X509_free(test_cert); + + // cert + CA + + test_store = X509_STORE_CTX_new(); + X509_STORE_CTX_set_cert(test_store, mX509ChildCert); + X509_STORE_CTX_set0_untrusted(test_store, sk_X509_new_null()); + sk_X509_push(X509_STORE_CTX_get0_untrusted(test_store), mX509IntermediateCert); + test_chain = new LLBasicCertificateChain(test_store); + X509_STORE_CTX_free(test_store); + ensure_equals("two elements in store", test_chain->size(), 2); + test_cert = (*test_chain)[0]->getOpenSSLX509(); + ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); + X509_free(test_cert); + test_cert = (*test_chain)[1]->getOpenSSLX509(); + ensure("validate second element in store is expected cert", !X509_cmp(test_cert, mX509IntermediateCert)); + X509_free(test_cert); + + // cert + nonrelated + + test_store = X509_STORE_CTX_new(); + X509_STORE_CTX_set_cert(test_store, mX509ChildCert); + X509_STORE_CTX_set0_untrusted(test_store, sk_X509_new_null()); + sk_X509_push(X509_STORE_CTX_get0_untrusted(test_store), mX509TestCert); + test_chain = new LLBasicCertificateChain(test_store); + X509_STORE_CTX_free(test_store); + ensure_equals("two elements in store", test_chain->size(), 1); + test_cert = (*test_chain)[0]->getOpenSSLX509(); + ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); + X509_free(test_cert); + + // cert + CA + nonrelated + test_store = X509_STORE_CTX_new(); + X509_STORE_CTX_set_cert(test_store, mX509ChildCert); + X509_STORE_CTX_set0_untrusted(test_store, sk_X509_new_null()); + sk_X509_push(X509_STORE_CTX_get0_untrusted(test_store), mX509IntermediateCert); + sk_X509_push(X509_STORE_CTX_get0_untrusted(test_store), mX509TestCert); + test_chain = new LLBasicCertificateChain(test_store); + X509_STORE_CTX_free(test_store); + ensure_equals("two elements in store", test_chain->size(), 2); + test_cert = (*test_chain)[0]->getOpenSSLX509(); + ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); + X509_free(test_cert); + test_cert = (*test_chain)[1]->getOpenSSLX509(); + ensure("validate second element in store is expected cert", !X509_cmp(test_cert, mX509IntermediateCert)); + X509_free(test_cert); + + // cert + intermediate + CA + test_store = X509_STORE_CTX_new(); + X509_STORE_CTX_set_cert(test_store, mX509ChildCert); + X509_STORE_CTX_set0_untrusted(test_store, sk_X509_new_null()); + sk_X509_push(X509_STORE_CTX_get0_untrusted(test_store), mX509IntermediateCert); + sk_X509_push(X509_STORE_CTX_get0_untrusted(test_store), mX509RootCert); + test_chain = new LLBasicCertificateChain(test_store); + X509_STORE_CTX_free(test_store); + ensure_equals("three elements in store", test_chain->size(), 3); + test_cert = (*test_chain)[0]->getOpenSSLX509(); + ensure("validate first element in store is expected cert", !X509_cmp(test_cert, mX509ChildCert)); + X509_free(test_cert); + test_cert = (*test_chain)[1]->getOpenSSLX509(); + ensure("validate second element in store is expected cert", !X509_cmp(test_cert, mX509IntermediateCert)); + X509_free(test_cert); + + test_cert = (*test_chain)[2]->getOpenSSLX509(); + ensure("validate second element in store is expected cert", !X509_cmp(test_cert, mX509RootCert)); + X509_free(test_cert); + } + + // test cert validation + template<> template<> + void sechandler_basic_test_object::test<9>() + { + // start with a trusted store with our known root cert + LLFile::remove("mycertstore.pem"); + LLPointer test_store = new LLBasicCertificateStore("mycertstore.pem"); + test_store->add(new LLBasicCertificate(mX509RootCert, &mValidationDate)); + LLSD validation_params; + + // validate basic trust for a chain containing only the intermediate cert. (1 deep) + LLPointer test_chain = new LLBasicCertificateChain(NULL); + + test_chain->add(new LLBasicCertificate(mX509IntermediateCert, &mValidationDate)); + + test_store->validate(0, test_chain, validation_params); + + // add the root certificate to the chain and revalidate + test_chain->add(new LLBasicCertificate(mX509RootCert, &mValidationDate)); + test_store->validate(0, test_chain, validation_params); + + // add the child cert at the head of the chain, and revalidate (3 deep chain) + test_chain->insert(test_chain->begin(), new LLBasicCertificate(mX509ChildCert, &mValidationDate)); + test_store->validate(0, test_chain, validation_params); + + // basic failure cases + test_chain = new LLBasicCertificateChain(NULL); + //validate with only the child cert in chain, but child cert was previously + // trusted + test_chain->add(new LLBasicCertificate(mX509ChildCert, &mValidationDate)); + + // validate without the trust flag. + test_store->validate(VALIDATION_POLICY_TRUSTED, test_chain, validation_params); + + // Validate with child cert but no parent, and no parent in CA store + test_store = new LLBasicCertificateStore("mycertstore.pem"); + ensure_throws("no CA, with only a child cert", + LLCertValidationTrustException, + (*test_chain)[0], + test_store->validate, + VALIDATION_POLICY_TRUSTED, + test_chain, + validation_params); + + + // validate without the trust flag. + test_store->validate(0, test_chain, validation_params); + + // clear out the store + test_store = new LLBasicCertificateStore("mycertstore.pem"); + // append the intermediate cert + test_chain->add(new LLBasicCertificate(mX509IntermediateCert, &mValidationDate)); + ensure_throws("no CA, with child and intermediate certs", + LLCertValidationTrustException, + (*test_chain)[1], + test_store->validate, + VALIDATION_POLICY_TRUSTED | VALIDATION_POLICY_TRUSTED, + test_chain, + validation_params); + // validate without the trust flag + test_store->validate(0, test_chain, validation_params); + + // Test time validity + LLSD child_info; + ((*test_chain)[0])->getLLSD(child_info); + validation_params = LLSD::emptyMap(); + validation_params[CERT_VALIDATION_DATE] = LLDate(child_info[CERT_VALID_FROM].asDate().secondsSinceEpoch() + 1.0); + test_store->validate(VALIDATION_POLICY_TIME | VALIDATION_POLICY_TRUSTED, + test_chain, validation_params); + + validation_params = LLSD::emptyMap(); + validation_params[CERT_VALIDATION_DATE] = child_info[CERT_VALID_FROM].asDate(); + + validation_params[CERT_VALIDATION_DATE] = LLDate(child_info[CERT_VALID_FROM].asDate().secondsSinceEpoch() - 1.0); + + // test not yet valid + ensure_throws("Child cert not yet valid" , + LLCertValidationExpirationException, + (*test_chain)[0], + test_store->validate, + VALIDATION_POLICY_TIME | VALIDATION_POLICY_TRUSTED, + test_chain, + validation_params); + validation_params = LLSD::emptyMap(); + validation_params[CERT_VALIDATION_DATE] = LLDate(child_info[CERT_VALID_TO].asDate().secondsSinceEpoch() + 1.0); + + // test cert expired + ensure_throws("Child cert expired", + LLCertValidationExpirationException, + (*test_chain)[0], + test_store->validate, + VALIDATION_POLICY_TIME | VALIDATION_POLICY_TRUSTED, + test_chain, + validation_params); + + // test SSL KU + // validate basic trust for a chain containing child and intermediate. + test_chain = new LLBasicCertificateChain(NULL); + test_chain->add(new LLBasicCertificate(mX509ChildCert, &mValidationDate)); + test_chain->add(new LLBasicCertificate(mX509IntermediateCert, &mValidationDate)); + test_store->validate(VALIDATION_POLICY_SSL_KU | VALIDATION_POLICY_TRUSTED, + test_chain, validation_params); + + test_chain = new LLBasicCertificateChain(NULL); + test_chain->add(new LLBasicCertificate(mX509TestCert, &mValidationDate)); + + test_store = new LLBasicCertificateStore("mycertstore.pem"); + ensure_throws("Cert doesn't have ku", + LLCertKeyUsageValidationException, + (*test_chain)[0], + test_store->validate, + VALIDATION_POLICY_SSL_KU | VALIDATION_POLICY_TRUSTED, + test_chain, + validation_params); + + test_store->validate(0, test_chain, validation_params); + } + +}; + diff --git a/indra/newview/tests/llslurl_test.cpp b/indra/newview/tests/llslurl_test.cpp index b96560b09d..3be44a9bd5 100644 --- a/indra/newview/tests/llslurl_test.cpp +++ b/indra/newview/tests/llslurl_test.cpp @@ -1,340 +1,340 @@ -/** - * @file llsecapi_test.cpp - * @author Roxie - * @date 2009-02-10 - * @brief Test the sec api functionality - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2014, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ -#include "../llviewerprecompiledheaders.h" -#include "../llviewernetwork.h" -#include "../test/lltut.h" -#include "../llslurl.h" -#include "../../llxml/llcontrol.h" -#include "llsdserialize.h" - -namespace -{ - -// Should not collide with other test programs creating temp files. -static const char * const TEST_FILENAME("llslurl_test.xml"); - -} - -// -// Stub implementation for LLTrans -// -class LLTrans -{ -public: - static std::string getString(const std::string &xml_desc, const LLStringUtil::format_map_t& args, bool def_string = false); -}; - -std::string LLTrans::getString(const std::string &xml_desc, const LLStringUtil::format_map_t& args, bool def_string) -{ - return std::string(); -} - -//---------------------------------------------------------------------------- -// Mock objects for the dependencies of the code we're testing - -LLControlGroup::LLControlGroup(const std::string& name) -: LLInstanceTracker(name) {} -LLControlGroup::~LLControlGroup() {} -LLControlVariable* LLControlGroup::declareString(const std::string& name, - const std::string& initial_val, - const std::string& comment, - LLControlVariable::ePersist persist) {return NULL;} -void LLControlGroup::setString(std::string_view name, const std::string& val){} - -std::string gCmdLineLoginURI; -std::string gCmdLineGridChoice; -std::string gCmdLineHelperURI; -std::string gLoginPage; -std::string gCurrentGrid; -std::string LLControlGroup::getString(std::string_view name) -{ - if (name == "CmdLineGridChoice") - return gCmdLineGridChoice; - else if (name == "CmdLineHelperURI") - return gCmdLineHelperURI; - else if (name == "LoginPage") - return gLoginPage; - else if (name == "CurrentGrid") - return gCurrentGrid; - return ""; -} - -LLSD LLControlGroup::getLLSD(std::string_view name) -{ - if (name == "CmdLineLoginURI") - { - if(!gCmdLineLoginURI.empty()) - { - return LLSD(gCmdLineLoginURI); - } - } - return LLSD(); -} - -LLPointer LLControlGroup::getControl(std::string_view name) -{ - ctrl_name_table_t::iterator iter = mNameTable.find(name.data()); - return iter == mNameTable.end() ? LLPointer() : iter->second; -} - -LLControlGroup gSavedSettings("test"); -const char *gSampleGridFile = - "" - "" - " " - " foo.bar.com" - " " - " helper_urihttps://foobar/helpers/" - " labelFoobar Grid" - " login_pagefoobar/loginpage" - " login_uri" - " " - " foobar/loginuri" - " " - " keynamefoo.bar.com" - " credential_typeagent" - " grid_login_idFooBar" - " " - " my.grid.com" - " " - " helper_urihttps://mygrid/helpers/" - " labelMy Grid" - " login_pagemygrid/loginpage" - " login_uri" - " " - " mygrid/loginuri" - " " - " keynamemy.grid.com" - " credential_typeagent" - " grid_login_idMyGrid" - " " - " " - "" - ; - -// ------------------------------------------------------------------------------------------- -// TUT -// ------------------------------------------------------------------------------------------- -namespace tut -{ - // Test wrapper declaration : wrapping nothing for the moment - struct slurlTest - { - slurlTest() - { - LLGridManager::getInstance()->initialize(std::string("")); - } - ~slurlTest() - { - } - }; - - // Tut templating thingamagic: test group, object and test instance - typedef test_group slurlTestFactory; - typedef slurlTestFactory::object slurlTestObject; - tut::slurlTestFactory tut_test("LLSlurl"); - - // --------------------------------------------------------------------------------------- - // Test functions - // --------------------------------------------------------------------------------------- - // construction from slurl string - template<> template<> - void slurlTestObject::test<1>() - { - llofstream gridfile(TEST_FILENAME); - gridfile << gSampleGridFile; - gridfile.close(); - - LLGridManager::getInstance()->initialize(TEST_FILENAME); - - LLGridManager::getInstance()->setGridChoice("util.agni.lindenlab.com"); - - LLSLURL slurl = LLSLURL(""); - ensure_equals("null slurl", (int)slurl.getType(), LLSLURL::LAST_LOCATION); - - slurl = LLSLURL("http://slurl.com/secondlife/myregion"); - ensure_equals("slurl.com slurl, region only - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("slurl.com slurl, region only", slurl.getSLURLString(), - "http://maps.secondlife.com/secondlife/myregion/128/128/0"); - - slurl = LLSLURL("http://maps.secondlife.com/secondlife/myregion/1/2/3"); - ensure_equals("maps.secondlife.com slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("maps.secondlife.com slurl, region + coords", slurl.getSLURLString(), - "http://maps.secondlife.com/secondlife/myregion/1/2/3"); - - slurl = LLSLURL("secondlife://"); - ensure_equals("secondlife: slurl, empty - type", slurl.getType(), LLSLURL::EMPTY); - - slurl = LLSLURL("secondlife:///"); - ensure_equals("secondlife: slurl, root - type", slurl.getType(), LLSLURL::EMPTY); - - slurl = LLSLURL("secondlife://myregion"); - ensure_equals("secondlife: slurl, region only - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("secondlife: slurl, region only", slurl.getSLURLString(), - "http://maps.secondlife.com/secondlife/myregion/128/128/0"); - - slurl = LLSLURL("secondlife://myregion/1/2/3"); - ensure_equals("secondlife: slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("secondlife slurl, region + coords", slurl.getSLURLString(), - "http://maps.secondlife.com/secondlife/myregion/1/2/3"); - - slurl = LLSLURL("/myregion"); - ensure_equals("/region slurl, region- type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("/region slurl, region ", slurl.getSLURLString(), - "http://maps.secondlife.com/secondlife/myregion/128/128/0"); - - slurl = LLSLURL("/myregion/1/2/3"); - ensure_equals("/: slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("/ slurl, region + coords", slurl.getSLURLString(), - "http://maps.secondlife.com/secondlife/myregion/1/2/3"); - - slurl = LLSLURL("my region/1/2/3"); - ensure_equals(" slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals(" slurl, region + coords", slurl.getSLURLString(), - "http://maps.secondlife.com/secondlife/my%20region/1/2/3"); - - LLGridManager::getInstance()->setGridChoice("my.grid.com"); - slurl = LLSLURL("https://my.grid.com/region/my%20region/1/2/3"); - ensure_equals("grid slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("grid slurl, region + coords", slurl.getSLURLString(), - "https://my.grid.com/region/my%20region/1/2/3"); - - slurl = LLSLURL("https://my.grid.com/region/my region"); - ensure_equals("grid slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("grid slurl, region + coords", slurl.getSLURLString(), - "https://my.grid.com/region/my%20region/128/128/0"); - - LLGridManager::getInstance()->setGridChoice("foo.bar.com"); - slurl = LLSLURL("/myregion/1/2/3"); - ensure_equals("/: slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("/ slurl, region + coords", slurl.getSLURLString(), - "https://foo.bar.com/region/myregion/1/2/3"); - - slurl = LLSLURL("myregion/1/2/3"); - ensure_equals(": slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals(" slurl, region + coords", slurl.getSLURLString(), - "https://foo.bar.com/region/myregion/1/2/3"); - - slurl = LLSLURL(LLSLURL::SIM_LOCATION_HOME); - ensure_equals("home", slurl.getType(), LLSLURL::HOME_LOCATION); - - slurl = LLSLURL(LLSLURL::SIM_LOCATION_LAST); - ensure_equals("last", slurl.getType(), LLSLURL::LAST_LOCATION); - - slurl = LLSLURL("secondlife:///app/foo/bar?12345"); - ensure_equals("app", slurl.getType(), LLSLURL::APP); - ensure_equals("appcmd", slurl.getAppCmd(), "foo"); - ensure_equals("apppath", slurl.getAppPath().size(), 1); - ensure_equals("apppath2", slurl.getAppPath()[0].asString(), "bar"); - ensure_equals("appquery", slurl.getAppQuery(), "12345"); - ensure_equals("grid1", slurl.getGrid(), "FooBar"); - - slurl = LLSLURL("secondlife://Aditi/app/foo/bar?12345"); - ensure_equals("app", slurl.getType(), LLSLURL::APP); - ensure_equals("appcmd", slurl.getAppCmd(), "foo"); - ensure_equals("apppath", slurl.getAppPath().size(), 1); - ensure_equals("apppath2", slurl.getAppPath()[0].asString(), "bar"); - ensure_equals("appquery", slurl.getAppQuery(), "12345"); - ensure_equals("grid2", slurl.getGrid(), "Aditi"); - - LLGridManager::getInstance()->setGridChoice("foo.bar.com"); - slurl = LLSLURL("secondlife:///secondlife/myregion/1/2/3"); - ensure_equals("/: slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("location", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("region" , "myregion", slurl.getRegion()); - ensure_equals("grid3", slurl.getGrid(), "util.agni.lindenlab.com"); - - slurl = LLSLURL("secondlife://Aditi/secondlife/myregion/1/2/3"); - ensure_equals("/: slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("location", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("region" , "myregion", slurl.getRegion()); - ensure_equals("grid4", slurl.getGrid(), "Aditi" ); - - LLGridManager::getInstance()->setGridChoice("my.grid.com"); - slurl = LLSLURL("https://my.grid.com/app/foo/bar?12345"); - ensure_equals("app", slurl.getType(), LLSLURL::APP); - ensure_equals("appcmd", slurl.getAppCmd(), "foo"); - ensure_equals("apppath", slurl.getAppPath().size(), 1); - ensure_equals("apppath2", slurl.getAppPath()[0].asString(), "bar"); - ensure_equals("appquery", slurl.getAppQuery(), "12345"); - - } - - // construction from grid/region/vector combos - template<> template<> - void slurlTestObject::test<2>() - { - llofstream gridfile(TEST_FILENAME); - gridfile << gSampleGridFile; - gridfile.close(); - - LLGridManager::getInstance()->initialize(TEST_FILENAME); - - LLSLURL slurl = LLSLURL("my.grid.com", "my region"); - ensure_equals("grid/region - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals("grid/region", slurl.getSLURLString(), - "https://my.grid.com/region/my%20region/128/128/0"); - - slurl = LLSLURL("my.grid.com", "my region", LLVector3(1,2,3)); - ensure_equals("grid/region/vector - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals(" grid/region/vector", slurl.getSLURLString(), - "https://my.grid.com/region/my%20region/1/2/3"); - - LLGridManager::getInstance()->setGridChoice("util.agni.lindenlab.com"); - slurl = LLSLURL("my region", LLVector3(1,2,3)); - ensure_equals("default grid/region/vector - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals(" default grid/region/vector", slurl.getSLURLString(), - "http://maps.secondlife.com/secondlife/my%20region/1/2/3"); - - LLGridManager::getInstance()->setGridChoice("MyGrid"); - slurl = LLSLURL("my region", LLVector3(1,2,3)); - ensure_equals("default grid/region/vector - type", slurl.getType(), LLSLURL::LOCATION); - ensure_equals(" default grid/region/vector", slurl.getSLURLString(), - "https://my.grid.com/region/my%20region/1/2/3"); - - } - // Accessors - template<> template<> - void slurlTestObject::test<3>() - { - llofstream gridfile(TEST_FILENAME); - gridfile << gSampleGridFile; - gridfile.close(); - - LLGridManager::getInstance()->initialize(TEST_FILENAME); - - LLGridManager::getInstance()->setGridChoice("my.grid.com"); - LLSLURL slurl = LLSLURL("https://my.grid.com/region/my%20region/1/2/3"); - ensure_equals("login string", slurl.getLoginString(), "uri:my region&1&2&3"); - ensure_equals("location string", slurl.getLocationString(), "my region/1/2/3"); - ensure_equals("grid", slurl.getGrid(), "my.grid.com"); - ensure_equals("region", slurl.getRegion(), "my region"); - ensure_equals("position", slurl.getPosition(), LLVector3(1, 2, 3)); - - } -} +/** + * @file llsecapi_test.cpp + * @author Roxie + * @date 2009-02-10 + * @brief Test the sec api functionality + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#include "../llviewerprecompiledheaders.h" +#include "../llviewernetwork.h" +#include "../test/lltut.h" +#include "../llslurl.h" +#include "../../llxml/llcontrol.h" +#include "llsdserialize.h" + +namespace +{ + +// Should not collide with other test programs creating temp files. +static const char * const TEST_FILENAME("llslurl_test.xml"); + +} + +// +// Stub implementation for LLTrans +// +class LLTrans +{ +public: + static std::string getString(const std::string &xml_desc, const LLStringUtil::format_map_t& args, bool def_string = false); +}; + +std::string LLTrans::getString(const std::string &xml_desc, const LLStringUtil::format_map_t& args, bool def_string) +{ + return std::string(); +} + +//---------------------------------------------------------------------------- +// Mock objects for the dependencies of the code we're testing + +LLControlGroup::LLControlGroup(const std::string& name) +: LLInstanceTracker(name) {} +LLControlGroup::~LLControlGroup() {} +LLControlVariable* LLControlGroup::declareString(const std::string& name, + const std::string& initial_val, + const std::string& comment, + LLControlVariable::ePersist persist) {return NULL;} +void LLControlGroup::setString(std::string_view name, const std::string& val){} + +std::string gCmdLineLoginURI; +std::string gCmdLineGridChoice; +std::string gCmdLineHelperURI; +std::string gLoginPage; +std::string gCurrentGrid; +std::string LLControlGroup::getString(std::string_view name) +{ + if (name == "CmdLineGridChoice") + return gCmdLineGridChoice; + else if (name == "CmdLineHelperURI") + return gCmdLineHelperURI; + else if (name == "LoginPage") + return gLoginPage; + else if (name == "CurrentGrid") + return gCurrentGrid; + return ""; +} + +LLSD LLControlGroup::getLLSD(std::string_view name) +{ + if (name == "CmdLineLoginURI") + { + if(!gCmdLineLoginURI.empty()) + { + return LLSD(gCmdLineLoginURI); + } + } + return LLSD(); +} + +LLPointer LLControlGroup::getControl(std::string_view name) +{ + ctrl_name_table_t::iterator iter = mNameTable.find(name.data()); + return iter == mNameTable.end() ? LLPointer() : iter->second; +} + +LLControlGroup gSavedSettings("test"); +const char *gSampleGridFile = + "" + "" + " " + " foo.bar.com" + " " + " helper_urihttps://foobar/helpers/" + " labelFoobar Grid" + " login_pagefoobar/loginpage" + " login_uri" + " " + " foobar/loginuri" + " " + " keynamefoo.bar.com" + " credential_typeagent" + " grid_login_idFooBar" + " " + " my.grid.com" + " " + " helper_urihttps://mygrid/helpers/" + " labelMy Grid" + " login_pagemygrid/loginpage" + " login_uri" + " " + " mygrid/loginuri" + " " + " keynamemy.grid.com" + " credential_typeagent" + " grid_login_idMyGrid" + " " + " " + "" + ; + +// ------------------------------------------------------------------------------------------- +// TUT +// ------------------------------------------------------------------------------------------- +namespace tut +{ + // Test wrapper declaration : wrapping nothing for the moment + struct slurlTest + { + slurlTest() + { + LLGridManager::getInstance()->initialize(std::string("")); + } + ~slurlTest() + { + } + }; + + // Tut templating thingamagic: test group, object and test instance + typedef test_group slurlTestFactory; + typedef slurlTestFactory::object slurlTestObject; + tut::slurlTestFactory tut_test("LLSlurl"); + + // --------------------------------------------------------------------------------------- + // Test functions + // --------------------------------------------------------------------------------------- + // construction from slurl string + template<> template<> + void slurlTestObject::test<1>() + { + llofstream gridfile(TEST_FILENAME); + gridfile << gSampleGridFile; + gridfile.close(); + + LLGridManager::getInstance()->initialize(TEST_FILENAME); + + LLGridManager::getInstance()->setGridChoice("util.agni.lindenlab.com"); + + LLSLURL slurl = LLSLURL(""); + ensure_equals("null slurl", (int)slurl.getType(), LLSLURL::LAST_LOCATION); + + slurl = LLSLURL("http://slurl.com/secondlife/myregion"); + ensure_equals("slurl.com slurl, region only - type", slurl.getType(), LLSLURL::LOCATION); + ensure_equals("slurl.com slurl, region only", slurl.getSLURLString(), + "http://maps.secondlife.com/secondlife/myregion/128/128/0"); + + slurl = LLSLURL("http://maps.secondlife.com/secondlife/myregion/1/2/3"); + ensure_equals("maps.secondlife.com slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); + ensure_equals("maps.secondlife.com slurl, region + coords", slurl.getSLURLString(), + "http://maps.secondlife.com/secondlife/myregion/1/2/3"); + + slurl = LLSLURL("secondlife://"); + ensure_equals("secondlife: slurl, empty - type", slurl.getType(), LLSLURL::EMPTY); + + slurl = LLSLURL("secondlife:///"); + ensure_equals("secondlife: slurl, root - type", slurl.getType(), LLSLURL::EMPTY); + + slurl = LLSLURL("secondlife://myregion"); + ensure_equals("secondlife: slurl, region only - type", slurl.getType(), LLSLURL::LOCATION); + ensure_equals("secondlife: slurl, region only", slurl.getSLURLString(), + "http://maps.secondlife.com/secondlife/myregion/128/128/0"); + + slurl = LLSLURL("secondlife://myregion/1/2/3"); + ensure_equals("secondlife: slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); + ensure_equals("secondlife slurl, region + coords", slurl.getSLURLString(), + "http://maps.secondlife.com/secondlife/myregion/1/2/3"); + + slurl = LLSLURL("/myregion"); + ensure_equals("/region slurl, region- type", slurl.getType(), LLSLURL::LOCATION); + ensure_equals("/region slurl, region ", slurl.getSLURLString(), + "http://maps.secondlife.com/secondlife/myregion/128/128/0"); + + slurl = LLSLURL("/myregion/1/2/3"); + ensure_equals("/: slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); + ensure_equals("/ slurl, region + coords", slurl.getSLURLString(), + "http://maps.secondlife.com/secondlife/myregion/1/2/3"); + + slurl = LLSLURL("my region/1/2/3"); + ensure_equals(" slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); + ensure_equals(" slurl, region + coords", slurl.getSLURLString(), + "http://maps.secondlife.com/secondlife/my%20region/1/2/3"); + + LLGridManager::getInstance()->setGridChoice("my.grid.com"); + slurl = LLSLURL("https://my.grid.com/region/my%20region/1/2/3"); + ensure_equals("grid slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); + ensure_equals("grid slurl, region + coords", slurl.getSLURLString(), + "https://my.grid.com/region/my%20region/1/2/3"); + + slurl = LLSLURL("https://my.grid.com/region/my region"); + ensure_equals("grid slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); + ensure_equals("grid slurl, region + coords", slurl.getSLURLString(), + "https://my.grid.com/region/my%20region/128/128/0"); + + LLGridManager::getInstance()->setGridChoice("foo.bar.com"); + slurl = LLSLURL("/myregion/1/2/3"); + ensure_equals("/: slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); + ensure_equals("/ slurl, region + coords", slurl.getSLURLString(), + "https://foo.bar.com/region/myregion/1/2/3"); + + slurl = LLSLURL("myregion/1/2/3"); + ensure_equals(": slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); + ensure_equals(" slurl, region + coords", slurl.getSLURLString(), + "https://foo.bar.com/region/myregion/1/2/3"); + + slurl = LLSLURL(LLSLURL::SIM_LOCATION_HOME); + ensure_equals("home", slurl.getType(), LLSLURL::HOME_LOCATION); + + slurl = LLSLURL(LLSLURL::SIM_LOCATION_LAST); + ensure_equals("last", slurl.getType(), LLSLURL::LAST_LOCATION); + + slurl = LLSLURL("secondlife:///app/foo/bar?12345"); + ensure_equals("app", slurl.getType(), LLSLURL::APP); + ensure_equals("appcmd", slurl.getAppCmd(), "foo"); + ensure_equals("apppath", slurl.getAppPath().size(), 1); + ensure_equals("apppath2", slurl.getAppPath()[0].asString(), "bar"); + ensure_equals("appquery", slurl.getAppQuery(), "12345"); + ensure_equals("grid1", slurl.getGrid(), "FooBar"); + + slurl = LLSLURL("secondlife://Aditi/app/foo/bar?12345"); + ensure_equals("app", slurl.getType(), LLSLURL::APP); + ensure_equals("appcmd", slurl.getAppCmd(), "foo"); + ensure_equals("apppath", slurl.getAppPath().size(), 1); + ensure_equals("apppath2", slurl.getAppPath()[0].asString(), "bar"); + ensure_equals("appquery", slurl.getAppQuery(), "12345"); + ensure_equals("grid2", slurl.getGrid(), "Aditi"); + + LLGridManager::getInstance()->setGridChoice("foo.bar.com"); + slurl = LLSLURL("secondlife:///secondlife/myregion/1/2/3"); + ensure_equals("/: slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); + ensure_equals("location", slurl.getType(), LLSLURL::LOCATION); + ensure_equals("region" , "myregion", slurl.getRegion()); + ensure_equals("grid3", slurl.getGrid(), "util.agni.lindenlab.com"); + + slurl = LLSLURL("secondlife://Aditi/secondlife/myregion/1/2/3"); + ensure_equals("/: slurl, region + coords - type", slurl.getType(), LLSLURL::LOCATION); + ensure_equals("location", slurl.getType(), LLSLURL::LOCATION); + ensure_equals("region" , "myregion", slurl.getRegion()); + ensure_equals("grid4", slurl.getGrid(), "Aditi" ); + + LLGridManager::getInstance()->setGridChoice("my.grid.com"); + slurl = LLSLURL("https://my.grid.com/app/foo/bar?12345"); + ensure_equals("app", slurl.getType(), LLSLURL::APP); + ensure_equals("appcmd", slurl.getAppCmd(), "foo"); + ensure_equals("apppath", slurl.getAppPath().size(), 1); + ensure_equals("apppath2", slurl.getAppPath()[0].asString(), "bar"); + ensure_equals("appquery", slurl.getAppQuery(), "12345"); + + } + + // construction from grid/region/vector combos + template<> template<> + void slurlTestObject::test<2>() + { + llofstream gridfile(TEST_FILENAME); + gridfile << gSampleGridFile; + gridfile.close(); + + LLGridManager::getInstance()->initialize(TEST_FILENAME); + + LLSLURL slurl = LLSLURL("my.grid.com", "my region"); + ensure_equals("grid/region - type", slurl.getType(), LLSLURL::LOCATION); + ensure_equals("grid/region", slurl.getSLURLString(), + "https://my.grid.com/region/my%20region/128/128/0"); + + slurl = LLSLURL("my.grid.com", "my region", LLVector3(1,2,3)); + ensure_equals("grid/region/vector - type", slurl.getType(), LLSLURL::LOCATION); + ensure_equals(" grid/region/vector", slurl.getSLURLString(), + "https://my.grid.com/region/my%20region/1/2/3"); + + LLGridManager::getInstance()->setGridChoice("util.agni.lindenlab.com"); + slurl = LLSLURL("my region", LLVector3(1,2,3)); + ensure_equals("default grid/region/vector - type", slurl.getType(), LLSLURL::LOCATION); + ensure_equals(" default grid/region/vector", slurl.getSLURLString(), + "http://maps.secondlife.com/secondlife/my%20region/1/2/3"); + + LLGridManager::getInstance()->setGridChoice("MyGrid"); + slurl = LLSLURL("my region", LLVector3(1,2,3)); + ensure_equals("default grid/region/vector - type", slurl.getType(), LLSLURL::LOCATION); + ensure_equals(" default grid/region/vector", slurl.getSLURLString(), + "https://my.grid.com/region/my%20region/1/2/3"); + + } + // Accessors + template<> template<> + void slurlTestObject::test<3>() + { + llofstream gridfile(TEST_FILENAME); + gridfile << gSampleGridFile; + gridfile.close(); + + LLGridManager::getInstance()->initialize(TEST_FILENAME); + + LLGridManager::getInstance()->setGridChoice("my.grid.com"); + LLSLURL slurl = LLSLURL("https://my.grid.com/region/my%20region/1/2/3"); + ensure_equals("login string", slurl.getLoginString(), "uri:my region&1&2&3"); + ensure_equals("location string", slurl.getLocationString(), "my region/1/2/3"); + ensure_equals("grid", slurl.getGrid(), "my.grid.com"); + ensure_equals("region", slurl.getRegion(), "my region"); + ensure_equals("position", slurl.getPosition(), LLVector3(1, 2, 3)); + + } +} diff --git a/indra/newview/tests/llviewerassetstats_test.cpp b/indra/newview/tests/llviewerassetstats_test.cpp index 0671e02b90..d5e281bba8 100644 --- a/indra/newview/tests/llviewerassetstats_test.cpp +++ b/indra/newview/tests/llviewerassetstats_test.cpp @@ -1,575 +1,575 @@ -/** - * @file llviewerassetstats_tut.cpp - * @date 2010-10-28 - * @brief Test cases for some of newview/llviewerassetstats.cpp - * - * $LicenseInfo:firstyear=2010&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "linden_common.h" - -#include -#include - -#include "lltut.h" -#include "../llviewerassetstats.h" -#include "lluuid.h" -#include "llsdutil.h" -#include "llregionhandle.h" -#include "lltracethreadrecorder.h" -#include "../llvoavatar.h" - -namespace LLStatViewer -{ - LLTrace::SampleStatHandle<> FPS_SAMPLE("fpssample"); -} - -void LLVOAvatar::getNearbyRezzedStats(std::vector& counts, F32& avg_cloud_time, S32& cloud_avatars) -{ - counts.resize(3); - counts[0] = 0; - counts[1] = 0; - counts[2] = 1; -} - -// static -std::string LLVOAvatar::rezStatusToString(S32 rez_status) -{ - if (rez_status==0) return "cloud"; - if (rez_status==1) return "gray"; - if (rez_status==2) return "textured"; - return "unknown"; -} - -// static -LLViewerStats::StatsAccumulator& LLViewerStats::PhaseMap::getPhaseStats(const std::string& phase_name) -{ - static LLViewerStats::StatsAccumulator junk; - return junk; -} - -static const char * all_keys[] = -{ - "duration", - "fps", - "get_other_http", - "get_other_udp", - "get_texture_temp_http", - "get_texture_temp_udp", - "get_texture_non_temp_http", - "get_texture_non_temp_udp", - "get_wearable_http", - "get_wearable_udp", - "get_sound_http", - "get_sound_udp", - "get_gesture_http", - "get_gesture_udp" -}; - -static const char * resp_keys[] = -{ - "get_other_http", - "get_other_udp", - "get_texture_temp_http", - "get_texture_temp_udp", - "get_texture_non_temp_http", - "get_texture_non_temp_udp", - "get_wearable_http", - "get_wearable_udp", - "get_sound_http", - "get_sound_udp", - "get_gesture_http", - "get_gesture_udp" -}; - -static const char * sub_keys[] = -{ - "dequeued", - "enqueued", - "resp_count", - "resp_max", - "resp_min", - "resp_mean" -}; - -static const char * mmm_resp_keys[] = -{ - "fps" -}; - -static const char * mmm_sub_keys[] = -{ - "count", - "max", - "min", - "mean" -}; - -static const LLUUID region1("4e2d81a3-6263-6ffe-ad5c-8ce04bee07e8"); -static const LLUUID region2("68762cc8-b68b-4e45-854b-e830734f2d4a"); -static const U64 region1_handle(0x0000040000003f00ULL); -static const U64 region2_handle(0x0000030000004200ULL); -static const std::string region1_handle_str("0000040000003f00"); -static const std::string region2_handle_str("0000030000004200"); - -#if 0 -static bool -is_empty_map(const LLSD & sd) -{ - return sd.isMap() && 0 == sd.size(); -} -#endif - -#if 0 -static bool -is_single_key_map(const LLSD & sd, const std::string & key) -{ - return sd.isMap() && 1 == sd.size() && sd.has(key); -} -#endif - -static bool -is_double_key_map(const LLSD & sd, const std::string & key1, const std::string & key2) -{ - return sd.isMap() && 2 == sd.size() && sd.has(key1) && sd.has(key2); -} - -#if 0 -static bool -is_triple_key_map(const LLSD & sd, const std::string & key1, const std::string & key2, const std::string& key3) -{ - return sd.isMap() && 3 == sd.size() && sd.has(key1) && sd.has(key2) && sd.has(key3); -} -#endif - -static bool -is_no_stats_map(const LLSD & sd) -{ - return is_double_key_map(sd, "duration", "regions"); -} - -static bool -is_single_slot_array(const LLSD & sd, U64 region_handle) -{ - U32 grid_x(0), grid_y(0); - grid_from_region_handle(region_handle, &grid_x, &grid_y); - - return (sd.isArray() && - 1 == sd.size() && - sd[0].has("grid_x") && - sd[0].has("grid_y") && - sd[0]["grid_x"].isInteger() && - sd[0]["grid_y"].isInteger() && - grid_x == sd[0]["grid_x"].asInteger() && - grid_y == sd[0]["grid_y"].asInteger()); -} - -static bool -is_double_slot_array(const LLSD & sd, U64 region_handle1, U64 region_handle2) -{ - U32 grid_x1(0), grid_y1(0); - U32 grid_x2(0), grid_y2(0); - grid_from_region_handle(region_handle1, &grid_x1, &grid_y1); - grid_from_region_handle(region_handle2, &grid_x2, &grid_y2); - - return (sd.isArray() && - 2 == sd.size() && - sd[0].has("grid_x") && - sd[0].has("grid_y") && - sd[0]["grid_x"].isInteger() && - sd[0]["grid_y"].isInteger() && - sd[1].has("grid_x") && - sd[1].has("grid_y") && - sd[1]["grid_x"].isInteger() && - sd[1]["grid_y"].isInteger() && - ((grid_x1 == sd[0]["grid_x"].asInteger() && - grid_y1 == sd[0]["grid_y"].asInteger() && - grid_x2 == sd[1]["grid_x"].asInteger() && - grid_y2 == sd[1]["grid_y"].asInteger()) || - (grid_x1 == sd[1]["grid_x"].asInteger() && - grid_y1 == sd[1]["grid_y"].asInteger() && - grid_x2 == sd[0]["grid_x"].asInteger() && - grid_y2 == sd[0]["grid_y"].asInteger()))); -} - -static LLSD -get_region(const LLSD & sd, U64 region_handle1) -{ - U32 grid_x(0), grid_y(0); - grid_from_region_handle(region_handle1, &grid_x, &grid_y); - - for (LLSD::array_const_iterator it(sd["regions"].beginArray()); - sd["regions"].endArray() != it; - ++it) - { - if ((*it).has("grid_x") && - (*it).has("grid_y") && - (*it)["grid_x"].isInteger() && - (*it)["grid_y"].isInteger() && - (*it)["grid_x"].asInteger() == grid_x && - (*it)["grid_y"].asInteger() == grid_y) - { - return *it; - } - } - return LLSD(); -} - -namespace tut -{ - struct tst_viewerassetstats_index - { - tst_viewerassetstats_index() - { - LLTrace::set_master_thread_recorder(&mThreadRecorder); - } - - ~tst_viewerassetstats_index() - { - LLTrace::set_master_thread_recorder(NULL); - } - - LLTrace::ThreadRecorder mThreadRecorder; - }; - typedef test_group tst_viewerassetstats_index_t; - typedef tst_viewerassetstats_index_t::object tst_viewerassetstats_index_object_t; - tut::tst_viewerassetstats_index_t tut_tst_viewerassetstats_index("tst_viewerassetstats_test"); - - // Testing free functions without global stats allocated - template<> template<> - void tst_viewerassetstats_index_object_t::test<1>() - { - // Check that helpers aren't bothered by missing global stats - ensure("Global gViewerAssetStats should be NULL", (NULL == gViewerAssetStats)); - - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_TEXTURE, false, false); - - LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_TEXTURE, false, false); - - LLViewerAssetStatsFF::record_response(LLViewerAssetType::AT_GESTURE, false, false, (U64Microseconds)12300000ULL); - } - - // Create a non-global instance and check the structure - template<> template<> - void tst_viewerassetstats_index_object_t::test<2>() - { - ensure("Global gViewerAssetStats should be NULL", (NULL == gViewerAssetStats)); - - LLViewerAssetStats * it = new LLViewerAssetStats(); - - ensure("Global gViewerAssetStats should still be NULL", (NULL == gViewerAssetStats)); - - LLSD sd_full = it->asLLSD(false); - - // Default (NULL) region ID doesn't produce LLSD results so should - // get an empty map back from output - ensure("Stat-less LLSD initially", is_no_stats_map(sd_full)); - - // Once the region is set, we will get a response even with no data collection - it->setRegion(region1_handle); - sd_full = it->asLLSD(false); - ensure("Correct single-key LLSD map root", is_double_key_map(sd_full, "duration", "regions")); - ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd_full["regions"], region1_handle)); - - LLSD sd = sd_full["regions"][0]; - - delete it; - - // Check the structure of the LLSD - for (int i = 0; i < LL_ARRAY_SIZE(all_keys); ++i) - { - std::string line = llformat("Has '%s' key", all_keys[i]); - ensure(line, sd.has(all_keys[i])); - } - - for (int i = 0; i < LL_ARRAY_SIZE(resp_keys); ++i) - { - for (int j = 0; j < LL_ARRAY_SIZE(sub_keys); ++j) - { - std::string line = llformat("Key '%s' has '%s' key", resp_keys[i], sub_keys[j]); - ensure(line, sd[resp_keys[i]].has(sub_keys[j])); - } - } - - for (int i = 0; i < LL_ARRAY_SIZE(mmm_resp_keys); ++i) - { - for (int j = 0; j < LL_ARRAY_SIZE(mmm_sub_keys); ++j) - { - std::string line = llformat("Key '%s' has '%s' key", mmm_resp_keys[i], mmm_sub_keys[j]); - ensure(line, sd[mmm_resp_keys[i]].has(mmm_sub_keys[j])); - } - } - } - - // Create a non-global instance and check some content - template<> template<> - void tst_viewerassetstats_index_object_t::test<3>() - { - LLViewerAssetStats * it = new LLViewerAssetStats(); - it->setRegion(region1_handle); - - LLSD sd = it->asLLSD(false); - ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration")); - ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd["regions"], region1_handle)); - sd = sd[0]; - - delete it; - - // Check a few points on the tree for content - ensure("sd[get_texture_temp_http][dequeued] is 0", (0 == sd["get_texture_temp_http"]["dequeued"].asInteger())); - ensure("sd[get_sound_udp][resp_min] is 0", (0.0 == sd["get_sound_udp"]["resp_min"].asReal())); - } - - // Create a global instance and verify free functions do something useful - template<> template<> - void tst_viewerassetstats_index_object_t::test<4>() - { - gViewerAssetStats = new LLViewerAssetStats(); - LLViewerAssetStatsFF::set_region(region1_handle); - - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_TEXTURE, false, false); - LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_TEXTURE, false, false); - - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_BODYPART, false, false); - LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_BODYPART, false, false); - - LLSD sd = gViewerAssetStats->asLLSD(false); - ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration")); - ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd["regions"], region1_handle)); - sd = sd["regions"][0]; - - // Check a few points on the tree for content - ensure("sd[get_texture_non_temp_udp][enqueued] is 1", (1 == sd["get_texture_non_temp_udp"]["enqueued"].asInteger())); - ensure("sd[get_texture_temp_udp][enqueued] is 0", (0 == sd["get_texture_temp_udp"]["enqueued"].asInteger())); - ensure("sd[get_texture_non_temp_http][enqueued] is 0", (0 == sd["get_texture_non_temp_http"]["enqueued"].asInteger())); - ensure("sd[get_texture_temp_http][enqueued] is 0", (0 == sd["get_texture_temp_http"]["enqueued"].asInteger())); - ensure("sd[get_gesture_udp][dequeued] is 0", (0 == sd["get_gesture_udp"]["dequeued"].asInteger())); - - // Reset and check zeros... - // Reset leaves current region in place - gViewerAssetStats->reset(); - sd = gViewerAssetStats->asLLSD(false)["regions"][region1_handle_str]; - - delete gViewerAssetStats; - gViewerAssetStats = NULL; - - ensure("sd[get_texture_non_temp_udp][enqueued] is reset", (0 == sd["get_texture_non_temp_udp"]["enqueued"].asInteger())); - ensure("sd[get_gesture_udp][dequeued] is reset", (0 == sd["get_gesture_udp"]["dequeued"].asInteger())); - } - - // Check multiple region collection - template<> template<> - void tst_viewerassetstats_index_object_t::test<5>() - { - gViewerAssetStats = new LLViewerAssetStats(); - - LLViewerAssetStatsFF::set_region(region1_handle); - - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_TEXTURE, false, false); - LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_TEXTURE, false, false); - - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_BODYPART, false, false); - LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_BODYPART, false, false); - - LLViewerAssetStatsFF::set_region(region2_handle); - - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_GESTURE, false, false); - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_GESTURE, false, false); - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_GESTURE, false, false); - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_GESTURE, false, false); - - LLSD sd = gViewerAssetStats->asLLSD(false); - - // std::cout << sd << std::endl; - - ensure("Correct double-key LLSD map root", is_double_key_map(sd, "duration", "regions")); - ensure("Correct double-slot LLSD array regions", is_double_slot_array(sd["regions"], region1_handle, region2_handle)); - LLSD sd1 = get_region(sd, region1_handle); - LLSD sd2 = get_region(sd, region2_handle); - ensure("Region1 is present in results", sd1.isMap()); - ensure("Region2 is present in results", sd2.isMap()); - - // Check a few points on the tree for content - ensure_equals("sd1[get_texture_non_temp_udp][enqueued] is 1", sd1["get_texture_non_temp_udp"]["enqueued"].asInteger(), 1); - ensure_equals("sd1[get_texture_temp_udp][enqueued] is 0", sd1["get_texture_temp_udp"]["enqueued"].asInteger(), 0); - ensure_equals("sd1[get_texture_non_temp_http][enqueued] is 0", sd1["get_texture_non_temp_http"]["enqueued"].asInteger(), 0); - ensure_equals("sd1[get_texture_temp_http][enqueued] is 0", sd1["get_texture_temp_http"]["enqueued"].asInteger(), 0); - ensure_equals("sd1[get_gesture_udp][dequeued] is 0", sd1["get_gesture_udp"]["dequeued"].asInteger(), 0); - - // Check a few points on the tree for content - ensure("sd2[get_gesture_udp][enqueued] is 4", (4 == sd2["get_gesture_udp"]["enqueued"].asInteger())); - ensure("sd2[get_gesture_udp][dequeued] is 0", (0 == sd2["get_gesture_udp"]["dequeued"].asInteger())); - ensure("sd2[get_texture_non_temp_udp][enqueued] is 0", (0 == sd2["get_texture_non_temp_udp"]["enqueued"].asInteger())); - - // Reset and check zeros... - // Reset leaves current region in place - gViewerAssetStats->reset(); - sd = gViewerAssetStats->asLLSD(false); - ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration")); - ensure("Correct single-slot LLSD array regions (p2)", is_single_slot_array(sd["regions"], region2_handle)); - sd2 = sd["regions"][0]; - - delete gViewerAssetStats; - gViewerAssetStats = NULL; - - ensure("sd2[get_texture_non_temp_udp][enqueued] is reset", (0 == sd2["get_texture_non_temp_udp"]["enqueued"].asInteger())); - ensure("sd2[get_gesture_udp][enqueued] is reset", (0 == sd2["get_gesture_udp"]["enqueued"].asInteger())); - } - - // Check multiple region collection jumping back-and-forth between regions - template<> template<> - void tst_viewerassetstats_index_object_t::test<6>() - { - gViewerAssetStats = new LLViewerAssetStats(); - - LLViewerAssetStatsFF::set_region(region1_handle); - - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_TEXTURE, false, false); - LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_TEXTURE, false, false); - - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_BODYPART, false, false); - LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_BODYPART, false, false); - - LLViewerAssetStatsFF::set_region(region2_handle); - - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_GESTURE, false, false); - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_GESTURE, false, false); - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_GESTURE, false, false); - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_GESTURE, false, false); - - LLViewerAssetStatsFF::set_region(region1_handle); - - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_TEXTURE, true, true); - LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_TEXTURE, true, true); - - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_BODYPART, false, false); - LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_BODYPART, false, false); - - LLViewerAssetStatsFF::set_region(region2_handle); - - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_GESTURE, false, false); - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_GESTURE, false, false); - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_GESTURE, false, false); - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_GESTURE, false, false); - - LLSD sd = gViewerAssetStats->asLLSD(false); - - ensure("Correct double-key LLSD map root", is_double_key_map(sd, "duration", "regions")); - ensure("Correct double-slot LLSD array regions", is_double_slot_array(sd["regions"], region1_handle, region2_handle)); - LLSD sd1 = get_region(sd, region1_handle); - LLSD sd2 = get_region(sd, region2_handle); - ensure("Region1 is present in results", sd1.isMap()); - ensure("Region2 is present in results", sd2.isMap()); - - // Check a few points on the tree for content - ensure("sd1[get_texture_non_temp_udp][enqueued] is 1", (1 == sd1["get_texture_non_temp_udp"]["enqueued"].asInteger())); - ensure("sd1[get_texture_temp_udp][enqueued] is 0", (0 == sd1["get_texture_temp_udp"]["enqueued"].asInteger())); - ensure("sd1[get_texture_non_temp_http][enqueued] is 0", (0 == sd1["get_texture_non_temp_http"]["enqueued"].asInteger())); - ensure("sd1[get_texture_temp_http][enqueued] is 1", (1 == sd1["get_texture_temp_http"]["enqueued"].asInteger())); - ensure("sd1[get_gesture_udp][dequeued] is 0", (0 == sd1["get_gesture_udp"]["dequeued"].asInteger())); - - // Check a few points on the tree for content - ensure("sd2[get_gesture_udp][enqueued] is 8", (8 == sd2["get_gesture_udp"]["enqueued"].asInteger())); - ensure("sd2[get_gesture_udp][dequeued] is 0", (0 == sd2["get_gesture_udp"]["dequeued"].asInteger())); - ensure("sd2[get_texture_non_temp_udp][enqueued] is 0", (0 == sd2["get_texture_non_temp_udp"]["enqueued"].asInteger())); - - // Reset and check zeros... - // Reset leaves current region in place - gViewerAssetStats->reset(); - sd = gViewerAssetStats->asLLSD(false); - ensure("Correct single-key LLSD map root", is_double_key_map(sd, "duration", "regions")); - ensure("Correct single-slot LLSD array regions (p2)", is_single_slot_array(sd["regions"], region2_handle)); - sd2 = get_region(sd, region2_handle); - ensure("Region2 is present in results", sd2.isMap()); - - delete gViewerAssetStats; - gViewerAssetStats = NULL; - - ensure_equals("sd2[get_texture_non_temp_udp][enqueued] is reset", sd2["get_texture_non_temp_udp"]["enqueued"].asInteger(), 0); - ensure_equals("sd2[get_gesture_udp][enqueued] is reset", sd2["get_gesture_udp"]["enqueued"].asInteger(), 0); - } - - // Non-texture assets ignore transport and persistence flags - template<> template<> - void tst_viewerassetstats_index_object_t::test<7>() - { - gViewerAssetStats = new LLViewerAssetStats(); - LLViewerAssetStatsFF::set_region(region1_handle); - - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_TEXTURE, false, false); - LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_TEXTURE, false, false); - - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_BODYPART, false, false); - LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_BODYPART, false, false); - - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_BODYPART, false, true); - LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_BODYPART, false, true); - - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_BODYPART, true, false); - LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_BODYPART, true, false); - - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_BODYPART, true, true); - LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_BODYPART, true, true); - - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_LSL_BYTECODE, false, false); - - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_LSL_BYTECODE, false, true); - - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_LSL_BYTECODE, true, false); - - LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_LSL_BYTECODE, true, true); - - LLSD sd = gViewerAssetStats->asLLSD(false); - ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration")); - ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd["regions"], region1_handle)); - sd = get_region(sd, region1_handle); - ensure("Region1 is present in results", sd.isMap()); - - // Check a few points on the tree for content - ensure("sd[get_gesture_udp][enqueued] is 0", (0 == sd["get_gesture_udp"]["enqueued"].asInteger())); - ensure("sd[get_gesture_udp][dequeued] is 0", (0 == sd["get_gesture_udp"]["dequeued"].asInteger())); - - ensure("sd[get_wearable_http][enqueued] is 2", (2 == sd["get_wearable_http"]["enqueued"].asInteger())); - ensure("sd[get_wearable_http][dequeued] is 2", (2 == sd["get_wearable_http"]["dequeued"].asInteger())); - - ensure("sd[get_wearable_udp][enqueued] is 2", (2 == sd["get_wearable_udp"]["enqueued"].asInteger())); - ensure("sd[get_wearable_udp][dequeued] is 2", (2 == sd["get_wearable_udp"]["dequeued"].asInteger())); - - ensure("sd[get_other_http][enqueued] is 2", (2 == sd["get_other_http"]["enqueued"].asInteger())); - ensure("sd[get_other_http][dequeued] is 0", (0 == sd["get_other_http"]["dequeued"].asInteger())); - - ensure("sd[get_other_udp][enqueued] is 2", (2 == sd["get_other_udp"]["enqueued"].asInteger())); - ensure("sd[get_other_udp][dequeued] is 0", (0 == sd["get_other_udp"]["dequeued"].asInteger())); - - // Reset and check zeros... - // Reset leaves current region in place - gViewerAssetStats->reset(); - sd = get_region(gViewerAssetStats->asLLSD(false), region1_handle); - ensure("Region1 is present in results", sd.isMap()); - - delete gViewerAssetStats; - gViewerAssetStats = NULL; - - ensure_equals("sd[get_texture_non_temp_udp][enqueued] is reset", sd["get_texture_non_temp_udp"]["enqueued"].asInteger(), 0); - ensure_equals("sd[get_gesture_udp][dequeued] is reset", sd["get_gesture_udp"]["dequeued"].asInteger(), 0); - } -} +/** + * @file llviewerassetstats_tut.cpp + * @date 2010-10-28 + * @brief Test cases for some of newview/llviewerassetstats.cpp + * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include +#include + +#include "lltut.h" +#include "../llviewerassetstats.h" +#include "lluuid.h" +#include "llsdutil.h" +#include "llregionhandle.h" +#include "lltracethreadrecorder.h" +#include "../llvoavatar.h" + +namespace LLStatViewer +{ + LLTrace::SampleStatHandle<> FPS_SAMPLE("fpssample"); +} + +void LLVOAvatar::getNearbyRezzedStats(std::vector& counts, F32& avg_cloud_time, S32& cloud_avatars) +{ + counts.resize(3); + counts[0] = 0; + counts[1] = 0; + counts[2] = 1; +} + +// static +std::string LLVOAvatar::rezStatusToString(S32 rez_status) +{ + if (rez_status==0) return "cloud"; + if (rez_status==1) return "gray"; + if (rez_status==2) return "textured"; + return "unknown"; +} + +// static +LLViewerStats::StatsAccumulator& LLViewerStats::PhaseMap::getPhaseStats(const std::string& phase_name) +{ + static LLViewerStats::StatsAccumulator junk; + return junk; +} + +static const char * all_keys[] = +{ + "duration", + "fps", + "get_other_http", + "get_other_udp", + "get_texture_temp_http", + "get_texture_temp_udp", + "get_texture_non_temp_http", + "get_texture_non_temp_udp", + "get_wearable_http", + "get_wearable_udp", + "get_sound_http", + "get_sound_udp", + "get_gesture_http", + "get_gesture_udp" +}; + +static const char * resp_keys[] = +{ + "get_other_http", + "get_other_udp", + "get_texture_temp_http", + "get_texture_temp_udp", + "get_texture_non_temp_http", + "get_texture_non_temp_udp", + "get_wearable_http", + "get_wearable_udp", + "get_sound_http", + "get_sound_udp", + "get_gesture_http", + "get_gesture_udp" +}; + +static const char * sub_keys[] = +{ + "dequeued", + "enqueued", + "resp_count", + "resp_max", + "resp_min", + "resp_mean" +}; + +static const char * mmm_resp_keys[] = +{ + "fps" +}; + +static const char * mmm_sub_keys[] = +{ + "count", + "max", + "min", + "mean" +}; + +static const LLUUID region1("4e2d81a3-6263-6ffe-ad5c-8ce04bee07e8"); +static const LLUUID region2("68762cc8-b68b-4e45-854b-e830734f2d4a"); +static const U64 region1_handle(0x0000040000003f00ULL); +static const U64 region2_handle(0x0000030000004200ULL); +static const std::string region1_handle_str("0000040000003f00"); +static const std::string region2_handle_str("0000030000004200"); + +#if 0 +static bool +is_empty_map(const LLSD & sd) +{ + return sd.isMap() && 0 == sd.size(); +} +#endif + +#if 0 +static bool +is_single_key_map(const LLSD & sd, const std::string & key) +{ + return sd.isMap() && 1 == sd.size() && sd.has(key); +} +#endif + +static bool +is_double_key_map(const LLSD & sd, const std::string & key1, const std::string & key2) +{ + return sd.isMap() && 2 == sd.size() && sd.has(key1) && sd.has(key2); +} + +#if 0 +static bool +is_triple_key_map(const LLSD & sd, const std::string & key1, const std::string & key2, const std::string& key3) +{ + return sd.isMap() && 3 == sd.size() && sd.has(key1) && sd.has(key2) && sd.has(key3); +} +#endif + +static bool +is_no_stats_map(const LLSD & sd) +{ + return is_double_key_map(sd, "duration", "regions"); +} + +static bool +is_single_slot_array(const LLSD & sd, U64 region_handle) +{ + U32 grid_x(0), grid_y(0); + grid_from_region_handle(region_handle, &grid_x, &grid_y); + + return (sd.isArray() && + 1 == sd.size() && + sd[0].has("grid_x") && + sd[0].has("grid_y") && + sd[0]["grid_x"].isInteger() && + sd[0]["grid_y"].isInteger() && + grid_x == sd[0]["grid_x"].asInteger() && + grid_y == sd[0]["grid_y"].asInteger()); +} + +static bool +is_double_slot_array(const LLSD & sd, U64 region_handle1, U64 region_handle2) +{ + U32 grid_x1(0), grid_y1(0); + U32 grid_x2(0), grid_y2(0); + grid_from_region_handle(region_handle1, &grid_x1, &grid_y1); + grid_from_region_handle(region_handle2, &grid_x2, &grid_y2); + + return (sd.isArray() && + 2 == sd.size() && + sd[0].has("grid_x") && + sd[0].has("grid_y") && + sd[0]["grid_x"].isInteger() && + sd[0]["grid_y"].isInteger() && + sd[1].has("grid_x") && + sd[1].has("grid_y") && + sd[1]["grid_x"].isInteger() && + sd[1]["grid_y"].isInteger() && + ((grid_x1 == sd[0]["grid_x"].asInteger() && + grid_y1 == sd[0]["grid_y"].asInteger() && + grid_x2 == sd[1]["grid_x"].asInteger() && + grid_y2 == sd[1]["grid_y"].asInteger()) || + (grid_x1 == sd[1]["grid_x"].asInteger() && + grid_y1 == sd[1]["grid_y"].asInteger() && + grid_x2 == sd[0]["grid_x"].asInteger() && + grid_y2 == sd[0]["grid_y"].asInteger()))); +} + +static LLSD +get_region(const LLSD & sd, U64 region_handle1) +{ + U32 grid_x(0), grid_y(0); + grid_from_region_handle(region_handle1, &grid_x, &grid_y); + + for (LLSD::array_const_iterator it(sd["regions"].beginArray()); + sd["regions"].endArray() != it; + ++it) + { + if ((*it).has("grid_x") && + (*it).has("grid_y") && + (*it)["grid_x"].isInteger() && + (*it)["grid_y"].isInteger() && + (*it)["grid_x"].asInteger() == grid_x && + (*it)["grid_y"].asInteger() == grid_y) + { + return *it; + } + } + return LLSD(); +} + +namespace tut +{ + struct tst_viewerassetstats_index + { + tst_viewerassetstats_index() + { + LLTrace::set_master_thread_recorder(&mThreadRecorder); + } + + ~tst_viewerassetstats_index() + { + LLTrace::set_master_thread_recorder(NULL); + } + + LLTrace::ThreadRecorder mThreadRecorder; + }; + typedef test_group tst_viewerassetstats_index_t; + typedef tst_viewerassetstats_index_t::object tst_viewerassetstats_index_object_t; + tut::tst_viewerassetstats_index_t tut_tst_viewerassetstats_index("tst_viewerassetstats_test"); + + // Testing free functions without global stats allocated + template<> template<> + void tst_viewerassetstats_index_object_t::test<1>() + { + // Check that helpers aren't bothered by missing global stats + ensure("Global gViewerAssetStats should be NULL", (NULL == gViewerAssetStats)); + + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_TEXTURE, false, false); + + LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_TEXTURE, false, false); + + LLViewerAssetStatsFF::record_response(LLViewerAssetType::AT_GESTURE, false, false, (U64Microseconds)12300000ULL); + } + + // Create a non-global instance and check the structure + template<> template<> + void tst_viewerassetstats_index_object_t::test<2>() + { + ensure("Global gViewerAssetStats should be NULL", (NULL == gViewerAssetStats)); + + LLViewerAssetStats * it = new LLViewerAssetStats(); + + ensure("Global gViewerAssetStats should still be NULL", (NULL == gViewerAssetStats)); + + LLSD sd_full = it->asLLSD(false); + + // Default (NULL) region ID doesn't produce LLSD results so should + // get an empty map back from output + ensure("Stat-less LLSD initially", is_no_stats_map(sd_full)); + + // Once the region is set, we will get a response even with no data collection + it->setRegion(region1_handle); + sd_full = it->asLLSD(false); + ensure("Correct single-key LLSD map root", is_double_key_map(sd_full, "duration", "regions")); + ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd_full["regions"], region1_handle)); + + LLSD sd = sd_full["regions"][0]; + + delete it; + + // Check the structure of the LLSD + for (int i = 0; i < LL_ARRAY_SIZE(all_keys); ++i) + { + std::string line = llformat("Has '%s' key", all_keys[i]); + ensure(line, sd.has(all_keys[i])); + } + + for (int i = 0; i < LL_ARRAY_SIZE(resp_keys); ++i) + { + for (int j = 0; j < LL_ARRAY_SIZE(sub_keys); ++j) + { + std::string line = llformat("Key '%s' has '%s' key", resp_keys[i], sub_keys[j]); + ensure(line, sd[resp_keys[i]].has(sub_keys[j])); + } + } + + for (int i = 0; i < LL_ARRAY_SIZE(mmm_resp_keys); ++i) + { + for (int j = 0; j < LL_ARRAY_SIZE(mmm_sub_keys); ++j) + { + std::string line = llformat("Key '%s' has '%s' key", mmm_resp_keys[i], mmm_sub_keys[j]); + ensure(line, sd[mmm_resp_keys[i]].has(mmm_sub_keys[j])); + } + } + } + + // Create a non-global instance and check some content + template<> template<> + void tst_viewerassetstats_index_object_t::test<3>() + { + LLViewerAssetStats * it = new LLViewerAssetStats(); + it->setRegion(region1_handle); + + LLSD sd = it->asLLSD(false); + ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration")); + ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd["regions"], region1_handle)); + sd = sd[0]; + + delete it; + + // Check a few points on the tree for content + ensure("sd[get_texture_temp_http][dequeued] is 0", (0 == sd["get_texture_temp_http"]["dequeued"].asInteger())); + ensure("sd[get_sound_udp][resp_min] is 0", (0.0 == sd["get_sound_udp"]["resp_min"].asReal())); + } + + // Create a global instance and verify free functions do something useful + template<> template<> + void tst_viewerassetstats_index_object_t::test<4>() + { + gViewerAssetStats = new LLViewerAssetStats(); + LLViewerAssetStatsFF::set_region(region1_handle); + + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_TEXTURE, false, false); + LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_TEXTURE, false, false); + + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_BODYPART, false, false); + LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_BODYPART, false, false); + + LLSD sd = gViewerAssetStats->asLLSD(false); + ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration")); + ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd["regions"], region1_handle)); + sd = sd["regions"][0]; + + // Check a few points on the tree for content + ensure("sd[get_texture_non_temp_udp][enqueued] is 1", (1 == sd["get_texture_non_temp_udp"]["enqueued"].asInteger())); + ensure("sd[get_texture_temp_udp][enqueued] is 0", (0 == sd["get_texture_temp_udp"]["enqueued"].asInteger())); + ensure("sd[get_texture_non_temp_http][enqueued] is 0", (0 == sd["get_texture_non_temp_http"]["enqueued"].asInteger())); + ensure("sd[get_texture_temp_http][enqueued] is 0", (0 == sd["get_texture_temp_http"]["enqueued"].asInteger())); + ensure("sd[get_gesture_udp][dequeued] is 0", (0 == sd["get_gesture_udp"]["dequeued"].asInteger())); + + // Reset and check zeros... + // Reset leaves current region in place + gViewerAssetStats->reset(); + sd = gViewerAssetStats->asLLSD(false)["regions"][region1_handle_str]; + + delete gViewerAssetStats; + gViewerAssetStats = NULL; + + ensure("sd[get_texture_non_temp_udp][enqueued] is reset", (0 == sd["get_texture_non_temp_udp"]["enqueued"].asInteger())); + ensure("sd[get_gesture_udp][dequeued] is reset", (0 == sd["get_gesture_udp"]["dequeued"].asInteger())); + } + + // Check multiple region collection + template<> template<> + void tst_viewerassetstats_index_object_t::test<5>() + { + gViewerAssetStats = new LLViewerAssetStats(); + + LLViewerAssetStatsFF::set_region(region1_handle); + + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_TEXTURE, false, false); + LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_TEXTURE, false, false); + + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_BODYPART, false, false); + LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_BODYPART, false, false); + + LLViewerAssetStatsFF::set_region(region2_handle); + + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_GESTURE, false, false); + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_GESTURE, false, false); + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_GESTURE, false, false); + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_GESTURE, false, false); + + LLSD sd = gViewerAssetStats->asLLSD(false); + + // std::cout << sd << std::endl; + + ensure("Correct double-key LLSD map root", is_double_key_map(sd, "duration", "regions")); + ensure("Correct double-slot LLSD array regions", is_double_slot_array(sd["regions"], region1_handle, region2_handle)); + LLSD sd1 = get_region(sd, region1_handle); + LLSD sd2 = get_region(sd, region2_handle); + ensure("Region1 is present in results", sd1.isMap()); + ensure("Region2 is present in results", sd2.isMap()); + + // Check a few points on the tree for content + ensure_equals("sd1[get_texture_non_temp_udp][enqueued] is 1", sd1["get_texture_non_temp_udp"]["enqueued"].asInteger(), 1); + ensure_equals("sd1[get_texture_temp_udp][enqueued] is 0", sd1["get_texture_temp_udp"]["enqueued"].asInteger(), 0); + ensure_equals("sd1[get_texture_non_temp_http][enqueued] is 0", sd1["get_texture_non_temp_http"]["enqueued"].asInteger(), 0); + ensure_equals("sd1[get_texture_temp_http][enqueued] is 0", sd1["get_texture_temp_http"]["enqueued"].asInteger(), 0); + ensure_equals("sd1[get_gesture_udp][dequeued] is 0", sd1["get_gesture_udp"]["dequeued"].asInteger(), 0); + + // Check a few points on the tree for content + ensure("sd2[get_gesture_udp][enqueued] is 4", (4 == sd2["get_gesture_udp"]["enqueued"].asInteger())); + ensure("sd2[get_gesture_udp][dequeued] is 0", (0 == sd2["get_gesture_udp"]["dequeued"].asInteger())); + ensure("sd2[get_texture_non_temp_udp][enqueued] is 0", (0 == sd2["get_texture_non_temp_udp"]["enqueued"].asInteger())); + + // Reset and check zeros... + // Reset leaves current region in place + gViewerAssetStats->reset(); + sd = gViewerAssetStats->asLLSD(false); + ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration")); + ensure("Correct single-slot LLSD array regions (p2)", is_single_slot_array(sd["regions"], region2_handle)); + sd2 = sd["regions"][0]; + + delete gViewerAssetStats; + gViewerAssetStats = NULL; + + ensure("sd2[get_texture_non_temp_udp][enqueued] is reset", (0 == sd2["get_texture_non_temp_udp"]["enqueued"].asInteger())); + ensure("sd2[get_gesture_udp][enqueued] is reset", (0 == sd2["get_gesture_udp"]["enqueued"].asInteger())); + } + + // Check multiple region collection jumping back-and-forth between regions + template<> template<> + void tst_viewerassetstats_index_object_t::test<6>() + { + gViewerAssetStats = new LLViewerAssetStats(); + + LLViewerAssetStatsFF::set_region(region1_handle); + + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_TEXTURE, false, false); + LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_TEXTURE, false, false); + + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_BODYPART, false, false); + LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_BODYPART, false, false); + + LLViewerAssetStatsFF::set_region(region2_handle); + + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_GESTURE, false, false); + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_GESTURE, false, false); + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_GESTURE, false, false); + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_GESTURE, false, false); + + LLViewerAssetStatsFF::set_region(region1_handle); + + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_TEXTURE, true, true); + LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_TEXTURE, true, true); + + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_BODYPART, false, false); + LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_BODYPART, false, false); + + LLViewerAssetStatsFF::set_region(region2_handle); + + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_GESTURE, false, false); + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_GESTURE, false, false); + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_GESTURE, false, false); + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_GESTURE, false, false); + + LLSD sd = gViewerAssetStats->asLLSD(false); + + ensure("Correct double-key LLSD map root", is_double_key_map(sd, "duration", "regions")); + ensure("Correct double-slot LLSD array regions", is_double_slot_array(sd["regions"], region1_handle, region2_handle)); + LLSD sd1 = get_region(sd, region1_handle); + LLSD sd2 = get_region(sd, region2_handle); + ensure("Region1 is present in results", sd1.isMap()); + ensure("Region2 is present in results", sd2.isMap()); + + // Check a few points on the tree for content + ensure("sd1[get_texture_non_temp_udp][enqueued] is 1", (1 == sd1["get_texture_non_temp_udp"]["enqueued"].asInteger())); + ensure("sd1[get_texture_temp_udp][enqueued] is 0", (0 == sd1["get_texture_temp_udp"]["enqueued"].asInteger())); + ensure("sd1[get_texture_non_temp_http][enqueued] is 0", (0 == sd1["get_texture_non_temp_http"]["enqueued"].asInteger())); + ensure("sd1[get_texture_temp_http][enqueued] is 1", (1 == sd1["get_texture_temp_http"]["enqueued"].asInteger())); + ensure("sd1[get_gesture_udp][dequeued] is 0", (0 == sd1["get_gesture_udp"]["dequeued"].asInteger())); + + // Check a few points on the tree for content + ensure("sd2[get_gesture_udp][enqueued] is 8", (8 == sd2["get_gesture_udp"]["enqueued"].asInteger())); + ensure("sd2[get_gesture_udp][dequeued] is 0", (0 == sd2["get_gesture_udp"]["dequeued"].asInteger())); + ensure("sd2[get_texture_non_temp_udp][enqueued] is 0", (0 == sd2["get_texture_non_temp_udp"]["enqueued"].asInteger())); + + // Reset and check zeros... + // Reset leaves current region in place + gViewerAssetStats->reset(); + sd = gViewerAssetStats->asLLSD(false); + ensure("Correct single-key LLSD map root", is_double_key_map(sd, "duration", "regions")); + ensure("Correct single-slot LLSD array regions (p2)", is_single_slot_array(sd["regions"], region2_handle)); + sd2 = get_region(sd, region2_handle); + ensure("Region2 is present in results", sd2.isMap()); + + delete gViewerAssetStats; + gViewerAssetStats = NULL; + + ensure_equals("sd2[get_texture_non_temp_udp][enqueued] is reset", sd2["get_texture_non_temp_udp"]["enqueued"].asInteger(), 0); + ensure_equals("sd2[get_gesture_udp][enqueued] is reset", sd2["get_gesture_udp"]["enqueued"].asInteger(), 0); + } + + // Non-texture assets ignore transport and persistence flags + template<> template<> + void tst_viewerassetstats_index_object_t::test<7>() + { + gViewerAssetStats = new LLViewerAssetStats(); + LLViewerAssetStatsFF::set_region(region1_handle); + + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_TEXTURE, false, false); + LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_TEXTURE, false, false); + + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_BODYPART, false, false); + LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_BODYPART, false, false); + + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_BODYPART, false, true); + LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_BODYPART, false, true); + + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_BODYPART, true, false); + LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_BODYPART, true, false); + + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_BODYPART, true, true); + LLViewerAssetStatsFF::record_dequeue(LLViewerAssetType::AT_BODYPART, true, true); + + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_LSL_BYTECODE, false, false); + + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_LSL_BYTECODE, false, true); + + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_LSL_BYTECODE, true, false); + + LLViewerAssetStatsFF::record_enqueue(LLViewerAssetType::AT_LSL_BYTECODE, true, true); + + LLSD sd = gViewerAssetStats->asLLSD(false); + ensure("Correct single-key LLSD map root", is_double_key_map(sd, "regions", "duration")); + ensure("Correct single-slot LLSD array regions", is_single_slot_array(sd["regions"], region1_handle)); + sd = get_region(sd, region1_handle); + ensure("Region1 is present in results", sd.isMap()); + + // Check a few points on the tree for content + ensure("sd[get_gesture_udp][enqueued] is 0", (0 == sd["get_gesture_udp"]["enqueued"].asInteger())); + ensure("sd[get_gesture_udp][dequeued] is 0", (0 == sd["get_gesture_udp"]["dequeued"].asInteger())); + + ensure("sd[get_wearable_http][enqueued] is 2", (2 == sd["get_wearable_http"]["enqueued"].asInteger())); + ensure("sd[get_wearable_http][dequeued] is 2", (2 == sd["get_wearable_http"]["dequeued"].asInteger())); + + ensure("sd[get_wearable_udp][enqueued] is 2", (2 == sd["get_wearable_udp"]["enqueued"].asInteger())); + ensure("sd[get_wearable_udp][dequeued] is 2", (2 == sd["get_wearable_udp"]["dequeued"].asInteger())); + + ensure("sd[get_other_http][enqueued] is 2", (2 == sd["get_other_http"]["enqueued"].asInteger())); + ensure("sd[get_other_http][dequeued] is 0", (0 == sd["get_other_http"]["dequeued"].asInteger())); + + ensure("sd[get_other_udp][enqueued] is 2", (2 == sd["get_other_udp"]["enqueued"].asInteger())); + ensure("sd[get_other_udp][dequeued] is 0", (0 == sd["get_other_udp"]["dequeued"].asInteger())); + + // Reset and check zeros... + // Reset leaves current region in place + gViewerAssetStats->reset(); + sd = get_region(gViewerAssetStats->asLLSD(false), region1_handle); + ensure("Region1 is present in results", sd.isMap()); + + delete gViewerAssetStats; + gViewerAssetStats = NULL; + + ensure_equals("sd[get_texture_non_temp_udp][enqueued] is reset", sd["get_texture_non_temp_udp"]["enqueued"].asInteger(), 0); + ensure_equals("sd[get_gesture_udp][dequeued] is reset", sd["get_gesture_udp"]["dequeued"].asInteger(), 0); + } +} diff --git a/indra/newview/tests/llviewerhelputil_test.cpp b/indra/newview/tests/llviewerhelputil_test.cpp index 16b796e6e7..9ee6625bf1 100644 --- a/indra/newview/tests/llviewerhelputil_test.cpp +++ b/indra/newview/tests/llviewerhelputil_test.cpp @@ -1,160 +1,160 @@ -/** - * @file llviewerhelputil_test.cpp - * @brief LLViewerHelpUtil tests - * @author Tofu Linden - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ -// Precompiled header -#include "../llviewerprecompiledheaders.h" - -#include "../test/lltut.h" - -#include "../llviewerhelputil.h" -#include "../llweb.h" -#include "llcontrol.h" - -#include - -// values for all of the supported substitutions parameters -static std::string gHelpURL; -static std::string gVersion; -static std::string gChannel; -static std::string gLanguage; -static std::string gGrid; -static std::string gOS; - -//---------------------------------------------------------------------------- -// Mock objects for the dependencies of the code we're testing - -LLControlGroup::LLControlGroup(const std::string& name) - : LLInstanceTracker(name) {} -LLControlGroup::~LLControlGroup() {} -LLControlVariable* LLControlGroup::declareString(const std::string& name, - const std::string& initial_val, - const std::string& comment, - LLControlVariable::ePersist persist) {return NULL;} -void LLControlGroup::setString(std::string_view name, const std::string& val){} -std::string LLControlGroup::getString(std::string_view name) -{ - if (name == "HelpURLFormat") - return gHelpURL; - return ""; -} -LLControlGroup gSavedSettings("test"); - -static void substitute_string(std::string &input, const std::string &search, const std::string &replace) -{ - size_t pos = input.find(search); - while (pos != std::string::npos) - { - input = input.replace(pos, search.size(), replace); - pos = input.find(search); - } -} - -#include "../llagent.h" -LLAgent::LLAgent() : mAgentAccess(NULL) { } -LLAgent::~LLAgent() { } -bool LLAgent::isGodlike() const { return false; } - -LLAgent gAgent; - -std::string LLWeb::expandURLSubstitutions(const std::string &url, - const LLSD &default_subs) -{ - (void)gAgent.isGodlike(); // ref symbol to stop compiler from stripping it - std::string new_url = url; - substitute_string(new_url, "[TOPIC]", default_subs["TOPIC"].asString()); - substitute_string(new_url, "[VERSION]", gVersion); - substitute_string(new_url, "[CHANNEL]", gChannel); - substitute_string(new_url, "[LANGUAGE]", gLanguage); - substitute_string(new_url, "[GRID]", gGrid); - substitute_string(new_url, "[OS]", gOS); - return new_url; -} - - -//---------------------------------------------------------------------------- - -namespace tut -{ - struct viewerhelputil - { - }; - - typedef test_group viewerhelputil_t; - typedef viewerhelputil_t::object viewerhelputil_object_t; - tut::viewerhelputil_t tut_viewerhelputil("LLViewerHelpUtil"); - - template<> template<> - void viewerhelputil_object_t::test<1>() - { - std::string topic("test_topic"); - std::string subresult; - - gHelpURL = "fooformat"; - subresult = LLViewerHelpUtil::buildHelpURL(topic); - ensure_equals("no substitution tags", subresult, "fooformat"); - - gHelpURL = ""; - subresult = LLViewerHelpUtil::buildHelpURL(topic); - ensure_equals("blank substitution format", subresult, ""); - - gHelpURL = "[TOPIC]"; - subresult = LLViewerHelpUtil::buildHelpURL(topic); - ensure_equals("topic name", subresult, "test_topic"); - - gHelpURL = "[LANGUAGE]"; - gLanguage = ""; - subresult = LLViewerHelpUtil::buildHelpURL(topic); - ensure_equals("simple substitution with blank", subresult, ""); - - gHelpURL = "[LANGUAGE]"; - gLanguage = "Esperanto"; - subresult = LLViewerHelpUtil::buildHelpURL(topic); - ensure_equals("simple substitution", subresult, "Esperanto"); - - gHelpURL = "http://secondlife.com/[LANGUAGE]"; - gLanguage = "Gaelic"; - subresult = LLViewerHelpUtil::buildHelpURL(topic); - ensure_equals("simple substitution with url", subresult, "http://secondlife.com/Gaelic"); - - gHelpURL = "[XXX]"; - subresult = LLViewerHelpUtil::buildHelpURL(topic); - ensure_equals("unknown substitution", subresult, "[XXX]"); - - gHelpURL = "[LANGUAGE]/[LANGUAGE]"; - gLanguage = "Esperanto"; - subresult = LLViewerHelpUtil::buildHelpURL(topic); - ensure_equals("multiple substitution", subresult, "Esperanto/Esperanto"); - - gHelpURL = "http://[CHANNEL]/[VERSION]/[LANGUAGE]/[OS]/[GRID]/[XXX]"; - gChannel = "Second Life Test"; - gVersion = "2.0"; - gLanguage = "gaelic"; - gOS = "AmigaOS 2.1"; - gGrid = "mysim"; - subresult = LLViewerHelpUtil::buildHelpURL(topic); - ensure_equals("complex substitution", subresult, "http://Second Life Test/2.0/gaelic/AmigaOS 2.1/mysim/[XXX]"); - } -} +/** + * @file llviewerhelputil_test.cpp + * @brief LLViewerHelpUtil tests + * @author Tofu Linden + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +// Precompiled header +#include "../llviewerprecompiledheaders.h" + +#include "../test/lltut.h" + +#include "../llviewerhelputil.h" +#include "../llweb.h" +#include "llcontrol.h" + +#include + +// values for all of the supported substitutions parameters +static std::string gHelpURL; +static std::string gVersion; +static std::string gChannel; +static std::string gLanguage; +static std::string gGrid; +static std::string gOS; + +//---------------------------------------------------------------------------- +// Mock objects for the dependencies of the code we're testing + +LLControlGroup::LLControlGroup(const std::string& name) + : LLInstanceTracker(name) {} +LLControlGroup::~LLControlGroup() {} +LLControlVariable* LLControlGroup::declareString(const std::string& name, + const std::string& initial_val, + const std::string& comment, + LLControlVariable::ePersist persist) {return NULL;} +void LLControlGroup::setString(std::string_view name, const std::string& val){} +std::string LLControlGroup::getString(std::string_view name) +{ + if (name == "HelpURLFormat") + return gHelpURL; + return ""; +} +LLControlGroup gSavedSettings("test"); + +static void substitute_string(std::string &input, const std::string &search, const std::string &replace) +{ + size_t pos = input.find(search); + while (pos != std::string::npos) + { + input = input.replace(pos, search.size(), replace); + pos = input.find(search); + } +} + +#include "../llagent.h" +LLAgent::LLAgent() : mAgentAccess(NULL) { } +LLAgent::~LLAgent() { } +bool LLAgent::isGodlike() const { return false; } + +LLAgent gAgent; + +std::string LLWeb::expandURLSubstitutions(const std::string &url, + const LLSD &default_subs) +{ + (void)gAgent.isGodlike(); // ref symbol to stop compiler from stripping it + std::string new_url = url; + substitute_string(new_url, "[TOPIC]", default_subs["TOPIC"].asString()); + substitute_string(new_url, "[VERSION]", gVersion); + substitute_string(new_url, "[CHANNEL]", gChannel); + substitute_string(new_url, "[LANGUAGE]", gLanguage); + substitute_string(new_url, "[GRID]", gGrid); + substitute_string(new_url, "[OS]", gOS); + return new_url; +} + + +//---------------------------------------------------------------------------- + +namespace tut +{ + struct viewerhelputil + { + }; + + typedef test_group viewerhelputil_t; + typedef viewerhelputil_t::object viewerhelputil_object_t; + tut::viewerhelputil_t tut_viewerhelputil("LLViewerHelpUtil"); + + template<> template<> + void viewerhelputil_object_t::test<1>() + { + std::string topic("test_topic"); + std::string subresult; + + gHelpURL = "fooformat"; + subresult = LLViewerHelpUtil::buildHelpURL(topic); + ensure_equals("no substitution tags", subresult, "fooformat"); + + gHelpURL = ""; + subresult = LLViewerHelpUtil::buildHelpURL(topic); + ensure_equals("blank substitution format", subresult, ""); + + gHelpURL = "[TOPIC]"; + subresult = LLViewerHelpUtil::buildHelpURL(topic); + ensure_equals("topic name", subresult, "test_topic"); + + gHelpURL = "[LANGUAGE]"; + gLanguage = ""; + subresult = LLViewerHelpUtil::buildHelpURL(topic); + ensure_equals("simple substitution with blank", subresult, ""); + + gHelpURL = "[LANGUAGE]"; + gLanguage = "Esperanto"; + subresult = LLViewerHelpUtil::buildHelpURL(topic); + ensure_equals("simple substitution", subresult, "Esperanto"); + + gHelpURL = "http://secondlife.com/[LANGUAGE]"; + gLanguage = "Gaelic"; + subresult = LLViewerHelpUtil::buildHelpURL(topic); + ensure_equals("simple substitution with url", subresult, "http://secondlife.com/Gaelic"); + + gHelpURL = "[XXX]"; + subresult = LLViewerHelpUtil::buildHelpURL(topic); + ensure_equals("unknown substitution", subresult, "[XXX]"); + + gHelpURL = "[LANGUAGE]/[LANGUAGE]"; + gLanguage = "Esperanto"; + subresult = LLViewerHelpUtil::buildHelpURL(topic); + ensure_equals("multiple substitution", subresult, "Esperanto/Esperanto"); + + gHelpURL = "http://[CHANNEL]/[VERSION]/[LANGUAGE]/[OS]/[GRID]/[XXX]"; + gChannel = "Second Life Test"; + gVersion = "2.0"; + gLanguage = "gaelic"; + gOS = "AmigaOS 2.1"; + gGrid = "mysim"; + subresult = LLViewerHelpUtil::buildHelpURL(topic); + ensure_equals("complex substitution", subresult, "http://Second Life Test/2.0/gaelic/AmigaOS 2.1/mysim/[XXX]"); + } +} diff --git a/indra/newview/tests/llviewernetwork_test.cpp b/indra/newview/tests/llviewernetwork_test.cpp index ddaa2a40da..40c2059d27 100644 --- a/indra/newview/tests/llviewernetwork_test.cpp +++ b/indra/newview/tests/llviewernetwork_test.cpp @@ -1,450 +1,450 @@ -/** - * @file llviewernetwork_test.cpp - * @author Roxie - * @date 2009-03-9 - * @brief Test the viewernetwork functionality - * - * $LicenseInfo:firstyear=2009&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2014, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ -#include "../llviewerprecompiledheaders.h" -#include "../llviewernetwork.h" -#include "../test/lltut.h" -#include "../../llxml/llcontrol.h" -#include "llfile.h" - -namespace -{ - -// Should not collide with other test programs creating temp files. -static const char * const TEST_FILENAME("llviewernetwork_test.xml"); - -} - -// -// Stub implementation for LLTrans -// -class LLTrans -{ -public: - static std::string getString(const std::string &xml_desc, const LLStringUtil::format_map_t& args, bool def_string = false); -}; - -std::string LLTrans::getString(const std::string &xml_desc, const LLStringUtil::format_map_t& args, bool def_string) -{ - std::string grid_label = std::string(); - if(xml_desc == "AgniGridLabel") - { - grid_label = "Second Life Main Grid (Agni)"; - } - else if(xml_desc == "AditiGridLabel") - { - grid_label = "Second Life Beta Test Grid (Aditi)"; - } - - return grid_label; -} - -//---------------------------------------------------------------------------- -// Mock objects for the dependencies of the code we're testing - -LLControlGroup::LLControlGroup(const std::string& name) -: LLInstanceTracker(name) {} -LLControlGroup::~LLControlGroup() {} -LLControlVariable* LLControlGroup::declareString(const std::string& name, - const std::string& initial_val, - const std::string& comment, - LLControlVariable::ePersist persist) {return NULL;} -void LLControlGroup::setString(std::string_view name, const std::string& val){} - -std::string gCmdLineLoginURI; -std::string gCmdLineGridChoice; -std::string gCmdLineHelperURI; -std::string gLoginPage; -std::string gCurrentGrid; -std::string LLControlGroup::getString(std::string_view name) -{ - if (name == "CmdLineGridChoice") - return gCmdLineGridChoice; - else if (name == "CmdLineHelperURI") - return gCmdLineHelperURI; - else if (name == "LoginPage") - return gLoginPage; - else if (name == "CurrentGrid") - return gCurrentGrid; - return ""; -} - -LLSD LLControlGroup::getLLSD(std::string_view name) -{ - if (name == "CmdLineLoginURI") - { - if(!gCmdLineLoginURI.empty()) - { - return LLSD(gCmdLineLoginURI); - } - } - return LLSD(); -} - -LLPointer LLControlGroup::getControl(std::string_view name) -{ - ctrl_name_table_t::iterator iter = mNameTable.find(name.data()); - return iter == mNameTable.end() ? LLPointer() : iter->second; -} - -LLControlGroup gSavedSettings("test"); - -const char *gSampleGridFile = - "" - "" - " " - " altgrid.long.name" - " " - " helper_urihttps://helper1/helpers/" - " labelAlternative Grid" - " login_pagealtgrid/loginpage" - " login_uri" - " " - " altgrid/myloginuri1" - " altgrid/myloginuri2" - " " - " keynamealtgrid.long.name" - " credential_typeagent" - " grid_login_idAltGrid" - " " - " minimal.long.name" - " " - " keynameminimal.long.name" - " " - " " - " util.agni.lindenlab.com " - " " - " helper_urihttps://helper1/helpers/" - " grid_login_idmylabel" - " labelmylabel" - " login_pageloginpage" - " login_uri" - " " - " myloginuri" - " " - " keynameutil.agni.lindenlab.com " - " " - " util.foobar.lindenlab.com" - " " - " helper_urihttps://helper1/helpers/" - " grid_login_idAditi " - " labelmylabel" - " login_pageloginpage" - " login_uri" - " " - " myloginuri" - " " - " update_query_url_basehttps://update.secondlife.com/update" - " keynameutil.foobar.lindenlab.com" - " " - " " - "" - ; -// ------------------------------------------------------------------------------------------- -// TUT -// ------------------------------------------------------------------------------------------- -namespace tut -{ - // Test wrapper declaration : wrapping nothing for the moment - struct viewerNetworkTest - { - viewerNetworkTest() - { - LLFile::remove(TEST_FILENAME); - gCmdLineLoginURI.clear(); - gCmdLineGridChoice.clear(); - gCmdLineHelperURI.clear(); - gLoginPage.clear(); - gCurrentGrid.clear(); - } - ~viewerNetworkTest() - { - LLFile::remove(TEST_FILENAME); - } - }; - - // Tut templating thingamagic: test group, object and test instance - typedef test_group viewerNetworkTestFactory; - typedef viewerNetworkTestFactory::object viewerNetworkTestObject; - tut::viewerNetworkTestFactory tut_test("LLViewerNetwork"); - - // --------------------------------------------------------------------------------------- - // Test functions - // --------------------------------------------------------------------------------------- - // initialization without a grid file - template<> template<> - void viewerNetworkTestObject::test<1>() - { - LLGridManager *manager = LLGridManager::getInstance(); - // grid file doesn't exist - manager->initialize(TEST_FILENAME); - // validate that some of the defaults are available. - std::map known_grids = manager->getKnownGrids(); - ensure_equals("Known grids is a string-string map of size 2", known_grids.size(), 2); - ensure_equals("Agni has the right name and label", - known_grids[std::string("util.agni.lindenlab.com")], - std::string("Second Life Main Grid (Agni)")); - ensure_equals("Aditi has the right name and label", - known_grids[std::string("util.aditi.lindenlab.com")], - std::string("Second Life Beta Test Grid (Aditi)")); - ensure_equals("name for agni", - LLGridManager::getInstance()->getGrid("util.agni.lindenlab.com"), - std::string("util.agni.lindenlab.com")); - ensure_equals("id for agni", - std::string("Agni"), - LLGridManager::getInstance()->getGridId("util.agni.lindenlab.com")); - ensure_equals("update url base for Agni", // relies on agni being the default - std::string("https://update.secondlife.com/update"), - LLGridManager::getInstance()->getUpdateServiceURL()); - ensure_equals("label for agni", - LLGridManager::getInstance()->getGridLabel("util.agni.lindenlab.com"), - std::string("Second Life Main Grid (Agni)")); - - std::vector login_uris; - LLGridManager::getInstance()->getLoginURIs(std::string("util.agni.lindenlab.com"), login_uris); - ensure_equals("Number of login uris for agni", 1, login_uris.size()); - ensure_equals("Agni login uri", - login_uris[0], - std::string("https://login.agni.lindenlab.com/cgi-bin/login.cgi")); - ensure_equals("Agni helper uri", - LLGridManager::getInstance()->getHelperURI("util.agni.lindenlab.com"), - std::string("https://secondlife.com/helpers/")); - ensure_equals("Agni login page", - LLGridManager::getInstance()->getLoginPage("util.agni.lindenlab.com"), - std::string("https://viewer-splash.secondlife.com/")); - ensure("Agni is a system grid", - LLGridManager::getInstance()->isSystemGrid("util.agni.lindenlab.com")); - - ensure_equals("name for aditi", - LLGridManager::getInstance()->getGrid("util.aditi.lindenlab.com"), - std::string("util.aditi.lindenlab.com")); - ensure_equals("id for aditi", - LLGridManager::getInstance()->getGridId("util.aditi.lindenlab.com"), - std::string("Aditi")); - ensure_equals("label for aditi", - LLGridManager::getInstance()->getGridLabel("util.aditi.lindenlab.com"), - std::string("Second Life Beta Test Grid (Aditi)")); - - LLGridManager::getInstance()->getLoginURIs(std::string("util.aditi.lindenlab.com"), login_uris); - - ensure_equals("Number of login uris for aditi", 1, login_uris.size()); - ensure_equals("Aditi login uri", - login_uris[0], - std::string("https://login.aditi.lindenlab.com/cgi-bin/login.cgi")); - ensure_equals("Aditi helper uri", - LLGridManager::getInstance()->getHelperURI("util.aditi.lindenlab.com"), - std::string("https://secondlife.aditi.lindenlab.com/helpers/")); - ensure_equals("Aditi login page", - LLGridManager::getInstance()->getLoginPage("util.aditi.lindenlab.com"), - std::string("https://viewer-splash.secondlife.com/")); - ensure("Aditi is a system grid", - LLGridManager::getInstance()->isSystemGrid("util.aditi.lindenlab.com")); - } - - // initialization with a grid file - template<> template<> - void viewerNetworkTestObject::test<2>() - { - llofstream gridfile(TEST_FILENAME); - gridfile << gSampleGridFile; - gridfile.close(); - - LLGridManager::getInstance()->initialize(TEST_FILENAME); - std::map known_grids = LLGridManager::getInstance()->getKnownGrids(); - ensure_equals("adding a grid via a grid file increases known grid size",4, - known_grids.size()); - - // Verify that Agni and Aditi were not overwritten - ensure_equals("Agni has the right name and label", - known_grids[std::string("util.agni.lindenlab.com")], - std::string("Second Life Main Grid (Agni)")); - ensure_equals("Aditi has the right name and label", - known_grids[std::string("util.aditi.lindenlab.com")], - std::string("Second Life Beta Test Grid (Aditi)")); - ensure_equals("name for agni", - LLGridManager::getInstance()->getGrid("util.agni.lindenlab.com"), - std::string("util.agni.lindenlab.com")); - ensure_equals("id for agni", - LLGridManager::getInstance()->getGridId("util.agni.lindenlab.com"), - std::string("Agni")); - ensure_equals("update url base for Agni", // relies on agni being the default - std::string("https://update.secondlife.com/update"), - LLGridManager::getInstance()->getUpdateServiceURL()); - ensure_equals("label for agni", - LLGridManager::getInstance()->getGridLabel("util.agni.lindenlab.com"), - std::string("Second Life Main Grid (Agni)")); - std::vector login_uris; - LLGridManager::getInstance()->getLoginURIs(std::string("util.agni.lindenlab.com"), login_uris); - ensure_equals("Number of login uris for agni", 1, login_uris.size()); - ensure_equals("Agni login uri", - login_uris[0], - std::string("https://login.agni.lindenlab.com/cgi-bin/login.cgi")); - ensure_equals("Agni helper uri", - LLGridManager::getInstance()->getHelperURI("util.agni.lindenlab.com"), - std::string("https://secondlife.com/helpers/")); - ensure_equals("Agni login page", - LLGridManager::getInstance()->getLoginPage("util.agni.lindenlab.com"), - std::string("https://viewer-splash.secondlife.com/")); - ensure("Agni is a system grid", - LLGridManager::getInstance()->isSystemGrid("util.agni.lindenlab.com")); - - ensure_equals("name for aditi", - LLGridManager::getInstance()->getGrid("util.aditi.lindenlab.com"), - std::string("util.aditi.lindenlab.com")); - ensure_equals("id for aditi", - LLGridManager::getInstance()->getGridId("util.aditi.lindenlab.com"), - std::string("Aditi")); - ensure_equals("label for aditi", - LLGridManager::getInstance()->getGridLabel("util.aditi.lindenlab.com"), - std::string("Second Life Beta Test Grid (Aditi)")); - - LLGridManager::getInstance()->getLoginURIs(std::string("util.aditi.lindenlab.com"), login_uris); - ensure_equals("Number of login uris for aditi", 1, login_uris.size()); - ensure_equals("Aditi login uri", - login_uris[0], - std::string("https://login.aditi.lindenlab.com/cgi-bin/login.cgi")); - ensure_equals("Aditi helper uri", - LLGridManager::getInstance()->getHelperURI("util.aditi.lindenlab.com"), - std::string("https://secondlife.aditi.lindenlab.com/helpers/")); - ensure_equals("Aditi login page", - LLGridManager::getInstance()->getLoginPage("util.aditi.lindenlab.com"), - std::string("https://viewer-splash.secondlife.com/")); - ensure("Aditi is a system grid", - LLGridManager::getInstance()->isSystemGrid("util.aditi.lindenlab.com")); - - // Check the additional grid from the file - ensure_equals("alternative grid is in name<->label map", - known_grids["altgrid.long.name"], - std::string("Alternative Grid")); - ensure_equals("alternative grid name is set", - LLGridManager::getInstance()->getGrid("altgrid.long.name"), - std::string("altgrid.long.name")); - ensure_equals("alternative grid id", - LLGridManager::getInstance()->getGridId("altgrid.long.name"), - std::string("AltGrid")); - ensure_equals("alternative grid label", - LLGridManager::getInstance()->getGridLabel("altgrid.long.name"), - std::string("Alternative Grid")); - std::vector alt_login_uris; - LLGridManager::getInstance()->getLoginURIs(std::string("altgrid.long.name"), alt_login_uris); - ensure_equals("Number of login uris for altgrid", 2, alt_login_uris.size()); - ensure_equals("alternative grid first login uri", - alt_login_uris[0], - std::string("altgrid/myloginuri1")); - ensure_equals("alternative grid second login uri", - alt_login_uris[1], - std::string("altgrid/myloginuri2")); - ensure_equals("alternative grid helper uri", - LLGridManager::getInstance()->getHelperURI("altgrid.long.name"), - std::string("https://helper1/helpers/")); - ensure_equals("alternative grid login page", - LLGridManager::getInstance()->getLoginPage("altgrid.long.name"), - std::string("altgrid/loginpage")); - ensure("alternative grid is NOT a system grid", - ! LLGridManager::getInstance()->isSystemGrid("altgrid.long.name")); - - ensure_equals("minimal grid is in name<->label map", - known_grids["minimal.long.name"], - std::string("minimal.long.name")); - ensure_equals("minimal grid name is set", - LLGridManager::getInstance()->getGrid("minimal.long.name"), - std::string("minimal.long.name")); - ensure_equals("minimal grid id", - LLGridManager::getInstance()->getGridId("minimal.long.name"), - std::string("minimal.long.name")); - ensure_equals("minimal grid label", - LLGridManager::getInstance()->getGridLabel("minimal.long.name"), - std::string("minimal.long.name")); - - LLGridManager::getInstance()->getLoginURIs(std::string("minimal.long.name"), alt_login_uris); - ensure_equals("Number of login uris for altgrid", 1, alt_login_uris.size()); - ensure_equals("minimal grid login uri", - alt_login_uris[0], - std::string("https://minimal.long.name/cgi-bin/login.cgi")); - ensure_equals("minimal grid helper uri", - LLGridManager::getInstance()->getHelperURI("minimal.long.name"), - std::string("https://minimal.long.name/helpers/")); - ensure_equals("minimal grid login page", - LLGridManager::getInstance()->getLoginPage("minimal.long.name"), - std::string("http://minimal.long.name/app/login/")); - - } - - - // validate grid selection - template<> template<> - void viewerNetworkTestObject::test<7>() - { - // adding a grid with simply a name will populate the values. - llofstream gridfile(TEST_FILENAME); - gridfile << gSampleGridFile; - gridfile.close(); - - LLGridManager::getInstance()->initialize(TEST_FILENAME); - - LLGridManager::getInstance()->setGridChoice("util.agni.lindenlab.com"); - ensure_equals("getGridLabel", - LLGridManager::getInstance()->getGridLabel(), - std::string("Second Life Main Grid (Agni)")); - ensure_equals("getGridId", - LLGridManager::getInstance()->getGridId(), - std::string("Agni")); - ensure_equals("getGrid", - LLGridManager::getInstance()->getGrid(), - std::string("util.agni.lindenlab.com")); - ensure_equals("getHelperURI", - LLGridManager::getInstance()->getHelperURI(), - std::string("https://secondlife.com/helpers/")); - ensure_equals("getLoginPage", - LLGridManager::getInstance()->getLoginPage(), - std::string("https://viewer-splash.secondlife.com/")); - ensure_equals("update url base for Agni", // relies on agni being the default - std::string("https://update.secondlife.com/update"), - LLGridManager::getInstance()->getUpdateServiceURL()); - ensure("Is Agni a production grid", LLGridManager::getInstance()->isInProductionGrid()); - std::vector uris; - LLGridManager::getInstance()->getLoginURIs(uris); - ensure_equals("getLoginURIs size", 1, uris.size()); - ensure_equals("getLoginURIs", - uris[0], - std::string("https://login.agni.lindenlab.com/cgi-bin/login.cgi")); - - LLGridManager::getInstance()->setGridChoice("altgrid.long.name"); - ensure_equals("getGridLabel", - LLGridManager::getInstance()->getGridLabel(), - std::string("Alternative Grid")); - ensure_equals("getGridId", - LLGridManager::getInstance()->getGridId(), - std::string("AltGrid")); - ensure("alternative grid is not a system grid", - !LLGridManager::getInstance()->isSystemGrid()); - ensure("alternative grid is not a production grid", - !LLGridManager::getInstance()->isInProductionGrid()); - } - -} +/** + * @file llviewernetwork_test.cpp + * @author Roxie + * @date 2009-03-9 + * @brief Test the viewernetwork functionality + * + * $LicenseInfo:firstyear=2009&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ +#include "../llviewerprecompiledheaders.h" +#include "../llviewernetwork.h" +#include "../test/lltut.h" +#include "../../llxml/llcontrol.h" +#include "llfile.h" + +namespace +{ + +// Should not collide with other test programs creating temp files. +static const char * const TEST_FILENAME("llviewernetwork_test.xml"); + +} + +// +// Stub implementation for LLTrans +// +class LLTrans +{ +public: + static std::string getString(const std::string &xml_desc, const LLStringUtil::format_map_t& args, bool def_string = false); +}; + +std::string LLTrans::getString(const std::string &xml_desc, const LLStringUtil::format_map_t& args, bool def_string) +{ + std::string grid_label = std::string(); + if(xml_desc == "AgniGridLabel") + { + grid_label = "Second Life Main Grid (Agni)"; + } + else if(xml_desc == "AditiGridLabel") + { + grid_label = "Second Life Beta Test Grid (Aditi)"; + } + + return grid_label; +} + +//---------------------------------------------------------------------------- +// Mock objects for the dependencies of the code we're testing + +LLControlGroup::LLControlGroup(const std::string& name) +: LLInstanceTracker(name) {} +LLControlGroup::~LLControlGroup() {} +LLControlVariable* LLControlGroup::declareString(const std::string& name, + const std::string& initial_val, + const std::string& comment, + LLControlVariable::ePersist persist) {return NULL;} +void LLControlGroup::setString(std::string_view name, const std::string& val){} + +std::string gCmdLineLoginURI; +std::string gCmdLineGridChoice; +std::string gCmdLineHelperURI; +std::string gLoginPage; +std::string gCurrentGrid; +std::string LLControlGroup::getString(std::string_view name) +{ + if (name == "CmdLineGridChoice") + return gCmdLineGridChoice; + else if (name == "CmdLineHelperURI") + return gCmdLineHelperURI; + else if (name == "LoginPage") + return gLoginPage; + else if (name == "CurrentGrid") + return gCurrentGrid; + return ""; +} + +LLSD LLControlGroup::getLLSD(std::string_view name) +{ + if (name == "CmdLineLoginURI") + { + if(!gCmdLineLoginURI.empty()) + { + return LLSD(gCmdLineLoginURI); + } + } + return LLSD(); +} + +LLPointer LLControlGroup::getControl(std::string_view name) +{ + ctrl_name_table_t::iterator iter = mNameTable.find(name.data()); + return iter == mNameTable.end() ? LLPointer() : iter->second; +} + +LLControlGroup gSavedSettings("test"); + +const char *gSampleGridFile = + "" + "" + " " + " altgrid.long.name" + " " + " helper_urihttps://helper1/helpers/" + " labelAlternative Grid" + " login_pagealtgrid/loginpage" + " login_uri" + " " + " altgrid/myloginuri1" + " altgrid/myloginuri2" + " " + " keynamealtgrid.long.name" + " credential_typeagent" + " grid_login_idAltGrid" + " " + " minimal.long.name" + " " + " keynameminimal.long.name" + " " + " " + " util.agni.lindenlab.com " + " " + " helper_urihttps://helper1/helpers/" + " grid_login_idmylabel" + " labelmylabel" + " login_pageloginpage" + " login_uri" + " " + " myloginuri" + " " + " keynameutil.agni.lindenlab.com " + " " + " util.foobar.lindenlab.com" + " " + " helper_urihttps://helper1/helpers/" + " grid_login_idAditi " + " labelmylabel" + " login_pageloginpage" + " login_uri" + " " + " myloginuri" + " " + " update_query_url_basehttps://update.secondlife.com/update" + " keynameutil.foobar.lindenlab.com" + " " + " " + "" + ; +// ------------------------------------------------------------------------------------------- +// TUT +// ------------------------------------------------------------------------------------------- +namespace tut +{ + // Test wrapper declaration : wrapping nothing for the moment + struct viewerNetworkTest + { + viewerNetworkTest() + { + LLFile::remove(TEST_FILENAME); + gCmdLineLoginURI.clear(); + gCmdLineGridChoice.clear(); + gCmdLineHelperURI.clear(); + gLoginPage.clear(); + gCurrentGrid.clear(); + } + ~viewerNetworkTest() + { + LLFile::remove(TEST_FILENAME); + } + }; + + // Tut templating thingamagic: test group, object and test instance + typedef test_group viewerNetworkTestFactory; + typedef viewerNetworkTestFactory::object viewerNetworkTestObject; + tut::viewerNetworkTestFactory tut_test("LLViewerNetwork"); + + // --------------------------------------------------------------------------------------- + // Test functions + // --------------------------------------------------------------------------------------- + // initialization without a grid file + template<> template<> + void viewerNetworkTestObject::test<1>() + { + LLGridManager *manager = LLGridManager::getInstance(); + // grid file doesn't exist + manager->initialize(TEST_FILENAME); + // validate that some of the defaults are available. + std::map known_grids = manager->getKnownGrids(); + ensure_equals("Known grids is a string-string map of size 2", known_grids.size(), 2); + ensure_equals("Agni has the right name and label", + known_grids[std::string("util.agni.lindenlab.com")], + std::string("Second Life Main Grid (Agni)")); + ensure_equals("Aditi has the right name and label", + known_grids[std::string("util.aditi.lindenlab.com")], + std::string("Second Life Beta Test Grid (Aditi)")); + ensure_equals("name for agni", + LLGridManager::getInstance()->getGrid("util.agni.lindenlab.com"), + std::string("util.agni.lindenlab.com")); + ensure_equals("id for agni", + std::string("Agni"), + LLGridManager::getInstance()->getGridId("util.agni.lindenlab.com")); + ensure_equals("update url base for Agni", // relies on agni being the default + std::string("https://update.secondlife.com/update"), + LLGridManager::getInstance()->getUpdateServiceURL()); + ensure_equals("label for agni", + LLGridManager::getInstance()->getGridLabel("util.agni.lindenlab.com"), + std::string("Second Life Main Grid (Agni)")); + + std::vector login_uris; + LLGridManager::getInstance()->getLoginURIs(std::string("util.agni.lindenlab.com"), login_uris); + ensure_equals("Number of login uris for agni", 1, login_uris.size()); + ensure_equals("Agni login uri", + login_uris[0], + std::string("https://login.agni.lindenlab.com/cgi-bin/login.cgi")); + ensure_equals("Agni helper uri", + LLGridManager::getInstance()->getHelperURI("util.agni.lindenlab.com"), + std::string("https://secondlife.com/helpers/")); + ensure_equals("Agni login page", + LLGridManager::getInstance()->getLoginPage("util.agni.lindenlab.com"), + std::string("https://viewer-splash.secondlife.com/")); + ensure("Agni is a system grid", + LLGridManager::getInstance()->isSystemGrid("util.agni.lindenlab.com")); + + ensure_equals("name for aditi", + LLGridManager::getInstance()->getGrid("util.aditi.lindenlab.com"), + std::string("util.aditi.lindenlab.com")); + ensure_equals("id for aditi", + LLGridManager::getInstance()->getGridId("util.aditi.lindenlab.com"), + std::string("Aditi")); + ensure_equals("label for aditi", + LLGridManager::getInstance()->getGridLabel("util.aditi.lindenlab.com"), + std::string("Second Life Beta Test Grid (Aditi)")); + + LLGridManager::getInstance()->getLoginURIs(std::string("util.aditi.lindenlab.com"), login_uris); + + ensure_equals("Number of login uris for aditi", 1, login_uris.size()); + ensure_equals("Aditi login uri", + login_uris[0], + std::string("https://login.aditi.lindenlab.com/cgi-bin/login.cgi")); + ensure_equals("Aditi helper uri", + LLGridManager::getInstance()->getHelperURI("util.aditi.lindenlab.com"), + std::string("https://secondlife.aditi.lindenlab.com/helpers/")); + ensure_equals("Aditi login page", + LLGridManager::getInstance()->getLoginPage("util.aditi.lindenlab.com"), + std::string("https://viewer-splash.secondlife.com/")); + ensure("Aditi is a system grid", + LLGridManager::getInstance()->isSystemGrid("util.aditi.lindenlab.com")); + } + + // initialization with a grid file + template<> template<> + void viewerNetworkTestObject::test<2>() + { + llofstream gridfile(TEST_FILENAME); + gridfile << gSampleGridFile; + gridfile.close(); + + LLGridManager::getInstance()->initialize(TEST_FILENAME); + std::map known_grids = LLGridManager::getInstance()->getKnownGrids(); + ensure_equals("adding a grid via a grid file increases known grid size",4, + known_grids.size()); + + // Verify that Agni and Aditi were not overwritten + ensure_equals("Agni has the right name and label", + known_grids[std::string("util.agni.lindenlab.com")], + std::string("Second Life Main Grid (Agni)")); + ensure_equals("Aditi has the right name and label", + known_grids[std::string("util.aditi.lindenlab.com")], + std::string("Second Life Beta Test Grid (Aditi)")); + ensure_equals("name for agni", + LLGridManager::getInstance()->getGrid("util.agni.lindenlab.com"), + std::string("util.agni.lindenlab.com")); + ensure_equals("id for agni", + LLGridManager::getInstance()->getGridId("util.agni.lindenlab.com"), + std::string("Agni")); + ensure_equals("update url base for Agni", // relies on agni being the default + std::string("https://update.secondlife.com/update"), + LLGridManager::getInstance()->getUpdateServiceURL()); + ensure_equals("label for agni", + LLGridManager::getInstance()->getGridLabel("util.agni.lindenlab.com"), + std::string("Second Life Main Grid (Agni)")); + std::vector login_uris; + LLGridManager::getInstance()->getLoginURIs(std::string("util.agni.lindenlab.com"), login_uris); + ensure_equals("Number of login uris for agni", 1, login_uris.size()); + ensure_equals("Agni login uri", + login_uris[0], + std::string("https://login.agni.lindenlab.com/cgi-bin/login.cgi")); + ensure_equals("Agni helper uri", + LLGridManager::getInstance()->getHelperURI("util.agni.lindenlab.com"), + std::string("https://secondlife.com/helpers/")); + ensure_equals("Agni login page", + LLGridManager::getInstance()->getLoginPage("util.agni.lindenlab.com"), + std::string("https://viewer-splash.secondlife.com/")); + ensure("Agni is a system grid", + LLGridManager::getInstance()->isSystemGrid("util.agni.lindenlab.com")); + + ensure_equals("name for aditi", + LLGridManager::getInstance()->getGrid("util.aditi.lindenlab.com"), + std::string("util.aditi.lindenlab.com")); + ensure_equals("id for aditi", + LLGridManager::getInstance()->getGridId("util.aditi.lindenlab.com"), + std::string("Aditi")); + ensure_equals("label for aditi", + LLGridManager::getInstance()->getGridLabel("util.aditi.lindenlab.com"), + std::string("Second Life Beta Test Grid (Aditi)")); + + LLGridManager::getInstance()->getLoginURIs(std::string("util.aditi.lindenlab.com"), login_uris); + ensure_equals("Number of login uris for aditi", 1, login_uris.size()); + ensure_equals("Aditi login uri", + login_uris[0], + std::string("https://login.aditi.lindenlab.com/cgi-bin/login.cgi")); + ensure_equals("Aditi helper uri", + LLGridManager::getInstance()->getHelperURI("util.aditi.lindenlab.com"), + std::string("https://secondlife.aditi.lindenlab.com/helpers/")); + ensure_equals("Aditi login page", + LLGridManager::getInstance()->getLoginPage("util.aditi.lindenlab.com"), + std::string("https://viewer-splash.secondlife.com/")); + ensure("Aditi is a system grid", + LLGridManager::getInstance()->isSystemGrid("util.aditi.lindenlab.com")); + + // Check the additional grid from the file + ensure_equals("alternative grid is in name<->label map", + known_grids["altgrid.long.name"], + std::string("Alternative Grid")); + ensure_equals("alternative grid name is set", + LLGridManager::getInstance()->getGrid("altgrid.long.name"), + std::string("altgrid.long.name")); + ensure_equals("alternative grid id", + LLGridManager::getInstance()->getGridId("altgrid.long.name"), + std::string("AltGrid")); + ensure_equals("alternative grid label", + LLGridManager::getInstance()->getGridLabel("altgrid.long.name"), + std::string("Alternative Grid")); + std::vector alt_login_uris; + LLGridManager::getInstance()->getLoginURIs(std::string("altgrid.long.name"), alt_login_uris); + ensure_equals("Number of login uris for altgrid", 2, alt_login_uris.size()); + ensure_equals("alternative grid first login uri", + alt_login_uris[0], + std::string("altgrid/myloginuri1")); + ensure_equals("alternative grid second login uri", + alt_login_uris[1], + std::string("altgrid/myloginuri2")); + ensure_equals("alternative grid helper uri", + LLGridManager::getInstance()->getHelperURI("altgrid.long.name"), + std::string("https://helper1/helpers/")); + ensure_equals("alternative grid login page", + LLGridManager::getInstance()->getLoginPage("altgrid.long.name"), + std::string("altgrid/loginpage")); + ensure("alternative grid is NOT a system grid", + ! LLGridManager::getInstance()->isSystemGrid("altgrid.long.name")); + + ensure_equals("minimal grid is in name<->label map", + known_grids["minimal.long.name"], + std::string("minimal.long.name")); + ensure_equals("minimal grid name is set", + LLGridManager::getInstance()->getGrid("minimal.long.name"), + std::string("minimal.long.name")); + ensure_equals("minimal grid id", + LLGridManager::getInstance()->getGridId("minimal.long.name"), + std::string("minimal.long.name")); + ensure_equals("minimal grid label", + LLGridManager::getInstance()->getGridLabel("minimal.long.name"), + std::string("minimal.long.name")); + + LLGridManager::getInstance()->getLoginURIs(std::string("minimal.long.name"), alt_login_uris); + ensure_equals("Number of login uris for altgrid", 1, alt_login_uris.size()); + ensure_equals("minimal grid login uri", + alt_login_uris[0], + std::string("https://minimal.long.name/cgi-bin/login.cgi")); + ensure_equals("minimal grid helper uri", + LLGridManager::getInstance()->getHelperURI("minimal.long.name"), + std::string("https://minimal.long.name/helpers/")); + ensure_equals("minimal grid login page", + LLGridManager::getInstance()->getLoginPage("minimal.long.name"), + std::string("http://minimal.long.name/app/login/")); + + } + + + // validate grid selection + template<> template<> + void viewerNetworkTestObject::test<7>() + { + // adding a grid with simply a name will populate the values. + llofstream gridfile(TEST_FILENAME); + gridfile << gSampleGridFile; + gridfile.close(); + + LLGridManager::getInstance()->initialize(TEST_FILENAME); + + LLGridManager::getInstance()->setGridChoice("util.agni.lindenlab.com"); + ensure_equals("getGridLabel", + LLGridManager::getInstance()->getGridLabel(), + std::string("Second Life Main Grid (Agni)")); + ensure_equals("getGridId", + LLGridManager::getInstance()->getGridId(), + std::string("Agni")); + ensure_equals("getGrid", + LLGridManager::getInstance()->getGrid(), + std::string("util.agni.lindenlab.com")); + ensure_equals("getHelperURI", + LLGridManager::getInstance()->getHelperURI(), + std::string("https://secondlife.com/helpers/")); + ensure_equals("getLoginPage", + LLGridManager::getInstance()->getLoginPage(), + std::string("https://viewer-splash.secondlife.com/")); + ensure_equals("update url base for Agni", // relies on agni being the default + std::string("https://update.secondlife.com/update"), + LLGridManager::getInstance()->getUpdateServiceURL()); + ensure("Is Agni a production grid", LLGridManager::getInstance()->isInProductionGrid()); + std::vector uris; + LLGridManager::getInstance()->getLoginURIs(uris); + ensure_equals("getLoginURIs size", 1, uris.size()); + ensure_equals("getLoginURIs", + uris[0], + std::string("https://login.agni.lindenlab.com/cgi-bin/login.cgi")); + + LLGridManager::getInstance()->setGridChoice("altgrid.long.name"); + ensure_equals("getGridLabel", + LLGridManager::getInstance()->getGridLabel(), + std::string("Alternative Grid")); + ensure_equals("getGridId", + LLGridManager::getInstance()->getGridId(), + std::string("AltGrid")); + ensure("alternative grid is not a system grid", + !LLGridManager::getInstance()->isSystemGrid()); + ensure("alternative grid is not a production grid", + !LLGridManager::getInstance()->isInProductionGrid()); + } + +} diff --git a/indra/newview/tests/llworldmap_test.cpp b/indra/newview/tests/llworldmap_test.cpp index 6245198888..8564dbeeb6 100644 --- a/indra/newview/tests/llworldmap_test.cpp +++ b/indra/newview/tests/llworldmap_test.cpp @@ -1,515 +1,515 @@ -/** - * @file llworldmap_test.cpp - * @author Merov Linden - * @date 2009-03-09 - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -// Dependencies -#include "linden_common.h" -#include "llapr.h" -#include "llsingleton.h" -#include "lltrans.h" -#include "lluistring.h" -#include "../llviewertexture.h" -#include "../llworldmapmessage.h" -// Class to test -#include "../llworldmap.h" -// Tut header -#include "../test/lltut.h" - -// ------------------------------------------------------------------------------------------- -// Stubbing: Declarations required to link and run the class being tested -// Notes: -// * Add here stubbed implementation of the few classes and methods used in the class to be tested -// * Add as little as possible (let the link errors guide you) -// * Do not make any assumption as to how those classes or methods work (i.e. don't copy/paste code) -// * A simulator for a class can be implemented here. Please comment and document thoroughly. - -// Stub image calls -void LLGLTexture::setBoostLevel(S32 ) { } -void LLGLTexture::setAddressMode(LLTexUnit::eTextureAddressMode ) { } -LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTexture(const LLUUID&, FTType, bool, LLGLTexture::EBoostLevel, S8, - LLGLint, LLGLenum, LLHost ) { return NULL; } - -// Stub related map calls -LLWorldMapMessage::LLWorldMapMessage() { } -LLWorldMapMessage::~LLWorldMapMessage() { } -void LLWorldMapMessage::sendItemRequest(U32 type, U64 handle) { } -void LLWorldMapMessage::sendMapBlockRequest(U16 min_x, U16 min_y, U16 max_x, U16 max_y, bool return_nonexistent) { } - -LLWorldMipmap::LLWorldMipmap() { } -LLWorldMipmap::~LLWorldMipmap() { } -void LLWorldMipmap::reset() { } -void LLWorldMipmap::dropBoostLevels() { } -void LLWorldMipmap::equalizeBoostLevels() { } -LLPointer LLWorldMipmap::getObjectsTile(U32 grid_x, U32 grid_y, S32 level, bool load) { return NULL; } - -// Stub other stuff -std::string LLTrans::getString(const std::string &, const LLStringUtil::format_map_t&, bool def_string) { return std::string("test_trans"); } -void LLUIString::updateResult() const { } -void LLUIString::setArg(const std::string& , const std::string& ) { } -void LLUIString::assign(const std::string& ) { } - -// End Stubbing -// ------------------------------------------------------------------------------------------- - -// ------------------------------------------------------------------------------------------- -// TUT -// ------------------------------------------------------------------------------------------- - -const F32 X_WORLD_TEST = 1000.0f * REGION_WIDTH_METERS; -const F32 Y_WORLD_TEST = 2000.0f * REGION_WIDTH_METERS; -const F32 Z_WORLD_TEST = 240.0f; -const std::string ITEM_NAME_TEST = "Item Foo"; -const std::string TOOLTIP_TEST = "Tooltip Foo"; - -const std::string SIM_NAME_TEST = "Sim Foo"; - -namespace tut -{ - // Test wrapper declarations - struct iteminfo_test - { - // Instance to be tested - LLItemInfo* mItem; - - // Constructor and destructor of the test wrapper - iteminfo_test() - { - LLUUID id; - mItem = new LLItemInfo(X_WORLD_TEST, Y_WORLD_TEST, ITEM_NAME_TEST, id); - } - ~iteminfo_test() - { - delete mItem; - } - }; - - struct siminfo_test - { - // Instance to be tested - LLSimInfo* mSim; - - // Constructor and destructor of the test wrapper - siminfo_test() - { - U64 handle = to_region_handle_global(X_WORLD_TEST, Y_WORLD_TEST); - mSim = new LLSimInfo(handle); - } - ~siminfo_test() - { - delete mSim; - } - }; - - struct worldmap_test - { - // Instance to be tested - LLWorldMap* mWorld; - - // Constructor and destructor of the test wrapper - worldmap_test() - { - mWorld = LLWorldMap::getInstance(); - } - ~worldmap_test() - { - mWorld = NULL; - } - }; - - // Tut templating thingamagic: test group, object and test instance - typedef test_group iteminfo_t; - typedef iteminfo_t::object iteminfo_object_t; - tut::iteminfo_t tut_iteminfo("LLItemInfo"); - - typedef test_group siminfo_t; - typedef siminfo_t::object siminfo_object_t; - tut::siminfo_t tut_siminfo("LLSimInfo"); - - typedef test_group worldmap_t; - typedef worldmap_t::object worldmap_object_t; - tut::worldmap_t tut_worldmap("LLWorldMap"); - - // --------------------------------------------------------------------------------------- - // Test functions - // Notes: - // * Test as many as you possibly can without requiring a full blown simulation of everything - // * The tests are executed in sequence so the test instance state may change between calls - // * Remember that you cannot test private methods with tut - // --------------------------------------------------------------------------------------- - - // --------------------------------------------------------------------------------------- - // Test the LLItemInfo interface - // --------------------------------------------------------------------------------------- - template<> template<> - void iteminfo_object_t::test<1>() - { - // Test 1 : setCount() / getCount() - mItem->setCount(10); - ensure("LLItemInfo::setCount() test failed", mItem->getCount() == 10); - // Test 2 : setTooltip() / getToolTip() - std::string tooltip = TOOLTIP_TEST; - mItem->setTooltip(tooltip); - ensure("LLItemInfo::setTooltip() test failed", mItem->getToolTip() == TOOLTIP_TEST); - // Test 3 : setElevation() / getGlobalPosition() - mItem->setElevation(Z_WORLD_TEST); - LLVector3d pos = mItem->getGlobalPosition(); - LLVector3d ref(X_WORLD_TEST, Y_WORLD_TEST, Z_WORLD_TEST); - ensure("LLItemInfo::getGlobalPosition() test failed", pos == ref); - // Test 4 : getName() - std::string name = mItem->getName(); - ensure("LLItemInfo::getName() test failed", name == ITEM_NAME_TEST); - // Test 5 : isName() - ensure("LLItemInfo::isName() test failed", mItem->isName(name)); - // Test 6 : getUUID() - LLUUID id; - ensure("LLItemInfo::getUUID() test failed", mItem->getUUID() == id); - // Test 7 : getRegionHandle() - U64 handle = to_region_handle_global(X_WORLD_TEST, Y_WORLD_TEST); - ensure("LLItemInfo::getRegionHandle() test failed", mItem->getRegionHandle() == handle); - } - // --------------------------------------------------------------------------------------- - // Test the LLSimInfo interface - // --------------------------------------------------------------------------------------- - // Test Setters and Accessors methods - template<> template<> - void siminfo_object_t::test<1>() - { - // Test 1 : setName() / getName() - std::string name = SIM_NAME_TEST; - mSim->setName(name); - ensure("LLSimInfo::setName() test failed", mSim->getName() == SIM_NAME_TEST); - // Test 2 : isName() - ensure("LLSimInfo::isName() test failed", mSim->isName(name)); - // Test 3 : getGlobalPos() - LLVector3 local; - LLVector3d ref(X_WORLD_TEST, Y_WORLD_TEST, 0.0f); - LLVector3d pos = mSim->getGlobalPos(local); - ensure("LLSimInfo::getGlobalPos() test failed", pos == ref); - // Test 4 : getGlobalOrigin() - pos = mSim->getGlobalOrigin(); - ensure("LLSimInfo::getGlobalOrigin() test failed", pos == ref); - // Test 5 : clearImage() - try { - mSim->clearImage(); - } catch (...) { - fail("LLSimInfo::clearImage() test failed"); - } - // Test 6 : dropImagePriority() - try { - mSim->dropImagePriority(); - } catch (...) { - fail("LLSimInfo::dropImagePriority() test failed"); - } - // Test 7 : updateAgentCount() - try { - mSim->updateAgentCount(0.0f); - } catch (...) { - fail("LLSimInfo::updateAgentCount() test failed"); - } - // Test 8 : getAgentCount() - S32 agents = mSim->getAgentCount(); - ensure("LLSimInfo::getAgentCount() test failed", agents == 0); - // Test 9 : setLandForSaleImage() / getLandForSaleImage() - LLUUID id; - mSim->setLandForSaleImage(id); - LLPointer image = mSim->getLandForSaleImage(); - ensure("LLSimInfo::getLandForSaleImage() test failed", image.isNull()); - // Test 10 : isPG() - mSim->setAccess(SIM_ACCESS_PG); - ensure("LLSimInfo::isPG() test failed", mSim->isPG()); - // Test 11 : isDown() - mSim->setAccess(SIM_ACCESS_DOWN); - ensure("LLSimInfo::isDown() test failed", mSim->isDown()); - // Test 12 : Access strings can't be accessed from unit test... - //ensure("LLSimInfo::getAccessString() test failed", mSim->getAccessString() == "Offline"); - // Test 13 : Region strings can't be accessed from unit test... - //mSim->setRegionFlags(REGION_FLAGS_SANDBOX); - //ensure("LLSimInfo::setRegionFlags() test failed", mSim->getFlagsString() == "Sandbox"); - } - // Test management of LLInfoItem lists - template<> template<> - void siminfo_object_t::test<2>() - { - // Test 14 : clearItems() - try { - mSim->clearItems(); - } catch (...) { - fail("LLSimInfo::clearItems() at init test failed"); - } - - // Test 15 : Verify that all the lists are empty - LLSimInfo::item_info_list_t list; - list = mSim->getTeleHub(); - ensure("LLSimInfo::getTeleHub() empty at init test failed", list.empty()); - list = mSim->getInfoHub(); - ensure("LLSimInfo::getInfoHub() empty at init test failed", list.empty()); - list = mSim->getPGEvent(); - ensure("LLSimInfo::getPGEvent() empty at init test failed", list.empty()); - list = mSim->getMatureEvent(); - ensure("LLSimInfo::getMatureEvent() empty at init test failed", list.empty()); - list = mSim->getLandForSale(); - ensure("LLSimInfo::getLandForSale() empty at init test failed", list.empty()); - list = mSim->getAgentLocation(); - ensure("LLSimInfo::getAgentLocation() empty at init test failed", list.empty()); - - // Create an item to be inserted - LLUUID id; - LLItemInfo item(X_WORLD_TEST, Y_WORLD_TEST, ITEM_NAME_TEST, id); - - // Insert the item in each list - mSim->insertTeleHub(item); - mSim->insertInfoHub(item); - mSim->insertPGEvent(item); - mSim->insertMatureEvent(item); - mSim->insertLandForSale(item); - mSim->insertAgentLocation(item); - - // Test 16 : Verify that the lists contain 1 item each - list = mSim->getTeleHub(); - ensure("LLSimInfo::insertTeleHub() test failed", list.size() == 1); - list = mSim->getInfoHub(); - ensure("LLSimInfo::insertInfoHub() test failed", list.size() == 1); - list = mSim->getPGEvent(); - ensure("LLSimInfo::insertPGEvent() test failed", list.size() == 1); - list = mSim->getMatureEvent(); - ensure("LLSimInfo::insertMatureEvent() test failed", list.size() == 1); - list = mSim->getLandForSale(); - ensure("LLSimInfo::insertLandForSale() test failed", list.size() == 1); - list = mSim->getAgentLocation(); - ensure("LLSimInfo::insertAgentLocation() test failed", list.size() == 1); - - // Test 17 : clearItems() - try { - mSim->clearItems(); - } catch (...) { - fail("LLSimInfo::clearItems() at end test failed"); - } - - // Test 18 : Verify that all the lists are empty again... *except* agent which is persisted!! (on purpose) - list = mSim->getTeleHub(); - ensure("LLSimInfo::getTeleHub() empty after clear test failed", list.empty()); - list = mSim->getInfoHub(); - ensure("LLSimInfo::getInfoHub() empty after clear test failed", list.empty()); - list = mSim->getPGEvent(); - ensure("LLSimInfo::getPGEvent() empty after clear test failed", list.empty()); - list = mSim->getMatureEvent(); - ensure("LLSimInfo::getMatureEvent() empty after clear test failed", list.empty()); - list = mSim->getLandForSale(); - ensure("LLSimInfo::getLandForSale() empty after clear test failed", list.empty()); - list = mSim->getAgentLocation(); - ensure("LLSimInfo::getAgentLocation() empty after clear test failed", list.size() == 1); - } - - // --------------------------------------------------------------------------------------- - // Test the LLWorldMap interface - // --------------------------------------------------------------------------------------- - // Test Setters and Accessors methods - template<> template<> - void worldmap_object_t::test<1>() - { - // Test 1 : reset() - try { - mWorld->reset(); - } catch (...) { - fail("LLWorldMap::reset() at init test failed"); - } - // Test 2 : clearImageRefs() - try { - mWorld->clearImageRefs(); - } catch (...) { - fail("LLWorldMap::clearImageRefs() test failed"); - } - // Test 3 : dropImagePriorities() - try { - mWorld->dropImagePriorities(); - } catch (...) { - fail("LLWorldMap::dropImagePriorities() test failed"); - } - // Test 4 : reloadItems() - try { - mWorld->reloadItems(true); - } catch (...) { - fail("LLWorldMap::reloadItems() test failed"); - } - // Test 5 : updateRegions() - try { - mWorld->updateRegions(1000, 1000, 1004, 1004); - } catch (...) { - fail("LLWorldMap::updateRegions() test failed"); - } - // Test 6 : equalizeBoostLevels() - try { - mWorld->equalizeBoostLevels(); - } catch (...) { - fail("LLWorldMap::equalizeBoostLevels() test failed"); - } - // Test 7 : getObjectsTile() - try { - LLPointer image = mWorld->getObjectsTile((U32)(X_WORLD_TEST/REGION_WIDTH_METERS), (U32)(Y_WORLD_TEST/REGION_WIDTH_METERS), 1); - ensure("LLWorldMap::getObjectsTile() failed", image.isNull()); - } catch (...) { - fail("LLWorldMap::getObjectsTile() test failed with exception"); - } - } - // Test management of LLSimInfo lists - template<> template<> - void worldmap_object_t::test<2>() - { - // Test 8 : reset() - try { - mWorld->reset(); - } catch (...) { - fail("LLWorldMap::reset() at init test failed"); - } - - // Test 9 : Verify that all the region list is empty - LLWorldMap::sim_info_map_t list; - list = mWorld->getRegionMap(); - ensure("LLWorldMap::getRegionMap() empty at init test failed", list.empty()); - - // Test 10 : Insert a region - bool success; - LLUUID id; - std::string name_sim = SIM_NAME_TEST; - success = mWorld->insertRegion( U32(X_WORLD_TEST), - U32(Y_WORLD_TEST), - name_sim, - id, - SIM_ACCESS_PG, - REGION_FLAGS_SANDBOX); - list = mWorld->getRegionMap(); - ensure("LLWorldMap::insertRegion() failed", success && (list.size() == 1)); - - // Test 11 : Insert an item in the same region -> number of regions doesn't increase - std::string name_item = ITEM_NAME_TEST; - success = mWorld->insertItem( U32(X_WORLD_TEST + REGION_WIDTH_METERS/2), - U32(Y_WORLD_TEST + REGION_WIDTH_METERS/2), - name_item, - id, - MAP_ITEM_LAND_FOR_SALE, - 0, 0); - list = mWorld->getRegionMap(); - ensure("LLWorldMap::insertItem() in existing region failed", success && (list.size() == 1)); - - // Test 12 : Insert an item in another region -> number of regions increases - success = mWorld->insertItem( U32(X_WORLD_TEST + REGION_WIDTH_METERS*2), - U32(Y_WORLD_TEST + REGION_WIDTH_METERS*2), - name_item, - id, - MAP_ITEM_LAND_FOR_SALE, - 0, 0); - list = mWorld->getRegionMap(); - ensure("LLWorldMap::insertItem() in unexisting region failed", success && (list.size() == 2)); - - // Test 13 : simInfoFromPosGlobal() in region - LLVector3d pos1( X_WORLD_TEST + REGION_WIDTH_METERS*2 + REGION_WIDTH_METERS/2, - Y_WORLD_TEST + REGION_WIDTH_METERS*2 + REGION_WIDTH_METERS/2, - 0.0f); - LLSimInfo* sim; - sim = mWorld->simInfoFromPosGlobal(pos1); - ensure("LLWorldMap::simInfoFromPosGlobal() test on existing region failed", sim != NULL); - - // Test 14 : simInfoFromPosGlobal() outside region - LLVector3d pos2( X_WORLD_TEST + REGION_WIDTH_METERS*4 + REGION_WIDTH_METERS/2, - Y_WORLD_TEST + REGION_WIDTH_METERS*4 + REGION_WIDTH_METERS/2, - 0.0f); - sim = mWorld->simInfoFromPosGlobal(pos2); - ensure("LLWorldMap::simInfoFromPosGlobal() test outside region failed", sim == NULL); - - // Test 15 : simInfoFromName() - sim = mWorld->simInfoFromName(name_sim); - ensure("LLWorldMap::simInfoFromName() test on existing region failed", sim != NULL); - - // Test 16 : simInfoFromHandle() - U64 handle = to_region_handle_global(X_WORLD_TEST, Y_WORLD_TEST); - sim = mWorld->simInfoFromHandle(handle); - ensure("LLWorldMap::simInfoFromHandle() test on existing region failed", sim != NULL); - - // Test 17 : simNameFromPosGlobal() - LLVector3d pos3( X_WORLD_TEST + REGION_WIDTH_METERS/2, - Y_WORLD_TEST + REGION_WIDTH_METERS/2, - 0.0f); - success = mWorld->simNameFromPosGlobal(pos3, name_sim); - ensure("LLWorldMap::simNameFromPosGlobal() test on existing region failed", success && (name_sim == SIM_NAME_TEST)); - - // Test 18 : reset() - try { - mWorld->reset(); - } catch (...) { - fail("LLWorldMap::reset() at end test failed"); - } - - // Test 19 : Verify that all the region list is empty - list = mWorld->getRegionMap(); - ensure("LLWorldMap::getRegionMap() empty at end test failed", list.empty()); - } - // Test tracking - template<> template<> - void worldmap_object_t::test<3>() - { - // Point to track - LLVector3d pos( X_WORLD_TEST + REGION_WIDTH_METERS/2, Y_WORLD_TEST + REGION_WIDTH_METERS/2, Z_WORLD_TEST); - - // Test 20 : no tracking - mWorld->cancelTracking(); - ensure("LLWorldMap::cancelTracking() at begin test failed", mWorld->isTracking() == false); - - // Test 21 : set tracking - mWorld->setTracking(pos); - ensure("LLWorldMap::setTracking() failed", mWorld->isTracking() && !mWorld->isTrackingValidLocation()); - - // Test 22 : set click and commit flags - mWorld->setTrackingDoubleClick(); - ensure("LLWorldMap::setTrackingDoubleClick() failed", mWorld->isTrackingDoubleClick()); - mWorld->setTrackingCommit(); - ensure("LLWorldMap::setTrackingCommit() failed", mWorld->isTrackingCommit()); - - // Test 23 : in rectangle test - bool inRect = mWorld->isTrackingInRectangle( X_WORLD_TEST, Y_WORLD_TEST, - X_WORLD_TEST + REGION_WIDTH_METERS, - Y_WORLD_TEST + REGION_WIDTH_METERS); - ensure("LLWorldMap::isTrackingInRectangle() in rectangle failed", inRect); - inRect = mWorld->isTrackingInRectangle( X_WORLD_TEST + REGION_WIDTH_METERS, - Y_WORLD_TEST + REGION_WIDTH_METERS, - X_WORLD_TEST + 2 * REGION_WIDTH_METERS, - Y_WORLD_TEST + 2 * REGION_WIDTH_METERS); - ensure("LLWorldMap::isTrackingInRectangle() outside rectangle failed", !inRect); - - // Test 24 : set tracking to valid and invalid - mWorld->setTrackingValid(); - ensure("LLWorldMap::setTrackingValid() failed", mWorld->isTrackingValidLocation() && !mWorld->isTrackingInvalidLocation()); - mWorld->setTrackingInvalid(); - ensure("LLWorldMap::setTrackingInvalid() failed", !mWorld->isTrackingValidLocation() && mWorld->isTrackingInvalidLocation()); - - // Test 25 : getTrackedPositionGlobal() - LLVector3d res = mWorld->getTrackedPositionGlobal(); - ensure("LLWorldMap::getTrackedPositionGlobal() failed", res == pos); - - // Test 26 : reset tracking - mWorld->cancelTracking(); - ensure("LLWorldMap::cancelTracking() at end test failed", mWorld->isTracking() == false); - } -} +/** + * @file llworldmap_test.cpp + * @author Merov Linden + * @date 2009-03-09 + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Dependencies +#include "linden_common.h" +#include "llapr.h" +#include "llsingleton.h" +#include "lltrans.h" +#include "lluistring.h" +#include "../llviewertexture.h" +#include "../llworldmapmessage.h" +// Class to test +#include "../llworldmap.h" +// Tut header +#include "../test/lltut.h" + +// ------------------------------------------------------------------------------------------- +// Stubbing: Declarations required to link and run the class being tested +// Notes: +// * Add here stubbed implementation of the few classes and methods used in the class to be tested +// * Add as little as possible (let the link errors guide you) +// * Do not make any assumption as to how those classes or methods work (i.e. don't copy/paste code) +// * A simulator for a class can be implemented here. Please comment and document thoroughly. + +// Stub image calls +void LLGLTexture::setBoostLevel(S32 ) { } +void LLGLTexture::setAddressMode(LLTexUnit::eTextureAddressMode ) { } +LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTexture(const LLUUID&, FTType, bool, LLGLTexture::EBoostLevel, S8, + LLGLint, LLGLenum, LLHost ) { return NULL; } + +// Stub related map calls +LLWorldMapMessage::LLWorldMapMessage() { } +LLWorldMapMessage::~LLWorldMapMessage() { } +void LLWorldMapMessage::sendItemRequest(U32 type, U64 handle) { } +void LLWorldMapMessage::sendMapBlockRequest(U16 min_x, U16 min_y, U16 max_x, U16 max_y, bool return_nonexistent) { } + +LLWorldMipmap::LLWorldMipmap() { } +LLWorldMipmap::~LLWorldMipmap() { } +void LLWorldMipmap::reset() { } +void LLWorldMipmap::dropBoostLevels() { } +void LLWorldMipmap::equalizeBoostLevels() { } +LLPointer LLWorldMipmap::getObjectsTile(U32 grid_x, U32 grid_y, S32 level, bool load) { return NULL; } + +// Stub other stuff +std::string LLTrans::getString(const std::string &, const LLStringUtil::format_map_t&, bool def_string) { return std::string("test_trans"); } +void LLUIString::updateResult() const { } +void LLUIString::setArg(const std::string& , const std::string& ) { } +void LLUIString::assign(const std::string& ) { } + +// End Stubbing +// ------------------------------------------------------------------------------------------- + +// ------------------------------------------------------------------------------------------- +// TUT +// ------------------------------------------------------------------------------------------- + +const F32 X_WORLD_TEST = 1000.0f * REGION_WIDTH_METERS; +const F32 Y_WORLD_TEST = 2000.0f * REGION_WIDTH_METERS; +const F32 Z_WORLD_TEST = 240.0f; +const std::string ITEM_NAME_TEST = "Item Foo"; +const std::string TOOLTIP_TEST = "Tooltip Foo"; + +const std::string SIM_NAME_TEST = "Sim Foo"; + +namespace tut +{ + // Test wrapper declarations + struct iteminfo_test + { + // Instance to be tested + LLItemInfo* mItem; + + // Constructor and destructor of the test wrapper + iteminfo_test() + { + LLUUID id; + mItem = new LLItemInfo(X_WORLD_TEST, Y_WORLD_TEST, ITEM_NAME_TEST, id); + } + ~iteminfo_test() + { + delete mItem; + } + }; + + struct siminfo_test + { + // Instance to be tested + LLSimInfo* mSim; + + // Constructor and destructor of the test wrapper + siminfo_test() + { + U64 handle = to_region_handle_global(X_WORLD_TEST, Y_WORLD_TEST); + mSim = new LLSimInfo(handle); + } + ~siminfo_test() + { + delete mSim; + } + }; + + struct worldmap_test + { + // Instance to be tested + LLWorldMap* mWorld; + + // Constructor and destructor of the test wrapper + worldmap_test() + { + mWorld = LLWorldMap::getInstance(); + } + ~worldmap_test() + { + mWorld = NULL; + } + }; + + // Tut templating thingamagic: test group, object and test instance + typedef test_group iteminfo_t; + typedef iteminfo_t::object iteminfo_object_t; + tut::iteminfo_t tut_iteminfo("LLItemInfo"); + + typedef test_group siminfo_t; + typedef siminfo_t::object siminfo_object_t; + tut::siminfo_t tut_siminfo("LLSimInfo"); + + typedef test_group worldmap_t; + typedef worldmap_t::object worldmap_object_t; + tut::worldmap_t tut_worldmap("LLWorldMap"); + + // --------------------------------------------------------------------------------------- + // Test functions + // Notes: + // * Test as many as you possibly can without requiring a full blown simulation of everything + // * The tests are executed in sequence so the test instance state may change between calls + // * Remember that you cannot test private methods with tut + // --------------------------------------------------------------------------------------- + + // --------------------------------------------------------------------------------------- + // Test the LLItemInfo interface + // --------------------------------------------------------------------------------------- + template<> template<> + void iteminfo_object_t::test<1>() + { + // Test 1 : setCount() / getCount() + mItem->setCount(10); + ensure("LLItemInfo::setCount() test failed", mItem->getCount() == 10); + // Test 2 : setTooltip() / getToolTip() + std::string tooltip = TOOLTIP_TEST; + mItem->setTooltip(tooltip); + ensure("LLItemInfo::setTooltip() test failed", mItem->getToolTip() == TOOLTIP_TEST); + // Test 3 : setElevation() / getGlobalPosition() + mItem->setElevation(Z_WORLD_TEST); + LLVector3d pos = mItem->getGlobalPosition(); + LLVector3d ref(X_WORLD_TEST, Y_WORLD_TEST, Z_WORLD_TEST); + ensure("LLItemInfo::getGlobalPosition() test failed", pos == ref); + // Test 4 : getName() + std::string name = mItem->getName(); + ensure("LLItemInfo::getName() test failed", name == ITEM_NAME_TEST); + // Test 5 : isName() + ensure("LLItemInfo::isName() test failed", mItem->isName(name)); + // Test 6 : getUUID() + LLUUID id; + ensure("LLItemInfo::getUUID() test failed", mItem->getUUID() == id); + // Test 7 : getRegionHandle() + U64 handle = to_region_handle_global(X_WORLD_TEST, Y_WORLD_TEST); + ensure("LLItemInfo::getRegionHandle() test failed", mItem->getRegionHandle() == handle); + } + // --------------------------------------------------------------------------------------- + // Test the LLSimInfo interface + // --------------------------------------------------------------------------------------- + // Test Setters and Accessors methods + template<> template<> + void siminfo_object_t::test<1>() + { + // Test 1 : setName() / getName() + std::string name = SIM_NAME_TEST; + mSim->setName(name); + ensure("LLSimInfo::setName() test failed", mSim->getName() == SIM_NAME_TEST); + // Test 2 : isName() + ensure("LLSimInfo::isName() test failed", mSim->isName(name)); + // Test 3 : getGlobalPos() + LLVector3 local; + LLVector3d ref(X_WORLD_TEST, Y_WORLD_TEST, 0.0f); + LLVector3d pos = mSim->getGlobalPos(local); + ensure("LLSimInfo::getGlobalPos() test failed", pos == ref); + // Test 4 : getGlobalOrigin() + pos = mSim->getGlobalOrigin(); + ensure("LLSimInfo::getGlobalOrigin() test failed", pos == ref); + // Test 5 : clearImage() + try { + mSim->clearImage(); + } catch (...) { + fail("LLSimInfo::clearImage() test failed"); + } + // Test 6 : dropImagePriority() + try { + mSim->dropImagePriority(); + } catch (...) { + fail("LLSimInfo::dropImagePriority() test failed"); + } + // Test 7 : updateAgentCount() + try { + mSim->updateAgentCount(0.0f); + } catch (...) { + fail("LLSimInfo::updateAgentCount() test failed"); + } + // Test 8 : getAgentCount() + S32 agents = mSim->getAgentCount(); + ensure("LLSimInfo::getAgentCount() test failed", agents == 0); + // Test 9 : setLandForSaleImage() / getLandForSaleImage() + LLUUID id; + mSim->setLandForSaleImage(id); + LLPointer image = mSim->getLandForSaleImage(); + ensure("LLSimInfo::getLandForSaleImage() test failed", image.isNull()); + // Test 10 : isPG() + mSim->setAccess(SIM_ACCESS_PG); + ensure("LLSimInfo::isPG() test failed", mSim->isPG()); + // Test 11 : isDown() + mSim->setAccess(SIM_ACCESS_DOWN); + ensure("LLSimInfo::isDown() test failed", mSim->isDown()); + // Test 12 : Access strings can't be accessed from unit test... + //ensure("LLSimInfo::getAccessString() test failed", mSim->getAccessString() == "Offline"); + // Test 13 : Region strings can't be accessed from unit test... + //mSim->setRegionFlags(REGION_FLAGS_SANDBOX); + //ensure("LLSimInfo::setRegionFlags() test failed", mSim->getFlagsString() == "Sandbox"); + } + // Test management of LLInfoItem lists + template<> template<> + void siminfo_object_t::test<2>() + { + // Test 14 : clearItems() + try { + mSim->clearItems(); + } catch (...) { + fail("LLSimInfo::clearItems() at init test failed"); + } + + // Test 15 : Verify that all the lists are empty + LLSimInfo::item_info_list_t list; + list = mSim->getTeleHub(); + ensure("LLSimInfo::getTeleHub() empty at init test failed", list.empty()); + list = mSim->getInfoHub(); + ensure("LLSimInfo::getInfoHub() empty at init test failed", list.empty()); + list = mSim->getPGEvent(); + ensure("LLSimInfo::getPGEvent() empty at init test failed", list.empty()); + list = mSim->getMatureEvent(); + ensure("LLSimInfo::getMatureEvent() empty at init test failed", list.empty()); + list = mSim->getLandForSale(); + ensure("LLSimInfo::getLandForSale() empty at init test failed", list.empty()); + list = mSim->getAgentLocation(); + ensure("LLSimInfo::getAgentLocation() empty at init test failed", list.empty()); + + // Create an item to be inserted + LLUUID id; + LLItemInfo item(X_WORLD_TEST, Y_WORLD_TEST, ITEM_NAME_TEST, id); + + // Insert the item in each list + mSim->insertTeleHub(item); + mSim->insertInfoHub(item); + mSim->insertPGEvent(item); + mSim->insertMatureEvent(item); + mSim->insertLandForSale(item); + mSim->insertAgentLocation(item); + + // Test 16 : Verify that the lists contain 1 item each + list = mSim->getTeleHub(); + ensure("LLSimInfo::insertTeleHub() test failed", list.size() == 1); + list = mSim->getInfoHub(); + ensure("LLSimInfo::insertInfoHub() test failed", list.size() == 1); + list = mSim->getPGEvent(); + ensure("LLSimInfo::insertPGEvent() test failed", list.size() == 1); + list = mSim->getMatureEvent(); + ensure("LLSimInfo::insertMatureEvent() test failed", list.size() == 1); + list = mSim->getLandForSale(); + ensure("LLSimInfo::insertLandForSale() test failed", list.size() == 1); + list = mSim->getAgentLocation(); + ensure("LLSimInfo::insertAgentLocation() test failed", list.size() == 1); + + // Test 17 : clearItems() + try { + mSim->clearItems(); + } catch (...) { + fail("LLSimInfo::clearItems() at end test failed"); + } + + // Test 18 : Verify that all the lists are empty again... *except* agent which is persisted!! (on purpose) + list = mSim->getTeleHub(); + ensure("LLSimInfo::getTeleHub() empty after clear test failed", list.empty()); + list = mSim->getInfoHub(); + ensure("LLSimInfo::getInfoHub() empty after clear test failed", list.empty()); + list = mSim->getPGEvent(); + ensure("LLSimInfo::getPGEvent() empty after clear test failed", list.empty()); + list = mSim->getMatureEvent(); + ensure("LLSimInfo::getMatureEvent() empty after clear test failed", list.empty()); + list = mSim->getLandForSale(); + ensure("LLSimInfo::getLandForSale() empty after clear test failed", list.empty()); + list = mSim->getAgentLocation(); + ensure("LLSimInfo::getAgentLocation() empty after clear test failed", list.size() == 1); + } + + // --------------------------------------------------------------------------------------- + // Test the LLWorldMap interface + // --------------------------------------------------------------------------------------- + // Test Setters and Accessors methods + template<> template<> + void worldmap_object_t::test<1>() + { + // Test 1 : reset() + try { + mWorld->reset(); + } catch (...) { + fail("LLWorldMap::reset() at init test failed"); + } + // Test 2 : clearImageRefs() + try { + mWorld->clearImageRefs(); + } catch (...) { + fail("LLWorldMap::clearImageRefs() test failed"); + } + // Test 3 : dropImagePriorities() + try { + mWorld->dropImagePriorities(); + } catch (...) { + fail("LLWorldMap::dropImagePriorities() test failed"); + } + // Test 4 : reloadItems() + try { + mWorld->reloadItems(true); + } catch (...) { + fail("LLWorldMap::reloadItems() test failed"); + } + // Test 5 : updateRegions() + try { + mWorld->updateRegions(1000, 1000, 1004, 1004); + } catch (...) { + fail("LLWorldMap::updateRegions() test failed"); + } + // Test 6 : equalizeBoostLevels() + try { + mWorld->equalizeBoostLevels(); + } catch (...) { + fail("LLWorldMap::equalizeBoostLevels() test failed"); + } + // Test 7 : getObjectsTile() + try { + LLPointer image = mWorld->getObjectsTile((U32)(X_WORLD_TEST/REGION_WIDTH_METERS), (U32)(Y_WORLD_TEST/REGION_WIDTH_METERS), 1); + ensure("LLWorldMap::getObjectsTile() failed", image.isNull()); + } catch (...) { + fail("LLWorldMap::getObjectsTile() test failed with exception"); + } + } + // Test management of LLSimInfo lists + template<> template<> + void worldmap_object_t::test<2>() + { + // Test 8 : reset() + try { + mWorld->reset(); + } catch (...) { + fail("LLWorldMap::reset() at init test failed"); + } + + // Test 9 : Verify that all the region list is empty + LLWorldMap::sim_info_map_t list; + list = mWorld->getRegionMap(); + ensure("LLWorldMap::getRegionMap() empty at init test failed", list.empty()); + + // Test 10 : Insert a region + bool success; + LLUUID id; + std::string name_sim = SIM_NAME_TEST; + success = mWorld->insertRegion( U32(X_WORLD_TEST), + U32(Y_WORLD_TEST), + name_sim, + id, + SIM_ACCESS_PG, + REGION_FLAGS_SANDBOX); + list = mWorld->getRegionMap(); + ensure("LLWorldMap::insertRegion() failed", success && (list.size() == 1)); + + // Test 11 : Insert an item in the same region -> number of regions doesn't increase + std::string name_item = ITEM_NAME_TEST; + success = mWorld->insertItem( U32(X_WORLD_TEST + REGION_WIDTH_METERS/2), + U32(Y_WORLD_TEST + REGION_WIDTH_METERS/2), + name_item, + id, + MAP_ITEM_LAND_FOR_SALE, + 0, 0); + list = mWorld->getRegionMap(); + ensure("LLWorldMap::insertItem() in existing region failed", success && (list.size() == 1)); + + // Test 12 : Insert an item in another region -> number of regions increases + success = mWorld->insertItem( U32(X_WORLD_TEST + REGION_WIDTH_METERS*2), + U32(Y_WORLD_TEST + REGION_WIDTH_METERS*2), + name_item, + id, + MAP_ITEM_LAND_FOR_SALE, + 0, 0); + list = mWorld->getRegionMap(); + ensure("LLWorldMap::insertItem() in unexisting region failed", success && (list.size() == 2)); + + // Test 13 : simInfoFromPosGlobal() in region + LLVector3d pos1( X_WORLD_TEST + REGION_WIDTH_METERS*2 + REGION_WIDTH_METERS/2, + Y_WORLD_TEST + REGION_WIDTH_METERS*2 + REGION_WIDTH_METERS/2, + 0.0f); + LLSimInfo* sim; + sim = mWorld->simInfoFromPosGlobal(pos1); + ensure("LLWorldMap::simInfoFromPosGlobal() test on existing region failed", sim != NULL); + + // Test 14 : simInfoFromPosGlobal() outside region + LLVector3d pos2( X_WORLD_TEST + REGION_WIDTH_METERS*4 + REGION_WIDTH_METERS/2, + Y_WORLD_TEST + REGION_WIDTH_METERS*4 + REGION_WIDTH_METERS/2, + 0.0f); + sim = mWorld->simInfoFromPosGlobal(pos2); + ensure("LLWorldMap::simInfoFromPosGlobal() test outside region failed", sim == NULL); + + // Test 15 : simInfoFromName() + sim = mWorld->simInfoFromName(name_sim); + ensure("LLWorldMap::simInfoFromName() test on existing region failed", sim != NULL); + + // Test 16 : simInfoFromHandle() + U64 handle = to_region_handle_global(X_WORLD_TEST, Y_WORLD_TEST); + sim = mWorld->simInfoFromHandle(handle); + ensure("LLWorldMap::simInfoFromHandle() test on existing region failed", sim != NULL); + + // Test 17 : simNameFromPosGlobal() + LLVector3d pos3( X_WORLD_TEST + REGION_WIDTH_METERS/2, + Y_WORLD_TEST + REGION_WIDTH_METERS/2, + 0.0f); + success = mWorld->simNameFromPosGlobal(pos3, name_sim); + ensure("LLWorldMap::simNameFromPosGlobal() test on existing region failed", success && (name_sim == SIM_NAME_TEST)); + + // Test 18 : reset() + try { + mWorld->reset(); + } catch (...) { + fail("LLWorldMap::reset() at end test failed"); + } + + // Test 19 : Verify that all the region list is empty + list = mWorld->getRegionMap(); + ensure("LLWorldMap::getRegionMap() empty at end test failed", list.empty()); + } + // Test tracking + template<> template<> + void worldmap_object_t::test<3>() + { + // Point to track + LLVector3d pos( X_WORLD_TEST + REGION_WIDTH_METERS/2, Y_WORLD_TEST + REGION_WIDTH_METERS/2, Z_WORLD_TEST); + + // Test 20 : no tracking + mWorld->cancelTracking(); + ensure("LLWorldMap::cancelTracking() at begin test failed", mWorld->isTracking() == false); + + // Test 21 : set tracking + mWorld->setTracking(pos); + ensure("LLWorldMap::setTracking() failed", mWorld->isTracking() && !mWorld->isTrackingValidLocation()); + + // Test 22 : set click and commit flags + mWorld->setTrackingDoubleClick(); + ensure("LLWorldMap::setTrackingDoubleClick() failed", mWorld->isTrackingDoubleClick()); + mWorld->setTrackingCommit(); + ensure("LLWorldMap::setTrackingCommit() failed", mWorld->isTrackingCommit()); + + // Test 23 : in rectangle test + bool inRect = mWorld->isTrackingInRectangle( X_WORLD_TEST, Y_WORLD_TEST, + X_WORLD_TEST + REGION_WIDTH_METERS, + Y_WORLD_TEST + REGION_WIDTH_METERS); + ensure("LLWorldMap::isTrackingInRectangle() in rectangle failed", inRect); + inRect = mWorld->isTrackingInRectangle( X_WORLD_TEST + REGION_WIDTH_METERS, + Y_WORLD_TEST + REGION_WIDTH_METERS, + X_WORLD_TEST + 2 * REGION_WIDTH_METERS, + Y_WORLD_TEST + 2 * REGION_WIDTH_METERS); + ensure("LLWorldMap::isTrackingInRectangle() outside rectangle failed", !inRect); + + // Test 24 : set tracking to valid and invalid + mWorld->setTrackingValid(); + ensure("LLWorldMap::setTrackingValid() failed", mWorld->isTrackingValidLocation() && !mWorld->isTrackingInvalidLocation()); + mWorld->setTrackingInvalid(); + ensure("LLWorldMap::setTrackingInvalid() failed", !mWorld->isTrackingValidLocation() && mWorld->isTrackingInvalidLocation()); + + // Test 25 : getTrackedPositionGlobal() + LLVector3d res = mWorld->getTrackedPositionGlobal(); + ensure("LLWorldMap::getTrackedPositionGlobal() failed", res == pos); + + // Test 26 : reset tracking + mWorld->cancelTracking(); + ensure("LLWorldMap::cancelTracking() at end test failed", mWorld->isTracking() == false); + } +} diff --git a/indra/newview/tests/llworldmipmap_test.cpp b/indra/newview/tests/llworldmipmap_test.cpp index 7e1b432e1b..0a5de18e0f 100644 --- a/indra/newview/tests/llworldmipmap_test.cpp +++ b/indra/newview/tests/llworldmipmap_test.cpp @@ -1,165 +1,165 @@ -/** - * @file llworldmipmap_test.cpp - * @author Merov Linden - * @date 2009-02-03 - * - * $LicenseInfo:firstyear=2006&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; - * version 2.1 of the License only. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -// Dependencies -#include "linden_common.h" -#include "../llviewertexture.h" -#include "../llviewercontrol.h" -// Class to test -#include "../llworldmipmap.h" -// Tut header -#include "../test/lltut.h" - -// ------------------------------------------------------------------------------------------- -// Stubbing: Declarations required to link and run the class being tested -// Notes: -// * Add here stubbed implementation of the few classes and methods used in the class to be tested -// * Add as little as possible (let the link errors guide you) -// * Do not make any assumption as to how those classes or methods work (i.e. don't copy/paste code) -// * A simulator for a class can be implemented here. Please comment and document thoroughly. - -void LLGLTexture::setBoostLevel(S32 ) { } -LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTextureFromUrl(const std::string&, FTType, bool, LLGLTexture::EBoostLevel, S8, - LLGLint, LLGLenum, const LLUUID& ) { return NULL; } - -LLControlGroup::LLControlGroup(const std::string& name) : LLInstanceTracker(name) { } -LLControlGroup::~LLControlGroup() { } -std::string LLControlGroup::getString(std::string_view) { return std::string("test_url"); } -LLControlGroup gSavedSettings("test_settings"); - -// End Stubbing -// ------------------------------------------------------------------------------------------- - -// ------------------------------------------------------------------------------------------- -// TUT -// ------------------------------------------------------------------------------------------- -namespace tut -{ - // Test wrapper declaration - struct worldmipmap_test - { - // Derived test class - class LLTestWorldMipmap : public LLWorldMipmap - { - // Put here stubbs of virtual methods we shouldn't call all the way down - }; - // Instance to be tested - LLTestWorldMipmap* mMap; - - // Constructor and destructor of the test wrapper - worldmipmap_test() - { - mMap = new LLTestWorldMipmap; - } - ~worldmipmap_test() - { - delete mMap; - } - }; - - // Tut templating thingamagic: test group, object and test instance - typedef test_group worldmipmap_t; - typedef worldmipmap_t::object worldmipmap_object_t; - tut::worldmipmap_t tut_worldmipmap("LLWorldMipmap"); - - // --------------------------------------------------------------------------------------- - // Test functions - // Notes: - // * Test as many as you possibly can without requiring a full blown simulation of everything - // * The tests are executed in sequence so the test instance state may change between calls - // * Remember that you cannot test private methods with tut - // --------------------------------------------------------------------------------------- - // Test static methods - // Test 1 : scaleToLevel() - template<> template<> - void worldmipmap_object_t::test<1>() - { - S32 level = mMap->scaleToLevel(0.0); - ensure("scaleToLevel() test 1 failed", level == LLWorldMipmap::MAP_LEVELS); - level = mMap->scaleToLevel((F32)LLWorldMipmap::MAP_TILE_SIZE); - ensure("scaleToLevel() test 2 failed", level == 1); - level = mMap->scaleToLevel(10.f * LLWorldMipmap::MAP_TILE_SIZE); - ensure("scaleToLevel() test 3 failed", level == 1); - } - // Test 2 : globalToMipmap() - template<> template<> - void worldmipmap_object_t::test<2>() - { - U32 grid_x, grid_y; - mMap->globalToMipmap(1000.f*REGION_WIDTH_METERS, 1000.f*REGION_WIDTH_METERS, 1, &grid_x, &grid_y); - ensure("globalToMipmap() test 1 failed", (grid_x == 1000) && (grid_y == 1000)); - mMap->globalToMipmap(0.0, 0.0, LLWorldMipmap::MAP_LEVELS, &grid_x, &grid_y); - ensure("globalToMipmap() test 2 failed", (grid_x == 0) && (grid_y == 0)); - } - // Test 3 : getObjectsTile() - template<> template<> - void worldmipmap_object_t::test<3>() - { - // Depends on some inline methods in LLViewerImage... Thinking about how to make this work - // LLPointer img = mMap->getObjectsTile(0, 0, 1); - // ensure("getObjectsTile() test failed", img.isNull()); - } - // Test 4 : equalizeBoostLevels() - template<> template<> - void worldmipmap_object_t::test<4>() - { - try - { - mMap->equalizeBoostLevels(); - } - catch (...) - { - fail("equalizeBoostLevels() test failed"); - } - } - // Test 5 : dropBoostLevels() - template<> template<> - void worldmipmap_object_t::test<5>() - { - try - { - mMap->dropBoostLevels(); - } - catch (...) - { - fail("dropBoostLevels() test failed"); - } - } - // Test 6 : reset() - template<> template<> - void worldmipmap_object_t::test<6>() - { - try - { - mMap->reset(); - } - catch (...) - { - fail("reset() test failed"); - } - } -} +/** + * @file llworldmipmap_test.cpp + * @author Merov Linden + * @date 2009-02-03 + * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Dependencies +#include "linden_common.h" +#include "../llviewertexture.h" +#include "../llviewercontrol.h" +// Class to test +#include "../llworldmipmap.h" +// Tut header +#include "../test/lltut.h" + +// ------------------------------------------------------------------------------------------- +// Stubbing: Declarations required to link and run the class being tested +// Notes: +// * Add here stubbed implementation of the few classes and methods used in the class to be tested +// * Add as little as possible (let the link errors guide you) +// * Do not make any assumption as to how those classes or methods work (i.e. don't copy/paste code) +// * A simulator for a class can be implemented here. Please comment and document thoroughly. + +void LLGLTexture::setBoostLevel(S32 ) { } +LLViewerFetchedTexture* LLViewerTextureManager::getFetchedTextureFromUrl(const std::string&, FTType, bool, LLGLTexture::EBoostLevel, S8, + LLGLint, LLGLenum, const LLUUID& ) { return NULL; } + +LLControlGroup::LLControlGroup(const std::string& name) : LLInstanceTracker(name) { } +LLControlGroup::~LLControlGroup() { } +std::string LLControlGroup::getString(std::string_view) { return std::string("test_url"); } +LLControlGroup gSavedSettings("test_settings"); + +// End Stubbing +// ------------------------------------------------------------------------------------------- + +// ------------------------------------------------------------------------------------------- +// TUT +// ------------------------------------------------------------------------------------------- +namespace tut +{ + // Test wrapper declaration + struct worldmipmap_test + { + // Derived test class + class LLTestWorldMipmap : public LLWorldMipmap + { + // Put here stubbs of virtual methods we shouldn't call all the way down + }; + // Instance to be tested + LLTestWorldMipmap* mMap; + + // Constructor and destructor of the test wrapper + worldmipmap_test() + { + mMap = new LLTestWorldMipmap; + } + ~worldmipmap_test() + { + delete mMap; + } + }; + + // Tut templating thingamagic: test group, object and test instance + typedef test_group worldmipmap_t; + typedef worldmipmap_t::object worldmipmap_object_t; + tut::worldmipmap_t tut_worldmipmap("LLWorldMipmap"); + + // --------------------------------------------------------------------------------------- + // Test functions + // Notes: + // * Test as many as you possibly can without requiring a full blown simulation of everything + // * The tests are executed in sequence so the test instance state may change between calls + // * Remember that you cannot test private methods with tut + // --------------------------------------------------------------------------------------- + // Test static methods + // Test 1 : scaleToLevel() + template<> template<> + void worldmipmap_object_t::test<1>() + { + S32 level = mMap->scaleToLevel(0.0); + ensure("scaleToLevel() test 1 failed", level == LLWorldMipmap::MAP_LEVELS); + level = mMap->scaleToLevel((F32)LLWorldMipmap::MAP_TILE_SIZE); + ensure("scaleToLevel() test 2 failed", level == 1); + level = mMap->scaleToLevel(10.f * LLWorldMipmap::MAP_TILE_SIZE); + ensure("scaleToLevel() test 3 failed", level == 1); + } + // Test 2 : globalToMipmap() + template<> template<> + void worldmipmap_object_t::test<2>() + { + U32 grid_x, grid_y; + mMap->globalToMipmap(1000.f*REGION_WIDTH_METERS, 1000.f*REGION_WIDTH_METERS, 1, &grid_x, &grid_y); + ensure("globalToMipmap() test 1 failed", (grid_x == 1000) && (grid_y == 1000)); + mMap->globalToMipmap(0.0, 0.0, LLWorldMipmap::MAP_LEVELS, &grid_x, &grid_y); + ensure("globalToMipmap() test 2 failed", (grid_x == 0) && (grid_y == 0)); + } + // Test 3 : getObjectsTile() + template<> template<> + void worldmipmap_object_t::test<3>() + { + // Depends on some inline methods in LLViewerImage... Thinking about how to make this work + // LLPointer img = mMap->getObjectsTile(0, 0, 1); + // ensure("getObjectsTile() test failed", img.isNull()); + } + // Test 4 : equalizeBoostLevels() + template<> template<> + void worldmipmap_object_t::test<4>() + { + try + { + mMap->equalizeBoostLevels(); + } + catch (...) + { + fail("equalizeBoostLevels() test failed"); + } + } + // Test 5 : dropBoostLevels() + template<> template<> + void worldmipmap_object_t::test<5>() + { + try + { + mMap->dropBoostLevels(); + } + catch (...) + { + fail("dropBoostLevels() test failed"); + } + } + // Test 6 : reset() + template<> template<> + void worldmipmap_object_t::test<6>() + { + try + { + mMap->reset(); + } + catch (...) + { + fail("reset() test failed"); + } + } +} -- cgit v1.2.3